From 678dde710baa358959c2e09bed4352587b954ba9 Mon Sep 17 00:00:00 2001 From: Thomas Breloff Date: Tue, 10 May 2016 13:40:25 -0400 Subject: [PATCH] arrows with updated quiver recipe; only pyplot so far --- src/Plots.jl | 1 + src/args.jl | 6 +++ src/backends/pyplot.jl | 41 +++++++++++++++- src/components.jl | 32 ++++++++++++ src/recipes.jl | 108 ++++++++++++++++++----------------------- src/series_args.jl | 14 ++++-- src/utils.jl | 5 ++ 7 files changed, 139 insertions(+), 68 deletions(-) diff --git a/src/Plots.jl b/src/Plots.jl index e6e5ef31..91fe70bb 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -114,6 +114,7 @@ export brush, Surface, OHLC, + arrow, colorscheme, ColorScheme, diff --git a/src/args.jl b/src/args.jl index 0f63714a..67759915 100644 --- a/src/args.jl +++ b/src/args.jl @@ -150,10 +150,12 @@ _seriesDefaults[:xerror] = nothing _seriesDefaults[:yerror] = nothing _seriesDefaults[:ribbon] = nothing _seriesDefaults[:quiver] = nothing +_seriesDefaults[:arrow] = nothing # allows for adding arrows to line/path... call `arrow(args...)` _seriesDefaults[:normalize] = false # do we want a normalized histogram? _seriesDefaults[:weights] = nothing # optional weights for histograms (1D and 2D) _seriesDefaults[:contours] = false # add contours to 3d surface and wireframe plots _seriesDefaults[:match_dimensions] = false # do rows match x (true) or y (false) for heatmap/image/spy? see issue 196 + # this ONLY effects whether or not the z-matrix is transposed for a heatmap display! const _plotDefaults = KW() @@ -343,6 +345,7 @@ add_aliases(:yerror, :yerr, :yerrorbar, :err, :errorbar) add_aliases(:quiver, :velocity, :quiver2d, :gradient) add_aliases(:normalize, :norm, :normed, :normalized) add_aliases(:aspect_ratio, :aspectratio, :axis_ratio, :axisratio, :ratio) +add_aliases(:match_dimensions, :transpose, :transpose_z) # add all pluralized forms to the _keyAliases dict @@ -464,6 +467,9 @@ function processLineArg(d::KW, arg) arg.color == nothing || (d[:fillcolor] = arg.color == :auto ? :auto : colorscheme(arg.color)) arg.alpha == nothing || (d[:fillalpha] = arg.alpha) + elseif typeof(arg) <: Arrow || arg in (:arrow, :arrows) + d[:arrow] = arg + # linealpha elseif allAlphas(arg) d[:linealpha] = arg diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 0649806d..d682a0e9 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -29,7 +29,7 @@ supportedArgs(::PyPlotBackend) = [ :grid, :legend, :colorbar, :marker_z, :levels, :xerror, :yerror, - :ribbon, :quiver, + :ribbon, :quiver, :arrow, :orientation, :overwrite_figure, :polar, @@ -151,7 +151,7 @@ function buildPyPlotPath(x, y) for i=1:n mat[i,1] = x[i] mat[i,2] = y[i] - nan = !isfinite(x[i]) || !isfinite(y[i]) + nan = !ok(x[i], y[i]) codes[i] = if nan _path_CLOSEPOLY else @@ -415,9 +415,46 @@ function _add_series(pkg::PyPlotBackend, plt::Plot, d::KW) color = pylinecolor(d), linewidth = d[:linewidth], linestyle = getPyPlotLineStyle(lt, d[:linestyle]), + solid_capstyle = "round", + dash_capstyle = "round", drawstyle = getPyPlotStepStyle(lt) )[1] push!(handles, handle) + + if d[:arrow] != nothing + if !is3d(d) # TODO: handle 3d later + n = length(x) + a = d[:arrow] + @assert typeof(a) == Arrow + arrowprops = KW( + # :arrowstyle => (a.style == :open ? "->" : (a.style == :closed ? "-|>" : string(a.style))), + :arrowstyle => "simple,head_length=$(a.headlength),head_width=$(a.headwidth)", + # :arrowstyle => "simple", + :shrinkA => 0, + :shrinkB => 0, + :edgecolor => pylinecolor(d), + :facecolor => pylinecolor(d), + :linewidth => d[:linewidth], + :linestyle => getPyPlotLineStyle(lt, d[:linestyle]), + # :head_length => a.headlength, + # :head_width => a.headwidth, + ) + for i=2:n + xystart = (x[i-1], y[i-1]) + xyend = (x[i], y[i]) + if ok(xystart) && ok(xyend) + if i==n || !ok(x[i+1], y[i+1]) + # add the arrow from xystart to xyend + ax[:annotate]("", + xytext = (0.001xystart[1] + 0.999xyend[1], 0.001xystart[2] + 0.999xyend[2]), + xy = xyend, + arrowprops = arrowprops + ) + end + end + end + end + end end end diff --git a/src/components.jl b/src/components.jl index f406607f..53f0905e 100644 --- a/src/components.jl +++ b/src/components.jl @@ -398,6 +398,38 @@ type OHLC{T<:Real} close::T end +# ----------------------------------------------------------------------- + +# style is :open or :closed (for now) +immutable Arrow + style::Symbol + headlength::Float64 + headwidth::Float64 +end + +function arrow(args...) + style = :simple + headlength = 0.3 + headwidth = 0.2 + for arg in args + T = typeof(arg) + if T == Symbol + style = arg + elseif T <: Number + headlength = headwidth = Float64(arg) + elseif T <: Tuple && length(arg) == 2 + headlength, headwidth = Float64(arg[1]), Float64(arg[2]) + else + warn("Skipped arrow arg $arg") + end + end + Arrow(style, headlength, headwidth) +end + + + + +# ----------------------------------------------------------------------- # @require FixedSizeArrays begin diff --git a/src/recipes.jl b/src/recipes.jl index 6d1f30f8..42d08bbb 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -320,69 +320,46 @@ end # quiver # function apply_series_recipe(d::KW, ::Type{Val{:quiver}}) -# d[:label] = "" -# d[:linetype] = :scatter -# -# # create a second series to draw the arrow shaft -# dpath = copy(d) -# error_style!(dpath) -# dpath[:markershape] = :none -# -# velocity = error_zipit(d[:quiver]) -# xorig, yorig = d[:x], d[:y] -# -# # for each point, we create an arrow of velocity vi, translated to the x/y coordinates -# # x, y = zeros(0), zeros(0) -# paths = P2[] -# arrows = P2[] -# arrowshapes = Shape[] -# for i = 1:max(length(xorig), length(yorig)) -# -# # get the starting position -# xi = get_mod(xorig, i) -# yi = get_mod(yorig, i) -# p = P2(xi, yi) -# -# # get the velocity -# vi = get_mod(velocity, i) -# vx, vy = if istuple(vi) -# first(vi), last(vi) -# elseif isscalar(vi) -# vi, vi -# else -# error("unexpected vi type $(typeof(vi)) for quiver: $vi") -# end -# v = P2(vx, vy) -# -# nanappend!(paths, [p, p+v]) -# push!(arrows, p+v) -# push!(arrowshapes, makearrowhead(compute_angle(v))) -# -# # # dist = sqrt(vx^2 + vy^2) -# # dist = norm(v) -# # arrow_h = 0.1dist # height of arrowhead -# # arrow_w = 0.5arrow_h # halfwidth of arrowhead -# # U1 = v ./ dist # vector of arrowhead height -# # U2 = P2(-U1[2], U1[1]) # vector of arrowhead halfwidth -# # U1 *= arrow_h -# # U2 *= arrow_w -# # -# # append!(pts, P2(xi, yi) .+ P2[(0,0), v-U1, v-U1+U2, v, v-U1-U2, v-U1, (NaN,NaN)]) -# # # a1 = v - arrow_h * U1 + arrow_w * U2 -# # # a2 = v - arrow_h * U1 - arrow_w * U2 -# # # nanappend!(x, xi + [0.0, vx, a1[1], a2[1], vx]) -# # # nanappend!(y, yi + [0.0, vy, a1[2], a2[2], vy]) -# end -# -# # d[:x], d[:y] = Plots.unzip(pts) -# dpath[:x], dpath[:y] = Plots.unzip(paths) -# d[:x], d[:y] = Plots.unzip(arrows) -# d[:markershape] = arrowshapes -# -# KW[dpath, d] -# end +function quiver_using_arrows(d::KW) + d[:label] = "" + d[:linetype] = :path + if !isa(d[:arrow], Arrow) + d[:arrow] = arrow() + end -function apply_series_recipe(d::KW, ::Type{Val{:quiver}}) + velocity = error_zipit(d[:quiver]) + xorig, yorig = d[:x], d[:y] + + # for each point, we create an arrow of velocity vi, translated to the x/y coordinates + x, y = zeros(0), zeros(0) + for i = 1:max(length(xorig), length(yorig)) + # get the starting position + xi = get_mod(xorig, i) + yi = get_mod(yorig, i) + + # get the velocity + vi = get_mod(velocity, i) + vx, vy = if istuple(vi) + first(vi), last(vi) + elseif isscalar(vi) + vi, vi + elseif isa(vi,Function) + vi(xi, yi) + else + error("unexpected vi type $(typeof(vi)) for quiver: $vi") + end + + # add the points + nanappend!(x, [xi, xi+vx, NaN]) + nanappend!(y, [yi, yi+vy, NaN]) + end + + d[:x], d[:y] = x, y + KW[d] +end + +# function apply_series_recipe(d::KW, ::Type{Val{:quiver}}) +function quiver_using_hack(d::KW) d[:label] = "" d[:linetype] = :shape @@ -427,6 +404,13 @@ function apply_series_recipe(d::KW, ::Type{Val{:quiver}}) KW[d] end +function apply_series_recipe(d::KW, ::Type{Val{:quiver}}) + if :arrow in supportedArgs() + quiver_using_arrows(d) + else + quiver_using_hack(d) + end +end # --------------------------------------------------------------------------- diff --git a/src/series_args.jl b/src/series_args.jl index b4ff4138..19c5015f 100644 --- a/src/series_args.jl +++ b/src/series_args.jl @@ -184,12 +184,18 @@ function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter) end # handle quiver plots - if lt == :quiver - d[:linetype] = lt = :path - d[:linewidth] = 0 - end + # either a series of velocity vectors are passed in (`:quiver` keyword), + # or we just add arrows to the path + + # if lt == :quiver + # d[:linetype] = lt = :path + # d[:linewidth] = 0 + # end if get(d, :quiver, nothing) != nothing append!(ret, apply_series_recipe(copy(d), Val{:quiver})) + elseif lt == :quiver + d[:linetype] = lt = :path + d[:arrow] = arrow() end # now that we've processed a given series... optionally split into diff --git a/src/utils.jl b/src/utils.jl index e7e61dd2..31644622 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -291,6 +291,11 @@ function transpose_z(d::KW, z, transpose_on_match::Bool = true) end end +function ok(x::Number, y::Number, z::Number = 0) + isfinite(x) && isfinite(y) && isfinite(z) +end +ok(tup::Tuple) = ok(tup...) + # ---------------------------------------------------------------