@opendata-ai/openchart-engine 6.0.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +155 -19
- package/dist/index.js +1513 -164
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__test-fixtures__/specs.ts +6 -3
- package/src/__tests__/axes.test.ts +168 -4
- package/src/__tests__/compile-chart.test.ts +23 -12
- package/src/__tests__/compile-layer.test.ts +386 -0
- package/src/__tests__/dimensions.test.ts +6 -3
- package/src/__tests__/legend.test.ts +6 -3
- package/src/__tests__/scales.test.ts +176 -2
- package/src/annotations/__tests__/compute.test.ts +8 -4
- package/src/charts/bar/__tests__/compute.test.ts +12 -6
- package/src/charts/bar/compute.ts +21 -5
- package/src/charts/column/__tests__/compute.test.ts +14 -7
- package/src/charts/column/compute.ts +21 -6
- package/src/charts/dot/__tests__/compute.test.ts +10 -5
- package/src/charts/dot/compute.ts +10 -4
- package/src/charts/line/__tests__/compute.test.ts +102 -11
- package/src/charts/line/__tests__/curves.test.ts +51 -0
- package/src/charts/line/__tests__/labels.test.ts +2 -1
- package/src/charts/line/__tests__/mark-options.test.ts +175 -0
- package/src/charts/line/area.ts +19 -8
- package/src/charts/line/compute.ts +64 -25
- package/src/charts/line/curves.ts +40 -0
- package/src/charts/pie/__tests__/compute.test.ts +10 -5
- package/src/charts/pie/compute.ts +2 -1
- package/src/charts/rule/index.ts +127 -0
- package/src/charts/scatter/__tests__/compute.test.ts +10 -5
- package/src/charts/scatter/compute.ts +15 -5
- package/src/charts/text/index.ts +92 -0
- package/src/charts/tick/index.ts +84 -0
- package/src/charts/utils.ts +1 -1
- package/src/compile.ts +175 -23
- package/src/compiler/__tests__/compile.test.ts +4 -4
- package/src/compiler/__tests__/normalize.test.ts +4 -4
- package/src/compiler/__tests__/validate.test.ts +25 -26
- package/src/compiler/index.ts +1 -1
- package/src/compiler/normalize.ts +77 -4
- package/src/compiler/types.ts +6 -2
- package/src/compiler/validate.ts +167 -35
- package/src/graphs/__tests__/compile-graph.test.ts +2 -2
- package/src/graphs/compile-graph.ts +2 -2
- package/src/index.ts +17 -1
- package/src/layout/axes.ts +122 -20
- package/src/layout/dimensions.ts +15 -9
- package/src/layout/scales.ts +320 -31
- package/src/legend/compute.ts +9 -6
- package/src/tables/__tests__/compile-table.test.ts +1 -1
- package/src/tooltips/__tests__/compute.test.ts +10 -5
- package/src/tooltips/compute.ts +32 -14
- package/src/transforms/__tests__/bin.test.ts +88 -0
- package/src/transforms/__tests__/calculate.test.ts +146 -0
- package/src/transforms/__tests__/conditional.test.ts +109 -0
- package/src/transforms/__tests__/filter.test.ts +59 -0
- package/src/transforms/__tests__/index.test.ts +93 -0
- package/src/transforms/__tests__/predicates.test.ts +176 -0
- package/src/transforms/__tests__/timeunit.test.ts +129 -0
- package/src/transforms/bin.ts +87 -0
- package/src/transforms/calculate.ts +60 -0
- package/src/transforms/conditional.ts +46 -0
- package/src/transforms/filter.ts +17 -0
- package/src/transforms/index.ts +48 -0
- package/src/transforms/predicates.ts +90 -0
- package/src/transforms/timeunit.ts +88 -0
package/dist/index.js
CHANGED
|
@@ -503,11 +503,79 @@ function computeAnnotations(spec, scales, chartArea, strategy, isDark = false, o
|
|
|
503
503
|
// src/charts/bar/compute.ts
|
|
504
504
|
import { abbreviateNumber, formatNumber } from "@opendata-ai/openchart-core";
|
|
505
505
|
|
|
506
|
+
// src/transforms/predicates.ts
|
|
507
|
+
function isFieldPredicate(pred) {
|
|
508
|
+
return "field" in pred;
|
|
509
|
+
}
|
|
510
|
+
function evaluateFieldPredicate(datum, pred) {
|
|
511
|
+
const value = datum[pred.field];
|
|
512
|
+
if (pred.valid !== void 0) {
|
|
513
|
+
const isValid = value !== null && value !== void 0 && !Number.isNaN(value);
|
|
514
|
+
return pred.valid ? isValid : !isValid;
|
|
515
|
+
}
|
|
516
|
+
if (pred.equal !== void 0) {
|
|
517
|
+
return value === pred.equal;
|
|
518
|
+
}
|
|
519
|
+
const numValue = Number(value);
|
|
520
|
+
if (pred.lt !== void 0) {
|
|
521
|
+
return numValue < pred.lt;
|
|
522
|
+
}
|
|
523
|
+
if (pred.lte !== void 0) {
|
|
524
|
+
return numValue <= pred.lte;
|
|
525
|
+
}
|
|
526
|
+
if (pred.gt !== void 0) {
|
|
527
|
+
return numValue > pred.gt;
|
|
528
|
+
}
|
|
529
|
+
if (pred.gte !== void 0) {
|
|
530
|
+
return numValue >= pred.gte;
|
|
531
|
+
}
|
|
532
|
+
if (pred.range !== void 0) {
|
|
533
|
+
const [min3, max3] = pred.range;
|
|
534
|
+
return numValue >= min3 && numValue <= max3;
|
|
535
|
+
}
|
|
536
|
+
if (pred.oneOf !== void 0) {
|
|
537
|
+
return pred.oneOf.includes(value);
|
|
538
|
+
}
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
function evaluatePredicate(datum, predicate) {
|
|
542
|
+
if (isFieldPredicate(predicate)) {
|
|
543
|
+
return evaluateFieldPredicate(datum, predicate);
|
|
544
|
+
}
|
|
545
|
+
if ("and" in predicate) {
|
|
546
|
+
return predicate.and.every((p) => evaluatePredicate(datum, p));
|
|
547
|
+
}
|
|
548
|
+
if ("or" in predicate) {
|
|
549
|
+
return predicate.or.some((p) => evaluatePredicate(datum, p));
|
|
550
|
+
}
|
|
551
|
+
if ("not" in predicate) {
|
|
552
|
+
return !evaluatePredicate(datum, predicate.not);
|
|
553
|
+
}
|
|
554
|
+
return true;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/transforms/conditional.ts
|
|
558
|
+
function resolveConditionalValue(datum, channelDef) {
|
|
559
|
+
const conditions = Array.isArray(channelDef.condition) ? channelDef.condition : [channelDef.condition];
|
|
560
|
+
for (const cond of conditions) {
|
|
561
|
+
if (evaluatePredicate(datum, cond.test)) {
|
|
562
|
+
if (cond.field !== void 0) {
|
|
563
|
+
return datum[cond.field];
|
|
564
|
+
}
|
|
565
|
+
return cond.value;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return channelDef.value;
|
|
569
|
+
}
|
|
570
|
+
function isConditionalValueDef(def) {
|
|
571
|
+
return def !== null && typeof def === "object" && "condition" in def;
|
|
572
|
+
}
|
|
573
|
+
|
|
506
574
|
// src/charts/utils.ts
|
|
507
575
|
var DEFAULT_COLOR = "#1b7fa3";
|
|
508
576
|
function scaleValue(scale, scaleType, value) {
|
|
509
577
|
if (value == null) return null;
|
|
510
|
-
if (scaleType === "time") {
|
|
578
|
+
if (scaleType === "time" || scaleType === "utc") {
|
|
511
579
|
const date2 = value instanceof Date ? value : new Date(String(value));
|
|
512
580
|
if (Number.isNaN(date2.getTime())) return null;
|
|
513
581
|
return scale(date2);
|
|
@@ -596,8 +664,10 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
|
|
|
596
664
|
}
|
|
597
665
|
const bandwidth = yScale.bandwidth();
|
|
598
666
|
const baseline = xScale(0);
|
|
599
|
-
const
|
|
600
|
-
const
|
|
667
|
+
const colorEnc = encoding.color && "field" in encoding.color ? encoding.color : void 0;
|
|
668
|
+
const conditionalColor = encoding.color && isConditionalValueDef(encoding.color) ? encoding.color : void 0;
|
|
669
|
+
const colorField = colorEnc?.field;
|
|
670
|
+
const isSequentialColor = colorEnc?.type === "quantitative";
|
|
601
671
|
if (!colorField || isSequentialColor) {
|
|
602
672
|
return computeSimpleBars(
|
|
603
673
|
spec.data,
|
|
@@ -608,7 +678,8 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
|
|
|
608
678
|
bandwidth,
|
|
609
679
|
baseline,
|
|
610
680
|
scales,
|
|
611
|
-
isSequentialColor
|
|
681
|
+
isSequentialColor,
|
|
682
|
+
conditionalColor
|
|
612
683
|
);
|
|
613
684
|
}
|
|
614
685
|
return computeStackedBars(
|
|
@@ -657,7 +728,7 @@ function computeStackedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
657
728
|
}
|
|
658
729
|
return marks;
|
|
659
730
|
}
|
|
660
|
-
function computeSimpleBars(data, valueField, categoryField, xScale, yScale, bandwidth, baseline, scales, sequentialColor = false) {
|
|
731
|
+
function computeSimpleBars(data, valueField, categoryField, xScale, yScale, bandwidth, baseline, scales, sequentialColor = false, conditionalColor) {
|
|
661
732
|
const marks = [];
|
|
662
733
|
for (const row of data) {
|
|
663
734
|
const category = String(row[categoryField] ?? "");
|
|
@@ -665,7 +736,16 @@ function computeSimpleBars(data, valueField, categoryField, xScale, yScale, band
|
|
|
665
736
|
if (!Number.isFinite(value)) continue;
|
|
666
737
|
const bandY = yScale(category);
|
|
667
738
|
if (bandY === void 0) continue;
|
|
668
|
-
|
|
739
|
+
let color2;
|
|
740
|
+
if (conditionalColor) {
|
|
741
|
+
color2 = String(
|
|
742
|
+
resolveConditionalValue(row, conditionalColor) ?? getColor(scales, "__default__")
|
|
743
|
+
);
|
|
744
|
+
} else if (sequentialColor) {
|
|
745
|
+
color2 = getSequentialColor(scales, value);
|
|
746
|
+
} else {
|
|
747
|
+
color2 = getColor(scales, "__default__");
|
|
748
|
+
}
|
|
669
749
|
const xPos = value >= 0 ? baseline : xScale(value);
|
|
670
750
|
const barWidth = Math.max(Math.abs(xScale(value) - baseline), MIN_BAR_WIDTH);
|
|
671
751
|
const aria = {
|
|
@@ -794,8 +874,10 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
|
|
|
794
874
|
}
|
|
795
875
|
const bandwidth = xScale.bandwidth();
|
|
796
876
|
const baseline = yScale(0);
|
|
797
|
-
const
|
|
798
|
-
const
|
|
877
|
+
const colorEnc = encoding.color && "field" in encoding.color ? encoding.color : void 0;
|
|
878
|
+
const conditionalColor = encoding.color && isConditionalValueDef(encoding.color) ? encoding.color : void 0;
|
|
879
|
+
const colorField = colorEnc?.field;
|
|
880
|
+
const isSequentialColor = colorEnc?.type === "quantitative";
|
|
799
881
|
if (colorField && !isSequentialColor) {
|
|
800
882
|
const categoryGroups = groupByField(spec.data, xChannel.field);
|
|
801
883
|
const needsStacking = Array.from(categoryGroups.values()).some((rows) => rows.length > 1);
|
|
@@ -833,10 +915,11 @@ function computeColumnMarks(spec, scales, _chartArea, _strategy) {
|
|
|
833
915
|
bandwidth,
|
|
834
916
|
baseline,
|
|
835
917
|
scales,
|
|
836
|
-
isSequentialColor
|
|
918
|
+
isSequentialColor,
|
|
919
|
+
conditionalColor
|
|
837
920
|
);
|
|
838
921
|
}
|
|
839
|
-
function computeSimpleColumns(data, categoryField, valueField, xScale, yScale, bandwidth, baseline, scales, sequentialColor = false) {
|
|
922
|
+
function computeSimpleColumns(data, categoryField, valueField, xScale, yScale, bandwidth, baseline, scales, sequentialColor = false, conditionalColor) {
|
|
840
923
|
const marks = [];
|
|
841
924
|
for (const row of data) {
|
|
842
925
|
const category = String(row[categoryField] ?? "");
|
|
@@ -844,7 +927,16 @@ function computeSimpleColumns(data, categoryField, valueField, xScale, yScale, b
|
|
|
844
927
|
if (!Number.isFinite(value)) continue;
|
|
845
928
|
const bandX = xScale(category);
|
|
846
929
|
if (bandX === void 0) continue;
|
|
847
|
-
|
|
930
|
+
let color2;
|
|
931
|
+
if (conditionalColor) {
|
|
932
|
+
color2 = String(
|
|
933
|
+
resolveConditionalValue(row, conditionalColor) ?? getColor(scales, "__default__")
|
|
934
|
+
);
|
|
935
|
+
} else if (sequentialColor) {
|
|
936
|
+
color2 = getSequentialColor(scales, value);
|
|
937
|
+
} else {
|
|
938
|
+
color2 = getColor(scales, "__default__");
|
|
939
|
+
}
|
|
848
940
|
const yPos = yScale(value);
|
|
849
941
|
const columnHeight = Math.max(Math.abs(baseline - yPos), MIN_COLUMN_HEIGHT);
|
|
850
942
|
const y2 = value >= 0 ? yPos : baseline;
|
|
@@ -1019,7 +1111,9 @@ function computeDotMarks(spec, scales, _chartArea, _strategy) {
|
|
|
1019
1111
|
}
|
|
1020
1112
|
const bandwidth = yScale.bandwidth();
|
|
1021
1113
|
const baseline = xScale(0);
|
|
1022
|
-
const
|
|
1114
|
+
const colorEnc = encoding.color && "field" in encoding.color ? encoding.color : void 0;
|
|
1115
|
+
const isSequentialColor = colorEnc?.type === "quantitative";
|
|
1116
|
+
const colorField = isSequentialColor ? void 0 : colorEnc?.field;
|
|
1023
1117
|
if (colorField) {
|
|
1024
1118
|
return computeDumbbellMarks(
|
|
1025
1119
|
spec.data,
|
|
@@ -1040,7 +1134,8 @@ function computeDotMarks(spec, scales, _chartArea, _strategy) {
|
|
|
1040
1134
|
yScale,
|
|
1041
1135
|
bandwidth,
|
|
1042
1136
|
baseline,
|
|
1043
|
-
scales
|
|
1137
|
+
scales,
|
|
1138
|
+
isSequentialColor
|
|
1044
1139
|
);
|
|
1045
1140
|
}
|
|
1046
1141
|
function computeDumbbellMarks(data, valueField, categoryField, colorField, xScale, yScale, bandwidth, scales) {
|
|
@@ -1100,7 +1195,7 @@ function computeDumbbellMarks(data, valueField, categoryField, colorField, xScal
|
|
|
1100
1195
|
}
|
|
1101
1196
|
return marks;
|
|
1102
1197
|
}
|
|
1103
|
-
function computeLollipopMarks(data, valueField, categoryField, xScale, yScale, bandwidth, baseline, scales) {
|
|
1198
|
+
function computeLollipopMarks(data, valueField, categoryField, xScale, yScale, bandwidth, baseline, scales, isSequentialColor = false) {
|
|
1104
1199
|
const marks = [];
|
|
1105
1200
|
for (const row of data) {
|
|
1106
1201
|
const category = String(row[categoryField] ?? "");
|
|
@@ -1110,7 +1205,7 @@ function computeLollipopMarks(data, valueField, categoryField, xScale, yScale, b
|
|
|
1110
1205
|
if (bandY === void 0) continue;
|
|
1111
1206
|
const cx = xScale(value);
|
|
1112
1207
|
const cy = bandY + bandwidth / 2;
|
|
1113
|
-
const color2 = getColor(scales, "__default__");
|
|
1208
|
+
const color2 = isSequentialColor ? getSequentialColor(scales, value) : getColor(scales, "__default__");
|
|
1114
1209
|
const stemX = Math.min(baseline, cx);
|
|
1115
1210
|
const stemWidth = Math.abs(cx - baseline);
|
|
1116
1211
|
if (stemWidth > 0) {
|
|
@@ -1694,6 +1789,139 @@ function pie_default() {
|
|
|
1694
1789
|
return pie;
|
|
1695
1790
|
}
|
|
1696
1791
|
|
|
1792
|
+
// ../../node_modules/.bun/d3-shape@3.2.0/node_modules/d3-shape/src/curve/basis.js
|
|
1793
|
+
function point(that, x2, y2) {
|
|
1794
|
+
that._context.bezierCurveTo(
|
|
1795
|
+
(2 * that._x0 + that._x1) / 3,
|
|
1796
|
+
(2 * that._y0 + that._y1) / 3,
|
|
1797
|
+
(that._x0 + 2 * that._x1) / 3,
|
|
1798
|
+
(that._y0 + 2 * that._y1) / 3,
|
|
1799
|
+
(that._x0 + 4 * that._x1 + x2) / 6,
|
|
1800
|
+
(that._y0 + 4 * that._y1 + y2) / 6
|
|
1801
|
+
);
|
|
1802
|
+
}
|
|
1803
|
+
function Basis(context) {
|
|
1804
|
+
this._context = context;
|
|
1805
|
+
}
|
|
1806
|
+
Basis.prototype = {
|
|
1807
|
+
areaStart: function() {
|
|
1808
|
+
this._line = 0;
|
|
1809
|
+
},
|
|
1810
|
+
areaEnd: function() {
|
|
1811
|
+
this._line = NaN;
|
|
1812
|
+
},
|
|
1813
|
+
lineStart: function() {
|
|
1814
|
+
this._x0 = this._x1 = this._y0 = this._y1 = NaN;
|
|
1815
|
+
this._point = 0;
|
|
1816
|
+
},
|
|
1817
|
+
lineEnd: function() {
|
|
1818
|
+
switch (this._point) {
|
|
1819
|
+
case 3:
|
|
1820
|
+
point(this, this._x1, this._y1);
|
|
1821
|
+
// falls through
|
|
1822
|
+
case 2:
|
|
1823
|
+
this._context.lineTo(this._x1, this._y1);
|
|
1824
|
+
break;
|
|
1825
|
+
}
|
|
1826
|
+
if (this._line || this._line !== 0 && this._point === 1) this._context.closePath();
|
|
1827
|
+
this._line = 1 - this._line;
|
|
1828
|
+
},
|
|
1829
|
+
point: function(x2, y2) {
|
|
1830
|
+
x2 = +x2, y2 = +y2;
|
|
1831
|
+
switch (this._point) {
|
|
1832
|
+
case 0:
|
|
1833
|
+
this._point = 1;
|
|
1834
|
+
this._line ? this._context.lineTo(x2, y2) : this._context.moveTo(x2, y2);
|
|
1835
|
+
break;
|
|
1836
|
+
case 1:
|
|
1837
|
+
this._point = 2;
|
|
1838
|
+
break;
|
|
1839
|
+
case 2:
|
|
1840
|
+
this._point = 3;
|
|
1841
|
+
this._context.lineTo((5 * this._x0 + this._x1) / 6, (5 * this._y0 + this._y1) / 6);
|
|
1842
|
+
// falls through
|
|
1843
|
+
default:
|
|
1844
|
+
point(this, x2, y2);
|
|
1845
|
+
break;
|
|
1846
|
+
}
|
|
1847
|
+
this._x0 = this._x1, this._x1 = x2;
|
|
1848
|
+
this._y0 = this._y1, this._y1 = y2;
|
|
1849
|
+
}
|
|
1850
|
+
};
|
|
1851
|
+
function basis_default(context) {
|
|
1852
|
+
return new Basis(context);
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
// ../../node_modules/.bun/d3-shape@3.2.0/node_modules/d3-shape/src/curve/cardinal.js
|
|
1856
|
+
function point2(that, x2, y2) {
|
|
1857
|
+
that._context.bezierCurveTo(
|
|
1858
|
+
that._x1 + that._k * (that._x2 - that._x0),
|
|
1859
|
+
that._y1 + that._k * (that._y2 - that._y0),
|
|
1860
|
+
that._x2 + that._k * (that._x1 - x2),
|
|
1861
|
+
that._y2 + that._k * (that._y1 - y2),
|
|
1862
|
+
that._x2,
|
|
1863
|
+
that._y2
|
|
1864
|
+
);
|
|
1865
|
+
}
|
|
1866
|
+
function Cardinal(context, tension) {
|
|
1867
|
+
this._context = context;
|
|
1868
|
+
this._k = (1 - tension) / 6;
|
|
1869
|
+
}
|
|
1870
|
+
Cardinal.prototype = {
|
|
1871
|
+
areaStart: function() {
|
|
1872
|
+
this._line = 0;
|
|
1873
|
+
},
|
|
1874
|
+
areaEnd: function() {
|
|
1875
|
+
this._line = NaN;
|
|
1876
|
+
},
|
|
1877
|
+
lineStart: function() {
|
|
1878
|
+
this._x0 = this._x1 = this._x2 = this._y0 = this._y1 = this._y2 = NaN;
|
|
1879
|
+
this._point = 0;
|
|
1880
|
+
},
|
|
1881
|
+
lineEnd: function() {
|
|
1882
|
+
switch (this._point) {
|
|
1883
|
+
case 2:
|
|
1884
|
+
this._context.lineTo(this._x2, this._y2);
|
|
1885
|
+
break;
|
|
1886
|
+
case 3:
|
|
1887
|
+
point2(this, this._x1, this._y1);
|
|
1888
|
+
break;
|
|
1889
|
+
}
|
|
1890
|
+
if (this._line || this._line !== 0 && this._point === 1) this._context.closePath();
|
|
1891
|
+
this._line = 1 - this._line;
|
|
1892
|
+
},
|
|
1893
|
+
point: function(x2, y2) {
|
|
1894
|
+
x2 = +x2, y2 = +y2;
|
|
1895
|
+
switch (this._point) {
|
|
1896
|
+
case 0:
|
|
1897
|
+
this._point = 1;
|
|
1898
|
+
this._line ? this._context.lineTo(x2, y2) : this._context.moveTo(x2, y2);
|
|
1899
|
+
break;
|
|
1900
|
+
case 1:
|
|
1901
|
+
this._point = 2;
|
|
1902
|
+
this._x1 = x2, this._y1 = y2;
|
|
1903
|
+
break;
|
|
1904
|
+
case 2:
|
|
1905
|
+
this._point = 3;
|
|
1906
|
+
// falls through
|
|
1907
|
+
default:
|
|
1908
|
+
point2(this, x2, y2);
|
|
1909
|
+
break;
|
|
1910
|
+
}
|
|
1911
|
+
this._x0 = this._x1, this._x1 = this._x2, this._x2 = x2;
|
|
1912
|
+
this._y0 = this._y1, this._y1 = this._y2, this._y2 = y2;
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
var cardinal_default = (function custom(tension) {
|
|
1916
|
+
function cardinal(context) {
|
|
1917
|
+
return new Cardinal(context, tension);
|
|
1918
|
+
}
|
|
1919
|
+
cardinal.tension = function(tension2) {
|
|
1920
|
+
return custom(+tension2);
|
|
1921
|
+
};
|
|
1922
|
+
return cardinal;
|
|
1923
|
+
})(0);
|
|
1924
|
+
|
|
1697
1925
|
// ../../node_modules/.bun/d3-shape@3.2.0/node_modules/d3-shape/src/curve/monotone.js
|
|
1698
1926
|
function sign(x2) {
|
|
1699
1927
|
return x2 < 0 ? -1 : 1;
|
|
@@ -1706,7 +1934,7 @@ function slope2(that, t) {
|
|
|
1706
1934
|
var h = that._x1 - that._x0;
|
|
1707
1935
|
return h ? (3 * (that._y1 - that._y0) / h - t) / 2 : t;
|
|
1708
1936
|
}
|
|
1709
|
-
function
|
|
1937
|
+
function point3(that, t02, t12) {
|
|
1710
1938
|
var x0 = that._x0, y0 = that._y0, x1 = that._x1, y1 = that._y1, dx = (x1 - x0) / 3;
|
|
1711
1939
|
that._context.bezierCurveTo(x0 + dx, y0 + dx * t02, x1 - dx, y1 - dx * t12, x1, y1);
|
|
1712
1940
|
}
|
|
@@ -1730,7 +1958,7 @@ MonotoneX.prototype = {
|
|
|
1730
1958
|
this._context.lineTo(this._x1, this._y1);
|
|
1731
1959
|
break;
|
|
1732
1960
|
case 3:
|
|
1733
|
-
|
|
1961
|
+
point3(this, this._t0, slope2(this, this._t0));
|
|
1734
1962
|
break;
|
|
1735
1963
|
}
|
|
1736
1964
|
if (this._line || this._line !== 0 && this._point === 1) this._context.closePath();
|
|
@@ -1750,10 +1978,10 @@ MonotoneX.prototype = {
|
|
|
1750
1978
|
break;
|
|
1751
1979
|
case 2:
|
|
1752
1980
|
this._point = 3;
|
|
1753
|
-
|
|
1981
|
+
point3(this, slope2(this, t12 = slope3(this, x2, y2)), t12);
|
|
1754
1982
|
break;
|
|
1755
1983
|
default:
|
|
1756
|
-
|
|
1984
|
+
point3(this, this._t0, t12 = slope3(this, x2, y2));
|
|
1757
1985
|
break;
|
|
1758
1986
|
}
|
|
1759
1987
|
this._x0 = this._x1, this._x1 = x2;
|
|
@@ -1788,6 +2016,115 @@ function monotoneX(context) {
|
|
|
1788
2016
|
return new MonotoneX(context);
|
|
1789
2017
|
}
|
|
1790
2018
|
|
|
2019
|
+
// ../../node_modules/.bun/d3-shape@3.2.0/node_modules/d3-shape/src/curve/natural.js
|
|
2020
|
+
function Natural(context) {
|
|
2021
|
+
this._context = context;
|
|
2022
|
+
}
|
|
2023
|
+
Natural.prototype = {
|
|
2024
|
+
areaStart: function() {
|
|
2025
|
+
this._line = 0;
|
|
2026
|
+
},
|
|
2027
|
+
areaEnd: function() {
|
|
2028
|
+
this._line = NaN;
|
|
2029
|
+
},
|
|
2030
|
+
lineStart: function() {
|
|
2031
|
+
this._x = [];
|
|
2032
|
+
this._y = [];
|
|
2033
|
+
},
|
|
2034
|
+
lineEnd: function() {
|
|
2035
|
+
var x2 = this._x, y2 = this._y, n = x2.length;
|
|
2036
|
+
if (n) {
|
|
2037
|
+
this._line ? this._context.lineTo(x2[0], y2[0]) : this._context.moveTo(x2[0], y2[0]);
|
|
2038
|
+
if (n === 2) {
|
|
2039
|
+
this._context.lineTo(x2[1], y2[1]);
|
|
2040
|
+
} else {
|
|
2041
|
+
var px = controlPoints(x2), py = controlPoints(y2);
|
|
2042
|
+
for (var i0 = 0, i1 = 1; i1 < n; ++i0, ++i1) {
|
|
2043
|
+
this._context.bezierCurveTo(px[0][i0], py[0][i0], px[1][i0], py[1][i0], x2[i1], y2[i1]);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
if (this._line || this._line !== 0 && n === 1) this._context.closePath();
|
|
2048
|
+
this._line = 1 - this._line;
|
|
2049
|
+
this._x = this._y = null;
|
|
2050
|
+
},
|
|
2051
|
+
point: function(x2, y2) {
|
|
2052
|
+
this._x.push(+x2);
|
|
2053
|
+
this._y.push(+y2);
|
|
2054
|
+
}
|
|
2055
|
+
};
|
|
2056
|
+
function controlPoints(x2) {
|
|
2057
|
+
var i, n = x2.length - 1, m, a = new Array(n), b = new Array(n), r = new Array(n);
|
|
2058
|
+
a[0] = 0, b[0] = 2, r[0] = x2[0] + 2 * x2[1];
|
|
2059
|
+
for (i = 1; i < n - 1; ++i) a[i] = 1, b[i] = 4, r[i] = 4 * x2[i] + 2 * x2[i + 1];
|
|
2060
|
+
a[n - 1] = 2, b[n - 1] = 7, r[n - 1] = 8 * x2[n - 1] + x2[n];
|
|
2061
|
+
for (i = 1; i < n; ++i) m = a[i] / b[i - 1], b[i] -= m, r[i] -= m * r[i - 1];
|
|
2062
|
+
a[n - 1] = r[n - 1] / b[n - 1];
|
|
2063
|
+
for (i = n - 2; i >= 0; --i) a[i] = (r[i] - a[i + 1]) / b[i];
|
|
2064
|
+
b[n - 1] = (x2[n] + a[n - 1]) / 2;
|
|
2065
|
+
for (i = 0; i < n - 1; ++i) b[i] = 2 * x2[i + 1] - a[i + 1];
|
|
2066
|
+
return [a, b];
|
|
2067
|
+
}
|
|
2068
|
+
function natural_default(context) {
|
|
2069
|
+
return new Natural(context);
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
// ../../node_modules/.bun/d3-shape@3.2.0/node_modules/d3-shape/src/curve/step.js
|
|
2073
|
+
function Step(context, t) {
|
|
2074
|
+
this._context = context;
|
|
2075
|
+
this._t = t;
|
|
2076
|
+
}
|
|
2077
|
+
Step.prototype = {
|
|
2078
|
+
areaStart: function() {
|
|
2079
|
+
this._line = 0;
|
|
2080
|
+
},
|
|
2081
|
+
areaEnd: function() {
|
|
2082
|
+
this._line = NaN;
|
|
2083
|
+
},
|
|
2084
|
+
lineStart: function() {
|
|
2085
|
+
this._x = this._y = NaN;
|
|
2086
|
+
this._point = 0;
|
|
2087
|
+
},
|
|
2088
|
+
lineEnd: function() {
|
|
2089
|
+
if (0 < this._t && this._t < 1 && this._point === 2) this._context.lineTo(this._x, this._y);
|
|
2090
|
+
if (this._line || this._line !== 0 && this._point === 1) this._context.closePath();
|
|
2091
|
+
if (this._line >= 0) this._t = 1 - this._t, this._line = 1 - this._line;
|
|
2092
|
+
},
|
|
2093
|
+
point: function(x2, y2) {
|
|
2094
|
+
x2 = +x2, y2 = +y2;
|
|
2095
|
+
switch (this._point) {
|
|
2096
|
+
case 0:
|
|
2097
|
+
this._point = 1;
|
|
2098
|
+
this._line ? this._context.lineTo(x2, y2) : this._context.moveTo(x2, y2);
|
|
2099
|
+
break;
|
|
2100
|
+
case 1:
|
|
2101
|
+
this._point = 2;
|
|
2102
|
+
// falls through
|
|
2103
|
+
default: {
|
|
2104
|
+
if (this._t <= 0) {
|
|
2105
|
+
this._context.lineTo(this._x, y2);
|
|
2106
|
+
this._context.lineTo(x2, y2);
|
|
2107
|
+
} else {
|
|
2108
|
+
var x1 = this._x * (1 - this._t) + x2 * this._t;
|
|
2109
|
+
this._context.lineTo(x1, this._y);
|
|
2110
|
+
this._context.lineTo(x1, y2);
|
|
2111
|
+
}
|
|
2112
|
+
break;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
this._x = x2, this._y = y2;
|
|
2116
|
+
}
|
|
2117
|
+
};
|
|
2118
|
+
function step_default(context) {
|
|
2119
|
+
return new Step(context, 0.5);
|
|
2120
|
+
}
|
|
2121
|
+
function stepBefore(context) {
|
|
2122
|
+
return new Step(context, 0);
|
|
2123
|
+
}
|
|
2124
|
+
function stepAfter(context) {
|
|
2125
|
+
return new Step(context, 1);
|
|
2126
|
+
}
|
|
2127
|
+
|
|
1791
2128
|
// ../../node_modules/.bun/d3-shape@3.2.0/node_modules/d3-shape/src/offset/none.js
|
|
1792
2129
|
function none_default(series, order) {
|
|
1793
2130
|
if (!((n = series.length) > 1)) return;
|
|
@@ -1845,6 +2182,22 @@ function stack_default() {
|
|
|
1845
2182
|
return stack;
|
|
1846
2183
|
}
|
|
1847
2184
|
|
|
2185
|
+
// src/charts/line/curves.ts
|
|
2186
|
+
var CURVE_MAP = {
|
|
2187
|
+
linear: linear_default,
|
|
2188
|
+
monotone: monotoneX,
|
|
2189
|
+
step: step_default,
|
|
2190
|
+
"step-before": stepBefore,
|
|
2191
|
+
"step-after": stepAfter,
|
|
2192
|
+
basis: basis_default,
|
|
2193
|
+
cardinal: cardinal_default,
|
|
2194
|
+
natural: natural_default
|
|
2195
|
+
};
|
|
2196
|
+
function resolveCurve(interpolate) {
|
|
2197
|
+
if (!interpolate) return monotoneX;
|
|
2198
|
+
return CURVE_MAP[interpolate] ?? monotoneX;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
1848
2201
|
// src/charts/line/area.ts
|
|
1849
2202
|
var DEFAULT_FILL_OPACITY = 0.15;
|
|
1850
2203
|
function computeSingleArea(spec, scales, _chartArea) {
|
|
@@ -1855,7 +2208,7 @@ function computeSingleArea(spec, scales, _chartArea) {
|
|
|
1855
2208
|
const yScale = scales.y.scale;
|
|
1856
2209
|
const domain = yScale.domain();
|
|
1857
2210
|
const baselineY = yScale(Math.min(domain[0], domain[1]));
|
|
1858
|
-
const colorField = encoding.color
|
|
2211
|
+
const colorField = encoding.color && "field" in encoding.color ? encoding.color.field : void 0;
|
|
1859
2212
|
const groups = /* @__PURE__ */ new Map();
|
|
1860
2213
|
if (!colorField) {
|
|
1861
2214
|
groups.set("__default__", spec.data);
|
|
@@ -1887,9 +2240,10 @@ function computeSingleArea(spec, scales, _chartArea) {
|
|
|
1887
2240
|
});
|
|
1888
2241
|
}
|
|
1889
2242
|
if (validPoints.length === 0) continue;
|
|
1890
|
-
const
|
|
2243
|
+
const curve = resolveCurve(spec.markDef.interpolate);
|
|
2244
|
+
const areaGenerator = area_default().x((d) => d.x).y0((d) => d.yBottom).y1((d) => d.yTop).curve(curve);
|
|
1891
2245
|
const pathStr = areaGenerator(validPoints) ?? "";
|
|
1892
|
-
const topLineGenerator = line_default().x((d) => d.x).y((d) => d.yTop).curve(
|
|
2246
|
+
const topLineGenerator = line_default().x((d) => d.x).y((d) => d.yTop).curve(curve);
|
|
1893
2247
|
const topPathStr = topLineGenerator(validPoints) ?? "";
|
|
1894
2248
|
const topPoints = validPoints.map((p) => ({ x: p.x, y: p.yTop }));
|
|
1895
2249
|
const bottomPoints = validPoints.map((p) => ({ x: p.x, y: p.yBottom }));
|
|
@@ -1907,6 +2261,7 @@ function computeSingleArea(spec, scales, _chartArea) {
|
|
|
1907
2261
|
strokeWidth: 2,
|
|
1908
2262
|
seriesKey: seriesKey === "__default__" ? void 0 : seriesKey,
|
|
1909
2263
|
data: validPoints.map((p) => p.row),
|
|
2264
|
+
dataPoints: validPoints.map((p) => ({ x: p.x, y: p.yTop, datum: p.row })),
|
|
1910
2265
|
aria
|
|
1911
2266
|
});
|
|
1912
2267
|
}
|
|
@@ -1916,7 +2271,7 @@ function computeStackedArea(spec, scales, chartArea) {
|
|
|
1916
2271
|
const encoding = spec.encoding;
|
|
1917
2272
|
const xChannel = encoding.x;
|
|
1918
2273
|
const yChannel = encoding.y;
|
|
1919
|
-
const colorField = encoding.color
|
|
2274
|
+
const colorField = encoding.color && "field" in encoding.color ? encoding.color.field : void 0;
|
|
1920
2275
|
if (!xChannel || !yChannel || !scales.x || !scales.y || !colorField) {
|
|
1921
2276
|
return computeSingleArea(spec, scales, chartArea);
|
|
1922
2277
|
}
|
|
@@ -1970,9 +2325,10 @@ function computeStackedArea(spec, scales, chartArea) {
|
|
|
1970
2325
|
validPoints.push({ x: xVal, yTop, yBottom });
|
|
1971
2326
|
}
|
|
1972
2327
|
if (validPoints.length === 0) continue;
|
|
1973
|
-
const
|
|
2328
|
+
const stackCurve = resolveCurve(spec.markDef.interpolate);
|
|
2329
|
+
const areaGenerator = area_default().x((p) => p.x).y0((p) => p.yBottom).y1((p) => p.yTop).curve(stackCurve);
|
|
1974
2330
|
const pathStr = areaGenerator(validPoints) ?? "";
|
|
1975
|
-
const topLineGenerator = line_default().x((p) => p.x).y((p) => p.yTop).curve(
|
|
2331
|
+
const topLineGenerator = line_default().x((p) => p.x).y((p) => p.yTop).curve(stackCurve);
|
|
1976
2332
|
const topPathStr = topLineGenerator(validPoints) ?? "";
|
|
1977
2333
|
const topPoints = validPoints.map((p) => ({ x: p.x, y: p.yTop }));
|
|
1978
2334
|
const bottomPoints = validPoints.map((p) => ({ x: p.x, y: p.yBottom }));
|
|
@@ -1995,6 +2351,11 @@ function computeStackedArea(spec, scales, chartArea) {
|
|
|
1995
2351
|
const xStr = String(d.data.__x__);
|
|
1996
2352
|
return rowsByXSeries.get(`${xStr}::${seriesKey}`) ?? d.data;
|
|
1997
2353
|
}),
|
|
2354
|
+
dataPoints: validPoints.map((p, idx) => {
|
|
2355
|
+
const xStr = String(layer[idx]?.data.__x__);
|
|
2356
|
+
const datum = rowsByXSeries.get(`${xStr}::${seriesKey}`) ?? layer[idx]?.data ?? {};
|
|
2357
|
+
return { x: p.x, y: p.yTop, datum };
|
|
2358
|
+
}),
|
|
1998
2359
|
aria
|
|
1999
2360
|
});
|
|
2000
2361
|
}
|
|
@@ -2019,11 +2380,14 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
|
|
|
2019
2380
|
if (!xChannel || !yChannel || !scales.x || !scales.y) {
|
|
2020
2381
|
return [];
|
|
2021
2382
|
}
|
|
2022
|
-
const
|
|
2383
|
+
const colorEnc = encoding.color && "field" in encoding.color ? encoding.color : void 0;
|
|
2384
|
+
const isSequentialColor = colorEnc?.type === "quantitative";
|
|
2385
|
+
const colorField = isSequentialColor ? void 0 : colorEnc?.field;
|
|
2386
|
+
const sequentialColorField = isSequentialColor ? colorEnc.field : void 0;
|
|
2023
2387
|
const groups = groupByField(spec.data, colorField);
|
|
2024
2388
|
const marks = [];
|
|
2025
2389
|
for (const [seriesKey, rows] of groups) {
|
|
2026
|
-
const color2 = getColor(scales, seriesKey);
|
|
2390
|
+
const color2 = isSequentialColor ? getSequentialColor(scales, _getMidValue(rows, sequentialColorField)) : getColor(scales, seriesKey);
|
|
2027
2391
|
const sortedRows = sortByField(rows, xChannel.field);
|
|
2028
2392
|
const pointsWithData = [];
|
|
2029
2393
|
const segments = [];
|
|
@@ -2038,14 +2402,15 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
|
|
|
2038
2402
|
}
|
|
2039
2403
|
continue;
|
|
2040
2404
|
}
|
|
2041
|
-
const
|
|
2042
|
-
currentSegment.push(
|
|
2043
|
-
pointsWithData.push({ ...
|
|
2405
|
+
const point5 = { x: xVal, y: yVal };
|
|
2406
|
+
currentSegment.push(point5);
|
|
2407
|
+
pointsWithData.push({ ...point5, row });
|
|
2044
2408
|
}
|
|
2045
2409
|
if (currentSegment.length > 0) {
|
|
2046
2410
|
segments.push(currentSegment);
|
|
2047
2411
|
}
|
|
2048
|
-
const
|
|
2412
|
+
const curve = resolveCurve(spec.markDef.interpolate);
|
|
2413
|
+
const lineGenerator = line_default().x((d) => d.x).y((d) => d.y).curve(curve);
|
|
2049
2414
|
const allPoints = [];
|
|
2050
2415
|
const pathParts = [];
|
|
2051
2416
|
for (const segment of segments) {
|
|
@@ -2077,31 +2442,49 @@ function computeLineMarks(spec, scales, _chartArea, _strategy) {
|
|
|
2077
2442
|
opacity: styleOverride?.opacity,
|
|
2078
2443
|
seriesKey: seriesStyleKey,
|
|
2079
2444
|
data: pointsWithData.map((p) => p.row),
|
|
2445
|
+
dataPoints: pointsWithData.map((p) => ({ x: p.x, y: p.y, datum: p.row })),
|
|
2080
2446
|
aria
|
|
2081
2447
|
};
|
|
2082
2448
|
marks.push(lineMark);
|
|
2083
|
-
const
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
const
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
data: p.row,
|
|
2096
|
-
aria: {
|
|
2097
|
-
label: `Data point: ${xChannel.field}=${String(p.row[xChannel.field])}, ${yChannel.field}=${String(p.row[yChannel.field])}`
|
|
2449
|
+
const markPoint = spec.markDef.point;
|
|
2450
|
+
const showPoints = markPoint === true || markPoint === "transparent" || isSequentialColor;
|
|
2451
|
+
if (showPoints) {
|
|
2452
|
+
const isTransparent = markPoint === "transparent";
|
|
2453
|
+
const seriesShowPoints = styleOverride?.showPoints !== false;
|
|
2454
|
+
for (let i = 0; i < pointsWithData.length; i++) {
|
|
2455
|
+
const p = pointsWithData[i];
|
|
2456
|
+
const visible = seriesShowPoints && !isTransparent;
|
|
2457
|
+
let pointColor = color2;
|
|
2458
|
+
if (isSequentialColor) {
|
|
2459
|
+
const val = Number(p.row[sequentialColorField]);
|
|
2460
|
+
pointColor = Number.isFinite(val) ? getSequentialColor(scales, val) : color2;
|
|
2098
2461
|
}
|
|
2099
|
-
|
|
2100
|
-
|
|
2462
|
+
const pointMark = {
|
|
2463
|
+
type: "point",
|
|
2464
|
+
cx: p.x,
|
|
2465
|
+
cy: p.y,
|
|
2466
|
+
r: visible ? DEFAULT_POINT_RADIUS : 0,
|
|
2467
|
+
fill: pointColor,
|
|
2468
|
+
stroke: visible ? "#ffffff" : "transparent",
|
|
2469
|
+
strokeWidth: visible ? 1.5 : 0,
|
|
2470
|
+
fillOpacity: isTransparent ? 0 : 1,
|
|
2471
|
+
data: p.row,
|
|
2472
|
+
aria: {
|
|
2473
|
+
label: `Data point: ${xChannel.field}=${String(p.row[xChannel.field])}, ${yChannel.field}=${String(p.row[yChannel.field])}`
|
|
2474
|
+
}
|
|
2475
|
+
};
|
|
2476
|
+
marks.push(pointMark);
|
|
2477
|
+
}
|
|
2101
2478
|
}
|
|
2102
2479
|
}
|
|
2103
2480
|
return marks;
|
|
2104
2481
|
}
|
|
2482
|
+
function _getMidValue(rows, field) {
|
|
2483
|
+
const values = rows.map((r) => Number(r[field])).filter(Number.isFinite);
|
|
2484
|
+
if (values.length === 0) return 0;
|
|
2485
|
+
const sorted = values.sort((a, b) => a - b);
|
|
2486
|
+
return sorted[Math.floor(sorted.length / 2)];
|
|
2487
|
+
}
|
|
2105
2488
|
|
|
2106
2489
|
// src/charts/line/labels.ts
|
|
2107
2490
|
import { estimateTextWidth as estimateTextWidth5, resolveCollisions as resolveCollisions4 } from "@opendata-ai/openchart-core";
|
|
@@ -2207,13 +2590,13 @@ var DEFAULT_PALETTE = [
|
|
|
2207
2590
|
"#a88f22",
|
|
2208
2591
|
"#858078"
|
|
2209
2592
|
];
|
|
2210
|
-
function groupSmallSlices(slices,
|
|
2593
|
+
function groupSmallSlices(slices, threshold2) {
|
|
2211
2594
|
const total = slices.reduce((sum, s) => sum + s.value, 0);
|
|
2212
2595
|
if (total === 0) return slices;
|
|
2213
2596
|
const big = [];
|
|
2214
2597
|
let otherValue = 0;
|
|
2215
2598
|
for (const slice2 of slices) {
|
|
2216
|
-
if (slice2.value / total <
|
|
2599
|
+
if (slice2.value / total < threshold2) {
|
|
2217
2600
|
otherValue += slice2.value;
|
|
2218
2601
|
} else {
|
|
2219
2602
|
big.push(slice2);
|
|
@@ -2231,7 +2614,7 @@ function groupSmallSlices(slices, threshold) {
|
|
|
2231
2614
|
function computePieMarks(spec, scales, chartArea, _strategy, isDonut = false) {
|
|
2232
2615
|
const encoding = spec.encoding;
|
|
2233
2616
|
const valueChannel = encoding.y ?? encoding.x;
|
|
2234
|
-
const categoryField = encoding.color
|
|
2617
|
+
const categoryField = encoding.color && "field" in encoding.color ? encoding.color.field : void 0;
|
|
2235
2618
|
if (!valueChannel) return [];
|
|
2236
2619
|
let slices = [];
|
|
2237
2620
|
if (categoryField) {
|
|
@@ -2419,6 +2802,75 @@ function clearRenderers() {
|
|
|
2419
2802
|
renderers.clear();
|
|
2420
2803
|
}
|
|
2421
2804
|
|
|
2805
|
+
// src/charts/rule/index.ts
|
|
2806
|
+
function computeRuleMarks(spec, scales, chartArea) {
|
|
2807
|
+
const encoding = spec.encoding;
|
|
2808
|
+
const xChannel = encoding.x;
|
|
2809
|
+
const yChannel = encoding.y;
|
|
2810
|
+
const x2Channel = encoding.x2;
|
|
2811
|
+
const y2Channel = encoding.y2;
|
|
2812
|
+
const colorEncoding = encoding.color && "field" in encoding.color ? encoding.color : void 0;
|
|
2813
|
+
const colorField = colorEncoding?.field;
|
|
2814
|
+
const marks = [];
|
|
2815
|
+
for (const row of spec.data) {
|
|
2816
|
+
let x1 = chartArea.x;
|
|
2817
|
+
let y1 = chartArea.y;
|
|
2818
|
+
let x2 = chartArea.x + chartArea.width;
|
|
2819
|
+
let y2 = chartArea.y + chartArea.height;
|
|
2820
|
+
if (xChannel && scales.x) {
|
|
2821
|
+
const xVal = scaleValue(scales.x.scale, scales.x.type, row[xChannel.field]);
|
|
2822
|
+
if (xVal == null) continue;
|
|
2823
|
+
x1 = xVal;
|
|
2824
|
+
x2 = xVal;
|
|
2825
|
+
}
|
|
2826
|
+
if (yChannel && scales.y) {
|
|
2827
|
+
const yVal = scaleValue(scales.y.scale, scales.y.type, row[yChannel.field]);
|
|
2828
|
+
if (yVal == null) continue;
|
|
2829
|
+
y1 = yVal;
|
|
2830
|
+
y2 = yVal;
|
|
2831
|
+
}
|
|
2832
|
+
if (xChannel && !yChannel) {
|
|
2833
|
+
y1 = chartArea.y;
|
|
2834
|
+
y2 = chartArea.y + chartArea.height;
|
|
2835
|
+
}
|
|
2836
|
+
if (yChannel && !xChannel) {
|
|
2837
|
+
x1 = chartArea.x;
|
|
2838
|
+
x2 = chartArea.x + chartArea.width;
|
|
2839
|
+
}
|
|
2840
|
+
if (x2Channel && scales.x) {
|
|
2841
|
+
const x2Val = scaleValue(scales.x.scale, scales.x.type, row[x2Channel.field]);
|
|
2842
|
+
if (x2Val != null) x2 = x2Val;
|
|
2843
|
+
}
|
|
2844
|
+
if (y2Channel && scales.y) {
|
|
2845
|
+
const y2Val = scaleValue(scales.y.scale, scales.y.type, row[y2Channel.field]);
|
|
2846
|
+
if (y2Val != null) y2 = y2Val;
|
|
2847
|
+
}
|
|
2848
|
+
const color2 = colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__");
|
|
2849
|
+
const strokeDashEncoding = encoding.strokeDash && "field" in encoding.strokeDash ? encoding.strokeDash : void 0;
|
|
2850
|
+
const strokeDasharray = strokeDashEncoding ? String(row[strokeDashEncoding.field] ?? "") : void 0;
|
|
2851
|
+
const aria = {
|
|
2852
|
+
label: `Rule from (${Math.round(x1)}, ${Math.round(y1)}) to (${Math.round(x2)}, ${Math.round(y2)})`
|
|
2853
|
+
};
|
|
2854
|
+
marks.push({
|
|
2855
|
+
type: "rule",
|
|
2856
|
+
x1,
|
|
2857
|
+
y1,
|
|
2858
|
+
x2,
|
|
2859
|
+
y2,
|
|
2860
|
+
stroke: color2,
|
|
2861
|
+
strokeWidth: 1,
|
|
2862
|
+
strokeDasharray: strokeDasharray || void 0,
|
|
2863
|
+
opacity: encoding.opacity && "field" in encoding.opacity ? Math.max(0, Math.min(1, Number(row[encoding.opacity.field]) || 1)) : void 0,
|
|
2864
|
+
data: row,
|
|
2865
|
+
aria
|
|
2866
|
+
});
|
|
2867
|
+
}
|
|
2868
|
+
return marks;
|
|
2869
|
+
}
|
|
2870
|
+
var ruleRenderer = (spec, scales, chartArea, _strategy, _theme) => {
|
|
2871
|
+
return computeRuleMarks(spec, scales, chartArea);
|
|
2872
|
+
};
|
|
2873
|
+
|
|
2422
2874
|
// ../../node_modules/.bun/d3-array@3.2.4/node_modules/d3-array/src/ascending.js
|
|
2423
2875
|
function ascending(a, b) {
|
|
2424
2876
|
return a == null || b == null ? NaN : a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
|
|
@@ -2648,6 +3100,15 @@ function min2(values, valueof) {
|
|
|
2648
3100
|
return min3;
|
|
2649
3101
|
}
|
|
2650
3102
|
|
|
3103
|
+
// ../../node_modules/.bun/d3-array@3.2.4/node_modules/d3-array/src/quantile.js
|
|
3104
|
+
function quantileSorted(values, p, valueof = number) {
|
|
3105
|
+
if (!(n = values.length) || isNaN(p = +p)) return;
|
|
3106
|
+
if (p <= 0 || n < 2) return +valueof(values[0], 0, values);
|
|
3107
|
+
if (p >= 1) return +valueof(values[n - 1], n - 1, values);
|
|
3108
|
+
var n, i = (n - 1) * p, i0 = Math.floor(i), value0 = +valueof(values[i0], i0, values), value1 = +valueof(values[i0 + 1], i0 + 1, values);
|
|
3109
|
+
return value0 + (value1 - value0) * (i - i0);
|
|
3110
|
+
}
|
|
3111
|
+
|
|
2651
3112
|
// ../../node_modules/.bun/d3-array@3.2.4/node_modules/d3-array/src/range.js
|
|
2652
3113
|
function range(start, stop, step) {
|
|
2653
3114
|
start = +start, stop = +stop, step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step;
|
|
@@ -2786,7 +3247,7 @@ function pointish(scale) {
|
|
|
2786
3247
|
};
|
|
2787
3248
|
return scale;
|
|
2788
3249
|
}
|
|
2789
|
-
function
|
|
3250
|
+
function point4() {
|
|
2790
3251
|
return pointish(band.apply(null, arguments).paddingInner(1));
|
|
2791
3252
|
}
|
|
2792
3253
|
|
|
@@ -3143,7 +3604,7 @@ function basis(t12, v0, v1, v2, v3) {
|
|
|
3143
3604
|
var t2 = t12 * t12, t3 = t2 * t12;
|
|
3144
3605
|
return ((1 - 3 * t12 + 3 * t2 - t3) * v0 + (4 - 6 * t2 + 3 * t3) * v1 + (1 + 3 * t12 + 3 * t2 - 3 * t3) * v2 + t3 * v3) / 6;
|
|
3145
3606
|
}
|
|
3146
|
-
function
|
|
3607
|
+
function basis_default2(values) {
|
|
3147
3608
|
var n = values.length - 1;
|
|
3148
3609
|
return function(t) {
|
|
3149
3610
|
var i = t <= 0 ? t = 0 : t >= 1 ? (t = 1, n - 1) : Math.floor(t * n), v1 = values[i], v2 = values[i + 1], v0 = i > 0 ? values[i - 1] : 2 * v1 - v2, v3 = i < n - 1 ? values[i + 2] : 2 * v2 - v1;
|
|
@@ -3221,7 +3682,7 @@ function rgbSpline(spline) {
|
|
|
3221
3682
|
};
|
|
3222
3683
|
};
|
|
3223
3684
|
}
|
|
3224
|
-
var rgbBasis = rgbSpline(
|
|
3685
|
+
var rgbBasis = rgbSpline(basis_default2);
|
|
3225
3686
|
var rgbBasisClosed = rgbSpline(basisClosed_default);
|
|
3226
3687
|
|
|
3227
3688
|
// ../../node_modules/.bun/d3-interpolate@3.0.1/node_modules/d3-interpolate/src/numberArray.js
|
|
@@ -3882,6 +4343,32 @@ function log() {
|
|
|
3882
4343
|
return scale;
|
|
3883
4344
|
}
|
|
3884
4345
|
|
|
4346
|
+
// ../../node_modules/.bun/d3-scale@4.0.2/node_modules/d3-scale/src/symlog.js
|
|
4347
|
+
function transformSymlog(c) {
|
|
4348
|
+
return function(x2) {
|
|
4349
|
+
return Math.sign(x2) * Math.log1p(Math.abs(x2 / c));
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
function transformSymexp(c) {
|
|
4353
|
+
return function(x2) {
|
|
4354
|
+
return Math.sign(x2) * Math.expm1(Math.abs(x2)) * c;
|
|
4355
|
+
};
|
|
4356
|
+
}
|
|
4357
|
+
function symlogish(transform) {
|
|
4358
|
+
var c = 1, scale = transform(transformSymlog(c), transformSymexp(c));
|
|
4359
|
+
scale.constant = function(_) {
|
|
4360
|
+
return arguments.length ? transform(transformSymlog(c = +_), transformSymexp(c)) : c;
|
|
4361
|
+
};
|
|
4362
|
+
return linearish(scale);
|
|
4363
|
+
}
|
|
4364
|
+
function symlog() {
|
|
4365
|
+
var scale = symlogish(transformer());
|
|
4366
|
+
scale.copy = function() {
|
|
4367
|
+
return copy(scale, symlog()).constant(scale.constant());
|
|
4368
|
+
};
|
|
4369
|
+
return initRange.apply(scale, arguments);
|
|
4370
|
+
}
|
|
4371
|
+
|
|
3885
4372
|
// ../../node_modules/.bun/d3-scale@4.0.2/node_modules/d3-scale/src/pow.js
|
|
3886
4373
|
function transformPow(exponent) {
|
|
3887
4374
|
return function(x2) {
|
|
@@ -3902,18 +4389,118 @@ function powish(transform) {
|
|
|
3902
4389
|
scale.exponent = function(_) {
|
|
3903
4390
|
return arguments.length ? (exponent = +_, rescale()) : exponent;
|
|
3904
4391
|
};
|
|
3905
|
-
return linearish(scale);
|
|
3906
|
-
}
|
|
3907
|
-
function pow() {
|
|
3908
|
-
var scale = powish(transformer());
|
|
4392
|
+
return linearish(scale);
|
|
4393
|
+
}
|
|
4394
|
+
function pow() {
|
|
4395
|
+
var scale = powish(transformer());
|
|
4396
|
+
scale.copy = function() {
|
|
4397
|
+
return copy(scale, pow()).exponent(scale.exponent());
|
|
4398
|
+
};
|
|
4399
|
+
initRange.apply(scale, arguments);
|
|
4400
|
+
return scale;
|
|
4401
|
+
}
|
|
4402
|
+
function sqrt2() {
|
|
4403
|
+
return pow.apply(null, arguments).exponent(0.5);
|
|
4404
|
+
}
|
|
4405
|
+
|
|
4406
|
+
// ../../node_modules/.bun/d3-scale@4.0.2/node_modules/d3-scale/src/quantile.js
|
|
4407
|
+
function quantile2() {
|
|
4408
|
+
var domain = [], range2 = [], thresholds = [], unknown;
|
|
4409
|
+
function rescale() {
|
|
4410
|
+
var i = 0, n = Math.max(1, range2.length);
|
|
4411
|
+
thresholds = new Array(n - 1);
|
|
4412
|
+
while (++i < n) thresholds[i - 1] = quantileSorted(domain, i / n);
|
|
4413
|
+
return scale;
|
|
4414
|
+
}
|
|
4415
|
+
function scale(x2) {
|
|
4416
|
+
return x2 == null || isNaN(x2 = +x2) ? unknown : range2[bisect_default(thresholds, x2)];
|
|
4417
|
+
}
|
|
4418
|
+
scale.invertExtent = function(y2) {
|
|
4419
|
+
var i = range2.indexOf(y2);
|
|
4420
|
+
return i < 0 ? [NaN, NaN] : [
|
|
4421
|
+
i > 0 ? thresholds[i - 1] : domain[0],
|
|
4422
|
+
i < thresholds.length ? thresholds[i] : domain[domain.length - 1]
|
|
4423
|
+
];
|
|
4424
|
+
};
|
|
4425
|
+
scale.domain = function(_) {
|
|
4426
|
+
if (!arguments.length) return domain.slice();
|
|
4427
|
+
domain = [];
|
|
4428
|
+
for (let d of _) if (d != null && !isNaN(d = +d)) domain.push(d);
|
|
4429
|
+
domain.sort(ascending);
|
|
4430
|
+
return rescale();
|
|
4431
|
+
};
|
|
4432
|
+
scale.range = function(_) {
|
|
4433
|
+
return arguments.length ? (range2 = Array.from(_), rescale()) : range2.slice();
|
|
4434
|
+
};
|
|
4435
|
+
scale.unknown = function(_) {
|
|
4436
|
+
return arguments.length ? (unknown = _, scale) : unknown;
|
|
4437
|
+
};
|
|
4438
|
+
scale.quantiles = function() {
|
|
4439
|
+
return thresholds.slice();
|
|
4440
|
+
};
|
|
4441
|
+
scale.copy = function() {
|
|
4442
|
+
return quantile2().domain(domain).range(range2).unknown(unknown);
|
|
4443
|
+
};
|
|
4444
|
+
return initRange.apply(scale, arguments);
|
|
4445
|
+
}
|
|
4446
|
+
|
|
4447
|
+
// ../../node_modules/.bun/d3-scale@4.0.2/node_modules/d3-scale/src/quantize.js
|
|
4448
|
+
function quantize() {
|
|
4449
|
+
var x0 = 0, x1 = 1, n = 1, domain = [0.5], range2 = [0, 1], unknown;
|
|
4450
|
+
function scale(x2) {
|
|
4451
|
+
return x2 != null && x2 <= x2 ? range2[bisect_default(domain, x2, 0, n)] : unknown;
|
|
4452
|
+
}
|
|
4453
|
+
function rescale() {
|
|
4454
|
+
var i = -1;
|
|
4455
|
+
domain = new Array(n);
|
|
4456
|
+
while (++i < n) domain[i] = ((i + 1) * x1 - (i - n) * x0) / (n + 1);
|
|
4457
|
+
return scale;
|
|
4458
|
+
}
|
|
4459
|
+
scale.domain = function(_) {
|
|
4460
|
+
return arguments.length ? ([x0, x1] = _, x0 = +x0, x1 = +x1, rescale()) : [x0, x1];
|
|
4461
|
+
};
|
|
4462
|
+
scale.range = function(_) {
|
|
4463
|
+
return arguments.length ? (n = (range2 = Array.from(_)).length - 1, rescale()) : range2.slice();
|
|
4464
|
+
};
|
|
4465
|
+
scale.invertExtent = function(y2) {
|
|
4466
|
+
var i = range2.indexOf(y2);
|
|
4467
|
+
return i < 0 ? [NaN, NaN] : i < 1 ? [x0, domain[0]] : i >= n ? [domain[n - 1], x1] : [domain[i - 1], domain[i]];
|
|
4468
|
+
};
|
|
4469
|
+
scale.unknown = function(_) {
|
|
4470
|
+
return arguments.length ? (unknown = _, scale) : scale;
|
|
4471
|
+
};
|
|
4472
|
+
scale.thresholds = function() {
|
|
4473
|
+
return domain.slice();
|
|
4474
|
+
};
|
|
4475
|
+
scale.copy = function() {
|
|
4476
|
+
return quantize().domain([x0, x1]).range(range2).unknown(unknown);
|
|
4477
|
+
};
|
|
4478
|
+
return initRange.apply(linearish(scale), arguments);
|
|
4479
|
+
}
|
|
4480
|
+
|
|
4481
|
+
// ../../node_modules/.bun/d3-scale@4.0.2/node_modules/d3-scale/src/threshold.js
|
|
4482
|
+
function threshold() {
|
|
4483
|
+
var domain = [0.5], range2 = [0, 1], unknown, n = 1;
|
|
4484
|
+
function scale(x2) {
|
|
4485
|
+
return x2 != null && x2 <= x2 ? range2[bisect_default(domain, x2, 0, n)] : unknown;
|
|
4486
|
+
}
|
|
4487
|
+
scale.domain = function(_) {
|
|
4488
|
+
return arguments.length ? (domain = Array.from(_), n = Math.min(domain.length, range2.length - 1), scale) : domain.slice();
|
|
4489
|
+
};
|
|
4490
|
+
scale.range = function(_) {
|
|
4491
|
+
return arguments.length ? (range2 = Array.from(_), n = Math.min(domain.length, range2.length - 1), scale) : range2.slice();
|
|
4492
|
+
};
|
|
4493
|
+
scale.invertExtent = function(y2) {
|
|
4494
|
+
var i = range2.indexOf(y2);
|
|
4495
|
+
return [domain[i - 1], domain[i]];
|
|
4496
|
+
};
|
|
4497
|
+
scale.unknown = function(_) {
|
|
4498
|
+
return arguments.length ? (unknown = _, scale) : unknown;
|
|
4499
|
+
};
|
|
3909
4500
|
scale.copy = function() {
|
|
3910
|
-
return
|
|
4501
|
+
return threshold().domain(domain).range(range2).unknown(unknown);
|
|
3911
4502
|
};
|
|
3912
|
-
initRange.apply(scale, arguments);
|
|
3913
|
-
return scale;
|
|
3914
|
-
}
|
|
3915
|
-
function sqrt2() {
|
|
3916
|
-
return pow.apply(null, arguments).exponent(0.5);
|
|
4503
|
+
return initRange.apply(scale, arguments);
|
|
3917
4504
|
}
|
|
3918
4505
|
|
|
3919
4506
|
// ../../node_modules/.bun/d3-time@3.1.0/node_modules/d3-time/src/interval.js
|
|
@@ -4856,6 +5443,11 @@ function time() {
|
|
|
4856
5443
|
return initRange.apply(calendar(timeTicks, timeTickInterval, timeYear, timeMonth, timeSunday, timeDay, timeHour, timeMinute, second, timeFormat).domain([new Date(2e3, 0, 1), new Date(2e3, 0, 2)]), arguments);
|
|
4857
5444
|
}
|
|
4858
5445
|
|
|
5446
|
+
// ../../node_modules/.bun/d3-scale@4.0.2/node_modules/d3-scale/src/utcTime.js
|
|
5447
|
+
function utcTime() {
|
|
5448
|
+
return initRange.apply(calendar(utcTicks, utcTickInterval, utcYear, utcMonth, utcSunday, utcDay, utcHour, utcMinute, second, utcFormat).domain([Date.UTC(2e3, 0, 1), Date.UTC(2e3, 0, 2)]), arguments);
|
|
5449
|
+
}
|
|
5450
|
+
|
|
4859
5451
|
// ../../node_modules/.bun/d3-scale@4.0.2/node_modules/d3-scale/src/sequential.js
|
|
4860
5452
|
function transformer2() {
|
|
4861
5453
|
var x0 = 0, x1 = 1, t02, t12, k10, transform, interpolator = identity, clamp = false, unknown;
|
|
@@ -4911,8 +5503,10 @@ function computeScatterMarks(spec, scales, _chartArea, _strategy) {
|
|
|
4911
5503
|
}
|
|
4912
5504
|
const xScale = scales.x.scale;
|
|
4913
5505
|
const yScale = scales.y.scale;
|
|
4914
|
-
const
|
|
4915
|
-
const
|
|
5506
|
+
const colorEnc = encoding.color && "field" in encoding.color ? encoding.color : void 0;
|
|
5507
|
+
const isSequentialColor = colorEnc?.type === "quantitative";
|
|
5508
|
+
const colorField = colorEnc?.field;
|
|
5509
|
+
const sizeField = encoding.size && "field" in encoding.size ? encoding.size.field : void 0;
|
|
4916
5510
|
let sizeScale;
|
|
4917
5511
|
if (sizeField) {
|
|
4918
5512
|
const sizeValues = spec.data.map((d) => Number(d[sizeField])).filter((v) => Number.isFinite(v));
|
|
@@ -4927,8 +5521,14 @@ function computeScatterMarks(spec, scales, _chartArea, _strategy) {
|
|
|
4927
5521
|
if (!Number.isFinite(xVal) || !Number.isFinite(yVal)) continue;
|
|
4928
5522
|
const cx = xScale(xVal);
|
|
4929
5523
|
const cy = yScale(yVal);
|
|
4930
|
-
const category = colorField ? String(row[colorField] ?? "") : void 0;
|
|
4931
|
-
|
|
5524
|
+
const category = colorField && !isSequentialColor ? String(row[colorField] ?? "") : void 0;
|
|
5525
|
+
let color2;
|
|
5526
|
+
if (isSequentialColor && colorField) {
|
|
5527
|
+
const val = Number(row[colorField]);
|
|
5528
|
+
color2 = Number.isFinite(val) ? getSequentialColor(scales, val) : getColor(scales, "__default__");
|
|
5529
|
+
} else {
|
|
5530
|
+
color2 = getColor(scales, category ?? "__default__");
|
|
5531
|
+
}
|
|
4932
5532
|
let radius = DEFAULT_POINT_RADIUS2;
|
|
4933
5533
|
if (sizeScale && sizeField) {
|
|
4934
5534
|
const sizeVal = Number(row[sizeField]);
|
|
@@ -5025,8 +5625,104 @@ var scatterRenderer = (spec, scales, chartArea, strategy, _theme) => {
|
|
|
5025
5625
|
return marks;
|
|
5026
5626
|
};
|
|
5027
5627
|
|
|
5628
|
+
// src/charts/text/index.ts
|
|
5629
|
+
function computeTextMarks(spec, scales) {
|
|
5630
|
+
const encoding = spec.encoding;
|
|
5631
|
+
const xChannel = encoding.x;
|
|
5632
|
+
const yChannel = encoding.y;
|
|
5633
|
+
const textChannel = encoding.text;
|
|
5634
|
+
if (!textChannel || !("field" in textChannel)) return [];
|
|
5635
|
+
const marks = [];
|
|
5636
|
+
const colorEncoding = encoding.color && "field" in encoding.color ? encoding.color : void 0;
|
|
5637
|
+
const colorField = colorEncoding?.field;
|
|
5638
|
+
const sizeEncoding = encoding.size && "field" in encoding.size ? encoding.size : void 0;
|
|
5639
|
+
for (const row of spec.data) {
|
|
5640
|
+
let x2 = 0;
|
|
5641
|
+
if (xChannel && scales.x) {
|
|
5642
|
+
const xVal = scaleValue(scales.x.scale, scales.x.type, row[xChannel.field]);
|
|
5643
|
+
if (xVal == null) continue;
|
|
5644
|
+
x2 = xVal;
|
|
5645
|
+
}
|
|
5646
|
+
let y2 = 0;
|
|
5647
|
+
if (yChannel && scales.y) {
|
|
5648
|
+
const yVal = scaleValue(scales.y.scale, scales.y.type, row[yChannel.field]);
|
|
5649
|
+
if (yVal == null) continue;
|
|
5650
|
+
y2 = yVal;
|
|
5651
|
+
}
|
|
5652
|
+
const text = String(row[textChannel.field] ?? "");
|
|
5653
|
+
if (!text) continue;
|
|
5654
|
+
const color2 = colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__");
|
|
5655
|
+
const fontSize = sizeEncoding ? Math.max(8, Math.min(48, Number(row[sizeEncoding.field]) || 12)) : 12;
|
|
5656
|
+
const aria = {
|
|
5657
|
+
label: text
|
|
5658
|
+
};
|
|
5659
|
+
marks.push({
|
|
5660
|
+
type: "textMark",
|
|
5661
|
+
x: x2,
|
|
5662
|
+
y: y2,
|
|
5663
|
+
text,
|
|
5664
|
+
fill: color2,
|
|
5665
|
+
fontSize,
|
|
5666
|
+
textAnchor: "middle",
|
|
5667
|
+
angle: encoding.angle && "field" in encoding.angle ? Number(row[encoding.angle.field]) || 0 : void 0,
|
|
5668
|
+
data: row,
|
|
5669
|
+
aria
|
|
5670
|
+
});
|
|
5671
|
+
}
|
|
5672
|
+
return marks;
|
|
5673
|
+
}
|
|
5674
|
+
var textRenderer = (spec, scales, _chartArea, _strategy, _theme) => {
|
|
5675
|
+
return computeTextMarks(spec, scales);
|
|
5676
|
+
};
|
|
5677
|
+
|
|
5678
|
+
// src/charts/tick/index.ts
|
|
5679
|
+
var DEFAULT_TICK_LENGTH = 18;
|
|
5680
|
+
function computeTickMarks(spec, scales, _chartArea) {
|
|
5681
|
+
const encoding = spec.encoding;
|
|
5682
|
+
const xChannel = encoding.x;
|
|
5683
|
+
const yChannel = encoding.y;
|
|
5684
|
+
if (!xChannel || !yChannel || !scales.x || !scales.y) return [];
|
|
5685
|
+
const colorEncoding = encoding.color && "field" in encoding.color ? encoding.color : void 0;
|
|
5686
|
+
const colorField = colorEncoding?.field;
|
|
5687
|
+
const marks = [];
|
|
5688
|
+
const isHorizontal = xChannel.type === "quantitative" && yChannel.type !== "quantitative";
|
|
5689
|
+
const orient = isHorizontal ? "horizontal" : "vertical";
|
|
5690
|
+
for (const row of spec.data) {
|
|
5691
|
+
const xVal = scaleValue(scales.x.scale, scales.x.type, row[xChannel.field]);
|
|
5692
|
+
const yVal = scaleValue(scales.y.scale, scales.y.type, row[yChannel.field]);
|
|
5693
|
+
if (xVal == null || yVal == null) continue;
|
|
5694
|
+
const color2 = colorField ? getColor(scales, String(row[colorField] ?? "__default__")) : getColor(scales, "__default__");
|
|
5695
|
+
const aria = {
|
|
5696
|
+
label: `${row[xChannel.field]}, ${row[yChannel.field]}`
|
|
5697
|
+
};
|
|
5698
|
+
marks.push({
|
|
5699
|
+
type: "tick",
|
|
5700
|
+
x: xVal,
|
|
5701
|
+
y: yVal,
|
|
5702
|
+
length: DEFAULT_TICK_LENGTH,
|
|
5703
|
+
orient,
|
|
5704
|
+
stroke: color2,
|
|
5705
|
+
strokeWidth: 1,
|
|
5706
|
+
opacity: encoding.opacity && "field" in encoding.opacity ? Math.max(0, Math.min(1, Number(row[encoding.opacity.field]) || 1)) : void 0,
|
|
5707
|
+
data: row,
|
|
5708
|
+
aria
|
|
5709
|
+
});
|
|
5710
|
+
}
|
|
5711
|
+
return marks;
|
|
5712
|
+
}
|
|
5713
|
+
var tickRenderer = (spec, scales, chartArea, _strategy, _theme) => {
|
|
5714
|
+
return computeTickMarks(spec, scales, chartArea);
|
|
5715
|
+
};
|
|
5716
|
+
|
|
5028
5717
|
// src/compiler/normalize.ts
|
|
5029
|
-
import {
|
|
5718
|
+
import {
|
|
5719
|
+
isChartSpec,
|
|
5720
|
+
isGraphSpec,
|
|
5721
|
+
isLayerSpec,
|
|
5722
|
+
isTableSpec,
|
|
5723
|
+
resolveMarkDef,
|
|
5724
|
+
resolveMarkType
|
|
5725
|
+
} from "@opendata-ai/openchart-core";
|
|
5030
5726
|
function normalizeChromeField(value) {
|
|
5031
5727
|
if (value === void 0) return void 0;
|
|
5032
5728
|
if (typeof value === "string") return { text: value };
|
|
@@ -5081,6 +5777,7 @@ function inferEncodingTypes(encoding, data, warnings) {
|
|
|
5081
5777
|
for (const channel of ["x", "y", "color", "size", "detail"]) {
|
|
5082
5778
|
const spec = result[channel];
|
|
5083
5779
|
if (!spec) continue;
|
|
5780
|
+
if ("condition" in spec) continue;
|
|
5084
5781
|
if (!spec.type) {
|
|
5085
5782
|
const inferred = inferFieldType(data, spec.field);
|
|
5086
5783
|
result[channel] = { ...spec, type: inferred };
|
|
@@ -5131,8 +5828,11 @@ function normalizeAnnotations(annotations) {
|
|
|
5131
5828
|
}
|
|
5132
5829
|
function normalizeChartSpec(spec, warnings) {
|
|
5133
5830
|
const encoding = inferEncodingTypes(spec.encoding, spec.data, warnings);
|
|
5831
|
+
const markType = resolveMarkType(spec.mark);
|
|
5832
|
+
const markDef = resolveMarkDef(spec.mark);
|
|
5134
5833
|
return {
|
|
5135
|
-
|
|
5834
|
+
markType,
|
|
5835
|
+
markDef,
|
|
5136
5836
|
data: spec.data,
|
|
5137
5837
|
encoding,
|
|
5138
5838
|
chrome: normalizeChrome(spec.chrome),
|
|
@@ -5190,6 +5890,13 @@ function normalizeGraphSpec(spec, _warnings) {
|
|
|
5190
5890
|
};
|
|
5191
5891
|
}
|
|
5192
5892
|
function normalizeSpec(spec, warnings = []) {
|
|
5893
|
+
if (isLayerSpec(spec)) {
|
|
5894
|
+
const leaves = flattenLayers(spec);
|
|
5895
|
+
if (leaves.length === 0) {
|
|
5896
|
+
throw new Error("LayerSpec has no leaf chart specs after flattening");
|
|
5897
|
+
}
|
|
5898
|
+
return normalizeChartSpec(leaves[0], warnings);
|
|
5899
|
+
}
|
|
5193
5900
|
if (isChartSpec(spec)) {
|
|
5194
5901
|
return normalizeChartSpec(spec, warnings);
|
|
5195
5902
|
}
|
|
@@ -5199,13 +5906,37 @@ function normalizeSpec(spec, warnings = []) {
|
|
|
5199
5906
|
if (isGraphSpec(spec)) {
|
|
5200
5907
|
return normalizeGraphSpec(spec, warnings);
|
|
5201
5908
|
}
|
|
5202
|
-
throw new Error(
|
|
5909
|
+
throw new Error(
|
|
5910
|
+
`Unknown spec shape. Expected mark (chart), layer, type: 'table', or type: 'graph'.`
|
|
5911
|
+
);
|
|
5912
|
+
}
|
|
5913
|
+
function flattenLayers(spec, parentData, parentEncoding, parentTransforms) {
|
|
5914
|
+
const resolvedData = spec.data ?? parentData;
|
|
5915
|
+
const resolvedEncoding = parentEncoding && spec.encoding ? { ...parentEncoding, ...spec.encoding } : spec.encoding ?? parentEncoding;
|
|
5916
|
+
const resolvedTransforms = [...parentTransforms ?? [], ...spec.transform ?? []];
|
|
5917
|
+
const leaves = [];
|
|
5918
|
+
for (const child of spec.layer) {
|
|
5919
|
+
if (isLayerSpec(child)) {
|
|
5920
|
+
leaves.push(...flattenLayers(child, resolvedData, resolvedEncoding, resolvedTransforms));
|
|
5921
|
+
} else {
|
|
5922
|
+
const mergedData = child.data ?? resolvedData ?? [];
|
|
5923
|
+
const mergedEncoding = resolvedEncoding ? { ...resolvedEncoding, ...child.encoding } : child.encoding;
|
|
5924
|
+
const mergedTransforms = [...resolvedTransforms, ...child.transform ?? []];
|
|
5925
|
+
leaves.push({
|
|
5926
|
+
...child,
|
|
5927
|
+
data: mergedData,
|
|
5928
|
+
encoding: mergedEncoding,
|
|
5929
|
+
transform: mergedTransforms.length > 0 ? mergedTransforms : void 0
|
|
5930
|
+
});
|
|
5931
|
+
}
|
|
5932
|
+
}
|
|
5933
|
+
return leaves;
|
|
5203
5934
|
}
|
|
5204
5935
|
|
|
5205
5936
|
// src/compiler/validate.ts
|
|
5206
5937
|
import {
|
|
5207
|
-
|
|
5208
|
-
|
|
5938
|
+
MARK_ENCODING_RULES,
|
|
5939
|
+
MARK_TYPES
|
|
5209
5940
|
} from "@opendata-ai/openchart-core";
|
|
5210
5941
|
var VALID_FIELD_TYPES = /* @__PURE__ */ new Set(["quantitative", "temporal", "nominal", "ordinal"]);
|
|
5211
5942
|
var VALID_DARK_MODES = /* @__PURE__ */ new Set(["auto", "force", "off"]);
|
|
@@ -5227,7 +5958,7 @@ function isNumeric(value) {
|
|
|
5227
5958
|
return false;
|
|
5228
5959
|
}
|
|
5229
5960
|
function validateChartSpec(spec, errors) {
|
|
5230
|
-
const
|
|
5961
|
+
const markType = typeof spec.mark === "string" ? spec.mark : spec.mark?.type;
|
|
5231
5962
|
if (!Array.isArray(spec.data)) {
|
|
5232
5963
|
errors.push({
|
|
5233
5964
|
message: 'Spec error: "data" must be an array',
|
|
@@ -5257,17 +5988,17 @@ function validateChartSpec(spec, errors) {
|
|
|
5257
5988
|
return;
|
|
5258
5989
|
}
|
|
5259
5990
|
if (!spec.encoding || typeof spec.encoding !== "object") {
|
|
5260
|
-
const rules2 =
|
|
5991
|
+
const rules2 = MARK_ENCODING_RULES[markType];
|
|
5261
5992
|
const requiredChannels = Object.entries(rules2).filter(([, rule]) => rule.required).map(([ch]) => ch);
|
|
5262
5993
|
errors.push({
|
|
5263
|
-
message: `Spec error: ${
|
|
5994
|
+
message: `Spec error: ${markType} chart requires an "encoding" object`,
|
|
5264
5995
|
path: "encoding",
|
|
5265
5996
|
code: "MISSING_FIELD",
|
|
5266
5997
|
suggestion: `Add an encoding object with required channels: ${requiredChannels.join(", ")}. Example: encoding: { ${requiredChannels.map((ch) => `${ch}: { field: "...", type: "..." }`).join(", ")} }`
|
|
5267
5998
|
});
|
|
5268
5999
|
return;
|
|
5269
6000
|
}
|
|
5270
|
-
const rules =
|
|
6001
|
+
const rules = MARK_ENCODING_RULES[markType];
|
|
5271
6002
|
const encoding = spec.encoding;
|
|
5272
6003
|
const dataColumns = new Set(Object.keys(firstRow));
|
|
5273
6004
|
const availableColumns = [...dataColumns].join(", ");
|
|
@@ -5275,17 +6006,29 @@ function validateChartSpec(spec, errors) {
|
|
|
5275
6006
|
if (rule.required && !encoding[channel]) {
|
|
5276
6007
|
const allowedTypes = rule.allowedTypes.join(" or ");
|
|
5277
6008
|
errors.push({
|
|
5278
|
-
message: `Spec error: ${
|
|
6009
|
+
message: `Spec error: ${markType} chart requires encoding.${channel} but none was provided`,
|
|
5279
6010
|
path: `encoding.${channel}`,
|
|
5280
6011
|
code: "MISSING_FIELD",
|
|
5281
6012
|
suggestion: `Add encoding.${channel} with a field from your data (${availableColumns}) and type (${allowedTypes}). Example: ${channel}: { field: "${[...dataColumns][0] ?? "myField"}", type: "${rule.allowedTypes[0]}" }`
|
|
5282
6013
|
});
|
|
5283
6014
|
}
|
|
5284
6015
|
}
|
|
6016
|
+
const transformFields = /* @__PURE__ */ new Set();
|
|
6017
|
+
if (Array.isArray(spec.transform)) {
|
|
6018
|
+
for (const t of spec.transform) {
|
|
6019
|
+
if (typeof t.as === "string") transformFields.add(t.as);
|
|
6020
|
+
if (Array.isArray(t.as)) {
|
|
6021
|
+
for (const f of t.as) {
|
|
6022
|
+
if (typeof f === "string") transformFields.add(f);
|
|
6023
|
+
}
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
}
|
|
5285
6027
|
for (const [channel, channelSpec] of Object.entries(encoding)) {
|
|
5286
6028
|
if (!channelSpec || typeof channelSpec !== "object") continue;
|
|
5287
6029
|
const channelObj = channelSpec;
|
|
5288
6030
|
const channelRule = rules[channel];
|
|
6031
|
+
if ("condition" in channelObj) continue;
|
|
5289
6032
|
if (!channelObj.field || typeof channelObj.field !== "string") {
|
|
5290
6033
|
errors.push({
|
|
5291
6034
|
message: `Spec error: encoding.${channel} must have a "field" string`,
|
|
@@ -5295,7 +6038,7 @@ function validateChartSpec(spec, errors) {
|
|
|
5295
6038
|
});
|
|
5296
6039
|
continue;
|
|
5297
6040
|
}
|
|
5298
|
-
if (!dataColumns.has(channelObj.field)) {
|
|
6041
|
+
if (!dataColumns.has(channelObj.field) && !transformFields.has(channelObj.field)) {
|
|
5299
6042
|
errors.push({
|
|
5300
6043
|
message: `Spec error: encoding.${channel}.field "${channelObj.field}" does not exist in data. Available columns: ${availableColumns}`,
|
|
5301
6044
|
path: `encoding.${channel}.field`,
|
|
@@ -5314,7 +6057,7 @@ function validateChartSpec(spec, errors) {
|
|
|
5314
6057
|
if (channelRule && channelObj.type && channelRule.allowedTypes.length > 0) {
|
|
5315
6058
|
if (!channelRule.allowedTypes.includes(channelObj.type)) {
|
|
5316
6059
|
errors.push({
|
|
5317
|
-
message: `Spec error: encoding.${channel} for ${
|
|
6060
|
+
message: `Spec error: encoding.${channel} for ${markType} chart does not accept type "${channelObj.type}". Allowed types: ${channelRule.allowedTypes.join(", ")}`,
|
|
5318
6061
|
path: `encoding.${channel}.type`,
|
|
5319
6062
|
code: "ENCODING_MISMATCH",
|
|
5320
6063
|
suggestion: `Change encoding.${channel}.type to one of: ${channelRule.allowedTypes.join(", ")}`
|
|
@@ -5592,6 +6335,79 @@ function validateGraphSpec(spec, errors) {
|
|
|
5592
6335
|
}
|
|
5593
6336
|
}
|
|
5594
6337
|
}
|
|
6338
|
+
function validateLayerSpec(spec, errors) {
|
|
6339
|
+
const layer = spec.layer;
|
|
6340
|
+
if (layer.length === 0) {
|
|
6341
|
+
errors.push({
|
|
6342
|
+
message: 'Spec error: "layer" must be a non-empty array',
|
|
6343
|
+
path: "layer",
|
|
6344
|
+
code: "EMPTY_DATA",
|
|
6345
|
+
suggestion: "Add at least one layer with a mark and encoding"
|
|
6346
|
+
});
|
|
6347
|
+
return;
|
|
6348
|
+
}
|
|
6349
|
+
for (let i = 0; i < layer.length; i++) {
|
|
6350
|
+
const child = layer[i];
|
|
6351
|
+
if (!child || typeof child !== "object" || Array.isArray(child)) {
|
|
6352
|
+
errors.push({
|
|
6353
|
+
message: `Spec error: layer[${i}] must be an object`,
|
|
6354
|
+
path: `layer[${i}]`,
|
|
6355
|
+
code: "INVALID_TYPE",
|
|
6356
|
+
suggestion: "Each layer must be a chart spec (with mark) or a nested layer spec (with layer)"
|
|
6357
|
+
});
|
|
6358
|
+
continue;
|
|
6359
|
+
}
|
|
6360
|
+
const childObj = child;
|
|
6361
|
+
const isNestedLayer = "layer" in childObj && Array.isArray(childObj.layer);
|
|
6362
|
+
const isChildChart = "mark" in childObj;
|
|
6363
|
+
if (!isNestedLayer && !isChildChart) {
|
|
6364
|
+
errors.push({
|
|
6365
|
+
message: `Spec error: layer[${i}] must have a "mark" field or a "layer" array`,
|
|
6366
|
+
path: `layer[${i}]`,
|
|
6367
|
+
code: "MISSING_FIELD",
|
|
6368
|
+
suggestion: "Each layer must be a chart spec (with mark + encoding) or a nested layer spec (with layer array)"
|
|
6369
|
+
});
|
|
6370
|
+
continue;
|
|
6371
|
+
}
|
|
6372
|
+
if (isNestedLayer) {
|
|
6373
|
+
validateLayerSpec(childObj, errors);
|
|
6374
|
+
} else if (isChildChart) {
|
|
6375
|
+
const mark = childObj.mark;
|
|
6376
|
+
let markValue;
|
|
6377
|
+
if (typeof mark === "string") {
|
|
6378
|
+
markValue = mark;
|
|
6379
|
+
} else if (mark && typeof mark === "object" && !Array.isArray(mark)) {
|
|
6380
|
+
markValue = mark.type;
|
|
6381
|
+
}
|
|
6382
|
+
if (!markValue || !MARK_TYPES.has(markValue)) {
|
|
6383
|
+
errors.push({
|
|
6384
|
+
message: `Spec error: layer[${i}].mark "${markValue ?? String(mark)}" is not a valid mark type`,
|
|
6385
|
+
path: `layer[${i}].mark`,
|
|
6386
|
+
code: "INVALID_VALUE",
|
|
6387
|
+
suggestion: `Change mark to one of: ${[...MARK_TYPES].join(", ")}`
|
|
6388
|
+
});
|
|
6389
|
+
continue;
|
|
6390
|
+
}
|
|
6391
|
+
const hasOwnData = Array.isArray(childObj.data) && childObj.data.length > 0;
|
|
6392
|
+
const parentHasData = Array.isArray(spec.data) && spec.data.length > 0;
|
|
6393
|
+
if (hasOwnData || parentHasData) {
|
|
6394
|
+
const mergedForValidation = { ...childObj };
|
|
6395
|
+
if (!hasOwnData && parentHasData) {
|
|
6396
|
+
mergedForValidation.data = spec.data;
|
|
6397
|
+
}
|
|
6398
|
+
if (spec.encoding && typeof spec.encoding === "object") {
|
|
6399
|
+
mergedForValidation.encoding = {
|
|
6400
|
+
...spec.encoding,
|
|
6401
|
+
...childObj.encoding ?? {}
|
|
6402
|
+
};
|
|
6403
|
+
}
|
|
6404
|
+
if (mergedForValidation.data && mergedForValidation.encoding) {
|
|
6405
|
+
validateChartSpec(mergedForValidation, errors);
|
|
6406
|
+
}
|
|
6407
|
+
}
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
}
|
|
5595
6411
|
function validateSpec(spec) {
|
|
5596
6412
|
const errors = [];
|
|
5597
6413
|
if (!spec || typeof spec !== "object" || Array.isArray(spec)) {
|
|
@@ -5601,45 +6417,58 @@ function validateSpec(spec) {
|
|
|
5601
6417
|
{
|
|
5602
6418
|
message: "Spec error: spec must be a non-null object",
|
|
5603
6419
|
code: "INVALID_TYPE",
|
|
5604
|
-
suggestion: 'Pass a spec object with at least a "
|
|
6420
|
+
suggestion: 'Pass a spec object with at least a "mark" field for charts, e.g. { mark: "line", data: [...], encoding: {...} }'
|
|
5605
6421
|
}
|
|
5606
6422
|
],
|
|
5607
6423
|
normalized: null
|
|
5608
6424
|
};
|
|
5609
6425
|
}
|
|
5610
6426
|
const obj = spec;
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
valid: false,
|
|
5614
|
-
errors: [
|
|
5615
|
-
{
|
|
5616
|
-
message: 'Spec error: spec must have a "type" field',
|
|
5617
|
-
path: "type",
|
|
5618
|
-
code: "MISSING_FIELD",
|
|
5619
|
-
suggestion: `Add a type field. Valid types: ${[...CHART_TYPES].join(", ")}, table, graph`
|
|
5620
|
-
}
|
|
5621
|
-
],
|
|
5622
|
-
normalized: null
|
|
5623
|
-
};
|
|
5624
|
-
}
|
|
5625
|
-
const isChart = CHART_TYPES.has(obj.type);
|
|
6427
|
+
const hasLayer = "layer" in obj && Array.isArray(obj.layer);
|
|
6428
|
+
const hasMark = "mark" in obj;
|
|
5626
6429
|
const isTable = obj.type === "table";
|
|
5627
6430
|
const isGraph = obj.type === "graph";
|
|
5628
|
-
|
|
6431
|
+
const isLayer = hasLayer && !isTable && !isGraph;
|
|
6432
|
+
const isChart = hasMark && !hasLayer && !isTable && !isGraph;
|
|
6433
|
+
if (!isChart && !isTable && !isGraph && !isLayer) {
|
|
5629
6434
|
return {
|
|
5630
6435
|
valid: false,
|
|
5631
6436
|
errors: [
|
|
5632
6437
|
{
|
|
5633
|
-
message:
|
|
5634
|
-
path: "
|
|
5635
|
-
code: "
|
|
5636
|
-
suggestion: `
|
|
6438
|
+
message: 'Spec error: spec must have a "mark" field for charts, a "layer" array for layered charts, or a "type" field for tables/graphs',
|
|
6439
|
+
path: "mark",
|
|
6440
|
+
code: "MISSING_FIELD",
|
|
6441
|
+
suggestion: `Add a "mark" field for charts (e.g. mark: "bar"), a "layer" array for layered charts, or a "type" field for tables/graphs (type: "table" or type: "graph"). Valid mark types: ${[...MARK_TYPES].join(", ")}`
|
|
5637
6442
|
}
|
|
5638
6443
|
],
|
|
5639
6444
|
normalized: null
|
|
5640
6445
|
};
|
|
5641
6446
|
}
|
|
6447
|
+
if (isLayer) {
|
|
6448
|
+
validateLayerSpec(obj, errors);
|
|
6449
|
+
}
|
|
5642
6450
|
if (isChart) {
|
|
6451
|
+
const mark = obj.mark;
|
|
6452
|
+
let markValue;
|
|
6453
|
+
if (typeof mark === "string") {
|
|
6454
|
+
markValue = mark;
|
|
6455
|
+
} else if (mark && typeof mark === "object" && !Array.isArray(mark)) {
|
|
6456
|
+
markValue = mark.type;
|
|
6457
|
+
}
|
|
6458
|
+
if (!markValue || !MARK_TYPES.has(markValue)) {
|
|
6459
|
+
return {
|
|
6460
|
+
valid: false,
|
|
6461
|
+
errors: [
|
|
6462
|
+
{
|
|
6463
|
+
message: `Spec error: "${markValue ?? String(mark)}" is not a valid mark type. Valid mark types: ${[...MARK_TYPES].join(", ")}`,
|
|
6464
|
+
path: "mark",
|
|
6465
|
+
code: "INVALID_VALUE",
|
|
6466
|
+
suggestion: `Change mark to one of: ${[...MARK_TYPES].join(", ")}`
|
|
6467
|
+
}
|
|
6468
|
+
],
|
|
6469
|
+
normalized: null
|
|
6470
|
+
};
|
|
6471
|
+
}
|
|
5643
6472
|
validateChartSpec(obj, errors);
|
|
5644
6473
|
} else if (isTable) {
|
|
5645
6474
|
validateTableSpec(obj, errors);
|
|
@@ -5956,9 +6785,9 @@ function buildGraphTooltips(nodes) {
|
|
|
5956
6785
|
}
|
|
5957
6786
|
function compileGraph(spec, options) {
|
|
5958
6787
|
const { spec: normalized } = compile(spec);
|
|
5959
|
-
if (normalized.type !== "graph") {
|
|
6788
|
+
if (!("type" in normalized) || normalized.type !== "graph") {
|
|
5960
6789
|
throw new Error(
|
|
5961
|
-
|
|
6790
|
+
"compileGraph received a non-graph spec. Use compileChart or compileTable instead."
|
|
5962
6791
|
);
|
|
5963
6792
|
}
|
|
5964
6793
|
const graphSpec = normalized;
|
|
@@ -6102,6 +6931,14 @@ function thinTicksUntilFit(ticks2, fontSize, fontWeight, measureText) {
|
|
|
6102
6931
|
}
|
|
6103
6932
|
function continuousTicks(resolvedScale, density) {
|
|
6104
6933
|
const scale = resolvedScale.scale;
|
|
6934
|
+
if (!("ticks" in scale) || typeof scale.ticks !== "function") {
|
|
6935
|
+
const domain = scale.domain();
|
|
6936
|
+
return domain.map((value) => ({
|
|
6937
|
+
value,
|
|
6938
|
+
position: scale(value),
|
|
6939
|
+
label: formatTickLabel(value, resolvedScale)
|
|
6940
|
+
}));
|
|
6941
|
+
}
|
|
6105
6942
|
const explicitCount = resolvedScale.channel.axis?.tickCount;
|
|
6106
6943
|
const count = explicitCount ?? TICK_COUNTS[density];
|
|
6107
6944
|
const rawTicks = scale.ticks(count);
|
|
@@ -6133,13 +6970,24 @@ function categoricalTicks(resolvedScale, density) {
|
|
|
6133
6970
|
});
|
|
6134
6971
|
return ticks2;
|
|
6135
6972
|
}
|
|
6973
|
+
var NUMERIC_SCALE_TYPES = /* @__PURE__ */ new Set([
|
|
6974
|
+
"linear",
|
|
6975
|
+
"log",
|
|
6976
|
+
"pow",
|
|
6977
|
+
"sqrt",
|
|
6978
|
+
"symlog",
|
|
6979
|
+
"quantile",
|
|
6980
|
+
"quantize",
|
|
6981
|
+
"threshold"
|
|
6982
|
+
]);
|
|
6983
|
+
var TEMPORAL_SCALE_TYPES = /* @__PURE__ */ new Set(["time", "utc"]);
|
|
6136
6984
|
function formatTickLabel(value, resolvedScale) {
|
|
6137
6985
|
const formatStr = resolvedScale.channel.axis?.format;
|
|
6138
|
-
if (resolvedScale.type
|
|
6986
|
+
if (TEMPORAL_SCALE_TYPES.has(resolvedScale.type)) {
|
|
6139
6987
|
if (formatStr) return String(value);
|
|
6140
6988
|
return formatDate(value);
|
|
6141
6989
|
}
|
|
6142
|
-
if (
|
|
6990
|
+
if (NUMERIC_SCALE_TYPES.has(resolvedScale.type)) {
|
|
6143
6991
|
const num = value;
|
|
6144
6992
|
if (formatStr) {
|
|
6145
6993
|
const fmt = buildD3Formatter3(formatStr);
|
|
@@ -6150,6 +6998,27 @@ function formatTickLabel(value, resolvedScale) {
|
|
|
6150
6998
|
}
|
|
6151
6999
|
return String(value);
|
|
6152
7000
|
}
|
|
7001
|
+
function resolveExplicitTicks(values, resolvedScale) {
|
|
7002
|
+
const scale = resolvedScale.scale;
|
|
7003
|
+
return values.map((value) => {
|
|
7004
|
+
let position;
|
|
7005
|
+
if (TEMPORAL_SCALE_TYPES.has(resolvedScale.type)) {
|
|
7006
|
+
const d = value instanceof Date ? value : new Date(String(value));
|
|
7007
|
+
position = scale(d);
|
|
7008
|
+
} else if (resolvedScale.type === "band" || resolvedScale.type === "point" || resolvedScale.type === "ordinal") {
|
|
7009
|
+
const s = String(value);
|
|
7010
|
+
const bandScale = resolvedScale.type === "band" ? scale : null;
|
|
7011
|
+
position = bandScale ? (bandScale(s) ?? 0) + bandScale.bandwidth() / 2 : scale(s) ?? 0;
|
|
7012
|
+
} else {
|
|
7013
|
+
position = scale(value);
|
|
7014
|
+
}
|
|
7015
|
+
return {
|
|
7016
|
+
value,
|
|
7017
|
+
position,
|
|
7018
|
+
label: formatTickLabel(value, resolvedScale)
|
|
7019
|
+
};
|
|
7020
|
+
});
|
|
7021
|
+
}
|
|
6153
7022
|
function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
6154
7023
|
const result = {};
|
|
6155
7024
|
const baseDensity = strategy.axisLabelDensity;
|
|
@@ -6183,14 +7052,22 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
6183
7052
|
const { fontSize } = tickLabelStyle;
|
|
6184
7053
|
const { fontWeight } = tickLabelStyle;
|
|
6185
7054
|
if (scales.x) {
|
|
6186
|
-
const
|
|
7055
|
+
const axisConfig = scales.x.channel.axis;
|
|
7056
|
+
let allTicks;
|
|
7057
|
+
if (axisConfig?.values) {
|
|
7058
|
+
allTicks = resolveExplicitTicks(axisConfig.values, scales.x);
|
|
7059
|
+
} else if (scales.x.type === "band" || scales.x.type === "point" || scales.x.type === "ordinal") {
|
|
7060
|
+
allTicks = categoricalTicks(scales.x, xDensity);
|
|
7061
|
+
} else {
|
|
7062
|
+
allTicks = continuousTicks(scales.x, xDensity);
|
|
7063
|
+
}
|
|
6187
7064
|
const gridlines = allTicks.map((t) => ({
|
|
6188
7065
|
position: t.position,
|
|
6189
7066
|
major: true
|
|
6190
7067
|
}));
|
|
6191
|
-
const shouldThin = scales.x.type !== "band" && !
|
|
7068
|
+
const shouldThin = scales.x.type !== "band" && !axisConfig?.tickCount && !axisConfig?.values;
|
|
6192
7069
|
const ticks2 = shouldThin ? thinTicksUntilFit(allTicks, fontSize, fontWeight, measureText) : allTicks;
|
|
6193
|
-
let tickAngle =
|
|
7070
|
+
let tickAngle = axisConfig?.labelAngle ?? axisConfig?.tickAngle;
|
|
6194
7071
|
if (tickAngle === void 0 && scales.x.type === "band" && ticks2.length > 1) {
|
|
6195
7072
|
const bandwidth = scales.x.scale.bandwidth();
|
|
6196
7073
|
let maxLabelWidth = 0;
|
|
@@ -6202,35 +7079,62 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
6202
7079
|
tickAngle = -45;
|
|
6203
7080
|
}
|
|
6204
7081
|
}
|
|
7082
|
+
const axisTitle = axisConfig?.title ?? axisConfig?.label;
|
|
6205
7083
|
result.x = {
|
|
6206
7084
|
ticks: ticks2,
|
|
6207
|
-
gridlines:
|
|
6208
|
-
label:
|
|
7085
|
+
gridlines: axisConfig?.grid ? gridlines : [],
|
|
7086
|
+
label: axisTitle,
|
|
6209
7087
|
labelStyle: axisLabelStyle,
|
|
6210
7088
|
tickLabelStyle,
|
|
6211
7089
|
tickAngle,
|
|
6212
7090
|
start: { x: chartArea.x, y: chartArea.y + chartArea.height },
|
|
6213
|
-
end: { x: chartArea.x + chartArea.width, y: chartArea.y + chartArea.height }
|
|
7091
|
+
end: { x: chartArea.x + chartArea.width, y: chartArea.y + chartArea.height },
|
|
7092
|
+
orient: axisConfig?.orient,
|
|
7093
|
+
domainLine: axisConfig?.domain,
|
|
7094
|
+
tickMarks: axisConfig?.ticks,
|
|
7095
|
+
offset: axisConfig?.offset,
|
|
7096
|
+
titlePadding: axisConfig?.titlePadding,
|
|
7097
|
+
labelPadding: axisConfig?.labelPadding,
|
|
7098
|
+
labelOverlap: axisConfig?.labelOverlap,
|
|
7099
|
+
labelFlush: axisConfig?.labelFlush
|
|
6214
7100
|
};
|
|
6215
7101
|
}
|
|
6216
7102
|
if (scales.y) {
|
|
6217
|
-
const
|
|
7103
|
+
const axisConfig = scales.y.channel.axis;
|
|
7104
|
+
let allTicks;
|
|
7105
|
+
if (axisConfig?.values) {
|
|
7106
|
+
allTicks = resolveExplicitTicks(axisConfig.values, scales.y);
|
|
7107
|
+
} else if (scales.y.type === "band" || scales.y.type === "point" || scales.y.type === "ordinal") {
|
|
7108
|
+
allTicks = categoricalTicks(scales.y, yDensity);
|
|
7109
|
+
} else {
|
|
7110
|
+
allTicks = continuousTicks(scales.y, yDensity);
|
|
7111
|
+
}
|
|
6218
7112
|
const gridlines = allTicks.map((t) => ({
|
|
6219
7113
|
position: t.position,
|
|
6220
7114
|
major: true
|
|
6221
7115
|
}));
|
|
6222
|
-
const shouldThin = scales.y.type !== "band" && !
|
|
7116
|
+
const shouldThin = scales.y.type !== "band" && !axisConfig?.tickCount && !axisConfig?.values;
|
|
6223
7117
|
const ticks2 = shouldThin ? thinTicksUntilFit(allTicks, fontSize, fontWeight, measureText) : allTicks;
|
|
7118
|
+
const axisTitle = axisConfig?.title ?? axisConfig?.label;
|
|
7119
|
+
const tickAngle = axisConfig?.labelAngle ?? axisConfig?.tickAngle;
|
|
6224
7120
|
result.y = {
|
|
6225
7121
|
ticks: ticks2,
|
|
6226
7122
|
// Y-axis gridlines are shown by default (standard editorial practice)
|
|
6227
7123
|
gridlines,
|
|
6228
|
-
label:
|
|
7124
|
+
label: axisTitle,
|
|
6229
7125
|
labelStyle: axisLabelStyle,
|
|
6230
7126
|
tickLabelStyle,
|
|
6231
|
-
tickAngle
|
|
7127
|
+
tickAngle,
|
|
6232
7128
|
start: { x: chartArea.x, y: chartArea.y },
|
|
6233
|
-
end: { x: chartArea.x, y: chartArea.y + chartArea.height }
|
|
7129
|
+
end: { x: chartArea.x, y: chartArea.y + chartArea.height },
|
|
7130
|
+
orient: axisConfig?.orient,
|
|
7131
|
+
domainLine: axisConfig?.domain,
|
|
7132
|
+
tickMarks: axisConfig?.ticks,
|
|
7133
|
+
offset: axisConfig?.offset,
|
|
7134
|
+
titlePadding: axisConfig?.titlePadding,
|
|
7135
|
+
labelPadding: axisConfig?.labelPadding,
|
|
7136
|
+
labelOverlap: axisConfig?.labelOverlap,
|
|
7137
|
+
labelFlush: axisConfig?.labelFlush
|
|
6234
7138
|
};
|
|
6235
7139
|
}
|
|
6236
7140
|
return result;
|
|
@@ -6270,7 +7174,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy) {
|
|
|
6270
7174
|
padding
|
|
6271
7175
|
);
|
|
6272
7176
|
const total = { x: 0, y: 0, width, height };
|
|
6273
|
-
const isRadial = spec.
|
|
7177
|
+
const isRadial = spec.markType === "arc";
|
|
6274
7178
|
const encoding = spec.encoding;
|
|
6275
7179
|
const xAxis = encoding.x?.axis;
|
|
6276
7180
|
const hasXAxisLabel = !!xAxis?.label;
|
|
@@ -6295,15 +7199,17 @@ function computeDimensions(spec, options, legendLayout, theme, strategy) {
|
|
|
6295
7199
|
} else {
|
|
6296
7200
|
xAxisHeight = hasXAxisLabel ? 48 : 26;
|
|
6297
7201
|
}
|
|
7202
|
+
const topAxisGap = isRadial && chrome.topHeight === 0 ? 0 : axisMargin;
|
|
6298
7203
|
const margins = {
|
|
6299
|
-
top: padding + chrome.topHeight +
|
|
7204
|
+
top: padding + chrome.topHeight + topAxisGap,
|
|
6300
7205
|
right: padding + (isRadial ? padding : axisMargin),
|
|
6301
7206
|
bottom: padding + chrome.bottomHeight + xAxisHeight,
|
|
6302
7207
|
left: padding + (isRadial ? padding : axisMargin)
|
|
6303
7208
|
};
|
|
6304
7209
|
const labelDensity = spec.labels.density;
|
|
6305
|
-
if ((spec.
|
|
6306
|
-
const
|
|
7210
|
+
if ((spec.markType === "line" || spec.markType === "area") && labelDensity !== "none") {
|
|
7211
|
+
const colorEnc = encoding.color;
|
|
7212
|
+
const colorField = colorEnc && "field" in colorEnc ? colorEnc.field : void 0;
|
|
6307
7213
|
if (colorField) {
|
|
6308
7214
|
let maxLabelWidth = 0;
|
|
6309
7215
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6321,7 +7227,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy) {
|
|
|
6321
7227
|
}
|
|
6322
7228
|
}
|
|
6323
7229
|
if (encoding.y && !isRadial) {
|
|
6324
|
-
if (spec.
|
|
7230
|
+
if (spec.markType === "bar" || spec.markType === "circle" || encoding.y.type === "nominal" || encoding.y.type === "ordinal") {
|
|
6325
7231
|
const yField = encoding.y.field;
|
|
6326
7232
|
let maxLabelWidth = 0;
|
|
6327
7233
|
for (const row of spec.data) {
|
|
@@ -6385,7 +7291,8 @@ function computeDimensions(spec, options, legendLayout, theme, strategy) {
|
|
|
6385
7291
|
fallbackMode,
|
|
6386
7292
|
padding
|
|
6387
7293
|
);
|
|
6388
|
-
const
|
|
7294
|
+
const fallbackTopAxisGap = isRadial && fallbackChrome.topHeight === 0 ? 0 : axisMargin;
|
|
7295
|
+
const newTop = padding + fallbackChrome.topHeight + fallbackTopAxisGap;
|
|
6389
7296
|
const topDelta = margins.top - newTop;
|
|
6390
7297
|
const newBottom = padding + fallbackChrome.bottomHeight + xAxisHeight;
|
|
6391
7298
|
const bottomDelta = margins.bottom - newBottom;
|
|
@@ -6455,6 +7362,15 @@ function uniqueStrings(values) {
|
|
|
6455
7362
|
}
|
|
6456
7363
|
return result;
|
|
6457
7364
|
}
|
|
7365
|
+
function applyContinuousConfig(scale, channel) {
|
|
7366
|
+
if (channel.scale?.clamp) {
|
|
7367
|
+
scale.clamp(true);
|
|
7368
|
+
}
|
|
7369
|
+
if (channel.scale?.reverse) {
|
|
7370
|
+
const [r0, r1] = scale.range();
|
|
7371
|
+
scale.range([r1, r0]);
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
6458
7374
|
function buildTimeScale(channel, data, rangeStart, rangeEnd) {
|
|
6459
7375
|
const values = parseDates(fieldValues(data, channel.field));
|
|
6460
7376
|
const domain = channel.scale?.domain ? [new Date(channel.scale.domain[0]), new Date(channel.scale.domain[1])] : extent(values);
|
|
@@ -6462,8 +7378,19 @@ function buildTimeScale(channel, data, rangeStart, rangeEnd) {
|
|
|
6462
7378
|
if (channel.scale?.nice !== false) {
|
|
6463
7379
|
scale.nice();
|
|
6464
7380
|
}
|
|
7381
|
+
applyContinuousConfig(scale, channel);
|
|
6465
7382
|
return { scale, type: "time", channel };
|
|
6466
7383
|
}
|
|
7384
|
+
function buildUtcScale(channel, data, rangeStart, rangeEnd) {
|
|
7385
|
+
const values = parseDates(fieldValues(data, channel.field));
|
|
7386
|
+
const domain = channel.scale?.domain ? [new Date(channel.scale.domain[0]), new Date(channel.scale.domain[1])] : extent(values);
|
|
7387
|
+
const scale = utcTime().domain(domain).range([rangeStart, rangeEnd]);
|
|
7388
|
+
if (channel.scale?.nice !== false) {
|
|
7389
|
+
scale.nice();
|
|
7390
|
+
}
|
|
7391
|
+
applyContinuousConfig(scale, channel);
|
|
7392
|
+
return { scale, type: "utc", channel };
|
|
7393
|
+
}
|
|
6467
7394
|
function buildLinearScale(channel, data, rangeStart, rangeEnd) {
|
|
6468
7395
|
const values = parseNumbers(fieldValues(data, channel.field));
|
|
6469
7396
|
let domainMin;
|
|
@@ -6484,23 +7411,141 @@ function buildLinearScale(channel, data, rangeStart, rangeEnd) {
|
|
|
6484
7411
|
if (channel.scale?.nice !== false) {
|
|
6485
7412
|
scale.nice();
|
|
6486
7413
|
}
|
|
7414
|
+
applyContinuousConfig(scale, channel);
|
|
6487
7415
|
return { scale, type: "linear", channel };
|
|
6488
7416
|
}
|
|
6489
7417
|
function buildLogScale(channel, data, rangeStart, rangeEnd) {
|
|
6490
7418
|
const values = parseNumbers(fieldValues(data, channel.field)).filter((v) => v > 0);
|
|
6491
|
-
const domainMin = min2(values) ?? 1;
|
|
6492
|
-
const domainMax = max2(values) ?? 10;
|
|
6493
|
-
const scale = log().domain([domainMin, domainMax]).range([rangeStart, rangeEnd])
|
|
7419
|
+
const domainMin = channel.scale?.domain ? channel.scale.domain[0] : min2(values) ?? 1;
|
|
7420
|
+
const domainMax = channel.scale?.domain ? channel.scale.domain[1] : max2(values) ?? 10;
|
|
7421
|
+
const scale = log().domain([domainMin, domainMax]).range([rangeStart, rangeEnd]);
|
|
7422
|
+
if (channel.scale?.base !== void 0) {
|
|
7423
|
+
scale.base(channel.scale.base);
|
|
7424
|
+
}
|
|
7425
|
+
if (channel.scale?.nice !== false) {
|
|
7426
|
+
scale.nice();
|
|
7427
|
+
}
|
|
7428
|
+
applyContinuousConfig(scale, channel);
|
|
6494
7429
|
return { scale, type: "log", channel };
|
|
6495
7430
|
}
|
|
7431
|
+
function buildPowScale(channel, data, rangeStart, rangeEnd) {
|
|
7432
|
+
const values = parseNumbers(fieldValues(data, channel.field));
|
|
7433
|
+
let domainMin;
|
|
7434
|
+
let domainMax;
|
|
7435
|
+
if (channel.scale?.domain) {
|
|
7436
|
+
[domainMin, domainMax] = channel.scale.domain;
|
|
7437
|
+
} else {
|
|
7438
|
+
domainMin = min2(values) ?? 0;
|
|
7439
|
+
domainMax = max2(values) ?? 1;
|
|
7440
|
+
if (channel.scale?.zero !== false) {
|
|
7441
|
+
domainMin = Math.min(0, domainMin);
|
|
7442
|
+
domainMax = Math.max(0, domainMax);
|
|
7443
|
+
}
|
|
7444
|
+
}
|
|
7445
|
+
const scale = pow().domain([domainMin, domainMax]).range([rangeStart, rangeEnd]);
|
|
7446
|
+
if (channel.scale?.exponent !== void 0) {
|
|
7447
|
+
scale.exponent(channel.scale.exponent);
|
|
7448
|
+
}
|
|
7449
|
+
if (channel.scale?.nice !== false) {
|
|
7450
|
+
scale.nice();
|
|
7451
|
+
}
|
|
7452
|
+
applyContinuousConfig(scale, channel);
|
|
7453
|
+
return { scale, type: "pow", channel };
|
|
7454
|
+
}
|
|
7455
|
+
function buildSqrtScale(channel, data, rangeStart, rangeEnd) {
|
|
7456
|
+
const values = parseNumbers(fieldValues(data, channel.field));
|
|
7457
|
+
let domainMin;
|
|
7458
|
+
let domainMax;
|
|
7459
|
+
if (channel.scale?.domain) {
|
|
7460
|
+
[domainMin, domainMax] = channel.scale.domain;
|
|
7461
|
+
} else {
|
|
7462
|
+
domainMin = min2(values) ?? 0;
|
|
7463
|
+
domainMax = max2(values) ?? 1;
|
|
7464
|
+
if (channel.scale?.zero !== false) {
|
|
7465
|
+
domainMin = Math.min(0, domainMin);
|
|
7466
|
+
domainMax = Math.max(0, domainMax);
|
|
7467
|
+
}
|
|
7468
|
+
}
|
|
7469
|
+
const scale = sqrt2().domain([domainMin, domainMax]).range([rangeStart, rangeEnd]);
|
|
7470
|
+
if (channel.scale?.nice !== false) {
|
|
7471
|
+
scale.nice();
|
|
7472
|
+
}
|
|
7473
|
+
applyContinuousConfig(scale, channel);
|
|
7474
|
+
return { scale, type: "sqrt", channel };
|
|
7475
|
+
}
|
|
7476
|
+
function buildSymlogScale(channel, data, rangeStart, rangeEnd) {
|
|
7477
|
+
const values = parseNumbers(fieldValues(data, channel.field));
|
|
7478
|
+
let domainMin;
|
|
7479
|
+
let domainMax;
|
|
7480
|
+
if (channel.scale?.domain) {
|
|
7481
|
+
[domainMin, domainMax] = channel.scale.domain;
|
|
7482
|
+
} else {
|
|
7483
|
+
domainMin = min2(values) ?? 0;
|
|
7484
|
+
domainMax = max2(values) ?? 1;
|
|
7485
|
+
if (channel.scale?.zero !== false) {
|
|
7486
|
+
domainMin = Math.min(0, domainMin);
|
|
7487
|
+
domainMax = Math.max(0, domainMax);
|
|
7488
|
+
}
|
|
7489
|
+
}
|
|
7490
|
+
const scale = symlog().domain([domainMin, domainMax]).range([rangeStart, rangeEnd]);
|
|
7491
|
+
if (channel.scale?.constant !== void 0) {
|
|
7492
|
+
scale.constant(channel.scale.constant);
|
|
7493
|
+
}
|
|
7494
|
+
if (channel.scale?.nice !== false) {
|
|
7495
|
+
scale.nice();
|
|
7496
|
+
}
|
|
7497
|
+
applyContinuousConfig(scale, channel);
|
|
7498
|
+
return { scale, type: "symlog", channel };
|
|
7499
|
+
}
|
|
7500
|
+
function buildQuantileScale(channel, data, rangeStart, rangeEnd) {
|
|
7501
|
+
const values = parseNumbers(fieldValues(data, channel.field));
|
|
7502
|
+
const range2 = channel.scale?.range ? channel.scale.range : evenRange(rangeStart, rangeEnd, 4);
|
|
7503
|
+
const scale = quantile2().domain(values).range(range2);
|
|
7504
|
+
return { scale, type: "quantile", channel };
|
|
7505
|
+
}
|
|
7506
|
+
function buildQuantizeScale(channel, data, rangeStart, rangeEnd) {
|
|
7507
|
+
const values = parseNumbers(fieldValues(data, channel.field));
|
|
7508
|
+
const domainMin = channel.scale?.domain ? channel.scale.domain[0] : min2(values) ?? 0;
|
|
7509
|
+
const domainMax = channel.scale?.domain ? channel.scale.domain[1] : max2(values) ?? 1;
|
|
7510
|
+
const range2 = channel.scale?.range ? channel.scale.range : evenRange(rangeStart, rangeEnd, 4);
|
|
7511
|
+
const scale = quantize().domain([domainMin, domainMax]).range(range2);
|
|
7512
|
+
return { scale, type: "quantize", channel };
|
|
7513
|
+
}
|
|
7514
|
+
function buildThresholdScale(channel, _data, rangeStart, rangeEnd) {
|
|
7515
|
+
const domainBreaks = channel.scale?.domain ? channel.scale.domain : [0.5];
|
|
7516
|
+
const range2 = channel.scale?.range ? channel.scale.range : evenRange(rangeStart, rangeEnd, domainBreaks.length + 1);
|
|
7517
|
+
const scale = threshold().domain(domainBreaks).range(range2);
|
|
7518
|
+
return { scale, type: "threshold", channel };
|
|
7519
|
+
}
|
|
7520
|
+
function evenRange(start, end, count) {
|
|
7521
|
+
if (count <= 1) return [start];
|
|
7522
|
+
const step = (end - start) / (count - 1);
|
|
7523
|
+
return Array.from({ length: count }, (_, i) => start + step * i);
|
|
7524
|
+
}
|
|
6496
7525
|
function buildBandScale(channel, data, rangeStart, rangeEnd) {
|
|
6497
7526
|
const values = channel.scale?.domain ? channel.scale.domain : uniqueStrings(fieldValues(data, channel.field));
|
|
6498
|
-
const
|
|
7527
|
+
const padding = channel.scale?.padding ?? 0.35;
|
|
7528
|
+
const scale = band().domain(values).range([rangeStart, rangeEnd]).padding(padding);
|
|
7529
|
+
if (channel.scale?.paddingInner !== void 0) {
|
|
7530
|
+
scale.paddingInner(channel.scale.paddingInner);
|
|
7531
|
+
}
|
|
7532
|
+
if (channel.scale?.paddingOuter !== void 0) {
|
|
7533
|
+
scale.paddingOuter(channel.scale.paddingOuter);
|
|
7534
|
+
}
|
|
7535
|
+
if (channel.scale?.reverse) {
|
|
7536
|
+
const [r0, r1] = scale.range();
|
|
7537
|
+
scale.range([r1, r0]);
|
|
7538
|
+
}
|
|
6499
7539
|
return { scale, type: "band", channel };
|
|
6500
7540
|
}
|
|
6501
7541
|
function buildPointScale(channel, data, rangeStart, rangeEnd) {
|
|
6502
7542
|
const values = channel.scale?.domain ? channel.scale.domain : uniqueStrings(fieldValues(data, channel.field));
|
|
6503
|
-
const
|
|
7543
|
+
const padding = channel.scale?.padding ?? 0.5;
|
|
7544
|
+
const scale = point4().domain(values).range([rangeStart, rangeEnd]).padding(padding);
|
|
7545
|
+
if (channel.scale?.reverse) {
|
|
7546
|
+
const [r0, r1] = scale.range();
|
|
7547
|
+
scale.range([r1, r0]);
|
|
7548
|
+
}
|
|
6504
7549
|
return { scale, type: "point", channel };
|
|
6505
7550
|
}
|
|
6506
7551
|
function buildOrdinalColorScale(channel, data, palette) {
|
|
@@ -6520,10 +7565,24 @@ function buildPositionalScale(channel, data, rangeStart, rangeEnd, chartType, ax
|
|
|
6520
7565
|
switch (channel.scale.type) {
|
|
6521
7566
|
case "time":
|
|
6522
7567
|
return buildTimeScale(channel, data, rangeStart, rangeEnd);
|
|
7568
|
+
case "utc":
|
|
7569
|
+
return buildUtcScale(channel, data, rangeStart, rangeEnd);
|
|
6523
7570
|
case "linear":
|
|
6524
7571
|
return buildLinearScale(channel, data, rangeStart, rangeEnd);
|
|
6525
7572
|
case "log":
|
|
6526
7573
|
return buildLogScale(channel, data, rangeStart, rangeEnd);
|
|
7574
|
+
case "pow":
|
|
7575
|
+
return buildPowScale(channel, data, rangeStart, rangeEnd);
|
|
7576
|
+
case "sqrt":
|
|
7577
|
+
return buildSqrtScale(channel, data, rangeStart, rangeEnd);
|
|
7578
|
+
case "symlog":
|
|
7579
|
+
return buildSymlogScale(channel, data, rangeStart, rangeEnd);
|
|
7580
|
+
case "quantile":
|
|
7581
|
+
return buildQuantileScale(channel, data, rangeStart, rangeEnd);
|
|
7582
|
+
case "quantize":
|
|
7583
|
+
return buildQuantizeScale(channel, data, rangeStart, rangeEnd);
|
|
7584
|
+
case "threshold":
|
|
7585
|
+
return buildThresholdScale(channel, data, rangeStart, rangeEnd);
|
|
6527
7586
|
case "band":
|
|
6528
7587
|
return buildBandScale(channel, data, rangeStart, rangeEnd);
|
|
6529
7588
|
case "point":
|
|
@@ -6539,7 +7598,7 @@ function buildPositionalScale(channel, data, rangeStart, rangeEnd, chartType, ax
|
|
|
6539
7598
|
return buildLinearScale(channel, data, rangeStart, rangeEnd);
|
|
6540
7599
|
case "nominal":
|
|
6541
7600
|
case "ordinal":
|
|
6542
|
-
if (chartType === "bar"
|
|
7601
|
+
if (chartType === "bar" || chartType === "circle" && axis === "y") {
|
|
6543
7602
|
return buildBandScale(channel, data, rangeStart, rangeEnd);
|
|
6544
7603
|
}
|
|
6545
7604
|
return buildPointScale(channel, data, rangeStart, rangeEnd);
|
|
@@ -6550,7 +7609,7 @@ function buildPositionalScale(channel, data, rangeStart, rangeEnd, chartType, ax
|
|
|
6550
7609
|
function computeScales(spec, chartArea, data) {
|
|
6551
7610
|
const result = {};
|
|
6552
7611
|
const encoding = spec.encoding;
|
|
6553
|
-
if (spec.
|
|
7612
|
+
if (spec.markType === "point") {
|
|
6554
7613
|
if (encoding.x?.type === "quantitative" && encoding.x.scale?.zero === void 0) {
|
|
6555
7614
|
if (!encoding.x.scale) {
|
|
6556
7615
|
encoding.x.scale = { zero: false };
|
|
@@ -6568,7 +7627,7 @@ function computeScales(spec, chartArea, data) {
|
|
|
6568
7627
|
}
|
|
6569
7628
|
if (encoding.x) {
|
|
6570
7629
|
let xData = data;
|
|
6571
|
-
if (spec.
|
|
7630
|
+
if (spec.markType === "bar" && encoding.color && encoding.x.type === "quantitative") {
|
|
6572
7631
|
const yField = encoding.y?.field;
|
|
6573
7632
|
const xField = encoding.x.field;
|
|
6574
7633
|
if (yField) {
|
|
@@ -6589,13 +7648,14 @@ function computeScales(spec, chartArea, data) {
|
|
|
6589
7648
|
xData,
|
|
6590
7649
|
chartArea.x,
|
|
6591
7650
|
chartArea.x + chartArea.width,
|
|
6592
|
-
spec.
|
|
7651
|
+
spec.markType,
|
|
6593
7652
|
"x"
|
|
6594
7653
|
);
|
|
6595
7654
|
}
|
|
6596
7655
|
if (encoding.y) {
|
|
6597
7656
|
let yData = data;
|
|
6598
|
-
|
|
7657
|
+
const isVerticalBar = spec.markType === "bar" && (encoding.x?.type === "nominal" || encoding.x?.type === "ordinal") && encoding.y.type === "quantitative";
|
|
7658
|
+
if ((isVerticalBar || spec.markType === "area") && encoding.color && encoding.y.type === "quantitative") {
|
|
6599
7659
|
const xField = encoding.x?.field;
|
|
6600
7660
|
const yField = encoding.y.field;
|
|
6601
7661
|
if (xField) {
|
|
@@ -6616,7 +7676,7 @@ function computeScales(spec, chartArea, data) {
|
|
|
6616
7676
|
yData,
|
|
6617
7677
|
chartArea.y + chartArea.height,
|
|
6618
7678
|
chartArea.y,
|
|
6619
|
-
spec.
|
|
7679
|
+
spec.markType,
|
|
6620
7680
|
"y"
|
|
6621
7681
|
);
|
|
6622
7682
|
}
|
|
@@ -6633,10 +7693,12 @@ function computeScales(spec, chartArea, data) {
|
|
|
6633
7693
|
"#a88f22",
|
|
6634
7694
|
"#858078"
|
|
6635
7695
|
];
|
|
6636
|
-
if (encoding.color
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
7696
|
+
if ("field" in encoding.color) {
|
|
7697
|
+
if (encoding.color.type === "quantitative") {
|
|
7698
|
+
result.color = buildSequentialColorScale(encoding.color, data, defaultPalette);
|
|
7699
|
+
} else {
|
|
7700
|
+
result.color = buildOrdinalColorScale(encoding.color, data, defaultPalette);
|
|
7701
|
+
}
|
|
6640
7702
|
}
|
|
6641
7703
|
}
|
|
6642
7704
|
return result;
|
|
@@ -6651,12 +7713,12 @@ var LEGEND_PADDING = 8;
|
|
|
6651
7713
|
var LEGEND_RIGHT_WIDTH = 120;
|
|
6652
7714
|
var RIGHT_LEGEND_MAX_HEIGHT_RATIO = 0.4;
|
|
6653
7715
|
var TOP_LEGEND_MAX_ROWS = 2;
|
|
6654
|
-
function swatchShapeForType(
|
|
6655
|
-
switch (
|
|
7716
|
+
function swatchShapeForType(markType) {
|
|
7717
|
+
switch (markType) {
|
|
6656
7718
|
case "line":
|
|
6657
7719
|
return "line";
|
|
6658
|
-
case "
|
|
6659
|
-
case "
|
|
7720
|
+
case "point":
|
|
7721
|
+
case "circle":
|
|
6660
7722
|
return "circle";
|
|
6661
7723
|
default:
|
|
6662
7724
|
return "square";
|
|
@@ -6665,10 +7727,11 @@ function swatchShapeForType(chartType) {
|
|
|
6665
7727
|
function extractColorEntries(spec, theme) {
|
|
6666
7728
|
const colorEnc = spec.encoding.color;
|
|
6667
7729
|
if (!colorEnc) return [];
|
|
7730
|
+
if ("condition" in colorEnc) return [];
|
|
6668
7731
|
if (colorEnc.type === "quantitative") return [];
|
|
6669
7732
|
const uniqueValues = [...new Set(spec.data.map((d) => String(d[colorEnc.field])))];
|
|
6670
7733
|
const palette = theme.colors.categorical;
|
|
6671
|
-
const shape = swatchShapeForType(spec.
|
|
7734
|
+
const shape = swatchShapeForType(spec.markType);
|
|
6672
7735
|
return uniqueValues.map((value, i) => ({
|
|
6673
7736
|
label: value,
|
|
6674
7737
|
color: palette[i % palette.length],
|
|
@@ -7492,7 +8555,7 @@ function buildFields(row, encoding, color2) {
|
|
|
7492
8555
|
value: formatValue(row[encoding.x.field], encoding.x.type, encoding.x.axis?.format)
|
|
7493
8556
|
});
|
|
7494
8557
|
}
|
|
7495
|
-
if (encoding.size) {
|
|
8558
|
+
if (encoding.size && "field" in encoding.size) {
|
|
7496
8559
|
fields.push({
|
|
7497
8560
|
label: encoding.size.axis?.label ?? encoding.size.field,
|
|
7498
8561
|
value: formatValue(row[encoding.size.field], encoding.size.type, encoding.size.axis?.format)
|
|
@@ -7513,12 +8576,20 @@ function getTooltipTitle(row, encoding) {
|
|
|
7513
8576
|
if (encoding.y?.type === "nominal" || encoding.y?.type === "ordinal") {
|
|
7514
8577
|
return String(row[encoding.y.field] ?? "");
|
|
7515
8578
|
}
|
|
7516
|
-
if (encoding.color) {
|
|
8579
|
+
if (encoding.color && "field" in encoding.color) {
|
|
7517
8580
|
return String(row[encoding.color.field] ?? "");
|
|
7518
8581
|
}
|
|
7519
8582
|
return void 0;
|
|
7520
8583
|
}
|
|
7521
|
-
function tooltipsForLine(
|
|
8584
|
+
function tooltipsForLine(mark, encoding, _markIndex) {
|
|
8585
|
+
if (mark.dataPoints) {
|
|
8586
|
+
for (const dp of mark.dataPoints) {
|
|
8587
|
+
dp.tooltip = {
|
|
8588
|
+
title: getTooltipTitle(dp.datum, encoding),
|
|
8589
|
+
fields: buildFields(dp.datum, encoding, mark.stroke)
|
|
8590
|
+
};
|
|
8591
|
+
}
|
|
8592
|
+
}
|
|
7522
8593
|
return [];
|
|
7523
8594
|
}
|
|
7524
8595
|
function tooltipsForPoint(mark, encoding, markIndex) {
|
|
@@ -7534,8 +8605,9 @@ function tooltipsForRect(mark, encoding, markIndex) {
|
|
|
7534
8605
|
function tooltipsForArc(mark, encoding, markIndex) {
|
|
7535
8606
|
const row = mark.data;
|
|
7536
8607
|
const fields = [];
|
|
7537
|
-
|
|
7538
|
-
|
|
8608
|
+
const colorEnc = encoding.color && "field" in encoding.color ? encoding.color : void 0;
|
|
8609
|
+
if (colorEnc) {
|
|
8610
|
+
const categoryName = String(row[colorEnc.field] ?? "");
|
|
7539
8611
|
if (encoding.y) {
|
|
7540
8612
|
fields.push({
|
|
7541
8613
|
label: categoryName,
|
|
@@ -7550,10 +8622,18 @@ function tooltipsForArc(mark, encoding, markIndex) {
|
|
|
7550
8622
|
color: mark.fill
|
|
7551
8623
|
});
|
|
7552
8624
|
}
|
|
7553
|
-
const title =
|
|
8625
|
+
const title = colorEnc ? String(row[colorEnc.field] ?? "") : void 0;
|
|
7554
8626
|
return [[`arc-${markIndex}`, { title, fields }]];
|
|
7555
8627
|
}
|
|
7556
|
-
function tooltipsForArea(
|
|
8628
|
+
function tooltipsForArea(mark, encoding, _markIndex) {
|
|
8629
|
+
if (mark.dataPoints) {
|
|
8630
|
+
for (const dp of mark.dataPoints) {
|
|
8631
|
+
dp.tooltip = {
|
|
8632
|
+
title: getTooltipTitle(dp.datum, encoding),
|
|
8633
|
+
fields: buildFields(dp.datum, encoding, mark.fill)
|
|
8634
|
+
};
|
|
8635
|
+
}
|
|
8636
|
+
}
|
|
7557
8637
|
return [];
|
|
7558
8638
|
}
|
|
7559
8639
|
function computeTooltipDescriptors(spec, marks) {
|
|
@@ -7586,16 +8666,205 @@ function computeTooltipDescriptors(spec, marks) {
|
|
|
7586
8666
|
return descriptors;
|
|
7587
8667
|
}
|
|
7588
8668
|
|
|
8669
|
+
// src/transforms/bin.ts
|
|
8670
|
+
function computeStep(extent2, maxbins, nice2) {
|
|
8671
|
+
const span = extent2[1] - extent2[0];
|
|
8672
|
+
if (span === 0) return 1;
|
|
8673
|
+
let step = span / maxbins;
|
|
8674
|
+
if (nice2) {
|
|
8675
|
+
const magnitude = 10 ** Math.floor(Math.log10(step));
|
|
8676
|
+
const residual = step / magnitude;
|
|
8677
|
+
if (residual <= 1.5) {
|
|
8678
|
+
step = magnitude;
|
|
8679
|
+
} else if (residual <= 3.5) {
|
|
8680
|
+
step = 2 * magnitude;
|
|
8681
|
+
} else if (residual <= 7.5) {
|
|
8682
|
+
step = 5 * magnitude;
|
|
8683
|
+
} else {
|
|
8684
|
+
step = 10 * magnitude;
|
|
8685
|
+
}
|
|
8686
|
+
}
|
|
8687
|
+
return step;
|
|
8688
|
+
}
|
|
8689
|
+
function runBin(data, transform) {
|
|
8690
|
+
const params = transform.bin === true ? {} : transform.bin;
|
|
8691
|
+
const maxbins = params.maxbins ?? 10;
|
|
8692
|
+
const nice2 = params.nice ?? true;
|
|
8693
|
+
const field = transform.field;
|
|
8694
|
+
let extent2 = params.extent;
|
|
8695
|
+
if (!extent2) {
|
|
8696
|
+
let min3 = Infinity;
|
|
8697
|
+
let max3 = -Infinity;
|
|
8698
|
+
for (const row of data) {
|
|
8699
|
+
const v = Number(row[field]);
|
|
8700
|
+
if (Number.isFinite(v)) {
|
|
8701
|
+
if (v < min3) min3 = v;
|
|
8702
|
+
if (v > max3) max3 = v;
|
|
8703
|
+
}
|
|
8704
|
+
}
|
|
8705
|
+
extent2 = [min3 === Infinity ? 0 : min3, max3 === -Infinity ? 0 : max3];
|
|
8706
|
+
}
|
|
8707
|
+
const step = params.step ?? computeStep(extent2, maxbins, nice2);
|
|
8708
|
+
const [startAs, endAs] = Array.isArray(transform.as) ? transform.as : [transform.as, void 0];
|
|
8709
|
+
return data.map((row) => {
|
|
8710
|
+
const v = Number(row[field]);
|
|
8711
|
+
const newRow = { ...row };
|
|
8712
|
+
if (!Number.isFinite(v)) {
|
|
8713
|
+
newRow[startAs] = null;
|
|
8714
|
+
if (endAs) newRow[endAs] = null;
|
|
8715
|
+
} else {
|
|
8716
|
+
const binStart = Math.floor((v - extent2[0]) / step) * step + extent2[0];
|
|
8717
|
+
newRow[startAs] = binStart;
|
|
8718
|
+
if (endAs) newRow[endAs] = binStart + step;
|
|
8719
|
+
}
|
|
8720
|
+
return newRow;
|
|
8721
|
+
});
|
|
8722
|
+
}
|
|
8723
|
+
|
|
8724
|
+
// src/transforms/calculate.ts
|
|
8725
|
+
function evaluateExpression(datum, expr) {
|
|
8726
|
+
const fieldValue = Number(datum[expr.field]);
|
|
8727
|
+
switch (expr.op) {
|
|
8728
|
+
case "abs":
|
|
8729
|
+
return Math.abs(fieldValue);
|
|
8730
|
+
case "round":
|
|
8731
|
+
return Math.round(fieldValue);
|
|
8732
|
+
case "floor":
|
|
8733
|
+
return Math.floor(fieldValue);
|
|
8734
|
+
case "ceil":
|
|
8735
|
+
return Math.ceil(fieldValue);
|
|
8736
|
+
case "log":
|
|
8737
|
+
return Math.log(fieldValue);
|
|
8738
|
+
case "sqrt":
|
|
8739
|
+
return Math.sqrt(fieldValue);
|
|
8740
|
+
}
|
|
8741
|
+
const operand = expr.field2 !== void 0 ? Number(datum[expr.field2]) : expr.value ?? 0;
|
|
8742
|
+
switch (expr.op) {
|
|
8743
|
+
case "+":
|
|
8744
|
+
return fieldValue + operand;
|
|
8745
|
+
case "-":
|
|
8746
|
+
return fieldValue - operand;
|
|
8747
|
+
case "*":
|
|
8748
|
+
return fieldValue * operand;
|
|
8749
|
+
case "/":
|
|
8750
|
+
return operand === 0 ? NaN : fieldValue / operand;
|
|
8751
|
+
}
|
|
8752
|
+
}
|
|
8753
|
+
function runCalculate(data, transform) {
|
|
8754
|
+
return data.map((row) => ({
|
|
8755
|
+
...row,
|
|
8756
|
+
[transform.as]: evaluateExpression(row, transform.calculate)
|
|
8757
|
+
}));
|
|
8758
|
+
}
|
|
8759
|
+
|
|
8760
|
+
// src/transforms/filter.ts
|
|
8761
|
+
function runFilter(data, predicate) {
|
|
8762
|
+
return data.filter((datum) => evaluatePredicate(datum, predicate));
|
|
8763
|
+
}
|
|
8764
|
+
|
|
8765
|
+
// src/transforms/timeunit.ts
|
|
8766
|
+
function extractTimeUnit(date2, unit2) {
|
|
8767
|
+
switch (unit2) {
|
|
8768
|
+
case "year":
|
|
8769
|
+
return date2.getFullYear();
|
|
8770
|
+
case "quarter":
|
|
8771
|
+
return Math.floor(date2.getMonth() / 3) + 1;
|
|
8772
|
+
case "month":
|
|
8773
|
+
return date2.getMonth();
|
|
8774
|
+
// 0-indexed like JS Date
|
|
8775
|
+
case "week": {
|
|
8776
|
+
const d = new Date(date2.getTime());
|
|
8777
|
+
d.setHours(0, 0, 0, 0);
|
|
8778
|
+
d.setDate(d.getDate() + 3 - (d.getDay() + 6) % 7);
|
|
8779
|
+
const yearStart = new Date(d.getFullYear(), 0, 1);
|
|
8780
|
+
return Math.ceil(((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
8781
|
+
}
|
|
8782
|
+
case "day":
|
|
8783
|
+
return date2.getDay();
|
|
8784
|
+
// 0 = Sunday
|
|
8785
|
+
case "dayofyear": {
|
|
8786
|
+
const start = new Date(date2.getFullYear(), 0, 0);
|
|
8787
|
+
const diff = date2.getTime() - start.getTime();
|
|
8788
|
+
return Math.floor(diff / 864e5);
|
|
8789
|
+
}
|
|
8790
|
+
case "date":
|
|
8791
|
+
return date2.getDate();
|
|
8792
|
+
// 1-31
|
|
8793
|
+
case "hours":
|
|
8794
|
+
return date2.getHours();
|
|
8795
|
+
case "minutes":
|
|
8796
|
+
return date2.getMinutes();
|
|
8797
|
+
case "seconds":
|
|
8798
|
+
return date2.getSeconds();
|
|
8799
|
+
case "milliseconds":
|
|
8800
|
+
return date2.getMilliseconds();
|
|
8801
|
+
// Compound units
|
|
8802
|
+
case "yearmonth":
|
|
8803
|
+
return `${date2.getFullYear()}-${String(date2.getMonth() + 1).padStart(2, "0")}`;
|
|
8804
|
+
case "yearmonthdate":
|
|
8805
|
+
return `${date2.getFullYear()}-${String(date2.getMonth() + 1).padStart(2, "0")}-${String(date2.getDate()).padStart(2, "0")}`;
|
|
8806
|
+
case "monthdate":
|
|
8807
|
+
return `${String(date2.getMonth() + 1).padStart(2, "0")}-${String(date2.getDate()).padStart(2, "0")}`;
|
|
8808
|
+
case "hoursminutes":
|
|
8809
|
+
return `${String(date2.getHours()).padStart(2, "0")}:${String(date2.getMinutes()).padStart(2, "0")}`;
|
|
8810
|
+
}
|
|
8811
|
+
}
|
|
8812
|
+
function toDate(value) {
|
|
8813
|
+
if (value instanceof Date) return value;
|
|
8814
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
8815
|
+
const d = new Date(value);
|
|
8816
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
8817
|
+
}
|
|
8818
|
+
return null;
|
|
8819
|
+
}
|
|
8820
|
+
function runTimeUnit(data, transform) {
|
|
8821
|
+
return data.map((row) => {
|
|
8822
|
+
const date2 = toDate(row[transform.field]);
|
|
8823
|
+
return {
|
|
8824
|
+
...row,
|
|
8825
|
+
[transform.as]: date2 ? extractTimeUnit(date2, transform.timeUnit) : null
|
|
8826
|
+
};
|
|
8827
|
+
});
|
|
8828
|
+
}
|
|
8829
|
+
|
|
8830
|
+
// src/transforms/index.ts
|
|
8831
|
+
function runTransforms(data, transforms) {
|
|
8832
|
+
let result = data;
|
|
8833
|
+
for (const transform of transforms) {
|
|
8834
|
+
if ("filter" in transform) {
|
|
8835
|
+
result = runFilter(result, transform.filter);
|
|
8836
|
+
} else if ("bin" in transform) {
|
|
8837
|
+
result = runBin(result, transform);
|
|
8838
|
+
} else if ("calculate" in transform) {
|
|
8839
|
+
result = runCalculate(result, transform);
|
|
8840
|
+
} else if ("timeUnit" in transform) {
|
|
8841
|
+
result = runTimeUnit(result, transform);
|
|
8842
|
+
}
|
|
8843
|
+
}
|
|
8844
|
+
return result;
|
|
8845
|
+
}
|
|
8846
|
+
|
|
7589
8847
|
// src/compile.ts
|
|
7590
8848
|
var builtinRenderers = {
|
|
7591
8849
|
line: lineRenderer,
|
|
7592
8850
|
area: areaRenderer,
|
|
7593
8851
|
bar: barRenderer,
|
|
7594
|
-
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
8852
|
+
// horizontal bars
|
|
8853
|
+
"bar:vertical": columnRenderer,
|
|
8854
|
+
// vertical bars (old 'column')
|
|
8855
|
+
point: scatterRenderer,
|
|
8856
|
+
// old 'scatter'
|
|
8857
|
+
arc: pieRenderer,
|
|
8858
|
+
// old 'pie' (donut handled via innerRadius)
|
|
8859
|
+
"arc:donut": donutRenderer,
|
|
8860
|
+
// old 'donut'
|
|
8861
|
+
circle: dotRenderer,
|
|
8862
|
+
// old 'dot'
|
|
8863
|
+
text: textRenderer,
|
|
8864
|
+
rule: ruleRenderer,
|
|
8865
|
+
tick: tickRenderer,
|
|
8866
|
+
rect: columnRenderer
|
|
8867
|
+
// rect uses column renderer (RectMark output) as baseline for heatmaps
|
|
7599
8868
|
};
|
|
7600
8869
|
for (const [type, renderer] of Object.entries(builtinRenderers)) {
|
|
7601
8870
|
registerChartRenderer(type, renderer);
|
|
@@ -7665,13 +8934,17 @@ function computeBandRowObstacles(marks, scales) {
|
|
|
7665
8934
|
}
|
|
7666
8935
|
function compileChart(spec, options) {
|
|
7667
8936
|
const { spec: normalized } = compile(spec);
|
|
7668
|
-
if (normalized.type === "table") {
|
|
8937
|
+
if ("type" in normalized && normalized.type === "table") {
|
|
7669
8938
|
throw new Error("compileChart received a table spec. Use compileTable instead.");
|
|
7670
8939
|
}
|
|
7671
|
-
if (normalized.type === "graph") {
|
|
8940
|
+
if ("type" in normalized && normalized.type === "graph") {
|
|
7672
8941
|
throw new Error("compileChart received a graph spec. Use compileGraph instead.");
|
|
7673
8942
|
}
|
|
7674
8943
|
let chartSpec = normalized;
|
|
8944
|
+
const rawTransforms = spec.transform;
|
|
8945
|
+
if (rawTransforms && rawTransforms.length > 0) {
|
|
8946
|
+
chartSpec = { ...chartSpec, data: runTransforms(chartSpec.data, rawTransforms) };
|
|
8947
|
+
}
|
|
7675
8948
|
const breakpoint = getBreakpoint(options.width);
|
|
7676
8949
|
const heightClass = getHeightClass(options.height);
|
|
7677
8950
|
const strategy = getLayoutStrategy(breakpoint, heightClass);
|
|
@@ -7745,7 +9018,7 @@ function compileChart(spec, options) {
|
|
|
7745
9018
|
}
|
|
7746
9019
|
const finalLegend = computeLegend(chartSpec, strategy, theme, legendArea);
|
|
7747
9020
|
let renderData = chartSpec.data;
|
|
7748
|
-
if (chartSpec.hiddenSeries.length > 0 && chartSpec.encoding.color) {
|
|
9021
|
+
if (chartSpec.hiddenSeries.length > 0 && chartSpec.encoding.color && "field" in chartSpec.encoding.color) {
|
|
7749
9022
|
const colorField = chartSpec.encoding.color.field;
|
|
7750
9023
|
const hiddenSet = new Set(chartSpec.hiddenSeries);
|
|
7751
9024
|
renderData = renderData.filter((row) => !hiddenSet.has(String(row[colorField])));
|
|
@@ -7779,12 +9052,26 @@ function compileChart(spec, options) {
|
|
|
7779
9052
|
}
|
|
7780
9053
|
}
|
|
7781
9054
|
scales.defaultColor = theme.colors.categorical[0];
|
|
7782
|
-
const isRadial = chartSpec.
|
|
9055
|
+
const isRadial = chartSpec.markType === "arc";
|
|
7783
9056
|
const axes = isRadial ? { x: void 0, y: void 0 } : computeAxes(scales, chartArea, strategy, theme, options.measureText);
|
|
7784
9057
|
if (!isRadial) {
|
|
7785
9058
|
computeGridlines(axes, chartArea);
|
|
7786
9059
|
}
|
|
7787
|
-
|
|
9060
|
+
let rendererKey = renderSpec.markType;
|
|
9061
|
+
if (rendererKey === "bar") {
|
|
9062
|
+
const xType = renderSpec.encoding.x?.type;
|
|
9063
|
+
const yType = renderSpec.encoding.y?.type;
|
|
9064
|
+
const isVertical = (xType === "nominal" || xType === "ordinal" || xType === "temporal") && yType === "quantitative";
|
|
9065
|
+
if (isVertical) {
|
|
9066
|
+
rendererKey = "bar:vertical";
|
|
9067
|
+
}
|
|
9068
|
+
} else if (rendererKey === "arc") {
|
|
9069
|
+
const innerRadius = renderSpec.markDef.innerRadius;
|
|
9070
|
+
if (innerRadius && innerRadius > 0) {
|
|
9071
|
+
rendererKey = "arc:donut";
|
|
9072
|
+
}
|
|
9073
|
+
}
|
|
9074
|
+
const renderer = getChartRenderer(rendererKey);
|
|
7788
9075
|
const marks = renderer ? renderer(renderSpec, scales, chartArea, strategy, theme) : [];
|
|
7789
9076
|
const obstacles = [];
|
|
7790
9077
|
if (finalLegend.bounds.width > 0) {
|
|
@@ -7792,7 +9079,7 @@ function compileChart(spec, options) {
|
|
|
7792
9079
|
}
|
|
7793
9080
|
obstacles.push(...computeMarkObstacles(marks, scales));
|
|
7794
9081
|
for (const mark of marks) {
|
|
7795
|
-
if (
|
|
9082
|
+
if ("label" in mark && mark.label?.visible) {
|
|
7796
9083
|
obstacles.push(computeLabelBounds(mark.label));
|
|
7797
9084
|
}
|
|
7798
9085
|
}
|
|
@@ -7813,7 +9100,7 @@ function compileChart(spec, options) {
|
|
|
7813
9100
|
const tooltipDescriptors = computeTooltipDescriptors(chartSpec, marks);
|
|
7814
9101
|
const altText = generateAltText(
|
|
7815
9102
|
{
|
|
7816
|
-
|
|
9103
|
+
mark: chartSpec.markType,
|
|
7817
9104
|
data: chartSpec.data,
|
|
7818
9105
|
encoding: chartSpec.encoding,
|
|
7819
9106
|
chrome: chartSpec.chrome
|
|
@@ -7822,7 +9109,7 @@ function compileChart(spec, options) {
|
|
|
7822
9109
|
);
|
|
7823
9110
|
const dataTableFallback = generateDataTable(
|
|
7824
9111
|
{
|
|
7825
|
-
|
|
9112
|
+
mark: chartSpec.markType,
|
|
7826
9113
|
data: chartSpec.data,
|
|
7827
9114
|
encoding: chartSpec.encoding
|
|
7828
9115
|
},
|
|
@@ -7852,10 +9139,63 @@ function compileChart(spec, options) {
|
|
|
7852
9139
|
}
|
|
7853
9140
|
};
|
|
7854
9141
|
}
|
|
9142
|
+
function compileLayer(spec, options) {
|
|
9143
|
+
const leaves = flattenLayers(spec);
|
|
9144
|
+
if (leaves.length === 0) {
|
|
9145
|
+
throw new Error("LayerSpec has no leaf chart specs after flattening");
|
|
9146
|
+
}
|
|
9147
|
+
if (leaves.length === 1) {
|
|
9148
|
+
const singleSpec = buildPrimarySpec(leaves, spec);
|
|
9149
|
+
return compileChart(singleSpec, options);
|
|
9150
|
+
}
|
|
9151
|
+
const primarySpec = buildPrimarySpec(leaves, spec);
|
|
9152
|
+
const primaryLayout = compileChart(primarySpec, options);
|
|
9153
|
+
const allMarks = [];
|
|
9154
|
+
const seenLabels = /* @__PURE__ */ new Set();
|
|
9155
|
+
const mergedLegendEntries = [...primaryLayout.legend.entries];
|
|
9156
|
+
for (const entry of mergedLegendEntries) {
|
|
9157
|
+
seenLabels.add(entry.label);
|
|
9158
|
+
}
|
|
9159
|
+
for (const leaf of leaves) {
|
|
9160
|
+
const leafLayout = compileChart(leaf, options);
|
|
9161
|
+
allMarks.push(...leafLayout.marks);
|
|
9162
|
+
for (const entry of leafLayout.legend.entries) {
|
|
9163
|
+
if (!seenLabels.has(entry.label)) {
|
|
9164
|
+
seenLabels.add(entry.label);
|
|
9165
|
+
mergedLegendEntries.push(entry);
|
|
9166
|
+
}
|
|
9167
|
+
}
|
|
9168
|
+
}
|
|
9169
|
+
return {
|
|
9170
|
+
...primaryLayout,
|
|
9171
|
+
marks: allMarks,
|
|
9172
|
+
legend: {
|
|
9173
|
+
...primaryLayout.legend,
|
|
9174
|
+
entries: mergedLegendEntries
|
|
9175
|
+
}
|
|
9176
|
+
};
|
|
9177
|
+
}
|
|
9178
|
+
function buildPrimarySpec(leaves, layerSpec) {
|
|
9179
|
+
const allData = leaves.flatMap((leaf) => leaf.data);
|
|
9180
|
+
const primary = {
|
|
9181
|
+
...leaves[0],
|
|
9182
|
+
data: allData,
|
|
9183
|
+
// Layer-level chrome overrides leaf chrome
|
|
9184
|
+
chrome: layerSpec.chrome ?? leaves[0].chrome,
|
|
9185
|
+
labels: layerSpec.labels ?? leaves[0].labels,
|
|
9186
|
+
legend: layerSpec.legend ?? leaves[0].legend,
|
|
9187
|
+
responsive: layerSpec.responsive ?? leaves[0].responsive,
|
|
9188
|
+
theme: layerSpec.theme ?? leaves[0].theme,
|
|
9189
|
+
darkMode: layerSpec.darkMode ?? leaves[0].darkMode,
|
|
9190
|
+
hiddenSeries: layerSpec.hiddenSeries ?? leaves[0].hiddenSeries
|
|
9191
|
+
};
|
|
9192
|
+
return primary;
|
|
9193
|
+
}
|
|
7855
9194
|
function compileTable(spec, options) {
|
|
7856
9195
|
const { spec: normalized } = compile(spec);
|
|
7857
|
-
|
|
7858
|
-
|
|
9196
|
+
const normType = "type" in normalized ? normalized.type : void 0;
|
|
9197
|
+
if (normType !== "table") {
|
|
9198
|
+
throw new Error(`compileTable received a non-table spec. Use compileChart instead.`);
|
|
7859
9199
|
}
|
|
7860
9200
|
const tableSpec = normalized;
|
|
7861
9201
|
const mergedThemeConfig = options.theme ? { ...tableSpec.theme, ...options.theme } : tableSpec.theme;
|
|
@@ -7873,10 +9213,19 @@ export {
|
|
|
7873
9213
|
compile,
|
|
7874
9214
|
compileChart,
|
|
7875
9215
|
compileGraph2 as compileGraph,
|
|
9216
|
+
compileLayer,
|
|
7876
9217
|
compileTable,
|
|
9218
|
+
evaluatePredicate,
|
|
7877
9219
|
getChartRenderer,
|
|
9220
|
+
isConditionalValueDef,
|
|
7878
9221
|
normalizeSpec,
|
|
7879
9222
|
registerChartRenderer,
|
|
9223
|
+
resolveConditionalValue,
|
|
9224
|
+
runBin,
|
|
9225
|
+
runCalculate,
|
|
9226
|
+
runFilter,
|
|
9227
|
+
runTimeUnit,
|
|
9228
|
+
runTransforms,
|
|
7880
9229
|
validateSpec
|
|
7881
9230
|
};
|
|
7882
9231
|
//# sourceMappingURL=index.js.map
|