Skip to main content

plotting - Do I have to code each case of this Grid full of plots separately?


I have written some custom functions to draw multi-panel graphs like this one: enter image description here


It's done by passing a matrix of (custom) plotting functions to a MultiPanelGraph function, which pulls apart the head, argument and options of those plots, adds a few of its own, and then puts it all together. There is also some funkiness to deal with plot labels and footnotes, but I've removed these from the code below for simplicity. The PanelHeightFactor type options are custom options to the custom functions for the individual plots. Likewise the MultiPanel option tells the custom functions for the individual plots to, for example, turn off ticks and tick marks on particular sub-plots so that they all fit together neatly as shown in the picture above, and to turn the PlotLabel into a panel label inside the plot frame using Prolog.


Attributes[MultiPanelGraph]={HoldFirst};
MultiPanelGraph[{{l_[largs__,lopts___Rule], r_[rargs__,ropts___Rule]}},
mpopts:OptionsPattern[{MultiPanelGraph,myLineGraph}]] :=

Module[{ (* there was some stuff here *)},With[{
wl = PanelWidthFactor /.{lopts}/.{PanelWidthFactor->1/2},
wr = PanelWidthFactor /.{ropts}/.{PanelWidthFactor->1/2},
Grid[{{l @@ Join[{largs},{lopts},
{MultiPanel-> {All,Left},PanelHeightFactor->1,PanelWidthFactor->wl,
ImageMargins->0}],
r @@ Join[{rargs},{ropts},
{MultiPanel->{All,Right},PanelHeightFactor->1,
PanelWidthFactor->wr,ImageMargins->0}]}},
Sequence @@ FilterRules[Join[{mpopts},{lopts},{ropts}],Grid],

Spacings -> {0,0} , ItemSize -> {{4.8+42.*wl, 4.+42.*wr},Full}, Alignment -> Left]

It works fine (aside from some issues to do with ImageSize being measured in points and ItemSize being measured in x-heights or some silly thing - a topic for another question), but I would have to code every single case - 2 by 1, 2 by 2, 3 by 4, whatever else some manager decides he or she wants - separately. Doable, but tedious.


I can imagine capturing the dimensions of the grid to get the appropriate default PanelWidthFactor and PanelHeightFactor, but beyond that I don’t know if there is a neat way to capture all the possible cases in one definition of the function. Does anyone have any suggestions for coding this in a way that doesn’t require each separate case to be coded to match separately? It would have to handle telling the subplot which ticks to use depending on its position.


EDIT: A couple of additional business requirements that might be useful to know:



  • It's essential for the plot frame to be the same width and height, regardless of how many panels there are, unless this is explicitly overridden (in my code, by having an explicit PanelHeightFactor or PanelWidthFactor option in the code for the subgraph).

  • Panels can be of dated data (DateListPlot or a custom DateListBarChart) or undated data (ListPlot, BarChart), but never both in the same chart.

  • The subplots will have some options (namely footnotes and source notes) that need to be captured and merged to be shown down the bottom of the main plot.




Answer



The nub of the answer turned out to rest on solutions to this question, particularly Heike's.


The trick is to Map Hold onto the subplots at the required level, and also to extract the Head of the plotting function in the right way. Heike's answer to that question gives a simple version of the solution. Below is a cutdown version of my actual code, where myLineGraph etc are custom functions that take MultiPanel, PanelHeightFactor and PanelWidthFactor options that determine the size of the graph panel relative to a standard dimension, and which sets of frame labels should show on the graph.


MultiPanelGraph[gs_?MatrixQ,mpopts:OptionsPattern[{MultiPanelGraph,myLineGraph,myBarGraph,myUndatedLineGraph}]]:=
Module[{nr,nc, gsheld,subrules,subargs,subplots, pwfs,phfs,mph = OptionValue[Title]},
{nr, nc} = Dimensions[gs];
gsheld = Map[Hold, Unevaluated[gs], {2}];
subrules = Table[Cases[gsheld[[i,j]],_Rule,{2}],{i, nr},{j, nc}];
(* collects all the options explicitly set for each sub-plot *)
subargs = Table[Cases[gsheld[[i,j]],Except[_Rule],{2}],{i, nr},{j, nc}];

(* collects all the non-option arguments for each sub-plot *)
pwfs = Flatten@Table[PanelWidthFactor /.{subrules[[1,j]]}/.{PanelWidthFactor->1/nc},{j, nc}];
(* assume only options specified in first row graphs override default PWF *)
phfs = Flatten@Table[PanelHeightFactor /.{subrules[[i,1]]}/.{PanelHeightFactor->1/nr},{i, nr}];
(* assume only options specified in first row graphs override default PHF *)
subplots = Table[gsheld[[i, j, 1, 0]]@@ Join[{Flatten[subargs[[i,j]],1]},{subrules[[i,j]]},
{MultiPanel-> {Switch[i,1,If[nr==1,All,Top],nr,Bottom,_,Middle],
Switch[j,1,If[nc==1,All,Left],nc,Right,_,Center]},
(* determines panel side - top, left etc. Captures case
where only one row or one column - NB 1,1 will not look like a one-panel graph *)

PanelHeightFactor->phfs[[i]],PanelWidthFactor->pwfs[[j]],ImageMargins->0}],
{i,nr},{j,nc}];
(* put head and args of subplots together with other arguments
that need to be passed to them *)
Grid[subplots, Alignment->Left, Spacings->{0,0}, ItemSize-> {Full, Full}]
]

Comments

Popular posts from this blog

plotting - How to draw lines between specified dots on ListPlot?

I would like to create a plot where I have unconnected dots and some connected. So far, I have figured out how to draw the dots. My code is the following: ListPlot[{{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4,13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full] I have thought using ListLinePlot command, but I don't know how to specify to the command to draw only selected lines between the dots. Do have any suggestions/hints on how to do that? Thank you. Answer One possibility would be to use Epilog with Line : ListPlot[ {{1, 1}, {2, 2}, {3, 3}, {4, 4}, {1, 4}, {2, 5}, {3, 6}, {4, 7}, {1, 7}, {2, 8}, {3, 9}, {4, 10}, {1, 10}, {2, 11}, {3, 12}, {4, 13}, {2.5, 7}}, Ticks -> {{1, 2, 3, 4}, None}, AxesStyle -> Thin, TicksStyle -> Directive[Black, Bold, 12], Mesh -> Full, Epilog -> { Line[ ...

dynamic - How can I make a clickable ArrayPlot that returns input?

I would like to create a dynamic ArrayPlot so that the rectangles, when clicked, provide the input. Can I use ArrayPlot for this? Or is there something else I should have to use? Answer ArrayPlot is much more than just a simple array like Grid : it represents a ranged 2D dataset, and its visualization can be finetuned by options like DataReversed and DataRange . These features make it quite complicated to reproduce the same layout and order with Grid . Here I offer AnnotatedArrayPlot which comes in handy when your dataset is more than just a flat 2D array. The dynamic interface allows highlighting individual cells and possibly interacting with them. AnnotatedArrayPlot works the same way as ArrayPlot and accepts the same options plus Enabled , HighlightCoordinates , HighlightStyle and HighlightElementFunction . data = {{Missing["HasSomeMoreData"], GrayLevel[ 1], {RGBColor[0, 1, 1], RGBColor[0, 0, 1], GrayLevel[1]}, RGBColor[0, 1, 0]}, {GrayLevel[0], GrayLevel...

Is there a way to do conditional matrix loop using 'continue'

I have the following: n = 3; m = 5; ww = RandomReal[{0, 0.1}, {n, n}]; uu = RandomReal[{0, 1}, {m, n}]; pp = RandomReal[{0, 1}, {n, n}]; ss = RandomInteger[{0, 5}, {m, n}]; Grid[{{"ww", "uu", "pp", "ss"}, {ww // TableForm, uu // TableForm, pp // TableForm, ss // TableForm}}, Spacings -> {5, 2}, Dividers -> All] where I would like to look at every element of matrix ss and produce a matrix tt , with zeroes at the locations in ss which have zeroes, and in all other positions do the following: tt = (-1/Subscript[ww, m]) Log[(1 - uu)/(Subscript[pp, m - 1])], where Subscript[ww, m] is the value at index of ww matrix and where Subscript[pp, m - 1] is the value at index-1 of pp matrix. So for example if the first value ever read from matrix ss happens to be 2, then value taken from matrix ww would be from the row 2, but from pp would be from row 1. Also how to tell difference between a 0 as a valid value from within the matrix elemen...