graphein 0.4.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/LICENSE +21 -21
- package/README.md +60 -60
- package/dist/index.js +282 -63
- package/dist/index.js.map +1 -1
- package/package.json +61 -60
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) {
|
|
@@ -2635,6 +2662,9 @@ function analyzeCategory(rows, categoryField, measureField) {
|
|
|
2635
2662
|
});
|
|
2636
2663
|
return { count: order.length, total, top, bottom, topShare: total > 0 ? top.value / total : 0 };
|
|
2637
2664
|
}
|
|
2665
|
+
function xLabel(x) {
|
|
2666
|
+
return x instanceof Date ? prettyDate(x) : toKey(x);
|
|
2667
|
+
}
|
|
2638
2668
|
function analyzeSeriesFamily(rows, xField, yField, seriesField) {
|
|
2639
2669
|
const readX = accessor(xField);
|
|
2640
2670
|
const readY = accessor(yField);
|
|
@@ -2653,7 +2683,7 @@ function analyzeSeriesFamily(rows, xField, yField, seriesField) {
|
|
|
2653
2683
|
}
|
|
2654
2684
|
const x = readX(d);
|
|
2655
2685
|
g.values.push(y);
|
|
2656
|
-
g.labels.push(
|
|
2686
|
+
g.labels.push(xLabel(x));
|
|
2657
2687
|
g.raws.push(x);
|
|
2658
2688
|
}
|
|
2659
2689
|
const out = [];
|
|
@@ -3558,7 +3588,7 @@ function computeBins(values, opts = {}) {
|
|
|
3558
3588
|
[min, max] = opts.extent;
|
|
3559
3589
|
} else {
|
|
3560
3590
|
for (const v of values) {
|
|
3561
|
-
if (Number.
|
|
3591
|
+
if (!Number.isFinite(v)) continue;
|
|
3562
3592
|
if (v < min) min = v;
|
|
3563
3593
|
if (v > max) max = v;
|
|
3564
3594
|
}
|
|
@@ -3582,7 +3612,7 @@ function applyBin(transform2, data) {
|
|
|
3582
3612
|
const finite4 = [];
|
|
3583
3613
|
for (const row of data) {
|
|
3584
3614
|
const n = toNumber(read(row));
|
|
3585
|
-
if (
|
|
3615
|
+
if (Number.isFinite(n)) finite4.push(n);
|
|
3586
3616
|
}
|
|
3587
3617
|
const layout2 = computeBins(finite4, {
|
|
3588
3618
|
maxbins: transform2.maxbins,
|
|
@@ -3596,7 +3626,7 @@ function applyBin(transform2, data) {
|
|
|
3596
3626
|
const out = { ...row };
|
|
3597
3627
|
const v = toNumber(read(row));
|
|
3598
3628
|
const outside = transform2.extent != null && (v < transform2.extent[0] || v > transform2.extent[1]);
|
|
3599
|
-
if (layout2 == null || Number.
|
|
3629
|
+
if (layout2 == null || !Number.isFinite(v) || outside) {
|
|
3600
3630
|
out[startField] = null;
|
|
3601
3631
|
if (endField) out[endField] = null;
|
|
3602
3632
|
return out;
|
|
@@ -4643,6 +4673,26 @@ function validateSpec(spec) {
|
|
|
4643
4673
|
}
|
|
4644
4674
|
}
|
|
4645
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
|
+
}
|
|
4646
4696
|
if (type === "kpi") {
|
|
4647
4697
|
const value = spec.value;
|
|
4648
4698
|
if (value === void 0) {
|
|
@@ -6274,6 +6324,21 @@ function stackedYExtent(rows, xField, yField) {
|
|
|
6274
6324
|
for (const v of neg.values()) if (v < min) min = v;
|
|
6275
6325
|
return [min, max];
|
|
6276
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
|
+
}
|
|
6277
6342
|
function resolveSeriesField(spec) {
|
|
6278
6343
|
const enc = spec.encoding;
|
|
6279
6344
|
if (enc.series?.field) return enc.series.field;
|
|
@@ -6335,7 +6400,8 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6335
6400
|
let categories;
|
|
6336
6401
|
let xDomainNum = null;
|
|
6337
6402
|
if (xKind === "band" || xKind === "point") {
|
|
6338
|
-
|
|
6403
|
+
const cfgDomain = enc.x.scale?.domain;
|
|
6404
|
+
categories = opts.shared?.categories ?? (Array.isArray(cfgDomain) && typeof cfgDomain[0] === "string" ? cfgDomain : uniqueStrings(data, xField));
|
|
6339
6405
|
} else if (xKind === "time") {
|
|
6340
6406
|
if (opts.shared?.xDomain) {
|
|
6341
6407
|
xDomainNum = opts.shared.xDomain;
|
|
@@ -6348,12 +6414,23 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6348
6414
|
} else {
|
|
6349
6415
|
xDomainNum = extent(data, xField) ?? [0, 1];
|
|
6350
6416
|
const xScaleCfg = enc.x.scale;
|
|
6351
|
-
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") {
|
|
6352
6425
|
xDomainNum = [xScaleCfg.domain[0], xScaleCfg.domain[1]];
|
|
6353
6426
|
} else if (xScaleCfg?.nice !== false) {
|
|
6354
6427
|
xDomainNum = niceDomain(xDomainNum[0], xDomainNum[1], 10);
|
|
6355
6428
|
}
|
|
6356
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;
|
|
6357
6434
|
const sharedY = opts.shared?.yDomain;
|
|
6358
6435
|
let yDomain;
|
|
6359
6436
|
if (sharedY) {
|
|
@@ -6364,24 +6441,39 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6364
6441
|
const ext = extent(data, yField) ?? [0, 1];
|
|
6365
6442
|
yDomain = [ext[0], ext[1]];
|
|
6366
6443
|
}
|
|
6367
|
-
|
|
6444
|
+
const forceZero = yIsLog ? false : yScaleCfg?.zero ?? wantsZeroBaseline(spec);
|
|
6445
|
+
if (forceZero && !sharedY) {
|
|
6368
6446
|
yDomain = [Math.min(0, yDomain[0]), Math.max(0, yDomain[1])];
|
|
6369
6447
|
}
|
|
6370
|
-
const yScaleCfg = enc.y.scale;
|
|
6371
6448
|
const xAxisCfg = axisCfg(spec, "x");
|
|
6372
6449
|
const yAxisCfg = axisCfg(spec, "y");
|
|
6373
6450
|
const yTickCount = yAxisCfg.ticks ?? 6;
|
|
6451
|
+
const yExplicitTicks = yAxisCfg.tickValues && yAxisCfg.tickValues.length ? yAxisCfg.tickValues.slice() : null;
|
|
6374
6452
|
const yExplicit = yScaleCfg?.domain && typeof yScaleCfg.domain[0] === "number" || !!sharedY;
|
|
6375
6453
|
let yTickValues;
|
|
6376
|
-
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) {
|
|
6377
6466
|
if (yScaleCfg?.domain && typeof yScaleCfg.domain[0] === "number") {
|
|
6378
6467
|
yDomain = [yScaleCfg.domain[0], yScaleCfg.domain[1]];
|
|
6379
6468
|
}
|
|
6380
6469
|
const lo = Math.min(yDomain[0], yDomain[1]);
|
|
6381
6470
|
const hi = Math.max(yDomain[0], yDomain[1]);
|
|
6382
|
-
yTickValues = numericTickValues(yDomain[0], yDomain[1], yTickCount).filter(
|
|
6471
|
+
yTickValues = (yExplicitTicks ?? numericTickValues(yDomain[0], yDomain[1], yTickCount)).filter(
|
|
6383
6472
|
(v) => v >= lo - 1e-9 && v <= hi + 1e-9
|
|
6384
6473
|
);
|
|
6474
|
+
} else if (yExplicitTicks) {
|
|
6475
|
+
yTickValues = yExplicitTicks;
|
|
6476
|
+
yDomain = [Math.min(yDomain[0], ...yExplicitTicks), Math.max(yDomain[1], ...yExplicitTicks)];
|
|
6385
6477
|
} else if (yScaleCfg?.nice !== false) {
|
|
6386
6478
|
yTickValues = numericTickValues(yDomain[0], yDomain[1], yTickCount);
|
|
6387
6479
|
if (yTickValues.length >= 2) {
|
|
@@ -6409,7 +6501,19 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6409
6501
|
xStepMs = xt.length > 1 ? xt[1] - xt[0] : 0;
|
|
6410
6502
|
xLabels = xt.map((v) => enc.x.format ? formatValue(v, enc.x.format) : smartDate(v, xStepMs));
|
|
6411
6503
|
} else {
|
|
6412
|
-
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
|
+
}
|
|
6413
6517
|
xTickValues = xt;
|
|
6414
6518
|
xLabels = xt.map((v) => formatNumericTick(v, enc.x.format ?? xAxisCfg.format));
|
|
6415
6519
|
}
|
|
@@ -6436,9 +6540,10 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6436
6540
|
yAxis: { show: yShow, labels: yLabels, title: resolveAxisTitle(enc.y, yAxisCfg) }
|
|
6437
6541
|
});
|
|
6438
6542
|
const plot = frame.plot;
|
|
6439
|
-
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 });
|
|
6440
6545
|
const yPixel = (value) => yScale.map(toNumber(value));
|
|
6441
|
-
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);
|
|
6442
6547
|
const xModel = buildXModel(xKind, {
|
|
6443
6548
|
field: xField,
|
|
6444
6549
|
type: xType,
|
|
@@ -6514,7 +6619,8 @@ function buildXModel(kind, args) {
|
|
|
6514
6619
|
};
|
|
6515
6620
|
}
|
|
6516
6621
|
const domain = args.domainNum ?? [0, 1];
|
|
6517
|
-
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 });
|
|
6518
6624
|
return {
|
|
6519
6625
|
kind,
|
|
6520
6626
|
field: args.field,
|
|
@@ -7228,6 +7334,12 @@ function boxProbe(model, geom) {
|
|
|
7228
7334
|
if (model.seriesField) probe[model.seriesField] = geom.seriesValue;
|
|
7229
7335
|
return probe;
|
|
7230
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
|
+
}
|
|
7231
7343
|
function drawBox(surface, model) {
|
|
7232
7344
|
const layout2 = computeBoxes(model);
|
|
7233
7345
|
if (!layout2) return;
|
|
@@ -7259,9 +7371,7 @@ function buildBoxInteraction(model) {
|
|
|
7259
7371
|
return {
|
|
7260
7372
|
region: model.plot,
|
|
7261
7373
|
hitTest: (px, py) => {
|
|
7262
|
-
const hit = geoms.find(
|
|
7263
|
-
(g) => px >= g.left - 4 && px <= g.right + 4 && py >= g.top - 6 && py <= g.bottom + 6
|
|
7264
|
-
);
|
|
7374
|
+
const hit = geoms.find((g) => hitsBoxGeom(model, g, showOutliers, px, py));
|
|
7265
7375
|
if (!hit) return null;
|
|
7266
7376
|
const s = hit.stats;
|
|
7267
7377
|
const rows = [
|
|
@@ -7294,10 +7404,15 @@ function buildBoxInteraction(model) {
|
|
|
7294
7404
|
};
|
|
7295
7405
|
},
|
|
7296
7406
|
pick: (px, py) => {
|
|
7297
|
-
const hit = geoms.find(
|
|
7298
|
-
(g) => px >= g.left - 4 && px <= g.right + 4 && py >= g.top - 6 && py <= g.bottom + 6
|
|
7299
|
-
);
|
|
7407
|
+
const hit = geoms.find((g) => hitsBoxGeom(model, g, showOutliers, px, py));
|
|
7300
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
|
+
}
|
|
7301
7416
|
return { kind: "point", fields: [model.x.field], tuples: [[hit.xValue]] };
|
|
7302
7417
|
}
|
|
7303
7418
|
};
|
|
@@ -7554,6 +7669,18 @@ function measureMax(labels, font) {
|
|
|
7554
7669
|
for (const label of labels) max = Math.max(max, measureText(label, font).width);
|
|
7555
7670
|
return max;
|
|
7556
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
|
+
}
|
|
7557
7684
|
function drawRoundedCell(ctx, x, y, width, height, radius) {
|
|
7558
7685
|
if (!finite3(x) || !finite3(y) || !finite3(width) || !finite3(height) || width <= 0 || height <= 0) return;
|
|
7559
7686
|
ctx.beginPath();
|
|
@@ -7602,7 +7729,7 @@ function drawHeatmap(surface, spec, tokens, size, context) {
|
|
|
7602
7729
|
const { x, y, color } = heatmap.encoding;
|
|
7603
7730
|
const xCats = uniqueStrings(rows, x.field);
|
|
7604
7731
|
const yCats = uniqueStrings(rows, y.field);
|
|
7605
|
-
const colorDomain =
|
|
7732
|
+
const colorDomain = heatmapColorDomain(rows, color.field);
|
|
7606
7733
|
if (xCats.length === 0 || yCats.length === 0 || !colorDomain || content.width <= 0 || content.height <= 0) return;
|
|
7607
7734
|
const labelSize = tokens.font.size.small;
|
|
7608
7735
|
const labelFont = fontString(labelSize, tokens.font.family, tokens.font.weight.normal);
|
|
@@ -7769,16 +7896,33 @@ function clamp3(value, min, max) {
|
|
|
7769
7896
|
function direction(angle) {
|
|
7770
7897
|
return { x: Math.sin(angle), y: -Math.cos(angle) };
|
|
7771
7898
|
}
|
|
7772
|
-
function
|
|
7773
|
-
const
|
|
7774
|
-
const
|
|
7775
|
-
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}%`;
|
|
7776
7919
|
}
|
|
7777
|
-
function labelText(slice, placement, options) {
|
|
7920
|
+
function labelText(slice, placement, options, percents) {
|
|
7778
7921
|
const content = options.content ?? (placement === "inside" ? "percent" : "category-percent");
|
|
7922
|
+
const percentText = formatPercent(percents.get(slice.index) ?? 0);
|
|
7779
7923
|
switch (content) {
|
|
7780
7924
|
case "percent":
|
|
7781
|
-
return
|
|
7925
|
+
return percentText;
|
|
7782
7926
|
case "value":
|
|
7783
7927
|
return options.formatValue(slice.value);
|
|
7784
7928
|
case "category":
|
|
@@ -7787,7 +7931,7 @@ function labelText(slice, placement, options) {
|
|
|
7787
7931
|
return `${slice.label} \xB7 ${options.formatValue(slice.value)}`;
|
|
7788
7932
|
case "category-percent":
|
|
7789
7933
|
default:
|
|
7790
|
-
return `${slice.label} \xB7 ${
|
|
7934
|
+
return `${slice.label} \xB7 ${percentText}`;
|
|
7791
7935
|
}
|
|
7792
7936
|
}
|
|
7793
7937
|
function relaxColumn(idealYs, lineHeight2, top, bottom) {
|
|
@@ -7817,6 +7961,10 @@ function planPieLabels(options) {
|
|
|
7817
7961
|
const origInnerR = options.innerR;
|
|
7818
7962
|
const insideSlices = [];
|
|
7819
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
|
+
});
|
|
7820
7968
|
const insideLabelR = origInnerR > 0 ? (origInnerR + origOuterR) / 2 : origOuterR * 0.62;
|
|
7821
7969
|
const radialBand = origInnerR > 0 ? origOuterR - origInnerR : origOuterR;
|
|
7822
7970
|
for (const slice of slices) {
|
|
@@ -7826,20 +7974,20 @@ function planPieLabels(options) {
|
|
|
7826
7974
|
if (placement === "inside") goesInside = true;
|
|
7827
7975
|
else if (placement === "outside") goesInside = false;
|
|
7828
7976
|
else {
|
|
7829
|
-
const insideText = labelText(slice, "inside", options);
|
|
7977
|
+
const insideText = labelText(slice, "inside", options, percents);
|
|
7830
7978
|
const tangential = (slice.endAngle - slice.startAngle) * insideLabelR;
|
|
7831
7979
|
const fitsTangential = measure(insideText) + INSIDE_PAD <= tangential;
|
|
7832
7980
|
const fitsRadial = lineHeight2 * 0.8 <= radialBand;
|
|
7833
7981
|
goesInside = fitsTangential && fitsRadial;
|
|
7834
7982
|
}
|
|
7835
7983
|
if (goesInside) {
|
|
7836
|
-
insideSlices.push({ slice, mid, text: labelText(slice, "inside", options) });
|
|
7984
|
+
insideSlices.push({ slice, mid, text: labelText(slice, "inside", options, percents) });
|
|
7837
7985
|
} else {
|
|
7838
7986
|
const dir = direction(mid);
|
|
7839
7987
|
outsideSlices.push({
|
|
7840
7988
|
slice,
|
|
7841
7989
|
mid,
|
|
7842
|
-
text: labelText(slice, "outside", options),
|
|
7990
|
+
text: labelText(slice, "outside", options, percents),
|
|
7843
7991
|
side: dir.x >= 0 ? "right" : "left"
|
|
7844
7992
|
});
|
|
7845
7993
|
}
|
|
@@ -8226,17 +8374,29 @@ function buildStages(spec, palette) {
|
|
|
8226
8374
|
order.push(key);
|
|
8227
8375
|
}
|
|
8228
8376
|
}
|
|
8229
|
-
|
|
8377
|
+
const stages = [];
|
|
8378
|
+
for (const key of order) {
|
|
8230
8379
|
const { category, value } = totals.get(key);
|
|
8380
|
+
if (value <= 0) continue;
|
|
8231
8381
|
const rgba2 = colorScale.map(key);
|
|
8232
|
-
|
|
8382
|
+
stages.push({
|
|
8233
8383
|
key,
|
|
8234
8384
|
label: key === "" ? "(blank)" : key,
|
|
8235
8385
|
value,
|
|
8236
8386
|
category,
|
|
8237
8387
|
color: rgbaToCss(rgba2),
|
|
8238
8388
|
rgba: rgba2
|
|
8239
|
-
};
|
|
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);
|
|
8240
8400
|
});
|
|
8241
8401
|
}
|
|
8242
8402
|
function drawFunnel(surface, spec, tokens, size, context) {
|
|
@@ -8274,12 +8434,10 @@ function drawFunnel(surface, spec, tokens, size, context) {
|
|
|
8274
8434
|
const n = stages.length;
|
|
8275
8435
|
const gap = Math.min(6, Math.max(2, content.height / n / 12));
|
|
8276
8436
|
const bandH = content.height / n;
|
|
8277
|
-
const widthOf = (v) => v / maxValue * funnelW;
|
|
8437
|
+
const widthOf = (v) => Math.max(0, v) / maxValue * funnelW;
|
|
8278
8438
|
const alphaOf = (s) => emphasis ? emphasis.match({ [stageField]: s.category }) ? 1 : emphasis.dim : 1;
|
|
8279
|
-
const
|
|
8280
|
-
|
|
8281
|
-
return stages[i].value / stages[0].value;
|
|
8282
|
-
};
|
|
8439
|
+
const percents = funnelPercents(stages, mode);
|
|
8440
|
+
const pctOf = (i) => percents[i] ?? 0;
|
|
8283
8441
|
const sketch = resolveSketch(spec);
|
|
8284
8442
|
const pen = sketch ? new RoughPen(ctx, sketch) : null;
|
|
8285
8443
|
const bands = [];
|
|
@@ -8423,8 +8581,8 @@ function valueField(ref) {
|
|
|
8423
8581
|
function formatDelta(delta, specFormat) {
|
|
8424
8582
|
const sign2 = delta >= 0 ? "+" : "-";
|
|
8425
8583
|
const abs = Math.abs(delta);
|
|
8426
|
-
if (abs <= 1) return sign2 + formatNumber2(abs, ".1%");
|
|
8427
8584
|
if (specFormat) return sign2 + formatValue(abs, specFormat);
|
|
8585
|
+
if (abs <= 1) return sign2 + formatNumber2(abs, ".1%");
|
|
8428
8586
|
return sign2 + formatNumber2(abs, ",.0f");
|
|
8429
8587
|
}
|
|
8430
8588
|
function cardRectFor(surface, tokens, size, spec) {
|
|
@@ -8695,11 +8853,13 @@ function buildGraph(spec, palette) {
|
|
|
8695
8853
|
target.targetLinks.push(link);
|
|
8696
8854
|
links.push(link);
|
|
8697
8855
|
}
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
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
|
+
}
|
|
8702
8861
|
}
|
|
8862
|
+
computeNodeValues(order);
|
|
8703
8863
|
assignLayers(order);
|
|
8704
8864
|
const maxLayer = order.reduce((m, n) => Math.max(m, n.layer), 0);
|
|
8705
8865
|
const columns = Array.from({ length: maxLayer + 1 }, () => []);
|
|
@@ -8707,6 +8867,7 @@ function buildGraph(spec, palette) {
|
|
|
8707
8867
|
return { nodes: order, links, columns, maxLayer };
|
|
8708
8868
|
}
|
|
8709
8869
|
function assignLayers(nodes) {
|
|
8870
|
+
for (const n of nodes) n.layer = 0;
|
|
8710
8871
|
const indeg = /* @__PURE__ */ new Map();
|
|
8711
8872
|
for (const n of nodes) indeg.set(n, n.targetLinks.length);
|
|
8712
8873
|
const queue = nodes.filter((n) => (indeg.get(n) ?? 0) === 0);
|
|
@@ -8722,14 +8883,58 @@ function assignLayers(nodes) {
|
|
|
8722
8883
|
if (d === 0) queue.push(t);
|
|
8723
8884
|
}
|
|
8724
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
|
+
}
|
|
8725
8893
|
const maxLayer = nodes.reduce((m, n) => Math.max(m, n.layer), 0);
|
|
8726
8894
|
for (const n of nodes) {
|
|
8727
8895
|
if (n.sourceLinks.length === 0 && n.targetLinks.length > 0) n.layer = maxLayer;
|
|
8728
8896
|
}
|
|
8729
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
|
+
}
|
|
8730
8932
|
function layout(graph, area2, nodeWidth, pad3) {
|
|
8731
8933
|
const { columns, links, maxLayer } = graph;
|
|
8732
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;
|
|
8733
8938
|
for (const col of columns) {
|
|
8734
8939
|
for (const n of col) {
|
|
8735
8940
|
n.x0 = area2.x + n.layer * kx;
|
|
@@ -8740,26 +8945,26 @@ function layout(graph, area2, nodeWidth, pad3) {
|
|
|
8740
8945
|
for (const col of columns) {
|
|
8741
8946
|
if (col.length === 0) continue;
|
|
8742
8947
|
const sum = col.reduce((s, n) => s + n.value, 0);
|
|
8743
|
-
const avail = area2.height - (col.length - 1) *
|
|
8744
|
-
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);
|
|
8745
8950
|
}
|
|
8746
|
-
if (!Number.isFinite(ky) || ky
|
|
8951
|
+
if (!Number.isFinite(ky) || ky < 0) ky = 0;
|
|
8747
8952
|
for (const col of columns) {
|
|
8748
8953
|
let y = area2.y;
|
|
8749
8954
|
for (const n of col) {
|
|
8750
8955
|
n.y0 = y;
|
|
8751
|
-
n.y1 = y + Math.max(
|
|
8752
|
-
y = n.y1 +
|
|
8956
|
+
n.y1 = y + Math.max(0, n.value * ky);
|
|
8957
|
+
y = n.y1 + effectivePad;
|
|
8753
8958
|
}
|
|
8754
8959
|
centerColumn(col, area2);
|
|
8755
8960
|
}
|
|
8756
|
-
for (const l of links) l.width = Math.max(
|
|
8961
|
+
for (const l of links) l.width = Math.max(0, l.value * ky);
|
|
8757
8962
|
for (let i = 0; i < 6; i += 1) {
|
|
8758
8963
|
const alpha = Math.pow(0.97, i);
|
|
8759
8964
|
relax(columns, true, alpha);
|
|
8760
|
-
resolveAll(columns, area2,
|
|
8965
|
+
resolveAll(columns, area2, effectivePad);
|
|
8761
8966
|
relax(columns, false, alpha);
|
|
8762
|
-
resolveAll(columns, area2,
|
|
8967
|
+
resolveAll(columns, area2, effectivePad);
|
|
8763
8968
|
}
|
|
8764
8969
|
computeLinkBreadths(graph);
|
|
8765
8970
|
}
|
|
@@ -9001,6 +9206,7 @@ function featureKey(feature, featureId) {
|
|
|
9001
9206
|
if (featureId === "id" && feature.id != null) return toKey(feature.id);
|
|
9002
9207
|
const v = feature.properties?.[featureId];
|
|
9003
9208
|
if (v != null) return toKey(v);
|
|
9209
|
+
return "";
|
|
9004
9210
|
}
|
|
9005
9211
|
if (feature.id != null) return toKey(feature.id);
|
|
9006
9212
|
const props = feature.properties ?? {};
|
|
@@ -12012,6 +12218,12 @@ function buildGroups(leaves) {
|
|
|
12012
12218
|
}
|
|
12013
12219
|
return order.map((key) => groups.get(key));
|
|
12014
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
|
+
}
|
|
12015
12227
|
function drawRect2(ctx, pen, rect, fill, stroke) {
|
|
12016
12228
|
if (rect.width <= 0 || rect.height <= 0) return;
|
|
12017
12229
|
if (pen) {
|
|
@@ -12056,6 +12268,7 @@ function addTileLabels(surface, tokens, tile, valueFormat) {
|
|
|
12056
12268
|
function drawTreemap(surface, spec, tokens, size, context) {
|
|
12057
12269
|
const treemap = spec;
|
|
12058
12270
|
const ctx = surface.marks.ctx;
|
|
12271
|
+
const emphasis = context?.emphasis ?? null;
|
|
12059
12272
|
const content = drawTitleBlock(surface, tokens, size, treemap.title);
|
|
12060
12273
|
const leaves = buildLeaves(treemap);
|
|
12061
12274
|
colorLeaves(leaves, treemap, tokens);
|
|
@@ -12077,6 +12290,12 @@ function drawTreemap(surface, spec, tokens, size, context) {
|
|
|
12077
12290
|
const tiles = [];
|
|
12078
12291
|
const stroke = tokens.color.background;
|
|
12079
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
|
+
};
|
|
12080
12299
|
ctx.save();
|
|
12081
12300
|
if (treemap.encoding.group) {
|
|
12082
12301
|
const groups = buildGroups(leaves);
|
|
@@ -12091,14 +12310,14 @@ function drawTreemap(surface, spec, tokens, size, context) {
|
|
|
12091
12310
|
for (const leafNode of layoutTreemap(groupNode.item.leaves, inner)) {
|
|
12092
12311
|
const tile = { ...leafNode.item, rect: leafNode.rect };
|
|
12093
12312
|
tiles.push(tile);
|
|
12094
|
-
|
|
12313
|
+
drawTile(tile);
|
|
12095
12314
|
}
|
|
12096
12315
|
}
|
|
12097
12316
|
} else {
|
|
12098
12317
|
for (const leafNode of layoutTreemap(leaves, content)) {
|
|
12099
12318
|
const tile = { ...leafNode.item, rect: leafNode.rect };
|
|
12100
12319
|
tiles.push(tile);
|
|
12101
|
-
|
|
12320
|
+
drawTile(tile);
|
|
12102
12321
|
}
|
|
12103
12322
|
}
|
|
12104
12323
|
ctx.restore();
|