ChartView
ChartView renders charts by consuming a unified stream of K-line bars and script execution events from a ChartProvider. It handles layout, rendering, and user interaction.
For a comparison with data provider approaches, see Chart View Overview.
Architecture
The client-side ChartView connects to your data and execution source through the ChartProvider trait. The provider streams a single unified ChartStreamEvent sequence — candlestick bars and per-script output events — to the chart renderer.
When to Use
Use ChartView with a remote provider when:
- Script compilation and execution happen on a server
- You want a thin client that only renders — no local VM dependency
- You're building a web-based charting platform with server-side execution
Use ChartView with LocalChartProvider (requires the local feature) when:
- You want fully in-process VM execution
- You're prototyping or building a desktop/CLI application
Creating a ChartView
use navi_chart::{ChartView, ChartEvent, Size, Theme};
// Create an empty chart — no symbol or timeframe required at construction.
let cv = ChartView::new(
provider, // impl ChartProvider
my_platform, // impl Platform
Size { width: 800.0, height: 600.0 },
my_canvas_ctx, // impl DrawingContext
);
cv.set_theme(Theme::dark());
cv.set_locale("en");
// Set symbol and timeframe when ready — this starts the data stream.
cv.set_symbol("NASDAQ:AAPL");
cv.set_timeframe(TimeFrame::days(1));Generic Parameters
ChartView<P, T> is generic over:
| Parameter | Description |
|---|---|
P: ChartProvider | Your data and script event source |
T: Clone + PartialEq + Display | A user-defined "tag" type attached to each script. Its Display output is used as the label title for compile-error slots. |
Implementing ChartProvider
The ChartProvider trait has one associated type and one method that returns a unified stream:
use futures_util::Stream;
use navi_chart::ChartProvider;
use navi_chart_wire::{ChartStreamEvent, ChartStreamRequest};
use navi_types::TimeFrame;
pub trait ChartProvider {
/// Script handle understood by this provider.
///
/// The provider chooses what shape this takes — Navi source,
/// a hash, a cache key, a UUID, a pre-compiled `Program`, …
type Script: Clone + 'static;
/// Stream unified K-line + script events for the given symbol, timeframe,
/// and scripts. Events are tagged with `script_id` values `0..n`
/// corresponding to the index in `request.scripts`.
fn chart_stream(
&self,
symbol: String,
timeframe: TimeFrame,
request: ChartStreamRequest<Self::Script>,
) -> impl Stream<Item = ChartStreamEvent> + 'static;
}Choosing the Script handle type
type Script lets the provider decide how a script is identified. Common choices:
type Script = … | When to use it |
|---|---|
String | The handle is the Navi source code. The backend compiles on every chart_stream call (or caches by hash internally). |
u64 / a content hash | The client uploads source once; only the hash is passed in ChartStreamRequest. |
Uuid | The backend maintains a session-scoped registry. |
navi_chart::Program | Use LocalChartProvider — the client compiles locally and ships the bytecode. |
ChartStreamRequest
ChartStreamRequest<S> carries the script list and global configuration:
pub struct ChartStreamRequest<S> {
/// Scripts to execute, in order. Events in the stream are tagged
/// with their 0-based index in this list.
pub scripts: Vec<ScriptDescriptor<S>>,
/// Locale for diagnostic messages (e.g. `"en"`, `"zh-CN"`).
pub locale: String,
/// Which trading sessions to accept as input bars.
pub input_sessions: InputSessions,
}Each ScriptDescriptor<S> carries the script handle and user-overridden input values:
pub struct ScriptDescriptor<S> {
pub script: S,
pub input_values: InputValues, // HashMap<usize, serde_json::Value>
}ChartStreamEvent
ChartStreamEvent is the item type yielded by chart_stream. Your provider must produce these events in the correct order:
pub enum ChartStreamEvent {
/// A candlestick bar. Historical bars come before `HistoryEnd`;
/// realtime bars (which may update in place) come after.
Bar(Candlestick),
/// Boundary between historical and realtime data. Emitted exactly once.
/// Applies globally to all scripts in the session.
HistoryEnd,
/// An event produced by a specific script.
Script(TaggedScriptEvent),
/// A session-level unrecoverable error. The stream terminates after this.
Error(Error),
}
pub struct TaggedScriptEvent {
/// Zero-based index into `ChartStreamRequest::scripts`.
pub script_id: u64,
pub event: ScriptEventPayload,
}
pub enum ScriptEventPayload {
/// Script metadata. Emit once per script before the first bar.
SessionInfo { script_info: Arc<ScriptInfo>, symbol_info: Box<SymbolInfo> },
/// Visual output event (plots, labels, lines, etc.).
Draw(DrawEvent),
/// Strategy order/fill event.
Strategy(StrategyEvent),
/// A per-script error. Does not affect other scripts in the session.
Error(Error),
}Stream Protocol
SessionInfo{id=0} SessionInfo{id=1} ← per-script, before first bar
Bar(t0) Script{id=0, Draw} Script{id=1, Draw}
Bar(t1) Script{id=0, Draw} Script{id=1, Draw}
…
HistoryEnd ← global, exactly once
Bar(realtime) Script{id=0, Draw} …LocalChartProvider
The LocalChartProvider (requires the local feature) runs scripts in-process using the Navi VM. It wraps any DataProvider:
use navi_chart::LocalChartProvider;
let provider = LocalChartProvider::new(data_provider);
// Optionally configure:
// .with_execution_limits(limits)
// .with_mtf_wait(Duration::from_millis(200))
let cv = ChartView::new(provider, platform, size, ctx);
cv.set_symbol(symbol);
cv.set_timeframe(tf);The DataProvider supplies K-line data for both the main series and any request.security() MTF calls.
Adding Scripts
ChartView::add_script accepts a value of type P::Script:
// With a remote string-handle provider:
let id = cv.add_script(
"//@version=6\nindicator(\"RSI\")\nplot(ta.rsi(close, 14))".to_string(),
"rsi".to_string(),
);
// With LocalChartProvider (Script = Program):
use navi_chart::Program;
let program: Program = compile(source).await?;
let id = cv.add_script(program, "rsi".to_string());When any script is added, removed, or has its configuration changed, ChartView restarts the session — calling chart_stream() again with the updated script list. All scripts replay from bar 0.
Persistence
ChartView exposes save() / load() for snapshot persistence:
let snapshot = cv.save(); // ChartSnapshot<T, P::Script>
let json = serde_json::to_string(&snapshot).unwrap();
let snapshot: ChartSnapshot<String, MyScript> = serde_json::from_str(&json).unwrap();
cv.load(snapshot);Shared API
ChartView implements six traits. Full documentation for each:
- Script Management — add, remove, query scripts
- Interaction — mouse, wheel, drag, cursor, selection
- Annotations — CRUD, properties, selection, clipboard
- Drawing Tools — tool state machine, sticky mode, magnet
- Viewport — pane ratios, scroll, zoom, highlighted bar
- Settings & Events — theme, locale, symbol/timeframe, strategy reports, poll_event
Weak References
Use ChartView::downgrade() to obtain a WeakChartView — a non-owning reference that does not prevent the chart from being dropped:
let weak: WeakChartView<_> = cv.downgrade();
let callback = move || {
if let Some(cv) = weak.upgrade() {
cv.on_mouse_up(pos);
}
};WeakChartView implements Clone, so the same handle can be shared across multiple closures.