Source

126 chart lines · 2 files · 3.5 kBBenchmark harness excluded · shared/mount.ts

Gridded surface wind4,800 records · CSV · 164.0 kB
@charts-poc/demo-data/wind

Selection: Complete published snapshot

longitude
number
latitude
number
u
number
v
number

Remote Sensing Systems / giCentreobservablehq/plot@356f579b1d94 · revision 356f579b1d94 · test/data/wind.csv · Observable Plot repository MIT; upstream source credited · SHA-256 906a2dcf864aPinned snapshot

cases/38-contour-topography/tanstack.ts92 lines · entry
cases/38-contour-topography/tanstack.ts
import { createMark, defineChart } from '@tanstack/charts'
import { contours } from 'd3-contour'
import { geoPath, geoTransform } from 'd3-geo'
import { scaleThreshold } from 'd3-scale'
import { contourThresholds, windSpeedGrid } from './transform'
import { tanstackMount } from '../../shared/mount'
import type { ContourMultiPolygon } from 'd3-contour'
import type { ConformanceInput } from '../../types'
import type { SceneNode } from '@tanstack/charts'

const colors = ['#dbeafe', '#bfdbfe', '#93c5fd', '#60a5fa', '#2563eb']

const definition = (input: ConformanceInput) => {
  const grid = windSpeedGrid(input.revision)
  const geometry = contours()
    .size([grid.width, grid.height])
    .thresholds([...contourThresholds])(grid.values)

  return defineChart({
    marks: [contourMark(geometry, grid.width, grid.height)],
    color: {
      scale: scaleThreshold<number, string>,
      domain: contourThresholds.slice(1),
      range: colors,
    },
    margin: 12,
  })
}

function contourMark(
  geometry: readonly ContourMultiPolygon[],
  gridWidth: number,
  gridHeight: number,
) {
  return createMark<ContourMultiPolygon, never, never>(({ markIndex }) => {
    const id = `contour-${markIndex}`

    return {
      id,
      channels: {
        color: {
          scale: 'color',
          values: geometry.map((contour) => contour.value),
        },
      },
      render: ({ chart, color }) => {
        const projection = geoTransform({
          point(x, y) {
            this.stream.point(
              chart.x + (x / gridWidth) * chart.width,
              chart.y + chart.height - (y / gridHeight) * chart.height,
            )
          },
        })
        const path = geoPath(projection)
        const children: SceneNode[] = []

        for (let index = 0; index < geometry.length; index++) {
          const contour = geometry[index]
          if (contour === undefined) continue
          const pathData = path(contour)
          if (pathData === null) continue
          children.push({
            kind: 'area',
            key: `${id}:${index}`,
            points: [],
            path: pathData,
            style: {
              fill: color(contour.value),
              stroke: '#ffffff',
              strokeWidth: 0.75,
            },
          })
        }

        return {
          nodes: [
            {
              kind: 'group',
              key: id,
              className: 'ts-chart__area',
              ariaHidden: true,
              children,
            },
          ],
        }
      },
    }
  })
}

export const mount = tanstackMount(definition, 'Filled wind-speed contours')
cases/38-contour-topography/transform.ts34 lines · support
cases/38-contour-topography/transform.ts
import { wind } from '@charts-poc/demo-data/wind'

export interface ContourGrid {
  width: number
  height: number
  values: number[]
}

const sourceWidth = 80
export const contourGridWidth = 64
export const contourGridHeight = 60
export const contourThresholds = [2, 4, 6, 8, 10]

export function windSpeedGrid(revision: number): ContourGrid {
  const firstColumn = (revision % 2) * 8
  const values: number[] = []

  for (let row = 0; row < contourGridHeight; row++) {
    const rowOffset = row * sourceWidth
    for (let column = 0; column < contourGridWidth; column++) {
      const observation = wind[rowOffset + firstColumn + column]
      if (!observation) {
        throw new Error('Observable Plot wind grid is incomplete.')
      }
      values.push(Math.hypot(observation.u, observation.v))
    }
  }

  return {
    width: contourGridWidth,
    height: contourGridHeight,
    values,
  }
}