reorg/cleanup; removed old layouts and subplots; created axes.jl and layouts.jl
This commit is contained in:
parent
0d96c49f4a
commit
3e8f325ddc
12
src/Plots.jl
12
src/Plots.jl
@ -16,6 +16,7 @@ export
|
||||
Subplot,
|
||||
AbstractLayout,
|
||||
GridLayout,
|
||||
grid,
|
||||
EmptyLayout,
|
||||
@layout,
|
||||
# RowsLayout,
|
||||
@ -171,18 +172,25 @@ export
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
import Measures
|
||||
import Measures: Length, AbsoluteLength, Measure, BoundingBox, mm, cm, inch, pt, width, height
|
||||
typealias BBox Measures.Absolute2DBox
|
||||
export BBox, BoundingBox, mm, cm, inch, pt, px, pct
|
||||
|
||||
# ---------------------------------------------------------
|
||||
|
||||
include("types.jl")
|
||||
include("utils.jl")
|
||||
include("colors.jl")
|
||||
include("components.jl")
|
||||
include("axes.jl")
|
||||
include("backends.jl")
|
||||
include("args.jl")
|
||||
include("themes.jl")
|
||||
include("plot.jl")
|
||||
include("series_args.jl")
|
||||
include("series_new.jl")
|
||||
# include("subplot.jl")
|
||||
# include("layouts.jl")
|
||||
include("layouts.jl")
|
||||
include("subplots.jl")
|
||||
include("recipes.jl")
|
||||
include("animation.jl")
|
||||
|
||||
@ -952,8 +952,12 @@ function _update_subplot_args(plt::Plot, sp::Subplot, d_in::KW, subplot_index::I
|
||||
for letter in (:x, :y, :z)
|
||||
# get (maybe initialize) the axis
|
||||
axissym = symbol(letter, :axis)
|
||||
axis = get!(spargs, axissym, Axis(letter))
|
||||
|
||||
axis = if haskey(spargs, axissym)
|
||||
spargs[axissym]
|
||||
else
|
||||
spargs[axissym] = Axis(letter)
|
||||
end
|
||||
|
||||
# grab magic args (for example `xaxis = (:flip, :log)`)
|
||||
args = wraptuple(get(d_in, axissym, ()))
|
||||
|
||||
|
||||
129
src/axes.jl
Normal file
129
src/axes.jl
Normal file
@ -0,0 +1,129 @@
|
||||
|
||||
|
||||
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),
|
||||
:discrete_map => Dict(), # map discrete values to continuous plot values
|
||||
:discrete_values => Tuple{Float64,Any}[],
|
||||
:use_minor => false,
|
||||
:show => true, # show or hide the axis? (useful for linked subplots)
|
||||
)
|
||||
merge!(d, _axis_defaults)
|
||||
|
||||
# update the defaults
|
||||
update!(Axis(d), args...; kw...)
|
||||
end
|
||||
|
||||
# update an Axis object with magic args and keywords
|
||||
function update!(a::Axis, args...; kw...)
|
||||
# first process args
|
||||
d = a.d
|
||||
for arg in args
|
||||
T = typeof(arg)
|
||||
arg = get(_scaleAliases, arg, arg)
|
||||
# scale, flip, label, lim, tick = axis_symbols(letter, "scale", "flip", "label", "lims", "ticks")
|
||||
|
||||
if typeof(arg) <: Font
|
||||
d[:tickfont] = arg
|
||||
d[:guidefont] = arg
|
||||
|
||||
elseif arg in _allScales
|
||||
d[:scale] = arg
|
||||
|
||||
elseif arg in (:flip, :invert, :inverted)
|
||||
d[:flip] = true
|
||||
|
||||
elseif T <: @compat(AbstractString)
|
||||
d[:label] = arg
|
||||
|
||||
# xlims/ylims
|
||||
elseif (T <: Tuple || T <: AVec) && length(arg) == 2
|
||||
sym = typeof(arg[1]) <: Number ? :lims : :ticks
|
||||
d[sym] = arg
|
||||
|
||||
# xticks/yticks
|
||||
elseif T <: AVec
|
||||
d[:ticks] = arg
|
||||
|
||||
elseif arg == nothing
|
||||
d[:ticks] = []
|
||||
|
||||
elseif typeof(arg) <: Number
|
||||
d[:rotation] = arg
|
||||
|
||||
else
|
||||
warn("Skipped $(letter)axis arg $arg")
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# then override for any keywords... only those keywords that already exists in d
|
||||
for (k,v) in kw
|
||||
# sym = symbol(string(k)[2:end])
|
||||
if haskey(d, k)
|
||||
d[k] = v
|
||||
end
|
||||
end
|
||||
a
|
||||
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]
|
||||
|
||||
# get discrete ticks, or not
|
||||
function get_ticks(a::Axis)
|
||||
ticks = a[:ticks]
|
||||
dvals = a[:discrete_values]
|
||||
if !isempty(dvals) && ticks == :auto
|
||||
vals, labels = unzip(dvals)
|
||||
else
|
||||
ticks
|
||||
end
|
||||
end
|
||||
|
||||
function expand_extrema!(a::Axis, v::Number)
|
||||
emin, emax = a[:extrema]
|
||||
a[:extrema] = (min(v, emin), max(v, emax))
|
||||
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))
|
||||
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))
|
||||
end
|
||||
a[:extrema]
|
||||
end
|
||||
|
||||
# these methods track the discrete values which correspond to axis continuous values (cv)
|
||||
# whenever we have discrete values, we automatically set the ticks to match.
|
||||
# we return the plot value
|
||||
function discrete_value!(a::Axis, v)
|
||||
cv = get(a[:discrete_map], v, NaN)
|
||||
if isnan(cv)
|
||||
emin, emax = a[:extrema]
|
||||
cv = max(0.5, emax + 1.0)
|
||||
expand_extrema!(a, cv)
|
||||
a[:discrete_map][v] = cv
|
||||
push!(a[:discrete_values], (cv, v))
|
||||
end
|
||||
cv
|
||||
end
|
||||
|
||||
# add the discrete value for each item
|
||||
function discrete_value!(a::Axis, v::AVec)
|
||||
Float64[discrete_value!(a, vi) for vi=v]
|
||||
end
|
||||
@ -257,145 +257,6 @@ function text(str, args...)
|
||||
end
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
|
||||
xaxis(args...) = Axis(:x, args...)
|
||||
yaxis(args...) = Axis(:y, args...)
|
||||
zaxis(args...) = Axis(:z, args...)
|
||||
|
||||
|
||||
# const _axis_symbols = (:label, :lims, :ticks, :scale, :flip, :rotation)
|
||||
# const _axis_symbols_fonts_colors = (
|
||||
# :guidefont, :tickfont,
|
||||
# :foreground_color_axis,
|
||||
# :foreground_color_border,
|
||||
# :foreground_color_text,
|
||||
# :foreground_color_guide
|
||||
# )
|
||||
|
||||
function Axis(letter::Symbol, args...; kw...)
|
||||
# init with values from _plot_defaults
|
||||
d = KW(
|
||||
:letter => letter,
|
||||
:extrema => (Inf, -Inf),
|
||||
:discrete_map => Dict(), # map discrete values to continuous plot values
|
||||
:discrete_values => Tuple{Float64,Any}[],
|
||||
:use_minor => false,
|
||||
:show => true, # show or hide the axis? (useful for linked subplots)
|
||||
)
|
||||
merge!(d, _axis_defaults)
|
||||
|
||||
# update the defaults
|
||||
update!(Axis(d), args...; kw...)
|
||||
end
|
||||
|
||||
# update an Axis object with magic args and keywords
|
||||
function update!(a::Axis, args...; kw...)
|
||||
# first process args
|
||||
d = a.d
|
||||
for arg in args
|
||||
T = typeof(arg)
|
||||
arg = get(_scaleAliases, arg, arg)
|
||||
# scale, flip, label, lim, tick = axis_symbols(letter, "scale", "flip", "label", "lims", "ticks")
|
||||
|
||||
if typeof(arg) <: Font
|
||||
d[:tickfont] = arg
|
||||
d[:guidefont] = arg
|
||||
|
||||
elseif arg in _allScales
|
||||
d[:scale] = arg
|
||||
|
||||
elseif arg in (:flip, :invert, :inverted)
|
||||
d[:flip] = true
|
||||
|
||||
elseif T <: @compat(AbstractString)
|
||||
d[:label] = arg
|
||||
|
||||
# xlims/ylims
|
||||
elseif (T <: Tuple || T <: AVec) && length(arg) == 2
|
||||
sym = typeof(arg[1]) <: Number ? :lims : :ticks
|
||||
d[sym] = arg
|
||||
|
||||
# xticks/yticks
|
||||
elseif T <: AVec
|
||||
d[:ticks] = arg
|
||||
|
||||
elseif arg == nothing
|
||||
d[:ticks] = []
|
||||
|
||||
elseif typeof(arg) <: Number
|
||||
d[:rotation] = arg
|
||||
|
||||
else
|
||||
warn("Skipped $(letter)axis arg $arg")
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# then override for any keywords... only those keywords that already exists in d
|
||||
for (k,v) in kw
|
||||
# sym = symbol(string(k)[2:end])
|
||||
if haskey(d, k)
|
||||
d[k] = v
|
||||
end
|
||||
end
|
||||
a
|
||||
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]
|
||||
|
||||
# get discrete ticks, or not
|
||||
function get_ticks(a::Axis)
|
||||
ticks = a[:ticks]
|
||||
dvals = a[:discrete_values]
|
||||
if !isempty(dvals) && ticks == :auto
|
||||
vals, labels = unzip(dvals)
|
||||
else
|
||||
ticks
|
||||
end
|
||||
end
|
||||
|
||||
function expand_extrema!(a::Axis, v::Number)
|
||||
emin, emax = a[:extrema]
|
||||
a[:extrema] = (min(v, emin), max(v, emax))
|
||||
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))
|
||||
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))
|
||||
end
|
||||
a[:extrema]
|
||||
end
|
||||
|
||||
# these methods track the discrete values which correspond to axis continuous values (cv)
|
||||
# whenever we have discrete values, we automatically set the ticks to match.
|
||||
# we return the plot value
|
||||
function discrete_value!(a::Axis, v)
|
||||
cv = get(a[:discrete_map], v, NaN)
|
||||
if isnan(cv)
|
||||
emin, emax = a[:extrema]
|
||||
cv = max(0.5, emax + 1.0)
|
||||
expand_extrema!(a, cv)
|
||||
a[:discrete_map][v] = cv
|
||||
push!(a[:discrete_values], (cv, v))
|
||||
end
|
||||
cv
|
||||
end
|
||||
|
||||
# add the discrete value for each item
|
||||
function discrete_value!(a::Axis, v::AVec)
|
||||
Float64[discrete_value!(a, vi) for vi=v]
|
||||
end
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
immutable Stroke
|
||||
|
||||
381
src/layouts.jl
Normal file
381
src/layouts.jl
Normal file
@ -0,0 +1,381 @@
|
||||
|
||||
# NOTE: (0,0) is the top-left !!!
|
||||
|
||||
# allow pixels and percentages
|
||||
const px = AbsoluteLength(0.254)
|
||||
const pct = Length{:pct, Float64}(1.0)
|
||||
|
||||
Base.(:.*)(m::Measure, n::Number) = m * n
|
||||
Base.(:.*)(n::Number, m::Measure) = m * n
|
||||
Base.(:-)(m::Measure, a::AbstractArray) = map(ai -> m - ai, a)
|
||||
Base.(:-)(a::AbstractArray, m::Measure) = map(ai -> ai - m, a)
|
||||
Base.zero(::Type{typeof(mm)}) = 0mm
|
||||
Base.one(::Type{typeof(mm)}) = 1mm
|
||||
Base.typemin(::typeof(mm)) = -Inf*mm
|
||||
Base.typemax(::typeof(mm)) = Inf*mm
|
||||
|
||||
Base.(:+)(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 + m2.value))
|
||||
Base.(:+)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (1 + m1.value))
|
||||
Base.(:-)(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 - m2.value))
|
||||
Base.(:-)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (m1.value - 1))
|
||||
Base.(:*)(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * m2.value)
|
||||
Base.(:*)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.value)
|
||||
Base.(:/)(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value / m2.value)
|
||||
Base.(:/)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value / m1.value)
|
||||
|
||||
|
||||
Base.zero(::Type{typeof(pct)}) = 0pct
|
||||
Base.one(::Type{typeof(pct)}) = 1pct
|
||||
Base.typemin(::typeof(pct)) = 0pct
|
||||
Base.typemax(::typeof(pct)) = 1pct
|
||||
|
||||
const defaultbox = BoundingBox(0mm, 0mm, 0mm, 0mm)
|
||||
|
||||
left(bbox::BoundingBox) = bbox.x0[1]
|
||||
top(bbox::BoundingBox) = bbox.x0[2]
|
||||
right(bbox::BoundingBox) = left(bbox) + width(bbox)
|
||||
bottom(bbox::BoundingBox) = top(bbox) + height(bbox)
|
||||
Base.size(bbox::BoundingBox) = (width(bbox), height(bbox))
|
||||
|
||||
# Base.(:*){T,N}(m1::Length{T,N}, m2::Length{T,N}) = Length{T,N}(m1.value * m2.value)
|
||||
ispositive(m::Measure) = m.value > 0
|
||||
|
||||
# union together bounding boxes
|
||||
function Base.(:+)(bb1::BoundingBox, bb2::BoundingBox)
|
||||
# empty boxes don't change the union
|
||||
ispositive(width(bb1)) || return bb2
|
||||
ispositive(height(bb1)) || return bb2
|
||||
ispositive(width(bb2)) || return bb1
|
||||
ispositive(height(bb2)) || return bb1
|
||||
|
||||
l = min(left(bb1), left(bb2))
|
||||
t = min(top(bb1), top(bb2))
|
||||
r = max(right(bb1), right(bb2))
|
||||
b = max(bottom(bb1), bottom(bb2))
|
||||
BoundingBox(l, t, r-l, b-t)
|
||||
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) + left(child)
|
||||
t = top(parent) + top(child)
|
||||
w = width(child)
|
||||
h = height(child)
|
||||
BoundingBox(l, t, w, h)
|
||||
end
|
||||
|
||||
function Base.show(io::IO, bbox::BoundingBox)
|
||||
print(io, "BBox{l,t,r,b,w,h = $(left(bbox)),$(top(bbox)), $(right(bbox)),$(bottom(bbox)), $(width(bbox)),$(height(bbox))}")
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# AbstractLayout
|
||||
|
||||
Base.show(io::IO, layout::AbstractLayout) = print(io, "$(typeof(layout))$(size(layout))")
|
||||
|
||||
# this is the available area for drawing everything in this layout... as percentages of total canvas
|
||||
bbox(layout::AbstractLayout) = layout.bbox
|
||||
bbox!(layout::AbstractLayout, bb::BoundingBox) = (layout.bbox = bb)
|
||||
|
||||
# layouts are recursive, tree-like structures, and most will have a parent field
|
||||
Base.parent(layout::AbstractLayout) = layout.parent
|
||||
parent_bbox(layout::AbstractLayout) = bbox(parent(layout))
|
||||
|
||||
# NOTE: these should be implemented for subplots in each backend!
|
||||
# they represent the minimum size of the axes and guides
|
||||
min_padding_left(layout::AbstractLayout) = 0mm
|
||||
min_padding_top(layout::AbstractLayout) = 0mm
|
||||
min_padding_right(layout::AbstractLayout) = 0mm
|
||||
min_padding_bottom(layout::AbstractLayout) = 0mm
|
||||
|
||||
padding_w(layout::AbstractLayout) = left_padding(layout) + right_padding(layout)
|
||||
padding_h(layout::AbstractLayout) = bottom_padding(layout) + top_padding(layout)
|
||||
padding(layout::AbstractLayout) = (padding_w(layout), padding_h(layout))
|
||||
|
||||
update_position!(layout::AbstractLayout) = nothing
|
||||
update_child_bboxes!(layout::AbstractLayout) = nothing
|
||||
|
||||
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, k::Symbol, v) = get(layout.attr, k, v)
|
||||
attr!(layout::AbstractLayout, v, k::Symbol) = (layout.attr[k] = v)
|
||||
hasattr(layout::AbstractLayout, k::Symbol) = haskey(layout.attr, k)
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# RootLayout
|
||||
|
||||
# this is the parent of the top-level layout
|
||||
immutable RootLayout <: AbstractLayout end
|
||||
|
||||
Base.parent(::RootLayout) = nothing
|
||||
parent_bbox(::RootLayout) = defaultbox
|
||||
bbox(::RootLayout) = defaultbox
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# EmptyLayout
|
||||
|
||||
# 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(); kw...) = EmptyLayout(parent, defaultbox, KW(kw))
|
||||
|
||||
Base.size(layout::EmptyLayout) = (0,0)
|
||||
Base.length(layout::EmptyLayout) = 0
|
||||
Base.getindex(layout::EmptyLayout, r::Int, c::Int) = nothing
|
||||
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# GridLayout
|
||||
|
||||
# nested, gridded layout with optional size percentages
|
||||
type GridLayout <: AbstractLayout
|
||||
parent::AbstractLayout
|
||||
bbox::BoundingBox
|
||||
grid::Matrix{AbstractLayout} # Nested layouts. Each position is a AbstractLayout, which allows for arbitrary recursion
|
||||
widths::Vector{Measure}
|
||||
heights::Vector{Measure}
|
||||
attr::KW
|
||||
end
|
||||
|
||||
grid(args...; kw...) = GridLayout(args...; kw...)
|
||||
|
||||
function GridLayout(dims...;
|
||||
parent = RootLayout(),
|
||||
widths = ones(dims[2]),
|
||||
heights = ones(dims[1]),
|
||||
kw...)
|
||||
grid = Matrix{AbstractLayout}(dims...)
|
||||
layout = GridLayout(
|
||||
parent,
|
||||
defaultbox,
|
||||
grid,
|
||||
Measure[w*pct for w in widths],
|
||||
Measure[h*pct for h in heights],
|
||||
# convert(Vector{Float64}, widths),
|
||||
# convert(Vector{Float64}, heights),
|
||||
KW(kw))
|
||||
fill!(grid, EmptyLayout(layout))
|
||||
layout
|
||||
end
|
||||
|
||||
Base.size(layout::GridLayout) = size(layout.grid)
|
||||
Base.length(layout::GridLayout) = length(layout.grid)
|
||||
Base.getindex(layout::GridLayout, r::Int, c::Int) = layout.grid[r,c]
|
||||
function Base.setindex!(layout::GridLayout, v, r::Int, c::Int)
|
||||
layout.grid[r,c] = v
|
||||
end
|
||||
|
||||
min_padding_left(layout::GridLayout) = maximum(map(min_padding_left, layout.grid[:,1]))
|
||||
min_padding_top(layout::GridLayout) = maximum(map(min_padding_top, layout.grid[1,:]))
|
||||
min_padding_right(layout::GridLayout) = maximum(map(min_padding_right, layout.grid[:,end]))
|
||||
min_padding_bottom(layout::GridLayout) = maximum(map(min_padding_bottom, layout.grid[end,:]))
|
||||
|
||||
|
||||
update_position!(layout::GridLayout) = map(update_position!, layout.grid)
|
||||
|
||||
|
||||
# recursively compute the bounding boxes for the layout and plotarea (relative to canvas!)
|
||||
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_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_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
|
||||
# @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
|
||||
# @show total_plotarea_horizontal total_plotarea_vertical
|
||||
|
||||
# normalize widths/heights so they sum to 1
|
||||
denom_w = sum(layout.widths)
|
||||
denom_h = sum(layout.heights)
|
||||
# @show denom_w, denom_h
|
||||
|
||||
# we have all the data we need... lets compute the plot areas
|
||||
for r=1:nr, c=1:nc
|
||||
child = layout[r,c]
|
||||
|
||||
# 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))
|
||||
|
||||
# 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_height = total_plotarea_vertical[r] * layout.heights[r] / denom_h
|
||||
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_height = pad_top[r] + plotarea_height + pad_bottom[r]
|
||||
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
|
||||
plotarea!(child, crop(layout.bbox, child_plotarea))
|
||||
bbox!(child, crop(layout.bbox, child_bbox))
|
||||
# @show child_plotarea child_bbox
|
||||
|
||||
# recursively update the child's children
|
||||
update_child_bboxes!(child)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
calc_num_subplots(layout::AbstractLayout) = 1
|
||||
function calc_num_subplots(layout::GridLayout)
|
||||
tot = 0
|
||||
for l in layout.grid
|
||||
tot += calc_num_subplots(l)
|
||||
end
|
||||
tot
|
||||
end
|
||||
|
||||
function compute_gridsize(numplts::Int, nr::Int, nc::Int)
|
||||
# figure out how many rows/columns we need
|
||||
if nr < 1
|
||||
if nc < 1
|
||||
nr = round(Int, sqrt(numplts))
|
||||
nc = ceil(Int, numplts / nr)
|
||||
else
|
||||
nr = ceil(Int, numplts / nc)
|
||||
end
|
||||
else
|
||||
nc = ceil(Int, numplts / nr)
|
||||
end
|
||||
nr, nc
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# constructors
|
||||
|
||||
# pass the layout arg through
|
||||
function build_layout(d::KW)
|
||||
build_layout(get(d, :layout, default(:layout)))
|
||||
end
|
||||
|
||||
function build_layout(n::Integer)
|
||||
nr, nc = compute_gridsize(n, -1, -1)
|
||||
build_layout(GridLayout(nr, nc), n)
|
||||
end
|
||||
|
||||
function build_layout{I<:Integer}(sztup::NTuple{2,I})
|
||||
nr, nc = sztup
|
||||
build_layout(GridLayout(nr, nc))
|
||||
end
|
||||
|
||||
function build_layout{I<:Integer}(sztup::NTuple{3,I})
|
||||
n, nr, nc = sztup
|
||||
nr, nc = compute_gridsize(n, nr, nc)
|
||||
build_layout(GridLayout(nr, nc), n)
|
||||
end
|
||||
|
||||
# compute number of subplots
|
||||
function build_layout(layout::GridLayout)
|
||||
# nr, nc = size(layout)
|
||||
# build_layout(layout, nr*nc)
|
||||
|
||||
# recursively get the size of the grid
|
||||
n = calc_num_subplots(layout)
|
||||
build_layout(layout, n)
|
||||
end
|
||||
|
||||
# n is the number of subplots
|
||||
function build_layout(layout::GridLayout, n::Integer)
|
||||
nr, nc = size(layout)
|
||||
subplots = Subplot[]
|
||||
spmap = SubplotMap()
|
||||
i = 0
|
||||
for r=1:nr, c=1:nc
|
||||
l = layout[r,c]
|
||||
if isa(l, EmptyLayout)
|
||||
sp = Subplot(backend(), parent=layout)
|
||||
layout[r,c] = sp
|
||||
push!(subplots, sp)
|
||||
spmap[attr(l,:label,gensym())] = 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, n-i)
|
||||
append!(subplots, sps)
|
||||
merge!(spmap, m)
|
||||
i += length(sps)
|
||||
end
|
||||
i >= n && break # only add n subplots
|
||||
end
|
||||
layout, subplots, spmap
|
||||
end
|
||||
|
||||
build_layout(huh) = error("unhandled layout type $(typeof(huh)): $huh")
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# @layout macro
|
||||
|
||||
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))
|
||||
|
||||
else
|
||||
# if it's something else, just return that (might be an existing layout?)
|
||||
expr
|
||||
end
|
||||
end
|
||||
|
||||
function create_grid(s::Symbol)
|
||||
:(EmptyLayout(label = $(QuoteNode(s))))
|
||||
end
|
||||
|
||||
macro layout(mat::Expr)
|
||||
create_grid(mat)
|
||||
end
|
||||
@ -1,178 +0,0 @@
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# GridLayout
|
||||
# -----------------------------------------------------------
|
||||
|
||||
"Simple grid, indices are row-major."
|
||||
immutable GridLayout <: AbstractLayout
|
||||
nr::Int
|
||||
nc::Int
|
||||
end
|
||||
|
||||
Base.length(layout::GridLayout) = layout.nr * layout.nc
|
||||
Base.start(layout::GridLayout) = 1
|
||||
Base.done(layout::GridLayout, state) = state > length(layout)
|
||||
function Base.next(layout::GridLayout, state)
|
||||
r = div(state-1, layout.nc) + 1
|
||||
c = mod1(state, layout.nc)
|
||||
(r,c), state + 1
|
||||
end
|
||||
|
||||
nrows(layout::GridLayout) = layout.nr
|
||||
ncols(layout::GridLayout) = layout.nc
|
||||
ncols(layout::GridLayout, row::Int) = layout.nc
|
||||
|
||||
# get the plot index given row and column
|
||||
Base.getindex(layout::GridLayout, r::Int, c::Int) = (r-1) * layout.nc + c
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# RowsLayout
|
||||
# -----------------------------------------------------------
|
||||
|
||||
"Number of plots per row"
|
||||
immutable RowsLayout <: AbstractLayout
|
||||
numplts::Int
|
||||
rowcounts::AbstractVector{Int}
|
||||
end
|
||||
|
||||
Base.length(layout::RowsLayout) = layout.numplts
|
||||
Base.start(layout::RowsLayout) = 1
|
||||
Base.done(layout::RowsLayout, state) = state > length(layout)
|
||||
function Base.next(layout::RowsLayout, state)
|
||||
r = 1
|
||||
c = 0
|
||||
for i = 1:state
|
||||
c += 1
|
||||
if c > layout.rowcounts[r]
|
||||
r += 1
|
||||
c = 1
|
||||
end
|
||||
end
|
||||
(r,c), state + 1
|
||||
end
|
||||
|
||||
nrows(layout::RowsLayout) = length(layout.rowcounts)
|
||||
ncols(layout::RowsLayout, row::Int) = row < 1 ? 0 : (row > nrows(layout) ? 0 : layout.rowcounts[row])
|
||||
|
||||
# get the plot index given row and column
|
||||
Base.getindex(layout::RowsLayout, r::Int, c::Int) = sum(layout.rowcounts[1:r-1]) + c
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# FlexLayout
|
||||
# -----------------------------------------------------------
|
||||
|
||||
"Flexible, nested layout with optional size percentages."
|
||||
immutable FlexLayout <: AbstractLayout
|
||||
n::Int
|
||||
grid::Matrix # Nested layouts. Each position
|
||||
# can be a plot index or another FlexLayout
|
||||
widths::Vector{Float64}
|
||||
heights::Vector{Float64}
|
||||
end
|
||||
|
||||
typealias IntOrFlex Union{Int,FlexLayout}
|
||||
|
||||
Base.length(layout::FlexLayout) = layout.n
|
||||
Base.start(layout::FlexLayout) = 1
|
||||
Base.done(layout::FlexLayout, state) = state > length(layout)
|
||||
function Base.next(layout::FlexLayout, state)
|
||||
# TODO: change this method to return more info
|
||||
# TODO: might consider multiple iterator types.. some backends might have an easier time row-by-row for example
|
||||
error()
|
||||
r = 1
|
||||
c = 0
|
||||
for i = 1:state
|
||||
c += 1
|
||||
if c > layout.rowcounts[r]
|
||||
r += 1
|
||||
c = 1
|
||||
end
|
||||
end
|
||||
(r,c), state + 1
|
||||
end
|
||||
|
||||
nrows(layout::FlexLayout) = size(layout.grid, 1)
|
||||
ncols(layout::FlexLayout, row::Int) = size(layout.grid, 2)
|
||||
|
||||
# get the plot index given row and column
|
||||
Base.getindex(layout::FlexLayout, r::Int, c::Int) = layout.grid[r,c]
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# we're taking in a nested structure of some kind... parse it out and build a FlexLayout
|
||||
function subplotlayout(mat::AbstractVecOrMat; widths = nothing, heights = nothing)
|
||||
n = 0
|
||||
nr, nc = size(mat)
|
||||
grid = Array(IntOrFlex, nr, nc)
|
||||
for i=1:nr, j=1:nc
|
||||
v = mat[i,j]
|
||||
|
||||
if isa(v, Integer)
|
||||
grid[i,j] = Int(v)
|
||||
n += 1
|
||||
|
||||
elseif isa(v, Tuple)
|
||||
warn("need to handle tuples somehow... (idx, sizepct)")
|
||||
grid[i,j] = nothing
|
||||
|
||||
elseif v == nothing
|
||||
grid[i,j] = nothing
|
||||
|
||||
elseif isa(v, AbstractVecOrMat)
|
||||
grid[i,j] = layout(v)
|
||||
n += grid[i,j].n
|
||||
|
||||
else
|
||||
error("How do we process? $v")
|
||||
end
|
||||
end
|
||||
|
||||
if widths == nothing
|
||||
widths = ones(nc) ./ nc
|
||||
end
|
||||
if heights == nothing
|
||||
heights = ones(nr) ./ nr
|
||||
end
|
||||
|
||||
FlexLayout(n, grid, widths, heights)
|
||||
end
|
||||
|
||||
|
||||
function subplotlayout(sz::Tuple{Int,Int})
|
||||
GridLayout(sz...)
|
||||
end
|
||||
|
||||
function subplotlayout(rowcounts::AVec{Int})
|
||||
RowsLayout(sum(rowcounts), rowcounts)
|
||||
end
|
||||
|
||||
function subplotlayout(numplts::Int, nr::Int, nc::Int)
|
||||
|
||||
# figure out how many rows/columns we need
|
||||
if nr == -1
|
||||
if nc == -1
|
||||
nr = round(Int, sqrt(numplts))
|
||||
nc = ceil(Int, numplts / nr)
|
||||
else
|
||||
nr = ceil(Int, numplts / nc)
|
||||
end
|
||||
else
|
||||
nc = ceil(Int, numplts / nr)
|
||||
end
|
||||
|
||||
# if it's a perfect rectangle, just create a grid
|
||||
if numplts == nr * nc
|
||||
return GridLayout(nr, nc)
|
||||
end
|
||||
|
||||
# create the rowcounts vector
|
||||
i = 0
|
||||
rowcounts = Int[]
|
||||
for r in 1:nr
|
||||
cnt = min(nc, numplts - i)
|
||||
push!(rowcounts, cnt)
|
||||
i += cnt
|
||||
end
|
||||
|
||||
RowsLayout(numplts, rowcounts)
|
||||
end
|
||||
@ -1,339 +0,0 @@
|
||||
|
||||
### WARNING: this file is deprecated ###
|
||||
|
||||
# ------------------------------------------------------------
|
||||
|
||||
Base.string(subplt::Subplot) = "Subplot{$(subplt.backend) p=$(subplt.p) n=$(subplt.n)}"
|
||||
Base.print(io::IO, subplt::Subplot) = print(io, string(subplt))
|
||||
Base.show(io::IO, subplt::Subplot) = print(io, string(subplt))
|
||||
|
||||
function Base.copy(subplt::Subplot)
|
||||
subplot(subplt.plts, subplt.layout, subplt.plotargs)
|
||||
end
|
||||
|
||||
Base.getindex(subplt::Subplot, args...) = subplt.plts[subplt.layout[args...]]
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
getplot(subplt::Subplot, idx::Int = subplt.n) = subplt.plts[mod1(idx, subplt.p)]
|
||||
getplotargs(subplt::Subplot, idx::Int) = getplot(subplt, idx).plotargs
|
||||
convertSeriesIndex(subplt::Subplot, n::Int) = ceil(Int, n / subplt.p)
|
||||
|
||||
# ------------------------------------------------------------
|
||||
|
||||
function validateSubplotSupported()
|
||||
if !subplotSupported()
|
||||
error(CURRENT_BACKEND.sym, " does not support the subplot/subplot! commands at this time. Try one of: ", join(filter(pkg->subplotSupported(_backend_instance(pkg)), backends()),", "))
|
||||
end
|
||||
end
|
||||
|
||||
"""
|
||||
Create a series of plots:
|
||||
```
|
||||
y = rand(100,3)
|
||||
subplot(y; n = 3) # create an automatic grid, and let it figure out the nr/nc... will put plots 1 and 2 on the first row, and plot 3 by itself on the 2nd row
|
||||
subplot(y; n = 3, nr = 1) # create an automatic grid, but fix the number of rows to 1 (so there are n columns)
|
||||
subplot(y; n = 3, nc = 1) # create an automatic grid, but fix the number of columns to 1 (so there are n rows)
|
||||
subplot(y; layout = [1, 2]) # explicit layout by row... plot #1 goes by itself in the first row, plots 2 and 3 split the 2nd row (note the n kw is unnecessary)
|
||||
subplot(plts, n; nr = -1, nc = -1) # build a layout from existing plots
|
||||
subplot(plts, layout) # build a layout from existing plots
|
||||
```
|
||||
"""
|
||||
function subplot(args...; kw...)
|
||||
validateSubplotSupported()
|
||||
d = KW(kw)
|
||||
preprocessArgs!(d)
|
||||
|
||||
# for plotting recipes, swap out the args and update the parameter dictionary
|
||||
args = RecipesBase.apply_recipe(d, KW(kw), args...; issubplot=true)
|
||||
_add_markershape(d)
|
||||
|
||||
# figure out the layout
|
||||
layoutarg = get(d, :layout, nothing)
|
||||
if layoutarg != nothing
|
||||
layout = subplotlayout(layoutarg)
|
||||
else
|
||||
n = get(d, :n, -1)
|
||||
if n < 0
|
||||
error("You must specify either layout or n when creating a subplot: ", d)
|
||||
end
|
||||
layout = subplotlayout(n, get(d, :nr, -1), get(d, :nc, -1))
|
||||
end
|
||||
|
||||
# initialize the individual plots
|
||||
pkg = backend()
|
||||
plts = Plot{typeof(pkg)}[]
|
||||
for i in 1:length(layout)
|
||||
di = getPlotArgs(pkg, d, i)
|
||||
di[:subplot] = true
|
||||
dumpdict(di, "Plot args (subplot $i)")
|
||||
push!(plts, _create_plot(pkg, di))
|
||||
end
|
||||
|
||||
# create the object and do the plotting
|
||||
subplt = Subplot(nothing, plts, pkg, length(layout), 0, layout, d, false, false, false, (r,c) -> (nothing,nothing))
|
||||
subplot!(subplt, args...; kw...)
|
||||
|
||||
subplt
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
|
||||
# NOTE: for the subplot calls building from existing plots, we need the first plot to be separate to ensure dispatch calls this instead of the more general subplot(args...; kw...)
|
||||
|
||||
# grid layout
|
||||
function subplot{P}(plt1::Plot{P}, plts::Plot{P}...; kw...)
|
||||
d = KW(kw)
|
||||
layout = if haskey(d, :layout)
|
||||
subplotlayout(d[:layout])
|
||||
else
|
||||
subplotlayout(length(plts)+1, get(d, :nr, -1), get(d, :nc, -1))
|
||||
end
|
||||
# layout = subplotlayout(length(plts)+1, get(d, :nr, -1), get(d, :nc, -1))
|
||||
subplot(vcat(plt1, plts...), layout, d)
|
||||
end
|
||||
|
||||
# explicit layout
|
||||
function subplot{P,I<:Integer}(pltsPerRow::AVec{I}, plt1::Plot{P}, plts::Plot{P}...; kw...)
|
||||
layout = subplotlayout(pltsPerRow)
|
||||
subplot(vcat(plt1, plts...), layout, KW(kw))
|
||||
end
|
||||
|
||||
# this will be called internally
|
||||
function subplot{P<:AbstractBackend}(plts::AVec{Plot{P}}, layout::AbstractLayout, d::KW)
|
||||
validateSubplotSupported()
|
||||
p = length(layout)
|
||||
n = sum([plt.n for plt in plts])
|
||||
subplt = Subplot(nothing, collect(plts), P(), p, n, layout, KW(), false, false, false, (r,c) -> (nothing,nothing))
|
||||
|
||||
_preprocess_subplot(subplt, d)
|
||||
_postprocess_subplot(subplt, d)
|
||||
|
||||
subplt
|
||||
end
|
||||
|
||||
# TODO: hcat/vcat subplots and plots together arbitrarily
|
||||
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
function _preprocess_subplot(subplt::Subplot, d::KW, args = ())
|
||||
validateSubplotSupported()
|
||||
userkw = preprocessArgs!(d)
|
||||
|
||||
# for plotting recipes, swap out the args and update the parameter dictionary
|
||||
args = RecipesBase.apply_recipe(d, userkw, args...; issubplot=true)
|
||||
_add_markershape(d)
|
||||
|
||||
dumpdict(d, "After subplot! preprocessing")
|
||||
|
||||
# get the full plotargs, overriding any new settings
|
||||
# TODO: subplt.plotargs should probably be merged sooner and actually used
|
||||
# for color selection, etc. (i.e. if we overwrite the subplot palettes to [:heat :rainbow])
|
||||
# then we need to overwrite plt[1].plotargs[:color_palette] to :heat before it's actually used
|
||||
# for color selection!
|
||||
|
||||
# first merge the new args into the subplot's plotargs. then process the plot args and merge
|
||||
# those into the plot's plotargs. (example... `palette = [:blues :reds]` goes into subplt.plotargs,
|
||||
# then the ColorGradient for :blues/:reds is merged into plot 1/2 plotargs, which is then used for color selection)
|
||||
for i in 1:length(subplt.layout)
|
||||
subplt.plts[i].plotargs = getPlotArgs(backend(), merge(subplt.plts[i].plotargs, d), i)
|
||||
end
|
||||
merge!(subplt.plotargs, d)
|
||||
|
||||
# process links. TODO: extract to separate function
|
||||
for s in (:linkx, :linky, :linkfunc)
|
||||
if haskey(d, s)
|
||||
setfield!(subplt, s, d[s])
|
||||
delete!(d, s)
|
||||
end
|
||||
end
|
||||
|
||||
args
|
||||
end
|
||||
|
||||
function _postprocess_subplot(subplt::Subplot, d::KW)
|
||||
# init (after plot creation)
|
||||
if !subplt.initialized
|
||||
subplt.initialized = _create_subplot(subplt, false)
|
||||
end
|
||||
|
||||
# add title, axis labels, ticks, etc
|
||||
for (i,plt) in enumerate(subplt.plts)
|
||||
di = plt.plotargs
|
||||
dumpdict(di, "Updating sp $i")
|
||||
_update_plot(plt, di)
|
||||
end
|
||||
|
||||
_update_plot_pos_size(subplt, d)
|
||||
|
||||
# handle links
|
||||
subplt.linkx && link_axis(subplt, true)
|
||||
subplt.linky && link_axis(subplt, false)
|
||||
|
||||
# set this to be current
|
||||
current(subplt)
|
||||
end
|
||||
|
||||
# ------------------------------------------------------------------------------------------------
|
||||
|
||||
"""
|
||||
Adds to a subplot.
|
||||
"""
|
||||
|
||||
# current subplot
|
||||
function subplot!(args...; kw...)
|
||||
validateSubplotSupported()
|
||||
subplot!(current(), args...; kw...)
|
||||
end
|
||||
|
||||
|
||||
# not allowed:
|
||||
function subplot!(plt::Plot, args...; kw...)
|
||||
error("Can't call subplot! on a Plot!")
|
||||
end
|
||||
|
||||
|
||||
# # this adds to a specific subplot... most plot commands will flow through here
|
||||
function subplot!(subplt::Subplot, args...; kw...)
|
||||
# validateSubplotSupported()
|
||||
|
||||
d = KW(kw)
|
||||
args = _preprocess_subplot(subplt, d, args)
|
||||
|
||||
# create the underlying object (each backend will do this differently)
|
||||
# note: we call it once before doing the individual plots, and once after
|
||||
# this is because some backends need to set up the subplots and then plot,
|
||||
# and others need to do it the other way around
|
||||
if !subplt.initialized
|
||||
subplt.initialized = _create_subplot(subplt, true)
|
||||
end
|
||||
|
||||
groupby = if haskey(d, :group)
|
||||
extractGroupArgs(d[:group], args...)
|
||||
else
|
||||
nothing
|
||||
end
|
||||
|
||||
_add_series_subplot(subplt, d, groupby, args...)
|
||||
_postprocess_subplot(subplt, d)
|
||||
|
||||
# show it automatically?
|
||||
if haskey(d, :show) && d[:show]
|
||||
gui()
|
||||
end
|
||||
|
||||
subplt
|
||||
end
|
||||
|
||||
|
||||
# not allowed:
|
||||
function plot!(subplt::Subplot, args...; kw...)
|
||||
error("Can't call plot! on a Subplot!")
|
||||
end
|
||||
|
||||
# given a fully processed KW, add the series to the Plot
|
||||
function _add_series_subplot(plt::Plot, d::KW)
|
||||
# setTicksFromStringVector(d, d, :x, :xticks)
|
||||
# setTicksFromStringVector(d, d, :y, :yticks)
|
||||
setTicksFromStringVector(plt, d, d, "x")
|
||||
setTicksFromStringVector(plt, d, d, "y")
|
||||
setTicksFromStringVector(plt, d, d, "z")
|
||||
|
||||
# this is the actual call to the backend
|
||||
_add_series(plt.backend, plt, d)
|
||||
|
||||
_add_annotations(plt, d)
|
||||
warnOnUnsupportedScales(plt.backend, d)
|
||||
end
|
||||
|
||||
|
||||
# handle the grouping... add a series for each group
|
||||
function _add_series_subplot(subplt::Subplot, d::KW, groupby::GroupBy, args...)
|
||||
starting_n = subplt.n
|
||||
for (i, glab) in enumerate(groupby.groupLabels)
|
||||
tmpd = copy(d)
|
||||
tmpd[:numUncounted] = subplt.n - starting_n
|
||||
_add_series_subplot(subplt, tmpd, nothing, args...;
|
||||
idxfilter = groupby.groupIds[i],
|
||||
grouplabel = string(glab))
|
||||
end
|
||||
end
|
||||
|
||||
# process, filter, and add to the correct plot
|
||||
function _add_series_subplot(subplt::Subplot, d::KW, ::Void, args...;
|
||||
idxfilter = nothing,
|
||||
grouplabel = "")
|
||||
process_inputs(subplt, d, args...)
|
||||
|
||||
if idxfilter != nothing
|
||||
# add the group name as the label if there isn't one passed in
|
||||
get!(d, :label, grouplabel)
|
||||
# filter the data
|
||||
filter_data!(d, idxfilter)
|
||||
end
|
||||
|
||||
kwList, xmeta, ymeta = build_series_args(subplt, d)
|
||||
|
||||
# TODO: something useful with meta info?
|
||||
|
||||
for (i,di) in enumerate(kwList)
|
||||
|
||||
subplt.n += 1
|
||||
plt = getplot(subplt)
|
||||
plt.n += 1
|
||||
|
||||
# cleanup the dictionary that we pass into the plot! command
|
||||
di[:show] = false
|
||||
di[:subplot] = true
|
||||
for k in (:title, :xguide, :xticks, :xlims, :xscale, :xflip,
|
||||
:yguide, :yticks, :ylims, :yscale, :yflip)
|
||||
delete!(di, k)
|
||||
end
|
||||
dumpdict(di, "subplot! kwList $i")
|
||||
dumpdict(plt.plotargs, "plt.plotargs before plotting")
|
||||
|
||||
_replace_linewidth(di)
|
||||
_add_series_subplot(plt, di)
|
||||
end
|
||||
end
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
# handle "linking" the subplot axes together
|
||||
# each backend should implement the _remove_axis and _expand_limits methods
|
||||
function link_axis(subplt::Subplot, isx::Bool)
|
||||
|
||||
# collect the list of plots and the expanded limits for those plots that should be linked on this axis
|
||||
includedPlots = Any[]
|
||||
# lims = [Inf, -Inf]
|
||||
lims = Dict{Int,Any}() # maps column to xlim
|
||||
for (i,(r,c)) in enumerate(subplt.layout)
|
||||
|
||||
# shouldlink will be a bool or nothing. if nothing, then use linkx/y (which is true if we get to this code)
|
||||
shouldlink = subplt.linkfunc(r,c)[isx ? 1 : 2]
|
||||
if shouldlink == nothing || shouldlink
|
||||
plt = subplt.plts[i]
|
||||
|
||||
# if we don't have this
|
||||
k = isx ? c : r
|
||||
if (firstone = !haskey(lims, k))
|
||||
lims[k] = [Inf, -Inf]
|
||||
end
|
||||
|
||||
isinner = (isx && r < nrows(subplt.layout)) || (!isx && !firstone)
|
||||
push!(includedPlots, (plt, isinner, k))
|
||||
|
||||
_expand_limits(lims[k], plt, isx)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# do the axis adjustments
|
||||
for (plt, isinner, k) in includedPlots
|
||||
if isinner
|
||||
_remove_axis(plt, isx)
|
||||
end
|
||||
(isx ? xlims! : ylims!)(plt, lims[k]...)
|
||||
end
|
||||
end
|
||||
487
src/subplots.jl
487
src/subplots.jl
@ -1,361 +1,17 @@
|
||||
|
||||
|
||||
function Subplot{T<:AbstractBackend}(::T; parent = RootLayout())
|
||||
Subplot{T}(parent, defaultbox, defaultbox, KW(), nothing, nothing)
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
plotarea!(sp::Subplot, bbox::BoundingBox) = (sp.plotarea = bbox)
|
||||
|
||||
Base.show(io::IO, layout::AbstractLayout) = print(io, "$(typeof(layout))$(size(layout))")
|
||||
|
||||
# this is the available area for drawing everything in this layout... as percentages of total canvas
|
||||
bbox(layout::AbstractLayout) = layout.bbox
|
||||
bbox!(layout::AbstractLayout, bb::BoundingBox) = (layout.bbox = bb)
|
||||
|
||||
# layouts are recursive, tree-like structures, and most will have a parent field
|
||||
Base.parent(layout::AbstractLayout) = layout.parent
|
||||
parent_bbox(layout::AbstractLayout) = bbox(parent(layout))
|
||||
|
||||
# # this is a calculation of the percentage of free space available in the canvas
|
||||
# # after accounting for the size of guides and axes
|
||||
# free_size(layout::AbstractLayout) = (free_width(layout), free_height(layout))
|
||||
# free_width(layout::AbstractLayout) = width(layout.bbox) - used_width(layout)
|
||||
# free_height(layout::AbstractLayout) = height(layout.bbox) - used_height(layout)
|
||||
|
||||
# NOTE: these should be implemented for subplots in each backend!
|
||||
# they represent the minimum size of the axes and guides
|
||||
min_padding_left(layout::AbstractLayout) = 0mm
|
||||
min_padding_top(layout::AbstractLayout) = 0mm
|
||||
min_padding_right(layout::AbstractLayout) = 0mm
|
||||
min_padding_bottom(layout::AbstractLayout) = 0mm
|
||||
|
||||
padding_w(layout::AbstractLayout) = left_padding(layout) + right_padding(layout)
|
||||
padding_h(layout::AbstractLayout) = bottom_padding(layout) + top_padding(layout)
|
||||
padding(layout::AbstractLayout) = (padding_w(layout), padding_h(layout))
|
||||
|
||||
|
||||
update_position!(layout::AbstractLayout) = nothing
|
||||
update_child_bboxes!(layout::AbstractLayout) = nothing
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
Base.size(layout::EmptyLayout) = (0,0)
|
||||
Base.length(layout::EmptyLayout) = 0
|
||||
Base.getindex(layout::EmptyLayout, r::Int, c::Int) = nothing
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
Base.parent(::RootLayout) = nothing
|
||||
parent_bbox(::RootLayout) = defaultbox
|
||||
bbox(::RootLayout) = defaultbox
|
||||
|
||||
# Base.size(layout::RootLayout) = (1,1)
|
||||
# Base.length(layout::RootLayout) = 1
|
||||
# Base.getindex(layout::RootLayout, r::Int, c::Int) = layout.child
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
Base.size(sp::Subplot) = (1,1)
|
||||
Base.length(sp::Subplot) = 1
|
||||
Base.getindex(sp::Subplot, r::Int, c::Int) = sp
|
||||
|
||||
|
||||
# used_width(sp::Subplot) = yaxis_width(sp)
|
||||
# used_height(sp::Subplot) = xaxis_height(sp) + title_height(sp)
|
||||
|
||||
# used_width(subplot::Subplot) = error("used_width(::Subplot) must be implemented by each backend")
|
||||
# used_height(subplot::Subplot) = error("used_height(::Subplot) must be implemented by each backend")
|
||||
|
||||
# # this should return a bounding box (relative to the canvas) for the plot area (inside the spines/ticks)
|
||||
# plotarea_bbox(subplot::Subplot) = error("plotarea_bbox(::Subplot) must be implemented by each backend")
|
||||
|
||||
# # bounding box (relative to canvas) for plot area
|
||||
# # note: we assume the x axis is on the left, and y axis is on the bottom
|
||||
# function plotarea_bbox(sp::Subplot)
|
||||
# xh = xaxis_height(sp)
|
||||
# yw = yaxis_width(sp)
|
||||
# crop(bbox(sp), BoundingBox(yw, xh, width(sp) - yw,
|
||||
# height(sp) - xh - title_height(sp)))
|
||||
# end
|
||||
|
||||
# NOTE: this is unnecessary I think as it is the same as bbox(::Subplot)
|
||||
# # this should return a bounding box (relative to the canvas) for the whole subplot (plotarea, ticks, and guides)
|
||||
# subplot_bbox(subplot::Subplot) = error("subplot_bbox(::Subplot) must be implemented by each backend")
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
Base.size(layout::GridLayout) = size(layout.grid)
|
||||
Base.length(layout::GridLayout) = length(layout.grid)
|
||||
Base.getindex(layout::GridLayout, r::Int, c::Int) = layout.grid[r,c]
|
||||
function Base.setindex!(layout::GridLayout, v, r::Int, c::Int)
|
||||
layout.grid[r,c] = v
|
||||
end
|
||||
|
||||
min_padding_left(layout::GridLayout) = maximum(map(min_padding_left, layout.grid[:,1]))
|
||||
min_padding_top(layout::GridLayout) = maximum(map(min_padding_top, layout.grid[1,:]))
|
||||
min_padding_right(layout::GridLayout) = maximum(map(min_padding_right, layout.grid[:,end]))
|
||||
min_padding_bottom(layout::GridLayout) = maximum(map(min_padding_bottom, layout.grid[end,:]))
|
||||
|
||||
# function used_width(layout::GridLayout)
|
||||
# w = 0mm
|
||||
# nr,nc = size(layout)
|
||||
# for c=1:nc
|
||||
# @show w
|
||||
# w += maximum([used_width(layout[r,c]) for r=1:nr])
|
||||
# for r=1:nr
|
||||
# @show used_width(layout[r,c])
|
||||
# end
|
||||
# @show w
|
||||
# end
|
||||
# w
|
||||
# end
|
||||
#
|
||||
# function used_height(layout::GridLayout)
|
||||
# h = 0mm
|
||||
# nr,nc = size(layout)
|
||||
# for r=1:nr
|
||||
# h += maximum([used_height(layout[r,c]) for c=1:nc])
|
||||
# end
|
||||
# h
|
||||
# end
|
||||
|
||||
update_position!(layout::GridLayout) = map(update_position!, layout.grid)
|
||||
|
||||
|
||||
# recursively compute the bounding boxes for the layout and plotarea (relative to canvas!)
|
||||
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_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_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
|
||||
# @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
|
||||
# @show total_plotarea_horizontal total_plotarea_vertical
|
||||
|
||||
# normalize widths/heights so they sum to 1
|
||||
denom_w = sum(layout.widths)
|
||||
denom_h = sum(layout.heights)
|
||||
# @show denom_w, denom_h
|
||||
|
||||
# we have all the data we need... lets compute the plot areas
|
||||
for r=1:nr, c=1:nc
|
||||
child = layout[r,c]
|
||||
|
||||
# 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))
|
||||
|
||||
# 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_height = total_plotarea_vertical[r] * layout.heights[r] / denom_h
|
||||
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_height = pad_top[r] + plotarea_height + pad_bottom[r]
|
||||
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
|
||||
plotarea!(child, crop(layout.bbox, child_plotarea))
|
||||
bbox!(child, crop(layout.bbox, child_bbox))
|
||||
# @show child_plotarea child_bbox
|
||||
|
||||
# recursively update the child's children
|
||||
update_child_bboxes!(child)
|
||||
end
|
||||
end
|
||||
|
||||
# # a recursive method to first compute the free space by bubbling the free space
|
||||
# # up the tree, then assigning bounding boxes according to height/width percentages
|
||||
# # note: this should be called after all axis objects are updated to re-compute the
|
||||
# # bounding boxes for the layout tree
|
||||
# function update_child_bboxes!(layout::GridLayout) #, parent_bbox::BoundingBox = defaultbox)
|
||||
# # initialize the free space (per child!)
|
||||
# nr, nc = size(layout)
|
||||
# freew, freeh = free_size(layout)
|
||||
# @show freew, freeh
|
||||
# freew /= sum(layout.widths)
|
||||
# freeh /= sum(layout.heights)
|
||||
# @show freew, freeh
|
||||
#
|
||||
# @show layout.bbox
|
||||
#
|
||||
# # TODO: this should really track used/free space for each row/column so that we can align plot areas properly
|
||||
#
|
||||
# # l, b = 0.0, 0.0
|
||||
# rights = Measure[0mm for i=1:nc] #zeros(nc) .* pct
|
||||
# bottoms = Measure[0mm for i=1:nr] # ones(nr) .* pct
|
||||
# for r=1:nr, c=1:nc
|
||||
# # compute the child's bounding box relative to the parent
|
||||
# child = layout[r,c]
|
||||
# usedw, usedh = used_size(child)
|
||||
# @show r,c, usedw, usedh
|
||||
#
|
||||
# plot_l = (c == 1 ? 0mm : rights[c-1])
|
||||
# plot_t = (r == 1 ? height(layout) : bottoms[r-1])
|
||||
# # bottom = (r == 1 ? 0 : bottoms[r-1])
|
||||
# plot_w = freew * layout.widths[c]
|
||||
# plot_h = freeh * layout.heights[r]
|
||||
# right = plot_l + usedw + plot_w
|
||||
# bottom = plot_t - usedh - plot_h
|
||||
# # plot_t = bottom + usedh + freeh * layout.heights[r]
|
||||
# child_bbox = BoundingBox(plot_l, bottom, plot_w, plot_h)
|
||||
# @show child_bbox
|
||||
#
|
||||
# rights[c] = right
|
||||
# bottoms[r] = bottom
|
||||
#
|
||||
# # then compute the bounding box relative to the canvas, and cache it in the child object
|
||||
# bbox!(child, crop(bbox(layout), child_bbox))
|
||||
# @show child.bbox
|
||||
#
|
||||
# # now recursively update the child
|
||||
# update_child_bboxes!(child)
|
||||
# end
|
||||
# end
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# # build_layout should return a tuple: (a top-level layout, a list of subplots, and a SubplotMap)
|
||||
# function build_layout(d::KW)
|
||||
# layout = get(d, :layout, default(:layout))
|
||||
# T = typeof(layout)
|
||||
# if T <: AbstractLayout
|
||||
# build_layout(layout)
|
||||
# elseif T <: Integer
|
||||
# if layout == 1
|
||||
# # just a single subplot
|
||||
# sp = Subplot(backend())
|
||||
# sp, Subplot[sp], SubplotMap((1,1) => sp)
|
||||
# else
|
||||
# nr, nc = compute_gridsize(layout)
|
||||
# build_layout(GridLayout(nr, nc), layout)
|
||||
# end
|
||||
# elseif T <: NTuple{2}
|
||||
# build_layout(GridLayout(layout...))
|
||||
# elseif T <: NTuple{3}
|
||||
# n, nr, nc = layout
|
||||
# build_layout(GridLayout())
|
||||
# else
|
||||
# error("unhandled layout type $T: $layout")
|
||||
# end
|
||||
# end
|
||||
|
||||
calc_num_subplots(layout::AbstractLayout) = 1
|
||||
function calc_num_subplots(layout::GridLayout)
|
||||
tot = 0
|
||||
for l in layout.grid
|
||||
tot += calc_num_subplots(l)
|
||||
end
|
||||
tot
|
||||
end
|
||||
|
||||
|
||||
# pass the layout arg through
|
||||
function build_layout(d::KW)
|
||||
build_layout(get(d, :layout, default(:layout)))
|
||||
end
|
||||
|
||||
function build_layout(n::Integer)
|
||||
nr, nc = compute_gridsize(n, -1, -1)
|
||||
build_layout(GridLayout(nr, nc), n)
|
||||
end
|
||||
|
||||
function build_layout{I<:Integer}(sztup::NTuple{2,I})
|
||||
nr, nc = sztup
|
||||
build_layout(GridLayout(nr, nc))
|
||||
end
|
||||
|
||||
function build_layout{I<:Integer}(sztup::NTuple{3,I})
|
||||
n, nr, nc = sztup
|
||||
nr, nc = compute_gridsize(n, nr, nc)
|
||||
build_layout(GridLayout(nr, nc), n)
|
||||
end
|
||||
|
||||
# compute number of subplots
|
||||
function build_layout(layout::GridLayout)
|
||||
# nr, nc = size(layout)
|
||||
# build_layout(layout, nr*nc)
|
||||
|
||||
# recursively get the size of the grid
|
||||
n = calc_num_subplots(layout)
|
||||
build_layout(layout, n)
|
||||
end
|
||||
|
||||
# n is the number of subplots
|
||||
function build_layout(layout::GridLayout, n::Integer)
|
||||
nr, nc = size(layout)
|
||||
subplots = Subplot[]
|
||||
spmap = SubplotMap()
|
||||
i = 0
|
||||
for r=1:nr, c=1:nc
|
||||
l = layout[r,c]
|
||||
if isa(l, EmptyLayout)
|
||||
sp = Subplot(backend(), parent=layout)
|
||||
layout[r,c] = sp
|
||||
push!(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, n-i)
|
||||
append!(subplots, sps)
|
||||
merge!(spmap, m)
|
||||
i += length(sps)
|
||||
end
|
||||
i >= n && break # only add n subplots
|
||||
end
|
||||
layout, subplots, spmap
|
||||
end
|
||||
|
||||
build_layout(huh) = error("unhandled layout type $(typeof(huh)): $huh")
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
function compute_gridsize(numplts::Int, nr::Int, nc::Int)
|
||||
# figure out how many rows/columns we need
|
||||
if nr < 1
|
||||
if nc < 1
|
||||
nr = round(Int, sqrt(numplts))
|
||||
nc = ceil(Int, numplts / nr)
|
||||
else
|
||||
nr = ceil(Int, numplts / nc)
|
||||
end
|
||||
else
|
||||
nc = ceil(Int, numplts / nr)
|
||||
end
|
||||
nr, nc
|
||||
end
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
get_subplot(plt::Plot, sp::Subplot) = sp
|
||||
get_subplot(plt::Plot, i::Integer) = plt.subplots[i]
|
||||
get_subplot(plt::Plot, k) = plt.spmap[k]
|
||||
@ -365,138 +21,3 @@ 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
|
||||
# Base.done(layout::GridLayout, state) = state > length(layout)
|
||||
# function Base.next(layout::GridLayout, state)
|
||||
# # TODO: change this method to return more info
|
||||
# # TODO: might consider multiple iterator types.. some backends might have an easier time row-by-row for example
|
||||
# error()
|
||||
# r = 1
|
||||
# c = 0
|
||||
# for i = 1:state
|
||||
# c += 1
|
||||
# if c > layout.rowcounts[r]
|
||||
# r += 1
|
||||
# c = 1
|
||||
# end
|
||||
# end
|
||||
# (r,c), state + 1
|
||||
# end
|
||||
|
||||
# nrows(layout::GridLayout) = size(layout, 1)
|
||||
# ncols(layout::GridLayout) = size(layout, 2)
|
||||
|
||||
# get the plot index given row and column
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# # we're taking in a nested structure of some kind... parse it out and build a GridLayout
|
||||
# function subplotlayout(mat::AbstractVecOrMat; widths = nothing, heights = nothing)
|
||||
# n = 0
|
||||
# nr, nc = size(mat)
|
||||
# grid = Array(IntOrFlex, nr, nc)
|
||||
# for i=1:nr, j=1:nc
|
||||
# v = mat[i,j]
|
||||
#
|
||||
# if isa(v, Integer)
|
||||
# grid[i,j] = Int(v)
|
||||
# n += 1
|
||||
#
|
||||
# elseif isa(v, Tuple)
|
||||
# warn("need to handle tuples somehow... (idx, sizepct)")
|
||||
# grid[i,j] = nothing
|
||||
#
|
||||
# elseif v == nothing
|
||||
# grid[i,j] = nothing
|
||||
#
|
||||
# elseif isa(v, AbstractVecOrMat)
|
||||
# grid[i,j] = layout(v)
|
||||
# n += grid[i,j].n
|
||||
#
|
||||
# else
|
||||
# error("How do we process? $v")
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# if widths == nothing
|
||||
# widths = ones(nc) ./ nc
|
||||
# end
|
||||
# if heights == nothing
|
||||
# heights = ones(nr) ./ nr
|
||||
# end
|
||||
#
|
||||
# GridLayout(n, grid, widths, heights)
|
||||
# end
|
||||
#
|
||||
#
|
||||
# function subplotlayout(sz::Tuple{Int,Int})
|
||||
# GridLayout(sz...)
|
||||
# end
|
||||
#
|
||||
# function subplotlayout(rowcounts::AVec{Int})
|
||||
# RowsLayout(sum(rowcounts), rowcounts)
|
||||
# end
|
||||
#
|
||||
# function subplotlayout(numplts::Int, nr::Int, nc::Int)
|
||||
#
|
||||
# # figure out how many rows/columns we need
|
||||
# if nr == -1
|
||||
# if nc == -1
|
||||
# nr = round(Int, sqrt(numplts))
|
||||
# nc = ceil(Int, numplts / nr)
|
||||
# else
|
||||
# nr = ceil(Int, numplts / nc)
|
||||
# end
|
||||
# else
|
||||
# nc = ceil(Int, numplts / nr)
|
||||
# end
|
||||
#
|
||||
# # if it's a perfect rectangle, just create a grid
|
||||
# if numplts == nr * nc
|
||||
# return GridLayout(nr, nc)
|
||||
# end
|
||||
#
|
||||
# # create the rowcounts vector
|
||||
# i = 0
|
||||
# rowcounts = Int[]
|
||||
# for r in 1:nr
|
||||
# cnt = min(nc, numplts - i)
|
||||
# push!(rowcounts, cnt)
|
||||
# i += cnt
|
||||
# end
|
||||
#
|
||||
# RowsLayout(numplts, rowcounts)
|
||||
# end
|
||||
|
||||
197
src/types.jl
197
src/types.jl
@ -1,153 +1,32 @@
|
||||
|
||||
# TODO: I declare lots of types here because of the lacking ability to do forward declarations in current Julia
|
||||
# I should move these to the relevant files when something like "extern" is implemented
|
||||
|
||||
typealias AVec AbstractVector
|
||||
typealias AMat AbstractMatrix
|
||||
typealias KW Dict{Symbol,Any}
|
||||
|
||||
immutable PlotsDisplay <: Display end
|
||||
|
||||
abstract AbstractBackend
|
||||
abstract AbstractPlot{T<:AbstractBackend}
|
||||
abstract AbstractLayout
|
||||
|
||||
typealias KW Dict{Symbol,Any}
|
||||
# -----------------------------------------------------------
|
||||
|
||||
immutable InputWrapper{T}
|
||||
obj::T
|
||||
end
|
||||
|
||||
wrap{T}(obj::T) = InputWrapper{T}(obj)
|
||||
Base.isempty(wrapper::InputWrapper) = false
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Axes
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# simple wrapper around a KW so we can hold all attributes pertaining to the axis in one place
|
||||
type Axis #<: Associative{Symbol,Any}
|
||||
type Axis
|
||||
d::KW
|
||||
end
|
||||
|
||||
type AxisView
|
||||
label::UTF8String
|
||||
axis::Axis
|
||||
end
|
||||
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Layouts
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# NOTE: (0,0) is the top-left !!!
|
||||
|
||||
import Measures
|
||||
import Measures: Length, AbsoluteLength, Measure, BoundingBox, mm, cm, inch, pt, width, height
|
||||
export BBox, BoundingBox, mm, cm, inch, pt, px, pct
|
||||
|
||||
typealias BBox Measures.Absolute2DBox
|
||||
|
||||
# allow pixels and percentages
|
||||
const px = AbsoluteLength(0.254)
|
||||
const pct = Length{:pct, Float64}(1.0)
|
||||
|
||||
Base.(:.*)(m::Measure, n::Number) = m * n
|
||||
Base.(:.*)(n::Number, m::Measure) = m * n
|
||||
Base.(:-)(m::Measure, a::AbstractArray) = map(ai -> m - ai, a)
|
||||
Base.(:-)(a::AbstractArray, m::Measure) = map(ai -> ai - m, a)
|
||||
Base.zero(::Type{typeof(mm)}) = 0mm
|
||||
Base.one(::Type{typeof(mm)}) = 1mm
|
||||
Base.typemin(::typeof(mm)) = -Inf*mm
|
||||
Base.typemax(::typeof(mm)) = Inf*mm
|
||||
|
||||
Base.(:+)(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 + m2.value))
|
||||
Base.(:+)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (1 + m1.value))
|
||||
Base.(:-)(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * (1 - m2.value))
|
||||
Base.(:-)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * (m1.value - 1))
|
||||
Base.(:*)(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value * m2.value)
|
||||
Base.(:*)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value * m1.value)
|
||||
Base.(:/)(m1::AbsoluteLength, m2::Length{:pct}) = AbsoluteLength(m1.value / m2.value)
|
||||
Base.(:/)(m1::Length{:pct}, m2::AbsoluteLength) = AbsoluteLength(m2.value / m1.value)
|
||||
|
||||
|
||||
Base.zero(::Type{typeof(pct)}) = 0pct
|
||||
Base.one(::Type{typeof(pct)}) = 1pct
|
||||
Base.typemin(::typeof(pct)) = 0pct
|
||||
Base.typemax(::typeof(pct)) = 1pct
|
||||
|
||||
const defaultbox = BoundingBox(0mm, 0mm, 0mm, 0mm)
|
||||
|
||||
# left(bbox::BoundingBox) = bbox.left
|
||||
# bottom(bbox::BoundingBox) = bbox.bottom
|
||||
# right(bbox::BoundingBox) = bbox.right
|
||||
# top(bbox::BoundingBox) = bbox.top
|
||||
# width(bbox::BoundingBox) = bbox.right - bbox.left
|
||||
# height(bbox::BoundingBox) = bbox.top - bbox.bottom
|
||||
|
||||
|
||||
|
||||
left(bbox::BoundingBox) = bbox.x0[1]
|
||||
top(bbox::BoundingBox) = bbox.x0[2]
|
||||
right(bbox::BoundingBox) = left(bbox) + width(bbox)
|
||||
bottom(bbox::BoundingBox) = top(bbox) + height(bbox)
|
||||
Base.size(bbox::BoundingBox) = (width(bbox), height(bbox))
|
||||
|
||||
# Base.(:*){T,N}(m1::Length{T,N}, m2::Length{T,N}) = Length{T,N}(m1.value * m2.value)
|
||||
ispositive(m::Measure) = m.value > 0
|
||||
|
||||
# union together bounding boxes
|
||||
function Base.(:+)(bb1::BoundingBox, bb2::BoundingBox)
|
||||
# empty boxes don't change the union
|
||||
ispositive(width(bb1)) || return bb2
|
||||
ispositive(height(bb1)) || return bb2
|
||||
ispositive(width(bb2)) || return bb1
|
||||
ispositive(height(bb2)) || return bb1
|
||||
|
||||
l = min(left(bb1), left(bb2))
|
||||
t = min(top(bb1), top(bb2))
|
||||
r = max(right(bb1), right(bb2))
|
||||
b = max(bottom(bb1), bottom(bb2))
|
||||
BoundingBox(l, t, r-l, b-t)
|
||||
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) + left(child)
|
||||
t = top(parent) + top(child)
|
||||
w = width(child)
|
||||
h = height(child)
|
||||
BoundingBox(l, t, w, h)
|
||||
end
|
||||
|
||||
function Base.show(io::IO, bbox::BoundingBox)
|
||||
print(io, "BBox{l,t,r,b,w,h = $(left(bbox)),$(top(bbox)), $(right(bbox)),$(bottom(bbox)), $(width(bbox)),$(height(bbox))}")
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
abstract AbstractLayout
|
||||
|
||||
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(); kw...) = EmptyLayout(parent, defaultbox, KW(kw))
|
||||
|
||||
# this is the parent of the top-level layout
|
||||
immutable RootLayout <: AbstractLayout
|
||||
# child::AbstractLayout
|
||||
end
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# a single subplot
|
||||
@ -156,57 +35,14 @@ type Subplot{T<:AbstractBackend} <: AbstractLayout
|
||||
bbox::BoundingBox # the canvas area which is available to this subplot
|
||||
plotarea::BoundingBox # the part where the data goes
|
||||
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)
|
||||
|
||||
# Subplot(parent = RootLayout(); attr = KW())
|
||||
end
|
||||
|
||||
function Subplot{T<:AbstractBackend}(::T; parent = RootLayout())
|
||||
Subplot{T}(parent, defaultbox, defaultbox, KW(), nothing, nothing)
|
||||
end
|
||||
|
||||
plotarea!(sp::Subplot, bbox::BoundingBox) = (sp.plotarea = bbox)
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# nested, gridded layout with optional size percentages
|
||||
type GridLayout <: AbstractLayout
|
||||
parent::AbstractLayout
|
||||
bbox::BoundingBox
|
||||
grid::Matrix{AbstractLayout} # Nested layouts. Each position is a AbstractLayout, which allows for arbitrary recursion
|
||||
widths::Vector{Measure}
|
||||
heights::Vector{Measure}
|
||||
attr::KW
|
||||
end
|
||||
|
||||
function GridLayout(dims...;
|
||||
parent = RootLayout(),
|
||||
widths = ones(dims[2]),
|
||||
heights = ones(dims[1]),
|
||||
kw...)
|
||||
grid = Matrix{AbstractLayout}(dims...)
|
||||
layout = GridLayout(
|
||||
parent,
|
||||
defaultbox,
|
||||
grid,
|
||||
Measure[w*pct for w in widths],
|
||||
Measure[h*pct for h in heights],
|
||||
# convert(Vector{Float64}, widths),
|
||||
# convert(Vector{Float64}, heights),
|
||||
KW(kw))
|
||||
fill!(grid, EmptyLayout(layout))
|
||||
layout
|
||||
end
|
||||
|
||||
|
||||
# -----------------------------------------------------------
|
||||
|
||||
typealias SubplotMap Dict{Any, Subplot}
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Plot
|
||||
# -----------------------------------------------------------
|
||||
|
||||
type Series
|
||||
@ -216,6 +52,8 @@ 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
|
||||
@ -239,22 +77,5 @@ 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
|
||||
# -----------------------------------------------------------
|
||||
|
||||
# type Subplot{T<:AbstractBackend, L<:AbstractLayout} <: AbstractPlot{T}
|
||||
# o # the underlying object
|
||||
# plts::Vector{Plot{T}} # the individual plots
|
||||
# backend::T
|
||||
# p::Int # number of plots
|
||||
# n::Int # number of series
|
||||
# layout::L
|
||||
# plotargs::KW
|
||||
# initialized::Bool
|
||||
# linkx::Bool
|
||||
# linky::Bool
|
||||
# linkfunc::Function # maps (row,column) -> (BoolOrNothing, BoolOrNothing)... if xlink/ylink are nothing, then use subplt.linkx/y
|
||||
# end
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user