diff --git a/src/args.jl b/src/args.jl index e8922c1e..d93e3f30 100644 --- a/src/args.jl +++ b/src/args.jl @@ -192,10 +192,10 @@ const _plot_defaults = KW( :window_title => "Plots.jl", :show => false, :layout => 1, - :link => false, - :linkx => false, - :linky => false, - :linkfunc => nothing, + :link => :none, + # :linkx => false, + # :linky => false, + # :linkfunc => nothing, :overwrite_figure => true, :html_output_format => :auto, ) @@ -251,6 +251,7 @@ const _suppress_warnings = Set{Symbol}([ :subplot, :subplot_index, :series_plotindex, + :link, ]) # add defaults for the letter versions @@ -391,8 +392,8 @@ add_aliases(:size, :windowsize, :wsize) add_aliases(:window_title, :windowtitle, :wtitle) add_aliases(:show, :gui, :display) add_aliases(:color_palette, :palette) -add_aliases(:linkx, :xlink) -add_aliases(:linky, :ylink) +# add_aliases(:linkx, :xlink) +# add_aliases(:linky, :ylink) add_aliases(:overwrite_figure, :clf, :clearfig, :overwrite, :reuse) add_aliases(:xerror, :xerr, :xerrorbar) add_aliases(:yerror, :yerr, :yerrorbar, :err, :errorbar) @@ -656,21 +657,21 @@ function preprocessArgs!(d::KW) d[:colorbar] = convertLegendValue(d[:colorbar]) end - # handle subplot links - if haskey(d, :link) - l = d[:link] - if isa(l, Bool) - d[:linkx] = l - d[:linky] = l - elseif isa(l, Function) - d[:linkx] = true - d[:linky] = true - d[:linkfunc] = l - 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)") - end - delete!(d, :link) - end + # # handle subplot links + # if haskey(d, :link) + # l = d[:link] + # if isa(l, Bool) + # d[:linkx] = l + # d[:linky] = l + # elseif isa(l, Function) + # d[:linkx] = true + # d[:linky] = true + # d[:linkfunc] = l + # 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)") + # end + # delete!(d, :link) + # 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_guide, fg) color_or_match!(axis.d, :foreground_color_text, fg) + + # TODO: need to handle linking here? end # now we can get rid of the axis keys without a letter diff --git a/src/axes.jl b/src/axes.jl index efe98a05..4b92fd06 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -4,23 +4,21 @@ xaxis(args...; kw...) = Axis(:x, args...; kw...) yaxis(args...; kw...) = Axis(:y, args...; kw...) zaxis(args...; kw...) = Axis(:z, args...; kw...) +# ------------------------------------------------------------------------- function Axis(letter::Symbol, args...; kw...) # init with values from _plot_defaults d = KW( :letter => letter, - :extrema => (Inf, -Inf), + # :extrema => (Inf, -Inf), + :extrema => Extrema(), :discrete_map => Dict(), # map discrete values to discrete indices - # :discrete_values => Tuple{Float64,Any}[], - # :discrete_values => [], :continuous_values => zeros(0), :use_minor => false, :show => true, # show or hide the axis? (useful for linked subplots) ) merge!(d, _axis_defaults) d[:discrete_values] = [] - # DD(d) - # @show args kw # update the defaults update!(Axis(d), args...; kw...) @@ -88,41 +86,52 @@ function update!(axis::Axis, args...; kw...) axis end +# ------------------------------------------------------------------------- -Base.show(io::IO, a::Axis) = dumpdict(a.d, "Axis", true) -Base.getindex(a::Axis, k::Symbol) = getindex(a.d, k) -Base.setindex!(a::Axis, v, ks::Symbol...) = setindex!(a.d, v, ks...) -Base.haskey(a::Axis, k::Symbol) = haskey(a.d, k) -Base.extrema(a::Axis) = a[:extrema] +Base.show(io::IO, axis::Axis) = dumpdict(axis.d, "Axis", true) +Base.getindex(axis::Axis, k::Symbol) = getindex(axis.d, k) +Base.setindex!(axis::Axis, v, ks::Symbol...) = setindex!(axis.d, v, ks...) +Base.haskey(axis::Axis, k::Symbol) = haskey(axis.d, k) +Base.extrema(axis::Axis) = (ex = axis[:extrema]; (ex.emin, ex.emax)) # get discrete ticks, or not -function get_ticks(a::Axis) - ticks = a[:ticks] - dvals = a[:discrete_values] +function get_ticks(axis::Axis) + ticks = axis[:ticks] + dvals = axis[:discrete_values] if !isempty(dvals) && ticks == :auto - # vals, labels = unzip(dvals) - a[:continuous_values], dvals + axis[:continuous_values], dvals else ticks 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 -function expand_extrema!{MIN<:Number,MAX<:Number}(a::Axis, v::Tuple{MIN,MAX}) - emin, emax = a[:extrema] - a[:extrema] = (min(v[1], emin), max(v[2], emax)) + +function expand_extrema!(axis::Axis, v::Number) + expand_extrema!(axis[:extrema], v) end -function expand_extrema!{N<:Number}(a::Axis, v::AVec{N}) - if !isempty(v) - emin, emax = a[:extrema] - a[:extrema] = (min(minimum(v), emin), max(maximum(v), emax)) +function expand_extrema!{MIN<:Number,MAX<:Number}(axis::Axis, v::Tuple{MIN,MAX}) + ex = axis[:extrema] + ex.emin = min(v[1], ex.emin) + 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 - a[:extrema] + ex end +# ------------------------------------------------------------------------- + # push the limits out slightly function widen(lmin, lmax) span = lmax - lmin @@ -132,7 +141,8 @@ end # using the axis extrema and limit overrides, return the min/max value for this axis 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] if isa(lims, Tuple) && length(lims) == 2 if isfinite(lims[1]) @@ -152,58 +162,62 @@ function axis_limits(axis::Axis, should_widen::Bool = true) 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. # we return (continuous_value, discrete_index) -function discrete_value!(a::Axis, dv) - cv_idx = get(a[:discrete_map], dv, -1) - # @show a[:discrete_map], a[:discrete_values], dv +function discrete_value!(axis::Axis, dv) + cv_idx = get(axis[:discrete_map], dv, -1) + # @show axis[:discrete_map], axis[:discrete_values], dv if cv_idx == -1 - emin, emax = a[:extrema] - cv = max(0.5, emax + 1.0) - expand_extrema!(a, cv) - push!(a[:discrete_values], dv) - push!(a[:continuous_values], cv) - cv_idx = length(a[:discrete_values]) - a[:discrete_map][dv] = cv_idx + ex = axis[:extrema] + cv = max(0.5, ex.emax + 1.0) + expand_extrema!(axis, cv) + push!(axis[:discrete_values], dv) + push!(axis[:continuous_values], cv) + cv_idx = length(axis[:discrete_values]) + axis[:discrete_map][dv] = cv_idx cv, cv_idx else - cv = a[:continuous_values][cv_idx] + cv = axis[:continuous_values][cv_idx] cv, cv_idx end end -# continuous value... just pass back with a negative index -function discrete_value!(a::Axis, cv::Number) +# continuous value... just pass back with axis negative index +function discrete_value!(axis::Axis, cv::Number) cv, -1 end # 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) cvec = zeros(n) discrete_indices = zeros(Int, 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 cvec, discrete_indices end # 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) cmat = zeros(n,m) discrete_indices = zeros(Int, n, 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 cmat, discrete_indices end -function discrete_value!(a::Axis, v::Surface) - map(Surface, discrete_value!(a, v.surf)) +function discrete_value!(axis::Axis, v::Surface) + map(Surface, discrete_value!(axis, v.surf)) end +# ------------------------------------------------------------------------- + function pie_labels(sp::Subplot, series::Series) d = series.d if haskey(d,:x_discrete_indices) diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 97e0db85..b868b486 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -841,23 +841,23 @@ end # ----------------------------------------------------------------- -function set_lims!(sp::Subplot{PyPlotBackend}, axis::Axis) - lims = copy(axis[:extrema]) - lims_override = axis[:lims] - if lims_override != :auto - if isfinite(lims_override[1]) - lims[1] = lims_override[1] - end - if isfinite(lims_override[2]) - lims[2] = lims_override[2] - end - end - - # TODO: check for polar, do set_tlim/set_rlim instead - - # pyplot's set_xlim (or y/z) method: - sp.o[symbol(:set_, axis[:letter], :lim)](lims...) -end +# function set_lims!(sp::Subplot{PyPlotBackend}, axis::Axis) +# lims = copy(axis[:extrema]) +# lims_override = axis[:lims] +# if lims_override != :auto +# if isfinite(lims_override[1]) +# lims[1] = lims_override[1] +# end +# if isfinite(lims_override[2]) +# lims[2] = lims_override[2] +# end +# end +# +# # TODO: check for polar, do set_tlim/set_rlim instead +# +# # pyplot's set_xlim (or y/z) method: +# sp.o[symbol(:set_, axis[:letter], :lim)](lims...) +# 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) for letter in letters - axis = get_axis(sp, letter) - expand_extrema!(axis, series.d[letter]) + # axis = get_axis(sp, letter) + # expand_extrema!(axis, series.d[letter]) # set_lims!(sp, axis) - setPyPlotLims(sp.o, axis) + setPyPlotLims(sp.o, sp.attr[symbol(letter, :axis)]) end end diff --git a/src/layouts.jl b/src/layouts.jl index 715e7fc2..b2116b62 100644 --- a/src/layouts.jl +++ b/src/layouts.jl @@ -438,6 +438,7 @@ function build_layout(layout::GridLayout, n::Integer) end i >= n && break # only add n subplots end + layout, subplots, spmap end @@ -478,7 +479,7 @@ end # ---------------------------------------------------------------------- # @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) # something like {0.2w}? if v.head == :call && v.args[1] == :* @@ -490,7 +491,9 @@ function add_layout_pct!(kw::KW, v::Expr, idx::Integer) elseif units == :w return kw[:w] = num*pct 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 @@ -498,7 +501,9 @@ function add_layout_pct!(kw::KW, v::Expr, idx::Integer) end 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 function create_grid(expr::Expr) @@ -522,7 +527,7 @@ function create_grid(expr::Expr) s = expr.args[1] kw = KW() 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 # @show kw :(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) create_grid(mat) 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 diff --git a/src/plot.jl b/src/plot.jl index cf0dd3b7..8ab41d7b 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -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 # to generate a list of RecipeData objects (data + attributes) next_series = shift!(still_to_process) - series_list = RecipesBase.apply_recipe(next_series.d, next_series.args...) - for series in series_list + for recipedata in RecipesBase.apply_recipe(next_series.d, next_series.args...) - # series should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes - if !(typeof(series) <: RecipeData) - error("Inputs couldn't be processed... expected RecipeData but got: $series") + # recipedata should be of type RecipeData. if it's not then the inputs must not have been fully processed by recipes + if !(typeof(recipedata) <: RecipeData) + error("Inputs couldn't be processed... expected RecipeData but got: $recipedata") end - # @show series - if isempty(series.args) + if isempty(recipedata.args) # when the arg tuple is empty, that means there's nothing left to recursively # process... finish up and add to the kw_list - kw = series.d + kw = recipedata.d _add_markershape(kw) # if there was a grouping, filter the data here @@ -347,8 +345,8 @@ function _plot!(plt::Plot, d::KW, args...) warnOnUnsupportedScales(plt.backend, kw) push!(kw_list, kw) - # handle error bars by creating new series data... these will have - # the same series index as the series they are copied from + # handle error bars by creating new recipedata data... these will have + # the same recipedata index as the recipedata they are copied from for esym in (:xerror, :yerror) if get(d, esym, nothing) != nothing # we make a copy of the KW and apply an errorbar recipe @@ -360,7 +358,7 @@ function _plot!(plt::Plot, d::KW, args...) else # 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 @@ -398,7 +396,11 @@ function _plot!(plt::Plot, d::KW, args...) _update_subplot_args(plt, sp, d, idx) 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! # TODO: we probably shouldn't use i for tracking series index, but rather explicitly track it in recipes diff --git a/src/types.jl b/src/types.jl index 69957c90..8021f299 100644 --- a/src/types.jl +++ b/src/types.jl @@ -27,6 +27,12 @@ type Axis d::KW end +type Extrema + emin::Float64 + emax::Float64 +end +Extrema() = Extrema(Inf, -Inf) + # ----------------------------------------------------------- # a single subplot diff --git a/src/utils.jl b/src/utils.jl index 0f4d744d..a910f7f9 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -483,42 +483,20 @@ end function setxy!{X,Y}(plt::Plot, xy::Tuple{X,Y}, i::Integer) series = plt.series_list[i] 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) end function setxyz!{X,Y,Z}(plt::Plot, xyz::Tuple{X,Y,Z}, i::Integer) series = plt.series_list[i] 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) 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