@opendata-ai/openchart-core 2.9.1 → 2.11.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 CHANGED
@@ -1,10 +1,15 @@
1
1
  /**
2
2
  * Responsive breakpoints and layout strategies.
3
3
  *
4
- * Three breakpoints based on container width:
4
+ * Width breakpoints:
5
5
  * - compact: < 400px (mobile, small embeds)
6
6
  * - medium: 400-700px (tablet, sidebars)
7
7
  * - full: > 700px (desktop, full-width)
8
+ *
9
+ * Height classes:
10
+ * - cramped: < 200px (dashboard widgets, thumbnails)
11
+ * - short: 200-350px (embedded panels, short containers)
12
+ * - normal: > 350px (standard containers)
8
13
  */
9
14
  /** Responsive breakpoint based on container width. */
10
15
  type Breakpoint = 'compact' | 'medium' | 'full';
@@ -12,6 +17,12 @@ type Breakpoint = 'compact' | 'medium' | 'full';
12
17
  * Determine the breakpoint for a given container width.
13
18
  */
14
19
  declare function getBreakpoint(width: number): Breakpoint;
20
+ /** Height classification based on container height. */
21
+ type HeightClass = 'cramped' | 'short' | 'normal';
22
+ /**
23
+ * Determine the height class for a given container height.
24
+ */
25
+ declare function getHeightClass(height: number): HeightClass;
15
26
  /** Label display mode at a given breakpoint. */
16
27
  type LabelMode = 'all' | 'important' | 'none';
17
28
  /** Legend position at a given breakpoint. */
@@ -20,9 +31,11 @@ type LegendPosition = 'top' | 'right' | 'bottom' | 'bottom-right' | 'inline';
20
31
  type AnnotationPosition = 'inline' | 'tooltip-only';
21
32
  /** Axis label density (controls tick count reduction). */
22
33
  type AxisLabelDensity = 'full' | 'reduced' | 'minimal';
34
+ /** Chrome display mode based on available height. */
35
+ type ChromeMode = 'full' | 'compact' | 'hidden';
23
36
  /**
24
37
  * Layout strategy defining how the visualization adapts to available space.
25
- * Returned by getLayoutStrategy() based on the current breakpoint.
38
+ * Returned by getLayoutStrategy() based on width breakpoint and height class.
26
39
  */
27
40
  interface LayoutStrategy {
28
41
  /** How data labels are displayed. */
@@ -33,15 +46,21 @@ interface LayoutStrategy {
33
46
  annotationPosition: AnnotationPosition;
34
47
  /** Axis tick density. */
35
48
  axisLabelDensity: AxisLabelDensity;
49
+ /** Chrome display mode: full, compact (title only), or hidden. */
50
+ chromeMode: ChromeMode;
51
+ /** Max fraction of container height for legend (0-1). -1 means unlimited. */
52
+ legendMaxHeight: number;
36
53
  }
37
54
  /**
38
- * Get the layout strategy for a given breakpoint.
55
+ * Get the layout strategy for a given breakpoint and height class.
39
56
  *
40
57
  * Compact: minimal chrome, no inline labels, legend on top, reduced axes.
41
58
  * Medium: moderate labels, legend on top, reduced axes.
42
59
  * Full: all labels, legend on right, full axes.
60
+ *
61
+ * Height constraints further reduce chrome and legend when container is short.
43
62
  */
44
- declare function getLayoutStrategy(breakpoint: Breakpoint): LayoutStrategy;
63
+ declare function getLayoutStrategy(breakpoint: Breakpoint, heightClass?: HeightClass): LayoutStrategy;
45
64
 
46
65
  /**
47
66
  * Table column configuration types.
@@ -1274,6 +1293,8 @@ interface LegendEntry {
1274
1293
  shape: 'circle' | 'square' | 'line';
1275
1294
  /** Whether this entry is currently highlighted/active. */
1276
1295
  active?: boolean;
1296
+ /** True for overflow indicator entries ("+N more"). Not interactive. */
1297
+ overflow?: boolean;
1277
1298
  }
1278
1299
  /** Resolved legend layout with position and entries. */
