@qfo/qfchart 0.8.2 → 0.8.4

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,54 +1,92 @@
1
- import { SeriesRenderer, RenderContext } from './SeriesRenderer';
2
- import { textToBase64Image } from '../../utils/CanvasUtils';
3
-
4
- export class ScatterRenderer implements SeriesRenderer {
5
- render(context: RenderContext): any {
6
- const { seriesName, xAxisIndex, yAxisIndex, dataArray, colorArray, plotOptions } = context;
7
- const defaultColor = '#2962ff';
8
- const style = plotOptions.style; // 'circles', 'cross', 'char'
9
-
10
- // Special handling for invisible 'char' style
11
- if (style === 'char') {
12
- return {
13
- name: seriesName,
14
- type: 'scatter',
15
- xAxisIndex: xAxisIndex,
16
- yAxisIndex: yAxisIndex,
17
- symbolSize: 0, // Invisible
18
- data: dataArray.map((val, i) => ({
19
- value: [i, val],
20
- itemStyle: { opacity: 0 },
21
- })),
22
- silent: true, // No interaction
23
- };
24
- }
25
-
26
- const scatterData = dataArray
27
- .map((val, i) => {
28
- if (val === null) return null;
29
- const pointColor = colorArray[i] || plotOptions.color || defaultColor;
30
- const item: any = {
31
- value: [i, val],
32
- itemStyle: { color: pointColor },
33
- };
34
-
35
- if (style === 'cross') {
36
- item.symbol = `image://${textToBase64Image('+', pointColor, '24px')}`;
37
- item.symbolSize = 16;
38
- } else {
39
- item.symbol = 'circle';
40
- item.symbolSize = 6;
41
- }
42
- return item;
43
- })
44
- .filter((item) => item !== null);
45
-
46
- return {
47
- name: seriesName,
48
- type: 'scatter',
49
- xAxisIndex: xAxisIndex,
50
- yAxisIndex: yAxisIndex,
51
- data: scatterData,
52
- };
53
- }
54
- }
1
+ import { SeriesRenderer, RenderContext } from './SeriesRenderer';
2
+ import { textToBase64Image } from '../../utils/CanvasUtils';
3
+
4
+ export class ScatterRenderer implements SeriesRenderer {
5
+ render(context: RenderContext): any {
6
+ const { seriesName, xAxisIndex, yAxisIndex, dataArray, colorArray, plotOptions } = context;
7
+ const defaultColor = '#2962ff';
8
+ const style = plotOptions.style; // 'circles', 'cross', 'char'
9
+
10
+ // plotchar: render the Unicode character at the data point
11
+ if (style === 'char') {
12
+ const { optionsArray, candlestickData } = context;
13
+ const defaultChar = plotOptions.char || '•';
14
+ const defaultLocation = plotOptions.location || 'abovebar';
15
+
16
+ const charData = dataArray
17
+ .map((val, i) => {
18
+ if (val === null || val === undefined || (typeof val === 'number' && isNaN(val))) return null;
19
+
20
+ const pointOpts = optionsArray?.[i] || {};
21
+ const char = pointOpts.char || defaultChar;
22
+ const color = pointOpts.color || colorArray[i] || plotOptions.color || defaultColor;
23
+ const location = pointOpts.location || defaultLocation;
24
+ const size = pointOpts.size || plotOptions.size || 'normal';
25
+
26
+ // Positioning based on location
27
+ let yValue = val;
28
+ let symbolOffset: (string | number)[] = [0, 0];
29
+
30
+ if (location === 'abovebar' || location === 'AboveBar' || location === 'ab') {
31
+ if (candlestickData && candlestickData[i]) yValue = candlestickData[i].high;
32
+ symbolOffset = [0, '-150%'];
33
+ } else if (location === 'belowbar' || location === 'BelowBar' || location === 'bl') {
34
+ if (candlestickData && candlestickData[i]) yValue = candlestickData[i].low;
35
+ symbolOffset = [0, '150%'];
36
+ }
37
+ // absolute / top / bottom: yValue stays as-is
38
+
39
+ // Size mapping — matches TradingView's plotchar sizing
40
+ const sizeMap: Record<string, string> = {
41
+ tiny: '18px', small: '26px', normal: '34px', large: '42px', huge: '54px', auto: '28px',
42
+ };
43
+ const fontSize = sizeMap[size] || '34px';
44
+
45
+ return {
46
+ value: [i, yValue],
47
+ symbol: `image://${textToBase64Image(char, color, fontSize)}`,
48
+ symbolSize: parseInt(fontSize) + 8,
49
+ symbolOffset: symbolOffset,
50
+ };
51
+ })
52
+ .filter((item) => item !== null);
53
+
54
+ return {
55
+ name: seriesName,
56
+ type: 'scatter',
57
+ xAxisIndex: xAxisIndex,
58
+ yAxisIndex: yAxisIndex,
59
+ z: 10, // Render in front of candles
60
+ data: charData,
61
+ };
62
+ }
63
+
64
+ const scatterData = dataArray
65
+ .map((val, i) => {
66
+ if (val === null) return null;
67
+ const pointColor = colorArray[i] || plotOptions.color || defaultColor;
68
+ const item: any = {
69
+ value: [i, val],
70
+ itemStyle: { color: pointColor },
71
+ };
72
+
73
+ if (style === 'cross') {
74
+ item.symbol = `image://${textToBase64Image('+', pointColor, '24px')}`;
75
+ item.symbolSize = 16;
76
+ } else {
77
+ item.symbol = 'circle';
78
+ item.symbolSize = 6;
79
+ }
80
+ return item;
81
+ })
82
+ .filter((item) => item !== null);
83
+
84
+ return {
85
+ name: seriesName,
86
+ type: 'scatter',
87
+ xAxisIndex: xAxisIndex,
88
+ yAxisIndex: yAxisIndex,
89
+ data: scatterData,
90
+ };
91
+ }
92
+ }
@@ -40,6 +40,8 @@ export class ShapeRenderer implements SeriesRenderer {
40
40
  // Positioning based on location
41
41
  let yValue = val; // Default to absolute value
42
42
  let symbolOffset: (string | number)[] = [0, 0];
43
+ const isLabelUp = shape.includes('label_up') || shape === 'labelup';
44
+ const isLabelDown = shape.includes('label_down') || shape === 'labeldown';
43
45
 
44
46
  if (location === 'abovebar' || location === 'AboveBar' || location === 'ab') {
45
47
  // Shape above the candle
@@ -83,6 +85,15 @@ export class ShapeRenderer implements SeriesRenderer {
83
85
  }
84
86
  }
85
87
 
88
+ // Anchor labelup/labeldown so the arrow tip sits at the data point.
89
+ // labelup: arrow tip points up (at top of shape) → shift shape down
90
+ // labeldown: arrow tip points down (at bottom of shape) → shift shape up
91
+ if (isLabelUp) {
92
+ symbolOffset = [symbolOffset[0], '50%'];
93
+ } else if (isLabelDown) {
94
+ symbolOffset = [symbolOffset[0], '-50%'];
95
+ }
96
+
86
97
  // Get label configuration based on location
87
98
  const labelConfig = ShapeUtils.getLabelConfig(shape, location);
88
99
 
@@ -115,6 +126,7 @@ export class ShapeRenderer implements SeriesRenderer {
115
126
  type: 'scatter',
116
127
  xAxisIndex: xAxisIndex,
117
128
  yAxisIndex: yAxisIndex,
129
+ z: 10, // Render shapes in front of candles (z: 5)
118
130
  data: shapeData,
119
131
  };
120
132
  }