@opendata-ai/openchart-core 7.1.2 → 7.1.4
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 +46 -12
- package/dist/index.js +129 -95
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/colors/__tests__/contrast.test.ts +32 -24
- package/src/colors/contrast.ts +18 -9
- package/src/index.ts +1 -0
- package/src/responsive/index.ts +1 -0
- package/src/responsive/metrics.ts +15 -0
- package/src/styles/chrome.css +20 -1
- package/src/theme/__tests__/dark-mode.test.ts +29 -0
- package/src/theme/__tests__/resolve.test.ts +10 -0
- package/src/theme/dark-mode.ts +43 -11
- package/src/theme/resolve.ts +11 -0
- package/src/types/spec.ts +20 -0
package/dist/index.d.ts
CHANGED
|
@@ -857,6 +857,26 @@ interface ThemeConfig {
|
|
|
857
857
|
};
|
|
858
858
|
/** Border radius for chart container and tooltips. */
|
|
859
859
|
borderRadius?: number;
|
|
860
|
+
/**
|
|
861
|
+
* Per-element chrome text color overrides. Font sizes and weights come
|
|
862
|
+
* from the typography scale and are not overridable here; only color is.
|
|
863
|
+
* An override survives dark-mode adaptation (adaptTheme preserves any
|
|
864
|
+
* chrome color the spec set explicitly).
|
|
865
|
+
*/
|
|
866
|
+
chrome?: {
|
|
867
|
+
/** Eyebrow (kicker) text color. */
|
|
868
|
+
eyebrow?: string;
|
|
869
|
+
/** Title text color. */
|
|
870
|
+
title?: string;
|
|
871
|
+
/** Subtitle text color. */
|
|
872
|
+
subtitle?: string;
|
|
873
|
+
/** Source/attribution text color. */
|
|
874
|
+
source?: string;
|
|
875
|
+
/** Byline text color. */
|
|
876
|
+
byline?: string;
|
|
877
|
+
/** Footer text color. */
|
|
878
|
+
footer?: string;
|
|
879
|
+
};
|
|
860
880
|
}
|
|
861
881
|
/**
|
|
862
882
|
* Label density mode controlling how many data labels are shown.
|
|
@@ -3587,12 +3607,6 @@ declare function simulateColorBlindness(color: string, type: ColorBlindnessType)
|
|
|
3587
3607
|
*/
|
|
3588
3608
|
declare function checkPaletteDistinguishability(colors: string[], type: ColorBlindnessType, minDistance?: number): boolean;
|
|
3589
3609
|
|
|
3590
|
-
/**
|
|
3591
|
-
* WCAG contrast ratio utilities.
|
|
3592
|
-
*
|
|
3593
|
-
* Uses d3-color for color space parsing and manipulation.
|
|
3594
|
-
* All functions accept CSS color strings (hex, rgb, hsl, named colors).
|
|
3595
|
-
*/
|
|
3596
3610
|
/**
|
|
3597
3611
|
* Compute the WCAG contrast ratio between two colors.
|
|
3598
3612
|
* Returns a value between 1 (identical) and 21 (black on white).
|
|
@@ -3601,13 +3615,23 @@ declare function contrastRatio(fg: string, bg: string): number;
|
|
|
3601
3615
|
/**
|
|
3602
3616
|
* Pick a legible label color (white or near-black) for text placed on top of `bg`.
|
|
3603
3617
|
*
|
|
3604
|
-
*
|
|
3605
|
-
*
|
|
3618
|
+
* Uses a perceptual luminance threshold rather than a strict 4.5:1 contrast
|
|
3619
|
+
* gate. WCAG's 4.5:1 ratio is calibrated for body text on a page; bold value
|
|
3620
|
+
* labels sitting on a saturated filled bar read fine at lower ratios. A pure
|
|
3621
|
+
* contrast-ratio gate sends mid-tone fills (e.g. slate `#94a3b8`, L≈0.36) to
|
|
3622
|
+
* dark text even though white reads cleaner on them.
|
|
3623
|
+
*
|
|
3624
|
+
* The threshold is mode-dependent because of simultaneous contrast: the same
|
|
3625
|
+
* bar fill looks lighter against a dark canvas than against a white page, so
|
|
3626
|
+
* dark text reads more grounded on it. Dark mode therefore uses a lower
|
|
3627
|
+
* threshold (L < 0.30) to pivot mid-tone fills to dark text sooner.
|
|
3606
3628
|
*
|
|
3607
|
-
*
|
|
3608
|
-
*
|
|
3629
|
+
* - Light mode (L < 0.42 → white): white on saturated and mid-tone fills;
|
|
3630
|
+
* dark text only on genuinely light fills (`#b0b0b0` and lighter).
|
|
3631
|
+
* - Dark mode (L < 0.30 → white): saturated fills keep white; mid-tone fills
|
|
3632
|
+
* (slate `#94a3b8`, cyan `#06b6d4`, mid-grey) pivot to dark text.
|
|
3609
3633
|
*/
|
|
3610
|
-
declare function pickLabelColor(bg: string): string;
|
|
3634
|
+
declare function pickLabelColor(bg: string, darkMode?: boolean): string;
|
|
3611
3635
|
/**
|
|
3612
3636
|
* Check if two colors meet WCAG AA contrast requirements.
|
|
3613
3637
|
* Normal text: 4.5:1, large text (18px+ bold or 24px+): 3:1.
|
|
@@ -3835,6 +3859,16 @@ declare const TICK_LABEL_OFFSET = 6;
|
|
|
3835
3859
|
* standard (non-compact) viewports. Omitted on compact viewports to save space.
|
|
3836
3860
|
*/
|
|
3837
3861
|
declare const AXIS_TITLE_TRAILING_PAD = 4;
|
|
3862
|
+
/**
|
|
3863
|
+
* Breathing room between the widest tick label's far edge and the rotated
|
|
3864
|
+
* y-axis title center. The title glyph extends ~halfGlyph (ceil(bodyFontSize/2))
|
|
3865
|
+
* from its center toward the tick labels, so visible clearance is
|
|
3866
|
+
* AXIS_TITLE_GAP - halfGlyph (~7px at default body size 13).
|
|
3867
|
+
*
|
|
3868
|
+
* Used in both the engine (dimensions.ts margin reservation) and the renderer
|
|
3869
|
+
* (axes.ts title placement). Both must agree on this value.
|
|
3870
|
+
*/
|
|
3871
|
+
declare const AXIS_TITLE_GAP = 14;
|
|
3838
3872
|
/**
|
|
3839
3873
|
* Width below which "narrow" adjustments apply: extra iOS Safari top padding and
|
|
3840
3874
|
* tighter category label gaps. Sits between compact (< 400) and medium (400–700).
|
|
@@ -4221,4 +4255,4 @@ interface TileMapBuilderOptions {
|
|
|
4221
4255
|
*/
|
|
4222
4256
|
declare function tileMap(data: Record<string, number | null> | DataRow[], options?: TileMapBuilderOptions): TileMapSpec;
|
|
4223
4257
|
|
|
4224
|
-
export { type A11yMetadata, AXIS_TITLE_OFFSET_COMPACT, AXIS_TITLE_OFFSET_DEFAULT, AXIS_TITLE_TRAILING_PAD, type AggregateOp, type AggregateTransform, type AnimationConfig, type AnimationEase, type AnimationPhaseConfig, type AnimationSpec, type AnimationStagger, type Annotation, type AnnotationAnchor, type AnnotationOffset, type AnnotationPosition, type ArcEncoding, type ArcMark, type AreaEncoding, type AreaMark, type AxisConfig, type AxisLabelDensity, type AxisLayout, type AxisTick, BRAND_FONT_SIZE, BRAND_MIN_WIDTH, BRAND_RESERVE_WIDTH, BREAKPOINT_COMPACT_MAX, BREAKPOINT_MEDIUM_MAX, type BarColumnConfig, type BarEncoding, type BarListEncoding, type BarListLayout, type BarListRowMark, type BarListSpec, type BarListSpecWithoutData, type BarTableCell, type BaseLegendLayout, type BinParams, type BinTransform, type Breakpoint, CATEGORICAL_PALETTE, CHART_ENCODING_RULES, CHART_TYPES, COMPACT_WIDTH, type CalculateExpression, type CalculateTransform, type CategoricalLegendLayout, 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 CircleEncoding, type ColorBlindnessType, type ColumnConfig, type CompileOptions, type CompileTableOptions, type Condition, type ConditionalValueDef, DEFAULT_THEME, DIVERGING_PALETTES, type DarkMode, type DataRow, type DateGranularity, type Display, type DivergingPalette, EXTENDED_OFFSET_STRATEGIES, type ElementEdit, type ElementRef, type Encoding, type EncodingChannel, type EncodingRule, type EndpointLabelEntry, type EndpointLabelsConfig, type EndpointLabelsLayout, type FieldPredicate, type FieldRef, type FieldType, type FilterPredicate, type FilterTransform, type FlagTableCell, type FoldTransform, GRAPH_ENCODING_RULES, type GradientColorStop, type GradientDef, type GradientLegendLayout, type GradientStop, type GraphChannelRule, type GraphEdge, type GraphEdgeLayout, type GraphEncoding, type GraphEncodingChannel, type GraphLayout, type GraphLayoutConfig, type GraphNode, type GraphNodeLayout, type GraphSpec, type GraphSpecWithoutData, type Gridline, HEIGHT_CRAMPED_MAX, HEIGHT_SHORT_MAX, HPAD_COMPACT_FRACTION, HPAD_COMPACT_MIN, type HeatmapColumnConfig, type HeatmapTableCell, type HeightClass, type ImageColumnConfig, type ImageTableCell, LABEL_GAP_COMPACT, LABEL_GAP_DEFAULT, LABEL_GAP_NARROW_MAX, type LabelCandidate, type LabelConfig, type LabelDensity, type LabelMode, type LabelPriority, type LabelSpec, type LayerSpec, type LayoutStrategy, type LegendConfig, type LegendEntry, type LegendLayout, type LegendPosition, type LineEncoding, type LineMark, type LinearGradient, type LogicalAnd, type LogicalNot, type LogicalOr, type LollipopEncoding, MARK_DISPLAY_NAMES, MARK_ENCODING_RULES, MARK_TYPES, MAX_LEFT_LABEL_FRACTION_COMPACT, MAX_LEFT_LABEL_FRACTION_DEFAULT, MAX_LEFT_LABEL_FRACTION_MEDIUM, MAX_LEFT_LABEL_FRACTION_MEDIUM_MAX, type Margins, type Mark, type MarkAria, type MarkDef, type MarkEvent, type MarkType, type MeasureTextFn, type Metric, NARROW_VIEWPORT_MAX, type NodeOverride, OFFSET_STRATEGIES, type OffsetStrategy, type PaginationState, type Point, type PointEncoding, type PointMark, type RadialGradient, type RangeAnnotation, type Rect, type RectEncoding, type RectMark, type RefLineAnnotation, type RelativeTimeRef, type ResolveConfig, type ResolveMode, type ResolvedAnimation, type ResolvedAnnotation, type ResolvedChrome, type ResolvedChromeElement, type ResolvedColumn, type ResolvedLabel, type ResolvedMetricBar, type ResolvedMetricCell, type ResolvedTheme, type RuleMarkLayout, SEQUENTIAL_PALETTES, type SankeyEncoding, type SankeyLayout, type SankeyLinkColor, type SankeyLinkMark, type SankeyNodeAlign, type SankeyNodeMark, type SankeySpec, type SankeySpecWithoutData, type ScaleConfig, type ScaleType, type SequentialPalette, type SeriesStyle, type SortState, type SparklineColumnConfig, type SparklineData, type SparklineTableCell, type StoredVizSpec, TICK_LABEL_OFFSET, TOP_PAD_EXTRA_NARROW, TOP_PAD_NARROW_MAX, type TableBuilderOptions, type TableCell, type TableCellBase, type TableLayout, type TableRow, type TableSpec, type TableSpecWithoutData, type TextAnnotation, type TextEncoding, type TextMarkLayout, type TextStyle, type TextTableCell, type Theme, type ThemeChromeDefaults, type ThemeColors, type ThemeConfig, type ThemeFontSizes, type ThemeFontWeights, type ThemeFonts, type ThemeSpacing, type TickEncoding, type TickMarkLayout, type TileMapBuilderOptions, type TileMapEncoding, type TileMapLayout, type TileMapPalette, type TileMapSpec, type TileMapSpecWithoutData, type TileMapTileMark, type TimeUnit, type TimeUnitTransform, type TooltipContent, type TooltipField, type Transform, type VizSpec, type WindowFieldDef, type WindowOp, type WindowSortField, type WindowTransform, abbreviateNumber, adaptColorForDarkMode, adaptTheme, areaChart, barChart, buildD3Formatter, buildTemporalFormatter, checkPaletteDistinguishability, columnChart, computeChrome, computeLabelBounds, contrastRatio, dataTable, detectCollision, donutChart, dotChart, elementRef, estimateTextWidth, findAccessibleColor, formatDate, formatNumber, generateAltText, generateAriaLabels, generateDataTable, getAxisTitleOffset, getBreakpoint, getHeightClass, getLayoutStrategy, getRepresentativeColor, inferFieldType, isBarListSpec, isChartSpec, isConditionalDef, isEncodingChannel, isGradientDef, isGraphSpec, isLayerSpec, isRangeAnnotation, isRefLineAnnotation, isSankeySpec, isTableSpec, isTextAnnotation, isTileMapSpec, lineChart, meetsAA, pickLabelColor, pieChart, resolveCollisions, resolveMarkDef, resolveMarkType, resolveTheme, scatterChart, simulateColorBlindness, tileMap, wrapText };
|
|
4258
|
+
export { type A11yMetadata, AXIS_TITLE_GAP, AXIS_TITLE_OFFSET_COMPACT, AXIS_TITLE_OFFSET_DEFAULT, AXIS_TITLE_TRAILING_PAD, type AggregateOp, type AggregateTransform, type AnimationConfig, type AnimationEase, type AnimationPhaseConfig, type AnimationSpec, type AnimationStagger, type Annotation, type AnnotationAnchor, type AnnotationOffset, type AnnotationPosition, type ArcEncoding, type ArcMark, type AreaEncoding, type AreaMark, type AxisConfig, type AxisLabelDensity, type AxisLayout, type AxisTick, BRAND_FONT_SIZE, BRAND_MIN_WIDTH, BRAND_RESERVE_WIDTH, BREAKPOINT_COMPACT_MAX, BREAKPOINT_MEDIUM_MAX, type BarColumnConfig, type BarEncoding, type BarListEncoding, type BarListLayout, type BarListRowMark, type BarListSpec, type BarListSpecWithoutData, type BarTableCell, type BaseLegendLayout, type BinParams, type BinTransform, type Breakpoint, CATEGORICAL_PALETTE, CHART_ENCODING_RULES, CHART_TYPES, COMPACT_WIDTH, type CalculateExpression, type CalculateTransform, type CategoricalLegendLayout, 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 CircleEncoding, type ColorBlindnessType, type ColumnConfig, type CompileOptions, type CompileTableOptions, type Condition, type ConditionalValueDef, DEFAULT_THEME, DIVERGING_PALETTES, type DarkMode, type DataRow, type DateGranularity, type Display, type DivergingPalette, EXTENDED_OFFSET_STRATEGIES, type ElementEdit, type ElementRef, type Encoding, type EncodingChannel, type EncodingRule, type EndpointLabelEntry, type EndpointLabelsConfig, type EndpointLabelsLayout, type FieldPredicate, type FieldRef, type FieldType, type FilterPredicate, type FilterTransform, type FlagTableCell, type FoldTransform, GRAPH_ENCODING_RULES, type GradientColorStop, type GradientDef, type GradientLegendLayout, type GradientStop, type GraphChannelRule, type GraphEdge, type GraphEdgeLayout, type GraphEncoding, type GraphEncodingChannel, type GraphLayout, type GraphLayoutConfig, type GraphNode, type GraphNodeLayout, type GraphSpec, type GraphSpecWithoutData, type Gridline, HEIGHT_CRAMPED_MAX, HEIGHT_SHORT_MAX, HPAD_COMPACT_FRACTION, HPAD_COMPACT_MIN, type HeatmapColumnConfig, type HeatmapTableCell, type HeightClass, type ImageColumnConfig, type ImageTableCell, LABEL_GAP_COMPACT, LABEL_GAP_DEFAULT, LABEL_GAP_NARROW_MAX, type LabelCandidate, type LabelConfig, type LabelDensity, type LabelMode, type LabelPriority, type LabelSpec, type LayerSpec, type LayoutStrategy, type LegendConfig, type LegendEntry, type LegendLayout, type LegendPosition, type LineEncoding, type LineMark, type LinearGradient, type LogicalAnd, type LogicalNot, type LogicalOr, type LollipopEncoding, MARK_DISPLAY_NAMES, MARK_ENCODING_RULES, MARK_TYPES, MAX_LEFT_LABEL_FRACTION_COMPACT, MAX_LEFT_LABEL_FRACTION_DEFAULT, MAX_LEFT_LABEL_FRACTION_MEDIUM, MAX_LEFT_LABEL_FRACTION_MEDIUM_MAX, type Margins, type Mark, type MarkAria, type MarkDef, type MarkEvent, type MarkType, type MeasureTextFn, type Metric, NARROW_VIEWPORT_MAX, type NodeOverride, OFFSET_STRATEGIES, type OffsetStrategy, type PaginationState, type Point, type PointEncoding, type PointMark, type RadialGradient, type RangeAnnotation, type Rect, type RectEncoding, type RectMark, type RefLineAnnotation, type RelativeTimeRef, type ResolveConfig, type ResolveMode, type ResolvedAnimation, type ResolvedAnnotation, type ResolvedChrome, type ResolvedChromeElement, type ResolvedColumn, type ResolvedLabel, type ResolvedMetricBar, type ResolvedMetricCell, type ResolvedTheme, type RuleMarkLayout, SEQUENTIAL_PALETTES, type SankeyEncoding, type SankeyLayout, type SankeyLinkColor, type SankeyLinkMark, type SankeyNodeAlign, type SankeyNodeMark, type SankeySpec, type SankeySpecWithoutData, type ScaleConfig, type ScaleType, type SequentialPalette, type SeriesStyle, type SortState, type SparklineColumnConfig, type SparklineData, type SparklineTableCell, type StoredVizSpec, TICK_LABEL_OFFSET, TOP_PAD_EXTRA_NARROW, TOP_PAD_NARROW_MAX, type TableBuilderOptions, type TableCell, type TableCellBase, type TableLayout, type TableRow, type TableSpec, type TableSpecWithoutData, type TextAnnotation, type TextEncoding, type TextMarkLayout, type TextStyle, type TextTableCell, type Theme, type ThemeChromeDefaults, type ThemeColors, type ThemeConfig, type ThemeFontSizes, type ThemeFontWeights, type ThemeFonts, type ThemeSpacing, type TickEncoding, type TickMarkLayout, type TileMapBuilderOptions, type TileMapEncoding, type TileMapLayout, type TileMapPalette, type TileMapSpec, type TileMapSpecWithoutData, type TileMapTileMark, type TimeUnit, type TimeUnitTransform, type TooltipContent, type TooltipField, type Transform, type VizSpec, type WindowFieldDef, type WindowOp, type WindowSortField, type WindowTransform, abbreviateNumber, adaptColorForDarkMode, adaptTheme, areaChart, barChart, buildD3Formatter, buildTemporalFormatter, checkPaletteDistinguishability, columnChart, computeChrome, computeLabelBounds, contrastRatio, dataTable, detectCollision, donutChart, dotChart, elementRef, estimateTextWidth, findAccessibleColor, formatDate, formatNumber, generateAltText, generateAriaLabels, generateDataTable, getAxisTitleOffset, getBreakpoint, getHeightClass, getLayoutStrategy, getRepresentativeColor, inferFieldType, isBarListSpec, isChartSpec, isConditionalDef, isEncodingChannel, isGradientDef, isGraphSpec, isLayerSpec, isRangeAnnotation, isRefLineAnnotation, isSankeySpec, isTableSpec, isTextAnnotation, isTileMapSpec, lineChart, meetsAA, pickLabelColor, pieChart, resolveCollisions, resolveMarkDef, resolveMarkType, resolveTheme, scatterChart, simulateColorBlindness, tileMap, wrapText };
|
package/dist/index.js
CHANGED
|
@@ -661,10 +661,11 @@ function contrastRatio(fg, bg) {
|
|
|
661
661
|
const darker2 = Math.min(l1, l2);
|
|
662
662
|
return (lighter + 0.05) / (darker2 + 0.05);
|
|
663
663
|
}
|
|
664
|
-
function pickLabelColor(bg) {
|
|
664
|
+
function pickLabelColor(bg, darkMode = false) {
|
|
665
665
|
const WHITE = "#ffffff";
|
|
666
666
|
const DARK = "#111111";
|
|
667
|
-
|
|
667
|
+
const threshold = darkMode ? 0.3 : 0.42;
|
|
668
|
+
return relativeLuminance(bg) < threshold ? WHITE : DARK;
|
|
668
669
|
}
|
|
669
670
|
function meetsAA(fg, bg, largeText = false) {
|
|
670
671
|
const ratio = contrastRatio(fg, bg);
|
|
@@ -812,99 +813,6 @@ var DIVERGING_PALETTES = {
|
|
|
812
813
|
brownTeal: [...DIVERGING_BROWN_TEAL.stops]
|
|
813
814
|
};
|
|
814
815
|
|
|
815
|
-
// src/theme/dark-mode.ts
|
|
816
|
-
var DARK_BG = ACHROMATIC_RAMP.bg;
|
|
817
|
-
var DARK_TEXT = ACHROMATIC_RAMP.fg;
|
|
818
|
-
function adaptColorForDarkMode(color2, lightBg, darkBg) {
|
|
819
|
-
if (rgb(color2) == null) {
|
|
820
|
-
if (typeof console !== "undefined" && console.warn) {
|
|
821
|
-
console.warn(
|
|
822
|
-
`[openchart] adaptColorForDarkMode: unparseable color "${color2}", returning unchanged. Use precomputed sRGB hex.`
|
|
823
|
-
);
|
|
824
|
-
}
|
|
825
|
-
return color2;
|
|
826
|
-
}
|
|
827
|
-
const originalRatio = contrastRatio(color2, lightBg);
|
|
828
|
-
const c = hsl(color2);
|
|
829
|
-
if (c == null || Number.isNaN(c.h)) {
|
|
830
|
-
const r = rgb(color2);
|
|
831
|
-
if (r == null) return color2;
|
|
832
|
-
const darkBgLum = _luminanceFromHex(darkBg);
|
|
833
|
-
const isLight = darkBgLum < 0.5;
|
|
834
|
-
if (isLight) return color2;
|
|
835
|
-
const inverted = hsl(color2);
|
|
836
|
-
if (inverted == null) return color2;
|
|
837
|
-
inverted.l = 1 - inverted.l;
|
|
838
|
-
return inverted.formatHex();
|
|
839
|
-
}
|
|
840
|
-
let lo = 0;
|
|
841
|
-
let hi = 1;
|
|
842
|
-
let bestColor = color2;
|
|
843
|
-
let bestDiff = Infinity;
|
|
844
|
-
for (let i = 0; i < 20; i++) {
|
|
845
|
-
const mid = (lo + hi) / 2;
|
|
846
|
-
const candidate = hsl(c.h, c.s, mid);
|
|
847
|
-
const hex2 = candidate.formatHex();
|
|
848
|
-
const ratio = contrastRatio(hex2, darkBg);
|
|
849
|
-
const diff = Math.abs(ratio - originalRatio);
|
|
850
|
-
if (diff < bestDiff) {
|
|
851
|
-
bestDiff = diff;
|
|
852
|
-
bestColor = hex2;
|
|
853
|
-
}
|
|
854
|
-
if (ratio < originalRatio) {
|
|
855
|
-
lo = mid;
|
|
856
|
-
} else {
|
|
857
|
-
hi = mid;
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
return bestColor;
|
|
861
|
-
}
|
|
862
|
-
function _luminanceFromHex(color2) {
|
|
863
|
-
const c = rgb(color2);
|
|
864
|
-
if (c == null) return 0;
|
|
865
|
-
return (0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) / 255;
|
|
866
|
-
}
|
|
867
|
-
function adaptTheme(theme) {
|
|
868
|
-
const inputBg = theme.colors.background;
|
|
869
|
-
const isTransparent = inputBg === "transparent";
|
|
870
|
-
const alreadyDark = isTransparent || _luminanceFromHex(inputBg) < 0.2;
|
|
871
|
-
const darkBg = alreadyDark ? inputBg : DARK_BG;
|
|
872
|
-
const darkText = DARK_TEXT;
|
|
873
|
-
const darkGridline = "rgba(255,255,255,0.05)";
|
|
874
|
-
const darkAxis = "#a1a1aa";
|
|
875
|
-
const darkMuted = ACHROMATIC_RAMP.fgMuted;
|
|
876
|
-
const categorical = theme.colors.categorical;
|
|
877
|
-
return {
|
|
878
|
-
...theme,
|
|
879
|
-
isDark: true,
|
|
880
|
-
colors: {
|
|
881
|
-
...theme.colors,
|
|
882
|
-
background: darkBg,
|
|
883
|
-
text: darkText,
|
|
884
|
-
gridline: darkGridline,
|
|
885
|
-
axis: darkAxis,
|
|
886
|
-
annotationFill: "rgba(255,255,255,0.06)",
|
|
887
|
-
annotationText: darkMuted,
|
|
888
|
-
categorical,
|
|
889
|
-
// Sparkline trend colors tuned for dark surfaces: teal-leaning green
|
|
890
|
-
// and coral red read better than the saturated light-mode tokens.
|
|
891
|
-
// Any non-default value is treated as a user override and preserved.
|
|
892
|
-
positive: theme.colors.positive !== "#16a34a" ? theme.colors.positive : "#34d399",
|
|
893
|
-
negative: theme.colors.negative !== "#dc2626" ? theme.colors.negative : "#f87171"
|
|
894
|
-
},
|
|
895
|
-
chrome: {
|
|
896
|
-
// Eyebrow keeps its accent tint (cyan in both modes); the other
|
|
897
|
-
// chrome elements desaturate to a muted gray on the dark canvas.
|
|
898
|
-
eyebrow: theme.chrome.eyebrow,
|
|
899
|
-
title: { ...theme.chrome.title, color: darkText },
|
|
900
|
-
subtitle: { ...theme.chrome.subtitle, color: darkMuted },
|
|
901
|
-
source: { ...theme.chrome.source, color: darkMuted },
|
|
902
|
-
byline: { ...theme.chrome.byline, color: darkMuted },
|
|
903
|
-
footer: { ...theme.chrome.footer, color: darkMuted }
|
|
904
|
-
}
|
|
905
|
-
};
|
|
906
|
-
}
|
|
907
|
-
|
|
908
816
|
// src/theme/defaults.ts
|
|
909
817
|
var DEFAULT_THEME = {
|
|
910
818
|
colors: {
|
|
@@ -988,6 +896,123 @@ var DEFAULT_THEME = {
|
|
|
988
896
|
}
|
|
989
897
|
};
|
|
990
898
|
|
|
899
|
+
// src/theme/dark-mode.ts
|
|
900
|
+
var DARK_BG = ACHROMATIC_RAMP.bg;
|
|
901
|
+
var DARK_TEXT = ACHROMATIC_RAMP.fg;
|
|
902
|
+
function adaptColorForDarkMode(color2, lightBg, darkBg) {
|
|
903
|
+
if (rgb(color2) == null) {
|
|
904
|
+
if (typeof console !== "undefined" && console.warn) {
|
|
905
|
+
console.warn(
|
|
906
|
+
`[openchart] adaptColorForDarkMode: unparseable color "${color2}", returning unchanged. Use precomputed sRGB hex.`
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
return color2;
|
|
910
|
+
}
|
|
911
|
+
const originalRatio = contrastRatio(color2, lightBg);
|
|
912
|
+
const c = hsl(color2);
|
|
913
|
+
if (c == null || Number.isNaN(c.h)) {
|
|
914
|
+
const r = rgb(color2);
|
|
915
|
+
if (r == null) return color2;
|
|
916
|
+
const darkBgLum = _luminanceFromHex(darkBg);
|
|
917
|
+
const isLight = darkBgLum < 0.5;
|
|
918
|
+
if (isLight) return color2;
|
|
919
|
+
const inverted = hsl(color2);
|
|
920
|
+
if (inverted == null) return color2;
|
|
921
|
+
inverted.l = 1 - inverted.l;
|
|
922
|
+
return inverted.formatHex();
|
|
923
|
+
}
|
|
924
|
+
let lo = 0;
|
|
925
|
+
let hi = 1;
|
|
926
|
+
let bestColor = color2;
|
|
927
|
+
let bestDiff = Infinity;
|
|
928
|
+
for (let i = 0; i < 20; i++) {
|
|
929
|
+
const mid = (lo + hi) / 2;
|
|
930
|
+
const candidate = hsl(c.h, c.s, mid);
|
|
931
|
+
const hex2 = candidate.formatHex();
|
|
932
|
+
const ratio = contrastRatio(hex2, darkBg);
|
|
933
|
+
const diff = Math.abs(ratio - originalRatio);
|
|
934
|
+
if (diff < bestDiff) {
|
|
935
|
+
bestDiff = diff;
|
|
936
|
+
bestColor = hex2;
|
|
937
|
+
}
|
|
938
|
+
if (ratio < originalRatio) {
|
|
939
|
+
lo = mid;
|
|
940
|
+
} else {
|
|
941
|
+
hi = mid;
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return bestColor;
|
|
945
|
+
}
|
|
946
|
+
function _luminanceFromHex(color2) {
|
|
947
|
+
const c = rgb(color2);
|
|
948
|
+
if (c == null) return 0;
|
|
949
|
+
return (0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) / 255;
|
|
950
|
+
}
|
|
951
|
+
function adaptTheme(theme) {
|
|
952
|
+
const inputBg = theme.colors.background;
|
|
953
|
+
const isTransparent = inputBg === "transparent";
|
|
954
|
+
const alreadyDark = isTransparent || _luminanceFromHex(inputBg) < 0.2;
|
|
955
|
+
const darkBg = alreadyDark ? inputBg : DARK_BG;
|
|
956
|
+
const darkMuted = ACHROMATIC_RAMP.fgMuted;
|
|
957
|
+
const light = DEFAULT_THEME.colors;
|
|
958
|
+
const lightChrome = DEFAULT_THEME.chrome;
|
|
959
|
+
const overridden = (current, lightDefault, darkDefault) => current === lightDefault ? darkDefault : current;
|
|
960
|
+
const darkText = overridden(theme.colors.text, light.text, DARK_TEXT);
|
|
961
|
+
const darkGridline = overridden(theme.colors.gridline, light.gridline, "rgba(255,255,255,0.05)");
|
|
962
|
+
const darkAxis = overridden(theme.colors.axis, light.axis, "#a1a1aa");
|
|
963
|
+
const categorical = theme.colors.categorical;
|
|
964
|
+
return {
|
|
965
|
+
...theme,
|
|
966
|
+
isDark: true,
|
|
967
|
+
colors: {
|
|
968
|
+
...theme.colors,
|
|
969
|
+
background: darkBg,
|
|
970
|
+
text: darkText,
|
|
971
|
+
gridline: darkGridline,
|
|
972
|
+
axis: darkAxis,
|
|
973
|
+
annotationFill: overridden(
|
|
974
|
+
theme.colors.annotationFill,
|
|
975
|
+
light.annotationFill,
|
|
976
|
+
"rgba(255,255,255,0.06)"
|
|
977
|
+
),
|
|
978
|
+
annotationText: overridden(theme.colors.annotationText, light.annotationText, darkMuted),
|
|
979
|
+
categorical,
|
|
980
|
+
// Sparkline trend colors tuned for dark surfaces: teal-leaning green
|
|
981
|
+
// and coral red read better than the saturated light-mode tokens.
|
|
982
|
+
// Any non-default value is treated as a user override and preserved.
|
|
983
|
+
positive: theme.colors.positive !== "#16a34a" ? theme.colors.positive : "#34d399",
|
|
984
|
+
negative: theme.colors.negative !== "#dc2626" ? theme.colors.negative : "#f87171"
|
|
985
|
+
},
|
|
986
|
+
chrome: {
|
|
987
|
+
// Eyebrow keeps its accent tint (cyan in both modes); the other
|
|
988
|
+
// chrome elements desaturate to a muted gray on the dark canvas.
|
|
989
|
+
// Each color only adapts if the spec left it at the light default,
|
|
990
|
+
// so explicit chrome color overrides survive dark-mode adaptation.
|
|
991
|
+
eyebrow: theme.chrome.eyebrow,
|
|
992
|
+
title: {
|
|
993
|
+
...theme.chrome.title,
|
|
994
|
+
color: overridden(theme.chrome.title.color, lightChrome.title.color, darkText)
|
|
995
|
+
},
|
|
996
|
+
subtitle: {
|
|
997
|
+
...theme.chrome.subtitle,
|
|
998
|
+
color: overridden(theme.chrome.subtitle.color, lightChrome.subtitle.color, darkMuted)
|
|
999
|
+
},
|
|
1000
|
+
source: {
|
|
1001
|
+
...theme.chrome.source,
|
|
1002
|
+
color: overridden(theme.chrome.source.color, lightChrome.source.color, darkMuted)
|
|
1003
|
+
},
|
|
1004
|
+
byline: {
|
|
1005
|
+
...theme.chrome.byline,
|
|
1006
|
+
color: overridden(theme.chrome.byline.color, lightChrome.byline.color, darkMuted)
|
|
1007
|
+
},
|
|
1008
|
+
footer: {
|
|
1009
|
+
...theme.chrome.footer,
|
|
1010
|
+
color: overridden(theme.chrome.footer.color, lightChrome.footer.color, darkMuted)
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
|
|
991
1016
|
// src/theme/resolve.ts
|
|
992
1017
|
function deepMerge(target, source) {
|
|
993
1018
|
const result = { ...target };
|
|
@@ -1034,6 +1059,13 @@ function themeConfigToPartial(config) {
|
|
|
1034
1059
|
if (config.borderRadius !== void 0) {
|
|
1035
1060
|
partial.borderRadius = config.borderRadius;
|
|
1036
1061
|
}
|
|
1062
|
+
if (config.chrome) {
|
|
1063
|
+
const chrome = {};
|
|
1064
|
+
for (const [element, color2] of Object.entries(config.chrome)) {
|
|
1065
|
+
if (color2 !== void 0) chrome[element] = { color: color2 };
|
|
1066
|
+
}
|
|
1067
|
+
partial.chrome = chrome;
|
|
1068
|
+
}
|
|
1037
1069
|
return partial;
|
|
1038
1070
|
}
|
|
1039
1071
|
function relativeLuminance2(hex2) {
|
|
@@ -1520,6 +1552,7 @@ var HPAD_COMPACT_FRACTION = 0.5;
|
|
|
1520
1552
|
var HPAD_COMPACT_MIN = 4;
|
|
1521
1553
|
var TICK_LABEL_OFFSET = 6;
|
|
1522
1554
|
var AXIS_TITLE_TRAILING_PAD = 4;
|
|
1555
|
+
var AXIS_TITLE_GAP = 14;
|
|
1523
1556
|
var NARROW_VIEWPORT_MAX = 500;
|
|
1524
1557
|
var TOP_PAD_EXTRA_NARROW = 10;
|
|
1525
1558
|
var TOP_PAD_NARROW_MAX = NARROW_VIEWPORT_MAX;
|
|
@@ -2987,6 +3020,7 @@ function tileMap(data, options) {
|
|
|
2987
3020
|
};
|
|
2988
3021
|
}
|
|
2989
3022
|
export {
|
|
3023
|
+
AXIS_TITLE_GAP,
|
|
2990
3024
|
AXIS_TITLE_OFFSET_COMPACT,
|
|
2991
3025
|
AXIS_TITLE_OFFSET_DEFAULT,
|
|
2992
3026
|
AXIS_TITLE_TRAILING_PAD,
|