graphein 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +278 -62
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -456,6 +456,10 @@ function logScale(opts) {
|
|
|
456
456
|
map(value) {
|
|
457
457
|
const [d0, d1] = transformedDomain;
|
|
458
458
|
const [r0, r1] = range;
|
|
459
|
+
if (!Number.isFinite(value) || value * sign2 <= 0) return NaN;
|
|
460
|
+
if (d0 === d1) {
|
|
461
|
+
return clamp11 ? clampValue2(r0, Math.min(r0, r1), Math.max(r0, r1)) : r0;
|
|
462
|
+
}
|
|
459
463
|
const transformed = transform(value, sign2, base);
|
|
460
464
|
const mapped = r0 + (transformed - d0) / (d1 - d0) * (r1 - r0);
|
|
461
465
|
return clamp11 ? clampValue2(mapped, Math.min(r0, r1), Math.max(r0, r1)) : mapped;
|
|
@@ -463,6 +467,7 @@ function logScale(opts) {
|
|
|
463
467
|
invert(pixel) {
|
|
464
468
|
const [d0, d1] = transformedDomain;
|
|
465
469
|
const [r0, r1] = range;
|
|
470
|
+
if (d0 === d1) return domain[0];
|
|
466
471
|
const input = clamp11 ? clampValue2(pixel, Math.min(r0, r1), Math.max(r0, r1)) : pixel;
|
|
467
472
|
return untransform(d0 + (input - r0) / (r1 - r0) * (d1 - d0), sign2, base);
|
|
468
473
|
},
|
|
@@ -1938,6 +1943,10 @@ var SI_SYMBOLS = [
|
|
|
1938
1943
|
function groupThousands(intDigits) {
|
|
1939
1944
|
return intDigits.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
1940
1945
|
}
|
|
1946
|
+
function clampDigits(value, min, max) {
|
|
1947
|
+
if (!Number.isFinite(value)) return min;
|
|
1948
|
+
return Math.min(max, Math.max(min, Math.trunc(value)));
|
|
1949
|
+
}
|
|
1941
1950
|
function applyGroup(numStr, group) {
|
|
1942
1951
|
if (!group) return numStr;
|
|
1943
1952
|
const neg = numStr.startsWith("-");
|
|
@@ -1957,11 +1966,11 @@ function formatSI(value, precision) {
|
|
|
1957
1966
|
if (abs >= factor) {
|
|
1958
1967
|
const scaled = value / factor;
|
|
1959
1968
|
const digits2 = precision ?? (Math.abs(scaled) >= 100 ? 0 : Math.abs(scaled) >= 10 ? 1 : 2);
|
|
1960
|
-
return trimZeros(scaled.toFixed(digits2)) + symbol;
|
|
1969
|
+
return trimZeros(scaled.toFixed(clampDigits(digits2, 0, 100))) + symbol;
|
|
1961
1970
|
}
|
|
1962
1971
|
}
|
|
1963
1972
|
const digits = precision ?? (abs >= 100 || abs === 0 ? 0 : abs >= 1 ? 1 : 2);
|
|
1964
|
-
return trimZeros(value.toFixed(digits));
|
|
1973
|
+
return trimZeros(value.toFixed(clampDigits(digits, 0, 100)));
|
|
1965
1974
|
}
|
|
1966
1975
|
function formatNumber2(value, spec) {
|
|
1967
1976
|
if (value == null || Number.isNaN(value)) return "";
|
|
@@ -1971,10 +1980,10 @@ function formatNumber2(value, spec) {
|
|
|
1971
1980
|
let body;
|
|
1972
1981
|
switch (type) {
|
|
1973
1982
|
case "f":
|
|
1974
|
-
body = applyGroup(value.toFixed(precision ?? 2), group);
|
|
1983
|
+
body = applyGroup(value.toFixed(clampDigits(precision ?? 2, 0, 100)), group);
|
|
1975
1984
|
break;
|
|
1976
1985
|
case "%":
|
|
1977
|
-
body = applyGroup((value * 100).toFixed(precision ?? 0), group) + "%";
|
|
1986
|
+
body = applyGroup((value * 100).toFixed(clampDigits(precision ?? 0, 0, 100)), group) + "%";
|
|
1978
1987
|
break;
|
|
1979
1988
|
case "s":
|
|
1980
1989
|
body = formatSI(value, precision);
|
|
@@ -1983,17 +1992,17 @@ function formatNumber2(value, spec) {
|
|
|
1983
1992
|
body = applyGroup(Math.round(value).toString(), group);
|
|
1984
1993
|
break;
|
|
1985
1994
|
case "e":
|
|
1986
|
-
body = value.toExponential(precision ?? 2);
|
|
1995
|
+
body = value.toExponential(clampDigits(precision ?? 2, 0, 100));
|
|
1987
1996
|
break;
|
|
1988
1997
|
case "g":
|
|
1989
|
-
body = trimZeros(Number(value.toPrecision(precision ?? 6)).toString());
|
|
1998
|
+
body = trimZeros(Number(value.toPrecision(clampDigits(precision ?? 6, 1, 100))).toString());
|
|
1990
1999
|
body = applyGroup(body, group);
|
|
1991
2000
|
break;
|
|
1992
2001
|
default: {
|
|
1993
2002
|
if (Number.isInteger(value)) {
|
|
1994
2003
|
body = applyGroup(value.toString(), group);
|
|
1995
2004
|
} else {
|
|
1996
|
-
const digits = precision ?? 6;
|
|
2005
|
+
const digits = clampDigits(precision ?? 6, 0, 100);
|
|
1997
2006
|
body = applyGroup(trimZeros(value.toFixed(digits)), group);
|
|
1998
2007
|
}
|
|
1999
2008
|
}
|
|
@@ -2122,7 +2131,13 @@ function toNumber(value) {
|
|
|
2122
2131
|
}
|
|
2123
2132
|
return NaN;
|
|
2124
2133
|
}
|
|
2134
|
+
var ISO_YEAR_RE = /^(\d{4})$/;
|
|
2125
2135
|
var ISO_DATE_RE = /^(\d{4})-(\d{2})(?:-(\d{2}))?$/;
|
|
2136
|
+
var ISO_DATETIME_RE = /^(\d{4})-(\d{2})-(\d{2})[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:?\d{2})?$/;
|
|
2137
|
+
function isLocalCalendarDate(year, month, day) {
|
|
2138
|
+
const d = new Date(year, month - 1, day);
|
|
2139
|
+
return d.getFullYear() === year && d.getMonth() === month - 1 && d.getDate() === day;
|
|
2140
|
+
}
|
|
2126
2141
|
function toDate(value) {
|
|
2127
2142
|
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
|
|
2128
2143
|
if (typeof value === "number") {
|
|
@@ -2130,8 +2145,20 @@ function toDate(value) {
|
|
|
2130
2145
|
return Number.isNaN(d.getTime()) ? null : d;
|
|
2131
2146
|
}
|
|
2132
2147
|
if (typeof value === "string") {
|
|
2148
|
+
const y = ISO_YEAR_RE.exec(value);
|
|
2149
|
+
if (y) {
|
|
2150
|
+
const d = new Date(Date.parse(value));
|
|
2151
|
+
return d.getUTCFullYear() === +y[1] && d.getUTCMonth() === 0 && d.getUTCDate() === 1 ? d : null;
|
|
2152
|
+
}
|
|
2133
2153
|
const m = ISO_DATE_RE.exec(value);
|
|
2134
|
-
if (m)
|
|
2154
|
+
if (m) {
|
|
2155
|
+
const year = +m[1];
|
|
2156
|
+
const month = +m[2];
|
|
2157
|
+
const day = m[3] ? +m[3] : 1;
|
|
2158
|
+
return isLocalCalendarDate(year, month, day) ? new Date(year, month - 1, day) : null;
|
|
2159
|
+
}
|
|
2160
|
+
const dt = ISO_DATETIME_RE.exec(value);
|
|
2161
|
+
if (dt && !isLocalCalendarDate(+dt[1], +dt[2], +dt[3])) return null;
|
|
2135
2162
|
const ms = Date.parse(value);
|
|
2136
2163
|
return Number.isNaN(ms) ? null : new Date(ms);
|
|
2137
2164
|
}
|
|
@@ -2527,7 +2554,7 @@ function coerceFinite(value) {
|
|
|
2527
2554
|
if (value === null || value === void 0) {
|
|
2528
2555
|
return null;
|
|
2529
2556
|
}
|
|
2530
|
-
const numberValue =
|
|
2557
|
+
const numberValue = toNumber(value);
|
|
2531
2558
|
return Number.isFinite(numberValue) ? numberValue : null;
|
|
2532
2559
|
}
|
|
2533
2560
|
function makePathKey(path) {
|
|
@@ -3561,7 +3588,7 @@ function computeBins(values, opts = {}) {
|
|
|
3561
3588
|
[min, max] = opts.extent;
|
|
3562
3589
|
} else {
|
|
3563
3590
|
for (const v of values) {
|
|
3564
|
-
if (Number.
|
|
3591
|
+
if (!Number.isFinite(v)) continue;
|
|
3565
3592
|
if (v < min) min = v;
|
|
3566
3593
|
if (v > max) max = v;
|
|
3567
3594
|
}
|
|
@@ -3585,7 +3612,7 @@ function applyBin(transform2, data) {
|
|
|
3585
3612
|
const finite4 = [];
|
|
3586
3613
|
for (const row of data) {
|
|
3587
3614
|
const n = toNumber(read(row));
|
|
3588
|
-
if (
|
|
3615
|
+
if (Number.isFinite(n)) finite4.push(n);
|
|
3589
3616
|
}
|
|
3590
3617
|
const layout2 = computeBins(finite4, {
|
|
3591
3618
|
maxbins: transform2.maxbins,
|
|
@@ -3599,7 +3626,7 @@ function applyBin(transform2, data) {
|
|
|
3599
3626
|
const out = { ...row };
|
|
3600
3627
|
const v = toNumber(read(row));
|
|
3601
3628
|
const outside = transform2.extent != null && (v < transform2.extent[0] || v > transform2.extent[1]);
|
|
3602
|
-
if (layout2 == null || Number.
|
|
3629
|
+
if (layout2 == null || !Number.isFinite(v) || outside) {
|
|
3603
3630
|
out[startField] = null;
|
|
3604
3631
|
if (endField) out[endField] = null;
|
|
3605
3632
|
return out;
|
|
@@ -4646,6 +4673,26 @@ function validateSpec(spec) {
|
|
|
4646
4673
|
}
|
|
4647
4674
|
}
|
|
4648
4675
|
}
|
|
4676
|
+
if (isObject3(spec.encoding)) {
|
|
4677
|
+
for (const ch of ["x", "y"]) {
|
|
4678
|
+
const channel = spec.encoding[ch];
|
|
4679
|
+
if (!isObject3(channel) || !isObject3(channel.scale)) continue;
|
|
4680
|
+
const scale = channel.scale;
|
|
4681
|
+
if (scale.type !== "log") continue;
|
|
4682
|
+
const domain = scale.domain;
|
|
4683
|
+
if (Array.isArray(domain) && domain.some((v) => typeof v === "number" && v <= 0)) {
|
|
4684
|
+
warn(
|
|
4685
|
+
`encoding.${ch}.scale.domain`,
|
|
4686
|
+
'A "log" scale needs a strictly positive domain; values <= 0 have no logarithm. The domain will be clamped to the positive data range.',
|
|
4687
|
+
{
|
|
4688
|
+
rule: "log-scale-nonpositive-domain",
|
|
4689
|
+
severity: "warning",
|
|
4690
|
+
fix: [{ op: "remove", path: toPointer(`encoding.${ch}.scale.domain`) }]
|
|
4691
|
+
}
|
|
4692
|
+
);
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4649
4696
|
if (type === "kpi") {
|
|
4650
4697
|
const value = spec.value;
|
|
4651
4698
|
if (value === void 0) {
|
|
@@ -6277,6 +6324,21 @@ function stackedYExtent(rows, xField, yField) {
|
|
|
6277
6324
|
for (const v of neg.values()) if (v < min) min = v;
|
|
6278
6325
|
return [min, max];
|
|
6279
6326
|
}
|
|
6327
|
+
function positiveLogDomain(requested, data, field, base) {
|
|
6328
|
+
let lo = requested[0];
|
|
6329
|
+
let hi = requested[1];
|
|
6330
|
+
if (!(lo > 0) || !(hi > 0)) {
|
|
6331
|
+
const read = accessor(field);
|
|
6332
|
+
const positives = data.map((d) => toNumber(read(d))).filter((v) => Number.isFinite(v) && v > 0);
|
|
6333
|
+
const pmin = positives.length ? Math.min(...positives) : 1;
|
|
6334
|
+
const pmax = positives.length ? Math.max(...positives) : base;
|
|
6335
|
+
if (!(lo > 0)) lo = pmin;
|
|
6336
|
+
if (!(hi > 0)) hi = pmax;
|
|
6337
|
+
}
|
|
6338
|
+
if (hi < lo) [lo, hi] = [hi, lo];
|
|
6339
|
+
if (lo === hi) hi = lo * base;
|
|
6340
|
+
return [lo, hi];
|
|
6341
|
+
}
|
|
6280
6342
|
function resolveSeriesField(spec) {
|
|
6281
6343
|
const enc = spec.encoding;
|
|
6282
6344
|
if (enc.series?.field) return enc.series.field;
|
|
@@ -6338,7 +6400,8 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6338
6400
|
let categories;
|
|
6339
6401
|
let xDomainNum = null;
|
|
6340
6402
|
if (xKind === "band" || xKind === "point") {
|
|
6341
|
-
|
|
6403
|
+
const cfgDomain = enc.x.scale?.domain;
|
|
6404
|
+
categories = opts.shared?.categories ?? (Array.isArray(cfgDomain) && typeof cfgDomain[0] === "string" ? cfgDomain : uniqueStrings(data, xField));
|
|
6342
6405
|
} else if (xKind === "time") {
|
|
6343
6406
|
if (opts.shared?.xDomain) {
|
|
6344
6407
|
xDomainNum = opts.shared.xDomain;
|
|
@@ -6351,12 +6414,23 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6351
6414
|
} else {
|
|
6352
6415
|
xDomainNum = extent(data, xField) ?? [0, 1];
|
|
6353
6416
|
const xScaleCfg = enc.x.scale;
|
|
6354
|
-
if (xScaleCfg?.
|
|
6417
|
+
if (xScaleCfg?.type === "log") {
|
|
6418
|
+
xDomainNum = positiveLogDomain(
|
|
6419
|
+
xScaleCfg.domain && typeof xScaleCfg.domain[0] === "number" ? [xScaleCfg.domain[0], xScaleCfg.domain[1]] : xDomainNum,
|
|
6420
|
+
data,
|
|
6421
|
+
xField,
|
|
6422
|
+
xScaleCfg.base ?? 10
|
|
6423
|
+
);
|
|
6424
|
+
} else if (xScaleCfg?.domain && typeof xScaleCfg.domain[0] === "number") {
|
|
6355
6425
|
xDomainNum = [xScaleCfg.domain[0], xScaleCfg.domain[1]];
|
|
6356
6426
|
} else if (xScaleCfg?.nice !== false) {
|
|
6357
6427
|
xDomainNum = niceDomain(xDomainNum[0], xDomainNum[1], 10);
|
|
6358
6428
|
}
|
|
6359
6429
|
}
|
|
6430
|
+
const yScaleCfg = enc.y.scale;
|
|
6431
|
+
const yIsLog = yScaleCfg?.type === "log";
|
|
6432
|
+
const yBase = yScaleCfg?.base ?? 10;
|
|
6433
|
+
const yClamp = yScaleCfg?.clamp === true;
|
|
6360
6434
|
const sharedY = opts.shared?.yDomain;
|
|
6361
6435
|
let yDomain;
|
|
6362
6436
|
if (sharedY) {
|
|
@@ -6367,24 +6441,39 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6367
6441
|
const ext = extent(data, yField) ?? [0, 1];
|
|
6368
6442
|
yDomain = [ext[0], ext[1]];
|
|
6369
6443
|
}
|
|
6370
|
-
|
|
6444
|
+
const forceZero = yIsLog ? false : yScaleCfg?.zero ?? wantsZeroBaseline(spec);
|
|
6445
|
+
if (forceZero && !sharedY) {
|
|
6371
6446
|
yDomain = [Math.min(0, yDomain[0]), Math.max(0, yDomain[1])];
|
|
6372
6447
|
}
|
|
6373
|
-
const yScaleCfg = enc.y.scale;
|
|
6374
6448
|
const xAxisCfg = axisCfg(spec, "x");
|
|
6375
6449
|
const yAxisCfg = axisCfg(spec, "y");
|
|
6376
6450
|
const yTickCount = yAxisCfg.ticks ?? 6;
|
|
6451
|
+
const yExplicitTicks = yAxisCfg.tickValues && yAxisCfg.tickValues.length ? yAxisCfg.tickValues.slice() : null;
|
|
6377
6452
|
const yExplicit = yScaleCfg?.domain && typeof yScaleCfg.domain[0] === "number" || !!sharedY;
|
|
6378
6453
|
let yTickValues;
|
|
6379
|
-
if (
|
|
6454
|
+
if (yIsLog) {
|
|
6455
|
+
if (yScaleCfg?.domain && typeof yScaleCfg.domain[0] === "number") {
|
|
6456
|
+
yDomain = positiveLogDomain([yScaleCfg.domain[0], yScaleCfg.domain[1]], data, yField, yBase);
|
|
6457
|
+
} else if (!sharedY) {
|
|
6458
|
+
yDomain = positiveLogDomain(yDomain, data, yField, yBase);
|
|
6459
|
+
}
|
|
6460
|
+
const lo = Math.min(yDomain[0], yDomain[1]);
|
|
6461
|
+
const hi = Math.max(yDomain[0], yDomain[1]);
|
|
6462
|
+
yTickValues = (yExplicitTicks ?? logScale({ domain: yDomain, range: [0, 1], base: yBase }).ticks()).filter(
|
|
6463
|
+
(v) => v > 0 && v >= lo - 1e-9 && v <= hi + 1e-9
|
|
6464
|
+
);
|
|
6465
|
+
} else if (yExplicit) {
|
|
6380
6466
|
if (yScaleCfg?.domain && typeof yScaleCfg.domain[0] === "number") {
|
|
6381
6467
|
yDomain = [yScaleCfg.domain[0], yScaleCfg.domain[1]];
|
|
6382
6468
|
}
|
|
6383
6469
|
const lo = Math.min(yDomain[0], yDomain[1]);
|
|
6384
6470
|
const hi = Math.max(yDomain[0], yDomain[1]);
|
|
6385
|
-
yTickValues = numericTickValues(yDomain[0], yDomain[1], yTickCount).filter(
|
|
6471
|
+
yTickValues = (yExplicitTicks ?? numericTickValues(yDomain[0], yDomain[1], yTickCount)).filter(
|
|
6386
6472
|
(v) => v >= lo - 1e-9 && v <= hi + 1e-9
|
|
6387
6473
|
);
|
|
6474
|
+
} else if (yExplicitTicks) {
|
|
6475
|
+
yTickValues = yExplicitTicks;
|
|
6476
|
+
yDomain = [Math.min(yDomain[0], ...yExplicitTicks), Math.max(yDomain[1], ...yExplicitTicks)];
|
|
6388
6477
|
} else if (yScaleCfg?.nice !== false) {
|
|
6389
6478
|
yTickValues = numericTickValues(yDomain[0], yDomain[1], yTickCount);
|
|
6390
6479
|
if (yTickValues.length >= 2) {
|
|
@@ -6412,7 +6501,19 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6412
6501
|
xStepMs = xt.length > 1 ? xt[1] - xt[0] : 0;
|
|
6413
6502
|
xLabels = xt.map((v) => enc.x.format ? formatValue(v, enc.x.format) : smartDate(v, xStepMs));
|
|
6414
6503
|
} else {
|
|
6415
|
-
const
|
|
6504
|
+
const xLo = Math.min(xDomainNum[0], xDomainNum[1]);
|
|
6505
|
+
const xHi = Math.max(xDomainNum[0], xDomainNum[1]);
|
|
6506
|
+
const xExplicitTicks = xAxisCfg.tickValues && xAxisCfg.tickValues.length ? xAxisCfg.tickValues.slice() : null;
|
|
6507
|
+
let xt;
|
|
6508
|
+
if (xExplicitTicks) {
|
|
6509
|
+
xt = xExplicitTicks.filter((v) => v >= xLo - 1e-9 && v <= xHi + 1e-9);
|
|
6510
|
+
} else if (enc.x.scale?.type === "log") {
|
|
6511
|
+
xt = logScale({ domain: xDomainNum, range: [0, 1], base: enc.x.scale.base ?? 10 }).ticks().filter((v) => v > 0 && v >= xLo - 1e-9 && v <= xHi + 1e-9);
|
|
6512
|
+
} else {
|
|
6513
|
+
xt = numericTickValues(xDomainNum[0], xDomainNum[1], xAxisCfg.ticks ?? 8).filter(
|
|
6514
|
+
(v) => v >= xLo - 1e-9 && v <= xHi + 1e-9
|
|
6515
|
+
);
|
|
6516
|
+
}
|
|
6416
6517
|
xTickValues = xt;
|
|
6417
6518
|
xLabels = xt.map((v) => formatNumericTick(v, enc.x.format ?? xAxisCfg.format));
|
|
6418
6519
|
}
|
|
@@ -6439,9 +6540,10 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6439
6540
|
yAxis: { show: yShow, labels: yLabels, title: resolveAxisTitle(enc.y, yAxisCfg) }
|
|
6440
6541
|
});
|
|
6441
6542
|
const plot = frame.plot;
|
|
6442
|
-
const
|
|
6543
|
+
const yRange = [plot.y + plot.height, plot.y];
|
|
6544
|
+
const yScale = yIsLog ? logScale({ domain: yDomain, range: yRange, base: yBase, clamp: yClamp }) : linearScale({ domain: yDomain, range: yRange, clamp: yClamp });
|
|
6443
6545
|
const yPixel = (value) => yScale.map(toNumber(value));
|
|
6444
|
-
const baseline = clampPx(yScale.map(0), plot.y, plot.y + plot.height);
|
|
6546
|
+
const baseline = yIsLog ? plot.y + plot.height : clampPx(yScale.map(0), plot.y, plot.y + plot.height);
|
|
6445
6547
|
const xModel = buildXModel(xKind, {
|
|
6446
6548
|
field: xField,
|
|
6447
6549
|
type: xType,
|
|
@@ -6517,7 +6619,8 @@ function buildXModel(kind, args) {
|
|
|
6517
6619
|
};
|
|
6518
6620
|
}
|
|
6519
6621
|
const domain = args.domainNum ?? [0, 1];
|
|
6520
|
-
const
|
|
6622
|
+
const clamp11 = args.scaleCfg?.clamp === true;
|
|
6623
|
+
const continuous = kind === "time" ? timeScale({ domain, range: args.range }) : args.scaleCfg?.type === "log" ? logScale({ domain, range: args.range, base: args.scaleCfg.base ?? 10, clamp: clamp11 }) : linearScale({ domain, range: args.range, clamp: clamp11 });
|
|
6521
6624
|
return {
|
|
6522
6625
|
kind,
|
|
6523
6626
|
field: args.field,
|
|
@@ -7231,6 +7334,12 @@ function boxProbe(model, geom) {
|
|
|
7231
7334
|
if (model.seriesField) probe[model.seriesField] = geom.seriesValue;
|
|
7232
7335
|
return probe;
|
|
7233
7336
|
}
|
|
7337
|
+
function hitsBoxGeom(model, geom, showOutliers, px, py) {
|
|
7338
|
+
const inWhiskerExtent = px >= geom.left - 4 && px <= geom.right + 4 && py >= geom.top - 6 && py <= geom.bottom + 6;
|
|
7339
|
+
if (inWhiskerExtent) return true;
|
|
7340
|
+
if (!showOutliers || geom.stats.outliers.length === 0) return false;
|
|
7341
|
+
return geom.stats.outliers.some((o) => Math.abs(px - geom.cx) <= 6 && Math.abs(py - model.y.pixel(o)) <= 6);
|
|
7342
|
+
}
|
|
7234
7343
|
function drawBox(surface, model) {
|
|
7235
7344
|
const layout2 = computeBoxes(model);
|
|
7236
7345
|
if (!layout2) return;
|
|
@@ -7262,9 +7371,7 @@ function buildBoxInteraction(model) {
|
|
|
7262
7371
|
return {
|
|
7263
7372
|
region: model.plot,
|
|
7264
7373
|
hitTest: (px, py) => {
|
|
7265
|
-
const hit = geoms.find(
|
|
7266
|
-
(g) => px >= g.left - 4 && px <= g.right + 4 && py >= g.top - 6 && py <= g.bottom + 6
|
|
7267
|
-
);
|
|
7374
|
+
const hit = geoms.find((g) => hitsBoxGeom(model, g, showOutliers, px, py));
|
|
7268
7375
|
if (!hit) return null;
|
|
7269
7376
|
const s = hit.stats;
|
|
7270
7377
|
const rows = [
|
|
@@ -7297,10 +7404,15 @@ function buildBoxInteraction(model) {
|
|
|
7297
7404
|
};
|
|
7298
7405
|
},
|
|
7299
7406
|
pick: (px, py) => {
|
|
7300
|
-
const hit = geoms.find(
|
|
7301
|
-
(g) => px >= g.left - 4 && px <= g.right + 4 && py >= g.top - 6 && py <= g.bottom + 6
|
|
7302
|
-
);
|
|
7407
|
+
const hit = geoms.find((g) => hitsBoxGeom(model, g, showOutliers, px, py));
|
|
7303
7408
|
if (!hit) return null;
|
|
7409
|
+
if (model.series.length > 1 && model.seriesField && hit.seriesValue !== void 0) {
|
|
7410
|
+
return {
|
|
7411
|
+
kind: "point",
|
|
7412
|
+
fields: [model.x.field, model.seriesField],
|
|
7413
|
+
tuples: [[hit.xValue, hit.seriesValue]]
|
|
7414
|
+
};
|
|
7415
|
+
}
|
|
7304
7416
|
return { kind: "point", fields: [model.x.field], tuples: [[hit.xValue]] };
|
|
7305
7417
|
}
|
|
7306
7418
|
};
|
|
@@ -7557,6 +7669,18 @@ function measureMax(labels, font) {
|
|
|
7557
7669
|
for (const label of labels) max = Math.max(max, measureText(label, font).width);
|
|
7558
7670
|
return max;
|
|
7559
7671
|
}
|
|
7672
|
+
function heatmapColorDomain(rows, field) {
|
|
7673
|
+
const read = accessor(field);
|
|
7674
|
+
let min = Infinity;
|
|
7675
|
+
let max = -Infinity;
|
|
7676
|
+
for (const row of rows) {
|
|
7677
|
+
const value = toNumber(read(row));
|
|
7678
|
+
if (!finite3(value)) continue;
|
|
7679
|
+
min = Math.min(min, value);
|
|
7680
|
+
max = Math.max(max, value);
|
|
7681
|
+
}
|
|
7682
|
+
return min === Infinity ? null : [min, max];
|
|
7683
|
+
}
|
|
7560
7684
|
function drawRoundedCell(ctx, x, y, width, height, radius) {
|
|
7561
7685
|
if (!finite3(x) || !finite3(y) || !finite3(width) || !finite3(height) || width <= 0 || height <= 0) return;
|
|
7562
7686
|
ctx.beginPath();
|
|
@@ -7605,7 +7729,7 @@ function drawHeatmap(surface, spec, tokens, size, context) {
|
|
|
7605
7729
|
const { x, y, color } = heatmap.encoding;
|
|
7606
7730
|
const xCats = uniqueStrings(rows, x.field);
|
|
7607
7731
|
const yCats = uniqueStrings(rows, y.field);
|
|
7608
|
-
const colorDomain =
|
|
7732
|
+
const colorDomain = heatmapColorDomain(rows, color.field);
|
|
7609
7733
|
if (xCats.length === 0 || yCats.length === 0 || !colorDomain || content.width <= 0 || content.height <= 0) return;
|
|
7610
7734
|
const labelSize = tokens.font.size.small;
|
|
7611
7735
|
const labelFont = fontString(labelSize, tokens.font.family, tokens.font.weight.normal);
|
|
@@ -7772,16 +7896,33 @@ function clamp3(value, min, max) {
|
|
|
7772
7896
|
function direction(angle) {
|
|
7773
7897
|
return { x: Math.sin(angle), y: -Math.cos(angle) };
|
|
7774
7898
|
}
|
|
7775
|
-
function
|
|
7776
|
-
const
|
|
7777
|
-
const
|
|
7778
|
-
return
|
|
7899
|
+
function reconcilePercents(values) {
|
|
7900
|
+
const cleaned = values.map((value) => Number.isFinite(value) && value > 0 ? value : 0);
|
|
7901
|
+
const total = cleaned.reduce((sum, value) => sum + value, 0);
|
|
7902
|
+
if (total <= 0) return cleaned.map(() => 0);
|
|
7903
|
+
const exact = cleaned.map((value, index) => {
|
|
7904
|
+
const pct = value / total * 100;
|
|
7905
|
+
const floor = Math.floor(pct);
|
|
7906
|
+
return { index, floor, remainder: pct - floor };
|
|
7907
|
+
});
|
|
7908
|
+
let remaining = 100 - exact.reduce((sum, item) => sum + item.floor, 0);
|
|
7909
|
+
const out = exact.map((item) => item.floor);
|
|
7910
|
+
for (const item of [...exact].sort((a, b) => b.remainder - a.remainder || a.index - b.index)) {
|
|
7911
|
+
if (remaining <= 0) break;
|
|
7912
|
+
out[item.index]++;
|
|
7913
|
+
remaining--;
|
|
7914
|
+
}
|
|
7915
|
+
return out;
|
|
7916
|
+
}
|
|
7917
|
+
function formatPercent(percent) {
|
|
7918
|
+
return `${percent}%`;
|
|
7779
7919
|
}
|
|
7780
|
-
function labelText(slice, placement, options) {
|
|
7920
|
+
function labelText(slice, placement, options, percents) {
|
|
7781
7921
|
const content = options.content ?? (placement === "inside" ? "percent" : "category-percent");
|
|
7922
|
+
const percentText = formatPercent(percents.get(slice.index) ?? 0);
|
|
7782
7923
|
switch (content) {
|
|
7783
7924
|
case "percent":
|
|
7784
|
-
return
|
|
7925
|
+
return percentText;
|
|
7785
7926
|
case "value":
|
|
7786
7927
|
return options.formatValue(slice.value);
|
|
7787
7928
|
case "category":
|
|
@@ -7790,7 +7931,7 @@ function labelText(slice, placement, options) {
|
|
|
7790
7931
|
return `${slice.label} \xB7 ${options.formatValue(slice.value)}`;
|
|
7791
7932
|
case "category-percent":
|
|
7792
7933
|
default:
|
|
7793
|
-
return `${slice.label} \xB7 ${
|
|
7934
|
+
return `${slice.label} \xB7 ${percentText}`;
|
|
7794
7935
|
}
|
|
7795
7936
|
}
|
|
7796
7937
|
function relaxColumn(idealYs, lineHeight2, top, bottom) {
|
|
@@ -7820,6 +7961,10 @@ function planPieLabels(options) {
|
|
|
7820
7961
|
const origInnerR = options.innerR;
|
|
7821
7962
|
const insideSlices = [];
|
|
7822
7963
|
const outsideSlices = [];
|
|
7964
|
+
const percents = /* @__PURE__ */ new Map();
|
|
7965
|
+
reconcilePercents(slices.map((slice) => slice.value)).forEach((percent, i) => {
|
|
7966
|
+
percents.set(slices[i].index, percent);
|
|
7967
|
+
});
|
|
7823
7968
|
const insideLabelR = origInnerR > 0 ? (origInnerR + origOuterR) / 2 : origOuterR * 0.62;
|
|
7824
7969
|
const radialBand = origInnerR > 0 ? origOuterR - origInnerR : origOuterR;
|
|
7825
7970
|
for (const slice of slices) {
|
|
@@ -7829,20 +7974,20 @@ function planPieLabels(options) {
|
|
|
7829
7974
|
if (placement === "inside") goesInside = true;
|
|
7830
7975
|
else if (placement === "outside") goesInside = false;
|
|
7831
7976
|
else {
|
|
7832
|
-
const insideText = labelText(slice, "inside", options);
|
|
7977
|
+
const insideText = labelText(slice, "inside", options, percents);
|
|
7833
7978
|
const tangential = (slice.endAngle - slice.startAngle) * insideLabelR;
|
|
7834
7979
|
const fitsTangential = measure(insideText) + INSIDE_PAD <= tangential;
|
|
7835
7980
|
const fitsRadial = lineHeight2 * 0.8 <= radialBand;
|
|
7836
7981
|
goesInside = fitsTangential && fitsRadial;
|
|
7837
7982
|
}
|
|
7838
7983
|
if (goesInside) {
|
|
7839
|
-
insideSlices.push({ slice, mid, text: labelText(slice, "inside", options) });
|
|
7984
|
+
insideSlices.push({ slice, mid, text: labelText(slice, "inside", options, percents) });
|
|
7840
7985
|
} else {
|
|
7841
7986
|
const dir = direction(mid);
|
|
7842
7987
|
outsideSlices.push({
|
|
7843
7988
|
slice,
|
|
7844
7989
|
mid,
|
|
7845
|
-
text: labelText(slice, "outside", options),
|
|
7990
|
+
text: labelText(slice, "outside", options, percents),
|
|
7846
7991
|
side: dir.x >= 0 ? "right" : "left"
|
|
7847
7992
|
});
|
|
7848
7993
|
}
|
|
@@ -8229,17 +8374,29 @@ function buildStages(spec, palette) {
|
|
|
8229
8374
|
order.push(key);
|
|
8230
8375
|
}
|
|
8231
8376
|
}
|
|
8232
|
-
|
|
8377
|
+
const stages = [];
|
|
8378
|
+
for (const key of order) {
|
|
8233
8379
|
const { category, value } = totals.get(key);
|
|
8380
|
+
if (value <= 0) continue;
|
|
8234
8381
|
const rgba2 = colorScale.map(key);
|
|
8235
|
-
|
|
8382
|
+
stages.push({
|
|
8236
8383
|
key,
|
|
8237
8384
|
label: key === "" ? "(blank)" : key,
|
|
8238
8385
|
value,
|
|
8239
8386
|
category,
|
|
8240
8387
|
color: rgbaToCss(rgba2),
|
|
8241
8388
|
rgba: rgba2
|
|
8242
|
-
};
|
|
8389
|
+
});
|
|
8390
|
+
}
|
|
8391
|
+
return stages;
|
|
8392
|
+
}
|
|
8393
|
+
function safeRatio(numerator, denominator) {
|
|
8394
|
+
return Number.isFinite(numerator) && Number.isFinite(denominator) && denominator !== 0 ? numerator / denominator : 0;
|
|
8395
|
+
}
|
|
8396
|
+
function funnelPercents(stages, mode = "first") {
|
|
8397
|
+
return stages.map((stage, i) => {
|
|
8398
|
+
if (mode === "previous") return i === 0 ? 1 : safeRatio(stage.value, stages[i - 1].value);
|
|
8399
|
+
return safeRatio(stage.value, stages[0]?.value ?? 0);
|
|
8243
8400
|
});
|
|
8244
8401
|
}
|
|
8245
8402
|
function drawFunnel(surface, spec, tokens, size, context) {
|
|
@@ -8277,12 +8434,10 @@ function drawFunnel(surface, spec, tokens, size, context) {
|
|
|
8277
8434
|
const n = stages.length;
|
|
8278
8435
|
const gap = Math.min(6, Math.max(2, content.height / n / 12));
|
|
8279
8436
|
const bandH = content.height / n;
|
|
8280
|
-
const widthOf = (v) => v / maxValue * funnelW;
|
|
8437
|
+
const widthOf = (v) => Math.max(0, v) / maxValue * funnelW;
|
|
8281
8438
|
const alphaOf = (s) => emphasis ? emphasis.match({ [stageField]: s.category }) ? 1 : emphasis.dim : 1;
|
|
8282
|
-
const
|
|
8283
|
-
|
|
8284
|
-
return stages[i].value / stages[0].value;
|
|
8285
|
-
};
|
|
8439
|
+
const percents = funnelPercents(stages, mode);
|
|
8440
|
+
const pctOf = (i) => percents[i] ?? 0;
|
|
8286
8441
|
const sketch = resolveSketch(spec);
|
|
8287
8442
|
const pen = sketch ? new RoughPen(ctx, sketch) : null;
|
|
8288
8443
|
const bands = [];
|
|
@@ -8426,8 +8581,8 @@ function valueField(ref) {
|
|
|
8426
8581
|
function formatDelta(delta, specFormat) {
|
|
8427
8582
|
const sign2 = delta >= 0 ? "+" : "-";
|
|
8428
8583
|
const abs = Math.abs(delta);
|
|
8429
|
-
if (abs <= 1) return sign2 + formatNumber2(abs, ".1%");
|
|
8430
8584
|
if (specFormat) return sign2 + formatValue(abs, specFormat);
|
|
8585
|
+
if (abs <= 1) return sign2 + formatNumber2(abs, ".1%");
|
|
8431
8586
|
return sign2 + formatNumber2(abs, ",.0f");
|
|
8432
8587
|
}
|
|
8433
8588
|
function cardRectFor(surface, tokens, size, spec) {
|
|
@@ -8698,11 +8853,13 @@ function buildGraph(spec, palette) {
|
|
|
8698
8853
|
target.targetLinks.push(link);
|
|
8699
8854
|
links.push(link);
|
|
8700
8855
|
}
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8856
|
+
const feedback = removeFeedbackLinks(order);
|
|
8857
|
+
if (feedback.size > 0) {
|
|
8858
|
+
for (let i = links.length - 1; i >= 0; i -= 1) {
|
|
8859
|
+
if (feedback.has(links[i])) links.splice(i, 1);
|
|
8860
|
+
}
|
|
8705
8861
|
}
|
|
8862
|
+
computeNodeValues(order);
|
|
8706
8863
|
assignLayers(order);
|
|
8707
8864
|
const maxLayer = order.reduce((m, n) => Math.max(m, n.layer), 0);
|
|
8708
8865
|
const columns = Array.from({ length: maxLayer + 1 }, () => []);
|
|
@@ -8710,6 +8867,7 @@ function buildGraph(spec, palette) {
|
|
|
8710
8867
|
return { nodes: order, links, columns, maxLayer };
|
|
8711
8868
|
}
|
|
8712
8869
|
function assignLayers(nodes) {
|
|
8870
|
+
for (const n of nodes) n.layer = 0;
|
|
8713
8871
|
const indeg = /* @__PURE__ */ new Map();
|
|
8714
8872
|
for (const n of nodes) indeg.set(n, n.targetLinks.length);
|
|
8715
8873
|
const queue = nodes.filter((n) => (indeg.get(n) ?? 0) === 0);
|
|
@@ -8725,14 +8883,58 @@ function assignLayers(nodes) {
|
|
|
8725
8883
|
if (d === 0) queue.push(t);
|
|
8726
8884
|
}
|
|
8727
8885
|
}
|
|
8886
|
+
for (const n of nodes) {
|
|
8887
|
+
if (!seen.has(n)) {
|
|
8888
|
+
seen.add(n);
|
|
8889
|
+
const incoming = n.targetLinks.reduce((m, l) => Math.max(m, l.source.layer + 1), 0);
|
|
8890
|
+
n.layer = Math.max(n.layer, incoming);
|
|
8891
|
+
}
|
|
8892
|
+
}
|
|
8728
8893
|
const maxLayer = nodes.reduce((m, n) => Math.max(m, n.layer), 0);
|
|
8729
8894
|
for (const n of nodes) {
|
|
8730
8895
|
if (n.sourceLinks.length === 0 && n.targetLinks.length > 0) n.layer = maxLayer;
|
|
8731
8896
|
}
|
|
8732
8897
|
}
|
|
8898
|
+
function removeFeedbackLinks(nodes) {
|
|
8899
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
8900
|
+
const visited = /* @__PURE__ */ new Set();
|
|
8901
|
+
const feedback = /* @__PURE__ */ new Set();
|
|
8902
|
+
const visit = (n) => {
|
|
8903
|
+
visiting.add(n);
|
|
8904
|
+
for (const link of n.sourceLinks) {
|
|
8905
|
+
const target = link.target;
|
|
8906
|
+
if (visiting.has(target)) {
|
|
8907
|
+
feedback.add(link);
|
|
8908
|
+
continue;
|
|
8909
|
+
}
|
|
8910
|
+
if (!visited.has(target)) visit(target);
|
|
8911
|
+
}
|
|
8912
|
+
visiting.delete(n);
|
|
8913
|
+
visited.add(n);
|
|
8914
|
+
};
|
|
8915
|
+
for (const n of nodes) {
|
|
8916
|
+
if (!visited.has(n)) visit(n);
|
|
8917
|
+
}
|
|
8918
|
+
if (feedback.size === 0) return feedback;
|
|
8919
|
+
for (const n of nodes) {
|
|
8920
|
+
n.sourceLinks = n.sourceLinks.filter((l) => !feedback.has(l));
|
|
8921
|
+
n.targetLinks = n.targetLinks.filter((l) => !feedback.has(l));
|
|
8922
|
+
}
|
|
8923
|
+
return feedback;
|
|
8924
|
+
}
|
|
8925
|
+
function computeNodeValues(nodes) {
|
|
8926
|
+
for (const n of nodes) {
|
|
8927
|
+
const out = n.sourceLinks.reduce((s, l) => s + l.value, 0);
|
|
8928
|
+
const inc = n.targetLinks.reduce((s, l) => s + l.value, 0);
|
|
8929
|
+
n.value = Math.max(out, inc);
|
|
8930
|
+
}
|
|
8931
|
+
}
|
|
8733
8932
|
function layout(graph, area2, nodeWidth, pad3) {
|
|
8734
8933
|
const { columns, links, maxLayer } = graph;
|
|
8735
8934
|
const kx = maxLayer > 0 ? (area2.width - nodeWidth) / maxLayer : 0;
|
|
8935
|
+
const maxColumnSize = columns.reduce((m, col) => Math.max(m, col.length), 0);
|
|
8936
|
+
const desiredPad = Math.max(0, pad3);
|
|
8937
|
+
const effectivePad = maxColumnSize > 1 && desiredPad * (maxColumnSize - 1) >= area2.height && area2.height > 0 ? area2.height * 0.25 / (maxColumnSize - 1) : desiredPad;
|
|
8736
8938
|
for (const col of columns) {
|
|
8737
8939
|
for (const n of col) {
|
|
8738
8940
|
n.x0 = area2.x + n.layer * kx;
|
|
@@ -8743,26 +8945,26 @@ function layout(graph, area2, nodeWidth, pad3) {
|
|
|
8743
8945
|
for (const col of columns) {
|
|
8744
8946
|
if (col.length === 0) continue;
|
|
8745
8947
|
const sum = col.reduce((s, n) => s + n.value, 0);
|
|
8746
|
-
const avail = area2.height - (col.length - 1) *
|
|
8747
|
-
if (sum > 0
|
|
8948
|
+
const avail = Math.max(0, area2.height - (col.length - 1) * effectivePad);
|
|
8949
|
+
if (sum > 0) ky = Math.min(ky, avail / sum);
|
|
8748
8950
|
}
|
|
8749
|
-
if (!Number.isFinite(ky) || ky
|
|
8951
|
+
if (!Number.isFinite(ky) || ky < 0) ky = 0;
|
|
8750
8952
|
for (const col of columns) {
|
|
8751
8953
|
let y = area2.y;
|
|
8752
8954
|
for (const n of col) {
|
|
8753
8955
|
n.y0 = y;
|
|
8754
|
-
n.y1 = y + Math.max(
|
|
8755
|
-
y = n.y1 +
|
|
8956
|
+
n.y1 = y + Math.max(0, n.value * ky);
|
|
8957
|
+
y = n.y1 + effectivePad;
|
|
8756
8958
|
}
|
|
8757
8959
|
centerColumn(col, area2);
|
|
8758
8960
|
}
|
|
8759
|
-
for (const l of links) l.width = Math.max(
|
|
8961
|
+
for (const l of links) l.width = Math.max(0, l.value * ky);
|
|
8760
8962
|
for (let i = 0; i < 6; i += 1) {
|
|
8761
8963
|
const alpha = Math.pow(0.97, i);
|
|
8762
8964
|
relax(columns, true, alpha);
|
|
8763
|
-
resolveAll(columns, area2,
|
|
8965
|
+
resolveAll(columns, area2, effectivePad);
|
|
8764
8966
|
relax(columns, false, alpha);
|
|
8765
|
-
resolveAll(columns, area2,
|
|
8967
|
+
resolveAll(columns, area2, effectivePad);
|
|
8766
8968
|
}
|
|
8767
8969
|
computeLinkBreadths(graph);
|
|
8768
8970
|
}
|
|
@@ -9004,6 +9206,7 @@ function featureKey(feature, featureId) {
|
|
|
9004
9206
|
if (featureId === "id" && feature.id != null) return toKey(feature.id);
|
|
9005
9207
|
const v = feature.properties?.[featureId];
|
|
9006
9208
|
if (v != null) return toKey(v);
|
|
9209
|
+
return "";
|
|
9007
9210
|
}
|
|
9008
9211
|
if (feature.id != null) return toKey(feature.id);
|
|
9009
9212
|
const props = feature.properties ?? {};
|
|
@@ -12015,6 +12218,12 @@ function buildGroups(leaves) {
|
|
|
12015
12218
|
}
|
|
12016
12219
|
return order.map((key) => groups.get(key));
|
|
12017
12220
|
}
|
|
12221
|
+
function tileProbe(treemap, tile) {
|
|
12222
|
+
const probe = { [treemap.encoding.category.field]: tile.category };
|
|
12223
|
+
const groupField = treemap.encoding.group?.field;
|
|
12224
|
+
if (groupField) probe[groupField] = tile.group;
|
|
12225
|
+
return probe;
|
|
12226
|
+
}
|
|
12018
12227
|
function drawRect2(ctx, pen, rect, fill, stroke) {
|
|
12019
12228
|
if (rect.width <= 0 || rect.height <= 0) return;
|
|
12020
12229
|
if (pen) {
|
|
@@ -12059,6 +12268,7 @@ function addTileLabels(surface, tokens, tile, valueFormat) {
|
|
|
12059
12268
|
function drawTreemap(surface, spec, tokens, size, context) {
|
|
12060
12269
|
const treemap = spec;
|
|
12061
12270
|
const ctx = surface.marks.ctx;
|
|
12271
|
+
const emphasis = context?.emphasis ?? null;
|
|
12062
12272
|
const content = drawTitleBlock(surface, tokens, size, treemap.title);
|
|
12063
12273
|
const leaves = buildLeaves(treemap);
|
|
12064
12274
|
colorLeaves(leaves, treemap, tokens);
|
|
@@ -12080,6 +12290,12 @@ function drawTreemap(surface, spec, tokens, size, context) {
|
|
|
12080
12290
|
const tiles = [];
|
|
12081
12291
|
const stroke = tokens.color.background;
|
|
12082
12292
|
const groupRects = [];
|
|
12293
|
+
const drawTile = (tile) => {
|
|
12294
|
+
const alpha = emphasis ? emphasis.match(tileProbe(treemap, tile)) ? 1 : emphasis.dim : 1;
|
|
12295
|
+
if (alpha !== 1) ctx.globalAlpha = alpha;
|
|
12296
|
+
drawRect2(ctx, pen, tile.rect, tile.fillCss, stroke);
|
|
12297
|
+
if (alpha !== 1) ctx.globalAlpha = 1;
|
|
12298
|
+
};
|
|
12083
12299
|
ctx.save();
|
|
12084
12300
|
if (treemap.encoding.group) {
|
|
12085
12301
|
const groups = buildGroups(leaves);
|
|
@@ -12094,14 +12310,14 @@ function drawTreemap(surface, spec, tokens, size, context) {
|
|
|
12094
12310
|
for (const leafNode of layoutTreemap(groupNode.item.leaves, inner)) {
|
|
12095
12311
|
const tile = { ...leafNode.item, rect: leafNode.rect };
|
|
12096
12312
|
tiles.push(tile);
|
|
12097
|
-
|
|
12313
|
+
drawTile(tile);
|
|
12098
12314
|
}
|
|
12099
12315
|
}
|
|
12100
12316
|
} else {
|
|
12101
12317
|
for (const leafNode of layoutTreemap(leaves, content)) {
|
|
12102
12318
|
const tile = { ...leafNode.item, rect: leafNode.rect };
|
|
12103
12319
|
tiles.push(tile);
|
|
12104
|
-
|
|
12320
|
+
drawTile(tile);
|
|
12105
12321
|
}
|
|
12106
12322
|
}
|
|
12107
12323
|
ctx.restore();
|