@massimo-cassandro/minimo 1.18.1 → 1.18.3

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 (63) hide show
  1. package/README.md +2 -1
  2. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/index.js +92 -0
  3. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/hbars.js +356 -0
  4. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/pie.js +245 -0
  5. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/radial-bars.js +177 -0
  6. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/rating-display.js +835 -0
  7. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/costruzione-assi.js +212 -0
  8. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/costruzione-linee.js +101 -0
  9. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/defaults.js +185 -0
  10. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/display-current.js +27 -0
  11. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/grid-areas.js +129 -0
  12. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/parse-opts.js +64 -0
  13. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile-src/paths-setup.js +73 -0
  14. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/settimanale-mensile.js +126 -0
  15. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/charts/signal-bars.js +290 -0
  16. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/src/week-utils.js +122 -0
  17. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/hbars-test.js +52 -0
  18. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/mcn-test.js +74 -0
  19. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/pie-test.js +16 -0
  20. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/radial-bars-test.js +48 -0
  21. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/settimanale-mensile-dati-settimanale.js +426 -0
  22. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/settimanale-mensile-test.js +260 -0
  23. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/browser-test/src/signal-bars-test.js +64 -0
  24. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/node-test.mjs +86 -0
  25. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/hBars-node-test.mjs +40 -0
  26. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/lines-node-test.mjs +73 -0
  27. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/mcn-node-test.mjs +64 -0
  28. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/pie-node-test.mjs +12 -0
  29. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/radialsBar-node-test.mjs +44 -0
  30. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/settimanale-mensile-node-test.mjs +258 -0
  31. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/node-test/src/signalBars-node-test.mjs +36 -0
  32. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/test-readme.md +24 -0
  33. package/charts/TODO charts/1a-vers DA RIFATTORIZZARE/test/textToPath-test.mjs +14 -0
  34. package/charts/TODO charts/???/InfoBox.js +456 -0
  35. package/charts/TODO charts/???/README-upg OLD/342/206/222NEW.md" +53 -0
  36. package/charts/TODO charts/???/README.md +1 -0
  37. package/charts/TODO charts/???/TODO.md +39 -0
  38. package/charts/TODO charts/???/base-cfg.js +66 -0
  39. package/charts/TODO charts/???/rating-displayOLD.js +837 -0
  40. package/charts/TODO charts/docs/TODO.md +1 -0
  41. package/charts/TODO charts/docs/costruzione display rating.md +145 -0
  42. package/charts/TODO charts/test/node-test.mjs +92 -0
  43. package/charts/TODO charts/test/test-readme.md +23 -0
  44. package/charts/TODO charts/test/test-src/rating-display-extra-test.js +132 -0
  45. package/charts/TODO charts/test/test-src/rating-display-test.js +155 -0
  46. package/charts/TODO charts/test/textToPath-test.mjs +14 -0
  47. package/charts/TODO.md +1 -1
  48. package/design-tokens/tokens/{design-tokens/_src → src/components/modal-popup}/modal-popup.minimo.tokens.jsonc +48 -2
  49. package/design-tokens/utilities/build-tokens-src/config.mjs +5 -2
  50. package/design-tokens/utilities/build-tokens-src/formats/penpot.mjs +1 -1
  51. package/design-tokens/utilities/build-tokens-src/platforms.mjs +4 -1
  52. package/design-tokens/utilities/build-tokens.mjs +5 -3
  53. package/design-tokens/utilities/check-unresolved-custom-props.mjs +5 -2
  54. package/dev-tools/starter-kit/webpack.config.mjs +6 -6
  55. package/package.json +17 -10
  56. package/snippets/flash-messages.html.twig +34 -0
  57. package/src/components/modal-alert/modal-alert.js +3 -1
  58. package/src/components/modal-popup/modal-popup.js +68 -4
  59. package/{design-tokens/_src → src/components/modal-popup}/modal-popup.minimo.tokens.mjs +51 -2
  60. package/src/components/modal-popup/modal-popup.module.css +24 -32
  61. package/src/components/unsplash-page/unsplash-page.js +3 -6
  62. package/src/components/unsplash-page/unsplash-page.module.css +11 -6
  63. package/src/custom-properties.css +21 -12
