11 KiB
11 KiB
KLineChart CSV 可视化 Demo — 项目说明书
1. 项目概览
基于 KLineChart v10 的独立 K 线图表可视化 Demo,支持从本地 CSV 文件加载 OHLCV 历史数据,提供周期切换、指标叠加、深浅主题、缩放拖拽、截图导出等完整交互能力。
技术栈:
| 技术 | 版本 | 用途 |
|---|---|---|
| KLineChart | 10.0.2 | 核心 K 线渲染引擎 |
| Vite | 5.x | 本地开发服务器 |
| Canvas 2D | 原生 | 图表绘制 |
2. 文件结构
debug/
├── index.html # 页面结构:工具栏 + 图表容器
├── main.js # 全部业务逻辑(约 600 行)
├── style.css # 界面样式,含深色主题
├── package.json # 依赖声明(klinecharts + vite)
├── package-lock.json # 版本锁定
└── data/
└── eurgbp_m1.csv # 示例数据(EUR/GBP 1分钟,2021-2026,约 186 万行)
3. 功能清单
| 功能 | 操作方式 | 说明 |
|---|---|---|
| CSV 加载 | 📂 按钮选择 / 拖拽 | 自动识别 5 种 CSV 格式 |
| 周期切换 | 1m / 5m / 15m / 1h / 1d | 实时聚合原始 1m 数据 |
| 缩放 | +/−/⟲ 按钮、鼠标滚轮、键盘 +/− | barSpace 范围 2–50 |
| 翻页 | ←/→ 键盘、拖拽、滚动条 | 分页加载(每次 500 根) |
| 指标 | 工具栏按钮 或 右键菜单 | MA / BOLL / MACD / RSI / VOL |
| 深色主题 | ☾/☀ 按钮 或 D 键 | chart.setStyles('dark') |
| 截图 | ▣ 按钮 或 S 键 | getConvertPictureUrl() 下载 PNG |
| 十字光标面板 | 鼠标移动 | 顶部实时显示 OHLCV + 涨跌幅 |
| 柱体详情 | 左键点击柱体 | 弹出详细 OHLCV 浮窗 |
| 右键菜单 | 右键图表 | 切换指标 / 主题 / 截图 |
| 自定义指标 | registerIndicator() |
扩展任意指标 |
4. CSV 数据格式
自动识别以下格式(列顺序可自定义,支持带表头/无表头):
格式 A:Binance 格式(时间戳 + OHLCV)
1630000000000,0.00145000,0.00145000,0.00144000,0.00144000,5259702
格式 B:带表头格式
timestamp,open,high,low,close,volume
1630000000000,0.00145000,0.00145000,0.00144000,0.00144000,5259702
格式 C:点号日期格式(当前示例)
2021.07.06,00:10,0.85627,0.85629,0.85625,0.85628,7000000.0
列顺序:日期, 时间, O, H, L, C, V
格式 D:横杠日期格式
2021-07-06,00:10,0.85627,0.85629,0.85625,0.85628,7000000.0
格式 E:YYYYMMDD 格式
20210706,0.85627,0.85629,0.85625,0.85628,7000000.0
时间戳自动处理: 13 位数字 → 毫秒;10 位数字 → 秒(自动 ×1000)
5. 快速开始
cd debug
npm install
npm run dev
浏览器打开 http://localhost:1789,拖入或点击 📂 选择 CSV 文件即可。
6. 核心代码结构(main.js)
┌─────────────────────────────────────┐
│ loadRawCSV(source) │ 1. 读取文件(File 或 URL)
│ └─ parseCSV(text) │ 2. 自动识别格式,解析到 TypedArray
├─────────────────────────────────────┤
│ aggregateBars(data, range, span) │ 3. 1m → 5m/15m/1h/1d 聚合
├─────────────────────────────────────┤
│ loadDataLoader() │ 4. 返回 setDataLoader 参数
│ └─ getBars({type, timestamp…}) │ 5. init/forward/backward 分页
├─────────────────────────────────────┤
│ mountChart(source) │ 6. init → setSymbol → setPeriod
│ ├─ chart.setDataLoader(...) │ 7. setDataLoader → 图表加载数据
│ ├─ chart.createIndicator(...) │ 8. 创建指标
│ └─ subscribeAction(...) │ 9. 绑定 crosshair/click 事件
└─────────────────────────────────────┘
7. 自定义指标开发
7.1 注册指标
在 init() 调用之前注册:
import { registerIndicator, init } from 'klinecharts'
registerIndicator({
name: 'MYAVG',
shortName: 'MYAVG',
series: 'price',
calcParams: [5, 10, 20],
precision: 5,
shouldOhlc: true,
figures: [
{ key: 'a1', title: 'A5: ', type: 'line' },
{ key: 'a2', title: 'A10: ', type: 'line' },
{ key: 'a3', title: 'A20: ', type: 'line' }
],
regenerateFigures: (params) =>
params.map((p, i) => ({ key: `a${i+1}`, title: `A${p}: `, type: 'line' })),
calc: (dataList, indicator) => {
const sums = indicator.calcParams.map(() => 0)
return dataList.map((bar, i) => {
const out = {}
indicator.calcParams.forEach((p, idx) => {
sums[idx] += bar.close
if (i >= p - 1) out[`a${idx+1}`] = sums[idx] / (i + 1)
})
return out
})
}
})
// 注册后再 init
const chart = init('chart', { /* options */ })
chart.createIndicator({ name: 'MYAVG', paneId: 'new' })
7.2 字段说明
| 字段 | 必需 | 类型 | 说明 |
|---|---|---|---|
name |
是 | string | 唯一标识,用于 createIndicator({ name }) 匹配 |
shortName |
是 | string | 工具提示中显示的简称 |
series |
是 | 'price' | 'volume' | 'normal' |
系列类型,决定 Y 轴行为 |
calcParams |
否 | number[] |
计算参数,可被 overrideIndicator 修改 |
precision |
否 | number | 结果数值的小数位数 |
shouldOhlc |
否 | boolean | 计算是否需要 OHLC 字段 |
figures |
是 | Figure[] |
图形配置,定义画几条线/柱 |
calc |
是 | () => D[] |
核心计算函数 |
minValue / maxValue |
否 | number | 固定 Y 轴最小/最大值 |
styles |
否 | Partial<IndicatorStyle> |
覆盖默认配色 |
7.3 Figure 类型
type |
说明 |
|---|---|
line |
折线 |
bar |
柱状图(支持 baseValue: 0 画零轴柱) |
circle |
圆点 |
text |
文字标注 |
每个 figure 至少需要 key 和 type,key 对应 calc 返回数据中的字段名。
7.4 计算函数约定
calc: (dataList: KLineData[], indicator) => D[]
dataList是当前可见窗口内的 K 线数据(每根含timestamp/open/high/low/close/volume)- 返回数组长度等于
dataList长度 - 每项为对象,键名与
figures[].key对应 - 数据不足时某键可省略(不会画该点),例如 EMA 前 N 个点
8. KLineChart v10 API 速查
8.1 图表生命周期
| 方法 | 说明 |
|---|---|
init(container, options?) |
创建图表实例 |
dispose(container) |
销毁,释放资源 |
setSymbol({ ticker, pricePrecision, volumePrecision }) |
设置交易品种信息 |
setPeriod({ span, type }) |
设置周期,会触发数据重载 |
setDataLoader(loader) |
设置数据加载器 |
resetData() |
强制重新加载数据 |
调用顺序: init → setSymbol → setPeriod → setDataLoader
8.2 指标
| 方法 | 说明 |
|---|---|
createIndicator(name, isStack?) |
添加指标,isStack=true 叠加主图 |
createIndicator({ name, paneId: 'new' }) |
新建独立面板 |
createIndicator({ name, paneId: 'candle_pane' }, true) |
叠加主图 |
removeIndicator({ name }) |
移除指标 |
overrideIndicator({ id, calcParams: [...] }) |
修改指标参数 |
registerIndicator(template) |
注册自定义指标(需 init 前调用) |
getSupportedIndicators() |
获取内置指标名列表 |
8.3 样式与布局
| 方法 | 说明 |
|---|---|
setStyles('dark' | 'light' | partial) |
切换主题或局部修改样式 |
setBarSpace(value) |
设置柱间距(等同于缩放) |
getBarSpace() |
获取当前 barSpace {bar, gapBar} |
setPaneOptions({ paneId, height }) |
设置面板高度 |
scrollToRealTime() |
滚动到最新数据 |
scrollByDistance(dist) |
按像素距离滚动 |
8.4 事件订阅
| Action 类型 | 触发时机 | 回调参数 |
|---|---|---|
onCrosshairChange |
鼠标移动 | { x, y, paneId, kLineData, timestamp, dataIndex } |
onCandleBarClick |
点击柱体 | 同上 |
onZoom |
滚轮缩放 | barSpace |
onScroll |
拖动翻页 | — |
onVisibleRangeChange |
可见范围变化 | 起始/结束索引 |
onPaneDrag |
面板高度拖拽 | paneId, height |
8.5 截图与导出
const url = chart.getConvertPictureUrl(
false, // 是否包含叠加层
'png', // 'png' | 'jpeg' | 'bmp'
'#ffffff' // 背景色
)
9. 内置指标一览
| 指标 | 叠加/独立 | 说明 |
|---|---|---|
| MA | 叠加 | 简单移动平均,参数默认 6/12/20 |
| EMA | 独立 | 指数移动平均 |
| BOLL | 叠加 | 布林带 |
| MACD | 独立 | 异同移动平均 |
| RSI | 独立 | 相对强弱指数 |
| SAR | 独立 | 抛物线转向 |
| BBI | 叠加 | 多空指标 |
| SMA | 叠加 | 简易平均线 |
| VOL | 独立 | 成交量 |
10. 扩展场景
10.1 接入后端 API
替换 loadRawCSV() 中的 fetch('/data/eurgbp_m1.csv') 为后端接口:
function loadRawCSV(source) {
return fetch('/api/kline?symbol=BTCUSDT&limit=1000')
.then(r => r.json())
.then(rows => {
// rows 格式: [{t: 1630000000000, o: ..., h: ..., l: ..., c: ..., v: ...}]
// 转换为内部格式 {ts, o, h, l, c, v, len}
const count = rows.length
return {
ts: new Float64Array(rows.map(r => r.t)),
o: new Float64Array(rows.map(r => r.o)),
h: new Float64Array(rows.map(r => r.h)),
l: new Float64Array(rows.map(r => r.l)),
c: new Float64Array(rows.map(r => r.c)),
v: new Float64Array(rows.map(r => r.v || 0)),
len: count
}
})
}
getBars 中的 forward/backward 分页自动交给后端处理。
10.2 WebSocket 实时推送
chart.setDataLoader({
getBars: ({ type, timestamp, period, callback }) => { /* REST 历史 */ },
subscribeBar: ({ symbol, period, callback }) => {
ws = new WebSocket(`wss://.../ws?symbol=${symbol}`)
ws.onmessage = (evt) => {
const bar = JSON.parse(evt.data)
// 回调单根 K 线,相同 timestamp 更新最后根
callback(bar)
}
},
unsubscribeBar: () => { ws.close() }
})
10.3 多图表联动
const chart1 = init('chart1')
const chart2 = init('chart2')
chart1.subscribeAction('onCrosshairChange', (crosshair) => {
// 通过 executeAction 同步到另一图表
chart2.executeAction('onCrosshairChange', { x: crosshair.x, y: null })
})
11. 故障排查
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 图表空白 | 调用顺序不对 | 确认 init → setSymbol → setPeriod → setDataLoader |
| 无数据 | getBars 未调用 callback | 确保 callback 一定被调用(即使失败) |
| 时间错乱 | 时间戳单位错误 | 确认 timestamp 为毫秒 |
| 分页失败 | more 标记错误 | 检查 { forward, backward } 返回值 |
| 指标不显示 | name 不匹配 | createIndicator 的 name 必须等于注册时的 name |
| 滚动条消失 | data 太少 | 数据量 < 2 不会显示滚动条 |
| 深色/浅色切换失败 | 样式名拼写错误 | 使用 'dark' / 'light' |