From 0d96c49f4a8c3383eca66dce8ca5e78976ba7833 Mon Sep 17 00:00:00 2001 From: Thomas Breloff Date: Wed, 18 May 2016 23:55:03 -0400 Subject: [PATCH] layout macro; attr rename; getindex; fixes and cleanup --- src/Plots.jl | 3 ++ src/args.jl | 4 +-- src/backends/pyplot.jl | 20 +++++------ src/plot.jl | 22 ++++++------ src/series_new.jl | 8 ++--- src/subplots.jl | 81 ++++++++++++++++++++++++++++++------------ src/types.jl | 29 +++++++++------ 7 files changed, 107 insertions(+), 60 deletions(-) diff --git a/src/Plots.jl b/src/Plots.jl index 66e31bca..bcc2fc65 100644 --- a/src/Plots.jl +++ b/src/Plots.jl @@ -17,11 +17,14 @@ export AbstractLayout, GridLayout, EmptyLayout, + @layout, # RowsLayout, # FlexLayout, AVec, AMat, KW, + attr, + attr!, wrap, set_theme, diff --git a/src/args.jl b/src/args.jl index 87012e21..8f12dab8 100644 --- a/src/args.jl +++ b/src/args.jl @@ -234,7 +234,7 @@ for letter in (:x,:y,:z) _axis_defaults_byletter[symbol(letter,k)] = nothing end end -@show _axis_defaults +# @show _axis_defaults # for letter in (:x, :y, :z) # for (k,v) in _axis_defaults # _axis_defaults[symbol(letter,k)] = v @@ -922,7 +922,7 @@ end # update a subplots args and axes function _update_subplot_args(plt::Plot, sp::Subplot, d_in::KW, subplot_index::Integer) pargs = plt.plotargs - spargs = sp.subplotargs + spargs = sp.attr # @show subplot_index, sp for (k,v) in _subplot_defaults slice_arg!(d_in, spargs, k, v, subplot_index) diff --git a/src/backends/pyplot.jl b/src/backends/pyplot.jl index 60601bd9..d1327305 100644 --- a/src/backends/pyplot.jl +++ b/src/backends/pyplot.jl @@ -293,7 +293,7 @@ end function py_bbox_axis(ax, letter) ticks = py_bbox_ticks(ax, letter) labels = py_bbox_axislabel(ax, letter) - letter == "x" && @show ticks labels ticks+labels + # letter == "x" && @show ticks labels ticks+labels ticks + labels end @@ -395,14 +395,14 @@ end function _initialize_subplot(plt::Plot{PyPlotBackend}, sp::Subplot{PyPlotBackend}) fig = plt.o # add a new axis, and force it to create a new one by setting a distinct label - proj = sp.subplotargs[:projection] + proj = sp.attr[:projection] ax = fig[:add_axes]( [0,0,1,1], label = string(gensym()), projection = (proj in (nothing,:none) ? nothing : string(proj)) ) # projection = - # ax = fig[:add_subplot](111, projection = _py_projections[sp.subplotargs[:projection]]) + # ax = fig[:add_subplot](111, projection = _py_projections[sp.attr[:projection]]) # for axis in (:xaxis, :yaxis) # ax[axis][:_autolabelpos] = false # end @@ -948,7 +948,7 @@ function _add_series(plt::Plot{PyPlotBackend}, series::Series) end # this sets the bg color inside the grid - ax[:set_axis_bgcolor](getPyPlotColor(d[:subplot].subplotargs[:background_color_inside])) + ax[:set_axis_bgcolor](getPyPlotColor(d[:subplot].attr[:background_color_inside])) # handle area filling fillrange = d[:fillrange] @@ -1137,7 +1137,7 @@ function _update_plot(plt::Plot{PyPlotBackend}, d::KW) # figorax = plt.o # ax = getLeftAxis(figorax) for sp in plt.subplots - spargs = sp.subplotargs + spargs = sp.attr ax = getAxis(sp) # ticksz = get(d, :tickfont, plt.plotargs[:tickfont]).pointsize # guidesz = get(d, :guidefont, spargs[:guidefont]).pointsize @@ -1348,7 +1348,7 @@ const _pyplot_legend_pos = KW( # function addPyPlotLegend(plt::Plot) # function addPyPlotLegend(plt::Plot, ax) function addPyPlotLegend(plt::Plot, sp::Subplot, ax) - leg = sp.subplotargs[:legend] + leg = sp.attr[:legend] if leg != :none # gotta do this to ensure both axes are included labels = [] @@ -1376,19 +1376,19 @@ function addPyPlotLegend(plt::Plot, sp::Subplot, ax) labels, #[d[:label] for d in args], loc = get(_pyplot_legend_pos, leg, "best"), scatterpoints = 1, - fontsize = sp.subplotargs[:legendfont].pointsize + fontsize = sp.attr[:legendfont].pointsize # framealpha = 0.6 ) leg[:set_zorder](1000) - fgcolor = getPyPlotColor(sp.subplotargs[:foreground_color_legend]) + fgcolor = getPyPlotColor(sp.attr[:foreground_color_legend]) for txt in leg[:get_texts]() PyPlot.plt[:setp](txt, color = fgcolor) end # set some legend properties frame = leg[:get_frame]() - frame[:set_facecolor](getPyPlotColor(sp.subplotargs[:background_color_legend])) + frame[:set_facecolor](getPyPlotColor(sp.attr[:background_color_legend])) frame[:set_edgecolor](fgcolor) end end @@ -1402,7 +1402,7 @@ function finalizePlot(plt::Plot{PyPlotBackend}) ax = getAxis(sp) addPyPlotLegend(plt, sp, ax) for asym in (:xaxis, :yaxis, :zaxis) - updateAxisColors(ax, sp.subplotargs[asym]) + updateAxisColors(ax, sp.attr[asym]) end end drawfig(plt.o) diff --git a/src/plot.jl b/src/plot.jl index f6b9f274..541c7a03 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -46,7 +46,7 @@ function plot(args...; kw...) pkg = backend() d = KW(kw) preprocessArgs!(d) - DD(d,"pre") + # DD(d,"pre") # create an empty Plot, update the args using the inputs, then pass it # to the backend to finish backend-specific initialization @@ -61,7 +61,7 @@ function plot(args...; kw...) # update the subplot/axis args from inputs, then pass to backend to init further sp.plt = plt _update_subplot_args(plt, sp, copy(d), idx) - # DD(sp.subplotargs[:xaxis].d,"$idx") + # DD(sp.attr[:xaxis].d,"$idx") # TODO: i'd like to know what projection we're using by this point... can I push this off until later?? # I won't easily be able to auto-determine what series types are coming... @@ -167,7 +167,7 @@ function _apply_series_recipe(plt::Plot, d::KW) # adjust extrema and discrete info for s in (:x, :y, :z) data = d[s] - axis = sp.subplotargs[symbol(s, "axis")] + axis = sp.attr[symbol(s, "axis")] if eltype(data) <: Number expand_extrema!(axis, data) else @@ -330,18 +330,20 @@ function _plot!(plt::Plot, d::KW, args...) end # get the Subplot object to which the series belongs - sp = slice_arg(get(kw, :subplot, :auto), i) - if sp == :auto - sp = 1 # TODO: something useful + sp = get(kw, :subplot, :auto) + sp = if sp == :auto + mod1(i,length(plt.subplots)) + else + slice_arg(sp, i) end sp = kw[:subplot] = get_subplot(plt, sp) idx = get_subplot_index(plt, sp) # we update subplot args in case something like the color palatte is part of the recipe - # DD(sp.subplotargs[:xaxis].d, "before USA $i") + # DD(sp.attr[:xaxis].d, "before USA $i") # DD(kw, "kw") _update_subplot_args(plt, sp, kw, idx) - # DD(sp.subplotargs[:xaxis].d, "after USA $i") + # DD(sp.attr[:xaxis].d, "after USA $i") # set default values, select from attribute cycles, and generally set the final attributes _add_defaults!(kw, plt, sp, i) @@ -355,9 +357,9 @@ function _plot!(plt::Plot, d::KW, args...) # For example, a histogram is just a bar plot with binned data, a bar plot is really a filled step plot, # and a step plot is really just a path. So any backend that supports drawing a path will implicitly # be able to support step, bar, and histogram plots (and any recipes that use those components). - # DD(sp.subplotargs[:xaxis].d, "before $i") + # DD(sp.attr[:xaxis].d, "before $i") _apply_series_recipe(plt, kw) - # DD(sp.subplotargs[:xaxis].d, "after $i") + # DD(sp.attr[:xaxis].d, "after $i") end # now that we're done adding all the series, add the annotations diff --git a/src/series_new.jl b/src/series_new.jl index e7dc898c..c93d6aca 100644 --- a/src/series_new.jl +++ b/src/series_new.jl @@ -27,7 +27,7 @@ function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int) aliasesAndAutopick(d, :markershape, _markerAliases, supportedMarkers(pkg), plotIndex) # update color - d[:seriescolor] = getSeriesRGBColor(d[:seriescolor], sp.subplotargs, plotIndex) + d[:seriescolor] = getSeriesRGBColor(d[:seriescolor], sp.attr, plotIndex) # update colors for csym in (:linecolor, :markercolor, :fillcolor) @@ -38,16 +38,16 @@ function _add_defaults!(d::KW, plt::Plot, sp::Subplot, commandIndex::Int) d[:seriescolor] end else - getSeriesRGBColor(d[csym], sp.subplotargs, plotIndex) + getSeriesRGBColor(d[csym], sp.attr, plotIndex) end end # update markerstrokecolor c = d[:markerstrokecolor] c = if c == :match - sp.subplotargs[:foreground_color_subplot] + sp.attr[:foreground_color_subplot] else - getSeriesRGBColor(c, sp.subplotargs, plotIndex) + getSeriesRGBColor(c, sp.attr, plotIndex) end d[:markerstrokecolor] = c diff --git a/src/subplots.jl b/src/subplots.jl index 1924ecc9..d8731385 100644 --- a/src/subplots.jl +++ b/src/subplots.jl @@ -125,29 +125,29 @@ function update_child_bboxes!(layout::GridLayout) nr, nc = size(layout) # create a matrix for each minimum padding direction - minpad_left = map(min_padding_left, layout.grid) - minpad_top = map(min_padding_top, layout.grid) - minpad_right = map(min_padding_right, layout.grid) + minpad_left = map(min_padding_left, layout.grid) + minpad_top = map(min_padding_top, layout.grid) + minpad_right = map(min_padding_right, layout.grid) minpad_bottom = map(min_padding_bottom, layout.grid) # @show minpad_left minpad_top minpad_right minpad_bottom # get the max horizontal (left and right) padding over columns, # and max vertical (bottom and top) padding over rows # TODO: add extra padding here - pad_left = maximum(minpad_left, 1) - pad_top = maximum(minpad_top, 2) - pad_right = maximum(minpad_right, 1) + pad_left = maximum(minpad_left, 1) + pad_top = maximum(minpad_top, 2) + pad_right = maximum(minpad_right, 1) pad_bottom = maximum(minpad_bottom, 2) # @show pad_left pad_top pad_right pad_bottom # scale this up to the total padding in each direction total_pad_horizontal = (pad_left + pad_right) .* nc - total_pad_vertical = (pad_top + pad_bottom) .* nr + total_pad_vertical = (pad_top + pad_bottom) .* nr # @show total_pad_horizontal total_pad_vertical # now we can compute the total plot area in each direction - total_plotarea_horizontal = width(layout) - total_pad_horizontal - total_plotarea_vertical = height(layout) - total_pad_vertical + total_plotarea_horizontal = width(layout) - total_pad_horizontal + total_plotarea_vertical = height(layout) - total_pad_vertical # @show total_plotarea_horizontal total_plotarea_vertical # normalize widths/heights so they sum to 1 @@ -161,19 +161,19 @@ function update_child_bboxes!(layout::GridLayout) # get the top-left corner of this child child_left = (c == 1 ? 0mm : right(layout[r, c-1].bbox)) - child_top = (r == 1 ? 0mm : bottom(layout[r-1, c].bbox)) + child_top = (r == 1 ? 0mm : bottom(layout[r-1, c].bbox)) # compute plot area - plotarea_left = child_left + pad_left[c] - plotarea_top = child_top + pad_top[r] - plotarea_width = total_plotarea_horizontal[c] * layout.widths[c] / denom_w + plotarea_left = child_left + pad_left[c] + plotarea_top = child_top + pad_top[r] + plotarea_width = total_plotarea_horizontal[c] * layout.widths[c] / denom_w plotarea_height = total_plotarea_vertical[r] * layout.heights[r] / denom_h - child_plotarea = BoundingBox(plotarea_left, plotarea_top, plotarea_width, plotarea_height) + child_plotarea = BoundingBox(plotarea_left, plotarea_top, plotarea_width, plotarea_height) # compute child bbox - child_width = pad_left[c] + plotarea_width + pad_right[c] + child_width = pad_left[c] + plotarea_width + pad_right[c] child_height = pad_top[r] + plotarea_height + pad_bottom[r] - child_bbox = BoundingBox(child_left, child_top, child_width, child_height) + child_bbox = BoundingBox(child_left, child_top, child_width, child_height) # @show (r,c) child_plotarea child_bbox # the bounding boxes are currently relative to the parent, but we need them relative to the canvas @@ -308,25 +308,29 @@ function build_layout(layout::GridLayout, n::Integer) nr, nc = size(layout) subplots = Subplot[] spmap = SubplotMap() - i = 1 + i = 0 for r=1:nr, c=1:nc - i > n && break # only add n subplots l = layout[r,c] if isa(l, EmptyLayout) sp = Subplot(backend(), parent=layout) layout[r,c] = sp push!(subplots, sp) - spmap[length(subplots)] = sp + spmap[attr(l,:label)] = sp + if hasattr(l,:width) + layout.widths[c] = attr(l,:width) + end + if hasattr(l,:height) + layout.heights[r] = attr(l,:height) + end i += 1 elseif isa(l, GridLayout) # sub-grid - l, sps, m = build_layout(l) + l, sps, m = build_layout(l, n-i) append!(subplots, sps) - for (k,v) in m - spmap[k+length(subplots)] = v - end + merge!(spmap, m) i += length(sps) end + i >= n && break # only add n subplots end layout, subplots, spmap end @@ -361,6 +365,37 @@ get_subplot_index(plt::Plot, idx::Integer) = idx get_subplot_index(plt::Plot, sp::Subplot) = findfirst(sp->sp === plt.subplots[2], plt.subplots) # ---------------------------------------------------------------------- + +function create_grid(expr::Expr) + cellsym = gensym(:cell) + constructor = if expr.head == :vcat + :(let + $cellsym = GridLayout($(length(expr.args)), 1) + $([:($cellsym[$i,1] = $(create_grid(expr.args[i]))) for i=1:length(expr.args)]...) + $cellsym + end) + elseif expr.head in (:hcat,:row) + :(let + $cellsym = GridLayout(1, $(length(expr.args))) + $([:($cellsym[1,$i] = $(create_grid(expr.args[i]))) for i=1:length(expr.args)]...) + $cellsym + end) + + elseif expr.head == :curly + length(expr.args) == 3 || error("Should be width and height in curly. Got: ", expr.args) + s,w,h = expr.args + :(EmptyLayout(label = $(QuoteNode(s)), width = $w, height = $h)) + end +end + +function create_grid(s::Symbol) + :(EmptyLayout(label = $(QuoteNode(s)))) +end + +macro layout(mat::Expr) + create_grid(mat) +end + # ---------------------------------------------------------------------- # Base.start(layout::GridLayout) = 1 diff --git a/src/types.jl b/src/types.jl index 8e3ffd7a..027a9421 100644 --- a/src/types.jl +++ b/src/types.jl @@ -99,11 +99,6 @@ function Base.(:+)(bb1::BoundingBox, bb2::BoundingBox) ispositive(width(bb2)) || return bb1 ispositive(height(bb2)) || return bb1 - # if width(bb1) <= 0mm || height(bb1) <= 0mm - # return bb2 - # elseif width(bb2) <= 0mm || height(bb2) <= 0mm - # return bb1 - # end l = min(left(bb1), left(bb2)) t = min(top(bb1), top(bb2)) r = max(right(bb1), right(bb2)) @@ -114,10 +109,6 @@ end # this creates a bounding box in the parent's scope, where the child bounding box # is relative to the parent function crop(parent::BoundingBox, child::BoundingBox) - # l = left(parent) + width(parent) * left(child) - # b = bottom(parent) + height(parent) * bottom(child) - # w = width(parent) * width(child) - # h = height(parent) * height(child) l = left(parent) + left(child) t = top(parent) + top(child) w = width(child) @@ -137,14 +128,20 @@ width(layout::AbstractLayout) = width(layout.bbox) height(layout::AbstractLayout) = height(layout.bbox) plotarea!(layout::AbstractLayout, bbox::BoundingBox) = nothing +attr(layout::AbstractLayout, k::Symbol) = layout.attr[k] +attr!(layout::AbstractLayout, v, k::Symbol) = (layout.attr[k] = v) +hasattr(layout::AbstractLayout, k::Symbol) = haskey(layout.attr, k) + # ----------------------------------------------------------- # contains blank space type EmptyLayout <: AbstractLayout parent::AbstractLayout bbox::BoundingBox + attr::KW # store label, width, and height for initialization + # label # this is the label that the subplot will take (since we create a layout before initialization) end -EmptyLayout(parent = RootLayout()) = EmptyLayout(parent, defaultbox) +EmptyLayout(parent = RootLayout(); kw...) = EmptyLayout(parent, defaultbox, KW(kw)) # this is the parent of the top-level layout immutable RootLayout <: AbstractLayout @@ -158,7 +155,7 @@ type Subplot{T<:AbstractBackend} <: AbstractLayout parent::AbstractLayout bbox::BoundingBox # the canvas area which is available to this subplot plotarea::BoundingBox # the part where the data goes - subplotargs::KW # args specific to this subplot + attr::KW # args specific to this subplot # axisviews::Vector{AxisView} o # can store backend-specific data... like a pyplot ax plt # the enclosing Plot object (can't give it a type because of no forward declarations) @@ -216,6 +213,9 @@ type Series d::KW end +attr(series::Series, k::Symbol) = series.d[k] +attr!(series::Series, v, k::Symbol) = (series.d[k] = v) + type Plot{T<:AbstractBackend} <: AbstractPlot{T} backend::T # the backend type n::Int # number of series @@ -232,6 +232,13 @@ function Plot() Subplot[], SubplotMap(), EmptyLayout()) end +# TODO: make a decision... should plt[1] return the first subplot or the first series?? +# Base.getindex(plt::Plot, i::Integer) = plt.subplots[i] +Base.getindex(plt::Plot, s::Symbol) = plt.spmap[s] +Base.getindex(plt::Plot, r::Integer, c::Integer) = plt.layout[r,c] +attr(plt::Plot, k::Symbol) = plt.plotargs[k] +attr!(plt::Plot, v, k::Symbol) = (plt.plotargs[k] = v) + # ----------------------------------------------------------- # Subplot # -----------------------------------------------------------