@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.
Files changed (46) hide show
  1. package/README.md +137 -3
  2. package/dist/area.d.ts +2 -0
  3. package/dist/area.js +39 -31
  4. package/dist/bar.d.ts +20 -1
  5. package/dist/bar.js +395 -519
  6. package/dist/base-chart.d.ts +21 -1
  7. package/dist/base-chart.js +166 -93
  8. package/dist/chart-group.d.ts +137 -0
  9. package/dist/chart-group.js +1155 -0
  10. package/dist/chart-interface.d.ts +1 -1
  11. package/dist/donut-center-content.d.ts +1 -0
  12. package/dist/donut-center-content.js +21 -38
  13. package/dist/donut-chart.js +30 -15
  14. package/dist/gauge-chart.d.ts +20 -0
  15. package/dist/gauge-chart.js +229 -133
  16. package/dist/legend-state.d.ts +19 -0
  17. package/dist/legend-state.js +81 -0
  18. package/dist/legend.d.ts +5 -2
  19. package/dist/legend.js +45 -38
  20. package/dist/line.js +3 -1
  21. package/dist/pie-chart.d.ts +3 -0
  22. package/dist/pie-chart.js +45 -19
  23. package/dist/scatter.d.ts +16 -0
  24. package/dist/scatter.js +165 -0
  25. package/dist/tooltip.d.ts +2 -1
  26. package/dist/tooltip.js +21 -25
  27. package/dist/types.d.ts +19 -1
  28. package/dist/utils.js +11 -19
  29. package/dist/validation.d.ts +4 -0
  30. package/dist/validation.js +19 -0
  31. package/dist/x-axis.d.ts +10 -0
  32. package/dist/x-axis.js +190 -149
  33. package/dist/xy-chart.d.ts +40 -1
  34. package/dist/xy-chart.js +488 -165
  35. package/dist/y-axis.d.ts +7 -2
  36. package/dist/y-axis.js +99 -10
  37. package/docs/chart-group.md +213 -0
  38. package/docs/components.md +321 -0
  39. package/docs/donut-chart.md +193 -0
  40. package/docs/gauge-chart.md +175 -0
  41. package/docs/getting-started.md +311 -0
  42. package/docs/pie-chart.md +123 -0
  43. package/docs/theming.md +162 -0
  44. package/docs/word-cloud-chart.md +98 -0
  45. package/docs/xy-chart.md +517 -0
  46. 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
+ ```
@@ -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.