@tranding-custom-chart/renderer-canvas 0.0.0-stage → 2.0.0

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.
Files changed (104) hide show
  1. package/README.md +40 -2
  2. package/dist/canvas-engine.d.ts +82 -0
  3. package/dist/canvas-engine.d.ts.map +1 -0
  4. package/dist/canvas-engine.js +594 -0
  5. package/dist/dpi.d.ts +31 -0
  6. package/dist/dpi.d.ts.map +1 -0
  7. package/dist/dpi.js +65 -0
  8. package/dist/index.d.ts +38 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +37 -0
  11. package/dist/renderers/bar-renderer.d.ts +26 -0
  12. package/dist/renderers/bar-renderer.d.ts.map +1 -0
  13. package/dist/renderers/bar-renderer.js +45 -0
  14. package/dist/renderers/baseline-renderer.d.ts +20 -0
  15. package/dist/renderers/baseline-renderer.d.ts.map +1 -0
  16. package/dist/renderers/baseline-renderer.js +101 -0
  17. package/dist/renderers/box-plot-renderer.d.ts +14 -0
  18. package/dist/renderers/box-plot-renderer.d.ts.map +1 -0
  19. package/dist/renderers/box-plot-renderer.js +76 -0
  20. package/dist/renderers/bubble-renderer.d.ts +14 -0
  21. package/dist/renderers/bubble-renderer.d.ts.map +1 -0
  22. package/dist/renderers/bubble-renderer.js +37 -0
  23. package/dist/renderers/candlestick-renderer.d.ts +18 -0
  24. package/dist/renderers/candlestick-renderer.d.ts.map +1 -0
  25. package/dist/renderers/candlestick-renderer.js +87 -0
  26. package/dist/renderers/columns-renderer.d.ts +21 -0
  27. package/dist/renderers/columns-renderer.d.ts.map +1 -0
  28. package/dist/renderers/columns-renderer.js +35 -0
  29. package/dist/renderers/crosshair-renderer.d.ts +19 -0
  30. package/dist/renderers/crosshair-renderer.d.ts.map +1 -0
  31. package/dist/renderers/crosshair-renderer.js +71 -0
  32. package/dist/renderers/curved-line-renderer.d.ts +17 -0
  33. package/dist/renderers/curved-line-renderer.d.ts.map +1 -0
  34. package/dist/renderers/curved-line-renderer.js +46 -0
  35. package/dist/renderers/drawing-renderer.d.ts +19 -0
  36. package/dist/renderers/drawing-renderer.d.ts.map +1 -0
  37. package/dist/renderers/drawing-renderer.js +487 -0
  38. package/dist/renderers/grid-renderer.d.ts +26 -0
  39. package/dist/renderers/grid-renderer.d.ts.map +1 -0
  40. package/dist/renderers/grid-renderer.js +103 -0
  41. package/dist/renderers/heatmap-renderer.d.ts +16 -0
  42. package/dist/renderers/heatmap-renderer.d.ts.map +1 -0
  43. package/dist/renderers/heatmap-renderer.js +80 -0
  44. package/dist/renderers/high-low-renderer.d.ts +19 -0
  45. package/dist/renderers/high-low-renderer.d.ts.map +1 -0
  46. package/dist/renderers/high-low-renderer.js +28 -0
  47. package/dist/renderers/hlc-area-renderer.d.ts +22 -0
  48. package/dist/renderers/hlc-area-renderer.d.ts.map +1 -0
  49. package/dist/renderers/hlc-area-renderer.js +72 -0
  50. package/dist/renderers/hollow-candlestick-renderer.d.ts +26 -0
  51. package/dist/renderers/hollow-candlestick-renderer.d.ts.map +1 -0
  52. package/dist/renderers/hollow-candlestick-renderer.js +71 -0
  53. package/dist/renderers/horizontal-bar-renderer.d.ts +25 -0
  54. package/dist/renderers/horizontal-bar-renderer.d.ts.map +1 -0
  55. package/dist/renderers/horizontal-bar-renderer.js +52 -0
  56. package/dist/renderers/last-price-renderer.d.ts +17 -0
  57. package/dist/renderers/last-price-renderer.d.ts.map +1 -0
  58. package/dist/renderers/last-price-renderer.js +46 -0
  59. package/dist/renderers/line-renderer.d.ts +23 -0
  60. package/dist/renderers/line-renderer.d.ts.map +1 -0
  61. package/dist/renderers/line-renderer.js +80 -0
  62. package/dist/renderers/line-with-markers-renderer.d.ts +13 -0
  63. package/dist/renderers/line-with-markers-renderer.d.ts.map +1 -0
  64. package/dist/renderers/line-with-markers-renderer.js +35 -0
  65. package/dist/renderers/marker-renderer.d.ts +16 -0
  66. package/dist/renderers/marker-renderer.d.ts.map +1 -0
  67. package/dist/renderers/marker-renderer.js +91 -0
  68. package/dist/renderers/mixed-renderer.d.ts +18 -0
  69. package/dist/renderers/mixed-renderer.d.ts.map +1 -0
  70. package/dist/renderers/mixed-renderer.js +123 -0
  71. package/dist/renderers/ohlc-bar-renderer.d.ts +21 -0
  72. package/dist/renderers/ohlc-bar-renderer.d.ts.map +1 -0
  73. package/dist/renderers/ohlc-bar-renderer.js +73 -0
  74. package/dist/renderers/pie-renderer.d.ts +26 -0
  75. package/dist/renderers/pie-renderer.d.ts.map +1 -0
  76. package/dist/renderers/pie-renderer.js +71 -0
  77. package/dist/renderers/polar-area-renderer.d.ts +13 -0
  78. package/dist/renderers/polar-area-renderer.d.ts.map +1 -0
  79. package/dist/renderers/polar-area-renderer.js +44 -0
  80. package/dist/renderers/radar-renderer.d.ts +16 -0
  81. package/dist/renderers/radar-renderer.d.ts.map +1 -0
  82. package/dist/renderers/radar-renderer.js +99 -0
  83. package/dist/renderers/scatter-renderer.d.ts +14 -0
  84. package/dist/renderers/scatter-renderer.d.ts.map +1 -0
  85. package/dist/renderers/scatter-renderer.js +42 -0
  86. package/dist/renderers/stacked-bar-renderer.d.ts +14 -0
  87. package/dist/renderers/stacked-bar-renderer.d.ts.map +1 -0
  88. package/dist/renderers/stacked-bar-renderer.js +52 -0
  89. package/dist/renderers/step-line-renderer.d.ts +19 -0
  90. package/dist/renderers/step-line-renderer.d.ts.map +1 -0
  91. package/dist/renderers/step-line-renderer.js +52 -0
  92. package/dist/renderers/supertrend-renderer.d.ts +18 -0
  93. package/dist/renderers/supertrend-renderer.d.ts.map +1 -0
  94. package/dist/renderers/supertrend-renderer.js +80 -0
  95. package/dist/renderers/volume-profile-renderer.d.ts +25 -0
  96. package/dist/renderers/volume-profile-renderer.d.ts.map +1 -0
  97. package/dist/renderers/volume-profile-renderer.js +83 -0
  98. package/dist/renderers/watermark-renderer.d.ts +15 -0
  99. package/dist/renderers/watermark-renderer.d.ts.map +1 -0
  100. package/dist/renderers/watermark-renderer.js +26 -0
  101. package/dist/scheduler.d.ts +25 -0
  102. package/dist/scheduler.d.ts.map +1 -0
  103. package/dist/scheduler.js +77 -0
  104. package/package.json +33 -4
