Plots.jl/src/subplot.jl
Thomas Breloff 48d802fc40 qwt subplots
2015-10-10 00:03:23 -04:00

277 lines
7.4 KiB
Julia

function subplotlayout(sz::@compat(Tuple{Int,Int}))
# create a GridLayout
GridLayout(sz...)
end
function subplotlayout(rowcounts::AVec{Int})
# create a FlexLayout
FlexLayout(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
FlexLayout(numplts, rowcounts)
end
# # create a layout directly
# SubplotLayout(rowcounts::AbstractVector{Int}) = SubplotLayout(sum(rowcounts), rowcounts)
# # create a layout given counts... nr/nc == -1 implies we figure out a good number automatically
# 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
# # create the rowcounts vector
# i = 0
# rowcounts = Int[]
# for r in 1:nr
# cnt = min(nc, numplts - i)
# push!(rowcounts, cnt)
# i += cnt
# end
# SubplotLayout(numplts, rowcounts)
# end
Base.length(layout::FlexLayout) = layout.numplts
Base.start(layout::FlexLayout) = 1
Base.done(layout::FlexLayout, state) = state > length(layout)
function Base.next(layout::FlexLayout, 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::FlexLayout) = length(layout.rowcounts)
ncols(layout::FlexLayout, row::Int) = row < 1 ? 0 : (row > nrows(layout) ? 0 : layout.rowcounts[row])
# get the plot index given row and column
Base.getindex(layout::FlexLayout, r::Int, c::Int) = sum(layout.rowcounts[1:r-1]) + c
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
# handle "linking" the subplot axes together
# each backend should implement the handleLinkInner and expandLimits! methods
function linkAxis(subplt::Subplot, isx::Bool)
lims = [Inf, -Inf]
for (i,(r,c)) in enumerate(subplt.layout)
plt = subplt.plts[i]
if (isx && r < nrows(subplt.layout)) || (!isx && c > 1)
handleLinkInner(plt, isx)
end
expandLimits!(lims, plt, isx)
end
@show lims
for plt in subplt.plts
(isx ? xlims! : ylims!)(plt, lims...)
end
end
# ------------------------------------------------------------
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))
getplot(subplt::Subplot, idx::Int = subplt.n) = subplt.plts[mod1(idx, subplt.p)]
getinitargs(subplt::Subplot, idx::Int) = getplot(subplt, idx).initargs
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(backendInstance(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)
```
"""
function subplot(args...; kw...)
validateSubplotSupported()
d = Dict(kw)
preprocessArgs!(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[]
ds = Dict[]
for i in 1:length(layout)
push!(ds, getPlotArgs(pkg, d, i))
push!(plts, plot(pkg; ds[i]...))
end
# tmpd = getPlotKeywordArgs(pkg, kw, 1, 0) # TODO: this should happen in the plot creation loop... think... what if we want to set a title per subplot??
# # shouldShow = tmpd[:show]
# # tmpd[:show] = false
# plts = Plot[plot(pkg; tmpd...) for i in 1:length(layout)]
# # tmpd[:show] = shouldShow
# create the object and do the plotting
subplt = Subplot(nothing, plts, pkg, length(layout), 0, layout, ds, false, false, false)
subplot!(subplt, args...; kw...)
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()
# if !subplotSupported()
# error(CURRENT_BACKEND.sym, " does not support the subplot/subplot! commands at this time. Try one of: ", join(filter(pkg->subplotSupported(backendInstance(pkg)), backends()),", "))
# end
d = Dict(kw)
preprocessArgs!(d)
dumpdict(d, "After subplot! preprocessing")
haskey(d, :linkx) && (subplt.linkx = d[:linkx])
haskey(d, :linky) && (subplt.linky = d[:linky])
kwList, xmeta, ymeta = createKWargsList(subplt, args...; d...)
# TODO: something useful with meta info?
for (i,di) in enumerate(kwList)
subplt.n += 1
plt = getplot(subplt) # get the Plot object where this series will be drawn
di[:show] = false
dumpdict(di, "subplot! kwList $i")
plot!(plt; di...)
end
# create the underlying object (each backend will do this differently)
if !subplt.initialized
buildSubplotObject!(subplt)
subplt.initialized = true
end
# add title, axis labels, ticks, etc
for (i,plt) in enumerate(subplt.plts)
di = copy(d)
for (k,v) in di
if typeof(v) <: AVec
di[k] = v[mod1(i, length(v))]
end
end
dumpdict(di, "Updating sp $i")
updatePlotItems(plt, di)
end
subplt.linkx && linkAxis(subplt, true)
subplt.linky && linkAxis(subplt, false)
# set this to be current
current(subplt)
# show it automatically?
if haskey(d, :show) && d[:show]
gui()
end
subplt
end