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
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chart-factory",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Token-driven, responsive D3 chart library: area, line, slope, bar, scatter, dot charts and sortable tables that fill their container by default.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"d3",
|
|
7
|
+
"charts",
|
|
8
|
+
"chart",
|
|
9
|
+
"visualization",
|
|
10
|
+
"dataviz",
|
|
11
|
+
"data-visualization",
|
|
12
|
+
"svg",
|
|
13
|
+
"design-tokens",
|
|
14
|
+
"responsive-charts"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/jolsufka/chart-factory.git"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/jolsufka/chart-factory#readme",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/jolsufka/chart-factory/issues"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "Jacob Olsufka",
|
|
27
|
+
"main": "./src/core/d3-chart-factory.js",
|
|
28
|
+
"module": "./src/core/d3-chart-factory.js",
|
|
29
|
+
"unpkg": "./dist/chart-factory.min.js",
|
|
30
|
+
"jsdelivr": "./dist/chart-factory.min.js",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": "./src/core/d3-chart-factory.js",
|
|
33
|
+
"./base": "./src/core/d3-base.js",
|
|
34
|
+
"./table": "./src/table/d3-table.js",
|
|
35
|
+
"./core/tokens.css": "./src/core/tokens.css",
|
|
36
|
+
"./core/base.css": "./src/core/base.css",
|
|
37
|
+
"./table/tokens.css": "./src/table/tokens.css",
|
|
38
|
+
"./table/table.css": "./src/table/table.css",
|
|
39
|
+
"./styles.css": "./dist/chart-factory.css",
|
|
40
|
+
"./dist/*": "./dist/*"
|
|
41
|
+
},
|
|
42
|
+
"style": "./dist/chart-factory.css",
|
|
43
|
+
"sideEffects": [
|
|
44
|
+
"./src/core/d3-chart-factory.js",
|
|
45
|
+
"*.css"
|
|
46
|
+
],
|
|
47
|
+
"files": [
|
|
48
|
+
"src",
|
|
49
|
+
"dist",
|
|
50
|
+
"index.d.ts",
|
|
51
|
+
"docs",
|
|
52
|
+
"README.md",
|
|
53
|
+
"LICENSE"
|
|
54
|
+
],
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "node build.mjs",
|
|
57
|
+
"watch": "node build.mjs --watch",
|
|
58
|
+
"serve": "node serve.mjs",
|
|
59
|
+
"conformance": "node scripts/conformance.mjs",
|
|
60
|
+
"test": "node scripts/bundle-smoke.mjs && playwright test",
|
|
61
|
+
"test:update": "UPDATE_GOLDENS=1 playwright test regression",
|
|
62
|
+
"prepare": "npm run build",
|
|
63
|
+
"bundle-smoke": "node scripts/bundle-smoke.mjs"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"d3": "^7.0.0",
|
|
67
|
+
"d3-hexbin": ">=0.2.0",
|
|
68
|
+
"d3-sankey": ">=0.12.0"
|
|
69
|
+
},
|
|
70
|
+
"peerDependenciesMeta": {
|
|
71
|
+
"d3-hexbin": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"d3-sankey": {
|
|
75
|
+
"optional": true
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"devDependencies": {
|
|
79
|
+
"@playwright/test": "^1.61.1",
|
|
80
|
+
"esbuild": "^0.24.0"
|
|
81
|
+
},
|
|
82
|
+
"types": "./index.d.ts"
|
|
83
|
+
}
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chart Factory - Shared Base Styles
|
|
3
|
+
*
|
|
4
|
+
* Common styles used across all D3 visualization components.
|
|
5
|
+
* Import after tokens.css:
|
|
6
|
+
*
|
|
7
|
+
* @import 'chart-factory/core/tokens.css';
|
|
8
|
+
* @import 'chart-factory/core/base.css';
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/* ===========================
|
|
12
|
+
CONTAINER
|
|
13
|
+
=========================== */
|
|
14
|
+
|
|
15
|
+
.d3-container {
|
|
16
|
+
display: block;
|
|
17
|
+
width: 100%;
|
|
18
|
+
max-width: var(--container-max-width, 660px);
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
background: var(--color-bg-container);
|
|
21
|
+
padding: var(--container-padding, var(--space-8));
|
|
22
|
+
box-shadow: var(--shadow-container);
|
|
23
|
+
text-align: left;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Charts fill their container; svg viewBox scaling is applied by the factory */
|
|
27
|
+
.d3-container svg,
|
|
28
|
+
.chart-container svg {
|
|
29
|
+
display: block;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* ===========================
|
|
33
|
+
CHART ELEMENT DEFAULTS
|
|
34
|
+
===========================
|
|
35
|
+
The canonical look for factory-generated SVG elements. Historically these
|
|
36
|
+
rules were copy-pasted into every example page's <style> block, which meant
|
|
37
|
+
package consumers loading only tokens.css + base.css got browser-default
|
|
38
|
+
black axes and gridlines. They belong here. Scoped under `svg` so generic
|
|
39
|
+
class names (.line, .dot) don't leak onto host-page HTML. */
|
|
40
|
+
|
|
41
|
+
svg .line {
|
|
42
|
+
fill: none;
|
|
43
|
+
stroke-width: var(--line-stroke-width);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
svg .axis-x,
|
|
47
|
+
svg .axis-y {
|
|
48
|
+
font-family: var(--font-family);
|
|
49
|
+
font-size: var(--font-size-sm);
|
|
50
|
+
color: var(--axis-color);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
svg .axis-x path,
|
|
54
|
+
svg .axis-y path,
|
|
55
|
+
svg .axis-x line,
|
|
56
|
+
svg .axis-y line {
|
|
57
|
+
stroke: var(--color-border-light, #e5e5e5);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
svg .axis-y .domain {
|
|
61
|
+
display: none;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
svg .grid line {
|
|
65
|
+
stroke: var(--color-border-light, #e5e5e5);
|
|
66
|
+
stroke-opacity: var(--grid-stroke-opacity);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
svg .grid path {
|
|
70
|
+
stroke-width: 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Fallback fill only — dots that set their own fill attribute (per-category
|
|
74
|
+
coloring in the Dot family) keep it. */
|
|
75
|
+
svg .dot:not([fill]) {
|
|
76
|
+
fill: var(--chart-primary);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Legend swatches must never be squished by a tight flex row (would turn a
|
|
80
|
+
circular dot into an oval) — pin their box regardless of available space. */
|
|
81
|
+
.legend-dot,
|
|
82
|
+
.legend-swatch {
|
|
83
|
+
flex: none;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ===========================
|
|
87
|
+
TITLE / SUBTITLE / FOOTER
|
|
88
|
+
=========================== */
|
|
89
|
+
|
|
90
|
+
.d3-title {
|
|
91
|
+
font-family: var(--font-family);
|
|
92
|
+
font-size: var(--font-size-title);
|
|
93
|
+
font-weight: var(--font-weight-bold);
|
|
94
|
+
margin: 0 0 var(--space-2) 0;
|
|
95
|
+
color: var(--color-black);
|
|
96
|
+
letter-spacing: var(--letter-spacing-tight);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.d3-subtitle {
|
|
100
|
+
font-family: var(--font-family);
|
|
101
|
+
font-size: var(--font-size-subtitle);
|
|
102
|
+
color: var(--color-text-subtle);
|
|
103
|
+
margin: 0 0 var(--space-6) 0;
|
|
104
|
+
line-height: var(--line-height-normal);
|
|
105
|
+
font-weight: var(--font-weight-normal);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.d3-footer {
|
|
109
|
+
margin-top: 0;
|
|
110
|
+
padding-top: var(--space-4);
|
|
111
|
+
border-top: 1px solid var(--color-border-footer);
|
|
112
|
+
font-family: var(--font-family);
|
|
113
|
+
font-size: var(--font-size-xs);
|
|
114
|
+
color: var(--color-text-faint);
|
|
115
|
+
text-transform: uppercase;
|
|
116
|
+
letter-spacing: var(--letter-spacing-wider);
|
|
117
|
+
font-weight: var(--font-weight-semibold);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ===========================
|
|
121
|
+
TOOLTIPS (Shared)
|
|
122
|
+
=========================== */
|
|
123
|
+
|
|
124
|
+
/* Chart hover tooltip — the node TooltipManager creates ('.d3-tooltip').
|
|
125
|
+
Positioning (fixed/left/top/opacity/z-index) is managed by JS; this class
|
|
126
|
+
owns the appearance. */
|
|
127
|
+
.d3-tooltip {
|
|
128
|
+
position: fixed;
|
|
129
|
+
background: var(--color-bg-container);
|
|
130
|
+
border: 1px solid var(--color-border-tooltip);
|
|
131
|
+
border-radius: 6px;
|
|
132
|
+
padding: var(--tooltip-padding, 10px 13px);
|
|
133
|
+
box-shadow: var(--shadow-tooltip);
|
|
134
|
+
z-index: var(--tooltip-z-index, 9999);
|
|
135
|
+
pointer-events: none;
|
|
136
|
+
font-family: var(--font-family);
|
|
137
|
+
font-size: var(--tooltip-font-size, 12px);
|
|
138
|
+
line-height: 1.45;
|
|
139
|
+
min-width: var(--tooltip-min-width, 130px);
|
|
140
|
+
max-width: 320px;
|
|
141
|
+
display: flex;
|
|
142
|
+
flex-direction: column;
|
|
143
|
+
gap: var(--tooltip-row-gap, 4px);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Standard tooltip shell rows (TooltipManager.rows).
|
|
147
|
+
Hierarchy: title > values > labels > notes.
|
|
148
|
+
Alignment discipline: text labels flush LEFT, numeric values flush RIGHT. */
|
|
149
|
+
.d3-tooltip .d3-tooltip-title {
|
|
150
|
+
font-size: var(--tooltip-title-font-size, 15px);
|
|
151
|
+
font-weight: var(--font-weight-semibold);
|
|
152
|
+
color: var(--color-black);
|
|
153
|
+
letter-spacing: var(--letter-spacing-tight);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.d3-tooltip .d3-tooltip-label {
|
|
157
|
+
color: var(--color-text-subtle);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.d3-tooltip .d3-tooltip-value {
|
|
161
|
+
font-weight: var(--font-weight-semibold);
|
|
162
|
+
color: var(--color-black);
|
|
163
|
+
font-variant-numeric: tabular-nums;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.d3-tooltip .d3-tooltip-row {
|
|
167
|
+
display: flex;
|
|
168
|
+
align-items: baseline;
|
|
169
|
+
justify-content: space-between;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* In swatched rows the label is the middle of three flex children —
|
|
173
|
+
grow it so it hugs the swatch on the left instead of centering.
|
|
174
|
+
(No flex gap: the swatch's own margin spaces it from the label; the
|
|
175
|
+
value's margin guarantees the minimum label-to-value separation.) */
|
|
176
|
+
.d3-tooltip .d3-tooltip-row .d3-tooltip-label {
|
|
177
|
+
flex: 1 1 auto;
|
|
178
|
+
text-align: left;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.d3-tooltip .d3-tooltip-row .d3-tooltip-value {
|
|
182
|
+
text-align: right;
|
|
183
|
+
margin-left: var(--space-4);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.d3-tooltip .d3-tooltip-note {
|
|
187
|
+
font-size: var(--font-size-sm);
|
|
188
|
+
color: var(--color-text-muted);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.d3-tooltip .d3-tooltip-footer {
|
|
192
|
+
border-top: 1px solid var(--color-border-row);
|
|
193
|
+
padding-top: var(--tooltip-row-gap, 3px);
|
|
194
|
+
margin-top: 1px;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.d3-tooltip .d3-tooltip-bar {
|
|
198
|
+
height: 3px;
|
|
199
|
+
border-radius: 2px;
|
|
200
|
+
margin-top: 2px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.d3-tooltip .d3-tooltip-swatch {
|
|
204
|
+
display: inline-block;
|
|
205
|
+
width: 8px;
|
|
206
|
+
height: 8px;
|
|
207
|
+
border-radius: 50%;
|
|
208
|
+
margin-right: var(--space-1);
|
|
209
|
+
flex: none;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.d3-tooltip-chart {
|
|
213
|
+
display: flex;
|
|
214
|
+
align-items: flex-end;
|
|
215
|
+
gap: var(--tooltip-bar-gap);
|
|
216
|
+
height: var(--tooltip-chart-height);
|
|
217
|
+
position: relative;
|
|
218
|
+
box-sizing: border-box;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.d3-tooltip-bar {
|
|
222
|
+
flex: 1;
|
|
223
|
+
background-color: var(--color-text-muted);
|
|
224
|
+
border-radius: var(--tooltip-bar-border-radius);
|
|
225
|
+
min-width: var(--tooltip-bar-min-width);
|
|
226
|
+
position: relative;
|
|
227
|
+
max-height: var(--tooltip-chart-height);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.d3-tooltip-bar-label {
|
|
231
|
+
position: absolute;
|
|
232
|
+
bottom: calc(-1 * var(--tooltip-label-offset-y));
|
|
233
|
+
left: 50%;
|
|
234
|
+
transform: translateX(-50%);
|
|
235
|
+
font-size: var(--font-size-xs);
|
|
236
|
+
color: var(--color-text-muted);
|
|
237
|
+
white-space: nowrap;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.d3-tooltip-bar-value {
|
|
241
|
+
position: absolute;
|
|
242
|
+
top: calc(-1 * var(--tooltip-value-offset-y));
|
|
243
|
+
left: 50%;
|
|
244
|
+
transform: translateX(-50%);
|
|
245
|
+
font-size: var(--font-size-sm);
|
|
246
|
+
font-weight: var(--font-weight-semibold);
|
|
247
|
+
color: var(--color-black);
|
|
248
|
+
white-space: nowrap;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* ===========================
|
|
252
|
+
RESPONSIVE
|
|
253
|
+
=========================== */
|
|
254
|
+
|
|
255
|
+
@media (max-width: 768px) {
|
|
256
|
+
.d3-container {
|
|
257
|
+
padding: var(--container-padding-mobile);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.d3-title {
|
|
261
|
+
font-size: var(--title-font-size-mobile);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* ===========================
|
|
266
|
+
CHART SHELL (universal config: title / subtitle / legend / footer)
|
|
267
|
+
Rendered by the responsive wrapper around the chart svg — one
|
|
268
|
+
implementation for every builder, token-driven, dark-mode-live.
|
|
269
|
+
=========================== */
|
|
270
|
+
.d3-shell-title {
|
|
271
|
+
font-family: var(--font-family);
|
|
272
|
+
font-size: var(--shell-title-size, 15px);
|
|
273
|
+
font-weight: var(--font-weight-bold);
|
|
274
|
+
color: var(--color-black);
|
|
275
|
+
letter-spacing: var(--letter-spacing-tight);
|
|
276
|
+
margin: 0 0 2px 0;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.d3-shell-subtitle {
|
|
280
|
+
font-family: var(--font-family);
|
|
281
|
+
font-size: var(--shell-subtitle-size, 11px);
|
|
282
|
+
color: var(--color-text-subtle);
|
|
283
|
+
margin: 0 0 var(--space-3) 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.d3-shell--featured.d3-shell-title {
|
|
287
|
+
font-size: var(--shell-featured-title-size, 20px);
|
|
288
|
+
margin-bottom: var(--space-1);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.d3-shell--featured.d3-shell-subtitle {
|
|
292
|
+
font-size: var(--shell-featured-subtitle-size, 14px);
|
|
293
|
+
margin-bottom: var(--space-4);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.d3-shell-legend {
|
|
297
|
+
margin-bottom: var(--space-3);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.d3-shell-footer {
|
|
301
|
+
font-family: var(--font-family);
|
|
302
|
+
font-size: var(--font-size-xs);
|
|
303
|
+
color: var(--color-text-faint);
|
|
304
|
+
text-transform: uppercase;
|
|
305
|
+
letter-spacing: var(--letter-spacing-wide);
|
|
306
|
+
margin-top: var(--space-4);
|
|
307
|
+
padding-top: var(--space-3);
|
|
308
|
+
border-top: 1px solid var(--color-border-row);
|
|
309
|
+
display: flex;
|
|
310
|
+
justify-content: space-between;
|
|
311
|
+
align-items: center;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* Chart story (story: config): narrative summary module rendered by the
|
|
315
|
+
wrapper below the svg, above the footer. Variants replicate the picked
|
|
316
|
+
story-module mockups: panel (accent-tinted takeaway), bubble (card with
|
|
317
|
+
an up-pointing tail — the chart is speaking), band (full-width dark
|
|
318
|
+
stat lede). All colors flow through the --story-* tokens; the `accent`
|
|
319
|
+
option overrides --story-accent inline on the module. */
|
|
320
|
+
.chart-story {
|
|
321
|
+
font-family: var(--font-family);
|
|
322
|
+
font-size: var(--story-font-size);
|
|
323
|
+
line-height: var(--story-line-height);
|
|
324
|
+
color: var(--color-text-primary);
|
|
325
|
+
margin-top: var(--space-3);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.chart-story-body { margin: 0; }
|
|
329
|
+
|
|
330
|
+
/* Optional uppercase lead-in (bubble and band variants) */
|
|
331
|
+
.chart-story-overline {
|
|
332
|
+
display: flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
gap: var(--space-1);
|
|
335
|
+
font-size: var(--font-size-sm);
|
|
336
|
+
font-weight: var(--font-weight-bold);
|
|
337
|
+
letter-spacing: var(--letter-spacing-wider);
|
|
338
|
+
text-transform: uppercase;
|
|
339
|
+
color: var(--color-text-subtle);
|
|
340
|
+
margin-bottom: 3px;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.chart-story-icon { display: inline-flex; flex: none; }
|
|
344
|
+
.chart-story-icon svg { display: block; }
|
|
345
|
+
|
|
346
|
+
/* panel — soft accent wash + accent hairline; icon and bold accent label
|
|
347
|
+
lead the prose inline */
|
|
348
|
+
.chart-story--panel {
|
|
349
|
+
display: flex;
|
|
350
|
+
gap: var(--space-2);
|
|
351
|
+
align-items: flex-start;
|
|
352
|
+
background: color-mix(in srgb, var(--story-accent) var(--story-panel-bg-alpha), transparent);
|
|
353
|
+
border: 1px solid color-mix(in srgb, var(--story-accent) var(--story-panel-border-alpha), transparent);
|
|
354
|
+
border-radius: 8px;
|
|
355
|
+
padding: 11px 14px;
|
|
356
|
+
}
|
|
357
|
+
.chart-story--panel .chart-story-icon { margin-top: 2px; }
|
|
358
|
+
.chart-story-label {
|
|
359
|
+
color: var(--story-accent);
|
|
360
|
+
font-weight: var(--font-weight-bold);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/* bubble — container-background card whose rotated-square tail points up
|
|
364
|
+
at the chart */
|
|
365
|
+
.chart-story--bubble {
|
|
366
|
+
position: relative;
|
|
367
|
+
margin-top: calc(var(--space-3) + 7px); /* room for the tail */
|
|
368
|
+
background: var(--color-bg-container);
|
|
369
|
+
border: 1px solid var(--color-border-light);
|
|
370
|
+
border-radius: 10px;
|
|
371
|
+
padding: 11px 15px;
|
|
372
|
+
box-shadow: var(--shadow-tooltip);
|
|
373
|
+
}
|
|
374
|
+
.chart-story--bubble::before {
|
|
375
|
+
content: "";
|
|
376
|
+
position: absolute;
|
|
377
|
+
top: -7px;
|
|
378
|
+
left: 34px;
|
|
379
|
+
width: 12px;
|
|
380
|
+
height: 12px;
|
|
381
|
+
background: var(--color-bg-container);
|
|
382
|
+
border-left: 1px solid var(--color-border-light);
|
|
383
|
+
border-top: 1px solid var(--color-border-light);
|
|
384
|
+
transform: rotate(45deg);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* band — full-width dark lede strip; spans the CHART CONTAINER (not any
|
|
388
|
+
outer card the page may have); <b>/<strong> pick up the accent */
|
|
389
|
+
.chart-story--band {
|
|
390
|
+
background: var(--story-band-bg);
|
|
391
|
+
color: var(--story-band-text);
|
|
392
|
+
border-radius: 6px;
|
|
393
|
+
padding: 13px 16px 14px;
|
|
394
|
+
}
|
|
395
|
+
.chart-story--band .chart-story-overline { color: var(--story-band-label); }
|
|
396
|
+
.chart-story--band .chart-story-body b,
|
|
397
|
+
.chart-story--band .chart-story-body strong { color: var(--story-accent); }
|
|
398
|
+
|
|
399
|
+
/* ===========================
|
|
400
|
+
CHART STATES (state: 'loading' | 'empty' | 'error')
|
|
401
|
+
===========================
|
|
402
|
+
The state box takes the chart's exact footprint (height set inline by
|
|
403
|
+
the wrapper), so the real chart swaps in with zero layout jump. Loading
|
|
404
|
+
skeletons breathe on a constant 5s cadence with one straight shimmer
|
|
405
|
+
band (9s cycle: ~3.3s crossing + ~5.7s rest) clipped to the shapes. */
|
|
406
|
+
|
|
407
|
+
.d3-state {
|
|
408
|
+
position: relative;
|
|
409
|
+
width: 100%;
|
|
410
|
+
overflow: hidden;
|
|
411
|
+
display: flex;
|
|
412
|
+
flex-direction: column;
|
|
413
|
+
align-items: center;
|
|
414
|
+
justify-content: center;
|
|
415
|
+
gap: var(--space-2);
|
|
416
|
+
text-align: center;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.d3-state-msg {
|
|
420
|
+
font-family: var(--font-family);
|
|
421
|
+
font-size: var(--font-size-md);
|
|
422
|
+
color: var(--color-text-muted);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.d3-state-glyph {
|
|
426
|
+
font-size: 22px;
|
|
427
|
+
line-height: 1;
|
|
428
|
+
color: var(--color-text-faint);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.d3-state--error .d3-state-glyph { color: var(--chart-red); opacity: 0.7; }
|
|
432
|
+
.d3-state--error .d3-state-msg { color: var(--color-text-primary); }
|
|
433
|
+
|
|
434
|
+
/* Loading fallback: three pulsing dots */
|
|
435
|
+
.d3-state-dots { display: flex; gap: 5px; }
|
|
436
|
+
.d3-state-dots span {
|
|
437
|
+
width: 7px;
|
|
438
|
+
height: 7px;
|
|
439
|
+
border-radius: 50%;
|
|
440
|
+
background: var(--color-text-faint);
|
|
441
|
+
animation: d3-state-pulse 1.2s ease-in-out infinite;
|
|
442
|
+
}
|
|
443
|
+
.d3-state-dots span:nth-child(2) { animation-delay: 0.2s; }
|
|
444
|
+
.d3-state-dots span:nth-child(3) { animation-delay: 0.4s; }
|
|
445
|
+
@keyframes d3-state-pulse {
|
|
446
|
+
0%, 80%, 100% { opacity: 0.25; transform: scale(0.85); }
|
|
447
|
+
40% { opacity: 1; transform: scale(1); }
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/* Loading skeleton: breathing columns. The shimmer is ONE band swept
|
|
451
|
+
across the whole set — each column's ::after is sized/offset in
|
|
452
|
+
container coordinates (via the --sw-* custom properties the wrapper
|
|
453
|
+
sets) so the gradient lines up seamlessly across all columns. */
|
|
454
|
+
.d3-state--columns { display: block; }
|
|
455
|
+
.d3-skel-col {
|
|
456
|
+
position: absolute;
|
|
457
|
+
bottom: 8%;
|
|
458
|
+
border-radius: 3px 3px 0 0;
|
|
459
|
+
background: var(--color-bg-highlight);
|
|
460
|
+
overflow: hidden;
|
|
461
|
+
transition: height 5s ease-in-out;
|
|
462
|
+
}
|
|
463
|
+
.d3-skel-col::after {
|
|
464
|
+
content: '';
|
|
465
|
+
position: absolute;
|
|
466
|
+
top: 0;
|
|
467
|
+
bottom: 0;
|
|
468
|
+
left: var(--sw-left);
|
|
469
|
+
width: var(--sw-width);
|
|
470
|
+
background: linear-gradient(90deg, transparent 42%,
|
|
471
|
+
color-mix(in srgb, var(--color-text-faint) 16%, transparent) 50%,
|
|
472
|
+
transparent 58%);
|
|
473
|
+
animation: d3-skel-sweep 9s linear infinite;
|
|
474
|
+
transform: translateX(-100%);
|
|
475
|
+
}
|
|
476
|
+
@keyframes d3-skel-sweep {
|
|
477
|
+
0% { transform: translateX(-100%); }
|
|
478
|
+
37% { transform: translateX(100%); }
|
|
479
|
+
100% { transform: translateX(100%); }
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* Rows/trend skeletons are SVG (shimmer clipped to the shapes via SMIL);
|
|
483
|
+
the svg just needs to fill the state box. */
|
|
484
|
+
.d3-state--rows svg, .d3-state--trend svg { display: block; width: 100%; height: 100%; }
|
|
485
|
+
|
|
486
|
+
/* ============================================
|
|
487
|
+
Facet helper (ChartFactory.facet) — minimal small-multiples chrome.
|
|
488
|
+
The grid itself is inline (display:grid on the caller's container);
|
|
489
|
+
these style the panels the helper creates inside it.
|
|
490
|
+
============================================ */
|
|
491
|
+
.d3-facet-panel { min-width: 0; }
|
|
492
|
+
.d3-facet-chart { display: block; width: 100%; }
|
|
493
|
+
|
|
494
|
+
/* Pulsing anomaly rings (Line.createPulseAnomalies) — library-owned so npm
|
|
495
|
+
consumers get the pulse without copying example CSS. The builder sets
|
|
496
|
+
animation: none inline when animate: false; users who prefer reduced
|
|
497
|
+
motion get static rings automatically. */
|
|
498
|
+
svg circle.anomaly-pulse {
|
|
499
|
+
animation: d3-anomaly-pulse var(--chart-animation-duration, 2s) ease-in-out infinite;
|
|
500
|
+
}
|
|
501
|
+
@keyframes d3-anomaly-pulse {
|
|
502
|
+
0% { opacity: 0.6; r: var(--anomaly-pulse-radius, 4px); }
|
|
503
|
+
50% { opacity: 0; r: calc(var(--anomaly-pulse-radius, 4px) * 4); }
|
|
504
|
+
100% { opacity: 0.6; r: var(--anomaly-pulse-radius, 4px); }
|
|
505
|
+
}
|
|
506
|
+
@media (prefers-reduced-motion: reduce) {
|
|
507
|
+
svg circle.anomaly-pulse { animation: none; }
|
|
508
|
+
}
|