@opendata-ai/openchart-engine 7.0.0 → 7.0.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 +19 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/compile/layer.ts +1 -0
- package/src/compile.ts +10 -0
- package/src/compiler/normalize.ts +6 -0
- package/src/layout/dimensions.ts +17 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendata-ai/openchart-engine",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.3",
|
|
4
4
|
"description": "Headless compiler for openchart: spec validation, data compilation, scales, and layout",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Riley Hilliard",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"typecheck": "tsc --noEmit"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@opendata-ai/openchart-core": "7.0.
|
|
51
|
+
"@opendata-ai/openchart-core": "7.0.3",
|
|
52
52
|
"d3-array": "^3.2.0",
|
|
53
53
|
"d3-format": "^3.1.2",
|
|
54
54
|
"d3-interpolate": "^3.0.0",
|
package/src/compile/layer.ts
CHANGED
|
@@ -491,6 +491,7 @@ function buildPrimarySpec(leaves: ChartSpec[], layerSpec: LayerSpec): ChartSpec
|
|
|
491
491
|
darkMode: layerSpec.darkMode ?? leaves[0].darkMode,
|
|
492
492
|
watermark: layerSpec.watermark ?? leaves[0].watermark,
|
|
493
493
|
hiddenSeries: layerSpec.hiddenSeries ?? leaves[0].hiddenSeries,
|
|
494
|
+
endpointLabels: layerSpec.endpointLabels ?? leaves[0].endpointLabels,
|
|
494
495
|
};
|
|
495
496
|
|
|
496
497
|
return primary;
|
package/src/compile.ts
CHANGED
|
@@ -552,15 +552,25 @@ export function compileChart(spec: unknown, options: CompileOptions): ChartLayou
|
|
|
552
552
|
// chartArea was shrunk to exclude legend space, so expand it back to include
|
|
553
553
|
// the reserved margin. This way computeLegend positions the legend outside
|
|
554
554
|
// the data area (in the margin) instead of overlapping data marks.
|
|
555
|
+
//
|
|
556
|
+
// Top/bottom legends sit above/below the chart and aren't constrained by
|
|
557
|
+
// y-axis labels. Use the full container width so wrapping decisions match
|
|
558
|
+
// the first pass (which uses options.width). Without this, charts with wide
|
|
559
|
+
// y-axis labels (horizontal bars, long category names) artificially narrow
|
|
560
|
+
// the legend and trigger premature wrapping.
|
|
555
561
|
const legendArea: Rect = { ...chartArea };
|
|
556
562
|
if ('entries' in legendLayout && legendLayout.entries.length > 0) {
|
|
557
563
|
const gap = legendGap(options.width);
|
|
558
564
|
switch (legendLayout.position) {
|
|
559
565
|
case 'top':
|
|
566
|
+
legendArea.x = theme.spacing.padding;
|
|
567
|
+
legendArea.width = options.width - theme.spacing.padding * 2;
|
|
560
568
|
legendArea.y -= legendLayout.bounds.height + gap;
|
|
561
569
|
legendArea.height += legendLayout.bounds.height + gap;
|
|
562
570
|
break;
|
|
563
571
|
case 'bottom':
|
|
572
|
+
legendArea.x = theme.spacing.padding;
|
|
573
|
+
legendArea.width = options.width - theme.spacing.padding * 2;
|
|
564
574
|
// Bottom legend sits below the x-axis tick row, not over it. Expand
|
|
565
575
|
// legendArea by xAxisHeight + legendHeight + gap so the bottom-anchored
|
|
566
576
|
// legend lands beneath the axis. Mirrors dimensions.ts which reserved
|
|
@@ -468,6 +468,7 @@ export function flattenLayers(
|
|
|
468
468
|
parentEncoding?: Encoding,
|
|
469
469
|
parentTransforms?: import('@opendata-ai/openchart-core').Transform[],
|
|
470
470
|
parentWatermark?: boolean,
|
|
471
|
+
parentEndpointLabels?: boolean | import('@opendata-ai/openchart-core').EndpointLabelsConfig,
|
|
471
472
|
): ChartSpec[] {
|
|
472
473
|
const resolvedData = spec.data ?? parentData;
|
|
473
474
|
const resolvedEncoding: Encoding | undefined =
|
|
@@ -477,6 +478,7 @@ export function flattenLayers(
|
|
|
477
478
|
const resolvedTransforms = [...(parentTransforms ?? []), ...(spec.transform ?? [])];
|
|
478
479
|
// Layer-level watermark propagates to children (child can still override)
|
|
479
480
|
const resolvedWatermark = spec.watermark ?? parentWatermark;
|
|
481
|
+
const resolvedEndpointLabels = spec.endpointLabels ?? parentEndpointLabels;
|
|
480
482
|
|
|
481
483
|
const leaves: ChartSpec[] = [];
|
|
482
484
|
|
|
@@ -490,6 +492,7 @@ export function flattenLayers(
|
|
|
490
492
|
resolvedEncoding,
|
|
491
493
|
resolvedTransforms,
|
|
492
494
|
resolvedWatermark,
|
|
495
|
+
resolvedEndpointLabels,
|
|
493
496
|
),
|
|
494
497
|
);
|
|
495
498
|
} else {
|
|
@@ -509,6 +512,9 @@ export function flattenLayers(
|
|
|
509
512
|
...(child.watermark === undefined && resolvedWatermark !== undefined
|
|
510
513
|
? { watermark: resolvedWatermark }
|
|
511
514
|
: {}),
|
|
515
|
+
...(child.endpointLabels === undefined && resolvedEndpointLabels !== undefined
|
|
516
|
+
? { endpointLabels: resolvedEndpointLabels }
|
|
517
|
+
: {}),
|
|
512
518
|
} as ChartSpec);
|
|
513
519
|
}
|
|
514
520
|
}
|
package/src/layout/dimensions.ts
CHANGED
|
@@ -153,12 +153,13 @@ function getSparklinePad(spec: NormalizedChartSpec): {
|
|
|
153
153
|
point === 'last' || point === true || point === 'endpoints' || point === 'transparent';
|
|
154
154
|
const dotLeft =
|
|
155
155
|
point === 'first' || point === true || point === 'endpoints' || point === 'transparent';
|
|
156
|
+
const hasDots = dotRight || dotLeft;
|
|
156
157
|
|
|
157
158
|
return {
|
|
158
159
|
left: dotLeft ? Math.max(strokePad, dotPad) : strokePad,
|
|
159
160
|
right: dotRight ? Math.max(strokePad, dotPad) : strokePad,
|
|
160
|
-
top: strokePad,
|
|
161
|
-
bottom: strokePad,
|
|
161
|
+
top: hasDots ? Math.max(strokePad, dotPad) : strokePad,
|
|
162
|
+
bottom: hasDots ? Math.max(strokePad, dotPad) : strokePad,
|
|
162
163
|
};
|
|
163
164
|
}
|
|
164
165
|
|
|
@@ -361,8 +362,13 @@ export function computeDimensions(
|
|
|
361
362
|
// is kept; the rollback path subtracts it back when stripped.
|
|
362
363
|
const wantsMetrics = !!spec.metrics && spec.metrics.length > 0 && chromeMode !== 'hidden';
|
|
363
364
|
const tentativeMetricsHeight = wantsMetrics ? metricBarHeight() : 0;
|
|
365
|
+
// topAxisGap sits between the legend (or chrome, if no legend) and the
|
|
366
|
+
// chart area. It accounts for the general axis margin plus any inline
|
|
367
|
+
// tick-label overhang. Placing it after the legend (below) keeps the
|
|
368
|
+
// subtitle-to-legend gap tight while reserving physical space for ticks
|
|
369
|
+
// that protrude above the chart area.
|
|
364
370
|
const margins: Margins = {
|
|
365
|
-
top: topPad + chrome.topHeight + tentativeMetricsHeight
|
|
371
|
+
top: topPad + chrome.topHeight + tentativeMetricsHeight,
|
|
366
372
|
right: hPad + (isRadial ? hPad : axisMargin),
|
|
367
373
|
bottom: padding + chrome.bottomHeight + xAxisHeight,
|
|
368
374
|
left: hPad + (isRadial ? hPad : axisMargin),
|
|
@@ -609,6 +615,10 @@ export function computeDimensions(
|
|
|
609
615
|
// above.
|
|
610
616
|
}
|
|
611
617
|
|
|
618
|
+
// Add topAxisGap after legend so it sits between the legend (or chrome
|
|
619
|
+
// when there's no legend) and the chart area.
|
|
620
|
+
margins.top += topAxisGap;
|
|
621
|
+
|
|
612
622
|
// Chart area is what's left after margins
|
|
613
623
|
let chartArea: Rect = {
|
|
614
624
|
x: margins.left,
|
|
@@ -643,7 +653,7 @@ export function computeDimensions(
|
|
|
643
653
|
// until resolveMetrics decides otherwise).
|
|
644
654
|
const fallbackTopAxisGap =
|
|
645
655
|
isRadial && fallbackChrome.topHeight === 0 ? 0 : axisMargin + inlineTickOverhang;
|
|
646
|
-
const newTop = topPad + fallbackChrome.topHeight +
|
|
656
|
+
const newTop = topPad + fallbackChrome.topHeight + tentativeMetricsHeight;
|
|
647
657
|
const topDelta = margins.top - newTop;
|
|
648
658
|
const newBottom = padding + fallbackChrome.bottomHeight + xAxisHeight;
|
|
649
659
|
const bottomDelta = margins.bottom - newBottom;
|
|
@@ -656,7 +666,8 @@ export function computeDimensions(
|
|
|
656
666
|
legendLayout.entries.length > 0 &&
|
|
657
667
|
legendLayout.position === 'top'
|
|
658
668
|
? legendLayout.bounds.height + gap
|
|
659
|
-
: 0)
|
|
669
|
+
: 0) +
|
|
670
|
+
fallbackTopAxisGap;
|
|
660
671
|
margins.bottom = newBottom;
|
|
661
672
|
|
|
662
673
|
chartArea = {
|
|
@@ -709,8 +720,8 @@ export function computeDimensions(
|
|
|
709
720
|
// topPad
|
|
710
721
|
// chrome.topHeight (title / subtitle / eyebrow)
|
|
711
722
|
// tentativeMetricsHeight (KPI bar — placed here)
|
|
712
|
-
// topAxisGap (axisMargin + inlineTickOverhang)
|
|
713
723
|
// [optional top legend band]
|
|
724
|
+
// topAxisGap (axisMargin + inlineTickOverhang)
|
|
714
725
|
// chartArea
|
|
715
726
|
// The metric bar belongs with chrome, above the legend, so its y is
|
|
716
727
|
// computed off chrome.topHeight only — not the full legend-inclusive
|