724 lines
24 KiB
Julia
724 lines
24 KiB
Julia
|
|
# NOTE: (0,0) is the top-left !!!
|
|
|
|
# allow pixels and percentages
|
|
const px = AbsoluteLength(0.254)
|
|
const pct = Length{:pct, Float64}(1.0)
|
|
|
|
to_pixels(m::AbsoluteLength) = m / 0.254
|
|
|
|
const _cbar_width = 5mm
|
|
|
|
@compat Base.:.*(m::Measure, n::Number) = m * n
|
|
@compat Base.:.*(n::Number, m::Measure) = m * n
|
|
@compat Base.:-(m::Measure, a::AbstractArray) = map(ai -> m - ai, a)
|
|
@compat 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.convert{F<:AbstractFloat}(::Type{F}, l::AbsoluteLength) = convert(F, l.value)
|
|
|
|
# TODO: these are unintuitive and may cause tricky bugs
|
|
# @compat Base.:+(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 + m2.value))
|
|
# @compat Base.:+(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (1 + m1.value))
|
|
# @compat Base.:-(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 - m2.value))
|
|
# @compat Base.:-(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (m1.value - 1))
|
|
|
|
@compat Base.:*(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * m2.value)
|
|
@compat Base.:*(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.value)
|
|
@compat Base.:/(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value / m2.value)
|
|
@compat 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.x0[1]
|
|
top(bbox::BoundingBox) = bbox.x0[2]
|
|
right(bbox::BoundingBox) = left(bbox) + width(bbox)
|
|
bottom(bbox::BoundingBox) = top(bbox) + height(bbox)
|
|
Base.size(bbox::BoundingBox) = (width(bbox), height(bbox))
|
|
|
|
# @compat 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
|
|
@compat 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
|
|
|
|
l = min(left(bb1), left(bb2))
|
|
t = min(top(bb1), top(bb2))
|
|
r = max(right(bb1), right(bb2))
|
|
b = max(bottom(bb1), bottom(bb2))
|
|
BoundingBox(l, t, r-l, b-t)
|
|
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) + left(child)
|
|
t = top(parent) + top(child)
|
|
w = width(child)
|
|
h = height(child)
|
|
BoundingBox(l, t, w, h)
|
|
end
|
|
|
|
# convert x,y coordinates from absolute coords to percentages...
|
|
# returns x_pct, y_pct
|
|
function xy_mm_to_pcts(x::AbsoluteLength, y::AbsoluteLength, figw, figh, flipy = true)
|
|
xmm, ymm = x.value, y.value
|
|
if flipy
|
|
ymm = figh.value - ymm # flip y when origin in bottom-left
|
|
end
|
|
xmm / figw.value, ymm / figh.value
|
|
end
|
|
|
|
# convert a bounding box from absolute coords to percentages...
|
|
# returns an array of percentages of figure size: [left, bottom, width, height]
|
|
function bbox_to_pcts(bb::BoundingBox, figw, figh, flipy = true)
|
|
mms = Float64[f(bb).value for f in (left,bottom,width,height)]
|
|
if flipy
|
|
mms[2] = figh.value - mms[2] # flip y when origin in bottom-left
|
|
end
|
|
mms ./ Float64[figw.value, figh.value, figw.value, figh.value]
|
|
end
|
|
|
|
function Base.show(io::IO, bbox::BoundingBox)
|
|
print(io, "BBox{l,t,r,b,w,h = $(left(bbox)),$(top(bbox)), $(right(bbox)),$(bottom(bbox)), $(width(bbox)),$(height(bbox))}")
|
|
end
|
|
|
|
# -----------------------------------------------------------
|
|
# AbstractLayout
|
|
|
|
Base.show(io::IO, layout::AbstractLayout) = print(io, "$(typeof(layout))$(size(layout))")
|
|
|
|
# create a new bbox
|
|
function bbox(x, y, w, h; h_anchor = :left, v_anchor = :top)
|
|
left = if h_anchor == :left
|
|
x
|
|
else
|
|
x - w * (h_anchor == :right ? 1.0 : 0.5)
|
|
end
|
|
top = if v_anchor == :top
|
|
y
|
|
else
|
|
y - h * (v_anchor == :bottom ? 1.0 : 0.5)
|
|
end
|
|
BoundingBox(left, top, w, 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))
|
|
|
|
# padding_w(layout::AbstractLayout) = left_padding(layout) + right_padding(layout)
|
|
# padding_h(layout::AbstractLayout) = bottom_padding(layout) + top_padding(layout)
|
|
# padding(layout::AbstractLayout) = (padding_w(layout), padding_h(layout))
|
|
|
|
update_position!(layout::AbstractLayout) = nothing
|
|
update_child_bboxes!(layout::AbstractLayout, minimum_perimeter = [0mm,0mm,0mm,0mm]) = nothing
|
|
|
|
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
|
|
|
|
attr(layout::AbstractLayout, k::Symbol) = layout.attr[k]
|
|
attr(layout::AbstractLayout, k::Symbol, v) = get(layout.attr, k, v)
|
|
attr!(layout::AbstractLayout, v, k::Symbol) = (layout.attr[k] = v)
|
|
hasattr(layout::AbstractLayout, k::Symbol) = haskey(layout.attr, k)
|
|
|
|
leftpad(layout::AbstractLayout) = 0mm
|
|
toppad(layout::AbstractLayout) = 0mm
|
|
rightpad(layout::AbstractLayout) = 0mm
|
|
bottompad(layout::AbstractLayout) = 0mm
|
|
|
|
# -----------------------------------------------------------
|
|
# RootLayout
|
|
|
|
# this is the parent of the top-level layout
|
|
immutable RootLayout <: AbstractLayout end
|
|
|
|
Base.parent(::RootLayout) = nothing
|
|
parent_bbox(::RootLayout) = defaultbox
|
|
bbox(::RootLayout) = defaultbox
|
|
|
|
# -----------------------------------------------------------
|
|
# EmptyLayout
|
|
|
|
# contains blank space
|
|
type EmptyLayout <: AbstractLayout
|
|
parent::AbstractLayout
|
|
bbox::BoundingBox
|
|
attr::KW # store label, width, and height for initialization
|
|
# label # this is the label that the subplot will take (since we create a layout before initialization)
|
|
end
|
|
EmptyLayout(parent = RootLayout(); kw...) = EmptyLayout(parent, defaultbox, KW(kw))
|
|
|
|
Base.size(layout::EmptyLayout) = (0,0)
|
|
Base.length(layout::EmptyLayout) = 0
|
|
Base.getindex(layout::EmptyLayout, r::Int, c::Int) = nothing
|
|
|
|
_update_min_padding!(layout::EmptyLayout) = nothing
|
|
|
|
# -----------------------------------------------------------
|
|
# GridLayout
|
|
|
|
# nested, gridded layout with optional size percentages
|
|
type GridLayout <: AbstractLayout
|
|
parent::AbstractLayout
|
|
minpad::Tuple # leftpad, toppad, rightpad, bottompad
|
|
bbox::BoundingBox
|
|
grid::Matrix{AbstractLayout} # Nested layouts. Each position is a AbstractLayout, which allows for arbitrary recursion
|
|
widths::Vector{Measure}
|
|
heights::Vector{Measure}
|
|
attr::KW
|
|
end
|
|
|
|
grid(args...; kw...) = GridLayout(args...; kw...)
|
|
|
|
function GridLayout(dims...;
|
|
parent = RootLayout(),
|
|
widths = zeros(dims[2]),
|
|
heights = zeros(dims[1]),
|
|
kw...)
|
|
grid = Matrix{AbstractLayout}(dims...)
|
|
layout = GridLayout(
|
|
parent,
|
|
(20mm, 5mm, 2mm, 10mm),
|
|
defaultbox,
|
|
grid,
|
|
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
|
|
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]
|
|
function Base.setindex!(layout::GridLayout, v, r::Int, c::Int)
|
|
layout.grid[r,c] = v
|
|
end
|
|
|
|
leftpad(layout::GridLayout) = layout.minpad[1]
|
|
toppad(layout::GridLayout) = layout.minpad[2]
|
|
rightpad(layout::GridLayout) = layout.minpad[3]
|
|
bottompad(layout::GridLayout) = layout.minpad[4]
|
|
|
|
|
|
# here's how this works... first we recursively "update the minimum padding" (which
|
|
# means to calculate the minimum size needed from the edge of the subplot to plot area)
|
|
# for the whole layout tree. then we can compute the "padding borders" of this
|
|
# layout as the biggest padding of the children on the perimeter. then we need to
|
|
# recursively pass those borders back down the tree, one side at a time, but ONLY
|
|
# to those perimeter children.
|
|
|
|
# leftpad, toppad, rightpad, bottompad
|
|
function _update_min_padding!(layout::GridLayout)
|
|
map(_update_min_padding!, layout.grid)
|
|
layout.minpad = (
|
|
maximum(map(leftpad, layout.grid[:,1])),
|
|
maximum(map(toppad, layout.grid[1,:])),
|
|
maximum(map(rightpad, layout.grid[:,end])),
|
|
maximum(map(bottompad, layout.grid[end,:]))
|
|
)
|
|
end
|
|
|
|
|
|
function update_position!(layout::GridLayout)
|
|
map(update_position!, layout.grid)
|
|
end
|
|
|
|
# some lengths are fixed... we have to split up the free space among the list v
|
|
function recompute_lengths(v)
|
|
# dump(v)
|
|
tot = 0pct
|
|
cnt = 0
|
|
for vi in v
|
|
if vi == 0pct
|
|
cnt += 1
|
|
else
|
|
tot += vi
|
|
end
|
|
end
|
|
leftover = 1.0pct - tot
|
|
if cnt > 1 && leftover.value <= 0
|
|
error("Not enough length left over in layout! v = $v, cnt = $cnt, leftover = $leftover")
|
|
end
|
|
|
|
# now fill in the blanks
|
|
Measure[(vi == 0pct ? leftover / cnt : vi) for vi in v]
|
|
end
|
|
|
|
# recursively compute the bounding boxes for the layout and plotarea (relative to canvas!)
|
|
function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0mm,0mm])
|
|
nr, nc = size(layout)
|
|
|
|
# # create a matrix for each minimum padding direction
|
|
# _update_min_padding!(layout)
|
|
|
|
minpad_left = map(leftpad, layout.grid)
|
|
minpad_top = map(toppad, layout.grid)
|
|
minpad_right = map(rightpad, layout.grid)
|
|
minpad_bottom = map(bottompad, 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
|
|
# TODO: add extra padding here
|
|
pad_left = maximum(minpad_left, 1)
|
|
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
|
|
|
|
# make sure the perimeter match the parent
|
|
pad_left[1] = max(pad_left[1], minimum_perimeter[1])
|
|
pad_top[1] = max(pad_top[1], minimum_perimeter[2])
|
|
pad_right[end] = max(pad_right[end], minimum_perimeter[3])
|
|
pad_bottom[end] = max(pad_bottom[end], minimum_perimeter[4])
|
|
|
|
# scale this up to the total padding in each direction
|
|
total_pad_horizontal = sum(pad_left + pad_right)
|
|
total_pad_vertical = sum(pad_top + pad_bottom)
|
|
# @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
|
|
|
|
# recompute widths/heights
|
|
layout.widths = recompute_lengths(layout.widths)
|
|
layout.heights = recompute_lengths(layout.heights)
|
|
# @show layout.widths layout.heights
|
|
|
|
# normalize widths/heights so they sum to 1
|
|
# denom_w = sum(layout.widths)
|
|
# denom_h = sum(layout.heights)
|
|
# @show layout.widths layout.heights denom_w, denom_h
|
|
|
|
# we have all the data we need... lets compute the plot areas and set the bounding boxes
|
|
for r=1:nr, c=1:nc
|
|
child = layout[r,c]
|
|
|
|
# get the top-left corner of this child... the first one is top-left of the parent (i.e. layout)
|
|
child_left = (c == 1 ? left(layout.bbox) : right(layout[r, c-1].bbox))
|
|
child_top = (r == 1 ? top(layout.bbox) : bottom(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_plotarea_horizontal * layout.widths[c]
|
|
plotarea_height = total_plotarea_vertical * layout.heights[r]
|
|
plotarea!(child, 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]
|
|
bbox!(child, BoundingBox(child_left, child_top, child_width, child_height))
|
|
|
|
# this is the minimum perimeter as decided by this child's parent, so that
|
|
# all children on this border have the same value
|
|
min_child_perimeter = [
|
|
c == 1 ? layout.minpad[1] : 0mm,
|
|
r == 1 ? layout.minpad[2] : 0mm,
|
|
c == nc ? layout.minpad[3] : 0mm,
|
|
r == nr ? layout.minpad[4] : 0mm
|
|
]
|
|
|
|
# recursively update the child's children
|
|
update_child_bboxes!(child, min_child_perimeter)
|
|
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
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
calc_num_subplots(layout::AbstractLayout) = 1
|
|
function calc_num_subplots(layout::GridLayout)
|
|
tot = 0
|
|
for l in layout.grid
|
|
tot += calc_num_subplots(l)
|
|
end
|
|
tot
|
|
end
|
|
|
|
function compute_gridsize(numplts::Int, nr::Int, nc::Int)
|
|
# figure out how many rows/columns we need
|
|
if nr < 1
|
|
if nc < 1
|
|
nr = round(Int, sqrt(numplts))
|
|
nc = ceil(Int, numplts / nr)
|
|
else
|
|
nr = ceil(Int, numplts / nc)
|
|
end
|
|
else
|
|
nc = ceil(Int, numplts / nr)
|
|
end
|
|
nr, nc
|
|
end
|
|
|
|
# ----------------------------------------------------------------------
|
|
# constructors
|
|
|
|
# pass the layout arg through
|
|
function layout_args(d::KW)
|
|
layout_args(get(d, :layout, default(:layout)))
|
|
end
|
|
|
|
function layout_args(d::KW, n_override::Integer)
|
|
layout, n = layout_args(get(d, :layout, n_override))
|
|
if n != n_override
|
|
error("When doing layout, n != n_override. You're probably trying to force existing plots into a layout that doesn't fit them.")
|
|
end
|
|
layout, n
|
|
end
|
|
|
|
function layout_args(n::Integer)
|
|
nr, nc = compute_gridsize(n, -1, -1)
|
|
GridLayout(nr, nc), n
|
|
end
|
|
|
|
function layout_args{I<:Integer}(sztup::NTuple{2,I})
|
|
nr, nc = sztup
|
|
GridLayout(nr, nc), nr*nc
|
|
end
|
|
|
|
function layout_args{I<:Integer}(sztup::NTuple{3,I})
|
|
n, nr, nc = sztup
|
|
nr, nc = compute_gridsize(n, nr, nc)
|
|
GridLayout(nr, nc), n
|
|
end
|
|
|
|
# compute number of subplots
|
|
function layout_args(layout::GridLayout)
|
|
# recursively get the size of the grid
|
|
n = calc_num_subplots(layout)
|
|
layout, n
|
|
end
|
|
|
|
layout_args(huh) = error("unhandled layout type $(typeof(huh)): $huh")
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
|
|
function build_layout(args...)
|
|
layout, n = layout_args(args...)
|
|
build_layout(layout, n)
|
|
end
|
|
|
|
# # just a single subplot
|
|
# function build_layout(sp::Subplot, n::Integer)
|
|
# sp, Subplot[sp], SubplotMap(gensym() => sp)
|
|
# end
|
|
|
|
# n is the number of subplots... build a grid and initialize the inner subplots recursively
|
|
function build_layout(layout::GridLayout, n::Integer)
|
|
nr, nc = size(layout)
|
|
subplots = Subplot[]
|
|
spmap = SubplotMap()
|
|
i = 0
|
|
for r=1:nr, c=1:nc
|
|
l = layout[r,c]
|
|
if isa(l, EmptyLayout) && !get(l.attr, :blank, false)
|
|
sp = Subplot(backend(), parent=layout)
|
|
layout[r,c] = sp
|
|
push!(subplots, sp)
|
|
spmap[attr(l,:label,gensym())] = sp
|
|
if get(l.attr, :width, :auto) != :auto
|
|
layout.widths[c] = attr(l,:width)
|
|
end
|
|
if get(l.attr, :height, :auto) != :auto
|
|
layout.heights[r] = attr(l,:height)
|
|
end
|
|
i += 1
|
|
elseif isa(l, GridLayout)
|
|
# sub-grid
|
|
l, sps, m = build_layout(l, n-i)
|
|
append!(subplots, sps)
|
|
merge!(spmap, m)
|
|
i += length(sps)
|
|
end
|
|
i >= n && break # only add n subplots
|
|
end
|
|
|
|
layout, subplots, spmap
|
|
end
|
|
|
|
# build a layout from a list of existing Plot objects
|
|
# TODO... much of the logic overlaps with the method above... can we merge?
|
|
function build_layout(layout::GridLayout, numsp::Integer, plts::AVec{Plot})
|
|
nr, nc = size(layout)
|
|
subplots = Subplot[]
|
|
spmap = SubplotMap()
|
|
i = 0
|
|
for r=1:nr, c=1:nc
|
|
l = layout[r,c]
|
|
if isa(l, EmptyLayout) && !get(l.attr, :blank, false)
|
|
plt = shift!(plts) # grab the first plot out of the list
|
|
layout[r,c] = plt.layout
|
|
append!(subplots, plt.subplots)
|
|
merge!(spmap, plt.spmap)
|
|
if get(l.attr, :width, :auto) != :auto
|
|
layout.widths[c] = attr(l,:width)
|
|
end
|
|
if get(l.attr, :height, :auto) != :auto
|
|
layout.heights[r] = attr(l,:height)
|
|
end
|
|
i += length(plt.subplots)
|
|
elseif isa(l, GridLayout)
|
|
# sub-grid
|
|
l, sps, m = build_layout(l, numsp-i, plts)
|
|
append!(subplots, sps)
|
|
merge!(spmap, m)
|
|
i += length(sps)
|
|
end
|
|
i >= numsp && break # only add n subplots
|
|
end
|
|
layout, subplots, spmap
|
|
end
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
# @layout macro
|
|
|
|
function add_layout_pct!(kw::KW, v::Expr, idx::Integer, nidx::Integer)
|
|
# dump(v)
|
|
# something like {0.2w}?
|
|
if v.head == :call && v.args[1] == :*
|
|
num = v.args[2]
|
|
if length(v.args) == 3 && isa(num, Number)
|
|
units = v.args[3]
|
|
if units == :h
|
|
return kw[:h] = num*pct
|
|
elseif units == :w
|
|
return kw[:w] = num*pct
|
|
elseif units in (:pct, :px, :mm, :cm, :inch)
|
|
idx == 1 && (kw[:w] = v)
|
|
(idx == 2 || nidx == 1) && (kw[:h] = v)
|
|
# return kw[idx == 1 ? :w : :h] = v
|
|
end
|
|
end
|
|
end
|
|
error("Couldn't match layout curly (idx=$idx): $v")
|
|
end
|
|
|
|
function add_layout_pct!(kw::KW, v::Number, idx::Integer)
|
|
# kw[idx == 1 ? :w : :h] = v*pct
|
|
idx == 1 && (kw[:w] = v*pct)
|
|
(idx == 2 || nidx == 1) && (kw[:h] = v*pct)
|
|
end
|
|
|
|
isrow(v) = isa(v, Expr) && v.head in (:hcat,:row)
|
|
iscol(v) = isa(v, Expr) && v.head == :vcat
|
|
rowsize(v) = isrow(v) ? length(v.args) : 1
|
|
|
|
|
|
function create_grid(expr::Expr)
|
|
# cellsym = gensym(:cell)
|
|
# @show expr
|
|
if iscol(expr)
|
|
create_grid_vcat(expr)
|
|
# rowsizes = map(rowsize, expr.args)
|
|
# rmin, rmax = extrema(rowsizes)
|
|
# if rmin > 0 && rmin == rmax
|
|
# # we have a grid... build the whole thing
|
|
# # note: rmin is the number of columns
|
|
# nr = length(expr.args)
|
|
# nc = rmin
|
|
#
|
|
# :(let cell = GridLayout($nr, $nc)
|
|
# $([:(cell[$r,$c] = $(create_grid(expr.args[r], c))) for r=1:nr, c=1:nc]...)
|
|
# for r=1:nr
|
|
# layout = $(create_grid(expr.args[r])
|
|
# cell[r,]
|
|
# $([:($cellsym[$r,1] = $(create_grid(expr.args[r]))) for r=1:length(expr.args)]...)
|
|
# $cellsym
|
|
# end)
|
|
# else
|
|
# # otherwise just build one row at a time
|
|
# :(let
|
|
# $cellsym = GridLayout($(length(expr.args)), 1)
|
|
# $([:($cellsym[$i,1] = $(create_grid(expr.args[i]))) for i=1:length(expr.args)]...)
|
|
# $cellsym
|
|
# end)
|
|
# end
|
|
elseif isrow(expr)
|
|
:(let cell = GridLayout(1, $(length(expr.args)))
|
|
$([:(cell[1,$i] = $(create_grid(v))) for (i,v) in enumerate(expr.args)]...)
|
|
cell
|
|
end)
|
|
# :(let
|
|
# $cellsym = GridLayout(1, $(length(expr.args)))
|
|
# $([:($cellsym[1,$i] = $(create_grid(expr.args[i]))) for i=1:length(expr.args)]...)
|
|
# $cellsym
|
|
# end)
|
|
|
|
elseif expr.head == :curly
|
|
create_grid_curly(expr)
|
|
else
|
|
# if it's something else, just return that (might be an existing layout?)
|
|
expr
|
|
end
|
|
end
|
|
|
|
function create_grid_vcat(expr::Expr)
|
|
rowsizes = map(rowsize, expr.args)
|
|
rmin, rmax = extrema(rowsizes)
|
|
# @show rmin, rmax
|
|
if rmin > 0 && rmin == rmax
|
|
# we have a grid... build the whole thing
|
|
# note: rmin is the number of columns
|
|
nr = length(expr.args)
|
|
nc = rmin
|
|
# @show nr, nc
|
|
body = Expr(:block)
|
|
for r=1:nr
|
|
arg = expr.args[r]
|
|
# @show r, arg
|
|
if isrow(arg)
|
|
for (c,item) in enumerate(arg.args)
|
|
push!(body.args, :(cell[$r,$c] = $(create_grid(item))))
|
|
end
|
|
else
|
|
push!(body.args, :(cell[$r,1] = $(create_grid(arg))))
|
|
end
|
|
end
|
|
# @show body
|
|
:(let cell = GridLayout($nr, $nc)
|
|
$body
|
|
cell
|
|
end)
|
|
# :(let cell = GridLayout($nr, $nc)
|
|
# $([:(cell[$r,$c] = $(create_grid(expr.args[r], c))) for r=1:nr, c=1:nc]...)
|
|
# for r=1:nr
|
|
# layout = $(create_grid(expr.args[r])
|
|
# cell[r,]
|
|
# $([:($cellsym[$r,1] = $(create_grid(expr.args[r]))) for r=1:length(expr.args)]...)
|
|
# $cellsym
|
|
# end)
|
|
else
|
|
# otherwise just build one row at a time
|
|
:(let cell = GridLayout($(length(expr.args)), 1)
|
|
$([:(cell[$i,1] = $(create_grid(v))) for (i,v) in enumerate(expr.args)]...)
|
|
cell
|
|
end)
|
|
# :(let
|
|
# $cellsym = GridLayout($(length(expr.args)), 1)
|
|
# $([:($cellsym[$i,1] = $(create_grid(expr.args[i]))) for i=1:length(expr.args)]...)
|
|
# $cellsym
|
|
# end)
|
|
end
|
|
end
|
|
|
|
function create_grid_curly(expr::Expr)
|
|
s = expr.args[1]
|
|
kw = KW()
|
|
for (i,arg) in enumerate(expr.args[2:end])
|
|
add_layout_pct!(kw, arg, i, length(expr.args)-1)
|
|
end
|
|
# @show kw
|
|
:(EmptyLayout(label = $(QuoteNode(s)), width = $(get(kw, :w, QuoteNode(:auto))), height = $(get(kw, :h, QuoteNode(:auto)))))
|
|
end
|
|
|
|
function create_grid(s::Symbol)
|
|
:(EmptyLayout(label = $(QuoteNode(s)), blank = $(s == :_)))
|
|
end
|
|
|
|
macro layout(mat::Expr)
|
|
create_grid(mat)
|
|
end
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# make all reference the same axis extrema/values
|
|
function link_axes!(axes::Axis...)
|
|
a1 = axes[1]
|
|
for i=2:length(axes)
|
|
a2 = axes[i]
|
|
for k in (:extrema, :discrete_values, :continuous_values, :discrete_map)
|
|
a2[k] = a1[k]
|
|
end
|
|
end
|
|
end
|
|
|
|
# for some vector or matrix of layouts, filter only the Subplots and link those axes
|
|
function link_axes!(a::AbstractArray{AbstractLayout}, axissym::Symbol)
|
|
subplots = filter(l -> isa(l, Subplot), a)
|
|
axes = [sp.attr[axissym] for sp in subplots]
|
|
if length(axes) > 0
|
|
link_axes!(axes...)
|
|
end
|
|
end
|
|
|
|
# don't do anything for most layout types
|
|
function link_axes!(l::AbstractLayout, link::Symbol)
|
|
end
|
|
|
|
# process a GridLayout, recursively linking axes according to the link symbol
|
|
function link_axes!(layout::GridLayout, link::Symbol)
|
|
nr, nc = size(layout)
|
|
if link in (:x, :both)
|
|
for c=1:nc
|
|
link_axes!(layout.grid[:,c], :xaxis)
|
|
end
|
|
end
|
|
if link in (:y, :both)
|
|
for r=1:nr
|
|
link_axes!(layout.grid[r,:], :yaxis)
|
|
end
|
|
end
|
|
if link == :all
|
|
link_axes!(layout.grid, :xaxis)
|
|
link_axes!(layout.grid, :yaxis)
|
|
end
|
|
for l in layout.grid
|
|
link_axes!(l, link)
|
|
end
|
|
end
|