Fix for "segmented" attributes with NaNs
This commit is contained in:
parent
cd851df28a
commit
42b3c5625f
@ -842,6 +842,7 @@ const _pgfplotsx_marker = [
|
|||||||
:rtriangle,
|
:rtriangle,
|
||||||
:cross,
|
:cross,
|
||||||
:xcross,
|
:xcross,
|
||||||
|
:x,
|
||||||
:star5,
|
:star5,
|
||||||
:pentagon,
|
:pentagon,
|
||||||
:hline,
|
:hline,
|
||||||
|
|||||||
@ -1561,7 +1561,7 @@ function gr_add_series(sp, series)
|
|||||||
gr_draw_markers(series, x, y, clims)
|
gr_draw_markers(series, x, y, clims)
|
||||||
end
|
end
|
||||||
elseif st === :shape
|
elseif st === :shape
|
||||||
gr_draw_shapes(series, x, y, clims)
|
gr_draw_shapes(series, clims)
|
||||||
elseif st in (:path3d, :scatter3d)
|
elseif st in (:path3d, :scatter3d)
|
||||||
gr_draw_segments_3d(series, x, y, z, clims)
|
gr_draw_segments_3d(series, x, y, z, clims)
|
||||||
if st === :scatter3d || series[:markershape] !== :none
|
if st === :scatter3d || series[:markershape] !== :none
|
||||||
@ -1613,12 +1613,13 @@ end
|
|||||||
function gr_draw_segments(series, x, y, fillrange, clims)
|
function gr_draw_segments(series, x, y, fillrange, clims)
|
||||||
st = series[:seriestype]
|
st = series[:seriestype]
|
||||||
if x !== nothing && length(x) > 1
|
if x !== nothing && length(x) > 1
|
||||||
segments = iter_segments(series, st)
|
segments = series_segments(series, st)
|
||||||
# do area fill
|
# do area fill
|
||||||
if fillrange !== nothing
|
if fillrange !== nothing
|
||||||
GR.setfillintstyle(GR.INTSTYLE_SOLID)
|
GR.setfillintstyle(GR.INTSTYLE_SOLID)
|
||||||
fr_from, fr_to = (is_2tuple(fillrange) ? fillrange : (y, fillrange))
|
fr_from, fr_to = (is_2tuple(fillrange) ? fillrange : (y, fillrange))
|
||||||
for (i, rng) in enumerate(segments)
|
for segment in segments
|
||||||
|
i, rng = segment.attr_index, segment.range
|
||||||
fc = get_fillcolor(series, clims, i)
|
fc = get_fillcolor(series, clims, i)
|
||||||
gr_set_fillcolor(fc)
|
gr_set_fillcolor(fc)
|
||||||
fx = _cycle(x, vcat(rng, reverse(rng)))
|
fx = _cycle(x, vcat(rng, reverse(rng)))
|
||||||
@ -1630,7 +1631,8 @@ function gr_draw_segments(series, x, y, fillrange, clims)
|
|||||||
|
|
||||||
# draw the line(s)
|
# draw the line(s)
|
||||||
if st in (:path, :straightline)
|
if st in (:path, :straightline)
|
||||||
for (i, rng) in enumerate(segments)
|
for segment in segments
|
||||||
|
i, rng = segment.attr_index, segment.range
|
||||||
lc = get_linecolor(series, clims, i)
|
lc = get_linecolor(series, clims, i)
|
||||||
gr_set_line(
|
gr_set_line(
|
||||||
get_linewidth(series, i), get_linestyle(series, i), lc, series
|
get_linewidth(series, i), get_linestyle(series, i), lc, series
|
||||||
@ -1648,8 +1650,9 @@ end
|
|||||||
function gr_draw_segments_3d(series, x, y, z, clims)
|
function gr_draw_segments_3d(series, x, y, z, clims)
|
||||||
if series[:seriestype] === :path3d && length(x) > 1
|
if series[:seriestype] === :path3d && length(x) > 1
|
||||||
lz = series[:line_z]
|
lz = series[:line_z]
|
||||||
segments = iter_segments(series, :path3d)
|
segments = series_segments(series, :path3d)
|
||||||
for (i, rng) in enumerate(segments)
|
for segment in segments
|
||||||
|
i, rng = segment.attr_index, segment.range
|
||||||
lc = get_linecolor(series, clims, i)
|
lc = get_linecolor(series, clims, i)
|
||||||
gr_set_line(
|
gr_set_line(
|
||||||
get_linewidth(series, i), get_linestyle(series, i), lc, series
|
get_linewidth(series, i), get_linestyle(series, i), lc, series
|
||||||
@ -1674,8 +1677,9 @@ function gr_draw_markers(
|
|||||||
|
|
||||||
shapes = series[:markershape]
|
shapes = series[:markershape]
|
||||||
if shapes != :none
|
if shapes != :none
|
||||||
for (i, rng) in enumerate(iter_segments(series, :scatter))
|
for segment in series_segments(series, :scatter)
|
||||||
rng = intersect(eachindex(x), rng)
|
i = segment.attr_index
|
||||||
|
rng = intersect(eachindex(x), segment.range)
|
||||||
if !isempty(rng)
|
if !isempty(rng)
|
||||||
ms = get_thickness_scaling(series) * _cycle(msize, i)
|
ms = get_thickness_scaling(series) * _cycle(msize, i)
|
||||||
msw = get_thickness_scaling(series) * _cycle(strokewidth, i)
|
msw = get_thickness_scaling(series) * _cycle(strokewidth, i)
|
||||||
@ -1688,9 +1692,10 @@ function gr_draw_markers(
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function gr_draw_shapes(series, x, y, clims)
|
function gr_draw_shapes(series, clims)
|
||||||
x, y = shape_data(series)
|
x, y = shape_data(series)
|
||||||
for (i,rng) in enumerate(iter_segments(x, y))
|
for segment in series_segments(series, :shape)
|
||||||
|
i, rng = segment.attr_index, segment.range
|
||||||
if length(rng) > 1
|
if length(rng) > 1
|
||||||
# connect to the beginning
|
# connect to the beginning
|
||||||
rng = vcat(rng, rng[1])
|
rng = vcat(rng, rng[1])
|
||||||
|
|||||||
@ -339,9 +339,10 @@ end
|
|||||||
|
|
||||||
function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, opt)
|
function pgfx_add_series!(::Val{:path}, axis, series_opt, series, series_func, opt)
|
||||||
# treat segments
|
# treat segments
|
||||||
segments = iter_segments(series, series[:seriestype])
|
segments = collect(series_segments(series, series[:seriestype]))
|
||||||
sf = opt[:fillrange]
|
sf = opt[:fillrange]
|
||||||
for (i, rng) in enumerate(segments)
|
for segment in segments
|
||||||
|
i, rng = segment.attr_index, segment.range
|
||||||
segment_opt = PGFPlotsX.Options()
|
segment_opt = PGFPlotsX.Options()
|
||||||
segment_opt = merge(segment_opt, pgfx_linestyle(opt, i))
|
segment_opt = merge(segment_opt, pgfx_linestyle(opt, i))
|
||||||
if opt[:markershape] != :none
|
if opt[:markershape] != :none
|
||||||
|
|||||||
@ -643,7 +643,7 @@ function plotly_series(plt::Plot, series::Series)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function plotly_series_shapes(plt::Plot, series::Series, clims)
|
function plotly_series_shapes(plt::Plot, series::Series, clims)
|
||||||
segments = iter_segments(series)
|
segments = collect(series_segments(series))
|
||||||
plotattributes_outs = Vector{KW}(undef, length(segments))
|
plotattributes_outs = Vector{KW}(undef, length(segments))
|
||||||
|
|
||||||
# TODO: create a plotattributes_out for each polygon
|
# TODO: create a plotattributes_out for each polygon
|
||||||
@ -662,7 +662,8 @@ function plotly_series_shapes(plt::Plot, series::Series, clims)
|
|||||||
for (letter, data) in zip((:x, :y), shape_data(series, 100))
|
for (letter, data) in zip((:x, :y), shape_data(series, 100))
|
||||||
)
|
)
|
||||||
|
|
||||||
for (i,rng) in enumerate(segments)
|
for segment in segments
|
||||||
|
i, rng = segment.attr_index, segment.range
|
||||||
length(rng) < 2 && continue
|
length(rng) < 2 && continue
|
||||||
|
|
||||||
# to draw polygons, we actually draw lines with fill
|
# to draw polygons, we actually draw lines with fill
|
||||||
@ -705,14 +706,16 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z
|
|||||||
hasfillrange = st in (:path, :scatter, :scattergl, :straightline) &&
|
hasfillrange = st in (:path, :scatter, :scattergl, :straightline) &&
|
||||||
(isa(series[:fillrange], AbstractVector) || isa(series[:fillrange], Tuple))
|
(isa(series[:fillrange], AbstractVector) || isa(series[:fillrange], Tuple))
|
||||||
|
|
||||||
segments = iter_segments(series, st)
|
segments = collect(series_segments(series, st))
|
||||||
plotattributes_outs = fill(KW(), (hasfillrange ? 2 : 1 ) * length(segments))
|
plotattributes_outs = fill(KW(), (hasfillrange ? 2 : 1 ) * length(segments))
|
||||||
|
|
||||||
needs_scatter_fix = !isscatter && hasmarker && !any(isnan,y) && length(segments) > 1
|
needs_scatter_fix = !isscatter && hasmarker && !any(isnan,y) && length(segments) > 1
|
||||||
|
|
||||||
for (i,rng) in enumerate(segments)
|
for (k, segment) in enumerate(segments)
|
||||||
|
i, rng = segment.attr_index, segment.range
|
||||||
|
|
||||||
plotattributes_out = deepcopy(plotattributes_base)
|
plotattributes_out = deepcopy(plotattributes_base)
|
||||||
plotattributes_out[:showlegend] = i==1 ? should_add_to_legend(series) : false
|
plotattributes_out[:showlegend] = k==1 ? should_add_to_legend(series) : false
|
||||||
plotattributes_out[:legendgroup] = series[:label]
|
plotattributes_out[:legendgroup] = series[:label]
|
||||||
|
|
||||||
# set the type
|
# set the type
|
||||||
@ -805,9 +808,9 @@ function plotly_series_segments(series::Series, plotattributes_base::KW, x, y, z
|
|||||||
delete!(plotattributes_out, :fillcolor)
|
delete!(plotattributes_out, :fillcolor)
|
||||||
end
|
end
|
||||||
|
|
||||||
plotattributes_outs[(2 * i - 1):(2 * i)] = [plotattributes_out_fillrange, plotattributes_out]
|
plotattributes_outs[(2k-1):(2k)] = [plotattributes_out_fillrange, plotattributes_out]
|
||||||
else
|
else
|
||||||
plotattributes_outs[i] = plotattributes_out
|
plotattributes_outs[k] = plotattributes_out
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -440,9 +440,10 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
|||||||
# end
|
# end
|
||||||
# push!(handles, handle)
|
# push!(handles, handle)
|
||||||
# else
|
# else
|
||||||
for (i, rng) in enumerate(iter_segments(series, st))
|
for (k, segment) in enumerate(series_segments(series, st))
|
||||||
|
i, rng = segment.attr_index, segment.range
|
||||||
handle = ax."plot"((arg[rng] for arg in xyargs)...;
|
handle = ax."plot"((arg[rng] for arg in xyargs)...;
|
||||||
label = i == 1 ? series[:label] : "",
|
label = k == 1 ? series[:label] : "",
|
||||||
zorder = series[:series_plotindex],
|
zorder = series[:series_plotindex],
|
||||||
color = py_color(single_color(get_linecolor(series, clims, i)), get_linealpha(series, i)),
|
color = py_color(single_color(get_linecolor(series, clims, i)), get_linealpha(series, i)),
|
||||||
linewidth = py_thickness_scale(plt, get_linewidth(series, i)),
|
linewidth = py_thickness_scale(plt, get_linewidth(series, i)),
|
||||||
@ -486,7 +487,8 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
|||||||
if series[:markershape] != :none && st in (
|
if series[:markershape] != :none && st in (
|
||||||
:path, :scatter, :path3d, :scatter3d, :steppre, :steppost, :bar
|
:path, :scatter, :path3d, :scatter3d, :steppre, :steppost, :bar
|
||||||
)
|
)
|
||||||
for (i, rng) in enumerate(iter_segments(series, :scatter))
|
for segment in series_segments(series, :scatter)
|
||||||
|
i, rng = segment.attr_index, segment.range
|
||||||
xyargs = if st == :bar && !isvertical(series)
|
xyargs = if st == :bar && !isvertical(series)
|
||||||
if RecipesPipeline.is3d(sp)
|
if RecipesPipeline.is3d(sp)
|
||||||
y[rng], x[rng], z[rng]
|
y[rng], x[rng], z[rng]
|
||||||
@ -679,7 +681,8 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
|||||||
|
|
||||||
if st == :shape
|
if st == :shape
|
||||||
handle = []
|
handle = []
|
||||||
for (i, rng) in enumerate(iter_segments(series))
|
for segment in series_segments(series)
|
||||||
|
i, rng = segment.attr_index, segment.range
|
||||||
if length(rng) > 1
|
if length(rng) > 1
|
||||||
path = pypath."Path"(hcat(x[rng], y[rng]))
|
path = pypath."Path"(hcat(x[rng], y[rng]))
|
||||||
patches = pypatches."PathPatch"(
|
patches = pypatches."PathPatch"(
|
||||||
@ -706,7 +709,8 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
|||||||
# handle area filling
|
# handle area filling
|
||||||
fillrange = series[:fillrange]
|
fillrange = series[:fillrange]
|
||||||
if fillrange !== nothing && st != :contour
|
if fillrange !== nothing && st != :contour
|
||||||
for (i, rng) in enumerate(iter_segments(series))
|
for segment in series_segments(series)
|
||||||
|
i, rng = segment.attr_index, segment.range
|
||||||
f, dim1, dim2 = if isvertical(series)
|
f, dim1, dim2 = if isvertical(series)
|
||||||
:fill_between, x[rng], y[rng]
|
:fill_between, x[rng], y[rng]
|
||||||
else
|
else
|
||||||
|
|||||||
@ -1031,13 +1031,26 @@ const _examples = PlotExample[
|
|||||||
[quote
|
[quote
|
||||||
yv = ones(9)
|
yv = ones(9)
|
||||||
ys = [1; 1; NaN; ones(6)]
|
ys = [1; 1; NaN; ones(6)]
|
||||||
plot(
|
y = 5 .- [yv 2ys 3yv 4ys]
|
||||||
5 .- [yv 2ys 3yv 4ys],
|
|
||||||
|
plt_color_rows = plot(
|
||||||
|
y,
|
||||||
seriestype = [:path :path :scatter :scatter],
|
seriestype = [:path :path :scatter :scatter],
|
||||||
markershape = [:utriangle, :rect],
|
markershape = [:utriangle, :rect],
|
||||||
markersize = 8,
|
markersize = 8,
|
||||||
color = [:red, :black],
|
color = [:red, :black],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
plt_z_cols = plot(
|
||||||
|
y,
|
||||||
|
markershape = [:utriangle :x :circle :square],
|
||||||
|
markersize = [5 10 10 5],
|
||||||
|
marker_z = [5 4 3 2],
|
||||||
|
line_z = [1 3 3 1],
|
||||||
|
linewidth = [1 10 5 1]
|
||||||
|
)
|
||||||
|
|
||||||
|
plot(plt_color_rows, plt_z_cols)
|
||||||
end]
|
end]
|
||||||
),
|
),
|
||||||
PlotExample( # 49
|
PlotExample( # 49
|
||||||
|
|||||||
52
src/utils.jl
52
src/utils.jl
@ -53,10 +53,16 @@ function Base.push!(segments::Segments{T}, vs::AVec) where T
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
struct SeriesSegment
|
||||||
|
# indexes of this segement in series data vectors
|
||||||
|
range::UnitRange
|
||||||
|
# index into vector-valued attributes corresponding to this segment
|
||||||
|
attr_index::Int
|
||||||
|
end
|
||||||
|
|
||||||
# -----------------------------------------------------
|
# -----------------------------------------------------
|
||||||
# helper to manage NaN-separated segments
|
# helper to manage NaN-separated segments
|
||||||
|
struct NaNSegmentsIterator
|
||||||
mutable struct SegmentsIterator
|
|
||||||
args::Tuple
|
args::Tuple
|
||||||
n1::Int
|
n1::Int
|
||||||
n2::Int
|
n2::Int
|
||||||
@ -66,28 +72,26 @@ function iter_segments(args...)
|
|||||||
tup = Plots.wraptuple(args)
|
tup = Plots.wraptuple(args)
|
||||||
n1 = minimum(map(firstindex, tup))
|
n1 = minimum(map(firstindex, tup))
|
||||||
n2 = maximum(map(lastindex, tup))
|
n2 = maximum(map(lastindex, tup))
|
||||||
SegmentsIterator(tup, n1, n2)
|
NaNSegmentsIterator(tup, n1, n2)
|
||||||
end
|
end
|
||||||
|
|
||||||
function iter_segments(series::Series, seriestype::Symbol = :path)
|
function series_segments(series::Series, seriestype::Symbol = :path)
|
||||||
x, y, z = series[:x], series[:y], series[:z]
|
x, y, z = series[:x], series[:y], series[:z]
|
||||||
if x === nothing
|
x === nothing && return UnitRange{Int}[]
|
||||||
return UnitRange{Int}[]
|
|
||||||
elseif has_attribute_segments(series)
|
|
||||||
if any(isnan,y)
|
|
||||||
return [iter_segments(y)...]
|
|
||||||
elseif seriestype in (:scatter, :scatter3d)
|
|
||||||
return [[i] for i in eachindex(y)]
|
|
||||||
else
|
|
||||||
return [i:(i + 1) for i in firstindex(y):lastindex(y)-1]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
segs = UnitRange{Int}[]
|
|
||||||
args = RecipesPipeline.is3d(series) ? (x, y, z) : (x, y)
|
args = RecipesPipeline.is3d(series) ? (x, y, z) : (x, y)
|
||||||
for seg in iter_segments(args...)
|
nan_segments = collect(iter_segments(args...))
|
||||||
push!(segs, seg)
|
|
||||||
|
if has_attribute_segments(series)
|
||||||
|
return Iterators.flatten(map(nan_segments) do r
|
||||||
|
if seriestype in (:scatter, :scatter3d)
|
||||||
|
(SeriesSegment(i:i, i) for i in r)
|
||||||
|
else
|
||||||
|
(SeriesSegment(i:i+1, i) for i in first(r):last(r)-1)
|
||||||
end
|
end
|
||||||
return segs
|
end)
|
||||||
|
else
|
||||||
|
return (SeriesSegment(r, 1) for r in nan_segments)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -97,7 +101,7 @@ anynan(args::Tuple) = i -> anynan(i,args)
|
|||||||
anynan(istart::Int, iend::Int, args::Tuple) = any(anynan(args), istart:iend)
|
anynan(istart::Int, iend::Int, args::Tuple) = any(anynan(args), istart:iend)
|
||||||
allnan(istart::Int, iend::Int, args::Tuple) = all(anynan(args), istart:iend)
|
allnan(istart::Int, iend::Int, args::Tuple) = all(anynan(args), istart:iend)
|
||||||
|
|
||||||
function Base.iterate(itr::SegmentsIterator, nextidx::Int = itr.n1)
|
function Base.iterate(itr::NaNSegmentsIterator, nextidx::Int = itr.n1)
|
||||||
i = findfirst(!anynan(itr.args), nextidx:itr.n2)
|
i = findfirst(!anynan(itr.args), nextidx:itr.n2)
|
||||||
i === nothing && return nothing
|
i === nothing && return nothing
|
||||||
nextval = nextidx + i - 1
|
nextval = nextidx + i - 1
|
||||||
@ -107,6 +111,7 @@ function Base.iterate(itr::SegmentsIterator, nextidx::Int = itr.n1)
|
|||||||
|
|
||||||
nextval:nextnan-1, nextnan
|
nextval:nextnan-1, nextnan
|
||||||
end
|
end
|
||||||
|
Base.IteratorSize(::NaNSegmentsIterator) = Base.SizeUnknown()
|
||||||
|
|
||||||
# Find minimal type that can contain NaN and x
|
# Find minimal type that can contain NaN and x
|
||||||
# To allow use of NaN separated segments with categorical x axis
|
# To allow use of NaN separated segments with categorical x axis
|
||||||
@ -575,13 +580,8 @@ end
|
|||||||
function has_attribute_segments(series::Series)
|
function has_attribute_segments(series::Series)
|
||||||
# we want to check if a series needs to be split into segments just because
|
# we want to check if a series needs to be split into segments just because
|
||||||
# of its attributes
|
# of its attributes
|
||||||
for letter in (:x, :y, :z)
|
|
||||||
# If we have NaNs in the data they define the segments and
|
|
||||||
# SegmentsIterator is used
|
|
||||||
series[letter] !== nothing && NaN in collect(series[letter]) && return false
|
|
||||||
end
|
|
||||||
series[:seriestype] == :shape && return false
|
series[:seriestype] == :shape && return false
|
||||||
# ... else we check relevant attributes if they have multiple inputs
|
# check relevant attributes if they have multiple inputs
|
||||||
return any(
|
return any(
|
||||||
(typeof(series[attr]) <: AbstractVector && length(series[attr]) > 1)
|
(typeof(series[attr]) <: AbstractVector && length(series[attr]) > 1)
|
||||||
for
|
for
|
||||||
|
|||||||
@ -175,14 +175,8 @@ end
|
|||||||
@test_throws ArgumentError gif(anim)
|
@test_throws ArgumentError gif(anim)
|
||||||
end
|
end
|
||||||
|
|
||||||
@testset "Segments" begin
|
@testset "NaN-separated Segments" begin
|
||||||
function segments(args...)
|
segments(args...) = collect(iter_segments(args...))
|
||||||
segs = UnitRange{Int}[]
|
|
||||||
for seg in iter_segments(args...)
|
|
||||||
push!(segs,seg)
|
|
||||||
end
|
|
||||||
segs
|
|
||||||
end
|
|
||||||
|
|
||||||
nan10 = fill(NaN,10)
|
nan10 = fill(NaN,10)
|
||||||
@test segments(11:20) == [1:10]
|
@test segments(11:20) == [1:10]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user