From 51021c799d1d971e68bf035b7c3925f7819ef722 Mon Sep 17 00:00:00 2001 From: Thomas Breloff Date: Thu, 17 Sep 2015 14:44:29 -0400 Subject: [PATCH] accept vectors of tuples; implement ohlc for gadfly with an example --- docs/example_generation.jl | 3 ++ src/Plots.jl | 4 +++ src/args.jl | 1 + src/backends/gadfly.jl | 68 +++++++---------------------------- src/backends/gadfly_shapes.jl | 29 ++++++++++++++- src/plot.jl | 11 ++++++ src/plotter.jl | 2 +- 7 files changed, 60 insertions(+), 58 deletions(-) diff --git a/docs/example_generation.jl b/docs/example_generation.jl index e889ff65..47231b7d 100644 --- a/docs/example_generation.jl +++ b/docs/example_generation.jl @@ -83,6 +83,9 @@ const examples = PlotExample[ PlotExample("", "", [:(subplot!(randn(100,3)))]), + PlotExample("Open/High/Low/Close", + "Pass in a vector of 4-tuples as your `y` argument. Adjust the tick width with arg `markersize`.", + [:(n=10; hgt=rand(n)+1; bot=randn(n); openpct=rand(n); closepct=rand(n);), :(y = [(openpct[i]*hgt[i]+bot[i], bot[i]+hgt[i], bot[i], closepct[i]*hgt[i]+bot[i]) for i in 1:n];), :(ohlc(y; markersize=10))]), ] diff --git a/src/Plots.jl b/src/Plots.jl index 47008ffc..8776e026 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -32,6 +32,8 @@ export hline!, vline, vline!, + ohlc, + ohlc!, savepng, @@ -76,6 +78,8 @@ hline(args...; kw...) = plot(args...; kw..., linetype = :hline) hline!(args...; kw...) = plot!(args...; kw..., linetype = :hline) vline(args...; kw...) = plot(args...; kw..., linetype = :vline) vline!(args...; kw...) = plot!(args...; kw..., linetype = :vline) +ohlc(args...; kw...) = plot(args...; kw..., linetype = :ohlc) +ohlc!(args...; kw...) = plot!(args...; kw..., linetype = :ohlc) # --------------------------------------------------------- diff --git a/src/args.jl b/src/args.jl index 727253e2..f577be91 100644 --- a/src/args.jl +++ b/src/args.jl @@ -16,6 +16,7 @@ const TYPES = [:line, :bar, :hline, :vline, + :ohlc, ] const STYLES = [:solid, :dash, :dot, :dashdot, :dashdotdot] const MARKERS = [:ellipse, :rect, :diamond, :utriangle, :dtriangle, :cross, :xcross, :star1, :star2, :hexagon] diff --git a/src/backends/gadfly.jl b/src/backends/gadfly.jl index b3dc83dc..cdd232cd 100644 --- a/src/backends/gadfly.jl +++ b/src/backends/gadfly.jl @@ -9,7 +9,7 @@ gadfly!() = plotter!(:gadfly) supportedArgs(::GadflyPackage) = setdiff(ARGS, [:heatmap_c, :fillto, :pos]) supportedAxes(::GadflyPackage) = setdiff(ALL_AXES, [:right]) -supportedTypes(::GadflyPackage) = [:none, :line, :step, :sticks, :scatter, :heatmap, :hexbin, :hist, :bar, :hline, :vline] +supportedTypes(::GadflyPackage) = [:none, :line, :step, :sticks, :scatter, :heatmap, :hexbin, :hist, :bar, :hline, :vline, :ohlc] supportedStyles(::GadflyPackage) = [:auto, :solid] supportedMarkers(::GadflyPackage) = [:none, :auto, :rect, :ellipse, :diamond, :cross] @@ -45,79 +45,32 @@ function getLineGeoms(d::Dict) lt = d[:linetype] lt in (:heatmap,:hexbin) && return [Gadfly.Geom.hexbin(xbincount = d[:nbins], ybincount = d[:nbins])] lt == :hist && return [Gadfly.Geom.histogram(bincount = d[:nbins])] - lt == :none && return [Gadfly.Geom.path] + # lt == :none && return [Gadfly.Geom.path] lt == :line && return [Gadfly.Geom.path] lt == :scatter && return [Gadfly.Geom.point] lt == :bar && return [Gadfly.Geom.bar] lt == :step && return [Gadfly.Geom.step] + + # NOTE: we won't actually show this (we'll set width to 0 later), but we need a geom so that Gadfly doesn't complain + if lt in (:none, :ohlc) + return [Gadfly.Geom.path] + end + # lt == :sticks && return [Gadfly.Geom.bar] error("linetype $lt not currently supported with Gadfly") end -# function createGadflyAnnotation(d::Dict) - -# if d[:marker] == :rect -# # get the width/height of the square (both are sz) -# sz = d[:markersize] * Gadfly.px -# halfsz = sz/2 - -# # remap x/y to the corner position of the squares -# xs = map(z -> Gadfly.Compose.Measure(;cx=z) - halfsz, float(d[:x])) -# ys = map(z -> Gadfly.Compose.Measure(;cy=z) + halfsz, float(d[:y])) - -# # return an Annotation which will add those shapes to each point in the series -# return Gadfly.Guide.annotation(Gadfly.compose(Gadfly.context(), Gadfly.rectangle(xs,ys,[sz],[sz]), Gadfly.fill(d[:markercolor]), Gadfly.stroke(nothing))) - -# else -# # make circles -# sz = 0.5 * d[:markersize] * Gadfly.px -# xs = collect(float(d[:x])) -# ys = collect(float(d[:y])) - -# # return an Annotation which will add those shapes to each point in the series -# return Gadfly.Guide.annotation(Gadfly.compose(Gadfly.context(), Gadfly.circle(xs,ys,[sz]), Gadfly.fill(d[:markercolor]), Gadfly.stroke(nothing))) - -# end -# end - # serious hack (I think?) to draw my own shapes as annotations... will it work? who knows... function getMarkerGeomsAndGuides(d::Dict) marker = d[:marker] - if marker == :none + if marker == :none && d[:linetype] != :ohlc return [],[] end return [], [createGadflyAnnotation(d)] end - # # special handling for other marker shapes... gotta create Compose contexts and map them to series points using Guide.Annotation - # elseif marker == :rect - # # get the width/height of the square (both are sz) - # sz = d[:markersize] * Gadfly.px - # halfsz = sz/2 - - # # remap x/y to the corner position of the squares - # xs = map(z -> Gadfly.Compose.Measure(;cx=z) - halfsz, float(d[:x])) - # ys = map(z -> Gadfly.Compose.Measure(;cy=z) + halfsz, float(d[:y])) - - # # return an Annotation which will add those shapes to each point in the series - # return [], [Gadfly.Guide.annotation(Gadfly.compose(Gadfly.context(), Gadfly.rectangle(xs,ys,[sz],[sz]), Gadfly.fill(d[:markercolor]), Gadfly.stroke(nothing)))] - - # else - # # make circles - # sz = 0.5 * d[:markersize] * Gadfly.px - # xs = collect(float(d[:x])) - # ys = collect(float(d[:y])) - - # # return an Annotation which will add those shapes to each point in the series - # return [], [Gadfly.Guide.annotation(Gadfly.compose(Gadfly.context(), Gadfly.circle(xs,ys,[sz]), Gadfly.fill(d[:markercolor]), Gadfly.stroke(nothing)))] - - # end - - # otherwise just return a Geom.point - # [Gadfly.Geom.point], [] -# end function addGadflyFixedLines!(gplt, d::Dict) @@ -153,6 +106,7 @@ function addGadflySeries!(gplt, d::Dict) elseif d[:linetype] in (:hline, :vline) addGadflyFixedLines!(gplt, d) return + end gfargs = [] @@ -161,7 +115,9 @@ function addGadflySeries!(gplt, d::Dict) append!(gfargs, getLineGeoms(d)) # handle markers + # @show d[:y] geoms, guides = getMarkerGeomsAndGuides(d) + # @show d[:y] append!(gfargs, geoms) append!(gplt.guides, guides) diff --git a/src/backends/gadfly_shapes.jl b/src/backends/gadfly_shapes.jl index 4a521bdc..109e42ad 100644 --- a/src/backends/gadfly_shapes.jl +++ b/src/backends/gadfly_shapes.jl @@ -10,7 +10,13 @@ function createGadflyAnnotation(d::Dict) x, y = d[:x], d[:y] marker = d[:marker] - if marker == :rect + if d[:linetype] == :ohlc + shape = ohlcshape(x, y, d[:markersize]) + d[:y] = Float64[z[1] for z in y] + d[:linetype] = :none + return Gadfly.Guide.annotation(Gadfly.compose(Gadfly.context(), shape, Gadfly.fill(nothing), Gadfly.stroke(d[:color]))) + + elseif marker == :rect shape = square(x, y, sz) elseif marker == :diamond @@ -86,3 +92,24 @@ function cross(xs::AbstractArray, ys::AbstractArray, rs::AbstractArray) return Gadfly.polygon(polys) end + + +# Base.isfinite{T<:Real}(x::Tuple{T,T,T,T}) = isfinite(x[1]) && isfinite(x[2]) && isfinite(x[3]) && isfinite(x[4]) + +function ohlcshape{T}(xs::AVec, ys::AVec{Tuple{T,T,T,T}}, tickwidth::Real) + @assert length(xs) == length(ys) + n = length(xs) + u = tickwidth * Compose.px + polys = Vector{Vector{Tuple{Compose.Measure, Compose.Measure}}}(n) + for i in 1:n + x = Compose.x_measure(xs[i]) + o,h,l,c = map(Compose.y_measure, ys[i]) + polys[i] = Tuple{Compose.Measure, Compose.Measure}[ + (x, o), (x - u, o), (x, o), # open tick + (x, l), (x, h), (x, c), # high/low bar + (x + u, c), (x, c) # close tick + ] + end + return Gadfly.polygon(polys) +end + diff --git a/src/plot.jl b/src/plot.jl index d06cbe82..5d390291 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -362,6 +362,17 @@ function createKWargsList(plt::PlottingObject, n::Integer; kw...) ret end +# vector of tuples... to be used for things like OHLC +createKWargsList{T<:Tuple}(plt::PlottingObject, y::AVec{T}; kw...) = createKWargsList(plt, 1:length(y), y; kw...) + +function createKWargsList{S<:Real, T<:Tuple}(plt::PlottingObject, x::AVec{S}, y::AVec{T}; kw...) + d = getPlotKeywordArgs(plt.plotter, kw, 1, plt.n + 1) + d[:x] = x + d[:y] = y + [d] +end + + # TODO: handle DataFrames (might have NAs!) # ------------------------- diff --git a/src/plotter.jl b/src/plotter.jl index 62d6df30..8bf1808d 100644 --- a/src/plotter.jl +++ b/src/plotter.jl @@ -89,7 +89,7 @@ function plotter() if !(currentBackendSymbol in INITIALIZED_BACKENDS) # initialize - println("[Plots.jl] Initializing package: ", CURRENT_BACKEND.sym) + println("[Plots.jl] Initializing backend: ", CURRENT_BACKEND.sym) if currentBackendSymbol == :qwt try @eval import Qwt