diff --git a/src/Plots.jl b/src/Plots.jl index cd4a052a..267889f9 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -14,10 +14,11 @@ export AbstractPlot, Plot, Subplot, - SubplotLayout, + AbstractLayout, GridLayout, - RowsLayout, - FlexLayout, + EmptyLayout, + # RowsLayout, + # FlexLayout, AVec, AMat, KW, diff --git a/src/args.jl b/src/args.jl index 2372a2ea..ca649a1a 100644 --- a/src/args.jl +++ b/src/args.jl @@ -156,6 +156,7 @@ _seriesDefaults[:weights] = nothing # optional weights for histograms _seriesDefaults[:contours] = false # add contours to 3d surface and wireframe plots _seriesDefaults[:match_dimensions] = false # do rows match x (true) or y (false) for heatmap/image/spy? see issue 196 # this ONLY effects whether or not the z-matrix is transposed for a heatmap display! +_seriesDefaults[:subplot_index] = :auto const _plotDefaults = KW() diff --git a/src/backends.jl b/src/backends.jl index 956861f5..acc87b80 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -48,8 +48,8 @@ include("backends/web.jl") plot(pkg::AbstractBackend; kw...) = error("plot($pkg; kw...) is not implemented") plot!(pkg::AbstractBackend, plt::Plot; kw...) = error("plot!($pkg, plt; kw...) is not implemented") _update_plot(pkg::AbstractBackend, plt::Plot, d::KW) = error("_update_plot($pkg, plt, d) is not implemented") -subplot(pkg::AbstractBackend; kw...) = error("subplot($pkg; kw...) is not implemented") -subplot!(pkg::AbstractBackend, subplt::Subplot; kw...) = error("subplot!($pkg, subplt; kw...) is not implemented") +# subplot(pkg::AbstractBackend; kw...) = error("subplot($pkg; kw...) is not implemented") +# subplot!(pkg::AbstractBackend, subplt::Subplot; kw...) = error("subplot!($pkg, subplt; kw...) is not implemented") # don't do anything as a default _create_backend_figure(plt::Plot) = nothing diff --git a/src/backends/bokeh.jl b/src/backends/bokeh.jl index f842b355..86ecd847 100644 --- a/src/backends/bokeh.jl +++ b/src/backends/bokeh.jl @@ -209,10 +209,10 @@ end # ---------------------------------------------------------------- -function _create_subplot(subplt::Subplot{BokehBackend}, isbefore::Bool) - # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example - -end +# function _create_subplot(subplt::Subplot{BokehBackend}, isbefore::Bool) +# # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example +# +# end function _expand_limits(lims, plt::Plot{BokehBackend}, isx::Bool) @@ -234,6 +234,6 @@ function Base.display(::PlotsDisplay, plt::Plot{BokehBackend}) Bokeh.showplot(plt.o) end -function Base.display(::PlotsDisplay, plt::Subplot{BokehBackend}) - # TODO: display/show the subplot -end +# function Base.display(::PlotsDisplay, plt::Subplot{BokehBackend}) +# # TODO: display/show the subplot +# end diff --git a/src/backends/gadfly.jl b/src/backends/gadfly.jl index 67d817c4..2c37516a 100644 --- a/src/backends/gadfly.jl +++ b/src/backends/gadfly.jl @@ -626,12 +626,12 @@ end # ---------------------------------------------------------------- -# create the underlying object (each backend will do this differently) -function _create_subplot(subplt::Subplot{GadflyBackend}, isbefore::Bool) - isbefore && return false # wait until after plotting to create the subplots - subplt.o = nothing - true -end +# # create the underlying object (each backend will do this differently) +# function _create_subplot(subplt::Subplot{GadflyBackend}, isbefore::Bool) +# isbefore && return false # wait until after plotting to create the subplots +# subplt.o = nothing +# true +# end function _remove_axis(plt::Plot{GadflyBackend}, isx::Bool) @@ -651,31 +651,31 @@ end getGadflyContext(plt::Plot{GadflyBackend}) = plt.o -getGadflyContext(subplt::Subplot{GadflyBackend}) = buildGadflySubplotContext(subplt) +# getGadflyContext(subplt::Subplot{GadflyBackend}) = buildGadflySubplotContext(subplt) -# create my Compose.Context grid by hstacking and vstacking the Gadfly.Plot objects -function buildGadflySubplotContext(subplt::Subplot) - rows = Any[] - row = Any[] - for (i,(r,c)) in enumerate(subplt.layout) - - # add the Plot object to the row - push!(row, getGadflyContext(subplt.plts[i])) - - # add the row - if c == ncols(subplt.layout, r) - push!(rows, Gadfly.hstack(row...)) - row = Any[] - end - end - - # stack the rows - Gadfly.vstack(rows...) -end +# # create my Compose.Context grid by hstacking and vstacking the Gadfly.Plot objects +# function buildGadflySubplotContext(subplt::Subplot) +# rows = Any[] +# row = Any[] +# for (i,(r,c)) in enumerate(subplt.layout) +# +# # add the Plot object to the row +# push!(row, getGadflyContext(subplt.plts[i])) +# +# # add the row +# if c == ncols(subplt.layout, r) +# push!(rows, Gadfly.hstack(row...)) +# row = Any[] +# end +# end +# +# # stack the rows +# Gadfly.vstack(rows...) +# end setGadflyDisplaySize(w,h) = Compose.set_default_graphic_size(w * Compose.px, h * Compose.px) setGadflyDisplaySize(plt::Plot) = setGadflyDisplaySize(plt.plotargs[:size]...) -setGadflyDisplaySize(subplt::Subplot) = setGadflyDisplaySize(getplotargs(subplt, 1)[:size]...) +# setGadflyDisplaySize(subplt::Subplot) = setGadflyDisplaySize(getplotargs(subplt, 1)[:size]...) # ------------------------------------------------------------------------- @@ -708,39 +708,39 @@ function Base.display(::PlotsDisplay, plt::Plot{GadflyBackend}) end -function Base.display(::PlotsDisplay, subplt::Subplot{GadflyBackend}) - setGadflyDisplaySize(getplotargs(subplt,1)[:size]...) - ctx = buildGadflySubplotContext(subplt) - - # taken from Gadfly since I couldn't figure out how to do it directly - - filename = string(Gadfly.tempname(), ".html") - output = open(filename, "w") - - plot_output = IOBuffer() - Gadfly.draw(Gadfly.SVGJS(plot_output, Compose.default_graphic_width, - Compose.default_graphic_height, false), ctx) - plotsvg = takebuf_string(plot_output) - - write(output, - """ - - - - Gadfly Plot - - - - - - $(plotsvg) - - - """) - close(output) - Gadfly.open_file(filename) -end +# function Base.display(::PlotsDisplay, subplt::Subplot{GadflyBackend}) +# setGadflyDisplaySize(getplotargs(subplt,1)[:size]...) +# ctx = buildGadflySubplotContext(subplt) +# +# # taken from Gadfly since I couldn't figure out how to do it directly +# +# filename = string(Gadfly.tempname(), ".html") +# output = open(filename, "w") +# +# plot_output = IOBuffer() +# Gadfly.draw(Gadfly.SVGJS(plot_output, Compose.default_graphic_width, +# Compose.default_graphic_height, false), ctx) +# plotsvg = takebuf_string(plot_output) +# +# write(output, +# """ +# +# +# +# Gadfly Plot +# +# +# +# +# +# $(plotsvg) +# +# +# """) +# close(output) +# Gadfly.open_file(filename) +# end diff --git a/src/backends/glvisualize.jl b/src/backends/glvisualize.jl index 150f1fbf..a8b40243 100644 --- a/src/backends/glvisualize.jl +++ b/src/backends/glvisualize.jl @@ -143,9 +143,9 @@ end # ---------------------------------------------------------------- -function _create_subplot(subplt::Subplot{GLVisualizeBackend}) - # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example -end +# function _create_subplot(subplt::Subplot{GLVisualizeBackend}) +# # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example +# end function _expand_limits(lims, plt::Plot{GLVisualizeBackend}, isx::Bool) # TODO: call expand limits for each plot data @@ -169,6 +169,6 @@ function Base.display(::PlotsDisplay, plt::Plot{GLVisualizeBackend}) # wouldn't actually need to do anything end -function Base.display(::PlotsDisplay, plt::Subplot{GLVisualizeBackend}) - # TODO: display/show the subplot -end +# function Base.display(::PlotsDisplay, plt::Subplot{GLVisualizeBackend}) +# # TODO: display/show the subplot +# end diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 767ba3f2..304bc7f7 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -832,21 +832,21 @@ function gr_display(plt::Plot{GRBackend}, clear=true, update=true, update && GR.updatews() end -function gr_display(subplt::Subplot{GRBackend}) - clear = true - update = false - l = enumerate(subplt.layout) - nr = nrows(subplt.layout) - for (i, (r, c)) in l - nc = ncols(subplt.layout, r) - if i == length(l) - update = true - end - subplot = [(c-1)/nc, c/nc, 1-r/nr, 1-(r-1)/nr] - gr_display(subplt.plts[i], clear, update, subplot) - clear = false - end -end +# function gr_display(subplt::Subplot{GRBackend}) +# clear = true +# update = false +# l = enumerate(subplt.layout) +# nr = nrows(subplt.layout) +# for (i, (r, c)) in l +# nc = ncols(subplt.layout, r) +# if i == length(l) +# update = true +# end +# subplot = [(c-1)/nc, c/nc, 1-r/nr, 1-(r-1)/nr] +# gr_display(subplt.plts[i], clear, update, subplot) +# clear = false +# end +# end # function _create_plot(pkg::GRBackend, d::KW) # Plot(nothing, pkg, 0, d, KW[]) @@ -948,7 +948,7 @@ function Base.display(::PlotsDisplay, plt::Plot{GRBackend}) gr_display(plt) end -function Base.display(::PlotsDisplay, plt::Subplot{GRBackend}) - gr_display(plt) - true -end +# function Base.display(::PlotsDisplay, plt::Subplot{GRBackend}) +# gr_display(plt) +# true +# end diff --git a/src/backends/immerse.jl b/src/backends/immerse.jl index 733c1b79..a974448b 100644 --- a/src/backends/immerse.jl +++ b/src/backends/immerse.jl @@ -88,51 +88,51 @@ end # ---------------------------------------------------------------- -function _create_subplot(subplt::Subplot{ImmerseBackend}, isbefore::Bool) - return false - # isbefore && return false -end - -function showSubplotObject(subplt::Subplot{ImmerseBackend}) - # create the Gtk window with vertical box vsep - d = getplotargs(subplt,1) - w,h = d[:size] - vsep = Gtk.GtkBoxLeaf(:v) - win = Gtk.GtkWindowLeaf(vsep, d[:windowtitle], w, h) - - figindices = [] - row = Gtk.GtkBoxLeaf(:h) - push!(vsep, row) - for (i,(r,c)) in enumerate(subplt.layout) - plt = subplt.plts[i] - - # get the components... box is the main plot GtkBox, and canvas is the GtkCanvas where it's plotted - box, toolbar, canvas = Immerse.createPlotGuiComponents() - - # add the plot's box to the row - push!(row, box) - - # create the figure and store the index returned for destruction later - figidx = Immerse.figure(canvas) - push!(figindices, figidx) - - fig = Immerse.figure(figidx) - plt.o = (fig, plt.o[2]) - - # add the row - if c == ncols(subplt.layout, r) - row = Gtk.GtkBoxLeaf(:h) - push!(vsep, row) - end - - end - - # destructor... clean up plots - Gtk.on_signal_destroy((x...) -> ([Immerse.dropfig(Immerse._display,i) for i in figindices]; subplt.o = nothing), win) - - subplt.o = win - true -end +# function _create_subplot(subplt::Subplot{ImmerseBackend}, isbefore::Bool) +# return false +# # isbefore && return false +# end +# +# function showSubplotObject(subplt::Subplot{ImmerseBackend}) +# # create the Gtk window with vertical box vsep +# d = getplotargs(subplt,1) +# w,h = d[:size] +# vsep = Gtk.GtkBoxLeaf(:v) +# win = Gtk.GtkWindowLeaf(vsep, d[:windowtitle], w, h) +# +# figindices = [] +# row = Gtk.GtkBoxLeaf(:h) +# push!(vsep, row) +# for (i,(r,c)) in enumerate(subplt.layout) +# plt = subplt.plts[i] +# +# # get the components... box is the main plot GtkBox, and canvas is the GtkCanvas where it's plotted +# box, toolbar, canvas = Immerse.createPlotGuiComponents() +# +# # add the plot's box to the row +# push!(row, box) +# +# # create the figure and store the index returned for destruction later +# figidx = Immerse.figure(canvas) +# push!(figindices, figidx) +# +# fig = Immerse.figure(figidx) +# plt.o = (fig, plt.o[2]) +# +# # add the row +# if c == ncols(subplt.layout, r) +# row = Gtk.GtkBoxLeaf(:h) +# push!(vsep, row) +# end +# +# end +# +# # destructor... clean up plots +# Gtk.on_signal_destroy((x...) -> ([Immerse.dropfig(Immerse._display,i) for i in figindices]; subplt.o = nothing), win) +# +# subplt.o = win +# true +# end function _remove_axis(plt::Plot{ImmerseBackend}, isx::Bool) @@ -151,7 +151,7 @@ end # ---------------------------------------------------------------- getGadflyContext(plt::Plot{ImmerseBackend}) = plt.o[2] -getGadflyContext(subplt::Subplot{ImmerseBackend}) = buildGadflySubplotContext(subplt) +# getGadflyContext(subplt::Subplot{ImmerseBackend}) = buildGadflySubplotContext(subplt) function Base.display(::PlotsDisplay, plt::Plot{ImmerseBackend}) @@ -168,20 +168,20 @@ function Base.display(::PlotsDisplay, plt::Plot{ImmerseBackend}) end -function Base.display(::PlotsDisplay, subplt::Subplot{ImmerseBackend}) - - # if we haven't created the window yet, do it - if subplt.o == nothing - showSubplotObject(subplt) - end - - # display the plots by creating a fresh Immerse.Figure object from the GtkCanvas and Gadfly.Plot - for plt in subplt.plts - fig, gplt = plt.o - Immerse.figure(fig.figno; displayfig = false) - display(gplt) - end - - # o is the window... show it - showall(subplt.o) -end +# function Base.display(::PlotsDisplay, subplt::Subplot{ImmerseBackend}) +# +# # if we haven't created the window yet, do it +# if subplt.o == nothing +# showSubplotObject(subplt) +# end +# +# # display the plots by creating a fresh Immerse.Figure object from the GtkCanvas and Gadfly.Plot +# for plt in subplt.plts +# fig, gplt = plt.o +# Immerse.figure(fig.figno; displayfig = false) +# display(gplt) +# end +# +# # o is the window... show it +# showall(subplt.o) +# end diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index bf2adcd5..54ca1550 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -135,10 +135,10 @@ end # ---------------------------------------------------------------- -function _create_subplot(subplt::Subplot{PlotlyBackend}, isbefore::Bool) - # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example - true -end +# function _create_subplot(subplt::Subplot{PlotlyBackend}, isbefore::Bool) +# # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example +# true +# end function _expand_limits(lims, plt::Plot{PlotlyBackend}, isx::Bool) # TODO: call expand limits for each plot data @@ -516,15 +516,15 @@ function get_series_json(plt::Plot{PlotlyBackend}) JSON.json(map(d -> plotly_series(d, plt.plotargs), plt.seriesargs)) end -function get_series_json(subplt::Subplot{PlotlyBackend}) - ds = KW[] - for (i,plt) in enumerate(subplt.plts) - for d in plt.seriesargs - push!(ds, plotly_series(d, plt.plotargs, plot_index = i)) - end - end - JSON.json(ds) -end +# function get_series_json(subplt::Subplot{PlotlyBackend}) +# ds = KW[] +# for (i,plt) in enumerate(subplt.plts) +# for d in plt.seriesargs +# push!(ds, plotly_series(d, plt.plotargs, plot_index = i)) +# end +# end +# JSON.json(ds) +# end # ---------------------------------------------------------------- @@ -557,29 +557,29 @@ function js_body(plt::Plot{PlotlyBackend}, uuid) end -function html_body(subplt::Subplot{PlotlyBackend}) - w, h = subplt.plts[1].plotargs[:size] - html = ["
"] - nr = nrows(subplt.layout) - ph = h / nr - - for r in 1:nr - push!(html, "
") - - nc = ncols(subplt.layout, r) - pw = w / nc - - for c in 1:nc - plt = subplt[r,c] - push!(html, html_body(plt, "float:left; width:$(pw)px; height:$(ph)px;")) - end - - push!(html, "
") - end - push!(html, "
") - - join(html) -end +# function html_body(subplt::Subplot{PlotlyBackend}) +# w, h = subplt.plts[1].plotargs[:size] +# html = ["
"] +# nr = nrows(subplt.layout) +# ph = h / nr +# +# for r in 1:nr +# push!(html, "
") +# +# nc = ncols(subplt.layout, r) +# pw = w / nc +# +# for c in 1:nc +# plt = subplt[r,c] +# push!(html, html_body(plt, "float:left; width:$(pw)px; height:$(ph)px;")) +# end +# +# push!(html, "
") +# end +# push!(html, "
") +# +# join(html) +# end # ---------------------------------------------------------------- diff --git a/src/backends/plotlyjs.jl b/src/backends/plotlyjs.jl index f57e05d9..6adc70e2 100644 --- a/src/backends/plotlyjs.jl +++ b/src/backends/plotlyjs.jl @@ -178,10 +178,10 @@ end # ---------------------------------------------------------------- -function _create_subplot(subplt::Subplot{PlotlyJSBackend}, isbefore::Bool) - # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example - true -end +# function _create_subplot(subplt::Subplot{PlotlyJSBackend}, isbefore::Bool) +# # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example +# true +# end function _expand_limits(lims, plt::Plot{PlotlyJSBackend}, isx::Bool) # TODO: call expand limits for each plot data @@ -206,6 +206,6 @@ function Base.display(::PlotsDisplay, plt::Plot{PlotlyJSBackend}) display(plt.o) end -function Base.display(::PlotsDisplay, plt::Subplot{PlotlyJSBackend}) - error() -end +# function Base.display(::PlotsDisplay, plt::Subplot{PlotlyJSBackend}) +# error() +# end diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 13981157..7e5c0909 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -222,40 +222,62 @@ end # --------------------------------------------------------------------------- -type PyPlotAxisWrapper - ax - rightax - fig - kwargs # for add_subplot -end - -getfig(wrap::PyPlotAxisWrapper) = wrap.fig - - - -# get a reference to the correct axis -function getLeftAxis(wrap::PyPlotAxisWrapper) - if wrap.ax == nothing - axes = wrap.fig.o[:axes] - if isempty(axes) - return wrap.fig.o[:add_subplot](111; wrap.kwargs...) - end - axes[1] - else - wrap.ax +function getAxis(plt::Plot{PyPlotBackend}, subplot::Subplot = plt.subplots[1]) + if subplot.o == nothing + @show subplot + fig = plt.o + @show plt + # if fig == nothing + # fig = + # TODO: actual coords? + # NOTE: might want to use ax[:get_tightbbox](ax[:get_renderer_cache]()) to calc size of guides? + # NOTE: can set subplot location later with ax[:set_position]([left, bottom, width, height]) + left, bottom, width, height = 0.3, 0.3, 0.5, 0.5 + ax = fig[:add_axes]([left, bottom, width, height]) + subplot.o = ax end + subplot.o end -function getRightAxis(wrap::PyPlotAxisWrapper) - if wrap.rightax == nothing - wrap.rightax = getLeftAxis(wrap)[:twinx]() - end - wrap.rightax -end +getLeftAxis(plt::Plot{PyPlotBackend}, subplot::Subplot = plt.subplots[1]) = getAxis(plt, subplot) +getfig(o) = o -getLeftAxis(plt::Plot{PyPlotBackend}) = getLeftAxis(plt.o) -getRightAxis(plt::Plot{PyPlotBackend}) = getRightAxis(plt.o) -getAxis(plt::Plot{PyPlotBackend}, axis::Symbol) = (axis == :right ? getRightAxis : getLeftAxis)(plt) +# --------------------------------------------------------------------------- + +# type PyPlotAxisWrapper +# ax +# rightax +# fig +# kwargs # for add_subplot +# end +# +# getfig(wrap::PyPlotAxisWrapper) = wrap.fig +# +# +# +# # get a reference to the correct axis +# function getLeftAxis(wrap::PyPlotAxisWrapper) +# if wrap.ax == nothing +# axes = wrap.fig.o[:axes] +# if isempty(axes) +# return wrap.fig.o[:add_subplot](111; wrap.kwargs...) +# end +# axes[1] +# else +# wrap.ax +# end +# end +# +# function getRightAxis(wrap::PyPlotAxisWrapper) +# if wrap.rightax == nothing +# wrap.rightax = getLeftAxis(wrap)[:twinx]() +# end +# wrap.rightax +# end +# +# getLeftAxis(plt::Plot{PyPlotBackend}) = getLeftAxis(plt.o) +# getRightAxis(plt::Plot{PyPlotBackend}) = getRightAxis(plt.o) +# # getAxis(plt::Plot{PyPlotBackend}, axis::Symbol) = (axis == :right ? getRightAxis : getLeftAxis)(plt) function handleSmooth(plt::Plot{PyPlotBackend}, ax, d::KW, smooth::Bool) @@ -272,13 +294,13 @@ handleSmooth(plt::Plot{PyPlotBackend}, ax, d::KW, smooth::Real) = handleSmooth(p # --------------------------------------------------------------------------- -makePyPlotCurrent(wrap::PyPlotAxisWrapper) = wrap.ax == nothing ? PyPlot.figure(wrap.fig.o[:number]) : nothing -makePyPlotCurrent(plt::Plot{PyPlotBackend}) = plt.o == nothing ? nothing : makePyPlotCurrent(plt.o) - - -function _before_add_series(plt::Plot{PyPlotBackend}) - makePyPlotCurrent(plt) -end +# makePyPlotCurrent(wrap::PyPlotAxisWrapper) = wrap.ax == nothing ? PyPlot.figure(wrap.fig.o[:number]) : nothing +# makePyPlotCurrent(plt::Plot{PyPlotBackend}) = plt.o == nothing ? nothing : makePyPlotCurrent(plt.o) +# +# +# function _before_add_series(plt::Plot{PyPlotBackend}) +# makePyPlotCurrent(plt) +# end # ------------------------------------------------------------------ @@ -297,7 +319,7 @@ function pyplot_figure(plotargs::KW) fig[:set_size_inches](w, h, forward = true) fig[:set_facecolor](getPyPlotColor(plotargs[:background_color_outside])) fig[:set_dpi](DPI) - fig[:set_tight_layout](true) + # fig[:set_tight_layout](true) # clear the figure PyPlot.clf() @@ -315,21 +337,27 @@ end # --------------------------------------------------------------------------- -function _create_backend_figure(plt::Plot) - if haskey(plt.plotargs, :subplot) - wrap = nothing - else - wrap = PyPlotAxisWrapper(nothing, nothing, pyplot_figure(plt.plotargs), []) - pyplot_3d_setup!(wrap, plt.plotargs) - if get(plt.plotargs, :polar, false) - push!(wrap.kwargs, (:polar, true)) - end - end - wrap - # plt = Plot(wrap, pkg, 0, plt.plotargs, KW[]) - # plt +function _create_backend_figure(plt::Plot{PyPlotBackend}) + fig = pyplot_figure(plt.plotargs) + # TODO: handle 3d and polar + fig end +# function _create_backend_figure(plt::Plot) +# if haskey(plt.plotargs, :subplot) +# wrap = nothing +# else +# wrap = PyPlotAxisWrapper(nothing, nothing, pyplot_figure(plt.plotargs), []) +# pyplot_3d_setup!(wrap, plt.plotargs) +# if get(plt.plotargs, :polar, false) +# push!(wrap.kwargs, (:polar, true)) +# end +# end +# wrap +# # plt = Plot(wrap, pkg, 0, plt.plotargs, KW[]) +# # plt +# end + # --------------------------------------------------------------------------- function fix_xy_lengths!(plt::Plot{PyPlotBackend}, d::KW) @@ -368,11 +396,12 @@ pyfillcolormap(d::KW) = getPyPlotColorMap(d[:fillcolor], d[:fillalpha]) function _add_series(plt::Plot{PyPlotBackend}, series::Series) d = series.d st = d[:seriestype] - if !(st in supportedTypes(pkg)) - error("seriestype $(st) is unsupported in PyPlot. Choose from: $(supportedTypes(pkg))") + if !(st in supportedTypes(plt.backend)) + error("seriestype $(st) is unsupported in PyPlot. Choose from: $(supportedTypes(plt.backend))") end # 3D plots have a different underlying Axes object in PyPlot + # TODO: BUG: this adds to kwargs but never changes anything... source of subplot(wireframe(...)) bug? if st in _3dTypes && isempty(plt.o.kwargs) push!(plt.o.kwargs, (:projection, "3d")) end @@ -380,7 +409,8 @@ function _add_series(plt::Plot{PyPlotBackend}, series::Series) # PyPlot doesn't handle mismatched x/y fix_xy_lengths!(plt, d) - ax = getAxis(plt, d[:axis]) + # ax = getAxis(plt, d[:axis]) + ax = getAxis(plt, get(d, :subplot, plt.subplots[1])) x, y, z = d[:x], d[:y], d[:z] @show typeof((x,y,z)) xyargs = (st in _3dTypes ? (x,y,z) : (x,y)) @@ -769,11 +799,11 @@ end # given a dimension (:x, :y, or :z), loop over the seriesargs KWs to find the min/max of the underlying data -function minmaxseries(ds, dimension, axis) +function minmaxseries(series_list, dimension, axis) lo, hi = Inf, -Inf - for d in ds - d[:axis] == axis || continue - v = d[dimension] + for series in series_list + series.d[:axis] == axis || continue + v = series.d[dimension] if length(v) > 0 vlo, vhi = extrema(v) lo = min(lo, vlo) @@ -795,13 +825,13 @@ function set_lims!(plt::Plot{PyPlotBackend}, axis::Symbol) ax = getAxis(plt, axis) pargs = plt.plotargs if pargs[:xlims] == :auto - ax[pargs[:polar] ? :set_tlim : :set_xlim](minmaxseries(plt.seriesargs, :x, axis)...) + ax[pargs[:polar] ? :set_tlim : :set_xlim](minmaxseries(plt.series_list, :x, axis)...) end if pargs[:ylims] == :auto - ax[pargs[:polar] ? :set_rlim : :set_ylim](minmaxseries(plt.seriesargs, :y, axis)...) + ax[pargs[:polar] ? :set_rlim : :set_ylim](minmaxseries(plt.series_list, :y, axis)...) end if pargs[:zlims] == :auto && haskey(ax, :set_zlim) - ax[:set_zlim](minmaxseries(plt.seriesargs, :z, axis)...) + ax[:set_zlim](minmaxseries(plt.series_list, :z, axis)...) end end @@ -811,7 +841,7 @@ end # the x/y data for each handle (for example, plot and scatter) function setxy!{X,Y}(plt::Plot{PyPlotBackend}, xy::Tuple{X,Y}, i::Integer) - d = plt.seriesargs[i] + d = plt.series_list[i].d d[:x], d[:y] = xy for handle in d[:serieshandle] try @@ -826,7 +856,7 @@ end function setxyz!{X,Y,Z}(plt::Plot{PyPlotBackend}, xyz::Tuple{X,Y,Z}, i::Integer) - d = plt.seriesargs[i] + d = plt.series_list[i].d d[:x], d[:y], d[:z] = xyz for handle in d[:serieshandle] handle[:set_data](d[:x], d[:y]) @@ -901,9 +931,9 @@ function updateAxisColors(ax, d::KW) ax[:title][:set_color](guidecolor) end -function usingRightAxis(plt::Plot{PyPlotBackend}) - any(args -> args[:axis] in (:right,:auto), plt.seriesargs) -end +# function usingRightAxis(plt::Plot{PyPlotBackend}) +# any(args -> args[:axis] in (:right,:auto), plt.seriesargs) +# end # -------------------------------------------------------------------------- @@ -912,7 +942,8 @@ end function _update_plot(plt::Plot{PyPlotBackend}, d::KW) # @show d figorax = plt.o - ax = getLeftAxis(figorax) + # ax = getLeftAxis(figorax) + ax = getAxis(plt, plt.subplots[1]) # ticksz = get(d, :tickfont, plt.plotargs[:tickfont]).pointsize guidesz = get(d, :guidefont, plt.plotargs[:guidefont]).pointsize @@ -920,15 +951,16 @@ function _update_plot(plt::Plot{PyPlotBackend}, d::KW) haskey(d, :title) && ax[:set_title](d[:title]) ax[:title][:set_fontsize](guidesz) - # handle right y axis - axes = [getLeftAxis(figorax)] - if usingRightAxis(plt) - push!(axes, getRightAxis(figorax)) - if get(d, :yrightlabel, "") != "" - rightax = getRightAxis(figorax) - rightax[:set_ylabel](d[:yrightlabel]) - end - end + axes = [ax] + # # handle right y axis + # axes = [getLeftAxis(figorax)] + # if usingRightAxis(plt) + # push!(axes, getRightAxis(figorax)) + # if get(d, :yrightlabel, "") != "" + # rightax = getRightAxis(figorax) + # rightax[:set_ylabel](d[:yrightlabel]) + # end + # end for letter in ("x", "y", "z") axissym = symbol(letter*"axis") @@ -1044,7 +1076,7 @@ end # # # this will be called internally, when creating a subplot from existing plots # # NOTE: if I ever need to "Rebuild a "ubplot from individual Plot's"... this is what I should use! -# function subplot(plts::AVec{Plot{PyPlotBackend}}, layout::SubplotLayout, d::KW) +# function subplot(plts::AVec{Plot{PyPlotBackend}}, layout::AbstractLayout, d::KW) # validateSubplotSupported() # # p = length(layout) @@ -1104,15 +1136,27 @@ function addPyPlotLegend(plt::Plot, ax) leg = plt.plotargs[:legend] if leg != :none # gotta do this to ensure both axes are included - args = filter(x -> !(x[:seriestype] in ( - :hist,:density,:hexbin,:hist2d,:hline,:vline, - :contour,:contour3d,:surface,:wireframe, - :heatmap,:path3d,:scatter3d, :pie, :image - )), plt.seriesargs) - args = filter(x -> x[:label] != "", args) - if length(args) > 0 - leg = ax[:legend]([d[:serieshandle][1] for d in args], - [d[:label] for d in args], + labels = [] + handles = [] + for series in plt.series_list + if series.d[:label] != "" && !(series.d[:seriestype] in ( + :hist,:density,:hexbin,:hist2d,:hline,:vline, + :contour,:contour3d,:surface,:wireframe, + :heatmap,:path3d,:scatter3d, :pie, :image)) + push!(handles, series.d[:serieshandle][1]) + push!(labels, series.d[:label]) + end + end + # args = filter(x -> !(x.d[:seriestype] in ( + # :hist,:density,:hexbin,:hist2d,:hline,:vline, + # :contour,:contour3d,:surface,:wireframe, + # :heatmap,:path3d,:scatter3d, :pie, :image + # )), plt.series_list) + # args = filter(x -> x[:label] != "", args) + # if length(args) > 0 + if !isempty(handles) + leg = ax[:legend](handles, #[d[:serieshandle][1] for d in args], + labels, #[d[:label] for d in args], loc = get(_pyplot_legend_pos, leg, "best"), scatterpoints = 1, fontsize = plt.plotargs[:legendfont].pointsize diff --git a/src/backends/qwt.jl b/src/backends/qwt.jl index 0e7a1904..87eb6211 100644 --- a/src/backends/qwt.jl +++ b/src/backends/qwt.jl @@ -269,28 +269,28 @@ end # ------------------------------- -# create the underlying object (each backend will do this differently) -function _create_subplot(subplt::Subplot{QwtBackend}, isbefore::Bool) - isbefore && return false - i = 0 - rows = Any[] - row = Any[] - for (i,(r,c)) in enumerate(subplt.layout) - push!(row, subplt.plts[i].o) - if c == ncols(subplt.layout, r) - push!(rows, Qwt.hsplitter(row...)) - row = Any[] - end - end - # for rowcnt in subplt.layout.rowcounts - # push!(rows, Qwt.hsplitter([plt.o for plt in subplt.plts[(1:rowcnt) + i]]...)) - # i += rowcnt - # end - subplt.o = Qwt.vsplitter(rows...) - # Qwt.resizewidget(subplt.o, getplotargs(subplt,1)[:size]...) - # Qwt.moveToLastScreen(subplt.o) # hack so it goes to my center monitor... sorry - true -end +# # create the underlying object (each backend will do this differently) +# function _create_subplot(subplt::Subplot{QwtBackend}, isbefore::Bool) +# isbefore && return false +# i = 0 +# rows = Any[] +# row = Any[] +# for (i,(r,c)) in enumerate(subplt.layout) +# push!(row, subplt.plts[i].o) +# if c == ncols(subplt.layout, r) +# push!(rows, Qwt.hsplitter(row...)) +# row = Any[] +# end +# end +# # for rowcnt in subplt.layout.rowcounts +# # push!(rows, Qwt.hsplitter([plt.o for plt in subplt.plts[(1:rowcnt) + i]]...)) +# # i += rowcnt +# # end +# subplt.o = Qwt.vsplitter(rows...) +# # Qwt.resizewidget(subplt.o, getplotargs(subplt,1)[:size]...) +# # Qwt.moveToLastScreen(subplt.o) # hack so it goes to my center monitor... sorry +# true +# end function _expand_limits(lims, plt::Plot{QwtBackend}, isx::Bool) for series in plt.o.lines @@ -311,13 +311,13 @@ function Base.writemime(io::IO, ::MIME"image/png", plt::Plot{QwtBackend}) write(io, readall("/tmp/dfskjdhfkh.png")) end -function Base.writemime(io::IO, ::MIME"image/png", subplt::Subplot{QwtBackend}) - for plt in subplt.plts - Qwt.refresh(plt.o) - end - Qwt.savepng(subplt.o, "/tmp/dfskjdhfkh.png") - write(io, readall("/tmp/dfskjdhfkh.png")) -end +# function Base.writemime(io::IO, ::MIME"image/png", subplt::Subplot{QwtBackend}) +# for plt in subplt.plts +# Qwt.refresh(plt.o) +# end +# Qwt.savepng(subplt.o, "/tmp/dfskjdhfkh.png") +# write(io, readall("/tmp/dfskjdhfkh.png")) +# end function Base.display(::PlotsDisplay, plt::Plot{QwtBackend}) @@ -325,9 +325,9 @@ function Base.display(::PlotsDisplay, plt::Plot{QwtBackend}) Qwt.showwidget(plt.o) end -function Base.display(::PlotsDisplay, subplt::Subplot{QwtBackend}) - for plt in subplt.plts - Qwt.refresh(plt.o) - end - Qwt.showwidget(subplt.o) -end +# function Base.display(::PlotsDisplay, subplt::Subplot{QwtBackend}) +# for plt in subplt.plts +# Qwt.refresh(plt.o) +# end +# Qwt.showwidget(subplt.o) +# end diff --git a/src/backends/template.jl b/src/backends/template.jl index 537903e9..db01eb2e 100644 --- a/src/backends/template.jl +++ b/src/backends/template.jl @@ -57,9 +57,9 @@ end # ---------------------------------------------------------------- -function _create_subplot(subplt::Subplot{[PkgName]AbstractBackend}, isbefore::Bool) - # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example -end +# function _create_subplot(subplt::Subplot{[PkgName]AbstractBackend}, isbefore::Bool) +# # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example +# end function _expand_limits(lims, plt::Plot{[PkgName]AbstractBackend}, isx::Bool) # TODO: call expand limits for each plot data @@ -79,6 +79,6 @@ function Base.display(::PlotsDisplay, plt::Plot{[PkgName]AbstractBackend}) # TODO: display/show the plot end -function Base.display(::PlotsDisplay, plt::Subplot{[PkgName]AbstractBackend}) - # TODO: display/show the subplot -end +# function Base.display(::PlotsDisplay, plt::Subplot{[PkgName]AbstractBackend}) +# # TODO: display/show the subplot +# end diff --git a/src/backends/unicodeplots.jl b/src/backends/unicodeplots.jl index 80e7a8e1..d2bd5f3c 100644 --- a/src/backends/unicodeplots.jl +++ b/src/backends/unicodeplots.jl @@ -247,10 +247,10 @@ end # we don't do very much for subplots... just stack them vertically -function _create_subplot(subplt::Subplot{UnicodePlotsBackend}, isbefore::Bool) - isbefore && return false - true -end +# function _create_subplot(subplt::Subplot{UnicodePlotsBackend}, isbefore::Bool) +# isbefore && return false +# true +# end function Base.display(::PlotsDisplay, plt::Plot{UnicodePlotsBackend}) @@ -260,8 +260,8 @@ end -function Base.display(::PlotsDisplay, subplt::Subplot{UnicodePlotsBackend}) - for plt in subplt.plts - gui(plt) - end -end +# function Base.display(::PlotsDisplay, subplt::Subplot{UnicodePlotsBackend}) +# for plt in subplt.plts +# gui(plt) +# end +# end diff --git a/src/backends/winston.jl b/src/backends/winston.jl index 4ea2ce54..6b90b457 100644 --- a/src/backends/winston.jl +++ b/src/backends/winston.jl @@ -261,9 +261,9 @@ end # ---------------------------------------------------------------- -function _create_subplot(subplt::Subplot{WinstonBackend}, isbefore::Bool) - # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example -end +# function _create_subplot(subplt::Subplot{WinstonBackend}, isbefore::Bool) +# # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example +# end # ---------------------------------------------------------------- @@ -302,6 +302,6 @@ function Base.display(::PlotsDisplay, plt::Plot{WinstonBackend}) end -function Base.display(::PlotsDisplay, subplt::Subplot{WinstonBackend}) - # TODO: display/show the Subplot object -end +# function Base.display(::PlotsDisplay, subplt::Subplot{WinstonBackend}) +# # TODO: display/show the Subplot object +# end diff --git a/src/components.jl b/src/components.jl index 84867249..9e073868 100644 --- a/src/components.jl +++ b/src/components.jl @@ -257,10 +257,6 @@ function text(str, args...) end # ----------------------------------------------------------------------- -# simple wrapper around a KW so we can hold all attributes pertaining to the axis in one place -type Axis #<: Associative{Symbol,Any} - d::KW -end xaxis(args...) = Axis("x", args...) yaxis(args...) = Axis("y", args...) @@ -299,7 +295,7 @@ function Axis(letter::AbstractString, args...; kw...) end # update an Axis object with magic args and keywords -function update!(a::Axis, args..., kw...) +function update!(a::Axis, args...; kw...) # first process args d = a.d for arg in args diff --git a/src/old_layouts.jl b/src/old_layouts.jl index 76f1bde8..8261a4d6 100644 --- a/src/old_layouts.jl +++ b/src/old_layouts.jl @@ -4,7 +4,7 @@ # ----------------------------------------------------------- "Simple grid, indices are row-major." -immutable GridLayout <: SubplotLayout +immutable GridLayout <: AbstractLayout nr::Int nc::Int end @@ -30,7 +30,7 @@ Base.getindex(layout::GridLayout, r::Int, c::Int) = (r-1) * layout.nc + c # ----------------------------------------------------------- "Number of plots per row" -immutable RowsLayout <: SubplotLayout +immutable RowsLayout <: AbstractLayout numplts::Int rowcounts::AbstractVector{Int} end @@ -62,7 +62,7 @@ Base.getindex(layout::RowsLayout, r::Int, c::Int) = sum(layout.rowcounts[1:r-1]) # ----------------------------------------------------------- "Flexible, nested layout with optional size percentages." -immutable FlexLayout <: SubplotLayout +immutable FlexLayout <: AbstractLayout n::Int grid::Matrix # Nested layouts. Each position # can be a plot index or another FlexLayout diff --git a/src/old_subplot.jl b/src/old_subplot.jl index 67baa993..64846daa 100644 --- a/src/old_subplot.jl +++ b/src/old_subplot.jl @@ -100,7 +100,7 @@ function subplot{P,I<:Integer}(pltsPerRow::AVec{I}, plt1::Plot{P}, plts::Plot{P} end # this will be called internally -function subplot{P<:AbstractBackend}(plts::AVec{Plot{P}}, layout::SubplotLayout, d::KW) +function subplot{P<:AbstractBackend}(plts::AVec{Plot{P}}, layout::AbstractLayout, d::KW) validateSubplotSupported() p = length(layout) n = sum([plt.n for plt in plts]) diff --git a/src/plot.jl b/src/plot.jl index f4f1f3ef..0a7b8726 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -47,10 +47,15 @@ function plot(args...; kw...) d = KW(kw) preprocessArgs!(d) + layout = pop!(d, :layout, Subplot()) + subplots = Subplot[layout] # TODO: build full list + smap = SubplotMap(1 => layout) # TODO: actually build a map + + # TODO: this seems wrong... I only call getPlotArgs when creating a new plot?? plotargs = merge(d, getPlotArgs(pkg, d, 1)) # plt = _create_plot(pkg, plotargs) # create a new, blank plot - plt = Plot(nothing, pkg, 0, plt.plotargs, KW[]) + plt = Plot(nothing, pkg, 0, plotargs, Series[], subplots, smap, layout) plt.o = _create_backend_figure(plt) # now update the plot diff --git a/src/series_new.jl b/src/series_new.jl index b5e2a5c5..07752e40 100644 --- a/src/series_new.jl +++ b/src/series_new.jl @@ -2,11 +2,7 @@ # we are going to build recipes to do the processing and splitting of the args -# build the argument dictionary for a series -# function getSeriesArgs(pkg::AbstractBackend, plotargs::KW, d, commandIndex::Int, plotIndex::Int, globalIndex::Int) # TODO, pass in plotargs, not plt function _add_defaults!(d::KW, plt::Plot, commandIndex::Int) - # kwdict = KW(d) - # d = KW() pkg = plt.backend n = plt.n plotargs = getplotargs(plt, n) @@ -18,16 +14,10 @@ function _add_defaults!(d::KW, plt::Plot, commandIndex::Int) setDictValue(d, d, k, commandIndex, _seriesDefaults) end - # # groupby args? - # for k in (:idxfilter, :numUncounted, :dataframe) - # if haskey(kwdict, k) - # d[k] = kwdict[k] - # end - # end - - # if haskey(_typeAliases, d[:seriestype]) - # d[:seriestype] = _typeAliases[d[:seriestype]] - # end + if d[:subplot_index] == :auto + # TODO: something useful + d[:subplot_index] = 1 + end aliasesAndAutopick(d, :axis, _axesAliases, supportedAxes(pkg), plotIndex) aliasesAndAutopick(d, :linestyle, _styleAliases, supportedStyles(pkg), plotIndex) @@ -76,9 +66,7 @@ function _add_defaults!(d::KW, plt::Plot, commandIndex::Int) label = string(label, " (R)") end d[:label] = label - - # warnOnUnsupported(pkg, d) - + d end diff --git a/src/subplots.jl b/src/subplots.jl index 1e99829f..b024c95e 100644 --- a/src/subplots.jl +++ b/src/subplots.jl @@ -1,18 +1,24 @@ -# ----------------------------------------------------------- -# GridLayout -# ----------------------------------------------------------- +Base.size(layout::EmptyLayout) = (0,0) +Base.length(layout::EmptyLayout) = 0 +Base.getindex(layout::EmptyLayout, r::Int, c::Int) = nothing + + +Base.size(layout::RootLayout) = (1,1) +Base.length(layout::RootLayout) = 1 +# Base.getindex(layout::RootLayout, r::Int, c::Int) = layout.child + +Base.size(subplot::Subplot) = (1,1) +Base.length(subplot::Subplot) = 1 +Base.getindex(subplot::Subplot, r::Int, c::Int) = subplot -"nested, gridded layout with optional size percentages." -immutable GridLayout <: SubplotLayout - grid::Matrix # Nested layouts. Each position is an AbstractSubplot or another GridLayout - widths::Vector{Float64} - heights::Vector{Float64} -end Base.size(layout::GridLayout) = size(layout.grid) Base.length(layout::GridLayout) = length(layout.grid) +Base.getindex(layout::GridLayout, r::Int, c::Int) = layout.grid[r,c] + + # Base.start(layout::GridLayout) = 1 # Base.done(layout::GridLayout, state) = state > length(layout) # function Base.next(layout::GridLayout, state) @@ -31,11 +37,10 @@ Base.length(layout::GridLayout) = length(layout.grid) # (r,c), state + 1 # end -nrows(layout::GridLayout) = size(layout, 1) -ncols(layout::GridLayout) = size(layout, 2) +# nrows(layout::GridLayout) = size(layout, 1) +# ncols(layout::GridLayout) = size(layout, 2) # get the plot index given row and column -Base.getindex(layout::GridLayout, r::Int, c::Int) = layout.grid[r,c] # ----------------------------------------------------------- diff --git a/src/types.jl b/src/types.jl index 0f581d79..aa7c5ba5 100644 --- a/src/types.jl +++ b/src/types.jl @@ -16,39 +16,74 @@ end wrap{T}(obj::T) = InputWrapper{T}(obj) Base.isempty(wrapper::InputWrapper) = false +# ----------------------------------------------------------- +# Axes +# ----------------------------------------------------------- + +# simple wrapper around a KW so we can hold all attributes pertaining to the axis in one place +type Axis #<: Associative{Symbol,Any} + d::KW +end + type AxisView label::UTF8String axis::Axis end -abstract AbstractSubplot -immutable EmptySubplot <: AbstractSubplot end -type Subplot <: AbstractSubplot - axisviews::Vector{AxisView} - subplotargs::KW # args specific to this subplot - obj # can store backend-specific data... like a pyplot ax +# ----------------------------------------------------------- +# Layouts +# ----------------------------------------------------------- + +abstract AbstractLayout + +# ----------------------------------------------------------- + +# contains blank space +immutable EmptyLayout <: AbstractLayout end + +# this is the parent of the top-level layout +immutable RootLayout <: AbstractLayout + # child::AbstractLayout end -type Series - d::KW - # x - # y - # z - # subplots::Vector{Subplot} +# ----------------------------------------------------------- + +# a single subplot +type Subplot <: AbstractLayout + parent::AbstractLayout + attr::KW # args specific to this subplot + # axisviews::Vector{AxisView} + o # can store backend-specific data... like a pyplot ax + + # Subplot(parent = RootLayout(); attr = KW()) end -# function Series(d::KW) -# x = pop!(d, :x) -# y = pop!(d, :y) -# z = pop!(d, :z) -# Series(d, x, y, z) -# end +Subplot() = Subplot(RootLayout(), KW(), nothing) + +# ----------------------------------------------------------- + +# nested, gridded layout with optional size percentages +immutable GridLayout <: AbstractLayout + parent::AbstractLayout + grid::Matrix{AbstractLayout} # Nested layouts. Each position is a AbstractLayout, which allows for arbitrary recursion + # widths::Vector{Float64} + # heights::Vector{Float64} + attr::KW +end + +# ----------------------------------------------------------- + +typealias SubplotMap Dict{Any, Subplot} # ----------------------------------------------------------- # Plot # ----------------------------------------------------------- +type Series + d::KW +end + type Plot{T<:AbstractBackend} <: AbstractPlot{T} o # the backend's plot object backend::T # the backend type @@ -57,19 +92,15 @@ type Plot{T<:AbstractBackend} <: AbstractPlot{T} # seriesargs::Vector{KW} # arguments for each series series_list::Vector{Series} # arguments for each series subplots::Vector{Subplot} + subplot_map::SubplotMap # provide any label as a map to a subplot + layout::AbstractLayout end -# ----------------------------------------------------------- -# Layout -# ----------------------------------------------------------- - -abstract SubplotLayout - # ----------------------------------------------------------- # Subplot # ----------------------------------------------------------- -# type Subplot{T<:AbstractBackend, L<:SubplotLayout} <: AbstractPlot{T} +# type Subplot{T<:AbstractBackend, L<:AbstractLayout} <: AbstractPlot{T} # o # the underlying object # plts::Vector{Plot{T}} # the individual plots # backend::T