How to manipulate 2D plots?

137

132

When it comes to visual analysis, large datasets or data with intricate internal details often makes plotting in 2D useless, as the outcome is either just a fraction of the full dataset, or no details can be observed in the mess of datapoints. How can one make the process of changing the plot range and/or zooming, panning, etc. less tedious than doing it programmatically and iteratively, from time to time? I often meet with this issue, and developed various methods to deal with it (like this). Though I have now a working solution that I would like to share (see below), I also am highly interested in what kind of methods and tricks others invented to visualize and manipulate complex 2D data with ease.

István Zachar

Posted 2012-06-21T01:08:38.520

Reputation: 44 135

Answers

155

Completely redesigned at 2015 12 19! Simplified interface, more functionality, robust performance. Plots can now be easily uploaded to StackExchange from within PlotExplorer thanks to halirutan! Code is at end of post.


Functionality:

  • Works with any graphics object, plots, charts, etc. (e.g. Plot, ListPlot, ArrayPlot, RegionPlot, GeoGraphics, BarChart, you name it).
  • Can handle graphics with legend, if legend is within the frame of the graphics object.
  • Compatible with Manipulate, DynamicModule and similar functions.
  • Click anywhere + drag the mouse zooms in/out on the point where the mouse was clicked.
  • Ctrl + drag toggles zooming rectangle (from Szabolcs).
  • Shift + drag pans the plot (from Heike).
  • Alt shows coordinates (only when over the plot so that other global Alt-functionality (like Alt+C+L to remove output) can be still used). *)
  • Double click anywhere resets the plot (from kguler).
  • Resize handler at bottom right corner to manipulate ImageSize (does not change aspect ratio).
  • Ctrl + resize also changes aspect ratio.
  • Menu button appears in upper right corner when mouse is over. Some functionality is only available for certain graphics.
    • Replot: updates the iterator(s) of an iterator-based plot to comply with the current plot range. Only available for iterator-based plots holding their first argument where the iterator structure can be corresponded with the plot range, like Plot, DensityPlot, StreamPlot. DiscretePlot, ParametricPlot, PolarPlot and similar cannot be replotted as the iterator values for these functions cannot be deduced from plot ranges.
    • Linear, Log, LogLog, LogLinear: changes the scaling function for the plot. Only available for plots that accept the option ScalingFunctions. Note, that logarithmic scale cannot handle nonpositive plot ranges.
    • Copy actual PlotRange, ImageSize, AspectRatio to clipboard.
    • Copying, pasting, saving graphics expression or rasterized image version.
    • Upload image to StackExchange! Thanks to halirutan's help, it is now possible to directly call some SETools functions from under PlotExplorer, so you don't even have to open the SEUploader palette!
    • Open in external application: first exports rasterized image as TIFF to the system temporary directory and then calls SystemOpen to call for the default image-viewer application.
    • Extra functionality can be easily added to the Menu button, by adding to GraphicsButton in the code. Some functions that require a dynamically manipulable structure instead of the final graphics ojects (like Replot) are inserted into the button from within the DynamicModule of PlotExplorer.

Full set of commands in the context menu:

enter image description here

