trying out measures

This commit is contained in:
Thomas Breloff 2016-05-17 15:45:05 -04:00
parent 2ac17afe30
commit 606229a08f
5 changed files with 173 additions and 89 deletions

View File

@ -5,3 +5,4 @@ Colors
Reexport
Compat
FixedSizeArrays
Measures

View File

@ -241,26 +241,33 @@ drawax(ax) = ax[:draw](renderer(ax[:get_figure]()))
# # merge a list of bounding boxes together to become the area that surrounds them all
# bbox_union(bboxes) = pytransforms.Bbox[:union](bboxes)
function py_bbox_pct(obj)
pybbox_pixels = obj[:get_window_extent]()
fig = obj[:get_figure]()
pybbox_pct = pybbox_pixels[:inverse_transformed](fig[:transFigure])
left, right, bottom, top = pybbox_pct[:get_points]()
BoundingBox(left, bottom, right, top)
# function py_bbox_pct(obj)
# pybbox_pixels = obj[:get_window_extent]()
# fig = obj[:get_figure]()
# pybbox_pct = pybbox_pixels[:inverse_transformed](fig[:transFigure])
# left, right, bottom, top = pybbox_pct[:get_points]()
# BoundingBox(left, bottom, right, top)
# end
function py_bbox(obj)
l, r, b, t = obj[:get_window_extent]()[:get_points]()
BoundingBox(l*px, b*px, (r-l)*px, (t-b)*px)
end
# bbox_from_pyplot(obj) =
py_bbox_fig(plt::Plot) = py_bbox(plt.o)
function py_bbox_ticks(ax, letter)
# fig = ax[:get_figure]()
# @show fig
labels = ax[symbol("get_"*letter*"ticklabels")]()
# @show labels
# bboxes = []
bbox_union = BoundingBox()
bbox_union = defaultbox
for lab in labels
# @show lab,lab[:get_text]()
bbox = py_bbox_pct(lab)
bbox = py_bbox(lab)
bbox_union = bbox_union + bbox
# @show bbox_union
end
@ -269,7 +276,7 @@ end
function py_bbox_axislabel(ax, letter)
pyaxis_label = ax[symbol("get_"*letter*"axis")]()[:label]
py_bbox_pct(pyaxis_label)
py_bbox(pyaxis_label)
end
# get a bounding box for the whole axis
@ -279,7 +286,7 @@ end
# get a bounding box for the title area
function py_bbox_title(ax)
py_bbox_pct(ax[:title])
py_bbox(ax[:title])
end
xaxis_height(sp::Subplot{PyPlotBackend}) = height(py_bbox_axis(sp.o,"x"))
@ -293,26 +300,37 @@ title_height(sp::Subplot{PyPlotBackend}) = height(py_bbox_title(sp.o))
# cache the plotarea bbox while we're doing that (need to add plotarea field to Subplot)
function plotarea_bbox(sp::Subplot{PyPlotBackend})
ax = sp.o
plot_bb = py_bbox_pct(ax)
plot_bb = py_bbox(ax)
xbb = py_bbox_axis(ax, "x")
ybb = py_bbox_axis(ax, "y")
titbb = py_bbox_title(ax)
items = [xbb, ybb, titbb]
# TODO: add in margin/padding from sp.attr
leftpad = max(0, left(plot_bb) - minimum(map(left, items)))
bottompad = max(0, bottom(plot_bb) - minimum(map(bottom, items)))
rightpad = max(0, maximum(map(right, items)) - right(plot_bb))
toppad = max(0, maximum(map(top, items)) - top(plot_bb))
# crop(bbox(sp), BoundingBox(yaxis_width(sp), xaxis_height(sp), 1, 1 - title_height(sp)))
crop(bbox(sp), BoundingBox(leftpad, bottompad, 1 - rightpad, 1 - toppad))
leftpad = max(0mm, left(plot_bb) - minimum(map(left, items)))
bottompad = max(0mm, bottom(plot_bb) - minimum(map(bottom, items)))
rightpad = max(0mm, maximum(map(right, items)) - right(plot_bb))
toppad = max(0mm, maximum(map(top, items)) - top(plot_bb))
crop(bbox(sp), BoundingBox(leftpad, bottompad,
width(sp) - rightpad - leftpad,
height(sp) - toppad - bottompad))
end
# ---------------------------------------------------------------------------
function update_position!(sp::Subplot{PyPlotBackend})
ax = sp.o
bb = plotarea_bbox(sp)
ax[:set_position]([f(bb) for f in (left, bottom, width, height)])
figw, figh = size(py_bbox(ax[:get_figure]()))
plot_bb = plotarea_bbox(sp)
@show sp.bbox plot_bb
# l = float(left(plot_bb) / px) / figw
# b = float(bottom(plot_bb) / px) / figh
# w = float(width(plot_bb) / px) / figw
mms = Float64[f(plot_bb).value for f in (left, bottom, width, height)]
@show mms
pcts = mms ./ Float64[figw.value, figh.value, figw.value, figh.value]
@show pcts
ax[:set_position](pcts)
end
# each backend should set up the subplot here
@ -1297,7 +1315,8 @@ function finalizePlot(plt::Plot{PyPlotBackend})
ax[:title][:set_color](getPyPlotColor(plt.plotargs[:titlefont].color))
end
drawfig(plt.o)
update_bboxes!(plt.layout)
plt.layout.bbox = py_bbox_fig(plt)
upadte_child_bboxes!(plt.layout)
update_position!(plt.layout)
PyPlot.draw()
end

