Skip to content

refac: port legacy TDD charts to canvas charts#9096

Merged
djbarnwal merged 10 commits intomainfrom
refac/port-tdd-canvas-charts
Mar 27, 2026
Merged

refac: port legacy TDD charts to canvas charts#9096
djbarnwal merged 10 commits intomainfrom
refac/port-tdd-canvas-charts

Conversation

@djbarnwal
Copy link
Copy Markdown
Member

  • Replace the Vega-based TDDAlternateChart with a new TDDCanvasChart that reuses the existing canvas chart components
  • Add brush selection and highlight controller support to canvas charts for TDD interactivity
  • Remove legacy Vega templates (area, line, bar, stacked-area, stacked-bar, etc.) and TDD-specific Vega utilities (patch-vega-spec, tdd-tooltip-formatter, vega-signal-manager)
  • Unify styling between canvas charts and TDD charts

This PR is in preparation of https://linear.app/rilldata/issue/APP-797/provide-a-way-for-users-to-change-chart-type-in-metric-charts-in

Checklist:

  • Covered by tests
  • Ran it and it works as intended
  • Reviewed the diff before requesting a review
  • Checked for unhandled edge cases
  • Linked the issues it closes
  • Checked if the docs need to be updated. If so, create a separate Linear DOCS issue
  • Intend to cherry-pick into the release branch
  • I'm proud of this work!

Copy link
Copy Markdown
Contributor

@ericpgreen2 ericpgreen2 left a comment

Choose a reason for hiding this comment

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

1. TDDCanvasChart naming ties the component to a surface

The name TDDCanvasChart implies this is a canvas-specific component, but the whole value of this refactor is reusing the shared chart components (Chart.svelte, CartesianChartProvider, etc.) across any context: Explore, Canvas, standalone, embed. A chart that renders in both Explore's TDD view and Canvas dashboards is not a "Canvas chart."

Consider TDDChart or TDDMeasureChart instead. The component is really an adapter that wires TDD-specific props (metrics view name, time/filter stores, dimension data) into the generic Chart component. Its name should reflect that role, not a rendering surface.

Same consideration applies to TDD_TO_COMPONENT_CHART_TYPE in tdd-chart-config.ts, which maps to generic chart types, not canvas-specific ones.

2. Hardcoded brush_ts signal name in compileToBrushedVegaSpec

brush-builder.ts:71 handles only brush_ts when injecting the clear handler. When the temporal field is named something other than ts (e.g., timestamp, created_on), Vega-Lite generates brush_timestamp or brush_created_on instead. The clear-on-Escape handler won't fire for those signals, leaving the brush stuck.

The resolveSignalIntervalField in vega-signals.ts already handles arbitrary field names with a fallback loop. The compiled spec signal patching should do the same: iterate over signals and match any brush_* signal that isn't brush_x or other known non-temporal signals, rather than hardcoding brush_ts.

3. Async compilation race condition

Chart.svelte:86-98 calls compileToBrushedVegaSpec asynchronously without canceling in-flight requests. If the spec changes twice in quick succession (e.g., theme toggle + data refresh), the first promise could resolve after the second, overwriting the newer compiled spec with a stale one. Consider tracking a generation counter:

let compileGeneration = 0;
$: {
  if (useBrush && spec && JSON.stringify(spec) !== JSON.stringify(prevVlSpec)) {
    prevVlSpec = spec;
    const gen = ++compileGeneration;
    void compileToBrushedVegaSpec(spec, isThemeModeDark, theme).then((compiled) => {
      if (gen === compileGeneration) vegaSpec = compiled;
    });
  }
}

4. Dead code left behind

Several exports are now unused after the template deletions:

  • TDDChartMap and ChartType enum in web-common/src/components/vega/types.ts (was only used by the deleted utils.ts; the URL state mappers use their own separate maps)
  • TDDAlternateCharts and TDDBarCharts types in time-dimension-details/types.ts (no remaining imports outside the file itself)

These should be cleaned up in this PR since this is where they became dead.

5. Grain snapping edge case in handleTddBrushEnd

MeasureChart.svelte:258-267 snaps the brush interval to grain boundaries using ceil-start / floor-end. If the brush is entirely within a single grain boundary, endDt could end up before startDt after snapping. Consider adding a guard:

if (+endDt <= +startDt) return;

6. JSON.stringify for deep equality

Chart.svelte:90 and VegaRenderer.svelte:74 use JSON.stringify comparison for memoization. For the VL spec object, this could serialize a large tree on every reactive tick. Not blocking, but worth noting for performance-sensitive paths.

7. Unused viewVL binding in CanvasChart.svelte

CanvasChart.svelte:19 declares let viewVL: View and binds it but never reads it. Should be removed until needed.


Developed in collaboration with Claude Code

@djbarnwal djbarnwal requested a review from ericpgreen2 March 24, 2026 13:41
Copy link
Copy Markdown
Contributor

@ericpgreen2 ericpgreen2 left a comment

Choose a reason for hiding this comment

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

Great consolidation — ~2k lines of legacy Vega templates and TDD-specific utilities replaced with ~500 lines that reuse the existing canvas chart infrastructure. The brush builder, highlight controller, and signal utilities are well-tested and cleanly factored.

Regression to address before merge

Area chart missing time comparison support. TDDChart.STACKED_AREA maps to area_chart in tdd-chart-config.ts, but cartesian/area/spec.ts does not call createComparisonTransforms — unlike the line, bar, and stacked-bar specs which all do. Since ChartTypeSelector.svelte enables stacked area when hasComparison is true (which includes time comparison without dimension values), this is a reachable path where CartesianChartProvider fetches comparison data but the area spec silently ignores it. The old patchSpecForTimeComparison handled this explicitly.

Smaller observations

  • compileToBrushedVegaSpec is marked async but only calls the synchronous vega-lite compile(). The async keyword is misleading since there's no await in the body. Consider removing it or adding a comment about future intent.
  • Chart.svelte uses JSON.stringify(spec) !== JSON.stringify(prevVlSpec) for memoization. Fine for now, but worth keeping an eye on as spec complexity grows.
  • CanvasChart.svelte and ChartContainer.svelte both declare view bindings that are never read. If these are scaffolding for APP-797, a brief comment would help; otherwise they're dead code.
  • The .gitignore addition for .claude/projects/ is unrelated to this refactor.

Developed in collaboration with Claude Code

@djbarnwal djbarnwal merged commit 2878c04 into main Mar 27, 2026
16 of 19 checks passed
@djbarnwal djbarnwal deleted the refac/port-tdd-canvas-charts branch March 27, 2026 15:41
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.

2 participants