@opendata-ai/openchart-core 6.7.1 → 6.9.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 +30 -3
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/layout/__tests__/chrome.test.ts +35 -0
- package/src/layout/chrome.ts +11 -0
- package/src/types/spec.ts +30 -3
package/package.json
CHANGED
|
@@ -144,6 +144,41 @@ describe('computeChrome', () => {
|
|
|
144
144
|
expect(narrow.subtitle!.y).toBeGreaterThan(wide.subtitle!.y);
|
|
145
145
|
});
|
|
146
146
|
|
|
147
|
+
it('reserves extra height when subtitle contains newline characters', () => {
|
|
148
|
+
const chrome: Chrome = { title: 'Title', subtitle: 'Line one\nLine two' };
|
|
149
|
+
const withNewline = computeChrome(chrome, theme, 600);
|
|
150
|
+
|
|
151
|
+
const chromeNoNewline: Chrome = { title: 'Title', subtitle: 'Line one Line two' };
|
|
152
|
+
const withoutNewline = computeChrome(chromeNoNewline, theme, 600);
|
|
153
|
+
|
|
154
|
+
// The newline version should reserve more top height since it forces two lines
|
|
155
|
+
expect(withNewline.topHeight).toBeGreaterThan(withoutNewline.topHeight);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('handles consecutive newlines in chrome text', () => {
|
|
159
|
+
const chrome: Chrome = { title: 'Title', subtitle: 'Above\n\nBelow' };
|
|
160
|
+
const result = computeChrome(chrome, theme, 600);
|
|
161
|
+
|
|
162
|
+
// Three segments: "Above", "", "Below" -> 3 lines total
|
|
163
|
+
// This should be taller than a simple two-line subtitle
|
|
164
|
+
const twoLine: Chrome = { title: 'Title', subtitle: 'Above\nBelow' };
|
|
165
|
+
const twoLineResult = computeChrome(twoLine, theme, 600);
|
|
166
|
+
|
|
167
|
+
expect(result.topHeight).toBeGreaterThan(twoLineResult.topHeight);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('handles newlines combined with long text that also word-wraps', () => {
|
|
171
|
+
const longSegment = 'This is a very long segment that should wrap at narrow widths on its own';
|
|
172
|
+
const chrome: Chrome = { title: 'Title', subtitle: `${longSegment}\nShort` };
|
|
173
|
+
const result = computeChrome(chrome, theme, 300);
|
|
174
|
+
|
|
175
|
+
// At narrow width, the long segment wraps AND the \n adds another line
|
|
176
|
+
const noNewline: Chrome = { title: 'Title', subtitle: longSegment };
|
|
177
|
+
const noNewlineResult = computeChrome(noNewline, theme, 300);
|
|
178
|
+
|
|
179
|
+
expect(result.topHeight).toBeGreaterThan(noNewlineResult.topHeight);
|
|
180
|
+
});
|
|
181
|
+
|
|
147
182
|
it('uses measureText function when provided', () => {
|
|
148
183
|
const measureText = (text: string, fontSize: number) => ({
|
|
149
184
|
width: text.length * fontSize * 0.6,
|
package/src/layout/chrome.ts
CHANGED
|
@@ -91,6 +91,17 @@ function estimateLineCount(
|
|
|
91
91
|
): number {
|
|
92
92
|
if (maxWidth <= 0) return 1;
|
|
93
93
|
|
|
94
|
+
// Split on explicit newlines first, then estimate wrapping per segment
|
|
95
|
+
const segments = text.split('\n');
|
|
96
|
+
if (segments.length > 1) {
|
|
97
|
+
return segments.reduce((total, segment) => {
|
|
98
|
+
return (
|
|
99
|
+
total +
|
|
100
|
+
(segment.length === 0 ? 1 : estimateLineCount(segment, style, maxWidth, _measureText))
|
|
101
|
+
);
|
|
102
|
+
}, 0);
|
|
103
|
+
}
|
|
104
|
+
|
|
94
105
|
const charWidth = estimateCharWidth(style.fontSize, style.fontWeight);
|
|
95
106
|
const maxChars = Math.floor(maxWidth / charWidth);
|
|
96
107
|
|
package/src/types/spec.ts
CHANGED
|
@@ -483,9 +483,17 @@ export type Annotation = TextAnnotation | RangeAnnotation | RefLineAnnotation;
|
|
|
483
483
|
|
|
484
484
|
/**
|
|
485
485
|
* Dark mode behavior.
|
|
486
|
-
*
|
|
487
|
-
* - "
|
|
488
|
-
* -
|
|
486
|
+
*
|
|
487
|
+
* - `"auto"` - Checks the `prefers-color-scheme` media query to detect the
|
|
488
|
+
* user's system-level preference. This does NOT detect class-based dark mode
|
|
489
|
+
* toggles (e.g. `document.documentElement.classList.toggle('dark')`). If your
|
|
490
|
+
* app uses class-based dark mode, use VizThemeProvider with `"force"` or
|
|
491
|
+
* `"off"` instead of `"auto"` and toggle based on your app's state.
|
|
492
|
+
* - `"force"` - Always render in dark mode regardless of system preference.
|
|
493
|
+
* - `"off"` - Always render in light mode (default).
|
|
494
|
+
*
|
|
495
|
+
* All components (Chart, Sankey, Graph) inherit darkMode from VizThemeProvider
|
|
496
|
+
* when no explicit prop is passed.
|
|
489
497
|
*/
|
|
490
498
|
export type DarkMode = 'auto' | 'force' | 'off';
|
|
491
499
|
|
|
@@ -576,6 +584,8 @@ export interface LegendConfig {
|
|
|
576
584
|
columns?: number;
|
|
577
585
|
/** Max number of legend entries before truncation. Remaining entries show as "+N more". */
|
|
578
586
|
symbolLimit?: number;
|
|
587
|
+
/** Maximum number of rows for top-positioned legends before truncation. Defaults to 2. */
|
|
588
|
+
maxRows?: number;
|
|
579
589
|
}
|
|
580
590
|
|
|
581
591
|
// ---------------------------------------------------------------------------
|
|
@@ -916,6 +926,15 @@ export interface SankeySpec {
|
|
|
916
926
|
iterations?: number;
|
|
917
927
|
/** Link coloring strategy. Defaults to 'gradient'. */
|
|
918
928
|
linkStyle?: SankeyLinkColor;
|
|
929
|
+
/** Link fill opacity (0-1). Defaults to 0.5 in light mode, 0.75 in dark mode. */
|
|
930
|
+
linkOpacity?: number;
|
|
931
|
+
/**
|
|
932
|
+
* Which side of each node to place labels. 'auto' uses the default heuristic
|
|
933
|
+
* (left-column right, right-column left, middle right). 'right' forces all
|
|
934
|
+
* labels to the right of their nodes. 'left' forces all labels to the left.
|
|
935
|
+
* Defaults to 'auto'.
|
|
936
|
+
*/
|
|
937
|
+
nodeLabelAlign?: 'auto' | 'left' | 'right';
|
|
919
938
|
/** Editorial chrome (title, subtitle, source, byline, footer). */
|
|
920
939
|
chrome?: Chrome;
|
|
921
940
|
/** Legend display configuration. */
|
|
@@ -926,6 +945,14 @@ export interface SankeySpec {
|
|
|
926
945
|
darkMode?: DarkMode;
|
|
927
946
|
/** Animation configuration for entrance animations. */
|
|
928
947
|
animation?: AnimationSpec;
|
|
948
|
+
/**
|
|
949
|
+
* d3-format string applied to flow values in tooltips and ARIA labels.
|
|
950
|
+
* Uses the literal suffix extension: ".0f%" appends "%" to the formatted
|
|
951
|
+
* number (data value 28 renders as "28%"). For currency: "$,.0f" or "$~s".
|
|
952
|
+
* For SI suffixes: "~s" (renders 1000 as "1k"). When not set, values use
|
|
953
|
+
* the default number formatter.
|
|
954
|
+
*/
|
|
955
|
+
valueFormat?: string;
|
|
929
956
|
}
|
|
930
957
|
|
|
931
958
|
/**
|