vendor: 6,256 bytes, lines 1-158
1/* * 2 * 3 * (c) 2010-2024 Torstein Honsi 4 * 5 * License: www.highcharts.com/license 6 * 7 * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! 8 * 9 * */ 10'use strict'; 11/* * 12 * 13 * Imports 14 * 15 * */ 16import U from '../Utilities.js'; 17const { clamp, pick, pushUnique, stableSort } = U; 18/* * 19 * 20 * Namespace 21 * 22 * */ 23var RendererUtilities; 24(function (RendererUtilities) { 25 /* * 26 * 27 * Declarations 28 * 29 * */ 30 /* * 31 * 32 * Functions 33 * 34 * */ 35 /* eslint-disable valid-jsdoc */ 36 /** 37 * General distribution algorithm for distributing labels of differing size 38 * along a confined length in two dimensions. The algorithm takes an array 39 * of objects containing a size, a target and a rank. It will place the 40 * labels as close as possible to their targets, skipping the lowest ranked 41 * labels if necessary. 42 * @private 43 */ 44 function distribute(boxes, len, maxDistance) { 45 // Original array will be altered with added .pos 46 const origBoxes = boxes, reducedLen = origBoxes.reducedLen || len, sortByRank = (a, b) => (b.rank || 0) - (a.rank || 0), sortByTarget = (a, b) => a.target - b.target, restBoxes = [], // The outranked overshoot 47 boxesLength = boxes.length, forDeletion = [], push = restBoxes.push; 48 let i, cursor, step, overlapping = true, box, target, total = 0, equalRank; 49 // If the total size exceeds the len, remove those boxes with the lowest 50 // rank 51 i = boxesLength; 52 while (i--) { 53 total += boxes[i].size; 54 } 55 // Sort by rank, then slice away overshoot 56 if (total > reducedLen) { 57 stableSort(boxes, sortByRank); 58 equalRank = boxes[0].rank === boxes[boxes.length - 1].rank; 59 step = equalRank ? boxesLength / 2 : -1; 60 cursor = equalRank ? step : boxesLength - 1; 61 // When the boxes have equal rank (pie data labels, flags - #10073), 62 // decimate the boxes by starting in the middle and gradually remove 63 // more items inside the array. When they are sorted by rank, just 64 // remove the ones with the lowest rank from the end. 65 while (step && total > reducedLen) { 66 i = Math.floor(cursor); 67 box = boxes[i]; 68 if (pushUnique(forDeletion, i)) { 69 total -= box.size; 70 } 71 cursor += step; 72 // Start over the decimation with smaller steps 73 if (equalRank && cursor >= boxes.length) { 74 step /= 2; 75 cursor = step; 76 } 77 } 78 // Clean out the boxes marked for deletion 79 forDeletion 80 .sort((a, b) => b - a) 81 .forEach((i) => push.apply(restBoxes, boxes.splice(i, 1))); 82 } 83 // Order by target 84 stableSort(boxes, sortByTarget); 85 // So far we have been mutating the original array. Now 86 // create a copy with target arrays 87 boxes = boxes.map((box) => ({ 88 size: box.size, 89 targets: [box.target], 90 align: pick(box.align, 0.5) 91 })); 92 while (overlapping) { 93 // Initial positions: target centered in box 94 i = boxes.length; 95 while (i--) { 96 box = boxes[i]; 97 // Composite box, average of targets 98 target = (Math.min.apply(0, box.targets) + 99 Math.max.apply(0, box.targets)) / 2; 100 box.pos = clamp(target - box.size * box.align, 0, len - box.size); 101 } 102 // Detect overlap and join boxes 103 i = boxes.length; 104 overlapping = false; 105 while (i--) { 106 // Overlap 107 if (i > 0 && 108 boxes[i - 1].pos + boxes[i - 1].size > 109 boxes[i].pos) { 110 // Add this size to the previous box 111 boxes[i - 1].size += boxes[i].size; 112 boxes[i - 1].targets = boxes[i - 1] 113 .targets 114 .concat(boxes[i].targets); 115 boxes[i - 1].align = 0.5; 116 // Overlapping right, push left 117 if (boxes[i - 1].pos + boxes[i - 1].size > len) { 118 boxes[i - 1].pos = len - boxes[i - 1].size; 119 } 120 boxes.splice(i, 1); // Remove this item 121 overlapping = true; 122 } 123 } 124 } 125 // Add the rest (hidden boxes) 126 push.apply(origBoxes, restBoxes); 127 // Now the composite boxes are placed, we need to put the original boxes 128 // within them 129 i = 0; 130 boxes.some((box) => { 131 let posInCompositeBox = 0; 132 // Exceeded maxDistance => abort 133 return (box.targets || []).some(() => { 134 origBoxes[i].pos = box.pos + posInCompositeBox; 135 // If the distance between the position and the target exceeds 136 // maxDistance, abort the loop and decrease the length in 137 // increments of 10% to recursively reduce the number of 138 // visible boxes by rank. Once all boxes are within the 139 // maxDistance, we're good. 140 if (typeof maxDistance !== 'undefined' && 141 Math.abs(origBoxes[i].pos - origBoxes[i].target) > maxDistance) { 142 // Reset the positions that are already set 143 origBoxes 144 .slice(0, i + 1) 145 .forEach((box) => delete box.pos); 146 // Try with a smaller length 147 origBoxes.reducedLen = 148 (origBoxes.reducedLen || len) - (len * 0.1); 149 // Recurse 150 if (origBoxes.reducedLen > len * 0.1) { 151 distribute(origBoxes, len, maxDistance); 152 } 153 // Exceeded maxDistance => abort 154 return true; 155 } 156 posInCompositeBox += origBoxes[i].size; 157 i++; 158 return false;
159 }); 160 }); 161 // Add the rest (hidden) boxes and sort by target 162 stableSort(origBoxes, sortByTarget); 163 return origBoxes; 164 } 165 RendererUtilities.distribute = distribute; 166})(RendererUtilities || (RendererUtilities = {})); 167/* * 168 * 169 * Default Export 170 * 171 * */ 172export default RendererUtilities;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.