Extrema type and link_axis; link keyword, removed old link logic

This commit is contained in:
Thomas Breloff 2016-05-27 22:03:56 -04:00
parent 048c60614c
commit ce82e07dc9
7 changed files with 188 additions and 133 deletions

View File

@ -192,10 +192,10 @@ const _plot_defaults = KW(
:window_title => "Plots.jl", :window_title => "Plots.jl",
:show => false, :show => false,
:layout => 1, :layout => 1,
:link => false, :link => :none,
:linkx => false, # :linkx => false,
:linky => false, # :linky => false,
:linkfunc => nothing, # :linkfunc => nothing,
:overwrite_figure => true, :overwrite_figure => true,
:html_output_format => :auto, :html_output_format => :auto,
) )
@ -251,6 +251,7 @@ const _suppress_warnings = Set{Symbol}([
:subplot, :subplot,
:subplot_index, :subplot_index,
:series_plotindex, :series_plotindex,
:link,
]) ])
# add defaults for the letter versions # add defaults for the letter versions
@ -391,8 +392,8 @@ add_aliases(:size, :windowsize, :wsize)
add_aliases(:window_title, :windowtitle, :wtitle) add_aliases(:window_title, :windowtitle, :wtitle)
add_aliases(:show, :gui, :display) add_aliases(:show, :gui, :display)
add_aliases(:color_palette, :palette) add_aliases(:color_palette, :palette)
add_aliases(:linkx, :xlink) # add_aliases(:linkx, :xlink)
add_aliases(:linky, :ylink) # add_aliases(:linky, :ylink)
add_aliases(:overwrite_figure, :clf, :clearfig, :overwrite, :reuse) add_aliases(:overwrite_figure, :clf, :clearfig, :overwrite, :reuse)
add_aliases(:xerror, :xerr, :xerrorbar) add_aliases(:xerror, :xerr, :xerrorbar)
add_aliases(:yerror, :yerr, :yerrorbar, :err, :errorbar) add_aliases(:yerror, :yerr, :yerrorbar, :err, :errorbar)
@ -656,21 +657,21 @@ function preprocessArgs!(d::KW)
d[:colorbar] = convertLegendValue(d[:colorbar]) d[:colorbar] = convertLegendValue(d[:colorbar])
end end
# handle subplot links # # handle subplot links
if haskey(d, :link) # if haskey(d, :link)
l = d[:link] # l = d[:link]
if isa(l, Bool) # if isa(l, Bool)
d[:linkx] = l # d[:linkx] = l
d[:linky] = l # d[:linky] = l
elseif isa(l, Function) # elseif isa(l, Function)
d[:linkx] = true # d[:linkx] = true
d[:linky] = true # d[:linky] = true
d[:linkfunc] = l # d[:linkfunc] = l
else # else
warn("Unhandled/invalid link $l. Should be a Bool or a function mapping (row,column) -> (linkx, linky), where linkx/y can be Bool or Void (nothing)") # warn("Unhandled/invalid link $l. Should be a Bool or a function mapping (row,column) -> (linkx, linky), where linkx/y can be Bool or Void (nothing)")
end # end
delete!(d, :link) # delete!(d, :link)
end # end
end end
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -904,6 +905,8 @@ function _update_subplot_args(plt::Plot, sp::Subplot, d_in::KW, subplot_index::I
color_or_match!(axis.d, :foreground_color_border, fg) color_or_match!(axis.d, :foreground_color_border, fg)
color_or_match!(axis.d, :foreground_color_guide, fg) color_or_match!(axis.d, :foreground_color_guide, fg)
color_or_match!(axis.d, :foreground_color_text, fg) color_or_match!(axis.d, :foreground_color_text, fg)
# TODO: need to handle linking here?
end end
# now we can get rid of the axis keys without a letter # now we can get rid of the axis keys without a letter

View File

@ -4,23 +4,21 @@ xaxis(args...; kw...) = Axis(:x, args...; kw...)
yaxis(args...; kw...) = Axis(:y, args...; kw...) yaxis(args...; kw...) = Axis(:y, args...; kw...)
zaxis(args...; kw...) = Axis(:z, args...; kw...) zaxis(args...; kw...) = Axis(:z, args...; kw...)
# -------------------------------------------------------------------------
function Axis(letter::Symbol, args...; kw...) function Axis(letter::Symbol, args...; kw...)
# init with values from _plot_defaults # init with values from _plot_defaults
d = KW( d = KW(
:letter => letter, :letter => letter,
:extrema => (Inf, -Inf), # :extrema => (Inf, -Inf),
:extrema => Extrema(),
:discrete_map => Dict(), # map discrete values to discrete indices :discrete_map => Dict(), # map discrete values to discrete indices
# :discrete_values => Tuple{Float64,Any}[],
# :discrete_values => [],
:continuous_values => zeros(0), :continuous_values => zeros(0),
:use_minor => false, :use_minor => false,
:show => true, # show or hide the axis? (useful for linked subplots) :show => true, # show or hide the axis? (useful for linked subplots)
) )
merge!(d, _axis_defaults) merge!(d, _axis_defaults)
d[:discrete_values] = [] d[:discrete_values] = []
# DD(d)
# @show args kw
# update the defaults # update the defaults
update!(Axis(d), args...; kw...) update!(Axis(d), args...; kw...)
@ -88,41 +86,52 @@ function update!(axis::Axis, args...; kw...)
axis axis
end end
# -------------------------------------------------------------------------
Base.show(io::IO, a::Axis) = dumpdict(a.d, "Axis", true) Base.show(io::IO, axis::Axis) = dumpdict(axis.d, "Axis", true)
Base.getindex(a::Axis, k::Symbol) = getindex(a.d, k) Base.getindex(axis::Axis, k::Symbol) = getindex(axis.d, k)
Base.setindex!(a::Axis, v, ks::Symbol...) = setindex!(a.d, v, ks...) Base.setindex!(axis::Axis, v, ks::Symbol...) = setindex!(axis.d, v, ks...)
Base.haskey(a::Axis, k::Symbol) = haskey(a.d, k) Base.haskey(axis::Axis, k::Symbol) = haskey(axis.d, k)
Base.extrema(a::Axis) = a[:extrema] Base.extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax))
# get discrete ticks, or not # get discrete ticks, or not
function get_ticks(a::Axis) function get_ticks(axis::Axis)
ticks = a[:ticks] ticks = axis[:ticks]
dvals = a[:discrete_values] dvals = axis[:discrete_values]
if !isempty(dvals) && ticks == :auto if !isempty(dvals) && ticks == :auto
# vals, labels = unzip(dvals) axis[:continuous_values], dvals
a[:continuous_values], dvals
else else
ticks ticks
end end
end end
function expand_extrema!(a::Axis, v::Number) # -------------------------------------------------------------------------
emin, emax = a[:extrema]
a[:extrema] = (min(v, emin), max(v, emax)) function expand_extrema!(ex::Extrema, v::Number)
ex.emin = min(v, ex.emin)
ex.emax = max(v, ex.emax)
ex
end end
function expand_extrema!{MIN<:Number,MAX<:Number}(a::Axis, v::Tuple{MIN,MAX})
emin, emax = a[:extrema] function expand_extrema!(axis::Axis, v::Number)
a[:extrema] = (min(v[1], emin), max(v[2], emax)) expand_extrema!(axis[:extrema], v)
end end
function expand_extrema!{N<:Number}(a::Axis, v::AVec{N}) function expand_extrema!{MIN<:Number,MAX<:Number}(axis::Axis, v::Tuple{MIN,MAX})
if !isempty(v) ex = axis[:extrema]
emin, emax = a[:extrema] ex.emin = min(v[1], ex.emin)
a[:extrema] = (min(minimum(v), emin), max(maximum(v), emax)) ex.emax = max(v[2], ex.emax)
ex
end
function expand_extrema!{N<:Number}(axis::Axis, v::AVec{N})
ex = axis[:extrema]
for vi in v
expand_extrema!(ex, vi)
end end
a[:extrema] ex
end end
# -------------------------------------------------------------------------
# push the limits out slightly # push the limits out slightly
function widen(lmin, lmax) function widen(lmin, lmax)
span = lmax - lmin span = lmax - lmin
@ -132,7 +141,8 @@ end
# using the axis extrema and limit overrides, return the min/max value for this axis # using the axis extrema and limit overrides, return the min/max value for this axis
function axis_limits(axis::Axis, should_widen::Bool = true) function axis_limits(axis::Axis, should_widen::Bool = true)
amin, amax = axis[:extrema] ex = axis[:extrema]
amin, amax = ex.emin, ex.emax
lims = axis[:lims] lims = axis[:lims]
if isa(lims, Tuple) && length(lims) == 2 if isa(lims, Tuple) && length(lims) == 2
if isfinite(lims[1]) if isfinite(lims[1])
@ -152,58 +162,62 @@ function axis_limits(axis::Axis, should_widen::Bool = true)
end end
end end
# these methods track the discrete values which correspond to axis continuous values (cv) # -------------------------------------------------------------------------
# these methods track the discrete (categorical) values which correspond to axis continuous values (cv)
# whenever we have discrete values, we automatically set the ticks to match. # whenever we have discrete values, we automatically set the ticks to match.
# we return (continuous_value, discrete_index) # we return (continuous_value, discrete_index)
function discrete_value!(a::Axis, dv) function discrete_value!(axis::Axis, dv)
cv_idx = get(a[:discrete_map], dv, -1) cv_idx = get(axis[:discrete_map], dv, -1)
# @show a[:discrete_map], a[:discrete_values], dv # @show axis[:discrete_map], axis[:discrete_values], dv
if cv_idx == -1 if cv_idx == -1
emin, emax = a[:extrema] ex = axis[:extrema]
cv = max(0.5, emax + 1.0) cv = max(0.5, ex.emax + 1.0)
expand_extrema!(a, cv) expand_extrema!(axis, cv)
push!(a[:discrete_values], dv) push!(axis[:discrete_values], dv)
push!(a[:continuous_values], cv) push!(axis[:continuous_values], cv)
cv_idx = length(a[:discrete_values]) cv_idx = length(axis[:discrete_values])
a[:discrete_map][dv] = cv_idx axis[:discrete_map][dv] = cv_idx
cv, cv_idx cv, cv_idx
else else
cv = a[:continuous_values][cv_idx] cv = axis[:continuous_values][cv_idx]
cv, cv_idx cv, cv_idx
end end
end end
# continuous value... just pass back with a negative index # continuous value... just pass back with axis negative index
function discrete_value!(a::Axis, cv::Number) function discrete_value!(axis::Axis, cv::Number)
cv, -1 cv, -1
end end
# add the discrete value for each item. return the continuous values and the indices # add the discrete value for each item. return the continuous values and the indices
function discrete_value!(a::Axis, v::AVec) function discrete_value!(axis::Axis, v::AVec)
n = length(v) n = length(v)
cvec = zeros(n) cvec = zeros(n)
discrete_indices = zeros(Int, n) discrete_indices = zeros(Int, n)
for i=1:n for i=1:n
cvec[i], discrete_indices[i] = discrete_value!(a, v[i]) cvec[i], discrete_indices[i] = discrete_value!(axis, v[i])
end end
cvec, discrete_indices cvec, discrete_indices
end end
# add the discrete value for each item. return the continuous values and the indices # add the discrete value for each item. return the continuous values and the indices
function discrete_value!(a::Axis, v::AMat) function discrete_value!(axis::Axis, v::AMat)
n,m = size(v) n,m = size(v)
cmat = zeros(n,m) cmat = zeros(n,m)
discrete_indices = zeros(Int, n, m) discrete_indices = zeros(Int, n, m)
for i=1:n, j=1:m for i=1:n, j=1:m
cmat[i,j], discrete_indices[i,j] = discrete_value!(a, v[i,j]) cmat[i,j], discrete_indices[i,j] = discrete_value!(axis, v[i,j])
end end
cmat, discrete_indices cmat, discrete_indices
end end
function discrete_value!(a::Axis, v::Surface) function discrete_value!(axis::Axis, v::Surface)
map(Surface, discrete_value!(a, v.surf)) map(Surface, discrete_value!(axis, v.surf))
end end
# -------------------------------------------------------------------------
function pie_labels(sp::Subplot, series::Series) function pie_labels(sp::Subplot, series::Series)
d = series.d d = series.d
if haskey(d,:x_discrete_indices) if haskey(d,:x_discrete_indices)