1279
1300
  interface LegendLayout {
@@ -1788,6 +1809,14 @@ declare function resolveTheme(userTheme?: ThemeConfig, base?: Theme): ResolvedTh
1788
1809
  *
1789
1810
  * Takes a Chrome spec + resolved theme and produces a ResolvedChrome
1790
1811
  * with computed text positions, styles, and total chrome heights.
1812
+ *
1813
+ * Supports three chrome modes:
1814
+ * - full: all chrome elements rendered at normal size
1815
+ * - compact: title only, no subtitle/source/byline/footer
1816
+ * - hidden: no chrome at all (maximizes chart area)
1817
+ *
1818
+ * Font sizes scale down continuously at narrow widths to keep
1819
+ * chrome proportional to the container.
1791
1820
  */
1792
1821
 
1793
1822
  /**
@@ -1800,17 +1829,11 @@ declare function resolveTheme(userTheme?: ThemeConfig, base?: Theme): ResolvedTh
1800
1829
  * @param theme - The fully resolved theme.
1801
1830
  * @param width - Total available width in pixels.
1802
1831
  * @param measureText - Optional real text measurement function from the adapter.
1832
+ * @param chromeMode - Chrome display mode: full, compact (title only), or hidden.
1833
+ * @param padding - Override padding (for scaled padding from dimensions).
1803
1834
  */
1804
- declare function computeChrome(chrome: Chrome | undefined, theme: ResolvedTheme, width: number, measureText?: MeasureTextFn): ResolvedChrome;
1835
+ declare function computeChrome(chrome: Chrome | undefined, theme: ResolvedTheme, width: number, measureText?: MeasureTextFn, chromeMode?: ChromeMode, padding?: number): ResolvedChrome;
1805
1836
 
1806
- /**
1807
- * Heuristic text measurement for environments without a DOM.
1808
- *
1809
- * These are intentionally approximate. Adapters can provide a real
1810
- * measureText function via CompileOptions for higher accuracy.
1811
- * The engine uses the real function when available, falls back to
1812
- * these heuristics when not.
1813
- */
1814
1837
  /**
1815
1838
  * Estimate the rendered width of a text string.
1816
1839
  *
@@ -1822,6 +1845,13 @@ declare function computeChrome(chrome: Chrome | undefined, theme: ResolvedTheme,
1822
1845
  * @param fontWeight - Font weight (100-900). Defaults to 400.
1823
1846
  */
1824
1847
  declare function estimateTextWidth(text: string, fontSize: number, fontWeight?: number): number;
1848
+ /**
1849
+ * Width reserved for the "OpenData" brand watermark in the bottom-right corner.
1850
+ * Accounts for ~8 chars at font size 20 with mixed 500/600 weight, plus a gap
1851
+ * so adjacent text doesn't crowd it. Used by chrome and legend layout to avoid
1852
+ * overlapping the brand.
1853
+ */
1854
+ declare const BRAND_RESERVE_WIDTH = 110;
1825
1855
 
1826
1856
  /**
1827
1857
  * Label collision detection and resolution.
@@ -1853,6 +1883,10 @@ interface LabelCandidate {
1853
1883
  /** Text style to apply. */
1854
1884
  style: TextStyle;
1855
1885
  }
1886
+ /**
1887
+ * Detect AABB (axis-aligned bounding box) overlap between two rectangles.
1888
+ */
1889
+ declare function detectCollision(a: Rect, b: Rect): boolean;
1856
1890
  /**
1857
1891
  * Resolve label collisions using a greedy placement algorithm.
1858
1892
  *
@@ -1865,6 +1899,11 @@ interface LabelCandidate {
1865
1899
  * @returns Array of resolved labels with computed positions and visibility.
1866
1900
  */
1867
1901
  declare function resolveCollisions(labels: LabelCandidate[]): ResolvedLabel[];
1902
+ /**
1903
+ * Compute the bounding rect of a resolved label from its position and text.
1904
+ * Uses heuristic text measurement so it works without DOM access.
1905
+ */
1906
+ declare function computeLabelBounds(label: ResolvedLabel): Rect;
1868
1907
 
1869
1908
  /**
1870
1909
  * Locale-aware number and date formatting utilities.
@@ -2113,4 +2152,4 @@ declare function scatterChart(data: DataRow[], x: FieldRef, y: FieldRef, options
2113
2152
  */
2114
2153
  declare function dataTable(data: DataRow[], options?: TableBuilderOptions): TableSpec;
2115
2154
 
