Legend items with italic parts in AlgebraOfGraphics

Is there a way to display legend items in AoG that are made of rich text objects? For example, in biology it is usual for species names to be written in italics. It seems the best way to do this with Makie is to use RichText. However, this causes problems when you try to use these elements in legends using AoG, see below for a minimal working example:

using CairoMakie, Makie, AlgebraOfGraphics, DataFrames

df = DataFrame(x = [1,2,3,4], y = [1,2,3,4], r = ["Ec", "Vn", "Ec", "Vn"])

rf = [
    "Ec" => rich(rich("E. coli", font = :italic), " MG1655"), 
    "Vn" => rich("V. natriegens", font = :italic),
]

data(df) * mapping(:x, :y, color = :r => renamer(rf) => "Species") * visual(Scatter) |> draw

which gives the error:

ERROR: MethodError: no method matching isless(::Makie.RichText, ::Makie.RichText)
The function `isless` exists, but no method is defined for this combination of argument types.

A potential fix for this is to extend some base functions allowing sorting on RichText objects:

import Base: isless, isequal
Base.isequal(a::Makie.RichText, b::Makie.RichText) = a == b
Base.isless(a::Makie.RichText, b::Makie.RichText) = Base.isless(String(a), String(b))

This is also reported in an issue on Github: renamer returning RichText causes issues · Issue #695 · MakieOrg/AlgebraOfGraphics.jl · GitHub. Note, this problem is not just restricted to the renamer function, but rather any time a RichText object is used when a sort is called internally somewhere.

I tried making a small fix for this, but these extensions break other plots as reported in the issue.

Does anyone have a way to address this issue? Is there a “good” workaround?

LaTeX to the rescue.

using CairoMakie, AlgebraOfGraphics, DataFrames, LaTeXStrings

df = DataFrame(
    x = [1, 2, 3, 4],
    y = [1, 2, 3, 4],
    r = ["Ec", "Vn", "Ec", "Vn"],
)

rf = [
    "Ec" => L"\mathit{E.\ coli}\ \mathrm{MG1655}",
    "Vn" => L"\mathit{V.\ natriegens}",
]

plt =
    data(df) *
    mapping(:x, :y, color = :r => renamer(rf) => "Species") *
    visual(Scatter)

draw(plt)

Thank you, this works!

For reference: I also set the font to Gyre to match the rest of Makie (I don’t like Computer Modern)

using CairoMakie, Makie, AlgebraOfGraphics, DataFrames, LaTeXStrings, MathTeXEngine
set_texfont_family!(FontFamily("TeXGyreHeros"))

df = DataFrame(x = [1,2,3,4], y = [1,2,3,4], r = ["Ec", "Vn", "Ec", "Vn"])

rf = [
    "Ec" => L"\mathit{E.\ coli}\ \mathrm{MG1655}",
    "Vn" => L"\mathit{V.\ natriegens}",
]

data(df) * mapping(:x => "This is the x label", :y, color = :r => renamer(rf) => "Species") * visual(Scatter) |> draw

The problem here is just that I haven’t decided yet if rich text should be sortable or not, given that it has all this metadata. How should two rich text objects with the same text but different metadata be sorted? Maybe it doesn’t matter if metadata is ignored but I feared just being sloppy about it might have some unintended consequences in other places where isless is used.

You can always use the categories mechanism in the scale settings to relabel categories. The good thing there is that it’s after scale fitting, so the labels have already been sorted, so there’s no isless problem with rich text. You can still sort there if you want, though, it’s quite flexible. The renamer mechanism was just the only way to do it before scales were introduced.

Great, this works also! Thanks :slight_smile:

Yep, after looking at Makie/src/richtext.jl I also realized why these definitions were missing, it is not clear at all what the ordering should be… However, the categories solution solves it, so probably not necessary to make any decision :slight_smile:

Here is the code for those interested:

using CairoMakie, Makie, AlgebraOfGraphics, DataFrames

df = DataFrame(x = [1,2,3,4], y = [1,2,3,4], r = ["Ec", "Vn", "Ec", "Vn"])

rf2 = [
    "Ec" => rich(rich("E. coli", font = :italic), " MG1655"), 
    "Vn" => rich("V. natriegens", font = :italic),
]

data(df) * mapping(:x => "This is the x label", :y, color = :r => "Species") * visual(Scatter) |> draw(
    scales(
        Color = (; categories = rf2,)
    ), 
)