@record-evolution/widget-value 1.1.18 → 1.1.20

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "REWidget widget-value",
4
4
  "license": "MIT",
5
5
  "author": "widget-value",
6
- "version": "1.1.18",
6
+ "version": "1.1.20",
7
7
  "engines": {
8
8
  "node": "18.18.2",
9
9
  "npm": ">=10.0.2"
@@ -28,7 +28,7 @@
28
28
  "release": "npm run build && npm run types && npm version patch --tag-version-prefix='' && git push && git push --tag && npm run build"
29
29
  },
30
30
  "dependencies": {
31
- "lit": "^3.3.0"
31
+ "lit": "^3.3.1"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@custom-elements-manifest/analyzer": "^0.10.4",
@@ -4,28 +4,12 @@
4
4
  "label": "Temperature",
5
5
  "unit": "°C",
6
6
  "precision": 2,
7
- "styling": {},
8
- "advanced": {
9
- "averageLatest": 1
10
- },
11
- "data": [
12
- {
13
- "value": 90
14
- }
15
- ]
7
+ "value": 90
16
8
  },
17
9
  {
18
10
  "label": "Pressure",
19
11
  "unit": "Pa",
20
- "styling": {},
21
- "advanced": {
22
- "averageLatest": 1
23
- },
24
- "data": [
25
- {
26
- "value": 80
27
- }
28
- ]
12
+ "value": 80
29
13
  }
30
14
  ]
31
15
  }
@@ -18,20 +18,20 @@ export type Unit = string;
18
18
  /**
19
19
  * Number of digits after the decimal point. (Default 0)
20
20
  */
21
- export type Precision = number;
21
+ export type Decimals = number;
22
22
  /**
23
- * Calculate the average over the given number of newest values. (If you use "Split data by", then per each of the pivot dataseries.) If not specified then the latest value is shown without modification.
23
+ * The value of the gauge
24
24
  */
25
- export type AverageLatestValues = number;
25
+ export type Value = number;
26
26
  /**
27
- * If you provide timestamp data, the delivered value is only shown in the gauge when the age of the data is not older than the given maximum Latency in seconds.
27
+ * Draw multiple Charts based on the split column of a data table.
28
28
  */
29
- export type MaximumLatency = number;
29
+ export type MultiChart = boolean;
30
30
  /**
31
31
  * This should be an ISO String date like 2023-11-04T22:47:52.351152+00:00. Will only be used to detect data age of data.
32
32
  */
33
33
  export type Timestamp = string;
34
- export type Value = number;
34
+ export type Value1 = number;
35
35
  /**
36
36
  * You can specify a column in the input data to autogenerate dataseries for each distinct entry in this column. E.g. if you have a table with columns [city, timestamp, temperature] and specify 'city' as split column, then you will get a value field for each city.
37
37
  */
@@ -41,17 +41,27 @@ export type SplitDataBy = string;
41
41
  */
42
42
  export type Data = {
43
43
  tsp?: Timestamp;
44
- value?: Value;
44
+ value?: Value1;
45
45
  pivot?: SplitDataBy;
46
46
  [k: string]: unknown;
47
47
  }[];
48
+ /**
49
+ * Calculate the average over the given number of newest values. (If you use "Split data by", then per each of the pivot dataseries.) If not specified then the latest value is shown without modification.
50
+ */
51
+ export type AverageLatestValues = number;
52
+ /**
53
+ * If you provide timestamp data, the delivered value is only shown in the gauge when the age of the data is not older than the given maximum Latency in seconds.
54
+ */
55
+ export type MaximumLatency = number;
48
56
  export type ValueDisplays = {
49
57
  label?: Label;
50
58
  unit?: Unit;
51
- precision?: Precision;
59
+ precision?: Decimals;
60
+ value?: Value;
61
+ multiChart?: MultiChart;
62
+ data?: Data;
52
63
  styling?: Styling;
53
64
  advanced?: AdvancedSettings;
54
- data?: Data;
55
65
  [k: string]: unknown;
56
66
  }[];
57
67
 
@@ -35,17 +35,67 @@
35
35
  "order": 2
36
36
  },
