@aeriz/chart 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/LICENSE +201 -201
  2. package/README.md +53 -446
  3. package/dist/aeriz.cjs +2575 -2387
  4. package/dist/aeriz.esm.js +1576 -1423
  5. package/dist/aeriz.esm.min.js +264 -1
  6. package/dist/aeriz.js +2575 -2387
  7. package/dist/aeriz.min.cjs +266 -1
  8. package/dist/aeriz.min.js +266 -1
  9. package/dist/esm/base.js +73 -0
  10. package/dist/esm/cartesian.js +11 -0
  11. package/dist/esm/charts/area.js +9 -0
  12. package/dist/esm/charts/bar.js +4 -0
  13. package/dist/esm/charts/boxplot.js +4 -0
  14. package/dist/esm/charts/bubble.js +3 -0
  15. package/dist/esm/charts/composite.js +11 -0
  16. package/dist/esm/charts/funnel.js +3 -0
  17. package/dist/esm/charts/gauge.js +5 -0
  18. package/dist/esm/charts/grouped-bar.js +5 -0
  19. package/dist/esm/charts/hbar.js +6 -0
  20. package/dist/esm/charts/heatmap.js +7 -0
  21. package/dist/esm/charts/line.js +9 -0
  22. package/dist/esm/charts/multi-line.js +8 -0
  23. package/dist/esm/charts/pie.js +6 -0
  24. package/dist/esm/charts/polar.js +3 -0
  25. package/dist/esm/charts/radar.js +10 -0
  26. package/dist/esm/charts/realtime.js +12 -0
  27. package/dist/esm/charts/scatter.js +6 -0
  28. package/dist/esm/charts/sparkline.js +5 -0
  29. package/dist/esm/charts/stacked-bar.js +7 -0
  30. package/dist/esm/charts/treemap.js +10 -0
  31. package/dist/esm/charts/waterfall.js +5 -0
  32. package/dist/esm/engine.js +33 -0
  33. package/dist/esm/index.js +2 -0
  34. package/dist/esm/jquery.js +4 -0
  35. package/dist/esm/math.js +32 -0
  36. package/dist/esm/react.js +4 -0
  37. package/dist/esm/shallow.js +3 -0
  38. package/dist/esm/svelte.js +4 -0
  39. package/dist/esm/vue.js +2 -0
  40. package/{src → dist}/index.d.ts +160 -30
  41. package/dist/jquery.d.ts +10 -0
  42. package/dist/react.d.ts +22 -0
  43. package/dist/svelte.d.ts +17 -0
  44. package/dist/types/area.d.ts +1 -0
  45. package/dist/types/bar.d.ts +1 -0
  46. package/dist/types/boxplot.d.ts +1 -0
  47. package/dist/types/bubble.d.ts +1 -0
  48. package/dist/types/composite.d.ts +1 -0
  49. package/dist/types/funnel.d.ts +1 -0
  50. package/dist/types/gauge.d.ts +1 -0
  51. package/dist/types/grouped-bar.d.ts +1 -0
  52. package/dist/types/hbar.d.ts +1 -0
  53. package/dist/types/heatmap.d.ts +1 -0
  54. package/dist/types/line.d.ts +1 -0
  55. package/dist/types/multi-line.d.ts +1 -0
  56. package/dist/types/pie.d.ts +1 -0
  57. package/dist/types/polar.d.ts +1 -0
  58. package/dist/types/radar.d.ts +1 -0
  59. package/dist/types/realtime.d.ts +1 -0
  60. package/dist/types/scatter.d.ts +1 -0
  61. package/dist/types/sparkline.d.ts +1 -0
  62. package/dist/types/stacked-bar.d.ts +1 -0
  63. package/dist/types/treemap.d.ts +1 -0
  64. package/dist/types/waterfall.d.ts +1 -0
  65. package/dist/vue.d.ts +13 -0
  66. package/package.json +60 -29
  67. package/src/base.js +0 -562
  68. package/src/charts.js +0 -1365
  69. package/src/composite.js +0 -187
  70. package/src/engine.js +0 -287
  71. package/src/index.js +0 -18
  72. package/src/math.js +0 -178