View File

@ -841,23 +841,23 @@ end
# ----------------------------------------------------------------- # -----------------------------------------------------------------
function set_lims!(sp::Subplot{PyPlotBackend}, axis::Axis) # function set_lims!(sp::Subplot{PyPlotBackend}, axis::Axis)
lims = copy(axis[:extrema]) # lims = copy(axis[:extrema])
lims_override = axis[:lims] # lims_override = axis[:lims]
if lims_override != :auto # if lims_override != :auto
if isfinite(lims_override[1]) # if isfinite(lims_override[1])
lims[1] = lims_override[1] # lims[1] = lims_override[1]
end # end
if isfinite(lims_override[2]) # if isfinite(lims_override[2])
lims[2] = lims_override[2] # lims[2] = lims_override[2]
end # end
end # end
#
# TODO: check for polar, do set_tlim/set_rlim instead # # TODO: check for polar, do set_tlim/set_rlim instead
#
# pyplot's set_xlim (or y/z) method: # # pyplot's set_xlim (or y/z) method:
sp.o[symbol(:set_, axis[:letter], :lim)](lims...) # sp.o[symbol(:set_, axis[:letter], :lim)](lims...)
end # end
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
@ -867,10 +867,10 @@ get_axis(sp::Subplot, letter::Symbol) = sp.attr[symbol(letter, :axis)]
function update_limits!(sp::Subplot{PyPlotBackend}, series::Series, letters) function update_limits!(sp::Subplot{PyPlotBackend}, series::Series, letters)
for letter in letters for letter in letters
axis = get_axis(sp, letter) # axis = get_axis(sp, letter)
expand_extrema!(axis, series.d[letter]) # expand_extrema!(axis, series.d[letter])
# set_lims!(sp, axis) # set_lims!(sp, axis)
setPyPlotLims(sp.o, axis) setPyPlotLims(sp.o, sp.attr[symbol(letter, :axis)])
end end
end end

