Skip to main content

vistral

Use when building React streaming data visualizations with @timeplus/vistral — creating charts, writing VistralSpec, using VistralChart, configuring streaming data sources, or wiring up live data via ChartHandle.

Informations de source

Dépôt
timeplus-io/vistral
Dernière activité de la source
1 avril 2026 à 17:19
Langue détectée de SKILL.md
anglais
Étoiles
64
Forks
2

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
vistral
description
Use when building React streaming data visualizations with @timeplus/vistral — creating charts, writing VistralSpec, using VistralChart, configuring streaming data sources, or wiring up live data via ChartHandle.
# Vistral — Streaming Data Visualization ## Overview `@timeplus/vistral` is a React library for real-time streaming charts built on AntV G2. Two APIs exist — pick the right one before writing any code. ## Which API to Use ``` Need custom marks / full G2 control? → Grammar API: VistralChart + VistralSpec Want a quick chart with minimal config? → Config API: StreamChart + TimeSeriesConfig / BarColumnConfig / etc. ``` **AI agents should default to the Grammar API** — it's more expressive and maps cleanly to spec-driven generation. --- ## Grammar API (VistralChart + VistralSpec) ### Component ```tsx import { VistralChart, type ChartHandle } from '@timeplus/vistral'; import type { VistralSpec } from '@timeplus/vistral'; const ref = useRef<ChartHandle>(null); <VistralChart ref={ref} spec={spec} source={dataSource} // optional: declarative initial data height={400} theme="dark" // string | VistralTheme — 'dark', 'light', registered name, or VistralTheme object onReady={(handle) => { handle.append(rows); // add rows, re-render handle.replace(rows); // replace all data, re-render handle.clear(); // empty buffer, re-render handle.g2; // raw G2 instance }} /> ``` ### VistralSpec Fields | Field | Type | Description | |-------|------|-------------| | `marks` | `MarkSpec[]` | **Required.** Visual elements: `line`, `area`, `interval`, `point`, `rect`, `text`, etc. | | `scales` | `Record<string, ScaleSpec>` | Scale config per channel (`x`, `y`, `color`). Per-mark scales override. | | `transforms` | `TransformSpec[]` | Visual transforms: `stackY`, `dodgeX`, `bin`, `group` | | `temporal` | `TemporalSpec` | Streaming time control (see below) | | `streaming` | `StreamingSpec` | `maxItems`, `mode`, `throttle` | | `axes` | `AxesSpec` | `{ x?, y? }` — title, grid, label format/rotate | | `legend` | `LegendSpec \| false` | `{ position?, interactive? }` or `false` to hide | | `tooltip` | `TooltipSpec \| false` | `{ title?, items[] }` — `items[].format` maps to G2 `valueFormatter` | | `coordinate` | `CoordinateSpec` | `polar`, `theta`, `transpose`, etc. | | `annotations` | `AnnotationSpec[]` | Reference lines, ranges, text overlays | | `interactions` | `InteractionSpec[]` | `{ type: 'tooltip' \| 'brush' \| ... }` | | `theme` | `string \| VistralTheme` | Spec-level theme — overridden by component `theme` prop. Prefer setting theme on the component. | | `animate` | `boolean` | Disable for streaming: `animate: false` | | `g2Overrides` | `Record<string, unknown>` | Raw G2 options deep-merged last — override anything | ### Common Mark Pattern ```tsx const spec: VistralSpec = { marks: [{ type: 'line', encode: { x: 'time', y: 'value', color: 'series' }, scales: { x: { type: 'time' } }, style: { shape: 'smooth' }, }], temporal: { mode: 'axis', field: 'time', range: 5 }, axes: { y: { labels: { format: (v) => `${Number(v).toFixed(1)}` } } }, tooltip: { items: [{ field: 'value', name: 'Value', format: (v) => `${v}` }] }, animate: false, theme: 'dark', }; ``` ### Temporal Modes | Mode | When to use | Key fields | |------|-------------|------------| | `axis` | Sliding time window on X | `field: 'time'`, `range: 5` (minutes) | | `frame` | Latest timestamp only | `field: 'time'` | | `key` | Latest value per entity | `field: 'id'` or `field: ['region','server']` | ### g2Overrides — Escape Hatch Use for any G2 option not modeled by VistralSpec: ```tsx g2Overrides: { axis: { y: { tickCount: 5 } }, // tick count paddingLeft: 60, // view padding tooltip: { // raw G2 tooltip items: [{ channel: 'y', valueFormatter: (v) => formatBps(v) }] } } ``` --- ## Custom Themes ```tsx import { registerTheme, type VistralTheme } from '@timeplus/vistral'; // Register once at app startup (module level) registerTheme('corporate', { extends: 'light', // inherit from 'dark' (default) or 'light' palette: ['#0066CC', '#FF6600', '#00AA44'], font: { family: 'Roboto, sans-serif', size: 12 }, axis: { grid: { color: '#E0E0E0', dash: [4, 4] }, label: { color: '#333' } }, tooltip: { background: '#FFF', text: { color: '#111' }, border: { color: '#E0E0E0' } }, legend: { label: { color: '#333' } }, } satisfies VistralTheme); // Use by name <VistralChart spec={spec} theme="corporate" /> <StreamChart config={config} data={data} theme="corporate" /> // Or pass a VistralTheme object directly (no registration needed) <VistralChart spec={spec} theme={{ palette: ['#FF73B6', '#8890FF'], axis: { grid: { color: '#1A1A2E' } } }} /> ``` Built-in themes: `'dark'` (default) and `'light'`. Custom themes deep-merge onto the base. --- ## StreamDataSource Format ```tsx import type { StreamDataSource } from '@timeplus/vistral'; const source: StreamDataSource = { columns: [ { name: 'time', type: 'datetime64' }, { name: 'value', type: 'float64' }, { name: 'series', type: 'string' }, ], data: [ { time: '2024-01-01T00:00:00Z', value: 42.5, series: 'A' }, // object rows // OR array rows (must match column order): // ['2024-01-01T00:00:00Z', 42.5, 'A'], ], }; ``` **Column types:** `string`, `number`, `datetime`, `datetime64`, `float32`, `float64`, `int32`, `int64`, `boolean` --- ## Streaming Pattern (Grammar API) ```tsx function LiveChart() { const handleRef = useRef<ChartHandle>(null); const loadedRef = useRef(false); // ← REQUIRED: React 18 Strict Mode guard useEffect(() => { if (!loadedRef.current && handleRef.current) { loadedRef.current = true; handleRef.current.append(historicalRows); // seed history once } }, []); useEffect(() => { const id = setInterval(() => { handleRef.current?.append(newRows); // push live rows }, 1000); return () => clearInterval(id); }, []); return <VistralChart ref={handleRef} spec={spec} height={400} theme="dark" />; } // Note: use `ref` for ongoing appends, `onReady` for one-time setup. // Do NOT duplicate seeding in both — use loadedRef to guard whichever you choose. ``` --- ## Config API (StreamChart) — Quick Reference ```tsx import { StreamChart, useStreamingData } from '@timeplus/vistral'; import type { TimeSeriesConfig, StreamDataSource } from '@timeplus/vistral'; function QuickChart() { // useStreamingData<ElementType> — generic is the ROW type, not array const { data, append } = useStreamingData<Record<string, unknown>>([], 1000); const config: TimeSeriesConfig = { chartType: 'line', xAxis: 'time', yAxis: 'value', color: 'series', lineStyle: 'curve', legend: true, temporal: { mode: 'axis', field: 'time', range: 5 }, }; // StreamChart has NO ref/ChartHandle (use VistralChart for imperative control) return <StreamChart config={config} data={{ columns, data }} theme="dark" />; } ``` --- ## Critical Gotchas | Mistake | Fix | |---------|-----| | `useStreamingData<Row[]>` (wrong generic) | Use `useStreamingData<Row>` — element type, not array | | `<StreamChart ref={...}>` | `StreamChart` has NO ref/ChartHandle. Use `VistralChart` for imperative append/replace/clear. | | `append([singleRow])` when row is already an array | Wrap: `append([[singleRow]])` — `append` treats top-level array as multiple items | | No `loadedRef` guard when seeding history | React 18 Strict Mode runs effects twice → doubled rows | | `tooltip.items[].format` not working | `format` IS supported — translated to G2's `valueFormatter` internally (v0.1.6+). Use `g2Overrides.tooltip` only for raw G2 tooltip features beyond what `TooltipSpec` models. | | `legend.interactive` silently ignored | Use `legend: { interactive: true }` — now translated correctly | --- ## Axis Label Formatting ```tsx axes: { y: { title: 'Throughput', labels: { 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(0)} bps`; } } } } ```
Voir sur GitHub