@gramex/radar
Version:
A radial beeswarm or dot plot.
3 lines (2 loc) • 3.85 kB
JavaScript
var q=(t,s,i,e=c=>[c])=>t.selectAll(`${s}.${i}`).data(e).join(s).attr("class",i);function R(t,s,i){t=typeof t=="string"?document.querySelector(t):t;let e=t.closest("svg");return s=s??(e?.viewBox?.animVal?.width||e?.width?.animVal?.value)??0,i=i??(e?.viewBox?.animVal?.height||e?.height?.animVal?.value)??0,{el:t,container:e,width:s,height:i}}function N(t,{data:s,width:i,height:e,cx:c,cy:g,level:p="level",spoke:M="spoke",levels:b,spokes:E,levelScale:L,spokeScale:$,spokeLabelROffset:D=-10,nodeTag:C="circle",d3:G=globalThis.d3}){({el:t,width:i,height:e}=R(t._groups?t.node():t,i,e));let V,y,d,h;({cx:c,cy:g,level:p,spoke:M,levels:b,spokes:E,levelScale:L,spokeScale:$,cellData:V,levelData:y,spokeData:d,r:h}=z({data:s,width:i,height:e,cx:c,cy:g,level:p,spoke:M,levels:b,spokes:E,levelScale:L,spokeScale:$,d3:G}));let O=G.select(t),j=r=>q(O,"g",r).attr("transform",`translate(${c},${g})`),n=q(j("cells"),"path","cell",V).attr("d",({r0:r,r1:u,a0:f,a1:m})=>`M${P(r,f)} L${P(u,f)} A${u},${u} 0 0,1 ${P(u,m)} L${P(r,m)} A${r},${r} 0 0,0 ${P(r,f)}`),a=q(j("level-labels"),"text","level-label",y).text(({level:r})=>r).attr("text-anchor","middle").attr("dominant-baseline","ideographic").attr("transform",({r0:r,r1:u})=>`translate(${(r+u)/2},0)`),o=y.at(-1).r1+(D??0),l=q(j("spoke-labels"),"text","spoke-label",d).text(({spoke:r})=>r).attr("text-anchor","middle").attr("dominant-baseline","middle").attr("transform",({a0:r,a1:u})=>{let f=(r+u)/2,m=f*180/Math.PI;return`translate(${o*Math.cos(f)}, ${o*Math.sin(f)}) rotate(${0<=m&&m<=180?m-90:m+90})`}),w=q(j("nodes"),C,"node",s).attr("transform",r=>`translate(${r.x},${r.y})`);return{cells:n,levelLabels:a,spokeLabels:l,nodes:w,r:h}}function A(t,s,i,e){if(Array.isArray(t))return t;if(t)if(typeof t=="function"){let c=new Map;return s.forEach(g=>{let p=i(g);c.set(p,(c.get(p)||0)+1)}),t(c)}else throw new Error(`${e} must be an array or function, not ${t}`);else return Array.from(new Set(s.map(i)))}function z({data:t,width:s,height:i,cx:e,cy:c,level:g,spoke:p,levels:M,spokes:b,levelScale:E,spokeScale:L,d3:$}){[e,c]=[e??s/2,c??i/2],[g,p]=[B(g),B(p)],[M,b]=[A(M,t,g,"levels"),A(b,t,p,"spokes")];let D=Math.min(s,i)/2;E||(E=(n,a)=>[a*D/M.length,(a+1)*D/M.length]),L||(L=(n,a)=>[a*360/b.length,(a+1)*360/b.length]);let C=M.map((n,a)=>{let[o,l]=E(n,a);return{level:n,levelIndex:a,r0:o,r1:l}});D=$.max(C,n=>n.r1);let G=b.map((n,a)=>{let[o,l]=L(n,a).map(w=>w*Math.PI/180);return{spoke:n,spokeIndex:a,a0:o,a1:l}}),V=C.flatMap(n=>G.map(a=>Object.assign({},n,a))),y=$.index(V,n=>n.level,n=>n.spoke),d=$.flatRollup(t,n=>({n:n.length,data:n}),g,p).filter(([n,a])=>y.has(n)&&y.get(n).has(a)).map(([n,a,o])=>Object.assign(y.get(n).get(a),o)).sort((n,a)=>a.n-n.n),h=10,O=!1;for(let n=0;n<10&&!O;n++){V.forEach(o=>delete o.points),$.cross($.range(-h,h),$.range(-h,h),(o,l)=>({i:o,j:l})).map(({i:o,j:l})=>{let w=(o+.5)*D/h,r=(l+.5)*D/h,u=Math.sqrt(w*w+r*r),f=Math.atan2(r,w);f<0&&(f+=2*Math.PI);let m=V.find(({r0:_,r1:F,a0:T,a1:x})=>_<u&&u<F&&T<f&&f<x);return m&&(m.points=m.points||[]).push({i:o,j:l,x:w,y:r,r:u,a:f}),{i:o,j:l,x:w,y:r,r:u,a:f,cell:m}}),O=d.every(o=>o.points?.length>=o.n);let a=$.min(d,o=>o.points?.length/o.n);h+=Math.max(2,Math.ceil(h/Math.sqrt(a))-h)}if(!O)throw new Error("Cannot fit data. Check levelScale, spokeScale, missing values.");d.filter(n=>n.n).forEach(n=>{let a=H([n.points],n.n);n.data.forEach((o,l)=>Object.assign(o,{x:a[l].x,y:a[l].y}))});let j=D/h/2;return{cx:e,cy:c,level:g,spoke:p,levels:M,spokes:b,levelScale:E,spokeScale:L,cellData:V,levelData:C,spokeData:G,r:j}}function H(t,s,i=0){for(;i<s&&i<t.length;){let e=t[i],c=Math.floor(e.length/2);t.splice(i,1,e[c]),c>0&&t.push(e.slice(0,c)),c<e.length-1&&t.push(e.slice(c+1)),i++}return t}var P=(t,s)=>[t*Math.cos(s),t*Math.sin(s)].join(),B=t=>typeof t=="function"?t:s=>s[t];export{N as radar};
//# sourceMappingURL=radar.js.map