UNPKG

@d3plus/math

Version:

Mathematical functions to aid in calculating visualizations.

289 lines (281 loc) 25.1 kB
/* @d3plus/math v3.0.5 Mathematical functions to aid in calculating visualizations. Copyright (c) 2025 D3plus - https://d3plus.org @license MIT */ (t=>{"function"==typeof define&&define.amd?define(t):t()})(function(){if("undefined"!=typeof window){try{if("undefined"==typeof SVGElement||Boolean(SVGElement.prototype.innerHTML))return}catch(t){return}function r(t){switch(t.nodeType){case 1:var e=t,n="";return n+="<"+e.tagName,e.hasAttributes()&&[].forEach.call(e.attributes,function(t){n+=" "+t.name+'="'+t.value+'"'}),n+=">",e.hasChildNodes()&&[].forEach.call(e.childNodes,function(t){n+=r(t)}),n+="</"+e.tagName+">";case 3:return t.textContent.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");case 8:return"\x3c!--"+t.nodeValue+"--\x3e"}}Object.defineProperty(SVGElement.prototype,"innerHTML",{get:function(){var e="";return[].forEach.call(this.childNodes,function(t){e+=r(t)}),e},set:function(t){for(;this.firstChild;)this.removeChild(this.firstChild);try{var e=new DOMParser,n=(e.async=!1,"<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>"+t+"</svg>"),r=e.parseFromString(n,"text/xml").documentElement;[].forEach.call(r.childNodes,function(t){this.appendChild(this.ownerDocument.importNode(t,!0))}.bind(this))}catch(t){throw new Error("Error parsing markup string")}}}),Object.defineProperty(SVGElement.prototype,"innerSVG",{get:function(){return this.innerHTML},set:function(t){this.innerHTML=t}})}}),((t,e)=>{"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define("@d3plus/math",["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).d3plus={})})(this,function(t){ /** Create a new column x row matrix. @private @param {number} columns @param {number} rows @return {Array<Array<number>>} matrix @example makeMatrix(10, 10); */function l(e,n){var r=[];for(let t=0;t<e;t++){var a=[];for(let t=0;t<n;t++)a.push(0);r.push(a)}return r} /** Generates incrementally computed values based on the sums and sums of squares for the data array @private @param {number} j @param {number} i @param {Array<number>} sums @param {Array<number>} sumsOfSquares @return {number} @example ssq(0, 1, [-1, 0, 2], [1, 1, 5]); */function p(t,e,n,r){let a;// s(j, i) var i;// mu(j, i) return(a=0<t?(i=(n[e]-n[t-1])/(e-t+1),r[e]-r[t-1]-(e-t+1)*i*i):r[e]-n[e]*n[e]/(e+1))<0?0:a} /** Function that recursively divides and conquers computations for cluster j @private @param {number} iMin Minimum index in cluster to be computed @param {number} iMax Maximum index in cluster to be computed @param {number} cluster Index of the cluster currently being computed @param {Array<Array<number>>} matrix @param {Array<Array<number>>} backtrackMatrix @param {Array<number>} sums @param {Array<number>} sumsOfSquares */ /** Initializes the main matrices used in Ckmeans and kicks off the divide and conquer cluster computation strategy @private @param {Array<number>} data sorted array of values @param {Array<Array<number>>} matrix @param {Array<Array<number>>} backtrackMatrix */function s(n,r,a){var i=r[0]?r[0].length:0,o=n[Math.floor(i/2)],l=[],s=[]; // Shift values by the median to improve numeric stability // Initialize first column in matrix & backtrackMatrix for(let t=0,e;t<i;++t)e=n[t]-o,0===t?(l.push(e),s.push(e*e)):(l.push(l[t-1]+e),s.push(s[t-1]+e*e)), // Initialize for cluster = 0 r[0][t]=p(0,t,l,s),a[0][t]=0; // Initialize the rest of the columns for(let e=1;e<r.length;++e){let t=i-1;!function t(r,a,i,o,l,s,h){if(!(a<r)){ // Start at midpoint between iMin and iMax var u=Math.floor((r+a)/2);o[i][u]=o[i-1][u-1],l[i][u]=u;let e=i,n=(// the lower end for j i<r&&(e=Math.max(e,l[i][r-1]||0)),e=Math.max(e,l[i-1][u]||0),u-1);for(let t=// the upper end for j n=a<o.length-1?Math.min(n,l[i][a+1]||0):n;t>=e;--t){var f=p(t,u,s,h);if(f+o[i-1][e-1]>=o[i][u])break; // Examine the lower bound of the cluster border var c=p(e,u,s,h)+o[i-1][e-1];c<o[i][u]&&( // Shrink the lower bound o[i][u]=c,l[i][u]=e),e++,(c=f+o[i-1][t-1])<o[i][u]&&(o[i][u]=c,l[i][u]=t)}t(r,u-1,i,o,l,s,h),t(u+1,a,i,o,l,s,h)}}(t=e<r.length-1?e:t,i-1,e,r,a,l,s)}} /** @module ckmeans @desc Ported to ES6 from the excellent [simple-statistics](https://github.com/simple-statistics/simple-statistics) packages. Ckmeans clustering is an improvement on heuristic-based clustering approaches like Jenks. The algorithm was developed in [Haizhou Wang and Mingzhou Song](http://journal.r-project.org/archive/2011-2/RJournal_2011-2_Wang+Song.pdf) as a [dynamic programming](https://en.wikipedia.org/wiki/Dynamic_programming) approach to the problem of clustering numeric data into groups with the least within-group sum-of-squared-deviations. Minimizing the difference within groups - what Wang & Song refer to as `withinss`, or within sum-of-squares, means that groups are optimally homogenous within and the data is split into representative groups. This is very useful for visualization, where you may want to represent a continuous variable in discrete color or style groups. This function can provide groups that emphasize differences between data. Being a dynamic approach, this algorithm is based on two matrices that store incrementally-computed values for squared deviations and backtracking indexes. This implementation is based on Ckmeans 3.4.6, which introduced a new divide and conquer approach that improved runtime from O(kn^2) to O(kn log(n)). Unlike the [original implementation](https://cran.r-project.org/web/packages/Ckmeans.1d.dp/index.html), this implementation does not include any code to automatically determine the optimal number of clusters: this information needs to be explicitly provided. ### References _Ckmeans.1d.dp: Optimal k-means Clustering in One Dimension by Dynamic Programming_ Haizhou Wang and Mingzhou Song ISSN 2073-4859 from The R Journal Vol. 3/2, December 2011 @param {Array<number>} data input data, as an array of number values @param {number} nClusters number of desired classes. This cannot be greater than the number of values in the data array. @returns {Array<Array<number>>} clustered input @private @example ckmeans([-1, 2, -1, 2, 4, 5, 6, -1, 2, -1], 3); // The input, clustered into groups of similar numbers. //= [[-1, -1, -1, -1], [2, 2, 2], [4, 5, 6]]); */function W(e,n){let r,a;if(void 0===n)for(var t of e)null!=t&&(void 0===r?t>=t&&(r=a=t):(r>t&&(r=t),a<t&&(a=t)));else{let t=-1;for(var i of e)null!=(i=n(i,++t,e))&&(void 0===r?i>=i&&(r=a=i):(r>i&&(r=i),a<i&&(a=i)))}return[r,a]}function k(t){return Array.from(function*(t){for(var e of t)yield*e}(t))}function G(t,e,n){t=+t,e=+e,n=(a=arguments.length)<2?(e=t,t=0,1):a<3?1:+n;for(var r=-1,a=0|Math.max(0,Math.ceil((e-t)/n)),i=new Array(a);++r<a;)i[r]=t+r*n;return i}function D(t,e){for(var n,r,a=t.length,i=t[a-1],o=e[0],l=e[1],s=i[0],h=i[1],u=!1,f=0;f<a;++f)n=(i=t[f])[0],l<(r=i[1])!=l<h&&o<(s-n)*(l-r)/(h-r)+n&&(u=!u),s=n,h=r;return u} /** @function lineIntersection @desc Finds the intersection point (if there is one) of the lines p1q1 and p2q2. @param {Array} p1 The first point of the first line segment, which should always be an `[x, y]` formatted Array. @param {Array} q1 The second point of the first line segment, which should always be an `[x, y]` formatted Array. @param {Array} p2 The first point of the second line segment, which should always be an `[x, y]` formatted Array. @param {Array} q2 The second point of the second line segment, which should always be an `[x, y]` formatted Array. @returns {Boolean} */function m(t,e,n,r){ // allow for some margins due to numerical errors var a=t[0]-e[0],i=n[0]-r[0],o=t[1]-e[1],l=n[1]-r[1],s=a*l-o*i; // find the intersection point between the two infinite lines return Math.abs(s)<1e-9?null:[((t=t[0]*e[1]-t[1]*e[0])*i-(e=n[0]*r[1]-n[1]*r[0])*a)/s,(t*l-e*o)/s]} /** @function segmentBoxContains @desc Checks whether a point is inside the bounding box of a line segment. @param {Array} s1 The first point of the line segment to be used for the bounding box, which should always be an `[x, y]` formatted Array. @param {Array} s2 The second point of the line segment to be used for the bounding box, which should always be an `[x, y]` formatted Array. @param {Array} p The point to be checked, which should always be an `[x, y]` formatted Array. @returns {Boolean} */function d(t,e,n){var[n,r]=n;return!(n<Math.min(t[0],e[0])-1e-9||n>Math.max(t[0],e[0])+1e-9||r<Math.min(t[1],e[1])-1e-9||r>Math.max(t[1],e[1])+1e-9)} /** @function segmentsIntersect @desc Checks whether the line segments p1q1 && p2q2 intersect. @param {Array} p1 The first point of the first line segment, which should always be an `[x, y]` formatted Array. @param {Array} q1 The second point of the first line segment, which should always be an `[x, y]` formatted Array. @param {Array} p2 The first point of the second line segment, which should always be an `[x, y]` formatted Array. @param {Array} q2 The second point of the second line segment, which should always be an `[x, y]` formatted Array. @returns {Boolean} */function h(t,e,n,r){var a=m(t,e,n,r);return!!a&&d(t,e,a)&&d(n,r,a)} /** @function polygonInside @desc Checks if one polygon is inside another polygon. @param {Array} polyA An Array of `[x, y]` points to be used as the inner polygon, checking if it is inside polyA. @param {Array} polyB An Array of `[x, y]` points to be used as the containing polygon. @returns {Boolean} */function F(n,r){let a=-1;var t=n.length,i=r.length;let o=n[t-1];for(;++a<t;){var l=o;o=n[a];let t=-1,e=r[i-1];for(;++t<i;){var s=e;if(e=r[t],h(l,o,s,e))return!1}}return D(r,n[0])} /** @function pointDistanceSquared @desc Returns the squared euclidean distance between two points. @param {Array} p1 The first point, which should always be an `[x, y]` formatted Array. @param {Array} p2 The second point, which should always be an `[x, y]` formatted Array. @returns {Number} */var B=(t,e)=>{var n=e[0]-t[0],e=e[1]-t[1];return n*n+e*e}; /** @function polygonRayCast @desc Gives the two closest intersection points between a ray cast from a point inside a polygon. The two points should lie on opposite sides of the origin. @param {Array} poly The polygon to test against, which should be an `[x, y]` formatted Array. @param {Array} origin The origin point of the ray to be cast, which should be an `[x, y]` formatted Array. @param {Number} [alpha = 0] The angle in radians of the ray. @returns {Array} An array containing two values, the closest point on the left and the closest point on the right. If either point cannot be found, that value will be `null`. */function U(t,e,n=0){var[r,a]=e=[e[0]+1e-9*Math.cos(n),e[1]+1e-9*Math.sin(n)],i=[r+Math.cos(n),a+Math.sin(n)];let o=0,l=(Math.abs(i[0]-r)<1e-9&&(o=1),-1);var s=t.length;let h=t[s-1],u=Number.MAX_VALUE,f=Number.MAX_VALUE,c=null,p=null;for(;++l<s;){var g=h,v=m(e,i,g,h=t[l]);v&&d(g,h,v)&&(g=B(e,v),v[o]<e[o]?g<u&&(u=g,c=v):v[o]>e[o]&&g<f&&(f=g,p=v))}return[c,p]} /** @function pointRotate @desc Rotates a point around a given origin. @param {Array} p The point to be rotated, which should always be an `[x, y]` formatted Array. @param {Number} alpha The angle in radians to rotate. @param {Array} [origin = [0, 0]] The origin point of the rotation, which should always be an `[x, y]` formatted Array. @returns {Boolean} */function r(t,e,n=[0,0]){var r=Math.cos(e),e=Math.sin(e),a=t[0]-n[0],t=t[1]-n[1];return[r*a-e*t+n[0],e*a+r*t+n[1]]} /** @function polygonRotate @desc Rotates a point around a given origin. @param {Array} poly The polygon to be rotated, which should be an Array of `[x, y]` values. @param {Number} alpha The angle in radians to rotate. @param {Array} [origin = [0, 0]] The origin point of the rotation, which should be an `[x, y]` formatted Array. @returns {Boolean} */var X=(t,e,n=[0,0])=>t.map(t=>r(t,e,n)); /** @desc square distance from a point to a segment @param {Array} point @param {Array} segmentAnchor1 @param {Array} segmentAnchor2 @private */ /** @param {Array} polygon @param {Number} first @param {Number} last @param {Number} sqTolerance @param {Array} simplified @private */function u(e,n,r,t,a){let i,o=t;for(let t=n+1;t<r;t++){var l=((t,e,n)=>{let r=e[0],a=e[1];var i,e=n[0]-r,o=n[1]-a;return 0==e&&0==o||(1<(i=((t[0]-r)*e+(t[1]-a)*o)/(e*e+o*o))?(r=n[0],a=n[1]):0<i&&(r+=e*i,a+=o*i)),(e=t[0]-r)*e+(o=t[1]-a)*o} /** @desc basic distance-based simplification @param {Array} polygon @param {Number} sqTolerance @private */)(e[t],e[n],e[r]);l>o&&(i=t,o=l)}o>t&&(1<i-n&&u(e,n,i,t,a),a.push(e[i]),1<r-i)&&u(e,i,r,t,a)} /** @desc simplification using Ramer-Douglas-Peucker algorithm @param {Array} polygon @param {Number} sqTolerance @private */ /** @function largestRect @desc Simplifies the points of a polygon using both the Ramer-Douglas-Peucker algorithm and basic distance-based simplification. Adapted to an ES6 module from the excellent [Simplify.js](http://mourner.github.io/simplify-js/). @author Vladimir Agafonkin @param {Array} poly An Array of points that represent a polygon. @param {Number} [tolerance = 1] Affects the amount of simplification (in the same metric as the point coordinates). @param {Boolean} [highestQuality = false] Excludes distance-based preprocessing step which leads to highest quality simplification but runs ~10-20 times slower. */var _=(t,e=1,n=!1)=>{var r;return t.length<=2||(e=e*e,t=n?t:((n,r)=>{let a,i=n[0];var o=[i];for(let t=1,e=n.length;t<e;t++)a=n[t],B(a,i)>r&&(o.push(a),i=a);return i!==a&&o.push(a),o})(t,e),r=(n=t).length-1,u(n,0,r,e,e=[n[0]]),e.push(n[r]),t=e),t}; // Algorithm constants let z={};// step size for the aspect ratio /** @function path2polygon @desc Transforms a path string into an Array of points. @param {String} path An SVG string path, commonly the "d" property of a <path> element. @param {Number} [segmentLength = 50] The length of line segments when converting curves line segments. Higher values lower computation time, but will result in curves that are more rigid. @returns {Array} */let o=Math.PI; /** @function pointDistance @desc Calculates the pixel distance between two points. @param {Array} p1 The first point, which should always be an `[x, y]` formatted Array. @param {Array} p2 The second point, which should always be an `[x, y]` formatted Array. @returns {Number} */t.ckmeans=function(t,e){if(e>t.length)throw new Error("Cannot generate more classes than there are data values");var n=t.slice().sort((t,e)=>t-e); // we'll use this as the maximum number of clusters // if all of the input values are identical, there's one cluster with all of the input in it. if(1===( /** For a sorted input, counting the number of unique values is possible in constant time and constant memory. This is a simple implementation of the algorithm. Values are compared with `===`, so objects and non-primitive objects are not handled in any special way. @private @param {Array} input an array of primitive values. @returns {number} count of unique values @example uniqueCountSorted([1, 2, 3]); // => 3 uniqueCountSorted([1, 1, 1]); // => 1 */e=>{let n,r=0;for(let t=0;t<e.length;t++)0!==t&&e[t]===n||(n=e[t],r++);return r})(n))return[n];var r=l(e,n.length); // This is a dynamic programming way to solve the problem of minimizing within-cluster sum of squares. It's similar to linear regression in this way, and this calculation incrementally computes the sum of squares that are later read. s(n,l(e,n.length),r); // The real work of Ckmeans clustering happens in the matrix generation: the generated matrices encode all possible clustering combinations, and once they're generated we can solve for the best clustering groups very quickly. let a=r[0]?r[0].length-1:0;var i=[]; // Backtrack the clusters from the dynamic programming matrix. This starts at the bottom-right corner of the matrix (if the top-left is 0, 0), and moves the cluster target with the loop. for(let t=r.length-1;0<=t;t--){var o=r[t][a]; // fill the cluster from the sorted input by taking a slice of the array. the backtrack matrix makes this easy - it stores the indexes where the cluster should start and end. i[t]=n.slice(o,a+1),0<t&&(a=o-1)}return i} /** @function closest @desc Finds the closest numeric value in an array. @param {Number} n The number value to use when searching the array. @param {Array} arr The array of values to test against. */,t.closest=function(n,t=[]){if(t&&t instanceof Array&&t.length)return t.reduce((t,e)=>Math.abs(e-n)<Math.abs(t-n)?e:t)},t.largestRect= /** @typedef {Object} largestRect @desc The returned Object of the largestRect function. @property {Number} width The width of the rectangle @property {Number} height The height of the rectangle @property {Number} cx The x coordinate of the rectangle's center @property {Number} cy The y coordinate of the rectangle's center @property {Number} angle The rotation angle of the rectangle in degrees. The anchor of rotation is the center point. @property {Number} area The area of the largest rectangle. @property {Array} points An array of x/y coordinates for each point in the rectangle, useful for rendering paths. */ /** @function largestRect @author Daniel Smilkov [dsmilkov@gmail.com] @desc An angle of zero means that the longer side of the polygon (the width) will be aligned with the x axis. An angle of 90 and/or -90 means that the longer side of the polygon (the width) will be aligned with the y axis. The value can be a number between -90 and 90 specifying the angle of rotation of the polygon, a string which is parsed to a number, or an array of numbers specifying the possible rotations of the polygon. @param {Array} poly An Array of points that represent a polygon. @param {Object} [options] An Object that allows for overriding various parameters of the algorithm. @param {Number|String|Array} [options.angle = d3.range(-90, 95, 5)] The allowed rotations of the final rectangle. @param {Number|String|Array} [options.aspectRatio] The ratio between the width and height of the rectangle. The value can be a number, a string which is parsed to a number, or an array of numbers specifying the possible aspect ratios of the final rectangle. @param {Number} [options.maxAspectRatio = 15] The maximum aspect ratio (width/height) allowed for the rectangle. This property should only be used if the aspectRatio is not provided. @param {Number} [options.minAspectRatio = 1] The minimum aspect ratio (width/height) allowed for the rectangle. This property should only be used if the aspectRatio is not provided. @param {Number} [options.nTries = 20] The number of randomly drawn points inside the polygon which the algorithm explores as possible center points of the maximal rectangle. @param {Number} [options.minHeight = 0] The minimum height of the rectangle. @param {Number} [options.minWidth = 0] The minimum width of the rectangle. @param {Number} [options.tolerance = 0.02] The simplification tolerance factor, between 0 and 1. A larger tolerance corresponds to more extensive simplification. @param {Array} [options.origin] The center point of the rectangle. If specified, the rectangle will be fixed at that point, otherwise the algorithm optimizes across all possible points. The given value can be either a two dimensional array specifying the x and y coordinate of the origin or an array of two dimensional points specifying multiple possible center points of the rectangle. @param {Boolean} [options.cache] Whether or not to cache the result, which would be used in subsequent calculations to preserve consistency and speed up calculation time. @return {largestRect} */function(a,i={}){if(a.length<3)return i.verbose&&console.error("polygon has to have at least 3 points",a),null; // For visualization debugging purposes var o=[],e=( // User's input normalization i=Object.assign({angle:G(-90,95,5),cache:!0,maxAspectRatio:15,minAspectRatio:1,minHeight:0,minWidth:0,nTries:20,tolerance:.02,verbose:!1},i)).angle instanceof Array?i.angle:"number"==typeof i.angle?[i.angle]:"string"!=typeof i.angle||isNaN(i.angle)?[]:[Number(i.angle)],O=i.aspectRatio instanceof Array?i.aspectRatio:"number"==typeof i.aspectRatio?[i.aspectRatio]:"string"!=typeof i.aspectRatio||isNaN(i.aspectRatio)?[]:[Number(i.aspectRatio)],n=i.origin&&i.origin instanceof Array?i.origin[0]instanceof Array?i.origin:[i.origin]:[];let t;if(i.cache&&(t=k(a).join(","),t=(t=(t=(t=(t=(t+="-"+i.minAspectRatio)+"-"+i.maxAspectRatio)+"-"+i.minHeight)+"-"+i.minWidth)+"-"+e.join(","))+"-"+n.join(","),z[t]))return z[t];var l=Math.abs((t=>{for(var e,n=-1,r=t.length,a=t[r-1],i=0;++n<r;)e=a,a=t[n],i+=e[1]*a[0]-e[0]*a[1];return i/2})(a));// take absolute value of the signed area if(0===l)return i.verbose&&console.error("polygon has 0 area",a),null; // get the width of the bounding box of the original polygon to determine tolerance var[r,s]=W(a,t=>t[0]),[h,u]=W(a,t=>t[1]),f=Math.min(s-r,u-h)*i.tolerance,[c,p]=(0<f&&(a=_(a,f)),i.events&&o.push({type:"simplify",poly:a}), // get the width of the bounding box of the simplified polygon [r,s]=W(a,t=>t[0]),[h,u]=W(a,t=>t[1]),[s-r,u-h]),g=Math.min(c,p)/50; // populate possible center points with random points inside the polygon if(!n.length){ // get the centroid of the polygon f=(t=>{for(var e,n,r=-1,a=t.length,i=0,o=0,l=t[a-1],s=0;++r<a;)e=l,l=t[r],s+=n=e[0]*l[1]-l[0]*e[1],i+=(e[0]+l[0])*n,o+=(e[1]+l[1])*n;return[i/(s*=3),o/s]})(a);if(!isFinite(f[0]))return i.verbose&&console.error("cannot find centroid",a),null;D(a,f)&&n.push(f);let t=i.nTries; // get few more points inside the polygon for(;t;){var v=[Math.random()*c+r,Math.random()*p+h];D(a,v)&&n.push(v),t--}}i.events&&o.push({type:"origins",points:n});let m=0,d=null;for(let t=0;t<e.length;t++){var M=e[t],y=-M*Math.PI/180;i.events&&o.push({type:"angle",angle:M});for(let t=0;t<n.length;t++){var b=n[t],[x,w]=U(a,b,y),[b,A]=U(a,b,y+Math.PI/2),R=[]; // generate improved origins x&&w&&R.push([(x[0]+w[0])/2,(x[1]+w[1])/2]),// average along with width axis b&&A&&R.push([(b[0]+A[0])/2,(b[1]+A[1])/2]),// average along with height axis i.events&&o.push({type:"modifOrigin",idx:t,p1W:x,p2W:w,p1H:b,p2H:A,modifOrigins:R});for(let t=0;t<R.length;t++){var E=R[t],[N,P]=(i.events&&o.push({type:"origin",cx:E[0],cy:E[1]}),U(a,E,y));if(null!==N&&null!==P){var N=Math.min(B(E,N),B(E,P)),T=2*Math.sqrt(N),[P,N]=U(a,E,y+Math.PI/2);if(null!==P&&null!==N){var P=Math.min(B(E,P),B(E,N)),C=2*Math.sqrt(P);if(!(T*C<m)){let r=O;r.length||(N=Math.max(i.minAspectRatio,i.minWidth/C,m/(C*C)),P=Math.min(i.maxAspectRatio,T/i.minHeight,T*T/m),r=G(N,P+.5,.5));for(let n=0;n<r.length;n++){var H=r[n]; // do a binary search to find the max width that works let t=Math.max(i.minWidth,Math.sqrt(m*H)),e=Math.min(T,C*H);if(!(e*C<m))for(i.events&&e-t>=g&&o.push({type:"aRatio",aRatio:H});e-t>=g;){var j=(t+e)/2,L=j/H,[S,V]=E,I=[[S-j/2,V-L/2],[S+j/2,V-L/2],[S+j/2,V+L/2],[S-j/2,V+L/2]],q=F(I=X(I,y,E),a);q?( // we know that the area is already greater than the maxArea found so far m=j*L,I.push(I[0]),d={area:m,cx:S,cy:V,width:j,height:L,angle:-M,points:I},t=j):e=j,i.events&&o.push({type:"rectangle",areaFraction:j*L/l,cx:S,cy:V,width:j,height:L,angle:M,insidePoly:q})}}}}}}}}return i.cache&&(z[t]=d),i.events?Object.assign(d||{},{events:o}):d},t.lineIntersection=m,t.path2polygon=(t,e=50)=>{if("undefined"==typeof document)return[];var n=document.createElementNS("http://www.w3.org/2000/svg","path"),r=(n.setAttribute("d",t),n.getTotalLength()),a=r/e<10?r/10:r/e,i=[];for(let t=0;t<a;t++){var o=n.getPointAtLength(t*r/(a-1));i.push([o.x,o.y])}return i},t.pointDistance=(t,e)=>Math.sqrt(B(t,e)),t.pointDistanceSquared=B,t.pointRotate=r,t.polygonInside=F,t.polygonRayCast=U,t.polygonRotate=X,t.segmentBoxContains=d,t.segmentsIntersect=h,t.shapeEdgePoint=(n,r,a="circle")=>{if(n<0&&(n=2*o+n),"square"!==a)return"circle"===a?[r*Math.cos(n),r*Math.sin(n)]:null;{var i,a=o/180*45;let t=0,e=0;return n<o/2?(i=Math.tan(n),t+=n<a?r:r/i,e+=n<a?i*r:r):n<=o?(i=Math.tan(o-n),t-=n<o-a?r/i:r,e+=n<o-a?r:i*r):n<a+o?(t-=r,e-=Math.tan(n-o)*r):n<3*o/2?(t-=r/Math.tan(n-o),e-=r):n<2*o-a?(t+=r/Math.tan(2*o-n),e-=r):(t+=r,e-=Math.tan(2*o-n)*r),[t,e]}},t.simplify=_}); //# sourceMappingURL=d3plus-math.full.js.map