@moderneinc/react-charts 1.5.2 → 1.6.0-next.0d285b

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.
Files changed (28) hide show
  1. package/dist/components/campaign-timeline/campaign-timeline.component.d.ts +3 -0
  2. package/dist/components/campaign-timeline/campaign-timeline.constants.d.ts +13 -0
  3. package/dist/components/campaign-timeline/campaign-timeline.types.d.ts +191 -0
  4. package/dist/components/campaign-timeline/components/axis-band.component.d.ts +11 -0
  5. package/dist/components/campaign-timeline/components/burndown-band.component.d.ts +18 -0
  6. package/dist/components/campaign-timeline/components/event-lane-band.component.d.ts +16 -0
  7. package/dist/components/campaign-timeline/components/playhead.component.d.ts +14 -0
  8. package/dist/components/campaign-timeline/hooks/use-campaign-timeline.hook.d.ts +33 -0
  9. package/dist/components/campaign-timeline/utils/burndown-paths.d.ts +29 -0
  10. package/dist/components/campaign-timeline/utils/layout-event-flags.d.ts +43 -0
  11. package/dist/components/campaign-timeline/utils/measure-text.d.ts +9 -0
  12. package/dist/components/campaign-timeline/utils/pack-event-rows.d.ts +21 -0
  13. package/dist/components/morph-chart/hooks/use-morph-chart.hook.d.ts +3 -1
  14. package/dist/components/morph-chart/morph-chart.types.d.ts +3 -0
  15. package/dist/components/morph-chart/utils/parliament-renderer.d.ts +2 -0
  16. package/dist/components/parliament-chart/hooks/use-parliament-chart.hook.d.ts +3 -1
  17. package/dist/components/parliament-chart/parliament-chart.constants.d.ts +26 -1
  18. package/dist/components/parliament-chart/utils/parliament-svg-enhanced.d.ts +5 -0
  19. package/dist/components/parliament-chart/utils/parliament-svg-patterns.d.ts +0 -18
  20. package/dist/index.cjs +12 -62
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +14457 -16765
  23. package/dist/theme/color-utils.d.ts +9 -0
  24. package/dist/theme/default-colors.d.ts +13 -5
  25. package/dist/theme/mono-font.d.ts +10 -0
  26. package/dist/theme/readable-color.d.ts +9 -0
  27. package/dist/utils/is-development.d.ts +5 -0
  28. package/package.json +18 -18
