Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Different categorical X/Y scales across facets #550

Open
jkrumbiegel opened this issue Sep 5, 2024 · 3 comments
Open

Different categorical X/Y scales across facets #550

jkrumbiegel opened this issue Sep 5, 2024 · 3 comments

Comments

@jkrumbiegel
Copy link
Member

using layout = dims(1) one can currently create a facet layout where each x or y axis is separate, but only if the data is continuous:

df = (
    x1 = [1, 2, 3],
    x2 = [4, 5, 6],
    y1 = [7, 8, 9],
    y2 = [10, 11, 12],
)

data(df) * mapping([:x1, :x2], [:y1, :y2], layout = dims(1)) * visual(Scatter) |> draw(scales(Layout = (; palette = [(1, 1), (2, 1)])))
image

(never mind the empty big legend, that's a separate bug)

This technique doesn't work with categorical data though, because the internal processing works such that categorical scales are determined together across all facets (unlike continuous, which are determined per facet first and potentially merged later).

df = (
    x1 = ["A", "B", "C"],
    x2 = ["D", "E", "F"],
    y1 = [7, 8, 9],
    y2 = [10, 11, 12],
)

data(df) * mapping([:x1, :x2], [:y1, :y2], layout = dims(1)) * visual(Scatter) |> draw(scales(Layout = (; palette = [(1, 1), (2, 1)])))
image

The X/Y scales are actually special because they can be separated, unlike the scales needing a legend or colorbar (unless we use one legend/colorbar per facet, but at that point we can also just plot multiple plots). It would be nice if we could visualize completely unrelated categorical scales on different facets while still sharing things like continuous colorbars or color groups.

@jkrumbiegel
Copy link
Member Author

Seems not to be easily possible in base ggplot either, however I found this extension as an inspiration https://ggforce.data-imaginist.com/reference/facet_matrix.html

@jkrumbiegel
Copy link
Member Author

One difficulty in implementation is that the scales mechanism doesn't expect scales split into facets, rather it expects one set of attributes per scale. For example, if two different categorical columns were split over a row layout, then the categories setting etc. for that scale would have to differ per facet. Maybe it would need to take an array of settings, but that sounds complicated.

@jkrumbiegel
Copy link
Member Author

A manual workaround is possible, where we transform the columns in the dataset into integers representing the categories. We offset the indices so far that with unlinked axes, it's not visible that there's actually just one scale, it will look like they are distinct. Then we do the work of axis labeling manually (which usually AoG would do for us with categorical data). It's not pretty but it's also not super complex to do if this kind of plot is needed:

df = DataFrame(
    color = ["red", "green", "blue"],
    size = ["small", "medium", "big"],
    shape = ["square", "circle", "triangle"],
    y1 = 1:3,
    y2 = 4:6,
    y3 = 7:9,
)

function categorical_to_int(vec)
    d = Dict{eltype(vec),Int}()
    indices = map(vec) do el
        get!(d, el) do
            length(d) + 1
        end
    end
    prs = collect(pairs(d))
    cats = first.(prs)[last.(prs)]
    return indices, cats
end

function cat_transform(df, cols)
    outdf = copy(df)

    offset = 0
    tickvalues = Int[]
    ticklabels = []
    for sym in cols
        indices, cats = categorical_to_int(outdf[!, sym])
        outdf[!, sym] = indices .+ offset
        append!(ticklabels, cats)
        append!(tickvalues, (1:length(cats)) .+ offset)
        offset += 10 * length(cats)
    end

    return outdf, tickvalues, ticklabels
end

df2, tickvalues, ticklabels = cat_transform(df, [:color, :size, :shape])

data(df2) *
    mapping([:color, :size, :shape], [:y1, :y2, :y3], layout = dims(1)) *
    visual(Scatter) |> draw(; axis = (; xticks = (tickvalues, ticklabels)))
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant