venn-helper
Version:
Area Proportional Venn and Euler Diagrams
8 lines (7 loc) • 14 kB
JavaScript
import{nelderMead as ee,bisect as ne,conjugateGradient as te,zeros as re,zerosM as V,norm2 as W,scale as ie}from"fmin";function O(e){var t=j(e),r=t.filter(function(b){return Z(b,e)}),n=0,a=0,i=[],s;if(r.length>1){var f=_(r);r=r.map(b=>({...b,angle:Math.atan2(b.x-f.x,b.y-f.y)})).sort((b,A)=>A.angle-b.angle);var u=r[r.length-1];for(s=0;s<r.length;++s){var o=r[s];a+=(u.x+o.x)*(o.y-u.y);for(var l={x:(o.x+u.x)/2,y:(o.y+u.y)/2},c=null,h=0;h<o.parentIndex.length;++h)if(u.parentIndex.indexOf(o.parentIndex[h])>-1){var g=e[o.parentIndex[h]],x=Math.atan2(o.x-g.x,o.y-g.y),d=Math.atan2(u.x-g.x,u.y-g.y),v=d-x;v<0&&(v+=2*Math.PI);var p=d-v/2,y=z(l,{x:g.x+g.radius*Math.sin(p),y:g.y+g.radius*Math.cos(p)});y>g.radius*2&&(y=g.radius*2),(c===null||c.width>y)&&(c={circle:g,width:y,p1:o,p2:u})}c!==null&&(i.push(c),n+=w(c.circle.radius,c.width),u=o)}}else{var m=e[0];for(s=1;s<e.length;++s){let b=e[s];(m==null||b.radius<m.radius)&&(m=b)}var C=!1;for(s=0;s<e.length;++s){let b=e[s];if(b!==void 0&&z(b,m)>Math.abs(m.radius-b.radius)){C=!0;break}}C?n=a=0:(n=m.radius*m.radius*Math.PI,i.push({circle:m,p1:{x:m.x,y:m.y+m.radius},p2:{x:m.x-1e-10,y:m.y+m.radius},width:m.radius*2}))}return a/=2,{overlap:n+a,stats:{area:n+a,arcArea:n,polygonArea:a,arcs:i,innerPoints:r,intersectionPoints:t}}}function Z(e,t){for(var r=0;r<t.length;++r){let n=t[r];if(n!==void 0&&z(e,n)>n.radius+1e-10)return!1}return!0}function j(e){for(var t=[],r=0;r<e.length;++r)for(var n=r+1;n<e.length;++n){let s=e[r],f=e[n];if(!(!s||!f))for(var a=q(s,f),i=0;i<a.length;++i){let u=a[i];u&&t.push({...u,parentIndex:[r,n]})}}return t}function w(e,t){return e*e*Math.acos(1-t/e)-(e-t)*Math.sqrt(t*(2*e-t))}function z(e,t){return Math.sqrt((e.x-t.x)*(e.x-t.x)+(e.y-t.y)*(e.y-t.y))}function L(e,t,r){if(r>=e+t)return 0;if(r<=Math.abs(e-t))return Math.PI*Math.min(e,t)*Math.min(e,t);var n=e-(r*r-t*t+e*e)/(2*r),a=t-(r*r-e*e+t*t)/(2*r);return w(e,n)+w(t,a)}function q(e,t){var r=z(e,t),n=e.radius,a=t.radius;if(r>=n+a||r<=Math.abs(n-a))return[];var i=(n*n-a*a+r*r)/(2*r),s=Math.sqrt(n*n-i*i),f=e.x+i*(t.x-e.x)/r,u=e.y+i*(t.y-e.y)/r,o=-(t.y-e.y)*(s/r),l=-(t.x-e.x)*(s/r);return[{x:f+o,y:u-l},{x:f-o,y:u+l}]}function _(e){let t=e.reduce((r,n)=>({x:r.x+n.x,y:r.y+n.y}),{x:0,y:0});return t.x/=e.length,t.y/=e.length,t}var ae=new Map([["greedy",H],["MDS",k],["best",fe]]);function X(e,t){t=t??{},t.maxIterations=t.maxIterations||500,t.seed=t.seed??Math.random();let r=ae.get(t.layout??"best");var n=F;e=se(e);var a=r(e,t),i=[],s=[];for(let c of Object.keys(a)){let h=a[c];h&&(i.push(h.x),i.push(h.y),s.push(c))}for(var f=0,u=ee(function(c){f+=1;for(var h={},g=0;g<s.length;++g){var x=s[g];let v=a[x];h[x]={x:c[2*g],y:c[2*g+1],radius:v.radius,setid:x,size:v.size,rowid:v.rowid}}return n(h,e)},i,t),o=u.x,l=0;l<s.length;++l){let c=s[l],h=a[c];h.x=o[2*l],h.y=o[2*l+1]}return a}var Y=1e-10;function B(e,t,r){return Math.min(e,t)*Math.min(e,t)*Math.PI<=r+Y?Math.abs(e-t):ne(function(n){return L(e,t,n)-r},0,e+t)}function se(e){e=e.slice();var t=[],r={},n,a,i,s;for(let f of e)f.sets.length==1?t.push(f.sets[0]):f.sets.length==2&&(i=f.sets[0],s=f.sets[1],r[`${i},${s}`]=!0,r[`${s},${i}`]=!0);for(t.sort((f,u)=>f.toString().localeCompare(u.toString())),n=0;n<t.length;++n)for(i=t[n],a=n+1;a<t.length;++a)s=t[a],`${i},${s}`in r||e.push({sets:[i,s],size:0});return e}function oe(e,t,r){var n=V(t.length,t.length),a=V(t.length,t.length);return e.filter(function(i){return i.sets.length===2}).map(function(i){var s=r[i.sets[0]],f=r[i.sets[1]];if(s===void 0||f===void 0)return i;var u=Math.sqrt(t[s].size/Math.PI),o=Math.sqrt(t[f].size/Math.PI),l=B(u,o,i.size);n[s][f]=n[f][s]=l;var c=0;i.size+1e-10>=Math.min(t[s].size,t[f].size)?c=1:i.size<=1e-10&&(c=-1),a[s][f]=a[f][s]=c}),{distances:n,constraints:a}}function ue(e,t,r,n){var a=0,i;for(i=0;i<t.length;++i)t[i]=0;for(i=0;i<r.length;++i)for(var s=e[2*i],f=e[2*i+1],u=i+1;u<r.length;++u){var o=e[2*u],l=e[2*u+1],c=r[i][u],h=n[i][u],g=(o-s)*(o-s)+(l-f)*(l-f),x=Math.sqrt(g),d=g-c*c;h>0&&x<=c||h<0&&x>=c||(a+=2*d*d,t[2*i]+=4*d*(s-o),t[2*i+1]+=4*d*(f-l),t[2*u]+=4*d*(o-s),t[2*u+1]+=4*d*(l-f))}return a}function fe(e,t){var r=H(e,t),n=F;if(e.length>=8){var a=k(e,t),i=n(a,e),s=n(r,e);i+1e-8<s&&(r=a)}return r}function le(e=1){return()=>{e=e+1831565813|0;let t=Math.imul(e^e>>>15,1|e);return t=Math.imul(t^t>>>7,61|t)^t,((t^t>>>14)>>>0)/4294967296}}function k(e,t){var r=t.restarts||10,n=[],a={},i;for(i=0;i<e.length;++i){var s=e[i];s?.sets.length===1&&(s.sets[0]&&(a[s.sets[0]]=n.length),n.push(s))}var f=oe(e,n,a),u=f.distances,o=f.constraints,l=W(u.map(W))/u.length;u=u.map(function(m){return m.map(function(C){return C/l})});let c=(t.seed??Math.random())*2**32>>>0,h=le(c);var g,x;for(i=0;i<r;++i){var d=re(u.length*2).map(()=>h());x=te((m,C)=>ue(m,C,u,o),d,t),(!g||x.fx<g.fx)&&(g=x)}var v=g?.x,p={};for(i=0;i<n.length;++i){var y=n[i];let m=y?.sets[0];y===void 0||m===void 0||(p[m]={x:v[2*i]*l,y:v[2*i+1]*l,radius:Math.sqrt(y.size/Math.PI),setid:m,size:y.size,rowid:Object.keys(p).length})}if(t.history)for(let m of t.history)ie(m.x,l);return p}function H(e,t){for(var r=F,n={},a={},i,s=0;s<e.length;++s){var f=e[s];f.sets.length==1&&(i=f.sets[0],n[i]={x:1e10,y:1e10,rowid:Object.keys(n).length,size:f.size,setid:i,radius:Math.sqrt(f.size/Math.PI)},a[i]=[])}for(e=e.filter(function(M){return M.sets.length==2}),s=0;s<e.length;++s){var u=e[s],o=u.weight?u.weight:1,l=u.sets[0],c=u.sets[1];if(l===void 0||c===void 0)continue;let M=n[l],R=n[c];!M.size||!R.size||(u.size+Y>=Math.min(M.size,R.size)&&(o=0),a[l]?.push({set:c,size:u.size,weight:o}),a[c]?.push({set:l,size:u.size,weight:o}))}var h=[];for(i in a){let M=a[i];if(M){var g=0;for(s=0;s<M.length;++s){let R=M[s];R&&(g+=R.size*R.weight)}h.push({set:i,size:g})}}h.sort((M,R)=>R.size-M.size);var x={};function d(M){return M.set in x}function v(M,R){let P=n[R];P&&(P.x=M.x,P.y=M.y),x[R]=!0}for(v({x:0,y:0},h[0]?.set??""),s=1;s<h.length;++s){var p=h[s]?.set,y=p?a[p]?.filter(d):null;let M=p?n[p]:null;if(y?.sort((R,P)=>P.size-R.size),y?.length===0||!y)throw"ERROR: missing pairwise overlap information";for(var m=[],C=0;C<y.length;++C){let R=y[C];if(!R)continue;var b=n[y[C].set];if(!b)continue;let P=B(M.radius,b.radius,R.size);m.push({x:b.x+P,y:b.y}),m.push({x:b.x-P,y:b.y}),m.push({y:b.y+P,x:b.x}),m.push({y:b.y-P,x:b.x});for(var A=C+1;A<y.length;++A)for(var I=n[y[A].set],U=B(M.radius,I.radius,y[A].size),$=q({x:b.x,y:b.y,radius:P},{x:I.x,y:I.y,radius:U}),S=0;S<$.length;++S)m.push($[S])}var T=1e50,E=m[0];for(C=0;C<m.length;++C){n[p].x=m[C].x,n[p].y=m[C].y;var N=r(n,e);N<T&&(T=N,E=m[C])}v(E,p)}return n}function F(e,t){var r=0;function n(l){return l.map(function(c){return e[c]})}for(var a=0;a<t.length;++a){var i=t[a],s;if(!(!i||i.sets.length===1)){if(i.sets.length===2){var f=e[i.sets[0]],u=e[i.sets[1]];if(f===void 0||u===void 0)continue;s=L(f.radius,u.radius,z(f,u))}else s=O(n(i.sets)).overlap;var o=i.weight?i.weight:1;r+=o*(s-i.size)*(s-i.size)}}return r}function ce(e,t,r){r===null?e.sort(function(d,v){return v.radius-d.radius}):e.sort(r);var n;if(e.length>0){var a=e[0].x,i=e[0].y;for(n=0;n<e.length;++n)e[n].x-=a,e[n].y-=i}if(e.length==2){let d=e[0],v=e[1];if(d&&v){var s=z(d,v);s<Math.abs(v.radius-d.radius)&&(v.x=d.x+d.radius-v.radius-1e-10,v.y=v.y)}}if(e.length>1){var f=Math.atan2(e[1].x,e[1].y)-(t??0),u=Math.cos(f),o=Math.sin(f),l,c;for(n=0;n<e.length;++n){let d=e[n];d&&(l=d.x,c=d.y,d.x=u*l-o*c,d.y=o*l+u*c)}}if(e.length>2){for(var h=Math.atan2(e[2].x,e[2].y)-(t??0);h<0;)h+=2*Math.PI;for(;h>2*Math.PI;)h-=2*Math.PI;if(h>Math.PI){var g=e[1].y/(1e-10+e[1].x);for(n=0;n<e.length;++n){let d=e[n];if(d){var x=(d.x+g*d.y)/(1+g*g);d.x=2*x-d.x,d.y=2*x*g-d.y}}}}}function de(e){e.map(function(o){o.parent=o});function t(o){if(o)return o.parent!==o&&(o.parent=t(o.parent)),o.parent}function r(o,l){var c=t(o),h=t(l);c&&(c.parent=h)}for(var n=0;n<e.length;++n)for(var a=n+1;a<e.length;++a){let o=e[n],l=e[a];if(!(!o||!l)){var i=o.radius+l.radius;z(o,l)+1e-10<i&&r(l,o)}}var s={},f;for(n=0;n<e.length;++n){let o=e[n];if(!o)continue;let c=t(e[n])?.parent?.setid;c&&(c in s||(s[c]=[]),s[c]&&s[c].push(o))}e.map(function(o){delete o.parent});var u=[];for(f in s){let o=s[f];o&&u.push(o)}return u}function D(e){var t=function(r){var n=Math.max.apply(null,e.map(function(i){return i[r]+i.radius})),a=Math.min.apply(null,e.map(function(i){return i[r]-i.radius}));return{max:n,min:a}};return{xRange:t("x"),yRange:t("y")}}function J(e,t,r){t===null&&(t=Math.PI/2);var n=[],a,i;for(i in e){var s=e[i];s&&n.push({...s,setid:i})}var f=de(n);for(a=0;a<f.length;++a){let d=f[a];if(d){ce(d,t,r);var u=D(d);d.size=(u.xRange.max-u.xRange.min)*(u.yRange.max-u.yRange.min),d.bounds=u}}f.sort(function(d,v){return!d.size||!v.size?0:v.size-d.size});var l=f[0].bounds,c=(l.xRange.max-l.xRange.min)/50;function h(d,v,p){if(d&&!(!d.size||!d.bounds)){var y=d.bounds,m,C,b;v?m=l.xRange.max-y.xRange.min+c:(m=l.xRange.max-y.xRange.max,b=(y.xRange.max-y.xRange.min)/2-(l.xRange.max-l.xRange.min)/2,b<0&&(m+=b)),p?C=l.yRange.max-y.yRange.min+c:(C=l.yRange.max-y.yRange.max,b=(y.yRange.max-y.yRange.min)/2-(l.yRange.max-l.yRange.min)/2,b<0&&(C+=b));for(var A=0;A<d.length;++A){let I=d[A];I&&(I.x+=m,I.y+=C,n.push(I))}}}for(var g=1;g<f.length;)h(f[g],!0,!1),h(f[g+1],!1,!0),h(f[g+2],!0,!0),g+=3,l=D(n);var x={};for(let d of n)x[d.setid]=d;return x}function K(e,t,r,n){var a=[],i=[];for(var s in e){let y=e[s];y&&(i.push(s),a.push(y))}t-=2*n,r-=2*n;var f=D(a),u=f.xRange,o=f.yRange;if(u.max==u.min||o.max==o.min)return e;for(var l=t/(u.max-u.min),c=r/(o.max-o.min),h=Math.min(c,l),g=(t-(u.max-u.min)*h)/2,x=(r-(o.max-o.min)*h)/2,d={},v=0;v<a.length;++v){var p=a[v];let y=i[v];!p||!y||(d[y]={...p,radius:h*p.radius,x:n+g+(p.x-u.min)*h,y:n+x+(p.y-o.min)*h})}return d}import{nelderMead as he}from"fmin";function Ae(e,t){let{circles:r,intersections:n}=ge(e,t);return{$schema:"https://vega.github.io/schema/vega/v5.json",width:t.width,height:t.height,padding:t.padding,data:[{name:"circles",values:r},{name:"intersections",values:n}],scales:[{name:"color",type:"ordinal",domain:{data:"circles",field:"set_id"},range:"category"}],marks:[{type:"symbol",from:{data:"circles"},encode:{enter:{x:{field:"x"},y:{field:"y"},size:{field:"size"},shape:{value:"circle"},fillOpacity:{value:.3},fill:{scale:"color",field:"set_id"},tooltip:[{field:"text",type:"quantitative"}]},hover:{fillOpacity:{value:.5}},update:{fillOpacity:{value:.3}}}},{type:"path",from:{data:"intersections"},encode:{enter:{path:{field:"path"},fill:{value:"grey"},fillOpacity:{value:0},tooltip:[{field:"text",type:"quantitative"}]},hover:{stroke:{value:"black"},strokeWidth:{value:1},fill:{value:"grey"}},update:{strokeWidth:{value:0}}}},{type:"text",from:{data:"intersections"},encode:{enter:{x:{field:"textX"},y:{field:"textY"},text:{field:"size"},fontSize:{value:14},fill:{scale:"color",field:"set_id"},fontWeight:{value:"normal"}}}}]}}function ge(e,{layout:t,height:r,width:n,padding:a,seed:i,orientation:s,set_id_delimiter:f,orientationOrder:u}){let o=e.filter(x=>x.size!==0&&x.sets.length>0);if(o.length===0)return{circles:[],intersections:[]};let l=X(o,{layout:t,seed:i});s!==Math.PI/2&&(l=J(l,s,u)),l=K(l,n,r,a);let c=ye(l,o),h=o.map(x=>{let d=x.sets.join(","),{x:v,y:p,disjoint:y}=c[d];return{set_id:x.sets.join(f),sets:x.sets,path:me(x.sets.map(m=>l[m])),textX:y?void 0:v,textY:y?void 0:p,size:x.size}}).filter(x=>x.sets.length>1);return{circles:Object.entries(l).map(([x,d])=>({set_id:x,x:d.x,y:d.y,size:Math.pow(d.radius*2,2),textX:c[x].x,textY:c[x].y})),intersections:h}}function me(e){let{stats:t}=O(e);var r=t.arcs;if(r.length===0)return"M 0 0";if(r.length===1){var n=r[0].circle;return xe(n.x,n.y,n.radius)}var a=[`
M`,r[0].p2.x,r[0].p2.y];for(let i of r){let s=i.circle.radius,f=i.width>s;a.push(`
A`,s,s,0,f?1:0,1,i.p1.x,i.p1.y)}return a.join(" ")}function xe(e,t,r){var n=[];return n.push(`
M`,e.toString(),t.toString()),n.push(`
m`,-r,0),n.push(`
a`,r,r,0,1,0,r*2,0),n.push(`
a`,r,r,0,1,0,-r*2,0),n.join(" ")}function ye(e,t,r=","){for(var n={},a=ve(e),i=0;i<t.length;++i){for(var s=t[i].sets,f={},u={},o=0;o<s.length;++o){f[s[o]]=!0;var l=a[s[o]];if(l)for(var c=0;c<l.length;++c)u[l[c]]=!0}var h=[],g=[];for(var x in e)x in f?h.push(e[x]):x in u||g.push(e[x]);var d=Q(h,g);n[s.join(r)]=d,d.disjoint&&t[i].size>0&&console.log("WARNING: area "+s+" not represented on screen")}return n}function ve(e){var t={},r=Object.keys(e);r.forEach(u=>t[u]=[]);for(var n=0;n<r.length;n++)for(var a=e[r[n]],i=n+1;i<r.length;++i){var s=e[r[i]],f=z(a,s);f+s.radius<=a.radius+1e-10?t[r[i]].push(r[n]):f+a.radius<=s.radius+1e-10&&t[r[n]].push(r[i])}return t}function Q(e,t){var r=[],n;for(n=0;n<e.length;++n){var a=e[n];a&&(r.push({x:a.x,y:a.y}),r.push({x:a.x+a.radius/2,y:a.y}),r.push({x:a.x-a.radius/2,y:a.y}),r.push({x:a.x,y:a.y+a.radius/2}),r.push({x:a.x,y:a.y-a.radius/2}))}var i=r[0],s=G(r[0],e,t);for(n=1;n<r.length;++n){var f=G(r[n],e,t);f>=s&&(i=r[n],s=f)}var u=he(function(c){return-1*G({x:c[0],y:c[1]},e,t)},[i.x,i.y],{maxIterations:500,minErrorDelta:1e-10}).x,o={x:u[0],y:u[1]},l=!0;for(n=0;n<e.length;++n)if(z(o,e[n])>e[n].radius){l=!1;break}for(n=0;n<t.length;++n)if(z(o,t[n])<t[n].radius){l=!1;break}if(!l)if(e.length==1)o={x:e[0].x,y:e[0].y};else{let{stats:c}=O(e);c.arcs.length===0?o={x:0,y:-1e3,disjoint:!0}:c.arcs.length===1?o={x:c.arcs[0].circle.x,y:c.arcs[0].circle.y}:t.length?o=Q(e,[]):o=_(c.arcs.map(function(h){return h.p1}))}return o}function G(e,t,r){var n=t[0].radius-z(t[0],e),a,i;for(a=1;a<t.length;++a)i=t[a].radius-z(t[a],e),i<=n&&(n=i);for(a=0;a<r.length;++a)i=z(r[a],e)-r[a].radius,i<=n&&(n=i);return n}export{fe as bestInitialLayout,Ae as chartVega,w as circleArea,q as circleCircleIntersection,L as circleOverlap,xe as circlePath,Q as computeTextCentre,ye as computeTextCentres,k as constrainedMDSLayout,Z as containedInCircles,de as disjointCluster,z as distance,B as distanceFromIntersectArea,_ as getCenter,oe as getDistanceMatrices,H as greedyLayout,O as intersectionArea,me as intersectionAreaPath,F as lossFunction,J as normalizeSolution,K as scaleSolution,X as venn,ge as vennSolution};