From 862ac3af8adcabc357e4fafadd8a0a1d66a7ab9c Mon Sep 17 00:00:00 2001 From: Thomas Breloff Date: Fri, 20 May 2016 11:38:07 -0400 Subject: [PATCH] fixes/improvements to annotations; added series_annotations keyword --- src/args.jl | 6 +- src/backends/bokeh.jl | 2 +- src/backends/gadfly.jl | 2 +- src/backends/glvisualize.jl | 2 +- src/backends/gr.jl | 2 +- src/backends/plotly.jl | 2 +- src/backends/pyplot.jl | 49 +++++++++-------- src/backends/qwt.jl | 2 +- src/backends/template.jl | 24 ++++---- src/backends/unicodeplots.jl | 2 +- src/backends/winston.jl | 2 +- src/components.jl | 1 + src/plot.jl | 104 ++++++++++++++++++++--------------- 13 files changed, 111 insertions(+), 89 deletions(-) diff --git a/src/args.jl b/src/args.jl index fbe47f4c..96b49793 100644 --- a/src/args.jl +++ b/src/args.jl @@ -159,6 +159,7 @@ _series_defaults[:contours] = false # add contours to 3d surface an _series_defaults[:match_dimensions] = false # do rows match x (true) or y (false) for heatmap/image/spy? see issue 196 # this ONLY effects whether or not the z-matrix is transposed for a heatmap display! _series_defaults[:subplot] = :auto # which subplot(s) does this series belong to? +_series_defaults[:series_annotations] = [] # a list of annotations which apply to the coordinates of this series const _plot_defaults = KW() @@ -199,7 +200,7 @@ _subplot_defaults[:legend] = :best _subplot_defaults[:colorbar] = :legend _subplot_defaults[:legendfont] = font(8) _subplot_defaults[:grid] = true -_subplot_defaults[:annotation] = nothing # annotation tuple(s)... (x,y,annotation) +_subplot_defaults[:annotations] = [] # annotation tuples... list of (x,y,annotation) # _subplot_defaults[:polar] = false _subplot_defaults[:projection] = :none # can also be :polar or :3d _subplot_defaults[:aspect_ratio] = :none # choose from :none or :equal @@ -346,7 +347,7 @@ add_aliases(:fillrange, :fillrng, :frange, :fillto, :fill_between) add_aliases(:group, :g, :grouping) add_aliases(:bins, :bin, :nbin, :nbins, :nb) add_aliases(:ribbon, :rib) -add_aliases(:annotation, :ann, :anns, :annotate, :annotations) +add_aliases(:annotations, :ann, :anns, :annotate, :annotation) add_aliases(:xguide, :xlabel, :xlab, :xl) add_aliases(:xlims, :xlim, :xlimit, :xlimits) add_aliases(:xticks, :xtick) @@ -385,6 +386,7 @@ add_aliases(:match_dimensions, :transpose, :transpose_z) add_aliases(:subplot, :sp, :subplt, :splt) add_aliases(:projection, :proj) add_aliases(:title_location, :title_loc, :titleloc) +add_aliases(:series_annotations, :series_ann, :seriesann, :series_anns, :seriesanns, :series_annotation) # add all pluralized forms to the _keyAliases dict diff --git a/src/backends/bokeh.jl b/src/backends/bokeh.jl index fd56af91..2844443d 100644 --- a/src/backends/bokeh.jl +++ b/src/backends/bokeh.jl @@ -3,7 +3,7 @@ supportedArgs(::BokehBackend) = [ - # :annotation, + # :annotations, # :axis, # :background_color, :linecolor, diff --git a/src/backends/gadfly.jl b/src/backends/gadfly.jl index 67f16c08..dc9defe2 100644 --- a/src/backends/gadfly.jl +++ b/src/backends/gadfly.jl @@ -3,7 +3,7 @@ supportedArgs(::GadflyBackend) = [ - :annotation, + :annotations, :background_color, :foreground_color, :color_palette, :group, :label, :seriestype, :seriescolor, :seriesalpha, diff --git a/src/backends/glvisualize.jl b/src/backends/glvisualize.jl index 017ea36b..0274209b 100644 --- a/src/backends/glvisualize.jl +++ b/src/backends/glvisualize.jl @@ -3,7 +3,7 @@ # [WEBSITE] supportedArgs(::GLVisualizeBackend) = [ - # :annotation, + # :annotations, # :axis, # :background_color, # :color_palette, diff --git a/src/backends/gr.jl b/src/backends/gr.jl index ccccd028..32122271 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -3,7 +3,7 @@ supportedArgs(::GRBackend) = [ - :annotation, + :annotations, :background_color, :foreground_color, :color_palette, :background_color_legend, :background_color_inside, :background_color_outside, :foreground_color_legend, :foreground_color_grid, :foreground_color_axis, diff --git a/src/backends/plotly.jl b/src/backends/plotly.jl index 7bcbe523..807003a6 100644 --- a/src/backends/plotly.jl +++ b/src/backends/plotly.jl @@ -2,7 +2,7 @@ # https://plot.ly/javascript/getting-started supportedArgs(::PlotlyBackend) = [ - :annotation, + :annotations, :background_color, :foreground_color, :color_palette, # :background_color_legend, :background_color_inside, :background_color_outside, # :foreground_color_legend, :foreground_color_grid, :foreground_color_axis, diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 597dba71..1369b613 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -3,7 +3,7 @@ supportedArgs(::PyPlotBackend) = [ - :annotation, + :annotations, :background_color, :foreground_color, :color_palette, :background_color_legend, :background_color_inside, :background_color_outside, :foreground_color_legend, :foreground_color_grid, :foreground_color_axis, @@ -1153,6 +1153,11 @@ function _update_plot(plt::Plot{PyPlotBackend}, d::KW) # ticksz = get(d, :tickfont, plt.plotargs[:tickfont]).pointsize # guidesz = get(d, :guidefont, attr[:guidefont]).pointsize + # add the annotations + for ann in attr[:annotations] + createPyPlotAnnotationObject(sp, ann...) + end + # title if haskey(attr, :title) loc = lowercase(string(attr[:title_location])) @@ -1252,28 +1257,28 @@ end # TODO: these should apply to a Subplot, NOT a Plot -# function createPyPlotAnnotationObject(plt::Plot{PyPlotBackend}, x, y, val::@compat(AbstractString)) -# ax = getLeftAxis(plt) -# ax[:annotate](val, xy = (x,y)) -# end -# -# -# function createPyPlotAnnotationObject(plt::Plot{PyPlotBackend}, x, y, val::PlotText) -# ax = getLeftAxis(plt) -# ax[:annotate](val.str, -# xy = (x,y), -# family = val.font.family, -# color = getPyPlotColor(val.font.color), -# horizontalalignment = val.font.halign == :hcenter ? "center" : string(val.font.halign), -# verticalalignment = val.font.valign == :vcenter ? "center" : string(val.font.valign), -# rotation = val.font.rotation * 180 / π, -# size = val.font.pointsize -# ) -# end -# -# function _add_annotations{X,Y,V}(plt::Plot{PyPlotBackend}, anns::AVec{@compat(Tuple{X,Y,V})}) +function createPyPlotAnnotationObject(sp::Subplot{PyPlotBackend}, x, y, val::@compat(AbstractString)) + ax = sp.o + ax[:annotate](val, xy = (x,y)) +end + + +function createPyPlotAnnotationObject(sp::Subplot{PyPlotBackend}, x, y, val::PlotText) + ax = sp.o + ax[:annotate](val.str, + xy = (x,y), + family = val.font.family, + color = getPyPlotColor(val.font.color), + horizontalalignment = val.font.halign == :hcenter ? "center" : string(val.font.halign), + verticalalignment = val.font.valign == :vcenter ? "center" : string(val.font.valign), + rotation = val.font.rotation * 180 / π, + size = val.font.pointsize + ) +end + +# function _add_annotations(sp::Subplot{PyPlotBackend}, anns::AVec) # for ann in anns -# createPyPlotAnnotationObject(plt, ann...) +# createPyPlotAnnotationObject(sp, ann...) # end # end diff --git a/src/backends/qwt.jl b/src/backends/qwt.jl index 0b1cc853..cc76d733 100644 --- a/src/backends/qwt.jl +++ b/src/backends/qwt.jl @@ -3,7 +3,7 @@ supportedArgs(::QwtBackend) = [ - :annotation, + :annotations, :axis, :background_color, :linecolor, diff --git a/src/backends/template.jl b/src/backends/template.jl index db01eb2e..1e41ef10 100644 --- a/src/backends/template.jl +++ b/src/backends/template.jl @@ -24,11 +24,11 @@ function _add_series(plt::Plot{[PkgName]Backend}, series::Series) # TODO: add one series to the underlying package end -function _add_annotations{X,Y,V}(plt::Plot{[PkgName]AbstractBackend}, anns::AVec{@compat(Tuple{X,Y,V})}) - for ann in anns - # TODO: add the annotation to the plot - end -end +# function _add_annotations{X,Y,V}(plt::Plot{[PkgName]AbstractBackend}, anns::AVec{@compat(Tuple{X,Y,V})}) +# for ann in anns +# # TODO: add the annotation to the plot +# end +# end # ---------------------------------------------------------------- @@ -61,13 +61,13 @@ end # # TODO: build the underlying Subplot object. this is where you might layout the panes within a GUI window, for example # end -function _expand_limits(lims, plt::Plot{[PkgName]AbstractBackend}, isx::Bool) - # TODO: call expand limits for each plot data -end - -function _remove_axis(plt::Plot{[PkgName]AbstractBackend}, isx::Bool) - # TODO: if plot is inner subplot, might need to remove ticks or axis labels -end +# function _expand_limits(lims, plt::Plot{[PkgName]AbstractBackend}, isx::Bool) +# # TODO: call expand limits for each plot data +# end +# +# function _remove_axis(plt::Plot{[PkgName]AbstractBackend}, isx::Bool) +# # TODO: if plot is inner subplot, might need to remove ticks or axis labels +# end # ---------------------------------------------------------------- diff --git a/src/backends/unicodeplots.jl b/src/backends/unicodeplots.jl index aeffb056..ca218544 100644 --- a/src/backends/unicodeplots.jl +++ b/src/backends/unicodeplots.jl @@ -2,7 +2,7 @@ # https://github.com/Evizero/UnicodePlots.jl supportedArgs(::UnicodePlotsBackend) = [ - # :annotation, + # :annotations, # :args, # :axis, # :background_color, diff --git a/src/backends/winston.jl b/src/backends/winston.jl index 6229923f..97f0637b 100644 --- a/src/backends/winston.jl +++ b/src/backends/winston.jl @@ -4,7 +4,7 @@ # credit goes to https://github.com/jverzani for contributing to the first draft of this backend implementation supportedArgs(::WinstonBackend) = [ - :annotation, + :annotations, # :args, # :axis, # :background_color, diff --git a/src/components.jl b/src/components.jl index 52fc5034..981a4955 100644 --- a/src/components.jl +++ b/src/components.jl @@ -252,6 +252,7 @@ immutable PlotText end PlotText(str) = PlotText(string(str), font()) +text(t::PlotText) = t function text(str, args...) PlotText(string(str), font(args...)) end diff --git a/src/plot.jl b/src/plot.jl index 4c604dd2..257b783a 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -172,14 +172,14 @@ function _plot!(plt::Plot, d::KW, args...) args = tuple(extractGroupArgs(d[:group], args...), args...) end - # initialize the annotations list with what was passed in - # TODO: there must be cleaner way to handle this! - anns = annotations(get(d, :annotation, NTuple{3}[])) - if typeof(anns) <: AVec{PlotText} - anns = NTuple{3}[] - else - delete!(d, :annotation) - end + # # initialize the annotations list with what was passed in + # # TODO: there must be cleaner way to handle this! + # anns = annotations(get(d, :annotation, NTuple{3}[])) + # if typeof(anns) <: AVec{PlotText} + # anns = NTuple{3}[] + # else + # delete!(d, :annotation) + # end # for plotting recipes, swap out the args and update the parameter dictionary @@ -250,12 +250,12 @@ function _plot!(plt::Plot, d::KW, args...) # !!! note: at this point, kw_list is fully decomposed into individual series... one KW per series !!! - # TODO: move annotations into subplot update - # now include any annotations which were added during recipes - for kw in kw_list - append!(anns, annotations(pop!(kw, :annotation, []))) - end - # @show anns + # # TODO: move annotations into subplot update + # # now include any annotations which were added during recipes + # for kw in kw_list + # append!(anns, annotations(pop!(kw, :annotation, []))) + # end + # # @show anns # for kw in kw_list @@ -296,6 +296,18 @@ function _plot!(plt::Plot, d::KW, args...) sp = kw[:subplot] = get_subplot(plt, sp) idx = get_subplot_index(plt, sp) + # strip out series annotations (those which are based on series x/y coords) + # and add them to the subplot attr + sp_anns = annotations(sp.attr[:annotations]) + anns = annotations(pop!(kw, :series_annotations, [])) + if length(anns) > 0 + x, y = kw[:x], kw[:y] + nx, ny, na = map(length, (x,y,anns)) + n = max(nx, ny, na) + anns = [(x[mod1(i,nx)], y[mod1(i,ny)], text(anns[mod1(i,na)])) for i=1:n] + end + sp.attr[:annotations] = vcat(sp_anns, anns) + # we update subplot args in case something like the color palatte is part of the recipe # DD(sp.attr[:xaxis].d, "before USA $i") # DD(kw, "kw") @@ -319,8 +331,8 @@ function _plot!(plt::Plot, d::KW, args...) # DD(sp.attr[:xaxis].d, "after $i") end - # now that we're done adding all the series, add the annotations - _add_annotations(plt, anns) + # # now that we're done adding all the series, add the annotations + # _add_annotations(plt, anns) # TODO just need to pass plt... and we should do all non-series updates here _update_plot(plt, plt.plotargs) @@ -484,38 +496,40 @@ end # -------------------------------------------------------------------- -annotations(::@compat(Void)) = [] -annotations{X,Y,V}(v::AVec{@compat(Tuple{X,Y,V})}) = v -annotations{X,Y,V}(t::@compat(Tuple{X,Y,V})) = [t] -annotations(v::AVec{PlotText}) = v -annotations(v::AVec) = map(PlotText, v) -annotations(anns) = error("Expecting a tuple (or vector of tuples) for annotations: ", - "(x, y, annotation)\n got: $(typeof(anns))") +annotations(::Void) = [] +annotations(anns::AVec) = anns +annotations(anns) = Any[anns] +# annotations{X,Y,V}(v::AVec{@compat(Tuple{X,Y,V})}) = v +# annotations{X,Y,V}(t::@compat(Tuple{X,Y,V})) = [t] +# annotations(v::AVec{PlotText}) = v +# annotations(v::AVec) = map(PlotText, v) +# annotations(anns) = error("Expecting a tuple (or vector of tuples) for annotations: ", +# "(x, y, annotation)\n got: $(typeof(anns))") -function annotations(plt::Plot, anns) - anns = annotations(anns) - # if we just have a list of PlotText objects, then create (x,y,text) tuples - if typeof(anns) <: AVec{PlotText} - x, y = plt[plt.n] - anns = Tuple{Float64,Float64,PlotText}[(x[i], y[i], t) for (i,t) in enumerate(anns)] - end - anns -end +# function annotations(plt::Plot, anns) +# anns = annotations(anns) +# # if we just have a list of PlotText objects, then create (x,y,text) tuples +# if typeof(anns) <: AVec{PlotText} +# x, y = plt[plt.n] +# anns = Tuple{Float64,Float64,PlotText}[(x[i], y[i], t) for (i,t) in enumerate(anns)] +# end +# anns +# end -function _add_annotations(plt::Plot, d::KW) - anns = annotations(get(d, :annotation, nothing)) - if !isempty(anns) - - # if we just have a list of PlotText objects, then create (x,y,text) tuples - if typeof(anns) <: AVec{PlotText} - x, y = plt[plt.n] - anns = Tuple{Float64,Float64,PlotText}[(x[i], y[i], t) for (i,t) in enumerate(anns)] - end - - _add_annotations(plt, anns) - end -end +# function _add_annotations(plt::Plot, d::KW) +# anns = annotations(get(d, :annotation, nothing)) +# if !isempty(anns) +# +# # if we just have a list of PlotText objects, then create (x,y,text) tuples +# if typeof(anns) <: AVec{PlotText} +# x, y = plt[plt.n] +# anns = Tuple{Float64,Float64,PlotText}[(x[i], y[i], t) for (i,t) in enumerate(anns)] +# end +# +# _add_annotations(plt, anns) +# end +# end # --------------------------------------------------------------------