28 std::vector<double> x;
29 std::vector<double> y;
33inline std::size_t decimLowerBound(
const double *x, std::size_t n,
double target) {
34 std::size_t lo = 0, hi = n;
36 std::size_t mid = (lo + hi) >> 1;
37 if (x[mid] < target) lo = mid + 1;
else hi = mid;
44inline void decimVisibleRange(
const double *x, std::size_t n,
double x0,
double x1,
45 std::size_t &i0, std::size_t &i1) {
46 if (n == 0) { i0 = i1 = 0;
return; }
47 i0 = decimLowerBound(x, n, x0);
48 i1 = decimLowerBound(x, n, x1);
57inline DecimatedSeries decimateM4(
const double *x,
const double *y, std::size_t n,
58 double x0,
double x1,
int width) {
61 decimVisibleRange(x, n, x0, x1, i0, i1);
62 if (i1 <= i0)
return out;
63 const int cols = width < 1 ? 1 : width;
64 const double span = (x1 - x0) != 0.0 ? (x1 - x0) : 1.0;
65 auto bucketOf = [&](
double xv) ->
int {
66 int b =
static_cast<int>(std::floor(((xv - x0) / span) * cols));
67 if (b < 0) b = 0;
else if (b >= cols) b = cols - 1;
71 out.x.reserve(
static_cast<std::size_t
>(cols) * 4);
72 out.y.reserve(
static_cast<std::size_t
>(cols) * 4);
74 std::size_t bFirst = i0, bLast = i0, bMinI = i0, bMaxI = i0;
75 double bMinY = 0.0, bMaxY = 0.0;
77 if (curBucket < 0)
return;
78 std::size_t idxs[4] = { bFirst, bMinI, bMaxI, bLast };
79 std::sort(idxs, idxs + 4);
81 for (std::size_t ii : idxs) {
82 if (
static_cast<long long>(ii) != prev) {
83 out.x.push_back(x[ii]);
84 out.y.push_back(y[ii]);
85 prev =
static_cast<long long>(ii);
90 for (std::size_t i = i0; i < i1; ++i) {
91 double xv = x[i], yv = y[i];
96 bFirst = i; bLast = i; bMinI = i; bMaxI = i;
97 bMinY = std::isfinite(yv) ? yv : INFINITY;
98 bMaxY = std::isfinite(yv) ? yv : -INFINITY;
101 if (std::isfinite(yv)) {
102 if (yv < bMinY) { bMinY = yv; bMinI = i; }
103 if (yv > bMaxY) { bMaxY = yv; bMaxI = i; }
113inline DecimatedSeries decimateM2(
const double *x,
const double *y, std::size_t n,
114 double x0,
double x1,
int width) {
117 decimVisibleRange(x, n, x0, x1, i0, i1);
118 if (i1 <= i0)
return out;
119 const int cols = width < 1 ? 1 : width;
120 const double span = (x1 - x0) != 0.0 ? (x1 - x0) : 1.0;
121 auto bucketOf = [&](
double xv) ->
int {
122 int b =
static_cast<int>(std::floor(((xv - x0) / span) * cols));
123 if (b < 0) b = 0;
else if (b >= cols) b = cols - 1;
126 out.x.reserve(
static_cast<std::size_t
>(cols) * 2);
127 out.y.reserve(
static_cast<std::size_t
>(cols) * 2);
129 std::size_t bMinI = i0, bMaxI = i0;
130 double bMinY = 0.0, bMaxY = 0.0;
132 if (curBucket < 0)
return;
133 std::size_t a = std::min(bMinI, bMaxI), b = std::max(bMinI, bMaxI);
134 out.x.push_back(x[a]); out.y.push_back(y[a]);
135 if (b != a) { out.x.push_back(x[b]); out.y.push_back(y[b]); }
137 for (std::size_t i = i0; i < i1; ++i) {
138 double xv = x[i], yv = y[i];
139 int bk = bucketOf(xv);
140 if (bk != curBucket) {
142 curBucket = bk; bMinI = i; bMaxI = i;
143 bMinY = std::isfinite(yv) ? yv : INFINITY;
144 bMaxY = std::isfinite(yv) ? yv : -INFINITY;
145 }
else if (std::isfinite(yv)) {
146 if (yv < bMinY) { bMinY = yv; bMinI = i; }
147 if (yv > bMaxY) { bMaxY = yv; bMaxI = i; }
155inline DecimatedSeries decimateLTTB(
const double *x,
const double *y, std::size_t n,
156 double x0,
double x1,
int threshold) {
159 decimVisibleRange(x, n, x0, x1, i0, i1);
160 const std::size_t cnt = i1 - i0;
161 if (cnt == 0)
return out;
162 if (threshold < 3 ||
static_cast<std::size_t
>(threshold) >= cnt) {
163 for (std::size_t i = i0; i < i1; ++i) { out.x.push_back(x[i]); out.y.push_back(y[i]); }
166 out.x.reserve(
static_cast<std::size_t
>(threshold));
167 out.y.reserve(
static_cast<std::size_t
>(threshold));
168 const double bucketSize =
static_cast<double>(cnt - 2) / (threshold - 2);
170 out.x.push_back(x[i0]); out.y.push_back(y[i0]);
171 for (
int i = 0; i < threshold - 2; ++i) {
172 std::size_t avgStart = i0 +
static_cast<std::size_t
>(std::floor((i + 1) * bucketSize)) + 1;
173 std::size_t avgEnd = i0 +
static_cast<std::size_t
>(std::floor((i + 2) * bucketSize)) + 1;
174 if (avgEnd > i1) avgEnd = i1;
175 double avgX = 0.0, avgY = 0.0;
176 const std::size_t avgN = avgEnd > avgStart ? (avgEnd - avgStart) : 1;
177 for (std::size_t j = avgStart; j < avgEnd; ++j) { avgX += x[j]; avgY += y[j]; }
178 avgX /= avgN; avgY /= avgN;
179 const std::size_t rangeStart = i0 +
static_cast<std::size_t
>(std::floor(i * bucketSize)) + 1;
180 const std::size_t rangeEnd = i0 +
static_cast<std::size_t
>(std::floor((i + 1) * bucketSize)) + 1;
181 const double ax = x[a], ay = y[a];
182 double maxArea = -1.0;
183 std::size_t maxIdx = rangeStart;
184 for (std::size_t j = rangeStart; j < rangeEnd && j < i1; ++j) {
185 double area = std::fabs((ax - avgX) * (y[j] - ay) - (ax - x[j]) * (avgY - ay));
186 if (area > maxArea) { maxArea = area; maxIdx = j; }
188 out.x.push_back(x[maxIdx]); out.y.push_back(y[maxIdx]);
191 out.x.push_back(x[i1 - 1]); out.y.push_back(y[i1 - 1]);
197enum class DecimAlgo { M4 = 0, LTTB = 1, None = 2, M2 = 3 };
199inline DecimatedSeries decimateSeries(
const double *x,
const double *y, std::size_t n,
200 double x0,
double x1,
int width, DecimAlgo algo) {
201 const int w = width < 1 ? 1 : width;
203 decimVisibleRange(x, n, x0, x1, i0, i1);
204 const std::size_t cnt = i1 - i0;
205 if (algo == DecimAlgo::None || cnt <=
static_cast<std::size_t
>(w) * 2) {
207 for (std::size_t i = i0; i < i1; ++i) { out.x.push_back(x[i]); out.y.push_back(y[i]); }
210 if (algo == DecimAlgo::LTTB)
return decimateLTTB(x, y, n, x0, x1, w);
211 if (algo == DecimAlgo::M2)
return decimateM2(x, y, n, x0, x1, w);
212 return decimateM4(x, y, n, x0, x1, w);
220inline std::vector<DecimatedSeries> buildPyramid(
const double *x,
const double *y,
221 std::size_t n, std::size_t baseTarget = 8000) {
222 std::vector<DecimatedSeries> levels;
223 const double *cx = x;
224 const double *cy = y;
226 while (s > baseTarget) {
227 const int cols =
static_cast<int>(std::max<std::size_t>(1, (s + 7) / 8));
229 if (d.x.size() >= s)
break;
230 levels.push_back(std::move(d));
231 cx = levels.back().x.data();
232 cy = levels.back().y.data();
233 s = levels.back().x.size();
240inline DecimatedSeries decimateLOD(
const double *rawX,
const double *rawY, std::size_t rawN,
241 const std::vector<DecimatedSeries> &pyramid,
242 double x0,
double x1,
int width, DecimAlgo algo) {
243 const int w = width < 1 ? 1 : width;
244 const double *bx = rawX;
245 const double *by = rawY;
246 std::size_t bn = rawN;
247 for (
int i =
static_cast<int>(pyramid.size()) - 1; i >= -1; --i) {
248 const double *lx;
const double *ly; std::size_t ln;
249 if (i >= 0) { lx = pyramid[i].x.data(); ly = pyramid[i].y.data(); ln = pyramid[i].x.size(); }
250 else { lx = rawX; ly = rawY; ln = rawN; }
252 decimVisibleRange(lx, ln, x0, x1, a, b);
253 if (b - a >=
static_cast<std::size_t
>(w) * 2 || i == -1) { bx = lx; by = ly; bn = ln;
break; }
255 return decimateSeries(bx, by, bn, x0, x1, w, algo);