chartjs-chart-sankey 0.12.1 → 0.14.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.
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * chartjs-chart-sankey v0.12.1
2
+ * chartjs-chart-sankey v0.14.0
3
3
  * https://github.com/kurkle/chartjs-chart-sankey#readme
4
4
  * (c) 2024 Jukka Kurkela
5
5
  * Released under the MIT license
6
6
  */
7
- !function(t,o){"object"==typeof exports&&"undefined"!=typeof module?o(require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["chart.js","chart.js/helpers"],o):o((t="undefined"!=typeof globalThis?globalThis:t||self).Chart,t.Chart.helpers)}(this,(function(t,o){"use strict";function e(t){return t&&-1!==["min","max"].indexOf(t)?t:"max"}const r=t=>void 0!==t;function n(t,o){const e=t.filter((t=>!o.has(t)));return e.length?e:t.slice(0,1)}const a=(t,o)=>t.x!==o.x?t.x-o.x:t.y-o.y;let i=-1;function s(t,o,e=function(){return i=i<100?i+1:0,i}()){let r=0;for(const n of t)n.node._visited!==e&&(n.node._visited=e,r+=n.node[o].length+s(n.node[o],o,e));return r}const l=t=>(o,e)=>s(o.node[t],t)-s(e.node[t],t)||o.node[t].length-e.node[t].length;function c(t,o){t.from.sort(l("from"));for(const e of t.from){const t=e.node;r(t.y)||(t.y=o,c(t,o)),o=Math.max(t.y+t.out,o)}return o}function h(t,o){t.to.sort(l("to"));for(const e of t.to){const t=e.node;r(t.y)||(t.y=o,h(t,o)),o=Math.max(t.y+t.in,o)}return o}function f(t,o){return r(t.y)?t.y:(t.y=o,o)}function d(t,o){t.sort(((t,o)=>Math.max(o.in,o.out)-Math.max(t.in,t.out)));const e=t[0];e.y=0;const n=c(e,0),a=h(e,0),i=function(t,o){const e=t.filter((t=>0===t.x)),n=t.filter((t=>t.x===o)),a=e.filter((t=>!r(t.y))),i=n.filter((t=>!r(t.y))),s=t.filter((t=>t.x>0&&t.x<o&&!r(t.y)));let l=e.reduce(((t,o)=>Math.max(t,o.y+o.out||0)),0),d=n.reduce(((t,o)=>Math.max(t,o.y+o.in||0)),0),u=0;return l>=d?(a.forEach((t=>{l=f(t,l),l=Math.max(l+t.out,h(t,l))})),i.forEach((t=>{d=f(t,d),d=Math.max(d+t.in,h(t,d))}))):(i.forEach((t=>{d=f(t,d),d=Math.max(d+t.in,h(t,d))})),a.forEach((t=>{l=f(t,l),l=Math.max(l+t.out,h(t,l))}))),s.forEach((o=>{let e=t.filter((t=>t.x===o.x&&r(t.y))).reduce(((t,o)=>Math.max(t,o.y+Math.max(o.in,o.out))),0);e=f(o,e),e=Math.max(e+o.in,c(o,e)),e=Math.max(e+o.out,h(o,e)),u=Math.max(u,e)})),Math.max(l,d,u)}(t,o);return Math.max(n,a,i)}function u(t,o,e,i){const s=[...t.values()],l=function(t,o){const e=new Set(o.map((t=>t.to))),a=new Set(o.map((t=>t.from))),i=new Set([...t.keys()]);let s=0;for(;i.size;){const a=n([...i],e);for(const o of a){const e=t.get(o);r(e.x)||(e.x=s),i.delete(o)}i.size&&(e.clear(),o.filter((t=>i.has(t.from))).forEach((t=>e.add(t.to))),s++)}return[...t.keys()].filter((t=>!a.has(t))).forEach((o=>{const e=t.get(o);e.column||(e.x=s)})),[...t.values()].reduce(((t,o)=>Math.max(t,o.x)),0)}(t,o),c=e?function(t,o){let e=0,r=0;for(let n=0;n<=o;n++){let o=r;const a=t.filter((t=>t.x===n)).sort(((t,o)=>t.priority-o.priority));r=a.length&&a[0].to.filter((t=>t.node.x>n+1)).reduce(((t,o)=>t+o.flow),0)||0;for(const t of a)t.y=o,o+=Math.max(t.out,t.in);e=Math.max(o,e)}return e}(s,l):d(s,l),h=function(t,o){let e=1,r=0,n=0,i=0;const s=[];t.sort(a);for(const a of t){if(a.y){if(0===a.x)s.push(a.y);else{for(r!==a.x&&(r=a.x,n=0),e=n+1;e<s.length&&!(s[e]>a.y);e++);n=e}a.y+=e*o,e++}i=Math.max(i,a.y+Math.max(a.in,a.out))}return i}(s,.03*c);return function(t,o){t.forEach((t=>{const e=Math[o](t.in||t.out,t.out||t.in),r=e<t.in,n=e<t.out;let a=0,i=t.from.length;t.from.sort(((t,o)=>t.node.y+t.node.out/2-(o.node.y+o.node.out/2))).forEach(((t,o)=>{r?t.addY=o*(e-t.flow)/(i-1):(t.addY=a,a+=t.flow)})),a=0,i=t.to.length,t.to.sort(((t,o)=>t.node.y+t.node.in/2-(o.node.y+o.node.in/2))).forEach(((t,o)=>{n?t.addY=o*(e-t.flow)/(i-1):(t.addY=a,a+=t.flow)}))}))}(s,i),{maxX:l,maxY:h}}function x(t,o,e){for(const r of t)if(r.key===o&&r.index===e)return r.addY;return 0}class y extends t.DatasetController{parseObjectData(t,o,r,n){const{from:a="from",to:i="to",flow:s="flow"}=this.options.parsing,l=o.map((({[a]:t,[i]:o,[s]:e})=>({from:t,to:o,flow:e}))),{xScale:c,yScale:h}=t,f=[],d=this._nodes=function(t){const o=new Map;for(let e=0;e<t.length;e++){const{from:r,to:n,flow:a}=t[e];if(o.has(r)){const t=o.get(r);t.out+=a,t.to.push({key:n,flow:a,index:e})}else o.set(r,{key:r,in:0,out:a,from:[],to:[{key:n,flow:a,index:e}]});if(o.has(n)){const t=o.get(n);t.in+=a,t.from.push({key:r,flow:a,index:e})}else o.set(n,{key:n,in:a,out:0,from:[{key:r,flow:a,index:e}],to:[]})}const e=(t,o)=>o.flow-t.flow;return[...o.values()].forEach((t=>{t.from=t.from.sort(e),t.from.forEach((t=>{t.node=o.get(t.key)})),t.to=t.to.sort(e),t.to.forEach((t=>{t.node=o.get(t.key)}))})),o}(l),{column:y,priority:p,size:m}=this.getDataset();if(p)for(const t of d.values())t.key in p&&(t.priority=p[t.key]);if(y)for(const t of d.values())t.key in y&&(t.column=!0,t.x=y[t.key]);const{maxX:g,maxY:M}=u(d,l,!!p,e(m));this._maxX=g,this._maxY=M;for(let t=0,o=l.length;t<o;++t){const o=l[t],e=d.get(o.from),r=d.get(o.to),n=e.y+x(e.to,o.to,t),a=r.y+x(r.from,o.from,t);f.push({x:c.parse(e.x,t),y:h.parse(n,t),_custom:{from:e,to:r,x:c.parse(r.x,t),y:h.parse(a,t),height:h.parse(o.flow,t)}})}return f.slice(r,r+n)}getMinMax(t){return{min:0,max:t===this._cachedMeta.xScale?this._maxX:this._maxY}}update(t){const{data:o}=this._cachedMeta;this.updateElements(o,0,o.length,t)}updateElements(t,e,r,n){const{xScale:a,yScale:i}=this._cachedMeta,s=this.resolveDataElementOptions(e,n),l=this.getSharedOptions(n,t[e],s),c=this.getDataset(),h=o.valueOrDefault(c.borderWidth,1)/2+.5,f=o.valueOrDefault(c.nodeWidth,10);for(let o=e;o<e+r;o++){const e=this.getParsed(o),r=e._custom,s=i.getPixelForValue(e.y);this.updateElement(t[o],o,{x:a.getPixelForValue(e.x)+f+h,y:s,x2:a.getPixelForValue(r.x)-h,y2:i.getPixelForValue(r.y),from:r.from,to:r.to,progress:"reset"===n?0:1,height:Math.abs(i.getPixelForValue(e.y+r.height)-s),options:this.resolveDataElementOptions(o,n)},n)}this.updateSharedOptions(l,n)}_drawLabels(){const t=this._ctx,r=this._nodes||new Map,n=this.getDataset(),a=e(n.size),i=o.valueOrDefault(n.borderWidth,1),s=o.valueOrDefault(n.nodeWidth,10),l=n.labels,{xScale:c,yScale:h}=this._cachedMeta;t.save();const f=this.chart.chartArea;for(const o of r.values()){const e=c.getPixelForValue(o.x),r=h.getPixelForValue(o.y),d=Math[a](o.in||o.out,o.out||o.in),u=Math.abs(h.getPixelForValue(o.y+d)-r),x=l&&l[o.key]||o.key;let y=e;t.fillStyle=n.color||"black",t.textBaseline="middle",e<f.width/2?(t.textAlign="left",y+=s+i+4):(t.textAlign="right",y-=i+4),this._drawLabel(x,r,u,t,y)}t.restore()}_drawLabel(t,e,r,n,a){const i=o.toFont(this.options.font,this.chart.options.font),s=o.isNullOrUndef(t)?[]:function(t){const e=[],r=o.isArray(t)?t:o.isNullOrUndef(t)?[]:[t];for(;r.length;){const t=r.pop();"string"==typeof t?e.unshift.apply(e,t.split("\n")):Array.isArray(t)?r.push.apply(r,t):o.isNullOrUndef(r)||e.unshift(""+t)}return e}(t),l=s.length,c=e+r/2,h=i.lineHeight,f=o.valueOrDefault(this.options.padding,h/2);if(n.font=i.string,l>1){const t=c-h*l/2+f;for(let o=0;o<l;o++)n.fillText(s[o],a,t+o*h)}else n.fillText(t,a,c)}_drawNodes(){const t=this._ctx,r=this._nodes||new Map,n=this.getDataset(),a=e(n.size),{xScale:i,yScale:s}=this._cachedMeta,l=o.valueOrDefault(n.borderWidth,1),c=o.valueOrDefault(n.nodeWidth,10);t.save(),t.strokeStyle=n.borderColor||"black",t.lineWidth=l;for(const o of r.values()){t.fillStyle=o.color;const e=i.getPixelForValue(o.x),r=s.getPixelForValue(o.y),n=Math[a](o.in||o.out,o.out||o.in),h=Math.abs(s.getPixelForValue(o.y+n)-r);l&&t.strokeRect(e,r,c,h),t.fillRect(e,r,c,h)}t.restore()}draw(){const t=this._ctx,o=this.getMeta().data||[],e=[];for(let t=0,r=o.length;t<r;++t){const r=o[t];r.from.color=r.options.colorFrom,r.to.color=r.options.colorTo,r.active&&e.push(r)}for(const t of e)t.from.color=t.options.colorFrom,t.to.color=t.options.colorTo;this._drawNodes();for(let e=0,r=o.length;e<r;++e)o[e].draw(t);this._drawLabels()}}y.id="sankey",y.defaults={dataElementType:"flow",animations:{numbers:{type:"number",properties:["x","y","x2","y2","height"]},progress:{easing:"linear",duration:t=>"data"===t.type?200*(t.parsed._custom.x-t.parsed.x):void 0,delay:t=>"data"===t.type?500*t.parsed.x+20*t.dataIndex:void 0},colors:{type:"color",properties:["colorFrom","colorTo"]}},transitions:{hide:{animations:{colors:{type:"color",properties:["colorFrom","colorTo"],to:"transparent"}}},show:{animations:{colors:{type:"color",properties:["colorFrom","colorTo"],from:"transparent"}}}}},y.overrides={interaction:{mode:"nearest",intersect:!0},datasets:{clip:!1,parsing:!0},plugins:{tooltip:{callbacks:{title:()=>"",label(t){const o=t.dataset.data[t.dataIndex];return o.from+" -> "+o.to+": "+o.flow}}},legend:{display:!1}},scales:{x:{type:"linear",bounds:"data",display:!1,min:0,offset:!1},y:{type:"linear",bounds:"data",display:!1,min:0,reverse:!0,offset:!1}},layout:{padding:{top:3,left:3,right:13,bottom:3}}};const p=(t,o,e,r)=>t<e?{cp1:{x:t+(e-t)/3*2,y:o},cp2:{x:t+(e-t)/3,y:r}}:{cp1:{x:t-(t-e)/3,y:0},cp2:{x:e+(t-e)/3,y:0}},m=(t,o,e)=>({x:t.x+e*(o.x-t.x),y:t.y+e*(o.y-t.y)});class g extends t.Element{constructor(t){super(),this.options=void 0,this.x=void 0,this.y=void 0,this.x2=void 0,this.y2=void 0,this.height=void 0,t&&Object.assign(this,t)}draw(t){const{x:e,x2:r,y:n,y2:a,height:i,progress:s}=this,{cp1:l,cp2:c}=p(e,n,r,a);0!==s&&(t.save(),s<1&&(t.beginPath(),t.rect(e,Math.min(n,a),(r-e)*s+1,Math.abs(a-n)+i+1),t.clip()),function(t,{x:e,x2:r,options:n}){let a;"from"===n.colorMode?a=o.color(n.colorFrom).alpha(.5).rgbString():"to"===n.colorMode?a=o.color(n.colorTo).alpha(.5).rgbString():(a=t.createLinearGradient(e,0,r,0),a.addColorStop(0,o.color(n.colorFrom).alpha(.5).rgbString()),a.addColorStop(1,o.color(n.colorTo).alpha(.5).rgbString())),t.fillStyle=a,t.strokeStyle=a,t.lineWidth=.5}(t,this),t.beginPath(),t.moveTo(e,n),t.bezierCurveTo(l.x,l.y,c.x,c.y,r,a),t.lineTo(r,a+i),t.bezierCurveTo(c.x,c.y+i,l.x,l.y+i,e,n+i),t.lineTo(e,n),t.stroke(),t.closePath(),t.fill(),t.restore())}inRange(t,o,e){const{x:r,y:n,x2:a,y2:i,height:s}=this.getProps(["x","y","x2","y2","height"],e);if(t<r||t>a)return!1;const{cp1:l,cp2:c}=p(r,n,a,i),h=(t-r)/(a-r),f={x:a,y:i},d=m({x:r,y:n},l,h),u=m(l,c,h),x=m(c,f,h),y=m(d,u,h),g=m(u,x,h),M=m(y,g,h).y;return o>=M&&o<=M+s}inXRange(t,o){const{x:e,x2:r}=this.getProps(["x","x2"],o);return t>=e&&t<=r}inYRange(t,o){const{y:e,y2:r,height:n}=this.getProps(["y","y2","height"],o),a=Math.min(e,r),i=Math.max(e,r)+n;return t>=a&&t<=i}getCenterPoint(t){const{x:o,y:e,x2:r,y2:n,height:a}=this.getProps(["x","y","x2","y2","height"],t);return{x:(o+r)/2,y:(e+n+a)/2}}tooltipPosition(t){return this.getCenterPoint(t)}getRange(t){return"x"===t?this.width/2:this.height/2}}g.id="flow",g.defaults={colorFrom:"red",colorTo:"green",colorMode:"gradient",hoverColorFrom:(t,e)=>o.getHoverColor(e.colorFrom),hoverColorTo:(t,e)=>o.getHoverColor(e.colorTo)},t.Chart.register(y,g)}));
7
+ !function(t,o){"object"==typeof exports&&"undefined"!=typeof module?o(require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["chart.js","chart.js/helpers"],o):o((t="undefined"!=typeof globalThis?globalThis:t||self).Chart,t.Chart.helpers)}(this,(function(t,o){"use strict";const e=t=>void 0!==t;function r(t){return t&&-1!==["min","max"].indexOf(t)?t:"max"}const n=(t,o)=>o.flow===t.flow?t.index-o.index:o.flow-t.flow;function s(t,{size:o,priority:e,column:s}){const i=new Map;for(let o=0;o<t.length;o++){const{from:e,to:r,flow:n}=t[o],s=i.get(e)??{key:e,in:0,out:0,size:0,from:[],to:[]},a=(e===r?s:i.get(r))??{key:r,in:0,out:0,size:0,from:[],to:[]};s.out+=n,s.to.push({key:r,flow:n,index:o,node:a,addY:0}),1===s.to.length&&i.set(e,s),a.in+=n,a.from.push({key:e,flow:n,index:o,node:s,addY:0}),1===a.from.length&&i.set(r,a)}return((t,o)=>{const e=r(o);for(const o of t.values())o.from.sort(n),o.to.sort(n),o.size=Math[e](o.in||o.out,o.out||o.in)})(i,o),((t,o)=>{if(o)for(const e of t.values())e.key in o&&(e.priority=o[e.key])})(i,e),((t,o)=>{if(o)for(const e of t.values())e.key in o&&(e.column=!0,e.x=o[e.key])})(i,s),i}const i=1e-6,a=(t,o=new Set)=>{const e=[];for(const r of t)o.has(r.key)||(o.add(r.key),e.push(r.key,...a(r.to.map((t=>t.node)),o)));return e},l=(t,o)=>{const e=o.filter((t=>0===t.from.length)),r=e.map((t=>t.key)),n=a(e),s=new Set(n);for(const o of t)s.has(o.from)||s.has(o.to)||(r.push(o.from),s.add(o.from)),s.add(o.to);return r},c=(t,o)=>{const e=new Set(t.filter((t=>o.has(t.from))).map((t=>t.to))),r=[...o],n=r.filter((t=>!e.has(t)));return n.length?n:r.slice(0,1)};let h=-1;function f(t,o,e=function(){return h=h<100?h+1:0,h}()){let r=0;for(const n of t)n.node._visited!==e&&(n.node._visited=e,r+=n.node[o].length+f(n.node[o],o,e));return r}const d=t=>(o,e)=>f(o.node[t],t)-f(e.node[t],t)||o.node[t].length-e.node[t].length;function u(t,o){if(!t.from.length)return o;t.from.sort(d("from"));for(const r of t.from){const t=r.node;e(t.y)||(t.y=o,u(t,o?o+i:0)),o=Math.max(t.y+t.out,o)}return t.y+t.size}function y(t,o){if(!t.to.length)return o;t.to.sort(d("to"));for(const r of t.to){const t=r.node;e(t.y)||(t.y=o,y(t,o?o+i:0)),o=Math.max(t.y+Math.max(t.in,t.out),o)}return t.y+t.size}function p(t,o){return e(t.y)?t.y:(t.y=o,o)}function x(t,o){if(!t.length)return 0;const r=((t,o)=>{const e=[...t].sort(((t,o)=>t.size-o.size)).pop().size,r=t.filter((t=>t.size===e));return 1===r.length?r[0]:(r.sort(((t,o)=>t.x-o.x)),0===r[0].x?r[0]:r[r.length-1].x===o?r.pop():r[Math.floor(r.length/2)])})(t,o);return r.y=0,u(r,0),y(r,0),function(t,o){const r=t.filter((t=>0===t.x)),n=t.filter((t=>t.x===o)),s=r.filter((t=>!e(t.y))),a=n.filter((t=>!e(t.y))),l=t.filter((t=>t.x>0&&t.x<o&&!e(t.y)));let c=r.reduce(((t,o)=>Math.max(t,o.y+o.out||0)),0)+i,h=n.reduce(((t,o)=>Math.max(t,o.y+o.in||0)),0)+i,f=0;c>=h?(s.forEach((t=>{c=p(t,c),c=Math.max(c+t.out,y(t,c))})),a.forEach((t=>{h=p(t,h),h=Math.max(h+t.in,u(t,h))}))):(s.forEach((t=>{c=p(t,c)})),a.forEach((t=>{h=p(t,h),h=Math.max(h+t.in,u(t,h))}))),l.forEach((o=>{let r=t.filter((t=>t.x===o.x&&e(t.y))).reduce(((t,o)=>Math.max(t,o.y+Math.max(o.in,o.out))),0);r=p(o,r),r=Math.max(r+o.in,u(o,r)),r=Math.max(r+o.out,y(o,r)),f=Math.max(f,r)})),Math.max(c,h,f)}(t,o),((t,o)=>{let e=0;for(let r=0;r<=o;r++){const o=t.filter((t=>t.x===r)).sort(((t,o)=>t.y-o.y));let n=0;for(const t of o)t.y<n&&(t.y=n),n=t.y+t.size;e=Math.max(e,n)}return e})(t,o)}const m=(t,o)=>t.x!==o.x?t.x-o.x:t.y===o.y?t.size-o.size:t.y-o.y;function g(t,o,{priority:r,height:n,nodePadding:s,modeX:i}){const a=[...t.values()],h=function(t,o,r){const n=o.filter((t=>t.from!==t.to)),s=[...t.keys()],i=[...t.values()],a=new Set(s);let h=0;for(;a.size;){const r=0===h?l(o,i):c(n,a);if(!r.length)throw new Error("Fatal error: Unable to place nodes to columns. Please report this issue.");for(const o of r){const r=t.get(o);r&&!e(r.x)&&(r.x=h),a.delete(o)}a.size&&h++}const f=i.reduce(((t,o)=>Math.max(t,o.x)),0);if("edge"===r){const e=new Set(o.map((t=>t.from)));s.filter((t=>!e.has(t))).forEach((o=>{const e=t.get(o);e&&!e.column&&(e.x=f)}))}return f}(t,o,i),f=r?function(t,o){let e=0,r=0;for(let n=0;n<=o;n++){let o=r;const s=t.filter((t=>t.x===n)).sort(((t,o)=>(t.priority??0)-(o.priority??0)));r=s.length&&s[0].to.filter((t=>t.node.x>n+1)).reduce(((t,o)=>t+o.flow),0)||0;for(const t of s)t.y=o,o+=Math.max(t.out,t.in);e=Math.max(o,e)}return e}(a,h):x(a,h),d=function(t,o){let e=0;const r=new Map,n=[];t.sort(m);for(const i of t){const t=(s=i.x,r.has(s)||(r.set(s,n.length),n.push([])),r.get(s)),a=n[t];if(i.y){a.push(i.y);let e=a.length;if(i.in){for(let o=0;o<t;o++){const t=n[o];for(let o=0;o<t.length&&!(t[o]>i.y);o++)e=Math.max(o+1,e)}for(;a.length<e;)a.push(i.y)}i.y+=e*o}e=Math.max(e,i.y+Math.max(i.in,i.out))}var s;return e}(a,f/n*s);return function(t){t.forEach((t=>{const o=t.size,e=o<t.in,r=o<t.out;let n=0,s=t.from.length;t.from.sort(((t,o)=>t.node.y+t.node.out/2-(o.node.y+o.node.out/2))).forEach(((t,r)=>{e?t.addY=r*(o-t.flow)/(s-1):(t.addY=n,n+=t.flow)})),n=0,s=t.to.length,t.to.sort(((t,o)=>t.node.y+t.node.in/2-(o.node.y+o.node.in/2))).forEach(((t,e)=>{r?t.addY=e*(o-t.flow)/(s-1):(t.addY=n,n+=t.flow)}))}))}(a),{maxX:h,maxY:d}}function M(t,o,e){for(const r of t)if(r.key===o&&r.index===e)return r.addY;return 0}class w extends t.DatasetController{parseObjectData(t,o,e,r){const n=((t,o)=>{const{from:e="from",to:r="to",flow:n="flow"}=o;return t.map((({[e]:t,[r]:o,[n]:s})=>({from:t,to:o,flow:s})))})(o,this.options.parsing),{xScale:i,yScale:a}=t,l=[],c=this._nodes=s(n,this.options),{maxX:h,maxY:f}=g(c,n,{priority:!!this.options.priority,height:this.chart.canvas.height,nodePadding:this.options.nodePadding,modeX:this.options.modeX});if(this._maxX=h,this._maxY=f,!i||!a)return[];for(let t=0,o=n.length;t<o;++t){const o=n[t],e=c.get(o.from),r=c.get(o.to);if(!e||!r)continue;const s=(e.y??0)+M(e.to,o.to,t),h=(r.y??0)+M(r.from,o.from,t);l.push({x:i.parse(e.x,t),y:a.parse(s,t),_custom:{from:e,to:r,x:i.parse(r.x,t),y:a.parse(h,t),height:a.parse(o.flow,t),flow:o.flow}})}return l.slice(e,e+r)}getMinMax(t){return{min:0,max:t===this._cachedMeta.xScale?this._maxX:this._maxY}}update(t){const{data:o}=this._cachedMeta;this.updateElements(o,0,o.length,t)}updateElements(t,o,e,r){const{xScale:n,yScale:s}=this._cachedMeta;if(!n||!s)return;const i=this.resolveDataElementOptions(o,r),a=this.getSharedOptions(i),{borderWidth:l,nodeWidth:c=10}=this.options,h=l?l/2+.5:0;for(let i=o;i<o+e;i++){const o=this.getParsed(i),e=o._custom,a=s.getPixelForValue(o.y);this.updateElement(t[i],i,{x:n.getPixelForValue(o.x)+c+h,y:a,x2:n.getPixelForValue(e.x)-h,y2:s.getPixelForValue(e.y),from:e.from,to:e.to,progress:"reset"===r?0:1,height:Math.abs(s.getPixelForValue(o.y+e.height)-a),options:this.resolveDataElementOptions(i,r)},r)}this.updateSharedOptions(a,r,i)}_drawLabels(){const t=this.chart.ctx,o=this.options,e=this._nodes||new Map,n=r(o.size),s=o.borderWidth??1,i=o.nodeWidth??10,a=o.labels,{xScale:l,yScale:c}=this._cachedMeta;if(!l||!c)return;t.save();const h=this.chart.chartArea;for(const r of e.values()){const e=l.getPixelForValue(r.x),f=c.getPixelForValue(r.y),d=Math[n](r.in||r.out,r.out||r.in),u=Math.abs(c.getPixelForValue(r.y+d)-f),y=a?.[r.key]??r.key;let p=e;t.fillStyle=o.color??"black",t.textBaseline="middle",e<h.width/2?(t.textAlign="left",p+=i+s+4):(t.textAlign="right",p-=s+4),this._drawLabel(y,f,u,t,p)}t.restore()}_drawLabel(t,e,r,n,s){const i=o.toFont(this.options.font,this.chart.options.font),a=function(t){if(!t)return[];const o=[],e=Array.isArray(t)?t:[t];for(;e.length;){const t=e.pop();"string"==typeof t?o.unshift(...t.split("\n")):Array.isArray(t)?e.push(...t):t&&o.unshift(`${t}`)}return o}(t),l=a.length,c=e+r/2,h=i.lineHeight,f=o.valueOrDefault(this.options.padding,h/2);if(n.font=i.string,l>1){const t=c-h*l/2+f;for(let o=0;o<l;o++)n.fillText(a[o],s,t+o*h)}else n.fillText(t,s,c)}_drawNodes(){const t=this.chart.ctx,o=this._nodes||new Map,{borderColor:e,borderWidth:n=0,nodeWidth:s=10,size:i}=this.options,a=r(i),{xScale:l,yScale:c}=this._cachedMeta;t.save(),e&&n&&(t.strokeStyle=e,t.lineWidth=n);for(const e of o.values()){t.fillStyle=e.color??"black";const o=l.getPixelForValue(e.x),r=c.getPixelForValue(e.y),i=Math[a](e.in||e.out,e.out||e.in),h=Math.abs(c.getPixelForValue(e.y+i)-r);n&&t.strokeRect(o,r,s,h),t.fillRect(o,r,s,h)}t.restore()}draw(){const t=this.chart.ctx,o=this.getMeta().data??[],e=[];for(let t=0,r=o.length;t<r;++t){const r=o[t];r.from.color=r.options.colorFrom,r.to.color=r.options.colorTo,r.active&&e.push(r)}for(const t of e)t.from.color=t.options.colorFrom,t.to.color=t.options.colorTo;this._drawNodes();for(let e=0,r=o.length;e<r;++e)o[e].draw(t);this._drawLabels()}}w.id="sankey",w.defaults={dataElementType:"flow",animations:{numbers:{type:"number",properties:["x","y","x2","y2","height"]},progress:{easing:"linear",duration:t=>"data"===t.type?200*(t.parsed._custom.x-t.parsed.x):void 0,delay:t=>"data"===t.type?500*t.parsed.x+20*t.dataIndex:void 0},colors:{type:"color",properties:["colorFrom","colorTo"]}},color:"black",borderColor:"black",borderWidth:1,modeX:"edge",nodeWidth:10,nodePadding:10,transitions:{hide:{animations:{colors:{type:"color",properties:["colorFrom","colorTo"],to:"transparent"}}},show:{animations:{colors:{type:"color",properties:["colorFrom","colorTo"],from:"transparent"}}}}},w.overrides={interaction:{mode:"nearest",intersect:!0},datasets:{clip:!1,parsing:{from:"from",to:"to",flow:"flow"}},plugins:{tooltip:{callbacks:{title:()=>"",label(t){const o=t.parsed._custom;return o.from.key+" -> "+o.to.key+": "+o.flow}}},legend:{display:!1}},scales:{x:{type:"linear",bounds:"data",display:!1,min:0,offset:!1},y:{type:"linear",bounds:"data",display:!1,min:0,reverse:!0,offset:!1}},layout:{padding:{top:3,left:3,right:13,bottom:3}}};const b=(t,o,e,r)=>t<e?{cp1:{x:t+(e-t)/3*2,y:o},cp2:{x:t+(e-t)/3,y:r}}:{cp1:{x:t-(t-e)/3,y:0},cp2:{x:e+(t-e)/3,y:0}},v=(t,o,e)=>({x:t.x+e*(o.x-t.x),y:t.y+e*(o.y-t.y)}),k=(t,e)=>o.color(t).alpha(e).rgbString(),P=(t,o)=>"string"==typeof t?k(t,o):t;class _ extends t.Element{draw(t){const{x:o,x2:e,y:r,y2:n,height:s,progress:i}=this,{cp1:a,cp2:l}=b(o,r,e,n);0!==i&&(t.save(),i<1&&(t.beginPath(),t.rect(o,Math.min(r,n),(e-o)*i+1,Math.abs(n-r)+s+1),t.clip()),function(t,{x:o,x2:e,options:r}){let n="black";"from"===r.colorMode?n=P(r.colorFrom,r.alpha):"to"===r.colorMode?n=P(r.colorTo,r.alpha):"string"==typeof r.colorFrom&&"string"==typeof r.colorTo&&(n=t.createLinearGradient(o,0,e,0),n.addColorStop(0,k(r.colorFrom,r.alpha)),n.addColorStop(1,k(r.colorTo,r.alpha))),t.fillStyle=n,t.strokeStyle=n,t.lineWidth=.5}(t,this),t.beginPath(),t.moveTo(o,r),t.bezierCurveTo(a.x,a.y,l.x,l.y,e,n),t.lineTo(e,n+s),t.bezierCurveTo(l.x,l.y+s,a.x,a.y+s,o,r+s),t.lineTo(o,r),t.stroke(),t.closePath(),t.fill(),t.restore())}inRange(t,o,e){const{x:r,y:n,x2:s,y2:i,height:a}=this.getProps(["x","y","x2","y2","height"],e);if(t<r||t>s)return!1;const{cp1:l,cp2:c}=b(r,n,s,i),h=(t-r)/(s-r),f={x:s,y:i},d=v({x:r,y:n},l,h),u=v(l,c,h),y=v(c,f,h),p=v(d,u,h),x=v(u,y,h),m=v(p,x,h).y;return o>=m&&o<=m+a}inXRange(t,o){const{x:e,x2:r}=this.getProps(["x","x2"],o);return t>=e&&t<=r}inYRange(t,o){const{y:e,y2:r,height:n}=this.getProps(["y","y2","height"],o),s=Math.min(e,r),i=Math.max(e,r)+n;return t>=s&&t<=i}getCenterPoint(t){const{x:o,y:e,x2:r,y2:n,height:s}=this.getProps(["x","y","x2","y2","height"],t);return{x:(o+r)/2,y:(e+n+s)/2}}tooltipPosition(t){return this.getCenterPoint(t)}getRange(t){return"x"===t?this.width/2:this.height/2}constructor(t){super(),t&&Object.assign(this,t)}}_.id="flow",_.defaults={colorFrom:"red",colorTo:"green",colorMode:"gradient",alpha:.5,hoverColorFrom:(t,e)=>o.getHoverColor(e.colorFrom),hoverColorTo:(t,e)=>o.getHoverColor(e.colorTo)},_.descriptors={_scriptable:!0},t.Chart.register(w,_)}));
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "chartjs-chart-sankey",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
4
4
  "description": "Chart.js module for creating sankey diagrams",
