Beautiful Makie Gallery

There are some plots I don’t know how to make. A ridge plot is kind of 3D.

https://edav.info/ridgeline.html

here in the documentation of makie, http://makie.juliaplots.org/stable/plotting_functions/density.html#Gradients

6 Likes

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.

1 Like

Ternary plots are used for example for phase diagrams.

Soil composition too

image

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

1 Like

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

4 Likes

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.

9 Likes

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
1 Like

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

15 Likes

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/

30 Likes

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.