229 lines
7.0 KiB
Julia
229 lines
7.0 KiB
Julia
|
|
|
|
# TODO: there should be a distinction between an object that will manage a full plot, vs a component of a plot.
|
|
# the PlotRecipe as currently implemented is more of a "custom component"
|
|
# a recipe should fully describe the plotting command(s) and call them, likewise for updating.
|
|
# actually... maybe those should explicitly derive from AbstractPlot???
|
|
|
|
abstract PlotRecipe
|
|
|
|
getRecipeXY(recipe::PlotRecipe) = Float64[], Float64[]
|
|
getRecipeArgs(recipe::PlotRecipe) = ()
|
|
|
|
plot(recipe::PlotRecipe, args...; kw...) = plot(getRecipeXY(recipe)..., args...; getRecipeArgs(recipe)..., kw...)
|
|
plot!(recipe::PlotRecipe, args...; kw...) = plot!(getRecipeXY(recipe)..., args...; getRecipeArgs(recipe)..., kw...)
|
|
plot!(plt::Plot, recipe::PlotRecipe, args...; kw...) = plot!(getRecipeXY(recipe)..., args...; getRecipeArgs(recipe)..., kw...)
|
|
|
|
num_series(x::AMat) = size(x,2)
|
|
num_series(x) = 1
|
|
|
|
_apply_recipe(d::Dict; kw...) = ()
|
|
|
|
# if it's not a recipe, just do nothing and return the args
|
|
function _apply_recipe(d::Dict, args...; issubplot=false, kw...)
|
|
if issubplot && !haskey(d, :n) && !haskey(d, :layout)
|
|
# put in a sensible default
|
|
d[:n] = maximum(map(num_series, args))
|
|
end
|
|
args
|
|
end
|
|
|
|
# # -------------------------------------------------
|
|
|
|
# function rotate(x::Real, y::Real, θ::Real; center = (0,0))
|
|
# cx = x - center[1]
|
|
# cy = y - center[2]
|
|
# xrot = cx * cos(θ) - cy * sin(θ)
|
|
# yrot = cy * cos(θ) + cx * sin(θ)
|
|
# xrot + center[1], yrot + center[2]
|
|
# end
|
|
|
|
# # -------------------------------------------------
|
|
|
|
# type EllipseRecipe <: PlotRecipe
|
|
# w::Float64
|
|
# h::Float64
|
|
# x::Float64
|
|
# y::Float64
|
|
# θ::Float64
|
|
# end
|
|
# EllipseRecipe(w,h,x,y) = EllipseRecipe(w,h,x,y,0)
|
|
|
|
# # return x,y coords of a rotated ellipse, centered at the origin
|
|
# function rotatedEllipse(w, h, x, y, θ, rotθ)
|
|
# # # coord before rotation
|
|
# xpre = w * cos(θ)
|
|
# ypre = h * sin(θ)
|
|
|
|
# # rotate and translate
|
|
# r = rotate(xpre, ypre, rotθ)
|
|
# x + r[1], y + r[2]
|
|
# end
|
|
|
|
# function getRecipeXY(ep::EllipseRecipe)
|
|
# x, y = unzip([rotatedEllipse(ep.w, ep.h, ep.x, ep.y, u, ep.θ) for u in linspace(0,2π,100)])
|
|
# top = rotate(0, ep.h, ep.θ)
|
|
# right = rotate(ep.w, 0, ep.θ)
|
|
# linex = Float64[top[1], 0, right[1]] + ep.x
|
|
# liney = Float64[top[2], 0, right[2]] + ep.y
|
|
# Any[x, linex], Any[y, liney]
|
|
# end
|
|
|
|
# function getRecipeArgs(ep::EllipseRecipe)
|
|
# [(:line, (3, [:dot :solid], [:red :blue], :path))]
|
|
# end
|
|
|
|
# # -------------------------------------------------
|
|
|
|
|
|
# "Correlation scatter matrix"
|
|
# function corrplot{T<:Real,S<:Real}(mat::AMat{T}, corrmat::AMat{S} = cor(mat);
|
|
# colors = :redsblues,
|
|
# labels = nothing, kw...)
|
|
# m = size(mat,2)
|
|
# centers = Float64[mean(extrema(mat[:,i])) for i in 1:m]
|
|
|
|
# # might be a mistake?
|
|
# @assert m <= 20
|
|
# @assert size(corrmat) == (m,m)
|
|
|
|
# # create a subplot grid, and a gradient from -1 to 1
|
|
# p = subplot(rand(0,m^2); n=m^2, leg=false, grid=false, kw...)
|
|
# cgrad = ColorGradient(colors, [-1,1])
|
|
|
|
# # make all the plots
|
|
# for i in 1:m
|
|
# for j in 1:m
|
|
# idx = p.layout[i,j]
|
|
# plt = p.plts[idx]
|
|
# if i==j
|
|
# # histogram on diagonal
|
|
# histogram!(plt, mat[:,i], c=:black)
|
|
# i > 1 && plot!(plt, yticks = :none)
|
|
# elseif i < j
|
|
# # annotate correlation value in upper triangle
|
|
# mi, mj = centers[i], centers[j]
|
|
# plot!(plt, [mj], [mi],
|
|
# ann = (mj, mi, text(@sprintf("Corr:\n%0.3f", corrmat[i,j]), 15)),
|
|
# yticks=:none)
|
|
# else
|
|
# # scatter plots in lower triangle; color determined by correlation
|
|
# c = RGBA(RGB(getColorZ(cgrad, corrmat[i,j])), 0.3)
|
|
# scatter!(plt, mat[:,j], mat[:,i], w=0, ms=3, c=c, smooth=true)
|
|
# end
|
|
|
|
# if labels != nothing && length(labels) >= m
|
|
# i == m && xlabel!(plt, string(labels[j]))
|
|
# j == 1 && ylabel!(plt, string(labels[i]))
|
|
# end
|
|
# end
|
|
# end
|
|
|
|
# # link the axes
|
|
# subplot!(p, link = (r,c) -> (true, r!=c))
|
|
# end
|
|
|
|
|
|
"Sparsity plot... heatmap of non-zero values of a matrix"
|
|
function spy{T<:Real}(z::AMat{T}; kw...)
|
|
# I,J,V = findnz(z)
|
|
# heatmap(J, I; leg=false, yflip=true, kw...)
|
|
heatmap(map(zi->float(zi!=0), z); leg=false, yflip=true, kw...)
|
|
end
|
|
|
|
"Adds a+bx... straight line over the current plot"
|
|
function abline!(plt::Plot, a, b; kw...)
|
|
plot!(plt, [extrema(plt)...], x -> b + a*x; kw...)
|
|
end
|
|
|
|
abline!(args...; kw...) = abline!(current(), args...; kw...)
|
|
|
|
# -------------------------------------------------
|
|
# Arc Diagram
|
|
|
|
curvecolor(value, min, max, grad) = getColorZ(grad, (value-min)/(max-min))
|
|
|
|
"Plots a clockwise arc, from source to destiny, colored by weight"
|
|
function arc!(source, destiny, weight, min, max, grad)
|
|
radius = (destiny - source) / 2
|
|
arc = Plots.partialcircle(0, π, 30, radius)
|
|
x, y = Plots.unzip(arc)
|
|
plot!(x .+ radius .+ source, y, line = (curvecolor(weight, min, max, grad), 0.5, 2))
|
|
end
|
|
|
|
"""
|
|
`arcdiagram(source, destiny, weight[, grad])`
|
|
|
|
Plots an arc diagram, form `source` to `destiny` (clockwise), using `weight` to determine the colors.
|
|
"""
|
|
function arcdiagram(source, destiny, weight, grad=ColorGradient(:bluesreds))
|
|
|
|
if length(source) == length(destiny) == length(weight)
|
|
|
|
vertices = vcat(source, destiny)
|
|
|
|
xmin, xmax = extrema(vertices)
|
|
plot(xlim=(xmin - 0.5, xmax + 0.5))
|
|
|
|
wmin,wmax = extrema(weight)
|
|
|
|
for (i, j, value) in zip(source,destiny,weight)
|
|
arc!(i, j, value, wmin, wmax, grad)
|
|
end
|
|
|
|
scatter!(vertices, zeros(length(vertices)), leg=false)
|
|
|
|
else
|
|
|
|
throw(ArgumentError("source, destiny and weight should have the same length"))
|
|
|
|
end
|
|
end
|
|
|
|
"""
|
|
`arcdiagram(mat[, grad])`
|
|
|
|
Plots an arc diagram of a matrix, form rows to columns (clockwise),
|
|
using the values on the matrix as weights to determine the colors.
|
|
Doesn't show edges with value zero if the input is sparse.
|
|
For simmetric matrices, only the upper triangular values are used.
|
|
"""
|
|
function arcdiagram{T}(mat::AbstractArray{T,2}, grad=ColorGradient(:bluesreds))
|
|
nrow, ncol = size(mat) # rows are sources and columns are destinies
|
|
|
|
nosymmetric = !issym(mat) # plots only triu for symmetric matrices
|
|
nosparse = !issparse(mat) # doesn't plot zeros from a sparse matrix
|
|
|
|
L = length(mat)
|
|
|
|
source = Array(Int, L)
|
|
destiny = Array(Int, L)
|
|
weight = Array(T, L)
|
|
|
|
idx = 1
|
|
for i in 1:nrow, j in 1:ncol
|
|
value = mat[i, j]
|
|
if !isnan(value) && ( nosparse || value != zero(T) ) # TODO: deal with Nullable
|
|
|
|
if i < j
|
|
source[idx] = i
|
|
destiny[idx] = j
|
|
weight[idx] = value
|
|
idx += 1
|
|
elseif nosymmetric && (i > j)
|
|
source[idx] = i
|
|
destiny[idx] = j
|
|
weight[idx] = value
|
|
idx += 1
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
resize!(source, idx-1)
|
|
resize!(destiny, idx-1)
|
|
resize!(weight, idx-1)
|
|
|
|
arcdiagram(source, destiny, weight, grad)
|
|
end
|