@opendata-ai/openchart-core 7.1.3 → 7.2.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
@@ -334,6 +334,8 @@ interface AxisConfig {
334
334
  labelPadding?: number;
335
335
  /** Color override for axis tick labels and title. Useful in dual-axis charts to match axis color to its series. */
336
336
  labelColor?: string;
337
+ /** Literal string appended to every formatted tick label. e.g. "B" gives "$4.5B" when format is "$,.1~f". */
338
+ labelSuffix?: string;
337
339
  /** Secondary data field to display alongside each tick label. Renders in lighter weight/color. Only effective on categorical y-axis labels (horizontal bar charts). */
338
340
  labelField?: string;
339
341
  /**
@@ -847,6 +849,19 @@ interface ThemeConfig {
847
849
  family?: string;
848
850
  /** Monospace font family (for tabular numbers). */
849
851
  mono?: string;
852
+ /** Font size overrides in pixels. Partial — only specified keys are overridden. */
853
+ sizes?: {
854
+ /** Chart title. Default: 26. */
855
+ title?: number;
856
+ /** Subtitle below the title. Default: 14. */
857
+ subtitle?: number;
858
+ /** Body text (tooltips, legend labels). Default: 13. */
859
+ body?: number;
860
+ /** Small text (source line, footer). Default: 11. */
861
+ small?: number;
862
+ /** Axis tick labels. Default: 11. */
863
+ axisTick?: number;
864
+ };
850
865
  };
851
866
  /** Spacing overrides in pixels. */
852
867
  spacing?: {
@@ -854,9 +869,33 @@ interface ThemeConfig {
854
869
  padding?: number;
855
870
  /** Gap between chrome elements (title to subtitle, etc.). */
856
871
  chromeGap?: number;
872
+ /** Height reserved below chart area for x-axis tick labels. Increase when large axisTick font sizes cause label clipping. */
873
+ xAxisHeight?: number;
874
+ /** Gap in pixels between the x-axis line and tick label text. Increase when larger axisTick fonts sit too close to the axis line. */
875
+ xAxisLabelPadding?: number;
857
876
  };
858
877
  /** Border radius for chart container and tooltips. */
859
878
  borderRadius?: number;
879
+ /**
880
+ * Per-element chrome text color overrides. Font sizes and weights come
881
+ * from the typography scale and are not overridable here; only color is.
882
+ * An override survives dark-mode adaptation (adaptTheme preserves any
883
+ * chrome color the spec set explicitly).
884
+ */
885
+ chrome?: {
886
+ /** Eyebrow (kicker) text color. */
887
+ eyebrow?: string;
888
+ /** Title text color. */
889
+ title?: string;
890
+ /** Subtitle text color. */
891
+ subtitle?: string;
892
+ /** Source/attribution text color. */
893
+ source?: string;
894
+ /** Byline text color. */
895
+ byline?: string;
896
+ /** Footer text color. */
897
+ footer?: string;
898
+ };
860
899
  }
861
900
  /**
862
901
  * Label density mode controlling how many data labels are shown.
@@ -874,10 +913,14 @@ interface LabelConfig {
874
913
  format?: string;
875
914
  /** Literal string prepended to each formatted label value (e.g. "-" or "$"). */
876
915
  prefix?: string;
916
+ /** Literal string appended to each formatted label value (e.g. "%" or "x"). */
917
+ suffix?: string;
877
918
  /** Fixed CSS color for all labels. Overrides the default fill-derived color. */
878
919
  color?: string;
879
920
  /** Per-series pixel offsets for fine-tuning label positions, keyed by series name. */
880
921
  offsets?: Record<string, AnnotationOffset>;
922
+ /** Font size in pixels for bar/column value labels. */
923
+ fontSize?: number;
881
924
  }
882
925
  /** Shorthand: `false` disables all labels, `true` uses defaults, or pass a full config object. */
883
926
  type LabelSpec = boolean | LabelConfig;
