diff --git a/src/arg_desc.jl b/src/arg_desc.jl index d490d8c9..d6cbe564 100644 --- a/src/arg_desc.jl +++ b/src/arg_desc.jl @@ -53,7 +53,8 @@ const _arg_desc = KW( :colorbar_entry => "Bool. Include this series in the color bar? Set to `false` to exclude.", # plot args -:plot_title => "String. Title for the whole plot (not the subplots) (Note: Not currently implemented)", +:plot_title => "String. Title for the whole plot (not the subplots)", +:plot_titlevspan => "Number in [0,1]. Vertical span of the whole plot title (fraction of the plot height)", :background_color => "Color Type. Base color for all backgrounds.", :background_color_outside => "Color Type or `:match` (matches `:background_color`). Color outside the plot area(s)", :foreground_color => "Color Type. Base color for all foregrounds.", diff --git a/src/args.jl b/src/args.jl index 965f3fc2..96fbaed6 100644 --- a/src/args.jl +++ b/src/args.jl @@ -331,13 +331,15 @@ const _series_defaults = KW( const _plot_defaults = KW( :plot_title => "", + :plot_titleindex => 0, :plot_titlefontsize => 16, - :plot_title_location => :center, # also :left or :right + :plot_titlelocation => :center, # also :left or :right :plot_titlefontfamily => :match, :plot_titlefonthalign => :hcenter, :plot_titlefontvalign => :vcenter, :plot_titlefontrotation => 0.0, :plot_titlefontcolor => :match, + :plot_titlevspan => 0.05, # vertical span of the plot title, here 5% :background_color => colorant"white", # default for all backgrounds, :background_color_outside => :match, # background outside grid, :foreground_color => :auto, # default for all foregrounds, and title color, diff --git a/src/backends/pgfplotsx.jl b/src/backends/pgfplotsx.jl index 22cc9342..030ddefa 100644 --- a/src/backends/pgfplotsx.jl +++ b/src/backends/pgfplotsx.jl @@ -118,11 +118,22 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) end for sp in plt.subplots - bb1 = sp.plotarea bb2 = bbox(sp) + dx, dy = bb2.x0 + sp_width = width(bb2) + sp_height = height(bb2) + if sp[:subplot_index] == plt[:plot_titleindex] + x = dx + sp_width / 2 - 10mm # FIXME: get rid of magic constant + y = dy + sp_height / 2 + pgfx_add_annotation!(the_plot, x, y, PlotText(plt[:plot_title], plottitlefont(plt)), pgfx_thickness_scaling(plt); + cs = "", + options = PGFPlotsX.Options("anchor" => "center") + ) + continue + end + sp_width = width(bb2) sp_height = height(bb2) - dx, dy = bb2.x0 lpad = leftpad(sp) + sp[:left_margin] rpad = rightpad(sp) + sp[:right_margin] tpad = toppad(sp) + sp[:top_margin] @@ -131,7 +142,6 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) dy += tpad axis_height = sp_height - (tpad + bpad) axis_width = sp_width - (rpad + lpad) - title_cstr = plot_color(sp[:titlefontcolor]) title_a = alpha(title_cstr) title_loc = sp[:titlelocation] @@ -999,24 +1009,27 @@ function pgfx_marker(plotattributes, i = 1) ) end -function pgfx_add_annotation!(o, x, y, val, thickness_scaling = 1) +function pgfx_add_annotation!(o, x, y, val, thickness_scaling = 1; cs = "axis cs:", options = PGFPlotsX.Options()) # Construct the style string. cstr = val.font.color a = alpha(cstr) push!( o, - [ + join([ "\\node", - PGFPlotsX.Options( - get((center = "", left = "right", right = "left"), val.font.halign, "") => - nothing, - "color" => cstr, - "draw opacity" => convert(Float16, a), - "rotate" => val.font.rotation, - "font" => pgfx_font(val.font.pointsize, thickness_scaling), - ), - " at (axis cs:$x, $y) {$(val.str)};", - ], + sprint(PGFPlotsX.print_tex, merge( + PGFPlotsX.Options( + get((hcenter = "", left = "right", right = "left"), val.font.halign, "") => + nothing, + "color" => cstr, + "draw opacity" => convert(Float16, a), + "rotate" => val.font.rotation, + "font" => pgfx_font(val.font.pointsize, thickness_scaling), + ), + options + )), + string(" at (", cs, x, ",", y, ") {", val.str, "};"), + ]), ) end diff --git a/src/pipeline.jl b/src/pipeline.jl index 61f52cd1..8d54b9cb 100644 --- a/src/pipeline.jl +++ b/src/pipeline.jl @@ -262,6 +262,7 @@ function _subplot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW}) sp_attrs[sp] = attr end + _add_plot_title!(plt) # override subplot/axis args. `sp_attrs` take precendence for (idx, sp) in enumerate(plt.subplots) attr = if !haskey(plotattributes, :subplot) || plotattributes[:subplot] == idx @@ -274,12 +275,35 @@ function _subplot_setup(plt::Plot, plotattributes::AKW, kw_list::Vector{KW}) # do we need to link any axes together? link_axes!(plt.layout, plt[:link]) + return nothing end function series_idx(kw_list::AVec{KW}, kw::AKW) Int(kw[:series_plotindex]) - Int(kw_list[1][:series_plotindex]) + 1 end +function _add_plot_title!(plt) + plot_title = plt[:plot_title] + if plot_title != "" + the_layout = plt.layout + vspan = plt[:plot_titlevspan] + plt.layout = grid(2, 1, heights=(vspan, 1 - vspan)) + plt.layout.grid[1, 1] = subplot = Subplot(plt.backend, parent=plt.layout[1, 1]) + plt.layout.grid[2, 1] = the_layout + subplot.plt = plt + # propagate arguments plt[:plot_titleXXX] --> subplot[:titleXXX] + for sym ∈ filter(x -> startswith(string(x), "plot_title"), keys(_plot_defaults)) + subplot[Symbol(string(sym)[length("plot_") + 1:end])] = plt[sym] + end + plt[:force_minpad] = nothing, 0px, nothing, 0px + subplot[:subplot_index] = last(plt.subplots)[:subplot_index] + 1 + plt[:plot_titleindex] = subplot[:subplot_index] + subplot[:framestyle] = :none + subplot[:margin] = 0px + push!(plt.subplots, subplot) + end + return nothing +end ## Series recipes diff --git a/src/plot.jl b/src/plot.jl index a3373917..a01e6451 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -160,6 +160,7 @@ function plot!(plt1::Plot, plt2::Plot, plts_tail::Plot...; kw...) cmdidx += 1 end end + _add_plot_title!(plt) # first apply any args for the subplots for (idx,sp) in enumerate(plt.subplots) @@ -225,6 +226,16 @@ function prepare_output(plt::Plot) _update_min_padding!(sp) end + # spedific to :plot_title see _add_plot_title! + force_minpad = get(plt, :force_minpad, ()) + if !isempty(force_minpad) + for i ∈ eachindex(plt.layout.grid) + plt.layout.grid[i].minpad = Tuple( + i === nothing ? j : i for (i, j) ∈ zip(force_minpad, plt.layout.grid[i].minpad) + ) + end + end + # now another pass down, to update the bounding boxes update_child_bboxes!(plt.layout)