Conversation
✅ Deploy Preview for svelteplot ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
📦 Preview package for this PR is published! Version: Install it with: npm install svelteplot@pr-523
# or install the specific version
npm install svelteplot@0.12.0-pr-523.0 |
|
📦 Preview package for this PR is published! Version: Install it with: npm install svelteplot@pr-523
# or install the specific version
npm install svelteplot@0.12.0-pr-523.1 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ecb8917dc9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const markChannels = ['x', 'y', 'fill'] as const; | ||
|
|
||
| const markFill = $derived( | ||
| isDenseGridMode || isSamplerMode ? (RAW_VALUE as any) : (value as any) |
There was a problem hiding this comment.
Avoid forcing contour values into the global color scale
This always registers a fill channel backed by scalar field values, even when the contour is rendered with constant styles (fill="none", stroke="currentColor"). In mixed plots, that injects numeric contour values into the shared color scale domain and can change type/domain inference for other marks (for example, categorical color encodings become mixed with numeric categories), producing incorrect colors and legends outside the contour itself.
Useful? React with 👍 / 👎.
|
📦 Preview package for this PR is published! Version: Install it with: npm install svelteplot@pr-523
# or install the specific version
npm install svelteplot@0.12.0-pr-523.2 |
|
📦 Preview package for this PR is published! Version: Install it with: npm install svelteplot@pr-523
# or install the specific version
npm install svelteplot@0.12.0-pr-523.3 |
|
📦 Preview package for this PR is published! Version: Install it with: npm install svelteplot@pr-523
# or install the specific version
npm install svelteplot@0.12.0-pr-523.4 |
|
📦 Preview package for this PR is published! Version: Install it with: npm install svelteplot@pr-523
# or install the specific version
npm install svelteplot@0.12.0-pr-523.5 |
|
📦 Preview package for this PR is published! Version: Install it with: npm install svelteplot@pr-523
# or install the specific version
npm install svelteplot@0.12.0-pr-523.6 |
|
📦 Preview package for this PR is published! Version: Install it with: npm install svelteplot@pr-523
# or install the specific version
npm install svelteplot@0.12.0-pr-523.7 |
Adds a new `<Contour>` mark that renders iso-contour lines and filled
contour bands from a scalar field using the marching-squares algorithm
(d3-contour).
Supports the same three input modes as `<Raster>`:
- **Dense grid**: flat row-major array + `width`/`height`
- **Function sampling**: `value={(x, y) => number}` evaluated on a pixel grid
- **Scatter interpolation**: point cloud with x/y/value channels, spatially
interpolated via nearest/barycentric/random-walk (reuses rasterInterpolate helpers)
Key design decisions:
- `fill="value"` / `stroke="value"` are special keywords that map each
contour level's threshold through the plot color scale; any other string
is a constant CSS color
- When `fill` is active, `stroke` defaults to `"none"`; otherwise `"currentColor"`
- Color scale domain is registered via `<Mark>`'s fill channel so all modes
(including function-sampling) correctly infer domain and type
- For sampler mode, a cheap 20×20 coarse sample registers the color domain
while the full-resolution grid is (re-)evaluated on every render, allowing
impure functions (closures over reactive state, `performance.now()`) to work
Includes:
- `src/lib/marks/Contour.svelte` — the mark component
- Docs page at `/marks/contour` with interactive examples
- Six example plots (volcano, filled bands, sampled, weather, quantile, interactive swirl)
- 15 unit tests covering all three modes and style defaults
- Visual regression snapshots
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add key `(contourGeom.value)` to `{#each}` block (svelte/require-each-key)
- Run Prettier on four new files (quantile.svelte, volcano.svelte,
weather.svelte, contour.test.svelte.ts)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove spurious "and" in fill="value" description - Fix "four interpolation strategies" to list all four (add `none`) - Fix broken Example link to point to /examples/contour/volcano - Replace mismatched live example in explicit-thresholds section with one that actually demonstrates an explicit thresholds array - Fix "Pass a function to thresholds" inaccuracy in quantile section (the example passes a precomputed array, not a function) - Fix wrong band-count comment (7 → produces 9 bands, not 10) - Reword quantile section intro for clarity Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the manual floor/accumulate loop with d3-array's range(), which handles floating-point precision correctly and is more idiomatic. Also simplify the control flow: resolve the interval case with an early return rather than converting to an intermediate floor/range object. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…contours
Previously the fill channel was always registered with scalar field values,
which polluted the shared color scale domain even when fill/stroke were
plain CSS colors. This corrupted type/domain inference for sibling marks
(e.g. categorical color encodings gained unexpected numeric entries).
Now scalar values are only registered when fill="value" or stroke="value"
is active (markUsesColorScale). The three modes are handled separately:
- Dense grid / sampler: fill channel omitted entirely when not coloring by value
- Scatter: fill channel is always registered (it's the delivery mechanism for
scalar values into computeContours via scaledData[].resolved.fill), but
passes { value, scale: false } when color scale registration is not needed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When `value` is omitted and `fill` or `stroke` is a data accessor
(field name string or function) rather than a CSS color, it is
automatically promoted to the `value` channel and replaced with the
`"value"` keyword — matching Observable Plot's behaviour.
Examples:
<Contour data={pts} x="x" y="y" fill="temp" />
-- equivalent to: value="temp" fill="value"
<Contour data={pts} x="x" y="y" stroke={(d) => d.temp} />
-- equivalent to: value={(d) => d.temp} stroke="value"
Detection uses the existing isColorOrNull helper (d3-color backed) plus
explicit checks for SVG/SveltePlot constants ("none", "value",
"inherit") that d3-color does not recognise as CSS colors. CSS color
strings (named colors, hex, functional notation) and the "value" /
"none" keywords are never promoted.
Throws when both fill and stroke are accessors (ambiguous).
Adds 4 unit tests covering: field-name promotion, function promotion,
no promotion when value is explicit, no promotion for CSS color strings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both marks now read from getPlotDefaults().contour / .raster at
component initialisation, so project-wide defaults (e.g. blur,
pixelSize, interpolate, smooth) can be set with setPlotDefaults():
setPlotDefaults({ contour: { blur: 1, pixelSize: 3 } });
setPlotDefaults({ raster: { pixelSize: 2, imageRendering: 'pixelated' } });
PlotDefaults.contour and .raster were already declared in the type
system; this adds the missing runtime wiring following the same
DEFAULTS pattern used by Dot, Link, Sphere, and other marks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new example plot derived from the existing interpolated raster example, using the CA55 aeromagnetic survey dataset. The contour mark overlays the interpolated field with Dot markers at the original measurement positions, demonstrating scatter-to-contour interpolation with the fill shorthand (fill="MAG_IGRF90" auto-promotes to value). Also removes accidental debug console.log calls left in Contour.svelte. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
📦 Preview package for this PR is published! Version: Install it with: npm install svelteplot@pr-523
# or install the specific version
npm install svelteplot@0.12.0-pr-523.8 |
|
📦 Preview package for this PR is published! Version: Install it with: npm install svelteplot@pr-523
# or install the specific version
npm install svelteplot@0.12.0-pr-523.9 |
|
📦 Preview package for this PR is published! Version: Install it with: npm install svelteplot@pr-523
# or install the specific version
npm install svelteplot@0.12.0-pr-523.10 |
x1/x2/y1/y2 in ContourMarkProps are typed as number (sampler bounds), so casting Symbol values directly to that type fails svelte-check. Using `as unknown as ContourMarkProps['x1']` satisfies TypeScript. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
📦 Preview package for this PR is published! Version: Install it with: npm install svelteplot@pr-523
# or install the specific version
npm install svelteplot@0.12.0-pr-523.11 |
resolves #83
Summary
<Contour>mark that renders iso-contour lines and filled contour bands from a scalar field using the marching-squares algorithm (d3-contour)Contour mark
Supports the same three input modes as
<Raster>:width/heightvalue={(x, y) => number}evaluated on a pixel gridx/y/valuechannels, spatially interpolated via nearest/barycentric/random-walk (reuses existingrasterInterpolatehelpers)Key design decisions:
fill="value"/stroke="value"map each contour level's threshold through the plot's color scale; any other string is a constant CSS colorfillis active,strokedefaults to"none"; otherwise"currentColor"performance.now(), etc.) work correctlyAspect ratio fix
heightFromAspectwas called withplotWidthForAspectRatio(full outer width, auto-margins zeroed to avoid a feedback loop) instead ofplotWidth(the actual inner plot area). This madeplotHeightlarger thanplotWidthbymarginLeft + marginRight, distorting the data-space aspect ratio whenever axes were present.Fix: pass
plotWidthtoheightFromAspect. The feedback loop (height → tick density → label widths → autoMarginLeft → plotWidth → height) converges in at most one reactive update in practice.plotWidthForAspectRatiois retained for geo-projection aspect-ratio computation.Test plan
pnpm test— 750 tests passingpnpm lint— no errorspnpm check— 0 errors, pre-existing warnings onlypnpm dev)🤖 Generated with Claude Code