2116
- export { type A11yMetadata, type AggregateOp, type Annotation, type AnnotationAnchor, type AnnotationOffset, type AnnotationPosition, type ArcMark, type AreaMark, type AxisConfig, type AxisLabelDensity, type AxisLayout, type AxisTick, type BarColumnConfig, type BarTableCell, type Breakpoint, CATEGORICAL_PALETTE, CHART_ENCODING_RULES, CHART_TYPES, type CategoricalPalette, type CategoryColorsConfig, type CategoryTableCell, type CellStyle, type ChannelRule, type ChartBuilderOptions, type ChartEventHandlers, type ChartLayout, type ChartSpec, type ChartSpecOverride, type ChartSpecWithoutData, type ChartType, type Chrome, type ChromeDefaults, type ChromeKey, type ChromeText, type ChromeTextStyle, type ColorBlindnessType, type ColumnConfig, type CompileOptions, type CompileTableOptions, DEFAULT_THEME, DIVERGING_PALETTES, type DarkMode, type DataRow, type DateGranularity, type DivergingPalette, type ElementEdit, type Encoding, type EncodingChannel, type EncodingRule, type FieldRef, type FieldType, type FlagTableCell, GRAPH_ENCODING_RULES, type GraphChannelRule, type GraphEdge, type GraphEdgeLayout, type GraphEncoding, type GraphEncodingChannel, type GraphLayout, type GraphLayoutConfig, type GraphNode, type GraphNodeLayout, type GraphSpec, type GraphSpecWithoutData, type Gridline, type HeatmapColumnConfig, type HeatmapTableCell, type ImageColumnConfig, type ImageTableCell, type LabelCandidate, type LabelConfig, type LabelDensity, type LabelMode, type LabelPriority, type LayoutStrategy, type LegendConfig, type LegendEntry, type LegendLayout, type LegendPosition, type LineMark, type Margins, type Mark, type MarkAria, type MarkEvent, type MeasureTextFn, type NodeOverride, type PaginationState, type Point, type PointMark, type RangeAnnotation, type Rect, type RectMark, type RefLineAnnotation, type ResolvedAnnotation, type ResolvedChrome, type ResolvedChromeElement, type ResolvedColumn, type ResolvedLabel, type ResolvedTheme, SEQUENTIAL_PALETTES, type ScaleConfig, type SequentialPalette, type SeriesStyle, type SortState, type SparklineColumnConfig, type SparklineData, type SparklineTableCell, type StoredVizSpec, type TableBuilderOptions, type TableCell, type TableCellBase, type TableLayout, type TableRow, type TableSpec, type TableSpecWithoutData, type TextAnnotation, type TextStyle, type TextTableCell, type Theme, type ThemeChromeDefaults, type ThemeColors, type ThemeConfig, type ThemeFontSizes, type ThemeFontWeights, type ThemeFonts, type ThemeSpacing, type TooltipContent, type TooltipField, type VizSpec, abbreviateNumber, adaptColorForDarkMode, adaptTheme, areaChart, barChart, buildD3Formatter, checkPaletteDistinguishability, columnChart, computeChrome, contrastRatio, dataTable, donutChart, dotChart, estimateTextWidth, findAccessibleColor, formatDate, formatNumber, generateAltText, generateAriaLabels, generateDataTable, getBreakpoint, getLayoutStrategy, inferFieldType, isChartSpec, isGraphSpec, isRangeAnnotation, isRefLineAnnotation, isTableSpec, isTextAnnotation, lineChart, meetsAA, pieChart, resolveCollisions, resolveTheme, scatterChart, simulateColorBlindness };
2155
+ export { type A11yMetadata, type AggregateOp, type Annotation, type AnnotationAnchor, type AnnotationOffset, type AnnotationPosition, type ArcMark, type AreaMark, type AxisConfig, type AxisLabelDensity, type AxisLayout, type AxisTick, BRAND_RESERVE_WIDTH, type BarColumnConfig, type BarTableCell, type Breakpoint, CATEGORICAL_PALETTE, CHART_ENCODING_RULES, CHART_TYPES, type CategoricalPalette, type CategoryColorsConfig, type CategoryTableCell, type CellStyle, type ChannelRule, type ChartBuilderOptions, type ChartEventHandlers, type ChartLayout, type ChartSpec, type ChartSpecOverride, type ChartSpecWithoutData, type ChartType, type Chrome, type ChromeDefaults, type ChromeKey, type ChromeMode, type ChromeText, type ChromeTextStyle, type ColorBlindnessType, type ColumnConfig, type CompileOptions, type CompileTableOptions, DEFAULT_THEME, DIVERGING_PALETTES, type DarkMode, type DataRow, type DateGranularity, type DivergingPalette, type ElementEdit, type Encoding, type EncodingChannel, type EncodingRule, type FieldRef, type FieldType, type FlagTableCell, GRAPH_ENCODING_RULES, type GraphChannelRule, type GraphEdge, type GraphEdgeLayout, type GraphEncoding, type GraphEncodingChannel, type GraphLayout, type GraphLayoutConfig, type GraphNode, type GraphNodeLayout, type GraphSpec, type GraphSpecWithoutData, type Gridline, type HeatmapColumnConfig, type HeatmapTableCell, type HeightClass, type ImageColumnConfig, type ImageTableCell, type LabelCandidate, type LabelConfig, type LabelDensity, type LabelMode, type LabelPriority, type LayoutStrategy, type LegendConfig, type LegendEntry, type LegendLayout, type LegendPosition, type LineMark, type Margins, type Mark, type MarkAria, type MarkEvent, type MeasureTextFn, type NodeOverride, type PaginationState, type Point, type PointMark, type RangeAnnotation, type Rect, type RectMark, type RefLineAnnotation, type ResolvedAnnotation, type ResolvedChrome, type ResolvedChromeElement, type ResolvedColumn, type ResolvedLabel, type ResolvedTheme, SEQUENTIAL_PALETTES, type ScaleConfig, type SequentialPalette, type SeriesStyle, type SortState, type SparklineColumnConfig, type SparklineData, type SparklineTableCell, type StoredVizSpec, type TableBuilderOptions, type TableCell, type TableCellBase, type TableLayout, type TableRow, type TableSpec, type TableSpecWithoutData, type TextAnnotation, type TextStyle, type TextTableCell, type Theme, type ThemeChromeDefaults, type ThemeColors, type ThemeConfig, type ThemeFontSizes, type ThemeFontWeights, type ThemeFonts, type ThemeSpacing, type TooltipContent, type TooltipField, type VizSpec, abbreviateNumber, adaptColorForDarkMode, adaptTheme, areaChart, barChart, buildD3Formatter, checkPaletteDistinguishability, columnChart, computeChrome, computeLabelBounds, contrastRatio, dataTable, detectCollision, donutChart, dotChart, estimateTextWidth, findAccessibleColor, formatDate, formatNumber, generateAltText, generateAriaLabels, generateDataTable, getBreakpoint, getHeightClass, getLayoutStrategy, inferFieldType, isChartSpec, isGraphSpec, isRangeAnnotation, isRefLineAnnotation, isTableSpec, isTextAnnotation, lineChart, meetsAA, pieChart, resolveCollisions, resolveTheme, scatterChart, simulateColorBlindness };
package/dist/index.js CHANGED
@@ -908,10 +908,14 @@ var WEIGHT_ADJUSTMENT = {
908
908
  800: 1.1,
909
909
  900: 1.12
910
910
  };
