boxplot, violin, quiver, and line series recipes; plus some fixes

This commit is contained in:
Thomas Breloff 2016-05-13 11:21:32 -04:00
parent 26779cbf4a
commit 8ec3d18339
7 changed files with 86 additions and 44 deletions

View File

@ -222,8 +222,8 @@ plot3d(args...; kw...) = plot(args...; kw..., linetype = :path3d)
plot3d!(args...; kw...) = plot!(args...; kw..., linetype = :path3d)
scatter3d(args...; kw...) = plot(args...; kw..., linetype = :scatter3d)
scatter3d!(args...; kw...) = plot!(args...; kw..., linetype = :scatter3d)
boxplot(args...; kw...) = plot(args...; kw..., linetype = :box)
boxplot!(args...; kw...) = plot!(args...; kw..., linetype = :box)
boxplot(args...; kw...) = plot(args...; kw..., linetype = :boxplot)
boxplot!(args...; kw...) = plot!(args...; kw..., linetype = :boxplot)
violin(args...; kw...) = plot(args...; kw..., linetype = :violin)
violin!(args...; kw...) = plot!(args...; kw..., linetype = :violin)
quiver(args...; kw...) = plot(args...; kw..., linetype = :quiver)

View File

@ -11,7 +11,7 @@ const _3dTypes = [:path3d, :scatter3d, :surface, :wireframe, :contour3d]
const _allTypes = vcat([
:none, :line, :path, :steppre, :steppost, :sticks, :scatter,
:heatmap, :hexbin, :hist, :hist2d, :hist3d, :density, :bar, :hline, :vline, :ohlc,
:contour, :pie, :shape, :box, :violin, :quiver, :image
:contour, :pie, :shape, :image #, :boxplot, :violin, :quiver,
], _3dTypes)
@compat const _typeAliases = KW(
:n => :none,
@ -38,7 +38,7 @@ const _allTypes = vcat([
:shapes => :shape,
:poly => :shape,
:polygon => :shape,
:boxplot => :box,
:box => :boxplot,
:velocity => :quiver,
:gradient => :quiver,
:img => :image,

View File

@ -28,7 +28,7 @@ supportedAxes(::GadflyBackend) = [:auto, :left]
supportedTypes(::GadflyBackend) = [
:none, :line, :path, :steppre, :steppost, :sticks,
:scatter, :hist2d, :hexbin, :hist,
:bar, :box, :violin, :quiver,
:bar, #:box, :violin, :quiver,
:hline, :vline, :contour, :shape
]
supportedStyles(::GadflyBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]

View File

@ -40,7 +40,7 @@ supportedAxes(::PyPlotBackend) = _allAxes
supportedTypes(::PyPlotBackend) = [
:none, :line, :path, :steppre, :steppost, :shape,
:scatter, :hist2d, :hexbin, :hist, :density,
:bar, :sticks, :box, :violin, :quiver,
:bar, :sticks, #:box, :violin, :quiver,
:hline, :vline, :heatmap, :pie, :image,
:contour, :contour3d, :path3d, :scatter3d, :surface, :wireframe
]

View File

@ -86,12 +86,15 @@ end
# natively by the backend
function _apply_series_recipe(plt::Plot, d::KW)
lt = d[:linetype]
# dumpdict(d, "apply_series_recipe", true)
if lt in supportedTypes()
# println("adding series!!")
warnOnUnsupported(plt.backend, d)
_add_series(plt.backend, plt, d)
else
# get a sub list of series for this linetype
try
series_list = RecipesBase.apply_recipe(Val{lt}, d[:x], d[:y], d[:z])
series_list = try
RecipesBase.apply_recipe(d, Val{lt}, d[:x], d[:y], d[:z])
catch
warn("Exception during apply_recipe(Val{$lt}, ...) with types ($(typeof(d[:x])), $(typeof(d[:y])), $(typeof(d[:z])))")
rethrow()
@ -142,12 +145,25 @@ function _plot!(plt::Plot, d::KW, args...)
next_series = pop!(still_to_process)
series_list = RecipesBase.apply_recipe(next_series.d, next_series.args...)
for series in series_list
# @show series
if isempty(series.args)
# finish processing and add to the kw_list
_add_markershape(series.d)
_filter_input_data!(series.d)
warnOnUnsupportedArgs(plt.backend, series.d)
warnOnUnsupportedScales(plt.backend, series.d)
# TODO:
# # map functions to vectors
# if isa(d[:marker_z], Function)
# d[:marker_z] = map(d[:marker_z], d[:x])
# end
# # handle ribbons
# if get(d, :ribbon, nothing) != nothing
# rib = d[:ribbon]
# d[:fillrange] = (d[:y] - rib, d[:y] + rib)
# end
push!(kw_list, series.d)
else
push!(still_to_process, series)
@ -229,7 +245,7 @@ function _plot!(plt::Plot, d::KW, args...)
# todo: while the linetype is not supported, try to apply a recipe of f(Val{lt}, x,y,z) recursively
# the default should throw an error because we can't handle that linetype
_apply_series_recipe(kw)
_apply_series_recipe(plt, kw)
# _add_series(plt.backend, plt, kw)

View File

@ -204,28 +204,42 @@ end
# ---------------------------------------------------------------------------
"""
`apply_series_recipe` should take a processed series KW dict and break it up
into component parts. For example, a box plot is made up of `shape` for the
boxes, `path` for the lines, and `scatter` for the outliers.
# """
# `apply_series_recipe` should take a processed series KW dict and break it up
# into component parts. For example, a box plot is made up of `shape` for the
# boxes, `path` for the lines, and `scatter` for the outliers.
#
# Returns a Vector{KW}.
# """
# apply_series_recipe(d::KW, lt) = KW[d]
Returns a Vector{KW}.
"""
apply_series_recipe(d::KW, lt) = KW[d]
# for linetype `line`, need to sort by x values
@recipe function f(::Type{Val{:line}}, x, y, z)
# order by x
indices = sortperm(d[:x])
d[:x] = d[:x][indices]
d[:y] = d[:y][indices]
if typeof(d[:z]) <: AVec
d[:z] = d[:z][indices]
end
d[:linetype] = :path
()
end
# ---------------------------------------------------------------------------
# Box Plot
const _box_halfwidth = 0.4
function apply_series_recipe(d::KW, ::Type{Val{:box}})
# function apply_series_recipe(d::KW, ::Type{Val{:box}})
@recipe function f(::Type{Val{:boxplot}}, x, y, z)
# dumpdict(d, "box before", true)
# TODO: add scatter series with outliers
# create a list of shapes, where each shape is a single boxplot
shapes = Shape[]
d[:linetype] = :shape
groupby = extractGroupArgs(d[:x])
groupby = extractGroupArgs(x)
for (i, glabel) in enumerate(groupby.groupLabels)
@ -249,10 +263,16 @@ function apply_series_recipe(d::KW, ::Type{Val{:box}})
push!(shapes, Shape(xcoords, ycoords))
end
d[:x], d[:y] = shape_coords(shapes)
d[:plotarg_overrides] = KW(:xticks => (1:length(shapes), groupby.groupLabels))
# d[:plotarg_overrides] = KW(:xticks => (1:length(shapes), groupby.groupLabels))
KW[d]
d[:linetype] = :shape
xticklabels --> groupby.groupLabels
# we want to set the fields directly inside series recipes... args are ignored
d[:x], d[:y] = shape_coords(shapes)
() # expects a tuple returned
# KW[d]
end
# ---------------------------------------------------------------------------
@ -260,17 +280,13 @@ end
# if the user has KernelDensity installed, use this for violin plots.
# otherwise, just use a histogram
try
Pkg.installed("KernelDensity")
import KernelDensity
# warn("using KD for violin")
if is_installed("KernelDensity")
@eval import KernelDensity
@eval function violin_coords(y)
kd = KernelDensity.kde(y, npoints = 30)
kd.density, kd.x
end
catch
# warn("using hist for violin")
else
@eval function violin_coords(y)
edges, widths = hist(y, 20)
centers = 0.5 * (edges[1:end-1] + edges[2:end])
@ -280,13 +296,13 @@ catch
end
function apply_series_recipe(d::KW, ::Type{Val{:violin}})
# function apply_series_recipe(d::KW, ::Type{Val{:violin}})
@recipe function f(::Type{Val{:violin}}, x, y, z)
# dumpdict(d, "box before", true)
# TODO: add scatter series with outliers
# create a list of shapes, where each shape is a single boxplot
shapes = Shape[]
d[:linetype] = :shape
groupby = extractGroupArgs(d[:x])
for (i, glabel) in enumerate(groupby.groupLabels)
@ -304,10 +320,14 @@ function apply_series_recipe(d::KW, ::Type{Val{:violin}})
push!(shapes, Shape(xcoords, ycoords))
end
d[:x], d[:y] = shape_coords(shapes)
d[:plotarg_overrides] = KW(:xticks => (1:length(shapes), groupby.groupLabels))
# d[:plotarg_overrides] = KW(:xticks => (1:length(shapes), groupby.groupLabels))
d[:linetype] = :shape
xticklabels --> groupby.groupLabels
KW[d]
d[:x], d[:y] = shape_coords(shapes)
()
# KW[d]
end
# ---------------------------------------------------------------------------
@ -353,18 +373,22 @@ end
# we will create a series of path segments, where each point represents one
# side of an errorbar
function apply_series_recipe(d::KW, ::Type{Val{:yerror}})
# function apply_series_recipe(d::KW, ::Type{Val{:yerror}})
@recipe function f(::Type{Val{:yerror}}, x, y, z)
error_style!(d)
d[:markershape] = :hline
d[:x], d[:y] = error_coords(d[:x], d[:y], error_zipit(d[:yerror]))
KW[d]
# KW[d]
()
end
function apply_series_recipe(d::KW, ::Type{Val{:xerror}})
# function apply_series_recipe(d::KW, ::Type{Val{:xerror}})
@recipe function f(::Type{Val{:xerror}}, x, y, z)
error_style!(d)
d[:markershape] = :vline
d[:y], d[:x] = error_coords(d[:y], d[:x], error_zipit(d[:xerror]))
KW[d]
# KW[d]
()
end
@ -407,7 +431,7 @@ function quiver_using_arrows(d::KW)
end
d[:x], d[:y] = x, y
KW[d]
# KW[d]
end
# function apply_series_recipe(d::KW, ::Type{Val{:quiver}})
@ -453,15 +477,17 @@ function quiver_using_hack(d::KW)
end
d[:x], d[:y] = Plots.unzip(pts[2:end])
KW[d]
# KW[d]
end
function apply_series_recipe(d::KW, ::Type{Val{:quiver}})
# function apply_series_recipe(d::KW, ::Type{Val{:quiver}})
@recipe function f(::Type{Val{:quiver}}, x, y, z)
if :arrow in supportedArgs()
quiver_using_arrows(d)
else
quiver_using_hack(d)
end
()
end

View File

@ -77,7 +77,7 @@ function _add_defaults!(d::KW, plt::Plot, commandIndex::Int)
end
d[:label] = label
warnOnUnsupported(pkg, d)
# warnOnUnsupported(pkg, d)
d
end
@ -329,8 +329,8 @@ end
# end
@recipe function f{X,Y}(x::AVec{X}, y::AVec{Y}, zf::Function)
x = TX <: Number ? sort(x) : x
y = TY <: Number ? sort(y) : y
x = X <: Number ? sort(x) : x
y = Y <: Number ? sort(y) : y
SliceIt, x, y, Surface(zf, x, y) # TODO: replace with SurfaceFunction when supported
end