37
37
  "precision": {
38
- "title": "Precision",
38
+ "title": "Decimals",
39
39
  "description": "Number of digits after the decimal point. (Default 0)",
40
40
  "type": "number",
41
41
  "dataDrivenDisabled": true,
42
42
  "order": 3
43
43
  },
44
+ "value": {
45
+ "title": "Value",
46
+ "description": "The value of the gauge",
47
+ "type": "number",
48
+ "condition": {
49
+ "relativePath": "../multiChart",
50
+ "showIfValueIn": [false]
51
+ },
52
+ "order": 5
53
+ },
54
+ "multiChart": {
55
+ "title": "Multi Chart",
56
+ "description": "Draw multiple Charts based on the split column of a data table.",
57
+ "type": "boolean",
58
+ "dataDrivenDisabled": true,
59
+ "order": 6
60
+ },
61
+ "data": {
62
+ "title": "Data",
63
+ "description": "The data used to draw this data series.",
64
+ "type": "array",
65
+ "condition": {
66
+ "relativePath": "../multiChart",
67
+ "showIfValueIn": [true]
68
+ },
69
+ "order": 7,
70
+ "items": {
71
+ "type": "object",
72
+ "properties": {
73
+ "tsp": {
74
+ "title": "Timestamp",
75
+ "description": "This should be an ISO String date like 2023-11-04T22:47:52.351152+00:00. Will only be used to detect data age of data.",
76
+ "type": "string",
77
+ "order": 1
78
+ },
79
+ "value": {
80
+ "title": "Value",
81
+ "type": "number",
82
+ "required": true,
83
+ "order": 1
84
+ },
85
+ "pivot": {
86
+ "title": "Split Data by",
87
+ "description": "You can specify a column in the input data to autogenerate dataseries for each distinct entry in this column. E.g. if you have a table with columns [city, timestamp, temperature] and specify 'city' as split column, then you will get a value field for each city.",
88
+ "type": "string",
89
+ "order": 2
90
+ }
91
+ }
92
+ }
93
+ },
44
94
  "styling": {
45
95
  "title": "Styling",
46
96
  "description": "",
47
97
  "type": "object",
48
- "order": 6,
98
+ "order": 8,
49
99
  "properties": {
50
100
  "labelColor": {
51
101
  "title": "Label Color",
@@ -65,7 +115,7 @@
65
115
  "title": "Advanced Settings",
66
116
  "description": "",
67
117
  "type": "object",
68
- "order": 7,
118
+ "order": 9,
69
119
  "properties": {
70
120
  "averageLatest": {
71
121
  "title": "Average Latest Values",
@@ -82,35 +132,6 @@
82
132
  "order": 7
83
133
  }
84
134
  }
85
- },
86
- "data": {
87
- "title": "Data",
88
- "description": "The data used to draw this data series.",
89
- "type": "array",
90
- "order": 4,
91
- "items": {
92
- "type": "object",
93
- "properties": {
94
- "tsp": {
95
- "title": "Timestamp",
96
- "description": "This should be an ISO String date like 2023-11-04T22:47:52.351152+00:00. Will only be used to detect data age of data.",
97
- "type": "string",
98
- "order": 1
99
- },
100
- "value": {
101
- "title": "Value",
102
- "type": "number",
103
- "required": true,
104
- "order": 1
105
- },
106
- "pivot": {
107
- "title": "Split Data by",
108
- "description": "You can specify a column in the input data to autogenerate dataseries for each distinct entry in this column. E.g. if you have a table with columns [city, timestamp, temperature] and specify 'city' as split column, then you will get a value field for each city.",
109
- "type": "string",
110
- "order": 2
111
- }
112
- }
113
- }
114
135
  }
115
136
  }
116
137
  }
@@ -1,6 +1,6 @@
1
- import { html, css, LitElement, PropertyValueMap } from 'lit'
1
+ import { html, css, LitElement, PropertyValueMap, PropertyValues } from 'lit'
2
2
  import { repeat } from 'lit/directives/repeat.js'
3
- import { customElement, property, state } from 'lit/decorators.js'
3
+ import { customElement, property, query, state } from 'lit/decorators.js'
4
4
  import { InputData } from './definition-schema.js'
5
5
 
