@iamproperty/components 5.5.1-beta-1 → 5.5.1-beta-4

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