@ons/design-system 72.9.2 → 72.10.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 (33) hide show
  1. package/components/chart/_chart.scss +74 -1
  2. package/components/chart/_macro.njk +64 -5
  3. package/components/chart/_macro.spec.js +405 -0
  4. package/components/chart/bar-chart.js +2 -2
  5. package/components/chart/boxplot.js +37 -0
  6. package/components/chart/chart-constants.js +13 -0
  7. package/components/chart/chart.js +86 -46
  8. package/components/chart/columnrange-chart.js +94 -0
  9. package/components/chart/common-chart-options.js +28 -15
  10. package/components/chart/example-bar-chart-with-annotations.njk +1 -1
  11. package/components/chart/example-bar-chart-with-point-range-and-reference-line-annotations.njk +95 -0
  12. package/components/chart/example-bar-with-confidence-levels.njk +71 -0
  13. package/components/chart/example-clustered-column-chart.njk +1 -3
  14. package/components/chart/example-column-chart-with-annotations.njk +1 -1
  15. package/components/chart/example-column-chart-with-range-annotations.njk +64 -0
  16. package/components/chart/example-column-chart-with-reference-line-annotations.njk +64 -0
  17. package/components/chart/example-column-with-confidence-levels.njk +61 -0
  18. package/components/chart/example-line-chart-with-annotations.njk +3 -3
  19. package/components/chart/example-line-chart-with-markers.njk +1 -1
  20. package/components/chart/example-line-chart-with-range-annotations-inside.njk +238 -0
  21. package/components/chart/example-line-chart-with-range-annotations-outside-left-right.njk +240 -0
  22. package/components/chart/example-line-chart-with-range-annotations-outside-top-bottom.njk +239 -0
  23. package/components/chart/example-line-chart-with-reference-line-annotations.njk +236 -0
  24. package/components/chart/example-scatter-chart.njk +1 -1
  25. package/components/chart/range-annotations-options.js +216 -0
  26. package/components/chart/reference-line-annotations-options.js +93 -0
  27. package/components/chart/scatter-chart.js +15 -0
  28. package/components/chart/specific-chart-options.js +1 -1
  29. package/components/chart/utilities.js +96 -0
  30. package/css/main.css +1 -1
  31. package/package.json +1 -1
  32. package/scripts/main.es5.js +1 -1
  33. package/scripts/main.js +1 -1
