@opendata-ai/openchart-engine 6.27.2 → 6.28.4
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 +38 -6
- package/dist/index.js +1032 -521
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +31 -4
- package/src/__tests__/legend.test.ts +2 -2
- package/src/barlist/__tests__/compile-barlist.test.ts +200 -0
- package/src/barlist/compile-barlist.ts +380 -0
- package/src/barlist/types.ts +28 -0
- package/src/charts/bar/__tests__/compute.test.ts +120 -0
- package/src/charts/bar/compute.ts +77 -45
- package/src/charts/bar/index.ts +1 -0
- package/src/charts/bar/labels.ts +3 -2
- package/src/charts/column/compute.ts +60 -27
- package/src/charts/column/index.ts +1 -0
- package/src/charts/column/labels.ts +2 -1
- package/src/charts/line/__tests__/compute.test.ts +2 -2
- package/src/charts/line/area.ts +25 -4
- package/src/charts/line/compute.ts +15 -5
- package/src/compile.ts +26 -1
- package/src/compiler/normalize.ts +25 -1
- package/src/compiler/types.ts +5 -3
- package/src/compiler/validate.ts +120 -5
- package/src/index.ts +5 -0
- package/src/layout/axes/ticks.ts +6 -4
- package/src/layout/axes.ts +2 -2
- package/src/layout/dimensions.ts +10 -4
- package/src/layout/scales.ts +10 -0
- package/src/legend/wrap.ts +1 -1
- package/src/tables/__tests__/heatmap.test.ts +22 -0
- package/src/tables/heatmap.ts +28 -0
- package/src/tilemap/__tests__/compile-tilemap.test.ts +10 -5
- package/src/tilemap/compile-tilemap.ts +51 -30
- package/src/tooltips/compute.ts +4 -2
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 adaptTheme5,
|
|
5
5
|
BREAKPOINT_COMPACT_MAX as BREAKPOINT_COMPACT_MAX2,
|
|
6
6
|
computeLabelBounds,
|
|
7
|
-
estimateTextWidth as
|
|
7
|
+
estimateTextWidth as estimateTextWidth16,
|
|
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 resolveTheme5,
|
|
15
15
|
TICK_LABEL_OFFSET
|
|
16
16
|
} from "@opendata-ai/openchart-core";
|
|
17
17
|
|
|
@@ -4312,8 +4312,9 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
|
|
|
4312
4312
|
const conditionalColor = encoding.color && isConditionalValueDef(encoding.color) ? encoding.color : void 0;
|
|
4313
4313
|
const colorField = colorEnc?.field;
|
|
4314
4314
|
const isSequentialColor = colorEnc?.type === "quantitative";
|
|
4315
|
+
let marks;
|
|
4315
4316
|
if (!colorField || isSequentialColor) {
|
|
4316
|
-
|
|
4317
|
+
marks = computeSimpleBars(
|
|
4317
4318
|
spec.data,
|
|
4318
4319
|
xChannel.field,
|
|
4319
4320
|
yChannel.field,
|
|
@@ -4325,13 +4326,40 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
|
|
|
4325
4326
|
isSequentialColor,
|
|
4326
4327
|
conditionalColor
|
|
4327
4328
|
);
|
|
4328
|
-
}
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4329
|
+
} else {
|
|
4330
|
+
const categoryGroups = groupByField(spec.data, yChannel.field);
|
|
4331
|
+
const needsStacking = Array.from(categoryGroups.values()).some((rows) => rows.length > 1);
|
|
4332
|
+
if (needsStacking) {
|
|
4333
|
+
const stackDisabled = xChannel.stack === null || xChannel.stack === false;
|
|
4334
|
+
if (stackDisabled) {
|
|
4335
|
+
marks = computeGroupedBars(
|
|
4336
|
+
spec.data,
|
|
4337
|
+
xChannel.field,
|
|
4338
|
+
yChannel.field,
|
|
4339
|
+
colorField,
|
|
4340
|
+
xScale,
|
|
4341
|
+
yScale,
|
|
4342
|
+
bandwidth,
|
|
4343
|
+
baseline,
|
|
4344
|
+
scales
|
|
4345
|
+
);
|
|
4346
|
+
} else {
|
|
4347
|
+
const stackMode = xChannel.stack === "normalize" ? "normalize" : xChannel.stack === "center" ? "center" : "zero";
|
|
4348
|
+
marks = computeStackedBars(
|
|
4349
|
+
spec.data,
|
|
4350
|
+
xChannel.field,
|
|
4351
|
+
yChannel.field,
|
|
4352
|
+
colorField,
|
|
4353
|
+
xScale,
|
|
4354
|
+
yScale,
|
|
4355
|
+
bandwidth,
|
|
4356
|
+
baseline,
|
|
4357
|
+
scales,
|
|
4358
|
+
stackMode
|
|
4359
|
+
);
|
|
4360
|
+
}
|
|
4361
|
+
} else {
|
|
4362
|
+
marks = computeColoredBars(
|
|
4335
4363
|
spec.data,
|
|
4336
4364
|
xChannel.field,
|
|
4337
4365
|
yChannel.field,
|
|
@@ -4343,31 +4371,8 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
|
|
|
4343
4371
|
scales
|
|
4344
4372
|
);
|
|
4345
4373
|
}
|
|
4346
|
-
const stackMode = xChannel.stack === "normalize" ? "normalize" : xChannel.stack === "center" ? "center" : "zero";
|
|
4347
|
-
return computeStackedBars(
|
|
4348
|
-
spec.data,
|
|
4349
|
-
xChannel.field,
|
|
4350
|
-
yChannel.field,
|
|
4351
|
-
colorField,
|
|
4352
|
-
xScale,
|
|
4353
|
-
yScale,
|
|
4354
|
-
bandwidth,
|
|
4355
|
-
baseline,
|
|
4356
|
-
scales,
|
|
4357
|
-
stackMode
|
|
4358
|
-
);
|
|
4359
4374
|
}
|
|
4360
|
-
return
|
|
4361
|
-
spec.data,
|
|
4362
|
-
xChannel.field,
|
|
4363
|
-
yChannel.field,
|
|
4364
|
-
colorField,
|
|
4365
|
-
xScale,
|
|
4366
|
-
yScale,
|
|
4367
|
-
bandwidth,
|
|
4368
|
-
baseline,
|
|
4369
|
-
scales
|
|
4370
|
-
);
|
|
4375
|
+
return applyMarkDefOverrides(marks, spec, bandwidth);
|
|
4371
4376
|
}
|
|
4372
4377
|
function computeStackedBars(data, valueField, categoryField, colorField, xScale, yScale, bandwidth, _baseline, scales, stackMode = "zero") {
|
|
4373
4378
|
const marks = [];
|
|
@@ -4486,6 +4491,27 @@ function computeColoredBars(data, valueField, categoryField, colorField, xScale,
|
|
|
4486
4491
|
}
|
|
4487
4492
|
return marks;
|
|
4488
4493
|
}
|
|
4494
|
+
function applyMarkDefOverrides(marks, spec, bandwidth) {
|
|
4495
|
+
const { markDef } = spec;
|
|
4496
|
+
const fixedSize = markDef.size;
|
|
4497
|
+
const crSpec = markDef.cornerRadius;
|
|
4498
|
+
if (fixedSize == null && crSpec == null) return marks;
|
|
4499
|
+
for (const mark of marks) {
|
|
4500
|
+
if (fixedSize != null && mark.stackGroup === void 0) {
|
|
4501
|
+
const barHeight = Math.min(fixedSize, bandwidth);
|
|
4502
|
+
const offset = (bandwidth - barHeight) / 2;
|
|
4503
|
+
mark.y = mark.y + offset;
|
|
4504
|
+
mark.height = barHeight;
|
|
4505
|
+
}
|
|
4506
|
+
const effectiveHeight = mark.height;
|
|
4507
|
+
if (crSpec === "pill") {
|
|
4508
|
+
mark.cornerRadius = effectiveHeight / 2;
|
|
4509
|
+
} else if (typeof crSpec === "number") {
|
|
4510
|
+
mark.cornerRadius = crSpec;
|
|
4511
|
+
}
|
|
4512
|
+
}
|
|
4513
|
+
return marks;
|
|
4514
|
+
}
|
|
4489
4515
|
function computeSimpleBars(data, valueField, categoryField, xScale, yScale, bandwidth, baseline, scales, sequentialColor = false, conditionalColor) {
|
|
4490
4516
|
const marks = [];
|
|
4491
4517
|
for (const row of data) {
|
|
@@ -4572,7 +4598,7 @@ var LABEL_FONT_SIZE = 11;
|
|
|
4572
4598
|
var LABEL_FONT_WEIGHT = 600;
|
|
4573
4599
|
var LABEL_PADDING = 6;
|
|
4574
4600
|
var MIN_WIDTH_FOR_INSIDE_LABEL = 40;
|
|
4575
|
-
function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField) {
|
|
4601
|
+
function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField, labelColor) {
|
|
4576
4602
|
const targetMarks = filterByDensity(marks, density);
|
|
4577
4603
|
const candidates = [];
|
|
4578
4604
|
const fitsInSegment = [];
|
|
@@ -4623,11 +4649,11 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
|
|
|
4623
4649
|
} else {
|
|
4624
4650
|
if (isNegative) {
|
|
4625
4651
|
anchorX = mark.x - LABEL_PADDING;
|
|
4626
|
-
fill = getRepresentativeColor(mark.fill);
|
|
4652
|
+
fill = labelColor ?? getRepresentativeColor(mark.fill);
|
|
4627
4653
|
textAnchor = "end";
|
|
4628
4654
|
} else {
|
|
4629
4655
|
anchorX = mark.x + mark.width + LABEL_PADDING;
|
|
4630
|
-
fill = getRepresentativeColor(mark.fill);
|
|
4656
|
+
fill = labelColor ?? getRepresentativeColor(mark.fill);
|
|
4631
4657
|
textAnchor = "start";
|
|
4632
4658
|
}
|
|
4633
4659
|
}
|
|
@@ -4700,7 +4726,8 @@ var barRenderer = (spec, scales, chartArea, strategy, _theme) => {
|
|
|
4700
4726
|
spec.labels.density,
|
|
4701
4727
|
spec.labels.format,
|
|
4702
4728
|
spec.labels.prefix,
|
|
4703
|
-
valueField
|
|
4729
|
+
valueField,
|
|
4730
|
+
spec.labels.color
|
|
4704
4731
|
);
|
|
4705
4732
|
for (let i = 0; i < marks.length && i < labels.length; i++) {
|
|
4706
4733
|
marks[i].label = labels[i];
|
|
@@ -4729,13 +4756,14 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
|
|
|
4729
4756
|
const conditionalColor = encoding.color && isConditionalValueDef(encoding.color) ? encoding.color : void 0;
|
|
4730
4757
|
const colorField = colorEnc?.field;
|
|
4731
4758
|
const isSequentialColor = colorEnc?.type === "quantitative";
|
|
4759
|
+
let marks;
|
|
4732
4760
|
if (colorField && !isSequentialColor) {
|
|
4733
4761
|
const categoryGroups = groupByField(spec.data, xChannel.field);
|
|
4734
4762
|
const needsStacking = Array.from(categoryGroups.values()).some((rows) => rows.length > 1);
|
|
4735
4763
|
if (needsStacking) {
|
|
4736
4764
|
const stackDisabled = yChannel.stack === null || yChannel.stack === false;
|
|
4737
4765
|
if (stackDisabled) {
|
|
4738
|
-
|
|
4766
|
+
marks = computeGroupedColumns(
|
|
4739
4767
|
spec.data,
|
|
4740
4768
|
xChannel.field,
|
|
4741
4769
|
yChannel.field,
|
|
@@ -4746,9 +4774,23 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
|
|
|
4746
4774
|
baseline,
|
|
4747
4775
|
scales
|
|
4748
4776
|
);
|
|
4777
|
+
} else {
|
|
4778
|
+
const stackMode = yChannel.stack === "normalize" ? "normalize" : yChannel.stack === "center" ? "center" : "zero";
|
|
4779
|
+
marks = computeStackedColumns(
|
|
4780
|
+
spec.data,
|
|
4781
|
+
xChannel.field,
|
|
4782
|
+
yChannel.field,
|
|
4783
|
+
colorField,
|
|
4784
|
+
xScale,
|
|
4785
|
+
yScale,
|
|
4786
|
+
bandwidth,
|
|
4787
|
+
baseline,
|
|
4788
|
+
scales,
|
|
4789
|
+
stackMode
|
|
4790
|
+
);
|
|
4749
4791
|
}
|
|
4750
|
-
|
|
4751
|
-
|
|
4792
|
+
} else {
|
|
4793
|
+
marks = computeColoredColumns(
|
|
4752
4794
|
spec.data,
|
|
4753
4795
|
xChannel.field,
|
|
4754
4796
|
yChannel.field,
|
|
@@ -4757,34 +4799,24 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
|
|
|
4757
4799
|
yScale,
|
|
4758
4800
|
bandwidth,
|
|
4759
4801
|
baseline,
|
|
4760
|
-
scales
|
|
4761
|
-
stackMode
|
|
4802
|
+
scales
|
|
4762
4803
|
);
|
|
4763
4804
|
}
|
|
4764
|
-
|
|
4805
|
+
} else {
|
|
4806
|
+
marks = computeSimpleColumns(
|
|
4765
4807
|
spec.data,
|
|
4766
4808
|
xChannel.field,
|
|
4767
4809
|
yChannel.field,
|
|
4768
|
-
colorField,
|
|
4769
4810
|
xScale,
|
|
4770
4811
|
yScale,
|
|
4771
4812
|
bandwidth,
|
|
4772
4813
|
baseline,
|
|
4773
|
-
scales
|
|
4814
|
+
scales,
|
|
4815
|
+
isSequentialColor,
|
|
4816
|
+
conditionalColor
|
|
4774
4817
|
);
|
|
4775
4818
|
}
|
|
4776
|
-
return
|
|
4777
|
-
spec.data,
|
|
4778
|
-
xChannel.field,
|
|
4779
|
-
yChannel.field,
|
|
4780
|
-
xScale,
|
|
4781
|
-
yScale,
|
|
4782
|
-
bandwidth,
|
|
4783
|
-
baseline,
|
|
4784
|
-
scales,
|
|
4785
|
-
isSequentialColor,
|
|
4786
|
-
conditionalColor
|
|
4787
|
-
);
|
|
4819
|
+
return applyMarkDefOverrides2(marks, spec, bandwidth);
|
|
4788
4820
|
}
|
|
4789
4821
|
function computeSimpleColumns(data, categoryField, valueField, xScale, yScale, bandwidth, baseline, scales, sequentialColor = false, conditionalColor) {
|
|
4790
4822
|
const marks = [];
|
|
@@ -4950,6 +4982,27 @@ function computeStackedColumns(data, categoryField, valueField, colorField, xSca
|
|
|
4950
4982
|
}
|
|
4951
4983
|
return marks;
|
|
4952
4984
|
}
|
|
4985
|
+
function applyMarkDefOverrides2(marks, spec, bandwidth) {
|
|
4986
|
+
const { markDef } = spec;
|
|
4987
|
+
const fixedSize = markDef.size;
|
|
4988
|
+
const crSpec = markDef.cornerRadius;
|
|
4989
|
+
if (fixedSize == null && crSpec == null) return marks;
|
|
4990
|
+
for (const mark of marks) {
|
|
4991
|
+
if (fixedSize != null && mark.stackGroup === void 0) {
|
|
4992
|
+
const barWidth = Math.min(fixedSize, bandwidth);
|
|
4993
|
+
const offset = (bandwidth - barWidth) / 2;
|
|
4994
|
+
mark.x = mark.x + offset;
|
|
4995
|
+
mark.width = barWidth;
|
|
4996
|
+
}
|
|
4997
|
+
const effectiveWidth = mark.width;
|
|
4998
|
+
if (crSpec === "pill") {
|
|
4999
|
+
mark.cornerRadius = effectiveWidth / 2;
|
|
5000
|
+
} else if (typeof crSpec === "number") {
|
|
5001
|
+
mark.cornerRadius = crSpec;
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
return marks;
|
|
5005
|
+
}
|
|
4953
5006
|
|
|
4954
5007
|
// src/charts/column/labels.ts
|
|
4955
5008
|
import {
|
|
@@ -4961,7 +5014,7 @@ import {
|
|
|
4961
5014
|
var LABEL_FONT_SIZE2 = 10;
|
|
4962
5015
|
var LABEL_FONT_WEIGHT2 = 600;
|
|
4963
5016
|
var LABEL_OFFSET_Y = 6;
|
|
4964
|
-
function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField) {
|
|
5017
|
+
function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField, labelColor) {
|
|
4965
5018
|
const targetMarks = filterByDensity(marks, density);
|
|
4966
5019
|
const formatter = buildD3Formatter2(labelFormat);
|
|
4967
5020
|
const candidates = [];
|
|
@@ -5002,7 +5055,7 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, l
|
|
|
5002
5055
|
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
5003
5056
|
fontSize: LABEL_FONT_SIZE2,
|
|
5004
5057
|
fontWeight: LABEL_FONT_WEIGHT2,
|
|
5005
|
-
fill: getRepresentativeColor2(mark.fill),
|
|
5058
|
+
fill: labelColor ?? getRepresentativeColor2(mark.fill),
|
|
5006
5059
|
lineHeight: 1.2,
|
|
5007
5060
|
textAnchor: "middle",
|
|
5008
5061
|
dominantBaseline: isNegative ? "hanging" : "auto"
|
|
@@ -5032,7 +5085,8 @@ var columnRenderer = (spec, scales, chartArea, strategy, _theme) => {
|
|
|
5032
5085
|
spec.labels.density,
|
|
5033
5086
|
spec.labels.format,
|
|
5034
5087
|
spec.labels.prefix,
|
|
5035
|
-
valueField
|
|
5088
|
+
valueField,
|
|
5089
|
+
spec.labels.color
|
|
5036
5090
|
);
|
|
5037
5091
|
for (let i = 0; i < marks.length && i < labels.length; i++) {
|
|
5038
5092
|
marks[i].label = labels[i];
|
|
@@ -5355,9 +5409,26 @@ function computeSingleArea(spec, scales, _chartArea) {
|
|
|
5355
5409
|
const ariaLabel = seriesKey === "__default__" ? `Area with ${validPoints.length} data points` : `${seriesKey}: area with ${validPoints.length} data points`;
|
|
5356
5410
|
const aria = { label: ariaLabel };
|
|
5357
5411
|
const markFill = spec.markDef.fill;
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5412
|
+
let fillValue;
|
|
5413
|
+
let fillOpacity;
|
|
5414
|
+
if (markFill != null) {
|
|
5415
|
+
fillValue = markFill;
|
|
5416
|
+
fillOpacity = isGradientDef3(markFill) ? 1 : spec.markDef.opacity ?? (y2Channel ? 0.25 : DEFAULT_FILL_OPACITY);
|
|
5417
|
+
} else {
|
|
5418
|
+
const colorStr = getRepresentativeColor4(color2);
|
|
5419
|
+
fillValue = {
|
|
5420
|
+
gradient: "linear",
|
|
5421
|
+
x1: 0,
|
|
5422
|
+
y1: 0,
|
|
5423
|
+
x2: 0,
|
|
5424
|
+
y2: 1,
|
|
5425
|
+
stops: [
|
|
5426
|
+
{ offset: 0, color: colorStr, opacity: 0.12 },
|
|
5427
|
+
{ offset: 1, color: colorStr, opacity: 0 }
|
|
5428
|
+
]
|
|
5429
|
+
};
|
|
5430
|
+
fillOpacity = 1;
|
|
5431
|
+
}
|
|
5361
5432
|
marks.push({
|
|
5362
5433
|
type: "area",
|
|
5363
5434
|
topPoints,
|
|
@@ -5367,7 +5438,7 @@ function computeSingleArea(spec, scales, _chartArea) {
|
|
|
5367
5438
|
fill: fillValue,
|
|
5368
5439
|
fillOpacity,
|
|
5369
5440
|
stroke: getRepresentativeColor4(isGradientDef3(fillValue) ? color2 : fillValue),
|
|
5370
|
-
strokeWidth: spec.display === "sparkline" ? 1.25 :
|
|
5441
|
+
strokeWidth: spec.markDef.strokeWidth ?? (spec.display === "sparkline" ? 1.25 : 1.5),
|
|
5371
5442
|
seriesKey: seriesKey === "__default__" ? void 0 : seriesKey,
|
|
5372
5443
|
data: validPoints.map((p) => p.row),
|
|
5373
5444
|
dataPoints: validPoints.map((p) => ({ x: p.x, y: p.yTop, datum: p.row })),
|
|
@@ -5483,7 +5554,7 @@ function computeAreaMarks(spec, scales, chartArea) {
|
|
|
5483
5554
|
|
|
5484
5555
|
// src/charts/line/compute.ts
|
|
5485
5556
|
import { getRepresentativeColor as getRepresentativeColor5 } from "@opendata-ai/openchart-core";
|
|
5486
|
-
var DEFAULT_STROKE_WIDTH =
|
|
5557
|
+
var DEFAULT_STROKE_WIDTH = 1.5;
|
|
5487
5558
|
var SPARKLINE_STROKE_WIDTH = 1.25;
|
|
5488
5559
|
var DEFAULT_POINT_RADIUS = 3;
|
|
5489
5560
|
function computeLineMarks(spec, scales, _chartArea, _strategy) {
|
|
@@ -5551,7 +5622,7 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
|
|
|
5551
5622
|
points: allPoints,
|
|
5552
5623
|
path: combinedPath,
|
|
5553
5624
|
stroke: strokeColor,
|
|
5554
|
-
strokeWidth: styleOverride?.strokeWidth ?? (spec.display === "sparkline" ? SPARKLINE_STROKE_WIDTH : DEFAULT_STROKE_WIDTH),
|
|
5625
|
+
strokeWidth: styleOverride?.strokeWidth ?? spec.markDef.strokeWidth ?? (spec.display === "sparkline" ? SPARKLINE_STROKE_WIDTH : DEFAULT_STROKE_WIDTH),
|
|
5555
5626
|
strokeDasharray,
|
|
5556
5627
|
opacity: styleOverride?.opacity,
|
|
5557
5628
|
seriesKey: seriesStyleKey,
|
|
@@ -5561,25 +5632,30 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
|
|
|
5561
5632
|
};
|
|
5562
5633
|
marks.push(lineMark);
|
|
5563
5634
|
const markPoint = spec.markDef.point;
|
|
5564
|
-
const showPoints = markPoint === true || markPoint === "transparent" || isSequentialColor;
|
|
5635
|
+
const showPoints = markPoint === true || markPoint === "transparent" || markPoint === "endpoints" || isSequentialColor;
|
|
5565
5636
|
if (showPoints) {
|
|
5566
5637
|
const isTransparent = markPoint === "transparent";
|
|
5638
|
+
const isEndpoints = markPoint === "endpoints";
|
|
5567
5639
|
const seriesShowPoints = styleOverride?.showPoints !== false;
|
|
5640
|
+
const lastIdx = pointsWithData.length - 1;
|
|
5568
5641
|
for (let i = 0; i < pointsWithData.length; i++) {
|
|
5569
5642
|
const p = pointsWithData[i];
|
|
5570
|
-
const
|
|
5643
|
+
const isEndpoint = i === 0 || i === lastIdx;
|
|
5644
|
+
const visible = seriesShowPoints && !isTransparent && (!isEndpoints || isEndpoint);
|
|
5571
5645
|
let pointColor = color2;
|
|
5572
5646
|
if (isSequentialColor) {
|
|
5573
5647
|
const val = Number(p.row[sequentialColorField]);
|
|
5574
5648
|
pointColor = Number.isFinite(val) ? getSequentialColor(scales, val) : color2;
|
|
5575
5649
|
}
|
|
5650
|
+
const hollow = isEndpoints && visible;
|
|
5651
|
+
const pointColorStr = getRepresentativeColor5(pointColor);
|
|
5576
5652
|
const pointMark = {
|
|
5577
5653
|
type: "point",
|
|
5578
5654
|
cx: p.x,
|
|
5579
5655
|
cy: p.y,
|
|
5580
5656
|
r: visible ? DEFAULT_POINT_RADIUS : 0,
|
|
5581
|
-
fill:
|
|
5582
|
-
stroke: visible ? "#ffffff" : "transparent",
|
|
5657
|
+
fill: hollow ? "transparent" : pointColorStr,
|
|
5658
|
+
stroke: hollow ? pointColorStr : visible ? "#ffffff" : "transparent",
|
|
5583
5659
|
strokeWidth: visible ? 1.5 : 0,
|
|
5584
5660
|
fillOpacity: isTransparent ? 0 : 1,
|
|
5585
5661
|
data: p.row,
|
|
@@ -6309,262 +6385,90 @@ function registerBuiltinRenderers() {
|
|
|
6309
6385
|
}
|
|
6310
6386
|
registerBuiltinRenderers();
|
|
6311
6387
|
|
|
6312
|
-
// src/
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6388
|
+
// src/barlist/compile-barlist.ts
|
|
6389
|
+
import {
|
|
6390
|
+
adaptTheme,
|
|
6391
|
+
buildD3Formatter as buildD3Formatter4,
|
|
6392
|
+
computeChrome,
|
|
6393
|
+
estimateTextWidth as estimateTextWidth7,
|
|
6394
|
+
formatNumber as formatNumber2,
|
|
6395
|
+
resolveTheme
|
|
6396
|
+
} from "@opendata-ai/openchart-core";
|
|
6397
|
+
|
|
6398
|
+
// src/compiler/animation.ts
|
|
6399
|
+
var ENTER_DEFAULTS = {
|
|
6400
|
+
duration: 500,
|
|
6401
|
+
ease: "smooth",
|
|
6402
|
+
staggerDelay: 80,
|
|
6403
|
+
staggerOrder: "index",
|
|
6404
|
+
annotationDelay: 200
|
|
6405
|
+
};
|
|
6406
|
+
var MAX_TOTAL_STAGGER_MS = 2e3;
|
|
6407
|
+
function resolveAnimation(spec) {
|
|
6408
|
+
if (spec === void 0 || spec === false) return void 0;
|
|
6409
|
+
if (spec === true) {
|
|
6410
|
+
return {
|
|
6411
|
+
enabled: true,
|
|
6412
|
+
duration: ENTER_DEFAULTS.duration,
|
|
6413
|
+
ease: ENTER_DEFAULTS.ease,
|
|
6414
|
+
staggerDelay: ENTER_DEFAULTS.staggerDelay,
|
|
6415
|
+
staggerOrder: ENTER_DEFAULTS.staggerOrder,
|
|
6416
|
+
annotationDelay: ENTER_DEFAULTS.annotationDelay
|
|
6417
|
+
};
|
|
6316
6418
|
}
|
|
6317
|
-
const
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
const rm = mark;
|
|
6321
|
-
obstacles.push({ x: rm.x, y: rm.y, width: rm.width, height: rm.height });
|
|
6322
|
-
} else if (mark.type === "point") {
|
|
6323
|
-
const pm = mark;
|
|
6324
|
-
obstacles.push({
|
|
6325
|
-
x: pm.cx - pm.r,
|
|
6326
|
-
y: pm.cy - pm.r,
|
|
6327
|
-
width: pm.r * 2,
|
|
6328
|
-
height: pm.r * 2
|
|
6329
|
-
});
|
|
6330
|
-
}
|
|
6419
|
+
const config = spec;
|
|
6420
|
+
if (config.enter === false || config.enter === void 0 && !hasAnyPhase(config)) {
|
|
6421
|
+
return void 0;
|
|
6331
6422
|
}
|
|
6332
|
-
|
|
6423
|
+
const enterConfig = resolvePhaseConfig(config.enter);
|
|
6424
|
+
return {
|
|
6425
|
+
enabled: true,
|
|
6426
|
+
duration: enterConfig.duration,
|
|
6427
|
+
ease: enterConfig.ease,
|
|
6428
|
+
staggerDelay: enterConfig.staggerDelay,
|
|
6429
|
+
staggerOrder: enterConfig.staggerOrder,
|
|
6430
|
+
annotationDelay: config.annotationDelay ?? ENTER_DEFAULTS.annotationDelay
|
|
6431
|
+
};
|
|
6333
6432
|
}
|
|
6334
|
-
function
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
let cy;
|
|
6338
|
-
let left2;
|
|
6339
|
-
let right2;
|
|
6340
|
-
if (mark.type === "point") {
|
|
6341
|
-
const pm = mark;
|
|
6342
|
-
cy = pm.cy;
|
|
6343
|
-
left2 = pm.cx - pm.r;
|
|
6344
|
-
right2 = pm.cx + pm.r;
|
|
6345
|
-
} else if (mark.type === "rect") {
|
|
6346
|
-
const rm = mark;
|
|
6347
|
-
cy = rm.y + rm.height / 2;
|
|
6348
|
-
left2 = rm.x;
|
|
6349
|
-
right2 = rm.x + rm.width;
|
|
6350
|
-
} else {
|
|
6351
|
-
continue;
|
|
6352
|
-
}
|
|
6353
|
-
const key = Math.round(cy);
|
|
6354
|
-
const existing = rows.get(key);
|
|
6355
|
-
if (existing) {
|
|
6356
|
-
existing.minX = Math.min(existing.minX, left2);
|
|
6357
|
-
existing.maxX = Math.max(existing.maxX, right2);
|
|
6358
|
-
} else {
|
|
6359
|
-
rows.set(key, { minX: left2, maxX: right2, bandY: cy });
|
|
6360
|
-
}
|
|
6361
|
-
}
|
|
6362
|
-
const bandScale = scales.y.scale;
|
|
6363
|
-
const bandwidth = bandScale.bandwidth?.() ?? 0;
|
|
6364
|
-
if (bandwidth === 0) return [];
|
|
6365
|
-
const obstacles = [];
|
|
6366
|
-
for (const { minX, maxX, bandY } of rows.values()) {
|
|
6367
|
-
obstacles.push({
|
|
6368
|
-
x: minX,
|
|
6369
|
-
y: bandY - bandwidth / 2,
|
|
6370
|
-
width: maxX - minX,
|
|
6371
|
-
height: bandwidth
|
|
6372
|
-
});
|
|
6373
|
-
}
|
|
6374
|
-
return obstacles;
|
|
6433
|
+
function clampStaggerDelay(delay, elementCount) {
|
|
6434
|
+
if (elementCount <= 1) return 0;
|
|
6435
|
+
return Math.min(delay, MAX_TOTAL_STAGGER_MS / elementCount);
|
|
6375
6436
|
}
|
|
6376
|
-
function
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
return "arc:donut";
|
|
6388
|
-
}
|
|
6437
|
+
function hasAnyPhase(config) {
|
|
6438
|
+
return config.enter !== void 0 || config.update !== void 0 || config.exit !== void 0;
|
|
6439
|
+
}
|
|
6440
|
+
function resolvePhaseConfig(phase) {
|
|
6441
|
+
if (phase === void 0 || phase === true) {
|
|
6442
|
+
return {
|
|
6443
|
+
duration: ENTER_DEFAULTS.duration,
|
|
6444
|
+
ease: ENTER_DEFAULTS.ease,
|
|
6445
|
+
staggerDelay: ENTER_DEFAULTS.staggerDelay,
|
|
6446
|
+
staggerOrder: ENTER_DEFAULTS.staggerOrder
|
|
6447
|
+
};
|
|
6389
6448
|
}
|
|
6390
|
-
|
|
6449
|
+
const cfg = phase;
|
|
6450
|
+
const stagger = resolveStagger(cfg.stagger);
|
|
6451
|
+
return {
|
|
6452
|
+
duration: cfg.duration ?? ENTER_DEFAULTS.duration,
|
|
6453
|
+
ease: cfg.ease ?? ENTER_DEFAULTS.ease,
|
|
6454
|
+
staggerDelay: stagger.delay,
|
|
6455
|
+
staggerOrder: stagger.order
|
|
6456
|
+
};
|
|
6391
6457
|
}
|
|
6392
|
-
function
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
// bar height is the primary value encoding
|
|
6397
|
-
case "point":
|
|
6398
|
-
return mark.cy;
|
|
6399
|
-
// y position for scatter
|
|
6400
|
-
case "arc":
|
|
6401
|
-
return mark.endAngle - mark.startAngle;
|
|
6402
|
-
// arc angle extent
|
|
6403
|
-
case "line":
|
|
6404
|
-
case "area":
|
|
6405
|
-
return 0;
|
|
6406
|
-
// series marks don't have individual values
|
|
6407
|
-
default:
|
|
6408
|
-
return 0;
|
|
6409
|
-
}
|
|
6410
|
-
}
|
|
6411
|
-
function assignAnimationIndices(marks, animation) {
|
|
6412
|
-
if (!animation?.enabled) return;
|
|
6413
|
-
if (animation.staggerOrder === "value") {
|
|
6414
|
-
const indexed = marks.map((m, i) => ({ mark: m, idx: i }));
|
|
6415
|
-
indexed.sort((a, b) => {
|
|
6416
|
-
const av = getMarkPrimaryValue(a.mark);
|
|
6417
|
-
const bv = getMarkPrimaryValue(b.mark);
|
|
6418
|
-
return av - bv;
|
|
6419
|
-
});
|
|
6420
|
-
for (let i = 0; i < indexed.length; i++) {
|
|
6421
|
-
const m = indexed[i].mark;
|
|
6422
|
-
if (m.type === "rect" && m.stackGroup) continue;
|
|
6423
|
-
m.animationIndex = i;
|
|
6424
|
-
}
|
|
6425
|
-
}
|
|
6426
|
-
const groupIndexMap = /* @__PURE__ */ new Map();
|
|
6427
|
-
const groupStackPos = /* @__PURE__ */ new Map();
|
|
6428
|
-
let nextGroupIndex = 0;
|
|
6429
|
-
for (const mark of marks) {
|
|
6430
|
-
if (mark.type === "rect" && mark.stackGroup) {
|
|
6431
|
-
const rect = mark;
|
|
6432
|
-
const group = rect.stackGroup;
|
|
6433
|
-
if (!groupIndexMap.has(group)) {
|
|
6434
|
-
groupIndexMap.set(group, nextGroupIndex++);
|
|
6435
|
-
}
|
|
6436
|
-
rect.animationIndex = groupIndexMap.get(group);
|
|
6437
|
-
const pos = groupStackPos.get(group) ?? 0;
|
|
6438
|
-
rect.stackPos = pos;
|
|
6439
|
-
groupStackPos.set(group, pos + 1);
|
|
6440
|
-
}
|
|
6458
|
+
function resolveStagger(stagger) {
|
|
6459
|
+
if (stagger === false) return { delay: 0, order: "index" };
|
|
6460
|
+
if (stagger === void 0 || stagger === true) {
|
|
6461
|
+
return { delay: ENTER_DEFAULTS.staggerDelay, order: ENTER_DEFAULTS.staggerOrder };
|
|
6441
6462
|
}
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
if (!scales.color) return;
|
|
6447
|
-
const hasExplicitRange = !!(encoding.color && "field" in encoding.color && encoding.color.scale?.range?.length);
|
|
6448
|
-
if (hasExplicitRange) return;
|
|
6449
|
-
if (scales.color.type === "sequential") {
|
|
6450
|
-
const seqStops = Object.values(theme.colors.sequential)[0] ?? theme.colors.categorical;
|
|
6451
|
-
scales.color.scale.range([
|
|
6452
|
-
seqStops[0],
|
|
6453
|
-
seqStops[seqStops.length - 1]
|
|
6454
|
-
]);
|
|
6455
|
-
} else {
|
|
6456
|
-
scales.color.scale.range(theme.colors.categorical);
|
|
6457
|
-
}
|
|
6458
|
-
}
|
|
6459
|
-
|
|
6460
|
-
// src/compile/data-clip.ts
|
|
6461
|
-
function filterClippedDomains(data, encoding) {
|
|
6462
|
-
let result = data;
|
|
6463
|
-
for (const channel of ["x", "y"]) {
|
|
6464
|
-
const enc = encoding[channel];
|
|
6465
|
-
if (!enc?.scale?.clip || !enc.scale.domain) continue;
|
|
6466
|
-
const domain = enc.scale.domain;
|
|
6467
|
-
const field = enc.field;
|
|
6468
|
-
if (Array.isArray(domain) && domain.length === 2 && typeof domain[0] === "number") {
|
|
6469
|
-
const [lo, hi] = domain;
|
|
6470
|
-
result = result.filter((row) => {
|
|
6471
|
-
const v = Number(row[field]);
|
|
6472
|
-
return Number.isFinite(v) && v >= lo && v <= hi;
|
|
6473
|
-
});
|
|
6474
|
-
}
|
|
6475
|
-
}
|
|
6476
|
-
return result;
|
|
6477
|
-
}
|
|
6478
|
-
|
|
6479
|
-
// src/compile/watermark-obstacle.ts
|
|
6480
|
-
import { BRAND_RESERVE_WIDTH } from "@opendata-ai/openchart-core";
|
|
6481
|
-
var WATERMARK_HEIGHT = 30;
|
|
6482
|
-
var X_AXIS_EXTENT_WITH_LABEL = 48;
|
|
6483
|
-
var X_AXIS_EXTENT_TICKS_ONLY = 26;
|
|
6484
|
-
function computeWatermarkObstacle(dims, watermark, axes, theme) {
|
|
6485
|
-
if (!watermark) return null;
|
|
6486
|
-
const chartArea = dims.chartArea;
|
|
6487
|
-
const brandPadding = theme.spacing.padding;
|
|
6488
|
-
const brandX = dims.total.width - brandPadding - BRAND_RESERVE_WIDTH;
|
|
6489
|
-
const xAxisExtent = axes.x?.label ? X_AXIS_EXTENT_WITH_LABEL : axes.x ? X_AXIS_EXTENT_TICKS_ONLY : 0;
|
|
6490
|
-
const firstBottomChrome = dims.chrome.source ?? dims.chrome.byline ?? dims.chrome.footer;
|
|
6491
|
-
const brandY = firstBottomChrome ? chartArea.y + chartArea.height + xAxisExtent + firstBottomChrome.y : chartArea.y + chartArea.height + xAxisExtent + theme.spacing.chartToFooter;
|
|
6492
|
-
return { x: brandX, y: brandY, width: BRAND_RESERVE_WIDTH, height: WATERMARK_HEIGHT };
|
|
6493
|
-
}
|
|
6494
|
-
|
|
6495
|
-
// src/compiler/animation.ts
|
|
6496
|
-
var ENTER_DEFAULTS = {
|
|
6497
|
-
duration: 500,
|
|
6498
|
-
ease: "smooth",
|
|
6499
|
-
staggerDelay: 80,
|
|
6500
|
-
staggerOrder: "index",
|
|
6501
|
-
annotationDelay: 200
|
|
6502
|
-
};
|
|
6503
|
-
var MAX_TOTAL_STAGGER_MS = 2e3;
|
|
6504
|
-
function resolveAnimation(spec) {
|
|
6505
|
-
if (spec === void 0 || spec === false) return void 0;
|
|
6506
|
-
if (spec === true) {
|
|
6507
|
-
return {
|
|
6508
|
-
enabled: true,
|
|
6509
|
-
duration: ENTER_DEFAULTS.duration,
|
|
6510
|
-
ease: ENTER_DEFAULTS.ease,
|
|
6511
|
-
staggerDelay: ENTER_DEFAULTS.staggerDelay,
|
|
6512
|
-
staggerOrder: ENTER_DEFAULTS.staggerOrder,
|
|
6513
|
-
annotationDelay: ENTER_DEFAULTS.annotationDelay
|
|
6514
|
-
};
|
|
6515
|
-
}
|
|
6516
|
-
const config = spec;
|
|
6517
|
-
if (config.enter === false || config.enter === void 0 && !hasAnyPhase(config)) {
|
|
6518
|
-
return void 0;
|
|
6519
|
-
}
|
|
6520
|
-
const enterConfig = resolvePhaseConfig(config.enter);
|
|
6521
|
-
return {
|
|
6522
|
-
enabled: true,
|
|
6523
|
-
duration: enterConfig.duration,
|
|
6524
|
-
ease: enterConfig.ease,
|
|
6525
|
-
staggerDelay: enterConfig.staggerDelay,
|
|
6526
|
-
staggerOrder: enterConfig.staggerOrder,
|
|
6527
|
-
annotationDelay: config.annotationDelay ?? ENTER_DEFAULTS.annotationDelay
|
|
6528
|
-
};
|
|
6529
|
-
}
|
|
6530
|
-
function clampStaggerDelay(delay, elementCount) {
|
|
6531
|
-
if (elementCount <= 1) return 0;
|
|
6532
|
-
return Math.min(delay, MAX_TOTAL_STAGGER_MS / elementCount);
|
|
6533
|
-
}
|
|
6534
|
-
function hasAnyPhase(config) {
|
|
6535
|
-
return config.enter !== void 0 || config.update !== void 0 || config.exit !== void 0;
|
|
6536
|
-
}
|
|
6537
|
-
function resolvePhaseConfig(phase) {
|
|
6538
|
-
if (phase === void 0 || phase === true) {
|
|
6539
|
-
return {
|
|
6540
|
-
duration: ENTER_DEFAULTS.duration,
|
|
6541
|
-
ease: ENTER_DEFAULTS.ease,
|
|
6542
|
-
staggerDelay: ENTER_DEFAULTS.staggerDelay,
|
|
6543
|
-
staggerOrder: ENTER_DEFAULTS.staggerOrder
|
|
6544
|
-
};
|
|
6545
|
-
}
|
|
6546
|
-
const cfg = phase;
|
|
6547
|
-
const stagger = resolveStagger(cfg.stagger);
|
|
6548
|
-
return {
|
|
6549
|
-
duration: cfg.duration ?? ENTER_DEFAULTS.duration,
|
|
6550
|
-
ease: cfg.ease ?? ENTER_DEFAULTS.ease,
|
|
6551
|
-
staggerDelay: stagger.delay,
|
|
6552
|
-
staggerOrder: stagger.order
|
|
6553
|
-
};
|
|
6554
|
-
}
|
|
6555
|
-
function resolveStagger(stagger) {
|
|
6556
|
-
if (stagger === false) return { delay: 0, order: "index" };
|
|
6557
|
-
if (stagger === void 0 || stagger === true) {
|
|
6558
|
-
return { delay: ENTER_DEFAULTS.staggerDelay, order: ENTER_DEFAULTS.staggerOrder };
|
|
6559
|
-
}
|
|
6560
|
-
return {
|
|
6561
|
-
delay: stagger.delay ?? ENTER_DEFAULTS.staggerDelay,
|
|
6562
|
-
order: stagger.order ?? ENTER_DEFAULTS.staggerOrder
|
|
6563
|
-
};
|
|
6463
|
+
return {
|
|
6464
|
+
delay: stagger.delay ?? ENTER_DEFAULTS.staggerDelay,
|
|
6465
|
+
order: stagger.order ?? ENTER_DEFAULTS.staggerOrder
|
|
6466
|
+
};
|
|
6564
6467
|
}
|
|
6565
6468
|
|
|
6566
6469
|
// src/compiler/normalize.ts
|
|
6567
6470
|
import {
|
|
6471
|
+
isBarListSpec,
|
|
6568
6472
|
isChartSpec,
|
|
6569
6473
|
isGraphSpec,
|
|
6570
6474
|
isLayerSpec,
|
|
@@ -6822,7 +6726,8 @@ function normalizeLabels(labels) {
|
|
|
6822
6726
|
density: labels.density ?? "auto",
|
|
6823
6727
|
format: labels.format ?? "",
|
|
6824
6728
|
prefix: labels.prefix ?? "",
|
|
6825
|
-
offsets: labels.offsets
|
|
6729
|
+
offsets: labels.offsets,
|
|
6730
|
+
color: labels.color
|
|
6826
6731
|
};
|
|
6827
6732
|
}
|
|
6828
6733
|
function normalizeChartSpec(spec, warnings) {
|
|
@@ -6968,6 +6873,22 @@ function normalizeTileMapSpec(spec, warnings) {
|
|
|
6968
6873
|
valueFormat: spec.valueFormat
|
|
6969
6874
|
};
|
|
6970
6875
|
}
|
|
6876
|
+
function normalizeBarListSpec(spec, _warnings) {
|
|
6877
|
+
return {
|
|
6878
|
+
type: "barlist",
|
|
6879
|
+
data: spec.data,
|
|
6880
|
+
encoding: spec.encoding,
|
|
6881
|
+
barHeight: spec.barHeight ?? 6,
|
|
6882
|
+
cornerRadius: spec.cornerRadius ?? "pill",
|
|
6883
|
+
maxItems: spec.maxItems ?? 20,
|
|
6884
|
+
chrome: normalizeChrome(spec.chrome),
|
|
6885
|
+
theme: spec.theme ?? {},
|
|
6886
|
+
darkMode: spec.darkMode ?? "off",
|
|
6887
|
+
watermark: spec.watermark ?? true,
|
|
6888
|
+
animation: spec.animation ?? true,
|
|
6889
|
+
valueFormat: spec.valueFormat
|
|
6890
|
+
};
|
|
6891
|
+
}
|
|
6971
6892
|
function normalizeSpec(spec, warnings = []) {
|
|
6972
6893
|
if (isLayerSpec(spec)) {
|
|
6973
6894
|
const leaves = flattenLayers(spec);
|
|
@@ -6991,8 +6912,11 @@ function normalizeSpec(spec, warnings = []) {
|
|
|
6991
6912
|
if (isTileMapSpec(spec)) {
|
|
6992
6913
|
return normalizeTileMapSpec(spec, warnings);
|
|
6993
6914
|
}
|
|
6915
|
+
if (isBarListSpec(spec)) {
|
|
6916
|
+
return normalizeBarListSpec(spec, warnings);
|
|
6917
|
+
}
|
|
6994
6918
|
throw new Error(
|
|
6995
|
-
`Unknown spec shape. Expected mark (chart), layer, type: 'table', type: 'graph', type: 'sankey', or type: '
|
|
6919
|
+
`Unknown spec shape. Expected mark (chart), layer, type: 'table', type: 'graph', type: 'sankey', type: 'tilemap', or type: 'barlist'.`
|
|
6996
6920
|
);
|
|
6997
6921
|
}
|
|
6998
6922
|
function flattenLayers(spec, parentData, parentEncoding, parentTransforms, parentWatermark) {
|
|
@@ -7632,6 +7556,98 @@ function validateTileMapSpec(spec, errors) {
|
|
|
7632
7556
|
});
|
|
7633
7557
|
}
|
|
7634
7558
|
}
|
|
7559
|
+
function validateBarListSpec(spec, errors) {
|
|
7560
|
+
if (!Array.isArray(spec.data)) {
|
|
7561
|
+
errors.push({
|
|
7562
|
+
message: 'Spec error: barlist spec requires a "data" array',
|
|
7563
|
+
path: "data",
|
|
7564
|
+
code: "INVALID_TYPE",
|
|
7565
|
+
suggestion: 'Provide data as an array of objects, e.g. data: [{ label: "Category A", value: 42 }]'
|
|
7566
|
+
});
|
|
7567
|
+
return;
|
|
7568
|
+
}
|
|
7569
|
+
if (spec.data.length === 0) {
|
|
7570
|
+
errors.push({
|
|
7571
|
+
message: 'Spec error: "data" array must be non-empty',
|
|
7572
|
+
path: "data",
|
|
7573
|
+
code: "EMPTY_DATA",
|
|
7574
|
+
suggestion: "Add at least one data row"
|
|
7575
|
+
});
|
|
7576
|
+
return;
|
|
7577
|
+
}
|
|
7578
|
+
const firstRow = spec.data[0];
|
|
7579
|
+
if (typeof firstRow !== "object" || firstRow === null || Array.isArray(firstRow)) {
|
|
7580
|
+
errors.push({
|
|
7581
|
+
message: 'Spec error: each item in "data" must be a plain object',
|
|
7582
|
+
path: "data[0]",
|
|
7583
|
+
code: "INVALID_TYPE",
|
|
7584
|
+
suggestion: 'Each data item should be an object, e.g. { label: "Category A", value: 42 }'
|
|
7585
|
+
});
|
|
7586
|
+
return;
|
|
7587
|
+
}
|
|
7588
|
+
if (!spec.encoding || typeof spec.encoding !== "object") {
|
|
7589
|
+
errors.push({
|
|
7590
|
+
message: 'Spec error: barlist spec requires an "encoding" object with label and value channels',
|
|
7591
|
+
path: "encoding",
|
|
7592
|
+
code: "MISSING_FIELD",
|
|
7593
|
+
suggestion: 'Add an encoding object, e.g. encoding: { label: { field: "name", type: "nominal" }, value: { field: "count", type: "quantitative" } }'
|
|
7594
|
+
});
|
|
7595
|
+
return;
|
|
7596
|
+
}
|
|
7597
|
+
const encoding = spec.encoding;
|
|
7598
|
+
const dataColumns = new Set(Object.keys(firstRow));
|
|
7599
|
+
const availableColumns = [...dataColumns].join(", ");
|
|
7600
|
+
for (const channel of ["label", "value"]) {
|
|
7601
|
+
const ch = encoding[channel];
|
|
7602
|
+
if (!ch || typeof ch !== "object") {
|
|
7603
|
+
errors.push({
|
|
7604
|
+
message: `Spec error: barlist encoding requires "${channel}" channel`,
|
|
7605
|
+
path: `encoding.${channel}`,
|
|
7606
|
+
code: "MISSING_FIELD",
|
|
7607
|
+
suggestion: `Add encoding.${channel} with a field from your data (${availableColumns}). Example: ${channel}: { field: "${[...dataColumns][0] ?? "myField"}", type: "${channel === "value" ? "quantitative" : "nominal"}" }`
|
|
7608
|
+
});
|
|
7609
|
+
continue;
|
|
7610
|
+
}
|
|
7611
|
+
if (!ch.field || typeof ch.field !== "string") {
|
|
7612
|
+
errors.push({
|
|
7613
|
+
message: `Spec error: encoding.${channel} must have a "field" string`,
|
|
7614
|
+
path: `encoding.${channel}.field`,
|
|
7615
|
+
code: "MISSING_FIELD",
|
|
7616
|
+
suggestion: `Add a field name from your data columns: ${availableColumns}`
|
|
7617
|
+
});
|
|
7618
|
+
continue;
|
|
7619
|
+
}
|
|
7620
|
+
if (!dataColumns.has(ch.field)) {
|
|
7621
|
+
errors.push({
|
|
7622
|
+
message: `Spec error: encoding.${channel}.field "${ch.field}" does not exist in data. Available columns: ${availableColumns}`,
|
|
7623
|
+
path: `encoding.${channel}.field`,
|
|
7624
|
+
code: "DATA_FIELD_MISSING",
|
|
7625
|
+
suggestion: `Use one of the available data columns: ${availableColumns}`
|
|
7626
|
+
});
|
|
7627
|
+
}
|
|
7628
|
+
}
|
|
7629
|
+
for (const channel of ["subtitle", "color", "tooltip"]) {
|
|
7630
|
+
const ch = encoding[channel];
|
|
7631
|
+
if (!ch) continue;
|
|
7632
|
+
const field = ch.field;
|
|
7633
|
+
if (field && typeof field === "string" && !dataColumns.has(field)) {
|
|
7634
|
+
errors.push({
|
|
7635
|
+
message: `Spec error: encoding.${channel}.field "${field}" does not exist in data. Available columns: ${availableColumns}`,
|
|
7636
|
+
path: `encoding.${channel}.field`,
|
|
7637
|
+
code: "DATA_FIELD_MISSING",
|
|
7638
|
+
suggestion: `Use one of the available data columns: ${availableColumns}`
|
|
7639
|
+
});
|
|
7640
|
+
}
|
|
7641
|
+
}
|
|
7642
|
+
if (spec.darkMode !== void 0 && !VALID_DARK_MODES.has(spec.darkMode)) {
|
|
7643
|
+
errors.push({
|
|
7644
|
+
message: 'Spec error: darkMode must be "auto", "force", or "off"',
|
|
7645
|
+
path: "darkMode",
|
|
7646
|
+
code: "INVALID_VALUE",
|
|
7647
|
+
suggestion: 'Use one of: "auto" (system preference), "force" (always dark), or "off" (always light)'
|
|
7648
|
+
});
|
|
7649
|
+
}
|
|
7650
|
+
}
|
|
7635
7651
|
function validateLayerSpec(spec, errors) {
|
|
7636
7652
|
const layer = spec.layer;
|
|
7637
7653
|
if (layer.length === 0) {
|
|
@@ -7704,105 +7720,550 @@ function validateLayerSpec(spec, errors) {
|
|
|
7704
7720
|
}
|
|
7705
7721
|
}
|
|
7706
7722
|
}
|
|
7707
|
-
}
|
|
7708
|
-
function validateSpec(spec) {
|
|
7709
|
-
const errors = [];
|
|
7710
|
-
if (!spec || typeof spec !== "object" || Array.isArray(spec)) {
|
|
7711
|
-
return {
|
|
7712
|
-
valid: false,
|
|
7713
|
-
errors: [
|
|
7714
|
-
{
|
|
7715
|
-
message: "Spec error: spec must be a non-null object",
|
|
7716
|
-
code: "INVALID_TYPE",
|
|
7717
|
-
suggestion: 'Pass a spec object with at least a "mark" field for charts, e.g. { mark: "line", data: [...], encoding: {...} }'
|
|
7718
|
-
}
|
|
7719
|
-
],
|
|
7720
|
-
normalized: null
|
|
7721
|
-
};
|
|
7723
|
+
}
|
|
7724
|
+
function validateSpec(spec) {
|
|
7725
|
+
const errors = [];
|
|
7726
|
+
if (!spec || typeof spec !== "object" || Array.isArray(spec)) {
|
|
7727
|
+
return {
|
|
7728
|
+
valid: false,
|
|
7729
|
+
errors: [
|
|
7730
|
+
{
|
|
7731
|
+
message: "Spec error: spec must be a non-null object",
|
|
7732
|
+
code: "INVALID_TYPE",
|
|
7733
|
+
suggestion: 'Pass a spec object with at least a "mark" field for charts, e.g. { mark: "line", data: [...], encoding: {...} }'
|
|
7734
|
+
}
|
|
7735
|
+
],
|
|
7736
|
+
normalized: null
|
|
7737
|
+
};
|
|
7738
|
+
}
|
|
7739
|
+
const obj = spec;
|
|
7740
|
+
const hasLayer = "layer" in obj && Array.isArray(obj.layer);
|
|
7741
|
+
const hasMark = "mark" in obj;
|
|
7742
|
+
const isTable = obj.type === "table";
|
|
7743
|
+
const isGraph = obj.type === "graph";
|
|
7744
|
+
const isSankey = obj.type === "sankey";
|
|
7745
|
+
const isTileMap = obj.type === "tilemap";
|
|
7746
|
+
const isBarList = obj.type === "barlist";
|
|
7747
|
+
const isLayer = hasLayer && !isTable && !isGraph && !isSankey && !isTileMap && !isBarList;
|
|
7748
|
+
const isChart = hasMark && !hasLayer && !isTable && !isGraph && !isSankey && !isTileMap && !isBarList;
|
|
7749
|
+
if (!isChart && !isTable && !isGraph && !isSankey && !isTileMap && !isBarList && !isLayer) {
|
|
7750
|
+
return {
|
|
7751
|
+
valid: false,
|
|
7752
|
+
errors: [
|
|
7753
|
+
{
|
|
7754
|
+
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/barlist',
|
|
7755
|
+
path: "mark",
|
|
7756
|
+
code: "MISSING_FIELD",
|
|
7757
|
+
suggestion: `Add a "mark" field for charts (e.g. mark: "bar"), a "layer" array for layered charts, or a "type" field (type: "table", type: "graph", type: "sankey", type: "tilemap", or type: "barlist"). Valid mark types: ${[...MARK_TYPES].join(", ")}`
|
|
7758
|
+
}
|
|
7759
|
+
],
|
|
7760
|
+
normalized: null
|
|
7761
|
+
};
|
|
7762
|
+
}
|
|
7763
|
+
if (isLayer) {
|
|
7764
|
+
validateLayerSpec(obj, errors);
|
|
7765
|
+
}
|
|
7766
|
+
if (isChart) {
|
|
7767
|
+
const mark = obj.mark;
|
|
7768
|
+
let markValue;
|
|
7769
|
+
if (typeof mark === "string") {
|
|
7770
|
+
markValue = mark;
|
|
7771
|
+
} else if (mark && typeof mark === "object" && !Array.isArray(mark)) {
|
|
7772
|
+
markValue = mark.type;
|
|
7773
|
+
}
|
|
7774
|
+
if (!markValue || !MARK_TYPES.has(markValue)) {
|
|
7775
|
+
return {
|
|
7776
|
+
valid: false,
|
|
7777
|
+
errors: [
|
|
7778
|
+
{
|
|
7779
|
+
message: `Spec error: "${markValue ?? String(mark)}" is not a valid mark type. Valid mark types: ${[...MARK_TYPES].join(", ")}`,
|
|
7780
|
+
path: "mark",
|
|
7781
|
+
code: "INVALID_VALUE",
|
|
7782
|
+
suggestion: `Change mark to one of: ${[...MARK_TYPES].join(", ")}`
|
|
7783
|
+
}
|
|
7784
|
+
],
|
|
7785
|
+
normalized: null
|
|
7786
|
+
};
|
|
7787
|
+
}
|
|
7788
|
+
validateChartSpec(obj, errors);
|
|
7789
|
+
} else if (isTable) {
|
|
7790
|
+
validateTableSpec(obj, errors);
|
|
7791
|
+
} else if (isGraph) {
|
|
7792
|
+
validateGraphSpec(obj, errors);
|
|
7793
|
+
} else if (isSankey) {
|
|
7794
|
+
validateSankeySpec(obj, errors);
|
|
7795
|
+
} else if (isTileMap) {
|
|
7796
|
+
validateTileMapSpec(obj, errors);
|
|
7797
|
+
} else if (isBarList) {
|
|
7798
|
+
validateBarListSpec(obj, errors);
|
|
7799
|
+
}
|
|
7800
|
+
if (errors.length > 0) {
|
|
7801
|
+
return { valid: false, errors, normalized: null };
|
|
7802
|
+
}
|
|
7803
|
+
return {
|
|
7804
|
+
valid: true,
|
|
7805
|
+
errors: [],
|
|
7806
|
+
normalized: spec
|
|
7807
|
+
};
|
|
7808
|
+
}
|
|
7809
|
+
|
|
7810
|
+
// src/compiler/index.ts
|
|
7811
|
+
function compile(spec) {
|
|
7812
|
+
const validation = validateSpec(spec);
|
|
7813
|
+
if (!validation.valid || !validation.normalized) {
|
|
7814
|
+
const errorMessages = validation.errors.map((e) => e.message).join("\n");
|
|
7815
|
+
throw new Error(`Invalid spec:
|
|
7816
|
+
${errorMessages}`);
|
|
7817
|
+
}
|
|
7818
|
+
const warnings = [];
|
|
7819
|
+
const normalized = normalizeSpec(validation.normalized, warnings);
|
|
7820
|
+
return { spec: normalized, warnings };
|
|
7821
|
+
}
|
|
7822
|
+
|
|
7823
|
+
// src/barlist/compile-barlist.ts
|
|
7824
|
+
var DEFAULT_ROW_GAP = 8;
|
|
7825
|
+
var LABEL_BAR_GAP = 12;
|
|
7826
|
+
var BAR_VALUE_GAP = 12;
|
|
7827
|
+
var VALUE_WIDTH = 56;
|
|
7828
|
+
var LABEL_FONT_SIZE6 = 13;
|
|
7829
|
+
var LABEL_FONT_WEIGHT6 = 500;
|
|
7830
|
+
var SUBTITLE_FONT_SIZE = 12;
|
|
7831
|
+
var SUBTITLE_FONT_WEIGHT = 400;
|
|
7832
|
+
var VALUE_FONT_SIZE = 12;
|
|
7833
|
+
var VALUE_FONT_WEIGHT = 400;
|
|
7834
|
+
var BARLIST_COLORS = ["#06b6d4", "#34d399", "#fbbf24", "#f472b6", "#a78bfa"];
|
|
7835
|
+
function compileBarList(spec, options) {
|
|
7836
|
+
const { spec: normalized } = compile(spec);
|
|
7837
|
+
if (!("type" in normalized) || normalized.type !== "barlist") {
|
|
7838
|
+
throw new Error(
|
|
7839
|
+
"compileBarList received a non-barlist spec. Use compileChart, compileTable, compileGraph, compileSankey, or compileTileMap instead."
|
|
7840
|
+
);
|
|
7841
|
+
}
|
|
7842
|
+
const barlistSpec = normalized;
|
|
7843
|
+
const rawWatermark = spec.watermark;
|
|
7844
|
+
const watermark = rawWatermark !== void 0 ? barlistSpec.watermark : options.watermark ?? true;
|
|
7845
|
+
const mergedThemeConfig = options.theme ? { ...barlistSpec.theme, ...options.theme } : barlistSpec.theme;
|
|
7846
|
+
const lightTheme = resolveTheme(mergedThemeConfig);
|
|
7847
|
+
let theme = lightTheme;
|
|
7848
|
+
if (options.darkMode) {
|
|
7849
|
+
theme = adaptTheme(theme);
|
|
7850
|
+
}
|
|
7851
|
+
const chrome = computeChrome(
|
|
7852
|
+
{
|
|
7853
|
+
title: barlistSpec.chrome.title,
|
|
7854
|
+
subtitle: barlistSpec.chrome.subtitle,
|
|
7855
|
+
source: barlistSpec.chrome.source,
|
|
7856
|
+
byline: barlistSpec.chrome.byline,
|
|
7857
|
+
footer: barlistSpec.chrome.footer
|
|
7858
|
+
},
|
|
7859
|
+
theme,
|
|
7860
|
+
options.width,
|
|
7861
|
+
options.measureText,
|
|
7862
|
+
"full",
|
|
7863
|
+
void 0,
|
|
7864
|
+
watermark
|
|
7865
|
+
);
|
|
7866
|
+
const padding = theme.spacing.padding;
|
|
7867
|
+
const fullArea = {
|
|
7868
|
+
x: padding,
|
|
7869
|
+
y: padding + chrome.topHeight,
|
|
7870
|
+
width: options.width - padding * 2,
|
|
7871
|
+
height: options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
|
|
7872
|
+
};
|
|
7873
|
+
if (fullArea.width <= 0 || fullArea.height <= 0) {
|
|
7874
|
+
return emptyLayout(chrome, theme, options, watermark);
|
|
7875
|
+
}
|
|
7876
|
+
const labelField = barlistSpec.encoding.label.field;
|
|
7877
|
+
const valueField = barlistSpec.encoding.value.field;
|
|
7878
|
+
const subtitleField = barlistSpec.encoding.subtitle?.field;
|
|
7879
|
+
const colorField = barlistSpec.encoding.color?.field;
|
|
7880
|
+
const barHeight = barlistSpec.barHeight;
|
|
7881
|
+
const cornerRadius = barlistSpec.cornerRadius === "pill" ? barHeight / 2 : barlistSpec.cornerRadius;
|
|
7882
|
+
const rowContentHeight = Math.max(barHeight, LABEL_FONT_SIZE6 * 1.4);
|
|
7883
|
+
const rowHeight = rowContentHeight + DEFAULT_ROW_GAP;
|
|
7884
|
+
const maxFittingRows = Math.max(1, Math.floor(fullArea.height / rowHeight));
|
|
7885
|
+
const validRows = barlistSpec.data.filter((row) => {
|
|
7886
|
+
const val = row[valueField];
|
|
7887
|
+
return val !== null && val !== void 0 && !Number.isNaN(Number(val));
|
|
7888
|
+
}).sort((a, b) => Number(b[valueField]) - Number(a[valueField])).slice(0, Math.min(barlistSpec.maxItems, maxFittingRows));
|
|
7889
|
+
if (validRows.length === 0) {
|
|
7890
|
+
return emptyLayout(chrome, theme, options, watermark);
|
|
7891
|
+
}
|
|
7892
|
+
const maxValue = Math.max(...validRows.map((r) => Math.abs(Number(r[valueField]))));
|
|
7893
|
+
const colorMap = /* @__PURE__ */ new Map();
|
|
7894
|
+
let colorIndex = 0;
|
|
7895
|
+
const palette = BARLIST_COLORS;
|
|
7896
|
+
function getColor2(row, idx) {
|
|
7897
|
+
if (colorField) {
|
|
7898
|
+
const key = String(row[colorField] ?? "");
|
|
7899
|
+
if (!colorMap.has(key)) {
|
|
7900
|
+
colorMap.set(key, palette[colorIndex % palette.length]);
|
|
7901
|
+
colorIndex++;
|
|
7902
|
+
}
|
|
7903
|
+
return colorMap.get(key);
|
|
7904
|
+
}
|
|
7905
|
+
return palette[idx % palette.length];
|
|
7906
|
+
}
|
|
7907
|
+
const formatter = buildD3Formatter4(barlistSpec.valueFormat) ?? formatNumber2;
|
|
7908
|
+
const measureText = options.measureText ?? ((text, fontSize) => ({
|
|
7909
|
+
width: estimateTextWidth7(text, fontSize),
|
|
7910
|
+
height: fontSize
|
|
7911
|
+
}));
|
|
7912
|
+
const perRowLabelWidths = /* @__PURE__ */ new Map();
|
|
7913
|
+
let maxCombinedWidth = 0;
|
|
7914
|
+
for (let i = 0; i < validRows.length; i++) {
|
|
7915
|
+
const row = validRows[i];
|
|
7916
|
+
const label = String(row[labelField] ?? "");
|
|
7917
|
+
const labelW = measureText(label, LABEL_FONT_SIZE6, LABEL_FONT_WEIGHT6).width;
|
|
7918
|
+
perRowLabelWidths.set(i, labelW);
|
|
7919
|
+
let combined = labelW + 4;
|
|
7920
|
+
if (subtitleField && row[subtitleField] != null) {
|
|
7921
|
+
const subtitle = String(row[subtitleField]);
|
|
7922
|
+
combined = labelW + 6 + measureText(subtitle, SUBTITLE_FONT_SIZE, SUBTITLE_FONT_WEIGHT).width + 4;
|
|
7923
|
+
}
|
|
7924
|
+
maxCombinedWidth = Math.max(maxCombinedWidth, combined);
|
|
7925
|
+
}
|
|
7926
|
+
const isNarrow = fullArea.width < 400;
|
|
7927
|
+
const labelBarGap = isNarrow ? 8 : LABEL_BAR_GAP;
|
|
7928
|
+
const barValueGap = isNarrow ? 6 : BAR_VALUE_GAP;
|
|
7929
|
+
const valueWidth = isNarrow ? 44 : VALUE_WIDTH;
|
|
7930
|
+
const maxLabelPct = isNarrow ? 0.35 : 0.4;
|
|
7931
|
+
const labelWidth = Math.max(50, Math.min(maxCombinedWidth, fullArea.width * maxLabelPct));
|
|
7932
|
+
const barAreaWidth = fullArea.width - labelWidth - labelBarGap - barValueGap - valueWidth;
|
|
7933
|
+
const labelColor = theme.colors.text;
|
|
7934
|
+
const subtitleColor = options.darkMode ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.45)";
|
|
7935
|
+
const valueColor = options.darkMode ? "rgba(255,255,255,0.6)" : "rgba(0,0,0,0.55)";
|
|
7936
|
+
const rows = [];
|
|
7937
|
+
for (let i = 0; i < validRows.length; i++) {
|
|
7938
|
+
const row = validRows[i];
|
|
7939
|
+
const value2 = Number(row[valueField]);
|
|
7940
|
+
const labelText = String(row[labelField] ?? "");
|
|
7941
|
+
const formattedValue = formatter(value2);
|
|
7942
|
+
const barColor = getColor2(row, i);
|
|
7943
|
+
const pct = maxValue > 0 ? Math.abs(value2) / maxValue : 0;
|
|
7944
|
+
const rowY = fullArea.y + i * rowHeight;
|
|
7945
|
+
const centerY = rowY + rowContentHeight / 2;
|
|
7946
|
+
const labelX = fullArea.x;
|
|
7947
|
+
const labelStyle = {
|
|
7948
|
+
fontFamily: theme.fonts.family,
|
|
7949
|
+
fontSize: LABEL_FONT_SIZE6,
|
|
7950
|
+
fontWeight: LABEL_FONT_WEIGHT6,
|
|
7951
|
+
fill: labelColor,
|
|
7952
|
+
lineHeight: 1.4
|
|
7953
|
+
};
|
|
7954
|
+
let subtitle;
|
|
7955
|
+
if (subtitleField && row[subtitleField] != null) {
|
|
7956
|
+
const subtitleText = String(row[subtitleField]);
|
|
7957
|
+
const subtitleX = labelX + (perRowLabelWidths.get(i) ?? 0) + 6;
|
|
7958
|
+
subtitle = {
|
|
7959
|
+
text: subtitleText,
|
|
7960
|
+
x: subtitleX,
|
|
7961
|
+
y: centerY,
|
|
7962
|
+
style: {
|
|
7963
|
+
fontFamily: theme.fonts.family,
|
|
7964
|
+
fontSize: SUBTITLE_FONT_SIZE,
|
|
7965
|
+
fontWeight: SUBTITLE_FONT_WEIGHT,
|
|
7966
|
+
fill: subtitleColor,
|
|
7967
|
+
lineHeight: 1.4
|
|
7968
|
+
},
|
|
7969
|
+
visible: true
|
|
7970
|
+
};
|
|
7971
|
+
}
|
|
7972
|
+
const trackX = fullArea.x + labelWidth + labelBarGap;
|
|
7973
|
+
const trackY = centerY - barHeight / 2;
|
|
7974
|
+
const trackWidth = Math.max(barAreaWidth, 0);
|
|
7975
|
+
const barWidth = Math.max(pct * trackWidth, 0);
|
|
7976
|
+
const valueLabelX = trackX + trackWidth + barValueGap + valueWidth;
|
|
7977
|
+
const valueLabelStyle = {
|
|
7978
|
+
fontFamily: `${theme.fonts.family}, ui-monospace, monospace`,
|
|
7979
|
+
fontSize: VALUE_FONT_SIZE,
|
|
7980
|
+
fontWeight: VALUE_FONT_WEIGHT,
|
|
7981
|
+
fill: valueColor,
|
|
7982
|
+
lineHeight: 1.4
|
|
7983
|
+
};
|
|
7984
|
+
const rowMark = {
|
|
7985
|
+
type: "barlist-row",
|
|
7986
|
+
index: i,
|
|
7987
|
+
y: rowY,
|
|
7988
|
+
height: rowHeight,
|
|
7989
|
+
label: {
|
|
7990
|
+
text: labelText,
|
|
7991
|
+
x: labelX,
|
|
7992
|
+
y: centerY,
|
|
7993
|
+
style: labelStyle,
|
|
7994
|
+
visible: true
|
|
7995
|
+
},
|
|
7996
|
+
subtitle,
|
|
7997
|
+
track: {
|
|
7998
|
+
x: trackX,
|
|
7999
|
+
y: trackY,
|
|
8000
|
+
width: trackWidth,
|
|
8001
|
+
height: barHeight,
|
|
8002
|
+
cornerRadius
|
|
8003
|
+
},
|
|
8004
|
+
bar: {
|
|
8005
|
+
x: trackX,
|
|
8006
|
+
y: trackY,
|
|
8007
|
+
width: barWidth,
|
|
8008
|
+
height: barHeight,
|
|
8009
|
+
cornerRadius,
|
|
8010
|
+
fill: barColor
|
|
8011
|
+
},
|
|
8012
|
+
valueLabel: {
|
|
8013
|
+
text: formattedValue,
|
|
8014
|
+
x: valueLabelX,
|
|
8015
|
+
y: centerY,
|
|
8016
|
+
style: valueLabelStyle,
|
|
8017
|
+
visible: true
|
|
8018
|
+
},
|
|
8019
|
+
value: value2,
|
|
8020
|
+
formattedValue,
|
|
8021
|
+
aria: {
|
|
8022
|
+
role: "listitem",
|
|
8023
|
+
label: `${labelText}: ${formattedValue}`
|
|
8024
|
+
},
|
|
8025
|
+
animationIndex: i,
|
|
8026
|
+
data: row
|
|
8027
|
+
};
|
|
8028
|
+
rows.push(rowMark);
|
|
8029
|
+
}
|
|
8030
|
+
const tooltipDescriptors = /* @__PURE__ */ new Map();
|
|
8031
|
+
for (const row of rows) {
|
|
8032
|
+
const fields = [
|
|
8033
|
+
{ label: barlistSpec.encoding.value.title ?? valueField, value: row.formattedValue }
|
|
8034
|
+
];
|
|
8035
|
+
tooltipDescriptors.set(String(row.index), {
|
|
8036
|
+
title: row.label.text,
|
|
8037
|
+
fields
|
|
8038
|
+
});
|
|
8039
|
+
}
|
|
8040
|
+
const a11y = {
|
|
8041
|
+
altText: `Bar list showing ${rows.length} items ranked by ${valueField}`,
|
|
8042
|
+
dataTableFallback: rows.map((r) => [r.label.text, r.formattedValue]),
|
|
8043
|
+
role: "list",
|
|
8044
|
+
keyboardNavigable: rows.length > 0
|
|
8045
|
+
};
|
|
8046
|
+
const resolvedAnimation = resolveAnimation(barlistSpec.animation);
|
|
8047
|
+
return {
|
|
8048
|
+
area: fullArea,
|
|
8049
|
+
chrome,
|
|
8050
|
+
rows,
|
|
8051
|
+
tooltipDescriptors,
|
|
8052
|
+
a11y,
|
|
8053
|
+
theme,
|
|
8054
|
+
width: options.width,
|
|
8055
|
+
height: options.height,
|
|
8056
|
+
animation: resolvedAnimation,
|
|
8057
|
+
watermark,
|
|
8058
|
+
measureText
|
|
8059
|
+
};
|
|
8060
|
+
}
|
|
8061
|
+
function emptyLayout(chrome, theme, options, watermark) {
|
|
8062
|
+
return {
|
|
8063
|
+
area: { x: 0, y: 0, width: 0, height: 0 },
|
|
8064
|
+
chrome,
|
|
8065
|
+
rows: [],
|
|
8066
|
+
tooltipDescriptors: /* @__PURE__ */ new Map(),
|
|
8067
|
+
a11y: {
|
|
8068
|
+
altText: "Empty bar list",
|
|
8069
|
+
dataTableFallback: [],
|
|
8070
|
+
role: "list",
|
|
8071
|
+
keyboardNavigable: false
|
|
8072
|
+
},
|
|
8073
|
+
theme,
|
|
8074
|
+
width: options.width,
|
|
8075
|
+
height: options.height,
|
|
8076
|
+
watermark,
|
|
8077
|
+
animation: void 0,
|
|
8078
|
+
measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth7(text, fontSize), height: fontSize }))
|
|
8079
|
+
};
|
|
8080
|
+
}
|
|
8081
|
+
|
|
8082
|
+
// src/charts/post-process.ts
|
|
8083
|
+
function computeMarkObstacles(marks, scales) {
|
|
8084
|
+
if (scales.y?.type === "band") {
|
|
8085
|
+
return computeBandRowObstacles(marks, scales);
|
|
8086
|
+
}
|
|
8087
|
+
const obstacles = [];
|
|
8088
|
+
for (const mark of marks) {
|
|
8089
|
+
if (mark.type === "rect") {
|
|
8090
|
+
const rm = mark;
|
|
8091
|
+
obstacles.push({ x: rm.x, y: rm.y, width: rm.width, height: rm.height });
|
|
8092
|
+
} else if (mark.type === "point") {
|
|
8093
|
+
const pm = mark;
|
|
8094
|
+
obstacles.push({
|
|
8095
|
+
x: pm.cx - pm.r,
|
|
8096
|
+
y: pm.cy - pm.r,
|
|
8097
|
+
width: pm.r * 2,
|
|
8098
|
+
height: pm.r * 2
|
|
8099
|
+
});
|
|
8100
|
+
}
|
|
8101
|
+
}
|
|
8102
|
+
return obstacles;
|
|
8103
|
+
}
|
|
8104
|
+
function computeBandRowObstacles(marks, scales) {
|
|
8105
|
+
const rows = /* @__PURE__ */ new Map();
|
|
8106
|
+
for (const mark of marks) {
|
|
8107
|
+
let cy;
|
|
8108
|
+
let left2;
|
|
8109
|
+
let right2;
|
|
8110
|
+
if (mark.type === "point") {
|
|
8111
|
+
const pm = mark;
|
|
8112
|
+
cy = pm.cy;
|
|
8113
|
+
left2 = pm.cx - pm.r;
|
|
8114
|
+
right2 = pm.cx + pm.r;
|
|
8115
|
+
} else if (mark.type === "rect") {
|
|
8116
|
+
const rm = mark;
|
|
8117
|
+
cy = rm.y + rm.height / 2;
|
|
8118
|
+
left2 = rm.x;
|
|
8119
|
+
right2 = rm.x + rm.width;
|
|
8120
|
+
} else {
|
|
8121
|
+
continue;
|
|
8122
|
+
}
|
|
8123
|
+
const key = Math.round(cy);
|
|
8124
|
+
const existing = rows.get(key);
|
|
8125
|
+
if (existing) {
|
|
8126
|
+
existing.minX = Math.min(existing.minX, left2);
|
|
8127
|
+
existing.maxX = Math.max(existing.maxX, right2);
|
|
8128
|
+
} else {
|
|
8129
|
+
rows.set(key, { minX: left2, maxX: right2, bandY: cy });
|
|
8130
|
+
}
|
|
8131
|
+
}
|
|
8132
|
+
const bandScale = scales.y.scale;
|
|
8133
|
+
const bandwidth = bandScale.bandwidth?.() ?? 0;
|
|
8134
|
+
if (bandwidth === 0) return [];
|
|
8135
|
+
const obstacles = [];
|
|
8136
|
+
for (const { minX, maxX, bandY } of rows.values()) {
|
|
8137
|
+
obstacles.push({
|
|
8138
|
+
x: minX,
|
|
8139
|
+
y: bandY - bandwidth / 2,
|
|
8140
|
+
width: maxX - minX,
|
|
8141
|
+
height: bandwidth
|
|
8142
|
+
});
|
|
7722
8143
|
}
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
path: "mark",
|
|
7739
|
-
code: "MISSING_FIELD",
|
|
7740
|
-
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(", ")}`
|
|
7741
|
-
}
|
|
7742
|
-
],
|
|
7743
|
-
normalized: null
|
|
7744
|
-
};
|
|
8144
|
+
return obstacles;
|
|
8145
|
+
}
|
|
8146
|
+
function resolveRendererKey(markType, encoding, markDef) {
|
|
8147
|
+
if (markType === "bar") {
|
|
8148
|
+
const xType = encoding.x?.type;
|
|
8149
|
+
const yType = encoding.y?.type;
|
|
8150
|
+
const isVertical = (xType === "nominal" || xType === "ordinal" || xType === "temporal") && yType === "quantitative";
|
|
8151
|
+
if (isVertical) {
|
|
8152
|
+
return "bar:vertical";
|
|
8153
|
+
}
|
|
8154
|
+
} else if (markType === "arc") {
|
|
8155
|
+
const innerRadius = markDef.innerRadius;
|
|
8156
|
+
if (innerRadius && innerRadius > 0) {
|
|
8157
|
+
return "arc:donut";
|
|
8158
|
+
}
|
|
7745
8159
|
}
|
|
7746
|
-
|
|
7747
|
-
|
|
8160
|
+
return markType;
|
|
8161
|
+
}
|
|
8162
|
+
function getMarkPrimaryValue(mark) {
|
|
8163
|
+
switch (mark.type) {
|
|
8164
|
+
case "rect":
|
|
8165
|
+
return mark.height;
|
|
8166
|
+
// bar height is the primary value encoding
|
|
8167
|
+
case "point":
|
|
8168
|
+
return mark.cy;
|
|
8169
|
+
// y position for scatter
|
|
8170
|
+
case "arc":
|
|
8171
|
+
return mark.endAngle - mark.startAngle;
|
|
8172
|
+
// arc angle extent
|
|
8173
|
+
case "line":
|
|
8174
|
+
case "area":
|
|
8175
|
+
return 0;
|
|
8176
|
+
// series marks don't have individual values
|
|
8177
|
+
default:
|
|
8178
|
+
return 0;
|
|
7748
8179
|
}
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
8180
|
+
}
|
|
8181
|
+
function assignAnimationIndices(marks, animation) {
|
|
8182
|
+
if (!animation?.enabled) return;
|
|
8183
|
+
if (animation.staggerOrder === "value") {
|
|
8184
|
+
const indexed = marks.map((m, i) => ({ mark: m, idx: i }));
|
|
8185
|
+
indexed.sort((a, b) => {
|
|
8186
|
+
const av = getMarkPrimaryValue(a.mark);
|
|
8187
|
+
const bv = getMarkPrimaryValue(b.mark);
|
|
8188
|
+
return av - bv;
|
|
8189
|
+
});
|
|
8190
|
+
for (let i = 0; i < indexed.length; i++) {
|
|
8191
|
+
const m = indexed[i].mark;
|
|
8192
|
+
if (m.type === "rect" && m.stackGroup) continue;
|
|
8193
|
+
m.animationIndex = i;
|
|
7756
8194
|
}
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
8195
|
+
}
|
|
8196
|
+
const groupIndexMap = /* @__PURE__ */ new Map();
|
|
8197
|
+
const groupStackPos = /* @__PURE__ */ new Map();
|
|
8198
|
+
let nextGroupIndex = 0;
|
|
8199
|
+
for (const mark of marks) {
|
|
8200
|
+
if (mark.type === "rect" && mark.stackGroup) {
|
|
8201
|
+
const rect = mark;
|
|
8202
|
+
const group = rect.stackGroup;
|
|
8203
|
+
if (!groupIndexMap.has(group)) {
|
|
8204
|
+
groupIndexMap.set(group, nextGroupIndex++);
|
|
8205
|
+
}
|
|
8206
|
+
rect.animationIndex = groupIndexMap.get(group);
|
|
8207
|
+
const pos = groupStackPos.get(group) ?? 0;
|
|
8208
|
+
rect.stackPos = pos;
|
|
8209
|
+
groupStackPos.set(group, pos + 1);
|
|
7770
8210
|
}
|
|
7771
|
-
validateChartSpec(obj, errors);
|
|
7772
|
-
} else if (isTable) {
|
|
7773
|
-
validateTableSpec(obj, errors);
|
|
7774
|
-
} else if (isGraph) {
|
|
7775
|
-
validateGraphSpec(obj, errors);
|
|
7776
|
-
} else if (isSankey) {
|
|
7777
|
-
validateSankeySpec(obj, errors);
|
|
7778
|
-
} else if (isTileMap) {
|
|
7779
|
-
validateTileMapSpec(obj, errors);
|
|
7780
8211
|
}
|
|
7781
|
-
|
|
7782
|
-
|
|
8212
|
+
}
|
|
8213
|
+
|
|
8214
|
+
// src/compile/color-scale-range.ts
|
|
8215
|
+
function applyColorScaleRange(scales, encoding, theme) {
|
|
8216
|
+
if (!scales.color) return;
|
|
8217
|
+
const hasExplicitRange = !!(encoding.color && "field" in encoding.color && encoding.color.scale?.range?.length);
|
|
8218
|
+
if (hasExplicitRange) return;
|
|
8219
|
+
if (scales.color.type === "sequential") {
|
|
8220
|
+
const seqStops = Object.values(theme.colors.sequential)[0] ?? theme.colors.categorical;
|
|
8221
|
+
scales.color.scale.range([
|
|
8222
|
+
seqStops[0],
|
|
8223
|
+
seqStops[seqStops.length - 1]
|
|
8224
|
+
]);
|
|
8225
|
+
} else {
|
|
8226
|
+
scales.color.scale.range(theme.colors.categorical);
|
|
7783
8227
|
}
|
|
7784
|
-
return {
|
|
7785
|
-
valid: true,
|
|
7786
|
-
errors: [],
|
|
7787
|
-
normalized: spec
|
|
7788
|
-
};
|
|
7789
8228
|
}
|
|
7790
8229
|
|
|
7791
|
-
// src/
|
|
7792
|
-
function
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
const
|
|
7796
|
-
|
|
7797
|
-
|
|
8230
|
+
// src/compile/data-clip.ts
|
|
8231
|
+
function filterClippedDomains(data, encoding) {
|
|
8232
|
+
let result = data;
|
|
8233
|
+
for (const channel of ["x", "y"]) {
|
|
8234
|
+
const enc = encoding[channel];
|
|
8235
|
+
if (!enc?.scale?.clip || !enc.scale.domain) continue;
|
|
8236
|
+
const domain = enc.scale.domain;
|
|
8237
|
+
const field = enc.field;
|
|
8238
|
+
if (Array.isArray(domain) && domain.length === 2 && typeof domain[0] === "number") {
|
|
8239
|
+
const [lo, hi] = domain;
|
|
8240
|
+
result = result.filter((row) => {
|
|
8241
|
+
const v = Number(row[field]);
|
|
8242
|
+
return Number.isFinite(v) && v >= lo && v <= hi;
|
|
8243
|
+
});
|
|
8244
|
+
}
|
|
7798
8245
|
}
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
8246
|
+
return result;
|
|
8247
|
+
}
|
|
8248
|
+
|
|
8249
|
+
// src/compile/watermark-obstacle.ts
|
|
8250
|
+
import { BRAND_RESERVE_WIDTH } from "@opendata-ai/openchart-core";
|
|
8251
|
+
var WATERMARK_HEIGHT = 30;
|
|
8252
|
+
var X_AXIS_EXTENT_WITH_LABEL = 48;
|
|
8253
|
+
var X_AXIS_EXTENT_TICKS_ONLY = 26;
|
|
8254
|
+
function computeWatermarkObstacle(dims, watermark, axes, theme) {
|
|
8255
|
+
if (!watermark) return null;
|
|
8256
|
+
const chartArea = dims.chartArea;
|
|
8257
|
+
const brandPadding = theme.spacing.padding;
|
|
8258
|
+
const brandX = dims.total.width - brandPadding - BRAND_RESERVE_WIDTH;
|
|
8259
|
+
const xAxisExtent = axes.x?.label ? X_AXIS_EXTENT_WITH_LABEL : axes.x ? X_AXIS_EXTENT_TICKS_ONLY : 0;
|
|
8260
|
+
const firstBottomChrome = dims.chrome.source ?? dims.chrome.byline ?? dims.chrome.footer;
|
|
8261
|
+
const brandY = firstBottomChrome ? chartArea.y + chartArea.height + xAxisExtent + firstBottomChrome.y : chartArea.y + chartArea.height + xAxisExtent + theme.spacing.chartToFooter;
|
|
8262
|
+
return { x: brandX, y: brandY, width: BRAND_RESERVE_WIDTH, height: WATERMARK_HEIGHT };
|
|
7802
8263
|
}
|
|
7803
8264
|
|
|
7804
8265
|
// src/graphs/compile-graph.ts
|
|
7805
|
-
import { adaptTheme, computeChrome, resolveTheme } from "@opendata-ai/openchart-core";
|
|
8266
|
+
import { adaptTheme as adaptTheme2, computeChrome as computeChrome2, resolveTheme as resolveTheme2 } from "@opendata-ai/openchart-core";
|
|
7806
8267
|
|
|
7807
8268
|
// src/graphs/encoding.ts
|
|
7808
8269
|
var DEFAULT_NODE_RADIUS = 5;
|
|
@@ -8106,9 +8567,9 @@ function compileGraph(spec, options) {
|
|
|
8106
8567
|
const rawWatermark = spec.watermark;
|
|
8107
8568
|
const watermark = rawWatermark !== void 0 ? graphSpec.watermark : options.watermark ?? true;
|
|
8108
8569
|
const mergedThemeConfig = options.theme ? { ...graphSpec.theme, ...options.theme } : graphSpec.theme;
|
|
8109
|
-
let theme =
|
|
8570
|
+
let theme = resolveTheme2(mergedThemeConfig);
|
|
8110
8571
|
if (options.darkMode) {
|
|
8111
|
-
theme =
|
|
8572
|
+
theme = adaptTheme2(theme);
|
|
8112
8573
|
}
|
|
8113
8574
|
const compiledNodes = resolveNodeVisuals(
|
|
8114
8575
|
graphSpec.nodes,
|
|
@@ -8162,7 +8623,7 @@ function compileGraph(spec, options) {
|
|
|
8162
8623
|
linkStrength: graphSpec.layout.linkStrength,
|
|
8163
8624
|
centerForce: graphSpec.layout.centerForce
|
|
8164
8625
|
};
|
|
8165
|
-
const chrome =
|
|
8626
|
+
const chrome = computeChrome2(
|
|
8166
8627
|
{
|
|
8167
8628
|
title: graphSpec.chrome.title,
|
|
8168
8629
|
subtitle: graphSpec.chrome.subtitle,
|
|
@@ -8196,11 +8657,11 @@ function compileGraph(spec, options) {
|
|
|
8196
8657
|
var DEFAULT_COLLISION_PADDING = 5;
|
|
8197
8658
|
|
|
8198
8659
|
// src/layout/axes/thinning.ts
|
|
8199
|
-
import { estimateTextWidth as
|
|
8660
|
+
import { estimateTextWidth as estimateTextWidth8 } from "@opendata-ai/openchart-core";
|
|
8200
8661
|
var MIN_TICK_GAP_FACTOR = 1;
|
|
8201
8662
|
var MIN_TICK_COUNT = 2;
|
|
8202
8663
|
function measureLabel(text, fontSize, fontWeight, measureText) {
|
|
8203
|
-
return measureText ? measureText(text, fontSize, fontWeight).width :
|
|
8664
|
+
return measureText ? measureText(text, fontSize, fontWeight).width : estimateTextWidth8(text, fontSize, fontWeight);
|
|
8204
8665
|
}
|
|
8205
8666
|
function ticksOverlap(ticks2, fontSize, fontWeight, measureText, orientation = "horizontal") {
|
|
8206
8667
|
if (ticks2.length < 2) return false;
|
|
@@ -8242,11 +8703,11 @@ function thinTicksUntilFit(ticks2, fontSize, fontWeight, measureText, orientatio
|
|
|
8242
8703
|
// src/layout/axes/ticks.ts
|
|
8243
8704
|
import {
|
|
8244
8705
|
abbreviateNumber as abbreviateNumber2,
|
|
8245
|
-
buildD3Formatter as
|
|
8706
|
+
buildD3Formatter as buildD3Formatter5,
|
|
8246
8707
|
buildTemporalFormatter,
|
|
8247
|
-
estimateTextWidth as
|
|
8708
|
+
estimateTextWidth as estimateTextWidth9,
|
|
8248
8709
|
formatDate,
|
|
8249
|
-
formatNumber as
|
|
8710
|
+
formatNumber as formatNumber3
|
|
8250
8711
|
} from "@opendata-ai/openchart-core";
|
|
8251
8712
|
var Y_PX_PER_TICK = {
|
|
8252
8713
|
full: 40,
|
|
@@ -8291,7 +8752,8 @@ var NUMERIC_SCALE_TYPES = /* @__PURE__ */ new Set([
|
|
|
8291
8752
|
]);
|
|
8292
8753
|
var TEMPORAL_SCALE_TYPES = /* @__PURE__ */ new Set(["time", "utc"]);
|
|
8293
8754
|
function formatTickLabel(value2, resolvedScale) {
|
|
8294
|
-
const
|
|
8755
|
+
const axisConfig = resolvedScale.channel.axis || void 0;
|
|
8756
|
+
const formatStr = axisConfig?.format;
|
|
8295
8757
|
if (TEMPORAL_SCALE_TYPES.has(resolvedScale.type)) {
|
|
8296
8758
|
const temporalFmt = buildTemporalFormatter(formatStr);
|
|
8297
8759
|
if (temporalFmt) return temporalFmt(value2);
|
|
@@ -8301,11 +8763,11 @@ function formatTickLabel(value2, resolvedScale) {
|
|
|
8301
8763
|
if (NUMERIC_SCALE_TYPES.has(resolvedScale.type)) {
|
|
8302
8764
|
const num = value2;
|
|
8303
8765
|
if (formatStr) {
|
|
8304
|
-
const fmt =
|
|
8766
|
+
const fmt = buildD3Formatter5(formatStr);
|
|
8305
8767
|
if (fmt) return fmt(num);
|
|
8306
8768
|
}
|
|
8307
8769
|
if (Math.abs(num) >= 1e3) return abbreviateNumber2(num);
|
|
8308
|
-
return
|
|
8770
|
+
return formatNumber3(num);
|
|
8309
8771
|
}
|
|
8310
8772
|
return String(value2);
|
|
8311
8773
|
}
|
|
@@ -8319,8 +8781,8 @@ function continuousTicks(resolvedScale, density, targetCount) {
|
|
|
8319
8781
|
label: formatTickLabel(value2, resolvedScale)
|
|
8320
8782
|
}));
|
|
8321
8783
|
}
|
|
8322
|
-
const
|
|
8323
|
-
const count =
|
|
8784
|
+
const axCfg = resolvedScale.channel.axis || void 0;
|
|
8785
|
+
const count = axCfg?.tickCount ?? targetCount ?? TICK_COUNTS[density];
|
|
8324
8786
|
return buildContinuousTicks(resolvedScale, count);
|
|
8325
8787
|
}
|
|
8326
8788
|
function buildContinuousTicks(resolvedScale, count) {
|
|
@@ -8356,12 +8818,13 @@ function scaleSupportsTickCount(resolvedScale) {
|
|
|
8356
8818
|
function categoricalTicks(resolvedScale, density, orientation = "horizontal", bandwidth, labelAngle, fontSize, fontWeight, measureText, subtitleContext) {
|
|
8357
8819
|
const scale = resolvedScale.scale;
|
|
8358
8820
|
const domain = scale.domain();
|
|
8359
|
-
const
|
|
8821
|
+
const catAxisCfg = resolvedScale.channel.axis || void 0;
|
|
8822
|
+
const explicitTickCount = catAxisCfg?.tickCount;
|
|
8360
8823
|
let selectedValues = domain;
|
|
8361
8824
|
if (resolvedScale.type === "band" && orientation === "horizontal") {
|
|
8362
8825
|
if (bandwidth !== void 0 && bandwidth > 0 && fontSize !== void 0) {
|
|
8363
8826
|
const maxLabelWidth = domain.reduce((max4, v) => {
|
|
8364
|
-
const w = measureText ? measureText(v, fontSize, fontWeight ?? 400).width :
|
|
8827
|
+
const w = measureText ? measureText(v, fontSize, fontWeight ?? 400).width : estimateTextWidth9(v, fontSize, fontWeight ?? 400);
|
|
8365
8828
|
return Math.max(max4, w);
|
|
8366
8829
|
}, 0);
|
|
8367
8830
|
const angleRad = labelAngle !== void 0 ? Math.abs(labelAngle) * Math.PI / 180 : 0;
|
|
@@ -8523,7 +8986,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
|
|
|
8523
8986
|
};
|
|
8524
8987
|
const { fontSize } = tickLabelStyle;
|
|
8525
8988
|
const { fontWeight } = tickLabelStyle;
|
|
8526
|
-
if (scales.x && !dataContext?.skipX) {
|
|
8989
|
+
if (scales.x && !dataContext?.skipX && scales.x.channel.axis !== false) {
|
|
8527
8990
|
const axisConfig = scales.x.channel.axis;
|
|
8528
8991
|
const isContinuousX = scales.x.type !== "band" && scales.x.type !== "point" && scales.x.type !== "ordinal";
|
|
8529
8992
|
const xTargetCount = isContinuousX ? targetTickCount(chartArea.width, xDensity, "x") : void 0;
|
|
@@ -8601,7 +9064,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
|
|
|
8601
9064
|
labelFlush: axisConfig?.labelFlush
|
|
8602
9065
|
};
|
|
8603
9066
|
}
|
|
8604
|
-
if (scales.y && !dataContext?.skipY) {
|
|
9067
|
+
if (scales.y && !dataContext?.skipY && scales.y.channel.axis !== false) {
|
|
8605
9068
|
const axisConfig = scales.y.channel.axis;
|
|
8606
9069
|
const isContinuousY = scales.y.type !== "band" && scales.y.type !== "point" && scales.y.type !== "ordinal";
|
|
8607
9070
|
const yTargetCount = isContinuousY ? targetTickCount(chartArea.height, yDensity, "y") : void 0;
|
|
@@ -8677,8 +9140,8 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
|
|
|
8677
9140
|
import {
|
|
8678
9141
|
AXIS_TITLE_TRAILING_PAD,
|
|
8679
9142
|
BREAKPOINT_COMPACT_MAX,
|
|
8680
|
-
computeChrome as
|
|
8681
|
-
estimateTextWidth as
|
|
9143
|
+
computeChrome as computeChrome3,
|
|
9144
|
+
estimateTextWidth as estimateTextWidth11,
|
|
8682
9145
|
getAxisTitleOffset,
|
|
8683
9146
|
HPAD_COMPACT_FRACTION,
|
|
8684
9147
|
HPAD_COMPACT_MIN,
|
|
@@ -8693,12 +9156,12 @@ import {
|
|
|
8693
9156
|
} from "@opendata-ai/openchart-core";
|
|
8694
9157
|
|
|
8695
9158
|
// src/legend/wrap.ts
|
|
8696
|
-
import { COMPACT_WIDTH, estimateTextWidth as
|
|
9159
|
+
import { COMPACT_WIDTH, estimateTextWidth as estimateTextWidth10 } from "@opendata-ai/openchart-core";
|
|
8697
9160
|
var SWATCH_SIZE2 = 12;
|
|
8698
9161
|
var SWATCH_GAP2 = 6;
|
|
8699
9162
|
var ENTRY_GAP2 = 16;
|
|
8700
9163
|
var ENTRY_GAP_COMPACT = 10;
|
|
8701
|
-
var LEGEND_GAP =
|
|
9164
|
+
var LEGEND_GAP = 8;
|
|
8702
9165
|
function legendGap(width) {
|
|
8703
9166
|
return width < COMPACT_WIDTH ? 0 : LEGEND_GAP;
|
|
8704
9167
|
}
|
|
@@ -8712,7 +9175,7 @@ function measureLegendWrap(entries, maxWidth, labelStyle, maxRows, entryGap = EN
|
|
|
8712
9175
|
let fittingCount = entries.length;
|
|
8713
9176
|
let fittingCountLocked = false;
|
|
8714
9177
|
for (let i = 0; i < entries.length; i++) {
|
|
8715
|
-
const labelWidth =
|
|
9178
|
+
const labelWidth = estimateTextWidth10(
|
|
8716
9179
|
entries[i].label,
|
|
8717
9180
|
labelStyle.fontSize,
|
|
8718
9181
|
labelStyle.fontWeight
|
|
@@ -8758,7 +9221,9 @@ function getMinChartDims(display) {
|
|
|
8758
9221
|
}
|
|
8759
9222
|
function getSparklinePad(spec) {
|
|
8760
9223
|
const strokeWidth = spec.markDef.strokeWidth ?? 2;
|
|
8761
|
-
|
|
9224
|
+
const hasPoints = !!spec.markDef.point;
|
|
9225
|
+
const pointRadius = hasPoints ? 3 : 0;
|
|
9226
|
+
return Math.max(strokeWidth / 2 + 1, pointRadius + 1, 2);
|
|
8762
9227
|
}
|
|
8763
9228
|
function computeDimensions(spec, options, legendLayout, theme, strategy, watermark = true) {
|
|
8764
9229
|
const { width, height } = options;
|
|
@@ -8771,7 +9236,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8771
9236
|
if (isSparkline && !userExplicit.chrome) {
|
|
8772
9237
|
chromeMode = "hidden";
|
|
8773
9238
|
}
|
|
8774
|
-
const chrome =
|
|
9239
|
+
const chrome = computeChrome3(
|
|
8775
9240
|
chromeToInput(spec.chrome),
|
|
8776
9241
|
theme,
|
|
8777
9242
|
width,
|
|
@@ -8812,11 +9277,12 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8812
9277
|
const total = { x: 0, y: 0, width, height };
|
|
8813
9278
|
const isRadial = spec.markType === "arc";
|
|
8814
9279
|
const encoding = spec.encoding;
|
|
8815
|
-
const
|
|
9280
|
+
const xAxisSuppressed = encoding.x?.axis === false;
|
|
9281
|
+
const xAxis = !xAxisSuppressed && encoding.x?.axis;
|
|
8816
9282
|
const hasXAxisLabel = !!xAxis?.title;
|
|
8817
9283
|
const xTickAngle = xAxis?.labelAngle;
|
|
8818
9284
|
let xAxisHeight;
|
|
8819
|
-
if (isRadial) {
|
|
9285
|
+
if (isRadial || xAxisSuppressed) {
|
|
8820
9286
|
xAxisHeight = 0;
|
|
8821
9287
|
} else if (xTickAngle && Math.abs(xTickAngle) > 10) {
|
|
8822
9288
|
const angleRad = Math.abs(xTickAngle) * (Math.PI / 180);
|
|
@@ -8825,7 +9291,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8825
9291
|
if (xField) {
|
|
8826
9292
|
for (const row of spec.data) {
|
|
8827
9293
|
const label = String(row[xField] ?? "");
|
|
8828
|
-
const w =
|
|
9294
|
+
const w = estimateTextWidth11(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
|
|
8829
9295
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
8830
9296
|
}
|
|
8831
9297
|
}
|
|
@@ -8855,7 +9321,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8855
9321
|
const label = String(row[colorField] ?? "");
|
|
8856
9322
|
if (!seen.has(label)) {
|
|
8857
9323
|
seen.add(label);
|
|
8858
|
-
const w =
|
|
9324
|
+
const w = estimateTextWidth11(label, 11, 600);
|
|
8859
9325
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
8860
9326
|
}
|
|
8861
9327
|
}
|
|
@@ -8875,7 +9341,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8875
9341
|
const maxXStr = String(maxX);
|
|
8876
9342
|
for (const ann of spec.annotations) {
|
|
8877
9343
|
if (ann.type === "text" && String(ann.x) === maxXStr) {
|
|
8878
|
-
const textWidth =
|
|
9344
|
+
const textWidth = estimateTextWidth11(ann.text, ann.fontSize ?? 11, ann.fontWeight ?? 600);
|
|
8879
9345
|
const dx = ann.offset?.dx ?? 0;
|
|
8880
9346
|
const anchor = ann.anchor ?? "auto";
|
|
8881
9347
|
const baseRightExtent = anchor === "left" ? textWidth : (
|
|
@@ -8893,19 +9359,20 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8893
9359
|
}
|
|
8894
9360
|
}
|
|
8895
9361
|
}
|
|
8896
|
-
|
|
9362
|
+
const yAxisSuppressed = encoding.y?.axis === false;
|
|
9363
|
+
if (encoding.y && !isRadial && !yAxisSuppressed) {
|
|
8897
9364
|
if (spec.markType === "bar" || spec.markType === "circle" || spec.markType === "lollipop" || encoding.y.type === "nominal" || encoding.y.type === "ordinal") {
|
|
8898
9365
|
const yField = encoding.y.field;
|
|
8899
9366
|
const yLabelField = encoding.y.axis?.labelField;
|
|
8900
9367
|
let maxLabelWidth = 0;
|
|
8901
9368
|
for (const row of spec.data) {
|
|
8902
9369
|
const label = String(row[yField] ?? "");
|
|
8903
|
-
let w =
|
|
9370
|
+
let w = estimateTextWidth11(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
|
|
8904
9371
|
if (yLabelField) {
|
|
8905
9372
|
const subtitle = String(row[yLabelField] ?? "");
|
|
8906
9373
|
if (subtitle) {
|
|
8907
9374
|
const gap = theme.fonts.sizes.axisTick * 0.6;
|
|
8908
|
-
const subtitleWidth =
|
|
9375
|
+
const subtitleWidth = estimateTextWidth11(
|
|
8909
9376
|
subtitle,
|
|
8910
9377
|
theme.fonts.sizes.axisTick,
|
|
8911
9378
|
theme.fonts.weights.normal
|
|
@@ -8948,7 +9415,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8948
9415
|
}
|
|
8949
9416
|
const negPrefix = spec.data.some((r) => Number(r[yField]) < 0) ? "-" : "";
|
|
8950
9417
|
const labelEst = negPrefix + sampleLabel;
|
|
8951
|
-
const labelWidth =
|
|
9418
|
+
const labelWidth = estimateTextWidth11(
|
|
8952
9419
|
labelEst,
|
|
8953
9420
|
theme.fonts.sizes.axisTick,
|
|
8954
9421
|
theme.fonts.weights.normal
|
|
@@ -8985,7 +9452,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8985
9452
|
const minDims = getMinChartDims(spec.display);
|
|
8986
9453
|
if ((chartArea.width < minDims.width || chartArea.height < minDims.height) && chromeMode !== "hidden") {
|
|
8987
9454
|
const fallbackMode = chromeMode === "full" ? "compact" : "hidden";
|
|
8988
|
-
const fallbackChrome =
|
|
9455
|
+
const fallbackChrome = computeChrome3(
|
|
8989
9456
|
chromeToInput(spec.chrome),
|
|
8990
9457
|
theme,
|
|
8991
9458
|
width,
|
|
@@ -9120,6 +9587,13 @@ function buildLinearScale(channel, data, rangeStart, rangeEnd) {
|
|
|
9120
9587
|
const scale = linear2().domain([domainMin, domainMax]).range([rangeStart, rangeEnd]);
|
|
9121
9588
|
if (!channel.scale?.domain && channel.scale?.nice !== false) {
|
|
9122
9589
|
scale.nice();
|
|
9590
|
+
if (channel.scale?.zero === false) {
|
|
9591
|
+
const [nicedMin, nicedMax] = scale.domain();
|
|
9592
|
+
if (nicedMin < domainMin || nicedMax > domainMax) {
|
|
9593
|
+
scale.domain([domainMin, domainMax]);
|
|
9594
|
+
scale.nice(20);
|
|
9595
|
+
}
|
|
9596
|
+
}
|
|
9123
9597
|
}
|
|
9124
9598
|
applyContinuousConfig(scale, channel);
|
|
9125
9599
|
return { scale, type: "linear", channel };
|
|
@@ -9470,7 +9944,7 @@ function computeScales(spec, chartArea, data) {
|
|
|
9470
9944
|
}
|
|
9471
9945
|
|
|
9472
9946
|
// src/legend/compute.ts
|
|
9473
|
-
import { BRAND_RESERVE_WIDTH as BRAND_RESERVE_WIDTH2, COMPACT_WIDTH as COMPACT_WIDTH2, estimateTextWidth as
|
|
9947
|
+
import { BRAND_RESERVE_WIDTH as BRAND_RESERVE_WIDTH2, COMPACT_WIDTH as COMPACT_WIDTH2, estimateTextWidth as estimateTextWidth12 } from "@opendata-ai/openchart-core";
|
|
9474
9948
|
var LEGEND_PADDING = 8;
|
|
9475
9949
|
var LEGEND_RIGHT_WIDTH = 120;
|
|
9476
9950
|
var RIGHT_LEGEND_MAX_HEIGHT_RATIO = 0.4;
|
|
@@ -9587,7 +10061,7 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
|
|
|
9587
10061
|
}
|
|
9588
10062
|
if (resolvedPosition === "right" || resolvedPosition === "bottom-right") {
|
|
9589
10063
|
const maxLabelWidth = Math.max(
|
|
9590
|
-
...entries.map((e) =>
|
|
10064
|
+
...entries.map((e) => estimateTextWidth12(e.label, labelStyle.fontSize, labelStyle.fontWeight))
|
|
9591
10065
|
);
|
|
9592
10066
|
const legendWidth = Math.min(
|
|
9593
10067
|
LEGEND_RIGHT_WIDTH,
|
|
@@ -9647,7 +10121,7 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
|
|
|
9647
10121
|
entries = truncateEntries(entries, fittingCount);
|
|
9648
10122
|
}
|
|
9649
10123
|
const totalWidth = entries.reduce((sum2, entry) => {
|
|
9650
|
-
const labelWidth =
|
|
10124
|
+
const labelWidth = estimateTextWidth12(entry.label, labelStyle.fontSize, labelStyle.fontWeight);
|
|
9651
10125
|
return sum2 + SWATCH_SIZE2 + SWATCH_GAP2 + labelWidth + effectiveEntryGap;
|
|
9652
10126
|
}, 0);
|
|
9653
10127
|
const { rowCount } = measureLegendWrap(
|
|
@@ -9679,12 +10153,12 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
|
|
|
9679
10153
|
|
|
9680
10154
|
// src/sankey/compile-sankey.ts
|
|
9681
10155
|
import {
|
|
9682
|
-
adaptTheme as
|
|
9683
|
-
buildD3Formatter as
|
|
9684
|
-
computeChrome as
|
|
9685
|
-
estimateTextWidth as
|
|
9686
|
-
formatNumber as
|
|
9687
|
-
resolveTheme as
|
|
10156
|
+
adaptTheme as adaptTheme3,
|
|
10157
|
+
buildD3Formatter as buildD3Formatter6,
|
|
10158
|
+
computeChrome as computeChrome4,
|
|
10159
|
+
estimateTextWidth as estimateTextWidth13,
|
|
10160
|
+
formatNumber as formatNumber4,
|
|
10161
|
+
resolveTheme as resolveTheme3
|
|
9688
10162
|
} from "@opendata-ai/openchart-core";
|
|
9689
10163
|
|
|
9690
10164
|
// ../../node_modules/.bun/d3-array@2.12.1/node_modules/d3-array/src/max.js
|
|
@@ -10267,16 +10741,16 @@ function compileSankey(spec, options) {
|
|
|
10267
10741
|
const rawWatermark = spec.watermark;
|
|
10268
10742
|
const watermark = rawWatermark !== void 0 ? sankeySpec.watermark : options.watermark ?? true;
|
|
10269
10743
|
const mergedThemeConfig = options.theme ? { ...sankeySpec.theme, ...options.theme } : sankeySpec.theme;
|
|
10270
|
-
const lightTheme =
|
|
10744
|
+
const lightTheme = resolveTheme3(mergedThemeConfig);
|
|
10271
10745
|
let theme = lightTheme;
|
|
10272
10746
|
if (options.darkMode) {
|
|
10273
|
-
theme =
|
|
10747
|
+
theme = adaptTheme3(theme);
|
|
10274
10748
|
theme = {
|
|
10275
10749
|
...theme,
|
|
10276
10750
|
colors: { ...theme.colors, categorical: lightTheme.colors.categorical }
|
|
10277
10751
|
};
|
|
10278
10752
|
}
|
|
10279
|
-
const chrome =
|
|
10753
|
+
const chrome = computeChrome4(
|
|
10280
10754
|
{
|
|
10281
10755
|
title: sankeySpec.chrome.title,
|
|
10282
10756
|
subtitle: sankeySpec.chrome.subtitle,
|
|
@@ -10299,7 +10773,7 @@ function compileSankey(spec, options) {
|
|
|
10299
10773
|
height: options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
|
|
10300
10774
|
};
|
|
10301
10775
|
if (fullArea.width <= 0 || fullArea.height <= 0) {
|
|
10302
|
-
return
|
|
10776
|
+
return emptyLayout2(fullArea, chrome, theme, options, watermark);
|
|
10303
10777
|
}
|
|
10304
10778
|
const sourceField = sankeySpec.encoding.source.field;
|
|
10305
10779
|
const targetField = sankeySpec.encoding.target.field;
|
|
@@ -10335,7 +10809,7 @@ function compileSankey(spec, options) {
|
|
|
10335
10809
|
height: fullArea.height - legend.bounds.height - legendGap2
|
|
10336
10810
|
};
|
|
10337
10811
|
if (area.height <= 0) {
|
|
10338
|
-
return
|
|
10812
|
+
return emptyLayout2(area, chrome, theme, options, watermark);
|
|
10339
10813
|
}
|
|
10340
10814
|
const labelFontSize = theme.fonts.sizes.small;
|
|
10341
10815
|
const labelFontWeight = theme.fonts.weights.normal;
|
|
@@ -10363,7 +10837,7 @@ function compileSankey(spec, options) {
|
|
|
10363
10837
|
if (labelsLeft) continue;
|
|
10364
10838
|
const labelX = (node.x1 ?? nodeWidth) + LABEL_GAP;
|
|
10365
10839
|
const labelText = node.label ?? node.id;
|
|
10366
|
-
const labelWidth =
|
|
10840
|
+
const labelWidth = estimateTextWidth13(labelText, labelFontSize, labelFontWeight);
|
|
10367
10841
|
const overflow = labelX + labelWidth - rightEdge;
|
|
10368
10842
|
if (overflow > maxOverflow) maxOverflow = overflow;
|
|
10369
10843
|
}
|
|
@@ -10553,10 +11027,10 @@ function buildSankeyLegend(nodeColorMap, colorField, data, sourceField, targetFi
|
|
|
10553
11027
|
}
|
|
10554
11028
|
function formatFlowValue(value2, valueFormat) {
|
|
10555
11029
|
if (valueFormat) {
|
|
10556
|
-
const fmt =
|
|
11030
|
+
const fmt = buildD3Formatter6(valueFormat);
|
|
10557
11031
|
if (fmt) return fmt(value2);
|
|
10558
11032
|
}
|
|
10559
|
-
return
|
|
11033
|
+
return formatNumber4(value2);
|
|
10560
11034
|
}
|
|
10561
11035
|
function buildTooltipDescriptors(nodes, links, valueFormat) {
|
|
10562
11036
|
const descriptors = /* @__PURE__ */ new Map();
|
|
@@ -10587,7 +11061,7 @@ function buildTooltipDescriptors(nodes, links, valueFormat) {
|
|
|
10587
11061
|
}
|
|
10588
11062
|
return descriptors;
|
|
10589
11063
|
}
|
|
10590
|
-
function
|
|
11064
|
+
function emptyLayout2(area, chrome, theme, options, watermark) {
|
|
10591
11065
|
return {
|
|
10592
11066
|
area,
|
|
10593
11067
|
chrome,
|
|
@@ -10625,7 +11099,7 @@ function emptyLayout(area, chrome, theme, options, watermark) {
|
|
|
10625
11099
|
}
|
|
10626
11100
|
|
|
10627
11101
|
// src/tables/compile-table.ts
|
|
10628
|
-
import { computeChrome as
|
|
11102
|
+
import { computeChrome as computeChrome5, estimateTextWidth as estimateTextWidth14 } from "@opendata-ai/openchart-core";
|
|
10629
11103
|
|
|
10630
11104
|
// src/tables/bar-column.ts
|
|
10631
11105
|
var NEGATIVE_BAR_COLOR = "#c44e52";
|
|
@@ -10742,7 +11216,7 @@ function computeCategoryColors(data, column, theme, darkMode) {
|
|
|
10742
11216
|
}
|
|
10743
11217
|
|
|
10744
11218
|
// src/tables/format-cells.ts
|
|
10745
|
-
import { buildD3Formatter as
|
|
11219
|
+
import { buildD3Formatter as buildD3Formatter7, formatDate as formatDate2, formatNumber as formatNumber5 } from "@opendata-ai/openchart-core";
|
|
10746
11220
|
function isNumericValue(value2) {
|
|
10747
11221
|
if (typeof value2 === "number") return Number.isFinite(value2);
|
|
10748
11222
|
return false;
|
|
@@ -10761,7 +11235,7 @@ function formatCell(value2, column) {
|
|
|
10761
11235
|
};
|
|
10762
11236
|
}
|
|
10763
11237
|
if (column.format && isNumericValue(value2)) {
|
|
10764
|
-
const formatter =
|
|
11238
|
+
const formatter = buildD3Formatter7(column.format);
|
|
10765
11239
|
if (formatter) {
|
|
10766
11240
|
return {
|
|
10767
11241
|
value: value2,
|
|
@@ -10773,7 +11247,7 @@ function formatCell(value2, column) {
|
|
|
10773
11247
|
if (isNumericValue(value2)) {
|
|
10774
11248
|
return {
|
|
10775
11249
|
value: value2,
|
|
10776
|
-
formattedValue:
|
|
11250
|
+
formattedValue: formatNumber5(value2),
|
|
10777
11251
|
style
|
|
10778
11252
|
};
|
|
10779
11253
|
}
|
|
@@ -10793,19 +11267,28 @@ function formatCell(value2, column) {
|
|
|
10793
11267
|
function formatValueForSearch(value2, column) {
|
|
10794
11268
|
if (value2 == null) return "";
|
|
10795
11269
|
if (column.format && isNumericValue(value2)) {
|
|
10796
|
-
const formatter =
|
|
11270
|
+
const formatter = buildD3Formatter7(column.format);
|
|
10797
11271
|
if (formatter) {
|
|
10798
11272
|
return formatter(value2);
|
|
10799
11273
|
}
|
|
10800
11274
|
}
|
|
10801
11275
|
if (isNumericValue(value2)) {
|
|
10802
|
-
return
|
|
11276
|
+
return formatNumber5(value2);
|
|
10803
11277
|
}
|
|
10804
11278
|
return String(value2);
|
|
10805
11279
|
}
|
|
10806
11280
|
|
|
10807
11281
|
// src/tables/heatmap.ts
|
|
10808
11282
|
import { adaptColorForDarkMode as adaptColorForDarkMode2 } from "@opendata-ai/openchart-core";
|
|
11283
|
+
function relativeLuminance(hex2) {
|
|
11284
|
+
const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex2);
|
|
11285
|
+
if (!m) return 0;
|
|
11286
|
+
const [r, g, b] = [m[1], m[2], m[3]].map((c) => {
|
|
11287
|
+
const v = Number.parseInt(c, 16) / 255;
|
|
11288
|
+
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
|
|
11289
|
+
});
|
|
11290
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
11291
|
+
}
|
|
10809
11292
|
function interpolatorFromStops(stops) {
|
|
10810
11293
|
if (stops.length === 0) return () => "#ffffff";
|
|
10811
11294
|
if (stops.length === 1) return () => stops[0];
|
|
@@ -10858,7 +11341,19 @@ function computeHeatmapColors(data, column, theme, darkMode) {
|
|
|
10858
11341
|
if (darkMode) {
|
|
10859
11342
|
const lightBg = "#ffffff";
|
|
10860
11343
|
const darkBg = theme.colors.background;
|
|
11344
|
+
const originalStops = stops;
|
|
10861
11345
|
stops = stops.map((c) => adaptColorForDarkMode2(c, lightBg, darkBg));
|
|
11346
|
+
if (originalStops.length >= 2) {
|
|
11347
|
+
const origDirection = Math.sign(
|
|
11348
|
+
relativeLuminance(originalStops[originalStops.length - 1]) - relativeLuminance(originalStops[0])
|
|
11349
|
+
);
|
|
11350
|
+
const adaptedDirection = Math.sign(
|
|
11351
|
+
relativeLuminance(stops[stops.length - 1]) - relativeLuminance(stops[0])
|
|
11352
|
+
);
|
|
11353
|
+
if (origDirection !== 0 && adaptedDirection !== 0 && origDirection !== adaptedDirection) {
|
|
11354
|
+
stops = stops.slice().reverse();
|
|
11355
|
+
}
|
|
11356
|
+
}
|
|
10862
11357
|
}
|
|
10863
11358
|
const interpolator = interpolatorFromStops(stops);
|
|
10864
11359
|
const scale = sequential(interpolator).domain(domain).clamp(true);
|
|
@@ -11040,13 +11535,13 @@ function estimateColumnWidth(col, data, fontSize) {
|
|
|
11040
11535
|
if (col.image) return (col.image.width ?? 24) + PADDING;
|
|
11041
11536
|
if (col.flag) return 60;
|
|
11042
11537
|
const label = col.label ?? col.key;
|
|
11043
|
-
const headerWidth =
|
|
11538
|
+
const headerWidth = estimateTextWidth14(label, fontSize, 600) + PADDING;
|
|
11044
11539
|
const sampleSize = Math.min(100, data.length);
|
|
11045
11540
|
let maxDataWidth = 0;
|
|
11046
11541
|
for (let i = 0; i < sampleSize; i++) {
|
|
11047
11542
|
const val = data[i][col.key];
|
|
11048
11543
|
const text = val == null ? "" : String(val);
|
|
11049
|
-
const width =
|
|
11544
|
+
const width = estimateTextWidth14(text, fontSize, 400) + PADDING;
|
|
11050
11545
|
if (width > maxDataWidth) maxDataWidth = width;
|
|
11051
11546
|
}
|
|
11052
11547
|
return Math.max(MIN_WIDTH, headerWidth, maxDataWidth);
|
|
@@ -11230,7 +11725,7 @@ function compileTableLayout(spec, options, theme) {
|
|
|
11230
11725
|
return { id: rowId, cells, data: row };
|
|
11231
11726
|
});
|
|
11232
11727
|
const watermark = spec.watermark;
|
|
11233
|
-
const chrome =
|
|
11728
|
+
const chrome = computeChrome5(
|
|
11234
11729
|
{
|
|
11235
11730
|
title: spec.chrome.title,
|
|
11236
11731
|
subtitle: spec.chrome.subtitle,
|
|
@@ -11272,16 +11767,16 @@ function compileTableLayout(spec, options, theme) {
|
|
|
11272
11767
|
|
|
11273
11768
|
// src/tilemap/compile-tilemap.ts
|
|
11274
11769
|
import {
|
|
11275
|
-
adaptTheme as
|
|
11276
|
-
buildD3Formatter as
|
|
11277
|
-
computeChrome as
|
|
11278
|
-
estimateTextWidth as
|
|
11279
|
-
formatNumber as
|
|
11280
|
-
resolveTheme as
|
|
11770
|
+
adaptTheme as adaptTheme4,
|
|
11771
|
+
buildD3Formatter as buildD3Formatter8,
|
|
11772
|
+
computeChrome as computeChrome6,
|
|
11773
|
+
estimateTextWidth as estimateTextWidth15,
|
|
11774
|
+
formatNumber as formatNumber6,
|
|
11775
|
+
resolveTheme as resolveTheme4,
|
|
11281
11776
|
SEQUENTIAL_PALETTES
|
|
11282
11777
|
} from "@opendata-ai/openchart-core";
|
|
11283
|
-
var TILE_CORNER_RADIUS =
|
|
11284
|
-
var TILE_STROKE_WIDTH =
|
|
11778
|
+
var TILE_CORNER_RADIUS = 6;
|
|
11779
|
+
var TILE_STROKE_WIDTH = 1;
|
|
11285
11780
|
function compileTileMap(spec, options) {
|
|
11286
11781
|
const { spec: normalized } = compile(spec);
|
|
11287
11782
|
if (!("type" in normalized) || normalized.type !== "tilemap") {
|
|
@@ -11293,13 +11788,13 @@ function compileTileMap(spec, options) {
|
|
|
11293
11788
|
const rawWatermark = spec.watermark;
|
|
11294
11789
|
const watermark = rawWatermark !== void 0 ? tilemapSpec.watermark : options.watermark ?? true;
|
|
11295
11790
|
const mergedThemeConfig = options.theme ? { ...tilemapSpec.theme, ...options.theme } : tilemapSpec.theme;
|
|
11296
|
-
const lightTheme =
|
|
11791
|
+
const lightTheme = resolveTheme4(mergedThemeConfig);
|
|
11297
11792
|
let theme = lightTheme;
|
|
11298
11793
|
if (options.darkMode) {
|
|
11299
|
-
theme =
|
|
11794
|
+
theme = adaptTheme4(theme);
|
|
11300
11795
|
}
|
|
11301
11796
|
const isDarkMode = options.darkMode;
|
|
11302
|
-
const chrome =
|
|
11797
|
+
const chrome = computeChrome6(
|
|
11303
11798
|
{
|
|
11304
11799
|
title: tilemapSpec.chrome.title,
|
|
11305
11800
|
subtitle: tilemapSpec.chrome.subtitle,
|
|
@@ -11322,7 +11817,7 @@ function compileTileMap(spec, options) {
|
|
|
11322
11817
|
height: options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
|
|
11323
11818
|
};
|
|
11324
11819
|
if (fullArea.width <= 0 || fullArea.height <= 0) {
|
|
11325
|
-
return
|
|
11820
|
+
return emptyLayout3(chrome, theme, options, watermark);
|
|
11326
11821
|
}
|
|
11327
11822
|
const stateField = tilemapSpec.encoding.state.field;
|
|
11328
11823
|
const valueField = tilemapSpec.encoding.value.field;
|
|
@@ -11341,26 +11836,26 @@ function compileTileMap(spec, options) {
|
|
|
11341
11836
|
const min4 = values.length > 0 ? Math.min(...values) : 0;
|
|
11342
11837
|
const max4 = values.length > 0 ? Math.max(...values) : 100;
|
|
11343
11838
|
const paletteStops = [...SEQUENTIAL_PALETTES[tilemapSpec.palette] ?? SEQUENTIAL_PALETTES.blue];
|
|
11344
|
-
|
|
11345
|
-
const
|
|
11346
|
-
const
|
|
11839
|
+
const baseColor = isDarkMode ? paletteStops[0] : paletteStops[paletteStops.length - 1];
|
|
11840
|
+
const opacityRange = isDarkMode ? [0.15, 1] : [0.2, 1];
|
|
11841
|
+
const opacityScale = linear2().domain([min4, max4]).range(opacityRange).clamp(true);
|
|
11347
11842
|
const showLegend = tilemapSpec.legend?.show !== false;
|
|
11348
|
-
const legendBarHeight =
|
|
11349
|
-
const legendLabelGap =
|
|
11843
|
+
const legendBarHeight = 6;
|
|
11844
|
+
const legendLabelGap = 6;
|
|
11350
11845
|
const legendTotalHeight = showLegend ? legendBarHeight + legendLabelGap + 14 : 0;
|
|
11351
11846
|
const legendGap2 = showLegend ? 8 : 0;
|
|
11352
11847
|
const tileAreaHeight = fullArea.height - legendTotalHeight - legendGap2;
|
|
11353
|
-
const tilePositions = computeTilePositions(fullArea.width, tileAreaHeight,
|
|
11848
|
+
const tilePositions = computeTilePositions(fullArea.width, tileAreaHeight, 5);
|
|
11354
11849
|
const tileGridOffsetX = fullArea.x + (fullArea.width - tilePositions.gridWidth) / 2;
|
|
11355
11850
|
const tileGridOffsetY = fullArea.y;
|
|
11356
11851
|
const legendX = tileGridOffsetX;
|
|
11357
11852
|
const legendY = tileGridOffsetY + tilePositions.gridHeight + legendGap2;
|
|
11358
11853
|
const legendWidth = tilePositions.gridWidth;
|
|
11359
|
-
const formatter =
|
|
11854
|
+
const formatter = buildD3Formatter8(tilemapSpec.valueFormat) ?? formatNumber6;
|
|
11360
11855
|
const neutralFillLight = "#e0e0e0";
|
|
11361
|
-
const neutralFillDark = "#
|
|
11856
|
+
const neutralFillDark = "#1e2a30";
|
|
11362
11857
|
const neutralStrokeLight = "#d0d0d0";
|
|
11363
|
-
const neutralStrokeDark = "
|
|
11858
|
+
const neutralStrokeDark = "rgba(255,255,255,0.08)";
|
|
11364
11859
|
const neutralFill = isDarkMode ? neutralFillDark : neutralFillLight;
|
|
11365
11860
|
const neutralStroke = isDarkMode ? neutralStrokeDark : neutralStrokeLight;
|
|
11366
11861
|
const tiles = [];
|
|
@@ -11369,26 +11864,31 @@ function compileTileMap(spec, options) {
|
|
|
11369
11864
|
if (!pos) continue;
|
|
11370
11865
|
const hasData = stateValueMap.has(stateCode);
|
|
11371
11866
|
const value2 = hasData ? stateValueMap.get(stateCode) : null;
|
|
11372
|
-
const
|
|
11867
|
+
const opacity = hasData ? opacityScale(value2) : 0;
|
|
11868
|
+
const fill = hasData ? baseColor : neutralFill;
|
|
11869
|
+
const stroke = hasData ? isDarkMode ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)" : neutralStroke;
|
|
11373
11870
|
const formattedValue = hasData ? formatter(value2) : "\u2013";
|
|
11374
11871
|
const labelStyle = {
|
|
11375
11872
|
fontFamily: theme.fonts.family,
|
|
11376
|
-
fontSize: tilePositions.tileSize > 24 ?
|
|
11873
|
+
fontSize: tilePositions.tileSize > 24 ? 10 : 7,
|
|
11377
11874
|
fontWeight: 700,
|
|
11378
11875
|
fill: "#ffffff",
|
|
11379
11876
|
lineHeight: 1.2
|
|
11380
11877
|
};
|
|
11381
11878
|
const valueLabelStyle = {
|
|
11382
11879
|
fontFamily: theme.fonts.family,
|
|
11383
|
-
fontSize: tilePositions.tileSize > 24 ?
|
|
11384
|
-
fontWeight:
|
|
11385
|
-
fill: "
|
|
11880
|
+
fontSize: tilePositions.tileSize > 24 ? 10 : 7,
|
|
11881
|
+
fontWeight: 300,
|
|
11882
|
+
fill: "rgba(255,255,255,0.6)",
|
|
11386
11883
|
lineHeight: 1.2
|
|
11387
11884
|
};
|
|
11388
|
-
const
|
|
11885
|
+
const tileCenterX = tileGridOffsetX + pos.x + tilePositions.tileSize / 2;
|
|
11886
|
+
const tileTopY = tileGridOffsetY + pos.y;
|
|
11887
|
+
const sz = tilePositions.tileSize;
|
|
11888
|
+
const valueLabel = sz < 24 ? { text: "", x: 0, y: 0, style: valueLabelStyle, visible: false } : {
|
|
11389
11889
|
text: formattedValue,
|
|
11390
|
-
x:
|
|
11391
|
-
y:
|
|
11890
|
+
x: tileCenterX,
|
|
11891
|
+
y: tileTopY + sz * 0.78,
|
|
11392
11892
|
style: valueLabelStyle,
|
|
11393
11893
|
visible: true
|
|
11394
11894
|
};
|
|
@@ -11396,10 +11896,11 @@ function compileTileMap(spec, options) {
|
|
|
11396
11896
|
type: "tile",
|
|
11397
11897
|
stateCode,
|
|
11398
11898
|
x: tileGridOffsetX + pos.x,
|
|
11399
|
-
y:
|
|
11400
|
-
size:
|
|
11899
|
+
y: tileTopY,
|
|
11900
|
+
size: sz,
|
|
11401
11901
|
fill,
|
|
11402
|
-
|
|
11902
|
+
fillOpacity: hasData ? opacity : 1,
|
|
11903
|
+
stroke,
|
|
11403
11904
|
strokeWidth: TILE_STROKE_WIDTH,
|
|
11404
11905
|
cornerRadius: TILE_CORNER_RADIUS,
|
|
11405
11906
|
value: value2 ?? null,
|
|
@@ -11407,8 +11908,8 @@ function compileTileMap(spec, options) {
|
|
|
11407
11908
|
hasData,
|
|
11408
11909
|
label: {
|
|
11409
11910
|
text: stateCode,
|
|
11410
|
-
x:
|
|
11411
|
-
y:
|
|
11911
|
+
x: tileCenterX,
|
|
11912
|
+
y: tileTopY + sz * 0.28,
|
|
11412
11913
|
style: labelStyle,
|
|
11413
11914
|
visible: true
|
|
11414
11915
|
},
|
|
@@ -11434,10 +11935,12 @@ function compileTileMap(spec, options) {
|
|
|
11434
11935
|
}
|
|
11435
11936
|
let gradientLegend = null;
|
|
11436
11937
|
if (showLegend) {
|
|
11437
|
-
const
|
|
11438
|
-
|
|
11439
|
-
|
|
11440
|
-
|
|
11938
|
+
const numStops = 5;
|
|
11939
|
+
const gradientColorStops = Array.from({ length: numStops }, (_, i) => {
|
|
11940
|
+
const t = i / (numStops - 1);
|
|
11941
|
+
const o = opacityRange[0] + t * (opacityRange[1] - opacityRange[0]);
|
|
11942
|
+
return { offset: t, color: baseColor, opacity: o };
|
|
11943
|
+
});
|
|
11441
11944
|
gradientLegend = {
|
|
11442
11945
|
type: "gradient",
|
|
11443
11946
|
position: "bottom",
|
|
@@ -11474,6 +11977,7 @@ function compileTileMap(spec, options) {
|
|
|
11474
11977
|
keyboardNavigable: tiles.length > 0
|
|
11475
11978
|
};
|
|
11476
11979
|
const resolvedAnimation = resolveAnimation(tilemapSpec.animation);
|
|
11980
|
+
const contentHeight = tileGridOffsetY + tilePositions.gridHeight + legendGap2 + legendTotalHeight + chrome.bottomHeight + padding;
|
|
11477
11981
|
return {
|
|
11478
11982
|
area: fullArea,
|
|
11479
11983
|
chrome,
|
|
@@ -11483,13 +11987,13 @@ function compileTileMap(spec, options) {
|
|
|
11483
11987
|
a11y,
|
|
11484
11988
|
theme,
|
|
11485
11989
|
width: options.width,
|
|
11486
|
-
height:
|
|
11990
|
+
height: contentHeight,
|
|
11487
11991
|
animation: resolvedAnimation,
|
|
11488
11992
|
watermark,
|
|
11489
|
-
measureText: options.measureText ?? ((text, fontSize) => ({ width:
|
|
11993
|
+
measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth15(text, fontSize), height: fontSize }))
|
|
11490
11994
|
};
|
|
11491
11995
|
}
|
|
11492
|
-
function
|
|
11996
|
+
function emptyLayout3(chrome, theme, options, watermark) {
|
|
11493
11997
|
return {
|
|
11494
11998
|
area: { x: 0, y: 0, width: 0, height: 0 },
|
|
11495
11999
|
chrome,
|
|
@@ -11521,7 +12025,7 @@ function emptyLayout2(chrome, theme, options, watermark) {
|
|
|
11521
12025
|
height: options.height,
|
|
11522
12026
|
watermark,
|
|
11523
12027
|
animation: void 0,
|
|
11524
|
-
measureText: options.measureText ?? ((text, fontSize) => ({ width:
|
|
12028
|
+
measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth15(text, fontSize), height: fontSize }))
|
|
11525
12029
|
};
|
|
11526
12030
|
}
|
|
11527
12031
|
|
|
@@ -11529,7 +12033,7 @@ function emptyLayout2(chrome, theme, options, watermark) {
|
|
|
11529
12033
|
import {
|
|
11530
12034
|
buildTemporalFormatter as buildTemporalFormatter2,
|
|
11531
12035
|
formatDate as formatDate3,
|
|
11532
|
-
formatNumber as
|
|
12036
|
+
formatNumber as formatNumber7,
|
|
11533
12037
|
getRepresentativeColor as getRepresentativeColor10
|
|
11534
12038
|
} from "@opendata-ai/openchart-core";
|
|
11535
12039
|
function formatValue(value2, fieldType, format2) {
|
|
@@ -11544,18 +12048,20 @@ function formatValue(value2, fieldType, format2) {
|
|
|
11544
12048
|
try {
|
|
11545
12049
|
return format(format2)(value2);
|
|
11546
12050
|
} catch {
|
|
11547
|
-
return
|
|
12051
|
+
return formatNumber7(value2);
|
|
11548
12052
|
}
|
|
11549
12053
|
}
|
|
11550
|
-
return
|
|
12054
|
+
return formatNumber7(value2);
|
|
11551
12055
|
}
|
|
11552
12056
|
return String(value2);
|
|
11553
12057
|
}
|
|
11554
12058
|
function resolveLabel(ch) {
|
|
11555
|
-
|
|
12059
|
+
const ax = ch.axis || void 0;
|
|
12060
|
+
return ch.title ?? ax?.title ?? ch.field;
|
|
11556
12061
|
}
|
|
11557
12062
|
function resolveFormat(ch) {
|
|
11558
|
-
|
|
12063
|
+
const ax = ch.axis || void 0;
|
|
12064
|
+
return ch.format ?? ax?.format;
|
|
11559
12065
|
}
|
|
11560
12066
|
function buildExplicitTooltipFields(row, channels) {
|
|
11561
12067
|
return channels.map((ch) => ({
|
|
@@ -12412,9 +12918,9 @@ function compileChart(spec, options) {
|
|
|
12412
12918
|
chartSpec = { ...chartSpec, watermark: false };
|
|
12413
12919
|
}
|
|
12414
12920
|
const mergedThemeConfig = options.theme ? { ...chartSpec.theme, ...options.theme } : chartSpec.theme;
|
|
12415
|
-
let theme =
|
|
12921
|
+
let theme = resolveTheme5(mergedThemeConfig);
|
|
12416
12922
|
if (options.darkMode) {
|
|
12417
|
-
theme =
|
|
12923
|
+
theme = adaptTheme5(theme);
|
|
12418
12924
|
}
|
|
12419
12925
|
const preliminaryArea = {
|
|
12420
12926
|
x: 0,
|
|
@@ -12600,7 +13106,7 @@ function estimateYAxisLabelWidth(data, encoding, baseFontSize) {
|
|
|
12600
13106
|
let maxWidth = 0;
|
|
12601
13107
|
for (const row of data) {
|
|
12602
13108
|
const label = String(row[yField] ?? "");
|
|
12603
|
-
const w =
|
|
13109
|
+
const w = estimateTextWidth16(label, baseFontSize, 400);
|
|
12604
13110
|
if (w > maxWidth) maxWidth = w;
|
|
12605
13111
|
}
|
|
12606
13112
|
return maxWidth > 0 ? maxWidth + 10 : 40;
|
|
@@ -12629,7 +13135,7 @@ function estimateYAxisLabelWidth(data, encoding, baseFontSize) {
|
|
|
12629
13135
|
}
|
|
12630
13136
|
const hasNeg = data.some((r) => Number(r[yField]) < 0);
|
|
12631
13137
|
const labelEst = (hasNeg ? "-" : "") + sampleLabel;
|
|
12632
|
-
return
|
|
13138
|
+
return estimateTextWidth16(labelEst, baseFontSize, 400) + 10;
|
|
12633
13139
|
}
|
|
12634
13140
|
function compileLayerIndependent(leaves, layerSpec, options) {
|
|
12635
13141
|
if (leaves.length > 2) {
|
|
@@ -12646,10 +13152,11 @@ function compileLayerIndependent(leaves, layerSpec, options) {
|
|
|
12646
13152
|
`Dual-axis charts require matching x-field types across layers. Layer 0 has '${xType0}', layer 1 has '${xType1}'.`
|
|
12647
13153
|
);
|
|
12648
13154
|
}
|
|
12649
|
-
const theme =
|
|
13155
|
+
const theme = resolveTheme5(layerSpec.theme ?? leaf1.theme);
|
|
12650
13156
|
const axisFontSize = theme.fonts?.sizes?.axisTick ?? 11;
|
|
12651
13157
|
const rightAxisWidth = estimateYAxisLabelWidth(leaf1.data, leaf1.encoding, axisFontSize);
|
|
12652
|
-
const
|
|
13158
|
+
const yAxisConfig = leaf1.encoding?.y?.axis || void 0;
|
|
13159
|
+
const hasRightAxisTitle = !!yAxisConfig?.title;
|
|
12653
13160
|
const tickExtent = TICK_LABEL_OFFSET + rightAxisWidth;
|
|
12654
13161
|
const bodyFontSize = theme.fonts?.sizes?.body ?? 13;
|
|
12655
13162
|
const axisTitleOffset = getAxisTitleOffset2(options.width);
|
|
@@ -12911,9 +13418,9 @@ function compileTable(spec, options) {
|
|
|
12911
13418
|
}
|
|
12912
13419
|
const tableSpec = normalized;
|
|
12913
13420
|
const mergedThemeConfig = options.theme ? { ...tableSpec.theme, ...options.theme } : tableSpec.theme;
|
|
12914
|
-
let theme =
|
|
13421
|
+
let theme = resolveTheme5(mergedThemeConfig);
|
|
12915
13422
|
if (options.darkMode) {
|
|
12916
|
-
theme =
|
|
13423
|
+
theme = adaptTheme5(theme);
|
|
12917
13424
|
}
|
|
12918
13425
|
const rawWatermark = spec.watermark;
|
|
12919
13426
|
const watermark = rawWatermark !== void 0 ? tableSpec.watermark : options.watermark ?? true;
|
|
@@ -12928,10 +13435,14 @@ function compileSankey2(spec, options) {
|
|
|
12928
13435
|
function compileTileMap2(spec, options) {
|
|
12929
13436
|
return compileTileMap(spec, options);
|
|
12930
13437
|
}
|
|
13438
|
+
function compileBarList2(spec, options) {
|
|
13439
|
+
return compileBarList(spec, options);
|
|
13440
|
+
}
|
|
12931
13441
|
export {
|
|
12932
13442
|
clampStaggerDelay,
|
|
12933
13443
|
clearRenderers,
|
|
12934
13444
|
compile,
|
|
13445
|
+
compileBarList2 as compileBarList,
|
|
12935
13446
|
compileChart,
|
|
12936
13447
|
compileGraph2 as compileGraph,
|
|
12937
13448
|
compileLayer,
|