working on layouts
This commit is contained in:
parent
c5bcae1e34
commit
ae1f5b8b06
@ -66,6 +66,7 @@ function _initialize_backend(::PyPlotBackend)
|
||||
const pyticker = PyPlot.pywrap(PyPlot.pyimport("matplotlib.ticker"))
|
||||
const pycmap = PyPlot.pywrap(PyPlot.pyimport("matplotlib.cm"))
|
||||
const pynp = PyPlot.pywrap(PyPlot.pyimport("numpy"))
|
||||
const pytransforms = PyPlot.pywrap(PyPlot.pyimport("matplotlib.transforms"))
|
||||
end
|
||||
|
||||
PyPlot.ioff()
|
||||
@ -220,20 +221,110 @@ function add_pyfixedformatter(cbar, vals::AVec)
|
||||
cbar[:update_ticks]()
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Figure utils -- F*** matplotlib for making me work so hard to figure this crap out
|
||||
|
||||
# the drawing surface
|
||||
canvas(fig) = fig[:canvas]
|
||||
|
||||
# the object controlling draw commands
|
||||
renderer(fig) = canvas(fig)[:get_renderer]()
|
||||
|
||||
# draw commands... paint the screen (probably updating internals too)
|
||||
drawfig(fig) = fig[:draw](renderer(fig))
|
||||
drawax(ax) = ax[:draw](renderer(ax[:get_figure]()))
|
||||
|
||||
bbox(obj) = obj[:get_window_extent](renderer(obj[:get_figure]()))
|
||||
pos(obj) = obj[:get_position]()
|
||||
|
||||
# merge a list of bounding boxes together to become the area that surrounds them all
|
||||
bbox_union(bboxes) = pytransforms.Bbox[:union](bboxes)
|
||||
|
||||
function 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
|
||||
|
||||
# bbox_from_pyplot(obj) =
|
||||
|
||||
function bbox_ticks(ax, letter)
|
||||
# fig = ax[:get_figure]()
|
||||
# @show fig
|
||||
labels = ax[symbol("get_"*letter*"ticklabels")]()
|
||||
# @show labels
|
||||
# bboxes = []
|
||||
bbox_union = BoundingBox()
|
||||
for lab in labels
|
||||
# @show lab,lab[:get_text]()
|
||||
bbox = bbox_pct(lab)
|
||||
bbox_union = bbox_union + bbox
|
||||
# @show bbox_union
|
||||
end
|
||||
bbox_union
|
||||
end
|
||||
|
||||
function bbox_axislabel(ax, letter)
|
||||
pyaxis_label = ax[symbol("get_"*letter*"axis")]()
|
||||
bbox_pct(pyaxis_label)
|
||||
end
|
||||
|
||||
# get a bounding box for the whole axis
|
||||
function bbox_axis(ax, letter)
|
||||
bbox_ticks(ax, letter) + bbox_axislabel(ax, letter)
|
||||
end
|
||||
|
||||
# get a bounding box for the title area
|
||||
function bbox_title(ax)
|
||||
bbox_pct(ax[:title])
|
||||
end
|
||||
|
||||
xaxis_height(sp::Subplot{PyPlotBackend}) = height(bbox_axis(sp.o,"x"))
|
||||
yaxis_width(sp::Subplot{PyPlotBackend}) = width(bbox_axis(sp.o,"y"))
|
||||
title_height(sp::Subplot{PyPlotBackend}) = height(bbox_title(sp.o))
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# function used_width(sp::Subplot{PyPlotBackend})
|
||||
# ax = sp.o
|
||||
# width(bbox_axis(ax,"y"))
|
||||
# end
|
||||
#
|
||||
# function used_height(sp::Subplot{PyPlotBackend})
|
||||
# ax = sp.o
|
||||
# height(bbox_axis(ax,"x")) + height(bbox_title(ax))
|
||||
# end
|
||||
|
||||
|
||||
# # bounding box (relative to canvas) for plot area
|
||||
# function plotarea_bbox(sp::Subplot{PyPlotBackend})
|
||||
# crop(bbox(sp), BoundingBox())
|
||||
# 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)])
|
||||
end
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function getAxis(plt::Plot{PyPlotBackend}, subplot::Subplot = plt.subplots[1])
|
||||
if subplot.o == nothing
|
||||
@show subplot
|
||||
fig = plt.o
|
||||
@show plt
|
||||
@show fig
|
||||
# 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])
|
||||
# left, bottom, width, height = 0.3, 0.3, 0.5, 0.5
|
||||
|
||||
# init to the full canvas, then we can set_position later
|
||||
ax = fig[:add_axes]([0,0,1,1])
|
||||
subplot.o = ax
|
||||
end
|
||||
subplot.o
|
||||
@ -1183,6 +1274,9 @@ function finalizePlot(plt::Plot{PyPlotBackend})
|
||||
ax = getLeftAxis(plt)
|
||||
addPyPlotLegend(plt, ax)
|
||||
updateAxisColors(ax, plt.plotargs)
|
||||
drawfig(plt.o)
|
||||
update_bboxes!(plt.layout)
|
||||
update_position!(plt.layout)
|
||||
PyPlot.draw()
|
||||
end
|
||||
|
||||
|
||||
@ -47,15 +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
|
||||
layout, subplots, spmap = build_layout(pop!(d, :layout, :auto))
|
||||
# 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, plotargs, Series[], subplots, smap, layout)
|
||||
plt = Plot(nothing, pkg, 0, plotargs, Series[], subplots, spmap, layout)
|
||||
plt.o = _create_backend_figure(plt)
|
||||
|
||||
# now update the plot
|
||||
|
||||
175
src/subplots.jl
175
src/subplots.jl
@ -1,23 +1,188 @@
|
||||
|
||||
|
||||
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
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# 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)
|
||||
|
||||
# layouts are recursive, tree-like structures, and most will have a parent field
|
||||
Base.parent(layout::AbstractLayout) = layout.parent
|
||||
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)
|
||||
|
||||
used_size(layout::AbstractLayout) = (used_width(layout), used_height(layout))
|
||||
|
||||
# # compute the area which is available to this layout
|
||||
# function bbox_pct(layout::AbstractLayout)
|
||||
#
|
||||
# end
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
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
|
||||
|
||||
Base.size(layout::RootLayout) = (1,1)
|
||||
Base.length(layout::RootLayout) = 1
|
||||
update_position!(layout::EmptyLayout) = nothing
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
Base.parent(::RootLayout) = nothing
|
||||
parent_bbox(::RootLayout) = BoundingBox(0,0,1,1)
|
||||
bbox(::RootLayout) = BoundingBox(0,0,1,1)
|
||||
|
||||
# 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
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
Base.size(sp::Subplot) = (1,1)
|
||||
Base.length(sp::Subplot) = 1
|
||||
Base.getindex(sp::Subplot, r::Int, c::Int) = sp
|
||||
|
||||
|
||||
used_width(sp::Subplot) = yaxis_width(sp)
|
||||
used_height(sp::Subplot) = xaxis_height(sp) + title_height(sp)
|
||||
|
||||
# used_width(subplot::Subplot) = error("used_width(::Subplot) must be implemented by each backend")
|
||||
# used_height(subplot::Subplot) = error("used_height(::Subplot) must be implemented by each backend")
|
||||
|
||||
# # this should return a bounding box (relative to the canvas) for the plot area (inside the spines/ticks)
|
||||
# plotarea_bbox(subplot::Subplot) = error("plotarea_bbox(::Subplot) must be implemented by each backend")
|
||||
|
||||
# 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)))
|
||||
end
|
||||
|
||||
# NOTE: this is unnecessary I think as it is the same as bbox(::Subplot)
|
||||
# # this should return a bounding box (relative to the canvas) for the whole subplot (plotarea, ticks, and guides)
|
||||
# subplot_bbox(subplot::Subplot) = error("subplot_bbox(::Subplot) must be implemented by each backend")
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
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]
|
||||
function Base.setindex!(layout::GridLayout, v, r::Int, c::Int)
|
||||
layout.grid[r,c] = v
|
||||
end
|
||||
|
||||
function used_width(layout::GridLayout)
|
||||
w = 0.0
|
||||
nr,nc = size(layout)
|
||||
for c=1:nc
|
||||
w += maximum([used_width(layout(r,c)) for r=1:nr])
|
||||
end
|
||||
w
|
||||
end
|
||||
|
||||
function used_height(layout::GridLayout)
|
||||
h = 0.0
|
||||
nr,nc = size(layout)
|
||||
for r=1:nr
|
||||
h += maximum([used_height(layout(r,c)) for c=1:nc])
|
||||
end
|
||||
h
|
||||
end
|
||||
|
||||
update_position!(layout::GridLayout) = map(update_position!, layout.grid)
|
||||
|
||||
# a recursive method to first compute the free space by bubbling the free space
|
||||
# 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))
|
||||
# initialize the free space (per child!)
|
||||
nr, nc = size(layout)
|
||||
freew, freeh = free_size(layout)
|
||||
freew /= nc
|
||||
freeh /= nr
|
||||
|
||||
# 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
|
||||
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)
|
||||
child_bbox = BoundingBox(l, b, l + usedw + freew, b + usedh + freeh)
|
||||
|
||||
# then compute the bounding box relative to the canvas, and cache it in the child object
|
||||
bbox!(child, crop(bbox(layout), child_bbox))
|
||||
|
||||
# now recursively update the child
|
||||
update_bboxes!(child)
|
||||
end
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# return the top-level layout, a list of subplots, and a SubplotMap
|
||||
function build_layout(s::Symbol)
|
||||
s == :auto || error() # TODO: handle anything else
|
||||
layout = GridLayout(1, 2)
|
||||
nr, nc = size(layout)
|
||||
subplots = Subplot[]
|
||||
spmap = SubplotMap()
|
||||
for r=1:nr, c=1:nc
|
||||
sp = Subplot(backend(), parent=layout)
|
||||
layout[r,c] = sp
|
||||
push!(subplots, sp)
|
||||
spmap[(r,c)] = sp
|
||||
end
|
||||
layout, subplots, spmap
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# ----------------------------------------------------------------------
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Base.start(layout::GridLayout) = 1
|
||||
# Base.done(layout::GridLayout, state) = state > length(layout)
|
||||
|
||||
50
src/types.jl
50
src/types.jl
@ -35,12 +35,28 @@ 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
|
||||
end
|
||||
BoundingBox() = BoundingBox(0,0,0,0)
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
abstract AbstractLayout
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# contains blank space
|
||||
immutable EmptyLayout <: AbstractLayout end
|
||||
type EmptyLayout <: AbstractLayout
|
||||
parent::AbstractLayout
|
||||
bbox::BoundingBox
|
||||
end
|
||||
EmptyLayout(parent) = EmptyLayout(parent, BoundingBox(0,0,1,1))
|
||||
|
||||
# this is the parent of the top-level layout
|
||||
immutable RootLayout <: AbstractLayout
|
||||
@ -50,8 +66,9 @@ end
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# a single subplot
|
||||
type Subplot <: AbstractLayout
|
||||
type Subplot{T<:AbstractBackend} <: AbstractLayout
|
||||
parent::AbstractLayout
|
||||
bbox::BoundingBox # the canvas area which is available to this subplot
|
||||
attr::KW # args specific to this subplot
|
||||
# axisviews::Vector{AxisView}
|
||||
o # can store backend-specific data... like a pyplot ax
|
||||
@ -59,19 +76,40 @@ type Subplot <: AbstractLayout
|
||||
# Subplot(parent = RootLayout(); attr = KW())
|
||||
end
|
||||
|
||||
Subplot() = Subplot(RootLayout(), KW(), nothing)
|
||||
function Subplot{T<:AbstractBackend}(::T; parent = RootLayout())
|
||||
Subplot{T}(parent, BoundingBox(0,0,1,1), KW(), nothing)
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# nested, gridded layout with optional size percentages
|
||||
immutable GridLayout <: AbstractLayout
|
||||
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{Float64}
|
||||
heights::Vector{Float64}
|
||||
attr::KW
|
||||
end
|
||||
|
||||
function GridLayout(dims...;
|
||||
parent = RootLayout(),
|
||||
widths = ones(dims[2]),
|
||||
heights = ones(dims[1]),
|
||||
kw...)
|
||||
grid = Matrix{AbstractLayout}(dims...)
|
||||
layout = GridLayout(
|
||||
parent,
|
||||
BoundingBox(0,0,1,1),
|
||||
grid,
|
||||
convert(Vector{Float64}, widths),
|
||||
convert(Vector{Float64}, heights),
|
||||
KW(kw))
|
||||
fill!(grid, EmptyLayout(layout))
|
||||
layout
|
||||
end
|
||||
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
typealias SubplotMap Dict{Any, Subplot}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user