@graphysdk/node-renderer 1.8.1-beta.1786024899180
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.
- package/LICENSE.md +17 -0
- package/README.md +88 -0
- package/assets/.gitkeep +0 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +160 -0
- package/dist/index.mjs +812 -0
- package/package.json +79 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# License
|
|
2
|
+
|
|
3
|
+
This software is available under any of the following licenses, at your option,
|
|
4
|
+
depending on which one's conditions you meet:
|
|
5
|
+
|
|
6
|
+
- **PolyForm Noncommercial 1.0.0** — for noncommercial use (personal,
|
|
7
|
+
charitable, educational, research, government).
|
|
8
|
+
See [licenses/PolyForm-NonCommercial-1.0.0.md](licenses/PolyForm-NonCommercial-1.0.0.md)
|
|
9
|
+
|
|
10
|
+
- **PolyForm Small Business 1.0.0** — free commercial use for organizations
|
|
11
|
+
under the size thresholds defined in the license.
|
|
12
|
+
See [licenses/PolyForm-Small-Business-1.0.0.md](licenses/PolyForm-Small-Business-1.0.0.md)
|
|
13
|
+
|
|
14
|
+
- **PolyForm Free Trial 1.0.0** — a 32-day evaluation for anyone else.
|
|
15
|
+
See [licenses/PolyForm-Free-Trial-1.0.0.md](licenses/PolyForm-Free-Trial-1.0.0.md)
|
|
16
|
+
|
|
17
|
+
For commercial use beyond these terms, contact [hello@graphy.dev](mailto:hello@graphy.dev) or visit [https://graphy.dev](https://graphy.dev).
|
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# @graphysdk/node-renderer
|
|
2
|
+
|
|
3
|
+
Headless chart renderer for Node.js. Compiles specs via `@graphysdk/viz-engine` and paints to PNG using `@napi-rs/canvas` (Skia).
|
|
4
|
+
|
|
5
|
+
## Docs
|
|
6
|
+
|
|
7
|
+
- [Provider and renderer](https://docs.graphy.dev/sdk-next/rendering/provider-and-renderer)
|
|
8
|
+
- [Quickstart](https://docs.graphy.dev/sdk-next/quickstart)
|
|
9
|
+
|
|
10
|
+
## Serverless (Lambda, Vercel, etc.)
|
|
11
|
+
|
|
12
|
+
This package is designed for ephemeral Node runtimes:
|
|
13
|
+
|
|
14
|
+
- **Native module at runtime:** `@napi-rs/canvas` ships platform prebuilds via npm optional dependencies. Do not bundle it into your serverless JS artifact — keep it as a `dependencies` install so the correct `.node` binary is present on the target OS/arch.
|
|
15
|
+
- **Lazy load:** The Skia binding loads on the first `renderGraphToPng` call, not when the module is imported.
|
|
16
|
+
- **No temp files:** Rendering is in-memory (`Buffer` in, PNG `Buffer` out).
|
|
17
|
+
- **Inter via npm:** Default fonts load from `@fontsource/inter` at runtime using `require.resolve` (WOFF files in `node_modules`). No writable disk or checked-in TTFs required.
|
|
18
|
+
|
|
19
|
+
Host apps using Nitro, esbuild, or similar should treat `@graphysdk/node-renderer`, `@napi-rs/canvas`, and `@fontsource/inter` as **external** runtime dependencies (same as other native addons). For Nitro on Vercel, include `@fontsource/inter` in `traceDeps` so font files are copied into the function bundle:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
traceDeps: ['@graphysdk/node-renderer', '@napi-rs/canvas', '@fontsource/inter'],
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Fonts
|
|
26
|
+
|
|
27
|
+
Text measurement and drawing use the same `@napi-rs/canvas` context with family **`Inter`** (aligned with the browser renderer).
|
|
28
|
+
|
|
29
|
+
1. **Default (automatic):** `ensureDefaultFonts()` registers Latin Inter 400 and 600 from `@fontsource/inter` on first render. Works on Vercel as long as `@fontsource/inter` is installed (declared as a dependency of this package).
|
|
30
|
+
|
|
31
|
+
2. **Optional `assets/` override:** place TTF files at `assets/Inter-Regular.ttf` and `assets/Inter-SemiBold.ttf` beside the installed package if you need custom binaries instead of Fontsource.
|
|
32
|
+
|
|
33
|
+
3. **In-memory:** pass `fonts` in `RenderOptions` or call `registerFont({ family, data: Buffer })`. Optional `weight` / `style` on `FontRegistration` are dedup-cache keys only — Skia reads metrics from the font bytes.
|
|
34
|
+
|
|
35
|
+
Without registered fonts, Skia uses built-in fallbacks; layout may differ from the browser renderer.
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Low-level spec API
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { renderGraphToPng } from '@graphysdk/node-renderer';
|
|
43
|
+
import { createSpec, geom, pipe, scale } from '@graphysdk/viz-engine';
|
|
44
|
+
|
|
45
|
+
const input = pipe(createSpec({ x: 'category', y: 'value' }), geom.bar(), scale.x(), scale.y());
|
|
46
|
+
|
|
47
|
+
const png = await renderGraphToPng({ input, data }, { width: 1200, height: 800, theme: 'light' });
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Stored `GraphConfig` (agents / Redis shape)
|
|
51
|
+
|
|
52
|
+
Core- and API-shaped configs embed `data` on the object. Use the capability guard before rendering in production so you can fall back to Microlink when needed:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { canRenderConfigWithCanvas, renderChartConfigToPng } from '@graphysdk/node-renderer';
|
|
56
|
+
|
|
57
|
+
const capability = canRenderConfigWithCanvas(storedConfig);
|
|
58
|
+
if (!capability.ok) {
|
|
59
|
+
// fall back to browser screenshot path
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const png = await renderChartConfigToPng(storedConfig, { width: 1200, height: 800, theme: 'light' });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`renderChartConfigToPng` compiles via `@graphysdk/viz-engine` (same `GraphConfig` → spec conversion as the React renderer). Conversion is **lossy** — see the matrix below.
|
|
66
|
+
|
|
67
|
+
## Canvas capability matrix
|
|
68
|
+
|
|
69
|
+
| Category | Details |
|
|
70
|
+
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
71
|
+
| **Supported `type`** | `column`, `columnStacked`, `columnStackedFill`, `bar`, `barStacked`, `barStackedFill`, `line`, `areaStacked`, `pie`, `donut`, `scatter`, `bubble`, `combo` |
|
|
72
|
+
| **Unsupported `type`** | `funnel`, `heatmap`, `waterfall`, `mekko`, `table` (viz-engine throws) |
|
|
73
|
+
| **Canvas geoms** | Cartesian: bar, line, area, point, rule — Polar: bar (pie/donut) |
|
|
74
|
+
| **Rejected up front** | `_unstable_mapping`; `referenceLines.trendline` / `referenceLines.averageLine` |
|
|
75
|
+
| **Supported referenceLines** | `goalLine` (compiled to `geom.rule`) |
|
|
76
|
+
| **Legend display** | `pill` (color, stacked swatches, bubble/size); `direct` (series labels on panel edge) |
|
|
77
|
+
| **Lossy at compile** | Annotations, `themeOverrides`, series styles, headline numbers (see viz-engine `graph-config.converter`) |
|
|
78
|
+
| **Visual deltas vs browser** | Font metrics, anti-aliasing |
|
|
79
|
+
|
|
80
|
+
`CANVAS_SUPPORTED_GRAPH_TYPES`, `CANVAS_UNSUPPORTED_GRAPH_TYPES`, and `CANVAS_CAPABILITY_MATRIX` are exported from the package entrypoint.
|
|
81
|
+
|
|
82
|
+
## Dev server
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pnpm --filter @graphysdk/node-renderer dev
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Serves preset chart PNGs at `http://localhost:4310` (see `src/dev/server.ts`).
|
package/assets/.gitkeep
ADDED
|
File without changes
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var it=Object.create;var ue=Object.defineProperty;var st=Object.getOwnPropertyDescriptor;var at=Object.getOwnPropertyNames;var lt=Object.getPrototypeOf,dt=Object.prototype.hasOwnProperty;var ct=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of at(t))!dt.call(e,i)&&i!==n&&ue(e,i,{get:()=>t[i],enumerable:!(o=st(t,i))||o.enumerable});return e};var ut=(e,t,n)=>(n=e!=null?it(lt(e)):{},ct(t||!e||!e.__esModule?ue(n,"default",{value:e,enumerable:!0}):n,e));Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const fe=require("node:fs"),Le=require("node:module"),D=require("node:path"),ft=require("node:url"),gt=require("@graphysdk/viz-engine/graph-config"),N=require("d3-shape"),c=require("@graphysdk/viz-engine"),ht=require("node:buffer");var w=typeof document<"u"?document.currentScript:null;let U,J;const _e=()=>U?Promise.resolve(U):(J??=import("@napi-rs/canvas").then(e=>(U=e,e)).catch(e=>{throw J=void 0,e}),J),W=()=>{if(!U)throw new Error("Canvas runtime is not loaded. Call loadCanvasRuntime() before rendering.");return U},y="Inter",x=new Set;let ge=!1,q=!1;const we=e=>`${e.family}|${e.weight??400}|${e.style??"normal"}`,re=e=>{const t=we(e);x.has(t)||(W().GlobalFonts.register(e.data,e.family),x.add(t))},mt=[{file:"Inter-Regular.ttf",family:y,weight:400},{file:"Inter-SemiBold.ttf",family:y,weight:600}],bt=[{file:"inter-latin-400-normal.woff",family:y,weight:400},{file:"inter-latin-600-normal.woff",family:y,weight:600}],At="@fontsource/inter",pt=()=>{try{const e=Le.createRequire(typeof document>"u"?require("url").pathToFileURL(__filename).href:w&&w.tagName.toUpperCase()==="SCRIPT"&&w.src||new URL("index.cjs",document.baseURI).href),t=D.dirname(e.resolve("@graphysdk/node-renderer/package.json"));return D.join(t,"assets")}catch{return D.resolve(D.dirname(ft.fileURLToPath(typeof document>"u"?require("url").pathToFileURL(__filename).href:w&&w.tagName.toUpperCase()==="SCRIPT"&&w.src||new URL("index.cjs",document.baseURI).href)),"../assets")}},Ce=(e,t,n)=>{try{return fe.existsSync(e)?(re({family:t,data:fe.readFileSync(e),weight:n}),!0):!1}catch{return!1}},Tt=()=>{const e=pt();for(const{file:t,family:n,weight:o}of mt)Ce(D.join(e,t),n,o)&&(q=!0)},St=()=>{const e=Le.createRequire(typeof document>"u"?require("url").pathToFileURL(__filename).href:w&&w.tagName.toUpperCase()==="SCRIPT"&&w.src||new URL("index.cjs",document.baseURI).href);for(const{file:t,family:n,weight:o}of bt)if(!x.has(we({family:n,data:Buffer.alloc(0),weight:o})))try{const i=e.resolve(`${At}/files/${t}`);Ce(i,n,o)&&(q=!0)}catch{}},Ie=()=>(ge||(ge=!0,Tt(),St()),q),C=(e,t)=>e.x+t*e.width,_=(e,t)=>e.y+(1-t)*e.height,ee=e=>e==="catmull-rom"?N.curveCatmullRom:N.curveLinear,Be=e=>{switch(e){case"dashed":return[8,4];case"dotted":return[2,2];case"solid":return[]}},k=e=>e,yt=(e,t)=>t!=="connect"?e:e.filter(n=>c.getX(n)!==null&&c.getY(n)!==null),Rt=(e,t,n)=>{const o=ee(t.interpolate),i=N.area().curve(o),r=N.line().curve(o);if(e.mainAxis==="y"){const s=l=>_(n,c.getY(l)??0),a=l=>C(n,c.getXMin(l)??0),d=l=>C(n,c.getXMax(l)??c.getX(l)??0);i.y(s).x0(a).x1(d),r.y(s).x(d)}else{const s=l=>C(n,c.getX(l)??0),a=l=>_(n,c.getYMin(l)??0),d=l=>_(n,c.getYMax(l)??c.getY(l)??0);i.x(s).y0(a).y1(d),r.x(s).y(d)}if(t.missingValues==="gap"){const s=a=>c.getX(a)!==null&&c.getY(a)!==null;i.defined(s),r.defined(s)}return{areaGenerator:i,lineGenerator:r}},Et=k({geom:"area",coord:"cartesian",draw:(e,{layer:t,coordSystem:n,panel:o,colorScheme:i})=>{const r=c.createStyleResolver({colorScheme:i}).geomReaders(t),{areaGenerator:s,lineGenerator:a}=Rt(n,t.params,o),d=e;t.data.groupBy(c.GROUP_VARIABLES.group).forEach(l=>{const u=yt([...l],t.params.missingValues),f=u[0];if(!f)return;const g=r.get("color",f),h=r.get("alpha",f),m=r.get("strokeWidth",f),A=Be(r.get("lineType",f));e.save(),e.globalAlpha=h,e.fillStyle=g,e.beginPath(),s.context(d)(u),e.fill(),e.restore(),e.save(),e.globalAlpha=r.get("strokeAlpha",f),e.strokeStyle=g,e.lineWidth=m,e.setLineDash(A),e.lineJoin="round",e.lineCap="round",e.beginPath(),a.context(d)(u),e.stroke(),e.restore()})}}),he=(e,t,n,o,i,r)=>{const s=Math.max(0,Math.min(r.rx??r.ry??0,o/2)),a=Math.max(0,Math.min(r.ry??r.rx??0,i/2)),d=Math.PI/2;e.moveTo(t+s,n),e.lineTo(t+o-s,n),e.ellipse(t+o-s,n+a,s,a,0,-d,0),e.lineTo(t+o,n+i-a),e.ellipse(t+o-s,n+i-a,s,a,0,0,d),e.lineTo(t+s,n+i),e.ellipse(t+s,n+i-a,s,a,0,d,Math.PI),e.lineTo(t,n+a),e.ellipse(t+s,n+a,s,a,0,Math.PI,Math.PI*1.5),e.closePath()},Lt=k({geom:"bar",coord:"cartesian",draw:(e,{layer:t,coordSystem:n,panel:o,colorScheme:i})=>{const{Path2D:r}=W(),s=c.createStyleResolver({colorScheme:i}).geomReaders(t),a=n.mainAxis,d=new Map;for(const l of t.data){const u=c.getBarRectBounds(a,l);if(!u)continue;const f=C(o,u.x),g=o.y+u.y*o.height,h={x:f,y:g,width:u.width*o.width,height:u.height*o.height,fill:s.get("color",l),opacity:s.get("alpha",l)},m=c.buildColumnKey(a,u),A=d.get(m);A?A.bars.push(h):d.set(m,{bars:[h],style:{borderColor:s.get("borderColor",l),borderWidth:s.get("borderWidth",l),borderRadius:s.get("borderRadius",l),borderAlpha:s.get("alpha",l)}})}for(const{bars:l,style:u}of d.values()){const{borderColor:f,borderRadius:g,borderWidth:h,borderAlpha:m}=u,A=c.resolveBarBorderTreatment(h),p=g==="full"?void 0:c.resolveBarCornerRadiiPx({borderRadius:g,mainAxis:a});if(!(l.length>1)){const[b]=l;if(!b)continue;const v=p??c.resolveBarCornerRadiiPx({borderRadius:g,mainAxis:a,bounds:{width:b.width,height:b.height}}),E=new r;he(E,b.x,b.y,b.width,b.height,v),e.save(),e.globalAlpha=b.opacity,e.fillStyle=b.fill,e.fill(E),f!==void 0&&(e.clip(E),e.strokeStyle=f,e.lineWidth=A.outlineWidth,e.stroke(E)),e.restore();continue}const T=c.getBoundingRect(l),L=p??c.resolveBarCornerRadiiPx({borderRadius:g,mainAxis:a,bounds:{width:T.width,height:T.height}}),S=new r;he(S,T.x,T.y,T.width,T.height,L),e.save(),e.clip(S);for(const b of l)e.globalAlpha=b.opacity,e.fillStyle=b.fill,e.fillRect(b.x,b.y,b.width,b.height),f!==void 0&&(e.strokeStyle=f,e.lineWidth=A.separatorWidth,e.strokeRect(b.x,b.y,b.width,b.height));f!==void 0&&(e.globalAlpha=m,e.strokeStyle=f,e.lineWidth=A.outlineWidth,e.stroke(S)),e.restore()}}}),me=(e,t)=>{const n=/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(e.trim());if(n?.[1]!==void 0){const i=n[1],r=i.length===3?[...i].map(l=>l+l).join(""):i.slice(0,6),s=Number.parseInt(r.slice(0,2),16),a=Number.parseInt(r.slice(2,4),16),d=Number.parseInt(r.slice(4,6),16);return`rgba(${s}, ${a}, ${d}, ${t})`}const o=/^rgba?\(([^)]+)\)$/i.exec(e.trim());if(o?.[1]!==void 0){const i=o[1].split(",").map(d=>d.trim()),[r,s,a]=i;if(r!==void 0&&s!==void 0&&a!==void 0)return`rgba(${r}, ${s}, ${a}, ${t})`}return t===0?"rgba(0, 0, 0, 0)":e},_t=(e,t)=>t!=="connect"?e:e.filter(n=>c.getX(n)!==null&&c.getY(n)!==null),wt=(e,t)=>{const{seriesObservations:n,fillGenerator:o,panel:i,color:r,fillAlpha:s}=t,a=n.map(l=>c.getY(l)).filter(l=>l!==null).map(l=>_(i,l));if(a.length===0)return;const d=e.createLinearGradient(0,Math.min(...a),0,i.y+i.height);d.addColorStop(0,r),d.addColorStop(.7,me(r,0)),d.addColorStop(1,me(r,0)),e.save(),e.globalAlpha=s,e.fillStyle=d,e.beginPath(),o.context(e)(n),e.fill(),e.restore()},Ct=k({geom:"line",coord:"cartesian",draw:(e,{layer:t,panel:n,colorScheme:o})=>{const i=c.createStyleResolver({colorScheme:o}).geomReaders(t),r=N.line().x(a=>C(n,c.getX(a)??0)).y(a=>_(n,c.getY(a)??0)).curve(ee(t.params.interpolate)),s=N.area().x(a=>C(n,c.getX(a)??0)).y0(n.y+n.height).y1(a=>_(n,c.getY(a)??0)).curve(ee(t.params.interpolate));if(t.params.missingValues==="gap"){const a=d=>c.getX(d)!==null&&c.getY(d)!==null;r.defined(a),s.defined(a)}t.data.groupBy(c.GROUP_VARIABLES.group).forEach(a=>{const d=_t([...a],t.params.missingValues),l=d[0];if(!l)return;const u=i.get("strokeWidth",l),f=Be(i.get("lineType",l)),g=i.get("color",l),h=i.get("fillAlpha",l);h!==void 0&&wt(e,{seriesObservations:d,fillGenerator:s,panel:n,color:g,fillAlpha:h}),e.save(),e.globalAlpha=i.get("alpha",l),e.strokeStyle=g,e.lineWidth=u,e.setLineDash(f),e.lineJoin="round",e.lineCap="round",e.beginPath(),r.context(e)(d),e.stroke(),e.restore()})}}),It=k({geom:"point",coord:"cartesian",draw:(e,{layer:t,panel:n,colorScheme:o})=>{const i=c.createStyleResolver({colorScheme:o}).geomReaders(t);for(const r of t.data){const s=c.getX(r),a=c.getY(r);if(s===null||a===null)continue;const d=C(n,s),l=_(n,a),u=i.get("size",r)/2,f=i.get("borderWidth",r);e.save(),e.globalAlpha=i.get("alpha",r),e.fillStyle=i.get("color",r),e.strokeStyle=i.get("borderColor",r),e.lineWidth=f,e.beginPath(),e.arc(d,l,u,0,Math.PI*2),e.fill(),f>0&&e.stroke(),e.restore()}}}),ie=11,Bt="sans-serif",Nt=500,P=5,Ne=5,te=13,ve=6,V=12,be=21.6,vt=(e,t)=>{const n=e.mapping.y!==void 0,o=t.mainAxis==="y";return n!==o},Pt=(e,t,n)=>{const o=c.getRuleDashPattern(t,n);e.setLineDash(o),o.length>0&&(e.lineCap="round")},Ft=(e,t)=>X(e)&&X(t)?`${e}: ${t}`:X(e)?e:X(t)?t:null,X=e=>e!=null&&e.trim()!=="",kt=e=>{e.beginPath(),e.moveTo(2.48935,16.9323),e.lineTo(9.07098,9.82964),e.bezierCurveTo(9.51595,9.34944,9.51504,8.60728,9.0689,8.12818),e.lineTo(2.48866,1.06172),e.bezierCurveTo(1.85811,.384586,.974477,0,.049217,0),e.lineTo(0,0),e.lineTo(0,18),e.lineTo(.0443689,18),e.bezierCurveTo(.972463,18,1.85854,17.6131,2.48935,16.9323),e.closePath()},Pe=(e,t,n,o,i,r)=>{const s=Math.min(r,o/2,i/2);e.beginPath(),e.moveTo(t+s,n),e.lineTo(t+o-s,n),e.arcTo(t+o,n,t+o,n+s,s),e.lineTo(t+o,n+i-s),e.arcTo(t+o,n+i,t+o-s,n+i,s),e.lineTo(t+s,n+i),e.arcTo(t,n+i,t,n+i-s,s),e.lineTo(t,n+s),e.arcTo(t,n,t+s,n,s),e.closePath(),e.fill()},Dt=(e,t,n,o,i,r,s,a)=>{const d=n+P*2,l=ie+Ne*2,u=o==="start",f=u?r.x:r.x+r.width,g=_(r,i),h=u?-te:-(d-te),m=u?h+d-P:h+P-V;e.save(),e.translate(f,g),e.translate(0,-l/2),e.fillStyle=s,Pe(e,h,0,d,l,ve),e.save(),u||(e.translate(m+V/2,l/2),e.rotate(Math.PI),e.translate(-(m+V/2),-l/2)),e.translate(m,(l-be)/2),e.scale(V/10,be/18),e.fillStyle=s,kt(e),e.fill(),e.restore(),e.fillStyle=a,e.textAlign="left",e.textBaseline="middle",e.fillText(t,h+P,l/2),e.restore()},Ot=(e,t,n,o,i,r,s,a)=>{const d=n+P*2,l=ie+Ne*2,u=o==="start",f=C(r,i),g=u?r.y+r.height:r.y,h=u?-te:-8;e.save(),e.translate(f,g),e.translate(-d/2,0),e.fillStyle=s,Pe(e,0,h,d,l,ve),e.fillStyle=a,e.textAlign="left",e.textBaseline="middle",e.fillText(t,P,h+l/2),e.restore()},Ut=k({geom:"rule",coord:"cartesian",draw:(e,{layer:t,coordSystem:n,panel:o,compiled:i,textMeasurer:r,formattingLocale:s,colorScheme:a})=>{const d=t.data.getFirst();if(!d)return;const l=vt(t,n),u=l?c.getY(d):c.getX(d);if(u===null)return;const f=c.createStyleResolver({colorScheme:a}).geomReaders(t),g=f.get("color",d),h=c.getRuleLabelTextColor(g),m=f.get("strokeWidth",d);if(e.save(),e.strokeStyle=g,e.lineWidth=m,Pt(e,f.get("lineType",d),m),e.beginPath(),l){const S=_(o,u);e.moveTo(o.x,S),e.lineTo(o.x+o.width,S)}else{const S=C(o,u);e.moveTo(S,o.y),e.lineTo(S,o.y+o.height)}e.stroke(),e.restore();const A=Ft(t.params.label,c.formatRuleValue({guides:i.guides,numberFormat:i.config.numberFormat,layer:t,locale:s??i.config.parsingLocale}));if(A===null)return;const p=i.config.appearance.textScale,R={family:Bt,size:ie*p,weight:Nt};e.font=c.buildFontString(R);const T=r.measureText(A,R).width;(l?Dt:Ot)(e,A,T,t.params.labelPosition,u,o,g,h)}}),O=N.arc();O.digits(8);const Ht=k({geom:"bar",coord:"polar",draw:(e,{layer:t,coordSystem:n,panel:o,colorScheme:i})=>{const r=o.x+o.width/2,s=o.y+o.height/2,a=Math.min(o.width,o.height)/2,{Path2D:d}=W(),l=c.createStyleResolver({colorScheme:i}).geomReaders(t);e.save(),e.translate(r,s),e.scale(a,a);const u=e,f=new Map;for(const g of t.data){const{startAngle:h,endAngle:m}=c.getAngleExtent(g),{innerRadius:A,outerRadius:p}=c.getRadiusExtent(g);if(h===null||m===null||A===null||p===null)continue;const R={startAngle:h,endAngle:m,innerRadius:A,outerRadius:p,fill:l.get("color",g),opacity:l.get("alpha",g)},T=c.buildSliceGroupKey(n.bandAxis,R),L=f.get(T);L?L.items.push(R):f.set(T,{items:[R],style:{borderColor:l.get("borderColor",g),borderWidth:l.get("borderWidth",g),borderRadius:l.get("borderRadius",g),borderAlpha:l.get("alpha",g)}})}for(const{items:g,style:h}of f.values()){const{borderColor:m,borderRadius:A,borderWidth:p,borderAlpha:R}=h,T=c.resolveBarBorderTreatment(p),L=m!==void 0&&a>0,S=c.getUnionExtent(g),b=c.resolvePolarBarCornerRadiusUnit(A,S.outerRadius-S.innerRadius),v=a>0&&(L||b>0);e.save();let E;v&&(E=new d,O.cornerRadius(b),O.context(E)(S),e.clip(E)),O.cornerRadius(0);for(const Q of g)e.globalAlpha=Q.opacity,e.fillStyle=Q.fill,e.beginPath(),O.context(u)(Q),e.fill(),L&&(e.strokeStyle=m,e.lineWidth=T.separatorWidth/a,e.stroke());L&&E&&(e.globalAlpha=R,e.strokeStyle=m,e.lineWidth=T.outlineWidth/a,e.stroke(E)),e.restore()}e.restore()}}),Mt={bar:{cartesian:Lt,polar:Ht},line:{cartesian:Ct},area:{cartesian:Et},point:{cartesian:It},rule:{cartesian:Ut}},Fe=(e,t)=>Mt[e.geom]?.[t.type]??null,Gt=(e,t,n)=>{e.draw(t,n)};let Ae;const ke=()=>(Ae??=c.createCompiler(),Ae),se=["line","areaStacked","bar","barStacked","barStackedFill","column","columnStacked","columnStackedFill","combo","pie","donut","scatter","bubble"],De=["funnel","heatmap","waterfall","mekko","table"],Wt=new Set(se),Vt={supportedGraphTypes:[...se],unsupportedGraphTypes:[...De],supportedGeoms:{cartesian:["bar","line","area","point","rule"],polar:["bar"]}},B=e=>({ok:!1,reason:e}),j=e=>typeof e=="object"&&e!==null&&!Array.isArray(e),Xt=e=>{if(!j(e))return!1;const{columns:t,rows:n}=e;return!Array.isArray(t)||!Array.isArray(n)?!1:t.every(o=>j(o)&&typeof o.key=="string")},Kt=e=>{if("_unstable_mapping"in e&&e._unstable_mapping!==void 0)return!0;const t=e.referenceLines;return j(t)?t.trendline!==void 0||t.averageLine!==void 0:!1},Oe=e=>{if(!j(e))return B("Chart config must be a plain object.");if(!Xt(e.data))return B("Chart config is missing a valid `data` object (columns + rows).");const{data:t,...n}=e;return{ok:!0,input:n,data:t}},Ue=e=>{const t=Oe(e);if(!t.ok)return t;if(Kt(e))return B("Canvas renderer does not support `_unstable_mapping`, `referenceLines.trendline`, or `referenceLines.averageLine`.");const n=t.input.type??"column";if(!Wt.has(n))return B(`Unsupported chart type for canvas rendering: ${n}.`);try{const o=ke(),i=gt.convertGraphConfig(t.input,t.data),r=o.compile({input:i,data:t.data});if(!r.ok)return B(`Chart config failed to compile: ${r.errors.map(s=>s.message).join("; ")}`);for(const s of r.compiled.layers)if(Fe(s,r.compiled.coordSystem)===null)return B(`No canvas geom renderer for ${s.geom} (${r.compiled.coordSystem.type} coord).`);return{ok:!0,compiled:r.compiled}}catch(o){const i=o instanceof Error?o.message:String(o);return B(`Chart config failed to compile: ${i}`)}},K=6,z=10,Yt=20,ae=y,le=12,He=y,H=12,de=1.3,M=10,G=12,F=6,Me=16,zt=24,Ge=6,We=3,ne=4,qt=8,jt="#9ca3af",Ve=16,Zt=1.3,Xe=13,Qt=1.4,Ke=12,Jt=1.4,Z=8,ce=12,$t=1.3,xt=200,en=120,tn=80,Ye=22,nn=9,on=11,rn=10,Y=9,pe=12,sn='-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',an=11.5,ln=550,dn={light:.55,mid:.75,dark:.08},cn={light:.62,mid:.8,dark:.85},un=.22,fn=.65,gn=y,hn=12,mn={family:ae,size:le},bn={family:gn,size:hn},An={family:He,size:H},pn=8,Te=1,Tn=[2,3],Sn=[1,3],yn=(e,t,n,o,i)=>{if(!t.isVisible||t.ticks.length===0)return;const r=t.position==="bottom"||t.position==="top",s=t.ticksVisible?1:0;e.save(),e.font=c.buildFontString(mn),e.fillStyle=i.textSecondary,e.strokeStyle=i.gridLine,e.lineWidth=1,e.setLineDash([]);for(const a of t.ticks){const d=r?a.position:1-a.position;t.ticksVisible&&En(e,t.position,d,n,o),_n(e,t.position,d,n,o,a.formattedLabel,t.labelRotation,s)}e.restore()},Rn=(e,t,n,o)=>{if(e==="bottom"||e==="top"){const a=o.x+t*o.width,d=e==="top"?n.y+n.height:n.y,l=e==="top"?-K:K;return{x1:a,y1:d,x2:a,y2:d+l}}const i=o.y+t*o.height,r=e==="right"?n.x:n.x+n.width,s=e==="right"?K:-K;return{x1:r,y1:i,x2:r+s,y2:i}},En=(e,t,n,o,i)=>{const{x1:r,y1:s,x2:a,y2:d}=Rn(t,n,o,i);e.beginPath(),e.moveTo(r,s),e.lineTo(a,d),e.stroke()},Ln=(e,t,n,o,i)=>{if(e==="bottom"||e==="top"){const s=o.x+t*o.width;return e==="bottom"?{x:s,y:n.y+i,textAlign:"center",textBaseline:"top"}:{x:s,y:n.y+n.height-i,textAlign:"center",textBaseline:"alphabetic"}}const r=o.y+t*o.height;return e==="left"?{x:n.x+n.width-i,y:r,textAlign:"right",textBaseline:"middle"}:{x:n.x+i,y:r,textAlign:"left",textBaseline:"middle"}},_n=(e,t,n,o,i,r,s,a)=>{const d=z*a;if(s!==0&&(t==="top"||t==="bottom")){const u=i.x+n*i.width,f=t==="top";e.save(),e.translate(u,f?o.y+o.height-d:o.y+d),e.rotate(s*Math.PI/180),e.textAlign=f?"left":"right",e.textBaseline="middle",e.fillText(r,0,0),e.restore();return}const l=Ln(t,n,o,i,d);e.textAlign=l.textAlign,e.textBaseline=l.textBaseline,e.fillText(r,l.x,l.y)},wn=(e,t,n,o)=>{if(!t.isVisible||!t.label)return;e.save(),e.font=c.buildFontString(bn),e.fillStyle=o.textPrimary,e.textBaseline="middle";const{x:i,y:r,alignment:s}=Cn(t.position,n);e.textAlign=s,e.fillText(t.label,i,r),e.restore()},Cn=(e,t)=>{const n=t.y+t.height/2;return e==="left"?{x:t.x,y:n,alignment:"start"}:e==="right"?{x:t.x+t.width,y:n,alignment:"end"}:{x:t.x+t.width/2,y:n,alignment:"center"}},In="data:image/png;base64,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",I=Ye;let $;const Bn=()=>{if($)return $;const{Image:e}=W(),t=new e,n=In.replace(/^data:image\/png;base64,/,"");return t.src=ht.Buffer.from(n,"base64"),$=t,t},ze=(e,t,n="full")=>!e||t.width<en||t.height<tn?"hidden":t.width<xt||n==="mini"?"mini":"full",Nn=e=>{const t=vn(e);if(t===null)return"light";const n=Pn(t);return n<=un?"dark":n<=fn?"mid":"light"},oe=(e,t,n)=>e.placement!==n||ze(e.enabled,t,e.variant)==="hidden"?0:Ye,qe=(e,t,n,o,i,r)=>{if(t.placement!==r)return;const s=ze(t.enabled,i,t.variant);if(s==="hidden")return;const a=Nn(o.background),d=a==="dark",l={family:sn,size:an,weight:400},u={...l,weight:ln};let f=0;s==="full"&&(e.font=c.buildFontString(l),f=e.measureText("Made with ").width,e.font=c.buildFontString(u),f+=e.measureText("Graphy").width);const g=s==="mini"?0:nn,h=s==="mini"?0:on,m=s==="mini"?0:rn,A=s==="mini"?I:g+Y+m+f+h,p=n.x+n.width-A,R=n.y+Math.max(0,(n.height||I)/2-I/2);e.save(),e.globalAlpha=cn[a],e.textBaseline="middle",e.textAlign="left",d||(e.shadowColor="rgba(0, 0, 0, 0.09)",e.shadowBlur=4,e.shadowOffsetY=1),e.beginPath(),e.roundRect(p,R,A,I,I/2),e.fillStyle=`rgba(255, 255, 255, ${dn[a]})`,e.fill(),e.shadowColor="transparent",e.shadowBlur=0,e.shadowOffsetY=0;const T=d?"#FFFFFF":"#2A2A28",L=s==="mini"?p+(A-Y)/2:p+g,S=R+(I-pe)/2;if(e.save(),d&&(e.filter="invert(1)"),e.drawImage(Bn(),L,S,Y,pe),e.restore(),s==="full"){const b=p+g+Y+m,v=R+I/2;e.fillStyle=T,e.font=c.buildFontString(l),e.fillText("Made with ",b,v);const E=e.measureText("Made with ").width;e.font=c.buildFontString(u),e.fillText("Graphy",b+E,v)}e.restore()},vn=e=>{const t=e.trim();if(!t.startsWith("#"))return null;const n=t.slice(1);if(n.length===3){const o=n[0],i=n[1],r=n[2];return o===void 0||i===void 0||r===void 0?null:{red:Number.parseInt(o+o,16),green:Number.parseInt(i+i,16),blue:Number.parseInt(r+r,16)}}return n.length>=6?{red:Number.parseInt(n.slice(0,2),16),green:Number.parseInt(n.slice(2,4),16),blue:Number.parseInt(n.slice(4,6),16)}:null},Pn=({red:e,green:t,blue:n})=>{const o=i=>{const r=i/255;return r<=.03928?r/12.92:((r+.055)/1.055)**2.4};return .2126*o(e)+.7152*o(t)+.0722*o(n)},Fn={family:y,size:Ve,weight:600},kn={family:y,size:Xe},Dn={family:y,size:Ke},On={family:y,size:ce,weight:500},Un={family:y,size:ce},je=Ve*Zt,Hn=Xe*Qt,Ze=Ke*Jt,Qe=ce*$t,Je=e=>{if(e===null)return null;if(e.label!==void 0&&e.label!=="")return e.label;if(e.url!==void 0&&e.url!==""&&c.isSafeUrl(e.url))try{return new URL(e.url).hostname}catch{return e.url}return null},Mn=(e,t)=>{let n=0;const o=e.isTitleVisible&&e.title!==null,i=e.isSubtitleVisible&&e.subtitle!==null;o&&(n+=je),i&&(n>0&&(n+=Z),n+=Hn);const r=oe(e.brandMark,t,"header");!o&&!i&&r>0&&(n=r);const s=e.isCaptionVisible&&e.caption!==null,a=e.isSourceVisible&&Je(e.source)!==null,d=oe(e.brandMark,t,"footer");let l=0;return s&&(l+=Ze),(a||d>0)&&(l>0&&(l+=Z),l+=Math.max(Qe,d)),{headerHeight:n,footerHeight:l}},Gn=(e,t,n,o,i)=>{if(n.height===0)return;e.save(),e.textBaseline="top",e.textAlign="left";let r=n.y;t.isTitleVisible&&t.title!==null&&(e.font=c.buildFontString(Fn),e.fillStyle=o.textPrimary,e.fillText(c.extractPlainText(t.title),n.x,r),r+=je+Z),t.isSubtitleVisible&&t.subtitle!==null&&(e.font=c.buildFontString(kn),e.fillStyle=o.textSecondary,e.fillText(c.extractPlainText(t.subtitle),n.x,r)),e.restore(),qe(e,t.brandMark,n,o,i,"header")},Wn=(e,t,n,o,i)=>{if(n.height===0)return;const r=t.isCaptionVisible?t.caption:null,s=t.isSourceVisible?Je(t.source):null,a=oe(t.brandMark,i,"footer");if(r===null&&s===null&&a===0)return;e.save(),e.textBaseline="top",e.textAlign="left";let d=n.y;if(r!==null&&(e.font=c.buildFontString(Dn),e.fillStyle=o.textSecondary,e.fillText(c.extractPlainText(r),n.x,d),d+=Ze+Z),s!==null){let l=n.x;l=Se(e,"Source: ",On,o.textSecondary,l,d),Se(e,s,Un,o.textSecondary,l,d)}e.restore(),qe(e,t.brandMark,{x:n.x,y:d,width:n.width,height:Math.max(Qe,a)},o,i,"footer")},Se=(e,t,n,o,i,r)=>(e.font=c.buildFontString(n),e.fillStyle=o,e.fillText(t,i,r),i+e.measureText(t).width),Vn=(e,t,n,o,i)=>{e.save(),e.lineCap="round",e.strokeStyle=i.gridLine;for(const r of c.computePanelBorderPaths(n.border,o,pn))e.lineWidth=r.lineWidth??Te,e.setLineDash(ye(r.lineStyle)),Xn(e,r.segments),e.stroke();for(const r of t){if(!r.gridVisible)continue;const s=r.position==="top"||r.position==="bottom";e.lineWidth=r.gridLineWidth??Te,e.setLineDash(ye(r.gridLineStyle));for(const a of r.ticks){if(a.position<=0||a.position>=1)continue;const d=s?a.position:1-a.position;if(e.beginPath(),s){const l=o.x+d*o.width;e.moveTo(l,o.y),e.lineTo(l,o.y+o.height)}else{const l=o.y+d*o.height;e.moveTo(o.x,l),e.lineTo(o.x+o.width,l)}e.stroke()}}e.restore()},ye=e=>{switch(e){case"dashed":return Tn;case"dotted":return Sn;case"solid":return[]}},Xn=(e,t)=>{e.beginPath();for(const n of t)switch(n.type){case"move":e.moveTo(n.x,n.y);break;case"line":e.lineTo(n.x,n.y);break;case"arc":e.arc(n.cx,n.cy,n.radius,n.startAngle,n.endAngle);break;case"close":e.closePath();break}},Kn=(e,t,n,o)=>{e.save(),e.font=c.buildFontString(An),e.textBaseline="middle";for(const r of t)r.display==="direct"&&Yn(e,r,n,o);const i=new Map;for(const r of t){if(r.display!=="pill"||r.items.length===0)continue;const s=i.get(r.position);s?s.push(r):i.set(r.position,[r])}for(const[r,s]of i){const a=n[r];a&&(r==="top"||r==="bottom"?zn(e,s,a,o):qn(e,s,a,o))}e.restore()},Yn=(e,t,n,o)=>{const i=n[t.position];if(i){e.textAlign="left";for(const r of t.items)r.normalizedY!==null&&(e.fillStyle=typeof r.visual.color=="string"?r.visual.color:o.textPrimary,e.fillText(r.formattedLabel,i.x+qt,_(i,r.normalizedY)))}},zn=(e,t,n,o)=>{const i=$e(t),r=i.reduce((l,u,f)=>l+Zn(u)+e.measureText(u.label).width+(f===0?0:Re(u)),0),s=H*de;let a=n.x+Math.max(0,(n.width-r)/2);const d=n.y+Ge+Math.max(s,G)/2;for(let l=0;l<i.length;l++){const u=i[l];u&&(l>0&&(a+=Re(u)),a=xe(e,u,a,d,o))}},qn=(e,t,n,o)=>{const i=$e(t),r=Math.max(H*de,G),s=4,a=i.length*r+Math.max(0,i.length-1)*s;let d=n.y+Math.max(0,(n.height-a)/2)+r/2;for(const l of i)xe(e,l,n.x,d,o),d+=r+s},$e=e=>{const t=[];for(const n of e){const o=n.aesthetics.includes("size");n.items.forEach((i,r)=>{const s=i.visual.size,a=o&&typeof s=="number"&&Number.isFinite(s)&&s>0?s:void 0;t.push({label:i.formattedLabel,colors:jn(i.visual.color),bubbleSize:a,isFirstInGroup:r===0})})}return t},jn=e=>typeof e=="string"?[e]:Array.isArray(e)?e.filter(t=>typeof t=="string"):[],Re=e=>e.isFirstInGroup?zt:Me,Zn=e=>{if(e.bubbleSize!==void 0)return e.bubbleSize+F;const t=e.colors.length;if(t===0)return 0;if(t===1)return M+F;const n=Math.min(t,We);return M+(n-1)*ne+F},xe=(e,t,n,o,i)=>{let r=n;if(t.bubbleSize!==void 0){const s=t.bubbleSize/2;e.fillStyle=t.colors[0]??jt,e.beginPath(),e.arc(r+s,o,s,0,Math.PI*2),e.fill(),r+=t.bubbleSize+F}else if(t.colors.length>0){const s=t.colors.slice(0,We);for(let a=0;a<s.length;a++){const d=s[a];d&&(e.fillStyle=d,e.fillRect(r+a*ne,o-G/2,M,G))}r+=M+(s.length-1)*ne+F}return e.fillStyle=i.textPrimary,e.textAlign="left",e.fillText(t.label,r,o),r+e.measureText(t.label).width},Ee=(e,t)=>Math.min(Math.max(...e),t),Qn=e=>t=>{if(!t.isVisible||t.ticks.length===0)return 0;const n=t.position==="top"||t.position==="bottom",o=t.ticks.map(s=>e.measureText(s.formattedLabel,{family:ae,size:le})),i=t.labelMaxWidthPx??Number.POSITIVE_INFINITY;if(n){if(t.labelRotation!==0){const s=o.map(a=>a.width);return Ee(s,i)+2*z}return Math.max(...o.map(s=>s.height))+z}const r=o.map(s=>s.width);return Ee(r,i)+z},Jn=e=>t=>{if(t.items.length===0)return 0;const n={family:He,size:H};if(t.position==="top"||t.position==="bottom"){const r=H*de;return Math.max(r,G)+Ge*2}const i=t.items.map(r=>{const s=e.measureText(r.formattedLabel,n).width;return M+F+s});return Math.max(...i)+Me},$n=e=>(t,n)=>{const{labelLineHeight:o,labelFontWeight:i}=c.getDifferenceArrowDimensions(n,1);return e.measureText(t,{family:y,size:o,weight:i})},xn=e=>(t,n)=>{const{fontSize:o,fontWeight:i}=c.getDataLabelDimensions(t,1);return e.measureText(n,{family:y,size:o,weight:i})},eo=e=>({measureAxis:Qn(e),measureAxisLabel:()=>Yt,measureLegend:Jn(e),measureTickLabel:t=>e.measureText(t,{family:ae,size:le}),measureDifferenceArrowLabel:$n(e),measureDataLabel:xn(e),measureHeadline:()=>({width:0,height:0}),measureHeadlineItemWidths:()=>[]});class et{constructor(){this.currentFont="";const t=W().createCanvas(1,1);this.ctx=t.getContext("2d")}measureText(t,n){const o=c.buildFontString(n);this.currentFont!==o&&(this.ctx.font=o,this.currentFont=o);const i=this.ctx.measureText(t);return{width:i.width,height:i.fontBoundingBoxAscent+i.fontBoundingBoxDescent,ascent:i.fontBoundingBoxAscent,descent:i.fontBoundingBoxDescent}}}const tt={background:"#ffffff",textPrimary:"#111827",textSecondary:"#6b7280",gridLine:"#e5e7eb",panelBorder:"#d1d5db",legendPillBackground:"rgba(0, 0, 0, 0.04)",legendPillBorder:"rgba(0, 0, 0, 0.08)"},nt={background:"#0b0f17",textPrimary:"#e5e7eb",textSecondary:"#9ca3af",gridLine:"#1f2937",panelBorder:"#374151",legendPillBackground:"rgba(255, 255, 255, 0.06)",legendPillBorder:"rgba(255, 255, 255, 0.12)"},to=async({input:e,data:t},n)=>{const i=ke().compile({input:e,data:t});if(!i.ok)throw new Error(`Failed to compile graph spec: ${i.errors.map(r=>r.message).join("; ")}`);return ot(i.compiled,n)},ot=async(e,t)=>{const{Canvas:n}=await _e();if(Ie(),t.fonts)for(const p of t.fonts)re(p);const o=c.formatLegends({legends:e.guides.legends,numberFormat:e.config.numberFormat,parsingLocale:e.config.parsingLocale,formattingLocale:t.formattingLocale}),i={width:t.width,height:t.height},{headerHeight:r,footerHeight:s}=Mn(e.config.content,i),a={headerSize:{width:t.width,height:r},footerSize:{width:t.width,height:s}},d=new et,{layout:l,formattedAxes:u}=new c.LayoutCompiler(eo(d)).compile({axes:e.guides.axes,numberFormat:e.config.numberFormat,parsingLocale:e.config.parsingLocale,formattedLegends:o,containerSize:{width:t.width,height:t.height},externalMeasurements:a,formattingLocale:t.formattingLocale,layout:e.config.layout}),f=t.pixelRatio??2,g=new n(t.width*f,t.height*f),h=g.getContext("2d");h.scale(f,f);const m=t.colorScheme??"light";return no(h,m==="dark"?nt:tt,m,t.width,t.height,e,u,o,l,d,t.formattingLocale),g.encode("png")},no=(e,t,n,o,i,r,s,a,d,l,u)=>{e.fillStyle=t.background,e.fillRect(0,0,o,i),Vn(e,s,r.guides.panel,d.panel,t),e.save(),e.beginPath(),e.rect(d.panel.x,d.panel.y,d.panel.width,d.panel.height),e.clip();const f={coordSystem:r.coordSystem,panel:d.panel,compiled:r,theme:t,colorScheme:n,textMeasurer:l,formattingLocale:u};for(const g of r.layers){const h=Fe(g,r.coordSystem);h&&Gt(h,e,{...f,layer:g})}e.restore();for(const g of s){const h=d.axes[g.position];h&&yn(e,g,h,d.panel,t)}for(const g of r.guides.axes){const h=d.axisLabels[g.position];h&&wn(e,g,h,t)}Kn(e,a,d.legends,t),Gn(e,r.config.content,d.header,t,{width:o,height:i}),Wn(e,r.config.content,d.footer,t,{width:o,height:i})};class rt extends Error{constructor(t){super(t),this.name="ChartConfigRenderError"}}const oo=async(e,t)=>{const n=Ue(e);if(!n.ok)throw new rt(n.reason);return ot(n.compiled,t)};exports.CANVAS_CAPABILITY_MATRIX=Vt;exports.CANVAS_SUPPORTED_GRAPH_TYPES=se;exports.CANVAS_UNSUPPORTED_GRAPH_TYPES=De;exports.ChartConfigRenderError=rt;exports.NodeCanvasTextMeasurer=et;exports.canRenderConfigWithCanvas=Ue;exports.darkTheme=nt;exports.ensureDefaultFonts=Ie;exports.lightTheme=tt;exports.loadCanvasRuntime=_e;exports.parseChartConfig=Oe;exports.registerFont=re;exports.renderChartConfigToPng=oo;exports.renderGraphToPng=to;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { ColorScheme } from '@graphysdk/viz-engine';
|
|
2
|
+
import { CompiledSpec } from '@graphysdk/viz-engine';
|
|
3
|
+
import { Data } from '@graphysdk/viz-engine';
|
|
4
|
+
import { FontSpec } from '@graphysdk/viz-engine';
|
|
5
|
+
import { GraphConfig } from '@graphysdk/viz-engine/graph-config';
|
|
6
|
+
import { Locale } from '@graphysdk/viz-engine';
|
|
7
|
+
import { MeasuredText } from '@graphysdk/viz-engine';
|
|
8
|
+
import type * as NapiCanvas from '@napi-rs/canvas';
|
|
9
|
+
import { SpecInput } from '@graphysdk/viz-engine';
|
|
10
|
+
import { TextMeasurer } from '@graphysdk/viz-engine';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Whether a stored chart config can be rendered with the canvas PNG pipeline.
|
|
14
|
+
* Compiles via viz-engine and checks each layer has a node geom renderer.
|
|
15
|
+
*/
|
|
16
|
+
export declare const canRenderConfigWithCanvas: (config: unknown) => CanvasChartConfigResult;
|
|
17
|
+
|
|
18
|
+
export declare const CANVAS_CAPABILITY_MATRIX: {
|
|
19
|
+
readonly supportedGraphTypes: readonly ["line", "areaStacked", "bar", "barStacked", "barStackedFill", "column", "columnStacked", "columnStackedFill", "combo", "pie", "donut", "scatter", "bubble"];
|
|
20
|
+
readonly unsupportedGraphTypes: readonly ["funnel", "heatmap", "waterfall", "mekko", "table"];
|
|
21
|
+
readonly supportedGeoms: {
|
|
22
|
+
readonly cartesian: readonly ["bar", "line", "area", "point", "rule"];
|
|
23
|
+
readonly polar: readonly ["bar"];
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Chart types the canvas pipeline supports (maps to node-renderer geoms after viz-engine conversion). */
|
|
28
|
+
export declare const CANVAS_SUPPORTED_GRAPH_TYPES: readonly ["line", "areaStacked", "bar", "barStacked", "barStackedFill", "column", "columnStacked", "columnStackedFill", "combo", "pie", "donut", "scatter", "bubble"];
|
|
29
|
+
|
|
30
|
+
/** Chart types viz-engine cannot convert (compile throws). */
|
|
31
|
+
export declare const CANVAS_UNSUPPORTED_GRAPH_TYPES: readonly ["funnel", "heatmap", "waterfall", "mekko", "table"];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Outcome of {@link canRenderConfigWithCanvas}: the {@link CompiledSpec} the check compiled to
|
|
35
|
+
* validate, so the render that follows paints it directly instead of compiling a second time.
|
|
36
|
+
*/
|
|
37
|
+
export declare type CanvasChartConfigResult = {
|
|
38
|
+
ok: true;
|
|
39
|
+
compiled: CompiledSpec;
|
|
40
|
+
} | ChartConfigRejection;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Lazy loader for `@napi-rs/canvas`. The native binding is platform-specific and
|
|
44
|
+
* must stay a runtime dependency (not bundled into serverless JS). Deferring the
|
|
45
|
+
* import until the first render avoids loading Skia when the module is imported
|
|
46
|
+
* but charts are not rendered.
|
|
47
|
+
*/
|
|
48
|
+
declare type CanvasModule = typeof NapiCanvas;
|
|
49
|
+
|
|
50
|
+
/** A rejection carries the reason both checks below report. */
|
|
51
|
+
declare type ChartConfigRejection = {
|
|
52
|
+
ok: false;
|
|
53
|
+
reason: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export declare class ChartConfigRenderError extends Error {
|
|
57
|
+
constructor(reason: string);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Split of core/API-shaped JSON into the high-level config and the table it embeds. */
|
|
61
|
+
export declare type ChartConfigResult = {
|
|
62
|
+
ok: true;
|
|
63
|
+
input: GraphConfig;
|
|
64
|
+
data: Data;
|
|
65
|
+
} | ChartConfigRejection;
|
|
66
|
+
|
|
67
|
+
export declare const darkTheme: RenderTheme;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Loads Inter from `@fontsource/inter` (npm dependency) and optional `assets/` TTF overrides.
|
|
71
|
+
* Safe on Vercel: font files resolve through `node_modules` at runtime; no writable disk required.
|
|
72
|
+
* Callers can still pass `fonts: [{ family, data: Buffer }]` in {@link RenderOptions} for custom faces.
|
|
73
|
+
*/
|
|
74
|
+
export declare const ensureDefaultFonts: () => boolean;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Caller-supplied font registration. Buffer-based so callers can ship fonts without disk I/O.
|
|
78
|
+
*
|
|
79
|
+
* `weight` and `style` are used only for the idempotency cache key. `GlobalFonts.register`
|
|
80
|
+
* reads weight and style from the font file itself — register separate files per weight,
|
|
81
|
+
* and use distinct `family` strings when multiple weights must be addressable in `ctx.font`.
|
|
82
|
+
*/
|
|
83
|
+
export declare interface FontRegistration {
|
|
84
|
+
family: string;
|
|
85
|
+
data: Buffer;
|
|
86
|
+
/** Cache-key only; not passed to `GlobalFonts.register`. */
|
|
87
|
+
weight?: number;
|
|
88
|
+
/** Cache-key only; not passed to `GlobalFonts.register`. */
|
|
89
|
+
style?: 'normal' | 'italic';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export declare const lightTheme: RenderTheme;
|
|
93
|
+
|
|
94
|
+
export declare const loadCanvasRuntime: () => Promise<CanvasModule>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* `TextMeasurer` backed by `@napi-rs/canvas`. Used both during layout (the
|
|
98
|
+
* engine's `LayoutMeasurer` wraps it) and at draw time (the renderer reuses the
|
|
99
|
+
* same `ctx.measureText()` semantics) — measurements and drawing stay
|
|
100
|
+
* consistent.
|
|
101
|
+
*
|
|
102
|
+
* Note: no emoji-width correction is needed here. The browser measurer applies
|
|
103
|
+
* one because it measures with Canvas but renders text via DOM `<text>`, and
|
|
104
|
+
* the two engines disagree on emoji width. The Node renderer measures *and*
|
|
105
|
+
* draws with the same `@napi-rs/canvas` context, so layout is internally
|
|
106
|
+
* consistent — raw widths are correct.
|
|
107
|
+
*/
|
|
108
|
+
export declare class NodeCanvasTextMeasurer implements TextMeasurer {
|
|
109
|
+
private readonly ctx;
|
|
110
|
+
private currentFont;
|
|
111
|
+
constructor();
|
|
112
|
+
measureText(text: string, font: FontSpec): MeasuredText;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Splits core/API-shaped `{ type, data, … }` JSON into viz-engine's `{ input, data }` pair.
|
|
117
|
+
*/
|
|
118
|
+
export declare const parseChartConfig: (config: unknown) => ChartConfigResult;
|
|
119
|
+
|
|
120
|
+
/** Registers a font with @napi-rs/canvas. Idempotent per {@link registrationKey} (family + weight + style). */
|
|
121
|
+
export declare const registerFont: (registration: FontRegistration) => void;
|
|
122
|
+
|
|
123
|
+
/** Renders a stored chart config (core-shaped JSON with embedded `data`) to PNG. */
|
|
124
|
+
export declare const renderChartConfigToPng: (config: unknown, options: RenderOptions) => Promise<Buffer>;
|
|
125
|
+
|
|
126
|
+
/** Compiles `input` against `data`, then renders the result to a PNG buffer (static output, no hover). */
|
|
127
|
+
export declare const renderGraphToPng: ({ input, data }: RenderGraphToPngInput, options: RenderOptions) => Promise<Buffer>;
|
|
128
|
+
|
|
129
|
+
export declare interface RenderGraphToPngInput {
|
|
130
|
+
input: SpecInput;
|
|
131
|
+
data: Data;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export declare interface RenderOptions {
|
|
135
|
+
width: number;
|
|
136
|
+
height: number;
|
|
137
|
+
/** Backing buffer scale for retina-quality output. Defaults to 2. */
|
|
138
|
+
pixelRatio?: number;
|
|
139
|
+
/** Picks the render theme and resolves light-dark stylesheet colors. Defaults to `'light'`. */
|
|
140
|
+
colorScheme?: ColorScheme;
|
|
141
|
+
fonts?: FontRegistration[];
|
|
142
|
+
formattingLocale?: Locale;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Resolved color palette used by the Node renderer. Mirrors the role-based theme
|
|
147
|
+
* variables in `@graphysdk/react-renderer/theme` but as plain hex values — Node has
|
|
148
|
+
* no CSS-variable resolution.
|
|
149
|
+
*/
|
|
150
|
+
export declare interface RenderTheme {
|
|
151
|
+
background: string;
|
|
152
|
+
textPrimary: string;
|
|
153
|
+
textSecondary: string;
|
|
154
|
+
gridLine: string;
|
|
155
|
+
panelBorder: string;
|
|
156
|
+
legendPillBackground: string;
|
|
157
|
+
legendPillBorder: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export { }
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
import { existsSync as tt, readFileSync as ot } from "node:fs";
|
|
2
|
+
import { createRequire as Le } from "node:module";
|
|
3
|
+
import O from "node:path";
|
|
4
|
+
import { fileURLToPath as nt } from "node:url";
|
|
5
|
+
import { convertGraphConfig as rt } from "@graphysdk/viz-engine/graph-config";
|
|
6
|
+
import { curveCatmullRom as st, curveLinear as it, area as Re, line as _e, arc as at } from "d3-shape";
|
|
7
|
+
import { createStyleResolver as D, GROUP_VARIABLES as we, getX as w, getY as _, getXMin as lt, getXMax as dt, getYMin as ct, getYMax as ft, getBarRectBounds as ut, buildColumnKey as ht, resolveBarBorderTreatment as Ie, resolveBarCornerRadiiPx as $, getBoundingRect as gt, getRuleDashPattern as mt, getRuleLabelTextColor as At, formatRuleValue as bt, buildFontString as S, getAngleExtent as pt, getRadiusExtent as yt, buildSliceGroupKey as Tt, getUnionExtent as Et, resolvePolarBarCornerRadiusUnit as St, createCompiler as Lt, isSafeUrl as Rt, extractPlainText as te, computePanelBorderPaths as _t, getDataLabelDimensions as wt, getDifferenceArrowDimensions as It, formatLegends as Bt, LayoutCompiler as Nt } from "@graphysdk/viz-engine";
|
|
8
|
+
import { Buffer as Ct } from "node:buffer";
|
|
9
|
+
let M, x;
|
|
10
|
+
const vt = () => M ? Promise.resolve(M) : (x ??= import("@napi-rs/canvas").then((e) => (M = e, e)).catch((e) => {
|
|
11
|
+
throw x = void 0, e;
|
|
12
|
+
}), x), V = () => {
|
|
13
|
+
if (!M)
|
|
14
|
+
throw new Error("Canvas runtime is not loaded. Call loadCanvasRuntime() before rendering.");
|
|
15
|
+
return M;
|
|
16
|
+
}, T = "Inter", oe = /* @__PURE__ */ new Set();
|
|
17
|
+
let ue = !1, Z = !1;
|
|
18
|
+
const Be = (e) => `${e.family}|${e.weight ?? 400}|${e.style ?? "normal"}`, Ne = (e) => {
|
|
19
|
+
const t = Be(e);
|
|
20
|
+
oe.has(t) || (V().GlobalFonts.register(e.data, e.family), oe.add(t));
|
|
21
|
+
}, Pt = [
|
|
22
|
+
{ file: "Inter-Regular.ttf", family: T, weight: 400 },
|
|
23
|
+
{ file: "Inter-SemiBold.ttf", family: T, weight: 600 }
|
|
24
|
+
], kt = [
|
|
25
|
+
{ file: "inter-latin-400-normal.woff", family: T, weight: 400 },
|
|
26
|
+
{ file: "inter-latin-600-normal.woff", family: T, weight: 600 }
|
|
27
|
+
], Dt = "@fontsource/inter", Ft = () => {
|
|
28
|
+
try {
|
|
29
|
+
const e = Le(import.meta.url), t = O.dirname(e.resolve("@graphysdk/node-renderer/package.json"));
|
|
30
|
+
return O.join(t, "assets");
|
|
31
|
+
} catch {
|
|
32
|
+
return O.resolve(O.dirname(nt(import.meta.url)), "../assets");
|
|
33
|
+
}
|
|
34
|
+
}, Ce = (e, t, o) => {
|
|
35
|
+
try {
|
|
36
|
+
return tt(e) ? (Ne({
|
|
37
|
+
family: t,
|
|
38
|
+
data: ot(e),
|
|
39
|
+
weight: o
|
|
40
|
+
}), !0) : !1;
|
|
41
|
+
} catch {
|
|
42
|
+
return !1;
|
|
43
|
+
}
|
|
44
|
+
}, Ot = () => {
|
|
45
|
+
const e = Ft();
|
|
46
|
+
for (const { file: t, family: o, weight: n } of Pt)
|
|
47
|
+
Ce(O.join(e, t), o, n) && (Z = !0);
|
|
48
|
+
}, Ht = () => {
|
|
49
|
+
const e = Le(import.meta.url);
|
|
50
|
+
for (const { file: t, family: o, weight: n } of kt)
|
|
51
|
+
if (!oe.has(Be({ family: o, data: Buffer.alloc(0), weight: n })))
|
|
52
|
+
try {
|
|
53
|
+
const s = e.resolve(`${Dt}/files/${t}`);
|
|
54
|
+
Ce(s, o, n) && (Z = !0);
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
}, Mt = () => (ue || (ue = !0, Ot(), Ht()), Z), B = (e, t) => e.x + t * e.width, I = (e, t) => e.y + (1 - t) * e.height, ne = (e) => e === "catmull-rom" ? st : it, ve = (e) => {
|
|
58
|
+
switch (e) {
|
|
59
|
+
case "dashed":
|
|
60
|
+
return [8, 4];
|
|
61
|
+
case "dotted":
|
|
62
|
+
return [2, 2];
|
|
63
|
+
case "solid":
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}, F = (e) => e, Gt = (e, t) => t !== "connect" ? e : e.filter((o) => w(o) !== null && _(o) !== null), Ut = (e, t, o) => {
|
|
67
|
+
const n = ne(t.interpolate), s = Re().curve(n), r = _e().curve(n);
|
|
68
|
+
if (e.mainAxis === "y") {
|
|
69
|
+
const i = (l) => I(o, _(l) ?? 0), a = (l) => B(o, lt(l) ?? 0), d = (l) => B(o, dt(l) ?? w(l) ?? 0);
|
|
70
|
+
s.y(i).x0(a).x1(d), r.y(i).x(d);
|
|
71
|
+
} else {
|
|
72
|
+
const i = (l) => B(o, w(l) ?? 0), a = (l) => I(o, ct(l) ?? 0), d = (l) => I(o, ft(l) ?? _(l) ?? 0);
|
|
73
|
+
s.x(i).y0(a).y1(d), r.x(i).y(d);
|
|
74
|
+
}
|
|
75
|
+
if (t.missingValues === "gap") {
|
|
76
|
+
const i = (a) => w(a) !== null && _(a) !== null;
|
|
77
|
+
s.defined(i), r.defined(i);
|
|
78
|
+
}
|
|
79
|
+
return { areaGenerator: s, lineGenerator: r };
|
|
80
|
+
}, Wt = F({
|
|
81
|
+
geom: "area",
|
|
82
|
+
coord: "cartesian",
|
|
83
|
+
draw: (e, { layer: t, coordSystem: o, panel: n, colorScheme: s }) => {
|
|
84
|
+
const r = D({ colorScheme: s }).geomReaders(t), { areaGenerator: i, lineGenerator: a } = Ut(o, t.params, n), d = e;
|
|
85
|
+
t.data.groupBy(we.group).forEach((l) => {
|
|
86
|
+
const c = Gt([...l], t.params.missingValues), f = c[0];
|
|
87
|
+
if (!f) return;
|
|
88
|
+
const u = r.get("color", f), h = r.get("alpha", f), g = r.get("strokeWidth", f), A = ve(r.get("lineType", f));
|
|
89
|
+
e.save(), e.globalAlpha = h, e.fillStyle = u, e.beginPath(), i.context(d)(c), e.fill(), e.restore(), e.save(), e.globalAlpha = r.get("strokeAlpha", f), e.strokeStyle = u, e.lineWidth = g, e.setLineDash(A), e.lineJoin = "round", e.lineCap = "round", e.beginPath(), a.context(d)(c), e.stroke(), e.restore();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}), he = (e, t, o, n, s, r) => {
|
|
93
|
+
const i = Math.max(0, Math.min(r.rx ?? r.ry ?? 0, n / 2)), a = Math.max(0, Math.min(r.ry ?? r.rx ?? 0, s / 2)), d = Math.PI / 2;
|
|
94
|
+
e.moveTo(t + i, o), e.lineTo(t + n - i, o), e.ellipse(t + n - i, o + a, i, a, 0, -d, 0), e.lineTo(t + n, o + s - a), e.ellipse(t + n - i, o + s - a, i, a, 0, 0, d), e.lineTo(t + i, o + s), e.ellipse(t + i, o + s - a, i, a, 0, d, Math.PI), e.lineTo(t, o + a), e.ellipse(t + i, o + a, i, a, 0, Math.PI, Math.PI * 1.5), e.closePath();
|
|
95
|
+
}, Vt = F({
|
|
96
|
+
geom: "bar",
|
|
97
|
+
coord: "cartesian",
|
|
98
|
+
draw: (e, { layer: t, coordSystem: o, panel: n, colorScheme: s }) => {
|
|
99
|
+
const { Path2D: r } = V(), i = D({ colorScheme: s }).geomReaders(t), a = o.mainAxis, d = /* @__PURE__ */ new Map();
|
|
100
|
+
for (const l of t.data) {
|
|
101
|
+
const c = ut(a, l);
|
|
102
|
+
if (!c) continue;
|
|
103
|
+
const f = B(n, c.x), u = n.y + c.y * n.height, h = {
|
|
104
|
+
x: f,
|
|
105
|
+
y: u,
|
|
106
|
+
width: c.width * n.width,
|
|
107
|
+
height: c.height * n.height,
|
|
108
|
+
fill: i.get("color", l),
|
|
109
|
+
opacity: i.get("alpha", l)
|
|
110
|
+
}, g = ht(a, c), A = d.get(g);
|
|
111
|
+
A ? A.bars.push(h) : d.set(g, {
|
|
112
|
+
bars: [h],
|
|
113
|
+
style: {
|
|
114
|
+
borderColor: i.get("borderColor", l),
|
|
115
|
+
borderWidth: i.get("borderWidth", l),
|
|
116
|
+
borderRadius: i.get("borderRadius", l),
|
|
117
|
+
borderAlpha: i.get("alpha", l)
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
for (const { bars: l, style: c } of d.values()) {
|
|
122
|
+
const { borderColor: f, borderRadius: u, borderWidth: h, borderAlpha: g } = c, A = Ie(h), b = u === "full" ? void 0 : $({ borderRadius: u, mainAxis: a });
|
|
123
|
+
if (!(l.length > 1)) {
|
|
124
|
+
const [m] = l;
|
|
125
|
+
if (!m) continue;
|
|
126
|
+
const v = b ?? $({ borderRadius: u, mainAxis: a, bounds: { width: m.width, height: m.height } }), L = new r();
|
|
127
|
+
he(L, m.x, m.y, m.width, m.height, v), e.save(), e.globalAlpha = m.opacity, e.fillStyle = m.fill, e.fill(L), f !== void 0 && (e.clip(L), e.strokeStyle = f, e.lineWidth = A.outlineWidth, e.stroke(L)), e.restore();
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const p = gt(l), R = b ?? $({ borderRadius: u, mainAxis: a, bounds: { width: p.width, height: p.height } }), y = new r();
|
|
131
|
+
he(y, p.x, p.y, p.width, p.height, R), e.save(), e.clip(y);
|
|
132
|
+
for (const m of l)
|
|
133
|
+
e.globalAlpha = m.opacity, e.fillStyle = m.fill, e.fillRect(m.x, m.y, m.width, m.height), f !== void 0 && (e.strokeStyle = f, e.lineWidth = A.separatorWidth, e.strokeRect(m.x, m.y, m.width, m.height));
|
|
134
|
+
f !== void 0 && (e.globalAlpha = g, e.strokeStyle = f, e.lineWidth = A.outlineWidth, e.stroke(y)), e.restore();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}), ge = (e, t) => {
|
|
138
|
+
const o = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(e.trim());
|
|
139
|
+
if (o?.[1] !== void 0) {
|
|
140
|
+
const s = o[1], r = s.length === 3 ? [...s].map((l) => l + l).join("") : s.slice(0, 6), i = Number.parseInt(r.slice(0, 2), 16), a = Number.parseInt(r.slice(2, 4), 16), d = Number.parseInt(r.slice(4, 6), 16);
|
|
141
|
+
return `rgba(${i}, ${a}, ${d}, ${t})`;
|
|
142
|
+
}
|
|
143
|
+
const n = /^rgba?\(([^)]+)\)$/i.exec(e.trim());
|
|
144
|
+
if (n?.[1] !== void 0) {
|
|
145
|
+
const s = n[1].split(",").map((d) => d.trim()), [r, i, a] = s;
|
|
146
|
+
if (r !== void 0 && i !== void 0 && a !== void 0)
|
|
147
|
+
return `rgba(${r}, ${i}, ${a}, ${t})`;
|
|
148
|
+
}
|
|
149
|
+
return t === 0 ? "rgba(0, 0, 0, 0)" : e;
|
|
150
|
+
}, Kt = (e, t) => t !== "connect" ? e : e.filter((o) => w(o) !== null && _(o) !== null), Xt = (e, t) => {
|
|
151
|
+
const { seriesObservations: o, fillGenerator: n, panel: s, color: r, fillAlpha: i } = t, a = o.map((l) => _(l)).filter((l) => l !== null).map((l) => I(s, l));
|
|
152
|
+
if (a.length === 0) return;
|
|
153
|
+
const d = e.createLinearGradient(0, Math.min(...a), 0, s.y + s.height);
|
|
154
|
+
d.addColorStop(0, r), d.addColorStop(0.7, ge(r, 0)), d.addColorStop(1, ge(r, 0)), e.save(), e.globalAlpha = i, e.fillStyle = d, e.beginPath(), n.context(e)(o), e.fill(), e.restore();
|
|
155
|
+
}, zt = F({
|
|
156
|
+
geom: "line",
|
|
157
|
+
coord: "cartesian",
|
|
158
|
+
draw: (e, { layer: t, panel: o, colorScheme: n }) => {
|
|
159
|
+
const s = D({ colorScheme: n }).geomReaders(t), r = _e().x((a) => B(o, w(a) ?? 0)).y((a) => I(o, _(a) ?? 0)).curve(ne(t.params.interpolate)), i = Re().x((a) => B(o, w(a) ?? 0)).y0(o.y + o.height).y1((a) => I(o, _(a) ?? 0)).curve(ne(t.params.interpolate));
|
|
160
|
+
if (t.params.missingValues === "gap") {
|
|
161
|
+
const a = (d) => w(d) !== null && _(d) !== null;
|
|
162
|
+
r.defined(a), i.defined(a);
|
|
163
|
+
}
|
|
164
|
+
t.data.groupBy(we.group).forEach((a) => {
|
|
165
|
+
const d = Kt([...a], t.params.missingValues), l = d[0];
|
|
166
|
+
if (!l) return;
|
|
167
|
+
const c = s.get("strokeWidth", l), f = ve(s.get("lineType", l)), u = s.get("color", l), h = s.get("fillAlpha", l);
|
|
168
|
+
h !== void 0 && Xt(e, { seriesObservations: d, fillGenerator: i, panel: o, color: u, fillAlpha: h }), e.save(), e.globalAlpha = s.get("alpha", l), e.strokeStyle = u, e.lineWidth = c, e.setLineDash(f), e.lineJoin = "round", e.lineCap = "round", e.beginPath(), r.context(e)(d), e.stroke(), e.restore();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}), Yt = F({
|
|
172
|
+
geom: "point",
|
|
173
|
+
coord: "cartesian",
|
|
174
|
+
draw: (e, { layer: t, panel: o, colorScheme: n }) => {
|
|
175
|
+
const s = D({ colorScheme: n }).geomReaders(t);
|
|
176
|
+
for (const r of t.data) {
|
|
177
|
+
const i = w(r), a = _(r);
|
|
178
|
+
if (i === null || a === null) continue;
|
|
179
|
+
const d = B(o, i), l = I(o, a), c = s.get("size", r) / 2, f = s.get("borderWidth", r);
|
|
180
|
+
e.save(), e.globalAlpha = s.get("alpha", r), e.fillStyle = s.get("color", r), e.strokeStyle = s.get("borderColor", r), e.lineWidth = f, e.beginPath(), e.arc(d, l, c, 0, Math.PI * 2), e.fill(), f > 0 && e.stroke(), e.restore();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}), ae = 11, qt = "sans-serif", Zt = 500, P = 5, Pe = 5, re = 13, ke = 6, K = 12, me = 21.6, jt = (e, t) => {
|
|
184
|
+
const o = e.mapping.y !== void 0, n = t.mainAxis === "y";
|
|
185
|
+
return o !== n;
|
|
186
|
+
}, Qt = (e, t, o) => {
|
|
187
|
+
const n = mt(t, o);
|
|
188
|
+
e.setLineDash(n), n.length > 0 && (e.lineCap = "round");
|
|
189
|
+
}, Jt = (e, t) => X(e) && X(t) ? `${e}: ${t}` : X(e) ? e : X(t) ? t : null, X = (e) => e != null && e.trim() !== "", $t = (e) => {
|
|
190
|
+
e.beginPath(), e.moveTo(2.48935, 16.9323), e.lineTo(9.07098, 9.82964), e.bezierCurveTo(9.51595, 9.34944, 9.51504, 8.60728, 9.0689, 8.12818), e.lineTo(2.48866, 1.06172), e.bezierCurveTo(1.85811, 0.384586, 0.974477, 0, 0.049217, 0), e.lineTo(0, 0), e.lineTo(0, 18), e.lineTo(0.0443689, 18), e.bezierCurveTo(0.972463, 18, 1.85854, 17.6131, 2.48935, 16.9323), e.closePath();
|
|
191
|
+
}, De = (e, t, o, n, s, r) => {
|
|
192
|
+
const i = Math.min(r, n / 2, s / 2);
|
|
193
|
+
e.beginPath(), e.moveTo(t + i, o), e.lineTo(t + n - i, o), e.arcTo(t + n, o, t + n, o + i, i), e.lineTo(t + n, o + s - i), e.arcTo(t + n, o + s, t + n - i, o + s, i), e.lineTo(t + i, o + s), e.arcTo(t, o + s, t, o + s - i, i), e.lineTo(t, o + i), e.arcTo(t, o, t + i, o, i), e.closePath(), e.fill();
|
|
194
|
+
}, xt = (e, t, o, n, s, r, i, a) => {
|
|
195
|
+
const d = o + P * 2, l = ae + Pe * 2, c = n === "start", f = c ? r.x : r.x + r.width, u = I(r, s), h = c ? -re : -(d - re), g = c ? h + d - P : h + P - K;
|
|
196
|
+
e.save(), e.translate(f, u), e.translate(0, -l / 2), e.fillStyle = i, De(e, h, 0, d, l, ke), e.save(), c || (e.translate(g + K / 2, l / 2), e.rotate(Math.PI), e.translate(-(g + K / 2), -l / 2)), e.translate(g, (l - me) / 2), e.scale(K / 10, me / 18), e.fillStyle = i, $t(e), e.fill(), e.restore(), e.fillStyle = a, e.textAlign = "left", e.textBaseline = "middle", e.fillText(t, h + P, l / 2), e.restore();
|
|
197
|
+
}, eo = (e, t, o, n, s, r, i, a) => {
|
|
198
|
+
const d = o + P * 2, l = ae + Pe * 2, c = n === "start", f = B(r, s), u = c ? r.y + r.height : r.y, h = c ? -re : -8;
|
|
199
|
+
e.save(), e.translate(f, u), e.translate(-d / 2, 0), e.fillStyle = i, De(e, 0, h, d, l, ke), e.fillStyle = a, e.textAlign = "left", e.textBaseline = "middle", e.fillText(t, P, h + l / 2), e.restore();
|
|
200
|
+
}, to = F({
|
|
201
|
+
geom: "rule",
|
|
202
|
+
coord: "cartesian",
|
|
203
|
+
draw: (e, { layer: t, coordSystem: o, panel: n, compiled: s, textMeasurer: r, formattingLocale: i, colorScheme: a }) => {
|
|
204
|
+
const d = t.data.getFirst();
|
|
205
|
+
if (!d) return;
|
|
206
|
+
const l = jt(t, o), c = l ? _(d) : w(d);
|
|
207
|
+
if (c === null) return;
|
|
208
|
+
const f = D({ colorScheme: a }).geomReaders(t), u = f.get("color", d), h = At(u), g = f.get("strokeWidth", d);
|
|
209
|
+
if (e.save(), e.strokeStyle = u, e.lineWidth = g, Qt(e, f.get("lineType", d), g), e.beginPath(), l) {
|
|
210
|
+
const y = I(n, c);
|
|
211
|
+
e.moveTo(n.x, y), e.lineTo(n.x + n.width, y);
|
|
212
|
+
} else {
|
|
213
|
+
const y = B(n, c);
|
|
214
|
+
e.moveTo(y, n.y), e.lineTo(y, n.y + n.height);
|
|
215
|
+
}
|
|
216
|
+
e.stroke(), e.restore();
|
|
217
|
+
const A = Jt(
|
|
218
|
+
t.params.label,
|
|
219
|
+
bt({
|
|
220
|
+
guides: s.guides,
|
|
221
|
+
numberFormat: s.config.numberFormat,
|
|
222
|
+
layer: t,
|
|
223
|
+
locale: i ?? s.config.parsingLocale
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
if (A === null) return;
|
|
227
|
+
const b = s.config.appearance.textScale, E = {
|
|
228
|
+
family: qt,
|
|
229
|
+
size: ae * b,
|
|
230
|
+
weight: Zt
|
|
231
|
+
};
|
|
232
|
+
e.font = S(E);
|
|
233
|
+
const p = r.measureText(A, E).width;
|
|
234
|
+
(l ? xt : eo)(e, A, p, t.params.labelPosition, c, n, u, h);
|
|
235
|
+
}
|
|
236
|
+
}), H = at();
|
|
237
|
+
H.digits(8);
|
|
238
|
+
const oo = F({
|
|
239
|
+
geom: "bar",
|
|
240
|
+
coord: "polar",
|
|
241
|
+
draw: (e, { layer: t, coordSystem: o, panel: n, colorScheme: s }) => {
|
|
242
|
+
const r = n.x + n.width / 2, i = n.y + n.height / 2, a = Math.min(n.width, n.height) / 2, { Path2D: d } = V(), l = D({ colorScheme: s }).geomReaders(t);
|
|
243
|
+
e.save(), e.translate(r, i), e.scale(a, a);
|
|
244
|
+
const c = e, f = /* @__PURE__ */ new Map();
|
|
245
|
+
for (const u of t.data) {
|
|
246
|
+
const { startAngle: h, endAngle: g } = pt(u), { innerRadius: A, outerRadius: b } = yt(u);
|
|
247
|
+
if (h === null || g === null || A === null || b === null) continue;
|
|
248
|
+
const E = {
|
|
249
|
+
startAngle: h,
|
|
250
|
+
endAngle: g,
|
|
251
|
+
innerRadius: A,
|
|
252
|
+
outerRadius: b,
|
|
253
|
+
fill: l.get("color", u),
|
|
254
|
+
opacity: l.get("alpha", u)
|
|
255
|
+
}, p = Tt(o.bandAxis, E), R = f.get(p);
|
|
256
|
+
R ? R.items.push(E) : f.set(p, {
|
|
257
|
+
items: [E],
|
|
258
|
+
style: {
|
|
259
|
+
borderColor: l.get("borderColor", u),
|
|
260
|
+
borderWidth: l.get("borderWidth", u),
|
|
261
|
+
borderRadius: l.get("borderRadius", u),
|
|
262
|
+
borderAlpha: l.get("alpha", u)
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
for (const { items: u, style: h } of f.values()) {
|
|
267
|
+
const { borderColor: g, borderRadius: A, borderWidth: b, borderAlpha: E } = h, p = Ie(b), R = g !== void 0 && a > 0, y = Et(u), m = St(A, y.outerRadius - y.innerRadius), v = a > 0 && (R || m > 0);
|
|
268
|
+
e.save();
|
|
269
|
+
let L;
|
|
270
|
+
v && (L = new d(), H.cornerRadius(m), H.context(L)(y), e.clip(L)), H.cornerRadius(0);
|
|
271
|
+
for (const J of u)
|
|
272
|
+
e.globalAlpha = J.opacity, e.fillStyle = J.fill, e.beginPath(), H.context(c)(J), e.fill(), R && (e.strokeStyle = g, e.lineWidth = p.separatorWidth / a, e.stroke());
|
|
273
|
+
R && L && (e.globalAlpha = E, e.strokeStyle = g, e.lineWidth = p.outlineWidth / a, e.stroke(L)), e.restore();
|
|
274
|
+
}
|
|
275
|
+
e.restore();
|
|
276
|
+
}
|
|
277
|
+
}), no = {
|
|
278
|
+
bar: { cartesian: Vt, polar: oo },
|
|
279
|
+
line: { cartesian: zt },
|
|
280
|
+
area: { cartesian: Wt },
|
|
281
|
+
point: { cartesian: Yt },
|
|
282
|
+
rule: { cartesian: to }
|
|
283
|
+
}, Fe = (e, t) => (
|
|
284
|
+
// `CompiledLayer.geom` is a plain string (a custom geom carries a name outside `GeomName`); the
|
|
285
|
+
// node renderer ships only built-ins, so an unregistered name simply resolves to `null`.
|
|
286
|
+
no[e.geom]?.[t.type] ?? null
|
|
287
|
+
), ro = (e, t, o) => {
|
|
288
|
+
e.draw(t, o);
|
|
289
|
+
};
|
|
290
|
+
let Ae;
|
|
291
|
+
const Oe = () => (Ae ??= Lt(), Ae), He = [
|
|
292
|
+
"line",
|
|
293
|
+
"areaStacked",
|
|
294
|
+
"bar",
|
|
295
|
+
"barStacked",
|
|
296
|
+
"barStackedFill",
|
|
297
|
+
"column",
|
|
298
|
+
"columnStacked",
|
|
299
|
+
"columnStackedFill",
|
|
300
|
+
"combo",
|
|
301
|
+
"pie",
|
|
302
|
+
"donut",
|
|
303
|
+
"scatter",
|
|
304
|
+
"bubble"
|
|
305
|
+
], so = [
|
|
306
|
+
"funnel",
|
|
307
|
+
"heatmap",
|
|
308
|
+
"waterfall",
|
|
309
|
+
"mekko",
|
|
310
|
+
"table"
|
|
311
|
+
], io = new Set(He), Fn = {
|
|
312
|
+
supportedGraphTypes: [...He],
|
|
313
|
+
unsupportedGraphTypes: [...so],
|
|
314
|
+
supportedGeoms: {
|
|
315
|
+
cartesian: ["bar", "line", "area", "point", "rule"],
|
|
316
|
+
polar: ["bar"]
|
|
317
|
+
}
|
|
318
|
+
}, C = (e) => ({ ok: !1, reason: e }), j = (e) => typeof e == "object" && e !== null && !Array.isArray(e), ao = (e) => {
|
|
319
|
+
if (!j(e)) return !1;
|
|
320
|
+
const { columns: t, rows: o } = e;
|
|
321
|
+
return !Array.isArray(t) || !Array.isArray(o) ? !1 : t.every((n) => j(n) && typeof n.key == "string");
|
|
322
|
+
}, lo = (e) => {
|
|
323
|
+
if ("_unstable_mapping" in e && e._unstable_mapping !== void 0) return !0;
|
|
324
|
+
const t = e.referenceLines;
|
|
325
|
+
return j(t) ? t.trendline !== void 0 || t.averageLine !== void 0 : !1;
|
|
326
|
+
}, co = (e) => {
|
|
327
|
+
if (!j(e))
|
|
328
|
+
return C("Chart config must be a plain object.");
|
|
329
|
+
if (!ao(e.data))
|
|
330
|
+
return C("Chart config is missing a valid `data` object (columns + rows).");
|
|
331
|
+
const { data: t, ...o } = e;
|
|
332
|
+
return { ok: !0, input: o, data: t };
|
|
333
|
+
}, fo = (e) => {
|
|
334
|
+
const t = co(e);
|
|
335
|
+
if (!t.ok) return t;
|
|
336
|
+
if (lo(e))
|
|
337
|
+
return C(
|
|
338
|
+
"Canvas renderer does not support `_unstable_mapping`, `referenceLines.trendline`, or `referenceLines.averageLine`."
|
|
339
|
+
);
|
|
340
|
+
const o = t.input.type ?? "column";
|
|
341
|
+
if (!io.has(o))
|
|
342
|
+
return C(`Unsupported chart type for canvas rendering: ${o}.`);
|
|
343
|
+
try {
|
|
344
|
+
const n = Oe(), s = rt(t.input, t.data), r = n.compile({ input: s, data: t.data });
|
|
345
|
+
if (!r.ok)
|
|
346
|
+
return C(
|
|
347
|
+
`Chart config failed to compile: ${r.errors.map((i) => i.message).join("; ")}`
|
|
348
|
+
);
|
|
349
|
+
for (const i of r.compiled.layers)
|
|
350
|
+
if (Fe(i, r.compiled.coordSystem) === null)
|
|
351
|
+
return C(`No canvas geom renderer for ${i.geom} (${r.compiled.coordSystem.type} coord).`);
|
|
352
|
+
return { ok: !0, compiled: r.compiled };
|
|
353
|
+
} catch (n) {
|
|
354
|
+
const s = n instanceof Error ? n.message : String(n);
|
|
355
|
+
return C(`Chart config failed to compile: ${s}`);
|
|
356
|
+
}
|
|
357
|
+
}, z = 6, q = 10, uo = 20, le = T, de = 12, Me = T, G = 12, ce = 1.3, U = 10, W = 12, k = 6, Ge = 16, ho = 24, Ue = 6, We = 3, se = 4, go = 8, mo = "#9ca3af", Ve = 16, Ao = 1.3, Ke = 13, bo = 1.4, Xe = 12, po = 1.4, Q = 8, fe = 12, yo = 1.3, To = 200, Eo = 120, So = 80, ze = 22, Lo = 9, Ro = 11, _o = 10, Y = 9, be = 12, wo = '-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif', Io = 11.5, Bo = 550, No = { light: 0.55, mid: 0.75, dark: 0.08 }, Co = { light: 0.62, mid: 0.8, dark: 0.85 }, vo = 0.22, Po = 0.65, ko = T, Do = 12, Fo = { family: le, size: de }, Oo = { family: ko, size: Do }, Ho = { family: Me, size: G }, Mo = 8, pe = 1, Go = [2, 3], Uo = [1, 3], Wo = (e, t, o, n, s) => {
|
|
358
|
+
if (!t.isVisible || t.ticks.length === 0) return;
|
|
359
|
+
const r = t.position === "bottom" || t.position === "top", i = t.ticksVisible ? 1 : 0;
|
|
360
|
+
e.save(), e.font = S(Fo), e.fillStyle = s.textSecondary, e.strokeStyle = s.gridLine, e.lineWidth = 1, e.setLineDash([]);
|
|
361
|
+
for (const a of t.ticks) {
|
|
362
|
+
const d = r ? a.position : 1 - a.position;
|
|
363
|
+
t.ticksVisible && Ko(e, t.position, d, o, n), zo(
|
|
364
|
+
e,
|
|
365
|
+
t.position,
|
|
366
|
+
d,
|
|
367
|
+
o,
|
|
368
|
+
n,
|
|
369
|
+
a.formattedLabel,
|
|
370
|
+
t.labelRotation,
|
|
371
|
+
i
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
e.restore();
|
|
375
|
+
}, Vo = (e, t, o, n) => {
|
|
376
|
+
if (e === "bottom" || e === "top") {
|
|
377
|
+
const a = n.x + t * n.width, d = e === "top" ? o.y + o.height : o.y, l = e === "top" ? -z : z;
|
|
378
|
+
return { x1: a, y1: d, x2: a, y2: d + l };
|
|
379
|
+
}
|
|
380
|
+
const s = n.y + t * n.height, r = e === "right" ? o.x : o.x + o.width, i = e === "right" ? z : -z;
|
|
381
|
+
return { x1: r, y1: s, x2: r + i, y2: s };
|
|
382
|
+
}, Ko = (e, t, o, n, s) => {
|
|
383
|
+
const { x1: r, y1: i, x2: a, y2: d } = Vo(t, o, n, s);
|
|
384
|
+
e.beginPath(), e.moveTo(r, i), e.lineTo(a, d), e.stroke();
|
|
385
|
+
}, Xo = (e, t, o, n, s) => {
|
|
386
|
+
if (e === "bottom" || e === "top") {
|
|
387
|
+
const i = n.x + t * n.width;
|
|
388
|
+
return e === "bottom" ? { x: i, y: o.y + s, textAlign: "center", textBaseline: "top" } : { x: i, y: o.y + o.height - s, textAlign: "center", textBaseline: "alphabetic" };
|
|
389
|
+
}
|
|
390
|
+
const r = n.y + t * n.height;
|
|
391
|
+
return e === "left" ? { x: o.x + o.width - s, y: r, textAlign: "right", textBaseline: "middle" } : { x: o.x + s, y: r, textAlign: "left", textBaseline: "middle" };
|
|
392
|
+
}, zo = (e, t, o, n, s, r, i, a) => {
|
|
393
|
+
const d = q * a;
|
|
394
|
+
if (i !== 0 && (t === "top" || t === "bottom")) {
|
|
395
|
+
const c = s.x + o * s.width, f = t === "top";
|
|
396
|
+
e.save(), e.translate(c, f ? n.y + n.height - d : n.y + d), e.rotate(i * Math.PI / 180), e.textAlign = f ? "left" : "right", e.textBaseline = "middle", e.fillText(r, 0, 0), e.restore();
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const l = Xo(t, o, n, s, d);
|
|
400
|
+
e.textAlign = l.textAlign, e.textBaseline = l.textBaseline, e.fillText(r, l.x, l.y);
|
|
401
|
+
}, Yo = (e, t, o, n) => {
|
|
402
|
+
if (!t.isVisible || !t.label) return;
|
|
403
|
+
e.save(), e.font = S(Oo), e.fillStyle = n.textPrimary, e.textBaseline = "middle";
|
|
404
|
+
const { x: s, y: r, alignment: i } = qo(t.position, o);
|
|
405
|
+
e.textAlign = i, e.fillText(t.label, s, r), e.restore();
|
|
406
|
+
}, qo = (e, t) => {
|
|
407
|
+
const o = t.y + t.height / 2;
|
|
408
|
+
return e === "left" ? { x: t.x, y: o, alignment: "start" } : e === "right" ? { x: t.x + t.width, y: o, alignment: "end" } : { x: t.x + t.width / 2, y: o, alignment: "center" };
|
|
409
|
+
}, Zo = "data:image/png;base64,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", N = ze;
|
|
410
|
+
let ee;
|
|
411
|
+
const jo = () => {
|
|
412
|
+
if (ee) return ee;
|
|
413
|
+
const { Image: e } = V(), t = new e(), o = Zo.replace(/^data:image\/png;base64,/, "");
|
|
414
|
+
return t.src = Ct.from(o, "base64"), ee = t, t;
|
|
415
|
+
}, Ye = (e, t, o = "full") => !e || t.width < Eo || t.height < So ? "hidden" : t.width < To || o === "mini" ? "mini" : "full", Qo = (e) => {
|
|
416
|
+
const t = Jo(e);
|
|
417
|
+
if (t === null) return "light";
|
|
418
|
+
const o = $o(t);
|
|
419
|
+
return o <= vo ? "dark" : o <= Po ? "mid" : "light";
|
|
420
|
+
}, ie = (e, t, o) => e.placement !== o || Ye(e.enabled, t, e.variant) === "hidden" ? 0 : ze, qe = (e, t, o, n, s, r) => {
|
|
421
|
+
if (t.placement !== r) return;
|
|
422
|
+
const i = Ye(t.enabled, s, t.variant);
|
|
423
|
+
if (i === "hidden") return;
|
|
424
|
+
const a = Qo(n.background), d = a === "dark", l = { family: wo, size: Io, weight: 400 }, c = { ...l, weight: Bo };
|
|
425
|
+
let f = 0;
|
|
426
|
+
i === "full" && (e.font = S(l), f = e.measureText("Made with ").width, e.font = S(c), f += e.measureText("Graphy").width);
|
|
427
|
+
const u = i === "mini" ? 0 : Lo, h = i === "mini" ? 0 : Ro, g = i === "mini" ? 0 : _o, A = i === "mini" ? N : u + Y + g + f + h, b = o.x + o.width - A, E = o.y + Math.max(0, (o.height || N) / 2 - N / 2);
|
|
428
|
+
e.save(), e.globalAlpha = Co[a], e.textBaseline = "middle", e.textAlign = "left", d || (e.shadowColor = "rgba(0, 0, 0, 0.09)", e.shadowBlur = 4, e.shadowOffsetY = 1), e.beginPath(), e.roundRect(b, E, A, N, N / 2), e.fillStyle = `rgba(255, 255, 255, ${No[a]})`, e.fill(), e.shadowColor = "transparent", e.shadowBlur = 0, e.shadowOffsetY = 0;
|
|
429
|
+
const p = d ? "#FFFFFF" : "#2A2A28", R = i === "mini" ? b + (A - Y) / 2 : b + u, y = E + (N - be) / 2;
|
|
430
|
+
if (e.save(), d && (e.filter = "invert(1)"), e.drawImage(jo(), R, y, Y, be), e.restore(), i === "full") {
|
|
431
|
+
const m = b + u + Y + g, v = E + N / 2;
|
|
432
|
+
e.fillStyle = p, e.font = S(l), e.fillText("Made with ", m, v);
|
|
433
|
+
const L = e.measureText("Made with ").width;
|
|
434
|
+
e.font = S(c), e.fillText("Graphy", m + L, v);
|
|
435
|
+
}
|
|
436
|
+
e.restore();
|
|
437
|
+
}, Jo = (e) => {
|
|
438
|
+
const t = e.trim();
|
|
439
|
+
if (!t.startsWith("#")) return null;
|
|
440
|
+
const o = t.slice(1);
|
|
441
|
+
if (o.length === 3) {
|
|
442
|
+
const n = o[0], s = o[1], r = o[2];
|
|
443
|
+
return n === void 0 || s === void 0 || r === void 0 ? null : {
|
|
444
|
+
red: Number.parseInt(n + n, 16),
|
|
445
|
+
green: Number.parseInt(s + s, 16),
|
|
446
|
+
blue: Number.parseInt(r + r, 16)
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
return o.length >= 6 ? {
|
|
450
|
+
red: Number.parseInt(o.slice(0, 2), 16),
|
|
451
|
+
green: Number.parseInt(o.slice(2, 4), 16),
|
|
452
|
+
blue: Number.parseInt(o.slice(4, 6), 16)
|
|
453
|
+
} : null;
|
|
454
|
+
}, $o = ({ red: e, green: t, blue: o }) => {
|
|
455
|
+
const n = (s) => {
|
|
456
|
+
const r = s / 255;
|
|
457
|
+
return r <= 0.03928 ? r / 12.92 : ((r + 0.055) / 1.055) ** 2.4;
|
|
458
|
+
};
|
|
459
|
+
return 0.2126 * n(e) + 0.7152 * n(t) + 0.0722 * n(o);
|
|
460
|
+
}, xo = { family: T, size: Ve, weight: 600 }, en = { family: T, size: Ke }, tn = { family: T, size: Xe }, on = { family: T, size: fe, weight: 500 }, nn = { family: T, size: fe }, Ze = Ve * Ao, rn = Ke * bo, je = Xe * po, Qe = fe * yo, Je = (e) => {
|
|
461
|
+
if (e === null) return null;
|
|
462
|
+
if (e.label !== void 0 && e.label !== "") return e.label;
|
|
463
|
+
if (e.url !== void 0 && e.url !== "" && Rt(e.url))
|
|
464
|
+
try {
|
|
465
|
+
return new URL(e.url).hostname;
|
|
466
|
+
} catch {
|
|
467
|
+
return e.url;
|
|
468
|
+
}
|
|
469
|
+
return null;
|
|
470
|
+
}, sn = (e, t) => {
|
|
471
|
+
let o = 0;
|
|
472
|
+
const n = e.isTitleVisible && e.title !== null, s = e.isSubtitleVisible && e.subtitle !== null;
|
|
473
|
+
n && (o += Ze), s && (o > 0 && (o += Q), o += rn);
|
|
474
|
+
const r = ie(e.brandMark, t, "header");
|
|
475
|
+
!n && !s && r > 0 && (o = r);
|
|
476
|
+
const i = e.isCaptionVisible && e.caption !== null, a = e.isSourceVisible && Je(e.source) !== null, d = ie(e.brandMark, t, "footer");
|
|
477
|
+
let l = 0;
|
|
478
|
+
return i && (l += je), (a || d > 0) && (l > 0 && (l += Q), l += Math.max(Qe, d)), { headerHeight: o, footerHeight: l };
|
|
479
|
+
}, an = (e, t, o, n, s) => {
|
|
480
|
+
if (o.height === 0) return;
|
|
481
|
+
e.save(), e.textBaseline = "top", e.textAlign = "left";
|
|
482
|
+
let r = o.y;
|
|
483
|
+
t.isTitleVisible && t.title !== null && (e.font = S(xo), e.fillStyle = n.textPrimary, e.fillText(te(t.title), o.x, r), r += Ze + Q), t.isSubtitleVisible && t.subtitle !== null && (e.font = S(en), e.fillStyle = n.textSecondary, e.fillText(te(t.subtitle), o.x, r)), e.restore(), qe(e, t.brandMark, o, n, s, "header");
|
|
484
|
+
}, ln = (e, t, o, n, s) => {
|
|
485
|
+
if (o.height === 0) return;
|
|
486
|
+
const r = t.isCaptionVisible ? t.caption : null, i = t.isSourceVisible ? Je(t.source) : null, a = ie(t.brandMark, s, "footer");
|
|
487
|
+
if (r === null && i === null && a === 0) return;
|
|
488
|
+
e.save(), e.textBaseline = "top", e.textAlign = "left";
|
|
489
|
+
let d = o.y;
|
|
490
|
+
if (r !== null && (e.font = S(tn), e.fillStyle = n.textSecondary, e.fillText(te(r), o.x, d), d += je + Q), i !== null) {
|
|
491
|
+
let l = o.x;
|
|
492
|
+
l = ye(e, "Source: ", on, n.textSecondary, l, d), ye(e, i, nn, n.textSecondary, l, d);
|
|
493
|
+
}
|
|
494
|
+
e.restore(), qe(
|
|
495
|
+
e,
|
|
496
|
+
t.brandMark,
|
|
497
|
+
{ x: o.x, y: d, width: o.width, height: Math.max(Qe, a) },
|
|
498
|
+
n,
|
|
499
|
+
s,
|
|
500
|
+
"footer"
|
|
501
|
+
);
|
|
502
|
+
}, ye = (e, t, o, n, s, r) => (e.font = S(o), e.fillStyle = n, e.fillText(t, s, r), s + e.measureText(t).width), dn = (e, t, o, n, s) => {
|
|
503
|
+
e.save(), e.lineCap = "round", e.strokeStyle = s.gridLine;
|
|
504
|
+
for (const r of _t(o.border, n, Mo))
|
|
505
|
+
e.lineWidth = r.lineWidth ?? pe, e.setLineDash(Te(r.lineStyle)), cn(e, r.segments), e.stroke();
|
|
506
|
+
for (const r of t) {
|
|
507
|
+
if (!r.gridVisible) continue;
|
|
508
|
+
const i = r.position === "top" || r.position === "bottom";
|
|
509
|
+
e.lineWidth = r.gridLineWidth ?? pe, e.setLineDash(Te(r.gridLineStyle));
|
|
510
|
+
for (const a of r.ticks) {
|
|
511
|
+
if (a.position <= 0 || a.position >= 1) continue;
|
|
512
|
+
const d = i ? a.position : 1 - a.position;
|
|
513
|
+
if (e.beginPath(), i) {
|
|
514
|
+
const l = n.x + d * n.width;
|
|
515
|
+
e.moveTo(l, n.y), e.lineTo(l, n.y + n.height);
|
|
516
|
+
} else {
|
|
517
|
+
const l = n.y + d * n.height;
|
|
518
|
+
e.moveTo(n.x, l), e.lineTo(n.x + n.width, l);
|
|
519
|
+
}
|
|
520
|
+
e.stroke();
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
e.restore();
|
|
524
|
+
}, Te = (e) => {
|
|
525
|
+
switch (e) {
|
|
526
|
+
case "dashed":
|
|
527
|
+
return Go;
|
|
528
|
+
case "dotted":
|
|
529
|
+
return Uo;
|
|
530
|
+
case "solid":
|
|
531
|
+
return [];
|
|
532
|
+
}
|
|
533
|
+
}, cn = (e, t) => {
|
|
534
|
+
e.beginPath();
|
|
535
|
+
for (const o of t)
|
|
536
|
+
switch (o.type) {
|
|
537
|
+
case "move":
|
|
538
|
+
e.moveTo(o.x, o.y);
|
|
539
|
+
break;
|
|
540
|
+
case "line":
|
|
541
|
+
e.lineTo(o.x, o.y);
|
|
542
|
+
break;
|
|
543
|
+
case "arc":
|
|
544
|
+
e.arc(o.cx, o.cy, o.radius, o.startAngle, o.endAngle);
|
|
545
|
+
break;
|
|
546
|
+
case "close":
|
|
547
|
+
e.closePath();
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
}, fn = (e, t, o, n) => {
|
|
551
|
+
e.save(), e.font = S(Ho), e.textBaseline = "middle";
|
|
552
|
+
for (const r of t)
|
|
553
|
+
r.display === "direct" && un(e, r, o, n);
|
|
554
|
+
const s = /* @__PURE__ */ new Map();
|
|
555
|
+
for (const r of t) {
|
|
556
|
+
if (r.display !== "pill" || r.items.length === 0) continue;
|
|
557
|
+
const i = s.get(r.position);
|
|
558
|
+
i ? i.push(r) : s.set(r.position, [r]);
|
|
559
|
+
}
|
|
560
|
+
for (const [r, i] of s) {
|
|
561
|
+
const a = o[r];
|
|
562
|
+
a && (r === "top" || r === "bottom" ? hn(e, i, a, n) : gn(e, i, a, n));
|
|
563
|
+
}
|
|
564
|
+
e.restore();
|
|
565
|
+
}, un = (e, t, o, n) => {
|
|
566
|
+
const s = o[t.position];
|
|
567
|
+
if (s) {
|
|
568
|
+
e.textAlign = "left";
|
|
569
|
+
for (const r of t.items)
|
|
570
|
+
r.normalizedY !== null && (e.fillStyle = typeof r.visual.color == "string" ? r.visual.color : n.textPrimary, e.fillText(r.formattedLabel, s.x + go, I(s, r.normalizedY)));
|
|
571
|
+
}
|
|
572
|
+
}, hn = (e, t, o, n) => {
|
|
573
|
+
const s = $e(t), r = s.reduce(
|
|
574
|
+
(l, c, f) => l + An(c) + e.measureText(c.label).width + (f === 0 ? 0 : Ee(c)),
|
|
575
|
+
0
|
|
576
|
+
), i = G * ce;
|
|
577
|
+
let a = o.x + Math.max(0, (o.width - r) / 2);
|
|
578
|
+
const d = o.y + Ue + Math.max(i, W) / 2;
|
|
579
|
+
for (let l = 0; l < s.length; l++) {
|
|
580
|
+
const c = s[l];
|
|
581
|
+
c && (l > 0 && (a += Ee(c)), a = xe(e, c, a, d, n));
|
|
582
|
+
}
|
|
583
|
+
}, gn = (e, t, o, n) => {
|
|
584
|
+
const s = $e(t), r = Math.max(G * ce, W), i = 4, a = s.length * r + Math.max(0, s.length - 1) * i;
|
|
585
|
+
let d = o.y + Math.max(0, (o.height - a) / 2) + r / 2;
|
|
586
|
+
for (const l of s)
|
|
587
|
+
xe(e, l, o.x, d, n), d += r + i;
|
|
588
|
+
}, $e = (e) => {
|
|
589
|
+
const t = [];
|
|
590
|
+
for (const o of e) {
|
|
591
|
+
const n = o.aesthetics.includes("size");
|
|
592
|
+
o.items.forEach((s, r) => {
|
|
593
|
+
const i = s.visual.size, a = n && typeof i == "number" && Number.isFinite(i) && i > 0 ? i : void 0;
|
|
594
|
+
t.push({
|
|
595
|
+
label: s.formattedLabel,
|
|
596
|
+
colors: mn(s.visual.color),
|
|
597
|
+
bubbleSize: a,
|
|
598
|
+
isFirstInGroup: r === 0
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
return t;
|
|
603
|
+
}, mn = (e) => typeof e == "string" ? [e] : Array.isArray(e) ? e.filter((t) => typeof t == "string") : [], Ee = (e) => e.isFirstInGroup ? ho : Ge, An = (e) => {
|
|
604
|
+
if (e.bubbleSize !== void 0) return e.bubbleSize + k;
|
|
605
|
+
const t = e.colors.length;
|
|
606
|
+
if (t === 0) return 0;
|
|
607
|
+
if (t === 1) return U + k;
|
|
608
|
+
const o = Math.min(t, We);
|
|
609
|
+
return U + (o - 1) * se + k;
|
|
610
|
+
}, xe = (e, t, o, n, s) => {
|
|
611
|
+
let r = o;
|
|
612
|
+
if (t.bubbleSize !== void 0) {
|
|
613
|
+
const i = t.bubbleSize / 2;
|
|
614
|
+
e.fillStyle = t.colors[0] ?? mo, e.beginPath(), e.arc(r + i, n, i, 0, Math.PI * 2), e.fill(), r += t.bubbleSize + k;
|
|
615
|
+
} else if (t.colors.length > 0) {
|
|
616
|
+
const i = t.colors.slice(0, We);
|
|
617
|
+
for (let a = 0; a < i.length; a++) {
|
|
618
|
+
const d = i[a];
|
|
619
|
+
d && (e.fillStyle = d, e.fillRect(
|
|
620
|
+
r + a * se,
|
|
621
|
+
n - W / 2,
|
|
622
|
+
U,
|
|
623
|
+
W
|
|
624
|
+
));
|
|
625
|
+
}
|
|
626
|
+
r += U + (i.length - 1) * se + k;
|
|
627
|
+
}
|
|
628
|
+
return e.fillStyle = s.textPrimary, e.textAlign = "left", e.fillText(t.label, r, n), r + e.measureText(t.label).width;
|
|
629
|
+
}, Se = (e, t) => Math.min(Math.max(...e), t), bn = (e) => (t) => {
|
|
630
|
+
if (!t.isVisible || t.ticks.length === 0) return 0;
|
|
631
|
+
const o = t.position === "top" || t.position === "bottom", n = t.ticks.map(
|
|
632
|
+
(i) => e.measureText(i.formattedLabel, {
|
|
633
|
+
family: le,
|
|
634
|
+
size: de
|
|
635
|
+
})
|
|
636
|
+
), s = t.labelMaxWidthPx ?? Number.POSITIVE_INFINITY;
|
|
637
|
+
if (o) {
|
|
638
|
+
if (t.labelRotation !== 0) {
|
|
639
|
+
const i = n.map((a) => a.width);
|
|
640
|
+
return Se(i, s) + 2 * q;
|
|
641
|
+
}
|
|
642
|
+
return Math.max(...n.map((i) => i.height)) + q;
|
|
643
|
+
}
|
|
644
|
+
const r = n.map((i) => i.width);
|
|
645
|
+
return Se(r, s) + q;
|
|
646
|
+
}, pn = (e) => (t) => {
|
|
647
|
+
if (t.items.length === 0) return 0;
|
|
648
|
+
const o = { family: Me, size: G };
|
|
649
|
+
if (t.position === "top" || t.position === "bottom") {
|
|
650
|
+
const r = G * ce;
|
|
651
|
+
return Math.max(r, W) + Ue * 2;
|
|
652
|
+
}
|
|
653
|
+
const s = t.items.map((r) => {
|
|
654
|
+
const i = e.measureText(r.formattedLabel, o).width;
|
|
655
|
+
return U + k + i;
|
|
656
|
+
});
|
|
657
|
+
return Math.max(...s) + Ge;
|
|
658
|
+
}, yn = (e) => (t, o) => {
|
|
659
|
+
const { labelLineHeight: n, labelFontWeight: s } = It(o, 1);
|
|
660
|
+
return e.measureText(t, {
|
|
661
|
+
family: T,
|
|
662
|
+
size: n,
|
|
663
|
+
weight: s
|
|
664
|
+
});
|
|
665
|
+
}, Tn = (e) => (t, o) => {
|
|
666
|
+
const { fontSize: n, fontWeight: s } = wt(t, 1);
|
|
667
|
+
return e.measureText(o, {
|
|
668
|
+
family: T,
|
|
669
|
+
size: n,
|
|
670
|
+
weight: s
|
|
671
|
+
});
|
|
672
|
+
}, En = (e) => ({
|
|
673
|
+
measureAxis: bn(e),
|
|
674
|
+
measureAxisLabel: () => uo,
|
|
675
|
+
measureLegend: pn(e),
|
|
676
|
+
measureTickLabel: (t) => e.measureText(t, { family: le, size: de }),
|
|
677
|
+
measureDifferenceArrowLabel: yn(e),
|
|
678
|
+
measureDataLabel: Tn(e),
|
|
679
|
+
measureHeadline: () => ({ width: 0, height: 0 }),
|
|
680
|
+
measureHeadlineItemWidths: () => []
|
|
681
|
+
});
|
|
682
|
+
class Sn {
|
|
683
|
+
constructor() {
|
|
684
|
+
this.currentFont = "";
|
|
685
|
+
const t = V().createCanvas(1, 1);
|
|
686
|
+
this.ctx = t.getContext("2d");
|
|
687
|
+
}
|
|
688
|
+
measureText(t, o) {
|
|
689
|
+
const n = S(o);
|
|
690
|
+
this.currentFont !== n && (this.ctx.font = n, this.currentFont = n);
|
|
691
|
+
const s = this.ctx.measureText(t);
|
|
692
|
+
return {
|
|
693
|
+
width: s.width,
|
|
694
|
+
height: s.fontBoundingBoxAscent + s.fontBoundingBoxDescent,
|
|
695
|
+
ascent: s.fontBoundingBoxAscent,
|
|
696
|
+
descent: s.fontBoundingBoxDescent
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
const Ln = {
|
|
701
|
+
background: "#ffffff",
|
|
702
|
+
textPrimary: "#111827",
|
|
703
|
+
textSecondary: "#6b7280",
|
|
704
|
+
gridLine: "#e5e7eb",
|
|
705
|
+
panelBorder: "#d1d5db",
|
|
706
|
+
legendPillBackground: "rgba(0, 0, 0, 0.04)",
|
|
707
|
+
legendPillBorder: "rgba(0, 0, 0, 0.08)"
|
|
708
|
+
}, Rn = {
|
|
709
|
+
background: "#0b0f17",
|
|
710
|
+
textPrimary: "#e5e7eb",
|
|
711
|
+
textSecondary: "#9ca3af",
|
|
712
|
+
gridLine: "#1f2937",
|
|
713
|
+
panelBorder: "#374151",
|
|
714
|
+
legendPillBackground: "rgba(255, 255, 255, 0.06)",
|
|
715
|
+
legendPillBorder: "rgba(255, 255, 255, 0.12)"
|
|
716
|
+
}, On = async ({ input: e, data: t }, o) => {
|
|
717
|
+
const s = Oe().compile({ input: e, data: t });
|
|
718
|
+
if (!s.ok)
|
|
719
|
+
throw new Error(
|
|
720
|
+
`Failed to compile graph spec: ${s.errors.map((r) => r.message).join("; ")}`
|
|
721
|
+
);
|
|
722
|
+
return et(s.compiled, o);
|
|
723
|
+
}, et = async (e, t) => {
|
|
724
|
+
const { Canvas: o } = await vt();
|
|
725
|
+
if (Mt(), t.fonts)
|
|
726
|
+
for (const b of t.fonts) Ne(b);
|
|
727
|
+
const n = Bt({
|
|
728
|
+
legends: e.guides.legends,
|
|
729
|
+
numberFormat: e.config.numberFormat,
|
|
730
|
+
parsingLocale: e.config.parsingLocale,
|
|
731
|
+
formattingLocale: t.formattingLocale
|
|
732
|
+
}), s = { width: t.width, height: t.height }, { headerHeight: r, footerHeight: i } = sn(e.config.content, s), a = {
|
|
733
|
+
headerSize: { width: t.width, height: r },
|
|
734
|
+
footerSize: { width: t.width, height: i }
|
|
735
|
+
}, d = new Sn(), { layout: l, formattedAxes: c } = new Nt(En(d)).compile({
|
|
736
|
+
axes: e.guides.axes,
|
|
737
|
+
numberFormat: e.config.numberFormat,
|
|
738
|
+
parsingLocale: e.config.parsingLocale,
|
|
739
|
+
formattedLegends: n,
|
|
740
|
+
containerSize: { width: t.width, height: t.height },
|
|
741
|
+
externalMeasurements: a,
|
|
742
|
+
formattingLocale: t.formattingLocale,
|
|
743
|
+
layout: e.config.layout
|
|
744
|
+
}), f = t.pixelRatio ?? 2, u = new o(t.width * f, t.height * f), h = u.getContext("2d");
|
|
745
|
+
h.scale(f, f);
|
|
746
|
+
const g = t.colorScheme ?? "light";
|
|
747
|
+
return _n(
|
|
748
|
+
h,
|
|
749
|
+
g === "dark" ? Rn : Ln,
|
|
750
|
+
g,
|
|
751
|
+
t.width,
|
|
752
|
+
t.height,
|
|
753
|
+
e,
|
|
754
|
+
c,
|
|
755
|
+
n,
|
|
756
|
+
l,
|
|
757
|
+
d,
|
|
758
|
+
t.formattingLocale
|
|
759
|
+
), u.encode("png");
|
|
760
|
+
}, _n = (e, t, o, n, s, r, i, a, d, l, c) => {
|
|
761
|
+
e.fillStyle = t.background, e.fillRect(0, 0, n, s), dn(e, i, r.guides.panel, d.panel, t), e.save(), e.beginPath(), e.rect(d.panel.x, d.panel.y, d.panel.width, d.panel.height), e.clip();
|
|
762
|
+
const f = {
|
|
763
|
+
coordSystem: r.coordSystem,
|
|
764
|
+
panel: d.panel,
|
|
765
|
+
compiled: r,
|
|
766
|
+
theme: t,
|
|
767
|
+
colorScheme: o,
|
|
768
|
+
textMeasurer: l,
|
|
769
|
+
formattingLocale: c
|
|
770
|
+
};
|
|
771
|
+
for (const u of r.layers) {
|
|
772
|
+
const h = Fe(u, r.coordSystem);
|
|
773
|
+
h && ro(h, e, { ...f, layer: u });
|
|
774
|
+
}
|
|
775
|
+
e.restore();
|
|
776
|
+
for (const u of i) {
|
|
777
|
+
const h = d.axes[u.position];
|
|
778
|
+
h && Wo(e, u, h, d.panel, t);
|
|
779
|
+
}
|
|
780
|
+
for (const u of r.guides.axes) {
|
|
781
|
+
const h = d.axisLabels[u.position];
|
|
782
|
+
h && Yo(e, u, h, t);
|
|
783
|
+
}
|
|
784
|
+
fn(e, a, d.legends, t), an(e, r.config.content, d.header, t, { width: n, height: s }), ln(e, r.config.content, d.footer, t, { width: n, height: s });
|
|
785
|
+
};
|
|
786
|
+
class wn extends Error {
|
|
787
|
+
constructor(t) {
|
|
788
|
+
super(t), this.name = "ChartConfigRenderError";
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
const Hn = async (e, t) => {
|
|
792
|
+
const o = fo(e);
|
|
793
|
+
if (!o.ok)
|
|
794
|
+
throw new wn(o.reason);
|
|
795
|
+
return et(o.compiled, t);
|
|
796
|
+
};
|
|
797
|
+
export {
|
|
798
|
+
Fn as CANVAS_CAPABILITY_MATRIX,
|
|
799
|
+
He as CANVAS_SUPPORTED_GRAPH_TYPES,
|
|
800
|
+
so as CANVAS_UNSUPPORTED_GRAPH_TYPES,
|
|
801
|
+
wn as ChartConfigRenderError,
|
|
802
|
+
Sn as NodeCanvasTextMeasurer,
|
|
803
|
+
fo as canRenderConfigWithCanvas,
|
|
804
|
+
Rn as darkTheme,
|
|
805
|
+
Mt as ensureDefaultFonts,
|
|
806
|
+
Ln as lightTheme,
|
|
807
|
+
vt as loadCanvasRuntime,
|
|
808
|
+
co as parseChartConfig,
|
|
809
|
+
Ne as registerFont,
|
|
810
|
+
Hn as renderChartConfigToPng,
|
|
811
|
+
On as renderGraphToPng
|
|
812
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@graphysdk/node-renderer",
|
|
3
|
+
"author": "Graphy",
|
|
4
|
+
"description": "Headless PNG renderer for Graphy charts (Node)",
|
|
5
|
+
"homepage": "https://docs.graphy.dev/sdk-next/rendering/provider-and-renderer",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"graphy",
|
|
9
|
+
"chart",
|
|
10
|
+
"png",
|
|
11
|
+
"node",
|
|
12
|
+
"headless",
|
|
13
|
+
"renderer"
|
|
14
|
+
],
|
|
15
|
+
"version": "1.8.1-beta.1786024899180",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": [
|
|
18
|
+
"**/version.ts"
|
|
19
|
+
],
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"assets",
|
|
27
|
+
"package.json",
|
|
28
|
+
"LICENSE.md",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.mjs",
|
|
35
|
+
"require": "./dist/index.cjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@fontsource/inter": "5.2.8",
|
|
40
|
+
"@napi-rs/canvas": "^0.1.76",
|
|
41
|
+
"d3-shape": "^3.2.0",
|
|
42
|
+
"@graphysdk/viz-engine": "1.8.1-beta.1786024899180"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@rollup/plugin-replace": "^6.0.3",
|
|
46
|
+
"@types/d3-shape": "^3.1.8",
|
|
47
|
+
"@types/node": "^24.1.0",
|
|
48
|
+
"rollup-plugin-node-externals": "^8.1.1",
|
|
49
|
+
"tslib": "^2.8.1",
|
|
50
|
+
"tsx": "^4.7.1",
|
|
51
|
+
"vite": "^6.4.3",
|
|
52
|
+
"vite-plugin-dts": "^4.5.4",
|
|
53
|
+
"vitest": "^4.1.0",
|
|
54
|
+
"@graphytools/eslint-plugin-graphy": "1.0.0",
|
|
55
|
+
"@graphytools/eslint-config": "0.0.1",
|
|
56
|
+
"@graphytools/typescript-config": "0.0.1",
|
|
57
|
+
"@graphytools/vitest-config": "1.0.0"
|
|
58
|
+
},
|
|
59
|
+
"lint-staged": {
|
|
60
|
+
"*.{ts,tsx,js,jsx,json,css,md}": [
|
|
61
|
+
"prettier --write"
|
|
62
|
+
],
|
|
63
|
+
"*.{ts,tsx}": [
|
|
64
|
+
"eslint --max-warnings=0 --fix"
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "vite build",
|
|
69
|
+
"dev": "tsx watch --conditions=local src/dev/server.ts",
|
|
70
|
+
"test": "vitest run",
|
|
71
|
+
"test:dist": "vitest run src/__tests__/build-output.spec.ts",
|
|
72
|
+
"test:watch": "TZ=utc vitest",
|
|
73
|
+
"lint": "eslint . --max-warnings 0",
|
|
74
|
+
"lint:fix": "eslint . --fix",
|
|
75
|
+
"format": "prettier --write .",
|
|
76
|
+
"format:check": "prettier --check .",
|
|
77
|
+
"typecheck": "tsc --noEmit"
|
|
78
|
+
}
|
|
79
|
+
}
|