@qfo/qfchart 0.6.4 → 0.6.6
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/README.md +1 -0
- package/dist/qfchart.min.browser.js +15 -15
- package/dist/qfchart.min.es.js +15 -15
- package/package.json +1 -1
- package/src/QFChart.ts +9 -6
- package/src/components/LayoutManager.ts +53 -49
- package/src/components/SeriesBuilder.ts +250 -941
- package/src/components/SeriesRendererFactory.ts +36 -0
- package/src/components/renderers/BackgroundRenderer.ts +47 -0
- package/src/components/renderers/FillRenderer.ts +99 -0
- package/src/components/renderers/HistogramRenderer.ts +20 -0
- package/src/components/renderers/LineRenderer.ts +44 -0
- package/src/components/renderers/OHLCBarRenderer.ts +161 -0
- package/src/components/renderers/ScatterRenderer.ts +54 -0
- package/src/components/renderers/SeriesRenderer.ts +20 -0
- package/src/components/renderers/ShapeRenderer.ts +121 -0
- package/src/components/renderers/StepRenderer.ts +39 -0
- package/src/types.ts +205 -205
- package/src/utils/AxisUtils.ts +63 -0
- package/src/utils/ColorUtils.ts +32 -0
- package/src/utils/ShapeUtils.ts +140 -0
- /package/src/{Utils.ts → utils/CanvasUtils.ts} +0 -0
package/src/types.ts
CHANGED
|
@@ -1,205 +1,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
|
-
|
|
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
|
|
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
|
+
|
|
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
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { OHLCV } from '../types';
|
|
2
|
+
|
|
3
|
+
export class AxisUtils {
|
|
4
|
+
// Create min/max functions that apply padding
|
|
5
|
+
public static createMinFunction(paddingPercent: number) {
|
|
6
|
+
return (value: { min: number; max: number }) => {
|
|
7
|
+
const range = value.max - value.min;
|
|
8
|
+
const padding = range * (paddingPercent / 100);
|
|
9
|
+
return value.min - padding;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public static createMaxFunction(paddingPercent: number) {
|
|
14
|
+
return (value: { min: number; max: number }) => {
|
|
15
|
+
const range = value.max - value.min;
|
|
16
|
+
const padding = range * (paddingPercent / 100);
|
|
17
|
+
return value.max + padding;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Auto-detect the appropriate number of decimal places for price display
|
|
23
|
+
* based on actual market data values.
|
|
24
|
+
*
|
|
25
|
+
* For prices like BTCUSDC (~97000), returns 2.
|
|
26
|
+
* For prices like PUMPUSDT (~0.002), returns 6.
|
|
27
|
+
*
|
|
28
|
+
* The algorithm examines a representative close price and determines
|
|
29
|
+
* how many decimals are needed to show meaningful precision.
|
|
30
|
+
*/
|
|
31
|
+
public static autoDetectDecimals(marketData: OHLCV[]): number {
|
|
32
|
+
if (!marketData || marketData.length === 0) return 2;
|
|
33
|
+
|
|
34
|
+
// Use the last close price as the representative value
|
|
35
|
+
const price = marketData[marketData.length - 1].close;
|
|
36
|
+
|
|
37
|
+
if (price === 0 || !isFinite(price) || isNaN(price)) return 2;
|
|
38
|
+
|
|
39
|
+
const absPrice = Math.abs(price);
|
|
40
|
+
|
|
41
|
+
// For prices >= 1, use 2 decimals (e.g. 97000.12, 1.45)
|
|
42
|
+
if (absPrice >= 1) return 2;
|
|
43
|
+
|
|
44
|
+
// For prices < 1, count leading zeros after the decimal point
|
|
45
|
+
// and add 4 extra digits for meaningful precision (increased from 2).
|
|
46
|
+
// e.g. 0.002119 -> 3 leading zeros -> 3 + 4 = 7
|
|
47
|
+
// We cap at 10 to avoid excessive precision.
|
|
48
|
+
const leadingZeros = Math.ceil(-Math.log10(absPrice));
|
|
49
|
+
return Math.min(leadingZeros + 4, 10);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Format a numeric value with the given number of decimal places.
|
|
54
|
+
* This is the centralized formatting function used by Y-axis labels,
|
|
55
|
+
* markLine labels, and countdown labels.
|
|
56
|
+
*/
|
|
57
|
+
public static formatValue(value: number, decimals: number): string {
|
|
58
|
+
if (typeof value === 'number') {
|
|
59
|
+
return value.toFixed(decimals);
|
|
60
|
+
}
|
|
61
|
+
return String(value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class ColorUtils {
|
|
2
|
+
/**
|
|
3
|
+
* Parse color string and extract opacity
|
|
4
|
+
* Supports: hex (#RRGGBB), named colors (green, red), rgba(r,g,b,a), rgb(r,g,b)
|
|
5
|
+
*/
|
|
6
|
+
public static parseColor(colorStr: string): { color: string; opacity: number } {
|
|
7
|
+
if (!colorStr) {
|
|
8
|
+
return { color: '#888888', opacity: 0.2 };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Check for rgba format
|
|
12
|
+
const rgbaMatch = colorStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
|
13
|
+
if (rgbaMatch) {
|
|
14
|
+
const r = rgbaMatch[1];
|
|
15
|
+
const g = rgbaMatch[2];
|
|
16
|
+
const b = rgbaMatch[3];
|
|
17
|
+
const a = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1;
|
|
18
|
+
|
|
19
|
+
// Return rgb color and separate opacity
|
|
20
|
+
return {
|
|
21
|
+
color: `rgb(${r},${g},${b})`,
|
|
22
|
+
opacity: a,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// For hex or named colors, default opacity to 0.3 for fill areas
|
|
27
|
+
return {
|
|
28
|
+
color: colorStr,
|
|
29
|
+
opacity: 0.3,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export class ShapeUtils {
|
|
2
|
+
public static getShapeSymbol(shape: string): string {
|
|
3
|
+
// SVG Paths need to be:
|
|
4
|
+
// 1. Valid SVG path data strings
|
|
5
|
+
// 2. Ideally centered around the origin or a standard box (e.g., 0 0 24 24)
|
|
6
|
+
// 3. ECharts path:// format expects just the path data usually, but complex shapes might need 'image://' or better paths.
|
|
7
|
+
// For simple shapes, standard ECharts symbols or simple paths work.
|
|
8
|
+
|
|
9
|
+
switch (shape) {
|
|
10
|
+
case 'arrowdown':
|
|
11
|
+
// Blocky arrow down
|
|
12
|
+
return 'path://M12 24l-12-12h8v-12h8v12h8z';
|
|
13
|
+
|
|
14
|
+
case 'arrowup':
|
|
15
|
+
// Blocky arrow up
|
|
16
|
+
return 'path://M12 0l12 12h-8v12h-8v-12h-8z';
|
|
17
|
+
|
|
18
|
+
case 'circle':
|
|
19
|
+
return 'circle';
|
|
20
|
+
|
|
21
|
+
case 'cross':
|
|
22
|
+
// Plus sign (+)
|
|
23
|
+
return 'path://M11 2h2v9h9v2h-9v9h-2v-9h-9v-2h9z';
|
|
24
|
+
|
|
25
|
+
case 'diamond':
|
|
26
|
+
return 'diamond'; // Built-in
|
|
27
|
+
|
|
28
|
+
case 'flag':
|
|
29
|
+
// Flag on a pole
|
|
30
|
+
return 'path://M6 2v20h2v-8h12l-2-6 2-6h-12z';
|
|
31
|
+
|
|
32
|
+
case 'labeldown':
|
|
33
|
+
// Bubble pointing down: Rounded rect with a triangle at bottom
|
|
34
|
+
return 'path://M4 2h16a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-6l-2 4l-2 -4h-6a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2z';
|
|
35
|
+
|
|
36
|
+
case 'labelup':
|
|
37
|
+
// Bubble pointing up: Rounded rect with triangle at top
|
|
38
|
+
return 'path://M12 2l2 4h6a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-16a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h6z';
|
|
39
|
+
|
|
40
|
+
case 'square':
|
|
41
|
+
return 'rect';
|
|
42
|
+
|
|
43
|
+
case 'triangledown':
|
|
44
|
+
// Pointing down
|
|
45
|
+
return 'path://M12 21l-10-18h20z';
|
|
46
|
+
|
|
47
|
+
case 'triangleup':
|
|
48
|
+
// Pointing up
|
|
49
|
+
return 'triangle'; // Built-in is pointing up
|
|
50
|
+
|
|
51
|
+
case 'xcross':
|
|
52
|
+
// 'X' shape
|
|
53
|
+
return 'path://M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z';
|
|
54
|
+
|
|
55
|
+
default:
|
|
56
|
+
return 'circle';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public static getShapeRotation(shape: string): number {
|
|
61
|
+
// With custom paths defined above, we might not need rotation unless we reuse shapes.
|
|
62
|
+
// Built-in triangle is UP.
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public static getShapeSize(size: string, width?: number, height?: number): number | number[] {
|
|
67
|
+
// If both width and height are specified, use them directly
|
|
68
|
+
if (width !== undefined && height !== undefined) {
|
|
69
|
+
return [width, height];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Base size from the size parameter
|
|
73
|
+
let baseSize: number;
|
|
74
|
+
switch (size) {
|
|
75
|
+
case 'tiny':
|
|
76
|
+
baseSize = 8;
|
|
77
|
+
break;
|
|
78
|
+
case 'small':
|
|
79
|
+
baseSize = 12;
|
|
80
|
+
break;
|
|
81
|
+
case 'normal':
|
|
82
|
+
case 'auto':
|
|
83
|
+
baseSize = 16;
|
|
84
|
+
break;
|
|
85
|
+
case 'large':
|
|
86
|
+
baseSize = 24;
|
|
87
|
+
break;
|
|
88
|
+
case 'huge':
|
|
89
|
+
baseSize = 32;
|
|
90
|
+
break;
|
|
91
|
+
default:
|
|
92
|
+
baseSize = 16;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// If only width is specified, preserve aspect ratio (assume square default)
|
|
96
|
+
if (width !== undefined) {
|
|
97
|
+
return [width, width];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// If only height is specified, preserve aspect ratio (assume square default)
|
|
101
|
+
if (height !== undefined) {
|
|
102
|
+
return [height, height];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Default uniform size
|
|
106
|
+
return baseSize;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Helper to determine label position and distance relative to shape BASED ON LOCATION
|
|
110
|
+
public static getLabelConfig(shape: string, location: string): { position: string; distance: number } {
|
|
111
|
+
// Text position should be determined by location, not shape direction
|
|
112
|
+
|
|
113
|
+
switch (location) {
|
|
114
|
+
case 'abovebar':
|
|
115
|
+
// Shape is above the candle, text should be above the shape
|
|
116
|
+
return { position: 'top', distance: 5 };
|
|
117
|
+
|
|
118
|
+
case 'belowbar':
|
|
119
|
+
// Shape is below the candle, text should be below the shape
|
|
120
|
+
return { position: 'bottom', distance: 5 };
|
|
121
|
+
|
|
122
|
+
case 'top':
|
|
123
|
+
// Shape at top of chart, text below it
|
|
124
|
+
return { position: 'bottom', distance: 5 };
|
|
125
|
+
|
|
126
|
+
case 'bottom':
|
|
127
|
+
// Shape at bottom of chart, text above it
|
|
128
|
+
return { position: 'top', distance: 5 };
|
|
129
|
+
|
|
130
|
+
case 'absolute':
|
|
131
|
+
default:
|
|
132
|
+
// For labelup/down, text is INSIDE the shape
|
|
133
|
+
if (shape === 'labelup' || shape === 'labeldown') {
|
|
134
|
+
return { position: 'inside', distance: 0 };
|
|
135
|
+
}
|
|
136
|
+
// For other shapes, text above by default
|
|
137
|
+
return { position: 'top', distance: 5 };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
File without changes
|