@opendata-ai/openchart-core 6.23.1 → 6.24.1
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 +17 -4
- package/dist/index.js +27 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/colors/contrast.ts +35 -23
- package/src/index.ts +1 -0
- package/src/layout/chrome.ts +6 -2
- package/src/layout/index.ts +1 -0
- package/src/layout/text-measure.ts +10 -0
- package/src/layout/text-wrap.ts +2 -2
- package/src/types/layout.ts +2 -0
- package/src/types/spec.ts +8 -2
package/package.json
CHANGED
package/src/colors/contrast.ts
CHANGED
|
@@ -65,30 +65,42 @@ export function findAccessibleColor(baseColor: string, bg: string, targetRatio =
|
|
|
65
65
|
if (c == null) return baseColor;
|
|
66
66
|
|
|
67
67
|
const bgLum = relativeLuminance(bg);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
for (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
68
|
+
const baseLum = relativeLuminance(baseColor);
|
|
69
|
+
|
|
70
|
+
// Try both directions: prefer the one matching bg luminance, but fall back
|
|
71
|
+
// to the other if the base color is already at the extreme (e.g. white on
|
|
72
|
+
// a medium-luminance background can't be lightened, so darken instead).
|
|
73
|
+
const preferDarken = bgLum > 0.5;
|
|
74
|
+
const directions = preferDarken ? [true, false] : [false, true];
|
|
75
|
+
|
|
76
|
+
for (const darken of directions) {
|
|
77
|
+
// Skip impossible directions: can't lighten white or darken black.
|
|
78
|
+
if (!darken && baseLum > 0.95) continue;
|
|
79
|
+
if (darken && baseLum < 0.05) continue;
|
|
80
|
+
|
|
81
|
+
let lo = 0;
|
|
82
|
+
let hi = 1;
|
|
83
|
+
let best: string | null = null;
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < 20; i++) {
|
|
86
|
+
const mid = (lo + hi) / 2;
|
|
87
|
+
const adjusted = darken
|
|
88
|
+
? rgb(c.r * (1 - mid), c.g * (1 - mid), c.b * (1 - mid))
|
|
89
|
+
: rgb(c.r + (255 - c.r) * mid, c.g + (255 - c.g) * mid, c.b + (255 - c.b) * mid);
|
|
90
|
+
|
|
91
|
+
const hex = adjusted.formatHex();
|
|
92
|
+
const ratio = contrastRatio(hex, bg);
|
|
93
|
+
|
|
94
|
+
if (ratio >= targetRatio) {
|
|
95
|
+
best = hex;
|
|
96
|
+
hi = mid;
|
|
97
|
+
} else {
|
|
98
|
+
lo = mid;
|
|
99
|
+
}
|
|
90
100
|
}
|
|
101
|
+
|
|
102
|
+
if (best) return best;
|
|
91
103
|
}
|
|
92
104
|
|
|
93
|
-
return
|
|
105
|
+
return baseColor;
|
|
94
106
|
}
|
package/src/index.ts
CHANGED
package/src/layout/chrome.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
BRAND_FONT_SIZE,
|
|
27
27
|
BRAND_MIN_WIDTH,
|
|
28
28
|
BRAND_RESERVE_WIDTH,
|
|
29
|
+
COMPACT_WIDTH,
|
|
29
30
|
estimateCharWidth,
|
|
30
31
|
estimateTextHeight,
|
|
31
32
|
} from './text-measure';
|
|
@@ -236,9 +237,12 @@ export function computeChrome(
|
|
|
236
237
|
topY += estimateTextHeight(style.fontSize, lineCount, style.lineHeight) + chromeGap;
|
|
237
238
|
}
|
|
238
239
|
|
|
239
|
-
// Add chromeToChart gap if there are any top elements
|
|
240
|
+
// Add chromeToChart gap if there are any top elements. Tighten on narrow
|
|
241
|
+
// viewports so the subtitle doesn't float far above a legend or chart area.
|
|
240
242
|
const hasTopChrome = titleNorm || subtitleNorm;
|
|
241
|
-
const
|
|
243
|
+
const chromeToChart =
|
|
244
|
+
width < COMPACT_WIDTH ? Math.min(theme.spacing.chromeToChart, 2) : theme.spacing.chromeToChart;
|
|
245
|
+
const topHeight = hasTopChrome ? topY - pad + chromeToChart - chromeGap : 0;
|
|
242
246
|
|
|
243
247
|
// Bottom chrome text hidden in compact mode, but brand watermark still
|
|
244
248
|
// renders for wide-enough charts. Reserve space so it doesn't overflow.
|
package/src/layout/index.ts
CHANGED
|
@@ -73,6 +73,16 @@ export const BRAND_FONT_SIZE = 12;
|
|
|
73
73
|
/** Minimum chart width to render the brand watermark (px). */
|
|
74
74
|
export const BRAND_MIN_WIDTH = 120;
|
|
75
75
|
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Responsive breakpoints
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Width threshold below which layout tightens spacing (legend gaps, chrome
|
|
82
|
+
* padding, label gaps) to maximize data area on small screens.
|
|
83
|
+
*/
|
|
84
|
+
export const COMPACT_WIDTH = 420;
|
|
85
|
+
|
|
76
86
|
/**
|
|
77
87
|
* Estimate the rendered height of a text block.
|
|
78
88
|
*
|
package/src/layout/text-wrap.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* accurate wrapping. Otherwise falls back to a character-width
|
|
7
7
|
* heuristic driven by `estimateCharWidth` from text-measure.ts.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* the
|
|
9
|
+
* Non-browser callers (SSR, tests) omit the measureText argument to
|
|
10
|
+
* use the heuristic fallback. Do not change the signature without
|
|
11
11
|
* re-verifying visual baselines for every caller.
|
|
12
12
|
*/
|
|
13
13
|
|
package/src/types/layout.ts
CHANGED
|
@@ -1030,6 +1030,8 @@ export interface SankeyLayout {
|
|
|
1030
1030
|
animation?: ResolvedAnimation;
|
|
1031
1031
|
/** Whether the tryOpenData.ai watermark is enabled. */
|
|
1032
1032
|
watermark: boolean;
|
|
1033
|
+
/** Real text measurement function from the adapter (for accurate SVG text wrapping). */
|
|
1034
|
+
measureText?: MeasureTextFn;
|
|
1033
1035
|
}
|
|
1034
1036
|
|
|
1035
1037
|
// ---------------------------------------------------------------------------
|
package/src/types/spec.ts
CHANGED
|
@@ -523,6 +523,8 @@ interface AnnotationBase {
|
|
|
523
523
|
opacity?: number;
|
|
524
524
|
/** Z-index for render ordering. Higher values render on top. */
|
|
525
525
|
zIndex?: number;
|
|
526
|
+
/** When false, the annotation is always shown even at compact breakpoints. Default true. */
|
|
527
|
+
responsive?: boolean;
|
|
526
528
|
}
|
|
527
529
|
|
|
528
530
|
/**
|
|
@@ -588,13 +590,15 @@ export interface RangeAnnotation extends AnnotationBase {
|
|
|
588
590
|
* Useful for baselines (zero), targets, or thresholds.
|
|
589
591
|
*/
|
|
590
592
|
export interface RefLineAnnotation extends AnnotationBase {
|
|
591
|
-
type: 'refline';
|
|
593
|
+
type: 'refline' | 'rule';
|
|
592
594
|
/** X-axis value for a vertical reference line. */
|
|
593
595
|
x?: string | number;
|
|
594
596
|
/** Y-axis value for a horizontal reference line. */
|
|
595
597
|
y?: string | number;
|
|
596
598
|
/** Line style. */
|
|
597
599
|
style?: 'solid' | 'dashed' | 'dotted';
|
|
600
|
+
/** Raw SVG dash pattern override, e.g. [4, 4]. Takes precedence over style. */
|
|
601
|
+
strokeDash?: number[];
|
|
598
602
|
/** Line width in pixels. */
|
|
599
603
|
strokeWidth?: number;
|
|
600
604
|
/** Pixel offset for the reference line label. */
|
|
@@ -715,6 +719,8 @@ export interface LegendConfig {
|
|
|
715
719
|
symbolLimit?: number;
|
|
716
720
|
/** Maximum number of rows for top-positioned legends before truncation. Defaults to 2. */
|
|
717
721
|
maxRows?: number;
|
|
722
|
+
/** Series names to exclude from the legend. Excluded series still render in the chart. */
|
|
723
|
+
exclude?: string[];
|
|
718
724
|
}
|
|
719
725
|
|
|
720
726
|
// ---------------------------------------------------------------------------
|
|
@@ -1408,7 +1414,7 @@ export function isRangeAnnotation(annotation: Annotation): annotation is RangeAn
|
|
|
1408
1414
|
|
|
1409
1415
|
/** Check if an annotation is a RefLineAnnotation. */
|
|
1410
1416
|
export function isRefLineAnnotation(annotation: Annotation): annotation is RefLineAnnotation {
|
|
1411
|
-
return annotation.type === 'refline';
|
|
1417
|
+
return annotation.type === 'refline' || annotation.type === 'rule';
|
|
1412
1418
|
}
|
|
1413
1419
|
|
|
1414
1420
|
// ---------------------------------------------------------------------------
|