added quiver plot/arg; fixed gadfly shapes to allow vector of shapes

This commit is contained in:
Thomas Breloff 2016-04-08 16:05:20 -04:00
parent 9bfcb64542
commit 2fc973245d
9 changed files with 185 additions and 17 deletions

View File

@ -70,6 +70,8 @@ export
boxplot!, boxplot!,
violin, violin,
violin!, violin!,
quiver,
quiver!,
title!, title!,
xlabel!, xlabel!,
@ -196,6 +198,8 @@ boxplot(args...; kw...) = plot(args...; kw..., linetype = :box)
boxplot!(args...; kw...) = plot!(args...; kw..., linetype = :box) boxplot!(args...; kw...) = plot!(args...; kw..., linetype = :box)
violin(args...; kw...) = plot(args...; kw..., linetype = :violin) violin(args...; kw...) = plot(args...; kw..., linetype = :violin)
violin!(args...; kw...) = plot!(args...; kw..., linetype = :violin) violin!(args...; kw...) = plot!(args...; kw..., linetype = :violin)
quiver(args...; kw...) = plot(args...; kw..., linetype = :quiver)
quiver!(args...; kw...) = plot!(args...; kw..., linetype = :quiver)
title!(s::AbstractString; kw...) = plot!(; title = s, kw...) title!(s::AbstractString; kw...) = plot!(; title = s, kw...)

View File

@ -11,7 +11,7 @@ const _3dTypes = [:path3d, :scatter3d, :surface, :wireframe]
const _allTypes = vcat([ const _allTypes = vcat([
:none, :line, :path, :steppre, :steppost, :sticks, :scatter, :none, :line, :path, :steppre, :steppost, :sticks, :scatter,
:heatmap, :hexbin, :hist, :hist2d, :hist3d, :density, :bar, :hline, :vline, :ohlc, :heatmap, :hexbin, :hist, :hist2d, :hist3d, :density, :bar, :hline, :vline, :ohlc,
:contour, :pie, :shape, :box, :violin :contour, :pie, :shape, :box, :violin, :quiver
], _3dTypes) ], _3dTypes)
@compat const _typeAliases = KW( @compat const _typeAliases = KW(
:n => :none, :n => :none,
@ -39,6 +39,8 @@ const _allTypes = vcat([
:poly => :shape, :poly => :shape,
:polygon => :shape, :polygon => :shape,
:boxplot => :box, :boxplot => :box,
:velocity => :quiver,
:gradient => :quiver,
) )
like_histogram(linetype::Symbol) = linetype in (:hist, :density) like_histogram(linetype::Symbol) = linetype in (:hist, :density)
@ -148,6 +150,7 @@ _seriesDefaults[:orientation] = :vertical
_seriesDefaults[:xerror] = nothing _seriesDefaults[:xerror] = nothing
_seriesDefaults[:yerror] = nothing _seriesDefaults[:yerror] = nothing
_seriesDefaults[:ribbon] = nothing _seriesDefaults[:ribbon] = nothing
_seriesDefaults[:quiver] = nothing
const _plotDefaults = KW() const _plotDefaults = KW()
@ -333,6 +336,9 @@ end
:xerrorbar => :xerror, :xerrorbar => :xerror,
:yerr => :yerror, :yerr => :yerror,
:yerrorbar => :yerror, :yerrorbar => :yerror,
:velocity => :quiver,
:quiver2d => :quiver,
:gradient => :quiver,
) )
# add all pluralized forms to the _keyAliases dict # add all pluralized forms to the _keyAliases dict

View File