@@ -0,0 +1,835 @@
1
+ /**
2
+ * rating display 1a vers
3
+ *
4
+ * Mostra un indicatore tipo contachilometri su una scala da 1 a 4 o 5
5
+ */
6
+
7
+
8
+ export async function ratingDisplay({
9
+
10
+ // SECTION args
11
+
12
+ /** container (selettore o elemento DOM), se null viene restituito il codice SVG */
13
+ container = null,
14
+
15
+ /** se true aggiunge width e height */
16
+ addSvgSize = true,
17
+
18
+
19
+ /** eventuali classi da aggiungere all'elemento svg */
20
+ svgClassName = null,
21
+
22
+ /** modalità debug, se attiva mostra dei riferimenti grafici */
23
+ debug = false,
24
+
25
+ /**
26
+ valore da rappresentare
27
+ in base a questo viene assegnato il posizionamento rispetto alla scala:
28
+ 1-1,99 : prima fascia
29
+ 2-2,99 : seconda ...
30
+ La scala parte da 1
31
+ L'ultima porzione rappresenta tutti i valori >= 4 per la versione a 4 porzioni
32
+ e i valori >= 5 per l'altra
33
+ */
34
+ displayValue = null,
35
+
36
+ /**
37
+ Valore di partenza della scala
38
+ */
39
+ displayScaleStartValue = 0,
40
+
41
+ /**
42
+ se true, l'asticella viene posizionata al centro del colore corrispondente
43
+ se false, viene posizionata in proporzione al valore effettivo
44
+ */
45
+ displayValueForceCenter = false,
46
+
47
+ /**
48
+ fill, traccia e classe dello sfondo del display principale
49
+ oggetto analogo a
50
+ {
51
+ fill: null,
52
+ strokeWidth: null,
53
+ strokeColor: null,
54
+ className: null,
55
+ }
56
+ */
57
+ displayArc = null,
58
+
59
+ /** numero di porzioni del tachimetro */
60
+ scale = 5, // 4 o 5
61
+
62
+ /**
63
+ colori da applicare alle porzioni del tachimetro. Se non impostati vengono usati quelli di base
64
+ I colori vengono assegnati agli elementi della scala secondo il loro ordine, a partire dal proimo a sinistra
65
+ Se un colore non è presente viene usato il nero (#000)
66
+ */
67
+ scaleColors = null,
68
+
69
+ /** eventuale classe da applicare agli elementi della scala */
70
+ scaleClassName = null,
71
+
72
+ /**
73
+ segni di separazione degli elementi della scala
74
+ vengono aggiunti anche `stroke-width="1" fill="none" stroke-linecap="butt"`
75
+ se `scaleticksColor` è null, vengono usati gli stessi colori di `scaleColors`
76
+ */
77
+ scaleticksColor = null,
78
+
79
+ /**
80
+ asticella
81
+ */
82
+ rodColor = '#000',
83
+ rodClassName = null,
84
+
85
+ /**
86
+ etichetta principale
87
+ se presente è un array in cui ogni elemento corrisponde ad una riga (max 2)
88
+ dell'etichetta:
89
+
90
+ displayLabel: [
91
+ {
92
+ label: <text> || null,
93
+ font: obj || null, // oggetto analogo a this.defaults.font
94
+ fill: <color>,
95
+ fontFilePath: path // percorso al file del font, utilizzato se `this.textToPath === true`,
96
+ },
97
+ {...}
98
+ ]
99
+ */
100
+ displayLabel = null,
101
+
102
+ /** distanza della prima riga di etichetta dalla base dell'asticella */
103
+ displayLabelTopMargin = 4,
104
+
105
+ /** distanza tra le due righe dell'etichetta */
106
+ displayLabelRowGap = 2,
107
+
108
+
109
+ // =>> minidisplay
110
+ /**
111
+ minidisplay
112
+ se impostato è un array di uno o due elementi, in cui ognuno di questo
113
+ è un oggetto così impostato:
114
+
115
+ miniDisplay = [
116
+ {
117
+ position: 'sx' | 'dx' (aliases: left | right),
118
+ value: number,
119
+ type: 'gauge' || 'value' // gauge = mini tachimetro, value: mostra il valore indicato in value
120
+ typeValueFont: obj || null // oggetto analogo a quelli definito in displayLabel per la rappresentazione
121
+ // di `type: value`. Se typeValueFont.label non è presente, viene utilizzato
122
+ // `value` (arrotondato all'intero)
123
+ mdLabel: array || null // array di oggetti (uno per ogni riga di etichetta) analogo a quello
124
+ // indicato nel display principale (displayLabel)
125
+
126
+ mdArc: { // oggetto simile a displayArc, è relativo all'arco che avvolge il mini display
127
+ fill: null,
128
+ strokeWidth: null,
129
+ strokeColor: null,
130
+ className: null,
131
+ }
132
+ }
133
+ ...
134
+ ],
135
+
136
+ se
137
+ */
138
+ miniDisplay = null,
139
+
140
+ /** distanza dalla base del display principale */
141
+ miniDisplayTopMargin = 0,
142
+
143
+ /** distanza della prima riga di etichette dalla base del minidisplay */
144
+ miniDisplayLabelTopMargin = 0,
145
+
146
+ /** distanza tra le due righe dell'etichetta */
147
+ miniDisplayLabelRowGap = 2,
148
+
149
+
150
+ animation = false,
151
+ // animation_ms = 100, // number
152
+ // animation_fps = 60, // number
153
+
154
+ // !SECTION
155
+
156
+ }) {
157
+
158
+ try {
159
+
160
+ scaleColors??= this.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 = this.utils.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
+ scale - 1,
171
+ Math.floor(value) - displayScaleStartValue
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 - ((displayArc?.strokeWidth??0) / 2);
190
+ },
191
+
192
+ get w() {
193
+ return this.r * 2 +
194
+ // spessore traccia arco esterno, se presente
195
+ (displayArc?.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
+ ((displayArc?.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 + ((displayArc?.strokeWidth??0) / 2);
216
+ },
217
+ get cy() {
218
+ return this.r + ((displayArc?.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(displayValue == null || typeof displayValue !== 'number') {
231
+ throw '`displayValue` is requested and must be a number';
232
+ }
233
+ if([4,5].indexOf(scale) === -1) {
234
+ throw '`scale` must be 4 or 5';
235
+ }
236
+
237
+ displayValue = Math.min(displayValue, scale + .99);
238
+
239
+ /*
240
+ la scala parte da 1 e arriva a `scale` (viene tolta una piccola
241
+ porzione finale per maggiore precisione)
242
+ */
243
+ const parsedDisplayValue = displayValueForceCenter? Math.floor(displayValue) + .499 : displayValue;
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 = displayScaleStartValue, // 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 - displayScaleStartValue) * 240) / (scale - .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 - displayScaleStartValue) * 240) / (scale - .001));
317
+ }
318
+
319
+ // container element
320
+ this.utils.parseContainer(container);
321
+
322
+ // =>> definizione SVG
323
+ const svgCanvas = this.createSvgCanvas(container);
324
+ svgCanvas
325
+ // .viewbox(0, 0, sizes.mainSvg.w, sizes.mainSvg.h)
326
+ // .size(sizes.mainSvg.w, sizes.mainSvg.h)
327
+ .fill('none')
328
+ ;
329
+
330
+ if(svgClassName) {
331
+ svgCanvas.addClass(svgClassName);
332
+ }
333
+
334
+
335
+ // =>> gruppo Display Principale
336
+ const mainDisplayGroup = svgCanvas.group()
337
+ .attr({
338
+ 'data-debug-info': debug? 'Gruppo Display Principale' : null,
339
+ });
340
+
341
+
342
+ // =>> function getDisplayArcPath
343
+ const getDisplayArcPath = (radius, cx, cy) => {
344
+ const startPointX = cx - (radius * Math.cos(rad30)),
345
+ startPointY = cy + (radius * Math.sin(rad30)),
346
+
347
+ endPointX = cx + (radius * Math.cos(rad30)),
348
+ endPointY = cy + (radius * Math.sin(rad30));
349
+
350
+ return `M ${startPointX},${startPointY} ` +
351
+ // rx ry x-axis-angle large-arc-flag sweep-flag end-x end-y
352
+ `A ${radius} ${radius} 0 1 1 ${endPointX} ${endPointY}`;
353
+ };
354
+
355
+ // =>> display principale
356
+ mainDisplayGroup.path(
357
+ // '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'
358
+ getDisplayArcPath(sizes.mainDisplay.r, sizes.mainDisplay.cx, sizes.mainDisplay.cy)
359
+ )
360
+ .attr({
361
+ 'data-debug-info': debug? 'area display principale' : null,
362
+ class: displayArc?.className, // se il valore è null o undefined l'attributo viene ignorato
363
+ fill: displayArc?.fill?? 'none',
364
+ stroke: displayArc?.strokeColor,
365
+ 'stroke-width': displayArc?.strokeWidth,
366
+ 'stroke-linecap': displayArc?.strokeWidth? 'butt' : null,
367
+ });
368
+
369
+
370
+ // TODO generare dinamicamente
371
+ // =>> scale & ticks
372
+
373
+ let ticks_paths, scale_paths;
374
+ if(scale === 4) {
375
+ ticks_paths = [
376
+ 'M31.8145,64.3924l-12.5627-7.253',
377
+ 'M110.2255,31.2336V4.6702',
378
+ 'M174.4747,72.6224l26.6597-15.3919'
379
+ ];
380
+
381
+ scale_paths = [
382
+ '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',
383
+ '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',
384
+ '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',
385
+ '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'
386
+ ];
387
+
388
+ } else { // 5
389
+ ticks_paths = [
390
+ 'M22.1717,81.0456l-11.8401-3.8471',
391
+ 'M76.5125,33.9593l-9.0032-20.2213',
392
+ 'M140.9257,40.7459l12.0048-26.963',
393
+ 'M180.4367,86.9106l29.6283-9.6269'
394
+ ];
395
+
396
+ scale_paths = [
397
+ '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',
398
+ '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',
399
+ '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',
400
+ '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',
401
+ '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'
402
+ ];
403
+ }
404
+
405
+ const ticksGroup = mainDisplayGroup.group()
406
+ .attr({
407
+ 'data-debug-info': debug? 'ticks' : null,
408
+ fill: 'none',
409
+ 'stroke-width': .5,
410
+ 'stroke-linecap': 'butt',
411
+
412
+ });
413
+ ticks_paths.forEach((p, idx) => {
414
+ ticksGroup.path(p)
415
+ .attr({
416
+ stroke: scaleticksColor? scaleticksColor : scaleColors?.[idx + 1]?? '#000',
417
+ });
418
+ });
419
+
420
+ const scaleGroup = mainDisplayGroup.group().attr({
421
+ 'data-debug-info': debug? 'scales' : null,
422
+ class: scaleClassName,
423
+ stroke: 'none',
424
+ });
425
+ scale_paths.forEach((p, idx) => {
426
+ scaleGroup.path(p)
427
+ .attr({
428
+ fill: scaleColors?.[idx]?? '#000'
429
+ });
430
+ });
431
+
432
+
433
+ // =>> asticella
434
+ const rodGroup = mainDisplayGroup.group()
435
+ .attr({
436
+ 'data-debug-info': debug? 'asticella' : null,
437
+ fill: rodColor,
438
+ class: rodClassName
439
+ });
440
+
441
+ // centro
442
+
443
+ // versione statica
444
+ // 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');
445
+
446
+ // versione con path calcolato
447
+ // https://www.smashingmagazine.com/2019/03/svg-circle-decomposition-paths/
448
+ const ringPath = ({cx, cy, outerRadius, innerRadius}) => {
449
+
450
+ return `M ${cx - outerRadius/2}, ${cy}`+
451
+ `a ${outerRadius/2},${outerRadius/2} 0 1,0 ${outerRadius},0 ` +
452
+ `a ${outerRadius/2},${outerRadius/2} 0 1,0 -${outerRadius},0 ` +
453
+
454
+ `M ${cx - innerRadius/2}, ${cy} ` +
455
+ `a ${innerRadius/2},${innerRadius/2} 0 1,0 ${innerRadius},0 ` +
456
+ `a ${innerRadius/2},${innerRadius/2} 0 1,0 -${innerRadius},0`;
457
+ };
458
+
459
+ rodGroup.path(ringPath({
460
+ cx: sizes.mainDisplay.cx,
461
+ cy: sizes.mainDisplay.cy,
462
+ outerRadius: 24,
463
+ innerRadius: 14
464
+ }))
465
+ .attr({'fill-rule': 'evenodd'});
466
+
467
+ // bbox del solo anello di base dell'asticella
468
+ // usato per il posizionamento delle etichette
469
+ const rodGroupBaseBbox = rodGroup.bbox();
470
+
471
+ // asta
472
+ // TODO generare dinamicamente
473
+ rodGroup.polygon('59.1813 139.0943 59.1813 139.0943 98.8572 108.491 105.5448 120.0899 59.1813 139.0943')
474
+ .transform({
475
+
476
+ origin: {
477
+ x: sizes.mainDisplay.cx,
478
+ y: sizes.mainDisplay.cy,
479
+ },
480
+ rotate: rotazioneAsticella
481
+ })
482
+ .attr({
483
+ 'data-debug-info': debug? `Value: ${parsedDisplayValue}, color index: ${getSanitizedColorIndex(parsedDisplayValue)}, {Angolo: ${rotazioneAsticella}, Start value: ${displayScaleStartValue}` : null,
484
+ });
485
+
486
+
487
+ // =>> function setLabel
488
+ const setLabel = async ({
489
+ labelObj,
490
+ baseX,
491
+ baseY,
492
+ dy,
493
+ rowGap = 0, // ulteriore correzione tra le distanze delle due righe
494
+ parentEl = svgCanvas
495
+ }) => {
496
+ if(labelObj?.label) {
497
+
498
+ const [label_bbox, labelEl] = await this.createTextEl ({
499
+ text: labelObj.label,
500
+ textFill: labelObj.fill??= '#000',
501
+ fontAttr: labelObj.font??{},
502
+ fontFilePath: labelObj.fontFilePath,
503
+ parentElement: parentEl
504
+ });
505
+
506
+ // posizionamento Y
507
+ // dy è lo spostamento aggiuntivo pari all'altezza dell'eventuale riga precedente
508
+ const labelY = baseY + (dy? dy + rowGap : 0);
509
+
510
+ // posizionamento, al centro sotto l'asticella
511
+ labelEl.move(
512
+ baseX - label_bbox.width / 2,
513
+ labelY
514
+ )
515
+ .attr({
516
+ 'data-debug-info': debug? `label “${labelObj.label}”` : null
517
+ });
518
+
519
+ return label_bbox.height;
520
+
521
+ }
522
+ };
523
+
524
+ // =>> displayLabel
525
+ if(displayLabelTopMargin != null && typeof displayLabelTopMargin !== 'number') {
526
+ throw '`displayLabelTopMargin` must be null or a number';
527
+ }
528
+
529
+ displayLabel??=[];
530
+ if(displayLabel.length > 2) {
531
+ throw 'No more than 2 `displayLabel` elements can be defined';
532
+ }
533
+
534
+ let prevLabelHeight = 0;
535
+ for await (const labelObj of displayLabel) {
536
+
537
+ prevLabelHeight = await setLabel({
538
+ labelObj: labelObj,
539
+ baseX: sizes.mainDisplay.cx,
540
+ baseY: rodGroupBaseBbox.y2 + (displayLabelTopMargin??0),
541
+ dy: prevLabelHeight,
542
+ rowGap: displayLabelRowGap,
543
+ parentEl: mainDisplayGroup
544
+ });
545
+ }
546
+
547
+ // =>> ricalcolo altezza gruppo principale
548
+ const mainDisplayGroupBbox = mainDisplayGroup.bbox();
549
+ sizes.mainSvg.h = mainDisplayGroupBbox.y + mainDisplayGroupBbox.y2;
550
+
551
+ // SECTION miniDisplay
552
+ if(miniDisplayTopMargin != null && typeof miniDisplayTopMargin !== 'number') {
553
+ throw '`miniDisplayTopMargin` must be null or a number';
554
+ }
555
+
556
+ miniDisplay??=[];
557
+ if(miniDisplay.length > 2) {
558
+ throw 'No more than 2 `miniDisplay` elements can be defined';
559
+ }
560
+
561
+
562
+ let miniDisplayIdx = 1, miniDisplayMaxHeight = 0, mdArcHeight = null;
563
+ for await (const md of miniDisplay) {
564
+
565
+
566
+ const errorPrefix = `miniDisplay #${miniDisplayIdx}`;
567
+
568
+ // =>> controlli
569
+ ['position', 'type', 'value'].forEach(item => {
570
+ if(md[item] == null) {
571
+ throw `${errorPrefix}: '${item}' is requested`;
572
+ }
573
+ });
574
+
575
+ if(['sx', 'dx', 'left', 'right'].indexOf(md.position) === -1) {
576
+ throw `${errorPrefix}: 'position' must be one of 'sx', 'dx', 'left' or 'right'`;
577
+ }
578
+
579
+ if(typeof md.value !== 'number') {
580
+ throw `${errorPrefix}: 'value' must be a number`;
581
+ }
582
+
583
+ if(['gauge', 'value'].indexOf(md.type) === -1) {
584
+ throw `${errorPrefix}: 'type' must be one of 'gauge' or 'value'`;
585
+ }
586
+
587
+ // default
588
+ md.mdArc ??= {};
589
+ md.mdArc.strokeWidth ??= 0;
590
+
591
+
592
+ // =>> dimensioni
593
+ const
594
+ // raggio circonferenza che include l'arco
595
+ mdRadius = 26,
596
+
597
+ mdWidrh = (mdRadius * 2) + (md.mdArc?.strokeWidth??0), // impegna sia la parte destra che la sinistra
598
+
599
+ // margine orizzontale dal bordo esterno dell'SVG
600
+ xMargin = 20,
601
+
602
+ // coordinate dell'origine del miniDisplay
603
+ mdOrigin = {
604
+ y: sizes.mainSvg.h + (miniDisplayTopMargin?? 0),
605
+ x: (md.position === 'sx' || md.position === 'left')
606
+ ? xMargin
607
+ : sizes.mainSvg.w - xMargin - mdWidrh
608
+ }
609
+ ;
610
+
611
+
612
+ // =>> mdGroup
613
+ const mdGroup = svgCanvas.group()
614
+ .attr({
615
+ 'data-debug-info': debug? `miniDisplay #${miniDisplayIdx}` : null,
616
+ });
617
+
618
+ // =>> definizione arco
619
+ const
620
+ // coordinate del centro della circonferenza che include l'arco
621
+ mdArcCx= mdOrigin.x + mdRadius, // + md.mdArc.strokeWidth / 2,
622
+ mdArcCy= mdOrigin.y + mdRadius // + md.mdArc.strokeWidth / 2,
623
+ ;
624
+
625
+ // minidisplay arc
626
+ mdGroup.path(getDisplayArcPath(mdRadius, mdArcCx, mdArcCy))
627
+ .attr({
628
+ 'data-debug-info': debug? `miniDisplay #${miniDisplayIdx}: arco` : null,
629
+ class: md.mdArc.className, // se il valore è null o undefined l'attributo viene ignorato
630
+ fill: md.mdArc.fill?? 'none',
631
+ stroke: md.mdArc.strokeWidth > 0? md.mdArc.strokeColor : null,
632
+ 'stroke-width': md.mdArc.strokeWidth > 0? md.mdArc.strokeWidth : null,
633
+ 'stroke-linecap': md.mdArc.strokeWidth > 0? 'round' : null,
634
+ });
635
+
636
+ // altezza arco, per il posizionamento delle etichette
637
+ // viene misurato solo il primo arco, in modo che le etichette siano comq allineate
638
+ // se necessario, utilizzare il parametro miniDisplayLabelTopMargin per distanziare il testo dalla base dell'arco
639
+ if(mdArcHeight == null) {
640
+ mdArcHeight = mdGroup.bbox().height;
641
+ }
642
+
643
+
644
+ // =>> modalità "value"
645
+ if(md.type === 'value') {
646
+
647
+ md.typeValueFont ??= {};
648
+ md.typeValueFont.label ??= String(Math.floor(md.value));
649
+
650
+ const [value_bbox, valueEl] = await this.createTextEl ({
651
+ text: md.typeValueFont.label,
652
+ textFill: md.typeValueFont.fill??= '#000',
653
+ fontAttr: md.typeValueFont.font??{},
654
+ fontFilePath: md.typeValueFont.fontFilePath,
655
+ parentElement: mdGroup
656
+ });
657
+
658
+ valueEl.move(
659
+ mdArcCx - value_bbox.w/2,
660
+ mdArcCy - value_bbox.h/2 - 2, // 2: spostamento basline verso l'alto
661
+ )
662
+ .attr({
663
+ 'data-debug-info': debug? `miniDisplay #${miniDisplayIdx}: value` : null,
664
+ });
665
+
666
+ // =>> modalità "gauge"
667
+ } else {
668
+
669
+ // segmento indicatore colore
670
+ const segmentStrokeWidth = 6
671
+ // spazio segmento arco
672
+ ,distanzaSegmentoArco = 2
673
+ // correzione raggio per dare spazio allo spessore della traccia
674
+ ,segmentRadius = mdRadius - segmentStrokeWidth/2 - distanzaSegmentoArco
675
+ // ampiezza angolo che sottende il segmento circolare
676
+ // 30 è l'angolo del punto iniziale
677
+ ,segmentAngle = this.utils.toRadians(240/scale - 30)
678
+
679
+ ,startPointX = mdArcCx - (segmentRadius * Math.cos(rad30))
680
+ ,startPointY = mdArcCy + (segmentRadius * Math.sin(rad30))
681
+ ,endPointX = mdArcCx - (segmentRadius * Math.cos(segmentAngle))
682
+ ,endPointY = mdArcCy - (segmentRadius * Math.sin(segmentAngle))
683
+
684
+ ,segmentPath = `M ${startPointX},${startPointY} ` +
685
+ // rx ry x-axis-angle large-arc-flag sweep-flag end-x end-y
686
+ `A ${segmentRadius} ${segmentRadius} 0 0 1 ${endPointX} ${endPointY}`
687
+
688
+ ,segmentGroup = mdGroup.group()
689
+ ;
690
+
691
+ // =>> segmento mini display gauge
692
+ segmentGroup.path(segmentPath)
693
+ .attr({
694
+ stroke: getColorFromValue(md.value),
695
+ 'stroke-width': segmentStrokeWidth,
696
+ });
697
+
698
+ // =>> anello mini asticella
699
+ const ringOuterRadius = 12;
700
+ mdGroup.path( ringPath({
701
+ cx: mdArcCx,
702
+ cy: mdArcCy,
703
+ outerRadius: ringOuterRadius,
704
+ innerRadius: 6
705
+ }))
706
+ .attr({'fill-rule': 'evenodd'})
707
+ .attr({
708
+ fill: rodColor,
709
+ class: rodClassName
710
+ });
711
+
712
+ // =>> mini asticella
713
+ // viene prima creata allineata con l'asse X del mini display e poi ruotata al centro della porzione d'inizio
714
+ // in seguito, la rotazione applicata al gruppo la porta nella posizione definitiva
715
+ segmentGroup.polygon(
716
+ `${mdArcCx - ringOuterRadius/2 + 2},${mdArcCy - 3} `+
717
+ `${mdArcCx - segmentRadius + 6},${mdArcCy} `+
718
+ `${mdArcCx - ringOuterRadius/2 + 2},${mdArcCy + 3}`
719
+ )
720
+ .attr({
721
+ fill: rodColor,
722
+ class: rodClassName
723
+ })
724
+ .transform({
725
+ origin: {
726
+ x: mdArcCx,
727
+ y: mdArcCy,
728
+ },
729
+ rotate: ((240/scale) / 2) - 30
730
+ })
731
+ ;
732
+
733
+ segmentGroup.transform({
734
+ origin: {
735
+ x: mdArcCx,
736
+ y: mdArcCy,
737
+ },
738
+ rotate: (240/scale) * getSanitizedColorIndex(md.value) - displayScaleStartValue
739
+ });
740
+ }
741
+
742
+ // =>> label mini display
743
+
744
+ if(miniDisplayLabelTopMargin != null && typeof miniDisplayLabelTopMargin !== 'number') {
745
+ throw `${errorPrefix}: 'miniDisplayLabelTopMargin' must be null or a number`;
746
+ }
747
+
748
+ md.mdLabel??=[];
749
+ if(md.mdLabel.length > 2) {
750
+ throw `${errorPrefix}: No more than 2 'mdLabel' elements can be defined`;
751
+ }
752
+
753
+
754
+ let prevLabelHeight = 0;
755
+ for await (const labelObj of md.mdLabel) {
756
+
757
+ prevLabelHeight = await setLabel({
758
+ labelObj: labelObj,
759
+ baseX: mdArcCx,
760
+ baseY: mdOrigin.y + mdArcHeight + (miniDisplayLabelTopMargin??0),
761
+ dy: prevLabelHeight,
762
+ rowGap: miniDisplayLabelRowGap,
763
+ parentEl: mdGroup
764
+ });
765
+ }
766
+
767
+
768
+
769
+ // =>> indicatori per debug
770
+ if(debug) {
771
+ // centro mini display
772
+ const gap = 10;
773
+ const debug_group = mdGroup.group()
774
+ .attr({
775
+ 'data-debug-info': 'centro mini display',
776
+ stroke: '#05923e',
777
+ fill: 'none',
778
+ 'stroke-width': .5,
779
+ class: 'debug-info'
780
+ });
781
+ debug_group.line({x1: mdArcCx - gap, y1: mdArcCy, x2: mdArcCx + gap, y2: mdArcCy});
782
+ debug_group.line({x1: mdArcCx, y1: mdArcCy - gap, x2: mdArcCx, y2: mdArcCy + gap});
783
+
784
+ // origine mini display
785
+ // debug_group.line({x1: mdOrigin.x - gap, y1: mdOrigin.y, x2: mdOrigin.x + gap, y2: mdOrigin.y});
786
+ // debug_group.line({x1: mdOrigin.x, y1: mdOrigin.y - gap, x2: mdOrigin.x, y2: mdOrigin.y + gap});
787
+ }
788
+
789
+ // =>> ricalcolo miniDisplayMaxHeight
790
+ miniDisplayMaxHeight = Math.max(
791
+ miniDisplayMaxHeight,
792
+ mdGroup.bbox().h + (miniDisplayTopMargin?? 0) + md.mdArc.strokeWidth // strokeWidth: spessore traccia nel caso non ci sia testo
793
+ );
794
+ miniDisplayIdx++;
795
+
796
+ }
797
+
798
+ // !SECTION end miniDisplay
799
+
800
+ // =>> aggiornamento dimensioni SVG
801
+ sizes.mainSvg.h += miniDisplayMaxHeight;
802
+ // sizes.mainSvg.w = mainDisplayGroupBbox.w;
803
+
804
+ svgCanvas
805
+ .viewbox(0, 0, sizes.mainSvg.w, sizes.mainSvg.h);
806
+
807
+ if(addSvgSize) {
808
+ svgCanvas.size(sizes.mainSvg.w, sizes.mainSvg.h);
809
+ }
810
+
811
+
812
+ // indicatori per debug
813
+ if(debug) {
814
+ // centro display e rotazione asticella
815
+ const gap = sizes.mainDisplay.r;
816
+ const debug_group1 = svgCanvas.group()
817
+ .attr({
818
+ 'data-debug-info': 'centro rotazione asticella',
819
+ stroke: '#e20613',
820
+ fill: 'none',
821
+ 'stroke-width': .5,
822
+ class: 'debug-info'
823
+ });
824
+ debug_group1.line({x1: sizes.mainDisplay.cx - gap, y1: sizes.mainDisplay.cy, x2: sizes.mainDisplay.cx + gap, y2: sizes.mainDisplay.cy});
825
+ debug_group1.line({x1: sizes.mainDisplay.cx, y1: sizes.mainDisplay.cy - gap, x2: sizes.mainDisplay.cx, y2: sizes.mainDisplay.cy + gap});
826
+ }
827
+
828
+ if(!container) {
829
+ return svgCanvas.svg();
830
+ }
831
+
832
+ } catch(e) {
833
+ console.error( 'AdaCharts → ratingDisplay:', e ); // eslint-disable-line
834
+ }
835
+ }