@d3plus/math
Version:
Mathematical functions to aid in calculating visualizations.
289 lines (281 loc) • 25.1 kB
JavaScript
/*
@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,"&").replace(/</g,"<").replace(/>/g,">");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