@indiminds/charts 1.0.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.
Files changed (51) hide show
  1. package/LICENSE +14 -0
  2. package/README.md +159 -0
  3. package/dist/bar-chart/bar-chart.d.ts +26 -0
  4. package/dist/bar-chart/index.d.ts +2 -0
  5. package/dist/bar-chart/index.js +1 -0
  6. package/dist/bar-chart/types.d.ts +48 -0
  7. package/dist/candlestick/candlestick-chart.d.ts +73 -0
  8. package/dist/candlestick/data-sync.d.ts +29 -0
  9. package/dist/candlestick/index.d.ts +2 -0
  10. package/dist/candlestick/index.js +1 -0
  11. package/dist/candlestick/interaction.d.ts +10 -0
  12. package/dist/candlestick/layout-calculator.d.ts +23 -0
  13. package/dist/candlestick/pane-state.d.ts +19 -0
  14. package/dist/candlestick/panel-resize.d.ts +4 -0
  15. package/dist/candlestick/price-style.d.ts +9 -0
  16. package/dist/candlestick/renderers/candles.d.ts +5 -0
  17. package/dist/candlestick/renderers/grid.d.ts +2 -0
  18. package/dist/candlestick/renderers/index.d.ts +20 -0
  19. package/dist/candlestick/renderers/indicators.d.ts +30 -0
  20. package/dist/candlestick/renderers/overlays.d.ts +8 -0
  21. package/dist/candlestick/renderers/price-line.d.ts +4 -0
  22. package/dist/candlestick/viewport.d.ts +28 -0
  23. package/dist/canvas-chart-base.d.ts +23 -0
  24. package/dist/category-axis.d.ts +9 -0
  25. package/dist/category-layout.d.ts +9 -0
  26. package/dist/chart-shell.d.ts +14 -0
  27. package/dist/combo-chart/combo-chart.d.ts +26 -0
  28. package/dist/combo-chart/index.d.ts +2 -0
  29. package/dist/combo-chart/index.js +1 -0
  30. package/dist/combo-chart/types.d.ts +75 -0
  31. package/dist/config.d.ts +94 -0
  32. package/dist/line-chart/index.d.ts +2 -0
  33. package/dist/line-chart/index.js +1 -0
  34. package/dist/line-chart/line-chart.d.ts +21 -0
  35. package/dist/line-chart/types.d.ts +30 -0
  36. package/dist/pie-chart/index.d.ts +2 -0
  37. package/dist/pie-chart/index.js +1 -0
  38. package/dist/pie-chart/pie-chart.d.ts +21 -0
  39. package/dist/pie-chart/types.d.ts +26 -0
  40. package/dist/plot-frame.d.ts +19 -0
  41. package/dist/renderers/helpers.d.ts +17 -0
  42. package/dist/renderers/primitives/axis-pills.d.ts +11 -0
  43. package/dist/renderers/primitives/context.d.ts +36 -0
  44. package/dist/renderers/primitives/decorations.d.ts +44 -0
  45. package/dist/renderers/primitives/grouped-bars.d.ts +43 -0
  46. package/dist/renderers/primitives/index.d.ts +5 -0
  47. package/dist/renderers/primitives/marks.d.ts +40 -0
  48. package/dist/renderers/value-axis.d.ts +18 -0
  49. package/dist/scale.d.ts +36 -0
  50. package/dist/types.d.ts +53 -0
  51. package/package.json +67 -0