911
- function estimateTextWidth(text, fontSize, fontWeight = 400) {
911
+ function estimateCharWidth(fontSize, fontWeight = 400) {
912
912
  const weightFactor = WEIGHT_ADJUSTMENT[fontWeight] ?? 1;
913
- return text.length * fontSize * AVG_CHAR_WIDTH_RATIO * weightFactor;
913
+ return fontSize * AVG_CHAR_WIDTH_RATIO * weightFactor;
914
+ }
915
+ function estimateTextWidth(text, fontSize, fontWeight = 400) {
916
+ return text.length * estimateCharWidth(fontSize, fontWeight);
914
917
  }
918
+ var BRAND_RESERVE_WIDTH = 110;
915
919
  function estimateTextHeight(fontSize, lineCount = 1, lineHeight = 1.3) {
916
920
  return fontSize * lineHeight * lineCount;
917
921
  }
@@ -922,10 +926,19 @@ function normalizeChromeText(value) {
922
926
  if (typeof value === "string") return { text: value };
923
927
  return { text: value.text, style: value.style, offset: value.offset };
924
928
  }
925
- function buildTextStyle(defaults, fontFamily, textColor, overrides) {
929
+ function scaleFontSize(baseFontSize, width) {
930
+ if (width >= 500) return baseFontSize;
931
+ if (width <= 250) return Math.max(Math.round(baseFontSize * 0.72), 10);
932
+ const t = (width - 250) / 250;
933
+ return Math.max(Math.round(baseFontSize * (0.72 + t * 0.28)), 10);
934
+ }
935
+ function buildTextStyle(defaults, fontFamily, textColor, width, overrides) {
936
+ const hasExplicitSize = overrides?.fontSize !== void 0;
937
+ const baseFontSize = overrides?.fontSize ?? defaults.fontSize;
938
+ const fontSize = hasExplicitSize ? baseFontSize : scaleFontSize(baseFontSize, width);
926
939
  return {
927
940
  fontFamily: overrides?.fontFamily ?? fontFamily,
928
- fontSize: overrides?.fontSize ?? defaults.fontSize,
941
+ fontSize,
929
942
  fontWeight: overrides?.fontWeight ?? defaults.fontWeight,
930
943
  fill: overrides?.color ?? textColor ?? defaults.color,
931
944
  lineHeight: defaults.lineHeight,
@@ -933,26 +946,34 @@ function buildTextStyle(defaults, fontFamily, textColor, overrides) {
933
946
  dominantBaseline: "hanging"
934
947
  };
935
948
  }
936
- function measureWidth(text, style, measureText) {
937
- if (measureText) {
938
- return measureText(text, style.fontSize, style.fontWeight).width;
949
+ function estimateLineCount(text, style, maxWidth, _measureText) {
950
+ if (maxWidth <= 0) return 1;
951
+ const charWidth = estimateCharWidth(style.fontSize, style.fontWeight);
952
+ const maxChars = Math.floor(maxWidth / charWidth);
953
+ if (text.length <= maxChars) return 1;
954
+ const words = text.split(" ");
955
+ let lines = 1;
956
+ let current = "";
957
+ for (const word of words) {
958
+ const candidate = current ? `${current} ${word}` : word;
959
+ if (candidate.length > maxChars && current) {
960
+ lines++;
961
+ current = word;
962
+ } else {
963
+ current = candidate;
964
+ }
939
965
  }
940
- return estimateTextWidth(text, style.fontSize, style.fontWeight);
941
- }
942
- function estimateLineCount(text, style, maxWidth, measureText) {
943
- const fullWidth = measureWidth(text, style, measureText);
944
- if (fullWidth <= maxWidth) return 1;
945
- return Math.ceil(fullWidth / maxWidth);
966
+ return lines;
946
967
  }
947
- function computeChrome(chrome, theme, width, measureText) {
948
- if (!chrome) {
968
+ function computeChrome(chrome, theme, width, measureText, chromeMode = "full", padding) {
969
+ if (!chrome || chromeMode === "hidden") {
949
970
  return { topHeight: 0, bottomHeight: 0 };
950
971
  }
951
- const padding = theme.spacing.padding;
972
+ const pad2 = padding ?? theme.spacing.padding;
952
973
  const chromeGap = theme.spacing.chromeGap;
953
- const maxWidth = width - padding * 2;
974
+ const maxWidth = width - pad2 * 2;
954
975
  const fontFamily = theme.fonts.family;
955
- let topY = padding;
976
+ let topY = pad2;
956
977
  const topElements = {};
957
978
  const titleNorm = normalizeChromeText(chrome.title);
958
979
  if (titleNorm) {
@@ -960,12 +981,13 @@ function computeChrome(chrome, theme, width, measureText) {
960
981
  theme.chrome.title,
961
982
  fontFamily,
962
983
  theme.chrome.title.color,
984
+ width,
963
985
  titleNorm.style
964
986
  );
965
987
  const lineCount = estimateLineCount(titleNorm.text, style, maxWidth, measureText);
966
988
  const element = {
967
989
  text: titleNorm.text,
968
- x: padding + (titleNorm.offset?.dx ?? 0),
990
+ x: pad2 + (titleNorm.offset?.dx ?? 0),
969
991
  y: topY + (titleNorm.offset?.dy ?? 0),
970
992
  maxWidth,
971
993
  style
@@ -973,18 +995,19 @@ function computeChrome(chrome, theme, width, measureText) {
973
995
  topElements.title = element;
974
996
  topY += estimateTextHeight(style.fontSize, lineCount, style.lineHeight) + chromeGap;
975
997
  }
976
- const subtitleNorm = normalizeChromeText(chrome.subtitle);
998
+ const subtitleNorm = chromeMode === "compact" ? null : normalizeChromeText(chrome.subtitle);
977
999
  if (subtitleNorm) {
978
1000
  const style = buildTextStyle(
979
1001
  theme.chrome.subtitle,
980
1002
  fontFamily,
981
1003
  theme.chrome.subtitle.color,
1004
+ width,
982
1005
  subtitleNorm.style
983
1006
  );
984
1007
  const lineCount = estimateLineCount(subtitleNorm.text, style, maxWidth, measureText);
985
1008
  const element = {
986
1009
  text: subtitleNorm.text,
987
- x: padding + (subtitleNorm.offset?.dx ?? 0),
1010
+ x: pad2 + (subtitleNorm.offset?.dx ?? 0),
988
1011
  y: topY + (subtitleNorm.offset?.dy ?? 0),
989
1012
  maxWidth,
990
1013
  style
@@ -993,7 +1016,15 @@ function computeChrome(chrome, theme, width, measureText) {
993
1016
  topY += estimateTextHeight(style.fontSize, lineCount, style.lineHeight) + chromeGap;
994
1017
  }
995
1018
  const hasTopChrome = titleNorm || subtitleNorm;
996
- const topHeight = hasTopChrome ? topY - padding + theme.spacing.chromeToChart - chromeGap : 0;
1019
+ const topHeight = hasTopChrome ? topY - pad2 + theme.spacing.chromeToChart - chromeGap : 0;
1020
+ if (chromeMode === "compact") {
1021
+ return {
1022
+ topHeight,
1023
+ bottomHeight: 0,
1024
+ ...topElements
1025
+ };
1026
+ }
1027
+ const bottomMaxWidth = maxWidth - BRAND_RESERVE_WIDTH;
997
1028
  const bottomElements = {};
998
1029
  let bottomHeight = 0;
999
1030
  const bottomItems = [];
@@ -1024,21 +1055,27 @@ function computeChrome(chrome, theme, width, measureText) {
1024
1055
  if (bottomItems.length > 0) {
1025
1056
  bottomHeight += theme.spacing.chartToFooter;
1026
1057
  for (const item of bottomItems) {
1027
- const style = buildTextStyle(item.defaults, fontFamily, item.defaults.color, item.norm.style);
1028
- const lineCount = estimateLineCount(item.norm.text, style, maxWidth, measureText);
1058
+ const style = buildTextStyle(
1059
+ item.defaults,
1060
+ fontFamily,
1061
+ item.defaults.color,
1062
+ width,
1063
+ item.norm.style
1064
+ );
1065
+ const lineCount = estimateLineCount(item.norm.text, style, bottomMaxWidth, measureText);
1029
1066
  const height = estimateTextHeight(style.fontSize, lineCount, style.lineHeight);
1030
1067
  bottomElements[item.key] = {
1031
1068
  text: item.norm.text,
1032
- x: padding + (item.norm.offset?.dx ?? 0),
1069
+ x: pad2 + (item.norm.offset?.dx ?? 0),
1033
1070
  y: bottomHeight + (item.norm.offset?.dy ?? 0),
1034
1071
  // offset from where bottom chrome starts
1035
- maxWidth,
1072
+ maxWidth: bottomMaxWidth,
1036
1073
  style
1037
1074
  };
1038
1075
  bottomHeight += height + chromeGap;
1039
1076
  }
1040
1077
  bottomHeight -= chromeGap;
1041
- bottomHeight += padding;
1078
+ bottomHeight += pad2;
1042
1079
  }
1043
1080
  return {
1044
1081
  topHeight,
@@ -1056,31 +1093,65 @@ function getBreakpoint(width) {
1056
1093
  if (width <= BREAKPOINT_MEDIUM_MAX) return "medium";
1057
1094
  return "full";
1058
1095
  }
1059
- function getLayoutStrategy(breakpoint) {
1096
+ var HEIGHT_CRAMPED_MAX = 200;
1097
+ var HEIGHT_SHORT_MAX = 350;
1098
+ function getHeightClass(height) {
1099
+ if (height < HEIGHT_CRAMPED_MAX) return "cramped";
1100
+ if (height <= HEIGHT_SHORT_MAX) return "short";
1101
+ return "normal";
1102
+ }
1103
+ function getWidthStrategy(breakpoint) {
1060
1104
  switch (breakpoint) {
1061
1105
  case "compact":
1062
1106
  return {
1063
1107
  labelMode: "none",
1064
1108
  legendPosition: "top",
1065
1109
  annotationPosition: "tooltip-only",
1066
- axisLabelDensity: "minimal"
1110
+ axisLabelDensity: "minimal",
1111
+ chromeMode: "full",
1112
+ legendMaxHeight: -1
1067
1113
  };
1068
1114
  case "medium":
1069
1115
  return {
1070
1116
  labelMode: "important",
1071
1117
  legendPosition: "top",
1072
1118
  annotationPosition: "inline",
1073
- axisLabelDensity: "reduced"
1119
+ axisLabelDensity: "reduced",
1120
+ chromeMode: "full",
1121
+ legendMaxHeight: -1
1074
1122
  };
1075
1123
  case "full":
1076
1124
  return {
1077
1125
  labelMode: "all",
1078
1126
  legendPosition: "right",
1079
1127
  annotationPosition: "inline",
1080
- axisLabelDensity: "full"
1128
+ axisLabelDensity: "full",
1129
+ chromeMode: "full",
1130
+ legendMaxHeight: -1
1081
1131
  };
1082
1132
  }
1083
1133
  }
1134
+ function applyHeightConstraints(strategy, heightClass) {
1135
+ if (heightClass === "normal") return strategy;
1136
+ if (heightClass === "cramped") {
1137
+ return {
1138
+ ...strategy,
1139
+ chromeMode: "hidden",
1140
+ legendMaxHeight: 0,
1141
+ labelMode: "none",
1142
+ annotationPosition: "tooltip-only"
1143
+ };
1144
+ }
1145
+ return {
1146
+ ...strategy,
1147
+ chromeMode: "compact",
1148
+ legendMaxHeight: 0.15
1149
+ };
1150
+ }
1151
+ function getLayoutStrategy(breakpoint, heightClass = "normal") {
1152
+ const base = getWidthStrategy(breakpoint);
1153
+ return applyHeightConstraints(base, heightClass);
1154
+ }
1084
1155
 
1085
1156
  // src/labels/collision.ts
1086
1157
  var PRIORITY_ORDER = {
@@ -1166,6 +1237,19 @@ function resolveCollisions(labels) {
1166
1237
  }
1167
1238
  return results;
1168
1239
  }
1240
+ function computeLabelBounds(label) {
1241
+ const fontSize = label.style.fontSize;
1242
+ const fontWeight = label.style.fontWeight;
1243
+ const width = estimateTextWidth(label.text, fontSize, fontWeight);
1244
+ const height = fontSize * (label.style.lineHeight ?? 1.2);
1245
+ let x = label.x;
1246
+ if (label.style.textAnchor === "middle") {
1247
+ x = label.x - width / 2;
1248
+ } else if (label.style.textAnchor === "end") {
1249
+ x = label.x - width;
1250
+ }
1251
+ return { x, y: label.y, width, height };
1252
+ }
1169
1253
 
1170
1254
  // ../../node_modules/.bun/d3-format@3.1.2/node_modules/d3-format/src/formatDecimal.js
1171
1255
  function formatDecimal_default(x) {
@@ -2483,6 +2567,7 @@ function dataTable(data, options) {
2483
2567
  return spec;
2484
2568
  }
2485
2569
  export {
2570
+ BRAND_RESERVE_WIDTH,
2486
2571
  CATEGORICAL_PALETTE,
2487
2572
  CHART_ENCODING_RULES,
2488
2573
  CHART_TYPES,
@@ -2499,8 +2584,10 @@ export {
2499
2584
  checkPaletteDistinguishability,
2500
2585
  columnChart,
2501
2586
  computeChrome,
2587
+ computeLabelBounds,
2502
2588
  contrastRatio,
2503
2589
  dataTable,
2590
+ detectCollision,
2504
2591
  donutChart,
2505
2592
  dotChart,
2506
2593
  estimateTextWidth,
@@ -2511,6 +2598,7 @@ export {
2511
2598
  generateAriaLabels,
2512
2599
  generateDataTable,
2513
2600
  getBreakpoint,
2601
+ getHeightClass,
2514
2602
  getLayoutStrategy,
2515
2603
  inferFieldType,
2516
2604
  isChartSpec,