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.
소스 정보
- 저장소
- timeplus-io/vistral
- 최근 소스 활동
- 2026년 4월 1일 17:19
- 감지된 SKILL.md 언어
- 영어
- 스타
- 64
- 포크
- 2
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
SKILL.md 표시 중
SKILL.md
소스 지침 · 읽기 전용 미리보기- 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`;
}
}
}
}
```
GitHub에서 보기