@internetstiftelsen/charts 0.13.2 → 0.14.0

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.
@@ -10,17 +10,18 @@ new DonutChart(config: DonutChartConfig)
10
10
 
11
11
  ### Config Options
12
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
- | `donut` | `DonutConfig` | - | Donut-specific configuration |
21
- | `valueLabel` | `DonutValueLabelConfig` | - | On-chart outside label/value rendering configuration |
22
- | `theme` | `DeepPartial<ChartTheme>` | - | Theme customization |
23
- | `responsive` | `ResponsiveConfig` | - | Declarative container-query responsive overrides (theme + components) |
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
+ | `donut` | `DonutConfig` | - | Donut-specific configuration |
21
+ | `valueLabel` | `DonutValueLabelConfig` | - | On-chart outside label/value rendering configuration |
22
+ | `animate` | `boolean \| DonutAnimationConfig` | `false` | Opt-in segment animation for initial render and `update()` |
23
+ | `theme` | `DeepPartial<ChartTheme>` | - | Theme customization |
24
+ | `responsive` | `ResponsiveConfig` | - | Declarative container-query responsive overrides (theme + components) |
24
25
 
25
26
  ### Donut Config
26
27
 
@@ -40,13 +41,50 @@ valueLabel: {
40
41
  position?: 'outside' | 'auto', // default: 'auto'
41
42
  outsideOffset?: number, // default: 16
42
43
  minVerticalSpacing?: number, // default: 14
43
- formatter?: (label, value, data, percentage) => string, // default: `${label}: ${value}`
44
+ maxLabelWidth?: number,
45
+ oversizedBehavior?: 'truncate' | 'wrap' | 'hide', // default: 'truncate'
46
+ forceVisible?: boolean, // default: false
47
+ labelFormatter?: (label, value, data, percentage) => string,
48
+ valueFormatter?: (label, value, data, percentage) => string,
49
+ separator?: string, // default: ': '
50
+ formatter?: (label, value, data, percentage) => string,
44
51
  }
45
52
  ```
46
53
 
47
54
  Donut value labels are rendered outside the ring with leader lines. `auto`
48
55
  currently resolves to the same outside placement as `outside`.
49
56
 
57
+ By default, donut value labels render as `{label}: {value}`. Use
58
+ `labelFormatter`, `valueFormatter`, and `separator` to customize those parts
59
+ while keeping the label and value structurally separate. When `maxLabelWidth` is
60
+ set, `oversizedBehavior` applies to the label part only, so long labels can be
61
+ truncated, wrapped, or hidden without truncating the value. The `percentage`
62
+ argument is the computed segment share from `0` to `100`.
63
+
64
+ Use `formatter` for full custom label text. When `formatter` is provided, the
65
+ returned string is treated as a single label and overflow behavior applies to
66
+ that whole string.
67
+
68
+ Set `forceVisible: true` to keep value labels rendered when
69
+ `oversizedBehavior: 'hide'` would normally hide them.
70
+
71
+ ### Animation Config
72
+
73
+ ```typescript
74
+ animate: boolean | {
75
+ show?: boolean, // default: true when object is provided
76
+ duration?: number, // default: 700
77
+ easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' |
78
+ 'bounce-out' | 'elastic-out' | 'spring-out' |
79
+ `linear(${string})` | ((progress: number) => number),
80
+ }
81
+ ```
82
+
83
+ Animation is off by default. When enabled, segments grow from zero length on the
84
+ first render and animate from their previous geometry on `chart.update(...)` and
85
+ legend visibility changes. Use `chart.whenReady()` when surrounding UI or tests
86
+ need to wait until the current animation has finished.
87
+
50
88
  ## Example
51
89
 
52
90
  ```javascript
@@ -73,8 +111,13 @@ const chart = new DonutChart({
73
111
  },
74
112
  valueLabel: {
75
113
  show: true,
76
- formatter: (label, _value, _data, percentage) =>
77
- `${label}: ${percentage.toFixed(1)}%`,
114
+ formatter: (label, _value, _data, percentage) => {
115
+ return `${label}: ${percentage.toFixed(1)}%`;
116
+ },
117
+ },
118
+ animate: {
119
+ duration: 700,
120
+ easing: 'ease-in-out',
78
121
  },
79
122
  });
