From 4a96122067577f3fd422ab79a9288bcc5ded2889 Mon Sep 17 00:00:00 2001 From: Thomas Breloff Date: Sat, 21 May 2016 00:04:33 -0400 Subject: [PATCH] colorbar layout fixes; process_axis_arg and related fix --- src/args.jl | 43 ++++++++------- src/axes.jl | 74 ++++++++++++++------------ src/backends/pyplot.jl | 118 ++++++++++++++++++++++++++++++----------- 3 files changed, 150 insertions(+), 85 deletions(-) diff --git a/src/args.jl b/src/args.jl index 96b49793..e9ae5c0a 100644 --- a/src/args.jl +++ b/src/args.jl @@ -613,26 +613,29 @@ end function preprocessArgs!(d::KW) replaceAliases!(d, _keyAliases) - # # handle axis args - # for letter in ("x", "y", "z") - # asym = symbol(letter * "axis") - # for arg in wraptuple(pop!(d, asym, ())) - # processAxisArg(d, letter, arg) - # end - # # delete!(d, asym) - # - # # # NOTE: this logic was moved to _add_plotargs... - # # # turn :labels into :ticks_and_labels - # # tsym = symbol(letter * "ticks") - # # if haskey(d, tsym) && ticksType(d[tsym]) == :labels - # # d[tsym] = (1:length(d[tsym]), d[tsym]) - # # end - # # - # # ssym = symbol(letter * "scale") - # # if haskey(d, ssym) && haskey(_scaleAliases, d[ssym]) - # # d[ssym] = _scaleAliases[d[ssym]] - # # end - # end + # handle axis args + for letter in (:x, :y, :z) + asym = symbol(letter, :axis) + args = pop!(d, asym, ()) + if !(typeof(args) <: Axis) + for arg in wraptuple(args) + process_axis_arg!(d, arg, letter) + end + end + # delete!(d, asym) + + # # NOTE: this logic was moved to _add_plotargs... + # # turn :labels into :ticks_and_labels + # tsym = symbol(letter * "ticks") + # if haskey(d, tsym) && ticksType(d[tsym]) == :labels + # d[tsym] = (1:length(d[tsym]), d[tsym]) + # end + # + # ssym = symbol(letter * "scale") + # if haskey(d, ssym) && haskey(_scaleAliases, d[ssym]) + # d[ssym] = _scaleAliases[d[ssym]] + # end + end # # if title is just a single string, then assume we want plot_title # # TODO: make a decision if this is correct diff --git a/src/axes.jl b/src/axes.jl index 21dbb362..0ff69976 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -21,46 +21,50 @@ function Axis(letter::Symbol, args...; kw...) update!(Axis(d), args...; kw...) end +function process_axis_arg!(d::KW, arg, letter = "") + T = typeof(arg) + arg = get(_scaleAliases, arg, arg) + + if typeof(arg) <: Font + d[symbol(letter,:tickfont)] = arg + d[symbol(letter,:guidefont)] = arg + + elseif arg in _allScales + d[symbol(letter,:scale)] = arg + + elseif arg in (:flip, :invert, :inverted) + d[symbol(letter,:flip)] = true + + elseif T <: @compat(AbstractString) + d[symbol(letter,:guide)] = arg + + # xlims/ylims + elseif (T <: Tuple || T <: AVec) && length(arg) == 2 + sym = typeof(arg[1]) <: Number ? :lims : :ticks + d[symbol(letter,sym)] = arg + + # xticks/yticks + elseif T <: AVec + d[symbol(letter,:ticks)] = arg + + elseif arg == nothing + d[symbol(letter,:ticks)] = [] + + elseif typeof(arg) <: Number + d[symbol(letter,:rotation)] = arg + + else + warn("Skipped $(letter)axis arg $arg") + + end +end + # update an Axis object with magic args and keywords function update!(a::Axis, args...; kw...) # first process args d = a.d for arg in args - T = typeof(arg) - arg = get(_scaleAliases, arg, arg) - - if typeof(arg) <: Font - d[:tickfont] = arg - d[:guidefont] = arg - - elseif arg in _allScales - d[:scale] = arg - - elseif arg in (:flip, :invert, :inverted) - d[:flip] = true - - elseif T <: @compat(AbstractString) - d[:guide] = arg - - # xlims/ylims - elseif (T <: Tuple || T <: AVec) && length(arg) == 2 - sym = typeof(arg[1]) <: Number ? :lims : :ticks - d[sym] = arg - - # xticks/yticks - elseif T <: AVec - d[:ticks] = arg - - elseif arg == nothing - d[:ticks] = [] - - elseif typeof(arg) <: Number - d[:rotation] = arg - - else - warn("Skipped $(letter)axis arg $arg") - - end + process_axis_arg!(d, arg) end # then override for any keywords... only those keywords that already exists in d diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 1369b613..8ea5f462 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -267,22 +267,31 @@ function py_bbox(obj) BoundingBox(l*px, (ft-t)*px, (r-l)*px, (t-b)*px) end +# get the bounding box of the union of the objects +function py_bbox(v::AVec) + bbox_union = defaultbox + for obj in v + bbox_union = bbox_union + py_bbox(obj) + end + bbox_union +end function py_bbox_ticks(ax, letter) # fig = ax[:get_figure]() # @show fig labels = ax[symbol("get_"*letter*"ticklabels")]() - # @show labels - # bboxes = [] - bbox_union = defaultbox - for lab in labels - # @show lab,lab[:get_text]() - bbox = py_bbox(lab) - bbox_union = bbox_union + bbox - # @show letter,bbox bbox_union - # @show bbox_union - end - bbox_union + py_bbox(labels) + # # @show labels + # # bboxes = [] + # bbox_union = defaultbox + # for lab in labels + # # @show lab,lab[:get_text]() + # bbox = py_bbox(lab) + # bbox_union = bbox_union + bbox + # # @show letter,bbox bbox_union + # # @show bbox_union + # end + # bbox_union end function py_bbox_axislabel(ax, letter) @@ -316,6 +325,8 @@ min_padding_top(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, min_padding_right(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, right, -1) min_padding_bottom(layout::Subplot{PyPlotBackend}) = compute_min_padding(layout, bottom,-1) +const _cbar_width = 5mm + # loop over the guides and axes and compute how far they "stick out" from the plot area, # so that we know the minimum padding we need to avoid cropping and overlapping text. # `func` is one of (left,top,right,bottom), and we multiply by 1 or -1 depending on direction @@ -337,6 +348,14 @@ function compute_min_padding(sp::Subplot{PyPlotBackend}, func::Function, mult::N # @show padding end + if func == right && haskey(sp.attr, :cbar_ax) + # TODO: need bounding box of colorbar labels + # bb = py_bbox(sp.attr[:cbar_ax][:get_ticklabels]()) + bb = BoundingBox(0mm,0mm,15mm,15mm) + sp.attr[:cbar_width] = _cbar_width + width(bb) + padding = padding + sp.attr[:cbar_width] + end + # if func == top # titlebbox = py_bbox_title(ax) # padding = max(padding, height(titlebbox)) @@ -374,24 +393,47 @@ end # --------------------------------------------------------------------------- +# TODO: add this (and _cbar_width) to layouts.jl? +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 update_position!(sp::Subplot{PyPlotBackend}) ax = sp.o ax == nothing && return figw, figh = size(py_bbox_fig(sp.plt)) # plot_bb = plotarea_bbox(sp) - plot_bb = sp.plotarea + # plot_bb = sp.plotarea # @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)] - - mms[2] = figh.value - mms[2] - # @show mms - pcts = mms ./ Float64[figw.value, figh.value, figw.value, figh.value] + # mms = Float64[f(plot_bb).value for f in (left, bottom, width, height)] + # + # mms[2] = figh.value - mms[2] + # # @show mms + # pcts = mms ./ Float64[figw.value, figh.value, figw.value, figh.value] + pcts = bbox_to_pcts(sp.plotarea, figw, figh) # @show pcts ax[:set_position](pcts) + + # set the cbar position if there is one + # @show sp.attr[:cbar_ax] + if haskey(sp.attr, :cbar_ax) + cbw = sp.attr[:cbar_width] + # cb_bbox = BoundingBox(figw - cbw, 0.1figh, cbw, figh - 0.2pct) + # this is the bounding box of just the colors of the colorbar (not labels) + cb_bbox = BoundingBox(right(sp.bbox)-cbw+2mm, top(sp.bbox)+2mm, _cbar_width, height(sp.bbox)-4mm) + pcts = bbox_to_pcts(cb_bbox, figw, figh) + # @show cbw cb_bbox pcts + sp.attr[:cbar_ax][:set_position](pcts) + end end # each backend should set up the subplot here @@ -935,24 +977,40 @@ function _add_series(plt::Plot{PyPlotBackend}, series::Series) handleSmooth(plt, ax, d, d[:smooth]) # add the colorbar legend - if needs_colorbar && attr(d[:subplot], :colorbar) != :none + sp = d[:subplot] + if needs_colorbar && sp.attr[:colorbar] != :none # cbar = PyPlot.colorbar(handles[end], ax=ax) # do we need a discrete colorbar? - if discrete_colorbar_values == nothing - PyPlot.colorbar(handles[end], ax=ax) - else - # add_pyfixedformatter(cbar, discrete_colorbar_values) + handle = handles[end] + kw = KW() + if discrete_colorbar_values != nothing locator, formatter = get_locator_and_formatter(discrete_colorbar_values) - vals = 1:length(discrete_colorbar_values) - PyPlot.colorbar(handles[end], - ax = ax, - ticks = locator, - format = formatter, - boundaries = vcat(0, vals + 0.5), - values = vals - ) + kw[:values] = 1:length(discrete_colorbar_values) + kw[:ticks] = locator + kw[:format] = formatter + kw[:boundaries] = vcat(0, kw[:values] + 0.5) end + + fig = plt.o + cbax = fig[:add_axes]([0.8,0.1,0.03,0.8]) + sp.attr[:cbar_handle] = fig[:colorbar](handle, cax = cbax, kw...) + sp.attr[:cbar_ax] = cbax + + # if discrete_colorbar_values == nothing + # PyPlot.colorbar(handles[end], ax=ax) + # else + # # add_pyfixedformatter(cbar, discrete_colorbar_values) + # locator, formatter = get_locator_and_formatter(discrete_colorbar_values) + # vals = 1:length(discrete_colorbar_values) + # PyPlot.colorbar(handles[end], + # ax = ax, + # ticks = locator, + # format = formatter, + # boundaries = vcat(0, vals + 0.5), + # values = vals + # ) + # end end # this sets the bg color inside the grid