@opendata-ai/openchart-engine 6.25.3 → 6.26.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 +46 -4
- package/dist/index.js +888 -80
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/compound-labels.test.ts +147 -0
- package/src/compile.ts +64 -21
- package/src/compiler/normalize.ts +74 -7
- package/src/compiler/types.ts +3 -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 +27 -1
- package/src/layout/dimensions.ts +21 -3
- 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",
|
|
@@ -6543,9 +6553,146 @@ import {
|
|
|
6543
6553
|
isLayerSpec,
|
|
6544
6554
|
isSankeySpec,
|
|
6545
6555
|
isTableSpec,
|
|
6556
|
+
isTileMapSpec,
|
|
6546
6557
|
resolveMarkDef,
|
|
6547
6558
|
resolveMarkType
|
|
6548
6559
|
} from "@opendata-ai/openchart-core";
|
|
6560
|
+
|
|
6561
|
+
// src/tilemap/layout.ts
|
|
6562
|
+
var US_STATE_TILES = [
|
|
6563
|
+
// Row 0
|
|
6564
|
+
{ state: "ME", col: 10, row: 0 },
|
|
6565
|
+
// Row 1
|
|
6566
|
+
{ state: "VT", col: 9, row: 1 },
|
|
6567
|
+
{ state: "NH", col: 10, row: 1 },
|
|
6568
|
+
// Row 2
|
|
6569
|
+
{ state: "WA", col: 0, row: 2 },
|
|
6570
|
+
{ state: "ID", col: 1, row: 2 },
|
|
6571
|
+
{ state: "MT", col: 2, row: 2 },
|
|
6572
|
+
{ state: "ND", col: 3, row: 2 },
|
|
6573
|
+
{ state: "MN", col: 4, row: 2 },
|
|
6574
|
+
{ state: "WI", col: 5, row: 2 },
|
|
6575
|
+
{ state: "MI", col: 6, row: 2 },
|
|
6576
|
+
{ state: "NY", col: 8, row: 2 },
|
|
6577
|
+
{ state: "MA", col: 9, row: 2 },
|
|
6578
|
+
// Row 3
|
|
6579
|
+
{ state: "OR", col: 0, row: 3 },
|
|
6580
|
+
{ state: "NV", col: 1, row: 3 },
|
|
6581
|
+
{ state: "WY", col: 2, row: 3 },
|
|
6582
|
+
{ state: "SD", col: 3, row: 3 },
|
|
6583
|
+
{ state: "IA", col: 4, row: 3 },
|
|
6584
|
+
{ state: "IL", col: 5, row: 3 },
|
|
6585
|
+
{ state: "IN", col: 6, row: 3 },
|
|
6586
|
+
{ state: "OH", col: 7, row: 3 },
|
|
6587
|
+
{ state: "PA", col: 8, row: 3 },
|
|
6588
|
+
{ state: "NJ", col: 9, row: 3 },
|
|
6589
|
+
{ state: "CT", col: 10, row: 3 },
|
|
6590
|
+
// Row 4
|
|
6591
|
+
{ state: "CA", col: 0, row: 4 },
|
|
6592
|
+
{ state: "UT", col: 1, row: 4 },
|
|
6593
|
+
{ state: "CO", col: 2, row: 4 },
|
|
6594
|
+
{ state: "NE", col: 3, row: 4 },
|
|
6595
|
+
{ state: "MO", col: 4, row: 4 },
|
|
6596
|
+
{ state: "KY", col: 5, row: 4 },
|
|
6597
|
+
{ state: "WV", col: 6, row: 4 },
|
|
6598
|
+
{ state: "VA", col: 7, row: 4 },
|
|
6599
|
+
{ state: "MD", col: 8, row: 4 },
|
|
6600
|
+
{ state: "DE", col: 9, row: 4 },
|
|
6601
|
+
{ state: "RI", col: 10, row: 4 },
|
|
6602
|
+
// Row 5
|
|
6603
|
+
{ state: "AZ", col: 1, row: 5 },
|
|
6604
|
+
{ state: "NM", col: 2, row: 5 },
|
|
6605
|
+
{ state: "KS", col: 3, row: 5 },
|
|
6606
|
+
{ state: "AR", col: 4, row: 5 },
|
|
6607
|
+
{ state: "TN", col: 5, row: 5 },
|
|
6608
|
+
{ state: "NC", col: 6, row: 5 },
|
|
6609
|
+
{ state: "SC", col: 7, row: 5 },
|
|
6610
|
+
{ state: "DC", col: 8, row: 5 },
|
|
6611
|
+
// Row 6
|
|
6612
|
+
{ state: "AK", col: 0, row: 6 },
|
|
6613
|
+
{ state: "OK", col: 3, row: 6 },
|
|
6614
|
+
{ state: "LA", col: 4, row: 6 },
|
|
6615
|
+
{ state: "MS", col: 5, row: 6 },
|
|
6616
|
+
{ state: "AL", col: 6, row: 6 },
|
|
6617
|
+
{ state: "GA", col: 7, row: 6 },
|
|
6618
|
+
// Row 7
|
|
6619
|
+
{ state: "HI", col: 1, row: 7 },
|
|
6620
|
+
{ state: "TX", col: 3, row: 7 },
|
|
6621
|
+
{ state: "FL", col: 7, row: 7 }
|
|
6622
|
+
];
|
|
6623
|
+
var STATE_CODE_SET = new Set(US_STATE_TILES.map((t) => t.state));
|
|
6624
|
+
var STATE_NAMES = {
|
|
6625
|
+
AL: "Alabama",
|
|
6626
|
+
AK: "Alaska",
|
|
6627
|
+
AZ: "Arizona",
|
|
6628
|
+
AR: "Arkansas",
|
|
6629
|
+
CA: "California",
|
|
6630
|
+
CO: "Colorado",
|
|
6631
|
+
CT: "Connecticut",
|
|
6632
|
+
DE: "Delaware",
|
|
6633
|
+
DC: "District of Columbia",
|
|
6634
|
+
FL: "Florida",
|
|
6635
|
+
GA: "Georgia",
|
|
6636
|
+
HI: "Hawaii",
|
|
6637
|
+
ID: "Idaho",
|
|
6638
|
+
IL: "Illinois",
|
|
6639
|
+
IN: "Indiana",
|
|
6640
|
+
IA: "Iowa",
|
|
6641
|
+
KS: "Kansas",
|
|
6642
|
+
KY: "Kentucky",
|
|
6643
|
+
LA: "Louisiana",
|
|
6644
|
+
ME: "Maine",
|
|
6645
|
+
MD: "Maryland",
|
|
6646
|
+
MA: "Massachusetts",
|
|
6647
|
+
MI: "Michigan",
|
|
6648
|
+
MN: "Minnesota",
|
|
6649
|
+
MS: "Mississippi",
|
|
6650
|
+
MO: "Missouri",
|
|
6651
|
+
MT: "Montana",
|
|
6652
|
+
NE: "Nebraska",
|
|
6653
|
+
NV: "Nevada",
|
|
6654
|
+
NH: "New Hampshire",
|
|
6655
|
+
NJ: "New Jersey",
|
|
6656
|
+
NM: "New Mexico",
|
|
6657
|
+
NY: "New York",
|
|
6658
|
+
NC: "North Carolina",
|
|
6659
|
+
ND: "North Dakota",
|
|
6660
|
+
OH: "Ohio",
|
|
6661
|
+
OK: "Oklahoma",
|
|
6662
|
+
OR: "Oregon",
|
|
6663
|
+
PA: "Pennsylvania",
|
|
6664
|
+
RI: "Rhode Island",
|
|
6665
|
+
SC: "South Carolina",
|
|
6666
|
+
SD: "South Dakota",
|
|
6667
|
+
TN: "Tennessee",
|
|
6668
|
+
TX: "Texas",
|
|
6669
|
+
UT: "Utah",
|
|
6670
|
+
VT: "Vermont",
|
|
6671
|
+
VA: "Virginia",
|
|
6672
|
+
WA: "Washington",
|
|
6673
|
+
WV: "West Virginia",
|
|
6674
|
+
WI: "Wisconsin",
|
|
6675
|
+
WY: "Wyoming"
|
|
6676
|
+
};
|
|
6677
|
+
var GRID_COLS = 12;
|
|
6678
|
+
var GRID_ROWS = 8;
|
|
6679
|
+
function computeTilePositions(availableWidth, availableHeight, gap = 4) {
|
|
6680
|
+
const maxTileW = (availableWidth - gap * (GRID_COLS - 1)) / GRID_COLS;
|
|
6681
|
+
const maxTileH = (availableHeight - gap * (GRID_ROWS - 1)) / GRID_ROWS;
|
|
6682
|
+
const tileSize = Math.max(1, Math.floor(Math.min(maxTileW, maxTileH)));
|
|
6683
|
+
const gridWidth = tileSize * GRID_COLS + gap * (GRID_COLS - 1);
|
|
6684
|
+
const gridHeight = tileSize * GRID_ROWS + gap * (GRID_ROWS - 1);
|
|
6685
|
+
const positions = /* @__PURE__ */ new Map();
|
|
6686
|
+
for (const { state, col, row } of US_STATE_TILES) {
|
|
6687
|
+
positions.set(state, {
|
|
6688
|
+
x: col * (tileSize + gap),
|
|
6689
|
+
y: row * (tileSize + gap)
|
|
6690
|
+
});
|
|
6691
|
+
}
|
|
6692
|
+
return { tileSize, gap, positions, gridWidth, gridHeight };
|
|
6693
|
+
}
|
|
6694
|
+
|
|
6695
|
+
// src/compiler/normalize.ts
|
|
6549
6696
|
function normalizeChromeField(value2) {
|
|
6550
6697
|
if (value2 === void 0) return void 0;
|
|
6551
6698
|
if (typeof value2 === "string") return { text: value2 };
|
|
@@ -6651,6 +6798,16 @@ function normalizeAnnotations(annotations) {
|
|
|
6651
6798
|
}
|
|
6652
6799
|
});
|
|
6653
6800
|
}
|
|
6801
|
+
function normalizeLabels(labels) {
|
|
6802
|
+
if (labels === false) return { density: "none", format: "", prefix: "" };
|
|
6803
|
+
if (labels === true || labels === void 0) return { density: "auto", format: "", prefix: "" };
|
|
6804
|
+
return {
|
|
6805
|
+
density: labels.density ?? "auto",
|
|
6806
|
+
format: labels.format ?? "",
|
|
6807
|
+
prefix: labels.prefix ?? "",
|
|
6808
|
+
offsets: labels.offsets
|
|
6809
|
+
};
|
|
6810
|
+
}
|
|
6654
6811
|
function normalizeChartSpec(spec, warnings) {
|
|
6655
6812
|
const encoding = inferEncodingTypes(spec.encoding, spec.data, warnings);
|
|
6656
6813
|
const markType = resolveMarkType(spec.mark);
|
|
@@ -6662,12 +6819,7 @@ function normalizeChartSpec(spec, warnings) {
|
|
|
6662
6819
|
encoding,
|
|
6663
6820
|
chrome: normalizeChrome(spec.chrome),
|
|
6664
6821
|
annotations: normalizeAnnotations(spec.annotations),
|
|
6665
|
-
labels:
|
|
6666
|
-
density: spec.labels?.density ?? "auto",
|
|
6667
|
-
format: spec.labels?.format ?? "",
|
|
6668
|
-
prefix: spec.labels?.prefix ?? "",
|
|
6669
|
-
offsets: spec.labels?.offsets
|
|
6670
|
-
},
|
|
6822
|
+
labels: normalizeLabels(spec.labels),
|
|
6671
6823
|
legend: spec.legend,
|
|
6672
6824
|
responsive: spec.responsive ?? true,
|
|
6673
6825
|
theme: spec.theme ?? {},
|
|
@@ -6741,6 +6893,45 @@ function normalizeGraphSpec(spec, _warnings) {
|
|
|
6741
6893
|
watermark: spec.watermark ?? true
|
|
6742
6894
|
};
|
|
6743
6895
|
}
|
|
6896
|
+
function normalizeTileMapSpec(spec, warnings) {
|
|
6897
|
+
let data = Array.isArray(spec.data) ? spec.data : [];
|
|
6898
|
+
if (!Array.isArray(spec.data)) {
|
|
6899
|
+
data = Object.entries(spec.data).map(([state, value2]) => ({ state, value: value2 }));
|
|
6900
|
+
}
|
|
6901
|
+
let encoding = spec.encoding;
|
|
6902
|
+
if (!encoding) {
|
|
6903
|
+
encoding = {
|
|
6904
|
+
state: { field: "state", type: "nominal" },
|
|
6905
|
+
value: { field: "value", type: "quantitative" }
|
|
6906
|
+
};
|
|
6907
|
+
}
|
|
6908
|
+
let matchedCount = 0;
|
|
6909
|
+
for (const row of data) {
|
|
6910
|
+
const stateCode = String(row[encoding.state.field]);
|
|
6911
|
+
if (STATE_CODE_SET.has(stateCode)) {
|
|
6912
|
+
matchedCount++;
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
const matchRatio = data.length > 0 ? matchedCount / data.length : 0;
|
|
6916
|
+
if (matchRatio < 0.5 && data.length > 0) {
|
|
6917
|
+
warnings.push(
|
|
6918
|
+
`TileMap data: only ${matchedCount} of ${data.length} rows have valid US state codes (expected \u226550%)`
|
|
6919
|
+
);
|
|
6920
|
+
}
|
|
6921
|
+
return {
|
|
6922
|
+
type: "tilemap",
|
|
6923
|
+
data,
|
|
6924
|
+
encoding,
|
|
6925
|
+
palette: spec.palette ?? "blue",
|
|
6926
|
+
chrome: normalizeChrome(spec.chrome),
|
|
6927
|
+
legend: spec.legend,
|
|
6928
|
+
theme: spec.theme ?? {},
|
|
6929
|
+
darkMode: spec.darkMode ?? "off",
|
|
6930
|
+
watermark: spec.watermark ?? true,
|
|
6931
|
+
animation: spec.animation,
|
|
6932
|
+
valueFormat: spec.valueFormat
|
|
6933
|
+
};
|
|
6934
|
+
}
|
|
6744
6935
|
function normalizeSpec(spec, warnings = []) {
|
|
6745
6936
|
if (isLayerSpec(spec)) {
|
|
6746
6937
|
const leaves = flattenLayers(spec);
|
|
@@ -6761,8 +6952,11 @@ function normalizeSpec(spec, warnings = []) {
|
|
|
6761
6952
|
if (isSankeySpec(spec)) {
|
|
6762
6953
|
return normalizeSankeySpec(spec, warnings);
|
|
6763
6954
|
}
|
|
6955
|
+
if (isTileMapSpec(spec)) {
|
|
6956
|
+
return normalizeTileMapSpec(spec, warnings);
|
|
6957
|
+
}
|
|
6764
6958
|
throw new Error(
|
|
6765
|
-
`Unknown spec shape. Expected mark (chart), layer, type: 'table', type: 'graph', or type: '
|
|
6959
|
+
`Unknown spec shape. Expected mark (chart), layer, type: 'table', type: 'graph', type: 'sankey', or type: 'tilemap'.`
|
|
6766
6960
|
);
|
|
6767
6961
|
}
|
|
6768
6962
|
function flattenLayers(spec, parentData, parentEncoding, parentTransforms, parentWatermark) {
|
|
@@ -7312,6 +7506,96 @@ function validateSankeySpec(spec, errors) {
|
|
|
7312
7506
|
});
|
|
7313
7507
|
}
|
|
7314
7508
|
}
|
|
7509
|
+
function validateTileMapSpec(spec, errors) {
|
|
7510
|
+
if (!spec.data || typeof spec.data !== "object") {
|
|
7511
|
+
errors.push({
|
|
7512
|
+
message: 'Spec error: tilemap spec requires a "data" field (record or array)',
|
|
7513
|
+
path: "data",
|
|
7514
|
+
code: "INVALID_TYPE",
|
|
7515
|
+
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'
|
|
7516
|
+
});
|
|
7517
|
+
return;
|
|
7518
|
+
}
|
|
7519
|
+
if (!Array.isArray(spec.data) && Object.keys(spec.data).length === 0) {
|
|
7520
|
+
errors.push({
|
|
7521
|
+
message: 'Spec error: "data" must have at least one entry',
|
|
7522
|
+
path: "data",
|
|
7523
|
+
code: "EMPTY_DATA",
|
|
7524
|
+
suggestion: 'Add at least one state-value pair, e.g. { "CA": 12000 }'
|
|
7525
|
+
});
|
|
7526
|
+
return;
|
|
7527
|
+
}
|
|
7528
|
+
if (Array.isArray(spec.data)) {
|
|
7529
|
+
if (spec.data.length === 0) {
|
|
7530
|
+
errors.push({
|
|
7531
|
+
message: 'Spec error: "data" array must be non-empty',
|
|
7532
|
+
path: "data",
|
|
7533
|
+
code: "EMPTY_DATA",
|
|
7534
|
+
suggestion: "Add at least one data row"
|
|
7535
|
+
});
|
|
7536
|
+
return;
|
|
7537
|
+
}
|
|
7538
|
+
const firstRow = spec.data[0];
|
|
7539
|
+
if (typeof firstRow !== "object" || firstRow === null || Array.isArray(firstRow)) {
|
|
7540
|
+
errors.push({
|
|
7541
|
+
message: 'Spec error: each item in "data" must be a plain object',
|
|
7542
|
+
path: "data[0]",
|
|
7543
|
+
code: "INVALID_TYPE",
|
|
7544
|
+
suggestion: 'Each data item should be an object, e.g. { state: "CA", value: 12000 }'
|
|
7545
|
+
});
|
|
7546
|
+
return;
|
|
7547
|
+
}
|
|
7548
|
+
if (!spec.encoding || typeof spec.encoding !== "object") {
|
|
7549
|
+
errors.push({
|
|
7550
|
+
message: 'Spec error: tilemap spec with array data requires an "encoding" object with state and value channels',
|
|
7551
|
+
path: "encoding",
|
|
7552
|
+
code: "MISSING_FIELD",
|
|
7553
|
+
suggestion: 'Add an encoding object, e.g. encoding: { state: { field: "state", type: "nominal" }, value: { field: "value", type: "quantitative" } }'
|
|
7554
|
+
});
|
|
7555
|
+
return;
|
|
7556
|
+
}
|
|
7557
|
+
const encoding = spec.encoding;
|
|
7558
|
+
const dataColumns = new Set(Object.keys(firstRow));
|
|
7559
|
+
const availableColumns = [...dataColumns].join(", ");
|
|
7560
|
+
for (const channel of ["state", "value"]) {
|
|
7561
|
+
const ch = encoding[channel];
|
|
7562
|
+
if (!ch || typeof ch !== "object") {
|
|
7563
|
+
errors.push({
|
|
7564
|
+
message: `Spec error: tilemap encoding requires "${channel}" channel`,
|
|
7565
|
+
path: `encoding.${channel}`,
|
|
7566
|
+
code: "MISSING_FIELD",
|
|
7567
|
+
suggestion: `Add encoding.${channel} with a field from your data (${availableColumns}). Example: ${channel}: { field: "${[...dataColumns][0] ?? "myField"}", type: "${channel === "value" ? "quantitative" : "nominal"}" }`
|
|
7568
|
+
});
|
|
7569
|
+
continue;
|
|
7570
|
+
}
|
|
7571
|
+
if (!ch.field || typeof ch.field !== "string") {
|
|
7572
|
+
errors.push({
|
|
7573
|
+
message: `Spec error: encoding.${channel} must have a "field" string`,
|
|
7574
|
+
path: `encoding.${channel}.field`,
|
|
7575
|
+
code: "MISSING_FIELD",
|
|
7576
|
+
suggestion: `Add a field name from your data columns: ${availableColumns}`
|
|
7577
|
+
});
|
|
7578
|
+
continue;
|
|
7579
|
+
}
|
|
7580
|
+
if (!dataColumns.has(ch.field)) {
|
|
7581
|
+
errors.push({
|
|
7582
|
+
message: `Spec error: encoding.${channel}.field "${ch.field}" does not exist in data. Available columns: ${availableColumns}`,
|
|
7583
|
+
path: `encoding.${channel}.field`,
|
|
7584
|
+
code: "DATA_FIELD_MISSING",
|
|
7585
|
+
suggestion: `Use one of the available data columns: ${availableColumns}`
|
|
7586
|
+
});
|
|
7587
|
+
}
|
|
7588
|
+
}
|
|
7589
|
+
}
|
|
7590
|
+
if (spec.darkMode !== void 0 && !VALID_DARK_MODES.has(spec.darkMode)) {
|
|
7591
|
+
errors.push({
|
|
7592
|
+
message: 'Spec error: darkMode must be "auto", "force", or "off"',
|
|
7593
|
+
path: "darkMode",
|
|
7594
|
+
code: "INVALID_VALUE",
|
|
7595
|
+
suggestion: 'Use one of: "auto" (system preference), "force" (always dark), or "off" (always light)'
|
|
7596
|
+
});
|
|
7597
|
+
}
|
|
7598
|
+
}
|
|
7315
7599
|
function validateLayerSpec(spec, errors) {
|
|
7316
7600
|
const layer = spec.layer;
|
|
7317
7601
|
if (layer.length === 0) {
|
|
@@ -7406,17 +7690,18 @@ function validateSpec(spec) {
|
|
|
7406
7690
|
const isTable = obj.type === "table";
|
|
7407
7691
|
const isGraph = obj.type === "graph";
|
|
7408
7692
|
const isSankey = obj.type === "sankey";
|
|
7409
|
-
const
|
|
7410
|
-
const
|
|
7411
|
-
|
|
7693
|
+
const isTileMap = obj.type === "tilemap";
|
|
7694
|
+
const isLayer = hasLayer && !isTable && !isGraph && !isSankey && !isTileMap;
|
|
7695
|
+
const isChart = hasMark && !hasLayer && !isTable && !isGraph && !isSankey && !isTileMap;
|
|
7696
|
+
if (!isChart && !isTable && !isGraph && !isSankey && !isTileMap && !isLayer) {
|
|
7412
7697
|
return {
|
|
7413
7698
|
valid: false,
|
|
7414
7699
|
errors: [
|
|
7415
7700
|
{
|
|
7416
|
-
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',
|
|
7701
|
+
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',
|
|
7417
7702
|
path: "mark",
|
|
7418
7703
|
code: "MISSING_FIELD",
|
|
7419
|
-
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: "
|
|
7704
|
+
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(", ")}`
|
|
7420
7705
|
}
|
|
7421
7706
|
],
|
|
7422
7707
|
normalized: null
|
|
@@ -7454,6 +7739,8 @@ function validateSpec(spec) {
|
|
|
7454
7739
|
validateGraphSpec(obj, errors);
|
|
7455
7740
|
} else if (isSankey) {
|
|
7456
7741
|
validateSankeySpec(obj, errors);
|
|
7742
|
+
} else if (isTileMap) {
|
|
7743
|
+
validateTileMapSpec(obj, errors);
|
|
7457
7744
|
}
|
|
7458
7745
|
if (errors.length > 0) {
|
|
7459
7746
|
return { valid: false, errors, normalized: null };
|
|
@@ -8016,7 +8303,7 @@ function scaleSupportsTickCount(resolvedScale) {
|
|
|
8016
8303
|
const scale = resolvedScale.scale;
|
|
8017
8304
|
return "ticks" in scale && typeof scale.ticks === "function";
|
|
8018
8305
|
}
|
|
8019
|
-
function categoricalTicks(resolvedScale, density, orientation = "horizontal", bandwidth, labelAngle, fontSize, fontWeight, measureText) {
|
|
8306
|
+
function categoricalTicks(resolvedScale, density, orientation = "horizontal", bandwidth, labelAngle, fontSize, fontWeight, measureText, subtitleContext) {
|
|
8020
8307
|
const scale = resolvedScale.scale;
|
|
8021
8308
|
const domain = scale.domain();
|
|
8022
8309
|
const explicitTickCount = resolvedScale.channel.axis?.tickCount;
|
|
@@ -8052,14 +8339,37 @@ function categoricalTicks(resolvedScale, density, orientation = "horizontal", ba
|
|
|
8052
8339
|
selectedValues = domain.filter((_, i) => i % step === 0);
|
|
8053
8340
|
}
|
|
8054
8341
|
}
|
|
8342
|
+
let subtitleMap;
|
|
8343
|
+
if (subtitleContext) {
|
|
8344
|
+
const { data, fieldName, labelField } = subtitleContext;
|
|
8345
|
+
if (data.length > 0) {
|
|
8346
|
+
subtitleMap = /* @__PURE__ */ new Map();
|
|
8347
|
+
for (const row of data) {
|
|
8348
|
+
const key = String(row[fieldName] ?? "");
|
|
8349
|
+
if (!subtitleMap.has(key)) {
|
|
8350
|
+
const val = row[labelField];
|
|
8351
|
+
if (val != null) {
|
|
8352
|
+
subtitleMap.set(key, String(val));
|
|
8353
|
+
}
|
|
8354
|
+
}
|
|
8355
|
+
}
|
|
8356
|
+
}
|
|
8357
|
+
}
|
|
8055
8358
|
const ticks2 = selectedValues.map((value2) => {
|
|
8056
8359
|
const bandScale = resolvedScale.type === "band" ? scale : null;
|
|
8057
8360
|
const pos = bandScale ? (bandScale(value2) ?? 0) + bandScale.bandwidth() / 2 : scale(value2) ?? 0;
|
|
8058
|
-
|
|
8361
|
+
const tick = {
|
|
8059
8362
|
value: value2,
|
|
8060
8363
|
position: pos,
|
|
8061
8364
|
label: value2
|
|
8062
8365
|
};
|
|
8366
|
+
if (subtitleMap) {
|
|
8367
|
+
const subtitle = subtitleMap.get(value2);
|
|
8368
|
+
if (subtitle !== void 0) {
|
|
8369
|
+
tick.subtitle = subtitle;
|
|
8370
|
+
}
|
|
8371
|
+
}
|
|
8372
|
+
return tick;
|
|
8063
8373
|
});
|
|
8064
8374
|
return ticks2;
|
|
8065
8375
|
}
|
|
@@ -8131,7 +8441,7 @@ function fitContinuousTicks(scale, initialTicks, initialCount, fontSize, fontWei
|
|
|
8131
8441
|
const fallback = bestWithinFloor ?? buildContinuousTicks(scale, floor);
|
|
8132
8442
|
return thinTicksUntilFit(fallback, fontSize, fontWeight, measureText, orientation);
|
|
8133
8443
|
}
|
|
8134
|
-
function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
8444
|
+
function computeAxes(scales, chartArea, strategy, theme, measureText, dataContext) {
|
|
8135
8445
|
const result = {};
|
|
8136
8446
|
const baseDensity = strategy.axisLabelDensity;
|
|
8137
8447
|
const yDensity = effectiveDensity(
|
|
@@ -8249,7 +8559,19 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
8249
8559
|
if (axisConfig?.values) {
|
|
8250
8560
|
allTicks = resolveExplicitTicks(axisConfig.values, scales.y);
|
|
8251
8561
|
} else if (!isContinuousY) {
|
|
8252
|
-
|
|
8562
|
+
const yFieldName = dataContext?.encoding.y?.field;
|
|
8563
|
+
const yLabelField = axisConfig?.labelField;
|
|
8564
|
+
allTicks = categoricalTicks(
|
|
8565
|
+
scales.y,
|
|
8566
|
+
yDensity,
|
|
8567
|
+
"vertical",
|
|
8568
|
+
void 0,
|
|
8569
|
+
void 0,
|
|
8570
|
+
void 0,
|
|
8571
|
+
void 0,
|
|
8572
|
+
void 0,
|
|
8573
|
+
yFieldName && yLabelField && dataContext ? { data: dataContext.data, fieldName: yFieldName, labelField: yLabelField } : void 0
|
|
8574
|
+
);
|
|
8253
8575
|
} else {
|
|
8254
8576
|
allTicks = continuousTicks(scales.y, yDensity, yTargetCount);
|
|
8255
8577
|
}
|
|
@@ -8483,10 +8805,23 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8483
8805
|
if (encoding.y && !isRadial) {
|
|
8484
8806
|
if (spec.markType === "bar" || spec.markType === "circle" || spec.markType === "lollipop" || encoding.y.type === "nominal" || encoding.y.type === "ordinal") {
|
|
8485
8807
|
const yField = encoding.y.field;
|
|
8808
|
+
const yLabelField = encoding.y.axis?.labelField;
|
|
8486
8809
|
let maxLabelWidth = 0;
|
|
8487
8810
|
for (const row of spec.data) {
|
|
8488
8811
|
const label = String(row[yField] ?? "");
|
|
8489
|
-
|
|
8812
|
+
let w = estimateTextWidth10(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
|
|
8813
|
+
if (yLabelField) {
|
|
8814
|
+
const subtitle = String(row[yLabelField] ?? "");
|
|
8815
|
+
if (subtitle) {
|
|
8816
|
+
const gap = theme.fonts.sizes.axisTick * 0.6;
|
|
8817
|
+
const subtitleWidth = estimateTextWidth10(
|
|
8818
|
+
subtitle,
|
|
8819
|
+
theme.fonts.sizes.axisTick,
|
|
8820
|
+
theme.fonts.weights.normal
|
|
8821
|
+
);
|
|
8822
|
+
w += gap + subtitleWidth;
|
|
8823
|
+
}
|
|
8824
|
+
}
|
|
8490
8825
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
8491
8826
|
}
|
|
8492
8827
|
if (maxLabelWidth > 0) {
|
|
@@ -8540,7 +8875,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8540
8875
|
if (options.rightAxisReserve && options.rightAxisReserve > 0) {
|
|
8541
8876
|
margins.right = Math.max(margins.right, hPad + options.rightAxisReserve);
|
|
8542
8877
|
}
|
|
8543
|
-
if (legendLayout.entries.length > 0) {
|
|
8878
|
+
if ("entries" in legendLayout && legendLayout.entries.length > 0) {
|
|
8544
8879
|
const gap = legendGap(width);
|
|
8545
8880
|
if (legendLayout.position === "right" || legendLayout.position === "bottom-right") {
|
|
8546
8881
|
margins.right += legendLayout.bounds.width + 8;
|
|
@@ -8574,7 +8909,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8574
8909
|
const bottomDelta = margins.bottom - newBottom;
|
|
8575
8910
|
if (topDelta > 0 || bottomDelta > 0) {
|
|
8576
8911
|
const gap = legendGap(width);
|
|
8577
|
-
margins.top = newTop + (legendLayout.entries.length > 0 && legendLayout.position === "top" ? legendLayout.bounds.height + gap : 0);
|
|
8912
|
+
margins.top = newTop + ("entries" in legendLayout && legendLayout.entries.length > 0 && legendLayout.position === "top" ? legendLayout.bounds.height + gap : 0);
|
|
8578
8913
|
margins.bottom = newBottom;
|
|
8579
8914
|
chartArea = {
|
|
8580
8915
|
x: margins.left,
|
|
@@ -9899,7 +10234,7 @@ function compileSankey(spec, options) {
|
|
|
9899
10234
|
theme,
|
|
9900
10235
|
fullArea
|
|
9901
10236
|
);
|
|
9902
|
-
const legendGap2 = legend.entries.length > 0 ? 4 : 0;
|
|
10237
|
+
const legendGap2 = "entries" in legend && legend.entries.length > 0 ? 4 : 0;
|
|
9903
10238
|
const area = {
|
|
9904
10239
|
x: fullArea.x,
|
|
9905
10240
|
y: fullArea.y + legend.bounds.height + legendGap2,
|
|
@@ -10842,11 +11177,266 @@ function compileTableLayout(spec, options, theme) {
|
|
|
10842
11177
|
};
|
|
10843
11178
|
}
|
|
10844
11179
|
|
|
11180
|
+
// src/tilemap/compile-tilemap.ts
|
|
11181
|
+
import {
|
|
11182
|
+
adaptTheme as adaptTheme3,
|
|
11183
|
+
buildD3Formatter as buildD3Formatter7,
|
|
11184
|
+
computeChrome as computeChrome5,
|
|
11185
|
+
estimateTextWidth as estimateTextWidth14,
|
|
11186
|
+
formatNumber as formatNumber5,
|
|
11187
|
+
resolveTheme as resolveTheme3,
|
|
11188
|
+
SEQUENTIAL_PALETTES
|
|
11189
|
+
} from "@opendata-ai/openchart-core";
|
|
11190
|
+
var TILE_CORNER_RADIUS = 2;
|
|
11191
|
+
var TILE_STROKE_WIDTH = 0;
|
|
11192
|
+
function compileTileMap(spec, options) {
|
|
11193
|
+
const { spec: normalized } = compile(spec);
|
|
11194
|
+
if (!("type" in normalized) || normalized.type !== "tilemap") {
|
|
11195
|
+
throw new Error(
|
|
11196
|
+
"compileTileMap received a non-tilemap spec. Use compileChart, compileTable, compileGraph, or compileSankey instead."
|
|
11197
|
+
);
|
|
11198
|
+
}
|
|
11199
|
+
const tilemapSpec = normalized;
|
|
11200
|
+
const rawWatermark = spec.watermark;
|
|
11201
|
+
const watermark = rawWatermark !== void 0 ? tilemapSpec.watermark : options.watermark ?? true;
|
|
11202
|
+
const mergedThemeConfig = options.theme ? { ...tilemapSpec.theme, ...options.theme } : tilemapSpec.theme;
|
|
11203
|
+
const lightTheme = resolveTheme3(mergedThemeConfig);
|
|
11204
|
+
let theme = lightTheme;
|
|
11205
|
+
if (options.darkMode) {
|
|
11206
|
+
theme = adaptTheme3(theme);
|
|
11207
|
+
}
|
|
11208
|
+
const isDarkMode = options.darkMode;
|
|
11209
|
+
const chrome = computeChrome5(
|
|
11210
|
+
{
|
|
11211
|
+
title: tilemapSpec.chrome.title,
|
|
11212
|
+
subtitle: tilemapSpec.chrome.subtitle,
|
|
11213
|
+
source: tilemapSpec.chrome.source,
|
|
11214
|
+
byline: tilemapSpec.chrome.byline,
|
|
11215
|
+
footer: tilemapSpec.chrome.footer
|
|
11216
|
+
},
|
|
11217
|
+
theme,
|
|
11218
|
+
options.width,
|
|
11219
|
+
options.measureText,
|
|
11220
|
+
"full",
|
|
11221
|
+
void 0,
|
|
11222
|
+
watermark
|
|
11223
|
+
);
|
|
11224
|
+
const padding = theme.spacing.padding;
|
|
11225
|
+
const fullArea = {
|
|
11226
|
+
x: padding,
|
|
11227
|
+
y: padding + chrome.topHeight,
|
|
11228
|
+
width: options.width - padding * 2,
|
|
11229
|
+
height: options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
|
|
11230
|
+
};
|
|
11231
|
+
if (fullArea.width <= 0 || fullArea.height <= 0) {
|
|
11232
|
+
return emptyLayout2(chrome, theme, options, watermark);
|
|
11233
|
+
}
|
|
11234
|
+
const stateField = tilemapSpec.encoding.state.field;
|
|
11235
|
+
const valueField = tilemapSpec.encoding.value.field;
|
|
11236
|
+
const stateValueMap = /* @__PURE__ */ new Map();
|
|
11237
|
+
for (const row of tilemapSpec.data) {
|
|
11238
|
+
const stateCode = String(row[stateField]);
|
|
11239
|
+
const raw = row[valueField];
|
|
11240
|
+
if (STATE_CODE_SET.has(stateCode) && raw !== null && raw !== void 0) {
|
|
11241
|
+
const value2 = Number(raw);
|
|
11242
|
+
if (!Number.isNaN(value2)) {
|
|
11243
|
+
stateValueMap.set(stateCode, value2);
|
|
11244
|
+
}
|
|
11245
|
+
}
|
|
11246
|
+
}
|
|
11247
|
+
const values = Array.from(stateValueMap.values());
|
|
11248
|
+
const min4 = values.length > 0 ? Math.min(...values) : 0;
|
|
11249
|
+
const max4 = values.length > 0 ? Math.max(...values) : 100;
|
|
11250
|
+
const paletteStops = [...SEQUENTIAL_PALETTES[tilemapSpec.palette] ?? SEQUENTIAL_PALETTES.blue];
|
|
11251
|
+
if (isDarkMode) paletteStops.reverse();
|
|
11252
|
+
const domain = paletteStops.map((_, i) => min4 + i / (paletteStops.length - 1) * (max4 - min4));
|
|
11253
|
+
const colorScale = linear2().domain(domain).range(paletteStops).clamp(true);
|
|
11254
|
+
const showLegend = tilemapSpec.legend?.show !== false;
|
|
11255
|
+
const legendBarHeight = 12;
|
|
11256
|
+
const legendLabelGap = 4;
|
|
11257
|
+
const legendTotalHeight = showLegend ? legendBarHeight + legendLabelGap + 14 : 0;
|
|
11258
|
+
const legendGap2 = showLegend ? 8 : 0;
|
|
11259
|
+
const tileAreaHeight = fullArea.height - legendTotalHeight - legendGap2;
|
|
11260
|
+
const tilePositions = computeTilePositions(fullArea.width, tileAreaHeight, 4);
|
|
11261
|
+
const tileGridOffsetX = fullArea.x + (fullArea.width - tilePositions.gridWidth) / 2;
|
|
11262
|
+
const tileGridOffsetY = fullArea.y;
|
|
11263
|
+
const legendX = tileGridOffsetX;
|
|
11264
|
+
const legendY = tileGridOffsetY + tilePositions.gridHeight + legendGap2;
|
|
11265
|
+
const legendWidth = tilePositions.gridWidth;
|
|
11266
|
+
const formatter = buildD3Formatter7(tilemapSpec.valueFormat) ?? formatNumber5;
|
|
11267
|
+
const neutralFillLight = "#e0e0e0";
|
|
11268
|
+
const neutralFillDark = "#2a2a3e";
|
|
11269
|
+
const neutralStrokeLight = "#d0d0d0";
|
|
11270
|
+
const neutralStrokeDark = "#3a3a50";
|
|
11271
|
+
const neutralFill = isDarkMode ? neutralFillDark : neutralFillLight;
|
|
11272
|
+
const neutralStroke = isDarkMode ? neutralStrokeDark : neutralStrokeLight;
|
|
11273
|
+
const tiles = [];
|
|
11274
|
+
for (const { state: stateCode } of US_STATE_TILES) {
|
|
11275
|
+
const pos = tilePositions.positions.get(stateCode);
|
|
11276
|
+
if (!pos) continue;
|
|
11277
|
+
const hasData = stateValueMap.has(stateCode);
|
|
11278
|
+
const value2 = hasData ? stateValueMap.get(stateCode) : null;
|
|
11279
|
+
const fill = hasData ? colorScale(value2) : neutralFill;
|
|
11280
|
+
const formattedValue = hasData ? formatter(value2) : "\u2013";
|
|
11281
|
+
const labelStyle = {
|
|
11282
|
+
fontFamily: theme.fonts.family,
|
|
11283
|
+
fontSize: tilePositions.tileSize > 24 ? 14 : 11,
|
|
11284
|
+
fontWeight: 700,
|
|
11285
|
+
fill: "#ffffff",
|
|
11286
|
+
lineHeight: 1.2
|
|
11287
|
+
};
|
|
11288
|
+
const valueLabelStyle = {
|
|
11289
|
+
fontFamily: theme.fonts.family,
|
|
11290
|
+
fontSize: tilePositions.tileSize > 24 ? 12 : 10,
|
|
11291
|
+
fontWeight: 400,
|
|
11292
|
+
fill: "#ffffff",
|
|
11293
|
+
lineHeight: 1.2
|
|
11294
|
+
};
|
|
11295
|
+
const valueLabel = tilePositions.tileSize < 24 ? { text: "", x: 0, y: 0, style: valueLabelStyle, visible: false } : {
|
|
11296
|
+
text: formattedValue,
|
|
11297
|
+
x: tileGridOffsetX + pos.x + tilePositions.tileSize / 2,
|
|
11298
|
+
y: tileGridOffsetY + pos.y + tilePositions.tileSize / 2 + 8,
|
|
11299
|
+
style: valueLabelStyle,
|
|
11300
|
+
visible: true
|
|
11301
|
+
};
|
|
11302
|
+
const tile = {
|
|
11303
|
+
type: "tile",
|
|
11304
|
+
stateCode,
|
|
11305
|
+
x: tileGridOffsetX + pos.x,
|
|
11306
|
+
y: tileGridOffsetY + pos.y,
|
|
11307
|
+
size: tilePositions.tileSize,
|
|
11308
|
+
fill,
|
|
11309
|
+
stroke: neutralStroke,
|
|
11310
|
+
strokeWidth: TILE_STROKE_WIDTH,
|
|
11311
|
+
cornerRadius: TILE_CORNER_RADIUS,
|
|
11312
|
+
value: value2 ?? null,
|
|
11313
|
+
formattedValue,
|
|
11314
|
+
hasData,
|
|
11315
|
+
label: {
|
|
11316
|
+
text: stateCode,
|
|
11317
|
+
x: tileGridOffsetX + pos.x + tilePositions.tileSize / 2,
|
|
11318
|
+
y: tileGridOffsetY + pos.y + tilePositions.tileSize / 2 - 4,
|
|
11319
|
+
style: labelStyle,
|
|
11320
|
+
visible: true
|
|
11321
|
+
},
|
|
11322
|
+
valueLabel,
|
|
11323
|
+
data: { state: stateCode, value: value2, stateName: STATE_NAMES[stateCode] ?? stateCode },
|
|
11324
|
+
aria: {
|
|
11325
|
+
role: "img",
|
|
11326
|
+
label: `${STATE_NAMES[stateCode] ?? stateCode}: ${formattedValue}`
|
|
11327
|
+
},
|
|
11328
|
+
animationIndex: 0
|
|
11329
|
+
};
|
|
11330
|
+
tiles.push(tile);
|
|
11331
|
+
}
|
|
11332
|
+
const indices = Array.from({ length: tiles.length }, (_, i) => i);
|
|
11333
|
+
let seed = 42;
|
|
11334
|
+
for (let i = indices.length - 1; i > 0; i--) {
|
|
11335
|
+
seed = seed * 1103515245 + 12345 & 2147483647;
|
|
11336
|
+
const j = seed % (i + 1);
|
|
11337
|
+
[indices[i], indices[j]] = [indices[j], indices[i]];
|
|
11338
|
+
}
|
|
11339
|
+
for (let i = 0; i < tiles.length; i++) {
|
|
11340
|
+
tiles[i].animationIndex = indices[i];
|
|
11341
|
+
}
|
|
11342
|
+
let gradientLegend = null;
|
|
11343
|
+
if (showLegend) {
|
|
11344
|
+
const gradientColorStops = paletteStops.map((color2, i) => ({
|
|
11345
|
+
offset: i / (paletteStops.length - 1),
|
|
11346
|
+
color: color2
|
|
11347
|
+
}));
|
|
11348
|
+
gradientLegend = {
|
|
11349
|
+
type: "gradient",
|
|
11350
|
+
position: "bottom",
|
|
11351
|
+
bounds: { x: legendX, y: legendY, width: legendWidth, height: legendBarHeight },
|
|
11352
|
+
labelStyle: {
|
|
11353
|
+
fontFamily: theme.fonts.family,
|
|
11354
|
+
fontSize: 11,
|
|
11355
|
+
fontWeight: 400,
|
|
11356
|
+
fill: theme.colors.text,
|
|
11357
|
+
lineHeight: 1.2
|
|
11358
|
+
},
|
|
11359
|
+
colorStops: gradientColorStops,
|
|
11360
|
+
minLabel: formatter(min4),
|
|
11361
|
+
maxLabel: formatter(max4)
|
|
11362
|
+
};
|
|
11363
|
+
}
|
|
11364
|
+
const tooltipDescriptors = /* @__PURE__ */ new Map();
|
|
11365
|
+
for (const tile of tiles) {
|
|
11366
|
+
const fields = [
|
|
11367
|
+
{
|
|
11368
|
+
label: "Value",
|
|
11369
|
+
value: tile.formattedValue
|
|
11370
|
+
}
|
|
11371
|
+
];
|
|
11372
|
+
tooltipDescriptors.set(tile.stateCode, {
|
|
11373
|
+
title: STATE_NAMES[tile.stateCode] ?? tile.stateCode,
|
|
11374
|
+
fields
|
|
11375
|
+
});
|
|
11376
|
+
}
|
|
11377
|
+
const a11y = {
|
|
11378
|
+
altText: `Tile map of US states showing values from ${formatter(min4)} to ${formatter(max4)}`,
|
|
11379
|
+
dataTableFallback: tiles.map((t) => [t.stateCode, t.formattedValue]),
|
|
11380
|
+
role: "img",
|
|
11381
|
+
keyboardNavigable: tiles.length > 0
|
|
11382
|
+
};
|
|
11383
|
+
const resolvedAnimation = resolveAnimation(tilemapSpec.animation);
|
|
11384
|
+
return {
|
|
11385
|
+
area: fullArea,
|
|
11386
|
+
chrome,
|
|
11387
|
+
tiles,
|
|
11388
|
+
gradientLegend,
|
|
11389
|
+
tooltipDescriptors,
|
|
11390
|
+
a11y,
|
|
11391
|
+
theme,
|
|
11392
|
+
width: options.width,
|
|
11393
|
+
height: options.height,
|
|
11394
|
+
animation: resolvedAnimation,
|
|
11395
|
+
watermark,
|
|
11396
|
+
measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth14(text, fontSize), height: fontSize }))
|
|
11397
|
+
};
|
|
11398
|
+
}
|
|
11399
|
+
function emptyLayout2(chrome, theme, options, watermark) {
|
|
11400
|
+
return {
|
|
11401
|
+
area: { x: 0, y: 0, width: 0, height: 0 },
|
|
11402
|
+
chrome,
|
|
11403
|
+
tiles: [],
|
|
11404
|
+
gradientLegend: {
|
|
11405
|
+
type: "gradient",
|
|
11406
|
+
position: "bottom",
|
|
11407
|
+
bounds: { x: 0, y: 0, width: 0, height: 0 },
|
|
11408
|
+
labelStyle: {
|
|
11409
|
+
fontFamily: theme.fonts.family,
|
|
11410
|
+
fontSize: 11,
|
|
11411
|
+
fontWeight: 400,
|
|
11412
|
+
fill: theme.colors.text,
|
|
11413
|
+
lineHeight: 1.2
|
|
11414
|
+
},
|
|
11415
|
+
colorStops: [],
|
|
11416
|
+
minLabel: "0",
|
|
11417
|
+
maxLabel: "0"
|
|
11418
|
+
},
|
|
11419
|
+
tooltipDescriptors: /* @__PURE__ */ new Map(),
|
|
11420
|
+
a11y: {
|
|
11421
|
+
altText: "Empty tile map",
|
|
11422
|
+
dataTableFallback: [],
|
|
11423
|
+
role: "img",
|
|
11424
|
+
keyboardNavigable: false
|
|
11425
|
+
},
|
|
11426
|
+
theme,
|
|
11427
|
+
width: options.width,
|
|
11428
|
+
height: options.height,
|
|
11429
|
+
watermark,
|
|
11430
|
+
animation: void 0,
|
|
11431
|
+
measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth14(text, fontSize), height: fontSize }))
|
|
11432
|
+
};
|
|
11433
|
+
}
|
|
11434
|
+
|
|
10845
11435
|
// src/tooltips/compute.ts
|
|
10846
11436
|
import {
|
|
10847
11437
|
buildTemporalFormatter as buildTemporalFormatter2,
|
|
10848
11438
|
formatDate as formatDate3,
|
|
10849
|
-
formatNumber as
|
|
11439
|
+
formatNumber as formatNumber6,
|
|
10850
11440
|
getRepresentativeColor as getRepresentativeColor10
|
|
10851
11441
|
} from "@opendata-ai/openchart-core";
|
|
10852
11442
|
function formatValue(value2, fieldType, format2) {
|
|
@@ -10861,10 +11451,10 @@ function formatValue(value2, fieldType, format2) {
|
|
|
10861
11451
|
try {
|
|
10862
11452
|
return format(format2)(value2);
|
|
10863
11453
|
} catch {
|
|
10864
|
-
return
|
|
11454
|
+
return formatNumber6(value2);
|
|
10865
11455
|
}
|
|
10866
11456
|
}
|
|
10867
|
-
return
|
|
11457
|
+
return formatNumber6(value2);
|
|
10868
11458
|
}
|
|
10869
11459
|
return String(value2);
|
|
10870
11460
|
}
|
|
@@ -11214,8 +11804,78 @@ function runCalculate(data, transform) {
|
|
|
11214
11804
|
}
|
|
11215
11805
|
|
|
11216
11806
|
// src/transforms/filter.ts
|
|
11807
|
+
function applyOffset2(anchor, offset, unit2) {
|
|
11808
|
+
const d = new Date(anchor.getTime());
|
|
11809
|
+
switch (unit2) {
|
|
11810
|
+
case "year":
|
|
11811
|
+
d.setFullYear(d.getFullYear() + offset);
|
|
11812
|
+
break;
|
|
11813
|
+
case "quarter":
|
|
11814
|
+
d.setMonth(d.getMonth() + offset * 3);
|
|
11815
|
+
break;
|
|
11816
|
+
case "month":
|
|
11817
|
+
d.setMonth(d.getMonth() + offset);
|
|
11818
|
+
break;
|
|
11819
|
+
case "week":
|
|
11820
|
+
d.setDate(d.getDate() + offset * 7);
|
|
11821
|
+
break;
|
|
11822
|
+
case "day":
|
|
11823
|
+
d.setDate(d.getDate() + offset);
|
|
11824
|
+
break;
|
|
11825
|
+
}
|
|
11826
|
+
return d.getTime();
|
|
11827
|
+
}
|
|
11828
|
+
function resolveRef(data, field, ref) {
|
|
11829
|
+
let anchorMs = ref.anchor === "max" ? -Infinity : Infinity;
|
|
11830
|
+
for (const row of data) {
|
|
11831
|
+
const val = row[field];
|
|
11832
|
+
if (val == null) continue;
|
|
11833
|
+
const ms = new Date(val).getTime();
|
|
11834
|
+
if (Number.isNaN(ms)) continue;
|
|
11835
|
+
if (ref.anchor === "max" && ms > anchorMs) anchorMs = ms;
|
|
11836
|
+
if (ref.anchor === "min" && ms < anchorMs) anchorMs = ms;
|
|
11837
|
+
}
|
|
11838
|
+
if (!Number.isFinite(anchorMs)) return 0;
|
|
11839
|
+
return applyOffset2(new Date(anchorMs), ref.offset, ref.unit);
|
|
11840
|
+
}
|
|
11841
|
+
function resolveRelativeRefs(data, predicate) {
|
|
11842
|
+
if ("and" in predicate) {
|
|
11843
|
+
return { and: predicate.and.map((p) => resolveRelativeRefs(data, p)) };
|
|
11844
|
+
}
|
|
11845
|
+
if ("or" in predicate) {
|
|
11846
|
+
return { or: predicate.or.map((p) => resolveRelativeRefs(data, p)) };
|
|
11847
|
+
}
|
|
11848
|
+
if ("not" in predicate) {
|
|
11849
|
+
return { not: resolveRelativeRefs(data, predicate.not) };
|
|
11850
|
+
}
|
|
11851
|
+
if ("field" in predicate) {
|
|
11852
|
+
const fp = predicate;
|
|
11853
|
+
let needsCopy = false;
|
|
11854
|
+
const resolved = {};
|
|
11855
|
+
for (const prop of ["lt", "lte", "gt", "gte"]) {
|
|
11856
|
+
if (isRelativeTimeRef(fp[prop])) {
|
|
11857
|
+
resolved[prop] = resolveRef(data, fp.field, fp[prop]);
|
|
11858
|
+
needsCopy = true;
|
|
11859
|
+
}
|
|
11860
|
+
}
|
|
11861
|
+
if (fp.range) {
|
|
11862
|
+
const [lo, hi] = fp.range;
|
|
11863
|
+
const loResolved = isRelativeTimeRef(lo) ? resolveRef(data, fp.field, lo) : lo;
|
|
11864
|
+
const hiResolved = isRelativeTimeRef(hi) ? resolveRef(data, fp.field, hi) : hi;
|
|
11865
|
+
if (isRelativeTimeRef(lo) || isRelativeTimeRef(hi)) {
|
|
11866
|
+
resolved.range = [loResolved, hiResolved];
|
|
11867
|
+
needsCopy = true;
|
|
11868
|
+
}
|
|
11869
|
+
}
|
|
11870
|
+
if (needsCopy) {
|
|
11871
|
+
return { ...fp, ...resolved };
|
|
11872
|
+
}
|
|
11873
|
+
}
|
|
11874
|
+
return predicate;
|
|
11875
|
+
}
|
|
11217
11876
|
function runFilter(data, predicate) {
|
|
11218
|
-
|
|
11877
|
+
const resolved = resolveRelativeRefs(data, predicate);
|
|
11878
|
+
return data.filter((datum) => evaluatePredicate(datum, resolved));
|
|
11219
11879
|
}
|
|
11220
11880
|
|
|
11221
11881
|
// src/transforms/fold.ts
|
|
@@ -11307,6 +11967,131 @@ function runTimeUnit(data, transform) {
|
|
|
11307
11967
|
});
|
|
11308
11968
|
}
|
|
11309
11969
|
|
|
11970
|
+
// src/transforms/window.ts
|
|
11971
|
+
function groupKey2(row, groupby) {
|
|
11972
|
+
return groupby.map((f) => String(row[f] ?? "")).join("\0");
|
|
11973
|
+
}
|
|
11974
|
+
function tryParseDate(val) {
|
|
11975
|
+
if (val == null) return NaN;
|
|
11976
|
+
if (typeof val === "number") return new Date(val).getTime();
|
|
11977
|
+
if (typeof val === "string" && (val.includes("-") || val.includes("T"))) {
|
|
11978
|
+
const ms = new Date(val).getTime();
|
|
11979
|
+
return ms;
|
|
11980
|
+
}
|
|
11981
|
+
return NaN;
|
|
11982
|
+
}
|
|
11983
|
+
function compareValues(a, b, order) {
|
|
11984
|
+
const dir = order === "descending" ? -1 : 1;
|
|
11985
|
+
const dateA = tryParseDate(a);
|
|
11986
|
+
const dateB = tryParseDate(b);
|
|
11987
|
+
if (!Number.isNaN(dateA) && !Number.isNaN(dateB)) {
|
|
11988
|
+
return dir * (dateA - dateB);
|
|
11989
|
+
}
|
|
11990
|
+
const numA = Number(a);
|
|
11991
|
+
const numB = Number(b);
|
|
11992
|
+
if (Number.isFinite(numA) && Number.isFinite(numB)) {
|
|
11993
|
+
return dir * (numA - numB);
|
|
11994
|
+
}
|
|
11995
|
+
return dir * String(a ?? "").localeCompare(String(b ?? ""));
|
|
11996
|
+
}
|
|
11997
|
+
function runWindow(data, transform) {
|
|
11998
|
+
if (data.length === 0) return [];
|
|
11999
|
+
const { window: windowDefs, sort, groupby = [] } = transform;
|
|
12000
|
+
const indexed = data.map((row, i) => ({ row, originalIndex: i }));
|
|
12001
|
+
const groups = /* @__PURE__ */ new Map();
|
|
12002
|
+
for (const entry of indexed) {
|
|
12003
|
+
const key = groupby.length > 0 ? groupKey2(entry.row, groupby) : "";
|
|
12004
|
+
const existing = groups.get(key);
|
|
12005
|
+
if (existing) {
|
|
12006
|
+
existing.push(entry);
|
|
12007
|
+
} else {
|
|
12008
|
+
groups.set(key, [entry]);
|
|
12009
|
+
}
|
|
12010
|
+
}
|
|
12011
|
+
const result = new Array(data.length);
|
|
12012
|
+
for (const groupEntries of groups.values()) {
|
|
12013
|
+
const sorted = [...groupEntries].sort((a, b) => {
|
|
12014
|
+
for (const s of sort) {
|
|
12015
|
+
const cmp = compareValues(a.row[s.field], b.row[s.field], s.order ?? "ascending");
|
|
12016
|
+
if (cmp !== 0) return cmp;
|
|
12017
|
+
}
|
|
12018
|
+
return 0;
|
|
12019
|
+
});
|
|
12020
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
12021
|
+
const entry = sorted[i];
|
|
12022
|
+
const outRow = { ...entry.row };
|
|
12023
|
+
for (const def of windowDefs) {
|
|
12024
|
+
const offset = def.offset ?? 1;
|
|
12025
|
+
let computed = null;
|
|
12026
|
+
switch (def.op) {
|
|
12027
|
+
case "lag": {
|
|
12028
|
+
const lagIdx = i - offset;
|
|
12029
|
+
computed = lagIdx >= 0 ? sorted[lagIdx].row[def.field] ?? null : null;
|
|
12030
|
+
break;
|
|
12031
|
+
}
|
|
12032
|
+
case "lead": {
|
|
12033
|
+
const leadIdx = i + offset;
|
|
12034
|
+
computed = leadIdx < sorted.length ? sorted[leadIdx].row[def.field] ?? null : null;
|
|
12035
|
+
break;
|
|
12036
|
+
}
|
|
12037
|
+
case "diff": {
|
|
12038
|
+
const lagIdx = i - offset;
|
|
12039
|
+
if (lagIdx >= 0) {
|
|
12040
|
+
const current = Number(entry.row[def.field]);
|
|
12041
|
+
const lagged = Number(sorted[lagIdx].row[def.field]);
|
|
12042
|
+
computed = Number.isFinite(current) && Number.isFinite(lagged) ? current - lagged : null;
|
|
12043
|
+
}
|
|
12044
|
+
break;
|
|
12045
|
+
}
|
|
12046
|
+
case "pct_change": {
|
|
12047
|
+
const lagIdx = i - offset;
|
|
12048
|
+
if (lagIdx >= 0) {
|
|
12049
|
+
const current = Number(entry.row[def.field]);
|
|
12050
|
+
const lagged = Number(sorted[lagIdx].row[def.field]);
|
|
12051
|
+
if (Number.isFinite(current) && Number.isFinite(lagged) && lagged !== 0) {
|
|
12052
|
+
computed = (current - lagged) / lagged;
|
|
12053
|
+
}
|
|
12054
|
+
}
|
|
12055
|
+
break;
|
|
12056
|
+
}
|
|
12057
|
+
case "cumsum": {
|
|
12058
|
+
const val = Number(entry.row[def.field]);
|
|
12059
|
+
const addend = Number.isFinite(val) ? val : 0;
|
|
12060
|
+
if (i === 0) {
|
|
12061
|
+
computed = addend;
|
|
12062
|
+
} else {
|
|
12063
|
+
const prev = Number(result[sorted[i - 1].originalIndex]?.[def.as] ?? 0);
|
|
12064
|
+
computed = prev + addend;
|
|
12065
|
+
}
|
|
12066
|
+
break;
|
|
12067
|
+
}
|
|
12068
|
+
case "rank": {
|
|
12069
|
+
let rank = i + 1;
|
|
12070
|
+
for (let j = 0; j < i; j++) {
|
|
12071
|
+
const isTie = sort.every(
|
|
12072
|
+
(s) => String(sorted[j].row[s.field]) === String(entry.row[s.field])
|
|
12073
|
+
);
|
|
12074
|
+
if (isTie) {
|
|
12075
|
+
rank = result[sorted[j].originalIndex]?.[def.as];
|
|
12076
|
+
break;
|
|
12077
|
+
}
|
|
12078
|
+
}
|
|
12079
|
+
computed = rank;
|
|
12080
|
+
break;
|
|
12081
|
+
}
|
|
12082
|
+
case "first_value": {
|
|
12083
|
+
computed = sorted[0].row[def.field] ?? null;
|
|
12084
|
+
break;
|
|
12085
|
+
}
|
|
12086
|
+
}
|
|
12087
|
+
outRow[def.as] = computed;
|
|
12088
|
+
}
|
|
12089
|
+
result[entry.originalIndex] = outRow;
|
|
12090
|
+
}
|
|
12091
|
+
}
|
|
12092
|
+
return result;
|
|
12093
|
+
}
|
|
12094
|
+
|
|
11310
12095
|
// src/transforms/index.ts
|
|
11311
12096
|
function runTransforms(data, transforms) {
|
|
11312
12097
|
let result = data;
|
|
@@ -11323,6 +12108,8 @@ function runTransforms(data, transforms) {
|
|
|
11323
12108
|
result = runAggregate(result, transform);
|
|
11324
12109
|
} else if ("fold" in transform) {
|
|
11325
12110
|
result = runFold(result, transform);
|
|
12111
|
+
} else if ("window" in transform) {
|
|
12112
|
+
result = runWindow(result, transform);
|
|
11326
12113
|
}
|
|
11327
12114
|
}
|
|
11328
12115
|
return result;
|
|
@@ -11410,14 +12197,21 @@ function compileChart(spec, options) {
|
|
|
11410
12197
|
}
|
|
11411
12198
|
};
|
|
11412
12199
|
}
|
|
11413
|
-
if (bp.labels) {
|
|
11414
|
-
|
|
11415
|
-
|
|
11416
|
-
|
|
11417
|
-
|
|
11418
|
-
|
|
11419
|
-
|
|
11420
|
-
|
|
12200
|
+
if (bp.labels !== void 0) {
|
|
12201
|
+
if (typeof bp.labels === "boolean") {
|
|
12202
|
+
chartSpec = {
|
|
12203
|
+
...chartSpec,
|
|
12204
|
+
labels: bp.labels ? { density: "auto", format: "", prefix: "" } : { density: "none", format: "", prefix: "" }
|
|
12205
|
+
};
|
|
12206
|
+
} else {
|
|
12207
|
+
chartSpec = {
|
|
12208
|
+
...chartSpec,
|
|
12209
|
+
labels: {
|
|
12210
|
+
...chartSpec.labels,
|
|
12211
|
+
...bp.labels
|
|
12212
|
+
}
|
|
12213
|
+
};
|
|
12214
|
+
}
|
|
11421
12215
|
}
|
|
11422
12216
|
if (bp.legend) {
|
|
11423
12217
|
chartSpec = {
|
|
@@ -11439,9 +12233,9 @@ function compileChart(spec, options) {
|
|
|
11439
12233
|
const rawAnimationSpec = overrides?.[breakpoint]?.animation ?? rawSpec.animation;
|
|
11440
12234
|
const resolvedAnimation = resolveAnimation(rawAnimationSpec);
|
|
11441
12235
|
const mergedThemeConfig = options.theme ? { ...chartSpec.theme, ...options.theme } : chartSpec.theme;
|
|
11442
|
-
let theme =
|
|
12236
|
+
let theme = resolveTheme4(mergedThemeConfig);
|
|
11443
12237
|
if (options.darkMode) {
|
|
11444
|
-
theme =
|
|
12238
|
+
theme = adaptTheme4(theme);
|
|
11445
12239
|
}
|
|
11446
12240
|
const preliminaryArea = {
|
|
11447
12241
|
x: 0,
|
|
@@ -11453,7 +12247,7 @@ function compileChart(spec, options) {
|
|
|
11453
12247
|
const dims = computeDimensions(chartSpec, options, legendLayout, theme, strategy, watermark);
|
|
11454
12248
|
const chartArea = dims.chartArea;
|
|
11455
12249
|
const legendArea = { ...chartArea };
|
|
11456
|
-
if (legendLayout.entries.length > 0) {
|
|
12250
|
+
if ("entries" in legendLayout && legendLayout.entries.length > 0) {
|
|
11457
12251
|
const gap = legendGap(options.width);
|
|
11458
12252
|
switch (legendLayout.position) {
|
|
11459
12253
|
case "top":
|
|
@@ -11482,7 +12276,10 @@ function compileChart(spec, options) {
|
|
|
11482
12276
|
applyColorScaleRange(scales, renderSpec.encoding, theme);
|
|
11483
12277
|
scales.defaultColor = chartSpec.markDef.fill ?? chartSpec.markDef.stroke ?? theme.colors.categorical[0];
|
|
11484
12278
|
const isRadial = chartSpec.markType === "arc";
|
|
11485
|
-
const axes = isRadial ? { x: void 0, y: void 0 } : computeAxes(scales, chartArea, strategy, theme, options.measureText
|
|
12279
|
+
const axes = isRadial ? { x: void 0, y: void 0 } : computeAxes(scales, chartArea, strategy, theme, options.measureText, {
|
|
12280
|
+
data: renderSpec.data,
|
|
12281
|
+
encoding: renderSpec.encoding
|
|
12282
|
+
});
|
|
11486
12283
|
if (!isRadial) {
|
|
11487
12284
|
computeGridlines(axes, chartArea);
|
|
11488
12285
|
}
|
|
@@ -11576,7 +12373,8 @@ function compileLayer(spec, options) {
|
|
|
11576
12373
|
const primaryLayout = compileChart(primarySpec, options);
|
|
11577
12374
|
const allMarks = [];
|
|
11578
12375
|
const seenLabels = /* @__PURE__ */ new Set();
|
|
11579
|
-
const
|
|
12376
|
+
const pLegend = primaryLayout.legend;
|
|
12377
|
+
const mergedLegendEntries = "entries" in pLegend ? [...pLegend.entries] : [];
|
|
11580
12378
|
for (const entry of mergedLegendEntries) {
|
|
11581
12379
|
seenLabels.add(entry.label);
|
|
11582
12380
|
}
|
|
@@ -11588,10 +12386,13 @@ function compileLayer(spec, options) {
|
|
|
11588
12386
|
for (const { leaf } of indexedLeaves) {
|
|
11589
12387
|
const leafLayout = compileChart(leaf, options);
|
|
11590
12388
|
allMarks.push(...leafLayout.marks);
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
12389
|
+
const leafLeg = leafLayout.legend;
|
|
12390
|
+
if ("entries" in leafLeg) {
|
|
12391
|
+
for (const entry of leafLeg.entries) {
|
|
12392
|
+
if (!seenLabels.has(entry.label)) {
|
|
12393
|
+
seenLabels.add(entry.label);
|
|
12394
|
+
mergedLegendEntries.push(entry);
|
|
12395
|
+
}
|
|
11595
12396
|
}
|
|
11596
12397
|
}
|
|
11597
12398
|
}
|
|
@@ -11600,7 +12401,7 @@ function compileLayer(spec, options) {
|
|
|
11600
12401
|
marks: allMarks,
|
|
11601
12402
|
legend: {
|
|
11602
12403
|
...primaryLayout.legend,
|
|
11603
|
-
entries: mergedLegendEntries
|
|
12404
|
+
..."entries" in pLegend ? { entries: mergedLegendEntries } : {}
|
|
11604
12405
|
}
|
|
11605
12406
|
};
|
|
11606
12407
|
}
|
|
@@ -11614,7 +12415,7 @@ function estimateYAxisLabelWidth(data, encoding, baseFontSize) {
|
|
|
11614
12415
|
let maxWidth = 0;
|
|
11615
12416
|
for (const row of data) {
|
|
11616
12417
|
const label = String(row[yField] ?? "");
|
|
11617
|
-
const w =
|
|
12418
|
+
const w = estimateTextWidth15(label, baseFontSize, 400);
|
|
11618
12419
|
if (w > maxWidth) maxWidth = w;
|
|
11619
12420
|
}
|
|
11620
12421
|
return maxWidth > 0 ? maxWidth + 10 : 40;
|
|
@@ -11643,7 +12444,7 @@ function estimateYAxisLabelWidth(data, encoding, baseFontSize) {
|
|
|
11643
12444
|
}
|
|
11644
12445
|
const hasNeg = data.some((r) => Number(r[yField]) < 0);
|
|
11645
12446
|
const labelEst = (hasNeg ? "-" : "") + sampleLabel;
|
|
11646
|
-
return
|
|
12447
|
+
return estimateTextWidth15(labelEst, baseFontSize, 400) + 10;
|
|
11647
12448
|
}
|
|
11648
12449
|
function compileLayerIndependent(leaves, layerSpec, options) {
|
|
11649
12450
|
if (leaves.length > 2) {
|
|
@@ -11660,7 +12461,7 @@ function compileLayerIndependent(leaves, layerSpec, options) {
|
|
|
11660
12461
|
`Dual-axis charts require matching x-field types across layers. Layer 0 has '${xType0}', layer 1 has '${xType1}'.`
|
|
11661
12462
|
);
|
|
11662
12463
|
}
|
|
11663
|
-
const theme =
|
|
12464
|
+
const theme = resolveTheme4(layerSpec.theme ?? leaf1.theme);
|
|
11664
12465
|
const axisFontSize = theme.fonts?.sizes?.axisTick ?? 11;
|
|
11665
12466
|
const rightAxisWidth = estimateYAxisLabelWidth(leaf1.data, leaf1.encoding, axisFontSize);
|
|
11666
12467
|
const hasRightAxisTitle = !!leaf1.encoding?.y?.axis?.title;
|
|
@@ -11777,9 +12578,12 @@ function compileLayerIndependent(leaves, layerSpec, options) {
|
|
|
11777
12578
|
return tagged;
|
|
11778
12579
|
});
|
|
11779
12580
|
const seenLabels = /* @__PURE__ */ new Set();
|
|
11780
|
-
const
|
|
12581
|
+
const l0Legend = layout0.legend;
|
|
12582
|
+
const l1Legend = layout1.legend;
|
|
12583
|
+
const mergedLegendEntries = "entries" in l0Legend ? [...l0Legend.entries] : [];
|
|
11781
12584
|
for (const entry of mergedLegendEntries) seenLabels.add(entry.label);
|
|
11782
|
-
|
|
12585
|
+
const l1Entries = "entries" in l1Legend ? l1Legend.entries : [];
|
|
12586
|
+
for (const entry of l1Entries) {
|
|
11783
12587
|
if (!seenLabels.has(entry.label)) {
|
|
11784
12588
|
seenLabels.add(entry.label);
|
|
11785
12589
|
mergedLegendEntries.push(entry);
|
|
@@ -11809,7 +12613,7 @@ function compileLayerIndependent(leaves, layerSpec, options) {
|
|
|
11809
12613
|
marks,
|
|
11810
12614
|
legend: {
|
|
11811
12615
|
...layout0.legend,
|
|
11812
|
-
entries: mergedLegendEntries
|
|
12616
|
+
..."entries" in l0Legend ? { entries: mergedLegendEntries } : {}
|
|
11813
12617
|
},
|
|
11814
12618
|
tooltipDescriptors: mergedTooltips
|
|
11815
12619
|
};
|
|
@@ -11922,9 +12726,9 @@ function compileTable(spec, options) {
|
|
|
11922
12726
|
}
|
|
11923
12727
|
const tableSpec = normalized;
|
|
11924
12728
|
const mergedThemeConfig = options.theme ? { ...tableSpec.theme, ...options.theme } : tableSpec.theme;
|
|
11925
|
-
let theme =
|
|
12729
|
+
let theme = resolveTheme4(mergedThemeConfig);
|
|
11926
12730
|
if (options.darkMode) {
|
|
11927
|
-
theme =
|
|
12731
|
+
theme = adaptTheme4(theme);
|
|
11928
12732
|
}
|
|
11929
12733
|
const rawWatermark = spec.watermark;
|
|
11930
12734
|
const watermark = rawWatermark !== void 0 ? tableSpec.watermark : options.watermark ?? true;
|
|
@@ -11936,6 +12740,9 @@ function compileGraph2(spec, options) {
|
|
|
11936
12740
|
function compileSankey2(spec, options) {
|
|
11937
12741
|
return compileSankey(spec, options);
|
|
11938
12742
|
}
|
|
12743
|
+
function compileTileMap2(spec, options) {
|
|
12744
|
+
return compileTileMap(spec, options);
|
|
12745
|
+
}
|
|
11939
12746
|
export {
|
|
11940
12747
|
clampStaggerDelay,
|
|
11941
12748
|
clearRenderers,
|
|
@@ -11945,6 +12752,7 @@ export {
|
|
|
11945
12752
|
compileLayer,
|
|
11946
12753
|
compileSankey2 as compileSankey,
|
|
11947
12754
|
compileTable,
|
|
12755
|
+
compileTileMap2 as compileTileMap,
|
|
11948
12756
|
evaluatePredicate,
|
|
11949
12757
|
getChartRenderer,
|
|
11950
12758
|
isConditionalValueDef,
|