apply_series_recipe framework and boxplot; fix Gadfly scales for tick labels

This commit is contained in:
Thomas Breloff 2016-04-07 15:56:09 -04:00
parent e8ed79a6bb
commit f3f29fb54f
8 changed files with 95 additions and 46 deletions

View File

@ -66,6 +66,8 @@ export
scatter3d,
scatter3d!,
abline!,
boxplot,
boxplot!,
title!,
xlabel!,
@ -188,6 +190,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)
title!(s::AbstractString; kw...) = plot!(; title = s, kw...)

View File

@ -11,7 +11,7 @@ const _3dTypes = [:path3d, :scatter3d, :surface, :wireframe]
const _allTypes = vcat([
:none, :line, :path, :steppre, :steppost, :sticks, :scatter,
:heatmap, :hexbin, :hist, :hist2d, :hist3d, :density, :bar, :hline, :vline, :ohlc,
:contour, :pie, :shape
:contour, :pie, :shape, :box
], _3dTypes)
@compat const _typeAliases = KW(
:n => :none,
@ -38,6 +38,7 @@ const _allTypes = vcat([
:shapes => :shape,
:poly => :shape,
:polygon => :shape,
:boxplot => :box,
)
like_histogram(linetype::Symbol) = linetype in (:hist, :density)

View File

@ -315,17 +315,25 @@ function addGadflyTicksGuide(gplt, ticks, isx::Bool)
replaceType(gplt.guides, gtype(ticks = collect(ticks)))
# set the ticks and the labels
# Note: this is pretty convoluted, but I think it works. We set the ticks using Gadfly.Guide,
# and then set the label function (wraps a dict lookup) through a continuous Gadfly.Scale.
elseif ttype == :ticks_and_labels
gtype = isx ? Gadfly.Guide.xticks : Gadfly.Guide.yticks
replaceType(gplt.guides, gtype(ticks = collect(ticks[1])))
# TODO add xtick_label function (given tick, return label??)
# Scale.x_discrete(; labels=nothing, levels=nothing, order=nothing)
# # TODO add xtick_label function (given tick, return label??)
# # Scale.x_discrete(; labels=nothing, levels=nothing, order=nothing)
# filterGadflyScale(gplt, isx)
# gfunc = isx ? Gadfly.Scale.x_discrete : Gadfly.Scale.y_discrete
# labelmap = Dict(zip(ticks...))
# labelfunc = val -> labelmap[val]
# push!(gplt.scales, gfunc(levels = collect(ticks[1]), labels = labelfunc))
filterGadflyScale(gplt, isx)
gfunc = isx ? Gadfly.Scale.x_discrete : Gadfly.Scale.y_discrete
gfunc = isx ? Gadfly.Scale.x_continuous : Gadfly.Scale.y_continuous
labelmap = Dict(zip(ticks...))
labelfunc = val -> labelmap[val]
push!(gplt.scales, gfunc(levels = collect(ticks[1]), labels = labelfunc))
push!(gplt.scales, gfunc(labels = labelfunc))
else
error("Invalid input for $(isx ? "xticks" : "yticks"): ", ticks)

View File

@ -80,7 +80,7 @@ supportedArgs(::GadflyBackend) = [
]
supportedAxes(::GadflyBackend) = [:auto, :left]
supportedTypes(::GadflyBackend) = [:none, :line, :path, :steppre, :steppost, :sticks,
:scatter, :hist2d, :hexbin, :hist, :bar,
:scatter, :hist2d, :hexbin, :hist, :bar, :box,
:hline, :vline, :contour, :shape]
supportedStyles(::GadflyBackend) = [:auto, :solid, :dash, :dot, :dashdot, :dashdotdot]
supportedMarkers(::GadflyBackend) = vcat(_allMarkers, Shape)
@ -167,7 +167,7 @@ supportedArgs(::PyPlotBackend) = [
]
supportedAxes(::PyPlotBackend) = _allAxes
supportedTypes(::PyPlotBackend) = [:none, :line, :path, :steppre, :steppost, #:sticks,
:scatter, :hist2d, :hexbin, :hist, :density, :bar,
:scatter, :hist2d, :hexbin, :hist, :density, :bar, :box,
:hline, :vline, :contour, :path3d, :scatter3d, :surface, :wireframe, :heatmap]
supportedStyles(::PyPlotBackend) = [:auto, :solid, :dash, :dot, :dashdot]
# supportedMarkers(::PyPlotBackend) = [:none, :auto, :rect, :ellipse, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star5, :hexagon]

View File

@ -219,6 +219,13 @@ function _add_series(plt::Plot, d::KW, ::Void, args...;
delete!(di, k)
end
# merge in plotarg_overrides
plotarg_overrides = pop!(di, :plotarg_overrides, nothing)
if plotarg_overrides != nothing
merge!(plt.plotargs, plotarg_overrides)
end
# dumpdict(plt.plotargs, "pargs", true)
dumpdict(di, "Series $i")
_add_series(plt.backend, plt; di...)

View File

@ -28,6 +28,59 @@ function _apply_recipe(d::KW, args...; issubplot=false, kw...)
args
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.
Returns a Vector{KW}.
"""
apply_series_recipe(d::KW, lt) = KW[d]
# -------------------------------------------------
# Box Plot
function apply_series_recipe(d::KW, ::Type{Val{:box}})
# 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)
# filter y values, then compute quantiles
q1,q2,q3,q4,q5 = quantile(d[:y][groupby.groupIds[i]], linspace(0,1,5))
# make the shape
l, m, r = i - 0.3, i, i + 0.3
xcoords = [
m, l, r, m, m, NaN, # lower T
l, l, r, r, l, NaN, # lower box
l, l, r, r, l, NaN, # upper box
m, l, r, m, m # upper T
]
ycoords = [
q1, q1, q1, q1, q2, NaN, # lower T
q2, q3, q3, q2, q2, NaN, # lower box
q4, q3, q3, q4, q4, NaN, # upper box
q5, q5, q5, q5, q4, NaN, # upper T
]
push!(shapes, Shape(xcoords, ycoords))
end
d[:x], d[:y] = shape_coords(shapes)
d[:plotarg_overrides] = KW(:xticks => (1:length(shapes), groupby.groupLabels))
KW[d]
end
# -------------------------------------------------
function rotate(x::Real, y::Real, θ::Real; center = (0,0))

View File

@ -92,7 +92,7 @@ compute_xyz(x::Void, y::Void, z::Void) = error("x/y/z are all nothing!")
# create n=max(mx,my) series arguments. the shorter list is cycled through
# note: everything should flow through this
function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter)
x, y, z = map(a -> pop!(kw, a, nothing), (:x, :y, :z))
x, y, z = map(sym -> pop!(kw, sym, nothing), (:x, :y, :z))
if nothing == x == y == z
return [], nothing, nothing
end
@ -101,18 +101,6 @@ function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter)
ys, ymeta = convertToAnyVector(y, kw)
zs, zmeta = convertToAnyVector(z, kw)
# if idxfilter != nothing
# xs = filter_data(xs, idxfilter)
# ys = filter_data(ys, idxfilter)
# zs = filter_data(zs, idxfilter)
# # # filter the data
# # for sym in (:x, :y, :z)
# # @show "before" sym, d[sym], idxfilter
# # d[sym] = filter_data(get(d, sym, nothing), idxfilter)
# # @show "after" sym, d[sym], idxfilter
# # end
# end
mx = length(xs)
my = length(ys)
mz = length(zs)
@ -135,30 +123,11 @@ function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter)
n = plt.n + i
dumpdict(d, "before getSeriesArgs")
# @show numUncounted i n commandIndex convertSeriesIndex(plt, n)
d = getSeriesArgs(plt.backend, getplotargs(plt, n), d, commandIndex, convertSeriesIndex(plt, n), n)
dumpdict(d, "after getSeriesArgs")
# @show map(typeof, (xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)]))
d[:x], d[:y], d[:z] = compute_xyz(xs[mod1(i,mx)], ys[mod1(i,my)], zs[mod1(i,mz)])
# @show map(typeof, (d[:x], d[:y], d[:z]))
# # NOTE: this should be handled by the time it gets here
lt = d[:linetype]
# if isa(d[:y], Surface)
# if lt in (:contour, :heatmap, :surface, :wireframe)
# z = d[:y]
# d[:y] = 1:size(z,2)
# d[:z] = z
# end
# end
# if haskey(d, :idxfilter)
# idxfilter = pop!(d, :idxfilter)
# d[:x] = d[:x][idxfilter]
# d[:y] = d[:y][idxfilter]
# end
# for linetype `line`, need to sort by x values
if lt == :line
@ -169,6 +138,11 @@ function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter)
d[:linetype] = :path
end
# special handling for missing x in box plot... all the same category
if lt == :box && xs[mod1(i,mx)] == nothing
d[:x] = ones(Int, length(d[:y]))
end
# map functions to vectors
if isa(d[:zcolor], Function)
d[:zcolor] = map(d[:zcolor], d[:x])
@ -177,14 +151,15 @@ function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter)
d[:fillrange] = map(d[:fillrange], d[:x])
end
# cleanup those fields that were used only for generating kw args
# delete!(d, :dataframe)
# for k in (:idxfilter, :numUncounted, :dataframe)
# delete!(d, k)
# end
# 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
# parts... polygons, lines, and scatters)
# note: we pass in a Val type (i.e. Val{:box}) so that we can dispatch on the linetype
kwlist = apply_series_recipe(d, Val{lt})
append!(ret, kwlist)
# add it to our series list
push!(ret, d)
# # add it to our series list
# push!(ret, d)
end
ret, xmeta, ymeta

View File

@ -118,6 +118,7 @@ end
nop() = nothing
notimpl() = error("This has not been implemented yet")
get_mod(v::AVec, idx::Int) = v[mod1(idx, length(v))]
get_mod(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))]