@aeriz/chart 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +64 -431
- package/dist/aeriz.cjs +2781 -2338
- package/dist/aeriz.esm.js +1859 -1459
- package/dist/aeriz.esm.min.js +278 -1
- package/dist/aeriz.js +2781 -2338
- package/dist/aeriz.min.cjs +282 -1
- package/dist/aeriz.min.js +282 -1
- package/dist/esm/base.js +84 -0
- package/dist/esm/cartesian.js +13 -0
- package/dist/esm/charts/area.js +9 -0
- package/dist/esm/charts/bar.js +4 -0
- package/dist/esm/charts/boxplot.js +4 -0
- package/dist/esm/charts/bubble.js +3 -0
- package/dist/esm/charts/composite.js +11 -0
- package/dist/esm/charts/funnel.js +3 -0
- package/dist/esm/charts/gauge.js +5 -0
- package/dist/esm/charts/grouped-bar.js +5 -0
- package/dist/esm/charts/hbar.js +6 -0
- package/dist/esm/charts/heatmap.js +7 -0
- package/dist/esm/charts/line.js +9 -0
- package/dist/esm/charts/multi-line.js +8 -0
- package/dist/esm/charts/pie.js +6 -0
- package/dist/esm/charts/polar.js +3 -0
- package/dist/esm/charts/radar.js +10 -0
- package/dist/esm/charts/realtime.js +14 -0
- package/dist/esm/charts/scatter.js +6 -0
- package/dist/esm/charts/sparkline.js +5 -0
- package/dist/esm/charts/stacked-bar.js +7 -0
- package/dist/esm/charts/treemap.js +10 -0
- package/dist/esm/charts/waterfall.js +5 -0
- package/dist/esm/engine.js +33 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/jquery.js +5 -0
- package/dist/esm/math.js +32 -0
- package/dist/esm/react.js +4 -0
- package/dist/esm/shallow.js +5 -0
- package/dist/esm/svelte.js +4 -0
- package/dist/esm/vue.js +4 -0
- package/dist/index.d.ts +323 -0
- package/dist/jquery.d.ts +21 -0
- package/dist/react.d.ts +59 -0
- package/dist/svelte.d.ts +56 -0
- package/dist/types/area.d.ts +1 -0
- package/dist/types/bar.d.ts +1 -0
- package/dist/types/boxplot.d.ts +1 -0
- package/dist/types/bubble.d.ts +1 -0
- package/dist/types/composite.d.ts +1 -0
- package/dist/types/funnel.d.ts +1 -0
- package/dist/types/gauge.d.ts +1 -0
- package/dist/types/grouped-bar.d.ts +1 -0
- package/dist/types/hbar.d.ts +1 -0
- package/dist/types/heatmap.d.ts +1 -0
- package/dist/types/line.d.ts +1 -0
- package/dist/types/multi-line.d.ts +1 -0
- package/dist/types/pie.d.ts +1 -0
- package/dist/types/polar.d.ts +1 -0
- package/dist/types/radar.d.ts +1 -0
- package/dist/types/realtime.d.ts +1 -0
- package/dist/types/scatter.d.ts +1 -0
- package/dist/types/sparkline.d.ts +1 -0
- package/dist/types/stacked-bar.d.ts +1 -0
- package/dist/types/treemap.d.ts +1 -0
- package/dist/types/waterfall.d.ts +1 -0
- package/dist/vue.d.ts +63 -0
- package/package.json +60 -29
- package/src/base.js +0 -562
- package/src/charts.js +0 -1365
- package/src/composite.js +0 -187
- package/src/engine.js +0 -287
- package/src/index.d.ts +0 -517
- package/src/index.js +0 -18
- package/src/math.js +0 -178
package/dist/aeriz.min.js
CHANGED
|
@@ -1 +1,282 @@
|
|
|
1
|
-
var Aeriz=(function(){"use strict";const M=(function(){const TAU=Math.PI*2;const clamp=(v,lo,hi)=>v<lo?lo:v>hi?hi:v;const lerp=(a,b,t)=>a+(b-a)*t;function extent(arr,fn=(x)=>x){let mn=Infinity,mx=-Infinity;for(let i=0;i<arr.length;i++){const v=fn(arr[i],i);if(v<mn)mn=v;if(v>mx)mx=v;}return[mn===Infinity?0:mn,mx===-Infinity?1:mx];}function niceTicks(min,max,count=5){if(min===max){min-=0.5;max+=0.5;}const span=max-min;const step0=span/Math.max(1,count);const mag=Math.pow(10,Math.floor(Math.log10(step0)));const norm=step0/mag;let step;if(norm<1.5)step=mag;else if(norm<3)step=2*mag;else if(norm<7)step=5*mag;else step=10*mag;const lo=Math.ceil(min/step)*step;const hi=Math.floor(max/step)*step;const ticks=[];for(let v=lo;v<=hi+step/2;v+=step){ticks.push(Math.round(v/step)*step);}return ticks;}function niceDomain(min,max,count=5){if(min===max){min-=0.5;max+=0.5;}const span=max-min;const step0=span/Math.max(1,count);const mag=Math.pow(10,Math.floor(Math.log10(step0)));const norm=step0/mag;let step;if(norm<1.5)step=mag;else if(norm<3)step=2*mag;else if(norm<7)step=5*mag;else step=10*mag;return[Math.floor(min/step)*step,Math.ceil(max/step)*step];}const T={sec:1e3,min:6e4,hour:36e5,day:864e5,week:6.048e8,month:2.628e9};function timeNiceTicks(minMs,maxMs,count=6){if(minMs===maxMs){minMs-=1000;maxMs+=1000;}const span=maxMs-minMs;const target=span/count;const units=[[T.sec,1],[T.sec,5],[T.sec,15],[T.sec,30],[T.min,1],[T.min,5],[T.min,15],[T.min,30],[T.hour,1],[T.hour,3],[T.hour,6],[T.hour,12],[T.day,1],[T.day,2],[T.week,1],[T.month,1],[T.month,3],[T.month,6],[T.month,12],];let step=T.sec;for(const[u,n]of units)if(u*n<=target*1.5)step=u*n;const lo=Math.ceil(minMs/step)*step;const ticks=[];for(let v=lo;v<=maxMs;v+=step)ticks.push(v);return ticks;}function timeFormat(step){if(step<T.min)return(t)=>new Date(t).toISOString().slice(11,19);if(step<T.hour)return(t)=>new Date(t).toISOString().slice(11,16);if(step<T.day)return(t)=>new Date(t).toISOString().slice(11,16);if(step<T.month)return(t)=>new Date(t).toISOString().slice(5,10);return(t)=>new Date(t).toISOString().slice(0,7);}function minMaxDecimate(data,targetCount,fn=(d)=>d.value){if(data.length<=targetCount)return data;const bucket=data.length/targetCount;const out=[];for(let i=0;i<targetCount;i++){const lo=Math.floor(i*bucket);const hi=Math.min(data.length,Math.floor((i+1)*bucket));let mnI=lo,mxI=lo;let mn=fn(data[lo],lo),mx=mn;for(let j=lo+1;j<hi;j++){const v=fn(data[j],j);if(v<mn){mn=v;mnI=j;}if(v>mx){mx=v;mxI=j;}}if(mnI<mxI){out.push(data[mnI]);out.push(data[mxI]);}else{out.push(data[mxI]);out.push(data[mnI]);}}return out;}function shouldDecimate(n,pxWidth){return n>pxWidth*2;}function bezierControlPoints(pts,tension=1/6){const n=pts.length;if(n<2)return[];const cps=[];for(let i=0;i<n-1;i++){const p0=pts[i-1]||pts[i];const p1=pts[i];const p2=pts[i+1];const p3=pts[i+2]||p2;cps.push({c1x:p1.x+(p2.x-p0.x)*tension,c1y:p1.y+(p2.y-p0.y)*tension,c2x:p2.x-(p3.x-p1.x)*tension,c2y:p2.y-(p3.y-p1.y)*tension,});}return cps;}function springStep(v,tgt,vel,cfg={}){const stiffness=cfg.stiffness||170;const damping=cfg.damping||26;const dt=1/60;const F=-stiffness*(v-tgt);const D=-damping*vel;const a=F+D;vel=vel+a*dt;v=v+vel*dt;const done=Math.abs(vel)<0.05&&Math.abs(v-tgt)<0.05;if(done){v=tgt;vel=0;}return{value:v,velocity:vel,done};}function colorParse(c){if(!c)return[0,0,0,1];if(c[0]==='#'){const h=c.slice(1);if(h.length===3)return[parseInt(h[0]+h[0],16),parseInt(h[1]+h[1],16),parseInt(h[2]+h[2],16),1];if(h.length===6)return[parseInt(h.slice(0,2),16),parseInt(h.slice(2,4),16),parseInt(h.slice(4,6),16),1];if(h.length===8)return[parseInt(h.slice(0,2),16),parseInt(h.slice(2,4),16),parseInt(h.slice(4,6),16),parseInt(h.slice(6,8),16)/255];}const m=c.match(/rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)(?:[,\s/]+([\d.]+))?\s*\)/);if(m)return[+m[1],+m[2],+m[3],m[4]!==undefined?+m[4]:1];return[0,0,0,1];}function colorAlpha(c,a){const[r,g,b]=colorParse(c);return`rgba(${r|0},${g|0},${b|0},${a})`;}function colorMix(a,b,t){const[r1,g1,b1,a1]=colorParse(a);const[r2,g2,b2,a2]=colorParse(b);return`rgba(${(r1+(r2-r1)*t)|0},${(g1+(g2-g1)*t)|0},${(b1+(b2-b1)*t)|0},${a1+(a2-a1)*t})`;}const PALETTE=['#64c8ff','#10b981','#f59e0b','#ef4444','#8b5cf6','#ec4899','#84cc16','#6366f1',];return{TAU,clamp,lerp,extent,niceTicks,niceDomain,timeNiceTicks,timeFormat,minMaxDecimate,shouldDecimate,bezierControlPoints,springStep,colorParse,colorAlpha,colorMix,PALETTE};})();const TAU=M.TAU;const PALETTE=M.PALETTE;class Engine{constructor(canvas){this.canvas=canvas;this.ctx=canvas.getContext('2d');this.w=0;this.h=0;this.dpr=1;this._gradCache=new Map();this._gradCacheMax=32;this._snap=true;}resize(w,h){const dpr=(typeof window!=='undefined'&&window.devicePixelRatio)||1;this.canvas.width=Math.round(w*dpr);this.canvas.height=Math.round(h*dpr);this.canvas.style.width=w+'px';this.canvas.style.height=h+'px';this.ctx.setTransform(dpr,0,0,dpr,0,0);this.w=w;this.h=h;this.dpr=dpr;this._gradCache.clear();}clear(color){if(color){this.ctx.fillStyle=color;this.ctx.fillRect(0,0,this.w,this.h);}else{this.ctx.clearRect(0,0,this.w,this.h);}}rect(x,y,w,h,opts={}){if(!(w>0)||!(h>0))return;const ctx=this.ctx;if(opts.radius&&opts.radius>0){const r=Math.max(0,Math.min(opts.radius,w/2,h/2));ctx.beginPath();ctx.moveTo(x+r,y);ctx.arcTo(x+w,y,x+w,y+h,r);ctx.arcTo(x+w,y+h,x,y+h,r);ctx.arcTo(x,y+h,x,y,r);ctx.arcTo(x,y,x+w,y,r);ctx.closePath();}else{ctx.beginPath();ctx.rect(x,y,w,h);}if(opts.fill){ctx.fillStyle=opts.fill;ctx.fill();}if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1;ctx.stroke();}}rects(items,fill){const ctx=this.ctx;ctx.fillStyle=fill;ctx.beginPath();for(const r of items){if(!(r.w>0)||!(r.h>0))continue;if(r.radius&&r.radius>0){const rad=Math.max(0,Math.min(r.radius,r.w/2,r.h/2));ctx.moveTo(r.x+rad,r.y);ctx.arcTo(r.x+r.w,r.y,r.x+r.w,r.y+r.h,rad);ctx.arcTo(r.x+r.w,r.y+r.h,r.x,r.y+r.h,rad);ctx.arcTo(r.x,r.y+r.h,r.x,r.y,rad);ctx.arcTo(r.x,r.y,r.x+r.w,r.y,rad);ctx.closePath();}else{ctx.rect(r.x,r.y,r.w,r.h);}}ctx.fill();}circle(x,y,r,opts={}){if(!(r>0))return;const ctx=this.ctx;ctx.beginPath();ctx.arc(x,y,r,0,TAU);if(opts.fill){ctx.fillStyle=opts.fill;ctx.fill();}if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1;ctx.stroke();}}circles(items,fill,fixedRadius){const ctx=this.ctx;ctx.fillStyle=fill;ctx.beginPath();for(const c of items){const r=fixedRadius!=null?fixedRadius:c.r;if(!(r>0))continue;ctx.moveTo(c.x+r,c.y);ctx.arc(c.x,c.y,r,0,TAU);}ctx.fill();}poly(points,opts={}){if(points.length<2)return;const ctx=this.ctx;const snap=this._snap&&opts.lineWidth===1;const adj=snap?0.5:0;ctx.beginPath();ctx.moveTo(points[0].x+adj,points[0].y+adj);for(let i=1;i<points.length;i++){ctx.lineTo(points[i].x+adj,points[i].y+adj);}if(opts.close)ctx.closePath();if(opts.fill){ctx.fillStyle=opts.fill;ctx.fill();}if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1.5;ctx.lineCap=opts.cap||'butt';ctx.lineJoin=opts.join||'miter';if(opts.dash)ctx.setLineDash(opts.dash);ctx.stroke();if(opts.dash)ctx.setLineDash([]);}}smooth(points,cps,opts={}){if(points.length<2)return;const ctx=this.ctx;ctx.beginPath();ctx.moveTo(points[0].x,points[0].y);for(let i=0;i<cps.length;i++){const c=cps[i];const p=points[i+1];ctx.bezierCurveTo(c.c1x,c.c1y,c.c2x,c.c2y,p.x,p.y);}if(opts.fill){ctx.fillStyle=opts.fill;ctx.fill();}if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||2;ctx.lineCap=opts.cap||'round';ctx.lineJoin=opts.join||'round';ctx.stroke();}}smoothArea(points,cps,y0,fill){if(points.length<2)return;const ctx=this.ctx;ctx.beginPath();ctx.moveTo(points[0].x,y0);ctx.lineTo(points[0].x,points[0].y);for(let i=0;i<cps.length;i++){const c=cps[i];const p=points[i+1];ctx.bezierCurveTo(c.c1x,c.c1y,c.c2x,c.c2y,p.x,p.y);}ctx.lineTo(points[points.length-1].x,y0);ctx.closePath();ctx.fillStyle=fill;ctx.fill();}step(points,mode,opts={}){if(points.length<2)return;const ctx=this.ctx;ctx.beginPath();ctx.moveTo(points[0].x,points[0].y);for(let i=1;i<points.length;i++){const a=points[i-1],b=points[i];if(mode==='after'){ctx.lineTo(b.x,a.y);ctx.lineTo(b.x,b.y);}else if(mode==='middle'){const mx=(a.x+b.x)/2;ctx.lineTo(mx,a.y);ctx.lineTo(mx,b.y);ctx.lineTo(b.x,b.y);}else{ctx.lineTo(a.x,b.y);ctx.lineTo(b.x,b.y);}}if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1.5;ctx.stroke();}}text(str,x,y,opts={}){const ctx=this.ctx;ctx.font=opts.font||'11px ui-monospace, monospace';ctx.fillStyle=opts.fill||'#000';ctx.textAlign=opts.align||'left';ctx.textBaseline=opts.baseline||'alphabetic';if(opts.rotate){ctx.save();ctx.translate(x,y);ctx.rotate(opts.rotate);ctx.fillText(str,0,0);ctx.restore();}else{ctx.fillText(str,x,y);}}measure(str,font){this.ctx.font=font||'11px ui-monospace, monospace';return this.ctx.measureText(str).width;}arc(cx,cy,rIn,rOut,a0,a1,opts={}){const ri=Math.max(0,rIn);const ro=Math.max(0,rOut);if(ro<=0)return;const ctx=this.ctx;ctx.beginPath();ctx.arc(cx,cy,ro,a0,a1);ctx.arc(cx,cy,ri,a1,a0,true);ctx.closePath();if(opts.fill){ctx.fillStyle=opts.fill;ctx.fill();}if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1;ctx.stroke();}}vGradient(key,x0,y0,y1,stops){if(this._gradCache.has(key))return this._gradCache.get(key);const g=this.ctx.createLinearGradient(x0,y0,x0,y1);for(const[pos,color]of stops)g.addColorStop(pos,color);if(this._gradCache.size>=this._gradCacheMax){const first=this._gradCache.keys().next().value;this._gradCache.delete(first);}this._gradCache.set(key,g);return g;}clip(x,y,w,h){const ctx=this.ctx;ctx.save();ctx.beginPath();ctx.rect(x,y,w,h);ctx.clip();}unclip(){this.ctx.restore();}withAlpha(a,fn){const ctx=this.ctx;const prev=ctx.globalAlpha;ctx.globalAlpha=prev*a;fn();ctx.globalAlpha=prev;}}const charts=new Set();let rafId=null;let lastTime=0;let fps=60;let frames=0;let fpsT=0;function tick(now){if(now-fpsT>500){fps=Math.round((1000*frames)/(now-fpsT));frames=0;fpsT=now;}frames++;let active=false;for(const c of charts){try{const stillActive=c._tick(now);if(stillActive)active=true;}catch(e){console.error(e);}}rafId=(charts.size>0&&active)?requestAnimationFrame(tick):null;}function schedule(chart){charts.add(chart);if(rafId===null){fpsT=lastTime=performance.now();rafId=requestAnimationFrame(tick);}}const scheduler={schedule,fps:()=>fps,count:()=>charts.size};function getFPS(){return fps;}function getActiveCount(){return charts.size;}const TOKENS={bg:['--aeriz-bg','#ffffff'],surface:['--aeriz-surface','transparent'],text:['--aeriz-text','#222'],muted:['--aeriz-muted','#888'],grid:['--aeriz-grid','rgba(0,0,0,0.08)'],axis:['--aeriz-axis','rgba(0,0,0,0.3)'],accent:['--aeriz-accent','#64c8ff'],font:['--aeriz-font','ui-monospace, SFMono-Regular, Menlo, monospace'],fontSize:['--aeriz-font-size','11','number'],lineWidth:['--aeriz-line-width','2','number'],pointRadius:['--aeriz-point-radius','3','number'],barRadius:['--aeriz-bar-radius','3','number'],tooltipBg:['--aeriz-tooltip-bg','rgba(20,20,28,0.95)'],tooltipFg:['--aeriz-tooltip-fg','#fff'],};function resolveTheme(el){const cs=getComputedStyle(el);const out={};for(const[k,[varName,fallback,kind]]of Object.entries(TOKENS)){const raw=(cs.getPropertyValue(varName)||'').trim();const v=raw||fallback;out[k]=kind==='number'?parseFloat(v):v;}return out;}function readPalette(el){const cs=getComputedStyle(el);const out=[];for(let i=1;i<=12;i++){const v=(cs.getPropertyValue(`--aeriz-color-${i}`)||'').trim();if(v)out.push(v);}return out.length?out:M.PALETTE;}class Tooltip{constructor(parent){this.parent=parent;this.el=document.createElement('div');Object.assign(this.el.style,{position:'absolute',pointerEvents:'none',padding:'6px 10px',borderRadius:'4px',fontSize:'11px',fontFamily:'inherit',lineHeight:'1.5',whiteSpace:'nowrap',opacity:'0',transform:'translate(-50%, calc(-100% - 8px))',transition:'opacity 0.1s',zIndex:'20',boxShadow:'0 4px 16px rgba(0,0,0,0.3)',});parent.appendChild(this.el);}show(x,y,content,theme){this.el.style.background=theme.tooltipBg;this.el.style.color=theme.tooltipFg;if(typeof content==='string'){this.el.textContent=content;}else{this.el.innerHTML='';if(content.title){const t=document.createElement('div');t.style.cssText='opacity:0.7;font-size:10px;margin-bottom:4px';t.textContent=content.title;this.el.appendChild(t);}for(const r of content.rows){const row=document.createElement('div');row.style.cssText='display:flex;align-items:center;gap:6px;line-height:1.4';if(r.color){const sw=document.createElement('span');sw.style.cssText=`width:8px;height:8px;border-radius:2px;background:${r.color};display:inline-block`;row.appendChild(sw);}const nm=document.createElement('span');nm.style.cssText='opacity:0.8;flex:1';nm.textContent=r.name||'';row.appendChild(nm);const val=document.createElement('span');val.style.cssText='font-weight:700;margin-left:12px';val.textContent=r.value==null?'—':String(r.value);row.appendChild(val);this.el.appendChild(row);}}this.el.style.left=x+'px';this.el.style.top=y+'px';this.el.style.opacity='1';}hide(){this.el.style.opacity='0';}destroy(){this.el.remove();}}class Overlay{constructor(parent){this.parent=parent;this.el=document.createElement('div');Object.assign(this.el.style,{position:'absolute',inset:'0',display:'none',alignItems:'center',justifyContent:'center',flexDirection:'column',gap:'8px',fontSize:'12px',fontFamily:'inherit',pointerEvents:'none',zIndex:'15',});parent.appendChild(this.el);}show(kind,msg,theme){const el=this.el;el.style.display='flex';el.style.background=M.colorAlpha(theme.bg,0.85);el.style.color=theme.text;el.innerHTML='';const icon=document.createElement('div');icon.style.fontSize='20px';icon.style.opacity='0.6';if(kind==='loading'){icon.innerHTML=`<svg width="22" height="22" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9" fill="none" stroke="${theme.accent}" stroke-width="2.5" stroke-linecap="round" stroke-dasharray="14 50"><animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite"/></circle></svg>`;}else if(kind==='empty'){icon.textContent='◌';}else if(kind==='error'){icon.textContent='!';icon.style.color='#ef4444';}el.appendChild(icon);if(msg){const t=document.createElement('div');t.style.opacity='0.85';t.textContent=msg;el.appendChild(t);}}hide(){this.el.style.display='none';}destroy(){this.el.remove();}}class HitIndex{constructor(){this.regions=[];}clear(){this.regions.length=0;}add(r){this.regions.push(r);}find(x,y){for(let i=this.regions.length-1;i>=0;i--){const r=this.regions[i];if(x>=r.x&&x<=r.x+r.w&&y>=r.y&&y<=r.y+r.h)return r;}return null;}}class Chart{constructor(container,options={}){if(!container)throw new Error('Chart: container is required');this.container=typeof container==='string'?document.querySelector(container):container;this.options=options;this.container.classList.add('aeriz');if(getComputedStyle(this.container).position==='static'){this.container.style.position='relative';}if(!this.container.style.height&&this.container.offsetHeight===0){this.container.style.height='320px';}this.canvas=document.createElement('canvas');this.canvas.style.cssText='display:block;width:100%;height:100%;touch-action:none';this.container.appendChild(this.canvas);this.engine=new Engine(this.canvas);this.tooltip=new Tooltip(this.container);this.overlay=new Overlay(this.container);this.hits=new HitIndex();this.theme=resolveTheme(this.container);this.palette=options.palette||readPalette(this.container);this._cursor=null;this._hover=null;this._dirty=true;this._destroyed=false;this._mounted=false;this._anim={progress:0,start:0,duration:600};this._state=null;this._setupEvents();this._observeResize();this._resize();schedule(this);}invalidate(){this.theme=resolveTheme(this.container);this.palette=this.options.palette||readPalette(this.container);this.engine._gradCache.clear();this._dirty=true;schedule(this);}setData(data){this.options.data=data;if(this._state==='empty')this._state=null;this._anim.start=performance.now();this._anim.progress=0;this._dirty=true;schedule(this);return this;}setLoading(msg){this._state='loading';this._stateMsg=msg||'';this._dirty=true;schedule(this);return this;}setEmpty(msg){this._state='empty';this._stateMsg=msg||'No data';this._dirty=true;schedule(this);return this;}setError(msg){this._state='error';this._stateMsg=msg||'Error';this._dirty=true;schedule(this);return this;}clearState(){this._state=null;this._dirty=true;schedule(this);return this;}destroy(){this._destroyed=true;charts.delete(this);this._ro&&this._ro.disconnect();this._removeEvents();this.tooltip.destroy();this.overlay.destroy();this.canvas.remove();this.container.classList.remove('aeriz');}_resize(){const w=this.container.clientWidth;const h=this.container.clientHeight;if(w===this.engine.w&&h===this.engine.h)return;this.engine.resize(Math.max(50,w),Math.max(50,h));this.theme=resolveTheme(this.container);this._dirty=true;}_observeResize(){if(typeof ResizeObserver==='undefined')return;this._ro=new ResizeObserver(()=>{this._resize();schedule(this);});this._ro.observe(this.container);}_setupEvents(){const c=this.canvas;this._handlers={move:(e)=>this._onMove(e),leave:()=>this._onLeave(),click:(e)=>this._onClick(e),};c.addEventListener('mousemove',this._handlers.move);c.addEventListener('mouseleave',this._handlers.leave);c.addEventListener('click',this._handlers.click);}_removeEvents(){const c=this.canvas;c.removeEventListener('mousemove',this._handlers.move);c.removeEventListener('mouseleave',this._handlers.leave);c.removeEventListener('click',this._handlers.click);}_ptr(e){const r=this.canvas.getBoundingClientRect();return{x:e.clientX-r.left,y:e.clientY-r.top};}_onMove(e){const p=this._ptr(e);this._cursor=p;const hit=this.hits.find(p.x,p.y);const changed=(hit?._id??null)!==(this._hover?._id??null);this._hover=hit;if(changed){if(hit&&hit.tooltip){this.tooltip.show(hit.tipX!=null?hit.tipX:hit.x+hit.w/2,hit.tipY!=null?hit.tipY:hit.y,hit.tooltip,this.theme,);}else if(!hit&&!this._sharedTooltip){this.tooltip.hide();}this._dirty=true;schedule(this);}if(typeof this._onMoveX==='function')this._onMoveX(p);}_onLeave(){this._cursor=null;this._hover=null;this.tooltip.hide();this._dirty=true;schedule(this);}_onClick(e){const p=this._ptr(e);const hit=this.hits.find(p.x,p.y);if(this.options.onClick)this.options.onClick(hit,p,this);}_tick(now){if(this._destroyed)return false;let active=false;if(this._anim.progress<1){if(!this._anim.start)this._anim.start=now;this._anim.progress=Math.min(1,(now-this._anim.start)/this._anim.duration);this._dirty=true;if(this._anim.progress<1)active=true;}if(this._dirty){this._draw();this._dirty=false;}return active;}_draw(){const e=this.engine;e.clear();if(this.theme.surface&&this.theme.surface!=='transparent'){e.rect(0,0,e.w,e.h,{fill:this.theme.surface});}if(this._state){this.overlay.show(this._state,this._stateMsg,this.theme);this.hits.clear();return;}else{this.overlay.hide();}this.hits.clear();if(typeof this._render==='function')this._render();}_axisPad(opts={}){const t=this.theme;return{l:opts.l!=null?opts.l:50,r:opts.r!=null?opts.r:16,t:opts.t!=null?opts.t:16,b:opts.b!=null?opts.b:32,};}_drawCartesian(spec){const e=this.engine;const t=this.theme;const pad=this._axisPad(spec.pad);const px=pad.l,py=pad.t;const pw=e.w-pad.l-pad.r;const ph=e.h-pad.t-pad.b;const yMin=spec.yMin!=null?spec.yMin:0;const yMax=spec.yMax;const yScale=spec.yScale||'linear';const font=`${t.fontSize}px ${t.font}`;let yTicks,yToPx;if(yScale==='log'){const lo=Math.max(1e-10,spec.yMin||1);const hi=yMax;const lLo=Math.log10(lo),lHi=Math.log10(hi);yToPx=v=>py+ph*(1-(Math.log10(Math.max(lo,v))-lLo)/(lHi-lLo));yTicks=[];for(let p=Math.floor(lLo);p<=Math.ceil(lHi);p++){const v=Math.pow(10,p);if(v>=lo&&v<=hi)yTicks.push(v);}}else{yTicks=M.niceTicks(yMin,yMax,5);yToPx=v=>py+ph*(1-(v-yMin)/(yMax-yMin));}e.ctx.strokeStyle=t.grid;e.ctx.lineWidth=1;e.ctx.beginPath();for(const v of yTicks){const y=Math.round(yToPx(v))+0.5;e.ctx.moveTo(px,y);e.ctx.lineTo(px+pw,y);}e.ctx.stroke();const yFmt=spec.yTickFormat||(v=>yScale==='log'&&v>=1000?v.toExponential(0):String(Math.round(v*100)/100));for(const v of yTicks){e.text(yFmt(v),px-6,yToPx(v),{fill:t.muted,font,align:'right',baseline:'middle',});}let xToPx,xTicks,xFmt;if(spec.xTime){const{min,max}=spec.xTime;xToPx=v=>px+pw*(v-min)/(max-min||1);xTicks=M.timeNiceTicks(min,max,6);const step=xTicks.length>1?xTicks[1]-xTicks[0]:(max-min);xFmt=spec.xTickFormat||M.timeFormat(step);}else if(spec.xLinear){const{min,max}=spec.xLinear;xToPx=v=>px+pw*(v-min)/(max-min||1);xTicks=M.niceTicks(min,max,6);xFmt=spec.xTickFormat||(v=>String(v));}else if(spec.xLabels){const n=spec.xLabels.length;xToPx=i=>px+(i+0.5)*(pw/n);xTicks=spec.xLabels.map((_,i)=>i);xFmt=spec.xTickFormat||(i=>String(spec.xLabels[i]));}if(xToPx){let lastEnd=-Infinity;for(const v of xTicks){const x=xToPx(v);const txt=xFmt(v);const w=e.measure(txt,font);if(x-w/2<lastEnd+4)continue;lastEnd=x+w/2;e.text(txt,x,py+ph+6,{fill:t.muted,font,align:'center',baseline:'top',});}}e.poly([{x:px,y:py+ph},{x:px+pw,y:py+ph}],{stroke:t.axis,lineWidth:1,});return{px,py,pw,ph,yToPx,xToPx,yTicks,xTicks,yMin,yMax};}_drawLegend(items,pw){const e=this.engine;const t=this.theme;const font=`${t.fontSize}px ${t.font}`;const swatch=10,gap=16,rowH=18;let x=0,y=0,maxW=pw;for(const it of items){const tw=e.measure(it.label,font);const w=swatch+6+tw;if(x+w>maxW&&x>0){x=0;y+=rowH;}e.rect(x,y+4,swatch,swatch,{fill:it.hidden?t.muted:it.color,radius:2});e.text(it.label,x+swatch+4,y+swatch/2+4,{fill:it.hidden?t.muted:t.text,font,align:'left',baseline:'middle',});this.hits.add({_id:'legend-'+it.idx,x,y,w:swatch+4+tw,h:rowH,legendIdx:it.idx,});x+=w+gap;}return y+rowH+6;}_colorOf(idx,item){if(item&&item.color)return item.color;return this.palette[idx%this.palette.length];}}class BarChart extends Chart{_render(){const data=this.options.data||[];if(data.length===0){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;const labelOf=this.options.label||(d=>d.label);const valueOf=this.options.value||(d=>d.value);const values=data.map((d,i)=>valueOf(d,i));const yMin=Math.min(0,...values);const yMax=Math.max(...values);const[yLo,yHi]=M.niceDomain(yMin,yMax,5);const ax=this._drawCartesian({yMin:yLo,yMax:yHi,xLabels:data.map(labelOf),yTickFormat:this.options.yTickFormat,xTickFormat:this.options.xTickFormat,});const slot=ax.pw/data.length;const barW=Math.max(2,Math.min(60,slot-8));const prog=this._anim.progress;const radius=t.barRadius;const groups=new Map();const fmt=this.options.dataLabels;for(let i=0;i<data.length;i++){const v=values[i];const x=ax.px+(i+0.5)*slot-barW/2;const y0=ax.yToPx(0);const y1=ax.yToPx(v);const top=Math.min(y0,y1);const h=Math.abs(y1-y0)*prog;const animTop=v>=0?y0-h:y0;const color=this._colorOf(i,data[i]);if(!groups.has(color))groups.set(color,[]);groups.get(color).push({x,y:animTop,w:barW,h,radius});this.hits.add({_id:i,x,y:Math.min(animTop,animTop+h),w:barW,h:Math.abs(h),tipX:x+barW/2,tipY:animTop,tooltip:`${labelOf(data[i], i)}: ${v}`,});if(fmt&&prog>0.95){const txt=typeof fmt==='function'?fmt(data[i],i):String(v);e.text(txt,x+barW/2,animTop-4,{fill:t.text,font:`${t.fontSize}px ${t.font}`,align:'center',baseline:'bottom',});}}for(const[color,items]of groups)e.rects(items,color);if(this._hover){const r=this._hover;e.rect(r.x,r.y,r.w,r.h,{stroke:t.text,lineWidth:1.5});}}}class HBarChart extends Chart{_axisPad(){return{l:80,r:16,t:16,b:32};}_render(){const data=this.options.data||[];if(data.length===0){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;const labelOf=this.options.label||(d=>d.label);const valueOf=this.options.value||(d=>d.value);const values=data.map((d,i)=>valueOf(d,i));const xMin=Math.min(0,...values);const xMax=Math.max(...values);const[xLo,xHi]=M.niceDomain(xMin,xMax,5);const pad=this._axisPad();const px=pad.l,py=pad.t;const pw=e.w-pad.l-pad.r;const ph=e.h-pad.t-pad.b;const font=`${t.fontSize}px ${t.font}`;const xTicks=M.niceTicks(xLo,xHi,5);const xToPx=v=>px+pw*(v-xLo)/(xHi-xLo);e.ctx.strokeStyle=t.grid;e.ctx.lineWidth=1;e.ctx.beginPath();for(const v of xTicks){const x=Math.round(xToPx(v))+0.5;e.ctx.moveTo(x,py);e.ctx.lineTo(x,py+ph);}e.ctx.stroke();for(const v of xTicks){e.text(String(v),xToPx(v),py+ph+6,{fill:t.muted,font,align:'center',baseline:'top',});}e.poly([{x:px,y:py+ph},{x:px+pw,y:py+ph}],{stroke:t.axis,lineWidth:1});const slot=ph/data.length;const barH=Math.max(2,Math.min(40,slot-6));const prog=this._anim.progress;const groups=new Map();for(let i=0;i<data.length;i++){const v=values[i];const yMid=py+(i+0.5)*slot;const y=yMid-barH/2;const x0=xToPx(0);const x1=xToPx(v);const start=Math.min(x0,x1);const w=Math.abs(x1-x0)*prog;const animLeft=v>=0?x0:x0-w;const color=this._colorOf(i,data[i]);if(!groups.has(color))groups.set(color,[]);groups.get(color).push({x:animLeft,y,w,h:barH,radius:t.barRadius});this.hits.add({_id:i,x:animLeft,y,w,h:barH,tipX:animLeft+w/2,tipY:y,tooltip:`${labelOf(data[i], i)}: ${v}`,});e.text(labelOf(data[i],i),px-6,yMid,{fill:t.muted,font,align:'right',baseline:'middle',});}for(const[color,items]of groups)e.rects(items,color);if(this._hover){const r=this._hover;e.rect(r.x,r.y,r.w,r.h,{stroke:t.text,lineWidth:1.5});}}}class LineChart extends Chart{_render(){const data=this.options.data||[];if(data.length===0){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;const labelOf=this.options.label||(d=>d.label);const valueOf=this.options.value||(d=>d.value);const values=data.map((d,i)=>valueOf(d,i));const yMin=Math.min(0,...values);const yMax=Math.max(...values);const[yLo,yHi]=M.niceDomain(yMin,yMax,5);const useTime=this.options.xScale==='time';let spec={yMin:yLo,yMax:yHi,yScale:this.options.yScale,yTickFormat:this.options.yTickFormat,xTickFormat:this.options.xTickFormat,};if(useTime){const xs=data.map((d,i)=>+labelOf(d,i));spec.xTime={min:xs[0],max:xs[xs.length-1]};}else{spec.xLabels=data.map(labelOf);}const ax=this._drawCartesian(spec);const color=this.options.color||this.palette[0];const interp=this.options.interpolation||'smooth';const lineW=t.lineWidth;const prog=this._anim.progress;let renderData=data;if(M.shouldDecimate(data.length,ax.pw)){renderData=M.minMaxDecimate(data,Math.floor(ax.pw),valueOf);}const points=renderData.map((d,i)=>{const xv=useTime?+labelOf(d,i):i;const idx=useTime?null:i;const x=useTime?ax.xToPx(xv):ax.px+((idx+0.5)/renderData.length)*ax.pw;const y=ax.yToPx(valueOf(d,i));return{x,y,d,i};});e.clip(ax.px,ax.py,ax.pw*prog,ax.ph);if(interp==='smooth'||interp==='linear'){const cps=interp==='smooth'?M.bezierControlPoints(points):null;const grad=e.vGradient(`area-${color}`,ax.px,ax.py,ax.py+ax.ph,[[0,M.colorAlpha(color,0.25)],[1,M.colorAlpha(color,0)],]);if(cps)e.smoothArea(points,cps,ax.py+ax.ph,grad);else{const areaPts=points.concat([{x:points[points.length-1].x,y:ax.py+ax.ph},{x:points[0].x,y:ax.py+ax.ph},]);e.poly(areaPts,{fill:grad});}if(cps)e.smooth(points,cps,{stroke:color,lineWidth:lineW});else e.poly(points,{stroke:color,lineWidth:lineW,cap:'round',join:'round'});}else if(interp==='step'||interp.startsWith('step-')){const mode=interp==='step'?'before':interp.split('-')[1];e.step(points,mode,{stroke:color,lineWidth:lineW});}e.unclip();if(renderData===data&&data.length<=50){const pr=t.pointRadius;e.circles(points,color,pr);for(const p of points){this.hits.add({_id:p.i,x:p.x-8,y:p.y-8,w:16,h:16,tipX:p.x,tipY:p.y-pr,tooltip:`${labelOf(p.d, p.i)}: ${valueOf(p.d, p.i)}`,});}}else{const colW=ax.pw/renderData.length;for(let i=0;i<renderData.length;i++){const p=points[i];this.hits.add({_id:p.i,x:p.x-colW/2,y:ax.py,w:colW,h:ax.ph,tipX:p.x,tipY:p.y-8,tooltip:`${labelOf(p.d, p.i)}: ${valueOf(p.d, p.i)}`,});}}if(this._hover){const p=points.find(pp=>pp.i===this._hover._id);if(p)e.circle(p.x,p.y,t.pointRadius+3,{stroke:color,lineWidth:2,fill:t.bg,});}}}class AreaChart extends Chart{_render(){const series=this.options.series||[];const cats=this.options.categories||[];if(!series.length||!cats.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;const stacked=this.options.stacked===true;let yMax;if(stacked){yMax=0;for(let i=0;i<cats.length;i++){let sum=0;for(const s of series)sum+=+s.values[i]||0;if(sum>yMax)yMax=sum;}}else{yMax=-Infinity;for(const s of series)for(const v of s.values)if(+v>yMax)yMax=+v;}const[yLo,yHi]=M.niceDomain(0,yMax,5);const ax=this._drawCartesian({yMin:yLo,yMax:yHi,xLabels:cats,yTickFormat:this.options.yTickFormat,});const lineW=t.lineWidth;const prog=this._anim.progress;e.clip(ax.px,ax.py,ax.pw*prog,ax.ph);if(stacked){const cum=new Array(cats.length).fill(0);const seriesPts=[];for(let s=0;s<series.length;s++){const ser=series[s];const ptsTop=[],ptsBot=[];for(let i=0;i<cats.length;i++){const v=+ser.values[i]||0;const x=ax.px+((i+0.5)/cats.length)*ax.pw;const yBot=ax.yToPx(cum[i]);const yTop=ax.yToPx(cum[i]+v);ptsTop.push({x,y:yTop});ptsBot.push({x,y:yBot});cum[i]+=v;}const color=this._colorOf(s,ser);const cps=M.bezierControlPoints(ptsTop);const ctx=e.ctx;ctx.beginPath();ctx.moveTo(ptsTop[0].x,ptsTop[0].y);for(let i=0;i<cps.length;i++){ctx.bezierCurveTo(cps[i].c1x,cps[i].c1y,cps[i].c2x,cps[i].c2y,ptsTop[i+1].x,ptsTop[i+1].y);}for(let i=ptsBot.length-1;i>=0;i--)ctx.lineTo(ptsBot[i].x,ptsBot[i].y);ctx.closePath();ctx.fillStyle=M.colorAlpha(color,0.7);ctx.fill();ctx.strokeStyle=color;ctx.lineWidth=lineW;ctx.stroke();seriesPts.push({ser,color,ptsTop});}}else{for(let s=0;s<series.length;s++){const ser=series[s];const pts=[];for(let i=0;i<cats.length;i++){pts.push({x:ax.px+((i+0.5)/cats.length)*ax.pw,y:ax.yToPx(+ser.values[i]||0),});}const color=this._colorOf(s,ser);const cps=M.bezierControlPoints(pts);const grad=e.vGradient(`area-${s}-${color}`,ax.px,ax.py,ax.py+ax.ph,[[0,M.colorAlpha(color,0.4)],[1,M.colorAlpha(color,0)],]);e.smoothArea(pts,cps,ax.py+ax.ph,grad);e.smooth(pts,cps,{stroke:color,lineWidth:lineW});}}e.unclip();this._drawLegend(series.map((s,i)=>({label:s.name||'Series '+(i+1),color:this._colorOf(i,s),idx:i,hidden:false,})),ax.pw);}}class StackedBarChart extends Chart{_render(){const series=this.options.series||[];const cats=this.options.categories||[];if(!series.length||!cats.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;const totals=cats.map((_,i)=>series.reduce((a,s)=>a+(+s.values[i]||0),0));const[yLo,yHi]=M.niceDomain(0,Math.max(...totals),5);const ax=this._drawCartesian({yMin:yLo,yMax:yHi,xLabels:cats,yTickFormat:this.options.yTickFormat,});const slot=ax.pw/cats.length;const barW=Math.max(2,Math.min(50,slot-10));const prog=this._anim.progress;const groups=new Map();for(let i=0;i<cats.length;i++){let cum=0;const x=ax.px+(i+0.5)*slot-barW/2;for(let s=0;s<series.length;s++){const v=+series[s].values[i]||0;if(v<=0)continue;const segH=((v/yHi)*ax.ph)*prog;const y=ax.yToPx(cum+v);cum+=v;const color=this._colorOf(s,series[s]);if(!groups.has(color))groups.set(color,[]);const isTop=s===series.length-1;groups.get(color).push({x,y,w:barW,h:segH,radius:isTop?t.barRadius:0,});this.hits.add({_id:`${i}-${s}`,x,y,w:barW,h:segH,tipX:x+barW/2,tipY:y,tooltip:{title:cats[i],rows:[{color,name:series[s].name||'Series '+(s+1),value:v,}],},});}}for(const[color,items]of groups)e.rects(items,color);this._drawLegend(series.map((s,i)=>({label:s.name||'Series '+(i+1),color:this._colorOf(i,s),idx:i,hidden:false,})),ax.pw);}}class GroupedBarChart extends Chart{_render(){const series=this.options.series||[];const cats=this.options.categories||[];if(!series.length||!cats.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;let yMax=-Infinity;for(const s of series)for(const v of s.values)if(+v>yMax)yMax=+v;const[yLo,yHi]=M.niceDomain(0,yMax,5);const ax=this._drawCartesian({yMin:yLo,yMax:yHi,xLabels:cats,yTickFormat:this.options.yTickFormat,});const slot=ax.pw/cats.length;const inner=slot*0.8;const barW=Math.max(2,inner/series.length);const prog=this._anim.progress;const groups=new Map();for(let i=0;i<cats.length;i++){const xCenter=ax.px+(i+0.5)*slot;const left=xCenter-inner/2;for(let s=0;s<series.length;s++){const v=+series[s].values[i]||0;const x=left+s*barW;const h=((v/yHi)*ax.ph)*prog;const y=ax.yToPx(v);const color=this._colorOf(s,series[s]);if(!groups.has(color))groups.set(color,[]);groups.get(color).push({x,y,w:barW-1,h,radius:t.barRadius});this.hits.add({_id:`${i}-${s}`,x,y,w:barW-1,h,tipX:x+barW/2,tipY:y,tooltip:`${cats[i]} · ${series[s].name || s}: ${v}`,});}}for(const[color,items]of groups)e.rects(items,color);this._drawLegend(series.map((s,i)=>({label:s.name||'Series '+(i+1),color:this._colorOf(i,s),idx:i,hidden:false,})),ax.pw);}}class MultiLineChart extends Chart{_render(){const series=this.options.series||[];const cats=this.options.categories||[];if(!series.length||!cats.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;const interp=this.options.interpolation||'smooth';let yMax=-Infinity,yMin=Infinity;for(const s of series)for(const v of s.values){if(+v>yMax)yMax=+v;if(+v<yMin)yMin=+v;}const[yLo,yHi]=M.niceDomain(Math.min(0,yMin),yMax,5);const useTime=this.options.xScale==='time';let spec={yMin:yLo,yMax:yHi,yTickFormat:this.options.yTickFormat,xTickFormat:this.options.xTickFormat,};if(useTime){spec.xTime={min:+cats[0],max:+cats[cats.length-1]};}else{spec.xLabels=cats;}const ax=this._drawCartesian(spec);const lineW=t.lineWidth;const prog=this._anim.progress;e.clip(ax.px,ax.py,ax.pw*prog,ax.ph);const allPts=[];for(let s=0;s<series.length;s++){const ser=series[s];if(ser.hidden)continue;const color=this._colorOf(s,ser);const pts=ser.values.map((v,i)=>({x:useTime?ax.xToPx(+cats[i]):ax.px+((i+0.5)/cats.length)*ax.pw,y:ax.yToPx(+v),i,s,v,}));allPts.push({pts,color,s,ser});if(interp==='smooth'){const cps=M.bezierControlPoints(pts);e.smooth(pts,cps,{stroke:color,lineWidth:lineW});}else if(interp==='linear'){e.poly(pts,{stroke:color,lineWidth:lineW,cap:'round',join:'round'});}else{const mode=interp==='step'?'before':interp.split('-')[1];e.step(pts,mode,{stroke:color,lineWidth:lineW});}if(ser.values.length<=50)e.circles(pts,color,t.pointRadius);}e.unclip();if(this._cursor&&allPts.length){const cx=this._cursor.x;if(cx>=ax.px&&cx<=ax.px+ax.pw){const i=useTime?Math.round(((cx-ax.px)/ax.pw)*(cats.length-1)):Math.floor((cx-ax.px)/(ax.pw/cats.length));const idx=Math.max(0,Math.min(cats.length-1,i));const xPx=useTime?ax.xToPx(+cats[idx]):ax.px+(idx+0.5)*(ax.pw/cats.length);e.poly([{x:xPx,y:ax.py},{x:xPx,y:ax.py+ax.ph}],{stroke:t.muted,lineWidth:1,dash:[3,3],});const rows=[];for(const sp of allPts){const p=sp.pts[idx];if(!p)continue;e.circle(p.x,p.y,t.pointRadius+2,{fill:t.bg,stroke:sp.color,lineWidth:2,});rows.push({color:sp.color,name:sp.ser.name||'Series '+(sp.s+1),value:sp.ser.values[idx],});}const title=useTime?new Date(+cats[idx]).toISOString().slice(0,19).replace('T',' '):String(cats[idx]);this.tooltip.show(xPx,ax.py+8,{title,rows},t);this._sharedTooltip=true;}}this._drawLegend(series.map((s,i)=>({label:s.name||'Series '+(i+1),color:this._colorOf(i,s),idx:i,hidden:!!s.hidden,})),ax.pw);}}class ScatterChart extends Chart{_render(){const data=this.options.data||[];if(!data.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;let xMin=Infinity,xMax=-Infinity,yMin=Infinity,yMax=-Infinity;for(const d of data){if(d.x<xMin)xMin=d.x;if(d.x>xMax)xMax=d.x;if(d.y<yMin)yMin=d.y;if(d.y>yMax)yMax=d.y;}const[xLo,xHi]=M.niceDomain(xMin,xMax,6);const[yLo,yHi]=M.niceDomain(yMin,yMax,5);const ax=this._drawCartesian({yMin:yLo,yMax:yHi,xLinear:{min:xLo,max:xHi},});const prog=this._anim.progress;const groups=new Map();const defaultR=t.pointRadius+1;for(let i=0;i<data.length;i++){const d=data[i];const cx=ax.xToPx(d.x);const cy=ax.yToPx(d.y);const r=(d.r||defaultR)*prog;const g=d.group!=null?d.group:0;const color=this._colorOf(g,{color:d.color});const key=color+':'+r.toFixed(1);if(!groups.has(key))groups.set(key,{color,r,items:[]});groups.get(key).items.push({x:cx,y:cy});this.hits.add({_id:i,x:cx-r-2,y:cy-r-2,w:r*2+4,h:r*2+4,tipX:cx,tipY:cy-r,tooltip:`(${d.x}, ${d.y})${d.label ? ` · ${d.label}` : ''}`,});}e.withAlpha(0.7,()=>{for(const{color,r,items}of groups.values())e.circles(items,color,r);});}}class BubbleChart extends Chart{_render(){const data=this.options.data||[];if(!data.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;let xMin=Infinity,xMax=-Infinity,yMin=Infinity,yMax=-Infinity,zMax=0;for(const d of data){if(d.x<xMin)xMin=d.x;if(d.x>xMax)xMax=d.x;if(d.y<yMin)yMin=d.y;if(d.y>yMax)yMax=d.y;if((d.z||0)>zMax)zMax=d.z;}const[xLo,xHi]=M.niceDomain(xMin,xMax,6);const[yLo,yHi]=M.niceDomain(yMin,yMax,5);const ax=this._drawCartesian({yMin:yLo,yMax:yHi,xLinear:{min:xLo,max:xHi},});const prog=this._anim.progress;const maxR=32,minR=4;for(let i=0;i<data.length;i++){const d=data[i];const cx=ax.xToPx(d.x);const cy=ax.yToPx(d.y);const r=(minR+(Math.sqrt((d.z||0)/Math.max(1,zMax)))*(maxR-minR))*prog;const color=d.color||this._colorOf(d.group!=null?d.group:i);e.circle(cx,cy,r,{fill:M.colorAlpha(color,0.5),stroke:color,lineWidth:1.5,});this.hits.add({_id:i,x:cx-r,y:cy-r,w:r*2,h:r*2,tipX:cx,tipY:cy-r,tooltip:`${d.label || ''} (${d.x}, ${d.y}) · z=${d.z}`,});}}}class PieChart extends Chart{_render(){const data=this.options.data||[];if(!data.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;const labelOf=this.options.label||(d=>d.label);const valueOf=this.options.value||(d=>d.value);const total=data.reduce((a,d,i)=>a+valueOf(d,i),0);if(total<=0){this.setEmpty('No data');return;}const cx=e.w/2,cy=e.h/2+4;const R=Math.min(e.w,e.h)/2-24;const innerR=this.options.innerRadius!=null?(typeof this.options.innerRadius==='number'?this.options.innerRadius:R*0.55):0;const prog=this._anim.progress;let a0=-Math.PI/2;for(let i=0;i<data.length;i++){const v=valueOf(data[i],i);const sweep=(v/total)*TAU*prog;const a1=a0+sweep;const color=this._colorOf(i,data[i]);const isHover=this._hover&&this._hover._id===i;const offset=isHover?6:0;const mid=(a0+a1)/2;const ox=Math.cos(mid)*offset,oy=Math.sin(mid)*offset;e.arc(cx+ox,cy+oy,innerR,R,a0,a1,{fill:color});const midR=(innerR+R)/2;const hx=cx+Math.cos(mid)*midR;const hy=cy+Math.sin(mid)*midR;this.hits.add({_id:i,x:hx-12,y:hy-12,w:24,h:24,tipX:hx,tipY:hy-8,tooltip:`${labelOf(data[i], i)}: ${v} (${(v / total * 100).toFixed(1)}%)`,});a0=a1;}if(innerR>0&&this.options.centerLabel!==false){const center=this._hover?`${(valueOf(data[this._hover._id], this._hover._id) / total * 100).toFixed(0)}%`:(this.options.totalLabel||total.toLocaleString());e.text(center,cx,cy,{fill:t.text,font:`600 ${t.fontSize * 1.6}px ${t.font}`,align:'center',baseline:'middle',});}}}class RadarChart extends Chart{_render(){const series=this.options.series||[];const axes=this.options.axes||[];if(!series.length||!axes.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;const cx=e.w/2;const cy=e.h/2+4;const R=Math.min(e.w,e.h)/2-32;const N=axes.length;const angle=i=>-Math.PI/2+(i/N)*TAU;let max=-Infinity;for(const s of series)for(const v of s.values)if(+v>max)max=+v;const yLo=0;const niceMax=M.niceDomain(0,max,5)[1];const rings=M.niceTicks(0,niceMax,4).filter(v=>v>0);e.ctx.strokeStyle=t.grid;e.ctx.lineWidth=1;for(const v of rings){const r=(v/niceMax)*R;e.ctx.beginPath();for(let i=0;i<=N;i++){const a=angle(i%N);const x=cx+Math.cos(a)*r;const y=cy+Math.sin(a)*r;if(i===0)e.ctx.moveTo(x,y);else e.ctx.lineTo(x,y);}e.ctx.stroke();}e.ctx.strokeStyle=t.grid;for(let i=0;i<N;i++){const a=angle(i);e.ctx.beginPath();e.ctx.moveTo(cx,cy);e.ctx.lineTo(cx+Math.cos(a)*R,cy+Math.sin(a)*R);e.ctx.stroke();}const font=`${t.fontSize}px ${t.font}`;for(let i=0;i<N;i++){const a=angle(i);const lx=cx+Math.cos(a)*(R+14);const ly=cy+Math.sin(a)*(R+14);e.text(axes[i],lx,ly,{fill:t.muted,font,align:Math.cos(a)>0.3?'left':Math.cos(a)<-0.3?'right':'center',baseline:Math.sin(a)>0.3?'top':Math.sin(a)<-0.3?'bottom':'middle',});}const prog=this._anim.progress;for(let s=0;s<series.length;s++){const ser=series[s];const color=this._colorOf(s,ser);const pts=[];for(let i=0;i<N;i++){const v=(+ser.values[i]||0)*prog;const r=(v/niceMax)*R;const a=angle(i);pts.push({x:cx+Math.cos(a)*r,y:cy+Math.sin(a)*r});}e.poly(pts,{fill:M.colorAlpha(color,0.25),stroke:color,lineWidth:2,close:true,});e.circles(pts,color,t.pointRadius);}this._drawLegend(series.map((s,i)=>({label:s.name||'Series '+(i+1),color:this._colorOf(i,s),idx:i,hidden:false,})),e.w-16);}}class PolarChart extends Chart{_render(){const data=this.options.data||[];if(!data.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;const labelOf=this.options.label||(d=>d.label);const valueOf=this.options.value||(d=>d.value);const N=data.length;const cx=e.w/2,cy=e.h/2+4;const R=Math.min(e.w,e.h)/2-24;const max=Math.max(...data.map((d,i)=>valueOf(d,i)));const prog=this._anim.progress;for(let i=0;i<N;i++){const v=valueOf(data[i],i);const a0=-Math.PI/2+(i/N)*TAU;const a1=a0+(TAU/N)*0.92;const r=(v/max)*R*prog;const color=this._colorOf(i,data[i]);e.arc(cx,cy,0,r,a0,a1,{fill:color});const mid=(a0+a1)/2;const lx=cx+Math.cos(mid)*(R+12);const ly=cy+Math.sin(mid)*(R+12);e.text(labelOf(data[i],i),lx,ly,{fill:t.muted,font:`${t.fontSize}px ${t.font}`,align:'center',baseline:'middle',});}}}class GaugeChart extends Chart{setValue(v){this.options.value=v;this._anim.start=performance.now();this._anim.progress=0;this._dirty=true;scheduler.schedule(this);return this;}_render(){const e=this.engine;const t=this.theme;const val=this.options.value||0;const max=this.options.max||100;const cx=e.w/2;const cy=e.h*0.7;const R=Math.min(e.w/2,e.h*0.7)-12;const r=R-18;const a0=Math.PI*1.1;const a1=Math.PI*1.9;const span=a1-a0;const prog=this._anim.progress;const color=this.options.color||this.palette[0];e.arc(cx,cy,r,R,a0,a1,{fill:t.grid});const aEnd=a0+span*(val/max)*prog;if(aEnd>a0)e.arc(cx,cy,r,R,a0,aEnd,{fill:color});const num=Math.round(val);const unit=this.options.unit||'';e.text(`${num}${unit}`,cx,cy,{fill:t.text,font:`700 ${R / 2.5}px ${t.font}`,align:'center',baseline:'middle',});if(this.options.label){e.text(this.options.label,cx,cy+R/2.5,{fill:t.muted,font:`${t.fontSize}px ${t.font}`,align:'center',baseline:'top',});}}}class HeatmapChart extends Chart{_axisPad(){return{l:60,r:16,t:16,b:40};}_render(){const data=this.options.data||[];const xLabels=this.options.xLabels||[];const yLabels=this.options.yLabels||[];if(!data.length||!xLabels.length||!yLabels.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;const pad=this._axisPad();const px=pad.l,py=pad.t;const pw=e.w-pad.l-pad.r;const ph=e.h-pad.t-pad.b;const font=`${t.fontSize}px ${t.font}`;let vMax=-Infinity,vMin=Infinity;for(const d of data){if(d.value>vMax)vMax=d.value;if(d.value<vMin)vMin=d.value;}const cellW=pw/xLabels.length;const cellH=ph/yLabels.length;const lo=this.options.colorLow||M.colorAlpha(this.palette[0],0.1);const hi=this.options.colorHigh||this.palette[0];const gap=1;const prog=this._anim.progress;for(const d of data){const xi=typeof d.x==='number'?d.x:xLabels.indexOf(d.x);const yi=typeof d.y==='number'?d.y:yLabels.indexOf(d.y);if(xi<0||yi<0)continue;const x=px+xi*cellW;const y=py+yi*cellH;const tNorm=vMax>vMin?(d.value-vMin)/(vMax-vMin):0.5;const fill=M.colorMix(lo,hi,tNorm*prog);e.rect(x+gap,y+gap,cellW-gap*2,cellH-gap*2,{fill,radius:2});this.hits.add({_id:`${xi}-${yi}`,x,y,w:cellW,h:cellH,tipX:x+cellW/2,tipY:y,tooltip:`${xLabels[xi]} · ${yLabels[yi]}: ${d.value}`,});}for(let i=0;i<xLabels.length;i++){e.text(xLabels[i],px+(i+0.5)*cellW,py+ph+4,{fill:t.muted,font,align:'center',baseline:'top',});}for(let i=0;i<yLabels.length;i++){e.text(yLabels[i],px-6,py+(i+0.5)*cellH,{fill:t.muted,font,align:'right',baseline:'middle',});}}}class BoxplotChart extends Chart{_render(){const data=this.options.data||[];if(!data.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;let yMax=-Infinity,yMin=Infinity;for(const d of data){if(d.max>yMax)yMax=d.max;if(d.min<yMin)yMin=d.min;if(d.outliers)for(const o of d.outliers){if(o>yMax)yMax=o;if(o<yMin)yMin=o;}}const[yLo,yHi]=M.niceDomain(yMin,yMax,5);const ax=this._drawCartesian({yMin:yLo,yMax:yHi,xLabels:data.map(d=>d.label),});const slot=ax.pw/data.length;const boxW=Math.min(40,slot*0.5);for(let i=0;i<data.length;i++){const d=data[i];const cx=ax.px+(i+0.5)*slot;const yMin=ax.yToPx(d.min);const yMax=ax.yToPx(d.max);const yQ1=ax.yToPx(d.q1);const yQ3=ax.yToPx(d.q3);const yMed=ax.yToPx(d.median);const color=this._colorOf(i,d);e.poly([{x:cx,y:yMax},{x:cx,y:yMin}],{stroke:color,lineWidth:1});e.poly([{x:cx-boxW/3,y:yMax},{x:cx+boxW/3,y:yMax}],{stroke:color,lineWidth:1});e.poly([{x:cx-boxW/3,y:yMin},{x:cx+boxW/3,y:yMin}],{stroke:color,lineWidth:1});e.rect(cx-boxW/2,yQ3,boxW,yQ1-yQ3,{fill:M.colorAlpha(color,0.4),stroke:color,lineWidth:1.5,});e.poly([{x:cx-boxW/2,y:yMed},{x:cx+boxW/2,y:yMed}],{stroke:color,lineWidth:2,});if(d.outliers){e.circles(d.outliers.map(v=>({x:cx,y:ax.yToPx(v)})),color,2);}this.hits.add({_id:i,x:cx-boxW/2,y:yQ3,w:boxW,h:yQ1-yQ3,tipX:cx,tipY:yQ3,tooltip:{title:d.label,rows:[{name:'min',value:d.min},{name:'Q1',value:d.q1},{name:'med',value:d.median},{name:'Q3',value:d.q3},{name:'max',value:d.max},],},});}}}class FunnelChart extends Chart{_render(){const data=this.options.data||[];if(!data.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;const labelOf=this.options.label||(d=>d.label);const valueOf=this.options.value||(d=>d.value);const N=data.length;const max=Math.max(...data.map((d,i)=>valueOf(d,i)));const pad=24;const w=e.w-pad*2;const segH=(e.h-pad*2)/N;const cx=e.w/2;const prog=this._anim.progress;const font=`${t.fontSize}px ${t.font}`;for(let i=0;i<N;i++){const v=valueOf(data[i],i);const wTop=(v/max)*w*prog;const wBot=i+1<N?(valueOf(data[i+1],i+1)/max)*w*prog:wTop*0.85;const yTop=pad+i*segH;const yBot=yTop+segH-2;const color=this._colorOf(i,data[i]);const trap=[{x:cx-wTop/2,y:yTop},{x:cx+wTop/2,y:yTop},{x:cx+wBot/2,y:yBot},{x:cx-wBot/2,y:yBot},];e.poly(trap,{fill:color,close:true});e.text(`${labelOf(data[i], i)} ${v}`,cx,yTop+segH/2,{fill:'#fff',font:`600 ${t.fontSize}px ${t.font}`,align:'center',baseline:'middle',});this.hits.add({_id:i,x:cx-wTop/2,y:yTop,w:wTop,h:segH,tipX:cx,tipY:yTop,tooltip:`${labelOf(data[i], i)}: ${v} (${(v / max * 100).toFixed(1)}%)`,});}}}class TreemapChart extends Chart{_render(){const data=this.options.data||[];if(!data.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;const labelOf=this.options.label||(d=>d.label);const valueOf=this.options.value||(d=>d.value);const items=data.map((d,i)=>({label:labelOf(d,i),value:valueOf(d,i),color:this._colorOf(i,d),data:d,_id:i,})).sort((a,b)=>b.value-a.value);const total=items.reduce((a,x)=>a+x.value,0);const rect={x:4,y:4,w:e.w-8,h:e.h-8};const prog=this._anim.progress;squarify(items,total,rect,(it,r)=>{const padIn=2;const x=r.x+padIn,y=r.y+padIn;const w=(r.w-padIn*2)*prog;const h=(r.h-padIn*2)*prog;if(w<1||h<1)return;e.rect(x,y,w,h,{fill:it.color,radius:2});if(w>60&&h>30){e.text(it.label,x+6,y+6,{fill:'#fff',font:`600 ${t.fontSize}px ${t.font}`,align:'left',baseline:'top',});e.text(String(it.value),x+6,y+6+t.fontSize+2,{fill:'rgba(255,255,255,0.85)',font:`${t.fontSize}px ${t.font}`,align:'left',baseline:'top',});}this.hits.add({_id:it._id,x,y,w,h,tipX:x+w/2,tipY:y,tooltip:`${it.label}: ${it.value}`,});});}}function squarify(items,total,rect,draw){if(!items.length)return;const area=rect.w*rect.h;let i=0;while(i<items.length){const remaining=items.slice(i);const remTotal=remaining.reduce((a,x)=>a+x.value,0);if(remTotal<=0)break;const remArea=(remTotal/total)*area;const w=Math.min(rect.w,rect.h);let row=[],rowSum=0;let bestRatio=Infinity;for(let j=0;j<remaining.length;j++){const next=remaining[j];const trialSum=rowSum+next.value;const trialAreaPx=(trialSum/remTotal)*remArea;const trialW=trialAreaPx/w;let worst=0;for(const r of[...row,next]){const a=(r.value/trialSum)*trialAreaPx;const ratio=Math.max(trialW/(a/trialW),(a/trialW)/trialW);if(ratio>worst)worst=ratio;}if(worst>bestRatio)break;bestRatio=worst;row.push(next);rowSum=trialSum;}if(!row.length)row=[remaining[0]],rowSum=remaining[0].value;const isHoriz=rect.w>=rect.h;const rowAreaPx=(rowSum/remTotal)*remArea;const stripT=rowAreaPx/w;let cursor=isHoriz?rect.y:rect.x;for(const it of row){const itLen=((it.value/rowSum)*rowAreaPx)/stripT;const r=isHoriz?{x:rect.x,y:cursor,w:stripT,h:itLen}:{x:cursor,y:rect.y,w:itLen,h:stripT};const r2=isHoriz?{x:rect.x,y:cursor,w:stripT,h:itLen}:{x:cursor,y:rect.y,w:itLen,h:stripT};const placedRect=isHoriz?{x:rect.x,y:cursor,w:stripT,h:itLen}:{x:cursor,y:rect.y,w:itLen,h:stripT};draw(it,placedRect);cursor+=itLen;}if(isHoriz){rect.x+=stripT;rect.w-=stripT;}else{rect.y+=stripT;rect.h-=stripT;}i+=row.length;if(rect.w<1||rect.h<1)break;}}class Sparkline extends Chart{_render(){const data=this.options.data||[];if(!data.length)return;const e=this.engine;const t=this.theme;const valueOf=this.options.value||(v=>+v);const values=data.map(valueOf);const yMin=Math.min(...values),yMax=Math.max(...values);const yRange=yMax-yMin||1;const pad=2;const w=e.w-pad*2;const h=e.h-pad*2;const points=values.map((v,i)=>({x:pad+(i/(values.length-1||1))*w,y:pad+h-((v-yMin)/yRange)*h,}));const color=this.options.color||this.palette[0];const cps=M.bezierControlPoints(points);if(this.options.fill!==false){const grad=e.vGradient(`spark-${color}`,0,pad,pad+h,[[0,M.colorAlpha(color,0.3)],[1,M.colorAlpha(color,0)],]);e.smoothArea(points,cps,pad+h,grad);}e.smooth(points,cps,{stroke:color,lineWidth:t.lineWidth});if(this.options.lastDot!==false){const last=points[points.length-1];e.circle(last.x,last.y,t.pointRadius+1,{fill:color});}}}class RealtimeChart extends Chart{constructor(container,options={}){super(container,options);this.options.windowSize=options.windowSize||200;this._series=(options.series||[]).map(s=>({name:s.name||'Series',color:s.color,values:[],hidden:false,}));this._streaming=true;}push(values){const arr=Array.isArray(values)?values:[values];for(let i=0;i<this._series.length;i++){const s=this._series[i];s.values.push(arr[i]!=null?arr[i]:0);if(s.values.length>this.options.windowSize)s.values.shift();}if(this._state==='empty')this._state=null;this._dirty=true;scheduler.schedule(this);}clear(){for(const s of this._series)s.values.length=0;this._dirty=true;scheduler.schedule(this);}_render(){const e=this.engine;const t=this.theme;const hasData=this._series.some(s=>s.values.length>0);if(!hasData){const ax=this._drawCartesian({yMin:0,yMax:100,yTickFormat:this.options.yTickFormat,});e.text('streaming…',ax.px+ax.pw/2,ax.py+ax.ph/2,{fill:t.muted,font:`${t.fontSize}px ${t.font}`,align:'center',baseline:'middle',});return;}let yMin=Infinity,yMax=-Infinity;for(const s of this._series)for(const v of s.values){if(v<yMin)yMin=v;if(v>yMax)yMax=v;}const[yLo,yHi]=M.niceDomain(Math.min(0,yMin),yMax,5);const N=this.options.windowSize;const ax=this._drawCartesian({yMin:yLo,yMax:yHi,yTickFormat:this.options.yTickFormat,});for(let s=0;s<this._series.length;s++){const ser=this._series[s];if(ser.hidden)continue;const color=this._colorOf(s,ser);const pts=ser.values.map((v,i)=>({x:ax.px+(i/(N-1))*ax.pw,y:ax.yToPx(v),}));if(pts.length<2){if(pts.length===1)e.circle(pts[0].x,pts[0].y,t.pointRadius+2,{fill:color});continue;}const cps=M.bezierControlPoints(pts);e.smooth(pts,cps,{stroke:color,lineWidth:t.lineWidth});const last=pts[pts.length-1];e.circle(last.x,last.y,t.pointRadius+2,{fill:color});}this._drawLegend(this._series.map((s,i)=>({label:s.name,color:this._colorOf(i,s),idx:i,hidden:s.hidden,})),ax.pw);}}class WaterfallChart extends Chart{_render(){const data=this.options.data||[];if(!data.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;let cum=0;const segs=data.map(d=>{if(d.isTotal){const seg={from:0,to:d.value,value:d.value,isTotal:true,label:d.label};cum=d.value;return seg;}else{const seg={from:cum,to:cum+d.value,value:d.value,label:d.label};cum+=d.value;return seg;}});const allVals=segs.flatMap(s=>[s.from,s.to]);const[yLo,yHi]=M.niceDomain(Math.min(...allVals,0),Math.max(...allVals),5);const ax=this._drawCartesian({yMin:yLo,yMax:yHi,xLabels:data.map(d=>d.label),});const slot=ax.pw/segs.length;const barW=Math.max(2,Math.min(60,slot-12));const prog=this._anim.progress;const upColor=this.options.colorUp||'#10b981';const dnColor=this.options.colorDown||'#ef4444';const totalColor=this.options.colorTotal||'#6366f1';for(let i=0;i<segs.length;i++){const s=segs[i];const x=ax.px+(i+0.5)*slot-barW/2;const y0=ax.yToPx(s.from);const y1=ax.yToPx(s.to);const top=Math.min(y0,y1);const fullH=Math.abs(y1-y0);const h=fullH*prog;const animTop=s.to>=s.from?y0-h:y0;const color=s.isTotal?totalColor:(s.value>=0?upColor:dnColor);e.rect(x,animTop,barW,h,{fill:color,radius:t.barRadius});if(i<segs.length-1&&!segs[i+1].isTotal){const nextX=ax.px+(i+1.5)*slot-barW/2;const yEnd=ax.yToPx(s.to);e.poly([{x:x+barW,y:yEnd},{x:nextX,y:yEnd}],{stroke:t.muted,lineWidth:1,dash:[3,3],});}this.hits.add({_id:i,x,y:animTop,w:barW,h,tipX:x+barW/2,tipY:animTop,tooltip:`${s.label}: ${s.value > 0 && !s.isTotal ? '+' : ''}${s.value}`,});}}}class CompositeChart extends Chart{_render(){const layers=this.options.layers||[];const cats=this.options.categories||[];if(!layers.length||!cats.length){this.setEmpty('No data');return;}const e=this.engine;const t=this.theme;const leftLayers=layers.filter(l=>l.axis!=='right');const rightLayers=layers.filter(l=>l.axis==='right');const domain=(ll)=>{let mn=Infinity,mx=-Infinity;for(const l of ll){if(l.type==='scatter'){for(const p of l.values){if(p.y<mn)mn=p.y;if(p.y>mx)mx=p.y;}}else{for(const v of l.values){if(+v<mn)mn=+v;if(+v>mx)mx=+v;}}}if(mn===Infinity)return[0,1];return M.niceDomain(Math.min(0,mn),mx,5);};const[yLLo,yLHi]=domain(leftLayers);const[yRLo,yRHi]=rightLayers.length?domain(rightLayers):[0,1];const pad={l:50,r:rightLayers.length?50:16,t:30,b:32};const px=pad.l,py=pad.t;const pw=e.w-pad.l-pad.r;const ph=e.h-pad.t-pad.b;const font=`${t.fontSize}px ${t.font}`;const yLPx=v=>py+ph*(1-(v-yLLo)/(yLHi-yLLo));const yRPx=v=>py+ph*(1-(v-yRLo)/(yRHi-yRLo));const yLTicks=M.niceTicks(yLLo,yLHi,5);const yRTicks=rightLayers.length?M.niceTicks(yRLo,yRHi,5):[];e.ctx.strokeStyle=t.grid;e.ctx.lineWidth=1;e.ctx.beginPath();for(const v of yLTicks){const y=Math.round(yLPx(v))+0.5;e.ctx.moveTo(px,y);e.ctx.lineTo(px+pw,y);}e.ctx.stroke();const yLFmt=this.options.yLeftTickFormat||(v=>String(v));const yRFmt=this.options.yRightTickFormat||(v=>String(v));for(const v of yLTicks){e.text(yLFmt(v),px-6,yLPx(v),{fill:t.muted,font,align:'right',baseline:'middle',});}for(const v of yRTicks){e.text(yRFmt(v),px+pw+6,yRPx(v),{fill:t.muted,font,align:'left',baseline:'middle',});}const useTime=this.options.xScale==='time';const xToPx=useTime?(i)=>px+((+cats[i]-+cats[0])/(+cats[cats.length-1]-+cats[0]||1))*pw:(i)=>px+((i+0.5)/cats.length)*pw;let lastEnd=-Infinity;for(let i=0;i<cats.length;i++){const x=xToPx(i);const txt=useTime?new Date(+cats[i]).toISOString().slice(0,10):String(cats[i]);const w=e.measure(txt,font);if(x-w/2<lastEnd+4)continue;lastEnd=x+w/2;e.text(txt,x,py+ph+6,{fill:t.muted,font,align:'center',baseline:'top',});}e.poly([{x:px,y:py+ph},{x:px+pw,y:py+ph}],{stroke:t.axis,lineWidth:1});const prog=this._anim.progress;const lineW=t.lineWidth;const barLayers=layers.filter(l=>l.type==='bar');const slot=pw/cats.length;const innerSlot=slot*0.7;const barW=barLayers.length?Math.max(2,innerSlot/barLayers.length):0;barLayers.forEach((layer,bi)=>{const yPx=layer.axis==='right'?yRPx:yLPx;const yBase=yPx(0);const color=layer.color||this._colorOf(layers.indexOf(layer),layer);const items=[];for(let i=0;i<cats.length;i++){const v=+layer.values[i]||0;const xC=xToPx(i);const x=xC-innerSlot/2+bi*barW;const y0=yBase;const y1=yPx(v);const top=Math.min(y0,y1);const h=Math.abs(y1-y0)*prog;const animTop=v>=0?y0-h:y0;items.push({x,y:animTop,w:barW-1,h,radius:t.barRadius});this.hits.add({_id:`${layer.name || bi}-${i}`,x,y:animTop,w:barW-1,h,tipX:x+barW/2,tipY:animTop,tooltip:`${layer.name || ''} · ${cats[i]}: ${v}`,});}e.rects(items,color);});e.clip(px,py,pw*prog,ph);layers.filter(l=>l.type==='area').forEach((layer)=>{const yPx=layer.axis==='right'?yRPx:yLPx;const color=layer.color||this._colorOf(layers.indexOf(layer),layer);const pts=layer.values.map((v,i)=>({x:xToPx(i),y:yPx(+v)}));const cps=M.bezierControlPoints(pts);const grad=e.vGradient(`area-${layer.name}-${color}`,px,py,py+ph,[[0,M.colorAlpha(color,0.4)],[1,M.colorAlpha(color,0)],]);e.smoothArea(pts,cps,py+ph,grad);e.smooth(pts,cps,{stroke:color,lineWidth:lineW});});layers.filter(l=>l.type==='line').forEach((layer)=>{const yPx=layer.axis==='right'?yRPx:yLPx;const color=layer.color||this._colorOf(layers.indexOf(layer),layer);const pts=layer.values.map((v,i)=>({x:xToPx(i),y:yPx(+v),i,v}));const cps=M.bezierControlPoints(pts);e.smooth(pts,cps,{stroke:color,lineWidth:lineW});if(pts.length<=50)e.circles(pts,color,t.pointRadius);for(const p of pts){this.hits.add({_id:`${layer.name || 'line'}-${p.i}`,x:p.x-6,y:p.y-6,w:12,h:12,tipX:p.x,tipY:p.y-t.pointRadius,tooltip:`${layer.name || ''} · ${cats[p.i]}: ${p.v}`,});}});e.unclip();layers.filter(l=>l.type==='scatter').forEach((layer)=>{const yPx=layer.axis==='right'?yRPx:yLPx;const color=layer.color||this._colorOf(layers.indexOf(layer),layer);const items=layer.values.map((p)=>({x:xToPx(typeof p.x==='number'?p.x:cats.indexOf(p.x)),y:yPx(p.y),}));e.withAlpha(0.7,()=>e.circles(items,color,layer.radius||t.pointRadius+1));});this._drawLegend(layers.map((l,i)=>({label:(l.name||l.type)+(l.axis==='right'?' (R)':''),color:l.color||this._colorOf(i,l),idx:i,hidden:false,})),pw);}}return{Chart,Engine,getFPS,getActiveCount,scheduler,math:M,PALETTE,BarChart,HBarChart,LineChart,AreaChart,StackedBarChart,GroupedBarChart,MultiLineChart,ScatterChart,BubbleChart,PieChart,RadarChart,PolarChart,GaugeChart,HeatmapChart,BoxplotChart,FunnelChart,TreemapChart,Sparkline,RealtimeChart,WaterfallChart,CompositeChart,};})();if(typeof module!=='undefined'&&module.exports)module.exports=Aeriz;if(typeof window!=='undefined')window.Aeriz=Aeriz;
|
|
1
|
+
/*! @aeriz/chart v2.0.0 | Apache-2.0 | https://aeriz.dev/chart/ */
|
|
2
|
+
/*! @aeriz/chart v2.0.0 | Apache-2.0 | https://aeriz.dev/chart/ */var Aeriz=(function(){'use strict';const TAU=Math.PI*2;const clamp=(v,lo,hi)=>v<lo?lo:v>hi?hi:v;const lerp=(a,b,t)=>a+(b-a)*t;function extent(arr,fn=(x)=>x){let mn=Infinity,mx=-Infinity;for(let i=0;i<arr.length;i++){const v=fn(arr[i],i);if(v<mn)mn=v;if(v>mx)mx=v;}
|
|
3
|
+
return[mn===Infinity?0:mn,mx===-Infinity?1:mx];}
|
|
4
|
+
function niceTicks(min,max,count=5){if(min===max){min-=0.5;max+=0.5;}
|
|
5
|
+
const span=max-min;const step0=span/Math.max(1,count);const mag=Math.pow(10,Math.floor(Math.log10(step0)));const norm=step0/mag;let step;if(norm<1.5)step=mag;else if(norm<3)step=2*mag;else if(norm<7)step=5*mag;else step=10*mag;const lo=Math.ceil(min/step-1e-9)*step;const hi=Math.floor(max/step+1e-9)*step;const ticks=[];for(let i=0,v=lo;v<=hi+step/2;v=lo+ ++i*step){ticks.push(+v.toPrecision(12));}
|
|
6
|
+
return ticks;}
|
|
7
|
+
function niceDomain(min,max,count=5){if(min===max){min-=0.5;max+=0.5;}
|
|
8
|
+
const span=max-min;const step0=span/Math.max(1,count);const mag=Math.pow(10,Math.floor(Math.log10(step0)));const norm=step0/mag;let step;if(norm<1.5)step=mag;else if(norm<3)step=2*mag;else if(norm<7)step=5*mag;else step=10*mag;return[+(Math.floor(min/step+1e-9)*step).toPrecision(12),+(Math.ceil(max/step-1e-9)*step).toPrecision(12)];}
|
|
9
|
+
const T={sec:1e3,min:6e4,hour:36e5,day:864e5,week:6.048e8,month:2.628e9};function timeNiceTicks(minMs,maxMs,count=6){if(minMs===maxMs){minMs-=1000;maxMs+=1000;}
|
|
10
|
+
const span=maxMs-minMs;const target=span/count;const units=[[T.sec,1],[T.sec,5],[T.sec,15],[T.sec,30],[T.min,1],[T.min,5],[T.min,15],[T.min,30],[T.hour,1],[T.hour,3],[T.hour,6],[T.hour,12],[T.day,1],[T.day,2],[T.week,1],[T.month,1],[T.month,3],[T.month,6],[T.month,12],];let step=T.sec;for(const[u,n]of units)if(u*n<=target*1.5)step=u*n;const lo=Math.ceil(minMs/step)*step;const ticks=[];for(let v=lo;v<=maxMs;v+=step)ticks.push(v);return ticks;}
|
|
11
|
+
function timeFormat(step){if(step<T.min)return(t)=>new Date(t).toISOString().slice(11,19);if(step<T.hour)return(t)=>new Date(t).toISOString().slice(11,16);if(step<T.day)return(t)=>new Date(t).toISOString().slice(11,16);if(step<T.month)return(t)=>new Date(t).toISOString().slice(5,10);return(t)=>new Date(t).toISOString().slice(0,7);}
|
|
12
|
+
function minMaxDecimate(data,targetCount,fn=(d)=>d.value){if(data.length<=targetCount)return data;const bucket=data.length/targetCount;const out=[];for(let i=0;i<targetCount;i++){const lo=Math.floor(i*bucket);const hi=Math.min(data.length,Math.floor((i+1)*bucket));let mnI=lo,mxI=lo;let mn=fn(data[lo],lo),mx=mn;for(let j=lo+1;j<hi;j++){const v=fn(data[j],j);if(v<mn){mn=v;mnI=j;}
|
|
13
|
+
if(v>mx){mx=v;mxI=j;}}
|
|
14
|
+
if(mnI<mxI){out.push(data[mnI]);out.push(data[mxI]);}
|
|
15
|
+
else{out.push(data[mxI]);out.push(data[mnI]);}}
|
|
16
|
+
return out;}
|
|
17
|
+
function minMaxDecimateIdx(n,targetCount,valueAt){const out=[];if(n<=targetCount){for(let i=0;i<n;i++)out.push(i);return out;}
|
|
18
|
+
const bucket=n/targetCount;for(let b=0;b<targetCount;b++){const lo=Math.floor(b*bucket);const hi=Math.min(n,Math.floor((b+1)*bucket));let mnI=lo,mxI=lo,mn=valueAt(lo),mx=mn;for(let j=lo+1;j<hi;j++){const v=valueAt(j);if(v<mn){mn=v;mnI=j;}
|
|
19
|
+
if(v>mx){mx=v;mxI=j;}}
|
|
20
|
+
if(mnI===mxI)out.push(mnI);else if(mnI<mxI)out.push(mnI,mxI);else out.push(mxI,mnI);}
|
|
21
|
+
return out;}
|
|
22
|
+
function shouldDecimate(n,pxWidth){return n>pxWidth*2;}
|
|
23
|
+
function bezierControlPoints(pts,tension=1/6){const n=pts.length;if(n<2)return[];const cps=[];for(let i=0;i<n-1;i++){const p0=pts[i-1]||pts[i];const p1=pts[i];const p2=pts[i+1];const p3=pts[i+2]||p2;const lo=Math.min(p1.y,p2.y),hi=Math.max(p1.y,p2.y);cps.push({c1x:p1.x+(p2.x-p0.x)*tension,c1y:clamp(p1.y+(p2.y-p0.y)*tension,lo,hi),c2x:p2.x-(p3.x-p1.x)*tension,c2y:clamp(p2.y-(p3.y-p1.y)*tension,lo,hi),});}
|
|
24
|
+
return cps;}
|
|
25
|
+
function springStep(v,tgt,vel,cfg={}){const stiffness=cfg.stiffness||170;const damping=cfg.damping||26;const dt=1/60;const F=-stiffness*(v-tgt);const D=-damping*vel;const a=F+D;vel=vel+a*dt;v=v+vel*dt;const done=Math.abs(vel)<0.05&&Math.abs(v-tgt)<0.05;if(done){v=tgt;vel=0;}
|
|
26
|
+
return{value:v,velocity:vel,done};}
|
|
27
|
+
function colorParse(c){if(!c)return[0,0,0,1];if(c[0]==='#'){const h=c.slice(1);if(h.length===3)return[parseInt(h[0]+h[0],16),parseInt(h[1]+h[1],16),parseInt(h[2]+h[2],16),1];if(h.length===6)return[parseInt(h.slice(0,2),16),parseInt(h.slice(2,4),16),parseInt(h.slice(4,6),16),1];if(h.length===8)return[parseInt(h.slice(0,2),16),parseInt(h.slice(2,4),16),parseInt(h.slice(4,6),16),parseInt(h.slice(6,8),16)/255];}
|
|
28
|
+
const m=c.match(/rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:[,\s/]+([\d.]+)(%?))?\s*\)/);if(m)return[+m[1],+m[2],+m[3],m[4]===undefined?1:m[5]?m[4]/100:+m[4]];return[0,0,0,1];}
|
|
29
|
+
function colorAlpha(c,a){const[r,g,b]=colorParse(c);return`rgba(${r|0},${g|0},${b|0},${a})`;}
|
|
30
|
+
function colorMix(a,b,t){const[r1,g1,b1,a1]=colorParse(a);const[r2,g2,b2,a2]=colorParse(b);return`rgba(${(r1+(r2-r1)*t)|0},${(g1+(g2-g1)*t)|0},${(b1+(b2-b1)*t)|0},${a1+(a2-a1)*t})`;}
|
|
31
|
+
const PALETTE=['#64c8ff','#10b981','#f59e0b','#ef4444','#8b5cf6','#ec4899','#84cc16','#6366f1',];const NF=new Map();function nf(opts){const key=JSON.stringify(opts);let f=NF.get(key);if(!f)NF.set(key,(f=new Intl.NumberFormat(undefined,opts)));return f;}
|
|
32
|
+
function formatNumber(v,fmt,axis){if(typeof fmt==='function')return String(fmt(v));if(typeof v!=='number'||!isFinite(v))return v==null?'—':String(v);if(fmt&&typeof fmt==='object')return nf(fmt).format(v);return nf(axis&&Math.abs(v)>=1e4?{notation:'compact',maximumFractionDigits:1}:{maximumFractionDigits:2}).format(v);}
|
|
33
|
+
class Engine{constructor(canvas){this.canvas=canvas;this.ctx=canvas.getContext('2d');this.w=0;this.h=0;this.dpr=1;this._gradCache=new Map();this._gradCacheMax=32;this._snap=true;}
|
|
34
|
+
resize(w,h){const dpr=(typeof window!=='undefined'&&window.devicePixelRatio)||1;this.canvas.width=Math.round(w*dpr);this.canvas.height=Math.round(h*dpr);this.ctx.setTransform(dpr,0,0,dpr,0,0);this.w=w;this.h=h;this.dpr=dpr;this._font=null;this._gradCache.clear();}
|
|
35
|
+
clear(color){if(color){this.ctx.fillStyle=color;this.ctx.fillRect(0,0,this.w,this.h);}else{this.ctx.clearRect(0,0,this.w,this.h);}}
|
|
36
|
+
rect(x,y,w,h,opts={}){if(!(w>0)||!(h>0))return;const ctx=this.ctx;if(opts.radius&&opts.radius>0){const r=Math.max(0,Math.min(opts.radius,w/2,h/2));ctx.beginPath();ctx.moveTo(x+r,y);ctx.arcTo(x+w,y,x+w,y+h,r);ctx.arcTo(x+w,y+h,x,y+h,r);ctx.arcTo(x,y+h,x,y,r);ctx.arcTo(x,y,x+w,y,r);ctx.closePath();}else{ctx.beginPath();ctx.rect(x,y,w,h);}
|
|
37
|
+
if(opts.fill){ctx.fillStyle=opts.fill;ctx.fill();}
|
|
38
|
+
if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1;ctx.stroke();}}
|
|
39
|
+
rects(items,fill){const ctx=this.ctx;ctx.fillStyle=fill;ctx.beginPath();for(const r of items){if(!(r.w>0)||!(r.h>0))continue;if(r.radius&&r.radius>0){const rad=Math.max(0,Math.min(r.radius,r.w/2,r.h/2));ctx.moveTo(r.x+rad,r.y);ctx.arcTo(r.x+r.w,r.y,r.x+r.w,r.y+r.h,rad);ctx.arcTo(r.x+r.w,r.y+r.h,r.x,r.y+r.h,rad);ctx.arcTo(r.x,r.y+r.h,r.x,r.y,rad);ctx.arcTo(r.x,r.y,r.x+r.w,r.y,rad);ctx.closePath();}else{ctx.rect(r.x,r.y,r.w,r.h);}}
|
|
40
|
+
ctx.fill();}
|
|
41
|
+
circle(x,y,r,opts={}){if(!(r>0))return;const ctx=this.ctx;ctx.beginPath();ctx.arc(x,y,r,0,TAU);if(opts.fill){ctx.fillStyle=opts.fill;ctx.fill();}
|
|
42
|
+
if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1;ctx.stroke();}}
|
|
43
|
+
circles(items,fill,fixedRadius){const ctx=this.ctx;ctx.fillStyle=fill;ctx.beginPath();for(const c of items){const r=fixedRadius!=null?fixedRadius:c.r;if(!(r>0))continue;ctx.moveTo(c.x+r,c.y);ctx.arc(c.x,c.y,r,0,TAU);}
|
|
44
|
+
ctx.fill();}
|
|
45
|
+
poly(points,opts={}){if(points.length<2)return;const ctx=this.ctx;const snap=this._snap&&opts.lineWidth===1;const adj=snap?0.5:0;ctx.beginPath();ctx.moveTo(points[0].x+adj,points[0].y+adj);for(let i=1;i<points.length;i++){ctx.lineTo(points[i].x+adj,points[i].y+adj);}
|
|
46
|
+
if(opts.close)ctx.closePath();if(opts.fill){ctx.fillStyle=opts.fill;ctx.fill();}
|
|
47
|
+
if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1.5;ctx.lineCap=opts.cap||'butt';ctx.lineJoin=opts.join||'miter';if(opts.dash)ctx.setLineDash(opts.dash);ctx.stroke();if(opts.dash)ctx.setLineDash([]);}}
|
|
48
|
+
smooth(points,cps,opts={}){if(points.length<2)return;const ctx=this.ctx;ctx.beginPath();ctx.moveTo(points[0].x,points[0].y);for(let i=0;i<cps.length;i++){const c=cps[i];const p=points[i+1];ctx.bezierCurveTo(c.c1x,c.c1y,c.c2x,c.c2y,p.x,p.y);}
|
|
49
|
+
if(opts.fill){ctx.fillStyle=opts.fill;ctx.fill();}
|
|
50
|
+
if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||2;ctx.lineCap=opts.cap||'round';ctx.lineJoin=opts.join||'round';ctx.stroke();}}
|
|
51
|
+
smoothArea(points,cps,y0,fill){if(points.length<2)return;const ctx=this.ctx;ctx.beginPath();ctx.moveTo(points[0].x,y0);ctx.lineTo(points[0].x,points[0].y);for(let i=0;i<cps.length;i++){const c=cps[i];const p=points[i+1];ctx.bezierCurveTo(c.c1x,c.c1y,c.c2x,c.c2y,p.x,p.y);}
|
|
52
|
+
ctx.lineTo(points[points.length-1].x,y0);ctx.closePath();ctx.fillStyle=fill;ctx.fill();}
|
|
53
|
+
step(points,mode,opts={}){if(points.length<2)return;const ctx=this.ctx;ctx.beginPath();ctx.moveTo(points[0].x,points[0].y);for(let i=1;i<points.length;i++){const a=points[i-1],b=points[i];if(mode==='after'){ctx.lineTo(b.x,a.y);ctx.lineTo(b.x,b.y);}else if(mode==='middle'){const mx=(a.x+b.x)/2;ctx.lineTo(mx,a.y);ctx.lineTo(mx,b.y);ctx.lineTo(b.x,b.y);}else{ctx.lineTo(a.x,b.y);ctx.lineTo(b.x,b.y);}}
|
|
54
|
+
if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1.5;ctx.stroke();}}
|
|
55
|
+
_setFont(font){font=font||'11px ui-monospace, monospace';if(this._font!==font){this.ctx.font=font;this._font=font;}}
|
|
56
|
+
text(str,x,y,opts={}){const ctx=this.ctx;this._setFont(opts.font);ctx.fillStyle=opts.fill||'#000';ctx.textAlign=opts.align||'left';ctx.textBaseline=opts.baseline||'alphabetic';if(opts.rotate){ctx.save();ctx.translate(x,y);ctx.rotate(opts.rotate);ctx.fillText(str,0,0);ctx.restore();}else{ctx.fillText(str,x,y);}}
|
|
57
|
+
measure(str,font){this._setFont(font);return this.ctx.measureText(str).width;}
|
|
58
|
+
arc(cx,cy,rIn,rOut,a0,a1,opts={}){const ri=Math.max(0,rIn);const ro=Math.max(0,rOut);if(ro<=0)return;const ctx=this.ctx;ctx.beginPath();ctx.arc(cx,cy,ro,a0,a1);ctx.arc(cx,cy,ri,a1,a0,true);ctx.closePath();if(opts.fill){ctx.fillStyle=opts.fill;ctx.fill();}
|
|
59
|
+
if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1;ctx.stroke();}}
|
|
60
|
+
vGradient(key,x0,y0,y1,stops){if(this._gradCache.has(key))return this._gradCache.get(key);const g=this.ctx.createLinearGradient(x0,y0,x0,y1);for(const[pos,color]of stops)g.addColorStop(pos,color);if(this._gradCache.size>=this._gradCacheMax){const first=this._gradCache.keys().next().value;this._gradCache.delete(first);}
|
|
61
|
+
this._gradCache.set(key,g);return g;}
|
|
62
|
+
clip(x,y,w,h){const ctx=this.ctx;ctx.save();ctx.beginPath();ctx.rect(x,y,w,h);ctx.clip();}
|
|
63
|
+
unclip(){this.ctx.restore();this._font=null;}
|
|
64
|
+
withAlpha(a,fn){const ctx=this.ctx;const prev=ctx.globalAlpha;ctx.globalAlpha=prev*a;fn();ctx.globalAlpha=prev;}}
|
|
65
|
+
const M={TAU,clamp,lerp,extent,niceTicks,niceDomain,timeNiceTicks,timeFormat,minMaxDecimate,minMaxDecimateIdx,shouldDecimate,bezierControlPoints,springStep,colorParse,colorAlpha,colorMix,PALETTE,formatNumber};const charts=new Set();let rafId=null;function tick(now){let active=false;for(const c of charts){try{if(c._tick(now))active=true;}catch(e){console.error(e);}}
|
|
66
|
+
rafId=charts.size>0&&active?requestAnimationFrame(tick):null;}
|
|
67
|
+
function schedule(chart){if(chart._destroyed)return;charts.add(chart);if(rafId===null)rafId=requestAnimationFrame(tick);}
|
|
68
|
+
const mq=(q)=>(typeof matchMedia==='function'?matchMedia(q):null);const reducedMotion=mq('(prefers-reduced-motion: reduce)');const darkScheme=mq('(prefers-color-scheme: dark)');darkScheme?.addEventListener?.('change',()=>{for(const c of charts){c._theme();schedule(c);}});const TOKENS=['bg','surface','text','muted','grid','axis','accent','font','fontSize','lineWidth','pointRadius','barRadius','tooltipBg','tooltipFg'];const cssVar=(k)=>'--aeriz-'+k.replace(/[A-Z]/g,(c)=>'-'+c.toLowerCase());function backgroundOf(el){for(let n=el;n&&n.nodeType===1;n=n.parentElement){const c=getComputedStyle(n).backgroundColor;if(c&&M.colorParse(c)[3]>0.5)return c;}
|
|
69
|
+
return'#ffffff';}
|
|
70
|
+
function resolveTheme(el){const cs=getComputedStyle(el);const v={};for(const k of TOKENS)v[k]=cs.getPropertyValue(cssVar(k)).trim();const text=v.text||cs.color||'#222';const num=(s,d)=>(s&&!isNaN(parseFloat(s))?parseFloat(s):d);return{bg:v.bg||backgroundOf(el),surface:v.surface||'transparent',text,muted:v.muted||M.colorAlpha(text,0.62),grid:v.grid||M.colorAlpha(text,0.09),axis:v.axis||M.colorAlpha(text,0.3),accent:v.accent||'#64c8ff',font:v.font||cs.fontFamily||'system-ui, sans-serif',fontSize:num(v.fontSize,11),lineWidth:num(v.lineWidth,2),pointRadius:num(v.pointRadius,3),barRadius:num(v.barRadius,3),tooltipBg:v.tooltipBg||'rgba(20,22,30,0.94)',tooltipFg:v.tooltipFg||'#fff',};}
|
|
71
|
+
function readPalette(el){const cs=getComputedStyle(el);const out=[];for(let i=1;i<=12;i++){const v=(cs.getPropertyValue(`--aeriz-color-${i}`)||'').trim();if(v)out.push(v);}
|
|
72
|
+
return out.length?out:M.PALETTE;}
|
|
73
|
+
function div(parent,css,tag='div'){const el=document.createElement(tag);if(css)el.style.cssText=css;parent.appendChild(el);return el;}
|
|
74
|
+
function tipText(content){if(typeof content==='string')return content;return[content.title,...content.rows.map((r)=>`${r.name ? r.name + ' ' : ''}${r.value == null ? '—' : r.value}`)].filter(Boolean).join(', ');}
|
|
75
|
+
class Tooltip{constructor(parent){this.parent=parent;this.el=div(parent,'position:absolute;left:0;top:0;pointer-events:none;padding:6px 10px;border-radius:4px;'+'font:inherit;font-size:11px;line-height:1.5;white-space:nowrap;opacity:0;transition:opacity .1s;'+'z-index:20;box-shadow:0 4px 16px rgba(0,0,0,.3)');this.el.setAttribute('aria-hidden','true');}
|
|
76
|
+
show(x,y,content,theme){const el=this.el;el.style.background=theme.tooltipBg;el.style.color=theme.tooltipFg;if(typeof content==='string'){el.textContent=content;}else{el.textContent='';if(content.title)div(el,'opacity:.7;font-size:10px;margin-bottom:4px').textContent=content.title;for(const r of content.rows){const row=div(el,'display:flex;align-items:center;gap:6px;line-height:1.4');if(r.color)div(row,`width:8px;height:8px;border-radius:2px;background:${r.color}`,'span');div(row,'opacity:.8;flex:1','span').textContent=r.name||'';div(row,'font-weight:700;margin-left:12px','span').textContent=r.value==null?'—':String(r.value);}}
|
|
77
|
+
const w=el.offsetWidth,h=el.offsetHeight;const pw=this.parent.clientWidth;const left=Math.max(0,Math.min(pw-w,x-w/2));const top=y-h-8<0?y+12:y-h-8;el.style.transform=`translate(${Math.round(left)}px,${Math.round(top)}px)`;el.style.opacity='1';}
|
|
78
|
+
hide(){this.el.style.opacity='0';}
|
|
79
|
+
destroy(){this.el.remove();}}
|
|
80
|
+
class Overlay{constructor(parent){this.el=div(parent,'position:absolute;inset:0;display:none;align-items:center;justify-content:center;flex-direction:column;'+'gap:8px;font:inherit;font-size:12px;pointer-events:none;z-index:15');}
|
|
81
|
+
show(kind,msg,theme){const el=this.el;el.style.display='flex';el.style.background=M.colorAlpha(theme.bg,0.85);el.style.color=theme.text;el.textContent='';el.setAttribute('role',kind==='error'?'alert':'status');const icon=div(el,'font-size:20px;opacity:.6;line-height:1');if(kind==='loading'){icon.innerHTML=`<svg width="22" height="22" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-dasharray="14 50"><animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="1s" repeatCount="indefinite"/></circle></svg>`;icon.style.color=theme.accent;}else{icon.textContent=kind==='error'?'!':'◌';if(kind==='error')icon.style.color='#ef4444';}
|
|
82
|
+
if(msg)div(el,'opacity:.85').textContent=msg;}
|
|
83
|
+
hide(){this.el.style.display='none';this.el.removeAttribute('role');}
|
|
84
|
+
destroy(){this.el.remove();}}
|
|
85
|
+
class HitIndex{constructor(){this.regions=[];}
|
|
86
|
+
clear(){this.regions.length=0;}
|
|
87
|
+
add(r){this.regions.push(r);}
|
|
88
|
+
find(x,y){for(let i=this.regions.length-1;i>=0;i--){const r=this.regions[i];if(r.test?r.test(x,y):x>=r.x&&x<=r.x+r.w&&y>=r.y&&y<=r.y+r.h)return r;}
|
|
89
|
+
return null;}}
|
|
90
|
+
const isList=(d)=>Array.isArray(d)||ArrayBuffer.isView(d);const isObj=(d)=>d!=null&&typeof d==='object'&&!isList(d);function toRows(data){if(isList(data)){if(!data.length||Array.isArray(data[0]))return[];return typeof data[0]==='number'?Array.from(data,(v,i)=>({label:String(i+1),value:v})):data;}
|
|
91
|
+
return isObj(data)?Object.keys(data).map((k)=>({label:k,value:+data[k]})):[];}
|
|
92
|
+
const said=new Set();function warn(msg){if(said.has(msg))return;said.add(msg);console.warn('[aeriz] '+msg);}
|
|
93
|
+
const COMMON=['data','title','colors','format','legend','animate','emptyText','onClick','onLegend'];const SHAPES={rows:'an array of numbers, an object like { Jan: 64 }, or rows like [{ label, value }]',series:'{ categories: [...], series: [{ name, values: [...] }] }',stream:'{ series: [{ name }] } (values arrive with chart.push())',points:'points like [{ x, y }]',matrix:'{ x: [...], y: [...], values: [[...], ...] } with values[y][x]',boxes:'rows like [{ label, min, q1, median, q3, max }]',steps:'rows like [{ label, value }] (add total: true for a total bar)',number:'a number',};const DATA='data: { categories, series }';const RENAMED={ariaLabel:'title',palette:'colors',color:'colors',xTickFormat:'labelFormat (category axis) or format (value axis)',yTickFormat:'format',yLeftTickFormat:'format on the series',yRightTickFormat:'format on the series',categories:DATA,series:DATA,layers:DATA,axes:DATA,xLabels:'data: { x, y, values }',yLabels:'data: { x, y, values }',interpolation:'curve',windowSize:'window',yMin:'min',yMax:'max',waitingText:'emptyText',colorLow:'colors: { low, high }',colorHigh:'colors: { low, high }',colorUp:'colors: { up, down, total }',colorDown:'colors: { up, down, total }',colorTotal:'colors: { up, down, total }',minRadius:'radius: [min, max]',maxRadius:'radius: [min, max]',centerLabel:'center',totalLabel:'center',type:'the chart class itself',};const FUNCS=['label','value','labelFormat','onClick','onLegend'];function distance(a,b){const d=Array.from({length:b.length+1},(_,j)=>j);for(let i=1;i<=a.length;i++){let p=d[0];d[0]=i;for(let j=1;j<=b.length;j++){const t=d[j];d[j]=Math.min(d[j]+1,d[j-1]+1,p+(a[i-1]===b[j-1]?0:1));p=t;}}
|
|
94
|
+
return d[b.length];}
|
|
95
|
+
function checkOptions(meta,o){const name=meta.name;const allowed=[...COMMON,...meta.options];for(const k of Object.keys(o)){const v=o[k];if(!allowed.includes(k)){const hint=meta.renamed?.[k]||RENAMED[k];if(hint){warn(`${name}: "${k}" is a 1.x option. In 2.0 use ${hint}.`);continue;}
|
|
96
|
+
let best=null,bd=3;for(const a of allowed){const dd=a.toLowerCase()===k.toLowerCase()?0:distance(k,a);if(dd<bd){bd=dd;best=a;}}
|
|
97
|
+
warn(`${name}: unknown option "${k}"${best ? ` — did you mean "${best}"?` : `. Options: ${allowed.join(', ')}`}`);continue;}
|
|
98
|
+
if(v==null)continue;if(FUNCS.includes(k)&&typeof v!=='function')warn(`${name}: "${k}" must be a function${k === 'label' || k === 'value' ? `, like ${k}: (row) => row.${k === 'label' ? 'name' : 'amount'}` : ''}.`);const en=meta.enums?.[k];if(en&&!en.includes(v))warn(`${name}: ${k} must be ${en.map((x) => `'${x}'`).join(' | ')}, got ${JSON.stringify(v)}.`);if(k==='format'&&typeof v!=='function'&&!isObj(v))warn(`${name}: format must be Intl.NumberFormat options like { style: 'percent' } or a function (v) => string.`);if(k==='colors'&&(meta.colors?!isObj(v):!Array.isArray(v)))warn(`${name}: colors must be ${meta.colors || 'an array like [\'#64c8ff\', \'#10b981\']'}.`);}
|
|
99
|
+
if('data'in o)checkData(meta,o.data,o);}
|
|
100
|
+
function checkData(meta,d,o){if(d==null)return;const kind=meta.data;const bad=(why)=>warn(`${meta.name}: data must be ${SHAPES[kind]}${why ? ` — ${why}` : ''}.`);const first=isList(d)?d[0]:undefined;switch(kind){case'rows':if(isList(d)){if(Array.isArray(first))return bad(`pairs like ['Jan', 64] were removed in 2.0`);if(first!=null&&typeof first!=='number'&&!isObj(first))return bad(`got ${typeof first} items`);if(isObj(first)&&!o.value&&!('value'in first))warn(`${meta.name}: rows need a "value" field, or pass value: (row) => row.amount.`);if(isObj(first)&&!o.label&&!('label'in first))warn(`${meta.name}: rows need a "label" field, or pass label: (row) => row.name.`);}else if(!isObj(d))bad(`got ${typeof d}`);return;case'series':case'stream':{if(!isObj(d)||!Array.isArray(d.series)||(kind==='series'&&!isList(d.categories)))return bad(isList(d)?'got an array':'');const n=kind==='series'?d.categories.length:0;d.series.forEach((s,i)=>{const who=`series[${i}]${s?.name ? ` "${s.name}"` : ''}`;if(kind==='stream')return;if(!s||!isList(s.values))return warn(`${meta.name}: ${who} needs values: [...].`);if(s.values.length!==n)warn(`${meta.name}: ${who} has ${s.values.length} values but there are ${n} categories.`);});return;}
|
|
101
|
+
case'matrix':if(!isObj(d)||!isList(d.x)||!isList(d.y)||!Array.isArray(d.values))return bad(isList(d)?'got an array':'');if(d.values.length!==d.y.length)warn(`${meta.name}: values has ${d.values.length} rows but y has ${d.y.length} labels.`);return;case'number':if(typeof d!=='number')bad(`got ${typeof d}`);return;default:{if(!Array.isArray(d))return bad(`got ${isObj(d) ? 'an object' : typeof d}`);if(!first)return;const need={points:['x','y'],boxes:['q1','median','q3'],steps:['value']}[kind];const miss=need.filter((k)=>!(k in first));if(miss.length)bad(`the first item has no ${miss.join(', ')}`);if(kind==='steps'&&'isTotal'in first)warn(`${meta.name}: "isTotal" is 1.x. In 2.0 use total: true.`);if(kind==='points'&&('r'in first||'z'in first))warn(`${meta.name}: point "${'r' in first ? 'r' : 'z'}" is 1.x. In 2.0 use size.`);}}}
|
|
102
|
+
const STATES=['loading','empty','error'];class Chart{static get meta(){return null;}
|
|
103
|
+
constructor(container,options={}){const meta=this.constructor.meta;const el=typeof container==='string'?document.querySelector(container):container;if(!el)throw new Error(`aeriz: ${meta ? meta.name + ': ' : ''}container not found${typeof container === 'string' ? ': ' + container : ''}`);this.container=el;this.options={...options};if(meta)checkOptions(meta,this.options);this._restore={position:el.style.position,height:el.style.height};el.classList.add('aeriz');if(getComputedStyle(el).position==='static')el.style.position='relative';if(!el.style.height&&el.offsetHeight===0)el.style.height='320px';this.wrap=div(el,'display:flex;flex-direction:column;width:100%;height:100%');this.legendEl=div(this.wrap,'display:none;flex:none;flex-wrap:wrap;gap:2px 12px;padding:0 0 6px;font:inherit;font-size:11px');this.plot=div(this.wrap,'position:relative;flex:1 1 auto;min-height:0');this.canvas=document.createElement('canvas');this.canvas.style.cssText='position:absolute;inset:0;display:block;width:100%;height:100%;touch-action:pan-y;outline:none';this.canvas.tabIndex=0;this.canvas.setAttribute('role','img');this.plot.appendChild(this.canvas);this.live=div(this.plot,'position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap');this.live.setAttribute('aria-live','polite');this.engine=new Engine(this.canvas);this.tooltip=new Tooltip(this.plot);this.overlay=new Overlay(this.plot);this.hits=new HitIndex();this._off=new Set();this._legendKey='';this._cursor=null;this._hover=null;this._dirty=true;this._destroyed=false;this._visible=typeof IntersectionObserver==='undefined';this._anim={progress:0,start:0,duration:600};this._state=null;this._auto=false;this._theme();this._label();this._setupEvents();this._observe();this._resize();if(options.animate===false||reducedMotion?.matches)this._anim.progress=1;schedule(this);}
|
|
104
|
+
update(patch={}){const meta=this.constructor.meta;if(meta)checkOptions(meta,patch);Object.assign(this.options,patch);this._cache=null;if('colors'in patch)this._theme();if(this._auto){this._state=null;this._auto=false;}
|
|
105
|
+
this._label();this._restart();return this;}
|
|
106
|
+
setState(state,text){if(state!=null&&!STATES.includes(state)){throw new Error(`aeriz: setState() takes 'loading', 'empty', 'error' or null — got ${JSON.stringify(state)}`);}
|
|
107
|
+
this._state=state||null;this._stateMsg=text??(state==='empty'?this.options.emptyText||'No data':state==='error'?'Error':'');this._auto=false;this._dirty=true;schedule(this);return this;}
|
|
108
|
+
render(){if(this._destroyed)return this;this._theme();this._anim.progress=1;this._resize();this._dirty=false;this._draw();return this;}
|
|
109
|
+
destroy(){if(this._destroyed)return;this._destroyed=true;charts.delete(this);this._ro?.disconnect();this._io?.disconnect();this._removeEvents();this.wrap.remove();const el=this.container;el.classList.remove('aeriz');el.style.position=this._restore.position;el.style.height=this._restore.height;}
|
|
110
|
+
_theme(){const o=this.options;this.theme=resolveTheme(this.container);this.palette=Array.isArray(o.colors)&&o.colors.length?o.colors:readPalette(this.container);this.engine._gradCache.clear();this._legendKey='';this._dirty=true;}
|
|
111
|
+
_empty(msg=this.options.emptyText||'No data'){this._state='empty';this._stateMsg=msg;this._auto=true;this.overlay.show('empty',msg,this.theme);}
|
|
112
|
+
_restart(){const instant=this.options.animate===false||reducedMotion?.matches;this._anim.start=0;this._anim.progress=instant?1:0;this._dirty=true;schedule(this);}
|
|
113
|
+
_label(){this.canvas.setAttribute('aria-label',this.options.title||this.constructor.meta?.aria||'Chart');}
|
|
114
|
+
_fmt(v){return M.formatNumber(v,this.options.format);}
|
|
115
|
+
_tickFmt(v){return M.formatNumber(v,this.options.format,true);}
|
|
116
|
+
_lab(l,time){const f=this._fn('labelFormat');return f?String(f(l)):time?new Date(+l).toLocaleString():String(l);}
|
|
117
|
+
_xFmt(v){const f=this._fn('labelFormat');return f?String(f(v)):this._fmt(v);}
|
|
118
|
+
_isOff(i){return this._off.has(i);}
|
|
119
|
+
_fn(k){const f=this.options[k];return typeof f==='function'?f:null;}
|
|
120
|
+
_hit(h){if(!h)return null;const out={index:h.index,label:h.label};if('value'in h)out.value=h.value;if('series'in h)out.series=h.series;if('row'in h)out.row=h.row;return out;}
|
|
121
|
+
_click(h){const hit=this._hit(h);if(hit)this.options.onClick?.(hit,this);}
|
|
122
|
+
_resize(){const w=this.plot.clientWidth;const h=this.plot.clientHeight;if(w===this.engine.w&&h===this.engine.h)return false;this.engine.resize(Math.max(50,w),Math.max(50,h));this._dirty=true;return true;}
|
|
123
|
+
_observe(){if(typeof ResizeObserver!=='undefined'){this._ro=new ResizeObserver(()=>{if(this._resize())schedule(this);});this._ro.observe(this.plot);}
|
|
124
|
+
if(typeof IntersectionObserver!=='undefined'){this._io=new IntersectionObserver((entries)=>{this._visible=entries[entries.length-1].isIntersecting;if(this._visible&&this._dirty)schedule(this);});this._io.observe(this.container);}}
|
|
125
|
+
_setupEvents(){const c=this.canvas;this._handlers={pointermove:(e)=>this._onMove(e),pointerdown:(e)=>{if(e.pointerType!=='mouse')this._onMove(e);},pointerleave:(e)=>{if(e.pointerType==='mouse')this._onLeave();},click:(e)=>this._onClick(e),keydown:(e)=>this._onKey(e),blur:()=>this._onLeave(),};for(const k in this._handlers)c.addEventListener(k,this._handlers[k]);this._legendClick=(e)=>{const b=e.target.closest('button[data-i]');if(b)this._toggle(+b.dataset.i);};this.legendEl.addEventListener('click',this._legendClick);}
|
|
126
|
+
_removeEvents(){for(const k in this._handlers)this.canvas.removeEventListener(k,this._handlers[k]);this.legendEl.removeEventListener('click',this._legendClick);}
|
|
127
|
+
_toggle(i){const items=this._legend()||[];if(this._off.has(i))this._off.delete(i);else if(items.length-this._off.size>1)this._off.add(i);this._legendKey='';this.options.onLegend?.(i,!this._off.has(i),this);this._restart();}
|
|
128
|
+
_ptr(e){const r=this.canvas.getBoundingClientRect();return{x:e.clientX-r.left,y:e.clientY-r.top};}
|
|
129
|
+
_setHover(hit,p){const changed=(hit?._id??null)!==(this._hover?._id??null);this._hover=hit;if(changed){if(hit&&hit.tooltip){this.tooltip.show(hit.tipX??hit.x+hit.w/2,hit.tipY??hit.y,hit.tooltip,this.theme);}else if(!this._follow){this.tooltip.hide();}}
|
|
130
|
+
if(changed||this._follow){this._dirty=true;schedule(this);}
|
|
131
|
+
if(typeof this._onMoveX==='function')this._onMoveX(p);return changed;}
|
|
132
|
+
_onMove(e){const p=this._ptr(e);this._cursor=p;this._setHover(this.hits.find(p.x,p.y),p);}
|
|
133
|
+
_onLeave(){this._cursor=null;this._hover=null;this._key=-1;this.tooltip.hide();this._dirty=true;schedule(this);}
|
|
134
|
+
_onClick(e){const p=this._ptr(e);this._click(this.hits.find(p.x,p.y)||(this._follow?this._hover:null));}
|
|
135
|
+
_onKey(e){const k=e.key;const fwd=k==='ArrowRight'||k==='ArrowDown';const back=k==='ArrowLeft'||k==='ArrowUp';if(k==='Escape')return this._onLeave();if(k==='Enter'&&this._hover)return this._click(this._hover);if(!fwd&&!back&&k!=='Home'&&k!=='End')return;const steps=this._steps?.length?this._steps.map((x)=>({x,y:this.engine.h/2})):this.hits.regions.filter((r)=>r.tooltip);if(!steps.length)return;e.preventDefault();const n=steps.length;const cur=this._key??-1;this._key=k==='Home'?0:k==='End'?n-1:cur<0?(fwd?0:n-1):(cur+(fwd?1:n-1))%n;const s=steps[this._key];if(this._steps?.length){this._cursor={x:s.x,y:s.y};this._setHover(null,this._cursor);this._dirty=true;this._tick(performance.now());this.live.textContent=this._tipNow?tipText(this._tipNow):'';}else{this._cursor={x:s.tipX??s.x+s.w/2,y:s.tipY??s.y};this._setHover(s,this._cursor);this.live.textContent=tipText(s.tooltip);}}
|
|
136
|
+
_tick(now){if(this._destroyed)return false;if(!this._visible){if(this._anim.progress<1)this._anim.start=0;return false;}
|
|
137
|
+
let active=false;const a=this._anim;if(a.progress<1){if(!a.start)a.start=now;const t=Math.min(1,(now-a.start)/a.duration);a.progress=t===1?1:1-Math.pow(1-t,3);this._dirty=true;if(t<1)active=true;}
|
|
138
|
+
if(this._dirty){this._dirty=false;this._draw();}
|
|
139
|
+
return active;}
|
|
140
|
+
_draw(){const e=this.engine;this._renderLegend();this._resize();e.clear();this.hits.clear();this._steps=null;if(this.theme.surface&&this.theme.surface!=='transparent'){e.rect(0,0,e.w,e.h,{fill:this.theme.surface});}
|
|
141
|
+
if(this._state&&!this._auto){this.overlay.show(this._state,this._stateMsg,this.theme);return;}
|
|
142
|
+
this._state=null;this._auto=false;this.overlay.hide();if(typeof this._render==='function')this._render();}
|
|
143
|
+
_legend(){return null;}
|
|
144
|
+
_renderLegend(){const items=this.options.legend===false?null:this._legend();const key=items?items.map((it,i)=>`${it.label}|${it.color}|${this._off.has(i)}`).join(','):'';if(key===this._legendKey)return;this._legendKey=key;const el=this.legendEl;el.textContent='';el.style.display=items&&items.length?'flex':'none';if(!items)return;el.style.color=this.theme.text;items.forEach((it,i)=>{const off=this._off.has(i);const b=div(el,'display:inline-flex;align-items:center;gap:6px;padding:2px 0;border:0;background:none;color:inherit;'+`font:inherit;letter-spacing:normal;text-transform:none;border-radius:0;box-shadow:none;min-width:0;min-height:0;cursor:pointer;opacity:${off ? 0.4 : 1}`,'button');b.type='button';b.dataset.i=i;b.setAttribute('aria-pressed',String(!off));div(b,`width:10px;height:10px;border-radius:2px;background:${it.color}`,'span');b.appendChild(document.createTextNode(it.label));});}
|
|
145
|
+
_seriesLegend(series){return series.map((s,i)=>({label:s.name||'Series '+(i+1),color:this._colorOf(i,s)}));}
|
|
146
|
+
_colorOf(idx,item){if(item&&item.color)return item.color;return this.palette[idx%this.palette.length];}}
|
|
147
|
+
const GONE={setData:'update({ data })',setLoading:"setState('loading', text)",setEmpty:"setState('empty', text)",setError:"setState('error', text)",clearState:'setState(null)',invalidate:'render()',setValue:'update({ data: value })',};for(const k in GONE){Chart.prototype[k]=function(){throw new Error(`aeriz 2.0: chart.${k}() was removed. Use chart.${GONE[k]}.`);};}
|
|
148
|
+
const DTF=new Map();function dateFmt(opts){const k=JSON.stringify(opts);let f=DTF.get(k);if(!f)DTF.set(k,(f=new Intl.DateTimeFormat(undefined,opts)));return f;}
|
|
149
|
+
function localTimeFormat(step){const o=step<6e4?{hour:'2-digit',minute:'2-digit',second:'2-digit',hour12:false}:step<864e5?{hour:'2-digit',minute:'2-digit',hour12:false}:step<2.5e9?{month:'short',day:'numeric'}:step<3e10?{year:'2-digit',month:'short'}:{year:'numeric'};const f=dateFmt(o);return(t)=>f.format(t);}
|
|
150
|
+
function drawCartesian(chart,spec){const e=chart.engine;const t=chart.theme;const font=`${t.fontSize}px ${t.font}`;const yMin=spec.yMin!=null?spec.yMin:0;const yMax=spec.yMax;const yScale=spec.yScale||'linear';let yTicks,yNorm;if(yScale==='log'){const lo=Math.max(1e-10,spec.yMin||1);const lLo=Math.log10(lo),lHi=Math.log10(Math.max(lo*10,yMax));yNorm=(v)=>(Math.log10(Math.max(lo,v))-lLo)/(lHi-lLo);yTicks=[];for(let p=Math.floor(lLo);p<=Math.ceil(lHi);p++){const v=Math.pow(10,p);if(v>=lo&&v<=yMax)yTicks.push(v);}}else{yTicks=M.niceTicks(yMin,yMax,5);yNorm=(v)=>(v-yMin)/(yMax-yMin||1);}
|
|
151
|
+
const yFmt=spec.yFormat?(v)=>M.formatNumber(v,spec.yFormat,true):(v)=>chart._tickFmt(v);const yText=yTicks.map((v)=>yFmt(v));let maxW=0;for(const s of yText)maxW=Math.max(maxW,e.measure(s,font));const right=spec.right;let rTicks=[],rText=[],rNorm=null,rMaxW=0;if(right){rTicks=M.niceTicks(right.yMin,right.yMax,5);rNorm=(v)=>(v-right.yMin)/(right.yMax-right.yMin||1);const rf=right.yFormat?(v)=>M.formatNumber(v,right.yFormat,true):(v)=>chart._tickFmt(v);rText=rTicks.map((v)=>rf(v));for(const s of rText)rMaxW=Math.max(rMaxW,e.measure(s,font));}
|
|
152
|
+
const p=spec.pad||{};const pad={l:p.l!=null?p.l:Math.ceil(maxW)+12,r:p.r!=null?p.r:right?Math.ceil(rMaxW)+12:16,t:p.t!=null?p.t:12,b:p.b!=null?p.b:t.fontSize+16,};const px=pad.l,py=pad.t;const pw=Math.max(1,e.w-pad.l-pad.r);const ph=Math.max(1,e.h-pad.t-pad.b);const yToPx=(v)=>py+ph*(1-yNorm(v));const yRToPx=rNorm?(v)=>py+ph*(1-rNorm(v)):null;const ctx=e.ctx;ctx.strokeStyle=t.grid;ctx.lineWidth=1;ctx.beginPath();for(const v of yTicks){const y=Math.round(yToPx(v))+0.5;ctx.moveTo(px,y);ctx.lineTo(px+pw,y);}
|
|
153
|
+
ctx.stroke();yTicks.forEach((v,i)=>e.text(yText[i],px-6,yToPx(v),{fill:t.muted,font,align:'right',baseline:'middle'}));rTicks.forEach((v,i)=>e.text(rText[i],px+pw+6,yRToPx(v),{fill:t.muted,font,align:'left',baseline:'middle'}));let xToPx,xTicks,xFmt;const lf=chart._fn('labelFormat');if(spec.xTime){const{min,max}=spec.xTime;xToPx=(v)=>px+(pw*(v-min))/(max-min||1);xTicks=M.timeNiceTicks(min,max,Math.max(2,Math.floor(pw/90)));const step=xTicks.length>1?xTicks[1]-xTicks[0]:max-min;xFmt=lf?(v)=>String(lf(v)):localTimeFormat(step);}else if(spec.xLinear){const{min,max}=spec.xLinear;xToPx=(v)=>px+(pw*(v-min))/(max-min||1);xTicks=M.niceTicks(min,max,6);xFmt=lf?(v)=>String(lf(v)):(v)=>chart._tickFmt(v);}else if(spec.xLabels){const n=spec.xLabels.length||1;xToPx=(i)=>px+(i+0.5)*(pw/n);xFmt=(i)=>chart._lab(spec.xLabels[i]);const guess=Math.max(e.measure(xFmt(0),font),e.measure(xFmt(n-1),font))+6;const k=Math.max(1,Math.ceil((n*guess)/pw));xTicks=[];for(let i=0;i<n;i+=k)xTicks.push(i);}
|
|
154
|
+
if(xToPx){let lastEnd=-Infinity;for(const v of xTicks){const txt=xFmt(v);const w=e.measure(txt,font);const x=Math.max(w/2,Math.min(e.w-w/2,xToPx(v)));if(x-w/2<lastEnd+6)continue;lastEnd=x+w/2;e.text(txt,x,py+ph+6,{fill:t.muted,font,align:'center',baseline:'top'});}}
|
|
155
|
+
e.poly([{x:px,y:py+ph},{x:px+pw,y:py+ph}],{stroke:t.axis,lineWidth:1});return{px,py,pw,ph,yToPx,yRToPx,xToPx,yTicks,xTicks,yMin,yMax};}
|
|
156
|
+
function range(arrays,lo=Infinity,hi=-Infinity){for(const arr of arrays){for(let i=0;i<arr.length;i++){const v=+arr[i];if(v<lo)lo=v;if(v>hi)hi=v;}}
|
|
157
|
+
if(lo===Infinity)return[0,1];return[lo,hi];}
|
|
158
|
+
function valueDomain(o,lo,hi){const[a,b]=M.niceDomain(o.min??lo,o.max??hi,5);return[o.min??a,o.max??b];}
|
|
159
|
+
const AXIS=['labelFormat','min','max'];class BarChart extends Chart{static get meta(){return{id:'bar',name:'BarChart',aria:'Bar chart',data:'rows',options:['label','value','labelFormat','min','max','dataLabels']};}
|
|
160
|
+
_render(){const data=toRows(this.options.data);if(!data.length)return this._empty();const e=this.engine;const t=this.theme;const o=this.options;const labelOf=this._fn('label')||((d)=>d.label);const valueOf=this._fn('value')||((d)=>d.value);const values=data.map((d,i)=>+valueOf(d,i)||0);const labels=data.map((d,i)=>labelOf(d,i));const[lo,hi]=range([values],0,0);const[yLo,yHi]=valueDomain(o,lo,hi);const dl=o.dataLabels;const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLabels:labels,pad:dl?{t:t.fontSize+10}:undefined,});const slot=ax.pw/data.length;const barW=Math.max(1,Math.min(60,slot*0.8,slot-2));const prog=this._anim.progress;const y0=ax.yToPx(Math.max(yLo,0));const groups=new Map();const font=`${t.fontSize}px ${t.font}`;for(let i=0;i<data.length;i++){const v=values[i];const x=ax.px+(i+0.5)*slot-barW/2;const h=Math.abs(ax.yToPx(v)-y0)*prog;const top=v>=0?y0-h:y0;const color=this._colorOf(i,data[i]);if(!groups.has(color))groups.set(color,[]);groups.get(color).push({x,y:top,w:barW,h,radius:t.barRadius});const label=this._lab(labels[i]);this.hits.add({_id:i,x,y:Math.min(top,y0-2),w:barW,h:Math.max(h,4),tipX:x+barW/2,tipY:top,tooltip:`${label}: ${this._fmt(v)}`,index:i,label:labels[i],value:v,row:data[i],});if(dl&&prog>0.95){const txt=typeof dl==='function'?dl(v,data[i],i):this._fmt(v);e.text(txt,x+barW/2,v>=0?top-4:top+h+4,{fill:t.text,font,align:'center',baseline:v>=0?'bottom':'top',});}}
|
|
161
|
+
for(const[color,items]of groups)e.rects(items,color);if(this._hover){const r=this._hover;e.rect(r.x,r.y,r.w,r.h,{stroke:t.text,lineWidth:1.5});}}}
|
|
162
|
+
class HBarChart extends Chart{static get meta(){return{id:'hbar',name:'HBarChart',aria:'Horizontal bar chart',data:'rows',options:['label','value','labelFormat','min','max']};}
|
|
163
|
+
_render(){const data=toRows(this.options.data);if(!data.length)return this._empty();const e=this.engine;const t=this.theme;const o=this.options;const labelOf=this._fn('label')||((d)=>d.label);const valueOf=this._fn('value')||((d)=>d.value);const values=data.map((d,i)=>+valueOf(d,i)||0);const raw=data.map((d,i)=>labelOf(d,i));const labels=raw.map((l)=>this._lab(l));const[lo,hi]=range([values],0,0);const[xLo,xHi]=valueDomain(o,lo,hi);const font=`${t.fontSize}px ${t.font}`;let labelW=0;for(const s of labels)labelW=Math.max(labelW,e.measure(s,font));const px=Math.min(Math.ceil(labelW)+12,e.w*0.4),py=12;const pw=Math.max(1,e.w-px-20);const ph=Math.max(1,e.h-py-t.fontSize-16);const xTicks=M.niceTicks(xLo,xHi,Math.max(2,Math.min(6,Math.floor(pw/70))));const xToPx=(v)=>px+(pw*(v-xLo))/(xHi-xLo||1);e.ctx.strokeStyle=t.grid;e.ctx.lineWidth=1;e.ctx.beginPath();for(const v of xTicks){const x=Math.round(xToPx(v))+0.5;e.ctx.moveTo(x,py);e.ctx.lineTo(x,py+ph);}
|
|
164
|
+
e.ctx.stroke();for(const v of xTicks){e.text(this._tickFmt(v),xToPx(v),py+ph+6,{fill:t.muted,font,align:'center',baseline:'top'});}
|
|
165
|
+
e.poly([{x:px,y:py+ph},{x:px+pw,y:py+ph}],{stroke:t.axis,lineWidth:1});const slot=ph/data.length;const barH=Math.max(1,Math.min(40,slot*0.75,slot-2));const prog=this._anim.progress;const x0=xToPx(Math.max(xLo,0));const groups=new Map();for(let i=0;i<data.length;i++){const v=values[i];const yMid=py+(i+0.5)*slot;const y=yMid-barH/2;const w=Math.abs(xToPx(v)-x0)*prog;const left=v>=0?x0:x0-w;const color=this._colorOf(i,data[i]);if(!groups.has(color))groups.set(color,[]);groups.get(color).push({x:left,y,w,h:barH,radius:t.barRadius});this.hits.add({_id:i,x:Math.min(left,x0-2),y,w:Math.max(w,4),h:barH,tipX:left+w/2,tipY:y,tooltip:`${labels[i]}: ${this._fmt(v)}`,index:i,label:raw[i],value:v,row:data[i],});e.text(labels[i],px-6,yMid,{fill:t.muted,font,align:'right',baseline:'middle'});}
|
|
166
|
+
for(const[color,items]of groups)e.rects(items,color);if(this._hover){const r=this._hover;e.rect(r.x,r.y,r.w,r.h,{stroke:t.text,lineWidth:1.5});}}}
|
|
167
|
+
const LABEL=(d)=>d.label;const VALUE=(d)=>d.value;class LineChart extends Chart{static get meta(){return{id:'line',name:'LineChart',aria:'Line chart',data:'rows',options:['label','value','labelFormat','min','max','curve','fill','points','xScale','yScale'],enums:{curve:['smooth','linear','step'],xScale:['category','time'],yScale:['linear','log']},};}
|
|
168
|
+
_render(){const o=this.options;const labelOf=this._fn('label')||LABEL;const valueOf=this._fn('value')||VALUE;let c=this._cache;if(!c||c.data!==o.data||c.valueOf!==valueOf||c.labelOf!==labelOf){const d=o.data;const nums=d&&typeof d[0]==='number'&&(Array.isArray(d)||ArrayBuffer.isView(d))&&!this._fn('value')&&!this._fn('label');const rows=nums?d:toRows(d);const values=nums?Float64Array.from(d):new Float64Array(rows.length);if(!nums)for(let i=0;i<rows.length;i++)values[i]=+valueOf(rows[i],i);const labels=nums?Array.from({length:d.length},(_,i)=>i+1):null;c=this._cache={data:o.data,rows,valueOf,labelOf,values,idx:null,idxW:0,xs:null,labels,range:null};}
|
|
169
|
+
const data=c.rows;if(!data.length)return this._empty();const e=this.engine;const t=this.theme;const n=data.length;const values=c.values;const log=o.yScale==='log';const[lo,hi]=(c.range||=range([values],log?Infinity:0));const[yLo,yHi]=log?[o.min??lo,o.max??hi]:valueDomain(o,lo,hi);const useTime=o.xScale==='time';const spec={yMin:yLo,yMax:yHi,yScale:o.yScale};const xs=useTime?(c.xs||=Array.from(data,(d,i)=>+labelOf(d,i))):null;if(useTime)spec.xTime={min:xs[0],max:xs[n-1]};else spec.xLabels=c.labels||=Array.from(data,(d,i)=>labelOf(d,i));const ax=drawCartesian(this,spec);const color=this.palette[0];const interp=o.curve||'smooth';const lineW=t.lineWidth;const prog=this._anim.progress;const xAt=useTime?(i)=>ax.xToPx(xs[i]):(i)=>ax.px+((i+0.5)/n)*ax.pw;const decimated=M.shouldDecimate(n,ax.pw);if(decimated&&(!c.idx||c.idxW!==Math.floor(ax.pw))){c.idxW=Math.floor(ax.pw);c.idx=M.minMaxDecimateIdx(n,c.idxW,(i)=>values[i]);}
|
|
170
|
+
const idx=decimated?c.idx:null;const count=idx?idx.length:n;const points=new Array(count);for(let k=0;k<count;k++){const i=idx?idx[k]:k;points[k]={x:xAt(i),y:ax.yToPx(values[i]),i};}
|
|
171
|
+
e.clip(ax.px-lineW,ax.py-lineW,(ax.pw+lineW*2)*prog,ax.ph+lineW*2);const smooth=interp==='smooth'&&!decimated;const base=ax.py+ax.ph;if(interp==='smooth'||interp==='linear'){const cps=smooth?M.bezierControlPoints(points):null;if(o.fill!==false){const grad=e.vGradient(`area-${color}`,ax.px,ax.py,base,[[0,M.colorAlpha(color,0.25)],[1,M.colorAlpha(color,0)],]);if(cps)e.smoothArea(points,cps,base,grad);else e.poly(points.concat([{x:points[count-1].x,y:base},{x:points[0].x,y:base}]),{fill:grad});}
|
|
172
|
+
if(cps)e.smooth(points,cps,{stroke:color,lineWidth:lineW});else e.poly(points,{stroke:color,lineWidth:lineW,cap:'round',join:'round'});}else{e.step(points,'before',{stroke:color,lineWidth:lineW});}
|
|
173
|
+
e.unclip();const labelAt=(i)=>(useTime?xs[i]:c.labels[i]);const tip=(i)=>`${this._lab(labelAt(i), useTime)}: ${this._fmt(values[i])}`;if(!decimated&&n<=50){if(o.points!==false)e.circles(points,color,t.pointRadius);for(const p of points){this.hits.add({_id:p.i,x:p.x-8,y:p.y-8,w:16,h:16,tipX:p.x,tipY:p.y-t.pointRadius,tooltip:tip(p.i),index:p.i,label:labelAt(p.i),value:values[p.i],row:data[p.i],});}}else{for(let k=0;k<count;k++){const p=points[k];const l=k?(points[k-1].x+p.x)/2:ax.px;const r=k<count-1?(points[k+1].x+p.x)/2:ax.px+ax.pw;if(r<=l&&k<count-1)continue;this.hits.add({_id:p.i,x:l,y:ax.py,w:Math.max(1,r-l),h:ax.ph,tipX:p.x,tipY:p.y-8,tooltip:tip(p.i),index:p.i,label:labelAt(p.i),value:values[p.i],row:data[p.i],});}}
|
|
174
|
+
if(this._hover){const p=points.find((pp)=>pp.i===this._hover._id);if(p)e.circle(p.x,p.y,t.pointRadius+3,{stroke:color,lineWidth:2,fill:t.bg});}}}
|
|
175
|
+
class AreaChart extends Chart{static get meta(){return{id:'area',name:'AreaChart',aria:'Area chart',data:'series',options:['labelFormat','min','max','stacked']};}
|
|
176
|
+
_legend(){return this._seriesLegend(this.options.data?.series||[]);}
|
|
177
|
+
_render(){const o=this.options;const{categories:cats=[],series=[]}=o.data||{};if(!series.length||!cats.length)return this._empty();const e=this.engine;const t=this.theme;const stacked=o.stacked===true;const on=series.map((s,i)=>i).filter((i)=>!this._isOff(i));let yMin=0,yMax;if(stacked){yMax=0;for(let i=0;i<cats.length;i++){let sum=0;for(const s of on)sum+=+series[s].values[i]||0;if(sum>yMax)yMax=sum;}}else{[yMin,yMax]=range(on.map((s)=>series[s].values),0,0);}
|
|
178
|
+
const[yLo,yHi]=valueDomain(o,yMin,yMax);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLabels:cats});const lineW=t.lineWidth;const prog=this._anim.progress;const xAt=(i)=>ax.px+((i+0.5)/cats.length)*ax.pw;const ctx=e.ctx;e.clip(ax.px,ax.py-lineW,ax.pw*prog,ax.ph+lineW*2);const cum=new Array(cats.length).fill(0);for(const s of on){const ser=series[s];const color=this._colorOf(s,ser);const top=[],bot=[];for(let i=0;i<cats.length;i++){const v=+ser.values[i]||0;bot.push({x:xAt(i),y:ax.yToPx(stacked?cum[i]:Math.max(yLo,0))});top.push({x:xAt(i),y:ax.yToPx(stacked?cum[i]+v:v)});if(stacked)cum[i]+=v;}
|
|
179
|
+
const cps=M.bezierControlPoints(top);ctx.beginPath();ctx.moveTo(top[0].x,top[0].y);for(let i=0;i<cps.length;i++){ctx.bezierCurveTo(cps[i].c1x,cps[i].c1y,cps[i].c2x,cps[i].c2y,top[i+1].x,top[i+1].y);}
|
|
180
|
+
for(let i=bot.length-1;i>=0;i--)ctx.lineTo(bot[i].x,bot[i].y);ctx.closePath();ctx.fillStyle=stacked?M.colorAlpha(color,0.7):e.vGradient(`area-${s}-${color}`,ax.px,ax.py,ax.py+ax.ph,[[0,M.colorAlpha(color,0.4)],[1,M.colorAlpha(color,0)]]);ctx.fill();e.smooth(top,cps,{stroke:color,lineWidth:lineW});}
|
|
181
|
+
e.unclip();const slot=ax.pw/cats.length;for(let i=0;i<cats.length;i++){this.hits.add({_id:i,x:ax.px+i*slot,y:ax.py,w:slot,h:ax.ph,tipX:xAt(i),tipY:ax.py+8,index:i,label:cats[i],tooltip:{title:this._lab(cats[i]),rows:on.map((s)=>({color:this._colorOf(s,series[s]),name:series[s].name||'Series '+(s+1),value:this._fmt(+series[s].values[i])})),},});}
|
|
182
|
+
if(this._hover){const x=xAt(this._hover._id);e.poly([{x,y:ax.py},{x,y:ax.py+ax.ph}],{stroke:t.muted,lineWidth:1,dash:[3,3]});}}}
|
|
183
|
+
class StackedBarChart extends Chart{static get meta(){return{id:'stacked-bar',name:'StackedBarChart',aria:'Stacked bar chart',data:'series',options:['labelFormat','min','max']};}
|
|
184
|
+
_legend(){return this._seriesLegend(this.options.data?.series||[]);}
|
|
185
|
+
_render(){const o=this.options;const{categories:cats=[],series=[]}=o.data||{};if(!series.length||!cats.length)return this._empty();const e=this.engine;const t=this.theme;const on=series.map((s,i)=>i).filter((i)=>!this._isOff(i));let lo=0,hi=0;for(let i=0;i<cats.length;i++){let pos=0,neg=0;for(const s of on){const v=+series[s].values[i]||0;if(v>=0)pos+=v;else neg+=v;}
|
|
186
|
+
if(pos>hi)hi=pos;if(neg<lo)lo=neg;}
|
|
187
|
+
const[yLo,yHi]=valueDomain(o,lo,hi);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLabels:cats});const slot=ax.pw/cats.length;const barW=Math.max(1,Math.min(50,slot*0.8,slot-2));const prog=this._anim.progress;const groups=new Map();const y0=ax.yToPx(0);const lastPos=[],lastNeg=[];for(let i=0;i<cats.length;i++){let pos=0,neg=0;const x=ax.px+(i+0.5)*slot-barW/2;for(const s of on){const v=+series[s].values[i]||0;if(!v)continue;const from=v>0?pos:neg;const to=from+v;if(v>0)pos=to;else neg=to;const ya=y0+(ax.yToPx(from)-y0)*prog;const yb=y0+(ax.yToPx(to)-y0)*prog;const y=Math.min(ya,yb),h=Math.abs(yb-ya);const color=this._colorOf(s,series[s]);if(!groups.has(color))groups.set(color,[]);const item={x,y,w:barW,h,radius:0};groups.get(color).push(item);(v>0?lastPos:lastNeg)[i]=item;this.hits.add({_id:`${i}-${s}`,x,y,w:barW,h,tipX:x+barW/2,tipY:y,index:i,label:cats[i],series:s,value:v,row:series[s],tooltip:{title:this._lab(cats[i]),rows:[{color,name:series[s].name||'Series '+(s+1),value:this._fmt(v)}]},});}}
|
|
188
|
+
for(const it of lastPos)if(it)it.radius=t.barRadius;for(const it of lastNeg)if(it)it.radius=t.barRadius;for(const[color,items]of groups)e.rects(items,color);if(this._hover){const r=this._hover;e.rect(r.x,r.y,r.w,r.h,{stroke:t.text,lineWidth:1.5});}}}
|
|
189
|
+
class GroupedBarChart extends Chart{static get meta(){return{id:'grouped-bar',name:'GroupedBarChart',aria:'Grouped bar chart',data:'series',options:['labelFormat','min','max']};}
|
|
190
|
+
_legend(){return this._seriesLegend(this.options.data?.series||[]);}
|
|
191
|
+
_render(){const o=this.options;const{categories:cats=[],series=[]}=o.data||{};if(!series.length||!cats.length)return this._empty();const e=this.engine;const t=this.theme;const on=series.map((s,i)=>i).filter((i)=>!this._isOff(i));const[lo,hi]=range(on.map((s)=>series[s].values),0,0);const[yLo,yHi]=valueDomain(o,lo,hi);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLabels:cats});const slot=ax.pw/cats.length;const inner=slot*0.8;const barW=Math.max(1,inner/on.length);const prog=this._anim.progress;const y0=ax.yToPx(0);const groups=new Map();for(let i=0;i<cats.length;i++){const left=ax.px+(i+0.5)*slot-inner/2;on.forEach((s,k)=>{const v=+series[s].values[i]||0;const x=left+k*barW;const h=Math.abs(ax.yToPx(v)-y0)*prog;const y=v>=0?y0-h:y0;const color=this._colorOf(s,series[s]);if(!groups.has(color))groups.set(color,[]);const w=Math.max(1,barW-(barW>4?1:0));groups.get(color).push({x,y,w,h,radius:t.barRadius});this.hits.add({_id:`${i}-${s}`,x,y:Math.min(y,y0-2),w,h:Math.max(h,4),tipX:x+barW/2,tipY:y,index:i,label:cats[i],series:s,value:v,row:series[s],tooltip:{title:this._lab(cats[i]),rows:[{color,name:series[s].name||'Series '+(s+1),value:this._fmt(v)}]},});});}
|
|
192
|
+
for(const[color,items]of groups)e.rects(items,color);if(this._hover){const r=this._hover;e.rect(r.x,r.y,r.w,r.h,{stroke:t.text,lineWidth:1.5});}}}
|
|
193
|
+
class MultiLineChart extends Chart{static get meta(){return{id:'multi-line',name:'MultiLineChart',aria:'Multi line chart',data:'series',options:['labelFormat','min','max','curve','xScale'],enums:{curve:['smooth','linear','step'],xScale:['category','time']}};}
|
|
194
|
+
constructor(container,options){super(container,options);this._follow=true;}
|
|
195
|
+
_legend(){return this._seriesLegend(this.options.data?.series||[]);}
|
|
196
|
+
_render(){const o=this.options;const{categories:cats=[],series=[]}=o.data||{};if(!series.length||!cats.length)return this._empty();const e=this.engine;const t=this.theme;const interp=o.curve||'smooth';const on=series.map((s,i)=>i).filter((i)=>!this._isOff(i));const[lo,hi]=range(on.map((s)=>series[s].values),0);const[yLo,yHi]=valueDomain(o,lo,hi);const useTime=o.xScale==='time';const spec={yMin:yLo,yMax:yHi};if(useTime)spec.xTime={min:+cats[0],max:+cats[cats.length-1]};else spec.xLabels=cats;const ax=drawCartesian(this,spec);const lineW=t.lineWidth;const prog=this._anim.progress;const xAt=useTime?(i)=>ax.xToPx(+cats[i]):(i)=>ax.px+((i+0.5)/cats.length)*ax.pw;this._steps=cats.map((_,i)=>xAt(i));e.clip(ax.px-lineW,ax.py-lineW,(ax.pw+lineW*2)*prog,ax.ph+lineW*2);const all=[];for(const s of on){const ser=series[s];const color=this._colorOf(s,ser);const pts=ser.values.map((v,i)=>({x:xAt(i),y:ax.yToPx(+v)}));all.push({pts,color,s,ser});if(interp==='smooth')e.smooth(pts,M.bezierControlPoints(pts),{stroke:color,lineWidth:lineW});else if(interp==='linear')e.poly(pts,{stroke:color,lineWidth:lineW,cap:'round',join:'round'});else e.step(pts,'before',{stroke:color,lineWidth:lineW});if(pts.length<=50)e.circles(pts,color,t.pointRadius);}
|
|
197
|
+
e.unclip();this._tipNow=null;const cx=this._cursor?.x;if(cx!=null&&cx>=ax.px-4&&cx<=ax.px+ax.pw+4&&all.length){let idx=0,best=Infinity;for(let i=0;i<cats.length;i++){const d=Math.abs(this._steps[i]-cx);if(d<best){best=d;idx=i;}}
|
|
198
|
+
const xPx=this._steps[idx];e.poly([{x:xPx,y:ax.py},{x:xPx,y:ax.py+ax.ph}],{stroke:t.muted,lineWidth:1,dash:[3,3]});const rows=[];for(const sp of all){const p=sp.pts[idx];if(!p)continue;e.circle(p.x,p.y,t.pointRadius+2,{fill:t.bg,stroke:sp.color,lineWidth:2});rows.push({color:sp.color,name:sp.ser.name||'Series '+(sp.s+1),value:this._fmt(+sp.ser.values[idx])});}
|
|
199
|
+
const title=this._lab(cats[idx],useTime);this._tipNow={title,rows};this._hover={_id:idx,index:idx,label:cats[idx]};this.tooltip.show(xPx,ax.py+8,this._tipNow,t);}else if(this._cursor){this.tooltip.hide();}}}
|
|
200
|
+
function xyExtent(data){let xMin=Infinity,xMax=-Infinity,yMin=Infinity,yMax=-Infinity;for(const d of data){if(d.x<xMin)xMin=d.x;if(d.x>xMax)xMax=d.x;if(d.y<yMin)yMin=d.y;if(d.y>yMax)yMax=d.y;}
|
|
201
|
+
return[xMin,xMax,yMin,yMax];}
|
|
202
|
+
class ScatterChart extends Chart{static get meta(){return{id:'scatter',name:'ScatterChart',aria:'Scatter chart',data:'points',options:['labelFormat','min','max']};}
|
|
203
|
+
_render(){const o=this.options;const data=o.data||[];if(!data.length)return this._empty();const e=this.engine;const t=this.theme;const[xMin,xMax,yMin,yMax]=xyExtent(data);const[xLo,xHi]=M.niceDomain(xMin,xMax,6);const[yLo,yHi]=valueDomain(o,yMin,yMax);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLinear:{min:xLo,max:xHi},});const prog=this._anim.progress;const groups=new Map();const defaultR=t.pointRadius+1;const hitsOn=data.length<=20000;for(let i=0;i<data.length;i++){const d=data[i];const cx=ax.xToPx(d.x);const cy=ax.yToPx(d.y);const r=(d.size||defaultR)*prog;const color=d.color||this._colorOf(d.group!=null?d.group:0);const key=color+':'+r.toFixed(1);if(!groups.has(key))groups.set(key,{color,r,items:[]});groups.get(key).items.push({x:cx,y:cy});if(hitsOn){this.hits.add({_id:i,x:cx-r-2,y:cy-r-2,w:r*2+4,h:r*2+4,tipX:cx,tipY:cy-r,index:i,label:d.label,value:d.y,row:d,tooltip:`${d.label ? d.label + ' ' : ''}(${this._xFmt(d.x)}, ${this._fmt(d.y)})`,});}}
|
|
204
|
+
e.withAlpha(0.7,()=>{for(const{color,r,items}of groups.values())e.circles(items,color,r);});if(this._hover){const h=this._hover;e.circle(h.tipX,h.y+h.h/2,h.w/2,{stroke:t.text,lineWidth:1.5});}}}
|
|
205
|
+
class BubbleChart extends Chart{static get meta(){return{id:'bubble',name:'BubbleChart',aria:'Bubble chart',data:'points',options:['labelFormat','min','max','radius']};}
|
|
206
|
+
_render(){const o=this.options;const data=o.data||[];if(!data.length)return this._empty();const e=this.engine;const t=this.theme;const[xMin,xMax,yMin,yMax]=xyExtent(data);let zMax=0;for(const d of data)if((d.size||0)>zMax)zMax=d.size;const px=(xMax-xMin||1)*0.08,py=(yMax-yMin||1)*0.1;const[xLo,xHi]=M.niceDomain(xMin-px,xMax+px,6);const[yLo,yHi]=valueDomain(o,yMin-py,yMax+py);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLinear:{min:xLo,max:xHi},});const prog=this._anim.progress;const[minR,maxR]=Array.isArray(o.radius)?o.radius:[4,32];for(let i=0;i<data.length;i++){const d=data[i];const cx=ax.xToPx(d.x);const cy=ax.yToPx(d.y);const r=(minR+Math.sqrt((d.size||0)/Math.max(1e-9,zMax))*(maxR-minR))*prog;const color=d.color||this._colorOf(d.group!=null?d.group:i);const hover=this._hover&&this._hover._id===i;e.circle(cx,cy,r,{fill:M.colorAlpha(color,hover?0.75:0.5),stroke:color,lineWidth:1.5});this.hits.add({_id:i,x:cx-r,y:cy-r,w:r*2,h:r*2,test:(x,y)=>(x-cx)**2+(y-cy)**2<=r*r,tipX:cx,tipY:cy-r,index:i,label:d.label,value:d.y,row:d,tooltip:`${d.label ? d.label + ' ' : ''}(${this._xFmt(d.x)}, ${this._fmt(d.y)}) · ${this._fmt(d.size)}`,});}}}
|
|
207
|
+
function inSector(cx,cy,rIn,rOut,a0,a1){return(x,y)=>{const dx=x-cx,dy=y-cy;const d=dx*dx+dy*dy;if(d<rIn*rIn||d>rOut*rOut)return false;let a=Math.atan2(dy,dx);while(a<a0)a+=TAU;return a<=a1;};}
|
|
208
|
+
class PieChart extends Chart{static get meta(){return{id:'pie',name:'PieChart',aria:'Pie chart',data:'rows',options:['label','value','innerRadius','center']};}
|
|
209
|
+
_legend(){const o=this.options;const labelOf=this._fn('label')||((d)=>d.label);return toRows(o.data).map((d,i)=>({label:String(labelOf(d,i)),color:this._colorOf(i,d)}));}
|
|
210
|
+
_render(){const o=this.options;const data=toRows(o.data);if(!data.length)return this._empty();const e=this.engine;const t=this.theme;const labelOf=this._fn('label')||((d)=>d.label);const valueOf=this._fn('value')||((d)=>d.value);const raw=data.map((d,i)=>+valueOf(d,i)||0);const neg=raw.findIndex((v)=>v<0);if(neg>=0)warn(`PieChart: values can't be negative — "${labelOf(data[neg], neg)}" is ${raw[neg]}. It is drawn as 0.`);const vals=raw.map((v,i)=>(this._isOff(i)?0:Math.max(0,v)));const total=vals.reduce((a,v)=>a+v,0);if(total<=0)return this._empty();const cx=e.w/2,cy=e.h/2;const R=Math.max(4,Math.min(e.w,e.h)/2-12);const ir=+o.innerRadius||0;if(ir>0.9)warn(`PieChart: innerRadius is a ratio from 0 to 0.9 (0.6 = donut) — got ${ir}.`);const innerR=R*Math.min(0.9,Math.max(0,ir));const prog=this._anim.progress;let a0=-Math.PI/2;for(let i=0;i<data.length;i++){const v=vals[i];if(!v)continue;const a1=a0+(v/total)*TAU*prog;const color=this._colorOf(i,data[i]);const hover=this._hover&&this._hover._id===i;const mid=(a0+a1)/2;const off=hover?6:0;e.arc(cx+Math.cos(mid)*off,cy+Math.sin(mid)*off,innerR,R,a0,a1,{fill:color,stroke:data.length>1?t.bg:null,lineWidth:1.5,});const midR=(innerR+R)/2;this.hits.add({_id:i,x:cx-R,y:cy-R,w:R*2,h:R*2,test:inSector(cx,cy,innerR,R+off,a0,a1),tipX:cx+Math.cos(mid)*midR,tipY:cy+Math.sin(mid)*midR-8,index:i,label:labelOf(data[i],i),value:v,row:data[i],tooltip:`${this._lab(labelOf(data[i], i))}: ${this._fmt(v)} (${((v / total) * 100).toFixed(1)}%)`,});a0=a1;}
|
|
211
|
+
if(innerR>0&&o.center!==false){const h=this._hover;const center=h?`${((vals[h._id] / total) * 100).toFixed(0)}%`:o.center??this._fmt(total);e.text(center,cx,cy,{fill:t.text,font:`600 ${Math.max(12, Math.min(t.fontSize * 1.8, innerR / 2.2))}px ${t.font}`,align:'center',baseline:'middle',});}}}
|
|
212
|
+
class RadarChart extends Chart{static get meta(){return{id:'radar',name:'RadarChart',aria:'Radar chart',data:'series',options:['labelFormat','max']};}
|
|
213
|
+
_legend(){return this._seriesLegend(this.options.data?.series||[]);}
|
|
214
|
+
_render(){const o=this.options;const{categories:axes=[],series=[]}=o.data||{};if(!series.length||!axes.length)return this._empty();const e=this.engine;const t=this.theme;const font=`${t.fontSize}px ${t.font}`;let labelW=0;const names=axes.map((a)=>this._lab(a));for(const a of names)labelW=Math.max(labelW,e.measure(a,font));const cx=e.w/2;const cy=e.h/2;const R=Math.max(10,Math.min(e.w/2-labelW-18,e.h/2-t.fontSize-16));const N=axes.length;const angle=(i)=>-Math.PI/2+(i/N)*TAU;const on=series.map((s,i)=>i).filter((i)=>!this._isOff(i));const max=range(on.map((s)=>series[s].values),0)[1];const niceMax=o.max??(M.niceDomain(0,max,5)[1]||1);const ctx=e.ctx;ctx.strokeStyle=t.grid;ctx.lineWidth=1;for(const v of M.niceTicks(0,niceMax,4)){if(v<=0)continue;const r=(v/niceMax)*R;ctx.beginPath();for(let i=0;i<=N;i++){const a=angle(i%N);if(i===0)ctx.moveTo(cx+Math.cos(a)*r,cy+Math.sin(a)*r);else ctx.lineTo(cx+Math.cos(a)*r,cy+Math.sin(a)*r);}
|
|
215
|
+
ctx.stroke();}
|
|
216
|
+
ctx.beginPath();for(let i=0;i<N;i++){const a=angle(i);ctx.moveTo(cx,cy);ctx.lineTo(cx+Math.cos(a)*R,cy+Math.sin(a)*R);}
|
|
217
|
+
ctx.stroke();for(let i=0;i<N;i++){const a=angle(i);e.text(names[i],cx+Math.cos(a)*(R+10),cy+Math.sin(a)*(R+10),{fill:t.muted,font,align:Math.cos(a)>0.3?'left':Math.cos(a)<-0.3?'right':'center',baseline:Math.sin(a)>0.3?'top':Math.sin(a)<-0.3?'bottom':'middle',});}
|
|
218
|
+
const prog=this._anim.progress;for(const s of on){const ser=series[s];const color=this._colorOf(s,ser);const pts=[];for(let i=0;i<N;i++){const v=+ser.values[i]||0;const r=Math.min(1,(v*prog)/niceMax)*R;const a=angle(i);const p={x:cx+Math.cos(a)*r,y:cy+Math.sin(a)*r};pts.push(p);this.hits.add({_id:`${s}-${i}`,x:p.x-7,y:p.y-7,w:14,h:14,tipX:p.x,tipY:p.y-4,index:i,label:axes[i],series:s,value:v,row:ser,tooltip:{title:names[i],rows:[{color,name:ser.name||'Series '+(s+1),value:this._fmt(v)}]},});}
|
|
219
|
+
e.poly(pts,{fill:M.colorAlpha(color,0.2),stroke:color,lineWidth:2,close:true,join:'round'});e.circles(pts,color,t.pointRadius);}
|
|
220
|
+
if(this._hover){const h=this._hover;e.circle(h.x+7,h.y+7,t.pointRadius+3,{stroke:t.text,lineWidth:1.5});}}}
|
|
221
|
+
class PolarChart extends Chart{static get meta(){return{id:'polar',name:'PolarChart',aria:'Polar chart',data:'rows',options:['label','value','scale'],enums:{scale:['sqrt','linear']}};}
|
|
222
|
+
_render(){const o=this.options;const data=toRows(o.data);if(!data.length)return this._empty();const e=this.engine;const t=this.theme;const labelOf=this._fn('label')||((d)=>d.label);const valueOf=this._fn('value')||((d)=>d.value);const N=data.length;const font=`${t.fontSize}px ${t.font}`;const cx=e.w/2,cy=e.h/2;const R=Math.max(10,Math.min(e.w,e.h)/2-t.fontSize-14);let max=0;for(let i=0;i<N;i++)max=Math.max(max,+valueOf(data[i],i)||0);const prog=this._anim.progress;const sqrt=o.scale!=='linear';e.circle(cx,cy,R,{stroke:t.grid,lineWidth:1});for(let i=0;i<N;i++){const v=Math.max(0,+valueOf(data[i],i)||0);const a0=-Math.PI/2+(i/N)*TAU;const a1=a0+(TAU/N)*0.92;const ratio=max?v/max:0;const r=(sqrt?Math.sqrt(ratio):ratio)*R*prog;const color=this._colorOf(i,data[i]);const hover=this._hover&&this._hover._id===i;e.arc(cx,cy,0,r+(hover?4:0),a0,a1,{fill:color});const mid=(a0+a1)/2;const c=Math.cos(mid),s=Math.sin(mid);e.text(String(labelOf(data[i],i)),cx+c*(R+8),cy+s*(R+8),{fill:t.muted,font,align:c>0.3?'left':c<-0.3?'right':'center',baseline:s>0.3?'top':s<-0.3?'bottom':'middle',});this.hits.add({_id:i,x:cx-R,y:cy-R,w:R*2,h:R*2,test:inSector(cx,cy,0,Math.max(r,12),a0,a1),tipX:cx+c*r*0.7,tipY:cy+s*r*0.7-6,index:i,label:labelOf(data[i],i),value:v,row:data[i],tooltip:`${labelOf(data[i], i)}: ${this._fmt(v)}`,});}}}
|
|
223
|
+
class GaugeChart extends Chart{static get meta(){return{id:'gauge',name:'GaugeChart',aria:'Gauge',data:'number',options:['min','max','unit'],renamed:{value:'data',label:'title'},};}
|
|
224
|
+
update(patch={}){if('data'in patch)this._from=this._shown;return super.update(patch);}
|
|
225
|
+
_render(){const e=this.engine;const t=this.theme;const o=this.options;const min=o.min??0;const max=o.max??100;const target=Math.max(min,Math.min(max,+o.data||0));const from=this._from??min;const val=from+(target-from)*this._anim.progress;this._shown=val;const cx=e.w/2;const cy=e.h*0.68;const R=Math.max(10,Math.min(e.w/2-8,e.h*0.62));const r=R*0.8;const a0=Math.PI*1.1;const a1=Math.PI*1.9;const color=this.palette[0];e.arc(cx,cy,r,R,a0,a1,{fill:t.grid});const aEnd=a0+((a1-a0)*(val-min))/(max-min||1);if(aEnd>a0)e.arc(cx,cy,r,R,a0,aEnd,{fill:color});const txt=(o.format?this._fmt(val):String(Math.round(val)))+(o.unit||'');e.text(txt,cx,cy-R*0.08,{fill:t.text,font:`700 ${Math.max(12, R / 2.6)}px ${t.font}`,align:'center',baseline:'middle',});if(o.title){e.text(o.title,cx,cy+R*0.22,{fill:t.muted,font:`${t.fontSize}px ${t.font}`,align:'center',baseline:'top'});}
|
|
226
|
+
this.canvas.setAttribute('aria-label',`${o.title || 'Gauge'}: ${txt}`);}}
|
|
227
|
+
class HeatmapChart extends Chart{static get meta(){return{id:'heatmap',name:'HeatmapChart',aria:'Heatmap',data:'matrix',options:['labelFormat'],colors:'an object like { low: \'#eef\', high: \'#36c\' }'};}
|
|
228
|
+
_render(){const o=this.options;const{x:xLabels=[],y:yLabels=[],values=[]}=o.data||{};if(!values.length||!xLabels.length||!yLabels.length)return this._empty();const e=this.engine;const t=this.theme;const font=`${t.fontSize}px ${t.font}`;let labelW=0;const yText=Array.from(yLabels,(l)=>this._lab(l));for(const s of yText)labelW=Math.max(labelW,e.measure(s,font));const px=Math.ceil(labelW)+12,py=8;const pw=Math.max(1,e.w-px-8);const ph=Math.max(1,e.h-py-t.fontSize-14);let vMax=-Infinity,vMin=Infinity;for(const row of values){for(const v of row||[]){if(v>vMax)vMax=v;if(v<vMin)vMin=v;}}
|
|
229
|
+
const cellW=pw/xLabels.length;const cellH=ph/yLabels.length;const c=o.colors||{};const hi=c.high||this.palette[0];const lo=c.low||M.colorAlpha(hi,0.08);const gap=cellW>6&&cellH>6?1:0;const prog=this._anim.progress;for(let cy=0;cy<yLabels.length;cy++){const row=values[cy]||[];for(let cx=0;cx<xLabels.length;cx++){const v=row[cx];if(v==null||v!==v)continue;const x=px+cx*cellW;const y=py+cy*cellH;const n=vMax>vMin?(v-vMin)/(vMax-vMin):0.5;e.rect(x+gap,y+gap,cellW-gap*2,cellH-gap*2,{fill:M.colorMix(lo,hi,n*prog),radius:gap?2:0});this.hits.add({_id:cy*xLabels.length+cx,x,y,w:cellW,h:cellH,tipX:x+cellW/2,tipY:y,index:cx,series:cy,label:xLabels[cx],value:v,row:{x:xLabels[cx],y:yLabels[cy],value:v},tooltip:`${this._lab(xLabels[cx])} · ${yText[cy]}: ${this._fmt(v)}`,});}}
|
|
230
|
+
let lastEnd=-Infinity;for(let i=0;i<xLabels.length;i++){const txt=this._lab(xLabels[i]);const w=e.measure(txt,font);const x=px+(i+0.5)*cellW;if(x-w/2<lastEnd+4)continue;lastEnd=x+w/2;e.text(txt,x,py+ph+6,{fill:t.muted,font,align:'center',baseline:'top'});}
|
|
231
|
+
const step=Math.ceil((t.fontSize+2)/cellH);for(let i=0;i<yLabels.length;i+=step){e.text(yText[i],px-6,py+(i+0.5)*cellH,{fill:t.muted,font,align:'right',baseline:'middle'});}
|
|
232
|
+
if(this._hover){const r=this._hover;e.rect(r.x+0.5,r.y+0.5,r.w-1,r.h-1,{stroke:t.text,lineWidth:1.5});}}}
|
|
233
|
+
class BoxplotChart extends Chart{static get meta(){return{id:'boxplot',name:'BoxplotChart',aria:'Box plot',data:'boxes',options:['labelFormat','min','max']};}
|
|
234
|
+
_render(){const o=this.options;const data=o.data||[];if(!data.length)return this._empty();const e=this.engine;let yMax=-Infinity,yMin=Infinity;for(const d of data){if(d.max>yMax)yMax=d.max;if(d.min<yMin)yMin=d.min;if(d.outliers)for(const v of d.outliers){if(v>yMax)yMax=v;if(v<yMin)yMin=v;}}
|
|
235
|
+
const[yLo,yHi]=valueDomain(o,yMin,yMax);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLabels:data.map((d)=>d.label)});const slot=ax.pw/data.length;const boxW=Math.min(40,slot*0.5);const prog=this._anim.progress;for(let i=0;i<data.length;i++){const d=data[i];const cx=ax.px+(i+0.5)*slot;const med=ax.yToPx(d.median);const y=(v)=>med+(ax.yToPx(v)-med)*prog;const color=this._colorOf(i,d);const hover=this._hover&&this._hover._id===i;e.poly([{x:cx,y:y(d.max)},{x:cx,y:y(d.min)}],{stroke:color,lineWidth:1});e.poly([{x:cx-boxW/3,y:y(d.max)},{x:cx+boxW/3,y:y(d.max)}],{stroke:color,lineWidth:1});e.poly([{x:cx-boxW/3,y:y(d.min)},{x:cx+boxW/3,y:y(d.min)}],{stroke:color,lineWidth:1});e.rect(cx-boxW/2,y(d.q3),boxW,y(d.q1)-y(d.q3),{fill:M.colorAlpha(color,hover?0.6:0.4),stroke:color,lineWidth:1.5,});e.poly([{x:cx-boxW/2,y:med},{x:cx+boxW/2,y:med}],{stroke:color,lineWidth:2});if(d.outliers)e.circles(d.outliers.map((v)=>({x:cx,y:y(v)})),color,2);const top=ax.yToPx(d.max),bot=ax.yToPx(d.min);this.hits.add({_id:i,x:cx-boxW/2,y:top,w:boxW,h:Math.max(4,bot-top),tipX:cx,tipY:ax.yToPx(d.q3),index:i,label:d.label,value:d.median,row:d,tooltip:{title:this._lab(d.label),rows:[['max',d.max],['Q3',d.q3],['median',d.median],['Q1',d.q1],['min',d.min]].map(([name,v])=>({name,value:this._fmt(v)})),},});}}}
|
|
236
|
+
class FunnelChart extends Chart{static get meta(){return{id:'funnel',name:'FunnelChart',aria:'Funnel chart',data:'rows',options:['label','value','labelColor']};}
|
|
237
|
+
_render(){const o=this.options;const data=toRows(o.data);if(!data.length)return this._empty();const e=this.engine;const t=this.theme;const labelOf=this._fn('label')||((d)=>d.label);const valueOf=this._fn('value')||((d)=>d.value);const N=data.length;const vals=data.map((d,i)=>Math.max(0,+valueOf(d,i)||0));const max=Math.max(1e-9,vals.reduce((a,v)=>(v>a?v:a),0));const first=vals[0]||max;const pad=12;const w=e.w-pad*2;const segH=(e.h-pad*2)/N;const cx=e.w/2;const prog=this._anim.progress;const font=`600 ${t.fontSize}px ${t.font}`;for(let i=0;i<N;i++){const v=vals[i];const wTop=(v/max)*w*prog;const wBot=i+1<N?(vals[i+1]/max)*w*prog:wTop*0.85;const yTop=pad+i*segH;const yBot=yTop+segH-2;const color=this._colorOf(i,data[i]);const hover=this._hover&&this._hover._id===i;e.withAlpha(hover?1:0.9,()=>e.poly([{x:cx-wTop/2,y:yTop},{x:cx+wTop/2,y:yTop},{x:cx+wBot/2,y:yBot},{x:cx-wBot/2,y:yBot},],{fill:color,close:true}));const txt=`${labelOf(data[i], i)} ${this._fmt(v)}`;const tw=e.measure(txt,font);const inside=tw+16<Math.min(wTop,wBot);e.text(txt,inside?cx:cx+Math.max(wTop,wBot)/2+8,yTop+segH/2,{fill:inside?o.labelColor||'#fff':t.text,font,align:inside?'center':'left',baseline:'middle',});this.hits.add({_id:i,x:cx-Math.max(wTop,8)/2,y:yTop,w:Math.max(wTop,8),h:segH,tipX:cx,tipY:yTop,index:i,label:labelOf(data[i],i),value:v,row:data[i],tooltip:`${labelOf(data[i], i)}: ${this._fmt(v)} (${((v / first) * 100).toFixed(1)}%)`,});}}}
|
|
238
|
+
function squarify(items,rect,place){let i=0;let{x,y,w,h}=rect;let rest=items.reduce((a,it)=>a+it.value,0);while(i<items.length&&rest>0&&w>=1&&h>=1){const side=Math.min(w,h);const scale=(w*h)/rest;let sum=0,best=Infinity,j=i;for(;j<items.length;j++){const s=sum+items[j].value;const len=(s*scale)/side;let worst=0;for(let k=i;k<=j;k++){const l=(items[k].value*scale)/len;worst=Math.max(worst,len/l,l/len);}
|
|
239
|
+
if(worst>best)break;best=worst;sum=s;}
|
|
240
|
+
if(j===i){sum=items[i].value;j=i+1;}
|
|
241
|
+
const thick=(sum*scale)/side;let pos=0;for(let k=i;k<j;k++){const l=(items[k].value*scale)/thick;place(items[k],w>=h?{x,y:y+pos,w:thick,h:l}:{x:x+pos,y,w:l,h:thick});pos+=l;}
|
|
242
|
+
if(w>=h){x+=thick;w-=thick;}else{y+=thick;h-=thick;}
|
|
243
|
+
rest-=sum;i=j;}}
|
|
244
|
+
class TreemapChart extends Chart{static get meta(){return{id:'treemap',name:'TreemapChart',aria:'Treemap',data:'rows',options:['label','value','labelColor']};}
|
|
245
|
+
_render(){const o=this.options;const data=toRows(o.data);if(!data.length)return this._empty();const e=this.engine;const t=this.theme;const labelOf=this._fn('label')||((d)=>d.label);const valueOf=this._fn('value')||((d)=>d.value);const items=data.map((d,i)=>({raw:labelOf(d,i),label:String(labelOf(d,i)),value:Math.max(0,+valueOf(d,i)||0),color:this._colorOf(i,d),data:d,_id:i})).filter((it)=>it.value>0).sort((a,b)=>b.value-a.value);if(!items.length)return this._empty();const prog=this._anim.progress;const fg=o.labelColor||'#fff';squarify(items,{x:0,y:0,w:e.w,h:e.h},(it,r)=>{const gap=1;const w=(r.w-gap*2)*prog;const h=(r.h-gap*2)*prog;const x=r.x+gap+(r.w-gap*2-w)/2;const y=r.y+gap+(r.h-gap*2-h)/2;if(w<1||h<1)return;const hover=this._hover&&this._hover._id===it._id;e.withAlpha(hover?0.85:1,()=>e.rect(x,y,w,h,{fill:it.color,radius:2}));if(w>48&&h>t.fontSize*2+12){e.text(it.label,x+6,y+6,{fill:fg,font:`600 ${t.fontSize}px ${t.font}`,align:'left',baseline:'top'});e.withAlpha(0.85,()=>e.text(this._fmt(it.value),x+6,y+8+t.fontSize,{fill:fg,font:`${t.fontSize}px ${t.font}`,align:'left',baseline:'top'}));}
|
|
246
|
+
this.hits.add({_id:it._id,x,y,w,h,tipX:x+w/2,tipY:y,index:it._id,label:it.raw,value:it.value,row:it.data,tooltip:`${it.label}: ${this._fmt(it.value)}`,});});}}
|
|
247
|
+
class SparklineChart extends Chart{static get meta(){return{id:'sparkline',name:'SparklineChart',aria:'Sparkline',data:'rows',options:['label','value','min','max','fill','lastDot']};}
|
|
248
|
+
_render(){const o=this.options;const data=toRows(o.data);if(!data.length)return;const e=this.engine;const t=this.theme;const valueOf=this._fn('value')||((d)=>d.value);const labelOf=this._fn('label')||((d)=>d.label);const values=data.map((d,i)=>+valueOf(d,i));const[lo,hi]=range([values]);const yMin=o.min??lo,yMax=o.max??hi;const yRange=yMax-yMin||1;const pad=Math.max(2,t.pointRadius+1);const w=e.w-pad*2;const h=e.h-pad*2;const points=values.map((v,i)=>({x:pad+(i/(values.length-1||1))*w,y:pad+h-((v-yMin)/yRange)*h,}));const color=this.palette[0];const cps=M.bezierControlPoints(points);e.clip(0,0,e.w*this._anim.progress,e.h);if(o.fill!==false){const grad=e.vGradient(`spark-${color}`,0,pad,pad+h,[[0,M.colorAlpha(color,0.3)],[1,M.colorAlpha(color,0)]]);e.smoothArea(points,cps,pad+h,grad);}
|
|
249
|
+
e.smooth(points,cps,{stroke:color,lineWidth:t.lineWidth});e.unclip();if(o.lastDot!==false){const last=points[points.length-1];e.circle(last.x,last.y,t.pointRadius+1,{fill:color});}
|
|
250
|
+
const n=points.length;points.forEach((p,i)=>{const l=i?(points[i-1].x+p.x)/2:0;const r=i<n-1?(points[i+1].x+p.x)/2:e.w;const label=labelOf(data[i],i);this.hits.add({_id:i,x:l,y:0,w:r-l,h:e.h,tipX:p.x,tipY:p.y-4,index:i,label,value:values[i],row:data[i],tooltip:`${label}: ${this._fmt(values[i])}`});});if(this._hover){const p=points[this._hover._id];if(p)e.circle(p.x,p.y,t.pointRadius+1,{fill:t.bg,stroke:color,lineWidth:2});}}}
|
|
251
|
+
class RealtimeChart extends Chart{static get meta(){return{id:'realtime',name:'RealtimeChart',aria:'Realtime chart',data:'stream',options:['min','max','window']};}
|
|
252
|
+
constructor(container,options={}){super(container,options);this._series=[];this._sync();this._follow=true;}
|
|
253
|
+
_sync(){const list=this.options.data?.series||[{name:'Series'}];this._series=list.map((s,i)=>({name:s.name||'Series '+(i+1),color:s.color,values:this._series[i]?.values||[]}));}
|
|
254
|
+
update(patch={}){super.update(patch);if('data'in patch)this._sync();return this;}
|
|
255
|
+
_legend(){return this._series.length>1?this._seriesLegend(this._series):null;}
|
|
256
|
+
push(values){const arr=Array.isArray(values)?values:[values];const N=this.options.window||200;for(let i=0;i<this._series.length;i++){const v=this._series[i].values;v.push(arr[i]!=null?+arr[i]:NaN);if(v.length>N)v.splice(0,v.length-N);}
|
|
257
|
+
if(this._auto){this._state=null;this._auto=false;}
|
|
258
|
+
this._dirty=true;this._anim.progress=1;schedule(this);return this;}
|
|
259
|
+
clear(){for(const s of this._series)s.values.length=0;this._dirty=true;schedule(this);return this;}
|
|
260
|
+
_render(){const e=this.engine;const t=this.theme;const o=this.options;const on=this._series.map((s,i)=>i).filter((i)=>!this._isOff(i));const hasData=this._series.some((s)=>s.values.length>0);if(!hasData){const ax=drawCartesian(this,{yMin:o.min??0,yMax:o.max??100});e.text(o.emptyText||'Waiting for data…',ax.px+ax.pw/2,ax.py+ax.ph/2,{fill:t.muted,font:`${t.fontSize}px ${t.font}`,align:'center',baseline:'middle',});return;}
|
|
261
|
+
const[lo,hi]=range(on.map((s)=>this._series[s].values.filter((v)=>v===v)),0);const[yLo,yHi]=valueDomain(o,lo,hi);const N=o.window||200;const ax=drawCartesian(this,{yMin:yLo,yMax:yHi});const xAt=(i,len)=>ax.px+((N-len+i)/(N-1))*ax.pw;const len=this._series[0].values.length;this._steps=[];for(let i=0;i<len;i++)this._steps.push(xAt(i,len));e.clip(ax.px,ax.py-t.lineWidth,ax.pw+t.lineWidth,ax.ph+t.lineWidth*2);const rows=[];let k=-1;if(this._cursor){let best=Infinity;this._steps.forEach((x,i)=>{const d=Math.abs(x-this._cursor.x);if(d<best){best=d;k=i;}});}
|
|
262
|
+
for(const s of on){const ser=this._series[s];const color=this._colorOf(s,ser);const pts=ser.values.map((v,i)=>({x:xAt(i,ser.values.length),y:ax.yToPx(v)}));if(pts.length===1)e.circle(pts[0].x,pts[0].y,t.pointRadius+2,{fill:color});if(pts.length<2)continue;e.poly(pts,{stroke:color,lineWidth:t.lineWidth,cap:'round',join:'round'});const last=pts[pts.length-1];e.circle(last.x,last.y,t.pointRadius+1,{fill:color});if(k>=0&&pts[k]){e.circle(pts[k].x,pts[k].y,t.pointRadius+2,{fill:t.bg,stroke:color,lineWidth:2});rows.push({color,name:ser.name,value:this._fmt(ser.values[k])});}}
|
|
263
|
+
e.unclip();this._tipNow=null;if(k>=0&&rows.length&&this._cursor.x>=ax.px-4){const x=this._steps[k];e.poly([{x,y:ax.py},{x,y:ax.py+ax.ph}],{stroke:t.muted,lineWidth:1,dash:[3,3]});this._tipNow={title:`#${k + 1 - len}`,rows};this._hover={_id:k,index:k,label:k+1-len};this.tooltip.show(x,ax.py+8,this._tipNow,t);}}}
|
|
264
|
+
class WaterfallChart extends Chart{static get meta(){return{id:'waterfall',name:'WaterfallChart',aria:'Waterfall chart',data:'steps',options:['labelFormat','min','max'],colors:'an object like { up, down, total }'};}
|
|
265
|
+
_render(){const o=this.options;const data=Array.isArray(o.data)?o.data:[];if(!data.length)return this._empty();const e=this.engine;const t=this.theme;let cum=0;const segs=data.map((d)=>{const v=+d.value||0;const seg=d.total?{from:0,to:v}:{from:cum,to:cum+v};cum=seg.to;return{...seg,value:v,total:!!d.total,label:d.label};});const[lo,hi]=range([segs.map((s)=>s.from),segs.map((s)=>s.to)],0,0);const[yLo,yHi]=valueDomain(o,lo,hi);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLabels:segs.map((s)=>s.label)});const slot=ax.pw/segs.length;const barW=Math.max(1,Math.min(60,slot*0.7));const prog=this._anim.progress;const c=o.colors||{};const up=c.up||'#10b981';const down=c.down||'#ef4444';const total=c.total||'#6366f1';for(let i=0;i<segs.length;i++){const s=segs[i];const x=ax.px+(i+0.5)*slot-barW/2;const y0=ax.yToPx(s.from);const h=Math.abs(ax.yToPx(s.to)-y0)*prog;const top=s.to>=s.from?y0-h:y0;e.rect(x,top,barW,Math.max(h,1),{fill:s.total?total:s.value>=0?up:down,radius:t.barRadius});if(i<segs.length-1&&prog===1){const yEnd=ax.yToPx(s.to);e.poly([{x:x+barW,y:yEnd},{x:x+slot,y:yEnd}],{stroke:t.muted,lineWidth:1,dash:[3,3]});}
|
|
266
|
+
this.hits.add({_id:i,x,y:top,w:barW,h:Math.max(h,4),tipX:x+barW/2,tipY:top,index:i,label:s.label,value:s.value,row:data[i],tooltip:`${this._lab(s.label)}: ${s.value > 0 && !s.total ? '+' : ''}${this._fmt(s.value)}`,});}
|
|
267
|
+
if(this._hover){const r=this._hover;e.rect(r.x,r.y,r.w,r.h,{stroke:t.text,lineWidth:1.5});}}}
|
|
268
|
+
const TYPES=['bar','line','area','scatter'];class CompositeChart extends Chart{static get meta(){return{id:'composite',name:'CompositeChart',aria:'Composite chart',data:'series',options:['labelFormat','min','max','xScale'],enums:{xScale:['category','time']}};}
|
|
269
|
+
constructor(container,options){super(container,options);this._follow=true;}
|
|
270
|
+
_legend(){return(this.options.data?.series||[]).map((l,i)=>({label:(l.name||l.type)+(l.axis==='right'?' (R)':''),color:this._colorOf(i,l),}));}
|
|
271
|
+
_render(){const o=this.options;const{categories:cats=[],series:all=[]}=o.data||{};if(!all.length||!cats.length)return this._empty();const e=this.engine;const t=this.theme;all.forEach((l,i)=>{if(!TYPES.includes(l.type))warn(`CompositeChart: series[${i}] type must be ${TYPES.map((x) => `'${x}'`).join(' | ')} — got ${JSON.stringify(l.type)}.`);});const layers=all.map((l,i)=>({...l,i,color:this._colorOf(i,l)})).filter((l)=>!this._isOff(l.i));const domain=(ll)=>{let mn=0,mx=-Infinity;for(const l of ll)for(const v of l.values){if(v==null)continue;const y=+v;if(y<mn)mn=y;if(y>mx)mx=y;}
|
|
272
|
+
return mx===-Infinity?[0,1]:M.niceDomain(mn,mx,5);};const left=layers.filter((l)=>l.axis!=='right');const rightL=layers.filter((l)=>l.axis==='right');let[lLo,lHi]=domain(left);if(o.min!=null||o.max!=null)[lLo,lHi]=valueDomain(o,o.min??lLo,o.max??lHi);const fmtOf=(ll)=>ll.find((l)=>l.format)?.format;const useTime=o.xScale==='time';const ax=drawCartesian(this,{yMin:lLo,yMax:lHi,yFormat:fmtOf(left),right:rightL.length?(([a,b])=>({yMin:a,yMax:b,yFormat:fmtOf(rightL)}))(domain(rightL)):null,xLabels:useTime?null:cats,xTime:useTime?{min:+cats[0],max:+cats[cats.length-1]}:null,});const yPx=(l)=>(l.axis==='right'&&ax.yRToPx?ax.yRToPx:ax.yToPx);const xAt=useTime?(i)=>ax.xToPx(+cats[i]):(i)=>ax.px+((i+0.5)/cats.length)*ax.pw;this._steps=cats.map((_,i)=>xAt(i));const prog=this._anim.progress;const lineW=t.lineWidth;const bars=layers.filter((l)=>l.type==='bar');const slot=ax.pw/cats.length;const inner=slot*0.7;const barW=bars.length?Math.max(1,inner/bars.length):0;bars.forEach((l,bi)=>{const toPx=yPx(l);const y0=toPx(0);const items=[];for(let i=0;i<cats.length;i++){const v=+l.values[i]||0;const h=Math.abs(toPx(v)-y0)*prog;items.push({x:xAt(i)-inner/2+bi*barW,y:v>=0?y0-h:y0,w:Math.max(1,barW-1),h,radius:t.barRadius});}
|
|
273
|
+
e.rects(items,l.color);});e.clip(ax.px-lineW,ax.py-lineW,(ax.pw+lineW*2)*prog,ax.ph+lineW*2);for(const l of layers.filter((x)=>x.type==='area')){const toPx=yPx(l);const pts=l.values.map((v,i)=>({x:xAt(i),y:toPx(+v)}));const cps=M.bezierControlPoints(pts);const grad=e.vGradient(`area-${l.i}-${l.color}`,ax.px,ax.py,ax.py+ax.ph,[[0,M.colorAlpha(l.color,0.4)],[1,M.colorAlpha(l.color,0)]]);e.smoothArea(pts,cps,Math.min(ax.py+ax.ph,toPx(0)),grad);e.smooth(pts,cps,{stroke:l.color,lineWidth:lineW});}
|
|
274
|
+
for(const l of layers.filter((x)=>x.type==='line')){const toPx=yPx(l);const pts=l.values.map((v,i)=>({x:xAt(i),y:toPx(+v)}));e.smooth(pts,M.bezierControlPoints(pts),{stroke:l.color,lineWidth:lineW});if(pts.length<=50)e.circles(pts,l.color,t.pointRadius);}
|
|
275
|
+
e.unclip();for(const l of layers.filter((x)=>x.type==='scatter')){const toPx=yPx(l);const items=[];l.values.forEach((v,i)=>{if(v!=null)items.push({x:xAt(i),y:toPx(+v)});});e.withAlpha(0.8,()=>e.circles(items,l.color,t.pointRadius+1));}
|
|
276
|
+
this._tipNow=null;const cx=this._cursor?.x;if(cx!=null&&cx>=ax.px&&cx<=ax.px+ax.pw){let idx=0,best=Infinity;this._steps.forEach((x,i)=>{const d=Math.abs(x-cx);if(d<best){best=d;idx=i;}});const x=this._steps[idx];e.withAlpha(0.06,()=>e.rect(x-slot/2,ax.py,slot,ax.ph,{fill:t.text}));const rows=[];for(const l of layers){const v=l.values[idx];if(v==null)continue;rows.push({color:l.color,name:l.name||l.type,value:M.formatNumber(+v,l.format??o.format)});}
|
|
277
|
+
this._tipNow={title:this._lab(cats[idx],useTime),rows};this._hover={_id:idx,index:idx,label:cats[idx]};this.tooltip.show(x,ax.py+8,this._tipNow,t);}else if(this._cursor){this.tooltip.hide();}}}
|
|
278
|
+
const METHODS=['update','setState','render','destroy','push','clear'];function install($,charts){if(!$||!$.fn)throw new Error('aeriz: install($, { BarChart }) needs jQuery as the first argument');const types=$.fn.aerizChart?.types||{};for(const T of Object.values(charts||{}))if(T&&T.meta)types[T.meta.id]=T;function aerizChart(cmd,...args){if(cmd==='instance')return this.first().data('aeriz')||null;if(METHODS.includes(cmd)){return this.each(function(){const c=$(this).data('aeriz');if(!c)throw new Error(`aeriz: .aerizChart('${cmd}') — there is no chart here. Create one first, e.g. .aerizChart('bar', { data })`);if(typeof c[cmd]!=='function')throw new Error(`aeriz: ${c.constructor.meta.name} has no ${cmd}()`);c[cmd](...args);if(cmd==='destroy')$(this).removeData('aeriz');});}
|
|
279
|
+
const T=types[cmd];if(!T){const ids=Object.keys(types);throw new Error(typeof cmd==='function'?`aeriz 2.0: pass the chart id, not the class — install($, { ${cmd.meta?.name || 'BarChart'} }) then .aerizChart('${cmd.meta?.id || 'bar'}', options)`:`aeriz: unknown chart '${cmd}'. ${ids.length ? `Installed: ${ids.join(', ')}` : 'Call install($, { BarChart }) first'}.`);}
|
|
280
|
+
return this.each(function(){$(this).data('aeriz')?.destroy();$(this).data('aeriz',new T(this,args[0]||{}));});}
|
|
281
|
+
aerizChart.types=types;$.fn.aerizChart=aerizChart;return $;}
|
|
282
|
+
const Aeriz={Chart,PALETTE,BarChart,HBarChart,LineChart,AreaChart,StackedBarChart,GroupedBarChart,MultiLineChart,ScatterChart,BubbleChart,PieChart,RadarChart,PolarChart,GaugeChart,HeatmapChart,BoxplotChart,FunnelChart,TreemapChart,SparklineChart,RealtimeChart,WaterfallChart,CompositeChart};if(typeof window!=='undefined'&&window.jQuery)install(window.jQuery,Aeriz);return Aeriz;})();if(typeof module!=='undefined'&&module.exports)module.exports=Aeriz;if(typeof window!=='undefined')window.Aeriz=Aeriz;
|