View File

@ -438,6 +438,7 @@ function build_layout(layout::GridLayout, n::Integer)
end end
i >= n && break # only add n subplots i >= n && break # only add n subplots
end end
layout, subplots, spmap layout, subplots, spmap
end end
@ -478,7 +479,7 @@ end
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------
# @layout macro # @layout macro
function add_layout_pct!(kw::KW, v::Expr, idx::Integer) function add_layout_pct!(kw::KW, v::Expr, idx::Integer, nidx::Integer)
# dump(v) # dump(v)
# something like {0.2w}? # something like {0.2w}?
if v.head == :call && v.args[1] == :* if v.head == :call && v.args[1] == :*
@ -490,7 +491,9 @@ function add_layout_pct!(kw::KW, v::Expr, idx::Integer)
elseif units == :w elseif units == :w
return kw[:w] = num*pct return kw[:w] = num*pct
elseif units in (:pct, :px, :mm, :cm, :inch) elseif units in (:pct, :px, :mm, :cm, :inch)
return kw[idx == 1 ? :w : :h] = v idx == 1 && (kw[:w] = v)
(idx == 2 || nidx == 1) && (kw[:h] = v)
# return kw[idx == 1 ? :w : :h] = v
end end
end end
end end
@ -498,7 +501,9 @@ function add_layout_pct!(kw::KW, v::Expr, idx::Integer)
end end
function add_layout_pct!(kw::KW, v::Number, idx::Integer) function add_layout_pct!(kw::KW, v::Number, idx::Integer)
kw[idx == 1 ? :w : :h] = v*pct # kw[idx == 1 ? :w : :h] = v*pct
idx == 1 && (kw[:w] = v*pct)
(idx == 2 || nidx == 1) && (kw[:h] = v*pct)
end end
function create_grid(expr::Expr) function create_grid(expr::Expr)
@ -522,7 +527,7 @@ function create_grid(expr::Expr)
s = expr.args[1] s = expr.args[1]
kw = KW() kw = KW()
for (i,arg) in enumerate(expr.args[2:end]) for (i,arg) in enumerate(expr.args[2:end])
add_layout_pct!(kw, arg, i) add_layout_pct!(kw, arg, i, length(expr.args)-1)
end end
# @show kw # @show kw
:(EmptyLayout(label = $(QuoteNode(s)), width = $(get(kw, :w, QuoteNode(:auto))), height = $(get(kw, :h, QuoteNode(:auto))))) :(EmptyLayout(label = $(QuoteNode(s)), width = $(get(kw, :w, QuoteNode(:auto))), height = $(get(kw, :h, QuoteNode(:auto)))))
@ -540,3 +545,50 @@ end
macro layout(mat::Expr) macro layout(mat::Expr)
create_grid(mat) create_grid(mat)
end end
# -------------------------------------------------------------------------
# make all reference the same axis extrema/values
function link_axes!(axes::Axis...)
a1 = axes[1]
for i=2:length(axes)
a2 = axes[i]
for k in (:extrema, :discrete_values, :continuous_values, :discrete_map)
a2[k] = a1[k]
end
end
end
# for some vector or matrix of layouts, filter only the Subplots and link those axes
function link_axes!(a::AbstractArray{AbstractLayout}, axissym::Symbol)
subplots = filter(l -> isa(l, Subplot), a)
axes = [sp.attr[axissym] for sp in subplots]
link_axes!(axes...)
end
# don't do anything for most layout types
function link_axes!(l::AbstractLayout, link::Symbol)
end
# process a GridLayout, recursively linking axes according to the link symbol
function link_axes!(layout::GridLayout, link::Symbol)
nr, nc = size(layout)
if link in (:x, :both)
for c=1:nc
link_axes!(layout.grid[:,c], :xaxis)
end
end
if link in (:y, :both)
for r=1:nr
link_axes!(layout.grid[r,:], :yaxis)
end
end
if link == :all
link_axes!(layout.grid, :xaxis)
link_axes!(layout.grid, :yaxis)
end
for l in layout.grid
link_axes!(l, link)
end
end