80
123
 
@@ -40,6 +40,7 @@ gauge: {
40
40
  | 'ease-in-out'
41
41
  | 'bounce-out'
42
42
  | 'elastic-out'
43
+ | 'spring-out'
43
44
  | `linear(...)` // CSS-like piecewise linear easing
44
45
  | ((t: number) => number),
45
46
  },
@@ -60,6 +61,9 @@ gauge: {
60
61
  fontFamily?: string, // default: theme.axis.fontFamily
61
62
  fontWeight?: number | string, // default: 700
62
63
  color?: string, // default: #111827
64
+ maxLabelWidth?: number,
65
+ oversizedBehavior?: 'truncate' | 'wrap' | 'hide', // default: 'truncate'
66
+ forceVisible?: boolean, // default: false
63
67
  },
64
68
  needle?: boolean | {
65
69
  show?: boolean, // default: true
@@ -86,6 +90,9 @@ gauge: {
86
90
  fontFamily?: string, // default: theme.axis.fontFamily
87
91
  fontWeight?: number | string, // default: theme.axis.fontWeight
88
92
  color?: string, // default: #4b5563
93
+ maxLabelWidth?: number,
94
+ oversizedBehavior?: 'truncate' | 'wrap' | 'hide', // default: 'truncate'
95
+ forceVisible?: boolean, // default: false
89
96
  },
90
97
  },
