@opendirectory.dev/skills 0.1.58 → 0.1.60
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/package.json +1 -1
- package/registry.json +18 -0
- package/skills/graphic-chart/README.md +104 -0
- package/skills/graphic-chart/SKILL.md +318 -0
- package/skills/graphic-chart/evals/evals.json +30 -0
- package/skills/graphic-chart/references/chart-library.md +487 -0
- package/skills/graphic-chart/references/style-presets.md +219 -0
- package/skills/graphic-chart/scripts/export-chart.sh +182 -0
- package/skills/graphic-chart/scripts/screenshot-chart.mjs +143 -0
- package/skills/graphic-gif/README.md +99 -0
- package/skills/graphic-gif/SKILL.md +313 -0
- package/skills/graphic-gif/evals/evals.json +30 -0
- package/skills/graphic-gif/references/animation-library.md +446 -0
- package/skills/graphic-gif/references/style-presets.md +194 -0
- package/skills/graphic-gif/scripts/capture-and-encode.mjs +201 -0
- package/skills/graphic-gif/scripts/export-gif.sh +274 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
# Chart Library — graphic-chart (ECharts v6)
|
|
2
|
+
|
|
3
|
+
8 chart types with full Apache ECharts v6 config specs. Read this before generating HTML in Step 3.
|
|
4
|
+
|
|
5
|
+
CDN:
|
|
6
|
+
```html
|
|
7
|
+
<script src="https://cdn.jsdelivr.net/npm/echarts@6.0.0/dist/echarts.min.js"></script>
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
**No plugins needed.** ECharts v6 has built-in data labels, annotations (markPoint/markLine/markArea), and all chart types. No separate CDN scripts.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Universal Init Pattern (apply to ALL chart types)
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
window.__chartReady = false;
|
|
18
|
+
|
|
19
|
+
document.fonts.ready.then(() => {
|
|
20
|
+
const chart = echarts.init(document.getElementById('chart'), null, { renderer: 'canvas' });
|
|
21
|
+
|
|
22
|
+
// CRITICAL: register BEFORE setOption — ECharts bug #14101/#17500
|
|
23
|
+
chart.on('finished', () => { window.__chartReady = true; });
|
|
24
|
+
chart.on('rendered', () => {
|
|
25
|
+
clearTimeout(window.__renderDebounce);
|
|
26
|
+
window.__renderDebounce = setTimeout(() => { window.__chartReady = true; }, 100);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const option = {
|
|
30
|
+
animation: false, // instant render for Playwright screenshot
|
|
31
|
+
backgroundColor: 'transparent',
|
|
32
|
+
// ... chart-specific config
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
chart.setOption(option); // ALWAYS last
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## ECharts Structure vs Chart.js (LLM trap — read this)
|
|
42
|
+
|
|
43
|
+
| Concept | Chart.js | ECharts |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| Chart structure | `{ type, data: {labels, datasets}, options }` | **Flat:** `{ xAxis, yAxis, series, grid, title, legend }` |
|
|
46
|
+
| Category labels | `data.labels: ['Q1','Q2']` | `xAxis: { type: 'category', data: ['Q1','Q2'] }` |
|
|
47
|
+
| Series data | `datasets: [{ data: [...] }]` | `series: [{ type: 'bar', data: [...] }]` |
|
|
48
|
+
| Highlight bar | `backgroundColor: array` | `data: [{ value: N, itemStyle: { color } }]` per item |
|
|
49
|
+
| Doughnut | `type: 'doughnut'` | `type: 'pie'` + `radius: ['40%','70%']` |
|
|
50
|
+
| Area | `fill: 'origin'` | `areaStyle: {}` on series |
|
|
51
|
+
| Data labels | `chartjs-plugin-datalabels` (external) | `label: { show: true }` built into every series |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 1. bar
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
{
|
|
59
|
+
animation: false,
|
|
60
|
+
backgroundColor: 'transparent',
|
|
61
|
+
grid: {
|
|
62
|
+
left: '8%', right: '8%', top: 80, bottom: 60,
|
|
63
|
+
containLabel: true,
|
|
64
|
+
},
|
|
65
|
+
xAxis: {
|
|
66
|
+
type: 'category', // REQUIRED — not inferred
|
|
67
|
+
data: ['Q1', 'Q2', 'Q3', 'Q4'],
|
|
68
|
+
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.15)' } },
|
|
69
|
+
axisTick: { show: false },
|
|
70
|
+
axisLabel: {
|
|
71
|
+
color: 'var(--text-muted)',
|
|
72
|
+
fontFamily: 'var(--font-body)',
|
|
73
|
+
fontSize: 14,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
yAxis: {
|
|
77
|
+
type: 'value',
|
|
78
|
+
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.07)' } },
|
|
79
|
+
axisLabel: {
|
|
80
|
+
color: 'var(--text-muted)',
|
|
81
|
+
fontFamily: 'var(--font-body)',
|
|
82
|
+
fontSize: 12,
|
|
83
|
+
formatter: (v) => '$' + v + 'k', // customize per data
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
series: [{
|
|
87
|
+
type: 'bar',
|
|
88
|
+
name: 'Revenue',
|
|
89
|
+
barMaxWidth: 72,
|
|
90
|
+
itemStyle: {
|
|
91
|
+
color: palette[0],
|
|
92
|
+
borderRadius: [4, 4, 0, 0], // rounded top corners
|
|
93
|
+
},
|
|
94
|
+
// Highlight one bar — per-item override
|
|
95
|
+
data: [
|
|
96
|
+
{ value: 420, itemStyle: { color: palette[1], borderRadius: [4,4,0,0] } },
|
|
97
|
+
{ value: 510, itemStyle: { color: palette[1], borderRadius: [4,4,0,0] } },
|
|
98
|
+
{ value: 480, itemStyle: { color: palette[1], borderRadius: [4,4,0,0] } },
|
|
99
|
+
{ value: 780, itemStyle: { color: palette[0], borderRadius: [4,4,0,0] } }, // highlighted
|
|
100
|
+
],
|
|
101
|
+
// Built-in data label — no plugin
|
|
102
|
+
label: {
|
|
103
|
+
show: false, // set true to show value on bar
|
|
104
|
+
position: 'top',
|
|
105
|
+
formatter: '{c}',
|
|
106
|
+
color: 'var(--text)',
|
|
107
|
+
fontFamily: 'var(--font-body)',
|
|
108
|
+
fontSize: 13,
|
|
109
|
+
},
|
|
110
|
+
// Highlight annotation — label above bar with callout
|
|
111
|
+
markPoint: {
|
|
112
|
+
symbol: 'pin',
|
|
113
|
+
symbolSize: 0, // hide symbol, show label only
|
|
114
|
+
label: {
|
|
115
|
+
show: true,
|
|
116
|
+
formatter: '$780k',
|
|
117
|
+
backgroundColor: palette[0],
|
|
118
|
+
color: '#000',
|
|
119
|
+
borderRadius: 4,
|
|
120
|
+
padding: [6, 10],
|
|
121
|
+
fontWeight: 'bold',
|
|
122
|
+
fontSize: 13,
|
|
123
|
+
},
|
|
124
|
+
data: [{ coord: ['Q4', 780] }], // [categoryLabel, value]
|
|
125
|
+
},
|
|
126
|
+
}]
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Horizontal bar:** Swap axes — put `type: 'category'` on `yAxis`, `type: 'value'` on `xAxis`.
|
|
131
|
+
|
|
132
|
+
**Multi-series bar:**
|
|
133
|
+
```javascript
|
|
134
|
+
series: [
|
|
135
|
+
{ type: 'bar', name: '2023', data: [...], itemStyle: { color: palette[0], borderRadius: [4,4,0,0] } },
|
|
136
|
+
{ type: 'bar', name: '2024', data: [...], itemStyle: { color: palette[1], borderRadius: [4,4,0,0] } },
|
|
137
|
+
]
|
|
138
|
+
legend: { show: true, bottom: 0, textStyle: { color: 'var(--text)' } }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Stacked bar:** Add `stack: 'total'` on each series.
|
|
142
|
+
|
|
143
|
+
**Dark vs light grid color:**
|
|
144
|
+
- Dark presets: `splitLine: { lineStyle: { color: 'rgba(255,255,255,0.07)' } }`
|
|
145
|
+
- Light presets: `splitLine: { lineStyle: { color: 'rgba(0,0,0,0.08)' } }`
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 2. line
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
{
|
|
153
|
+
animation: false,
|
|
154
|
+
backgroundColor: 'transparent',
|
|
155
|
+
grid: { left: '8%', right: '8%', top: 80, bottom: 60, containLabel: true },
|
|
156
|
+
xAxis: {
|
|
157
|
+
type: 'category',
|
|
158
|
+
data: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
|
|
159
|
+
boundaryGap: false, // line starts at first point, not center of band
|
|
160
|
+
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.15)' } },
|
|
161
|
+
axisTick: { show: false },
|
|
162
|
+
axisLabel: { color: 'var(--text-muted)', fontSize: 12 },
|
|
163
|
+
},
|
|
164
|
+
yAxis: {
|
|
165
|
+
type: 'value',
|
|
166
|
+
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.07)' } },
|
|
167
|
+
axisLabel: { color: 'var(--text-muted)', fontSize: 12, formatter: '{value}k' },
|
|
168
|
+
},
|
|
169
|
+
series: [{
|
|
170
|
+
type: 'line',
|
|
171
|
+
name: 'ARR ($k)',
|
|
172
|
+
data: [12, 18, 22, 25, 31, 38, 44, 52, 61, 68, 78, 95],
|
|
173
|
+
smooth: true, // natural curve (vs false = straight segments)
|
|
174
|
+
lineStyle: { color: palette[0], width: 2.5 },
|
|
175
|
+
itemStyle: { color: palette[0] },
|
|
176
|
+
symbolSize: 8,
|
|
177
|
+
symbol: 'circle',
|
|
178
|
+
// Highlight specific point (e.g. December)
|
|
179
|
+
markPoint: {
|
|
180
|
+
symbolSize: 0,
|
|
181
|
+
label: {
|
|
182
|
+
show: true,
|
|
183
|
+
formatter: '$95k',
|
|
184
|
+
backgroundColor: palette[0],
|
|
185
|
+
color: '#000',
|
|
186
|
+
borderRadius: 4,
|
|
187
|
+
padding: [6, 10],
|
|
188
|
+
fontWeight: 'bold',
|
|
189
|
+
fontSize: 13,
|
|
190
|
+
},
|
|
191
|
+
data: [{ coord: ['Dec', 95] }],
|
|
192
|
+
},
|
|
193
|
+
}]
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## 3. area
|
|
200
|
+
|
|
201
|
+
**No `type: 'area'` in ECharts — it's `type: 'line'` with `areaStyle`.**
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
series: [{
|
|
205
|
+
type: 'line', // NOT 'area'
|
|
206
|
+
smooth: true,
|
|
207
|
+
lineStyle: { color: palette[0], width: 2 },
|
|
208
|
+
itemStyle: { color: palette[0] },
|
|
209
|
+
areaStyle: { // THIS is what makes it an area chart
|
|
210
|
+
color: {
|
|
211
|
+
type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
|
|
212
|
+
colorStops: [
|
|
213
|
+
{ offset: 0, color: paletteAlpha[0] }, // top: semi-transparent
|
|
214
|
+
{ offset: 1, color: 'rgba(0,0,0,0)' }, // bottom: fully transparent
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
data: [5, 8, 12, 10, 15, 22, 28, 25, 32, 38, 45, 52],
|
|
219
|
+
symbolSize: 6,
|
|
220
|
+
}]
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 4. pie
|
|
226
|
+
|
|
227
|
+
**CRITICAL sizing:** Pie fills 100% of container. Always use body `padding: 64px 80px` and `.chart-container { max-height: 860px }` — see SKILL.md rule #12.
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
{
|
|
231
|
+
animation: false,
|
|
232
|
+
backgroundColor: 'transparent',
|
|
233
|
+
// No xAxis/yAxis for pie
|
|
234
|
+
legend: {
|
|
235
|
+
orient: 'vertical',
|
|
236
|
+
right: '5%',
|
|
237
|
+
top: 'center',
|
|
238
|
+
textStyle: {
|
|
239
|
+
color: 'var(--text)',
|
|
240
|
+
fontFamily: 'var(--font-body)',
|
|
241
|
+
fontSize: 20, // 20px = readable at 2× retina output
|
|
242
|
+
},
|
|
243
|
+
itemGap: 20,
|
|
244
|
+
formatter: (name) => {
|
|
245
|
+
// Include % in legend — requires access to data
|
|
246
|
+
return name; // customize with data lookup if needed
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
series: [{
|
|
250
|
+
type: 'pie',
|
|
251
|
+
radius: '65%', // size as % of container
|
|
252
|
+
center: ['42%', '50%'], // shift left to give legend room
|
|
253
|
+
data: [
|
|
254
|
+
{ value: 45, name: 'Product A', itemStyle: { color: palette[0] } },
|
|
255
|
+
{ value: 28, name: 'Product B', itemStyle: { color: palette[1] } },
|
|
256
|
+
{ value: 17, name: 'Product C', itemStyle: { color: palette[2] } },
|
|
257
|
+
{ value: 10, name: 'Other', itemStyle: { color: palette[3] } },
|
|
258
|
+
],
|
|
259
|
+
// Built-in labels — no plugin needed
|
|
260
|
+
label: {
|
|
261
|
+
show: true,
|
|
262
|
+
formatter: '{b}\n{d}%', // name + percentage
|
|
263
|
+
color: '#09090B', // dark text on bright palette colors
|
|
264
|
+
fontFamily: 'var(--font-body)',
|
|
265
|
+
fontSize: 18,
|
|
266
|
+
fontWeight: 'bold',
|
|
267
|
+
minMargin: 5,
|
|
268
|
+
},
|
|
269
|
+
labelLine: {
|
|
270
|
+
show: true,
|
|
271
|
+
length: 15,
|
|
272
|
+
length2: 10,
|
|
273
|
+
lineStyle: { color: 'rgba(255,255,255,0.3)' },
|
|
274
|
+
},
|
|
275
|
+
// Hide label on tiny slices (< 12%)
|
|
276
|
+
labelFilter: (params) => params.data.value >= 12,
|
|
277
|
+
itemStyle: { borderColor: 'var(--bg)', borderWidth: 3 },
|
|
278
|
+
emphasis: { scale: true, scaleSize: 6 },
|
|
279
|
+
}]
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**No annotation plugin for pie** — ECharts pie doesn't support markPoint/markLine.
|
|
284
|
+
|
|
285
|
+
If > 7 segments, consolidate smallest as "Other".
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## 5. doughnut
|
|
290
|
+
|
|
291
|
+
Same as pie + `radius: ['40%', '70%']`. No `type: 'doughnut'` in ECharts.
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
series: [{
|
|
295
|
+
type: 'pie',
|
|
296
|
+
radius: ['40%', '68%'], // [inner, outer] — creates the hole
|
|
297
|
+
center: ['42%', '50%'],
|
|
298
|
+
// All other config same as pie
|
|
299
|
+
}]
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Optional center label:**
|
|
303
|
+
```javascript
|
|
304
|
+
// Register as a custom ECharts plugin after chart init:
|
|
305
|
+
chart.setOption({
|
|
306
|
+
graphic: [{
|
|
307
|
+
type: 'text',
|
|
308
|
+
left: 'center',
|
|
309
|
+
top: 'center',
|
|
310
|
+
style: {
|
|
311
|
+
text: '73%',
|
|
312
|
+
font: 'bold 32px Inter, sans-serif',
|
|
313
|
+
fill: 'var(--text)',
|
|
314
|
+
textAlign: 'center',
|
|
315
|
+
}
|
|
316
|
+
}]
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## 6. scatter
|
|
323
|
+
|
|
324
|
+
```javascript
|
|
325
|
+
{
|
|
326
|
+
animation: false,
|
|
327
|
+
backgroundColor: 'transparent',
|
|
328
|
+
grid: { left: '8%', right: '8%', top: 80, bottom: 60, containLabel: true },
|
|
329
|
+
xAxis: {
|
|
330
|
+
type: 'value', // value axis — not category
|
|
331
|
+
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.07)' } },
|
|
332
|
+
axisLabel: { color: 'var(--text-muted)', fontSize: 12 },
|
|
333
|
+
},
|
|
334
|
+
yAxis: {
|
|
335
|
+
type: 'value',
|
|
336
|
+
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.07)' } },
|
|
337
|
+
axisLabel: { color: 'var(--text-muted)', fontSize: 12 },
|
|
338
|
+
},
|
|
339
|
+
series: [{
|
|
340
|
+
type: 'scatter',
|
|
341
|
+
symbolSize: 12,
|
|
342
|
+
itemStyle: { color: palette[0], opacity: 0.85 },
|
|
343
|
+
data: [[1.5, 2.3], [2.0, 3.1], [3.5, 1.8], [4.2, 4.5]], // [x, y] pairs
|
|
344
|
+
emphasis: { scale: true },
|
|
345
|
+
}]
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Highlight a specific point:
|
|
350
|
+
```javascript
|
|
351
|
+
data: [
|
|
352
|
+
[1.5, 2.3],
|
|
353
|
+
{ value: [4.2, 4.5], itemStyle: { color: palette[0], borderColor: '#fff', borderWidth: 2 } }
|
|
354
|
+
]
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 7. radar
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
{
|
|
363
|
+
animation: false,
|
|
364
|
+
backgroundColor: 'transparent',
|
|
365
|
+
radar: {
|
|
366
|
+
shape: 'circle', // or 'polygon'
|
|
367
|
+
indicator: [
|
|
368
|
+
{ name: 'Speed', max: 100 },
|
|
369
|
+
{ name: 'Accuracy', max: 100 },
|
|
370
|
+
{ name: 'Strength', max: 100 },
|
|
371
|
+
{ name: 'Stamina', max: 100 },
|
|
372
|
+
{ name: 'Agility', max: 100 },
|
|
373
|
+
],
|
|
374
|
+
radius: '65%',
|
|
375
|
+
center: ['50%', '55%'],
|
|
376
|
+
axisName: { color: 'var(--text)', fontSize: 13 },
|
|
377
|
+
splitLine: { lineStyle: { color: 'rgba(255,255,255,0.12)' } },
|
|
378
|
+
splitArea: { show: false },
|
|
379
|
+
axisLine: { lineStyle: { color: 'rgba(255,255,255,0.15)' } },
|
|
380
|
+
},
|
|
381
|
+
legend: {
|
|
382
|
+
bottom: 0,
|
|
383
|
+
textStyle: { color: 'var(--text)', fontSize: 14 },
|
|
384
|
+
},
|
|
385
|
+
series: [{
|
|
386
|
+
type: 'radar',
|
|
387
|
+
data: [{
|
|
388
|
+
name: 'Team A',
|
|
389
|
+
value: [85, 92, 70, 88, 76],
|
|
390
|
+
lineStyle: { color: palette[0], width: 2 },
|
|
391
|
+
itemStyle: { color: palette[0] },
|
|
392
|
+
areaStyle: { color: paletteAlpha[0] },
|
|
393
|
+
}]
|
|
394
|
+
}]
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## 8. treemap
|
|
401
|
+
|
|
402
|
+
```javascript
|
|
403
|
+
{
|
|
404
|
+
animation: false,
|
|
405
|
+
backgroundColor: 'transparent',
|
|
406
|
+
series: [{
|
|
407
|
+
type: 'treemap',
|
|
408
|
+
width: '100%',
|
|
409
|
+
height: '100%',
|
|
410
|
+
roam: false, // disable pan/zoom (static PNG)
|
|
411
|
+
nodeClick: false,
|
|
412
|
+
breadcrumb: { show: false },
|
|
413
|
+
label: {
|
|
414
|
+
show: true,
|
|
415
|
+
formatter: '{b}\n{c}', // name + value
|
|
416
|
+
fontSize: 14,
|
|
417
|
+
fontFamily: 'var(--font-body)',
|
|
418
|
+
color: '#fff',
|
|
419
|
+
},
|
|
420
|
+
itemStyle: {
|
|
421
|
+
borderColor: 'var(--bg)',
|
|
422
|
+
borderWidth: 2,
|
|
423
|
+
gapWidth: 2,
|
|
424
|
+
},
|
|
425
|
+
levels: [{
|
|
426
|
+
itemStyle: { borderWidth: 3, borderColor: 'var(--bg)' },
|
|
427
|
+
upperLabel: { show: false },
|
|
428
|
+
}],
|
|
429
|
+
data: [
|
|
430
|
+
{ name: 'Category A', value: 45, itemStyle: { color: palette[0] } },
|
|
431
|
+
{ name: 'Category B', value: 28, itemStyle: { color: palette[1] } },
|
|
432
|
+
{ name: 'Category C', value: 17, itemStyle: { color: palette[2] } },
|
|
433
|
+
{ name: 'Other', value: 10, itemStyle: { color: palette[3] } },
|
|
434
|
+
],
|
|
435
|
+
}]
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Highlight Patterns (applies to bar, line, scatter)
|
|
442
|
+
|
|
443
|
+
**Per-item color highlight (most common):**
|
|
444
|
+
```javascript
|
|
445
|
+
data: [
|
|
446
|
+
420, 510, 480,
|
|
447
|
+
{ value: 780, itemStyle: { color: palette[0], borderRadius: [4,4,0,0] } } // highlighted
|
|
448
|
+
]
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**MarkPoint callout label above bar:**
|
|
452
|
+
```javascript
|
|
453
|
+
markPoint: {
|
|
454
|
+
symbolSize: 0, // invisible pin, just show the label
|
|
455
|
+
data: [{ coord: ['Q4', 780] }],
|
|
456
|
+
label: {
|
|
457
|
+
show: true,
|
|
458
|
+
formatter: '$780k',
|
|
459
|
+
backgroundColor: palette[0],
|
|
460
|
+
color: '#000',
|
|
461
|
+
fontWeight: 'bold',
|
|
462
|
+
fontSize: 14,
|
|
463
|
+
fontFamily: 'var(--font-body)',
|
|
464
|
+
borderRadius: 4,
|
|
465
|
+
padding: [6, 12],
|
|
466
|
+
},
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
**MarkArea background highlight:**
|
|
471
|
+
```javascript
|
|
472
|
+
markArea: {
|
|
473
|
+
silent: true,
|
|
474
|
+
itemStyle: { color: 'rgba(250,204,21,0.1)', borderColor: 'rgba(250,204,21,0.5)', borderWidth: 1 },
|
|
475
|
+
data: [[{ xAxis: 'Q4' }, { xAxis: 'Q4' }]],
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
**MarkLine (average, target, threshold):**
|
|
480
|
+
```javascript
|
|
481
|
+
markLine: {
|
|
482
|
+
silent: true,
|
|
483
|
+
lineStyle: { type: 'dashed', color: 'rgba(255,255,255,0.4)', width: 1.5 },
|
|
484
|
+
label: { formatter: 'Avg: {c}', color: 'var(--text-muted)' },
|
|
485
|
+
data: [{ type: 'average' }],
|
|
486
|
+
}
|
|
487
|
+
```
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Style Presets — graphic-chart
|
|
2
|
+
|
|
3
|
+
5 recommended presets for data visualization charts. Each includes CSS tokens AND a data color palette — both are required.
|
|
4
|
+
|
|
5
|
+
The CSS tokens control: background, typography, grid lines, axis labels.
|
|
6
|
+
The data palette controls: bar/line/point colors and pie segment fills.
|
|
7
|
+
|
|
8
|
+
Read this file before generating HTML in Step 3. Apply ALL tokens from the chosen preset.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. clean-slate
|
|
13
|
+
|
|
14
|
+
**Character:** Professional. Enterprise-safe. High-trust. Works for any audience.
|
|
15
|
+
|
|
16
|
+
```css
|
|
17
|
+
:root {
|
|
18
|
+
--bg: #FFFFFF;
|
|
19
|
+
--bg-elevated: #F8FAFC;
|
|
20
|
+
--text: #0F172A;
|
|
21
|
+
--text-muted: #64748B;
|
|
22
|
+
--accent: #3B82F6;
|
|
23
|
+
--divider: #E2E8F0;
|
|
24
|
+
--font-display: 'Plus Jakarta Sans', sans-serif;
|
|
25
|
+
--font-body: 'Plus Jakarta Sans', sans-serif;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Font CDN:
|
|
30
|
+
```html
|
|
31
|
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Data palette (ordered by visual hierarchy):
|
|
35
|
+
```javascript
|
|
36
|
+
const palette = ['#3B82F6','#10B981','#F59E0B','#EF4444','#8B5CF6','#EC4899','#06B6D4'];
|
|
37
|
+
const paletteAlpha = ['rgba(59,130,246,0.15)','rgba(16,185,129,0.15)','rgba(245,158,11,0.15)','rgba(239,68,68,0.15)','rgba(139,92,246,0.15)','rgba(236,72,153,0.15)','rgba(6,182,212,0.15)'];
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Grid line color: `rgba(0,0,0,0.07)`
|
|
41
|
+
Axis tick color: `#64748B`
|
|
42
|
+
|
|
43
|
+
**Best for:** B2B reports, LinkedIn posts, investor decks, any content requiring professional restraint.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 2. midnight-editorial
|
|
48
|
+
|
|
49
|
+
**Character:** Editorial. Premium dark. Authoritative.
|
|
50
|
+
|
|
51
|
+
```css
|
|
52
|
+
:root {
|
|
53
|
+
--bg: #111111;
|
|
54
|
+
--bg-elevated: #1A1A1A;
|
|
55
|
+
--text: #F5F5F5;
|
|
56
|
+
--text-muted: #9CA3AF;
|
|
57
|
+
--accent: #D8F90A;
|
|
58
|
+
--divider: #2D2D2D;
|
|
59
|
+
--font-display: 'Instrument Serif', serif;
|
|
60
|
+
--font-body: 'Inter', sans-serif;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Font CDN:
|
|
65
|
+
```html
|
|
66
|
+
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Data palette:
|
|
70
|
+
```javascript
|
|
71
|
+
const palette = ['#D8F90A','#60A5FA','#34D399','#FB923C','#C084FC','#F472B6','#38BDF8'];
|
|
72
|
+
const paletteAlpha = ['rgba(216,249,10,0.18)','rgba(96,165,250,0.18)','rgba(52,211,153,0.18)','rgba(251,146,60,0.18)','rgba(192,132,252,0.18)','rgba(244,114,182,0.18)','rgba(56,189,248,0.18)'];
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Grid line color: `rgba(255,255,255,0.07)`
|
|
76
|
+
Axis tick color: `#9CA3AF`
|
|
77
|
+
|
|
78
|
+
**Best for:** Thought leadership content, editorial publications, agency presentations, premium brand charts.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 3. matt-gray
|
|
83
|
+
|
|
84
|
+
**Character:** Neutral. Sophisticated. Board-room-safe.
|
|
85
|
+
|
|
86
|
+
```css
|
|
87
|
+
:root {
|
|
88
|
+
--bg: #F5F5F0;
|
|
89
|
+
--bg-elevated: #EBEBЕ6;
|
|
90
|
+
--text: #1C1C1C;
|
|
91
|
+
--text-muted: #737373;
|
|
92
|
+
--accent: #1C1C1C;
|
|
93
|
+
--divider: #D4D4C8;
|
|
94
|
+
--font-display: 'DM Serif Display', serif;
|
|
95
|
+
--font-body: 'DM Sans', sans-serif;
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Font CDN:
|
|
100
|
+
```html
|
|
101
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Data palette (rich single-accent approach for this neutral style):
|
|
105
|
+
```javascript
|
|
106
|
+
const palette = ['#1C1C1C','#525252','#737373','#A3A3A3','#D4D4D4','#404040','#262626'];
|
|
107
|
+
// For multi-series, add a single accent color:
|
|
108
|
+
// const palette = ['#1C1C1C','#D97706','#525252','#92400E','#A3A3A3','#404040','#262626'];
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Grid line color: `rgba(0,0,0,0.09)`
|
|
112
|
+
Axis tick color: `#737373`
|
|
113
|
+
|
|
114
|
+
**Best for:** Internal reviews, board materials, consultancy reports, mixed professional audiences.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 4. electric-burst
|
|
119
|
+
|
|
120
|
+
**Character:** Bold. High-contrast. Energy.
|
|
121
|
+
|
|
122
|
+
```css
|
|
123
|
+
:root {
|
|
124
|
+
--bg: #09090B;
|
|
125
|
+
--bg-elevated: #18181B;
|
|
126
|
+
--text: #FAFAFA;
|
|
127
|
+
--text-muted: #A1A1AA;
|
|
128
|
+
--accent: #FACC15;
|
|
129
|
+
--divider: #27272A;
|
|
130
|
+
--font-display: 'Space Grotesk', sans-serif;
|
|
131
|
+
--font-body: 'DM Sans', sans-serif;
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Font CDN:
|
|
136
|
+
```html
|
|
137
|
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=DM+Sans:wght@400;500&display=swap" rel="stylesheet">
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Data palette:
|
|
141
|
+
```javascript
|
|
142
|
+
const palette = ['#FACC15','#60A5FA','#4ADE80','#F87171','#A78BFA','#FB923C','#34D399'];
|
|
143
|
+
const paletteAlpha = ['rgba(250,204,21,0.18)','rgba(96,165,250,0.18)','rgba(74,222,128,0.18)','rgba(248,113,113,0.18)','rgba(167,139,250,0.18)','rgba(251,146,60,0.18)','rgba(52,211,153,0.18)'];
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Grid line color: `rgba(255,255,255,0.07)`
|
|
147
|
+
Axis tick color: `#A1A1AA`
|
|
148
|
+
|
|
149
|
+
**Accent usage rule for electric-burst:** Yellow (`#FACC15`) on the primary dataset or highlight annotation only. Use blue/green for secondary series. Yellow everywhere = no yellow.
|
|
150
|
+
|
|
151
|
+
**Best for:** Growth metrics, social media content, startup metrics, bold data stories for tech audiences.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 5. brutalist
|
|
156
|
+
|
|
157
|
+
**Character:** Raw. Stark. Confrontational. Zero decoration.
|
|
158
|
+
|
|
159
|
+
```css
|
|
160
|
+
:root {
|
|
161
|
+
--bg: #FFFFFF;
|
|
162
|
+
--bg-elevated: #F5F5F5;
|
|
163
|
+
--text: #000000;
|
|
164
|
+
--text-muted: #666666;
|
|
165
|
+
--accent: #FF0000;
|
|
166
|
+
--divider: #000000;
|
|
167
|
+
--font-display: 'Space Mono', monospace;
|
|
168
|
+
--font-body: 'Space Mono', monospace;
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Font CDN:
|
|
173
|
+
```html
|
|
174
|
+
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Data palette:
|
|
178
|
+
```javascript
|
|
179
|
+
const palette = ['#000000','#FF0000','#555555','#888888','#BBBBBB','#333333','#777777'];
|
|
180
|
+
const paletteAlpha = ['rgba(0,0,0,0.15)','rgba(255,0,0,0.15)','rgba(85,85,85,0.15)','rgba(136,136,136,0.15)','rgba(187,187,187,0.15)','rgba(51,51,51,0.15)','rgba(119,119,119,0.15)'];
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Grid line color: `rgba(0,0,0,0.15)`
|
|
184
|
+
Axis tick color: `#666666`
|
|
185
|
+
|
|
186
|
+
**Chart.js defaults override for brutalist:**
|
|
187
|
+
```javascript
|
|
188
|
+
Chart.defaults.font.family = 'Space Mono, monospace';
|
|
189
|
+
Chart.defaults.color = '#666666';
|
|
190
|
+
Chart.defaults.borderColor = 'rgba(0,0,0,0.15)';
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Best for:** Design-forward agencies, bold comparisons, impact charts where the data itself is the aesthetic.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Chart.js Global Defaults — Set Before Chart Init
|
|
198
|
+
|
|
199
|
+
Apply immediately after `document.fonts.ready.then(() => {`:
|
|
200
|
+
```javascript
|
|
201
|
+
Chart.defaults.font.family = getComputedStyle(document.documentElement)
|
|
202
|
+
.getPropertyValue('--font-body').trim() || 'system-ui, sans-serif';
|
|
203
|
+
Chart.defaults.color = '#64748B'; // use --text-muted value for each preset
|
|
204
|
+
Chart.defaults.borderColor = '#E2E8F0'; // use --divider value for each preset
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Per-preset `Chart.defaults.color` values:
|
|
208
|
+
- clean-slate: `#64748B`
|
|
209
|
+
- midnight-editorial: `#9CA3AF`
|
|
210
|
+
- matt-gray: `#737373`
|
|
211
|
+
- electric-burst: `#A1A1AA`
|
|
212
|
+
- brutalist: `#666666`
|
|
213
|
+
|
|
214
|
+
Per-preset `Chart.defaults.borderColor` values:
|
|
215
|
+
- clean-slate: `rgba(226,232,240,1)` → `#E2E8F0`
|
|
216
|
+
- midnight-editorial: `rgba(45,45,45,1)` → `#2D2D2D`
|
|
217
|
+
- matt-gray: `rgba(212,212,200,1)` → `#D4D4C8`
|
|
218
|
+
- electric-burst: `rgba(39,39,42,1)` → `#27272A`
|
|
219
|
+
- brutalist: `rgba(0,0,0,0.15)`
|