Skip to content

Commit abb9ecc

Browse files
Fix handling of vector attributes in plotly code (#5175)
* fix segment handlin in plotly code * remove show * change handling of segments to fix plotly bar plots * formatting + show * more shows * fix histogram case * one example testset per backend * fix pythonplot code * runic formatting
1 parent 6c353ee commit abb9ecc

File tree

5 files changed

+74
-82
lines changed

5 files changed

+74
-82
lines changed

src/backends/plotly.jl

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,8 @@ function plotly_layout(plt::Plot)
335335
plotattributes_out[:hovermode] = "none"
336336
end
337337

338-
return plotattributes_out = recursive_merge(plotattributes_out, plt.attr[:extra_plot_kwargs])
338+
return plotattributes_out =
339+
recursive_merge(plotattributes_out, plt.attr[:extra_plot_kwargs])
339340
end
340341

341342
function plotly_add_legend!(plotattributes_out::KW, sp::Subplot)
@@ -758,12 +759,8 @@ function plotly_colorbar(sp::Subplot)
758759
end
759760

760761
function plotly_series_shapes(plt::Plot, series::Series, clims)
761-
segments = series_segments(series; check = true)
762-
plotattributes_outs = map(i -> KW(), 1:length(segments))
763-
764-
# TODO: create a plotattributes_out for each polygon
765-
# x, y = series[:x], series[:y]
766-
762+
plotattributes_outs = KW[]
763+
i = 1
767764
# these are the axes that the series should be mapped to
768765
x_idx, y_idx = plotly_link_indicies(plt, series[:subplot])
769766
plotattributes_base = KW(
@@ -773,23 +770,16 @@ function plotly_series_shapes(plt::Plot, series::Series, clims)
773770
:legendgroup => series[:label],
774771
)
775772

776-
x, y = (
777-
plotly_data(series, letter, data) for
778-
(letter, data) in zip((:x, :y), shape_data(series, 100))
779-
)
780-
781-
for (k, segment) in enumerate(segments)
782-
i, rng = segment.attr_index, segment.range
783-
length(rng) < 2 && continue
773+
for (xs, ys) in zip(nansplit(series[:x]), nansplit(series[:y]))
784774

785775
# to draw polygons, we actually draw lines with fill
786776
plotattributes_out = merge(
787777
plotattributes_base,
788778
KW(
789779
:type => "scatter",
790780
:mode => "lines",
791-
:x => vcat(x[rng], x[rng[1]]),
792-
:y => vcat(y[rng], y[rng[1]]),
781+
:x => xs,
782+
:y => ys,
793783
:fill => "tozeroy",
794784
:fillcolor => rgba_string(
795785
plot_color(get_fillcolor(series, clims, i), get_fillalpha(series, i)),
@@ -805,10 +795,11 @@ function plotly_series_shapes(plt::Plot, series::Series, clims)
805795
:dash => string(get_linestyle(series, i)),
806796
)
807797
end
808-
plotattributes_out[:showlegend] = k == 1 ? should_add_to_legend(series) : false
798+
plotattributes_out[:showlegend] = i == 1 ? should_add_to_legend(series) : false
809799
plotly_polar!(plotattributes_out, series)
810800
plotly_adjust_hover_label!(plotattributes_out, _cycle(series[:hover], i))
811-
plotattributes_outs[k] = merge(plotattributes_out, series[:extra_kwargs])
801+
push!(plotattributes_outs, merge(plotattributes_out, series[:extra_kwargs]))
802+
i += 1
812803
end
813804
if series[:fill_z] !== nothing
814805
push!(plotattributes_outs, plotly_colorbar_hack(series, plotattributes_base, :fill))

src/backends/pythonplot.jl

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series)
451451
end
452452

453453
if st === :shape
454-
for segment in series_segments(series)
454+
for segment in series_segments(series, st)
455455
i, rng = segment.attr_index, segment.range
456456
if length(rng) > 1
457457
lc = get_linecolor(series, clims, i, cbar_scale)
@@ -463,8 +463,6 @@ function _py_add_series(plt::Plot{PythonPlotBackend}, series::Series)
463463
has_fs = !isnothing(fs)
464464

465465
path = mpl.path.Path(hcat(x[rng], y[rng]))
466-
# FIXME: path can be un-filled e.g. ex 56,
467-
# where rectangles are created using 4 paths instead of a single one
468466

469467
# shape outline (and potentially solid fill)
470468
mpl.patches.PathPatch(

src/recipes.jl

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -482,15 +482,15 @@ end
482482
y := yseg.pts
483483
# expand attributes to match indices in new series data
484484
for k in _segmenting_vector_attributes _segmenting_array_attributes
485-
if (v = get(plotattributes, k, nothing)) isa AVec
486-
if eachindex(v) != eachindex(y)
487-
@warn "Indices $(eachindex(v)) of attribute `$k` do not match data indices $(eachindex(y))."
488-
end
489-
# Each segment is 6 elements long, including the NaN separator.
490-
# One segment is created for each non-NaN element of `procy`.
491-
# There is no trailing NaN, so the last repetition is dropped.
492-
plotattributes[k] = @views repeat(v[valid_i]; inner = 6)[1:(end - 1)]
493-
end
485+
# if (v = get(plotattributes, k, nothing)) isa AVec
486+
# if eachindex(v) != eachindex(y)
487+
# @warn "Indices $(eachindex(v)) of attribute `$k` do not match data indices $(eachindex(y))."
488+
# end
489+
# # Each segment is 6 elements long, including the NaN separator.
490+
# # One segment is created for each non-NaN element of `procy`.
491+
# # There is no trailing NaN, so the last repetition is dropped.
492+
# plotattributes[k] = @views repeat(v[valid_i]; inner = 6)[1:(end - 1)]
493+
# end
494494
end
495495
()
496496
end

src/utils.jl

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# ---------------------------------------------------------------
2-
bool_env(x, default)::Bool =
3-
try
4-
return parse(Bool, get(ENV, x, default))
5-
catch e
6-
@warn e
7-
return false
2+
function bool_env(x, default)::Bool
3+
try
4+
return parse(Bool, get(ENV, x, default))
5+
catch e
6+
@warn e
7+
return false
8+
end
89
end
910

1011
treats_y_as_x(seriestype) =
@@ -36,10 +37,8 @@ to_nan(::Type{NTuple{2, Float64}}) = (NaN, NaN)
3637
to_nan(::Type{NTuple{3, Float64}}) = (NaN, NaN, NaN)
3738

3839
coords(segs::Segments{Float64}) = segs.pts
39-
coords(segs::Segments{NTuple{2, Float64}}) =
40-
(map(p -> p[1], segs.pts), map(p -> p[2], segs.pts))
41-
coords(segs::Segments{NTuple{3, Float64}}) =
42-
(map(p -> p[1], segs.pts), map(p -> p[2], segs.pts), map(p -> p[3], segs.pts))
40+
coords(segs::Segments{NTuple{2, Float64}}) = (map(p -> p[1], segs.pts), map(p -> p[2], segs.pts))
41+
coords(segs::Segments{NTuple{3, Float64}}) = (map(p -> p[1], segs.pts), map(p -> p[2], segs.pts), map(p -> p[3], segs.pts))
4342

4443
function Base.push!(segments::Segments{T}, vs...) where {T}
4544
isempty(segments.pts) || push!(segments.pts, to_nan(T))
@@ -112,21 +111,21 @@ function series_segments(series::Series, seriestype::Symbol = :path; check = fal
112111
end
113112

114113
segments = if has_attribute_segments(series)
115-
map(nan_segments) do r
114+
(
116115
if seriestype === :shape
117-
warn_on_inconsistent_shape_attr(series, x, y, z, r)
118-
(SeriesSegment(r, first(r)),)
116+
# warn_on_inconsistent_shape_attr(series, x, y, z, r)
117+
(SeriesSegment(segment, j),)
119118
elseif seriestype in (:scatter, :scatter3d)
120-
(SeriesSegment(i:i, i) for i in r)
119+
(SeriesSegment(i:i, i) for i in segment)
121120
else
122-
(SeriesSegment(i:(i + 1), i) for i in first(r):(last(r) - 1))
123-
end
124-
end |> Iterators.flatten
121+
(SeriesSegment(i:(i + 1), i) for i in first(segment):(last(segment) - 1))
122+
end for (j, segment) in enumerate(nan_segments)
123+
) |> Iterators.flatten
125124
else
126125
(SeriesSegment(r, 1) for r in nan_segments)
127126
end
128127

129-
warn_on_attr_dim_mismatch(series, x, y, z, segments)
128+
# warn_on_attr_dim_mismatch(series, x, y, z, segments)
130129
return segments
131130
end
132131

@@ -198,7 +197,8 @@ _cycle(wrapper::InputWrapper, idx::Int) = wrapper.obj
198197
_cycle(wrapper::InputWrapper, idx::AVec{Int}) = wrapper.obj
199198

200199
_cycle(v::AVec, idx::Int) = v[mod(idx, axes(v, 1))]
201-
_cycle(v::AMat, idx::Int) = size(v, 1) == 1 ? v[end, mod(idx, axes(v, 2))] : v[:, mod(idx, axes(v, 2))]
200+
_cycle(v::AMat, idx::Int) =
201+
size(v, 1) == 1 ? v[end, mod(idx, axes(v, 2))] : v[:, mod(idx, axes(v, 2))]
202202
_cycle(v, idx::Int) = v
203203

204204
_cycle(v::AVec, indices::AVec{Int}) = map(i -> _cycle(v, i), indices)
@@ -376,20 +376,22 @@ function nanvcat(vs::AVec)
376376
return v_out
377377
end
378378

379-
sort_3d_axes(x, y, z, letter) =
380-
if letter === :x
381-
x, y, z
382-
elseif letter === :y
383-
y, x, z
384-
else
385-
z, y, x
379+
function sort_3d_axes(x, y, z, letter)
380+
return if letter === :x
381+
x, y, z
382+
elseif letter === :y
383+
y, x, z
384+
else
385+
z, y, x
386+
end
386387
end
387388

388-
axes_letters(sp, letter) =
389-
if RecipesPipeline.is3d(sp)
390-
sort_3d_axes(:x, :y, :z, letter)
391-
else
392-
letter === :x ? (:x, :y) : (:y, :x)
389+
function axes_letters(sp, letter)
390+
return if RecipesPipeline.is3d(sp)
391+
sort_3d_axes(:x, :y, :z, letter)
392+
else
393+
letter === :x ? (:x, :y) : (:y, :x)
394+
end
393395
end
394396

395397
handle_surface(z) = z
@@ -1009,19 +1011,20 @@ function ___straightline_data(xl, yl, x, y, exp_fact)
10091011
)
10101012
end
10111013

1012-
__straightline_data(xl, yl, x, y, exp_fact) =
1013-
if (n = length(x)) == 2
1014-
___straightline_data(xl, yl, x, y, exp_fact)
1015-
else
1016-
k, r = divrem(n, 3)
1017-
@assert r == 0 "Misformed data. `straightline_data` either accepts vectors of length 2 or 3k. The provided series has length $n"
1018-
xdata, ydata = fill(NaN, n), fill(NaN, n)
1019-
for i in 1:k
1020-
inds = (3i - 2):(3i - 1)
1021-
xdata[inds], ydata[inds] =
1022-
___straightline_data(xl, yl, x[inds], y[inds], exp_fact)
1014+
function __straightline_data(xl, yl, x, y, exp_fact)
1015+
return if (n = length(x)) == 2
1016+
___straightline_data(xl, yl, x, y, exp_fact)
1017+
else
1018+
k, r = divrem(n, 3)
1019+
@assert r == 0 "Misformed data. `straightline_data` either accepts vectors of length 2 or 3k. The provided series has length $n"
1020+
xdata, ydata = fill(NaN, n), fill(NaN, n)
1021+
for i in 1:k
1022+
inds = (3i - 2):(3i - 1)
1023+
xdata[inds], ydata[inds] =
1024+
___straightline_data(xl, yl, x[inds], y[inds], exp_fact)
1025+
end
1026+
xdata, ydata
10231027
end
1024-
xdata, ydata
10251028
end
10261029

10271030
_straightline_data(::Val{true}, ::Function, ::Function, ::Function, ::Function, args...) =
@@ -1282,12 +1285,13 @@ function protectedstring(s)
12821285
To suppress all axis labels, pass an empty string to `xlabel`, etc.
12831286
To suppress units in axis labels pass `unitformat = :nounit` or `unitformat=(l,u)->l`
12841287
(equivalently for `xunitformat`, `yunitformat`, etc.).
1285-
""", :protectedstring, force = true
1288+
""",
1289+
:protectedstring,
1290+
force = true,
12861291
)
12871292
return ProtectedString(s)
12881293
end
12891294

1290-
12911295
"""
12921296
P_str(s)
12931297

test/test_backends.jl

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ is_pkgeval() || @testset "PlotlyJS" begin
213213
end
214214
end
215215

216-
is_pkgeval() || @testset "Examples" begin
216+
is_pkgeval() || @testset "Examples $be" for be in TEST_BACKENDS
217217
callback(m, pkgname, i) = begin
218218
pl = m.Plots.current()
219219
save_func = (; pgfplotsx = m.Plots.pdf, unicodeplots = m.Plots.txt) # fastest `savefig` for each backend
@@ -224,9 +224,8 @@ is_pkgeval() || @testset "Examples" begin
224224
)
225225
@test filesize(fn) > 1_000
226226
end
227-
Sys.islinux() && for be in TEST_BACKENDS
228-
skip = vcat(Plots._backend_skips[be], blacklist)
229-
Plots.test_examples(be; skip, callback, disp = is_ci(), strict = true) # `ci` display for coverage
230-
closeall()
231-
end
227+
Sys.islinux() || continue
228+
skip = vcat(Plots._backend_skips[be], blacklist)
229+
Plots.test_examples(be; skip, callback, disp = is_ci(), strict = true) # `ci` display for coverage
230+
closeall()
232231
end

0 commit comments

Comments
 (0)