UNPKG

chartjs-chart-treenode-test

Version:
7 lines (6 loc) 9.3 kB
/*! * chartjs-chart-treenode v1.0.0 * (c) 2025 Francesco Riva * Released under the MIT License */ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).ChartTreeNode=t()}(this,function(){"use strict";const e={id:"treeGraph",wrapText(e,t,o){if(!t||0===t.length)return[""];if(e.measureText(t).width<=o)return[t];const a=t.split(" ");if(1===a.length)return[t];const r=[];let n="";for(let t=0;t<a.length;t++){const l=a[t],s=n+(n?" ":"")+l;e.measureText(s).width<=o?n=s:n?(r.push(n),n=l):n=l}return n&&r.push(n),r.length>0?r:[t]},calculateMaxLabelWidth(e){if(!e._treeGraphData)return 100;const t=e.chartArea;if(!0===e.data.datasets[0].vertical){const o=t.bottom-t.top-80,a=e._treeGraphData.maxDepth||1,r=o/Math.max(1,a);return Math.max(60,.7*r)}{const o=t.right-t.left-100,a=e._treeGraphData.maxDepth||1,r=o/Math.max(1,a);return Math.max(60,.7*r)}},setupNodeCoordinates(e){if("scatter"!==e.config.type)return;const t=e.data.datasets[0],o=t.data;if(!o||!Array.isArray(o))return;function a(e,t){e.sort((e,o)=>{const a=t[e.label]??Number.MAX_SAFE_INTEGER,r=t[o.label]??Number.MAX_SAFE_INTEGER;return a!==r?a-r:e.subtreeSize!==o.subtreeSize?o.subtreeSize-e.subtreeSize:e.label.localeCompare(o.label)});const o=1/(e.length+1);e.forEach((t,a)=>{t.y=o*(a+1),t.assignedSpace=function(e,t){const o=1/t,a=Math.log(e.subtreeSize||1)+1;return o*a}(t,e.length)})}function r(e,t){const o=function(e){const t=new Map;return e.forEach(e=>{const o=e.parents.map(e=>e.label).sort().join("|");t.has(o)||t.set(o,{parents:e.parents,children:[]}),t.get(o).children.push(e)}),Array.from(t.values())}(e);o.forEach(e=>{!function(e,t){const{parents:o,children:a}=e;a.sort((e,o)=>{const a=t[e.label]??Number.MAX_SAFE_INTEGER,r=t[o.label]??Number.MAX_SAFE_INTEGER;return a!==r?a-r:e.subtreeSize!==o.subtreeSize?o.subtreeSize-e.subtreeSize:e.label.localeCompare(o.label)});const r=function(e){if(0===e.length)return.5;const t=e.map(e=>e.y).filter(e=>null!==e);if(0===t.length)return.5;let o=0,a=0;return e.forEach(e=>{if(null!==e.y){const t=e.subtreeSize||1;a+=e.y*t,o+=t}}),o>0?a/o:.5}(o),n=function(e,t){const o=e.reduce((e,t)=>e+(t.subtreeSize||1),0),a=t.reduce((e,t)=>e+(t.subtreeSize||1),0),r=Math.min(.8,a/o*.6);return Math.max(.1,r)}(o,a);!function(e,t,o){if(0===e.length)return;if(1===e.length)return e[0].y=t,void(e[0].assignedSpace=o);const a=e.reduce((e,t)=>e+(t.subtreeSize||1),0);let r=-o/2;e.forEach(e=>{const n=(e.subtreeSize||1)/a,l=o*n;e.y=t+r+l/2,e.assignedSpace=l,r+=l})}(a,r,n)}(e,t)})}const n=function(e){const t={};return e.forEach(e=>{t[e.from]||(t[e.from]={label:e.from,children:[],parents:[]}),t[e.to]||(t[e.to]={label:e.to,children:[],parents:[]}),t[e.from].children.push(t[e.to]),t[e.to].parents.push(t[e.from])}),t}(o),{maxDepth:l}=function(e,t={}){let o;Object.values(e).forEach(e=>{e.depth=null,e.y=null,e.subtreeSize=0,e.children=e.children||[],e.parents=e.parents||[]}),Object.values(e).filter(e=>0===e.parents.length).forEach(e=>e.depth=0);do{o=!1,Object.values(e).forEach(e=>{if(0===e.parents.length)return;const t=Math.max(-1,...e.parents.map(e=>e.depth??-1))+1;(null===e.depth||e.depth<t)&&(e.depth=t,o=!0)})}while(o);const n=t.column||{};return Object.entries(e).forEach(([e,t])=>{void 0!==n[e]&&(t.depth=n[e])}),function(e){const t=new Set;function o(e){if(t.has(e.label))return e.subtreeSize;if(t.add(e.label),0===e.children.length)return e.subtreeSize=1,1;let a=1;return e.children.forEach(e=>{a+=o(e)}),e.subtreeSize=a,a}Object.values(e).forEach(e=>{t.has(e.label)||o(e)})}(e),function(e,t){const o=t.priority||{},n=new Map;Object.values(e).forEach(e=>{n.has(e.depth)||n.set(e.depth,[]),n.get(e.depth).push(e)});for(let e=0;e<=Math.max(...n.keys());e++){const t=n.get(e)||[];0===e?a(t,o):r(t,o)}}(e,t),function(e){const t=Object.values(e),o=t.map(e=>e.y).filter(e=>null!==e);if(0===o.length)return;const a=Math.min(...o),r=Math.max(...o)-a;0===r?t.forEach(e=>{null!==e.y&&(e.y=.5)}):t.forEach(e=>{null!==e.y&&(e.y=.1+(e.y-a)/r*.8)});!function(e){const t=new Map;Object.values(e).forEach(e=>{t.has(e.depth)||t.set(e.depth,[]),t.get(e.depth).push(e)}),t.forEach(e=>{if(e.length<=1)return;e.sort((e,t)=>e.y-t.y);const t=.08;for(let o=1;o<e.length;o++){const a=e[o-1],r=e[o],n=a.y+t;r.y<n&&(r.y=n)}const o=e[e.length-1];if(o.y>.9){const t=o.y-.9;e.forEach(e=>{e.y-=t})}})}(e)}(e),{maxDepth:Math.max(...Object.values(e).map(e=>e.depth)),maxY:1}}(n),s=Object.values(n);return s.forEach(e=>{const t=o.filter(t=>t.to===e.label),a=o.filter(t=>t.from===e.label);t.length>0?e.value=t.reduce((e,t)=>e+t.value,0):a.length>0?e.value=a[0].value:e.value=0}),e._treeGraphData={nodes:n,allNodes:s,maxDepth:l,links:o,toCanvasCoords:function(o,a){const r=e.chartArea,n=r.right-r.left-100,i=r.bottom-r.top-80,c=Math.min(...s.map(e=>e.y)),h=(a-c)/(Math.max(...s.map(e=>e.y))-c||1);return!0===t.vertical?{x:r.left+50+h*n,y:r.top+40+o/(l||1)*i}:{x:r.left+50+o/(l||1)*n,y:r.top+40+h*i}}},e._treeGraphData},setupMouseListener(e){if(e._tooltipHandlerAttached)return;const t=e.canvas;e._hoveredNode=null,t.addEventListener("mousemove",o=>{if(!e._treeGraphData)return;const a=t.getBoundingClientRect(),r=o.clientX-a.left,n=o.clientY-a.top;e._hoveredNode=null;for(const t of e._treeGraphData.allNodes){const{x:o,y:a}=e._treeGraphData.toCanvasCoords(t.depth,t.y),l=r-o,s=n-a;if(Math.sqrt(l*l+s*s)<=10){e._hoveredNode=t;break}}e.draw()}),e._tooltipHandlerAttached=!0},calculateTooltipPosition(e,t,o,a,r){const n=e.chartArea;let l=t-a/2,s=o-r-10,i="top";if(s<n.top+8&&(s=o+10,i="bottom"),l<n.left+8&&("top"===i||"bottom"===i?(l=t+10,s=o-r/2,i="right"):l=n.left+8),l+a>n.right-8&&("top"===i||"bottom"===i?(l=t-a-10,s=o-r/2,i="left"):l=n.right-a-8),s+r>n.bottom-8)if("bottom"===i){const e=o-r-10;e>=n.top+8?(s=e,i="top"):s=n.bottom-r-8}else"left"!==i&&"right"!==i||(s=Math.min(s,n.bottom-r-8));return"left"!==i&&"right"!==i||(s<n.top+8?s=n.top+8:s+r>n.bottom-8&&(s=n.bottom-r-8)),{x:l,y:s,position:i}},drawTooltip(e,t){const o=e.ctx,{x:a,y:r}=e._treeGraphData.toCanvasCoords(t.depth,t.y),n="Helvetica, Arial, sans-serif",l=[t.label,`${e.data.datasets[0].label||"Value"}: ${t.value}`];o.save(),o.font=`13px ${n}`;const s=Math.max(...l.map(e=>o.measureText(e).width))+16,i=17*l.length+16-4,{x:c,y:h,position:u}=this.calculateTooltipPosition(e,a,r,s,i);o.shadowColor="rgba(0, 0, 0, 0.2)",o.shadowBlur=4,o.shadowOffsetX=2,o.shadowOffsetY=2,o.fillStyle="rgba(0, 0, 0, 0.8)",this.drawRoundedRect(o,c,h,s,i,6),o.fill(),o.shadowColor="transparent",o.shadowBlur=0,o.shadowOffsetX=0,o.shadowOffsetY=0,o.strokeStyle="rgba(255, 255, 255, 0.2)",o.lineWidth=1,this.drawRoundedRect(o,c,h,s,i,6),o.stroke(),this.drawTooltipArrow(o,a,r,c,h,s,i,u),o.fillStyle="#ffffff",o.textAlign="left",o.textBaseline="top",l.forEach((e,t)=>{const a=h+8+17*t,r=c+8;0===t?(o.font=`bold 13px ${n}`,o.fillStyle="#ffffff"):(o.font=`13px ${n}`,o.fillStyle="#cccccc"),o.fillText(e,r,a)}),o.restore()},drawRoundedRect(e,t,o,a,r,n){e.beginPath(),e.moveTo(t+n,o),e.lineTo(t+a-n,o),e.quadraticCurveTo(t+a,o,t+a,o+n),e.lineTo(t+a,o+r-n),e.quadraticCurveTo(t+a,o+r,t+a-n,o+r),e.lineTo(t+n,o+r),e.quadraticCurveTo(t,o+r,t,o+r-n),e.lineTo(t,o+n),e.quadraticCurveTo(t,o,t+n,o),e.closePath()},drawTooltipArrow(e,t,o,a,r,n,l,s){switch(e.fillStyle="rgba(0, 0, 0, 0.8)",e.beginPath(),s){case"top":const t=a+n/2;e.moveTo(t-6,r+l),e.lineTo(t+6,r+l),e.lineTo(t,r+l+6);break;case"bottom":const o=a+n/2;e.moveTo(o-6,r),e.lineTo(o+6,r),e.lineTo(o,r-6);break;case"left":const s=r+l/2;e.moveTo(a+n,s-6),e.lineTo(a+n,s+6),e.lineTo(a+n+6,s);break;case"right":const i=r+l/2;e.moveTo(a,i-6),e.lineTo(a,i+6),e.lineTo(a-6,i)}e.closePath(),e.fill()},afterDraw(e){const t=this.setupNodeCoordinates(e);t&&(this.setupMouseListener(e),this.drawGraph(e,t))},afterResize(e){this.setupNodeCoordinates(e)},drawGraph(e,{allNodes:t,links:o,toCanvasCoords:a}){const r=e.ctx,n=e.data.datasets[0],l=!0===n.vertical;t.forEach(e=>{e.children.forEach(t=>{const o=a(e.depth,e.y),n=a(t.depth,t.y);if(r.save(),r.strokeStyle="#888",r.lineWidth=2,r.beginPath(),l){const e=.5*(n.y-o.y);r.moveTo(o.x,o.y),r.bezierCurveTo(o.x,o.y+e,n.x,n.y-e,n.x,n.y)}else{const e=.5*(n.x-o.x);r.moveTo(o.x,o.y),r.bezierCurveTo(o.x+e,o.y,n.x-e,n.y,n.x,n.y)}r.stroke(),r.restore()})}),t.forEach(t=>{const{x:o,y:s}=a(t.depth,t.y);r.save();const i=(n.nodeColors||{})[t.label]||"black",c=n.nodeRadius||10,h=n.nodeBorder||"0px",u=n.nodeBorderColor||"black";let f=0;"string"==typeof h&&h.endsWith("px")&&(f=parseInt(h.replace("px",""))),f>0&&(r.beginPath(),r.arc(o,s,c+f/2,0,2*Math.PI),r.fillStyle=u,r.fill()),r.beginPath(),r.arc(o,s,c,0,2*Math.PI),r.fillStyle=i,r.fill();const d=n.labelFontSize||12,p=n.labelFontWeight||"bold",b=n.labelFontFamily||"sans-serif";r.font=`${p} ${d}px ${b}`,r.fillStyle="#222",r.textAlign="center",r.textBaseline="top";const m=this.calculateMaxLabelWidth(e),y=n.showValue??!1,x=this.wrapText(r,t.label,m);y&&x.push(`(${t.value})`);const g=d+2;let v,T;l?(T=o+c+8,v=s-x.length*g/2,r.textAlign="left",r.textBaseline="top"):(T=o,v=s+c+4,r.textAlign="center",r.textBaseline="top"),x.forEach((e,t)=>{const o=v+t*g;r.fillText(e,T,o)}),r.restore()});const s=e._hoveredNode;s&&this.drawTooltip(e,s)}};return"undefined"!=typeof window&&window.Chart&&window.Chart.register(e),e});