@@ -0,0 +1,73 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Engine}from'./engine.js';import*as M from'./math.js';const charts=new Set();let rafId=null;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;}
3
+ frames++;let active=false;for(const c of charts){try{if(c._tick(now))active=true;}catch(e){console.error(e);}}
4
+ rafId=charts.size>0&&active?requestAnimationFrame(tick):null;}
5
+ function schedule(chart){if(chart._destroyed)return;charts.add(chart);if(rafId===null){fpsT=performance.now();rafId=requestAnimationFrame(tick);}}
6
+ export const scheduler={schedule,fps:()=>fps,count:()=>charts.size};export function getFPS(){return fps;}
7
+ export function getActiveCount(){return charts.size;}
8
+ 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.invalidate();});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;}
9
+ return'#ffffff';}
10
+ 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',};}
11
+ 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);}
12
+ return out.length?out:M.PALETTE;}
13
+ function div(parent,css,tag='div'){const el=document.createElement(tag);if(css)el.style.cssText=css;parent.appendChild(el);return el;}
14
+ 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(', ');}
15
+ 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');}
16
+ 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);}}
17
+ 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';}
18
+ hide(){this.el.style.opacity='0';}
19
+ destroy(){this.el.remove();}}
20
+ 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');}
21
+ 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';}
22
+ if(msg)div(el,'opacity:.85').textContent=msg;}
23
+ hide(){this.el.style.display='none';this.el.removeAttribute('role');}
24
+ destroy(){this.el.remove();}}
25
+ class HitIndex{constructor(){this.regions=[];}
26
+ clear(){this.regions.length=0;}
27
+ add(r){this.regions.push(r);}
28
+ 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;}
29
+ return null;}}
30
+ export function toRows(data){if(!data)return[];if(!Array.isArray(data)){return typeof data==='object'?Object.keys(data).map((k)=>({label:k,value:+data[k]})):[];}
31
+ const first=data[0];if(typeof first==='number')return data.map((v,i)=>({label:String(i+1),value:v}));if(Array.isArray(first))return data.map((d)=>({label:d[0],value:+d[1]}));return data;}
32
+ export class Chart{constructor(container,options={}){const el=typeof container==='string'?document.querySelector(container):container;if(!el)throw new Error(`aeriz: container not found${typeof container === 'string' ? ': ' + container : ''}`);this.container=el;this.options=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.theme=resolveTheme(el);this.palette=options.palette||readPalette(el);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._label();this._setupEvents();this._observe();this._resize();if(options.animate===false||reducedMotion?.matches)this._anim.progress=1;schedule(this);}
33
+ invalidate(){this.theme=resolveTheme(this.container);this.palette=this.options.palette||readPalette(this.container);this.engine._gradCache.clear();this._legendKey='';this._dirty=true;schedule(this);}
34
+ update(patch={}){Object.assign(this.options,patch);this._cache=null;if('palette'in patch)this.palette=patch.palette||readPalette(this.container);if(this._auto){this._state=null;this._auto=false;}
35
+ this._label();this._restart();return this;}
36
+ setData(data){return this.update({data});}
37
+ setLoading(msg){return this._setState('loading',msg||'');}
38
+ setEmpty(msg){return this._setState('empty',msg||'No data');}
39
+ setError(msg){return this._setState('error',msg||'Error');}
40
+ clearState(){return this._setState(null);}
41
+ 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;}
42
+ _setState(kind,msg){this._state=kind;this._stateMsg=msg;this._auto=false;this._dirty=true;schedule(this);return this;}
43
+ _empty(msg=this.options.emptyText||'No data'){this._state='empty';this._stateMsg=msg;this._auto=true;this.overlay.show('empty',msg,this.theme);}
44
+ _restart(){const instant=this.options.animate===false||reducedMotion?.matches;this._anim.start=0;this._anim.progress=instant?1:0;this._dirty=true;schedule(this);}
45
+ _label(){const o=this.options;this.canvas.setAttribute('aria-label',o.ariaLabel||o.title||this._kind||'Chart');}
46
+ _fmt(v){return M.formatNumber(v,this.options.format);}
47
+ _tickFmt(v){return M.formatNumber(v,this.options.format,true);}
48
+ _isOff(i){return this._off.has(i);}
49
+ _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;}
50
+ _observe(){if(typeof ResizeObserver!=='undefined'){this._ro=new ResizeObserver(()=>{if(this._resize())schedule(this);});this._ro.observe(this.plot);}
51
+ 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);}}
52
+ _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);}
53
+ _removeEvents(){for(const k in this._handlers)this.canvas.removeEventListener(k,this._handlers[k]);this.legendEl.removeEventListener('click',this._legendClick);}
54
+ _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();}
55
+ _ptr(e){const r=this.canvas.getBoundingClientRect();return{x:e.clientX-r.left,y:e.clientY-r.top};}
56
+ _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();}}
57
+ if(changed||this._follow){this._dirty=true;schedule(this);}
58
+ if(typeof this._onMoveX==='function')this._onMoveX(p);return changed;}
59
+ _onMove(e){const p=this._ptr(e);this._cursor=p;this._setHover(this.hits.find(p.x,p.y),p);}
60
+ _onLeave(){this._cursor=null;this._hover=null;this._key=-1;this.tooltip.hide();this._dirty=true;schedule(this);}
61
+ _onClick(e){const p=this._ptr(e);const hit=this.hits.find(p.x,p.y)||(this._follow?this._hover:null);this.options.onClick?.(hit,p,this);}
62
+ _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.options.onClick?.(this._hover,this._cursor,this);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);}}
63
+ _tick(now){if(this._destroyed)return false;if(!this._visible){if(this._anim.progress<1)this._anim.start=0;return false;}
64
+ 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;}
65
+ if(this._dirty){this._dirty=false;this._draw();}
66
+ return active;}
67
+ _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});}
68
+ if(this._state&&!this._auto){this.overlay.show(this._state,this._stateMsg,this.theme);return;}
69
+ this._state=null;this._auto=false;this.overlay.hide();if(typeof this._render==='function')this._render();}
70
+ _legend(){return null;}
71
+ _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));});}
72
+ _seriesLegend(series){return series.map((s,i)=>({label:s.name||'Series '+(i+1),color:this._colorOf(i,s)}));}
73
+ _colorOf(idx,item){if(item&&item.color)return item.color;return this.palette[idx%this.palette.length];}}
@@ -0,0 +1,11 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import*as M from'./math.js';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;}
3
+ export 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);}
4
+ export 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);}
5
+ const yFmt=spec.yTickFormat||((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.yTickFormat||((v)=>chart._tickFmt(v));rText=rTicks.map((v)=>rf(v));for(const s of rText)rMaxW=Math.max(rMaxW,e.measure(s,font));}
6
+ 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);}
7
+ 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;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=spec.xTickFormat||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=spec.xTickFormat||((v)=>chart._tickFmt(v));}else if(spec.xLabels){const n=spec.xLabels.length||1;xToPx=(i)=>px+(i+0.5)*(pw/n);xFmt=spec.xTickFormat||((i)=>String(spec.xLabels[i]));const guess=Math.max(e.measure(xFmt(0,0),font),e.measure(xFmt(n-1,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);}
8
+ if(xToPx){let lastEnd=-Infinity;for(const v of xTicks){const txt=xFmt(v,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'});}}
9
+ 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};}
10
+ export 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;}}
11
+ if(lo===Infinity)return[0,1];return[lo,hi];}
@@ -0,0 +1,9 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart}from'../base.js';import{drawCartesian,range}from'../cartesian.js';import*as M from'../math.js';export class AreaChart extends Chart{get _kind(){return'Area chart';}
3
+ _legend(){return this._seriesLegend(this.options.series||[]);}
4
+ _render(){const o=this.options;const series=o.series||[];const cats=o.categories||[];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);}
5
+ const[yLo,yHi]=M.niceDomain(yMin,yMax,5);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLabels:cats,yTickFormat:o.yTickFormat,xTickFormat:o.xTickFormat});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;}
6
+ 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);}
7
+ 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});}
8
+ 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,tooltip:{title:String(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])})),},});}
9
+ 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]});}}}
@@ -0,0 +1,4 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart,toRows}from'../base.js';import{drawCartesian,range}from'../cartesian.js';import*as M from'../math.js';export class BarChart extends Chart{get _kind(){return'Bar chart';}
3
+ _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=o.label||((d)=>d.label);const valueOf=o.value||((d)=>d.value);const values=data.map((d,i)=>+valueOf(d,i)||0);const[lo,hi]=range([values],0,0);const[yLo,yHi]=M.niceDomain(lo,hi,5);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLabels:data.map((d,i)=>labelOf(d,i)),yTickFormat:o.yTickFormat,xTickFormat:o.xTickFormat,pad:o.dataLabels?{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(0);const groups=new Map();const dl=o.dataLabels;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(o.color?0:i,data[i].color?data[i]:{color:o.color});if(!groups.has(color))groups.set(color,[]);groups.get(color).push({x,y:top,w:barW,h,radius:t.barRadius});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:`${labelOf(data[i], i)}: ${this._fmt(v)}`,data:data[i],index:i,value:v,});if(dl&&prog>0.95){const txt=typeof dl==='function'?dl(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',});}}
4
+ 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});}}}
@@ -0,0 +1,4 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart}from'../base.js';import{drawCartesian}from'../cartesian.js';import*as M from'../math.js';export class BoxplotChart extends Chart{get _kind(){return'Boxplot chart';}
3
+ _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;}}
4
+ const[yLo,yHi]=M.niceDomain(yMin,yMax,5);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLabels:data.map((d)=>d.label),yTickFormat:o.yTickFormat});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),data:d,index:i,tooltip:{title:String(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)})),},});}}}
@@ -0,0 +1,3 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart}from'../base.js';import{drawCartesian}from'../cartesian.js';import{xyExtent}from'./scatter.js';import*as M from'../math.js';export class BubbleChart extends Chart{get _kind(){return'Bubble chart';}
3
+ _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.z||0)>zMax)zMax=d.z;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]=M.niceDomain(yMin-py,yMax+py,5);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLinear:{min:xLo,max:xHi},yTickFormat:o.yTickFormat,xTickFormat:o.xTickFormat,});const prog=this._anim.progress;const maxR=o.maxRadius||32,minR=o.minRadius||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(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,data:d,index:i,tooltip:`${d.label ? d.label + ' ' : ''}(${this._fmt(d.x)}, ${this._fmt(d.y)}) · z=${this._fmt(d.z)}`,});}}}
@@ -0,0 +1,11 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart}from'../base.js';import{drawCartesian}from'../cartesian.js';import*as M from'../math.js';export class CompositeChart extends Chart{get _kind(){return'Composite chart';}
3
+ constructor(container,options){super(container,options);this._follow=true;}
4
+ _legend(){return(this.options.layers||[]).map((l,i)=>({label:(l.name||l.type)+(l.axis==='right'?' (R)':''),color:this._colorOf(i,l),}));}
5
+ _render(){const o=this.options;const all=o.layers||[];const cats=o.categories||[];if(!all.length||!cats.length)return this._empty();const e=this.engine;const t=this.theme;const layers=all.map((l,i)=>({...l,i,color:this._colorOf(i,l)})).filter((l)=>!this._isOff(l.i));const yOf=(l,v)=>(l.type==='scatter'?v.y:+v);const domain=(ll)=>{let mn=0,mx=-Infinity;for(const l of ll)for(const v of l.values){const y=yOf(l,v);if(y<mn)mn=y;if(y>mx)mx=y;}
6
+ 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');const[lLo,lHi]=domain(left);const useTime=o.xScale==='time';const ax=drawCartesian(this,{yMin:lLo,yMax:lHi,yTickFormat:o.yLeftTickFormat||o.yTickFormat,right:rightL.length?(([a,b])=>({yMin:a,yMax:b,yTickFormat:o.yRightTickFormat}))(domain(rightL)):null,xLabels:useTime?null:cats,xTime:useTime?{min:+cats[0],max:+cats[cats.length-1]}:null,xTickFormat:o.xTickFormat,});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});}
7
+ 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});}
8
+ 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);}
9
+ e.unclip();for(const l of layers.filter((x)=>x.type==='scatter')){const toPx=yPx(l);const items=l.values.map((p)=>({x:xAt(typeof p.x==='number'?p.x:cats.indexOf(p.x)),y:toPx(p.y)}));e.withAlpha(0.8,()=>e.circles(items,l.color,l.radius||t.pointRadius+1));}
10
+ 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){let v;if(l.type==='scatter'){const p=l.values.find((p)=>(typeof p.x==='number'?p.x:cats.indexOf(p.x))===idx);if(!p)continue;v=p.y;}else v=+l.values[idx];rows.push({color:l.color,name:l.name||l.type,value:this._fmt(v)});}
11
+ this._tipNow={title:useTime?new Date(+cats[idx]).toLocaleString():String(cats[idx]),rows};this._hover={_id:idx,index:idx};this.tooltip.show(x,ax.py+8,this._tipNow,t);}else if(this._cursor){this.tooltip.hide();}}}
@@ -0,0 +1,3 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart,toRows}from'../base.js';export class FunnelChart extends Chart{get _kind(){return'Funnel chart';}
3
+ _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=o.label||((d)=>d.label);const valueOf=o.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,data:data[i],index:i,value:v,tooltip:`${labelOf(data[i], i)}: ${this._fmt(v)} (${((v / first) * 100).toFixed(1)}%)`,});}}}
@@ -0,0 +1,5 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart}from'../base.js';export class GaugeChart extends Chart{get _kind(){return'Gauge chart';}
3
+ setValue(v){this._from=this._shown??0;return this.update({value:v});}
4
+ _render(){const e=this.engine;const t=this.theme;const o=this.options;const min=o.min||0;const max=o.max!=null?o.max:100;const target=Math.max(min,Math.min(max,+o.value||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=o.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.label){e.text(o.label,cx,cy+R*0.22,{fill:t.muted,font:`${t.fontSize}px ${t.font}`,align:'center',baseline:'top'});}
5
+ this.canvas.setAttribute('aria-label',`${o.ariaLabel || o.label || o.title || 'Gauge'}: ${txt}`);}}
@@ -0,0 +1,5 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart}from'../base.js';import{drawCartesian,range}from'../cartesian.js';import*as M from'../math.js';export class GroupedBarChart extends Chart{get _kind(){return'Grouped bar chart';}
3
+ _legend(){return this._seriesLegend(this.options.series||[]);}
4
+ _render(){const o=this.options;const series=o.series||[];const cats=o.categories||[];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]=M.niceDomain(lo,hi,5);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLabels:cats,yTickFormat:o.yTickFormat,xTickFormat:o.xTickFormat});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,series:s,value:v,tooltip:{title:String(cats[i]),rows:[{color,name:series[s].name||'Series '+(s+1),value:this._fmt(v)}]},});});}
5
+ 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});}}}
@@ -0,0 +1,6 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart,toRows}from'../base.js';import{range}from'../cartesian.js';import*as M from'../math.js';export class HBarChart extends Chart{get _kind(){return'Horizontal bar chart';}
3
+ _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=o.label||((d)=>d.label);const valueOf=o.value||((d)=>d.value);const values=data.map((d,i)=>+valueOf(d,i)||0);const labels=data.map((d,i)=>String(labelOf(d,i)));const[lo,hi]=range([values],0,0);const[xLo,xHi]=M.niceDomain(lo,hi,5);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);const xFmt=o.xTickFormat||((v)=>this._tickFmt(v));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);}
4
+ e.ctx.stroke();for(const v of xTicks){e.text(xFmt(v),xToPx(v),py+ph+6,{fill:t.muted,font,align:'center',baseline:'top'});}
5
+ 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(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(o.color?0:i,data[i].color?data[i]:{color:o.color});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)}`,data:data[i],index:i,value:v,});e.text(labels[i],px-6,yMid,{fill:t.muted,font,align:'right',baseline:'middle'});}
6
+ 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});}}}
@@ -0,0 +1,7 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart}from'../base.js';import*as M from'../math.js';export class HeatmapChart extends Chart{get _kind(){return'Heatmap chart';}
3
+ _render(){const o=this.options;const data=o.data||[];const xLabels=o.xLabels||[];const yLabels=o.yLabels||[];if(!data.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;for(const s of yLabels)labelW=Math.max(labelW,e.measure(String(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 d of data){if(d.value>vMax)vMax=d.value;if(d.value<vMin)vMin=d.value;}
4
+ const cellW=pw/xLabels.length;const cellH=ph/yLabels.length;const lo=o.colorLow||M.colorAlpha(this.palette[0],0.08);const hi=o.colorHigh||this.palette[0];const gap=cellW>6&&cellH>6?1:0;const prog=this._anim.progress;const xi=new Map(xLabels.map((l,i)=>[l,i]));const yi=new Map(yLabels.map((l,i)=>[l,i]));for(let k=0;k<data.length;k++){const d=data[k];const cx=typeof d.x==='number'?d.x:xi.get(d.x)??-1;const cy=typeof d.y==='number'?d.y:yi.get(d.y)??-1;if(cx<0||cy<0)continue;const x=px+cx*cellW;const y=py+cy*cellH;const n=vMax>vMin?(d.value-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:k,x,y,w:cellW,h:cellH,tipX:x+cellW/2,tipY:y,data:d,index:k,value:d.value,tooltip:`${xLabels[cx]} · ${yLabels[cy]}: ${this._fmt(d.value)}`,});}
5
+ let lastEnd=-Infinity;for(let i=0;i<xLabels.length;i++){const txt=String(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'});}
6
+ const step=Math.ceil((t.fontSize+2)/cellH);for(let i=0;i<yLabels.length;i+=step){e.text(String(yLabels[i]),px-6,py+(i+0.5)*cellH,{fill:t.muted,font,align:'right',baseline:'middle'});}
7
+ 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});}}}
@@ -0,0 +1,9 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart,toRows}from'../base.js';import{drawCartesian,range}from'../cartesian.js';import*as M from'../math.js';const LABEL=(d)=>d.label;const VALUE=(d)=>d.value;export class LineChart extends Chart{get _kind(){return'Line chart';}
3
+ _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=o.label||LABEL;const valueOf=o.value||VALUE;const n=data.length;let c=this._cache;if(!c||c.data!==o.data||c.valueOf!==valueOf||c.labelOf!==labelOf){const values=new Float64Array(n);for(let i=0;i<n;i++)values[i]=+valueOf(data[i],i);c=this._cache={data:o.data,valueOf,labelOf,values,idx:null,idxW:0,xs:null,labels:null,range:null};}
4
+ const values=c.values;const[lo,hi]=(c.range||=range([values],o.yScale==='log'?Infinity:0));const[yLo,yHi]=o.yScale==='log'?[lo,hi]:M.niceDomain(lo,hi,5);const useTime=o.xScale==='time';const spec={yMin:yLo,yMax:yHi,yScale:o.yScale,yTickFormat:o.yTickFormat,xTickFormat:o.xTickFormat,};const xs=useTime?(c.xs||=data.map((d,i)=>+labelOf(d,i))):null;if(useTime)spec.xTime={min:xs[0],max:xs[n-1]};else spec.xLabels=c.labels||=data.map((d,i)=>labelOf(d,i));const ax=drawCartesian(this,spec);const color=o.color||this.palette[0];const interp=o.interpolation||'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]);}
5
+ 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};}
6
+ 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});}
7
+ 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.startsWith('step')){e.step(points,interp==='step'?'before':interp.split('-')[1],{stroke:color,lineWidth:lineW});}
8
+ e.unclip();const tip=(i)=>`${useTime ? new Date(xs[i]).toLocaleString() : labelOf(data[i], i)}: ${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),data:data[p.i],index:p.i,value:values[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),data:data[p.i],index:p.i,value:values[p.i],});}}
9
+ 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});}}}
@@ -0,0 +1,8 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart}from'../base.js';import{drawCartesian,range}from'../cartesian.js';import*as M from'../math.js';export class MultiLineChart extends Chart{get _kind(){return'Multi line chart';}
3
+ constructor(container,options){super(container,options);this._follow=true;}
4
+ _legend(){return this._seriesLegend(this.options.series||[]);}
5
+ _render(){const o=this.options;const series=o.series||[];const cats=o.categories||[];if(!series.length||!cats.length)return this._empty();const e=this.engine;const t=this.theme;const interp=o.interpolation||'smooth';const on=series.map((s,i)=>i).filter((i)=>!this._isOff(i)&&!series[i].hidden);const[lo,hi]=range(on.map((s)=>series[s].values),0);const[yLo,yHi]=M.niceDomain(lo,hi,5);const useTime=o.xScale==='time';const spec={yMin:yLo,yMax:yHi,yTickFormat:o.yTickFormat,xTickFormat:o.xTickFormat};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,interp==='step'?'before':interp.split('-')[1],{stroke:color,lineWidth:lineW});if(pts.length<=50)e.circles(pts,color,t.pointRadius);}
6
+ 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;}}
7
+ 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])});}
8
+ const title=useTime?new Date(+cats[idx]).toLocaleString():String(cats[idx]);this._tipNow={title,rows};this._hover={_id:idx,index:idx};this.tooltip.show(xPx,ax.py+8,this._tipNow,t);}else if(this._cursor){this.tooltip.hide();}}}
@@ -0,0 +1,6 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart,toRows}from'../base.js';import{TAU}from'../math.js';export 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;};}
3
+ export class PieChart extends Chart{get _kind(){return'Pie chart';}
4
+ _legend(){const o=this.options;const labelOf=o.label||((d)=>d.label);return toRows(o.data).map((d,i)=>({label:String(labelOf(d,i)),color:this._colorOf(i,d)}));}
5
+ _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=o.label||((d)=>d.label);const valueOf=o.value||((d)=>d.value);const vals=data.map((d,i)=>(this._isOff(i)?0:Math.max(0,+valueOf(d,i)||0)));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;const innerR=ir==null||ir===false?0:typeof ir==='number'?(ir<=1?R*ir:ir):R*0.55;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,data:data[i],index:i,value:v,tooltip:`${labelOf(data[i], i)}: ${this._fmt(v)} (${((v / total) * 100).toFixed(1)}%)`,});a0=a1;}
6
+ if(innerR>0&&o.centerLabel!==false){const h=this._hover;const center=h?`${((vals[h._id] / total) * 100).toFixed(0)}%`:o.totalLabel||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',});}}}
@@ -0,0 +1,3 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart,toRows}from'../base.js';import{inSector}from'./pie.js';import{TAU}from'../math.js';export class PolarChart extends Chart{get _kind(){return'Polar chart';}
3
+ _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=o.label||((d)=>d.label);const valueOf=o.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,data:data[i],index:i,value:v,tooltip:`${labelOf(data[i], i)}: ${this._fmt(v)}`,});}}}
@@ -0,0 +1,10 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart}from'../base.js';import{range}from'../cartesian.js';import*as M from'../math.js';import{TAU}from'../math.js';export class RadarChart extends Chart{get _kind(){return'Radar chart';}
3
+ _legend(){return this._seriesLegend(this.options.series||[]);}
4
+ _render(){const o=this.options;const series=o.series||[];const axes=o.axes||[];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;for(const a of axes)labelW=Math.max(labelW,e.measure(String(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=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);}
5
+ ctx.stroke();}
6
+ 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);}
7
+ ctx.stroke();for(let i=0;i<N;i++){const a=angle(i);e.text(String(axes[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',});}
8
+ 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=((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,series:s,value:v,tooltip:{title:String(axes[i]),rows:[{color,name:ser.name||'Series '+(s+1),value:this._fmt(v)}]},});}
9
+ e.poly(pts,{fill:M.colorAlpha(color,0.2),stroke:color,lineWidth:2,close:true,join:'round'});e.circles(pts,color,t.pointRadius);}
10
+ if(this._hover){const h=this._hover;e.circle(h.x+7,h.y+7,t.pointRadius+3,{stroke:t.text,lineWidth:1.5});}}}
@@ -0,0 +1,12 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart,scheduler}from'../base.js';import{drawCartesian,range}from'../cartesian.js';import*as M from'../math.js';export class RealtimeChart extends Chart{get _kind(){return'Realtime chart';}
3
+ constructor(container,options={}){super(container,options);this.options.windowSize=options.windowSize||200;this._series=(options.series||[{name:'Series'}]).map((s)=>({name:s.name||'Series',color:s.color,values:[]}));this._follow=true;this._streaming=true;}
4
+ _legend(){return this._series.length>1?this._seriesLegend(this._series):null;}
5
+ push(values){const arr=Array.isArray(values)?values:[values];const N=this.options.windowSize;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);}
6
+ if(this._auto){this._state=null;this._auto=false;}
7
+ this._dirty=true;this._anim.progress=1;scheduler.schedule(this);return this;}
8
+ clear(){for(const s of this._series)s.values.length=0;this._dirty=true;scheduler.schedule(this);return this;}
9
+ _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:0,yMax:100,yTickFormat:o.yTickFormat});e.text(o.waitingText||'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;}
10
+ const[lo,hi]=range(on.map((s)=>this._series[s].values.filter((v)=>v===v)),o.yMin!=null?o.yMin:0);const[yLo,yHi]=o.yMax!=null?[o.yMin||0,o.yMax]:M.niceDomain(lo,hi,5);const N=o.windowSize;const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,yTickFormat:o.yTickFormat});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;}});}
11
+ 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])});}}
12
+ 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.tooltip.show(x,ax.py+8,this._tipNow,t);}}}
@@ -0,0 +1,6 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart}from'../base.js';import{drawCartesian}from'../cartesian.js';import*as M from'../math.js';export 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;}
3
+ return[xMin,xMax,yMin,yMax];}
4
+ export class ScatterChart extends Chart{get _kind(){return'Scatter chart';}
5
+ _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]=M.niceDomain(yMin,yMax,5);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLinear:{min:xLo,max:xHi},yTickFormat:o.yTickFormat,xTickFormat:o.xTickFormat,});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.r||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,data:d,index:i,tooltip:`(${this._fmt(d.x)}, ${this._fmt(d.y)})${d.label ? ` · ${d.label}` : ''}`,});}}
6
+ 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});}}}
@@ -0,0 +1,5 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart}from'../base.js';import{range}from'../cartesian.js';import*as M from'../math.js';export class Sparkline extends Chart{get _kind(){return'Sparkline';}
3
+ _render(){const o=this.options;const data=o.data||[];if(!data.length)return;const e=this.engine;const t=this.theme;const valueOf=o.value||((v)=>(typeof v==='object'&&v?+v.value:+v));const values=data.map((d,i)=>valueOf(d,i));const[yMin,yMax]=range([values]);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=o.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);}
4
+ 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});}
5
+ 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;this.hits.add({_id:i,x:l,y:0,w:r-l,h:e.h,tipX:p.x,tipY:p.y-4,index:i,value:values[i],tooltip: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});}}}
@@ -0,0 +1,7 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart}from'../base.js';import{drawCartesian}from'../cartesian.js';import*as M from'../math.js';export class StackedBarChart extends Chart{get _kind(){return'Stacked bar chart';}
3
+ _legend(){return this._seriesLegend(this.options.series||[]);}
4
+ _render(){const o=this.options;const series=o.series||[];const cats=o.categories||[];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;}
5
+ if(pos>hi)hi=pos;if(neg<lo)lo=neg;}
6
+ const[yLo,yHi]=M.niceDomain(lo,hi,5);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLabels:cats,yTickFormat:o.yTickFormat,xTickFormat:o.xTickFormat});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,series:s,value:v,tooltip:{title:String(cats[i]),rows:[{color,name:series[s].name||'Series '+(s+1),value:this._fmt(v)}]},});}}
7
+ 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});}}}
@@ -0,0 +1,10 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart,toRows}from'../base.js';export 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);}
3
+ if(worst>best)break;best=worst;sum=s;}
4
+ if(j===i){sum=items[i].value;j=i+1;}
5
+ 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;}
6
+ if(w>=h){x+=thick;w-=thick;}else{y+=thick;h-=thick;}
7
+ rest-=sum;i=j;}}
8
+ export class TreemapChart extends Chart{get _kind(){return'Treemap chart';}
9
+ _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=o.label||((d)=>d.label);const valueOf=o.value||((d)=>d.value);const items=data.map((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'}));}
10
+ this.hits.add({_id:it._id,x,y,w,h,tipX:x+w/2,tipY:y,data:it.data,index:it._id,value:it.value,tooltip:`${it.label}: ${this._fmt(it.value)}`,});});}}
@@ -0,0 +1,5 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{Chart,toRows}from'../base.js';import{drawCartesian,range}from'../cartesian.js';import*as M from'../math.js';export class WaterfallChart extends Chart{get _kind(){return'Waterfall chart';}
3
+ _render(){const o=this.options;const data=toRows(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.isTotal?{from:0,to:v}:{from:cum,to:cum+v};cum=seg.to;return{...seg,value:v,isTotal:!!d.isTotal,label:d.label};});const[lo,hi]=range([segs.map((s)=>s.from),segs.map((s)=>s.to)],0,0);const[yLo,yHi]=M.niceDomain(lo,hi,5);const ax=drawCartesian(this,{yMin:yLo,yMax:yHi,xLabels:segs.map((s)=>s.label),yTickFormat:o.yTickFormat});const slot=ax.pw/segs.length;const barW=Math.max(1,Math.min(60,slot*0.7));const prog=this._anim.progress;const up=o.colorUp||'#10b981';const down=o.colorDown||'#ef4444';const total=o.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 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.isTotal?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]});}
4
+ this.hits.add({_id:i,x,y:top,w:barW,h:Math.max(h,4),tipX:x+barW/2,tipY:top,data:data[i],index:i,value:s.value,tooltip:`${s.label}: ${s.value > 0 && !s.isTotal ? '+' : ''}${this._fmt(s.value)}`,});}
5
+ if(this._hover){const r=this._hover;e.rect(r.x,r.y,r.w,r.h,{stroke:t.text,lineWidth:1.5});}}}
@@ -0,0 +1,33 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{TAU}from'./math.js';export 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;}
3
+ 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();}
4
+ 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);}}
5
+ 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);}
6
+ if(opts.fill){ctx.fillStyle=opts.fill;ctx.fill();}
7
+ if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1;ctx.stroke();}}
8
+ 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);}}
9
+ ctx.fill();}
10
+ 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();}
11
+ if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1;ctx.stroke();}}
12
+ 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);}
13
+ ctx.fill();}
14
+ 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);}
15
+ if(opts.close)ctx.closePath();if(opts.fill){ctx.fillStyle=opts.fill;ctx.fill();}
16
+ 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([]);}}
17
+ 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);}
18
+ if(opts.fill){ctx.fillStyle=opts.fill;ctx.fill();}
19
+ if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||2;ctx.lineCap=opts.cap||'round';ctx.lineJoin=opts.join||'round';ctx.stroke();}}
20
+ 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);}
21
+ ctx.lineTo(points[points.length-1].x,y0);ctx.closePath();ctx.fillStyle=fill;ctx.fill();}
22
+ 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);}}
23
+ if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1.5;ctx.stroke();}}
24
+ _setFont(font){font=font||'11px ui-monospace, monospace';if(this._font!==font){this.ctx.font=font;this._font=font;}}
25
+ 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);}}
26
+ measure(str,font){this._setFont(font);return this.ctx.measureText(str).width;}
27
+ 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();}
28
+ if(opts.stroke){ctx.strokeStyle=opts.stroke;ctx.lineWidth=opts.lineWidth||1;ctx.stroke();}}
29
+ 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);}
30
+ this._gradCache.set(key,g);return g;}
31
+ clip(x,y,w,h){const ctx=this.ctx;ctx.save();ctx.beginPath();ctx.rect(x,y,w,h);ctx.clip();}
32
+ unclip(){this.ctx.restore();this._font=null;}
33
+ withAlpha(a,fn){const ctx=this.ctx;const prev=ctx.globalAlpha;ctx.globalAlpha=prev*a;fn();ctx.globalAlpha=prev;}}
@@ -0,0 +1,2 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ export{Chart,getFPS,getActiveCount,scheduler,toRows}from'./base.js';export{Engine}from'./engine.js';export{drawCartesian}from'./cartesian.js';export*as math from'./math.js';export{PALETTE}from'./math.js';export{BarChart}from'./charts/bar.js';export{HBarChart}from'./charts/hbar.js';export{LineChart}from'./charts/line.js';export{AreaChart}from'./charts/area.js';export{StackedBarChart}from'./charts/stacked-bar.js';export{GroupedBarChart}from'./charts/grouped-bar.js';export{MultiLineChart}from'./charts/multi-line.js';export{ScatterChart}from'./charts/scatter.js';export{BubbleChart}from'./charts/bubble.js';export{PieChart}from'./charts/pie.js';export{RadarChart}from'./charts/radar.js';export{PolarChart}from'./charts/polar.js';export{GaugeChart}from'./charts/gauge.js';export{HeatmapChart}from'./charts/heatmap.js';export{BoxplotChart}from'./charts/boxplot.js';export{FunnelChart}from'./charts/funnel.js';export{TreemapChart}from'./charts/treemap.js';export{Sparkline}from'./charts/sparkline.js';export{RealtimeChart}from'./charts/realtime.js';export{WaterfallChart}from'./charts/waterfall.js';export{CompositeChart}from'./charts/composite.js';
@@ -0,0 +1,4 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ export function install($,registry){if(!$||!$.fn)throw new Error('aeriz: install($) needs jQuery');$.fn.aerizChart=function(type,options){const first=this.first().data('aeriz');if(type==='instance')return first;if(typeof type==='string'&&first&&typeof first[type]==='function'){const args=[].slice.call(arguments,1);return this.each(function(){const c=$(this).data('aeriz');if(!c)return;c[type](...args);if(type==='destroy')$(this).removeData('aeriz');});}
3
+ const T=typeof type==='string'?registry&&registry[type]:type;if(typeof T!=='function')throw new Error(`aeriz: unknown chart type ${type}`);return this.each(function(){$(this).data('aeriz')?.destroy();$(this).data('aeriz',new T(this,options||{}));});};return $;}
4
+ export default install;
@@ -0,0 +1,32 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ export const TAU=Math.PI*2;export const clamp=(v,lo,hi)=>v<lo?lo:v>hi?hi:v;export const lerp=(a,b,t)=>a+(b-a)*t;export 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
+ export 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
+ export 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};export 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
+ export 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
+ export 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
+ export 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
+ export function shouldDecimate(n,pxWidth){return n>pxWidth*2;}
23
+ export 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
+ export 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
+ export 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
+ export function colorAlpha(c,a){const[r,g,b]=colorParse(c);return`rgba(${r|0},${g|0},${b|0},${a})`;}
30
+ export 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
+ export 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
+ export 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);}
@@ -0,0 +1,4 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{createElement,forwardRef,useEffect,useRef}from'react';import{same}from'./shallow.js';function merge(options,data){return data===undefined?{...options}:{...options,data};}
3
+ function setRef(ref,v){if(typeof ref==='function')ref(v);else if(ref)ref.current=v;}
4
+ export const AerizChart=forwardRef(function AerizChart({type,options,data,onReady,...rest},ref){const el=useRef(null);const chart=useRef(null);const last=useRef(null);const next=merge(options,data);useEffect(()=>{const c=new type(el.current,next);chart.current=c;last.current=next;setRef(ref,c);onReady?.(c);return()=>{c.destroy();chart.current=null;setRef(ref,null);};},[type]);useEffect(()=>{const c=chart.current;if(!c)return;if(same(last.current,next))Object.assign(c.options,next);else c.update(next);last.current=next;});return createElement('div',{ref:el,...rest});});export default AerizChart;
@@ -0,0 +1,3 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ const plain=(v)=>v&&typeof v==='object'&&Object.getPrototypeOf(v)===Object.prototype;export function same(a,b,deep=true){if(a===b)return true;if(!a||!b)return false;const ka=Object.keys(a);if(ka.length!==Object.keys(b).length)return false;for(const k of ka){const x=a[k],y=b[k];if(x===y||(typeof x==='function'&&typeof y==='function'))continue;if(deep&&plain(x)&&plain(y)&&same(x,y,false))continue;return false;}
3
+ return true;}
@@ -0,0 +1,4 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{same}from'./shallow.js';export function aeriz(node,params){let{type,onReady,...options}=params;let chart=new type(node,options);onReady?.(chart);return{update(next){const{type:t,onReady:r,...o}=next;if(t!==type){chart.destroy();type=t;chart=new t(node,o);r?.(chart);}else if(same(o,options)){Object.assign(chart.options,o);}else{chart.update(o);}
3
+ options=o;},destroy(){chart.destroy();},};}
4
+ export default aeriz;
@@ -0,0 +1,2 @@
1
+ /*! @aeriz/chart v1.1.0 | Apache-2.0 | https://aeriz.dev/chart/ */
2
+ import{defineComponent,h,onBeforeUnmount,onMounted,ref,toRaw,watch}from'vue';import{same}from'./shallow.js';export const AerizChart=defineComponent({name:'AerizChart',props:{type:{type:Function,required:true},options:{type:Object,default:()=>({})},data:{type:null,default:undefined},},emits:['ready','click'],setup(props,{emit,expose}){const el=ref(null);let chart=null;let last=null;const opts=()=>{const o={...toRaw(props.options)};if(props.data!==undefined)o.data=toRaw(props.data);if(!o.onClick)o.onClick=(hit,p,c)=>emit('click',hit,p,c);return o;};const mount=()=>{last=opts();chart=new props.type(el.value,last);emit('ready',chart);};onMounted(mount);watch(()=>props.type,()=>{chart?.destroy();mount();});watch(()=>[props.options,props.data],()=>{const o=opts();if(!chart)return;if(same(last,o))Object.assign(chart.options,o);else chart.update(o);last=o;});onBeforeUnmount(()=>{chart?.destroy();chart=null;});expose({chart:()=>chart});return()=>h('div',{ref:el});},});export default AerizChart;