插件
插件从外部扩展图表:在子图中绘制(序列下方或上方)、读取 K 线,其余一切都通过公开 API 完成,比如注册画线类型、监听事件、添加指标。可选功能也可以这样做成独立的包。
第一个插件
这个插件标出可见 K 线中的最高价和最低价:
import type { ChartPlugin } from '@kumochart-sdk/core';
const visibleExtremes: ChartPlugin = {
name: 'visible-extremes',
paint: ({ ctx, layer, pane, bars, visible, theme }) => {
if (layer !== 'above' || pane.id !== 'main' || !visible) {
return;
}
let high = -Infinity;
let low = Infinity;
for (let index = visible.from; index <= visible.to; index += 1) {
high = Math.max(high, bars.high(index));
low = Math.min(low, bars.low(index));
}
ctx.setLineDash([4, 4]);
ctx.strokeStyle = theme.textMuted;
ctx.fillStyle = theme.textMuted;
for (const value of [high, low]) {
const y = Math.round(pane.valueToY(value)) + 0.5;
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(pane.width, y);
ctx.stroke();
ctx.fillText(pane.formatValue(value), 8, y - 4);
}
}
};
chart.plugins.add(visibleExtremes);
chart.plugins.remove('visible-extremes');每当图表重绘序列时都会调用 paint:数据变化、滚动、缩放、尺寸变化和修改配置之后。十字光标移动和拖动画线不会重绘序列,所以这些操作不会产生插件的开销。
绘制
paint(context) 会对每个子图、每一层各调用一次:
below:在网格之后、序列之前。适合绘制背景类内容,例如成交量分布或交易时段底色。above:在序列和指标之后,位于画线、委托单和十字光标之下。
上下文已经裁剪到子图的绘图区,并平移到它的左上角,单位是 CSS 像素;图表会在每个插件前后保存和恢复画布状态。
| 成员 | 说明 |
|---|---|
ctx、pixelRatio | 2D 上下文和设备像素比,用来画出清晰的线 |
layer | below 或 above |
pane | { id, width, height, valueToY(value), yToValue(y), formatValue(value) } |
bars、source | 显示的 K 线和源数据(见下文) |
visible | 与绘图区相交的显示 K 线的序号范围 { from, to },没有时为 null |
barSpacing | 相邻 K 线中心的间距(像素) |
indexToX(index)、xToIndex(x) | 连续的 K 线序号与 x 坐标互相换算 |
timeToX(time) | 任意时间的 x 坐标,K 线之间插值,数据范围之外外推 |
theme、font | 主题和图表字体 { family, size } |
withAlpha(color, alpha) 可以把主题色变成半透明的颜色。
读取数据
bars 是显示的 K 线:按周期聚合、经过 K 线变换,并被正在进行的回放截断。source 是应用推送的源数据,按源周期组织,止于回放游标;它的粒度更细,比如日 K 里的每一根 1 分钟 K 线。没有设置源周期时,两者是同一组 K 线。
两者都是 BarsView:列式、只读。应用也可以通过 chart.data.bars 和 chart.data.source 拿到同样的视图:
| 成员 | 说明 |
|---|---|
length | K 线数量 |
revision | K 线每次变化都会改变 |
time(i)、open(i)、high(i)、low(i)、close(i)、volume(i) | 第 i 根 K 线的字段,不分配对象 |
bar(i) | 以对象形式返回这根 K 线,超出范围时为 null |
lowerBound(time) | 第一个时间不早于 time 的序号;全部更早时为 length |
用 revision 和读取的序号范围作为缓存键,插件就只在数据变化时计算一次,而不是每帧都算:
let key = '';
let cached: Summary | undefined;
function summaryOf(source: BarsView, from: number, to: number): Summary {
const next = `${source.revision}:${from}:${to}`;
if (next !== key || !cached) {
key = next;
cached = summarize(source, from, to);
}
return cached;
}显示 K 线 [from, to] 对应的源数据从 source.lowerBound(bars.time(from)) 开始,到 source.lowerBound(bars.time(to + 1)) 之前结束;最后一根显示 K 线则到 source.length 为止。
宿主
attach(host) 在第一次绘制之前调用,传入图表和同样的数据视图:
| 成员 | 说明 |
|---|---|
chart | 公开的 API:注册画线类型、订阅事件等 |
bars、source | 数据视图 |
repaint() | 在下一帧重绘,例如插件自身的设置改变之后 |
有设置项的插件可以保存宿主,在设置改变时请求重绘。下面这个水印插件在 K 线后面显示一段文字:
import { withAlpha, type ChartPlugin, type PluginHost } from '@kumochart-sdk/core';
function watermark(initial: string): ChartPlugin & { setLabel(label: string): void } {
let label = initial;
let host: PluginHost | null = null;
return {
name: 'watermark',
attach: (given) => {
host = given;
},
detach: () => {
host = null;
},
paint: ({ ctx, layer, pane, theme, font }) => {
if (layer !== 'below' || pane.id !== 'main') {
return;
}
ctx.font = `600 ${font.size * 4}px ${font.family}`;
ctx.fillStyle = withAlpha(theme.textMuted, 0.15);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(label, pane.width / 2, pane.height / 2);
},
setLabel: (next) => {
label = next;
host?.repaint();
}
};
}
const mark = watermark('BTC/USDT · 15m');
chart.plugins.add(mark);
mark.setLabel('ETH/USDT · 1h');attach() 中建立的东西要在 detach() 中释放;chart.plugins.remove() 和 chart.destroy() 都会调用它,此时图表仍然可用:
attach: (host) => {
off = host.chart.on('replay', ({ state }) => updateLabel(state.time));
},
detach: () => {
off?.();
}读取数据的画线
自定义画线类型的几何信息里也有同样的视图(geometry.bars、geometry.source),所以固定范围成交量分布、仓位这类测量行情的工具不需要插件也能实现。当然也可以由插件在 attach() 中注册它们。
发布插件
- 导出一个接收选项、返回插件的工厂函数,这样用户可以用不同的名称添加多个实例。
- 把
@kumochart-sdk/core声明为 peer dependency,版本与你开发时使用的一致。 - 在创建插件时校验选项,并像图表一样抛出指明错误值的
ChartError。 - 不要把状态放在插件对象之外:一个插件实例只属于一个图表。