(data: ReadonlyArray<DataPoint>, targetPoints: number)
| 86 | } |
| 87 | |
| 88 | function lttbIndicesForDataPoints(data: ReadonlyArray<DataPoint>, targetPoints: number): Int32Array { |
| 89 | const n = data.length; |
| 90 | const lastIndex = n - 1; |
| 91 | |
| 92 | if (targetPoints <= 0 || n === 0) return new Int32Array(0); |
| 93 | if (targetPoints === 1) return new Int32Array([0]); |
| 94 | if (targetPoints === 2) return n >= 2 ? new Int32Array([0, lastIndex]) : new Int32Array([0]); |
| 95 | if (n <= targetPoints) { |
| 96 | const indices = new Int32Array(n); |
| 97 | for (let i = 0; i < n; i++) indices[i] = i; |
| 98 | return indices; |
| 99 | } |
| 100 | |
| 101 | const indices = new Int32Array(targetPoints); |
| 102 | indices[0] = 0; |
| 103 | indices[targetPoints - 1] = lastIndex; |
| 104 | |
| 105 | const bucketSize = (n - 2) / (targetPoints - 2); |
| 106 | |
| 107 | let a = 0; |
| 108 | let out = 1; |
| 109 | |
| 110 | const pLast = data[lastIndex]!; |
| 111 | const lastX = isTupleDataPoint(pLast) ? pLast[0] : pLast.x; |
| 112 | const lastY = isTupleDataPoint(pLast) ? pLast[1] : pLast.y; |
| 113 | |
| 114 | for (let bucket = 0; bucket < targetPoints - 2; bucket++) { |
| 115 | // Current bucket: candidate points are [rangeStart, rangeEndExclusive) and never include lastIndex. |
| 116 | let rangeStart = Math.floor(bucketSize * bucket) + 1; |
| 117 | let rangeEndExclusive = Math.min(Math.floor(bucketSize * (bucket + 1)) + 1, lastIndex); |
| 118 | if (rangeStart >= rangeEndExclusive) { |
| 119 | // Defensive: ensure at least one candidate point. |
| 120 | rangeStart = Math.min(rangeStart, lastIndex - 1); |
| 121 | rangeEndExclusive = Math.min(rangeStart + 1, lastIndex); |
| 122 | } |
| 123 | |
| 124 | // Next bucket for average: [nextRangeStart, nextRangeEndExclusive) |
| 125 | const nextRangeStart = Math.floor(bucketSize * (bucket + 1)) + 1; |
| 126 | const nextRangeEndExclusive = Math.min(Math.floor(bucketSize * (bucket + 2)) + 1, lastIndex); |
| 127 | |
| 128 | // If there are no points in the next bucket, use the last point as the average. |
| 129 | let avgX = lastX; |
| 130 | let avgY = lastY; |
| 131 | if (nextRangeStart < nextRangeEndExclusive) { |
| 132 | let sumX = 0; |
| 133 | let sumY = 0; |
| 134 | let avgCount = 0; |
| 135 | for (let i = nextRangeStart; i < nextRangeEndExclusive; i++) { |
| 136 | const p = data[i]!; |
| 137 | const x = isTupleDataPoint(p) ? p[0] : p.x; |
| 138 | const y = isTupleDataPoint(p) ? p[1] : p.y; |
| 139 | sumX += x; |
| 140 | sumY += y; |
| 141 | avgCount++; |
| 142 | } |
| 143 | if (avgCount > 0) { |
| 144 | avgX = sumX / avgCount; |
| 145 | avgY = sumY / avgCount; |
no test coverage detected