5
5
  "main": "dist/chartjs-chart-sankey.js",
6
6
  "module": "dist/chartjs-chart-sankey.esm.js",
7
+ "type": "module",
7
8
  "types": "types/index.esm.d.ts",
8
9
  "exports": {
9
10
  "types": "./types/index.esm.d.ts",
@@ -14,15 +15,13 @@
14
15
  "scripts": {
15
16
  "build": "rollup -c",
16
17
  "autobuild": "rollup -c -w",
17
- "dev": "karma start --no-single-run --auto-watch --browsers chrome",
18
- "dev:ff": "karma start --no-single-run --auto-watch --browsers firefox",
19
- "lint": "concurrently --group \"npm:lint-*\"",
20
- "lint-js": "eslint \"src/**/*.js\" \"test/**/*.js\"",
21
- "lint-md": "eslint \"**/*.md\"",
22
- "lint-types": "eslint \"types/**/*.ts\" && tsc -p types/tests/",
23
- "test": "cross-env NODE_ENV=test concurrently --group \"npm:test-*\"",
24
- "test-lint": "npm run lint",
25
- "test-karma": "karma start --no-auto-watch --single-run"
18
+ "dev": "karma start ./karma.conf.cjs --no-single-run --auto-watch --browsers chrome",
19
+ "dev:ff": "karma start ./karma.conf.cjs --no-single-run --auto-watch --browsers firefox",
20
+ "lint": "eslint",
21
+ "typecheck": "tsc --noEmit && tsc --noEmit -p types/tests/",
22
+ "test": "npm run test:unit && npm run test:fixture",
23
+ "test:unit": "jest",
24
+ "test:fixture": "cross-env NODE_ENV=test karma start ./karma.conf.cjs --no-auto-watch --single-run"
26
25
  },
27
26
  "repository": {
28
27
  "type": "git",
@@ -44,20 +43,29 @@
44
43
  },
45
44
  "homepage": "https://github.com/kurkle/chartjs-chart-sankey#readme",
46
45
  "devDependencies": {
47
- "@rollup/plugin-node-resolve": "^15.0.1",
46
+ "@eslint/eslintrc": "^3.1.0",
47
+ "@eslint/js": "^9.13.0",
48
+ "@eslint/markdown": "^6.2.1",
49
+ "@jest/globals": "^29.7.0",
50
+ "@rollup/plugin-node-resolve": "^15.3.0",
51
+ "@rollup/plugin-swc": "^0.4.0",
48
52
  "@rollup/plugin-terser": "^0.4.0",
49
- "@typescript-eslint/eslint-plugin": "^5.10.2",
50
- "@typescript-eslint/parser": "^5.10.2",
51
- "chart.js": "^4.0.1",
53
+ "@swc/core": "^1.7.40",
54
+ "@types/jest": "^29.5.14",
55
+ "@types/node": "^22.8.1",
56
+ "@typescript-eslint/eslint-plugin": "^8.11.0",
57
+ "@typescript-eslint/parser": "^8.11.0",
58
+ "chart.js": "^4.4.5",
52
59
  "chartjs-test-utils": "^0.5.0",
53
- "concurrently": "^8.0.1",
54
60
  "cross-env": "^7.0.3",
55
- "eslint": "^8.8.0",
61
+ "eslint": "^9.13.0",
56
62
  "eslint-config-chartjs": "^0.3.0",
57
- "eslint-plugin-es": "^4.1.0",
58
- "eslint-plugin-html": "^7.1.0",
59
- "eslint-plugin-markdown": "^3.0.0",
60
- "jasmine-core": "^4.0.0",
63
+ "eslint-config-prettier": "^9.1.0",
64
+ "eslint-plugin-prettier": "^5.2.1",
65
+ "eslint-plugin-simple-import-sort": "^12.1.1",
66
+ "eslint-plugin-unused-imports": "^4.1.4",
67
+ "globals": "^15.11.0",
68
+ "jest": "^29.7.0",
61
69
  "karma": "^6.3.2",
62
70
  "karma-chrome-launcher": "^3.1.0",
63
71
  "karma-coverage": "^2.0.3",
@@ -66,8 +74,11 @@
66
74
  "karma-jasmine-html-reporter": "^2.0.0",
67
75
  "karma-rollup-preprocessor": "7.0.7",
68
76
  "karma-spec-reporter": "^0.0.36",
69
- "rollup": "^3.3.0",
70
- "rollup-plugin-istanbul": "^4.0.0",
77
+ "rollup": "^4.24.0",
78
+ "rollup-plugin-cleanup": "^3.2.1",
79
+ "rollup-plugin-istanbul": "^5.0.0",
80
+ "ts-jest": "^29.2.5",
81
+ "ts-node": "^10.9.2",
71
82
  "typescript": "^5.0.4"
72
83
  },
73
84
  "peerDependencies": {
@@ -1,45 +1,52 @@
1
1
  import {
2
+ CartesianScaleTypeRegistry,
2
3
  Chart,
3
4
  ChartComponent,
4
- CartesianScaleTypeRegistry,
5
+ Color,
5
6
  DatasetController,
6
7
  Element,
7
8
  FontSpec,
9
+ Scriptable,
8
10
  ScriptableContext,
9
- VisualElement
10
- } from 'chart.js';
11
+ VisualElement,
12
+ } from 'chart.js'
11
13
 
12
- type AnyObject = Record<string, unknown>;
14
+ type AnyObject = Record<string, unknown>
13
15
 
14
16
  declare module 'chart.js' {
15
-
16
17
  /* raw data element */
17
18
  interface SankeyDataPoint {
18
- from: string;
19
- to: string;
20
- flow: number;
19
+ from: string
20
+ to: string
21
+ flow: number
21
22
  }
22
23
 
23
24
  /* dataset configuration */
24
25
  interface SankeyControllerDatasetOptions {
25
- label: string;
26
- data: Array<SankeyDataPoint>;
27
- colorFrom: (data: ScriptableContext<'sankey'>) => string;
28
- colorTo: (data: ScriptableContext<'sankey'>) => string;
29
- colorMode: 'gradient' | 'from' | 'to';
26
+ label: string
27
+ data: Array<SankeyDataPoint>
28
+ colorFrom: (data: ScriptableContext<'sankey'>) => string
29
+ colorTo: (data: ScriptableContext<'sankey'>) => string
30
+ colorMode: 'gradient' | 'from' | 'to'
31
+ hoverColorFrom?: Scriptable<string, ScriptableContext<'sankey'>>
32
+ hoverColorTo?: Scriptable<string, ScriptableContext<'sankey'>>
30
33
  /* Map<node.key, priority_value> */
31
34
  priority?: Record<string, number>
32
35
  column?: Record<string, number>
33
36
  /* Map<node.key, label> */
34
37
  labels?: Record<string, string>
35
38
 
36
- size?: 'min' | 'max' /* defaults to max */
37
- borderWidth?: number /* defaults to 1 */
38
- nodeWidth?: number /* defaults to 10 */
39
- color?: string /* defaults to 'black' */
40
- borderColor?: string /* defaults to 'black' */
41
- font?: FontSpec /* defaults to chart.options.font */
42
- padding?: number /* defaults to font.lineHeight / 2 */
39
+ modeX?: 'edge' | 'even' /* defaults to 'edge' */
40
+ size?: 'min' | 'max' /* defaults to 'max' */
41
+ borderWidth?: number /* defaults to 1 */
42
+ nodeWidth?: number /* defaults to 10 */
43
+ nodePadding?: number /* defaults to 10 (pixels) */
44
+ color?: string /* defaults to 'black' */
45
+ borderColor?: string /* defaults to 'black' */
46
+ font?: FontSpec /* defaults to chart.options.font */
47
+ padding?: number /* defaults to font.lineHeight / 2 */
48
+
49
+ parsing: { from: string; to: string; flow: string }
43
50
  }
44
51
 
45
52
  type FromToElement = {
@@ -47,6 +54,7 @@ declare module 'chart.js' {
47
54
  flow: number
48
55
  key: string
49
56
  node: SankeyNode
57
+ index: number
50
58
  }
51
59
 
52
60
  type SankeyNode = {
@@ -56,6 +64,8 @@ declare module 'chart.js' {
56
64
  in: number
57
65
  /* number of out => connections */
58
66
  out: number
67
+ /* node size, based on size option */
68
+ size: number
59
69
  from: Array<FromToElement>
60
70
  to: Array<FromToElement>
61
71
  /* true if x is defined by SankeyControllerDatasetOptions.column map */
@@ -64,60 +74,75 @@ declare module 'chart.js' {
64
74
  priority?: number
65
75
  y?: number
66
76
  x?: number
77
+ color?: Color
78
+ /** internal */
79
+ _visited?: number
67
80
  }
68
81
 
69
82
  interface SankeyParsedData {
70
83
  x: number
71
84
  y: number
72
85
  _custom: {
73
- from: SankeyNode,
86
+ from: SankeyNode
74
87
  to: SankeyNode
75
88
  x: number
76
89
  y: number
77
- height: number,
90
+ height: number
91
+ flow: number
78
92
  }
79
93
  }
80
94
 
81
95
  interface ChartTypeRegistry {
82
96
  sankey: {
83
- datasetOptions: SankeyControllerDatasetOptions;
84
- defaultDataPoint: SankeyDataPoint;
85
- parsedDataType: SankeyParsedData;
97
+ datasetOptions: SankeyControllerDatasetOptions
98
+ defaultDataPoint: SankeyDataPoint
99
+ parsedDataType: SankeyParsedData
86
100
  metaExtensions: AnyObject
87
101
  /* TODO: define sankey chart options */
88
- chartOptions: AnyObject;
89
- scales: keyof CartesianScaleTypeRegistry;
90
- };
102
+ chartOptions: FlowOptions
103
+ scales: keyof CartesianScaleTypeRegistry
104
+ }
91
105
  }
92
106
  }
93
107
 
108
+ export interface FlowProps {
109
+ x: number
110
+ y: number
111
+ x2: number
112
+ y2: number
113
+ height: number
114
+ width: number
115
+ }
116
+
94
117
  export interface FlowOptions {
95
- colorMode: 'gradient' | 'from' | 'to';
96
- colorFrom: string
97
- colorTo: string
118
+ alpha: number
119
+ colorMode: 'gradient' | 'from' | 'to'
120
+ colorFrom: 'string'
121
+ colorTo: Color
122
+ hoverColorFrom: Color
123
+ hoverColorTo: Color
98
124
  }
99
125
 
100
126
  export interface FlowConfig {
101
- x: number;
102
- y: number;
103
- x2: number;
104
- y2: number;
105
- height: number;
127
+ x: number
128
+ y: number
129
+ x2: number
130
+ y2: number
131
+ height: number
106
132
  options: FlowOptions
107
133
  }
108
134
 
109
135
  export type SankeyController = DatasetController
110
136
  export const SankeyController: ChartComponent & {
111
- prototype: SankeyController;
112
- new(chart: Chart, datasetIndex: number): SankeyController;
113
- };
137
+ prototype: SankeyController
138
+ new (chart: Chart, datasetIndex: number): SankeyController
139
+ }
114
140
 
115
- export interface Flow<
116
- T extends FlowConfig = FlowConfig,
117
- O extends FlowOptions = FlowOptions
118
- > extends Element<T, O>, VisualElement {}
141
+ export interface Flow<T extends FlowConfig = FlowConfig, O extends FlowOptions = FlowOptions>
142
+ extends Element<T, O>,
143
+ VisualElement {}
119
144
 
120
145
  export const Flow: ChartComponent & {
121
- prototype: Flow;
122
- new(cfg: FlowConfig): Flow;
123
- };
146
+ prototype: Flow
147
+ new (cfg: FlowConfig): Flow
148
+ }