View File

@ -309,19 +309,17 @@ function _plot!(plt::Plot, d::KW, args...)
# grab the first in line to be processed and pass it through apply_recipe # grab the first in line to be processed and pass it through apply_recipe
# to generate a list of RecipeData objects (data + attributes) # to generate a list of RecipeData objects (data + attributes)
next_series = shift!(still_to_process) next_series = shift!(still_to_process)
series_list = RecipesBase.apply_recipe(next_series.d, next_series.args...) for recipedata in RecipesBase.apply_recipe(next_series.d, next_series.args...)
for series in series_list
# series should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes # recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes
if !(typeof(series) <: RecipeData) if !(typeof(recipedata) <: RecipeData)
error("Inputs couldn't be processed... expected RecipeData but got: $series") error("Inputs couldn't be processed... expected RecipeData but got: $recipedata")
end end
# @show series if isempty(recipedata.args)
if isempty(series.args)
# when the arg tuple is empty, that means there's nothing left to recursively # when the arg tuple is empty, that means there's nothing left to recursively
# process... finish up and add to the kw_list # process... finish up and add to the kw_list
kw = series.d kw = recipedata.d
_add_markershape(kw) _add_markershape(kw)
# if there was a grouping, filter the data here # if there was a grouping, filter the data here
@ -347,8 +345,8 @@ function _plot!(plt::Plot, d::KW, args...)
warnOnUnsupportedScales(plt.backend, kw) warnOnUnsupportedScales(plt.backend, kw)
push!(kw_list, kw) push!(kw_list, kw)
# handle error bars by creating new series data... these will have # handle error bars by creating new recipedata data... these will have
# the same series index as the series they are copied from # the same recipedata index as the recipedata they are copied from
for esym in (:xerror, :yerror) for esym in (:xerror, :yerror)
if get(d, esym, nothing) != nothing if get(d, esym, nothing) != nothing
# we make a copy of the KW and apply an errorbar recipe # we make a copy of the KW and apply an errorbar recipe
@ -360,7 +358,7 @@ function _plot!(plt::Plot, d::KW, args...)
else else
# args are non-empty, so there's still processing to do... add it back to the queue # args are non-empty, so there's still processing to do... add it back to the queue
push!(still_to_process, series) push!(still_to_process, recipedata)
end end
end end
end end
@ -398,7 +396,11 @@ function _plot!(plt::Plot, d::KW, args...)
_update_subplot_args(plt, sp, d, idx) _update_subplot_args(plt, sp, d, idx)
end end
# !!! note: at this point, kw_list is fully decomposed into individual series... one KW per series !!! # do we need to link any axes together?
link_axes!(plt.layout, plt.attr[:link])
# !!! note: At this point, kw_list is fully decomposed into individual series... one KW per series. !!!
# !!! The next step is to recursively apply series recipes until the backend supports that series type !!!
# this is it folks! # this is it folks!
# TODO: we probably shouldn't use i for tracking series index, but rather explicitly track it in recipes # TODO: we probably shouldn't use i for tracking series index, but rather explicitly track it in recipes

