Skip to content

Commit

Permalink
Merge ededfb8 into 0750b58
Browse files Browse the repository at this point in the history
  • Loading branch information
asinghvi17 authored Sep 9, 2024
2 parents 0750b58 + ededfb8 commit 9363109
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 29 deletions.
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "DimensionalData"
uuid = "0703355e-b756-11e9-17c0-8b28908087d0"
authors = ["Rafael Schouten <rafaelschouten@gmail.com>"]
version = "0.27.9"
version = "0.27.10"

[deps]
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"
Expand Down Expand Up @@ -57,7 +57,7 @@ InvertedIndices = "1"
IteratorInterfaceExtensions = "1"
JLArrays = "0.1"
LinearAlgebra = "1"
Makie = "0.19, 0.20, 0.21"
Makie = "0.20, 0.21"
OffsetArrays = "1"
Plots = "1"
PrecompileTools = "1"
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,22 @@ DimensionalData.jl provides tools and abstractions for working with datasets tha

DimensionalData is a pluggable, generalised version of [AxisArrays.jl](https://github.com/JuliaArrays/AxisArrays.jl) with a cleaner syntax, and additional functionality found in NamedDims.jl. It has similar goals to pythons [xarray](http://xarray.pydata.org/en/stable/), and is primarily written for use with spatial data in [Rasters.jl](https://github.com/rafaqz/Rasters.jl).

> [!IMPORTANT]
> INSTALLATION
## Installation

```shell
julia>]
pkg> add DimensionalData
```

## Quick start

Start using the package:

```julia
using DimensionalData
```

The basic syntax is:
The basic syntax to create a dimensional array (`DimArray`) is:

```julia
A = DimArray(rand(50, 31), (X(), Y(10.0:40.0)));
Expand Down Expand Up @@ -76,7 +77,7 @@ A[Y=1:10, X=1]
19.0 0.605331
```

One can also subset by lookup, using a `Selector`, lets try `At`:
One can also subset by lookup, using a `Selector`, let's try `At`:

```julia
A[Y(At(25))]
Expand Down Expand Up @@ -125,8 +126,7 @@ using DimensionalData
using DimensionalData.Lookup, DimensionalData.Dimensions
```

> [!IMPORTANT]
> Alternative Packages
## Alternative packages

There are a lot of similar Julia packages in this space. AxisArrays.jl, NamedDims.jl, NamedArrays.jl are registered alternative that each cover some of the functionality provided by DimensionalData.jl. DimensionalData.jl should be able to replicate most of their syntax and functionality.

Expand Down
93 changes: 72 additions & 21 deletions ext/DimensionalDataMakie.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,8 @@ using DimensionalData.Dimensions, DimensionalData.LookupArrays

const DD = DimensionalData

# Handle changes between Makie 0.19 and 0.20
const SurfaceLikeCompat = isdefined(Makie, :SurfaceLike) ? Makie.SurfaceLike : Union{Makie.VertexGrid,Makie.CellGrid,Makie.ImageLike}

_paired(args...) = map(x -> x isa Pair ? x : x => x, args)


# Shared docstrings: keep things consistent.

const AXISLEGENDKW_DOC = """
Expand Down Expand Up @@ -138,31 +134,39 @@ for (f1, f2) in _paired(:plot => :heatmap, :heatmap, :image, :contour, :contourf
) where T
replacements = _keywords2dimpairs(x, y)
A1, A2, args, merged_attributes = _surface2(A, $f2, attributes, replacements)
p = if $(f1 == :surface)
axis_type = if haskey(merged_attributes, :axis) && haskey(merged_attributes.axis, :type)
to_value(type)
else
Makie.args_preferred_axis(Makie.Plot{$f2}, args...)
end

p = if axis_type isa Type && axis_type <: Union{LScene, Makie.PolarAxis}
# surface is an LScene so we cant pass attributes
p = Makie.$f2(args...; attributes...)
# And instead set axisnames manually
if !isnothing(p.axis.scene[OldAxis])
if p.axis isa LScene && !isnothing(p.axis.scene[OldAxis])
p.axis.scene[OldAxis][:names, :axisnames] = map(DD.label, DD.dims(A2))
end
p
else
else # axis_type isa Nothing, axis_type isa Makie.Axis or GeoAxis or similar
Makie.$f2(args...; merged_attributes...)
end
# Add a Colorbar for heatmaps and contourf
# TODO: why not surface too?
if T isa Real && $(f1 in (:plot, :heatmap, :contourf))
Colorbar(p.figure[1, 2], p.plot;
label=DD.label(A), colorbarkw...
)
end
p
return p
end
function Makie.$f1!(axis, A::AbstractDimMatrix;
x=nothing, y=nothing, colorbarkw=(;), attributes...
)
replacements = _keywords2dimpairs(x, y)
_, _, args, _ = _surface2(A, $f2, attributes, replacements)
# No ColourBar in the ! in-place versions
# No Colorbar in the ! in-place versions
return Makie.$f2!(axis, args...; attributes...)
end
function Makie.$f1!(axis, A::Observable{<:AbstractDimMatrix};
Expand All @@ -182,9 +186,10 @@ function _surface2(A, plotfunc, attributes, replacements)
lookup_attributes, newdims = _split_attributes(A1)
A2 = _restore_dim_names(set(A1, map(Pair, newdims, newdims)...), A, replacements)
P = Plot{plotfunc}
args = Makie.convert_arguments(P, A2)
# PTrait = Makie.conversion_trait(P, A2)
# status = Makie.got_converted(P, PTrait, converted)
PTrait = Makie.conversion_trait(P, A2)
# We define conversions by trait for all of the explicitly overridden functions,
# so we can just use the trait here.
args = Makie.convert_arguments(PTrait, A2)

# if status === true
# args = converted
Expand Down Expand Up @@ -371,6 +376,7 @@ Makie.plottype(::AbstractDimArray{<:Any,3}) = Makie.Volume
# Makie.expand_dimensions(::Makie.PointBased, y::IntervalSets.AbstractInterval) = (keys(y), y)

# Conversions
# Generic conversion for arbitrary recipes that don't define a conversion trait
function Makie.convert_arguments(t::Type{<:Makie.AbstractPlot}, A::AbstractDimMatrix)
A1 = _prepare_for_makie(A)
tr = Makie.conversion_trait(t, A)
Expand All @@ -381,6 +387,7 @@ function Makie.convert_arguments(t::Type{<:Makie.AbstractPlot}, A::AbstractDimMa
end
return xs, ys, last(Makie.convert_arguments(t, parent(A1)))
end
# PointBased conversions (scatter, lines, poly, etc)
function Makie.convert_arguments(t::Makie.PointBased, A::AbstractDimVector)
A1 = _prepare_for_makie(A)
xs = parent(lookup(A, 1))
Expand All @@ -389,29 +396,47 @@ end
function Makie.convert_arguments(t::Makie.PointBased, A::AbstractDimMatrix)
return Makie.convert_arguments(t, parent(A))
end
function Makie.convert_arguments(t::SurfaceLikeCompat, A::AbstractDimMatrix)
# Grid based conversions (surface, image, heatmap, contour, meshimage, etc)

# VertexGrid is for e.g. contour and surface, it uses a position per vertex.
function Makie.convert_arguments(t::Makie.VertexGrid, A::AbstractDimMatrix)
A1 = _prepare_for_makie(A)
xs, ys = map(_lookup_to_vector, lookup(A1))
# the following will not work for irregular spacings, we'll need to add a check for this.
# If the lookup is intervals, use the midpoint of each interval
# as the sampling point.
# If the lookup is points, just use the points.
xs, ys = map(_lookup_to_vertex_vector, lookup(A1))
return xs, ys, last(Makie.convert_arguments(t, parent(A1)))
end
# ImageLike is for e.g. image, meshimage, etc. It uses an interval based sampling method so requires regular spacing.
function Makie.convert_arguments(t::Makie.ImageLike, A::AbstractDimMatrix)
A1 = _prepare_for_makie(A)
xs, ys = map(_lookup_to_interval, lookup(A))
# the following will not work for irregular spacings, we'll need to add a check for this.
xlookup, ylookup, = lookup(A1) # take the first two dimensions only
# We need to make sure the lookups are regular intervals.
_check_regular_or_categorical_sampling(xlookup; axis = :x)
_check_regular_or_categorical_sampling(ylookup; axis = :y)
# Convert the lookups to intervals (<: Makie.EndPoints).
xs, ys = map(_lookup_to_interval, (xlookup, ylookup))
return xs, ys, last(Makie.convert_arguments(t, parent(A1)))
end

# CellGrid is for e.g. heatmap, contourf, etc. It uses vertices as corners of cells, so
# there have to be n+1 vertices for n cells on an axis.
function Makie.convert_arguments(
t::Makie.CellGrid, A::AbstractDimMatrix
)
A1 = _prepare_for_makie(A)
xs, ys = map(_lookup_to_vector, lookup(A1))
return xs, ys, last(Makie.convert_arguments(t, parent(A1)))
end

# VolumeLike is for e.g. volume, volumeslices, etc. It uses a regular grid.
function Makie.convert_arguments(t::Makie.VolumeLike, A::AbstractDimArray{<:Any,3})
A1 = _prepare_for_makie(A)
xs, ys, zs = map(_lookup_to_interval, lookup(A1))
# the following will not work for irregular spacings
xl, yl, zl = lookup(A1)
_check_regular_or_categorical_sampling(xl; axis = :x)
_check_regular_or_categorical_sampling(yl; axis = :y)
_check_regular_or_categorical_sampling(zl; axis = :z)
xs, ys, zs = map(_lookup_to_interval, (xl, yl, zl))
return xs, ys, zs, last(Makie.convert_arguments(t, parent(A1)))
end

Expand All @@ -423,7 +448,7 @@ function Makie.convert_arguments(t::Type{Plot{Makie.volumeslices}}, A::AbstractD
end
# # fallbacks with descriptive error messages
function Makie.convert_arguments(t::Makie.ConversionTrait, A::AbstractDimArray{<:Any,N}) where {N}
@warn "$t not implemented for `AbstractDimArray` with $N dims, falling back to parent array type"
@warn "Conversion trait $t not implemented for `AbstractDimArray` with $N dims, falling back to parent array type"
return Makie.convert_arguments(t, parent(A))
end

Expand All @@ -432,9 +457,11 @@ end
# These can just forward to the relevant converts.
Makie.expand_dimensions(t::Makie.PointBased, A::AbstractDimVector) = Makie.convert_arguments(t, A)
Makie.expand_dimensions(t::Makie.PointBased, A::AbstractDimMatrix) = Makie.convert_arguments(t, A)
Makie.expand_dimensions(t::SurfaceLikeCompat, A::AbstractDimMatrix) = Makie.convert_arguments(t, A)
Makie.expand_dimensions(t::Makie.VertexGrid, A::AbstractDimMatrix) = Makie.convert_arguments(t, A)
Makie.expand_dimensions(t::Makie.ImageLike, A::AbstractDimMatrix) = Makie.convert_arguments(t, A)
Makie.expand_dimensions(t::Makie.CellGrid, A::AbstractDimMatrix) = Makie.convert_arguments(t, A)
Makie.expand_dimensions(t::Makie.VolumeLike, A::AbstractDimArray{<:Any,3}) = Makie.convert_arguments(t, A)
Makie.expand_dimensions(t::Makie.VolumeLike, A::AbstractDimArray{<:Any,3}) = Makie.convert_arguments(t, A)
Makie.expand_dimensions(t::Type{Plot{Makie.volumeslices}}, A::AbstractDimArray{<:Any,3}) = Makie.convert_arguments(t, A)
end

# Utility methods
Expand Down Expand Up @@ -464,6 +491,19 @@ function _categorical_or_dependent(A, ::Nothing)
end
end

# Check for regular sampling on a lookup, throw an error if not.
# Here, we assume
function _check_regular_or_categorical_sampling(l; axis = nothing)
if !(DD.isregular(l) || DD.iscategorical(l))
throw(ArgumentError("""
DimensionalDataMakie: The $(isnothing(axis) ? "" : "$axis-axis ")lookup is not regularly spaced, which is required for image-like plot types in Makie.
The lookup was:
$l
You can solve this by resampling your raster, or by using a more permissive plot type like `heatmap`, `surface`, `contour`, or `contourf`.
"""))
end
end

# Simplify dimension lookups and move information to axis attributes
_split_attributes(A) = _split_attributes(dims(A))
Expand Down Expand Up @@ -561,7 +601,18 @@ function _lookup_to_vector(l)
end
end

function _lookup_to_vertex_vector(l)
if isintervals(l)
bs = intervalbounds(l)
return @. first(bs) + (last(bs) - first(bs)) / 2
else # ispoints
return collect(parent(l))
end
end

function _lookup_to_interval(l)
# TODO: warn or error if not regular sampling.
# Maybe use Preferences.jl to determine if we should error or warn.
l1 = if isnolookup(l)
Sampled(parent(l); order=ForwardOrdered(), sampling=Intervals(Center()), span=Regular(1))
elseif ispoints(l)
Expand Down

0 comments on commit 9363109

Please sign in to comment.