@@ -0,0 +1,37 @@
1
+ import ChartConstants from './chart-constants';
2
+ import ColumnChart from './column-chart';
3
+
4
+ class Boxplot {
5
+ constructor() {
6
+ this.constants = ChartConstants.constants();
7
+ this.columnChart = new ColumnChart();
8
+ }
9
+
10
+ getBoxplotOptions = (config, useStackedLayout, extraLines) => {
11
+ return {
12
+ plotOptions: {
13
+ boxplot: {
14
+ ...this.columnChart.getPointPadding(config, useStackedLayout, extraLines, false),
15
+ boxDashStyle: 'Solid',
16
+ fillColor: this.constants.uncertaintyRangeColor,
17
+ lineWidth: 0,
18
+ medianColor: this.constants.estimateLineColor,
19
+ medianDashStyle: 'Solid',
20
+ medianWidth: 3,
21
+ stemWidth: 0,
22
+ whiskerWidth: 0,
23
+ },
24
+ },
25
+ legend: {
26
+ enabled: false, // Legend is handled via custom HTML for box plots, so we disable the default Highcharts legend
27
+ symbolRadius: 0,
28
+ itemStyle: {
29
+ fontSize: this.constants.defaultFontSize,
30
+ color: this.constants.legendLabelColor,
31
+ },
32
+ },
33
+ };
34
+ };
35
+ }
36
+
37
+ export default Boxplot;
@@ -9,7 +9,10 @@ class ChartConstants {
9
9
  categoryLabelColor: '#414042',
10
10
  gridLineColor: '#d9d9d9',
11
11
  zeroLineColor: '#b3b3b3',
12
+ estimateLineColor: '#003c57',
13
+ uncertaintyRangeColor: 'rgba(32, 96, 149, 0.65)',
12
14
  defaultFontSize: '0.875rem', // 14px
15
+ shadingColor: '#ececec',
13
16
  lineMarkerStyles: [
14
17
  {
15
18
  radius: 4,
@@ -93,6 +96,16 @@ class ChartConstants {
93
96
  lineColor: '#ffffff',
94
97
  },
95
98
  ],
99
+ confidenceLevelMarkerStyles: [
100
+ {
101
+ radius: 6,
102
+ symbol: 'diamond',
103
+ lineWidth: 2,
104
+ lineColor: '#222',
105
+ outline: true,
106
+ fillColor: 'white',
107
+ },
108
+ ],
96
109
  };
97
110
 
98
111
  return constants;
@@ -1,6 +1,7 @@
1
1
  import Highcharts from 'highcharts';
2
2
  import 'highcharts/modules/accessibility';
3
3
  import 'highcharts/modules/annotations';
4
+ import 'highcharts/highcharts-more';
4
5
 
5
6
  import CommonChartOptions from './common-chart-options';
6
7
  import SpecificChartOptions from './specific-chart-options';
@@ -8,8 +9,13 @@ import LineChart from './line-chart';
8
9
  import BarChart from './bar-chart';
9
10
  import ColumnChart from './column-chart';
10
11
  import ScatterChart from './scatter-chart';
12
+ import Boxplot from './boxplot';
11
13
  import AnnotationsOptions from './annotations-options';
14
+ import RangeAnnotationsOptions from './range-annotations-options';
15
+ import ReferenceLineAnnotationsOptions from './reference-line-annotations-options';
16
+ import { preparePlotLinesAndBands, mergeConfigs } from './utilities';
12
17
  import AreaChart from './area-chart';
18
+ import ColumnRangeChart from './columnrange-chart';
13
19
 
14
20
  class HighchartsBaseChart {
15
21
  static selector() {
@@ -29,8 +35,18 @@ class HighchartsBaseChart {
29
35
  this.useStackedLayout = this.node.hasAttribute('data-highcharts-use-stacked-layout');
30
36
  this.config = JSON.parse(this.node.querySelector(`[data-highcharts-config--${this.id}]`).textContent);
31
37
  if (this.node.querySelector(`[data-highcharts-annotations--${this.id}]`)) {
32
- const annotations = JSON.parse(this.node.querySelector(`[data-highcharts-annotations--${this.id}]`).textContent);
33
- this.annotationsOptions = new AnnotationsOptions(annotations);
38
+ this.annotations = JSON.parse(this.node.querySelector(`[data-highcharts-annotations--${this.id}]`).textContent);
39
+ this.annotationsOptions = new AnnotationsOptions(this.annotations);
40
+ }
41
+ if (this.node.querySelector(`[data-highcharts-range-annotations--${this.id}]`)) {
42
+ this.rangeAnnotations = JSON.parse(this.node.querySelector(`[data-highcharts-range-annotations--${this.id}]`).textContent);
43
+ this.rangeAnnotationsOptions = new RangeAnnotationsOptions(this.rangeAnnotations);
44
+ }
45
+ if (this.node.querySelector(`[data-highcharts-reference-line-annotations--${this.id}]`)) {
46
+ this.referenceLineAnnotations = JSON.parse(
47
+ this.node.querySelector(`[data-highcharts-reference-line-annotations--${this.id}]`).textContent,
48
+ );
49
+ this.referenceLineAnnotationsOptions = new ReferenceLineAnnotationsOptions(this.referenceLineAnnotations);
34
50
  }
35
51
  this.percentageHeightDesktop = this.node.dataset.highchartsPercentageHeightDesktop;
36
52
  this.percentageHeightMobile = this.node.dataset.highchartsPercentageHeightMobile;
@@ -47,13 +63,18 @@ class HighchartsBaseChart {
47
63
  ? parseInt(this.node.dataset.highchartsYAxisTickIntervalDesktop)
48
64
  : undefined;
49
65
  this.commonChartOptions = new CommonChartOptions(this.xAxisTickIntervalDesktop, this.yAxisTickIntervalDesktop);
66
+ this.estimateLineLabel = this.node.dataset.highchartsEstimateLineLabel;
67
+ this.uncertainyRangeLabel = this.node.dataset.highchartsUncertaintyRangeLabel;
50
68
  this.specificChartOptions = new SpecificChartOptions(this.theme, this.chartType, this.config);
51
69
  this.lineChart = new LineChart();
52
70
  this.barChart = new BarChart();
53
71
  this.columnChart = new ColumnChart();
54
72
  this.areaChart = new AreaChart();
55
73
  this.scatterChart = new ScatterChart();
74
+ this.columnRangeChart = new ColumnRangeChart();
75
+ this.boxplot = new Boxplot();
56
76
  this.extraLines = this.checkForExtraLines();
77
+ this.extraScatter = this.checkForExtraScatter();
57
78
  if (window.isCommonChartOptionsDefined === undefined) {
58
79
  this.setCommonChartOptions();
59
80
  window.isCommonChartOptionsDefined = true;
@@ -62,6 +83,7 @@ class HighchartsBaseChart {
62
83
  this.setSpecificChartOptions();
63
84
  this.setResponsiveOptions();
64
85
  this.setLoadEvent();
86
+ this.setRenderEvent();
65
87
  this.setWindowResizeEvent();
66
88
  this.chart = Highcharts.chart(chartNode, this.config);
67
89
  }
@@ -71,83 +93,69 @@ class HighchartsBaseChart {
71
93
  return this.chartType === 'line' ? 0 : this.config.series.filter((series) => series.type === 'line').length;
72
94
  };
73
95
 
96
+ // Check for the number of extra line series in the config
97
+ checkForExtraScatter = () => {
98
+ return this.chartType === 'scatter' ? 0 : this.config.series.filter((series) => series.type === 'scatter').length;
99
+ };
100
+
74
101
  // Set up the global Highcharts options which are used for all charts
75
102
  setCommonChartOptions = () => {
76
103
  const chartOptions = this.commonChartOptions.getOptions();
77
104
  Highcharts.setOptions(chartOptions);
78
105
  };
79
106
 
80
- // Utility function to merge two configs together
81
- mergeConfigs = (baseConfig, newConfig) => {
82
- // If newConfig is null/undefined, return baseConfig
83
- if (!newConfig) return baseConfig;
84
-
85
- // Create a new object to store the merged result
86
- const merged = { ...baseConfig };
87
-
88
- // Iterate through all keys in newConfig
89
- Object.keys(newConfig).forEach((key) => {
90
- // Get values from both configs for this key
91
- const baseValue = merged[key];
92
- const newValue = newConfig[key];
93
-
94
- // If both values are objects (and not null), recursively merge them
95
- if (
96
- baseValue &&
97
- newValue &&
98
- typeof baseValue === 'object' &&
99
- typeof newValue === 'object' &&
100
- !Array.isArray(baseValue) &&
101
- !Array.isArray(newValue)
102
- ) {
103
- merged[key] = this.mergeConfigs(baseValue, newValue);
104
- } else {
105
- // For non-objects and arrays use the new value
106
- // If the new value is null/undefined, use the base value
107
- merged[key] = newValue ?? baseValue;
108
- }
109
- });
110
-
111
- return merged;
112
- };
113
-
114
107
  // Set up options for specific charts and chart types
115
108
  setSpecificChartOptions = () => {
116
109
  const specificChartOptions = this.specificChartOptions.getOptions();
117
110
  const lineChartOptions = this.lineChart.getLineChartOptions();
118
111
  const barChartOptions = this.barChart.getBarChartOptions(this.useStackedLayout);
112
+ const columnRangeChartOptions = this.columnRangeChart.getColumnRangeChartOptions();
119
113
  const columnChartOptions = this.columnChart.getColumnChartOptions(this.config, this.useStackedLayout, this.extraLines);
120
114
  const areaChartOptions = this.areaChart.getAreaChartOptions();
121
115
  const scatterChartOptions = this.scatterChart.getScatterChartOptions();
116
+ const boxplotOptions = this.boxplot.getBoxplotOptions(this.config, this.useStackedLayout, this.extraLines);
122
117
  // Merge specificChartOptions with the existing config
123
- this.config = this.mergeConfigs(this.config, specificChartOptions);
118
+ this.config = mergeConfigs(this.config, specificChartOptions);
124
119
 
125
120
  if (this.chartType === 'line') {
126
121
  // Merge the line chart options with the existing config
127
- this.config = this.mergeConfigs(this.config, lineChartOptions);
122
+ this.config = mergeConfigs(this.config, lineChartOptions);
128
123
  }
129
124
 
130
125
  if (this.chartType === 'bar') {
131
126
  // Merge the bar chart options with the existing config
132
- this.config = this.mergeConfigs(this.config, barChartOptions);
127
+ this.config = mergeConfigs(this.config, barChartOptions);
128
+ }
129
+ if (this.chartType === 'columnrange') {
130
+ // Merge the bar chart options with the existing config
131
+ this.config = mergeConfigs(this.config, columnRangeChartOptions);
133
132
  }
134
133
  if (this.chartType === 'column') {
135
134
  // Merge the column chart options with the existing config
136
- this.config = this.mergeConfigs(this.config, columnChartOptions);
135
+ this.config = mergeConfigs(this.config, columnChartOptions);
137
136
  }
138
137
  if (this.chartType === 'area') {
139
138
  // Merge the area chart options with the existing config
140
- this.config = this.mergeConfigs(this.config, areaChartOptions);
139
+ this.config = mergeConfigs(this.config, areaChartOptions);
141
140
  }
142
141
  if (this.chartType === 'scatter') {
143
142
  // Merge the scatter chart options with the existing config
144
- this.config = this.mergeConfigs(this.config, scatterChartOptions);
143
+ this.config = mergeConfigs(this.config, scatterChartOptions);
144
+ }
145
+ if (this.chartType === 'boxplot') {
146
+ // Merge the boxplot chart options with the existing config
147
+ this.config = mergeConfigs(this.config, boxplotOptions);
145
148
  }
146
149
 
147
150
  if (this.extraLines > 0) {
148
- this.config = this.mergeConfigs(this.config, this.lineChart.getLineChartOptions());
151
+ this.config = mergeConfigs(this.config, this.lineChart.getLineChartOptions());
149
152
  if (this.chartType === 'column') {
150
- this.config = this.mergeConfigs(this.config, columnChartOptions);
153
+ this.config = mergeConfigs(this.config, columnChartOptions);
154
+ }
155
+ }
156
+ if (this.extraScatter > 0) {
157
+ if (this.chartType === 'columnrange') {
158
+ this.config = mergeConfigs(this.config, columnRangeChartOptions);
151
159
  }
152
160
  }
153
161
 
@@ -166,19 +174,28 @@ class HighchartsBaseChart {
166
174
  // All responsive rules should be defined here to avoid overriding existing rules
167
175
  setResponsiveOptions = () => {
168
176
  let mobileChartOptions = this.commonChartOptions.getMobileOptions(this.xAxisTickIntervalMobile, this.yAxisTickIntervalMobile);
169
- if (this.chartType === 'column') {
177
+ if (this.chartType === 'column' || this.chartType === 'boxplot') {
170
178
  const mobileColumnChartOptions = this.columnChart.getColumnChartMobileOptions(
171
179
  this.config,
172
180
  this.useStackedLayout,
173
181
  this.extraLines,
174
182
  );
175
- mobileChartOptions = this.mergeConfigs(mobileChartOptions, mobileColumnChartOptions);
183
+ mobileChartOptions = mergeConfigs(mobileChartOptions, mobileColumnChartOptions);
176
184
  }
177
185
 
178
186
  if (!this.config.responsive) {
179
187
  this.config.responsive = {};
180
188
  }
181
189
 
190
+ const { desktopAllPlotLinesAndBands, mobileAllPlotLinesAndBands } = preparePlotLinesAndBands(
191
+ this.annotations,
192
+ this.rangeAnnotations,
193
+ this.rangeAnnotationsOptions,
194
+ this.referenceLineAnnotationsOptions,
195
+ this.commonChartOptions,
196
+ this.chartType,
197
+ );
198
+
182
199
  let rules = [
183
200
  {
184
201
  condition: {
@@ -198,6 +215,7 @@ class HighchartsBaseChart {
198
215
  },
199
216
  chartOptions: {
200
217
  annotations: this.annotationsOptions ? this.annotationsOptions.getAnnotationsOptionsMobile() : undefined,
218
+ ...(mobileAllPlotLinesAndBands != {} ? mobileAllPlotLinesAndBands : null),
201
219
  },
202
220
  },
203
221
  {
@@ -206,6 +224,7 @@ class HighchartsBaseChart {
206
224
  },
207
225
  chartOptions: {
208
226
  annotations: this.annotationsOptions ? this.annotationsOptions.getAnnotationsOptionsDesktop() : undefined,
227
+ ...(desktopAllPlotLinesAndBands != {} ? desktopAllPlotLinesAndBands : null),
209
228
  },
210
229
  },
211
230
  ];
@@ -238,6 +257,15 @@ class HighchartsBaseChart {
238
257
  if (this.chartType === 'scatter') {
239
258
  this.scatterChart.updateMarkers(currentChart);
240
259
  }
260
+ if (this.chartType === 'columnrange') {
261
+ this.columnRangeChart.updateColumnRangeChartHeight(this.config, currentChart);
262
+ this.commonChartOptions.hideDataLabels(currentChart.series);
263
+
264
+ if (this.extraScatter > 0) {
265
+ const scatterSeries = currentChart.series.filter((series) => series.type === 'scatter');
266
+ this.scatterChart.updateMarkersForConfidenceLevels(scatterSeries);
267
+ }
268
+ }
241
269
  if (this.chartType != 'bar') {
242
270
  this.commonChartOptions.adjustChartHeight(currentChart, this.percentageHeightDesktop, this.percentageHeightMobile);
243
271
  }
@@ -257,6 +285,18 @@ class HighchartsBaseChart {
257
285
  };
258
286
  };
259
287
 
288
+ setRenderEvent = () => {
289
+ if (!this.config.chart.events) {
290
+ this.config.chart.events = {};
291
+ }
292
+ this.config.chart.events.render = (event) => {
293
+ const currentChart = event.target;
294
+ if (this.rangeAnnotationsOptions) {
295
+ this.rangeAnnotationsOptions.addLine(currentChart);
296
+ }
297
+ };
298
+ };
299
+
260
300
  // Set resize events - throttled to 50ms
261
301
  // All resize events should be defined here to avoid overriding existing events
262
302
  setWindowResizeEvent = () => {
@@ -0,0 +1,94 @@
1
+ import ChartConstants from './chart-constants';
2
+
3
+ class ColumnRangeChart {
4
+ constructor() {
5
+ this.constants = ChartConstants.constants();
6
+ }
7
+
8
+ getColumnRangeChartOptions = () => {
9
+ return {
10
+ plotOptions: {
11
+ columnrange: {
12
+ color: this.constants.uncertaintyRangeColor, // Set the default color for the column range
13
+ pointWidth: 20, // Fixed bar height
14
+ pointPadding: 0,
15
+ groupPadding: 0,
16
+ borderWidth: 0,
17
+ borderRadius: 0,
18
+ dataLabels: {
19
+ enabled: false,
20
+ },
21
+ },
22
+ },
23
+ xAxis: {
24
+ // Update the category label colours for bar charts
25
+ labels: {
26
+ style: {
27
+ color: this.constants.categoryLabelColor,
28
+ },
29
+ useHTML: false,
30
+ },
31
+ // remove the tick marks for bar charts
32
+ tickWidth: 0,
33
+ tickLength: 0,
34
+ gridlineWidth: 0,
35
+ tickColor: 'transparent',
36
+ title: { align: 'high', textAlign: 'middle', reserveSpace: false, rotation: 0, y: -25, useHTML: true },
37
+ },
38
+ yAxis: {
39
+ labels: {
40
+ rotation: 0,
41
+ useHTML: true,
42
+ style: {
43
+ whiteSpace: 'nowrap',
44
+ color: this.constants.categoryLabelColor,
45
+ },
46
+ },
47
+ tickLength: 0,
48
+ tickWidth: 0,
49
+ tickColor: 'transparent',
50
+ gridlineWidth: 0,
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
+ legend: {
61
+ symbolRadius: 0,
62
+ itemStyle: {
63
+ fontSize: this.constants.defaultFontSize,
64
+ color: this.constants.legendLabelColor,
65
+ },
66
+ },
67
+ };
68
+ };
69
+
70
+ // This updates the height of the vertical axis and overall chart to fit the number of categories
71
+ // Note that the vertical axis on a bar chart is the x axis
72
+ updateColumnRangeChartHeight = (config, currentChart) => {
73
+ const numberOfCategories = config.xAxis.categories.length;
74
+ let barHeight = 20; // Height of each individual bar - set in bar-chart-plot-options
75
+ let groupSpacing = 0; // Space we want between category groups, or between series groups for cluster charts
76
+ let categoriesTotalHeight = 0;
77
+ let totalSpaceHeight = 0;
78
+
79
+ groupSpacing = 14;
80
+ categoriesTotalHeight = numberOfCategories * barHeight;
81
+
82
+ totalSpaceHeight = numberOfCategories * groupSpacing;
83
+
84
+ config.xAxis.height = categoriesTotalHeight + totalSpaceHeight;
85
+ const totalHeight = currentChart.plotTop + config.xAxis.height + currentChart.marginBottom;
86
+ if (totalHeight !== currentChart.chartHeight) {
87
+ currentChart.setSize(null, totalHeight, false);
88
+ }
89
+
90
+ currentChart.redraw();
91
+ };
92
+ }
93
+
94
+ export default ColumnRangeChart;
@@ -81,15 +81,6 @@ class CommonChartOptions {
81
81
  },
82
82
  lineColor: this.constants.gridLineColor,
83
83
  gridLineColor: this.constants.gridLineColor,
84
- // Add zero line
85
- plotLines: [
86
- {
87
- color: this.constants.zeroLineColor,
88
- width: 1.5,
89
- value: 0,
90
- zIndex: 2,
91
- },
92
- ],
93
84
  // Add tick marks
94
85
  tickWidth: 1,
95
86
  tickLength: 6,
@@ -143,6 +134,25 @@ class CommonChartOptions {
143
134
 
144
135
  getOptions = () => this.options;
145
136
 
137
+ // TODO: A future ticket will add support for other plot lines which are not
138
+ // reference line annotations, and will be styled like the zero line
139
+ // See ticket https://jira.ons.gov.uk/browse/CCB-63
140
+ getPlotLines = () => {
141
+ // Add zero line
142
+ return {
143
+ yAxis: {
144
+ plotLines: [
145
+ {
146
+ color: this.constants.zeroLineColor,
147
+ width: 1.5,
148
+ value: 0,
149
+ zIndex: 2,
150
+ },
151
+ ],
152
+ },
153
+ };
154
+ };
155
+
146
156
  getMobileOptions = (xAxisTickInterval, yAxisTickInterval) => {
147
157
  return {
148
158
  xAxis: {
@@ -165,7 +175,7 @@ class CommonChartOptions {
165
175
  };
166
176
 
167
177
  disableLegendForSingleSeries = (config) => {
168
- if (config.series.length === 1) {
178
+ if (config.chart.type != 'boxplot' && config.series.length === 1) {
169
179
  config.legend = {
170
180
  enabled: false,
171
181
  };
@@ -187,12 +197,15 @@ class CommonChartOptions {
187
197
  x: 30, // Adjust label position to account for longer line
188
198
  });
189
199
  } else {
200
+ if (!symbol) return;
190
201
  // Set the symbol size for bar / column series
191
- symbol.attr({
192
- width: 12,
193
- height: 12,
194
- y: 8,
195
- });
202
+ else {
203
+ symbol.attr({
204
+ width: 12,
205
+ height: 12,
206
+ y: 8,
207
+ });
208
+ }
196
209
  }
197
210
  });
198
211
  }
@@ -52,7 +52,7 @@
52
52
  ],
53
53
  "annotations": [
54
54
  {
55
- "text": "A test annotation",
55
+ "text": "A test point annotation",
56
56
  "point": {"x": 2, "y": 3},
57
57
  "labelOffsetX": 40,
58
58
  "labelOffsetY": -30
@@ -0,0 +1,95 @@
1
+ {% from "components/chart/_macro.njk" import onsChart %}
2
+
3
+ {{
4
+ onsChart({
5
+ "chartType": "bar",
6
+ "description": "Volume sales, seasonally adjusted, Great Britain, January 2022 to January 2025",
7
+ "theme": "primary",
8
+ "title": "Food stores showed a strong rise on the month, while non-food stores fell",
9
+ "subtitle": "Figure 6: Upward contribution from housing and household services (including energy) saw the annual CPIH inflation rate rise",
10
+ "id": "uuid",
11
+ "caption": "Source: Monthly Business Survey, Retail Sales Inquiry from the Office for National Statistics",
12
+ "download": {
13
+ 'title': 'Download Figure 1 data',
14
+ 'itemsList': [
15
+ {
16
+ "text": "Excel spreadsheet (XLSX format, 18KB)",
17
+ "url": "#"
18
+ },
19
+ {
20
+ "text": "Simple text file (CSV format, 25KB)",
21
+ "url": "#"
22
+ },
23
+ {
24
+ "text": "Image (PNG format, 25KB)",
25
+ "url": "#"
26
+ }
27
+ ]
28
+ },
29
+ "xAxis": {
30
+ "categories": [
31
+ "All retailing",
32
+ "All retailing excluding Automotive fuel",
33
+ "Food stores",
34
+ "Department stores",
35
+ "Other non-food stores",
36
+ "Textile clothing \u0026 footwear stores",
37
+ "Household goods stores",
38
+ "Non-store retailing",
39
+ "Automotive fuel"
40
+ ],
41
+ "type": "linear"
42
+ },
43
+ "yAxis": {
44
+ "title": "Percent (%)"
45
+ },
46
+ "series": [
47
+ {
48
+ "data": [1.7, 2.1, 5.6, 0, -0.6, -2.7, -1.7, 2.4, -1.2],
49
+ "dataLabels": false,
50
+ "name": "Jan-25"
51
+ }
52
+ ],
53
+ "annotations": [
54
+ {
55
+ "text": "A test point annotation",
56
+ "point": {"x": 2, "y": 3},
57
+ "labelOffsetX": 40,
58
+ "labelOffsetY": -30
59
+ }
60
+ ],
61
+ "rangeAnnotations": [
62
+ {
63
+ "text": "A test x axis range annotation",
64
+ "range": {"axisValue1": 1, "axisValue2": 2},
65
+ "axis": "x",
66
+ "labelOffsetX": 10,
67
+ "labelOffsetY": -60
68
+ },
69
+ {
70
+ "text": "A test y axis range annotation with a width of 80px",
71
+ "range": {"axisValue1": 5, "axisValue2": 6},
72
+ "axis": "y",
73
+ "labelOffsetX": -80,
74
+ "labelOffsetY": 220,
75
+ "labelWidth": 80
76
+ }
77
+ ],
78
+ "referenceLineAnnotations": [
79
+ {
80
+ "text": "A test x axis reference line annotation",
81
+ "value": 8,
82
+ "axis": "x",
83
+ "labelOffsetY": -30
84
+ },
85
+ {
86
+ "text": "A test y axis reference line annotation",
87
+ "value": 1.5,
88
+ "axis": "y",
89
+ "labelWidth": 100,
90
+ "labelOffsetX": 10,
91
+ "labelOffsetY": 140
92
+ }
93
+ ]
94
+ })
95
+ }}
@@ -0,0 +1,71 @@
1
+ {% from "components/chart/_macro.njk" import onsChart %}
2
+
3
+ {{
4
+ onsChart({
5
+ "chartType": "columnrange",
6
+ "isChartInverted": true,
7
+ "description": "Volume sales, seasonally adjusted, Great Britain, January 2022 to January 2025",
8
+ "theme": "primary",
9
+ "title": "Food stores showed a strong rise on the month, while non-food stores fell",
10
+ "subtitle": "Figure 6: Upward contribution from housing and household services (including energy) saw the annual CPIH inflation rate rise",
11
+ "id": "uuid",
12
+ "caption": "Source: Monthly Business Survey, Retail Sales Inquiry from the Office for National Statistics",
13
+ "download": {
14
+ 'title': 'Download Figure 1 data',
15
+ 'itemsList': [
16
+ {
17
+ "text": "Excel spreadsheet (XLSX format, 18KB)",
18
+ "url": "#"
19
+ },
20
+ {
21
+ "text": "Simple text file (CSV format, 25KB)",
22
+ "url": "#"
23
+ },
24
+ {
25
+ "text": "Image (PNG format, 25KB)",
26
+ "url": "#"
27
+ }
28
+ ]
29
+ },
30
+ "legend": true,
31
+ "xAxis": {
32
+ "categories": [
33
+ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
34
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
35
+ ],
36
+ "type": "linear",
37
+ "title": "Categories"
38
+ },
39
+ "yAxis": {
40
+ "title": "Temperature ( °C )"
41
+ },
42
+ "series": [
43
+ {
44
+ "data": [
45
+ [-9.5, 8.0],
46
+ [-7.8, 8.3],
47
+ [-13.1, 9.2],
48
+ [-4.4, 15.7],
49
+ [-1.0, 20.8],
50
+ [3.1, 28.4],
51
+ [5.9, 27.0],
52
+ [4.6, 23.0],
53
+ [4.9, 19.3],
54
+ [-5.2, 11.6],
55
+ [],
56
+ [-10.5, 12.0],
57
+ [-12.1, 18.5]
58
+ ],
59
+ "dataLabels": true,
60
+ "name": "Values"
61
+ },
62
+ {
63
+ "data": [1,2,3,4,5,6,7,8,9,10,3,11,12],
64
+ "marker": true,
65
+ "dataLabels": false,
66
+ "name": "Another test data source",
67
+ "type": "scatter"
68
+ }
69
+ ]
70
+ })
71
+ }}
@@ -26,9 +26,7 @@
26
26
  }
27
27
  ]
28
28
  },
29
- "legend": {
30
- "enabled": true
31
- },
29
+ "legend": true,
32
30
  "series": [
33
31
  {
34
32
  "data": [6.2, 1.5, 15.9, 1.7],