6
6
  type Dataseries = Exclude<InputData['dataseries'], undefined>[number] & { needleValue?: number }
@@ -28,14 +28,19 @@ export class WidgetValue extends LitElement {
28
28
  @state() private themeTitleColor?: string
29
29
  @state() private themeSubtitleColor?: string
30
30
 
31
+ @query('.value-container')
32
+ valueContainer?: HTMLDivElement
33
+
34
+ @query('.sizing-container')
35
+ sizingContainer?: HTMLDivElement
36
+
31
37
  version: string = 'versionplaceholder'
32
38
 
33
39
  private resizeObserver: ResizeObserver
40
+ private lastLargerWidthTime?: number
41
+ private origWidth: number = 0
42
+ private origHeight: number = 0
34
43
 
35
- valueContainer?: HTMLDivElement
36
- boxes?: HTMLDivElement[]
37
- origWidth: number = 0
38
- origHeight: number = 0
39
44
  constructor() {
40
45
  super()
41
46
  this.resizeObserver = new ResizeObserver(this.applyData.bind(this))
@@ -50,18 +55,19 @@ export class WidgetValue extends LitElement {
50
55
  }
51
56
 
52
57
  protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>) {
53
- this.valueContainer = this?.shadowRoot?.querySelector('.value-container') as HTMLDivElement
54
-
55
58
  this.registerTheme(this.theme)
56
- this.sizingSetup()
57
- this.transformData()
58
- this.applyData()
59
59
  }
60
60
 
61
- update(changedProperties: Map<string, any>) {
61
+ protected update(changedProperties: PropertyValues): void {
62
62
  if (changedProperties.has('inputData')) {
63
- this.sizingSetup()
64
63
  this.transformData()
64
+ }
65
+ super.update(changedProperties)
66
+ }
67
+
68
+ protected updated(changedProperties: Map<string, any>) {
69
+ if (changedProperties.has('inputData')) {
70
+ this.sizingSetup()
65
71
  this.applyData()
66
72
  }
67
73
 
@@ -69,7 +75,7 @@ export class WidgetValue extends LitElement {
69
75
  this.registerTheme(this.theme)
70
76
  }
71
77
 
72
- super.update(changedProperties)
78
+ super.updated(changedProperties)
73
79
  }
74
80
 
75
81
  registerTheme(theme?: Theme) {
@@ -83,20 +89,32 @@ export class WidgetValue extends LitElement {
83
89
 
84
90
  sizingSetup() {
85
91
  const boxes = Array.from(
86
- this?.shadowRoot?.querySelectorAll(
87
- '.sizing-container > .single-value'
88
- ) as NodeListOf<HTMLDivElement>
92
+ this.sizingContainer?.querySelectorAll('.single-value') as NodeListOf<HTMLDivElement>
89
93
  )
90
94
 
91
- this.origWidth =
92
- boxes?.map((b) => b.getBoundingClientRect().width).reduce((p, c) => (c > p ? c : p), 0) ?? 0
93
- this.origHeight =
94
- boxes?.map((b) => b.getBoundingClientRect().height).reduce((p, c) => (c > p ? c : p), 0) ?? 0
95
+ let width = 0
96
+ let height = 0
97
+ for (const box of boxes) {
98
+ const dims = box.getBoundingClientRect()
99
+ width = Math.max(dims.width, width)
100
+ height = Math.max(dims.height, height)
101
+ }
102
+
103
+ if (width < this.origWidth) {
104
+ if (!this.lastLargerWidthTime || Date.now() - this.lastLargerWidthTime > 5000) {
105
+ this.origWidth = width
106
+ }
107
+ } else {
108
+ this.origWidth = width
109
+ this.lastLargerWidthTime = Date.now()
110
+ }
111
+ this.origHeight = height
95
112
  }
96
113
 
97
114
  applyData() {
98
- const userWidth = this.valueContainer?.getBoundingClientRect().width
99
- const userHeight = this.valueContainer?.getBoundingClientRect().height
115
+ if (!this.valueContainer) return
116
+ const userWidth = this.valueContainer.getBoundingClientRect().width
117
+ const userHeight = this.valueContainer.getBoundingClientRect().height
100
118
  const count = this.dataSets.size
101
119
 
102
120
  const width = this.origWidth
@@ -110,7 +128,7 @@ export class WidgetValue extends LitElement {
110
128
  const uhgap = userHeight - 12 * (r - 1)
111
129
  const m = uwgap / width / c
112
130
  const size = m * m * width * height * count
113
- if (r * m * height <= uhgap) fits.push({ c, m, size, width, height, userWidth, userHeight })
131
+ if (r * m * height <= uhgap) fits.push({ r, c, m, size, width, height, userWidth, userHeight })
114
132
  }
115
133
 
116
134
  for (let r = 1; r <= count; r++) {
@@ -119,53 +137,47 @@ export class WidgetValue extends LitElement {
119
137
  const uhgap = userHeight - 12 * (r - 1)
120
138
  const m = uhgap / height / r
121
139
  const size = m * m * width * height * count
122
- if (c * m * width <= uwgap) fits.push({ r, m, size, width, height, userWidth, userHeight })
140
+ if (c * m * width <= uwgap) fits.push({ r, c, m, size, width, height, userWidth, userHeight })
123
141
  }
124
142
  const maxSize = fits.reduce((p, c) => (c.size < p ? p : c.size), 0)
125
143
  const fit = fits.find((f) => f.size === maxSize)
126
- const modifier = fit?.m ?? 1
144
+ if (!fit) return
145
+ const modifier = fit.m ?? 1
127
146
  // console.log('FITS', fits, 'modifier', modifier, 'cols',fit?.c, 'rows', fit?.r, 'new size', fit?.size.toFixed(0), 'total space', (userWidth* userHeight).toFixed(0))
128
-
129
147
  const boxes = Array.from(
130
- this?.shadowRoot?.querySelectorAll(
131
- '.value-container > .single-value'
132
- ) as NodeListOf<HTMLDivElement>
148
+ this.valueContainer?.querySelectorAll('.single-value') as NodeListOf<HTMLDivElement>
133
149
  )
150
+ this.valueContainer.style.gridTemplateColumns = `repeat(${fit.c}, 1fr)`
134
151
 
135
- boxes?.forEach((box) =>
152
+ for (const box of boxes) {
136
153
  box.setAttribute(
137
154
  'style',
138
155
  `width:${modifier * width}px; height:${modifier * height}px; padding:${modifier * 6}px`
139
156
  )
140
- )
141
157
 
142
- boxes?.forEach((n) => {
143
- const label: string | null = n.getAttribute('label')
158
+ const label: string | null = box.getAttribute('label')
144
159
  const ds: Dataseries | undefined = this.dataSets.get(label ?? '')
145
- const numberText = n.querySelector('.current-value') as HTMLDivElement
160
+
161
+ const numberText = box.querySelector('.current-value') as HTMLDivElement
146
162
  numberText.setAttribute(
147
163
  'style',
148
164
  `font-size: ${32 * modifier}px;
149
165
  color: ${ds?.styling?.valueColor || this.theme?.theme_object?.color?.[0] || this.themeTitleColor};`
150
166
  )
151
- })
152
167
 
153
- boxes?.forEach((n) => {
154
- const label: string | null = n.getAttribute('label')
155
- const ds: Dataseries | undefined = this.dataSets.get(label ?? '')
156
- const labelText = n.querySelector('.label') as HTMLDivElement
168
+ const labelText = box.querySelector('.label') as HTMLDivElement
157
169
  labelText.setAttribute(
158
170
  'style',
159
171
  `font-size: ${26 * modifier}px;
160
172
  color: ${ds?.styling?.labelColor || this.theme?.theme_object?.color?.[1] || this.themeSubtitleColor};`
161
173
  )
162
- const unitText = n.querySelector('.unit') as HTMLDivElement
174
+ const unitText = box.querySelector('.unit') as HTMLDivElement
163
175
  unitText.setAttribute(
164
176
  'style',
165
177
  `font-size: ${26 * modifier}px;
166
178
  color: ${ds?.styling?.valueColor || this.theme?.theme_object?.color?.[0] || this.themeTitleColor};`
167
179
  )
168
- })
180
+ }
169
181
 
170
182
  this.textActive = true
171
183
  }
@@ -180,8 +192,10 @@ export class WidgetValue extends LitElement {
180
192
  // ?.sort((a, b) => a.order - b.order)
181
193
  ?.forEach((ds) => {
182
194
  // pivot data
183
- const distincts = [...new Set(ds.data?.map((d: Data) => d.pivot))].sort() as string[]
184
- ds.needleValue = undefined
195
+ const distincts = ds.multiChart
196
+ ? ([...new Set(ds.data?.map((d: Data) => d.pivot))].sort() as string[])
197
+ : ['']
198
+
185
199
  distincts.forEach((piv) => {
186
200
  const prefix = piv ?? ''
187
201
  const label = ds.label ?? ''
@@ -192,6 +206,8 @@ export class WidgetValue extends LitElement {
192
206
  precision: ds.precision,
193
207
  advanced: ds.advanced,
194
208
  styling: ds.styling,
209
+ multiChart: ds.multiChart,
210
+ value: ds.value,
195
211
  data: distincts.length === 1 ? ds.data : ds.data?.filter((d) => d.pivot === piv),
196
212
  needleValue: undefined
197
213
  }
@@ -205,23 +221,20 @@ export class WidgetValue extends LitElement {
205
221
  if (typeof ds.advanced?.averageLatest !== 'number' || !isNaN(ds.advanced?.averageLatest))
206
222
  ds.advanced.averageLatest = 1
207
223
 
208
- const data = ds?.data?.slice(-ds?.advanced?.averageLatest || -1) ?? []
209
- const values = (data?.map((d) => d.value)?.filter((p) => p !== undefined) ?? []) as number[]
210
- const average = values.reduce((p, c) => p + c, 0) / values.length
211
-
212
- // Check age of data Latency
213
- const tsp = Date.parse(data?.[0]?.tsp ?? '')
214
- if (isNaN(tsp)) {
215
- const now = new Date().getTime()
216
- if (now - tsp > (ds.advanced?.maxLatency ?? Infinity) * 1000) ds.needleValue = undefined
224
+ if (!ds.multiChart) {
225
+ ds.needleValue = ds.value
226
+ } else {
227
+ const data = ds?.data?.slice(-ds?.advanced?.averageLatest || -1) ?? []
228
+ const values = (data?.map((d) => d.value)?.filter((p) => p !== undefined) ?? []) as number[]
229
+ ds.needleValue = values.reduce((p, c) => p + c, 0) / values.length
230
+ // Check age of data Latency
231
+ const tsp = Date.parse(data?.[0]?.tsp ?? '')
232
+ if (isNaN(tsp)) {
233
+ const now = new Date().getTime()
234
+ if (now - tsp > (ds.advanced?.maxLatency ?? Infinity) * 1000) ds.needleValue = undefined
235
+ }
217
236
  }
218
-
219
- ds.needleValue = average
220
237
  })
221
-
222
- this.requestUpdate()
223
- await this.updateComplete
224
- // console.log('Value Datasets', this.dataSets)
225
238
  }
226
239
 
227
240
  static styles = css`
@@ -264,10 +277,7 @@ export class WidgetValue extends LitElement {
264
277
  }
265
278
 
266
279
  .value-container {
267
- display: flex;
268
- flex-wrap: wrap;
269
- align-items: center;
270
- justify-content: center;
280
+ display: grid;
271
281
  line-height: 0.9;
272
282
  flex: 1;
273
283
  overflow: hidden;
@@ -281,7 +291,6 @@ export class WidgetValue extends LitElement {
281
291
  align-items: end;
282
292
  padding: 6px;
283
293
  box-sizing: border-box;
284
- /* border-left: 4px solid #ddd; */
285
294
  }
286
295
 
287
296
  .current-value {
@@ -299,13 +308,8 @@ export class WidgetValue extends LitElement {
299
308
 
300
309
  .sizing-container {
301
310
  position: absolute;
302
- left: 10000px;
303
- display: flex;
311
+ left: 100000px;
304
312
  line-height: 0.9;
305
- flex-wrap: wrap;
306
- align-items: center;
307
- justify-content: center;
308
- flex: 1;
309
313
  overflow: hidden;
310
314
  gap: 12px;
311
315
  }