smarter cleanup of dicts; layout fixes: split out update_min_padding and added minimum_perimeter logic; check for nonempty axes when linking; add subplot processing and linking when combining plots

This commit is contained in:
Thomas Breloff 2016-06-10 00:16:13 -04:00
parent d7098f77c2
commit 8007d0dd6a
3 changed files with 115 additions and 105 deletions

View File

@ -896,78 +896,75 @@ end
# update attr from an input dictionary # update attr from an input dictionary
function _update_plot_args(plt::Plot, d_in::KW) function _update_plot_args(plt::Plot, d_in::KW)
pargs = plt.attr
for (k,v) in _plot_defaults for (k,v) in _plot_defaults
slice_arg!(d_in, pargs, k, v) slice_arg!(d_in, plt.attr, k, v)
end end
# handle colors # handle colors
bg = convertColor(pargs[:background_color]) bg = convertColor(plt.attr[:background_color])
fg = pargs[:foreground_color] fg = plt.attr[:foreground_color]
if fg == :auto if fg == :auto
fg = isdark(bg) ? colorant"white" : colorant"black" fg = isdark(bg) ? colorant"white" : colorant"black"
end end
pargs[:background_color] = bg plt.attr[:background_color] = bg
pargs[:foreground_color] = convertColor(fg) plt.attr[:foreground_color] = convertColor(fg)
# color_or_match!(pargs, :background_color_outside, bg) # color_or_match!(plt.attr, :background_color_outside, bg)
color_or_nothing!(pargs, :background_color_outside) color_or_nothing!(plt.attr, :background_color_outside)
end end
# update a subplots args and axes # update a subplots args and axes
function _update_subplot_args(plt::Plot, sp::Subplot, d_in::KW, subplot_index::Integer; remove_pair = true) function _update_subplot_args(plt::Plot, sp::Subplot, d_in::KW, subplot_index::Integer; remove_pair = true)
pargs = plt.attr
spargs = sp.attr
anns = pop!(sp.attr, :annotations, []) anns = pop!(sp.attr, :annotations, [])
# grab those args which apply to this subplot # grab those args which apply to this subplot
for (k,v) in _subplot_defaults for (k,v) in _subplot_defaults
slice_arg!(d_in, spargs, k, v, subplot_index, remove_pair = remove_pair) slice_arg!(d_in, sp.attr, k, v, subplot_index, remove_pair = remove_pair)
end end
# extend annotations # extend annotations
sp.attr[:annotations] = vcat(anns, sp[:annotations]) sp.attr[:annotations] = vcat(anns, sp[:annotations])
# handle legend/colorbar # handle legend/colorbar
spargs[:legend] = convertLegendValue(spargs[:legend]) sp.attr[:legend] = convertLegendValue(sp.attr[:legend])
spargs[:colorbar] = convertLegendValue(spargs[:colorbar]) sp.attr[:colorbar] = convertLegendValue(sp.attr[:colorbar])
if spargs[:colorbar] == :legend if sp.attr[:colorbar] == :legend
spargs[:colorbar] = spargs[:legend] sp.attr[:colorbar] = sp.attr[:legend]
end end
# background colors # background colors
# bg = color_or_match!(spargs, :background_color_subplot, pargs[:background_color]) # bg = color_or_match!(sp.attr, :background_color_subplot, plt.attr[:background_color])
color_or_nothing!(spargs, :background_color_subplot) color_or_nothing!(sp.attr, :background_color_subplot)
bg = sp[:background_color_subplot] bg = convertColor(sp[:background_color_subplot])
spargs[:color_palette] = get_color_palette(spargs[:color_palette], bg, 30) sp.attr[:color_palette] = get_color_palette(sp.attr[:color_palette], bg, 30)
# color_or_match!(spargs, :background_color_legend, bg) # color_or_match!(sp.attr, :background_color_legend, bg)
color_or_nothing!(spargs, :background_color_legend) color_or_nothing!(sp.attr, :background_color_legend)
# color_or_match!(spargs, :background_color_inside, bg) # color_or_match!(sp.attr, :background_color_inside, bg)
color_or_nothing!(spargs, :background_color_inside) color_or_nothing!(sp.attr, :background_color_inside)
# foreground colors # foreground colors
# fg = color_or_match!(spargs, :foreground_color_subplot, pargs[:foreground_color]) # fg = color_or_match!(sp.attr, :foreground_color_subplot, plt.attr[:foreground_color])
color_or_nothing!(spargs, :foreground_color_subplot) color_or_nothing!(sp.attr, :foreground_color_subplot)
# color_or_match!(spargs, :foreground_color_legend, fg) # color_or_match!(sp.attr, :foreground_color_legend, fg)
color_or_nothing!(spargs, :foreground_color_legend) color_or_nothing!(sp.attr, :foreground_color_legend)
# color_or_match!(spargs, :foreground_color_grid, fg) # color_or_match!(sp.attr, :foreground_color_grid, fg)
color_or_nothing!(spargs, :foreground_color_grid) color_or_nothing!(sp.attr, :foreground_color_grid)
# color_or_match!(spargs, :foreground_color_title, fg) # color_or_match!(sp.attr, :foreground_color_title, fg)
color_or_nothing!(spargs, :foreground_color_title) color_or_nothing!(sp.attr, :foreground_color_title)
# for k in (:left_margin, :top_margin, :right_margin, :bottom_margin) # for k in (:left_margin, :top_margin, :right_margin, :bottom_margin)
# if spargs[k] == :match # if sp.attr[k] == :match
# spargs[k] = spargs[:margin] # sp.attr[k] = sp.attr[:margin]
# end # end
# end # end
for letter in (:x, :y, :z) for letter in (:x, :y, :z)
# get (maybe initialize) the axis # get (maybe initialize) the axis
axissym = Symbol(letter, :axis) axissym = Symbol(letter, :axis)
axis = if haskey(spargs, axissym) axis = if haskey(sp.attr, axissym)
spargs[axissym] sp.attr[axissym]
else else
spargs[axissym] = Axis(sp, letter) sp.attr[axissym] = Axis(sp, letter)
end end
# grab magic args (for example `xaxis = (:flip, :log)`) # grab magic args (for example `xaxis = (:flip, :log)`)
@ -987,7 +984,8 @@ function _update_subplot_args(plt::Plot, sp::Subplot, d_in::KW, subplot_index::I
# then get those args that were passed with a leading letter: `xlabel = "X"` # then get those args that were passed with a leading letter: `xlabel = "X"`
lk = Symbol(letter, k) lk = Symbol(letter, k)
if haskey(d_in, lk) if haskey(d_in, lk)
kw[k] = slice_arg(pop!(d_in, lk), subplot_index) # kw[k] = slice_arg(pop!(d_in, lk), subplot_index)
kw[k] = slice_arg(d_in[lk], subplot_index)
end end
end end
@ -1007,10 +1005,10 @@ function _update_subplot_args(plt::Plot, sp::Subplot, d_in::KW, subplot_index::I
# TODO: need to handle linking here? # TODO: need to handle linking here?
end end
# now we can get rid of the axis keys without a letter # # now we can get rid of the axis keys without a letter
for k in keys(_axis_defaults) # for k in keys(_axis_defaults)
delete!(d_in, k) # delete!(d_in, k)
end # end
end end

View File

@ -97,19 +97,12 @@ bbox!(layout::AbstractLayout, bb::BoundingBox) = (layout.bbox = bb)
Base.parent(layout::AbstractLayout) = layout.parent Base.parent(layout::AbstractLayout) = layout.parent
parent_bbox(layout::AbstractLayout) = bbox(parent(layout)) parent_bbox(layout::AbstractLayout) = bbox(parent(layout))
# NOTE: these should be implemented for subplots in each backend! # padding_w(layout::AbstractLayout) = left_padding(layout) + right_padding(layout)
# they represent the minimum size of the axes and guides # padding_h(layout::AbstractLayout) = bottom_padding(layout) + top_padding(layout)
min_padding_left(layout::AbstractLayout) = 0mm # padding(layout::AbstractLayout) = (padding_w(layout), padding_h(layout))
min_padding_top(layout::AbstractLayout) = 0mm
min_padding_right(layout::AbstractLayout) = 0mm
min_padding_bottom(layout::AbstractLayout) = 0mm
padding_w(layout::AbstractLayout) = left_padding(layout) + right_padding(layout) update_position!(layout::AbstractLayout) = nothing
padding_h(layout::AbstractLayout) = bottom_padding(layout) + top_padding(layout) update_child_bboxes!(layout::AbstractLayout, minimum_perimeter = [0mm,0mm,0mm,0mm]) = nothing
padding(layout::AbstractLayout) = (padding_w(layout), padding_h(layout))
_update_position!(layout::AbstractLayout) = nothing
update_child_bboxes!(layout::AbstractLayout) = nothing
width(layout::AbstractLayout) = width(layout.bbox) width(layout::AbstractLayout) = width(layout.bbox)
height(layout::AbstractLayout) = height(layout.bbox) height(layout::AbstractLayout) = height(layout.bbox)
@ -204,6 +197,12 @@ rightpad(layout::GridLayout) = layout.minpad[3]
bottompad(layout::GridLayout) = layout.minpad[4] 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 # leftpad, toppad, rightpad, bottompad
function _update_min_padding!(layout::GridLayout) function _update_min_padding!(layout::GridLayout)
@ -217,10 +216,11 @@ function _update_min_padding!(layout::GridLayout)
end end
function _update_position!(layout::GridLayout) function update_position!(layout::GridLayout)
map(_update_position!, layout.grid) map(update_position!, layout.grid)
end end
# some lengths are fixed... we have to split up the free space among the list v
function recompute_lengths(v) function recompute_lengths(v)
# dump(v) # dump(v)
tot = 0pct tot = 0pct
@ -242,11 +242,11 @@ function recompute_lengths(v)
end end
# recursively compute the bounding boxes for the layout and plotarea (relative to canvas!) # recursively compute the bounding boxes for the layout and plotarea (relative to canvas!)
function update_child_bboxes!(layout::GridLayout) function update_child_bboxes!(layout::GridLayout, minimum_perimeter = [0mm,0mm,0mm,0mm])
nr, nc = size(layout) nr, nc = size(layout)
# create a matrix for each minimum padding direction # # create a matrix for each minimum padding direction
_update_min_padding!(layout) # _update_min_padding!(layout)
minpad_left = map(leftpad, layout.grid) minpad_left = map(leftpad, layout.grid)
minpad_top = map(toppad, layout.grid) minpad_top = map(toppad, layout.grid)
@ -263,6 +263,12 @@ function update_child_bboxes!(layout::GridLayout)
pad_bottom = maximum(minpad_bottom, 2) pad_bottom = maximum(minpad_bottom, 2)
# @show pad_left pad_top pad_right pad_bottom # @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 # scale this up to the total padding in each direction
total_pad_horizontal = sum(pad_left + pad_right) total_pad_horizontal = sum(pad_left + pad_right)
total_pad_vertical = sum(pad_top + pad_bottom) total_pad_vertical = sum(pad_top + pad_bottom)
@ -303,8 +309,17 @@ function update_child_bboxes!(layout::GridLayout)
child_height = pad_top[r] + plotarea_height + pad_bottom[r] child_height = pad_top[r] + plotarea_height + pad_bottom[r]
bbox!(child, BoundingBox(child_left, child_top, child_width, child_height)) 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 # recursively update the child's children
update_child_bboxes!(child) update_child_bboxes!(child, min_child_perimeter)
end end
end end
@ -376,40 +391,21 @@ end
layout_args(huh) = error("unhandled layout type $(typeof(huh)): $huh") layout_args(huh) = error("unhandled layout type $(typeof(huh)): $huh")
# # pass the layout arg through
# function build_layout(d::KW) # ----------------------------------------------------------------------
# build_layout(get(d, :layout, default(:layout)))
# end
#
# function build_layout(n::Integer)
# nr, nc = compute_gridsize(n, -1, -1)
# build_layout(GridLayout(nr, nc), n)
# end
#
# function build_layout{I<:Integer}(sztup::NTuple{2,I})
# nr, nc = sztup
# build_layout(GridLayout(nr, nc))
# end
#
# function build_layout{I<:Integer}(sztup::NTuple{3,I})
# n, nr, nc = sztup
# nr, nc = compute_gridsize(n, nr, nc)
# build_layout(GridLayout(nr, nc), n)
# end
#
# # compute number of subplots
# function build_layout(layout::GridLayout)
# # recursively get the size of the grid
# n = calc_num_subplots(layout)
# build_layout(layout, n)
# end
function build_layout(args...) function build_layout(args...)
layout, n = layout_args(args...) layout, n = layout_args(args...)
build_layout(layout, n) build_layout(layout, n)
end end
# n is the number of subplots # # 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) function build_layout(layout::GridLayout, n::Integer)
nr, nc = size(layout) nr, nc = size(layout)
subplots = Subplot[] subplots = Subplot[]
@ -644,7 +640,9 @@ end
function link_axes!(a::AbstractArray{AbstractLayout}, axissym::Symbol) function link_axes!(a::AbstractArray{AbstractLayout}, axissym::Symbol)
subplots = filter(l -> isa(l, Subplot), a) subplots = filter(l -> isa(l, Subplot), a)
axes = [sp.attr[axissym] for sp in subplots] axes = [sp.attr[axissym] for sp in subplots]
link_axes!(axes...) if length(axes) > 0
link_axes!(axes...)
end
end end
# don't do anything for most layout types # don't do anything for most layout types

View File

@ -102,6 +102,25 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...)
end end
end end
# just in case the backend needs to set up the plot (make it current or something)
_prepare_plot_object(plt)
# first apply any args for the subplots
for (idx,sp) in enumerate(plt.subplots)
_update_subplot_args(plt, sp, d, idx, remove_pair = false)
end
# # now we can get rid of the axis keys without a letter
# for k in keys(_axis_defaults)
# delete!(d, k)
# for letter in (:x,:y,:z)
# delete!(d, Symbol(letter,k))
# end
# end
# do we need to link any axes together?
link_axes!(plt.layout, plt[:link])
# finish up # finish up
current(plt) current(plt)
if get(d, :show, default(:show)) if get(d, :show, default(:show))
@ -305,17 +324,17 @@ function _plot!(plt::Plot, d::KW, args...)
end end
end end
# merge in anything meant for plot/subplot # merge in anything meant for plot/subplot/axis
for kw in kw_list for kw in kw_list
for (k,v) in kw for (k,v) in kw
for defdict in (_plot_defaults, _subplot_defaults) for defdict in (_plot_defaults,
_subplot_defaults,
_axis_defaults,
_axis_defaults_byletter)
if haskey(defdict, k) if haskey(defdict, k)
d[k] = pop!(kw, k) d[k] = pop!(kw, k)
end end
end end
# if haskey(_plot_defaults, k) || haskey(_subplot_defaults, k)
# d[k] = v
# end
end end
end end
@ -351,9 +370,6 @@ function _plot!(plt::Plot, d::KW, args...)
# this is it folks! # this is it folks!
# TODO: we probably shouldn't use i for tracking series index, but rather explicitly track it in recipes # TODO: we probably shouldn't use i for tracking series index, but rather explicitly track it in recipes
for (i,kw) in enumerate(kw_list) for (i,kw) in enumerate(kw_list)
# if !(get(kw, :seriestype, :none) in (:xerror, :yerror))
# plt.n += 1
# end
command_idx = kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1 command_idx = kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1
# get the Subplot object to which the series belongs # get the Subplot object to which the series belongs
@ -393,15 +409,6 @@ function _plot!(plt::Plot, d::KW, args...)
_apply_series_recipe(plt, kw) _apply_series_recipe(plt, kw)
end end
# # everything is processed, time to compute the layout bounding boxes
# _before_layout_calcs(plt)
# w, h = plt.attr[:size]
# plt.layout.bbox = BoundingBox(0mm, 0mm, w*px, h*px)
# update_child_bboxes!(plt.layout)
#
# # TODO just need to pass plt... and we should do all non-series updates here
# _update_plot_object(plt)
current(plt) current(plt)
# note: lets ignore the show param and effectively use the semicolon at the end of the REPL statement # note: lets ignore the show param and effectively use the semicolon at the end of the REPL statement
@ -429,8 +436,15 @@ function prepare_output(plt::Plot)
w, h = plt.attr[:size] w, h = plt.attr[:size]
plt.layout.bbox = BoundingBox(0mm, 0mm, w*px, h*px) plt.layout.bbox = BoundingBox(0mm, 0mm, w*px, h*px)
# 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)
# now another pass down, to update the bounding boxes
update_child_bboxes!(plt.layout) update_child_bboxes!(plt.layout)
# the backend callback, to reposition subplots, etc
_update_plot_object(plt) _update_plot_object(plt)
end end