From 38804898c534e38679f40c336b5de5877e33db47 Mon Sep 17 00:00:00 2001 From: Thomas Breloff Date: Wed, 15 Jun 2016 12:52:36 -0400 Subject: [PATCH] iter_segments and curve series type --- src/Plots.jl | 2 ++ src/args.jl | 14 +++++++++ src/backends/pyplot.jl | 2 ++ src/plot.jl | 5 ++++ src/recipes.jl | 67 ++++++++++++++++++++++++++++++++++++++++++ src/series_new.jl | 24 +++++++-------- src/utils.jl | 51 ++++++++++++++++++++++++++++++++ 7 files changed, 153 insertions(+), 12 deletions(-) diff --git a/src/Plots.jl b/src/Plots.jl index 163ca115..df048c69 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -109,6 +109,7 @@ export chorddiagram, test_examples, + iter_segments, translate, translate!, @@ -183,6 +184,7 @@ end @shorthands boxplot @shorthands violin @shorthands quiver +@shorthands curves pie(args...; kw...) = plot(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing) pie!(args...; kw...) = plot!(args...; kw..., seriestype = :pie, aspect_ratio = :equal, grid=false, xticks=nothing, yticks=nothing) diff --git a/src/args.jl b/src/args.jl index bbbda4f3..4b88a5f9 100644 --- a/src/args.jl +++ b/src/args.jl @@ -11,6 +11,16 @@ function add_aliases(sym::Symbol, aliases::Symbol...) end end +function add_non_underscore_aliases!(aliases::KW) + for (k,v) in aliases + s = string(k) + if '_' in s + aliases[Symbol(replace(s, "_", ""))] = v + end + end +end + + # ------------------------------------------------------------ const _allAxes = [:auto, :left, :right] @@ -61,8 +71,12 @@ const _allTypes = vcat([ :imagesc => :image, :hist => :histogram, :hist2d => :histogram2d, + :bezier => :curves, + :bezier_curves => :curves, ) +add_non_underscore_aliases!(_typeAliases) + like_histogram(seriestype::Symbol) = seriestype in (:histogram, :density) like_line(seriestype::Symbol) = seriestype in (:line, :path, :steppre, :steppost) like_surface(seriestype::Symbol) = seriestype in (:contour, :contour3d, :heatmap, :surface, :wireframe, :image) diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 152a58a0..92831fca 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -444,6 +444,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) lc = pyart3d.Line3DCollection(segments; kw...) lc[:set_array](d[:line_z]) ax[:add_collection3d](lc, zs=z) #, zdir='y') + lc else for i=1:n segments[i] = [(cycle(x,i), cycle(y,i)), (cycle(x,i+1), cycle(y,i+1))] @@ -451,6 +452,7 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series) lc = pycollections.LineCollection(segments; kw...) lc[:set_array](d[:line_z]) ax[:add_collection](lc) + lc end push!(handles, handle) needs_colorbar = true diff --git a/src/plot.jl b/src/plot.jl index f1e82a0b..d5cf7b6b 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -172,6 +172,11 @@ function _apply_series_recipe(plt::Plot, d::KW) sp_idx = get_subplot_index(plt, sp) _update_subplot_args(plt, sp, d, sp_idx) + # do we want to override the series type? + if !is3d(st) && d[:z] != nothing && (size(d[:x]) == size(d[:y]) == size(d[:z])) + st = d[:seriestype] = (st == :scatter ? :scatter3d : :path3d) + end + # change to a 3d projection for this subplot? if is3d(st) sp.attr[:projection] = "3d" diff --git a/src/recipes.jl b/src/recipes.jl index 807a3e94..54efd526 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -338,6 +338,73 @@ end @deps sticks path scatter +# --------------------------------------------------------------------------- +# bezier curves + +# get the value of the curve point at position t +function bezier_value(pts::AVec, t::Real) + val = 0.0 + n = length(pts)-1 + for (i,p) in enumerate(pts) + val += p * binomial(n, i-1) * (1-t)^(n-i+1) * t^(i-1) + end + val +end + +# create segmented bezier curves in place of line segments +@recipe function f(::Type{Val{:curves}}, x, y, z) + args = z != nothing ? (x,y,z) : (x,y) + newx, newy = zeros(0), zeros(0) + fr = d[:fillrange] + newfr = fr != nothing ? zeros(0) : nothing + newz = z != nothing ? zeros(0) : nothing + lz = d[:line_z] + newlz = lz != nothing ? zeros(0) : nothing + + # for each line segment (point series with no NaNs), convert it into a bezier curve + # where the points are the control points of the curve + for rng in iter_segments(args...) + length(rng) < 2 && continue + ts = linspace(0, 1, pop!(d, :npoints, 20)) + nanappend!(newx, map(t -> bezier_value(cycle(x,rng), t), ts)) + nanappend!(newy, map(t -> bezier_value(cycle(y,rng), t), ts)) + if z != nothing + nanappend!(newz, map(t -> bezier_value(cycle(z,rng), t), ts)) + end + if fr != nothing + nanappend!(newfr, map(t -> bezier_value(cycle(fr,rng), t), ts)) + end + if lz != nothing + lzrng = cycle(lz, rng) # the line_z's for this segment + # @show lzrng, sizeof(lzrng) map(t -> 1+floor(Int, t * (length(rng)-1)), ts) + # choose the line_z value of the control point just before this t + push!(newlz, 0.0) + append!(newlz, map(t -> lzrng[1+floor(Int, t * (length(rng)-1))], ts)) + # lzrng = vcat() + # nanappend!(newlz, #map(t -> bezier_value(cycle(lz,rng), t), ts)) + end + end + + x := newx + y := newy + if z == nothing + seriestype := :path + else + seriestype := :path3d + z := newz + end + if fr != nothing + fillrange := newfr + end + if lz != nothing + line_z := newlz + linecolor := (isa(d[:linecolor], ColorGradient) ? d[:linecolor] : default_gradient()) + end + # Plots.DD(d) + () +end +@deps curves path + # --------------------------------------------------------------------------- # create a bar plot as a filled step function diff --git a/src/series_new.jl b/src/series_new.jl index c4cddc0c..f145d33e 100644 --- a/src/series_new.jl +++ b/src/series_new.jl @@ -292,22 +292,22 @@ end # # 3d line or scatter @recipe function f(x::AVec, y::AVec, z::AVec) - st = get(d, :seriestype, :none) - if st == :scatter - d[:seriestype] = :scatter3d - elseif !is3d(st) - d[:seriestype] = :path3d - end + # st = get(d, :seriestype, :none) + # if st == :scatter + # d[:seriestype] = :scatter3d + # elseif !is3d(st) + # d[:seriestype] = :path3d + # end SliceIt, x, y, z end @recipe function f(x::AMat, y::AMat, z::AMat) - st = get(d, :seriestype, :none) - if size(x) == size(y) == size(z) - if !is3d(st) - seriestype := :path3d - end - end + # st = get(d, :seriestype, :none) + # if size(x) == size(y) == size(z) + # if !is3d(st) + # seriestype := :path3d + # end + # end SliceIt, x, y, z end diff --git a/src/utils.jl b/src/utils.jl index b9dbf5cd..1fdab38f 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -136,6 +136,53 @@ function imageHack(d::KW) d[:z], d[:fillcolor] = replace_image_with_heatmap(d[:z].surf) end # --------------------------------------------------------------- + +# ----------------------------------------------------- +# helper to manage NaN-separated segments + +type SegmentsIterator + args::Tuple + nextidx::Int + n::Int +end +function iter_segments(args...) + tup = Plots.wraptuple(args) + n = maximum(map(length, tup)) + SegmentsIterator(tup, 0, n) +end + +# helpers to figure out if there are NaN values in a list of array types +anynan(i::Int, args...) = any(a -> isnan(cycle(a,i)), args) +anynan(istart::Int, iend::Int, args...) = any(i -> anynan(i, args...), istart:iend) +allnan(istart::Int, iend::Int, args...) = all(i -> anynan(i, args...), istart:iend) + +Base.start(itr::SegmentsIterator) = (itr.nextidx = 1) #resets +Base.done(itr::SegmentsIterator, unused::Int) = itr.nextidx > itr.n +function Base.next(itr::SegmentsIterator, unused::Int) + i = istart = iend = itr.nextidx + + # find the next NaN, and iend is the one before + while i <= itr.n + 1 + if i > itr.n || anynan(i, itr.args...) + # done... array end or found NaN + iend = i-1 + break + end + i += 1 + end + + # find the next non-NaN, and set itr.nextidx + while i <= itr.n + if !anynan(i, itr.args...) + break + end + i += 1 + end + + itr.nextidx = i + istart:iend, 0 +end + # ------------------------------------------------------------------------------------ @@ -146,6 +193,10 @@ Base.cycle(v::AVec, idx::Int) = v[mod1(idx, length(v))] Base.cycle(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))] Base.cycle(v, idx::Int) = v +Base.cycle(v::AVec, indices::AVec{Int}) = map(i -> cycle(v,i), indices) +Base.cycle(v::AMat, indices::AVec{Int}) = map(i -> cycle(v,i), indices) +Base.cycle(v, idx::AVec{Int}) = v + makevec(v::AVec) = v makevec{T}(v::T) = T[v]