From 350ffdee25765a9076e6441325b1bc3edae5e3f5 Mon Sep 17 00:00:00 2001 From: Thomas Breloff Date: Thu, 3 Nov 2016 13:29:20 -0400 Subject: [PATCH] Base.cycle to cycle; text_size; animate; MixedMeasures; SeriesAnnotations --- src/Plots.jl | 1 + src/animation.jl | 29 ++++++++++++++++++++++++++++- src/backends.jl | 34 ++++++++++++++++++++++++++-------- src/backends/gr.jl | 16 ++++++++++------ src/components.jl | 45 +++++++++++++++++++++++++++++++++++++++++---- src/layouts.jl | 25 +++++++++++++++++++++++++ src/pipeline.jl | 25 ++++++++++++++++++++----- src/utils.jl | 20 ++++++++++---------- 8 files changed, 161 insertions(+), 34 deletions(-) diff --git a/src/Plots.jl b/src/Plots.jl index 7fa730e0..a9abf607 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -89,6 +89,7 @@ export Animation, frame, gif, + animate, @animate, @gif, diff --git a/src/animation.jl b/src/animation.jl index c991ada9..5d0464b3 100644 --- a/src/animation.jl +++ b/src/animation.jl @@ -16,6 +16,33 @@ function frame{P<:AbstractPlot}(anim::Animation, plt::P=current()) push!(anim.frames, filename) end +giffn() = (isijulia() ? "tmp.gif" : tempname()*".gif") + +type FrameIterator + itr + every::Int + kw +end +FrameIterator(itr; every=1, kw...) = FrameIterator(itr, every, kw) + +""" +Animate from an iterator which returns the plot args each iteration. +""" +function animate(fitr::FrameIterator, fn = giffn(); kw...) + anim = Animation() + for (i, plotargs) in enumerate(fitr.itr) + if mod1(i, fitr.every) == 1 + plot(wraptuple(plotargs)...; fitr.kw...) + frame(anim) + end + end + gif(anim, fn; kw...) +end + +# most things will implement this +function animate(obj, fn = giffn(); every=1, fps=20, loop=0, kw...) + animate(FrameIterator(obj, every, kw); fps=fps, loop=loop) +end # ----------------------------------------------- @@ -24,7 +51,7 @@ immutable AnimatedGif filename::String end -function gif(anim::Animation, fn = (isijulia() ? "tmp.gif" : tempname()*".gif"); fps::Integer = 20, loop::Integer = 0) +function gif(anim::Animation, fn = giffn(); fps::Integer = 20, loop::Integer = 0) fn = abspath(fn) try diff --git a/src/backends.jl b/src/backends.jl index 2118d732..7bb42ad3 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -51,6 +51,20 @@ _before_layout_calcs(plt::Plot) = nothing title_padding(sp::Subplot) = sp[:title] == "" ? 0mm : sp[:titlefont].pointsize * pt guide_padding(axis::Axis) = axis[:guide] == "" ? 0mm : axis[:guidefont].pointsize * pt +"Returns the (width,height) of a text label." +function text_size(lablen::Int, sz::Number, rot::Number = 0) + # we need to compute the size of the ticks generically + # this means computing the bounding box and then getting the width/height + ptsz = sz * pt + width = 0.8lablen * ptsz + + # now compute the generalized "height" after rotation as the "opposite+adjacent" of 2 triangles + height = abs(sind(rot)) * width + abs(cosd(rot)) * ptsz + width = abs(sind(rot+90)) * width + abs(cosd(rot+90)) * ptsz + width, height +end +text_size(lab::AbstractString, sz::Number, rot::Number = 0) = text_size(length(lab), sz, rot) + # account for the size/length/rotation of tick labels function tick_padding(axis::Axis) ticks = get_ticks(axis) @@ -59,19 +73,23 @@ function tick_padding(axis::Axis) else vals, labs = ticks isempty(labs) && return 0mm - ptsz = axis[:tickfont].pointsize * pt - - # we need to compute the size of the ticks generically - # this means computing the bounding box and then getting the width/height + # ptsz = axis[:tickfont].pointsize * pt longest_label = maximum(length(lab) for lab in labs) - labelwidth = 0.8longest_label * ptsz # generalize by "rotating" y labels rot = axis[:rotation] + (axis[:letter] == :y ? 90 : 0) - # now compute the generalized "height" after rotation as the "opposite+adjacent" of 2 triangles - hgt = abs(sind(rot)) * labelwidth + abs(cosd(rot)) * ptsz + 1mm - hgt + # # we need to compute the size of the ticks generically + # # this means computing the bounding box and then getting the width/height + # labelwidth = 0.8longest_label * ptsz + # + # + # # now compute the generalized "height" after rotation as the "opposite+adjacent" of 2 triangles + # hgt = abs(sind(rot)) * labelwidth + abs(cosd(rot)) * ptsz + 1mm + # hgt + + # get the height of the rotated label + text_size(longest_label, axis[:tickfont].pointsize, rot)[2] end end diff --git a/src/backends/gr.jl b/src/backends/gr.jl index c12e1f0c..f6e565af 100644 --- a/src/backends/gr.jl +++ b/src/backends/gr.jl @@ -1021,14 +1021,18 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas) end end for ann in sp[:annotations] - x, y, val = ann - x, y = if is3d(sp) - # GR.wc3towc(x, y, z) + if isa(ann, SeriesAnnotation) + # TODO handle series annotations else - GR.wctondc(x, y) + x, y, val = ann + x, y = if is3d(sp) + # GR.wc3towc(x, y, z) + else + GR.wctondc(x, y) + end + gr_set_font(val.font) + gr_text(x, y, val.str) end - gr_set_font(val.font) - gr_text(x, y, val.str) end GR.restorestate() end diff --git a/src/components.jl b/src/components.jl index 49dbf949..2c2a025e 100644 --- a/src/components.jl +++ b/src/components.jl @@ -308,10 +308,6 @@ function text(str, args...) end -annotations(::Void) = [] -annotations(anns::AVec) = anns -annotations(anns) = Any[anns] - # ----------------------------------------------------------------------- @@ -387,6 +383,47 @@ end # ----------------------------------------------------------------------- +type SeriesAnnotations + strs::AbstractVector # the labels/names + font::Font + shape::Nullable{Shape} + shapefill::Brush + shapestroke::Stroke + bboxes::Vector{BBox} +end +function SeriesAnnotations(strs::AbstractVector, args...) + fnt = font() + shp = Nullable{Shape}() + br = brush(:steelblue) + stk = stroke(:black, 1) + for arg in args + if isa(arg, Shape) + shp = Nullable{Shape}(arg) + elseif isa(arg, Brush) + brush = arg + elseif isa(arg, Stroke) + stk = arg + elseif isa(arg, Font) + fnt = arg + elseif isa(arg, Symbol) && haskey(_shapes, arg) + shape = _shapes[arg] + else + warn("Unused SeriesAnnotations arg: $arg ($(typeof(arg)))") + end + end + # note: x/y coords are added later + SeriesAnnotations(strs, fnt, shp, br, stk, BBox[]) +end + + + +annotations(::Void) = [] +annotations(anns::AVec) = anns +annotations(anns) = Any[anns] +annotations(sa::SeriesAnnotations) = sa + +# ----------------------------------------------------------------------- + "type which represents z-values for colors and sizes (and anything else that might come up)" immutable ZValues values::Vector{Float64} diff --git a/src/layouts.jl b/src/layouts.jl index 6c47949c..aa003a90 100644 --- a/src/layouts.jl +++ b/src/layouts.jl @@ -96,6 +96,31 @@ function Base.show(io::IO, bbox::BoundingBox) print(io, "BBox{l,t,r,b,w,h = $(left(bbox)),$(top(bbox)), $(right(bbox)),$(bottom(bbox)), $(width(bbox)),$(height(bbox))}") end +# ----------------------------------------------------------- + +# points combined by x/y, pct, and length +type MixedMeasures + xy::Float64 + pct::Float64 + len::AbsoluteLength +end + +function resolve_mixed(mix::MixedMeasures, sp::Subplot, letter::Symbol) + xy = mix.xy + pct = mix.pct + if mix.len != 0mm + f = (letter == :x ? width : height) + totlen = f(plotarea(sp)) + pct += mix.len / totlen + end + if pct != 0 + amin, amax = axis_limits(sp[Symbol(letter,:axis)]) + xy += pct * (amax-amin) + end + xy +end + + # ----------------------------------------------------------- # AbstractLayout diff --git a/src/pipeline.jl b/src/pipeline.jl index 958594a0..031209a9 100644 --- a/src/pipeline.jl +++ b/src/pipeline.jl @@ -333,14 +333,29 @@ function _prepare_annotations(sp::Subplot, d::KW) # strip out series annotations (those which are based on series x/y coords) # and add them to the subplot attr sp_anns = annotations(sp[:annotations]) - anns = annotations(pop!(d, :series_annotations, [])) - if length(anns) > 0 + series_anns = annotations(pop!(d, :series_annotations, [])) + if isa(series_anns, SeriesAnnotations) + # x, y = d[:x], d[:y] + # nx, ny, na = map(length, (x,y,series_anns.strs)) + # n = max(nx, ny, na) + # for i=1:n + # str = cycle(series_anns.strs,i) + # xi = cycle(x,i) + # yi = cycle(y,i) + # mwidth, mheight = text_size(str, series_anns.font.pointsize) + # xsz = measure_to_data(sp[:xaxis], mwidth) + # ysz = measure_to_data(sp[:yaxis], mheight) + error() + + elseif length(series_anns) > 0 x, y = d[:x], d[:y] - nx, ny, na = map(length, (x,y,anns)) + nx, ny, na = map(length, (x,y,series_anns)) n = max(nx, ny, na) - anns = [(x[mod1(i,nx)], y[mod1(i,ny)], text(anns[mod1(i,na)])) for i=1:n] + series_anns = [(x[mod1(i,nx)], y[mod1(i,ny)], text(series_anns[mod1(i,na)])) for i=1:n] end - sp.attr[:annotations] = vcat(sp_anns, anns) + sp.attr[:annotations] = vcat(sp_anns, series_anns) + # sp[:series_annotations] = series_anns + # sp[:annotations] = sp_anns end function _expand_subplot_extrema(sp::Subplot, d::KW, st::Symbol) diff --git a/src/utils.jl b/src/utils.jl index 7483d0a8..3af2252b 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -234,19 +234,19 @@ end nop() = nothing notimpl() = error("This has not been implemented yet") -Base.cycle(wrapper::InputWrapper, idx::Int) = wrapper.obj -Base.cycle(wrapper::InputWrapper, idx::AVec{Int}) = wrapper.obj +cycle(wrapper::InputWrapper, idx::Int) = wrapper.obj +cycle(wrapper::InputWrapper, idx::AVec{Int}) = wrapper.obj -Base.cycle(v::AVec, idx::Int) = v[mod1(idx, length(v))] -Base.cycle(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))] -Base.cycle(v, idx::Int) = v +cycle(v::AVec, idx::Int) = v[mod1(idx, length(v))] +cycle(v::AMat, idx::Int) = size(v,1) == 1 ? v[1, mod1(idx, size(v,2))] : v[:, mod1(idx, size(v,2))] +cycle(v, idx::Int) = v -Base.cycle(v::AVec, indices::AVec{Int}) = map(i -> cycle(v,i), indices) -Base.cycle(v::AMat, indices::AVec{Int}) = map(i -> cycle(v,i), indices) -Base.cycle(v, indices::AVec{Int}) = fill(v, length(indices)) +cycle(v::AVec, indices::AVec{Int}) = map(i -> cycle(v,i), indices) +cycle(v::AMat, indices::AVec{Int}) = map(i -> cycle(v,i), indices) +cycle(v, indices::AVec{Int}) = fill(v, length(indices)) -Base.cycle(grad::ColorGradient, idx::Int) = cycle(grad.colors, idx) -Base.cycle(grad::ColorGradient, indices::AVec{Int}) = cycle(grad.colors, indices) +cycle(grad::ColorGradient, idx::Int) = cycle(grad.colors, idx) +cycle(grad::ColorGradient, indices::AVec{Int}) = cycle(grad.colors, indices) makevec(v::AVec) = v makevec{T}(v::T) = T[v]