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

Remove strict dependency on coordinate arrays #51

Merged
merged 7 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions components/array-metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ import { Box, Divider } from 'theme-ui'
import useStore from './data/store'

const ArrayMetadata = ({ array }) => {
const zattrs = useStore((state) => state.dataset?.getZattrs(array))
const zattrs = useStore((state) => state.dataset?.getZattrs(array) ?? {})
const keys = Object.keys(zattrs)

const rows =
keys.length === 0
? [['Metadata', 'Missing coordinate array and attributes']]
: keys.map((key, i) => {
const value = Array.isArray(zattrs[key])
? `[${zattrs[key].join(', ')}]`
: zattrs[key]
return [key, value]
})
return (
<Box>
{Object.keys(zattrs).map((key, i) => {
const value = Array.isArray(zattrs[key])
? `[${zattrs[key].join(', ')}]`
: zattrs[key]

{rows.map(([key, value], i) => {
return (
<Row
key={key}
Expand Down Expand Up @@ -47,7 +51,7 @@ const ArrayMetadata = ({ array }) => {
>
{value}
</Column>
{i === keys.length - 1 ? (
{i === rows.length - 1 ? (
<Column start={1} width={4}>
<Divider sx={{ width: '100%' }} />
</Column>
Expand Down
7 changes: 1 addition & 6 deletions components/data/dataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,7 @@ class Dataset {
}
const prefix = this.pyramid ? '0/' : ''

const zattrs = this.metadata.metadata[`${prefix}${arrayName}/.zattrs`]

if (!zattrs) {
throw new Error(`No .zattrs found for ${arrayName}`)
}
return zattrs
return this.metadata.metadata[`${prefix}${arrayName}/.zattrs`]
}
}

Expand Down
23 changes: 11 additions & 12 deletions components/region/line-chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import RegionInfo from './region-info'
const LineChart = ({ selector, range, centerPoint, yValues, index }) => {
const variable = useStore((state) => state.dataset.level.variable)
const { units } = useStore((state) => state.dataset.getZattrs(variable.name))
const { units: selectorUnits } = useStore((state) =>
state.dataset.getZattrs(selector.name)
const { units: selectorUnits } = useStore(
(state) => state.dataset.getZattrs(selector.name) ?? {}
)
const { array, cfAxis } = useStore((state) => state.selectors[index].metadata)
const isTime = cfAxis === 'T'
Expand All @@ -37,7 +37,7 @@ const LineChart = ({ selector, range, centerPoint, yValues, index }) => {
const formatter = useCallback(
(x) => {
if (!array) {
return ''
return `Index=${x}`
} else if (array.data[x]) {
if (isTime) {
return (
Expand All @@ -56,21 +56,20 @@ const LineChart = ({ selector, range, centerPoint, yValues, index }) => {
const handleDownload = useCallback(
(e) => {
e.stopPropagation()
if (!array) {
return
}

const rows = yValues
.map((d, i) =>
d === variable.nullValue ? null : [Number(array.data[offset + i]), d]
)
.filter(Boolean)
const rows = yValues.map((d, i) => [
array ? Number(array.data[offset + i]) : offset + i,
d === variable.nullValue ? null : d,
])

if (rows.length === 0) {
return
}

rows.unshift([selector.name, variable.name])
rows.unshift([
array ? selector.name : `${selector.name} (index)`,
variable.name,
])
const csvContent =
'data:text/csv;charset=utf-8,' +
rows.map((row) => row.join(',')).join('\n')
Expand Down
6 changes: 4 additions & 2 deletions components/region/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import RegionChart from './region-chart'
const Plots = () => {
const variable = useStore((state) => state.dataset.level.variable?.name)
const selectors = useStore((state) => state.selectors)
const shape = useStore((state) => state.dataset.level.variable?.shape)
const chunkShape = useStore(
(state) => state.dataset.level.variable?.chunk_shape
)

if (!variable) {
return
Expand All @@ -16,7 +18,7 @@ const Plots = () => {
const selectorLines = selectors.filter(
(selector) =>
typeof selector.chunk === 'number' &&
shape[selector.metadata.dimensionIndex] > 1
chunkShape[selector.metadata.dimensionIndex] > 1
)

if (selectorLines.length === 0) {
Expand Down
2 changes: 2 additions & 0 deletions components/selector/selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const Selector = ({ index }) => {
let display
if (shape === 1) {
display = <SingleValue index={index} />
} else if (!selector.metadata.array) {
display = <Slider index={index} skipArrayLabels />
} else if (typeof selector.metadata.array.data[0] === 'string') {
display = <Dropdown index={index} />
} else {
Expand Down
32 changes: 20 additions & 12 deletions components/selector/slider.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const sx = {
},
}

const Slider = ({ index }) => {
const Slider = ({ index, skipArrayLabels = false }) => {
const [playing, setPlaying] = usePlay(index, { incrementChunk: true })
const selector = useStore(
(state) => state.selectors && state.selectors[index]
Expand Down Expand Up @@ -68,6 +68,24 @@ const Slider = ({ index }) => {
setSelector(index, sliderValue)
}, [index, sliderValue])

let value
if (skipArrayLabels) {
value = `Index=${sliderValue.chunk * chunk_shape + sliderValue.index}`
} else if (selector.metadata.cfAxis === 'T') {
value = (
<DateDisplay
array={selector.metadata.array}
selector={{ ...selector, ...sliderValue }}
chunkShape={chunk_shape}
/>
)
} else {
value =
selector.metadata.array.data[
sliderValue.chunk * chunk_shape + sliderValue.index
]
}

return (
<Flex sx={{ flexDirection: 'column', gap: 1, mt: 3 }}>
<Row columns={[4]}>
Expand Down Expand Up @@ -133,17 +151,7 @@ const Slider = ({ index }) => {

<Box sx={{ ...sx.subLabel, pb: 1 }}>
<Flex sx={{ gap: 2 }}>
{selector.metadata.cfAxis === 'T' ? (
<DateDisplay
array={selector.metadata.array}
selector={{ ...selector, ...sliderValue }}
chunkShape={chunk_shape}
/>
) : (
selector.metadata.array.data[
sliderValue.chunk * chunk_shape + sliderValue.index
]
)}
{value}
<Box>
(
<Box as='span' sx={{ color: 'primary' }}>
Expand Down
48 changes: 33 additions & 15 deletions components/utils/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const COMPRESSORS = {
blosc: Blosc,
}

const MAX_ARRAY_LENGTH = 1000000

const calculateRange = (arr, { nullValue }) => {
const filteredData = arr.filter((d) => !Number.isNaN(d) && d !== nullValue)
if (filteredData.length < 2) {
Expand Down Expand Up @@ -94,6 +96,10 @@ export const toKeyArray = (chunkKey, { chunk_separator }) => {
return chunkKey.split(chunk_separator).map(Number)
}

const isArrayOverSizeLimit = (dimensions) => {
return dimensions.reduce((product, d) => product * d, 1) >= MAX_ARRAY_LENGTH
}

const getChunkShapeOverride = (chunkShape, shape, dimensions, axes) => {
if (chunkShape.length === 1) {
return null
Expand All @@ -102,8 +108,7 @@ const getChunkShapeOverride = (chunkShape, shape, dimensions, axes) => {
const fullSpace =
dimensions
.filter((d) => [axes?.X, axes?.Y].includes(d))
.every((d) => d <= 360) &&
chunkShape.reduce((product, d) => product * d, 1) < 1000000
.every((d) => d <= 360) && !isArrayOverSizeLimit(chunkShape)

return dimensions.map((d, i) => {
if ([axes?.X, axes?.Y].includes(d)) {
Expand All @@ -127,10 +132,12 @@ const getChunksOverrides = (metadata, variables, cfAxes) => {
const result = {}

coordinates.forEach((coordinate) => {
const { shape, chunks } = metadata.metadata[`${coordinate}/.zarray`]
if (metadata.metadata[`${coordinate}/.zarray`]) {
const { shape, chunks } = metadata.metadata[`${coordinate}/.zarray`]

if (shape.some((d, i) => d !== chunks[i])) {
result[coordinate] = shape
if (shape.some((d, i) => d !== chunks[i])) {
result[coordinate] = shape
}
}
})

Expand Down Expand Up @@ -205,10 +212,15 @@ export const getArrays = async (
)
)

const result = [...variables, ...coords].reduce((accum, arrayName) => {
accum[arrayName] = null
return accum
}, {})
const result = [...variables, ...coords]
.filter(
(arrayName) =>
metadata.metadata[`${level ? `${level}/` : ''}${arrayName}/.zarray`]
)
.reduce((accum, arrayName) => {
accum[arrayName] = null
return accum
}, {})
const keys = Object.keys(result)

const arrs = await Promise.all(
Expand Down Expand Up @@ -340,12 +352,18 @@ export const getVariableInfo = async (
const selectorCoordinates = await Promise.all(
dimensions
.map((coord) => arrays[coord])
.map((arr, i) =>
isSpatialDimension(dimensions[i])
? null
: // TODO: handle chunked coordinate arrays
arr.get_chunk([0], { headers: headers[name] })
)
.map((arr, i) => {
if (
isSpatialDimension(dimensions[i]) ||
!arr ||
isArrayOverSizeLimit(arr.shape)
) {
return null
} else {
// TODO: handle chunked coordinate arrays
return arr.get_chunk([0], { headers: headers[name] })
}
})
)

const selectors = dimensions.map((d, i) => {
Expand Down
88 changes: 59 additions & 29 deletions components/utils/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export const getVariables = (metadata, cfAxes, pyramid) => {

const prefix = pyramid ? '0/' : ''

let variables
const multiDimensionalVariables = Object.keys(metadata.metadata)
.map((k) => k.match(pyramid ? /0\/\w+(?=\/\.zarray)/ : /\w+(?=\/\.zarray)/))
.filter(Boolean)
Expand All @@ -158,38 +159,14 @@ export const getVariables = (metadata, cfAxes, pyramid) => {
if (multiDimensionalVariables.length === 0) {
throw new Error('Please provide a dataset with at least 2D data arrays.')
}
variables = multiDimensionalVariables

const variablesWithCoords = multiDimensionalVariables.filter((d) =>
metadata.metadata[`${prefix}${d}/.zattrs`]['_ARRAY_DIMENSIONS'].every(
(dim) => metadata.metadata[`${prefix}${dim}/.zarray`]
)
)

if (variablesWithCoords.length === 0) {
const missingCoordinates = multiDimensionalVariables.reduce((a, d) => {
metadata.metadata[`${prefix}${d}/.zattrs`]['_ARRAY_DIMENSIONS'].forEach(
(dim) => {
if (!metadata.metadata[`${prefix}${dim}/.zarray`]) {
a.add(dim)
}
}
)
return a
}, new Set())
throw new Error(
`No viewable variables found. Missing coordinate information for ${
missingCoordinates.size > 1 ? 'dimensions' : 'dimension'
}: ${Array.from(missingCoordinates).join(', ')}.`
)
}

const variables = variablesWithCoords.filter((d) => cfAxes[d])

if (variables.length === 0) {
const variablesWithCfAxes = variables.filter((d) => cfAxes[d])
if (variablesWithCfAxes.length === 0) {
throw new Error(
`No viewable variables found. Unable to infer spatial dimensions for ${
variablesWithCoords.size > 1 ? 'variables' : 'variable'
}: ${Array.from(variablesWithCoords)
variables.size > 1 ? 'variables' : 'variable'
}: ${Array.from(variables)
.map(
(v) =>
`${v} (${metadata.metadata[`${prefix}${v}/.zattrs`][
Expand All @@ -199,6 +176,59 @@ export const getVariables = (metadata, cfAxes, pyramid) => {
.join(', ')}.`
)
}
variables = variablesWithCfAxes

if (pyramid) {
// @carbonplan/maps requires all dimensions to have coordinate arrays
const variablesWithCoords = variables.filter((d) =>
metadata.metadata[`${prefix}${d}/.zattrs`]['_ARRAY_DIMENSIONS'].every(
(dim) => metadata.metadata[`${prefix}${dim}/.zarray`]
)
)

if (variablesWithCoords.length === 0) {
const missingCoordinates = variables.reduce((a, d) => {
metadata.metadata[`${prefix}${d}/.zattrs`]['_ARRAY_DIMENSIONS'].forEach(
(dim) => {
if (!metadata.metadata[`${prefix}${dim}/.zarray`]) {
a.add(dim)
}
}
)
return a
}, new Set())
throw new Error(
`No viewable variables found. Missing coordinate information for ${
missingCoordinates.size > 1 ? 'dimensions' : 'dimension'
}: ${Array.from(missingCoordinates).join(', ')}.`
)
}
variables = variablesWithCoords
} else {
// proxy map rendering requires access to spatial coordinates
const variablesWithSpatialCoords = variables.filter((d) =>
[cfAxes[d].X, cfAxes[d].Y].every(
(dim) => metadata.metadata[`${prefix}${dim}/.zarray`]
)
)

if (variablesWithSpatialCoords.length === 0) {
const missingCoordinates = variables.reduce((a, d) => {
;[cfAxes[d].X, cfAxes[d].Y].forEach((dim) => {
if (!metadata.metadata[`${prefix}${dim}/.zarray`]) {
a.add(dim)
}
})
return a
}, new Set())
throw new Error(
`No viewable variables found. Missing coordinate information for spatial ${
missingCoordinates.size > 1 ? 'dimensions' : 'dimension'
}: ${Array.from(missingCoordinates).join(', ')}.`
)
}
variables = variablesWithSpatialCoords
}

const levels = Object.keys(metadata.metadata)
.map((k) => k.match(new RegExp(`[0-9]+(?=\/${variables[0]}\/.zarray)`)))
Expand Down
Loading
Loading