View File

@ -1,39 +1,5 @@
left(bbox::BoundingBox) = bbox.left
bottom(bbox::BoundingBox) = bbox.bottom
right(bbox::BoundingBox) = bbox.right
top(bbox::BoundingBox) = bbox.top
width(bbox::BoundingBox) = bbox.right - bbox.left
height(bbox::BoundingBox) = bbox.top - bbox.bottom
Base.size(bbox::BoundingBox) = (width(bbox), height(bbox))
# union together bounding boxes
function Base.(:+)(bb1::BoundingBox, bb2::BoundingBox)
# empty boxes don't change the union
if width(bb1) <= 0 || height(bb1) <= 0
return bb2
elseif width(bb2) <= 0 || height(bb2) <= 0
return bb1
end
BoundingBox(
min(bb1.left, bb2.left),
min(bb1.bottom, bb2.bottom),
max(bb1.right, bb2.right),
max(bb1.top, bb2.top)
)
end
# this creates a bounding box in the parent's scope, where the child bounding box
# is relative to the parent
function crop(parent::BoundingBox, child::BoundingBox)
l = left(parent) + width(parent) * left(child)
w = width(parent) * width(child)
b = bottom(parent) + height(parent) * bottom(child)
h = height(parent) * height(child)
BoundingBox(l, b, l+w, b+h)
end
# ----------------------------------------------------------------------
@ -48,8 +14,8 @@ parent_bbox(layout::AbstractLayout) = bbox(parent(layout))
# this is a calculation of the percentage of free space available in the canvas
# after accounting for the size of guides and axes
free_size(layout::AbstractLayout) = (free_width(layout), free_height(layout))
free_width(layout::AbstractLayout) = 1.0 - used_width(layout)
free_height(layout::AbstractLayout) = 1.0 - used_height(layout)
free_width(layout::AbstractLayout) = width(layout.bbox) - used_width(layout)
free_height(layout::AbstractLayout) = height(layout.bbox) - used_height(layout)
used_size(layout::AbstractLayout) = (used_width(layout), used_height(layout))
@ -59,7 +25,7 @@ used_size(layout::AbstractLayout) = (used_width(layout), used_height(layout))
# end
update_position!(layout::AbstractLayout) = nothing
update_bboxes!(layout::AbstractLayout) = nothing
upadte_child_bboxes!(layout::AbstractLayout) = nothing
# ----------------------------------------------------------------------
@ -67,15 +33,15 @@ Base.size(layout::EmptyLayout) = (0,0)
Base.length(layout::EmptyLayout) = 0
Base.getindex(layout::EmptyLayout, r::Int, c::Int) = nothing
used_width(layout::EmptyLayout) = 0.0
used_height(layout::EmptyLayout) = 0.0
used_width(layout::EmptyLayout) = 0pct
used_height(layout::EmptyLayout) = 0pct
# ----------------------------------------------------------------------
Base.parent(::RootLayout) = nothing
parent_bbox(::RootLayout) = BoundingBox(0,0,1,1)
bbox(::RootLayout) = BoundingBox(0,0,1,1)
parent_bbox(::RootLayout) = defaultbox
bbox(::RootLayout) = defaultbox
# Base.size(layout::RootLayout) = (1,1)
# Base.length(layout::RootLayout) = 1
@ -100,7 +66,10 @@ used_height(sp::Subplot) = xaxis_height(sp) + title_height(sp)
# bounding box (relative to canvas) for plot area
# note: we assume the x axis is on the left, and y axis is on the bottom
function plotarea_bbox(sp::Subplot)
crop(bbox(sp), BoundingBox(yaxis_width(sp), xaxis_height(sp), 1, 1 - title_height(sp)))
xh = xaxis_height(sp)
yw = yaxis_width(sp)
crop(bbox(sp), BoundingBox(yw, xh, width(sp) - yw,
height(sp) - xh - title_height(sp)))
end
# NOTE: this is unnecessary I think as it is the same as bbox(::Subplot)
@ -117,16 +86,21 @@ function Base.setindex!(layout::GridLayout, v, r::Int, c::Int)
end
function used_width(layout::GridLayout)
w = 0.0
w = 0mm
nr,nc = size(layout)
for c=1:nc
@show w
w += maximum([used_width(layout[r,c]) for r=1:nr])
for r=1:nr
@show used_width(layout[r,c])
end
@show w
end
w
end
function used_height(layout::GridLayout)
h = 0.0
h = 0mm
nr,nc = size(layout)
for r=1:nr
h += maximum([used_height(layout[r,c]) for c=1:nc])
@ -140,39 +114,46 @@ update_position!(layout::GridLayout) = map(update_position!, layout.grid)
# up the tree, then assigning bounding boxes according to height/width percentages
# note: this should be called after all axis objects are updated to re-compute the
# bounding boxes for the layout tree
function update_bboxes!(layout::GridLayout) #, parent_bbox::BoundingBox = BoundingBox(0,0,1,1))
function upadte_child_bboxes!(layout::GridLayout) #, parent_bbox::BoundingBox = defaultbox)
# initialize the free space (per child!)
nr, nc = size(layout)
freew, freeh = free_size(layout)
@show freew, freeh
freew /= sum(layout.widths)
freeh /= sum(layout.heights)
@show freew, freeh
# TODO: this should really track used/free space for each row/column so that we can align plot areas properly
# l, b = 0.0, 0.0
rights = zeros(nc)
bottoms = ones(nr)
rights = Measure[0mm for i=1:nc] #zeros(nc) .* pct
bottoms = Measure[1pct for i=1:nr] # ones(nr) .* pct
for r=1:nr, c=1:nc
# compute the child's bounding box relative to the parent
child = layout[r,c]
usedw, usedh = used_size(child)
@show r,c, usedw, usedh
left = (c == 1 ? 0 : rights[c-1])
top = (r == 1 ? 1 : bottoms[r-1])
plot_l = (c == 1 ? 0mm : rights[c-1])
plot_t = (r == 1 ? height(layout) : bottoms[r-1])
# bottom = (r == 1 ? 0 : bottoms[r-1])
right = left + usedw + freew * layout.widths[c]
bottom = top - usedh - freeh * layout.heights[r]
# top = bottom + usedh + freeh * layout.heights[r]
child_bbox = BoundingBox(left, bottom, right, top)
plot_w = freew * layout.widths[c]
plot_h = freeh * layout.heights[r]
right = plot_l + usedw + plot_w
bottom = plot_t - usedh - plot_h
# plot_t = bottom + usedh + freeh * layout.heights[r]
child_bbox = BoundingBox(plot_l, bottom, plot_w, plot_h)
@show child_bbox
rights[c] = right
bottoms[r] = bottom
# then compute the bounding box relative to the canvas, and cache it in the child object
bbox!(child, crop(bbox(layout), child_bbox))
@show child.bbox
# now recursively update the child
update_bboxes!(child)
upadte_child_bboxes!(child)
end
end

