Files
KlineChart_dev/README.md
T
2026-08-13 21:03:50 +08:00

331 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. 快速开始
```bash
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()` 调用之前注册:
```js
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 计算函数约定
```js
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 截图与导出
```js
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')` 为后端接口:
```js
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 实时推送
```js
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 多图表联动
```js
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'` |