@iamproperty/components 5.5.0 → 5.5.1-beta-2

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 (96) hide show
  1. package/assets/css/components/actionbar.css +1 -1
  2. package/assets/css/components/actionbar.css.map +1 -1
  3. package/assets/css/components/applied-filters.css +1 -1
  4. package/assets/css/components/applied-filters.css.map +1 -1
  5. package/assets/css/components/card.css +1 -1
  6. package/assets/css/components/card.css.map +1 -1
  7. package/assets/css/components/card.global.css +1 -1
  8. package/assets/css/components/card.global.css.map +1 -1
  9. package/assets/css/components/charts.css +1 -1
  10. package/assets/css/components/charts.css.map +1 -1
  11. package/assets/css/components/fileupload.css.map +1 -1
  12. package/assets/css/components/header.css +1 -1
  13. package/assets/css/components/header.css.map +1 -1
  14. package/assets/css/components/nav.css.map +1 -1
  15. package/assets/css/components/slider.css.map +1 -1
  16. package/assets/css/components/tabs.css +1 -1
  17. package/assets/css/components/tabs.css.map +1 -1
  18. package/assets/css/core.min.css +1 -1
  19. package/assets/css/core.min.css.map +1 -1
  20. package/assets/css/style.min.css +1 -1
  21. package/assets/css/style.min.css.map +1 -1
  22. package/assets/js/components/accordion/accordion.component.min.js +1 -1
  23. package/assets/js/components/actionbar/actionbar.component.js +12 -3
  24. package/assets/js/components/actionbar/actionbar.component.min.js +6 -6
  25. package/assets/js/components/actionbar/actionbar.component.min.js.map +1 -1
  26. package/assets/js/components/address-lookup/address-lookup.component.js +7 -0
  27. package/assets/js/components/address-lookup/address-lookup.component.min.js +4 -4
  28. package/assets/js/components/address-lookup/address-lookup.component.min.js.map +1 -1
  29. package/assets/js/components/applied-filters/applied-filters.component.min.js +6 -6
  30. package/assets/js/components/applied-filters/applied-filters.component.min.js.map +1 -1
  31. package/assets/js/components/card/card.component.min.js +3 -3
  32. package/assets/js/components/chart/chart.component.js +71 -0
  33. package/assets/js/components/collapsible-side/collapsible-side.component.min.js +1 -1
  34. package/assets/js/components/fileupload/fileupload.component.js +1 -1
  35. package/assets/js/components/fileupload/fileupload.component.min.js +5 -5
  36. package/assets/js/components/fileupload/fileupload.component.min.js.map +1 -1
  37. package/assets/js/components/filterlist/filterlist.component.min.js +1 -1
  38. package/assets/js/components/header/header.component.min.js +2 -2
  39. package/assets/js/components/nav/nav.component.min.js +1 -1
  40. package/assets/js/components/notification/notification.component.min.js +1 -1
  41. package/assets/js/components/pagination/pagination.component.min.js +1 -1
  42. package/assets/js/components/search/search.component.min.js +1 -1
  43. package/assets/js/components/search/search.component.min.js.map +1 -1
  44. package/assets/js/components/table/table.component.js +2 -2
  45. package/assets/js/components/table/table.component.min.js +6 -6
  46. package/assets/js/components/table/table.component.min.js.map +1 -1
  47. package/assets/js/components/tabs/tabs.component.min.js +2 -2
  48. package/assets/js/dynamic.min.js +1 -1
  49. package/assets/js/dynamic.min.js.map +1 -1
  50. package/assets/js/modules/applied-filters.js +39 -7
  51. package/assets/js/modules/chart.js +613 -111
  52. package/assets/js/modules/fileupload.js +11 -0
  53. package/assets/js/modules/helpers.js +16 -0
  54. package/assets/js/modules/table.js +62 -11
  55. package/assets/js/scripts.bundle.js +31 -31
  56. package/assets/js/scripts.bundle.js.map +1 -1
  57. package/assets/js/scripts.bundle.min.js +2 -2
  58. package/assets/js/scripts.bundle.min.js.map +1 -1
  59. package/assets/sass/_elements.scss +1 -1
  60. package/assets/sass/_functions/variables.scss +80 -0
  61. package/assets/sass/_utilities.scss +1 -0
  62. package/assets/sass/components/actionbar.scss +16 -0
  63. package/assets/sass/components/applied-filters.scss +6 -48
  64. package/assets/sass/components/card.global.scss +4 -0
  65. package/assets/sass/components/card.scss +1 -1
  66. package/assets/sass/components/charts.scss +981 -234
  67. package/assets/sass/components/header.scss +8 -1
  68. package/assets/sass/components/tabs.scss +10 -1
  69. package/assets/sass/elements/badge-tag.scss +82 -0
  70. package/assets/sass/elements/buttons.scss +13 -1
  71. package/assets/sass/elements/details.scss +94 -5
  72. package/assets/sass/elements/dialog.scss +2 -0
  73. package/assets/sass/elements/forms.scss +26 -22
  74. package/assets/sass/elements/tooltips.scss +4 -3
  75. package/assets/sass/foundations/root.scss +11 -0
  76. package/assets/sass/helpers/wider-colours.scss +11 -0
  77. package/assets/ts/components/actionbar/actionbar.component.ts +14 -3
  78. package/assets/ts/components/address-lookup/address-lookup.component.ts +9 -0
  79. package/assets/ts/components/chart/README.md +37 -0
  80. package/assets/ts/components/chart/chart.component.ts +98 -0
  81. package/assets/ts/components/fileupload/fileupload.component.ts +1 -1
  82. package/assets/ts/components/table/table.component.ts +2 -2
  83. package/assets/ts/modules/applied-filters.ts +61 -7
  84. package/assets/ts/modules/chart.ts +808 -119
  85. package/assets/ts/modules/fileupload.ts +19 -0
  86. package/assets/ts/modules/helpers.ts +23 -1
  87. package/assets/ts/modules/table.ts +86 -12
  88. package/dist/components.es.js +397 -381
  89. package/dist/components.umd.js +60 -78
  90. package/dist/style.css +1 -1
  91. package/package.json +1 -1
  92. package/src/components/AppliedFilters/AppliedFilters.vue +1 -1
  93. package/src/components/Chart/Chart.vue +26 -96
  94. package/src/components/Header/Header.vue +1 -1
  95. package/src/components/Table/Table.vue +2 -2
  96. package/assets/sass/elements/badge.scss +0 -29