@@ -0,0 +1,3 @@
1
+ import { FunctionComponent } from 'react';
2
+ import { CampaignTimelineProps } from './campaign-timeline.types';
3
+ export declare const CampaignTimeline: FunctionComponent<CampaignTimelineProps>;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shared between the hook that MEASURES and the components that RENDER -
3
+ * duplicating any of these lets collision detection drift from the painted text.
4
+ */
5
+ /** Used where the parent reports no width (SSR, jsdom, collapsed container). */
6
+ export declare const FALLBACK_WIDTH = 900;
7
+ /** Spacing so the plot and the grip's rounded end don't sit flush to the edge. */
8
+ export declare const RIGHT_INSET = 10;
9
+ export declare const TICK_SPACING = 90;
10
+ /** How close, in pixels, the playhead must be to an event to snap to it. */
11
+ export declare const SNAP_RADIUS = 8;
12
+ export declare const TICK_FONT_SIZE = 10.5;
13
+ export declare const MIN_LABEL_GAP = 12;
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Three bands on one shared x-axis - burn-down, axis, row-packed events -
3
+ * scrubbed by a single playhead. Event names are the caller's vocabulary.
4
+ */
5
+ /** A discrete run/action on the event track. */
6
+ export type CampaignEvent = {
7
+ /** Stable identity, used for packing, selection, and React keys. */
8
+ id: string;
9
+ /** Caller-supplied display name. Arbitrary - drives the flag's label. */
10
+ name: string;
11
+ /** Start of the event (ms since epoch). Positions the flag's pole. */
12
+ start: number;
13
+ /**
14
+ * NOTE: never drawn. Flags anchor at `start` and are sized to their label;
15
+ * `end` only widens the domain, adds a snap target, and marks "still
16
+ * running" when absent. `null` is accepted so JSON passes through unstripped.
17
+ */
18
+ end?: number | null;
19
+ /**
20
+ * Contrast-corrected against the surface, so one palette works in both
21
+ * themes. Burn-down series colors are not - they carry no text.
22
+ */
23
+ color?: string;
24
+ /** Passed straight through to `onEventClick`. The component never reads it. */
25
+ meta?: Record<string, unknown>;
26
+ };
27
+ /** One stacked band beneath the burn-down line. */
28
+ export type BurndownSeries = {
29
+ /** Key into each point's `values` map. */
30
+ key: string;
31
+ /** Human-readable name for this band. The component never reads it - it is
32
+ * kept alongside the data so callers building their own legend don't need a
33
+ * parallel key-to-label map. */
34
+ label: string;
35
+ /** Fill color for this band. Drawn as a low-opacity area. */
36
+ color: string;
37
+ };
38
+ /**
39
+ * A sampled check point: per-series counts still in flight. The burn-down line
40
+ * is the SUM of these, so bands always cap exactly at the line.
41
+ */
42
+ export type BurndownPoint = {
43
+ timestamp: number;
44
+ values: Record<string, number>;
45
+ };
46
+ export type BurndownConfig = {
47
+ /** The fixed population the campaign started with - the dashed ceiling. */
48
+ total: number;
49
+ /** Stacked bottom-up in array order. */
50
+ series: BurndownSeries[];
51
+ /**
52
+ * Sorted defensively. The line steps rather than interpolating: a gap
53
+ * between samples means "no data", not "a smooth decline".
54
+ */
55
+ points: BurndownPoint[];
56
+ };
57
+ /** Emitted when the playhead moves. */
58
+ export type PlayheadChangeReason = 'drag' | 'keyboard' | 'click';
59
+ /** Resolved palette handed to every band, so all three draw from one source. */
60
+ export type CampaignTimelineColors = {
61
+ surface: string;
62
+ /**
63
+ * WARNING: the MUI palette value, not the painted `surface`. Overriding
64
+ * `--rc-campaign-surface` to another lightness invalidates contrast math.
65
+ */
66
+ surfaceValue: string;
67
+ isDark: boolean;
68
+ track: string;
69
+ border: string;
70
+ grid: string;
71
+ muted: string;
72
+ text: string;
73
+ accent: string;
74
+ accentContrast: string;
75
+ };
76
+ /** A tick position plus whether it earned a label. */
77
+ export type PlacedTick = {
78
+ timestamp: number;
79
+ x: number;
80
+ label: string;
81
+ /**
82
+ * False on edge overflow, collision, or repetition; the tick still renders as
83
+ * a minor mark. Axis and gridlines both read this so they cannot disagree.
84
+ */
85
+ showLabel: boolean;
86
+ };
87
+ export type CampaignTimelineProps = {
88
+ /** Events to lay out on the packed track. Order is irrelevant. */
89
+ events: CampaignEvent[];
90
+ /** Burn-down series and check points. */
91
+ burndown: BurndownConfig;
92
+ /** Controlled playhead position (ms since epoch). */
93
+ time?: number;
94
+ /** Initial playhead position when uncontrolled. Defaults to the domain end. */
95
+ defaultTime?: number;
96
+ /** Called as the playhead moves. */
97
+ onTimeChange?: (time: number, reason: PlayheadChangeReason) => void;
98
+ /**
99
+ * Snap the playhead to nearby event starts/ends on click and drag.
100
+ * Arrow-key stepping always moves event-to-event regardless of this flag.
101
+ * @default true
102
+ */
103
+ snapToEvents?: boolean;
104
+ /**
105
+ * False makes the chart display-only: no playhead, no slider role, input
106
+ * ignored, `onTimeChange` never fires. Flags stay clickable - this disables
107
+ * scrubbing, not selection.
108
+ * @default true
109
+ */
110
+ enableScrubbing?: boolean;
111
+ /**
112
+ * Whether the campaign has finished. Complete graphs the span of its own
113
+ * data; incomplete runs to wall-clock now, so elapsed silence occupies width.
114
+ *
115
+ * NOTE: wall-clock is read once per instance - the edge does not advance on
116
+ * its own. Pass `horizonEnd` on an interval for that.
117
+ * @default true
118
+ */
119
+ complete?: boolean;
120
+ /**
121
+ * How far data is known - typically the last status refresh. The line stops
122
+ * here and the rest is hatched: unrefreshed, not empty. Defaults to no hatch.
123
+ */
124
+ now?: number;
125
+ /**
126
+ * Extends the x-domain to at least this time, so the chart can show
127
+ * wall-clock time past the last refresh. Never shrinks the domain: a value
128
+ * earlier than the last data point has no effect - including under
129
+ * `complete={false}`, which sets a floor for the right edge rather than
130
+ * overriding one.
131
+ */
132
+ horizonEnd?: number;
133
+ /**
134
+ * Fraction of the plot width at which the newest data sits. `2/3` leaves the
135
+ * last third empty; `1` runs the data flush to the right edge.
136
+ *
137
+ * Trailing room is what a flag's label needs: the last event's banner is
138
+ * drawn to the RIGHT of its pole, so a pole at the edge has nowhere to put
139
+ * its text and the label flips to a chip on the other side. Reserving space
140
+ * here keeps the newest - usually most interesting - event reading like
141
+ * every other one.
142
+ *
143
+ * Applies to whatever right edge the domain settled on, so it composes with
144
+ * `horizonEnd` and with `complete={false}`'s wall-clock edge rather than
145
+ * fighting them. Clamped to (0, 1].
146
+ *
147
+ * @default 1
148
+ */
149
+ trailingRatio?: number;
150
+ /** Ids of events drawn in the selected state. */
151
+ selectedEventIds?: string[];
152
+ /** Called when an event flag is clicked. */
153
+ onEventClick?: (event: CampaignEvent) => void;
154
+ /** Called when an event flag is hovered (null on leave). */
155
+ onEventHover?: (event: CampaignEvent | null) => void;
156
+ /**
157
+ * Width in pixels. Omit to fill the parent element and track it on resize.
158
+ * Falls back to 900 where the parent reports no width (SSR, jsdom, or a
159
+ * collapsed container).
160
+ */
161
+ width?: number;
162
+ /** Height of the burn-down band. @default 132 */
163
+ burndownHeight?: number;
164
+ /** Height of the shared axis strip. @default 26 */
165
+ axisHeight?: number;
166
+ /** Height of each packed event row. @default 26 */
167
+ laneHeight?: number;
168
+ /**
169
+ * Formats axis tick labels. The default picks ONE resolution for the whole
170
+ * axis from the tick spacing d3 chose: day-and-coarser renders "Aug 4",
171
+ * anything finer adds a time.
172
+ */
173
+ formatDate?: (timestamp: number) => string;
174
+ /** Formats the playhead grip label. */
175
+ formatPlayheadLabel?: (timestamp: number) => string;
176
+ /** Formats the `total` and `remaining` annotations. */
177
+ formatValue?: (value: number) => string;
178
+ /** Label beside the dashed total line. @default 'total' */
179
+ totalLabel?: string;
180
+ /** Label on the callout tracking the line's end. @default 'in flight' */
181
+ remainingLabel?: string;
182
+ /** Suffix on flags whose event has no end. @default 'running' */
183
+ runningLabel?: string;
184
+ /** Text shown inside the hatched region. Hidden when the region is too
185
+ * narrow to hold it. */
186
+ hatchLabel?: string;
187
+ /** Fallback flag color. Defaults to the theme's primary color. */
188
+ defaultEventColor?: string;
189
+ /** Accessible name for the playhead slider. @default 'Timeline position' */
190
+ playheadAriaLabel?: string;
191
+ };
@@ -0,0 +1,11 @@
1
+ import { CampaignTimelineColors, PlacedTick } from '../campaign-timeline.types';
2
+ type AxisBandProps = {
3
+ ticks: PlacedTick[];
4
+ plotWidth: number;
5
+ height: number;
6
+ colors: CampaignTimelineColors;
7
+ fontFamily: string;
8
+ };
9
+ /** Memoized: ticks are stable while the playhead moves. */
10
+ export declare const AxisBand: import('react').MemoExoticComponent<({ ticks, plotWidth, height, colors, fontFamily }: AxisBandProps) => import("react").JSX.Element>;
11
+ export {};
@@ -0,0 +1,18 @@
1
+ import { BurndownPoint, BurndownSeries, CampaignTimelineColors } from '../campaign-timeline.types';
2
+ type BurndownBandProps = {
3
+ points: BurndownPoint[];
4
+ series: BurndownSeries[];
5
+ total: number;
6
+ height: number;
7
+ plotWidth: number;
8
+ scaleX: (timestamp: number) => number;
9
+ /** Data is only known up to here; the line stops rather than guessing. */
10
+ now: number;
11
+ colors: CampaignTimelineColors;
12
+ formatValue: (value: number) => string;
13
+ totalLabel: string;
14
+ remainingLabel: string;
15
+ fontFamily: string;
16
+ };
17
+ export declare const BurndownBand: import('react').MemoExoticComponent<({ points, series, total, height, plotWidth, scaleX, now, colors, formatValue, totalLabel, remainingLabel, fontFamily }: BurndownBandProps) => import("react").JSX.Element>;
18
+ export {};
@@ -0,0 +1,16 @@
1
+ import { CampaignEvent, CampaignTimelineColors } from '../campaign-timeline.types';
2
+ import { FlagLayout } from '../utils/layout-event-flags';
3
+ type EventLaneBandProps = {
4
+ flags: FlagLayout[];
5
+ rowCount: number;
6
+ laneHeight: number;
7
+ plotWidth: number;
8
+ defaultColor: string;
9
+ selectedIds: Set<string>;
10
+ colors: CampaignTimelineColors;
11
+ fontFamily: string;
12
+ onEventClick?: (event: CampaignEvent) => void;
13
+ onEventHover?: (event: CampaignEvent | null) => void;
14
+ };
15
+ export declare const EventLaneBand: import('react').MemoExoticComponent<({ flags, rowCount, laneHeight, plotWidth, defaultColor, selectedIds, colors, fontFamily, onEventClick, onEventHover }: EventLaneBandProps) => import("react").JSX.Element>;
16
+ export {};
@@ -0,0 +1,14 @@
1
+ import { FunctionComponent } from 'react';
2
+ type PlayheadProps = {
3
+ x: number;
4
+ totalHeight: number;
5
+ /** Vertical center of the axis strip, where the grip sits. */
6
+ gripCenterY: number;
7
+ label: string;
8
+ color: string;
9
+ contrastColor: string;
10
+ fontFamily: string;
11
+ plotWidth: number;
12
+ };
13
+ export declare const Playhead: FunctionComponent<PlayheadProps>;
14
+ export {};
@@ -0,0 +1,33 @@
1
+ import { KeyboardEvent as ReactKeyboardEvent, PointerEvent as ReactPointerEvent } from 'react';
2
+ import { BurndownConfig, CampaignEvent, PlacedTick, PlayheadChangeReason } from '../campaign-timeline.types';
3
+ type UseCampaignTimelineProps = {
4
+ events: CampaignEvent[];
5
+ burndown: BurndownConfig;
6
+ time?: number;
7
+ defaultTime?: number;
8
+ onTimeChange?: (time: number, reason: PlayheadChangeReason) => void;
9
+ snapToEvents: boolean;
10
+ enableScrubbing: boolean;
11
+ complete: boolean;
12
+ now?: number;
13
+ horizonEnd?: number;
14
+ trailingRatio: number;
15
+ width?: number;
16
+ fontFamily: string;
17
+ formatDate?: (timestamp: number) => string;
18
+ };
19
+ export declare const useCampaignTimeline: ({ events, burndown, time, defaultTime, onTimeChange, snapToEvents, enableScrubbing, complete, now, horizonEnd, trailingRatio, width: widthProp, fontFamily, formatDate }: UseCampaignTimelineProps) => {
20
+ containerRef: import('react').RefObject<HTMLDivElement>;
21
+ width: number;
22
+ plotWidth: number;
23
+ domain: [number, number];
24
+ placedTicks: PlacedTick[];
25
+ scaleX: (timestamp: number) => number;
26
+ currentTime: number;
27
+ isScrubbing: boolean;
28
+ handlePointerDown: (event: ReactPointerEvent<HTMLDivElement>) => void;
29
+ handlePointerMove: (event: ReactPointerEvent<HTMLDivElement>) => void;
30
+ handlePointerUp: (event: ReactPointerEvent<HTMLDivElement>) => void;
31
+ handleKeyDown: (event: ReactKeyboardEvent<HTMLDivElement>) => void;
32
+ };
33
+ export {};
@@ -0,0 +1,29 @@
1
+ import { BurndownPoint, BurndownSeries } from '../campaign-timeline.types';
2
+ type Scale = (value: number) => number;
3
+ /** Sum of every series at a point - the count still in flight. */
4
+ export declare const sumSeries: (point: BurndownPoint, series: BurndownSeries[]) => number;
5
+ export declare const remainingByPoint: (points: BurndownPoint[], series: BurndownSeries[]) => number[];
6
+ /**
7
+ * Points at or before `now`, ascending, with anything non-finite dropped.
8
+ *
9
+ * WARNING: filtering has to happen HERE rather than only in
10
+ * `buildBurndownGeometry`, because the caller derives the y-domain from these
11
+ * points - a single NaN value would make `scaleLinear` return NaN for every
12
+ * input and blank the entire band.
13
+ */
14
+ export declare const knownPoints: (points: BurndownPoint[], now: number, series: BurndownSeries[]) => BurndownPoint[];
15
+ type BurndownBandPath = {
16
+ key: string;
17
+ color: string;
18
+ d: string;
19
+ };
20
+ type BurndownGeometry = {
21
+ linePath: string;
22
+ bands: BurndownBandPath[];
23
+ };
24
+ /**
25
+ * Bands come from the stack; the line is those values re-summed. They coincide
26
+ * only because the default stack offset puts the top of the stack at the sum.
27
+ */
28
+ export declare const buildBurndownGeometry: (points: BurndownPoint[], series: BurndownSeries[], xScale: Scale, yScale: Scale) => BurndownGeometry;
29
+ export {};
@@ -0,0 +1,43 @@
1
+ import { CampaignEvent } from '../campaign-timeline.types';
2
+ export declare const POLE_WIDTH = 2.5;
3
+ export declare const BANNER_HEIGHT = 20;
4
+ export declare const FLAG_FONT_SIZE = 11;
5
+ export declare const FLAG_FONT_WEIGHT = 600;
6
+ export type FlagLayout = {
7
+ event: CampaignEvent;
8
+ /** Pixel x of the event's start - where the pole sits. */
9
+ poleX: number;
10
+ width: number;
11
+ row: number;
12
+ label: string;
13
+ /** True when the label was moved left of the pole to stay on canvas. */
14
+ labelOutside: boolean;
15
+ labelX: number;
16
+ /** Chip behind an outside label. Zero width when the label is inline. */
17
+ chipX: number;
18
+ chipWidth: number;
19
+ /** The flag's full rendered extent - its packing footprint. */
20
+ x0: number;
21
+ x1: number;
22
+ };
23
+ type LayoutEventFlagsOptions = {
24
+ events: CampaignEvent[];
25
+ xScale: (timestamp: number) => number;
26
+ plotWidth: number;
27
+ fontFamily: string;
28
+ runningLabel: string;
29
+ gap?: number;
30
+ };
31
+ /** An event with no end is still running - it has no known conclusion. */
32
+ export declare const isRunning: (event: CampaignEvent) => boolean;
33
+ /**
34
+ * A flag whose label was pushed outside contributes that label to its
35
+ * footprint, or a neighbour would pack into space the label already occupies.
36
+ */
37
+ export declare const layoutEventFlags: ({ events, xScale, plotWidth, fontFamily, runningLabel, gap }: LayoutEventFlagsOptions) => {
38
+ flags: FlagLayout[];
39
+ rowCount: number;
40
+ };
41
+ /** Rectangle whose left edge tapers to a point, so the eye lands on the start. */
42
+ export declare const buildFlagBannerPath: (x: number, y: number, width: number, height: number) => string;
43
+ export {};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Text measurement for row packing: flags are packed by rendered extent, so the
3
+ * packer needs label widths before anything reaches the DOM.
4
+ */
5
+ /**
6
+ * Falls back to a character-count estimate where canvas metrics are
7
+ * unavailable (SSR, jsdom without `canvas`), so flags still measure differently.
8
+ */
9
+ export declare const measureText: (text: string, fontSize: number, fontFamily: string, fontWeight?: number | string) => number;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Greedy first-fit row packing, in PIXEL space rather than time space: two
3
+ * events minutes apart can still have labels that overlap on screen.
4
+ */
5
+ /** A flag's horizontal footprint, in pixels. */
6
+ type PackableItem = {
7
+ /** Its pole, or its label chip when the label was pushed outside. */
8
+ x0: number;
9
+ x1: number;
10
+ };
11
+ type PackedRows = {
12
+ /** Row index per input position, 0 = topmost. */
13
+ rows: number[];
14
+ rowCount: number;
15
+ };
16
+ /**
17
+ * Rows come back indexed by input POSITION, not keyed by event id, so duplicate
18
+ * ids in caller data cannot collapse two flags onto one row.
19
+ */
20
+ export declare const packEventRows: (items: PackableItem[], gap?: number) => PackedRows;
21
+ export {};
@@ -1,3 +1,4 @@
1
+ import { PaletteMode } from '@mui/material/styles';
1
2
  import { RefObject } from 'react';
