@ceeblue/web-utils 3.0.0 → 3.1.0
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/dist/web-utils.d.ts +145 -11
- package/dist/web-utils.js +361 -29
- package/dist/web-utils.js.map +1 -1
- package/dist/web-utils.min.js +1 -1
- package/dist/web-utils.min.js.map +1 -1
- package/package.json +1 -1
package/dist/web-utils.d.ts
CHANGED
|
@@ -208,19 +208,69 @@ declare class BitReader extends Loggable {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
|
-
*
|
|
211
|
+
* Class to compute a weighted average byte rate over a specified time interval.
|
|
212
|
+
*
|
|
213
|
+
* This class continuously tracks data transmission and computes the byte rate
|
|
214
|
+
* based on a weighted average, considering both the duration and the number of
|
|
215
|
+
* bytes in each sample. It allows for real-time monitoring of bandwidth usage
|
|
216
|
+
* and provides mechanisms to dynamically adjust the measurement interval.
|
|
217
|
+
*
|
|
218
|
+
* Features:
|
|
219
|
+
* - Computes the byte rate using a **weighted average** approach.
|
|
220
|
+
* - Allows setting a custom interval for tracking.
|
|
221
|
+
* - Supports dynamic clipping to manually shrink the observation window.
|
|
212
222
|
*/
|
|
213
223
|
declare class ByteRate {
|
|
224
|
+
/**
|
|
225
|
+
* Raised when new bytes are added
|
|
226
|
+
*/
|
|
214
227
|
onBytes(bytes: number): void;
|
|
215
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Returns the interval used for computing the byte rate
|
|
230
|
+
*/
|
|
231
|
+
get interval(): number;
|
|
232
|
+
/**
|
|
233
|
+
* Sets a new interval for computing the average byte rate
|
|
234
|
+
*/
|
|
235
|
+
set interval(value: number);
|
|
236
|
+
private _interval;
|
|
216
237
|
private _bytes;
|
|
217
238
|
private _time;
|
|
218
|
-
private
|
|
219
|
-
private
|
|
220
|
-
|
|
239
|
+
private _samples;
|
|
240
|
+
private _clip;
|
|
241
|
+
/**
|
|
242
|
+
* Constructor initializes the ByteRate object with a specified interval (default: 1000ms).
|
|
243
|
+
* It sets up necessary variables to track byte rate over time.
|
|
244
|
+
*
|
|
245
|
+
* @param interval - Time interval in milliseconds to compute the byte rate.
|
|
246
|
+
*/
|
|
247
|
+
constructor(interval?: number);
|
|
248
|
+
/**
|
|
249
|
+
* Returns the computed byte rate rounded to the nearest integer
|
|
250
|
+
*/
|
|
221
251
|
value(): number;
|
|
252
|
+
/**
|
|
253
|
+
* Computes the exact byte rate in bytes per second
|
|
254
|
+
*/
|
|
222
255
|
exact(): number;
|
|
223
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Adds a new byte sample to the tracking system.
|
|
258
|
+
* Updates the list of samples and recomputes the byte rate
|
|
259
|
+
*
|
|
260
|
+
* @param bytes - Number of bytes added in this interval
|
|
261
|
+
*/
|
|
262
|
+
addBytes(bytes: number): ByteRate;
|
|
263
|
+
/**
|
|
264
|
+
* Clears all recorded byte rate data.
|
|
265
|
+
*/
|
|
266
|
+
clear(): ByteRate;
|
|
267
|
+
/**
|
|
268
|
+
* Clips the byte rate tracking by marking the last sample as clipped.
|
|
269
|
+
* If a previous clip exists, removes the clipped sample and all preceding samples.
|
|
270
|
+
* Allows to shrink the interval manually between two positions.
|
|
271
|
+
*/
|
|
272
|
+
clip(): ByteRate;
|
|
273
|
+
private updateSamples;
|
|
224
274
|
}
|
|
225
275
|
|
|
226
276
|
/**
|
|
@@ -350,16 +400,20 @@ declare class EventEmitter extends Loggable {
|
|
|
350
400
|
* Event subscription
|
|
351
401
|
* @param name Name of event without the `on` prefix (ex: `log` to `onLog` event declared)
|
|
352
402
|
* @param event Subscriber Function
|
|
353
|
-
* @param
|
|
403
|
+
* @param options.signal Optional `AbortSignal` to stop this or multiple subscriptions in same time
|
|
354
404
|
*/
|
|
355
|
-
on(name: string, event: Function,
|
|
405
|
+
on(name: string, event: Function, options?: {
|
|
406
|
+
signal?: AbortSignal;
|
|
407
|
+
}): void;
|
|
356
408
|
/**
|
|
357
409
|
* Event subscription only one time, once time fired it's automatically unsubscribe
|
|
358
410
|
* @param name Name of event without the `on` prefix (ex: `log` to `onLog` event declared)
|
|
359
411
|
* @param event Subscriber Function
|
|
360
|
-
* @param
|
|
412
|
+
* @param options.abortSignal Optional `AbortSignal` to stop this or multiple subscriptions in same time
|
|
361
413
|
*/
|
|
362
|
-
once(name: string, event: Function,
|
|
414
|
+
once(name: string, event: Function, options?: {
|
|
415
|
+
signal?: AbortSignal;
|
|
416
|
+
}): void;
|
|
363
417
|
/**
|
|
364
418
|
* Event unsubscription
|
|
365
419
|
* @param name Name of event without the 'on' prefix (ex: 'log' to 'onLog' event declared)
|
|
@@ -1033,6 +1087,86 @@ declare namespace EpochTime {
|
|
|
1033
1087
|
export { EpochTime_decodeTimestamp as decodeTimestamp, EpochTime_encodeTimestamp as encodeTimestamp, EpochTime_getLatency as getLatency };
|
|
1034
1088
|
}
|
|
1035
1089
|
|
|
1090
|
+
/**
|
|
1091
|
+
* Copyright 2024 Ceeblue B.V.
|
|
1092
|
+
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
1093
|
+
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
1094
|
+
*/
|
|
1095
|
+
/**
|
|
1096
|
+
* An user-interface compoment to vizualize real-time metrics
|
|
1097
|
+
*/
|
|
1098
|
+
declare class UIMetrics {
|
|
1099
|
+
/**
|
|
1100
|
+
* get graph margin in pixels
|
|
1101
|
+
*/
|
|
1102
|
+
get graphMargin(): number;
|
|
1103
|
+
/**
|
|
1104
|
+
* set graph margin in pixels
|
|
1105
|
+
*/
|
|
1106
|
+
set graphMargin(value: number);
|
|
1107
|
+
/**
|
|
1108
|
+
* get text margin in pixels
|
|
1109
|
+
*/
|
|
1110
|
+
get textMargin(): number;
|
|
1111
|
+
/**
|
|
1112
|
+
* set text margin in pixels
|
|
1113
|
+
*/
|
|
1114
|
+
set textMargin(value: number);
|
|
1115
|
+
/**
|
|
1116
|
+
* get metric line height in pixels
|
|
1117
|
+
*/
|
|
1118
|
+
get lineHeight(): number;
|
|
1119
|
+
/**
|
|
1120
|
+
* set metric line height in pixels
|
|
1121
|
+
*/
|
|
1122
|
+
set lineHeight(value: number);
|
|
1123
|
+
/**
|
|
1124
|
+
* get label width in pixels
|
|
1125
|
+
*/
|
|
1126
|
+
get labelWidth(): number;
|
|
1127
|
+
/**
|
|
1128
|
+
* set label width in pixels
|
|
1129
|
+
*/
|
|
1130
|
+
set labelWidth(value: number);
|
|
1131
|
+
/**
|
|
1132
|
+
* get legend font size in pixels
|
|
1133
|
+
*/
|
|
1134
|
+
get legendFontSize(): number;
|
|
1135
|
+
/**
|
|
1136
|
+
* set legend font size in pixels
|
|
1137
|
+
*/
|
|
1138
|
+
set legendFontSize(value: number);
|
|
1139
|
+
/**
|
|
1140
|
+
* get the metric unit-step in pixels
|
|
1141
|
+
*/
|
|
1142
|
+
get stepSize(): number;
|
|
1143
|
+
/**
|
|
1144
|
+
* set the metric unit-step in pixels
|
|
1145
|
+
*/
|
|
1146
|
+
set stepSize(value: number);
|
|
1147
|
+
private _ui;
|
|
1148
|
+
private _html?;
|
|
1149
|
+
private _lineHeight;
|
|
1150
|
+
private _labelWidth;
|
|
1151
|
+
private _graphMargin;
|
|
1152
|
+
private _textMargin;
|
|
1153
|
+
private _legendFontSize;
|
|
1154
|
+
private _stepSize;
|
|
1155
|
+
private _ranges;
|
|
1156
|
+
constructor(ui: HTMLElement);
|
|
1157
|
+
/**
|
|
1158
|
+
* Reset metrics stats, essentially rescaling the metrics
|
|
1159
|
+
*/
|
|
1160
|
+
reset(): void;
|
|
1161
|
+
/**
|
|
1162
|
+
* build metric from stats
|
|
1163
|
+
* @param stats Map with stats per entry
|
|
1164
|
+
* @returns
|
|
1165
|
+
*/
|
|
1166
|
+
display(stats: Map<string, Array<string | number>>): void;
|
|
1167
|
+
private _drawCircle;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1036
1170
|
/**
|
|
1037
1171
|
* Copyright 2024 Ceeblue B.V.
|
|
1038
1172
|
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
@@ -1041,4 +1175,4 @@ declare namespace EpochTime {
|
|
|
1041
1175
|
|
|
1042
1176
|
declare const VERSION: string;
|
|
1043
1177
|
|
|
1044
|
-
export { BinaryReader, BinaryWriter, BitReader, ByteRate, Connect, EpochTime, EventEmitter, FixMap, type ILog, Log, LogLevel, Loggable, NetAddress, Numbers, Queue, SDP, Util, VERSION, WebSocketReliable, type WebSocketReliableError, log };
|
|
1178
|
+
export { BinaryReader, BinaryWriter, BitReader, ByteRate, Connect, EpochTime, EventEmitter, FixMap, type ILog, Log, LogLevel, Loggable, NetAddress, Numbers, Queue, SDP, UIMetrics, Util, VERSION, WebSocketReliable, type WebSocketReliableError, log };
|
package/dist/web-utils.js
CHANGED
|
@@ -841,38 +841,146 @@ class BitReader extends Loggable {
|
|
|
841
841
|
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
842
842
|
*/
|
|
843
843
|
/**
|
|
844
|
-
*
|
|
844
|
+
* Class to compute a weighted average byte rate over a specified time interval.
|
|
845
|
+
*
|
|
846
|
+
* This class continuously tracks data transmission and computes the byte rate
|
|
847
|
+
* based on a weighted average, considering both the duration and the number of
|
|
848
|
+
* bytes in each sample. It allows for real-time monitoring of bandwidth usage
|
|
849
|
+
* and provides mechanisms to dynamically adjust the measurement interval.
|
|
850
|
+
*
|
|
851
|
+
* Features:
|
|
852
|
+
* - Computes the byte rate using a **weighted average** approach.
|
|
853
|
+
* - Allows setting a custom interval for tracking.
|
|
854
|
+
* - Supports dynamic clipping to manually shrink the observation window.
|
|
845
855
|
*/
|
|
846
856
|
class ByteRate {
|
|
857
|
+
/**
|
|
858
|
+
* Raised when new bytes are added
|
|
859
|
+
*/
|
|
847
860
|
onBytes(bytes) { }
|
|
848
|
-
|
|
849
|
-
|
|
861
|
+
/**
|
|
862
|
+
* Returns the interval used for computing the byte rate
|
|
863
|
+
*/
|
|
864
|
+
get interval() {
|
|
865
|
+
return this._interval;
|
|
850
866
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
this.
|
|
867
|
+
/**
|
|
868
|
+
* Sets a new interval for computing the average byte rate
|
|
869
|
+
*/
|
|
870
|
+
set interval(value) {
|
|
871
|
+
this._interval = value;
|
|
872
|
+
this.updateSamples();
|
|
856
873
|
}
|
|
874
|
+
/**
|
|
875
|
+
* Constructor initializes the ByteRate object with a specified interval (default: 1000ms).
|
|
876
|
+
* It sets up necessary variables to track byte rate over time.
|
|
877
|
+
*
|
|
878
|
+
* @param interval - Time interval in milliseconds to compute the byte rate.
|
|
879
|
+
*/
|
|
880
|
+
constructor(interval = 1000) {
|
|
881
|
+
this._interval = interval;
|
|
882
|
+
this.clear();
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Returns the computed byte rate rounded to the nearest integer
|
|
886
|
+
*/
|
|
857
887
|
value() {
|
|
858
888
|
return Math.round(this.exact());
|
|
859
889
|
}
|
|
890
|
+
/**
|
|
891
|
+
* Computes the exact byte rate in bytes per second
|
|
892
|
+
*/
|
|
860
893
|
exact() {
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
this._value = (this._bytes * 1000) / elapsed;
|
|
866
|
-
this._bytes = 0;
|
|
867
|
-
this._time = now;
|
|
868
|
-
}
|
|
869
|
-
return this._value;
|
|
894
|
+
// compute rate/s
|
|
895
|
+
this.updateSamples();
|
|
896
|
+
const duration = time() - this._time;
|
|
897
|
+
return duration ? (this._bytes / duration) * 1000 : 0;
|
|
870
898
|
}
|
|
899
|
+
/**
|
|
900
|
+
* Adds a new byte sample to the tracking system.
|
|
901
|
+
* Updates the list of samples and recomputes the byte rate
|
|
902
|
+
*
|
|
903
|
+
* @param bytes - Number of bytes added in this interval
|
|
904
|
+
*/
|
|
871
905
|
addBytes(bytes) {
|
|
906
|
+
var _a;
|
|
907
|
+
const time$1 = time();
|
|
908
|
+
const lastSample = this.updateSamples(time$1)[this._samples.length - 1];
|
|
909
|
+
const lastTime = (_a = lastSample === null || lastSample === void 0 ? void 0 : lastSample.time) !== null && _a !== void 0 ? _a : this._time;
|
|
910
|
+
if (time$1 > lastTime) {
|
|
911
|
+
this._samples.push({ bytes, time: time$1, clip: false });
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
// no new duration => attach byte to last-one
|
|
915
|
+
if (!lastSample) {
|
|
916
|
+
// Ignore, was before our ByteRate scope !
|
|
917
|
+
return this;
|
|
918
|
+
}
|
|
919
|
+
lastSample.bytes += bytes;
|
|
920
|
+
}
|
|
872
921
|
this._bytes += bytes;
|
|
873
922
|
this.onBytes(bytes);
|
|
874
923
|
return this;
|
|
875
924
|
}
|
|
925
|
+
/**
|
|
926
|
+
* Clears all recorded byte rate data.
|
|
927
|
+
*/
|
|
928
|
+
clear() {
|
|
929
|
+
this._bytes = 0;
|
|
930
|
+
this._time = time();
|
|
931
|
+
this._samples = [];
|
|
932
|
+
this._clip = false;
|
|
933
|
+
return this;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Clips the byte rate tracking by marking the last sample as clipped.
|
|
937
|
+
* If a previous clip exists, removes the clipped sample and all preceding samples.
|
|
938
|
+
* Allows to shrink the interval manually between two positions.
|
|
939
|
+
*/
|
|
940
|
+
clip() {
|
|
941
|
+
if (this._clip) {
|
|
942
|
+
this._clip = false;
|
|
943
|
+
let removes = 0;
|
|
944
|
+
for (const sample of this._samples) {
|
|
945
|
+
this._bytes -= sample.bytes;
|
|
946
|
+
++removes;
|
|
947
|
+
this._time = sample.time;
|
|
948
|
+
if (sample.clip) {
|
|
949
|
+
break;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
this._samples.splice(0, removes);
|
|
953
|
+
}
|
|
954
|
+
const lastSample = this._samples[this._samples.length - 1];
|
|
955
|
+
if (lastSample) {
|
|
956
|
+
lastSample.clip = true;
|
|
957
|
+
this._clip = true;
|
|
958
|
+
}
|
|
959
|
+
return this;
|
|
960
|
+
}
|
|
961
|
+
updateSamples(now = time()) {
|
|
962
|
+
// Remove obsolete sample
|
|
963
|
+
const timeOK = now - this._interval;
|
|
964
|
+
let removes = 0;
|
|
965
|
+
let sample;
|
|
966
|
+
while (this._time < timeOK && (sample = this._samples[removes])) {
|
|
967
|
+
this._bytes -= sample.bytes;
|
|
968
|
+
if (sample.clip) {
|
|
969
|
+
this._clip = sample.clip = false;
|
|
970
|
+
}
|
|
971
|
+
if (sample.time > timeOK) {
|
|
972
|
+
// only a part of the sample to delete !
|
|
973
|
+
sample.bytes *= (sample.time - timeOK) / (sample.time - this._time);
|
|
974
|
+
this._time = timeOK;
|
|
975
|
+
this._bytes += sample.bytes;
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
++removes;
|
|
979
|
+
this._time = sample.time;
|
|
980
|
+
}
|
|
981
|
+
this._samples.splice(0, removes);
|
|
982
|
+
return this._samples;
|
|
983
|
+
}
|
|
876
984
|
}/**
|
|
877
985
|
* Copyright 2024 Ceeblue B.V.
|
|
878
986
|
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
@@ -1062,7 +1170,7 @@ function defineMediaExt(type, params) {
|
|
|
1062
1170
|
break;
|
|
1063
1171
|
}
|
|
1064
1172
|
// Fix mediaExt in removing the possible '.' prefix
|
|
1065
|
-
trimStart(params.mediaExt, '.');
|
|
1173
|
+
params.mediaExt = trimStart(params.mediaExt, '.');
|
|
1066
1174
|
}
|
|
1067
1175
|
/**
|
|
1068
1176
|
* Build an URL from {@link Type | type} and {@link Params | params}
|
|
@@ -1196,25 +1304,25 @@ class EventEmitter extends Loggable {
|
|
|
1196
1304
|
* Event subscription
|
|
1197
1305
|
* @param name Name of event without the `on` prefix (ex: `log` to `onLog` event declared)
|
|
1198
1306
|
* @param event Subscriber Function
|
|
1199
|
-
* @param
|
|
1307
|
+
* @param options.signal Optional `AbortSignal` to stop this or multiple subscriptions in same time
|
|
1200
1308
|
*/
|
|
1201
|
-
on(name, event,
|
|
1309
|
+
on(name, event, options) {
|
|
1310
|
+
var _a;
|
|
1202
1311
|
if (!event) {
|
|
1203
1312
|
throw Error('event to subscribe cannot be null');
|
|
1204
1313
|
}
|
|
1205
1314
|
const events = this._event(name);
|
|
1206
1315
|
events.add(event);
|
|
1207
|
-
|
|
1208
|
-
abort.signal.addEventListener('abort', () => events.delete(event), { once: true });
|
|
1209
|
-
}
|
|
1316
|
+
(_a = options === null || options === void 0 ? void 0 : options.signal) === null || _a === void 0 ? void 0 : _a.addEventListener('abort', () => events.delete(event), { once: true });
|
|
1210
1317
|
}
|
|
1211
1318
|
/**
|
|
1212
1319
|
* Event subscription only one time, once time fired it's automatically unsubscribe
|
|
1213
1320
|
* @param name Name of event without the `on` prefix (ex: `log` to `onLog` event declared)
|
|
1214
1321
|
* @param event Subscriber Function
|
|
1215
|
-
* @param
|
|
1322
|
+
* @param options.abortSignal Optional `AbortSignal` to stop this or multiple subscriptions in same time
|
|
1216
1323
|
*/
|
|
1217
|
-
once(name, event,
|
|
1324
|
+
once(name, event, options) {
|
|
1325
|
+
var _a;
|
|
1218
1326
|
if (!event) {
|
|
1219
1327
|
throw Error('event to subscribe cannot be null');
|
|
1220
1328
|
}
|
|
@@ -1223,9 +1331,7 @@ class EventEmitter extends Loggable {
|
|
|
1223
1331
|
events.delete(event); // delete from events
|
|
1224
1332
|
event(...args); // execute event
|
|
1225
1333
|
});
|
|
1226
|
-
|
|
1227
|
-
abort.signal.addEventListener('abort', () => events.delete(event), { once: true });
|
|
1228
|
-
}
|
|
1334
|
+
(_a = options === null || options === void 0 ? void 0 : options.signal) === null || _a === void 0 ? void 0 : _a.addEventListener('abort', () => events.delete(event), { once: true });
|
|
1229
1335
|
}
|
|
1230
1336
|
/**
|
|
1231
1337
|
* Event unsubscription
|
|
@@ -2057,4 +2163,230 @@ function encodeTimestamp(context, lineWidth, blocksPerRow = 32, now = new Date()
|
|
|
2057
2163
|
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
2058
2164
|
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
2059
2165
|
*/
|
|
2060
|
-
|
|
2166
|
+
/**
|
|
2167
|
+
* An user-interface compoment to vizualize real-time metrics
|
|
2168
|
+
*/
|
|
2169
|
+
class UIMetrics {
|
|
2170
|
+
/**
|
|
2171
|
+
* get graph margin in pixels
|
|
2172
|
+
*/
|
|
2173
|
+
get graphMargin() {
|
|
2174
|
+
return this._graphMargin;
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* set graph margin in pixels
|
|
2178
|
+
*/
|
|
2179
|
+
set graphMargin(value) {
|
|
2180
|
+
this._graphMargin = value;
|
|
2181
|
+
}
|
|
2182
|
+
/**
|
|
2183
|
+
* get text margin in pixels
|
|
2184
|
+
*/
|
|
2185
|
+
get textMargin() {
|
|
2186
|
+
return this._textMargin;
|
|
2187
|
+
}
|
|
2188
|
+
/**
|
|
2189
|
+
* set text margin in pixels
|
|
2190
|
+
*/
|
|
2191
|
+
set textMargin(value) {
|
|
2192
|
+
this._textMargin = value;
|
|
2193
|
+
}
|
|
2194
|
+
/**
|
|
2195
|
+
* get metric line height in pixels
|
|
2196
|
+
*/
|
|
2197
|
+
get lineHeight() {
|
|
2198
|
+
return this._lineHeight;
|
|
2199
|
+
}
|
|
2200
|
+
/**
|
|
2201
|
+
* set metric line height in pixels
|
|
2202
|
+
*/
|
|
2203
|
+
set lineHeight(value) {
|
|
2204
|
+
this._lineHeight = value;
|
|
2205
|
+
}
|
|
2206
|
+
/**
|
|
2207
|
+
* get label width in pixels
|
|
2208
|
+
*/
|
|
2209
|
+
get labelWidth() {
|
|
2210
|
+
return this._labelWidth;
|
|
2211
|
+
}
|
|
2212
|
+
/**
|
|
2213
|
+
* set label width in pixels
|
|
2214
|
+
*/
|
|
2215
|
+
set labelWidth(value) {
|
|
2216
|
+
this._labelWidth = value;
|
|
2217
|
+
}
|
|
2218
|
+
/**
|
|
2219
|
+
* get legend font size in pixels
|
|
2220
|
+
*/
|
|
2221
|
+
get legendFontSize() {
|
|
2222
|
+
return this._legendFontSize;
|
|
2223
|
+
}
|
|
2224
|
+
/**
|
|
2225
|
+
* set legend font size in pixels
|
|
2226
|
+
*/
|
|
2227
|
+
set legendFontSize(value) {
|
|
2228
|
+
this._legendFontSize = value;
|
|
2229
|
+
}
|
|
2230
|
+
/**
|
|
2231
|
+
* get the metric unit-step in pixels
|
|
2232
|
+
*/
|
|
2233
|
+
get stepSize() {
|
|
2234
|
+
return this._stepSize;
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* set the metric unit-step in pixels
|
|
2238
|
+
*/
|
|
2239
|
+
set stepSize(value) {
|
|
2240
|
+
this._stepSize = value;
|
|
2241
|
+
}
|
|
2242
|
+
constructor(ui) {
|
|
2243
|
+
this._ui = ui;
|
|
2244
|
+
// default values in pixels
|
|
2245
|
+
this._lineHeight = 40;
|
|
2246
|
+
this._labelWidth = 170;
|
|
2247
|
+
this._graphMargin = 5;
|
|
2248
|
+
this._textMargin = 5;
|
|
2249
|
+
this._legendFontSize = 13;
|
|
2250
|
+
this._stepSize = 10;
|
|
2251
|
+
this._ranges = {};
|
|
2252
|
+
}
|
|
2253
|
+
/**
|
|
2254
|
+
* Reset metrics stats, essentially rescaling the metrics
|
|
2255
|
+
*/
|
|
2256
|
+
reset() {
|
|
2257
|
+
this._ranges = {};
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* build metric from stats
|
|
2261
|
+
* @param stats Map with stats per entry
|
|
2262
|
+
* @returns
|
|
2263
|
+
*/
|
|
2264
|
+
display(stats) {
|
|
2265
|
+
if (this._html != null) {
|
|
2266
|
+
// CPU processing, skip one stats!
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
this._html = '';
|
|
2270
|
+
const averageWidth = (this._legendFontSize / 2) * 7; // 7 chars (1 char width ≈ fontSize/2)
|
|
2271
|
+
const width = this._ui.clientWidth - averageWidth;
|
|
2272
|
+
const graphHeight = this._lineHeight - 2 * this._graphMargin;
|
|
2273
|
+
const graphMiddle = Math.round(this._lineHeight / 2);
|
|
2274
|
+
const textY = Math.round(this._lineHeight / 2 + this._textMargin);
|
|
2275
|
+
const titleWidth = this._labelWidth - 2 * this._textMargin;
|
|
2276
|
+
const averageCenter = averageWidth / 2;
|
|
2277
|
+
for (const [key, values] of stats) {
|
|
2278
|
+
let x = this._labelWidth + values.length * this._stepSize;
|
|
2279
|
+
if (x >= width) {
|
|
2280
|
+
x -= values.splice(0, Math.ceil((x - width) / this._stepSize)).length * this._stepSize;
|
|
2281
|
+
}
|
|
2282
|
+
if (!values.length) {
|
|
2283
|
+
continue;
|
|
2284
|
+
}
|
|
2285
|
+
/*
|
|
2286
|
+
<svg class="list-group-item p-0" style="height: 40px;" xmlns="http://www.w3.org/2000/svg">
|
|
2287
|
+
<text x="5" y="22">M text</text>
|
|
2288
|
+
<path fill="none" d="M100 0 110 22 120 0 130 20" stroke-width="1" stroke="brown"/>
|
|
2289
|
+
</svg>
|
|
2290
|
+
*/
|
|
2291
|
+
this._html +=
|
|
2292
|
+
'<svg class="list-group-item p-0" style="height: ' +
|
|
2293
|
+
this._lineHeight +
|
|
2294
|
+
'px" xmlns="http://www.w3.org/2000/svg">';
|
|
2295
|
+
this._html += '<text x="' + this._textMargin + '" y="' + textY + '">' + key + '</text>';
|
|
2296
|
+
this._html +=
|
|
2297
|
+
'<text x="' +
|
|
2298
|
+
titleWidth +
|
|
2299
|
+
'" y="' +
|
|
2300
|
+
textY +
|
|
2301
|
+
'" text-anchor="end">' +
|
|
2302
|
+
values[values.length - 1].toString() +
|
|
2303
|
+
'</text>';
|
|
2304
|
+
this._html += '<path fill="none" d="M' + this._labelWidth + ' ' + graphMiddle;
|
|
2305
|
+
this._html += 'H' + (width + averageCenter);
|
|
2306
|
+
this._html += '" stroke-width="1" stroke="lightgray" stroke-dasharray="10,10"/>';
|
|
2307
|
+
this._html += '<path fill="none" stroke-width="1" stroke="brown" d="M';
|
|
2308
|
+
let min = Number.POSITIVE_INFINITY;
|
|
2309
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
2310
|
+
for (let i = 0; i < values.length; ++i) {
|
|
2311
|
+
const value = parseFloat(values[i].toString());
|
|
2312
|
+
if (value < min) {
|
|
2313
|
+
min = value;
|
|
2314
|
+
}
|
|
2315
|
+
if (value > max) {
|
|
2316
|
+
max = value;
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
let range = this._ranges[key];
|
|
2320
|
+
if (!range) {
|
|
2321
|
+
this._ranges[key] = range = { min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY };
|
|
2322
|
+
}
|
|
2323
|
+
range.min = Math.min(range.min, min);
|
|
2324
|
+
range.max = Math.max(range.max, max);
|
|
2325
|
+
const delta = range.max - range.min;
|
|
2326
|
+
let minCircle = '';
|
|
2327
|
+
let maxCircle = '';
|
|
2328
|
+
for (let i = 0; i < values.length; ++i) {
|
|
2329
|
+
x -= this._stepSize;
|
|
2330
|
+
const value = parseFloat(values[i].toString());
|
|
2331
|
+
const y = graphMiddle + (delta ? Math.round((0.5 - (value - range.min) / delta) * graphHeight) : 0);
|
|
2332
|
+
this._html += x + ' ' + y + ' ';
|
|
2333
|
+
if (value === min) {
|
|
2334
|
+
maxCircle = maxCircle || this._drawCircle(x, y, value);
|
|
2335
|
+
}
|
|
2336
|
+
else if (value === max) {
|
|
2337
|
+
minCircle = minCircle || this._drawCircle(x, y, value);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
this._html += '" />'; // end path
|
|
2341
|
+
// Average
|
|
2342
|
+
const average = Math.round((max - min) / 2);
|
|
2343
|
+
this._html += '<text text-anchor="middle" font-size="' + this._legendFontSize + '" y="' + textY + '">';
|
|
2344
|
+
this._html +=
|
|
2345
|
+
'<tspan x="' +
|
|
2346
|
+
(width + averageCenter) +
|
|
2347
|
+
'" dy="-0.5em">' +
|
|
2348
|
+
(min !== max ? '≈' : '=') +
|
|
2349
|
+
(min + average) +
|
|
2350
|
+
'</tspan>';
|
|
2351
|
+
this._html += '<tspan x="' + (width + averageCenter) + '" dy="1em">±' + average + '</tspan>';
|
|
2352
|
+
this._html += '</text>';
|
|
2353
|
+
this._html += minCircle + maxCircle;
|
|
2354
|
+
this._html += '</svg>';
|
|
2355
|
+
}
|
|
2356
|
+
requestAnimationFrame(() => {
|
|
2357
|
+
if (this._html != null) {
|
|
2358
|
+
this._ui.innerHTML = this._html;
|
|
2359
|
+
this._html = undefined;
|
|
2360
|
+
}
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
_drawCircle(x, y, value) {
|
|
2364
|
+
let circle = '<circle cx="' + x + '" cy="' + y + '" r="2" fill="green" />';
|
|
2365
|
+
const legendFontHeight = 0.7 * this._legendFontSize;
|
|
2366
|
+
const graphMiddle = Math.round(this._lineHeight / 2);
|
|
2367
|
+
if (y < graphMiddle) {
|
|
2368
|
+
// legend below
|
|
2369
|
+
y += this.textMargin + legendFontHeight;
|
|
2370
|
+
}
|
|
2371
|
+
else {
|
|
2372
|
+
// legend above
|
|
2373
|
+
y -= this.textMargin;
|
|
2374
|
+
}
|
|
2375
|
+
circle +=
|
|
2376
|
+
'<text font-style="italic" font-size="' +
|
|
2377
|
+
this._legendFontSize +
|
|
2378
|
+
'" x="' +
|
|
2379
|
+
(x - this._legendFontSize) +
|
|
2380
|
+
'" y="' +
|
|
2381
|
+
y +
|
|
2382
|
+
'">' +
|
|
2383
|
+
value +
|
|
2384
|
+
'</text>';
|
|
2385
|
+
return circle;
|
|
2386
|
+
}
|
|
2387
|
+
}/**
|
|
2388
|
+
* Copyright 2024 Ceeblue B.V.
|
|
2389
|
+
* This file is part of https://github.com/CeeblueTV/web-utils which is released under GNU Affero General Public License.
|
|
2390
|
+
* See file LICENSE or go to https://spdx.org/licenses/AGPL-3.0-or-later.html for full license details.
|
|
2391
|
+
*/
|
|
2392
|
+
const VERSION = '3.1.0';export{BinaryReader,BinaryWriter,BitReader,ByteRate,Connect,EpochTime,EventEmitter,FixMap,Log,LogLevel,Loggable,NetAddress,Numbers,Queue,SDP,UIMetrics,Util,VERSION,WebSocketReliable,log};//# sourceMappingURL=web-utils.js.map
|