layout macro; attr rename; getindex; fixes and cleanup

This commit is contained in:
Thomas Breloff 2016-05-18 23:55:03 -04:00
parent bfc3fc1dec
commit 0d96c49f4a
7 changed files with 107 additions and 60 deletions

View File

@ -17,11 +17,14 @@ export
AbstractLayout,
GridLayout,
EmptyLayout,
@layout,
# RowsLayout,
# FlexLayout,
AVec,
AMat,
KW,
attr,
attr!,
wrap,
set_theme,

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
# -----------------------------------------------------------