diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index ad02fefb..3c2bbfe5 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -249,22 +249,21 @@ drawax(ax) = ax[:draw](renderer(ax[:get_figure]())) # BoundingBox(left, bottom, right, top) # end -function py_bbox_fig(obj) - fl, fr, fb, ft = fig[:get_window_extent]() +get_extents(obj) = obj[:get_window_extent]()[:get_points]() + +function py_bbox_fig(fig) + fl, fr, fb, ft = get_extents(fig) BoundingBox(0px, 0px, (fr-fl)*px, (ft-fb)*px) end +py_bbox_fig(plt::Plot) = py_bbox_fig(plt.o) # compute a bounding box (with origin top-left), however pyplot gives coords with origin bottom-left function py_bbox(obj) - fl, fr, fb, ft = py_bbox_fig(obj[:get_figure]()) - l, r, b, t = obj[:get_window_extent]()[:get_points]() + fl, fr, fb, ft = get_extents(obj[:get_figure]()) + l, r, b, t = get_extents(obj) BoundingBox(l*px, (ft-t)*px, (r-l)*px, (t-b)*px) - # BoundingBox(l*px, (t*px, (r-l)*px, (t-b)*px) end -# bbox_from_pyplot(obj) = - -py_bbox_fig(plt::Plot) = py_bbox_fig(plt.o) function py_bbox_ticks(ax, letter) # fig = ax[:get_figure]() @@ -301,10 +300,26 @@ end # the most extreme guide/axis in each direction. Can pass method (left,top,right,bottom) # and aggregation (minimum or maximum) into a method to do this. -min_padding_left(layout::Subplot{PyPlotBackend}) = 0mm -min_padding_top(layout::Subplot{PyPlotBackend}) = 0mm -min_padding_right(layout::Subplot{PyPlotBackend}) = 0mm -min_padding_bottom(layout::Subplot{PyPlotBackend}) = 0mm +min_padding_left(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, left, 1) +min_padding_top(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, top, -1) +min_padding_right(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, right, -1) +min_padding_bottom(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, bottom, 1) + +# loop over the guides and axes and compute how far they "stick out" from the plot area, +# so that we know the minimum padding we need to avoid cropping and overlapping text. +# `func` is one of (left,top,right,bottom), and we multiply by 1 or -1 depending on direction +function compute_min_padding(sp::Subplot{PyPlotBackend}, func::Function, mult::Number) + ax = sp.o + plotbb = py_bbox(ax) + padding = 0mm + for bb in (py_bbox_axis(ax, "x"), + py_bbox_axis(ax, "y"), + py_bbox_title(ax)) + diff = func(plotbb) - func(bb) + padding = max(padding, mult * diff) + end + padding +end # xaxis_height(sp::Subplot{PyPlotBackend}) = height(py_bbox_axis(sp.o,"x")) # yaxis_width(sp::Subplot{PyPlotBackend}) = width(py_bbox_axis(sp.o,"y")) @@ -336,9 +351,10 @@ min_padding_bottom(layout::Subplot{PyPlotBackend}) = 0mm function update_position!(sp::Subplot{PyPlotBackend}) ax = sp.o - figw, figh = size(py_bbox(ax[:get_figure]())) + figw, figh = size(py_bbox_fig(sp.plt)) - plot_bb = plotarea_bbox(sp) + # plot_bb = plotarea_bbox(sp) + plot_bb = sp.plotarea @show sp.bbox plot_bb # l = float(left(plot_bb) / px) / figw # b = float(bottom(plot_bb) / px) / figh diff --git a/src/plot.jl b/src/plot.jl index 83550e43..87bde9c5 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -57,6 +57,7 @@ function plot(args...; kw...) plt.layout, plt.subplots, plt.spmap = build_layout(plt.plotargs) for sp in plt.subplots # update the subplot/axis args from inputs, then pass to backend to init further + sp.plt = plt _update_subplot_args(plt, sp, d) _initialize_subplot(plt, sp) end diff --git a/src/subplots.jl b/src/subplots.jl index 9ab4b46f..39b61826 100644 --- a/src/subplots.jl +++ b/src/subplots.jl @@ -127,6 +127,7 @@ function update_child_bboxes!(layout::GridLayout) minpad_top = map(min_padding_top, layout.grid) minpad_right = map(min_padding_right, layout.grid) minpad_bottom = map(min_padding_bottom, layout.grid) + @show minpad_left minpad_top minpad_right minpad_bottom # get the max horizontal (left and right) padding over columns, # and max vertical (bottom and top) padding over rows @@ -135,42 +136,48 @@ function update_child_bboxes!(layout::GridLayout) pad_top = maximum(minpad_top, 2) pad_right = maximum(minpad_right, 1) pad_bottom = maximum(minpad_bottom, 2) + @show pad_left pad_top pad_right pad_bottom # scale this up to the total padding in each direction - total_pad_horizontal = (pad_left + pad_right) * nc - total_pad_vertical = (pad_top + pad_bottom) * nr + total_pad_horizontal = (pad_left + pad_right) .* nc + total_pad_vertical = (pad_top + pad_bottom) .* nr + @show total_pad_horizontal total_pad_vertical # now we can compute the total plot area in each direction total_plotarea_horizontal = width(layout) - total_pad_horizontal total_plotarea_vertical = height(layout) - total_pad_vertical + @show total_plotarea_horizontal total_plotarea_vertical # normalize widths/heights so they sum to 1 - layout.widths ./ sum(layout.widths) - layout.heights ./ sum(layout.heights) + denom_w = sum(layout.widths) + denom_h = sum(layout.heights) + @show denom_w, denom_h # we have all the data we need... lets compute the plot areas for r=1:nr, c=1:nc child = layout[r,c] # get the top-left corner of this child - child_left = (c == 1 ? 0mm : right(layout[r, c-1]) - child_top = (r == 1 ? 0mm : top(layout[r-1, c])) + child_left = (c == 1 ? 0mm : right(layout[r, c-1].bbox)) + child_top = (r == 1 ? 0mm : top(layout[r-1, c].bbox)) # compute plot area plotarea_left = child_left + pad_left[c] plotarea_top = child_top + pad_top[r] - plotarea_width = total_pad_horizontal * layout.widths[c] - plotarea_height = total_pad_vertical * layout.heights[r] - child.plotarea = BoundingBox(plotarea_left, plotarea_top, plotarea_width, plotarea_height) + plotarea_width = total_plotarea_horizontal[c] * layout.widths[c] / denom_w + plotarea_height = total_plotarea_vertical[r] * layout.heights[r] / denom_h + child_plotarea = BoundingBox(plotarea_left, plotarea_top, plotarea_width, plotarea_height) # compute child bbox child_width = pad_left[c] + plotarea_width + pad_right[c] child_height = pad_top[r] + plotarea_height + pad_bottom[r] - child.bbox = BoundingBox(child_left, child_top, child_width, child_height) + child_bbox = BoundingBox(child_left, child_top, child_width, child_height) + @show (r,c) child_plotarea child_bbox # the bounding boxes are currently relative to the parent, but we need them relative to the canvas - child.plotarea = crop(layout.bbox, child.plotarea) - child.bbox = crop(layout.bbox, child.plotarea) + plotarea!(child, crop(layout.bbox, child_plotarea)) + bbox!(child, crop(layout.bbox, child_bbox)) + @show child_plotarea child_bbox # recursively update the child's children update_child_bboxes!(child) diff --git a/src/types.jl b/src/types.jl index 9e66fad9..6db74964 100644 --- a/src/types.jl +++ b/src/types.jl @@ -47,6 +47,15 @@ typealias BBox Measures.Absolute2DBox const px = AbsoluteLength(0.254) const pct = Length{:pct, Float64}(1.0) +Base.(:.*)(m::Measure, n::Number) = m * n +Base.(:.*)(n::Number, m::Measure) = m * n +Base.(:-)(m::Measure, a::AbstractArray) = map(ai -> m - ai, a) +Base.(:-)(a::AbstractArray, m::Measure) = map(ai -> ai - m, a) +Base.zero(::Type{typeof(mm)}) = 0mm +Base.one(::Type{typeof(mm)}) = 1mm +Base.typemin(::typeof(mm)) = -Inf*mm +Base.typemax(::typeof(mm)) = Inf*mm + Base.(:+)(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 + m2.value)) Base.(:+)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (1 + m1.value)) Base.(:-)(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 - m2.value)) @@ -56,6 +65,12 @@ Base.(:*)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.v Base.(:/)(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value / m2.value) Base.(:/)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value / m1.value) + +Base.zero(::Type{typeof(pct)}) = 0pct +Base.one(::Type{typeof(pct)}) = 1pct +Base.typemin(::typeof(pct)) = 0pct +Base.typemax(::typeof(pct)) = 1pct + const defaultbox = BoundingBox(0mm, 0mm, 0mm, 0mm) # left(bbox::BoundingBox) = bbox.left @@ -120,6 +135,7 @@ abstract AbstractLayout width(layout::AbstractLayout) = width(layout.bbox) height(layout::AbstractLayout) = height(layout.bbox) +plotarea!(layout::AbstractLayout, bbox::BoundingBox) = nothing # ----------------------------------------------------------- @@ -151,12 +167,12 @@ type Subplot{T<:AbstractBackend} <: AbstractLayout end function Subplot{T<:AbstractBackend}(::T; parent = RootLayout()) - Subplot{T}(parent, defaultbox, KW(), nothing) + Subplot{T}(parent, defaultbox, defaultbox, KW(), nothing, nothing) end -# ----------------------------------------------------------- +plotarea!(sp::Subplot, bbox::BoundingBox) = (sp.plotarea = bbox) -# TODO: i'll want a plotarea! method to set the plotarea only if the field exists +# ----------------------------------------------------------- # nested, gridded layout with optional size percentages type GridLayout <: AbstractLayout