@qfo/qfchart 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +222 -0
- package/README.md +271 -0
- package/dist/index.d.ts +465 -0
- package/dist/qfchart.min.browser.js +109 -0
- package/package.json +71 -0
- package/src/QFChart.ts +1198 -0
- package/src/Utils.ts +31 -0
- package/src/components/AbstractPlugin.ts +104 -0
- package/src/components/DrawingEditor.ts +248 -0
- package/src/components/GraphicBuilder.ts +263 -0
- package/src/components/Indicator.ts +104 -0
- package/src/components/LayoutManager.ts +459 -0
- package/src/components/PluginManager.ts +234 -0
- package/src/components/SeriesBuilder.ts +192 -0
- package/src/components/TooltipFormatter.ts +97 -0
- package/src/index.ts +6 -0
- package/src/plugins/FibonacciTool.ts +192 -0
- package/src/plugins/LineTool.ts +190 -0
- package/src/plugins/MeasureTool.ts +344 -0
- package/src/types.ts +160 -0
- package/src/utils/EventBus.ts +67 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import * as echarts from 'echarts';
|
|
2
|
+
|
|
3
|
+
type EventType = 'mouse:down' | 'mouse:move' | 'mouse:up' | 'mouse:click' | 'chart:resize' | 'chart:dataZoom' | 'chart:updated' | 'plugin:activated' | 'plugin:deactivated' | 'drawing:hover' | 'drawing:mouseout' | 'drawing:mousedown' | 'drawing:click' | 'drawing:point:hover' | 'drawing:point:mouseout' | 'drawing:point:mousedown' | 'drawing:point:click' | 'drawing:selected' | 'drawing:deselected' | 'drawing:deleted';
|
|
4
|
+
type EventHandler<T = any> = (payload: T) => void;
|
|
5
|
+
declare class EventBus {
|
|
6
|
+
private handlers;
|
|
7
|
+
on<T = any>(event: EventType, handler: EventHandler<T>): void;
|
|
8
|
+
off<T = any>(event: EventType, handler: EventHandler<T>): void;
|
|
9
|
+
emit<T = any>(event: EventType, payload?: T): void;
|
|
10
|
+
clear(): void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface OHLCV {
|
|
14
|
+
time: number;
|
|
15
|
+
open: number;
|
|
16
|
+
high: number;
|
|
17
|
+
low: number;
|
|
18
|
+
close: number;
|
|
19
|
+
volume: number;
|
|
20
|
+
}
|
|
21
|
+
interface IndicatorPoint {
|
|
22
|
+
time: number;
|
|
23
|
+
value: number | null;
|
|
24
|
+
options?: {
|
|
25
|
+
color?: string;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
type IndicatorStyle = 'line' | 'columns' | 'histogram' | 'circles' | 'cross' | 'background';
|
|
29
|
+
interface IndicatorOptions {
|
|
30
|
+
style: IndicatorStyle;
|
|
31
|
+
color: string;
|
|
32
|
+
linewidth?: number;
|
|
33
|
+
}
|
|
34
|
+
interface IndicatorPlot {
|
|
35
|
+
data: IndicatorPoint[];
|
|
36
|
+
options: IndicatorOptions;
|
|
37
|
+
}
|
|
38
|
+
interface Indicator$1 {
|
|
39
|
+
id: string;
|
|
40
|
+
plots: {
|
|
41
|
+
[name: string]: IndicatorPlot;
|
|
42
|
+
};
|
|
43
|
+
paneIndex: number;
|
|
44
|
+
height?: number;
|
|
45
|
+
collapsed?: boolean;
|
|
46
|
+
titleColor?: string;
|
|
47
|
+
controls?: {
|
|
48
|
+
collapse?: boolean;
|
|
49
|
+
maximize?: boolean;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
interface QFChartOptions {
|
|
53
|
+
title?: string;
|
|
54
|
+
titleColor?: string;
|
|
55
|
+
backgroundColor?: string;
|
|
56
|
+
upColor?: string;
|
|
57
|
+
downColor?: string;
|
|
58
|
+
fontColor?: string;
|
|
59
|
+
fontFamily?: string;
|
|
60
|
+
padding?: number;
|
|
61
|
+
height?: string | number;
|
|
62
|
+
controls?: {
|
|
63
|
+
collapse?: boolean;
|
|
64
|
+
maximize?: boolean;
|
|
65
|
+
fullscreen?: boolean;
|
|
66
|
+
};
|
|
67
|
+
dataZoom?: {
|
|
68
|
+
visible?: boolean;
|
|
69
|
+
position?: 'top' | 'bottom';
|
|
70
|
+
height?: number;
|
|
71
|
+
start?: number;
|
|
72
|
+
end?: number;
|
|
73
|
+
};
|
|
74
|
+
databox?: {
|
|
75
|
+
position: 'floating' | 'left' | 'right';
|
|
76
|
+
};
|
|
77
|
+
layout?: {
|
|
78
|
+
mainPaneHeight: string;
|
|
79
|
+
gap: number;
|
|
80
|
+
};
|
|
81
|
+
watermark?: boolean;
|
|
82
|
+
}
|
|
83
|
+
interface Coordinate {
|
|
84
|
+
x: number;
|
|
85
|
+
y: number;
|
|
86
|
+
}
|
|
87
|
+
interface DataCoordinate {
|
|
88
|
+
timeIndex: number;
|
|
89
|
+
value: number;
|
|
90
|
+
paneIndex?: number;
|
|
91
|
+
}
|
|
92
|
+
interface ChartContext {
|
|
93
|
+
getChart(): any;
|
|
94
|
+
getMarketData(): OHLCV[];
|
|
95
|
+
getTimeToIndex(): Map<number, number>;
|
|
96
|
+
getOptions(): QFChartOptions;
|
|
97
|
+
events: EventBus;
|
|
98
|
+
coordinateConversion: {
|
|
99
|
+
pixelToData: (point: Coordinate) => DataCoordinate | null;
|
|
100
|
+
dataToPixel: (point: DataCoordinate) => Coordinate | null;
|
|
101
|
+
};
|
|
102
|
+
disableTools(): void;
|
|
103
|
+
setZoom(start: number, end: number): void;
|
|
104
|
+
addDrawing(drawing: DrawingElement): void;
|
|
105
|
+
removeDrawing(id: string): void;
|
|
106
|
+
getDrawing(id: string): DrawingElement | undefined;
|
|
107
|
+
updateDrawing(drawing: DrawingElement): void;
|
|
108
|
+
lockChart(): void;
|
|
109
|
+
unlockChart(): void;
|
|
110
|
+
}
|
|
111
|
+
type DrawingType = 'line' | 'fibonacci';
|
|
112
|
+
interface DrawingElement {
|
|
113
|
+
id: string;
|
|
114
|
+
type: DrawingType;
|
|
115
|
+
points: DataCoordinate[];
|
|
116
|
+
paneIndex?: number;
|
|
117
|
+
style?: {
|
|
118
|
+
color?: string;
|
|
119
|
+
lineWidth?: number;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
interface PluginConfig {
|
|
123
|
+
id: string;
|
|
124
|
+
name?: string;
|
|
125
|
+
icon?: string;
|
|
126
|
+
hotkey?: string;
|
|
127
|
+
}
|
|
128
|
+
interface Plugin {
|
|
129
|
+
id: string;
|
|
130
|
+
name?: string;
|
|
131
|
+
icon?: string;
|
|
132
|
+
init(context: ChartContext): void;
|
|
133
|
+
activate?(): void;
|
|
134
|
+
deactivate?(): void;
|
|
135
|
+
destroy?(): void;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
declare class Indicator implements Indicator$1 {
|
|
139
|
+
id: string;
|
|
140
|
+
plots: {
|
|
141
|
+
[name: string]: IndicatorPlot;
|
|
142
|
+
};
|
|
143
|
+
paneIndex: number;
|
|
144
|
+
height?: number;
|
|
145
|
+
collapsed: boolean;
|
|
146
|
+
titleColor?: string;
|
|
147
|
+
controls?: {
|
|
148
|
+
collapse?: boolean;
|
|
149
|
+
maximize?: boolean;
|
|
150
|
+
};
|
|
151
|
+
constructor(id: string, plots: {
|
|
152
|
+
[name: string]: IndicatorPlot;
|
|
153
|
+
}, paneIndex: number, options?: {
|
|
154
|
+
height?: number;
|
|
155
|
+
collapsed?: boolean;
|
|
156
|
+
titleColor?: string;
|
|
157
|
+
controls?: {
|
|
158
|
+
collapse?: boolean;
|
|
159
|
+
maximize?: boolean;
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
toggleCollapse(): void;
|
|
163
|
+
isVisible(): boolean;
|
|
164
|
+
/**
|
|
165
|
+
* Update indicator data incrementally by merging new points
|
|
166
|
+
*
|
|
167
|
+
* @param plots - New plots data to merge (same structure as constructor)
|
|
168
|
+
*
|
|
169
|
+
* @remarks
|
|
170
|
+
* This method merges new indicator data with existing data by timestamp.
|
|
171
|
+
* - New timestamps are added
|
|
172
|
+
* - Existing timestamps are updated with new values
|
|
173
|
+
* - All data is automatically sorted by time after merge
|
|
174
|
+
*
|
|
175
|
+
* **Important**: This method only updates the indicator's internal data structure.
|
|
176
|
+
* To see the changes reflected in the chart, you MUST call `chart.updateData()`
|
|
177
|
+
* after updating indicator data.
|
|
178
|
+
*
|
|
179
|
+
* **Usage Pattern**:
|
|
180
|
+
* ```typescript
|
|
181
|
+
* // 1. Update indicator data first
|
|
182
|
+
* indicator.updateData({
|
|
183
|
+
* macd: { data: [{ time: 1234567890, value: 150 }], options: { style: 'line', color: '#2962FF' } }
|
|
184
|
+
* });
|
|
185
|
+
*
|
|
186
|
+
* // 2. Then update chart data to trigger re-render
|
|
187
|
+
* chart.updateData([
|
|
188
|
+
* { time: 1234567890, open: 100, high: 105, low: 99, close: 103, volume: 1000 }
|
|
189
|
+
* ]);
|
|
190
|
+
* ```
|
|
191
|
+
*
|
|
192
|
+
* **Note**: If you update indicator data without corresponding market data changes,
|
|
193
|
+
* this typically indicates a recalculation scenario. In normal workflows, indicator
|
|
194
|
+
* values are derived from market data, so indicator updates should correspond to
|
|
195
|
+
* new or modified market bars.
|
|
196
|
+
*/
|
|
197
|
+
updateData(plots: {
|
|
198
|
+
[name: string]: IndicatorPlot;
|
|
199
|
+
}): void;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
declare class QFChart implements ChartContext {
|
|
203
|
+
private chart;
|
|
204
|
+
private options;
|
|
205
|
+
private marketData;
|
|
206
|
+
private indicators;
|
|
207
|
+
private timeToIndex;
|
|
208
|
+
private pluginManager;
|
|
209
|
+
private drawingEditor;
|
|
210
|
+
events: EventBus;
|
|
211
|
+
private isMainCollapsed;
|
|
212
|
+
private maximizedPaneId;
|
|
213
|
+
private selectedDrawingId;
|
|
214
|
+
private drawings;
|
|
215
|
+
coordinateConversion: {
|
|
216
|
+
pixelToData: (point: {
|
|
217
|
+
x: number;
|
|
218
|
+
y: number;
|
|
219
|
+
}) => {
|
|
220
|
+
timeIndex: number;
|
|
221
|
+
value: number;
|
|
222
|
+
paneIndex: number;
|
|
223
|
+
} | null;
|
|
224
|
+
dataToPixel: (point: {
|
|
225
|
+
timeIndex: number;
|
|
226
|
+
value: number;
|
|
227
|
+
paneIndex?: number;
|
|
228
|
+
}) => {
|
|
229
|
+
x: number;
|
|
230
|
+
y: number;
|
|
231
|
+
} | null;
|
|
232
|
+
};
|
|
233
|
+
private readonly upColor;
|
|
234
|
+
private readonly downColor;
|
|
235
|
+
private readonly defaultPadding;
|
|
236
|
+
private padding;
|
|
237
|
+
private dataIndexOffset;
|
|
238
|
+
private rootContainer;
|
|
239
|
+
private layoutContainer;
|
|
240
|
+
private toolbarContainer;
|
|
241
|
+
private leftSidebar;
|
|
242
|
+
private rightSidebar;
|
|
243
|
+
private chartContainer;
|
|
244
|
+
constructor(container: HTMLElement, options?: QFChartOptions);
|
|
245
|
+
private onKeyDown;
|
|
246
|
+
private onFullscreenChange;
|
|
247
|
+
private bindDrawingEvents;
|
|
248
|
+
getChart(): echarts.ECharts;
|
|
249
|
+
getMarketData(): OHLCV[];
|
|
250
|
+
getTimeToIndex(): Map<number, number>;
|
|
251
|
+
getOptions(): QFChartOptions;
|
|
252
|
+
disableTools(): void;
|
|
253
|
+
registerPlugin(plugin: Plugin): void;
|
|
254
|
+
addDrawing(drawing: DrawingElement): void;
|
|
255
|
+
removeDrawing(id: string): void;
|
|
256
|
+
getDrawing(id: string): DrawingElement | undefined;
|
|
257
|
+
updateDrawing(drawing: DrawingElement): void;
|
|
258
|
+
private isLocked;
|
|
259
|
+
private lockedState;
|
|
260
|
+
lockChart(): void;
|
|
261
|
+
unlockChart(): void;
|
|
262
|
+
setZoom(start: number, end: number): void;
|
|
263
|
+
setMarketData(data: OHLCV[]): void;
|
|
264
|
+
/**
|
|
265
|
+
* Update market data incrementally without full re-render
|
|
266
|
+
* Merges new/updated OHLCV data with existing data by timestamp
|
|
267
|
+
*
|
|
268
|
+
* @param data - Array of OHLCV data to merge
|
|
269
|
+
*
|
|
270
|
+
* @remarks
|
|
271
|
+
* **Performance Optimization**: This method only triggers a chart update if the data array contains
|
|
272
|
+
* new or modified bars. If an empty array is passed, no update occurs (expected behavior).
|
|
273
|
+
*
|
|
274
|
+
* **Usage Pattern for Updating Indicators**:
|
|
275
|
+
* When updating both market data and indicators, follow this order:
|
|
276
|
+
*
|
|
277
|
+
* 1. Update indicator data first using `indicator.updateData(plots)`
|
|
278
|
+
* 2. Then call `chart.updateData(newBars)` with the new/modified market data
|
|
279
|
+
*
|
|
280
|
+
* The chart update will trigger a re-render that includes the updated indicator data.
|
|
281
|
+
*
|
|
282
|
+
* **Important**: If you update indicator data without updating market data (e.g., recalculation
|
|
283
|
+
* with same bars), you must still call `chart.updateData([...])` with at least one bar
|
|
284
|
+
* to trigger the re-render. Calling with an empty array will NOT trigger an update.
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```typescript
|
|
288
|
+
* // Step 1: Update indicator data
|
|
289
|
+
* macdIndicator.updateData({
|
|
290
|
+
* macd: { data: [{ time: 1234567890, value: 150 }], options: { style: 'line', color: '#2962FF' } }
|
|
291
|
+
* });
|
|
292
|
+
*
|
|
293
|
+
* // Step 2: Update market data (triggers re-render with new indicator data)
|
|
294
|
+
* chart.updateData([
|
|
295
|
+
* { time: 1234567890, open: 100, high: 105, low: 99, close: 103, volume: 1000 }
|
|
296
|
+
* ]);
|
|
297
|
+
* ```
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```typescript
|
|
301
|
+
* // If only updating existing bar (e.g., real-time tick updates):
|
|
302
|
+
* const lastBar = { ...existingBar, close: newPrice, high: Math.max(existingBar.high, newPrice) };
|
|
303
|
+
* chart.updateData([lastBar]); // Updates by timestamp
|
|
304
|
+
* ```
|
|
305
|
+
*/
|
|
306
|
+
updateData(data: OHLCV[]): void;
|
|
307
|
+
addIndicator(id: string, plots: {
|
|
308
|
+
[name: string]: IndicatorPlot;
|
|
309
|
+
}, options?: {
|
|
310
|
+
isOverlay?: boolean;
|
|
311
|
+
height?: number;
|
|
312
|
+
titleColor?: string;
|
|
313
|
+
controls?: {
|
|
314
|
+
collapse?: boolean;
|
|
315
|
+
maximize?: boolean;
|
|
316
|
+
};
|
|
317
|
+
}): Indicator;
|
|
318
|
+
setIndicator(id: string, plot: IndicatorPlot, isOverlay?: boolean): void;
|
|
319
|
+
removeIndicator(id: string): void;
|
|
320
|
+
toggleIndicator(id: string, action?: 'collapse' | 'maximize' | 'fullscreen'): void;
|
|
321
|
+
resize(): void;
|
|
322
|
+
destroy(): void;
|
|
323
|
+
private rebuildTimeIndex;
|
|
324
|
+
private render;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
declare abstract class AbstractPlugin implements Plugin {
|
|
328
|
+
id: string;
|
|
329
|
+
name?: string;
|
|
330
|
+
icon?: string;
|
|
331
|
+
protected context: ChartContext;
|
|
332
|
+
private eventListeners;
|
|
333
|
+
constructor(config: PluginConfig);
|
|
334
|
+
init(context: ChartContext): void;
|
|
335
|
+
/**
|
|
336
|
+
* Lifecycle hook called after context is initialized.
|
|
337
|
+
* Override this instead of init().
|
|
338
|
+
*/
|
|
339
|
+
protected onInit(): void;
|
|
340
|
+
activate(): void;
|
|
341
|
+
/**
|
|
342
|
+
* Lifecycle hook called when the plugin is activated.
|
|
343
|
+
*/
|
|
344
|
+
protected onActivate(): void;
|
|
345
|
+
deactivate(): void;
|
|
346
|
+
/**
|
|
347
|
+
* Lifecycle hook called when the plugin is deactivated.
|
|
348
|
+
*/
|
|
349
|
+
protected onDeactivate(): void;
|
|
350
|
+
destroy(): void;
|
|
351
|
+
/**
|
|
352
|
+
* Lifecycle hook called when the plugin is destroyed.
|
|
353
|
+
*/
|
|
354
|
+
protected onDestroy(): void;
|
|
355
|
+
/**
|
|
356
|
+
* Register an event listener that will be automatically cleaned up on destroy.
|
|
357
|
+
*/
|
|
358
|
+
protected on(event: EventType, handler: EventHandler): void;
|
|
359
|
+
/**
|
|
360
|
+
* Remove a specific event listener.
|
|
361
|
+
*/
|
|
362
|
+
protected off(event: EventType, handler: EventHandler): void;
|
|
363
|
+
/**
|
|
364
|
+
* Remove all listeners registered by this plugin.
|
|
365
|
+
*/
|
|
366
|
+
protected removeAllListeners(): void;
|
|
367
|
+
/**
|
|
368
|
+
* Access to the ECharts instance.
|
|
369
|
+
*/
|
|
370
|
+
protected get chart(): any;
|
|
371
|
+
/**
|
|
372
|
+
* Access to market data.
|
|
373
|
+
*/
|
|
374
|
+
protected get marketData(): OHLCV[];
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
declare class MeasureTool extends AbstractPlugin {
|
|
378
|
+
private zr;
|
|
379
|
+
private state;
|
|
380
|
+
private startPoint;
|
|
381
|
+
private endPoint;
|
|
382
|
+
private group;
|
|
383
|
+
private rect;
|
|
384
|
+
private labelRect;
|
|
385
|
+
private labelText;
|
|
386
|
+
private lineV;
|
|
387
|
+
private lineH;
|
|
388
|
+
private arrowStart;
|
|
389
|
+
private arrowEnd;
|
|
390
|
+
constructor(options: {
|
|
391
|
+
name?: string;
|
|
392
|
+
icon?: string;
|
|
393
|
+
});
|
|
394
|
+
protected onInit(): void;
|
|
395
|
+
protected onActivate(): void;
|
|
396
|
+
protected onDeactivate(): void;
|
|
397
|
+
protected onDestroy(): void;
|
|
398
|
+
private onMouseDown;
|
|
399
|
+
private onChartInteraction;
|
|
400
|
+
private onClick;
|
|
401
|
+
private enableClearListeners;
|
|
402
|
+
private clearHandlers;
|
|
403
|
+
private disableClearListeners;
|
|
404
|
+
private onMouseMove;
|
|
405
|
+
private initGraphic;
|
|
406
|
+
private removeGraphic;
|
|
407
|
+
private updateGraphic;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
declare class LineTool extends AbstractPlugin {
|
|
411
|
+
private zr;
|
|
412
|
+
private state;
|
|
413
|
+
private startPoint;
|
|
414
|
+
private endPoint;
|
|
415
|
+
private group;
|
|
416
|
+
private line;
|
|
417
|
+
private startCircle;
|
|
418
|
+
private endCircle;
|
|
419
|
+
constructor(options: {
|
|
420
|
+
name?: string;
|
|
421
|
+
icon?: string;
|
|
422
|
+
});
|
|
423
|
+
protected onInit(): void;
|
|
424
|
+
protected onActivate(): void;
|
|
425
|
+
protected onDeactivate(): void;
|
|
426
|
+
protected onDestroy(): void;
|
|
427
|
+
private onMouseDown;
|
|
428
|
+
private onChartInteraction;
|
|
429
|
+
private onClick;
|
|
430
|
+
private saveDataCoordinates;
|
|
431
|
+
private updateGraphicFromData;
|
|
432
|
+
private enableClearListeners;
|
|
433
|
+
private clearHandlers;
|
|
434
|
+
private disableClearListeners;
|
|
435
|
+
private onMouseMove;
|
|
436
|
+
private initGraphic;
|
|
437
|
+
private removeGraphic;
|
|
438
|
+
private updateGraphic;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
declare class FibonacciTool extends AbstractPlugin {
|
|
442
|
+
private startPoint;
|
|
443
|
+
private endPoint;
|
|
444
|
+
private state;
|
|
445
|
+
private graphicGroup;
|
|
446
|
+
private readonly levels;
|
|
447
|
+
private readonly colors;
|
|
448
|
+
constructor(options?: {
|
|
449
|
+
name?: string;
|
|
450
|
+
icon?: string;
|
|
451
|
+
});
|
|
452
|
+
onActivate(): void;
|
|
453
|
+
onDeactivate(): void;
|
|
454
|
+
private bindEvents;
|
|
455
|
+
private unbindEvents;
|
|
456
|
+
private onClick;
|
|
457
|
+
private onMouseMove;
|
|
458
|
+
private initGraphic;
|
|
459
|
+
private removeGraphic;
|
|
460
|
+
private updateGraphic;
|
|
461
|
+
private saveDrawing;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export { AbstractPlugin, FibonacciTool, LineTool, MeasureTool, QFChart };
|
|
465
|
+
export type { ChartContext, Coordinate, DataCoordinate, DrawingElement, DrawingType, Indicator$1 as Indicator, IndicatorOptions, IndicatorPlot, IndicatorPoint, IndicatorStyle, OHLCV, Plugin, PluginConfig, QFChartOptions };
|