@internetstiftelsen/charts 0.9.2 → 0.10.1
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/README.md +137 -3
- package/dist/area.d.ts +2 -0
- package/dist/area.js +39 -31
- package/dist/bar.d.ts +20 -1
- package/dist/bar.js +395 -519
- package/dist/base-chart.d.ts +21 -1
- package/dist/base-chart.js +166 -93
- package/dist/chart-group.d.ts +137 -0
- package/dist/chart-group.js +1155 -0
- package/dist/chart-interface.d.ts +1 -1
- package/dist/donut-center-content.d.ts +1 -0
- package/dist/donut-center-content.js +21 -38
- package/dist/donut-chart.js +30 -15
- package/dist/gauge-chart.d.ts +20 -0
- package/dist/gauge-chart.js +229 -133
- package/dist/legend-state.d.ts +19 -0
- package/dist/legend-state.js +81 -0
- package/dist/legend.d.ts +5 -2
- package/dist/legend.js +45 -38
- package/dist/line.js +3 -1
- package/dist/pie-chart.d.ts +3 -0
- package/dist/pie-chart.js +45 -19
- package/dist/scatter.d.ts +16 -0
- package/dist/scatter.js +165 -0
- package/dist/tooltip.d.ts +2 -1
- package/dist/tooltip.js +21 -25
- package/dist/types.d.ts +19 -1
- package/dist/utils.js +11 -19
- package/dist/validation.d.ts +4 -0
- package/dist/validation.js +19 -0
- package/dist/x-axis.d.ts +10 -0
- package/dist/x-axis.js +190 -149
- package/dist/xy-chart.d.ts +40 -1
- package/dist/xy-chart.js +488 -165
- package/dist/y-axis.d.ts +7 -2
- package/dist/y-axis.js +99 -10
- package/docs/chart-group.md +213 -0
- package/docs/components.md +321 -0
- package/docs/donut-chart.md +193 -0
- package/docs/gauge-chart.md +175 -0
- package/docs/getting-started.md +311 -0
- package/docs/pie-chart.md +123 -0
- package/docs/theming.md +162 -0
- package/docs/word-cloud-chart.md +98 -0
- package/docs/xy-chart.md +517 -0
- package/package.json +6 -4
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
This guide covers installation and basic usage with Vanilla JavaScript and React.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @internetstiftelsen/charts
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Vanilla JavaScript
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import { XYChart } from '@internetstiftelsen/charts/xy-chart';
|
|
15
|
+
import { Line } from '@internetstiftelsen/charts/line';
|
|
16
|
+
import { Bar } from '@internetstiftelsen/charts/bar';
|
|
17
|
+
import { XAxis } from '@internetstiftelsen/charts/x-axis';
|
|
18
|
+
import { YAxis } from '@internetstiftelsen/charts/y-axis';
|
|
19
|
+
import { Grid } from '@internetstiftelsen/charts/grid';
|
|
20
|
+
import { Tooltip } from '@internetstiftelsen/charts/tooltip';
|
|
21
|
+
import { Legend } from '@internetstiftelsen/charts/legend';
|
|
22
|
+
import { Title } from '@internetstiftelsen/charts/title';
|
|
23
|
+
|
|
24
|
+
// Your data
|
|
25
|
+
const data = [
|
|
26
|
+
{ date: '2010', revenue: 100, expenses: 80 },
|
|
27
|
+
{ date: '2011', revenue: 150, expenses: 90 },
|
|
28
|
+
{ date: '2012', revenue: 200, expenses: 110 },
|
|
29
|
+
{ date: '2013', revenue: 250, expenses: 130 },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
// Create chart
|
|
33
|
+
const chart = new XYChart({ data });
|
|
34
|
+
|
|
35
|
+
// Add components
|
|
36
|
+
chart
|
|
37
|
+
.addChild(new Title({ text: 'Revenue vs Expenses' }))
|
|
38
|
+
.addChild(new Grid({ category: false, value: true }))
|
|
39
|
+
.addChild(new XAxis({ dataKey: 'date' }))
|
|
40
|
+
.addChild(new YAxis())
|
|
41
|
+
.addChild(
|
|
42
|
+
new Tooltip({
|
|
43
|
+
formatter: (dataKey, value) => `<strong>${dataKey}</strong>: $${value}k`,
|
|
44
|
+
}),
|
|
45
|
+
)
|
|
46
|
+
.addChild(new Legend({ position: 'bottom' }))
|
|
47
|
+
.addChild(new Line({ dataKey: 'revenue' })) // Auto-assigned color
|
|
48
|
+
.addChild(new Line({ dataKey: 'expenses' })); // Auto-assigned color
|
|
49
|
+
|
|
50
|
+
// Render to DOM (automatically resizes with container)
|
|
51
|
+
chart.render('#chart-container');
|
|
52
|
+
|
|
53
|
+
// Later: update with new data
|
|
54
|
+
chart.update(newData);
|
|
55
|
+
|
|
56
|
+
// Clean up when done
|
|
57
|
+
chart.destroy();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Sizing
|
|
61
|
+
|
|
62
|
+
By default, charts size themselves from the render container.
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
const chart = new XYChart({ data });
|
|
66
|
+
chart.render('#chart-container');
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For fixed-size charts, set top-level `width` and `height`:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
const chart = new XYChart({
|
|
73
|
+
data,
|
|
74
|
+
width: 800,
|
|
75
|
+
height: 400,
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Grouping Charts
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
import { ChartGroup } from '@internetstiftelsen/charts/chart-group';
|
|
83
|
+
import { XYChart } from '@internetstiftelsen/charts/xy-chart';
|
|
84
|
+
import { Line } from '@internetstiftelsen/charts/line';
|
|
85
|
+
import { Bar } from '@internetstiftelsen/charts/bar';
|
|
86
|
+
import { Legend } from '@internetstiftelsen/charts/legend';
|
|
87
|
+
import { Title } from '@internetstiftelsen/charts/title';
|
|
88
|
+
|
|
89
|
+
const lineChart = new XYChart({ data: lineData });
|
|
90
|
+
lineChart.addChild(new Line({ dataKey: 'revenue' }));
|
|
91
|
+
|
|
92
|
+
const barChart = new XYChart({ data: barData });
|
|
93
|
+
barChart.addChild(new Bar({ dataKey: 'revenue' }));
|
|
94
|
+
|
|
95
|
+
const group = new ChartGroup({
|
|
96
|
+
cols: 2,
|
|
97
|
+
gap: 20,
|
|
98
|
+
height: 420,
|
|
99
|
+
syncY: true,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
group
|
|
103
|
+
.addChild(new Title({ text: 'Revenue vs Expenses' }))
|
|
104
|
+
.addChart(barChart)
|
|
105
|
+
.addChart(lineChart)
|
|
106
|
+
.addChild(new Legend());
|
|
107
|
+
|
|
108
|
+
group.render('#chart-group');
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Each child chart keeps its own rendering logic, scales, tooltips, and responsive
|
|
112
|
+
behavior. The shared group legend controls matching series keys across all child
|
|
113
|
+
charts, and group height can be fixed with `height` or inherited from the render
|
|
114
|
+
container. Use `syncY: true` when you want visible vertical `XYChart` children
|
|
115
|
+
to share the same Y domain.
|
|
116
|
+
|
|
117
|
+
`ChartGroup` also supports responsive layout breakpoints at both levels:
|
|
118
|
+
|
|
119
|
+
- group-level `responsive.breakpoints` can override `cols` and `gap`
|
|
120
|
+
- `addChart(..., options)` can override `span`, `height`, `order`, and `hidden`
|
|
121
|
+
|
|
122
|
+
These breakpoints use the same `minWidth` / `maxWidth` matching rules as chart
|
|
123
|
+
responsive config, and all matching breakpoints merge in declaration order.
|
|
124
|
+
|
|
125
|
+
## React Integration
|
|
126
|
+
|
|
127
|
+
```jsx
|
|
128
|
+
import { useRef, useEffect } from 'react';
|
|
129
|
+
import { XYChart } from '@internetstiftelsen/charts/xy-chart';
|
|
130
|
+
import { Line } from '@internetstiftelsen/charts/line';
|
|
131
|
+
import { Bar } from '@internetstiftelsen/charts/bar';
|
|
132
|
+
import { XAxis } from '@internetstiftelsen/charts/x-axis';
|
|
133
|
+
import { YAxis } from '@internetstiftelsen/charts/y-axis';
|
|
134
|
+
import { Grid } from '@internetstiftelsen/charts/grid';
|
|
135
|
+
import { Tooltip } from '@internetstiftelsen/charts/tooltip';
|
|
136
|
+
import { Legend } from '@internetstiftelsen/charts/legend';
|
|
137
|
+
|
|
138
|
+
function Chart({ data }) {
|
|
139
|
+
const containerRef = useRef(null);
|
|
140
|
+
const chartRef = useRef(null);
|
|
141
|
+
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (containerRef.current) {
|
|
144
|
+
// Create chart
|
|
145
|
+
const chart = new XYChart({ data });
|
|
146
|
+
|
|
147
|
+
chart
|
|
148
|
+
.addChild(new Grid({ value: true }))
|
|
149
|
+
.addChild(new XAxis({ dataKey: 'column' }))
|
|
150
|
+
.addChild(new YAxis())
|
|
151
|
+
.addChild(new Tooltip())
|
|
152
|
+
.addChild(new Legend({ position: 'bottom' }))
|
|
153
|
+
.addChild(new Line({ dataKey: 'value1' }))
|
|
154
|
+
.addChild(new Line({ dataKey: 'value2' }));
|
|
155
|
+
|
|
156
|
+
chart.render(containerRef.current);
|
|
157
|
+
chartRef.current = chart;
|
|
158
|
+
|
|
159
|
+
return () => {
|
|
160
|
+
chart.destroy();
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}, []);
|
|
164
|
+
|
|
165
|
+
// Update when data changes
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
if (chartRef.current && data) {
|
|
168
|
+
chartRef.current.update(data);
|
|
169
|
+
}
|
|
170
|
+
}, [data]);
|
|
171
|
+
|
|
172
|
+
return <div ref={containerRef} />;
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Donut Chart Example
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
import { DonutChart } from '@internetstiftelsen/charts/donut-chart';
|
|
180
|
+
import { DonutCenterContent } from '@internetstiftelsen/charts/donut-center-content';
|
|
181
|
+
import { Legend } from '@internetstiftelsen/charts/legend';
|
|
182
|
+
import { Tooltip } from '@internetstiftelsen/charts/tooltip';
|
|
183
|
+
|
|
184
|
+
const data = [
|
|
185
|
+
{ name: 'Desktop', value: 450 },
|
|
186
|
+
{ name: 'Mobile', value: 320 },
|
|
187
|
+
{ name: 'Tablet', value: 130 },
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
const chart = new DonutChart({
|
|
191
|
+
data,
|
|
192
|
+
valueKey: 'value',
|
|
193
|
+
labelKey: 'name',
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
chart
|
|
197
|
+
.addChild(
|
|
198
|
+
new DonutCenterContent({
|
|
199
|
+
mainValue: '900',
|
|
200
|
+
title: 'Total',
|
|
201
|
+
subtitle: 'visitors',
|
|
202
|
+
}),
|
|
203
|
+
)
|
|
204
|
+
.addChild(new Legend({ position: 'bottom' }))
|
|
205
|
+
.addChild(new Tooltip());
|
|
206
|
+
|
|
207
|
+
chart.render('#donut-container');
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Pie Chart Example
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
import { PieChart } from '@internetstiftelsen/charts/pie-chart';
|
|
214
|
+
import { Legend } from '@internetstiftelsen/charts/legend';
|
|
215
|
+
import { Tooltip } from '@internetstiftelsen/charts/tooltip';
|
|
216
|
+
|
|
217
|
+
const data = [
|
|
218
|
+
{ name: 'Desktop', value: 450 },
|
|
219
|
+
{ name: 'Mobile', value: 320 },
|
|
220
|
+
{ name: 'Tablet', value: 130 },
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
const chart = new PieChart({
|
|
224
|
+
data,
|
|
225
|
+
valueKey: 'value',
|
|
226
|
+
labelKey: 'name',
|
|
227
|
+
pie: {
|
|
228
|
+
sort: 'none',
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
chart.addChild(new Legend({ position: 'bottom' })).addChild(new Tooltip());
|
|
233
|
+
|
|
234
|
+
chart.render('#pie-container');
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Gauge Chart Example
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
import { GaugeChart } from '@internetstiftelsen/charts/gauge-chart';
|
|
241
|
+
import { Legend } from '@internetstiftelsen/charts/legend';
|
|
242
|
+
import { Tooltip } from '@internetstiftelsen/charts/tooltip';
|
|
243
|
+
|
|
244
|
+
const data = [{ label: 'KPI', value: 72, target: 80 }];
|
|
245
|
+
|
|
246
|
+
const chart = new GaugeChart({
|
|
247
|
+
data,
|
|
248
|
+
valueKey: 'value',
|
|
249
|
+
targetValueKey: 'target',
|
|
250
|
+
gauge: {
|
|
251
|
+
min: 0,
|
|
252
|
+
max: 100,
|
|
253
|
+
halfCircle: true,
|
|
254
|
+
segments: [
|
|
255
|
+
{ from: 0, to: 60, color: '#10b981', label: 'Good' },
|
|
256
|
+
{ from: 60, to: 85, color: '#f59e0b', label: 'Warning' },
|
|
257
|
+
{ from: 85, to: 100, color: '#ef4444', label: 'Risk' },
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
chart.addChild(new Legend({ position: 'bottom' })).addChild(new Tooltip());
|
|
263
|
+
|
|
264
|
+
chart.render('#gauge-container');
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Word Cloud Example
|
|
268
|
+
|
|
269
|
+
```javascript
|
|
270
|
+
import { WordCloudChart } from '@internetstiftelsen/charts/word-cloud-chart';
|
|
271
|
+
import { Title } from '@internetstiftelsen/charts/title';
|
|
272
|
+
|
|
273
|
+
const data = [
|
|
274
|
+
{ word: 'internet', count: 96 },
|
|
275
|
+
{ word: 'social', count: 82 },
|
|
276
|
+
{ word: 'news', count: 75 },
|
|
277
|
+
{ word: 'streaming', count: 68 },
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
const chart = new WordCloudChart({
|
|
281
|
+
data,
|
|
282
|
+
wordCloud: {
|
|
283
|
+
minWordLength: 3,
|
|
284
|
+
minValue: 5,
|
|
285
|
+
minFontSize: 3,
|
|
286
|
+
maxFontSize: 20,
|
|
287
|
+
padding: 1,
|
|
288
|
+
spiral: 'archimedean',
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
chart.addChild(new Title({ text: 'Most mentioned words' }));
|
|
293
|
+
|
|
294
|
+
chart.render('#word-cloud-container');
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
`minFontSize` and `maxFontSize` are percentages of the smaller plot-area
|
|
298
|
+
dimension and define the relative size range passed into `d3-cloud`. Word
|
|
299
|
+
clouds accept flat `{ word, count }` rows and use theme typography/colors
|
|
300
|
+
directly when laying out and rendering the cloud.
|
|
301
|
+
|
|
302
|
+
## Next Steps
|
|
303
|
+
|
|
304
|
+
- [XYChart API](./xy-chart.md) - Line and bar charts
|
|
305
|
+
- [ChartGroup API](./chart-group.md) - Combined chart layouts
|
|
306
|
+
- [WordCloudChart API](./word-cloud-chart.md) - Word clouds and frequency filtering
|
|
307
|
+
- [DonutChart API](./donut-chart.md) - Donut/pie charts
|
|
308
|
+
- [PieChart API](./pie-chart.md) - Pie charts
|
|
309
|
+
- [GaugeChart API](./gauge-chart.md) - Gauge charts
|
|
310
|
+
- [Components](./components.md) - Axes, Grid, Tooltip, Legend, Title
|
|
311
|
+
- [Theming](./theming.md) - Customize colors and styles
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# PieChart API
|
|
2
|
+
|
|
3
|
+
A chart for displaying proportional categorical data as pie slices.
|
|
4
|
+
|
|
5
|
+
## Constructor
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
new PieChart(config: PieChartConfig)
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Config Options
|
|
12
|
+
|
|
13
|
+
| Option | Type | Default | Description |
|
|
14
|
+
| ------------ | ------------------------- | --------- | --------------------------------------------------------------------- |
|
|
15
|
+
| `data` | `DataItem[]` | required | Array of data objects |
|
|
16
|
+
| `width` | `number` | - | Explicit chart width in pixels |
|
|
17
|
+
| `height` | `number` | - | Explicit chart height in pixels |
|
|
18
|
+
| `valueKey` | `string` | `'value'` | Key for numeric values in data |
|
|
19
|
+
| `labelKey` | `string` | `'name'` | Key for segment labels in data |
|
|
20
|
+
| `pie` | `PieConfig` | - | Pie-specific configuration |
|
|
21
|
+
| `valueLabel` | `PieValueLabelConfig` | - | On-chart slice label/value rendering configuration |
|
|
22
|
+
| `theme` | `DeepPartial<ChartTheme>` | - | Theme customization |
|
|
23
|
+
| `responsive` | `ResponsiveConfig` | - | Declarative container-query responsive overrides (theme + components) |
|
|
24
|
+
|
|
25
|
+
### Pie Config
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
pie: {
|
|
29
|
+
innerRadius?: number, // 0-1, percentage of outer radius (default: 0)
|
|
30
|
+
startAngle?: number, // Radians (default: 0)
|
|
31
|
+
endAngle?: number, // Radians (default: Math.PI * 2)
|
|
32
|
+
padAngle?: number, // Radians between slices (default: theme.donut.padAngle)
|
|
33
|
+
cornerRadius?: number, // Corner radius in pixels (default: theme.donut.cornerRadius)
|
|
34
|
+
sort?: 'none' | 'ascending' | 'descending' | ((a, b) => number),
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### ValueLabel Config
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
valueLabel: {
|
|
42
|
+
show?: boolean, // default: false
|
|
43
|
+
position?: 'inside' | 'outside' | 'auto', // default: 'auto'
|
|
44
|
+
minInsidePercentage?: number, // default: 8
|
|
45
|
+
outsideOffset?: number, // default: 16
|
|
46
|
+
insideMargin?: number, // default: 8
|
|
47
|
+
minVerticalSpacing?: number, // default: 14
|
|
48
|
+
formatter?: (label, value, data, percentage) => string, // default: `${label}: ${value}`
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Rendered pie value-label text defaults to `{label}: {value}` and can be customized with `valueLabel.formatter`. The `percentage` argument is the computed slice share from `0` to `100`.
|
|
53
|
+
|
|
54
|
+
## Example
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
import { PieChart } from '@internetstiftelsen/charts/pie-chart';
|
|
58
|
+
import { Legend } from '@internetstiftelsen/charts/legend';
|
|
59
|
+
import { Tooltip } from '@internetstiftelsen/charts/tooltip';
|
|
60
|
+
import { Title } from '@internetstiftelsen/charts/title';
|
|
61
|
+
|
|
62
|
+
const data = [
|
|
63
|
+
{ name: 'Desktop', value: 450 },
|
|
64
|
+
{ name: 'Mobile', value: 320 },
|
|
65
|
+
{ name: 'Tablet', value: 130 },
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const chart = new PieChart({
|
|
69
|
+
data,
|
|
70
|
+
valueKey: 'value',
|
|
71
|
+
labelKey: 'name',
|
|
72
|
+
pie: {
|
|
73
|
+
padAngle: 0.02,
|
|
74
|
+
sort: 'none',
|
|
75
|
+
},
|
|
76
|
+
valueLabel: {
|
|
77
|
+
show: true,
|
|
78
|
+
position: 'auto',
|
|
79
|
+
formatter: (label, _value, _data, percentage) =>
|
|
80
|
+
`${label}: ${percentage.toFixed(1)}%`,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
chart
|
|
85
|
+
.addChild(new Title({ text: 'Device Distribution' }))
|
|
86
|
+
.addChild(new Legend({ position: 'bottom' }))
|
|
87
|
+
.addChild(new Tooltip());
|
|
88
|
+
|
|
89
|
+
chart.render('#chart-container');
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Responsive Height and Labels
|
|
93
|
+
|
|
94
|
+
- PieChart uses container-driven `100%` height (same behavior model as width).
|
|
95
|
+
- Set an explicit container height for predictable visual sizing.
|
|
96
|
+
- Value label font size shrinks with the chart size for better fit on smaller pies.
|
|
97
|
+
|
|
98
|
+
## Negative Values
|
|
99
|
+
|
|
100
|
+
Negative values are skipped (not rendered) and logged as warnings. If all rows
|
|
101
|
+
are negative, the chart throws an error because there is nothing to render.
|
|
102
|
+
|
|
103
|
+
When `valueLabel.position` is `inside`, the formatted value-label text that does not
|
|
104
|
+
fit inside its slice is hidden. In `auto` mode, labels that are too tight (based on
|
|
105
|
+
`insideMargin`) move outside instead.
|
|
106
|
+
|
|
107
|
+
## Supported Components
|
|
108
|
+
|
|
109
|
+
PieChart supports the following components via `addChild()`:
|
|
110
|
+
|
|
111
|
+
- `Title` - Chart title
|
|
112
|
+
- `Legend` - Interactive legend (click to toggle slices)
|
|
113
|
+
- `Tooltip` - Hover/focus tooltips with slice info
|
|
114
|
+
|
|
115
|
+
## Tooltip Formatting
|
|
116
|
+
|
|
117
|
+
The default tooltip shows label, value, and percentage. Customize with a formatter:
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
new Tooltip({
|
|
121
|
+
formatter: (dataKey, value, data) => `${dataKey}: ${value.toLocaleString()} visitors (${data.region})`,
|
|
122
|
+
});
|
|
123
|
+
```
|
package/docs/theming.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Theming
|
|
2
|
+
|
|
3
|
+
Customize the appearance of charts through the theme configuration.
|
|
4
|
+
|
|
5
|
+
Theme overrides are deep-partial. You can override nested fields like
|
|
6
|
+
`theme.axis.fontSize` or `theme.donut.centerContent.mainValue.color` without
|
|
7
|
+
redefining the rest of those nested objects.
|
|
8
|
+
|
|
9
|
+
## Theme Configuration
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
const chart = new XYChart({
|
|
13
|
+
data,
|
|
14
|
+
theme: {
|
|
15
|
+
margins: {
|
|
16
|
+
top: 30,
|
|
17
|
+
right: 30,
|
|
18
|
+
bottom: 40,
|
|
19
|
+
left: 60,
|
|
20
|
+
},
|
|
21
|
+
colorPalette: ['#ff6b6b', '#4ecdc4', '#45b7d1', '#f7b731'],
|
|
22
|
+
grid: {
|
|
23
|
+
color: '#e0e0e0',
|
|
24
|
+
opacity: 0.5,
|
|
25
|
+
},
|
|
26
|
+
axis: {
|
|
27
|
+
fontFamily: 'Inter, sans-serif',
|
|
28
|
+
fontSize: 12,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Built-in theme presets are exported from `@internetstiftelsen/charts/theme`:
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
import { defaultTheme, newspaperTheme, themes } from '@internetstiftelsen/charts/theme';
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Theme Options
|
|
41
|
+
|
|
42
|
+
| Option | Type | Default | Description |
|
|
43
|
+
| --------------------- | ---------- | ---------------------------------------------- | -------------------------------------------- |
|
|
44
|
+
| `margins` | `object` | `{ top: 20, right: 20, bottom: 20, left: 20 }` | Base margins |
|
|
45
|
+
| `colorPalette` | `string[]` | - | Colors for auto-assignment |
|
|
46
|
+
| `grid.color` | `string` | `'#e0e0e0'` | Grid line color |
|
|
47
|
+
| `grid.opacity` | `number` | `0.5` | Grid line opacity |
|
|
48
|
+
| `fontFamily` | `string` | - | Base font family |
|
|
49
|
+
| `axis.fontFamily` | `string` | - | Axis text font |
|
|
50
|
+
| `axis.fontSize` | `number` | - | Axis text size in pixels |
|
|
51
|
+
| `legend.paddingX` | `number` | `0` | Horizontal legend layout padding |
|
|
52
|
+
| `legend.itemSpacingX` | `number` | `20` | Horizontal spacing between legend items |
|
|
53
|
+
| `legend.itemSpacingY` | `number` | `8` | Vertical spacing between wrapped legend rows |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Color Palette
|
|
58
|
+
|
|
59
|
+
The color palette is used for automatic color assignment. Series without explicit colors get assigned colors from this palette based on their index.
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
theme: {
|
|
63
|
+
colorPalette: [
|
|
64
|
+
'#4ecdc4', // First series
|
|
65
|
+
'#ff6b6b', // Second series
|
|
66
|
+
'#45b7d1', // Third series
|
|
67
|
+
'#f7b731', // Fourth series
|
|
68
|
+
// ... continues cycling
|
|
69
|
+
],
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Manual Color Assignment
|
|
76
|
+
|
|
77
|
+
Override auto-colors by specifying colors directly on series:
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
// Lines
|
|
81
|
+
chart.addChild(new Line({ dataKey: 'revenue', stroke: '#00ff00' }));
|
|
82
|
+
chart.addChild(new Line({ dataKey: 'expenses', stroke: '#ff0000' }));
|
|
83
|
+
|
|
84
|
+
// Bars
|
|
85
|
+
chart.addChild(new Bar({ dataKey: 'sales', fill: '#4ecdc4' }));
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
For DonutChart, specify colors in the data:
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
const data = [
|
|
92
|
+
{ name: 'Desktop', value: 450, color: '#4ecdc4' },
|
|
93
|
+
{ name: 'Mobile', value: 320, color: '#ff6b6b' },
|
|
94
|
+
{ name: 'Tablet', value: 130, color: '#45b7d1' },
|
|
95
|
+
];
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Donut Theme Defaults
|
|
101
|
+
|
|
102
|
+
DonutChart has additional theme defaults:
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
theme: {
|
|
106
|
+
donut: {
|
|
107
|
+
innerRadius: 0.5, // Inner radius ratio (0-1)
|
|
108
|
+
padAngle: 0.02, // Gap between segments
|
|
109
|
+
cornerRadius: 0, // Rounded corners
|
|
110
|
+
centerContent: {
|
|
111
|
+
mainValue: {
|
|
112
|
+
fontSize: 32,
|
|
113
|
+
fontWeight: 'bold',
|
|
114
|
+
color: '#1f2a36',
|
|
115
|
+
},
|
|
116
|
+
title: {
|
|
117
|
+
fontSize: 14,
|
|
118
|
+
fontWeight: 'normal',
|
|
119
|
+
color: '#6b7280',
|
|
120
|
+
},
|
|
121
|
+
subtitle: {
|
|
122
|
+
fontSize: 12,
|
|
123
|
+
fontWeight: 'normal',
|
|
124
|
+
color: '#9ca3af',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Responsive Behavior
|
|
134
|
+
|
|
135
|
+
Theme config controls styling, not sizing. To control size, either pass
|
|
136
|
+
top-level `width` and `height` on the chart config or style the container.
|
|
137
|
+
|
|
138
|
+
For fixed-size charts:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
const chart = new XYChart({
|
|
142
|
+
data,
|
|
143
|
+
width: 800,
|
|
144
|
+
height: 400,
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
For responsive charts:
|
|
149
|
+
|
|
150
|
+
```css
|
|
151
|
+
.chart-container {
|
|
152
|
+
width: 100%;
|
|
153
|
+
max-width: 1200px;
|
|
154
|
+
height: 600px;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Charts render with container-driven `100%` height (same model as width). Set an
|
|
159
|
+
explicit container height for predictable visual sizing when you are not using a
|
|
160
|
+
fixed `height`.
|
|
161
|
+
|
|
162
|
+
No manual resize calls needed - charts use ResizeObserver to respond automatically.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# WordCloudChart API
|
|
2
|
+
|
|
3
|
+
`WordCloudChart` renders a word cloud from flat rows with a fixed shape:
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
[
|
|
7
|
+
{ word: 'internet', count: 96 },
|
|
8
|
+
{ word: 'social', count: 82 },
|
|
9
|
+
];
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Constructor
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
new WordCloudChart(config: WordCloudChartConfig)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Config Options
|
|
19
|
+
|
|
20
|
+
| Option | Type | Default | Description |
|
|
21
|
+
| ------------ | ------------------------- | -------- | ------------------------------------------------ |
|
|
22
|
+
| `data` | `DataItem[]` | required | Flat `{ word, count }` rows |
|
|
23
|
+
| `width` | `number` | - | Explicit chart width in pixels |
|
|
24
|
+
| `height` | `number` | - | Explicit chart height in pixels |
|
|
25
|
+
| `theme` | `DeepPartial<ChartTheme>` | - | Theme customization |
|
|
26
|
+
| `responsive` | `ResponsiveConfig` | - | Declarative container-query responsive overrides |
|
|
27
|
+
| `wordCloud` | `WordCloudConfig` | - | Layout and filtering options |
|
|
28
|
+
|
|
29
|
+
### `wordCloud`
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
wordCloud: {
|
|
33
|
+
maxWords?: number; // default: 75
|
|
34
|
+
minWordLength?: number; // default: 1
|
|
35
|
+
minValue?: number; // default: 1
|
|
36
|
+
minFontSize?: number; // default: 3 (% of smaller plot-area dimension)
|
|
37
|
+
maxFontSize?: number; // default: 20 (% of smaller plot-area dimension)
|
|
38
|
+
padding?: number; // default: 1
|
|
39
|
+
rotation?: 'none' | 'right-angle'; // optional preset
|
|
40
|
+
spiral?: 'archimedean' | 'rectangular';
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Example
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { WordCloudChart } from '@internetstiftelsen/charts/word-cloud-chart';
|
|
48
|
+
import { Title } from '@internetstiftelsen/charts/title';
|
|
49
|
+
|
|
50
|
+
const data = [
|
|
51
|
+
{ word: 'internet', count: 96 },
|
|
52
|
+
{ word: 'social', count: 82 },
|
|
53
|
+
{ word: 'news', count: 75 },
|
|
54
|
+
{ word: 'streaming', count: 68 },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const chart = new WordCloudChart({
|
|
58
|
+
data,
|
|
59
|
+
wordCloud: {
|
|
60
|
+
minWordLength: 3,
|
|
61
|
+
minValue: 10,
|
|
62
|
+
minFontSize: 3,
|
|
63
|
+
maxFontSize: 20,
|
|
64
|
+
padding: 1,
|
|
65
|
+
spiral: 'archimedean',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
chart.addChild(new Title({ text: 'Most mentioned words' }));
|
|
70
|
+
|
|
71
|
+
chart.render('#word-cloud');
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Data Rules
|
|
75
|
+
|
|
76
|
+
- Grouped datasets are not supported.
|
|
77
|
+
- Word text is normalized with `trim()`.
|
|
78
|
+
- Rows with empty words, non-numeric counts, counts below `minValue`, or words
|
|
79
|
+
shorter than `minWordLength` are skipped.
|
|
80
|
+
- Duplicate surviving words are aggregated after normalization.
|
|
81
|
+
- The chart throws if no valid words remain after filtering.
|
|
82
|
+
|
|
83
|
+
## Supported Components
|
|
84
|
+
|
|
85
|
+
- `Title`
|
|
86
|
+
|
|
87
|
+
`XAxis`, `YAxis`, `Grid`, `Legend`, and `Tooltip` are not used by
|
|
88
|
+
`WordCloudChart`.
|
|
89
|
+
|
|
90
|
+
## Notes
|
|
91
|
+
|
|
92
|
+
- `minFontSize` and `maxFontSize` use percentages of the smaller plot-area
|
|
93
|
+
dimension and define the relative size range passed into `d3-cloud`.
|
|
94
|
+
- The chart uses theme typography and palette colors directly when configuring
|
|
95
|
+
`d3-cloud` and rendering the final SVG.
|
|
96
|
+
- If `rotation` is omitted, the chart uses the native `d3-cloud` rotate
|
|
97
|
+
accessor.
|
|
98
|
+
- `rotation: 'right-angle'` mixes `0` and `90` degree labels.
|