... did I mention I'm a bit of a geek?
Pkg.add("Plots")
Pkg.add("PlotRecipes")
Pkg.add("PyPlot")
Pkg.checkout("Plots")
Pkg.checkout("PlotRecipes")
using Plots, PlotRecipes
pyplot()
Plots.PyPlotBackend()
years = 2000:2016
activities = [
("Work", [3,2,3,5,7,10,10,12,16,13,11,12,12,11,10,10,10]),
("Video Games", [8,7,6,4,1,.5,.5,.5,.5,.5,.5,.25,.25,.25,.25,.25,.25]),
("Sports", [2,2,2,1,2,2,1,.5,.5,.5,.5,.5,1,1,.5,.5,.25]),
("Family", [.1,.1,2,3,3,3,3,3,2,2,5,5,6,6,6,7,8]),
("Sleep", zeros(17))
]
labels, hours = Plots.unzip(activities)
hours = hcat(hours...)
hours[:,5] = 24 - sum(hours,2)
pcts = hours ./ sum(hours,2)
portfoliocomposition(pcts, years, lab=labels', legend=:topleft, dir=:h)
plot!(xguide="Year", title="My adult life", bg=RGBA(1,1,1,0.5))
yaxis!("Hours per Day",(linspace(0,1,4),0:8:24))
plot(
heatmap(years, labels, pcts', leg=false, c=:blues),
wireframe(labels, years, pcts, contours=true, leg=false, zticks=nothing),
contour(years, labels, pcts', fill=true, c=:plasma),
layout = @layout [a b{0.6w}; c{0.3h}]
)
languages = ["julia","c/c++","python","javascript","go","c#","java","matlab","other"]
pcts = [
0 0 0 0 0 0 0.9 0 0.1
0 0 0 0 0 0 0.9 0 0.1
0 0 0 0 0 0 0.6 0 0.4
0 0 0 0 0 0.2 0.5 0 0.3
0 0.2 0 0 0 0.2 0.3 0 0.3
0 0 1 0 0 0 0 0 0
0 0.3 0.7 0 0 0 0 0 0
0 0.6 0.4 0 0 0 0 0 0
0 0.8 0.2 0 0 0 0 0 0
0 0.7 0.3 0 0 0 0 0 0
0 0.6 0.4 0 0 0 0 0 0
0 0.6 0.4 0 0 0 0 0 0
0 0.5 0.4 0.1 0 0 0 0 0
0 0.15 0.15 0.15 0.15 0 0.15 0.15 0.1
0.2 0 0.1 0 0 0 0.35 0.35 0
0.9 0 0.1 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0
]
α = vcat(1, 0.3ones(8))';
anns = [
(2002, 0.8, text("School",15,0.1pi)),
(2007.5, 0.6, text("Banks", 15,0.1pi)),
(2012.5, 0.4, text("Funds", 15,0.1pi)),
(2015, 0.2, text("C Tech",15,0.1pi)),
];
portfoliocomposition(pcts, years, lab=languages', dir=:h, α = α)
plot!(xguide="Year", title="My programming time", bg=RGBA(1,1,1,0.7))
vline!([2004, 2011, 2014], line=(:black,0.5,6,:dash), anns=anns, primary=false)
"Powerful convenience"
... you know, just like Julia!
I didn't think we had a visualization library that fulfilled those requirements...
... so I started building.
pyplot()
scatter(rand(100,4), palette = :blues, shape=:auto, markersize=(5:5:20)', alpha=0.2)
histogram(randn(100,4), layout=4, legend=false)
unicodeplots()
plot(cumsum(randn(1000)))
+------------------------------------------------------------+ 16 | d| y1 | ,"| |...,. ., | | |]@Fl| |\ |P | ||'`'l . ||]aW | |/ |. . l d.],,/`|"/ | |L____@__J__________________@____________________1O1@L1_____.| | |1."L. W@]N | \ || | | \W F| ,/ "|. |` |` | | / l. @ | .] | | ' || .` . // | | | / l .[ | | | . | | .. , /| | | l\. @\ | || a[|.uW@ Mda @/ | | ]P| .aPO .` \]|\ldl'O@@`""U` | | " l,WT ", / |/ "'[' "T` \ | | |U` \ | || ` ' | | ' |,` "` | | \/ | -32 | |\ | +------------------------------------------------------------+ 0 1000
display
called when returned to the REPLpng("tmp")
png("tmp.png")
png(current(), "tmp")
savefig("tmp.png")
savefig(current(), "tmp.png")
writemime(open("tmp.png","w"), MIME("image/png"), current())
Convenient shorthands and alternative names
pyplot()
y = rand(10)
# equivalent:
plot(y, c = :red, ms = 10, shape = :^, yscale = :log)
plot(y, seriescolor = :red, markersize = 10, markershape = :utriangle, yscale = :log10)
Set related attributes all at once and let Plots guess what you want
# equivalent:
plot(y, linewidth = 3, linealpha = 0.5, linecolor = :blue, linestyle = :dot,
markersize = 10, markeralpha = 0.8, markercolor = :red,
markerstrokewidth = 10, markerstrokecolor = :black, markerstrokealpha = 0.2,
xlims = (0,15), xticks = 2:3:10)
plot(y, line = (3,0.5,:blue,:dot), marker = (10,0.8,:red,stroke(10,:black,.2)), xaxis = ((0,15),2:3:10))
# Right
p1 = plot(rand(10,4), label = ["A" "B" "C" "D"])
# WRONG
p2 = plot(rand(10, 4), label = ["A","B","C","D"])
plot(p1, p2)
If there's not enough values, they are cycled, or repeated.
plot(rand(10,4), marker = (8, [:red :blue]))
stypes = [:path, :step, :sticks, :scatter, :bar, :hline, :vline, :histogram, :density]
plot(randn(20,1), st = stypes, layout = length(stypes), w = 2, m = 4)
plot!(ticks=nothing, leg=false, title=stypes', size=(700,500))
gr()
x = linspace(-5,5,50)
y = linspace(-3,10,30)
f(x,y) = sin(x) + x*cos(y)^2
sts = [:contour, :heatmap, :surface, :wireframe]
plot(x, y, f, st = sts, layout = length(sts))
grid(numrows, numcols)
widths
and heights
accept vectors of percentages.pyplot()
plot(rand(10,6), layout = grid(2, 3, widths=[0.1,0.7,0.2]))
@layout
macro¶From a nested array construction, create arbitrarily complex layouts with sizing and subplot names.
Sizing and alignment is relative to the "plot area", not the "subplot", so axes are lined up nicely.
p = plot(rand(10,9), layout = @layout [
a{0.2w} b
c{0.7h} grid(2,2)
d e
])
p.spmap[:c][:yaxis].d[:guide] = "Subplot C"; p
Position using a bounding box of absolute and/or relative coordinates.
Values to pass to inset_subplots:
plot([x->1/x, sin], 1, 15, fill=(0, 0.3, :orange), leg=false,
title = ["Subplot A" "Subplot B"], titleloc = [:left :right],
inset = (1, bbox(0, 0, 0.6, 200px, :right)))
Transform data and attributes.
Users and package developers need only import RecipesBase.jl to define new recipes.
using Plots, PlotDocs
PlotDocs.pretty_print_expr(STDOUT, macroexpand(:(
@recipe function f(v::Float64, optional::Int = 10; key = :value)
attr1 := 20
attr2 --> :blue
v
end
)))
function RecipesBase.apply_recipe(d::Dict{Symbol,Any},v::Float64,optional::Int=10; issubplot=false) if RecipesBase._debug_recipes[1] println("apply_recipe args: ",Any[:(v::Float64),:(optional::Int=10)]) end begin key = get!(d,:key,:value) end series_list = RecipesBase.RecipeData[] func_return = begin d[:attr1] = 20 get!(d,:attr2,:blue) v end if func_return != nothing push!(series_list,RecipesBase.RecipeData(d,RecipesBase.wrap_tuple(func_return))) end series_list end
# build the nodes and adjacency matrix for the seriestype heirarchy
sts = [:shape, :bar, :histogram, :heatmap, :histogram2d, :marginalhist]
n = length(sts)
x, y = 0.45*[-1,-1,-1,1,1,0], [0,1,2,0,1.5,3]
totw, toth = 2, 4
adjmat = zeros(n,n)
for (i,j) in enumerate([2,3,6,5,6])
adjmat[j,i] = 1
end
# create a graph of the adjacency matrix, where coordinates of the nodes are give by `func`
plt = graphplot(adjmat, func = _ -> (x,y,nothing), xlim=(-1,1), ylim=(-.5,3.5),
ms=0, w=3, leg=false, yflip=true, curvature_scalar=0.1,
ann=(0,3,text("marginalhist",:hcenter,:top)))
# sneakiness... add an inset subplot for the 5 recipes under marginalhist
args = Any[(randn(10),), (randn(10),), (randn(1000),), (randn(10,5),), (randn(1000),randn(1000))]
for (i,st) in enumerate(sts[1:n-1])
xi, yi = (x[i]-(-1))/totw, (y[i]-(-0.5))/toth
plot!(args[i]..., subplot=i+1, inset = (1, bbox(xi*w-100px, yi*h-25px, 200px, 50px)),
ticks=nothing, st=st, leg=false, title=st)
end
plt
When to use: custom visualization of user types
Notes: user recipes are processed early in the pipeline and can override dispatch of inputs.
Return the input arguments for future dispatch.
Example: Distributions
using Distributions
function default_range(dist::Distribution, n = 4)
μ, σ = mean(dist), std(dist)
linspace(μ - n*σ,μ + n*σ, 100)
end
@recipe function f(dist::Distribution, x = default_range(dist); func = pdf)
delete!(d, :func)
y = map(xi -> func(dist, xi), x)
seriestype --> :path
x, y
end
plot(Gamma(2), 0:0.1:10, fill=(0,0.3,:orange), m=:c)
When a data type is a drop-in replacement for something else.
Example: SymPy
In SymPy, massive functionality was enabled with two lines of code:
@recipe f{T<:Sym}(::Type{T}, v::T) = lambdify(v)
@recipe f{S<:AbstractVector{Sym}}(::Type{S}, ss::S) = Function[lambdify(s) for s in ss]
type MyWrapper; v; end
@recipe f(wrapper::MyWrapper) = wrapper.v
surface(MyWrapper(rand(10,20)))
After user recipes and type recipes have been processed, but before the plot is built.
Build subplots/layouts.
Example: Marginal Histograms (from PlotRecipes.jl)
using PlotRecipes
n = 10000
marginalhist(1./(.3+rand(n)), randn(n), density=true, fill=(0,:orange), margin=1mm)
For a given series (such as a line or scatter plot), transform into series of composed of lower-level components.
Example: Box Plot
A boxplot is composed of shapes and scatters. First grouped by x-value, then aggregated and summarized.
import RDatasets
singers = RDatasets.dataset("lattice","singer")
violin(singers,:VoicePart,:Height,marker=(0.2,:blue,stroke(0)))
boxplot!(singers,:VoicePart,:Height,marker=(0.3,:orange,stroke(2)))
Find me:
Other projects and organizations I'm involved with:
Need a consultant, co-founder, or employee? I'm open to exceptional opportunities.