@@ -0,0 +1,80 @@
1
+ /**
2
+ * @file packages/chart-renderer-canvas/src/renderers/line-renderer.ts
3
+ * Stroked line and gradient area fill renderer.
4
+ */
5
+ export function renderLineSeries(options) {
6
+ const { ctx, points, xScale, yScale, color, strokeWidth = 2, lineDash, areaFill, startIndex = 0, endIndex = points.length - 1, } = options;
7
+ if (points.length === 0 || startIndex > endIndex)
8
+ return;
9
+ const start = Math.max(0, startIndex);
10
+ const end = Math.min(points.length - 1, endIndex);
11
+ ctx.save();
12
+ ctx.strokeStyle = color;
13
+ ctx.lineWidth = strokeWidth;
14
+ ctx.lineJoin = 'round';
15
+ ctx.lineCap = 'round';
16
+ if (lineDash && lineDash.length > 0) {
17
+ ctx.setLineDash(lineDash);
18
+ }
19
+ // Draw Line Path
20
+ let inSegment = false;
21
+ ctx.beginPath();
22
+ for (let i = start; i <= end; i++) {
23
+ const val = points[i];
24
+ if (val === null || val === undefined || !Number.isFinite(val)) {
25
+ inSegment = false;
26
+ continue;
27
+ }
28
+ const x = xScale.scale(i);
29
+ const y = yScale.scale(val);
30
+ if (!inSegment) {
31
+ ctx.moveTo(x, y);
32
+ inSegment = true;
33
+ }
34
+ else {
35
+ ctx.lineTo(x, y);
36
+ }
37
+ }
38
+ ctx.stroke();
39
+ // Draw Area Fill if configured
40
+ if (areaFill) {
41
+ const firstIdx = points.findIndex((v, idx) => idx >= start && idx <= end && v != null);
42
+ let lastIdx = -1;
43
+ for (let i = end; i >= start; i--) {
44
+ if (points[i] != null) {
45
+ lastIdx = i;
46
+ break;
47
+ }
48
+ }
49
+ if (firstIdx !== -1 && lastIdx !== -1 && lastIdx >= firstIdx) {
50
+ ctx.beginPath();
51
+ inSegment = false;
52
+ for (let i = firstIdx; i <= lastIdx; i++) {
53
+ const val = points[i];
54
+ if (val === null || val === undefined || !Number.isFinite(val)) {
55
+ continue;
56
+ }
57
+ const x = xScale.scale(i);
58
+ const y = yScale.scale(val);
59
+ if (!inSegment) {
60
+ ctx.moveTo(x, y);
61
+ inSegment = true;
62
+ }
63
+ else {
64
+ ctx.lineTo(x, y);
65
+ }
66
+ }
67
+ const endX = xScale.scale(lastIdx);
68
+ const startX = xScale.scale(firstIdx);
69
+ ctx.lineTo(endX, areaFill.baselineY);
70
+ ctx.lineTo(startX, areaFill.baselineY);
71
+ ctx.closePath();
72
+ const grad = ctx.createLinearGradient(0, yScale.scale(yScale.domain()[1]), 0, areaFill.baselineY);
73
+ grad.addColorStop(0, areaFill.gradientTopColor);
74
+ grad.addColorStop(1, areaFill.gradientBottomColor);
75
+ ctx.fillStyle = grad;
76
+ ctx.fill();
77
+ }
78
+ }
79
+ ctx.restore();
80
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @file packages/chart-renderer-canvas/src/renderers/line-with-markers-renderer.ts
3
+ * Canvas 2D renderer for Line with Markers charts.
4
+ * Draws continuous polyline with circular anchor markers at each discrete data coordinate.
5
+ */
6
+ import { type LineRenderOptions } from './line-renderer.js';
7
+ export interface LineWithMarkersRenderOptions extends LineRenderOptions {
8
+ readonly markerRadius?: number | undefined;
9
+ readonly markerFillColor?: string | undefined;
10
+ readonly markerStrokeColor?: string | undefined;
11
+ }
12
+ export declare function renderLineWithMarkers(options: LineWithMarkersRenderOptions): void;
13
+ //# sourceMappingURL=line-with-markers-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"line-with-markers-renderer.d.ts","sourceRoot":"","sources":["../../src/renderers/line-with-markers-renderer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE9E,MAAM,WAAW,4BAA6B,SAAQ,iBAAiB;IACrE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACjD;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,IAAI,CA4CjF"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @file packages/chart-renderer-canvas/src/renderers/line-with-markers-renderer.ts
3
+ * Canvas 2D renderer for Line with Markers charts.
4
+ * Draws continuous polyline with circular anchor markers at each discrete data coordinate.
5
+ */
6
+ import { renderLineSeries } from './line-renderer.js';
7
+ export function renderLineWithMarkers(options) {
8
+ const { ctx, points, xScale, yScale, color, markerRadius = 3, markerFillColor = '#ffffff', markerStrokeColor = color, startIndex = 0, endIndex = points.length - 1, } = options;
9
+ if (points.length === 0 || startIndex > endIndex)
10
+ return;
11
+ // 1. Render continuous line series
12
+ renderLineSeries(options);
13
+ // 2. Render circular markers
14
+ const start = Math.max(0, startIndex);
15
+ const end = Math.min(points.length - 1, endIndex);
16
+ const slotWidth = points.length > 1 ? Math.abs(xScale.scale(1) - xScale.scale(0)) : 10;
17
+ const markerStep = slotWidth < 8 ? Math.max(1, Math.ceil(12 / Math.max(0.1, slotWidth))) : 1;
18
+ const effectiveRadius = slotWidth < 4 ? 1.5 : markerRadius;
19
+ ctx.save();
20
+ for (let i = start; i <= end; i += markerStep) {
21
+ const val = points[i];
22
+ if (val === null || val === undefined || isNaN(val))
23
+ continue;
24
+ const x = xScale.scale(i);
25
+ const y = yScale.scale(val);
26
+ ctx.beginPath();
27
+ ctx.arc(x, y, effectiveRadius, 0, Math.PI * 2);
28
+ ctx.fillStyle = markerFillColor;
29
+ ctx.fill();
30
+ ctx.lineWidth = 1.5;
31
+ ctx.strokeStyle = markerStrokeColor;
32
+ ctx.stroke();
33
+ }
34
+ ctx.restore();
35
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @file packages/chart-renderer-canvas/src/renderers/marker-renderer.ts
3
+ * Renders pin and shaped marker annotations on series coordinates.
4
+ */
5
+ import type { Dimension, Padding, Scale, SeriesMarker } from '@tranding-custom-chart/core';
6
+ export interface MarkerRenderOptions {
7
+ readonly ctx: CanvasRenderingContext2D;
8
+ readonly bounds: Dimension;
9
+ readonly padding: Padding;
10
+ readonly xScale: Scale<number>;
11
+ readonly yScale: Scale<number>;
12
+ readonly markers: readonly SeriesMarker[];
13
+ readonly dpr?: number | undefined;
14
+ }
15
+ export declare function renderSeriesMarkers(options: MarkerRenderOptions): void;
16
+ //# sourceMappingURL=marker-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"marker-renderer.d.ts","sourceRoot":"","sources":["../../src/renderers/marker-renderer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3F,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,wBAAwB,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CA6FtE"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @file packages/chart-renderer-canvas/src/renderers/marker-renderer.ts
3
+ * Renders pin and shaped marker annotations on series coordinates.
4
+ */
5
+ export function renderSeriesMarkers(options) {
6
+ const { ctx, xScale, yScale, markers } = options;
7
+ ctx.save();
8
+ for (const marker of markers) {
9
+ const x = xScale.scale(marker.index);
10
+ const targetPrice = marker.price ?? 0;
11
+ let y = yScale.scale(targetPrice);
12
+ const size = marker.size ?? 12;
13
+ const half = size / 2;
14
+ const shape = marker.shape ?? 'circle';
15
+ const position = marker.position ?? 'aboveBar';
16
+ // Position offset relative to point
17
+ if (position === 'aboveBar') {
18
+ y -= size + 4;
19
+ }
20
+ else if (position === 'belowBar') {
21
+ y += size + 4;
22
+ }
23
+ ctx.fillStyle = marker.color;
24
+ ctx.strokeStyle = '#ffffff';
25
+ ctx.lineWidth = 1.5;
26
+ ctx.beginPath();
27
+ if (shape === 'circle') {
28
+ ctx.arc(x, y, half, 0, Math.PI * 2);
29
+ ctx.fill();
30
+ ctx.stroke();
31
+ }
32
+ else if (shape === 'square') {
33
+ ctx.fillRect(x - half, y - half, size, size);
34
+ ctx.strokeRect(x - half, y - half, size, size);
35
+ }
36
+ else if (shape === 'arrowUp') {
37
+ ctx.moveTo(x, y - half);
38
+ ctx.lineTo(x + half, y + half);
39
+ ctx.lineTo(x - half, y + half);
40
+ ctx.closePath();
41
+ ctx.fill();
42
+ }
43
+ else if (shape === 'arrowDown') {
44
+ ctx.moveTo(x, y + half);
45
+ ctx.lineTo(x + half, y - half);
46
+ ctx.lineTo(x - half, y - half);
47
+ ctx.closePath();
48
+ ctx.fill();
49
+ }
50
+ else if (shape === 'star') {
51
+ const spikes = 5;
52
+ const outerRadius = half;
53
+ const innerRadius = half * 0.45;
54
+ let rot = (Math.PI / 2) * 3;
55
+ const step = Math.PI / spikes;
56
+ ctx.moveTo(x, y - outerRadius);
57
+ for (let i = 0; i < spikes; i++) {
58
+ let sx = x + Math.cos(rot) * outerRadius;
59
+ let sy = y + Math.sin(rot) * outerRadius;
60
+ ctx.lineTo(sx, sy);
61
+ rot += step;
62
+ sx = x + Math.cos(rot) * innerRadius;
63
+ sy = y + Math.sin(rot) * innerRadius;
64
+ ctx.lineTo(sx, sy);
65
+ rot += step;
66
+ }
67
+ ctx.lineTo(x, y - outerRadius);
68
+ ctx.closePath();
69
+ ctx.fill();
70
+ ctx.stroke();
71
+ }
72
+ // Render optional marker text label badge
73
+ if (marker.text) {
74
+ ctx.font = 'bold 9px monospace';
75
+ const textWidth = ctx.measureText(marker.text).width;
76
+ const badgeH = 14;
77
+ const badgeW = textWidth + 8;
78
+ const badgeY = position === 'aboveBar' ? y - half - badgeH - 2 : y + half + 2;
79
+ ctx.fillStyle = 'rgba(15, 23, 42, 0.85)';
80
+ ctx.fillRect(x - badgeW / 2, badgeY, badgeW, badgeH);
81
+ ctx.strokeStyle = marker.color;
82
+ ctx.lineWidth = 1;
83
+ ctx.strokeRect(x - badgeW / 2, badgeY, badgeW, badgeH);
84
+ ctx.fillStyle = '#ffffff';
85
+ ctx.textAlign = 'center';
86
+ ctx.textBaseline = 'middle';
87
+ ctx.fillText(marker.text, x, badgeY + badgeH / 2);
88
+ }
89
+ }
90
+ ctx.restore();
91
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @file packages/chart-renderer-canvas/src/renderers/mixed-renderer.ts
3
+ * Heterogeneous multi-dataset mixed chart renderer (Bar + Line + Spline + Area + Scatter).
4
+ * Coordinated multi-dataset rendering with dual-axis scale synchronization.
5
+ */
6
+ import type { Dimension, MixedDataset, Padding, Scale } from '@tranding-custom-chart/core';
7
+ export interface MixedRenderOptions {
8
+ readonly ctx: CanvasRenderingContext2D;
9
+ readonly bounds: Dimension;
10
+ readonly padding: Padding;
11
+ readonly xScale: Scale<number>;
12
+ readonly yScale: Scale<number>;
13
+ readonly secondaryYScale?: Scale<number> | undefined;
14
+ readonly datasets: readonly MixedDataset[];
15
+ readonly dpr?: number | undefined;
16
+ }
17
+ export declare function renderMixedDatasets(options: MixedRenderOptions): void;
18
+ //# sourceMappingURL=mixed-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mixed-renderer.d.ts","sourceRoot":"","sources":["../../src/renderers/mixed-renderer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAI3F,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,GAAG,EAAE,wBAAwB,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,CAAC;IAC3C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CA+HrE"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @file packages/chart-renderer-canvas/src/renderers/mixed-renderer.ts
3
+ * Heterogeneous multi-dataset mixed chart renderer (Bar + Line + Spline + Area + Scatter).
4
+ * Coordinated multi-dataset rendering with dual-axis scale synchronization.
5
+ */
6
+ import { computeCatmullRomBezierSegments } from '@tranding-custom-chart/core';
7
+ import { snapLine } from '../dpi.js';
8
+ export function renderMixedDatasets(options) {
9
+ const { ctx, bounds, padding, xScale, yScale, secondaryYScale, datasets, dpr = 1 } = options;
10
+ const left = padding.left;
11
+ const right = bounds.width - padding.right;
12
+ const top = padding.top;
13
+ const bottom = bounds.height - padding.bottom;
14
+ ctx.save();
15
+ // Clip to plot area to avoid spilling outside margins
16
+ ctx.beginPath();
17
+ ctx.rect(left, top, right - left, bottom - top);
18
+ ctx.clip();
19
+ // 1. Separate bar datasets to calculate grouped bar slotting
20
+ const activeBarDatasets = datasets.filter((d) => !d.hidden && d.type === 'bar');
21
+ const barCount = activeBarDatasets.length;
22
+ for (let barIdx = 0; barIdx < barCount; barIdx++) {
23
+ const ds = activeBarDatasets[barIdx];
24
+ const activeYScale = ds.yAxisPosition === 'right' && secondaryYScale ? secondaryYScale : yScale;
25
+ const baselineY = activeYScale.scale(0);
26
+ const color = ds.backgroundColor ?? ds.color ?? '#3b82f6';
27
+ ctx.fillStyle = color;
28
+ for (let i = 0; i < ds.data.length; i++) {
29
+ const val = ds.data[i];
30
+ if (val === undefined || val === null)
31
+ continue;
32
+ const slotCenter = xScale.scale(i);
33
+ // Grouped bar width & offset calculation
34
+ const slotWidth = Math.max(8, (right - left) / Math.max(1, ds.data.length));
35
+ const groupWidth = slotWidth * 0.75;
36
+ const singleBarWidth = Math.max(3, groupWidth / barCount);
37
+ const barX = slotCenter - groupWidth / 2 + barIdx * singleBarWidth;
38
+ const yVal = activeYScale.scale(val);
39
+ const barY = Math.min(yVal, baselineY);
40
+ const barHeight = Math.max(1, Math.abs(baselineY - yVal));
41
+ ctx.fillRect(barX, barY, singleBarWidth - 1, barHeight);
42
+ }
43
+ }
44
+ // 2. Render continuous series: Area, Spline, Line, Scatter
45
+ for (const ds of datasets) {
46
+ if (ds.hidden || ds.type === 'bar')
47
+ continue;
48
+ const activeYScale = ds.yAxisPosition === 'right' && secondaryYScale ? secondaryYScale : yScale;
49
+ const strokeColor = ds.color ?? '#10b981';
50
+ const strokeWidth = ds.strokeWidth ?? 2;
51
+ const points = [];
52
+ for (let i = 0; i < ds.data.length; i++) {
53
+ const val = ds.data[i];
54
+ if (val === undefined || val === null)
55
+ continue;
56
+ points.push({
57
+ x: xScale.scale(i),
58
+ y: activeYScale.scale(val),
59
+ });
60
+ }
61
+ if (points.length < 2 && ds.type !== 'scatter')
62
+ continue;
63
+ if (ds.type === 'area') {
64
+ const baselineY = activeYScale.scale(0);
65
+ const fillColor = ds.backgroundColor ?? 'rgba(16, 185, 129, 0.15)';
66
+ ctx.fillStyle = fillColor;
67
+ ctx.beginPath();
68
+ ctx.moveTo(points[0].x, baselineY);
69
+ for (const p of points) {
70
+ ctx.lineTo(p.x, p.y);
71
+ }
72
+ ctx.lineTo(points[points.length - 1].x, baselineY);
73
+ ctx.closePath();
74
+ ctx.fill();
75
+ // Top edge line
76
+ ctx.strokeStyle = strokeColor;
77
+ ctx.lineWidth = strokeWidth;
78
+ ctx.beginPath();
79
+ ctx.moveTo(points[0].x, points[0].y);
80
+ for (let i = 1; i < points.length; i++) {
81
+ ctx.lineTo(points[i].x, points[i].y);
82
+ }
83
+ ctx.stroke();
84
+ }
85
+ else if (ds.type === 'spline') {
86
+ ctx.strokeStyle = strokeColor;
87
+ ctx.lineWidth = strokeWidth;
88
+ ctx.beginPath();
89
+ ctx.moveTo(points[0].x, points[0].y);
90
+ const segments = computeCatmullRomBezierSegments(points, 1.0);
91
+ for (const seg of segments) {
92
+ ctx.bezierCurveTo(seg.cp1.x, seg.cp1.y, seg.cp2.x, seg.cp2.y, seg.p2.x, seg.p2.y);
93
+ }
94
+ ctx.stroke();
95
+ }
96
+ else if (ds.type === 'line') {
97
+ ctx.strokeStyle = strokeColor;
98
+ ctx.lineWidth = strokeWidth;
99
+ ctx.beginPath();
100
+ ctx.moveTo(points[0].x, points[0].y);
101
+ for (let i = 1; i < points.length; i++) {
102
+ ctx.lineTo(points[i].x, points[i].y);
103
+ }
104
+ ctx.stroke();
105
+ // Data markers
106
+ ctx.fillStyle = strokeColor;
107
+ for (const p of points) {
108
+ ctx.beginPath();
109
+ ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
110
+ ctx.fill();
111
+ }
112
+ }
113
+ else if (ds.type === 'scatter') {
114
+ ctx.fillStyle = strokeColor;
115
+ for (const p of points) {
116
+ ctx.beginPath();
117
+ ctx.arc(p.x, p.y, 4, 0, Math.PI * 2);
118
+ ctx.fill();
119
+ }
120
+ }
121
+ }
122
+ ctx.restore();
123
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @file packages/chart-renderer-canvas/src/renderers/ohlc-bar-renderer.ts
3
+ * Traditional American OHLC bar chart renderer with left/right ticks.
4
+ */
5
+ import type { ChartTheme, OHLCVBar, Scale } from '@tranding-custom-chart/core';
6
+ export interface OhlcBarRenderOptions {
7
+ readonly ctx: CanvasRenderingContext2D;
8
+ readonly bars: readonly OHLCVBar[];
9
+ readonly xScale: Scale<number>;
10
+ readonly yScale: Scale<number>;
11
+ readonly theme: ChartTheme;
12
+ readonly dpr: number;
13
+ readonly startIndex?: number | undefined;
14
+ readonly endIndex?: number | undefined;
15
+ readonly tickWidthRatio?: number | undefined;
16
+ readonly customUpColor?: string | undefined;
17
+ readonly customDownColor?: string | undefined;
18
+ readonly lineWidth?: number | undefined;
19
+ }
20
+ export declare function renderOhlcBars(options: OhlcBarRenderOptions): void;
21
+ //# sourceMappingURL=ohlc-bar-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ohlc-bar-renderer.d.ts","sourceRoot":"","sources":["../../src/renderers/ohlc-bar-renderer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAG/E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,GAAG,EAAE,wBAAwB,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAwFlE"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @file packages/chart-renderer-canvas/src/renderers/ohlc-bar-renderer.ts
3
+ * Traditional American OHLC bar chart renderer with left/right ticks.
4
+ */
5
+ import { snapLine } from '../dpi.js';
6
+ export function renderOhlcBars(options) {
7
+ const { ctx, bars, xScale, yScale, theme, dpr, startIndex = 0, endIndex = bars.length - 1, tickWidthRatio = 0.35, customUpColor, customDownColor, lineWidth = 1.2, } = options;
8
+ if (bars.length === 0 || startIndex > endIndex)
9
+ return;
10
+ const start = Math.max(0, startIndex);
11
+ const end = Math.min(bars.length - 1, endIndex);
12
+ let slotWidth = 8;
13
+ if ('stepWidth' in xScale && typeof xScale.stepWidth === 'number') {
14
+ slotWidth = xScale.stepWidth;
15
+ }
16
+ else if (bars.length > 1) {
17
+ const x0 = xScale.scale(0);
18
+ const x1 = xScale.scale(1);
19
+ slotWidth = Math.abs(x1 - x0) || 8;
20
+ }
21
+ const tickLength = slotWidth < 3 ? Math.max(0.5, slotWidth * 0.4) : Math.max(1, slotWidth * tickWidthRatio);
22
+ const upColor = customUpColor ?? theme.upCandle;
23
+ const downColor = customDownColor ?? theme.downCandle;
24
+ ctx.save();
25
+ ctx.lineWidth = slotWidth < 2 ? 0.75 : lineWidth;
26
+ // Render Bullish (Up) Bars
27
+ ctx.strokeStyle = upColor;
28
+ ctx.beginPath();
29
+ for (let i = start; i <= end; i++) {
30
+ const b = bars[i];
31
+ if (b.close >= b.open) {
32
+ const x = snapLine(xScale.scale(i), dpr);
33
+ const yHigh = snapLine(yScale.scale(b.high), dpr);
34
+ const yLow = snapLine(yScale.scale(b.low), dpr);
35
+ const yOpen = snapLine(yScale.scale(b.open), dpr);
36
+ const yClose = snapLine(yScale.scale(b.close), dpr);
37
+ // Vertical range line
38
+ ctx.moveTo(x, yHigh);
39
+ ctx.lineTo(x, yLow);
40
+ // Left tick for Open
41
+ ctx.moveTo(x - tickLength, yOpen);
42
+ ctx.lineTo(x, yOpen);
43
+ // Right tick for Close
44
+ ctx.moveTo(x, yClose);
45
+ ctx.lineTo(x + tickLength, yClose);
46
+ }
47
+ }
48
+ ctx.stroke();
49
+ // Render Bearish (Down) Bars
50
+ ctx.strokeStyle = downColor;
51
+ ctx.beginPath();
52
+ for (let i = start; i <= end; i++) {
53
+ const b = bars[i];
54
+ if (b.close < b.open) {
55
+ const x = snapLine(xScale.scale(i), dpr);
56
+ const yHigh = snapLine(yScale.scale(b.high), dpr);
57
+ const yLow = snapLine(yScale.scale(b.low), dpr);
58
+ const yOpen = snapLine(yScale.scale(b.open), dpr);
59
+ const yClose = snapLine(yScale.scale(b.close), dpr);
60
+ // Vertical range line
61
+ ctx.moveTo(x, yHigh);
62
+ ctx.lineTo(x, yLow);
63
+ // Left tick for Open
64
+ ctx.moveTo(x - tickLength, yOpen);
65
+ ctx.lineTo(x, yOpen);
66
+ // Right tick for Close
67
+ ctx.moveTo(x, yClose);
68
+ ctx.lineTo(x + tickLength, yClose);
69
+ }
70
+ }
71
+ ctx.stroke();
72
+ ctx.restore();
73
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @file packages/chart-renderer-canvas/src/renderers/pie-renderer.ts
3
+ * Canvas 2D renderer for Pie and Doughnut charts.
4
+ * Supports configurable inner cutout radius, hover slice explosion, slice borders,
5
+ * and centered KPI readout.
6
+ */
7
+ import type { PieSlice } from '@tranding-custom-chart/core';
8
+ export interface PieCenterText {
9
+ readonly title?: string | undefined;
10
+ readonly value?: string | undefined;
11
+ readonly subtitle?: string | undefined;
12
+ readonly color?: string | undefined;
13
+ }
14
+ export interface PieRenderOptions {
15
+ readonly cx: number;
16
+ readonly cy: number;
17
+ readonly radius: number;
18
+ readonly innerRadius?: number | undefined;
19
+ readonly hoverIndex?: number | null | undefined;
20
+ readonly hoverExplodeDistance?: number | undefined;
21
+ readonly borderColor?: string | undefined;
22
+ readonly borderWidth?: number | undefined;
23
+ readonly centerText?: PieCenterText | undefined;
24
+ }
25
+ export declare function renderPie(ctx: CanvasRenderingContext2D, slices: readonly PieSlice[], options: PieRenderOptions): void;
26
+ //# sourceMappingURL=pie-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pie-renderer.d.ts","sourceRoot":"","sources":["../../src/renderers/pie-renderer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AAE5D,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAChD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CACjD;AAED,wBAAgB,SAAS,CACvB,GAAG,EAAE,wBAAwB,EAC7B,MAAM,EAAE,SAAS,QAAQ,EAAE,EAC3B,OAAO,EAAE,gBAAgB,GACxB,IAAI,CAsFN"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @file packages/chart-renderer-canvas/src/renderers/pie-renderer.ts
3
+ * Canvas 2D renderer for Pie and Doughnut charts.
4
+ * Supports configurable inner cutout radius, hover slice explosion, slice borders,
5
+ * and centered KPI readout.
6
+ */
7
+ export function renderPie(ctx, slices, options) {
8
+ if (slices.length === 0 || options.radius <= 0)
9
+ return;
10
+ const total = slices.reduce((sum, s) => sum + Math.max(0, s.value), 0);
11
+ if (total <= 0)
12
+ return;
13
+ const { cx, cy, radius, innerRadius = 0, hoverIndex = null, hoverExplodeDistance = 6, borderColor = '#181b19', borderWidth = 2, centerText, } = options;
14
+ ctx.save();
15
+ let currentAngle = -Math.PI / 2; // start at 12 o'clock
16
+ for (let i = 0; i < slices.length; i++) {
17
+ const slice = slices[i];
18
+ const val = Math.max(0, slice.value);
19
+ const sliceAngle = (val / total) * 2 * Math.PI;
20
+ const endAngle = currentAngle + sliceAngle;
21
+ if (sliceAngle <= 0) {
22
+ currentAngle = endAngle;
23
+ continue;
24
+ }
25
+ const isHovered = hoverIndex === i;
26
+ let sliceCx = cx;
27
+ let sliceCy = cy;
28
+ if (isHovered && hoverExplodeDistance > 0) {
29
+ const bisector = currentAngle + sliceAngle / 2;
30
+ sliceCx += Math.cos(bisector) * hoverExplodeDistance;
31
+ sliceCy += Math.sin(bisector) * hoverExplodeDistance;
32
+ }
33
+ ctx.beginPath();
34
+ if (innerRadius > 0) {
35
+ // Doughnut Arc
36
+ ctx.arc(sliceCx, sliceCy, radius, currentAngle, endAngle, false);
37
+ ctx.arc(sliceCx, sliceCy, innerRadius, endAngle, currentAngle, true);
38
+ ctx.closePath();
39
+ }
40
+ else {
41
+ // Pie Arc
42
+ ctx.moveTo(sliceCx, sliceCy);
43
+ ctx.arc(sliceCx, sliceCy, radius, currentAngle, endAngle, false);
44
+ ctx.closePath();
45
+ }
46
+ ctx.fillStyle = slice.color;
47
+ ctx.fill();
48
+ if (borderWidth > 0 && borderColor) {
49
+ ctx.lineWidth = borderWidth;
50
+ ctx.strokeStyle = borderColor;
51
+ ctx.stroke();
52
+ }
53
+ currentAngle = endAngle;
54
+ }
55
+ // Draw Center KPI Text for Doughnut
56
+ if (innerRadius > 20 && centerText) {
57
+ ctx.textAlign = 'center';
58
+ ctx.textBaseline = 'middle';
59
+ if (centerText.value) {
60
+ ctx.font = 'bold 18px Inter, system-ui, sans-serif';
61
+ ctx.fillStyle = centerText.color ?? '#ffffff';
62
+ ctx.fillText(centerText.value, cx, cy - (centerText.title ? 4 : 0));
63
+ }
64
+ if (centerText.title) {
65
+ ctx.font = '500 11px Inter, system-ui, sans-serif';
66
+ ctx.fillStyle = centerText.color ? `${centerText.color}aa` : '#858e89';
67
+ ctx.fillText(centerText.title.toUpperCase(), cx, cy + (centerText.value ? 16 : 0));
68
+ }
69
+ }
70
+ ctx.restore();
71
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @file packages/chart-renderer-canvas/src/renderers/polar-area-renderer.ts
3
+ * High-performance 2D Canvas renderer for Polar Area Charts.
4
+ */
5
+ import type { Dimension, Padding, PolarAreaDatum } from '@tranding-custom-chart/core';
6
+ export interface PolarAreaRenderOptions {
7
+ readonly startAngle?: number;
8
+ readonly strokeColor?: string;
9
+ readonly strokeWidth?: number;
10
+ readonly showLabels?: boolean;
11
+ }
12
+ export declare function renderPolarArea(ctx: CanvasRenderingContext2D, data: readonly PolarAreaDatum[], dimension: Dimension, padding: Padding, options?: PolarAreaRenderOptions): void;
13
+ //# sourceMappingURL=polar-area-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polar-area-renderer.d.ts","sourceRoot":"","sources":["../../src/renderers/polar-area-renderer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAEtF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,wBAAgB,eAAe,CAC7B,GAAG,EAAE,wBAAwB,EAC7B,IAAI,EAAE,SAAS,cAAc,EAAE,EAC/B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,sBAA2B,GACnC,IAAI,CAgDN"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @file packages/chart-renderer-canvas/src/renderers/polar-area-renderer.ts
3
+ * High-performance 2D Canvas renderer for Polar Area Charts.
4
+ */
5
+ export function renderPolarArea(ctx, data, dimension, padding, options = {}) {
6
+ if (!data || data.length === 0)
7
+ return;
8
+ const plotWidth = dimension.width - padding.left - padding.right;
9
+ const plotHeight = dimension.height - padding.top - padding.bottom;
10
+ const cx = padding.left + plotWidth / 2;
11
+ const cy = padding.top + plotHeight / 2;
12
+ const maxRadius = Math.max(10, Math.min(plotWidth, plotHeight) / 2);
13
+ const maxVal = data.reduce((max, d) => Math.max(max, d.value), 0) || 1;
14
+ const sliceAngle = (Math.PI * 2) / data.length;
15
+ let currentAngle = options.startAngle ?? -Math.PI / 2;
16
+ const strokeColor = options.strokeColor ?? 'rgba(255, 255, 255, 0.4)';
17
+ const strokeWidth = options.strokeWidth ?? 1;
18
+ ctx.save();
19
+ ctx.lineWidth = strokeWidth;
20
+ ctx.strokeStyle = strokeColor;
21
+ for (let i = 0; i < data.length; i++) {
22
+ const d = data[i];
23
+ const sliceRadius = (d.value / maxVal) * maxRadius;
24
+ ctx.beginPath();
25
+ ctx.moveTo(cx, cy);
26
+ ctx.arc(cx, cy, sliceRadius, currentAngle, currentAngle + sliceAngle);
27
+ ctx.closePath();
28
+ ctx.fillStyle = d.color;
29
+ ctx.fill();
30
+ ctx.stroke();
31
+ if (options.showLabels) {
32
+ const midAngle = currentAngle + sliceAngle / 2;
33
+ const lx = cx + Math.cos(midAngle) * (sliceRadius + 14);
34
+ const ly = cy + Math.sin(midAngle) * (sliceRadius + 14);
35
+ ctx.fillStyle = '#d1d5db';
36
+ ctx.font = '11px system-ui, sans-serif';
37
+ ctx.textAlign = 'center';
38
+ ctx.textBaseline = 'middle';
39
+ ctx.fillText(`${d.label} (${d.value})`, lx, ly);
40
+ }
41
+ currentAngle += sliceAngle;
42
+ }
43
+ ctx.restore();
44
+ }