@massimo-cassandro/minimo 1.18.0 → 1.18.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 (50) hide show
  1. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/index.js +92 -0
  2. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/hbars.js +356 -0
  3. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/pie.js +245 -0
  4. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/radial-bars.js +177 -0
  5. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/rating-display.js +835 -0
  6. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/costruzione-assi.js +212 -0
  7. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/costruzione-linee.js +101 -0
  8. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/defaults.js +185 -0
  9. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/display-current.js +27 -0
  10. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/grid-areas.js +129 -0
  11. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/parse-opts.js +64 -0
  12. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/paths-setup.js +73 -0
  13. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile.js +126 -0
  14. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/signal-bars.js +290 -0
  15. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/week-utils.js +122 -0
  16. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/hbars-test.js +52 -0
  17. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/mcn-test.js +74 -0
  18. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/pie-test.js +16 -0
  19. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/radial-bars-test.js +48 -0
  20. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/settimanale-mensile-dati-settimanale.js +426 -0
  21. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/settimanale-mensile-test.js +260 -0
  22. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/signal-bars-test.js +64 -0
  23. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/node-test.mjs +86 -0
  24. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/hBars-node-test.mjs +40 -0
  25. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/lines-node-test.mjs +73 -0
  26. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/mcn-node-test.mjs +64 -0
  27. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/pie-node-test.mjs +12 -0
  28. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/radialsBar-node-test.mjs +44 -0
  29. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/settimanale-mensile-node-test.mjs +258 -0
  30. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/signalBars-node-test.mjs +36 -0
  31. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/test-readme.md +24 -0
  32. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/textToPath-test.mjs +14 -0
  33. package/charts/TODO charts/???/InfoBox.js +456 -0
  34. package/charts/TODO charts/???/README-upg OLD/342/206/222NEW.md" +53 -0
  35. package/charts/TODO charts/???/README.md +1 -0
  36. package/charts/TODO charts/???/TODO.md +39 -0
  37. package/charts/TODO charts/???/base-cfg.js +66 -0
  38. package/charts/TODO charts/???/rating-displayOLD.js +837 -0
  39. package/charts/TODO charts/docs/TODO.md +1 -0
  40. package/charts/TODO charts/docs/costruzione display rating.md +145 -0
  41. package/charts/TODO charts/test/node-test.mjs +92 -0
  42. package/charts/TODO charts/test/test-readme.md +23 -0
  43. package/charts/TODO charts/test/test-src/rating-display-extra-test.js +132 -0
  44. package/charts/TODO charts/test/test-src/rating-display-test.js +155 -0
  45. package/charts/TODO charts/test/textToPath-test.mjs +14 -0
  46. package/charts/TODO.md +1 -1
  47. package/package.json +1 -1
  48. package/snippets/flash-messages.html.twig +34 -0
  49. package/src/components/unsplash-page/unsplash-page.js +6 -8
  50. package/src/components/unsplash-page/unsplash-page.module.css +11 -6