@@ -1,151 +1,653 @@
1
- // @ts-nocheck
2
- import { ucfirst, unsnake } from './helpers.js';
3
- function chart(chartElement, min, max, type) {
4
- let chartKey = chartElement.querySelector('.chart__key');
5
- let chartYaxis = chartElement.querySelector('.chart__yaxis');
6
- let chartGuidelines = chartElement.querySelector('.chart__guidelines');
7
- // Chart key
8
- if (chartKey && chartKey.childElementCount == 0) {
9
- createChartKey(chartElement);
10
- }
11
- // Y Axis and Guidelines
12
- if (chartYaxis && chartYaxis.childElementCount == 0) {
13
- createChartYaxis(chartElement);
14
- }
15
- if (chartGuidelines && chartGuidelines.childElementCount == 0) {
16
- createChartGuidelines(chartElement);
17
- }
18
- // Create lines for line graph
19
- if (type == "line")
20
- createLines(chartElement, min, max);
21
- // Create pies
22
- if (type == "pie")
23
- createPies(chartElement);
24
- // Add css vars to cells
25
- Array.from(chartElement.querySelectorAll('tbody tr')).forEach((tr, index) => {
1
+ import { ucfirst, unsnake, numberOfDays } from './helpers.js';
2
+ // #region Functions that setup and trigger other functions
3
+ export const setupChart = (chartElement, chartOuter, tableElement) => {
4
+ // #region Reset the chart
5
+ // empty divs to re-populate
6
+ const chartOptions = chartOuter.querySelector('.chart__options');
7
+ chartOptions.innerHTML = `<span>Chart Type</span>`;
8
+ const chartKey = chartOuter.querySelector('.chart__key');
9
+ chartKey.innerHTML = '';
10
+ const chartGuidelines = chartOuter.querySelector('.chart__guidelines');
11
+ chartGuidelines.innerHTML = ``;
12
+ const chartYaxis = chartOuter.querySelector('.chart__yaxis');
13
+ chartYaxis.innerHTML = ``;
14
+ // Remove old input fields
15
+ Array.from(chartOuter.querySelectorAll(':scope > input[type="checkbox"],:scope > input[type="radio"]')).map((element) => { element.remove(); });
16
+ // #endregion
17
+ createTypeSwitcher(chartElement, chartKey, chartOptions);
18
+ let { xaxis, type } = getChartData(chartElement, chartOuter);
19
+ setCellData(chartElement, chartOuter, tableElement);
20
+ createChartKey(chartOuter, tableElement, chartKey);
21
+ createChartGuidelines(chartElement, chartOuter, chartGuidelines);
22
+ createChartYaxis(chartElement, chartOuter, chartYaxis);
23
+ const availableTypes = chartElement.hasAttribute('data-types') ? chartElement.getAttribute('data-types').split(',') : [type];
24
+ if (availableTypes.includes('line')) {
25
+ createLines(chartElement, chartOuter);
26
+ }
27
+ if (availableTypes.includes('pie'))
28
+ createPies(chartOuter);
29
+ if (xaxis) {
30
+ createXaxis(chartElement, chartOuter, xaxis);
31
+ }
32
+ if (chartElement.hasAttribute('data-slope')) // Need to check attribute is there not its value
33
+ createSlope(chartElement, chartOuter);
34
+ if (chartElement.classList.contains('chart--show-totals'))
35
+ createKeyTotals(chartElement, chartOuter);
36
+ if (availableTypes.includes('bar') || availableTypes.includes('dumbbell') || availableTypes.includes('responsive'))
37
+ setLongestLabel(chartOuter);
38
+ // Event handlers
39
+ setEventHandlers(chartElement, chartOuter);
40
+ return true;
41
+ };
42
+ // #endregion
43
+ // #region Event handlers and observers
44
+ export const setEventHandlers = function (chartElement, chartOuter) {
45
+ const showData = chartOuter.querySelectorAll(':scope > input[type="checkbox"]');
46
+ let { type } = getChartData(chartElement, chartOuter);
47
+ const availableTypes = chartElement.hasAttribute('data-types') ? chartElement.getAttribute('data-types').split(',') : [type];
48
+ for (var i = 0; i < showData.length; i++) {
49
+ showData[i].addEventListener('change', function () {
50
+ if (availableTypes.includes('pie'))
51
+ createPies(chartOuter);
52
+ //setupOptionalContent(chartElement,min,max); // TODO: move this to the observer and just update the data attribute
53
+ });
54
+ }
55
+ /* TO DO: do i need?
56
+ // Update chart type
57
+ const chartTypes = chartElement.querySelectorAll(':scope > input[type="radio"]');
58
+ for (var i = 0; i < chartTypes.length; i++) {
59
+ chartTypes[i].addEventListener('change', function() {
60
+ //setupOptionalContent(chartElement,min,max); // TODO: move this to the observer and just update the data attribute
61
+ });
62
+ }
63
+ */
64
+ };
65
+ export const setEventObservers = function (chartElement, chartOuter) {
66
+ if (chartElement.hasAttribute('data-series'))
67
+ return false;
68
+ let table = chartElement.querySelector('table');
69
+ let newTable = chartOuter.querySelector('table');
70
+ const attributesUpdated = (mutationList, observer) => {
71
+ observer.disconnect();
72
+ observer2.disconnect();
73
+ for (const mutation of mutationList) {
74
+ if (mutation.attributeName == 'class' || (mutation.type === 'attributes' && mutation.attributeName === 'data-total') || mutation.type === 'attributes') {
75
+ newTable.innerHTML = table.innerHTML;
76
+ setupChart(chartElement, chartOuter, newTable);
77
+ }
78
+ }
79
+ observer.observe(table, { characterData: true, subtree: true });
80
+ observer2.observe(chartElement, { attributes: true });
81
+ };
82
+ const tableUpdated = (mutationList, observer) => {
83
+ observer.disconnect();
84
+ observer2.disconnect();
85
+ for (const mutation of mutationList) {
86
+ if (mutation.type == "characterData" || (mutation.type == "childList" && mutation.addedNodes.length)) {
87
+ newTable.innerHTML = table.innerHTML;
88
+ setupChart(chartElement, chartOuter, newTable);
89
+ }
90
+ }
91
+ observer.observe(table, { characterData: true, subtree: true });
92
+ observer2.observe(chartElement, { attributes: true });
93
+ };
94
+ let observer = new MutationObserver(tableUpdated);
95
+ let observer2 = new MutationObserver(attributesUpdated);
96
+ observer.observe(table, { characterData: true, subtree: true });
97
+ observer2.observe(chartElement, { attributes: true });
98
+ return true;
99
+ };
100
+ // #endregion
101
+ // #region GET functions
102
+ export const getChartData = function (chartElement, chartOuter) {
103
+ let table = chartOuter.querySelector('.chart__wrapper table');
104
+ let min = chartElement.hasAttribute('data-min') ? chartElement.getAttribute('data-min') : 0;
105
+ let max = chartElement.hasAttribute('data-max') ? chartElement.getAttribute('data-max') : getLargestValue(table);
106
+ let type = chartElement.hasAttribute('data-type') ? chartElement.getAttribute('data-type') : 'column';
107
+ let yaxis = chartElement.hasAttribute('data-yaxis') ? chartElement.getAttribute('data-yaxis').split(',') : [];
108
+ let guidelines = chartElement.hasAttribute('data-guidelines') ? chartElement.getAttribute('data-guidelines').split(',') : [];
109
+ let targets = chartElement.hasAttribute('data-targets') ? JSON.parse(chartElement.getAttribute('data-targets')) : null;
110
+ let events = chartElement.hasAttribute('data-events') ? JSON.parse(chartElement.getAttribute('data-events')) : null;
111
+ let xaxis = chartElement.hasAttribute('data-xaxis') ? chartElement.getAttribute('data-xaxis').split(',') : null;
112
+ let increment = chartElement.hasAttribute('data-increment') ? chartElement.getAttribute('data-increment') : null;
113
+ let start = chartElement.hasAttribute('data-start') ? chartElement.getAttribute('data-start') : 0;
114
+ let end = chartElement.hasAttribute('data-end') ? chartElement.getAttribute('data-end') : getLargestValue(table); // TODO - get largest value from the data-xaxis
115
+ let slope = chartElement.hasAttribute('data-slope') ? chartElement.getAttribute('data-slope') : null;
116
+ let yInt = chartElement.hasAttribute('data-yint') ? chartElement.getAttribute('data-yint') : null;
117
+ return { min, max, type, yaxis, targets, events, xaxis, increment, start, end, slope, yInt, guidelines };
118
+ };
119
+ function getLargestValue(table) {
120
+ let values = Array.from(table.querySelectorAll('tbody td:not(:first-child)')).map((element) => {
121
+ let currentValue = String(element.textContent);
122
+ currentValue = currentValue.replace('£', '');
123
+ currentValue = currentValue.replace('%', '');
124
+ currentValue = currentValue.replace(',', '');
125
+ currentValue = Number.parseFloat(currentValue);
126
+ return currentValue;
127
+ });
128
+ let largetValue = Math.max(...values);
129
+ // TO DO round to the nearest 10, 100, 1000 and so on
130
+ return Math.ceil(largetValue);
131
+ }
132
+ const getValues = function (value, min, max, start) {
133
+ let cleanValue = String(value);
134
+ cleanValue = cleanValue.replace('£', '');
135
+ cleanValue = cleanValue.replace('%', '');
136
+ cleanValue = cleanValue.replace(',', '');
137
+ cleanValue = Number.parseFloat(cleanValue);
138
+ let percent = ((cleanValue - min) / (max - min)) * 100;
139
+ let axis = percent;
140
+ let bottom = 0;
141
+ if (start && start != 0) {
142
+ bottom = ((start - min) / (max - min)) * 100;
143
+ }
144
+ // If the value is negative the position below the 0 line
145
+ if (min < 0) {
146
+ bottom = Math.abs(((min) / (max - min)) * 100);
147
+ if (cleanValue < 0) {
148
+ percent = bottom - percent;
149
+ bottom = bottom - percent;
150
+ axis = bottom;
151
+ }
152
+ else {
153
+ percent = percent - bottom;
154
+ axis = percent + bottom;
155
+ }
156
+ }
157
+ return { percent, axis, bottom };
158
+ };
159
+ function getCoordinatesForPercent(percent, pieCount) {
160
+ // This moves the start point to the top middle point like a clock
161
+ if (pieCount > 1)
162
+ percent = percent - 0.25;
163
+ const x = Math.cos(2 * Math.PI * percent);
164
+ const y = Math.sin(2 * Math.PI * percent);
165
+ return [x * 100, y * 100];
166
+ }
167
+ // #endregion
168
+ // #region SET functions - set data attributes and classes
169
+ export const setCellData = function (chartElement, chartOuter, table) {
170
+ let { min, max } = getChartData(chartElement, chartOuter);
171
+ let chartType = chartElement.getAttribute('data-type');
172
+ let increment = chartElement.getAttribute('data-increment');
173
+ let incrementStart = chartElement.getAttribute('data-start');
174
+ let incrementEnd = chartElement.getAttribute('data-end');
175
+ let startDay = min;
176
+ // Change how gant charts are configured as this just seems bizarre now
177
+ if (increment == "days") {
178
+ max = numberOfDays(min, max);
179
+ min = 0;
180
+ chartElement.querySelector('tbody').setAttribute('style', `--single-day:${((1 / max) * 100)}%;`);
181
+ }
182
+ Array.from(table.querySelectorAll('tbody tr')).forEach((tr, index) => {
26
183
  let group = tr.querySelector('td:first-child, th:first-child') ? tr.querySelector('td:first-child, th:first-child').innerHTML : '';
27
- Array.from(tr.querySelectorAll('td[data-numeric]:not([data-numeric="0"]):not(:first-child)')).forEach((td, index) => {
28
- const value = Number.parseFloat(td.getAttribute('data-numeric'));
29
- let percent = ((value - min) / (max)) * 100;
30
- const content = td.innerHTML;
184
+ let coverageStart = 100;
185
+ let coverageEnd = 0;
186
+ let cumulativeComparison = 0;
187
+ // For waffle charts
188
+ let previousAfter = 0;
189
+ let rowPosition = 0;
190
+ let totalPercent = 0;
191
+ // Set the data numeric value if not set
192
+ Array.from(tr.querySelectorAll('td:not([data-numeric]):not(:first-child)')).forEach((td) => {
193
+ let value = parseFloat(td.textContent.replace('£', '').replace('%', '').replace(',', ''));
194
+ let start = 0;
195
+ if (increment == "days") {
196
+ let dates = td.textContent.split(' - ');
197
+ if (dates[1]) {
198
+ value = numberOfDays(dates[0], dates[1]);
199
+ start = numberOfDays(startDay, dates[0]) - 1;
200
+ }
201
+ }
202
+ td.setAttribute('data-numeric', value);
203
+ td.setAttribute('data-start', start);
204
+ });
205
+ // Set the data label value if not set
206
+ Array.from(tr.querySelectorAll('td:not([data-label])')).forEach((td, index) => {
207
+ td.setAttribute('data-label', table.querySelectorAll('thead th')[index].textContent);
208
+ });
209
+ if (tr.querySelector('[data-label="Total"]')) {
210
+ tr.setAttribute('data-total', tr.querySelector('[data-label="Total"][data-numeric]').getAttribute('data-numeric'));
211
+ }
212
+ if (tr.querySelector('[data-label="Min"]')) {
213
+ tr.setAttribute('data-min', tr.querySelector('[data-label="Min"][data-numeric]').getAttribute('data-numeric'));
214
+ }
215
+ if (tr.querySelector('[data-label="Max"]')) {
216
+ tr.setAttribute('data-max', tr.querySelector('[data-label="Max"][data-numeric]').getAttribute('data-numeric'));
217
+ }
218
+ if ((chartType == "proportional" || chartType == "waffle") && !tr.hasAttribute('data-total')) {
219
+ let total = 0;
220
+ Array.from(tr.querySelectorAll('td[data-numeric]:not(:first-child)')).forEach((td) => {
221
+ let display = getComputedStyle(td).display;
222
+ if (display == 'none')
223
+ return;
224
+ total += Number.parseFloat(td.getAttribute('data-numeric'));
225
+ });
226
+ tr.setAttribute('data-total', total);
227
+ }
228
+ let rowMin = tr.hasAttribute('data-min') ? tr.getAttribute('data-min') : min;
229
+ let rowMax = tr.hasAttribute('data-max') ? tr.getAttribute('data-max') : max;
230
+ // Add a useful index css var for the use of animatons.
231
+ tr.setAttribute('style', `--row-index:${index + 1};`);
232
+ if (rowMin < 0) {
233
+ let minBottom = Math.abs(((rowMin) / (rowMax - rowMin)) * 100);
234
+ chartElement.setAttribute('style', `--min-bottom: ${minBottom}%;`);
235
+ }
236
+ // Add css vars to cells
237
+ Array.from(tr.querySelectorAll('td[data-numeric]:not([data-label="Min"]):not([data-label="Max"]):not(:first-child)')).forEach((td) => {
238
+ let display = getComputedStyle(td).display;
239
+ if (display == 'none')
240
+ return;
31
241
  const label = td.getAttribute('data-label');
32
- let bottom = 0;
33
- // If the value is negative the position below the 0 line
34
- if (min < 0) {
35
- bottom = Math.abs((min) / (max) * 100);
36
- if (value < 0) {
37
- bottom = bottom - percent;
242
+ const content = td.innerHTML;
243
+ const value = Number.parseFloat(td.getAttribute('data-numeric'));
244
+ const start = Number.parseFloat(td.getAttribute('data-start'));
245
+ if (!td.querySelector('span[data-group]'))
246
+ td.innerHTML = `<span data-group="${group}" data-label="${label}">${content}</span>`;
247
+ if (!td.hasAttribute('style')) {
248
+ let { percent, bottom, axis } = getValues(value, rowMin, rowMax, start);
249
+ td.setAttribute('data-percent', percent);
250
+ td.setAttribute("style", `--bottom:${bottom}%;--percent:${percent}%;--axis:${axis}%;`);
251
+ if (tr.hasAttribute('data-total')) {
252
+ let rowTotal = tr.getAttribute('data-total');
253
+ let comparison = ((value - rowMin) / (rowTotal)) * 100;
254
+ cumulativeComparison += comparison;
255
+ td.setAttribute('data-comparison', comparison);
256
+ td.style.setProperty('--cumulative-comparision', `${cumulativeComparison}%`);
257
+ td.style.setProperty('--comparison', `${comparison}%`);
258
+ }
259
+ if (chartElement.classList.contains("chart--value-order")) {
260
+ let order = (10000 - Math.round(percent * 100));
261
+ td.style.setProperty('--order', `${order}%`);
262
+ }
263
+ if (chartType == "dumbbell") {
264
+ if (percent < coverageStart) {
265
+ tr.style.setProperty('--coverage-start', `${percent}%`);
266
+ coverageStart = percent;
267
+ }
268
+ if (percent > coverageEnd) {
269
+ tr.style.setProperty('--coverage-end', `${percent}%`);
270
+ coverageEnd = percent;
271
+ }
38
272
  }
273
+ if (chartType == "waffle") {
274
+ let actualPercent = Math.round(td.getAttribute('data-comparison'));
275
+ // Prevent the chart from spilling out of the top
276
+ totalPercent += actualPercent;
277
+ if (totalPercent > 100)
278
+ actualPercent = actualPercent - (totalPercent - 100);
279
+ let percentMinusAfter = previousAfter != 0 ? actualPercent - (10 - previousAfter) : actualPercent;
280
+ let rowHeight = percentMinusAfter < 10 ? 10 : Math.floor(percentMinusAfter / 10) * 10;
281
+ let rowWidth = percentMinusAfter < 10 ? percentMinusAfter * 10 : 100;
282
+ let maxWidth = actualPercent * 10;
283
+ td.style.setProperty('--rowPosition', `${rowPosition}%`);
284
+ td.style.setProperty('--rowHeight', `${rowHeight}%`);
285
+ td.style.setProperty('--rowWidth', `${rowWidth}%`);
286
+ td.style.setProperty('--maxWidth', `${maxWidth}%`);
287
+ // Create the psuedo element variables for the the block that sticks out BELOW the main row
288
+ let beforeWidth = 0;
289
+ if (previousAfter != 0) {
290
+ beforeWidth = 100 - (previousAfter * 10);
291
+ td.style.setProperty('--beforeWidth', `${beforeWidth}%`);
292
+ td.style.setProperty('--beforeHeight', `${10 / rowHeight * 100}%`);
293
+ td.style.setProperty('--beforeLeft', `${previousAfter * 10}%`);
294
+ }
295
+ // Create the psuedo element variables for the the block that sticks out ABOVE the main row
296
+ let afterWidth = Math.round(percentMinusAfter - rowHeight) * 10;
297
+ let afterHeight = 10 / (rowHeight) * 100;
298
+ td.style.setProperty('--afterWidth', `${afterWidth}%`);
299
+ td.style.setProperty('--afterHeight', `${afterHeight}%`);
300
+ // If the row width plus the previous after is under 10 it needs to be added to the new previousAfter variable
301
+ if (previousAfter + beforeWidth / 10 + rowWidth / 10 < 10)
302
+ previousAfter += beforeWidth / 10 + (rowWidth / 10);
303
+ else if (percentMinusAfter < 10)
304
+ previousAfter = percentMinusAfter;
305
+ else
306
+ previousAfter = afterWidth / 10;
307
+ // Add to the row position so that the new row is shoved up if needed
308
+ rowPosition += (rowWidth > 0 ? rowHeight : 0) + (afterWidth > 0 ? 10 : 0);
309
+ }
310
+ }
311
+ // totals
312
+ if (chartElement.classList.contains('chart--show-totals')) {
313
+ let chartTotal = chartElement.getAttribute('data-total') ? Number.parseFloat(chartElement.getAttribute('data-total')) : 0;
314
+ let keyTotal = chartElement.querySelector(`.key[data-label="${label}"]`) && chartElement.querySelector(`.key[data-label="${label}"]`).getAttribute('data-total') ? Number.parseFloat(chartElement.querySelector(`.key[data-label="${label}"]`).getAttribute('data-total')) : 0;
315
+ if (chartElement.querySelector(`.key[data-label="${label}"]`))
316
+ chartElement.querySelector(`.key[data-label="${label}"]`).setAttribute('data-total', keyTotal + value);
317
+ chartElement.setAttribute('data-total', chartTotal + value);
39
318
  }
40
- td.setAttribute("style", `--bottom:${bottom}%;--percent:${percent}%;`);
41
- td.innerHTML = `<span data-group="${group}" data-label="${label}">${content}</span>`;
42
319
  });
320
+ // Values for incremental charts i.e. histograms...
321
+ if (increment && incrementStart && incrementEnd) {
322
+ let firstCellValue = parseFloat(tr.querySelector('td:first-child').textContent.replace('£', '').replace('%', '').replace(',', ''));
323
+ let position = ((firstCellValue - incrementStart) / (incrementEnd - incrementStart)) * 100;
324
+ tr.setAttribute('style', `--position:${position}%;`);
325
+ }
43
326
  });
44
- }
45
- export const createChartKey = function (chartElement) {
46
- let chartKey = chartElement.querySelector('.chart__key');
47
- Array.from(chartElement.querySelectorAll('thead th')).forEach((arrayElement, index) => {
48
- chartKey.innerHTML += `<div class="key">${arrayElement.innerText}</div>`;
327
+ };
328
+ export const setLongestLabel = function (chartOuter) {
329
+ let chartWrapper = chartOuter.querySelector('.chart__wrapper');
330
+ let table = chartOuter.querySelector('.chart table');
331
+ // set the longest label attr so that the bar chart knows what margin to set on the left
332
+ let longestLabel = '';
333
+ Array.from(table.querySelectorAll('tbody tr td:first-child')).forEach((td) => {
334
+ if (typeof td.textContent != "undefined" && td.textContent.length > longestLabel.length)
335
+ longestLabel = td.textContent;
49
336
  });
337
+ chartWrapper.setAttribute('data-longest-label', longestLabel);
338
+ // set the longest data set attr so that the bar chart knows what margin to set on the left
339
+ let longestSet = '';
340
+ Array.from(table.querySelectorAll('thead tr th')).forEach((td) => {
341
+ if (td.textContent.length > longestSet.length)
342
+ longestSet = td.textContent;
343
+ });
344
+ chartWrapper.setAttribute('data-set-label', longestSet);
50
345
  };
51
- export const createChartGuidelines = function (chartElement) {
52
- let chartGuidelines = chartElement.querySelector('.chart__guidelines');
53
- const max = chartElement.getAttribute('data-max');
54
- const min = chartElement.getAttribute('data-min');
55
- chartGuidelines.innerHTML += `<div style="--value: 0;--percent:0%;" class="axis__point"><span>0</span></div>`;
56
- chartGuidelines.innerHTML += `<div style="--value: ${max};--percent:100%;" class="axis__point"><span>${max}</span></div>`;
346
+ // #endregion
347
+ // #region CREATE function
348
+ export const createTypeSwitcher = function (chartElement, chartKey, chartOptions) {
349
+ const chartID = `chart-${Date.now() + (Math.floor(Math.random() * 100) + 1)}`;
350
+ const availableTypes = chartElement.hasAttribute('data-types') ? chartElement.getAttribute('data-types').split(',') : [];
351
+ if (!chartElement.hasAttribute('data-types') && chartElement.hasAttribute('data-type'))
352
+ chartKey.insertAdjacentHTML('afterend', `<input type="radio" name="chart-type" value="${chartElement.getAttribute('data-type')}" checked="">`);
353
+ else if (chartElement.hasAttribute('data-types')) {
354
+ let chartType = chartElement.hasAttribute('data-type') ? chartElement.getAttribute('data-type') : 'column';
355
+ chartOptions.insertAdjacentHTML('beforebegin', availableTypes.map((type) => `<input type="radio" name="chart-type" value="${type}" id="${chartID}-${type}" ${chartType == type ? 'checked=""' : ''}>`).join(''));
356
+ chartOptions.insertAdjacentHTML('beforeend', availableTypes.map((type) => `<label for="${chartID}-${type}">${type}</label>`).join(''));
357
+ }
57
358
  };
58
- export const createChartYaxis = function (chartElement) {
59
- let chartYaxis = chartElement.querySelector('.chart__yaxis');
60
- const max = chartElement.getAttribute('data-max');
61
- const min = chartElement.getAttribute('data-min');
62
- chartYaxis.innerHTML += `<div style="--value: 0;--percent:0%;" class="axis__point"><span>0</span></div>`;
63
- chartYaxis.innerHTML += `<div style="--value: ${max};--percent:100%;" class="axis__point"><span>${max}</span></div>`;
359
+ export const createChartKey = function (chartOuter, tableElement, chartKey) {
360
+ const chartID = `chart-${Date.now() + (Math.floor(Math.random() * 100) + 1)}`;
361
+ //const chartOuter = chartElement.querySelector('.chart__outer');
362
+ let previousInput;
363
+ let headings = Array.from(tableElement.querySelectorAll('thead th'));
364
+ headings.forEach((arrayElement, index) => {
365
+ if (index != 0) {
366
+ previousInput = createChartKeyItem(chartID, index, arrayElement.textContent, chartKey, chartOuter, previousInput);
367
+ }
368
+ if (index == 50) {
369
+ headings.length = index + 1;
370
+ }
371
+ });
372
+ return true;
64
373
  };
65
- function getCoordinatesForPercent(percent) {
66
- const x = Math.cos(2 * Math.PI * percent);
67
- const y = Math.sin(2 * Math.PI * percent);
68
- return [x * 100, y * 100];
374
+ function createChartKeyItem(chartID, index, text, chartKey, chartOuter, previousInput) {
375
+ let input = document.createElement('input');
376
+ input.setAttribute('name', `${chartID}-dataset-${index}`);
377
+ input.setAttribute('id', `${chartID}-dataset-${index}`);
378
+ input.setAttribute('checked', `checked`);
379
+ input.setAttribute('type', `checkbox`);
380
+ if (index == 1)
381
+ chartOuter.prepend(input);
382
+ else
383
+ chartOuter.insertBefore(input, previousInput.nextSibling);
384
+ previousInput = input;
385
+ let label = document.createElement('label');
386
+ label.setAttribute('class', `key btn btn-action`);
387
+ label.setAttribute('for', `${chartID}-dataset-${index}`);
388
+ label.setAttribute('data-label', `${text}`);
389
+ label.innerHTML = `${text}`;
390
+ chartKey.append(label);
391
+ return previousInput;
69
392
  }
70
- export const createPies = function (chartElement) {
393
+ export const createChartGuidelines = function (chartElement, chartOuter, chartGuidelines) {
394
+ let { min, max, yaxis, increment, guidelines } = getChartData(chartElement, chartOuter);
395
+ if (guidelines.length)
396
+ yaxis = guidelines;
397
+ let startDay = min;
398
+ if (increment == "days") {
399
+ max = numberOfDays(min, max);
400
+ min = 0;
401
+ }
402
+ if (!chartGuidelines) {
403
+ chartGuidelines = document.createElement('div');
404
+ chartGuidelines.setAttribute('class', 'chart__guidelines');
405
+ }
406
+ chartGuidelines.innerHTML = '';
407
+ for (var i = 0; i < yaxis.length; i++) {
408
+ let value = parseFloat(yaxis[i].replace('£', '').replace('%', '').replace(',', ''));
409
+ if (increment == "days") {
410
+ value = numberOfDays(startDay, yaxis[i]) - 1;
411
+ }
412
+ let { axis } = getValues(value, min, max);
413
+ chartGuidelines.innerHTML += `<div class="guideline" style="--percent:${axis}%;"><span>${yaxis[i]}</span></div>`;
414
+ }
415
+ };
416
+ export const createChartYaxis = function (chartElement, chartOuter, chartYaxis) {
417
+ let { min, max, yaxis, increment } = getChartData(chartElement, chartOuter);
418
+ let startDay = min;
419
+ if (increment == "days") {
420
+ max = numberOfDays(min, max);
421
+ min = 0;
422
+ }
423
+ if (!chartYaxis) {
424
+ chartYaxis = document.createElement('div');
425
+ chartYaxis.setAttribute('class', 'chart__yaxis');
426
+ }
427
+ chartYaxis.innerHTML = '';
428
+ for (var i = 0; i < yaxis.length; i++) {
429
+ let value = parseFloat(yaxis[i].replace('£', '').replace('%', ''));
430
+ if (increment == "days") {
431
+ value = numberOfDays(startDay, yaxis[i]);
432
+ }
433
+ let { axis } = getValues(value, min, max);
434
+ chartYaxis.innerHTML += `<div class="axis__point" style="--percent:${axis}%;"><span>${yaxis[i]}</span></div>`;
435
+ }
436
+ };
437
+ export const createXaxis = function (chartElement, chartOuter, xaxis) {
438
+ const chart = chartOuter.querySelector('.chart');
439
+ let chartXaxis = chartOuter.querySelector('.chart__xaxis');
440
+ let { increment, start, end } = getChartData(chartElement, chartOuter);
441
+ if (!chartXaxis) {
442
+ chartXaxis = document.createElement('div');
443
+ chartXaxis.setAttribute('class', 'chart__xaxis');
444
+ }
445
+ if (increment && start && end) {
446
+ chartXaxis.innerHTML = '';
447
+ for (var i = 0; i < xaxis.length; i++) {
448
+ let value = parseFloat(xaxis[i].replace('£', '').replace('%', ''));
449
+ let position = ((value - start) / (end - start)) * 100;
450
+ chartXaxis.innerHTML += `<div class="axis__point" style="--percent:${position}%;"><span>${xaxis[i]}</span></div>`;
451
+ }
452
+ }
453
+ chart.prepend(chartXaxis);
454
+ };
455
+ export const createLines = function (chartElement, chartOuter) {
456
+ let { min, max } = getChartData(chartElement, chartOuter);
457
+ let chartType = chartElement.getAttribute('data-type');
458
+ let returnString = '';
459
+ //let chartWrapper = chartOuter.querySelector('.chart__wrapper');
460
+ let linesWrapper = chartOuter.querySelector('.chart__lines');
461
+ let items = Array.from(chartOuter.querySelectorAll('tbody tr'));
462
+ let lines = Array();
463
+ let linesCount = chartOuter.querySelectorAll('thead th:not(:first-child)').length;
464
+ let commands = Array();
465
+ let animatelines = Array();
466
+ let itemCount = items.length <= 1000 ? items.length : 1000;
467
+ let spacer = 200 / (itemCount - 1);
468
+ let spacerIndent = 0;
469
+ if (chartType == "combo") {
470
+ spacer = 200 / (itemCount);
471
+ spacerIndent = spacer / 2;
472
+ }
473
+ // Creates the lines array from the fields array
474
+ for (let i = 0; i < linesCount; i++) {
475
+ lines[i] = '';
476
+ animatelines[i] = '';
477
+ commands[i] = 'M';
478
+ }
479
+ // populate the lines array from the items array
480
+ let counter = 0;
481
+ Array.from(chartOuter.querySelectorAll('tbody tr')).forEach((item) => {
482
+ const display = getComputedStyle(item).display;
483
+ if (display != "none") {
484
+ Array.from(item.querySelectorAll('td:not(:first-child)')).forEach((cell, subindex) => {
485
+ if (!cell.classList.contains('chart__bar')) {
486
+ let value = cell.getAttribute('data-numeric');
487
+ let { axis } = getValues(value, min, max);
488
+ if (!Number.isNaN(axis)) {
489
+ lines[subindex] += `${commands[subindex]} ${(spacerIndent) + (spacer * counter)} ${100 - axis} `;
490
+ animatelines[subindex] += `${commands[subindex]} ${spacer * counter} 100 `;
491
+ commands[subindex] = 'L';
492
+ }
493
+ else {
494
+ commands[subindex] = 'M';
495
+ }
496
+ }
497
+ });
498
+ counter++;
499
+ }
500
+ });
501
+ lines.forEach((line, index) => {
502
+ returnString += `
503
+ <svg viewBox="0 0 200 100" class="line" preserveAspectRatio="none">
504
+ <path fill="none" d="${line}" style="--path: path('${animatelines[index]}');"></path>
505
+ </svg>`;
506
+ });
507
+ linesWrapper.innerHTML = returnString;
508
+ };
509
+ export const createPies = function (chartOuter) {
71
510
  let returnString = '';
72
- let pieWrapper = chartElement.querySelector('.pies');
73
- Array.from(chartElement.querySelectorAll('tbody tr')).forEach((item, index) => {
511
+ let chartInner = chartOuter.querySelector('.chart');
512
+ let pieWrapper = chartOuter.querySelector('.pies');
513
+ if (!pieWrapper) {
514
+ pieWrapper = document.createElement("div");
515
+ pieWrapper.setAttribute('class', 'pies');
516
+ chartInner.append(pieWrapper);
517
+ }
518
+ Array.from(chartInner.querySelectorAll('tbody tr')).forEach((item, index) => {
74
519
  let paths = '';
75
520
  let tooltips = '';
76
521
  let cumulativePercent = 0;
77
522
  let total = 0;
78
523
  let titleKey = item.querySelectorAll('td')[0];
79
524
  let title = titleKey.innerHTML;
80
- Array.from(item.querySelectorAll('td')).forEach((cell, subindex) => {
81
- if (subindex != 0) {
82
- let value = cell.getAttribute('data-numeric');
525
+ let pieCount = 0;
526
+ // Work out the total amount
527
+ Array.from(item.querySelectorAll('td')).forEach((td, subindex) => {
528
+ const display = getComputedStyle(td).display;
529
+ if (subindex != 0 && display != 'none') {
530
+ let value = td.getAttribute('data-numeric');
83
531
  value = value.replace('£', '');
84
532
  value = value.replace('%', '');
533
+ value = value.replace(',', '');
85
534
  value = Number.parseInt(value);
86
535
  total += value;
536
+ pieCount++;
87
537
  }
88
538
  });
89
- Array.from(item.querySelectorAll('td')).forEach((cell, subindex) => {
90
- if (subindex != 0) {
91
- let value = cell.getAttribute('data-numeric');
539
+ // Create the paths
540
+ Array.from(item.querySelectorAll('td')).forEach((td, subindex) => {
541
+ const display = getComputedStyle(td).display;
542
+ if (subindex != 0 && pieCount == 1 && display != "none") {
543
+ const pathData = `M 0 0 L 100 0 A 100 100 0 1 1 100 -0.01 L 0 0`;
544
+ paths += `<path d="${pathData}" style="${td.getAttribute('style')} --path-index: ${subindex};"></path>`;
545
+ tooltips += `<foreignObject x="-70" y="-70" width="140" height="140" ><div><span class="h5 mb-0"><span class="total d-block">${ucfirst(unsnake(title))}</span> ${ucfirst(unsnake(td.getAttribute('data-label')))}<br/> ${td.innerHTML}${td.hasAttribute('data-second') ? `${td.getAttribute('data-second-label')}: ${td.getAttribute('data-second')}` : ''}</span></div></foreignObject>`;
546
+ }
547
+ else if (subindex != 0) {
548
+ let value = td.getAttribute('data-numeric');
549
+ let hide = display == "none" ? "display: none;" : "";
92
550
  value = value.replace('£', '');
93
551
  value = value.replace('%', '');
552
+ value = value.replace(',', '');
94
553
  value = Number.parseInt(value);
95
554
  let percent = value / total;
96
- //lines[subindex-1] += `${command} ${spacer * index} ${100-percent} `;
97
- const [startX, startY] = getCoordinatesForPercent(cumulativePercent);
98
- // each slice starts where the last slice ended, so keep a cumulative percent
99
- cumulativePercent += percent;
100
- const [endX, endY] = getCoordinatesForPercent(cumulativePercent);
101
- // if the slice is more than 50%, take the large arc (the long way around)
102
- const largeArcFlag = percent > .5 ? 1 : 0;
103
- // create an array and join it just for code readability
555
+ const [startX, startY] = getCoordinatesForPercent(cumulativePercent, pieCount);
556
+ const [endX, endY] = getCoordinatesForPercent(cumulativePercent + percent, pieCount);
557
+ const largeArcFlag = percent > .5 ? 1 : 0; // if the slice is more than 50%, take the large arc (the long way around)
104
558
  const pathData = [
105
- `M ${startX} ${startY}`,
106
- `A 100 100 0 ${largeArcFlag} 1 ${endX} ${endY}`,
559
+ `M 0 0`,
560
+ `L ${(startX ? startX.toFixed(0) : 0)} ${(startY ? startY.toFixed(0) : 0)}`,
561
+ `A 100 100 0 ${largeArcFlag} 1 ${(endX ? endX.toFixed(0) : 0)} ${(endY ? endY.toFixed(0) : 0)}`,
107
562
  `L 0 0`, // Line
108
563
  ].join(' ');
109
- paths += `<path d="${pathData}"></path>`;
110
- tooltips += `<foreignObject x="-70" y="-70" width="140" height="140" style="transform: rotate(90deg)"><div><span class="h5 mb-0"><span class="total d-block">${ucfirst(unsnake(title))}</span> ${ucfirst(unsnake(cell.getAttribute('data-label')))}<br/> ${cell.innerHTML}</span></div></foreignObject>`;
564
+ paths += `<path d="${pathData}" style="${td.getAttribute('style')} --path-index: ${subindex};${hide}"></path>`;
565
+ tooltips += `<foreignObject x="-70" y="-70" width="140" height="140" ><div><span class="h5 mb-0"><span class="total d-block">${ucfirst(unsnake(title))}</span> ${ucfirst(unsnake(td.getAttribute('data-label')))}<br/> ${td.innerHTML}${td.hasAttribute('data-second') ? `${td.getAttribute('data-second-label')}: ${td.getAttribute('data-second')}` : ''}</span></div></foreignObject>`;
566
+ // each slice starts where the last slice ended, so keep a cumulative percent
567
+ if (display != 'none')
568
+ cumulativePercent += percent;
111
569
  }
112
570
  });
113
- returnString += `<div class="pie"><svg viewBox="-105 -105 210 210" style="transform: rotate(-90deg)" preserveAspectRatio="none">${paths}<foreignObject x="-70" y="-70" width="140" height="140" style="transform: rotate(90deg)"><div><span class="h5 mb-0">${title}</span></div></foreignObject>${tooltips}</svg></div>`;
571
+ returnString += `<div class="pie"><svg viewBox="-105 -105 210 210" preserveAspectRatio="none" style="--row-index: ${index + 1};">${paths}${tooltips}</svg><div><span class="h5 mb-0">${title}</span></div></div>`;
114
572
  });
115
573
  pieWrapper.innerHTML = returnString;
116
574
  };
117
- export const createLines = function (chartElement, min, max) {
118
- let returnString = '';
119
- let linesWrapper = chartElement.querySelector('.lines');
120
- let items = Array.from(chartElement.querySelectorAll('tbody tr'));
121
- let lines = Array();
122
- let spacer = 200 / (items.length - 1);
123
- // Creates the lines array from the fields array
124
- Array.from(chartElement.querySelectorAll('thead th')).forEach((field, index) => {
125
- if (index != 0) {
126
- lines[index - 1] = '';
575
+ export const createSlope = function (chartElement, chartOuter) {
576
+ let n = 0;
577
+ let totalX = 0;
578
+ let totalY = 0;
579
+ let totalXY = 0;
580
+ let totalXsquared = 0;
581
+ let { min, max, start, end, slope, yInt } = getChartData(chartElement, chartOuter);
582
+ let chart = chartOuter.querySelector('.chart');
583
+ let slopeWrapper = chartOuter.querySelector('.slope');
584
+ if (!slopeWrapper) {
585
+ slopeWrapper = document.createElement("div");
586
+ slopeWrapper.setAttribute('class', 'slope');
587
+ chart.prepend(slopeWrapper);
588
+ }
589
+ Array.from(chart.querySelectorAll('tbody tr')).forEach((tr) => {
590
+ const display = getComputedStyle(tr).display;
591
+ if (display != "none") {
592
+ let x = parseFloat(tr.querySelector('td:first-child').textContent);
593
+ let y = 0;
594
+ Array.from(tr.querySelectorAll('td:not(:first-child)')).forEach((td) => {
595
+ y += parseFloat(td.getAttribute('data-numeric'));
596
+ });
597
+ let xy = x * y;
598
+ let xSquared = x * x;
599
+ totalX += x;
600
+ totalY += y;
601
+ totalXY += xy;
602
+ totalXsquared += xSquared;
603
+ n++;
127
604
  }
128
605
  });
129
- // populate the lines array from the items array
130
- Array.from(chartElement.querySelectorAll('tbody tr')).forEach((item, index) => {
131
- Array.from(item.querySelectorAll('td')).forEach((cell, subindex) => {
132
- if (subindex != 0) {
133
- let value = cell.getAttribute('data-numeric');
134
- value = value.replace('£', '');
135
- value = value.replace('%', '');
136
- value = Number.parseFloat(value) - min;
137
- const percent = (value / max) * 100;
138
- let command = index == 0 ? 'M' : 'L';
139
- lines[subindex - 1] += `${command} ${spacer * index} ${100 - percent} `;
140
- }
141
- });
606
+ // Least squares method (https://www.youtube.com/watch?v=P8hT5nDai6A)
607
+ let m = slope ? parseFloat(slope) : ((n * totalXY) - (totalX * totalY)) / ((n * totalXsquared) - (totalX * totalX)); // Slope
608
+ let b = yInt ? parseFloat(yInt) : (totalY - (m * totalX)) / n; // Y intercept
609
+ let firstY = (m * parseFloat(start)) + b;
610
+ let lastY = (m * parseFloat(end)) + b;
611
+ let { percent: firstYPercent } = getValues(firstY, min, max);
612
+ let { percent: lastYPercent } = getValues(lastY, min, max);
613
+ slopeWrapper.innerHTML = `<svg viewBox="0 0 200 100" class="line" preserveAspectRatio="none"><path fill="none" d="M 0 ${100 - firstYPercent} L 200 ${100 - lastYPercent}" style="--path: path('M 0 100 L 200 100');"></path></svg>`;
614
+ };
615
+ function createKeyTotals(chartElement, chartOuter) {
616
+ let chartTotal = 0;
617
+ Array.from(chartOuter.querySelectorAll('tbody tr:not([data-total]) td[data-numeric]:not([data-label="Min"]):not([data-label="Max"]):not(:first-child)')).forEach((td) => {
618
+ const value = Number.parseFloat(td.getAttribute('data-numeric'));
619
+ chartTotal += value;
142
620
  });
143
- lines.forEach((line, index) => {
144
- returnString += `
145
- <svg viewBox="0 0 200 100" class="line" preserveAspectRatio="none">
146
- <path fill="none" d="${line}"></path>
147
- </svg>`;
621
+ // Get row totals already worked out
622
+ Array.from(chartOuter.querySelectorAll('tbody tr[data-total]')).forEach((tr) => {
623
+ const value = Number.parseFloat(tr.getAttribute('data-total'));
624
+ chartTotal += value;
148
625
  });
149
- linesWrapper.innerHTML = returnString;
150
- };
151
- export default chart;
626
+ chartElement.setAttribute('data-total', chartTotal);
627
+ Array.from(chartOuter.querySelectorAll('.chart__key .key[data-label]')).forEach((key) => {
628
+ if (key.querySelector('.chart__total'))
629
+ key.querySelector('.chart__total').remove();
630
+ let label = key.getAttribute('data-label');
631
+ let keyTotal = 0;
632
+ Array.from(chartOuter.querySelectorAll(`tbody td[data-label="${label}"]`)).forEach((td) => {
633
+ const value = Number.parseFloat(td.getAttribute('data-numeric'));
634
+ keyTotal += value;
635
+ });
636
+ let keyPercent = Math.round((keyTotal / chartTotal) * 100);
637
+ if (chartElement.hasAttribute('data-currency')) {
638
+ if (chartElement.getAttribute('data-currency') == "GBP") {
639
+ // @ts-ignore
640
+ keyTotal = new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP', trailingZeroDisplay: 'stripIfInteger' }).format(keyTotal);
641
+ }
642
+ }
643
+ else if (chartElement.hasAttribute('data-total-format')) {
644
+ keyTotal = chartElement.getAttribute('data-total-format').replace('{i}', keyTotal);
645
+ }
646
+ else {
647
+ keyTotal = new Intl.NumberFormat('en-GB').format(keyTotal);
648
+ }
649
+ key.innerHTML += `<span class="chart__total"><span class="chart__total__number"><span class="visually-hidden">Total: </span>${keyTotal}</span><span class="chart__total__percent"><span class="visually-hidden">Total percent: </span>${keyPercent}%</span></span>`;
650
+ });
651
+ }
652
+ // #endregion
653
+ export default setupChart;