graphein 0.10.0 → 0.12.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.d.ts +23 -0
- package/dist/index.js +450 -102
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6245,6 +6245,10 @@ function yGridEnabled(model) {
|
|
|
6245
6245
|
return model.spec.axes?.y?.grid !== false;
|
|
6246
6246
|
}
|
|
6247
6247
|
function drawAxesUnderlay(surface, model) {
|
|
6248
|
+
if (model.orientation === "horizontal") {
|
|
6249
|
+
drawHorizontalAxesUnderlay(surface, model);
|
|
6250
|
+
return;
|
|
6251
|
+
}
|
|
6248
6252
|
const ctx = surface.marks.ctx;
|
|
6249
6253
|
const { plot, tokens } = model;
|
|
6250
6254
|
const x0 = plot.x;
|
|
@@ -6318,6 +6322,58 @@ function drawAxesUnderlay(surface, model) {
|
|
|
6318
6322
|
}
|
|
6319
6323
|
ctx.restore();
|
|
6320
6324
|
}
|
|
6325
|
+
function drawHorizontalAxesUnderlay(surface, model) {
|
|
6326
|
+
const ctx = surface.marks.ctx;
|
|
6327
|
+
const { plot, tokens } = model;
|
|
6328
|
+
const x0 = plot.x;
|
|
6329
|
+
const y0 = plot.y;
|
|
6330
|
+
const y1 = plot.y + plot.height;
|
|
6331
|
+
ctx.save();
|
|
6332
|
+
ctx.lineWidth = 1;
|
|
6333
|
+
const pen = model.sketch ? new RoughPen(ctx, model.sketch) : null;
|
|
6334
|
+
const seg = (sx0, sy0, sx1, sy1, stroke) => {
|
|
6335
|
+
pen.polyline([{ x: sx0, y: sy0 }, { x: sx1, y: sy1 }], {
|
|
6336
|
+
stroke,
|
|
6337
|
+
strokeWidth: 1,
|
|
6338
|
+
roughness: model.sketch.roughness * 0.5,
|
|
6339
|
+
bowing: model.sketch.bowing * 0.55
|
|
6340
|
+
});
|
|
6341
|
+
};
|
|
6342
|
+
if (yGridEnabled(model)) {
|
|
6343
|
+
if (pen) {
|
|
6344
|
+
for (const t of model.yTicks) seg(t.pos, y0, t.pos, y1, tokens.color.grid);
|
|
6345
|
+
} else {
|
|
6346
|
+
ctx.strokeStyle = tokens.color.grid;
|
|
6347
|
+
ctx.beginPath();
|
|
6348
|
+
for (const t of model.yTicks) {
|
|
6349
|
+
const x = crisp(t.pos);
|
|
6350
|
+
ctx.moveTo(x, y0);
|
|
6351
|
+
ctx.lineTo(x, y1);
|
|
6352
|
+
}
|
|
6353
|
+
ctx.stroke();
|
|
6354
|
+
}
|
|
6355
|
+
}
|
|
6356
|
+
if (model.spec.axes?.x?.show !== false) {
|
|
6357
|
+
if (pen) {
|
|
6358
|
+
seg(x0, y0, x0, y1, tokens.color.axis);
|
|
6359
|
+
for (const t of model.xTicks) seg(x0 - TICK_SIZE, t.pos, x0, t.pos, tokens.color.axis);
|
|
6360
|
+
} else {
|
|
6361
|
+
ctx.strokeStyle = tokens.color.axis;
|
|
6362
|
+
ctx.beginPath();
|
|
6363
|
+
ctx.moveTo(crisp(x0), y0);
|
|
6364
|
+
ctx.lineTo(crisp(x0), y1);
|
|
6365
|
+
ctx.stroke();
|
|
6366
|
+
ctx.beginPath();
|
|
6367
|
+
for (const t of model.xTicks) {
|
|
6368
|
+
const y = crisp(t.pos);
|
|
6369
|
+
ctx.moveTo(x0 - TICK_SIZE, y);
|
|
6370
|
+
ctx.lineTo(x0, y);
|
|
6371
|
+
}
|
|
6372
|
+
ctx.stroke();
|
|
6373
|
+
}
|
|
6374
|
+
}
|
|
6375
|
+
ctx.restore();
|
|
6376
|
+
}
|
|
6321
6377
|
function thinXTicks(model, avgCharPx) {
|
|
6322
6378
|
const ticks2 = model.xTicks;
|
|
6323
6379
|
if (ticks2.length <= 1) return ticks2;
|
|
@@ -6388,9 +6444,12 @@ function drawOverlay(surface, model) {
|
|
|
6388
6444
|
const { plot, tokens, frame } = model;
|
|
6389
6445
|
const f = tokens.font;
|
|
6390
6446
|
const smallFont = fontString(f.size.small, f.family, f.weight.normal);
|
|
6391
|
-
|
|
6447
|
+
const horizontal = model.orientation === "horizontal";
|
|
6448
|
+
const gutterTicks = horizontal ? model.xTicks : model.yTicks;
|
|
6449
|
+
const gutterLabelsOn = horizontal ? model.spec.axes?.x?.labels !== false : model.spec.axes?.y?.labels !== false;
|
|
6450
|
+
if (gutterLabelsOn) {
|
|
6392
6451
|
const gutterRight = plot.x - TICK_SIZE - 4;
|
|
6393
|
-
for (const t of
|
|
6452
|
+
for (const t of gutterTicks) {
|
|
6394
6453
|
addText(surface, smallFont, {
|
|
6395
6454
|
left: 0,
|
|
6396
6455
|
top: t.pos,
|
|
@@ -6403,20 +6462,34 @@ function drawOverlay(surface, model) {
|
|
|
6403
6462
|
});
|
|
6404
6463
|
}
|
|
6405
6464
|
}
|
|
6406
|
-
|
|
6465
|
+
const bottomLabelsOn = horizontal ? model.spec.axes?.y?.labels !== false : model.spec.axes?.x?.labels !== false;
|
|
6466
|
+
if (bottomLabelsOn) {
|
|
6407
6467
|
const top = plot.y + plot.height + TICK_SIZE + 3;
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6468
|
+
if (horizontal) {
|
|
6469
|
+
for (const p of placeXLabels(model.yTicks, frame.originX, frame.width, smallFont)) {
|
|
6470
|
+
addText(surface, smallFont, {
|
|
6471
|
+
left: p.left,
|
|
6472
|
+
top,
|
|
6473
|
+
text: p.text,
|
|
6474
|
+
color: tokens.color.textMuted,
|
|
6475
|
+
size: f.size.small,
|
|
6476
|
+
transform: p.transform
|
|
6477
|
+
});
|
|
6478
|
+
}
|
|
6479
|
+
} else {
|
|
6480
|
+
const rotated = frame.xLabelAngle > 0;
|
|
6481
|
+
for (const p of resolveXLabels(model)) {
|
|
6482
|
+
addText(surface, smallFont, {
|
|
6483
|
+
left: p.left,
|
|
6484
|
+
top,
|
|
6485
|
+
text: p.text,
|
|
6486
|
+
color: tokens.color.textMuted,
|
|
6487
|
+
size: f.size.small,
|
|
6488
|
+
align: rotated ? "right" : void 0,
|
|
6489
|
+
transform: p.transform,
|
|
6490
|
+
transformOrigin: rotated ? "100% 0%" : void 0
|
|
6491
|
+
});
|
|
6492
|
+
}
|
|
6420
6493
|
}
|
|
6421
6494
|
}
|
|
6422
6495
|
drawAxisTitles(surface, model);
|
|
@@ -6428,6 +6501,32 @@ function drawAxisTitles(surface, model) {
|
|
|
6428
6501
|
const f = tokens.font;
|
|
6429
6502
|
const baseFont = fontString(f.size.base, f.family, f.weight.medium);
|
|
6430
6503
|
const xTitle = model.spec.axes?.x?.title ?? model.spec.encoding.x.title;
|
|
6504
|
+
const yTitle = model.spec.axes?.y?.title ?? model.spec.encoding.y.title;
|
|
6505
|
+
if (model.orientation === "horizontal") {
|
|
6506
|
+
if (yTitle) {
|
|
6507
|
+
addText(surface, baseFont, {
|
|
6508
|
+
left: plot.x + plot.width / 2,
|
|
6509
|
+
top: frame.originY + frame.height - Math.round(f.size.base * 1.35),
|
|
6510
|
+
text: yTitle,
|
|
6511
|
+
color: tokens.color.text,
|
|
6512
|
+
size: f.size.base,
|
|
6513
|
+
weight: f.weight.medium,
|
|
6514
|
+
transform: "translateX(-50%)"
|
|
6515
|
+
});
|
|
6516
|
+
}
|
|
6517
|
+
if (xTitle) {
|
|
6518
|
+
addText(surface, baseFont, {
|
|
6519
|
+
left: frame.originX + Math.round(f.size.base * 0.4),
|
|
6520
|
+
top: plot.y + plot.height / 2,
|
|
6521
|
+
text: xTitle,
|
|
6522
|
+
color: tokens.color.text,
|
|
6523
|
+
size: f.size.base,
|
|
6524
|
+
weight: f.weight.medium,
|
|
6525
|
+
transform: "translate(-50%, -50%) rotate(-90deg)"
|
|
6526
|
+
});
|
|
6527
|
+
}
|
|
6528
|
+
return;
|
|
6529
|
+
}
|
|
6431
6530
|
if (xTitle) {
|
|
6432
6531
|
addText(surface, baseFont, {
|
|
6433
6532
|
left: plot.x + plot.width / 2,
|
|
@@ -6439,7 +6538,6 @@ function drawAxisTitles(surface, model) {
|
|
|
6439
6538
|
transform: "translateX(-50%)"
|
|
6440
6539
|
});
|
|
6441
6540
|
}
|
|
6442
|
-
const yTitle = model.spec.axes?.y?.title ?? model.spec.encoding.y.title;
|
|
6443
6541
|
if (yTitle) {
|
|
6444
6542
|
addText(surface, baseFont, {
|
|
6445
6543
|
left: frame.originX + Math.round(f.size.base * 0.4),
|
|
@@ -6642,6 +6740,7 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6642
6740
|
const yField = enc.y.field;
|
|
6643
6741
|
const xType = fieldType(spec, enc.x, "nominal");
|
|
6644
6742
|
const xKind = xKindFor(spec, xType);
|
|
6743
|
+
const horizontal = spec.type === "bar" && spec.orientation === "horizontal";
|
|
6645
6744
|
const seriesField = resolveSeriesField(spec);
|
|
6646
6745
|
const rawSeries = groupBySeries(data, seriesField);
|
|
6647
6746
|
const seriesKeys = rawSeries.map((s) => s.key);
|
|
@@ -6797,6 +6896,19 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6797
6896
|
}));
|
|
6798
6897
|
const padding = { ...DEFAULT_PADDING, ...spec.padding };
|
|
6799
6898
|
const titleInput4 = resolveTitle(spec);
|
|
6899
|
+
const categoryAxisInput = {
|
|
6900
|
+
show: xShow,
|
|
6901
|
+
labels: xLabels,
|
|
6902
|
+
title: resolveAxisTitle(enc.x, xAxisCfg),
|
|
6903
|
+
edgeAnchored: !categories,
|
|
6904
|
+
labelAngle: xAxisCfg.labelAngle
|
|
6905
|
+
};
|
|
6906
|
+
const valueAxisInput = {
|
|
6907
|
+
show: yShow,
|
|
6908
|
+
labels: yLabels,
|
|
6909
|
+
title: resolveAxisTitle(enc.y, yAxisCfg),
|
|
6910
|
+
edgeAnchored: true
|
|
6911
|
+
};
|
|
6800
6912
|
const frame = computeFrame({
|
|
6801
6913
|
width: opts.width,
|
|
6802
6914
|
height: opts.height,
|
|
@@ -6806,8 +6918,8 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6806
6918
|
font: tokens.font,
|
|
6807
6919
|
title: titleInput4,
|
|
6808
6920
|
legend: legend.show && legendItems.length > 1 ? { items: legendItems, position: legend.position } : void 0,
|
|
6809
|
-
xAxis:
|
|
6810
|
-
yAxis:
|
|
6921
|
+
xAxis: horizontal ? valueAxisInput : categoryAxisInput,
|
|
6922
|
+
yAxis: horizontal ? categoryAxisInput : valueAxisInput
|
|
6811
6923
|
});
|
|
6812
6924
|
const legendHits = legendInteractive(spec) && seriesField && frame.legendItems ? frame.legendItems.map((item, i) => {
|
|
6813
6925
|
const rowH = Math.max(LEGEND_SWATCH2, Math.round(tokens.font.size.small * 1.35));
|
|
@@ -6825,16 +6937,18 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6825
6937
|
};
|
|
6826
6938
|
}) : void 0;
|
|
6827
6939
|
const plot = frame.plot;
|
|
6828
|
-
const
|
|
6829
|
-
const yScale = yIsLog ? logScale({ domain: yDomain, range:
|
|
6940
|
+
const valueRange = horizontal ? [plot.x, plot.x + plot.width] : [plot.y + plot.height, plot.y];
|
|
6941
|
+
const yScale = yIsLog ? logScale({ domain: yDomain, range: valueRange, base: yBase, clamp: yClamp }) : linearScale({ domain: yDomain, range: valueRange, clamp: yClamp });
|
|
6830
6942
|
const yPixel = (value) => yScale.map(toNumber(value));
|
|
6831
|
-
const
|
|
6943
|
+
const valueMin = horizontal ? plot.x : plot.y;
|
|
6944
|
+
const valueMax = horizontal ? plot.x + plot.width : plot.y + plot.height;
|
|
6945
|
+
const baseline = yIsLog ? horizontal ? plot.x : plot.y + plot.height : clampPx(yScale.map(0), valueMin, valueMax);
|
|
6832
6946
|
const xModel = buildXModel(xKind, {
|
|
6833
6947
|
field: xField,
|
|
6834
6948
|
type: xType,
|
|
6835
6949
|
categories,
|
|
6836
6950
|
domainNum: xDomainNum,
|
|
6837
|
-
range: [plot.x, plot.x + plot.width],
|
|
6951
|
+
range: horizontal ? [plot.y, plot.y + plot.height] : [plot.x, plot.x + plot.width],
|
|
6838
6952
|
paddingInner: spec.type === "bar" ? 0.2 : spec.type === "box" ? 0.32 : 0.5,
|
|
6839
6953
|
scaleCfg: enc.x.scale
|
|
6840
6954
|
});
|
|
@@ -6857,6 +6971,8 @@ function buildCartesianModel(spec, tokens, opts) {
|
|
|
6857
6971
|
series,
|
|
6858
6972
|
seriesField,
|
|
6859
6973
|
stacked,
|
|
6974
|
+
orientation: horizontal ? "horizontal" : "vertical",
|
|
6975
|
+
project: horizontal ? (catPixel, valPixel) => ({ x: valPixel, y: catPixel }) : (catPixel, valPixel) => ({ x: catPixel, y: valPixel }),
|
|
6860
6976
|
xTicks,
|
|
6861
6977
|
yTicks,
|
|
6862
6978
|
colorOf,
|
|
@@ -7254,14 +7370,11 @@ function finite2(value) {
|
|
|
7254
7370
|
function defaultRadius(model, spec) {
|
|
7255
7371
|
return Math.max(0, spec.cornerRadius ?? Math.min(4, model.tokens.radius.sm));
|
|
7256
7372
|
}
|
|
7257
|
-
function cornerSideFor(baseY, valueY) {
|
|
7258
|
-
if (valueY < baseY) return "top";
|
|
7259
|
-
if (valueY > baseY) return "bottom";
|
|
7260
|
-
return "none";
|
|
7261
|
-
}
|
|
7262
7373
|
function makeRadii(side, radius) {
|
|
7263
7374
|
if (side === "top") return [radius, radius, 0, 0];
|
|
7264
7375
|
if (side === "bottom") return [0, 0, radius, radius];
|
|
7376
|
+
if (side === "right") return [0, radius, radius, 0];
|
|
7377
|
+
if (side === "left") return [radius, 0, 0, radius];
|
|
7265
7378
|
return 0;
|
|
7266
7379
|
}
|
|
7267
7380
|
function drawRect(ctx, rect, radius, pen, alpha = 1) {
|
|
@@ -7281,28 +7394,39 @@ function drawRect(ctx, rect, radius, pen, alpha = 1) {
|
|
|
7281
7394
|
ctx.fill();
|
|
7282
7395
|
if (alpha !== 1) ctx.globalAlpha = prevAlpha;
|
|
7283
7396
|
}
|
|
7284
|
-
function
|
|
7285
|
-
if (!finite2(
|
|
7286
|
-
const
|
|
7287
|
-
const
|
|
7288
|
-
if (
|
|
7397
|
+
function orientedRect(horizontal, catStart, catThick, base, value, color) {
|
|
7398
|
+
if (!finite2(catStart) || !finite2(catThick) || !finite2(base) || !finite2(value)) return null;
|
|
7399
|
+
const lo = Math.min(base, value);
|
|
7400
|
+
const len = Math.abs(value - base);
|
|
7401
|
+
if (catThick <= 0 || len <= 0) return null;
|
|
7402
|
+
if (horizontal) {
|
|
7403
|
+
return {
|
|
7404
|
+
x: lo,
|
|
7405
|
+
y: catStart,
|
|
7406
|
+
width: len,
|
|
7407
|
+
height: catThick,
|
|
7408
|
+
color,
|
|
7409
|
+
cornerSide: value > base ? "right" : value < base ? "left" : "none"
|
|
7410
|
+
};
|
|
7411
|
+
}
|
|
7289
7412
|
return {
|
|
7290
|
-
x,
|
|
7291
|
-
y,
|
|
7292
|
-
width,
|
|
7293
|
-
height,
|
|
7413
|
+
x: catStart,
|
|
7414
|
+
y: lo,
|
|
7415
|
+
width: catThick,
|
|
7416
|
+
height: len,
|
|
7294
7417
|
color,
|
|
7295
|
-
cornerSide:
|
|
7418
|
+
cornerSide: value < base ? "top" : value > base ? "bottom" : "none"
|
|
7296
7419
|
};
|
|
7297
7420
|
}
|
|
7298
7421
|
function drawGroupedBars(ctx, model, radius, pen) {
|
|
7299
7422
|
const readX = accessor(model.x.field);
|
|
7300
7423
|
const readY = accessor(model.y.field ?? "");
|
|
7424
|
+
const horizontal = model.orientation === "horizontal";
|
|
7301
7425
|
const seriesCount = model.series.length;
|
|
7302
7426
|
const bandWidth = model.x.bandwidth;
|
|
7303
7427
|
const subWidth = seriesCount > 1 ? bandWidth / seriesCount : bandWidth;
|
|
7304
7428
|
const gap = seriesCount > 1 && subWidth > 3 ? Math.min(1, subWidth * 0.15) : 0;
|
|
7305
|
-
const
|
|
7429
|
+
const barThick = Math.max(0, subWidth - gap);
|
|
7306
7430
|
model.series.forEach((series, seriesIndex) => {
|
|
7307
7431
|
ctx.fillStyle = series.color;
|
|
7308
7432
|
for (const row of series.rows) {
|
|
@@ -7311,8 +7435,8 @@ function drawGroupedBars(ctx, model, radius, pen) {
|
|
|
7311
7435
|
if (raw == null || raw === "" || !finite2(value)) continue;
|
|
7312
7436
|
const center2 = model.x.pixel(readX(row));
|
|
7313
7437
|
if (center2 == null) continue;
|
|
7314
|
-
const
|
|
7315
|
-
const rect =
|
|
7438
|
+
const catStart = center2 - bandWidth / 2 + seriesIndex * subWidth + gap / 2;
|
|
7439
|
+
const rect = orientedRect(horizontal, catStart, barThick, model.y.baseline, model.y.pixel(value), series.color);
|
|
7316
7440
|
if (rect) drawRect(ctx, rect, radius, pen, rowAlpha(model.emphasis, row));
|
|
7317
7441
|
}
|
|
7318
7442
|
});
|
|
@@ -7320,6 +7444,7 @@ function drawGroupedBars(ctx, model, radius, pen) {
|
|
|
7320
7444
|
function drawStackedBars(ctx, model, radius, pen) {
|
|
7321
7445
|
const readX = accessor(model.x.field);
|
|
7322
7446
|
const readY = accessor(model.y.field ?? "");
|
|
7447
|
+
const horizontal = model.orientation === "horizontal";
|
|
7323
7448
|
const positiveTotals = /* @__PURE__ */ new Map();
|
|
7324
7449
|
const negativeTotals = /* @__PURE__ */ new Map();
|
|
7325
7450
|
const segments = [];
|
|
@@ -7339,7 +7464,7 @@ function drawStackedBars(ctx, model, radius, pen) {
|
|
|
7339
7464
|
const base = totals.get(categoryKey2) ?? 0;
|
|
7340
7465
|
const top = base + value;
|
|
7341
7466
|
totals.set(categoryKey2, top);
|
|
7342
|
-
const rect =
|
|
7467
|
+
const rect = orientedRect(horizontal, center2 - bandWidth / 2, bandWidth, model.y.pixel(base), model.y.pixel(top), series.color);
|
|
7343
7468
|
if (!rect) continue;
|
|
7344
7469
|
segments.push({ ...rect, stackEndKey, alpha: rowAlpha(model.emphasis, row) });
|
|
7345
7470
|
roundedEnds.set(stackEndKey, segments.length - 1);
|
|
@@ -10034,7 +10159,7 @@ function buildTable(opts) {
|
|
|
10034
10159
|
overflow: "auto",
|
|
10035
10160
|
pointerEvents: "auto",
|
|
10036
10161
|
boxSizing: "border-box",
|
|
10037
|
-
border: `1px solid ${tokens.color.border}`,
|
|
10162
|
+
border: `1px solid ${opts.sketch ? "transparent" : tokens.color.border}`,
|
|
10038
10163
|
borderRadius: opts.sketch ? SKETCH_BORDER_RADIUS2 : `${tokens.radius.sm}px`,
|
|
10039
10164
|
background: tokens.color.background
|
|
10040
10165
|
});
|
|
@@ -10065,14 +10190,23 @@ function paintTableCanvas(opts) {
|
|
|
10065
10190
|
const available = Math.max(0, rect.width - 2);
|
|
10066
10191
|
const widths = computeFittedWidths(opts.columns, available);
|
|
10067
10192
|
opts.widths = widths;
|
|
10193
|
+
const pen = opts.sketch ? new RoughPen(ctx, opts.sketch) : null;
|
|
10194
|
+
const w = Math.max(0, rect.width);
|
|
10195
|
+
const h = Math.max(0, rect.height);
|
|
10068
10196
|
ctx.save();
|
|
10069
10197
|
ctx.beginPath();
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10198
|
+
if (pen) {
|
|
10199
|
+
ctx.rect(rect.x, rect.y, w, h);
|
|
10200
|
+
ctx.fillStyle = tokens.color.background;
|
|
10201
|
+
ctx.fill();
|
|
10202
|
+
} else {
|
|
10203
|
+
roundedRect(ctx, rect.x, rect.y, w, h, tokens.radius.sm);
|
|
10204
|
+
ctx.fillStyle = tokens.color.background;
|
|
10205
|
+
ctx.fill();
|
|
10206
|
+
ctx.lineWidth = 1;
|
|
10207
|
+
ctx.strokeStyle = tokens.color.border;
|
|
10208
|
+
ctx.stroke();
|
|
10209
|
+
}
|
|
10076
10210
|
ctx.clip();
|
|
10077
10211
|
const headerRows = normalizedHeaderRows(opts);
|
|
10078
10212
|
const headerHeight = headerRows.length * metrics.headerHeight;
|
|
@@ -10084,17 +10218,18 @@ function paintTableCanvas(opts) {
|
|
|
10084
10218
|
const clipped = visibleRows < opts.rowCount;
|
|
10085
10219
|
const cueReserve = clipped && opts.clippedCue !== false && visibleRows > 0 ? metrics.rowHeight : 0;
|
|
10086
10220
|
const rowsToDraw = Math.max(0, visibleRows - (cueReserve > 0 ? 1 : 0));
|
|
10087
|
-
paintHeaderCanvas(opts, widths, headerRows, metrics);
|
|
10221
|
+
paintHeaderCanvas(opts, widths, headerRows, metrics, pen);
|
|
10088
10222
|
for (let rowIndex = 0; rowIndex < rowsToDraw; rowIndex += 1) {
|
|
10089
|
-
paintBodyRowCanvas(opts, widths, rowIndex, bodyTop + rowIndex * metrics.rowHeight, metrics);
|
|
10223
|
+
paintBodyRowCanvas(opts, widths, rowIndex, bodyTop + rowIndex * metrics.rowHeight, metrics, pen);
|
|
10090
10224
|
}
|
|
10091
10225
|
if (cueReserve > 0) {
|
|
10092
10226
|
paintClippedCueCanvas(opts, bodyTop + rowsToDraw * metrics.rowHeight, metrics, opts.rowCount - rowsToDraw);
|
|
10093
10227
|
}
|
|
10094
10228
|
if (opts.footerRow) {
|
|
10095
|
-
paintFooterCanvas(opts, widths, rect.y + rect.height - metrics.rowHeight, metrics);
|
|
10229
|
+
paintFooterCanvas(opts, widths, rect.y + rect.height - metrics.rowHeight, metrics, pen);
|
|
10096
10230
|
}
|
|
10097
10231
|
ctx.restore();
|
|
10232
|
+
if (pen) pen.rect(rect.x, rect.y, w, h, { stroke: tokens.color.border });
|
|
10098
10233
|
}
|
|
10099
10234
|
var FILL_EPS = 0.5;
|
|
10100
10235
|
var cachedScrollbarWidth = null;
|
|
@@ -10187,14 +10322,14 @@ function makeHeaderCell(opts, title2, colIndex, align, rowIndex = 0) {
|
|
|
10187
10322
|
th.textContent = title2;
|
|
10188
10323
|
const isSorted = colIndex != null && opts.sortState?.col === colIndex;
|
|
10189
10324
|
if (isSorted) th.setAttribute("aria-sort", opts.sortState?.dir === "desc" ? "descending" : "ascending");
|
|
10190
|
-
setCellBaseStyles(th, opts.tokens, align ?? "left", opts.stickyHeader !== false, metrics.padding);
|
|
10325
|
+
setCellBaseStyles(th, opts.tokens, align ?? "left", opts.stickyHeader !== false, metrics.padding, gridBorderColor(opts));
|
|
10191
10326
|
setStyles(th, {
|
|
10192
10327
|
top: `${rowIndex * metrics.headerHeight}px`,
|
|
10193
10328
|
zIndex: "4",
|
|
10194
10329
|
background: opts.tokens.color.surface,
|
|
10195
10330
|
color: opts.tokens.color.text,
|
|
10196
10331
|
fontWeight: String(opts.tokens.font.weight.bold),
|
|
10197
|
-
borderBottom: `1px solid ${opts
|
|
10332
|
+
borderBottom: `1px solid ${gridBorderColor(opts)}`,
|
|
10198
10333
|
userSelect: "none",
|
|
10199
10334
|
height: `${metrics.headerHeight}px`
|
|
10200
10335
|
});
|
|
@@ -10252,19 +10387,19 @@ function makeBodyRow(opts, rowIndex, rowHeight) {
|
|
|
10252
10387
|
const td = document.createElement("td");
|
|
10253
10388
|
const cell = opts.getCell(rowIndex, colIndex);
|
|
10254
10389
|
const rowBg = rowBackground(opts, rowIndex, cls);
|
|
10255
|
-
setCellBaseStyles(td, opts.tokens, column.align, false, metrics.padding);
|
|
10390
|
+
setCellBaseStyles(td, opts.tokens, column.align, false, metrics.padding, gridBorderColor(opts));
|
|
10256
10391
|
setStyles(td, {
|
|
10257
10392
|
position: colIndex < (opts.rowHeaderSpan ?? 0) ? "sticky" : "relative",
|
|
10258
10393
|
background: rowBg,
|
|
10259
10394
|
fontWeight: cls === "normal" ? String(opts.tokens.font.weight.normal) : String(opts.tokens.font.weight.bold),
|
|
10260
10395
|
color: opts.tokens.color.text,
|
|
10261
|
-
borderBottom: `1px solid ${opts
|
|
10396
|
+
borderBottom: `1px solid ${gridBorderColor(opts)}`,
|
|
10262
10397
|
height: rowHeight ? `${rowHeight}px` : `${metrics.rowHeight}px`,
|
|
10263
10398
|
whiteSpace: column.wrap === true ? "normal" : "nowrap"
|
|
10264
10399
|
});
|
|
10265
10400
|
const indent = opts.cellIndent?.(rowIndex, colIndex) ?? 0;
|
|
10266
10401
|
if (indent > 0) td.style.paddingLeft = `${metrics.paddingX + indent}px`;
|
|
10267
|
-
applyConditionalFormatting(td, opts, column, colIndex, cell, rowBg);
|
|
10402
|
+
applyConditionalFormatting(td, opts, column, colIndex, cell, rowBg, rowIndex);
|
|
10268
10403
|
if (td.childNodes.length === 0) {
|
|
10269
10404
|
td.textContent = formatDisplayValue(cell.value, column);
|
|
10270
10405
|
applyNegativeStyle(td, cell.value, column);
|
|
@@ -10282,7 +10417,7 @@ function buildFooter(opts) {
|
|
|
10282
10417
|
opts.columns.forEach((column, colIndex) => {
|
|
10283
10418
|
const td = document.createElement("td");
|
|
10284
10419
|
const cell = opts.footerRow?.cells[colIndex] ?? { value: null};
|
|
10285
|
-
setCellBaseStyles(td, opts.tokens, column.align, false, metrics.padding);
|
|
10420
|
+
setCellBaseStyles(td, opts.tokens, column.align, false, metrics.padding, gridBorderColor(opts));
|
|
10286
10421
|
setStyles(td, {
|
|
10287
10422
|
position: colIndex < (opts.rowHeaderSpan ?? 0) ? "sticky" : "sticky",
|
|
10288
10423
|
bottom: "0",
|
|
@@ -10290,8 +10425,8 @@ function buildFooter(opts) {
|
|
|
10290
10425
|
background: opts.tokens.color.surface,
|
|
10291
10426
|
color: opts.tokens.color.text,
|
|
10292
10427
|
fontWeight: String(opts.tokens.font.weight.bold),
|
|
10293
|
-
borderTop: `1px solid ${opts
|
|
10294
|
-
borderBottom: `1px solid ${opts
|
|
10428
|
+
borderTop: `1px solid ${gridBorderColor(opts)}`,
|
|
10429
|
+
borderBottom: `1px solid ${gridBorderColor(opts)}`,
|
|
10295
10430
|
height: `${metrics.rowHeight}px`,
|
|
10296
10431
|
whiteSpace: column.wrap === true ? "normal" : "nowrap"
|
|
10297
10432
|
});
|
|
@@ -10317,7 +10452,7 @@ function makeSpacerRow(opts, height) {
|
|
|
10317
10452
|
tr.appendChild(td);
|
|
10318
10453
|
return tr;
|
|
10319
10454
|
}
|
|
10320
|
-
function applyConditionalFormatting(td, opts, column, colIndex, cell, rowBg) {
|
|
10455
|
+
function applyConditionalFormatting(td, opts, column, colIndex, cell, rowBg, rowIndex = 0) {
|
|
10321
10456
|
const cf = column.conditionalFormat;
|
|
10322
10457
|
const raw = cell.raw;
|
|
10323
10458
|
const domain = opts.conditionalDomains?.[colIndex] ?? null;
|
|
@@ -10362,13 +10497,27 @@ function applyConditionalFormatting(td, opts, column, colIndex, cell, rowBg) {
|
|
|
10362
10497
|
}
|
|
10363
10498
|
if (!domain) return;
|
|
10364
10499
|
const ratio = normalized(numeric, domain);
|
|
10365
|
-
const bar = document.createElement("div");
|
|
10366
10500
|
const diverging2 = cf.negativeColor !== void 0 || cf.baseline === "zero";
|
|
10367
10501
|
const positive = numeric >= 0;
|
|
10368
10502
|
const barColor = alphaColor(positive ? cf.color ?? opts.tokens.color.accent : cf.negativeColor ?? opts.tokens.color.negative, 0.85);
|
|
10369
10503
|
const baseline = diverging2 ? normalized(0, domain) : 0;
|
|
10370
10504
|
const left = diverging2 ? Math.min(baseline, normalized(numeric, domain)) : 0;
|
|
10371
10505
|
const width = diverging2 ? Math.abs(normalized(numeric, domain) - baseline) : ratio;
|
|
10506
|
+
const text = document.createElement("span");
|
|
10507
|
+
text.textContent = cf.showValue === false ? "" : formatDisplayValue(cell.value, column);
|
|
10508
|
+
setStyles(text, overlayTextStyles(opts.tokens.color.text));
|
|
10509
|
+
td.style.background = rowBg;
|
|
10510
|
+
if (opts.sketch) {
|
|
10511
|
+
const colWidth = opts.widths?.[colIndex] ?? column.width ?? defaultColumnWidth(column);
|
|
10512
|
+
const rowHeight = densityMetrics(opts.density).rowHeight;
|
|
10513
|
+
const seed = (opts.sketch.seed ^ Math.imul(rowIndex + 1, 374761393) ^ Math.imul(colIndex + 1, 668265263)) >>> 0;
|
|
10514
|
+
const canvas = buildSketchDataBar(opts.sketch, barColor, colWidth, rowHeight, left, width, seed);
|
|
10515
|
+
if (canvas) td.appendChild(canvas);
|
|
10516
|
+
td.appendChild(text);
|
|
10517
|
+
applyNegativeStyle(text, cell.value, column);
|
|
10518
|
+
return;
|
|
10519
|
+
}
|
|
10520
|
+
const bar = document.createElement("div");
|
|
10372
10521
|
setStyles(bar, {
|
|
10373
10522
|
position: "absolute",
|
|
10374
10523
|
left: `calc(${Math.round(left * 1e3) / 10}% + 4px)`,
|
|
@@ -10380,14 +10529,38 @@ function applyConditionalFormatting(td, opts, column, colIndex, cell, rowBg) {
|
|
|
10380
10529
|
opacity: "0.9",
|
|
10381
10530
|
pointerEvents: "none"
|
|
10382
10531
|
});
|
|
10383
|
-
const text = document.createElement("span");
|
|
10384
|
-
text.textContent = cf.showValue === false ? "" : formatDisplayValue(cell.value, column);
|
|
10385
|
-
setStyles(text, overlayTextStyles(opts.tokens.color.text));
|
|
10386
|
-
td.style.background = rowBg;
|
|
10387
10532
|
td.append(bar, text);
|
|
10388
10533
|
applyNegativeStyle(text, cell.value, column);
|
|
10389
10534
|
}
|
|
10390
|
-
function
|
|
10535
|
+
function buildSketchDataBar(sketch, color, colWidth, rowHeight, leftRatio, widthRatio, seed) {
|
|
10536
|
+
const avail = Math.max(0, colWidth - 8);
|
|
10537
|
+
const barW = widthRatio * avail;
|
|
10538
|
+
const barH = Math.max(0, rowHeight - 10);
|
|
10539
|
+
if (barW <= 0.5 || barH <= 0) return null;
|
|
10540
|
+
const pad3 = 3;
|
|
10541
|
+
const leftPx = 4 + leftRatio * avail;
|
|
10542
|
+
const dpr = getDevicePixelRatio();
|
|
10543
|
+
const canvas = document.createElement("canvas");
|
|
10544
|
+
const cssW = barW + pad3 * 2;
|
|
10545
|
+
const cssH = barH + pad3 * 2;
|
|
10546
|
+
canvas.width = Math.max(1, Math.round(cssW * dpr));
|
|
10547
|
+
canvas.height = Math.max(1, Math.round(cssH * dpr));
|
|
10548
|
+
setStyles(canvas, {
|
|
10549
|
+
position: "absolute",
|
|
10550
|
+
left: `${leftPx - pad3}px`,
|
|
10551
|
+
top: `${5 - pad3}px`,
|
|
10552
|
+
width: `${cssW}px`,
|
|
10553
|
+
height: `${cssH}px`,
|
|
10554
|
+
pointerEvents: "none"
|
|
10555
|
+
});
|
|
10556
|
+
const ctx = canvas.getContext("2d");
|
|
10557
|
+
if (!ctx) return null;
|
|
10558
|
+
ctx.scale(dpr, dpr);
|
|
10559
|
+
const pen = new RoughPen(ctx, { ...sketch, seed });
|
|
10560
|
+
pen.rect(pad3, pad3, barW, barH, { stroke: color, fill: color, fillAlpha: 0.9, strokeAlpha: 0.9 });
|
|
10561
|
+
return canvas;
|
|
10562
|
+
}
|
|
10563
|
+
function setCellBaseStyles(cell, tokens, align, sticky, padding = "6px 10px", borderColor = tokens.color.border) {
|
|
10391
10564
|
setStyles(cell, {
|
|
10392
10565
|
boxSizing: "border-box",
|
|
10393
10566
|
padding,
|
|
@@ -10395,11 +10568,14 @@ function setCellBaseStyles(cell, tokens, align, sticky, padding = "6px 10px") {
|
|
|
10395
10568
|
whiteSpace: "nowrap",
|
|
10396
10569
|
overflow: "hidden",
|
|
10397
10570
|
textOverflow: "ellipsis",
|
|
10398
|
-
borderRight: `1px solid ${
|
|
10571
|
+
borderRight: `1px solid ${borderColor}`,
|
|
10399
10572
|
verticalAlign: "middle"
|
|
10400
10573
|
});
|
|
10401
10574
|
if (sticky) cell.style.position = "sticky";
|
|
10402
10575
|
}
|
|
10576
|
+
function gridBorderColor(opts) {
|
|
10577
|
+
return opts.sketch ? "transparent" : opts.tokens.color.border;
|
|
10578
|
+
}
|
|
10403
10579
|
function renderIconValue(td, icon, color, value, position) {
|
|
10404
10580
|
const iconSpan = document.createElement("span");
|
|
10405
10581
|
iconSpan.textContent = icon;
|
|
@@ -10453,7 +10629,7 @@ function normalizedHeaderRows(opts) {
|
|
|
10453
10629
|
}))
|
|
10454
10630
|
];
|
|
10455
10631
|
}
|
|
10456
|
-
function paintHeaderCanvas(opts, widths, rows, metrics) {
|
|
10632
|
+
function paintHeaderCanvas(opts, widths, rows, metrics, pen) {
|
|
10457
10633
|
rows.forEach((row, rowIndex) => {
|
|
10458
10634
|
let cursor = 0;
|
|
10459
10635
|
for (const cell of row) {
|
|
@@ -10464,7 +10640,7 @@ function paintHeaderCanvas(opts, widths, rows, metrics) {
|
|
|
10464
10640
|
const y = opts.rect.y + rowIndex * metrics.headerHeight;
|
|
10465
10641
|
const w = sumWidths(widths.slice(start, start + colSpan));
|
|
10466
10642
|
const h = metrics.headerHeight * rowSpan;
|
|
10467
|
-
paintCellBox(opts.ctx, x, y, w, h, opts.tokens.color.surface, opts.tokens.color.border);
|
|
10643
|
+
paintCellBox(opts.ctx, x, y, w, h, opts.tokens.color.surface, opts.tokens.color.border, false, pen);
|
|
10468
10644
|
paintCellText(opts, {
|
|
10469
10645
|
x,
|
|
10470
10646
|
y,
|
|
@@ -10480,7 +10656,7 @@ function paintHeaderCanvas(opts, widths, rows, metrics) {
|
|
|
10480
10656
|
}
|
|
10481
10657
|
});
|
|
10482
10658
|
}
|
|
10483
|
-
function paintBodyRowCanvas(opts, widths, rowIndex, y, metrics) {
|
|
10659
|
+
function paintBodyRowCanvas(opts, widths, rowIndex, y, metrics, pen) {
|
|
10484
10660
|
const cls = opts.rowClass?.(rowIndex) ?? "normal";
|
|
10485
10661
|
let x = opts.rect.x;
|
|
10486
10662
|
for (let colIndex = 0; colIndex < opts.columns.length; colIndex += 1) {
|
|
@@ -10490,8 +10666,8 @@ function paintBodyRowCanvas(opts, widths, rowIndex, y, metrics) {
|
|
|
10490
10666
|
const baseBg = canvasRowBackground(opts, rowIndex, cls);
|
|
10491
10667
|
const conditional = resolveCanvasConditional(opts, column, colIndex, cell, baseBg);
|
|
10492
10668
|
const bg = conditional.background ?? baseBg;
|
|
10493
|
-
paintCellBox(opts.ctx, x, y, width, metrics.rowHeight, bg, opts.tokens.color.border);
|
|
10494
|
-
if (conditional.bar) paintDataBar(opts, x, y, width, metrics.rowHeight, conditional.bar);
|
|
10669
|
+
paintCellBox(opts.ctx, x, y, width, metrics.rowHeight, bg, opts.tokens.color.border, false, pen);
|
|
10670
|
+
if (conditional.bar) paintDataBar(opts, x, y, width, metrics.rowHeight, conditional.bar, pen);
|
|
10495
10671
|
const indent = opts.cellIndent?.(rowIndex, colIndex) ?? 0;
|
|
10496
10672
|
const text = conditional.showValue === false ? "" : formatDisplayValue(cell.value, column);
|
|
10497
10673
|
const color = negativeTextColor(cell.value, column, conditional.color ?? opts.tokens.color.text);
|
|
@@ -10513,13 +10689,13 @@ function paintBodyRowCanvas(opts, widths, rowIndex, y, metrics) {
|
|
|
10513
10689
|
x += width;
|
|
10514
10690
|
}
|
|
10515
10691
|
}
|
|
10516
|
-
function paintFooterCanvas(opts, widths, y, metrics) {
|
|
10692
|
+
function paintFooterCanvas(opts, widths, y, metrics, pen) {
|
|
10517
10693
|
let x = opts.rect.x;
|
|
10518
10694
|
for (let colIndex = 0; colIndex < opts.columns.length; colIndex += 1) {
|
|
10519
10695
|
const column = opts.columns[colIndex];
|
|
10520
10696
|
const width = widths[colIndex] ?? 0;
|
|
10521
10697
|
const cell = opts.footerRow?.cells[colIndex] ?? { value: null};
|
|
10522
|
-
paintCellBox(opts.ctx, x, y, width, metrics.rowHeight, opts.tokens.color.surface, opts.tokens.color.border, true);
|
|
10698
|
+
paintCellBox(opts.ctx, x, y, width, metrics.rowHeight, opts.tokens.color.surface, opts.tokens.color.border, true, pen);
|
|
10523
10699
|
paintCellText(opts, {
|
|
10524
10700
|
x,
|
|
10525
10701
|
y,
|
|
@@ -10548,10 +10724,25 @@ function paintClippedCueCanvas(opts, y, metrics, remaining) {
|
|
|
10548
10724
|
paddingX: metrics.paddingX
|
|
10549
10725
|
});
|
|
10550
10726
|
}
|
|
10551
|
-
function paintCellBox(ctx, x, y, width, height, background, border, topBorder = false) {
|
|
10727
|
+
function paintCellBox(ctx, x, y, width, height, background, border, topBorder = false, pen) {
|
|
10552
10728
|
ctx.save();
|
|
10553
10729
|
ctx.fillStyle = background;
|
|
10554
10730
|
ctx.fillRect(x, y, width, height);
|
|
10731
|
+
if (pen) {
|
|
10732
|
+
pen.polyline(
|
|
10733
|
+
[
|
|
10734
|
+
{ x: x + width, y },
|
|
10735
|
+
{ x: x + width, y: y + height },
|
|
10736
|
+
{ x, y: y + height }
|
|
10737
|
+
],
|
|
10738
|
+
{ stroke: border, strokeWidth: 1 }
|
|
10739
|
+
);
|
|
10740
|
+
if (topBorder) {
|
|
10741
|
+
pen.polyline([{ x, y }, { x: x + width, y }], { stroke: border, strokeWidth: 1 });
|
|
10742
|
+
}
|
|
10743
|
+
ctx.restore();
|
|
10744
|
+
return;
|
|
10745
|
+
}
|
|
10555
10746
|
ctx.strokeStyle = border;
|
|
10556
10747
|
ctx.lineWidth = 1;
|
|
10557
10748
|
ctx.beginPath();
|
|
@@ -10662,9 +10853,18 @@ function resolveCanvasConditional(opts, column, colIndex, cell, rowBg) {
|
|
|
10662
10853
|
const color = alphaColor(positive ? cf.color ?? opts.tokens.color.accent : cf.negativeColor ?? opts.tokens.color.negative, 0.85);
|
|
10663
10854
|
return { background: rowBg, showValue: cf.showValue !== false, bar: { leftRatio, widthRatio, color } };
|
|
10664
10855
|
}
|
|
10665
|
-
function paintDataBar(opts, x, y, width, height, bar) {
|
|
10856
|
+
function paintDataBar(opts, x, y, width, height, bar, pen) {
|
|
10666
10857
|
const left = x + 4 + bar.leftRatio * Math.max(0, width - 8);
|
|
10667
10858
|
const barW = bar.widthRatio * Math.max(0, width - 8);
|
|
10859
|
+
if (pen) {
|
|
10860
|
+
pen.rect(left, y + 5, barW, Math.max(0, height - 10), {
|
|
10861
|
+
stroke: bar.color,
|
|
10862
|
+
fill: bar.color,
|
|
10863
|
+
fillAlpha: 0.9,
|
|
10864
|
+
strokeAlpha: 0.9
|
|
10865
|
+
});
|
|
10866
|
+
return;
|
|
10867
|
+
}
|
|
10668
10868
|
opts.ctx.save();
|
|
10669
10869
|
opts.ctx.globalAlpha *= 0.9;
|
|
10670
10870
|
opts.ctx.beginPath();
|
|
@@ -10673,6 +10873,72 @@ function paintDataBar(opts, x, y, width, height, bar) {
|
|
|
10673
10873
|
opts.ctx.fill();
|
|
10674
10874
|
opts.ctx.restore();
|
|
10675
10875
|
}
|
|
10876
|
+
function createSketchGridCanvas(rect) {
|
|
10877
|
+
const dpr = getDevicePixelRatio();
|
|
10878
|
+
const w = Math.max(0, rect.width);
|
|
10879
|
+
const h = Math.max(0, rect.height);
|
|
10880
|
+
const canvas = document.createElement("canvas");
|
|
10881
|
+
canvas.width = Math.max(1, Math.round(w * dpr));
|
|
10882
|
+
canvas.height = Math.max(1, Math.round(h * dpr));
|
|
10883
|
+
setStyles(canvas, {
|
|
10884
|
+
position: "absolute",
|
|
10885
|
+
left: `${rect.x}px`,
|
|
10886
|
+
top: `${rect.y}px`,
|
|
10887
|
+
width: `${w}px`,
|
|
10888
|
+
height: `${h}px`,
|
|
10889
|
+
pointerEvents: "none"
|
|
10890
|
+
});
|
|
10891
|
+
return { canvas, ctx: canvas.getContext("2d"), dpr };
|
|
10892
|
+
}
|
|
10893
|
+
function paintSketchGridOverlay(ctx, o) {
|
|
10894
|
+
const w = Math.max(0, o.rect.width);
|
|
10895
|
+
const h = Math.max(0, o.rect.height);
|
|
10896
|
+
ctx.save();
|
|
10897
|
+
ctx.setTransform(o.dpr, 0, 0, o.dpr, 0, 0);
|
|
10898
|
+
ctx.clearRect(0, 0, w, h);
|
|
10899
|
+
const metrics = densityMetrics(o.density);
|
|
10900
|
+
const headerHeight = o.headerRowCount * metrics.headerHeight;
|
|
10901
|
+
const footerHeight = o.hasFooter ? metrics.rowHeight : 0;
|
|
10902
|
+
const bodyTop = headerHeight;
|
|
10903
|
+
const bodyBottom = Math.max(bodyTop, h - footerHeight);
|
|
10904
|
+
const border = o.tokens.color.border;
|
|
10905
|
+
const base = o.sketch.seed >>> 0;
|
|
10906
|
+
const stroke = (x1, y1, x2, y2, seed) => {
|
|
10907
|
+
const pen = new RoughPen(ctx, { ...o.sketch, seed: seed >>> 0 });
|
|
10908
|
+
pen.polyline(
|
|
10909
|
+
[
|
|
10910
|
+
{ x: x1, y: y1 },
|
|
10911
|
+
{ x: x2, y: y2 }
|
|
10912
|
+
],
|
|
10913
|
+
{ stroke: border, strokeWidth: 1 }
|
|
10914
|
+
);
|
|
10915
|
+
};
|
|
10916
|
+
let cursor = 0;
|
|
10917
|
+
for (let i = 0; i < o.columns.length - 1; i += 1) {
|
|
10918
|
+
cursor += o.widths[i] ?? 0;
|
|
10919
|
+
const pinned = i < (o.rowHeaderSpan ?? 0);
|
|
10920
|
+
const x = pinned ? cursor : cursor - o.scrollLeft;
|
|
10921
|
+
if (x <= 0 || x >= w) continue;
|
|
10922
|
+
stroke(x, 0, x, h, base ^ Math.imul(i + 1, 2246822519));
|
|
10923
|
+
}
|
|
10924
|
+
ctx.save();
|
|
10925
|
+
ctx.beginPath();
|
|
10926
|
+
ctx.rect(0, bodyTop, w, Math.max(0, bodyBottom - bodyTop));
|
|
10927
|
+
ctx.clip();
|
|
10928
|
+
const firstRow = Math.max(0, Math.floor(o.scrollTop / metrics.rowHeight) - 1);
|
|
10929
|
+
for (let r = firstRow; r < o.rowCount; r += 1) {
|
|
10930
|
+
const y = bodyTop + (r + 1) * metrics.rowHeight - o.scrollTop;
|
|
10931
|
+
if (y <= bodyTop) continue;
|
|
10932
|
+
if (y >= bodyBottom) break;
|
|
10933
|
+
stroke(0, y, w, y, base ^ Math.imul(r + 1, 3266489917));
|
|
10934
|
+
}
|
|
10935
|
+
ctx.restore();
|
|
10936
|
+
if (headerHeight > 0 && headerHeight < h) stroke(0, headerHeight, w, headerHeight, (base ^ 2654435769) >>> 0);
|
|
10937
|
+
if (footerHeight > 0 && bodyBottom > bodyTop) stroke(0, bodyBottom, w, bodyBottom, (base ^ 2246822507) >>> 0);
|
|
10938
|
+
const framePen = new RoughPen(ctx, { ...o.sketch, seed: base });
|
|
10939
|
+
framePen.rect(1.5, 1.5, Math.max(0, w - 3), Math.max(0, h - 3), { stroke: border, strokeWidth: 1 });
|
|
10940
|
+
ctx.restore();
|
|
10941
|
+
}
|
|
10676
10942
|
function canvasRowBackground(opts, rowIndex, cls) {
|
|
10677
10943
|
if (cls !== "normal") return opts.tokens.color.surface;
|
|
10678
10944
|
if (opts.striped === true && rowIndex % 2 === 1) return subtleStripe(opts.tokens);
|
|
@@ -10775,7 +11041,7 @@ function drawTableCanvas(surface, spec, tokens, size) {
|
|
|
10775
11041
|
headerRows: buildTableHeaderRows(columns),
|
|
10776
11042
|
footerRow: buildTotalsRow(sortedRows, columns, read, spec),
|
|
10777
11043
|
density: spec.density,
|
|
10778
|
-
sketch: resolveSketch(spec)
|
|
11044
|
+
sketch: resolveSketch(spec)
|
|
10779
11045
|
});
|
|
10780
11046
|
}
|
|
10781
11047
|
function renderTable(surface, spec, tokens, size) {
|
|
@@ -10783,7 +11049,7 @@ function renderTable(surface, spec, tokens, size) {
|
|
|
10783
11049
|
const data = spec.data ?? [];
|
|
10784
11050
|
const columns = resolveColumns(data, spec);
|
|
10785
11051
|
const read = columns.map((column) => accessor(column.key));
|
|
10786
|
-
const sketch = resolveSketch(spec)
|
|
11052
|
+
const sketch = resolveSketch(spec);
|
|
10787
11053
|
let sortState = initialSortState(spec, columns);
|
|
10788
11054
|
let sortedRows = sortRows(data, columns, read, sortState);
|
|
10789
11055
|
const domains = columns.map(
|
|
@@ -10794,6 +11060,9 @@ function renderTable(surface, spec, tokens, size) {
|
|
|
10794
11060
|
);
|
|
10795
11061
|
const host = document.createElement("div");
|
|
10796
11062
|
surface.overlay.appendChild(host);
|
|
11063
|
+
const headerRowCount = buildTableHeaderRows(columns)?.length ?? 1;
|
|
11064
|
+
const overlay = sketch ? createSketchGridCanvas(rect) : null;
|
|
11065
|
+
if (overlay) surface.overlay.appendChild(overlay.canvas);
|
|
10797
11066
|
let frame = 0;
|
|
10798
11067
|
const renderWindow = () => {
|
|
10799
11068
|
const scrollTop = host.scrollTop || 0;
|
|
@@ -10801,7 +11070,8 @@ function renderTable(surface, spec, tokens, size) {
|
|
|
10801
11070
|
const start = Math.max(0, Math.floor(scrollTop / rowHeight) - BUFFER_ROWS);
|
|
10802
11071
|
const visible = Math.ceil(Math.max(rect.height, host.clientHeight || rect.height) / rowHeight) + BUFFER_ROWS * 2;
|
|
10803
11072
|
const end = Math.min(sortedRows.length, start + visible);
|
|
10804
|
-
|
|
11073
|
+
const footerRow = buildTotalsRow(sortedRows, columns, read, spec);
|
|
11074
|
+
const buildOpts = {
|
|
10805
11075
|
container: host,
|
|
10806
11076
|
tokens,
|
|
10807
11077
|
rect,
|
|
@@ -10822,11 +11092,28 @@ function renderTable(surface, spec, tokens, size) {
|
|
|
10822
11092
|
sortState: sortState ?? void 0,
|
|
10823
11093
|
conditionalDomains: domains,
|
|
10824
11094
|
headerRows: buildTableHeaderRows(columns),
|
|
10825
|
-
footerRow
|
|
11095
|
+
footerRow,
|
|
10826
11096
|
density: spec.density,
|
|
10827
11097
|
visibleRange: { start, end, rowHeight },
|
|
10828
11098
|
sketch
|
|
10829
|
-
}
|
|
11099
|
+
};
|
|
11100
|
+
buildTable(buildOpts);
|
|
11101
|
+
if (sketch && overlay?.ctx) {
|
|
11102
|
+
paintSketchGridOverlay(overlay.ctx, {
|
|
11103
|
+
sketch,
|
|
11104
|
+
tokens,
|
|
11105
|
+
rect,
|
|
11106
|
+
dpr: overlay.dpr,
|
|
11107
|
+
columns,
|
|
11108
|
+
widths: buildOpts.widths ?? [],
|
|
11109
|
+
rowCount: sortedRows.length,
|
|
11110
|
+
headerRowCount,
|
|
11111
|
+
hasFooter: Boolean(footerRow),
|
|
11112
|
+
density: spec.density,
|
|
11113
|
+
scrollLeft: host.scrollLeft || 0,
|
|
11114
|
+
scrollTop
|
|
11115
|
+
});
|
|
11116
|
+
}
|
|
10830
11117
|
};
|
|
10831
11118
|
host.addEventListener("scroll", () => {
|
|
10832
11119
|
if (frame !== 0) return;
|
|
@@ -10983,15 +11270,16 @@ function drawMatrixCanvas(surface, spec, tokens, size) {
|
|
|
10983
11270
|
conditionalDomains: prepared.bodyDomains,
|
|
10984
11271
|
headerRows: prepared.headerRows,
|
|
10985
11272
|
density: spec.density,
|
|
10986
|
-
sketch: resolveSketch(spec)
|
|
11273
|
+
sketch: resolveSketch(spec)
|
|
10987
11274
|
});
|
|
10988
11275
|
}
|
|
10989
11276
|
function renderMatrix(surface, spec, tokens, size) {
|
|
10990
11277
|
const rect = drawTitleBlock(surface, tokens, size, spec.title);
|
|
10991
11278
|
const prepared = prepareMatrix(spec);
|
|
11279
|
+
const sketch = resolveSketch(spec);
|
|
10992
11280
|
const host = document.createElement("div");
|
|
10993
11281
|
surface.overlay.appendChild(host);
|
|
10994
|
-
|
|
11282
|
+
const buildOpts = {
|
|
10995
11283
|
container: host,
|
|
10996
11284
|
tokens,
|
|
10997
11285
|
rect,
|
|
@@ -11018,8 +11306,45 @@ function renderMatrix(surface, spec, tokens, size) {
|
|
|
11018
11306
|
conditionalDomains: prepared.bodyDomains,
|
|
11019
11307
|
headerRows: prepared.headerRows,
|
|
11020
11308
|
density: spec.density,
|
|
11021
|
-
sketch
|
|
11022
|
-
}
|
|
11309
|
+
sketch
|
|
11310
|
+
};
|
|
11311
|
+
buildTable(buildOpts);
|
|
11312
|
+
if (sketch) {
|
|
11313
|
+
const overlay = createSketchGridCanvas(rect);
|
|
11314
|
+
if (overlay.ctx) {
|
|
11315
|
+
surface.overlay.appendChild(overlay.canvas);
|
|
11316
|
+
const ctx = overlay.ctx;
|
|
11317
|
+
const draw = () => {
|
|
11318
|
+
paintSketchGridOverlay(ctx, {
|
|
11319
|
+
sketch,
|
|
11320
|
+
tokens,
|
|
11321
|
+
rect,
|
|
11322
|
+
dpr: overlay.dpr,
|
|
11323
|
+
columns: prepared.columns,
|
|
11324
|
+
widths: buildOpts.widths ?? [],
|
|
11325
|
+
rowCount: prepared.rows.length,
|
|
11326
|
+
headerRowCount: prepared.headerRows.length,
|
|
11327
|
+
hasFooter: false,
|
|
11328
|
+
rowHeaderSpan: 1,
|
|
11329
|
+
density: spec.density,
|
|
11330
|
+
scrollLeft: host.scrollLeft || 0,
|
|
11331
|
+
scrollTop: host.scrollTop || 0
|
|
11332
|
+
});
|
|
11333
|
+
};
|
|
11334
|
+
draw();
|
|
11335
|
+
let frame = 0;
|
|
11336
|
+
host.addEventListener("scroll", () => {
|
|
11337
|
+
if (frame !== 0) return;
|
|
11338
|
+
frame = typeof requestAnimationFrame === "function" ? requestAnimationFrame(() => {
|
|
11339
|
+
frame = 0;
|
|
11340
|
+
draw();
|
|
11341
|
+
}) : window.setTimeout(() => {
|
|
11342
|
+
frame = 0;
|
|
11343
|
+
draw();
|
|
11344
|
+
}, 16);
|
|
11345
|
+
});
|
|
11346
|
+
}
|
|
11347
|
+
}
|
|
11023
11348
|
}
|
|
11024
11349
|
function prepareMatrix(spec) {
|
|
11025
11350
|
const data = spec.data ?? [];
|
|
@@ -12454,6 +12779,7 @@ function indexInteraction(model) {
|
|
|
12454
12779
|
const xFormat = model.spec.encoding.x?.format;
|
|
12455
12780
|
const yFormat = model.spec.encoding.y?.format;
|
|
12456
12781
|
const band = x.kind === "band";
|
|
12782
|
+
const horizontal = model.orientation === "horizontal";
|
|
12457
12783
|
const showDots = !model.stacked && !band;
|
|
12458
12784
|
const stopByKey = /* @__PURE__ */ new Map();
|
|
12459
12785
|
const lookups = [];
|
|
@@ -12488,7 +12814,7 @@ function indexInteraction(model) {
|
|
|
12488
12814
|
};
|
|
12489
12815
|
const hitTest = (px, py) => {
|
|
12490
12816
|
if (!inside(plot, px, py, 6)) return null;
|
|
12491
|
-
const best = nearestStop(px);
|
|
12817
|
+
const best = nearestStop(horizontal ? py : px);
|
|
12492
12818
|
const rows = [];
|
|
12493
12819
|
const dots = [];
|
|
12494
12820
|
let nearestDy = Infinity;
|
|
@@ -12516,15 +12842,19 @@ function indexInteraction(model) {
|
|
|
12516
12842
|
const anchorY = showDots && nearestIdx >= 0 ? dots[nearestIdx].py : py;
|
|
12517
12843
|
return {
|
|
12518
12844
|
key: best.key,
|
|
12519
|
-
anchorX: best.px,
|
|
12520
|
-
anchorY,
|
|
12845
|
+
anchorX: horizontal ? px : best.px,
|
|
12846
|
+
anchorY: horizontal ? best.px : anchorY,
|
|
12521
12847
|
content: { title: formatValue(best.value, xFormat), rows },
|
|
12522
12848
|
draw: (ctx) => {
|
|
12523
12849
|
ctx.save();
|
|
12524
12850
|
if (band) {
|
|
12525
12851
|
const half = x.bandwidth / 2;
|
|
12526
12852
|
ctx.fillStyle = withAlpha(model.tokens.color.text, 0.06);
|
|
12527
|
-
|
|
12853
|
+
if (horizontal) {
|
|
12854
|
+
ctx.fillRect(plot.x, best.px - half, plot.width, x.bandwidth);
|
|
12855
|
+
} else {
|
|
12856
|
+
ctx.fillRect(best.px - half, plot.y, x.bandwidth, plot.height);
|
|
12857
|
+
}
|
|
12528
12858
|
} else {
|
|
12529
12859
|
ctx.strokeStyle = withAlpha(model.tokens.color.text, 0.28);
|
|
12530
12860
|
ctx.lineWidth = 1;
|
|
@@ -12553,7 +12883,7 @@ function indexInteraction(model) {
|
|
|
12553
12883
|
};
|
|
12554
12884
|
const pick = (px, py) => {
|
|
12555
12885
|
if (!inside(plot, px, py, 6)) return null;
|
|
12556
|
-
const best = nearestStop(px);
|
|
12886
|
+
const best = nearestStop(horizontal ? py : px);
|
|
12557
12887
|
return { kind: "point", fields: [xField], tuples: [[best.value]] };
|
|
12558
12888
|
};
|
|
12559
12889
|
return { region: plot, hitTest, pick };
|
|
@@ -12920,6 +13250,8 @@ function buildComboModel(spec, tokens, opts) {
|
|
|
12920
13250
|
series: [{ key: "", label: name, color, value: name, rows: data }],
|
|
12921
13251
|
seriesField: void 0,
|
|
12922
13252
|
stacked: false,
|
|
13253
|
+
orientation: "vertical",
|
|
13254
|
+
project: (c, v) => ({ x: c, y: v }),
|
|
12923
13255
|
xTicks,
|
|
12924
13256
|
yTicks: sideTicks,
|
|
12925
13257
|
colorOf: () => color,
|
|
@@ -12947,6 +13279,8 @@ function buildComboModel(spec, tokens, opts) {
|
|
|
12947
13279
|
series: [],
|
|
12948
13280
|
seriesField: void 0,
|
|
12949
13281
|
stacked: false,
|
|
13282
|
+
orientation: "vertical",
|
|
13283
|
+
project: (c, v) => ({ x: c, y: v }),
|
|
12950
13284
|
xTicks,
|
|
12951
13285
|
yTicks: leftTicks,
|
|
12952
13286
|
colorOf: () => "",
|
|
@@ -13184,6 +13518,8 @@ function buildHistogramModel(spec, tokens, opts) {
|
|
|
13184
13518
|
series: [],
|
|
13185
13519
|
seriesField: void 0,
|
|
13186
13520
|
stacked: false,
|
|
13521
|
+
orientation: "vertical",
|
|
13522
|
+
project: (c, v) => ({ x: c, y: v }),
|
|
13187
13523
|
xTicks,
|
|
13188
13524
|
yTicks,
|
|
13189
13525
|
colorOf: () => color,
|
|
@@ -14387,6 +14723,8 @@ function buildWaterfallModel(spec, tokens, opts) {
|
|
|
14387
14723
|
series: [],
|
|
14388
14724
|
seriesField: void 0,
|
|
14389
14725
|
stacked: false,
|
|
14726
|
+
orientation: "vertical",
|
|
14727
|
+
project: (c, v) => ({ x: c, y: v }),
|
|
14390
14728
|
xTicks,
|
|
14391
14729
|
yTicks,
|
|
14392
14730
|
colorOf: () => incColor,
|
|
@@ -14648,6 +14986,8 @@ function buildSlopeModel(spec, tokens, opts) {
|
|
|
14648
14986
|
series: [],
|
|
14649
14987
|
seriesField: void 0,
|
|
14650
14988
|
stacked: false,
|
|
14989
|
+
orientation: "vertical",
|
|
14990
|
+
project: (c, v) => ({ x: c, y: v }),
|
|
14651
14991
|
xTicks,
|
|
14652
14992
|
yTicks,
|
|
14653
14993
|
colorOf: () => tokens.color.text,
|
|
@@ -15122,9 +15462,14 @@ function toPixel(model, axis, value) {
|
|
|
15122
15462
|
return px != null && Number.isFinite(px) ? px : void 0;
|
|
15123
15463
|
}
|
|
15124
15464
|
function pointPixels(model, ann) {
|
|
15125
|
-
const
|
|
15126
|
-
const
|
|
15127
|
-
|
|
15465
|
+
const cat = toPixel(model, "x", ann.x);
|
|
15466
|
+
const val = toPixel(model, "y", ann.y);
|
|
15467
|
+
if (cat === void 0 || val === void 0) return void 0;
|
|
15468
|
+
const { x, y } = model.project(cat, val);
|
|
15469
|
+
return { px: x, py: y };
|
|
15470
|
+
}
|
|
15471
|
+
function drawsHorizontalRule(model, axis) {
|
|
15472
|
+
return model.orientation === "horizontal" ? axis === "x" : axis === "y";
|
|
15128
15473
|
}
|
|
15129
15474
|
function anchorPixel(model, ann, axis, kind) {
|
|
15130
15475
|
if (kind === "line") return toPixel(model, axis, ann.value);
|
|
@@ -15173,6 +15518,7 @@ function drawAnnotations(surface, model) {
|
|
|
15173
15518
|
}
|
|
15174
15519
|
const axis = ann.axis === "x" ? "x" : "y";
|
|
15175
15520
|
const color = ann.color ?? tokens.color.textMuted;
|
|
15521
|
+
const ruleHorizontal = drawsHorizontalRule(model, axis);
|
|
15176
15522
|
if (kindOf(ann) === "line") {
|
|
15177
15523
|
const p = toPixel(model, axis, ann.value);
|
|
15178
15524
|
if (p === void 0) continue;
|
|
@@ -15181,7 +15527,7 @@ function drawAnnotations(surface, model) {
|
|
|
15181
15527
|
ctx.lineWidth = ann.strokeWidth ?? 1.5;
|
|
15182
15528
|
ctx.setLineDash(ann.strokeDash ?? DEFAULT_DASH);
|
|
15183
15529
|
ctx.beginPath();
|
|
15184
|
-
if (
|
|
15530
|
+
if (ruleHorizontal) {
|
|
15185
15531
|
const y = crisp(p);
|
|
15186
15532
|
ctx.moveTo(plot.x, y);
|
|
15187
15533
|
ctx.lineTo(x1, y);
|
|
@@ -15202,7 +15548,7 @@ function drawAnnotations(surface, model) {
|
|
|
15202
15548
|
ctx.save();
|
|
15203
15549
|
ctx.globalAlpha = ann.fillOpacity ?? DEFAULT_FILL_OPACITY;
|
|
15204
15550
|
ctx.fillStyle = color;
|
|
15205
|
-
if (
|
|
15551
|
+
if (ruleHorizontal) ctx.fillRect(plot.x, lo, plot.width, hi - lo);
|
|
15206
15552
|
else ctx.fillRect(lo, plot.y, hi - lo, plot.height);
|
|
15207
15553
|
ctx.restore();
|
|
15208
15554
|
ctx.save();
|
|
@@ -15211,7 +15557,7 @@ function drawAnnotations(surface, model) {
|
|
|
15211
15557
|
ctx.lineWidth = 1;
|
|
15212
15558
|
ctx.setLineDash(ann.strokeDash ?? [4, 4]);
|
|
15213
15559
|
ctx.beginPath();
|
|
15214
|
-
if (
|
|
15560
|
+
if (ruleHorizontal) {
|
|
15215
15561
|
ctx.moveTo(plot.x, crisp(lo));
|
|
15216
15562
|
ctx.lineTo(x1, crisp(lo));
|
|
15217
15563
|
ctx.moveTo(plot.x, crisp(hi));
|
|
@@ -15227,10 +15573,10 @@ function drawAnnotations(surface, model) {
|
|
|
15227
15573
|
}
|
|
15228
15574
|
ctx.restore();
|
|
15229
15575
|
}
|
|
15230
|
-
function labelBox(model,
|
|
15576
|
+
function labelBox(model, horizontalRule, pos, p) {
|
|
15231
15577
|
const { plot } = model;
|
|
15232
15578
|
const inset = 6;
|
|
15233
|
-
if (
|
|
15579
|
+
if (horizontalRule) {
|
|
15234
15580
|
const top = p - 3;
|
|
15235
15581
|
const transform3 = "translateY(-100%)";
|
|
15236
15582
|
if (pos === "start") return { left: plot.x + inset, top, width: plot.width - inset, align: "left", transform: transform3 };
|
|
@@ -15266,7 +15612,7 @@ function drawAnnotationLabels(surface, model) {
|
|
|
15266
15612
|
const axis = ann.axis === "x" ? "x" : "y";
|
|
15267
15613
|
const p = anchorPixel(model, ann, axis, kindOf(ann));
|
|
15268
15614
|
if (p === void 0) continue;
|
|
15269
|
-
box = labelBox(model, axis, ann.labelPosition ?? "end", p);
|
|
15615
|
+
box = labelBox(model, drawsHorizontalRule(model, axis), ann.labelPosition ?? "end", p);
|
|
15270
15616
|
}
|
|
15271
15617
|
const color = ann.color ?? tokens.color.text;
|
|
15272
15618
|
const pill = { background: withAlpha4(tokens.color.background, 0.72), radius: 3, padX: 3, padY: 1 };
|
|
@@ -16087,7 +16433,8 @@ function checkClippedMarks(model, spec, add) {
|
|
|
16087
16433
|
const pHi = model.y.pixel(hi);
|
|
16088
16434
|
const pLo = model.y.pixel(lo);
|
|
16089
16435
|
const EPS = 0.75;
|
|
16090
|
-
|
|
16436
|
+
const clipped = model.orientation === "horizontal" ? pLo < model.plot.x - EPS || pHi > model.plot.x + model.plot.width + EPS : pHi < top - EPS || pLo > bottom + EPS;
|
|
16437
|
+
if (clipped) {
|
|
16091
16438
|
add({
|
|
16092
16439
|
code: "marks-clipped",
|
|
16093
16440
|
severity: "warning",
|
|
@@ -16103,7 +16450,8 @@ function checkClippedMarks(model, spec, add) {
|
|
|
16103
16450
|
function checkAxisLabelOverlap(model, spec, tokens, add) {
|
|
16104
16451
|
const ticks2 = model.xTicks;
|
|
16105
16452
|
if (ticks2.length < 2) return;
|
|
16106
|
-
|
|
16453
|
+
const horizontal = model.orientation === "horizontal";
|
|
16454
|
+
if (!horizontal && model.frame.xLabelAngle > 0) return;
|
|
16107
16455
|
const font = fontString(tokens.font.size.small, tokens.font.family, tokens.font.weight.normal);
|
|
16108
16456
|
const GAP3 = 4;
|
|
16109
16457
|
let collisions = 0;
|
|
@@ -16111,7 +16459,7 @@ function checkAxisLabelOverlap(model, spec, tokens, add) {
|
|
|
16111
16459
|
const prev = ticks2[i - 1];
|
|
16112
16460
|
const cur = ticks2[i];
|
|
16113
16461
|
const spacing = Math.abs(cur.pos - prev.pos);
|
|
16114
|
-
const half = (measureText(prev.label, font).width + measureText(cur.label, font).width) / 2;
|
|
16462
|
+
const half = horizontal ? tokens.font.size.small : (measureText(prev.label, font).width + measureText(cur.label, font).width) / 2;
|
|
16115
16463
|
if (half + GAP3 > spacing) collisions++;
|
|
16116
16464
|
}
|
|
16117
16465
|
if (collisions > 0) {
|