@@ -0,0 +1,837 @@
1
+ /**
2
+ * rating display OLD
3
+ *
4
+ * Mostra un indicatore tipo contachilometri su una scala da 1 a 4 o 5
5
+ */
6
+
7
+ import { parseContainer, toRadians } from '../src/chart-utils.js';
8
+ import { createTextEl } from '../src/create-text-element.js';
9
+
10
+ export async function ratingDisplay({
11
+
12
+ // SECTION args
13
+
14
+ /** container (selettore o elemento DOM), se null viene restituito il codice SVG */
15
+ container = null,
16
+
17
+
18
+ /** eventuali classi da aggiungere all'elemento svg */
19
+ svgAttrs = {},
20
+
21
+ /** modalità debug, se attiva mostra dei riferimenti grafici */
22
+ debug = false,
23
+
24
+ /**
25
+ valore da rappresentare
26
+ in base a questo viene assegnato il posizionamento rispetto alla scala:
27
+ 1-1,99 : prima fascia
28
+ 2-2,99 : seconda ...
29
+ La scala parte da 1
30
+ L'ultima porzione rappresenta tutti i valori >= 4 per la versione a 4 porzioni
31
+ e i valori >= 5 per l'altra
32
+ */
33
+ value = null,
34
+
35
+ /**
36
+ Valore di partenza della scala
37
+ */
38
+ scaleStartValue = 0,
39
+
40
+ /**
41
+ se true, l'asticella viene posizionata al centro del colore corrispondente
42
+ se false, viene posizionata in proporzione al valore effettivo
43
+ */
44
+ rodPositionForceCenter = false,
45
+
46
+ /**
47
+ fill, traccia e classe dello sfondo del display principale
48
+ oggetto analogo a
49
+ {
50
+ fill: null,
51
+ strokeWidth: null,
52
+ strokeColor: null,
53
+ className: null,
54
+ }
55
+ */
56
+ displayBackgroundAttrs = {
57
+ fill: 'none',
58
+ },
59
+
60
+ /** numero di porzioni del tachimetro */
61
+ portions = 5, // 3, 4 o 5
62
+
63
+ /**
64
+ colori da applicare alle porzioni del tachimetro. Se non impostati vengono usati quelli di base
65
+ I colori vengono assegnati agli elementi della scala secondo il loro ordine, a partire dal proimo a sinistra
66
+ Se un colore non è presente viene usato il nero (#000)
67
+ */
68
+ scaleColors = null,
69
+ scaleAttrs = {},
70
+
71
+ /**
72
+ segni di separazione degli elementi della scala
73
+ vengono aggiunti anche `stroke-width="1" fill="none" stroke-linecap="butt"`
74
+ se `scaleticksColor` è null, vengono usati gli stessi colori di `scaleColors`
75
+ */
76
+ scaleticksColor = null,
77
+
78
+ /**
79
+ asticella
80
+ */
81
+ rodAttrs = {fill: '#000'},
82
+
83
+ /**
84
+ etichetta principale
85
+ se presente è un array in cui ogni elemento corrisponde ad una riga (max 2)
86
+ dell'etichetta:
87
+
88
+ displayLabel: [
89
+ {
90
+ label: <text> || null,
91
+ font: obj || null, // oggetto analogo a chartInstance.defaults.font
92
+ fill: <color>,
93
+ fontFilePath: path // percorso al file del font, utilizzato se `this.textToPath === true`,
94
+ },
95
+ {...}
96
+ ]
97
+ */
98
+ displayLabel = null,
99
+
100
+ /** distanza della prima riga di etichetta dalla base dell'asticella */
101
+ displayLabelTopMargin = 4,
102
+
103
+ /** distanza tra le due righe dell'etichetta */
104
+ displayLabelRowGap = 2,
105
+
106
+
107
+ // =>> minidisplay
108
+ /**
109
+ minidisplay
110
+ se impostato è un array di uno o due elementi, in cui ognuno di questo
111
+ è un oggetto così impostato:
112
+
113
+ miniDisplay = [
114
+ {
115
+ position: 'sx' | 'dx' (aliases: left | right),
116
+ value: number,
117
+ type: 'gauge' || 'value' // gauge = mini tachimetro, value: mostra il valore indicato in value
118
+ typeValueFont: obj || null // oggetto analogo a quelli definito in displayLabel per la rappresentazione
119
+ // di `type: value`. Se typeValueFont.label non è presente, viene utilizzato
120
+ // `value` (arrotondato all'intero)
121
+ mdLabel: array || null // array di oggetti (uno per ogni riga di etichetta) analogo a quello
122
+ // indicato nel display principale (displayLabel)
123
+
124
+ mdArc: { // oggetto simile a displayBackgroundAttrs, è relativo all'arco che avvolge il mini display
125
+ fill: null,
126
+ strokeWidth: null,
127
+ strokeColor: null,
128
+ className: null,
129
+ }
130
+ }
131
+ ...
132
+ ],
133
+
134
+ se
135
+ */
136
+ miniDisplay = null,
137
+
138
+ /** distanza dalla base del display principale */
139
+ miniDisplayTopMargin = 0,
140
+
141
+ /** distanza della prima riga di etichette dalla base del minidisplay */
142
+ miniDisplayLabelTopMargin = 0,
143
+
144
+ /** distanza tra le due righe dell'etichetta */
145
+ miniDisplayLabelRowGap = 2,
146
+
147
+
148
+ animation = false,
149
+ // animation_ms = 100, // number
150
+ // animation_fps = 60, // number
151
+
152
+ // !SECTION
153
+
154
+ }) {
155
+
156
+ const chartInstance = this;
157
+
158
+ try {
159
+
160
+ scaleColors??= chartInstance.defaults.colors;
161
+
162
+ // 30 gradi in radianti
163
+ // ampiezza dell'angolo della parte eccedente il raggio della circonferenza che include l'arco di cerchio
164
+ const rad30 = toRadians(30),
165
+
166
+ // assicura che il valore sia entro i limiti gestiti dal grafico
167
+ getSanitizedColorIndex = value => Math.max(
168
+ 0,
169
+ Math.min(
170
+ portions - 1,
171
+ Math.floor(value) - scaleStartValue
172
+ )
173
+ ),
174
+ getColorFromValue = value => scaleColors[getSanitizedColorIndex(value)]
175
+ ;
176
+
177
+ // =>> sizes
178
+
179
+ const sizes = {
180
+ // display principale
181
+ mainDisplay: {
182
+
183
+
184
+ // TODO il raggio deve considerare lo spessore della traccia per la scala non è costruita dinamicamente
185
+ // ripristinare il raggio statico se la scala (e l'asticella) vengono generate dinamicamente
186
+
187
+ // r: 110,
188
+ get r() {
189
+ return 110 - ((displayBackgroundAttrs?.strokeWidth??0) / 2);
190
+ },
191
+
192
+ get w() {
193
+ return this.r * 2 +
194
+ // spessore traccia arco esterno, se presente
195
+ (displayBackgroundAttrs?.strokeWidth??0);
196
+ },
197
+ /*
198
+ altezza dell'area del display
199
+ è pari al raggio + la distanza corrispondente ad uno spostamento di 30°
200
+ (l'arco di cerchio dell'area display è di 240°),
201
+ distanza che corrisponde al centro di rotazione dell'asticella.
202
+ La base dell'area del display principale è quindi la corda posizionata agli estremi dell'arco
203
+ */
204
+ get h() { return this.r + ( this.r * Math.sin(rad30) ) +
205
+ // spessore traccia arco esterno, se presente
206
+ ((displayBackgroundAttrs?.strokeWidth??0) / 2);
207
+ },
208
+
209
+ /*
210
+ centro di rotazione dell'asticella
211
+ corrisponde al centro della circonferenza che include l'arco dell'area display
212
+ viene considerato anche l'eventuale spessore dell'arco
213
+ */
214
+ get cx() {
215
+ return this.r + ((displayBackgroundAttrs?.strokeWidth??0) / 2);
216
+ },
217
+ get cy() {
218
+ return this.r + ((displayBackgroundAttrs?.strokeWidth??0) / 2);
219
+ }
220
+ }
221
+ };
222
+
223
+ // =>> dimensione dell'intero svg
224
+ sizes.mainSvg = {
225
+ w: sizes.mainDisplay.w,
226
+ h: sizes.mainDisplay.h // altezza provvisoria, andrà ricalcolata dopo il rendering dei miniDisplay
227
+ };
228
+
229
+ // =>> verifiche e impostazioni di base
230
+ if(value == null || typeof value !== 'number') {
231
+ throw '`value` is requested and must be a number';
232
+ }
233
+ if([3,4,5].indexOf(portions) === -1) {
234
+ throw '`portions` must be 3, 4 or 5';
235
+ }
236
+
237
+ value = Math.min(value, portions);
238
+
239
+ /*
240
+ la scala parte da 1 e arriva a `portions` (viene tolta una piccola
241
+ porzione finale per maggiore precisione)
242
+ */
243
+ const parsedDisplayValue = rodPositionForceCenter? Math.floor(value) + .499 : value;
244
+
245
+
246
+ // =>> animazione e posizione iniziale dell'asticella (rod)
247
+ let rotazioneAsticella = 0;
248
+
249
+ if(animation) {
250
+
251
+ // TODO animazione
252
+ // const display_animation = () => {
253
+ // const runAnimation = () => {
254
+
255
+ // const animationStepValue = parsedDisplayValue / animation_ms;
256
+
257
+ // let fpsInterval = animation_ms / animation_fps,
258
+ // currentDisplayValue = scaleStartValue, // valore iniziale. La scala parte da 1
259
+ // startTimestamp = Date.now(),
260
+ // animationEnd = false;
261
+
262
+ // const step = () => {
263
+ // let now = Date.now(),
264
+ // elapsed = now - startTimestamp;
265
+
266
+ // currentDisplayValue = Math.min(currentDisplayValue, parsedDisplayValue);
267
+
268
+ // if(currentDisplayValue >= parsedDisplayValue) {
269
+ // animationEnd = true;
270
+ // }
271
+
272
+ // // request another frame
273
+ // const animationRequest = window.requestAnimationFrame(step);
274
+
275
+ // // if enough time has elapsed, draw the next frame
276
+ // if (elapsed >= fpsInterval) {
277
+
278
+ // // Get ready for next frame by setting startTimestamp=now, but...
279
+ // // Also, adjust for fpsInterval not being multiple of 16.67
280
+ // startTimestamp = now - (elapsed % fpsInterval);
281
+
282
+ // svgElement.style.setProperty(
283
+ // '--rd-rot',
284
+ // Math.max(0, ((currentDisplayValue - scaleStartValue) * 240) / (portions - .001))
285
+ // );
286
+
287
+ // if(animationEnd) {
288
+ // window.cancelAnimationFrame(animationRequest);
289
+
290
+ // } else {
291
+ // currentDisplayValue += animationStepValue;
292
+ // // easing out a 2/3 del valore
293
+ // // if(currentDisplayValue >= parsedDisplayValue * 2/3) {
294
+ // // fpsInterval += 1;
295
+ // // }
296
+
297
+ // }
298
+ // }
299
+
300
+ // };// end step
301
+
302
+ // step();
303
+ // }; // end animazione_arco
304
+
305
+ // runAnimation();
306
+
307
+ // }; // end display_animation
308
+
309
+ // // NB la mutazione non è rilevata se la card è già visibile nel viewport
310
+ // // per questo è presente l'if iniziale
311
+ // if(inViewport) {
312
+ // display_animation();
313
+ // }
314
+
315
+ } else {
316
+ rotazioneAsticella = Math.max(0, ((parsedDisplayValue - scaleStartValue) * 240) / (portions - .001));
317
+ }
318
+
319
+ // container element
320
+ parseContainer(container);
321
+
322
+ // =>> definizione SVG
323
+ const svgCanvas = chartInstance.createSvgCanvas(container);
324
+ svgCanvas
325
+ // .viewbox(0, 0, sizes.mainSvg.w, sizes.mainSvg.h)
326
+ // .size(sizes.mainSvg.w, sizes.mainSvg.h)
327
+ .attr({
328
+ fill: 'none',
329
+ ...svgAttrs
330
+ });
331
+
332
+
333
+ // =>> gruppo Display Principale
334
+ const mainDisplayGroup = svgCanvas.group()
335
+ .attr({
336
+ 'data-debug-info': debug? 'Gruppo Display Principale' : null,
337
+ });
338
+
339
+
340
+ // =>> function getDisplayArcPath
341
+ const getDisplayArcPath = (radius, cx, cy) => {
342
+ const startPointX = cx - (radius * Math.cos(rad30)),
343
+ startPointY = cy + (radius * Math.sin(rad30)),
344
+
345
+ endPointX = cx + (radius * Math.cos(rad30)),
346
+ endPointY = cy + (radius * Math.sin(rad30));
347
+
348
+ return `M ${startPointX},${startPointY} ` +
349
+ // rx ry x-axis-angle large-arc-flag sweep-flag end-x end-y
350
+ `A ${radius} ${radius} 0 1 1 ${endPointX} ${endPointY}`;
351
+ };
352
+
353
+ // =>> display principale
354
+ mainDisplayGroup.path(
355
+ // 'M14.5589,164.7274C5.2967,148.6096,0,129.9234,0,110,0,49.2487,49.2487,0,110,0s110,49.2487,110,110c0,20.0586-5.3689,38.8632-14.748,55.0552l-190.6931-.3278Z'
356
+ getDisplayArcPath(sizes.mainDisplay.r, sizes.mainDisplay.cx, sizes.mainDisplay.cy)
357
+ )
358
+ .attr({
359
+ 'data-debug-info': debug? 'area display principale' : null,
360
+ class: displayBackgroundAttrs?.className, // se il valore è null o undefined l'attributo viene ignorato
361
+ fill: displayBackgroundAttrs?.fill?? 'none',
362
+ stroke: displayBackgroundAttrs?.strokeColor,
363
+ 'stroke-width': displayBackgroundAttrs?.strokeWidth,
364
+ 'stroke-linecap': displayBackgroundAttrs?.strokeWidth? 'butt' : null,
365
+ });
366
+
367
+
368
+ // TODO generare dinamicamente
369
+ // =>> portions & ticks
370
+
371
+ let ticks_paths, scale_paths;
372
+ if(portions === 3) {
373
+ ticks_paths = [
374
+ 'M44.32 27.94L56.5 42.53',
375
+ 'M179.45 31.06L158.99 55.29'
376
+ ];
377
+
378
+ scale_paths = [
379
+ 'M53.26,38.62c0.56-0.33,1.1-0.68,1.67-1.01c16.15-9.16,33.99-12.88,51.31-11.75c2.11-0.01,2.04,0.12,4,0.35c18.01,1.96,35.26,9.19,49.25,21.01l16.38-19.45c-18.69-14.98-41.94-23.05-65.63-23.08c-1.63-0.02-2.06,0.14-4,0.07C89.69,5.37,73.04,9.91,57.69,18.77c-4.76,2.75-9.22,5.83-13.37,9.17L53.26,38.62z',
380
+ 'M40.49,31.15c-8.57,7.59-15.68,16.4-21.22,26.01c0,0,0,0-1.93,3.51C1.09,91.51,0.54,129.77,19.26,162.2l-0.04-0.31c-14.08-33.52-12-69.73,5.94-96.72c0.87-1.42,2.3-3.29,2.3-3.29C33.22,54.05,40.41,47.1,49,41.32L40.49,31.15z',
381
+ 'M201.15,57.25L201.15,57.25l-2.07-3.42c-5.46-8.7-12.02-16.34-19.38-22.84l-16.48,19.57c5.04,4.76,9.58,10.17,13.49,16.18l2.11,3.4c0.01,0.01,0.02,0.02,0.02,0.04c11.4,19.83,12.28,45,0.02,66.23c-1.95,3.38-4.15,6.52-6.57,9.42l28.91,16.28C219.23,130.73,220.53,90.86,201.15,57.25z'
382
+ ];
383
+
384
+ } else if(portions === 4) {
385
+ ticks_paths = [
386
+ 'M31.8145,64.3924l-12.5627-7.253',
387
+ 'M110.2255,31.2336V4.6702',
388
+ 'M174.4747,72.6224l26.6597-15.3919'
389
+ ];
390
+
391
+ scale_paths = [
392
+ 'M25.1449,65.1604l-7.8217-4.5158C1.0679,91.4936,.5203,129.7554,19.2431,162.1841l-.0406-.3084c-14.0813-33.5175-11.9978-69.7327,5.9424-96.7153Z',
393
+ 'M106.2255,4.745c-16.5541,.6089-33.2051,5.1472-48.5497,14.0065-16.5527,9.5566-29.5166,22.9407-38.4241,38.3877l8.1965,4.7322c7.0425-9.5853,16.1881-17.8798,27.4637-24.2736,16.1538-9.16,33.9923-12.8843,51.3135-11.7545V4.745Z',
394
+ 'M199.0645,53.8066C179.2813,22.2909,145.2283,4.7195,110.2255,4.6702V26.1936c26.4244,2.8778,51.228,17.0771,66.4696,40.528l22.3694-12.915Z',
395
+ 'M201.1344,57.2305l-22.3293,12.8918c.0073,.0126,.0153,.0248,.0227,.0374,11.402,19.8312,12.2809,44.9971,.02,66.2336-1.9507,3.3787-4.1538,6.5209-6.5719,9.4163l28.9064,16.2815c18.0334-31.3837,19.3337-71.2517-.0479-104.8605Z'
396
+ ];
397
+
398
+ } else { // 5
399
+ ticks_paths = [
400
+ 'M22.1717,81.0456l-11.8401-3.8471',
401
+ 'M76.5125,33.9593l-9.0032-20.2213',
402
+ 'M140.9257,40.7459l12.0048-26.963',
403
+ 'M180.4367,86.9106l29.6283-9.6269'
404
+ ];
405
+
406
+ scale_paths = [
407
+ 'M15.9428,83.2275l-6.7733-2.2007c-7.4723,26.3461-4.7006,55.5677,10.0736,81.1573l-.0406-.3084c-11.1296-26.4916-12.1697-54.674-3.2596-78.6482Z',
408
+ 'M63.8892,15.4414c-2.0916,1.0298-4.1652,2.1275-6.2134,3.3101-23.385,13.5013-39.6072,34.641-47.3442,58.447l7.0957,2.3055c7.2439-17.1207,19.7513-31.8504,37.4847-41.9061,5.0895-2.886,10.3474-5.2258,15.7025-7.0513l-6.7253-15.1052Z',
409
+ 'M149.2476,12.2204C123.5877,1.9537,94.1199,1.8658,67.5094,13.7379l6.9424,15.593c21.8262-6.3824,44.9893-4.3134,64.9426,5.0201l9.8533-22.1306Z',
410
+ 'M184.0155,81.542l24.7449-8.04c-2.0469-5.5565-4.5906-11.0154-7.6519-16.3178-11.5244-19.9607-28.6157-34.6992-48.178-43.4012l-9.9482,22.3439c14.4784,7.5615,27.0454,19.0628,35.8455,34.0328,2.0891,3.6336,3.8213,7.4476,5.1877,11.3823Z',
411
+ 'M185.2117,85.3591c4.668,16.5795,2.9308,34.935-6.364,51.0341-1.9507,3.3787-4.1538,6.5209-6.5719,9.4163l28.9064,16.2815c14.5532-25.327,18.2002-56.1781,8.8828-84.8073l-24.8533,8.0754Z'
412
+ ];
413
+ }
414
+
415
+ const ticksGroup = mainDisplayGroup.group()
416
+ .attr({
417
+ 'data-debug-info': debug? 'ticks' : null,
418
+ fill: 'none',
419
+ 'stroke-width': .5,
420
+ 'stroke-linecap': 'butt',
421
+
422
+ });
423
+ ticks_paths.forEach((p, idx) => {
424
+ ticksGroup.path(p)
425
+ .attr({
426
+ stroke: scaleticksColor? scaleticksColor : scaleColors?.[idx + 1]?? '#000',
427
+ });
428
+ });
429
+
430
+ const scaleGroup = mainDisplayGroup.group().attr({
431
+ 'data-debug-info': debug? 'scales' : null,
432
+ stroke: 'none',
433
+ ...scaleAttrs
434
+ });
435
+ scale_paths.forEach((p, idx) => {
436
+ scaleGroup.path(p)
437
+ .attr({
438
+ fill: scaleColors?.[idx]?? '#000'
439
+ });
440
+ });
441
+
442
+
443
+ // =>> asticella
444
+ const rodGroup = mainDisplayGroup.group()
445
+ .attr({
446
+ 'data-debug-info': debug? 'asticella' : null,
447
+ ...rodAttrs
448
+ });
449
+
450
+ // centro
451
+
452
+ // versione statica
453
+ // rodGroup.path('M120.5713,103.6898c3.3137,5.7395,1.3472,13.0786-4.3923,16.3923-5.7395,3.3137-13.0786,1.3472-16.3923-4.3923s-1.3472-13.0786,4.3923-16.3923c5.7395-3.3137,13.0786-1.3472,16.3923,4.3923Zm-13.8923-.0622c-3.348,1.933-4.4952,6.2141-2.5622,9.5622s6.2141,4.4952,9.5622,2.5622,4.4952-6.2141,2.5622-9.5622-6.2141-4.4952-9.5622-2.5622Z');
454
+
455
+ // versione con path calcolato
456
+ // https://www.smashingmagazine.com/2019/03/svg-circle-decomposition-paths/
457
+ const ringPath = ({cx, cy, outerRadius, innerRadius}) => {
458
+
459
+ return `M ${cx - outerRadius/2}, ${cy}`+
460
+ `a ${outerRadius/2},${outerRadius/2} 0 1,0 ${outerRadius},0 ` +
461
+ `a ${outerRadius/2},${outerRadius/2} 0 1,0 -${outerRadius},0 ` +
462
+
463
+ `M ${cx - innerRadius/2}, ${cy} ` +
464
+ `a ${innerRadius/2},${innerRadius/2} 0 1,0 ${innerRadius},0 ` +
465
+ `a ${innerRadius/2},${innerRadius/2} 0 1,0 -${innerRadius},0`;
466
+ };
467
+
468
+ rodGroup.path(ringPath({
469
+ cx: sizes.mainDisplay.cx,
470
+ cy: sizes.mainDisplay.cy,
471
+ outerRadius: 24,
472
+ innerRadius: 14
473
+ }))
474
+ .attr({'fill-rule': 'evenodd'});
475
+
476
+ // bbox del solo anello di base dell'asticella
477
+ // usato per il posizionamento delle etichette
478
+ const rodGroupBaseBbox = rodGroup.bbox();
479
+
480
+ // asta
481
+ // TODO generare dinamicamente
482
+ rodGroup.polygon('59.1813 139.0943 59.1813 139.0943 98.8572 108.491 105.5448 120.0899 59.1813 139.0943')
483
+ .transform({
484
+
485
+ origin: {
486
+ x: sizes.mainDisplay.cx,
487
+ y: sizes.mainDisplay.cy,
488
+ },
489
+ rotate: rotazioneAsticella
490
+ })
491
+ .attr({
492
+ 'data-debug-info': debug? `Value: ${parsedDisplayValue}, color index: ${getSanitizedColorIndex(parsedDisplayValue)}, {Angolo: ${rotazioneAsticella}, Start value: ${scaleStartValue}` : null,
493
+ });
494
+
495
+
496
+ // =>> function setLabel
497
+ const setLabel = async ({
498
+ labelObj,
499
+ baseX,
500
+ baseY,
501
+ dy,
502
+ rowGap = 0, // ulteriore correzione tra le distanze delle due righe
503
+ parentEl = svgCanvas
504
+ }) => {
505
+ if(labelObj?.label) {
506
+
507
+ const [label_bbox, labelEl] = await createTextEl (chartInstance, {
508
+ text: labelObj.label,
509
+ textFill: labelObj.fill??= '#000',
510
+ fontAttr: labelObj.font??{},
511
+ fontFilePath: labelObj.fontFilePath,
512
+ parentElement: parentEl
513
+ });
514
+
515
+ // posizionamento Y
516
+ // dy è lo spostamento aggiuntivo pari all'altezza dell'eventuale riga precedente
517
+ const labelY = baseY + (dy? dy + rowGap : 0);
518
+
519
+ // posizionamento, al centro sotto l'asticella
520
+ labelEl.move(
521
+ baseX - label_bbox.width / 2,
522
+ labelY
523
+ )
524
+ .attr({
525
+ 'data-debug-info': debug? `label “${labelObj.label}”` : null
526
+ });
527
+
528
+ return label_bbox.height;
529
+
530
+ }
531
+ };
532
+
533
+ // =>> displayLabel
534
+ if(displayLabelTopMargin != null && typeof displayLabelTopMargin !== 'number') {
535
+ throw '`displayLabelTopMargin` must be null or a number';
536
+ }
537
+
538
+ displayLabel??=[];
539
+ if(displayLabel.length > 2) {
540
+ throw 'No more than 2 `displayLabel` elements can be defined';
541
+ }
542
+
543
+ let prevLabelHeight = 0;
544
+ for await (const labelObj of displayLabel) {
545
+
546
+ prevLabelHeight = await setLabel({
547
+ labelObj: labelObj,
548
+ baseX: sizes.mainDisplay.cx,
549
+ baseY: rodGroupBaseBbox.y2 + (displayLabelTopMargin??0),
550
+ dy: prevLabelHeight,
551
+ rowGap: displayLabelRowGap,
552
+ parentEl: mainDisplayGroup
553
+ });
554
+ }
555
+
556
+ // =>> ricalcolo altezza gruppo principale
557
+ const mainDisplayGroupBbox = mainDisplayGroup.bbox();
558
+ sizes.mainSvg.h = mainDisplayGroupBbox.y + mainDisplayGroupBbox.y2;
559
+
560
+ // SECTION miniDisplay
561
+ if(miniDisplayTopMargin != null && typeof miniDisplayTopMargin !== 'number') {
562
+ throw '`miniDisplayTopMargin` must be null or a number';
563
+ }
564
+
565
+ miniDisplay??=[];
566
+ if(miniDisplay.length > 2) {
567
+ throw 'No more than 2 `miniDisplay` elements can be defined';
568
+ }
569
+
570
+
571
+ let miniDisplayIdx = 1, miniDisplayMaxHeight = 0, mdArcHeight = null;
572
+ for await (const md of miniDisplay) {
573
+
574
+
575
+ const errorPrefix = `miniDisplay #${miniDisplayIdx}`;
576
+
577
+ // =>> controlli
578
+ ['position', 'type', 'value'].forEach(item => {
579
+ if(md[item] == null) {
580
+ throw `${errorPrefix}: '${item}' is requested`;
581
+ }
582
+ });
583
+
584
+ if(['sx', 'dx', 'left', 'right'].indexOf(md.position) === -1) {
585
+ throw `${errorPrefix}: 'position' must be one of 'sx', 'dx', 'left' or 'right'`;
586
+ }
587
+
588
+ if(typeof md.value !== 'number') {
589
+ throw `${errorPrefix}: 'value' must be a number`;
590
+ }
591
+
592
+ if(['gauge', 'value'].indexOf(md.type) === -1) {
593
+ throw `${errorPrefix}: 'type' must be one of 'gauge' or 'value'`;
594
+ }
595
+
596
+ // default
597
+ md.mdArc ??= {};
598
+ md.mdArc.strokeWidth ??= 0;
599
+
600
+
601
+ // =>> dimensioni
602
+ const
603
+ // raggio circonferenza che include l'arco
604
+ mdRadius = 26,
605
+
606
+ mdWidrh = (mdRadius * 2) + (md.mdArc?.strokeWidth??0), // impegna sia la parte destra che la sinistra
607
+
608
+ // margine orizzontale dal bordo esterno dell'SVG
609
+ xMargin = 20,
610
+
611
+ // coordinate dell'origine del miniDisplay
612
+ mdOrigin = {
613
+ y: sizes.mainSvg.h + (miniDisplayTopMargin?? 0),
614
+ x: (md.position === 'sx' || md.position === 'left')
615
+ ? xMargin
616
+ : sizes.mainSvg.w - xMargin - mdWidrh
617
+ }
618
+ ;
619
+
620
+
621
+ // =>> mdGroup
622
+ const mdGroup = svgCanvas.group()
623
+ .attr({
624
+ 'data-debug-info': debug? `miniDisplay #${miniDisplayIdx}` : null,
625
+ });
626
+
627
+ // =>> definizione arco
628
+ const
629
+ // coordinate del centro della circonferenza che include l'arco
630
+ mdArcCx= mdOrigin.x + mdRadius, // + md.mdArc.strokeWidth / 2,
631
+ mdArcCy= mdOrigin.y + mdRadius // + md.mdArc.strokeWidth / 2,
632
+ ;
633
+
634
+ // minidisplay arc
635
+ mdGroup.path(getDisplayArcPath(mdRadius, mdArcCx, mdArcCy))
636
+ .attr({
637
+ 'data-debug-info': debug? `miniDisplay #${miniDisplayIdx}: arco` : null,
638
+ class: md.mdArc.className, // se il valore è null o undefined l'attributo viene ignorato
639
+ fill: md.mdArc.fill?? 'none',
640
+ stroke: md.mdArc.strokeWidth > 0? md.mdArc.strokeColor : null,
641
+ 'stroke-width': md.mdArc.strokeWidth > 0? md.mdArc.strokeWidth : null,
642
+ 'stroke-linecap': md.mdArc.strokeWidth > 0? 'round' : null,
643
+ });
644
+
645
+ // altezza arco, per il posizionamento delle etichette
646
+ // viene misurato solo il primo arco, in modo che le etichette siano comq allineate
647
+ // se necessario, utilizzare il parametro miniDisplayLabelTopMargin per distanziare il testo dalla base dell'arco
648
+ if(mdArcHeight == null) {
649
+ mdArcHeight = mdGroup.bbox().height;
650
+ }
651
+
652
+
653
+ // =>> modalità "value"
654
+ if(md.type === 'value') {
655
+
656
+ md.typeValueFont ??= {};
657
+ md.typeValueFont.label ??= String(Math.floor(md.value));
658
+
659
+ const [value_bbox, valueEl] = await createTextEl (chartInstance, {
660
+ text: md.typeValueFont.label,
661
+ textFill: md.typeValueFont.fill??= '#000',
662
+ fontAttr: md.typeValueFont.font??{},
663
+ fontFilePath: md.typeValueFont.fontFilePath,
664
+ parentElement: mdGroup
665
+ });
666
+
667
+ valueEl.move(
668
+ mdArcCx - value_bbox.w/2,
669
+ mdArcCy - value_bbox.h/2 - 2, // 2: spostamento basline verso l'alto
670
+ )
671
+ .attr({
672
+ 'data-debug-info': debug? `miniDisplay #${miniDisplayIdx}: value` : null,
673
+ });
674
+
675
+ // =>> modalità "gauge"
676
+ } else {
677
+
678
+ // segmento indicatore colore
679
+ const segmentStrokeWidth = 6
680
+ // spazio segmento arco
681
+ ,distanzaSegmentoArco = 2
682
+ // correzione raggio per dare spazio allo spessore della traccia
683
+ ,segmentRadius = mdRadius - segmentStrokeWidth/2 - distanzaSegmentoArco
684
+ // ampiezza angolo che sottende il segmento circolare
685
+ // 30 è l'angolo del punto iniziale
686
+ ,segmentAngle = toRadians(240/portions - 30)
687
+
688
+ ,startPointX = mdArcCx - (segmentRadius * Math.cos(rad30))
689
+ ,startPointY = mdArcCy + (segmentRadius * Math.sin(rad30))
690
+ ,endPointX = mdArcCx - (segmentRadius * Math.cos(segmentAngle))
691
+ ,endPointY = mdArcCy - (segmentRadius * Math.sin(segmentAngle))
692
+
693
+ ,segmentPath = `M ${startPointX},${startPointY} ` +
694
+ // rx ry x-axis-angle large-arc-flag sweep-flag end-x end-y
695
+ `A ${segmentRadius} ${segmentRadius} 0 0 1 ${endPointX} ${endPointY}`
696
+
697
+ ,segmentGroup = mdGroup.group()
698
+ ;
699
+
700
+ // =>> segmento mini display gauge
701
+ segmentGroup.path(segmentPath)
702
+ .attr({
703
+ stroke: getColorFromValue(md.value),
704
+ 'stroke-width': segmentStrokeWidth,
705
+ });
706
+
707
+ // =>> anello mini asticella
708
+ const ringOuterRadius = 12;
709
+ mdGroup.path( ringPath({
710
+ cx: mdArcCx,
711
+ cy: mdArcCy,
712
+ outerRadius: ringOuterRadius,
713
+ innerRadius: 6
714
+ }))
715
+ .attr({'fill-rule': 'evenodd'})
716
+ .attr({
717
+ ...rodAttrs
718
+ });
719
+
720
+ // =>> mini asticella
721
+ // viene prima creata allineata con l'asse X del mini display e poi ruotata al centro della porzione d'inizio
722
+ // in seguito, la rotazione applicata al gruppo la porta nella posizione definitiva
723
+ segmentGroup.polygon(
724
+ `${mdArcCx - ringOuterRadius/2 + 2},${mdArcCy - 3} `+
725
+ `${mdArcCx - segmentRadius + 6},${mdArcCy} `+
726
+ `${mdArcCx - ringOuterRadius/2 + 2},${mdArcCy + 3}`
727
+ )
728
+ .attr({
729
+ ...rodAttrs
730
+ })
731
+ .transform({
732
+ origin: {
733
+ x: mdArcCx,
734
+ y: mdArcCy,
735
+ },
736
+ rotate: ((240/portions) / 2) - 30
737
+ })
738
+ ;
739
+
740
+ segmentGroup.transform({
741
+ origin: {
742
+ x: mdArcCx,
743
+ y: mdArcCy,
744
+ },
745
+ rotate: (240/portions) * getSanitizedColorIndex(md.value) - scaleStartValue
746
+ });
747
+ }
748
+
749
+ // =>> label mini display
750
+
751
+ if(miniDisplayLabelTopMargin != null && typeof miniDisplayLabelTopMargin !== 'number') {
752
+ throw `${errorPrefix}: 'miniDisplayLabelTopMargin' must be null or a number`;
753
+ }
754
+
755
+ md.mdLabel??=[];
756
+ if(md.mdLabel.length > 2) {
757
+ throw `${errorPrefix}: No more than 2 'mdLabel' elements can be defined`;
758
+ }
759
+
760
+
761
+ let prevLabelHeight = 0;
762
+ for await (const labelObj of md.mdLabel) {
763
+
764
+ prevLabelHeight = await setLabel({
765
+ labelObj: labelObj,
766
+ baseX: mdArcCx,
767
+ baseY: mdOrigin.y + mdArcHeight + (miniDisplayLabelTopMargin??0),
768
+ dy: prevLabelHeight,
769
+ rowGap: miniDisplayLabelRowGap,
770
+ parentEl: mdGroup
771
+ });
772
+ }
773
+
774
+
775
+
776
+ // =>> indicatori per debug
777
+ if(debug) {
778
+ // centro mini display
779
+ const gap = 10;
780
+ const debug_group = mdGroup.group()
781
+ .attr({
782
+ 'data-debug-info': 'centro mini display',
783
+ stroke: '#05923e',
784
+ fill: 'none',
785
+ 'stroke-width': .5,
786
+ class: 'debug-info'
787
+ });
788
+ debug_group.line({x1: mdArcCx - gap, y1: mdArcCy, x2: mdArcCx + gap, y2: mdArcCy});
789
+ debug_group.line({x1: mdArcCx, y1: mdArcCy - gap, x2: mdArcCx, y2: mdArcCy + gap});
790
+
791
+ // origine mini display
792
+ // debug_group.line({x1: mdOrigin.x - gap, y1: mdOrigin.y, x2: mdOrigin.x + gap, y2: mdOrigin.y});
793
+ // debug_group.line({x1: mdOrigin.x, y1: mdOrigin.y - gap, x2: mdOrigin.x, y2: mdOrigin.y + gap});
794
+ }
795
+
796
+ // =>> ricalcolo miniDisplayMaxHeight
797
+ miniDisplayMaxHeight = Math.max(
798
+ miniDisplayMaxHeight,
799
+ mdGroup.bbox().h + (miniDisplayTopMargin?? 0) + md.mdArc.strokeWidth // strokeWidth: spessore traccia nel caso non ci sia testo
800
+ );
801
+ miniDisplayIdx++;
802
+
803
+ }
804
+
805
+ // !SECTION end miniDisplay
806
+
807
+ // =>> aggiornamento dimensioni SVG
808
+ sizes.mainSvg.h += miniDisplayMaxHeight;
809
+ // sizes.mainSvg.w = mainDisplayGroupBbox.w;
810
+
811
+ svgCanvas.viewbox(0, 0, sizes.mainSvg.w, sizes.mainSvg.h);
812
+
813
+
814
+ // indicatori per debug
815
+ if(debug) {
816
+ // centro display e rotazione asticella
817
+ const gap = sizes.mainDisplay.r;
818
+ const debug_group1 = svgCanvas.group()
819
+ .attr({
820
+ 'data-debug-info': 'centro rotazione asticella',
821
+ stroke: '#e20613',
822
+ fill: 'none',
823
+ 'stroke-width': .5,
824
+ class: 'debug-info'
825
+ });
826
+ debug_group1.line({x1: sizes.mainDisplay.cx - gap, y1: sizes.mainDisplay.cy, x2: sizes.mainDisplay.cx + gap, y2: sizes.mainDisplay.cy});
827
+ debug_group1.line({x1: sizes.mainDisplay.cx, y1: sizes.mainDisplay.cy - gap, x2: sizes.mainDisplay.cx, y2: sizes.mainDisplay.cy + gap});
828
+ }
829
+
830
+ if(!container) {
831
+ return svgCanvas.svg();
832
+ }
833
+
834
+ } catch(e) {
835
+ console.error( 'AdaCharts → ratingDisplay:', e ); // eslint-disable-line
836
+ }
837
+ }