Limitations

  • Graphics editing functionality (i.e. when double-clicked) is suppressed.
  • If legend is positioned off the plot range (e.g. Placed[leg, After]), it cannot be displayed within the dynamic pane range. Workaround: supply legend that is positioned within the plot range (e.g. Placed[leg, Center]).
  • Replotting is only applicable when the plot is passed unevaluated (i.e. as PlotExplorer[Plot[...]] instead of PlotExplorer[plot] or PlotExplorer[Show[Plot[...], ...]]), as in the latter cases the iterator structure (and new iterator value(s)) cannot be deduced.
  • Replotting a non-deterministic plot yields different results every time.
  • A zoomed-in/out plot might look undersampled due to fixed sampling frequency. Workaround: use Replot.
  • A zoomed-in plot can be replotted, but it results in a smaller-than-original plot range. Zooming out does not revert to the original range automatically. Workaround: use Replot.
  • When using Manipulate/Dynamic/DynamicModule[PlotExplorer[Plot[...], ...], changes made byPlotExplorer` are discarded whenever the wrapper triggers a dynamic update (e.g. a slider is moved), and thus the plot reverts to the original.
  • When a plot is requested to be replotted in a domain where it is not defined, expect unwanted result, even error messages (e.g. extrapolation is used when InterpolatingFunction is plotted outside of its domain).

Known issues

  • BUG: In DensityPlot and GeoGraphics, any button object appears with a white background. Could not find a workaround.
  • MouseAppearance is not always displayed correctly.

Examples

Zoomin in-out by dragging plot. Double click to reset.

PlotExplorer@Plot[{Sin@x, Cos@x}, {x, 0, 100}]

enter image description here

Replotting.

PlotExplorer@ContourPlot[Sin[x y], {x, 0, 3}, {y, 0, 3}, 
     ColorFunction -> "BlueGreenYellow", PlotPoints -> 3]

enter image description here

Zooming rectangle (Ctrl + drag) and double click to reset.

PlotExplorer@ListLinePlot[Table[Accumulate[
    RandomReal[{-1, 1}, 250]], {3}], Filling -> Axis]

enter image description here

Accessing coordinates (Alt).

enter image description here

Resize and Ctrl + resize.

PlotExplorer@PlotExplorer@Graphics[
 {Red, Disk[], Green, Rectangle[{0, 0}, {2, 2}], Blue, Disk[{2, 2}]}]

enter image description here

Panning the plotted area using Shift + drag:

PlotExplorer@
 Plot[Evaluate@Table[BesselJ[n, x/50]*4, {n, 4}], {x, 0, 10000}, 
  Filling -> Axis, PlotRange -> {{0, 1000}, {-1, 1}}]

enter image description here

Code

GetPlotRange::usage = "GetPlotRange[gr] returns the actual unpadded plot range of \
graphics gr. GetPlotRange[gr, True] returns the actual padded plot \
range of gr. GetPlotRange can handle Graphics, Graphics3D and Graph \
objects.";
GetPlotRange[_[gr : (_Graphics | _Graphics3D | _Graph | _GeoGraphics), ___], pad_] := GetPlotRange[gr, pad];(*Handle Legended[\[Ellipsis]] and similar.*)
GetPlotRange[gr_GeoGraphics, False] := (PlotRange /. 
   Quiet@AbsoluteOptions@gr);(*TODO:does not handle PlotRangePadding.*)


GetPlotRange[gr_Graph, pad_: False] := 
 Charting`get2DPlotRange[gr, pad];
GetPlotRange[gr : (_Graphics | _Graphics3D), pad_: False] := 
  Module[{r = PlotRange /. Options@gr}, If[MatrixQ[r, NumericQ],
    (*TODO:does not handle PlotRangePadding*)r, 
    Last@Last@
      Reap@Rasterize[
        Show[gr, If[pad === False, PlotRangePadding -> None, {}], 
         Axes -> True, Frame -> False, 
         Ticks -> ((Sow@{##}; Automatic) &), 
         DisplayFunction -> Identity, ImageSize -> 0], 
        ImageResolution -> 1]]];


(* Joins and filters options, keeping the righmost if there are \
multiple instances of the same option. *)
filter[opts_List, head_] := 
  Reverse@DeleteDuplicatesBy[
    Reverse@FilterRules[Flatten@opts, First /@ Options@head], First];


(* Find and use SETools of Szabolcs & Halirutan *)
$SEToolsExist = 
  FileExistsQ@
   FileNameJoin@{$UserBaseDirectory, "Applications", "SETools", 
     "Java", "SETools.jar"};

(* If SETools exist, initiate JLink and include some functions *)
If[$SEToolsExist,
  Needs@"JLink`";
  JLink`InstallJava[];
  copyToClipboard[text_] := 
   Module[{nb}, nb = NotebookCreate[Visible -> False];
    NotebookWrite[nb, Cell[text, "Input"]];
    SelectionMove[nb, All, Notebook];
    FrontEndTokenExecute[nb, "Copy"];
    NotebookClose@nb;
    ];
  uploadButtonAction[img_] := 
   uploadButtonAction[img, "![Mathematica graphics](", ")"];
  uploadButtonAction[img_, wrapStart_String, wrapEnd_String] := 
   Module[{url},
    Check[url = stackImage@img, Return[]];
    copyToClipboard@(wrapStart <> url <> wrapEnd);
    ];
  stackImage::httperr = "Server returned respose code: `1`";
  stackImage::err = "Server returner error: `1`";
  stackImage::parseErr = "Could not parse the answer of the server.";
  stackImage[g_] := 
   Module[{url, client, method, data, partSource, part, entity, code, 
     response, error, result, parseXMLOutput}, 
    parseXMLOutput[str_String] := 
     Block[{xml = ImportString[str, {"HTML", "XMLObject"}], result}, 
      result = 
       Cases[xml, XMLElement["script", _, res_] :> StringTrim[res], 
         Infinity] /. {{s_String}} :> s;
      If[result =!= {} && StringMatchQ[result, "window.parent" ~~ __],
        Flatten@
        StringCases[result, 
         "window.parent." ~~ func__ ~~ "(" ~~ arg__ ~~ 
           ");" :> {StringMatchQ[func, "closeDialog"], 
           StringTrim[arg, "\""]}], $Failed]];
    parseXMLOutput[___] := $Failed;
    data = ExportString[g, "PNG"];
    JLink`JavaBlock[
     JLink`LoadJavaClass["de.halirutan.se.tools.SEUploader", 
      StaticsVisible -> True];
     response = 
      Check[SEUploader`sendImage@ToCharacterCode@data, 
       Return@$Failed]];
    If[response === $Failed, Return@$Failed];
    result = parseXMLOutput@response;
    If[result =!= $Failed,
     If[TrueQ@First@result, Last@result, 
      Message[stackImage::err, Last@result]; $Failed],
     Message[stackImage::parseErr]; $Failed]
    ];
  ];


GraphicsButton::usage = 
  "GraphicsButton[lbl, gr] represent a button that is labeled with \
lbl and offers functionality for the static graphics object gr. \
GraphicsButton[gr] uses a tiny version of gr as label.";
MenuItems::usage = 
  "MenuItems is an option for GraphicsButton that specifies \
additional label \[RuleDelayed] command pairs as a list to be \
included at the top of the action menu of GraphicsButton.";
Options[GraphicsButton] = 
  DeleteDuplicatesBy[
   Flatten@{MenuItems -> {}, RasterSize -> Automatic, 
     ColorSpace -> Automatic, ImageResolution -> 300, 
     Options@ActionMenu}, First];
GraphicsButton[expr_, opts : OptionsPattern[]] := 
  GraphicsButton[
   Pane[expr, ImageSize -> Small, ImageSizeAction -> "ShrinkToFit"], 
   expr, opts];
GraphicsButton[lbl_, expr_, opts : OptionsPattern[]] := 
  Module[{head, save, items = OptionValue@MenuItems, rasterizeOpts, 
    dir = $UserDocumentsDirectory, file = ""}, 
   rasterizeOpts = 
    filter[{Options@GraphicsButton, opts}, Options@Rasterize];
   save[format_] := (file = 
      SystemDialogInput["FileSave", 
       FileNameJoin@{dir, "." <> ToLowerCase@format}];
     If[file =!= $Failed && file =!= $Canceled, 
      dir = DirectoryName@file;
      Quiet@
       Export[file, 
        If[format === "NB", expr, 
         Rasterize[expr, "Image", rasterizeOpts]], format]]);
   head = Head@Unevaluated@expr;
   ActionMenu[lbl, Flatten@{
      If[items =!= {}, items, Nothing],
      "Copy expression" :> CopyToClipboard@expr,
      "Copy image" :> CopyToClipboard@Rasterize@expr,
      "Copy high-res image" :> 
       CopyToClipboard@Rasterize[expr, "Image", rasterizeOpts],
      "Paste expression" :> Paste@expr,
      "Paste image" :> Paste@Rasterize@expr,
      "Paste high-res image" :> 
       Paste@Rasterize[expr, "Image", rasterizeOpts],
      Delimiter,
      "Save as notebook\[Ellipsis]" :> save@"NB",
      "Save as JPEG\[Ellipsis]" :> save@"JPEG",
      "Save as TIFF\[Ellipsis]" :> save@"TIFF",
      "Save as BMP\[Ellipsis]" :> save@"BMP",
      Delimiter,
      Style["Upload image to StackExchange", 
        If[$SEToolsExist, Black, Gray]] :> 
       If[$SEToolsExist, uploadButtonAction@Rasterize@expr],
      "Open image in external application" :> 
       Module[{f = 
          Export[FileNameJoin@{$TemporaryDirectory, "temp_img.tiff"}, 
           Rasterize@expr, "TIFF"]}, 
        If[StringQ@f && FileExistsQ@f, SystemOpen@f]]
      }, filter[{Options@GraphicsButton, opts, {Method -> "Queued"}}, 
     Options@ActionMenu]]];


PlotExplorer::usage = 
  "PlotExplorer[plot] returns a manipulable version of plot. \
PlotExplorer can handle Graph and Graphics objects and plotting \
functions like Plot, LogPlot, ListPlot, DensityPlot, Streamplot, etc. \
PlotExplorer allows the modification of the plot range, image size \
and aspect ratio. If the supplied argument is a full specification of \
a plotting function holding its first argument (e.g. Plot) the result \
offers functionality to replot the function to the modified plot \
range. PlotExplorer has attribute HoldFirst.";
AppearanceFunction::usage = 
  "AppearanceFunction is an option for PlotExplorer that specifies \
the appearance function of the menu button. Use Automatic for the \
default appearance, Identity to display a classic button or None to \
omit the menu button.";
MenuPosition::usage = 
  "MenuPosition is an option for PlotExplorer that specifies the \
position of the (upper right corner of the) menu button within the \
graphics object.";
Attributes[PlotExplorer] = {HoldFirst};
Options[PlotExplorer] = {AppearanceFunction -> (Mouseover[
         Invisible@#, #] &@
       Framed[#, Background -> GrayLevel[.5, .5], RoundingRadius -> 5,
         FrameStyle -> None, Alignment -> {Center, Center}, 
        BaseStyle -> {FontFamily -> "Helvetica"}] &), 
   MenuPosition -> Scaled@{1, 1}};
PlotExplorer[expr_, rangeArg_: Automatic, sizeArg_: Automatic, 
   ratioArg_: Automatic, opts : OptionsPattern[]] := 
  Module[{plot = expr, held = Hold@expr, head, holdQ = True, 
    legQ = False, appearance, 
    position, $1Dplots = 
     Plot | LogPlot | LogLinearPlot | LogLogPlot, $2Dplots = 
     DensityPlot | ContourPlot | RegionPlot | StreamPlot | 
      StreamDensityPlot | VectorPlot | VectorDensityPlot | 
      LineIntegralConvolutionPlot | GeoGraphics}, head = held[[1, 0]];
   If[head === Symbol, holdQ = False; head = Head@expr];
   If[head === Legended, legQ = True;
    If[holdQ, held = held /. Legended[x_, ___] :> x;
     head = held[[1, 0]], head = Head@First@expr]];
   holdQ = holdQ && MatchQ[head, $1Dplots | $2Dplots];
   If[! holdQ, legQ = Head@expr === Legended;
    head = If[legQ, Head@First@expr, Head@expr]];
   If[Not@MatchQ[head, $1Dplots | $2Dplots | Graphics | Graph], expr, 
    DynamicModule[{dyn, gr, leg, replot, rescale, new, mid, len, 
      min = 0.1, f = {1, 1}, set, d, epilog, over = False, defRange, 
      range, size, ratio, pt1, pt1sc = None, pt2 = None, pt2sc = None,
       rect, button}, {gr, leg} = If[legQ, List @@ plot, {plot, None}];
     size = 
      If[sizeArg === Automatic, Rasterize[gr, "RasterSize"], 
       Setting@sizeArg];
     defRange = 
      If[rangeArg === Automatic, GetPlotRange[gr, False], 
       Setting@rangeArg];
     ratio = 
      If[ratioArg === Automatic, Divide @@ Reverse@size, 
       Setting@ratioArg];
     epilog = Epilog /. Quiet@AbsoluteOptions@plot;
     gr = plot;
     (*When r1 or r2 is e.g.{1,1} (scale region has zero width),
     EuclideanDistance by defult returns Infinity which is fine.*)
     d[p1_, p2_, {r1_, r2_}] := 
      Quiet@N@EuclideanDistance[Rescale[p1, r1], Rescale[p2, r2]];
     set[r_] := (range = new = r; mid = Mean /@ range;
       len = Abs[Subtract @@@ range]; pt1 = None; rect = {};);
     set@defRange;
     (*Replace plot range or insert if nonexistent*)
     replot[h_, hold_, r_] := 
      Module[{temp}, 
       ReleaseHold@
        Switch[h, $1Dplots, 
         temp = ReplacePart[
           hold, {{1, 2, 2} -> r[[1, 1]], {1, 2, 3} -> r[[1, 2]]}];
         If[MemberQ[temp, PlotRange, Infinity], 
          temp /. {_[PlotRange, _] -> (PlotRange -> r)}, 
          Insert[temp, PlotRange -> r, {1, -1}]], $2Dplots, 
         temp = ReplacePart[
           hold, {{1, 2, 2} -> r[[1, 1]], {1, 2, 3} -> 
             r[[1, 2]], {1, 3, 2} -> r[[2, 1]], {1, 3, 3} -> 
             r[[2, 2]]}];
         If[MemberQ[temp, PlotRange, Infinity], 
          temp /. {_[PlotRange, _] -> (PlotRange -> r)}, 
          Insert[temp, PlotRange -> r, {1, -1}]], _, hold]];
     rescale[h_, hold_, sc_] := 
      ReleaseHold@
       Switch[h, $1Dplots | $2Dplots, 
        If[MemberQ[hold, ScalingFunctions, Infinity], 
         hold /. {_[ScalingFunctions, _] -> (ScalingFunctions -> sc)},
          Insert[hold, ScalingFunctions -> sc, {1, -1}]], _, hold];
     appearance = 
      OptionValue@
        AppearanceFunction /. {Automatic :> (AppearanceFunction /. 
           Options@PlotExplorer)};
     position = OptionValue@MenuPosition /. Automatic -> Scaled@{1, 1};
     (*Print@Column@{rangeArg,sizeArg,ratioArg,appearance,position};*)
     button = 
      If[appearance === None, {}, 
       Inset[appearance@
         Dynamic@GraphicsButton["Menu", dyn, 
           Appearance -> If[appearance === Identity, Automatic, None],
            MenuItems -> 
            Flatten@{{Row@{"Copy PlotRange \[Rule] ", 
                  TextForm /@ range} :> (CopyToClipboard[
                  PlotRange -> range]), 
               Row@{"Copy ImageSize \[Rule] ", 
                  InputForm@size} :> (CopyToClipboard[
                  ImageSize -> size]), 
               Row@{"Copy AspectRatio \[Rule] ", 
                  InputForm@ratio} :> (CopyToClipboard[
                  AspectRatio -> ratio])}, 
              If[MatchQ[head, $1Dplots | $2Dplots], {Delimiter, 
                "Replot at current PlotRange" :> (gr = 
                    replot[head, held, range];), 
                "Linear" :> {gr = 
                    rescale[head, held, {Identity, Identity}];}, 
                "Log" :> {gr = 
                   rescale[head, held, {Identity, "Log"}]}, 
                "LogLinear" :> {gr = 
                   rescale[head, held, {"Log", Identity}]}, 
                "LogLog" :> {gr = 
                   rescale[head, held, {"Log", "Log"}]}}, {}], 
              Delimiter}], position, Scaled@{1, 1}, 
        Background -> None]];
     Deploy@Pane[EventHandler[Dynamic[MouseAppearance[Show[
           (*`dyn` is kept as the original expression with only \
updating `range`,`size` and `ratio`.*)
           dyn = Show[gr, PlotRange -> Dynamic@range, 
             ImageSize -> Dynamic@size, AspectRatio -> Dynamic@ratio],

           Epilog -> {epilog, 
             button, {FaceForm@{Blue, Opacity@.1}, 
              EdgeForm@{Thin, Dotted, Opacity@.5}, 
              Dynamic@rect}, {Dynamic@
               If[over && CurrentValue@"AltKey" && 
                 pt2 =!= None, {Antialiasing -> False, 
                 AbsoluteThickness@.25, GrayLevel[.5, .5], Dashing@{},
                  InfiniteLine@{pt2, pt2 + {1, 0}}, 
                 InfiniteLine@{pt2, pt2 + {0, 1}}}, {}]}}], 
          Which[over && CurrentValue@"AltKey" && pt2 =!= None, 
           Graphics@{Text[pt2, pt2, -{1.1, 1}, 
              Background -> GrayLevel[1, .7]]}, 
           CurrentValue@"ShiftKey", "LinkHand", 
           CurrentValue@"ControlKey", "ZoomView", True, Automatic]], 
         TrackedSymbols :> {gr}], {"MouseEntered" :> (over = True), 
         "MouseExited" :> (over = False), 
         "MouseMoved" :> (pt2 = MousePosition@"Graphics";), 
         "MouseClicked" :> (If[CurrentValue@"MouseClickCount" == 2, 
             set@defRange];), 
         "MouseDown" :> (pt1 = MousePosition@"Graphics";
           pt1sc = MousePosition@"GraphicsScaled";), 
         "MouseUp" :> (If[
            CurrentValue@"ControlKey" && d[pt1, pt2, new] > min, 
            range = Transpose@Sort@{pt1, pt2};]; set@range;), 
         "MouseDragged" :> (pt2 = MousePosition@"Graphics";
           pt2sc = MousePosition@"GraphicsScaled";

           Which[CurrentValue@"ShiftKey", 
            pt2 = MapThread[
               Rescale, {MousePosition@
                 "GraphicsScaled", {{0, 1}, {0, 1}}, new}] - pt1;
            range = new - pt2;,(*Panning*)CurrentValue@"ControlKey", 
            rect = If[pt1 === None || pt2 === None, {}, 
               Rectangle[pt1, pt2]];,(*Zooming rectangle*)True, 
            f = 10^(pt1sc - pt2sc);

            range = (mid + (1 - f) (pt1 - mid)) + 
              f/2 {-len, len}\[Transpose](*Zofom on `pt1`*)])}, 
        PassEventsDown -> True, PassEventsUp -> True], 
       Dynamic[size, (size = #;

          If[CurrentValue@"ControlKey", 
           ratio = Divide @@ Reverse@#]) &], 
       AppearanceElements -> "ResizeArea", ImageMargins -> 0, 
       FrameMargins -> 0]]]];

István Zachar

Posted 2012-06-21T01:08:38.520

Reputation: 44 135

This is an amazing tool. One issue that I'm running into is when I use it in a stylesheet other than 'default' the sliders and replot indicators mask the frame. Wondering if there is any solution to this other than using the default stylesheet. Also, the gridlines do not get drawn correctly if you use a gridlines function, ie: grids[min_,max_]=Range[Floor[min],Ceiling[max],increment] I think it has to do with redrawing but I have no clue how to fix it or if it's worthwhile, just an issue I noticed. – Andrew Stewart – 2014-03-31T20:22:25.843

Also, is there a way to export the plotted image? – Andrew Stewart – 2014-03-31T23:05:56.363

1This is great, +1! Now we need to extend it to allow for a replotting event in all cases possible – Rojo – 2012-06-21T03:21:51.830

1How can I get coordinates – P. Fonseca – 2012-06-21T06:25:00.467

1@Rojo, P.Fonseca: Replotting is indeed important, as the $y$-scale is usually cut off if zoomed out. I try to incorporate more functionality over time, coordinates are on the todo list. If you have other suggestions, please voice them! – István Zachar – 2012-06-21T09:39:02.727

+1 Magnificent! I can think of so many situations where this will be useful. – DavidC – 2012-06-21T11:51:41.520

I get shaking when I'm panning the plot (the axes can't seem to make up their mind whether to display or not). Using mma 8.0.1 on win7 – Ajasja – 2012-06-21T14:38:16.550

@Ajasja: I think that is because the plot tries to introduce either new ticks that causes imagepadding issues (-1000 is much larger than 0) or it tries to position the orthogonal (to panning direction) axis at different positions according to the changing plot range. If one does not specify a fixed position for the axes, I don't think this can be avoided. Try Heike's original here: if you drag the disk to the right, the vertical axis is constantly repositioned.

– István Zachar – 2012-06-21T14:53:32.803

Yes, If I try Heike's solution it suffers from the same problem. However adding ImagePadding->10 solves the problem. – Ajasja – 2012-06-21T15:26:33.853

Regarding the replotting: Perhaps [Ctrl]+DoubleClick or some other shortcut could be used to replot manually when needed. – Ajasja – 2012-06-21T15:37:17.427

@P.Fonseca As a dirty hack: If you delete the line Deployed@ then you will be able to right click on the plot and select "get coordinates". Trying to resize the plot however crashes my mma session:) – Ajasja – 2012-06-21T15:40:30.763

@Ajasja: Yes, giving explicit values to options ImagePadding or Axes should work, but I don't want to change any of these inside the function too retain the original plot. Also keep in mind that replot is only sensible for Plot, DensityPlot, etc., but not for ListPlot and similar list-based functions. As a principle I wanted to keep PlotExplorer the most general possible, dealing with any graphics. – István Zachar – 2012-06-21T16:05:33.023

@IstvánZachar, great that you're improving this :) :). It seems I lose all interactivity after replotting. Am I doing something wrong? – Rojo – 2012-06-22T15:54:02.523

@Rojo: Thanks, corrected now! The switch pushed was not reset when "MouseUp" event happened. – István Zachar – 2012-06-22T16:17:31.667

@István Zachar: I could not get replot to work in Mathematica 10.0.2. Here is my example: link.

– David – 2015-03-08T19:54:14.597

1Is there any way to integrate this with Manipulate (or something similar) to change the parameters of the plotted function? That would be a very handy addition and would make it fully interactive. – sebhofer – 2012-07-13T12:41:56.513

@David Please test it now! – István Zachar – 2015-12-19T09:36:29.053

@AndrewStewart could you please test added export functionality via the menu button? – István Zachar – 2015-12-19T09:37:07.343

@IstvánZachar Export works brilliantly! (Didn't work for me in Mathematica9 because of a FilterRules issue in DeleteDuplicatesBy, but after changing workbooks to Mathematica10 there don't appear to be any issues). Thanks so much, this is an amazing tool. Keeps me wondering why they haven't made this code native yet. – Andrew Stewart – 2016-01-11T19:41:14.993

For mac users, ctrl+leftclick triggers rightclick, so use ctrl+command+leftclick/drag to make zoom rectangle. – QuantumDot – 2016-05-31T21:52:39.940

@Halirutan tried to use this and got no output (10.4). Does anyone happen to have a known working version they could link to? – jamesson – 2017-08-11T05:23:34.377

@Halirutan n/m sorry works fine, I misread input parameters. – jamesson – 2017-08-11T06:42:52.297

This is amazing! And it's also totally ridiculous that these tools aren't already available for 2D plots. This works for Mathematica 11 on Linux, but not together with Manipulate. I get no errors, but the manipulable plot comes out just as if PlotExplorer@ wasn't there. – Jonatan Öström – 2018-03-08T15:42:33.330

Hi, could I use this function to manipulate ArryPlot as well? – Doron – 2012-12-15T04:55:41.380

Thank you very much for your tools. I want to get some sort of zoom for a manipulated environment like: Manipulate[PlotExplorer@Plot[Sin[x (1 + a x)], {x, 0, 6}], {a, 0, 2}] But all I get with that are errors, it seems that Manipulate blocks some of the functions. Does anybody know If one can change the behaviour of Manipulate or the PlotExplorer to get this compatible? Thanks! fanick – fanick – 2013-01-10T00:09:32.443

Does anybody know how to make this work with Show[]?: PlotExplorer@Show[ Plot[x, {x, 0, 10}], Graphics[Point[{1, 1}]] ] – Gabriel – 2013-05-24T11:54:19.953

@Gabriel: It should work now, please check. – István Zachar – 2013-09-05T18:58:01.150

@fanick: PlotExplorer now should work with Manipulate. Maybe the performance won't be satisfactory, but I'm afraid I can't do much more to improve speed. – István Zachar – 2013-09-05T19:01:58.373

49

In addition to István's fine answer, there is also Experimental`Explore[] which provides almost all the functionalities in his PlotExplorer. I think it was Szabolcs who first told me of this function.

If you call the above function with no arguments, you can choose to interactively work with either Plot, ParametricPlot, Manipulate or Graphics. Alternately, you can call it with either of these as an argument. Choosing Plot gives you a window like this:

enter image description here

Now you can simply enter the different functions that you're plotting and you can manipulate it using simple dropdown menus and checkboxes. Some of the features are

  • Edit all plot/axes/frame/etc. styles interactively
  • Real time update of changes
  • Pan the plot by clicking and dragging
  • Zoom the plot by pressing Option and dragging on a Mac (most likely Alt drag for Windows/Linux users)
  • Set the desired image size by dragging the plot
  • Add annotations which are inserted as Epilogs
  • Calculate the slope and value of the functions at any given x
  • Copy the corresponding Mathematica code for use in notebooks.

Here's an example of an explorer panel with lot of customizations done to the plot:

enter image description here

One caveat though — the fact that it is in Experimental` might give a hint that it is undocumented, possibly unstable and subject to change at any time without notice. Nevertheless, it is a very useful tool for those that do not wish to fiddle with their plots programmatically.

rm -rf

Posted 2012-06-21T01:08:38.520

Reputation: 85 395

2@rm-rf slightly less broken in v10 – bobthechemist – 2014-08-31T13:41:35.117

Thanks for adding this detailed description, I really hoped someone will do this! Adding to the caveat: I remember crashing the explorer many times by simply fiddling with the options/directives so I assume it is not part of the official canon because of these issues. Hopefully it will be integrated in version 9. – István Zachar – 2012-06-26T09:39:09.193

Does this support simply manipulating existing Graphics[] objects? – Ruslan – 2020-10-01T10:19:51.347

3When I try this in version 9, the plot button and the graphics button don't work ("Experimental`Explore::notmpl: Plot type exploration of Null is not currently supported." for both), ParametricPlot returns a window but also an error. And this is without even trying any of the features, this is just from clicking the buttons. – C. E. – 2013-04-03T08:57:47.910

@Anon Ah well, that's why it's in the Experimental` context... no guarantees, whatsoever :) I don't think it was broken deliberately; version 9 had a lot of changes to the front end and the low level box stuff, so I think it's just a matter of not updating them because it's low priority. Maybe it might be fixed (or not) in 9.0.4 – rm -rf – 2013-04-03T14:13:01.997

7

István's answer is very comprehensive, but a bit overkill for my taste. I adapted Szabolc's box zoom to include a panning function, and it is noticeably faster for large (i.e. many points) plots.

Left click drag to zoom, right click drag to pan, single left click to reset view. It doesn't work when the passed Graphics object was created with certain Options (e.g. Antialiasing -> False) though.

ExploreGraphics::usage = 
  "Pass a Graphics object to explore it by zooming and panning with \
   left and right mouse buttons respectively. Left click once to reset \
   view.";
OptAxesRedraw::usage = 
  "Option for ExploreGraphics to specify redrawing of axes. Default \
   True.";
Options[ExploreGraphics] = {OptAxesRedraw -> True};

ExploreGraphics[graph_Graphics, opts : OptionsPattern[] ] :=
  With[ {gr = First[graph],
        opt = 
     DeleteCases[Options[graph], 
      PlotRange -> _ | AspectRatio -> _ | AxesOrigin -> _],
        plr = PlotRange /. AbsoluteOptions[graph, PlotRange],
        ar = AspectRatio /. AbsoluteOptions[graph, AspectRatio],
        ao = AbsoluteOptions[AxesOrigin],
        rectangle = {Dashing[Small], Line[{#1, {First[#2], Last[#1]}, #2, {First[#1], Last[#2]}, #1}]} &,
        optAxesRedraw = OptionValue[OptAxesRedraw]},
       DynamicModule[ {dragging = False, first, second, rx1, rx2, ry1, ry2, range = plr},
        {{rx1, rx2}, {ry1, ry2}} = plr;
          Panel@EventHandler[Dynamic@
               Graphics[If[ dragging,
                            {gr, rectangle[first, second]},
                            gr
                          ], PlotRange -> Dynamic@range, AspectRatio -> ar, 
        AxesOrigin -> If[optAxesRedraw, Dynamic@Mean[range\[Transpose]], ao], Sequence @@ opt],
               {{"MouseDown", 1} :> (first = MousePosition["Graphics"]),
               {"MouseDragged", 1} :> (dragging = True;
                                       second = MousePosition["Graphics"]),
               {"MouseUp", 1} :> If[ dragging,
                                     dragging = False;
                                     range = {{rx1, rx2}, {ry1, ry2}} = Transpose@{first, second},
                                     range = {{rx1, rx2}, {ry1, ry2}} = plr
                                   ],
              {"MouseDown",2} :> (first = {sx1, sy1} = MousePosition["Graphics"]),
              {"MouseDragged", 2} :> (second = {sx2, sy2} = MousePosition["Graphics"];
                                          rx1 = rx1 - (sx2 - sx1);
                                          rx2 = rx2 - (sx2 - sx1);
                                          ry1 = ry1 - (sy2 - sy1);
                                          ry2 =  ry2 - (sy2 - sy1);
                                          range = {{rx1, rx2}, {ry1, ry2}})
              }]]
     ];

ZeitPolizei

Posted 2012-06-21T01:08:38.520

Reputation: 533

For ExploreGraphics[], how do you make a circle remain a circle (not squashed to an ellipse) when you zoom in with any type of rectangle (wide or tall)? I tried some things with AspectRatio and rx1,rx2,ry1,ry2, but couldn't make it work. Try this code with various drag rectangle shapes. g1 = Graphics[{Red, Thickness[0.01], Circle[{0, 0}, 1]}]; g2 = Graphics[{Gray, Polygon[4 {{-1, -1}, {-1, +1}, {+1, +1}, {+1, -1}, {-1, -1}}]}]; ExploreGraphics@Show[{g2, g1}, ImageSize -> 400, AspectRatio -> 1] – kmutiny – 2017-05-16T19:36:40.977

@kmutiny Having the AspectRatio change is intentional, because in a plot I will often want to only change the scaling of one Axis. Here is a slightly modified version of the function where the AspectRatio always stays the same. https://gist.github.com/gaetjen/68764d44c5c4ff281e035e7dbd9e970c I achieved this by forcing the zoom box to always be a square. (see lines 34-37)

– ZeitPolizei – 2017-05-17T15:35:24.253

Note, in the above function, the zoom box will be a square in the coordinate system of the Graphics object. Results may be unexpected if the aspect ratio of the initial plot range does not match up with the aspect ratio of the image. – ZeitPolizei – 2017-05-17T16:02:07.470

I get "Ticks::ticks: {Automatic,Automatic} is not a valid tick specification.". – rhermans – 2018-05-17T13:21:23.830