@@ -2200,6 +2243,10 @@ interface ThemeSpacing {
2200
2243
  chartToFooter: number;
2201
2244
  /** Internal padding within the chart area (axes margins). */
2202
2245
  axisMargin: number;
2246
+ /** Height reserved below the chart area for x-axis tick labels (and optional axis title). */
2247
+ xAxisHeight: number;
2248
+ /** Gap in pixels between the x-axis line and the top of non-rotated tick label text. */
2249
+ xAxisLabelPadding: number;
2203
2250
  }
2204
2251
  /** Default style configuration for a chrome text element. */
2205
2252
  interface ChromeDefaults {
@@ -3587,12 +3634,6 @@ declare function simulateColorBlindness(color: string, type: ColorBlindnessType)
3587
3634
  */
3588
3635
  declare function checkPaletteDistinguishability(colors: string[], type: ColorBlindnessType, minDistance?: number): boolean;
3589
3636
 
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
3637
  /**
3597
3638
  * Compute the WCAG contrast ratio between two colors.
3598
3639
  * Returns a value between 1 (identical) and 21 (black on white).
@@ -3601,13 +3642,23 @@ declare function contrastRatio(fg: string, bg: string): number;
3601
3642
  /**
3602
3643
  * Pick a legible label color (white or near-black) for text placed on top of `bg`.
3603
3644
  *
3604
- * Returns white when white clears 4.5:1 against `bg`, dark otherwise.
3605
- * Simpler and more reliable than `findAccessibleColor` for "label on filled bar."
3645
+ * Uses a perceptual luminance threshold rather than a strict 4.5:1 contrast
3646
+ * gate. WCAG's 4.5:1 ratio is calibrated for body text on a page; bold value
3647
+ * labels sitting on a saturated filled bar read fine at lower ratios. A pure
3648
+ * contrast-ratio gate sends mid-tone fills (e.g. slate `#94a3b8`, L≈0.36) to
3649
+ * dark text even though white reads cleaner on them.
3650
+ *
3651
+ * The threshold is mode-dependent because of simultaneous contrast: the same
3652
+ * bar fill looks lighter against a dark canvas than against a white page, so
3653
+ * dark text reads more grounded on it. Dark mode therefore uses a lower
3654
+ * threshold (L < 0.30) to pivot mid-tone fills to dark text sooner.
3606
3655
  *
3607
- * Note: mid-gray backgrounds (~#707070–#8a8a8a) fall in a WCAG gap where
3608
- * neither white nor dark clears 4.5:1. Default palettes don't produce these.
3656
+ * - Light mode (L < 0.42 → white): white on saturated and mid-tone fills;
3657
+ * dark text only on genuinely light fills (`#b0b0b0` and lighter).
3658
+ * - Dark mode (L < 0.30 → white): saturated fills keep white; mid-tone fills
3659
+ * (slate `#94a3b8`, cyan `#06b6d4`, mid-grey) pivot to dark text.
3609
3660
  */
3610
- declare function pickLabelColor(bg: string): string;
3661
+ declare function pickLabelColor(bg: string, darkMode?: boolean): string;
3611
3662
  /**
3612
3663
  * Check if two colors meet WCAG AA contrast requirements.
3613
3664
  * Normal text: 4.5:1, large text (18px+ bold or 24px+): 3:1.
@@ -3629,8 +3680,11 @@ declare function findAccessibleColor(baseColor: string, bg: string, targetRatio?
3629
3680
  * -> sRGB pipeline. Documented OKLCH source values are the contract; if
3630
3681
  * the conversion math changes, regenerate from the source rather than
3631
3682
  * editing hex literals directly.
3683
+ *
3684
+ * Tuned at L≈0.65, C≈0.20 (vs prior L≈0.70, C≈0.15) for more vivid,
3685
+ * saturated color on dark backgrounds where the lighter pastels read soft.
3632
3686
  */
3633
- declare const CATEGORICAL_PALETTE: readonly ["#06b6d4", "#eb7289", "#3bb974", "#ad87ed", "#e69c3a", "#4ba3f7", "#eb8656", "#8494fa", "#00b9c3"];
3687
+ declare const CATEGORICAL_PALETTE: readonly ["#06b6d4", "#ee4a73", "#00b054", "#a46bf5", "#e07d00", "#0091ff", "#f36000", "#6f7dff", "#00afbf"];
3634
3688
  type CategoricalPalette = typeof CATEGORICAL_PALETTE;
3635
3689
  /** Sequential palette definition: an array of color stops from light to dark. */
3636
3690
  interface SequentialPalette {
@@ -3835,6 +3889,16 @@ declare const TICK_LABEL_OFFSET = 6;
3835
3889
  * standard (non-compact) viewports. Omitted on compact viewports to save space.
3836
3890
  */
3837
3891
  declare const AXIS_TITLE_TRAILING_PAD = 4;
3892
+ /**
3893
+ * Breathing room between the widest tick label's far edge and the rotated
3894
+ * y-axis title center. The title glyph extends ~halfGlyph (ceil(bodyFontSize/2))
3895
+ * from its center toward the tick labels, so visible clearance is
3896
+ * AXIS_TITLE_GAP - halfGlyph (~7px at default body size 13).
3897
+ *
3898
+ * Used in both the engine (dimensions.ts margin reservation) and the renderer
3899
+ * (axes.ts title placement). Both must agree on this value.
3900
+ */
3901
+ declare const AXIS_TITLE_GAP = 14;
3838
3902
  /**
3839
3903
  * Width below which "narrow" adjustments apply: extra iOS Safari top padding and
3840
3904
  * tighter category label gaps. Sits between compact (< 400) and medium (400–700).
@@ -4221,4 +4285,4 @@ interface TileMapBuilderOptions {
4221
4285
  */
4222
4286
  declare function tileMap(data: Record<string, number | null> | DataRow[], options?: TileMapBuilderOptions): TileMapSpec;
4223
4287
 
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 };
4288
+ 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
- return contrastRatio(WHITE, bg) >= 4.5 ? WHITE : DARK;
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);
@@ -725,22 +726,22 @@ var ACHROMATIC_RAMP = {
725
726
  var CATEGORICAL_PALETTE = [
726
727
  "#06b6d4",
727
728
  // cyan, primary accent (sRGB literal, ~205°)
728
- "#eb7289",
729
- // rose — oklch(70% 0.15 10)
730
- "#3bb974",
731
- // emerald — oklch(70% 0.15 155)
732
- "#ad87ed",
733
- // violet — oklch(70% 0.15 300)
734
- "#e69c3a",
735
- // amber — oklch(75% 0.14 70)
736
- "#4ba3f7",
737
- // sky — oklch(70% 0.15 250)
738
- "#eb8656",
739
- // orange — oklch(72% 0.14 45)
740
- "#8494fa",
741
- // indigo — oklch(70% 0.15 275)
742
- "#00b9c3"
743
- // teal — oklch(70% 0.15 200)
729
+ "#ee4a73",
730
+ // rose — oklch(65% 0.20 10)
731
+ "#00b054",
732
+ // emerald — oklch(65% 0.20 155)
733
+ "#a46bf5",
734
+ // violet — oklch(65% 0.20 300)
735
+ "#e07d00",
736
+ // amber — oklch(68% 0.19 70)
737
+ "#0091ff",
738
+ // sky — oklch(65% 0.20 250)
739
+ "#f36000",
740
+ // orange — oklch(67% 0.20 45)
741
+ "#6f7dff",
742
+ // indigo — oklch(65% 0.20 275)
743
+ "#00afbf"
744
+ // teal — oklch(65% 0.20 200)
744
745
  ];
745
746
  var SEQUENTIAL_BLUE = {
746
747
  name: "blue",
@@ -812,6 +813,91 @@ var DIVERGING_PALETTES = {
812
813
  brownTeal: [...DIVERGING_BROWN_TEAL.stops]
813
814
  };
814
815
 
816
+ // src/theme/defaults.ts
817
+ var DEFAULT_THEME = {
818
+ colors: {
819
+ categorical: [...CATEGORICAL_PALETTE],
820
+ sequential: SEQUENTIAL_PALETTES,
821
+ diverging: DIVERGING_PALETTES,
822
+ background: "transparent",
823
+ text: "#09090b",
824
+ gridline: "rgba(0,0,0,0.1)",
825
+ // Used for axis lines/ticks AND axis tick label fill. Must clear WCAG AA
826
+ // contrast (4.5:1) on white because tick labels are rendered with this
827
+ // color. Zinc-500 hits ~5.7:1.
828
+ axis: "#71717a",
829
+ annotationFill: "rgba(0,0,0,0.04)",
830
+ annotationText: "#71717a",
831
+ positive: "#16a34a",
832
+ negative: "#dc2626"
833
+ },
834
+ fonts: {
835
+ family: '"Inter Variable", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
836
+ mono: '"JetBrains Mono", "Fira Code", "Cascadia Code", monospace',
837
+ sizes: {
838
+ title: 26,
839
+ subtitle: 14,
840
+ body: 13,
841
+ small: 11,
842
+ axisTick: 11
843
+ },
844
+ weights: {
845
+ normal: 450,
846
+ medium: 550,
847
+ semibold: 590,
848
+ bold: 700
849
+ }
850
+ },
851
+ spacing: {
852
+ padding: 20,
853
+ chromeGap: 4,
854
+ chromeToChart: 8,
855
+ chartToFooter: 8,
856
+ axisMargin: 6,
857
+ xAxisHeight: 26,
858
+ xAxisLabelPadding: 14
859
+ },
860
+ borderRadius: 2,
861
+ chrome: {
862
+ eyebrow: {
863
+ fontSize: 11,
864
+ fontWeight: 510,
865
+ color: "#06b6d4",
866
+ lineHeight: 1.4
867
+ },
868
+ title: {
869
+ fontSize: 26,
870
+ fontWeight: 590,
871
+ color: "#09090b",
872
+ lineHeight: 1.15
873
+ },
874
+ subtitle: {
875
+ fontSize: 14,
876
+ fontWeight: 400,
877
+ color: "#71717a",
878
+ lineHeight: 1.45
879
+ },
880
+ source: {
881
+ fontSize: 11,
882
+ fontWeight: 400,
883
+ color: "#71717a",
884
+ lineHeight: 1.4
885
+ },
886
+ byline: {
887
+ fontSize: 11,
888
+ fontWeight: 400,
889
+ color: "#71717a",
890
+ lineHeight: 1.4
891
+ },
892
+ footer: {
893
+ fontSize: 11,
894
+ fontWeight: 400,
895
+ color: "#71717a",
896
+ lineHeight: 1.4
897
+ }
898
+ }
899
+ };
900
+
815
901
  // src/theme/dark-mode.ts
816
902
  var DARK_BG = ACHROMATIC_RAMP.bg;
817
903
  var DARK_TEXT = ACHROMATIC_RAMP.fg;
@@ -869,10 +955,13 @@ function adaptTheme(theme) {
869
955
  const isTransparent = inputBg === "transparent";
870
956
  const alreadyDark = isTransparent || _luminanceFromHex(inputBg) < 0.2;
871
957
  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
958
  const darkMuted = ACHROMATIC_RAMP.fgMuted;
959
+ const light = DEFAULT_THEME.colors;
960
+ const lightChrome = DEFAULT_THEME.chrome;
961
+ const overridden = (current, lightDefault, darkDefault) => current === lightDefault ? darkDefault : current;
962
+ const darkText = overridden(theme.colors.text, light.text, DARK_TEXT);
963
+ const darkGridline = overridden(theme.colors.gridline, light.gridline, "rgba(255,255,255,0.05)");
964
+ const darkAxis = overridden(theme.colors.axis, light.axis, "#a1a1aa");
876
965
  const categorical = theme.colors.categorical;
877
966
  return {
878
967
  ...theme,
@@ -883,8 +972,12 @@ function adaptTheme(theme) {
883
972
  text: darkText,
884
973
  gridline: darkGridline,
885
974
  axis: darkAxis,
886
- annotationFill: "rgba(255,255,255,0.06)",
887
- annotationText: darkMuted,
975
+ annotationFill: overridden(
976
+ theme.colors.annotationFill,
977
+ light.annotationFill,
978
+ "rgba(255,255,255,0.06)"
979
+ ),
980
+ annotationText: overridden(theme.colors.annotationText, light.annotationText, darkMuted),
888
981
  categorical,
889
982
  // Sparkline trend colors tuned for dark surfaces: teal-leaning green
890
983
  // and coral red read better than the saturated light-mode tokens.
@@ -895,99 +988,33 @@ function adaptTheme(theme) {
895
988
  chrome: {
896
989
  // Eyebrow keeps its accent tint (cyan in both modes); the other
897
990
  // chrome elements desaturate to a muted gray on the dark canvas.
991
+ // Each color only adapts if the spec left it at the light default,
992
+ // so explicit chrome color overrides survive dark-mode adaptation.
898
993
  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 }
994
+ title: {
995
+ ...theme.chrome.title,
996
+ color: overridden(theme.chrome.title.color, lightChrome.title.color, darkText)
997
+ },
998
+ subtitle: {
999
+ ...theme.chrome.subtitle,
1000
+ color: overridden(theme.chrome.subtitle.color, lightChrome.subtitle.color, darkMuted)
1001
+ },
1002
+ source: {
1003
+ ...theme.chrome.source,
1004
+ color: overridden(theme.chrome.source.color, lightChrome.source.color, darkMuted)
1005
+ },
1006
+ byline: {
1007
+ ...theme.chrome.byline,
1008
+ color: overridden(theme.chrome.byline.color, lightChrome.byline.color, darkMuted)
1009
+ },
1010
+ footer: {
1011
+ ...theme.chrome.footer,
1012
+ color: overridden(theme.chrome.footer.color, lightChrome.footer.color, darkMuted)
1013
+ }
904
1014
  }
905
1015
  };
906
1016
  }
907
1017
 
908
- // src/theme/defaults.ts
909
- var DEFAULT_THEME = {
910
- colors: {
911
- categorical: [...CATEGORICAL_PALETTE],
912
- sequential: SEQUENTIAL_PALETTES,
913
- diverging: DIVERGING_PALETTES,
914
- background: "transparent",
915
- text: "#09090b",
916
- gridline: "rgba(0,0,0,0.1)",
917
- // Used for axis lines/ticks AND axis tick label fill. Must clear WCAG AA
918
- // contrast (4.5:1) on white because tick labels are rendered with this
919
- // color. Zinc-500 hits ~5.7:1.
920
- axis: "#71717a",
921
- annotationFill: "rgba(0,0,0,0.04)",
922
- annotationText: "#71717a",
923
- positive: "#16a34a",
924
- negative: "#dc2626"
925
- },
926
- fonts: {
927
- family: '"Inter Variable", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
928
- mono: '"JetBrains Mono", "Fira Code", "Cascadia Code", monospace',
929
- sizes: {
930
- title: 26,
931
- subtitle: 14,
932
- body: 13,
933
- small: 11,
934
- axisTick: 11
935
- },
936
- weights: {
937
- normal: 450,
938
- medium: 550,
939
- semibold: 590,
940
- bold: 700
941
- }
942
- },
943
- spacing: {
944
- padding: 20,
945
- chromeGap: 4,
946
- chromeToChart: 8,
947
- chartToFooter: 8,
948
- axisMargin: 6
949
- },
950
- borderRadius: 2,
951
- chrome: {
952
- eyebrow: {
953
- fontSize: 11,
954
- fontWeight: 510,
955
- color: "#06b6d4",
956
- lineHeight: 1.4
957
- },
958
- title: {
959
- fontSize: 26,
960
- fontWeight: 590,
961
- color: "#09090b",
962
- lineHeight: 1.15
963
- },
964
- subtitle: {
965
- fontSize: 14,
966
- fontWeight: 400,
967
- color: "#71717a",
968
- lineHeight: 1.45
969
- },
970
- source: {
971
- fontSize: 11,
972
- fontWeight: 400,
973
- color: "#71717a",
974
- lineHeight: 1.4
975
- },
976
- byline: {
977
- fontSize: 11,
978
- fontWeight: 400,
979
- color: "#71717a",
980
- lineHeight: 1.4
981
- },
982
- footer: {
983
- fontSize: 11,
984
- fontWeight: 400,
985
- color: "#71717a",
986
- lineHeight: 1.4
987
- }
988
- }
989
- };
990
-
991
1018
  // src/theme/resolve.ts
992
1019
  function deepMerge(target, source) {
993
1020
  const result = { ...target };
@@ -1023,17 +1050,28 @@ function themeConfigToPartial(config) {
1023
1050
  const fonts = {};
1024
1051
  if (config.fonts.family) fonts.family = config.fonts.family;
1025
1052
  if (config.fonts.mono) fonts.mono = config.fonts.mono;
1053
+ if (config.fonts.sizes) fonts.sizes = config.fonts.sizes;
1026
1054
  partial.fonts = fonts;
1027
1055
  }
1028
1056
  if (config.spacing) {
1029
1057
  const spacing = {};
1030
1058
  if (config.spacing.padding !== void 0) spacing.padding = config.spacing.padding;
1031
1059
  if (config.spacing.chromeGap !== void 0) spacing.chromeGap = config.spacing.chromeGap;
1060
+ if (config.spacing.xAxisHeight !== void 0) spacing.xAxisHeight = config.spacing.xAxisHeight;
1061
+ if (config.spacing.xAxisLabelPadding !== void 0)
1062
+ spacing.xAxisLabelPadding = config.spacing.xAxisLabelPadding;
1032
1063
  partial.spacing = spacing;
1033
1064
  }
1034
1065
  if (config.borderRadius !== void 0) {
1035
1066
  partial.borderRadius = config.borderRadius;
1036
1067
  }
1068
+ if (config.chrome) {
1069
+ const chrome = {};
1070
+ for (const [element, color2] of Object.entries(config.chrome)) {
1071
+ if (color2 !== void 0) chrome[element] = { color: color2 };
1072
+ }
1073
+ partial.chrome = chrome;
1074
+ }
1037
1075
  return partial;
1038
1076
  }
1039
1077
  function relativeLuminance2(hex2) {
@@ -1091,6 +1129,26 @@ function adjustOpacity(hex2, opacity) {
1091
1129
  }
1092
1130
  function resolveTheme(userTheme, base = DEFAULT_THEME) {
1093
1131
  let merged = userTheme ? deepMerge(base, themeConfigToPartial(userTheme)) : { ...base };
1132
+ if (userTheme?.fonts?.sizes) {
1133
+ const s = userTheme.fonts.sizes;
1134
+ merged = {
1135
+ ...merged,
1136
+ chrome: {
1137
+ ...merged.chrome,
1138
+ ...s.title !== void 0 && {
1139
+ title: { ...merged.chrome.title, fontSize: s.title }
1140
+ },
1141
+ ...s.subtitle !== void 0 && {
1142
+ subtitle: { ...merged.chrome.subtitle, fontSize: s.subtitle }
1143
+ },
1144
+ ...s.small !== void 0 && {
1145
+ source: { ...merged.chrome.source, fontSize: s.small },
1146
+ byline: { ...merged.chrome.byline, fontSize: s.small },
1147
+ footer: { ...merged.chrome.footer, fontSize: s.small }
1148
+ }
1149
+ }
1150
+ };
1151
+ }
1094
1152
  const dark = isDarkBackground(merged.colors.background);
1095
1153
  if (dark) {
1096
1154
  merged = adaptChromeForDarkBg(merged, merged.colors.text);
@@ -1520,6 +1578,7 @@ var HPAD_COMPACT_FRACTION = 0.5;
1520
1578
  var HPAD_COMPACT_MIN = 4;
1521
1579
  var TICK_LABEL_OFFSET = 6;
1522
1580
  var AXIS_TITLE_TRAILING_PAD = 4;
1581
+ var AXIS_TITLE_GAP = 14;
1523
1582
  var NARROW_VIEWPORT_MAX = 500;
1524
1583
  var TOP_PAD_EXTRA_NARROW = 10;
1525
1584
  var TOP_PAD_NARROW_MAX = NARROW_VIEWPORT_MAX;
@@ -2987,6 +3046,7 @@ function tileMap(data, options) {
2987
3046
  };
2988
3047
  }
2989
3048
  export {
3049
+ AXIS_TITLE_GAP,
2990
3050
  AXIS_TITLE_OFFSET_COMPACT,
2991
3051
  AXIS_TITLE_OFFSET_DEFAULT,
2992
3052
  AXIS_TITLE_TRAILING_PAD,