Skip to content

fix(observable-plot): avoid implicit scatter stacking - #522

Merged
ObservedObserver merged 1 commit into
mainfrom
codex/issue-470-observable-scatter-stack
Jul 28, 2026
Merged

ObservedObserver merged 1 commit into
mainfrom
codex/issue-470-observable-scatter-stack

Conversation

@ObservedObserver

Copy link
Copy Markdown
Member

Summary

  • make Observable Plot stacking mark-aware so point/scatter and line marks are not implicitly stacked when the Vega-Lite stack property is absent
  • preserve default zero stacking for bar/area marks and color-based series grouping
  • preserve explicit none, zero, normalize, and center semantics without passing Observable Plot's unsupported offset: "zero"
  • add regression coverage for scatter, color grouping, explicit stack modes, stack-axis selection, binned channels, and bar/area/line behavior

Reproduction and root cause

A minimal point spec with quantitative X/Y and nominal color reproduced the bug locally. Graphic Walker's stack/zero modes intentionally omit the Vega-Lite stack property, but the old Observable Plot converter treated undefined as enabled because it only checked that the value was not null or false. The failing regression test proved that the point mark was sent through Plot.stackY even though Vega-Lite emitted no stack transform.

The converter now resolves stacking from the mark type, quantitative non-binned channels, and explicit stack value:

  • absent stack only defaults for bar/area
  • explicit X takes precedence over Y, matching Vega-Lite
  • non-ranged positional marks use Plot.stackX2/stackY2 for the upper endpoint
  • bar/area use ranged stackX/stackY
  • explicit none/invalid modes suppress Observable Plot's implicit bar/area stack with range endpoints
  • normalize/center require a visible Graphic Walker color series; zero/true map to Plot's default offset object

Vega-Lite and Observable Plot parity

The implementation was checked against installed vega-lite@5.16.3 and @observablehq/plot@0.6.16 behavior, including stackable/default-stack marks, X-before-Y precedence, stackX2/stackY2, implicit-stack suppression, and Plot rejecting offset: "zero".

A separate runtime comparison used repeated categories (A: 1 + 2, B: 4 + 5) across bar/area, raw/aggregate inputs, and absent/zero/true modes (12 cases). Vega runtime and Plot both produced category endpoints A = 3, B = 9; explicit Plot stacking was channel-for-channel identical to each mark's implicit stack, so the change does not double-accumulate ungrouped bar/area values.

Validation

  • focused Jest: 2 suites, 34 tests passed
  • full @kanaries/graphic-walker Jest: 33 suites, 363 tests, 4 snapshots passed
  • node_modules/.bin/tsc -p packages/graphic-walker/tsconfig.json --noEmit
  • yarn workspace @kanaries/graphic-walker build
  • git diff --check

Independent review

The final exact diff was reviewed in a fresh, read-only Claude Code session using explicit model claude-opus-5:

  • session: da84ef5b-5e27-43bb-84b3-d3b3535dff14
  • JSON canonicalModel: claude-opus-5
  • verdict: APPROVE with no blocking findings

An earlier independent Opus 5 code review identified the no-color normalize/center regression; that finding was reproduced with four failing tests, fixed, fully revalidated, and then submitted to the fresh final delta review above.

Commit: 9dffbf465d05e0b81db05de33844a2a2b097043b

Fixes #470

@vercel

vercel Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
graphic-walker Ready Ready Preview, Comment Jul 28, 2026 2:55am

Request Review

@ObservedObserver
ObservedObserver marked this pull request as ready for review July 28, 2026 03:34
@cursor

cursor Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@ObservedObserver
ObservedObserver merged commit 160c381 into main Jul 28, 2026
5 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9dffbf465d

ℹ️ 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".

Comment on lines +239 to +240
const hasRequiredGroup = (mode !== 'normalize' && mode !== 'center') || Boolean(enc.color?.field);
if (enabled && hasRequiredGroup && explicitChannel && explicitlyStackableMarks.has(markType)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve normalized stacks when color is absent

When defaultAggregated is false and a bar or area chart has repeated values for a positional category, selecting normalize or center without a color field now makes this condition reject stacking. The subsequent suppressImplicitStack call converts the measure to y1: 0, y2: field (or the X equivalent), so the records overlap from zero instead of forming the requested normalized/centered stack. A color channel is not required for Plot.stackX/stackY to stack raw records by the opposing positional channel; this gate needs to distinguish the pre-aggregated singleton case from raw data rather than using color as the requirement.

Useful? React with 👍 / 👎.

This branch was successfully deployed

1 active deployment
Preview — 9dffbf46 Deployed Jul 28, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DEV-2207] ObservablePlot Mode Scatter Plot unexpected using stack mode

1 participant