@record-evolution/widget-gauge 1.5.21

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,394 @@
1
+ import { html, css, LitElement, PropertyValueMap } from 'lit';
2
+ import { repeat } from 'lit/directives/repeat.js'
3
+ import { property, state } from 'lit/decorators.js';
4
+ // import * as echarts from "echarts";
5
+ import { InputData, Data, Dataseries } from './types.js';
6
+ import type { EChartsOption, GaugeSeriesOption } from 'echarts';
7
+
8
+ // echarts.use([GaugeChart, CanvasRenderer]);
9
+
10
+ export class WidgetGauge extends LitElement {
11
+
12
+ @property({ type: Object })
13
+ inputData?: InputData = undefined
14
+
15
+ @state()
16
+ private dataSets: Dataseries[] = []
17
+
18
+ @state()
19
+ private canvasList: any = {}
20
+
21
+ resizeObserver: ResizeObserver
22
+ boxes?: HTMLDivElement[]
23
+ origWidth: number = 0
24
+ origHeight: number = 0
25
+ template: EChartsOption
26
+ modifier: number = 1
27
+ version: string = 'versionplaceholder'
28
+ constructor() {
29
+ super()
30
+ this.resizeObserver = new ResizeObserver(this.adjustSizes.bind(this))
31
+
32
+ this.template = {
33
+ title: {
34
+ text: 'Gauge',
35
+ left: 'center',
36
+ textStyle: {
37
+ fontSize: 10
38
+ }
39
+ },
40
+ series: [
41
+ {
42
+ type: 'gauge',
43
+ startAngle: 180,
44
+ endAngle: 0,
45
+ min: 33,
46
+ max: 99,
47
+ radius: '120%',
48
+ center: ['50%', '90%'],
49
+ progress: {
50
+ show: true,
51
+ clip: true,
52
+ width: 50,
53
+ roundCap: false,
54
+ itemStyle: {
55
+ color: 'auto'
56
+ }
57
+ },
58
+ axisLine: { show: false },
59
+ axisTick: { show: false },
60
+ splitLine: { show: false },
61
+ axisLabel: { show: false },
62
+ anchor: { show: false },
63
+ pointer: { show: false },
64
+ detail: {
65
+ valueAnimation: false,
66
+ fontSize: 25,
67
+ offsetCenter: [0, '-7%'],
68
+ color: 'inherit',
69
+ formatter: (val) => isNaN(val) ? '-' : val.toFixed()
70
+ },
71
+ title: {
72
+ offsetCenter: [0, '-35%'],
73
+ fontSize: 20
74
+ },
75
+ data: [
76
+ {
77
+ value: 70,
78
+ }
79
+ ]
80
+ } as GaugeSeriesOption,
81
+ {
82
+ type: 'gauge',
83
+ startAngle: 180,
84
+ endAngle: 0,
85
+ min: 33,
86
+ max: 99,
87
+ radius: '125%',
88
+ center: ['50%', '90%'],
89
+ axisLine: {
90
+ lineStyle: {
91
+ width: 20,
92
+ color: [
93
+ [0.2, '#67e0e3'],
94
+ [0.8, '#37a2da'],
95
+ [1, '#fd666d']
96
+ ]
97
+ }
98
+ },
99
+ axisTick: { show: false },
100
+ splitLine: {
101
+ length: 15,
102
+ distance: -25,
103
+ lineStyle: {
104
+ width: 2,
105
+ color: 'auto',
106
+ }
107
+ },
108
+ axisLabel: {
109
+ distance: -20,
110
+ color: '#999',
111
+ rotate: 'tangential',
112
+ fontSize: 12,
113
+ },
114
+ } as GaugeSeriesOption,
115
+ ]
116
+ };
117
+
118
+ }
119
+
120
+ update(changedProperties: Map<string, any>) {
121
+ changedProperties.forEach((oldValue, propName) => {
122
+ if (propName === 'inputData') {
123
+ this.transformData()
124
+ this.applyData()
125
+ }
126
+ })
127
+
128
+ super.update(changedProperties)
129
+ }
130
+
131
+ protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
132
+ this.resizeObserver.observe(this.shadowRoot?.querySelector('.wrapper') as HTMLDivElement)
133
+
134
+ this.sizingSetup()
135
+ this.transformData()
136
+ this.adjustSizes()
137
+ this.applyData()
138
+
139
+ }
140
+
141
+ sizingSetup() {
142
+ if (this.origWidth !== 0 && this.origHeight !== 0) return
143
+
144
+ this.boxes = Array.from(this?.shadowRoot?.querySelectorAll('.chart') as NodeListOf<HTMLDivElement>)
145
+ if (!this.boxes.length) return
146
+ this.origWidth = this.boxes?.map(b => b.getBoundingClientRect().width).reduce((p, c) => c > p ? c : p, 0) ?? 0
147
+ this.origHeight = this.boxes?.map(b => b.getBoundingClientRect().height).reduce((p, c) => c > p ? c : p, 0) ?? 0
148
+
149
+ if (this.origWidth > 0) this.origWidth += 16
150
+ if (this.origHeight > 0) this.origHeight += 16
151
+ // console.log('OrigWidth', this.origWidth, this.origHeight)
152
+
153
+ }
154
+
155
+ adjustSizes() {
156
+ // if (!this.origHeight) return
157
+ const container = this.shadowRoot?.querySelector('.gauge-container') as HTMLDivElement
158
+ if (!container) return
159
+ const userWidth = container.getBoundingClientRect().width
160
+ const userHeight = container.getBoundingClientRect().height
161
+ const count = this.dataSets.length
162
+
163
+ const width = this.origWidth
164
+ const height = this.origHeight
165
+ if (!userHeight || !userWidth || !width || !height) return
166
+ const fits = []
167
+ for (let c = 1; c <= count; c++) {
168
+ const r = Math.ceil(count / c)
169
+ const uwgap = (userWidth - 12 * (c - 1))
170
+ const uhgap = (userHeight - 12 * (r - 1))
171
+ const m = uwgap / width / c
172
+ const size = m * m * width * height * count
173
+ if (r * m * height < uhgap) fits.push({ c, m, size, width, height, userWidth, userHeight })
174
+ }
175
+
176
+ for (let r = 1; r <= count; r++) {
177
+ const c = Math.ceil(count / r)
178
+ const uwgap = (userWidth - 12 * (c - 1))
179
+ const uhgap = (userHeight - 12 * (r - 1))
180
+ const m = uhgap / height / r
181
+ const size = m * m * width * height * count
182
+ if (c * m * width < uwgap) fits.push({ r, m, size, width, height, userWidth, userHeight })
183
+ }
184
+
185
+ const maxSize = fits.reduce((p, c) => c.size < p ? p : c.size, 0)
186
+ const fit = fits.find(f => f.size === maxSize)
187
+ const modifier = (fit?.m ?? 0)
188
+
189
+ // console.log('FITS count', count, userWidth, userHeight, 'modifier', modifier, 'cols',fit?.c, 'rows', fit?.r, 'new size', fit?.size.toFixed(0), 'total space', (userWidth* userHeight).toFixed(0))
190
+
191
+ this.boxes?.forEach(box => box.setAttribute("style", `width:${modifier * width}px; height:${modifier * height}px`))
192
+
193
+ this.modifier = modifier
194
+
195
+ for (const canvas in this.canvasList) {
196
+ this.canvasList[canvas].resize()
197
+ }
198
+ this.applyData()
199
+ }
200
+
201
+ async transformData() {
202
+ if (!this?.inputData) return
203
+ this.dataSets = []
204
+ this.inputData.dataseries?.forEach(ds => {
205
+ // pivot data
206
+ const distincts = [...new Set(ds.data.map((d: Data) => d.pivot))]
207
+ if (distincts.length > 1) {
208
+ distincts.forEach((piv) => {
209
+ const pds: any = {
210
+ label: `${ds.label} ${piv}`,
211
+ unit: ds.unit,
212
+ averageLatest: ds.averageLatest,
213
+ valueColor: ds.valueColor,
214
+ sections: ds.sections,
215
+ backgroundColors: ds.backgroundColors,
216
+ data: ds.data.filter(d => d.pivot === piv)
217
+ }
218
+ this.dataSets.push(pds)
219
+ })
220
+ } else {
221
+ this.dataSets.push(ds)
222
+ }
223
+ })
224
+
225
+ // console.log('Gauge Datasets', this.dataSets)
226
+
227
+ }
228
+
229
+ applyData() {
230
+ const modifier = this.modifier
231
+ this.setupCharts()
232
+ for (const ds of this.dataSets) {
233
+
234
+ // compute derivative values
235
+ // filter latest values and calculate average
236
+ if (typeof ds.averageLatest !== 'number' || !isNaN(ds.averageLatest)) ds.averageLatest = 1
237
+ ds.data = ds.data.splice(-ds.averageLatest || -1)
238
+ ds.needleValue = ds.data.map(d => d.value).reduce((p, c) => p + c, 0) / ds.data.length ?? ds.sections?.[0]
239
+
240
+ ds.range = ds.sections?.[ds.sections?.length - 1] - ds.sections?.[0] ?? 100
241
+ if (isNaN(ds.range)) ds.range = 100
242
+ ds.ranges = ds.sections?.map((v, i, a) => v - (a?.[i - 1] ?? 0)).slice(1) ?? []
243
+
244
+ // const option = this.canvasList[ds.label].getOption()
245
+ const option = JSON.parse(JSON.stringify(this.template))
246
+ const ga = option.series[0],
247
+ ga2 = option.series[1]
248
+
249
+ // Title
250
+ option.title.text = ds.label
251
+ option.title.textStyle.fontSize = 22 * modifier
252
+
253
+ // Needle
254
+ ga.data[0].value = ds.needleValue
255
+ ga.data[0].name = ds.unit
256
+ ga.title.fontSize = 20 * modifier
257
+ ga.title.color = ds.valueColor ?? 'black'
258
+ ga.detail.color = ds.valueColor ?? 'black'
259
+ ga.detail.fontSize = 40 * modifier
260
+ ga.detail.formatter = (val: number) => isNaN(val) ? '-' : val.toFixed(0)
261
+ // ga.anchor.itemStyle.color = ds.valueColor
262
+ // ga.pointer.itemStyle.color = ds.valueColor
263
+
264
+ // Axis
265
+ ga2.min = ds.sections?.length ? Math.min(...ds.sections) : 0
266
+ ga2.max = ds.sections?.length ? Math.max(...ds.sections) : 100
267
+ ga.min = ga2.min
268
+ ga.max = ga2.max
269
+ // @ts-ignore
270
+ const colorSections = ds.backgroundColors?.map((b: string, i) => [(ds.sections?.[i + 1] - ga.min) / ds.range, b]).filter(([s]) => !isNaN(s))
271
+ ga2.axisLine.lineStyle.width = 8 * modifier
272
+ ga2.axisLine.lineStyle.color = colorSections?.length ? colorSections : ga2.axisLine.lineStyle.color
273
+ ga2.axisLabel.fontSize = 20 * modifier
274
+ // ga2.axisLabel.color = ds.valueColor
275
+ ga2.axisLabel.distance = -24 * modifier
276
+ ga2.splitLine.length = 16 * modifier
277
+ ga2.splitLine.distance = -16 * modifier
278
+
279
+ // Progress
280
+ let progressColor: string = ds.backgroundColors?.[ds.backgroundColors.length - 1] ?? 'green'
281
+ for (const [i, s] of ds.sections?.entries() ?? []) {
282
+ if (s > ds.needleValue) {
283
+ progressColor = ds.backgroundColors[i - 1] ?? ds.backgroundColors[0]
284
+ break
285
+ }
286
+ }
287
+ ga.progress.itemStyle.color = progressColor
288
+ ga.progress.width = 80 * modifier
289
+ // Apply
290
+ this.canvasList[ds.label]?.setOption(option)
291
+ }
292
+
293
+ }
294
+
295
+ setupCharts() {
296
+
297
+ // remove the gauge canvases of non provided data series
298
+ for (const label in this.canvasList) {
299
+ const ex = this.dataSets.find(ds => ds.label === label)
300
+ if (!ex) delete this.canvasList[label]
301
+ }
302
+
303
+ this.dataSets.forEach(ds => {
304
+ if (this.canvasList[ds.label]) return
305
+ const canvas = this.shadowRoot?.querySelector(`[name="${ds.label}"]`) as HTMLCanvasElement;
306
+ if (!canvas) return
307
+ // @ts-ignore
308
+ this.canvasList[ds.label] = echarts.init(canvas);
309
+ this.canvasList[ds.label].setOption(JSON.parse(JSON.stringify(this.template)))
310
+
311
+ })
312
+
313
+ }
314
+
315
+ static styles = css`
316
+ :host {
317
+ display: block;
318
+ color: var(--re-text-color, #000);
319
+ font-family: sans-serif;
320
+ box-sizing: border-box;
321
+ position: relative;
322
+ margin: auto;
323
+ }
324
+
325
+ .paging:not([active]) { display: none !important; }
326
+
327
+ .wrapper {
328
+ display: flex;
329
+ flex-direction: column;
330
+ height: 100%;
331
+ width: 100%;
332
+ padding: 16px;
333
+ box-sizing: border-box;
334
+ }
335
+ .gauge-container {
336
+ display: flex;
337
+ flex: 1;
338
+ justify-content: center;
339
+ align-items: center;
340
+ flex-wrap: wrap;
341
+ overflow: hidden;
342
+ position: relative;
343
+ gap: 12px;
344
+ }
345
+
346
+ header {
347
+ display: flex;
348
+ flex-direction: column;
349
+ margin: 0 0 16px 0;
350
+ }
351
+ h3 {
352
+ margin: 0;
353
+ max-width: 300px;
354
+ overflow: hidden;
355
+ text-overflow: ellipsis;
356
+ white-space: nowrap;
357
+ color: var(--re-text-color, #000) !important;
358
+ }
359
+ p {
360
+ margin: 10px 0 0 0;
361
+ max-width: 300px;
362
+ font-size: 14px;
363
+ line-height: 17px;
364
+ overflow: hidden;
365
+ text-overflow: ellipsis;
366
+ white-space: nowrap;
367
+ color: var(--re-text-color, #000) !important;
368
+ }
369
+
370
+ .chart {
371
+ width: 600px; /* will be overriden by adjustSizes */
372
+ height: 300px;
373
+ }
374
+
375
+ `;
376
+
377
+ render() {
378
+ return html`
379
+ <div class="wrapper">
380
+ <header>
381
+ <h3 class="paging" ?active=${this.inputData?.settings?.title}>${this.inputData?.settings?.title}</h3>
382
+ <p class="paging" ?active=${this.inputData?.settings?.subTitle}>${this.inputData?.settings?.subTitle}</p>
383
+ </header>
384
+ <div class="gauge-container">
385
+ ${repeat(this.dataSets, ds => ds.label, ds => html`
386
+ <div name="${ds.label}" class="chart" style="min-width: 600px; min-height: 300px; width: 600px; height: 300px;"></div>
387
+ `)}
388
+ </div>
389
+ </div>
390
+ `;
391
+ }
392
+ }
393
+
394
+ window.customElements.define('widget-gauge-versionplaceholder', WidgetGauge);