2
3
  import { HoveredData, MorphChartCategory, MorphChartDataPoint, MorphMode } from '../morph-chart.types';
3
4
  type UseMorphChartProps = {
@@ -5,6 +6,7 @@ type UseMorphChartProps = {
5
6
  data: MorphChartDataPoint[];
6
7
  categories: MorphChartCategory[];
7
8
  mode: 'parliament' | 'stacked-area';
9
+ paletteMode?: PaletteMode;
8
10
  width: number;
9
11
  height: number;
10
12
  margin: {
@@ -58,7 +60,7 @@ type UseMorphChartProps = {
58
60
  y: number;
59
61
  } | null) => void;
60
62
  };
61
- export declare const useMorphChart: ({ containerRef, data, categories, mode, width, height, margin, timeRange, showGrid, showAxes, axisLabelColor, axisLabelSize, formatDate, formatValue, markers, arcAngle, parliamentRadius, seatSize, animationDuration, onMorphComplete, onAnimationStateChange, onTimelineReady, onAnimationProgress, onHoveredDataChange, hoveredCategory: externalHoveredCategory, maxSeats, parliamentTimestamp, enableBrush, onTimeRangeChange, minAllowedTime: minAllowedTimeConstraint, maxAllowedTime: maxAllowedTimeConstraint, showScaleIndicator, reposPerSeat, timelineEvents, showTimeline, timelineHeight, timelineOffset, brushColor, showCrosshair, onCrosshairMove }: UseMorphChartProps) => {
63
+ export declare const useMorphChart: ({ containerRef, data, categories, mode, paletteMode, width, height, margin, timeRange, showGrid, showAxes, axisLabelColor, axisLabelSize, formatDate, formatValue, markers, arcAngle, parliamentRadius, seatSize, animationDuration, onMorphComplete, onAnimationStateChange, onTimelineReady, onAnimationProgress, onHoveredDataChange, hoveredCategory: externalHoveredCategory, maxSeats, parliamentTimestamp, enableBrush, onTimeRangeChange, minAllowedTime: minAllowedTimeConstraint, maxAllowedTime: maxAllowedTimeConstraint, showScaleIndicator, reposPerSeat, timelineEvents, showTimeline, timelineHeight, timelineOffset, brushColor, showCrosshair, onCrosshairMove }: UseMorphChartProps) => {
62
64
  isMorphing: boolean;
63
65
  currentMode: MorphMode;
64
66
  hoveredCategory: string | null;
@@ -1,3 +1,4 @@
1
+ import { PaletteMode } from '@mui/material/styles';
1
2
  import { D3StackedAreaCategory } from '../d3-stacked-area-chart/d3-stacked-area-chart.types';
2
3
  export type MorphMode = 'area' | 'parliament' | 'morphing';
3
4
  export type MorphChartDataPoint = {
@@ -25,6 +26,8 @@ export type MorphChartProps = {
25
26
  categories: MorphChartCategory[];
26
27
  /** Current visualization mode */
27
28
  mode: 'parliament' | 'stacked-area';
29
+ /** Light/dark palette mode, affects seat border styling */
30
+ paletteMode?: PaletteMode;
28
31
  /** Morph progress: 0 = fully area, 1 = fully parliament */
29
32
  morphProgress?: number;
30
33
  /** Chart dimensions */
@@ -1,3 +1,4 @@
1
+ import { PaletteMode } from '@mui/material/styles';
1
2
  import { Selection } from 'd3-selection';
2
3
  import { MorphChartCategory, ParliamentLayout } from '../morph-chart.types';
3
4
  /**
@@ -11,6 +12,7 @@ interface ParliamentRenderOptions {
11
12
  addClasses?: boolean;
12
13
  instanceId?: string;
13
14
  parliamentRadius?: number;
15
+ paletteMode?: PaletteMode;
14
16
  }
15
17
  /**
16
18
  * Render parliament seats into an SVG container
@@ -1,3 +1,4 @@
1
+ import { PaletteMode } from '@mui/material/styles';
1
2
  import { RefObject } from 'react';
2
3
  import { ChartConfig, HoveredData, ProcessedDataItem } from '../parliament-chart.types';
3
4
  import { ArcSweepDirection } from '../utils/parliament-animation';
@@ -13,6 +14,7 @@ type UseParliamentChartProps = {
13
14
  setActivePartyName: (name: string | null) => void;
14
15
  chartConfig: ChartConfig;
15
16
  seatSize: number;
17
+ paletteMode: PaletteMode;
16
18
  shouldAnimate?: boolean;
17
19
  animationDirection?: ArcSweepDirection;
18
20
  onAnimationComplete?: () => void;
@@ -22,5 +24,5 @@ type UseParliamentChartProps = {
22
24
  * Generates SVG visualization with seat circles and handles hover states
23
25
  * for displaying repository statistics.
24
26
  */
25
- export declare const useParliamentChart: ({ containerRef, processedData, totalRepositories, arcAngle, useEnhanced, maxSeats, setHoveredData, activePartyName, setActivePartyName, chartConfig, seatSize, shouldAnimate, animationDirection, onAnimationComplete }: UseParliamentChartProps) => void;
27
+ export declare const useParliamentChart: ({ containerRef, processedData, totalRepositories, arcAngle, useEnhanced, maxSeats, setHoveredData, activePartyName, setActivePartyName, chartConfig, seatSize, paletteMode, shouldAnimate, animationDirection, onAnimationComplete }: UseParliamentChartProps) => void;
26
28
  export {};
@@ -1,3 +1,4 @@
1
+ import { PaletteMode } from '@mui/material/styles';
1
2
  export declare const DEFAULT_CHART_CONFIG: {
2
3
  readonly arcAngle: 180;
3
4
  readonly maxSeats: 200;
@@ -7,7 +8,7 @@ export declare const DEFAULT_CHART_CONFIG: {
7
8
  readonly seatSize: 5;
8
9
  readonly minSeatSize: 4;
9
10
  readonly maxSeatSize: 18;
10
- readonly spacing: 1.1;
11
+ readonly spacing: 1.5;
11
12
  readonly innerRadiusRatio: 0.4;
12
13
  readonly arcAngleFlexibility: 5;
13
14
  };
@@ -16,6 +17,7 @@ export declare const DEFAULT_CHART_CONFIG: {
16
17
  readonly emptySeatsBorder: "#d8d8d8";
17
18
  readonly seatBorder: "#fff";
18
19
  };
20
+ readonly seatBorderDarken: 0.28;
19
21
  readonly animation: {
20
22
  readonly transitionDuration: "0.2s";
21
23
  readonly hoverOpacity: {
@@ -35,6 +37,29 @@ export declare const MODERNE_VULNERABILITY_COLORS: {
35
37
  NO_LST: string;
36
38
  DATA_MISSING: string;
37
39
  };
40
+ /**
41
+ * Resolve a seat's fill + border from its category color and label.
42
+ * Light mode: every category, including No LST, gets the classic two-tone
43
+ * dot (fill + darker border). Dark mode: regular categories get a border
44
+ * matching their fill (no visible ring); No LST gets a faint ghost fill and
45
+ * a dashed ghost border instead.
46
+ *
47
+ * `label` identifies No LST by name rather than by color — consumers theme
48
+ * No LST's color per palette mode, so it won't reliably match
49
+ * `HOLLOW_CATEGORY_COLORS` by hex.
50
+ *
51
+ * Pass `radius` to also get the proportional `strokeWidth` / `dashArray`.
52
+ */
53
+ export declare function resolveSeatStyle(color: string, radius?: number, options?: {
54
+ paletteMode?: PaletteMode;
55
+ label?: string;
56
+ }): {
57
+ fill: string;
58
+ stroke: string;
59
+ dashed: boolean;
60
+ strokeWidth?: number;
61
+ dashArray?: string;
62
+ };
38
63
  export declare const CAMPAIGN_NOT_APPLICABLE = "N/A";
39
64
  export declare const CAMPAIGN_NO_LST = "No LST";
40
65
  export declare const CAMPAIGN_DATA_MISSING = "Data Missing";
@@ -4,7 +4,12 @@ type Party = {
4
4
  id: string | number;
5
5
  name: string;
6
6
  seats: number;
7
+ /** Seat fill color. */
7
8
  colour: string;
9
+ /** Border color; defaults to a darker shade of `colour` when omitted. */
10
+ borderColour?: string;
11
+ /** When true the seat is drawn hollow (transparent fill, dashed border). */
12
+ dashed?: boolean;
8
13
  };
9
14
  type EnhancedParliamentOptions = {
10
15
  seatCount?: boolean;
@@ -15,21 +15,3 @@ export declare const createDataMissingHatchPattern: (hFunction?: typeof hastH) =
15
15
  * Uses -45-degree diagonal lines.
16
16
  */
17
17
  export declare const createNoLstHatchPattern: (hFunction?: typeof hastH) => Element;
18
- /**
19
- * Color values for hatch patterns to use in CSS and hover states.
20
- * Exported for consistency across components.
21
- */
22
- export declare const HATCH_PATTERN_COLORS: {
23
- readonly greyHatch: {
24
- readonly background: "#EDEFEF";
25
- readonly lines: "#ADB5BD";
26
- };
27
- readonly dataMissingHatch: {
28
- readonly background: "#FFF3CC";
29
- readonly lines: "#FFB800";
30
- };
31
- readonly noLstHatch: {
32
- readonly background: "#FFE5E5";
33
- readonly lines: "#ED4134";
34
- };
35
- };