91
98
  segments?: [
@@ -99,6 +106,11 @@ gauge: {
99
106
  }
100
107
  ```
101
108
 
109
+ Set `maxLabelWidth` on `valueLabelStyle` or `ticks.labelStyle` to cap rendered
110
+ label width. `oversizedBehavior` controls whether labels over that cap are
111
+ truncated, wrapped, or hidden. Set `forceVisible: true` to keep labels rendered
112
+ when `oversizedBehavior: 'hide'` would normally hide them.
113
+
102
114
  ## Example
103
115
 
104
116
  ```javascript
@@ -151,6 +163,8 @@ chart.render('#gauge-container');
151
163
 
152
164
  ## Animation Easing Example
153
165
 
166
+ `spring-out` is a duration-based cubic-bezier preset.
167
+
154
168
  ```typescript
155
169
  const chart = new GaugeChart({
156
170
  data: [{ value: 72 }],
package/docs/pie-chart.md CHANGED
@@ -10,17 +10,18 @@ new PieChart(config: PieChartConfig)
10
10
 
11
11
  ### Config Options
12
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) |
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
+ | `animate` | `boolean \| PieAnimationConfig` | `false` | Opt-in slice animation for initial render and `update()` |
23
+ | `theme` | `DeepPartial<ChartTheme>` | - | Theme customization |
24
+ | `responsive` | `ResponsiveConfig` | - | Declarative container-query responsive overrides (theme + components) |
24
25
 
25
26
  ### Pie Config
26
27
 
@@ -45,11 +46,46 @@ valueLabel: {
45
46
  outsideOffset?: number, // default: 16
46
47
  insideMargin?: number, // default: 8
47
48
  minVerticalSpacing?: number, // default: 14
48
- formatter?: (label, value, data, percentage) => string, // default: `${label}: ${value}`
49
+ maxLabelWidth?: number,
50
+ oversizedBehavior?: 'truncate' | 'wrap' | 'hide', // default: 'truncate'
51
+ forceVisible?: boolean, // default: false
52
+ labelFormatter?: (label, value, data, percentage) => string,
53
+ valueFormatter?: (label, value, data, percentage) => string,
54
+ separator?: string, // default: ': '
55
+ formatter?: (label, value, data, percentage) => string,
49
56
  }
50
57
  ```
51
58
 
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`.
59
+ Rendered pie value-label text defaults to `{label}: {value}`. Use
60
+ `labelFormatter`, `valueFormatter`, and `separator` to customize those parts
61
+ while keeping the label and value structurally separate. When `maxLabelWidth` is
62
+ set, `oversizedBehavior` applies to the label part only, so long labels can be
63
+ truncated, wrapped, or hidden without truncating the value. The `percentage`
64
+ argument is the computed slice share from `0` to `100`.
65
+
66
+ Use `formatter` for full custom label text. When `formatter` is provided, the
67
+ returned string is treated as a single label and overflow behavior applies to
68
+ that whole string.
69
+
70
+ Set `forceVisible: true` to keep value labels rendered when inside-fit checks or
71
+ `oversizedBehavior: 'hide'` would normally hide them.
72
+
73
+ ### Animation Config
74
+
75
+ ```typescript
76
+ animate: boolean | {
77
+ show?: boolean, // default: true when object is provided
78
+ duration?: number, // default: 700
79
+ easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' |
80
+ 'bounce-out' | 'elastic-out' | 'spring-out' |
81
+ `linear(${string})` | ((progress: number) => number),
82
+ }
83
+ ```
84
+
85
+ Animation is off by default. When enabled, slices grow from zero length on the
86
+ first render and animate from their previous geometry on `chart.update(...)` and
87
+ legend visibility changes. Use `chart.whenReady()` when surrounding UI or tests
88
+ need to wait until the current animation has finished.
53
89
 
54
90
  ## Example
55
91
 
@@ -76,8 +112,13 @@ const chart = new PieChart({
76
112
  valueLabel: {
77
113
  show: true,
78
114
  position: 'auto',
79
- formatter: (label, _value, _data, percentage) =>
80
- `${label}: ${percentage.toFixed(1)}%`,
115
+ formatter: (label, _value, _data, percentage) => {
116
+ return `${label}: ${percentage.toFixed(1)}%`;
117
+ },
118
+ },
119
+ animate: {
120
+ duration: 700,
121
+ easing: 'ease-in-out',
81
122
  },
82
123
  });
83
124
 
@@ -102,7 +143,8 @@ are negative, the chart throws an error because there is nothing to render.
102
143
 
103
144
  When `valueLabel.position` is `inside`, the formatted value-label text that does not
104
145
  fit inside its slice is hidden. In `auto` mode, labels that are too tight (based on
105
- `insideMargin`) move outside instead.
146
+ `insideMargin`) move outside instead. Set `valueLabel.forceVisible: true` to
147
+ keep inside labels rendered even when they do not fit.
106
148
 
107
149
  ## Supported Components
108
150
 
package/docs/theming.md CHANGED
@@ -18,7 +18,7 @@ const chart = new XYChart({
18
18
  bottom: 40,
19
19
  left: 60,
20
20
  },
21
- colorPalette: ['#ff6b6b', '#4ecdc4', '#45b7d1', '#f7b731'],
21
+ colorPalette: ['#ff4069', '#c51f46', '#ff7f99', '#ffb3c2'],
22
22
  grid: {
23
23
  color: '#e0e0e0',
24
24
  opacity: 0.5,
@@ -42,9 +42,14 @@ const chart = new XYChart({
42
42
  Built-in theme presets are exported from `@internetstiftelsen/charts/theme`:
43
43
 
44
44
  ```javascript
45
- import { defaultTheme, newspaperTheme, themes } from '@internetstiftelsen/charts/theme';
45
+ import { defaultTheme, rubyTheme, themes } from '@internetstiftelsen/charts/theme';
46
46
  ```
47
47
 
48
+ `defaultTheme` uses a mixed Internetstiftelsen palette. Accent presets are available as
49
+ `rubyTheme`, `peacockTheme`, `jadeTheme`, `lemonTheme`, and `oceanTheme`, and
50
+ through the `themes` map as `themes.default`, `themes.ruby`,
51
+ `themes.peacock`, `themes.jade`, `themes.lemon`, and `themes.ocean`.
52
+
48
53
  ## Theme Options
49
54
 
50
55
  | Option | Type | Default | Description |
@@ -75,10 +80,10 @@ The color palette is used for automatic color assignment. Series without explici
75
80
  ```javascript
76
81
  theme: {
77
82
  colorPalette: [
78
- '#4ecdc4', // First series
79
- '#ff6b6b', // Second series
80
- '#45b7d1', // Third series
81
- '#f7b731', // Fourth series
83
+ '#ff4069', // First series
84
+ '#c51f46', // Second series
85
+ '#ff7f99', // Third series
86
+ '#ffb3c2', // Fourth series
82
87
  // ... continues cycling
83
88
  ],
84
89
  }
@@ -92,20 +97,20 @@ Override auto-colors by specifying colors directly on series:
92
97
 
93
98
  ```javascript
94
99
  // Lines
95
- chart.addChild(new Line({ dataKey: 'revenue', stroke: '#00ff00' }));
96
- chart.addChild(new Line({ dataKey: 'expenses', stroke: '#ff0000' }));
100
+ chart.addChild(new Line({ dataKey: 'revenue', stroke: '#ff4069' }));
101
+ chart.addChild(new Line({ dataKey: 'expenses', stroke: '#c51f46' }));
97
102
 
98
103
  // Bars
99
- chart.addChild(new Bar({ dataKey: 'sales', fill: '#4ecdc4' }));
104
+ chart.addChild(new Bar({ dataKey: 'sales', fill: '#ff4069' }));
100
105
  ```
101
106
 
102
107
  For DonutChart, specify colors in the data:
103
108
 
104
109
  ```javascript
105
110
  const data = [
106
- { name: 'Desktop', value: 450, color: '#4ecdc4' },
107
- { name: 'Mobile', value: 320, color: '#ff6b6b' },
108
- { name: 'Tablet', value: 130, color: '#45b7d1' },
111
+ { name: 'Desktop', value: 450, color: '#ff4069' },
112
+ { name: 'Mobile', value: 320, color: '#c51f46' },
113
+ { name: 'Tablet', value: 130, color: '#ff7f99' },
109
114
  ];
110
115
  ```
111
116
 
package/docs/xy-chart.md CHANGED
@@ -252,6 +252,7 @@ animate: boolean | {
252
252
  | 'ease-in-out'
253
253
  | 'bounce-out'
254
254
  | 'elastic-out'
255
+ | 'spring-out'
255
256
  | `linear(...)`
256
257
  | ((t: number) => number),
257
258
  }
@@ -261,6 +262,7 @@ Notes:
261
262
 
262
263
  - Animation is off by default.
263
264
  - Animates XY series marks only. Axes, legends, tooltips, and value labels remain static.
265
+ - `spring-out` is a duration-based cubic-bezier preset.
264
266
  - Visual exports always render the final static state, even when the live chart is animated.
265
267
 
266
268
  ## Validation
@@ -344,6 +346,7 @@ new Line({
344
346
  }, // Data point visibility (default: 'always')
345
347
  valueLabel?: {
346
348
  show?: boolean,
349
+ forceVisible?: boolean,
347
350
  formatter?: (dataKey, value, data) => string
348
351
  } // Point value badges
349
352
  })