View File

@ -35,20 +35,100 @@ end
# Layouts
# -----------------------------------------------------------
# wraps bounding box coords (percent of parent area)
# NOTE: (0,0) is the bottom-left, and (1,1) is the top-right!
immutable BoundingBox
left::Float64
bottom::Float64
right::Float64
top::Float64
# # wraps bounding box coords (percent of parent area)
# # NOTE: (0,0) is the bottom-left, and (1,1) is the top-right!
# immutable BoundingBox
# left::Float64
# bottom::Float64
# right::Float64
# top::Float64
# end
# BoundingBox() = BoundingBox(0,0,0,0)
import Measures
import Measures: Length, AbsoluteLength, Measure, BoundingBox, mm, cm, inch, pt, width, height
export BBox, BoundingBox, mm, cm, inch, pt, px, pct
typealias BBox Measures.Absolute2DBox
# allow pixels and percentages
const px = AbsoluteLength(0.254)
const pct = Length{:pct, Float64}(1.0)
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))
Base.(:-)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (m1.value - 1))
Base.(:*)(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * m2.value)
Base.(:*)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.value)
Base.(:/)(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value / m2.value)
Base.(:/)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value / m1.value)
const defaultbox = BoundingBox(0mm, 0mm, 20mm, 20mm)
# left(bbox::BoundingBox) = bbox.left
# bottom(bbox::BoundingBox) = bbox.bottom
# right(bbox::BoundingBox) = bbox.right
# top(bbox::BoundingBox) = bbox.top
# width(bbox::BoundingBox) = bbox.right - bbox.left
# height(bbox::BoundingBox) = bbox.top - bbox.bottom
left(bbox::BoundingBox) = bbox.x0[1]
bottom(bbox::BoundingBox) = bbox.x0[2]
right(bbox::BoundingBox) = left(bbox) + width(bbox)
top(bbox::BoundingBox) = bottom(bbox) + height(bbox)
Base.size(bbox::BoundingBox) = (width(bbox), height(bbox))
# Base.(:*){T,N}(m1::Length{T,N}, m2::Length{T,N}) = Length{T,N}(m1.value * m2.value)
ispositive(m::Measure) = m.value > 0
# union together bounding boxes
function Base.(:+)(bb1::BoundingBox, bb2::BoundingBox)
# empty boxes don't change the union
ispositive(width(bb1)) || return bb2
ispositive(height(bb1)) || return bb2
ispositive(width(bb2)) || return bb1
ispositive(height(bb2)) || return bb1
# if width(bb1) <= 0mm || height(bb1) <= 0mm
# return bb2
# elseif width(bb2) <= 0mm || height(bb2) <= 0mm
# return bb1
# end
l = min(left(bb1), left(bb2))
b = min(bottom(bb1), bottom(bb2))
r = max(right(bb1), right(bb2))
t = max(top(bb1), top(bb2))
BoundingBox(l, b, r-l, t-b)
end
# this creates a bounding box in the parent's scope, where the child bounding box
# is relative to the parent
function crop(parent::BoundingBox, child::BoundingBox)
# l = left(parent) + width(parent) * left(child)
# b = bottom(parent) + height(parent) * bottom(child)
# w = width(parent) * width(child)
# h = height(parent) * height(child)
l = left(parent) + left(child)
b = bottom(parent) + bottom(child)
w = width(child)
h = height(child)
BoundingBox(l, b, w, h)
end
function Base.show(io::IO, bbox::BoundingBox)
print(io, "BBox{l,b,r,t,w,h = $(left(bbox)), $(bottom(bbox)), $(right(bbox)), $(top(bbox)), $(width(bbox)), $(height(bbox))}")
end
BoundingBox() = BoundingBox(0,0,0,0)
# -----------------------------------------------------------
abstract AbstractLayout
width(layout::AbstractLayout) = width(layout.bbox)
height(layout::AbstractLayout) = height(layout.bbox)
# -----------------------------------------------------------
# contains blank space
@ -56,7 +136,7 @@ type EmptyLayout <: AbstractLayout
parent::AbstractLayout
bbox::BoundingBox
end
EmptyLayout(parent = RootLayout()) = EmptyLayout(parent, BoundingBox(0,0,1,1))
EmptyLayout(parent = RootLayout()) = EmptyLayout(parent, defaultbox)
# this is the parent of the top-level layout
immutable RootLayout <: AbstractLayout
@ -77,7 +157,7 @@ type Subplot{T<:AbstractBackend} <: AbstractLayout
end
function Subplot{T<:AbstractBackend}(::T; parent = RootLayout())
Subplot{T}(parent, BoundingBox(0,0,1,1), KW(), nothing)
Subplot{T}(parent, defaultbox, KW(), nothing)
end
# -----------------------------------------------------------
@ -87,8 +167,8 @@ type GridLayout <: AbstractLayout
parent::AbstractLayout
bbox::BoundingBox
grid::Matrix{AbstractLayout} # Nested layouts. Each position is a AbstractLayout, which allows for arbitrary recursion
widths::Vector{Float64}
heights::Vector{Float64}
widths::Vector{Measure}
heights::Vector{Measure}
attr::KW
end
@ -100,10 +180,12 @@ function GridLayout(dims...;
grid = Matrix{AbstractLayout}(dims...)
layout = GridLayout(
parent,
BoundingBox(0,0,1,1),
defaultbox,
grid,
convert(Vector{Float64}, widths),
convert(Vector{Float64}, heights),
Measure[w*pct for w in widths],
Measure[h*pct for h in heights],
# convert(Vector{Float64}, widths),
# convert(Vector{Float64}, heights),
KW(kw))
fill!(grid, EmptyLayout(layout))
layout

View File

@ -3,6 +3,7 @@ julia 0.4
RecipesBase
Colors
Reexport
Measures
FactCheck
Cairo
Gadfly