From 3c3078875a5ffa15d1abc2b33f97f61fa508824e Mon Sep 17 00:00:00 2001 From: Thomas Breloff Date: Fri, 8 Apr 2016 10:35:17 -0400 Subject: [PATCH] errorbars --- src/args.jl | 3 ++ src/backends/plotly.jl | 2 +- src/backends/supported.jl | 10 ++++++ src/components.jl | 6 ++-- src/recipes.jl | 66 ++++++++++++++++++++++++++++++++------- src/series_args.jl | 7 +++++ src/utils.jl | 4 +++ 7 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/args.jl b/src/args.jl index 8c7460f0..a6e421c7 100644 --- a/src/args.jl +++ b/src/args.jl @@ -145,6 +145,7 @@ _seriesDefaults[:zcolor] = nothing # value for color scale # _seriesDefaults[:nlevels] = 15 _seriesDefaults[:levels] = 15 _seriesDefaults[:orientation] = :vertical +_seriesDefaults[:errorbar] = nothing const _plotDefaults = KW() @@ -324,6 +325,8 @@ end :clearfig => :overwrite_figure, :overwrite => :overwrite_figure, :reuse => :overwrite_figure, + :err => :errorbar, + :error => :errorbar, ) # add all pluralized forms to the _keyAliases dict diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index a8ed9bb4..0efa2a50 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -313,7 +313,7 @@ function plotly_series(d::KW; plot_index = nothing) elseif lt in (:hist, :density) d_out[:type] = "histogram" - isvert = d[:orientation] in (:vertical, :v, :vert) + isvert = isvertical(d) d_out[isvert ? :x : :y] = y d_out[isvert ? :nbinsx : :nbinsy] = d[:nbins] if lt == :density diff --git a/src/backends/supported.jl b/src/backends/supported.jl index 63cdee5c..069971d0 100644 --- a/src/backends/supported.jl +++ b/src/backends/supported.jl @@ -77,6 +77,8 @@ supportedArgs(::GadflyBackend) = [ :grid, # :surface, :levels, + :errorbar, + :orientation, ] supportedAxes(::GadflyBackend) = [:auto, :left] supportedTypes(::GadflyBackend) = [:none, :line, :path, :steppre, :steppost, :sticks, @@ -164,6 +166,8 @@ supportedArgs(::PyPlotBackend) = [ :linealpha, :markeralpha, :overwrite_figure, + :errorbar, + :orientation, ] supportedAxes(::PyPlotBackend) = _allAxes supportedTypes(::PyPlotBackend) = [:none, :line, :path, :steppre, :steppost, #:sticks, @@ -238,6 +242,8 @@ supportedArgs(::GRBackend) = [ :fillalpha, :linealpha, :markeralpha, + :errorbar, + :orientation, ] supportedAxes(::GRBackend) = _allAxes supportedTypes(::GRBackend) = [:none, :line, :path, :steppre, :steppost, :sticks, @@ -560,6 +566,8 @@ supportedArgs(::PlotlyBackend) = [ :legendfont, :grid, :levels, + :errorbar, + :orientation, ] supportedAxes(::PlotlyBackend) = [:auto, :left] supportedTypes(::PlotlyBackend) = [:none, :line, :path, :scatter, :steppre, :steppost, @@ -630,6 +638,8 @@ supportedArgs(::PlotlyJSBackend) = [ :legendfont, :grid, :levels, + :errorbar, + :orientation, ] supportedAxes(::PlotlyJSBackend) = [:auto, :left] supportedTypes(::PlotlyJSBackend) = [:none, :line, :path, :scatter, :steppre, :steppost, diff --git a/src/components.jl b/src/components.jl index 7548de86..da482b39 100644 --- a/src/components.jl +++ b/src/components.jl @@ -40,8 +40,10 @@ function shape_coords(shapes::AVec{Shape}) x, y = unzip(shapes[1].vertices) for shape in shapes[2:end] tmpx, tmpy = unzip(shape.vertices) - x = vcat(x, NaN, tmpx) - y = vcat(y, NaN, tmpy) + nanappend!(x, tmpx) + nanappend!(y, tmpy) + # x = vcat(x, NaN, tmpx) + # y = vcat(y, NaN, tmpy) end x, y end diff --git a/src/recipes.jl b/src/recipes.jl index 4d052d4d..a9939a85 100644 --- a/src/recipes.jl +++ b/src/recipes.jl @@ -29,7 +29,7 @@ function _apply_recipe(d::KW, args...; issubplot=false, kw...) end -# ------------------------------------------------- +# --------------------------------------------------------------------------- """ `apply_series_recipe` should take a processed series KW dict and break it up @@ -40,7 +40,7 @@ Returns a Vector{KW}. """ apply_series_recipe(d::KW, lt) = KW[d] -# ------------------------------------------------- +# --------------------------------------------------------------------------- # Box Plot const _box_halfwidth = 0.4 @@ -82,21 +82,23 @@ function apply_series_recipe(d::KW, ::Type{Val{:box}}) KW[d] end -# ------------------------------------------------- +# --------------------------------------------------------------------------- # Violin Plot +# 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") - function violin_coords(y) + # warn("using KD for violin") + @eval function violin_coords(y) kd = KernelDensity.kde(y, npoints = 30) kd.density, kd.x end catch - warn("using hist for violin") - function violin_coords(y) + # warn("using hist for violin") + @eval function violin_coords(y) edges, widths = hist(y, 20) centers = 0.5 * (edges[1:end-1] + edges[2:end]) ymin, ymax = extrema(y) @@ -135,7 +137,49 @@ function apply_series_recipe(d::KW, ::Type{Val{:violin}}) KW[d] end -# ------------------------------------------------- +# --------------------------------------------------------------------------- +# Error Bars + + +# 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{:errorbar}}) + d[:linetype] = :path + d[:markershape] = isvertical(d) ? :hline : :vline + d[:linecolor] = d[:markerstrokecolor] + d[:linewidth] = d[:markerstrokewidth] + d[:label] = "" + + # to simplify: when orientation is horizontal, x/y are swapped + ebar = pop!(d, :errorbar) + xorig, yorig = d[:x], d[:y] + if !isvertical(d) + xorig, yorig = yorig, xorig + end + # dumpdict(d, "err", true) + # @show isvertical(d) xorig yorig ebar + + # now assume vertical orientation + x, y = zeros(0), zeros(0) + w = 0.3 * mean(diff(x)) + for i = 1:max(length(xorig), length(yorig)) + # create a line segment from the bottom to the top of the errorbar + xi = get_mod(xorig, i) + yi = get_mod(yorig, i) + ebi = get_mod(ebar, i) + nanappend!(x, [xi, xi]) + nanappend!(y, [yi - ebi, yi + ebi]) + end + + d[:x], d[:y] = isvertical(d) ? (x, y) : (y, x) + KW[d] +end + + +# --------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +# --------------------------------------------------------------------------- function rotate(x::Real, y::Real, θ::Real; center = (0,0)) cx = x - center[1] @@ -145,7 +189,7 @@ function rotate(x::Real, y::Real, θ::Real; center = (0,0)) xrot + center[1], yrot + center[2] end -# ------------------------------------------------- +# --------------------------------------------------------------------------- type EllipseRecipe <: PlotRecipe w::Float64 @@ -236,7 +280,7 @@ function mat2list{T}(mat::AbstractArray{T,2}) resize!(source, idx-1), resize!(destiny, idx-1), resize!(weight, idx-1) end -# ------------------------------------------------- +# --------------------------------------------------------------------------- # Arc Diagram curvecolor(value, min, max, grad) = getColorZ(grad, (value-min)/(max-min)) @@ -292,7 +336,7 @@ For simmetric matrices, only the upper triangular values are used. """ arcdiagram{T}(mat::AbstractArray{T,2}; kargs...) = arcdiagram(mat2list(mat)...; kargs...) -# ------------------------------------------------- +# --------------------------------------------------------------------------- # Chord diagram arcshape(θ1, θ2) = Shape(vcat(Plots.partialcircle(θ1, θ2, 15, 1.1), diff --git a/src/series_args.jl b/src/series_args.jl index 49ce585c..1777ff7e 100644 --- a/src/series_args.jl +++ b/src/series_args.jl @@ -151,6 +151,13 @@ function build_series_args(plt::AbstractPlot, kw::KW) #, idxfilter) d[:fillrange] = map(d[:fillrange], d[:x]) end + # handle error bars and ribbons + if get(d, :errorbar, nothing) != nothing + # we make a copy of the KW and apply an errorbar recipe + append!(ret, apply_series_recipe(copy(d), Val{:errorbar})) + 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) diff --git a/src/utils.jl b/src/utils.jl index 9df8d7c5..a37d71ce 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -218,6 +218,7 @@ isscalar(::Real) = true isscalar(::Any) = false +isvertical(d::KW) = get(d, :orientation, :vertical) in (:vertical, :v, :vert) # ticksType{T<:Real,S<:Real}(ticks::@compat(Tuple{T,S})) = :limits @@ -236,6 +237,9 @@ Base.convert{T<:Real,S<:Real}(::Type{Vector{T}}, rng::Range{S}) = T[x for x in r Base.merge(a::AbstractVector, b::AbstractVector) = sort(unique(vcat(a,b))) +nanpush!(a::AbstractVector, b) = (push!(a, NaN); push!(a, b)) +nanappend!(a::AbstractVector, b) = (push!(a, NaN); append!(a, b)) + # --------------------------------------------------------------- wraptuple(x::@compat(Tuple)) = x