@qfo/qfchart 0.7.2 → 0.7.3

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,20 +1,67 @@
1
- import { SeriesRenderer, RenderContext } from './SeriesRenderer';
2
-
3
- export class HistogramRenderer implements SeriesRenderer {
4
- render(context: RenderContext): any {
5
- const { seriesName, xAxisIndex, yAxisIndex, dataArray, colorArray, plotOptions } = context;
6
- const defaultColor = '#2962ff';
7
-
8
- return {
9
- name: seriesName,
10
- type: 'bar',
11
- xAxisIndex: xAxisIndex,
12
- yAxisIndex: yAxisIndex,
13
- data: dataArray.map((val, i) => ({
14
- value: val,
15
- itemStyle: colorArray[i] ? { color: colorArray[i] } : undefined,
16
- })),
17
- itemStyle: { color: plotOptions.color || defaultColor },
18
- };
19
- }
20
- }
1
+ import { SeriesRenderer, RenderContext } from './SeriesRenderer';
2
+
3
+ export class HistogramRenderer implements SeriesRenderer {
4
+ render(context: RenderContext): any {
5
+ const { seriesName, xAxisIndex, yAxisIndex, dataArray, colorArray, plotOptions } = context;
6
+ const defaultColor = '#2962ff';
7
+ const histbase: number = plotOptions.histbase ?? 0;
8
+ const isColumns = plotOptions.style === 'columns';
9
+ const linewidth: number = plotOptions.linewidth ?? 1;
10
+
11
+ // Build data array: [index, value, color]
12
+ const customData = dataArray.map((val: number | null, i: number) => {
13
+ if (val === null || val === undefined || (typeof val === 'number' && isNaN(val))) return null;
14
+ return [i, val, colorArray[i] || plotOptions.color || defaultColor];
15
+ });
16
+
17
+ return {
18
+ name: seriesName,
19
+ type: 'custom',
20
+ xAxisIndex: xAxisIndex,
21
+ yAxisIndex: yAxisIndex,
22
+ renderItem: (params: any, api: any) => {
23
+ const idx = api.value(0);
24
+ const value = api.value(1);
25
+ const color = api.value(2);
26
+
27
+ if (value === null || value === undefined || isNaN(value)) {
28
+ return null;
29
+ }
30
+
31
+ const basePos = api.coord([idx, histbase]);
32
+ const valuePos = api.coord([idx, value]);
33
+ const candleWidth = api.size([1, 0])[0];
34
+
35
+ // Columns: thick bars (60% of candle width)
36
+ // Histogram: thin bars — scale with linewidth (like TradingView)
37
+ let barWidth: number;
38
+ if (isColumns) {
39
+ barWidth = candleWidth * 0.6;
40
+ } else {
41
+ // Thin line-like bars: linewidth controls pixel width (min 1px)
42
+ barWidth = Math.max(1, linewidth);
43
+ }
44
+
45
+ const x = basePos[0];
46
+ const yBase = basePos[1];
47
+ const yValue = valuePos[1];
48
+ const top = Math.min(yBase, yValue);
49
+ const height = Math.abs(yValue - yBase);
50
+
51
+ return {
52
+ type: 'rect',
53
+ shape: {
54
+ x: x - barWidth / 2,
55
+ y: top,
56
+ width: barWidth,
57
+ height: height || 1, // Minimum 1px for zero-height bars
58
+ },
59
+ style: {
60
+ fill: color,
61
+ },
62
+ };
63
+ },
64
+ data: customData.filter((d: any) => d !== null),
65
+ };
66
+ }
67
+ }
@@ -37,20 +37,12 @@ export class LinefillRenderer implements SeriesRenderer {
37
37
  return { name: seriesName, type: 'custom', xAxisIndex, yAxisIndex, data: [], silent: true };
38
38
  }
39
39
 
40
- // Compute y-range for axis scaling
41
- let yMin = Infinity, yMax = -Infinity;
42
- for (const lf of fillObjects) {
43
- for (const y of [lf.line1.y1, lf.line1.y2, lf.line2.y1, lf.line2.y2]) {
44
- if (y < yMin) yMin = y;
45
- if (y > yMax) yMax = y;
46
- }
47
- }
48
-
49
40
  // Use a SINGLE data entry spanning the full x-range so renderItem is always called.
50
41
  // ECharts filters a data item only when ALL its x-dimensions are on the same side
51
42
  // of the visible window. With dims 0=0 and 1=lastBar the item always straddles
52
43
  // the viewport, so renderItem fires exactly once regardless of scroll position.
53
- // Dims 2/3 are yMin/yMax for axis scaling.
44
+ // Note: We do NOT encode y-dimensions — drawing objects should not influence the
45
+ // y-axis auto-scaling.
54
46
  const totalBars = (context.candlestickData?.length || 0) + offset;
55
47
  const lastBarIndex = Math.max(0, totalBars - 1);
56
48
 
@@ -103,9 +95,9 @@ export class LinefillRenderer implements SeriesRenderer {
103
95
 
104
96
  return { type: 'group', children };
105
97
  },
106
- data: [[0, lastBarIndex, yMin, yMax]],
98
+ data: [[0, lastBarIndex]],
107
99
  clip: true,
108
- encode: { x: [0, 1], y: [2, 3] },
100
+ encode: { x: [0, 1] },
109
101
  z: 10, // Behind lines (z=15) but above other elements
110
102
  silent: true,
111
103
  emphasis: { disabled: true },
@@ -34,21 +34,12 @@ export class PolylineRenderer implements SeriesRenderer {
34
34
  return { name: seriesName, type: 'custom', xAxisIndex, yAxisIndex, data: [], silent: true };
35
35
  }
36
36
 
37
- // Compute y-range across all polylines for axis scaling
38
- let yMin = Infinity, yMax = -Infinity;
39
- for (const pl of polyObjects) {
40
- for (const pt of pl.points) {
41
- const p = pt.price ?? 0;
42
- if (p < yMin) yMin = p;
43
- if (p > yMax) yMax = p;
44
- }
45
- }
46
-
47
37
  // Use a SINGLE data entry spanning the full x-range so renderItem is always called.
48
38
  // ECharts filters a data item only when ALL its x-dimensions are on the same side
49
39
  // of the visible window. With dims 0=0 and 1=lastBar the item always straddles
50
40
  // the viewport, so renderItem fires exactly once regardless of scroll position.
51
- // Dims 2/3 are yMin/yMax for axis scaling.
41
+ // Note: We do NOT encode y-dimensions — drawing objects should not influence the
42
+ // y-axis auto-scaling.
52
43
  const totalBars = (context.candlestickData?.length || 0) + offset;
53
44
  const lastBarIndex = Math.max(0, totalBars - 1);
54
45
 
@@ -126,9 +117,11 @@ export class PolylineRenderer implements SeriesRenderer {
126
117
 
127
118
  return { type: 'group', children };
128
119
  },
129
- data: [[0, lastBarIndex, yMin, yMax]],
120
+ data: [[0, lastBarIndex]],
130
121
  clip: true,
131
- encode: { x: [0, 1], y: [2, 3] },
122
+ encode: { x: [0, 1] },
123
+ // Prevent ECharts visual system from overriding element colors with palette
124
+ itemStyle: { color: 'transparent', borderColor: 'transparent' },
132
125
  z: 12,
133
126
  silent: true,
134
127
  emphasis: { disabled: true },