@opendata-ai/openchart-engine 8.4.0 → 8.5.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 +26 -2
- package/dist/index.js +296 -75
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
generateAltText,
|
|
7
7
|
generateDataTable,
|
|
8
8
|
getBreakpoint as getBreakpoint3,
|
|
9
|
-
getHeightClass,
|
|
9
|
+
getHeightClass as getHeightClass3,
|
|
10
10
|
getLayoutStrategy,
|
|
11
11
|
isAxislessMark as isAxislessMark3,
|
|
12
12
|
MAX_SR_TABLE_ROWS,
|
|
@@ -839,6 +839,7 @@ var OBSTACLE_WEIGHTS = {
|
|
|
839
839
|
"data-label": 8,
|
|
840
840
|
"endpoint-label": 8,
|
|
841
841
|
legend: 6,
|
|
842
|
+
chrome: 6,
|
|
842
843
|
watermark: 6,
|
|
843
844
|
"axis-band": 6,
|
|
844
845
|
"area-fill": 1.5
|
|
@@ -1207,7 +1208,7 @@ function resolveAnnotationCollisions(annotations, originalSpecs, scales, chartAr
|
|
|
1207
1208
|
placedBounds.push(annotation.bounds ?? annotationBlockBounds(annotation, label, measure));
|
|
1208
1209
|
}
|
|
1209
1210
|
}
|
|
1210
|
-
function clampAnnotationsToBounds(annotations, svgWidth, svgHeight, measure = heuristicMeasure) {
|
|
1211
|
+
function clampAnnotationsToBounds(annotations, svgWidth, svgHeight, measure = heuristicMeasure, topBound = CLAMP_MARGIN) {
|
|
1211
1212
|
for (const annotation of annotations) {
|
|
1212
1213
|
if (annotation.type !== "text" || !annotation.label) continue;
|
|
1213
1214
|
const label = annotation.label;
|
|
@@ -1220,8 +1221,8 @@ function clampAnnotationsToBounds(annotations, svgWidth, svgHeight, measure = he
|
|
|
1220
1221
|
if (bounds.x + dx < CLAMP_MARGIN) {
|
|
1221
1222
|
dx = CLAMP_MARGIN - bounds.x;
|
|
1222
1223
|
}
|
|
1223
|
-
if (bounds.y <
|
|
1224
|
-
dy =
|
|
1224
|
+
if (bounds.y < topBound) {
|
|
1225
|
+
dy = topBound - bounds.y;
|
|
1225
1226
|
}
|
|
1226
1227
|
if (bounds.y + bounds.height + dy > svgHeight - CLAMP_MARGIN) {
|
|
1227
1228
|
dy = svgHeight - CLAMP_MARGIN - (bounds.y + bounds.height);
|
|
@@ -1667,7 +1668,7 @@ function computeAnnotations(spec, scalesOrCtx, chartArea, strategy, isDark, obst
|
|
|
1667
1668
|
autoQueue.length > 0 ? (i) => autoIndices.has(i) : void 0
|
|
1668
1669
|
);
|
|
1669
1670
|
if (ctx.svg.width > 0 && ctx.svg.height > 0) {
|
|
1670
|
-
clampAnnotationsToBounds(annotations, ctx.svg.width, ctx.svg.height, ctx.measure);
|
|
1671
|
+
clampAnnotationsToBounds(annotations, ctx.svg.width, ctx.svg.height, ctx.measure, ctx.topBound);
|
|
1671
1672
|
}
|
|
1672
1673
|
annotations.sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));
|
|
1673
1674
|
return annotations;
|
|
@@ -2867,6 +2868,7 @@ function computeBarLabels(marks, chartArea, density = "auto", labelFormatter, la
|
|
|
2867
2868
|
const candidates = [];
|
|
2868
2869
|
const fitsInSegment = [];
|
|
2869
2870
|
const displayAnchorX = [];
|
|
2871
|
+
const haloFlags = [];
|
|
2870
2872
|
const formatter = labelFormatter ?? null;
|
|
2871
2873
|
for (const mark of targetMarks) {
|
|
2872
2874
|
let valuePart;
|
|
@@ -2928,6 +2930,7 @@ function computeBarLabels(marks, chartArea, density = "auto", labelFormatter, la
|
|
|
2928
2930
|
const anchorY = centerY - textHeight / 2;
|
|
2929
2931
|
const leftEdgeX = textAnchor === "end" ? anchorX - textWidth : textAnchor === "middle" ? anchorX - textWidth / 2 : anchorX;
|
|
2930
2932
|
const fits = !(isStacked2 && textWidth > mark.width - 2 * LABEL_PADDING);
|
|
2933
|
+
haloFlags.push(!isInside);
|
|
2931
2934
|
displayAnchorX.push(anchorX);
|
|
2932
2935
|
fitsInSegment.push(fits);
|
|
2933
2936
|
candidates.push({
|
|
@@ -2972,7 +2975,8 @@ function computeBarLabels(marks, chartArea, density = "auto", labelFormatter, la
|
|
|
2972
2975
|
x: displayAnchorX[i],
|
|
2973
2976
|
y: candidates[i].anchorY + halfHeight,
|
|
2974
2977
|
style: candidates[i].style,
|
|
2975
|
-
visible: false
|
|
2978
|
+
visible: false,
|
|
2979
|
+
...haloFlags[i] ? {} : { halo: false }
|
|
2976
2980
|
});
|
|
2977
2981
|
} else {
|
|
2978
2982
|
const r = resolvedByOrig.get(i);
|
|
@@ -2981,6 +2985,7 @@ function computeBarLabels(marks, chartArea, density = "auto", labelFormatter, la
|
|
|
2981
2985
|
const { index: _index, ...rest } = r;
|
|
2982
2986
|
results.push({
|
|
2983
2987
|
...rest,
|
|
2988
|
+
...haloFlags[i] ? {} : { halo: false },
|
|
2984
2989
|
x: r.x + dx,
|
|
2985
2990
|
y: r.y + halfHeight,
|
|
2986
2991
|
connector: r.connector ? {
|
|
@@ -3774,6 +3779,7 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormatter
|
|
|
3774
3779
|
const targetMarks = filterByDensity(marks, density);
|
|
3775
3780
|
const formatter = labelFormatter ?? null;
|
|
3776
3781
|
const candidates = [];
|
|
3782
|
+
const haloFlags = [];
|
|
3777
3783
|
const fitsInSegment = [];
|
|
3778
3784
|
for (const mark of targetMarks) {
|
|
3779
3785
|
let valuePart;
|
|
@@ -3813,6 +3819,7 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormatter
|
|
|
3813
3819
|
fill = labelColor ?? getRepresentativeColor3(mark.fill);
|
|
3814
3820
|
}
|
|
3815
3821
|
fitsInSegment.push(!(isStacked2 && textHeight > mark.height - 2 * LABEL_PADDING_Y));
|
|
3822
|
+
haloFlags.push(!isStacked2);
|
|
3816
3823
|
candidates.push({
|
|
3817
3824
|
text: valuePart,
|
|
3818
3825
|
anchorX,
|
|
@@ -3854,7 +3861,8 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormatter
|
|
|
3854
3861
|
x: candidates[i].anchorX,
|
|
3855
3862
|
y: candidates[i].anchorY + ascent,
|
|
3856
3863
|
style: candidates[i].style,
|
|
3857
|
-
visible: false
|
|
3864
|
+
visible: false,
|
|
3865
|
+
...haloFlags[i] ? {} : { halo: false }
|
|
3858
3866
|
});
|
|
3859
3867
|
continue;
|
|
3860
3868
|
}
|
|
@@ -3863,6 +3871,7 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormatter
|
|
|
3863
3871
|
const { index: _index, ...rest } = r;
|
|
3864
3872
|
results.push({
|
|
3865
3873
|
...rest,
|
|
3874
|
+
...haloFlags[i] ? {} : { halo: false },
|
|
3866
3875
|
y: r.y + ascent,
|
|
3867
3876
|
// The connector originates at the label; shift its start to the baseline
|
|
3868
3877
|
// too so the leader stays glued to the shifted text.
|
|
@@ -7421,7 +7430,8 @@ function normalizeGraphSpec(spec, warnings) {
|
|
|
7421
7430
|
watermark: spec.watermark ?? true,
|
|
7422
7431
|
animation: spec.animation,
|
|
7423
7432
|
interaction: spec.interaction,
|
|
7424
|
-
legend: spec.legend
|
|
7433
|
+
legend: spec.legend,
|
|
7434
|
+
dimensions: spec.dimensions
|
|
7425
7435
|
};
|
|
7426
7436
|
}
|
|
7427
7437
|
function normalizeTileMapSpec(spec, warnings) {
|
|
@@ -10271,6 +10281,7 @@ import {
|
|
|
10271
10281
|
computeXAxisExtentFromLabels as computeXAxisExtentFromLabels2,
|
|
10272
10282
|
defaultNumberFormatter as defaultNumberFormatter6,
|
|
10273
10283
|
estimateTextWidth as estimateTextWidth16,
|
|
10284
|
+
getHeightClass,
|
|
10274
10285
|
HPAD_COMPACT_FRACTION,
|
|
10275
10286
|
HPAD_COMPACT_MIN,
|
|
10276
10287
|
isAxislessMark,
|
|
@@ -12268,6 +12279,7 @@ function resolveLayoutPlan(chartSpec, renderSpec, options, theme, strategy, wate
|
|
|
12268
12279
|
if (renderSpec.display === "sparkline" && !renderSpec.userExplicit?.chrome) {
|
|
12269
12280
|
chromeMode = "hidden";
|
|
12270
12281
|
}
|
|
12282
|
+
const crampedTitle = getHeightClass(height) === "cramped";
|
|
12271
12283
|
if (renderSpec.display === "sparkline" || isRadial) {
|
|
12272
12284
|
const legendContent = computeLegendContent(
|
|
12273
12285
|
chartSpec,
|
|
@@ -12287,7 +12299,8 @@ function resolveLayoutPlan(chartSpec, renderSpec, options, theme, strategy, wate
|
|
|
12287
12299
|
chromeMode,
|
|
12288
12300
|
padding,
|
|
12289
12301
|
watermark,
|
|
12290
|
-
bottomLegendReservation
|
|
12302
|
+
bottomLegendReservation,
|
|
12303
|
+
crampedTitle
|
|
12291
12304
|
);
|
|
12292
12305
|
return {
|
|
12293
12306
|
leftGutter: hPad,
|
|
@@ -12411,7 +12424,8 @@ function resolveLayoutPlan(chartSpec, renderSpec, options, theme, strategy, wate
|
|
|
12411
12424
|
chromeMode,
|
|
12412
12425
|
padding,
|
|
12413
12426
|
watermark,
|
|
12414
|
-
bottomLegendReservation
|
|
12427
|
+
bottomLegendReservation,
|
|
12428
|
+
crampedTitle
|
|
12415
12429
|
);
|
|
12416
12430
|
let effectiveXTickAngle = xTickAngle;
|
|
12417
12431
|
if (xTickAngle === void 0 && xLabels.length > 1) {
|
|
@@ -13550,6 +13564,54 @@ function topologyBBox(topology) {
|
|
|
13550
13564
|
var OVERLAY_LEGEND_INSET = 18;
|
|
13551
13565
|
var NO_DATA_SWATCH_GAP = 12;
|
|
13552
13566
|
var NO_DATA_LABEL_GAP = 5;
|
|
13567
|
+
var CATEGORICAL_SWATCH_SIZE = 10;
|
|
13568
|
+
var CATEGORICAL_SWATCH_GAP = 6;
|
|
13569
|
+
var CATEGORICAL_ENTRY_GAP = 16;
|
|
13570
|
+
var CATEGORICAL_ROW_HEIGHT = CATEGORICAL_SWATCH_SIZE + 6;
|
|
13571
|
+
function categoricalLegendLabelStyle(theme) {
|
|
13572
|
+
return {
|
|
13573
|
+
fontFamily: theme.fonts.family,
|
|
13574
|
+
fontSize: 11,
|
|
13575
|
+
fontWeight: 400,
|
|
13576
|
+
fill: theme.colors.text,
|
|
13577
|
+
lineHeight: 1.2
|
|
13578
|
+
};
|
|
13579
|
+
}
|
|
13580
|
+
function categoricalEntryWidth(label, labelStyle) {
|
|
13581
|
+
return CATEGORICAL_SWATCH_SIZE + CATEGORICAL_SWATCH_GAP + estimateTextWidth18(label, labelStyle.fontSize, labelStyle.fontWeight) + CATEGORICAL_ENTRY_GAP;
|
|
13582
|
+
}
|
|
13583
|
+
function noDataBlockWidth(labelStyle) {
|
|
13584
|
+
return CATEGORICAL_SWATCH_SIZE + NO_DATA_LABEL_GAP + estimateTextWidth18("No data", labelStyle.fontSize, labelStyle.fontWeight);
|
|
13585
|
+
}
|
|
13586
|
+
function wrapCategoricalEntries(widths, legendWidth) {
|
|
13587
|
+
const placed = [];
|
|
13588
|
+
let x = 0;
|
|
13589
|
+
let row = 0;
|
|
13590
|
+
for (let i = 0; i < widths.length; i++) {
|
|
13591
|
+
if (i > 0 && x + widths[i] > legendWidth) {
|
|
13592
|
+
x = 0;
|
|
13593
|
+
row += 1;
|
|
13594
|
+
}
|
|
13595
|
+
placed.push({ x, row });
|
|
13596
|
+
x += widths[i];
|
|
13597
|
+
}
|
|
13598
|
+
return placed;
|
|
13599
|
+
}
|
|
13600
|
+
function collectCategories(rows, field, domain) {
|
|
13601
|
+
const categories = Array.isArray(domain) ? domain.map((c) => String(c)) : [];
|
|
13602
|
+
const seen = new Set(categories);
|
|
13603
|
+
for (const row of rows) {
|
|
13604
|
+
const raw = row[field];
|
|
13605
|
+
if (raw != null) {
|
|
13606
|
+
const cat = String(raw);
|
|
13607
|
+
if (!seen.has(cat)) {
|
|
13608
|
+
seen.add(cat);
|
|
13609
|
+
categories.push(cat);
|
|
13610
|
+
}
|
|
13611
|
+
}
|
|
13612
|
+
}
|
|
13613
|
+
return categories;
|
|
13614
|
+
}
|
|
13553
13615
|
function legendTitleFor(colorEncoding) {
|
|
13554
13616
|
const base = colorEncoding.title ?? colorEncoding.field;
|
|
13555
13617
|
if (!base) return void 0;
|
|
@@ -13678,7 +13740,16 @@ function compileGeoMap(spec, options) {
|
|
|
13678
13740
|
}
|
|
13679
13741
|
if (legendBlockHeight === 0 && showChoroplethLegend && colorEncoding && !isQuantitative) {
|
|
13680
13742
|
const labelHeight = Math.ceil(theme.fonts.sizes.small * 1.3);
|
|
13681
|
-
|
|
13743
|
+
const labelStyle = categoricalLegendLabelStyle(theme);
|
|
13744
|
+
const widths = collectCategories(
|
|
13745
|
+
mapSpec.data,
|
|
13746
|
+
colorEncoding.field,
|
|
13747
|
+
colorEncoding.scale?.domain
|
|
13748
|
+
).map((cat) => categoricalEntryWidth(cat, labelStyle));
|
|
13749
|
+
widths.push(noDataBlockWidth(labelStyle));
|
|
13750
|
+
const placed = wrapCategoricalEntries(widths, fullArea.width);
|
|
13751
|
+
const rows = placed.length > 0 ? placed[placed.length - 1].row + 1 : 1;
|
|
13752
|
+
legendBlockHeight = rows * (Math.max(10, labelHeight) + 6);
|
|
13682
13753
|
}
|
|
13683
13754
|
if (showLegend && !pointLegendOverlay && mapSpec.points?.color) {
|
|
13684
13755
|
if (mapSpec.points.color.type === "quantitative") {
|
|
@@ -13747,6 +13818,11 @@ function compileGeoMap(spec, options) {
|
|
|
13747
13818
|
const droppedFeatures = [];
|
|
13748
13819
|
const formatter = buildD3Formatter2(mapSpec.encoding?.color?.format ?? mapSpec.valueFormat) ?? formatNumber3;
|
|
13749
13820
|
const neutralFill = isDarkMode ? theme.colors.neutral[800] : theme.colors.neutral[100];
|
|
13821
|
+
const noDataPattern = {
|
|
13822
|
+
type: "diagonal",
|
|
13823
|
+
base: neutralFill,
|
|
13824
|
+
line: isDarkMode ? theme.colors.neutral[600] : theme.colors.neutral[300]
|
|
13825
|
+
};
|
|
13750
13826
|
let featureMarks;
|
|
13751
13827
|
let continuousLegend = null;
|
|
13752
13828
|
let categoricalLegend = null;
|
|
@@ -13765,6 +13841,7 @@ function compileGeoMap(spec, options) {
|
|
|
13765
13841
|
colorField: colorEncoding.field,
|
|
13766
13842
|
classScale,
|
|
13767
13843
|
neutralFill,
|
|
13844
|
+
noDataPattern,
|
|
13768
13845
|
formatter,
|
|
13769
13846
|
droppedFeatures
|
|
13770
13847
|
});
|
|
@@ -13799,16 +13876,21 @@ function compileGeoMap(spec, options) {
|
|
|
13799
13876
|
height: continuousContent.barHeight
|
|
13800
13877
|
};
|
|
13801
13878
|
const labelY = bar.y + bar.height + CONTINUOUS_LABEL_GAP + labelStyle.fontSize;
|
|
13802
|
-
const hasUnmatched = featureMarks.some((m) =>
|
|
13803
|
-
const raw = m.data?.[colorEncoding.field];
|
|
13804
|
-
return raw == null || Number.isNaN(Number(raw));
|
|
13805
|
-
});
|
|
13879
|
+
const hasUnmatched = featureMarks.some((m) => m.noData);
|
|
13806
13880
|
const noDataX = bar.x + bar.width + NO_DATA_SWATCH_GAP;
|
|
13807
|
-
const
|
|
13881
|
+
const noDataLabelWidth = estimateTextWidth18(
|
|
13882
|
+
"No data",
|
|
13883
|
+
labelStyle.fontSize,
|
|
13884
|
+
labelStyle.fontWeight
|
|
13885
|
+
);
|
|
13886
|
+
const noData = hasUnmatched && noDataX + bar.height + NO_DATA_LABEL_GAP + noDataLabelWidth < fullArea.x + fullArea.width ? {
|
|
13808
13887
|
x: noDataX,
|
|
13809
13888
|
y: bar.y,
|
|
13810
13889
|
size: bar.height,
|
|
13811
13890
|
fill: neutralFill,
|
|
13891
|
+
// Same hatch the no-data features carry, so the key matches what
|
|
13892
|
+
// is on the map rather than a neutral the ramp's center mimics.
|
|
13893
|
+
pattern: noDataPattern,
|
|
13812
13894
|
label: "No data",
|
|
13813
13895
|
labelX: noDataX + bar.height + NO_DATA_LABEL_GAP,
|
|
13814
13896
|
labelY: bar.y + bar.height
|
|
@@ -13839,6 +13921,7 @@ function compileGeoMap(spec, options) {
|
|
|
13839
13921
|
colorScale: colorEncoding.scale,
|
|
13840
13922
|
isDarkMode: !!isDarkMode,
|
|
13841
13923
|
neutralFill,
|
|
13924
|
+
noDataPattern,
|
|
13842
13925
|
theme,
|
|
13843
13926
|
fullArea,
|
|
13844
13927
|
mapAreaHeight,
|
|
@@ -14290,6 +14373,7 @@ function buildQuantitativeMarks(opts) {
|
|
|
14290
14373
|
colorField,
|
|
14291
14374
|
classScale,
|
|
14292
14375
|
neutralFill,
|
|
14376
|
+
noDataPattern,
|
|
14293
14377
|
formatter,
|
|
14294
14378
|
droppedFeatures
|
|
14295
14379
|
} = opts;
|
|
@@ -14307,13 +14391,17 @@ function buildQuantitativeMarks(opts) {
|
|
|
14307
14391
|
const hasData = dataRow !== null;
|
|
14308
14392
|
let fill = neutralFill;
|
|
14309
14393
|
let binIndex;
|
|
14310
|
-
|
|
14394
|
+
let hasValue = false;
|
|
14395
|
+
if (hasData) {
|
|
14311
14396
|
const raw = dataRow[colorField];
|
|
14312
14397
|
if (raw != null) {
|
|
14313
14398
|
const v = Number(raw);
|
|
14314
14399
|
if (!Number.isNaN(v)) {
|
|
14315
|
-
|
|
14316
|
-
|
|
14400
|
+
hasValue = true;
|
|
14401
|
+
if (classScale) {
|
|
14402
|
+
fill = classScale.color(v);
|
|
14403
|
+
binIndex = classScale.binIndex(v);
|
|
14404
|
+
}
|
|
14317
14405
|
}
|
|
14318
14406
|
}
|
|
14319
14407
|
}
|
|
@@ -14344,7 +14432,8 @@ function buildQuantitativeMarks(opts) {
|
|
|
14344
14432
|
animationIndex: animIndex++,
|
|
14345
14433
|
bounds,
|
|
14346
14434
|
centroid: [cx, cy],
|
|
14347
|
-
binIndex
|
|
14435
|
+
binIndex,
|
|
14436
|
+
...hasValue ? {} : { noData: true, pattern: noDataPattern }
|
|
14348
14437
|
});
|
|
14349
14438
|
}
|
|
14350
14439
|
return { marks };
|
|
@@ -14358,6 +14447,7 @@ function buildCategoricalMarks(opts) {
|
|
|
14358
14447
|
colorScale: scaleConfig,
|
|
14359
14448
|
isDarkMode,
|
|
14360
14449
|
neutralFill,
|
|
14450
|
+
noDataPattern,
|
|
14361
14451
|
theme,
|
|
14362
14452
|
fullArea,
|
|
14363
14453
|
mapAreaHeight,
|
|
@@ -14366,24 +14456,7 @@ function buildCategoricalMarks(opts) {
|
|
|
14366
14456
|
showLegend,
|
|
14367
14457
|
droppedFeatures
|
|
14368
14458
|
} = opts;
|
|
14369
|
-
const categories =
|
|
14370
|
-
const explicitDomain = scaleConfig?.domain;
|
|
14371
|
-
if (explicitDomain && Array.isArray(explicitDomain)) {
|
|
14372
|
-
for (const cat of explicitDomain) {
|
|
14373
|
-
categories.push(String(cat));
|
|
14374
|
-
}
|
|
14375
|
-
}
|
|
14376
|
-
const seen = new Set(categories);
|
|
14377
|
-
for (const row of joined.values()) {
|
|
14378
|
-
const raw = row[colorField];
|
|
14379
|
-
if (raw != null) {
|
|
14380
|
-
const cat = String(raw);
|
|
14381
|
-
if (!seen.has(cat)) {
|
|
14382
|
-
seen.add(cat);
|
|
14383
|
-
categories.push(cat);
|
|
14384
|
-
}
|
|
14385
|
-
}
|
|
14386
|
-
}
|
|
14459
|
+
const categories = collectCategories(joined.values(), colorField, scaleConfig?.domain);
|
|
14387
14460
|
const explicitRange = scaleConfig?.range;
|
|
14388
14461
|
const colorSource = explicitRange && Array.isArray(explicitRange) ? explicitRange : CATEGORICAL_PALETTE2;
|
|
14389
14462
|
const categoryColors = /* @__PURE__ */ new Map();
|
|
@@ -14403,10 +14476,12 @@ function buildCategoricalMarks(opts) {
|
|
|
14403
14476
|
const dataRow = joined.get(featureId) ?? null;
|
|
14404
14477
|
const hasData = dataRow !== null;
|
|
14405
14478
|
let fill = neutralFill;
|
|
14479
|
+
let hasValue = false;
|
|
14406
14480
|
if (hasData) {
|
|
14407
14481
|
const raw = dataRow[colorField];
|
|
14408
14482
|
if (raw != null) {
|
|
14409
14483
|
const cat = String(raw);
|
|
14484
|
+
hasValue = true;
|
|
14410
14485
|
fill = categoryColors.get(cat) ?? neutralFill;
|
|
14411
14486
|
}
|
|
14412
14487
|
}
|
|
@@ -14436,18 +14511,13 @@ function buildCategoricalMarks(opts) {
|
|
|
14436
14511
|
},
|
|
14437
14512
|
animationIndex: animIndex++,
|
|
14438
14513
|
bounds,
|
|
14439
|
-
centroid: [cx, cy]
|
|
14514
|
+
centroid: [cx, cy],
|
|
14515
|
+
...hasValue ? {} : { noData: true, pattern: noDataPattern }
|
|
14440
14516
|
});
|
|
14441
14517
|
}
|
|
14442
14518
|
let legend = null;
|
|
14443
14519
|
if (showLegend && categories.length > 0) {
|
|
14444
|
-
const labelStyle =
|
|
14445
|
-
fontFamily: theme.fonts.family,
|
|
14446
|
-
fontSize: 11,
|
|
14447
|
-
fontWeight: 400,
|
|
14448
|
-
fill: theme.colors.text,
|
|
14449
|
-
lineHeight: 1.2
|
|
14450
|
-
};
|
|
14520
|
+
const labelStyle = categoricalLegendLabelStyle(theme);
|
|
14451
14521
|
const entries = categories.map((cat) => ({
|
|
14452
14522
|
label: cat,
|
|
14453
14523
|
color: categoryColors.get(cat),
|
|
@@ -14456,19 +14526,67 @@ function buildCategoricalMarks(opts) {
|
|
|
14456
14526
|
const legendX = fullArea.x;
|
|
14457
14527
|
const legendY = legendPosition === "bottom" ? fullArea.y + mapAreaHeight + legendReserveGap : fullArea.y + legendReserveGap;
|
|
14458
14528
|
const legendWidth = fullArea.width;
|
|
14459
|
-
const swatchSize =
|
|
14460
|
-
const swatchGap =
|
|
14461
|
-
const entryGap =
|
|
14529
|
+
const swatchSize = CATEGORICAL_SWATCH_SIZE;
|
|
14530
|
+
const swatchGap = CATEGORICAL_SWATCH_GAP;
|
|
14531
|
+
const entryGap = CATEGORICAL_ENTRY_GAP;
|
|
14532
|
+
const entryWidths = entries.map((e) => categoricalEntryWidth(e.label, labelStyle));
|
|
14533
|
+
const placed = wrapCategoricalEntries(entryWidths, legendWidth);
|
|
14534
|
+
const entryPositions = placed.map((p, i) => {
|
|
14535
|
+
const x = legendX + p.x;
|
|
14536
|
+
const y = legendY + p.row * CATEGORICAL_ROW_HEIGHT;
|
|
14537
|
+
return {
|
|
14538
|
+
x,
|
|
14539
|
+
y,
|
|
14540
|
+
labelX: x + swatchSize + swatchGap,
|
|
14541
|
+
labelY: y + swatchSize / 2,
|
|
14542
|
+
width: entryWidths[i],
|
|
14543
|
+
row: p.row
|
|
14544
|
+
};
|
|
14545
|
+
});
|
|
14546
|
+
let rows = placed.length > 0 ? placed[placed.length - 1].row + 1 : 1;
|
|
14547
|
+
const hasUnmatched = marks.some((m) => m.noData);
|
|
14548
|
+
const last = placed[placed.length - 1];
|
|
14549
|
+
const blockWidth = noDataBlockWidth(labelStyle);
|
|
14550
|
+
let noDataOffsetX = last ? last.x + entryWidths[entryWidths.length - 1] : 0;
|
|
14551
|
+
let noDataRow = last ? last.row : 0;
|
|
14552
|
+
if (noDataOffsetX + blockWidth > legendWidth) {
|
|
14553
|
+
noDataOffsetX = 0;
|
|
14554
|
+
noDataRow += 1;
|
|
14555
|
+
}
|
|
14556
|
+
const noDataX = legendX + noDataOffsetX;
|
|
14557
|
+
const noDataY = legendY + noDataRow * CATEGORICAL_ROW_HEIGHT;
|
|
14558
|
+
const chipSize = Math.round(swatchSize * 0.6);
|
|
14559
|
+
const noData = hasUnmatched && blockWidth <= legendWidth ? {
|
|
14560
|
+
x: noDataX + (swatchSize - chipSize) / 2,
|
|
14561
|
+
y: noDataY + swatchSize / 2 - chipSize / 2,
|
|
14562
|
+
size: chipSize,
|
|
14563
|
+
fill: neutralFill,
|
|
14564
|
+
// The same hatch the no-data features carry, so the key matches
|
|
14565
|
+
// what is on the map.
|
|
14566
|
+
pattern: noDataPattern,
|
|
14567
|
+
label: "No data",
|
|
14568
|
+
labelX: noDataX + swatchSize + NO_DATA_LABEL_GAP,
|
|
14569
|
+
labelY: noDataY + swatchSize / 2
|
|
14570
|
+
} : void 0;
|
|
14571
|
+
if (noData) rows = Math.max(rows, noDataRow + 1);
|
|
14462
14572
|
legend = {
|
|
14463
14573
|
type: "categorical",
|
|
14464
14574
|
position: legendPosition,
|
|
14465
|
-
bounds: {
|
|
14575
|
+
bounds: {
|
|
14576
|
+
x: legendX,
|
|
14577
|
+
y: legendY,
|
|
14578
|
+
width: legendWidth,
|
|
14579
|
+
height: rows * CATEGORICAL_ROW_HEIGHT
|
|
14580
|
+
},
|
|
14466
14581
|
labelStyle,
|
|
14467
14582
|
entries,
|
|
14583
|
+
entryPositions,
|
|
14584
|
+
rowHeight: CATEGORICAL_ROW_HEIGHT,
|
|
14468
14585
|
swatchSize,
|
|
14469
14586
|
swatchGap,
|
|
14470
14587
|
entryGap,
|
|
14471
|
-
swatchChipFill: isDarkMode ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)"
|
|
14588
|
+
swatchChipFill: isDarkMode ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.04)",
|
|
14589
|
+
noData
|
|
14472
14590
|
};
|
|
14473
14591
|
}
|
|
14474
14592
|
return { marks, legend };
|
|
@@ -14992,6 +15110,9 @@ function resolveGraphInteraction(cfg) {
|
|
|
14992
15110
|
};
|
|
14993
15111
|
}
|
|
14994
15112
|
|
|
15113
|
+
// src/graphs/types.ts
|
|
15114
|
+
var MAX_3D_NODES = 2e3;
|
|
15115
|
+
|
|
14995
15116
|
// src/graphs/compile-graph.ts
|
|
14996
15117
|
var graphNumberFormatter = defaultNumberFormatter8({ allIntegers: false, surface: "chart" });
|
|
14997
15118
|
function mergeOverrides(...layers) {
|
|
@@ -15147,6 +15268,7 @@ function compileGraph(spec, options) {
|
|
|
15147
15268
|
);
|
|
15148
15269
|
}
|
|
15149
15270
|
const graphSpec = normalized;
|
|
15271
|
+
const numDimensions = resolveNumDimensions(graphSpec, options.onWarn);
|
|
15150
15272
|
for (const [name, ch] of [
|
|
15151
15273
|
["nodeColor", graphSpec.encoding.nodeColor],
|
|
15152
15274
|
["edgeColor", graphSpec.encoding.edgeColor]
|
|
@@ -15288,9 +15410,39 @@ function compileGraph(spec, options) {
|
|
|
15288
15410
|
legendField,
|
|
15289
15411
|
initialHighlight,
|
|
15290
15412
|
seedNodeIds,
|
|
15291
|
-
edgeLegend
|
|
15413
|
+
edgeLegend,
|
|
15414
|
+
numDimensions
|
|
15292
15415
|
};
|
|
15293
15416
|
}
|
|
15417
|
+
function resolveNumDimensions(spec, onWarn) {
|
|
15418
|
+
const requested = spec.dimensions;
|
|
15419
|
+
if (requested !== void 0 && requested !== 2 && requested !== 3) {
|
|
15420
|
+
throw new Error(
|
|
15421
|
+
`Spec error: graph "dimensions" must be 2 or 3, received ${JSON.stringify(requested)}.`
|
|
15422
|
+
);
|
|
15423
|
+
}
|
|
15424
|
+
if (requested !== 3) return 2;
|
|
15425
|
+
if (spec.nodes.length > MAX_3D_NODES) {
|
|
15426
|
+
onWarn?.(
|
|
15427
|
+
`dimensions: 3 is unavailable for ${spec.nodes.length} nodes (the limit is ${MAX_3D_NODES}); rendering in 2D.`
|
|
15428
|
+
);
|
|
15429
|
+
return 2;
|
|
15430
|
+
}
|
|
15431
|
+
if (spec.interaction?.cursorRepulsion) {
|
|
15432
|
+
onWarn?.("interaction.cursorRepulsion is not supported in 3D; it will be ignored.");
|
|
15433
|
+
}
|
|
15434
|
+
if (spec.interaction?.springyDrag) {
|
|
15435
|
+
onWarn?.("interaction.springyDrag is not supported in 3D; it will be ignored.");
|
|
15436
|
+
}
|
|
15437
|
+
const hasStroke = (o) => o?.stroke !== void 0 || o?.strokeWidth !== void 0;
|
|
15438
|
+
const strokeOverrides = Object.values(spec.nodeOverrides ?? {}).some(hasStroke) || hasStroke(spec.seedNode?.style);
|
|
15439
|
+
if (strokeOverrides) {
|
|
15440
|
+
onWarn?.(
|
|
15441
|
+
"nodeOverrides/seedNode.style stroke and strokeWidth are not supported in 3D; they will be ignored."
|
|
15442
|
+
);
|
|
15443
|
+
}
|
|
15444
|
+
return 3;
|
|
15445
|
+
}
|
|
15294
15446
|
var DEFAULT_COLLISION_PADDING = 5;
|
|
15295
15447
|
|
|
15296
15448
|
// src/layout/dimensions.ts
|
|
@@ -15302,6 +15454,7 @@ import {
|
|
|
15302
15454
|
computeFieldFormatContext as computeFieldFormatContext6,
|
|
15303
15455
|
defaultNumberFormatter as defaultNumberFormatter9,
|
|
15304
15456
|
estimateTextWidth as estimateTextWidth20,
|
|
15457
|
+
getHeightClass as getHeightClass2,
|
|
15305
15458
|
HPAD_COMPACT_FRACTION as HPAD_COMPACT_FRACTION3,
|
|
15306
15459
|
HPAD_COMPACT_MIN as HPAD_COMPACT_MIN3,
|
|
15307
15460
|
isAxislessMark as isAxislessMark2,
|
|
@@ -15414,6 +15567,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
15414
15567
|
chromeMode = "hidden";
|
|
15415
15568
|
}
|
|
15416
15569
|
const bottomLegendReservation = plan ? hasLegendContent(plan.legendContent) && plan.legendContent.position === "bottom" ? plan.legendContent.height + legendGap(width) : 0 : legendLayoutHasContent(legendLayout) && legendLayout.position === "bottom" ? legendLayout.bounds.height + legendGap(width) : 0;
|
|
15570
|
+
const isCrampedHeight = getHeightClass2(height) === "cramped";
|
|
15417
15571
|
const chrome = plan?.chrome ?? computeChrome5(
|
|
15418
15572
|
chromeToInput(spec.chrome),
|
|
15419
15573
|
theme,
|
|
@@ -15422,7 +15576,8 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
15422
15576
|
chromeMode,
|
|
15423
15577
|
padding,
|
|
15424
15578
|
watermark,
|
|
15425
|
-
bottomLegendReservation
|
|
15579
|
+
bottomLegendReservation,
|
|
15580
|
+
isCrampedHeight
|
|
15426
15581
|
);
|
|
15427
15582
|
const chromeLayout = resolveChromeLayout(spec, options);
|
|
15428
15583
|
const effectiveHeight = chromeLayout === "grow" ? height + chrome.topHeight + chrome.bottomHeight : height;
|
|
@@ -15460,7 +15615,8 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
15460
15615
|
margins: margins2,
|
|
15461
15616
|
theme,
|
|
15462
15617
|
xAxisHeight: xAxisSpace,
|
|
15463
|
-
effectiveAxisGap: 0
|
|
15618
|
+
effectiveAxisGap: 0,
|
|
15619
|
+
topPad: padding
|
|
15464
15620
|
};
|
|
15465
15621
|
}
|
|
15466
15622
|
const total = { x: 0, y: 0, width, height: effectiveHeight };
|
|
@@ -15497,9 +15653,9 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
15497
15653
|
const inlineTickOverhang = yIsInlinePre ? theme.fonts.sizes.axisTick + INLINE_TICK_OVERHANG_PAD : 0;
|
|
15498
15654
|
const topAxisGap = isRadial && chrome.topHeight === 0 ? 0 : axisMargin + inlineTickOverhang;
|
|
15499
15655
|
const topPad = width < NARROW_VIEWPORT_MAX3 ? padding + TOP_PAD_EXTRA_NARROW2 : padding;
|
|
15500
|
-
const wantsMetrics = !!spec.metrics && spec.metrics.length > 0 && chromeMode !== "hidden";
|
|
15656
|
+
const wantsMetrics = !!spec.metrics && spec.metrics.length > 0 && chromeMode !== "hidden" && !isCrampedHeight;
|
|
15501
15657
|
const tentativeMetricsHeight = wantsMetrics ? metricBarHeight(metricFonts(theme)) : 0;
|
|
15502
|
-
const wantsSearch = !!spec.seriesSearch && chromeMode !== "hidden";
|
|
15658
|
+
const wantsSearch = !!spec.seriesSearch && chromeMode !== "hidden" && !isCrampedHeight;
|
|
15503
15659
|
const searchReservation = wantsSearch ? SERIES_SEARCH_INPUT_HEIGHT + SERIES_SEARCH_GAP : 0;
|
|
15504
15660
|
const margins = {
|
|
15505
15661
|
top: topPad + chrome.topHeight + tentativeMetricsHeight + searchReservation,
|
|
@@ -15757,6 +15913,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
15757
15913
|
) : void 0,
|
|
15758
15914
|
xAxisHeight,
|
|
15759
15915
|
effectiveAxisGap: fallbackEffectiveAxisGap,
|
|
15916
|
+
topPad,
|
|
15760
15917
|
chromeStripped: true
|
|
15761
15918
|
};
|
|
15762
15919
|
}
|
|
@@ -15781,7 +15938,8 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
15781
15938
|
metrics: resolvedMetrics,
|
|
15782
15939
|
seriesSearch: wantsSearch ? resolveSeriesSearch(spec, metricsTopY + (resolvedMetrics?.height ?? 0), metricsArea) : void 0,
|
|
15783
15940
|
xAxisHeight,
|
|
15784
|
-
effectiveAxisGap
|
|
15941
|
+
effectiveAxisGap,
|
|
15942
|
+
topPad
|
|
15785
15943
|
};
|
|
15786
15944
|
}
|
|
15787
15945
|
function resolveSeriesSearch(spec, y, area3) {
|
|
@@ -17063,40 +17221,73 @@ function inferAlignment(col, type) {
|
|
|
17063
17221
|
if (col.align) return col.align;
|
|
17064
17222
|
return type === "quantitative" ? "right" : "left";
|
|
17065
17223
|
}
|
|
17066
|
-
function
|
|
17224
|
+
function cellPaddingX(density) {
|
|
17225
|
+
if (density === "condensed") return 24;
|
|
17226
|
+
if (density === "relaxed") return 40;
|
|
17227
|
+
return 32;
|
|
17228
|
+
}
|
|
17229
|
+
function estimateColumnWidth(col, data, fontSize, paddingX) {
|
|
17067
17230
|
const MIN_WIDTH = 60;
|
|
17068
|
-
const PADDING = 24;
|
|
17069
17231
|
if (col.sparkline) return 140;
|
|
17070
|
-
if (col.image) return (col.image.width ?? 24) +
|
|
17232
|
+
if (col.image) return (col.image.width ?? 24) + paddingX;
|
|
17071
17233
|
if (col.flag) return 60;
|
|
17072
17234
|
const label = col.label ?? col.key;
|
|
17073
|
-
const headerWidth = estimateTextWidth22(label, fontSize, 600) +
|
|
17235
|
+
const headerWidth = estimateTextWidth22(label, fontSize, 600) + paddingX;
|
|
17074
17236
|
const sampleSize = Math.min(100, data.length);
|
|
17075
17237
|
let maxDataWidth = 0;
|
|
17076
17238
|
for (let i = 0; i < sampleSize; i++) {
|
|
17077
17239
|
const val = data[i][col.key];
|
|
17078
17240
|
const text = val == null ? "" : String(val);
|
|
17079
|
-
const width = estimateTextWidth22(text, fontSize, 400) +
|
|
17241
|
+
const width = estimateTextWidth22(text, fontSize, 400) + paddingX;
|
|
17080
17242
|
if (width > maxDataWidth) maxDataWidth = width;
|
|
17081
17243
|
}
|
|
17082
17244
|
return Math.max(MIN_WIDTH, headerWidth, maxDataWidth);
|
|
17083
17245
|
}
|
|
17084
|
-
|
|
17246
|
+
var MIN_COLUMN_WIDTH = 60;
|
|
17247
|
+
function resolveColumns(columns, data, totalWidth, theme, density, warnings = []) {
|
|
17085
17248
|
const fontSize = theme.fonts.sizes.body;
|
|
17086
|
-
const
|
|
17249
|
+
const paddingX = cellPaddingX(density);
|
|
17250
|
+
const isFixed = columns.map((col) => !!(col.sparkline || col.image || col.flag || col.width));
|
|
17087
17251
|
const naturalWidths = columns.map((col) => {
|
|
17088
17252
|
if (col.width) {
|
|
17089
17253
|
if (col.width.endsWith("px")) {
|
|
17090
17254
|
return parseInt(col.width, 10) || 100;
|
|
17091
17255
|
}
|
|
17092
17256
|
if (col.width.endsWith("%")) {
|
|
17093
|
-
return parseFloat(col.width) / 100 * totalWidth || 100;
|
|
17257
|
+
return Math.round(parseFloat(col.width) / 100 * totalWidth) || 100;
|
|
17094
17258
|
}
|
|
17095
17259
|
return parseInt(col.width, 10) || 100;
|
|
17096
17260
|
}
|
|
17097
|
-
return estimateColumnWidth(col, data, fontSize);
|
|
17261
|
+
return estimateColumnWidth(col, data, fontSize, paddingX);
|
|
17098
17262
|
});
|
|
17099
|
-
const
|
|
17263
|
+
const explicitIndices = columns.map((_, i) => i).filter((i) => isFixed[i] && !!columns[i].width);
|
|
17264
|
+
const visualFixedTotal = columns.reduce(
|
|
17265
|
+
(sum, col, i) => sum + (isFixed[i] && !col.width ? naturalWidths[i] : 0),
|
|
17266
|
+
0
|
|
17267
|
+
);
|
|
17268
|
+
const explicitTotal = explicitIndices.reduce((sum, i) => sum + naturalWidths[i], 0);
|
|
17269
|
+
const budgetForExplicit = totalWidth - visualFixedTotal;
|
|
17270
|
+
const scaledExplicitWidths = /* @__PURE__ */ new Map();
|
|
17271
|
+
if (explicitTotal > 0 && budgetForExplicit > 0 && explicitTotal > budgetForExplicit) {
|
|
17272
|
+
const scale = budgetForExplicit / explicitTotal;
|
|
17273
|
+
let cumulative = 0;
|
|
17274
|
+
let cumulativeTarget = 0;
|
|
17275
|
+
let floored = false;
|
|
17276
|
+
for (const i of explicitIndices) {
|
|
17277
|
+
cumulativeTarget += naturalWidths[i] * scale;
|
|
17278
|
+
const target = Math.round(cumulativeTarget) - cumulative;
|
|
17279
|
+
const width = Math.max(MIN_COLUMN_WIDTH, target);
|
|
17280
|
+
if (width > target) floored = true;
|
|
17281
|
+
scaledExplicitWidths.set(i, width);
|
|
17282
|
+
cumulative += width;
|
|
17283
|
+
}
|
|
17284
|
+
const outcome = floored ? `scaled proportionally down, but at least one column hit the ${MIN_COLUMN_WIDTH}px minimum, so the table will still overflow.` : "scaled proportionally to fit.";
|
|
17285
|
+
warnings.push(
|
|
17286
|
+
`[openchart] TABLE_WIDTH_OVERFLOW: explicit column widths summed to ${explicitTotal}px, exceeding the ${budgetForExplicit}px available in a ${totalWidth}px-wide table; ${outcome}`
|
|
17287
|
+
);
|
|
17288
|
+
}
|
|
17289
|
+
const resolvedFixedWidths = naturalWidths.map((w, i) => scaledExplicitWidths.get(i) ?? w);
|
|
17290
|
+
const fixedTotal = resolvedFixedWidths.reduce((sum, w, i) => sum + (isFixed[i] ? w : 0), 0);
|
|
17100
17291
|
const flexTotal = naturalWidths.reduce((sum, w, i) => sum + (isFixed[i] ? 0 : w), 0);
|
|
17101
17292
|
const remainingWidth = totalWidth - fixedTotal;
|
|
17102
17293
|
const flexScale = flexTotal > 0 && remainingWidth > 0 ? remainingWidth / flexTotal : 1;
|
|
@@ -17105,7 +17296,10 @@ function resolveColumns(columns, data, totalWidth, theme) {
|
|
|
17105
17296
|
return {
|
|
17106
17297
|
key: col.key,
|
|
17107
17298
|
label: col.label ?? col.key,
|
|
17108
|
-
width: Math.max(
|
|
17299
|
+
width: Math.max(
|
|
17300
|
+
MIN_COLUMN_WIDTH,
|
|
17301
|
+
isFixed[i] ? resolvedFixedWidths[i] : Math.round(naturalWidths[i] * flexScale)
|
|
17302
|
+
),
|
|
17109
17303
|
sortable: col.sortable ?? true,
|
|
17110
17304
|
align: inferAlignment(col, type),
|
|
17111
17305
|
type,
|
|
@@ -17228,6 +17422,8 @@ function buildTotalRow(label, columns, resolvedColumns, rows) {
|
|
|
17228
17422
|
style: {},
|
|
17229
17423
|
cellType: "text"
|
|
17230
17424
|
};
|
|
17425
|
+
const col = columns[i];
|
|
17426
|
+
if (col.total === false) return blank;
|
|
17231
17427
|
if (resolved.type !== "quantitative" || !SUMMABLE.has(resolved.cellType)) return blank;
|
|
17232
17428
|
let sum = 0;
|
|
17233
17429
|
let seen = false;
|
|
@@ -17251,7 +17447,17 @@ function buildTotalRow(label, columns, resolvedColumns, rows) {
|
|
|
17251
17447
|
function compileTableLayout(spec, options, theme) {
|
|
17252
17448
|
const data = spec.data;
|
|
17253
17449
|
const darkMode = theme.isDark;
|
|
17254
|
-
const
|
|
17450
|
+
const columnWarnings = [];
|
|
17451
|
+
const resolvedColumns = resolveColumns(
|
|
17452
|
+
spec.columns,
|
|
17453
|
+
data,
|
|
17454
|
+
options.width,
|
|
17455
|
+
theme,
|
|
17456
|
+
// The deprecated `compact` flag renders condensed padding too.
|
|
17457
|
+
spec.density ?? (spec.compact ? "condensed" : void 0),
|
|
17458
|
+
columnWarnings
|
|
17459
|
+
);
|
|
17460
|
+
emitSpecWarnings(columnWarnings, options.onWarn);
|
|
17255
17461
|
const sort = resolveSort(spec, resolvedColumns, options.sort);
|
|
17256
17462
|
const searchIndex = spec.search ? buildSearchIndex(data, spec.columns) : /* @__PURE__ */ new Map();
|
|
17257
17463
|
let currentData = data;
|
|
@@ -17578,8 +17784,8 @@ function compileQuantitative(tilemapSpec, options, theme, chrome, fullArea, isDa
|
|
|
17578
17784
|
const hasEmptyTiles = tiles.some((t) => !t.hasData);
|
|
17579
17785
|
const noDataLabel = "No data";
|
|
17580
17786
|
const noDataLabelWidth = estimateTextWidth23(noDataLabel, 11);
|
|
17581
|
-
const
|
|
17582
|
-
const barWidth = Math.max(0, legendWidth -
|
|
17787
|
+
const noDataBlockWidth2 = hasEmptyTiles ? NO_DATA_BAR_GAP + legendBarHeight + NO_DATA_LABEL_GAP2 + noDataLabelWidth : 0;
|
|
17788
|
+
const barWidth = Math.max(0, legendWidth - noDataBlockWidth2);
|
|
17583
17789
|
const noDataX = legendX + barWidth + NO_DATA_BAR_GAP;
|
|
17584
17790
|
gradientLegend = {
|
|
17585
17791
|
type: "gradient",
|
|
@@ -18991,7 +19197,7 @@ function compileChart(spec, optionsInput) {
|
|
|
18991
19197
|
chartSpec = { ...chartSpec, data: runTransforms(chartSpec.data, rawTransforms) };
|
|
18992
19198
|
}
|
|
18993
19199
|
const breakpoint = getBreakpoint3(options.width);
|
|
18994
|
-
const heightClass =
|
|
19200
|
+
const heightClass = getHeightClass3(options.height);
|
|
18995
19201
|
let strategy = getLayoutStrategy(breakpoint, heightClass);
|
|
18996
19202
|
const economy = resolveChromeEconomy(options.width, options.height);
|
|
18997
19203
|
const rawSpec = expandedSpec;
|
|
@@ -19135,7 +19341,7 @@ function compileChart(spec, optionsInput) {
|
|
|
19135
19341
|
watermark = false;
|
|
19136
19342
|
chartSpec = { ...chartSpec, watermark: false };
|
|
19137
19343
|
}
|
|
19138
|
-
if (heightClass === "cramped" && !chartSpec.userExplicit.watermark) {
|
|
19344
|
+
if ((heightClass === "minimal" || heightClass === "cramped") && !chartSpec.userExplicit.watermark) {
|
|
19139
19345
|
watermark = false;
|
|
19140
19346
|
chartSpec = { ...chartSpec, watermark: false };
|
|
19141
19347
|
}
|
|
@@ -19333,6 +19539,16 @@ function compileChart(spec, optionsInput) {
|
|
|
19333
19539
|
if (endpointLabels.entries.length > 0 && endpointLabels.bounds.width > 0) {
|
|
19334
19540
|
obstacles.push({ ...endpointLabels.bounds, kind: "endpoint-label" });
|
|
19335
19541
|
}
|
|
19542
|
+
const chromeBottomY = dims.topPad + dims.chrome.topHeight;
|
|
19543
|
+
if (dims.chrome.topHeight > 0) {
|
|
19544
|
+
obstacles.push({
|
|
19545
|
+
x: 0,
|
|
19546
|
+
y: 0,
|
|
19547
|
+
width: dims.total.width,
|
|
19548
|
+
height: chromeBottomY,
|
|
19549
|
+
kind: "chrome"
|
|
19550
|
+
});
|
|
19551
|
+
}
|
|
19336
19552
|
const annotationMeasure = options.measureText ? (text, font) => options.measureText(text, font.fontSize, font.fontWeight).width : heuristicMeasure;
|
|
19337
19553
|
let annotations = computeAnnotations(chartSpec, {
|
|
19338
19554
|
scales,
|
|
@@ -19343,7 +19559,10 @@ function compileChart(spec, optionsInput) {
|
|
|
19343
19559
|
svg: { width: dims.total.width, height: dims.total.height },
|
|
19344
19560
|
measure: annotationMeasure,
|
|
19345
19561
|
fontFamily: theme.fonts.family,
|
|
19346
|
-
autoThin: chartSpec.autoThin
|
|
19562
|
+
autoThin: chartSpec.autoThin,
|
|
19563
|
+
// Hand-placed blocks skip every nudge pass, so the clamp is the only lever
|
|
19564
|
+
// that keeps an author's offset from parking a callout on the title.
|
|
19565
|
+
topBound: dims.chrome.topHeight > 0 ? chromeBottomY : void 0
|
|
19347
19566
|
});
|
|
19348
19567
|
if (options.dev) {
|
|
19349
19568
|
emitSpecWarnings(
|
|
@@ -19609,7 +19828,8 @@ function compileFaceted(chartSpec, renderSpec, facetChannel, facetDirection, opt
|
|
|
19609
19828
|
svg: { width: options.width, height: options.height },
|
|
19610
19829
|
measure: annotationMeasure,
|
|
19611
19830
|
fontFamily: theme.fonts.family,
|
|
19612
|
-
autoThin: panelSpecWithDomains.autoThin
|
|
19831
|
+
autoThin: panelSpecWithDomains.autoThin,
|
|
19832
|
+
topBound: dims.chrome.topHeight > 0 ? dims.topPad + dims.chrome.topHeight : void 0
|
|
19613
19833
|
});
|
|
19614
19834
|
if (chartSpec.autoThin && panelAnnotations.length > 1) {
|
|
19615
19835
|
const thinned = thinAnnotations(
|
|
@@ -19785,6 +20005,7 @@ export {
|
|
|
19785
20005
|
DEFAULT_LINE_HEIGHT,
|
|
19786
20006
|
ENTER_DEFAULTS,
|
|
19787
20007
|
EXIT_DEFAULTS,
|
|
20008
|
+
MAX_3D_NODES,
|
|
19788
20009
|
UPDATE_DEFAULTS,
|
|
19789
20010
|
buildArcPath,
|
|
19790
20011
|
buildAreaPath,
|