@opendata-ai/openchart-engine 7.1.2 → 7.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +103 -93
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +3 -3
- package/src/__tests__/dimensions.test.ts +108 -0
- package/src/charts/bar/__tests__/labels.test.ts +72 -0
- package/src/charts/bar/index.ts +2 -1
- package/src/charts/bar/labels.ts +4 -3
- package/src/layout/axes/ticks.ts +8 -47
- package/src/layout/axes.ts +79 -41
- package/src/layout/dimensions.ts +13 -7
- package/src/layout/scales.ts +22 -4
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
adaptTheme as adaptTheme5,
|
|
4
4
|
computeLabelBounds,
|
|
5
|
-
estimateTextWidth as
|
|
5
|
+
estimateTextWidth as estimateTextWidth20,
|
|
6
6
|
generateAltText,
|
|
7
7
|
generateDataTable,
|
|
8
8
|
getBreakpoint,
|
|
@@ -1778,7 +1778,7 @@ var LABEL_FONT_SIZE = 11;
|
|
|
1778
1778
|
var LABEL_FONT_WEIGHT = 600;
|
|
1779
1779
|
var LABEL_PADDING = 6;
|
|
1780
1780
|
var MIN_WIDTH_FOR_INSIDE_LABEL = 40;
|
|
1781
|
-
function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField, labelColor) {
|
|
1781
|
+
function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labelPrefix, valueField, labelColor, darkMode = false) {
|
|
1782
1782
|
const targetMarks = filterByDensity(marks, density);
|
|
1783
1783
|
const candidates = [];
|
|
1784
1784
|
const fitsInSegment = [];
|
|
@@ -1815,16 +1815,16 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
|
|
|
1815
1815
|
let textAnchor;
|
|
1816
1816
|
if (isStacked2 && isInside) {
|
|
1817
1817
|
anchorX = mark.x + mark.width / 2;
|
|
1818
|
-
fill = pickLabelColor(bgColor);
|
|
1818
|
+
fill = pickLabelColor(bgColor, darkMode);
|
|
1819
1819
|
textAnchor = "middle";
|
|
1820
1820
|
} else if (isInside) {
|
|
1821
1821
|
if (isNegative) {
|
|
1822
1822
|
anchorX = mark.x + LABEL_PADDING;
|
|
1823
|
-
fill = pickLabelColor(bgColor);
|
|
1823
|
+
fill = pickLabelColor(bgColor, darkMode);
|
|
1824
1824
|
textAnchor = "start";
|
|
1825
1825
|
} else {
|
|
1826
1826
|
anchorX = mark.x + mark.width - LABEL_PADDING;
|
|
1827
|
-
fill = pickLabelColor(bgColor);
|
|
1827
|
+
fill = pickLabelColor(bgColor, darkMode);
|
|
1828
1828
|
textAnchor = "end";
|
|
1829
1829
|
}
|
|
1830
1830
|
} else {
|
|
@@ -1898,7 +1898,7 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
|
|
|
1898
1898
|
}
|
|
1899
1899
|
|
|
1900
1900
|
// src/charts/bar/index.ts
|
|
1901
|
-
var barRenderer = (spec, scales, chartArea, strategy,
|
|
1901
|
+
var barRenderer = (spec, scales, chartArea, strategy, theme) => {
|
|
1902
1902
|
const marks = computeBarMarks(spec, scales, chartArea, strategy);
|
|
1903
1903
|
const valueField = spec.encoding?.x && "field" in spec.encoding.x ? spec.encoding.x.field : void 0;
|
|
1904
1904
|
const labels = computeBarLabels(
|
|
@@ -1908,7 +1908,8 @@ var barRenderer = (spec, scales, chartArea, strategy, _theme) => {
|
|
|
1908
1908
|
spec.labels.format,
|
|
1909
1909
|
spec.labels.prefix,
|
|
1910
1910
|
valueField,
|
|
1911
|
-
spec.labels.color
|
|
1911
|
+
spec.labels.color,
|
|
1912
|
+
theme.isDark
|
|
1912
1913
|
);
|
|
1913
1914
|
for (let i = 0; i < marks.length && i < labels.length; i++) {
|
|
1914
1915
|
marks[i].label = labels[i];
|
|
@@ -9709,7 +9710,6 @@ import {
|
|
|
9709
9710
|
abbreviateNumber as abbreviateNumber2,
|
|
9710
9711
|
buildD3Formatter as buildD3Formatter5,
|
|
9711
9712
|
buildTemporalFormatter,
|
|
9712
|
-
estimateTextWidth as estimateTextWidth12,
|
|
9713
9713
|
formatDate,
|
|
9714
9714
|
formatNumber as formatNumber3
|
|
9715
9715
|
} from "@opendata-ai/openchart-core";
|
|
@@ -9819,35 +9819,16 @@ function scaleSupportsTickCount(resolvedScale) {
|
|
|
9819
9819
|
const scale = resolvedScale.scale;
|
|
9820
9820
|
return "ticks" in scale && typeof scale.ticks === "function";
|
|
9821
9821
|
}
|
|
9822
|
-
function categoricalTicks(resolvedScale, density, orientation = "horizontal",
|
|
9822
|
+
function categoricalTicks(resolvedScale, density, orientation = "horizontal", subtitleContext) {
|
|
9823
9823
|
const scale = resolvedScale.scale;
|
|
9824
9824
|
const domain = scale.domain();
|
|
9825
9825
|
const catAxisCfg = resolvedScale.channel.axis || void 0;
|
|
9826
9826
|
const explicitTickCount = catAxisCfg?.tickCount;
|
|
9827
9827
|
let selectedValues = domain;
|
|
9828
9828
|
if (resolvedScale.type === "band" && orientation === "horizontal") {
|
|
9829
|
-
if (
|
|
9830
|
-
const
|
|
9831
|
-
|
|
9832
|
-
return Math.max(max4, w);
|
|
9833
|
-
}, 0);
|
|
9834
|
-
const angleRad = labelAngle !== void 0 ? Math.abs(labelAngle) * Math.PI / 180 : 0;
|
|
9835
|
-
const footprint = angleRad > 0 ? maxLabelWidth * Math.abs(Math.cos(angleRad)) : maxLabelWidth;
|
|
9836
|
-
const minGap = fontSize * 0.5;
|
|
9837
|
-
if (footprint + minGap > bandwidth) {
|
|
9838
|
-
const maxFitting = Math.max(1, Math.floor(bandwidth / (footprint + minGap)));
|
|
9839
|
-
const cap = explicitTickCount ?? Math.min(domain.length, Math.max(maxFitting, TICK_COUNTS[density]));
|
|
9840
|
-
if (domain.length > cap) {
|
|
9841
|
-
const step = Math.ceil(domain.length / cap);
|
|
9842
|
-
selectedValues = domain.filter((_, i) => i % step === 0);
|
|
9843
|
-
}
|
|
9844
|
-
}
|
|
9845
|
-
} else {
|
|
9846
|
-
const maxTicks = explicitTickCount ?? TICK_COUNTS[density];
|
|
9847
|
-
if ((explicitTickCount || density !== "full") && domain.length > maxTicks) {
|
|
9848
|
-
const step = Math.ceil(domain.length / maxTicks);
|
|
9849
|
-
selectedValues = domain.filter((_, i) => i % step === 0);
|
|
9850
|
-
}
|
|
9829
|
+
if (explicitTickCount && domain.length > explicitTickCount) {
|
|
9830
|
+
const step = Math.ceil(domain.length / explicitTickCount);
|
|
9831
|
+
selectedValues = domain.filter((_, i) => i % step === 0);
|
|
9851
9832
|
}
|
|
9852
9833
|
} else if (resolvedScale.type !== "band") {
|
|
9853
9834
|
const maxTicks = explicitTickCount ?? TICK_COUNTS[density];
|
|
@@ -9958,6 +9939,34 @@ function fitContinuousTicks(scale, initialTicks, initialCount, fontSize, fontWei
|
|
|
9958
9939
|
const fallback = bestWithinFloor ?? buildContinuousTicks(scale, floor);
|
|
9959
9940
|
return thinTicksUntilFit(fallback, fontSize, fontWeight, measureText, orientation);
|
|
9960
9941
|
}
|
|
9942
|
+
function bandTicksOverlapAtAngle(ticks2, angleDeg, fontSize, fontWeight, measureText) {
|
|
9943
|
+
if (ticks2.length < 2) return false;
|
|
9944
|
+
const angleRad = Math.abs(angleDeg) * Math.PI / 180;
|
|
9945
|
+
const cosA = angleRad > 0 ? Math.abs(Math.cos(angleRad)) : 1;
|
|
9946
|
+
const minGap = fontSize * 0.5;
|
|
9947
|
+
for (let i = 0; i < ticks2.length - 1; i++) {
|
|
9948
|
+
const aWidth = measureLabel(ticks2[i].label, fontSize, fontWeight, measureText) * cosA;
|
|
9949
|
+
const bWidth = measureLabel(ticks2[i + 1].label, fontSize, fontWeight, measureText) * cosA;
|
|
9950
|
+
const aRight = ticks2[i].position + aWidth / 2;
|
|
9951
|
+
const bLeft = ticks2[i + 1].position - bWidth / 2;
|
|
9952
|
+
if (aRight + minGap > bLeft) return true;
|
|
9953
|
+
}
|
|
9954
|
+
return false;
|
|
9955
|
+
}
|
|
9956
|
+
function thinBandTicksIfNeeded(ticks2, angleDeg, fontSize, fontWeight, measureText) {
|
|
9957
|
+
if (!bandTicksOverlapAtAngle(ticks2, angleDeg, fontSize, fontWeight, measureText)) return ticks2;
|
|
9958
|
+
let current = ticks2;
|
|
9959
|
+
while (current.length > 2) {
|
|
9960
|
+
const thinned = [current[0]];
|
|
9961
|
+
for (let i = 2; i < current.length - 1; i += 2) {
|
|
9962
|
+
thinned.push(current[i]);
|
|
9963
|
+
}
|
|
9964
|
+
if (current.length > 1) thinned.push(current[current.length - 1]);
|
|
9965
|
+
current = thinned;
|
|
9966
|
+
if (!bandTicksOverlapAtAngle(current, angleDeg, fontSize, fontWeight, measureText)) break;
|
|
9967
|
+
}
|
|
9968
|
+
return current;
|
|
9969
|
+
}
|
|
9961
9970
|
function computeAxes(scales, chartArea, strategy, theme, measureText, dataContext) {
|
|
9962
9971
|
const result = {};
|
|
9963
9972
|
const baseDensity = strategy.axisLabelDensity;
|
|
@@ -9998,17 +10007,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
|
|
|
9998
10007
|
if (axisConfig?.values) {
|
|
9999
10008
|
allTicks = resolveExplicitTicks(axisConfig.values, scales.x);
|
|
10000
10009
|
} else if (!isContinuousX) {
|
|
10001
|
-
|
|
10002
|
-
allTicks = categoricalTicks(
|
|
10003
|
-
scales.x,
|
|
10004
|
-
xDensity,
|
|
10005
|
-
"horizontal",
|
|
10006
|
-
xBandwidth,
|
|
10007
|
-
axisConfig?.labelAngle,
|
|
10008
|
-
fontSize,
|
|
10009
|
-
fontWeight,
|
|
10010
|
-
measureText
|
|
10011
|
-
);
|
|
10010
|
+
allTicks = categoricalTicks(scales.x, xDensity, "horizontal");
|
|
10012
10011
|
} else {
|
|
10013
10012
|
allTicks = continuousTicks(scales.x, xDensity, xTargetCount);
|
|
10014
10013
|
}
|
|
@@ -10016,11 +10015,25 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
|
|
|
10016
10015
|
position: t.position,
|
|
10017
10016
|
major: true
|
|
10018
10017
|
}));
|
|
10018
|
+
let tickAngle = axisConfig?.labelAngle;
|
|
10019
|
+
if (tickAngle === void 0 && scales.x.type === "band" && allTicks.length > 1) {
|
|
10020
|
+
const bandwidth = scales.x.scale.bandwidth();
|
|
10021
|
+
let maxLabelWidth = 0;
|
|
10022
|
+
for (const t of allTicks) {
|
|
10023
|
+
const w = measureLabel(t.label, fontSize, fontWeight, measureText);
|
|
10024
|
+
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
10025
|
+
}
|
|
10026
|
+
if (maxLabelWidth > bandwidth * 0.85) {
|
|
10027
|
+
tickAngle = -45;
|
|
10028
|
+
}
|
|
10029
|
+
}
|
|
10019
10030
|
const hasExplicitValues = !!axisConfig?.values;
|
|
10020
|
-
const shouldThin = scales.x.type !== "band" && !hasExplicitValues;
|
|
10021
10031
|
let ticks2;
|
|
10022
|
-
if (
|
|
10032
|
+
if (hasExplicitValues) {
|
|
10023
10033
|
ticks2 = allTicks;
|
|
10034
|
+
} else if (scales.x.type === "band") {
|
|
10035
|
+
const effectiveAngle = tickAngle ?? 0;
|
|
10036
|
+
ticks2 = thinBandTicksIfNeeded(allTicks, effectiveAngle, fontSize, fontWeight, measureText);
|
|
10024
10037
|
} else if (isContinuousX) {
|
|
10025
10038
|
ticks2 = fitContinuousTicks(
|
|
10026
10039
|
scales.x,
|
|
@@ -10035,18 +10048,6 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
|
|
|
10035
10048
|
} else {
|
|
10036
10049
|
ticks2 = thinTicksUntilFit(allTicks, fontSize, fontWeight, measureText);
|
|
10037
10050
|
}
|
|
10038
|
-
let tickAngle = axisConfig?.labelAngle;
|
|
10039
|
-
if (tickAngle === void 0 && scales.x.type === "band" && ticks2.length > 1) {
|
|
10040
|
-
const bandwidth = scales.x.scale.bandwidth();
|
|
10041
|
-
let maxLabelWidth = 0;
|
|
10042
|
-
for (const t of ticks2) {
|
|
10043
|
-
const w = measureLabel(t.label, fontSize, fontWeight, measureText);
|
|
10044
|
-
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
10045
|
-
}
|
|
10046
|
-
if (maxLabelWidth > bandwidth * 0.85) {
|
|
10047
|
-
tickAngle = -45;
|
|
10048
|
-
}
|
|
10049
|
-
}
|
|
10050
10051
|
const axisTitle = axisConfig?.title;
|
|
10051
10052
|
const xLabelColor = axisConfig?.labelColor;
|
|
10052
10053
|
const xTickPosition = axisConfig?.tickPosition ?? "gutter";
|
|
@@ -10084,11 +10085,6 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
|
|
|
10084
10085
|
scales.y,
|
|
10085
10086
|
yDensity,
|
|
10086
10087
|
"vertical",
|
|
10087
|
-
void 0,
|
|
10088
|
-
void 0,
|
|
10089
|
-
void 0,
|
|
10090
|
-
void 0,
|
|
10091
|
-
void 0,
|
|
10092
10088
|
yFieldName && yLabelField && dataContext ? { data: dataContext.data, fieldName: yFieldName, labelField: yLabelField } : void 0
|
|
10093
10089
|
);
|
|
10094
10090
|
} else {
|
|
@@ -10150,10 +10146,11 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
|
|
|
10150
10146
|
|
|
10151
10147
|
// src/layout/dimensions.ts
|
|
10152
10148
|
import {
|
|
10149
|
+
AXIS_TITLE_GAP,
|
|
10153
10150
|
AXIS_TITLE_TRAILING_PAD as AXIS_TITLE_TRAILING_PAD2,
|
|
10154
10151
|
BREAKPOINT_COMPACT_MAX as BREAKPOINT_COMPACT_MAX2,
|
|
10155
10152
|
computeChrome as computeChrome3,
|
|
10156
|
-
estimateTextWidth as
|
|
10153
|
+
estimateTextWidth as estimateTextWidth15,
|
|
10157
10154
|
getAxisTitleOffset as getAxisTitleOffset2,
|
|
10158
10155
|
HPAD_COMPACT_FRACTION,
|
|
10159
10156
|
HPAD_COMPACT_MIN,
|
|
@@ -10169,7 +10166,7 @@ import {
|
|
|
10169
10166
|
} from "@opendata-ai/openchart-core";
|
|
10170
10167
|
|
|
10171
10168
|
// src/endpoint-labels/predict.ts
|
|
10172
|
-
import { estimateTextWidth as
|
|
10169
|
+
import { estimateTextWidth as estimateTextWidth12, wrapText as wrapText2 } from "@opendata-ai/openchart-core";
|
|
10173
10170
|
function predictEndpointLabelsWidth(spec, _theme) {
|
|
10174
10171
|
if (spec.endpointLabels === false) return 0;
|
|
10175
10172
|
if (spec.markType !== "line" && spec.markType !== "area") return 0;
|
|
@@ -10191,7 +10188,7 @@ function predictEndpointLabelsWidth(spec, _theme) {
|
|
|
10191
10188
|
for (const name of seriesNames) {
|
|
10192
10189
|
const lines = wrapText2(name, ENDPOINT_LABEL_FONT_SIZE, ENDPOINT_LABEL_FONT_WEIGHT, wrapWidth);
|
|
10193
10190
|
for (const line of lines) {
|
|
10194
|
-
const w =
|
|
10191
|
+
const w = estimateTextWidth12(line, ENDPOINT_LABEL_FONT_SIZE, ENDPOINT_LABEL_FONT_WEIGHT);
|
|
10195
10192
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
10196
10193
|
}
|
|
10197
10194
|
}
|
|
@@ -10211,14 +10208,14 @@ function predictEndpointLabelsWidth(spec, _theme) {
|
|
|
10211
10208
|
else if (maxAbs >= 1e6) sample = "1.5M";
|
|
10212
10209
|
else if (maxAbs >= 1e3) sample = "1.5K";
|
|
10213
10210
|
else sample = String(Math.round(maxAbs * 100) / 100);
|
|
10214
|
-
valueWidth =
|
|
10211
|
+
valueWidth = estimateTextWidth12(sample, ENDPOINT_VALUE_FONT_SIZE, ENDPOINT_VALUE_FONT_WEIGHT);
|
|
10215
10212
|
}
|
|
10216
10213
|
const textColumn = Math.max(maxLabelWidth, valueWidth);
|
|
10217
10214
|
return ENDPOINT_SWATCH_SIZE + ENDPOINT_GAP + textColumn + 4;
|
|
10218
10215
|
}
|
|
10219
10216
|
|
|
10220
10217
|
// src/legend/wrap.ts
|
|
10221
|
-
import { COMPACT_WIDTH, estimateTextWidth as
|
|
10218
|
+
import { COMPACT_WIDTH, estimateTextWidth as estimateTextWidth13 } from "@opendata-ai/openchart-core";
|
|
10222
10219
|
var SWATCH_SIZE2 = 12;
|
|
10223
10220
|
var SWATCH_GAP2 = 6;
|
|
10224
10221
|
var ENTRY_GAP2 = 16;
|
|
@@ -10237,7 +10234,7 @@ function measureLegendWrap(entries, maxWidth, labelStyle, maxRows, entryGap = EN
|
|
|
10237
10234
|
let fittingCount = entries.length;
|
|
10238
10235
|
let fittingCountLocked = false;
|
|
10239
10236
|
for (let i = 0; i < entries.length; i++) {
|
|
10240
|
-
const labelWidth =
|
|
10237
|
+
const labelWidth = estimateTextWidth13(
|
|
10241
10238
|
entries[i].label,
|
|
10242
10239
|
labelStyle.fontSize,
|
|
10243
10240
|
labelStyle.fontWeight
|
|
@@ -10260,7 +10257,7 @@ function measureLegendWrap(entries, maxWidth, labelStyle, maxRows, entryGap = EN
|
|
|
10260
10257
|
}
|
|
10261
10258
|
|
|
10262
10259
|
// src/layout/metrics.ts
|
|
10263
|
-
import { estimateTextWidth as
|
|
10260
|
+
import { estimateTextWidth as estimateTextWidth14 } from "@opendata-ai/openchart-core";
|
|
10264
10261
|
var LABEL_FONT_SIZE7 = 10;
|
|
10265
10262
|
var VALUE_FONT_SIZE2 = 22;
|
|
10266
10263
|
var LABEL_LINE_HEIGHT_RATIO = 1.4;
|
|
@@ -10289,7 +10286,7 @@ function computeMetricBar(metrics, metricsTopY, metricsArea, remainingChartHeigh
|
|
|
10289
10286
|
const cellWidth = metricsArea.width / metrics.length;
|
|
10290
10287
|
for (const metric of metrics) {
|
|
10291
10288
|
const text = valueRunText(metric);
|
|
10292
|
-
const measured = measureText ? measureText(text, VALUE_FONT_SIZE2, 510).width :
|
|
10289
|
+
const measured = measureText ? measureText(text, VALUE_FONT_SIZE2, 510).width : estimateTextWidth14(text, VALUE_FONT_SIZE2, 510);
|
|
10293
10290
|
if (measured > cellWidth - CELL_INNER_PAD) return void 0;
|
|
10294
10291
|
}
|
|
10295
10292
|
const labelLine = LABEL_FONT_SIZE7 * LABEL_LINE_HEIGHT_RATIO;
|
|
@@ -10421,7 +10418,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10421
10418
|
if (xField) {
|
|
10422
10419
|
for (const row of spec.data) {
|
|
10423
10420
|
const label = String(row[xField] ?? "");
|
|
10424
|
-
const w =
|
|
10421
|
+
const w = estimateTextWidth15(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
|
|
10425
10422
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
10426
10423
|
}
|
|
10427
10424
|
}
|
|
@@ -10473,7 +10470,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10473
10470
|
const label = String(row[colorField] ?? "");
|
|
10474
10471
|
if (!seen.has(label)) {
|
|
10475
10472
|
seen.add(label);
|
|
10476
|
-
const w =
|
|
10473
|
+
const w = estimateTextWidth15(label, 11, 600);
|
|
10477
10474
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
10478
10475
|
}
|
|
10479
10476
|
}
|
|
@@ -10493,7 +10490,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10493
10490
|
const maxXStr = String(maxX);
|
|
10494
10491
|
for (const ann of spec.annotations) {
|
|
10495
10492
|
if (ann.type === "text" && String(ann.x) === maxXStr) {
|
|
10496
|
-
const textWidth =
|
|
10493
|
+
const textWidth = estimateTextWidth15(ann.text, ann.fontSize ?? 11, ann.fontWeight ?? 600);
|
|
10497
10494
|
const dx = ann.offset?.dx ?? 0;
|
|
10498
10495
|
const anchor = ann.anchor ?? "auto";
|
|
10499
10496
|
const baseRightExtent = anchor === "left" ? textWidth : (
|
|
@@ -10524,12 +10521,12 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10524
10521
|
let maxLabelWidth = 0;
|
|
10525
10522
|
for (const row of spec.data) {
|
|
10526
10523
|
const label = String(row[yField] ?? "");
|
|
10527
|
-
let w =
|
|
10524
|
+
let w = estimateTextWidth15(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
|
|
10528
10525
|
if (yLabelField) {
|
|
10529
10526
|
const subtitle = String(row[yLabelField] ?? "");
|
|
10530
10527
|
if (subtitle) {
|
|
10531
10528
|
const gap = theme.fonts.sizes.axisTick * 0.6;
|
|
10532
|
-
const subtitleWidth =
|
|
10529
|
+
const subtitleWidth = estimateTextWidth15(
|
|
10533
10530
|
subtitle,
|
|
10534
10531
|
theme.fonts.sizes.axisTick,
|
|
10535
10532
|
theme.fonts.weights.normal
|
|
@@ -10572,7 +10569,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10572
10569
|
}
|
|
10573
10570
|
const negPrefix = spec.data.some((r) => Number(r[yField]) < 0) ? "-" : "";
|
|
10574
10571
|
const labelEst = negPrefix + sampleLabel;
|
|
10575
|
-
const labelWidth =
|
|
10572
|
+
const labelWidth = estimateTextWidth15(
|
|
10576
10573
|
labelEst,
|
|
10577
10574
|
theme.fonts.sizes.axisTick,
|
|
10578
10575
|
theme.fonts.weights.normal
|
|
@@ -10608,13 +10605,12 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10608
10605
|
else sampleLabelForTitle = "0.0";
|
|
10609
10606
|
}
|
|
10610
10607
|
const negPrefixForTitle = spec.data.some((r) => Number(r[yFieldForTitle]) < 0) ? "-" : "";
|
|
10611
|
-
estTickLabelWidth =
|
|
10608
|
+
estTickLabelWidth = estimateTextWidth15(
|
|
10612
10609
|
negPrefixForTitle + sampleLabelForTitle,
|
|
10613
10610
|
theme.fonts.sizes.axisTick,
|
|
10614
10611
|
theme.fonts.weights.normal
|
|
10615
10612
|
);
|
|
10616
10613
|
}
|
|
10617
|
-
const AXIS_TITLE_GAP = 8;
|
|
10618
10614
|
const dynamicTitleOffset = TICK_LABEL_OFFSET2 + estTickLabelWidth + AXIS_TITLE_GAP;
|
|
10619
10615
|
const axisTitleOffset = Math.max(dynamicTitleOffset, getAxisTitleOffset2(width));
|
|
10620
10616
|
const halfGlyph = Math.ceil(theme.fonts.sizes.body / 2);
|
|
@@ -10624,6 +10620,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10624
10620
|
if (options.rightAxisReserve && options.rightAxisReserve > 0) {
|
|
10625
10621
|
margins.right = Math.max(margins.right, hPad + options.rightAxisReserve);
|
|
10626
10622
|
}
|
|
10623
|
+
const hasTopLegend = "entries" in legendLayout && legendLayout.entries.length > 0 && legendLayout.position === "top";
|
|
10627
10624
|
if ("entries" in legendLayout && legendLayout.entries.length > 0) {
|
|
10628
10625
|
const gap = legendGap(width);
|
|
10629
10626
|
if (legendLayout.position === "right" || legendLayout.position === "bottom-right") {
|
|
@@ -10632,7 +10629,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10632
10629
|
margins.top += legendLayout.bounds.height + gap;
|
|
10633
10630
|
}
|
|
10634
10631
|
}
|
|
10635
|
-
margins.top += topAxisGap;
|
|
10632
|
+
margins.top += hasTopLegend ? inlineTickOverhang : topAxisGap;
|
|
10636
10633
|
let chartArea = {
|
|
10637
10634
|
x: margins.left,
|
|
10638
10635
|
y: margins.top,
|
|
@@ -10653,13 +10650,14 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
10653
10650
|
bottomLegendReservation
|
|
10654
10651
|
);
|
|
10655
10652
|
const fallbackTopAxisGap = isRadial && fallbackChrome.topHeight === 0 ? 0 : axisMargin + inlineTickOverhang;
|
|
10653
|
+
const fallbackEffectiveAxisGap = hasTopLegend ? inlineTickOverhang : fallbackTopAxisGap;
|
|
10656
10654
|
const newTop = topPad + fallbackChrome.topHeight + tentativeMetricsHeight;
|
|
10657
10655
|
const topDelta = margins.top - newTop;
|
|
10658
10656
|
const newBottom = bottomMargin(fallbackChrome.bottomHeight, padding, xAxisHeight);
|
|
10659
10657
|
const bottomDelta = margins.bottom - newBottom;
|
|
10660
10658
|
if (topDelta > 0 || bottomDelta > 0) {
|
|
10661
10659
|
const gap = legendGap(width);
|
|
10662
|
-
margins.top = newTop + ("entries" in legendLayout && legendLayout.entries.length > 0 && legendLayout.position === "top" ? legendLayout.bounds.height + gap : 0) +
|
|
10660
|
+
margins.top = newTop + ("entries" in legendLayout && legendLayout.entries.length > 0 && legendLayout.position === "top" ? legendLayout.bounds.height + gap : 0) + fallbackEffectiveAxisGap;
|
|
10663
10661
|
margins.bottom = newBottom;
|
|
10664
10662
|
chartArea = {
|
|
10665
10663
|
x: margins.left,
|
|
@@ -11064,7 +11062,13 @@ function computeScales(spec, chartArea, data) {
|
|
|
11064
11062
|
const xStackEnabled = encoding.x.stack === true || encoding.x.stack === "zero" || encoding.x.stack === "normalize" || encoding.x.stack === "center";
|
|
11065
11063
|
if (spec.markType === "bar" && encoding.color && encoding.x.type === "quantitative" && xStackEnabled) {
|
|
11066
11064
|
if (encoding.x.stack === "normalize") {
|
|
11067
|
-
|
|
11065
|
+
const existingAxis = encoding.x.axis;
|
|
11066
|
+
const axis = existingAxis === false || existingAxis?.format ? existingAxis : { ...typeof existingAxis === "object" ? existingAxis : {}, format: ".0%" };
|
|
11067
|
+
xChannel = {
|
|
11068
|
+
...encoding.x,
|
|
11069
|
+
scale: { ...encoding.x.scale, domain: [0, 1], nice: false },
|
|
11070
|
+
axis
|
|
11071
|
+
};
|
|
11068
11072
|
} else if (encoding.x.stack === "center") {
|
|
11069
11073
|
const yField = encoding.y?.field;
|
|
11070
11074
|
const xField = encoding.x.field;
|
|
@@ -11131,7 +11135,13 @@ function computeScales(spec, chartArea, data) {
|
|
|
11131
11135
|
}
|
|
11132
11136
|
if ((isBarStacked || isAreaStacked) && encoding.color && encoding.y.type === "quantitative") {
|
|
11133
11137
|
if (encoding.y.stack === "normalize") {
|
|
11134
|
-
|
|
11138
|
+
const existingAxis = encoding.y.axis;
|
|
11139
|
+
const axis = existingAxis === false || existingAxis?.format ? existingAxis : { ...typeof existingAxis === "object" ? existingAxis : {}, format: ".0%" };
|
|
11140
|
+
yChannel = {
|
|
11141
|
+
...encoding.y,
|
|
11142
|
+
scale: { ...encoding.y.scale, domain: [0, 1], nice: false },
|
|
11143
|
+
axis
|
|
11144
|
+
};
|
|
11135
11145
|
} else if (encoding.y.stack === "center") {
|
|
11136
11146
|
const xField = encoding.x?.field;
|
|
11137
11147
|
const yField = encoding.y.field;
|
|
@@ -11202,7 +11212,7 @@ function computeScales(spec, chartArea, data) {
|
|
|
11202
11212
|
}
|
|
11203
11213
|
|
|
11204
11214
|
// src/legend/compute.ts
|
|
11205
|
-
import { BRAND_RESERVE_WIDTH as BRAND_RESERVE_WIDTH2, COMPACT_WIDTH as COMPACT_WIDTH2, estimateTextWidth as
|
|
11215
|
+
import { BRAND_RESERVE_WIDTH as BRAND_RESERVE_WIDTH2, COMPACT_WIDTH as COMPACT_WIDTH2, estimateTextWidth as estimateTextWidth16 } from "@opendata-ai/openchart-core";
|
|
11206
11216
|
var LEGEND_PADDING = 8;
|
|
11207
11217
|
var LEGEND_RIGHT_WIDTH = 120;
|
|
11208
11218
|
var RIGHT_LEGEND_MAX_HEIGHT_RATIO = 0.4;
|
|
@@ -11318,7 +11328,7 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
|
|
|
11318
11328
|
}
|
|
11319
11329
|
if (resolvedPosition === "right" || resolvedPosition === "bottom-right") {
|
|
11320
11330
|
const maxLabelWidth = Math.max(
|
|
11321
|
-
...entries.map((e) =>
|
|
11331
|
+
...entries.map((e) => estimateTextWidth16(e.label, labelStyle.fontSize, labelStyle.fontWeight))
|
|
11322
11332
|
);
|
|
11323
11333
|
const legendWidth = Math.min(
|
|
11324
11334
|
LEGEND_RIGHT_WIDTH,
|
|
@@ -11381,7 +11391,7 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
|
|
|
11381
11391
|
entries = truncateEntries(entries, fittingCount);
|
|
11382
11392
|
}
|
|
11383
11393
|
const totalWidth = entries.reduce((sum2, entry) => {
|
|
11384
|
-
const labelWidth =
|
|
11394
|
+
const labelWidth = estimateTextWidth16(entry.label, labelStyle.fontSize, labelStyle.fontWeight);
|
|
11385
11395
|
return sum2 + SWATCH_SIZE2 + SWATCH_GAP2 + labelWidth + effectiveEntryGap;
|
|
11386
11396
|
}, 0);
|
|
11387
11397
|
const { rowCount } = measureLegendWrap(
|
|
@@ -11418,7 +11428,7 @@ import {
|
|
|
11418
11428
|
adaptTheme as adaptTheme3,
|
|
11419
11429
|
buildD3Formatter as buildD3Formatter6,
|
|
11420
11430
|
computeChrome as computeChrome4,
|
|
11421
|
-
estimateTextWidth as
|
|
11431
|
+
estimateTextWidth as estimateTextWidth17,
|
|
11422
11432
|
formatNumber as formatNumber4,
|
|
11423
11433
|
resolveTheme as resolveTheme4
|
|
11424
11434
|
} from "@opendata-ai/openchart-core";
|
|
@@ -12099,7 +12109,7 @@ function compileSankey(spec, options) {
|
|
|
12099
12109
|
if (labelsLeft) continue;
|
|
12100
12110
|
const labelX = (node.x1 ?? nodeWidth) + LABEL_GAP;
|
|
12101
12111
|
const labelText = node.label ?? node.id;
|
|
12102
|
-
const labelWidth =
|
|
12112
|
+
const labelWidth = estimateTextWidth17(labelText, labelFontSize, labelFontWeight);
|
|
12103
12113
|
const overflow = labelX + labelWidth - rightEdge;
|
|
12104
12114
|
if (overflow > maxOverflow) maxOverflow = overflow;
|
|
12105
12115
|
}
|
|
@@ -12363,7 +12373,7 @@ function emptyLayout3(area, chrome, theme, options, watermark) {
|
|
|
12363
12373
|
}
|
|
12364
12374
|
|
|
12365
12375
|
// src/tables/compile-table.ts
|
|
12366
|
-
import { computeChrome as computeChrome5, estimateTextWidth as
|
|
12376
|
+
import { computeChrome as computeChrome5, estimateTextWidth as estimateTextWidth18 } from "@opendata-ai/openchart-core";
|
|
12367
12377
|
|
|
12368
12378
|
// src/tables/bar-column.ts
|
|
12369
12379
|
var NEGATIVE_BAR_COLOR = "#c44e52";
|
|
@@ -12778,13 +12788,13 @@ function estimateColumnWidth(col, data, fontSize) {
|
|
|
12778
12788
|
if (col.image) return (col.image.width ?? 24) + PADDING;
|
|
12779
12789
|
if (col.flag) return 60;
|
|
12780
12790
|
const label = col.label ?? col.key;
|
|
12781
|
-
const headerWidth =
|
|
12791
|
+
const headerWidth = estimateTextWidth18(label, fontSize, 600) + PADDING;
|
|
12782
12792
|
const sampleSize = Math.min(100, data.length);
|
|
12783
12793
|
let maxDataWidth = 0;
|
|
12784
12794
|
for (let i = 0; i < sampleSize; i++) {
|
|
12785
12795
|
const val = data[i][col.key];
|
|
12786
12796
|
const text = val == null ? "" : String(val);
|
|
12787
|
-
const width =
|
|
12797
|
+
const width = estimateTextWidth18(text, fontSize, 400) + PADDING;
|
|
12788
12798
|
if (width > maxDataWidth) maxDataWidth = width;
|
|
12789
12799
|
}
|
|
12790
12800
|
return Math.max(MIN_WIDTH, headerWidth, maxDataWidth);
|
|
@@ -13013,7 +13023,7 @@ import {
|
|
|
13013
13023
|
adaptTheme as adaptTheme4,
|
|
13014
13024
|
buildD3Formatter as buildD3Formatter8,
|
|
13015
13025
|
computeChrome as computeChrome6,
|
|
13016
|
-
estimateTextWidth as
|
|
13026
|
+
estimateTextWidth as estimateTextWidth19,
|
|
13017
13027
|
formatNumber as formatNumber6,
|
|
13018
13028
|
resolveTheme as resolveTheme5,
|
|
13019
13029
|
SEQUENTIAL_PALETTES
|
|
@@ -13233,7 +13243,7 @@ function compileTileMap(spec, options) {
|
|
|
13233
13243
|
height: contentHeight,
|
|
13234
13244
|
animation: resolvedAnimation,
|
|
13235
13245
|
watermark,
|
|
13236
|
-
measureText: options.measureText ?? ((text, fontSize) => ({ width:
|
|
13246
|
+
measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth19(text, fontSize), height: fontSize }))
|
|
13237
13247
|
};
|
|
13238
13248
|
}
|
|
13239
13249
|
function emptyLayout4(chrome, theme, options, watermark) {
|
|
@@ -13268,7 +13278,7 @@ function emptyLayout4(chrome, theme, options, watermark) {
|
|
|
13268
13278
|
height: options.height,
|
|
13269
13279
|
watermark,
|
|
13270
13280
|
animation: void 0,
|
|
13271
|
-
measureText: options.measureText ?? ((text, fontSize) => ({ width:
|
|
13281
|
+
measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth19(text, fontSize), height: fontSize }))
|
|
13272
13282
|
};
|
|
13273
13283
|
}
|
|
13274
13284
|
|
|
@@ -14287,7 +14297,7 @@ function compileChart(spec, options) {
|
|
|
14287
14297
|
else sample = "0.0";
|
|
14288
14298
|
const negPrefix = renderSpec.data.some((r) => Number(r[yField]) < 0) ? "-" : "";
|
|
14289
14299
|
const labelEst = negPrefix + sample;
|
|
14290
|
-
const labelWidth =
|
|
14300
|
+
const labelWidth = estimateTextWidth20(
|
|
14291
14301
|
labelEst,
|
|
14292
14302
|
theme.fonts.sizes.axisTick,
|
|
14293
14303
|
theme.fonts.weights.normal
|