vis-data 7.1.10 → 8.0.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.
Files changed (79) hide show
  1. package/declarations/data-interface.d.ts +14 -14
  2. package/declarations/data-interface.d.ts.map +1 -1
  3. package/declarations/data-pipe.d.ts +5 -5
  4. package/declarations/data-pipe.d.ts.map +1 -1
  5. package/declarations/data-set-check.d.ts +2 -2
  6. package/declarations/data-set-check.d.ts.map +1 -1
  7. package/declarations/data-set-part.d.ts +1 -1
  8. package/declarations/data-set-part.d.ts.map +1 -1
  9. package/declarations/data-set.d.ts +4 -4
  10. package/declarations/data-set.d.ts.map +1 -1
  11. package/declarations/data-stream.d.ts +1 -1
  12. package/declarations/data-stream.d.ts.map +1 -1
  13. package/declarations/data-view-check.d.ts +2 -2
  14. package/declarations/data-view-check.d.ts.map +1 -1
  15. package/declarations/data-view.d.ts +4 -4
  16. package/declarations/data-view.d.ts.map +1 -1
  17. package/declarations/entry-esnext.d.ts +8 -8
  18. package/declarations/entry-esnext.d.ts.map +1 -1
  19. package/declarations/entry-peer.d.ts +1 -1
  20. package/declarations/entry-peer.d.ts.map +1 -1
  21. package/declarations/entry-standalone.d.ts +1 -1
  22. package/declarations/entry-standalone.d.ts.map +1 -1
  23. package/declarations/index.d.ts +4 -4
  24. package/declarations/index.d.ts.map +1 -1
  25. package/declarations/queue.d.ts +1 -1
  26. package/declarations/queue.d.ts.map +1 -1
  27. package/dist/esm.js +9502 -8532
  28. package/dist/esm.js.map +1 -1
  29. package/dist/umd.js +13772 -12802
  30. package/dist/umd.js.map +1 -1
  31. package/esnext/esm/vis-data.js +1957 -1934
  32. package/esnext/esm/vis-data.js.map +1 -1
  33. package/esnext/esm/vis-data.min.js +3 -3
  34. package/esnext/esm/vis-data.min.js.map +1 -1
  35. package/esnext/esm/vis-data.min.mjs +27 -0
  36. package/esnext/esm/vis-data.min.mjs.map +1 -0
  37. package/esnext/esm/vis-data.mjs +2005 -0
  38. package/esnext/esm/vis-data.mjs.map +1 -0
  39. package/esnext/umd/vis-data.cjs +2005 -0
  40. package/esnext/umd/vis-data.cjs.map +1 -0
  41. package/esnext/umd/vis-data.js +1970 -1960
  42. package/esnext/umd/vis-data.js.map +1 -1
  43. package/esnext/umd/vis-data.min.cjs +27 -0
  44. package/esnext/umd/vis-data.min.cjs.map +1 -0
  45. package/esnext/umd/vis-data.min.js +3 -3
  46. package/esnext/umd/vis-data.min.js.map +1 -1
  47. package/package.json +153 -29
  48. package/peer/esm/vis-data.js +9406 -8376
  49. package/peer/esm/vis-data.js.map +1 -1
  50. package/peer/esm/vis-data.min.js +29 -4
  51. package/peer/esm/vis-data.min.js.map +1 -1
  52. package/peer/esm/vis-data.min.mjs +59 -0
  53. package/peer/esm/vis-data.min.mjs.map +1 -0
  54. package/peer/esm/vis-data.mjs +13804 -0
  55. package/peer/esm/vis-data.mjs.map +1 -0
  56. package/peer/umd/vis-data.cjs +13818 -0
  57. package/peer/umd/vis-data.cjs.map +1 -0
  58. package/peer/umd/vis-data.js +13762 -12732
  59. package/peer/umd/vis-data.js.map +1 -1
  60. package/peer/umd/vis-data.min.cjs +59 -0
  61. package/peer/umd/vis-data.min.cjs.map +1 -0
  62. package/peer/umd/vis-data.min.js +34 -9
  63. package/peer/umd/vis-data.min.js.map +1 -1
  64. package/standalone/esm/vis-data.js +9406 -8376
  65. package/standalone/esm/vis-data.js.map +1 -1
  66. package/standalone/esm/vis-data.min.js +29 -4
  67. package/standalone/esm/vis-data.min.js.map +1 -1
  68. package/standalone/esm/vis-data.min.mjs +59 -0
  69. package/standalone/esm/vis-data.min.mjs.map +1 -0
  70. package/standalone/esm/vis-data.mjs +13804 -0
  71. package/standalone/esm/vis-data.mjs.map +1 -0
  72. package/standalone/umd/vis-data.cjs +13818 -0
  73. package/standalone/umd/vis-data.cjs.map +1 -0
  74. package/standalone/umd/vis-data.js +13762 -12732
  75. package/standalone/umd/vis-data.js.map +1 -1
  76. package/standalone/umd/vis-data.min.cjs +59 -0
  77. package/standalone/umd/vis-data.min.cjs.map +1 -0
  78. package/standalone/umd/vis-data.min.js +34 -9
  79. package/standalone/umd/vis-data.min.js.map +1 -1
