From 1bd3561b7034a399349366e99cfaba71640513bd Mon Sep 17 00:00:00 2001 From: Ivan Boikov <66747290+ivan-boikov@users.noreply.github.com> Date: Mon, 17 Apr 2023 06:47:26 +0000 Subject: [PATCH] Annotations improvements (#4721) * General annotation improvements, fixed 2D and added 3D annotations * Generalize locate_annotation * Thank you, reviewdog, very cool * Formatting * Formatting, again * Bump peter-evans/create-pull-request from 4 to 5 (#4720) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4 to 5. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/v4...v5) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Add bounds check in expand_extrema (#4718) * Add bounds check in expand_extrema * Stupid mistake * Annotations in 3D, general annotation improvement * Formatting * Tuples for arrays of known length Co-authored-by: t-bltg --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: t-bltg --- src/args.jl | 2 +- src/backends/gr.jl | 10 ++- src/backends/inspectdr.jl | 21 ++++-- src/backends/pgfplotsx.jl | 22 +++--- src/backends/plotly.jl | 29 ++++++++ src/backends/pythonplot.jl | 14 ++++ src/components.jl | 147 +++++++++++++++---------------------- 7 files changed, 142 insertions(+), 103 deletions(-) diff --git a/src/args.jl b/src/args.jl index 1f7de95dc..3e2511682 100644 --- a/src/args.jl +++ b/src/args.jl @@ -1810,7 +1810,7 @@ function _update_subplot_periphery(sp::Subplot, anns::AVec) # extend annotations, and ensure we always have a (x,y,PlotText) tuple newanns = [] for ann in vcat(anns, sp[:annotations]) - append!(newanns, process_annotation(sp, ann...)) + append!(newanns, process_annotation(sp, ann)) end sp.attr[:annotations] = newanns diff --git a/src/backends/gr.jl b/src/backends/gr.jl index 737a899b6..a2a1ed831 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -973,8 +973,14 @@ function gr_display(sp::Subplot{GRBackend}, w, h, vp_canvas::GRViewport) # add annotations for ann in sp[:annotations] - x, y, val = locate_annotation(sp, ann...) - x, y = gr_is3d(sp) ? gr_w3tondc(x, y, z) : GR.wctondc(x, y) + x, y = if is3d(sp) + x, y, z, val = locate_annotation(sp, ann...) + GR.setwindow(-1, 1, -1, 1) + gr_w3tondc(x, y, z) + else + x, y, val = locate_annotation(sp, ann...) + GR.wctondc(x, y) + end gr_set_font(val.font, sp) gr_text(x, y, val.str) end diff --git a/src/backends/inspectdr.jl b/src/backends/inspectdr.jl index c6ea6c7ac..07a435fa0 100644 --- a/src/backends/inspectdr.jl +++ b/src/backends/inspectdr.jl @@ -55,10 +55,10 @@ end # Hack: suggested point size does not seem adequate relative to plot size, for some reason. _inspectdr_mapptsize(v) = 1.5 * v -_inspectdr_add_annotations(plot, x, y, val) = nothing # What kind of annotation is this? +_inspectdr_add_annotations(plot, sp::Subplot, x, y, val) = nothing # What kind of annotation is this? #plot::InspectDR.Plot2D -function _inspectdr_add_annotations(plot, x, y, val::PlotText) +function _inspectdr_add_annotations(plot, sp::Subplot, x, y, val::PlotText) vmap = Dict{Symbol,Symbol}(:top => :t, :bottom => :b) # :vcenter hmap = Dict{Symbol,Symbol}(:left => :l, :right => :r) # :hcenter align = Symbol(get(vmap, val.font.valign, :c), get(hmap, val.font.halign, :c)) @@ -72,13 +72,24 @@ function _inspectdr_add_annotations(plot, x, y, val::PlotText) x = x, y = y, font = fnt, - angle = val.font.rotation, + angle = -val.font.rotation, # minus for consistency with other backends align = align, ) InspectDR.add(plot, ann) nothing end +# placement relative to figure +function _inspectdr_add_annotations( + plot, + sp::Subplot, + pos::Union{Tuple,Symbol}, + val::PlotText, +) + x, y, val = locate_annotation(sp, pos, val) + _inspectdr_add_annotations(plot, sp, x, y, val) +end + # --------------------------------------------------------------------------- function _inspectdr_getaxisticks(ticks, gridlines, xfrm) @@ -319,7 +330,7 @@ function _series_added(plt::Plot{InspectDRBackend}, series::Series) # this is all we need to add the series_annotations text anns = series[:series_annotations] for (xi, yi, str, fnt) in EachAnn(anns, x, y) - _inspectdr_add_annotations(plot, xi, yi, PlotText(str, fnt)) + _inspectdr_add_annotations(plot, sp, xi, yi, PlotText(str, fnt)) end end @@ -426,7 +437,7 @@ function _before_layout_calcs(plt::Plot{InspectDRBackend}) # add the annotations for ann in sp[:annotations] - _inspectdr_add_annotations(plot, ann...) + _inspectdr_add_annotations(plot, sp, ann...) end end diff --git a/src/backends/pgfplotsx.jl b/src/backends/pgfplotsx.jl index 9cc872661..1292c07f3 100644 --- a/src/backends/pgfplotsx.jl +++ b/src/backends/pgfplotsx.jl @@ -120,8 +120,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) y = dy + sp_h / 2 pgfx_add_annotation!( the_plot, - x, - y, + (x, y), PlotText(plt[:plot_title], plottitlefont(plt)), pgfx_thickness_scaling(plt); options = Options("anchor" => "center"), @@ -306,8 +305,7 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) for (xi, yi, str, fnt) in EachAnn(anns, series[:x], series[:y]) pgfx_add_annotation!( axis, - xi, - yi, + (xi, yi), PlotText(str, fnt), pgfx_thickness_scaling(series), ) @@ -315,9 +313,12 @@ function (pgfx_plot::PGFPlotsXPlot)(plt::Plot{PGFPlotsXBackend}) end # for series # add subplot annotations for ann in sp[:annotations] + # [1:end-1] -> coordinates, [end] is string + loc_val = locate_annotation(sp, ann...) pgfx_add_annotation!( axis, - locate_annotation(sp, ann...)..., + loc_val[1:(end - 1)], + loc_val[end], pgfx_thickness_scaling(sp), ) end @@ -1005,8 +1006,7 @@ end function pgfx_add_annotation!( o, - x, - y, + pos, val, thickness_scaling = 1; cs = "axis cs:", @@ -1025,7 +1025,10 @@ function pgfx_add_annotation!( ), options, ) - push!(o, "\\node$(sprint(PGFPlotsX.print_tex, ann_opt)) at ($(cs)$x,$y) {$(val.str)};") + push!( + o, + "\\node$(sprint(PGFPlotsX.print_tex, ann_opt)) at ($(cs)$(join(pos, ','))) {$(val.str)};", + ) end function pgfx_fillrange_series!(axis, series, series_func, i, fillrange, rng) @@ -1092,8 +1095,9 @@ function pgfx_sanitize_plot!(plt) if key === :annotations && subplot.attr[:annotations] !== nothing old_ann = subplot.attr[key] for i in eachindex(old_ann) + # [1:end-1] is a tuple of coordinates, [end] - text subplot.attr[key][i] = - (old_ann[i][1], old_ann[i][2], pgfx_sanitize_string(old_ann[i][3])) + (old_ann[i][1:(end - 1)]..., pgfx_sanitize_string(old_ann[i][end])) end elseif value isa Union{AbstractString,AVec{<:AbstractString}} subplot.attr[key] = pgfx_sanitize_string.(value) diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index 1711f0bf9..1c251c639 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -34,6 +34,17 @@ plotly_font(font::Font, color = font.color) = KW( plotly_annotation_dict(x, y, val; xref = "paper", yref = "paper") = KW(:text => val, :xref => xref, :x => x, :yref => yref, :y => y, :showarrow => false) +plotly_annotation_dict(x, y, z, val; xref = "paper", yref = "paper", zref = "paper") = KW( + :text => val, + :xref => xref, + :x => x, + :yref => yref, + :y => y, + :zref => zref, + :z => z, + :showarrow => false, +) + plotly_annotation_dict(x, y, ptxt::PlotText; xref = "paper", yref = "paper") = merge( plotly_annotation_dict(x, y, ptxt.str; xref = xref, yref = yref), KW( @@ -44,6 +55,24 @@ plotly_annotation_dict(x, y, ptxt::PlotText; xref = "paper", yref = "paper") = m ), ) +plotly_annotation_dict( + x, + y, + z, + ptxt::PlotText; + xref = "paper", + yref = "paper", + zref = "paper", +) = merge( + plotly_annotation_dict(x, y, z, ptxt.str; xref = xref, yref = yref, zref = zref), + KW( + :font => plotly_font(ptxt.font), + :xanchor => ptxt.font.halign === :hcenter ? :center : ptxt.font.halign, + :yanchor => ptxt.font.valign === :vcenter ? :middle : ptxt.font.valign, + :rotation => -ptxt.font.rotation, + ), +) + plotly_scale(scale::Symbol) = scale === :log10 ? "log" : "-" function shrink_by(lo, sz, ratio) diff --git a/src/backends/pythonplot.jl b/src/backends/pythonplot.jl index 02fb4e207..f1fc59df1 100644 --- a/src/backends/pythonplot.jl +++ b/src/backends/pythonplot.jl @@ -1307,6 +1307,20 @@ _py_add_annotations(sp::Subplot{PythonPlotBackend}, x, y, val::PlotText) = sp.o. zorder = 999, ) +_py_add_annotations(sp::Subplot{PythonPlotBackend}, x, y, z, val::PlotText) = sp.o.text3D( + x, + y, + z, + val.str; + size = _py_thickness_scale(sp.plt, val.font.pointsize), + horizontalalignment = val.font.halign === :hcenter ? "center" : string(val.font.halign), + verticalalignment = val.font.valign === :vcenter ? "center" : string(val.font.valign), + color = _py_color(val.font.color), + rotation = val.font.rotation, + family = val.font.family, + zorder = 999, +) + # ----------------------------------------------------------------- _py_legend_pos(pos::Tuple{S,T}) where {S<:Real,T<:Real} = "lower left" diff --git a/src/components.jl b/src/components.jl index 0725ca3c9..665b518f3 100644 --- a/src/components.jl +++ b/src/components.jl @@ -604,37 +604,46 @@ _annotation(sp::Subplot, font, lab, pos...; alphabet = "abcdefghijklmnopqrstuvwx assign_annotation_coord!(axis, x) = discrete_value!(axis, x)[1] assign_annotation_coord!(axis, x::TimeType) = assign_annotation_coord!(axis, Dates.value(x)) -# Expand arrays of coordinates, positions and labels into individual annotations -# and make sure labels are of type PlotText -function process_annotation(sp::Subplot, xs, ys, labs, font = _annotationfont(sp)) - anns = [] - labs = makevec(labs) - xlength = length(methods(length, (typeof(xs),))) == 0 ? 1 : length(xs) - ylength = length(methods(length, (typeof(ys),))) == 0 ? 1 : length(ys) - for i in 1:max(xlength, ylength, length(labs)) - x, y, lab = _cycle(xs, i), _cycle(ys, i), _cycle(labs, i) - x = assign_annotation_coord!(sp[:xaxis], x) - y = assign_annotation_coord!(sp[:yaxis], y) - push!(anns, _annotation(sp, font, lab, x, y)) - end - anns +_annotation_coords(pos::Symbol) = get(_positionAliases, pos, pos) +_annotation_coords(pos) = pos + +function _process_annotation_2d(sp::Subplot, x, y, lab, font = _annotationfont(sp)) + x = assign_annotation_coord!(sp[:xaxis], x) + y = assign_annotation_coord!(sp[:yaxis], y) + _annotation(sp, font, lab, x, y) end -function process_annotation( +_process_annotation_2d( sp::Subplot, - positions::Union{AVec{Symbol},Symbol,Tuple}, - labs, + pos::Union{Tuple,Symbol}, + lab, font = _annotationfont(sp), -) - anns = [] - positions, labs = makevec(positions), makevec(labs) - for i in 1:max(length(positions), length(labs)) - pos, lab = _cycle(positions, i), _cycle(labs, i) - push!(anns, _annotation(sp, font, lab, get(_positionAliases, pos, pos))) - end - anns +) = _annotation(sp, font, lab, _annotation_coords(pos)) + +function _process_annotation_3d(sp::Subplot, x, y, z, lab, font = _annotationfont(sp)) + x = assign_annotation_coord!(sp[:xaxis], x) + y = assign_annotation_coord!(sp[:yaxis], y) + z = assign_annotation_coord!(sp[:zaxis], z) + _annotation(sp, font, lab, x, y, z) end +_process_annotation_3d( + sp::Subplot, + pos::Union{Tuple,Symbol}, + lab, + font = _annotationfont(sp), +) = _annotation(sp, font, lab, _annotation_coords(pos)) + +function _process_annotation(sp::Subplot, ann, annotation_processor::Function) + ann = makevec.(ann) + [annotation_processor(sp, _cycle.(ann, i)...) for i in 1:maximum(length.(ann))] +end + +# Expand arrays of coordinates, positions and labels into individual annotations +# and make sure labels are of type PlotText +process_annotation(sp::Subplot, ann) = + _process_annotation(sp, ann, is3d(sp) ? _process_annotation_3d : _process_annotation_2d) + function _relative_position(xmin, xmax, pos::Length{:pct}, scale::Symbol) # !TODO Add more scales in the future (asinh, sqrt) ? if scale === :log || scale === :ln @@ -648,75 +657,41 @@ function _relative_position(xmin, xmax, pos::Length{:pct}, scale::Symbol) end end +# annotation coordinates in pct const position_multiplier = Dict( - :N => (0.5pct, 0.9pct), - :NE => (0.9pct, 0.9pct), - :E => (0.9pct, 0.5pct), - :SE => (0.9pct, 0.1pct), - :S => (0.5pct, 0.1pct), - :SW => (0.1pct, 0.1pct), - :W => (0.1pct, 0.5pct), - :NW => (0.1pct, 0.9pct), - :topleft => (0.1pct, 0.9pct), - :topcenter => (0.5pct, 0.9pct), - :topright => (0.9pct, 0.9pct), - :bottomleft => (0.1pct, 0.1pct), - :bottomcenter => (0.5pct, 0.1pct), - :bottomright => (0.9pct, 0.1pct), + :N => (0.5, 0.9), + :NE => (0.9, 0.9), + :E => (0.9, 0.5), + :SE => (0.9, 0.1), + :S => (0.5, 0.1), + :SW => (0.1, 0.1), + :W => (0.1, 0.5), + :NW => (0.1, 0.9), + :topleft => (0.1, 0.9), + :topcenter => (0.5, 0.9), + :topright => (0.9, 0.9), + :bottomleft => (0.1, 0.1), + :bottomcenter => (0.5, 0.1), + :bottomright => (0.9, 0.1), ) # Give each annotation coordinates based on specified position -function locate_annotation(sp::Subplot, pos::Symbol, label::PlotText) - x, y = position_multiplier[pos] - ( - _relative_position( - axis_limits(sp, :x)..., - x, - sp[get_attr_symbol(:x, :axis)][:scale], - ), +locate_annotation(sp::Subplot, rel::Tuple, label::PlotText) = ( + map(1:length(rel), (:x, :y, :z)) do i, letter _relative_position( - axis_limits(sp, :y)..., - y, - sp[get_attr_symbol(:y, :axis)][:scale], - ), - label, - ) -end -locate_annotation(sp::Subplot, x, y, label::PlotText) = (x, y, label) -locate_annotation(sp::Subplot, x, y, z, label::PlotText) = (x, y, z, label) - -locate_annotation(sp::Subplot, rel::NTuple{2,<:Number}, label::PlotText) = ( - _relative_position( - axis_limits(sp, :x)..., - rel[1] * Plots.pct, - sp[get_attr_symbol(:x, :axis)][:scale], - ), - _relative_position( - axis_limits(sp, :y)..., - rel[2] * Plots.pct, - sp[get_attr_symbol(:y, :axis)][:scale], - ), - label, -) -locate_annotation(sp::Subplot, rel::NTuple{3,<:Number}, label::PlotText) = ( - _relative_position( - axis_limits(sp, :x)..., - rel[1] * Plots.pct, - sp[get_attr_symbol(:x, :axis)][:scale], - ), - _relative_position( - axis_limits(sp, :y)..., - rel[2] * Plots.pct, - sp[get_attr_symbol(:y, :axis)][:scale], - ), - _relative_position( - axis_limits(sp, :z)..., - rel[3] * Plots.pct, - sp[get_attr_symbol(:z, :axis)][:scale], - ), + axis_limits(sp, letter)..., + rel[i] * pct, + sp[get_attr_symbol(letter, :axis)][:scale], + ) + end..., label, ) +locate_annotation(sp::Subplot, x, y, label::PlotText) = (x, y, label) +locate_annotation(sp::Subplot, x, y, z, label::PlotText) = (x, y, z, label) +locate_annotation(sp::Subplot, pos::Symbol, label::PlotText) = + locate_annotation(sp, position_multiplier[pos], label) + # ----------------------------------------------------------------------- function expand_extrema!(a::Axis, surf::Surface)