API 参考
所有内容都从 @kumochart-sdk/core 导出。类型按其描述的对象命名:XxxInput 是你传入的数据,Xxx 是图表返回的数据。
- createChart
- Chart
- data · replay · timeScale · priceScale · panes · series · indicators · drawings · orders · markers · alerts · crosshair · plugins
- 事件
- 配置项
- 周期与交易日历
- 错误
约定
- 时间(
TimeInput)可以是毫秒时间戳、ISO-8601 字符串或Date。图表返回的始终是毫秒时间戳。 - 坐标是相对于图表左上角的 CSS 像素。
- 逻辑索引以 K 线计数:第
i根 K 线的中心位于索引i。索引可以是小数,也可以超出数据范围。 - 输入会被复制。 图表从不保留或修改你传入的对象。
- API 调用不会触发事件。 只有用户操作(以及预警)才会。
createChart
function createChart(container: HTMLElement, options?: ChartOptionsInput): Chart;创建一个填满 container 的图表。容器无效、缺少必需的浏览器 API 或配置项非法时,抛出 ChartError。
扩展功能
较专门的功能属于扩展功能,注册后图表才会拥有它们;不用的应用不会把它们打包进去:
| 扩展 | 注册名称 |
|---|---|
heikinAshiTransform、renkoTransform、rangeTransform | K 线变换 heikin-ashi、renko、range(data.registerTransform) |
longPosition、shortPosition、pitchfork、fibExtension | 画线类型 long-position、short-position、pitchfork、fib-extension |
dateRange、datePriceRange、callout | 画线类型 date-range、date-price-range、callout |
import { createChart, registerExtras, renkoTransform } from '@kumochart-sdk/core';
registerExtras(chart); // 按上面的名称全部注册
chart.data.registerTransform('renko', renkoTransform); // 或者只注册用到的使用未注册的扩展功能会抛出 ChartError 并说明原因。Vue 和 React 组件通过 configure 属性注册,它会在其他属性生效之前执行。
Chart
| 成员 | 说明 |
|---|---|
element: HTMLElement | 图表在容器内创建的元素 |
options(): ChartOptions | 解析后的配置项(已冻结,请用 applyOptions 修改) |
applyOptions(options: ChartOptionsInput): void | 深度合并配置项的修改;数组和函数会被整体替换 |
layout(): Layout | 子图、坐标轴和分隔线的矩形区域,用于定位 HTML 浮层 |
resize(width, height): void | 设置尺寸;开启 autoSize 时,容器下一次尺寸变化会覆盖该尺寸 |
batch(operation: () => void): void | 执行多个调用,并在之后统一派发一次事件 |
on(type, listener): Unsubscribe | 订阅事件 |
select(selection: Selection | null): void | 选中画线、委托单、预警或标记 |
selection(): Selection | null | 当前选中的对象 |
saveState(): ChartState | 用户布局:主序列类型、视图、子图、指标、画线 |
loadState(state: ChartState): void | 以原子方式恢复布局;状态不合法时抛出错误,不做任何改变 |
screenshot(): HTMLCanvasElement | 渲染尚未绘制的变更,并返回包含整个图表的画布 |
destroy(): void | 移除元素并释放所有资源 |
interface Selection {
kind: 'drawing' | 'order' | 'alert' | 'marker';
id: string;
}data
主 OHLCV 序列。写入的是源数据;读取返回的是显示的 K 线,即源数据按当前周期聚合、经过 K 线变换(如果设置了)、并被正在进行的回放截断之后的结果。
| 成员 | 说明 |
|---|---|
set(bars: readonly BarInput[], options?: { interval? }): void | 替换源数据,时间必须严格递增。interval 声明这些 K 线的周期(如 '1m');不声明时 K 线按原样显示 |
update(bar: BarInput | readonly BarInput[]): void | 替换最后一根源 K 线(时间相同)或追加更新的 K 线。拒绝更早的 K 线 |
addTrades(trade: TradeInput | readonly TradeInput[]): number | 把逐笔成交合进源 K 线,需要已声明源周期。跳过被交易日历丢弃的成交,返回实际合入的笔数 |
prepend(bars: readonly BarInput[]): number | 插入更早的源 K 线;忽略时间等于或晚于第一根 K 线的数据;返回实际插入的数量 |
clear(): void | 移除全部 K 线并结束正在进行的回放;源周期和显示周期保留 |
sourceInterval(): Interval | null | 源 K 线的周期 |
timeframe(): Interval | null | 当前显示的周期;null 表示与源周期相同 |
setTimeframe(timeframe: IntervalInput | null): void | 把源数据聚合成更大的周期显示,无需重新请求数据;null 恢复为源周期 |
transform(): BarTransformInput | null | 当前用来生成显示 K 线的变换 |
setTransform(transform: BarTransformInput | null): void | 用周期本身的 K 线生成显示的 K 线,例如砖形图;null 恢复为周期本身的 K 线 |
registerTransform(type, definition: BarTransformDefinition) | 添加自定义变换 |
length: number | 显示的 K 线数量 |
at(index: number): Bar | null | 指定索引处显示的 K 线 |
last(): Bar | null | 最新一根显示的 K 线 |
indexOf(time: TimeInput): number | 时间完全一致的显示 K 线的索引,没有则返回 -1 |
toArray(): Bar[] | 全部显示 K 线的副本 |
bars: BarsView | 显示 K 线的只读列式视图,不复制数据(BarsView) |
source: BarsView | 源数据的视图,回放时止于回放游标 |
interface BarInput {
time: TimeInput;
open: number;
high: number;
low: number;
close: number;
volume?: number; // 默认 0
}
interface TradeInput {
time: TimeInput;
price: number;
size?: number; // 计入 K 线成交量,默认 0
}显示周期必须由整数根源 K 线组成:1m 可以合成 5m、15m、1h、4h、1D,但不能合成 90s;1W 和 1M 由 1D 合成。合成后的 K 线取第一根的开盘价、最高的最高价、最低的最低价、最后一根的收盘价和成交量之和,时间为该周期的起点(见交易日历)。落在最后一根源 K 线周期内的成交会更新它,更晚的成交会开启新 K 线,属于更早周期的成交会抛出错误。set() 时如果新的源周期仍能合成当前显示周期则保留,否则重置为 null。loadMore.before 是第一根源 K 线的时间。
K 线变换
interface BarTransformInput {
type: string; // 'heikin-ashi'、'renko'、'range'(扩展功能)或已注册的类型
params?: Record<string, unknown>;
}| 类型 | 参数 | 显示的 K 线 |
|---|---|---|
heikin-ashi | 无 | 平均 K 线,周期中的每根 K 线对应一根 |
renko | box:砖块大小 | 基于收盘价的砖块;超出上一块一个砖块大小才生成新砖,反转需要两个 |
range | range:每根的高低差 | 高低差恰好为 range 的 K 线;最后一根仍在形成中 |
这三种变换都是扩展功能,需要先注册。box 和 range 默认取最近 14 根 K 线的平均真实波幅,保留两位有效数字;推送新数据时保持不变,K 线重建时(新数据、切换周期或开始回放)重新计算。切换数据或周期时变换保持不变。指标、预警和标记都基于变换后的 K 线计算。砖形图和等幅 K 线不代表时间段:同一根周期 K 线中完成的砖块依次相隔 1 毫秒,K 线倒计时也会隐藏。参见周期与回放。
replay
行情回放:一个时间游标把它之后的源数据隐藏起来。周期合成、指标、预警、对比序列和标记都只能看到已回放的部分;更大的周期会由已回放的源 K 线实时拼出正在形成的那根 K 线。回放期间数据照常推送,直到游标走到才显示。这些调用不会触发事件;播放过程会触发 replay 事件。
| 成员 | 说明 |
|---|---|
start(time: TimeInput, settings?: Partial<ReplaySettings>): void | 开始(或重新开始)回放,显示截至 time(含)的源数据 |
stop(): void | 结束回放并显示全部数据 |
play(): void / pause(): void | 以每秒 speed 步播放,直到最后 |
step(count = 1): number | 前进 count 步;返回实际前进的步数 |
seek(time: TimeInput): void | 把游标移到第一根 K 线之后的任意时间,可前进也可后退 |
configure(settings: Partial<ReplaySettings>): void | 修改速度或步进方式,播放中也可以修改 |
state(): ReplayState | null | 回放状态;显示全部数据时为 null |
interface ReplaySettings {
speed: number; // 播放时每秒的步数,默认 1
step: 'bar' | 'source'; // 默认 'bar'
}
interface ReplayState extends ReplaySettings {
time: number; // 最后一根已回放的源数据时间
playing: boolean;
ended: boolean; // 所有源 K 线都已回放
}bar 步进会补完正在形成的显示 K 线,或显示下一根;source 步进每次只放出一根源 K 线。回放中预警照常在新放出的 K 线上触发,事件带有 replay: true。回放时最新 K 线的倒计时会隐藏。
timeScale
| 成员 | 说明 |
|---|---|
getVisibleRange(): TimeRange | null | 第一根和最后一根可见 K 线的时间 |
setVisibleRange({ from, to }): void | 显示两个时间之间的 K 线 |
getVisibleLogicalRange(): LogicalRange | null | 绘图区左右边缘处的逻辑索引 |
setVisibleLogicalRange(range: LogicalRange): void | 显示一段逻辑索引范围 |
scrollToTime(time, align?: 'left' | 'center' | 'right'): void | 默认 center |
scrollToIndex(index, align?): void | |
scrollToRealTime(): void | 回到最新 K 线,右侧保留配置的留白 |
isFollowing(): boolean | 最新 K 线是否在视图内,即新 K 线是否会自动滚入视图 |
fitContent(): void | 显示全部 K 线 |
zoomIn(): void / zoomOut(): void | 缩放一级;最新 K 线可见时保持其位置不变 |
getBarSpacing(): number / setBarSpacing(px): void | K 线间距 |
reset(): void | 恢复配置的 K 线间距和实时位置 |
timeToX(time): number | null / xToTime(x): number | null | 在 K 线之间插值,超出 K 线范围时外推 |
indexToX(index): number | null / xToIndex(x): number | null |
priceScale
chart.priceScale(paneId = 'main', scaleId = 'right'): PriceScaleApi每个子图都有一个 right(右侧)坐标,画线、委托单和预警使用它;还有一个 left(左侧)坐标,坐标轴在绘图区左边,有内容使用它时才显示。其他任意 id 表示没有坐标轴的叠加坐标。序列和指标通过 scale 选择所用的坐标,每个坐标只适配放在它上面的内容。
// 成交量叠加在主图底部的五分之一。
chart.indicators.add({ type: 'VOL', pane: 'main', scale: 'volume' });
chart.priceScale('main', 'volume').setMargins({ top: 0.8, bottom: 0 });
// 第二个品种显示在左侧坐标轴上。
chart.series.add({ type: 'line', scale: 'left', title: 'ETH', data: ethPoints });| 成员 | 说明 |
|---|---|
getRange(): PriceRange | null | 可见的数值范围 |
setRange({ min, max }): void | 固定范围并关闭自动缩放 |
autoScale(): void / isAutoScale(): boolean | 自动缩放让坐标适配可见数据。拖动价格坐标或在其上滚动滚轮缩放会关闭自动缩放(此时绘图区也可以上下拖动);双击坐标轴可重新开启 |
getMode(): PriceScaleMode / setMode(mode): void | 'normal' | 'log' | 'percent' | 'indexed' |
isInverted(): boolean / setInverted(inverted): void | 较大的值显示在下方 |
getMargins() / setMargins({ top, bottom } | null): void | 自动缩放时数据上下留出的空白,按子图高度的比例;null 跟随 priceScale.margins |
priceToY(price): number | null / yToPrice(y): number | null | 图表坐标 |
右侧坐标上没有内容时,它会跟随左侧坐标,画线因此保持对齐。左侧坐标轴同样可以拖动缩放、双击复位。所有坐标(包括 layout() 和指针事件)都从图表的左边缘算起,包含左侧坐标轴的宽度。
百分比和指数化模式以该子图第一个可见值为基准。当基准不可用(为零,或在指数化模式下为负数)时,以及对数坐标的子图中没有正值时,该子图按普通模式绘制。
panes
主图('main')始终位于最上方,占据其他子图剩下的空间。放到 'new' 中的指标和序列会获得独立的子图,最后一个占用者移除时,该子图也随之移除。
| 成员 | 说明 |
|---|---|
list(): Pane[] | 从上到下排列的子图 |
get(id): Pane | null | |
add({ id, title?, height? }): void | 在底部添加一个空子图 |
update(id, { title?, height? }): void | |
remove(id): void | 移除子图及其中的画线;其中的指标和序列必须先行移除 |
move(id, index): void | 调整子图顺序;主图始终位于索引 0 |
series
按时间与主 K 线对齐的附加序列,例如对比品种。
| 成员 | 说明 |
|---|---|
add(series: SeriesInput): string | 返回 id |
update(id, { title?, visible?, style?, scale? }): void | |
remove(id): void | 有预警读取该序列时会失败 |
get(id) / list() | |
setData(id, points) / updateData(id, point | points) / prependData(id, points) | 规则与 data 相同;按当前周期聚合(数值序列取每个周期的最后一个值),并受回放截断 |
registerType(type, definition: SeriesTypeDefinition): void | 为此图表添加自定义序列类型 |
types(): string[] | 可用的类型 |
interface SeriesInput {
id?: string;
type: string; // 'line', 'area', 'columns', 'candles', …
pane?: string; // 'main'(默认)、'new' 或某个子图的 id
scale?: string; // 'right'(默认)、'left' 或叠加坐标的 id
title?: string;
visible?: boolean;
style?: Record<string, unknown>; // 覆盖该类型的样式
data?: Array<{ time: TimeInput; value: number | null } | BarInput>;
}内置类型:candles、hollow-candles、ohlc、high-low、hlc-area(OHLC 数据点),以及 line、step-line、line-markers、area、baseline、columns(数值数据点)。它们的样式属性见 SeriesStyleMap;主序列通过 options.mainSeries 使用同一套类型。
indicators
| 成员 | 说明 |
|---|---|
add(indicator: IndicatorInput): string | 返回 id |
update(id, { params?, source?, visible?, colors?, scale? }) | |
remove(id): void | 有预警或其他指标读取该指标时会失败 |
get(id) / list() | |
valuesAt(id, index?): IndicatorValue[] | 某根 K 线上格式化后的值(默认为最新一根) |
register(type, definition: IndicatorDefinition): void | 为此图表添加自定义指标类型 |
types(): string[] | 可用的类型 |
scale 把指标放在所在子图的 right(默认)、left 或叠加坐标上,例如把成交量放在主图底部。source 可以是价格字段(默认 close),也可以是另一个指标的输出 { kind: 'indicator', id, key? },例如 RSI 的均线(见指标叠加指标)。参见技术指标。
drawings
| 成员 | 说明 |
|---|---|
set(drawings: readonly DrawingInput[]): void | 替换全部画线并清空撤销历史 |
add(drawing): string / update(id, patch) / remove(id) / clear() | 可撤销的操作 |
get(id) / list() | |
startTool(type, { style?, text?, continuous? }?): void | 让用户放置一条画线;continuous 让工具保持激活 |
duplicate(id): string | 复制一份,作为可撤销的步骤 |
bounds(id): DrawingBounds | null | 画线在图表坐标中覆盖的区域,可用于定位浮动工具栏 |
levels(id): string[] | 画线上可以设置预警的线,例如 ['top', 'bottom'] |
editText(id): boolean | 在原位编辑文字画线 |
cancelTool(): void / activeTool(): string | null | |
undo() / redo(): boolean / canUndo() / canRedo(): boolean | |
registerType(type, definition: DrawingTypeDefinition): void | 添加自定义画线类型 |
types(): string[] | 可用的类型 |
内置类型:trend-line、ray、extended-line、arrow、horizontal-line、horizontal-ray、vertical-line、rectangle、parallel-channel、path、fib-retracement、price-range 和 text。扩展功能:fib-extension、date-range、date-price-range、long-position、short-position、pitchfork 和 callout。参见画线工具。
orders
| 成员 | 说明 |
|---|---|
set(orders) / add(order) / update(id, patch) / remove(id) | |
get(id) / list() |
markers
| 成员 | 说明 |
|---|---|
set(markers) / add(marker) / update(id, patch) / remove(id) | |
get(id) / list() |
alerts
| 成员 | 说明 |
|---|---|
set(alerts) / add(alert) / update(id, patch) / remove(id) | |
get(id) / list() | 包含 status 和 triggeredAt |
rearm(id): void | 重新启用以 once 频率触发过的预警 |
委托单、标记和预警的说明见交易。
crosshair
| 成员 | 说明 |
|---|---|
get(): CrosshairState | null | 十字光标所在的位置 |
set({ time, price?, pane? }): void | 在没有指针的情况下显示十字光标,例如与另一个图表联动 |
clear(): void |
interface CrosshairState {
x: number;
y: number;
paneId: string | null; // 位于时间轴上时为 null
index: number; // 逻辑索引,可能超出数据范围
time: number | null;
price: number | null; // y 处在该子图坐标中对应的数值
bar: Bar | null; // 悬停处的 K 线
}plugins
| 成员 | 说明 |
|---|---|
add(plugin: ChartPlugin): void | 添加插件;名称不能重复 |
remove(name: string): void | 移除插件 |
get(name): ChartPlugin | null | |
list(): string[] | 插件名称,按绘制顺序排列 |
interface ChartPlugin {
name: string;
attach?(host: PluginHost): void; // 添加时调用
detach?(): void; // 移除插件或销毁图表时调用
paint?(context: PluginPaintContext): void; // 每个子图、每一层(序列下方 'below' 或上方 'above')各调用一次
}另外导出了 withAlpha(color, alpha),供插件和自定义画线生成半透明的主题色。参见插件。
事件
chart.on(type, listener) 返回一个用于取消订阅的函数。监听器在引发事件的操作完成之后运行;监听器抛出的错误会在所有监听器都运行完毕后重新抛出。
| 事件 | 载荷 | 触发时机 |
|---|---|---|
crosshairMove | { visible: true, ...CrosshairState } | { visible: false } | 指针在图表上移动或离开图表 |
click / doubleClick | ChartPointerEvent | 包含被点击的 target 对象(如果有) |
contextMenu | ChartPointerEvent | 注册监听后会屏蔽浏览器的右键菜单 |
visibleRangeChange | { range, logicalRange } | 可见的 K 线发生变化,无论原因 |
loadMore | { before } | 左侧隐藏的 K 线少于 loadMoreThreshold 根 |
selectionChange | { selection } | 用户选中或取消选中对象 |
paneResize | { paneId, height } | 用户拖动子图分隔线结束 |
drawingsChange | { reason, ids } | 用户创建、编辑、删除、撤销或重做了画线 |
toolChange | { tool: null } | 画线工具结束(画完或被用户取消) |
orderDrag / alertDrag | { id, phase, price, initialPrice } | phase 为 start、move、end 或 cancel |
buttonClick | { kind, id, buttonId } | 委托单或预警的标签按钮被点击 |
alertTrigger | { id, time, value, threshold, trigger, replay } | 满足预警条件;回放历史时 replay 为 true |
replay | { state: ReplayState, reason: 'step' | 'end' } | 回放播放时放出了新数据,或已到最后并暂停 |
interface ChartPointerEvent {
x: number;
y: number;
paneId: string | null;
index: number | null;
time: number | null;
price: number | null;
target: { kind: 'drawing' | 'order' | 'alert' | 'marker'; id: string; part: string } | null;
}配置项
括号内为默认值。
| 配置项 | 说明 |
|---|---|
autoSize(true) | 跟随容器尺寸 |
locale(运行环境的默认值) | 用于数字和日期的 BCP 47 语言标签 |
timeZone(本地时区) | IANA 时区,例如 Asia/Shanghai |
theme(darkTheme) | 颜色;同时也导出了 lightTheme |
font.family、font.size(12) | 坐标轴、图例和标签的文字 |
grid.vertical、grid.horizontal(true),grid.style(solid) | |
crosshair.mode(free) | free 跟随指针,magnet 吸附到悬停 K 线的收盘价 |
crosshair.vertical、crosshair.horizontal、crosshair.labels(true),crosshair.style(dashed) | |
mainSeries.type(candles) | 任意序列类型 |
mainSeries.source(close) | 线类图形所绘制的价格:open、high、low、close、hl2、hlc3、ohlc4 |
mainSeries.styles({}) | 按类型覆盖样式,例如 { candles: { upColor: '#0f0' } } |
mainSeries.barColor(null) | ({ index, bar, indicator }) => 颜色 | null,给单根 K 线着色(见给 K 线着色) |
timeScale.visible(true),timeScale.height(28) | |
timeScale.barSpacing(8),minBarSpacing(0.5),maxBarSpacing(60) | 每根 K 线占用的像素 |
timeScale.rightOffset(60) | 位于实时位置时,最新 K 线右侧的留白 |
timeScale.loadMoreThreshold(20) | 左侧隐藏的 K 线少于该数量时触发 loadMore |
timeScale.formatter(null) | (time, { kind, unit, locale, timeZone }) => string |
priceScale.visible(true),priceScale.minWidth(48) | |
priceScale.margins({ top: 0.1, bottom: 0.08 }) | 自动缩放时数据上下的留白,以子图高度的比例表示 |
priceScale.precision('auto') | 价格的小数位数;auto 表示根据数据推断 |
priceScale.formatter(null) | (value, { paneId, mode, precision, locale }) => string |
latestPrice.line、latestPrice.label(true),latestPrice.countdown(false),latestPrice.style(dotted) | |
legend.visible(true) | 在每个子图顶部显示 OHLC、指标和序列的数值 |
interaction.pan、wheelZoom、pinchZoom、axisScale、kineticScroll、keyboard(true) | |
interaction.longPressMs(300) | 触屏长按多久后显示十字光标 |
interaction.touchMagnifier(true) | 触屏放置锚点或拖动对象时,在手指上方显示放大镜 |
panes.defaultHeight(120),panes.minHeight(40),panes.separatorHeight(5) | |
drawings.magnet(off) | weak 在靠近 K 线开高低收时吸附,strong 总是吸附;按住 Ctrl/⌘ 临时反转 |
drawings.hitTolerance(6),drawings.handleRadius(5) | |
calendar.timeZone(null) | 交易日历所在时区;null 时使用 timeZone |
calendar.sessions([]) | 交易时段,如 ['09:30-11:30', '13:00-15:00'],时段外的数据会被丢弃;为空表示全天交易 |
calendar.dayStart('00:00') | 交易日的开始时间,如夜盘用 '21:00' |
calendar.weekdays([0, …, 6]) | 交易日的星期(0 = 周日);其他日期顺延到下一个交易日 |
calendar.weekStart(1) | 周线从星期几开始(0 = 周日) |
calendar.bucket(null) | (time, interval, fallback) => number | null:自定义某个时间所属的周期,返回 null 则丢弃 |
performance.maxPixelRatio(3) | 限制画布分辨率的上限 |
texts({}) | 画在画布上的文字,例如 { target: 'TP' };其余跟随 locale(zh 时为中文) |
键盘快捷键
启用 interaction.keyboard 后,在图表获得焦点时:方向键用于滚动和缩放,End 回到实时位置,Escape 取消拖动或画线工具并清除选中,Delete 删除选中的画线,Enter 结束折线,Ctrl/⌘ + C 和 Ctrl/⌘ + V 复制、粘贴选中的画线,Ctrl/⌘ + Z 撤销,Ctrl/⌘ + Shift + Z 或 Ctrl + Y 重做。绘制或拖动时,Shift 约束方向,Ctrl/⌘ 反转磁吸,Alt 拖出一份副本。
周期与交易日历
type IntervalInput = Interval | string; // '15s'、'1m'、'5m'、'1h'、'4h'、'1D'、'2D'、'1W'、'1M'、'3M'
interface Interval {
unit: 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month';
count: number;
}
function parseInterval(input: IntervalInput): Interval;
function formatInterval(interval: IntervalInput): string; // 标准写法,如 '1D'
function createCalendar(options?: CalendarInput): Calendar;
interface Calendar {
/** `time` 所属的 `interval` 周期 K 线的起点;被日历丢弃时返回 `null`。 */
bucket(time: TimeInput, interval: IntervalInput): number | null;
}简写中 m 表示分钟,M 表示月;h、D、W 大小写均可。createCalendar() 返回的就是图表内部使用的日历,选项与 calendar 配置项相同(timeZone 默认为本地时区),可以用它按同样的规则对齐你自己的数据。日内 K 线从所在交易时段的起点开始计算,日线、周线、月线以交易日当地零点为时间。详见周期与回放。
错误
所有非法参数、未知 id 或不合法的调用都会抛出 ChartError(已导出),错误消息会指出出错的值,例如:
[kumochart] bars[3] (time 1714642260000) is older than the last bar (time 1714642320000). update() only replaces the last bar or appends newer ones; use set() or prepend() to change history.所有操作都会先校验再修改,因此抛出错误时图表保持原样。