@@ -386,6 +389,7 @@ new Scatter({
386
389
  pointSize?: number, // Point radius in pixels (default: theme.line.point.size)
387
390
  valueLabel?: {
388
391
  show?: boolean,
392
+ forceVisible?: boolean,
389
393
  formatter?: (dataKey, value, data) => string
390
394
  } // Point value badges
391
395
  })
@@ -414,6 +418,7 @@ new Bar({
414
418
  show?: boolean,
415
419
  position?: 'inside' | 'outside',
416
420
  insidePosition?: 'top' | 'middle' | 'bottom',
421
+ forceVisible?: boolean,
417
422
  formatter?: (dataKey, value, data) => string
418
423
  }
419
424
  })
@@ -543,6 +548,7 @@ new Area({
543
548
  showPoints?: boolean, // Show points on top line (default: false)
544
549
  valueLabel?: {
545
550
  show?: boolean,
551
+ forceVisible?: boolean,
546
552
  formatter?: (dataKey, value, data) => string
547
553
  } // Point value badges
548
554
  })
@@ -567,6 +573,10 @@ For `Line`, `Scatter`, `Bar`, and `Area`, `valueLabel.formatter` receives
567
573
  `(dataKey, value, data)`, where `value` is the parsed numeric series value used
568
574
  for rendering that label.
569
575
 
