ChartView
ChartView 通过消费来自 ChartProvider 的统一事件流(K 线数据 + 脚本执行事件)来渲染图表,负责处理布局、渲染和用户交互。
与数据提供者方式的对比请参见图表视图概述。
架构
客户端 ChartView 通过 ChartProvider trait 连接数据和执行源。Provider 流式传输单一统一的 ChartStreamEvent 序列 — K 线 bar 和各脚本的输出事件。
适用场景
使用远程 Provider 时:
- 脚本编译和执行在服务器上进行
- 需要仅负责渲染的瘦客户端,无本地 VM 依赖
- 构建具有服务端执行能力的基于 Web 的图表平台
使用 LocalChartProvider(需要 local feature)时:
- 希望在进程内运行 VM
- 构建桌面/CLI 应用或进行原型开发
创建 ChartView
rust
use navi_chart::{ChartView, ChartEvent, Size, Theme};
// 創建空 chart,不需要在構造時提供 symbol 和 timeframe。
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("zh-HK");
// 準備好後再設置 symbol 和 timeframe,此時才開始拉數據。
cv.set_symbol("NASDAQ:AAPL");
cv.set_timeframe(TimeFrame::days(1));泛型参数
ChartView<P, T> 的泛型参数:
| 参数 | 说明 |
|---|---|
P: ChartProvider | 数据和脚本事件来源 |
T: Clone + PartialEq + Display | 用户自定义的脚本"标签"类型。其 Display 输出用作编译错误槽的标题。 |
实现 ChartProvider
rust
use futures_util::Stream;
use navi_chart::ChartProvider;
use navi_chart_wire::{ChartStreamEvent, ChartStreamRequest};
use navi_types::TimeFrame;
pub trait ChartProvider {
type Script: Clone + 'static;
fn chart_stream(
&self,
symbol: String,
timeframe: TimeFrame,
request: ChartStreamRequest<Self::Script>,
) -> impl Stream<Item = ChartStreamEvent> + 'static;
}选择 Script 句柄类型
type Script = … | 适用场景 |
|---|---|
String | 句柄即脚本源码,后端每次编译(或按哈希缓存) |
u64 / 内容哈希 | 客户端一次性上传源码,后续只传哈希 |
Uuid | 后端维护会话级注册表 |
navi_chart::Program | 使用 LocalChartProvider,客户端编译后传输字节码 |
ChartStreamRequest
rust
pub struct ChartStreamRequest<S> {
/// 脚本列表(有序)。流中事件以 0-based 索引标记。
pub scripts: Vec<ScriptDescriptor<S>>,
/// 诊断消息语言(如 `"zh-CN"`)。
pub locale: String,
/// 接受哪些交易时段的 bar。
pub input_sessions: InputSessions,
}
pub struct ScriptDescriptor<S> {
pub script: S,
pub input_values: InputValues, // HashMap<usize, serde_json::Value>
}ChartStreamEvent
rust
pub enum ChartStreamEvent {
/// K 线 bar。HistoryEnd 前为历史 bar,之后为实时 bar。
Bar(Candlestick),
/// 历史与实时数据的边界,仅发送一次,全局适用。
HistoryEnd,
/// 某个脚本产生的事件。
Script(TaggedScriptEvent),
/// session 级不可恢复错误,流在此之后终止。
Error(Error),
}
pub struct TaggedScriptEvent {
/// 对应 ChartStreamRequest::scripts 的 0-based 索引。
pub script_id: u64,
pub event: ScriptEventPayload,
}
pub enum ScriptEventPayload {
/// 脚本元数据,每个脚本在第一个 bar 前发送一次。
SessionInfo { script_info: Arc<ScriptInfo>, symbol_info: Box<SymbolInfo> },
/// 可视化输出事件(绘图、标签、线条等)。
Draw(DrawEvent),
/// 策略订单/成交事件。
Strategy(StrategyEvent),
/// 单个脚本错误,不影响其他脚本。
Error(Error),
}流协议
SessionInfo{id=0} SessionInfo{id=1} ← 每个脚本,在第一个 bar 前
Bar(t0) Script{id=0, Draw} Script{id=1, Draw}
…
HistoryEnd ← 全局,仅一次
Bar(实时) Script{id=0, Draw} …LocalChartProvider
LocalChartProvider(需要 local feature)在进程内使用 Navi VM 运行脚本:
rust
use navi_chart::LocalChartProvider;
let provider = LocalChartProvider::new(data_provider);
let cv = ChartView::new(provider, platform, size, ctx);
cv.set_symbol(symbol);
cv.set_timeframe(tf);添加脚本
rust
// 使用字符串句柄的远程 Provider:
let id = cv.add_script(
"//@version=6\nindicator(\"RSI\")\nplot(ta.rsi(close, 14))".to_string(),
"rsi".to_string(),
);
// 使用 LocalChartProvider(Script = Program):
let program: Program = compile(source).await?;
let id = cv.add_script(program, "rsi".to_string());每次脚本的添加、移除或配置变更,ChartView 都会重启 session,重新调用 chart_stream() 并从第 0 根 bar 开始回放。
持久化
rust
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);共享 API
ChartView 实现了六个 trait,完整文档见:
弱引用
rust
let weak: WeakChartView<_> = cv.downgrade();
let callback = move || {
if let Some(cv) = weak.upgrade() {
cv.on_mouse_up(pos);
}
};WeakChartView 实现了 Clone,可在多个闭包间共享。