@@ -0,0 +1,2005 @@
1
+ /**
2
+ * vis-data
3
+ * http://visjs.org/
4
+ *
5
+ * Manage unstructured data using DataSet. Add, update, and remove data, and listen for changes in the data.
6
+ *
7
+ * @version 8.0.0
8
+ * @date 2025-07-12T18:33:36.028Z
9
+ *
10
+ * @copyright (c) 2011-2017 Almende B.V, http://almende.com
11
+ * @copyright (c) 2017-2019 visjs contributors, https://github.com/visjs
12
+ *
13
+ * @license
14
+ * vis.js is dual licensed under both
15
+ *
16
+ * 1. The Apache 2.0 License
17
+ * http://www.apache.org/licenses/LICENSE-2.0
18
+ *
19
+ * and
20
+ *
21
+ * 2. The MIT License
22
+ * http://opensource.org/licenses/MIT
23
+ *
24
+ * vis.js may be distributed under either license.
25
+ */
26
+
27
+ import { pureDeepObjectAssign } from 'vis-util/esnext/esm/vis-util.js';
28
+ export { DELETE } from 'vis-util/esnext/esm/vis-util.js';
29
+ import { v4 } from 'uuid';
30
+
31
+ /**
32
+ * Create new data pipe.
33
+ * @param from - The source data set or data view.
34
+ * @remarks
35
+ * Example usage:
36
+ * ```typescript
37
+ * interface AppItem {
38
+ * whoami: string;
39
+ * appData: unknown;
40
+ * visData: VisItem;
41
+ * }
42
+ * interface VisItem {
43
+ * id: number;
44
+ * label: string;
45
+ * color: string;
46
+ * x: number;
47
+ * y: number;
48
+ * }
49
+ *
50
+ * const ds1 = new DataSet<AppItem, "whoami">([], { fieldId: "whoami" });
51
+ * const ds2 = new DataSet<VisItem, "id">();
52
+ *
53
+ * const pipe = createNewDataPipeFrom(ds1)
54
+ * .filter((item): boolean => item.enabled === true)
55
+ * .map<VisItem, "id">((item): VisItem => item.visData)
56
+ * .to(ds2);
57
+ *
58
+ * pipe.start();
59
+ * ```
60
+ * @returns A factory whose methods can be used to configure the pipe.
61
+ */
62
+ function createNewDataPipeFrom(from) {
63
+ return new DataPipeUnderConstruction(from);
64
+ }
65
+ /**
66
+ * Internal implementation of the pipe. This should be accessible only through
67
+ * `createNewDataPipeFrom` from the outside.
68
+ * @typeParam SI - Source item type.
69
+ * @typeParam SP - Source item type's id property name.
70
+ * @typeParam TI - Target item type.
71
+ * @typeParam TP - Target item type's id property name.
72
+ */
73
+ class SimpleDataPipe {
74
+ _source;
75
+ _transformers;
76
+ _target;
77
+ /**
78
+ * Bound listeners for use with `DataInterface['on' | 'off']`.
79
+ */
80
+ _listeners = {
81
+ add: this._add.bind(this),
82
+ remove: this._remove.bind(this),
83
+ update: this._update.bind(this),
84
+ };
85
+ /**
86
+ * Create a new data pipe.
87
+ * @param source - The data set or data view that will be observed.
88
+ * @param transformers - An array of transforming functions to be used to
89
+ * filter or transform the items in the pipe.
90
+ * @param target - The data set or data view that will receive the items.
91
+ */
92
+ constructor(source, transformers, target) {
93
+ this._source = source;
94
+ this._transformers = transformers;
95
+ this._target = target;
96
+ }
97
+ /** @inheritDoc */
98
+ all() {
99
+ this._target.update(this._transformItems(this._source.get()));
100
+ return this;
101
+ }
102
+ /** @inheritDoc */
103
+ start() {
104
+ this._source.on("add", this._listeners.add);
105
+ this._source.on("remove", this._listeners.remove);
106
+ this._source.on("update", this._listeners.update);
107
+ return this;
108
+ }
109
+ /** @inheritDoc */
110
+ stop() {
111
+ this._source.off("add", this._listeners.add);
112
+ this._source.off("remove", this._listeners.remove);
113
+ this._source.off("update", this._listeners.update);
114
+ return this;
115
+ }
116
+ /**
117
+ * Apply the transformers to the items.
118
+ * @param items - The items to be transformed.
119
+ * @returns The transformed items.
120
+ */
121
+ _transformItems(items) {
122
+ return this._transformers.reduce((items, transform) => {
123
+ return transform(items);
124
+ }, items);
125
+ }
126
+ /**
127
+ * Handle an add event.
128
+ * @param _name - Ignored.
129
+ * @param payload - The payload containing the ids of the added items.
130
+ */
131
+ _add(_name, payload) {
132
+ if (payload == null) {
133
+ return;
134
+ }
135
+ this._target.add(this._transformItems(this._source.get(payload.items)));
136
+ }
137
+ /**
138
+ * Handle an update event.
139
+ * @param _name - Ignored.
140
+ * @param payload - The payload containing the ids of the updated items.
141
+ */
142
+ _update(_name, payload) {
143
+ if (payload == null) {
144
+ return;
145
+ }
146
+ this._target.update(this._transformItems(this._source.get(payload.items)));
147
+ }
148
+ /**
149
+ * Handle a remove event.
150
+ * @param _name - Ignored.
151
+ * @param payload - The payload containing the data of the removed items.
152
+ */
153
+ _remove(_name, payload) {
154
+ if (payload == null) {
155
+ return;
156
+ }
157
+ this._target.remove(this._transformItems(payload.oldData));
158
+ }
159
+ }
160
+ /**
161
+ * Internal implementation of the pipe factory. This should be accessible
162
+ * only through `createNewDataPipeFrom` from the outside.
163
+ * @typeParam TI - Target item type.
164
+ * @typeParam TP - Target item type's id property name.
165
+ */
166
+ class DataPipeUnderConstruction {
167
+ _source;
168
+ /**
169
+ * Array transformers used to transform items within the pipe. This is typed
170
+ * as any for the sake of simplicity.
171
+ */
172
+ _transformers = [];
173
+ /**
174
+ * Create a new data pipe factory. This is an internal constructor that
175
+ * should never be called from outside of this file.
176
+ * @param source - The source data set or data view for this pipe.
177
+ */
178
+ constructor(source) {
179
+ this._source = source;
180
+ }
181
+ /**
182
+ * Filter the items.
183
+ * @param callback - A filtering function that returns true if given item
184
+ * should be piped and false if not.
185
+ * @returns This factory for further configuration.
186
+ */
187
+ filter(callback) {
188
+ this._transformers.push((input) => input.filter(callback));
189
+ return this;
190
+ }
191
+ /**
192
+ * Map each source item to a new type.
193
+ * @param callback - A mapping function that takes a source item and returns
194
+ * corresponding mapped item.
195
+ * @typeParam TI - Target item type.
196
+ * @typeParam TP - Target item type's id property name.
197
+ * @returns This factory for further configuration.
198
+ */
199
+ map(callback) {
200
+ this._transformers.push((input) => input.map(callback));
201
+ return this;
202
+ }
203
+ /**
204
+ * Map each source item to zero or more items of a new type.
205
+ * @param callback - A mapping function that takes a source item and returns
206
+ * an array of corresponding mapped items.
207
+ * @typeParam TI - Target item type.
208
+ * @typeParam TP - Target item type's id property name.
209
+ * @returns This factory for further configuration.
210
+ */
211
+ flatMap(callback) {
212
+ this._transformers.push((input) => input.flatMap(callback));
213
+ return this;
214
+ }
215
+ /**
216
+ * Connect this pipe to given data set.
217
+ * @param target - The data set that will receive the items from this pipe.
218
+ * @returns The pipe connected between given data sets and performing
219
+ * configured transformation on the processed items.
220
+ */
221
+ to(target) {
222
+ return new SimpleDataPipe(this._source, this._transformers, target);
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Determine whether a value can be used as an id.
228
+ * @param value - Input value of unknown type.
229
+ * @returns True if the value is valid id, false otherwise.
230
+ */
231
+ function isId(value) {
232
+ return typeof value === "string" || typeof value === "number";
233
+ }
234
+
235
+ /**
236
+ * A queue.
237
+ * @typeParam T - The type of method names to be replaced by queued versions.
238
+ */
239
+ class Queue {
240
+ /** Delay in milliseconds. If defined the queue will be periodically flushed. */
241
+ delay;
242
+ /** Maximum number of entries in the queue before it will be flushed. */
243
+ max;
244
+ _queue = [];
245
+ _timeout = null;
246
+ _extended = null;
247
+ /**
248
+ * Construct a new Queue.
249
+ * @param options - Queue configuration.
250
+ */
251
+ constructor(options) {
252
+ // options
253
+ this.delay = null;
254
+ this.max = Infinity;
255
+ this.setOptions(options);
256
+ }
257
+ /**
258
+ * Update the configuration of the queue.
259
+ * @param options - Queue configuration.
260
+ */
261
+ setOptions(options) {
262
+ if (options && typeof options.delay !== "undefined") {
263
+ this.delay = options.delay;
264
+ }
265
+ if (options && typeof options.max !== "undefined") {
266
+ this.max = options.max;
267
+ }
268
+ this._flushIfNeeded();
269
+ }
270
+ /**
271
+ * Extend an object with queuing functionality.
272
+ * The object will be extended with a function flush, and the methods provided in options.replace will be replaced with queued ones.
273
+ * @param object - The object to be extended.
274
+ * @param options - Additional options.
275
+ * @returns The created queue.
276
+ */
277
+ static extend(object, options) {
278
+ const queue = new Queue(options);
279
+ if (object.flush !== undefined) {
280
+ throw new Error("Target object already has a property flush");
281
+ }
282
+ object.flush = () => {
283
+ queue.flush();
284
+ };
285
+ const methods = [
286
+ {
287
+ name: "flush",
288
+ original: undefined,
289
+ },
290
+ ];
291
+ if (options && options.replace) {
292
+ for (let i = 0; i < options.replace.length; i++) {
293
+ const name = options.replace[i];
294
+ methods.push({
295
+ name: name,
296
+ // @TODO: better solution?
297
+ original: object[name],
298
+ });
299
+ // @TODO: better solution?
300
+ queue.replace(object, name);
301
+ }
302
+ }
303
+ queue._extended = {
304
+ object: object,
305
+ methods: methods,
306
+ };
307
+ return queue;
308
+ }
309
+ /**
310
+ * Destroy the queue. The queue will first flush all queued actions, and in case it has extended an object, will restore the original object.
311
+ */
312
+ destroy() {
313
+ this.flush();
314
+ if (this._extended) {
315
+ const object = this._extended.object;
316
+ const methods = this._extended.methods;
317
+ for (let i = 0; i < methods.length; i++) {
318
+ const method = methods[i];
319
+ if (method.original) {
320
+ // @TODO: better solution?
321
+ object[method.name] = method.original;
322
+ }
323
+ else {
324
+ // @TODO: better solution?
325
+ delete object[method.name];
326
+ }
327
+ }
328
+ this._extended = null;
329
+ }
330
+ }
331
+ /**
332
+ * Replace a method on an object with a queued version.
333
+ * @param object - Object having the method.
334
+ * @param method - The method name.
335
+ */
336
+ replace(object, method) {
337
+ /* eslint-disable-next-line @typescript-eslint/no-this-alias -- Function this is necessary in the function bellow, so class this has to be saved into a variable here. */
338
+ const me = this;
339
+ const original = object[method];
340
+ if (!original) {
341
+ throw new Error("Method " + method + " undefined");
342
+ }
343
+ object[method] = function (...args) {
344
+ // add this call to the queue
345
+ me.queue({
346
+ args: args,
347
+ fn: original,
348
+ context: this,
349
+ });
350
+ };
351
+ }
352
+ /**
353
+ * Queue a call.
354
+ * @param entry - The function or entry to be queued.
355
+ */
356
+ queue(entry) {
357
+ if (typeof entry === "function") {
358
+ this._queue.push({ fn: entry });
359
+ }
360
+ else {
361
+ this._queue.push(entry);
362
+ }
363
+ this._flushIfNeeded();
364
+ }
365
+ /**
366
+ * Check whether the queue needs to be flushed.
367
+ */
368
+ _flushIfNeeded() {
369
+ // flush when the maximum is exceeded.
370
+ if (this._queue.length > this.max) {
371
+ this.flush();
372
+ }
373
+ // flush after a period of inactivity when a delay is configured
374
+ if (this._timeout != null) {
375
+ clearTimeout(this._timeout);
376
+ this._timeout = null;
377
+ }
378
+ if (this.queue.length > 0 && typeof this.delay === "number") {
379
+ this._timeout = setTimeout(() => {
380
+ this.flush();
381
+ }, this.delay);
382
+ }
383
+ }
384
+ /**
385
+ * Flush all queued calls
386
+ */
387
+ flush() {
388
+ this._queue.splice(0).forEach((entry) => {
389
+ entry.fn.apply(entry.context || entry.fn, entry.args || []);
390
+ });
391
+ }
392
+ }
393
+
394
+ /**
395
+ * {@link DataSet} code that can be reused in {@link DataView} or other similar implementations of {@link DataInterface}.
396
+ * @typeParam Item - Item type that may or may not have an id.
397
+ * @typeParam IdProp - Name of the property that contains the id.
398
+ */
399
+ class DataSetPart {
400
+ _subscribers = {
401
+ "*": [],
402
+ add: [],
403
+ remove: [],
404
+ update: [],
405
+ };
406
+ /**
407
+ * Trigger an event
408
+ * @param event - Event name.
409
+ * @param payload - Event payload.
410
+ * @param senderId - Id of the sender.
411
+ */
412
+ _trigger(event, payload, senderId) {
413
+ if (event === "*") {
414
+ throw new Error("Cannot trigger event *");
415
+ }
416
+ [...this._subscribers[event], ...this._subscribers["*"]].forEach((subscriber) => {
417
+ subscriber(event, payload, senderId != null ? senderId : null);
418
+ });
419
+ }
420
+ /**
421
+ * Subscribe to an event, add an event listener.
422
+ * @remarks Non-function callbacks are ignored.
423
+ * @param event - Event name.
424
+ * @param callback - Callback method.
425
+ */
426
+ on(event, callback) {
427
+ if (typeof callback === "function") {
428
+ this._subscribers[event].push(callback);
429
+ }
430
+ // @TODO: Maybe throw for invalid callbacks?
431
+ }
432
+ /**
433
+ * Unsubscribe from an event, remove an event listener.
434
+ * @remarks If the same callback was subscribed more than once **all** occurences will be removed.
435
+ * @param event - Event name.
436
+ * @param callback - Callback method.
437
+ */
438
+ off(event, callback) {
439
+ this._subscribers[event] = this._subscribers[event].filter((subscriber) => subscriber !== callback);
440
+ }
441
+ /**
442
+ * @deprecated Use on instead (PS: DataView.subscribe === DataView.on).
443
+ */
444
+ subscribe = DataSetPart.prototype.on;
445
+ /**
446
+ * @deprecated Use off instead (PS: DataView.unsubscribe === DataView.off).
447
+ */
448
+ unsubscribe = DataSetPart.prototype.off;
449
+ /* develblock:start */
450
+ get testLeakSubscribers() {
451
+ return this._subscribers;
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Data stream
457
+ * @remarks
458
+ * {@link DataStream} offers an always up to date stream of items from a {@link DataSet} or {@link DataView}.
459
+ * That means that the stream is evaluated at the time of iteration, conversion to another data type or when {@link cache} is called, not when the {@link DataStream} was created.
460
+ * Multiple invocations of for example {@link toItemArray} may yield different results (if the data source like for example {@link DataSet} gets modified).
461
+ * @typeParam Item - The item type this stream is going to work with.
462
+ */
463
+ class DataStream {
464
+ _pairs;
465
+ /**
466
+ * Create a new data stream.
467
+ * @param pairs - The id, item pairs.
468
+ */
469
+ constructor(pairs) {
470
+ this._pairs = pairs;
471
+ }
472
+ /**
473
+ * Return an iterable of key, value pairs for every entry in the stream.
474
+ */
475
+ *[Symbol.iterator]() {
476
+ for (const [id, item] of this._pairs) {
477
+ yield [id, item];
478
+ }
479
+ }
480
+ /**
481
+ * Return an iterable of key, value pairs for every entry in the stream.
482
+ */
483
+ *entries() {
484
+ for (const [id, item] of this._pairs) {
485
+ yield [id, item];
486
+ }
487
+ }
488
+ /**
489
+ * Return an iterable of keys in the stream.
490
+ */
491
+ *keys() {
492
+ for (const [id] of this._pairs) {
493
+ yield id;
494
+ }
495
+ }
496
+ /**
497
+ * Return an iterable of values in the stream.
498
+ */
499
+ *values() {
500
+ for (const [, item] of this._pairs) {
501
+ yield item;
502
+ }
503
+ }
504
+ /**
505
+ * Return an array containing all the ids in this stream.
506
+ * @remarks
507
+ * The array may contain duplicities.
508
+ * @returns The array with all ids from this stream.
509
+ */
510
+ toIdArray() {
511
+ return [...this._pairs].map((pair) => pair[0]);
512
+ }
513
+ /**
514
+ * Return an array containing all the items in this stream.
515
+ * @remarks
516
+ * The array may contain duplicities.
517
+ * @returns The array with all items from this stream.
518
+ */
519
+ toItemArray() {
520
+ return [...this._pairs].map((pair) => pair[1]);
521
+ }
522
+ /**
523
+ * Return an array containing all the entries in this stream.
524
+ * @remarks
525
+ * The array may contain duplicities.
526
+ * @returns The array with all entries from this stream.
527
+ */
528
+ toEntryArray() {
529
+ return [...this._pairs];
530
+ }
531
+ /**
532
+ * Return an object map containing all the items in this stream accessible by ids.
533
+ * @remarks
534
+ * In case of duplicate ids (coerced to string so `7 == '7'`) the last encoutered appears in the returned object.
535
+ * @returns The object map of all id → item pairs from this stream.
536
+ */
537
+ toObjectMap() {
538
+ const map = Object.create(null);
539
+ for (const [id, item] of this._pairs) {
540
+ map[id] = item;
541
+ }
542
+ return map;
543
+ }
544
+ /**
545
+ * Return a map containing all the items in this stream accessible by ids.
546
+ * @returns The map of all id → item pairs from this stream.
547
+ */
548
+ toMap() {
549
+ return new Map(this._pairs);
550
+ }
551
+ /**
552
+ * Return a set containing all the (unique) ids in this stream.
553
+ * @returns The set of all ids from this stream.
554
+ */
555
+ toIdSet() {
556
+ return new Set(this.toIdArray());
557
+ }
558
+ /**
559
+ * Return a set containing all the (unique) items in this stream.
560
+ * @returns The set of all items from this stream.
561
+ */
562
+ toItemSet() {
563
+ return new Set(this.toItemArray());
564
+ }
565
+ /**
566
+ * Cache the items from this stream.
567
+ * @remarks
568
+ * This method allows for items to be fetched immediatelly and used (possibly multiple times) later.
569
+ * It can also be used to optimize performance as {@link DataStream} would otherwise reevaluate everything upon each iteration.
570
+ *
571
+ * ## Example
572
+ * ```javascript
573
+ * const ds = new DataSet([…])
574
+ *
575
+ * const cachedStream = ds.stream()
576
+ * .filter(…)
577
+ * .sort(…)
578
+ * .map(…)
579
+ * .cached(…) // Data are fetched, processed and cached here.
580
+ *
581
+ * ds.clear()
582
+ * chachedStream // Still has all the items.
583
+ * ```
584
+ * @returns A new {@link DataStream} with cached items (detached from the original {@link DataSet}).
585
+ */
586
+ cache() {
587
+ return new DataStream([...this._pairs]);
588
+ }
589
+ /**
590
+ * Get the distinct values of given property.
591
+ * @param callback - The function that picks and possibly converts the property.
592
+ * @typeParam T - The type of the distinct value.
593
+ * @returns A set of all distinct properties.
594
+ */
595
+ distinct(callback) {
596
+ const set = new Set();
597
+ for (const [id, item] of this._pairs) {
598
+ set.add(callback(item, id));
599
+ }
600
+ return set;
601
+ }
602
+ /**
603
+ * Filter the items of the stream.
604
+ * @param callback - The function that decides whether an item will be included.
605
+ * @returns A new data stream with the filtered items.
606
+ */
607
+ filter(callback) {
608
+ const pairs = this._pairs;
609
+ return new DataStream({
610
+ *[Symbol.iterator]() {
611
+ for (const [id, item] of pairs) {
612
+ if (callback(item, id)) {
613
+ yield [id, item];
614
+ }
615
+ }
616
+ },
617
+ });
618
+ }
619
+ /**
620
+ * Execute a callback for each item of the stream.
621
+ * @param callback - The function that will be invoked for each item.
622
+ */
623
+ forEach(callback) {
624
+ for (const [id, item] of this._pairs) {
625
+ callback(item, id);
626
+ }
627
+ }
628
+ /**
629
+ * Map the items into a different type.
630
+ * @param callback - The function that does the conversion.
631
+ * @typeParam Mapped - The type of the item after mapping.
632
+ * @returns A new data stream with the mapped items.
633
+ */
634
+ map(callback) {
635
+ const pairs = this._pairs;
636
+ return new DataStream({
637
+ *[Symbol.iterator]() {
638
+ for (const [id, item] of pairs) {
639
+ yield [id, callback(item, id)];
640
+ }
641
+ },
642
+ });
643
+ }
644
+ /**
645
+ * Get the item with the maximum value of given property.
646
+ * @param callback - The function that picks and possibly converts the property.
647
+ * @returns The item with the maximum if found otherwise null.
648
+ */
649
+ max(callback) {
650
+ const iter = this._pairs[Symbol.iterator]();
651
+ let curr = iter.next();
652
+ if (curr.done) {
653
+ return null;
654
+ }
655
+ let maxItem = curr.value[1];
656
+ let maxValue = callback(curr.value[1], curr.value[0]);
657
+ while (!(curr = iter.next()).done) {
658
+ const [id, item] = curr.value;
659
+ const value = callback(item, id);
660
+ if (value > maxValue) {
661
+ maxValue = value;
662
+ maxItem = item;
663
+ }
664
+ }
665
+ return maxItem;
666
+ }
667
+ /**
668
+ * Get the item with the minimum value of given property.
669
+ * @param callback - The function that picks and possibly converts the property.
670
+ * @returns The item with the minimum if found otherwise null.
671
+ */
672
+ min(callback) {
673
+ const iter = this._pairs[Symbol.iterator]();
674
+ let curr = iter.next();
675
+ if (curr.done) {
676
+ return null;
677
+ }
678
+ let minItem = curr.value[1];
679
+ let minValue = callback(curr.value[1], curr.value[0]);
680
+ while (!(curr = iter.next()).done) {
681
+ const [id, item] = curr.value;
682
+ const value = callback(item, id);
683
+ if (value < minValue) {
684
+ minValue = value;
685
+ minItem = item;
686
+ }
687
+ }
688
+ return minItem;
689
+ }
690
+ /**
691
+ * Reduce the items into a single value.
692
+ * @param callback - The function that does the reduction.
693
+ * @param accumulator - The initial value of the accumulator.
694
+ * @typeParam T - The type of the accumulated value.
695
+ * @returns The reduced value.
696
+ */
697
+ reduce(callback, accumulator) {
698
+ for (const [id, item] of this._pairs) {
699
+ accumulator = callback(accumulator, item, id);
700
+ }
701
+ return accumulator;
702
+ }
703
+ /**
704
+ * Sort the items.
705
+ * @param callback - Item comparator.
706
+ * @returns A new stream with sorted items.
707
+ */
708
+ sort(callback) {
709
+ return new DataStream({
710
+ [Symbol.iterator]: () => [...this._pairs]
711
+ .sort(([idA, itemA], [idB, itemB]) => callback(itemA, itemB, idA, idB))[Symbol.iterator](),
712
+ });
713
+ }
714
+ }
715
+
716
+ /**
717
+ * Add an id to given item if it doesn't have one already.
718
+ * @remarks
719
+ * The item will be modified.
720
+ * @param item - The item that will have an id after a call to this function.
721
+ * @param idProp - The key of the id property.
722
+ * @typeParam Item - Item type that may or may not have an id.
723
+ * @typeParam IdProp - Name of the property that contains the id.
724
+ * @returns true
725
+ */
726
+ function ensureFullItem(item, idProp) {
727
+ if (item[idProp] == null) {
728
+ // generate an id
729
+ item[idProp] = v4();
730
+ }
731
+ return item;
732
+ }
733
+ /**
734
+ * # DataSet
735
+ *
736
+ * Vis.js comes with a flexible DataSet, which can be used to hold and
737
+ * manipulate unstructured data and listen for changes in the data. The DataSet
738
+ * is key/value based. Data items can be added, updated and removed from the
739
+ * DataSet, and one can subscribe to changes in the DataSet. The data in the
740
+ * DataSet can be filtered and ordered. Data can be normalized when appending it
741
+ * to the DataSet as well.
742
+ *
743
+ * ## Example
744
+ *
745
+ * The following example shows how to use a DataSet.
746
+ *
747
+ * ```javascript
748
+ * // create a DataSet
749
+ * var options = {};
750
+ * var data = new vis.DataSet(options);
751
+ *
752
+ * // add items
753
+ * // note that the data items can contain different properties and data formats
754
+ * data.add([
755
+ * {id: 1, text: 'item 1', date: new Date(2013, 6, 20), group: 1, first: true},
756
+ * {id: 2, text: 'item 2', date: '2013-06-23', group: 2},
757
+ * {id: 3, text: 'item 3', date: '2013-06-25', group: 2},
758
+ * {id: 4, text: 'item 4'}
759
+ * ]);
760
+ *
761
+ * // subscribe to any change in the DataSet
762
+ * data.on('*', function (event, properties, senderId) {
763
+ * console.log('event', event, properties);
764
+ * });
765
+ *
766
+ * // update an existing item
767
+ * data.update({id: 2, group: 1});
768
+ *
769
+ * // remove an item
770
+ * data.remove(4);
771
+ *
772
+ * // get all ids
773
+ * var ids = data.getIds();
774
+ * console.log('ids', ids);
775
+ *
776
+ * // get a specific item
777
+ * var item1 = data.get(1);
778
+ * console.log('item1', item1);
779
+ *
780
+ * // retrieve a filtered subset of the data
781
+ * var items = data.get({
782
+ * filter: function (item) {
783
+ * return item.group == 1;
784
+ * }
785
+ * });
786
+ * console.log('filtered items', items);
787
+ * ```
788
+ * @typeParam Item - Item type that may or may not have an id.
789
+ * @typeParam IdProp - Name of the property that contains the id.
790
+ */
791
+ class DataSet extends DataSetPart {
792
+ /** Flush all queued calls. */
793
+ flush;
794
+ /** @inheritDoc */
795
+ length;
796
+ /** @inheritDoc */
797
+ get idProp() {
798
+ return this._idProp;
799
+ }
800
+ _options;
801
+ _data;
802
+ _idProp;
803
+ _queue = null;
804
+ /**
805
+ * Construct a new DataSet.
806
+ * @param data - Initial data or options.
807
+ * @param options - Options (type error if data is also options).
808
+ */
809
+ constructor(data, options) {
810
+ super();
811
+ // correctly read optional arguments
812
+ if (data && !Array.isArray(data)) {
813
+ options = data;
814
+ data = [];
815
+ }
816
+ this._options = options || {};
817
+ this._data = new Map(); // map with data indexed by id
818
+ this.length = 0; // number of items in the DataSet
819
+ this._idProp = this._options.fieldId || "id"; // name of the field containing id
820
+ // add initial data when provided
821
+ if (data && data.length) {
822
+ this.add(data);
823
+ }
824
+ this.setOptions(options);
825
+ }
826
+ /**
827
+ * Set new options.
828
+ * @param options - The new options.
829
+ */
830
+ setOptions(options) {
831
+ if (options && options.queue !== undefined) {
832
+ if (options.queue === false) {
833
+ // delete queue if loaded
834
+ if (this._queue) {
835
+ this._queue.destroy();
836
+ this._queue = null;
837
+ }
838
+ }
839
+ else {
840
+ // create queue and update its options
841
+ if (!this._queue) {
842
+ this._queue = Queue.extend(this, {
843
+ replace: ["add", "update", "remove"],
844
+ });
845
+ }
846
+ if (options.queue && typeof options.queue === "object") {
847
+ this._queue.setOptions(options.queue);
848
+ }
849
+ }
850
+ }
851
+ }
852
+ /**
853
+ * Add a data item or an array with items.
854
+ *
855
+ * After the items are added to the DataSet, the DataSet will trigger an event `add`. When a `senderId` is provided, this id will be passed with the triggered event to all subscribers.
856
+ *
857
+ * ## Example
858
+ *
859
+ * ```javascript
860
+ * // create a DataSet
861
+ * const data = new vis.DataSet()
862
+ *
863
+ * // add items
864
+ * const ids = data.add([
865
+ * { id: 1, text: 'item 1' },
866
+ * { id: 2, text: 'item 2' },
867
+ * { text: 'item without an id' }
868
+ * ])
869
+ *
870
+ * console.log(ids) // [1, 2, '<UUIDv4>']
871
+ * ```
872
+ * @param data - Items to be added (ids will be generated if missing).
873
+ * @param senderId - Sender id.
874
+ * @returns addedIds - Array with the ids (generated if not present) of the added items.
875
+ * @throws When an item with the same id as any of the added items already exists.
876
+ */
877
+ add(data, senderId) {
878
+ const addedIds = [];
879
+ let id;
880
+ if (Array.isArray(data)) {
881
+ // Array
882
+ const idsToAdd = data.map((d) => d[this._idProp]);
883
+ if (idsToAdd.some((id) => this._data.has(id))) {
884
+ throw new Error("A duplicate id was found in the parameter array.");
885
+ }
886
+ for (let i = 0, len = data.length; i < len; i++) {
887
+ id = this._addItem(data[i]);
888
+ addedIds.push(id);
889
+ }
890
+ }
891
+ else if (data && typeof data === "object") {
892
+ // Single item
893
+ id = this._addItem(data);
894
+ addedIds.push(id);
895
+ }
896
+ else {
897
+ throw new Error("Unknown dataType");
898
+ }
899
+ if (addedIds.length) {
900
+ this._trigger("add", { items: addedIds }, senderId);
901
+ }
902
+ return addedIds;
903
+ }
904
+ /**
905
+ * Update existing items. When an item does not exist, it will be created.
906
+ * @remarks
907
+ * The provided properties will be merged in the existing item. When an item does not exist, it will be created.
908
+ *
909
+ * After the items are updated, the DataSet will trigger an event `add` for the added items, and an event `update`. When a `senderId` is provided, this id will be passed with the triggered event to all subscribers.
910
+ *
911
+ * ## Example
912
+ *
913
+ * ```javascript
914
+ * // create a DataSet
915
+ * const data = new vis.DataSet([
916
+ * { id: 1, text: 'item 1' },
917
+ * { id: 2, text: 'item 2' },
918
+ * { id: 3, text: 'item 3' }
919
+ * ])
920
+ *
921
+ * // update items
922
+ * const ids = data.update([
923
+ * { id: 2, text: 'item 2 (updated)' },
924
+ * { id: 4, text: 'item 4 (new)' }
925
+ * ])
926
+ *
927
+ * console.log(ids) // [2, 4]
928
+ * ```
929
+ *
930
+ * ## Warning for TypeScript users
931
+ * This method may introduce partial items into the data set. Use add or updateOnly instead for better type safety.
932
+ * @param data - Items to be updated (if the id is already present) or added (if the id is missing).
933
+ * @param senderId - Sender id.
934
+ * @returns updatedIds - The ids of the added (these may be newly generated if there was no id in the item from the data) or updated items.
935
+ * @throws When the supplied data is neither an item nor an array of items.
936
+ */
937
+ update(data, senderId) {
938
+ const addedIds = [];
939
+ const updatedIds = [];
940
+ const oldData = [];
941
+ const updatedData = [];
942
+ const idProp = this._idProp;
943
+ const addOrUpdate = (item) => {
944
+ const origId = item[idProp];
945
+ if (origId != null && this._data.has(origId)) {
946
+ const fullItem = item; // it has an id, therefore it is a fullitem
947
+ const oldItem = Object.assign({}, this._data.get(origId));
948
+ // update item
949
+ const id = this._updateItem(fullItem);
950
+ updatedIds.push(id);
951
+ updatedData.push(fullItem);
952
+ oldData.push(oldItem);
953
+ }
954
+ else {
955
+ // add new item
956
+ const id = this._addItem(item);
957
+ addedIds.push(id);
958
+ }
959
+ };
960
+ if (Array.isArray(data)) {
961
+ // Array
962
+ for (let i = 0, len = data.length; i < len; i++) {
963
+ if (data[i] && typeof data[i] === "object") {
964
+ addOrUpdate(data[i]);
965
+ }
966
+ else {
967
+ console.warn("Ignoring input item, which is not an object at index " + i);
968
+ }
969
+ }
970
+ }
971
+ else if (data && typeof data === "object") {
972
+ // Single item
973
+ addOrUpdate(data);
974
+ }
975
+ else {
976
+ throw new Error("Unknown dataType");
977
+ }
978
+ if (addedIds.length) {
979
+ this._trigger("add", { items: addedIds }, senderId);
980
+ }
981
+ if (updatedIds.length) {
982
+ const props = { items: updatedIds, oldData: oldData, data: updatedData };
983
+ // TODO: remove deprecated property 'data' some day
984
+ //Object.defineProperty(props, 'data', {
985
+ // 'get': (function() {
986
+ // console.warn('Property data is deprecated. Use DataSet.get(ids) to retrieve the new data, use the oldData property on this object to get the old data');
987
+ // return updatedData;
988
+ // }).bind(this)
989
+ //});
990
+ this._trigger("update", props, senderId);
991
+ }
992
+ return addedIds.concat(updatedIds);
993
+ }
994
+ /**
995
+ * Update existing items. When an item does not exist, an error will be thrown.
996
+ * @remarks
997
+ * The provided properties will be deeply merged into the existing item.
998
+ * When an item does not exist (id not present in the data set or absent), an error will be thrown and nothing will be changed.
999
+ *
1000
+ * After the items are updated, the DataSet will trigger an event `update`.
1001
+ * When a `senderId` is provided, this id will be passed with the triggered event to all subscribers.
1002
+ *
1003
+ * ## Example
1004
+ *
1005
+ * ```javascript
1006
+ * // create a DataSet
1007
+ * const data = new vis.DataSet([
1008
+ * { id: 1, text: 'item 1' },
1009
+ * { id: 2, text: 'item 2' },
1010
+ * { id: 3, text: 'item 3' },
1011
+ * ])
1012
+ *
1013
+ * // update items
1014
+ * const ids = data.update([
1015
+ * { id: 2, text: 'item 2 (updated)' }, // works
1016
+ * // { id: 4, text: 'item 4 (new)' }, // would throw
1017
+ * // { text: 'item 4 (new)' }, // would also throw
1018
+ * ])
1019
+ *
1020
+ * console.log(ids) // [2]
1021
+ * ```
1022
+ * @param data - Updates (the id and optionally other props) to the items in this data set.
1023
+ * @param senderId - Sender id.
1024
+ * @returns updatedIds - The ids of the updated items.
1025
+ * @throws When the supplied data is neither an item nor an array of items, when the ids are missing.
1026
+ */
1027
+ updateOnly(data, senderId) {
1028
+ if (!Array.isArray(data)) {
1029
+ data = [data];
1030
+ }
1031
+ const updateEventData = data
1032
+ .map((update) => {
1033
+ const oldData = this._data.get(update[this._idProp]);
1034
+ if (oldData == null) {
1035
+ throw new Error("Updating non-existent items is not allowed.");
1036
+ }
1037
+ return { oldData, update };
1038
+ })
1039
+ .map(({ oldData, update, }) => {
1040
+ const id = oldData[this._idProp];
1041
+ const updatedData = pureDeepObjectAssign(oldData, update);
1042
+ this._data.set(id, updatedData);
1043
+ return {
1044
+ id,
1045
+ oldData: oldData,
1046
+ updatedData,
1047
+ };
1048
+ });
1049
+ if (updateEventData.length) {
1050
+ const props = {
1051
+ items: updateEventData.map((value) => value.id),
1052
+ oldData: updateEventData.map((value) => value.oldData),
1053
+ data: updateEventData.map((value) => value.updatedData),
1054
+ };
1055
+ // TODO: remove deprecated property 'data' some day
1056
+ //Object.defineProperty(props, 'data', {
1057
+ // 'get': (function() {
1058
+ // console.warn('Property data is deprecated. Use DataSet.get(ids) to retrieve the new data, use the oldData property on this object to get the old data');
1059
+ // return updatedData;
1060
+ // }).bind(this)
1061
+ //});
1062
+ this._trigger("update", props, senderId);
1063
+ return props.items;
1064
+ }
1065
+ else {
1066
+ return [];
1067
+ }
1068
+ }
1069
+ /** @inheritDoc */
1070
+ get(first, second) {
1071
+ // @TODO: Woudn't it be better to split this into multiple methods?
1072
+ // parse the arguments
1073
+ let id = undefined;
1074
+ let ids = undefined;
1075
+ let options = undefined;
1076
+ if (isId(first)) {
1077
+ // get(id [, options])
1078
+ id = first;
1079
+ options = second;
1080
+ }
1081
+ else if (Array.isArray(first)) {
1082
+ // get(ids [, options])
1083
+ ids = first;
1084
+ options = second;
1085
+ }
1086
+ else {
1087
+ // get([, options])
1088
+ options = first;
1089
+ }
1090
+ // determine the return type
1091
+ const returnType = options && options.returnType === "Object" ? "Object" : "Array";
1092
+ // @TODO: WTF is this? Or am I missing something?
1093
+ // var returnType
1094
+ // if (options && options.returnType) {
1095
+ // var allowedValues = ['Array', 'Object']
1096
+ // returnType =
1097
+ // allowedValues.indexOf(options.returnType) == -1
1098
+ // ? 'Array'
1099
+ // : options.returnType
1100
+ // } else {
1101
+ // returnType = 'Array'
1102
+ // }
1103
+ // build options
1104
+ const filter = options && options.filter;
1105
+ const items = [];
1106
+ let item = undefined;
1107
+ let itemIds = undefined;
1108
+ let itemId = undefined;
1109
+ // convert items
1110
+ if (id != null) {
1111
+ // return a single item
1112
+ item = this._data.get(id);
1113
+ if (item && filter && !filter(item)) {
1114
+ item = undefined;
1115
+ }
1116
+ }
1117
+ else if (ids != null) {
1118
+ // return a subset of items
1119
+ for (let i = 0, len = ids.length; i < len; i++) {
1120
+ item = this._data.get(ids[i]);
1121
+ if (item != null && (!filter || filter(item))) {
1122
+ items.push(item);
1123
+ }
1124
+ }
1125
+ }
1126
+ else {
1127
+ // return all items
1128
+ itemIds = [...this._data.keys()];
1129
+ for (let i = 0, len = itemIds.length; i < len; i++) {
1130
+ itemId = itemIds[i];
1131
+ item = this._data.get(itemId);
1132
+ if (item != null && (!filter || filter(item))) {
1133
+ items.push(item);
1134
+ }
1135
+ }
1136
+ }
1137
+ // order the results
1138
+ if (options && options.order && id == undefined) {
1139
+ this._sort(items, options.order);
1140
+ }
1141
+ // filter fields of the items
1142
+ if (options && options.fields) {
1143
+ const fields = options.fields;
1144
+ if (id != undefined && item != null) {
1145
+ item = this._filterFields(item, fields);
1146
+ }
1147
+ else {
1148
+ for (let i = 0, len = items.length; i < len; i++) {
1149
+ items[i] = this._filterFields(items[i], fields);
1150
+ }
1151
+ }
1152
+ }
1153
+ // return the results
1154
+ if (returnType == "Object") {
1155
+ const result = {};
1156
+ for (let i = 0, len = items.length; i < len; i++) {
1157
+ const resultant = items[i];
1158
+ // @TODO: Shoudn't this be this._fieldId?
1159
+ // result[resultant.id] = resultant
1160
+ const id = resultant[this._idProp];
1161
+ result[id] = resultant;
1162
+ }
1163
+ return result;
1164
+ }
1165
+ else {
1166
+ if (id != null) {
1167
+ // a single item
1168
+ return item ?? null;
1169
+ }
1170
+ else {
1171
+ // just return our array
1172
+ return items;
1173
+ }
1174
+ }
1175
+ }
1176
+ /** @inheritDoc */
1177
+ getIds(options) {
1178
+ const data = this._data;
1179
+ const filter = options && options.filter;
1180
+ const order = options && options.order;
1181
+ const itemIds = [...data.keys()];
1182
+ const ids = [];
1183
+ if (filter) {
1184
+ // get filtered items
1185
+ if (order) {
1186
+ // create ordered list
1187
+ const items = [];
1188
+ for (let i = 0, len = itemIds.length; i < len; i++) {
1189
+ const id = itemIds[i];
1190
+ const item = this._data.get(id);
1191
+ if (item != null && filter(item)) {
1192
+ items.push(item);
1193
+ }
1194
+ }
1195
+ this._sort(items, order);
1196
+ for (let i = 0, len = items.length; i < len; i++) {
1197
+ ids.push(items[i][this._idProp]);
1198
+ }
1199
+ }
1200
+ else {
1201
+ // create unordered list
1202
+ for (let i = 0, len = itemIds.length; i < len; i++) {
1203
+ const id = itemIds[i];
1204
+ const item = this._data.get(id);
1205
+ if (item != null && filter(item)) {
1206
+ ids.push(item[this._idProp]);
1207
+ }
1208
+ }
1209
+ }
1210
+ }
1211
+ else {
1212
+ // get all items
1213
+ if (order) {
1214
+ // create an ordered list
1215
+ const items = [];
1216
+ for (let i = 0, len = itemIds.length; i < len; i++) {
1217
+ const id = itemIds[i];
1218
+ items.push(data.get(id));
1219
+ }
1220
+ this._sort(items, order);
1221
+ for (let i = 0, len = items.length; i < len; i++) {
1222
+ ids.push(items[i][this._idProp]);
1223
+ }
1224
+ }
1225
+ else {
1226
+ // create unordered list
1227
+ for (let i = 0, len = itemIds.length; i < len; i++) {
1228
+ const id = itemIds[i];
1229
+ const item = data.get(id);
1230
+ if (item != null) {
1231
+ ids.push(item[this._idProp]);
1232
+ }
1233
+ }
1234
+ }
1235
+ }
1236
+ return ids;
1237
+ }
1238
+ /** @inheritDoc */
1239
+ getDataSet() {
1240
+ return this;
1241
+ }
1242
+ /** @inheritDoc */
1243
+ forEach(callback, options) {
1244
+ const filter = options && options.filter;
1245
+ const data = this._data;
1246
+ const itemIds = [...data.keys()];
1247
+ if (options && options.order) {
1248
+ // execute forEach on ordered list
1249
+ const items = this.get(options);
1250
+ for (let i = 0, len = items.length; i < len; i++) {
1251
+ const item = items[i];
1252
+ const id = item[this._idProp];
1253
+ callback(item, id);
1254
+ }
1255
+ }
1256
+ else {
1257
+ // unordered
1258
+ for (let i = 0, len = itemIds.length; i < len; i++) {
1259
+ const id = itemIds[i];
1260
+ const item = this._data.get(id);
1261
+ if (item != null && (!filter || filter(item))) {
1262
+ callback(item, id);
1263
+ }
1264
+ }
1265
+ }
1266
+ }
1267
+ /** @inheritDoc */
1268
+ map(callback, options) {
1269
+ const filter = options && options.filter;
1270
+ const mappedItems = [];
1271
+ const data = this._data;
1272
+ const itemIds = [...data.keys()];
1273
+ // convert and filter items
1274
+ for (let i = 0, len = itemIds.length; i < len; i++) {
1275
+ const id = itemIds[i];
1276
+ const item = this._data.get(id);
1277
+ if (item != null && (!filter || filter(item))) {
1278
+ mappedItems.push(callback(item, id));
1279
+ }
1280
+ }
1281
+ // order items
1282
+ if (options && options.order) {
1283
+ this._sort(mappedItems, options.order);
1284
+ }
1285
+ return mappedItems;
1286
+ }
1287
+ /**
1288
+ * Filter the fields of an item.
1289
+ * @param item - The item whose fields should be filtered.
1290
+ * @param fields - The names of the fields that will be kept.
1291
+ * @typeParam K - Field name type.
1292
+ * @returns The item without any additional fields.
1293
+ */
1294
+ _filterFields(item, fields) {
1295
+ if (!item) {
1296
+ // item is null
1297
+ return item;
1298
+ }
1299
+ return (Array.isArray(fields)
1300
+ ? // Use the supplied array
1301
+ fields
1302
+ : // Use the keys of the supplied object
1303
+ Object.keys(fields)).reduce((filteredItem, field) => {
1304
+ filteredItem[field] = item[field];
1305
+ return filteredItem;
1306
+ }, {});
1307
+ }
1308
+ /**
1309
+ * Sort the provided array with items.
1310
+ * @param items - Items to be sorted in place.
1311
+ * @param order - A field name or custom sort function.
1312
+ * @typeParam T - The type of the items in the items array.
1313
+ */
1314
+ _sort(items, order) {
1315
+ if (typeof order === "string") {
1316
+ // order by provided field name
1317
+ const name = order; // field name
1318
+ items.sort((a, b) => {
1319
+ // @TODO: How to treat missing properties?
1320
+ const av = a[name];
1321
+ const bv = b[name];
1322
+ return av > bv ? 1 : av < bv ? -1 : 0;
1323
+ });
1324
+ }
1325
+ else if (typeof order === "function") {
1326
+ // order by sort function
1327
+ items.sort(order);
1328
+ }
1329
+ else {
1330
+ // TODO: extend order by an Object {field:string, direction:string}
1331
+ // where direction can be 'asc' or 'desc'
1332
+ throw new TypeError("Order must be a function or a string");
1333
+ }
1334
+ }
1335
+ /**
1336
+ * Remove an item or multiple items by “reference” (only the id is used) or by id.
1337
+ *
1338
+ * The method ignores removal of non-existing items, and returns an array containing the ids of the items which are actually removed from the DataSet.
1339
+ *
1340
+ * After the items are removed, the DataSet will trigger an event `remove` for the removed items. When a `senderId` is provided, this id will be passed with the triggered event to all subscribers.
1341
+ *
1342
+ * ## Example
1343
+ * ```javascript
1344
+ * // create a DataSet
1345
+ * const data = new vis.DataSet([
1346
+ * { id: 1, text: 'item 1' },
1347
+ * { id: 2, text: 'item 2' },
1348
+ * { id: 3, text: 'item 3' }
1349
+ * ])
1350
+ *
1351
+ * // remove items
1352
+ * const ids = data.remove([2, { id: 3 }, 4])
1353
+ *
1354
+ * console.log(ids) // [2, 3]
1355
+ * ```
1356
+ * @param id - One or more items or ids of items to be removed.
1357
+ * @param senderId - Sender id.
1358
+ * @returns The ids of the removed items.
1359
+ */
1360
+ remove(id, senderId) {
1361
+ const removedIds = [];
1362
+ const removedItems = [];
1363
+ // force everything to be an array for simplicity
1364
+ const ids = Array.isArray(id) ? id : [id];
1365
+ for (let i = 0, len = ids.length; i < len; i++) {
1366
+ const item = this._remove(ids[i]);
1367
+ if (item) {
1368
+ const itemId = item[this._idProp];
1369
+ if (itemId != null) {
1370
+ removedIds.push(itemId);
1371
+ removedItems.push(item);
1372
+ }
1373
+ }
1374
+ }
1375
+ if (removedIds.length) {
1376
+ this._trigger("remove", { items: removedIds, oldData: removedItems }, senderId);
1377
+ }
1378
+ return removedIds;
1379
+ }
1380
+ /**
1381
+ * Remove an item by its id or reference.
1382
+ * @param id - Id of an item or the item itself.
1383
+ * @returns The removed item if removed, null otherwise.
1384
+ */
1385
+ _remove(id) {
1386
+ // @TODO: It origianlly returned the item although the docs say id.
1387
+ // The code expects the item, so probably an error in the docs.
1388
+ let ident;
1389
+ // confirm the id to use based on the args type
1390
+ if (isId(id)) {
1391
+ ident = id;
1392
+ }
1393
+ else if (id && typeof id === "object") {
1394
+ ident = id[this._idProp]; // look for the identifier field using ._idProp
1395
+ }
1396
+ // do the removing if the item is found
1397
+ if (ident != null && this._data.has(ident)) {
1398
+ const item = this._data.get(ident) || null;
1399
+ this._data.delete(ident);
1400
+ --this.length;
1401
+ return item;
1402
+ }
1403
+ return null;
1404
+ }
1405
+ /**
1406
+ * Clear the entire data set.
1407
+ *
1408
+ * After the items are removed, the {@link DataSet} will trigger an event `remove` for all removed items. When a `senderId` is provided, this id will be passed with the triggered event to all subscribers.
1409
+ * @param senderId - Sender id.
1410
+ * @returns removedIds - The ids of all removed items.
1411
+ */
1412
+ clear(senderId) {
1413
+ const ids = [...this._data.keys()];
1414
+ const items = [];
1415
+ for (let i = 0, len = ids.length; i < len; i++) {
1416
+ items.push(this._data.get(ids[i]));
1417
+ }
1418
+ this._data.clear();
1419
+ this.length = 0;
1420
+ this._trigger("remove", { items: ids, oldData: items }, senderId);
1421
+ return ids;
1422
+ }
1423
+ /**
1424
+ * Find the item with maximum value of a specified field.
1425
+ * @param field - Name of the property that should be searched for max value.
1426
+ * @returns Item containing max value, or null if no items.
1427
+ */
1428
+ max(field) {
1429
+ let max = null;
1430
+ let maxField = null;
1431
+ for (const item of this._data.values()) {
1432
+ const itemField = item[field];
1433
+ if (typeof itemField === "number" &&
1434
+ (maxField == null || itemField > maxField)) {
1435
+ max = item;
1436
+ maxField = itemField;
1437
+ }
1438
+ }
1439
+ return max || null;
1440
+ }
1441
+ /**
1442
+ * Find the item with minimum value of a specified field.
1443
+ * @param field - Name of the property that should be searched for min value.
1444
+ * @returns Item containing min value, or null if no items.
1445
+ */
1446
+ min(field) {
1447
+ let min = null;
1448
+ let minField = null;
1449
+ for (const item of this._data.values()) {
1450
+ const itemField = item[field];
1451
+ if (typeof itemField === "number" &&
1452
+ (minField == null || itemField < minField)) {
1453
+ min = item;
1454
+ minField = itemField;
1455
+ }
1456
+ }
1457
+ return min || null;
1458
+ }
1459
+ /**
1460
+ * Find all distinct values of a specified field
1461
+ * @param prop - The property name whose distinct values should be returned.
1462
+ * @returns Unordered array containing all distinct values. Items without specified property are ignored.
1463
+ */
1464
+ distinct(prop) {
1465
+ const data = this._data;
1466
+ const itemIds = [...data.keys()];
1467
+ const values = [];
1468
+ let count = 0;
1469
+ for (let i = 0, len = itemIds.length; i < len; i++) {
1470
+ const id = itemIds[i];
1471
+ const item = data.get(id);
1472
+ const value = item[prop];
1473
+ let exists = false;
1474
+ for (let j = 0; j < count; j++) {
1475
+ if (values[j] == value) {
1476
+ exists = true;
1477
+ break;
1478
+ }
1479
+ }
1480
+ if (!exists && value !== undefined) {
1481
+ values[count] = value;
1482
+ count++;
1483
+ }
1484
+ }
1485
+ return values;
1486
+ }
1487
+ /**
1488
+ * Add a single item. Will fail when an item with the same id already exists.
1489
+ * @param item - A new item to be added.
1490
+ * @returns Added item's id. An id is generated when it is not present in the item.
1491
+ */
1492
+ _addItem(item) {
1493
+ const fullItem = ensureFullItem(item, this._idProp);
1494
+ const id = fullItem[this._idProp];
1495
+ // check whether this id is already taken
1496
+ if (this._data.has(id)) {
1497
+ // item already exists
1498
+ throw new Error("Cannot add item: item with id " + id + " already exists");
1499
+ }
1500
+ this._data.set(id, fullItem);
1501
+ ++this.length;
1502
+ return id;
1503
+ }
1504
+ /**
1505
+ * Update a single item: merge with existing item.
1506
+ * Will fail when the item has no id, or when there does not exist an item with the same id.
1507
+ * @param update - The new item
1508
+ * @returns The id of the updated item.
1509
+ */
1510
+ _updateItem(update) {
1511
+ const id = update[this._idProp];
1512
+ if (id == null) {
1513
+ throw new Error("Cannot update item: item has no id (item: " +
1514
+ JSON.stringify(update) +
1515
+ ")");
1516
+ }
1517
+ const item = this._data.get(id);
1518
+ if (!item) {
1519
+ // item doesn't exist
1520
+ throw new Error("Cannot update item: no item with id " + id + " found");
1521
+ }
1522
+ this._data.set(id, { ...item, ...update });
1523
+ return id;
1524
+ }
1525
+ /** @inheritDoc */
1526
+ stream(ids) {
1527
+ if (ids) {
1528
+ const data = this._data;
1529
+ return new DataStream({
1530
+ *[Symbol.iterator]() {
1531
+ for (const id of ids) {
1532
+ const item = data.get(id);
1533
+ if (item != null) {
1534
+ yield [id, item];
1535
+ }
1536
+ }
1537
+ },
1538
+ });
1539
+ }
1540
+ else {
1541
+ return new DataStream({
1542
+ [Symbol.iterator]: this._data.entries.bind(this._data),
1543
+ });
1544
+ }
1545
+ }
1546
+ /* develblock:start */
1547
+ get testLeakData() {
1548
+ return this._data;
1549
+ }
1550
+ get testLeakIdProp() {
1551
+ return this._idProp;
1552
+ }
1553
+ get testLeakOptions() {
1554
+ return this._options;
1555
+ }
1556
+ get testLeakQueue() {
1557
+ return this._queue;
1558
+ }
1559
+ set testLeakQueue(v) {
1560
+ this._queue = v;
1561
+ }
1562
+ }
1563
+
1564
+ /**
1565
+ * DataView
1566
+ *
1567
+ * A DataView offers a filtered and/or formatted view on a DataSet. One can subscribe to changes in a DataView, and easily get filtered or formatted data without having to specify filters and field types all the time.
1568
+ *
1569
+ * ## Example
1570
+ * ```javascript
1571
+ * // create a DataSet
1572
+ * var data = new vis.DataSet();
1573
+ * data.add([
1574
+ * {id: 1, text: 'item 1', date: new Date(2013, 6, 20), group: 1, first: true},
1575
+ * {id: 2, text: 'item 2', date: '2013-06-23', group: 2},
1576
+ * {id: 3, text: 'item 3', date: '2013-06-25', group: 2},
1577
+ * {id: 4, text: 'item 4'}
1578
+ * ]);
1579
+ *
1580
+ * // create a DataView
1581
+ * // the view will only contain items having a property group with value 1,
1582
+ * // and will only output fields id, text, and date.
1583
+ * var view = new vis.DataView(data, {
1584
+ * filter: function (item) {
1585
+ * return (item.group == 1);
1586
+ * },
1587
+ * fields: ['id', 'text', 'date']
1588
+ * });
1589
+ *
1590
+ * // subscribe to any change in the DataView
1591
+ * view.on('*', function (event, properties, senderId) {
1592
+ * console.log('event', event, properties);
1593
+ * });
1594
+ *
1595
+ * // update an item in the data set
1596
+ * data.update({id: 2, group: 1});
1597
+ *
1598
+ * // get all ids in the view
1599
+ * var ids = view.getIds();
1600
+ * console.log('ids', ids); // will output [1, 2]
1601
+ *
1602
+ * // get all items in the view
1603
+ * var items = view.get();
1604
+ * ```
1605
+ * @typeParam Item - Item type that may or may not have an id.
1606
+ * @typeParam IdProp - Name of the property that contains the id.
1607
+ */
1608
+ class DataView extends DataSetPart {
1609
+ /** @inheritDoc */
1610
+ length = 0;
1611
+ /** @inheritDoc */
1612
+ get idProp() {
1613
+ return this.getDataSet().idProp;
1614
+ }
1615
+ _listener;
1616
+ _data; // constructor → setData
1617
+ _ids = new Set(); // ids of the items currently in memory (just contains a boolean true)
1618
+ _options;
1619
+ /**
1620
+ * Create a DataView.
1621
+ * @param data - The instance containing data (directly or indirectly).
1622
+ * @param options - Options to configure this data view.
1623
+ */
1624
+ constructor(data, options) {
1625
+ super();
1626
+ this._options = options || {};
1627
+ this._listener = this._onEvent.bind(this);
1628
+ this.setData(data);
1629
+ }
1630
+ // TODO: implement a function .config() to dynamically update things like configured filter
1631
+ // and trigger changes accordingly
1632
+ /**
1633
+ * Set a data source for the view.
1634
+ * @param data - The instance containing data (directly or indirectly).
1635
+ * @remarks
1636
+ * Note that when the data view is bound to a data set it won't be garbage
1637
+ * collected unless the data set is too. Use `dataView.setData(null)` or
1638
+ * `dataView.dispose()` to enable garbage collection before you lose the last
1639
+ * reference.
1640
+ */
1641
+ setData(data) {
1642
+ if (this._data) {
1643
+ // unsubscribe from current dataset
1644
+ if (this._data.off) {
1645
+ this._data.off("*", this._listener);
1646
+ }
1647
+ // trigger a remove of all items in memory
1648
+ const ids = this._data.getIds({ filter: this._options.filter });
1649
+ const items = this._data.get(ids);
1650
+ this._ids.clear();
1651
+ this.length = 0;
1652
+ this._trigger("remove", { items: ids, oldData: items });
1653
+ }
1654
+ if (data != null) {
1655
+ this._data = data;
1656
+ // trigger an add of all added items
1657
+ const ids = this._data.getIds({ filter: this._options.filter });
1658
+ for (let i = 0, len = ids.length; i < len; i++) {
1659
+ const id = ids[i];
1660
+ this._ids.add(id);
1661
+ }
1662
+ this.length = ids.length;
1663
+ this._trigger("add", { items: ids });
1664
+ }
1665
+ else {
1666
+ this._data = new DataSet();
1667
+ }
1668
+ // subscribe to new dataset
1669
+ if (this._data.on) {
1670
+ this._data.on("*", this._listener);
1671
+ }
1672
+ }
1673
+ /**
1674
+ * Refresh the DataView.
1675
+ * Useful when the DataView has a filter function containing a variable parameter.
1676
+ */
1677
+ refresh() {
1678
+ const ids = this._data.getIds({
1679
+ filter: this._options.filter,
1680
+ });
1681
+ const oldIds = [...this._ids];
1682
+ const newIds = {};
1683
+ const addedIds = [];
1684
+ const removedIds = [];
1685
+ const removedItems = [];
1686
+ // check for additions
1687
+ for (let i = 0, len = ids.length; i < len; i++) {
1688
+ const id = ids[i];
1689
+ newIds[id] = true;
1690
+ if (!this._ids.has(id)) {
1691
+ addedIds.push(id);
1692
+ this._ids.add(id);
1693
+ }
1694
+ }
1695
+ // check for removals
1696
+ for (let i = 0, len = oldIds.length; i < len; i++) {
1697
+ const id = oldIds[i];
1698
+ const item = this._data.get(id);
1699
+ if (item == null) {
1700
+ // @TODO: Investigate.
1701
+ // Doesn't happen during tests or examples.
1702
+ // Is it really impossible or could it eventually happen?
1703
+ // How to handle it if it does? The types guarantee non-nullable items.
1704
+ console.error("If you see this, report it please.");
1705
+ }
1706
+ else if (!newIds[id]) {
1707
+ removedIds.push(id);
1708
+ removedItems.push(item);
1709
+ this._ids.delete(id);
1710
+ }
1711
+ }
1712
+ this.length += addedIds.length - removedIds.length;
1713
+ // trigger events
1714
+ if (addedIds.length) {
1715
+ this._trigger("add", { items: addedIds });
1716
+ }
1717
+ if (removedIds.length) {
1718
+ this._trigger("remove", { items: removedIds, oldData: removedItems });
1719
+ }
1720
+ }
1721
+ /** @inheritDoc */
1722
+ get(first, second) {
1723
+ if (this._data == null) {
1724
+ return null;
1725
+ }
1726
+ // parse the arguments
1727
+ let ids = null;
1728
+ let options;
1729
+ if (isId(first) || Array.isArray(first)) {
1730
+ ids = first;
1731
+ options = second;
1732
+ }
1733
+ else {
1734
+ options = first;
1735
+ }
1736
+ // extend the options with the default options and provided options
1737
+ const viewOptions = Object.assign({}, this._options, options);
1738
+ // create a combined filter method when needed
1739
+ const thisFilter = this._options.filter;
1740
+ const optionsFilter = options && options.filter;
1741
+ if (thisFilter && optionsFilter) {
1742
+ viewOptions.filter = (item) => {
1743
+ return thisFilter(item) && optionsFilter(item);
1744
+ };
1745
+ }
1746
+ if (ids == null) {
1747
+ return this._data.get(viewOptions);
1748
+ }
1749
+ else {
1750
+ return this._data.get(ids, viewOptions);
1751
+ }
1752
+ }
1753
+ /** @inheritDoc */
1754
+ getIds(options) {
1755
+ if (this._data.length) {
1756
+ const defaultFilter = this._options.filter;
1757
+ const optionsFilter = options != null ? options.filter : null;
1758
+ let filter;
1759
+ if (optionsFilter) {
1760
+ if (defaultFilter) {
1761
+ filter = (item) => {
1762
+ return defaultFilter(item) && optionsFilter(item);
1763
+ };
1764
+ }
1765
+ else {
1766
+ filter = optionsFilter;
1767
+ }
1768
+ }
1769
+ else {
1770
+ filter = defaultFilter;
1771
+ }
1772
+ return this._data.getIds({
1773
+ filter: filter,
1774
+ order: options && options.order,
1775
+ });
1776
+ }
1777
+ else {
1778
+ return [];
1779
+ }
1780
+ }
1781
+ /** @inheritDoc */
1782
+ forEach(callback, options) {
1783
+ if (this._data) {
1784
+ const defaultFilter = this._options.filter;
1785
+ const optionsFilter = options && options.filter;
1786
+ let filter;
1787
+ if (optionsFilter) {
1788
+ if (defaultFilter) {
1789
+ filter = function (item) {
1790
+ return defaultFilter(item) && optionsFilter(item);
1791
+ };
1792
+ }
1793
+ else {
1794
+ filter = optionsFilter;
1795
+ }
1796
+ }
1797
+ else {
1798
+ filter = defaultFilter;
1799
+ }
1800
+ this._data.forEach(callback, {
1801
+ filter: filter,
1802
+ order: options && options.order,
1803
+ });
1804
+ }
1805
+ }
1806
+ /** @inheritDoc */
1807
+ map(callback, options) {
1808
+ if (this._data) {
1809
+ const defaultFilter = this._options.filter;
1810
+ const optionsFilter = options && options.filter;
1811
+ let filter;
1812
+ if (optionsFilter) {
1813
+ if (defaultFilter) {
1814
+ filter = (item) => {
1815
+ return defaultFilter(item) && optionsFilter(item);
1816
+ };
1817
+ }
1818
+ else {
1819
+ filter = optionsFilter;
1820
+ }
1821
+ }
1822
+ else {
1823
+ filter = defaultFilter;
1824
+ }
1825
+ return this._data.map(callback, {
1826
+ filter: filter,
1827
+ order: options && options.order,
1828
+ });
1829
+ }
1830
+ else {
1831
+ return [];
1832
+ }
1833
+ }
1834
+ /** @inheritDoc */
1835
+ getDataSet() {
1836
+ return this._data.getDataSet();
1837
+ }
1838
+ /** @inheritDoc */
1839
+ stream(ids) {
1840
+ return this._data.stream(ids || {
1841
+ [Symbol.iterator]: this._ids.keys.bind(this._ids),
1842
+ });
1843
+ }
1844
+ /**
1845
+ * Render the instance unusable prior to garbage collection.
1846
+ * @remarks
1847
+ * The intention of this method is to help discover scenarios where the data
1848
+ * view is being used when the programmer thinks it has been garbage collected
1849
+ * already. It's stricter version of `dataView.setData(null)`.
1850
+ */
1851
+ dispose() {
1852
+ if (this._data?.off) {
1853
+ this._data.off("*", this._listener);
1854
+ }
1855
+ const message = "This data view has already been disposed of.";
1856
+ const replacement = {
1857
+ get: () => {
1858
+ throw new Error(message);
1859
+ },
1860
+ set: () => {
1861
+ throw new Error(message);
1862
+ },
1863
+ configurable: false,
1864
+ };
1865
+ for (const key of Reflect.ownKeys(DataView.prototype)) {
1866
+ Object.defineProperty(this, key, replacement);
1867
+ }
1868
+ }
1869
+ /**
1870
+ * Event listener. Will propagate all events from the connected data set to the subscribers of the DataView, but will filter the items and only trigger when there are changes in the filtered data set.
1871
+ * @param event - The name of the event.
1872
+ * @param params - Parameters of the event.
1873
+ * @param senderId - Id supplied by the sender.
1874
+ */
1875
+ _onEvent(event, params, senderId) {
1876
+ if (!params || !params.items || !this._data) {
1877
+ return;
1878
+ }
1879
+ const ids = params.items;
1880
+ const addedIds = [];
1881
+ const updatedIds = [];
1882
+ const removedIds = [];
1883
+ const oldItems = [];
1884
+ const updatedItems = [];
1885
+ const removedItems = [];
1886
+ switch (event) {
1887
+ case "add":
1888
+ // filter the ids of the added items
1889
+ for (let i = 0, len = ids.length; i < len; i++) {
1890
+ const id = ids[i];
1891
+ const item = this.get(id);
1892
+ if (item) {
1893
+ this._ids.add(id);
1894
+ addedIds.push(id);
1895
+ }
1896
+ }
1897
+ break;
1898
+ case "update":
1899
+ // determine the event from the views viewpoint: an updated
1900
+ // item can be added, updated, or removed from this view.
1901
+ for (let i = 0, len = ids.length; i < len; i++) {
1902
+ const id = ids[i];
1903
+ const item = this.get(id);
1904
+ if (item) {
1905
+ if (this._ids.has(id)) {
1906
+ updatedIds.push(id);
1907
+ updatedItems.push(params.data[i]);
1908
+ oldItems.push(params.oldData[i]);
1909
+ }
1910
+ else {
1911
+ this._ids.add(id);
1912
+ addedIds.push(id);
1913
+ }
1914
+ }
1915
+ else {
1916
+ if (this._ids.has(id)) {
1917
+ this._ids.delete(id);
1918
+ removedIds.push(id);
1919
+ removedItems.push(params.oldData[i]);
1920
+ }
1921
+ }
1922
+ }
1923
+ break;
1924
+ case "remove":
1925
+ // filter the ids of the removed items
1926
+ for (let i = 0, len = ids.length; i < len; i++) {
1927
+ const id = ids[i];
1928
+ if (this._ids.has(id)) {
1929
+ this._ids.delete(id);
1930
+ removedIds.push(id);
1931
+ removedItems.push(params.oldData[i]);
1932
+ }
1933
+ }
1934
+ break;
1935
+ }
1936
+ this.length += addedIds.length - removedIds.length;
1937
+ if (addedIds.length) {
1938
+ this._trigger("add", { items: addedIds }, senderId);
1939
+ }
1940
+ if (updatedIds.length) {
1941
+ this._trigger("update", { items: updatedIds, oldData: oldItems, data: updatedItems }, senderId);
1942
+ }
1943
+ if (removedIds.length) {
1944
+ this._trigger("remove", { items: removedIds, oldData: removedItems }, senderId);
1945
+ }
1946
+ }
1947
+ }
1948
+
1949
+ /**
1950
+ * Check that given value is compatible with Vis Data Set interface.
1951
+ * @param idProp - The expected property to contain item id.
1952
+ * @param v - The value to be tested.
1953
+ * @returns True if all expected values and methods match, false otherwise.
1954
+ */
1955
+ function isDataSetLike(idProp, v) {
1956
+ return (typeof v === "object" &&
1957
+ v !== null &&
1958
+ idProp === v.idProp &&
1959
+ typeof v.add === "function" &&
1960
+ typeof v.clear === "function" &&
1961
+ typeof v.distinct === "function" &&
1962
+ typeof v.forEach === "function" &&
1963
+ typeof v.get === "function" &&
1964
+ typeof v.getDataSet === "function" &&
1965
+ typeof v.getIds === "function" &&
1966
+ typeof v.length === "number" &&
1967
+ typeof v.map === "function" &&
1968
+ typeof v.max === "function" &&
1969
+ typeof v.min === "function" &&
1970
+ typeof v.off === "function" &&
1971
+ typeof v.on === "function" &&
1972
+ typeof v.remove === "function" &&
1973
+ typeof v.setOptions === "function" &&
1974
+ typeof v.stream === "function" &&
1975
+ typeof v.update === "function" &&
1976
+ typeof v.updateOnly === "function");
1977
+ }
1978
+
1979
+ /**
1980
+ * Check that given value is compatible with Vis Data View interface.
1981
+ * @param idProp - The expected property to contain item id.
1982
+ * @param v - The value to be tested.
1983
+ * @returns True if all expected values and methods match, false otherwise.
1984
+ */
1985
+ function isDataViewLike(idProp, v) {
1986
+ return (typeof v === "object" &&
1987
+ v !== null &&
1988
+ idProp === v.idProp &&
1989
+ typeof v.forEach === "function" &&
1990
+ typeof v.get === "function" &&
1991
+ typeof v.getDataSet === "function" &&
1992
+ typeof v.getIds === "function" &&
1993
+ typeof v.length === "number" &&
1994
+ typeof v.map === "function" &&
1995
+ typeof v.off === "function" &&
1996
+ typeof v.on === "function" &&
1997
+ typeof v.stream === "function" &&
1998
+ isDataSetLike(idProp, v.getDataSet()));
1999
+ }
2000
+
2001
+ /* develblock:start */
2002
+ console.warn("You're running a development build.");
2003
+
2004
+ export { DataSet, DataStream, DataView, Queue, createNewDataPipeFrom, isDataSetLike, isDataViewLike };
2005
+ //# sourceMappingURL=vis-data.mjs.map