@opendata-ai/openchart-engine 6.25.4 → 6.27.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 +82 -4
- package/dist/index.js +1027 -76
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__test-fixtures__/specs.ts +33 -0
- package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +6 -0
- package/src/__tests__/compile-chart.test.ts +301 -0
- package/src/__tests__/compound-labels.test.ts +147 -0
- package/src/charts/line/area.ts +1 -1
- package/src/charts/line/compute.ts +7 -1
- package/src/compile.ts +222 -17
- package/src/compiler/normalize.ts +83 -1
- package/src/compiler/types.ts +41 -1
- package/src/compiler/validate.ts +124 -5
- package/src/index.ts +16 -1
- package/src/layout/axes/ticks.ts +34 -2
- package/src/layout/axes.ts +36 -3
- package/src/layout/dimensions.ts +98 -5
- package/src/legend/compute.ts +6 -1
- package/src/sankey/compile-sankey.ts +1 -1
- package/src/tilemap/__tests__/compile-tilemap.test.ts +322 -0
- package/src/tilemap/compile-tilemap.ts +383 -0
- package/src/tilemap/layout.ts +172 -0
- package/src/tilemap/types.ts +32 -0
- package/src/transforms/__tests__/filter-relative.test.ts +202 -0
- package/src/transforms/__tests__/window.test.ts +286 -0
- package/src/transforms/filter.ts +108 -3
- package/src/transforms/index.ts +5 -1
- package/src/transforms/predicates.ts +39 -9
- package/src/transforms/window.ts +185 -0
package/dist/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// src/compile.ts
|
|
2
2
|
import {
|
|
3
3
|
AXIS_TITLE_TRAILING_PAD as AXIS_TITLE_TRAILING_PAD2,
|
|
4
|
-
adaptTheme as
|
|
4
|
+
adaptTheme as adaptTheme4,
|
|
5
5
|
BREAKPOINT_COMPACT_MAX as BREAKPOINT_COMPACT_MAX2,
|
|
6
6
|
computeLabelBounds,
|
|
7
|
-
estimateTextWidth as
|
|
7
|
+
estimateTextWidth as estimateTextWidth15,
|
|
8
8
|
generateAltText,
|
|
9
9
|
generateDataTable,
|
|
10
10
|
getAxisTitleOffset as getAxisTitleOffset2,
|
|
11
11
|
getBreakpoint,
|
|
12
12
|
getHeightClass,
|
|
13
13
|
getLayoutStrategy,
|
|
14
|
-
resolveTheme as
|
|
14
|
+
resolveTheme as resolveTheme4,
|
|
15
15
|
TICK_LABEL_OFFSET
|
|
16
16
|
} from "@opendata-ai/openchart-core";
|
|
17
17
|
|
|
@@ -4115,6 +4115,12 @@ import { isGradientDef } from "@opendata-ai/openchart-core";
|
|
|
4115
4115
|
function isFieldPredicate(pred) {
|
|
4116
4116
|
return "field" in pred;
|
|
4117
4117
|
}
|
|
4118
|
+
function isRelativeTimeRef(value2) {
|
|
4119
|
+
return typeof value2 === "object" && value2 !== null && "anchor" in value2 && "offset" in value2 && "unit" in value2;
|
|
4120
|
+
}
|
|
4121
|
+
function toNum(v) {
|
|
4122
|
+
return typeof v === "number" ? v : NaN;
|
|
4123
|
+
}
|
|
4118
4124
|
function evaluateFieldPredicate(datum, pred) {
|
|
4119
4125
|
const value2 = datum[pred.field];
|
|
4120
4126
|
if (pred.valid !== void 0) {
|
|
@@ -4124,22 +4130,26 @@ function evaluateFieldPredicate(datum, pred) {
|
|
|
4124
4130
|
if (pred.equal !== void 0) {
|
|
4125
4131
|
return value2 == pred.equal;
|
|
4126
4132
|
}
|
|
4127
|
-
|
|
4133
|
+
let numValue = Number(value2);
|
|
4134
|
+
if (Number.isNaN(numValue) && value2 != null) {
|
|
4135
|
+
const ms = new Date(value2).getTime();
|
|
4136
|
+
if (!Number.isNaN(ms)) numValue = ms;
|
|
4137
|
+
}
|
|
4128
4138
|
if (pred.lt !== void 0) {
|
|
4129
|
-
return numValue < pred.lt;
|
|
4139
|
+
return numValue < toNum(pred.lt);
|
|
4130
4140
|
}
|
|
4131
4141
|
if (pred.lte !== void 0) {
|
|
4132
|
-
return numValue <= pred.lte;
|
|
4142
|
+
return numValue <= toNum(pred.lte);
|
|
4133
4143
|
}
|
|
4134
4144
|
if (pred.gt !== void 0) {
|
|
4135
|
-
return numValue > pred.gt;
|
|
4145
|
+
return numValue > toNum(pred.gt);
|
|
4136
4146
|
}
|
|
4137
4147
|
if (pred.gte !== void 0) {
|
|
4138
|
-
return numValue >= pred.gte;
|
|
4148
|
+
return numValue >= toNum(pred.gte);
|
|
4139
4149
|
}
|
|
4140
4150
|
if (pred.range !== void 0) {
|
|
4141
|
-
const [
|
|
4142
|
-
return numValue >=
|
|
4151
|
+
const [lo, hi] = pred.range;
|
|
4152
|
+
return numValue >= toNum(lo) && numValue <= toNum(hi);
|
|
4143
4153
|
}
|
|
4144
4154
|
if (pred.oneOf !== void 0) {
|
|
4145
4155
|
return pred.oneOf.some((v) => v == value2);
|
|
@@ -4356,16 +4366,16 @@ function computeStackedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
4356
4366
|
}
|
|
4357
4367
|
let cumulativeValue = stackMode === "center" ? -categoryTotal / 2 : 0;
|
|
4358
4368
|
for (const row of rows) {
|
|
4359
|
-
const
|
|
4369
|
+
const groupKey3 = String(row[colorField] ?? "");
|
|
4360
4370
|
const rawValue = Number(row[valueField] ?? 0);
|
|
4361
4371
|
if (!Number.isFinite(rawValue) || rawValue <= 0) continue;
|
|
4362
4372
|
const value2 = stackMode === "normalize" && categoryTotal > 0 ? rawValue / categoryTotal : rawValue;
|
|
4363
|
-
const color2 = getColor(scales,
|
|
4373
|
+
const color2 = getColor(scales, groupKey3);
|
|
4364
4374
|
const xLeft = xScale(cumulativeValue);
|
|
4365
4375
|
const xRight = xScale(cumulativeValue + value2);
|
|
4366
4376
|
const barWidth = Math.max(Math.abs(xRight - xLeft), MIN_BAR_WIDTH);
|
|
4367
4377
|
const aria = {
|
|
4368
|
-
label: `${category}, ${
|
|
4378
|
+
label: `${category}, ${groupKey3}: ${formatLabelValue(rawValue)}`
|
|
4369
4379
|
};
|
|
4370
4380
|
marks.push({
|
|
4371
4381
|
type: "rect",
|
|
@@ -4403,16 +4413,16 @@ function computeGroupedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
4403
4413
|
const bandY = yScale(category);
|
|
4404
4414
|
if (bandY === void 0) continue;
|
|
4405
4415
|
for (const row of rows) {
|
|
4406
|
-
const
|
|
4416
|
+
const groupKey3 = String(row[colorField] ?? "");
|
|
4407
4417
|
const value2 = Number(row[valueField] ?? 0);
|
|
4408
4418
|
if (!Number.isFinite(value2)) continue;
|
|
4409
|
-
const groupIndex = groupIndexMap.get(
|
|
4410
|
-
const color2 = getColor(scales,
|
|
4419
|
+
const groupIndex = groupIndexMap.get(groupKey3) ?? 0;
|
|
4420
|
+
const color2 = getColor(scales, groupKey3);
|
|
4411
4421
|
const xPos = value2 >= 0 ? baseline : xScale(value2);
|
|
4412
4422
|
const barWidth = Math.max(Math.abs(xScale(value2) - baseline), MIN_BAR_WIDTH);
|
|
4413
4423
|
const subY = bandY + groupIndex * (subBandHeight + gap);
|
|
4414
4424
|
const aria = {
|
|
4415
|
-
label: `${category}, ${
|
|
4425
|
+
label: `${category}, ${groupKey3}: ${formatLabelValue(value2)}`
|
|
4416
4426
|
};
|
|
4417
4427
|
marks.push({
|
|
4418
4428
|
type: "rect",
|
|
@@ -4438,12 +4448,12 @@ function computeColoredBars(data, valueField, categoryField, colorField, xScale,
|
|
|
4438
4448
|
if (!Number.isFinite(value2)) continue;
|
|
4439
4449
|
const bandY = yScale(category);
|
|
4440
4450
|
if (bandY === void 0) continue;
|
|
4441
|
-
const
|
|
4442
|
-
const color2 = getColor(scales,
|
|
4451
|
+
const groupKey3 = String(row[colorField] ?? "");
|
|
4452
|
+
const color2 = getColor(scales, groupKey3);
|
|
4443
4453
|
const xPos = value2 >= 0 ? baseline : xScale(value2);
|
|
4444
4454
|
const barWidth = Math.max(Math.abs(xScale(value2) - baseline), MIN_BAR_WIDTH);
|
|
4445
4455
|
const aria = {
|
|
4446
|
-
label: `${category}, ${
|
|
4456
|
+
label: `${category}, ${groupKey3}: ${formatLabelValue(value2)}`
|
|
4447
4457
|
};
|
|
4448
4458
|
marks.push({
|
|
4449
4459
|
type: "rect",
|
|
@@ -4810,13 +4820,13 @@ function computeColoredColumns(data, categoryField, valueField, colorField, xSca
|
|
|
4810
4820
|
if (!Number.isFinite(value2)) continue;
|
|
4811
4821
|
const bandX = xScale(category);
|
|
4812
4822
|
if (bandX === void 0) continue;
|
|
4813
|
-
const
|
|
4814
|
-
const color2 = getColor(scales,
|
|
4823
|
+
const groupKey3 = String(row[colorField] ?? "");
|
|
4824
|
+
const color2 = getColor(scales, groupKey3);
|
|
4815
4825
|
const yPos = yScale(value2);
|
|
4816
4826
|
const columnHeight = Math.max(Math.abs(baseline - yPos), MIN_COLUMN_HEIGHT);
|
|
4817
4827
|
const y2 = value2 >= 0 ? yPos : baseline;
|
|
4818
4828
|
const aria = {
|
|
4819
|
-
label: `${category}, ${
|
|
4829
|
+
label: `${category}, ${groupKey3}: ${formatLabelValue(value2)}`
|
|
4820
4830
|
};
|
|
4821
4831
|
marks.push({
|
|
4822
4832
|
type: "rect",
|
|
@@ -4854,17 +4864,17 @@ function computeGroupedColumns(data, categoryField, valueField, colorField, xSca
|
|
|
4854
4864
|
const bandX = xScale(category);
|
|
4855
4865
|
if (bandX === void 0) continue;
|
|
4856
4866
|
for (const row of rows) {
|
|
4857
|
-
const
|
|
4867
|
+
const groupKey3 = String(row[colorField] ?? "");
|
|
4858
4868
|
const value2 = Number(row[valueField] ?? 0);
|
|
4859
4869
|
if (!Number.isFinite(value2)) continue;
|
|
4860
|
-
const groupIndex = groupIndexMap.get(
|
|
4861
|
-
const color2 = getColor(scales,
|
|
4870
|
+
const groupIndex = groupIndexMap.get(groupKey3) ?? 0;
|
|
4871
|
+
const color2 = getColor(scales, groupKey3);
|
|
4862
4872
|
const yPos = yScale(value2);
|
|
4863
4873
|
const columnHeight = Math.max(Math.abs(baseline - yPos), MIN_COLUMN_HEIGHT);
|
|
4864
4874
|
const y2 = value2 >= 0 ? yPos : baseline;
|
|
4865
4875
|
const subX = bandX + groupIndex * (subBandWidth + gap);
|
|
4866
4876
|
const aria = {
|
|
4867
|
-
label: `${category}, ${
|
|
4877
|
+
label: `${category}, ${groupKey3}: ${formatLabelValue(value2)}`
|
|
4868
4878
|
};
|
|
4869
4879
|
marks.push({
|
|
4870
4880
|
type: "rect",
|
|
@@ -4895,16 +4905,16 @@ function computeStackedColumns(data, categoryField, valueField, colorField, xSca
|
|
|
4895
4905
|
}
|
|
4896
4906
|
let cumulativeValue = stackMode === "center" ? -categoryTotal / 2 : 0;
|
|
4897
4907
|
for (const row of rows) {
|
|
4898
|
-
const
|
|
4908
|
+
const groupKey3 = String(row[colorField] ?? "");
|
|
4899
4909
|
const rawValue = Number(row[valueField] ?? 0);
|
|
4900
4910
|
if (!Number.isFinite(rawValue) || rawValue <= 0) continue;
|
|
4901
4911
|
const value2 = stackMode === "normalize" && categoryTotal > 0 ? rawValue / categoryTotal : rawValue;
|
|
4902
|
-
const color2 = getColor(scales,
|
|
4912
|
+
const color2 = getColor(scales, groupKey3);
|
|
4903
4913
|
const yTop = yScale(cumulativeValue + value2);
|
|
4904
4914
|
const yBottom = yScale(cumulativeValue);
|
|
4905
4915
|
const columnHeight = Math.max(Math.abs(yBottom - yTop), MIN_COLUMN_HEIGHT);
|
|
4906
4916
|
const aria = {
|
|
4907
|
-
label: `${category}, ${
|
|
4917
|
+
label: `${category}, ${groupKey3}: ${formatLabelValue(rawValue)}`
|
|
4908
4918
|
};
|
|
4909
4919
|
marks.push({
|
|
4910
4920
|
type: "rect",
|
|
@@ -5341,7 +5351,7 @@ function computeSingleArea(spec, scales, _chartArea) {
|
|
|
5341
5351
|
fill: fillValue,
|
|
5342
5352
|
fillOpacity,
|
|
5343
5353
|
stroke: getRepresentativeColor4(isGradientDef3(fillValue) ? color2 : fillValue),
|
|
5344
|
-
strokeWidth: 2,
|
|
5354
|
+
strokeWidth: spec.display === "sparkline" ? 1.25 : 2,
|
|
5345
5355
|
seriesKey: seriesKey === "__default__" ? void 0 : seriesKey,
|
|
5346
5356
|
data: validPoints.map((p) => p.row),
|
|
5347
5357
|
dataPoints: validPoints.map((p) => ({ x: p.x, y: p.yTop, datum: p.row })),
|
|
@@ -5458,6 +5468,7 @@ function computeAreaMarks(spec, scales, chartArea) {
|
|
|
5458
5468
|
// src/charts/line/compute.ts
|
|
5459
5469
|
import { getRepresentativeColor as getRepresentativeColor5 } from "@opendata-ai/openchart-core";
|
|
5460
5470
|
var DEFAULT_STROKE_WIDTH = 2.5;
|
|
5471
|
+
var SPARKLINE_STROKE_WIDTH = 1.25;
|
|
5461
5472
|
var DEFAULT_POINT_RADIUS = 3;
|
|
5462
5473
|
function computeLineMarks(spec, scales, _chartArea, _strategy) {
|
|
5463
5474
|
const encoding = spec.encoding;
|
|
@@ -5524,7 +5535,7 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
|
|
|
5524
5535
|
points: allPoints,
|
|
5525
5536
|
path: combinedPath,
|
|
5526
5537
|
stroke: strokeColor,
|
|
5527
|
-
strokeWidth: styleOverride?.strokeWidth ?? DEFAULT_STROKE_WIDTH,
|
|
5538
|
+
strokeWidth: styleOverride?.strokeWidth ?? (spec.display === "sparkline" ? SPARKLINE_STROKE_WIDTH : DEFAULT_STROKE_WIDTH),
|
|
5528
5539
|
strokeDasharray,
|
|
5529
5540
|
opacity: styleOverride?.opacity,
|
|
5530
5541
|
seriesKey: seriesStyleKey,
|
|
@@ -6543,9 +6554,146 @@ import {
|
|
|
6543
6554
|
isLayerSpec,
|
|
6544
6555
|
isSankeySpec,
|
|
6545
6556
|
isTableSpec,
|
|
6557
|
+
isTileMapSpec,
|
|
6546
6558
|
resolveMarkDef,
|
|
6547
6559
|
resolveMarkType
|
|
6548
6560
|
} from "@opendata-ai/openchart-core";
|
|
6561
|
+
|
|
6562
|
+
// src/tilemap/layout.ts
|
|
6563
|
+
var US_STATE_TILES = [
|
|
6564
|
+
// Row 0
|
|
6565
|
+
{ state: "ME", col: 10, row: 0 },
|
|
6566
|
+
// Row 1
|
|
6567
|
+
{ state: "VT", col: 9, row: 1 },
|
|
6568
|
+
{ state: "NH", col: 10, row: 1 },
|
|
6569
|
+
// Row 2
|
|
6570
|
+
{ state: "WA", col: 0, row: 2 },
|
|
6571
|
+
{ state: "ID", col: 1, row: 2 },
|
|
6572
|
+
{ state: "MT", col: 2, row: 2 },
|
|
6573
|
+
{ state: "ND", col: 3, row: 2 },
|
|
6574
|
+
{ state: "MN", col: 4, row: 2 },
|
|
6575
|
+
{ state: "WI", col: 5, row: 2 },
|
|
6576
|
+
{ state: "MI", col: 6, row: 2 },
|
|
6577
|
+
{ state: "NY", col: 8, row: 2 },
|
|
6578
|
+
{ state: "MA", col: 9, row: 2 },
|
|
6579
|
+
// Row 3
|
|
6580
|
+
{ state: "OR", col: 0, row: 3 },
|
|
6581
|
+
{ state: "NV", col: 1, row: 3 },
|
|
6582
|
+
{ state: "WY", col: 2, row: 3 },
|
|
6583
|
+
{ state: "SD", col: 3, row: 3 },
|
|
6584
|
+
{ state: "IA", col: 4, row: 3 },
|
|
6585
|
+
{ state: "IL", col: 5, row: 3 },
|
|
6586
|
+
{ state: "IN", col: 6, row: 3 },
|
|
6587
|
+
{ state: "OH", col: 7, row: 3 },
|
|
6588
|
+
{ state: "PA", col: 8, row: 3 },
|
|
6589
|
+
{ state: "NJ", col: 9, row: 3 },
|
|
6590
|
+
{ state: "CT", col: 10, row: 3 },
|
|
6591
|
+
// Row 4
|
|
6592
|
+
{ state: "CA", col: 0, row: 4 },
|
|
6593
|
+
{ state: "UT", col: 1, row: 4 },
|
|
6594
|
+
{ state: "CO", col: 2, row: 4 },
|
|
6595
|
+
{ state: "NE", col: 3, row: 4 },
|
|
6596
|
+
{ state: "MO", col: 4, row: 4 },
|
|
6597
|
+
{ state: "KY", col: 5, row: 4 },
|
|
6598
|
+
{ state: "WV", col: 6, row: 4 },
|
|
6599
|
+
{ state: "VA", col: 7, row: 4 },
|
|
6600
|
+
{ state: "MD", col: 8, row: 4 },
|
|
6601
|
+
{ state: "DE", col: 9, row: 4 },
|
|
6602
|
+
{ state: "RI", col: 10, row: 4 },
|
|
6603
|
+
// Row 5
|
|
6604
|
+
{ state: "AZ", col: 1, row: 5 },
|
|
6605
|
+
{ state: "NM", col: 2, row: 5 },
|
|
6606
|
+
{ state: "KS", col: 3, row: 5 },
|
|
6607
|
+
{ state: "AR", col: 4, row: 5 },
|
|
6608
|
+
{ state: "TN", col: 5, row: 5 },
|
|
6609
|
+
{ state: "NC", col: 6, row: 5 },
|
|
6610
|
+
{ state: "SC", col: 7, row: 5 },
|
|
6611
|
+
{ state: "DC", col: 8, row: 5 },
|
|
6612
|
+
// Row 6
|
|
6613
|
+
{ state: "AK", col: 0, row: 6 },
|
|
6614
|
+
{ state: "OK", col: 3, row: 6 },
|
|
6615
|
+
{ state: "LA", col: 4, row: 6 },
|
|
6616
|
+
{ state: "MS", col: 5, row: 6 },
|
|
6617
|
+
{ state: "AL", col: 6, row: 6 },
|
|
6618
|
+
{ state: "GA", col: 7, row: 6 },
|
|
6619
|
+
// Row 7
|
|
6620
|
+
{ state: "HI", col: 1, row: 7 },
|
|
6621
|
+
{ state: "TX", col: 3, row: 7 },
|
|
6622
|
+
{ state: "FL", col: 7, row: 7 }
|
|
6623
|
+
];
|
|
6624
|
+
var STATE_CODE_SET = new Set(US_STATE_TILES.map((t) => t.state));
|
|
6625
|
+
var STATE_NAMES = {
|
|
6626
|
+
AL: "Alabama",
|
|
6627
|
+
AK: "Alaska",
|
|
6628
|
+
AZ: "Arizona",
|
|
6629
|
+
AR: "Arkansas",
|
|
6630
|
+
CA: "California",
|
|
6631
|
+
CO: "Colorado",
|
|
6632
|
+
CT: "Connecticut",
|
|
6633
|
+
DE: "Delaware",
|
|
6634
|
+
DC: "District of Columbia",
|
|
6635
|
+
FL: "Florida",
|
|
6636
|
+
GA: "Georgia",
|
|
6637
|
+
HI: "Hawaii",
|
|
6638
|
+
ID: "Idaho",
|
|
6639
|
+
IL: "Illinois",
|
|
6640
|
+
IN: "Indiana",
|
|
6641
|
+
IA: "Iowa",
|
|
6642
|
+
KS: "Kansas",
|
|
6643
|
+
KY: "Kentucky",
|
|
6644
|
+
LA: "Louisiana",
|
|
6645
|
+
ME: "Maine",
|
|
6646
|
+
MD: "Maryland",
|
|
6647
|
+
MA: "Massachusetts",
|
|
6648
|
+
MI: "Michigan",
|
|
6649
|
+
MN: "Minnesota",
|
|
6650
|
+
MS: "Mississippi",
|
|
6651
|
+
MO: "Missouri",
|
|
6652
|
+
MT: "Montana",
|
|
6653
|
+
NE: "Nebraska",
|
|
6654
|
+
NV: "Nevada",
|
|
6655
|
+
NH: "New Hampshire",
|
|
6656
|
+
NJ: "New Jersey",
|
|
6657
|
+
NM: "New Mexico",
|
|
6658
|
+
NY: "New York",
|
|
6659
|
+
NC: "North Carolina",
|
|
6660
|
+
ND: "North Dakota",
|
|
6661
|
+
OH: "Ohio",
|
|
6662
|
+
OK: "Oklahoma",
|
|
6663
|
+
OR: "Oregon",
|
|
6664
|
+
PA: "Pennsylvania",
|
|
6665
|
+
RI: "Rhode Island",
|
|
6666
|
+
SC: "South Carolina",
|
|
6667
|
+
SD: "South Dakota",
|
|
6668
|
+
TN: "Tennessee",
|
|
6669
|
+
TX: "Texas",
|
|
6670
|
+
UT: "Utah",
|
|
6671
|
+
VT: "Vermont",
|
|
6672
|
+
VA: "Virginia",
|
|
6673
|
+
WA: "Washington",
|
|
6674
|
+
WV: "West Virginia",
|
|
6675
|
+
WI: "Wisconsin",
|
|
6676
|
+
WY: "Wyoming"
|
|
6677
|
+
};
|
|
6678
|
+
var GRID_COLS = 12;
|
|
6679
|
+
var GRID_ROWS = 8;
|
|
6680
|
+
function computeTilePositions(availableWidth, availableHeight, gap = 4) {
|
|
6681
|
+
const maxTileW = (availableWidth - gap * (GRID_COLS - 1)) / GRID_COLS;
|
|
6682
|
+
const maxTileH = (availableHeight - gap * (GRID_ROWS - 1)) / GRID_ROWS;
|
|
6683
|
+
const tileSize = Math.max(1, Math.floor(Math.min(maxTileW, maxTileH)));
|
|
6684
|
+
const gridWidth = tileSize * GRID_COLS + gap * (GRID_COLS - 1);
|
|
6685
|
+
const gridHeight = tileSize * GRID_ROWS + gap * (GRID_ROWS - 1);
|
|
6686
|
+
const positions = /* @__PURE__ */ new Map();
|
|
6687
|
+
for (const { state, col, row } of US_STATE_TILES) {
|
|
6688
|
+
positions.set(state, {
|
|
6689
|
+
x: col * (tileSize + gap),
|
|
6690
|
+
y: row * (tileSize + gap)
|
|
6691
|
+
});
|
|
6692
|
+
}
|
|
6693
|
+
return { tileSize, gap, positions, gridWidth, gridHeight };
|
|
6694
|
+
}
|
|
6695
|
+
|
|
6696
|
+
// src/compiler/normalize.ts
|
|
6549
6697
|
function normalizeChromeField(value2) {
|
|
6550
6698
|
if (value2 === void 0) return void 0;
|
|
6551
6699
|
if (typeof value2 === "string") return { text: value2 };
|
|
@@ -6665,6 +6813,12 @@ function normalizeChartSpec(spec, warnings) {
|
|
|
6665
6813
|
const encoding = inferEncodingTypes(spec.encoding, spec.data, warnings);
|
|
6666
6814
|
const markType = resolveMarkType(spec.mark);
|
|
6667
6815
|
const markDef = resolveMarkDef(spec.mark);
|
|
6816
|
+
const display = spec.display ?? "full";
|
|
6817
|
+
if (display === "sparkline" && markType !== "line" && markType !== "area" && markType !== "bar" && markType !== "point") {
|
|
6818
|
+
warnings.push(
|
|
6819
|
+
`[openchart] display: 'sparkline' works best with mark: 'line' | 'area' | 'bar' | 'point'. Got mark: '${markType}' \u2014 rendering may degrade.`
|
|
6820
|
+
);
|
|
6821
|
+
}
|
|
6668
6822
|
return {
|
|
6669
6823
|
markType,
|
|
6670
6824
|
markDef,
|
|
@@ -6679,7 +6833,20 @@ function normalizeChartSpec(spec, warnings) {
|
|
|
6679
6833
|
darkMode: spec.darkMode ?? "off",
|
|
6680
6834
|
hiddenSeries: spec.hiddenSeries ?? [],
|
|
6681
6835
|
seriesStyles: spec.seriesStyles ?? {},
|
|
6682
|
-
watermark: spec.watermark ?? true
|
|
6836
|
+
watermark: spec.watermark ?? true,
|
|
6837
|
+
display,
|
|
6838
|
+
// Default empty userExplicit; compileChart overwrites this with the real
|
|
6839
|
+
// descriptor built from the raw expanded spec before normalize runs.
|
|
6840
|
+
userExplicit: {
|
|
6841
|
+
chrome: false,
|
|
6842
|
+
legend: false,
|
|
6843
|
+
xAxis: false,
|
|
6844
|
+
yAxis: false,
|
|
6845
|
+
labels: false,
|
|
6846
|
+
animation: false,
|
|
6847
|
+
watermark: false,
|
|
6848
|
+
crosshair: false
|
|
6849
|
+
}
|
|
6683
6850
|
};
|
|
6684
6851
|
}
|
|
6685
6852
|
function normalizeTableSpec(spec, _warnings) {
|
|
@@ -6746,6 +6913,45 @@ function normalizeGraphSpec(spec, _warnings) {
|
|
|
6746
6913
|
watermark: spec.watermark ?? true
|
|
6747
6914
|
};
|
|
6748
6915
|
}
|
|
6916
|
+
function normalizeTileMapSpec(spec, warnings) {
|
|
6917
|
+
let data = Array.isArray(spec.data) ? spec.data : [];
|
|
6918
|
+
if (!Array.isArray(spec.data)) {
|
|
6919
|
+
data = Object.entries(spec.data).map(([state, value2]) => ({ state, value: value2 }));
|
|
6920
|
+
}
|
|
6921
|
+
let encoding = spec.encoding;
|
|
6922
|
+
if (!encoding) {
|
|
6923
|
+
encoding = {
|
|
6924
|
+
state: { field: "state", type: "nominal" },
|
|
6925
|
+
value: { field: "value", type: "quantitative" }
|
|
6926
|
+
};
|
|
6927
|
+
}
|
|
6928
|
+
let matchedCount = 0;
|
|
6929
|
+
for (const row of data) {
|
|
6930
|
+
const stateCode = String(row[encoding.state.field]);
|
|
6931
|
+
if (STATE_CODE_SET.has(stateCode)) {
|
|
6932
|
+
matchedCount++;
|
|
6933
|
+
}
|
|
6934
|
+
}
|
|
6935
|
+
const matchRatio = data.length > 0 ? matchedCount / data.length : 0;
|
|
6936
|
+
if (matchRatio < 0.5 && data.length > 0) {
|
|
6937
|
+
warnings.push(
|
|
6938
|
+
`TileMap data: only ${matchedCount} of ${data.length} rows have valid US state codes (expected \u226550%)`
|
|
6939
|
+
);
|
|
6940
|
+
}
|
|
6941
|
+
return {
|
|
6942
|
+
type: "tilemap",
|
|
6943
|
+
data,
|
|
6944
|
+
encoding,
|
|
6945
|
+
palette: spec.palette ?? "blue",
|
|
6946
|
+
chrome: normalizeChrome(spec.chrome),
|
|
6947
|
+
legend: spec.legend,
|
|
6948
|
+
theme: spec.theme ?? {},
|
|
6949
|
+
darkMode: spec.darkMode ?? "off",
|
|
6950
|
+
watermark: spec.watermark ?? true,
|
|
6951
|
+
animation: spec.animation,
|
|
6952
|
+
valueFormat: spec.valueFormat
|
|
6953
|
+
};
|
|
6954
|
+
}
|
|
6749
6955
|
function normalizeSpec(spec, warnings = []) {
|
|
6750
6956
|
if (isLayerSpec(spec)) {
|
|
6751
6957
|
const leaves = flattenLayers(spec);
|
|
@@ -6766,8 +6972,11 @@ function normalizeSpec(spec, warnings = []) {
|
|
|
6766
6972
|
if (isSankeySpec(spec)) {
|
|
6767
6973
|
return normalizeSankeySpec(spec, warnings);
|
|
6768
6974
|
}
|
|
6975
|
+
if (isTileMapSpec(spec)) {
|
|
6976
|
+
return normalizeTileMapSpec(spec, warnings);
|
|
6977
|
+
}
|
|
6769
6978
|
throw new Error(
|
|
6770
|
-
`Unknown spec shape. Expected mark (chart), layer, type: 'table', type: 'graph', or type: '
|
|
6979
|
+
`Unknown spec shape. Expected mark (chart), layer, type: 'table', type: 'graph', type: 'sankey', or type: 'tilemap'.`
|
|
6771
6980
|
);
|
|
6772
6981
|
}
|
|
6773
6982
|
function flattenLayers(spec, parentData, parentEncoding, parentTransforms, parentWatermark) {
|
|
@@ -7317,6 +7526,96 @@ function validateSankeySpec(spec, errors) {
|
|
|
7317
7526
|
});
|
|
7318
7527
|
}
|
|
7319
7528
|
}
|
|
7529
|
+
function validateTileMapSpec(spec, errors) {
|
|
7530
|
+
if (!spec.data || typeof spec.data !== "object") {
|
|
7531
|
+
errors.push({
|
|
7532
|
+
message: 'Spec error: tilemap spec requires a "data" field (record or array)',
|
|
7533
|
+
path: "data",
|
|
7534
|
+
code: "INVALID_TYPE",
|
|
7535
|
+
suggestion: 'Provide data as either a record mapping state codes to values (e.g. { "CA": 12000, "TX": 8500 }) or an array of objects with state and value fields'
|
|
7536
|
+
});
|
|
7537
|
+
return;
|
|
7538
|
+
}
|
|
7539
|
+
if (!Array.isArray(spec.data) && Object.keys(spec.data).length === 0) {
|
|
7540
|
+
errors.push({
|
|
7541
|
+
message: 'Spec error: "data" must have at least one entry',
|
|
7542
|
+
path: "data",
|
|
7543
|
+
code: "EMPTY_DATA",
|
|
7544
|
+
suggestion: 'Add at least one state-value pair, e.g. { "CA": 12000 }'
|
|
7545
|
+
});
|
|
7546
|
+
return;
|
|
7547
|
+
}
|
|
7548
|
+
if (Array.isArray(spec.data)) {
|
|
7549
|
+
if (spec.data.length === 0) {
|
|
7550
|
+
errors.push({
|
|
7551
|
+
message: 'Spec error: "data" array must be non-empty',
|
|
7552
|
+
path: "data",
|
|
7553
|
+
code: "EMPTY_DATA",
|
|
7554
|
+
suggestion: "Add at least one data row"
|
|
7555
|
+
});
|
|
7556
|
+
return;
|
|
7557
|
+
}
|
|
7558
|
+
const firstRow = spec.data[0];
|
|
7559
|
+
if (typeof firstRow !== "object" || firstRow === null || Array.isArray(firstRow)) {
|
|
7560
|
+
errors.push({
|
|
7561
|
+
message: 'Spec error: each item in "data" must be a plain object',
|
|
7562
|
+
path: "data[0]",
|
|
7563
|
+
code: "INVALID_TYPE",
|
|
7564
|
+
suggestion: 'Each data item should be an object, e.g. { state: "CA", value: 12000 }'
|
|
7565
|
+
});
|
|
7566
|
+
return;
|
|
7567
|
+
}
|
|
7568
|
+
if (!spec.encoding || typeof spec.encoding !== "object") {
|
|
7569
|
+
errors.push({
|
|
7570
|
+
message: 'Spec error: tilemap spec with array data requires an "encoding" object with state and value channels',
|
|
7571
|
+
path: "encoding",
|
|
7572
|
+
code: "MISSING_FIELD",
|
|
7573
|
+
suggestion: 'Add an encoding object, e.g. encoding: { state: { field: "state", type: "nominal" }, value: { field: "value", type: "quantitative" } }'
|
|
7574
|
+
});
|
|
7575
|
+
return;
|
|
7576
|
+
}
|
|
7577
|
+
const encoding = spec.encoding;
|
|
7578
|
+
const dataColumns = new Set(Object.keys(firstRow));
|
|
7579
|
+
const availableColumns = [...dataColumns].join(", ");
|
|
7580
|
+
for (const channel of ["state", "value"]) {
|
|
7581
|
+
const ch = encoding[channel];
|
|
7582
|
+
if (!ch || typeof ch !== "object") {
|
|
7583
|
+
errors.push({
|
|
7584
|
+
message: `Spec error: tilemap encoding requires "${channel}" channel`,
|
|
7585
|
+
path: `encoding.${channel}`,
|
|
7586
|
+
code: "MISSING_FIELD",
|
|
7587
|
+
suggestion: `Add encoding.${channel} with a field from your data (${availableColumns}). Example: ${channel}: { field: "${[...dataColumns][0] ?? "myField"}", type: "${channel === "value" ? "quantitative" : "nominal"}" }`
|
|
7588
|
+
});
|
|
7589
|
+
continue;
|
|
7590
|
+
}
|
|
7591
|
+
if (!ch.field || typeof ch.field !== "string") {
|
|
7592
|
+
errors.push({
|
|
7593
|
+
message: `Spec error: encoding.${channel} must have a "field" string`,
|
|
7594
|
+
path: `encoding.${channel}.field`,
|
|
7595
|
+
code: "MISSING_FIELD",
|
|
7596
|
+
suggestion: `Add a field name from your data columns: ${availableColumns}`
|
|
7597
|
+
});
|
|
7598
|
+
continue;
|
|
7599
|
+
}
|
|
7600
|
+
if (!dataColumns.has(ch.field)) {
|
|
7601
|
+
errors.push({
|
|
7602
|
+
message: `Spec error: encoding.${channel}.field "${ch.field}" does not exist in data. Available columns: ${availableColumns}`,
|
|
7603
|
+
path: `encoding.${channel}.field`,
|
|
7604
|
+
code: "DATA_FIELD_MISSING",
|
|
7605
|
+
suggestion: `Use one of the available data columns: ${availableColumns}`
|
|
7606
|
+
});
|
|
7607
|
+
}
|
|
7608
|
+
}
|
|
7609
|
+
}
|
|
7610
|
+
if (spec.darkMode !== void 0 && !VALID_DARK_MODES.has(spec.darkMode)) {
|
|
7611
|
+
errors.push({
|
|
7612
|
+
message: 'Spec error: darkMode must be "auto", "force", or "off"',
|
|
7613
|
+
path: "darkMode",
|
|
7614
|
+
code: "INVALID_VALUE",
|
|
7615
|
+
suggestion: 'Use one of: "auto" (system preference), "force" (always dark), or "off" (always light)'
|
|
7616
|
+
});
|
|
7617
|
+
}
|
|
7618
|
+
}
|
|
7320
7619
|
function validateLayerSpec(spec, errors) {
|
|
7321
7620
|
const layer = spec.layer;
|
|
7322
7621
|
if (layer.length === 0) {
|
|
@@ -7411,17 +7710,18 @@ function validateSpec(spec) {
|
|
|
7411
7710
|
const isTable = obj.type === "table";
|
|
7412
7711
|
const isGraph = obj.type === "graph";
|
|
7413
7712
|
const isSankey = obj.type === "sankey";
|
|
7414
|
-
const
|
|
7415
|
-
const
|
|
7416
|
-
|
|
7713
|
+
const isTileMap = obj.type === "tilemap";
|
|
7714
|
+
const isLayer = hasLayer && !isTable && !isGraph && !isSankey && !isTileMap;
|
|
7715
|
+
const isChart = hasMark && !hasLayer && !isTable && !isGraph && !isSankey && !isTileMap;
|
|
7716
|
+
if (!isChart && !isTable && !isGraph && !isSankey && !isTileMap && !isLayer) {
|
|
7417
7717
|
return {
|
|
7418
7718
|
valid: false,
|
|
7419
7719
|
errors: [
|
|
7420
7720
|
{
|
|
7421
|
-
message: 'Spec error: spec must have a "mark" field for charts, a "layer" array for layered charts, or a "type" field for tables/graphs/sankey',
|
|
7721
|
+
message: 'Spec error: spec must have a "mark" field for charts, a "layer" array for layered charts, or a "type" field for tables/graphs/sankey/tilemap',
|
|
7422
7722
|
path: "mark",
|
|
7423
7723
|
code: "MISSING_FIELD",
|
|
7424
|
-
suggestion: `Add a "mark" field for charts (e.g. mark: "bar"), a "layer" array for layered charts, or a "type" field for tables/graphs/sankey (type: "table", type: "graph", or type: "
|
|
7724
|
+
suggestion: `Add a "mark" field for charts (e.g. mark: "bar"), a "layer" array for layered charts, or a "type" field for tables/graphs/sankey/tilemap (type: "table", type: "graph", type: "sankey", or type: "tilemap"). Valid mark types: ${[...MARK_TYPES].join(", ")}`
|
|
7425
7725
|
}
|
|
7426
7726
|
],
|
|
7427
7727
|
normalized: null
|
|
@@ -7459,6 +7759,8 @@ function validateSpec(spec) {
|
|
|
7459
7759
|
validateGraphSpec(obj, errors);
|
|
7460
7760
|
} else if (isSankey) {
|
|
7461
7761
|
validateSankeySpec(obj, errors);
|
|
7762
|
+
} else if (isTileMap) {
|
|
7763
|
+
validateTileMapSpec(obj, errors);
|
|
7462
7764
|
}
|
|
7463
7765
|
if (errors.length > 0) {
|
|
7464
7766
|
return { valid: false, errors, normalized: null };
|
|
@@ -8021,7 +8323,7 @@ function scaleSupportsTickCount(resolvedScale) {
|
|
|
8021
8323
|
const scale = resolvedScale.scale;
|
|
8022
8324
|
return "ticks" in scale && typeof scale.ticks === "function";
|
|
8023
8325
|
}
|
|
8024
|
-
function categoricalTicks(resolvedScale, density, orientation = "horizontal", bandwidth, labelAngle, fontSize, fontWeight, measureText) {
|
|
8326
|
+
function categoricalTicks(resolvedScale, density, orientation = "horizontal", bandwidth, labelAngle, fontSize, fontWeight, measureText, subtitleContext) {
|
|
8025
8327
|
const scale = resolvedScale.scale;
|
|
8026
8328
|
const domain = scale.domain();
|
|
8027
8329
|
const explicitTickCount = resolvedScale.channel.axis?.tickCount;
|
|
@@ -8057,14 +8359,37 @@ function categoricalTicks(resolvedScale, density, orientation = "horizontal", ba
|
|
|
8057
8359
|
selectedValues = domain.filter((_, i) => i % step === 0);
|
|
8058
8360
|
}
|
|
8059
8361
|
}
|
|
8362
|
+
let subtitleMap;
|
|
8363
|
+
if (subtitleContext) {
|
|
8364
|
+
const { data, fieldName, labelField } = subtitleContext;
|
|
8365
|
+
if (data.length > 0) {
|
|
8366
|
+
subtitleMap = /* @__PURE__ */ new Map();
|
|
8367
|
+
for (const row of data) {
|
|
8368
|
+
const key = String(row[fieldName] ?? "");
|
|
8369
|
+
if (!subtitleMap.has(key)) {
|
|
8370
|
+
const val = row[labelField];
|
|
8371
|
+
if (val != null) {
|
|
8372
|
+
subtitleMap.set(key, String(val));
|
|
8373
|
+
}
|
|
8374
|
+
}
|
|
8375
|
+
}
|
|
8376
|
+
}
|
|
8377
|
+
}
|
|
8060
8378
|
const ticks2 = selectedValues.map((value2) => {
|
|
8061
8379
|
const bandScale = resolvedScale.type === "band" ? scale : null;
|
|
8062
8380
|
const pos = bandScale ? (bandScale(value2) ?? 0) + bandScale.bandwidth() / 2 : scale(value2) ?? 0;
|
|
8063
|
-
|
|
8381
|
+
const tick = {
|
|
8064
8382
|
value: value2,
|
|
8065
8383
|
position: pos,
|
|
8066
8384
|
label: value2
|
|
8067
8385
|
};
|
|
8386
|
+
if (subtitleMap) {
|
|
8387
|
+
const subtitle = subtitleMap.get(value2);
|
|
8388
|
+
if (subtitle !== void 0) {
|
|
8389
|
+
tick.subtitle = subtitle;
|
|
8390
|
+
}
|
|
8391
|
+
}
|
|
8392
|
+
return tick;
|
|
8068
8393
|
});
|
|
8069
8394
|
return ticks2;
|
|
8070
8395
|
}
|
|
@@ -8136,7 +8461,7 @@ function fitContinuousTicks(scale, initialTicks, initialCount, fontSize, fontWei
|
|
|
8136
8461
|
const fallback = bestWithinFloor ?? buildContinuousTicks(scale, floor);
|
|
8137
8462
|
return thinTicksUntilFit(fallback, fontSize, fontWeight, measureText, orientation);
|
|
8138
8463
|
}
|
|
8139
|
-
function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
8464
|
+
function computeAxes(scales, chartArea, strategy, theme, measureText, dataContext) {
|
|
8140
8465
|
const result = {};
|
|
8141
8466
|
const baseDensity = strategy.axisLabelDensity;
|
|
8142
8467
|
const yDensity = effectiveDensity(
|
|
@@ -8168,7 +8493,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
8168
8493
|
};
|
|
8169
8494
|
const { fontSize } = tickLabelStyle;
|
|
8170
8495
|
const { fontWeight } = tickLabelStyle;
|
|
8171
|
-
if (scales.x) {
|
|
8496
|
+
if (scales.x && !dataContext?.skipX) {
|
|
8172
8497
|
const axisConfig = scales.x.channel.axis;
|
|
8173
8498
|
const isContinuousX = scales.x.type !== "band" && scales.x.type !== "point" && scales.x.type !== "ordinal";
|
|
8174
8499
|
const xTargetCount = isContinuousX ? targetTickCount(chartArea.width, xDensity, "x") : void 0;
|
|
@@ -8246,7 +8571,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
8246
8571
|
labelFlush: axisConfig?.labelFlush
|
|
8247
8572
|
};
|
|
8248
8573
|
}
|
|
8249
|
-
if (scales.y) {
|
|
8574
|
+
if (scales.y && !dataContext?.skipY) {
|
|
8250
8575
|
const axisConfig = scales.y.channel.axis;
|
|
8251
8576
|
const isContinuousY = scales.y.type !== "band" && scales.y.type !== "point" && scales.y.type !== "ordinal";
|
|
8252
8577
|
const yTargetCount = isContinuousY ? targetTickCount(chartArea.height, yDensity, "y") : void 0;
|
|
@@ -8254,7 +8579,19 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
8254
8579
|
if (axisConfig?.values) {
|
|
8255
8580
|
allTicks = resolveExplicitTicks(axisConfig.values, scales.y);
|
|
8256
8581
|
} else if (!isContinuousY) {
|
|
8257
|
-
|
|
8582
|
+
const yFieldName = dataContext?.encoding.y?.field;
|
|
8583
|
+
const yLabelField = axisConfig?.labelField;
|
|
8584
|
+
allTicks = categoricalTicks(
|
|
8585
|
+
scales.y,
|
|
8586
|
+
yDensity,
|
|
8587
|
+
"vertical",
|
|
8588
|
+
void 0,
|
|
8589
|
+
void 0,
|
|
8590
|
+
void 0,
|
|
8591
|
+
void 0,
|
|
8592
|
+
void 0,
|
|
8593
|
+
yFieldName && yLabelField && dataContext ? { data: dataContext.data, fieldName: yFieldName, labelField: yLabelField } : void 0
|
|
8594
|
+
);
|
|
8258
8595
|
} else {
|
|
8259
8596
|
allTicks = continuousTicks(scales.y, yDensity, yTargetCount);
|
|
8260
8597
|
}
|
|
@@ -8386,12 +8723,24 @@ function scalePadding(basePadding, width, height) {
|
|
|
8386
8723
|
}
|
|
8387
8724
|
var MIN_CHART_WIDTH = 60;
|
|
8388
8725
|
var MIN_CHART_HEIGHT = 40;
|
|
8726
|
+
function getMinChartDims(display) {
|
|
8727
|
+
return display === "sparkline" ? { width: 30, height: 20 } : { width: MIN_CHART_WIDTH, height: MIN_CHART_HEIGHT };
|
|
8728
|
+
}
|
|
8729
|
+
function getSparklinePad(spec) {
|
|
8730
|
+
const strokeWidth = spec.markDef.strokeWidth ?? 2;
|
|
8731
|
+
return Math.max(strokeWidth / 2 + 1, 2);
|
|
8732
|
+
}
|
|
8389
8733
|
function computeDimensions(spec, options, legendLayout, theme, strategy, watermark = true) {
|
|
8390
8734
|
const { width, height } = options;
|
|
8391
8735
|
const padding = scalePadding(theme.spacing.padding, width, height);
|
|
8392
8736
|
const hPad = width < BREAKPOINT_COMPACT_MAX ? Math.max(Math.round(padding * HPAD_COMPACT_FRACTION), HPAD_COMPACT_MIN) : padding;
|
|
8393
8737
|
const axisMargin = theme.spacing.axisMargin;
|
|
8394
|
-
const
|
|
8738
|
+
const userExplicit = spec.userExplicit;
|
|
8739
|
+
const isSparkline = spec.display === "sparkline";
|
|
8740
|
+
let chromeMode = strategy?.chromeMode ?? "full";
|
|
8741
|
+
if (isSparkline && !userExplicit.chrome) {
|
|
8742
|
+
chromeMode = "hidden";
|
|
8743
|
+
}
|
|
8395
8744
|
const chrome = computeChrome2(
|
|
8396
8745
|
chromeToInput(spec.chrome),
|
|
8397
8746
|
theme,
|
|
@@ -8401,6 +8750,35 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8401
8750
|
padding,
|
|
8402
8751
|
watermark
|
|
8403
8752
|
);
|
|
8753
|
+
if (isSparkline) {
|
|
8754
|
+
const total2 = { x: 0, y: 0, width, height };
|
|
8755
|
+
const sparkPad = getSparklinePad(spec);
|
|
8756
|
+
const xAxisSpace = userExplicit.xAxis ? 26 : 0;
|
|
8757
|
+
const yAxisSpace = userExplicit.yAxis ? 30 : 0;
|
|
8758
|
+
const margins2 = {
|
|
8759
|
+
top: chrome.topHeight + sparkPad,
|
|
8760
|
+
right: sparkPad,
|
|
8761
|
+
bottom: chrome.bottomHeight + sparkPad + xAxisSpace,
|
|
8762
|
+
left: sparkPad + yAxisSpace
|
|
8763
|
+
};
|
|
8764
|
+
if (userExplicit.legend && "entries" in legendLayout && legendLayout.entries.length > 0) {
|
|
8765
|
+
const gap = legendGap(width);
|
|
8766
|
+
if (legendLayout.position === "right" || legendLayout.position === "bottom-right") {
|
|
8767
|
+
margins2.right += legendLayout.bounds.width + 8;
|
|
8768
|
+
} else if (legendLayout.position === "top") {
|
|
8769
|
+
margins2.top += legendLayout.bounds.height + gap;
|
|
8770
|
+
} else if (legendLayout.position === "bottom") {
|
|
8771
|
+
margins2.bottom += legendLayout.bounds.height + gap;
|
|
8772
|
+
}
|
|
8773
|
+
}
|
|
8774
|
+
const chartArea2 = {
|
|
8775
|
+
x: margins2.left,
|
|
8776
|
+
y: margins2.top,
|
|
8777
|
+
width: Math.max(0, width - margins2.left - margins2.right),
|
|
8778
|
+
height: Math.max(0, height - margins2.top - margins2.bottom)
|
|
8779
|
+
};
|
|
8780
|
+
return { total: total2, chrome, chartArea: chartArea2, margins: margins2, theme };
|
|
8781
|
+
}
|
|
8404
8782
|
const total = { x: 0, y: 0, width, height };
|
|
8405
8783
|
const isRadial = spec.markType === "arc";
|
|
8406
8784
|
const encoding = spec.encoding;
|
|
@@ -8488,10 +8866,23 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8488
8866
|
if (encoding.y && !isRadial) {
|
|
8489
8867
|
if (spec.markType === "bar" || spec.markType === "circle" || spec.markType === "lollipop" || encoding.y.type === "nominal" || encoding.y.type === "ordinal") {
|
|
8490
8868
|
const yField = encoding.y.field;
|
|
8869
|
+
const yLabelField = encoding.y.axis?.labelField;
|
|
8491
8870
|
let maxLabelWidth = 0;
|
|
8492
8871
|
for (const row of spec.data) {
|
|
8493
8872
|
const label = String(row[yField] ?? "");
|
|
8494
|
-
|
|
8873
|
+
let w = estimateTextWidth10(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
|
|
8874
|
+
if (yLabelField) {
|
|
8875
|
+
const subtitle = String(row[yLabelField] ?? "");
|
|
8876
|
+
if (subtitle) {
|
|
8877
|
+
const gap = theme.fonts.sizes.axisTick * 0.6;
|
|
8878
|
+
const subtitleWidth = estimateTextWidth10(
|
|
8879
|
+
subtitle,
|
|
8880
|
+
theme.fonts.sizes.axisTick,
|
|
8881
|
+
theme.fonts.weights.normal
|
|
8882
|
+
);
|
|
8883
|
+
w += gap + subtitleWidth;
|
|
8884
|
+
}
|
|
8885
|
+
}
|
|
8495
8886
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
8496
8887
|
}
|
|
8497
8888
|
if (maxLabelWidth > 0) {
|
|
@@ -8545,7 +8936,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8545
8936
|
if (options.rightAxisReserve && options.rightAxisReserve > 0) {
|
|
8546
8937
|
margins.right = Math.max(margins.right, hPad + options.rightAxisReserve);
|
|
8547
8938
|
}
|
|
8548
|
-
if (legendLayout.entries.length > 0) {
|
|
8939
|
+
if ("entries" in legendLayout && legendLayout.entries.length > 0) {
|
|
8549
8940
|
const gap = legendGap(width);
|
|
8550
8941
|
if (legendLayout.position === "right" || legendLayout.position === "bottom-right") {
|
|
8551
8942
|
margins.right += legendLayout.bounds.width + 8;
|
|
@@ -8561,7 +8952,8 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8561
8952
|
width: Math.max(0, width - margins.left - margins.right),
|
|
8562
8953
|
height: Math.max(0, height - margins.top - margins.bottom)
|
|
8563
8954
|
};
|
|
8564
|
-
|
|
8955
|
+
const minDims = getMinChartDims(spec.display);
|
|
8956
|
+
if ((chartArea.width < minDims.width || chartArea.height < minDims.height) && chromeMode !== "hidden") {
|
|
8565
8957
|
const fallbackMode = chromeMode === "full" ? "compact" : "hidden";
|
|
8566
8958
|
const fallbackChrome = computeChrome2(
|
|
8567
8959
|
chromeToInput(spec.chrome),
|
|
@@ -8579,7 +8971,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8579
8971
|
const bottomDelta = margins.bottom - newBottom;
|
|
8580
8972
|
if (topDelta > 0 || bottomDelta > 0) {
|
|
8581
8973
|
const gap = legendGap(width);
|
|
8582
|
-
margins.top = newTop + (legendLayout.entries.length > 0 && legendLayout.position === "top" ? legendLayout.bounds.height + gap : 0);
|
|
8974
|
+
margins.top = newTop + ("entries" in legendLayout && legendLayout.entries.length > 0 && legendLayout.position === "top" ? legendLayout.bounds.height + gap : 0);
|
|
8583
8975
|
margins.bottom = newBottom;
|
|
8584
8976
|
chartArea = {
|
|
8585
8977
|
x: margins.left,
|
|
@@ -9108,7 +9500,8 @@ function truncateEntries(entries, maxCount) {
|
|
|
9108
9500
|
return truncated;
|
|
9109
9501
|
}
|
|
9110
9502
|
function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
|
|
9111
|
-
|
|
9503
|
+
const sparklineHidden = spec.display === "sparkline" && !spec.userExplicit.legend;
|
|
9504
|
+
if (sparklineHidden || spec.legend?.show === false || strategy.legendMaxHeight === 0) {
|
|
9112
9505
|
return {
|
|
9113
9506
|
position: "top",
|
|
9114
9507
|
entries: [],
|
|
@@ -9904,7 +10297,7 @@ function compileSankey(spec, options) {
|
|
|
9904
10297
|
theme,
|
|
9905
10298
|
fullArea
|
|
9906
10299
|
);
|
|
9907
|
-
const legendGap2 = legend.entries.length > 0 ? 4 : 0;
|
|
10300
|
+
const legendGap2 = "entries" in legend && legend.entries.length > 0 ? 4 : 0;
|
|
9908
10301
|
const area = {
|
|
9909
10302
|
x: fullArea.x,
|
|
9910
10303
|
y: fullArea.y + legend.bounds.height + legendGap2,
|
|
@@ -10847,11 +11240,266 @@ function compileTableLayout(spec, options, theme) {
|
|
|
10847
11240
|
};
|
|
10848
11241
|
}
|
|
10849
11242
|
|
|
11243
|
+
// src/tilemap/compile-tilemap.ts
|
|
11244
|
+
import {
|
|
11245
|
+
adaptTheme as adaptTheme3,
|
|
11246
|
+
buildD3Formatter as buildD3Formatter7,
|
|
11247
|
+
computeChrome as computeChrome5,
|
|
11248
|
+
estimateTextWidth as estimateTextWidth14,
|
|
11249
|
+
formatNumber as formatNumber5,
|
|
11250
|
+
resolveTheme as resolveTheme3,
|
|
11251
|
+
SEQUENTIAL_PALETTES
|
|
11252
|
+
} from "@opendata-ai/openchart-core";
|
|
11253
|
+
var TILE_CORNER_RADIUS = 2;
|
|
11254
|
+
var TILE_STROKE_WIDTH = 0;
|
|
11255
|
+
function compileTileMap(spec, options) {
|
|
11256
|
+
const { spec: normalized } = compile(spec);
|
|
11257
|
+
if (!("type" in normalized) || normalized.type !== "tilemap") {
|
|
11258
|
+
throw new Error(
|
|
11259
|
+
"compileTileMap received a non-tilemap spec. Use compileChart, compileTable, compileGraph, or compileSankey instead."
|
|
11260
|
+
);
|
|
11261
|
+
}
|
|
11262
|
+
const tilemapSpec = normalized;
|
|
11263
|
+
const rawWatermark = spec.watermark;
|
|
11264
|
+
const watermark = rawWatermark !== void 0 ? tilemapSpec.watermark : options.watermark ?? true;
|
|
11265
|
+
const mergedThemeConfig = options.theme ? { ...tilemapSpec.theme, ...options.theme } : tilemapSpec.theme;
|
|
11266
|
+
const lightTheme = resolveTheme3(mergedThemeConfig);
|
|
11267
|
+
let theme = lightTheme;
|
|
11268
|
+
if (options.darkMode) {
|
|
11269
|
+
theme = adaptTheme3(theme);
|
|
11270
|
+
}
|
|
11271
|
+
const isDarkMode = options.darkMode;
|
|
11272
|
+
const chrome = computeChrome5(
|
|
11273
|
+
{
|
|
11274
|
+
title: tilemapSpec.chrome.title,
|
|
11275
|
+
subtitle: tilemapSpec.chrome.subtitle,
|
|
11276
|
+
source: tilemapSpec.chrome.source,
|
|
11277
|
+
byline: tilemapSpec.chrome.byline,
|
|
11278
|
+
footer: tilemapSpec.chrome.footer
|
|
11279
|
+
},
|
|
11280
|
+
theme,
|
|
11281
|
+
options.width,
|
|
11282
|
+
options.measureText,
|
|
11283
|
+
"full",
|
|
11284
|
+
void 0,
|
|
11285
|
+
watermark
|
|
11286
|
+
);
|
|
11287
|
+
const padding = theme.spacing.padding;
|
|
11288
|
+
const fullArea = {
|
|
11289
|
+
x: padding,
|
|
11290
|
+
y: padding + chrome.topHeight,
|
|
11291
|
+
width: options.width - padding * 2,
|
|
11292
|
+
height: options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
|
|
11293
|
+
};
|
|
11294
|
+
if (fullArea.width <= 0 || fullArea.height <= 0) {
|
|
11295
|
+
return emptyLayout2(chrome, theme, options, watermark);
|
|
11296
|
+
}
|
|
11297
|
+
const stateField = tilemapSpec.encoding.state.field;
|
|
11298
|
+
const valueField = tilemapSpec.encoding.value.field;
|
|
11299
|
+
const stateValueMap = /* @__PURE__ */ new Map();
|
|
11300
|
+
for (const row of tilemapSpec.data) {
|
|
11301
|
+
const stateCode = String(row[stateField]);
|
|
11302
|
+
const raw = row[valueField];
|
|
11303
|
+
if (STATE_CODE_SET.has(stateCode) && raw !== null && raw !== void 0) {
|
|
11304
|
+
const value2 = Number(raw);
|
|
11305
|
+
if (!Number.isNaN(value2)) {
|
|
11306
|
+
stateValueMap.set(stateCode, value2);
|
|
11307
|
+
}
|
|
11308
|
+
}
|
|
11309
|
+
}
|
|
11310
|
+
const values = Array.from(stateValueMap.values());
|
|
11311
|
+
const min4 = values.length > 0 ? Math.min(...values) : 0;
|
|
11312
|
+
const max4 = values.length > 0 ? Math.max(...values) : 100;
|
|
11313
|
+
const paletteStops = [...SEQUENTIAL_PALETTES[tilemapSpec.palette] ?? SEQUENTIAL_PALETTES.blue];
|
|
11314
|
+
if (isDarkMode) paletteStops.reverse();
|
|
11315
|
+
const domain = paletteStops.map((_, i) => min4 + i / (paletteStops.length - 1) * (max4 - min4));
|
|
11316
|
+
const colorScale = linear2().domain(domain).range(paletteStops).clamp(true);
|
|
11317
|
+
const showLegend = tilemapSpec.legend?.show !== false;
|
|
11318
|
+
const legendBarHeight = 12;
|
|
11319
|
+
const legendLabelGap = 4;
|
|
11320
|
+
const legendTotalHeight = showLegend ? legendBarHeight + legendLabelGap + 14 : 0;
|
|
11321
|
+
const legendGap2 = showLegend ? 8 : 0;
|
|
11322
|
+
const tileAreaHeight = fullArea.height - legendTotalHeight - legendGap2;
|
|
11323
|
+
const tilePositions = computeTilePositions(fullArea.width, tileAreaHeight, 4);
|
|
11324
|
+
const tileGridOffsetX = fullArea.x + (fullArea.width - tilePositions.gridWidth) / 2;
|
|
11325
|
+
const tileGridOffsetY = fullArea.y;
|
|
11326
|
+
const legendX = tileGridOffsetX;
|
|
11327
|
+
const legendY = tileGridOffsetY + tilePositions.gridHeight + legendGap2;
|
|
11328
|
+
const legendWidth = tilePositions.gridWidth;
|
|
11329
|
+
const formatter = buildD3Formatter7(tilemapSpec.valueFormat) ?? formatNumber5;
|
|
11330
|
+
const neutralFillLight = "#e0e0e0";
|
|
11331
|
+
const neutralFillDark = "#2a2a3e";
|
|
11332
|
+
const neutralStrokeLight = "#d0d0d0";
|
|
11333
|
+
const neutralStrokeDark = "#3a3a50";
|
|
11334
|
+
const neutralFill = isDarkMode ? neutralFillDark : neutralFillLight;
|
|
11335
|
+
const neutralStroke = isDarkMode ? neutralStrokeDark : neutralStrokeLight;
|
|
11336
|
+
const tiles = [];
|
|
11337
|
+
for (const { state: stateCode } of US_STATE_TILES) {
|
|
11338
|
+
const pos = tilePositions.positions.get(stateCode);
|
|
11339
|
+
if (!pos) continue;
|
|
11340
|
+
const hasData = stateValueMap.has(stateCode);
|
|
11341
|
+
const value2 = hasData ? stateValueMap.get(stateCode) : null;
|
|
11342
|
+
const fill = hasData ? colorScale(value2) : neutralFill;
|
|
11343
|
+
const formattedValue = hasData ? formatter(value2) : "\u2013";
|
|
11344
|
+
const labelStyle = {
|
|
11345
|
+
fontFamily: theme.fonts.family,
|
|
11346
|
+
fontSize: tilePositions.tileSize > 24 ? 14 : 11,
|
|
11347
|
+
fontWeight: 700,
|
|
11348
|
+
fill: "#ffffff",
|
|
11349
|
+
lineHeight: 1.2
|
|
11350
|
+
};
|
|
11351
|
+
const valueLabelStyle = {
|
|
11352
|
+
fontFamily: theme.fonts.family,
|
|
11353
|
+
fontSize: tilePositions.tileSize > 24 ? 12 : 10,
|
|
11354
|
+
fontWeight: 400,
|
|
11355
|
+
fill: "#ffffff",
|
|
11356
|
+
lineHeight: 1.2
|
|
11357
|
+
};
|
|
11358
|
+
const valueLabel = tilePositions.tileSize < 24 ? { text: "", x: 0, y: 0, style: valueLabelStyle, visible: false } : {
|
|
11359
|
+
text: formattedValue,
|
|
11360
|
+
x: tileGridOffsetX + pos.x + tilePositions.tileSize / 2,
|
|
11361
|
+
y: tileGridOffsetY + pos.y + tilePositions.tileSize / 2 + 8,
|
|
11362
|
+
style: valueLabelStyle,
|
|
11363
|
+
visible: true
|
|
11364
|
+
};
|
|
11365
|
+
const tile = {
|
|
11366
|
+
type: "tile",
|
|
11367
|
+
stateCode,
|
|
11368
|
+
x: tileGridOffsetX + pos.x,
|
|
11369
|
+
y: tileGridOffsetY + pos.y,
|
|
11370
|
+
size: tilePositions.tileSize,
|
|
11371
|
+
fill,
|
|
11372
|
+
stroke: neutralStroke,
|
|
11373
|
+
strokeWidth: TILE_STROKE_WIDTH,
|
|
11374
|
+
cornerRadius: TILE_CORNER_RADIUS,
|
|
11375
|
+
value: value2 ?? null,
|
|
11376
|
+
formattedValue,
|
|
11377
|
+
hasData,
|
|
11378
|
+
label: {
|
|
11379
|
+
text: stateCode,
|
|
11380
|
+
x: tileGridOffsetX + pos.x + tilePositions.tileSize / 2,
|
|
11381
|
+
y: tileGridOffsetY + pos.y + tilePositions.tileSize / 2 - 4,
|
|
11382
|
+
style: labelStyle,
|
|
11383
|
+
visible: true
|
|
11384
|
+
},
|
|
11385
|
+
valueLabel,
|
|
11386
|
+
data: { state: stateCode, value: value2, stateName: STATE_NAMES[stateCode] ?? stateCode },
|
|
11387
|
+
aria: {
|
|
11388
|
+
role: "img",
|
|
11389
|
+
label: `${STATE_NAMES[stateCode] ?? stateCode}: ${formattedValue}`
|
|
11390
|
+
},
|
|
11391
|
+
animationIndex: 0
|
|
11392
|
+
};
|
|
11393
|
+
tiles.push(tile);
|
|
11394
|
+
}
|
|
11395
|
+
const indices = Array.from({ length: tiles.length }, (_, i) => i);
|
|
11396
|
+
let seed = 42;
|
|
11397
|
+
for (let i = indices.length - 1; i > 0; i--) {
|
|
11398
|
+
seed = seed * 1103515245 + 12345 & 2147483647;
|
|
11399
|
+
const j = seed % (i + 1);
|
|
11400
|
+
[indices[i], indices[j]] = [indices[j], indices[i]];
|
|
11401
|
+
}
|
|
11402
|
+
for (let i = 0; i < tiles.length; i++) {
|
|
11403
|
+
tiles[i].animationIndex = indices[i];
|
|
11404
|
+
}
|
|
11405
|
+
let gradientLegend = null;
|
|
11406
|
+
if (showLegend) {
|
|
11407
|
+
const gradientColorStops = paletteStops.map((color2, i) => ({
|
|
11408
|
+
offset: i / (paletteStops.length - 1),
|
|
11409
|
+
color: color2
|
|
11410
|
+
}));
|
|
11411
|
+
gradientLegend = {
|
|
11412
|
+
type: "gradient",
|
|
11413
|
+
position: "bottom",
|
|
11414
|
+
bounds: { x: legendX, y: legendY, width: legendWidth, height: legendBarHeight },
|
|
11415
|
+
labelStyle: {
|
|
11416
|
+
fontFamily: theme.fonts.family,
|
|
11417
|
+
fontSize: 11,
|
|
11418
|
+
fontWeight: 400,
|
|
11419
|
+
fill: theme.colors.text,
|
|
11420
|
+
lineHeight: 1.2
|
|
11421
|
+
},
|
|
11422
|
+
colorStops: gradientColorStops,
|
|
11423
|
+
minLabel: formatter(min4),
|
|
11424
|
+
maxLabel: formatter(max4)
|
|
11425
|
+
};
|
|
11426
|
+
}
|
|
11427
|
+
const tooltipDescriptors = /* @__PURE__ */ new Map();
|
|
11428
|
+
for (const tile of tiles) {
|
|
11429
|
+
const fields = [
|
|
11430
|
+
{
|
|
11431
|
+
label: "Value",
|
|
11432
|
+
value: tile.formattedValue
|
|
11433
|
+
}
|
|
11434
|
+
];
|
|
11435
|
+
tooltipDescriptors.set(tile.stateCode, {
|
|
11436
|
+
title: STATE_NAMES[tile.stateCode] ?? tile.stateCode,
|
|
11437
|
+
fields
|
|
11438
|
+
});
|
|
11439
|
+
}
|
|
11440
|
+
const a11y = {
|
|
11441
|
+
altText: `Tile map of US states showing values from ${formatter(min4)} to ${formatter(max4)}`,
|
|
11442
|
+
dataTableFallback: tiles.map((t) => [t.stateCode, t.formattedValue]),
|
|
11443
|
+
role: "img",
|
|
11444
|
+
keyboardNavigable: tiles.length > 0
|
|
11445
|
+
};
|
|
11446
|
+
const resolvedAnimation = resolveAnimation(tilemapSpec.animation);
|
|
11447
|
+
return {
|
|
11448
|
+
area: fullArea,
|
|
11449
|
+
chrome,
|
|
11450
|
+
tiles,
|
|
11451
|
+
gradientLegend,
|
|
11452
|
+
tooltipDescriptors,
|
|
11453
|
+
a11y,
|
|
11454
|
+
theme,
|
|
11455
|
+
width: options.width,
|
|
11456
|
+
height: options.height,
|
|
11457
|
+
animation: resolvedAnimation,
|
|
11458
|
+
watermark,
|
|
11459
|
+
measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth14(text, fontSize), height: fontSize }))
|
|
11460
|
+
};
|
|
11461
|
+
}
|
|
11462
|
+
function emptyLayout2(chrome, theme, options, watermark) {
|
|
11463
|
+
return {
|
|
11464
|
+
area: { x: 0, y: 0, width: 0, height: 0 },
|
|
11465
|
+
chrome,
|
|
11466
|
+
tiles: [],
|
|
11467
|
+
gradientLegend: {
|
|
11468
|
+
type: "gradient",
|
|
11469
|
+
position: "bottom",
|
|
11470
|
+
bounds: { x: 0, y: 0, width: 0, height: 0 },
|
|
11471
|
+
labelStyle: {
|
|
11472
|
+
fontFamily: theme.fonts.family,
|
|
11473
|
+
fontSize: 11,
|
|
11474
|
+
fontWeight: 400,
|
|
11475
|
+
fill: theme.colors.text,
|
|
11476
|
+
lineHeight: 1.2
|
|
11477
|
+
},
|
|
11478
|
+
colorStops: [],
|
|
11479
|
+
minLabel: "0",
|
|
11480
|
+
maxLabel: "0"
|
|
11481
|
+
},
|
|
11482
|
+
tooltipDescriptors: /* @__PURE__ */ new Map(),
|
|
11483
|
+
a11y: {
|
|
11484
|
+
altText: "Empty tile map",
|
|
11485
|
+
dataTableFallback: [],
|
|
11486
|
+
role: "img",
|
|
11487
|
+
keyboardNavigable: false
|
|
11488
|
+
},
|
|
11489
|
+
theme,
|
|
11490
|
+
width: options.width,
|
|
11491
|
+
height: options.height,
|
|
11492
|
+
watermark,
|
|
11493
|
+
animation: void 0,
|
|
11494
|
+
measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth14(text, fontSize), height: fontSize }))
|
|
11495
|
+
};
|
|
11496
|
+
}
|
|
11497
|
+
|
|
10850
11498
|
// src/tooltips/compute.ts
|
|
10851
11499
|
import {
|
|
10852
11500
|
buildTemporalFormatter as buildTemporalFormatter2,
|
|
10853
11501
|
formatDate as formatDate3,
|
|
10854
|
-
formatNumber as
|
|
11502
|
+
formatNumber as formatNumber6,
|
|
10855
11503
|
getRepresentativeColor as getRepresentativeColor10
|
|
10856
11504
|
} from "@opendata-ai/openchart-core";
|
|
10857
11505
|
function formatValue(value2, fieldType, format2) {
|
|
@@ -10866,10 +11514,10 @@ function formatValue(value2, fieldType, format2) {
|
|
|
10866
11514
|
try {
|
|
10867
11515
|
return format(format2)(value2);
|
|
10868
11516
|
} catch {
|
|
10869
|
-
return
|
|
11517
|
+
return formatNumber6(value2);
|
|
10870
11518
|
}
|
|
10871
11519
|
}
|
|
10872
|
-
return
|
|
11520
|
+
return formatNumber6(value2);
|
|
10873
11521
|
}
|
|
10874
11522
|
return String(value2);
|
|
10875
11523
|
}
|
|
@@ -11219,8 +11867,78 @@ function runCalculate(data, transform) {
|
|
|
11219
11867
|
}
|
|
11220
11868
|
|
|
11221
11869
|
// src/transforms/filter.ts
|
|
11870
|
+
function applyOffset2(anchor, offset, unit2) {
|
|
11871
|
+
const d = new Date(anchor.getTime());
|
|
11872
|
+
switch (unit2) {
|
|
11873
|
+
case "year":
|
|
11874
|
+
d.setFullYear(d.getFullYear() + offset);
|
|
11875
|
+
break;
|
|
11876
|
+
case "quarter":
|
|
11877
|
+
d.setMonth(d.getMonth() + offset * 3);
|
|
11878
|
+
break;
|
|
11879
|
+
case "month":
|
|
11880
|
+
d.setMonth(d.getMonth() + offset);
|
|
11881
|
+
break;
|
|
11882
|
+
case "week":
|
|
11883
|
+
d.setDate(d.getDate() + offset * 7);
|
|
11884
|
+
break;
|
|
11885
|
+
case "day":
|
|
11886
|
+
d.setDate(d.getDate() + offset);
|
|
11887
|
+
break;
|
|
11888
|
+
}
|
|
11889
|
+
return d.getTime();
|
|
11890
|
+
}
|
|
11891
|
+
function resolveRef(data, field, ref) {
|
|
11892
|
+
let anchorMs = ref.anchor === "max" ? -Infinity : Infinity;
|
|
11893
|
+
for (const row of data) {
|
|
11894
|
+
const val = row[field];
|
|
11895
|
+
if (val == null) continue;
|
|
11896
|
+
const ms = new Date(val).getTime();
|
|
11897
|
+
if (Number.isNaN(ms)) continue;
|
|
11898
|
+
if (ref.anchor === "max" && ms > anchorMs) anchorMs = ms;
|
|
11899
|
+
if (ref.anchor === "min" && ms < anchorMs) anchorMs = ms;
|
|
11900
|
+
}
|
|
11901
|
+
if (!Number.isFinite(anchorMs)) return 0;
|
|
11902
|
+
return applyOffset2(new Date(anchorMs), ref.offset, ref.unit);
|
|
11903
|
+
}
|
|
11904
|
+
function resolveRelativeRefs(data, predicate) {
|
|
11905
|
+
if ("and" in predicate) {
|
|
11906
|
+
return { and: predicate.and.map((p) => resolveRelativeRefs(data, p)) };
|
|
11907
|
+
}
|
|
11908
|
+
if ("or" in predicate) {
|
|
11909
|
+
return { or: predicate.or.map((p) => resolveRelativeRefs(data, p)) };
|
|
11910
|
+
}
|
|
11911
|
+
if ("not" in predicate) {
|
|
11912
|
+
return { not: resolveRelativeRefs(data, predicate.not) };
|
|
11913
|
+
}
|
|
11914
|
+
if ("field" in predicate) {
|
|
11915
|
+
const fp = predicate;
|
|
11916
|
+
let needsCopy = false;
|
|
11917
|
+
const resolved = {};
|
|
11918
|
+
for (const prop of ["lt", "lte", "gt", "gte"]) {
|
|
11919
|
+
if (isRelativeTimeRef(fp[prop])) {
|
|
11920
|
+
resolved[prop] = resolveRef(data, fp.field, fp[prop]);
|
|
11921
|
+
needsCopy = true;
|
|
11922
|
+
}
|
|
11923
|
+
}
|
|
11924
|
+
if (fp.range) {
|
|
11925
|
+
const [lo, hi] = fp.range;
|
|
11926
|
+
const loResolved = isRelativeTimeRef(lo) ? resolveRef(data, fp.field, lo) : lo;
|
|
11927
|
+
const hiResolved = isRelativeTimeRef(hi) ? resolveRef(data, fp.field, hi) : hi;
|
|
11928
|
+
if (isRelativeTimeRef(lo) || isRelativeTimeRef(hi)) {
|
|
11929
|
+
resolved.range = [loResolved, hiResolved];
|
|
11930
|
+
needsCopy = true;
|
|
11931
|
+
}
|
|
11932
|
+
}
|
|
11933
|
+
if (needsCopy) {
|
|
11934
|
+
return { ...fp, ...resolved };
|
|
11935
|
+
}
|
|
11936
|
+
}
|
|
11937
|
+
return predicate;
|
|
11938
|
+
}
|
|
11222
11939
|
function runFilter(data, predicate) {
|
|
11223
|
-
|
|
11940
|
+
const resolved = resolveRelativeRefs(data, predicate);
|
|
11941
|
+
return data.filter((datum) => evaluatePredicate(datum, resolved));
|
|
11224
11942
|
}
|
|
11225
11943
|
|
|
11226
11944
|
// src/transforms/fold.ts
|
|
@@ -11312,6 +12030,131 @@ function runTimeUnit(data, transform) {
|
|
|
11312
12030
|
});
|
|
11313
12031
|
}
|
|
11314
12032
|
|
|
12033
|
+
// src/transforms/window.ts
|
|
12034
|
+
function groupKey2(row, groupby) {
|
|
12035
|
+
return groupby.map((f) => String(row[f] ?? "")).join("\0");
|
|
12036
|
+
}
|
|
12037
|
+
function tryParseDate(val) {
|
|
12038
|
+
if (val == null) return NaN;
|
|
12039
|
+
if (typeof val === "number") return new Date(val).getTime();
|
|
12040
|
+
if (typeof val === "string" && (val.includes("-") || val.includes("T"))) {
|
|
12041
|
+
const ms = new Date(val).getTime();
|
|
12042
|
+
return ms;
|
|
12043
|
+
}
|
|
12044
|
+
return NaN;
|
|
12045
|
+
}
|
|
12046
|
+
function compareValues(a, b, order) {
|
|
12047
|
+
const dir = order === "descending" ? -1 : 1;
|
|
12048
|
+
const dateA = tryParseDate(a);
|
|
12049
|
+
const dateB = tryParseDate(b);
|
|
12050
|
+
if (!Number.isNaN(dateA) && !Number.isNaN(dateB)) {
|
|
12051
|
+
return dir * (dateA - dateB);
|
|
12052
|
+
}
|
|
12053
|
+
const numA = Number(a);
|
|
12054
|
+
const numB = Number(b);
|
|
12055
|
+
if (Number.isFinite(numA) && Number.isFinite(numB)) {
|
|
12056
|
+
return dir * (numA - numB);
|
|
12057
|
+
}
|
|
12058
|
+
return dir * String(a ?? "").localeCompare(String(b ?? ""));
|
|
12059
|
+
}
|
|
12060
|
+
function runWindow(data, transform) {
|
|
12061
|
+
if (data.length === 0) return [];
|
|
12062
|
+
const { window: windowDefs, sort, groupby = [] } = transform;
|
|
12063
|
+
const indexed = data.map((row, i) => ({ row, originalIndex: i }));
|
|
12064
|
+
const groups = /* @__PURE__ */ new Map();
|
|
12065
|
+
for (const entry of indexed) {
|
|
12066
|
+
const key = groupby.length > 0 ? groupKey2(entry.row, groupby) : "";
|
|
12067
|
+
const existing = groups.get(key);
|
|
12068
|
+
if (existing) {
|
|
12069
|
+
existing.push(entry);
|
|
12070
|
+
} else {
|
|
12071
|
+
groups.set(key, [entry]);
|
|
12072
|
+
}
|
|
12073
|
+
}
|
|
12074
|
+
const result = new Array(data.length);
|
|
12075
|
+
for (const groupEntries of groups.values()) {
|
|
12076
|
+
const sorted = [...groupEntries].sort((a, b) => {
|
|
12077
|
+
for (const s of sort) {
|
|
12078
|
+
const cmp = compareValues(a.row[s.field], b.row[s.field], s.order ?? "ascending");
|
|
12079
|
+
if (cmp !== 0) return cmp;
|
|
12080
|
+
}
|
|
12081
|
+
return 0;
|
|
12082
|
+
});
|
|
12083
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
12084
|
+
const entry = sorted[i];
|
|
12085
|
+
const outRow = { ...entry.row };
|
|
12086
|
+
for (const def of windowDefs) {
|
|
12087
|
+
const offset = def.offset ?? 1;
|
|
12088
|
+
let computed = null;
|
|
12089
|
+
switch (def.op) {
|
|
12090
|
+
case "lag": {
|
|
12091
|
+
const lagIdx = i - offset;
|
|
12092
|
+
computed = lagIdx >= 0 ? sorted[lagIdx].row[def.field] ?? null : null;
|
|
12093
|
+
break;
|
|
12094
|
+
}
|
|
12095
|
+
case "lead": {
|
|
12096
|
+
const leadIdx = i + offset;
|
|
12097
|
+
computed = leadIdx < sorted.length ? sorted[leadIdx].row[def.field] ?? null : null;
|
|
12098
|
+
break;
|
|
12099
|
+
}
|
|
12100
|
+
case "diff": {
|
|
12101
|
+
const lagIdx = i - offset;
|
|
12102
|
+
if (lagIdx >= 0) {
|
|
12103
|
+
const current = Number(entry.row[def.field]);
|
|
12104
|
+
const lagged = Number(sorted[lagIdx].row[def.field]);
|
|
12105
|
+
computed = Number.isFinite(current) && Number.isFinite(lagged) ? current - lagged : null;
|
|
12106
|
+
}
|
|
12107
|
+
break;
|
|
12108
|
+
}
|
|
12109
|
+
case "pct_change": {
|
|
12110
|
+
const lagIdx = i - offset;
|
|
12111
|
+
if (lagIdx >= 0) {
|
|
12112
|
+
const current = Number(entry.row[def.field]);
|
|
12113
|
+
const lagged = Number(sorted[lagIdx].row[def.field]);
|
|
12114
|
+
if (Number.isFinite(current) && Number.isFinite(lagged) && lagged !== 0) {
|
|
12115
|
+
computed = (current - lagged) / lagged;
|
|
12116
|
+
}
|
|
12117
|
+
}
|
|
12118
|
+
break;
|
|
12119
|
+
}
|
|
12120
|
+
case "cumsum": {
|
|
12121
|
+
const val = Number(entry.row[def.field]);
|
|
12122
|
+
const addend = Number.isFinite(val) ? val : 0;
|
|
12123
|
+
if (i === 0) {
|
|
12124
|
+
computed = addend;
|
|
12125
|
+
} else {
|
|
12126
|
+
const prev = Number(result[sorted[i - 1].originalIndex]?.[def.as] ?? 0);
|
|
12127
|
+
computed = prev + addend;
|
|
12128
|
+
}
|
|
12129
|
+
break;
|
|
12130
|
+
}
|
|
12131
|
+
case "rank": {
|
|
12132
|
+
let rank = i + 1;
|
|
12133
|
+
for (let j = 0; j < i; j++) {
|
|
12134
|
+
const isTie = sort.every(
|
|
12135
|
+
(s) => String(sorted[j].row[s.field]) === String(entry.row[s.field])
|
|
12136
|
+
);
|
|
12137
|
+
if (isTie) {
|
|
12138
|
+
rank = result[sorted[j].originalIndex]?.[def.as];
|
|
12139
|
+
break;
|
|
12140
|
+
}
|
|
12141
|
+
}
|
|
12142
|
+
computed = rank;
|
|
12143
|
+
break;
|
|
12144
|
+
}
|
|
12145
|
+
case "first_value": {
|
|
12146
|
+
computed = sorted[0].row[def.field] ?? null;
|
|
12147
|
+
break;
|
|
12148
|
+
}
|
|
12149
|
+
}
|
|
12150
|
+
outRow[def.as] = computed;
|
|
12151
|
+
}
|
|
12152
|
+
result[entry.originalIndex] = outRow;
|
|
12153
|
+
}
|
|
12154
|
+
}
|
|
12155
|
+
return result;
|
|
12156
|
+
}
|
|
12157
|
+
|
|
11315
12158
|
// src/transforms/index.ts
|
|
11316
12159
|
function runTransforms(data, transforms) {
|
|
11317
12160
|
let result = data;
|
|
@@ -11328,6 +12171,8 @@ function runTransforms(data, transforms) {
|
|
|
11328
12171
|
result = runAggregate(result, transform);
|
|
11329
12172
|
} else if ("fold" in transform) {
|
|
11330
12173
|
result = runFold(result, transform);
|
|
12174
|
+
} else if ("window" in transform) {
|
|
12175
|
+
result = runWindow(result, transform);
|
|
11331
12176
|
}
|
|
11332
12177
|
}
|
|
11333
12178
|
return result;
|
|
@@ -11394,7 +12239,7 @@ function compileChart(spec, options) {
|
|
|
11394
12239
|
}
|
|
11395
12240
|
let chartSpec = normalized;
|
|
11396
12241
|
const rawWatermark = expandedSpec.watermark;
|
|
11397
|
-
|
|
12242
|
+
let watermark = rawWatermark !== void 0 ? chartSpec.watermark : options.watermark ?? true;
|
|
11398
12243
|
const rawTransforms = expandedSpec.transform;
|
|
11399
12244
|
if (rawTransforms && rawTransforms.length > 0) {
|
|
11400
12245
|
chartSpec = { ...chartSpec, data: runTransforms(chartSpec.data, rawTransforms) };
|
|
@@ -11404,6 +12249,21 @@ function compileChart(spec, options) {
|
|
|
11404
12249
|
let strategy = getLayoutStrategy(breakpoint, heightClass);
|
|
11405
12250
|
const rawSpec = expandedSpec;
|
|
11406
12251
|
const overrides = rawSpec.overrides;
|
|
12252
|
+
const rawEncoding = rawSpec.encoding;
|
|
12253
|
+
const bpForExplicit = overrides?.[breakpoint];
|
|
12254
|
+
const bpEncoding = bpForExplicit?.encoding;
|
|
12255
|
+
const hasChromeKeys = (v) => !!v && typeof v === "object" && Object.keys(v).length > 0;
|
|
12256
|
+
const userExplicit = {
|
|
12257
|
+
chrome: hasChromeKeys(rawSpec.chrome) || hasChromeKeys(bpForExplicit?.chrome),
|
|
12258
|
+
legend: rawSpec.legend !== void 0 || bpForExplicit?.legend !== void 0,
|
|
12259
|
+
xAxis: rawEncoding?.x?.axis !== void 0 || bpEncoding?.x?.axis !== void 0,
|
|
12260
|
+
yAxis: rawEncoding?.y?.axis !== void 0 || bpEncoding?.y?.axis !== void 0,
|
|
12261
|
+
labels: rawSpec.labels !== void 0 || bpForExplicit?.labels !== void 0,
|
|
12262
|
+
animation: rawSpec.animation !== void 0 || bpForExplicit?.animation !== void 0,
|
|
12263
|
+
watermark: rawSpec.watermark !== void 0 || bpForExplicit?.watermark !== void 0,
|
|
12264
|
+
crosshair: rawSpec.crosshair !== void 0 || bpForExplicit?.crosshair !== void 0
|
|
12265
|
+
};
|
|
12266
|
+
chartSpec = { ...chartSpec, userExplicit };
|
|
11407
12267
|
if (overrides?.[breakpoint]) {
|
|
11408
12268
|
const bp = overrides[breakpoint];
|
|
11409
12269
|
if (bp.chrome) {
|
|
@@ -11447,13 +12307,84 @@ function compileChart(spec, options) {
|
|
|
11447
12307
|
};
|
|
11448
12308
|
strategy = { ...strategy, annotationPosition: "inline" };
|
|
11449
12309
|
}
|
|
12310
|
+
if (bp.display !== void 0) {
|
|
12311
|
+
chartSpec = {
|
|
12312
|
+
...chartSpec,
|
|
12313
|
+
display: bp.display
|
|
12314
|
+
};
|
|
12315
|
+
}
|
|
12316
|
+
if (bp.encoding !== void 0) {
|
|
12317
|
+
const bpEnc = bp.encoding;
|
|
12318
|
+
const mergedEncoding = { ...chartSpec.encoding };
|
|
12319
|
+
const NESTED_CHANNEL_KEYS = ["axis", "scale"];
|
|
12320
|
+
for (const channel of Object.keys(bpEnc)) {
|
|
12321
|
+
const baseCh = mergedEncoding[channel];
|
|
12322
|
+
const bpCh = bpEnc[channel];
|
|
12323
|
+
if (bpCh && baseCh) {
|
|
12324
|
+
const merged = { ...baseCh, ...bpCh };
|
|
12325
|
+
for (const key of NESTED_CHANNEL_KEYS) {
|
|
12326
|
+
const baseNested = baseCh[key];
|
|
12327
|
+
const bpNested = bpCh[key];
|
|
12328
|
+
if (baseNested && bpNested && typeof baseNested === "object" && typeof bpNested === "object" && !Array.isArray(baseNested) && !Array.isArray(bpNested)) {
|
|
12329
|
+
merged[key] = { ...baseNested, ...bpNested };
|
|
12330
|
+
}
|
|
12331
|
+
}
|
|
12332
|
+
mergedEncoding[channel] = merged;
|
|
12333
|
+
} else if (bpCh) {
|
|
12334
|
+
mergedEncoding[channel] = bpCh;
|
|
12335
|
+
}
|
|
12336
|
+
}
|
|
12337
|
+
chartSpec = {
|
|
12338
|
+
...chartSpec,
|
|
12339
|
+
encoding: mergedEncoding
|
|
12340
|
+
};
|
|
12341
|
+
}
|
|
12342
|
+
if (typeof bp.watermark === "boolean") {
|
|
12343
|
+
watermark = bp.watermark;
|
|
12344
|
+
chartSpec = { ...chartSpec, watermark };
|
|
12345
|
+
}
|
|
12346
|
+
}
|
|
12347
|
+
if (chartSpec.display === "sparkline" && !chartSpec.userExplicit.labels) {
|
|
12348
|
+
chartSpec = {
|
|
12349
|
+
...chartSpec,
|
|
12350
|
+
labels: { ...chartSpec.labels, density: "none" }
|
|
12351
|
+
};
|
|
12352
|
+
}
|
|
12353
|
+
let rawAnimationSpec = overrides?.[breakpoint]?.animation ?? rawSpec.animation;
|
|
12354
|
+
if (rawAnimationSpec === void 0 && chartSpec.display === "sparkline") {
|
|
12355
|
+
rawAnimationSpec = false;
|
|
12356
|
+
}
|
|
12357
|
+
if (chartSpec.display === "sparkline" && rawAnimationSpec !== false && rawAnimationSpec !== void 0) {
|
|
12358
|
+
const SPARK_DURATION = 1100;
|
|
12359
|
+
if (rawAnimationSpec === true) {
|
|
12360
|
+
rawAnimationSpec = { enter: { duration: SPARK_DURATION } };
|
|
12361
|
+
} else if (typeof rawAnimationSpec === "object") {
|
|
12362
|
+
const cfg = rawAnimationSpec;
|
|
12363
|
+
const enter = cfg.enter;
|
|
12364
|
+
if (enter === void 0 || enter === true) {
|
|
12365
|
+
rawAnimationSpec = {
|
|
12366
|
+
...cfg,
|
|
12367
|
+
enter: { duration: SPARK_DURATION }
|
|
12368
|
+
};
|
|
12369
|
+
} else if (typeof enter === "object" && enter !== null && enter.duration === void 0) {
|
|
12370
|
+
rawAnimationSpec = {
|
|
12371
|
+
...cfg,
|
|
12372
|
+
enter: { ...enter, duration: SPARK_DURATION }
|
|
12373
|
+
};
|
|
12374
|
+
}
|
|
12375
|
+
}
|
|
11450
12376
|
}
|
|
11451
|
-
const rawAnimationSpec = overrides?.[breakpoint]?.animation ?? rawSpec.animation;
|
|
11452
12377
|
const resolvedAnimation = resolveAnimation(rawAnimationSpec);
|
|
12378
|
+
const rawCrosshair = bpForExplicit?.crosshair ?? rawSpec.crosshair;
|
|
12379
|
+
const crosshair = chartSpec.display === "sparkline" && !chartSpec.userExplicit.crosshair ? false : rawCrosshair === true;
|
|
12380
|
+
if (chartSpec.display === "sparkline" && !chartSpec.userExplicit.watermark) {
|
|
12381
|
+
watermark = false;
|
|
12382
|
+
chartSpec = { ...chartSpec, watermark: false };
|
|
12383
|
+
}
|
|
11453
12384
|
const mergedThemeConfig = options.theme ? { ...chartSpec.theme, ...options.theme } : chartSpec.theme;
|
|
11454
|
-
let theme =
|
|
12385
|
+
let theme = resolveTheme4(mergedThemeConfig);
|
|
11455
12386
|
if (options.darkMode) {
|
|
11456
|
-
theme =
|
|
12387
|
+
theme = adaptTheme4(theme);
|
|
11457
12388
|
}
|
|
11458
12389
|
const preliminaryArea = {
|
|
11459
12390
|
x: 0,
|
|
@@ -11465,7 +12396,7 @@ function compileChart(spec, options) {
|
|
|
11465
12396
|
const dims = computeDimensions(chartSpec, options, legendLayout, theme, strategy, watermark);
|
|
11466
12397
|
const chartArea = dims.chartArea;
|
|
11467
12398
|
const legendArea = { ...chartArea };
|
|
11468
|
-
if (legendLayout.entries.length > 0) {
|
|
12399
|
+
if ("entries" in legendLayout && legendLayout.entries.length > 0) {
|
|
11469
12400
|
const gap = legendGap(options.width);
|
|
11470
12401
|
switch (legendLayout.position) {
|
|
11471
12402
|
case "top":
|
|
@@ -11494,7 +12425,14 @@ function compileChart(spec, options) {
|
|
|
11494
12425
|
applyColorScaleRange(scales, renderSpec.encoding, theme);
|
|
11495
12426
|
scales.defaultColor = chartSpec.markDef.fill ?? chartSpec.markDef.stroke ?? theme.colors.categorical[0];
|
|
11496
12427
|
const isRadial = chartSpec.markType === "arc";
|
|
11497
|
-
const
|
|
12428
|
+
const skipX = chartSpec.display === "sparkline" && !chartSpec.userExplicit.xAxis;
|
|
12429
|
+
const skipY = chartSpec.display === "sparkline" && !chartSpec.userExplicit.yAxis;
|
|
12430
|
+
const axes = isRadial ? { x: void 0, y: void 0 } : computeAxes(scales, chartArea, strategy, theme, options.measureText, {
|
|
12431
|
+
data: renderSpec.data,
|
|
12432
|
+
encoding: renderSpec.encoding,
|
|
12433
|
+
skipX,
|
|
12434
|
+
skipY
|
|
12435
|
+
});
|
|
11498
12436
|
if (!isRadial) {
|
|
11499
12437
|
computeGridlines(axes, chartArea);
|
|
11500
12438
|
}
|
|
@@ -11569,6 +12507,8 @@ function compileChart(spec, options) {
|
|
|
11569
12507
|
},
|
|
11570
12508
|
animation: resolvedAnimation,
|
|
11571
12509
|
watermark,
|
|
12510
|
+
display: chartSpec.display,
|
|
12511
|
+
crosshair,
|
|
11572
12512
|
measureText: options.measureText
|
|
11573
12513
|
};
|
|
11574
12514
|
}
|
|
@@ -11588,7 +12528,8 @@ function compileLayer(spec, options) {
|
|
|
11588
12528
|
const primaryLayout = compileChart(primarySpec, options);
|
|
11589
12529
|
const allMarks = [];
|
|
11590
12530
|
const seenLabels = /* @__PURE__ */ new Set();
|
|
11591
|
-
const
|
|
12531
|
+
const pLegend = primaryLayout.legend;
|
|
12532
|
+
const mergedLegendEntries = "entries" in pLegend ? [...pLegend.entries] : [];
|
|
11592
12533
|
for (const entry of mergedLegendEntries) {
|
|
11593
12534
|
seenLabels.add(entry.label);
|
|
11594
12535
|
}
|
|
@@ -11600,10 +12541,13 @@ function compileLayer(spec, options) {
|
|
|
11600
12541
|
for (const { leaf } of indexedLeaves) {
|
|
11601
12542
|
const leafLayout = compileChart(leaf, options);
|
|
11602
12543
|
allMarks.push(...leafLayout.marks);
|
|
11603
|
-
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
12544
|
+
const leafLeg = leafLayout.legend;
|
|
12545
|
+
if ("entries" in leafLeg) {
|
|
12546
|
+
for (const entry of leafLeg.entries) {
|
|
12547
|
+
if (!seenLabels.has(entry.label)) {
|
|
12548
|
+
seenLabels.add(entry.label);
|
|
12549
|
+
mergedLegendEntries.push(entry);
|
|
12550
|
+
}
|
|
11607
12551
|
}
|
|
11608
12552
|
}
|
|
11609
12553
|
}
|
|
@@ -11612,7 +12556,7 @@ function compileLayer(spec, options) {
|
|
|
11612
12556
|
marks: allMarks,
|
|
11613
12557
|
legend: {
|
|
11614
12558
|
...primaryLayout.legend,
|
|
11615
|
-
entries: mergedLegendEntries
|
|
12559
|
+
..."entries" in pLegend ? { entries: mergedLegendEntries } : {}
|
|
11616
12560
|
}
|
|
11617
12561
|
};
|
|
11618
12562
|
}
|
|
@@ -11626,7 +12570,7 @@ function estimateYAxisLabelWidth(data, encoding, baseFontSize) {
|
|
|
11626
12570
|
let maxWidth = 0;
|
|
11627
12571
|
for (const row of data) {
|
|
11628
12572
|
const label = String(row[yField] ?? "");
|
|
11629
|
-
const w =
|
|
12573
|
+
const w = estimateTextWidth15(label, baseFontSize, 400);
|
|
11630
12574
|
if (w > maxWidth) maxWidth = w;
|
|
11631
12575
|
}
|
|
11632
12576
|
return maxWidth > 0 ? maxWidth + 10 : 40;
|
|
@@ -11655,7 +12599,7 @@ function estimateYAxisLabelWidth(data, encoding, baseFontSize) {
|
|
|
11655
12599
|
}
|
|
11656
12600
|
const hasNeg = data.some((r) => Number(r[yField]) < 0);
|
|
11657
12601
|
const labelEst = (hasNeg ? "-" : "") + sampleLabel;
|
|
11658
|
-
return
|
|
12602
|
+
return estimateTextWidth15(labelEst, baseFontSize, 400) + 10;
|
|
11659
12603
|
}
|
|
11660
12604
|
function compileLayerIndependent(leaves, layerSpec, options) {
|
|
11661
12605
|
if (leaves.length > 2) {
|
|
@@ -11672,7 +12616,7 @@ function compileLayerIndependent(leaves, layerSpec, options) {
|
|
|
11672
12616
|
`Dual-axis charts require matching x-field types across layers. Layer 0 has '${xType0}', layer 1 has '${xType1}'.`
|
|
11673
12617
|
);
|
|
11674
12618
|
}
|
|
11675
|
-
const theme =
|
|
12619
|
+
const theme = resolveTheme4(layerSpec.theme ?? leaf1.theme);
|
|
11676
12620
|
const axisFontSize = theme.fonts?.sizes?.axisTick ?? 11;
|
|
11677
12621
|
const rightAxisWidth = estimateYAxisLabelWidth(leaf1.data, leaf1.encoding, axisFontSize);
|
|
11678
12622
|
const hasRightAxisTitle = !!leaf1.encoding?.y?.axis?.title;
|
|
@@ -11789,9 +12733,12 @@ function compileLayerIndependent(leaves, layerSpec, options) {
|
|
|
11789
12733
|
return tagged;
|
|
11790
12734
|
});
|
|
11791
12735
|
const seenLabels = /* @__PURE__ */ new Set();
|
|
11792
|
-
const
|
|
12736
|
+
const l0Legend = layout0.legend;
|
|
12737
|
+
const l1Legend = layout1.legend;
|
|
12738
|
+
const mergedLegendEntries = "entries" in l0Legend ? [...l0Legend.entries] : [];
|
|
11793
12739
|
for (const entry of mergedLegendEntries) seenLabels.add(entry.label);
|
|
11794
|
-
|
|
12740
|
+
const l1Entries = "entries" in l1Legend ? l1Legend.entries : [];
|
|
12741
|
+
for (const entry of l1Entries) {
|
|
11795
12742
|
if (!seenLabels.has(entry.label)) {
|
|
11796
12743
|
seenLabels.add(entry.label);
|
|
11797
12744
|
mergedLegendEntries.push(entry);
|
|
@@ -11821,7 +12768,7 @@ function compileLayerIndependent(leaves, layerSpec, options) {
|
|
|
11821
12768
|
marks,
|
|
11822
12769
|
legend: {
|
|
11823
12770
|
...layout0.legend,
|
|
11824
|
-
entries: mergedLegendEntries
|
|
12771
|
+
..."entries" in l0Legend ? { entries: mergedLegendEntries } : {}
|
|
11825
12772
|
},
|
|
11826
12773
|
tooltipDescriptors: mergedTooltips
|
|
11827
12774
|
};
|
|
@@ -11934,9 +12881,9 @@ function compileTable(spec, options) {
|
|
|
11934
12881
|
}
|
|
11935
12882
|
const tableSpec = normalized;
|
|
11936
12883
|
const mergedThemeConfig = options.theme ? { ...tableSpec.theme, ...options.theme } : tableSpec.theme;
|
|
11937
|
-
let theme =
|
|
12884
|
+
let theme = resolveTheme4(mergedThemeConfig);
|
|
11938
12885
|
if (options.darkMode) {
|
|
11939
|
-
theme =
|
|
12886
|
+
theme = adaptTheme4(theme);
|
|
11940
12887
|
}
|
|
11941
12888
|
const rawWatermark = spec.watermark;
|
|
11942
12889
|
const watermark = rawWatermark !== void 0 ? tableSpec.watermark : options.watermark ?? true;
|
|
@@ -11948,6 +12895,9 @@ function compileGraph2(spec, options) {
|
|
|
11948
12895
|
function compileSankey2(spec, options) {
|
|
11949
12896
|
return compileSankey(spec, options);
|
|
11950
12897
|
}
|
|
12898
|
+
function compileTileMap2(spec, options) {
|
|
12899
|
+
return compileTileMap(spec, options);
|
|
12900
|
+
}
|
|
11951
12901
|
export {
|
|
11952
12902
|
clampStaggerDelay,
|
|
11953
12903
|
clearRenderers,
|
|
@@ -11957,6 +12907,7 @@ export {
|
|
|
11957
12907
|
compileLayer,
|
|
11958
12908
|
compileSankey2 as compileSankey,
|
|
11959
12909
|
compileTable,
|
|
12910
|
+
compileTileMap2 as compileTileMap,
|
|
11960
12911
|
evaluatePredicate,
|
|
11961
12912
|
getChartRenderer,
|
|
11962
12913
|
isConditionalValueDef,
|