@opendata-ai/openchart-engine 6.25.2 → 6.25.3
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 +63 -27
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/axes.test.ts +75 -0
- package/src/layout/axes/ticks.ts +64 -9
- package/src/layout/axes.ts +13 -2
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
adaptTheme as adaptTheme3,
|
|
5
5
|
BREAKPOINT_COMPACT_MAX as BREAKPOINT_COMPACT_MAX2,
|
|
6
6
|
computeLabelBounds,
|
|
7
|
-
estimateTextWidth as
|
|
7
|
+
estimateTextWidth as estimateTextWidth14,
|
|
8
8
|
generateAltText,
|
|
9
9
|
generateDataTable,
|
|
10
10
|
getAxisTitleOffset as getAxisTitleOffset2,
|
|
@@ -7921,6 +7921,7 @@ import {
|
|
|
7921
7921
|
abbreviateNumber as abbreviateNumber2,
|
|
7922
7922
|
buildD3Formatter as buildD3Formatter4,
|
|
7923
7923
|
buildTemporalFormatter,
|
|
7924
|
+
estimateTextWidth as estimateTextWidth8,
|
|
7924
7925
|
formatDate,
|
|
7925
7926
|
formatNumber as formatNumber2
|
|
7926
7927
|
} from "@opendata-ai/openchart-core";
|
|
@@ -8015,16 +8016,41 @@ function scaleSupportsTickCount(resolvedScale) {
|
|
|
8015
8016
|
const scale = resolvedScale.scale;
|
|
8016
8017
|
return "ticks" in scale && typeof scale.ticks === "function";
|
|
8017
8018
|
}
|
|
8018
|
-
function categoricalTicks(resolvedScale, density) {
|
|
8019
|
+
function categoricalTicks(resolvedScale, density, orientation = "horizontal", bandwidth, labelAngle, fontSize, fontWeight, measureText) {
|
|
8019
8020
|
const scale = resolvedScale.scale;
|
|
8020
8021
|
const domain = scale.domain();
|
|
8021
8022
|
const explicitTickCount = resolvedScale.channel.axis?.tickCount;
|
|
8022
|
-
const maxTicks = explicitTickCount ?? TICK_COUNTS[density];
|
|
8023
8023
|
let selectedValues = domain;
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8024
|
+
if (resolvedScale.type === "band" && orientation === "horizontal") {
|
|
8025
|
+
if (bandwidth !== void 0 && bandwidth > 0 && fontSize !== void 0) {
|
|
8026
|
+
const maxLabelWidth = domain.reduce((max4, v) => {
|
|
8027
|
+
const w = measureText ? measureText(v, fontSize, fontWeight ?? 400).width : estimateTextWidth8(v, fontSize, fontWeight ?? 400);
|
|
8028
|
+
return Math.max(max4, w);
|
|
8029
|
+
}, 0);
|
|
8030
|
+
const angleRad = labelAngle !== void 0 ? Math.abs(labelAngle) * Math.PI / 180 : 0;
|
|
8031
|
+
const footprint = angleRad > 0 ? maxLabelWidth * Math.abs(Math.cos(angleRad)) : maxLabelWidth;
|
|
8032
|
+
const minGap = fontSize * 0.5;
|
|
8033
|
+
if (footprint + minGap > bandwidth) {
|
|
8034
|
+
const maxFitting = Math.max(1, Math.floor(bandwidth / (footprint + minGap)));
|
|
8035
|
+
const cap = explicitTickCount ?? Math.min(domain.length, Math.max(maxFitting, TICK_COUNTS[density]));
|
|
8036
|
+
if (domain.length > cap) {
|
|
8037
|
+
const step = Math.ceil(domain.length / cap);
|
|
8038
|
+
selectedValues = domain.filter((_, i) => i % step === 0);
|
|
8039
|
+
}
|
|
8040
|
+
}
|
|
8041
|
+
} else {
|
|
8042
|
+
const maxTicks = explicitTickCount ?? TICK_COUNTS[density];
|
|
8043
|
+
if ((explicitTickCount || density !== "full") && domain.length > maxTicks) {
|
|
8044
|
+
const step = Math.ceil(domain.length / maxTicks);
|
|
8045
|
+
selectedValues = domain.filter((_, i) => i % step === 0);
|
|
8046
|
+
}
|
|
8047
|
+
}
|
|
8048
|
+
} else if (resolvedScale.type !== "band") {
|
|
8049
|
+
const maxTicks = explicitTickCount ?? TICK_COUNTS[density];
|
|
8050
|
+
if (domain.length > maxTicks) {
|
|
8051
|
+
const step = Math.ceil(domain.length / maxTicks);
|
|
8052
|
+
selectedValues = domain.filter((_, i) => i % step === 0);
|
|
8053
|
+
}
|
|
8028
8054
|
}
|
|
8029
8055
|
const ticks2 = selectedValues.map((value2) => {
|
|
8030
8056
|
const bandScale = resolvedScale.type === "band" ? scale : null;
|
|
@@ -8145,7 +8171,17 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
8145
8171
|
if (axisConfig?.values) {
|
|
8146
8172
|
allTicks = resolveExplicitTicks(axisConfig.values, scales.x);
|
|
8147
8173
|
} else if (!isContinuousX) {
|
|
8148
|
-
|
|
8174
|
+
const xBandwidth = scales.x.type === "band" ? scales.x.scale.bandwidth() : void 0;
|
|
8175
|
+
allTicks = categoricalTicks(
|
|
8176
|
+
scales.x,
|
|
8177
|
+
xDensity,
|
|
8178
|
+
"horizontal",
|
|
8179
|
+
xBandwidth,
|
|
8180
|
+
axisConfig?.labelAngle,
|
|
8181
|
+
fontSize,
|
|
8182
|
+
fontWeight,
|
|
8183
|
+
measureText
|
|
8184
|
+
);
|
|
8149
8185
|
} else {
|
|
8150
8186
|
allTicks = continuousTicks(scales.x, xDensity, xTargetCount);
|
|
8151
8187
|
}
|
|
@@ -8213,7 +8249,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText) {
|
|
|
8213
8249
|
if (axisConfig?.values) {
|
|
8214
8250
|
allTicks = resolveExplicitTicks(axisConfig.values, scales.y);
|
|
8215
8251
|
} else if (!isContinuousY) {
|
|
8216
|
-
allTicks = categoricalTicks(scales.y, yDensity);
|
|
8252
|
+
allTicks = categoricalTicks(scales.y, yDensity, "vertical");
|
|
8217
8253
|
} else {
|
|
8218
8254
|
allTicks = continuousTicks(scales.y, yDensity, yTargetCount);
|
|
8219
8255
|
}
|
|
@@ -8270,7 +8306,7 @@ import {
|
|
|
8270
8306
|
AXIS_TITLE_TRAILING_PAD,
|
|
8271
8307
|
BREAKPOINT_COMPACT_MAX,
|
|
8272
8308
|
computeChrome as computeChrome2,
|
|
8273
|
-
estimateTextWidth as
|
|
8309
|
+
estimateTextWidth as estimateTextWidth10,
|
|
8274
8310
|
getAxisTitleOffset,
|
|
8275
8311
|
HPAD_COMPACT_FRACTION,
|
|
8276
8312
|
HPAD_COMPACT_MIN,
|
|
@@ -8285,7 +8321,7 @@ import {
|
|
|
8285
8321
|
} from "@opendata-ai/openchart-core";
|
|
8286
8322
|
|
|
8287
8323
|
// src/legend/wrap.ts
|
|
8288
|
-
import { COMPACT_WIDTH, estimateTextWidth as
|
|
8324
|
+
import { COMPACT_WIDTH, estimateTextWidth as estimateTextWidth9 } from "@opendata-ai/openchart-core";
|
|
8289
8325
|
var SWATCH_SIZE2 = 12;
|
|
8290
8326
|
var SWATCH_GAP2 = 6;
|
|
8291
8327
|
var ENTRY_GAP2 = 16;
|
|
@@ -8304,7 +8340,7 @@ function measureLegendWrap(entries, maxWidth, labelStyle, maxRows, entryGap = EN
|
|
|
8304
8340
|
let fittingCount = entries.length;
|
|
8305
8341
|
let fittingCountLocked = false;
|
|
8306
8342
|
for (let i = 0; i < entries.length; i++) {
|
|
8307
|
-
const labelWidth =
|
|
8343
|
+
const labelWidth = estimateTextWidth9(
|
|
8308
8344
|
entries[i].label,
|
|
8309
8345
|
labelStyle.fontSize,
|
|
8310
8346
|
labelStyle.fontWeight
|
|
@@ -8376,7 +8412,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8376
8412
|
if (xField) {
|
|
8377
8413
|
for (const row of spec.data) {
|
|
8378
8414
|
const label = String(row[xField] ?? "");
|
|
8379
|
-
const w =
|
|
8415
|
+
const w = estimateTextWidth10(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
|
|
8380
8416
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
8381
8417
|
}
|
|
8382
8418
|
}
|
|
@@ -8406,7 +8442,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8406
8442
|
const label = String(row[colorField] ?? "");
|
|
8407
8443
|
if (!seen.has(label)) {
|
|
8408
8444
|
seen.add(label);
|
|
8409
|
-
const w =
|
|
8445
|
+
const w = estimateTextWidth10(label, 11, 600);
|
|
8410
8446
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
8411
8447
|
}
|
|
8412
8448
|
}
|
|
@@ -8426,7 +8462,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8426
8462
|
const maxXStr = String(maxX);
|
|
8427
8463
|
for (const ann of spec.annotations) {
|
|
8428
8464
|
if (ann.type === "text" && String(ann.x) === maxXStr) {
|
|
8429
|
-
const textWidth =
|
|
8465
|
+
const textWidth = estimateTextWidth10(ann.text, ann.fontSize ?? 11, ann.fontWeight ?? 600);
|
|
8430
8466
|
const dx = ann.offset?.dx ?? 0;
|
|
8431
8467
|
const anchor = ann.anchor ?? "auto";
|
|
8432
8468
|
const baseRightExtent = anchor === "left" ? textWidth : (
|
|
@@ -8450,7 +8486,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8450
8486
|
let maxLabelWidth = 0;
|
|
8451
8487
|
for (const row of spec.data) {
|
|
8452
8488
|
const label = String(row[yField] ?? "");
|
|
8453
|
-
const w =
|
|
8489
|
+
const w = estimateTextWidth10(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
|
|
8454
8490
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
8455
8491
|
}
|
|
8456
8492
|
if (maxLabelWidth > 0) {
|
|
@@ -8486,7 +8522,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
8486
8522
|
}
|
|
8487
8523
|
const negPrefix = spec.data.some((r) => Number(r[yField]) < 0) ? "-" : "";
|
|
8488
8524
|
const labelEst = negPrefix + sampleLabel;
|
|
8489
|
-
const labelWidth =
|
|
8525
|
+
const labelWidth = estimateTextWidth10(
|
|
8490
8526
|
labelEst,
|
|
8491
8527
|
theme.fonts.sizes.axisTick,
|
|
8492
8528
|
theme.fonts.weights.normal
|
|
@@ -9007,7 +9043,7 @@ function computeScales(spec, chartArea, data) {
|
|
|
9007
9043
|
}
|
|
9008
9044
|
|
|
9009
9045
|
// src/legend/compute.ts
|
|
9010
|
-
import { BRAND_RESERVE_WIDTH as BRAND_RESERVE_WIDTH2, COMPACT_WIDTH as COMPACT_WIDTH2, estimateTextWidth as
|
|
9046
|
+
import { BRAND_RESERVE_WIDTH as BRAND_RESERVE_WIDTH2, COMPACT_WIDTH as COMPACT_WIDTH2, estimateTextWidth as estimateTextWidth11 } from "@opendata-ai/openchart-core";
|
|
9011
9047
|
var LEGEND_PADDING = 8;
|
|
9012
9048
|
var LEGEND_RIGHT_WIDTH = 120;
|
|
9013
9049
|
var RIGHT_LEGEND_MAX_HEIGHT_RATIO = 0.4;
|
|
@@ -9123,7 +9159,7 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
|
|
|
9123
9159
|
}
|
|
9124
9160
|
if (resolvedPosition === "right" || resolvedPosition === "bottom-right") {
|
|
9125
9161
|
const maxLabelWidth = Math.max(
|
|
9126
|
-
...entries.map((e) =>
|
|
9162
|
+
...entries.map((e) => estimateTextWidth11(e.label, labelStyle.fontSize, labelStyle.fontWeight))
|
|
9127
9163
|
);
|
|
9128
9164
|
const legendWidth = Math.min(
|
|
9129
9165
|
LEGEND_RIGHT_WIDTH,
|
|
@@ -9183,7 +9219,7 @@ function computeLegend(spec, strategy, theme, chartArea, watermark = true) {
|
|
|
9183
9219
|
entries = truncateEntries(entries, fittingCount);
|
|
9184
9220
|
}
|
|
9185
9221
|
const totalWidth = entries.reduce((sum2, entry) => {
|
|
9186
|
-
const labelWidth =
|
|
9222
|
+
const labelWidth = estimateTextWidth11(entry.label, labelStyle.fontSize, labelStyle.fontWeight);
|
|
9187
9223
|
return sum2 + SWATCH_SIZE2 + SWATCH_GAP2 + labelWidth + effectiveEntryGap;
|
|
9188
9224
|
}, 0);
|
|
9189
9225
|
const { rowCount } = measureLegendWrap(
|
|
@@ -9218,7 +9254,7 @@ import {
|
|
|
9218
9254
|
adaptTheme as adaptTheme2,
|
|
9219
9255
|
buildD3Formatter as buildD3Formatter5,
|
|
9220
9256
|
computeChrome as computeChrome3,
|
|
9221
|
-
estimateTextWidth as
|
|
9257
|
+
estimateTextWidth as estimateTextWidth12,
|
|
9222
9258
|
formatNumber as formatNumber3,
|
|
9223
9259
|
resolveTheme as resolveTheme2
|
|
9224
9260
|
} from "@opendata-ai/openchart-core";
|
|
@@ -9899,7 +9935,7 @@ function compileSankey(spec, options) {
|
|
|
9899
9935
|
if (labelsLeft) continue;
|
|
9900
9936
|
const labelX = (node.x1 ?? nodeWidth) + LABEL_GAP;
|
|
9901
9937
|
const labelText = node.label ?? node.id;
|
|
9902
|
-
const labelWidth =
|
|
9938
|
+
const labelWidth = estimateTextWidth12(labelText, labelFontSize, labelFontWeight);
|
|
9903
9939
|
const overflow = labelX + labelWidth - rightEdge;
|
|
9904
9940
|
if (overflow > maxOverflow) maxOverflow = overflow;
|
|
9905
9941
|
}
|
|
@@ -10161,7 +10197,7 @@ function emptyLayout(area, chrome, theme, options, watermark) {
|
|
|
10161
10197
|
}
|
|
10162
10198
|
|
|
10163
10199
|
// src/tables/compile-table.ts
|
|
10164
|
-
import { computeChrome as computeChrome4, estimateTextWidth as
|
|
10200
|
+
import { computeChrome as computeChrome4, estimateTextWidth as estimateTextWidth13 } from "@opendata-ai/openchart-core";
|
|
10165
10201
|
|
|
10166
10202
|
// src/tables/bar-column.ts
|
|
10167
10203
|
var NEGATIVE_BAR_COLOR = "#c44e52";
|
|
@@ -10576,13 +10612,13 @@ function estimateColumnWidth(col, data, fontSize) {
|
|
|
10576
10612
|
if (col.image) return (col.image.width ?? 24) + PADDING;
|
|
10577
10613
|
if (col.flag) return 60;
|
|
10578
10614
|
const label = col.label ?? col.key;
|
|
10579
|
-
const headerWidth =
|
|
10615
|
+
const headerWidth = estimateTextWidth13(label, fontSize, 600) + PADDING;
|
|
10580
10616
|
const sampleSize = Math.min(100, data.length);
|
|
10581
10617
|
let maxDataWidth = 0;
|
|
10582
10618
|
for (let i = 0; i < sampleSize; i++) {
|
|
10583
10619
|
const val = data[i][col.key];
|
|
10584
10620
|
const text = val == null ? "" : String(val);
|
|
10585
|
-
const width =
|
|
10621
|
+
const width = estimateTextWidth13(text, fontSize, 400) + PADDING;
|
|
10586
10622
|
if (width > maxDataWidth) maxDataWidth = width;
|
|
10587
10623
|
}
|
|
10588
10624
|
return Math.max(MIN_WIDTH, headerWidth, maxDataWidth);
|
|
@@ -11578,7 +11614,7 @@ function estimateYAxisLabelWidth(data, encoding, baseFontSize) {
|
|
|
11578
11614
|
let maxWidth = 0;
|
|
11579
11615
|
for (const row of data) {
|
|
11580
11616
|
const label = String(row[yField] ?? "");
|
|
11581
|
-
const w =
|
|
11617
|
+
const w = estimateTextWidth14(label, baseFontSize, 400);
|
|
11582
11618
|
if (w > maxWidth) maxWidth = w;
|
|
11583
11619
|
}
|
|
11584
11620
|
return maxWidth > 0 ? maxWidth + 10 : 40;
|
|
@@ -11607,7 +11643,7 @@ function estimateYAxisLabelWidth(data, encoding, baseFontSize) {
|
|
|
11607
11643
|
}
|
|
11608
11644
|
const hasNeg = data.some((r) => Number(r[yField]) < 0);
|
|
11609
11645
|
const labelEst = (hasNeg ? "-" : "") + sampleLabel;
|
|
11610
|
-
return
|
|
11646
|
+
return estimateTextWidth14(labelEst, baseFontSize, 400) + 10;
|
|
11611
11647
|
}
|
|
11612
11648
|
function compileLayerIndependent(leaves, layerSpec, options) {
|
|
11613
11649
|
if (leaves.length > 2) {
|