@euphrasiologist/lwphylo 1.1.10 → 1.1.11

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.
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- const t={circular:()=>{let t=200,n=2,e=[0,0],r={},i=0,s=0,a=t*t,c=10;function o(){const e=Math.exp(n);return i=e/(e-1)*t,s=n/t,a=t*t,h}function h(t){!function(){if(!r||"function"!=typeof r.xscale||"function"!=typeof r.yscale)throw new Error("phisheye.circular: call .scales(xscale, yscale) before using the fisheye.")}();const n=r.xscale(t.x),o=r.yscale(t.y),h=n-e[0],l=o-e[1],u=h*h+l*l;if(0===u||u>=a)return{x:n,y:o,z:u>=a?1:c};const d=Math.sqrt(u),f=i*(1-Math.exp(-d*s))/d*.75+.25;return{x:e[0]+h*f,y:e[1]+l*f,z:Math.min(f,c)}}return h.radius=function(n){return arguments.length?(t=Math.max(0,+n),o()):t},h.distortion=function(t){return arguments.length?(n=Math.max(0,+t),o()):n},h.focus=function(t){if(!arguments.length)return e.slice();if(!Array.isArray(t)||2!==t.length)throw new Error("focus expects [x, y] in pixels.");return e=[+t[0],+t[1]],h},h.scales=function(t,n){return arguments.length?(r={xscale:t,yscale:n},h):r},h.setScales=h.scales,h.clampZ=function(t){return arguments.length?(c=Math.max(1,+t),h):c},h.focusFromEvent=function(t,n){const e=n.getBoundingClientRect(),r=t.clientX-e.left,i=t.clientY-e.top;return h.focus([r,i])},o()}};function n(t,n,e,r){return{x:t+e*Math.cos(r),y:n+e*Math.sin(r)}}function e(t){const n=t%(2*Math.PI);return n<0?n+2*Math.PI:n}function r(t,r,i,s,a){let c=e(s),o=e(a)-c;o<=-Math.PI?o+=2*Math.PI:o>Math.PI&&(o-=2*Math.PI);const h=Math.abs(o)>Math.PI?1:0,l=o>=0?1:0,u=n(t,r,i,c),d=n(t,r,i,c+o);return["M",u.x,u.y,"A",i,i,0,h,l,d.x,d.y].join(" ")}function i(t,n=[]){n.push(t);for(let e=0;e<(t.children?.length||0);e++)n=i(t.children[e],n);return n}function s(t,n=!0){var e=[];for(const n of i(t))null===n.parent?e.push({parentId:null,parentLabel:null,thisId:n.id,thisLabel:n.label,children:n.children.map(t=>t.id),branchLength:0,isTip:!1,x:n.x,y:n.y,angle:n.angle}):e.push({parentId:n.parent.id,parentLabel:n.parent.label,thisId:n.id,thisLabel:n.label,children:n.children.map(t=>t.id),branchLength:n.branchLength,isTip:0==n.children.length,x:n.x,y:n.y,angle:n.angle});return n&&(e=e.sort(function(t,n){return t.thisId-n.thisId})),e}function a(t){const n=2*Math.PI,e=s(t,!0),r=new Map(e.map(t=>[t.thisId,t])),i=new Map(e.map(t=>[t.thisId,t.children||[]]));let a=null;for(const t of e)if(null==t.parentId){a=t.thisId;break}const c=[];!function t(n){const e=i.get(n)||[];if(0!==e.length)for(const n of e)t(n);else c.push(n)}(a);const o=Math.max(1,c.length),h=new Map;c.forEach((t,e)=>{h.set(t,e/o*n)}),function t(e){const r=i.get(e)||[];for(const n of r)t(n);if(r.length>0){let t=0,i=0;for(const n of r){const e=h.get(n);t+=Math.cos(e),i+=Math.sin(e)}h.set(e,(Math.atan2(i,t)%n+n)%n)}}(a);const l=new Map;l.set(a,0),function t(n){const e=i.get(n)||[],s=l.get(n)||0;for(const n of e){const e=r.get(n),i=e?.branchLength??0;l.set(n,s+i),t(n)}}(a);for(const t of e){const n=h.get(t.thisId)??0,e=l.get(t.thisId)??0;t.angle=n,t.r=e,t.x=e*Math.cos(n),t.y=e*Math.sin(n)}return e}function c(t){const n=t%(2*Math.PI);return n<0?n+2*Math.PI:n}function o(t,n){t=c(t);let e=(n=c(n))-t;return e<=-Math.PI?e+=2*Math.PI:e>Math.PI&&(e-=2*Math.PI),[t,t+e]}function h(t){const n={};return n.data=a(t),n.radii=function(t){const n=a(t),e=new Map(n.map(t=>[t.thisId,t])),r=n.find(t=>null==t.parentId)?.thisId,i=[];for(const t of n){if(t.thisId===r)continue;const n=e.get(t.parentId);if(!n)continue;const s=t.angle,a=n.r,c=t.r;i.push({parentId:n.thisId,childId:t.thisId,x0:a*Math.cos(s),y0:a*Math.sin(s),x1:c*Math.cos(s),y1:c*Math.sin(s),isTip:!!t.isTip})}return i}(t),n.arcs=function(t){const n=new Map(t.map(t=>[t.thisId,t])),e=t.find(t=>null==t.parentId)?.thisId,r=[];for(const i of t){if(i.thisId===e)continue;const t=n.get(i.parentId);if(!t)continue;const[s,a]=o(t.angle,i.angle);isFinite(t.r)&&0!==t.r&&s!==a&&r.push({parentId:t.thisId,childId:i.thisId,radius:t.r,start:s,end:a})}return r}(n.data),n}function l(t){const n=s(t),e=new Map(n.map((t,n)=>[t.thisId,n])),r=[];!function t(n){n.children&&0!==n.children.length?n.children.forEach(t):r.push(n.id)}(t);const i=new Map(r.map((t,n)=>[t,n+1]));return function t(r){const s=e.get(r.id);if(!r.children||0===r.children.length){const t=i.get(r.id);return n[s].y0=t,n[s].y1=t,t}const a=function(t){let n=0,e=0;for(let r of t)null!=r&&(r=+r)>=r&&(++n,e+=r);if(n)return e/n}(r.children.map(t));return n[s].y0=a,n[s].y1=a,a}(t),function t(r,i){const s=e.get(r.id),a=i??0,c=a+(n[s].branchLength??0);n[s].x0=a,n[s].x1=c,r.children&&r.children.length&&r.children.forEach(n=>t(n,c))}(t,0),n.map(t=>{const{y:n,x:e,angle:r,...i}=t;return i})}function u(t){const n=l(t),e=function(t){const n=l(t),e=new Map;for(const t of n){if(null==t.parentId)continue;const n=e.get(t.parentId);n?n.push(t):e.set(t.parentId,[t])}const r=[];for(const[t,n]of e.entries()){if(!n.length)continue;const e=n.map(t=>t.y0),i=Math.min(...e),s=Math.max(...e),a=n[0].x0;r.push({parentId:t,x0:a,x1:a,y0:i,y1:s,heights:s-i})}return r}(t),r=function(t){const n=l(t),e=new Map(n.map(t=>[t.thisId,t])),r=[];for(const t of n){if(null==t.parentId)continue;const n=e.get(t.parentId);if(!n)continue;const i=t.x0,s=t.y0,a=n.y0,c=Math.min(s,a),o=Math.max(s,a);r.push({parentId:t.parentId,childId:t.thisId,x:i,y0:c,y1:o})}return r}(t);new Map(n.map(t=>[t.thisId,t]));const i=n.filter(t=>null!=t.parentId).map(t=>({parentId:t.parentId,childId:t.thisId,thisId:t.thisId,thisLabel:t.thisLabel,isTip:t.isTip,x0:t.x0,x1:t.x1,y0:t.y0,y1:t.y0}));return{data:n,vertical_lines:e,child_vertical_lines:r,horizontal_lines:i}}function d(t){null===t.parent&&(function t(n){if(!n.children||0===n.children.length)return n.ntips=1,1;let e=0;for(const r of n.children)e+=t(r);return n.ntips=e,e}(t),t.start=0,t.end=2,t.angle=0,t.ntips=function(t){var n=0;for(const e of function(t){const n=[t],e=[];for(;n.length;){const t=n.shift();e.push(t);for(const e of t.children)n.push(e)}return e}(t))0==e.children.length&&n++;return n}(t),t.x=0,t.y=0);let n=t.start;for(let e=0;e<t.children.length;e++){const r=t.children[e],i=(t.end-t.start)*(r.ntips/t.ntips);r.start=n,r.end=n+i,r.angle=r.start+(r.end-r.start)/2,n=r.end;const s=r.angle*Math.PI,a=r.branchLength??0;r.x=t.x+a*Math.sin(s),r.y=t.y+a*Math.cos(s),d(r)}return t}function f(t){var n={},e=s(d(t));return n.data=e,n.edges=function(t,n=!1){const e=[...t].sort((t,n)=>t.thisId-n.thisId),r=new Map(e.map(t=>[t.thisId,t])),i=[];for(const t of e){if(null==t.parentId)continue;const e=r.get(t.parentId);e&&(n?(i.push({x1:t.x,y1:t.y,id1:t.thisId,x2:e.x,y2:t.y,id2:void 0}),i.push({x1:e.x,y1:t.y,id1:void 0,x2:e.x,y2:e.y,id2:t.parentId})):i.push({x1:t.x,y1:t.y,id1:t.thisId,x2:e.x,y2:e.y,id2:t.parentId}))}return i}(e),n}function p(t,n){const e=function(t,n="thisId"){return new Map(t.map(t=>[t[n],t]))}(n),r=e.get(t.parentId);return r?{px:r.fisheye.x,py:r.fisheye.y}:null}function I(t){const n=(t=String(t).replace(/\s+/g,"")).split(/(;|\(|\)|,)/),e={parent:null,children:[]};let r=e,i=0;for(const t of n)if(t&&";"!==t)if("("===t){const t={parent:r,children:[]};r.children.push(t),r=t}else if(","===t){r=r.parent;const t={parent:r,children:[]};r.children.push(t),r=t}else if(")"===t){if(r=r.parent,null===r)break}else{const n=t.split(":");1===n.length?t.startsWith(":")?(r.label="",r.branchLength=parseFloat(n[0])):(r.label=n[0],r.branchLength=null):2===n.length?(r.label=n[0],r.branchLength=parseFloat(n[1])):(console.warn(t,"Unhandled token with multiple ':' characters"),r.label=n[0]||"",r.branchLength=parseFloat(n[n.length-1])),r.id=i++}return null==e.id&&(e.id=i),e}function g(t,n){let e={};t.data.forEach(t=>{e[t.thisId]={...t}});let r={};const i=function(t){e[t]&&(r[t]=e[t],e[t].children&&e[t].children.forEach(t=>{i(t)}))};i(n);const s=[["data",Object.values(r)]],a=s[0][1].map(t=>t.thisId);var c=[];for(const n in t)"data"!==n&&c.push([n,t[n]]);var o=c.map(t=>[t[0],t[1].filter(t=>a.includes(t.thisId))]);return Object.fromEntries(s.concat(o))}export{r as describeArc,p as parentFisheye,t as phisheye,h as radialLayout,I as readTree,u as rectangleLayout,g as subTree,f as unrooted};
1
+ const n={circular:()=>{let n=200,t=2,e=[0,0],r={},i=0,s=0,c=n*n,a=10;function o(){const e=Math.exp(t);return i=e/(e-1)*n,s=t/n,c=n*n,h}function h(n){!function(){if(!r||"function"!=typeof r.xscale||"function"!=typeof r.yscale)throw new Error("phisheye.circular: call .scales(xscale, yscale) before using the fisheye.")}();const t=r.xscale(n.x),o=r.yscale(n.y),h=t-e[0],l=o-e[1],d=h*h+l*l;if(0===d||d>=c)return{x:t,y:o,z:d>=c?1:a};const u=Math.sqrt(d),f=i*(1-Math.exp(-u*s))/u*.75+.25;return{x:e[0]+h*f,y:e[1]+l*f,z:Math.min(f,a)}}return h.radius=function(t){return arguments.length?(n=Math.max(0,+t),o()):n},h.distortion=function(n){return arguments.length?(t=Math.max(0,+n),o()):t},h.focus=function(n){if(!arguments.length)return e.slice();if(!Array.isArray(n)||2!==n.length)throw new Error("focus expects [x, y] in pixels.");return e=[+n[0],+n[1]],h},h.scales=function(n,t){return arguments.length?(r={xscale:n,yscale:t},h):r},h.setScales=h.scales,h.clampZ=function(n){return arguments.length?(a=Math.max(1,+n),h):a},h.focusFromEvent=function(n,t){const e=t.getBoundingClientRect(),r=n.clientX-e.left,i=n.clientY-e.top;return h.focus([r,i])},o()}};function t(n,t,e,r){return{x:n+e*Math.cos(r),y:t-e*Math.sin(r)}}function e(n,e,r,i,s){const c=2*Math.PI,a=n=>(n%c+c)%c;let o=a(i),h=a(s),l=h-o;l<0&&(l+=c);const d=l>Math.PI?1:0,u=t(n,e,r,o),f=t(n,e,r,h);return["M",u.x,u.y,"A",r,r,0,d,0,f.x,f.y].join(" ")}function r(n,t=[]){t.push(n);for(let e=0;e<(n.children?.length||0);e++)t=r(n.children[e],t);return t}function i(n,t=!0){var e=[];for(const t of r(n))null===t.parent?e.push({parentId:null,parentLabel:null,thisId:t.id,thisLabel:t.label,children:t.children.map(n=>n.id),branchLength:0,isTip:!1,x:t.x,y:t.y,angle:t.angle}):e.push({parentId:t.parent.id,parentLabel:t.parent.label,thisId:t.id,thisLabel:t.label,children:t.children.map(n=>n.id),branchLength:t.branchLength,isTip:0==t.children.length,x:t.x,y:t.y,angle:t.angle});return t&&(e=e.sort(function(n,t){return n.thisId-t.thisId})),e}function s(n){const t=2*Math.PI,e=i(n,!0),r=new Map(e.map(n=>[n.thisId,n])),s=new Map(e.map(n=>[n.thisId,n.children||[]]));let c=null;for(const n of e)if(null==n.parentId){c=n.thisId;break}const a=[];!function n(t){const e=s.get(t)||[];if(0!==e.length)for(const t of e)n(t);else a.push(t)}(c);const o=Math.max(1,a.length),h=new Map;a.forEach((n,e)=>{h.set(n,e/o*t)}),function n(e){const r=s.get(e)||[];for(const t of r)n(t);if(r.length>0){let n=0,i=0;for(const t of r){const e=h.get(t);n+=Math.cos(e),i+=Math.sin(e)}h.set(e,(Math.atan2(i,n)%t+t)%t)}}(c);const l=new Map;l.set(c,0),function n(t){const e=s.get(t)||[],i=l.get(t)||0;for(const t of e){const e=r.get(t),s=e?.branchLength??0;l.set(t,i+s),n(t)}}(c);for(const n of e){const t=h.get(n.thisId)??0,e=l.get(n.thisId)??0;n.angle=t,n.r=e,n.x=e*Math.cos(t),n.y=e*Math.sin(t)}return e}function c(n){const t=n%(2*Math.PI);return t<0?t+2*Math.PI:t}function a(n,t){n=c(n);let e=(t=c(t))-n;return e<=-Math.PI?e+=2*Math.PI:e>Math.PI&&(e-=2*Math.PI),[n,n+e]}function o(n){const t={};return t.data=s(n),t.radii=function(n){const t=s(n),e=new Map(t.map(n=>[n.thisId,n])),r=t.find(n=>null==n.parentId)?.thisId,i=[];for(const n of t){if(n.thisId===r)continue;const t=e.get(n.parentId);if(!t)continue;const s=n.angle,c=t.r,a=n.r;i.push({parentId:t.thisId,childId:n.thisId,x0:c*Math.cos(s),y0:c*Math.sin(s),x1:a*Math.cos(s),y1:a*Math.sin(s),isTip:!!n.isTip})}return i}(n),t.arcs=function(n){const t=new Map(n.map(n=>[n.thisId,n])),e=n.find(n=>null==n.parentId)?.thisId,r=[];for(const i of n){if(i.thisId===e)continue;const n=t.get(i.parentId);if(!n)continue;const[s,c]=a(n.angle,i.angle);isFinite(n.r)&&0!==n.r&&s!==c&&r.push({parentId:n.thisId,childId:i.thisId,radius:n.r,start:s,end:c})}return r}(t.data),t}function h(n){const t=i(n),e=new Map(t.map((n,t)=>[n.thisId,t])),r=[];!function n(t){t.children&&0!==t.children.length?t.children.forEach(n):r.push(t.id)}(n);const s=new Map(r.map((n,t)=>[n,t+1]));return function n(r){const i=e.get(r.id);if(!r.children||0===r.children.length){const n=s.get(r.id);return t[i].y0=n,t[i].y1=n,n}const c=function(n){let t=0,e=0;for(let r of n)null!=r&&(r=+r)>=r&&(++t,e+=r);if(t)return e/t}(r.children.map(n));return t[i].y0=c,t[i].y1=c,c}(n),function n(r,i){const s=e.get(r.id),c=i??0,a=c+(t[s].branchLength??0);t[s].x0=c,t[s].x1=a,r.children&&r.children.length&&r.children.forEach(t=>n(t,a))}(n,0),t.map(n=>{const{y:t,x:e,angle:r,...i}=n;return i})}function l(n){const t=h(n),e=function(n){const t=h(n),e=new Map;for(const n of t){if(null==n.parentId)continue;const t=e.get(n.parentId);t?t.push(n):e.set(n.parentId,[n])}const r=[];for(const[n,t]of e.entries()){if(!t.length)continue;const e=t.map(n=>n.y0),i=Math.min(...e),s=Math.max(...e),c=t[0].x0;r.push({parentId:n,x0:c,x1:c,y0:i,y1:s,heights:s-i})}return r}(n),r=function(n){const t=h(n),e=new Map(t.map(n=>[n.thisId,n])),r=[];for(const n of t){if(null==n.parentId)continue;const t=e.get(n.parentId);if(!t)continue;const i=n.x0,s=n.y0,c=t.y0,a=Math.min(s,c),o=Math.max(s,c);r.push({parentId:n.parentId,childId:n.thisId,x:i,y0:a,y1:o})}return r}(n);new Map(t.map(n=>[n.thisId,n]));const i=t.filter(n=>null!=n.parentId).map(n=>({parentId:n.parentId,childId:n.thisId,thisId:n.thisId,thisLabel:n.thisLabel,isTip:n.isTip,x0:n.x0,x1:n.x1,y0:n.y0,y1:n.y0}));return{data:t,vertical_lines:e,child_vertical_lines:r,horizontal_lines:i}}function d(n){null===n.parent&&(function n(t){if(!t.children||0===t.children.length)return t.ntips=1,1;let e=0;for(const r of t.children)e+=n(r);return t.ntips=e,e}(n),n.start=0,n.end=2,n.angle=0,n.ntips=function(n){var t=0;for(const e of function(n){const t=[n],e=[];for(;t.length;){const n=t.shift();e.push(n);for(const e of n.children)t.push(e)}return e}(n))0==e.children.length&&t++;return t}(n),n.x=0,n.y=0);let t=n.start;for(let e=0;e<n.children.length;e++){const r=n.children[e],i=(n.end-n.start)*(r.ntips/n.ntips);r.start=t,r.end=t+i,r.angle=r.start+(r.end-r.start)/2,t=r.end;const s=r.angle*Math.PI,c=r.branchLength??0;r.x=n.x+c*Math.sin(s),r.y=n.y+c*Math.cos(s),d(r)}return n}function u(n){var t={},e=i(d(n));return t.data=e,t.edges=function(n,t=!1){const e=[...n].sort((n,t)=>n.thisId-t.thisId),r=new Map(e.map(n=>[n.thisId,n])),i=[];for(const n of e){if(null==n.parentId)continue;const e=r.get(n.parentId);e&&(t?(i.push({x1:n.x,y1:n.y,id1:n.thisId,x2:e.x,y2:n.y,id2:void 0}),i.push({x1:e.x,y1:n.y,id1:void 0,x2:e.x,y2:e.y,id2:n.parentId})):i.push({x1:n.x,y1:n.y,id1:n.thisId,x2:e.x,y2:e.y,id2:n.parentId}))}return i}(e),t}function f(n,t){const e=function(n,t="thisId"){return new Map(n.map(n=>[n[t],n]))}(t),r=e.get(n.parentId);return r?{px:r.fisheye.x,py:r.fisheye.y}:null}function p(n){const t=(n=String(n).replace(/\s+/g,"")).split(/(;|\(|\)|,)/),e={parent:null,children:[]};let r=e,i=0;for(const n of t)if(n&&";"!==n)if("("===n){const n={parent:r,children:[]};r.children.push(n),r=n}else if(","===n){r=r.parent;const n={parent:r,children:[]};r.children.push(n),r=n}else if(")"===n){if(r=r.parent,null===r)break}else{const t=n.split(":");1===t.length?n.startsWith(":")?(r.label="",r.branchLength=parseFloat(t[0])):(r.label=t[0],r.branchLength=null):2===t.length?(r.label=t[0],r.branchLength=parseFloat(t[1])):(console.warn(n,"Unhandled token with multiple ':' characters"),r.label=t[0]||"",r.branchLength=parseFloat(t[t.length-1])),r.id=i++}return null==e.id&&(e.id=i),e}function I(n,t){let e={};n.data.forEach(n=>{e[n.thisId]={...n}});let r={};const i=function(n){e[n]&&(r[n]=e[n],e[n].children&&e[n].children.forEach(n=>{i(n)}))};i(t);const s=[["data",Object.values(r)]],c=s[0][1].map(n=>n.thisId);var a=[];for(const t in n)"data"!==t&&a.push([t,n[t]]);var o=a.map(n=>[n[0],n[1].filter(n=>c.includes(n.thisId))]);return Object.fromEntries(s.concat(o))}export{e as describeArc,f as parentFisheye,n as phisheye,o as radialLayout,p as readTree,l as rectangleLayout,I as subTree,u as unrooted};
2
2
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@euphrasiologist/lwphylo",
3
- "version": "1.1.10",
3
+ "version": "1.1.11",
4
4
  "description": "A lightweight, low level javascript library to plot phylogenies from Newick files.",
