@ons/design-system 72.3.0 → 72.5.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.
Files changed (48) hide show
  1. package/components/button/_button.scss +171 -3
  2. package/components/button/_macro.njk +4 -0
  3. package/components/card/_card.scss +5 -0
  4. package/components/card/_macro.njk +10 -2
  5. package/components/card/_macro.spec.js +58 -0
  6. package/components/card/example-card-set-with-headline-figures.njk +62 -0
  7. package/components/chart/_chart.scss +29 -0
  8. package/components/chart/_macro.njk +101 -0
  9. package/components/chart/_macro.spec.js +437 -0
  10. package/components/chart/bar-chart.js +160 -0
  11. package/components/chart/chart-constants.js +21 -0
  12. package/components/chart/chart.dom.js +8 -0
  13. package/components/chart/chart.js +157 -0
  14. package/components/chart/column-chart.js +48 -0
  15. package/components/chart/common-chart-options.js +200 -0
  16. package/components/chart/example-bar-chart.njk +54 -0
  17. package/components/chart/example-clustered-bar-chart.njk +60 -0
  18. package/components/chart/example-clustered-column-chart.njk +72 -0
  19. package/components/chart/example-column-chart.njk +52 -0
  20. package/components/chart/example-line-chart.njk +219 -0
  21. package/components/chart/example-stacked-bar-chart.njk +60 -0
  22. package/components/chart/example-stacked-column-chart.njk +65 -0
  23. package/components/chart/line-chart.js +47 -0
  24. package/components/chart/specific-chart-options.js +22 -0
  25. package/components/description-list/_description-list.scss +2 -0
  26. package/components/footer/_footer.scss +12 -0
  27. package/components/footer/_macro.njk +38 -17
  28. package/components/header/_header.scss +18 -2
  29. package/components/header/_macro.njk +81 -8
  30. package/components/header/_macro.spec.js +97 -0
  31. package/components/header/example-header-with-search-button.njk +190 -0
  32. package/components/header/header.spec.js +128 -0
  33. package/components/hero/_hero.scss +13 -10
  34. package/components/hero/_macro.njk +49 -18
  35. package/components/hero/_macro.spec.js +21 -2
  36. package/components/hero/example-hero-grey.njk +10 -8
  37. package/components/icon/_macro.njk +41 -8
  38. package/components/input/_input.scss +15 -0
  39. package/components/input/_macro.njk +3 -3
  40. package/components/navigation/navigation.dom.js +17 -0
  41. package/components/navigation/navigation.js +72 -2
  42. package/css/main.css +1 -1
  43. package/js/main.js +2 -0
  44. package/package.json +4 -1
  45. package/scripts/main.es5.js +1 -1
  46. package/scripts/main.js +1 -1
  47. package/scss/main.scss +1 -0
  48. package/scss/utilities/_grid.scss +13 -4