package/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ Copyright (c) 2026 Indiminds Technologies LLP. All rights reserved.
2
+
3
+ This software and its associated documentation files (the "Software") are the intellectual property of Indiminds Technologies LLP.
4
+
5
+ Permission is hereby granted to download and view the source code via public registries like npm for evaluation purposes only.
6
+
7
+ STRICTLY PROHIBITED ACTIONS:
8
+ 1. Modification: You may not modify, adapt, alter, or create derivative works based on this Software.
9
+ 2. Distribution: You may not redistribute, sub-license, rent, lease, or sell this Software, in whole or in part, to any third party.
10
+ 3. Production/Commercial Use: You may not use this Software in any production, commercial, or revenue-generating environment without explicit written consent from the copyright holder.
11
+
12
+ Any use of this Software outside of evaluation requires a custom, explicit commercial license agreement. For licensing inquiries, please contact: sonu@indiminds.com.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+
2
+
3
+
4
+ # @indiminds/charts
5
+
6
+ A canvas-based charting engine for React/Next.js (and any other) apps —
7
+ zero dependency on any other charting library, zero dependency on Rust or
8
+ WebAssembly. Five chart types, each importable on its own:
9
+
10
+ ```
11
+ @indiminds/charts → CandlestickChart (candles + indicators + panes)
12
+ @indiminds/charts/bar → BarChart
13
+ @indiminds/charts/combo → ComboChart (grouped bars + line overlays)
14
+ @indiminds/charts/line → LineChart
15
+ @indiminds/charts/pie → PieChart (and donut)
16
+ ```
17
+
18
+ Import only the one you need — `./bar`, `./line`, etc. never pull in the
19
+ candlestick engine's pan/zoom/indicator machinery. `.` (the default import)
20
+ is the full trading-chart bundle.
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ npm install @indiminds/charts
26
+ ```
27
+
28
+ No peer dependencies. Every chart is a plain class that draws onto a
29
+ `<canvas>` you own — bring your own React (or Vue, or vanilla JS) wrapper.
30
+
31
+ ## Is this a Rust/WASM library?
32
+
33
+ No. Any indicator math (RSI, EMA, …) you see in the Indiminds trading app
34
+ comes from a separate Rust/WASM engine that has nothing to do with this
35
+ package. `CandlestickChart.setIndicators()` takes plain numbers — where
36
+ those numbers came from is entirely up to you. This package's only job is
37
+ turning numbers into pixels.
38
+
39
+ ## Quick start — a line chart
40
+
41
+ ```ts
42
+ import { LineChart } from "@indiminds/charts/line";
43
+
44
+ const canvas = document.querySelector("canvas")!;
45
+ const chart = new LineChart(canvas, canvas.parentElement!);
46
+
47
+ chart.setData({
48
+ categories: ["Mon", "Tue", "Wed", "Thu", "Fri"],
49
+ series: [
50
+ { id: "revenue", color: "#3b82f6", axis: "left", values: [12, 19, 14, 22, 25] },
51
+ ],
52
+ });
53
+
54
+ chart.onHoverChange = (event) => {
55
+ // event is null when the cursor leaves the canvas, otherwise
56
+ // { index, category, x, points: [{ id, value, x, y }, ...] }
57
+ };
58
+
59
+ // later, when the component unmounts:
60
+ chart.destroy();
61
+ ```
62
+
63
+ Every chart follows this same shape: construct with `(canvas, container)`,
64
+ call `setData(...)` whenever your data changes, read `onHoverChange` for
65
+ hover state. **The canvas draws; you own the tooltip** — every hover event
66
+ gives you screen coordinates (`x`/`y`) but never renders DOM itself, so it
67
+ fits whatever UI library you're already using.
68
+
69
+ ## The five chart types
70
+
71
+ ### `CandlestickChart` (default import)
72
+
73
+ The full trading chart: candles, OHLC bars, Heikin Ashi, line or area price
74
+ display (`setPriceStyle()`), indicator panes, pan/zoom, infinite-scroll
75
+ history.
76
+
77
+ ```ts
78
+ import { CandlestickChart } from "@indiminds/charts";
79
+
80
+ const chart = new CandlestickChart(canvas, container);
81
+ chart.setData(candles); // CandleData[] — { time, open, high, low, close, volume? }
82
+ chart.setPriceStyle("heikin-ashi"); // "candles" | "bars" | "heikin-ashi" | "line" | "area"
83
+ chart.setIndicators(renderableIndicators); // see RenderableIndicator in @indiminds/chart-types
84
+ chart.onCrosshairMove = (event) => { /* { candle, index } | null */ };
85
+ chart.onLayoutChange = (layout) => { /* pane geometry, for positioning DOM overlays */ };
86
+ chart.onLoadMore = (oldestTime) => { /* fetch older candles, then call setData again */ };
87
+ ```
88
+
89
+ ### `BarChart` (`./bar`)
90
+
91
+ Grouped or delta (before/after) bars over a fixed list of category labels.
92
+ No panning — you always see everything you pass in.
93
+
94
+ ```ts
95
+ import { BarChart } from "@indiminds/charts/bar";
96
+
97
+ chart.setData({
98
+ categories: ["A", "B", "C"],
99
+ series: [{ id: "s1", color: "#3b82f6", values: [10, 20, 15] }],
100
+ referenceLine: { category: "B", label: "Current" }, // optional
101
+ });
102
+ ```
103
+
104
+ ### `ComboChart` (`./combo`)
105
+
106
+ Grouped bars *plus* dual-axis line overlays *plus* marker pills — for when
107
+ one chart needs to carry more than one kind of series (e.g. a quantity as
108
+ bars and a ratio as a line, one shared category axis).
109
+
110
+ ### `LineChart` (`./line`)
111
+
112
+ A handful of line series on two independent Y-axes (`axis: "left" | "right"`
113
+ per series), with a left-to-right reveal animation on first draw.
114
+
115
+ ### `PieChart` (`./pie`)
116
+
117
+ ```ts
118
+ import { PieChart } from "@indiminds/charts/pie";
119
+
120
+ chart.setData({
121
+ slices: [
122
+ { id: "a", label: "Alpha", value: 42, color: "#3b82f6" },
123
+ { id: "b", label: "Beta", value: 27, color: "#f59e0b" },
124
+ ],
125
+ innerRadiusRatio: 0.65, // omit (or 0) for a full pie; >0 draws a donut
126
+ centerLabel: "69 total", // shown in the donut's hole
127
+ });
128
+
129
+ chart.onHoverChange = (event) => {
130
+ // { index, slice, fraction, x, y } | null — fraction is the slice's
131
+ // share of the total, 0-1
132
+ };
133
+ ```
134
+
135
+ ## Theming
136
+
137
+ Every chart reads its colors from a small set of CSS custom properties on
138
+ `document.documentElement` (`--chart-grid`, `--chart-axis`, `--up`, `--down`,
139
+ etc.) and re-reads them on theme change — set these once in your own
140
+ stylesheet and every chart follows your app's palette automatically. A
141
+ chart with no theme set falls back to a sensible dark-mode default. See
142
+ `src/types.ts`'s `COLORS`/`THEMED_TOKENS` for the full token list.
143
+
144
+ ## License
145
+
146
+ Proprietary — evaluation use only. See `LICENSE` for the full terms;
147
+ production/commercial use requires a separate agreement with Indiminds
148
+ Technologies LLP (sonu@indiminds.com).
149
+
150
+ ## Contributing / internal development
151
+
152
+ This package lives inside the Indiminds Back-Testing-UI monorepo. See the
153
+ repo root's `architecture.md` §3 for the internal folder structure and the
154
+ rules for adding a new chart type.
155
+
156
+ ```bash
157
+ pnpm --filter @indiminds/charts build # emits dist/ + .d.ts
158
+ pnpm --filter @indiminds/charts typecheck
159
+ ```
@@ -0,0 +1,26 @@
1
+ import { CanvasChartEngine } from "../canvas-chart-base";
2
+ import type { BarChartData, BarHoverEvent } from "./types";
3
+ /** Bars over a fixed list of labelled categories. There's no panning or
4
+ * zooming — you always see everything the caller passed in. */
5
+ export declare class BarChart extends CanvasChartEngine<BarHoverEvent> {
6
+ private data;
7
+ setData(data: BarChartData): void;
8
+ protected render(): void;
9
+ /** Every series carrying `baseValues` means the caller handed us before-and-
10
+ * after numbers, which draw as change bars rather than plain ones. */
11
+ private drawBars;
12
+ /** Vertical guide at one category (e.g. where the current price falls) with
13
+ * a labeled pill in the reserved top gutter. */
14
+ private drawReferenceLine;
15
+ /** Pill callouts above specific bars, pre-formatted by the caller. Several
16
+ * annotations on one category sit side by side above the tallest of their
17
+ * bars rather than stacking. */
18
+ private drawAnnotations;
19
+ /** Highest drawn point (smallest y) of the bars an annotation group points
20
+ * at. A delta bar's hollow cap reaches its old, larger value — anchor there
21
+ * too, or the pill lands inside the outline instead of above it. */
22
+ private annotatedBarTop;
23
+ /** Only the guide line — the value breakdown is a DOM tooltip owned by
24
+ * whoever consumes onHoverChange, which x/y let it position itself. */
25
+ private drawHover;
26
+ }
@@ -0,0 +1,2 @@
1
+ export { BarChart } from "./bar-chart";
2
+ export type { BarAnnotation, BarChartData, BarFillMode, BarHoverEvent, BarSeries, ReferenceLineSpec, } from "./types";
@@ -0,0 +1 @@
1
+ var te=Object.defineProperty;var ne=(e,r,t)=>r in e?te(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t;var f=(e,r,t)=>ne(e,typeof r!="symbol"?r+"":r,t);var x={bullish:"#22c55e",bearish:"#ef4444",grid:"#313150",text:"#8b8b93",crosshair:"#a1a1aa",priceLine:"#3b82f6",pill:"#313150",pillText:"#ffffff",divider:"#505064",dividerActive:"#8c8cb9",background:"transparent"},re=[["--chart-grid","grid"],["--chart-axis","text"],["--chart-crosshair","crosshair"],["--chart-1","priceLine"],["--chart-pill","pill"],["--chart-pill-foreground","pillText"],["--chart-divider","divider"],["--chart-divider-active","dividerActive"]];function I(){if(typeof document>"u")return;let e=getComputedStyle(document.documentElement);for(let[r,t]of re){let n=e.getPropertyValue(r).trim();n&&(x[t]=n)}}function Y(e,r,t){let n=e.getContext("2d");if(!n)throw new Error("Could not get 2D context");let o=new ResizeObserver(l=>{if(l.length===0)return;let{width:s,height:u}=l[0].contentRect;requestAnimationFrame(()=>t.onResize(Math.floor(s),Math.floor(u)))});o.observe(r),I();let a=new MutationObserver(()=>{I(),t.onThemeChange()});a.observe(document.documentElement,{attributes:!0,attributeFilter:["class"]});function i(l,s){let u=window.devicePixelRatio||1;n.setTransform(1,0,0,1,0,0),e.width=Math.floor(l*u),e.height=Math.floor(s*u),e.style.width=`${l}px`,e.style.height=`${s}px`,n.scale(u,u)}return{ctx:n,resizeBackingStore:i,destroy:()=>{o.disconnect(),a.disconnect()}}}var L=class{constructor(r,t){f(this,"canvas");f(this,"shell");f(this,"width",0);f(this,"height",0);f(this,"mouseX",null);f(this,"mouseY",null);f(this,"animationFrame",null);f(this,"onHoverChange");f(this,"handleMouseMove",r=>{let t=this.canvas.getBoundingClientRect();this.mouseX=r.clientX-t.left,this.mouseY=r.clientY-t.top,this.render()});f(this,"handleMouseLeave",()=>{this.mouseX=null,this.mouseY=null,this.onHoverChange?.(null),this.render()});this.canvas=r,this.shell=Y(r,t,{onResize:(n,o)=>this.resize(n,o),onThemeChange:()=>this.render()}),r.addEventListener("mousemove",this.handleMouseMove),r.addEventListener("mouseleave",this.handleMouseLeave)}destroy(){this.cancelAnimation(),this.canvas.removeEventListener("mousemove",this.handleMouseMove),this.canvas.removeEventListener("mouseleave",this.handleMouseLeave),this.shell.destroy()}animate(r,t,n){this.cancelAnimation();let o=performance.now(),a=i=>{let l=Math.min(1,(i-o)/r);n(1-(1-l)**t),this.render(),this.animationFrame=l<1?requestAnimationFrame(a):null};this.animationFrame=requestAnimationFrame(a)}cancelAnimation(){this.animationFrame!=null&&(cancelAnimationFrame(this.animationFrame),this.animationFrame=null)}resize(r,t){r===0||t===0||(this.width=r,this.height=t,this.shell.resizeBackingStore(r,t),this.render())}};var S={layout:{priceScaleWidth:60,timeScaleHeight:30},zoom:{default:100,min:10,max:800,inFactor:.95,outFactor:1.05},pan:{wheelSensitivity:1,linesToPx:16},viewport:{rightPaddingRatio:.2,maxLeftEmptyRatio:.8},infiniteScroll:{triggerThreshold:50},priceScale:{paddingRatio:.1},pane:{defaultHeight:120,minHeight:60,minChartHeight:120,handleHitZone:6}},R=S.layout,ve=S.zoom,ye=S.viewport,Ce=S.pan,Me=S.infiniteScroll,Se=S.priceScale,we=S.pane;var w="11px Inter, sans-serif",F="bold 11px Inter, sans-serif";function B(e,r){let{ctx:t}=e;t.save(),t.beginPath(),t.rect(0,e.top,e.chartWidth,e.height),t.clip(),t.beginPath(),r(t),t.restore()}function k(e,r,t){return Math.max(r/2,Math.min(e,t-r/2))}function X(e,r,t,n){e.save(),e.strokeStyle=x.crosshair,e.lineWidth=1,e.setLineDash([4,4]),e.beginPath(),e.moveTo(r,t),e.lineTo(r,n),e.stroke(),e.restore()}function oe(e,r){let t=e/Math.max(1,r),n=Math.pow(10,Math.floor(Math.log10(Math.max(t,1e-10)))),o=t/n,a;return o<1.5?a=1:o<3.5?a=2:o<7.5?a=5:a=10,a*n}function V(e,r=t=>t.toFixed(2)){let{ctx:t}=e,n=e.extent.max-e.extent.min;if(n<=0)return;let o=oe(n,Math.max(2,Math.round(e.height/40))),a=Math.ceil(e.extent.min/o)*o;t.save(),t.lineWidth=.5,t.globalAlpha=1,t.setLineDash([]),t.font=w,t.textBaseline="middle",t.textAlign="left";for(let i=a;i<=e.extent.max;i+=o){let l=e.toY(i);l<=e.top||l>=e.top+e.height||(t.strokeStyle=x.grid,t.beginPath(),t.moveTo(0,l),t.lineTo(e.chartWidth,l),t.stroke(),t.fillStyle=x.text,t.fillText(r(i),e.chartWidth+8,l))}t.restore()}function N(e,r,t,n,o,a={}){let{paddingX:i=6,height:l=16,radius:s=4}=a;e.font=o.font??w,e.textBaseline="middle",e.textAlign="center";let u=e.measureText(n).width+i*2;return e.fillStyle=o.background,ae(e,r-u/2,t,u,l,s),e.fill(),e.fillStyle=o.text,e.fillText(n,r,t+l/2),u}function ae(e,r,t,n,o,a){e.beginPath(),e.moveTo(r+a,t),e.arcTo(r+n,t,r+n,t+o,a),e.arcTo(r+n,t+o,r,t+o,a),e.arcTo(r,t+o,r,t,a),e.arcTo(r,t,r+n,t,a),e.closePath()}function z(e,r,t,n){let o=e.candleWidth*t,a=o/r,i=Math.max(1,a*(1-n));return{groupWidth:o,slotWidth:a,barWidth:i}}function $(e,r,t){return e-r.groupWidth/2+t*r.slotWidth+(r.slotWidth-r.barWidth)/2}var G=new Map;function K(e,r){let t=G.get(r);if(t)return t;let n=document.createElement("canvas");n.width=6,n.height=6;let o=n.getContext("2d");if(!o)return r;o.strokeStyle=r,o.lineWidth=1.5,o.beginPath(),o.moveTo(0,6),o.lineTo(6,0),o.moveTo(-1.5,1.5),o.lineTo(1.5,-1.5),o.moveTo(4.5,7.5),o.lineTo(7.5,4.5),o.stroke();let a=e.createPattern(n,"repeat");return a?(G.set(r,a),a):r}var ie=.45;function le(e,r){if(!/^#[0-9a-fA-F]{6}$/.test(e))return e;let t=parseInt(e.slice(1,3),16),n=parseInt(e.slice(3,5),16),o=parseInt(e.slice(5,7),16),a=i=>Math.round(i+(255-i)*r);return`rgb(${a(t)}, ${a(n)}, ${a(o)})`}function U(e,r,t={}){if(r.length===0)return;let{baseline:n=0,groupWidthRatio:o=.7,barGapRatio:a=.15,focusIndex:i=null,growProgress:l=1,slotCount:s,slotOffset:u=0}=t;B(e,h=>{h.globalAlpha=1,h.setLineDash([]);let c=e.toY(n),m=z(e,s??r.length,o,a),p=Math.min(e.endIndex,...r.map(d=>d.values.length));for(let d=Math.max(0,e.startIndex);d<p;d++){let b=e.toX(d),g=i!=null&&d===i;r.forEach((v,A)=>{let T=v.values[d];if(T==null||!Number.isFinite(T))return;let C=v.fillModes?.[d]??"solid",M=e.toY(T),y=$(b,m,u+A),D=Math.max(1,Math.abs(M-c)*l),W=M<=c?c-D:c,H=v.colors?.[d]??v.color,P=g?le(H,ie):H;if(C==="outline"){h.strokeStyle=P,h.lineWidth=g?2:1.5,h.strokeRect(y+.75,W+.75,Math.max(0,m.barWidth-1.5),Math.max(0,D-1.5));return}h.fillStyle=C==="hatched"?K(h,P):P,h.fillRect(y,W,m.barWidth,D),C==="hatched"&&(h.strokeStyle=P,h.lineWidth=1,h.strokeRect(y+.5,W+.5,Math.max(0,m.barWidth-1),Math.max(0,D-1))),g&&(h.strokeStyle=P,h.lineWidth=1.5,h.strokeRect(y+.75,W+.75,Math.max(0,m.barWidth-1.5),Math.max(0,D-1.5)))})}})}function Z(e,r,t={}){if(r.length===0)return;let{baseline:n=0,groupWidthRatio:o=.7,barGapRatio:a=.15}=t;B(e,i=>{i.globalAlpha=1,i.setLineDash([]);let l=e.toY(n),s=z(e,r.length,o,a),u=Math.min(e.endIndex,...r.map(h=>Math.min(h.from.length,h.to.length)));for(let h=Math.max(0,e.startIndex);h<u;h++){let c=e.toX(h);r.forEach((m,p)=>{let d=m.from[h],b=m.to[h];if(d==null||b==null||!Number.isFinite(d)||!Number.isFinite(b))return;let g=$(c,s,p),v=Math.min(d,b),A=Math.max(d,b),T=b>=d,C=e.toY(v),M=e.toY(A);if(i.fillStyle=m.color,i.fillRect(g,Math.min(C,l),s.barWidth,Math.max(1,Math.abs(C-l))),Math.abs(A-v)<1e-9)return;let y=Math.max(1,C-M);T?(i.fillStyle=K(i,m.color),i.fillRect(g,M,s.barWidth,y),i.strokeStyle=m.color,i.lineWidth=1,i.strokeRect(g+.5,M+.5,Math.max(0,s.barWidth-1),Math.max(0,y-1))):(i.strokeStyle=m.color,i.lineWidth=1.5,i.strokeRect(g+.75,M+.75,Math.max(0,s.barWidth-1.5),Math.max(0,y-1.5)))})}})}function q(e,r,t,n){if(e.mode==="price")return null;let o=1/0,a=-1/0;for(let c of r){let m=Math.min(n,c.length);for(let p=Math.max(0,t);p<m;p++){let d=c[p];d==null||!Number.isFinite(d)||(d<o&&(o=d),d>a&&(a=d))}}let i=Number.isFinite(o)&&Number.isFinite(a);if(e.mode==="auto"){if(!i)return null;let c=Math.max(a-o,1e-10),m=Math.max(c*e.cushionRatio,e.minCushion??0);return{min:o-m,max:a+m}}if(!i){let[c,m]=e.alwaysInclude??[e.min,e.max];return{min:c,max:m}}let l=a-o,s=Math.max(l*e.cushionRatio,e.minCushion??0),u=o-s,h=a+s;return e.alwaysInclude&&(u=Math.min(u,e.alwaysInclude[0]),h=Math.max(h,e.alwaysInclude[1])),{min:Math.max(u,e.min),max:Math.min(h,e.max)}}function J(e,r,t,n=8,o=8){let a=Math.max(1,t-n-o),i=Math.max(e.max-e.min,1e-10);return l=>r+n+(e.max-l)/i*a}function j(e,r,t){if(r.length===0)return;let{ctx:n}=e;n.save(),n.font=w,n.textBaseline="top",n.textAlign="center";let o=24;for(let s of r)o=Math.max(o,n.measureText(s).width);let a=Math.max(1,Math.ceil((o+12)/e.candleWidth)),i=Math.min(e.endIndex,r.length),l=Math.ceil(Math.max(0,e.startIndex)/a)*a;for(let s=l;s<i;s+=a){let u=e.toX(s);u<0||u>e.chartWidth||(n.strokeStyle=x.grid,n.lineWidth=.5,n.beginPath(),n.moveTo(u,0),n.lineTo(u,t),n.stroke(),n.fillStyle=x.text,n.fillText(r[s],u,t+8))}n.restore()}function Q(e,r){let t=e>0?r/e:r;return{slotWidth:t,toX:n=>n*t+t/2}}var E=0,O=26,se=600;function ee(e,r,t){if(!e)return r;let n=new Array(r.length);for(let o=0;o<r.length;o++){let a=e[o],i=r[o],l=a!=null&&i!=null&&Number.isFinite(a)&&Number.isFinite(i);n[o]=l?a+(i-a)*t:i}return n}function ue(e,r,t){return{...r,series:r.series.map((n,o)=>{let a=e.series[o];return{...n,values:ee(a?.values,n.values,t),baseValues:n.baseValues&&ee(a?.baseValues,n.baseValues,t)}})}}function he(e,r){return e.categories.length===r.categories.length&&e.series.length===r.series.length&&e.categories.every((t,n)=>t===r.categories[n])}var _=class extends L{constructor(){super(...arguments);f(this,"data",{categories:[],series:[]})}setData(t){let n=this.data;if(n.categories.length===0||!he(n,t)){this.data=t,this.render();return}this.animate(se,1.5,o=>{this.data=ue(n,t,o)})}render(){let{ctx:t}=this.shell;if(this.width===0||this.height===0)return;t.clearRect(0,0,this.width,this.height);let{categories:n,series:o,valueFormatter:a=p=>p.toLocaleString()}=this.data;if(n.length===0)return;let i=this.width-R.priceScaleWidth,l=this.height-R.timeScaleHeight-O,s=Q(n.length,i),u=o.flatMap(p=>p.baseValues?[p.values,p.baseValues]:[p.values]),h=q({mode:"auto",cushionRatio:.12},u,0,n.length)??{min:0,max:1},c={min:Math.min(h.min,E),max:Math.max(h.max,E)},m={ctx:t,chartWidth:i,priceScaleWidth:R.priceScaleWidth,top:O,height:l,extent:c,candleWidth:s.slotWidth,startIndex:0,endIndex:n.length,toX:s.toX,toY:J(c,O,l)};V(m,a),j(m,n,O+l),this.drawBars(m,o),this.drawAnnotations(m,n),this.drawReferenceLine(m,n),this.drawHover(m,n)}drawBars(t,n){if(n.every(a=>a.baseValues)){let a=n.map(i=>({color:i.color,from:i.baseValues,to:i.values}));Z(t,a,{baseline:E});return}let o=n.map(a=>({color:a.color,values:a.values,fillModes:a.fillModes}));U(t,o,{baseline:E})}drawReferenceLine(t,n){let o=this.data.referenceLine;if(!o)return;let a=n.indexOf(o.category);if(a<0)return;let i=t.toX(a),{ctx:l}=t;l.save(),l.strokeStyle=o.color??x.text,l.globalAlpha=.55,l.lineWidth=1,l.setLineDash([]),l.beginPath(),l.moveTo(i,0),l.lineTo(i,t.top+t.height),l.stroke(),l.restore(),l.save(),l.font=F;let s=l.measureText(o.label).width+16;l.restore();let u=k(i,s,t.chartWidth);N(l,u,3,o.label,{background:x.pill,text:x.pillText,font:F},{paddingX:8,height:20,radius:5})}drawAnnotations(t,n){let o=this.data.annotations;if(!o||o.length===0)return;let{ctx:a}=t,i=4,l=16;a.save(),a.font=w;for(let[s,u]of me(o,n.length)){let h=this.annotatedBarTop(t,s,u),c=Math.max(t.top+2,h-l-i),m=u.map(b=>a.measureText(b.label).width+12),p=m.reduce((b,g)=>b+g,0)+i*(u.length-1),d=k(t.toX(s),p,t.chartWidth)-p/2;u.forEach((b,g)=>{let v=N(a,d+m[g]/2,c,b.label,{background:b.background,text:b.color});d+=v+i})}a.restore()}annotatedBarTop(t,n,o){let a=t.top+t.height;for(let i of o){let l=this.data.series.find(c=>c.id===i.seriesId),s=l?.values[n];if(s==null||!Number.isFinite(s))continue;let u=l?.baseValues?.[n],h=u!=null&&Number.isFinite(u)?Math.max(s,u):s;a=Math.min(a,t.toY(h))}return a}drawHover(t,n){if(this.mouseX==null)return;let o=Math.floor(this.mouseX/t.candleWidth),a=Math.max(0,Math.min(n.length-1,o)),i=n[a];if(i==null)return;let l=t.toX(a);X(t.ctx,l,0,t.top+t.height),this.onHoverChange?.({index:a,category:i,x:l,y:this.mouseY??0})}};function me(e,r){let t=new Map;for(let n of e){if(n.index<0||n.index>=r)continue;let o=t.get(n.index)??[];o.push(n),t.set(n.index,o)}return t}export{_ as BarChart};
@@ -0,0 +1,48 @@
1
+ import type { BarFillMode } from "../renderers/primitives/grouped-bars";
2
+ export type { BarFillMode };
3
+ export interface BarSeries {
4
+ id: string;
5
+ color: string;
6
+ /** One entry per category, aligned by index to BarChartData.categories. */
7
+ values: readonly (number | null)[];
8
+ /** Per-bar style, aligned to `values`. Missing = "solid". Ignored when
9
+ * `baseValues` is set, since that draws before-and-after bars instead. */
10
+ fillModes?: readonly BarFillMode[];
11
+ /** The previous values. Supplying them draws each bar as a change from its
12
+ * old value to its new one. */
13
+ baseValues?: readonly (number | null)[];
14
+ }
15
+ /** A single labeled vertical guide, e.g. "current price" pinned to whichever
16
+ * category it currently falls on. */
17
+ export interface ReferenceLineSpec {
18
+ category: string;
19
+ label: string;
20
+ color?: string;
21
+ }
22
+ /** A small label above one bar. The text comes ready-made — the chart only
23
+ * places it. Several on one category sit side by side, so keep them short. */
24
+ export interface BarAnnotation {
25
+ index: number;
26
+ seriesId: string;
27
+ label: string;
28
+ background: string;
29
+ color: string;
30
+ }
31
+ export interface BarChartData {
32
+ /** X-axis labels, in display order. */
33
+ categories: readonly string[];
34
+ series: readonly BarSeries[];
35
+ /** How to write the numbers. Defaults to a plain formatted number; pass your
36
+ * own for units or percentages. */
37
+ valueFormatter?: (value: number) => string;
38
+ referenceLine?: ReferenceLineSpec;
39
+ annotations?: readonly BarAnnotation[];
40
+ }
41
+ export interface BarHoverEvent {
42
+ index: number;
43
+ category: string;
44
+ /** Where on the canvas the hover landed, so a caller can place its own
45
+ * tooltip there. */
46
+ x: number;
47
+ y: number;
48
+ }
@@ -0,0 +1,73 @@
1
+ import type { RenderableIndicator } from "@indiminds/chart-types";
2
+ import { type PriceStyle } from "./price-style";
3
+ import type { CandleData, CrosshairEvent, LayoutSnapshot } from "../types";
4
+ export type { CandleData, CrosshairEvent, LayoutSnapshot };
5
+ export type { RenderableIndicator };
6
+ export type { PriceStyle };
7
+ export declare class CandlestickChart {
8
+ private renderer;
9
+ private data;
10
+ /** Snapshot of `data` as of the last setData() call — not `data` itself,
11
+ * since a caller may mutate its array in place instead of replacing it. */
12
+ private lastSnapshot;
13
+ private priceStyle;
14
+ /** Heikin Ashi runs over the whole series, so it's built once per data
15
+ * change rather than per frame. Null means "not built yet". */
16
+ private heikinAshiData;
17
+ /** Every indicator on the chart, in drawing terms only. */
18
+ private indicators;
19
+ private paneGroups;
20
+ private priceOverlays;
21
+ /** The value range each pane drew with this frame — reused by the crosshair. */
22
+ private paneExtents;
23
+ private viewport;
24
+ private paneState;
25
+ private mouse;
26
+ private detachMouseEvents;
27
+ private detachPanelResizer;
28
+ private shell;
29
+ private layout;
30
+ private lastWidth;
31
+ private lastHeight;
32
+ /** Stops us asking for older candles again while a fetch is still running. */
33
+ private isLoadingHistory;
34
+ onLoadMore?: (oldestTime: number) => void;
35
+ onCrosshairMove?: (event: CrosshairEvent) => void;
36
+ /** Fires when pane geometry changes, so DOM overlays can follow it. */
37
+ onLayoutChange?: (layout: LayoutSnapshot) => void;
38
+ constructor(canvas: HTMLCanvasElement, container: HTMLElement);
39
+ /** `forceReset` is for when the caller *knows* the history was swapped
40
+ * wholesale (a symbol or interval change). Length-based inference can't
41
+ * see that on its own: two timeframes often return a similar bar count,
42
+ * which reads as a live tick and would leave the viewport scrolled where
43
+ * the previous timeframe left it. */
44
+ setData(newData: CandleData[], forceReset?: boolean): void;
45
+ /** How the price series itself is drawn. Indicators and panes are
46
+ * unaffected — they plot the same values whatever this is set to. */
47
+ setPriceStyle(style: PriceStyle): void;
48
+ /** What the price pane actually plots: the candles as they traded, or the
49
+ * Heikin Ashi transform of them. */
50
+ private displaySeries;
51
+ /** The only way to put indicators on the chart. Panes are only re-laid out
52
+ * when one appears or disappears — new values alone move nothing. */
53
+ setIndicators(indicators: RenderableIndicator[]): void;
54
+ /** Read-only view of what is currently on the chart — used by the legend. */
55
+ getIndicators(): readonly RenderableIndicator[];
56
+ destroy(): void;
57
+ /** Commit a divider drag: remember the new height and re-lay out. */
58
+ private applyPaneResize;
59
+ /** Recompute where each pane sits and tell listeners about the new geometry. */
60
+ private applyPaneLayout;
61
+ /** `force` skips the "same size, nothing to do" check — setIndicators() needs
62
+ * that when panes come and go at an unchanged canvas size. */
63
+ private resize;
64
+ /** Builds the argument bundle every draw primitive takes — one per pane,
65
+ * per frame. */
66
+ private makeDrawContext;
67
+ private render;
68
+ /** Turns a Y position inside a sub-pane back into that pane's value, for the
69
+ * crosshair label. Reuses the range the pane just drew with, so the two agree. */
70
+ private paneValueAt;
71
+ /** Which pane (if any) a canvas Y falls in — used by legend overlays. */
72
+ paneIdAt(y: number): string | null;
73
+ }
@@ -0,0 +1,29 @@
1
+ import type { CandleData } from "../types";
2
+ /**
3
+ * What kind of update setData() just got:
4
+ * "reset" — start over (first load, or a different symbol/interval)
5
+ * "historical" — older candles added to the front
6
+ * "live" — the newest candle changed, or one was added
7
+ * "noop" — nothing changed
8
+ */
9
+ export type SyncAction = "reset" | "historical" | "live" | "noop";
10
+ export interface SyncResult {
11
+ action: SyncAction;
12
+ /** How many candles were added to the end (only meaningful for "live"). */
13
+ candlesAdded: number;
14
+ }
15
+ /**
16
+ * A cheap stand-in for the previous candle array — just enough to classify
17
+ * the next update. Deliberately not the array itself: a caller may mutate
18
+ * its candle array in place (push/replace-last) rather than copying it on
19
+ * every tick, so by the time setData() runs again the old array reference
20
+ * would already reflect the new data too, making a reference-based diff
21
+ * meaningless.
22
+ */
23
+ export interface CandleSnapshot {
24
+ length: number;
25
+ lastTime: number;
26
+ }
27
+ export declare function snapshotOf(data: CandleData[]): CandleSnapshot | null;
28
+ /** Works out what changed since the last snapshot. */
29
+ export declare function resolveDataUpdate(newData: CandleData[], previous: CandleSnapshot | null): SyncResult;
@@ -0,0 +1,2 @@
1
+ export { CandlestickChart } from "./candlestick-chart";
2
+ export type { CandleData, CrosshairEvent, LayoutSnapshot, PriceStyle, RenderableIndicator, } from "./candlestick-chart";
@@ -0,0 +1 @@
1
+ var Fe=Object.defineProperty;var Ne=(e,t,n)=>t in e?Fe(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var g=(e,t,n)=>Ne(e,typeof t!="symbol"?t+"":t,n);var y={bullish:"#22c55e",bearish:"#ef4444",grid:"#313150",text:"#8b8b93",crosshair:"#a1a1aa",priceLine:"#3b82f6",pill:"#313150",pillText:"#ffffff",divider:"#505064",dividerActive:"#8c8cb9",background:"transparent"},_e=[["--chart-grid","grid"],["--chart-axis","text"],["--chart-crosshair","crosshair"],["--chart-1","priceLine"],["--chart-pill","pill"],["--chart-pill-foreground","pillText"],["--chart-divider","divider"],["--chart-divider-active","dividerActive"]];function Z(){if(typeof document>"u")return;let e=getComputedStyle(document.documentElement);for(let[t,n]of _e){let r=e.getPropertyValue(t).trim();r&&(y[n]=r)}}function ue(e,t,n){let r=e.getContext("2d");if(!r)throw new Error("Could not get 2D context");let i=new ResizeObserver(s=>{if(s.length===0)return;let{width:u,height:d}=s[0].contentRect;requestAnimationFrame(()=>n.onResize(Math.floor(u),Math.floor(d)))});i.observe(t),Z();let o=new MutationObserver(()=>{Z(),n.onThemeChange()});o.observe(document.documentElement,{attributes:!0,attributeFilter:["class"]});function a(s,u){let d=window.devicePixelRatio||1;r.setTransform(1,0,0,1,0,0),e.width=Math.floor(s*d),e.height=Math.floor(u*d),e.style.width=`${s}px`,e.style.height=`${u}px`,r.scale(d,d)}return{ctx:r,resizeBackingStore:a,destroy:()=>{i.disconnect(),o.disconnect()}}}var L={layout:{priceScaleWidth:60,timeScaleHeight:30},zoom:{default:100,min:10,max:800,inFactor:.95,outFactor:1.05},pan:{wheelSensitivity:1,linesToPx:16},viewport:{rightPaddingRatio:.2,maxLeftEmptyRatio:.8},infiniteScroll:{triggerThreshold:50},priceScale:{paddingRatio:.1},pane:{defaultHeight:120,minHeight:60,minChartHeight:120,handleHitZone:6}},U=L.layout,R=L.zoom,K=L.viewport,O=L.pan,de=L.infiniteScroll,J=L.priceScale,W=L.pane;function me(e){return e.length===0?null:{length:e.length,lastTime:e[e.length-1].time}}function ce(e,t){let n=e.length;if(!t||t.length===0)return{action:"reset",candlesAdded:n};if(n>t.length&&e[n-1].time===t.lastTime)return{action:"historical",candlesAdded:0};let i=n-t.length;return i===0||i===1?{action:"live",candlesAdded:i}:{action:"reset",candlesAdded:n}}function Ge(e,t){switch(e){case 1:return O.linesToPx;case 2:return t;default:return 1}}function pe(e,t,n,r){let i=l=>{l.preventDefault();let p=e.getBoundingClientRect(),h=Ge(l.deltaMode,e.clientHeight),c=l.deltaX*h,f=l.deltaY*h;l.shiftKey?t.pan(-f*O.wheelSensitivity):Math.abs(c)>Math.abs(f)?t.pan(-c*O.wheelSensitivity):t.zoom(f,l.clientX-p.left),r()},o=!1,a=0,s=l=>{o=!0,a=l.clientX,e.style.cursor="grabbing"},u=()=>{o=!1,e.style.cursor="crosshair"},d=l=>{let p=e.getBoundingClientRect();n.x=l.clientX-p.left,n.y=l.clientY-p.top,o&&(t.pan(l.clientX-a),a=l.clientX),r()},m=()=>{n.x=null,n.y=null,r()};return e.addEventListener("wheel",i,{passive:!1}),e.addEventListener("mousedown",s),e.addEventListener("mousemove",d),e.addEventListener("mouseleave",m),window.addEventListener("mouseup",u),()=>{e.removeEventListener("wheel",i),e.removeEventListener("mousedown",s),e.removeEventListener("mousemove",d),e.removeEventListener("mouseleave",m),window.removeEventListener("mouseup",u)}}function fe(e,t,n,r){let i=Math.max(0,e-t-r),o=[],a=0;for(let h of n){if(a+h.minHeight>i)break;a+=h.minHeight,o.push(h)}let s=o.reduce((h,c)=>h+c.height,0),u=new Map;if(s<=i)for(let h of o)u.set(h.id,h.height);else{let h=s-a,c=s-i;for(let f of o){let b=f.height-f.minHeight,v=h>0?b/h*c:0;u.set(f.id,Math.max(f.minHeight,f.height-v))}}let d=o.reduce((h,c)=>h+u.get(c.id),0),m=e-t-d,l=[],p=m;for(let h of o){let c=u.get(h.id);l.push({id:h.id,top:p,height:c,minHeight:h.minHeight}),p+=c}return{chartHeight:m,panes:l,timeAxisTop:p}}function I(e,t){return e.panes.find(n=>t>=n.top&&t<=n.top+n.height)}function be(e,t,n){let r=e.panes.filter(i=>i.id!==t).reduce((i,o)=>i+o.height,0);return Math.max(0,e.height-e.timeScaleHeight-n-r)}var k=class{constructor(){g(this,"requests",[]);g(this,"customHeights",new Map)}setRequests(t){let n=t.length!==this.requests.length||t.some((r,i)=>r.id!==this.requests[i]?.id);return this.requests=t,n}sizeSpecs(){return this.requests.map(t=>({id:t.id,height:this.customHeights.get(t.id)??t.defaultHeight,minHeight:t.minHeight}))}setCustomHeight(t,n){this.customHeights.set(t,n)}get count(){return this.requests.length}};var Ve=W.handleHitZone;function ge(e,t,n){let r=!1,i=null,o=0,a=0;function s(h){return h.clientY-e.getBoundingClientRect().top}function u(h){for(let c of t().panes)if(c.height>0&&Math.abs(h-c.top)<=Ve)return c.id;return null}function d(h){r||(e.style.cursor=u(s(h))?"ns-resize":"")}function m(h){if(!r||i===null)return;let c=t(),f=c.panes.find(C=>C.id===i);if(!f)return;let b=a-(s(h)-o),v=be(c,i,W.minChartHeight);n(i,Math.max(f.minHeight,Math.min(v,b)))}function l(h){let c=u(s(h));if(c===null)return;let f=t().panes.find(b=>b.id===c);f&&(r=!0,i=c,o=s(h),a=f.height,e.style.cursor="ns-resize",h.stopImmediatePropagation(),h.preventDefault())}function p(){r&&(r=!1,i=null,e.style.cursor="")}return e.addEventListener("mousedown",l),e.addEventListener("mousemove",d),window.addEventListener("mousemove",m),window.addEventListener("mouseup",p),()=>{e.removeEventListener("mousedown",l),e.removeEventListener("mousemove",d),window.removeEventListener("mousemove",m),window.removeEventListener("mouseup",p)}}function ye(e){return e!=="line"&&e!=="area"}function ve(e){let t=[],n=0,r=0;for(let i=0;i<e.length;i++){let o=e[i],a=(o.open+o.high+o.low+o.close)/4,s=i===0?(o.open+o.close)/2:(n+r)/2;t.push({time:o.time,open:s,high:Math.max(o.high,s,a),low:Math.min(o.low,s,a),close:a,volume:o.volume}),n=s,r=a}return t}var ze=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];var H="11px Inter, sans-serif";function B(e,t,n){let r=t-e;return i=>n-(i-e)/r*n}function F(e){let t=new Date(e),n=t.getDate().toString().padStart(2,"0"),r=ze[t.getMonth()],i=t.getHours().toString().padStart(2,"0"),o=t.getMinutes().toString().padStart(2,"0");return`${n}${r}, ${i}:${o}`}function N(e,t){if(t>=0&&t<e.length)return e[t].time;if(e.length<2)return e[0]?.time??null;let n=e[1].time-e[0].time;return t>=e.length?e[e.length-1].time+(t-(e.length-1))*n:e[0].time+t*n}function Ye(e,t,n,r,i,o,a,s,u){if(n.length!==0){e.strokeStyle=r,e.beginPath();for(let d of n){let m=t[d],l=(s+d-a)*i+i/2;e.moveTo(l,u(m.high)),e.lineTo(l,u(m.low))}e.stroke(),e.fillStyle=r;for(let d of n){let m=t[d],l=(s+d-a)*i+i/2,p=u(Math.max(m.open,m.close)),h=u(Math.min(m.open,m.close)),c=Math.max(1,h-p);e.fillRect(l-o/2,p,o,c)}}}function Xe(e,t,n,r,i,o,a,s,u){if(n.length===0)return;let d=o/2;e.strokeStyle=r,e.lineWidth=Math.min(2,Math.max(1,o*.18)),e.beginPath();for(let m of n){let l=t[m],p=(s+m-a)*i+i/2;e.moveTo(p,u(l.high)),e.lineTo(p,u(l.low));let h=u(l.open);e.moveTo(p-d,h),e.lineTo(p,h);let c=u(l.close);e.moveTo(p,c),e.lineTo(p+d,c)}e.stroke(),e.lineWidth=1}function Ce(e,t,n,r,i,o,a,s,u=!1){e.save(),e.beginPath(),e.rect(0,0,t.chartWidth,t.chartHeight),e.clip();let d=t.chartWidth/a,m=Math.max(1,d*.8),l=B(r,i,t.chartHeight),p=[],h=[];for(let f=0;f<n.length;f++)n[f].close>=n[f].open?p.push(f):h.push(f);let c=u?Xe:Ye;c(e,n,p,y.bullish,d,m,o,s,l),c(e,n,h,y.bearish,d,m,o,s,l),e.restore()}function M(e,t){let{ctx:n}=e;n.save(),n.beginPath(),n.rect(0,e.top,e.chartWidth,e.height),n.clip(),n.beginPath(),t(n),n.restore()}function D(e,t){e.strokeStyle=t.color,e.fillStyle=t.color,e.lineWidth=t.lineWidth??1.5,e.globalAlpha=t.opacity??1,e.setLineDash(t.dash??[])}function _(e,t){for(let n=Math.min(t,e.length)-1;n>=0;n--){let r=e[n];if(r!=null&&Number.isFinite(r))return{value:r,index:n}}return null}function j(e,t){t.length!==0&&M(e,n=>{for(let r of t){let i=e.toY(r.from),o=e.toY(r.to);n.fillStyle=r.color,n.fillRect(0,Math.min(i,o),e.chartWidth,Math.abs(o-i))}})}function Q(e,t){if(t.length===0)return;let{ctx:n}=e;n.save();for(let r of t){let i=e.toY(r.value);i<e.top||i>e.top+e.height||(D(n,{color:r.color,lineWidth:r.lineWidth??.5,dash:r.dash??[3,4]}),n.beginPath(),n.moveTo(0,i),n.lineTo(e.chartWidth,i),n.stroke(),r.label&&(n.globalAlpha=1,n.setLineDash([]),n.font=H,n.textBaseline="middle",n.textAlign="left",n.fillStyle=r.labelColor??r.color,n.fillText(r.label,e.chartWidth+5,i)))}n.restore()}function G(e,t){let n=e/Math.max(1,t),r=Math.pow(10,Math.floor(Math.log10(Math.max(n,1e-10)))),i=n/r,o;return i<1.5?o=1:i<3.5?o=2:i<7.5?o=5:o=10,o*r}function ee(e,t=n=>n.toFixed(2)){let{ctx:n}=e,r=e.extent.max-e.extent.min;if(r<=0)return;let i=G(r,Math.max(2,Math.round(e.height/40))),o=Math.ceil(e.extent.min/i)*i;n.save(),n.lineWidth=.5,n.globalAlpha=1,n.setLineDash([]),n.font=H,n.textBaseline="middle",n.textAlign="left";for(let a=o;a<=e.extent.max;a+=i){let s=e.toY(a);s<=e.top||s>=e.top+e.height||(n.strokeStyle=y.grid,n.beginPath(),n.moveTo(0,s),n.lineTo(e.chartWidth,s),n.stroke(),n.fillStyle=y.text,n.fillText(t(a),e.chartWidth+8,s))}n.restore()}function te(e,t,n){let{ctx:r}=e;r.save(),r.setLineDash([]),r.lineWidth=1;let i=n?y.dividerActive:y.divider;r.strokeStyle=i,r.globalAlpha=n?.6:.3,r.beginPath(),r.moveTo(0,e.top),r.lineTo(t,e.top),r.stroke();let o=28,a=3;r.fillStyle=i,r.globalAlpha=n?.9:.38,V(r,e.chartWidth/2-o/2,e.top-a/2,o,a,2),r.fill(),r.restore()}function V(e,t,n,r,i,o){e.beginPath(),e.moveTo(t+o,n),e.arcTo(t+r,n,t+r,n+i,o),e.arcTo(t+r,n+i,t,n+i,o),e.arcTo(t,n+i,t,n,o),e.arcTo(t,n,t+r,n,o),e.closePath()}function $e(e){let t=[1,2,3,5,10,15,20,30,50,75,100,150,200,500];for(let n of t)if(n>=e)return n;return t[t.length-1]}function xe(e,t,n,r,i,o,a,s){let{chartWidth:u,chartHeight:d,timeAxisTop:m}=t,l=u/a;e.font=H,e.lineWidth=.5;let p=i-r,h=Math.max(4,Math.round(d/45)),c=G(p,h),f=Math.ceil(r/c)*c;e.textBaseline="middle",e.textAlign="left";for(let S=f;S<=i;S+=c){let A=d-(S-r)/p*d;A<0||A>d||(e.strokeStyle=y.grid,e.beginPath(),e.moveTo(0,A),e.lineTo(u,A),e.stroke(),e.fillStyle=y.text,e.fillText(S.toFixed(2),u+8,A))}let v=90/l,C=$e(v);e.textBaseline="top",e.textAlign="center";let w=Math.floor(o)-s,T=Math.ceil(o+a)-s,x=w<0?Math.floor(w/C)*C:0;for(let S=x;S<=T;S+=C){let E=(s+S-o)*l+l/2;if(E<0||E>u)continue;let he=N(n,S);he!=null&&(e.strokeStyle=y.grid,e.beginPath(),e.moveTo(E,0),e.lineTo(E,m),e.stroke(),e.fillStyle=y.text,e.fillText(F(he),E,m+8))}e.strokeStyle=y.grid,e.lineWidth=1,e.beginPath(),e.moveTo(u,0),e.lineTo(u,m),e.moveTo(0,m),e.lineTo(u,m),e.stroke()}var Pe=[4,4],Me=[],ne="11px Inter, sans-serif",qe="bold 11px Inter, sans-serif",Se=18;function z(e,t){e.font!==t&&(e.font=t)}function we(e,t,n,r,i,o){let a=o-i,s=t.chartHeight-(n-i)/a*t.chartHeight,u=r?y.bullish:y.bearish;e.save(),e.strokeStyle=u,e.lineWidth=1,e.setLineDash(Pe),e.beginPath(),e.moveTo(0,s),e.lineTo(t.chartWidth,s),e.stroke(),e.setLineDash(Me),e.fillStyle=u,e.fillRect(t.chartWidth,s-10,t.priceScaleWidth,20),z(e,qe),e.fillStyle="white",e.textBaseline="middle",e.textAlign="left",e.fillText(n.toFixed(2),t.chartWidth+8,s),e.restore()}function Te(e,t,n,r,i,o,a,s,u,d,m){if(r<0||r>t.chartWidth||i<0||i>t.timeAxisTop)return;let l=t.chartWidth/u,p=Math.floor(s+r/l),h=p-d,c=N(n,h);if(c==null)return;let f=(p-s)*l+l/2,b=i<=t.chartHeight,v=b?void 0:I(t,i);if(e.strokeStyle=y.crosshair,e.lineWidth=1,e.setLineDash(Pe),e.beginPath(),e.moveTo(f,0),e.lineTo(f,t.timeAxisTop),(b||v)&&(e.moveTo(0,i),e.lineTo(t.chartWidth,i)),e.stroke(),e.setLineDash(Me),b){let x=o+(t.chartHeight-i)/t.chartHeight*(a-o);e.fillStyle=y.pill,e.fillRect(t.chartWidth,i-10,t.priceScaleWidth,20),z(e,ne),e.fillStyle=y.pillText,e.textBaseline="middle",e.textAlign="left",e.fillText(x.toFixed(2),t.chartWidth+8,i)}else if(v){let x=m?.(v.id,i);if(x!=null){z(e,ne);let S=e.measureText(x).width+8;e.fillStyle=y.pill,e.fillRect(t.chartWidth+2,i-Se/2,S,Se),e.fillStyle=y.pillText,e.textBaseline="middle",e.textAlign="left",e.fillText(x,t.chartWidth+6,i)}}let C=F(c);z(e,ne);let T=e.measureText(C).width+16;e.fillStyle=y.pill,e.fillRect(f-T/2,t.timeAxisTop,T,t.timeScaleHeight),e.fillStyle=y.pillText,e.textBaseline="top",e.textAlign="center",e.fillText(C,f,t.timeAxisTop+8)}function Le(e,t){let n=parseInt(e.slice(1,3),16),r=parseInt(e.slice(3,5),16),i=parseInt(e.slice(5,7),16);return`rgba(${n}, ${r}, ${i}, ${t})`}function Re(e,t,n,r,i,o,a,s,u){if(n.length===0)return;e.save(),e.beginPath(),e.rect(0,0,t.chartWidth,t.chartHeight),e.clip();let d=t.chartWidth/a,m=B(r,i,t.chartHeight);function l(h){return(s+h-o)*d+d/2}function p(){e.beginPath();for(let h=0;h<n.length;h++){let c=m(n[h].close);h===0?e.moveTo(l(h),c):e.lineTo(l(h),c)}}if(u){p(),e.lineTo(l(n.length-1),t.chartHeight),e.lineTo(l(0),t.chartHeight),e.closePath();let h=e.createLinearGradient(0,0,0,t.chartHeight);h.addColorStop(0,Le(y.priceLine,.28)),h.addColorStop(1,Le(y.priceLine,0)),e.fillStyle=h,e.fill()}p(),e.strokeStyle=y.priceLine,e.lineWidth=1.5,e.lineJoin="round",e.stroke(),e.restore()}var Y=class{constructor(t){g(this,"ctx");g(this,"drawGridAndAxes",(...t)=>xe(this.ctx,...t));g(this,"drawCandles",(...t)=>Ce(this.ctx,...t));g(this,"drawPriceLine",(...t)=>Re(this.ctx,...t));g(this,"drawCurrentPriceLine",(...t)=>we(this.ctx,...t));g(this,"drawCrosshair",(...t)=>Te(this.ctx,...t));this.ctx=t}clear(t){this.ctx.clearRect(0,0,t.width,t.height)}};function De(e,t,n,r){if(e.mode==="price")return null;let i=1/0,o=-1/0;for(let l of t){let p=Math.min(r,l.length);for(let h=Math.max(0,n);h<p;h++){let c=l[h];c==null||!Number.isFinite(c)||(c<i&&(i=c),c>o&&(o=c))}}let a=Number.isFinite(i)&&Number.isFinite(o);if(e.mode==="auto"){if(!a)return null;let l=Math.max(o-i,1e-10),p=Math.max(l*e.cushionRatio,e.minCushion??0);return{min:i-p,max:o+p}}if(!a){let[l,p]=e.alwaysInclude??[e.min,e.max];return{min:l,max:p}}let s=o-i,u=Math.max(s*e.cushionRatio,e.minCushion??0),d=i-u,m=o+u;return e.alwaysInclude&&(d=Math.min(d,e.alwaysInclude[0]),m=Math.max(m,e.alwaysInclude[1])),{min:Math.max(d,e.min),max:Math.min(m,e.max)}}function ie(e,t,n,r=8,i=8){let o=Math.max(1,n-r-i),a=Math.max(e.max-e.min,1e-10);return s=>t+r+(e.max-s)/a*o}function Ae(e,t,n,r=8,i=8){let o=Math.max(1,n-r-i),a=Math.max(e.max-e.min,1e-10);return s=>e.max-(s-t-r)/o*a}function re(e,t,n,r={}){let{colorMode:i="fixed",momentumColors:o,colorValues:a}=r;M(e,s=>{D(s,n),s.lineJoin="round",s.lineCap="round";let u=i==="directional"&&!!o,d=Math.min(e.endIndex,t.length),m=null,l=null,p=null;for(let h=Math.max(0,e.startIndex);h<d;h++){let c=t[h];if(c==null||!Number.isFinite(c)){m=null,l=null,p=null;continue}let f=e.toX(h),b=e.toY(c),v=a?.[h],C=v!=null&&v<0,w=u&&p!==null&&C!==p;if(m==null||l==null||w){u||s.moveTo(f,b),m=f,l=b,p=C;continue}u?(s.strokeStyle=C?o.bearStrong:o.bullStrong,s.beginPath(),s.moveTo(m,l),s.lineTo(f,b),s.stroke()):s.lineTo(f,b),m=f,l=b,p=C}u||s.stroke()})}function oe(e,t,n,r,i={}){let{colorMode:o="fixed",momentumColors:a,colorValues:s}=i,u=o==="directional"&&!!a,d=[],m=Math.min(e.endIndex,t.length,n.length);for(let l=Math.max(0,e.startIndex);l<m;l++){let p=t[l],h=n[l];p==null||h==null||!Number.isFinite(p)||!Number.isFinite(h)||d.push({x:e.toX(l),yu:e.toY(p),yl:e.toY(h),index:l})}d.length<2||M(e,l=>{D(l,r);let p=f=>{if(!(f.length<2)){l.beginPath(),l.moveTo(f[0].x,f[0].yu);for(let b=1;b<f.length;b++)l.lineTo(f[b].x,f[b].yu);for(let b=f.length-1;b>=0;b--)l.lineTo(f[b].x,f[b].yl);l.closePath(),l.fill()}};if(!u){p(d);return}let h=[],c=null;for(let f of d){let b=s?.[f.index],v=b!=null&&b<0;c!==null&&v!==c&&(l.fillStyle=c?a.bearStrong:a.bullStrong,p(h),h=[]),h.push(f),c=v}c!==null&&(l.fillStyle=c?a.bearStrong:a.bullStrong,p(h))})}function X(e,t,n){let r=e>=0,i=t==null?!0:r?e>=t:e<=t;return r?i?n.bullStrong:n.bullWeak:i?n.bearStrong:n.bearWeak}function ae(e,t,n,r={}){let{baseline:i=0,colorMode:o="fixed",momentumColors:a,colorValues:s,barWidthRatio:u=.95}=r;M(e,d=>{D(d,n);let m=e.toY(i),l=Math.max(1,e.candleWidth*u-1),p=Math.min(e.endIndex,t.length);for(let h=Math.max(0,e.startIndex);h<p;h++){let c=t[h];if(c==null||!Number.isFinite(c))continue;if(o==="momentum"&&a)d.fillStyle=X(c,t[h-1],a);else if(o==="directional"&&a){let b=s?.[h];d.fillStyle=b!=null&&b<0?a.bearStrong:a.bullStrong}let f=e.toY(c);d.fillRect(e.toX(h)-l/2,Math.min(f,m),l,Math.max(1,Math.abs(f-m)))}})}var P=16,We=6,Ze="10px 'Inter', sans-serif";function Ue(e,t,n){if(e.length<=1)return e;let r=e.map(i=>({...i})).sort((i,o)=>i.y-o.y);for(let i=1;i<r.length;i++){let o=r[i-1].y+P;r[i].y<o&&(r[i].y=o)}for(let i=r.length-2;i>=0;i--){let o=r[i+1].y-P;r[i].y>o&&(r[i].y=o)}for(let i of r)i.y=Math.max(t+P/2,Math.min(n-P/2,i.y));return r}function $(e,t){if(t.length===0)return;let{ctx:n}=e,r=e.top+e.height,i=t.map(o=>({...o,y:e.toY(o.value)})).filter(o=>o.y>=e.top-P&&o.y<=r+P);if(i.length!==0){n.save(),n.globalAlpha=1,n.setLineDash([]),n.font=Ze,n.textBaseline="middle",n.textAlign="left";for(let o of Ue(i,e.top,r)){let a=e.chartWidth+1,s=o.y-P/2,u=o.variant==="full"?e.priceScaleWidth:n.measureText(o.text).width+We*2;n.fillStyle=o.color,o.variant==="full"?n.fillRect(a,s,u,P):(V(n,a,s,u,P,3),n.fill()),n.fillStyle="#ffffff",n.fillText(o.text,a+We,o.y)}n.restore()}}function Ee(e){switch(e.type){case"line":case"histogram":return[e.values];case"band":return[e.upper,e.lower]}}function He(e){let t=[],n=[],r=new Map;for(let i of e){if(!i.visible)continue;if(i.target.pane==="price"){t.push(i);continue}let o=i.target.spec,a=r.get(o.id);if(a)a.indicators.push(i),a.spec={...a.spec,height:Math.max(a.spec.height,o.height),minHeight:Math.max(a.spec.minHeight,o.minHeight)};else{let s={spec:o,indicators:[i]};r.set(o.id,s),n.push(s)}}return{priceOverlays:t,panes:n}}function Oe(e,t,n,r){let{min:i,max:o}=e;for(let a of t)if(a.expandsPriceExtent)for(let s of a.plots)for(let u of Ee(s)){let d=Math.min(r,u.length);for(let m=Math.max(0,n);m<d;m++){let l=u[m];l==null||!Number.isFinite(l)||(l<i&&(i=l),l>o&&(o=l))}}return{min:i,max:o}}function Ie(e,t){switch(t.type){case"line":re(e,t.values,t.style,{colorMode:t.colorMode,momentumColors:t.momentumColors,colorValues:t.colorValues});break;case"band":oe(e,t.upper,t.lower,t.style,{colorMode:t.colorMode,momentumColors:t.momentumColors,colorValues:t.colorValues});break;case"histogram":ae(e,t.values,t.style,{baseline:t.baseline,colorMode:t.colorMode,momentumColors:t.momentumColors,colorValues:t.colorValues});break}}function Ke(e,t,n,r){for(let i=Math.min(n,e.length)-1;i>=0;i--){let o=e[i];if(o==null||!Number.isFinite(o))continue;let a=t?.[i];if((a!=null&&a<0)===r)return{value:o,index:i}}return null}function Je(e,t){if(!e.momentumColors)return e.style.color;switch(e.colorMode){case"momentum":return X(t.value,e.values[t.index-1],e.momentumColors);case"directional":return(e.colorValues?.[t.index]??t.value)<0?e.momentumColors.bearStrong:e.momentumColors.bullStrong;default:return e.style.color}}function ke(e,t,n,r){let i=[];for(let o of e)for(let a of o.plots){if(!a.axisPill)continue;if(a.type==="band"){for(let[d,m]of[[a.upper,"\u2191"],[a.lower,"\u2193"]]){let l=_(d,t);l&&i.push({value:l.value,color:a.style.color,text:`${m}${l.value.toFixed(n)}`,variant:r})}continue}if(a.type==="line"&&a.colorMode==="directional"&&a.momentumColors){for(let d of[!1,!0]){let m=Ke(a.values,a.colorValues,t,d);m&&i.push({value:m.value,color:d?a.momentumColors.bearStrong:a.momentumColors.bullStrong,text:m.value.toFixed(n),variant:r})}continue}let s=_(a.values,t);if(!s)continue;let u=a.type==="histogram"?Je(a,s):a.style.color;i.push({value:s.value,color:u,text:s.value.toFixed(n),variant:r})}return i}function se(e,t,n){let r=t.filter(i=>(i.layer??"front")===n);for(let i of r)for(let o of i.plots)Ie(e,o);n==="front"&&$(e,ke(t,e.endIndex,2,"snug"))}function Be(e,t,n,r,i){let o=t.indicators.flatMap(d=>d.plots.flatMap(m=>Ee(m))),a=De(t.spec.scale,o,n,r);if(!a)return null;let s=e(a),u=t.spec.valuePrecision??2;te(s,i.fullWidth,i.dividerHighlighted),t.spec.zones&&j(s,t.spec.zones),t.spec.grid&&ee(s,d=>d.toFixed(u)),t.spec.referenceLines&&Q(s,t.spec.referenceLines);for(let d of t.indicators)for(let m of d.plots)Ie(s,m);return $(s,ke(t.indicators,r,u,"full")),a}var q=class{constructor(){g(this,"visibleCandles",R.default);g(this,"rightOffset",0);g(this,"dataLength",0);g(this,"chartWidth",0)}update(t,n){this.dataLength=t,this.chartWidth=n}get candleWidth(){return this.chartWidth/this.visibleCandles}get rightBound(){return this.dataLength-1-this.rightOffset}get leftBound(){return this.rightBound-this.visibleCandles}get minRightOffset(){return-Math.floor(this.visibleCandles*K.maxLeftEmptyRatio)}get defaultRightOffset(){return-Math.floor(this.visibleCandles*K.rightPaddingRatio)}reset(t){this.dataLength=t,this.visibleCandles=Math.min(R.default,t),this.rightOffset=this.defaultRightOffset}zoom(t,n){let r=t>0?R.outFactor:R.inFactor,i=Math.max(R.min,Math.min(R.max,this.visibleCandles*r));if(i===this.visibleCandles)return;let o=this.leftBound+n/this.candleWidth;this.visibleCandles=i;let a=this.chartWidth/i,s=o-n/a;this.rightOffset=this.dataLength-1-(s+i),this.clampMin()}pan(t){this.rightOffset+=t/this.candleWidth,this.clampMin(),this.clampMax()}onLiveTick(t){t===1&&this.rightOffset>this.defaultRightOffset&&(this.rightOffset+=1)}clampMin(){this.rightOffset<this.minRightOffset&&(this.rightOffset=this.minRightOffset)}clampMax(){this.rightOffset>this.dataLength&&(this.rightOffset=this.dataLength)}};var le=class{constructor(t,n){g(this,"renderer");g(this,"data",[]);g(this,"lastSnapshot",null);g(this,"priceStyle","candles");g(this,"heikinAshiData",null);g(this,"indicators",[]);g(this,"paneGroups",[]);g(this,"priceOverlays",[]);g(this,"paneExtents",new Map);g(this,"viewport",new q);g(this,"paneState",new k);g(this,"mouse",{x:null,y:null});g(this,"detachMouseEvents");g(this,"detachPanelResizer");g(this,"shell");g(this,"layout",{width:0,height:0,chartWidth:0,chartHeight:0,priceScaleWidth:U.priceScaleWidth,timeScaleHeight:U.timeScaleHeight,panes:[],timeAxisTop:0});g(this,"lastWidth",0);g(this,"lastHeight",0);g(this,"isLoadingHistory",!1);g(this,"onLoadMore");g(this,"onCrosshairMove");g(this,"onLayoutChange");this.shell=ue(t,n,{onResize:(r,i)=>this.resize(r,i),onThemeChange:()=>this.render()}),this.renderer=new Y(this.shell.ctx),this.detachPanelResizer=ge(t,()=>this.layout,(r,i)=>this.applyPaneResize(r,i)),this.detachMouseEvents=pe(t,this.viewport,this.mouse,()=>this.render())}setData(t,n=!1){let{action:r,candlesAdded:i}=n?{action:"reset",candlesAdded:t.length}:ce(t,this.lastSnapshot);switch(this.data=t,this.lastSnapshot=me(t),this.heikinAshiData=null,r){case"reset":this.viewport.reset(t.length);break;case"historical":this.isLoadingHistory=!1;break;case"live":this.viewport.onLiveTick(i);break;case"noop":break}this.viewport.update(this.data.length,this.layout.chartWidth),this.render()}setPriceStyle(t){t!==this.priceStyle&&(this.priceStyle=t,this.render())}displaySeries(){return this.priceStyle!=="heikin-ashi"?this.data:(this.heikinAshiData??(this.heikinAshiData=ve(this.data)),this.heikinAshiData)}setIndicators(t){this.indicators=t;let{priceOverlays:n,panes:r}=He(t);this.priceOverlays=n,this.paneGroups=r;let i=r.map(a=>({id:a.spec.id,defaultHeight:a.spec.height,minHeight:Math.max(a.spec.minHeight,W.minHeight)}));this.paneState.setRequests(i)&&this.lastWidth>0?this.resize(this.lastWidth,this.lastHeight,!0):this.render()}getIndicators(){return this.indicators}destroy(){this.detachMouseEvents(),this.detachPanelResizer(),this.shell.destroy()}applyPaneResize(t,n){this.paneState.setCustomHeight(t,n),this.applyPaneLayout(),this.viewport.update(this.data.length,this.layout.chartWidth),this.render()}applyPaneLayout(){let{chartHeight:t,panes:n,timeAxisTop:r}=fe(this.layout.height,this.layout.timeScaleHeight,this.paneState.sizeSpecs(),W.minChartHeight);this.layout.chartHeight=t,this.layout.panes=n,this.layout.timeAxisTop=r,this.onLayoutChange?.({width:this.layout.width,chartWidth:this.layout.chartWidth,chartHeight:t,panes:n})}resize(t,n,r=!1){t===0||n===0||!r&&Math.abs(t-this.lastWidth)<1&&Math.abs(n-this.lastHeight)<1||(this.lastWidth=t,this.lastHeight=n,this.layout.width=t,this.layout.height=n,this.layout.chartWidth=t-this.layout.priceScaleWidth,this.applyPaneLayout(),this.shell.resizeBackingStore(t,n),this.viewport.update(this.data.length,this.layout.chartWidth),this.render())}makeDrawContext(t,n,r,i,o,a,s){let{leftBound:u,visibleCandles:d}=this.viewport,m=this.layout.chartWidth/d;return{ctx:this.shell.ctx,chartWidth:this.layout.chartWidth,priceScaleWidth:this.layout.priceScaleWidth,top:n,height:r,extent:t,candleWidth:m,startIndex:i,endIndex:o,toX:l=>(l-u)*m+m/2,toY:ie(t,n,r,a,s)}}render(){if(this.data.length===0||this.layout.width===0)return;this.renderer.clear(this.layout),this.paneExtents.clear();let{leftBound:t,rightBound:n}=this.viewport,r=Math.max(0,Math.floor(t)),i=Math.min(this.data.length,Math.ceil(n)+1);r<de.triggerThreshold&&!this.isLoadingHistory&&this.data.length>0&&(this.isLoadingHistory=!0,this.onLoadMore?.(this.data[0].time));let o=this.displaySeries().slice(r,i);if(o.length===0)return;let a=ye(this.priceStyle),s=1/0,u=-1/0;for(let b of o){let v=a?b.low:b.close,C=a?b.high:b.close;v<s&&(s=v),C>u&&(u=C)}({min:s,max:u}=Oe({min:s,max:u},this.priceOverlays,r,i));let d=u-s,m={min:s-d*J.paddingRatio,max:u+d*J.paddingRatio},{min:l,max:p}=m;this.renderer.drawGridAndAxes(this.layout,o,l,p,t,this.viewport.visibleCandles,r);let h=this.makeDrawContext(m,0,this.layout.chartHeight,r,i,0,0);se(h,this.priceOverlays,"behind"),this.priceStyle==="line"||this.priceStyle==="area"?this.renderer.drawPriceLine(this.layout,o,l,p,t,this.viewport.visibleCandles,r,this.priceStyle==="area"):this.renderer.drawCandles(this.layout,o,l,p,t,this.viewport.visibleCandles,r,this.priceStyle==="bars"),se(h,this.priceOverlays,"front");let c=this.displaySeries(),f=c[c.length-1];if(f&&this.renderer.drawCurrentPriceLine(this.layout,f.close,f.close>=f.open,l,p),this.mouse.x!==null&&this.mouse.y!==null){let b=Math.floor(t+this.mouse.x/this.viewport.candleWidth);this.onCrosshairMove?.({candle:o[b-r]??f,index:Math.max(0,Math.min(b,this.data.length-1))})}else this.onCrosshairMove?.(null);for(let b of this.paneGroups){let v=this.layout.panes.find(x=>x.id===b.spec.id);if(!v||v.height===0)continue;let C=b.spec.padTop??void 0,w=b.spec.padBottom??void 0,T=Be(x=>this.makeDrawContext(x,v.top,v.height,r,i,C??8,w??8),b,r,i,{fullWidth:this.layout.width,dividerHighlighted:this.mouse.y!==null&&Math.abs(this.mouse.y-v.top)<=6});T&&this.paneExtents.set(b.spec.id,T)}this.mouse.x!==null&&this.mouse.y!==null&&this.renderer.drawCrosshair(this.layout,o,this.mouse.x,this.mouse.y,l,p,t,this.viewport.visibleCandles,r,(b,v)=>this.paneValueAt(b,v))}paneValueAt(t,n){let r=this.layout.panes.find(u=>u.id===t),i=this.paneExtents.get(t);if(!r||!i)return null;let o=this.paneGroups.find(u=>u.spec.id===t),a=o?.spec.valuePrecision??2;return Ae(i,r.top,r.height,o?.spec.padTop??8,o?.spec.padBottom??8)(n).toFixed(a)}paneIdAt(t){return I(this.layout,t)?.id??null}};export{le as CandlestickChart};
@@ -0,0 +1,10 @@
1
+ import { ViewportState } from "./viewport";
2
+ /** Latest cursor position, or null when it's off the canvas. Shared object:
3
+ * the chart reads it every frame rather than being handed new values. */
4
+ export interface MouseState {
5
+ x: number | null;
6
+ y: number | null;
7
+ }
8
+ /** Wires up wheel, drag and hover on the canvas. Returns a function that
9
+ * removes every listener again. */
10
+ export declare function attachMouseEvents(canvas: HTMLCanvasElement, viewport: ViewportState, mouse: MouseState, onRender: () => void): () => void;
@@ -0,0 +1,23 @@
1
+ import type { ChartLayout, PaneRect } from "../types";
2
+ /** What a pane asks for, before space is actually available. */
3
+ export interface PaneSizeSpec {
4
+ id: string;
5
+ height: number;
6
+ minHeight: number;
7
+ }
8
+ export interface PaneLayoutResult {
9
+ chartHeight: number;
10
+ panes: PaneRect[];
11
+ /** Y of the time-scale strip — bottom of the last pane. */
12
+ timeAxisTop: number;
13
+ }
14
+ /** Stacks the sub-panes below the price pane. When space runs short they
15
+ * shrink first, and the bottom ones are dropped if that still isn't enough. */
16
+ export declare function computePaneLayout(totalHeight: number, timeScaleHeight: number, specs: PaneSizeSpec[], minChartHeight: number): PaneLayoutResult;
17
+ /** Pane rect by id, or undefined when that pane isn't currently laid out. */
18
+ export declare function findPane(layout: ChartLayout, id: string): PaneRect | undefined;
19
+ /** Which sub-pane a canvas Y falls in. Undefined means the price pane or the
20
+ * time axis — callers check those themselves. */
21
+ export declare function paneAtY(layout: ChartLayout, y: number): PaneRect | undefined;
22
+ /** How tall this pane may grow before the price pane hits its minimum. */
23
+ export declare function maxHeightForPane(layout: ChartLayout, paneId: string, minChartHeight: number): number;
@@ -0,0 +1,19 @@
1
+ import type { PaneSizeSpec } from "./layout-calculator";
2
+ /** A pane's identity and default size, before any user drag applies. */
3
+ export interface PaneRequest {
4
+ id: string;
5
+ defaultHeight: number;
6
+ minHeight: number;
7
+ }
8
+ export declare class PaneState {
9
+ private requests;
10
+ private customHeights;
11
+ /** Swaps in the current panes. True means the list itself changed, so the
12
+ * layout needs rebuilding. Dragged heights survive a pane disappearing. */
13
+ setRequests(requests: PaneRequest[]): boolean;
14
+ /** The heights to lay out with: each pane's dragged size, or its default. */
15
+ sizeSpecs(): PaneSizeSpec[];
16
+ /** Remember a drag. The caller clamps it — it's the one holding the layout. */
17
+ setCustomHeight(id: string, height: number): void;
18
+ get count(): number;
19
+ }
@@ -0,0 +1,4 @@
1
+ import type { ChartLayout } from "../types";
2
+ /** Makes the line above each sub-pane draggable to resize it. Register this
3
+ * before the pan handler, or a drag on a divider pans the chart instead. */
4
+ export declare function attachPanelResizer(canvas: HTMLCanvasElement, getLayout: () => ChartLayout, onPaneResize: (paneId: string, newHeight: number) => void): () => void;
@@ -0,0 +1,9 @@
1
+ import type { CandleData } from "../types";
2
+ export type PriceStyle = "candles" | "bars" | "line" | "area" | "heikin-ashi";
3
+ /** Line and area plot close alone, so they scale tighter. Every other style
4
+ * draws a full OHLC shape and the axis has to fit every high and low. */
5
+ export declare function usesOhlcExtent(style: PriceStyle): boolean;
6
+ /** Each bar is defined against the one before it, all the way back to the
7
+ * first — so run this over the whole series, never the visible slice, or the
8
+ * candles change shape as you pan. */
9
+ export declare function toHeikinAshi(data: readonly CandleData[]): CandleData[];
@@ -0,0 +1,5 @@
1
+ import type { CandleData, ChartLayout } from "../../types";
2
+ /** Draws the visible candles. They're split into up and down groups first, so
3
+ * the whole chart takes a handful of canvas colour changes rather than one
4
+ * per candle — that difference is most of the cost at a few hundred bars. */
5
+ export declare function drawCandles(ctx: CanvasRenderingContext2D, layout: ChartLayout, data: CandleData[], minPrice: number, maxPrice: number, leftBound: number, visibleCandles: number, startIndex: number, asBars?: boolean): void;
@@ -0,0 +1,2 @@
1
+ import type { CandleData, ChartLayout } from "../../types";
2
+ export declare function drawGridAndAxes(ctx: CanvasRenderingContext2D, layout: ChartLayout, data: CandleData[], minPrice: number, maxPrice: number, leftBound: number, visibleCandles: number, startIndex: number): void;
@@ -0,0 +1,20 @@
1
+ import type { ChartLayout } from "../../types";
2
+ import { drawCandles } from "./candles";
3
+ import { drawGridAndAxes } from "./grid";
4
+ import { drawCrosshair, drawCurrentPriceLine } from "./overlays";
5
+ import { drawPriceLine } from "./price-line";
6
+ type DropCtx<F> = F extends (ctx: CanvasRenderingContext2D, ...rest: infer R) => infer Ret ? (...args: R) => Ret : never;
7
+ /** Holds the canvas context so callers don't have to pass it to every draw
8
+ * call. Covers the fixed parts of a chart; indicators go through
9
+ * renderers/indicators.ts instead. */
10
+ export declare class ChartRenderer {
11
+ private readonly ctx;
12
+ constructor(ctx: CanvasRenderingContext2D);
13
+ clear(layout: ChartLayout): void;
14
+ drawGridAndAxes: DropCtx<typeof drawGridAndAxes>;
15
+ drawCandles: DropCtx<typeof drawCandles>;
16
+ drawPriceLine: DropCtx<typeof drawPriceLine>;
17
+ drawCurrentPriceLine: DropCtx<typeof drawCurrentPriceLine>;
18
+ drawCrosshair: DropCtx<typeof drawCrosshair>;
19
+ }
20
+ export {};
@@ -0,0 +1,30 @@
1
+ import type { Plot, RenderableIndicator, PaneSpec } from "@indiminds/chart-types";
2
+ import type { PriceExtent } from "../../types";
3
+ import { makeToY } from "../../scale";
4
+ import { type DrawContext } from "../../renderers/primitives";
5
+ /** The number series a plot is made of — what the scale and legend read. */
6
+ export declare function plotSeries(plot: Plot): (number | null)[][];
7
+ /** The indicators sharing one sub-pane. */
8
+ export interface PaneGroup {
9
+ spec: PaneSpec;
10
+ indicators: RenderableIndicator[];
11
+ }
12
+ /** Splits indicators into ones drawn over the candles and ones with their own
13
+ * pane. Same pane id = same pane, sized to whichever asked for the most room. */
14
+ export declare function groupByPane(indicators: readonly RenderableIndicator[]): {
15
+ priceOverlays: RenderableIndicator[];
16
+ panes: PaneGroup[];
17
+ };
18
+ /** Widens the price range to fit any overlay that asked for it. Most don't:
19
+ * a moving average always sits among the candles anyway. */
20
+ export declare function expandExtentForOverlays(extent: PriceExtent, overlays: readonly RenderableIndicator[], startIndex: number, endIndex: number): PriceExtent;
21
+ /** Draws the indicators that share the candles' scale. Called twice — once
22
+ * for the ones behind the candles, once for the ones in front. */
23
+ export declare function drawPriceOverlays(c: DrawContext, overlays: readonly RenderableIndicator[], layer: "behind" | "front"): void;
24
+ /** Draws one sub-pane back to front: divider, backdrop, plots, then labels.
25
+ * Returns the value range it used, which the crosshair needs afterwards. */
26
+ export declare function drawPane(makeContext: (extent: PriceExtent) => DrawContext, group: PaneGroup, startIndex: number, endIndex: number, options: {
27
+ fullWidth: number;
28
+ dividerHighlighted: boolean;
29
+ }): PriceExtent | null;
30
+ export { makeToY };
@@ -0,0 +1,8 @@
1
+ import type { CandleData, ChartLayout } from "../../types";
2
+ /** Dashed line at the latest price, with its value in the right-hand gutter. */
3
+ declare function drawCurrentPriceLine(ctx: CanvasRenderingContext2D, layout: ChartLayout, latestPrice: number, isBullish: boolean, minPrice: number, maxPrice: number): void;
4
+ /** Draws the crosshair across every pane at once, plus its value and time
5
+ * labels. `paneValueAt` turns a Y inside a sub-pane into that pane's own
6
+ * value, so this never needs to know what a pane is plotting. */
7
+ declare function drawCrosshair(ctx: CanvasRenderingContext2D, layout: ChartLayout, data: CandleData[], mouseX: number, mouseY: number, minPrice: number, maxPrice: number, leftBound: number, visibleCandles: number, startIndex: number, paneValueAt?: (paneId: string, y: number) => string | null): void;
8
+ export { drawCrosshair, drawCurrentPriceLine };
@@ -0,0 +1,4 @@
1
+ import type { CandleData, ChartLayout } from "../../types";
2
+ /** Close-only styles: a single stroked path, optionally filled down to the
3
+ * bottom of the plot. Both share one trace so the fill can't drift a pixel. */
4
+ export declare function drawPriceLine(ctx: CanvasRenderingContext2D, layout: ChartLayout, data: CandleData[], minPrice: number, maxPrice: number, leftBound: number, visibleCandles: number, startIndex: number, filled: boolean): void;