flutter_chen_kchart
Flutter 生态可用于生产环境的 K 线图表库 / A Production-Ready K-Line Chart for Flutter
🚀 Why flutter_chen_kchart?
A Flutter candlestick chart library focused on practical trading/charting scenarios. Built with
CustomPainter, supports cross-platform rendering and interactive chart operations.
选择理由 / Why Choose Us:
- 🏆 Flutter 生态首个 真正达到可商用的 K线图表库
- 🚀 原生性能 CustomPainter 实现,丝滑流畅
- 🌍 全平台支持 iOS/Android/Web/Windows/macOS/Linux
- 🔧 功能完整 开源包内提供常见图表与交互能力
- 📈 持续更新 长期维护,功能不断完善
4.x 核心新增能力 / Highlights
- 完整数据链路:支持直接传入 K 线,也支持
KChartDataSource管理首次加载、历史分页、实时订阅、断线重连、缓存和缺口修复。 - 专业坐标轴与视口:支持普通、百分比、对数价格轴,固定价格间隔、价格轴拖拽、自定义显示时区,以及跳转时间、回到最新和 fit content。
- 多 Pane 与指标系统:支持加权 Pane、自定义指标计算与绘制,以及 MA、EMA、BOLL、SAR、MACD、KDJ、RSI、WR、CCI、OBV、StochRSI 等内置指标。
- 交易场景叠加:支持订单、持仓开仓价、强平价、未实现盈亏、TP/SL 分组、成交历史 B/S 标记,以及异步改价确认。
- 完整绘图工作流:支持 OHLC 磁铁吸附、端点编辑、延长线/射线、撤销重做、事务合并、序列化持久化,以及数据变化后的时间锚点重定位。
- 大数据优化:实时指标增量计算、可视区裁剪、成交标记索引与文本缓存,可用于长历史 K 线和大量成交记录。
效果展示 / Effect Display
🌐 在线演示 / Online Demo
🌐 App演示 / App Demo
📦 快速开始 / Quick Start
1. 安装 / Install
dependencies:
flutter_chen_kchart: ^4.7.0
2. 基本用法 / Basic Usage
import 'package:flutter_chen_kchart/k_chart.dart';
final KChartController _controller = KChartController();
final DrawingToolManager _drawingManager =
DrawingToolManager(historyLimit: 100);
KChartWidget(
datas,
isTrendLine: false,
controller: _controller,
enableTheme: true,
colorVisionMode: ChartColorVisionMode.colorBlindFriendly,
minScale: 0.1,
maxScale: 5.0,
scaleSensitivity: 2.5,
onScaleChanged: (scale) {
print('Current scale: ${(scale * 100).toInt()}%');
},
enableDrawingTools: true,
drawingToolManager: _drawingManager,
enablePerformanceMode: true,
);
3. 数据格式与更新 / Data Contract & Updates
每根 K 线最少需要 time/open/high/low/close/vol。价格和成交量应转换为 double,time 使用 Unix 毫秒时间戳:
final bar = KLineEntity.fromCustom(
time: int.parse(item[0].toString()),
open: double.parse(item[1].toString()),
high: double.parse(item[2].toString()),
low: double.parse(item[3].toString()),
close: double.parse(item[4].toString()),
vol: double.parse(item[5].toString()),
);
time建议直接传交易所返回的 UTC Unix 毫秒时间戳。时间戳本身不携带时区,图表只在格式化时间轴和十字线时间时转换显示时区。timeZoneOffset: null使用设备当前时区;Duration.zero显示 UTC;例如Duration(hours: 8)固定显示 UTC+8。- 数据必须按时间从旧到新排列。
normalizeKChartBars可以排序、移除无时间数据并按时间去重。 - 图表不负责把逐笔成交聚合成 K 线。业务端按当前周期请求或生成 OHLCV 后传入,因此分钟、小时、日、周和月等周期都可以显示。
interval是业务周期标识并会传给KChartDataSource。秒、分、时、日、周固定周期还能用于数据质量和缺口检测;自然月不是固定毫秒长度,不执行固定间隔判断。
直接数据模式没有额外的 setData、appendData 或 updateLastData API,数据由业务状态管理并通过 datas 传入。推荐始终替换 List 引用:
// 以下方法放在持有图表数据的 State 中。
List<KLineEntity> bars = [];
void setData(List<KLineEntity> incoming) {
final next = normalizeKChartBars(incoming);
DataUtil.calculate(next);
setState(() => bars = next);
}
void prependHistory(List<KLineEntity> olderBars) {
// 加载更多得到的是更早数据,必须放在已有数据前面再统一排序去重。
final next = normalizeKChartBars([...olderBars, ...bars]);
DataUtil.calculate(next);
setState(() => bars = next);
}
void updateRealtimeBar(KLineEntity realtimeBar) {
// 同一 candle time 会替换;新的 candle time 会按时间插入。
final next = upsertKChartRealtimeBar(bars, realtimeBar);
final time = realtimeBar.time;
final changedIndex =
time == null ? -1 : findKChartBarIndexByTime(next, time);
DataUtil.calculateFrom(next, changedIndex < 0 ? 0 : changedIndex);
setState(() => bars = next);
}
如果必须原地修改同一个 List,修改 OHLCV 后递增 dataRevision;只有原地改变时间轴且列表长度、首尾时间都不变时,才递增 dataTopologyRevision。使用 KChartDataSource 时,排序去重、历史合并、实时替换和指标增量重算由库内部处理。
4. 画线撤销与持久化 / Drawing History & Persistence
_drawingManager.undo();
_drawingManager.redo();
_drawingManager.modeManager
..setMagnetMode(true)
..setMagnetSnapThreshold(50); // 屏幕像素 / logical pixels
final savedDrawings = _drawingManager.serializeTools();
_drawingManager.deserializeTools(savedDrawings); // 加载后建立新的历史基线
_drawingManager.updateToolProperties('trend-id', {
'extendLeft': true,
'extendRight': true,
});
// 也可通过 KChartController 操作当前图表
_controller.undoDrawing();
_controller.redoDrawing();
新增、移动、端点编辑、样式、显隐、删除和清空均支持撤销/重做。磁铁模式会在新建和端点编辑时吸附到最近 K 线的 OHLC,十字选择器会实时显示吸附位置。直接修改 DrawingTool 时,请用 beginHistoryTransaction() 和 commitHistoryTransaction() 包裹一次完整操作,避免拖动的每一帧形成独立历史。
数据源分页、实时补洞或使用新 datas 列表前插历史 K 线时,图表会按 K 线时间自动重定位画线锚点。若业务端必须原地修改同一个列表,请保留修改前的副本并调用 _drawingManager.rebaseToData(previousBars, currentBars)。
🛠️ 配置参数 / Configuration
| 参数/Property | 类型/Type | 默认值/Default | 说明/Description |
|---|---|---|---|
| minScale | double | 0.3 | 最小缩放比例 / Min scale |
| maxScale | double | 3.0 | 最大缩放比例 / Max scale |
| scaleSensitivity | double | 2.5 | 缩放灵敏度 / Scale sensitivity |
| enablePinchZoom | bool | true | 双指缩放 / Pinch zoom |
| enableScrollZoom | bool | true | 滚轮缩放 / Mouse wheel zoom |
| enableTheme | bool | true | 启用主题系统 / Enable theme |
| colorVisionMode | ChartColorVisionMode? | null | 颜色视觉模式,可启用色盲友好配色 / Color-vision mode |
| enableDrawingTools | bool | false | 启用绘图工具 / Drawing tools |
| drawingToolManager | DrawingToolManager? | null | 外部画线管理器,支持撤销、重做和持久化 / External drawing manager |
| enablePerformanceMode | bool | false | 性能优化 / Performance mode |
| controller | KChartController? | null | 控制器 / Controller |
| onScaleChanged | Function(double)? | null | 缩放回调 / Scale callback |
| dataSource | KChartDataSource? | null | 数据源协议 / Data source protocol |
| symbol | String? | null | 数据源模式交易对 / Data source symbol |
| interval | String? | null | 数据源模式周期 / Data source interval |
| dataRevision | int | 0 | 原地修改 K 线内容时递增 / In-place bar content revision |
| dataTopologyRevision | int | 0 | 原地修改 K 线时间轴时递增 / In-place bar timestamp revision |
| initialBarLimit | int | 500 | 首次请求数量 / Initial bar count |
| paginationBarLimit | int | 500 | 历史分页数量 / History page size |
| autoSubscribe | bool | true | 自动订阅实时K线 / Auto realtime subscription |
| autoScrollToLatest | bool | true | 位于最新边缘时实时数据自动跟随 / Auto-follow realtime latest edge |
| autoRepairRealtimeGaps | bool | true | 实时数据出现缺口时自动请求历史补洞 / Auto-repair realtime gaps |
| realtimeGapRepairLimit | int | 500 | 实时缺口补洞请求数量 / Realtime gap repair page size |
| realtimeReconnectPolicy | KChartRealtimeReconnectPolicy | default | 实时订阅断线重连策略 / Realtime reconnect policy |
| onDataLoadStateChanged | Function(KChartDataLoadState)? | null | 数据加载、分页、订阅状态回调 / Data load state callback |
| priceAxisMode | KChartPriceAxisMode | normal | 价格轴模式:普通、百分比、对数 / Price axis mode |
| priceAxisRange | KChartPriceRange? | null | 手动价格范围 / Manual price range |
| enablePriceAxisDrag | bool | false | 启用价格轴拖拽缩放 / Drag price axis |
| timeZoneOffset | Duration? | null | 固定显示时区;null 使用设备本地时区 / Fixed display offset; null uses device local time |
| showInfoDialog | bool | true | 显示十字线详情面板 / Show crosshair details |
| materialInfoDialog | bool | true | true 使用实色边框样式,false 使用渐变浮层样式 / Material or gradient info dialog |
| chartStyle.priceAxisTickInterval | double? | null | 固定 Y 轴价格间隔;为空时使用自然刻度 / Fixed Y-axis price step |
| chartStyle.priceAxisTickMode | KChartPriceAxisTickMode | nice | 自然刻度或旧版等分刻度 / Nice or legacy evenly-spaced ticks |
| onVisibleRangeChanged | Function(KChartVisibleRange)? | null | 可见范围回调 / Visible range callback |
| onPriceAxisRangeChanged | Function(KChartPriceRange)? | null | 价格轴范围回调 / Price range callback |
| panes | List | null | Pane 布局配置 / Pane layout |
| indicatorRegistry | KChartIndicatorRegistry? | null | 自定义指标注册表 / Indicator registry |
| indicatorConfigs | List | const [] | 指标参数配置 / Indicator configs |
| tradingOverlay | KChartTradingOverlay? | null | 交易叠加:订单线、持仓线、成交点 / Trading overlays |
| tradingLabels | KChartTradingLabels? | null | 交易线标签文案,默认跟随 isChinese / Trading label text |
| tradeMarkers | List | const [] | 外部买卖单 B/S 标记 / External buy/sell markers |
| tradeMarkerRevision | int | 0 | 原地修改 marker 列表时递增,用于刷新缓存 / Marker cache revision |
| onTradingLineDragUpdate | Function(KChartTradingLine, double)? | null | 交易线拖拽中回调 / Trading line drag update |
| onTradingLineDragConfirm | FutureOr<bool> Function(KChartTradingLine, double, double)? | null | 改价提交前异步确认 / Async amend confirmation |
| tradingLineDragConfirmTimeout | Duration | 15s | 改价确认超时,Duration.zero 表示禁用 / Amend confirmation timeout |
| onTradingLineDragEnd | Function(KChartTradingLine, double)? | null | 交易线拖拽结束回调 / Trading line drag end |
| onTradingLineTap | Function(KChartTradingLine)? | null | 交易线点击回调 / Trading line tap |
| onTradeMarkerTap | Function(KChartTradeMarker)? | null | 成交点点击回调 / Trade marker tap |
| onCrossLineChanged | Function(KChartCrossLineSelection)? | null | 十字线选中变化回调 / Crosshair selection callback |
| onCrossLineHidden | VoidCallback? | null | 十字线隐藏回调 / Crosshair hidden callback |
更多参数详见源码和注释。
数据源协议 / Data Source Protocol
KChartWidget 仍然支持直接传入 List<KLineEntity>。如果你希望图表自己管理首次加载、左滑分页、实时订阅和取消订阅,可以实现 KChartDataSource:
class ExchangeKChartDataSource extends KChartDataSource {
@override
Future<KChartBarsResult> getBars(KChartBarsRequest request) async {
final bars = await fetchBarsFromYourApi(
symbol: request.symbol,
interval: request.interval,
beforeTime: request.beforeTime,
limit: request.limit,
);
return KChartBarsResult(
bars: bars,
hasMore: bars.isNotEmpty,
nextBeforeTime: bars.isEmpty ? null : bars.first.time,
);
}
@override
Stream<KLineEntity> subscribeBars(KChartSubscription subscription) {
return connectYourRealtimeStream(
subscription.symbol,
subscription.interval,
);
}
@override
Future<void> unsubscribeBars(String subscriptionId) async {
await closeYourRealtimeStream(subscriptionId);
}
}
final controller = KChartController();
KChartWidget(
null,
controller: controller,
dataSource: ExchangeKChartDataSource(),
symbol: 'BTCUSDT',
interval: '1h',
autoRepairRealtimeGaps: true,
realtimeGapRepairLimit: 500,
realtimeReconnectPolicy: const KChartRealtimeReconnectPolicy(
maxAttempts: 5,
initialDelay: Duration(seconds: 1),
maxDelay: Duration(seconds: 30),
),
onDataLoadStateChanged: (state) {
if (state.isError) {
print('${state.phase} failed: ${state.error}');
}
if (state.isReconnecting) {
print('reconnect #${state.reconnectAttempt} after ${state.retryDelay}');
}
if (state.qualityReport?.hasIssues == true) {
print('data quality: ${state.qualityReport}');
}
},
isTrendLine: false,
)
项目侧也可以通过 KChartController 主动刷新或加载历史数据,适合“重试”“下拉刷新”“加载更早历史”这类业务入口:
await controller.reloadData(clearCache: true);
if (controller.canLoadMoreData && !controller.isLoadingMoreData) {
final appended = await controller.loadMoreData();
print('loaded older bars: $appended');
}
if (!controller.isAtLatest) {
await controller.scrollToLatest();
}
库本体只定义协议、缓存、状态事件和合并逻辑,不绑定任何具体交易所或网络库。交易所 API、WebSocket、鉴权、代理等仍然放在业务层或示例层实现。
KChartDataLoadState.qualityReport 会报告无时间戳、重复时间、乱序、非法 OHLC/成交量、非预期间隔和历史缺口等数据质量问题。项目可以用它做补洞、fallback、埋点或调试真实行情源。
当实时订阅合并后检测到固定周期缺口时,KChartWidget 默认会用最新实时 K 线时间作为 beforeTime 触发一次历史补洞请求,并把补洞结果通过 history 阶段状态回调返回。
实时 K 线 append/update 时,内置指标会从受影响的 K 线开始增量重算,避免每个 tick 都对全量历史执行完整指标计算。自定义指标可以实现 calculateFrom;未实现时会回退到 calculate 全量计算,以保证兼容性和结果正确性。
时间轴与价格轴 / Time & Price Axis
KChartController 支持读取和控制当前视图范围,适合项目中做“跳转到某笔成交时间”“回到最新”“fit content”“同步多图表”等能力:
final controller = KChartController();
KChartWidget(
datas,
controller: controller,
priceAxisMode: KChartPriceAxisMode.percentage,
chartStyle: ChartStyle().copyWith(
gridRows: 4,
// 设置后按固定价差绘制;不设置时默认使用自然刻度。
priceAxisTickInterval: 50,
),
// null: 系统本地时区;Duration.zero: UTC;Duration(hours: 8): UTC+8。
timeZoneOffset: const Duration(hours: 8),
enablePriceAxisDrag: true,
onVisibleRangeChanged: (range) {
print('${range.startTime} - ${range.endTime}');
},
onCrossLineChanged: (selection) {
print('${selection.index} ${selection.time} ${selection.price}');
},
onCrossLineHidden: () {
print('crosshair hidden');
},
isTrendLine: false,
);
await controller.jumpToTime(
tradeTime,
alignment: KChartTimeAlignment.center,
);
await controller.setLogicalRange(
const KChartLogicalRange(from: 100, to: 180),
);
await controller.fitContent();
controller.setPriceRange(const KChartPriceRange(min: 60000, max: 72000));
controller.clearPriceRange();
final x = controller.timeToCoordinate(tradeTime);
final price = controller.coordinateToPrice(120);
final y = controller.priceToCoordinate(65000);
final nearestTime = controller.coordinateToTime(180);
controller.showCrossLineAtTime(tradeTime);
controller.showCrossLineAtIndex(120, price: 65000);
controller.showCrossLineAtCoordinate(const Offset(180, 120));
controller.hideCrossLine();
gridRows 控制自动刻度的目标密度,不等同于固定价差。需要“每 50 USDT 一格”时设置 priceAxisTickInterval: 50;需要完全保留旧版按屏幕等分的行为时设置 priceAxisTickMode: KChartPriceAxisTickMode.evenlySpaced。价格轴默认会移除多余尾零,当前价和十字线仍严格遵循 precision。
Pane 与指标 / Panes & Indicators
默认仍然保持原有布局:主图、成交量、单副图。传入 panes 后,可以启用多个副图并调整高度权重:
KChartWidget(
datas,
isTrendLine: false,
panes: const [
KChartPaneConfig.main(weight: 3),
KChartPaneConfig.volume(weight: 1),
KChartPaneConfig.indicator(
indicatorId: KChartIndicatorIds.macd,
weight: 1,
),
KChartPaneConfig.indicator(
indicatorId: KChartIndicatorIds.rsi,
weight: 1,
),
],
);
内置副图指标当前支持 MACD、KDJ、RSI、WR、CCI。项目也可以注册自定义指标计算,计算结果会写入每根 KLineEntity.indicatorValues;如果在定义里声明 series,还可以直接作为自定义副图绘制:
final registry = KChartIndicatorRegistry([
KChartIndicatorDefinition(
id: 'MY_SIGNAL',
name: 'My Signal',
defaultParameters: const {'offset': 0},
series: const [
KChartIndicatorSeries(
valueKey: 'value',
label: 'Signal',
),
KChartIndicatorSeries(
valueKey: 'hist',
label: 'Histogram',
type: KChartIndicatorSeriesType.histogram,
),
],
calculate: (bars, config) {
final offset = config.parameters['offset'] ?? 0;
for (var i = 0; i < bars.length; i++) {
final bar = bars[i];
bar.indicatorValues['MY_SIGNAL.value'] = bar.close + offset;
bar.indicatorValues['MY_SIGNAL.hist'] =
i.isEven ? offset.toDouble() : -offset.toDouble();
}
},
calculateFrom: (bars, config, startIndex) {
final offset = config.parameters['offset'] ?? 0;
for (var i = startIndex; i < bars.length; i++) {
final bar = bars[i];
bar.indicatorValues['MY_SIGNAL.value'] = bar.close + offset;
bar.indicatorValues['MY_SIGNAL.hist'] =
i.isEven ? offset.toDouble() : -offset.toDouble();
}
},
),
]);
KChartWidget(
null,
dataSource: source,
symbol: 'BTCUSDT',
interval: '1h',
indicatorRegistry: registry,
indicatorConfigs: const [
KChartIndicatorConfig(
id: 'MY_SIGNAL',
parameters: {'offset': 10},
),
],
panes: const [
KChartPaneConfig.main(weight: 3),
KChartPaneConfig.indicator(indicatorId: 'MY_SIGNAL'),
],
isTrendLine: false,
);
内置指标也可以通过 indicatorConfigs 覆盖计算参数。常用参数 key 包括:BOLL.period/multiplier、MACD.fast/slow/signal/multiplier、KDJ.period/kSmoothing/dSmoothing、RSI.period、WR.period、CCI.period:
KChartWidget(
datas,
isTrendLine: false,
indicatorConfigs: const [
KChartIndicatorConfig(
id: KChartIndicatorIds.boll,
parameters: {'period': 20, 'multiplier': 2},
),
KChartIndicatorConfig(
id: KChartIndicatorIds.rsi,
parameters: {'period': 7},
),
KChartIndicatorConfig(
id: KChartIndicatorIds.macd,
parameters: {'fast': 12, 'slow': 26, 'signal': 9},
),
],
);
交易叠加 / Trading Overlay
业务侧可以把订单、持仓开仓价、强平价、止盈止损传给 tradingOverlay,把成交历史通过 tradeMarkers 传给 KChartWidget。库负责绘制、显隐、点击命中、拖拽预览和改价确认;真实下单、撤单和网络提交仍由业务层处理。
KChartTradingLine.order:通过status显示 Pending、Open、Partial、Filled、Canceled 或 Rejected。KChartTradingLine.position:表示开仓价,可通过unrealizedPnl、unrealizedPnlPercent显示未实现盈亏。KChartTradingLine.liquidation:显示强平价。KChartTradingLine.takeProfit/stopLoss:通过相同groupId与持仓组成 TP/SL 分组;KChartTradingLineGroup会绘制组连接提示,也可整组隐藏。showOrders、showPositions、showLiquidationPrice、showTakeProfitStopLoss、showTradeHistory:分别控制各类交易元素显隐;单条 line / marker 也有visible。maxMarkersPerBar:限制单根 K 线同时绘制的成交标记,超出部分聚合到最后一个标记,默认6。tradingLabels:可覆盖订单状态、持仓、强平和确认中文案;未传时自动跟随isChinese。onTradingLineDragConfirm:松手后异步确认,返回true才触发onTradingLineDragEnd;拒绝、异常、超时、订单失效或交易对切换都会恢复原价,迟到结果会被忽略。
tradeMarkers.time 必须精确匹配当前图表数据里的某个 KLineEntity.time。交易所 K 线通常使用当前周期 candle 的开盘时间;如果业务成交时间是具体成交毫秒,需要业务侧先按当前周期归一到对应 candle 的 time。时间匹配不到当前 K 线数据,或者价格 / X 坐标不在当前主图可视区域内,库不会显示这个 mark:
直接传入 datas 时,替换 List 会自动触发重绘;如果业务原地修改同一个 List,请递增 dataRevision。只有原地修改 K 线时间戳且长度、首尾时间都不变时才需要递增 dataTopologyRevision,普通 OHLC 实时更新不应递增它。
示例工程从当前 symbol + interval 的真实 K 线缓存构造交易叠加:最近两根 candle 生成 B/S 成交点,最近 40 根 candle 的价格区间生成开仓、强平、订单和 TP/SL,顶部“成交”按钮可切换成交历史,拖拽订单或 TP/SL 后会弹出确认框。
KChartWidget(
datas,
isTrendLine: false,
tradingOverlay: KChartTradingOverlay(
showTradeHistory: showTradeHistory,
groups: const [
KChartTradingLineGroup(id: 'position_1', label: 'Long #1'),
],
lines: [
KChartTradingLine.position(
id: 'entry_1',
price: 65000,
side: KChartTradeSide.buy,
quantity: '0.25',
unrealizedPnl: 320.5,
unrealizedPnlPercent: 1.97,
groupId: 'position_1',
),
KChartTradingLine.liquidation(
id: 'liq_1',
price: 59000,
side: KChartTradeSide.buy,
groupId: 'position_1',
),
KChartTradingLine.order(
id: 'order_1',
price: 66200,
side: KChartTradeSide.sell,
quantity: '0.10',
status: KChartTradingOrderStatus.partiallyFilled,
),
KChartTradingLine.takeProfit(
id: 'tp_1',
price: 68000,
side: KChartTradeSide.sell,
label: 'TP',
groupId: 'position_1',
),
KChartTradingLine.stopLoss(
id: 'sl_1',
price: 62000,
side: KChartTradeSide.sell,
label: 'SL',
groupId: 'position_1',
),
],
),
tradeMarkers: const [
KChartTradeMarker.buyOrder(
id: 'buy_1',
time: 1717200000000,
price: 65000,
),
KChartTradeMarker.sellOrder(
id: 'sell_1',
time: 1717286400000,
price: 66800,
),
KChartTradeMarker(
id: 'fill_1',
time: 1717372800000,
price: 66120,
side: KChartTradeSide.buy,
label: 'Fill',
style: KChartTradeMarkerStyle.arrow,
),
],
onTradingLineDragConfirm: (line, originalPrice, proposedPrice) async {
return await showAmendConfirmation(
line: line,
originalPrice: originalPrice,
proposedPrice: proposedPrice,
);
},
onTradingLineDragEnd: (line, price) {
// Only called after confirmation; submit the amend request here.
},
onTradingLineTap: (line) {
// Open order / position detail.
},
onTradeMarkerTap: (marker) {
// Open fill detail.
},
);
大量成交点性能 / Large Marker Performance
KChartWidget 内部长期持有 KChartTradingOverlayCache:K 线变化时一次性建立 time -> index 哈希表,marker 变化时按 candle index 排序,绘制和点击时通过二分查找只取当前可视区(含 1 根 overscan)的候选,并通过最多 256 项的 LRU 缓存复用 TextPainter。因此稳定数据下每帧开销由遍历全部成交历史降为 O(log M + V),其中 M 是 marker 总数,V 是可视区 marker 数。
建议业务侧传入不可变列表并在数据变化时替换列表引用。追加成交记录时保留已有 marker 对象(例如 [...oldMarkers, newMarker]),缓存会只索引新增项;不要在每次刷新时重新创建全部历史 marker。如果必须原地修改 tradeMarkers,请递增 tradeMarkerRevision;如果原地修改 tradingOverlay.markers,请递增 KChartTradingOverlay.revision。
仓库中的性能测试使用 20,000 根 K 线和 20,000 个 marker,并验证索引只构建一次、每帧只处理可视区候选、B/S 文本布局能够复用:
flutter test test/trading_overlay_painter_test.dart \
--plain-name "large trade history uses index, visible-range and text caches"
深度图性能 / Depth Performance
DepthChart 会缓存规范化后的买卖盘,并按半屏像素宽度自动降采样后绘制。业务侧通常应替换 bids / asks 列表引用;如果原地修改列表,请同步递增 dataRevision,确保缓存刷新。
无障碍配色 / Accessibility Palette
ChartThemeManager.setColorVisionMode(
ChartColorVisionMode.colorBlindFriendly,
);
ChartThemeManager.setColorVisionPalette(
const ChartColorVisionPalette(
upColor: Color(0xFF1E88E5),
dnColor: Color(0xFFFB8C00),
),
);
色盲友好模式会把默认的红涨绿跌切换为更容易区分的蓝涨橙跌,并同步调整深度图、当前价格标签和信息弹层中的涨跌颜色。
如果你有自己的视觉规范,也可以通过 ChartColorVisionPalette 单独指定色盲模式下的涨跌颜色。
📊 技术指标 / Indicators
- 主图指标:MA、EMA、BOLL、SAR
- 成交量指标:VOL、Volume MA
- 副图指标:MACD、KDJ、RSI、WR、CCI、OBV、StochRSI
- 支持自定义参数、逐条曲线配色/隐藏、自定义指标注册,以及
calculateFrom增量计算
🖌️ 绘图工具 / Drawing Tools
支持的绘图工具
- 📈 趋势线 / 趋势角度 - 支持角度显示
- ↕️ 垂直线 / 水平线 - 精确定位
- ➡️ 射线 / 水平射线 - 延伸至图表边界
- 🏹 箭头标注 - 重要位置标记
- ✚ 十字线 - 价格时间定位
绘图模式
- 🔄 连续绘制 - 快速添加多个图形
- 🎯 精确控制 - 像素级精度
- 🧲 磁铁吸附 - 新建和端点编辑时吸附最近 K 线 OHLC
- ↩️ 撤销重做 - 新建、移动、编辑、样式、显隐和删除均可回退
- 💾 持久化 - 支持序列化、恢复校验和数据时间轴重定位
- 👁️ 显示隐藏 - 灵活管理绘图
- 🗑️ 批量清除 - 一键清理
✨ 当前版本 / Current Package Scope
- 当前源码版本为
4.7.x,依赖安装版本从^4.7.0开始 - 包含完整 K 线、深度图、数据源协议、多 Pane/指标、专业坐标轴、控制器、交易叠加、绘图工具和主题系统
- 4.7.x 重点优化指标增量计算、实时缺口合并、程序化缩放稳定性,以及示例交易 UI、周期工具栏和深浅主题适配
- 示例工程位于
example/ - 详细变更记录见
CHANGELOG.md
💰 商用版获取 / Get Commercial Version
📞 联系方式 / Contact
- 💬 TG咨询:
taurus3914 - 🐛 GitHub Issues:技术问题讨论
📚 文档与示例 / Documentation
🤝 社区与支持 / Community & Support
- 🌟 GitHub - Star 支持我们
- 🐛 Issues - Bug 反馈
- 💬 Discussions - 功能讨论
🎯 路线图 / Roadmap
v5.x 计划
完全对标TradingView/主流交易所KChart
📜 许可证 / License
- 开源包: MIT License
- 商用支持/定制: 请通过上方联系方式咨询
📢 结语 / Final Words
🎉 Flutter 终于有了真正可商用的 K线图表库!
🚀 不再需要 WebView,不再被 TradingView 限制
💪 让你的 Flutter 金融应用更专业、更流畅!
立即开始 / Get Started Now
- ⭐ GitHub Star 支持我们
- 📦 安装免费版 体验功能
- 💬 联系我们 获取商用版
- 🚀 构建你的 专业金融应用
Made with ❤️ by Flutter Community
如果这个库对你有帮助,请在 GitHub 上给我们一个 Star ⭐
Libraries
- chart_style
- chart_translations
- depth_chart
- entity/candle_entity
- entity/cci_entity
- entity/depth_entity
- entity/drawing_tool_entity
- entity/index
- entity/info_window_entity
- entity/k_entity
- entity/k_line_entity
- entity/kdj_entity
- entity/macd_entity
- entity/rsi_entity
- entity/rw_entity
- entity/volume_entity
- extension/map_ext
- extension/num_ext
- k_chart
- k_chart_widget
- renderer/base_chart_painter
- renderer/base_chart_renderer
- renderer/chart_painter
- renderer/index
- renderer/main_renderer
- renderer/secondary_renderer
- renderer/vol_renderer
- utils/data_util
- utils/date_format_util
- utils/drawing_tool_manager
- utils/index
- utils/number_util