@opendata-ai/openchart-engine 8.0.0-rc.11 → 8.0.0-rc.12

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 CHANGED
@@ -376,6 +376,13 @@ interface NormalizedChartSpec {
376
376
  data: DataRow[];
377
377
  encoding: Encoding;
378
378
  chrome: NormalizedChrome;
379
+ /**
380
+ * How chrome height interacts with the height budget. `'grow'` treats the
381
+ * container height as the plot budget and grows the SVG by the chrome height;
382
+ * `'subtract'` (default) shrinks the plot to fit chrome. Carried through from
383
+ * the spec so the layout pipeline can honor it.
384
+ */
385
+ chromeLayout?: 'subtract' | 'grow';
379
386
  /** Optional KPI metric cells, passed through unchanged. */
380
387
  metrics?: _opendata_ai_openchart_core.Metric[];
381
388
  /**
package/dist/index.js CHANGED
@@ -10482,6 +10482,7 @@ function normalizeChartSpec(spec, warnings) {
10482
10482
  data: spec.data,
10483
10483
  encoding,
10484
10484
  chrome: normalizeChrome(spec.chrome),
10485
+ chromeLayout: spec.chromeLayout,
10485
10486
  metrics: spec.metrics,
10486
10487
  seriesSearch: normalizeSeriesSearch(spec, encoding, warnings),
10487
10488
  youDrawIt: normalizeYouDrawIt(spec),
@@ -12348,6 +12349,36 @@ ${errorMessages}`);
12348
12349
  return { spec: normalized, warnings };
12349
12350
  }
12350
12351
 
12352
+ // src/layout/shared.ts
12353
+ function chromeToInput(chrome) {
12354
+ return {
12355
+ eyebrow: chrome.eyebrow,
12356
+ title: chrome.title,
12357
+ subtitle: chrome.subtitle,
12358
+ source: chrome.source,
12359
+ byline: chrome.byline,
12360
+ footer: chrome.footer,
12361
+ brand: chrome.brand
12362
+ };
12363
+ }
12364
+ function scalePadding(basePadding, width, height) {
12365
+ const minDim = Math.min(width, height);
12366
+ if (minDim >= 500) return basePadding;
12367
+ if (minDim <= 200) return Math.max(Math.round(basePadding * 0.5), 4);
12368
+ const t = (minDim - 200) / 300;
12369
+ return Math.max(Math.round(basePadding * (0.5 + t * 0.5)), 4);
12370
+ }
12371
+ function bottomMargin(bottomHeight, padding, xAxisHeight) {
12372
+ return padding + bottomHeight + xAxisHeight;
12373
+ }
12374
+ var INLINE_TICK_OVERHANG_PAD = 6;
12375
+ function resolveChromeLayout(spec, options) {
12376
+ const encoding = spec?.encoding;
12377
+ const isFaceted = !!(encoding?.facet || encoding?.row || encoding?.column);
12378
+ if (isFaceted) return "subtract";
12379
+ return spec?.chromeLayout ?? options?.chromeLayout ?? "subtract";
12380
+ }
12381
+
12351
12382
  // src/barlist/compile-barlist.ts
12352
12383
  var DEFAULT_ROW_GAP = 8;
12353
12384
  var LABEL_BAR_GAP = 12;
@@ -12395,14 +12426,19 @@ function compileBarList(spec, options) {
12395
12426
  watermark
12396
12427
  );
12397
12428
  const padding = theme.spacing.padding;
12429
+ const chromeLayout = resolveChromeLayout(
12430
+ spec,
12431
+ options
12432
+ );
12433
+ const grownHeight = chromeLayout === "grow" ? options.height + chrome.topHeight + chrome.bottomHeight : options.height;
12398
12434
  const fullArea = {
12399
12435
  x: padding,
12400
12436
  y: padding + chrome.topHeight,
12401
12437
  width: options.width - padding * 2,
12402
- height: options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
12438
+ height: chromeLayout === "grow" ? options.height - padding * 2 : options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
12403
12439
  };
12404
12440
  if (fullArea.width <= 0 || fullArea.height <= 0) {
12405
- return emptyLayout2(chrome, theme, options, watermark);
12441
+ return emptyLayout2(chrome, theme, { ...options, height: grownHeight }, watermark);
12406
12442
  }
12407
12443
  const labelField = barlistSpec.encoding.label.field;
12408
12444
  const valueField = barlistSpec.encoding.value.field;
@@ -12587,7 +12623,7 @@ function compileBarList(spec, options) {
12587
12623
  a11y,
12588
12624
  theme,
12589
12625
  width: options.width,
12590
- height: options.height,
12626
+ height: grownHeight,
12591
12627
  animation: resolvedAnimation,
12592
12628
  watermark,
12593
12629
  measureText
@@ -14881,30 +14917,6 @@ function computeAxes(scales, chartArea, strategy, theme, measureText, dataContex
14881
14917
  return result;
14882
14918
  }
14883
14919
 
14884
- // src/layout/shared.ts
14885
- function chromeToInput(chrome) {
14886
- return {
14887
- eyebrow: chrome.eyebrow,
14888
- title: chrome.title,
14889
- subtitle: chrome.subtitle,
14890
- source: chrome.source,
14891
- byline: chrome.byline,
14892
- footer: chrome.footer,
14893
- brand: chrome.brand
14894
- };
14895
- }
14896
- function scalePadding(basePadding, width, height) {
14897
- const minDim = Math.min(width, height);
14898
- if (minDim >= 500) return basePadding;
14899
- if (minDim <= 200) return Math.max(Math.round(basePadding * 0.5), 4);
14900
- const t = (minDim - 200) / 300;
14901
- return Math.max(Math.round(basePadding * (0.5 + t * 0.5)), 4);
14902
- }
14903
- function bottomMargin(bottomHeight, padding, xAxisHeight) {
14904
- return padding + bottomHeight + xAxisHeight;
14905
- }
14906
- var INLINE_TICK_OVERHANG_PAD = 6;
14907
-
14908
14920
  // src/layout/plan.ts
14909
14921
  function createMeasureFn(measureText) {
14910
14922
  if (measureText) {
@@ -15126,7 +15138,9 @@ function resolveLayoutPlan(chartSpec, renderSpec, options, theme, strategy, wate
15126
15138
  if (hasTopLegend) topMargin += TOP_LEGEND_GAP_ABOVE + legendContent.height + gap;
15127
15139
  topMargin += effectiveTopAxisGap;
15128
15140
  const bMargin = bottomMargin(chrome.bottomHeight, padding, xAxisExtent);
15129
- const chartHeight = Math.max(0, height - topMargin - bMargin);
15141
+ const chromeLayout = resolveChromeLayout(renderSpec, options);
15142
+ const effectiveHeight = chromeLayout === "grow" ? height + chrome.topHeight + chrome.bottomHeight : height;
15143
+ const chartHeight = Math.max(0, effectiveHeight - topMargin - bMargin);
15130
15144
  const rightMargin = hPad + (isRadial ? hPad : axisMargin);
15131
15145
  const rightAxisReserve = options.rightAxisReserve ?? 0;
15132
15146
  const provisionalArea = {
@@ -18006,11 +18020,15 @@ function compileMap(spec, options) {
18006
18020
  watermark
18007
18021
  );
18008
18022
  const padding = getBreakpoint(options.width) === "compact" ? Math.max(HPAD_COMPACT_MIN2, Math.round(theme.spacing.padding * HPAD_COMPACT_FRACTION2)) : theme.spacing.padding;
18023
+ const chromeLayout = resolveChromeLayout(
18024
+ spec,
18025
+ options
18026
+ );
18009
18027
  const fullArea = {
18010
18028
  x: padding,
18011
18029
  y: padding + chrome.topHeight,
18012
18030
  width: options.width - padding * 2,
18013
- height: options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
18031
+ height: chromeLayout === "grow" ? options.height - padding * 2 : options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
18014
18032
  };
18015
18033
  if (fullArea.width <= 0 || fullArea.height <= 0) {
18016
18034
  return emptyLayout3(chrome, theme, options, watermark);
@@ -19387,6 +19405,7 @@ function legendLayoutHasContent(legendLayout) {
19387
19405
  }
19388
19406
  var MIN_CHART_WIDTH = 60;
19389
19407
  var MIN_CHART_HEIGHT2 = 40;
19408
+ var PLOT_SHARE_MAX = 0.4;
19390
19409
  var SERIES_SEARCH_INPUT_HEIGHT = 32;
19391
19410
  var SERIES_SEARCH_GAP = 12;
19392
19411
  function getMinChartDims(display) {
@@ -19429,8 +19448,10 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
19429
19448
  watermark,
19430
19449
  bottomLegendReservation
19431
19450
  );
19451
+ const chromeLayout = resolveChromeLayout(spec, options);
19452
+ const effectiveHeight = chromeLayout === "grow" ? height + chrome.topHeight + chrome.bottomHeight : height;
19432
19453
  if (isSparkline) {
19433
- const total2 = { x: 0, y: 0, width, height };
19454
+ const total2 = { x: 0, y: 0, width, height: effectiveHeight };
19434
19455
  const sparkPad = getSparklinePad(spec);
19435
19456
  const xAxisSpace = userExplicit.xAxis ? 26 : 0;
19436
19457
  const yAxisSpace = userExplicit.yAxis ? 30 : 0;
@@ -19454,7 +19475,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
19454
19475
  x: margins2.left,
19455
19476
  y: margins2.top,
19456
19477
  width: Math.max(0, width - margins2.left - margins2.right),
19457
- height: Math.max(0, height - margins2.top - margins2.bottom)
19478
+ height: Math.max(0, effectiveHeight - margins2.top - margins2.bottom)
19458
19479
  };
19459
19480
  return {
19460
19481
  total: total2,
@@ -19466,7 +19487,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
19466
19487
  effectiveAxisGap: 0
19467
19488
  };
19468
19489
  }
19469
- const total = { x: 0, y: 0, width, height };
19490
+ const total = { x: 0, y: 0, width, height: effectiveHeight };
19470
19491
  const isRadial = isAxislessMark2(spec.markType);
19471
19492
  const encoding = spec.encoding;
19472
19493
  const xAxisSuppressed = encoding.x?.axis === false;
@@ -19696,10 +19717,11 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
19696
19717
  x: margins.left,
19697
19718
  y: margins.top,
19698
19719
  width: Math.max(0, width - margins.left - margins.right),
19699
- height: Math.max(0, height - margins.top - margins.bottom)
19720
+ height: Math.max(0, effectiveHeight - margins.top - margins.bottom)
19700
19721
  };
19701
19722
  const minDims = getMinChartDims(spec.display);
19702
- if ((chartArea.width < minDims.width || chartArea.height < minDims.height) && chromeMode !== "hidden") {
19723
+ const chromeEatsPlot = chromeLayout !== "grow" && chrome.topHeight > height * PLOT_SHARE_MAX;
19724
+ if ((chartArea.width < minDims.width || chartArea.height < minDims.height || chromeEatsPlot) && chromeMode !== "hidden") {
19703
19725
  const fallbackMode = chromeMode === "full" ? "compact" : "hidden";
19704
19726
  const fallbackChrome = computeChrome5(
19705
19727
  chromeToInput(spec.chrome),
@@ -19725,7 +19747,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
19725
19747
  x: margins.left,
19726
19748
  y: margins.top,
19727
19749
  width: Math.max(0, width - margins.left - margins.right),
19728
- height: Math.max(0, height - margins.top - margins.bottom)
19750
+ height: Math.max(0, effectiveHeight - margins.top - margins.bottom)
19729
19751
  };
19730
19752
  const fallbackMetricsTopY = topPad + fallbackChrome.topHeight;
19731
19753
  const fallbackMetricsArea = { x: hPad, width: Math.max(0, width - hPad * 2) };
@@ -19742,7 +19764,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
19742
19764
  chartArea = {
19743
19765
  ...chartArea,
19744
19766
  y: margins.top,
19745
- height: Math.max(0, height - margins.top - margins.bottom)
19767
+ height: Math.max(0, effectiveHeight - margins.top - margins.bottom)
19746
19768
  };
19747
19769
  }
19748
19770
  return {
@@ -19758,7 +19780,8 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
19758
19780
  fallbackMetricsArea
19759
19781
  ) : void 0,
19760
19782
  xAxisHeight,
19761
- effectiveAxisGap: fallbackEffectiveAxisGap
19783
+ effectiveAxisGap: fallbackEffectiveAxisGap,
19784
+ chromeStripped: true
19762
19785
  };
19763
19786
  }
19764
19787
  }
@@ -19770,7 +19793,7 @@ function computeDimensions(spec, options, legendLayout, theme, strategy, waterma
19770
19793
  chartArea = {
19771
19794
  ...chartArea,
19772
19795
  y: margins.top,
19773
- height: Math.max(0, height - margins.top - margins.bottom)
19796
+ height: Math.max(0, effectiveHeight - margins.top - margins.bottom)
19774
19797
  };
19775
19798
  }
19776
19799
  return {
@@ -20522,14 +20545,19 @@ function compileSankey(spec, options) {
20522
20545
  watermark
20523
20546
  );
20524
20547
  const padding = theme.spacing.padding;
20548
+ const chromeLayout = resolveChromeLayout(
20549
+ spec,
20550
+ options
20551
+ );
20552
+ const grownHeight = chromeLayout === "grow" ? options.height + chrome.topHeight + chrome.bottomHeight : options.height;
20525
20553
  const fullArea = {
20526
20554
  x: padding,
20527
20555
  y: padding + chrome.topHeight,
20528
20556
  width: options.width - padding * 2,
20529
- height: options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
20557
+ height: chromeLayout === "grow" ? options.height - padding * 2 : options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
20530
20558
  };
20531
20559
  if (fullArea.width <= 0 || fullArea.height <= 0) {
20532
- return emptyLayout4(fullArea, chrome, theme, options, watermark);
20560
+ return emptyLayout4(fullArea, chrome, theme, { ...options, height: grownHeight }, watermark);
20533
20561
  }
20534
20562
  const sourceField = sankeySpec.encoding.source.field;
20535
20563
  const targetField = sankeySpec.encoding.target.field;
@@ -20569,7 +20597,7 @@ function compileSankey(spec, options) {
20569
20597
  height: fullArea.height - legend.bounds.height - legendGap2
20570
20598
  };
20571
20599
  if (area.height <= 0) {
20572
- return emptyLayout4(area, chrome, theme, options, watermark);
20600
+ return emptyLayout4(area, chrome, theme, { ...options, height: grownHeight }, watermark);
20573
20601
  }
20574
20602
  const labelFontSize = theme.fonts.sizes.small;
20575
20603
  const labelFontWeight = theme.fonts.weights.normal;
@@ -20721,7 +20749,7 @@ function compileSankey(spec, options) {
20721
20749
  theme,
20722
20750
  dimensions: {
20723
20751
  width: options.width,
20724
- height: options.height
20752
+ height: grownHeight
20725
20753
  },
20726
20754
  animation: resolvedAnimation,
20727
20755
  watermark,
@@ -21540,6 +21568,10 @@ var LEGEND_ENTRY_GAP = 16;
21540
21568
  function clamp(value2, min4, max4) {
21541
21569
  return Math.min(max4, Math.max(min4, value2));
21542
21570
  }
21571
+ function growContentHeight(contentHeight, chromeLayout, budgetHeight, chrome) {
21572
+ if (chromeLayout !== "grow") return contentHeight;
21573
+ return Math.max(contentHeight, budgetHeight + chrome.topHeight + chrome.bottomHeight);
21574
+ }
21543
21575
  function compileTileMap(spec, options) {
21544
21576
  const sugarWarnings = [];
21545
21577
  const expandedSpec = spec && typeof spec === "object" && !Array.isArray(spec) ? expandSpecSugar(spec, sugarWarnings) : spec;
@@ -21576,22 +21608,44 @@ function compileTileMap(spec, options) {
21576
21608
  watermark
21577
21609
  );
21578
21610
  const padding = getBreakpoint2(options.width) === "compact" ? Math.max(HPAD_COMPACT_MIN4, Math.round(theme.spacing.padding * HPAD_COMPACT_FRACTION4)) : theme.spacing.padding;
21611
+ const chromeLayout = resolveChromeLayout(
21612
+ spec,
21613
+ options
21614
+ );
21579
21615
  const fullArea = {
21580
21616
  x: padding,
21581
21617
  y: padding + chrome.topHeight,
21582
21618
  width: options.width - padding * 2,
21583
- height: options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
21619
+ height: chromeLayout === "grow" ? options.height - padding * 2 : options.height - chrome.topHeight - chrome.bottomHeight - padding * 2
21584
21620
  };
21585
21621
  if (fullArea.width <= 0 || fullArea.height <= 0) {
21586
21622
  return emptyLayout5(chrome, theme, options, watermark);
21587
21623
  }
21588
21624
  const isCategorical = tilemapSpec.encoding.color !== void 0 || tilemapSpec.colors !== void 0;
21589
21625
  if (isCategorical) {
21590
- return compileCategorical(tilemapSpec, options, theme, chrome, fullArea, isDarkMode, watermark);
21626
+ return compileCategorical(
21627
+ tilemapSpec,
21628
+ options,
21629
+ theme,
21630
+ chrome,
21631
+ fullArea,
21632
+ isDarkMode,
21633
+ watermark,
21634
+ chromeLayout
21635
+ );
21591
21636
  }
21592
- return compileQuantitative(tilemapSpec, options, theme, chrome, fullArea, isDarkMode, watermark);
21637
+ return compileQuantitative(
21638
+ tilemapSpec,
21639
+ options,
21640
+ theme,
21641
+ chrome,
21642
+ fullArea,
21643
+ isDarkMode,
21644
+ watermark,
21645
+ chromeLayout
21646
+ );
21593
21647
  }
21594
- function compileQuantitative(tilemapSpec, options, theme, chrome, fullArea, isDarkMode, watermark) {
21648
+ function compileQuantitative(tilemapSpec, options, theme, chrome, fullArea, isDarkMode, watermark, chromeLayout) {
21595
21649
  const stateField = tilemapSpec.encoding.state.field;
21596
21650
  const valueField = tilemapSpec.encoding.value.field;
21597
21651
  const stateValueMap = /* @__PURE__ */ new Map();
@@ -21692,7 +21746,12 @@ function compileQuantitative(tilemapSpec, options, theme, chrome, fullArea, isDa
21692
21746
  };
21693
21747
  const resolvedAnimation = resolveAnimation(tilemapSpec.animation);
21694
21748
  const padding = fullArea.x;
21695
- const contentHeight = tileGridOffsetY + tilePositions.gridHeight + legendGap2 + legendTotalHeight + chrome.bottomHeight + padding;
21749
+ const contentHeight = growContentHeight(
21750
+ tileGridOffsetY + tilePositions.gridHeight + legendGap2 + legendTotalHeight + chrome.bottomHeight + padding,
21751
+ chromeLayout,
21752
+ options.height,
21753
+ chrome
21754
+ );
21696
21755
  return {
21697
21756
  area: fullArea,
21698
21757
  chrome,
@@ -21709,7 +21768,7 @@ function compileQuantitative(tilemapSpec, options, theme, chrome, fullArea, isDa
21709
21768
  measureText: options.measureText ?? ((text, fontSize) => ({ width: estimateTextWidth23(text, fontSize), height: fontSize }))
21710
21769
  };
21711
21770
  }
21712
- function compileCategorical(tilemapSpec, options, theme, chrome, fullArea, isDarkMode, watermark) {
21771
+ function compileCategorical(tilemapSpec, options, theme, chrome, fullArea, isDarkMode, watermark, chromeLayout) {
21713
21772
  const stateField = tilemapSpec.encoding.state.field;
21714
21773
  const colorField = tilemapSpec.encoding.color?.field ?? tilemapSpec.encoding.value.field;
21715
21774
  const stateCategoryMap = /* @__PURE__ */ new Map();
@@ -21828,7 +21887,12 @@ function compileCategorical(tilemapSpec, options, theme, chrome, fullArea, isDar
21828
21887
  };
21829
21888
  const resolvedAnimation = resolveAnimation(tilemapSpec.animation);
21830
21889
  const padding = fullArea.x;
21831
- const contentHeight = tileGridOffsetY + tilePositions.gridHeight + legendGap2 + legendTotalHeight + chrome.bottomHeight + padding;
21890
+ const contentHeight = growContentHeight(
21891
+ tileGridOffsetY + tilePositions.gridHeight + legendGap2 + legendTotalHeight + chrome.bottomHeight + padding,
21892
+ chromeLayout,
21893
+ options.height,
21894
+ chrome
21895
+ );
21832
21896
  return {
21833
21897
  area: fullArea,
21834
21898
  chrome,
@@ -23213,7 +23277,10 @@ function compileChart(spec, optionsInput) {
23213
23277
  skipY,
23214
23278
  markType: chartSpec.markType,
23215
23279
  totalWidth: options.width,
23216
- precomputedYTicks: plan.yTickValues.length > 0 ? plan.yTickValues : void 0
23280
+ // If the guardrail stripped chrome, the plan's y-ticks were sized
23281
+ // against the pre-strip (shorter) plot. Drop them so computeAxes
23282
+ // regenerates against the final (taller) chartArea.
23283
+ precomputedYTicks: !dims.chromeStripped && plan.yTickValues.length > 0 ? plan.yTickValues : void 0
23217
23284
  });
23218
23285
  const xAxisExtent = axes.x?.extent ?? 0;
23219
23286
  dims.chrome.bottomAnchorY = chartArea.y + chartArea.height + xAxisExtent;
@@ -23336,7 +23403,10 @@ function compileChart(spec, optionsInput) {
23336
23403
  theme,
23337
23404
  dimensions: {
23338
23405
  width: options.width,
23339
- height: options.height
23406
+ // In chromeLayout 'grow' mode, computeDimensions grows total.height by the
23407
+ // chrome height so the plot keeps its full budget. In the default
23408
+ // 'subtract' mode total.height === options.height, so this is a no-op.
23409
+ height: dims.total.height
23340
23410
  },
23341
23411
  animation: resolvedAnimation,
23342
23412
  watermark,