diff --git a/src/Plots.jl b/src/Plots.jl index 1f16e68c..b5725c07 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -19,6 +19,8 @@ export GridLayout, grid, EmptyLayout, + bbox, + plotarea, @layout, AVec, AMat, diff --git a/src/arg_desc.jl b/src/arg_desc.jl index f0cab83a..e2e4247e 100644 --- a/src/arg_desc.jl +++ b/src/arg_desc.jl @@ -60,6 +60,7 @@ const _arg_desc = KW( :link => "Symbol. How/whether to link axis limits between subplots. Values: `:none`, `:x` (x axes are linked by columns), `:y` (y axes are linked by rows), `:both` (x and y are linked), `:all` (every subplot is linked together regardless of layout position).", :overwrite_figure => "Bool. Should we reuse the same GUI window/figure when plotting (true) or open a new one (false).", :html_output_format => "Symbol. When writing html output, what is the format? `:png` and `:svg` are currently supported.", +:inset_subplots => "nothing or vector of 2-tuple (parent,bbox). optionally pass a vector of (parent,bbox) tuples which are the parent layout and the relative bounding box of inset subplots", # subplot args :title => "String. Subplot title.", diff --git a/src/args.jl b/src/args.jl index 1a3962b8..e673c937 100644 --- a/src/args.jl +++ b/src/args.jl @@ -198,6 +198,8 @@ const _plot_defaults = KW( :link => :none, :overwrite_figure => true, :html_output_format => :auto, + :inset_subplots => nothing, # optionally pass a vector of (parent,bbox) tuples which are + # the parent layout and the relative bounding box of inset subplots ) @@ -256,6 +258,7 @@ const _suppress_warnings = Set{Symbol}([ :plot_object, :primary, :smooth, + :relative_bbox, ]) # add defaults for the letter versions diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 249575b5..538dc404 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -576,8 +576,8 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) x1, x2 = xaxis[:flip] ? (xmax,xmin) : (xmin,xmax) y1, y2 = yaxis[:flip] ? (ymax,ymin) : (ymin,ymax) - GR.axes(xtick, ytick, x1, y1, majorx, majory, ticksize) - GR.axes(xtick, ytick, x2, y2, -majorx, -majory, -ticksize) + GR.axes(xtick, ytick, x1, y1, 1, 1, ticksize) + GR.axes(xtick, ytick, x2, y2, -1, -1, -ticksize) end # end diff --git a/src/layouts.jl b/src/layouts.jl index 1944f8fc..0e2fa547 100644 --- a/src/layouts.jl +++ b/src/layouts.jl @@ -101,6 +101,9 @@ end Base.show(io::IO, layout::AbstractLayout) = print(io, "$(typeof(layout))$(size(layout))") +# create a new bbox +bbox(left, top, w, h) = BoundingBox(left, top, w, h) + # this is the available area for drawing everything in this layout... as percentages of total canvas bbox(layout::AbstractLayout) = layout.bbox bbox!(layout::AbstractLayout, bb::BoundingBox) = (layout.bbox = bb) @@ -116,8 +119,12 @@ parent_bbox(layout::AbstractLayout) = bbox(parent(layout)) update_position!(layout::AbstractLayout) = nothing update_child_bboxes!(layout::AbstractLayout, minimum_perimeter = [0mm,0mm,0mm,0mm]) = nothing -width(layout::AbstractLayout) = width(layout.bbox) -height(layout::AbstractLayout) = height(layout.bbox) +left(layout::AbstractLayout) = left(bbox(layout)) +top(layout::AbstractLayout) = top(bbox(layout)) +right(layout::AbstractLayout) = right(bbox(layout)) +bottom(layout::AbstractLayout) = bottom(bbox(layout)) +width(layout::AbstractLayout) = width(bbox(layout)) +height(layout::AbstractLayout) = height(bbox(layout)) plotarea(layout::AbstractLayout) = defaultbox plotarea!(layout::AbstractLayout, bbox::BoundingBox) = nothing @@ -335,6 +342,26 @@ function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0 end end +function update_inset_bboxes!(plt::Plot) + for sp in plt.inset_subplots + relative_bbox = sp[:relative_bbox] + # TODO: need to handle percentages... right now only AbsoluteLength works + + bb = bbox!(sp, bbox( + left(sp.parent) + left(relative_bbox), + top(sp.parent) + top(relative_bbox), + width(relative_bbox), + height(relative_bbox) + )) + + plotarea!(sp, bbox( + left(bb) + leftpad(sp), + top(bb) + toppad(sp), + width(bb) - leftpad(sp) - rightpad(sp), + height(bb) - toppad(sp) - bottompad(sp) + )) + end +end # ---------------------------------------------------------------------- diff --git a/src/plot.jl b/src/plot.jl index 9db85544..191a4d8b 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -350,6 +350,27 @@ function _plot!(plt::Plot, d::KW, args...) sp.attr[:subplot_index] = idx end + # handle inset subplots + insets = plt[:inset_subplots] + if insets != nothing + for (parent, bb) in insets + P = typeof(parent) + if P <: Integer + parent = plt.subplots[parent] + elseif P == Symbol + parent = plt.spmap[parent] + else + parent = plt.layout + end + sp = Subplot(backend(), parent=parent) + sp.plt = plt + sp.attr[:relative_bbox] = bb + push!(plt.subplots, sp) + sp.attr[:subplot_index] = length(plt.subplots) + push!(plt.inset_subplots, sp) + end + end + plt.init = true end @@ -440,10 +461,16 @@ function prepare_output(plt::Plot) # One pass down and back up the tree to compute the minimum padding # of the children on the perimeter. This is an backend callback. _update_min_padding!(plt.layout) + for sp in plt.inset_subplots + _update_min_padding!(sp) + end # now another pass down, to update the bounding boxes update_child_bboxes!(plt.layout) + # update those bounding boxes of inset subplots + update_inset_bboxes!(plt) + # the backend callback, to reposition subplots, etc _update_plot_object(plt) end diff --git a/src/types.jl b/src/types.jl index c39572c1..59bcb5f2 100644 --- a/src/types.jl +++ b/src/types.jl @@ -73,12 +73,14 @@ type Plot{T<:AbstractBackend} <: AbstractPlot{T} subplots::Vector{Subplot} spmap::SubplotMap # provide any label as a map to a subplot layout::AbstractLayout + inset_subplots::Vector{Subplot} # list of inset subplots init::Bool end function Plot() Plot(backend(), 0, KW(), KW(), Series[], nothing, - Subplot[], SubplotMap(), EmptyLayout(), false) + Subplot[], SubplotMap(), EmptyLayout(), + Subplot[], false) end # TODO: make a decision... should plt[1] return the first subplot or the first series??