diff --git a/src/args.jl b/src/args.jl index 6197f26d..4ed57aaa 100644 --- a/src/args.jl +++ b/src/args.jl @@ -896,78 +896,75 @@ end # update attr from an input dictionary function _update_plot_args(plt::Plot, d_in::KW) - pargs = plt.attr for (k,v) in _plot_defaults - slice_arg!(d_in, pargs, k, v) + slice_arg!(d_in, plt.attr, k, v) end # handle colors - bg = convertColor(pargs[:background_color]) - fg = pargs[:foreground_color] + bg = convertColor(plt.attr[:background_color]) + fg = plt.attr[:foreground_color] if fg == :auto fg = isdark(bg) ? colorant"white" : colorant"black" end - pargs[:background_color] = bg - pargs[:foreground_color] = convertColor(fg) - # color_or_match!(pargs, :background_color_outside, bg) - color_or_nothing!(pargs, :background_color_outside) + plt.attr[:background_color] = bg + plt.attr[:foreground_color] = convertColor(fg) + # color_or_match!(plt.attr, :background_color_outside, bg) + color_or_nothing!(plt.attr, :background_color_outside) end # update a subplots args and axes 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, []) # grab those args which apply to this subplot 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 # extend annotations sp.attr[:annotations] = vcat(anns, sp[:annotations]) # handle legend/colorbar - spargs[:legend] = convertLegendValue(spargs[:legend]) - spargs[:colorbar] = convertLegendValue(spargs[:colorbar]) - if spargs[:colorbar] == :legend - spargs[:colorbar] = spargs[:legend] + sp.attr[:legend] = convertLegendValue(sp.attr[:legend]) + sp.attr[:colorbar] = convertLegendValue(sp.attr[:colorbar]) + if sp.attr[:colorbar] == :legend + sp.attr[:colorbar] = sp.attr[:legend] end # background colors - # bg = color_or_match!(spargs, :background_color_subplot, pargs[:background_color]) - color_or_nothing!(spargs, :background_color_subplot) - bg = sp[:background_color_subplot] - spargs[:color_palette] = get_color_palette(spargs[:color_palette], bg, 30) - # color_or_match!(spargs, :background_color_legend, bg) - color_or_nothing!(spargs, :background_color_legend) - # color_or_match!(spargs, :background_color_inside, bg) - color_or_nothing!(spargs, :background_color_inside) + # bg = color_or_match!(sp.attr, :background_color_subplot, plt.attr[:background_color]) + color_or_nothing!(sp.attr, :background_color_subplot) + bg = convertColor(sp[:background_color_subplot]) + sp.attr[:color_palette] = get_color_palette(sp.attr[:color_palette], bg, 30) + # color_or_match!(sp.attr, :background_color_legend, bg) + color_or_nothing!(sp.attr, :background_color_legend) + # color_or_match!(sp.attr, :background_color_inside, bg) + color_or_nothing!(sp.attr, :background_color_inside) # foreground colors - # fg = color_or_match!(spargs, :foreground_color_subplot, pargs[:foreground_color]) - color_or_nothing!(spargs, :foreground_color_subplot) - # color_or_match!(spargs, :foreground_color_legend, fg) - color_or_nothing!(spargs, :foreground_color_legend) - # color_or_match!(spargs, :foreground_color_grid, fg) - color_or_nothing!(spargs, :foreground_color_grid) - # color_or_match!(spargs, :foreground_color_title, fg) - color_or_nothing!(spargs, :foreground_color_title) + # fg = color_or_match!(sp.attr, :foreground_color_subplot, plt.attr[:foreground_color]) + color_or_nothing!(sp.attr, :foreground_color_subplot) + # color_or_match!(sp.attr, :foreground_color_legend, fg) + color_or_nothing!(sp.attr, :foreground_color_legend) + # color_or_match!(sp.attr, :foreground_color_grid, fg) + color_or_nothing!(sp.attr, :foreground_color_grid) + # color_or_match!(sp.attr, :foreground_color_title, fg) + color_or_nothing!(sp.attr, :foreground_color_title) # for k in (:left_margin, :top_margin, :right_margin, :bottom_margin) - # if spargs[k] == :match - # spargs[k] = spargs[:margin] + # if sp.attr[k] == :match + # sp.attr[k] = sp.attr[:margin] # end # end for letter in (:x, :y, :z) # get (maybe initialize) the axis axissym = Symbol(letter, :axis) - axis = if haskey(spargs, axissym) - spargs[axissym] + axis = if haskey(sp.attr, axissym) + sp.attr[axissym] else - spargs[axissym] = Axis(sp, letter) + sp.attr[axissym] = Axis(sp, letter) end # 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"` lk = Symbol(letter, k) 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 @@ -1007,10 +1005,10 @@ function _update_subplot_args(plt::Plot, sp::Subplot, d_in::KW, subplot_index::I # TODO: need to handle linking here? end - # now we can get rid of the axis keys without a letter - for k in keys(_axis_defaults) - delete!(d_in, k) - end + # # now we can get rid of the axis keys without a letter + # for k in keys(_axis_defaults) + # delete!(d_in, k) + # end end diff --git a/src/layouts.jl b/src/layouts.jl index e66d60bf..bdcb5755 100644 --- a/src/layouts.jl +++ b/src/layouts.jl @@ -97,19 +97,12 @@ bbox!(layout::AbstractLayout, bb::BoundingBox) = (layout.bbox = bb) Base.parent(layout::AbstractLayout) = layout.parent parent_bbox(layout::AbstractLayout) = bbox(parent(layout)) -# NOTE: these should be implemented for subplots in each backend! -# they represent the minimum size of the axes and guides -min_padding_left(layout::AbstractLayout) = 0mm -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) +# padding_h(layout::AbstractLayout) = bottom_padding(layout) + top_padding(layout) +# padding(layout::AbstractLayout) = (padding_w(layout), padding_h(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) = nothing +update_position!(layout::AbstractLayout) = nothing +update_child_bboxes!(layout::AbstractLayout, minimum_perimeter = [0mm,0mm,0mm,0mm]) = nothing width(layout::AbstractLayout) = width(layout.bbox) height(layout::AbstractLayout) = height(layout.bbox) @@ -204,6 +197,12 @@ 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) @@ -217,10 +216,11 @@ function _update_min_padding!(layout::GridLayout) end -function _update_position!(layout::GridLayout) - map(_update_position!, layout.grid) +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 @@ -242,11 +242,11 @@ function recompute_lengths(v) end # 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) - # create a matrix for each minimum padding direction - _update_min_padding!(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) @@ -263,6 +263,12 @@ function update_child_bboxes!(layout::GridLayout) 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) @@ -303,8 +309,17 @@ function update_child_bboxes!(layout::GridLayout) 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) + update_child_bboxes!(child, min_child_perimeter) end end @@ -376,40 +391,21 @@ end 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...) layout, n = layout_args(args...) build_layout(layout, n) 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) nr, nc = size(layout) subplots = Subplot[] @@ -644,7 +640,9 @@ end function link_axes!(a::AbstractArray{AbstractLayout}, axissym::Symbol) subplots = filter(l -> isa(l, Subplot), a) axes = [sp.attr[axissym] for sp in subplots] - link_axes!(axes...) + if length(axes) > 0 + link_axes!(axes...) + end end # don't do anything for most layout types diff --git a/src/plot.jl b/src/plot.jl index 7ffeeb0d..b82a823e 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -102,6 +102,25 @@ function plot(plt1::Plot, plts_tail::Plot...; kw...) 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 current(plt) if get(d, :show, default(:show)) @@ -305,17 +324,17 @@ function _plot!(plt::Plot, d::KW, args...) end end - # merge in anything meant for plot/subplot + # merge in anything meant for plot/subplot/axis for kw in kw_list 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) d[k] = pop!(kw, k) end end - # if haskey(_plot_defaults, k) || haskey(_subplot_defaults, k) - # d[k] = v - # end end end @@ -351,9 +370,6 @@ function _plot!(plt::Plot, d::KW, args...) # this is it folks! # 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) - # if !(get(kw, :seriestype, :none) in (:xerror, :yerror)) - # plt.n += 1 - # end command_idx = kw[:series_plotindex] - kw_list[1][:series_plotindex] + 1 # 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) 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) # 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] 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) + # the backend callback, to reposition subplots, etc _update_plot_object(plt) end