Unconventional swarm plots
You can use swarm plots to simply separate scatter markers which share the same x
coordinate, and distinguish them by color and marker type.
The Julia benchmark plot
julia
# Load the required Julia packages
using Base.MathConstants
using CSV
using DataFrames
using AlgebraOfGraphics, SwarmMakie, CairoMakie
using StatsBase, CategoricalArrays
# Load benchmark data from file
benchmarks =
CSV.read(download("https://raw.githubusercontent.com/JuliaLang/Microbenchmarks/master/bin/benchmarks.csv"), DataFrame; header = ["language", "benchmark", "time"])
# Capitalize and decorate language names from datafile
dict = Dict(
"c" => "C",
"julia" => "Julia",
"lua" => "LuaJIT",
"fortran" => "Fortran",
"java" => "Java",
"javascript" => "JavaScript",
"matlab" => "Matlab",
"mathematica" => "Mathematica",
"python" => "Python",
"octave" => "Octave",
"r" => "R",
"rust" => "Rust",
"go" => "Go",
);
benchmarks[!, :language] = [dict[lang] for lang in benchmarks[!, :language]]
# Normalize benchmark times by C times
ctime = benchmarks[benchmarks[!, :language] .== "C", :]
benchmarks = innerjoin(benchmarks, ctime, on = :benchmark, makeunique = true)
select!(benchmarks, Not(:language_1))
rename!(benchmarks, :time_1 => :ctime)
benchmarks[!, :normtime] = benchmarks[!, :time] ./ benchmarks[!, :ctime];
# Compute the geometric mean for each language
langs = [];
means = [];
priorities = [];
for lang in benchmarks[!, :language]
data = benchmarks[benchmarks[!, :language] .== lang, :]
gmean = geomean(data[!, :normtime])
push!(langs, lang)
push!(means, gmean)
if (lang == "C")
push!(priorities, 1)
elseif (lang == "Julia")
push!(priorities, 2)
else
push!(priorities, 3)
end
end
# Add the geometric means back into the benchmarks dataframe
langmean = Dict(langs .=> tuple.(means, priorities))
benchmarks.geomean = first.(getindex.((langmean,), benchmarks.language))
benchmarks.priority = last.(getindex.((langmean,), benchmarks.language))
# Put C first, Julia second, and sort the rest by geometric mean
sort!(benchmarks, [:priority, :geomean]);
langs = CategoricalArray(benchmarks.language)
bms = CategoricalArray(benchmarks.benchmark)
f, a, p = beeswarm(
langs.refs, benchmarks.normtime;
color = bms.refs,
colormap = :Set2_8,
# markersize = 5,
marker = Circle,
axis = (;
yscale = log10,
xticklabelrotation = 0,
xticklabelsize = 12,
xticksvisible = false,
topspinecolor = :gray,
bottomspinecolor = :gray,
leftspinecolor = :gray,
rightspinecolor = :gray,
ylabel = "Time relative to C",
xticks = (1:length(unique(langs)), langs.pool.levels),
xminorticks = IntervalsBetween(2),
xgridvisible = false,
xminorgridvisible = true,
xminorgridcolor = (:black, 0.2),
yminorticks = IntervalsBetween(5),
yminorgridvisible = true,
),
figure = (; size = (1000, 618),)
)
leg = Legend(f[1, 2],
[MarkerElement(; color = Makie.categorical_colors(:Set1_8, 8)[i], marker = :circle, markersize = 11) for i in 1:length(bms.pool.levels)],
bms.pool.levels,
"Benchmark";
)
f
Benchmarks colored by language
julia
f, a, p = beeswarm(
bms.refs, benchmarks.normtime;
color = langs.refs,
colormap = Makie.Colors.distinguishable_colors(13),#:Set1_9,
# markersize = 5,
marker = Circle,
axis = (;
yscale = log10,
xticklabelrotation = 0,
xticklabelsize = 12,
xticksvisible = false,
topspinecolor = :gray,
bottomspinecolor = :gray,
leftspinecolor = :gray,
rightspinecolor = :gray,
ylabel = "Time relative to C",
xticks = (1:length(unique(bms)), bms.pool.levels),
xminorticks = IntervalsBetween(2),
xgridvisible = false,
xminorgridvisible = true,
xminorgridcolor = (:black, 0.2),
yminorticks = IntervalsBetween(5),
yminorgridvisible = true,
),
figure = (; size = (1000, 618),)
)
leg = Legend(f[1, 2],
[MarkerElement(; color = p.colormap[][i], marker = :circle, markersize = 11) for i in 1:length(langs.pool.levels)],
langs.pool.levels,
"Benchmark";
)
f
Custom markers
julia
# Get logos for programming languages
using CairoMakie
using CairoMakie.FileIO
using Librsvg_jll
function svg_to_img(svg)
mktempdir() do dir
pngpath = joinpath(dir, "img.png")
Librsvg_jll.rsvg_convert() do bin
open(`$bin -o $pngpath`, "w") do io
write(io, svg)
end
end
FileIO.load(pngpath)
end
end
language_logo_url(lang::String) = "https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/$(lowercase(lang))/$(lowercase(lang))-original.svg"
language_to_img(lang::String) = svg_to_img(read(download(language_logo_url(lang)), String))
language_marker_dict = Dict{String, Any}(
[key => language_to_img(key) for key in ("c", "fortran", "go", "java", "javascript", "julia", "matlab", "python", "r", "rust")]
)
language_marker_dict["octave"] = FileIO.load(File{format"PNG"}(download("https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Gnu-octave-logo.svg/2048px-Gnu-octave-logo.svg.png"))) .|> Makie.Colors.ARGB32
language_marker_dict["luajit"] = language_to_img("lua")
language_marker_dict["mathematica"] = read(download("https://upload.wikimedia.org/wikipedia/commons/2/20/Mathematica_Logo.svg"), String) |> svg_to_img
f, a, p = beeswarm(
bms.refs, benchmarks.normtime;
marker = getindex.((language_marker_dict,),lowercase.(benchmarks.language)),
markersize = 11,
axis = (;
yscale = log10,
xticklabelrotation = pi/8,
xticklabelsize = 12,
xticksvisible = false,
topspinecolor = :gray,
bottomspinecolor = :gray,
leftspinecolor = :gray,
rightspinecolor = :gray,
ylabel = "Time relative to C",
xticks = (1:length(unique(bms)), bms.pool.levels),
xminorticks = IntervalsBetween(2),
xgridvisible = false,
xminorgridvisible = true,
xminorgridcolor = (:black, 0.2),
yminorticks = IntervalsBetween(5),
yminorgridvisible = true,
),
figure = (; size = (1000, 618),)
)
leg = Legend(f[1, 2],
[MarkerElement(; marker = language_marker_dict[lowercase(lang)], markersize = 15) for lang in langs.pool.levels],
langs.pool.levels,
"Language";
)
f