Skip to content

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-CN");

// 准备好后再设置 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,可在多个闭包间共享。

基于 MIT 许可证发布。