Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Annotations improvements #4721

Merged
merged 11 commits into from
Apr 17, 2023
2 changes: 1 addition & 1 deletion src/args.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 8 additions & 2 deletions src/backends/gr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 16 additions & 5 deletions src/backends/inspectdr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
22 changes: 13 additions & 9 deletions src/backends/pgfplotsx.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down Expand Up @@ -306,18 +305,20 @@ 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),
)
end
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
Expand Down Expand Up @@ -1005,8 +1006,7 @@ end

function pgfx_add_annotation!(
o,
x,
y,
pos,
val,
thickness_scaling = 1;
cs = "axis cs:",
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
29 changes: 29 additions & 0 deletions src/backends/plotly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions src/backends/pythonplot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
147 changes: 61 additions & 86 deletions src/components.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
ivan-boikov marked this conversation as resolved.
Show resolved Hide resolved
_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)
Expand Down