@record-evolution/widget-gauge 1.7.35 → 1.7.37
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.
- package/README.md +1 -1
- package/dist/widget-gauge.js +260 -228
- package/dist/widget-gauge.js.map +1 -1
- package/package.json +6 -2
- package/src/widget-gauge.ts +118 -61
package/src/widget-gauge.ts
CHANGED
|
@@ -32,7 +32,12 @@ class WidgetGauge extends LitElement {
|
|
|
32
32
|
@state()
|
|
33
33
|
private canvasList: Map<
|
|
34
34
|
string,
|
|
35
|
-
{
|
|
35
|
+
{
|
|
36
|
+
echart?: echarts.ECharts
|
|
37
|
+
title?: HTMLHeadingElement
|
|
38
|
+
wrapper?: HTMLDivElement
|
|
39
|
+
lastConfig?: string
|
|
40
|
+
}
|
|
36
41
|
> = new Map()
|
|
37
42
|
|
|
38
43
|
@state() private themeBgColor?: string
|
|
@@ -60,6 +65,16 @@ class WidgetGauge extends LitElement {
|
|
|
60
65
|
})
|
|
61
66
|
|
|
62
67
|
this.template = {
|
|
68
|
+
// The registered ECharts theme carries its own canvas
|
|
69
|
+
// `backgroundColor` (chalk paints `rgba(41,52,65,1)`), which the
|
|
70
|
+
// gauge canvas draws as an opaque slab on top of the wrapper's
|
|
71
|
+
// background. That hides whatever colour the tile actually has —
|
|
72
|
+
// the `--re-tile-background-color` the host injects, or a user's
|
|
73
|
+
// custom colour — so the gauge sits in a differently coloured box.
|
|
74
|
+
// Forcing the option's background to transparent lets the wrapper's
|
|
75
|
+
// background show through; the wrapper already falls back to the
|
|
76
|
+
// theme's own colour when no override is set, so nothing is lost.
|
|
77
|
+
backgroundColor: 'transparent',
|
|
63
78
|
series: [
|
|
64
79
|
{
|
|
65
80
|
type: 'gauge',
|
|
@@ -258,20 +273,23 @@ class WidgetGauge extends LitElement {
|
|
|
258
273
|
// )
|
|
259
274
|
this.boxes = Array.from(this.gaugeContainer?.querySelectorAll('.chart') as NodeListOf<HTMLDivElement>)
|
|
260
275
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
276
|
+
const gridColumns = `repeat(${fit.c}, 1fr)`
|
|
277
|
+
if (this.gaugeContainer.style.gridTemplateColumns !== gridColumns)
|
|
278
|
+
this.gaugeContainer.style.gridTemplateColumns = gridColumns
|
|
279
|
+
|
|
280
|
+
// This runs on every data tick, not just on real resizes. Touching the
|
|
281
|
+
// style attribute and calling echart.resize() when nothing changed is
|
|
282
|
+
// not free: each resize is a full unanimated re-render that also snaps
|
|
283
|
+
// any running gauge animation, so six gauges hitched on every update.
|
|
284
|
+
// Only boxes whose target size actually differs are written and resized.
|
|
285
|
+
const boxStyle = `width:${modifier * chartW}px; height:${modifier * (chartH - this.textContainerHeight)}px`
|
|
286
|
+
this.boxes?.forEach((box) => {
|
|
287
|
+
if (box.getAttribute('style') === boxStyle) return
|
|
288
|
+
box.setAttribute('style', boxStyle)
|
|
289
|
+
echarts.getInstanceByDom(box)?.resize()
|
|
290
|
+
})
|
|
269
291
|
|
|
270
292
|
this.modifier = modifier
|
|
271
|
-
|
|
272
|
-
this.canvasList.forEach((canvasObj) => {
|
|
273
|
-
canvasObj.echart?.resize()
|
|
274
|
-
})
|
|
275
293
|
}
|
|
276
294
|
|
|
277
295
|
async transformData() {
|
|
@@ -336,45 +354,16 @@ class WidgetGauge extends LitElement {
|
|
|
336
354
|
}
|
|
337
355
|
ds.needleValue = isNaN(ds.needleValue as number) ? gaugeMin : ds.needleValue
|
|
338
356
|
|
|
339
|
-
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
// and ECharts would merge that back one level deeper each update until
|
|
346
|
-
// zrender's recursive merge() overflowed the stack. Both gauge series
|
|
347
|
-
// and every nested key touched below are declared in the template, so
|
|
348
|
-
// a clone of it is a complete option in its own right.
|
|
349
|
-
const option = window.structuredClone(this.template)
|
|
350
|
-
const seriesArr = option.series as GaugeSeriesOption[]
|
|
351
|
-
const ga: any = seriesArr?.[0],
|
|
352
|
-
ga2: any = seriesArr?.[1]
|
|
353
|
-
|
|
354
|
-
// Needle
|
|
355
|
-
// Check age of data Latency
|
|
356
|
-
const tsp = Date.parse(ds?.data?.[0]?.tsp ?? '')
|
|
357
|
-
if (isNaN(tsp)) {
|
|
357
|
+
// Blank the gauge when the data is stale. The newest row sits at the
|
|
358
|
+
// END of the data array (the needle averages `slice(-averageLatest)`),
|
|
359
|
+
// so that is the row whose age matters. Rows without a parseable tsp
|
|
360
|
+
// opt out of the check — without timestamps age cannot be judged.
|
|
361
|
+
const tsp = Date.parse(ds.data?.[ds.data.length - 1]?.tsp ?? '')
|
|
362
|
+
if (!isNaN(tsp)) {
|
|
358
363
|
const now = new Date().getTime()
|
|
359
364
|
if (now - tsp > (ds.advanced?.maxLatency ?? Infinity) * 1000) ds.needleValue = undefined
|
|
360
365
|
}
|
|
361
366
|
|
|
362
|
-
ga.data[0].value = ds.needleValue
|
|
363
|
-
ga.data[0].name = ds.unit
|
|
364
|
-
// unit style
|
|
365
|
-
|
|
366
|
-
ga.title.fontSize = 32 * modifier
|
|
367
|
-
ga.title.color = ds.valueColor || this.themeTitleColor
|
|
368
|
-
ga.title.opacity = 1
|
|
369
|
-
// value style
|
|
370
|
-
ga.detail.color = ds.valueColor || this.themeTitleColor
|
|
371
|
-
ga.detail.opacity = 1
|
|
372
|
-
ga.detail.fontSize = 60 * modifier
|
|
373
|
-
|
|
374
|
-
ga.detail.formatter = (val: number) => {
|
|
375
|
-
const num = Number(val)
|
|
376
|
-
return isNaN(num) ? '-' : num.toFixed(Math.floor(ds.precision ?? 0))
|
|
377
|
-
}
|
|
378
367
|
// Axis
|
|
379
368
|
const defaultColors = ['#bf444c', '#d88273', '#f6efa6']
|
|
380
369
|
const themeColors = this.theme?.theme_object?.color ?? defaultColors
|
|
@@ -409,9 +398,6 @@ class WidgetGauge extends LitElement {
|
|
|
409
398
|
const gaugeMax = sectionLimits?.[sectionLimits.length - 1] ?? 100
|
|
410
399
|
ds.range = Math.abs(gaugeMax - gaugeMin)
|
|
411
400
|
|
|
412
|
-
ga.min = ga2.min = gaugeMin
|
|
413
|
-
ga.max = ga2.max = gaugeMax
|
|
414
|
-
|
|
415
401
|
// percentages of the sections paired with the colors (must be strictly ascending for ECharts)
|
|
416
402
|
const colorSections = sectionLimits
|
|
417
403
|
.map((limit, i) => {
|
|
@@ -421,14 +407,6 @@ class WidgetGauge extends LitElement {
|
|
|
421
407
|
})
|
|
422
408
|
.filter(([s]) => !isNaN(s) && s >= 0)
|
|
423
409
|
|
|
424
|
-
ga2.axisLine.lineStyle.width = 8 * modifier
|
|
425
|
-
if (colorSections.length) ga2.axisLine.lineStyle.color = colorSections
|
|
426
|
-
ga2.axisLabel.fontSize = 24 * modifier
|
|
427
|
-
// ga2.axisLabel.color = ds.valueColor
|
|
428
|
-
ga2.axisLabel.distance = -24 * modifier
|
|
429
|
-
ga2.splitLine.length = 16 * modifier
|
|
430
|
-
ga2.splitLine.distance = -16 * modifier
|
|
431
|
-
|
|
432
410
|
// Progress
|
|
433
411
|
let progressColor = colors?.[colors.length - 1]
|
|
434
412
|
for (const [i, s] of sectionLimits?.entries() ?? []) {
|
|
@@ -440,10 +418,89 @@ class WidgetGauge extends LitElement {
|
|
|
440
418
|
break
|
|
441
419
|
}
|
|
442
420
|
}
|
|
421
|
+
|
|
422
|
+
const canvas = this.canvasList.get(ds.label)
|
|
423
|
+
const echart = canvas?.echart
|
|
424
|
+
|
|
425
|
+
// On a live dashboard this loop runs for every gauge on every data
|
|
426
|
+
// tick, so the steady-state path has to stay off the expensive
|
|
427
|
+
// full-option merge: everything below except the needle value and
|
|
428
|
+
// the progress color is configuration that only changes with the
|
|
429
|
+
// container size (modifier), the widget config, or the theme. When
|
|
430
|
+
// that signature is unchanged, send ECharts only the two values
|
|
431
|
+
// that move; the stored option keeps everything else. lazyUpdate
|
|
432
|
+
// defers processing to the next animation frame so several gauges
|
|
433
|
+
// updating in the same tick do not each force a synchronous render.
|
|
434
|
+
const configSig = JSON.stringify([
|
|
435
|
+
modifier,
|
|
436
|
+
ds.unit,
|
|
437
|
+
ds.precision,
|
|
438
|
+
ds.valueColor,
|
|
439
|
+
ds.sections,
|
|
440
|
+
this.themeTitleColor
|
|
441
|
+
])
|
|
442
|
+
if (canvas && canvas.lastConfig === configSig) {
|
|
443
|
+
echart?.setOption(
|
|
444
|
+
{
|
|
445
|
+
series: [
|
|
446
|
+
{
|
|
447
|
+
data: [{ value: ds.needleValue ?? null, name: ds.unit }],
|
|
448
|
+
progress: { itemStyle: { color: progressColor } }
|
|
449
|
+
}
|
|
450
|
+
]
|
|
451
|
+
},
|
|
452
|
+
{ lazyUpdate: true }
|
|
453
|
+
)
|
|
454
|
+
continue
|
|
455
|
+
}
|
|
456
|
+
if (canvas) canvas.lastConfig = configSig
|
|
457
|
+
|
|
458
|
+
// Always build the option from the template — never from getOption().
|
|
459
|
+
// Reading the rendered option back and handing it to setOption looks
|
|
460
|
+
// cheap but is not: getOption() deep-clones the whole stored option on
|
|
461
|
+
// every frame, and it normalizes every component to an array, so any
|
|
462
|
+
// future `{ ...option.x }` here would spread an array into `{ '0': x }`
|
|
463
|
+
// and ECharts would merge that back one level deeper each update until
|
|
464
|
+
// zrender's recursive merge() overflowed the stack. Both gauge series
|
|
465
|
+
// and every nested key touched below are declared in the template, so
|
|
466
|
+
// a clone of it is a complete option in its own right.
|
|
467
|
+
const option = window.structuredClone(this.template)
|
|
468
|
+
const seriesArr = option.series as GaugeSeriesOption[]
|
|
469
|
+
const ga: any = seriesArr?.[0],
|
|
470
|
+
ga2: any = seriesArr?.[1]
|
|
471
|
+
|
|
472
|
+
ga.data[0].value = ds.needleValue
|
|
473
|
+
ga.data[0].name = ds.unit
|
|
474
|
+
// unit style
|
|
475
|
+
|
|
476
|
+
ga.title.fontSize = 32 * modifier
|
|
477
|
+
ga.title.color = ds.valueColor || this.themeTitleColor
|
|
478
|
+
ga.title.opacity = 1
|
|
479
|
+
// value style
|
|
480
|
+
ga.detail.color = ds.valueColor || this.themeTitleColor
|
|
481
|
+
ga.detail.opacity = 1
|
|
482
|
+
ga.detail.fontSize = 60 * modifier
|
|
483
|
+
|
|
484
|
+
ga.detail.formatter = (val: number) => {
|
|
485
|
+
const num = Number(val)
|
|
486
|
+
return isNaN(num) ? '-' : num.toFixed(Math.floor(ds.precision ?? 0))
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
ga.min = ga2.min = gaugeMin
|
|
490
|
+
ga.max = ga2.max = gaugeMax
|
|
491
|
+
|
|
492
|
+
ga2.axisLine.lineStyle.width = 8 * modifier
|
|
493
|
+
if (colorSections.length) ga2.axisLine.lineStyle.color = colorSections
|
|
494
|
+
ga2.axisLabel.fontSize = 24 * modifier
|
|
495
|
+
// ga2.axisLabel.color = ds.valueColor
|
|
496
|
+
ga2.axisLabel.distance = -24 * modifier
|
|
497
|
+
ga2.splitLine.length = 16 * modifier
|
|
498
|
+
ga2.splitLine.distance = -16 * modifier
|
|
499
|
+
|
|
443
500
|
ga.progress.itemStyle.color = progressColor
|
|
444
501
|
ga.progress.width = 60 * modifier
|
|
445
502
|
|
|
446
|
-
const titleElement =
|
|
503
|
+
const titleElement = canvas?.title
|
|
447
504
|
if (titleElement) {
|
|
448
505
|
titleElement.style.fontSize = String(36 * modifier) + 'px'
|
|
449
506
|
titleElement.style.maxWidth = String(550 * modifier) + 'px'
|
|
@@ -452,7 +509,7 @@ class WidgetGauge extends LitElement {
|
|
|
452
509
|
}
|
|
453
510
|
|
|
454
511
|
// Apply
|
|
455
|
-
echart?.setOption(option)
|
|
512
|
+
echart?.setOption(option, { lazyUpdate: true })
|
|
456
513
|
}
|
|
457
514
|
}
|
|
458
515
|
deleteCharts() {
|