146
$\begingroup$

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.

$\endgroup$

3 Answers 3

166
$\begingroup$

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, [email protected]}, 
              EdgeForm@{Thin, Dotted, [email protected]}, 
              Dynamic@rect}, {Dynamic@
               If[over && CurrentValue@"AltKey" && 
                 pt2 =!= None, {Antialiasing -> False, 
                 [email protected], 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]]]];
$\endgroup$
28
  • 2
    $\begingroup$ This is great, +1! Now we need to extend it to allow for a replotting event in all cases possible $\endgroup$
    – Rojo
    Commented Jun 21, 2012 at 3:21
  • 1
    $\begingroup$ How can I get coordinates $\endgroup$
    – P. Fonseca
    Commented Jun 21, 2012 at 6:25
  • 1
    $\begingroup$ @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! $\endgroup$ Commented Jun 21, 2012 at 9:39
  • $\begingroup$ +1 Magnificent! I can think of so many situations where this will be useful. $\endgroup$
    – DavidC
    Commented Jun 21, 2012 at 11:51
  • 1
    $\begingroup$ Is 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. $\endgroup$
    – sebhofer
    Commented Jul 13, 2012 at 12:41
50
$\begingroup$

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.

$\endgroup$
5
  • $\begingroup$ 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. $\endgroup$ Commented Jun 26, 2012 at 9:39
  • 3
    $\begingroup$ When 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. $\endgroup$
    – C. E.
    Commented Apr 3, 2013 at 8:57
  • $\begingroup$ @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 $\endgroup$
    – rm -rf
    Commented Apr 3, 2013 at 14:13
  • 2
    $\begingroup$ @rm-rf slightly less broken in v10 $\endgroup$ Commented Aug 31, 2014 at 13:41
  • $\begingroup$ Does this support simply manipulating existing Graphics[] objects? $\endgroup$
    – Ruslan
    Commented Oct 1, 2020 at 10:19
7
$\begingroup$

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}})
              }]]
     ];
$\endgroup$
4
  • $\begingroup$ 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] $\endgroup$
    – user48879
    Commented May 16, 2017 at 19:36
  • $\begingroup$ @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. gist.github.com/gaetjen/68764d44c5c4ff281e035e7dbd9e970c I achieved this by forcing the zoom box to always be a square. (see lines 34-37) $\endgroup$ Commented May 17, 2017 at 15:35
  • $\begingroup$ 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. $\endgroup$ Commented May 17, 2017 at 16:02
  • $\begingroup$ I get "Ticks::ticks: {Automatic,Automatic} is not a valid tick specification.". $\endgroup$
    – rhermans
    Commented May 17, 2018 at 13:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.