576
+ Set `valueLabel.forceVisible: true` to render labels even when automatic fit
577
+ checks would normally hide them, such as tiny stacked bar segments or point
578
+ labels that cannot fit above or below the point.
579
+
570
580
  ### Area Stacking
571
581
 
572
582
  Area charts support stacking when series share the same `stackId`:
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.13.2",
2
+ "version": "0.14.0",
3
3
  "name": "@internetstiftelsen/charts",
4
4
  "type": "module",
5
5
  "sideEffects": false,
@@ -42,45 +42,45 @@
42
42
  "xlsx": "^0.18.5"
43
43
  },
44
44
  "devDependencies": {
45
- "@eslint/js": "^9.39.4",
46
- "@handsontable/react-wrapper": "^16.2.0",
47
- "@internetstiftelsen/styleguide": "^5.1.25",
45
+ "@eslint/js": "^10.0.1",
46
+ "@handsontable/react-wrapper": "^17.1.0",
47
+ "@internetstiftelsen/styleguide": "^5.1.27",
48
48
  "@radix-ui/react-label": "^2.1.8",
49
49
  "@radix-ui/react-select": "^2.2.6",
50
50
  "@radix-ui/react-switch": "^1.2.6",
51
51
  "@radix-ui/react-tabs": "^1.1.13",
52
52
  "@speed-highlight/core": "^1.2.15",
53
- "@tailwindcss/vite": "^4.2.2",
53
+ "@tailwindcss/vite": "^4.3.0",
54
54
  "@testing-library/dom": "^10.4.1",
55
55
  "@testing-library/jest-dom": "^6.9.1",
56
56
  "@testing-library/react": "^16.3.2",
57
57
  "@types/d3": "^7.4.3",
58
58
  "@types/d3-cloud": "^1.2.9",
59
- "@types/node": "^24.12.0",
60
- "@types/react": "^19.2.14",
59
+ "@types/node": "^25.9.1",
60
+ "@types/react": "^19.2.15",
61
61
  "@types/react-dom": "^19.2.3",
62
- "@vitejs/plugin-react-swc": "^4.3.0",
62
+ "@vitejs/plugin-react-swc": "^4.3.1",
63
63
  "class-variance-authority": "^0.7.1",
64
64
  "clsx": "^2.1.1",
65
- "eslint": "^9.39.4",
66
- "eslint-plugin-react-hooks": "^7.0.1",
67
- "eslint-plugin-react-refresh": "^0.4.26",
68
- "globals": "^16.5.0",
69
- "handsontable": "^16.2.0",
70
- "jsdom": "^27.4.0",
71
- "lucide-react": "^0.548.0",
72
- "prettier": "3.6.2",
65
+ "eslint": "^10.4.0",
66
+ "eslint-plugin-react-hooks": "^7.1.1",
67
+ "eslint-plugin-react-refresh": "^0.5.2",
68
+ "globals": "^17.6.0",
69
+ "handsontable": "^17.1.0",
70
+ "jsdom": "^29.1.1",
71
+ "lucide-react": "^1.16.0",
72
+ "prettier": "3.8.3",
73
73
  "radix-ui": "^1.4.3",
74
- "react": "^19.2.4",
75
- "react-dom": "^19.2.4",
76
- "sass": "^1.98.0",
77
- "tailwind-merge": "^3.5.0",
78
- "tailwindcss": "^4.2.2",
79
- "tsc-alias": "^1.8.16",
74
+ "react": "^19.2.6",
75
+ "react-dom": "^19.2.6",
76
+ "sass": "^1.100.0",
77
+ "tailwind-merge": "^3.6.0",
78
+ "tailwindcss": "^4.3.0",
79
+ "tsc-alias": "^1.8.17",
80
80
  "tw-animate-css": "^1.4.0",
81
- "typescript": "~5.9.3",
82
- "typescript-eslint": "^8.57.2",
83
- "vite": "^7.3.1",
84
- "vitest": "^4.1.1"
81
+ "typescript": "~6.0.3",
82
+ "typescript-eslint": "^8.59.4",
83
+ "vite": "^8.0.14",
84
+ "vitest": "^4.1.7"
85
85
  }
86
86
  }