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) plot3d!(args...; kw...) = plot!(args...; kw..., linetype = :path3d)
scatter3d(args...; kw...) = plot(args...; kw..., linetype = :scatter3d) scatter3d(args...; kw...) = plot(args...; kw..., linetype = :scatter3d)
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 = :boxplot)
boxplot!(args...; kw...) = plot!(args...; kw..., linetype = :box) boxplot!(args...; kw...) = plot!(args...; kw..., linetype = :boxplot)
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)

View File

@ -11,7 +11,7 @@ const _3dTypes = [:path3d, :scatter3d, :surface, :wireframe, :contour3d]
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, :quiver, :image :contour, :pie, :shape, :image #, :boxplot, :violin, :quiver,
], _3dTypes) ], _3dTypes)
@compat const _typeAliases = KW( @compat const _typeAliases = KW(
:n => :none, :n => :none,
@ -38,7 +38,7 @@ const _allTypes = vcat([
:shapes => :shape, :shapes => :shape,
:poly => :shape, :poly => :shape,
:polygon => :shape, :polygon => :shape,
:boxplot => :box, :box => :boxplot,
:velocity => :quiver, :velocity => :quiver,
:gradient => :quiver, :gradient => :quiver,
:img => :image, :img => :image,

View File

@ -28,7 +28,7 @@ supportedAxes(::GadflyBackend) = [:auto, :left]
supportedTypes(::GadflyBackend) = [ supportedTypes(::GadflyBackend) = [
:none, :line, :path, :steppre, :steppost, :sticks, :none, :line, :path, :steppre, :steppost, :sticks,
:scatter, :hist2d, :hexbin, :hist, :scatter, :hist2d, :hexbin, :hist,
:bar, :box, :violin, :quiver, :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]

View File

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

View File

@ -86,12 +86,15 @@ end
# natively by the backend # natively by the backend
function _apply_series_recipe(plt::Plot, d::KW) function _apply_series_recipe(plt::Plot, d::KW)
lt = d[:linetype] lt = d[:linetype]
# dumpdict(d, "apply_series_recipe", true)
if lt in supportedTypes() if lt in supportedTypes()
# println("adding series!!")
warnOnUnsupported(plt.backend, d)
_add_series(plt.backend, plt, d) _add_series(plt.backend, plt, d)
else else
# get a sub list of series for this linetype # get a sub list of series for this linetype
try series_list = try
series_list = RecipesBase.apply_recipe(Val{lt}, d[:x], d[:y], d[:z]) RecipesBase.apply_recipe(d, Val{lt}, d[:x], d[:y], d[:z])
catch catch
warn("Exception during apply_recipe(Val{$lt}, ...) with types ($(typeof(d[:x])), $(typeof(d[:y])), $(typeof(d[:z])))") warn("Exception during apply_recipe(Val{$lt}, ...) with types ($(typeof(d[:x])), $(typeof(d[:y])), $(typeof(d[:z])))")
rethrow() rethrow()
@ -142,12 +145,25 @@ function _plot!(plt::Plot, d::KW, args...)
next_series = pop!(still_to_process) next_series = pop!(still_to_process)
series_list = RecipesBase.apply_recipe(next_series.d, next_series.args...) series_list = RecipesBase.apply_recipe(next_series.d, next_series.args...)
for series in series_list for series in series_list
# @show series
if isempty(series.args) if isempty(series.args)
# finish processing and add to the kw_list # finish processing and add to the kw_list
_add_markershape(series.d) _add_markershape(series.d)
_filter_input_data!(series.d) _filter_input_data!(series.d)
warnOnUnsupportedArgs(plt.backend, series.d) warnOnUnsupportedArgs(plt.backend, series.d)
warnOnUnsupportedScales(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) push!(kw_list, series.d)
else else
push!(still_to_process, series) 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 # 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 # 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) # _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 # `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 # 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. # 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}.
""" # for linetype `line`, need to sort by x values
apply_series_recipe(d::KW, lt) = KW[d] @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 # Box Plot
const _box_halfwidth = 0.4 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) # dumpdict(d, "box before", true)
# TODO: add scatter series with outliers # TODO: add scatter series with outliers
# create a list of shapes, where each shape is a single boxplot # create a list of shapes, where each shape is a single boxplot
shapes = Shape[] shapes = Shape[]
d[:linetype] = :shape groupby = extractGroupArgs(x)
groupby = extractGroupArgs(d[:x])
for (i, glabel) in enumerate(groupby.groupLabels) 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)) push!(shapes, Shape(xcoords, ycoords))
end 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 end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@ -260,17 +280,13 @@ end
# if the user has KernelDensity installed, use this for violin plots. # if the user has KernelDensity installed, use this for violin plots.
# otherwise, just use a histogram # otherwise, just use a histogram
try if is_installed("KernelDensity")
Pkg.installed("KernelDensity") @eval import KernelDensity
import KernelDensity
# warn("using KD for violin")
@eval function violin_coords(y) @eval function violin_coords(y)
kd = KernelDensity.kde(y, npoints = 30) kd = KernelDensity.kde(y, npoints = 30)
kd.density, kd.x kd.density, kd.x
end end
catch else
# warn("using hist for violin")
@eval function violin_coords(y) @eval function violin_coords(y)
edges, widths = hist(y, 20) edges, widths = hist(y, 20)
centers = 0.5 * (edges[1:end-1] + edges[2:end]) centers = 0.5 * (edges[1:end-1] + edges[2:end])
@ -280,13 +296,13 @@ catch
end 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) # dumpdict(d, "box before", true)
# TODO: add scatter series with outliers # TODO: add scatter series with outliers
# create a list of shapes, where each shape is a single boxplot # create a list of shapes, where each shape is a single boxplot
shapes = Shape[] shapes = Shape[]
d[:linetype] = :shape
groupby = extractGroupArgs(d[:x]) groupby = extractGroupArgs(d[:x])
for (i, glabel) in enumerate(groupby.groupLabels) 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)) push!(shapes, Shape(xcoords, ycoords))
end 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 end
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@ -353,18 +373,22 @@ end
# we will create a series of path segments, where each point represents one # we will create a series of path segments, where each point represents one
# side of an errorbar # 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) error_style!(d)
d[:markershape] = :hline d[:markershape] = :hline
d[:x], d[:y] = error_coords(d[:x], d[:y], error_zipit(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}})
@recipe function f(::Type{Val{:xerror}}, x, y, z)
error_style!(d) error_style!(d)
d[:markershape] = :vline d[:markershape] = :vline
d[:y], d[:x] = error_coords(d[:y], d[:x], error_zipit(d[:xerror])) d[:y], d[:x] = error_coords(d[:y], d[:x], error_zipit(d[:xerror]))
KW[d] # KW[d]
()
end end
@ -407,7 +431,7 @@ function quiver_using_arrows(d::KW)
end end
d[:x], d[:y] = x, y d[:x], d[:y] = x, y
KW[d] # KW[d]
end end
# function apply_series_recipe(d::KW, ::Type{Val{:quiver}}) # function apply_series_recipe(d::KW, ::Type{Val{:quiver}})
@ -453,15 +477,17 @@ function quiver_using_hack(d::KW)
end end
d[:x], d[:y] = Plots.unzip(pts[2:end]) d[:x], d[:y] = Plots.unzip(pts[2:end])
KW[d] # KW[d]
end 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() if :arrow in supportedArgs()
quiver_using_arrows(d) quiver_using_arrows(d)
else else
quiver_using_hack(d) quiver_using_hack(d)
end end
()
end end

View File

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