@ -132,10 +132,16 @@ end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
get_shape(sym::Symbol) = _shapes[sym]
get_shape(shape::Shape) = shape
# extract the underlying ShapeGeometry object(s) # extract the underlying ShapeGeometry object(s)
getMarkerGeom(shape::Shape) = gadflyshape(shape) getMarkerGeom(shapes::AVec) = gadflyshape(map(get_shape, shapes))
getMarkerGeom(shape::Symbol) = gadflyshape(_shapes[shape]) getMarkerGeom(other) = gadflyshape(get_shape(other))
getMarkerGeom(shapes::AVec) = map(getMarkerGeom, shapes)
# getMarkerGeom(shape::Shape) = gadflyshape(shape)
# getMarkerGeom(shape::Symbol) = gadflyshape(_shapes[shape])
# getMarkerGeom(shapes::AVec) = gadflyshape(map(gadflyshape, shapes)) # map(getMarkerGeom, shapes)
function getMarkerGeom(d::KW) function getMarkerGeom(d::KW)
if d[:linetype] == :shape if d[:linetype] == :shape
Gadfly.Geom.polygon(fill = true, preserve_order = true) Gadfly.Geom.polygon(fill = true, preserve_order = true)

View File

@ -1,8 +1,9 @@
# Geometry which displays arbitrary shapes at given (x, y) positions. # Geometry which displays arbitrary shapes at given (x, y) positions.
# note: vertices is a list of shapes
immutable ShapeGeometry <: Gadfly.GeometryElement immutable ShapeGeometry <: Gadfly.GeometryElement
vertices::AbstractVector{@compat(Tuple{Float64,Float64})} vertices::AbstractVector #{Tuple{Float64,Float64}}
tag::Symbol tag::Symbol
function ShapeGeometry(shape; tag::Symbol=Gadfly.Geom.empty_tag) function ShapeGeometry(shape; tag::Symbol=Gadfly.Geom.empty_tag)
@ -66,24 +67,27 @@ function Gadfly.render(geom::ShapeGeometry, theme::Gadfly.Theme, aes::Gadfly.Aes
end end
function gadflyshape(sv::Shape) function gadflyshape(sv::Shape)
ShapeGeometry(sv.vertices) ShapeGeometry(Any[sv.vertices])
end
function gadflyshape(sv::AVec{Shape})
ShapeGeometry(Any[s.vertices for s in sv])
end end
# create a Compose context given a ShapeGeometry and the xs/ys/sizes # create a Compose context given a ShapeGeometry and the xs/ys/sizes
function make_polygon(geom::ShapeGeometry, xs::AbstractArray, ys::AbstractArray, rs::AbstractArray) function make_polygon(geom::ShapeGeometry, xs::AbstractArray, ys::AbstractArray, rs::AbstractArray)
n = max(length(xs), length(ys), length(rs)) n = max(length(xs), length(ys), length(rs))
T = @compat(Tuple{Compose.Measure, Compose.Measure}) T = Tuple{Compose.Measure, Compose.Measure}
polys = Array(Vector{T}, n) polys = Array(Vector{T}, n)
for i in 1:n for i in 1:n
x = Compose.x_measure(xs[mod1(i, length(xs))]) x = Compose.x_measure(xs[mod1(i, length(xs))])
y = Compose.y_measure(ys[mod1(i, length(ys))]) y = Compose.y_measure(ys[mod1(i, length(ys))])
r = rs[mod1(i, length(rs))] r = rs[mod1(i, length(rs))]
polys[i] = T[(x + r * sx, y + r * sy) for (sx,sy) in geom.vertices] polys[i] = T[(x + r * sx, y + r * sy) for (sx,sy) in get_mod(geom.vertices, i)]
end end
Gadfly.polygon(polys, geom.tag) Gadfly.polygon(polys, geom.tag)
end end
# --------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------

View File

@ -80,11 +80,12 @@ supportedArgs(::GadflyBackend) = [
:xerror, :xerror,
:yerror, :yerror,
:ribbon, :ribbon,
:quiver,
:orientation, :orientation,
] ]
supportedAxes(::GadflyBackend) = [:auto, :left] supportedAxes(::GadflyBackend) = [:auto, :left]
supportedTypes(::GadflyBackend) = [:none, :line, :path, :steppre, :steppost, :sticks, supportedTypes(::GadflyBackend) = [:none, :line, :path, :steppre, :steppost, :sticks,
:scatter, :hist2d, :hexbin, :hist, :bar, :box, :violin, :scatter, :hist2d, :hexbin, :hist, :bar, :box, :violin, :quiver,
:hline, :vline, :contour, :shape] :hline, :vline, :contour, :shape]
supportedStyles(::GadflyBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot] supportedStyles(::GadflyBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
supportedMarkers(::GadflyBackend) = vcat(_allMarkers, Shape) supportedMarkers(::GadflyBackend) = vcat(_allMarkers, Shape)
@ -171,11 +172,12 @@ supportedArgs(::PyPlotBackend) = [
:xerror, :xerror,
:yerror, :yerror,
:ribbon, :ribbon,
:quiver,
:orientation, :orientation,
] ]
supportedAxes(::PyPlotBackend) = _allAxes supportedAxes(::PyPlotBackend) = _allAxes
supportedTypes(::PyPlotBackend) = [:none, :line, :path, :steppre, :steppost, #:sticks, supportedTypes(::PyPlotBackend) = [:none, :line, :path, :steppre, :steppost, #:sticks,
:scatter, :hist2d, :hexbin, :hist, :density, :bar, :box, :violin, :scatter, :hist2d, :hexbin, :hist, :density, :bar, :box, :violin, :quiver,
:hline, :vline, :contour, :path3d, :scatter3d, :surface, :wireframe, :heatmap] :hline, :vline, :contour, :path3d, :scatter3d, :surface, :wireframe, :heatmap]
supportedStyles(::PyPlotBackend) = [:auto, :solid, :dash, :dot, :dashdot] supportedStyles(::PyPlotBackend) = [:auto, :solid, :dash, :dot, :dashdot]
# supportedMarkers(::PyPlotBackend) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :hexagon] # supportedMarkers(::PyPlotBackend) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :hexagon]
@ -249,6 +251,7 @@ supportedArgs(::GRBackend) = [
:xerror, :xerror,
:yerror, :yerror,
:ribbon, :ribbon,
:quiver,
:orientation, :orientation,
] ]
supportedAxes(::GRBackend) = _allAxes supportedAxes(::GRBackend) = _allAxes
@ -575,6 +578,7 @@ supportedArgs(::PlotlyBackend) = [
:xerror, :xerror,
:yerror, :yerror,
:ribbon, :ribbon,
:quiver,
:orientation, :orientation,
] ]
supportedAxes(::PlotlyBackend) = [:auto, :left] supportedAxes(::PlotlyBackend) = [:auto, :left]
@ -649,6 +653,7 @@ supportedArgs(::PlotlyJSBackend) = [
:xerror, :xerror,
:yerror, :yerror,
:ribbon, :ribbon,
:quiver,
:orientation, :orientation,
] ]
supportedAxes(::PlotlyJSBackend) = [:auto, :left] supportedAxes(::PlotlyJSBackend) = [:auto, :left]

