gaugeit.js 0.1.0 → 0.1.1

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.
@@ -0,0 +1,1117 @@
1
+ # Gaugeit.js integration recipes
2
+
3
+ These recipes target Gaugeit.js 0.1.1. Each recipe is intended to be copied as a
4
+ complete starting point. Replace application values and labels, but preserve package
5
+ names, import paths, global names, lifecycle cleanup, and option nesting.
6
+
7
+ ## 1. Simple semicircular gauge in plain HTML
8
+
9
+ Save as `index.html` beside `dist/`:
10
+
11
+ ```html
12
+ <!doctype html>
13
+ <html lang="en">
14
+ <head>
15
+ <meta charset="utf-8">
16
+ <meta name="viewport" content="width=device-width, initial-scale=1">
17
+ <title>Gaugeit.js arc gauge</title>
18
+ <link rel="stylesheet" href="./dist/gaugeit.min.css">
19
+ <style>
20
+ body {
21
+ max-width: 42rem;
22
+ margin: 2rem auto;
23
+ padding: 0 1rem;
24
+ font-family: system-ui, sans-serif;
25
+ }
26
+
27
+ #load-gauge {
28
+ width: min(100%, 32rem);
29
+ margin-inline: auto;
30
+ }
31
+ </style>
32
+ </head>
33
+ <body>
34
+ <h1>Engine load</h1>
35
+ <div id="load-gauge"></div>
36
+
37
+ <script src="./dist/gaugeit.umd.min.js"></script>
38
+ <script>
39
+ const gauge = Gaugeit.createGauge('#load-gauge', {
40
+ type: 'arc',
41
+ min: 0,
42
+ max: 100,
43
+ value: 42,
44
+ zones: [
45
+ { min: 0, max: 60, color: '#22c55e' },
46
+ { min: 60, max: 85, color: '#facc15' },
47
+ { min: 85, max: 100, color: '#ef4444' }
48
+ ],
49
+ readout: {
50
+ title: {
51
+ visible: true,
52
+ text: 'ENGINE LOAD'
53
+ },
54
+ value: {
55
+ visible: true,
56
+ suffix: '%'
57
+ }
58
+ },
59
+ accessibility: {
60
+ label: 'Engine load',
61
+ valueText: (value) => `${Math.round(value)} percent`
62
+ }
63
+ });
64
+
65
+ window.setTimeout(() => {
66
+ gauge.setValue(76);
67
+ }, 1000);
68
+ </script>
69
+ </body>
70
+ </html>
71
+ ```
72
+
73
+ ## 2. Classic framed instrument
74
+
75
+ ```html
76
+ <!doctype html>
77
+ <html lang="en">
78
+ <head>
79
+ <meta charset="utf-8">
80
+ <meta name="viewport" content="width=device-width, initial-scale=1">
81
+ <title>Classic Gaugeit.js instrument</title>
82
+ <link rel="stylesheet" href="./dist/gaugeit.min.css">
83
+ <style>
84
+ #voltage-gauge {
85
+ width: min(100%, 34rem);
86
+ margin: 2rem auto;
87
+ }
88
+ </style>
89
+ </head>
90
+ <body>
91
+ <div id="voltage-gauge"></div>
92
+
93
+ <script src="./dist/gaugeit.umd.min.js"></script>
94
+ <script>
95
+ const voltageGauge = Gaugeit.createGauge('#voltage-gauge', {
96
+ type: 'classic',
97
+ min: 0,
98
+ max: 240,
99
+ value: 118,
100
+ zones: [
101
+ { min: 0, max: 90, color: '#ef4444', width: 12 },
102
+ { min: 90, max: 150, color: '#22c55e', width: 12 },
103
+ { min: 150, max: 240, color: '#ef4444', width: 12 }
104
+ ],
105
+ pointer: {
106
+ type: 'spear',
107
+ colorByZone: true
108
+ },
109
+ readout: {
110
+ title: {
111
+ visible: true,
112
+ text: 'VOLTAGE'
113
+ },
114
+ value: {
115
+ visible: true,
116
+ fractionDigits: 0
117
+ },
118
+ unit: {
119
+ visible: true,
120
+ text: 'V'
121
+ }
122
+ },
123
+ accessibility: {
124
+ label: 'Voltage',
125
+ valueText: (value) => `${Math.round(value)} volts`
126
+ }
127
+ });
128
+ </script>
129
+ </body>
130
+ </html>
131
+ ```
132
+
133
+ ## 3. Radial center-zero balance gauge
134
+
135
+ ```html
136
+ <!doctype html>
137
+ <html lang="en">
138
+ <head>
139
+ <meta charset="utf-8">
140
+ <meta name="viewport" content="width=device-width, initial-scale=1">
141
+ <title>Center-zero Gaugeit.js gauge</title>
142
+ <link rel="stylesheet" href="./dist/gaugeit.min.css">
143
+ </head>
144
+ <body>
145
+ <div id="balance-gauge"></div>
146
+
147
+ <script src="./dist/gaugeit.umd.min.js"></script>
148
+ <script>
149
+ const balanceGauge = Gaugeit.createGauge('#balance-gauge', {
150
+ type: 'center-zero',
151
+ min: -100,
152
+ max: 100,
153
+ value: -18,
154
+ centerZero: {
155
+ zeroValue: 0,
156
+ symmetric: true,
157
+ marker: {
158
+ visible: true
159
+ }
160
+ },
161
+ zones: [
162
+ { min: -100, max: -60, color: '#ef4444' },
163
+ { min: -60, max: 60, color: '#22c55e' },
164
+ { min: 60, max: 100, color: '#ef4444' }
165
+ ],
166
+ readout: {
167
+ title: {
168
+ visible: true,
169
+ text: 'BALANCE'
170
+ },
171
+ value: {
172
+ visible: true,
173
+ prefix: ''
174
+ }
175
+ },
176
+ accessibility: {
177
+ label: 'Balance deviation',
178
+ valueText: (value) => `${Math.round(value)} balance units`
179
+ }
180
+ });
181
+ </script>
182
+ </body>
183
+ </html>
184
+ ```
185
+
186
+ The numeric range contains `zeroValue`. `symmetric: true` protects the centered
187
+ reference if the supplied endpoints are uneven.
188
+
189
+ ## 4. Classic linear center-zero instrument
190
+
191
+ ```html
192
+ <!doctype html>
193
+ <html lang="en">
194
+ <head>
195
+ <meta charset="utf-8">
196
+ <meta name="viewport" content="width=device-width, initial-scale=1">
197
+ <title>Classic linear center-zero instrument</title>
198
+ <link rel="stylesheet" href="./dist/gaugeit.min.css">
199
+ <style>
200
+ #trim-gauge {
201
+ width: min(100%, 42rem);
202
+ margin: 2rem auto;
203
+ }
204
+ </style>
205
+ </head>
206
+ <body>
207
+ <div id="trim-gauge"></div>
208
+
209
+ <script src="./dist/gaugeit.umd.min.js"></script>
210
+ <script>
211
+ const trimGauge = Gaugeit.createGauge('#trim-gauge', {
212
+ type: 'classic-linear-zero',
213
+ min: -50,
214
+ max: 50,
215
+ value: 12,
216
+ centerZero: {
217
+ zeroValue: 0,
218
+ symmetric: true,
219
+ marker: {
220
+ visible: true,
221
+ color: '#111827'
222
+ }
223
+ },
224
+ indicator: {
225
+ type: 'hairline',
226
+ color: '#b91c1c',
227
+ centerOffset: 0
228
+ },
229
+ readout: {
230
+ title: {
231
+ visible: true,
232
+ text: 'TRIM'
233
+ },
234
+ value: {
235
+ visible: true,
236
+ fractionDigits: 0
237
+ }
238
+ },
239
+ accessibility: {
240
+ label: 'Trim setting'
241
+ }
242
+ });
243
+ </script>
244
+ </body>
245
+ </html>
246
+ ```
247
+
248
+ Public linear Y and center offsets are positive-up, not raw SVG coordinates.
249
+
250
+ ## 5. Rolling tape gauge
251
+
252
+ ```html
253
+ <!doctype html>
254
+ <html lang="en">
255
+ <head>
256
+ <meta charset="utf-8">
257
+ <meta name="viewport" content="width=device-width, initial-scale=1">
258
+ <title>Gaugeit.js rolling tape</title>
259
+ <link rel="stylesheet" href="./dist/gaugeit.min.css">
260
+ <style>
261
+ #altitude-tape {
262
+ width: min(100%, 20rem);
263
+ margin: 2rem auto;
264
+ }
265
+ </style>
266
+ </head>
267
+ <body>
268
+ <div id="altitude-tape"></div>
269
+
270
+ <script src="./dist/gaugeit.umd.min.js"></script>
271
+ <script>
272
+ const altitudeTape = Gaugeit.createGauge('#altitude-tape', {
273
+ type: 'rolling-tape',
274
+ min: 0,
275
+ max: 40000,
276
+ value: 12500,
277
+ tape: {
278
+ orientation: 'vertical',
279
+ interval: 1000,
280
+ minorSubdivisions: 4,
281
+ majorSpacing: 54,
282
+ indicatorColor: '#b91c1c',
283
+ colorByZone: true
284
+ },
285
+ zones: [
286
+ { min: 0, max: 10000, color: '#22c55e' },
287
+ { min: 10000, max: 25000, color: '#facc15' },
288
+ { min: 25000, max: 40000, color: '#ef4444' }
289
+ ],
290
+ readout: {
291
+ title: {
292
+ visible: true,
293
+ text: 'ALTITUDE'
294
+ },
295
+ unit: {
296
+ visible: true,
297
+ text: 'ft'
298
+ }
299
+ },
300
+ accessibility: {
301
+ label: 'Altitude',
302
+ valueText: (value) => `${Math.round(value)} feet`
303
+ }
304
+ });
305
+
306
+ altitudeTape.setValue(13800, {
307
+ duration: 1400
308
+ });
309
+ </script>
310
+ </body>
311
+ </html>
312
+ ```
313
+
314
+ The tape moves behind its fixed reference line. Do not add a radial pointer to this
315
+ type.
316
+
317
+ ## 6. Dynamic value stream
318
+
319
+ Use `setValue()` rather than rebuilding options for each sample:
320
+
321
+ ```html
322
+ <!doctype html>
323
+ <html lang="en">
324
+ <head>
325
+ <meta charset="utf-8">
326
+ <meta name="viewport" content="width=device-width, initial-scale=1">
327
+ <title>Dynamic Gaugeit.js value</title>
328
+ <link rel="stylesheet" href="./dist/gaugeit.min.css">
329
+ </head>
330
+ <body>
331
+ <div id="live-gauge"></div>
332
+ <button id="stop" type="button">Stop updates</button>
333
+
334
+ <script src="./dist/gaugeit.umd.min.js"></script>
335
+ <script>
336
+ const gauge = Gaugeit.createGauge('#live-gauge', {
337
+ type: 'line-scale',
338
+ min: 0,
339
+ max: 100,
340
+ value: 50,
341
+ animation: {
342
+ duration: 400
343
+ },
344
+ readout: {
345
+ title: {
346
+ visible: true,
347
+ text: 'LIVE VALUE'
348
+ },
349
+ value: {
350
+ visible: true
351
+ }
352
+ }
353
+ });
354
+
355
+ const timer = window.setInterval(() => {
356
+ const nextValue = Math.round(Math.random() * 100);
357
+ gauge.setValue(nextValue);
358
+ }, 1000);
359
+
360
+ document.querySelector('#stop').addEventListener('click', () => {
361
+ window.clearInterval(timer);
362
+ gauge.destroy();
363
+ }, { once: true });
364
+ </script>
365
+ </body>
366
+ </html>
367
+ ```
368
+
369
+ Application-owned timers remain application-owned and must be cleaned up separately
370
+ from the gauge.
371
+
372
+ ## 7. Multiple gauges on one page
373
+
374
+ ```html
375
+ <!doctype html>
376
+ <html lang="en">
377
+ <head>
378
+ <meta charset="utf-8">
379
+ <meta name="viewport" content="width=device-width, initial-scale=1">
380
+ <title>Multiple Gaugeit.js instruments</title>
381
+ <link rel="stylesheet" href="./dist/gaugeit.min.css">
382
+ <style>
383
+ .gauge-grid {
384
+ display: grid;
385
+ grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
386
+ gap: 1rem;
387
+ }
388
+ </style>
389
+ </head>
390
+ <body>
391
+ <div class="gauge-grid">
392
+ <div id="speed"></div>
393
+ <div id="temperature"></div>
394
+ <div id="balance"></div>
395
+ </div>
396
+
397
+ <script src="./dist/gaugeit.umd.min.js"></script>
398
+ <script>
399
+ const gauges = [
400
+ Gaugeit.createGauge('#speed', {
401
+ type: 'arc',
402
+ min: 0,
403
+ max: 240,
404
+ value: 88,
405
+ accessibility: { label: 'Speed' }
406
+ }),
407
+
408
+ Gaugeit.createGauge('#temperature', {
409
+ type: 'classic',
410
+ min: -20,
411
+ max: 120,
412
+ value: 68,
413
+ accessibility: { label: 'Temperature' }
414
+ }),
415
+
416
+ Gaugeit.createGauge('#balance', {
417
+ type: 'center-zero',
418
+ min: -100,
419
+ max: 100,
420
+ value: 6,
421
+ accessibility: { label: 'Balance' }
422
+ })
423
+ ];
424
+
425
+ window.addEventListener('pagehide', () => {
426
+ for (const gauge of gauges) {
427
+ gauge.destroy();
428
+ }
429
+ }, { once: true });
430
+ </script>
431
+ </body>
432
+ </html>
433
+ ```
434
+
435
+ Each gauge owns one host. Do not reuse one instance across several hosts.
436
+
437
+ ## 8. Threshold warning light
438
+
439
+ ```js
440
+ const gauge = Gaugeit.createGauge('#temperature', {
441
+ type: 'heritage-round',
442
+ min: 0,
443
+ max: 120,
444
+ value: 72,
445
+ light: {
446
+ visible: true,
447
+ x: 160,
448
+ y: 88,
449
+ radius: 8,
450
+ glowRadius: 24,
451
+ color: 'red',
452
+ pulse: false,
453
+ blink: false,
454
+ flicker: false,
455
+ trigger: {
456
+ mode: 'above',
457
+ source: 'target',
458
+ value: 95
459
+ }
460
+ },
461
+ accessibility: {
462
+ label: 'Coolant temperature'
463
+ }
464
+ });
465
+ ```
466
+
467
+ `source: 'target'` makes the warning react to application intent immediately, without
468
+ waiting for pointer animation.
469
+
470
+ ## 9. Pulsing active light
471
+
472
+ ```js
473
+ const gauge = Gaugeit.createGauge('#status', {
474
+ type: 'classic',
475
+ value: 50,
476
+ light: {
477
+ visible: true,
478
+ x: 250,
479
+ y: 55,
480
+ radius: 8,
481
+ glowRadius: 26,
482
+ glowSharpness: 0.15,
483
+ glowOpacity: 0.2,
484
+ color: 'green',
485
+ on: true,
486
+ glow: true,
487
+ pulse: true,
488
+ pulseInterval: 2600,
489
+ blink: false,
490
+ flicker: false,
491
+ trigger: {
492
+ mode: 'manual'
493
+ }
494
+ }
495
+ });
496
+ ```
497
+
498
+ The light must be active before pulse is visible. `on` controls the state only in
499
+ manual trigger mode.
500
+
501
+ ## 10. Blinking warning light with flicker
502
+
503
+ ```js
504
+ const gauge = Gaugeit.createGauge('#pressure', {
505
+ type: 'classic',
506
+ min: 0,
507
+ max: 10,
508
+ value: 4.2,
509
+ light: {
510
+ visible: true,
511
+ color: 'amber',
512
+ radius: 8,
513
+ glowRadius: 22,
514
+ glowOpacity: 0.18,
515
+ pulse: true,
516
+ blink: true,
517
+ blinkInterval: 900,
518
+ flicker: true,
519
+ flickerIntensity: 0.08,
520
+ flickerGlowRadius: 0.06,
521
+ flickerMinInterval: 55,
522
+ flickerMaxInterval: 150,
523
+ trigger: {
524
+ mode: 'below',
525
+ source: 'displayed',
526
+ value: 2
527
+ }
528
+ }
529
+ });
530
+ ```
531
+
532
+ Blink takes precedence over pulse. Flicker remains an independent overlay. Browsers
533
+ requesting reduced motion receive a steady accessible state instead of motion.
534
+
535
+ ## 11. React controlled component
536
+
537
+ ```jsx
538
+ import { useMemo, useState } from 'react';
539
+ import Gaugeit from 'gaugeit.js/react';
540
+ import 'gaugeit.js/css';
541
+
542
+ export default function EngineLoadGauge() {
543
+ const [value, setValue] = useState(42);
544
+
545
+ const options = useMemo(() => ({
546
+ type: 'arc',
547
+ min: 0,
548
+ max: 100,
549
+ zones: [
550
+ { min: 0, max: 60, color: '#22c55e' },
551
+ { min: 60, max: 85, color: '#facc15' },
552
+ { min: 85, max: 100, color: '#ef4444' }
553
+ ],
554
+ readout: {
555
+ title: {
556
+ visible: true,
557
+ text: 'ENGINE LOAD'
558
+ },
559
+ value: {
560
+ visible: true,
561
+ suffix: '%'
562
+ }
563
+ },
564
+ accessibility: {
565
+ label: 'Engine load',
566
+ valueText: (current) => `${Math.round(current)} percent`
567
+ }
568
+ }), []);
569
+
570
+ return (
571
+ <section>
572
+ <Gaugeit
573
+ value={value}
574
+ options={options}
575
+ className="engine-load-gauge"
576
+ />
577
+
578
+ <label>
579
+ Load
580
+ <input
581
+ type="range"
582
+ min="0"
583
+ max="100"
584
+ value={value}
585
+ onChange={(event) => setValue(Number(event.target.value))}
586
+ />
587
+ </label>
588
+ </section>
589
+ );
590
+ }
591
+ ```
592
+
593
+ The explicit `value` prop remains authoritative even if `options` also contains a
594
+ value. Prefer not to duplicate it.
595
+
596
+ ## 12. React ref and imperative API
597
+
598
+ ```jsx
599
+ import { useMemo, useRef } from 'react';
600
+ import Gaugeit from 'gaugeit.js/react';
601
+ import 'gaugeit.js/css';
602
+
603
+ export default function ImperativeGauge() {
604
+ const gaugeRef = useRef(null);
605
+
606
+ const options = useMemo(() => ({
607
+ type: 'classic',
608
+ min: 0,
609
+ max: 100,
610
+ value: 25,
611
+ accessibility: {
612
+ label: 'Imperative demonstration gauge'
613
+ }
614
+ }), []);
615
+
616
+ function setWarningValue() {
617
+ gaugeRef.current?.setValue(90, {
618
+ duration: 1200,
619
+ easing: 'easeInOutCubic'
620
+ });
621
+ }
622
+
623
+ function changeTheme() {
624
+ gaugeRef.current?.updateOptions({
625
+ pointer: {
626
+ color: '#dc2626'
627
+ },
628
+ face: {
629
+ color: '#fff7ed'
630
+ }
631
+ });
632
+ }
633
+
634
+ return (
635
+ <div>
636
+ <Gaugeit
637
+ ref={gaugeRef}
638
+ options={options}
639
+ />
640
+
641
+ <button type="button" onClick={setWarningValue}>
642
+ Set warning value
643
+ </button>
644
+
645
+ <button type="button" onClick={changeTheme}>
646
+ Change theme
647
+ </button>
648
+ </div>
649
+ );
650
+ }
651
+ ```
652
+
653
+ The adapter destroys the live instance on unmount. Do not call `destroy()` manually
654
+ from ordinary component cleanup unless the component owns a separately created
655
+ instance outside the adapter.
656
+
657
+ ## 13. Laravel Composer asset installation
658
+
659
+ Install:
660
+
661
+ ```bash
662
+ composer require kasperi/gaugeit-js:^0.1
663
+ vendor/bin/gaugeit-install-assets public/assets/vendor/gaugeit
664
+ ```
665
+
666
+ Optional consuming-project automation:
667
+
668
+ ```json
669
+ {
670
+ "scripts": {
671
+ "assets:gaugeit": "vendor/bin/gaugeit-install-assets public/assets/vendor/gaugeit",
672
+ "post-install-cmd": [
673
+ "@assets:gaugeit"
674
+ ],
675
+ "post-update-cmd": [
676
+ "@assets:gaugeit"
677
+ ]
678
+ }
679
+ }
680
+ ```
681
+
682
+ Blade view:
683
+
684
+ ```blade
685
+ @push('styles')
686
+ <link
687
+ rel="stylesheet"
688
+ href="{{ asset('assets/vendor/gaugeit/gaugeit.min.css') }}"
689
+ >
690
+ @endpush
691
+
692
+ <section>
693
+ <h2>Prediction accuracy</h2>
694
+
695
+ <div
696
+ id="accuracy-gauge"
697
+ data-options='@json([
698
+ "type" => "arc",
699
+ "min" => 0,
700
+ "max" => 100,
701
+ "value" => $accuracy,
702
+ "readout" => [
703
+ "value" => [
704
+ "visible" => true,
705
+ "suffix" => "%"
706
+ ]
707
+ ],
708
+ "accessibility" => [
709
+ "label" => "Prediction accuracy"
710
+ ]
711
+ ])'
712
+ ></div>
713
+ </section>
714
+
715
+ @push('scripts')
716
+ <script src="{{ asset('assets/vendor/gaugeit/gaugeit.umd.min.js') }}"></script>
717
+ <script>
718
+ document.addEventListener('DOMContentLoaded', () => {
719
+ const host = document.querySelector('#accuracy-gauge');
720
+ if (!host) return;
721
+
722
+ const options = JSON.parse(host.dataset.options);
723
+ Gaugeit.createGauge(host, options);
724
+ });
725
+ </script>
726
+ @endpush
727
+ ```
728
+
729
+ The package is a browser-asset distribution. PHP does not render the SVG server-side.
730
+
731
+ ## 14. Custom element
732
+
733
+ ```html
734
+ <!doctype html>
735
+ <html lang="en">
736
+ <head>
737
+ <meta charset="utf-8">
738
+ <meta name="viewport" content="width=device-width, initial-scale=1">
739
+ <title>Gaugeit.js custom element</title>
740
+ <link rel="stylesheet" href="./dist/gaugeit.min.css">
741
+ </head>
742
+ <body>
743
+ <gauge-it
744
+ id="voltage"
745
+ type="classic"
746
+ min="0"
747
+ max="240"
748
+ value="88"
749
+ options='{"readout":{"title":{"visible":true,"text":"VOLTAGE"},"unit":{"visible":true,"text":"V"}},"accessibility":{"label":"Voltage"}}'
750
+ ></gauge-it>
751
+
752
+ <button id="increase" type="button">Increase</button>
753
+
754
+ <script src="./dist/gaugeit.umd.min.js"></script>
755
+ <script>
756
+ Gaugeit.defineGaugeitElement();
757
+
758
+ const element = document.querySelector('#voltage');
759
+
760
+ document.querySelector('#increase').addEventListener('click', () => {
761
+ element.value = Number(element.value) + 10;
762
+ });
763
+ </script>
764
+ </body>
765
+ </html>
766
+ ```
767
+
768
+ Define the element before relying on its upgraded behavior. The `options` attribute
769
+ must contain valid JSON.
770
+
771
+ ## 15. Data-attribute auto mounting
772
+
773
+ ```html
774
+ <!doctype html>
775
+ <html lang="en">
776
+ <head>
777
+ <meta charset="utf-8">
778
+ <meta name="viewport" content="width=device-width, initial-scale=1">
779
+ <title>Gaugeit.js auto mounting</title>
780
+ <link rel="stylesheet" href="./dist/gaugeit.min.css">
781
+ </head>
782
+ <body>
783
+ <div
784
+ data-gaugeit
785
+ data-gaugeit-type="arc"
786
+ data-gaugeit-min="0"
787
+ data-gaugeit-max="100"
788
+ data-gaugeit-value="63"
789
+ data-gaugeit-options='{"readout":{"value":{"visible":true,"suffix":"%"}},"accessibility":{"label":"Completion"}}'
790
+ ></div>
791
+
792
+ <script src="./dist/gaugeit.umd.min.js"></script>
793
+ <script>
794
+ const instances = Gaugeit.autoMount();
795
+
796
+ const first = instances[0];
797
+ first?.setValue(78);
798
+ </script>
799
+ </body>
800
+ </html>
801
+ ```
802
+
803
+ Repeated `autoMount()` calls return existing managed instances instead of duplicating
804
+ them.
805
+
806
+ ## 16. Custom gauge type from built-in layers
807
+
808
+ ```html
809
+ <!doctype html>
810
+ <html lang="en">
811
+ <head>
812
+ <meta charset="utf-8">
813
+ <meta name="viewport" content="width=device-width, initial-scale=1">
814
+ <title>Custom Gaugeit.js type</title>
815
+ <link rel="stylesheet" href="./dist/gaugeit.min.css">
816
+ </head>
817
+ <body>
818
+ <div id="form-gauge"></div>
819
+
820
+ <script src="./dist/gaugeit.umd.min.js"></script>
821
+ <script>
822
+ Gaugeit.registerGaugeType('football-form', {
823
+ description: 'Compact recent-form performance instrument.',
824
+
825
+ defaults: {
826
+ min: 0,
827
+ max: 100,
828
+
829
+ geometry: {
830
+ width: 280,
831
+ height: 170,
832
+ centerX: 140,
833
+ centerY: 148,
834
+ radius: 104,
835
+ startAngle: 200,
836
+ endAngle: 340
837
+ },
838
+
839
+ track: {
840
+ radius: 96,
841
+ width: 22
842
+ },
843
+
844
+ scale: {
845
+ radius: 96
846
+ },
847
+
848
+ labels: {
849
+ radius: 72
850
+ },
851
+
852
+ pointer: {
853
+ type: 'arrow',
854
+ length: 82,
855
+ width: 6,
856
+ color: '#0f172a'
857
+ },
858
+
859
+ readout: {
860
+ title: {
861
+ visible: true,
862
+ text: 'FORM'
863
+ },
864
+ value: {
865
+ visible: true,
866
+ suffix: '%'
867
+ }
868
+ }
869
+ },
870
+
871
+ layers: [
872
+ Gaugeit.layers.face,
873
+ Gaugeit.layers.zones,
874
+ Gaugeit.layers.ticks,
875
+ Gaugeit.layers.labels,
876
+ Gaugeit.layers.light,
877
+ Gaugeit.layers.readout,
878
+ Gaugeit.layers.pointer
879
+ ]
880
+ });
881
+
882
+ Gaugeit.createGauge('#form-gauge', {
883
+ type: 'football-form',
884
+ value: 74,
885
+ zones: [
886
+ { min: 0, max: 40, color: '#ef4444' },
887
+ { min: 40, max: 70, color: '#facc15' },
888
+ { min: 70, max: 100, color: '#22c55e' }
889
+ ],
890
+ accessibility: {
891
+ label: 'Recent form'
892
+ }
893
+ });
894
+ </script>
895
+ </body>
896
+ </html>
897
+ ```
898
+
899
+ The pointer is placed after the readout in the layer array, so it paints above text.
900
+
901
+ ## 17. Custom dynamic layer
902
+
903
+ ```js
904
+ const targetMarkerLayer = {
905
+ id: 'target-marker',
906
+
907
+ render({ options, renderer }) {
908
+ const group = renderer.group('target-marker', {
909
+ 'aria-hidden': 'true'
910
+ });
911
+
912
+ const marker = document.createElementNS(
913
+ 'http://www.w3.org/2000/svg',
914
+ 'circle'
915
+ );
916
+
917
+ marker.setAttribute('r', String(options.targetMarker.radius));
918
+ marker.setAttribute('fill', options.targetMarker.color);
919
+ group.append(marker);
920
+
921
+ return {
922
+ update(value) {
923
+ const point = Gaugeit.geometry.polarPoint(
924
+ options.geometry.centerX,
925
+ options.geometry.centerY,
926
+ options.targetMarker.distance,
927
+ Gaugeit.geometry.valueToAngle(value, options)
928
+ );
929
+
930
+ marker.setAttribute('cx', String(point.x));
931
+ marker.setAttribute('cy', String(point.y));
932
+ }
933
+ };
934
+ }
935
+ };
936
+
937
+ Gaugeit.registerGaugeType('target-marker-gauge', {
938
+ defaults: {
939
+ targetMarker: {
940
+ radius: 4,
941
+ distance: 102,
942
+ color: '#2563eb'
943
+ }
944
+ },
945
+
946
+ layers: [
947
+ Gaugeit.layers.face,
948
+ Gaugeit.layers.zones,
949
+ Gaugeit.layers.ticks,
950
+ Gaugeit.layers.labels,
951
+ targetMarkerLayer,
952
+ Gaugeit.layers.readout,
953
+ Gaugeit.layers.pointer
954
+ ]
955
+ });
956
+ ```
957
+
958
+ Use controller `value` for stable animated display semantics. Use
959
+ `state.pointerValue` only if the custom artwork should include jitter.
960
+
961
+ ## 18. Destroy and recreate safely
962
+
963
+ ```html
964
+ <!doctype html>
965
+ <html lang="en">
966
+ <head>
967
+ <meta charset="utf-8">
968
+ <meta name="viewport" content="width=device-width, initial-scale=1">
969
+ <title>Gaugeit.js lifecycle</title>
970
+ <link rel="stylesheet" href="./dist/gaugeit.min.css">
971
+ </head>
972
+ <body>
973
+ <div id="gauge"></div>
974
+ <button id="recreate" type="button">Recreate</button>
975
+ <button id="destroy" type="button">Destroy</button>
976
+
977
+ <script src="./dist/gaugeit.umd.min.js"></script>
978
+ <script>
979
+ const host = document.querySelector('#gauge');
980
+ let gauge = create();
981
+
982
+ function create() {
983
+ return Gaugeit.createGauge(host, {
984
+ type: 'arc',
985
+ min: 0,
986
+ max: 100,
987
+ value: 50,
988
+ accessibility: {
989
+ label: 'Lifecycle demonstration'
990
+ }
991
+ });
992
+ }
993
+
994
+ document.querySelector('#recreate').addEventListener('click', () => {
995
+ gauge.destroy();
996
+ gauge = create();
997
+ });
998
+
999
+ document.querySelector('#destroy').addEventListener('click', () => {
1000
+ gauge.destroy();
1001
+ });
1002
+ </script>
1003
+ </body>
1004
+ </html>
1005
+ ```
1006
+
1007
+ `createGauge()` is managed and already destroys an existing managed gauge on the same
1008
+ host. Explicit destruction is shown because it makes ownership clear and is required
1009
+ when the application is removing the component permanently.
1010
+
1011
+ ## 19. Listen for value and animation completion
1012
+
1013
+ ```js
1014
+ const host = document.querySelector('#gauge');
1015
+ const gauge = Gaugeit.createGauge(host, {
1016
+ type: 'arc',
1017
+ value: 10
1018
+ });
1019
+
1020
+ host.addEventListener('gaugeit:change', (event) => {
1021
+ console.log('Requested value:', event.detail.value);
1022
+ });
1023
+
1024
+ host.addEventListener('gaugeit:displaychange', (event) => {
1025
+ console.log('Displayed value:', event.detail.displayedValue);
1026
+ });
1027
+
1028
+ host.addEventListener('gaugeit:settled', (event) => {
1029
+ console.log('Animation settled:', event.detail.displayedValue);
1030
+ });
1031
+
1032
+ gauge.setValue(90);
1033
+ ```
1034
+
1035
+ Use host-scoped namespaced events. Do not install an unscoped global callback API.
1036
+
1037
+ ## 20. Switch complete configurations
1038
+
1039
+ Use `replaceOptions()` when switching instrument modes:
1040
+
1041
+ ```js
1042
+ const gauge = Gaugeit.createGauge('#instrument', {
1043
+ type: 'arc',
1044
+ min: 0,
1045
+ max: 100,
1046
+ value: 40
1047
+ });
1048
+
1049
+ function showTemperature(value) {
1050
+ gauge.replaceOptions({
1051
+ type: 'classic',
1052
+ min: -20,
1053
+ max: 120,
1054
+ value,
1055
+ readout: {
1056
+ title: {
1057
+ visible: true,
1058
+ text: 'TEMPERATURE'
1059
+ },
1060
+ value: {
1061
+ visible: true
1062
+ },
1063
+ unit: {
1064
+ visible: true,
1065
+ text: '°C'
1066
+ }
1067
+ },
1068
+ accessibility: {
1069
+ label: 'Temperature'
1070
+ }
1071
+ });
1072
+ }
1073
+
1074
+ function showBalance(value) {
1075
+ gauge.replaceOptions({
1076
+ type: 'center-zero',
1077
+ min: -100,
1078
+ max: 100,
1079
+ value,
1080
+ readout: {
1081
+ title: {
1082
+ visible: true,
1083
+ text: 'BALANCE'
1084
+ },
1085
+ value: {
1086
+ visible: true
1087
+ }
1088
+ },
1089
+ accessibility: {
1090
+ label: 'Balance'
1091
+ }
1092
+ });
1093
+ }
1094
+ ```
1095
+
1096
+ `updateOptions()` would retain earlier overrides that are omitted from the new mode.
1097
+ `replaceOptions()` resets omitted keys to the new type defaults.
1098
+
1099
+ ## Recipe verification checklist
1100
+
1101
+ Before returning generated integration code, verify:
1102
+
1103
+ - JavaScript distribution matches the environment.
1104
+ - CSS is loaded unless standalone is used.
1105
+ - The global is `Gaugeit`.
1106
+ - Package import paths use lowercase `gaugeit.js`.
1107
+ - The React component is `Gaugeit`.
1108
+ - The target exists before creation.
1109
+ - The selected built-in type name is exact.
1110
+ - Center-zero range contains its zero reference.
1111
+ - Readout child visibility is explicit.
1112
+ - Manual lights use `trigger.mode: 'manual'`.
1113
+ - Blink/pulse precedence is intentional.
1114
+ - Application timers and listeners have cleanup.
1115
+ - Component unmount destroys an owned instance.
1116
+ - CDN URLs pin `0.1.1`.
1117
+ - Accessibility label and value text are meaningful.