@opendata-ai/openchart-engine 8.5.2 → 8.6.1
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 +4 -0
- package/dist/index.js +640 -58
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2618,16 +2618,16 @@ function computeStackedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
2618
2618
|
}
|
|
2619
2619
|
let cumulativeValue = stackMode === "center" ? -categoryTotal / 2 : 0;
|
|
2620
2620
|
for (const row of rows) {
|
|
2621
|
-
const
|
|
2621
|
+
const groupKey4 = String(row[colorField] ?? "");
|
|
2622
2622
|
const rawValue = Number(row[valueField] ?? 0);
|
|
2623
2623
|
if (!Number.isFinite(rawValue) || rawValue <= 0) continue;
|
|
2624
2624
|
const value = stackMode === "normalize" && categoryTotal > 0 ? rawValue / categoryTotal : rawValue;
|
|
2625
|
-
const color = getColor(scales,
|
|
2625
|
+
const color = getColor(scales, groupKey4);
|
|
2626
2626
|
const xLeft = xScale(cumulativeValue);
|
|
2627
2627
|
const xRight = xScale(cumulativeValue + value);
|
|
2628
2628
|
const barWidth = Math.max(Math.abs(xRight - xLeft), MIN_BAR_WIDTH);
|
|
2629
2629
|
const aria = {
|
|
2630
|
-
label: `${category}, ${
|
|
2630
|
+
label: `${category}, ${groupKey4}: ${formatLabelValue(rawValue)}`
|
|
2631
2631
|
};
|
|
2632
2632
|
marks.push({
|
|
2633
2633
|
type: "rect",
|
|
@@ -2637,7 +2637,7 @@ function computeStackedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
2637
2637
|
height: bandwidth,
|
|
2638
2638
|
fill: isGradientDef(color) ? orientGradientForHorizontalBar(color) : color,
|
|
2639
2639
|
cornerRadius: 0,
|
|
2640
|
-
seriesKey:
|
|
2640
|
+
seriesKey: groupKey4,
|
|
2641
2641
|
data: row,
|
|
2642
2642
|
aria,
|
|
2643
2643
|
orient: "horizontal",
|
|
@@ -2676,16 +2676,16 @@ function computeGroupedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
2676
2676
|
const bandY = yScale(category);
|
|
2677
2677
|
if (bandY === void 0) continue;
|
|
2678
2678
|
for (const row of rows) {
|
|
2679
|
-
const
|
|
2679
|
+
const groupKey4 = String(row[colorField] ?? "");
|
|
2680
2680
|
const value = Number(row[valueField] ?? 0);
|
|
2681
2681
|
if (!Number.isFinite(value)) continue;
|
|
2682
|
-
const groupIndex = groupIndexMap.get(
|
|
2683
|
-
const color = getColor(scales,
|
|
2682
|
+
const groupIndex = groupIndexMap.get(groupKey4) ?? 0;
|
|
2683
|
+
const color = getColor(scales, groupKey4);
|
|
2684
2684
|
const xPos = value >= 0 ? baseline : xScale(value);
|
|
2685
2685
|
const barWidth = Math.max(Math.abs(xScale(value) - baseline), MIN_BAR_WIDTH);
|
|
2686
2686
|
const subY = bandY + groupOffset + groupIndex * (subBandHeight + gap);
|
|
2687
2687
|
const aria = {
|
|
2688
|
-
label: `${category}, ${
|
|
2688
|
+
label: `${category}, ${groupKey4}: ${formatLabelValue(value)}`
|
|
2689
2689
|
};
|
|
2690
2690
|
marks.push({
|
|
2691
2691
|
type: "rect",
|
|
@@ -2696,7 +2696,7 @@ function computeGroupedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
2696
2696
|
fill: isGradientDef(color) ? orientGradientForHorizontalBar(color) : color,
|
|
2697
2697
|
cornerRadius: BAR_CORNER_RADIUS,
|
|
2698
2698
|
cornerRadiusSides: valueEndCorners("horizontal", value < 0),
|
|
2699
|
-
seriesKey:
|
|
2699
|
+
seriesKey: groupKey4,
|
|
2700
2700
|
data: row,
|
|
2701
2701
|
aria,
|
|
2702
2702
|
orient: "horizontal"
|
|
@@ -2713,12 +2713,12 @@ function computeColoredBars(data, valueField, categoryField, colorField, xScale,
|
|
|
2713
2713
|
if (!Number.isFinite(value)) continue;
|
|
2714
2714
|
const bandY = yScale(category);
|
|
2715
2715
|
if (bandY === void 0) continue;
|
|
2716
|
-
const
|
|
2717
|
-
const color = getColor(scales,
|
|
2716
|
+
const groupKey4 = String(row[colorField] ?? "");
|
|
2717
|
+
const color = getColor(scales, groupKey4);
|
|
2718
2718
|
const xPos = value >= 0 ? baseline : xScale(value);
|
|
2719
2719
|
const barWidth = Math.max(Math.abs(xScale(value) - baseline), MIN_BAR_WIDTH);
|
|
2720
2720
|
const aria = {
|
|
2721
|
-
label: `${category}, ${
|
|
2721
|
+
label: `${category}, ${groupKey4}: ${formatLabelValue(value)}`
|
|
2722
2722
|
};
|
|
2723
2723
|
marks.push({
|
|
2724
2724
|
type: "rect",
|
|
@@ -2729,7 +2729,7 @@ function computeColoredBars(data, valueField, categoryField, colorField, xScale,
|
|
|
2729
2729
|
fill: isGradientDef(color) ? orientGradientForHorizontalBar(color) : color,
|
|
2730
2730
|
cornerRadius: BAR_CORNER_RADIUS,
|
|
2731
2731
|
cornerRadiusSides: valueEndCorners("horizontal", value < 0),
|
|
2732
|
-
seriesKey:
|
|
2732
|
+
seriesKey: groupKey4,
|
|
2733
2733
|
data: row,
|
|
2734
2734
|
aria,
|
|
2735
2735
|
orient: "horizontal"
|
|
@@ -3248,6 +3248,140 @@ var beeswarmRenderer = (spec, scales, chartArea, strategy, theme) => {
|
|
|
3248
3248
|
return computeBeeswarmMarks(spec, scales, chartArea, strategy, theme);
|
|
3249
3249
|
};
|
|
3250
3250
|
|
|
3251
|
+
// src/charts/binned-bar/compute.ts
|
|
3252
|
+
import { formatNumber } from "@opendata-ai/openchart-core";
|
|
3253
|
+
var OVERLAP_FILL_OPACITY = 0.55;
|
|
3254
|
+
var BIN_GAP = 1;
|
|
3255
|
+
var MIN_BIN_WIDTH = 1;
|
|
3256
|
+
var MIN_BAR_HEIGHT = 1;
|
|
3257
|
+
function computeBinnedBarMarks(spec, scales, _chartArea, _strategy, theme) {
|
|
3258
|
+
const encoding = spec.encoding;
|
|
3259
|
+
const xChannel = encoding.x;
|
|
3260
|
+
const x2Channel = encoding.x2;
|
|
3261
|
+
const yChannel = encoding.y;
|
|
3262
|
+
if (!xChannel || !x2Channel || !yChannel || !scales.x || !scales.y) return [];
|
|
3263
|
+
const xScale = scales.x.scale;
|
|
3264
|
+
const yScale = scales.y.scale;
|
|
3265
|
+
const yDomain = yScale.domain();
|
|
3266
|
+
const yIncludesZero = yDomain[0] <= 0 && yDomain[1] >= 0;
|
|
3267
|
+
const baseline = yIncludesZero ? yScale(0) : yScale(yDomain[0]);
|
|
3268
|
+
const colorEnc = encoding.color && "field" in encoding.color ? encoding.color : void 0;
|
|
3269
|
+
const colorField = colorEnc?.type === "quantitative" ? void 0 : colorEnc?.field;
|
|
3270
|
+
const groups = groupByField(spec.data, colorField);
|
|
3271
|
+
const stackEnabled = groups.size > 1 && (yChannel.stack === true || yChannel.stack === "zero" || yChannel.stack === "normalize" || yChannel.stack === "center");
|
|
3272
|
+
const marks = groups.size > 1 && stackEnabled ? stackedBins(groups, spec, scales, xScale, yScale, baseline, theme) : overlappingBins(groups, spec, scales, xScale, yScale, baseline, colorField);
|
|
3273
|
+
stampKeys(marks, xChannel.field, colorField);
|
|
3274
|
+
return marks;
|
|
3275
|
+
}
|
|
3276
|
+
function binGeometry(row, xField, x2Field, xScale) {
|
|
3277
|
+
const start = Number(row[xField]);
|
|
3278
|
+
const end = Number(row[x2Field]);
|
|
3279
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return void 0;
|
|
3280
|
+
const px0 = xScale(start);
|
|
3281
|
+
const px1 = xScale(end);
|
|
3282
|
+
const left = Math.min(px0, px1);
|
|
3283
|
+
const span = Math.abs(px1 - px0);
|
|
3284
|
+
return { x: left, width: Math.max(span - BIN_GAP, MIN_BIN_WIDTH) };
|
|
3285
|
+
}
|
|
3286
|
+
function ariaFor(row, xField, x2Field, yField, seriesKey) {
|
|
3287
|
+
const start = Number(row[xField]);
|
|
3288
|
+
const end = Number(row[x2Field]);
|
|
3289
|
+
const value = Number(row[yField]);
|
|
3290
|
+
const range = `${formatNumber(start)} to ${formatNumber(end)}`;
|
|
3291
|
+
const prefix = seriesKey ? `${seriesKey}, ` : "";
|
|
3292
|
+
return { label: `${prefix}${range}: ${formatNumber(value)}` };
|
|
3293
|
+
}
|
|
3294
|
+
function overlappingBins(groups, spec, scales, xScale, yScale, baseline, colorField) {
|
|
3295
|
+
const encoding = spec.encoding;
|
|
3296
|
+
const xField = encoding.x.field;
|
|
3297
|
+
const x2Field = encoding.x2.field;
|
|
3298
|
+
const yField = encoding.y.field;
|
|
3299
|
+
const multi = groups.size > 1;
|
|
3300
|
+
const fillOpacity = multi ? spec.markDef.fillOpacity ?? OVERLAP_FILL_OPACITY : spec.markDef.fillOpacity;
|
|
3301
|
+
const marks = [];
|
|
3302
|
+
for (const [key, rows] of groups) {
|
|
3303
|
+
const fill = getColor(scales, key);
|
|
3304
|
+
for (const row of rows) {
|
|
3305
|
+
const geom = binGeometry(row, xField, x2Field, xScale);
|
|
3306
|
+
if (!geom) continue;
|
|
3307
|
+
const value = Number(row[yField]);
|
|
3308
|
+
if (!Number.isFinite(value)) continue;
|
|
3309
|
+
const endY = yScale(value);
|
|
3310
|
+
const top = Math.min(baseline, endY);
|
|
3311
|
+
const height = Math.max(Math.abs(baseline - endY), MIN_BAR_HEIGHT);
|
|
3312
|
+
marks.push({
|
|
3313
|
+
type: "rect",
|
|
3314
|
+
x: geom.x,
|
|
3315
|
+
y: top,
|
|
3316
|
+
width: geom.width,
|
|
3317
|
+
height,
|
|
3318
|
+
fill,
|
|
3319
|
+
fillOpacity,
|
|
3320
|
+
// Histogram bars are contiguous; rounded corners read as gaps.
|
|
3321
|
+
cornerRadius: 0,
|
|
3322
|
+
orient: "vertical",
|
|
3323
|
+
...colorField ? { seriesKey: key } : {},
|
|
3324
|
+
data: row,
|
|
3325
|
+
aria: ariaFor(row, xField, x2Field, yField, colorField ? key : void 0)
|
|
3326
|
+
});
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
return marks;
|
|
3330
|
+
}
|
|
3331
|
+
function stackedBins(groups, spec, scales, xScale, yScale, baseline, theme) {
|
|
3332
|
+
const encoding = spec.encoding;
|
|
3333
|
+
const xField = encoding.x.field;
|
|
3334
|
+
const x2Field = encoding.x2.field;
|
|
3335
|
+
const yField = encoding.y.field;
|
|
3336
|
+
const offsets = /* @__PURE__ */ new Map();
|
|
3337
|
+
const seam = stackSeamStroke(theme);
|
|
3338
|
+
const marks = [];
|
|
3339
|
+
for (const [key, rows] of groups) {
|
|
3340
|
+
const fill = getColor(scales, key);
|
|
3341
|
+
for (const row of rows) {
|
|
3342
|
+
const geom = binGeometry(row, xField, x2Field, xScale);
|
|
3343
|
+
if (!geom) continue;
|
|
3344
|
+
const value = Number(row[yField]);
|
|
3345
|
+
if (!Number.isFinite(value) || value <= 0) continue;
|
|
3346
|
+
const binKey = serializeKeyValue(row[xField]);
|
|
3347
|
+
const used = offsets.get(binKey) ?? 0;
|
|
3348
|
+
const segmentHeight = Math.max(baseline - yScale(value), MIN_BAR_HEIGHT);
|
|
3349
|
+
const top = baseline - used - segmentHeight;
|
|
3350
|
+
offsets.set(binKey, used + segmentHeight);
|
|
3351
|
+
marks.push({
|
|
3352
|
+
type: "rect",
|
|
3353
|
+
x: geom.x,
|
|
3354
|
+
y: top,
|
|
3355
|
+
width: geom.width,
|
|
3356
|
+
height: segmentHeight,
|
|
3357
|
+
fill,
|
|
3358
|
+
cornerRadius: 0,
|
|
3359
|
+
orient: "vertical",
|
|
3360
|
+
stackGroup: binKey,
|
|
3361
|
+
stroke: seam,
|
|
3362
|
+
strokeWidth: 1,
|
|
3363
|
+
seriesKey: key,
|
|
3364
|
+
data: row,
|
|
3365
|
+
aria: ariaFor(row, xField, x2Field, yField, key)
|
|
3366
|
+
});
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
return marks;
|
|
3370
|
+
}
|
|
3371
|
+
function stampKeys(marks, xField, colorField) {
|
|
3372
|
+
const raw = marks.map((m) => {
|
|
3373
|
+
const series = colorField ? String(m.data[colorField] ?? "") : "";
|
|
3374
|
+
return `${series}|${serializeKeyValue(m.data[xField])}`;
|
|
3375
|
+
});
|
|
3376
|
+
const keys = dedupeKeys(raw);
|
|
3377
|
+
for (let i = 0; i < marks.length; i++) {
|
|
3378
|
+
marks[i].key = keys[i];
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
// src/charts/binned-bar/index.ts
|
|
3383
|
+
var binnedBarRenderer = (spec, scales, chartArea, strategy, theme) => computeBinnedBarMarks(spec, scales, chartArea, strategy, theme);
|
|
3384
|
+
|
|
3251
3385
|
// src/charts/calendar/compute.ts
|
|
3252
3386
|
import { defaultNumberFormatter as defaultNumberFormatter4, formatDate } from "@opendata-ai/openchart-core";
|
|
3253
3387
|
import { utcDay, utcMonday, utcSunday } from "d3-time";
|
|
@@ -3604,13 +3738,13 @@ function computeColoredColumns(data, categoryField, valueField, colorField, xSca
|
|
|
3604
3738
|
if (!Number.isFinite(value)) continue;
|
|
3605
3739
|
const bandX = xScale(category);
|
|
3606
3740
|
if (bandX === void 0) continue;
|
|
3607
|
-
const
|
|
3608
|
-
const color = getColor(scales,
|
|
3741
|
+
const groupKey4 = String(row[colorField] ?? "");
|
|
3742
|
+
const color = getColor(scales, groupKey4);
|
|
3609
3743
|
const yPos = yScale(value);
|
|
3610
3744
|
const columnHeight = Math.max(Math.abs(baseline - yPos), MIN_COLUMN_HEIGHT);
|
|
3611
3745
|
const y = value >= 0 ? yPos : baseline;
|
|
3612
3746
|
const aria = {
|
|
3613
|
-
label: `${category}, ${
|
|
3747
|
+
label: `${category}, ${groupKey4}: ${formatLabelValue(value)}`
|
|
3614
3748
|
};
|
|
3615
3749
|
marks.push({
|
|
3616
3750
|
type: "rect",
|
|
@@ -3621,7 +3755,7 @@ function computeColoredColumns(data, categoryField, valueField, colorField, xSca
|
|
|
3621
3755
|
fill: color,
|
|
3622
3756
|
cornerRadius: BAR_CORNER_RADIUS,
|
|
3623
3757
|
cornerRadiusSides: valueEndCorners("vertical", value < 0),
|
|
3624
|
-
seriesKey:
|
|
3758
|
+
seriesKey: groupKey4,
|
|
3625
3759
|
data: row,
|
|
3626
3760
|
aria,
|
|
3627
3761
|
orient: "vertical"
|
|
@@ -3650,17 +3784,17 @@ function computeGroupedColumns(data, categoryField, valueField, colorField, xSca
|
|
|
3650
3784
|
const bandX = xScale(category);
|
|
3651
3785
|
if (bandX === void 0) continue;
|
|
3652
3786
|
for (const row of rows) {
|
|
3653
|
-
const
|
|
3787
|
+
const groupKey4 = String(row[colorField] ?? "");
|
|
3654
3788
|
const value = Number(row[valueField] ?? 0);
|
|
3655
3789
|
if (!Number.isFinite(value)) continue;
|
|
3656
|
-
const groupIndex = groupIndexMap.get(
|
|
3657
|
-
const color = getColor(scales,
|
|
3790
|
+
const groupIndex = groupIndexMap.get(groupKey4) ?? 0;
|
|
3791
|
+
const color = getColor(scales, groupKey4);
|
|
3658
3792
|
const yPos = yScale(value);
|
|
3659
3793
|
const columnHeight = Math.max(Math.abs(baseline - yPos), MIN_COLUMN_HEIGHT);
|
|
3660
3794
|
const y = value >= 0 ? yPos : baseline;
|
|
3661
3795
|
const subX = bandX + groupIndex * (subBandWidth + gap);
|
|
3662
3796
|
const aria = {
|
|
3663
|
-
label: `${category}, ${
|
|
3797
|
+
label: `${category}, ${groupKey4}: ${formatLabelValue(value)}`
|
|
3664
3798
|
};
|
|
3665
3799
|
marks.push({
|
|
3666
3800
|
type: "rect",
|
|
@@ -3671,7 +3805,7 @@ function computeGroupedColumns(data, categoryField, valueField, colorField, xSca
|
|
|
3671
3805
|
fill: color,
|
|
3672
3806
|
cornerRadius: BAR_CORNER_RADIUS,
|
|
3673
3807
|
cornerRadiusSides: valueEndCorners("vertical", value < 0),
|
|
3674
|
-
seriesKey:
|
|
3808
|
+
seriesKey: groupKey4,
|
|
3675
3809
|
data: row,
|
|
3676
3810
|
aria,
|
|
3677
3811
|
orient: "vertical"
|
|
@@ -3693,16 +3827,16 @@ function computeStackedColumns(data, categoryField, valueField, colorField, xSca
|
|
|
3693
3827
|
}
|
|
3694
3828
|
let cumulativeValue = stackMode === "center" ? -categoryTotal / 2 : 0;
|
|
3695
3829
|
for (const row of rows) {
|
|
3696
|
-
const
|
|
3830
|
+
const groupKey4 = String(row[colorField] ?? "");
|
|
3697
3831
|
const rawValue = Number(row[valueField] ?? 0);
|
|
3698
3832
|
if (!Number.isFinite(rawValue) || rawValue <= 0) continue;
|
|
3699
3833
|
const value = stackMode === "normalize" && categoryTotal > 0 ? rawValue / categoryTotal : rawValue;
|
|
3700
|
-
const color = getColor(scales,
|
|
3834
|
+
const color = getColor(scales, groupKey4);
|
|
3701
3835
|
const yTop = yScale(cumulativeValue + value);
|
|
3702
3836
|
const yBottom = yScale(cumulativeValue);
|
|
3703
3837
|
const columnHeight = Math.max(Math.abs(yBottom - yTop), MIN_COLUMN_HEIGHT);
|
|
3704
3838
|
const aria = {
|
|
3705
|
-
label: `${category}, ${
|
|
3839
|
+
label: `${category}, ${groupKey4}: ${formatLabelValue(rawValue)}`
|
|
3706
3840
|
};
|
|
3707
3841
|
marks.push({
|
|
3708
3842
|
type: "rect",
|
|
@@ -3712,7 +3846,7 @@ function computeStackedColumns(data, categoryField, valueField, colorField, xSca
|
|
|
3712
3846
|
height: columnHeight,
|
|
3713
3847
|
fill: color,
|
|
3714
3848
|
cornerRadius: 0,
|
|
3715
|
-
seriesKey:
|
|
3849
|
+
seriesKey: groupKey4,
|
|
3716
3850
|
data: row,
|
|
3717
3851
|
aria,
|
|
3718
3852
|
orient: "vertical",
|
|
@@ -4515,7 +4649,10 @@ function computeSingleArea(spec, scales, _chartArea, theme) {
|
|
|
4515
4649
|
let fillOpacity;
|
|
4516
4650
|
if (markFill != null) {
|
|
4517
4651
|
fillValue = markFill;
|
|
4518
|
-
fillOpacity = isGradientDef3(markFill) ? 1 : spec.markDef.opacity ?? (y2Channel ? 0.25 : DEFAULT_FILL_OPACITY);
|
|
4652
|
+
fillOpacity = isGradientDef3(markFill) ? 1 : spec.markDef.fillOpacity ?? spec.markDef.opacity ?? (y2Channel ? 0.25 : DEFAULT_FILL_OPACITY);
|
|
4653
|
+
} else if (spec.markDef.fillOpacity != null) {
|
|
4654
|
+
fillValue = getRepresentativeColor6(color);
|
|
4655
|
+
fillOpacity = spec.markDef.fillOpacity;
|
|
4519
4656
|
} else {
|
|
4520
4657
|
const colorStr = getRepresentativeColor6(color);
|
|
4521
4658
|
fillValue = buildGradientFill(colorStr, defaultGradientStops);
|
|
@@ -4625,10 +4762,10 @@ function computeStackedArea(spec, scales, chartArea, theme) {
|
|
|
4625
4762
|
let fillOpacity;
|
|
4626
4763
|
if (markFill != null) {
|
|
4627
4764
|
fillValue = markFill;
|
|
4628
|
-
fillOpacity = isGradientDef3(markFill) ? 1 : spec.markDef.opacity ?? 0.7;
|
|
4765
|
+
fillOpacity = isGradientDef3(markFill) ? 1 : spec.markDef.fillOpacity ?? spec.markDef.opacity ?? 0.7;
|
|
4629
4766
|
} else {
|
|
4630
4767
|
fillValue = getRepresentativeColor6(color);
|
|
4631
|
-
fillOpacity = STACKED_FILL_OPACITY;
|
|
4768
|
+
fillOpacity = spec.markDef.fillOpacity ?? STACKED_FILL_OPACITY;
|
|
4632
4769
|
}
|
|
4633
4770
|
const rawPointKeys = validPoints.map((_p, idx) => serializeKeyValue(layer[idx]?.data.__x__));
|
|
4634
4771
|
marks.push({
|
|
@@ -5659,7 +5796,7 @@ var rangeRenderer = (spec, scales, chartArea, strategy, theme) => {
|
|
|
5659
5796
|
};
|
|
5660
5797
|
|
|
5661
5798
|
// src/charts/rect/index.ts
|
|
5662
|
-
import { formatNumber } from "@opendata-ai/openchart-core";
|
|
5799
|
+
import { formatNumber as formatNumber2 } from "@opendata-ai/openchart-core";
|
|
5663
5800
|
function bandwidthOf(scale) {
|
|
5664
5801
|
const band = scale?.scale;
|
|
5665
5802
|
return typeof band?.bandwidth === "function" ? band.bandwidth() : 0;
|
|
@@ -5690,7 +5827,7 @@ function computeRectMarks(spec, scales, _chartArea) {
|
|
|
5690
5827
|
fill = getColor(scales, "__default__");
|
|
5691
5828
|
}
|
|
5692
5829
|
const colorValue = colorEnc ? row[colorEnc.field] : void 0;
|
|
5693
|
-
const colorLabel = colorEnc && colorValue != null ? `, ${colorEnc.field}: ${typeof colorValue === "number" ?
|
|
5830
|
+
const colorLabel = colorEnc && colorValue != null ? `, ${colorEnc.field}: ${typeof colorValue === "number" ? formatNumber2(colorValue) : String(colorValue)}` : "";
|
|
5694
5831
|
const aria = {
|
|
5695
5832
|
label: `${row[xChannel.field]}, ${row[yChannel.field]}${colorLabel}`
|
|
5696
5833
|
};
|
|
@@ -6228,6 +6365,14 @@ var builtinRenderers = {
|
|
|
6228
6365
|
// horizontal bars
|
|
6229
6366
|
"bar:vertical": columnRenderer,
|
|
6230
6367
|
// vertical bars (old 'column')
|
|
6368
|
+
"bar:binned": binnedBarRenderer,
|
|
6369
|
+
// histogram: rects sized from a linear x scale via x2
|
|
6370
|
+
// Safety net. `compileChart` always expands sugar before dispatch, so these
|
|
6371
|
+
// keys should be unreachable — but an unregistered key yields zero marks
|
|
6372
|
+
// with no error, i.e. a silently blank chart, which is the exact failure
|
|
6373
|
+
// this whole mark family exists to remove.
|
|
6374
|
+
histogram: binnedBarRenderer,
|
|
6375
|
+
density: areaRenderer,
|
|
6231
6376
|
point: scatterRenderer,
|
|
6232
6377
|
// old 'scatter'
|
|
6233
6378
|
arc: pieRenderer,
|
|
@@ -6270,7 +6415,7 @@ import {
|
|
|
6270
6415
|
} from "@opendata-ai/openchart-core";
|
|
6271
6416
|
|
|
6272
6417
|
// src/compile/spec-sugar.ts
|
|
6273
|
-
import { resolveSchemeName } from "@opendata-ai/openchart-core";
|
|
6418
|
+
import { inferFieldType, resolveSchemeName } from "@opendata-ai/openchart-core";
|
|
6274
6419
|
|
|
6275
6420
|
// src/transforms/aggregate.ts
|
|
6276
6421
|
function computeAggregate(op, values) {
|
|
@@ -6382,9 +6527,11 @@ function uniqueFieldStrings(data, field) {
|
|
|
6382
6527
|
}
|
|
6383
6528
|
return out;
|
|
6384
6529
|
}
|
|
6385
|
-
function expandEncodingSugar(spec) {
|
|
6530
|
+
function expandEncodingSugar(spec, inheritedData) {
|
|
6386
6531
|
const encoding = spec.encoding;
|
|
6387
6532
|
if (!encoding) return spec;
|
|
6533
|
+
const markType = markTypeOf(spec);
|
|
6534
|
+
const specData = Array.isArray(spec.data) ? spec.data : inheritedData;
|
|
6388
6535
|
const generatedTransforms = [];
|
|
6389
6536
|
const updatedEncoding = { ...encoding };
|
|
6390
6537
|
let changed = false;
|
|
@@ -6394,14 +6541,33 @@ function expandEncodingSugar(spec) {
|
|
|
6394
6541
|
if (ch.bin != null && ch.bin !== false) {
|
|
6395
6542
|
const field = ch.field;
|
|
6396
6543
|
const outputField = `bin_${field}`;
|
|
6544
|
+
const resolvedType = ch.type ?? (specData && specData.length > 0 ? inferFieldType(specData, field) : void 0);
|
|
6545
|
+
const emitInterval = channel === "x" && resolvedType === "quantitative" && markType === "bar";
|
|
6546
|
+
const endField = `${outputField}_end`;
|
|
6547
|
+
const chScale = ch.scale;
|
|
6548
|
+
let binDef;
|
|
6549
|
+
if (chScale?.type === "log") {
|
|
6550
|
+
binDef = ch.bin === true ? { scaleType: "log" } : { ...ch.bin, scaleType: "log" };
|
|
6551
|
+
} else {
|
|
6552
|
+
binDef = ch.bin === true ? true : ch.bin;
|
|
6553
|
+
}
|
|
6397
6554
|
const binTransform = {
|
|
6398
|
-
bin:
|
|
6555
|
+
bin: binDef,
|
|
6399
6556
|
field,
|
|
6400
|
-
as: outputField
|
|
6557
|
+
as: emitInterval ? [outputField, endField] : outputField
|
|
6401
6558
|
};
|
|
6402
6559
|
generatedTransforms.push(binTransform);
|
|
6403
6560
|
const { bin: _bin, ...rest } = ch;
|
|
6404
|
-
updatedEncoding[channel] = {
|
|
6561
|
+
updatedEncoding[channel] = {
|
|
6562
|
+
...rest,
|
|
6563
|
+
field: outputField,
|
|
6564
|
+
// The axis should read `amount`, not `bin_amount`. Scoped to the same
|
|
6565
|
+
// guard: retitling every binned channel would move existing baselines.
|
|
6566
|
+
...emitInterval && rest.title == null ? { title: field } : {}
|
|
6567
|
+
};
|
|
6568
|
+
if (emitInterval) {
|
|
6569
|
+
updatedEncoding.x2 = { field: endField, type: "quantitative" };
|
|
6570
|
+
}
|
|
6405
6571
|
changed = true;
|
|
6406
6572
|
}
|
|
6407
6573
|
const current = updatedEncoding[channel] ?? ch;
|
|
@@ -6622,7 +6788,9 @@ function expandChannelSugar(spec, warnings) {
|
|
|
6622
6788
|
}
|
|
6623
6789
|
var COUNT_GROUP_CHANNELS = [
|
|
6624
6790
|
"x",
|
|
6791
|
+
"x2",
|
|
6625
6792
|
"y",
|
|
6793
|
+
"y2",
|
|
6626
6794
|
"color",
|
|
6627
6795
|
"detail",
|
|
6628
6796
|
"strokeDash",
|
|
@@ -6743,6 +6911,130 @@ function resolveSortSugar(spec, inheritedData) {
|
|
|
6743
6911
|
}
|
|
6744
6912
|
return changed ? { ...spec, encoding: updated } : spec;
|
|
6745
6913
|
}
|
|
6914
|
+
var HISTOGRAM_DEFAULT_MAXBINS = 20;
|
|
6915
|
+
var PROPORTION_FIELD = "__proportion";
|
|
6916
|
+
var GROUP_TOTAL_FIELD = "__group_total";
|
|
6917
|
+
function expandHistogramMark(spec) {
|
|
6918
|
+
if (markTypeOf(spec) !== "histogram") return spec;
|
|
6919
|
+
const markDef = typeof spec.mark === "string" ? {} : { ...spec.mark };
|
|
6920
|
+
const { binCount, normalize, ...restMark } = markDef;
|
|
6921
|
+
const encoding = { ...spec.encoding ?? {} };
|
|
6922
|
+
const x = { ...encoding.x ?? {} };
|
|
6923
|
+
if (x.bin == null) {
|
|
6924
|
+
const binParams = {
|
|
6925
|
+
maxbins: typeof binCount === "number" ? binCount : HISTOGRAM_DEFAULT_MAXBINS
|
|
6926
|
+
};
|
|
6927
|
+
const xScale = x.scale;
|
|
6928
|
+
if (xScale?.type === "log") {
|
|
6929
|
+
binParams.scaleType = "log";
|
|
6930
|
+
}
|
|
6931
|
+
x.bin = binParams;
|
|
6932
|
+
}
|
|
6933
|
+
x.type = x.type ?? "quantitative";
|
|
6934
|
+
encoding.x = x;
|
|
6935
|
+
if (encoding.y == null) {
|
|
6936
|
+
encoding.y = { aggregate: "count" };
|
|
6937
|
+
}
|
|
6938
|
+
const out = {
|
|
6939
|
+
...spec,
|
|
6940
|
+
mark: { ...restMark, type: "bar" },
|
|
6941
|
+
encoding
|
|
6942
|
+
};
|
|
6943
|
+
if (normalize === true) {
|
|
6944
|
+
out.__histogramNormalize = true;
|
|
6945
|
+
}
|
|
6946
|
+
return out;
|
|
6947
|
+
}
|
|
6948
|
+
var DENSITY_OVERLAP_FILL_OPACITY = 0.4;
|
|
6949
|
+
function distributionGroupFields(encoding) {
|
|
6950
|
+
const fields = [];
|
|
6951
|
+
for (const channel of ["color", "detail"]) {
|
|
6952
|
+
const ch = encoding[channel];
|
|
6953
|
+
if (typeof ch?.field === "string" && !fields.includes(ch.field)) fields.push(ch.field);
|
|
6954
|
+
}
|
|
6955
|
+
return fields;
|
|
6956
|
+
}
|
|
6957
|
+
var DENSITY_VALUE_FIELD = "value";
|
|
6958
|
+
var DENSITY_OUTPUT_FIELD = "density";
|
|
6959
|
+
function expandDensityMark(spec) {
|
|
6960
|
+
if (markTypeOf(spec) !== "density") return spec;
|
|
6961
|
+
const markDef = typeof spec.mark === "string" ? {} : { ...spec.mark };
|
|
6962
|
+
const { bandwidth, cumulative, steps, ...restMark } = markDef;
|
|
6963
|
+
const encoding = { ...spec.encoding ?? {} };
|
|
6964
|
+
const x = { ...encoding.x ?? {} };
|
|
6965
|
+
const field = typeof x.field === "string" ? x.field : void 0;
|
|
6966
|
+
if (field == null) return spec;
|
|
6967
|
+
const groupFields = distributionGroupFields(encoding);
|
|
6968
|
+
const densityTransform = {
|
|
6969
|
+
density: field,
|
|
6970
|
+
as: [DENSITY_VALUE_FIELD, DENSITY_OUTPUT_FIELD]
|
|
6971
|
+
};
|
|
6972
|
+
if (groupFields.length > 0) densityTransform.groupby = groupFields;
|
|
6973
|
+
if (typeof bandwidth === "number") densityTransform.bandwidth = bandwidth;
|
|
6974
|
+
if (cumulative === true) densityTransform.cumulative = true;
|
|
6975
|
+
if (typeof steps === "number") densityTransform.steps = steps;
|
|
6976
|
+
const transforms = (spec.transform ?? []).slice().concat(densityTransform);
|
|
6977
|
+
encoding.x = {
|
|
6978
|
+
...x,
|
|
6979
|
+
field: DENSITY_VALUE_FIELD,
|
|
6980
|
+
type: "quantitative",
|
|
6981
|
+
...x.title == null ? { title: field } : {}
|
|
6982
|
+
};
|
|
6983
|
+
const y = { ...encoding.y ?? {} };
|
|
6984
|
+
encoding.y = {
|
|
6985
|
+
title: cumulative === true ? "Cumulative share" : "Density",
|
|
6986
|
+
// A density's absolute height is not a quantity readers interpret, so the
|
|
6987
|
+
// axis is off unless the author asks for it.
|
|
6988
|
+
axis: false,
|
|
6989
|
+
...y,
|
|
6990
|
+
field: DENSITY_OUTPUT_FIELD,
|
|
6991
|
+
type: "quantitative",
|
|
6992
|
+
// Areas stack by default on a color field; overlapping translucent curves
|
|
6993
|
+
// are the whole point of a density comparison.
|
|
6994
|
+
stack: y.stack ?? null
|
|
6995
|
+
};
|
|
6996
|
+
const out = {
|
|
6997
|
+
...spec,
|
|
6998
|
+
mark: {
|
|
6999
|
+
interpolate: "linear",
|
|
7000
|
+
...groupFields.length > 0 ? { fillOpacity: DENSITY_OVERLAP_FILL_OPACITY } : {},
|
|
7001
|
+
...restMark,
|
|
7002
|
+
type: "area"
|
|
7003
|
+
},
|
|
7004
|
+
encoding,
|
|
7005
|
+
transform: transforms
|
|
7006
|
+
};
|
|
7007
|
+
if (out.crosshair === void 0) out.crosshair = false;
|
|
7008
|
+
if (out.endpointLabels === void 0) out.endpointLabels = false;
|
|
7009
|
+
return out;
|
|
7010
|
+
}
|
|
7011
|
+
function applyHistogramNormalize(spec) {
|
|
7012
|
+
if (spec.__histogramNormalize !== true) return spec;
|
|
7013
|
+
const { __histogramNormalize: _flag, ...rest } = spec;
|
|
7014
|
+
const encoding = { ...rest.encoding ?? {} };
|
|
7015
|
+
const y = { ...encoding.y ?? {} };
|
|
7016
|
+
const countField = typeof y.field === "string" ? y.field : "__count";
|
|
7017
|
+
const groupby = distributionGroupFields(encoding);
|
|
7018
|
+
const transforms = (rest.transform ?? []).slice();
|
|
7019
|
+
transforms.push({
|
|
7020
|
+
joinaggregate: [{ op: "sum", field: countField, as: GROUP_TOTAL_FIELD }],
|
|
7021
|
+
groupby
|
|
7022
|
+
});
|
|
7023
|
+
transforms.push({
|
|
7024
|
+
calculate: { op: "/", field: countField, field2: GROUP_TOTAL_FIELD },
|
|
7025
|
+
as: PROPORTION_FIELD
|
|
7026
|
+
});
|
|
7027
|
+
encoding.y = {
|
|
7028
|
+
...y,
|
|
7029
|
+
field: PROPORTION_FIELD,
|
|
7030
|
+
type: "quantitative",
|
|
7031
|
+
title: y.title === "Count" || y.title == null ? "Share" : y.title,
|
|
7032
|
+
// `axis: false` is an author turning the axis off; don't spread it into an
|
|
7033
|
+
// object and hand back an axis they asked not to have.
|
|
7034
|
+
axis: y.axis === false || y.axis === null ? y.axis : { format: "percent", ...y.axis ?? {} }
|
|
7035
|
+
};
|
|
7036
|
+
return { ...rest, encoding, transform: transforms };
|
|
7037
|
+
}
|
|
6746
7038
|
function expandChartSugar(spec, warnings, inheritedData) {
|
|
6747
7039
|
let out = unwrapDataValues(spec);
|
|
6748
7040
|
out = expandTopLevelTitle(out);
|
|
@@ -6752,9 +7044,12 @@ function expandChartSugar(spec, warnings, inheritedData) {
|
|
|
6752
7044
|
out = applyFixedSizeDefault(out);
|
|
6753
7045
|
out = expandAnnotationSugar(out, warnings);
|
|
6754
7046
|
out = expandChannelSugar(out, warnings);
|
|
6755
|
-
out =
|
|
7047
|
+
out = expandHistogramMark(out);
|
|
7048
|
+
out = expandDensityMark(out);
|
|
7049
|
+
out = expandEncodingSugar(out, inheritedData);
|
|
6756
7050
|
out = resolveSortSugar(out, inheritedData);
|
|
6757
7051
|
out = expandCountAggregate(out);
|
|
7052
|
+
out = applyHistogramNormalize(out);
|
|
6758
7053
|
return out;
|
|
6759
7054
|
}
|
|
6760
7055
|
function expandLayerSugar(spec, warnings, inheritedData, parentEncoding) {
|
|
@@ -7074,7 +7369,7 @@ function normalizeChrome(chrome) {
|
|
|
7074
7369
|
brand: normalizeChromeField(chrome.brand)
|
|
7075
7370
|
};
|
|
7076
7371
|
}
|
|
7077
|
-
function
|
|
7372
|
+
function inferFieldType2(data, field) {
|
|
7078
7373
|
const sampleSize = Math.min(50, data.length);
|
|
7079
7374
|
let numericCount = 0;
|
|
7080
7375
|
let dateCount = 0;
|
|
@@ -7116,13 +7411,13 @@ function inferEncodingTypes(encoding, data, warnings) {
|
|
|
7116
7411
|
if ("condition" in spec) continue;
|
|
7117
7412
|
if (!("field" in spec)) continue;
|
|
7118
7413
|
if (!spec.type) {
|
|
7119
|
-
const inferred =
|
|
7414
|
+
const inferred = inferFieldType2(data, spec.field);
|
|
7120
7415
|
result[channel] = { ...spec, type: inferred };
|
|
7121
7416
|
warnings.push(
|
|
7122
7417
|
`Inferred encoding.${channel}.type as "${inferred}" from data values for field "${spec.field}"`
|
|
7123
7418
|
);
|
|
7124
7419
|
} else {
|
|
7125
|
-
const actualType =
|
|
7420
|
+
const actualType = inferFieldType2(data, spec.field);
|
|
7126
7421
|
if (spec.type === "nominal" && actualType === "temporal") {
|
|
7127
7422
|
warnings.push(`Field "${spec.field}" looks temporal but was declared as nominal`);
|
|
7128
7423
|
}
|
|
@@ -7599,7 +7894,7 @@ function flattenLayers(spec, parentData, parentEncoding, parentTransforms, paren
|
|
|
7599
7894
|
|
|
7600
7895
|
// src/compiler/validate.ts
|
|
7601
7896
|
import {
|
|
7602
|
-
inferFieldType as
|
|
7897
|
+
inferFieldType as inferFieldType3,
|
|
7603
7898
|
MARK_ENCODING_RULES,
|
|
7604
7899
|
MARK_TYPES,
|
|
7605
7900
|
resolveSchemeName as resolveSchemeName2,
|
|
@@ -7718,8 +8013,8 @@ function validateRangeSpec(spec, errors) {
|
|
|
7718
8013
|
if (!x?.field || typeof x.field !== "string" || !y?.field || typeof y.field !== "string") {
|
|
7719
8014
|
return;
|
|
7720
8015
|
}
|
|
7721
|
-
const xType = x.type ??
|
|
7722
|
-
const yType = y.type ??
|
|
8016
|
+
const xType = x.type ?? inferFieldType3(data, x.field);
|
|
8017
|
+
const yType = y.type ?? inferFieldType3(data, y.field);
|
|
7723
8018
|
const xIsCategory = xType === "nominal" || xType === "ordinal";
|
|
7724
8019
|
const yIsCategory = yType === "nominal" || yType === "ordinal";
|
|
7725
8020
|
if (xIsCategory && yIsCategory) {
|
|
@@ -7856,7 +8151,7 @@ function validateCalendarSpec(spec, errors) {
|
|
|
7856
8151
|
}
|
|
7857
8152
|
const x = encoding.x;
|
|
7858
8153
|
if (!x?.field || typeof x.field !== "string") return;
|
|
7859
|
-
const xType = x.type ??
|
|
8154
|
+
const xType = x.type ?? inferFieldType3(data, x.field);
|
|
7860
8155
|
if (xType !== "temporal") {
|
|
7861
8156
|
if (!x.type) {
|
|
7862
8157
|
errors.push({
|
|
@@ -8065,12 +8360,16 @@ function validateChartSpec(spec, errors) {
|
|
|
8065
8360
|
if (typeof f === "string") transformFields.add(f);
|
|
8066
8361
|
}
|
|
8067
8362
|
}
|
|
8068
|
-
const opList = t.aggregate ?? t.window;
|
|
8363
|
+
const opList = t.aggregate ?? t.window ?? t.joinaggregate;
|
|
8069
8364
|
if (Array.isArray(opList)) {
|
|
8070
8365
|
for (const op of opList) {
|
|
8071
8366
|
if (op && typeof op.as === "string") transformFields.add(op.as);
|
|
8072
8367
|
}
|
|
8073
8368
|
}
|
|
8369
|
+
if (typeof t.density === "string" && t.as == null) {
|
|
8370
|
+
transformFields.add("value");
|
|
8371
|
+
transformFields.add("density");
|
|
8372
|
+
}
|
|
8074
8373
|
if (Array.isArray(t.groupby)) {
|
|
8075
8374
|
for (const g of t.groupby) {
|
|
8076
8375
|
if (typeof g === "string") transformFields.add(g);
|
|
@@ -8205,7 +8504,7 @@ function validateChartSpec(spec, errors) {
|
|
|
8205
8504
|
suggestion: "Provide a series name (string) or an array of series names."
|
|
8206
8505
|
});
|
|
8207
8506
|
} else {
|
|
8208
|
-
const colorType = colorCh.type ?? (colorCh.field && Array.isArray(spec.data) && spec.data.length > 0 ?
|
|
8507
|
+
const colorType = colorCh.type ?? (colorCh.field && Array.isArray(spec.data) && spec.data.length > 0 ? inferFieldType3(spec.data, colorCh.field) : void 0);
|
|
8209
8508
|
if (colorType === "quantitative") {
|
|
8210
8509
|
errors.push({
|
|
8211
8510
|
message: "Spec error: encoding.color.highlight is not supported on quantitative color channels",
|
|
@@ -8336,7 +8635,7 @@ function validateChartSpec(spec, errors) {
|
|
|
8336
8635
|
const data = Array.isArray(spec.data) ? spec.data : [];
|
|
8337
8636
|
const colorCh = encoding?.color;
|
|
8338
8637
|
if (colorCh && typeof colorCh === "object" && typeof colorCh.field === "string") {
|
|
8339
|
-
const colorType = colorCh.type ??
|
|
8638
|
+
const colorType = colorCh.type ?? inferFieldType3(data, colorCh.field);
|
|
8340
8639
|
if (colorType !== "quantitative") {
|
|
8341
8640
|
const distinct = new Set(data.map((row) => row[colorCh.field]));
|
|
8342
8641
|
if (distinct.size > 1) {
|
|
@@ -9756,8 +10055,14 @@ function computeBandRowObstacles(marks, scales) {
|
|
|
9756
10055
|
}
|
|
9757
10056
|
return obstacles;
|
|
9758
10057
|
}
|
|
10058
|
+
function isBinnedBarEncoding(markType, encoding) {
|
|
10059
|
+
return markType === "bar" && encoding.x?.type === "quantitative" && !!encoding.x2 && encoding.y?.type === "quantitative";
|
|
10060
|
+
}
|
|
9759
10061
|
function resolveRendererKey(markType, encoding, markDef) {
|
|
9760
10062
|
if (markType === "bar") {
|
|
10063
|
+
if (isBinnedBarEncoding(markType, encoding)) {
|
|
10064
|
+
return "bar:binned";
|
|
10065
|
+
}
|
|
9761
10066
|
const xType = encoding.x?.type;
|
|
9762
10067
|
const yType = encoding.y?.type;
|
|
9763
10068
|
const isVertical = (xType === "nominal" || xType === "ordinal" || xType === "temporal") && yType === "quantitative";
|
|
@@ -10750,10 +11055,25 @@ function computeScales(spec, chartArea, data) {
|
|
|
10750
11055
|
}
|
|
10751
11056
|
}
|
|
10752
11057
|
}
|
|
11058
|
+
if (spec.markType === "area" && encoding.x?.type === "quantitative" && encoding.x.scale?.zero === void 0) {
|
|
11059
|
+
if (!encoding.x.scale) {
|
|
11060
|
+
encoding.x.scale = { zero: false };
|
|
11061
|
+
} else {
|
|
11062
|
+
encoding.x.scale.zero = false;
|
|
11063
|
+
}
|
|
11064
|
+
}
|
|
11065
|
+
const binnedX = isBinnedBarEncoding(spec.markType, encoding) ? encoding.x : void 0;
|
|
11066
|
+
if (binnedX && binnedX.scale?.zero === void 0) {
|
|
11067
|
+
if (!binnedX.scale) {
|
|
11068
|
+
binnedX.scale = { zero: false };
|
|
11069
|
+
} else {
|
|
11070
|
+
binnedX.scale.zero = false;
|
|
11071
|
+
}
|
|
11072
|
+
}
|
|
10753
11073
|
if (encoding.x) {
|
|
10754
11074
|
let xData = data;
|
|
10755
11075
|
let xChannel = encoding.x;
|
|
10756
|
-
if (spec.markType === "range" && encoding.x2 && encoding.x.type === "quantitative") {
|
|
11076
|
+
if ((spec.markType === "range" || spec.markType === "bar") && encoding.x2 && encoding.x.type === "quantitative") {
|
|
10757
11077
|
const xField = encoding.x.field;
|
|
10758
11078
|
const x2Field = encoding.x2.field;
|
|
10759
11079
|
xData = [...data, ...data.map((row) => ({ [xField]: row[x2Field] }))];
|
|
@@ -10825,7 +11145,7 @@ function computeScales(spec, chartArea, data) {
|
|
|
10825
11145
|
if (encoding.y) {
|
|
10826
11146
|
let yData = data;
|
|
10827
11147
|
let yChannel = encoding.y;
|
|
10828
|
-
if (spec.markType === "range" && encoding.y2 && encoding.y.type === "quantitative") {
|
|
11148
|
+
if ((spec.markType === "range" || spec.markType === "bar") && encoding.y2 && encoding.y.type === "quantitative") {
|
|
10829
11149
|
const yField = encoding.y.field;
|
|
10830
11150
|
const y2Field = encoding.y2.field;
|
|
10831
11151
|
yData = [...data, ...data.map((row) => ({ [yField]: row[y2Field] }))];
|
|
@@ -11378,7 +11698,7 @@ import {
|
|
|
11378
11698
|
abbreviateNumber,
|
|
11379
11699
|
buildD3Formatter,
|
|
11380
11700
|
estimateTextWidth as estimateTextWidth13,
|
|
11381
|
-
formatNumber as
|
|
11701
|
+
formatNumber as formatNumber3
|
|
11382
11702
|
} from "@opendata-ai/openchart-core";
|
|
11383
11703
|
import { scaleLinear as scaleLinear3, scaleSqrt as scaleSqrt3 } from "d3-scale";
|
|
11384
11704
|
var LABEL_GAP = 6;
|
|
@@ -11399,7 +11719,7 @@ function formatValue(value, formatStr) {
|
|
|
11399
11719
|
if (fmt2) return fmt2(value);
|
|
11400
11720
|
}
|
|
11401
11721
|
if (Math.abs(value) >= 1e3) return abbreviateNumber(value);
|
|
11402
|
-
return
|
|
11722
|
+
return formatNumber3(value);
|
|
11403
11723
|
}
|
|
11404
11724
|
function niceFloor(value) {
|
|
11405
11725
|
if (!Number.isFinite(value) || value <= 0) return value;
|
|
@@ -12321,7 +12641,8 @@ function resolveLayoutPlan(chartSpec, renderSpec, options, theme, strategy, wate
|
|
|
12321
12641
|
const yAxisSuppressed = encoding.y?.axis === false;
|
|
12322
12642
|
let leftGutter = hPad + axisMargin;
|
|
12323
12643
|
if (encoding.y && !isRadial && !yAxisSuppressed && !yIsInline) {
|
|
12324
|
-
|
|
12644
|
+
const isBinnedBar = isBinnedBarEncoding(renderSpec.markType, encoding);
|
|
12645
|
+
if (!isBinnedBar && (encoding.y.type === "nominal" || encoding.y.type === "ordinal" || renderSpec.markType === "bar" || renderSpec.markType === "circle" || renderSpec.markType === "lollipop")) {
|
|
12325
12646
|
const yField = encoding.y.field;
|
|
12326
12647
|
const yLabelField = yAxisCfg?.labelField;
|
|
12327
12648
|
let maxLabelWidth = 0;
|
|
@@ -13364,7 +13685,7 @@ import {
|
|
|
13364
13685
|
CATEGORICAL_PALETTE as CATEGORICAL_PALETTE2,
|
|
13365
13686
|
computeChrome as computeChrome3,
|
|
13366
13687
|
estimateTextWidth as estimateTextWidth18,
|
|
13367
|
-
formatNumber as
|
|
13688
|
+
formatNumber as formatNumber4,
|
|
13368
13689
|
getBreakpoint,
|
|
13369
13690
|
HPAD_COMPACT_FRACTION as HPAD_COMPACT_FRACTION2,
|
|
13370
13691
|
HPAD_COMPACT_MIN as HPAD_COMPACT_MIN2,
|
|
@@ -13816,7 +14137,7 @@ function compileGeoMap(spec, options) {
|
|
|
13816
14137
|
compileWarnings.push(...filteredJoinWarnings);
|
|
13817
14138
|
}
|
|
13818
14139
|
const droppedFeatures = [];
|
|
13819
|
-
const formatter = buildD3Formatter2(mapSpec.encoding?.color?.format ?? mapSpec.valueFormat) ??
|
|
14140
|
+
const formatter = buildD3Formatter2(mapSpec.encoding?.color?.format ?? mapSpec.valueFormat) ?? formatNumber4;
|
|
13820
14141
|
const neutralFill = isDarkMode ? theme.colors.neutral[800] : theme.colors.neutral[100];
|
|
13821
14142
|
const noDataPattern = {
|
|
13822
14143
|
type: "diagonal",
|
|
@@ -15727,7 +16048,8 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
15727
16048
|
const yAxisSuppressed = encoding.y?.axis === false;
|
|
15728
16049
|
const yIsInline = yIsInlinePre;
|
|
15729
16050
|
if (encoding.y && !isRadial && !yAxisSuppressed && !yIsInline) {
|
|
15730
|
-
|
|
16051
|
+
const isBinnedBar = isBinnedBarEncoding(spec.markType, encoding);
|
|
16052
|
+
if (!plan && !isBinnedBar && (spec.markType === "bar" || spec.markType === "circle" || spec.markType === "lollipop" || encoding.y.type === "nominal" || encoding.y.type === "ordinal")) {
|
|
15731
16053
|
const yField = encoding.y.field;
|
|
15732
16054
|
const yLabelField = encoding.y.axis?.labelField;
|
|
15733
16055
|
let maxLabelWidth = 0;
|
|
@@ -18593,6 +18915,60 @@ function computeWaffleTooltips(spec, marks) {
|
|
|
18593
18915
|
}
|
|
18594
18916
|
return descriptors;
|
|
18595
18917
|
}
|
|
18918
|
+
function isBinnedBarSpec(spec) {
|
|
18919
|
+
return isBinnedBarEncoding(spec.markType, spec.encoding);
|
|
18920
|
+
}
|
|
18921
|
+
function computeBinnedBarTooltips(spec, marks) {
|
|
18922
|
+
const encoding = spec.encoding;
|
|
18923
|
+
const descriptors = /* @__PURE__ */ new Map();
|
|
18924
|
+
const xCh = encoding.x;
|
|
18925
|
+
const x2Ch = encoding.x2;
|
|
18926
|
+
const yCh = encoding.y;
|
|
18927
|
+
if (!xCh || !x2Ch || !yCh) return descriptors;
|
|
18928
|
+
const colorEnc = encoding.color && "field" in encoding.color ? encoding.color : void 0;
|
|
18929
|
+
const allChannels = [
|
|
18930
|
+
xCh,
|
|
18931
|
+
yCh,
|
|
18932
|
+
colorEnc,
|
|
18933
|
+
...encoding.tooltip ? Array.isArray(encoding.tooltip) ? encoding.tooltip : [encoding.tooltip] : []
|
|
18934
|
+
].filter((ch) => !!ch && "field" in ch);
|
|
18935
|
+
const fmtCache = buildFormatterCache(spec.data, allChannels);
|
|
18936
|
+
const xFormatter = getFormatter(fmtCache, xCh);
|
|
18937
|
+
const stackTotals = computeRectStackTotals(marks, encoding, fmtCache);
|
|
18938
|
+
for (let i = 0; i < marks.length; i++) {
|
|
18939
|
+
const mark = marks[i];
|
|
18940
|
+
if (mark.type !== "rect") continue;
|
|
18941
|
+
const row = mark.data;
|
|
18942
|
+
const start = formatValue2(row[xCh.field], "quantitative", xFormatter);
|
|
18943
|
+
const end = formatValue2(row[x2Ch.field], "quantitative", xFormatter);
|
|
18944
|
+
const title = `${start} \u2013 ${end}`;
|
|
18945
|
+
if (encoding.tooltip) {
|
|
18946
|
+
const channels = Array.isArray(encoding.tooltip) ? encoding.tooltip : [encoding.tooltip];
|
|
18947
|
+
descriptors.set(`rect-${i}`, {
|
|
18948
|
+
title,
|
|
18949
|
+
fields: buildExplicitTooltipFields(row, channels, fmtCache)
|
|
18950
|
+
});
|
|
18951
|
+
continue;
|
|
18952
|
+
}
|
|
18953
|
+
const fields = [];
|
|
18954
|
+
if (colorEnc) {
|
|
18955
|
+
fields.push({
|
|
18956
|
+
label: resolveLabel(colorEnc),
|
|
18957
|
+
value: String(row[colorEnc.field] ?? ""),
|
|
18958
|
+
color: getRepresentativeColor14(mark.fill)
|
|
18959
|
+
});
|
|
18960
|
+
}
|
|
18961
|
+
fields.push({
|
|
18962
|
+
label: resolveLabel(yCh),
|
|
18963
|
+
value: formatValue2(row[yCh.field], yCh.type, getFormatter(fmtCache, yCh)),
|
|
18964
|
+
...colorEnc ? {} : { color: getRepresentativeColor14(mark.fill) }
|
|
18965
|
+
});
|
|
18966
|
+
const total = mark.stackGroup !== void 0 ? stackTotals?.get(mark.stackGroup) : void 0;
|
|
18967
|
+
if (total) fields.push(total);
|
|
18968
|
+
descriptors.set(`rect-${i}`, { title, fields });
|
|
18969
|
+
}
|
|
18970
|
+
return descriptors;
|
|
18971
|
+
}
|
|
18596
18972
|
function computeParliamentTooltips(spec, marks) {
|
|
18597
18973
|
const encoding = spec.encoding;
|
|
18598
18974
|
const descriptors = /* @__PURE__ */ new Map();
|
|
@@ -18657,6 +19033,9 @@ function computeTooltipDescriptors(spec, marks) {
|
|
|
18657
19033
|
if (spec.markType === "parliament") {
|
|
18658
19034
|
return computeParliamentTooltips(spec, marks);
|
|
18659
19035
|
}
|
|
19036
|
+
if (isBinnedBarSpec(spec)) {
|
|
19037
|
+
return computeBinnedBarTooltips(spec, marks);
|
|
19038
|
+
}
|
|
18660
19039
|
const encoding = spec.encoding;
|
|
18661
19040
|
const descriptors = /* @__PURE__ */ new Map();
|
|
18662
19041
|
const allChannels = [
|
|
@@ -18717,6 +19096,19 @@ function computeStep(extent3, maxbins, nice) {
|
|
|
18717
19096
|
}
|
|
18718
19097
|
return step;
|
|
18719
19098
|
}
|
|
19099
|
+
function computeLogBinEdges(extent3, maxbins) {
|
|
19100
|
+
const lo = Math.max(extent3[0], Number.MIN_VALUE);
|
|
19101
|
+
const hi = Math.max(extent3[1], lo);
|
|
19102
|
+
const logMin = Math.log10(lo);
|
|
19103
|
+
const logMax = Math.log10(hi);
|
|
19104
|
+
const logSpan = logMax - logMin;
|
|
19105
|
+
if (logSpan === 0) return [lo, lo * 10];
|
|
19106
|
+
const edges = [];
|
|
19107
|
+
for (let i = 0; i <= maxbins; i++) {
|
|
19108
|
+
edges.push(10 ** (logMin + i / maxbins * logSpan));
|
|
19109
|
+
}
|
|
19110
|
+
return edges;
|
|
19111
|
+
}
|
|
18720
19112
|
function runBin(data, transform) {
|
|
18721
19113
|
const params = transform.bin === true ? {} : transform.bin;
|
|
18722
19114
|
const maxbins = params.maxbins ?? 10;
|
|
@@ -18729,14 +19121,38 @@ function runBin(data, transform) {
|
|
|
18729
19121
|
for (const row of data) {
|
|
18730
19122
|
const v = Number(row[field]);
|
|
18731
19123
|
if (Number.isFinite(v)) {
|
|
19124
|
+
if (params.scaleType === "log" && v <= 0) continue;
|
|
18732
19125
|
if (v < min4) min4 = v;
|
|
18733
19126
|
if (v > max4) max4 = v;
|
|
18734
19127
|
}
|
|
18735
19128
|
}
|
|
18736
19129
|
extent3 = [min4 === Infinity ? 0 : min4, max4 === -Infinity ? 0 : max4];
|
|
18737
19130
|
}
|
|
18738
|
-
const step = params.step ?? computeStep(extent3, maxbins, nice);
|
|
18739
19131
|
const [startAs, endAs] = Array.isArray(transform.as) ? transform.as : [transform.as, void 0];
|
|
19132
|
+
if (params.scaleType === "log") {
|
|
19133
|
+
const edges = computeLogBinEdges(extent3, maxbins);
|
|
19134
|
+
return data.map((row) => {
|
|
19135
|
+
const v = Number(row[field]);
|
|
19136
|
+
const newRow = { ...row };
|
|
19137
|
+
if (!Number.isFinite(v) || v <= 0) {
|
|
19138
|
+
newRow[startAs] = null;
|
|
19139
|
+
if (endAs) newRow[endAs] = null;
|
|
19140
|
+
} else {
|
|
19141
|
+
let lo = 0;
|
|
19142
|
+
let hi = edges.length - 2;
|
|
19143
|
+
while (lo < hi) {
|
|
19144
|
+
const mid = lo + hi + 1 >> 1;
|
|
19145
|
+
if (edges[mid] <= v) lo = mid;
|
|
19146
|
+
else hi = mid - 1;
|
|
19147
|
+
}
|
|
19148
|
+
const idx = Math.min(lo, edges.length - 2);
|
|
19149
|
+
newRow[startAs] = edges[idx];
|
|
19150
|
+
if (endAs) newRow[endAs] = edges[idx + 1];
|
|
19151
|
+
}
|
|
19152
|
+
return newRow;
|
|
19153
|
+
});
|
|
19154
|
+
}
|
|
19155
|
+
const step = params.step ?? computeStep(extent3, maxbins, nice);
|
|
18740
19156
|
return data.map((row) => {
|
|
18741
19157
|
const v = Number(row[field]);
|
|
18742
19158
|
const newRow = { ...row };
|
|
@@ -18788,6 +19204,135 @@ function runCalculate(data, transform) {
|
|
|
18788
19204
|
}));
|
|
18789
19205
|
}
|
|
18790
19206
|
|
|
19207
|
+
// src/transforms/density.ts
|
|
19208
|
+
import { deviation, quantile } from "d3-array";
|
|
19209
|
+
var DEFAULT_STEPS = 200;
|
|
19210
|
+
var MAX_STEPS = 1e4;
|
|
19211
|
+
var EXTENT_PADDING_BANDWIDTHS = 3;
|
|
19212
|
+
var DEGENERATE_BANDWIDTH = 1;
|
|
19213
|
+
var SQRT_2PI = Math.sqrt(2 * Math.PI);
|
|
19214
|
+
function silvermanBandwidth(values) {
|
|
19215
|
+
const n = values.length;
|
|
19216
|
+
if (n < 2) return DEGENERATE_BANDWIDTH;
|
|
19217
|
+
const sd = deviation(values) ?? 0;
|
|
19218
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
19219
|
+
const q1 = quantile(sorted, 0.25) ?? 0;
|
|
19220
|
+
const q3 = quantile(sorted, 0.75) ?? 0;
|
|
19221
|
+
const iqrScale = (q3 - q1) / 1.34;
|
|
19222
|
+
const spread = iqrScale > 0 ? Math.min(sd, iqrScale) : sd;
|
|
19223
|
+
if (!(spread > 0)) return DEGENERATE_BANDWIDTH;
|
|
19224
|
+
return 1.06 * spread * n ** (-1 / 5);
|
|
19225
|
+
}
|
|
19226
|
+
function resolveSteps(requested) {
|
|
19227
|
+
if (requested == null || !Number.isFinite(requested)) return DEFAULT_STEPS;
|
|
19228
|
+
return Math.min(MAX_STEPS, Math.max(2, Math.round(requested)));
|
|
19229
|
+
}
|
|
19230
|
+
function isUsableExtent(extent3) {
|
|
19231
|
+
const [lo, hi] = extent3;
|
|
19232
|
+
return Number.isFinite(lo) && Number.isFinite(hi) && hi > lo;
|
|
19233
|
+
}
|
|
19234
|
+
function gaussian(u) {
|
|
19235
|
+
return Math.exp(-0.5 * u * u) / SQRT_2PI;
|
|
19236
|
+
}
|
|
19237
|
+
function groupRows(data, groupby) {
|
|
19238
|
+
if (groupby.length === 0) return /* @__PURE__ */ new Map([["", data]]);
|
|
19239
|
+
const groups = /* @__PURE__ */ new Map();
|
|
19240
|
+
for (const row of data) {
|
|
19241
|
+
const key = groupby.map((f) => String(row[f] ?? "")).join("\0");
|
|
19242
|
+
const bucket = groups.get(key);
|
|
19243
|
+
if (bucket) bucket.push(row);
|
|
19244
|
+
else groups.set(key, [row]);
|
|
19245
|
+
}
|
|
19246
|
+
return groups;
|
|
19247
|
+
}
|
|
19248
|
+
function runDensity(data, transform) {
|
|
19249
|
+
const field = transform.density;
|
|
19250
|
+
const groupby = transform.groupby ?? [];
|
|
19251
|
+
const steps = resolveSteps(transform.steps);
|
|
19252
|
+
const [valueAs, densityAs] = transform.as ?? ["value", "density"];
|
|
19253
|
+
const cumulative = transform.cumulative === true;
|
|
19254
|
+
const counts = transform.counts === true;
|
|
19255
|
+
const groups = groupRows(data, groupby);
|
|
19256
|
+
const prepared = [];
|
|
19257
|
+
let widestBandwidth = 0;
|
|
19258
|
+
for (const rows of groups.values()) {
|
|
19259
|
+
const samples = [];
|
|
19260
|
+
for (const r of rows) {
|
|
19261
|
+
const raw = r[field];
|
|
19262
|
+
if (raw == null || raw === "") continue;
|
|
19263
|
+
const v = Number(raw);
|
|
19264
|
+
if (Number.isFinite(v)) samples.push(v);
|
|
19265
|
+
}
|
|
19266
|
+
if (samples.length === 0) continue;
|
|
19267
|
+
const bandwidth = transform.bandwidth && transform.bandwidth > 0 ? transform.bandwidth : silvermanBandwidth(samples);
|
|
19268
|
+
widestBandwidth = Math.max(widestBandwidth, bandwidth);
|
|
19269
|
+
prepared.push({ rows, samples, bandwidth });
|
|
19270
|
+
}
|
|
19271
|
+
if (prepared.length === 0) return [];
|
|
19272
|
+
let lo;
|
|
19273
|
+
let hi;
|
|
19274
|
+
if (transform.extent && isUsableExtent(transform.extent)) {
|
|
19275
|
+
[lo, hi] = transform.extent;
|
|
19276
|
+
} else {
|
|
19277
|
+
let dataMin = Number.POSITIVE_INFINITY;
|
|
19278
|
+
let dataMax = Number.NEGATIVE_INFINITY;
|
|
19279
|
+
for (const p of prepared) {
|
|
19280
|
+
for (const v of p.samples) {
|
|
19281
|
+
if (v < dataMin) dataMin = v;
|
|
19282
|
+
if (v > dataMax) dataMax = v;
|
|
19283
|
+
}
|
|
19284
|
+
}
|
|
19285
|
+
const pad = widestBandwidth * EXTENT_PADDING_BANDWIDTHS;
|
|
19286
|
+
lo = dataMin - pad;
|
|
19287
|
+
hi = dataMax + pad;
|
|
19288
|
+
}
|
|
19289
|
+
if (!(hi > lo)) {
|
|
19290
|
+
lo -= DEGENERATE_BANDWIDTH;
|
|
19291
|
+
hi += DEGENERATE_BANDWIDTH;
|
|
19292
|
+
}
|
|
19293
|
+
const stride = (hi - lo) / (steps - 1);
|
|
19294
|
+
const out = [];
|
|
19295
|
+
for (const { rows, samples, bandwidth } of prepared) {
|
|
19296
|
+
const groupValues = {};
|
|
19297
|
+
for (const f of groupby) groupValues[f] = rows[0][f];
|
|
19298
|
+
const n = samples.length;
|
|
19299
|
+
const scale = counts ? n : 1;
|
|
19300
|
+
const densities = new Array(steps);
|
|
19301
|
+
for (let i = 0; i < steps; i++) {
|
|
19302
|
+
const x = lo + i * stride;
|
|
19303
|
+
let sum = 0;
|
|
19304
|
+
for (const v of samples) sum += gaussian((x - v) / bandwidth);
|
|
19305
|
+
densities[i] = sum / (n * bandwidth);
|
|
19306
|
+
}
|
|
19307
|
+
if (cumulative) {
|
|
19308
|
+
const running = new Array(steps);
|
|
19309
|
+
let acc = 0;
|
|
19310
|
+
running[0] = 0;
|
|
19311
|
+
for (let i = 1; i < steps; i++) {
|
|
19312
|
+
acc += (densities[i] + densities[i - 1]) / 2 * stride;
|
|
19313
|
+
running[i] = acc;
|
|
19314
|
+
}
|
|
19315
|
+
const total = running[steps - 1] || 1;
|
|
19316
|
+
for (let i = 0; i < steps; i++) {
|
|
19317
|
+
out.push({
|
|
19318
|
+
...groupValues,
|
|
19319
|
+
[valueAs]: lo + i * stride,
|
|
19320
|
+
[densityAs]: running[i] / total * scale
|
|
19321
|
+
});
|
|
19322
|
+
}
|
|
19323
|
+
continue;
|
|
19324
|
+
}
|
|
19325
|
+
for (let i = 0; i < steps; i++) {
|
|
19326
|
+
out.push({
|
|
19327
|
+
...groupValues,
|
|
19328
|
+
[valueAs]: lo + i * stride,
|
|
19329
|
+
[densityAs]: densities[i] * scale
|
|
19330
|
+
});
|
|
19331
|
+
}
|
|
19332
|
+
}
|
|
19333
|
+
return out;
|
|
19334
|
+
}
|
|
19335
|
+
|
|
18791
19336
|
// src/transforms/filter.ts
|
|
18792
19337
|
function applyOffset2(anchor, offset, unit) {
|
|
18793
19338
|
const d = new Date(anchor.getTime());
|
|
@@ -18887,6 +19432,31 @@ function runFold(data, transform) {
|
|
|
18887
19432
|
return result;
|
|
18888
19433
|
}
|
|
18889
19434
|
|
|
19435
|
+
// src/transforms/joinaggregate.ts
|
|
19436
|
+
function groupKey2(row, groupby) {
|
|
19437
|
+
return groupby.map((f) => String(row[f] ?? "")).join("\0");
|
|
19438
|
+
}
|
|
19439
|
+
function runJoinAggregate(data, transform) {
|
|
19440
|
+
const groupby = transform.groupby ?? [];
|
|
19441
|
+
const groups = /* @__PURE__ */ new Map();
|
|
19442
|
+
for (const row of data) {
|
|
19443
|
+
const key = groupKey2(row, groupby);
|
|
19444
|
+
const bucket = groups.get(key);
|
|
19445
|
+
if (bucket) bucket.push(row);
|
|
19446
|
+
else groups.set(key, [row]);
|
|
19447
|
+
}
|
|
19448
|
+
const summaries = /* @__PURE__ */ new Map();
|
|
19449
|
+
for (const [key, rows] of groups) {
|
|
19450
|
+
const summary = {};
|
|
19451
|
+
for (const { op, field, as } of transform.joinaggregate) {
|
|
19452
|
+
const values = op === "count" ? rows.map(() => 1) : rows.map((r) => Number(r[field])).filter((v) => Number.isFinite(v));
|
|
19453
|
+
summary[as] = computeAggregate(op, values);
|
|
19454
|
+
}
|
|
19455
|
+
summaries.set(key, summary);
|
|
19456
|
+
}
|
|
19457
|
+
return data.map((row) => ({ ...row, ...summaries.get(groupKey2(row, groupby)) }));
|
|
19458
|
+
}
|
|
19459
|
+
|
|
18890
19460
|
// src/transforms/timeunit.ts
|
|
18891
19461
|
function extractTimeUnit(date, unit) {
|
|
18892
19462
|
switch (unit) {
|
|
@@ -18953,7 +19523,7 @@ function runTimeUnit(data, transform) {
|
|
|
18953
19523
|
}
|
|
18954
19524
|
|
|
18955
19525
|
// src/transforms/window.ts
|
|
18956
|
-
function
|
|
19526
|
+
function groupKey3(row, groupby) {
|
|
18957
19527
|
return groupby.map((f) => String(row[f] ?? "")).join("\0");
|
|
18958
19528
|
}
|
|
18959
19529
|
function tryParseDate(val) {
|
|
@@ -18985,7 +19555,7 @@ function runWindow(data, transform) {
|
|
|
18985
19555
|
const indexed = data.map((row, i) => ({ row, originalIndex: i }));
|
|
18986
19556
|
const groups = /* @__PURE__ */ new Map();
|
|
18987
19557
|
for (const entry of indexed) {
|
|
18988
|
-
const key = groupby.length > 0 ?
|
|
19558
|
+
const key = groupby.length > 0 ? groupKey3(entry.row, groupby) : "";
|
|
18989
19559
|
const existing = groups.get(key);
|
|
18990
19560
|
if (existing) {
|
|
18991
19561
|
existing.push(entry);
|
|
@@ -19091,6 +19661,10 @@ function runTransforms(data, transforms) {
|
|
|
19091
19661
|
result = runTimeUnit(result, transform);
|
|
19092
19662
|
} else if ("aggregate" in transform) {
|
|
19093
19663
|
result = runAggregate(result, transform);
|
|
19664
|
+
} else if ("joinaggregate" in transform) {
|
|
19665
|
+
result = runJoinAggregate(result, transform);
|
|
19666
|
+
} else if ("density" in transform) {
|
|
19667
|
+
result = runDensity(result, transform);
|
|
19094
19668
|
} else if ("fold" in transform) {
|
|
19095
19669
|
result = runFold(result, transform);
|
|
19096
19670
|
} else if ("window" in transform) {
|
|
@@ -19455,6 +20029,14 @@ function compileChart(spec, optionsInput) {
|
|
|
19455
20029
|
height: chartArea.height
|
|
19456
20030
|
};
|
|
19457
20031
|
}
|
|
20032
|
+
if (chartSpec.userExplicit.labels && chartSpec.labels?.density !== "none" && isBinnedBarEncoding(chartSpec.markType, renderSpec.encoding)) {
|
|
20033
|
+
emitSpecWarnings(
|
|
20034
|
+
[
|
|
20035
|
+
"[openchart] `labels` has no effect on a histogram (binned bar): a value over every bin obscures the distribution. Remove it, or use mark: 'bar' over pre-aggregated categories if you need per-bar values."
|
|
20036
|
+
],
|
|
20037
|
+
options.onWarn
|
|
20038
|
+
);
|
|
20039
|
+
}
|
|
19458
20040
|
const scales = computeScales(renderSpec, scaleArea, renderSpec.data);
|
|
19459
20041
|
emitSpecWarnings(
|
|
19460
20042
|
applyColorScaleRange(
|