View File

@ -27,6 +27,12 @@ type Axis
d::KW d::KW
end end
type Extrema
emin::Float64
emax::Float64
end
Extrema() = Extrema(Inf, -Inf)
# ----------------------------------------------------------- # -----------------------------------------------------------
# a single subplot # a single subplot

View File

@ -483,42 +483,20 @@ end
function setxy!{X,Y}(plt::Plot, xy::Tuple{X,Y}, i::Integer) function setxy!{X,Y}(plt::Plot, xy::Tuple{X,Y}, i::Integer)
series = plt.series_list[i] series = plt.series_list[i]
series.d[:x], series.d[:y] = xy series.d[:x], series.d[:y] = xy
sp = series.d[:subplot]
expand_extrema!(sp.attr[:xaxis], xy[1])
expand_extrema!(sp.attr[:yaxis], xy[2])
_series_updated(plt, series) _series_updated(plt, series)
end end
function setxyz!{X,Y,Z}(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) function setxyz!{X,Y,Z}(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer)
series = plt.series_list[i] series = plt.series_list[i]
series.d[:x], series.d[:y], series.d[:z] = xyz series.d[:x], series.d[:y], series.d[:z] = xyz
sp = series.d[:subplot]
expand_extrema!(sp.attr[:xaxis], xy[1])
expand_extrema!(sp.attr[:yaxis], xy[2])
expand_extrema!(sp.attr[:zaxis], xy[3])
_series_updated(plt, series) _series_updated(plt, series)
end end
#
#
# function setxy!{X,Y}(plt::Plot{PyPlotBackend}, xy::Tuple{X,Y}, i::Integer)
# series = plt.series_list[i]
# d = series.d
# d[:x], d[:y] = xy
# for handle in d[:serieshandle]
# try
# handle[:set_data](xy...)
# catch
# handle[:set_offsets](hcat(xy...))
# end
# end
# update_limits!(d[:subplot], series, (:x,:y))
# plt
# end
#
#
# function setxyz!{X,Y,Z}(plt::Plot{PyPlotBackend}, xyz::Tuple{X,Y,Z}, i::Integer)
# series = plt.series_list[i]
# d = series.d
# d[:x], d[:y], d[:z] = xyz
# for handle in d[:serieshandle]
# handle[:set_data](d[:x], d[:y])
# handle[:set_3d_properties](d[:z])
# end
# update_limits!(d[:subplot], series, (:x,:y,:z))
# plt
# end
# ------------------------------------------------------- # -------------------------------------------------------
# indexing notation # indexing notation