@opendata-ai/openchart-core 2.9.0 → 2.10.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,8 +1829,10 @@ 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
1837
  /**
1807
1838
  * Heuristic text measurement for environments without a DOM.
@@ -2113,4 +2144,4 @@ declare function scatterChart(data: DataRow[], x: FieldRef, y: FieldRef, options
2113
2144
  */
2114
2145
  declare function dataTable(data: DataRow[], options?: TableBuilderOptions): TableSpec;
2115
2146
 
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 };
2147
+ 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 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, contrastRatio, dataTable, 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
@@ -922,10 +922,19 @@ function normalizeChromeText(value) {
922
922
  if (typeof value === "string") return { text: value };
923
923
  return { text: value.text, style: value.style, offset: value.offset };
924
924
  }
925
- function buildTextStyle(defaults, fontFamily, textColor, overrides) {
925
+ function scaleFontSize(baseFontSize, width) {
926
+ if (width >= 500) return baseFontSize;
927
+ if (width <= 250) return Math.max(Math.round(baseFontSize * 0.72), 10);
928
+ const t = (width - 250) / 250;
929
+ return Math.max(Math.round(baseFontSize * (0.72 + t * 0.28)), 10);
930
+ }
931
+ function buildTextStyle(defaults, fontFamily, textColor, width, overrides) {
932
+ const hasExplicitSize = overrides?.fontSize !== void 0;
933
+ const baseFontSize = overrides?.fontSize ?? defaults.fontSize;
934
+ const fontSize = hasExplicitSize ? baseFontSize : scaleFontSize(baseFontSize, width);
926
935
  return {
927
936
  fontFamily: overrides?.fontFamily ?? fontFamily,
928
- fontSize: overrides?.fontSize ?? defaults.fontSize,
937
+ fontSize,
929
938
  fontWeight: overrides?.fontWeight ?? defaults.fontWeight,
930
939
  fill: overrides?.color ?? textColor ?? defaults.color,
931
940
  lineHeight: defaults.lineHeight,
@@ -944,15 +953,15 @@ function estimateLineCount(text, style, maxWidth, measureText) {
944
953
  if (fullWidth <= maxWidth) return 1;
945
954
  return Math.ceil(fullWidth / maxWidth);
946
955
  }
947
- function computeChrome(chrome, theme, width, measureText) {
948
- if (!chrome) {
956
+ function computeChrome(chrome, theme, width, measureText, chromeMode = "full", padding) {
957
+ if (!chrome || chromeMode === "hidden") {
949
958
  return { topHeight: 0, bottomHeight: 0 };
950
959
  }
951
- const padding = theme.spacing.padding;
960
+ const pad2 = padding ?? theme.spacing.padding;
952
961
  const chromeGap = theme.spacing.chromeGap;
953
- const maxWidth = width - padding * 2;
962
+ const maxWidth = width - pad2 * 2;
954
963
  const fontFamily = theme.fonts.family;
955
- let topY = padding;
964
+ let topY = pad2;
956
965
  const topElements = {};
957
966
  const titleNorm = normalizeChromeText(chrome.title);
958
967
  if (titleNorm) {
@@ -960,12 +969,13 @@ function computeChrome(chrome, theme, width, measureText) {
960
969
  theme.chrome.title,
961
970
  fontFamily,
962
971
  theme.chrome.title.color,
972
+ width,
963
973
  titleNorm.style
964
974
  );
965
975
  const lineCount = estimateLineCount(titleNorm.text, style, maxWidth, measureText);
966
976
  const element = {
967
977
  text: titleNorm.text,
968
- x: padding + (titleNorm.offset?.dx ?? 0),
978
+ x: pad2 + (titleNorm.offset?.dx ?? 0),
969
979
  y: topY + (titleNorm.offset?.dy ?? 0),
970
980
  maxWidth,
971
981
  style
@@ -973,18 +983,19 @@ function computeChrome(chrome, theme, width, measureText) {
973
983
  topElements.title = element;
974
984
  topY += estimateTextHeight(style.fontSize, lineCount, style.lineHeight) + chromeGap;
975
985
  }
976
- const subtitleNorm = normalizeChromeText(chrome.subtitle);
986
+ const subtitleNorm = chromeMode === "compact" ? null : normalizeChromeText(chrome.subtitle);
977
987
  if (subtitleNorm) {
978
988
  const style = buildTextStyle(
979
989
  theme.chrome.subtitle,
980
990
  fontFamily,
981
991
  theme.chrome.subtitle.color,
992
+ width,
982
993
  subtitleNorm.style
983
994
  );
984
995
  const lineCount = estimateLineCount(subtitleNorm.text, style, maxWidth, measureText);
985
996
  const element = {
986
997
  text: subtitleNorm.text,
987
- x: padding + (subtitleNorm.offset?.dx ?? 0),
998
+ x: pad2 + (subtitleNorm.offset?.dx ?? 0),
988
999
  y: topY + (subtitleNorm.offset?.dy ?? 0),
989
1000
  maxWidth,
990
1001
  style
@@ -993,7 +1004,14 @@ function computeChrome(chrome, theme, width, measureText) {
993
1004
  topY += estimateTextHeight(style.fontSize, lineCount, style.lineHeight) + chromeGap;
994
1005
  }
995
1006
  const hasTopChrome = titleNorm || subtitleNorm;
996
- const topHeight = hasTopChrome ? topY - padding + theme.spacing.chromeToChart - chromeGap : 0;
1007
+ const topHeight = hasTopChrome ? topY - pad2 + theme.spacing.chromeToChart - chromeGap : 0;
1008
+ if (chromeMode === "compact") {
1009
+ return {
1010
+ topHeight,
1011
+ bottomHeight: 0,
1012
+ ...topElements
1013
+ };
1014
+ }
997
1015
  const bottomElements = {};
998
1016
  let bottomHeight = 0;
999
1017
  const bottomItems = [];
@@ -1024,12 +1042,18 @@ function computeChrome(chrome, theme, width, measureText) {
1024
1042
  if (bottomItems.length > 0) {
1025
1043
  bottomHeight += theme.spacing.chartToFooter;
1026
1044
  for (const item of bottomItems) {
1027
- const style = buildTextStyle(item.defaults, fontFamily, item.defaults.color, item.norm.style);
1045
+ const style = buildTextStyle(
1046
+ item.defaults,
1047
+ fontFamily,
1048
+ item.defaults.color,
1049
+ width,
1050
+ item.norm.style
1051
+ );
1028
1052
  const lineCount = estimateLineCount(item.norm.text, style, maxWidth, measureText);
1029
1053
  const height = estimateTextHeight(style.fontSize, lineCount, style.lineHeight);
1030
1054
  bottomElements[item.key] = {
1031
1055
  text: item.norm.text,
1032
- x: padding + (item.norm.offset?.dx ?? 0),
1056
+ x: pad2 + (item.norm.offset?.dx ?? 0),
1033
1057
  y: bottomHeight + (item.norm.offset?.dy ?? 0),
1034
1058
  // offset from where bottom chrome starts
1035
1059
  maxWidth,
@@ -1038,7 +1062,7 @@ function computeChrome(chrome, theme, width, measureText) {
1038
1062
  bottomHeight += height + chromeGap;
1039
1063
  }
1040
1064
  bottomHeight -= chromeGap;
1041
- bottomHeight += padding;
1065
+ bottomHeight += pad2;
1042
1066
  }
1043
1067
  return {
1044
1068
  topHeight,
@@ -1056,31 +1080,65 @@ function getBreakpoint(width) {
1056
1080
  if (width <= BREAKPOINT_MEDIUM_MAX) return "medium";
1057
1081
  return "full";
1058
1082
  }
1059
- function getLayoutStrategy(breakpoint) {
1083
+ var HEIGHT_CRAMPED_MAX = 200;
1084
+ var HEIGHT_SHORT_MAX = 350;
1085
+ function getHeightClass(height) {
1086
+ if (height < HEIGHT_CRAMPED_MAX) return "cramped";
1087
+ if (height <= HEIGHT_SHORT_MAX) return "short";
1088
+ return "normal";
1089
+ }
1090
+ function getWidthStrategy(breakpoint) {
1060
1091
  switch (breakpoint) {
1061
1092
  case "compact":
1062
1093
  return {
1063
1094
  labelMode: "none",
1064
1095
  legendPosition: "top",
1065
1096
  annotationPosition: "tooltip-only",
1066
- axisLabelDensity: "minimal"
1097
+ axisLabelDensity: "minimal",
1098
+ chromeMode: "full",
1099
+ legendMaxHeight: -1
1067
1100
  };
1068
1101
  case "medium":
1069
1102
  return {
1070
1103
  labelMode: "important",
1071
1104
  legendPosition: "top",
1072
1105
  annotationPosition: "inline",
1073
- axisLabelDensity: "reduced"
1106
+ axisLabelDensity: "reduced",
1107
+ chromeMode: "full",
1108
+ legendMaxHeight: -1
1074
1109
  };
1075
1110
  case "full":
1076
1111
  return {
1077
1112
  labelMode: "all",
1078
1113
  legendPosition: "right",
1079
1114
  annotationPosition: "inline",
1080
- axisLabelDensity: "full"
1115
+ axisLabelDensity: "full",
1116
+ chromeMode: "full",
1117
+ legendMaxHeight: -1
1081
1118
  };
1082
1119
  }
1083
1120
  }
1121
+ function applyHeightConstraints(strategy, heightClass) {
1122
+ if (heightClass === "normal") return strategy;
1123
+ if (heightClass === "cramped") {
1124
+ return {
1125
+ ...strategy,
1126
+ chromeMode: "hidden",
1127
+ legendMaxHeight: 0,
1128
+ labelMode: "none",
1129
+ annotationPosition: "tooltip-only"
1130
+ };
1131
+ }
1132
+ return {
1133
+ ...strategy,
1134
+ chromeMode: "compact",
1135
+ legendMaxHeight: 0.15
1136
+ };
1137
+ }
1138
+ function getLayoutStrategy(breakpoint, heightClass = "normal") {
1139
+ const base = getWidthStrategy(breakpoint);
1140
+ return applyHeightConstraints(base, heightClass);
1141
+ }
1084
1142
 
1085
1143
  // src/labels/collision.ts
1086
1144
  var PRIORITY_ORDER = {
@@ -2511,6 +2569,7 @@ export {
2511
2569
  generateAriaLabels,
2512
2570
  generateDataTable,
2513
2571
  getBreakpoint,
2572
+ getHeightClass,
2514
2573
  getLayoutStrategy,
2515
2574
  inferFieldType,
2516
2575
  isChartSpec,