@@ -0,0 +1,437 @@
1
+ /** @jest-environment jsdom */
2
+
3
+ import * as cheerio from 'cheerio';
4
+
5
+ import axe from '../../tests/helpers/axe';
6
+ import { renderComponent } from '../../tests/helpers/rendering';
7
+ import {
8
+ EXAMPLE_LINE_CHART_REQUIRED_PARAMS,
9
+ EXAMPLE_LINE_CHART_WITH_CONFIG_PARAMS,
10
+ EXAMPLE_BAR_CHART_PARAMS,
11
+ EXAMPLE_COLUMN_CHART_PARAMS,
12
+ } from './_test-examples';
13
+
14
+ describe('Macro: Chart', () => {
15
+ describe('FOR: Line Chart', () => {
16
+ describe('GIVEN: Params: required', () => {
17
+ describe('WHEN: required params are provided', () => {
18
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_LINE_CHART_REQUIRED_PARAMS));
19
+
20
+ test('THEN: it passes jest-axe checks', async () => {
21
+ const results = await axe($.html());
22
+ expect(results).toHaveNoViolations();
23
+ });
24
+
25
+ test('THEN: it renders the subtitle', () => {
26
+ expect($('.ons-chart__subtitle').text()).toBe('A sample subtitle');
27
+ });
28
+
29
+ test('THEN: it renders the chart container with the correct data attributes', () => {
30
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-type')).toBe('line');
31
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-title')).toBe('Example Line Chart');
32
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-id')).toBe('chart-123');
33
+ });
34
+
35
+ test('THEN: it includes the Highcharts JSON config', () => {
36
+ const configScript = $(`script[data-highcharts-config--chart-123]`).html();
37
+ expect(configScript).toContain('"text":"X Axis Title"');
38
+ expect(configScript).toContain('"text":"Y Axis Title"');
39
+ });
40
+
41
+ test('THEN: it does NOT render optional fields', () => {
42
+ expect($('figcaption').length).toBe(0);
43
+ expect($('.ons-chart__download-title').length).toBe(0);
44
+ });
45
+ });
46
+ });
47
+
48
+ describe('GIVEN: Params: Theme', () => {
49
+ describe('WHEN: theme is provided', () => {
50
+ const $ = cheerio.load(
51
+ renderComponent('chart', {
52
+ ...EXAMPLE_LINE_CHART_REQUIRED_PARAMS,
53
+ theme: 'primary',
54
+ }),
55
+ );
56
+
57
+ test('THEN: it renders the chart container with the correct theme', () => {
58
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-theme')).toBe('primary');
59
+ });
60
+ });
61
+ });
62
+
63
+ describe('GIVEN: Params: Heading Level', () => {
64
+ describe('WHEN: heading level is provided', () => {
65
+ const $ = cheerio.load(
66
+ renderComponent('chart', {
67
+ ...EXAMPLE_LINE_CHART_REQUIRED_PARAMS,
68
+ headingLevel: 3,
69
+ download: {
70
+ title: 'Download Chart Data',
71
+ itemsList: [
72
+ { text: 'Download as PNG', url: 'https://example.com/chart.png' },
73
+ { text: 'Download as CSV', url: 'https://example.com/chart.csv' },
74
+ ],
75
+ },
76
+ }),
77
+ );
78
+
79
+ test('THEN: renders title with correct tag', () => {
80
+ expect($('.ons-chart__title')[0].tagName).toBe('h3');
81
+ });
82
+
83
+ test('THEN: renders subtitle with correct tag', () => {
84
+ expect($('.ons-chart__subtitle')[0].tagName).toBe('h4');
85
+ });
86
+
87
+ test('THEN: renders download title with correct tag', () => {
88
+ expect($('.ons-chart__download-title')[0].tagName).toBe('h5');
89
+ });
90
+ });
91
+ });
92
+
93
+ describe('GIVEN: Params: Config', () => {
94
+ describe('WHEN: config params are provided', () => {
95
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_LINE_CHART_WITH_CONFIG_PARAMS));
96
+ const configScript = $(`script[data-highcharts-config--chart-456]`).html();
97
+
98
+ test('THEN: it renders the legend when enabled', () => {
99
+ expect(configScript).toContain('"enabled":true');
100
+ });
101
+
102
+ test('THEN: it includes correct xAxis properties', () => {
103
+ expect(configScript).toContain('"text":"X Axis Label"');
104
+ expect(configScript).toContain('"categories":["A","B","C"]');
105
+ expect(configScript).toContain('"type":"linear"');
106
+ });
107
+
108
+ test('THEN: it includes correct yAxis properties', () => {
109
+ expect(configScript).toContain('"text":"Y Axis Label"');
110
+ expect(configScript).toContain('"labels":{"format":"{value:,.f}"}');
111
+ });
112
+
113
+ test('THEN: it includes correct series data', () => {
114
+ expect(configScript).toContain('"name":"Category 1"');
115
+ expect(configScript).toContain('"data":[5,15,25]');
116
+ expect(configScript).toContain('"name":"Category 2"');
117
+ expect(configScript).toContain('"data":[10,20,30]');
118
+ });
119
+ });
120
+ });
121
+
122
+ describe('GIVEN: Params: Caption', () => {
123
+ describe('WHEN: caption is provided', () => {
124
+ const $ = cheerio.load(
125
+ renderComponent('chart', {
126
+ ...EXAMPLE_LINE_CHART_REQUIRED_PARAMS,
127
+ caption: 'This is an example caption for the chart.',
128
+ }),
129
+ );
130
+
131
+ test('THEN: it renders the caption when provided', () => {
132
+ expect($('figcaption').text()).toBe('This is an example caption for the chart.');
133
+ });
134
+ });
135
+ });
136
+
137
+ describe('GIVEN: Params: Description', () => {
138
+ describe('WHEN: description is provided', () => {
139
+ const $ = cheerio.load(
140
+ renderComponent('chart', {
141
+ ...EXAMPLE_LINE_CHART_REQUIRED_PARAMS,
142
+ description: 'An accessible description for screen readers.',
143
+ }),
144
+ );
145
+
146
+ test('THEN: it renders the description for accessibility', () => {
147
+ expect($('.ons-u-vh').text()).toBe('An accessible description for screen readers.');
148
+ });
149
+ });
150
+ });
151
+
152
+ describe('GIVEN: Params: Download', () => {
153
+ describe('WHEN: download object are provided', () => {
154
+ const $ = cheerio.load(
155
+ renderComponent('chart', {
156
+ ...EXAMPLE_LINE_CHART_REQUIRED_PARAMS,
157
+ download: {
158
+ title: 'Download Chart Data',
159
+ itemsList: [
160
+ { text: 'Download as PNG', url: 'https://example.com/chart.png' },
161
+ { text: 'Download as CSV', url: 'https://example.com/chart.csv' },
162
+ ],
163
+ },
164
+ }),
165
+ );
166
+
167
+ test('THEN: it renders the download section correctly', () => {
168
+ expect($('.ons-chart__download-title').text()).toBe('Download Chart Data');
169
+
170
+ const downloadLinks = $('.ons-chart__download-title').next().find('li a');
171
+ expect(downloadLinks.eq(0).text()).toBe('Download as PNG');
172
+ expect(downloadLinks.eq(0).attr('href')).toBe('https://example.com/chart.png');
173
+ expect(downloadLinks.eq(1).text()).toBe('Download as CSV');
174
+ expect(downloadLinks.eq(1).attr('href')).toBe('https://example.com/chart.csv');
175
+ });
176
+ });
177
+ });
178
+ });
179
+
180
+ describe('FOR: Bar Chart', () => {
181
+ describe('GIVEN: Params: required', () => {
182
+ describe('WHEN: required params are provided', () => {
183
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_BAR_CHART_PARAMS));
184
+
185
+ test('THEN: it passes jest-axe checks', async () => {
186
+ const results = await axe($.html());
187
+ expect(results).toHaveNoViolations();
188
+ });
189
+
190
+ test('THEN: it renders the chart container with the correct data attributes', () => {
191
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-type')).toBe('bar');
192
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-theme')).toBe('alternate');
193
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-title')).toBe('Example Bar Chart');
194
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-id')).toBe('bar-chart-123');
195
+ });
196
+
197
+ test('THEN: it does NOT render optional fields', () => {
198
+ expect($('figcaption').length).toBe(0);
199
+ expect($('.ons-chart__download-title').length).toBe(0);
200
+ });
201
+
202
+ test('THEN: it includes the Highcharts JSON config', () => {
203
+ const configScript = $(`script[data-highcharts-config--bar-chart-123]`).html();
204
+ expect(configScript).toContain('"text":"X Axis Title"');
205
+ expect(configScript).toContain('"text":"Y Axis Title"');
206
+ });
207
+ });
208
+ });
209
+
210
+ describe('GIVEN: Params: Config', () => {
211
+ describe('WHEN: config params are provided', () => {
212
+ const $ = cheerio.load(
213
+ renderComponent('chart', {
214
+ ...EXAMPLE_BAR_CHART_PARAMS,
215
+ legend: true,
216
+ }),
217
+ );
218
+
219
+ test('THEN: it renders the legend when enabled', () => {
220
+ const configScript = $(`script[data-highcharts-config--bar-chart-123]`).html();
221
+ expect(configScript).toContain('"enabled":true');
222
+ });
223
+
224
+ test('THEN: it includes correct xAxis and yAxis titles', () => {
225
+ const configScript = $(`script[data-highcharts-config--bar-chart-123]`).html();
226
+ expect(configScript).toContain('"text":"X Axis Title"');
227
+ expect(configScript).toContain('"text":"Y Axis Title"');
228
+ });
229
+ });
230
+ });
231
+
232
+ describe('GIVEN: Params: Caption', () => {
233
+ describe('WHEN: caption is provided', () => {
234
+ const $ = cheerio.load(
235
+ renderComponent('chart', {
236
+ ...EXAMPLE_BAR_CHART_PARAMS,
237
+ caption: 'This is an example caption for the chart.',
238
+ }),
239
+ );
240
+
241
+ test('THEN: it renders the caption when provided', () => {
242
+ expect($('figcaption').text()).toBe('This is an example caption for the chart.');
243
+ });
244
+ });
245
+ });
246
+
247
+ describe('GIVEN: Params: Description', () => {
248
+ describe('WHEN: description is provided', () => {
249
+ const $ = cheerio.load(
250
+ renderComponent('chart', {
251
+ ...EXAMPLE_BAR_CHART_PARAMS,
252
+ description: 'An accessible description for screen readers.',
253
+ }),
254
+ );
255
+
256
+ test('THEN: it renders the description for accessibility', () => {
257
+ expect($('.ons-u-vh').text()).toBe('An accessible description for screen readers.');
258
+ });
259
+ });
260
+ });
261
+
262
+ describe('GIVEN: Params: Download', () => {
263
+ describe('WHEN: download object are provided', () => {
264
+ const $ = cheerio.load(
265
+ renderComponent('chart', {
266
+ ...EXAMPLE_BAR_CHART_PARAMS,
267
+ download: {
268
+ title: 'Download Chart Data',
269
+ itemsList: [
270
+ { text: 'Download as PNG', url: 'https://example.com/chart.png' },
271
+ { text: 'Download as CSV', url: 'https://example.com/chart.csv' },
272
+ ],
273
+ },
274
+ }),
275
+ );
276
+
277
+ test('THEN: it renders the download section correctly', () => {
278
+ expect($('.ons-chart__download-title').text()).toBe('Download Chart Data');
279
+
280
+ const downloadLinks = $('.ons-chart__download-title').next().find('li a');
281
+ expect(downloadLinks.eq(0).text()).toBe('Download as PNG');
282
+ expect(downloadLinks.eq(0).attr('href')).toBe('https://example.com/chart.png');
283
+ expect(downloadLinks.eq(1).text()).toBe('Download as CSV');
284
+ expect(downloadLinks.eq(1).attr('href')).toBe('https://example.com/chart.csv');
285
+ });
286
+ });
287
+ });
288
+
289
+ describe('GIVEN: Stacked Bar Chart', () => {
290
+ describe('WHEN: Stacked layout is enabled', () => {
291
+ const $ = cheerio.load(
292
+ renderComponent('chart', {
293
+ ...EXAMPLE_BAR_CHART_PARAMS,
294
+ useStackedLayout: true,
295
+ }),
296
+ );
297
+
298
+ test('THEN: it includes stacking in the config', () => {
299
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-use-stacked-layout')).toBe('true');
300
+ });
301
+
302
+ test('THEN: it renders a bar chart with stacked series', () => {
303
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-type')).toBe('bar');
304
+ });
305
+ });
306
+ });
307
+ });
308
+
309
+ describe('FOR: Column Chart', () => {
310
+ describe('GIVEN: Params: required', () => {
311
+ describe('WHEN: required params are provided', () => {
312
+ const $ = cheerio.load(renderComponent('chart', EXAMPLE_COLUMN_CHART_PARAMS));
313
+
314
+ test('THEN: it passes jest-axe checks', async () => {
315
+ const results = await axe($.html());
316
+ expect(results).toHaveNoViolations();
317
+ });
318
+
319
+ test('THEN: it renders the chart container with the correct data attributes', () => {
320
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-type')).toBe('column');
321
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-theme')).toBe('alternate');
322
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-title')).toBe('Example Column Chart');
323
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-id')).toBe('column-chart-123');
324
+ });
325
+
326
+ test('THEN: it does NOT render optional fields', () => {
327
+ expect($('figcaption').length).toBe(0);
328
+ expect($('.ons-chart__download-title').length).toBe(0);
329
+ });
330
+
331
+ test('THEN: it includes the Highcharts JSON config', () => {
332
+ const configScript = $(`script[data-highcharts-config--column-chart-123]`).html();
333
+ expect(configScript).toContain('"text":"X Axis Title"');
334
+ expect(configScript).toContain('"text":"Y Axis Title"');
335
+ });
336
+ });
337
+ });
338
+
339
+ describe('GIVEN: Params: Config', () => {
340
+ describe('WHEN: config params are provided', () => {
341
+ const $ = cheerio.load(
342
+ renderComponent('chart', {
343
+ ...EXAMPLE_COLUMN_CHART_PARAMS,
344
+ legend: true,
345
+ }),
346
+ );
347
+
348
+ test('THEN: it renders the legend when enabled', () => {
349
+ const configScript = $(`script[data-highcharts-config--column-chart-123]`).html();
350
+ expect(configScript).toContain('"enabled":true');
351
+ });
352
+
353
+ test('THEN: it includes correct xAxis and yAxis titles', () => {
354
+ const configScript = $(`script[data-highcharts-config--column-chart-123]`).html();
355
+ expect(configScript).toContain('"text":"X Axis Title"');
356
+ expect(configScript).toContain('"text":"Y Axis Title"');
357
+ });
358
+ });
359
+ });
360
+
361
+ describe('GIVEN: Params: Caption', () => {
362
+ describe('WHEN: caption is provided', () => {
363
+ const $ = cheerio.load(
364
+ renderComponent('chart', {
365
+ ...EXAMPLE_COLUMN_CHART_PARAMS,
366
+ caption: 'This is an example caption for the chart.',
367
+ }),
368
+ );
369
+
370
+ test('THEN: it renders the caption when provided', () => {
371
+ expect($('figcaption').text()).toBe('This is an example caption for the chart.');
372
+ });
373
+ });
374
+ });
375
+
376
+ describe('GIVEN: Params: Description', () => {
377
+ describe('WHEN: description is provided', () => {
378
+ const $ = cheerio.load(
379
+ renderComponent('chart', {
380
+ ...EXAMPLE_COLUMN_CHART_PARAMS,
381
+ description: 'An accessible description for screen readers.',
382
+ }),
383
+ );
384
+
385
+ test('THEN: it renders the description for accessibility', () => {
386
+ expect($('.ons-u-vh').text()).toBe('An accessible description for screen readers.');
387
+ });
388
+ });
389
+ });
390
+
391
+ describe('GIVEN: Params: Download', () => {
392
+ describe('WHEN: download object are provided', () => {
393
+ const $ = cheerio.load(
394
+ renderComponent('chart', {
395
+ ...EXAMPLE_COLUMN_CHART_PARAMS,
396
+ download: {
397
+ title: 'Download Chart Data',
398
+ itemsList: [
399
+ { text: 'Download as PNG', url: 'https://example.com/chart.png' },
400
+ { text: 'Download as CSV', url: 'https://example.com/chart.csv' },
401
+ ],
402
+ },
403
+ }),
404
+ );
405
+
406
+ test('THEN: it renders the download section correctly', () => {
407
+ expect($('.ons-chart__download-title').text()).toBe('Download Chart Data');
408
+
409
+ const downloadLinks = $('.ons-chart__download-title').next().find('li a');
410
+ expect(downloadLinks.eq(0).text()).toBe('Download as PNG');
411
+ expect(downloadLinks.eq(0).attr('href')).toBe('https://example.com/chart.png');
412
+ expect(downloadLinks.eq(1).text()).toBe('Download as CSV');
413
+ expect(downloadLinks.eq(1).attr('href')).toBe('https://example.com/chart.csv');
414
+ });
415
+ });
416
+ });
417
+
418
+ describe('GIVEN: Stacked Column Chart', () => {
419
+ describe('WHEN: Stacked layout is enabled', () => {
420
+ const $ = cheerio.load(
421
+ renderComponent('chart', {
422
+ ...EXAMPLE_COLUMN_CHART_PARAMS,
423
+ useStackedLayout: true,
424
+ }),
425
+ );
426
+
427
+ test('THEN: it includes stacking in the config', () => {
428
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-use-stacked-layout')).toBe('true');
429
+ });
430
+
431
+ test('THEN: it renders a column chart with stacked series', () => {
432
+ expect($('[data-highcharts-base-chart]').attr('data-highcharts-type')).toBe('column');
433
+ });
434
+ });
435
+ });
436
+ });
437
+ });
@@ -0,0 +1,160 @@
1
+ import ChartConstants from './chart-constants';
2
+
3
+ class BarChart {
4
+ constructor() {
5
+ this.constants = ChartConstants.constants();
6
+ }
7
+
8
+ getBarChartOptions = (useStackedLayout) => {
9
+ return {
10
+ plotOptions: {
11
+ bar: {
12
+ // Set the width of the bars to be 30px
13
+ // The spacing is worked out in the specific-chart-options.js file
14
+ pointWidth: 30, // Fixed bar height
15
+ pointPadding: 0,
16
+ groupPadding: 0,
17
+ borderWidth: 0,
18
+ borderRadius: 0,
19
+ // Set the data labels to be enabled and positioned outside the bars
20
+ // We can add custom formatting on each chart to move the labels inside the bars if the bar is wide enough
21
+ dataLabels: {
22
+ enabled: true,
23
+ inside: false,
24
+ style: {
25
+ textOutline: 'none',
26
+ // The design system does not include a semibold font weight, so we use 700 (bold) as an alternative.
27
+ fontWeight: '700',
28
+ color: this.constants.labelColor,
29
+ fontSize: this.constants.mobileFontSize,
30
+ },
31
+ },
32
+ },
33
+ series: {
34
+ stacking: useStackedLayout ? 'normal' : null,
35
+ },
36
+ },
37
+ xAxis: {
38
+ // Update the category label colours for bar charts
39
+ labels: {
40
+ style: {
41
+ color: this.constants.categoryLabelColor,
42
+ },
43
+ },
44
+ // remove the tick marks for bar charts
45
+ tickWidth: 0,
46
+ tickLength: 0,
47
+ tickColor: 'transparent',
48
+ title: { align: 'high', textAlign: 'middle', reserveSpace: false, rotation: 0, y: -25, useHTML: true },
49
+ },
50
+ yAxis: {
51
+ title: {
52
+ // Override the y Axis title settings for bar charts where the y axis is horizontal
53
+ textAlign: 'right',
54
+ offset: undefined,
55
+ y: 0,
56
+ reserveSpace: true,
57
+ useHTML: false,
58
+ },
59
+ },
60
+ };
61
+ };
62
+
63
+ // Updates the config to move the data labels inside the bars, but only if the bar is wide enough
64
+ // This may also need to run when the chart is resized
65
+ postLoadDataLabels = (currentChart) => {
66
+ const insideOptions = {
67
+ dataLabels: this.getBarChartLabelsInsideOptions(),
68
+ };
69
+ const outsideOptions = {
70
+ dataLabels: this.getBarChartLabelsOutsideOptions(),
71
+ };
72
+
73
+ currentChart.series.forEach((series) => {
74
+ const points = series.data;
75
+ points.forEach((point) => {
76
+ // Get the actual width of the data label
77
+ const labelWidth = point.dataLabel && point.dataLabel.getBBox().width;
78
+ // Move the data labels inside the bar if the bar is wider than the label plus some padding
79
+ if (point.shapeArgs.height > labelWidth + 20) {
80
+ point.update(insideOptions, false);
81
+ } else {
82
+ point.update(outsideOptions, false);
83
+ }
84
+ });
85
+ });
86
+
87
+ currentChart.redraw();
88
+ };
89
+
90
+ getBarChartLabelsInsideOptions = () => ({
91
+ inside: true,
92
+ align: 'right',
93
+ verticalAlign: 'middle',
94
+ style: {
95
+ color: 'white',
96
+ fontWeight: 'bold',
97
+ },
98
+ });
99
+
100
+ getBarChartLabelsOutsideOptions = () => ({
101
+ inside: false,
102
+ align: undefined,
103
+ verticalAlign: undefined,
104
+ style: {
105
+ textOutline: 'none',
106
+ // The design system does not include a semibold font weight, so we use 700 (bold) as an alternative.
107
+ fontWeight: '700',
108
+ color: this.constants.labelColor,
109
+ fontSize: this.constants.mobileFontSize,
110
+ },
111
+ });
112
+
113
+ // This updates the height of the vertical axis and overall chart to fit the number of categories
114
+ // Note that the vertical axis on a bar chart is the x axis
115
+ updateBarChartHeight = (config, currentChart, useStackedLayout) => {
116
+ const numberOfCategories = config.xAxis.categories.length;
117
+ const numberOfSeries = currentChart.series.length; // Get number of bar series
118
+ let barHeight = 30; // Height of each individual bar - set in bar-chart-plot-options
119
+ let groupSpacing = 0; // Space we want between category groups, or between series groups for cluster charts
120
+ let categoriesTotalHeight = 0;
121
+ let totalSpaceHeight = 0;
122
+ if (useStackedLayout == false && numberOfSeries > 1) {
123
+ // slightly lower bar height for cluster charts
124
+ barHeight = 28;
125
+ // for cluster charts there is no space between the bars within a series, and 14px between each series
126
+ groupSpacing = 14;
127
+ // lower barHeight for series with 3 categories or more
128
+ if (numberOfSeries >= 3) {
129
+ barHeight = 20;
130
+ }
131
+ categoriesTotalHeight = numberOfCategories * barHeight * numberOfSeries;
132
+
133
+ totalSpaceHeight = numberOfCategories * groupSpacing;
134
+ // work out the group padding for cluster charts which is measured in xAxis units.
135
+ const plotHeight = categoriesTotalHeight + totalSpaceHeight;
136
+ const xUnitHeight = plotHeight / numberOfCategories;
137
+ const groupPadding = groupSpacing / 2 / xUnitHeight;
138
+ currentChart.series.forEach((series) => {
139
+ series.update({
140
+ groupPadding: groupPadding,
141
+ pointWidth: barHeight,
142
+ });
143
+ });
144
+ } else {
145
+ groupSpacing = 10;
146
+ categoriesTotalHeight = numberOfCategories * barHeight;
147
+ totalSpaceHeight = (numberOfCategories - 1) * groupSpacing;
148
+ }
149
+
150
+ config.xAxis.height = categoriesTotalHeight + totalSpaceHeight;
151
+ const totalHeight = currentChart.plotTop + config.xAxis.height + currentChart.marginBottom;
152
+ if (totalHeight !== currentChart.chartHeight) {
153
+ currentChart.setSize(null, totalHeight, false);
154
+ }
155
+
156
+ currentChart.redraw();
157
+ };
158
+ }
159
+
160
+ export default BarChart;
@@ -0,0 +1,21 @@
1
+ class ChartConstants {
2
+ static constants() {
3
+ const constants = {
4
+ primaryTheme: ['#206095', '#27a0cc', '#003c57', '#118c7b', '#a8bd3a', '#871a5b', '#f66068', '#746cb1', '#22d0b6'],
5
+ // Alternate theme colours from https://service-manual.ons.gov.uk/data-visualisation/colours/using-colours-in-charts
6
+ alternateTheme: ['#206095', '#27A0CC', '#871A5B', '#A8BD3A', '#F66068'],
7
+ labelColor: '#414042',
8
+ axisLabelColor: '#707071',
9
+ categoryLabelColor: '#414042',
10
+ gridLineColor: '#d9d9d9',
11
+ zeroLineColor: '#b3b3b3',
12
+ // Responsive font sizes
13
+ mobileFontSize: '0.75rem', // 12px
14
+ desktopFontSize: '0.875rem', // 14px
15
+ };
16
+
17
+ return constants;
18
+ }
19
+ }
20
+
21
+ export default ChartConstants;
@@ -0,0 +1,8 @@
1
+ import HighchartsBaseChart from './chart';
2
+ import domready from '../../js/domready';
3
+
4
+ domready(async () => {
5
+ [HighchartsBaseChart].forEach((Component) => {
6
+ document.querySelectorAll(Component.selector()).forEach((el) => new Component(el));
7
+ });
8
+ });