@snowflake/stellar-vega
v0.51.2
Published
Stellar Vega is a Vega-Lite chart component for React, optimized for the Stellar Design System. It's a drop-in replacement for `react-vega` with better theming, error handling, and UX.
Downloads
259
Readme
@snowflake/stellar-vega
Stellar Vega is a Vega-Lite chart component for React, optimized for the Stellar Design System. It's a drop-in replacement for react-vega with better theming, error handling, and UX.
Features
✨ Automatic Theming - Charts adapt to light/dark mode automatically
🎨 Stellar Design - Consistent colors, typography, and spacing
🛡️ Type Safe - Full TypeScript support with comprehensive types
⚡ Performance - Optimized rendering and memory management
♿ Accessible - ARIA labels and semantic HTML
🔧 Customizable - Actions menu, tooltips, and transformations
Installation
pnpm add @snowflake/stellar-vegaBasic Usage
import { StellarVega } from '@snowflake/stellar-vega';
function MyChart() {
const spec = {
$schema: 'https://vega.github.io/schema/vega-lite/v6.json',
mark: 'bar',
data: {
values: [
{ category: 'A', value: 28 },
{ category: 'B', value: 55 },
{ category: 'C', value: 43 },
],
},
encoding: {
x: { field: 'category', type: 'nominal' },
y: { field: 'value', type: 'quantitative' },
},
};
return <StellarVega spec={spec} />;
}API Reference
StellarVega Component
Props
| Prop | Type | Description |
| ----------------- | -------------------------- | ---------------------------------------------------------------------------------------------- |
| spec | VegaSpec | The Vega-Lite specification (required) |
| options | StellarVegaOptions | Configuration options |
| onError | (error: Error) => void | Error callback |
| onNewView | (view: View) => void | Callback when Vega view is created |
| onMouseUp | EventListenerHandler | Mouse up event handler |
| signalListeners | Record<string, Function> | Vega signal listeners. Empty first-emit and unchanged rebuild point selections are suppressed. |
| transformations | VegaTransformation[] | Spec transformations to apply |
Example with Options
<StellarVega
spec={spec}
options={{ actions: true }}
onError={(error) => console.error('Chart error:', error)}
onNewView={(view) => console.log('View created:', view)}
/>Actions Menu
Enable the actions menu to allow users to export charts:
<StellarVega
spec={spec}
options={{
actions: {
onSaveSVG: () => {
// Custom SVG export logic
},
onSavePNG: () => {
// Custom PNG export logic
},
},
}}
/>Default implementations are provided for SVG and PNG export if callbacks aren't specified.
Transformations
Apply transformations to modify the spec before rendering:
import { StellarVega, hoverImprovements } from '@snowflake/stellar-vega';
<StellarVega spec={spec} transformations={[hoverImprovements]} />;lineSeriesOrder
Orders multi-series line chart legend and tooltip rows by each series' aggregated
y-value (sum) so the item order better matches perceived line order.
import { StellarVega, lineSeriesOrder } from '@snowflake/stellar-vega';
<StellarVega
spec={lineSpec}
transformations={[
lineSeriesOrder({ direction: 'descending' }), // or "ascending"
]}
/>;hoverImprovements
Adds enhanced hover interactions based on chart type:
- Bar charts: Full-height hover highlight behind bars
- Line charts: Vertical rule at hover position with data point dots
- Area charts: Vertical rule at hover position with data point dots
- Scatter charts (point/circle): Hover ring around points with nearest-point selection
- Arc charts (pie/donut): Subtle grow effect on hover, plus
hoverArc/clickArcselections - Rect charts (heatmap): Cell stroke on hover, plus
hoverRect/clickRectselections
Author-supplied params (for example a legend-bound legendFilter) are merged, not replaced. Click selections use per-mark signal names (clickX, clickY, clickXY, clickArc, clickRect) that hosts can listen to via signalListeners.
This transformation automatically detects the mark type and applies appropriate hover behavior.
// Works with bar charts
<StellarVega spec={barChartSpec} transformations={[hoverImprovements]} />
// Works with scatter charts
<StellarVega spec={scatterChartSpec} transformations={[hoverImprovements]} />brushSelection
Adds click-and-drag brush selection to charts. Users can select a region of data by clicking and dragging.
import { StellarVega, brushSelection } from '@snowflake/stellar-vega';
// Basic usage - auto-detects brush type based on chart
<StellarVega
spec={spec}
transformations={[brushSelection()]}
signalListeners={{
brush: (name, value) => {
console.log('Selected range:', value);
},
}}
/>;Options:
| Option | Type | Default | Description |
| ------------------- | ------------------------ | ------------- | ------------------------------------- |
| encodings | "x" | "y" | "xy" | Auto-detected | Which axes the brush projects onto |
| name | string | "brush" | Signal name for signalListeners |
| dimUnselected | boolean | true | Whether to dim data outside selection |
| unselectedOpacity | number | 0.3 | Opacity for unselected data |
Auto-detection:
- Scatter plots (point/circle) → 2D brush (
"xy") - Line/area/bar charts → 1D horizontal brush (
"x")
Pre-configured exports:
import { timeSeriesBrush, scatterBrush } from "@snowflake/stellar-vega";
// 1D horizontal brush for time series (signal name: "dateRange")
<StellarVega
spec={timeSeriesSpec}
transformations={[timeSeriesBrush]}
signalListeners={{
dateRange: (name, value) => {
const [start, end] = value.x;
filterData(start, end);
},
}}
/>
// 2D brush for scatter plots (signal name: "brush")
<StellarVega
spec={scatterSpec}
transformations={[scatterBrush]}
signalListeners={{
brush: (name, value) => {
console.log("X range:", value.x, "Y range:", value.y);
},
}}
/>Combining with hover improvements:
<StellarVega spec={scatterSpec} transformations={[hoverImprovements, brushSelection()]} />highlightMarks
Dims marks that are not in a highlight set (dashboard cross-highlight). Owned by Stellar Vega so it can compose with hover/brush opacity instead of consumers writing fillOpacity.
- Filled marks (bar, arc, rect, point, area) use
opacity - Stroked marks (line, trail) use
strokeOpacity - Applied on the data layer (hover overlays are skipped)
- Multiplied with an existing brush dim; an empty highlight set is a no-op
- Must be placed after
brushSelection()to compose with brush dimming - Complex existing opacity encodings that cannot be safely composed are preserved
- Update the set without rebuilding the view via
view.signal('highlightValues', …)
import { StellarVega, highlightMarks } from '@snowflake/stellar-vega';
<StellarVega
spec={spec}
transformations={[
hoverImprovements,
brushSelection(),
highlightMarks({ field: 'REGION', dimOpacity: 0.2 }),
]}
/>;
// later, no recompile:
view.signal('highlightValues', ['West', 'East']);
view.run();Options:
| Option | Type | Default | Description |
| ------------ | ----------- | ------------------- | ------------------------------------------- |
| field | string | required | Datum field compared to the highlight set |
| dimOpacity | number | 0.2 | Opacity multiplier for marks not in the set |
| name | string | "highlightValues" | Signal name for view.signal(name, values) |
| values | unknown[] | [] (no-op) | Initial highlight values |
Validation Utilities
isVegaSpec(value: unknown): value is VegaSpec
Type guard for checking if a value is a valid Vega-Lite spec.
import { isVegaSpec } from '@snowflake/stellar-vega';
if (isVegaSpec(someValue)) {
return <StellarVega spec={someValue} />;
}assertVegaSpec(value: unknown): asserts value is VegaSpec
Assertion function that throws if the value is not a valid spec.
import { assertVegaSpec } from '@snowflake/stellar-vega';
function processSpec(spec: unknown) {
assertVegaSpec(spec);
// spec is now typed as VegaSpec
return <StellarVega spec={spec} />;
}Hooks
useVegaLiteConfig()
Returns a Vega-Lite config object matching the current Stellar theme.
import { useVegaLiteConfig } from '@snowflake/stellar-vega';
function MyChart() {
const config = useVegaLiteConfig();
const spec = {
...mySpec,
config,
};
return <StellarVega spec={spec} />;
}Error Handling
The component displays a user-friendly error message when compilation fails:
<StellarVega
spec={invalidSpec}
onError={(error) => {
// Log to error tracking service
trackError(error);
}}
/>Advanced Usage
Custom Signal Listeners
Listen to Vega signals for interactive charts:
<StellarVega
spec={spec}
signalListeners={{
brush: (name, value) => {
console.log('Selection changed:', value);
},
}}
/>Signal listeners run immediately and preserve Vega's normal behavior. Stellar Vega only filters two point-selection artifacts: empty values from a view's first dataflow run and an unchanged selection re-announced when the view is rebuilt. Brush, hover, and custom signals are otherwise forwarded unchanged.
Accessing the Vega View
Get access to the underlying Vega View instance:
const viewRef = useRef<View | null>(null);
<StellarVega
spec={spec}
onNewView={(view) => {
viewRef.current = view;
// Access view methods
view.addSignalListener('width', (name, value) => {
console.log('Width changed:', value);
});
}}
/>;Optimizing for dashboards
<StellarVega spec={spec} /> compiles the Vega-Lite spec (spec-prep → Vega-Lite
compile → Vega parse) on every render where the spec changes structurally. That
work is CPU-bound and DOM-free. On a dashboard with many charts — or a chart
whose parent re-renders often — you can hoist it out of the render-blocking path
and memoize it with the useVegaCompiled hook, then hand the result to
<CompiledStellarVega> (the DOM-bound half of <StellarVega>).
import { useVegaCompiled, useVegaLiteConfig, CompiledStellarVega } from '@snowflake/stellar-vega';
function DashboardTile({ spec }: { spec: VegaSpec }) {
const config = useVegaLiteConfig();
// Compiled once per structurally-distinct spec; referentially stable across
// re-renders so <CompiledStellarVega> never rebuilds its Vega view needlessly.
const compiled = useVegaCompiled(spec, { config, showDomLegend: true });
return <CompiledStellarVega compiled={compiled} config={config} showDomLegend />;
}Notes:
useVegaCompilednever throws. Invalid specs and compile failures are surfaced via the returnedcompiled.errorfield;<CompiledStellarVega>renders the same error UI as<StellarVega>.- Memoization is structural. The hook recompiles only when the spec changes
in ways other than
width/height(dimension-only changes take the same in-place fast path<StellarVega>uses), thetransformationsarray changes, orconfig/showDomLegendchange. An inlinespec={{ ... }}literal does not thrash compilation. - Pass the same
configto both.useVegaCompileduses it during compilation and<CompiledStellarVega>uses it for DOM legend styling. - Bypassing the hook. You can call the pure
compileVegaSpec(...)function directly (e.g. off the main thread or ahead of time), but you must memoize the result yourself before passing it to<CompiledStellarVega compiled={...} />, otherwise the view rebuilds on every render.
<StellarVega> is itself a thin wrapper over useVegaCompiled +
<CompiledStellarVega>, so its behavior is identical — reach for the split only
when you want to control where and when compilation happens.
Migrating from react-vega
Stellar Vega is designed to be a drop-in replacement:
- import { Vega } from 'react-vega';
+ import { StellarVega } from '@snowflake/stellar-vega';
- <Vega spec={spec} />
+ <StellarVega spec={spec} />Key differences:
- Automatic theming - No need to pass theme config
- Better error handling - Built-in error UI and callbacks
- TypeScript first - Comprehensive type definitions
- Removed
onEmbed- UseonNewViewinstead for view access
Types
All types are exported for your convenience:
import type {
VegaSpec,
VegaLayer,
VegaTransformation,
VegaActions,
StellarVegaOptions,
Result,
} from '@snowflake/stellar-vega';Performance Considerations
- The component automatically cleans up Vega views on unmount
- ResizeObserver efficiently handles container size changes
- Tooltip handler is memoized to avoid recreations
- Config is memoized based on color scheme
Accessibility
- Charts have
role="img"andaria-labelattributes - Tooltips use
aria-live="polite"for screen reader announcements - Actions menu has proper ARIA labels
License
See LICENSE.
