@qfo/qfchart 0.6.8 → 0.7.1

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.
@@ -1,121 +1,121 @@
1
- import { SeriesRenderer, RenderContext } from './SeriesRenderer';
2
- import { ShapeUtils } from '../../utils/ShapeUtils';
3
-
4
- export class ShapeRenderer implements SeriesRenderer {
5
- render(context: RenderContext): any {
6
- const { seriesName, xAxisIndex, yAxisIndex, dataArray, colorArray, optionsArray, plotOptions, candlestickData } = context;
7
- const defaultColor = '#2962ff';
8
-
9
- const shapeData = dataArray
10
- .map((val, i) => {
11
- // Merge global options with per-point options to get location first
12
- const pointOpts = optionsArray[i] || {};
13
- const globalOpts = plotOptions;
14
- const location = pointOpts.location || globalOpts.location || 'absolute';
15
-
16
- // For location="absolute", always draw the shape (ignore value)
17
- // For other locations, only draw if value is truthy (TradingView behavior)
18
- if (location !== 'absolute' && !val) {
19
- return null;
20
- }
21
-
22
- // If we get here and val is null/undefined, it means location is absolute
23
- // In that case, we still need a valid value for positioning
24
- // Use the value if it exists, otherwise we'd need a fallback
25
- // But in TradingView, absolute location still expects a value for Y position
26
- if (val === null || val === undefined) {
27
- return null; // Can't plot without a Y coordinate
28
- }
29
-
30
- const color = pointOpts.color || globalOpts.color || defaultColor;
31
- const shape = pointOpts.shape || globalOpts.shape || 'circle';
32
- const size = pointOpts.size || globalOpts.size || 'normal';
33
- const text = pointOpts.text || globalOpts.text;
34
- const textColor = pointOpts.textcolor || globalOpts.textcolor || 'white';
35
-
36
- // NEW: Get width and height
37
- const width = pointOpts.width || globalOpts.width;
38
- const height = pointOpts.height || globalOpts.height;
39
-
40
- // Positioning based on location
41
- let yValue = val; // Default to absolute value
42
- let symbolOffset: (string | number)[] = [0, 0];
43
-
44
- if (location === 'abovebar') {
45
- // Shape above the candle
46
- if (candlestickData && candlestickData[i]) {
47
- yValue = candlestickData[i].high;
48
- }
49
- symbolOffset = [0, '-150%']; // Shift up
50
- } else if (location === 'belowbar') {
51
- // Shape below the candle
52
- if (candlestickData && candlestickData[i]) {
53
- yValue = candlestickData[i].low;
54
- }
55
- symbolOffset = [0, '150%']; // Shift down
56
- } else if (location === 'top') {
57
- // Shape at top of chart - we need to use a very high value
58
- // This would require knowing the y-axis max, which we don't have here easily
59
- // For now, use a placeholder approach - might need to calculate from data
60
- // Or we can use a percentage of the viewport? ECharts doesn't support that directly in scatter.
61
- // Best approach: use a large multiplier of current value or track max
62
- // Simplified: use coordinate system max (will need enhancement)
63
- yValue = val; // For now, keep absolute - would need axis max
64
- symbolOffset = [0, 0];
65
- } else if (location === 'bottom') {
66
- // Shape at bottom of chart
67
- yValue = val; // For now, keep absolute - would need axis min
68
- symbolOffset = [0, 0];
69
- }
70
-
71
- const symbol = ShapeUtils.getShapeSymbol(shape);
72
- const symbolSize = ShapeUtils.getShapeSize(size, width, height);
73
- const rotate = ShapeUtils.getShapeRotation(shape);
74
-
75
- // Special handling for labelup/down sizing - they contain text so they should be larger
76
- let finalSize: number | number[] = symbolSize;
77
- if (shape.includes('label')) {
78
- // If custom size, scale it up for labels
79
- if (Array.isArray(symbolSize)) {
80
- finalSize = [symbolSize[0] * 2.5, symbolSize[1] * 2.5];
81
- } else {
82
- finalSize = symbolSize * 2.5;
83
- }
84
- }
85
-
86
- // Get label configuration based on location
87
- const labelConfig = ShapeUtils.getLabelConfig(shape, location);
88
-
89
- const item: any = {
90
- value: [i, yValue],
91
- symbol: symbol,
92
- symbolSize: finalSize,
93
- symbolRotate: rotate,
94
- symbolOffset: symbolOffset,
95
- itemStyle: {
96
- color: color,
97
- },
98
- label: {
99
- show: !!text,
100
- position: labelConfig.position,
101
- distance: labelConfig.distance,
102
- formatter: text,
103
- color: textColor,
104
- fontSize: 10,
105
- fontWeight: 'bold',
106
- },
107
- };
108
-
109
- return item;
110
- })
111
- .filter((item) => item !== null);
112
-
113
- return {
114
- name: seriesName,
115
- type: 'scatter',
116
- xAxisIndex: xAxisIndex,
117
- yAxisIndex: yAxisIndex,
118
- data: shapeData,
119
- };
120
- }
121
- }
1
+ import { SeriesRenderer, RenderContext } from './SeriesRenderer';
2
+ import { ShapeUtils } from '../../utils/ShapeUtils';
3
+
4
+ export class ShapeRenderer implements SeriesRenderer {
5
+ render(context: RenderContext): any {
6
+ const { seriesName, xAxisIndex, yAxisIndex, dataArray, colorArray, optionsArray, plotOptions, candlestickData } = context;
7
+ const defaultColor = '#2962ff';
8
+
9
+ const shapeData = dataArray
10
+ .map((val, i) => {
11
+ // Merge global options with per-point options to get location first
12
+ const pointOpts = optionsArray[i] || {};
13
+ const globalOpts = plotOptions;
14
+ const location = pointOpts.location || globalOpts.location || 'absolute';
15
+
16
+ // For location="absolute", always draw the shape (ignore value)
17
+ // For other locations, only draw if value is truthy (TradingView behavior)
18
+ if (location !== 'absolute' && location !== 'Absolute' && !val) {
19
+ return null;
20
+ }
21
+
22
+ // If we get here and val is null/undefined, it means location is absolute
23
+ // In that case, we still need a valid value for positioning
24
+ // Use the value if it exists, otherwise we'd need a fallback
25
+ // But in TradingView, absolute location still expects a value for Y position
26
+ if (val === null || val === undefined) {
27
+ return null; // Can't plot without a Y coordinate
28
+ }
29
+
30
+ const color = pointOpts.color || globalOpts.color || defaultColor;
31
+ const shape = pointOpts.shape || globalOpts.shape || 'circle';
32
+ const size = pointOpts.size || globalOpts.size || 'normal';
33
+ const text = pointOpts.text || globalOpts.text;
34
+ const textColor = pointOpts.textcolor || globalOpts.textcolor || 'white';
35
+
36
+ // NEW: Get width and height
37
+ const width = pointOpts.width || globalOpts.width;
38
+ const height = pointOpts.height || globalOpts.height;
39
+
40
+ // Positioning based on location
41
+ let yValue = val; // Default to absolute value
42
+ let symbolOffset: (string | number)[] = [0, 0];
43
+
44
+ if (location === 'abovebar' || location === 'AboveBar' || location === 'ab') {
45
+ // Shape above the candle
46
+ if (candlestickData && candlestickData[i]) {
47
+ yValue = candlestickData[i].high;
48
+ }
49
+ symbolOffset = [0, '-150%']; // Shift up
50
+ } else if (location === 'belowbar' || location === 'BelowBar' || location === 'bl') {
51
+ // Shape below the candle
52
+ if (candlestickData && candlestickData[i]) {
53
+ yValue = candlestickData[i].low;
54
+ }
55
+ symbolOffset = [0, '150%']; // Shift down
56
+ } else if (location === 'top' || location === 'Top') {
57
+ // Shape at top of chart - we need to use a very high value
58
+ // This would require knowing the y-axis max, which we don't have here easily
59
+ // For now, use a placeholder approach - might need to calculate from data
60
+ // Or we can use a percentage of the viewport? ECharts doesn't support that directly in scatter.
61
+ // Best approach: use a large multiplier of current value or track max
62
+ // Simplified: use coordinate system max (will need enhancement)
63
+ yValue = val; // For now, keep absolute - would need axis max
64
+ symbolOffset = [0, 0];
65
+ } else if (location === 'bottom' || location === 'Bottom') {
66
+ // Shape at bottom of chart
67
+ yValue = val; // For now, keep absolute - would need axis min
68
+ symbolOffset = [0, 0];
69
+ }
70
+
71
+ const symbol = ShapeUtils.getShapeSymbol(shape);
72
+ const symbolSize = ShapeUtils.getShapeSize(size, width, height);
73
+ const rotate = ShapeUtils.getShapeRotation(shape);
74
+
75
+ // Special handling for labelup/down sizing - they contain text so they should be larger
76
+ let finalSize: number | number[] = symbolSize;
77
+ if (shape.includes('label')) {
78
+ // If custom size, scale it up for labels
79
+ if (Array.isArray(symbolSize)) {
80
+ finalSize = [symbolSize[0] * 2.5, symbolSize[1] * 2.5];
81
+ } else {
82
+ finalSize = symbolSize * 2.5;
83
+ }
84
+ }
85
+
86
+ // Get label configuration based on location
87
+ const labelConfig = ShapeUtils.getLabelConfig(shape, location);
88
+
89
+ const item: any = {
90
+ value: [i, yValue],
91
+ symbol: symbol,
92
+ symbolSize: finalSize,
93
+ symbolRotate: rotate,
94
+ symbolOffset: symbolOffset,
95
+ itemStyle: {
96
+ color: color,
97
+ },
98
+ label: {
99
+ show: !!text,
100
+ position: labelConfig.position,
101
+ distance: labelConfig.distance,
102
+ formatter: text,
103
+ color: textColor,
104
+ fontSize: 10,
105
+ fontWeight: 'bold',
106
+ },
107
+ };
108
+
109
+ return item;
110
+ })
111
+ .filter((item) => item !== null);
112
+
113
+ return {
114
+ name: seriesName,
115
+ type: 'scatter',
116
+ xAxisIndex: xAxisIndex,
117
+ yAxisIndex: yAxisIndex,
118
+ data: shapeData,
119
+ };
120
+ }
121
+ }
@@ -8,56 +8,59 @@ export class ShapeUtils {
8
8
 
9
9
  switch (shape) {
10
10
  case 'arrowdown':
11
- // Blocky arrow down
11
+ case 'shape_arrow_down':
12
12
  return 'path://M12 24l-12-12h8v-12h8v12h8z';
13
13
 
14
14
  case 'arrowup':
15
- // Blocky arrow up
15
+ case 'shape_arrow_up':
16
16
  return 'path://M12 0l12 12h-8v12h-8v-12h-8z';
17
17
 
18
18
  case 'circle':
19
+ case 'shape_circle':
19
20
  return 'circle';
20
21
 
21
22
  case 'cross':
22
- // Plus sign (+)
23
+ case 'shape_cross':
23
24
  return 'path://M11 2h2v9h9v2h-9v9h-2v-9h-9v-2h9z';
24
25
 
25
26
  case 'diamond':
26
- return 'diamond'; // Built-in
27
+ case 'shape_diamond':
28
+ return 'diamond';
27
29
 
28
30
  case 'flag':
29
- // Flag on a pole
31
+ case 'shape_flag':
30
32
  return 'path://M6 2v20h2v-8h12l-2-6 2-6h-12z';
31
33
 
32
34
  case 'labeldown':
33
- // Bubble pointing down: rect with small triangle at bottom center
35
+ case 'shape_label_down':
34
36
  return 'path://M2 1h20a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-8l-2 3-2-3h-8a1 1 0 0 1-1-1v-14a1 1 0 0 1 1-1z';
35
37
 
36
38
  case 'labelleft':
37
- // Bubble with small pointer on the left side (pointing left)
39
+ case 'shape_label_left':
38
40
  return 'path://M0 10l3-3v-5a1 1 0 0 1 1-1h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1h-18a1 1 0 0 1-1-1v-5z';
39
41
 
40
42
  case 'labelright':
41
- // Bubble with small pointer on the right side (pointing right)
43
+ case 'shape_label_right':
42
44
  return 'path://M24 10l-3-3v-5a1 1 0 0 0-1-1h-18a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h18a1 1 0 0 0 1-1v-5z';
43
45
 
44
46
  case 'labelup':
45
- // Bubble pointing up: small triangle at top, rect below
47
+ case 'shape_label_up':
46
48
  return 'path://M12 1l2 3h8a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1h-20a1 1 0 0 1-1-1v-14a1 1 0 0 1 1-1h8z';
47
49
 
48
50
  case 'square':
51
+ case 'shape_square':
49
52
  return 'rect';
50
53
 
51
54
  case 'triangledown':
52
- // Pointing down
55
+ case 'shape_triangle_down':
53
56
  return 'path://M12 21l-10-18h20z';
54
57
 
55
58
  case 'triangleup':
56
- // Pointing up
57
- return 'triangle'; // Built-in is pointing up
59
+ case 'shape_triangle_up':
60
+ return 'triangle';
58
61
 
59
62
  case 'xcross':
60
- // 'X' shape
63
+ case 'shape_xcross':
61
64
  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';
62
65
 
63
66
  default:
@@ -120,25 +123,30 @@ export class ShapeUtils {
120
123
 
121
124
  switch (location) {
122
125
  case 'abovebar':
126
+ case 'AboveBar':
123
127
  // Shape is above the candle, text should be above the shape
124
128
  return { position: 'top', distance: 5 };
125
129
 
126
130
  case 'belowbar':
131
+ case 'BelowBar':
127
132
  // Shape is below the candle, text should be below the shape
128
133
  return { position: 'bottom', distance: 5 };
129
134
 
130
135
  case 'top':
136
+ case 'Top':
131
137
  // Shape at top of chart, text below it
132
138
  return { position: 'bottom', distance: 5 };
133
139
 
134
140
  case 'bottom':
141
+ case 'Bottom':
135
142
  // Shape at bottom of chart, text above it
136
143
  return { position: 'top', distance: 5 };
137
144
 
138
145
  case 'absolute':
146
+ case 'Absolute':
139
147
  default:
140
148
  // For labelup/down, text is INSIDE the shape
141
- if (shape === 'labelup' || shape === 'labeldown') {
149
+ if (shape === 'labelup' || shape === 'labeldown' || shape === 'shape_label_up' || shape === 'shape_label_down') {
142
150
  return { position: 'inside', distance: 0 };
143
151
  }
144
152
  // For other shapes, text above by default