View File

@ -9,6 +9,13 @@ export
typealias P2 FixedSizeArrays.Vec{2,Float64} typealias P2 FixedSizeArrays.Vec{2,Float64}
typealias P3 FixedSizeArrays.Vec{3,Float64} typealias P3 FixedSizeArrays.Vec{3,Float64}
nanpush!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN,NaN)); push!(a, b))
nanappend!(a::AbstractVector{P2}, b) = (push!(a, P2(NaN,NaN)); append!(a, b))
nanpush!(a::AbstractVector{P3}, b) = (push!(a, P3(NaN,NaN,NaN)); push!(a, b))
nanappend!(a::AbstractVector{P3}, b) = (push!(a, P3(NaN,NaN,NaN)); append!(a, b))
compute_angle(v::P2) = (angle = atan2(v[2], v[1]); angle < 0 ? 2π - angle : angle)
# -------------------------------------------------------------
immutable Shape immutable Shape
vertices::AVec vertices::AVec
@ -98,6 +105,14 @@ function makecross(; offset = -0.5, radius = 1.0)
end end
from_polar(angle, dist) = P2(dist*cos(angle), dist*sin(angle))
function makearrowhead(angle; h = 2.0, w = 0.4)
tip = from_polar(angle, h)
Shape(P2[(0,0), from_polar(angle - 0.5π, w) - tip,
from_polar(angle + 0.5π, w) - tip, (0,0)])
end
const _shapes = KW( const _shapes = KW(
:ellipse => makeshape(20), :ellipse => makeshape(20),
:rect => makeshape(4, offset=-0.25), :rect => makeshape(4, offset=-0.25),

View File

@ -147,12 +147,18 @@ function error_style!(d::KW)
d[:label] = "" d[:label] = ""
end end
# if we're passed a tuple of vectors, convert to a vector of tuples
function error_zipit(ebar)
if istuple(ebar)
collect(zip(ebar...))
else
ebar
end
end
function error_coords(xorig, yorig, ebar) function error_coords(xorig, yorig, ebar)
# init empty x/y, and zip errors if passed Tuple{Vector,Vector} # init empty x/y, and zip errors if passed Tuple{Vector,Vector}
x, y = zeros(0), zeros(0) x, y = zeros(0), zeros(0)
if istuple(ebar)
ebar = collect(zip(ebar...))
end
# for each point, create a line segment from the bottom to the top of the errorbar # for each point, create a line segment from the bottom to the top of the errorbar
for i = 1:max(length(xorig), length(yorig)) for i = 1:max(length(xorig), length(yorig))
@ -177,19 +183,131 @@ end
function apply_series_recipe(d::KW, ::Type{Val{:yerror}}) function apply_series_recipe(d::KW, ::Type{Val{:yerror}})
error_style!(d) error_style!(d)
d[:markershape] = :hline d[:markershape] = :hline
d[:x], d[:y] = error_coords(d[:x], d[:y], d[:yerror]) d[:x], d[:y] = error_coords(d[:x], d[:y], error_zipit(d[:yerror]))
KW[d] KW[d]
end end
function apply_series_recipe(d::KW, ::Type{Val{:xerror}}) function apply_series_recipe(d::KW, ::Type{Val{:xerror}})
error_style!(d) error_style!(d)
d[:markershape] = :vline d[:markershape] = :vline
d[:y], d[:x] = error_coords(d[:y], d[:x], d[:xerror]) d[:y], d[:x] = error_coords(d[:y], d[:x], error_zipit(d[:xerror]))
KW[d] KW[d]
end 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 apply_series_recipe(d::KW, ::Type{Val{:quiver}})
d[:label] = ""
d[:linetype] = :shape
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
pts = P2[]
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
elseif isa(vi,Function)
vi(xi, yi)
else
error("unexpected vi type $(typeof(vi)) for quiver: $vi")
end
v = P2(vx, vy)
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
ppv = p+v
nanappend!(pts, P2[p, ppv-U1, ppv-U1+U2, ppv, ppv-U1-U2, ppv-U1])
end
d[:x], d[:y] = Plots.unzip(pts)
KW[d]
end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------

View File

@ -161,11 +161,20 @@ function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter)
# handle ribbons # handle ribbons
if get(d, :ribbon, nothing) != nothing if get(d, :ribbon, nothing) != nothing
# append!(ret, apply_series_recipe(copy(d), Val{:ribbon}))
rib = d[:ribbon] rib = d[:ribbon]
d[:fillrange] = (d[:y] - rib, d[:y] + rib) d[:fillrange] = (d[:y] - rib, d[:y] + rib)
end end
# handle quiver plots
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}))
end
# now that we've processed a given series... optionally split into # now that we've processed a given series... optionally split into
# multiple dicts through a recipe (for example, a box plot is split into component # multiple dicts through a recipe (for example, a box plot is split into component

View File

@ -240,6 +240,7 @@ Base.merge(a::AbstractVector, b::AbstractVector) = sort(unique(vcat(a,b)))
nanpush!(a::AbstractVector, b) = (push!(a, NaN); push!(a, b)) nanpush!(a::AbstractVector, b) = (push!(a, NaN); push!(a, b))
nanappend!(a::AbstractVector, b) = (push!(a, NaN); append!(a, b)) nanappend!(a::AbstractVector, b) = (push!(a, NaN); append!(a, b))
# --------------------------------------------------------------- # ---------------------------------------------------------------
wraptuple(x::@compat(Tuple)) = x wraptuple(x::@compat(Tuple)) = x