| render_with_liquid | false |
|---|
Vistral provides two API levels:
- Grammar API (
VistralSpec+VistralChart) — composable, low-level, full control over marks, scales, transforms, coordinates, and streaming behavior. - Chart Config API (
TimeSeriesConfig,BarColumnConfig,TableConfig,MarkdownConfig, etc. +StreamChart) — high-level, opinionated presets for common chart types including line/area, bar/column, single value, multiple value, data table, markdown, and geo map.
The core specification type. Every visualization in Vistral is described by a VistralSpec.
import { VistralChart, type VistralSpec } from '@timeplus/vistral';
const spec: VistralSpec = {
marks: [{ type: 'line', encode: { x: 'time', y: 'value' } }],
scales: { x: { type: 'time' }, y: { type: 'linear', nice: true } },
temporal: { mode: 'axis', field: 'time', range: 5 },
streaming: { maxItems: 2000 },
theme: 'dark',
animate: false,
};
<VistralChart spec={spec} source={dataSource} height={400} />| Field | Type | Description |
|---|---|---|
marks |
MarkSpec[] |
Required. Visual marks to render (line, area, interval, point, cell, rect, text, etc.) |
scales |
Record<string, ScaleSpec> |
Scale configs shared across all marks. Per-mark scales override these. |
transforms |
TransformSpec[] |
Transforms applied to all marks (stackY, dodgeX, bin, group, etc.) |
coordinate |
CoordinateSpec |
Coordinate system (cartesian, polar, theta, radar, transpose, etc.) |
streaming |
StreamingSpec |
Streaming data management (maxItems, append/replace mode, throttle) |
temporal |
TemporalSpec |
Temporal bounding (axis sliding window, frame snapshot, key deduplication) |
axes |
AxesSpec |
X/Y axis configuration (title, grid, labels) |
legend |
LegendSpec | false |
Legend configuration or false to hide |
tooltip |
TooltipSpec | false |
Tooltip configuration or false to hide |
annotations |
AnnotationSpec[] |
Reference lines, ranges, text annotations |
interactions |
InteractionSpec[] |
Interactions (tooltip, brush, element highlight, etc.) |
theme |
string | VistralTheme |
Theme name, registered name, or VistralTheme object. Default: 'dark'. Overridden by the theme prop on VistralChart. |
animate |
boolean |
Enable/disable animations. Default: false for streaming |
g2Overrides |
Record<string, unknown> |
Raw G2 options deep-merged on top of compiled output. Overrides always win. |
A mark is an atomic visual element — what to draw.
{
type: 'line', // Any G2 5.x mark type
encode: { x: 'time', y: 'value', color: 'series' },
scales: { y: { type: 'log' } }, // Per-mark scale overrides
transforms: [{ type: 'stackY' }], // Per-mark transforms
style: { shape: 'smooth', opacity: 0.8 },
labels: [{ text: 'value', overlapHide: true }],
tooltip: { title: 'time', items: [{ field: 'value', name: 'Value', format: (v) => `${v}` }] },
animate: false,
}Supported mark types: line, area, interval, point, rect, cell, link, polygon, vector, text, image, shape, box, boxplot, connector, lineX, lineY, range, rangeX, rangeY, density, heatmap, chord, gauge, liquid, sunburst, wordCloud, forceGraph, sankey, tree, treemap, pack, geoPath, geoView
Controls how streaming data is managed.
| Property | Type | Default | Description |
|---|---|---|---|
maxItems |
number |
1000 |
Maximum data rows retained in memory |
mode |
'append' | 'replace' |
'append' |
How new data is incorporated |
throttle |
number |
0 |
Minimum ms between render updates |
| Property | Type | Default | Description |
|---|---|---|---|
position |
'top' | 'bottom' | 'left' | 'right' |
- | Legend position |
interactive |
boolean |
false |
Enable click-to-toggle series on legend items |
Pass false instead of a LegendSpec to hide the legend entirely.
| Property | Type | Description |
|---|---|---|
title |
string | ((datum) => string) |
Tooltip header — field name or accessor function |
items |
TooltipItemSpec[] |
List of value rows shown in the tooltip |
Pass false instead of a TooltipSpec to disable the tooltip entirely.
TooltipItemSpec
| Property | Type | Description |
|---|---|---|
field |
string |
Data field to display |
name |
string |
Label shown next to the value |
format |
(value: unknown) => string |
Value formatter function. Translated to G2's valueFormatter internally. |
// Format network throughput in the tooltip
tooltip: {
title: (d) => String(d.time),
items: [
{
field: 'value',
name: 'Throughput',
format: (v) => {
const n = Number(v);
if (n >= 1e9) return `${(n / 1e9).toFixed(1)} Gbps`;
if (n >= 1e6) return `${(n / 1e6).toFixed(1)} Mbps`;
if (n >= 1e3) return `${(n / 1e3).toFixed(1)} Kbps`;
return `${n.toFixed(1)} bps`;
},
},
],
}A raw escape hatch for any G2 option that VistralSpec does not model directly. The value is deep-merged on top of the compiled G2 options as the last step — overrides always win, including over theme and temporal domain settings.
Merge rules:
- Plain objects are merged recursively (override wins at each leaf)
- Arrays replace entirely (no element-wise merging)
- Primitives and functions replace
// Control axis tick count (not modeled by VistralSpec)
g2Overrides: { axis: { y: { tickCount: 5 } } }
// View padding
g2Overrides: { paddingLeft: 60, paddingTop: 8 }
// Advanced tooltip using G2's native API
g2Overrides: {
tooltip: {
items: [{ channel: 'y', name: 'Value', valueFormatter: (v) => `${v} bps` }],
},
}Tip: Most tooltip formatting needs are covered by
spec.tooltip.items[].format(the typed path). Useg2Overrides.tooltiponly when you need G2-specific tooltip features not exposed byTooltipSpec.
Controls temporal bounding — how time governs data lifecycle on the canvas.
| Property | Type | Description |
|---|---|---|
mode |
'axis' | 'frame' | 'key' |
Required. Temporal binding mode |
field |
string | string[] |
Required. Data field(s) used for temporal binding |
range |
number | 'Infinity' |
Axis-mode only. Time window in minutes |
keyField |
string |
Key-mode only. Field identifying unique entities |
| Property | Type | Description |
|---|---|---|
type |
string |
'linear', 'log', 'pow', 'sqrt', 'time', 'ordinal', 'band', 'point', 'quantile', 'quantize', 'threshold', 'sequential' |
domain |
unknown[] |
Explicit domain |
range |
unknown[] |
Explicit range |
nice |
boolean |
Round to nice numbers |
clamp |
boolean |
Clamp values to domain |
padding |
number |
Padding for band/point scales |
mask |
string |
Time format mask (time scales) |
| Property | Type | Description |
|---|---|---|
type |
string |
'cartesian', 'polar', 'theta', 'radial', 'parallel', 'radar', 'helix', 'fisheye' |
transforms |
Array<{type: string}> |
Coordinate transforms, e.g. [{ type: 'transpose' }] |
import { VistralChart, type ChartHandle } from '@timeplus/vistral';
const ref = useRef<ChartHandle>(null);
<VistralChart
ref={ref}
spec={spec}
source={dataSource}
height={400}
onReady={(handle) => {
// handle.append(newRows)
// handle.replace(allRows)
// handle.clear()
// handle.g2 — underlying G2 instance
}}
/>| Prop | Type | Description |
|---|---|---|
spec |
VistralSpec |
Required. The visualization specification |
source |
StreamDataSource |
Initial/declarative data source |
theme |
string | VistralTheme |
Theme name, registered name, or VistralTheme object. Overrides spec.theme. Default: 'dark'. |
width |
number |
Explicit width in pixels (defaults to 100% of container) |
height |
number |
Explicit height in pixels (defaults to 100% of container) |
className |
string |
CSS class for wrapper div |
style |
CSSProperties |
Inline styles for wrapper div |
onReady |
(handle: ChartHandle) => void |
Called when chart is ready |
| Method | Description |
|---|---|
append(rows) |
Add rows to data buffer, re-render |
replace(rows) |
Replace all data, re-render |
clear() |
Empty data buffer, re-render |
g2 |
Direct access to G2 Chart instance |
import { StreamChart } from '@timeplus/vistral';
<StreamChart
config={config}
data={dataSource}
theme="dark"
/>| Prop | Type | Default | Description |
|---|---|---|---|
config |
ChartConfig |
required | Chart configuration — one of TimeSeriesConfig, BarColumnConfig, SingleValueConfig, MultipleValueConfig, TableConfig, MarkdownConfig, OHLCConfig, or GeoChartConfig |
data |
StreamDataSource |
required | Data source with columns and data |
theme |
string | VistralTheme |
'dark' |
Theme name, registered name, or VistralTheme object |
showTable |
boolean |
false |
Render a data table instead of the chart |
className |
string |
- | CSS class for wrapper div |
style |
CSSProperties |
- | Inline styles for wrapper div |
onConfigChange |
(config: ChartConfig) => void |
- | Called when config changes |
Convert high-level configs to VistralSpec:
import { compileTimeSeriesConfig, compileBarColumnConfig } from '@timeplus/vistral';
const spec = compileTimeSeriesConfig({
chartType: 'line',
xAxis: 'timestamp',
yAxis: 'value',
color: 'series',
lineStyle: 'curve',
temporal: { mode: 'axis', field: 'timestamp', range: 5 },
});
// spec is a VistralSpec — can be further customized before renderingFor advanced use, the spec engine functions are exported:
import { buildG2Options, translateToG2Spec, applyTemporalTransforms } from '@timeplus/vistral';
// Full pipeline: VistralSpec + data → G2 options
const g2Options = buildG2Options(spec, data);
// Individual steps
const specWithTemporal = applyTemporalTransforms(spec, data);
const g2Spec = translateToG2Spec(specWithTemporal);All chart types support a unified temporal configuration for handling streaming data:
| Property | Type | Description |
|---|---|---|
mode |
'axis' | 'frame' | 'key' |
Temporal binding mode |
field |
string | string[] |
Field name(s) for temporal binding |
range |
number | 'Infinity' |
Time range in minutes (axis mode only) |
| Mode | Description | Supported Charts |
|---|---|---|
axis |
Sliding time window on X-axis | Line, Area |
frame |
Filter to latest timestamp only | Table, Bar, Column, Geo (L7 + canvas), Markdown |
key |
Keep latest value per unique key | Table, Bar, Column, Geo (L7 + canvas), Markdown |
Example:
// Axis-bound: 5-minute sliding window
temporal: { mode: 'axis', field: 'timestamp', range: 5 }
// Frame-bound: show only latest timestamp
temporal: { mode: 'frame', field: 'timestamp' }
// Key-bound: deduplicate by ID
temporal: { mode: 'key', field: 'id' }
// Key-bound: deduplicate by composite key (Region + Server)
temporal: { mode: 'key', field: ['region', 'server'] }All chart configs extend this base:
| Property | Type | Default | Description |
|---|---|---|---|
chartType |
string |
required | Chart type identifier |
colors |
string[] |
Dawn palette | Custom color palette |
temporal |
TemporalConfig |
- | Temporal binding configuration |
| Property | Type | Default | Description |
|---|---|---|---|
chartType |
'line' | 'area' |
required | Chart type |
xAxis |
string |
required | Time field name |
yAxis |
string |
required | Value field name |
color |
string |
- | Series grouping field |
xTitle |
string |
- | X-axis title |
yTitle |
string |
- | Y-axis title |
yRange |
{ min?, max? } |
auto | Y-axis range |
dataLabel |
boolean |
false |
Show data labels |
showAll |
boolean |
false |
Show all labels or just last |
legend |
boolean |
false |
Show legend |
gridlines |
boolean |
false |
Show gridlines |
points |
boolean |
false |
Show data points |
lineStyle |
'curve' | 'straight' |
'curve' |
Line interpolation |
fractionDigits |
number |
2 |
Decimal places |
unit |
{ position, value } |
- | Unit prefix/suffix |
xFormat |
string |
auto | X-axis date format mask |
yTickLabel |
{ maxChar } |
25 |
Y-axis label max characters |
Temporal Usage: Use temporal.mode: 'axis' with temporal.range for sliding time windows.
| Property | Type | Default | Description |
|---|---|---|---|
chartType |
'bar' | 'column' |
required | Chart type |
xAxis |
string |
required | Category field |
yAxis |
string |
required | Value field |
color |
string |
- | Grouping field |
groupType |
'stack' | 'dodge' |
'stack' |
Multi-series layout |
xTitle |
string |
- | X-axis title |
yTitle |
string |
- | Y-axis title |
dataLabel |
boolean |
false |
Show data labels |
legend |
boolean |
false |
Show legend |
gridlines |
boolean |
false |
Show gridlines |
fractionDigits |
number |
2 |
Decimal places |
unit |
{ position, value } |
- | Unit prefix/suffix |
xTickLabel |
{ maxChar } |
10 |
X-axis label max characters |
yTickLabel |
{ maxChar } |
25 |
Y-axis label max characters |
Temporal Usage: Use temporal.mode: 'frame' or temporal.mode: 'key' for streaming data.
| Property | Type | Default | Description |
|---|---|---|---|
chartType |
'singleValue' |
required | Chart type |
yAxis |
string |
required | Value field |
fontSize |
number |
64 |
Font size in pixels |
color |
string |
'blue' |
Color palette name |
fractionDigits |
number |
2 |
Decimal places |
sparkline |
boolean |
false |
Show sparkline |
sparklineColor |
string |
'purple' |
Sparkline color |
delta |
boolean |
false |
Show change indicator |
increaseColor |
string |
'green' |
Color for positive changes |
decreaseColor |
string |
'red' |
Color for negative changes |
unit |
{ position, value } |
- | Unit prefix/suffix |
Note: SingleValue implicitly uses the latest value (key-bound behavior).
| Property | Type | Default | Description |
|---|---|---|---|
chartType |
'multipleValue' |
required | Chart type |
yAxis |
string |
required | Value field |
key |
string |
required | Key field to split values horizontally |
fontSize |
number |
48 |
Font size in pixels |
color |
string |
'cyan' |
Color palette name |
fractionDigits |
number |
2 |
Decimal places |
sparkline |
boolean |
false |
Show sparkline |
sparklineColor |
string |
'blue' |
Sparkline color |
delta |
boolean |
false |
Show change indicator |
increaseColor |
string |
'green' |
Color for positive changes |
decreaseColor |
string |
'red' |
Color for negative changes |
unit |
{ position, value } |
- | Unit prefix/suffix |
Note: MultipleValue auto-splits incoming streaming data by key and aligns them horizontally.
| Property | Type | Default | Description |
|---|---|---|---|
chartType |
'table' |
required | Chart type |
tableStyles |
Record<string, ColumnStyle> |
- | Per-column styling keyed by field name |
tableWrap |
boolean |
false |
Enable text wrapping in cells |
ColumnStyle Properties:
| Property | Type | Default | Description |
|---|---|---|---|
name |
string |
field name | Display name for the column header |
show |
boolean |
true |
Whether to show this column |
width |
number |
auto | Column width in pixels |
fractionDigits |
number |
browser default | Maximum decimal places for numeric values (uses toLocaleString) |
trend |
boolean |
false |
Show ▲▼ trend indicator next to the value. Arrow direction reflects change since the last update. An invisible placeholder is always reserved so layout stays stable when there is no change. |
increaseColor |
string |
'#22c55e' |
Arrow color when value increased. Only used when trend: true. |
decreaseColor |
string |
'#ef4444' |
Arrow color when value decreased. Only used when trend: true. |
miniChart |
'none' | 'sparkline' | 'bar' |
'none' |
Mini chart rendered inside the cell. 'bar' draws a left-aligned horizontal bar proportional to the column maximum, enabling easy row-to-row comparison. |
color |
ColorConfig |
- | Cell or row background coloring |
Trend tracking behaviour:
- When
temporal.mode === 'key': trend compares the current value vs the previous value for the same key entity (e.g. same stock symbol or host). Use this for live-updating key-bound tables. - Otherwise: trend compares the current last row vs the previous last row per column.
ColorConfig:
| Property | Type | Description |
|---|---|---|
type |
'none' | 'scale' | 'condition' |
Coloring strategy |
colorScale |
string |
Color scale name (used when type: 'scale') |
conditions |
Condition[] |
List of conditions evaluated top-to-bottom (used when type: 'condition') |
Condition:
| Property | Type | Description |
|---|---|---|
operator |
'gt' | 'lt' | 'eq' | 'gte' | 'lte' | 'contains' | '!contains' |
Comparison operator. contains / !contains perform substring matching on the string representation of the value. |
value |
string | number |
Value to compare against |
color |
string |
Background color applied to the cell when the condition matches |
highlightRow |
boolean |
When true, the entire row gets this color at 20% opacity instead of the individual cell. Cell-level coloring is suppressed for highlighted rows. |
Temporal Usage: Use temporal.mode: 'key' for deduplication and per-entity trend tracking, or temporal.mode: 'frame' for latest snapshot.
Renders a Markdown template populated with live values from streaming data.
| Property | Type | Default | Description |
|---|---|---|---|
chartType |
'markdown' |
required | Chart type |
content |
string |
required | Markdown template with placeholder syntax (see below) |
temporal |
TemporalConfig |
- | Controls how data is reduced before substitution |
Template Placeholder Syntax:
| Syntax | Mode | Description |
|---|---|---|
{{fieldName}} |
frame / axis / none | Replaced with the value of fieldName from the latest row in the current dataset |
{{@keyValue::fieldName}} |
key | Replaced with fieldName from the row whose key field (temporal.field) equals keyValue |
Temporal Binding:
temporal.mode |
Data reduction | Substitution |
|---|---|---|
| (none) | All rows kept | Last row |
'frame' |
Latest timestamp only | Last row |
'axis' |
Sliding time window | Last row |
'key' |
One row per key entity | {{@key::field}} syntax — each keyValue resolved independently |
Example — key mode:
const config: MarkdownConfig = {
chartType: 'markdown',
temporal: { mode: 'key', field: 'city' },
content: `
## Weather
| City | Temp |
|------|------|
| New York | {{@New York::temp_c}} °C |
| London | {{@London::temp_c}} °C |
`,
};Example — latest row:
const config: MarkdownConfig = {
chartType: 'markdown',
content: '## Status\n\nService **{{service}}** — {{status}} — {{rps}} req/s',
};MarkdownChart can be used standalone or via StreamChart. Use standalone when you want direct access to props without the StreamChart routing layer.
import { MarkdownChart, useStreamingData, type MarkdownConfig } from '@timeplus/vistral';
function WeatherCard() {
const { data, append } = useStreamingData<unknown[]>([], 100);
// ... append streaming rows ...
const dataSource = {
columns: [
{ name: 'city', type: 'string' },
{ name: 'temp_c', type: 'float64' },
{ name: 'humidity', type: 'int64' },
],
data,
};
const config: MarkdownConfig = {
chartType: 'markdown',
temporal: { mode: 'key', field: 'city' },
content: `
## Weather
| City | Temp | Humidity |
|------|------|----------|
| Tokyo | {{@Tokyo::temp_c}} °C | {{@Tokyo::humidity}}% |
| London | {{@London::temp_c}} °C | {{@London::humidity}}% |
`,
};
return <MarkdownChart config={config} data={dataSource} theme="dark" />;
}| Prop | Type | Default | Description |
|---|---|---|---|
config |
MarkdownConfig |
required | Markdown configuration with content template and optional temporal |
data |
StreamDataSource |
required | Streaming data source |
theme |
string | VistralTheme |
'dark' |
Theme name or object. Affects text color and code block backgrounds. |
className |
string |
- | CSS class for the outer wrapper div |
style |
CSSProperties |
- | Inline styles for the outer wrapper div |
| Property | Type | Default | Description |
|---|---|---|---|
chartType |
'geo' |
required | Chart type |
latitude |
string |
required | Latitude field name |
longitude |
string |
required | Longitude field name |
color |
string |
- | Color grouping field |
size |
{ key?, min?, max? } |
- | Point size configuration |
center |
[number, number] |
auto | Initial center [lat, lng] |
zoom |
number |
2 |
Initial zoom level (1-18) |
tileProvider |
string |
'cartodb-dark' / 'cartodb-light' |
Tile provider (auto-selected from theme if omitted) |
showZoomControl |
boolean |
true |
Show zoom buttons |
showCenterDisplay |
boolean |
false |
Show center coordinates |
pointOpacity |
number |
0.8 |
Point opacity (0-1) |
pointColor |
string |
'#3B82F6' |
Default point color (single-color mode) |
autoFit |
boolean |
false |
Automatically fit zoom and center to the bounding box of all visible points. Re-fits on each data update. Supported by both 'l7' and 'canvas' engines. |
mapEngine |
'l7' | 'canvas' |
'l7' |
Rendering engine (see below) |
mapboxToken |
string |
- | Mapbox API token, required only for Mapbox-hosted styles |
Tile Providers: 'openstreetmap', 'cartodb-dark', 'cartodb-light'
Map Engines:
| Engine | Description | Dependencies |
|---|---|---|
'l7' (default) |
AntV L7 + MapLibre GL — WebGL-accelerated, smooth pan/zoom, GPU-rendered points. Free tile providers work without any API token. | @antv/l7, @antv/l7-maps (bundled) |
'canvas' |
Built-in canvas tile renderer — draws map tiles on a <canvas> element. No additional dependencies, lower performance at large point counts. |
none |
Example — L7 engine (default, no token needed):
const config: GeoChartConfig = {
chartType: 'geo',
latitude: 'lat',
longitude: 'lng',
color: 'category',
tileProvider: 'cartodb-dark',
// mapEngine: 'l7', // default, can be omitted
};Example — canvas engine:
const config: GeoChartConfig = {
chartType: 'geo',
latitude: 'lat',
longitude: 'lng',
mapEngine: 'canvas',
tileProvider: 'cartodb-dark',
};Temporal Usage: Use temporal.mode: 'key' for tracking entities or temporal.mode: 'frame' for snapshots.
Manage streaming data with automatic size limiting.
const { data, append, replace, clear } = useStreamingData<T>(initialData, maxItems);| Return | Type | Description |
|---|---|---|
data |
T[] |
Current data array |
append |
(items: T[]) => void |
Add items to end |
replace |
(items: T[]) => void |
Replace all data |
clear |
() => void |
Clear all data |
Note: append treats arrays as multiple items. Wrap single row: append([[row]]) not append([row]).
Access the underlying AntV G2 chart instance.
const { chart, chartRef, isMouseOver, activeColor, setActiveColor } = useChart(options);Process raw data into chart-ready format.
const processedData = useDataSource(source, xKey, yKey, colorKey);Manage theme state.
const { theme, setTheme, toggleTheme, isDark } = useChartTheme('dark');Auto-detect suitable chart configuration from data schema.
const autoConfig = useAutoConfig(columns);Track value history for sparkline rendering.
const sparklineValues = useSparklineData(currentValue, maxHistory);import { isNumericColumn, isDateTimeColumn, isStringColumn } from '@timeplus/vistral';
isNumericColumn('float64'); // true
isDateTimeColumn('datetime64'); // true
isStringColumn('varchar'); // trueimport { formatNumber, abbreviateNumber, formatDuration, formatBytes } from '@timeplus/vistral';
formatNumber(1234567.89, 2); // "1,234,567.89"
abbreviateNumber(1500000); // "1.5m"
formatDuration(125000); // "2.1m"
formatBytes(1536000); // "1.46 MB"import { applyTemporalFilter, filterByLatestTimestamp, filterByKey } from '@timeplus/vistral';
// Filter to latest timestamp rows
const latestRows = filterByLatestTimestamp(data, timeFieldIndex);
// Keep latest row per unique key(s)
const deduplicated = filterByKey(data, keyFieldIndex); // keyFieldIndex can be number | number[]
// Apply temporal config (supports mode: 'key' with multiple fields)
const filtered = applyTemporalFilter(data, columns, { mode: 'key', field: ['id', 'category'] });import { processDataSource, rowToArray, findColumnIndex } from '@timeplus/vistral';
// Process data source for chart consumption
const processed = processDataSource(source, xKey, yKey, colorKey);
// Convert row object to array
const rowArray = rowToArray(rowObject, columns);
// Find column index by name
const index = findColumnIndex(columns, 'timestamp');Themes control the non-data ink of a chart: background, fonts, axis lines, grid, legend, tooltip, and data color palette. They do not change how data is mapped (marks, scales, encodings).
Two built-in themes are available: 'dark' (default) and 'light'. Pass them by name anywhere a theme is accepted.
<VistralChart spec={spec} theme="dark" />
<VistralChart spec={spec} theme="light" />
<StreamChart config={config} data={data} theme="light" />import type { VistralTheme, AxisStyleSpec } from '@timeplus/vistral';
interface AxisStyleSpec {
label?: { color?: string; size?: number };
title?: { color?: string; size?: number; fontWeight?: number };
grid?: { color?: string; dash?: number[] };
line?: { color?: string };
tick?: { color?: string };
}
interface VistralTheme {
extends?: 'dark' | 'light'; // Base theme to inherit from. Default: 'dark'
palette?: string[]; // Data series color palette
background?: string; // Chart background color
font?: {
family?: string; // Font family string
size?: number; // Base font size in px
};
axis?: AxisStyleSpec; // Axis styling (applies to both x and y)
legend?: {
label?: { color?: string; size?: number };
title?: { color?: string; size?: number };
background?: string;
};
tooltip?: {
background?: string;
text?: { color?: string; size?: number };
border?: { color?: string };
};
g2ThemeOverrides?: Record<string, unknown>; // Raw G2 theme options, merged last
}Custom themes deep-merge onto their base ('dark' or 'light'). Only specified fields override the base — unspecified fields inherit.
Register a named custom theme for reuse across components.
import { registerTheme, type VistralTheme } from '@timeplus/vistral';
// Call once at app startup (module level)
registerTheme('corporate', {
extends: 'light',
palette: ['#0066CC', '#FF6600', '#00AA44', '#9900CC'],
background: '#F8F9FA',
font: { family: 'Roboto, sans-serif', size: 12 },
axis: {
grid: { color: '#E0E0E0', dash: [4, 4] },
label: { color: '#333333' },
line: { color: '#CCCCCC' },
tick: { color: '#CCCCCC' },
},
tooltip: { background: '#FFFFFF', text: { color: '#111111' }, border: { color: '#E0E0E0' } },
legend: { label: { color: '#333333' } },
} satisfies VistralTheme);
// Use by name
<VistralChart spec={spec} theme="corporate" />
<StreamChart config={config} data={data} theme="corporate" />Built-in names 'dark' and 'light' cannot be overwritten.
Pass a VistralTheme object directly without registering:
const minimalDark: VistralTheme = {
palette: ['#FF73B6', '#8890FF', '#27CCA8'],
axis: { grid: { color: '#1A1A2E' } },
};
<VistralChart spec={spec} theme={minimalDark} />import { resolveTheme, isDarkTheme, DARK_THEME, LIGHT_THEME } from '@timeplus/vistral';
// Resolve any theme input to a complete VistralTheme
const resolved = resolveTheme('corporate'); // deep-merged onto its base
const resolved2 = resolveTheme({ palette: ['#FF0000'] }); // merged onto DARK_THEME
// Check if a theme resolves to dark mode
isDarkTheme('dark'); // true
isDarkTheme('light'); // false
isDarkTheme({ extends: 'light', palette: [...] }); // false
isDarkTheme(undefined); // true (defaults to dark)
// Built-in theme objects (Readonly)
DARK_THEME; // complete dark VistralTheme
LIGHT_THEME; // complete light VistralThemeWhen both spec.theme and the component theme prop are set, the component prop wins:
// spec.theme is ignored — component prop takes precedence
<VistralChart spec={{ ...spec, theme: 'light' }} theme="corporate" />import { multiColorPalettes, singleColorPalettes, findPaletteByLabel } from '@timeplus/vistral';
// Multi-color: 'Timeplus', 'Dawn', 'Morning', 'Midnight', 'Ocean', 'Sunset'
// Single-color: 'pink', 'red', 'orange', 'yellow', 'green', 'teal', 'indigo', 'purple', 'gray'
const palette = findPaletteByLabel('Timeplus');
// { label: 'Timeplus', values: [...], keyColor: 0, keyColorValue: '...' }Note:
VistralTheme.palettesets the data series colors directly and takes precedence over the config-levelcolorsfield. Usepalettein a theme when you want the color sequence to be part of a reusable theme definition.
Deprecated:
darkTheme,lightTheme,getTheme, andChartThemefrom@timeplus/vistralare deprecated. UseDARK_THEME,LIGHT_THEME,resolveTheme, andVistralThemeinstead.