chart-factory 0.1.2
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/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/chart-factory-table.js +1400 -0
- package/dist/chart-factory.css +2368 -0
- package/dist/chart-factory.js +11398 -0
- package/dist/chart-factory.min.js +7 -0
- package/docs/API.md +1815 -0
- package/docs/chart-guide.md +872 -0
- package/docs/color-guide.md +134 -0
- package/docs/conformance-matrix.md +864 -0
- package/docs/theming.md +107 -0
- package/index.d.ts +10453 -0
- package/package.json +83 -0
- package/src/core/base.css +508 -0
- package/src/core/builder-metadata.generated.mjs +406 -0
- package/src/core/d3-base.js +249 -0
- package/src/core/d3-chart-factory.js +18547 -0
- package/src/core/d3-data.js +0 -0
- package/src/core/d3-suggest.js +565 -0
- package/src/core/example-nav.css +62 -0
- package/src/core/example-nav.js +249 -0
- package/src/core/palettes.js +310 -0
- package/src/core/theme-toggle.js +126 -0
- package/src/core/tokens.css +792 -0
- package/src/table/d3-table.js +1570 -0
- package/src/table/table.css +899 -0
- package/src/table/tokens.css +157 -0
|
@@ -0,0 +1,2368 @@
|
|
|
1
|
+
/* chart-factory — generated CSS barrel (tokens + base + table). Do not edit. */
|
|
2
|
+
|
|
3
|
+
/* ==== src/core/tokens.css ==== */
|
|
4
|
+
/**
|
|
5
|
+
* Chart Factory - Shared Design Tokens
|
|
6
|
+
*
|
|
7
|
+
* These CSS custom properties define the visual foundation for all D3 components.
|
|
8
|
+
* Override these in your own stylesheet to customize the appearance.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* @import 'chart-factory/core/tokens.css';
|
|
12
|
+
*
|
|
13
|
+
* :root {
|
|
14
|
+
* --font-family: 'Your Font', sans-serif;
|
|
15
|
+
* --color-text-primary: #222;
|
|
16
|
+
* }
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
:root {
|
|
20
|
+
/* ===========================
|
|
21
|
+
SPACING
|
|
22
|
+
=========================== */
|
|
23
|
+
--space-0: 0;
|
|
24
|
+
--space-1: 4px;
|
|
25
|
+
--space-2: 8px;
|
|
26
|
+
--space-3: 12px;
|
|
27
|
+
--space-4: 15px;
|
|
28
|
+
--space-5: 20px;
|
|
29
|
+
--space-6: 25px;
|
|
30
|
+
--space-7: 35px;
|
|
31
|
+
--space-8: 40px;
|
|
32
|
+
|
|
33
|
+
/* ===========================
|
|
34
|
+
TYPOGRAPHY
|
|
35
|
+
=========================== */
|
|
36
|
+
/* Default: System font stack */
|
|
37
|
+
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
38
|
+
/* Alternative: Open Sans (uncomment and add Google Font import to use)
|
|
39
|
+
* Add to <head>: <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap" rel="stylesheet">
|
|
40
|
+
* --font-family: 'Open Sans', sans-serif;
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/* Font Sizes */
|
|
44
|
+
--font-size-xs: 9px;
|
|
45
|
+
--font-size-sm: 10px;
|
|
46
|
+
--font-size-base: 11px;
|
|
47
|
+
--font-size-md: 12px;
|
|
48
|
+
--font-size-lg: 13px;
|
|
49
|
+
--font-size-xl: 14px;
|
|
50
|
+
--font-size-title: 28px;
|
|
51
|
+
--font-size-subtitle: 13px;
|
|
52
|
+
/* Chart-shell text hierarchy (decided from live mockups 2026-07-18):
|
|
53
|
+
card tier 15/11 (ratio 1.36), featured tier 20/14 (ratio 1.43).
|
|
54
|
+
Title size tracks reading context, not chart size. */
|
|
55
|
+
--shell-title-size: 15px;
|
|
56
|
+
--shell-subtitle-size: var(--font-size-base);
|
|
57
|
+
--shell-featured-title-size: 20px;
|
|
58
|
+
--shell-featured-subtitle-size: 14px;
|
|
59
|
+
|
|
60
|
+
/* Font Weights */
|
|
61
|
+
--font-weight-normal: 400;
|
|
62
|
+
--font-weight-semibold: 600;
|
|
63
|
+
--font-weight-bold: 700;
|
|
64
|
+
|
|
65
|
+
/* Letter Spacing */
|
|
66
|
+
--letter-spacing-tight: -0.3px;
|
|
67
|
+
--letter-spacing-wide: 0.8px;
|
|
68
|
+
--letter-spacing-wider: 1px;
|
|
69
|
+
|
|
70
|
+
/* Line Height */
|
|
71
|
+
--line-height-normal: 1.6;
|
|
72
|
+
|
|
73
|
+
/* ===========================
|
|
74
|
+
COLORS
|
|
75
|
+
=========================== */
|
|
76
|
+
|
|
77
|
+
/* Text Colors */
|
|
78
|
+
--color-black: #000;
|
|
79
|
+
--color-text-primary: #333;
|
|
80
|
+
--color-text-muted: #666;
|
|
81
|
+
--color-text-subtle: #767676;
|
|
82
|
+
--color-text-faint: #999;
|
|
83
|
+
--color-text-disabled: #aaa;
|
|
84
|
+
|
|
85
|
+
/* Background Colors */
|
|
86
|
+
--color-bg-body: #f8f8f8;
|
|
87
|
+
--color-bg-container: white;
|
|
88
|
+
--color-bg-hover: #fafafa;
|
|
89
|
+
--color-bg-highlight: #f0f0f0;
|
|
90
|
+
|
|
91
|
+
/* Border Colors */
|
|
92
|
+
--color-border-header: #000;
|
|
93
|
+
--color-border-row: #f0f0f0;
|
|
94
|
+
--color-border-light: #e5e5e5; /* light axis/grid strokes */
|
|
95
|
+
--color-border-footer: #e8e8e8;
|
|
96
|
+
--color-border-tooltip: #ddd;
|
|
97
|
+
|
|
98
|
+
/* Shadows */
|
|
99
|
+
--shadow-container: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
100
|
+
--shadow-tooltip: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
101
|
+
|
|
102
|
+
/* ===========================
|
|
103
|
+
ANIMATIONS
|
|
104
|
+
=========================== */
|
|
105
|
+
--animation-duration-fast: 200ms;
|
|
106
|
+
--animation-duration-normal: 500ms;
|
|
107
|
+
--animation-duration-slow: 1000ms;
|
|
108
|
+
--animation-stagger-delay: 50ms;
|
|
109
|
+
|
|
110
|
+
/* ===========================
|
|
111
|
+
TOOLTIPS
|
|
112
|
+
=========================== */
|
|
113
|
+
--tooltip-z-index: 9999; /* above typical host-page modals/sticky chrome */
|
|
114
|
+
--tooltip-font-size: 12px; /* tooltip body (independent of chart axis text) */
|
|
115
|
+
--tooltip-title-font-size: 15px; /* ~1.25x body for clear title dominance */
|
|
116
|
+
--tooltip-row-gap: 4px; /* vertical rhythm between shell rows */
|
|
117
|
+
--tooltip-padding: 10px 13px;
|
|
118
|
+
--tooltip-border-radius: 6px;
|
|
119
|
+
--tooltip-min-width: 130px;
|
|
120
|
+
--tooltip-chart-height: 50px;
|
|
121
|
+
--tooltip-bar-gap: var(--space-1);
|
|
122
|
+
--tooltip-bar-min-width: var(--space-2);
|
|
123
|
+
--tooltip-bar-border-radius: 2px;
|
|
124
|
+
--tooltip-label-offset-y: 18px;
|
|
125
|
+
--tooltip-value-offset-y: 18px;
|
|
126
|
+
|
|
127
|
+
/* ===========================
|
|
128
|
+
CHART STORY (story: config — narrative summary module under the chart)
|
|
129
|
+
=========================== */
|
|
130
|
+
--story-font-size: var(--font-size-lg); /* body text of all three variants */
|
|
131
|
+
--story-line-height: 1.6;
|
|
132
|
+
/* Accent drives the panel tint/border, the bold lead-in label, and band
|
|
133
|
+
<b>/<strong> emphasis. The per-chart `accent` option overrides it via an
|
|
134
|
+
inline custom property on the module. */
|
|
135
|
+
--story-accent: var(--chart-primary);
|
|
136
|
+
/* Accent share in the panel wash / hairline (color-mix percentages) */
|
|
137
|
+
--story-panel-bg-alpha: 7%;
|
|
138
|
+
--story-panel-border-alpha: 25%;
|
|
139
|
+
/* Band variant: full-width dark lede strip */
|
|
140
|
+
--story-band-bg: #1f2937;
|
|
141
|
+
--story-band-text: #e5e7eb;
|
|
142
|
+
--story-band-label: #9ca3af; /* overline label tint on the band */
|
|
143
|
+
|
|
144
|
+
/* ===========================
|
|
145
|
+
RESPONSIVE
|
|
146
|
+
=========================== */
|
|
147
|
+
--container-padding: var(--space-8); /* .d3-container inner padding — the one knob for card breathing room */
|
|
148
|
+
--container-padding-mobile: var(--space-6) var(--space-5);
|
|
149
|
+
--title-font-size-mobile: 22px;
|
|
150
|
+
|
|
151
|
+
/* ===========================
|
|
152
|
+
CHART SHARED
|
|
153
|
+
=========================== */
|
|
154
|
+
|
|
155
|
+
/* Primary chart color — single-series default (line/area/bar/scatter,
|
|
156
|
+
forecast/actual lines, series-3). Brand blue, kept distinct from the Set2
|
|
157
|
+
categorical palette used for multi-series charts. */
|
|
158
|
+
--chart-primary: #3b82f6;
|
|
159
|
+
--chart-primary-light: rgba(59, 130, 246, 0.3);
|
|
160
|
+
--chart-primary-faint: rgba(59, 130, 246, 0.1);
|
|
161
|
+
|
|
162
|
+
/* Secondary chart colors */
|
|
163
|
+
--chart-secondary: #22c55e; /* line palette slot 2 (getLineColors) */
|
|
164
|
+
--chart-tertiary: #f97316; /* line palette slot 3 (getLineColors) */
|
|
165
|
+
--chart-green: #22c55e;
|
|
166
|
+
--chart-lime: #84cc16; /* mid-tier threshold color (progress bars) */
|
|
167
|
+
--chart-orange: #f97316;
|
|
168
|
+
--chart-purple: #a855f7;
|
|
169
|
+
--chart-purple-light: rgba(168, 85, 247, 0.4);
|
|
170
|
+
--chart-purple-faint: rgba(168, 85, 247, 0.05);
|
|
171
|
+
--chart-red: #ef4444;
|
|
172
|
+
|
|
173
|
+
/* Multi-series palette (for YoY, comparisons) */
|
|
174
|
+
--chart-series-1: #94a3b8;
|
|
175
|
+
--chart-series-2: #64748b;
|
|
176
|
+
--chart-series-3: var(--chart-primary);
|
|
177
|
+
|
|
178
|
+
/* Semantic chart colors */
|
|
179
|
+
--chart-trend-up: var(--chart-green);
|
|
180
|
+
--chart-trend-down: var(--chart-red);
|
|
181
|
+
--chart-anomaly: var(--chart-red);
|
|
182
|
+
--chart-forecast: var(--chart-primary);
|
|
183
|
+
--chart-actual: var(--chart-primary);
|
|
184
|
+
--chart-budget: var(--chart-green);
|
|
185
|
+
|
|
186
|
+
/* Neutral/gray for cumulative/baseline lines */
|
|
187
|
+
--chart-neutral: #6b7280;
|
|
188
|
+
--chart-neutral-light: rgba(107, 114, 128, 0.4);
|
|
189
|
+
--chart-neutral-faint: rgba(107, 114, 128, 0.05);
|
|
190
|
+
|
|
191
|
+
/* Axes */
|
|
192
|
+
--axis-color: var(--color-text-muted);
|
|
193
|
+
--axis-font-size: var(--font-size-xs);
|
|
194
|
+
--axis-label-font-size: var(--font-size-sm);
|
|
195
|
+
--axis-tick-size: 5px;
|
|
196
|
+
--axis-tick-padding: 3px;
|
|
197
|
+
|
|
198
|
+
/* Grid */
|
|
199
|
+
--grid-color: var(--color-border-row);
|
|
200
|
+
--grid-opacity: 0.5;
|
|
201
|
+
--grid-stroke-opacity: 0.5;
|
|
202
|
+
|
|
203
|
+
/* Hover states */
|
|
204
|
+
--hover-stroke-color: var(--color-text-primary);
|
|
205
|
+
--hover-stroke-width: 1;
|
|
206
|
+
--hover-dash-pattern: 3, 3;
|
|
207
|
+
--link-dot-radius: 3.5; /* snapped dot size on linked-crosshair (linkGroup) charts */
|
|
208
|
+
--brush-selection-opacity: 0.08; /* fill opacity of the brush selection window */
|
|
209
|
+
--brush-veil-opacity: 0.65; /* opacity of the outside-selection veil on linked charts */
|
|
210
|
+
|
|
211
|
+
/* Lines */
|
|
212
|
+
--line-stroke-width: 2;
|
|
213
|
+
--line-stroke-width-thin: 1;
|
|
214
|
+
--line-stroke-width-hover: 3;
|
|
215
|
+
--line-opacity: 1;
|
|
216
|
+
--line-opacity-muted: 0.3;
|
|
217
|
+
--line-dot-stroke-width: 2;
|
|
218
|
+
--line-transition-duration: 150;
|
|
219
|
+
|
|
220
|
+
/* Areas */
|
|
221
|
+
--area-opacity: 0.15;
|
|
222
|
+
--area-opacity-light: 0.1;
|
|
223
|
+
--area-gradient-start: 0.3;
|
|
224
|
+
--area-gradient-end: 0.02;
|
|
225
|
+
|
|
226
|
+
/* Points */
|
|
227
|
+
--point-radius: 4px;
|
|
228
|
+
--point-radius-small: 3px;
|
|
229
|
+
--point-radius-hover: 6px;
|
|
230
|
+
--point-stroke-width: 2px;
|
|
231
|
+
|
|
232
|
+
/* Confidence bands */
|
|
233
|
+
--band-opacity: 0.15;
|
|
234
|
+
--band-border-opacity: 0.3;
|
|
235
|
+
|
|
236
|
+
/* Anomaly indicators */
|
|
237
|
+
--anomaly-pulse-radius: 4;
|
|
238
|
+
--anomaly-dot-radius: 3;
|
|
239
|
+
|
|
240
|
+
/* Legend */
|
|
241
|
+
--legend-swatch-size: 12px;
|
|
242
|
+
--legend-line-width: 24px;
|
|
243
|
+
--legend-line-height: 2px;
|
|
244
|
+
--legend-gap: var(--space-4);
|
|
245
|
+
--legend-font-size: var(--font-size-sm);
|
|
246
|
+
/* Interactive legend: opacity of non-highlighted series when one is
|
|
247
|
+
hovered/isolated (chosen treatment: dim others, highlighted unchanged). */
|
|
248
|
+
--legend-dim-opacity: 0.18;
|
|
249
|
+
|
|
250
|
+
/* Trend indicators (legacy aliases) */
|
|
251
|
+
--trend-up-color: var(--chart-trend-up);
|
|
252
|
+
--trend-down-color: var(--chart-trend-down);
|
|
253
|
+
--trend-neutral-color: var(--color-text-muted);
|
|
254
|
+
|
|
255
|
+
/* Animation */
|
|
256
|
+
--chart-animation-duration: 2s;
|
|
257
|
+
--chart-pulse-min-opacity: 0;
|
|
258
|
+
--chart-pulse-max-opacity: 0.6;
|
|
259
|
+
|
|
260
|
+
/* Annotations: fade-in (ms) when an annotation waits for its anchor
|
|
261
|
+
mark's entrance animation before appearing */
|
|
262
|
+
--annotation-fade-duration: 200;
|
|
263
|
+
|
|
264
|
+
/* Event annotations (type: 'event'): tick + label band below the x-axis */
|
|
265
|
+
--annotation-event-band-offset: 22; /* px from plot bottom to the event ticks */
|
|
266
|
+
--annotation-event-tick-height: 8; /* event tick length */
|
|
267
|
+
--annotation-event-font-size: 10px; /* event label size */
|
|
268
|
+
--annotation-event-guide-color: #d1d5db; /* dotted in-plot guide (guide: true) */
|
|
269
|
+
|
|
270
|
+
/* Point/callout annotations: editorial two-line callouts + leader lines */
|
|
271
|
+
--annotation-title-size: 13px; /* titled callout value line (`title`) */
|
|
272
|
+
--annotation-leader-threshold: 14; /* px dot-to-label gap before an auto leader draws */
|
|
273
|
+
|
|
274
|
+
/* ===========================
|
|
275
|
+
SLOPE CHART SPECIFIC
|
|
276
|
+
=========================== */
|
|
277
|
+
|
|
278
|
+
/* Slope line widths */
|
|
279
|
+
--slope-line-width: var(--line-stroke-width);
|
|
280
|
+
--slope-line-width-hover: 3;
|
|
281
|
+
|
|
282
|
+
/* Slope point sizing */
|
|
283
|
+
--slope-point-radius: var(--point-radius);
|
|
284
|
+
--slope-point-radius-hover: 7px;
|
|
285
|
+
--slope-gap-radius: 6px;
|
|
286
|
+
|
|
287
|
+
/* Slope interaction states */
|
|
288
|
+
--slope-dimmed-opacity: 0.2;
|
|
289
|
+
--slope-transition-duration: 0.2s;
|
|
290
|
+
|
|
291
|
+
/* Slope animation */
|
|
292
|
+
--slope-animation-duration: 800ms;
|
|
293
|
+
--slope-stagger-delay: 80ms;
|
|
294
|
+
--slope-fade-duration: 300ms;
|
|
295
|
+
|
|
296
|
+
/* Slope highlight mode */
|
|
297
|
+
--slope-highlight-color: var(--chart-orange);
|
|
298
|
+
--slope-muted-color: #94a3b8;
|
|
299
|
+
|
|
300
|
+
/* Slope label dodge: minimum vertical gap between stacked side labels */
|
|
301
|
+
--slope-label-min-gap: 15;
|
|
302
|
+
/* Two-line right labels (createIndexed) need roughly double the gap */
|
|
303
|
+
--slope-label-min-gap-stacked: 34;
|
|
304
|
+
|
|
305
|
+
/* Indexed slope: hairline at the origin/no-change value */
|
|
306
|
+
--slope-baseline-color: var(--color-border-light);
|
|
307
|
+
|
|
308
|
+
/* Dodged-label leader lines (defaults to the hairline border color) */
|
|
309
|
+
--slope-leader-color: var(--color-border-light);
|
|
310
|
+
|
|
311
|
+
/* Slope band colors (quartiles, from bottom to top) */
|
|
312
|
+
--slope-band-1: #fef2f2;
|
|
313
|
+
--slope-band-2: #fefce8;
|
|
314
|
+
--slope-band-3: #f0fdf4;
|
|
315
|
+
--slope-band-4: #eff6ff;
|
|
316
|
+
|
|
317
|
+
/* ===========================
|
|
318
|
+
AREA CHART SPECIFIC
|
|
319
|
+
=========================== */
|
|
320
|
+
|
|
321
|
+
/* Area fill opacity */
|
|
322
|
+
--area-fill-opacity: 0.35;
|
|
323
|
+
--area-fill-opacity-hover: 0.5;
|
|
324
|
+
--area-fill-opacity-muted: 0.15;
|
|
325
|
+
|
|
326
|
+
/* Area stroke */
|
|
327
|
+
--area-stroke-width: 1.5;
|
|
328
|
+
--area-stroke-opacity: 1;
|
|
329
|
+
|
|
330
|
+
/* Categorical series palette — Set2 (house default, CVD-safe, muted).
|
|
331
|
+
Drives getAreaColors()/getLineColors(): stacked areas, multi-line, grouped/
|
|
332
|
+
stacked bars, donut, dot, scatter categories. Single-series charts use
|
|
333
|
+
--chart-primary, not these. Override any of these to restyle every
|
|
334
|
+
multi-series chart at once. */
|
|
335
|
+
--area-color-1: #66c2a5;
|
|
336
|
+
--area-color-2: #fc8d62;
|
|
337
|
+
--area-color-3: #8da0cb;
|
|
338
|
+
--area-color-4: #e78ac3;
|
|
339
|
+
--area-color-5: #a6d854;
|
|
340
|
+
--area-color-6: #ffd92f;
|
|
341
|
+
--area-color-7: #e5c494;
|
|
342
|
+
--area-color-8: #b3b3b3;
|
|
343
|
+
|
|
344
|
+
/* Area interaction */
|
|
345
|
+
--area-transition-duration: 0.2s;
|
|
346
|
+
--area-dimmed-opacity: 0.2;
|
|
347
|
+
|
|
348
|
+
/* Area animation */
|
|
349
|
+
--area-animation-duration: 1000ms;
|
|
350
|
+
--area-stagger-delay: 100ms;
|
|
351
|
+
|
|
352
|
+
/* Area gradient (for single-series with gradient fill) */
|
|
353
|
+
--area-gradient-opacity-top: 0.3;
|
|
354
|
+
--area-gradient-opacity-bottom: 0.02;
|
|
355
|
+
|
|
356
|
+
/* Smoothing curve tension (0 = linear, 1 = very smooth) */
|
|
357
|
+
--area-curve-tension: 0.4;
|
|
358
|
+
|
|
359
|
+
/* ===========================
|
|
360
|
+
STREAMGRAPH SPECIFIC
|
|
361
|
+
=========================== */
|
|
362
|
+
|
|
363
|
+
/* Stream layer fill */
|
|
364
|
+
--stream-fill-opacity: 1;
|
|
365
|
+
--stream-stroke-color: #fff;
|
|
366
|
+
--stream-stroke-width: 1;
|
|
367
|
+
|
|
368
|
+
/* Stream interaction */
|
|
369
|
+
--stream-dimmed-opacity: 0.25;
|
|
370
|
+
|
|
371
|
+
/* Stream labels */
|
|
372
|
+
--stream-label-size: 11;
|
|
373
|
+
|
|
374
|
+
/* ===========================
|
|
375
|
+
DOT CHART SPECIFIC
|
|
376
|
+
=========================== */
|
|
377
|
+
|
|
378
|
+
/* Dot sizing */
|
|
379
|
+
--dot-radius: 5px;
|
|
380
|
+
--dot-radius-hover: 7px;
|
|
381
|
+
|
|
382
|
+
/* Connector line (dumbbell) */
|
|
383
|
+
--dot-connector-width: 2.5;
|
|
384
|
+
--dot-connector-color: var(--color-text-disabled);
|
|
385
|
+
|
|
386
|
+
/* Row spacing */
|
|
387
|
+
--dot-row-height: 36px;
|
|
388
|
+
--dot-row-padding: 0.3;
|
|
389
|
+
|
|
390
|
+
/* Header positioning */
|
|
391
|
+
--dot-header-offset: 12px;
|
|
392
|
+
|
|
393
|
+
/* ===========================
|
|
394
|
+
COLOR TEMPERATURE PALETTE
|
|
395
|
+
=========================== */
|
|
396
|
+
/* Based on Kelvin color temperature scale
|
|
397
|
+
* Cold (blue) → Warm (amber)
|
|
398
|
+
* Useful for percentile bands, heat maps, diverging scales */
|
|
399
|
+
|
|
400
|
+
--temp-cold-10: #7AB8FF; /* 10000K - deep cold blue */
|
|
401
|
+
--temp-cold-9: #9CCAFF; /* 9000K - cold blue */
|
|
402
|
+
--temp-cold-8: #B8D9FF; /* 8000K - sky blue */
|
|
403
|
+
--temp-cold-7: #D6E8FF; /* 7000K - pale blue */
|
|
404
|
+
--temp-neutral: #F2F7FF; /* 6000K - cool white */
|
|
405
|
+
--temp-warm-5: #FFF1C1; /* 5000K - neutral daylight */
|
|
406
|
+
--temp-warm-4: #FFD27F; /* 4000K - warm white */
|
|
407
|
+
--temp-warm-3: #FFB347; /* 3000K - tungsten */
|
|
408
|
+
--temp-warm-2: #FF8C1A; /* 2000K - warm orange */
|
|
409
|
+
--temp-warm-1: #FF6A00; /* 1000K - deep amber */
|
|
410
|
+
|
|
411
|
+
/* ===========================
|
|
412
|
+
SCATTER PLOT / TRAJECTORY
|
|
413
|
+
=========================== */
|
|
414
|
+
|
|
415
|
+
/* Trail length (number of time units to show in trail) */
|
|
416
|
+
--scatter-trail-length: 5;
|
|
417
|
+
|
|
418
|
+
/* Trail styling */
|
|
419
|
+
--scatter-trail-width: 3;
|
|
420
|
+
--scatter-trail-width-hover: 4;
|
|
421
|
+
--scatter-trail-opacity: 0.7;
|
|
422
|
+
--scatter-trail-opacity-dimmed: 0.08;
|
|
423
|
+
|
|
424
|
+
/* Trail gradient (fade from start to end) */
|
|
425
|
+
--scatter-trail-gradient-start: 0.1;
|
|
426
|
+
--scatter-trail-gradient-end: 0.9;
|
|
427
|
+
|
|
428
|
+
/* Dot sizing */
|
|
429
|
+
--scatter-dot-radius: 6;
|
|
430
|
+
--scatter-dot-radius-hover: 10;
|
|
431
|
+
--scatter-dot-stroke-width: 2;
|
|
432
|
+
|
|
433
|
+
/* Start dot (trail origin marker) */
|
|
434
|
+
--scatter-start-dot-radius: 4;
|
|
435
|
+
--scatter-start-dot-stroke-width: 1.5;
|
|
436
|
+
--scatter-start-dot-opacity: 0.8;
|
|
437
|
+
|
|
438
|
+
/* Animation timing */
|
|
439
|
+
--scatter-animation-speed: 1000;
|
|
440
|
+
|
|
441
|
+
/* Quadrant chart */
|
|
442
|
+
--quadrant-line-width: 1.5;
|
|
443
|
+
--quadrant-bg-opacity: 0.08;
|
|
444
|
+
|
|
445
|
+
/* Density contour / KDE outlines (dark theme flips these to white-based) */
|
|
446
|
+
--contour-line-color: rgba(0, 0, 0, 0.15);
|
|
447
|
+
--kde-contour-line-color: rgba(0, 0, 0, 0.1);
|
|
448
|
+
|
|
449
|
+
/* ===========================
|
|
450
|
+
VOCABULARY FAMILIES (Release C)
|
|
451
|
+
=========================== */
|
|
452
|
+
|
|
453
|
+
/* Histogram */
|
|
454
|
+
--histogram-bar-gap: 1; /* px gap between bin bars */
|
|
455
|
+
--histogram-overlay-opacity: 0.55; /* overlay-series fill opacity */
|
|
456
|
+
|
|
457
|
+
/* Donut */
|
|
458
|
+
--donut-inner-ratio: 0.62; /* inner radius share; 0 = pie */
|
|
459
|
+
--donut-pad-angle: 0.008; /* radians between slices */
|
|
460
|
+
--donut-corner-radius: 2; /* px slice corner rounding */
|
|
461
|
+
|
|
462
|
+
/* Box plot */
|
|
463
|
+
--box-fill-opacity: 0.35; /* box body fill vs stroke */
|
|
464
|
+
--box-median-width: 2.5; /* median line stroke width */
|
|
465
|
+
--box-outlier-radius: 3; /* px outlier ring radius */
|
|
466
|
+
|
|
467
|
+
/* Calendar heatmap */
|
|
468
|
+
--calendar-cell-radius: 2; /* px cell corner rounding */
|
|
469
|
+
--calendar-month-gap: 3; /* px extra gutter at month starts */
|
|
470
|
+
--calendar-max-cell: 22; /* px step cap for the 7-col vertical layout */
|
|
471
|
+
|
|
472
|
+
/* Sankey */
|
|
473
|
+
--sankey-node-width: 12; /* px node bar width */
|
|
474
|
+
--sankey-node-padding: 12; /* px vertical gap between nodes */
|
|
475
|
+
--sankey-link-opacity: 0.3; /* link ribbon opacity (hover +0.25) */
|
|
476
|
+
--sankey-anim-duration: 900; /* ms each column's links take to draw */
|
|
477
|
+
--sankey-anim-pause: 300; /* ms the flow rests at a node before flowing on */
|
|
478
|
+
|
|
479
|
+
/* ===========================
|
|
480
|
+
BUMP CHART SPECIFIC
|
|
481
|
+
=========================== */
|
|
482
|
+
|
|
483
|
+
/* Layout */
|
|
484
|
+
--bump-margin-top: 30;
|
|
485
|
+
--bump-margin-bottom: 40;
|
|
486
|
+
--bump-label-gap: 10;
|
|
487
|
+
--bump-label-padding: 5;
|
|
488
|
+
--bump-axis-offset: 15;
|
|
489
|
+
|
|
490
|
+
/* Line styling */
|
|
491
|
+
--bump-line-width: 2.5;
|
|
492
|
+
--bump-line-width-hover: 3.5;
|
|
493
|
+
|
|
494
|
+
/* Dot styling */
|
|
495
|
+
--bump-dot-radius: 8;
|
|
496
|
+
--bump-dot-radius-hover: 10;
|
|
497
|
+
--bump-dot-stroke-width: 2;
|
|
498
|
+
--bump-rank-font-size: 10px;
|
|
499
|
+
|
|
500
|
+
/* Interaction states */
|
|
501
|
+
--bump-dimmed-opacity: 0.15;
|
|
502
|
+
--bump-transition-duration: 0.2s;
|
|
503
|
+
|
|
504
|
+
/* Animation */
|
|
505
|
+
--bump-animation-duration: 800;
|
|
506
|
+
--bump-stagger-delay: 100;
|
|
507
|
+
--bump-dot-fade-duration: 300;
|
|
508
|
+
|
|
509
|
+
/* ===========================
|
|
510
|
+
SEQUENTIAL COLOR SCALES (bars/heatmaps — createColorScale)
|
|
511
|
+
=========================== */
|
|
512
|
+
--scale-orange-lo: #fff5eb;
|
|
513
|
+
--scale-orange-hi: #ff6b35;
|
|
514
|
+
--scale-orange-red-hi: #d7191c;
|
|
515
|
+
--scale-blue-lo: #e0f3f8;
|
|
516
|
+
--scale-blue-hi: #4575b4;
|
|
517
|
+
--scale-teal-hi: #1a9988;
|
|
518
|
+
--scale-aqua-1: #a4d7e1;
|
|
519
|
+
--scale-aqua-2: #4ec1d4;
|
|
520
|
+
--scale-aqua-3: #0095b7;
|
|
521
|
+
--scale-aqua-4: #005e8b;
|
|
522
|
+
--scale-gray-lo: #f5f5f5;
|
|
523
|
+
--scale-gray-hi: #333333;
|
|
524
|
+
--scale-purple-1: #f3e5f5;
|
|
525
|
+
--scale-purple-2: #ce93d8;
|
|
526
|
+
--scale-purple-3: #ab47bc;
|
|
527
|
+
--scale-purple-4: #7b1fa2;
|
|
528
|
+
--scale-green-1: #e8f5e9;
|
|
529
|
+
--scale-green-2: #a5d6a7;
|
|
530
|
+
--scale-green-3: #66bb6a;
|
|
531
|
+
--scale-green-4: #2e7d32;
|
|
532
|
+
/* Diagonal band scales (Scatter.createDiagonal) — white center outward */
|
|
533
|
+
--scale-diag-green-1: #ffffff;
|
|
534
|
+
--scale-diag-green-2: #c6f6d5;
|
|
535
|
+
--scale-diag-green-3: #68d391;
|
|
536
|
+
--scale-diag-green-4: #38a169;
|
|
537
|
+
--scale-diag-red-1: #ffffff;
|
|
538
|
+
--scale-diag-red-2: #fed7d7;
|
|
539
|
+
--scale-diag-red-3: #fc8181;
|
|
540
|
+
--scale-diag-red-4: #e53e3e;
|
|
541
|
+
|
|
542
|
+
/* ===========================
|
|
543
|
+
BAR CHART SPECIFIC
|
|
544
|
+
=========================== */
|
|
545
|
+
|
|
546
|
+
/* Bar sizing */
|
|
547
|
+
--bar-height: 24px;
|
|
548
|
+
--bar-height-mini: 18px;
|
|
549
|
+
--bar-gap: 8px;
|
|
550
|
+
--bar-border-radius: 3px;
|
|
551
|
+
|
|
552
|
+
/* Zero baseline on mixed-sign bar charts (tracks the dark theme via the
|
|
553
|
+
underlying text token) */
|
|
554
|
+
--bar-zero-line-color: var(--color-text-primary);
|
|
555
|
+
--bar-zero-line-width: 1.5;
|
|
556
|
+
|
|
557
|
+
/* Gantt */
|
|
558
|
+
--gantt-bar-height: 18px;
|
|
559
|
+
--gantt-bar-gap: 6px;
|
|
560
|
+
--gantt-border-radius: 2px;
|
|
561
|
+
|
|
562
|
+
/* Waterfall connector hairlines (between consecutive bars) */
|
|
563
|
+
--waterfall-connector-color: rgba(0, 0, 0, 0.18);
|
|
564
|
+
|
|
565
|
+
/* Container */
|
|
566
|
+
--container-max-width: 660px;
|
|
567
|
+
|
|
568
|
+
/* Bar track (background) */
|
|
569
|
+
--bar-track-color: var(--color-border-row);
|
|
570
|
+
--bar-track-opacity: 0.5;
|
|
571
|
+
|
|
572
|
+
/* Label positioning */
|
|
573
|
+
--bar-label-gap: 8px;
|
|
574
|
+
--bar-value-padding: 6px;
|
|
575
|
+
--bar-inline-threshold: 0.35;
|
|
576
|
+
|
|
577
|
+
/* Lollipop variant */
|
|
578
|
+
--lollipop-stem-width: 2px;
|
|
579
|
+
--lollipop-dot-radius: 6px;
|
|
580
|
+
--lollipop-dot-radius-hover: 8px;
|
|
581
|
+
|
|
582
|
+
/* Bullet chart */
|
|
583
|
+
--bullet-range-opacity: 0.25;
|
|
584
|
+
--bullet-measure-height: 8px;
|
|
585
|
+
--bullet-marker-width: 3px;
|
|
586
|
+
--bullet-marker-height: 16px;
|
|
587
|
+
|
|
588
|
+
/* Animation */
|
|
589
|
+
--bar-animation-duration: 800ms;
|
|
590
|
+
--bar-stagger-delay: 50ms;
|
|
591
|
+
|
|
592
|
+
/* Row layout */
|
|
593
|
+
--bar-row-height: 32px;
|
|
594
|
+
--bar-category-width: 100px;
|
|
595
|
+
|
|
596
|
+
/* ===========================
|
|
597
|
+
STAT FORMATTING OPTIONS
|
|
598
|
+
=========================== */
|
|
599
|
+
/* Baseball batting average format: .345 instead of 0.345
|
|
600
|
+
* Use formatBaseballAvg() helper or set data-format="baseball-avg"
|
|
601
|
+
*
|
|
602
|
+
* JavaScript helper:
|
|
603
|
+
* function formatBaseballAvg(value) {
|
|
604
|
+
* return value.toFixed(3).replace(/^0\./, '.');
|
|
605
|
+
* }
|
|
606
|
+
*/
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/* ===========================
|
|
610
|
+
INSTAGRAM EXPORT TIER
|
|
611
|
+
===========================
|
|
612
|
+
Opt-in via <html data-size="instagram">, paired with `size: 'instagram'`
|
|
613
|
+
in chart config (the SIZE_PRESETS geometry). Scales the text, stroke,
|
|
614
|
+
marker, and annotation tokens for 1080px social-feed posters that are
|
|
615
|
+
viewed at phone size — roughly 2.4x the screen-reading defaults. Colors
|
|
616
|
+
are untouched, so it composes with the dark theme. Same runtime contract
|
|
617
|
+
as dark mode: builders resolve tokens at render, so flipping the
|
|
618
|
+
attribute needs a re-render (ChartFactory.rerenderAll()). */
|
|
619
|
+
:root[data-size="instagram"] {
|
|
620
|
+
/* Typography */
|
|
621
|
+
--font-size-xs: 22px;
|
|
622
|
+
--font-size-sm: 25px;
|
|
623
|
+
--font-size-base: 27px;
|
|
624
|
+
--font-size-md: 29px;
|
|
625
|
+
--font-size-lg: 32px;
|
|
626
|
+
--font-size-xl: 35px;
|
|
627
|
+
--font-size-title: 64px;
|
|
628
|
+
--font-size-subtitle: 32px;
|
|
629
|
+
--shell-title-size: 38px;
|
|
630
|
+
|
|
631
|
+
/* Marks */
|
|
632
|
+
--line-stroke-width: 5;
|
|
633
|
+
--area-stroke-width: 4;
|
|
634
|
+
--line-dot-stroke-width: 4;
|
|
635
|
+
--bar-height: 56;
|
|
636
|
+
--bar-gap: 18;
|
|
637
|
+
|
|
638
|
+
/* Annotations */
|
|
639
|
+
--annotation-title-size: 34px;
|
|
640
|
+
--annotation-event-font-size: 24px;
|
|
641
|
+
--annotation-event-band-offset: 48;
|
|
642
|
+
--annotation-event-tick-height: 18;
|
|
643
|
+
--annotation-leader-threshold: 30;
|
|
644
|
+
|
|
645
|
+
/* Slope label dodge gaps track the scaled label text */
|
|
646
|
+
--slope-label-min-gap: 34;
|
|
647
|
+
--slope-label-min-gap-stacked: 72;
|
|
648
|
+
|
|
649
|
+
/* Bump chart marks at poster weight */
|
|
650
|
+
--bump-line-width: 6;
|
|
651
|
+
--bump-line-width-hover: 8;
|
|
652
|
+
--bump-dot-radius: 17;
|
|
653
|
+
--bump-dot-radius-hover: 20;
|
|
654
|
+
--bump-dot-stroke-width: 4;
|
|
655
|
+
--bump-rank-font-size: 20px;
|
|
656
|
+
--bump-label-gap: 16;
|
|
657
|
+
--bump-axis-offset: 26;
|
|
658
|
+
--bump-margin-top: 40;
|
|
659
|
+
--bump-margin-bottom: 60;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/* ===========================
|
|
663
|
+
DARK THEME
|
|
664
|
+
===========================
|
|
665
|
+
Opt-in via <html data-theme="dark">. Only color-bearing tokens are
|
|
666
|
+
overridden; spacing, sizing, and opacity tokens carry over.
|
|
667
|
+
|
|
668
|
+
Builders resolve tokens live at render time (getCSSVar reads
|
|
669
|
+
documentElement), so switching themes at runtime requires a re-render:
|
|
670
|
+
call chart.rerender() on each chart facade (tables: table.update(data)).
|
|
671
|
+
CSS-styled surfaces (table chrome, tooltip shell, containers) restyle
|
|
672
|
+
immediately without one. */
|
|
673
|
+
:root[data-theme="dark"] {
|
|
674
|
+
/* Text — --color-black is the strongest ink, not literal black */
|
|
675
|
+
--color-black: #f4f4f5;
|
|
676
|
+
--color-text-primary: #e4e4e7;
|
|
677
|
+
--color-text-muted: #a1a1aa;
|
|
678
|
+
--color-text-subtle: #8b8b93;
|
|
679
|
+
--color-text-faint: #71717a;
|
|
680
|
+
--color-text-disabled: #52525b;
|
|
681
|
+
|
|
682
|
+
/* Backgrounds */
|
|
683
|
+
--color-bg-body: #101012;
|
|
684
|
+
--color-bg-container: #1b1b1e;
|
|
685
|
+
--color-bg-hover: #232327;
|
|
686
|
+
--color-bg-highlight: #2a2a2f;
|
|
687
|
+
|
|
688
|
+
/* Borders */
|
|
689
|
+
--color-border-header: #e4e4e7;
|
|
690
|
+
--color-border-row: #2a2a2e;
|
|
691
|
+
--color-border-light: #333338;
|
|
692
|
+
--color-border-footer: #2f2f34;
|
|
693
|
+
--color-border-tooltip: #3f3f46;
|
|
694
|
+
|
|
695
|
+
/* Shadows */
|
|
696
|
+
--shadow-container: 0 1px 3px rgba(0, 0, 0, 0.6);
|
|
697
|
+
--shadow-tooltip: 0 4px 14px rgba(0, 0, 0, 0.6);
|
|
698
|
+
|
|
699
|
+
/* Chart palette — brightened for dark surfaces */
|
|
700
|
+
--chart-primary: #60a5fa;
|
|
701
|
+
--chart-primary-light: rgba(96, 165, 250, 0.35);
|
|
702
|
+
--chart-primary-faint: rgba(96, 165, 250, 0.12);
|
|
703
|
+
--chart-secondary: #4ade80;
|
|
704
|
+
--chart-tertiary: #fb923c;
|
|
705
|
+
--chart-green: #4ade80;
|
|
706
|
+
--chart-lime: #a3e635;
|
|
707
|
+
--chart-orange: #fb923c;
|
|
708
|
+
--chart-purple: #c084fc;
|
|
709
|
+
--chart-purple-light: rgba(192, 132, 252, 0.4);
|
|
710
|
+
--chart-purple-faint: rgba(192, 132, 252, 0.08);
|
|
711
|
+
--chart-red: #f87171;
|
|
712
|
+
|
|
713
|
+
/* Multi-series palette — lighter gray is the more prominent one on dark */
|
|
714
|
+
--chart-series-1: #64748b;
|
|
715
|
+
--chart-series-2: #94a3b8;
|
|
716
|
+
|
|
717
|
+
/* Neutral/gray for cumulative/baseline lines */
|
|
718
|
+
--chart-neutral: #9ca3af;
|
|
719
|
+
--chart-neutral-light: rgba(156, 163, 175, 0.4);
|
|
720
|
+
--chart-neutral-faint: rgba(156, 163, 175, 0.08);
|
|
721
|
+
|
|
722
|
+
/* Slope */
|
|
723
|
+
--slope-muted-color: #52525b;
|
|
724
|
+
--slope-band-1: #2c1d1d;
|
|
725
|
+
--slope-band-2: #2b2717;
|
|
726
|
+
--slope-band-3: #16281c;
|
|
727
|
+
--slope-band-4: #17222f;
|
|
728
|
+
|
|
729
|
+
/* Misc structural strokes */
|
|
730
|
+
--stream-stroke-color: #1b1b1e;
|
|
731
|
+
--waterfall-connector-color: #52525b;
|
|
732
|
+
--dot-connector-color: #3f3f46;
|
|
733
|
+
--annotation-event-guide-color: #52525b;
|
|
734
|
+
--contour-line-color: rgba(255, 255, 255, 0.15);
|
|
735
|
+
--kde-contour-line-color: rgba(255, 255, 255, 0.12);
|
|
736
|
+
|
|
737
|
+
/* Categorical series palette — Set2, lifted for dark backgrounds */
|
|
738
|
+
--area-color-1: #74d0b3;
|
|
739
|
+
--area-color-2: #ff9e78;
|
|
740
|
+
--area-color-3: #a2b3dc;
|
|
741
|
+
--area-color-4: #ef9dd0;
|
|
742
|
+
--area-color-5: #b6e268;
|
|
743
|
+
--area-color-6: #ffe45c;
|
|
744
|
+
--area-color-7: #ecd2a8;
|
|
745
|
+
--area-color-8: #c6c6c6;
|
|
746
|
+
|
|
747
|
+
/* Temperature palette — extremes stay bright, middle fades to surface */
|
|
748
|
+
--temp-cold-10: #3f7fd1;
|
|
749
|
+
--temp-cold-9: #38689f;
|
|
750
|
+
--temp-cold-8: #2f5377;
|
|
751
|
+
--temp-cold-7: #263e52;
|
|
752
|
+
--temp-neutral: #26262b;
|
|
753
|
+
--temp-warm-5: #4a3a20;
|
|
754
|
+
--temp-warm-4: #6e4f22;
|
|
755
|
+
--temp-warm-3: #9c6a25;
|
|
756
|
+
--temp-warm-2: #d07f1f;
|
|
757
|
+
--temp-warm-1: #ff8a3d;
|
|
758
|
+
|
|
759
|
+
/* Sequential scales — lo ends re-anchor from near-white to surface-tinted
|
|
760
|
+
darks so low values fade into the background, high values stay vivid */
|
|
761
|
+
--scale-orange-lo: #2e2018;
|
|
762
|
+
--scale-orange-hi: #ff8a5c;
|
|
763
|
+
--scale-orange-red-hi: #e35d5d;
|
|
764
|
+
--scale-blue-lo: #16202c;
|
|
765
|
+
--scale-blue-hi: #6ea8dc;
|
|
766
|
+
--scale-teal-hi: #34b3a0;
|
|
767
|
+
--scale-aqua-1: #1a3a42;
|
|
768
|
+
--scale-aqua-2: #16606e;
|
|
769
|
+
--scale-aqua-3: #1d92ab;
|
|
770
|
+
--scale-aqua-4: #5cc6da;
|
|
771
|
+
--scale-gray-lo: #232326;
|
|
772
|
+
--scale-gray-hi: #d4d4d8;
|
|
773
|
+
--scale-purple-1: #241a2b;
|
|
774
|
+
--scale-purple-2: #4a3357;
|
|
775
|
+
--scale-purple-3: #7b57a0;
|
|
776
|
+
--scale-purple-4: #b58ae0;
|
|
777
|
+
--scale-green-1: #16241a;
|
|
778
|
+
--scale-green-2: #2f5138;
|
|
779
|
+
--scale-green-3: #4f8c58;
|
|
780
|
+
--scale-green-4: #7ac47f;
|
|
781
|
+
--scale-diag-green-1: #1b1b1e;
|
|
782
|
+
--scale-diag-green-2: #1d3524;
|
|
783
|
+
--scale-diag-green-3: #2f6b40;
|
|
784
|
+
--scale-diag-green-4: #4caf6d;
|
|
785
|
+
--scale-diag-red-1: #1b1b1e;
|
|
786
|
+
--scale-diag-red-2: #3a2023;
|
|
787
|
+
--scale-diag-red-3: #7a3b3b;
|
|
788
|
+
--scale-diag-red-4: #c25959;
|
|
789
|
+
|
|
790
|
+
/* Chart story — band lifts off the dark container surface; accent follows
|
|
791
|
+
--chart-primary automatically, and the panel wash re-mixes from it */
|
|
792
|
+
--story-band-bg: #2a2a2f;
|
|
793
|
+
--story-band-text: #e4e4e7;
|
|
794
|
+
--story-band-label: #a1a1aa;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
/* ==== src/core/base.css ==== */
|
|
799
|
+
/**
|
|
800
|
+
* Chart Factory - Shared Base Styles
|
|
801
|
+
*
|
|
802
|
+
* Common styles used across all D3 visualization components.
|
|
803
|
+
* Import after tokens.css:
|
|
804
|
+
*
|
|
805
|
+
* @import 'chart-factory/core/tokens.css';
|
|
806
|
+
* @import 'chart-factory/core/base.css';
|
|
807
|
+
*/
|
|
808
|
+
|
|
809
|
+
/* ===========================
|
|
810
|
+
CONTAINER
|
|
811
|
+
=========================== */
|
|
812
|
+
|
|
813
|
+
.d3-container {
|
|
814
|
+
display: block;
|
|
815
|
+
width: 100%;
|
|
816
|
+
max-width: var(--container-max-width, 660px);
|
|
817
|
+
box-sizing: border-box;
|
|
818
|
+
background: var(--color-bg-container);
|
|
819
|
+
padding: var(--container-padding, var(--space-8));
|
|
820
|
+
box-shadow: var(--shadow-container);
|
|
821
|
+
text-align: left;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/* Charts fill their container; svg viewBox scaling is applied by the factory */
|
|
825
|
+
.d3-container svg,
|
|
826
|
+
.chart-container svg {
|
|
827
|
+
display: block;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/* ===========================
|
|
831
|
+
CHART ELEMENT DEFAULTS
|
|
832
|
+
===========================
|
|
833
|
+
The canonical look for factory-generated SVG elements. Historically these
|
|
834
|
+
rules were copy-pasted into every example page's <style> block, which meant
|
|
835
|
+
package consumers loading only tokens.css + base.css got browser-default
|
|
836
|
+
black axes and gridlines. They belong here. Scoped under `svg` so generic
|
|
837
|
+
class names (.line, .dot) don't leak onto host-page HTML. */
|
|
838
|
+
|
|
839
|
+
svg .line {
|
|
840
|
+
fill: none;
|
|
841
|
+
stroke-width: var(--line-stroke-width);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
svg .axis-x,
|
|
845
|
+
svg .axis-y {
|
|
846
|
+
font-family: var(--font-family);
|
|
847
|
+
font-size: var(--font-size-sm);
|
|
848
|
+
color: var(--axis-color);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
svg .axis-x path,
|
|
852
|
+
svg .axis-y path,
|
|
853
|
+
svg .axis-x line,
|
|
854
|
+
svg .axis-y line {
|
|
855
|
+
stroke: var(--color-border-light, #e5e5e5);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
svg .axis-y .domain {
|
|
859
|
+
display: none;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
svg .grid line {
|
|
863
|
+
stroke: var(--color-border-light, #e5e5e5);
|
|
864
|
+
stroke-opacity: var(--grid-stroke-opacity);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
svg .grid path {
|
|
868
|
+
stroke-width: 0;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/* Fallback fill only — dots that set their own fill attribute (per-category
|
|
872
|
+
coloring in the Dot family) keep it. */
|
|
873
|
+
svg .dot:not([fill]) {
|
|
874
|
+
fill: var(--chart-primary);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/* Legend swatches must never be squished by a tight flex row (would turn a
|
|
878
|
+
circular dot into an oval) — pin their box regardless of available space. */
|
|
879
|
+
.legend-dot,
|
|
880
|
+
.legend-swatch {
|
|
881
|
+
flex: none;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
/* ===========================
|
|
885
|
+
TITLE / SUBTITLE / FOOTER
|
|
886
|
+
=========================== */
|
|
887
|
+
|
|
888
|
+
.d3-title {
|
|
889
|
+
font-family: var(--font-family);
|
|
890
|
+
font-size: var(--font-size-title);
|
|
891
|
+
font-weight: var(--font-weight-bold);
|
|
892
|
+
margin: 0 0 var(--space-2) 0;
|
|
893
|
+
color: var(--color-black);
|
|
894
|
+
letter-spacing: var(--letter-spacing-tight);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
.d3-subtitle {
|
|
898
|
+
font-family: var(--font-family);
|
|
899
|
+
font-size: var(--font-size-subtitle);
|
|
900
|
+
color: var(--color-text-subtle);
|
|
901
|
+
margin: 0 0 var(--space-6) 0;
|
|
902
|
+
line-height: var(--line-height-normal);
|
|
903
|
+
font-weight: var(--font-weight-normal);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.d3-footer {
|
|
907
|
+
margin-top: 0;
|
|
908
|
+
padding-top: var(--space-4);
|
|
909
|
+
border-top: 1px solid var(--color-border-footer);
|
|
910
|
+
font-family: var(--font-family);
|
|
911
|
+
font-size: var(--font-size-xs);
|
|
912
|
+
color: var(--color-text-faint);
|
|
913
|
+
text-transform: uppercase;
|
|
914
|
+
letter-spacing: var(--letter-spacing-wider);
|
|
915
|
+
font-weight: var(--font-weight-semibold);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/* ===========================
|
|
919
|
+
TOOLTIPS (Shared)
|
|
920
|
+
=========================== */
|
|
921
|
+
|
|
922
|
+
/* Chart hover tooltip — the node TooltipManager creates ('.d3-tooltip').
|
|
923
|
+
Positioning (fixed/left/top/opacity/z-index) is managed by JS; this class
|
|
924
|
+
owns the appearance. */
|
|
925
|
+
.d3-tooltip {
|
|
926
|
+
position: fixed;
|
|
927
|
+
background: var(--color-bg-container);
|
|
928
|
+
border: 1px solid var(--color-border-tooltip);
|
|
929
|
+
border-radius: 6px;
|
|
930
|
+
padding: var(--tooltip-padding, 10px 13px);
|
|
931
|
+
box-shadow: var(--shadow-tooltip);
|
|
932
|
+
z-index: var(--tooltip-z-index, 9999);
|
|
933
|
+
pointer-events: none;
|
|
934
|
+
font-family: var(--font-family);
|
|
935
|
+
font-size: var(--tooltip-font-size, 12px);
|
|
936
|
+
line-height: 1.45;
|
|
937
|
+
min-width: var(--tooltip-min-width, 130px);
|
|
938
|
+
max-width: 320px;
|
|
939
|
+
display: flex;
|
|
940
|
+
flex-direction: column;
|
|
941
|
+
gap: var(--tooltip-row-gap, 4px);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/* Standard tooltip shell rows (TooltipManager.rows).
|
|
945
|
+
Hierarchy: title > values > labels > notes.
|
|
946
|
+
Alignment discipline: text labels flush LEFT, numeric values flush RIGHT. */
|
|
947
|
+
.d3-tooltip .d3-tooltip-title {
|
|
948
|
+
font-size: var(--tooltip-title-font-size, 15px);
|
|
949
|
+
font-weight: var(--font-weight-semibold);
|
|
950
|
+
color: var(--color-black);
|
|
951
|
+
letter-spacing: var(--letter-spacing-tight);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
.d3-tooltip .d3-tooltip-label {
|
|
955
|
+
color: var(--color-text-subtle);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
.d3-tooltip .d3-tooltip-value {
|
|
959
|
+
font-weight: var(--font-weight-semibold);
|
|
960
|
+
color: var(--color-black);
|
|
961
|
+
font-variant-numeric: tabular-nums;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
.d3-tooltip .d3-tooltip-row {
|
|
965
|
+
display: flex;
|
|
966
|
+
align-items: baseline;
|
|
967
|
+
justify-content: space-between;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/* In swatched rows the label is the middle of three flex children —
|
|
971
|
+
grow it so it hugs the swatch on the left instead of centering.
|
|
972
|
+
(No flex gap: the swatch's own margin spaces it from the label; the
|
|
973
|
+
value's margin guarantees the minimum label-to-value separation.) */
|
|
974
|
+
.d3-tooltip .d3-tooltip-row .d3-tooltip-label {
|
|
975
|
+
flex: 1 1 auto;
|
|
976
|
+
text-align: left;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
.d3-tooltip .d3-tooltip-row .d3-tooltip-value {
|
|
980
|
+
text-align: right;
|
|
981
|
+
margin-left: var(--space-4);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.d3-tooltip .d3-tooltip-note {
|
|
985
|
+
font-size: var(--font-size-sm);
|
|
986
|
+
color: var(--color-text-muted);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
.d3-tooltip .d3-tooltip-footer {
|
|
990
|
+
border-top: 1px solid var(--color-border-row);
|
|
991
|
+
padding-top: var(--tooltip-row-gap, 3px);
|
|
992
|
+
margin-top: 1px;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
.d3-tooltip .d3-tooltip-bar {
|
|
996
|
+
height: 3px;
|
|
997
|
+
border-radius: 2px;
|
|
998
|
+
margin-top: 2px;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.d3-tooltip .d3-tooltip-swatch {
|
|
1002
|
+
display: inline-block;
|
|
1003
|
+
width: 8px;
|
|
1004
|
+
height: 8px;
|
|
1005
|
+
border-radius: 50%;
|
|
1006
|
+
margin-right: var(--space-1);
|
|
1007
|
+
flex: none;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
.d3-tooltip-chart {
|
|
1011
|
+
display: flex;
|
|
1012
|
+
align-items: flex-end;
|
|
1013
|
+
gap: var(--tooltip-bar-gap);
|
|
1014
|
+
height: var(--tooltip-chart-height);
|
|
1015
|
+
position: relative;
|
|
1016
|
+
box-sizing: border-box;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
.d3-tooltip-bar {
|
|
1020
|
+
flex: 1;
|
|
1021
|
+
background-color: var(--color-text-muted);
|
|
1022
|
+
border-radius: var(--tooltip-bar-border-radius);
|
|
1023
|
+
min-width: var(--tooltip-bar-min-width);
|
|
1024
|
+
position: relative;
|
|
1025
|
+
max-height: var(--tooltip-chart-height);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
.d3-tooltip-bar-label {
|
|
1029
|
+
position: absolute;
|
|
1030
|
+
bottom: calc(-1 * var(--tooltip-label-offset-y));
|
|
1031
|
+
left: 50%;
|
|
1032
|
+
transform: translateX(-50%);
|
|
1033
|
+
font-size: var(--font-size-xs);
|
|
1034
|
+
color: var(--color-text-muted);
|
|
1035
|
+
white-space: nowrap;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
.d3-tooltip-bar-value {
|
|
1039
|
+
position: absolute;
|
|
1040
|
+
top: calc(-1 * var(--tooltip-value-offset-y));
|
|
1041
|
+
left: 50%;
|
|
1042
|
+
transform: translateX(-50%);
|
|
1043
|
+
font-size: var(--font-size-sm);
|
|
1044
|
+
font-weight: var(--font-weight-semibold);
|
|
1045
|
+
color: var(--color-black);
|
|
1046
|
+
white-space: nowrap;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/* ===========================
|
|
1050
|
+
RESPONSIVE
|
|
1051
|
+
=========================== */
|
|
1052
|
+
|
|
1053
|
+
@media (max-width: 768px) {
|
|
1054
|
+
.d3-container {
|
|
1055
|
+
padding: var(--container-padding-mobile);
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
.d3-title {
|
|
1059
|
+
font-size: var(--title-font-size-mobile);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/* ===========================
|
|
1064
|
+
CHART SHELL (universal config: title / subtitle / legend / footer)
|
|
1065
|
+
Rendered by the responsive wrapper around the chart svg — one
|
|
1066
|
+
implementation for every builder, token-driven, dark-mode-live.
|
|
1067
|
+
=========================== */
|
|
1068
|
+
.d3-shell-title {
|
|
1069
|
+
font-family: var(--font-family);
|
|
1070
|
+
font-size: var(--shell-title-size, 15px);
|
|
1071
|
+
font-weight: var(--font-weight-bold);
|
|
1072
|
+
color: var(--color-black);
|
|
1073
|
+
letter-spacing: var(--letter-spacing-tight);
|
|
1074
|
+
margin: 0 0 2px 0;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
.d3-shell-subtitle {
|
|
1078
|
+
font-family: var(--font-family);
|
|
1079
|
+
font-size: var(--shell-subtitle-size, 11px);
|
|
1080
|
+
color: var(--color-text-subtle);
|
|
1081
|
+
margin: 0 0 var(--space-3) 0;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
.d3-shell--featured.d3-shell-title {
|
|
1085
|
+
font-size: var(--shell-featured-title-size, 20px);
|
|
1086
|
+
margin-bottom: var(--space-1);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
.d3-shell--featured.d3-shell-subtitle {
|
|
1090
|
+
font-size: var(--shell-featured-subtitle-size, 14px);
|
|
1091
|
+
margin-bottom: var(--space-4);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
.d3-shell-legend {
|
|
1095
|
+
margin-bottom: var(--space-3);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
.d3-shell-footer {
|
|
1099
|
+
font-family: var(--font-family);
|
|
1100
|
+
font-size: var(--font-size-xs);
|
|
1101
|
+
color: var(--color-text-faint);
|
|
1102
|
+
text-transform: uppercase;
|
|
1103
|
+
letter-spacing: var(--letter-spacing-wide);
|
|
1104
|
+
margin-top: var(--space-4);
|
|
1105
|
+
padding-top: var(--space-3);
|
|
1106
|
+
border-top: 1px solid var(--color-border-row);
|
|
1107
|
+
display: flex;
|
|
1108
|
+
justify-content: space-between;
|
|
1109
|
+
align-items: center;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/* Chart story (story: config): narrative summary module rendered by the
|
|
1113
|
+
wrapper below the svg, above the footer. Variants replicate the picked
|
|
1114
|
+
story-module mockups: panel (accent-tinted takeaway), bubble (card with
|
|
1115
|
+
an up-pointing tail — the chart is speaking), band (full-width dark
|
|
1116
|
+
stat lede). All colors flow through the --story-* tokens; the `accent`
|
|
1117
|
+
option overrides --story-accent inline on the module. */
|
|
1118
|
+
.chart-story {
|
|
1119
|
+
font-family: var(--font-family);
|
|
1120
|
+
font-size: var(--story-font-size);
|
|
1121
|
+
line-height: var(--story-line-height);
|
|
1122
|
+
color: var(--color-text-primary);
|
|
1123
|
+
margin-top: var(--space-3);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
.chart-story-body { margin: 0; }
|
|
1127
|
+
|
|
1128
|
+
/* Optional uppercase lead-in (bubble and band variants) */
|
|
1129
|
+
.chart-story-overline {
|
|
1130
|
+
display: flex;
|
|
1131
|
+
align-items: center;
|
|
1132
|
+
gap: var(--space-1);
|
|
1133
|
+
font-size: var(--font-size-sm);
|
|
1134
|
+
font-weight: var(--font-weight-bold);
|
|
1135
|
+
letter-spacing: var(--letter-spacing-wider);
|
|
1136
|
+
text-transform: uppercase;
|
|
1137
|
+
color: var(--color-text-subtle);
|
|
1138
|
+
margin-bottom: 3px;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
.chart-story-icon { display: inline-flex; flex: none; }
|
|
1142
|
+
.chart-story-icon svg { display: block; }
|
|
1143
|
+
|
|
1144
|
+
/* panel — soft accent wash + accent hairline; icon and bold accent label
|
|
1145
|
+
lead the prose inline */
|
|
1146
|
+
.chart-story--panel {
|
|
1147
|
+
display: flex;
|
|
1148
|
+
gap: var(--space-2);
|
|
1149
|
+
align-items: flex-start;
|
|
1150
|
+
background: color-mix(in srgb, var(--story-accent) var(--story-panel-bg-alpha), transparent);
|
|
1151
|
+
border: 1px solid color-mix(in srgb, var(--story-accent) var(--story-panel-border-alpha), transparent);
|
|
1152
|
+
border-radius: 8px;
|
|
1153
|
+
padding: 11px 14px;
|
|
1154
|
+
}
|
|
1155
|
+
.chart-story--panel .chart-story-icon { margin-top: 2px; }
|
|
1156
|
+
.chart-story-label {
|
|
1157
|
+
color: var(--story-accent);
|
|
1158
|
+
font-weight: var(--font-weight-bold);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/* bubble — container-background card whose rotated-square tail points up
|
|
1162
|
+
at the chart */
|
|
1163
|
+
.chart-story--bubble {
|
|
1164
|
+
position: relative;
|
|
1165
|
+
margin-top: calc(var(--space-3) + 7px); /* room for the tail */
|
|
1166
|
+
background: var(--color-bg-container);
|
|
1167
|
+
border: 1px solid var(--color-border-light);
|
|
1168
|
+
border-radius: 10px;
|
|
1169
|
+
padding: 11px 15px;
|
|
1170
|
+
box-shadow: var(--shadow-tooltip);
|
|
1171
|
+
}
|
|
1172
|
+
.chart-story--bubble::before {
|
|
1173
|
+
content: "";
|
|
1174
|
+
position: absolute;
|
|
1175
|
+
top: -7px;
|
|
1176
|
+
left: 34px;
|
|
1177
|
+
width: 12px;
|
|
1178
|
+
height: 12px;
|
|
1179
|
+
background: var(--color-bg-container);
|
|
1180
|
+
border-left: 1px solid var(--color-border-light);
|
|
1181
|
+
border-top: 1px solid var(--color-border-light);
|
|
1182
|
+
transform: rotate(45deg);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/* band — full-width dark lede strip; spans the CHART CONTAINER (not any
|
|
1186
|
+
outer card the page may have); <b>/<strong> pick up the accent */
|
|
1187
|
+
.chart-story--band {
|
|
1188
|
+
background: var(--story-band-bg);
|
|
1189
|
+
color: var(--story-band-text);
|
|
1190
|
+
border-radius: 6px;
|
|
1191
|
+
padding: 13px 16px 14px;
|
|
1192
|
+
}
|
|
1193
|
+
.chart-story--band .chart-story-overline { color: var(--story-band-label); }
|
|
1194
|
+
.chart-story--band .chart-story-body b,
|
|
1195
|
+
.chart-story--band .chart-story-body strong { color: var(--story-accent); }
|
|
1196
|
+
|
|
1197
|
+
/* ===========================
|
|
1198
|
+
CHART STATES (state: 'loading' | 'empty' | 'error')
|
|
1199
|
+
===========================
|
|
1200
|
+
The state box takes the chart's exact footprint (height set inline by
|
|
1201
|
+
the wrapper), so the real chart swaps in with zero layout jump. Loading
|
|
1202
|
+
skeletons breathe on a constant 5s cadence with one straight shimmer
|
|
1203
|
+
band (9s cycle: ~3.3s crossing + ~5.7s rest) clipped to the shapes. */
|
|
1204
|
+
|
|
1205
|
+
.d3-state {
|
|
1206
|
+
position: relative;
|
|
1207
|
+
width: 100%;
|
|
1208
|
+
overflow: hidden;
|
|
1209
|
+
display: flex;
|
|
1210
|
+
flex-direction: column;
|
|
1211
|
+
align-items: center;
|
|
1212
|
+
justify-content: center;
|
|
1213
|
+
gap: var(--space-2);
|
|
1214
|
+
text-align: center;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
.d3-state-msg {
|
|
1218
|
+
font-family: var(--font-family);
|
|
1219
|
+
font-size: var(--font-size-md);
|
|
1220
|
+
color: var(--color-text-muted);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
.d3-state-glyph {
|
|
1224
|
+
font-size: 22px;
|
|
1225
|
+
line-height: 1;
|
|
1226
|
+
color: var(--color-text-faint);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
.d3-state--error .d3-state-glyph { color: var(--chart-red); opacity: 0.7; }
|
|
1230
|
+
.d3-state--error .d3-state-msg { color: var(--color-text-primary); }
|
|
1231
|
+
|
|
1232
|
+
/* Loading fallback: three pulsing dots */
|
|
1233
|
+
.d3-state-dots { display: flex; gap: 5px; }
|
|
1234
|
+
.d3-state-dots span {
|
|
1235
|
+
width: 7px;
|
|
1236
|
+
height: 7px;
|
|
1237
|
+
border-radius: 50%;
|
|
1238
|
+
background: var(--color-text-faint);
|
|
1239
|
+
animation: d3-state-pulse 1.2s ease-in-out infinite;
|
|
1240
|
+
}
|
|
1241
|
+
.d3-state-dots span:nth-child(2) { animation-delay: 0.2s; }
|
|
1242
|
+
.d3-state-dots span:nth-child(3) { animation-delay: 0.4s; }
|
|
1243
|
+
@keyframes d3-state-pulse {
|
|
1244
|
+
0%, 80%, 100% { opacity: 0.25; transform: scale(0.85); }
|
|
1245
|
+
40% { opacity: 1; transform: scale(1); }
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/* Loading skeleton: breathing columns. The shimmer is ONE band swept
|
|
1249
|
+
across the whole set — each column's ::after is sized/offset in
|
|
1250
|
+
container coordinates (via the --sw-* custom properties the wrapper
|
|
1251
|
+
sets) so the gradient lines up seamlessly across all columns. */
|
|
1252
|
+
.d3-state--columns { display: block; }
|
|
1253
|
+
.d3-skel-col {
|
|
1254
|
+
position: absolute;
|
|
1255
|
+
bottom: 8%;
|
|
1256
|
+
border-radius: 3px 3px 0 0;
|
|
1257
|
+
background: var(--color-bg-highlight);
|
|
1258
|
+
overflow: hidden;
|
|
1259
|
+
transition: height 5s ease-in-out;
|
|
1260
|
+
}
|
|
1261
|
+
.d3-skel-col::after {
|
|
1262
|
+
content: '';
|
|
1263
|
+
position: absolute;
|
|
1264
|
+
top: 0;
|
|
1265
|
+
bottom: 0;
|
|
1266
|
+
left: var(--sw-left);
|
|
1267
|
+
width: var(--sw-width);
|
|
1268
|
+
background: linear-gradient(90deg, transparent 42%,
|
|
1269
|
+
color-mix(in srgb, var(--color-text-faint) 16%, transparent) 50%,
|
|
1270
|
+
transparent 58%);
|
|
1271
|
+
animation: d3-skel-sweep 9s linear infinite;
|
|
1272
|
+
transform: translateX(-100%);
|
|
1273
|
+
}
|
|
1274
|
+
@keyframes d3-skel-sweep {
|
|
1275
|
+
0% { transform: translateX(-100%); }
|
|
1276
|
+
37% { transform: translateX(100%); }
|
|
1277
|
+
100% { transform: translateX(100%); }
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
/* Rows/trend skeletons are SVG (shimmer clipped to the shapes via SMIL);
|
|
1281
|
+
the svg just needs to fill the state box. */
|
|
1282
|
+
.d3-state--rows svg, .d3-state--trend svg { display: block; width: 100%; height: 100%; }
|
|
1283
|
+
|
|
1284
|
+
/* ============================================
|
|
1285
|
+
Facet helper (ChartFactory.facet) — minimal small-multiples chrome.
|
|
1286
|
+
The grid itself is inline (display:grid on the caller's container);
|
|
1287
|
+
these style the panels the helper creates inside it.
|
|
1288
|
+
============================================ */
|
|
1289
|
+
.d3-facet-panel { min-width: 0; }
|
|
1290
|
+
.d3-facet-chart { display: block; width: 100%; }
|
|
1291
|
+
|
|
1292
|
+
/* Pulsing anomaly rings (Line.createPulseAnomalies) — library-owned so npm
|
|
1293
|
+
consumers get the pulse without copying example CSS. The builder sets
|
|
1294
|
+
animation: none inline when animate: false; users who prefer reduced
|
|
1295
|
+
motion get static rings automatically. */
|
|
1296
|
+
svg circle.anomaly-pulse {
|
|
1297
|
+
animation: d3-anomaly-pulse var(--chart-animation-duration, 2s) ease-in-out infinite;
|
|
1298
|
+
}
|
|
1299
|
+
@keyframes d3-anomaly-pulse {
|
|
1300
|
+
0% { opacity: 0.6; r: var(--anomaly-pulse-radius, 4px); }
|
|
1301
|
+
50% { opacity: 0; r: calc(var(--anomaly-pulse-radius, 4px) * 4); }
|
|
1302
|
+
100% { opacity: 0.6; r: var(--anomaly-pulse-radius, 4px); }
|
|
1303
|
+
}
|
|
1304
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1305
|
+
svg circle.anomaly-pulse { animation: none; }
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
|
|
1309
|
+
/* ==== src/table/tokens.css ==== */
|
|
1310
|
+
/**
|
|
1311
|
+
* D3Table - Table-Specific Design Tokens
|
|
1312
|
+
*
|
|
1313
|
+
* These tokens extend the core tokens for table-specific styling.
|
|
1314
|
+
* Import after core tokens:
|
|
1315
|
+
*
|
|
1316
|
+
* @import 'chart-factory/core/tokens.css';
|
|
1317
|
+
* @import 'chart-factory/table/tokens.css';
|
|
1318
|
+
*/
|
|
1319
|
+
|
|
1320
|
+
:root {
|
|
1321
|
+
/* ===========================
|
|
1322
|
+
TABLE TYPOGRAPHY
|
|
1323
|
+
=========================== */
|
|
1324
|
+
--table-font-size: var(--font-size-lg);
|
|
1325
|
+
--table-header-font-size: var(--font-size-sm);
|
|
1326
|
+
|
|
1327
|
+
/* Cell type font sizes (generic names) */
|
|
1328
|
+
--table-primary-font-size: var(--font-size-xl); /* Bold key column */
|
|
1329
|
+
--table-secondary-font-size: var(--font-size-md); /* Muted index column */
|
|
1330
|
+
|
|
1331
|
+
/* Key-column font family — column-width measurement honors it, so themes
|
|
1332
|
+
can retarget the primary cells' face without breaking layout */
|
|
1333
|
+
--table-primary-font: var(--font-family);
|
|
1334
|
+
|
|
1335
|
+
/* Legacy aliases (for backwards compatibility) */
|
|
1336
|
+
--table-team-font-size: var(--table-primary-font-size);
|
|
1337
|
+
--table-rank-font-size: var(--table-secondary-font-size);
|
|
1338
|
+
|
|
1339
|
+
/* ===========================
|
|
1340
|
+
TABLE LAYOUT
|
|
1341
|
+
=========================== */
|
|
1342
|
+
--table-cell-height: var(--space-7);
|
|
1343
|
+
--table-cell-padding-y: var(--space-1);
|
|
1344
|
+
--table-cell-padding-x: var(--space-5);
|
|
1345
|
+
--table-cell-padding-near: var(--space-2); /* Small padding on aligned edge */
|
|
1346
|
+
--table-header-padding-y: var(--space-2);
|
|
1347
|
+
--table-header-padding-bottom: var(--space-1);
|
|
1348
|
+
|
|
1349
|
+
--table-header-border: 2px solid var(--color-border-header);
|
|
1350
|
+
--table-row-border: 1px solid var(--color-border-row);
|
|
1351
|
+
|
|
1352
|
+
/* ===========================
|
|
1353
|
+
TABLE BAR CHARTS
|
|
1354
|
+
=========================== */
|
|
1355
|
+
--bar-margin-y: 3px;
|
|
1356
|
+
--table-bar-border-radius: 2px; /* renamed from --bar-border-radius, which collided with core tokens */
|
|
1357
|
+
--bar-opacity: 0.85;
|
|
1358
|
+
--bar-max-width-percent: 70; /* Max bar width as percentage of cell */
|
|
1359
|
+
--bar-max-width-percent-highlighted: 65; /* Slightly smaller when highlighted */
|
|
1360
|
+
--bar-label-max-position: 75; /* Max label position as percentage */
|
|
1361
|
+
--bar-label-margin: 2%;
|
|
1362
|
+
--bar-text-padding: var(--space-1);
|
|
1363
|
+
--bar-column-min-width: 130px;
|
|
1364
|
+
--bar-column-max-width: 250px;
|
|
1365
|
+
|
|
1366
|
+
/* Wrapped text cells (column config wrap: true) */
|
|
1367
|
+
--table-wrap-line-height: 1.35;
|
|
1368
|
+
--table-wrap-min-width: 110px; /* fill-layout floor for wrap columns */
|
|
1369
|
+
|
|
1370
|
+
/* Sorted-column highlight (neutral wash on the user-sorted column) */
|
|
1371
|
+
--table-sorted-column-bg: rgba(0, 0, 0, 0.03);
|
|
1372
|
+
--table-sorted-column-bg-hover: rgba(0, 0, 0, 0.05);
|
|
1373
|
+
|
|
1374
|
+
/* ===========================
|
|
1375
|
+
TABLE STICKY / CAP / TOTALS
|
|
1376
|
+
=========================== */
|
|
1377
|
+
--table-sticky-bg: #ffffff; /* opaque backing for stuck header/column cells */
|
|
1378
|
+
--table-sticky-edge-shadow: 6px 0 8px -6px rgba(0, 0, 0, 0.18);
|
|
1379
|
+
--table-total-border: 2px solid var(--color-black);
|
|
1380
|
+
|
|
1381
|
+
/* Column group header rule (inset from cell edges to gap adjacent groups) */
|
|
1382
|
+
--table-group-rule-color: var(--color-border-row);
|
|
1383
|
+
--table-group-rule-width: 1px;
|
|
1384
|
+
--table-group-rule-inset: 10px;
|
|
1385
|
+
|
|
1386
|
+
/* ===========================
|
|
1387
|
+
TABLE EXPANDABLE SUB-ROWS
|
|
1388
|
+
=========================== */
|
|
1389
|
+
--table-toggle-width: 14px;
|
|
1390
|
+
--table-subrow-indent: 22px;
|
|
1391
|
+
|
|
1392
|
+
/* ===========================
|
|
1393
|
+
TABLE IMAGES (logos / flags / headshots)
|
|
1394
|
+
=========================== */
|
|
1395
|
+
--table-image-size: 22px;
|
|
1396
|
+
--table-image-gap: var(--space-2);
|
|
1397
|
+
--table-image-radius: 4px;
|
|
1398
|
+
|
|
1399
|
+
/* ===========================
|
|
1400
|
+
TABLE HEATMAP
|
|
1401
|
+
=========================== */
|
|
1402
|
+
--heatmap-box-padding: var(--space-2);
|
|
1403
|
+
--heatmap-box-height: 25px;
|
|
1404
|
+
--heatmap-box-min-width: 40px;
|
|
1405
|
+
--heatmap-box-border-radius: var(--table-bar-border-radius);
|
|
1406
|
+
|
|
1407
|
+
/* ===========================
|
|
1408
|
+
TABLE COLUMN WIDTHS
|
|
1409
|
+
=========================== */
|
|
1410
|
+
--col-primary-max-width: 250px;
|
|
1411
|
+
--col-secondary-max-width: 40px;
|
|
1412
|
+
--col-default-max-width: 150px;
|
|
1413
|
+
--col-padding-total: 28px;
|
|
1414
|
+
--col-padding-secondary: 10px;
|
|
1415
|
+
|
|
1416
|
+
/* Secondary/rank column constraints */
|
|
1417
|
+
--col-secondary-min: 18px;
|
|
1418
|
+
|
|
1419
|
+
/* Legacy aliases */
|
|
1420
|
+
--col-rank-min: var(--col-secondary-min);
|
|
1421
|
+
--col-rank-max: var(--col-secondary-max-width);
|
|
1422
|
+
|
|
1423
|
+
/* ===========================
|
|
1424
|
+
TABLE SORTING
|
|
1425
|
+
=========================== */
|
|
1426
|
+
--sort-indicator-size: 10px;
|
|
1427
|
+
--sort-indicator-color: var(--color-text-muted);
|
|
1428
|
+
--sort-indicator-active-color: var(--color-black);
|
|
1429
|
+
--sort-hover-bg: var(--color-bg-hover);
|
|
1430
|
+
--sort-transition-duration: var(--animation-duration-fast);
|
|
1431
|
+
/* CSS-drawn chevron on the active sort column (idle columns show nothing) */
|
|
1432
|
+
--sort-chevron-size: 5px;
|
|
1433
|
+
--sort-chevron-stroke: 1.5px;
|
|
1434
|
+
--sort-indicator-opacity: 0.5;
|
|
1435
|
+
|
|
1436
|
+
/* ===========================
|
|
1437
|
+
FILL LAYOUT (readable floor)
|
|
1438
|
+
Per-column minimums summed into the table's min-width;
|
|
1439
|
+
below that floor the wrapper scrolls horizontally.
|
|
1440
|
+
=========================== */
|
|
1441
|
+
--table-min-col-width: 48px;
|
|
1442
|
+
--table-min-col-width-primary: 110px;
|
|
1443
|
+
--table-min-col-width-secondary: 28px;
|
|
1444
|
+
|
|
1445
|
+
/* ===========================
|
|
1446
|
+
TABLE RESPONSIVE
|
|
1447
|
+
=========================== */
|
|
1448
|
+
--table-font-size-mobile: var(--font-size-md);
|
|
1449
|
+
--table-header-font-size-mobile: var(--font-size-xs);
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/* ===========================
|
|
1453
|
+
DARK THEME
|
|
1454
|
+
===========================
|
|
1455
|
+
Companion to the core tokens' [data-theme="dark"] block — most table
|
|
1456
|
+
tokens reference core tokens and flip automatically; only the literal
|
|
1457
|
+
light-mode values are overridden here. */
|
|
1458
|
+
:root[data-theme="dark"] {
|
|
1459
|
+
/* White-based wash — the light theme's black-based rgba would vanish */
|
|
1460
|
+
--table-sorted-column-bg: rgba(255, 255, 255, 0.045);
|
|
1461
|
+
--table-sorted-column-bg-hover: rgba(255, 255, 255, 0.07);
|
|
1462
|
+
|
|
1463
|
+
/* Opaque backing for stuck header/column cells */
|
|
1464
|
+
--table-sticky-bg: var(--color-bg-container);
|
|
1465
|
+
--table-sticky-edge-shadow: 6px 0 8px -6px rgba(0, 0, 0, 0.7);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
|
|
1469
|
+
/* ==== src/table/table.css ==== */
|
|
1470
|
+
/**
|
|
1471
|
+
* D3Table Component Styles
|
|
1472
|
+
*
|
|
1473
|
+
* Import with core and table tokens:
|
|
1474
|
+
*
|
|
1475
|
+
* @import 'chart-factory/core/tokens.css';
|
|
1476
|
+
* @import 'chart-factory/core/base.css';
|
|
1477
|
+
* @import 'chart-factory/table/tokens.css';
|
|
1478
|
+
* @import 'chart-factory/table/table.css';
|
|
1479
|
+
*/
|
|
1480
|
+
|
|
1481
|
+
/* ===========================
|
|
1482
|
+
TABLE CONTAINER (extends .d3-container)
|
|
1483
|
+
=========================== */
|
|
1484
|
+
|
|
1485
|
+
.d3-table-container {
|
|
1486
|
+
display: inline-block;
|
|
1487
|
+
background: var(--color-bg-container);
|
|
1488
|
+
padding: var(--space-8);
|
|
1489
|
+
box-shadow: var(--shadow-container);
|
|
1490
|
+
text-align: left;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/* Wrapper for table + footer ensures footer matches table width */
|
|
1494
|
+
.d3-table-wrapper {
|
|
1495
|
+
display: inline-flex;
|
|
1496
|
+
flex-direction: column;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
/* Fill layout: the wrapper must be block-level so the scroll container
|
|
1500
|
+
is bounded by the page container, not shrink-wrapped to the table.
|
|
1501
|
+
(The footer still matches the table — it's 100% width too.)
|
|
1502
|
+
Applied by D3Table.prepareDom(). */
|
|
1503
|
+
.d3-table-wrapper--fill {
|
|
1504
|
+
display: block;
|
|
1505
|
+
width: 100%;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
.d3-table-title {
|
|
1509
|
+
font-family: var(--font-family);
|
|
1510
|
+
font-size: var(--font-size-title);
|
|
1511
|
+
font-weight: var(--font-weight-bold);
|
|
1512
|
+
margin: 0 0 var(--space-2) 0;
|
|
1513
|
+
color: var(--color-black);
|
|
1514
|
+
letter-spacing: var(--letter-spacing-tight);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
.d3-table-subtitle {
|
|
1518
|
+
font-family: var(--font-family);
|
|
1519
|
+
font-size: var(--font-size-subtitle);
|
|
1520
|
+
color: var(--color-text-subtle);
|
|
1521
|
+
margin: 0 0 var(--space-6) 0;
|
|
1522
|
+
line-height: var(--line-height-normal);
|
|
1523
|
+
font-weight: var(--font-weight-normal);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
.d3-table-footer {
|
|
1527
|
+
margin-top: 0;
|
|
1528
|
+
padding-top: var(--space-4);
|
|
1529
|
+
border-top: 1px solid var(--color-border-footer);
|
|
1530
|
+
font-family: var(--font-family);
|
|
1531
|
+
font-size: var(--font-size-xs);
|
|
1532
|
+
color: var(--color-text-faint);
|
|
1533
|
+
text-transform: uppercase;
|
|
1534
|
+
letter-spacing: var(--letter-spacing-wider);
|
|
1535
|
+
font-weight: var(--font-weight-semibold);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
/* ===========================
|
|
1539
|
+
TABLE BASE
|
|
1540
|
+
=========================== */
|
|
1541
|
+
|
|
1542
|
+
table {
|
|
1543
|
+
width: auto;
|
|
1544
|
+
border-collapse: collapse;
|
|
1545
|
+
font-family: var(--font-family);
|
|
1546
|
+
font-size: var(--table-font-size);
|
|
1547
|
+
margin-top: var(--space-3);
|
|
1548
|
+
margin-bottom: 0;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/* ===========================
|
|
1552
|
+
FILL LAYOUT (default)
|
|
1553
|
+
The table fills its container; headers and text cells wrap;
|
|
1554
|
+
numeric cells never wrap. Below the JS-computed min-width
|
|
1555
|
+
floor, .d3-table-scroll takes over with horizontal scroll.
|
|
1556
|
+
=========================== */
|
|
1557
|
+
|
|
1558
|
+
.d3-table-scroll {
|
|
1559
|
+
overflow-x: auto;
|
|
1560
|
+
max-width: 100%;
|
|
1561
|
+
-webkit-overflow-scrolling: touch;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
table.d3-table--fill {
|
|
1565
|
+
/* Fit content, up to the container, never below the computed floor:
|
|
1566
|
+
columns take their natural widths and leftover card width stays
|
|
1567
|
+
OUTSIDE the table as plain whitespace instead of pooling inside a
|
|
1568
|
+
stretched column. Bar columns then grow floor -> cap from measured
|
|
1569
|
+
container slack (adjustBarColumnFlex). */
|
|
1570
|
+
width: fit-content;
|
|
1571
|
+
max-width: 100%;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
table.d3-table--fill th {
|
|
1575
|
+
white-space: normal;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
table.d3-table--fill td {
|
|
1579
|
+
white-space: normal;
|
|
1580
|
+
overflow-wrap: break-word;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
table.d3-table--fill td.align-right,
|
|
1584
|
+
table.d3-table--fill td.align-center,
|
|
1585
|
+
table.d3-table--fill td.numeric-cell,
|
|
1586
|
+
table.d3-table--fill td.secondary-cell,
|
|
1587
|
+
table.d3-table--fill td.rank-cell {
|
|
1588
|
+
white-space: nowrap;
|
|
1589
|
+
overflow-wrap: normal;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
/* ===========================
|
|
1593
|
+
HEADER
|
|
1594
|
+
=========================== */
|
|
1595
|
+
|
|
1596
|
+
thead {
|
|
1597
|
+
border-bottom: var(--table-header-border);
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
th {
|
|
1601
|
+
text-align: left;
|
|
1602
|
+
padding: var(--table-header-padding-y) var(--table-cell-padding-x) var(--table-header-padding-bottom) var(--table-cell-padding-near);
|
|
1603
|
+
font-weight: var(--font-weight-normal);
|
|
1604
|
+
font-size: var(--table-header-font-size);
|
|
1605
|
+
text-transform: uppercase;
|
|
1606
|
+
letter-spacing: var(--letter-spacing-wide);
|
|
1607
|
+
color: var(--color-text-muted);
|
|
1608
|
+
white-space: nowrap;
|
|
1609
|
+
vertical-align: baseline;
|
|
1610
|
+
box-sizing: border-box;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
th:first-child {
|
|
1614
|
+
padding-left: 0;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
th:last-child {
|
|
1618
|
+
padding-right: 0;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
th.align-right {
|
|
1622
|
+
text-align: right;
|
|
1623
|
+
padding: var(--table-header-padding-y) var(--table-cell-padding-near) var(--table-header-padding-bottom) var(--table-cell-padding-x);
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
th.align-center {
|
|
1627
|
+
text-align: center;
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
/* ===========================
|
|
1631
|
+
BODY
|
|
1632
|
+
=========================== */
|
|
1633
|
+
|
|
1634
|
+
tbody tr {
|
|
1635
|
+
border-bottom: var(--table-row-border);
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
tbody tr:last-child {
|
|
1639
|
+
border-bottom: none;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
tbody tr:hover {
|
|
1643
|
+
background-color: var(--color-bg-hover);
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
td {
|
|
1647
|
+
padding: var(--table-cell-padding-y) var(--table-cell-padding-x) var(--table-cell-padding-y) var(--table-cell-padding-near);
|
|
1648
|
+
color: var(--color-text-primary);
|
|
1649
|
+
font-weight: var(--font-weight-normal);
|
|
1650
|
+
height: var(--table-cell-height);
|
|
1651
|
+
box-sizing: border-box;
|
|
1652
|
+
vertical-align: middle;
|
|
1653
|
+
white-space: nowrap;
|
|
1654
|
+
overflow: hidden;
|
|
1655
|
+
text-overflow: ellipsis;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
td:first-child {
|
|
1659
|
+
padding-left: 0;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
td:last-child {
|
|
1663
|
+
padding-right: 0;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
td.align-right {
|
|
1667
|
+
text-align: right;
|
|
1668
|
+
padding: var(--table-cell-padding-y) var(--table-cell-padding-near) var(--table-cell-padding-y) var(--table-cell-padding-x);
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
td.align-center {
|
|
1672
|
+
text-align: center;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
/* ===========================
|
|
1676
|
+
CELL TYPES
|
|
1677
|
+
=========================== */
|
|
1678
|
+
|
|
1679
|
+
/*
|
|
1680
|
+
* Primary cells - bold, larger font
|
|
1681
|
+
* Use for: names, titles, key identifiers
|
|
1682
|
+
* Generic: primary-cell | Legacy: team-cell
|
|
1683
|
+
*/
|
|
1684
|
+
td.primary-cell,
|
|
1685
|
+
td.team-cell {
|
|
1686
|
+
font-family: var(--table-primary-font, inherit);
|
|
1687
|
+
font-weight: var(--font-weight-bold);
|
|
1688
|
+
font-size: var(--table-primary-font-size, var(--table-team-font-size));
|
|
1689
|
+
color: var(--color-black);
|
|
1690
|
+
white-space: normal;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
/*
|
|
1694
|
+
* Secondary cells - subdued, smaller
|
|
1695
|
+
* Use for: ranks, indices, row numbers
|
|
1696
|
+
* Generic: secondary-cell | Legacy: rank-cell
|
|
1697
|
+
*/
|
|
1698
|
+
td.secondary-cell,
|
|
1699
|
+
td.rank-cell {
|
|
1700
|
+
color: var(--color-text-disabled);
|
|
1701
|
+
font-weight: var(--font-weight-semibold);
|
|
1702
|
+
font-size: var(--table-secondary-font-size, var(--table-rank-font-size));
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
th.auto-width.secondary-cell,
|
|
1706
|
+
td.auto-width.secondary-cell,
|
|
1707
|
+
th.auto-width.rank-cell,
|
|
1708
|
+
td.auto-width.rank-cell {
|
|
1709
|
+
/* Compact padding for narrow rank/index columns */
|
|
1710
|
+
padding-left: 0;
|
|
1711
|
+
padding-right: var(--space-2);
|
|
1712
|
+
min-width: var(--col-secondary-min, var(--col-rank-min));
|
|
1713
|
+
max-width: var(--col-secondary-max, var(--col-rank-max));
|
|
1714
|
+
text-align: left;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
|
|
1718
|
+
/*
|
|
1719
|
+
* Muted cells - de-emphasized text
|
|
1720
|
+
* Use for: supplementary info, metadata
|
|
1721
|
+
*/
|
|
1722
|
+
td.muted-cell {
|
|
1723
|
+
color: var(--color-text-disabled);
|
|
1724
|
+
font-size: var(--table-font-size);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
/*
|
|
1728
|
+
* Numeric cells - right-aligned, tabular figures
|
|
1729
|
+
* Use for: numbers, statistics, amounts
|
|
1730
|
+
*/
|
|
1731
|
+
td.numeric-cell {
|
|
1732
|
+
text-align: right;
|
|
1733
|
+
font-variant-numeric: tabular-nums;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
/* ===========================
|
|
1737
|
+
SUBTITLE CELLS
|
|
1738
|
+
=========================== */
|
|
1739
|
+
|
|
1740
|
+
td.has-subtitle {
|
|
1741
|
+
height: auto;
|
|
1742
|
+
padding-top: var(--space-2);
|
|
1743
|
+
padding-bottom: var(--space-2);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
.cell-with-subtitle {
|
|
1747
|
+
display: flex;
|
|
1748
|
+
flex-direction: column;
|
|
1749
|
+
gap: 1px;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
.cell-main-text {
|
|
1753
|
+
font-weight: var(--font-weight-bold);
|
|
1754
|
+
font-size: var(--table-primary-font-size);
|
|
1755
|
+
color: var(--color-black);
|
|
1756
|
+
line-height: 1.2;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
.cell-subtitle-text {
|
|
1760
|
+
font-weight: var(--font-weight-normal);
|
|
1761
|
+
font-size: var(--font-size-md);
|
|
1762
|
+
color: var(--color-text-disabled);
|
|
1763
|
+
line-height: 1.2;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/* ===========================
|
|
1767
|
+
STICKY HEADER / STICKY FIRST COLUMN
|
|
1768
|
+
=========================== */
|
|
1769
|
+
|
|
1770
|
+
/* Sticky header. border-collapse drops borders on stuck cells, so the
|
|
1771
|
+
header's bottom rule is redrawn as an inset shadow (on the last
|
|
1772
|
+
header row only — a group row above it carries no rule). */
|
|
1773
|
+
table.d3-table--sticky-header thead th {
|
|
1774
|
+
position: sticky;
|
|
1775
|
+
top: 0;
|
|
1776
|
+
z-index: 2;
|
|
1777
|
+
background: var(--table-sticky-bg, #fff);
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
table.d3-table--sticky-header thead tr:last-child th {
|
|
1781
|
+
box-shadow: inset 0 -2px 0 var(--color-black, #000);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
table.d3-table--sticky-header thead {
|
|
1785
|
+
border-bottom: none;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
/* In-wrapper vertical scrolling (config.maxHeight) */
|
|
1789
|
+
.d3-table-scroll--vertical {
|
|
1790
|
+
overflow-y: auto;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
/* Floating header: sticky emulation on horizontally-scrolling tables.
|
|
1794
|
+
JS translates the thead with the page scroll (position: sticky can't
|
|
1795
|
+
escape the horizontal scroll container). will-change lives ONLY on the
|
|
1796
|
+
floating state: on the base thead it would create a z-index:auto
|
|
1797
|
+
stacking context that traps the th's z-index, letting tbody bar fills
|
|
1798
|
+
and heatmap chips paint over the page-stuck header. */
|
|
1799
|
+
table.d3-table--sticky-header thead.d3-table-thead--floating {
|
|
1800
|
+
position: relative;
|
|
1801
|
+
z-index: 2;
|
|
1802
|
+
will-change: transform;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
table.d3-table--sticky-header thead.d3-table-thead--floating th {
|
|
1806
|
+
background: var(--table-sticky-bg, #fff);
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
/* Page-level sticky: overflow-x clip does not create a scroll container,
|
|
1810
|
+
applied by JS only while the table fits its container horizontally */
|
|
1811
|
+
.d3-table-scroll--clip {
|
|
1812
|
+
overflow-x: clip;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
/* Sticky first column */
|
|
1816
|
+
table.d3-table--sticky-first-col th:first-child,
|
|
1817
|
+
table.d3-table--sticky-first-col td:first-child {
|
|
1818
|
+
position: sticky;
|
|
1819
|
+
left: 0;
|
|
1820
|
+
z-index: 1;
|
|
1821
|
+
background: var(--table-sticky-bg, #fff);
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
table.d3-table--sticky-first-col thead th:first-child {
|
|
1825
|
+
z-index: 3;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
table.d3-table--sticky-first-col tbody tr:hover td:first-child {
|
|
1829
|
+
background-color: var(--color-bg-hover);
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
/* Edge shadow appears only once the container is actually scrolled */
|
|
1833
|
+
.d3-table-scroll.is-h-scrolled table.d3-table--sticky-first-col th:first-child,
|
|
1834
|
+
.d3-table-scroll.is-h-scrolled table.d3-table--sticky-first-col td:first-child {
|
|
1835
|
+
box-shadow: var(--table-sticky-edge-shadow);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
/* Both stickies on one table: the corner header cell needs both shadows */
|
|
1839
|
+
table.d3-table--sticky-header.d3-table--sticky-first-col thead tr:last-child th:first-child {
|
|
1840
|
+
box-shadow: inset 0 -2px 0 var(--color-black, #000);
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
.d3-table-scroll.is-h-scrolled table.d3-table--sticky-header.d3-table--sticky-first-col thead tr:last-child th:first-child {
|
|
1844
|
+
box-shadow: inset 0 -2px 0 var(--color-black, #000), var(--table-sticky-edge-shadow);
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
/* ===========================
|
|
1848
|
+
COLUMN GROUP HEADERS
|
|
1849
|
+
=========================== */
|
|
1850
|
+
|
|
1851
|
+
.d3-table-group-row th {
|
|
1852
|
+
padding-bottom: var(--space-1);
|
|
1853
|
+
vertical-align: bottom;
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
.d3-table-group-row th.group-header {
|
|
1857
|
+
text-align: center;
|
|
1858
|
+
color: var(--color-text-muted);
|
|
1859
|
+
/* The rule is a bottom-anchored background (not border-bottom) so it
|
|
1860
|
+
can be inset from the cell edges — adjacent groups get a visible
|
|
1861
|
+
gap that marks where one group ends and the next begins. */
|
|
1862
|
+
background-image: linear-gradient(
|
|
1863
|
+
var(--table-group-rule-color, var(--color-border-row, #e5e7eb)),
|
|
1864
|
+
var(--table-group-rule-color, var(--color-border-row, #e5e7eb)));
|
|
1865
|
+
background-repeat: no-repeat;
|
|
1866
|
+
background-position: bottom center;
|
|
1867
|
+
background-size: calc(100% - 2 * var(--table-group-rule-inset, 10px)) var(--table-group-rule-width, 1px);
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
/* ===========================
|
|
1871
|
+
ROW CAP (maxRows + Show all)
|
|
1872
|
+
=========================== */
|
|
1873
|
+
|
|
1874
|
+
tr.row-capped {
|
|
1875
|
+
display: none !important;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
.d3-table-showmore {
|
|
1879
|
+
display: block;
|
|
1880
|
+
background: none;
|
|
1881
|
+
border: 0;
|
|
1882
|
+
padding: var(--space-2) 0;
|
|
1883
|
+
margin-top: var(--space-1);
|
|
1884
|
+
font-family: var(--font-family);
|
|
1885
|
+
font-size: var(--table-header-font-size);
|
|
1886
|
+
text-transform: uppercase;
|
|
1887
|
+
letter-spacing: var(--letter-spacing-wide);
|
|
1888
|
+
color: var(--color-text-muted);
|
|
1889
|
+
cursor: pointer;
|
|
1890
|
+
text-align: left;
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
.d3-table-showmore:hover {
|
|
1894
|
+
color: var(--color-text-primary, var(--color-black));
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
.d3-table-showmore:focus-visible {
|
|
1898
|
+
outline: 2px solid var(--chart-primary, #3b82f6);
|
|
1899
|
+
outline-offset: 2px;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
/* ===========================
|
|
1903
|
+
FOOTER / TOTAL ROW
|
|
1904
|
+
=========================== */
|
|
1905
|
+
|
|
1906
|
+
tfoot td {
|
|
1907
|
+
font-weight: var(--font-weight-bold);
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
/* First summary row carries the heavy total rule; later rows (e.g. a
|
|
1911
|
+
per-game average under the totals) separate with the normal border */
|
|
1912
|
+
tfoot tr:first-child td {
|
|
1913
|
+
border-top: var(--table-total-border);
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
tfoot tr + tr td {
|
|
1917
|
+
border-top: var(--table-row-border);
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
/* ===========================
|
|
1921
|
+
EXPANDABLE SUB-ROWS
|
|
1922
|
+
=========================== */
|
|
1923
|
+
|
|
1924
|
+
/* Chevron toggle at the start of an expandable parent row's first cell.
|
|
1925
|
+
Same drawn-border chevron as sort indicators: points right collapsed,
|
|
1926
|
+
rotates to point down when expanded. */
|
|
1927
|
+
.row-toggle {
|
|
1928
|
+
background: none;
|
|
1929
|
+
border: 0;
|
|
1930
|
+
padding: 0;
|
|
1931
|
+
margin-right: var(--space-2);
|
|
1932
|
+
width: var(--table-toggle-width, 14px);
|
|
1933
|
+
cursor: pointer;
|
|
1934
|
+
color: var(--color-text-muted);
|
|
1935
|
+
display: inline-block;
|
|
1936
|
+
vertical-align: middle;
|
|
1937
|
+
line-height: 0;
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
.row-toggle::after {
|
|
1941
|
+
content: '';
|
|
1942
|
+
display: inline-block;
|
|
1943
|
+
width: var(--sort-chevron-size, 5px);
|
|
1944
|
+
height: var(--sort-chevron-size, 5px);
|
|
1945
|
+
border-right: var(--sort-chevron-stroke, 1.5px) solid currentColor;
|
|
1946
|
+
border-bottom: var(--sort-chevron-stroke, 1.5px) solid currentColor;
|
|
1947
|
+
transform: rotate(-45deg);
|
|
1948
|
+
transition: transform var(--sort-transition-duration) ease;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
.row-toggle:hover {
|
|
1952
|
+
color: var(--color-text-primary, var(--color-black));
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
.row-toggle:focus-visible {
|
|
1956
|
+
outline: 2px solid var(--chart-primary, #3b82f6);
|
|
1957
|
+
outline-offset: 2px;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
tr.expandable-row.expanded .row-toggle::after {
|
|
1961
|
+
transform: rotate(45deg);
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
tr.expandable-row td {
|
|
1965
|
+
cursor: default;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
/* Same-width spacer keeps childless parent rows aligned with toggled ones */
|
|
1969
|
+
.row-toggle-spacer {
|
|
1970
|
+
display: inline-block;
|
|
1971
|
+
width: var(--table-toggle-width, 14px);
|
|
1972
|
+
margin-right: var(--space-2);
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
/* Sub-rows: indented under the parent, quieter type */
|
|
1976
|
+
tr.sub-row td:first-child {
|
|
1977
|
+
padding-left: var(--table-subrow-indent, 22px);
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
tr.sub-row td.primary-cell,
|
|
1981
|
+
tr.sub-row td.team-cell {
|
|
1982
|
+
font-size: var(--table-font-size);
|
|
1983
|
+
font-weight: var(--font-weight-semibold);
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
/* ===========================
|
|
1987
|
+
IMAGE CELLS (logos / flags / headshots)
|
|
1988
|
+
=========================== */
|
|
1989
|
+
|
|
1990
|
+
.cell-with-image {
|
|
1991
|
+
display: flex;
|
|
1992
|
+
align-items: center;
|
|
1993
|
+
gap: var(--table-image-gap);
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
td.align-right .cell-with-image {
|
|
1997
|
+
justify-content: flex-end;
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
td.align-center .cell-with-image {
|
|
2001
|
+
justify-content: center;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
.d3-table-img {
|
|
2005
|
+
width: var(--table-image-size);
|
|
2006
|
+
height: var(--table-image-size);
|
|
2007
|
+
flex-shrink: 0;
|
|
2008
|
+
display: block;
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
img.d3-table-img.img-fit-cover { object-fit: cover; }
|
|
2012
|
+
img.d3-table-img.img-fit-contain { object-fit: contain; }
|
|
2013
|
+
|
|
2014
|
+
.d3-table-img.img-circle { border-radius: 50%; }
|
|
2015
|
+
.d3-table-img.img-rounded { border-radius: var(--table-image-radius); }
|
|
2016
|
+
.d3-table-img.img-square { border-radius: 0; }
|
|
2017
|
+
|
|
2018
|
+
/* Rows without an image keep an invisible slot so text stays aligned */
|
|
2019
|
+
.d3-table-img-placeholder {
|
|
2020
|
+
visibility: hidden;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
/* ===========================
|
|
2024
|
+
AUTO WIDTH UTILITY
|
|
2025
|
+
=========================== */
|
|
2026
|
+
|
|
2027
|
+
.auto-width {
|
|
2028
|
+
white-space: nowrap;
|
|
2029
|
+
/* width handled by JavaScript */
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
/* Opt-in wrapping for long text columns (column config `wrap: true`).
|
|
2033
|
+
Beats the auto-width nowrap; pair with a column maxWidth (applied as an
|
|
2034
|
+
inline style) to control where the wrap engages. Data cells only —
|
|
2035
|
+
headers keep their single-line layout. */
|
|
2036
|
+
td.wrap-cell,
|
|
2037
|
+
td.auto-width.wrap-cell {
|
|
2038
|
+
white-space: normal;
|
|
2039
|
+
overflow-wrap: break-word;
|
|
2040
|
+
line-height: var(--table-wrap-line-height, 1.35);
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
/* ===========================
|
|
2044
|
+
BAR CHART CELLS
|
|
2045
|
+
=========================== */
|
|
2046
|
+
|
|
2047
|
+
td.bar-cell {
|
|
2048
|
+
position: relative;
|
|
2049
|
+
min-width: var(--bar-column-min-width);
|
|
2050
|
+
overflow: visible;
|
|
2051
|
+
padding: var(--table-cell-padding-y) var(--table-cell-padding-x);
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
th.bar-cell {
|
|
2055
|
+
text-align: center;
|
|
2056
|
+
padding: var(--table-header-padding-y) var(--table-cell-padding-x) var(--table-header-padding-bottom) var(--table-cell-padding-x);
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
.bar-background {
|
|
2060
|
+
position: absolute;
|
|
2061
|
+
top: var(--bar-margin-y);
|
|
2062
|
+
left: var(--table-cell-padding-x);
|
|
2063
|
+
bottom: var(--bar-margin-y);
|
|
2064
|
+
height: auto;
|
|
2065
|
+
opacity: var(--bar-opacity);
|
|
2066
|
+
z-index: 0;
|
|
2067
|
+
border-radius: var(--table-bar-border-radius, 2px);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
/* Positioning context spanning the cell's padded interior: bar widths and
|
|
2071
|
+
label positions are percentages OF THE TRACK, so they can never bleed
|
|
2072
|
+
past the cell padding (the old cell-relative percentages overflowed
|
|
2073
|
+
tight columns by the padding width). */
|
|
2074
|
+
.bar-track {
|
|
2075
|
+
position: absolute;
|
|
2076
|
+
top: var(--bar-margin-y);
|
|
2077
|
+
bottom: var(--bar-margin-y);
|
|
2078
|
+
left: var(--table-cell-padding-x);
|
|
2079
|
+
right: var(--table-cell-padding-x);
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
.bar-track .bar-background {
|
|
2083
|
+
top: 0;
|
|
2084
|
+
bottom: 0;
|
|
2085
|
+
left: 0;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
.bar-track .bar-value {
|
|
2089
|
+
margin-left: 0;
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
/* Scoped to table cells: the ChartFactory bar builders also emit an SVG
|
|
2093
|
+
<text class="bar-value"> for their end-of-bar labels, and an unscoped rule
|
|
2094
|
+
here (position/top/transform) leaks onto those and floats them off their
|
|
2095
|
+
bars. Table bar-values are always a <div> inside a <td>, so `td` scoping
|
|
2096
|
+
fixes the collision without changing table appearance. */
|
|
2097
|
+
td .bar-value {
|
|
2098
|
+
position: absolute;
|
|
2099
|
+
z-index: 1;
|
|
2100
|
+
font-weight: var(--font-weight-semibold);
|
|
2101
|
+
text-align: right;
|
|
2102
|
+
color: var(--color-black);
|
|
2103
|
+
padding: 0 var(--bar-text-padding);
|
|
2104
|
+
white-space: nowrap;
|
|
2105
|
+
top: 50%;
|
|
2106
|
+
transform: translateY(-50%);
|
|
2107
|
+
margin-left: var(--table-cell-padding-x);
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
/* ===========================
|
|
2111
|
+
HEATMAP CELLS
|
|
2112
|
+
=========================== */
|
|
2113
|
+
|
|
2114
|
+
.heatmap-cell-padded {
|
|
2115
|
+
position: relative;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
.heatmap-cell-padded.align-right {
|
|
2119
|
+
text-align: right;
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
.heatmap-box {
|
|
2123
|
+
display: inline-flex;
|
|
2124
|
+
align-items: center;
|
|
2125
|
+
justify-content: center;
|
|
2126
|
+
padding: 0 var(--heatmap-box-padding);
|
|
2127
|
+
border-radius: var(--heatmap-box-border-radius);
|
|
2128
|
+
font-weight: var(--font-weight-semibold);
|
|
2129
|
+
text-align: center;
|
|
2130
|
+
min-width: var(--heatmap-box-min-width);
|
|
2131
|
+
height: var(--heatmap-box-height);
|
|
2132
|
+
box-sizing: border-box;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
.heatmap-value {
|
|
2136
|
+
position: relative;
|
|
2137
|
+
z-index: 1;
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
/* Missing value in a heatmap column: quiet em dash, no fill */
|
|
2141
|
+
.heatmap-box-empty {
|
|
2142
|
+
color: var(--color-text-faint);
|
|
2143
|
+
font-weight: var(--font-weight-normal);
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
/* ===========================
|
|
2147
|
+
HIGHLIGHTING
|
|
2148
|
+
=========================== */
|
|
2149
|
+
|
|
2150
|
+
.highlight-column {
|
|
2151
|
+
background-color: var(--color-bg-highlight);
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
.highlight-column .bar-background {
|
|
2155
|
+
left: var(--space-3);
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
.highlight-column .bar-value {
|
|
2159
|
+
margin-left: var(--space-3);
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
.highlight-header {
|
|
2163
|
+
background-color: var(--color-bg-highlight);
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
.highlight-row {
|
|
2167
|
+
background-color: var(--color-bg-highlight);
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
.highlight-row:hover {
|
|
2171
|
+
background-color: var(--color-bg-highlight);
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
/* ===========================
|
|
2175
|
+
SORTING
|
|
2176
|
+
=========================== */
|
|
2177
|
+
|
|
2178
|
+
.sortable-header {
|
|
2179
|
+
cursor: pointer;
|
|
2180
|
+
position: relative;
|
|
2181
|
+
user-select: none;
|
|
2182
|
+
transition: background-color var(--sort-transition-duration) ease;
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
/* Sortability is signaled ambiently: pointer cursor + hover/focus fill.
|
|
2186
|
+
Idle sortable columns show no glyph. */
|
|
2187
|
+
.sortable-header:hover,
|
|
2188
|
+
.sortable-header:focus-within {
|
|
2189
|
+
background-color: var(--sort-hover-bg);
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
/* The header's click/keyboard surface — a real button for aria-sort,
|
|
2193
|
+
Tab focus, and Enter/Space activation. Styled to be invisible. */
|
|
2194
|
+
.sort-button {
|
|
2195
|
+
background: none;
|
|
2196
|
+
border: 0;
|
|
2197
|
+
padding: 0;
|
|
2198
|
+
margin: 0;
|
|
2199
|
+
font: inherit;
|
|
2200
|
+
color: inherit;
|
|
2201
|
+
letter-spacing: inherit;
|
|
2202
|
+
text-transform: inherit;
|
|
2203
|
+
text-align: inherit;
|
|
2204
|
+
cursor: pointer;
|
|
2205
|
+
display: block;
|
|
2206
|
+
width: 100%;
|
|
2207
|
+
}
|
|
2208
|
+
|
|
2209
|
+
.sort-button:focus-visible {
|
|
2210
|
+
outline: 2px solid var(--chart-primary, #3b82f6);
|
|
2211
|
+
outline-offset: -2px;
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
/* Glue wrappers keep the indicator on the same line as the adjacent
|
|
2215
|
+
word — the chevron can never wrap onto a line by itself. */
|
|
2216
|
+
.sort-glue,
|
|
2217
|
+
.sort-glue-lead {
|
|
2218
|
+
white-space: nowrap;
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
.sort-indicator {
|
|
2222
|
+
display: inline-block;
|
|
2223
|
+
color: var(--sort-indicator-color);
|
|
2224
|
+
opacity: var(--sort-indicator-opacity, 0.5);
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
.sort-indicator.inactive {
|
|
2228
|
+
display: none;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
.sort-glue .sort-indicator.active {
|
|
2232
|
+
margin-left: var(--space-1);
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
.sort-glue-lead .sort-indicator.active {
|
|
2236
|
+
margin-right: var(--space-1);
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
/* CSS-drawn chevron: the same bordered box for both directions, rotated
|
|
2240
|
+
about its center — size, stroke, and baseline are exactly consistent.
|
|
2241
|
+
The rotation leaves the visible strokes entirely on one side of the
|
|
2242
|
+
box center (below it for ▾, above it for ▴), so each direction gets a
|
|
2243
|
+
compensating translate of size × √2/4 that puts the glyph's visual
|
|
2244
|
+
center exactly on the box center — up and down share one center point. */
|
|
2245
|
+
.sort-indicator.asc::after,
|
|
2246
|
+
.sort-indicator.desc::after {
|
|
2247
|
+
content: '';
|
|
2248
|
+
display: inline-block;
|
|
2249
|
+
width: var(--sort-chevron-size, 5px);
|
|
2250
|
+
height: var(--sort-chevron-size, 5px);
|
|
2251
|
+
border-right: var(--sort-chevron-stroke, 1.5px) solid currentColor;
|
|
2252
|
+
border-bottom: var(--sort-chevron-stroke, 1.5px) solid currentColor;
|
|
2253
|
+
vertical-align: 1px;
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
.sort-indicator.desc::after {
|
|
2257
|
+
transform: translateY(calc(var(--sort-chevron-size, 5px) * -0.354)) rotate(45deg);
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
.sort-indicator.asc::after {
|
|
2261
|
+
transform: translateY(calc(var(--sort-chevron-size, 5px) * 0.354)) rotate(225deg);
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
/* Sorted column highlighting — quiet neutral wash over the full column,
|
|
2265
|
+
header cell included, applied when the user sorts manually. */
|
|
2266
|
+
.sorted-column {
|
|
2267
|
+
background-color: var(--table-sorted-column-bg, rgba(0, 0, 0, 0.03));
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
/* A first column is flush-left by default; when it carries a column
|
|
2271
|
+
wash (sorted tint, permanent highlight) the text would hug the
|
|
2272
|
+
shaded edge, so those cells get the small near-edge padding. Row-
|
|
2273
|
+
level shading (hover, highlight-row) is deliberately excluded —
|
|
2274
|
+
per-row padding would misalign first-column text across rows. */
|
|
2275
|
+
th:first-child.sorted-column,
|
|
2276
|
+
td:first-child.sorted-column,
|
|
2277
|
+
th:first-child.highlight-header,
|
|
2278
|
+
td:first-child.highlight-column {
|
|
2279
|
+
padding-left: var(--table-cell-padding-near);
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
.sorted-column:hover {
|
|
2283
|
+
background-color: var(--table-sorted-column-bg-hover, rgba(0, 0, 0, 0.05));
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
th.sorted-column {
|
|
2287
|
+
color: var(--color-text-primary, var(--color-black));
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
/* ===========================
|
|
2291
|
+
TABLE TOOLTIPS
|
|
2292
|
+
=========================== */
|
|
2293
|
+
|
|
2294
|
+
.d3-table-tooltip {
|
|
2295
|
+
position: absolute;
|
|
2296
|
+
background: var(--color-bg-container);
|
|
2297
|
+
border: 1px solid var(--color-border-tooltip);
|
|
2298
|
+
border-radius: var(--tooltip-border-radius);
|
|
2299
|
+
padding: var(--tooltip-padding);
|
|
2300
|
+
box-shadow: var(--shadow-tooltip);
|
|
2301
|
+
z-index: 1000;
|
|
2302
|
+
pointer-events: none;
|
|
2303
|
+
font-family: var(--font-family);
|
|
2304
|
+
font-size: var(--font-size-md);
|
|
2305
|
+
min-width: var(--tooltip-min-width);
|
|
2306
|
+
overflow: hidden;
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
.d3-table-tooltip-chart {
|
|
2310
|
+
display: flex;
|
|
2311
|
+
align-items: flex-end;
|
|
2312
|
+
gap: var(--tooltip-bar-gap);
|
|
2313
|
+
height: var(--tooltip-chart-height);
|
|
2314
|
+
position: relative;
|
|
2315
|
+
box-sizing: border-box;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
.d3-table-tooltip-bar {
|
|
2319
|
+
flex: 1;
|
|
2320
|
+
background-color: var(--color-text-muted);
|
|
2321
|
+
border-radius: var(--tooltip-bar-border-radius);
|
|
2322
|
+
min-width: var(--tooltip-bar-min-width);
|
|
2323
|
+
position: relative;
|
|
2324
|
+
max-height: var(--tooltip-chart-height);
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
.d3-table-tooltip-bar-label {
|
|
2328
|
+
position: absolute;
|
|
2329
|
+
bottom: calc(-1 * var(--tooltip-label-offset-y));
|
|
2330
|
+
left: 50%;
|
|
2331
|
+
transform: translateX(-50%);
|
|
2332
|
+
font-size: var(--font-size-xs);
|
|
2333
|
+
color: var(--color-text-muted);
|
|
2334
|
+
white-space: nowrap;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
.d3-table-tooltip-bar-value {
|
|
2338
|
+
position: absolute;
|
|
2339
|
+
top: calc(-1 * var(--tooltip-value-offset-y));
|
|
2340
|
+
left: 50%;
|
|
2341
|
+
transform: translateX(-50%);
|
|
2342
|
+
font-size: var(--font-size-sm);
|
|
2343
|
+
font-weight: var(--font-weight-semibold);
|
|
2344
|
+
color: var(--color-black);
|
|
2345
|
+
white-space: nowrap;
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
/* ===========================
|
|
2349
|
+
RESPONSIVE
|
|
2350
|
+
=========================== */
|
|
2351
|
+
|
|
2352
|
+
@media (max-width: 768px) {
|
|
2353
|
+
.d3-table-container {
|
|
2354
|
+
padding: var(--container-padding-mobile);
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
.d3-table-title {
|
|
2358
|
+
font-size: var(--title-font-size-mobile);
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
table {
|
|
2362
|
+
font-size: var(--table-font-size-mobile);
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
th {
|
|
2366
|
+
font-size: var(--table-header-font-size-mobile);
|
|
2367
|
+
}
|
|
2368
|
+
}
|