Skip to content

Commit

Permalink
Allow dimensions to be used as AlgebraOfGraphics selectors (#823)
Browse files Browse the repository at this point in the history
* Allow dimensions to be used as AoG selectors

* Add a test from the issue's usecase

* Update plotrecipes.jl

* Fix tests

* Update plotrecipes.jl

* Allow types and constructed dims

Co-authored-by: Rafael Schouten <[email protected]>

* Don't use `DD.DimOrDimType` since that's a union with Symbol

why?

* complex logic to intercept dims that are just the name of the array

* Add a demo to the docs

* DD.name on DimArray is a tuple

* Add AoG to docs Project

* Update docs

* Update plotrecipes.jl

* Doh, forgot to remove `return`

* Don't accept dims that aren't in the dimarray

* Update tests for the last commit's changes

---------

Co-authored-by: Rafael Schouten <[email protected]>
  • Loading branch information
asinghvi17 and rafaqz authored Oct 30, 2024
1 parent 2599b0e commit 7a5c883
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 1 deletion.
6 changes: 5 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,18 @@ TableTraits = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"

[weakdeps]
AlgebraOfGraphics = "cbdf2221-f076-402e-a563-3d30da359d67"
CategoricalArrays = "324d7699-5711-5eae-9e2f-1d82baa6b597"
Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a"

[extensions]
DimensionalDataAlgebraOfGraphicsExt = "AlgebraOfGraphics"
DimensionalDataCategoricalArraysExt = "CategoricalArrays"
DimensionalDataMakie = "Makie"

[compat]
Adapt = "2, 3.0, 4"
AlgebraOfGraphics = "0.8"
Aqua = "0.8"
ArrayInterface = "7"
BenchmarkTools = "1"
Expand Down Expand Up @@ -74,6 +77,7 @@ Unitful = "1"
julia = "1.9"

[extras]
AlgebraOfGraphics = "cbdf2221-f076-402e-a563-3d30da359d67"
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
Expand All @@ -99,4 +103,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

[targets]
test = ["Aqua", "ArrayInterface", "BenchmarkTools", "CategoricalArrays", "ColorTypes", "Combinatorics", "CoordinateTransformations", "DataFrames", "Distributions", "Documenter", "GPUArrays", "ImageFiltering", "ImageTransformations", "JLArrays", "CairoMakie", "OffsetArrays", "Plots", "Random", "SafeTestsets", "StatsPlots", "Test", "Unitful"]
test = ["AlgebraOfGraphics", "Aqua", "ArrayInterface", "BenchmarkTools", "CategoricalArrays", "ColorTypes", "Combinatorics", "CoordinateTransformations", "DataFrames", "Distributions", "Documenter", "GPUArrays", "ImageFiltering", "ImageTransformations", "JLArrays", "CairoMakie", "OffsetArrays", "Plots", "Random", "SafeTestsets", "StatsPlots", "Test", "Unitful"]
1 change: 1 addition & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[deps]
AlgebraOfGraphics = "cbdf2221-f076-402e-a563-3d30da359d67"
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
Expand Down
48 changes: 48 additions & 0 deletions docs/src/plots.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and axes in the right places:

```@example Makie
using DimensionalData, CairoMakie
CairoMakie.activate!(type = :svg) # hide
A = rand(X(10:10:100), Y([:a, :b, :c]))
Makie.plot(A; colormap=:inferno)
Expand All @@ -32,13 +33,60 @@ favours the categorical variable for the X axis:
```@example Makie
Makie.rainclouds(A)
```

## AlgebraOfGraphics.jl

AlgebraOfGraphics.jl is a high-level plotting library built on top of Makie.jl that provides a declarative algebra for creating complex visualizations, similar to `ggplot2`'s "grammar of graphics" in R. It allows you to construct plots using algebraic operations like `*` and `+`, making it easy to create sophisticated graphics with minimal code.

Any `DimensionalArray` is also a `Tables.jl` table, so it can be used with `AlgebraOfGraphics.jl` directly. You can indicate columns in `mapping` with Symbols directly (like `:X` or `:Y`), **or** you can use the `Dim` type directly (like `X` or `Y`)!

!!! note
If your dimensional array is not named, then you can access the data as the **`:unnamed`** column.
Otherwise, the data is accessible by its name.

Let's start with a simple example, and plot a 2-D dimarray as a scatter plot, colored by its value.

```@example Makie
using DimensionalData, AlgebraOfGraphics, CairoMakie
CairoMakie.activate!(type = :svg) # hide
A = DimArray(rand(10, 10), (X(1:10), Y(1:10)), name = :data)
data(A) * mapping(X, Y; color = :data) * visual(Scatter) |> draw
```

Don't restrict yourself to standard visualizations! You can use all of AlgebraOfGraphics' features.

Let's plot each X-slice, faceted in Y:

```@example Makie
data(A) * mapping(X, :data; layout = Y => nonnumeric) * visual(Lines) |> draw
```

This approach is also applicable to `DimStack`s, since they also convert to DimTables. Let's see an example here.

We'll construct a DimStack with the `:data` layer being our DimArray `A`, and an X-only variable `:color` that we'll use to color the line.

```@example Makie
color_vec = DimVector(1:10, X)
ds = DimStack((; data = A, color = color_vec))
data(ds) * mapping(X, :data; color = :color, layout = Y => nonnumeric) * visual(Lines) |> draw
```

!!! note
If you wish to subset your DimArray, you can't pass selectors like `X(1 .. 2)` to AlgebraOfGraphics.
Instead, subset the DimArray you pass to `data` - this is a very cheap operation.

## Test series plots

### default colormap

```@example Makie
B = rand(X(10:10:100), Y([:a, :b, :c, :d, :e, :f, :g, :h, :i, :j]))
Makie.series(B)
```

### A different colormap
The colormap is controlled by the `color` argument, which can take as an input a named colormap, i.e. `:plasma` or a list of colours.

Expand Down
56 changes: 56 additions & 0 deletions ext/DimensionalDataAlgebraOfGraphicsExt.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
module DimensionalDataAlgebraOfGraphicsExt

import AlgebraOfGraphics as AoG
import DimensionalData as DD

# We can't use `DD.Dimensions.DimOrDimType` because that's a union with Symbol,
# which causes an ambiguity and would otherwise override the dedicated Symbol
# `select` method in AoG.
const DimOrType = Union{DD.Dimensions.Dimension, Type{<: DD.Dimensions.Dimension}}

#=
This extension allows DimensionalData `Dimension` types to be used
as column selectors for AlgebraOfGraphics.jl.
Specifically, this implements the `AoG.select` method for `Columns`
objects, which is the type that `AlgebraOfGraphics.data` returns.
=#

# The generic selector, to enable this to work even in `DataFrame(dimarray)`
function AoG.select(data::AoG.Columns, dim::DimOrType)
name = DD.name(dim)
v = AoG.getcolumn(data.columns, Symbol(name))
return (v,) => identity => AoG.to_string(name) => nothing
end

# The specific selector for `DimTable`s.
# This searches the dimensions of the dimtable for the appropriate dimension,
# so that e.g. `X` also applies to any `XDim`, and so forth.
function AoG.select(data::AoG.Columns{<: DD.AbstractDimTable}, dim::DimOrType)
# Query the dimensions in the table for the dimension
available_dimension = DD.dims(data.columns, dim)
# If the dimension is not found, it might be the name of the
# underlying array.
name = if isnothing(available_dimension)
if DD.name(dim) in DD.name(parent(data.columns))
error(
"Dimension $dim not found in DimTable with dimensions $(DD.dims(data.columns)), **but** it is" *
(length(DD.name(parent(data.columns))) > 1 ? "a layer of the parent array" : "the name of the parent array") *
".\n\nYou can pass this directly as a `Symbol`, i.e. **`:$(DD.name(dim))`**."
)
else
error("Dimension $dim not found in DimTable with dimensions $(DD.dims(data.columns)), and neither was it the name of the array ($(DD.name(parent(data.columns)))).")
end
else
# The dimension was found, so use that name.
DD.name(available_dimension)
end
# Get the column from the table
v = AoG.getcolumn(data.columns, Symbol(name))
# Return the column, with the appropriate labels
return (v,) => identity => AoG.to_string(name) => nothing
end

end
28 changes: 28 additions & 0 deletions test/plotrecipes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -378,3 +378,31 @@ end
# fig, ax, _ = M.volumeslices(A3abc; z=:a)
# M.volumeslices!(ax, A3abc;z=:a)
end

@testset "AlgebraOfGraphics" begin
using AlgebraOfGraphics, CairoMakie
using DimensionalData

# 1d
A1 = rand(X(1:5); name=:test)
A1c = rand(X('a':'e'); name=:test)

@testset "1d, symbol indexing" begin
@test_nowarn data(A1) * mapping(:X, :test) * visual(CairoMakie.Lines) |> draw
@test_nowarn data(A1c) * mapping(:X, :test) * visual(CairoMakie.Lines) |> draw
end

@testset "1d, dim indexing" begin
@test_nowarn data(A1) * mapping(X, :test) * visual(CairoMakie.Lines) |> draw
@test_nowarn data(A1c) * mapping(X, :test) * visual(CairoMakie.Lines) |> draw
end

A3 = DimArray(rand(21, 5, 4), (X, Y, Dim{:p}); name = :RandomData)

@testset "3d faceting" begin
@test_nowarn data(A3) * visual(CairoMakie.Heatmap) * mapping(X, :RandomData, Dim{:p}, layout = Y => nonnumeric) |> draw
fg = data(A3) * visual(CairoMakie.Heatmap) * mapping(X, :RandomData, Dim{:p}, layout = Y => nonnumeric) |> draw
# Test that the number of axes is equal to the size of A3 in the y dimension.
@test sum(x -> x isa AlgebraOfGraphics.Makie.Axis, AlgebraOfGraphics.Makie.contents(fg.figure.layout)) == size(A3, Y)
end
end

0 comments on commit 7a5c883

Please sign in to comment.