@qfo/qfchart 0.6.6 → 0.6.8

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.
@@ -0,0 +1,167 @@
1
+ import { SeriesRenderer, RenderContext } from './SeriesRenderer';
2
+ import { ColorUtils } from '../../utils/ColorUtils';
3
+
4
+ /**
5
+ * Renderer for Pine Script linefill.* drawing objects.
6
+ * Each linefill fills the area between two line objects as a polygon.
7
+ *
8
+ * Style name: 'linefill'
9
+ */
10
+ export class LinefillRenderer implements SeriesRenderer {
11
+ render(context: RenderContext): any {
12
+ const { seriesName, xAxisIndex, yAxisIndex, dataArray, dataIndexOffset } = context;
13
+ const offset = dataIndexOffset || 0;
14
+
15
+ // Collect all non-deleted linefill objects from the sparse dataArray.
16
+ // Same aggregation pattern as DrawingLineRenderer — objects are stored
17
+ // as an array in a single data entry.
18
+ const fillObjects: any[] = [];
19
+ const fillData: number[][] = [];
20
+
21
+ for (let i = 0; i < dataArray.length; i++) {
22
+ const val = dataArray[i];
23
+ if (!val) continue;
24
+
25
+ const items = Array.isArray(val) ? val : [val];
26
+ for (const lf of items) {
27
+ if (!lf || typeof lf !== 'object' || lf._deleted) continue;
28
+
29
+ const line1 = lf.line1;
30
+ const line2 = lf.line2;
31
+ if (!line1 || !line2 || line1._deleted || line2._deleted) continue;
32
+
33
+ fillObjects.push(lf);
34
+
35
+ // Store all 8 coordinates for the two lines
36
+ const xOff1 = line1.xloc === 'bar_index' ? offset : 0;
37
+ const xOff2 = line2.xloc === 'bar_index' ? offset : 0;
38
+ fillData.push([
39
+ line1.x1 + xOff1, line1.y1,
40
+ line1.x2 + xOff1, line1.y2,
41
+ line2.x1 + xOff2, line2.y1,
42
+ line2.x2 + xOff2, line2.y2,
43
+ ]);
44
+ }
45
+ }
46
+
47
+ if (fillData.length === 0) {
48
+ return { name: seriesName, type: 'custom', xAxisIndex, yAxisIndex, data: [], silent: true };
49
+ }
50
+
51
+ return {
52
+ name: seriesName,
53
+ type: 'custom',
54
+ xAxisIndex,
55
+ yAxisIndex,
56
+ renderItem: (params: any, api: any) => {
57
+ const idx = params.dataIndex;
58
+ const lf = fillObjects[idx];
59
+ if (!lf || lf._deleted) return;
60
+
61
+ const line1 = lf.line1;
62
+ const line2 = lf.line2;
63
+ if (!line1 || !line2 || line1._deleted || line2._deleted) return;
64
+
65
+ // Get data values: line1 start, line1 end, line2 start, line2 end
66
+ const l1x1 = api.value(0);
67
+ const l1y1 = api.value(1);
68
+ const l1x2 = api.value(2);
69
+ const l1y2 = api.value(3);
70
+ const l2x1 = api.value(4);
71
+ const l2y1 = api.value(5);
72
+ const l2x2 = api.value(6);
73
+ const l2y2 = api.value(7);
74
+
75
+ // Convert to pixel coordinates
76
+ let p1Start = api.coord([l1x1, l1y1]);
77
+ let p1End = api.coord([l1x2, l1y2]);
78
+ let p2Start = api.coord([l2x1, l2y1]);
79
+ let p2End = api.coord([l2x2, l2y2]);
80
+
81
+ // Handle line extensions — if lines are extended, extend the fill too
82
+ const extend1 = line1.extend || 'none';
83
+ const extend2 = line2.extend || 'none';
84
+ if (extend1 !== 'none' || extend2 !== 'none') {
85
+ const cs = params.coordSys;
86
+ const left = cs.x;
87
+ const right = cs.x + cs.width;
88
+ const top = cs.y;
89
+ const bottom = cs.y + cs.height;
90
+
91
+ if (extend1 !== 'none') {
92
+ [p1Start, p1End] = this.extendLine(p1Start, p1End, extend1, left, right, top, bottom);
93
+ }
94
+ if (extend2 !== 'none') {
95
+ [p2Start, p2End] = this.extendLine(p2Start, p2End, extend2, left, right, top, bottom);
96
+ }
97
+ }
98
+
99
+ // Parse color
100
+ const { color: fillColor, opacity: fillOpacity } = ColorUtils.parseColor(lf.color || 'rgba(128, 128, 128, 0.2)');
101
+
102
+ // Create a polygon: line1.start → line1.end → line2.end → line2.start
103
+ return {
104
+ type: 'polygon',
105
+ shape: {
106
+ points: [
107
+ p1Start,
108
+ p1End,
109
+ p2End,
110
+ p2Start,
111
+ ],
112
+ },
113
+ style: {
114
+ fill: fillColor,
115
+ opacity: fillOpacity,
116
+ },
117
+ silent: true,
118
+ };
119
+ },
120
+ data: fillData,
121
+ z: 10, // Behind lines (z=15) but above other elements
122
+ silent: true,
123
+ emphasis: { disabled: true },
124
+ };
125
+ }
126
+
127
+ private extendLine(
128
+ p1: number[],
129
+ p2: number[],
130
+ extend: string,
131
+ left: number,
132
+ right: number,
133
+ top: number,
134
+ bottom: number,
135
+ ): [number[], number[]] {
136
+ const dx = p2[0] - p1[0];
137
+ const dy = p2[1] - p1[1];
138
+
139
+ if (dx === 0 && dy === 0) return [p1, p2];
140
+
141
+ const extendPoint = (origin: number[], dir: number[]): number[] => {
142
+ let tMax = Infinity;
143
+ if (dir[0] !== 0) {
144
+ const tx = dir[0] > 0 ? (right - origin[0]) / dir[0] : (left - origin[0]) / dir[0];
145
+ tMax = Math.min(tMax, tx);
146
+ }
147
+ if (dir[1] !== 0) {
148
+ const ty = dir[1] > 0 ? (bottom - origin[1]) / dir[1] : (top - origin[1]) / dir[1];
149
+ tMax = Math.min(tMax, ty);
150
+ }
151
+ if (!isFinite(tMax)) tMax = 0;
152
+ return [origin[0] + tMax * dir[0], origin[1] + tMax * dir[1]];
153
+ };
154
+
155
+ let newP1 = p1;
156
+ let newP2 = p2;
157
+
158
+ if (extend === 'right' || extend === 'both') {
159
+ newP2 = extendPoint(p1, [dx, dy]);
160
+ }
161
+ if (extend === 'left' || extend === 'both') {
162
+ newP1 = extendPoint(p2, [-dx, -dy]);
163
+ }
164
+
165
+ return [newP1, newP2];
166
+ }
167
+ }
@@ -1,20 +1,21 @@
1
- import { IndicatorPlot, OHLCV } from '../../types';
2
-
3
- export interface RenderContext {
4
- seriesName: string;
5
- xAxisIndex: number;
6
- yAxisIndex: number;
7
- dataArray: any[];
8
- colorArray: any[];
9
- optionsArray: any[];
10
- plotOptions: any;
11
- candlestickData?: OHLCV[]; // For shape positioning
12
- plotDataArrays?: Map<string, number[]>; // For fill plots
13
- indicatorId?: string;
14
- plotName?: string;
15
- indicator?: any; // Reference to parent indicator object if needed
16
- }
17
-
18
- export interface SeriesRenderer {
19
- render(context: RenderContext): any;
20
- }
1
+ import { IndicatorPlot, OHLCV } from '../../types';
2
+
3
+ export interface RenderContext {
4
+ seriesName: string;
5
+ xAxisIndex: number;
6
+ yAxisIndex: number;
7
+ dataArray: any[];
8
+ colorArray: any[];
9
+ optionsArray: any[];
10
+ plotOptions: any;
11
+ candlestickData?: OHLCV[]; // For shape positioning
12
+ plotDataArrays?: Map<string, number[]>; // For fill plots
13
+ indicatorId?: string;
14
+ plotName?: string;
15
+ indicator?: any; // Reference to parent indicator object if needed
16
+ dataIndexOffset?: number; // Padding offset for converting bar_index to ECharts index
17
+ }
18
+
19
+ export interface SeriesRenderer {
20
+ render(context: RenderContext): any;
21
+ }
package/src/types.ts CHANGED
@@ -1,205 +1,207 @@
1
- import { EventBus } from './utils/EventBus';
2
-
3
- export interface OHLCV {
4
- time: number;
5
- open: number;
6
- high: number;
7
- low: number;
8
- close: number;
9
- volume: number;
10
- }
11
-
12
- export interface IndicatorPoint {
13
- time: number;
14
- value: number | number[] | null;
15
- options?: {
16
- color?: string;
17
- offset?: number;
18
- wickcolor?: string;
19
- bordercolor?: string;
20
- };
21
- }
22
-
23
- export type IndicatorStyle =
24
- | 'line'
25
- | 'step'
26
- | 'columns'
27
- | 'histogram'
28
- | 'circles'
29
- | 'cross'
30
- | 'background'
31
- | 'shape'
32
- | 'char'
33
- | 'bar'
34
- | 'candle'
35
- | 'barcolor'
36
- | 'fill';
37
-
38
- export interface IndicatorOptions {
39
- style: IndicatorStyle;
40
- color: string;
41
- overlay?: boolean; // Override indicator-level overlay setting for this specific plot
42
- offset?: number;
43
- linewidth?: number;
44
- smooth?: boolean;
45
- shape?: string;
46
- size?: string;
47
- text?: string;
48
- textcolor?: string;
49
- location?: string;
50
- width?: number;
51
- height?: number;
52
- wickcolor?: string;
53
- bordercolor?: string;
54
- }
55
-
56
- export interface IndicatorPlot {
57
- data?: IndicatorPoint[]; // Optional for fill plots
58
- options: IndicatorOptions;
59
- plot1?: string; // For fill plots: reference to first plot ID
60
- plot2?: string; // For fill plots: reference to second plot ID
61
- }
62
-
63
- // A collection of plots that make up a single indicator (e.g. MACD has macd line, signal line, histogram)
64
- export interface Indicator {
65
- id: string;
66
- plots: { [name: string]: IndicatorPlot };
67
- paneIndex: number;
68
- height?: number; // Desired height in percentage (e.g. 15 for 15%)
69
- collapsed?: boolean;
70
- titleColor?: string;
71
- controls?: {
72
- collapse?: boolean;
73
- maximize?: boolean;
74
- };
75
- }
76
-
77
- export interface QFChartOptions {
78
- title?: string; // Title for the main chart (e.g. "BTC/USDT")
79
- titleColor?: string;
80
- backgroundColor?: string;
81
- upColor?: string;
82
- downColor?: string;
83
- fontColor?: string;
84
- fontFamily?: string;
85
- padding?: number; // Horizontal padding (empty candles on sides), defaults to 0.2
86
- yAxisPadding?: number; // Vertical Y-axis padding in percentage (e.g., 5 = 5% padding), defaults to 5
87
- yAxisMin?: number | 'auto'; // Fixed minimum value for main Y-axis, or 'auto' for dynamic
88
- yAxisMax?: number | 'auto'; // Fixed maximum value for main Y-axis, or 'auto' for dynamic
89
- yAxisLabelFormatter?: (value: number) => string; // Custom formatter function for Y-axis labels
90
- yAxisDecimalPlaces?: number; // Number of decimal places for Y-axis labels. If undefined, auto-detected from data.
91
- lastPriceLine?: {
92
- // Configuration for the horizontal line showing the last price
93
- visible?: boolean;
94
- color?: string; // Defaults to current candle color or '#fff'
95
- lineStyle?: 'solid' | 'dashed' | 'dotted'; // Defaults to 'dashed'
96
- showCountdown?: boolean; // Show countdown to bar close
97
- };
98
- interval?: number; // Bar interval in milliseconds (required for countdown)
99
- height?: string | number;
100
- controls?: {
101
- collapse?: boolean;
102
- maximize?: boolean;
103
- fullscreen?: boolean;
104
- };
105
- dataZoom?: {
106
- visible?: boolean;
107
- position?: 'top' | 'bottom';
108
- height?: number; // height in %, default 6
109
- start?: number; // 0-100, default 50
110
- end?: number; // 0-100, default 100
111
- zoomOnTouch?: boolean; // Enable inside zoom on touch devices, default true
112
- };
113
- databox?: {
114
- position: 'floating' | 'left' | 'right';
115
- triggerOn?: 'mousemove' | 'click' | 'none'; // When to show tooltip/crosshair, default 'mousemove'
116
- };
117
- layout?: {
118
- mainPaneHeight: string; // e.g. "60%"
119
- gap: number; // e.g. 5 (percent)
120
- };
121
- watermark?: boolean; // Default true
122
- }
123
-
124
- // Plugin System Types
125
-
126
- export interface Coordinate {
127
- x: number;
128
- y: number;
129
- }
130
-
131
- export interface DataCoordinate {
132
- timeIndex: number;
133
- value: number;
134
- paneIndex?: number; // Optional pane index
135
- }
136
-
137
- export interface ChartContext {
138
- // Core Access
139
- getChart(): any; // echarts.ECharts instance
140
- getMarketData(): OHLCV[];
141
- getTimeToIndex(): Map<number, number>;
142
- getOptions(): QFChartOptions;
143
-
144
- // Event Bus
145
- events: EventBus;
146
-
147
- // Helpers
148
- coordinateConversion: {
149
- pixelToData: (point: Coordinate) => DataCoordinate | null;
150
- dataToPixel: (point: DataCoordinate) => Coordinate | null;
151
- };
152
-
153
- // Interaction Control
154
- disableTools(): void; // To disable other active tools
155
-
156
- // Zoom Control
157
- setZoom(start: number, end: number): void;
158
-
159
- // Drawing Management
160
- addDrawing(drawing: DrawingElement): void;
161
- removeDrawing(id: string): void;
162
- getDrawing(id: string): DrawingElement | undefined;
163
- updateDrawing(drawing: DrawingElement): void;
164
-
165
- // Interaction Locking
166
- lockChart(): void;
167
- unlockChart(): void;
168
- }
169
-
170
- export type DrawingType = 'line' | 'fibonacci';
171
-
172
- export interface DrawingElement {
173
- id: string;
174
- type: DrawingType;
175
- points: DataCoordinate[]; // [start, end]
176
- paneIndex?: number; // Pane where this drawing belongs (default 0)
177
- style?: {
178
- color?: string;
179
- lineWidth?: number;
180
- };
181
- }
182
-
183
- export interface PluginConfig {
184
- id: string;
185
- name?: string;
186
- icon?: string;
187
- hotkey?: string;
188
- }
189
-
190
- export interface Plugin {
191
- id: string;
192
- name?: string;
193
- icon?: string;
194
-
195
- init(context: ChartContext): void;
196
-
197
- // Called when the tool button is clicked/activated
198
- activate?(): void;
199
-
200
- // Called when the tool is deactivated
201
- deactivate?(): void;
202
-
203
- // Cleanup when plugin is removed
204
- destroy?(): void;
205
- }
1
+ import { EventBus } from './utils/EventBus';
2
+
3
+ export interface OHLCV {
4
+ time: number;
5
+ open: number;
6
+ high: number;
7
+ low: number;
8
+ close: number;
9
+ volume: number;
10
+ }
11
+
12
+ export interface IndicatorPoint {
13
+ time: number;
14
+ value: number | number[] | null;
15
+ options?: {
16
+ color?: string;
17
+ offset?: number;
18
+ wickcolor?: string;
19
+ bordercolor?: string;
20
+ };
21
+ }
22
+
23
+ export type IndicatorStyle =
24
+ | 'line'
25
+ | 'step'
26
+ | 'columns'
27
+ | 'histogram'
28
+ | 'circles'
29
+ | 'cross'
30
+ | 'background'
31
+ | 'shape'
32
+ | 'char'
33
+ | 'bar'
34
+ | 'candle'
35
+ | 'barcolor'
36
+ | 'fill'
37
+ | 'label'
38
+ | 'drawing_line';
39
+
40
+ export interface IndicatorOptions {
41
+ style: IndicatorStyle;
42
+ color: string;
43
+ overlay?: boolean; // Override indicator-level overlay setting for this specific plot
44
+ offset?: number;
45
+ linewidth?: number;
46
+ smooth?: boolean;
47
+ shape?: string;
48
+ size?: string;
49
+ text?: string;
50
+ textcolor?: string;
51
+ location?: string;
52
+ width?: number;
53
+ height?: number;
54
+ wickcolor?: string;
55
+ bordercolor?: string;
56
+ }
57
+
58
+ export interface IndicatorPlot {
59
+ data?: IndicatorPoint[]; // Optional for fill plots
60
+ options: IndicatorOptions;
61
+ plot1?: string; // For fill plots: reference to first plot ID
62
+ plot2?: string; // For fill plots: reference to second plot ID
63
+ }
64
+
65
+ // A collection of plots that make up a single indicator (e.g. MACD has macd line, signal line, histogram)
66
+ export interface Indicator {
67
+ id: string;
68
+ plots: { [name: string]: IndicatorPlot };
69
+ paneIndex: number;
70
+ height?: number; // Desired height in percentage (e.g. 15 for 15%)
71
+ collapsed?: boolean;
72
+ titleColor?: string;
73
+ controls?: {
74
+ collapse?: boolean;
75
+ maximize?: boolean;
76
+ };
77
+ }
78
+
79
+ export interface QFChartOptions {
80
+ title?: string; // Title for the main chart (e.g. "BTC/USDT")
81
+ titleColor?: string;
82
+ backgroundColor?: string;
83
+ upColor?: string;
84
+ downColor?: string;
85
+ fontColor?: string;
86
+ fontFamily?: string;
87
+ padding?: number; // Horizontal padding (empty candles on sides), defaults to 0.2
88
+ yAxisPadding?: number; // Vertical Y-axis padding in percentage (e.g., 5 = 5% padding), defaults to 5
89
+ yAxisMin?: number | 'auto'; // Fixed minimum value for main Y-axis, or 'auto' for dynamic
90
+ yAxisMax?: number | 'auto'; // Fixed maximum value for main Y-axis, or 'auto' for dynamic
91
+ yAxisLabelFormatter?: (value: number) => string; // Custom formatter function for Y-axis labels
92
+ yAxisDecimalPlaces?: number; // Number of decimal places for Y-axis labels. If undefined, auto-detected from data.
93
+ lastPriceLine?: {
94
+ // Configuration for the horizontal line showing the last price
95
+ visible?: boolean;
96
+ color?: string; // Defaults to current candle color or '#fff'
97
+ lineStyle?: 'solid' | 'dashed' | 'dotted'; // Defaults to 'dashed'
98
+ showCountdown?: boolean; // Show countdown to bar close
99
+ };
100
+ interval?: number; // Bar interval in milliseconds (required for countdown)
101
+ height?: string | number;
102
+ controls?: {
103
+ collapse?: boolean;
104
+ maximize?: boolean;
105
+ fullscreen?: boolean;
106
+ };
107
+ dataZoom?: {
108
+ visible?: boolean;
109
+ position?: 'top' | 'bottom';
110
+ height?: number; // height in %, default 6
111
+ start?: number; // 0-100, default 50
112
+ end?: number; // 0-100, default 100
113
+ zoomOnTouch?: boolean; // Enable inside zoom on touch devices, default true
114
+ };
115
+ databox?: {
116
+ position: 'floating' | 'left' | 'right';
117
+ triggerOn?: 'mousemove' | 'click' | 'none'; // When to show tooltip/crosshair, default 'mousemove'
118
+ };
119
+ layout?: {
120
+ mainPaneHeight: string; // e.g. "60%"
121
+ gap: number; // e.g. 5 (percent)
122
+ };
123
+ watermark?: boolean; // Default true
124
+ }
125
+
126
+ // Plugin System Types
127
+
128
+ export interface Coordinate {
129
+ x: number;
130
+ y: number;
131
+ }
132
+
133
+ export interface DataCoordinate {
134
+ timeIndex: number;
135
+ value: number;
136
+ paneIndex?: number; // Optional pane index
137
+ }
138
+
139
+ export interface ChartContext {
140
+ // Core Access
141
+ getChart(): any; // echarts.ECharts instance
142
+ getMarketData(): OHLCV[];
143
+ getTimeToIndex(): Map<number, number>;
144
+ getOptions(): QFChartOptions;
145
+
146
+ // Event Bus
147
+ events: EventBus;
148
+
149
+ // Helpers
150
+ coordinateConversion: {
151
+ pixelToData: (point: Coordinate) => DataCoordinate | null;
152
+ dataToPixel: (point: DataCoordinate) => Coordinate | null;
153
+ };
154
+
155
+ // Interaction Control
156
+ disableTools(): void; // To disable other active tools
157
+
158
+ // Zoom Control
159
+ setZoom(start: number, end: number): void;
160
+
161
+ // Drawing Management
162
+ addDrawing(drawing: DrawingElement): void;
163
+ removeDrawing(id: string): void;
164
+ getDrawing(id: string): DrawingElement | undefined;
165
+ updateDrawing(drawing: DrawingElement): void;
166
+
167
+ // Interaction Locking
168
+ lockChart(): void;
169
+ unlockChart(): void;
170
+ }
171
+
172
+ export type DrawingType = 'line' | 'fibonacci';
173
+
174
+ export interface DrawingElement {
175
+ id: string;
176
+ type: DrawingType;
177
+ points: DataCoordinate[]; // [start, end]
178
+ paneIndex?: number; // Pane where this drawing belongs (default 0)
179
+ style?: {
180
+ color?: string;
181
+ lineWidth?: number;
182
+ };
183
+ }
184
+
185
+ export interface PluginConfig {
186
+ id: string;
187
+ name?: string;
188
+ icon?: string;
189
+ hotkey?: string;
190
+ }
191
+
192
+ export interface Plugin {
193
+ id: string;
194
+ name?: string;
195
+ icon?: string;
196
+
197
+ init(context: ChartContext): void;
198
+
199
+ // Called when the tool button is clicked/activated
200
+ activate?(): void;
201
+
202
+ // Called when the tool is deactivated
203
+ deactivate?(): void;
204
+
205
+ // Cleanup when plugin is removed
206
+ destroy?(): void;
207
+ }