There are some plots I don’t know how to make. A ridge plot is kind of 3D.
here in the documentation of makie, http://makie.juliaplots.org/stable/plotting_functions/density.html#Gradients
Ternary plots in Makie would so useful for a class I am giving next quarter. If someone has the skills to implement it, that would be awesome.
What type of ternary plots? Scatter and quiver plots are pretty easy to code up a function for to fit your own purposes. I have no idea how making proper axes works, so i don’t know about heatmaps and stuff that normally fills a rectangle. I’ve put together a 3d surface plot, to plot the density of (the proportions of) 3 random variables. But it’s a bit clunky e.g. axis ticks will need to be adjusted with any camera move.
using CairoMakie,KernelDensity, Distributions
#helper functions
tern_it(x,y)= [x + 0.5 * y,sqrt(0.75) * y]' # convert proportions to cartisian coords on a triangle
closest(a,b) = findmin(abs.(a .-b))[2]
set_dens(a,b,k) = k.density[closest(a,collect(k.x)),
closest(b,collect(k.y))]
# generate data, convert it and get kernel density estimate
dat = rand(Dirichlet([2,1,0.5]),700)'
tern_dat =vcat(tern_it.(dat[:,1],dat[:,2])...)
ternKDE = kde(tern_dat)
# matrices f x and y points corresponding to triangle
myrange = collect(LinRange(-0.5,0.5,51))
fives = collect(LinRange(-0.5,0.5,51))
ys = hcat([collect(reverse(LinRange(0,1,51))) for i in 1:51]...) .* sqrt(0.75)
xs = vcat([myrange' .- fives' * i' for i in collect(reverse(LinRange(0,1,51)))]...) .+0.5
# z vales based on kernel density estimate
zs = set_dens.(xs,ys,fill(ternKDE,51,51))
# prep tick positions
uptick = [0.2,0.4,0.6,0.8] #.*sqrt(0.75)
sidetick = [0.2,0.4,0.6,0.8] .* 0.5 .- 0.1
lticks = Point.([(sidetick[i],uptick[i],-0.25) for i in 1:4])
# make the plot
f = Figure()
ax =Axis3(f[1,1])
# Draw axes
lines!(ax,[0,0.5,1,0],[0,sqrt(0.75),0,0],[0,0,0,0], color = :black)
#add ticks
annotations!(string.([0,0.2,0.4,0.6,0.8]), Point.([(0,-0.1,-0.25),
(0.2,-0.1,-0.25),(0.4,-0.1,-0.25),(0.6,-0.1,-0.25),(0.8,-0.1,-0.25)]))
annotations!(string.([0.2,0.4,0.6,0.8]), lticks)
# plot the data
surface!(ax,(xs) ,ys , zs , color = zs, colormap = :jet)
wireframe!(ax,(xs ) ,ys , zs )
hidedecorations!(ax)
hidespines!(ax)
f
Incidentally, there are some white patches where I wouldn’t expect them given the elevation. Not sure if it’s a problem with my code or a lighting thing. Still new to Makie.
Ternary plots are used for example for phase diagrams.
Soil composition too
Thanks both. I have my own uses as well, evolutionary game theory trajectories and density based visualisation of simulation results. I was just wondering what @juliohm specific use case is.
@EvoArt my use case is with compositional data: GitHub - JuliaEarth/CoDa.jl: Compositional data analysis in Julia
We need to plot contours of compositions, scatters and heatmaps basically.
Maybe a line plot using a symlog scale would also be good to include.
https://matplotlib.org/stable/gallery/scales/symlog_demo.html
Seems like there’s a fair bit of interest in ternary plots in Makie. Where’s the best place to discuss an implementation?
you can add ideas to this [Feature request] Ternary plots · Issue #906 · JuliaPlots/Makie.jl · GitHub
For those enthusiastic users, here a small challenge. The following was done with Gnuplot.jl (~ 10 lines of code). Could Makie do it? more or less with the same amount of lines. All the data is self contained in the image.
Where is the Gnuplot.jl code for this one? (I think the figure only includes the data processing code).
Indeed, I didn’t include the code for the figure. That will be too easy. Nevertheless, I will do it later. Now, I’m out, so no access to my laptop.
Ok. No, I was too generous with Gnuplot, is more than 10 lines (plus some auxiliary functions for the correct position of the text). Either way, here is the code (update version).
using Gnuplot, HTTP, CSV, Images, Suppressor
using Statistics, Colors, ColorSchemes
using DataFrames, DataFramesMeta
using Lazy: @>
import Gnuplot: PlotElement, DatasetText
data_url = "https://bit.ly/3kmlGn2"
@suppress begin
global astronauts
astronauts = CSV.File(HTTP.download(data_url)) |> DataFrame
end
offhr, rPts, α = 3.5, 40, 35
astro = @> begin
astronauts
@orderby(:year_of_mission)
@where(:total_eva_hrs .> 0.0)
@transform(ntotEVA = :total_eva_hrs/ maximum(:total_eva_hrs))
@transform(θ = LinRange(0, 2π - 2π/length(:name), length(:name)))
@transform(evaM = log10.((60 * :eva_hrs_mission/median(60 * :eva_hrs_mission)) .+ offhr))
@transform(xM = rPts * :evaM .* cos.(:θ), yM = rPts * :evaM .* sin.(:θ))
@transform(xMnm = rPts * (:evaM .- :total_number_of_missions .* :evaM./α) .* cos.(:θ))
@transform(yMnm = rPts * (:evaM .- :total_number_of_missions .* :evaM./α) .* sin.(:θ));
end
tierra = "https://climate.nasa.gov/system/internal_resources/details/original/309_ImageWall5_768px-60.jpg"
@suppress begin
global imgEarth
tierra = HTTP.download(tierra)
imgEarth = load(tierra);
end
x, y = astro.xM, astro.yM # end points
xs, ys = astro.xMnm, astro.yMnm # short lines starts
xo = yo = zeros(length(x)) # origin
xnb, ynb = 95 * cos.(astro.θ), 95 * sin.(astro.θ)
xne, yne = 110 * cos.(astro.θ), 110 * sin.(astro.θ)
ps, cp = 0.5 .+ 3astro.ntotEVA, astro.total_eva_hrs # point size, color palette
gridLines = LinRange(log10(offhr), maximum(astro.evaM), 6)
horas = (10 .^ gridLines .- offhr)*median(60*astro.eva_hrs_mission)/60
xg = [rPts * gl .* cos.(astro.θ) for gl in gridLines]
yg = [rPts * gl .* sin.(astro.θ) for gl in gridLines]
labM = "Duration of extravehicular activities during the mission in hours"
labT = "Total duration of all extravehicular activities in hours"
# Auxiliary functions
function segments(xs, xf, ys, yf, cp, ps; αc = "00", pal = :rainbow1)
out = Vector{Gnuplot.PlotElement}()
cpal = get(colorschemes[pal], cp, :extrema)
for k in 1:length(xs)
push!(out, PlotElement(data=DatasetText([xs[k], xf[k]], [ys[k], yf[k]]),
plot="w l not lc '#$(αc)$(hex(cpal[k]))' lw $(ps[k]) "))
end
out
end
function orot_text(θ; sdeg = 180) # θ in radians
if (θ*sdeg/π) > (sdeg/2) && θ*(sdeg/π) < (sdeg/2 + sdeg)
rot_θ = θ*sdeg/π - sdeg
right_left = "right"
else
rot_θ = θ*sdeg/π
right_left = "left"
end
rot_θ, right_left
end
function richtext(intext, r, θ; c = "#DFE6FF", fs = 10)
x, y = r * cos.(θ), r *sin.(θ)
tmp, current_text = "", " "
for i in 1:length(intext)
if current_text != intext[i]
k = 1974 == intext[i] ? i-1 : i # due to a small overlap
rot_θ, r_l = orot_text(θ[k])
set_label = "set label '$(intext[i])' at $(x[k]), $(y[k])"
frm_label = " font ',$(fs)' $(r_l) rot by $(rot_θ) front tc '$(c)'; "
tmp = tmp * set_label * frm_label
current_text = intext[i]
end
end
tmp
end
cmpal = :spring
bgrecta = "set obj rect from screen 0,0 to screen 1,1"
bgcolor = " behind fc 'black' fs solid noborder"
bgcolor = bgrecta * bgcolor
# the actual plot
@gp xrange = (-3.5rPts ,3.5rPts ) yrange = (-3.5rPts ,3.5rPts ) :-
@gp :- "set size square" :-
@gp :- "set colorbox horiz user origin .65,.13 size .28,.01 " :-
@gp :- "set border lw 2 lc 'white'" "set tics font ',35' tc 'white'" :-
@gp :- "set cblabel '$(labT)' offset 0.0,-2 font ',45' tc 'white'"
@gp :- "unset border" "unset ytics" "unset xtics" :-
@gp :- recipe(RGBA.(imgEarth), "flipy rot=0deg center=(0,0) dx = 0.05 dy =0.05") :-
@gp :- x y ps cp "w p not pt 7 ps var lc palette" palette(cmpal) :-
@gp :- segments(xs, x, ys, y, cp, ps/2; αc = "00", pal = cmpal) :-
@gp :- segments(xo, xs, yo, ys, cp, ps/2; αc = "CC", pal = cmpal) :-
@gp :- segments(xnb, xne, ynb, yne, cp, ps/3; αc = "4D", pal = cmpal) :-
@gp :- richtext(astro.year_of_mission, 60, astro.θ; fs = 30) :-
@gp :- richtext(astro.mission_title, 75, astro.θ; fs = 13)
@gp :- richtext(astro.name, 95, astro.θ; fs = 14)
for (indx, gl) in enumerate(gridLines)
xg, yg = rPts * gl .* cos.(astro.θ), rPts * gl .* sin.(astro.θ)
hrs = Int64(round(horas[indx], digits = 0))
set_hr = "set label '$(hrs)' at $(xg[1] + 0.5), $(y[1]+2) "
frm_hr = " font ',32' front tc '#FFDD33'"
@gp :- xg yg "w l lw 0.5 dt '._' lc 'white' not" :-
@gp :- set_hr * frm_hr :-
end
@gp :- [21.76272, rPts + 17] [0,0] "w l lw 2 dt '._' lc '#FFDD33' not"
@gp :- "set label 'evaM' at 47,-2 font ',45' tc '#FFDD33'" :-
@gp :- "set label 'evaM = $(labM)' at graph 0.715,0.16 font ',45' tc '#FFDD33'" :-
@gp :- """set label "ASTRONAUTS' EXTRAVEHICULAR ACTIVITIES" at graph 0.3,0.9 font ',75' front tc '#DFE6FF' """ :-
@gp :- """set label " Visualization by " at graph -0.18,0.1 font ',50' tc "white" """ :-
@gp :- """set label ' \\@LazarusAlon' at graph -0.18,0.1 font ',50' tc "#61AFEF" """ :-
@gp :- """set label " Data - Astronaut Database - Mariya Stavnichuk and Tatsuya Corlett" at graph -0.18,0.09 font ',30' tc "white" """ :-
@gp :- bgcolor
Gnuplot.save(term = "pngcairo enhanced size 7016,4960", output = "astronautas_alon_spring.png") # 4960 x 7016
# The font size apperance depends on the output size, which is not good (CairoMakie is better at doing that).
# For pdf outputs is more stable but the file is bigger.
Status `~/Documents/FunArt/astro/Project.toml`
[336ed68f] CSV v0.8.4
[35d6a980] ColorSchemes v3.11.0
[5ae59095] Colors v0.12.6
[a93c6f00] DataFrames v0.22.7
[1313f7d8] DataFramesMeta v0.6.0
[cd3eb016] HTTP v0.9.5
[916415d5] Images v0.23.3
[50d2b5c4] Lazy v0.15.1
[fd094767] Suppressor v0.2.0
[10745b16] Statistics
I was only motivated for half of it, but that’s how you could start:
# i added these at the top
@transform(align = tuple.(ifelse.(pi/2 .< :θ .< 3pi/2, ^(:right), ^(:left)), ^(:center)))
@transform(texttheta = ifelse.(pi/2 .< :θ .< 3pi/2, :θ .+ pi, :θ))
with_theme(theme_black()) do
f = Figure(resolution = (1000, 1000))
ax = Makie.Axis(f[1, 1],
title = "ASTRONAUTS' EXTRAVEHICULAR ACTIVITIES",
autolimitaspect = 1)
hidespines!(ax)
hidedecorations!(ax)
image!(-20..20, -20..20, rotr90(imgEarth))
n = nrow(astro)
text!(astro.name, position = @.(Point(cos(astro.θ), sin(astro.θ)) * 80),
rotation = astro.texttheta, textsize = 4, align = astro.align)
vehicles = @chain astro begin
@where([true; :ascend_shuttle[2:end] .!= :ascend_shuttle[1:end-1]])
end
text!(vehicles.ascend_shuttle, position = @.(Point(cos(vehicles.θ), sin(vehicles.θ)) * 50),
rotation = vehicles.texttheta, textsize = 4, align = vehicles.align)
limits!(ax, -100, 100, -100, 100)
save("earth.png", f, px_per_unit = 4)
end
that’s actually all that I was missing… haha… the rest is the usual stuff. Nice :D. Here, my final Makie version. https://lazarusa.github.io/BeautifulMakie/withTheme/theme_dark_astronauts/
Thanks, this was very helpful.
Here’s my initial attempt for the latest version of GLMakie.
Only a single color at the moment, and need to show upper/lower values too, but it’s a start.
"""
Parallel Coordinate Plot
# Arguments
- disp : Axis
- data : Matrix
- names : Vector or Tuple of names (for x-axis)
"""
function pcp!(disp, data, names)
k, n = size(data)
limits = extrema.(eachcol(data))
scaled = hcat([(d .- mi) ./ (ma - mi) for (d, (mi, ma)) in zip(eachcol(data), limits)]...)
vlines!(disp, 1:n; color=:black)
for i in 1:k
lines!(disp, scaled[i, :], color=(:blue, 0.1))
end
disp.xticks = (1:n, [string.(names)...])
return disp
end
On Jun 2021 lazarusA posted code for a 3d plot, with perspective.
At the Julia front page an animated 3d plot is apparent, is similar
with perspective possible with Makie.
Might this be generated from data from an input device, like an analog
to digital converter.
With the use of a GPU maybe this could be refreshed at 60 fps or more.