5
5
  "main": "dist/@euphrasiologist/lwphylo.js",
6
6
  "unpkg": "dist/@euphrasiologist/lwphylo.min.js",
@@ -1,28 +1,18 @@
1
- import polarToCartesian from "./polarToCartesian.js";
2
-
3
- /**
4
- * Describe an SVG arc path between two angles (radians).
5
- * Always draws the MINOR arc (≤ π). Angles are normalized to [0, 2π).
6
- */
7
- function normalize(theta) {
8
- const t = theta % (2 * Math.PI);
9
- return t < 0 ? t + 2 * Math.PI : t;
10
- }
1
+ import polarToCartesian from "./polarToCartesian.js"
11
2
 
12
3
  export default function describeArc(cx, cy, radius, startAngle, endAngle) {
13
- let a0 = normalize(startAngle);
14
- let a1 = normalize(endAngle);
4
+ const TAU = Math.PI * 2;
5
+ const norm = (t) => ((t % TAU) + TAU) % TAU;
15
6
 
16
- // Choose the minor arc direction
17
- let diff = a1 - a0;
18
- if (diff <= -Math.PI) diff += 2 * Math.PI;
19
- else if (diff > Math.PI) diff -= 2 * Math.PI;
7
+ let a0 = norm(startAngle);
8
+ let a1 = norm(endAngle);
9
+ let delta = a1 - a0; if (delta < 0) delta += TAU;
20
10
 
21
- const largeArcFlag = Math.abs(diff) > Math.PI ? 1 : 0; // for completeness (will be 0 with minor arc)
22
- const sweepFlag = diff >= 0 ? 1 : 0;
11
+ const largeArcFlag = delta > Math.PI ? 1 : 0;
12
+ const sweepFlag = 0; // CCW after Y inversion
23
13
 
24
14
  const start = polarToCartesian(cx, cy, radius, a0);
25
- const end = polarToCartesian(cx, cy, radius, a0 + diff);
15
+ const end = polarToCartesian(cx, cy, radius, a1);
26
16
 
27
17
  return [
28
18
  "M", start.x, start.y,
@@ -1,9 +1,12 @@
1
1
  /**
2
- * Convert polar to Cartesian: angle in radians, 0 at +x, CCW positive.
2
+ * Convert polar coordinates (radians) to cartesian (screen space).
3
+ * NOTE: Invert Y so angles increase counter-clockwise on screen,
4
+ * matching how you plot points with yScaleRadial (range [h,0]).
3
5
  */
4
- export default function polarToCartesian(cx, cy, r, angle) {
6
+ export default function (centerX, centerY, radius, angleInRadians) {
5
7
  return {
6
- x: cx + r * Math.cos(angle),
7
- y: cy + r * Math.sin(angle)
8
+ x: centerX + radius * Math.cos(angleInRadians),
9
+ y: centerY - radius * Math.sin(angleInRadians) // <- was + ; now -
8
10
  };
9
11
  }
12
+