Is there a way to create a tiled plot from a Python script? I want to replicate the interactive method: right click and do “Plot spectra” and in the dialog box select “Tiled plot” and “Plot all”. I have a workspace Group, and each workspace has several spectra (e.g. the output of a simultaneous fit).
Additionally I’d like the curves in each sub plot to be of different types (spectrum 0 “data” as a scatter plot with error bars, spectrum 1 “calc” as a line.)
Either traditional QTIplot or matplotlib based plotting would do.
It would also be good to do the tiled plot where the curves in each tile are NOT from the same workspace. E.g. data which is binned to reduce noise, and a calculated curve on finer time steps. That’s particularly useful if the curve is an oscillation and there isn’t always a binned data point on the maximum or minimum of each cycle. AppendSpectra and/or ConjoinWorkspaces don’t allow me to create the “ragged” workspaces which correspond to this case.
The function to use is plotSubplots
. Here is its description:
Signature: plotSubplots(source, indices, distribution=0, error_bars=False, window=None)
Docstring:
Open a tiled plot.
This plots one or more spectra, with X as the bin boundaries,
and Y as the counts in each bin.
If one workspace, each spectrum gets its own tile.
Otherwise, each workspace gets its own tile.
Args:
source: list of workspace names
indices: workspace index, or tuple or list of workspace indices to plot
distribution: whether or not to plot as a distribution
error_bars: bool, set to True to add error bars.
window: window used for plotting. If None a new one will be created
Returns:
A handle to window if one was specified, otherwise a handle to the created one. None in case of error.
File: d:\work\mantid\build\bin\debug\pymantidplot\__init__.py
Type: function
Thanks, that works at a basic level. However the “type” parameter is missing so I can’t specify line or scatter plots (as I can in plotSpectrum). The “window” parameter behaves differently, rather than overlaying the curves or adding more tiles it reuses the window, discarding the original contents.
I can do plotSpectrum(…,window=returned_value_from_plotSubplots) but that puts the new plots in the last tile - is there a function to select which tile is active, to add a plot to, or another argument to plotSpectrum()?
Interesting that “plotSubplots” didn’t come up when I used the search bar at the top of the Mantid web pages and put “tile” or “tiled”, either in the home page or under “Documentation” - “Python API” which (as I already knew) search different parts of the site. “plotSubplots” as a search term only brings up a release note and the C++ code, there doesn’t appear to be a Python API description as such.
Aha! This works… Having deduced the existence of method setActiveLayer() from the different error messages it gives, and then searched, I find a page:
http://www.mantidproject.org/MantidPlot:_Arranging_Layers
which is under “Usage - Mantidplot Help” and not linked from “Python API” as I might have expected. (Python API - Mantidplot has a very incomplete and outdated set of functions.) And the final catch is that layers are numbered from 1.
graphspec=plotSubplots(CalcCurves,0,error_bars=False)
for i in range(len(rawdata)):
graphspec.setActiveLayer(graphspec.layer(i+1))
plotSpectrum(rawdata[i],0,type=1,error_bars=True,window=graphspec)
Two additional observations about the mantidplot functions:
(a) many (or all?) of them don’t take keyword parameters, so examples such as
l.enableAxisLabels(axis, on = True)
(https://www.mantidproject.org/MantidPlot:_1D_Plots_in_Python#Customizing_the_axes)
don’t actually work. Although parameter names are mentioned in the documentation, the parameters are positional-only, rare in Python.
(b) the “graph”, “layer”, etc handles have many methods such as the above but these are not revealed by the handy tool tips that pop up in the script editor once you type l.enab
and wait. It’s necessary to go to the documentation each time, or guess.
Mantid workspaces, tables and algorithms behave rather better in both these respects.
Here’s my final, mostly working script. I abandoned “plotSubplots” since it doesn’t give a “type” option, if I want the bottom-most curve to be something other than Line. My script arranges the tiles in the order their workspaces appear in the group, not in alphabetical order as the interactive “tiled plot” does. There’s only one representative legend in tile 1.
A possible bug - the “g.arrangeLayers(False,True)” doesn’t actually resize the graph window, it just pretends to have done so and only a few tiles at the top left are visible. Manually resizing it “pops” it back to normal so all of the tiles are scaled to fill the new size.
# size of window
Nrows=6
Ncols=6
# fixed axis range for all tiles
Xrange=(0.0,10.0)
Yrange=(-0.3,0.2)
# workspaces can be a WorkspaceGroup or an array of individual ones
workspacename="TFscan143a_Workspaces"
workspaces=mtd[workspacename]
# choose spectra and plot types and whether to add error bars
spectra=[(0,Layer.Scatter,True),(1,Layer.Line,False)]
g = newGraph(workspacename)
g.setLayerCanvasSize(200, 150)
g.setNumLayers(len(workspaces))
g.setRows(Nrows)
g.setCols(Ncols)
g.arrangeLayers(False, True)
for i in range(len(workspaces)):
lay=g.layer(i+1)
g.setActiveLayer(lay)
for (spec,ctype,ebars) in spectra:
plotSpectrum(workspaces[i],spec,type=ctype,error_bars=ebars,window=g)
lay.enableAxis(0,True)
lay.enableAxis(2,True)
lay.setAxisTitle(0,"")
lay.setAxisTitle(2,"")
lay.setScale(0,Yrange[0],Yrange[1])
lay.setScale(2,Xrange[0],Xrange[1])
if(i==0):
# legend in layer 1 only, spectra names only
cnames=[]
for (j,(spec,ctype,ebars)) in enumerate(spectra):
cnames.append("\\l("+str(j+1)+")"+workspaces[0].getAxis(1).label(spec))
leg=lay.newLegend("\n".join(cnames))