@opendata-ai/openchart-engine 7.3.0 → 7.4.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 +4 -3
- package/dist/index.js +145 -80
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/compile-snapshot.test.ts.snap +4 -15
- package/src/__tests__/axes.test.ts +62 -0
- package/src/__tests__/dimensions.test.ts +63 -0
- package/src/__tests__/helpers/invariants.ts +4 -6
- package/src/__tests__/layout-fuzz.test.ts +24 -8
- package/src/__tests__/layout-invariants.test.ts +4 -2
- package/src/charts/bar/__tests__/compute.test.ts +131 -45
- package/src/charts/bar/compute.ts +44 -4
- package/src/charts/bar/index.ts +9 -2
- package/src/charts/bar/labels.ts +77 -26
- package/src/charts/column/labels.ts +29 -16
- package/src/charts/registry.ts +4 -2
- package/src/compile.ts +3 -1
- package/src/layout/axes/rotation.ts +36 -0
- package/src/layout/axes.ts +65 -35
- package/src/layout/plan.ts +28 -2
- package/src/layout/scales.ts +22 -0
package/dist/index.d.ts
CHANGED
|
@@ -556,10 +556,11 @@ declare function compile(spec: unknown): CompileResult;
|
|
|
556
556
|
* A chart renderer function.
|
|
557
557
|
*
|
|
558
558
|
* Takes a normalized spec, resolved scales, chart area, layout strategy,
|
|
559
|
-
*
|
|
560
|
-
*
|
|
559
|
+
* the resolved theme for theme-aware styling (e.g. label colors), and the
|
|
560
|
+
* full container width (used by geometry gates that must key off the
|
|
561
|
+
* container, not the derived plot width). Returns an array of marks to render.
|
|
561
562
|
*/
|
|
562
|
-
type ChartRenderer = (spec: NormalizedChartSpec, scales: ResolvedScales, chartArea: Rect, strategy: LayoutStrategy, theme: ResolvedTheme) => Mark[];
|
|
563
|
+
type ChartRenderer = (spec: NormalizedChartSpec, scales: ResolvedScales, chartArea: Rect, strategy: LayoutStrategy, theme: ResolvedTheme, containerWidth: number) => Mark[];
|
|
563
564
|
/**
|
|
564
565
|
* Register a chart renderer for a specific chart type.
|
|
565
566
|
*
|
package/dist/index.js
CHANGED
|
@@ -1925,7 +1925,7 @@ function computeEndpointLabels(spec, marks, theme, chartArea, strategy) {
|
|
|
1925
1925
|
}
|
|
1926
1926
|
|
|
1927
1927
|
// src/charts/bar/compute.ts
|
|
1928
|
-
import { isGradientDef } from "@opendata-ai/openchart-core";
|
|
1928
|
+
import { isGradientDef, NARROW_VIEWPORT_MAX } from "@opendata-ai/openchart-core";
|
|
1929
1929
|
|
|
1930
1930
|
// src/transforms/predicates.ts
|
|
1931
1931
|
function isFieldPredicate(pred) {
|
|
@@ -2094,7 +2094,8 @@ function orientGradientForHorizontalBar(grad) {
|
|
|
2094
2094
|
return { ...lg, x1: 0, y1: 0, x2: 1, y2: 0 };
|
|
2095
2095
|
}
|
|
2096
2096
|
var MIN_BAR_WIDTH = 1;
|
|
2097
|
-
|
|
2097
|
+
var MIN_GROUPED_BAR_THICKNESS = 8;
|
|
2098
|
+
function computeBarMarks(spec, scales, _chartArea, _strategy, containerWidth) {
|
|
2098
2099
|
const encoding = spec.encoding;
|
|
2099
2100
|
const xChannel = encoding.x;
|
|
2100
2101
|
const yChannel = encoding.y;
|
|
@@ -2143,7 +2144,8 @@ function computeBarMarks(spec, scales, _chartArea, _strategy) {
|
|
|
2143
2144
|
yScale,
|
|
2144
2145
|
bandwidth,
|
|
2145
2146
|
baseline,
|
|
2146
|
-
scales
|
|
2147
|
+
scales,
|
|
2148
|
+
containerWidth
|
|
2147
2149
|
);
|
|
2148
2150
|
} else {
|
|
2149
2151
|
const stackMode = xChannel.stack === "normalize" ? "normalize" : xChannel.stack === "center" ? "center" : "zero";
|
|
@@ -2218,7 +2220,7 @@ function computeStackedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
2218
2220
|
}
|
|
2219
2221
|
return marks;
|
|
2220
2222
|
}
|
|
2221
|
-
function computeGroupedBars(data, valueField, categoryField, colorField, xScale, yScale, bandwidth, baseline, scales) {
|
|
2223
|
+
function computeGroupedBars(data, valueField, categoryField, colorField, xScale, yScale, bandwidth, baseline, scales, containerWidth) {
|
|
2222
2224
|
const marks = [];
|
|
2223
2225
|
const categoryGroups = groupByField(data, categoryField);
|
|
2224
2226
|
const groupIndexMap = /* @__PURE__ */ new Map();
|
|
@@ -2231,7 +2233,17 @@ function computeGroupedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
2231
2233
|
const groupCount = groupIndexMap.size;
|
|
2232
2234
|
if (groupCount === 0) return marks;
|
|
2233
2235
|
const gap = Math.min(1, bandwidth * 0.05);
|
|
2234
|
-
|
|
2236
|
+
let groupHeight = bandwidth;
|
|
2237
|
+
let subBandHeight = (groupHeight - gap * (groupCount - 1)) / groupCount;
|
|
2238
|
+
const step = typeof yScale.step === "function" ? yScale.step() : bandwidth;
|
|
2239
|
+
const maxGroupHeight = Math.max(bandwidth, step - MIN_BAR_WIDTH);
|
|
2240
|
+
if (containerWidth < NARROW_VIEWPORT_MAX && subBandHeight < MIN_GROUPED_BAR_THICKNESS && maxGroupHeight > bandwidth) {
|
|
2241
|
+
const needed = MIN_GROUPED_BAR_THICKNESS * groupCount + gap * (groupCount - 1);
|
|
2242
|
+
groupHeight = Math.min(Math.max(groupHeight, needed), maxGroupHeight);
|
|
2243
|
+
subBandHeight = (groupHeight - gap * (groupCount - 1)) / groupCount;
|
|
2244
|
+
}
|
|
2245
|
+
subBandHeight = Math.max(subBandHeight, MIN_BAR_WIDTH);
|
|
2246
|
+
const groupOffset = (bandwidth - groupHeight) / 2;
|
|
2235
2247
|
for (const [category, rows] of categoryGroups) {
|
|
2236
2248
|
const bandY = yScale(category);
|
|
2237
2249
|
if (bandY === void 0) continue;
|
|
@@ -2243,7 +2255,7 @@ function computeGroupedBars(data, valueField, categoryField, colorField, xScale,
|
|
|
2243
2255
|
const color2 = getColor(scales, groupKey3);
|
|
2244
2256
|
const xPos = value2 >= 0 ? baseline : xScale(value2);
|
|
2245
2257
|
const barWidth = Math.max(Math.abs(xScale(value2) - baseline), MIN_BAR_WIDTH);
|
|
2246
|
-
const subY = bandY + groupIndex * (subBandHeight + gap);
|
|
2258
|
+
const subY = bandY + groupOffset + groupIndex * (subBandHeight + gap);
|
|
2247
2259
|
const aria = {
|
|
2248
2260
|
label: `${category}, ${groupKey3}: ${formatLabelValue(value2)}`
|
|
2249
2261
|
};
|
|
@@ -2422,6 +2434,7 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
|
|
|
2422
2434
|
const targetMarks = filterByDensity(marks, density);
|
|
2423
2435
|
const candidates = [];
|
|
2424
2436
|
const fitsInSegment = [];
|
|
2437
|
+
const displayAnchorX = [];
|
|
2425
2438
|
const formatter = buildD3Formatter(labelFormat);
|
|
2426
2439
|
for (const mark of targetMarks) {
|
|
2427
2440
|
let valuePart;
|
|
@@ -2479,12 +2492,15 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
|
|
|
2479
2492
|
textAnchor = "start";
|
|
2480
2493
|
}
|
|
2481
2494
|
}
|
|
2482
|
-
const
|
|
2495
|
+
const centerY = mark.y + mark.height / 2;
|
|
2496
|
+
const anchorY = centerY - textHeight / 2;
|
|
2497
|
+
const leftEdgeX = textAnchor === "end" ? anchorX - textWidth : textAnchor === "middle" ? anchorX - textWidth / 2 : anchorX;
|
|
2483
2498
|
const fits = !(isStacked2 && textWidth > mark.width - 2 * LABEL_PADDING);
|
|
2499
|
+
displayAnchorX.push(anchorX);
|
|
2484
2500
|
fitsInSegment.push(fits);
|
|
2485
2501
|
candidates.push({
|
|
2486
2502
|
text: valuePart,
|
|
2487
|
-
anchorX,
|
|
2503
|
+
anchorX: leftEdgeX,
|
|
2488
2504
|
anchorY,
|
|
2489
2505
|
width: textWidth,
|
|
2490
2506
|
height: textHeight,
|
|
@@ -2501,46 +2517,61 @@ function computeBarLabels(marks, _chartArea, density = "auto", labelFormat, labe
|
|
|
2501
2517
|
});
|
|
2502
2518
|
}
|
|
2503
2519
|
if (candidates.length === 0) return [];
|
|
2504
|
-
if (density === "all") {
|
|
2505
|
-
return candidates.map((c, i) => ({
|
|
2506
|
-
text: c.text,
|
|
2507
|
-
x: c.anchorX,
|
|
2508
|
-
y: c.anchorY,
|
|
2509
|
-
style: c.style,
|
|
2510
|
-
visible: fitsInSegment[i] !== false
|
|
2511
|
-
}));
|
|
2512
|
-
}
|
|
2513
2520
|
const fittingCandidates = [];
|
|
2514
2521
|
const unfittingIndices = /* @__PURE__ */ new Set();
|
|
2515
2522
|
for (let i = 0; i < candidates.length; i++) {
|
|
2516
2523
|
if (fitsInSegment[i] === false) {
|
|
2517
2524
|
unfittingIndices.add(i);
|
|
2518
2525
|
} else {
|
|
2519
|
-
fittingCandidates.push(candidates[i]);
|
|
2526
|
+
fittingCandidates.push({ ...candidates[i], index: i });
|
|
2520
2527
|
}
|
|
2521
2528
|
}
|
|
2522
|
-
const
|
|
2529
|
+
const resolvedByOrig = /* @__PURE__ */ new Map();
|
|
2530
|
+
for (const r of resolveCollisions(fittingCandidates)) {
|
|
2531
|
+
if (r.index !== void 0) resolvedByOrig.set(r.index, r);
|
|
2532
|
+
}
|
|
2533
|
+
const halfHeight = FONT_SIZE * 1.2 * 0.5;
|
|
2523
2534
|
const results = [];
|
|
2524
|
-
let resolvedIdx = 0;
|
|
2525
2535
|
for (let i = 0; i < candidates.length; i++) {
|
|
2526
2536
|
if (unfittingIndices.has(i)) {
|
|
2527
2537
|
results.push({
|
|
2528
2538
|
text: candidates[i].text,
|
|
2529
|
-
x:
|
|
2530
|
-
y: candidates[i].anchorY,
|
|
2539
|
+
x: displayAnchorX[i],
|
|
2540
|
+
y: candidates[i].anchorY + halfHeight,
|
|
2531
2541
|
style: candidates[i].style,
|
|
2532
2542
|
visible: false
|
|
2533
2543
|
});
|
|
2534
2544
|
} else {
|
|
2535
|
-
|
|
2545
|
+
const r = resolvedByOrig.get(i);
|
|
2546
|
+
if (!r) continue;
|
|
2547
|
+
const dx = displayAnchorX[i] - candidates[i].anchorX;
|
|
2548
|
+
const { index: _index, ...rest } = r;
|
|
2549
|
+
results.push({
|
|
2550
|
+
...rest,
|
|
2551
|
+
x: r.x + dx,
|
|
2552
|
+
y: r.y + halfHeight,
|
|
2553
|
+
connector: r.connector ? {
|
|
2554
|
+
...r.connector,
|
|
2555
|
+
from: {
|
|
2556
|
+
...r.connector.from,
|
|
2557
|
+
x: r.connector.from.x + dx,
|
|
2558
|
+
y: r.connector.from.y + halfHeight
|
|
2559
|
+
},
|
|
2560
|
+
to: {
|
|
2561
|
+
...r.connector.to,
|
|
2562
|
+
x: r.connector.to.x + dx,
|
|
2563
|
+
y: r.connector.to.y + halfHeight
|
|
2564
|
+
}
|
|
2565
|
+
} : void 0
|
|
2566
|
+
});
|
|
2536
2567
|
}
|
|
2537
2568
|
}
|
|
2538
2569
|
return results;
|
|
2539
2570
|
}
|
|
2540
2571
|
|
|
2541
2572
|
// src/charts/bar/index.ts
|
|
2542
|
-
var barRenderer = (spec, scales, chartArea, strategy, theme) => {
|
|
2543
|
-
const marks = computeBarMarks(spec, scales, chartArea, strategy);
|
|
2573
|
+
var barRenderer = (spec, scales, chartArea, strategy, theme, containerWidth) => {
|
|
2574
|
+
const marks = computeBarMarks(spec, scales, chartArea, strategy, containerWidth);
|
|
2544
2575
|
const valueField = spec.encoding?.x && "field" in spec.encoding.x ? spec.encoding.x.field : void 0;
|
|
2545
2576
|
const labels = computeBarLabels(
|
|
2546
2577
|
marks,
|
|
@@ -2852,7 +2883,8 @@ import {
|
|
|
2852
2883
|
buildD3Formatter as buildD3Formatter2,
|
|
2853
2884
|
estimateTextWidth as estimateTextWidth4,
|
|
2854
2885
|
getRepresentativeColor as getRepresentativeColor2,
|
|
2855
|
-
resolveCollisions as resolveCollisions2
|
|
2886
|
+
resolveCollisions as resolveCollisions2,
|
|
2887
|
+
textAscent
|
|
2856
2888
|
} from "@opendata-ai/openchart-core";
|
|
2857
2889
|
var LABEL_FONT_SIZE2 = 10;
|
|
2858
2890
|
var LABEL_FONT_WEIGHT2 = 600;
|
|
@@ -2903,22 +2935,22 @@ function computeColumnLabels(marks, _chartArea, density = "auto", labelFormat, l
|
|
|
2903
2935
|
fontWeight: LABEL_FONT_WEIGHT2,
|
|
2904
2936
|
fill: labelColor ?? getRepresentativeColor2(mark.fill),
|
|
2905
2937
|
lineHeight: 1.2,
|
|
2906
|
-
textAnchor: "middle"
|
|
2907
|
-
dominantBaseline: "hanging"
|
|
2938
|
+
textAnchor: "middle"
|
|
2908
2939
|
}
|
|
2909
2940
|
});
|
|
2910
2941
|
}
|
|
2911
2942
|
if (candidates.length === 0) return [];
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2943
|
+
const ascent = textAscent(FONT_SIZE);
|
|
2944
|
+
return resolveCollisions2(candidates).map((label) => ({
|
|
2945
|
+
...label,
|
|
2946
|
+
y: label.y + ascent,
|
|
2947
|
+
// The connector originates at the label; shift its start to the baseline
|
|
2948
|
+
// too so the leader stays glued to the shifted text.
|
|
2949
|
+
connector: label.connector ? {
|
|
2950
|
+
...label.connector,
|
|
2951
|
+
from: { ...label.connector.from, y: label.connector.from.y + ascent }
|
|
2952
|
+
} : void 0
|
|
2953
|
+
}));
|
|
2922
2954
|
}
|
|
2923
2955
|
|
|
2924
2956
|
// src/charts/column/index.ts
|
|
@@ -9534,7 +9566,7 @@ import {
|
|
|
9534
9566
|
MAX_LEFT_LABEL_FRACTION_DEFAULT,
|
|
9535
9567
|
MAX_LEFT_LABEL_FRACTION_MEDIUM,
|
|
9536
9568
|
MAX_LEFT_LABEL_FRACTION_MEDIUM_MAX,
|
|
9537
|
-
NARROW_VIEWPORT_MAX,
|
|
9569
|
+
NARROW_VIEWPORT_MAX as NARROW_VIEWPORT_MAX2,
|
|
9538
9570
|
TOP_PAD_EXTRA_NARROW
|
|
9539
9571
|
} from "@opendata-ai/openchart-core";
|
|
9540
9572
|
|
|
@@ -9875,6 +9907,15 @@ function placeLegend(content, chartArea, containerWidth, _theme, xAxisHeight) {
|
|
|
9875
9907
|
};
|
|
9876
9908
|
}
|
|
9877
9909
|
|
|
9910
|
+
// src/layout/axes/rotation.ts
|
|
9911
|
+
var BAND_FIT_FRACTION = 0.85;
|
|
9912
|
+
var AUTO_ROTATE_ANGLE = -45;
|
|
9913
|
+
function resolveBandTickAngle(explicitAngle, maxLabelWidth, bandwidth, labelCount) {
|
|
9914
|
+
if (explicitAngle !== void 0) return explicitAngle;
|
|
9915
|
+
if (labelCount < 2) return void 0;
|
|
9916
|
+
return maxLabelWidth > bandwidth * BAND_FIT_FRACTION ? AUTO_ROTATE_ANGLE : void 0;
|
|
9917
|
+
}
|
|
9918
|
+
|
|
9878
9919
|
// src/layout/axes/ticks.ts
|
|
9879
9920
|
import {
|
|
9880
9921
|
abbreviateNumber as abbreviateNumber2,
|
|
@@ -10259,6 +10300,16 @@ function evenRange(start, end, count) {
|
|
|
10259
10300
|
const step = (end - start) / (count - 1);
|
|
10260
10301
|
return Array.from({ length: count }, (_, i) => start + step * i);
|
|
10261
10302
|
}
|
|
10303
|
+
function estimateBandwidth(scaleConfig, plotWidth, n) {
|
|
10304
|
+
if (n <= 0) return 0;
|
|
10305
|
+
const padding = scaleConfig?.padding ?? 0.35;
|
|
10306
|
+
const paddingInner = scaleConfig?.paddingInner ?? padding;
|
|
10307
|
+
const paddingOuter = scaleConfig?.paddingOuter ?? padding;
|
|
10308
|
+
const denom = n - paddingInner + 2 * paddingOuter;
|
|
10309
|
+
if (denom <= 0) return 0;
|
|
10310
|
+
const step = plotWidth / denom;
|
|
10311
|
+
return step * (1 - paddingInner);
|
|
10312
|
+
}
|
|
10262
10313
|
function buildBandScale(channel, data, rangeStart, rangeEnd) {
|
|
10263
10314
|
const values = channel.scale?.domain ? channel.scale.domain : applyCategoricalSort(uniqueStrings(fieldValues(data, channel.field)), channel.sort);
|
|
10264
10315
|
const padding = channel.scale?.padding ?? 0.35;
|
|
@@ -10630,7 +10681,7 @@ function resolveLayoutPlan(chartSpec, renderSpec, options, theme, strategy, wate
|
|
|
10630
10681
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
10631
10682
|
}
|
|
10632
10683
|
if (maxLabelWidth > 0) {
|
|
10633
|
-
const labelGap = width <
|
|
10684
|
+
const labelGap = width < NARROW_VIEWPORT_MAX2 ? LABEL_GAP_COMPACT : LABEL_GAP_DEFAULT;
|
|
10634
10685
|
const maxLeftFraction = width < BREAKPOINT_COMPACT_MAX ? MAX_LEFT_LABEL_FRACTION_COMPACT : width < MAX_LEFT_LABEL_FRACTION_MEDIUM_MAX ? MAX_LEFT_LABEL_FRACTION_MEDIUM : MAX_LEFT_LABEL_FRACTION_DEFAULT;
|
|
10635
10686
|
const maxLeftReserved = Math.floor(width * maxLeftFraction);
|
|
10636
10687
|
const reserved = Math.min(hPad + maxLabelWidth + labelGap, maxLeftReserved);
|
|
@@ -10705,16 +10756,33 @@ function resolveLayoutPlan(chartSpec, renderSpec, options, theme, strategy, wate
|
|
|
10705
10756
|
watermark,
|
|
10706
10757
|
bottomLegendReservation
|
|
10707
10758
|
);
|
|
10759
|
+
let effectiveXTickAngle = xTickAngle;
|
|
10760
|
+
if (xTickAngle === void 0 && xLabels.length > 1) {
|
|
10761
|
+
const rightMarginEst = hPad + (isRadial ? hPad : axisMargin);
|
|
10762
|
+
const plotWidth = Math.max(0, width - leftGutter - Math.max(rightMarginEst, hPad));
|
|
10763
|
+
const bandwidth = estimateBandwidth(encoding.x?.scale, plotWidth, xLabels.length);
|
|
10764
|
+
let maxXLabelWidth = 0;
|
|
10765
|
+
for (const label of xLabels) {
|
|
10766
|
+
const w = measure(label, theme.fonts.sizes.axisTick, theme.fonts.weights.normal);
|
|
10767
|
+
if (w > maxXLabelWidth) maxXLabelWidth = w;
|
|
10768
|
+
}
|
|
10769
|
+
effectiveXTickAngle = resolveBandTickAngle(
|
|
10770
|
+
void 0,
|
|
10771
|
+
maxXLabelWidth,
|
|
10772
|
+
bandwidth,
|
|
10773
|
+
xLabels.length
|
|
10774
|
+
);
|
|
10775
|
+
}
|
|
10708
10776
|
const xAxisExtent = isRadial || xAxisSuppressed ? 0 : computeXAxisExtentFromLabels({
|
|
10709
10777
|
labels: xLabels,
|
|
10710
|
-
tickAngle:
|
|
10778
|
+
tickAngle: effectiveXTickAngle,
|
|
10711
10779
|
hasTitle: hasXAxisTitle,
|
|
10712
10780
|
tickFontSize: theme.fonts.sizes.axisTick,
|
|
10713
10781
|
tickFontWeight: theme.fonts.weights.normal,
|
|
10714
10782
|
xAxisHeight: theme.spacing.xAxisHeight,
|
|
10715
10783
|
measure
|
|
10716
10784
|
});
|
|
10717
|
-
const topPad = width <
|
|
10785
|
+
const topPad = width < NARROW_VIEWPORT_MAX2 ? padding + TOP_PAD_EXTRA_NARROW : padding;
|
|
10718
10786
|
const hasTopLegend = legendContent.entries.length > 0 && legendContent.position === "top";
|
|
10719
10787
|
const gap = legendGap(width);
|
|
10720
10788
|
const inlineTickOverhang = yIsInline ? theme.fonts.sizes.axisTick + INLINE_TICK_OVERHANG_PAD : 0;
|
|
@@ -11793,12 +11861,25 @@ function effectiveDensity(baseDensity, axisLength, minimalThreshold, reducedThre
|
|
|
11793
11861
|
}
|
|
11794
11862
|
var CONTINUOUS_TICK_FLOOR = 4;
|
|
11795
11863
|
var OVERSHOOT_TOLERANCE = 1.5;
|
|
11864
|
+
var MIN_QUANTITATIVE_TICKS = 2;
|
|
11865
|
+
function ensureMinContinuousTicks(scale, ticks2, requestedCount) {
|
|
11866
|
+
if (ticks2.length >= MIN_QUANTITATIVE_TICKS) return ticks2;
|
|
11867
|
+
if (!scale || !scaleSupportsTickCount(scale)) return ticks2;
|
|
11868
|
+
let best = ticks2;
|
|
11869
|
+
for (let n = Math.max(requestedCount, MIN_QUANTITATIVE_TICKS) + 1; n <= 10; n++) {
|
|
11870
|
+
const candidate = buildContinuousTicks(scale, n);
|
|
11871
|
+
if (candidate.length >= MIN_QUANTITATIVE_TICKS) return candidate;
|
|
11872
|
+
if (candidate.length > best.length) best = candidate;
|
|
11873
|
+
}
|
|
11874
|
+
return best;
|
|
11875
|
+
}
|
|
11796
11876
|
function fitContinuousTicks(scale, initialTicks, initialCount, fontSize, fontWeight, axisLength, orientation, measureText) {
|
|
11797
11877
|
if (!scale || !scaleSupportsTickCount(scale)) return initialTicks;
|
|
11878
|
+
const flooredTicks = ensureMinContinuousTicks(scale, initialTicks, initialCount);
|
|
11798
11879
|
const tolerance = initialCount * OVERSHOOT_TOLERANCE;
|
|
11799
|
-
const overshoots =
|
|
11800
|
-
const overlaps = ticksOverlap(
|
|
11801
|
-
if (!overshoots && !overlaps) return
|
|
11880
|
+
const overshoots = flooredTicks.length > tolerance;
|
|
11881
|
+
const overlaps = ticksOverlap(flooredTicks, fontSize, fontWeight, measureText, orientation);
|
|
11882
|
+
if (!overshoots && !overlaps) return flooredTicks;
|
|
11802
11883
|
const minThreshold = orientation === "vertical" ? HEIGHT_MINIMAL_THRESHOLD : WIDTH_MINIMAL_THRESHOLD;
|
|
11803
11884
|
const floor = axisLength >= minThreshold ? CONTINUOUS_TICK_FLOOR : 2;
|
|
11804
11885
|
let bestWithinFloor;
|
|
@@ -11817,7 +11898,8 @@ function fitContinuousTicks(scale, initialTicks, initialCount, fontSize, fontWei
|
|
|
11817
11898
|
}
|
|
11818
11899
|
if (candidate.length >= floor) bestWithinFloor = candidate;
|
|
11819
11900
|
}
|
|
11820
|
-
const
|
|
11901
|
+
const rawFallback = bestWithinFloor ?? buildContinuousTicks(scale, floor);
|
|
11902
|
+
const fallback = ensureMinContinuousTicks(scale, rawFallback, floor);
|
|
11821
11903
|
return thinTicksUntilFit(fallback, fontSize, fontWeight, measureText, orientation);
|
|
11822
11904
|
}
|
|
11823
11905
|
function bandTicksOverlapAtAngle(ticks2, angleDeg, fontSize, fontWeight, measureText) {
|
|
@@ -11904,9 +11986,7 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
|
|
|
11904
11986
|
const w = measureLabel(t.label, fontSize, fontWeight, measureText);
|
|
11905
11987
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
11906
11988
|
}
|
|
11907
|
-
|
|
11908
|
-
tickAngle = -45;
|
|
11909
|
-
}
|
|
11989
|
+
tickAngle = resolveBandTickAngle(void 0, maxLabelWidth, bandwidth, allTicks.length);
|
|
11910
11990
|
}
|
|
11911
11991
|
const hasExplicitValues = !!axisConfig?.values;
|
|
11912
11992
|
let ticks2;
|
|
@@ -12026,36 +12106,21 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
|
|
|
12026
12106
|
}
|
|
12027
12107
|
const totalWidth = dataContext?.totalWidth ?? chartArea.x + chartArea.width;
|
|
12028
12108
|
if (result.x) {
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12036
|
-
|
|
12037
|
-
|
|
12038
|
-
|
|
12039
|
-
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
12040
|
-
}
|
|
12041
|
-
rotatedHeight = Math.min(maxLabelWidth * Math.sin(angleRad) + 6, 120);
|
|
12042
|
-
}
|
|
12043
|
-
if (rotatedHeight != null) {
|
|
12044
|
-
result.x.extent = result.x.label ? rotatedHeight + X_AXIS_TITLE_BAND_ROTATED : rotatedHeight;
|
|
12045
|
-
} else {
|
|
12046
|
-
result.x.extent = computeXAxisExtentFromLabels2({
|
|
12047
|
-
labels: result.x.ticks.map((t) => t.label),
|
|
12048
|
-
tickAngle: result.x.tickAngle,
|
|
12049
|
-
hasTitle: !!result.x.label,
|
|
12050
|
-
tickFontSize: result.x.tickLabelStyle.fontSize,
|
|
12051
|
-
tickFontWeight: result.x.tickLabelStyle.fontWeight,
|
|
12052
|
-
xAxisHeight: theme.spacing.xAxisHeight
|
|
12053
|
-
});
|
|
12054
|
-
}
|
|
12109
|
+
const isRotated = !!result.x.tickAngle && Math.abs(result.x.tickAngle) > 10;
|
|
12110
|
+
result.x.extent = computeXAxisExtentFromLabels2({
|
|
12111
|
+
labels: result.x.ticks.map((t) => t.label),
|
|
12112
|
+
tickAngle: result.x.tickAngle,
|
|
12113
|
+
hasTitle: !!result.x.label,
|
|
12114
|
+
tickFontSize: result.x.tickLabelStyle.fontSize,
|
|
12115
|
+
tickFontWeight: result.x.tickLabelStyle.fontWeight,
|
|
12116
|
+
xAxisHeight: theme.spacing.xAxisHeight,
|
|
12117
|
+
measure: (t, fs, fw) => measureLabel(t, fs, fw, measureText)
|
|
12118
|
+
});
|
|
12055
12119
|
if (result.x.label) {
|
|
12120
|
+
const rotatedBand = isRotated ? result.x.extent - X_AXIS_TITLE_BAND_ROTATED + 14 : 35;
|
|
12056
12121
|
result.x.titlePosition = {
|
|
12057
12122
|
x: chartArea.x + chartArea.width / 2,
|
|
12058
|
-
y: chartArea.y + chartArea.height +
|
|
12123
|
+
y: chartArea.y + chartArea.height + rotatedBand
|
|
12059
12124
|
};
|
|
12060
12125
|
}
|
|
12061
12126
|
}
|
|
@@ -12103,7 +12168,7 @@ import {
|
|
|
12103
12168
|
MAX_LEFT_LABEL_FRACTION_DEFAULT as MAX_LEFT_LABEL_FRACTION_DEFAULT2,
|
|
12104
12169
|
MAX_LEFT_LABEL_FRACTION_MEDIUM as MAX_LEFT_LABEL_FRACTION_MEDIUM2,
|
|
12105
12170
|
MAX_LEFT_LABEL_FRACTION_MEDIUM_MAX as MAX_LEFT_LABEL_FRACTION_MEDIUM_MAX2,
|
|
12106
|
-
NARROW_VIEWPORT_MAX as
|
|
12171
|
+
NARROW_VIEWPORT_MAX as NARROW_VIEWPORT_MAX3,
|
|
12107
12172
|
TOP_PAD_EXTRA_NARROW as TOP_PAD_EXTRA_NARROW2
|
|
12108
12173
|
} from "@opendata-ai/openchart-core";
|
|
12109
12174
|
|
|
@@ -12324,7 +12389,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
12324
12389
|
const yIsInlinePre = (yTickPositionExplicitPre ?? (yIsLineOrAreaPre && yIsContinuousPre && yAxisOrientPre !== "right" ? "inline" : "gutter")) === "inline";
|
|
12325
12390
|
const inlineTickOverhang = yIsInlinePre ? theme.fonts.sizes.axisTick + INLINE_TICK_OVERHANG_PAD : 0;
|
|
12326
12391
|
const topAxisGap = isRadial && chrome.topHeight === 0 ? 0 : axisMargin + inlineTickOverhang;
|
|
12327
|
-
const topPad = width <
|
|
12392
|
+
const topPad = width < NARROW_VIEWPORT_MAX3 ? padding + TOP_PAD_EXTRA_NARROW2 : padding;
|
|
12328
12393
|
const wantsMetrics = !!spec.metrics && spec.metrics.length > 0 && chromeMode !== "hidden";
|
|
12329
12394
|
const tentativeMetricsHeight = wantsMetrics ? metricBarHeight(metricFonts(theme)) : 0;
|
|
12330
12395
|
const margins = {
|
|
@@ -12425,7 +12490,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
|
|
|
12425
12490
|
if (w > maxLabelWidth) maxLabelWidth = w;
|
|
12426
12491
|
}
|
|
12427
12492
|
if (maxLabelWidth > 0) {
|
|
12428
|
-
const labelGap = width <
|
|
12493
|
+
const labelGap = width < NARROW_VIEWPORT_MAX3 ? LABEL_GAP_COMPACT2 : LABEL_GAP_DEFAULT2;
|
|
12429
12494
|
const maxLeftFraction = width < BREAKPOINT_COMPACT_MAX3 ? MAX_LEFT_LABEL_FRACTION_COMPACT2 : width < MAX_LEFT_LABEL_FRACTION_MEDIUM_MAX2 ? MAX_LEFT_LABEL_FRACTION_MEDIUM2 : MAX_LEFT_LABEL_FRACTION_DEFAULT2;
|
|
12430
12495
|
const maxLeftReserved = Math.floor(width * maxLeftFraction);
|
|
12431
12496
|
const reserved = Math.min(hPad + maxLabelWidth + labelGap, maxLeftReserved);
|
|
@@ -15533,7 +15598,7 @@ function compileChart(spec, options) {
|
|
|
15533
15598
|
renderSpec.markDef
|
|
15534
15599
|
);
|
|
15535
15600
|
const renderer = getChartRenderer(rendererKey);
|
|
15536
|
-
const marks = renderer ? renderer(renderSpec, scales, chartArea, strategy, theme) : [];
|
|
15601
|
+
const marks = renderer ? renderer(renderSpec, scales, chartArea, strategy, theme, options.width) : [];
|
|
15537
15602
|
const endpointLabels = computeEndpointLabels(chartSpec, marks, theme, chartArea, strategy);
|
|
15538
15603
|
const obstacles = [];
|
|
15539
15604
|
if (finalLegend.bounds.width > 0) {
|