series_annotations refactor and gr/pyplot fixes
This commit is contained in:
parent
af1896dc36
commit
4dfadeaf15
@ -194,7 +194,7 @@ const _series_defaults = KW(
|
||||
: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!
|
||||
:subplot => :auto, # which subplot(s) does this series belong to?
|
||||
:series_annotations => [], # a list of annotations which apply to the coordinates of this series
|
||||
:series_annotations => nothing, # a list of annotations which apply to the coordinates of this series
|
||||
:primary => true, # when true, this "counts" as a series for color selection, etc. the main use is to allow
|
||||
# one logical series to be broken up (path and markers, for example)
|
||||
:hover => nothing, # text to display when hovering over the data points
|
||||
@ -440,7 +440,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, :title_position, :title_pos, :titlepos, :titleposition, :title_align, :title_alignment)
|
||||
add_aliases(:series_annotations, :series_ann, :seriesann, :series_anns, :seriesanns, :series_annotation)
|
||||
add_aliases(:series_annotations, :series_ann, :seriesann, :series_anns, :seriesanns, :series_annotation, :text, :txt, :texts, :txts)
|
||||
add_aliases(:html_output_format, :format, :fmt, :html_format)
|
||||
add_aliases(:orientation, :direction, :dir)
|
||||
add_aliases(:inset_subplots, :inset, :floating)
|
||||
@ -692,6 +692,11 @@ function preprocessArgs!(d::KW)
|
||||
end
|
||||
delete!(d, :fill)
|
||||
|
||||
# handle series annotations
|
||||
if haskey(d, :series_annotations)
|
||||
d[:series_annotations] = series_annotations(wraptuple(d[:series_annotations])...)
|
||||
end
|
||||
|
||||
# convert into strokes and brushes
|
||||
|
||||
if haskey(d, :arrow)
|
||||
|
||||
@ -56,8 +56,8 @@ 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
|
||||
# note:
|
||||
ptsz = 1.5sz * pt
|
||||
width = 0.5lablen * ptsz
|
||||
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
|
||||
|
||||
@ -269,12 +269,22 @@ end
|
||||
# ---------------------------------------------------------
|
||||
|
||||
# draw ONE Shape
|
||||
# function gr_draw_marker(xi, yi, msize, shape::Shape)
|
||||
# sx, sy = shape_coords(shape)
|
||||
# GR.selntran(0)
|
||||
# xi, yi = GR.wctondc(xi, yi)
|
||||
# GR.fillarea(xi + sx * 0.0015msize,
|
||||
# yi + sy * 0.0015msize)
|
||||
# GR.selntran(1)
|
||||
# end
|
||||
function gr_draw_marker(xi, yi, msize, shape::Shape)
|
||||
sx, sy = shape_coords(shape)
|
||||
# convert to ndc coords (percentages of window)
|
||||
GR.selntran(0)
|
||||
xi, yi = GR.wctondc(xi, yi)
|
||||
GR.fillarea(xi + sx * 0.0015msize,
|
||||
yi + sy * 0.0015msize)
|
||||
ms_ndc_x, ms_ndc_y = gr_pixels_to_ndc(msize, msize)
|
||||
GR.fillarea(xi .+ sx .* ms_ndc_x,
|
||||
yi .+ sy .* ms_ndc_y)
|
||||
GR.selntran(1)
|
||||
end
|
||||
|
||||
@ -288,10 +298,11 @@ end
|
||||
|
||||
# draw the markers, one at a time
|
||||
function gr_draw_markers(series::Series, x, y, msize, mz)
|
||||
shape = series[:markershape]
|
||||
if shape != :none
|
||||
shapes = series[:markershape]
|
||||
if shapes != :none
|
||||
for i=1:length(x)
|
||||
msi = cycle(msize, i)
|
||||
shape = cycle(shapes, i)
|
||||
cfunc = isa(shape, Shape) ? gr_set_fillcolor : gr_set_markercolor
|
||||
cfuncind = isa(shape, Shape) ? GR.setfillcolorind : GR.setmarkercolorind
|
||||
|
||||
@ -371,6 +382,9 @@ end
|
||||
# values are [xmin, xmax, ymin, ymax]. they range [0,1].
|
||||
const viewport_plotarea = zeros(4)
|
||||
|
||||
# the size of the current plot in pixels
|
||||
const gr_plot_size = zeros(2)
|
||||
|
||||
function gr_viewport_from_bbox(bb::BoundingBox, w, h, viewport_canvas)
|
||||
viewport = zeros(4)
|
||||
viewport[1] = viewport_canvas[2] * (left(bb) / w)
|
||||
@ -425,6 +439,13 @@ gr_view_ycenter() = 0.5 * (viewport_plotarea[3] + viewport_plotarea[4])
|
||||
gr_view_xdiff() = viewport_plotarea[2] - viewport_plotarea[1]
|
||||
gr_view_ydiff() = viewport_plotarea[4] - viewport_plotarea[3]
|
||||
|
||||
function gr_pixels_to_ndc(x_pixels, y_pixels)
|
||||
w,h = gr_plot_size
|
||||
totx = w * gr_view_xdiff()
|
||||
toty = h * gr_view_ydiff()
|
||||
x_pixels / totx, y_pixels / toty
|
||||
end
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------
|
||||
|
||||
@ -452,6 +473,7 @@ function gr_display(plt::Plot)
|
||||
# compute the viewport_canvas, normalized to the larger dimension
|
||||
viewport_canvas = Float64[0,1,0,1]
|
||||
w, h = plt[:size]
|
||||
gr_plot_size[:] = [w, h]
|
||||
if w > h
|
||||
ratio = float(h) / w
|
||||
msize = display_width_ratio * w
|
||||
@ -737,6 +759,10 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
x, y, z = series[:x], series[:y], series[:z]
|
||||
frng = series[:fillrange]
|
||||
|
||||
# add custom frame shapes to markershape?
|
||||
series_annotations_shapes!(series)
|
||||
# -------------------------------------------------------
|
||||
|
||||
# recompute data
|
||||
if typeof(z) <: Surface
|
||||
# if st == :heatmap
|
||||
@ -937,6 +963,53 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
GR.drawimage(xmin, xmax, ymax, ymin, w, h, rgba)
|
||||
end
|
||||
|
||||
# if anns != nothing
|
||||
# TODO handle series annotations
|
||||
# TODO: this should be moved with SeriesAnnotations... iterate like:
|
||||
# for (xi,yi,str,shape) in eachann(anns, sp) ... end
|
||||
# or maybe
|
||||
# anns.sp = sp
|
||||
# for (xi,yi,str,shape) in anns ... end
|
||||
# TODO: maybe scrap all of this and do some preprocessing to overwrite the marker shape with
|
||||
# a vector of the computed shapes?? then marker_z, etc will still work
|
||||
# @assert !is3d(sp)
|
||||
# shapefillcolor = plot_color(ann.shapefill.color, ann.shapefill.alpha)
|
||||
# shapestrokecolor = plot_color(ann.shapestroke.color, ann.shapestroke.alpha)
|
||||
# for i=1:length(y)
|
||||
# xi = cycle(x,i)
|
||||
# yi = cycle(y,i)
|
||||
# # @show anns.strs typeof(anns.strs)
|
||||
# str = cycle(anns.strs,i)
|
||||
|
||||
# if !isnull(anns.baseshape)
|
||||
# # get the width and height of the string (in mm)
|
||||
# sw, sh = text_size(str, anns.font.pointsize)
|
||||
#
|
||||
# # how much to scale the base shape?
|
||||
# xscale = 0.5 * resolve_mixed(MixedMeasures(0, 0, sw), sp, :x)
|
||||
# yscale = 0.5 * resolve_mixed(MixedMeasures(0, 0, sh), sp, :y)
|
||||
#
|
||||
# # get the shape for this x/y/str
|
||||
# shape = scale(get(anns.baseshape), xscale, yscale)
|
||||
# translate!(shape, xi, yi)
|
||||
#
|
||||
# # draw the interior
|
||||
# gr_set_fill(shapefillcolor)
|
||||
# GR.fillarea(shape_coords(shape)...)
|
||||
#
|
||||
# # draw the shapes
|
||||
# gr_set_line(anns.shapestroke.width, anns.shapestroke.style, shapestrokecolor)
|
||||
# GR.polyline(shape_coords(shape)...)
|
||||
# end
|
||||
|
||||
# this is all we need to add the series_annotations text
|
||||
anns = series[:series_annotations]
|
||||
for (xi,yi,str) in EachAnn(anns, x, y)
|
||||
gr_set_font(anns.font)
|
||||
gr_text(GR.wctondc(xi, yi)..., str)
|
||||
end
|
||||
# end
|
||||
|
||||
GR.restorestate()
|
||||
end
|
||||
|
||||
@ -1021,55 +1094,14 @@ function gr_display(sp::Subplot{GRBackend}, w, h, viewport_canvas)
|
||||
end
|
||||
end
|
||||
for ann in sp[:annotations]
|
||||
if isa(ann, SeriesAnnotations)
|
||||
# TODO handle series annotations
|
||||
# TODO: this should be moved with SeriesAnnotations... iterate like:
|
||||
# for (xi,yi,str,shape) in eachann(anns, sp) ... end
|
||||
# or maybe
|
||||
# anns.sp = sp
|
||||
# for (xi,yi,str,shape) in anns ... end
|
||||
@assert !is3d(sp)
|
||||
shapefillcolor = plot_color(ann.shapefill.color, ann.shapefill.alpha)
|
||||
shapestrokecolor = plot_color(ann.shapestroke.color, ann.shapestroke.alpha)
|
||||
for i=1:length(ann.y)
|
||||
xi = cycle(ann.x,i)
|
||||
yi = cycle(ann.y,i)
|
||||
str = cycle(ann.strs,i)
|
||||
|
||||
if !isnull(ann.baseshape)
|
||||
# get the width and height of the string (in mm)
|
||||
sw, sh = text_size(str, ann.font.pointsize)
|
||||
|
||||
# how much to scale the base shape?
|
||||
xscale = 0.5 * resolve_mixed(MixedMeasures(0, 0, sw), sp, :x)
|
||||
yscale = 0.5 * resolve_mixed(MixedMeasures(0, 0, sh), sp, :y)
|
||||
|
||||
# get the shape for this x/y/str
|
||||
shape = scale(get(ann.baseshape), xscale, yscale)
|
||||
translate!(shape, xi, yi)
|
||||
|
||||
# draw the interior
|
||||
gr_set_fill(shapefillcolor)
|
||||
GR.fillarea(shape_coords(shape)...)
|
||||
|
||||
# draw the shapes
|
||||
gr_set_line(ann.shapestroke.width, ann.shapestroke.style, shapestrokecolor)
|
||||
GR.polyline(shape_coords(shape)...)
|
||||
end
|
||||
|
||||
gr_set_font(ann.font)
|
||||
gr_text(GR.wctondc(xi, yi)..., str)
|
||||
end
|
||||
x, y, val = ann
|
||||
x, y = if is3d(sp)
|
||||
# GR.wc3towc(x, y, z)
|
||||
else
|
||||
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)
|
||||
GR.wctondc(x, y)
|
||||
end
|
||||
gr_set_font(val.font)
|
||||
gr_text(x, y, val.str)
|
||||
end
|
||||
GR.restorestate()
|
||||
end
|
||||
|
||||
@ -445,6 +445,9 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
error("Only numbers and vectors are supported with levels keyword")
|
||||
end
|
||||
|
||||
# add custom frame shapes to markershape?
|
||||
series_annotations_shapes!(series, :xy)
|
||||
|
||||
# for each plotting command, optionally build and add a series handle to the list
|
||||
|
||||
# line plot
|
||||
@ -560,16 +563,46 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
else
|
||||
xyargs
|
||||
end
|
||||
handle = ax[:scatter](xyargs...;
|
||||
label = series[:label],
|
||||
zorder = series[:series_plotindex] + 0.5,
|
||||
marker = py_marker(series[:markershape]),
|
||||
s = py_dpi_scale(plt, series[:markersize] .^ 2),
|
||||
edgecolors = py_markerstrokecolor(series),
|
||||
linewidths = py_dpi_scale(plt, series[:markerstrokewidth]),
|
||||
extrakw...
|
||||
)
|
||||
push!(handles, handle)
|
||||
|
||||
if isa(series[:markershape], AbstractVector{Shape})
|
||||
# this section will create one scatter per data point to accomodate the
|
||||
# vector of shapes
|
||||
handle = []
|
||||
x,y = xyargs
|
||||
shapes = series[:markershape]
|
||||
msc = py_markerstrokecolor(series)
|
||||
lw = py_dpi_scale(plt, series[:markerstrokewidth])
|
||||
for i=1:length(y)
|
||||
extrakw[:c] = if series[:marker_z] == nothing
|
||||
py_color_fix(py_color(cycle(series[:markercolor],i)), x)
|
||||
else
|
||||
extrakw[:c]
|
||||
end
|
||||
|
||||
push!(handle, ax[:scatter](cycle(x,i), cycle(y,i);
|
||||
label = series[:label],
|
||||
zorder = series[:series_plotindex] + 0.5,
|
||||
marker = py_marker(cycle(shapes,i)),
|
||||
s = py_dpi_scale(plt, cycle(series[:markersize],i) .^ 2),
|
||||
edgecolors = msc,
|
||||
linewidths = lw,
|
||||
extrakw...
|
||||
))
|
||||
end
|
||||
push!(handles, handle)
|
||||
else
|
||||
# do a normal scatter plot
|
||||
handle = ax[:scatter](xyargs...;
|
||||
label = series[:label],
|
||||
zorder = series[:series_plotindex] + 0.5,
|
||||
marker = py_marker(series[:markershape]),
|
||||
s = py_dpi_scale(plt, series[:markersize] .^ 2),
|
||||
edgecolors = py_markerstrokecolor(series),
|
||||
linewidths = py_dpi_scale(plt, series[:markerstrokewidth]),
|
||||
extrakw...
|
||||
)
|
||||
push!(handles, handle)
|
||||
end
|
||||
end
|
||||
|
||||
if st == :hexbin
|
||||
@ -849,6 +882,12 @@ function py_add_series(plt::Plot{PyPlotBackend}, series::Series)
|
||||
)
|
||||
push!(handles, handle)
|
||||
end
|
||||
|
||||
# this is all we need to add the series_annotations text
|
||||
anns = series[:series_annotations]
|
||||
for (xi,yi,str) in EachAnn(anns, x, y)
|
||||
py_add_annotations(sp, xi, yi, PlotText(str, anns.font))
|
||||
end
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
@ -23,6 +23,7 @@ immutable Shape
|
||||
# end
|
||||
end
|
||||
Shape(verts::AVec) = Shape(unzip(verts)...)
|
||||
Shape(s::Shape) = deepcopy(s)
|
||||
|
||||
get_xs(shape::Shape) = shape.x
|
||||
get_ys(shape::Shape) = shape.y
|
||||
@ -143,6 +144,8 @@ for n in [4,5,6,7,8]
|
||||
_shapes[Symbol("star$n")] = makestar(n)
|
||||
end
|
||||
|
||||
Shape(k::Symbol) = deepcopy(_shapes[k])
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
|
||||
@ -307,7 +310,7 @@ function text(str, args...)
|
||||
PlotText(string(str), font(args...))
|
||||
end
|
||||
|
||||
|
||||
Base.length(t::PlotText) = length(t.str)
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
@ -386,44 +389,104 @@ end
|
||||
type SeriesAnnotations
|
||||
strs::AbstractVector # the labels/names
|
||||
font::Font
|
||||
baseshape::Nullable{Shape}
|
||||
shapefill::Brush
|
||||
shapestroke::Stroke
|
||||
x
|
||||
y
|
||||
baseshape::Nullable
|
||||
# shapefill::Brush
|
||||
# shapestroke::Stroke
|
||||
# x
|
||||
# y
|
||||
end
|
||||
function series_annotations(strs::AbstractVector, args...)
|
||||
fnt = font()
|
||||
shp = Nullable{Shape}()
|
||||
br = brush(:steelblue)
|
||||
stk = stroke()
|
||||
α = nothing
|
||||
shp = Nullable{Any}()
|
||||
scalefactor = 1
|
||||
# br = brush(:steelblue)
|
||||
# stk = stroke()
|
||||
# α = nothing
|
||||
for arg in args
|
||||
if isa(arg, Shape)
|
||||
shp = Nullable{Shape}(arg)
|
||||
elseif isa(arg, Brush)
|
||||
brush = arg
|
||||
elseif isa(arg, Stroke)
|
||||
stk = arg
|
||||
if isa(arg, Shape) || (isa(arg, AbstractVector) && eltype(arg) == Shape)
|
||||
shp = Nullable(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)
|
||||
shp = _shapes[arg]
|
||||
elseif allAlphas(arg)
|
||||
α = arg
|
||||
# elseif allAlphas(arg)
|
||||
# α = arg
|
||||
elseif isa(arg, Number)
|
||||
scalefactor = arg
|
||||
else
|
||||
warn("Unused SeriesAnnotations arg: $arg ($(typeof(arg)))")
|
||||
end
|
||||
end
|
||||
if α != nothing
|
||||
br.alpha = α
|
||||
stk.alpha = α
|
||||
if scalefactor != 1
|
||||
for s in get(shp)
|
||||
scale!(s, scalefactor, scalefactor, (0,0))
|
||||
end
|
||||
end
|
||||
# if α != nothing
|
||||
# br.alpha = α
|
||||
# stk.alpha = α
|
||||
# end
|
||||
# note: x/y coords are added later
|
||||
SeriesAnnotations(strs, fnt, shp, br, stk, nothing, nothing)
|
||||
SeriesAnnotations(strs, fnt, shp)
|
||||
end
|
||||
series_annotations(anns::SeriesAnnotations) = anns
|
||||
series_annotations(::Void) = nothing
|
||||
|
||||
function series_annotations_shapes!(series::Series, scaletype::Symbol = :pixels)
|
||||
anns = series[:series_annotations]
|
||||
if anns != nothing && !isnull(anns.baseshape)
|
||||
# x = series[:x]
|
||||
# y = series[:y]
|
||||
# we should use baseshape to overwrite the markershape attribute
|
||||
# with a list of custom shapes for each
|
||||
msize = Float64[]
|
||||
shapes = Shape[begin
|
||||
# xi = cycle(x,i)
|
||||
# yi = cycle(y,i)
|
||||
str = cycle(anns.strs,i)
|
||||
|
||||
# get the width and height of the string (in mm)
|
||||
sw, sh = text_size(str, anns.font.pointsize)
|
||||
|
||||
# how much to scale the base shape?
|
||||
# note: it's a rough assumption that the shape fills the unit box [-1,-1,1,1],
|
||||
# so we scale the length-2 shape by 1/2 the total length
|
||||
# if scaletype == :pixels
|
||||
scalar = (backend() == PyPlotBackend() ? 1.7 : 1.0)
|
||||
xscale = 0.5to_pixels(sw) * scalar
|
||||
yscale = 0.55to_pixels(sh) * scalar
|
||||
# else
|
||||
# sp = series[:subplot]
|
||||
# xscale = 0.5 * resolve_mixed(MixedMeasures(0, 0, sw), sp, :x)
|
||||
# yscale = 0.5 * resolve_mixed(MixedMeasures(0, 0, sh), sp, :y)
|
||||
# end
|
||||
maxscale = max(xscale, yscale)
|
||||
push!(msize, maxscale)
|
||||
|
||||
# get the shape for this x/y/str
|
||||
# @show get(anns.baseshape) xscale,yscale
|
||||
baseshape = cycle(get(anns.baseshape),i)
|
||||
shape = scale(baseshape, xscale/maxscale, yscale/maxscale, (0,0))
|
||||
# @show shape
|
||||
end for i=1:length(anns.strs)]
|
||||
series[:markershape] = shapes
|
||||
series[:markersize] = msize #1 # the scaling is handled in the shapes
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
type EachAnn
|
||||
anns
|
||||
x
|
||||
y
|
||||
end
|
||||
Base.start(ea::EachAnn) = 1
|
||||
Base.done(ea::EachAnn, i) = ea.anns == nothing || isempty(ea.anns.strs) || i > length(ea.y)
|
||||
Base.next(ea::EachAnn, i) = ((cycle(ea.x,i), cycle(ea.y,i), cycle(ea.anns.strs,i)), i+1)
|
||||
|
||||
annotations(::Void) = []
|
||||
annotations(anns::AVec) = anns
|
||||
|
||||
@ -111,6 +111,7 @@ function resolve_mixed(mix::MixedMeasures, sp::Subplot, letter::Symbol)
|
||||
if mix.len != 0mm
|
||||
f = (letter == :x ? width : height)
|
||||
totlen = f(plotarea(sp))
|
||||
@show totlen
|
||||
pct += mix.len / totlen
|
||||
end
|
||||
if pct != 0
|
||||
|
||||
@ -333,17 +333,17 @@ 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])
|
||||
series_anns = annotations(pop!(d, :series_annotations, []))
|
||||
if isa(series_anns, SeriesAnnotations)
|
||||
series_anns.x = d[:x]
|
||||
series_anns.y = d[:y]
|
||||
elseif length(series_anns) > 0
|
||||
x, y = d[:x], d[:y]
|
||||
nx, ny, na = map(length, (x,y,series_anns))
|
||||
n = max(nx, ny, na)
|
||||
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, series_anns)
|
||||
# series_anns = annotations(pop!(d, :series_annotations, []))
|
||||
# if isa(series_anns, SeriesAnnotations)
|
||||
# series_anns.x = d[:x]
|
||||
# series_anns.y = d[:y]
|
||||
# elseif length(series_anns) > 0
|
||||
# x, y = d[:x], d[:y]
|
||||
# nx, ny, na = map(length, (x,y,series_anns))
|
||||
# n = max(nx, ny, na)
|
||||
# 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, series_anns)
|
||||
end
|
||||
|
||||
function _expand_subplot_extrema(sp::Subplot, d::KW, st::Symbol)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user