@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 +53 -14
- package/dist/index.js +119 -31
- package/dist/index.js.map +1 -1
- package/dist/styles.css +2 -0
- package/package.json +1 -1
- package/src/index.ts +5 -1
- package/src/labels/collision.ts +26 -0
- package/src/labels/index.ts +1 -1
- package/src/layout/__tests__/chrome.test.ts +17 -0
- package/src/layout/chrome.ts +90 -30
- package/src/layout/index.ts +6 -1
- package/src/layout/text-measure.ts +18 -2
- package/src/responsive/__tests__/breakpoints.test.ts +89 -1
- package/src/responsive/breakpoints.ts +89 -9
- package/src/responsive/index.ts +5 -0
- package/src/styles/viz.css +2 -0
- package/src/theme/__tests__/resolve.test.ts +57 -0
- package/src/types/layout.ts +2 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Responsive breakpoints and layout strategies.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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
|
|
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
|
|
911
|
+
function estimateCharWidth(fontSize, fontWeight = 400) {
|
|
912
912
|
const weightFactor = WEIGHT_ADJUSTMENT[fontWeight] ?? 1;
|
|
913
|
-
return
|
|
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
|
|
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
|
|
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
|
|
937
|
-
if (
|
|
938
|
-
|
|
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
|
|
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
|
|
972
|
+
const pad2 = padding ?? theme.spacing.padding;
|
|
952
973
|
const chromeGap = theme.spacing.chromeGap;
|
|
953
|
-
const maxWidth = width -
|
|
974
|
+
const maxWidth = width - pad2 * 2;
|
|
954
975
|
const fontFamily = theme.fonts.family;
|
|
955
|
-
let topY =
|
|
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:
|
|
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:
|
|
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 -
|
|
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(
|
|
1028
|
-
|
|
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:
|
|
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 +=
|
|
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
|
-
|
|
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,
|