@vizabi/bubblechart 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2759 @@
1
+ // https://github.com/vizabi/bubblechart#readme v0.0.1 build 1680085520205 Copyright 2023 Gapminder Foundation and contributors
2
+ (function (global, factory) {
3
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('VizabiSharedComponents'), require('d3'), require('mobx')) :
4
+ typeof define === 'function' && define.amd ? define(['VizabiSharedComponents', 'd3', 'mobx'], factory) :
5
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.BubbleChart = factory(global.VizabiSharedComponents, global.d3, global.mobx));
6
+ })(this, (function (VizabiSharedComponents, d3$1, mobx) { 'use strict';
7
+
8
+ class PanZoom {
9
+
10
+ constructor(context) {
11
+ this.context = context;
12
+
13
+ this.dragRectangle = d3$1.drag();
14
+ this.zoomer = d3$1.zoom();
15
+
16
+ // this.dragLock = false;
17
+
18
+ this.dragRectangle
19
+ .filter(event => !event.button)
20
+ .subject(this.dragSubject())
21
+ .on("start", this.drag().start)
22
+ .on("drag", this.drag().go)
23
+ .on("end", this.drag().stop);
24
+
25
+ this.zoomer
26
+ .filter(this.zoomFilter())
27
+ .scaleExtent([0.0625, +Infinity])
28
+ .on("start", this.zoom().start)
29
+ .on("zoom", this.zoom().go)
30
+ .on("end", this.zoom().stop);
31
+
32
+ this.zoomer.ratioX = 1;
33
+ this.zoomer.ratioY = 1;
34
+
35
+ this.context._zoomedXYMinMax = {
36
+ x: { zoomedMin: null, zoomedMax: null },
37
+ y: { zoomedMin: null, zoomedMax: null }
38
+ };
39
+ }
40
+
41
+ dragSubject() {
42
+ const _this = this.context;
43
+
44
+ return function(event) {
45
+ /*
46
+ * Do not drag if the Ctrl key, Meta key, or plus cursor mode is
47
+ * not enabled. Also do not drag if zoom-pinching on touchmove
48
+ * events.
49
+ */
50
+ if (!(event.sourceEvent.ctrlKey || event.sourceEvent.metaKey ||
51
+ _this.ui.cursorMode === "plus") || (_this.ui.cursorMode === "minus") ||
52
+ (event.sourceEvent.type === "touchmove" || event.sourceEvent.type === "touchstart") &&
53
+ (event.sourceEvent.touches.length > 1 || event.sourceEvent.targetTouches.length > 1)) {
54
+ return null;
55
+ }
56
+
57
+ return {
58
+ x: d3$1.pointer(event, this)[0],
59
+ y: d3$1.pointer(event, this)[1]
60
+ };
61
+ };
62
+ }
63
+
64
+ drag() {
65
+ const _this = this.context;
66
+ const self = this;
67
+
68
+ return {
69
+ start(event) {
70
+ /*
71
+ * Do not drag if the Ctrl key, Meta key, or plus cursor mode is
72
+ * not enabled. Also do not drag if zoom-pinching on touchmove
73
+ * events.
74
+ */
75
+ // if(!(event.sourceEvent.ctrlKey || event.sourceEvent.metaKey ||
76
+ // _this.ui.cursorMode === "plus") ||
77
+ // (event.sourceEvent.type === "touchmove" || event.sourceEvent.type === "touchstart") &&
78
+ // (event.sourceEvent.touches.length > 1 || event.sourceEvent.targetTouches.length > 1)) {
79
+ // return;
80
+ // }
81
+
82
+ // self.dragLock = true;
83
+ this.origin = {
84
+ x: d3$1.pointer(event, this)[0],
85
+ y: d3$1.pointer(event, this)[1]
86
+ };
87
+ _this.DOM.zoomRect.classed("vzb-invisible", false);
88
+ },
89
+
90
+ go(event) {
91
+ const origin = this.origin;
92
+ const mouse = {
93
+ x: event.x,
94
+ y: event.y
95
+ };
96
+
97
+ _this.DOM.zoomRect
98
+ .attr("x", Math.min(mouse.x, origin.x))
99
+ .attr("y", Math.min(mouse.y, origin.y))
100
+ .attr("width", Math.abs(mouse.x - origin.x))
101
+ .attr("height", Math.abs(mouse.y - origin.y));
102
+ },
103
+
104
+ stop(event) {
105
+ // if (!self.dragLock) return;
106
+ // self.dragLock = false;
107
+
108
+ _this.DOM.zoomRect
109
+ .attr("width", 0)
110
+ .attr("height", 0)
111
+ .classed("vzb-invisible", true);
112
+
113
+ this.target = {
114
+ x: d3$1.pointer(event, this)[0],
115
+ y: d3$1.pointer(event, this)[1]
116
+ };
117
+ if (Math.abs(this.origin.x - this.target.x) < 10 || Math.abs(this.origin.y - this.target.y) < 10) return;
118
+
119
+ /*
120
+ * Only compensate for dragging when the Ctrl key or Meta key
121
+ * are pressed, or if the cursorMode is not in plus mode.
122
+ */
123
+ const compensateDragging = event.sourceEvent.ctrlKey ||
124
+ event.sourceEvent.metaKey ||
125
+ _this.ui.cursorMode === "plus";
126
+
127
+ self._zoomOnRectangle(
128
+ d3$1.select(this),
129
+ this.origin.x,
130
+ this.origin.y,
131
+ this.target.x,
132
+ this.target.y,
133
+ compensateDragging, 500
134
+ );
135
+ }
136
+ };
137
+ }
138
+
139
+ zoomFilter() {
140
+ const _this = this.context;
141
+
142
+ return function(event) {
143
+
144
+ if (event.ctrlKey || event.metaKey) return false;
145
+
146
+ // Cancel drag lock when zoom-pinching via touchmove events.
147
+ if ((event.type === "touchmove" || event.type === "touchstart") &&
148
+ (event.touches.length > 1 || event.targetTouches.length > 1)) return true;
149
+
150
+ if ((event.type === "wheel" || event.type === "mousewheel") &&
151
+ _this.ui.zoomOnScrolling) {
152
+ // if (_this.scrollableAncestor) {
153
+ // _this.scrollableAncestor.scrollTop -= (event.deltaY || -event.wheelDelta);
154
+ // }
155
+ // event.scale = null;
156
+ //zoomer.scale(this.savedScale);
157
+ return true;
158
+ }
159
+
160
+ if ((event.type === "mousedown" || event.type === "touchstart") &&
161
+ (_this.ui.cursorMode !== "plus") && (_this.ui.cursorMode !== "minus") &&
162
+ (_this.ui.panWithArrow || _this.ui.cursorMode === "hand")) return true;
163
+
164
+ return false;
165
+ };
166
+ }
167
+
168
+ zoom() {
169
+ const _this = this.context;
170
+ const zoomer = this.zoomer;
171
+ const self = this;
172
+
173
+ return {
174
+ start() {
175
+ //this.savedScale = zoomer.scale;
176
+ if ((_this.ui.cursorMode !== "plus") && (_this.ui.cursorMode !== "minus")) {
177
+ _this.DOM.chartSvg.classed("vzb-zooming", true);
178
+ }
179
+
180
+ },
181
+ go(event) {
182
+
183
+ const sourceEvent = event.sourceEvent;
184
+
185
+ let zoom = event.transform.k;
186
+
187
+ let pan = [event.transform.x, event.transform.y];//event.translate;
188
+ let ratioY = zoomer.ratioY;
189
+ let ratioX = zoomer.ratioX;
190
+
191
+ _this.draggingNow = true;
192
+
193
+ //value protections and fallbacks
194
+ if (isNaN(zoom) || zoom == null) zoom = zoomer.scale;
195
+ if (isNaN(zoom) || zoom == null) zoom = 1;
196
+
197
+ //TODO: this is a patch to fix #221. A proper code review of zoom and zoomOnRectangle logic is needed
198
+ /*
199
+ * Mouse wheel and touchmove events set the zoom value
200
+ * independently of axis ratios. If the zoom event was triggered
201
+ * by a mouse wheel event scrolling down or touchmove event with
202
+ * more than 1 contact that sets zoom to 1, then set the axis
203
+ * ratios to 1 as well, which will fully zoom out.
204
+ */
205
+ if (zoom === 1 && sourceEvent !== null &&
206
+ ((sourceEvent.type === "wheel" || sourceEvent.type === "mousewheel") && (sourceEvent.deltaY || -sourceEvent.wheelDelta) > 0 ||
207
+ sourceEvent.type === "touchmove" && sourceEvent.touches.length > 1)) {
208
+ zoomer.ratioX = 1;
209
+ ratioX = 1;
210
+ zoomer.ratioY = 1;
211
+ ratioY = 1;
212
+ }
213
+
214
+ if (isNaN(pan[0]) || isNaN(pan[1]) || pan[0] == null || pan[1] == null) pan = [0, 0];
215
+
216
+ // limit the zooming, so that it never goes below min value of zoom for any of the axes
217
+ const minZoomScale = zoomer.scaleExtent()[0];
218
+ if (zoom * ratioY < minZoomScale) {
219
+ ratioY = minZoomScale / zoom;
220
+ zoomer.ratioY = ratioY;
221
+ }
222
+ if (zoom * ratioX < minZoomScale) {
223
+ ratioX = minZoomScale / zoom;
224
+ zoomer.ratioX = ratioX;
225
+ }
226
+
227
+ const zoomXOut = zoom * ratioX < 1;
228
+ const zoomYOut = zoom * ratioY < 1;
229
+
230
+ //limit the panning, so that we are never outside the possible range
231
+ if (!zoomXOut) {
232
+ if (pan[0] > 0) pan[0] = 0;
233
+ if (pan[0] < (1 - zoom * ratioX) * _this.width) pan[0] = (1 - zoom * ratioX) * _this.width;
234
+ } else {
235
+ if (pan[0] < 0) pan[0] = 0;
236
+ if (pan[0] > (1 - zoom * ratioX) * _this.width) pan[0] = (1 - zoom * ratioX) * _this.width;
237
+ }
238
+
239
+ if (!zoomYOut) {
240
+ if (pan[1] > 0) pan[1] = 0;
241
+ if (pan[1] < (1 - zoom * ratioY) * _this.height) pan[1] = (1 - zoom * ratioY) * _this.height;
242
+ } else {
243
+ if (pan[1] < 0) pan[1] = 0;
244
+ if (pan[1] > (1 - zoom * ratioY) * _this.height) pan[1] = (1 - zoom * ratioY) * _this.height;
245
+ }
246
+
247
+ //limit zoom translate
248
+ self.zoomSelection.property("__zoom", d3$1.zoomIdentity.translate(pan[0], pan[1]).scale(zoom));
249
+
250
+ const xPanOffset = _this.width * zoom * ratioX;
251
+ const yPanOffset = _this.height * zoom * ratioY;
252
+
253
+ const xRange = [0 * zoom * ratioX + pan[0], xPanOffset + pan[0]];
254
+ const yRange = [yPanOffset + pan[1], 0 * zoom * ratioY + pan[1]];
255
+
256
+ const xRangeBumped = _this._rangeBump(xRange);
257
+ const yRangeBumped = _this._rangeBump(yRange);
258
+
259
+ /*
260
+ * Shift xRange and yRange by the difference between the bumped
261
+ * ranges, which is scaled by the zoom factor. This accounts for
262
+ * the range bump, which controls a gutter around the
263
+ * bubblechart, while correctly zooming.
264
+ */
265
+ const xRangeMinOffset = (xRangeBumped[0] - xRange[0]) * zoom * ratioX;
266
+ const xRangeMaxOffset = (xRangeBumped[1] - xRange[1]) * zoom * ratioX;
267
+
268
+ const yRangeMinOffset = (yRangeBumped[0] - yRange[0]) * zoom * ratioY;
269
+ const yRangeMaxOffset = (yRangeBumped[1] - yRange[1]) * zoom * ratioY;
270
+
271
+ xRange[0] += xRangeMinOffset;
272
+ xRange[1] += xRangeMaxOffset;
273
+
274
+ yRange[0] += yRangeMinOffset;
275
+ yRange[1] += yRangeMaxOffset;
276
+
277
+ // Calculate the maximum xRange and yRange available.
278
+ const xRangeBounds = [0, _this.width];
279
+ const yRangeBounds = [_this.height, 0];
280
+
281
+ const xRangeBoundsBumped = _this._rangeBump(xRangeBounds);
282
+ const yRangeBoundsBumped = _this._rangeBump(yRangeBounds);
283
+
284
+ /*
285
+ * Set the pan to account for the range bump by subtracting
286
+ * offsets and preventing panning past the range bump gutter.
287
+ */
288
+ if (!zoomXOut) {
289
+ if (xRange[0] > xRangeBoundsBumped[0]) pan[0] = xRangeBoundsBumped[0] - xRangeMinOffset;
290
+ if (xRange[1] < xRangeBoundsBumped[1]) pan[0] = xRangeBoundsBumped[1] - xRangeMaxOffset - xPanOffset;
291
+ } else {
292
+ if (xRange[0] < xRangeBoundsBumped[0]) pan[0] = xRangeBoundsBumped[0] - xRangeMinOffset;
293
+ if (xRange[1] > xRangeBoundsBumped[1]) pan[0] = xRangeBoundsBumped[1] - xRangeMaxOffset - xPanOffset;
294
+ }
295
+
296
+ if (!zoomYOut) {
297
+ if (yRange[0] < yRangeBoundsBumped[0]) pan[1] = yRangeBoundsBumped[0] - yRangeMinOffset - yPanOffset;
298
+ if (yRange[1] > yRangeBoundsBumped[1]) pan[1] = yRangeBoundsBumped[1] - yRangeMaxOffset;
299
+ } else {
300
+ if (yRange[0] > yRangeBoundsBumped[0]) pan[1] = yRangeBoundsBumped[0] - yRangeMinOffset - yPanOffset;
301
+ if (yRange[1] < yRangeBoundsBumped[1]) pan[1] = yRangeBoundsBumped[1] - yRangeMaxOffset;
302
+ }
303
+
304
+ //zoomer.translate = pan;
305
+ //self.zoomSelection.property("__zoom", d3_zoomIdentity.translate(pan[0], pan[1]).scale(zoom));
306
+
307
+ /*
308
+ * Clamp the xRange and yRange by the amount that the bounds
309
+ * that are range bumped.
310
+ *
311
+ * Additionally, take the amount clamped on the end of the range
312
+ * and either subtract or add it to the range's other end. This
313
+ * prevents visible stretching of the range when only panning.
314
+ */
315
+ if (!zoomXOut) {
316
+ if (xRange[0] > xRangeBoundsBumped[0]) {
317
+ xRange[1] -= Math.abs(xRange[0] - xRangeBoundsBumped[0]);
318
+ xRange[0] = xRangeBoundsBumped[0];
319
+ }
320
+
321
+ if (xRange[1] < xRangeBoundsBumped[1]) {
322
+ xRange[0] += Math.abs(xRange[1] - xRangeBoundsBumped[1]);
323
+ xRange[1] = xRangeBoundsBumped[1];
324
+ }
325
+ } else {
326
+ if (xRange[0] < xRangeBoundsBumped[0]) {
327
+ xRange[1] += Math.abs(xRange[0] - xRangeBoundsBumped[0]);
328
+ xRange[0] = xRangeBoundsBumped[0];
329
+ }
330
+
331
+ if (xRange[1] > xRangeBoundsBumped[1]) {
332
+ xRange[0] -= Math.abs(xRange[1] - xRangeBoundsBumped[1]);
333
+ xRange[1] = xRangeBoundsBumped[1];
334
+ }
335
+ }
336
+
337
+ if (!zoomYOut) {
338
+ if (yRange[0] < yRangeBoundsBumped[0]) {
339
+ yRange[1] += Math.abs(yRange[0] - yRangeBoundsBumped[0]);
340
+ yRange[0] = yRangeBoundsBumped[0];
341
+ }
342
+
343
+ if (yRange[1] > yRangeBoundsBumped[1]) {
344
+ yRange[0] -= Math.abs(yRange[1] - yRangeBoundsBumped[1]);
345
+ yRange[1] = yRangeBoundsBumped[1];
346
+ }
347
+ } else {
348
+ if (yRange[0] > yRangeBoundsBumped[0]) {
349
+ yRange[1] -= Math.abs(yRange[0] - yRangeBoundsBumped[0]);
350
+ yRange[0] = yRangeBoundsBumped[0];
351
+ }
352
+
353
+ if (yRange[1] < yRangeBoundsBumped[1]) {
354
+ yRange[0] += Math.abs(yRange[1] - yRangeBoundsBumped[1]);
355
+ yRange[1] = yRangeBoundsBumped[1];
356
+ }
357
+ }
358
+
359
+ if (_this.MDL.x.scale.type === "ordinal") {
360
+ _this.xScale.rangeBands(xRange);
361
+ } else {
362
+ _this.xScale.range(xRange);
363
+ }
364
+
365
+ if (_this.MDL.y.scale.type === "ordinal") {
366
+ _this.yScale.rangeBands(yRange);
367
+ } else {
368
+ _this.yScale.range(yRange);
369
+ }
370
+
371
+ const formatter = function(n) {
372
+ return VizabiSharedComponents.LegacyUtils.isDate(n) ? n : +n.toFixed(2);
373
+ };
374
+
375
+ const zoomedXRange = xRangeBoundsBumped;
376
+ const zoomedYRange = yRangeBoundsBumped;
377
+
378
+ /*
379
+ * Set the zoomed min/max to the correct value depending on if the
380
+ * min/max values lie within the range bound regions.
381
+ */
382
+ /*
383
+ if(!zoomXOut) {
384
+ zoomedXRange[0] = xRangeBounds[0] > xRange[0] ? xRangeBounds[0] : xRange[0];
385
+ zoomedXRange[1] = xRangeBounds[1] < xRange[1] ? xRangeBounds[1] : xRange[1];
386
+ }
387
+
388
+ if(!zoomYOut) {
389
+ zoomedYRange[0] = yRangeBounds[0] < yRange[0] ? yRangeBounds[0] : yRange[0];
390
+ zoomedYRange[1] = yRangeBounds[1] > yRange[1] ? yRangeBounds[1] : yRange[1];
391
+ }
392
+ */
393
+
394
+ _this._zoomedXYMinMax = {
395
+ x: {
396
+ zoomedMin: formatter(_this.xScale.invert(zoomedXRange[0])),
397
+ zoomedMax: formatter(_this.xScale.invert(zoomedXRange[1]))
398
+ },
399
+ y: {
400
+ zoomedMin: formatter(_this.yScale.invert(zoomedYRange[0])),
401
+ zoomedMax: formatter(_this.yScale.invert(zoomedYRange[1]))
402
+ }
403
+ };
404
+
405
+ //TODO/*avoid storing it in URL*/
406
+ if (!zoomer.dontFeedToState) {
407
+ // _this.ui.panzoom = {
408
+ // x: Object.assign({}, _this._zoomedXYMinMax.x),
409
+ // y: Object.assign({}, _this._zoomedXYMinMax.y),
410
+ // }
411
+ _this.MDL.x.scale.zoomed = [_this._zoomedXYMinMax.x.zoomedMin, _this._zoomedXYMinMax.x.zoomedMax];
412
+ _this.MDL.y.scale.zoomed = [_this._zoomedXYMinMax.y.zoomedMin, _this._zoomedXYMinMax.y.zoomedMax];
413
+ }
414
+
415
+ const optionsY = _this.yAxis.labelerOptions();
416
+ const optionsX = _this.xAxis.labelerOptions();
417
+ optionsY.limitMaxTickNumber = zoom * ratioY < 1.5 ? 8 : zoom * ratioY * 8;
418
+ optionsX.limitMaxTickNumber = zoom * ratioX < 1.5 ? 8 : zoom * ratioX * 8;
419
+ optionsY.transitionDuration = zoomer.duration;
420
+ optionsX.transitionDuration = zoomer.duration;
421
+
422
+ _this.DOM.xAxisEl.call(_this.xAxis.labelerOptions(optionsX));
423
+ _this.DOM.yAxisEl.call(_this.yAxis.labelerOptions(optionsY));
424
+ _this.redrawData(zoomer.duration);
425
+ //_this._trails.run("resize", null, zoomer.duration);
426
+
427
+ zoomer.duration = 0;
428
+ },
429
+
430
+ stop() {
431
+ _this.DOM.chartSvg.classed("vzb-zooming", false);
432
+ // if (this.quitZoom) return;
433
+
434
+ //Force the update of the URL and history, with the same values
435
+ if (!zoomer.dontFeedToState) {
436
+ // _this.ui.panzoom = {
437
+ // x: Object.assign({}, _this._zoomedXYMinMax.x),
438
+ // y: Object.assign({}, _this._zoomedXYMinMax.y),
439
+ // }
440
+ _this.MDL.x.scale.zoomed = [_this._zoomedXYMinMax.x.zoomedMin, _this._zoomedXYMinMax.x.zoomedMax];
441
+ _this.MDL.y.scale.zoomed = [_this._zoomedXYMinMax.y.zoomedMin, _this._zoomedXYMinMax.y.zoomedMax];
442
+ //_this.model.marker.set(_this._zoomedXYMinMax, true, true);
443
+ }
444
+ zoomer.dontFeedToState = null;
445
+
446
+ _this.draggingNow = false;
447
+ }
448
+ };
449
+ }
450
+
451
+ expandCanvas(duration) {
452
+ const _this = this.context;
453
+ if (!duration) duration = _this.duration;
454
+
455
+ //d3 extent returns min and max of the input array as [min, max]
456
+ const mmX = d3$1.extent(VizabiSharedComponents.LegacyUtils.values(_this.frame.x));
457
+ const mmY = d3$1.extent(VizabiSharedComponents.LegacyUtils.values(_this.frame.y));
458
+
459
+ //protection agains unreasonable min-max results -- abort function
460
+ if (!mmX[0] && mmX[0] !== 0 || !mmX[1] && mmX[1] !== 0 || !mmY[0] && mmY[0] !== 0 || !mmY[1] && mmY[1] !== 0) {
461
+ return VizabiSharedComponents.LegacyUtils.warn("panZoom.expandCanvas: X or Y min/max are broken. Aborting with no action");
462
+ }
463
+ /*
464
+ * Use a range bumped scale to correctly accommodate the range bump
465
+ * gutter.
466
+ */
467
+ const suggestedFrame = {
468
+ x1: _this.xScale(mmX[0]),
469
+ y1: _this.yScale(mmY[0]),
470
+ x2: _this.xScale(mmX[1]),
471
+ y2: _this.yScale(mmY[1])
472
+ };
473
+ const xBounds = [0, _this.width];
474
+ const yBounds = [_this.height, 0];
475
+
476
+ // Get the current zoom frame based on the current dimensions.
477
+ const frame = {
478
+ x1: xBounds[0],
479
+ x2: xBounds[1],
480
+ y1: yBounds[0],
481
+ y2: yBounds[1]
482
+ };
483
+
484
+ const TOLERANCE = 0.0;
485
+
486
+ /*
487
+ * If there is no current zoom frame, or if any of the suggested frame
488
+ * points extend outside of the current zoom frame, then expand the
489
+ * canvas.
490
+ */
491
+ if (!_this.isCanvasPreviouslyExpanded ||
492
+ suggestedFrame.x1 < frame.x1 * (1 - TOLERANCE) || suggestedFrame.x2 > frame.x2 * (1 + TOLERANCE) ||
493
+ suggestedFrame.y2 < frame.y2 * (1 - TOLERANCE) || suggestedFrame.y1 > frame.y1 * (1 + TOLERANCE)) {
494
+ /*
495
+ * If there is already a zoom frame, then clamp the suggested frame
496
+ * points to only zoom out and expand the canvas.
497
+ *
498
+ * If any of x1, x2, y1, or y2 is within the current frame
499
+ * boundaries, then clamp them to the frame boundaries. If any of
500
+ * the above values will translate into a data value that is outside
501
+ * of the possible data range, then clamp them to the frame
502
+ * coordinate that corresponds to the maximum data value that can
503
+ * be displayed.
504
+ */
505
+ if (_this.isCanvasPreviouslyExpanded) {
506
+ /*
507
+ * Calculate bounds and bumped scale for calculating the data boundaries
508
+ * to which the suggested frame points need to be clamped.
509
+ */
510
+ const xBoundsBumped = _this._rangeBump(xBounds);
511
+ const yBoundsBumped = _this._rangeBump(yBounds);
512
+
513
+ if (suggestedFrame.x1 > xBoundsBumped[0]) suggestedFrame.x1 = xBoundsBumped[0];
514
+ if (suggestedFrame.x2 < xBoundsBumped[1]) suggestedFrame.x2 = xBoundsBumped[1];
515
+ if (suggestedFrame.y1 < yBoundsBumped[0]) suggestedFrame.y1 = yBoundsBumped[0];
516
+ if (suggestedFrame.y2 > yBoundsBumped[0]) suggestedFrame.y2 = yBoundsBumped[1];
517
+ }
518
+
519
+ _this.isCanvasPreviouslyExpanded = true;
520
+ this._zoomOnRectangle(_this.element, suggestedFrame.x1, suggestedFrame.y1,
521
+ suggestedFrame.x2, suggestedFrame.y2, false, duration);
522
+ } else {
523
+ _this.redrawDataPoints(duration);
524
+ }
525
+ }
526
+
527
+ zoomToMaxMin(zoomedMinX, zoomedMaxX, zoomedMinY, zoomedMaxY, duration, dontFeedToState) {
528
+ const _this = this.context;
529
+ let minX = zoomedMinX;
530
+ let maxX = zoomedMaxX;
531
+ let minY = zoomedMinY;
532
+ let maxY = zoomedMaxY;
533
+
534
+ const xDomain = _this.xScale.domain();
535
+ const yDomain = _this.yScale.domain();
536
+
537
+ /*
538
+ * Prevent zoomout if only one of zoom edges set outside domain
539
+ */
540
+ if (minX < xDomain[0] && maxX < xDomain[1]) minX = xDomain[0];
541
+ if (minX > xDomain[0] && maxX > xDomain[1]) maxX = xDomain[1];
542
+ if (minY < yDomain[0] && maxY < yDomain[1]) minY = yDomain[0];
543
+ if (minY > yDomain[0] && maxY > yDomain[1]) maxY = yDomain[1];
544
+
545
+
546
+ const xRange = [_this.xScale(minX), _this.xScale(maxX)];
547
+ const yRange = [_this.yScale(minY), _this.yScale(maxY)];
548
+
549
+
550
+ this._zoomOnRectangle(_this.element, xRange[0], yRange[0], xRange[1], yRange[1], false, duration, dontFeedToState);
551
+ }
552
+
553
+ _zoomOnRectangle(element, zoomedX1, zoomedY1, zoomedX2, zoomedY2, compensateDragging, duration, dontFeedToState) {
554
+ const _this = this.context;
555
+ const zoomer = this.zoomer;
556
+ const transform = d3$1.zoomTransform(this.zoomSelection.node());
557
+
558
+ const x1 = zoomedX1;
559
+ const y1 = zoomedY1;
560
+ const x2 = zoomedX2;
561
+ const y2 = zoomedY2;
562
+
563
+ /*
564
+ * When dragging to draw a rectangle, the translate vector has (x2 - x1)
565
+ * added to zoomer.translate()[0], and (y2 - 1) added to
566
+ * zoomer.translate()[1].
567
+ *
568
+ * We need to compensate for this addition when
569
+ * zooming with a rectangle, because zooming with a rectangle will
570
+ * update the translate vector with new values based on the rectangle
571
+ * dimensions.
572
+ */
573
+ if (compensateDragging) {
574
+ transform.translate(
575
+ x1 - x2,
576
+ y1 - y2
577
+ );
578
+ }
579
+
580
+ const xRangeBounds = [0, _this.width];
581
+ const yRangeBounds = [_this.height, 0];
582
+
583
+ const xRangeBoundsBumped = _this._rangeBump(xRangeBounds);
584
+ const yRangeBoundsBumped = _this._rangeBump(yRangeBounds);
585
+
586
+ const minZoom = zoomer.scaleExtent()[0];
587
+ const maxZoom = zoomer.scaleExtent()[1];
588
+ let zoom, ratioX, ratioY;
589
+
590
+ if (x1 == x2 || y1 == y2 || xRangeBoundsBumped[0] == xRangeBoundsBumped[1] || yRangeBoundsBumped[0] == yRangeBoundsBumped[1]) {
591
+ return VizabiSharedComponents.LegacyUtils.warn("_zoomOnRectangle(): can not proceed because this may result in infinity zooms");
592
+ }
593
+
594
+ if (Math.abs(x1 - x2) > Math.abs(y1 - y2)) {
595
+ zoom = Math.abs(yRangeBoundsBumped[0] - yRangeBoundsBumped[1]) / Math.abs(y1 - y2) * transform.k;
596
+
597
+ /*
598
+ * Clamp the zoom scalar to the maximum zoom allowed before
599
+ * calculating the next ratioX and ratioY.
600
+ */
601
+ if (zoom < minZoom) {
602
+ zoomer.ratioY *= zoom / transform.k;
603
+ zoom = minZoom;
604
+ }
605
+ if (zoom > maxZoom) zoom = maxZoom;
606
+
607
+ ratioX = Math.abs(xRangeBoundsBumped[0] - xRangeBoundsBumped[1]) / Math.abs(x1 - x2) * transform.k / zoom * zoomer.ratioX;
608
+ ratioY = zoomer.ratioY;
609
+ } else {
610
+ zoom = Math.abs(xRangeBoundsBumped[0] - xRangeBoundsBumped[1]) / Math.abs(x1 - x2) * transform.k;
611
+
612
+ /*
613
+ * Clamp the zoom scalar to the maximum zoom allowed before
614
+ * calculating the next ratioX and ratioY.
615
+ */
616
+ if (zoom < minZoom) {
617
+ zoomer.ratioX *= zoom / transform.k;
618
+ zoom = minZoom;
619
+ }
620
+ if (zoom > maxZoom) zoom = maxZoom;
621
+
622
+ ratioY = Math.abs(yRangeBoundsBumped[0] - yRangeBoundsBumped[1]) / Math.abs(y1 - y2) * transform.k / zoom * zoomer.ratioY;
623
+ ratioX = zoomer.ratioX;
624
+ }
625
+
626
+ const pan = [
627
+ (transform.x - Math.min(x1, x2)) / transform.k / zoomer.ratioX * zoom * ratioX + (xRangeBoundsBumped[0] - xRangeBounds[0]),
628
+ (transform.y - Math.min(y1, y2)) / transform.k / zoomer.ratioY * zoom * ratioY + (yRangeBoundsBumped[1] - yRangeBounds[1])
629
+ ];
630
+
631
+ zoomer.dontFeedToState = dontFeedToState;
632
+ zoomer.ratioY = ratioY || 1; //NaN defaults to 1
633
+ zoomer.ratioX = ratioX || 1; //NaN defaults to 1
634
+ zoomer.duration = duration ? duration : 0;
635
+
636
+ this.zoomSelection.call(zoomer.transform, d3$1.zoomIdentity.translate(pan[0], pan[1]).scale(zoom));
637
+ }
638
+
639
+ /*
640
+ * Incrementally zoom in or out and pan the view so that it never looses the point where click happened
641
+ * this function is a modified d3's own zoom behavior on double click
642
+ * for the original code see https://github.com/mbostock/d3/blob/master/src/behavior/zoom.js
643
+ * function dblclicked() and what it refers to
644
+ */
645
+ zoomByIncrement(event, direction, duration) {
646
+ const transform = d3$1.zoomTransform(this.zoomSelection.node());
647
+
648
+ let ratio = transform.k;
649
+ const pan = [transform.x, transform.y];
650
+
651
+ const mouse = d3$1.pointer(event, this.zoomSelection.node());
652
+ let k = Math.log(ratio) / Math.LN2;
653
+
654
+ //change factor direction based on the input. default is no direction supplied
655
+ if (direction == "plus" || !direction) k = Math.floor(k) + 1;
656
+ if (direction == "minus") k = Math.ceil(k) - 1;
657
+
658
+ //decode panning
659
+ let locus = [(mouse[0] - pan[0]) / ratio, (mouse[1] - pan[1]) / ratio];
660
+
661
+ //recalculate zoom ratio
662
+ const scaleExtent = this.zoomer.scaleExtent();
663
+ if (ratio == scaleExtent[0]) {
664
+ this.zoomer.ratioY = 1;
665
+ this.zoomer.ratioX = 1;
666
+ }
667
+ ratio = Math.max(scaleExtent[0], Math.min(scaleExtent[1], Math.pow(2, k)));
668
+
669
+ //recalculate panning
670
+ locus = [locus[0] * ratio + pan[0], locus[1] * ratio + pan[1]];
671
+ pan[0] += mouse[0] - locus[0];
672
+ pan[1] += mouse[1] - locus[1];
673
+
674
+ this.zoomer.duration = duration || 0;
675
+ this.zoomSelection.call(this.zoomer.transform, d3$1.zoomIdentity.translate(pan[0], pan[1]).scale(ratio));
676
+
677
+ }
678
+
679
+ /*
680
+ * Reset zoom values without triggering a zoom event.
681
+ */
682
+ resetZoomState(element) {
683
+ this.zoomer.ratioY = 1;
684
+ this.zoomer.ratioX = 1;
685
+ (element || this.zoomSelection).property("__zoom", d3$1.zoomIdentity);
686
+ }
687
+
688
+ reset(element, duration) {
689
+ const _this = this.context;
690
+ _this.isCanvasPreviouslyExpanded = false;
691
+
692
+ this.zoomer.ratioY = 1;
693
+ this.zoomer.ratioX = 1;
694
+ this.zoomer.duration = duration || 0;
695
+ (element || this.zoomSelection).call(this.zoomer.transform, d3$1.zoomIdentity);
696
+ }
697
+
698
+ rerun(element) {
699
+ (element || this.zoomSelection).call(this.zoomer.scaleBy, 1);
700
+ }
701
+
702
+ zoomSelection(element) {
703
+ this.zoomSelection = element;
704
+ }
705
+
706
+ }
707
+
708
+ class BCDecorations{
709
+ constructor(){
710
+ }
711
+
712
+ update(duration) {
713
+ const _this = this;
714
+
715
+ const uiSetting = this.ui.decorations;
716
+ const layoutProfile = this.services.layout.profile;
717
+ const margin = this.profileConstants.margin;
718
+
719
+ // x axis groups used for incomes
720
+ const showxAxisGroups = uiSetting.xAxisGroups
721
+ && uiSetting.xAxisGroups[this.MDL.x.data.concept]
722
+ && uiSetting.enabled
723
+ && layoutProfile !== "SMALL";
724
+
725
+ this.DOM.xAxisGroupsEl.classed("vzb-invisible", !showxAxisGroups);
726
+ if (showxAxisGroups) {
727
+ const axisGroupsData = VizabiSharedComponents.Utils.injectIndexes(uiSetting.xAxisGroups[this.MDL.x.data.concept]);
728
+ let xAxisGroups = this.DOM.xAxisGroupsEl.selectAll(".vzb-bc-x-axis-group").data(axisGroupsData);
729
+
730
+ xAxisGroups.exit().remove();
731
+ xAxisGroups = xAxisGroups.enter().append("g").attr("class", "vzb-bc-x-axis-group")
732
+ .each(function(){
733
+ const view = d3$1.select(this);
734
+ view.append("text").attr("class", "vzb-bc-x-axis-group-line").text("◆").style("text-anchor","middle");
735
+ view.append("text").attr("class", "vzb-bc-x-axis-group-text");
736
+ })
737
+ .merge(xAxisGroups);
738
+
739
+ const xAxisGroups_calcs = [];
740
+ let useShorterLabels = false;
741
+
742
+ // first pass: calculate label text sizes and margins
743
+ xAxisGroups.each(function(d, i){
744
+ const view = d3$1.select(this);
745
+
746
+ const text = view.select("text.vzb-bc-x-axis-group-text")
747
+ .text(_this.localise(d.label));
748
+
749
+ const calcs = {min: d.min, max: d.max};
750
+
751
+ calcs.textHeight = text.node().getBBox().height;
752
+ calcs.textWidth = text.node().getBBox().width;
753
+
754
+ calcs.boundaryMinX_px = _this.xScale(d.min || d.min === 0? d.min : d3$1.min(_this.xScale.domain())) || 0;
755
+ calcs.boundaryMaxX_px = _this.xScale(d.max || d.max === 0? d.max : d3$1.max(_this.xScale.domain())) || 0;
756
+
757
+ calcs.centerX_px = (calcs.boundaryMinX_px + calcs.boundaryMaxX_px) / 2;
758
+ calcs.marginX_px = (Math.abs(calcs.boundaryMinX_px - calcs.boundaryMaxX_px) - calcs.textWidth) / 2;
759
+
760
+ if (calcs.marginX_px - calcs.textHeight < 0) useShorterLabels = true;
761
+
762
+ xAxisGroups_calcs[i] = calcs;
763
+ });
764
+
765
+ // second pass: if at least one of labels doesn't fit, switch to compact mode and recalculate text sizes and margins
766
+ if (useShorterLabels) {
767
+ xAxisGroups.each(function(d, i){
768
+ const view = d3$1.select(this);
769
+
770
+ const text = view.select("text.vzb-bc-x-axis-group-text")
771
+ .text(_this.localise(d.label_short));
772
+
773
+ const calcs = xAxisGroups_calcs[i];
774
+
775
+ calcs.textWidth = text.node().getBBox().width;
776
+ calcs.marginX_px = (Math.abs(calcs.boundaryMinX_px - calcs.boundaryMaxX_px) - calcs.textWidth) / 2;
777
+
778
+ xAxisGroups_calcs[i] = calcs;
779
+ });
780
+ }
781
+
782
+ // third pass: actually put labels in places
783
+ xAxisGroups.each(function(d, i){
784
+ const view = d3$1.select(this);
785
+
786
+ const isFirst = (i == 0);
787
+ const isLast = (i == xAxisGroups_calcs.length - 1);
788
+ const calcs = xAxisGroups_calcs[i];
789
+ const minMargin = calcs.textHeight/4;
790
+ let x = calcs.centerX_px;
791
+
792
+ if (isFirst) x = xAxisGroups_calcs[i+1].boundaryMinX_px - Math.max(xAxisGroups_calcs[i+1].marginX_px, minMargin);
793
+ if (isLast) x = xAxisGroups_calcs[i-1].boundaryMaxX_px + Math.max(xAxisGroups_calcs[i-1].marginX_px, minMargin);
794
+
795
+ view.select("text.vzb-bc-x-axis-group-text")
796
+ .style("text-anchor", isFirst ? "end" : isLast ? "start" : "middle")
797
+ .transition()
798
+ .duration(duration || 0)
799
+ .attr("dy", "-0.2em")
800
+ .attr("y", calcs.textHeight)
801
+ .attr("x", x);
802
+
803
+ view.select("text.vzb-bc-x-axis-group-line")
804
+ .classed("vzb-invisible", isLast)
805
+ .transition()
806
+ .duration(duration || 0)
807
+ .attr("dy", "-0.2em")
808
+ .attr("y", calcs.textHeight * 0.9)
809
+ .attr("x", calcs.boundaryMaxX_px);
810
+ });
811
+
812
+ xAxisGroups.select("text.vzb-bc-x-axis-group-text").on("mouseenter", function(event, d) {
813
+ const calcs = xAxisGroups_calcs[d.i];
814
+ const parentView = d3$1.select(this.parentNode);
815
+
816
+ d3$1.select(this).attr("font-weight", "bold");
817
+ parentView.append("rect").lower()
818
+ .attr("x", calcs.boundaryMinX_px)
819
+ .attr("width", calcs.boundaryMaxX_px - calcs.boundaryMinX_px)
820
+ .attr("y", - margin.top)
821
+ .attr("height", _this.height + margin.top);
822
+
823
+ if (calcs.min || calcs.min === 0) parentView.append("line").lower()
824
+ .attr("x1", calcs.boundaryMinX_px)
825
+ .attr("x2", calcs.boundaryMinX_px)
826
+ .attr("y1", - margin.top)
827
+ .attr("y2", _this.height);
828
+
829
+ if (calcs.max || calcs.max === 0) parentView.append("line").lower()
830
+ .attr("x1", calcs.boundaryMaxX_px)
831
+ .attr("x2", calcs.boundaryMaxX_px)
832
+ .attr("y1", - margin.top)
833
+ .attr("y2", _this.height);
834
+
835
+ }).on("mouseleave", function() {
836
+ const parentView = d3$1.select(this.parentNode);
837
+
838
+ d3$1.select(this).attr("font-weight", null);
839
+ parentView.selectAll("rect").remove();
840
+ parentView.selectAll("line").remove();
841
+ });
842
+ }
843
+
844
+ // diagonal line that is used when the same idicator ID is used for both axis X and Y
845
+ const showLineEqualXY =
846
+ this.MDL.x.data.concept == this.MDL.y.data.concept
847
+ && uiSetting.enabled
848
+ && layoutProfile !== "SMALL";
849
+
850
+ this.DOM.lineEqualXY.classed("vzb-invisible", !showLineEqualXY);
851
+ if (showLineEqualXY) {
852
+ const min = d3$1.min(this.yScale.domain().concat(this.xScale.domain()));
853
+ const max = d3$1.max(this.yScale.domain().concat(this.xScale.domain()));
854
+
855
+ this.DOM.lineEqualXY
856
+ .transition()
857
+ .duration(duration || 0)
858
+ .attr("y1", this.yScale(min) || 0)
859
+ .attr("y2", this.yScale(max) || 0)
860
+ .attr("x1", this.xScale(min) || 0)
861
+ .attr("x2", this.xScale(max) || 0);
862
+ }
863
+ }
864
+ }
865
+
866
+ const {ICON_QUESTION} = VizabiSharedComponents.Icons;
867
+ const COLOR_WHITEISH = "rgb(253, 253, 253)";
868
+
869
+ const marginScaleH = (marginMin, ratio = 0) => height => marginMin + height * ratio;
870
+ const marginScaleW = (marginMin, ratio = 0) => width => marginMin + width * ratio;
871
+
872
+ function isTrailBubble(d){
873
+ return !!d[Symbol.for("trailHeadKey")];
874
+ }
875
+
876
+ const MAX_RADIUS_EM = 0.05;
877
+
878
+ const PROFILE_CONSTANTS = (width, height) => ({
879
+ SMALL: {
880
+ margin: { top: 30, bottom: 35, left: 30, right: 10},
881
+ leftMarginRatio: 1,
882
+ padding: 2,
883
+ minRadiusPx: 0.5,
884
+ maxRadiusPx: Math.max(0.5, MAX_RADIUS_EM * VizabiSharedComponents.LegacyUtils.hypotenuse(width, height)),
885
+ infoElHeight: 16,
886
+ yAxisTitleBottomMargin: 6,
887
+ xAxisTitleBottomMargin: 4
888
+ },
889
+ MEDIUM: {
890
+ margin: { top: 15, bottom: 40, left: 40, right: 15},
891
+ leftMarginRatio: 1.6,
892
+ padding: 2,
893
+ minRadiusPx: 1,
894
+ maxRadiusPx: Math.max(0.5, MAX_RADIUS_EM * VizabiSharedComponents.LegacyUtils.hypotenuse(width, height)),
895
+ infoElHeight: 20,
896
+ yAxisTitleBottomMargin: 3,
897
+ xAxisTitleBottomMargin: 4
898
+ },
899
+ LARGE: {
900
+ margin: { top: 15, bottom: marginScaleH(30, 0.03)(height), left: marginScaleW(31, 0.015)(width), right: 20},
901
+ leftMarginRatio: 1.8,
902
+ padding: 2,
903
+ minRadiusPx: 1,
904
+ maxRadiusPx: Math.max(0.5, MAX_RADIUS_EM * VizabiSharedComponents.LegacyUtils.hypotenuse(width, height)),
905
+ infoElHeight: 22,
906
+ yAxisTitleBottomMargin: 3,//marginScaleH(4, 0.01)(height),
907
+ xAxisTitleBottomMargin: marginScaleH(0, 0.01)(height),
908
+ hideSTitle: true
909
+ }
910
+ });
911
+
912
+ const PROFILE_CONSTANTS_FOR_PROJECTOR = (width, height) => ({
913
+ MEDIUM: {
914
+ margin: { top: 20, bottom: 55, left: 50, right: 20 },
915
+ yAxisTitleBottomMargin: 3,
916
+ xAxisTitleBottomMargin: 4,
917
+ infoElHeight: 26,
918
+ },
919
+ LARGE: {
920
+ margin: { top: 30, bottom: marginScaleH(45, 0.03)(height), left: marginScaleW(35, 0.025)(width), right: 30 },
921
+ yAxisTitleBottomMargin: 3,//marginScaleH(4, 0.01)(height),
922
+ xAxisTitleBottomMargin: marginScaleH(-5, 0.01)(height),
923
+ infoElHeight: 32,
924
+ hideSTitle: true
925
+ }
926
+ });
927
+
928
+ // BUBBLE CHART COMPONENT
929
+ class _VizabiBubbleChart extends VizabiSharedComponents.Chart {
930
+
931
+ constructor(config) {
932
+ config.subcomponents = [{
933
+ type: VizabiSharedComponents.Labels,
934
+ placeholder: ".vzb-bc-labels",
935
+ options: {
936
+ CSS_PREFIX: "vzb-bc",
937
+ LABELS_CONTAINER_CLASS: "vzb-bc-labels",
938
+ LINES_CONTAINER_CLASS: "vzb-bc-lines",
939
+ SUPPRESS_HIGHLIGHT_DURING_PLAY: false
940
+ },
941
+ name: "labels"
942
+ },{
943
+ type: VizabiSharedComponents.DateTimeBackground,
944
+ placeholder: ".vzb-bc-date"
945
+ }];
946
+
947
+ config.template = `
948
+ <svg class="vzb-bubblechart-svg vzb-export">
949
+ <svg class="vzb-bubblechart-svg-back">
950
+ <g class="vzb-bc-graph">
951
+ <g class="vzb-bc-date"></g>
952
+ <svg class="vzb-bc-axis-x"><g></g></svg>
953
+ <svg class="vzb-bc-axis-y"><g></g></svg>
954
+ <line class="vzb-bc-projection-x"></line>
955
+ <line class="vzb-bc-projection-y"></line>
956
+ </g>
957
+ </svg>
958
+ <svg class="vzb-bubblechart-svg-main">
959
+ <g class="vzb-bc-graph">
960
+ <g class="vzb-bc-axis-x-title"><text></text></g>
961
+ <g class="vzb-bc-axis-x-info vzb-noexport"></g>
962
+
963
+ <g class="vzb-bc-axis-y-title"><text></text></g>
964
+ <g class="vzb-bc-axis-y-info vzb-noexport"></g>
965
+ <svg class="vzb-bc-bubbles-crop">
966
+ <g class="vzb-zoom-selection"></g>
967
+ <rect class="vzb-bc-eventarea"></rect>
968
+ <g class="vzb-bc-trails"></g>
969
+ <g class="vzb-bc-bubbles"></g>
970
+ <rect class="vzb-bc-forecastoverlay vzb-hidden" x="0" y="0" width="100%" height="100%" fill="url(#vzb-bc-pattern-lines-${config.id})" pointer-events='none'></rect>
971
+ </svg>
972
+
973
+ <g class="vzb-bc-axis-y-subtitle"><text></text></g>
974
+ <g class="vzb-bc-axis-x-subtitle"><text></text></g>
975
+ <g class="vzb-bc-axis-s-title"><text></text></g>
976
+ <g class="vzb-bc-axis-c-title"><text></text></g>
977
+
978
+ <rect class="vzb-bc-zoom-rect"></rect>
979
+ </g>
980
+ <g class="vzb-datawarning-button vzb-noexport"></g>
981
+ </svg>
982
+ <svg class="vzb-bubblechart-svg-front">
983
+ <g class="vzb-bc-graph">
984
+ <svg class="vzb-bc-bubbles-crop">
985
+ <g class="vzb-bc-decorations">
986
+ <line class="vzb-bc-line-equal-xy vzb-invisible"></line>
987
+ <g class="vzb-bc-x-axis-groups"></g>
988
+ </g>
989
+ <g class="vzb-bc-lines"></g>
990
+ <g class="vzb-bc-bubble-crown vzb-hidden">
991
+ <circle class="vzb-crown-glow"></circle>
992
+ <circle class="vzb-crown"></circle>
993
+ </g>
994
+ </svg>
995
+ <svg class="vzb-bc-labels-crop">
996
+ <g class="vzb-bc-labels"></g>
997
+ </svg>
998
+ </g>
999
+ </svg>
1000
+ <svg width="0" height="0">
1001
+ <defs>
1002
+ <filter class="vzb-noexport" id="vzb-glow-filter-${config.id}" x="-50%" y="-50%" width="200%" height="200%">
1003
+ <feGaussianBlur in="SourceGraphic" stdDeviation="2"></feGaussianBlur>
1004
+ </filter>
1005
+ <pattern class="vzb-noexport" id="vzb-bc-pattern-lines-${config.id}" x="0" y="0" patternUnits="userSpaceOnUse" width="50" height="50" viewBox="0 0 10 10">
1006
+ <path d='M-1,1 l2,-2M0,10 l10,-10M9,11 l2,-2' stroke='black' stroke-width='3' opacity='0.08'/>
1007
+ </pattern>
1008
+ </defs>
1009
+ </svg>
1010
+ </svg>
1011
+ <div class="vzb-tooltip vzb-hidden vzb-tooltip-mobile"></div>
1012
+ `;
1013
+
1014
+ super(config);
1015
+ }
1016
+
1017
+ setup() {
1018
+
1019
+ this.DOM = {
1020
+ element: this.element,
1021
+ chartSvg: this.element.select("svg.vzb-bubblechart-svg-main"),
1022
+ chartSvgFront: this.element.select("svg.vzb-bubblechart-svg-front"),
1023
+ chartSvgBack: this.element.select("svg.vzb-bubblechart-svg-back"),
1024
+ chartSvgAll: this.element.selectAll("svg.vzb-bubblechart-svg"),
1025
+ graphAll: this.element.selectAll(".vzb-bc-graph"),
1026
+ bubbleContainerCropAll: this.element.selectAll(".vzb-bc-bubbles-crop"),
1027
+ zoomRect: this.element.select(".vzb-bc-zoom-rect"),
1028
+ eventArea: this.element.select(".vzb-bc-eventarea"),
1029
+ forecastOverlay: this.element.select(".vzb-bc-forecastoverlay"),
1030
+ tooltipMobile: this.element.select(".vzb-tooltip-mobile"),
1031
+ defs: this.element.select("defs")
1032
+ };
1033
+ this.DOM.chartSvg.select(".vzb-bc-graph").call(graph =>
1034
+ Object.assign(this.DOM, {
1035
+ graph: graph,
1036
+ ySubTitleEl: graph.select(".vzb-bc-axis-y-subtitle"),
1037
+ xSubTitleEl: graph.select(".vzb-bc-axis-x-subtitle"),
1038
+ yTitleEl: graph.select(".vzb-bc-axis-y-title"),
1039
+ xTitleEl: graph.select(".vzb-bc-axis-x-title"),
1040
+ sTitleEl: graph.select(".vzb-bc-axis-s-title"),
1041
+ cTitleEl: graph.select(".vzb-bc-axis-c-title"),
1042
+ yInfoEl: graph.select(".vzb-bc-axis-y-info"),
1043
+ xInfoEl: graph.select(".vzb-bc-axis-x-info"),
1044
+ trailsContainer: graph.select(".vzb-bc-trails"),
1045
+ bubbleContainer: graph.select(".vzb-bc-bubbles"),
1046
+ bubbleContainerCrop: graph.select(".vzb-bc-bubbles-crop"),
1047
+ zoomSelection: graph.select(".vzb-zoom-selection"),
1048
+ })
1049
+ );
1050
+ this.DOM.chartSvgFront.select(".vzb-bc-graph").call(graphFront => {
1051
+ Object.assign(this.DOM, {
1052
+ graphFront: graphFront,
1053
+ labelsContainer: graphFront.select(".vzb-bc-labels"),
1054
+ labelsContainerCrop: graphFront.select(".vzb-bc-labels-crop"),
1055
+ linesContainer: graphFront.select(".vzb-bc-lines"),
1056
+ bubbleCrown: graphFront.select(".vzb-bc-bubble-crown"),
1057
+ decorationsEl: graphFront.select(".vzb-bc-decorations"),
1058
+ });
1059
+ this.DOM.lineEqualXY = this.DOM.decorationsEl.select(".vzb-bc-line-equal-xy");
1060
+ this.DOM.xAxisGroupsEl = this.DOM.decorationsEl.select(".vzb-bc-x-axis-groups");
1061
+ });
1062
+ this.DOM.chartSvgBack.select(".vzb-bc-graph").call(graphBack => {
1063
+ Object.assign(this.DOM, {
1064
+ yAxisElContainer: graphBack.select(".vzb-bc-axis-y"),
1065
+ xAxisElContainer: graphBack.select(".vzb-bc-axis-x"),
1066
+ date: graphBack.select(".vzb-bc-date"),
1067
+ projectionX: graphBack.select(".vzb-bc-projection-x"),
1068
+ projectionY: graphBack.select(".vzb-bc-projection-y"),
1069
+ });
1070
+ this.DOM.yAxisEl = this.DOM.yAxisElContainer.select("g");
1071
+ this.DOM.xAxisEl = this.DOM.xAxisElContainer.select("g");
1072
+ });
1073
+
1074
+ //set filter
1075
+ this.DOM.bubbleCrown.selectAll(".vzb-crown-glow")
1076
+ .attr("filter", `url(${location.pathname}#vzb-glow-filter-${this.id})`);
1077
+
1078
+ this._date = this.findChild({type: "DateTimeBackground"});
1079
+ this._labels = this.findChild({type: "Labels"});
1080
+ this._panZoom = new PanZoom(this);
1081
+ this.decorations = new BCDecorations(this);
1082
+ this._initInfoElements();
1083
+
1084
+ this.scrollableAncestor = VizabiSharedComponents.LegacyUtils.findScrollableAncestor(this.element);
1085
+
1086
+ this.xAxis = VizabiSharedComponents.axisSmart("bottom");
1087
+ this.yAxis = VizabiSharedComponents.axisSmart("left");
1088
+
1089
+ this.axisTitleComplimentStrings = {Y: "", X: "", S: "", C: ""};
1090
+
1091
+ this.isCanvasPreviouslyExpanded = false;
1092
+ this.draggingNow = null;
1093
+
1094
+ this.hoverBubble = false;
1095
+ this.__lastStep = 0;
1096
+
1097
+ const _this = this;
1098
+ //keyboard listeners
1099
+ d3$1.select("body")
1100
+ .on("keydown", (event) => {
1101
+ if (_this.ui.cursorMode !== "arrow" && _this.ui.cursorMode !== "hand") return;
1102
+ if (event.metaKey || event.ctrlKey) _this.DOM.chartSvgAll.classed("vzb-zoomin", true);
1103
+ })
1104
+ .on("keyup", (event) => {
1105
+ if (_this.ui.cursorMode !== "arrow" && _this.ui.cursorMode !== "hand") return;
1106
+ if (!event.metaKey && !event.ctrlKey) _this.DOM.chartSvgAll.classed("vzb-zoomin", false);
1107
+ })
1108
+ //this is for the case when user would press ctrl and move away from the browser tab or window
1109
+ //keyup event would happen somewhere else and won't be captured, so zoomin class would get stuck
1110
+ .on("mouseenter", (event) => {
1111
+ if (_this.ui.cursorMode !== "arrow" && _this.ui.cursorMode !== "hand") return;
1112
+ if (!event.metaKey && !event.ctrlKey) _this.DOM.chartSvgAll.classed("vzb-zoomin", false);
1113
+ });
1114
+
1115
+ this.root.element.on("custom-resetZoom", () => {
1116
+ _this._panZoom.reset(null, 500);
1117
+ });
1118
+
1119
+ this._panZoom.zoomSelection(this.DOM.bubbleContainerCrop);
1120
+ this.DOM.bubbleContainerCrop
1121
+ .call(this._panZoom.dragRectangle)
1122
+ .call(this._panZoom.zoomer)
1123
+ .on("dblclick.zoom", null)
1124
+ .on("mouseup", () => {
1125
+ _this.draggingNow = false;
1126
+ })
1127
+ .on("click", (event) => {
1128
+ const cursor = _this.ui.cursorMode;
1129
+ if (!event.defaultPrevented && cursor !== "arrow" && cursor !== "hand") {
1130
+ _this._panZoom.zoomByIncrement(event, cursor, 500);
1131
+ }
1132
+ });
1133
+
1134
+ }
1135
+
1136
+ _initInfoElements() {
1137
+ const _this = this;
1138
+ const dataNotesDialog = () => this.root.findChild({type: "DataNotes"});
1139
+ const timeSlider = () => this.root.findChild({type: "TimeSlider"});
1140
+
1141
+ VizabiSharedComponents.LegacyUtils.setIcon(this.DOM.yInfoEl, ICON_QUESTION)
1142
+ .on("click", () => {
1143
+ dataNotesDialog().pin();
1144
+ })
1145
+ .on("mouseover", function() {
1146
+ if (timeSlider().ui.dragging) return;
1147
+ const rect = this.getBBox();
1148
+ const coord = VizabiSharedComponents.LegacyUtils.makeAbsoluteContext(this, this.farthestViewportElement)(rect.x - 10, rect.y + rect.height + 10);
1149
+ const toolRect = _this.root.element.node().getBoundingClientRect();
1150
+ const chartRect = _this.element.node().getBoundingClientRect();
1151
+ dataNotesDialog()
1152
+ .setEncoding(_this.MDL.y)
1153
+ .show()
1154
+ .setPos(coord.x + chartRect.left - toolRect.left, coord.y);
1155
+ })
1156
+ .on("mouseout", () => {
1157
+ if (timeSlider().ui.dragging) return;
1158
+ dataNotesDialog().hide();
1159
+ });
1160
+
1161
+ VizabiSharedComponents.LegacyUtils.setIcon(this.DOM.xInfoEl, ICON_QUESTION)
1162
+ .on("click", () => {
1163
+ dataNotesDialog().pin();
1164
+ })
1165
+ .on("mouseover", function() {
1166
+ if (timeSlider().ui.dragging) return;
1167
+ const rect = this.getBBox();
1168
+ const coord = VizabiSharedComponents.LegacyUtils.makeAbsoluteContext(this, this.farthestViewportElement)(rect.x - 10, rect.y + rect.height + 10);
1169
+ const toolRect = _this.root.element.node().getBoundingClientRect();
1170
+ const chartRect = _this.element.node().getBoundingClientRect();
1171
+ dataNotesDialog()
1172
+ .setEncoding(_this.MDL.x)
1173
+ .show()
1174
+ .setPos(coord.x + chartRect.left - toolRect.left, coord.y);
1175
+ })
1176
+ .on("mouseout", () => {
1177
+ if (timeSlider().ui.dragging) return;
1178
+ dataNotesDialog().hide();
1179
+ });
1180
+ }
1181
+
1182
+ get MDL(){
1183
+ return {
1184
+ frame: this.model.encoding.frame,
1185
+ selected: this.model.encoding.selected,
1186
+ highlighted: this.model.encoding.highlighted,
1187
+ superHighlighted: this.model.encoding.superhighlighted,
1188
+ y: this.model.encoding[this.state.alias?.y || "y"],
1189
+ x: this.model.encoding[this.state.alias?.x || "x"],
1190
+ size: this.model.encoding.size,
1191
+ color: this.model.encoding.color,
1192
+ label: this.model.encoding.label,
1193
+ trail: this.model.encoding.trail
1194
+ };
1195
+ }
1196
+
1197
+ draw() {
1198
+ this.localise = this.services.locale.auto(this.MDL.frame.interval);
1199
+
1200
+ //this.MDL.trail.config.show = false;
1201
+ //this.ui.cursorMode = "plus";
1202
+ this.sScale = this.MDL.size.scale.d3Scale;
1203
+
1204
+ this.TIMEDIM = this.MDL.frame.data.concept;
1205
+ this.KEYS = this.model.data.space.filter(dim => dim !== this.TIMEDIM);
1206
+
1207
+ if (this._updateLayoutProfile()) return; //return if exists with error
1208
+ this.addReaction(this._updateScales);
1209
+ this.addReaction(this.updateUIStrings);
1210
+ this.addReaction(this.updateTreemenu);
1211
+ this.addReaction(this._updateSize);
1212
+ this.addReaction(this.updateInfoElements);
1213
+ // this.addReaction(this._resetZoomMinMaxXReaction, this._resetZoomMinMaxX);
1214
+ // this.addReaction(this._resetZoomMinMaxYReaction, this._resetZoomMinMaxY);
1215
+ this.addReaction(this._updateOpacity);
1216
+ this.addReaction(this.updateColorPatterns);
1217
+ this.addReaction(this._updateShowYear);
1218
+ this.addReaction(this._updateYear);
1219
+ this.addReaction(this.drawData);
1220
+ this.addReaction(this._zoomToMarkerMaxMin);
1221
+ this.addReaction(this.redrawData);
1222
+
1223
+ this.addReaction(this._selectDataPoints);
1224
+ this.addReaction(this._highlightDataPoints);
1225
+ this.addReaction(this._blinkSuperHighlighted);
1226
+ this.addReaction(this._drawForecastOverlay);
1227
+ this.addReaction(this._setupCursorMode);
1228
+ this.addReaction(this.updateDecorations);
1229
+ }
1230
+
1231
+ drawData() {
1232
+ this.processFrameData();
1233
+ this._updateMarkerSizeLimits();
1234
+ this._createAndDeleteBubbles();
1235
+ //this.redrawData();
1236
+ }
1237
+
1238
+ updateColorPatterns() {
1239
+ if (this.MDL.color.scale.isPattern) {
1240
+ const colorConcept = this.MDL.color.data.concept;
1241
+ this.DOM.defs.selectAll(".flag")
1242
+ .data(this.MDL.color.data.domainData, d => d[0])
1243
+ .enter()
1244
+ .append("pattern")
1245
+ .attr("id", d => `flag-${d[0]}-${this.id}`)
1246
+ .attr("class", "flag")
1247
+ .attr("width", "100%")
1248
+ .attr("height", "100%")
1249
+ .attr("patternContentUnits", "objectBoundingBox")
1250
+ .html(d => d[1][colorConcept])
1251
+ .each(function() {
1252
+ const svg = d3$1.select(this).select("svg");
1253
+ if (svg.empty()) return;
1254
+
1255
+ if (!svg.attr("viewBox"))
1256
+ svg.attr("viewBox", `0 0 ${svg.attr("width")} ${svg.attr("height")}`);
1257
+
1258
+ // xMidYMid: center the image in the circle
1259
+ // slice: scale the image to fill the circle
1260
+ if (!svg.attr("preserveAspectRatio"))
1261
+ svg.attr("preserveAspectRatio", "xMidYMid slice");
1262
+
1263
+ svg.attr("width", 1).attr("height", 1);
1264
+ });
1265
+ // .attr("xlink:href", function(d) {
1266
+ // return "flags/" + d.CountryCode + ".svg";
1267
+ // });
1268
+ } else {
1269
+ this.DOM.defs.selectAll(".flag").remove();
1270
+ }
1271
+ }
1272
+
1273
+ _updateShowYear() {
1274
+ this.DOM.date.classed("vzb-hidden", !this.ui.timeInBackground);
1275
+ }
1276
+
1277
+ _updateYear() {
1278
+ const duration = this._getDuration();
1279
+ this._date.setText(this.MDL.frame.value, duration);
1280
+ }
1281
+
1282
+ __getColor(key, valueC) {
1283
+ return valueC != null ? (this.MDL.color.scale.isPattern ? `url(#flag-${key}-${this.id})` : this.cScale(valueC)) : COLOR_WHITEISH;
1284
+ }
1285
+
1286
+ _createAndDeleteBubbles() {
1287
+ const _this = this;
1288
+ const duration = this._getDuration();
1289
+ const transition = this._getTransition(duration);
1290
+ const data = this.__dataProcessed;
1291
+ let trailRedrawDate;
1292
+
1293
+ mobx.runInAction(()=>{
1294
+ trailRedrawDate = this.MDL.frame.stepScale.invert(Math.min(this.__lastStep, this.__lastStep = this.MDL.frame.step << 0) - 1);
1295
+ });
1296
+
1297
+ this.bubbles = this.DOM.bubbleContainer.selectAll(".vzb-bc-entity")
1298
+ .data(this.__dataProcessed, d => d[Symbol.for("key")])
1299
+ .join(
1300
+ enter => enter
1301
+ .append(d => {
1302
+ const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
1303
+ const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
1304
+ const trailLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
1305
+ const diagonalLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
1306
+ trailLine.classList.add("vzb-trail-line");
1307
+ diagonalLine.classList.add("vzb-diagonal-line");
1308
+ g.appendChild(circle);
1309
+ g.appendChild(diagonalLine);
1310
+ if (isTrailBubble(d)) g.appendChild(trailLine);
1311
+ return g;
1312
+ })
1313
+ .attr("class", "vzb-bc-entity")
1314
+ .attr("id", d => `vzb-bc-bubble-${d[Symbol.for("key")]}-${this.id}`)
1315
+ .style("opacity", d => d[Symbol.for("opacity")] = this._getBubbleOpacity(d))
1316
+ .call(selection => {
1317
+ if(!VizabiSharedComponents.LegacyUtils.isTouchDevice()){
1318
+ selection
1319
+ .on("mouseover", (event, d) => {
1320
+ if (this.ui.cursorMode !== "arrow" && this.ui.cursorMode !== "hand") return;
1321
+ if (this._labels.dragging) return;
1322
+ this._bubblesInteract().mouseover(event, d);
1323
+ })
1324
+ .on("mouseout", (event, d) => {
1325
+ if (this.ui.cursorMode !== "arrow" && this.ui.cursorMode !== "hand") return;
1326
+ if (this._labels.dragging) return;
1327
+ this._bubblesInteract().mouseout(event, d);
1328
+ })
1329
+ .on("click", (event, d) => {
1330
+ if (this.ui.cursorMode !== "arrow" && this.ui.cursorMode !== "hand") return;
1331
+ this._bubblesInteract().click(event, d);
1332
+ });
1333
+ } else {
1334
+ selection
1335
+ .onTap((event, d) => {
1336
+ event.stopPropagation();
1337
+ this._bubblesInteract().click(event, d);
1338
+ })
1339
+ .onLongTap(() => {});
1340
+ }
1341
+ })
1342
+ .each(function(d, index) {
1343
+ const dataNext = data[index + 1] || {};
1344
+ const isTrail = isTrailBubble(d);
1345
+ const isExtrapolated = d[Symbol.for("extrapolated")];
1346
+ const headTrail = isTrail && !dataNext[Symbol.for("trailHeadKey")];
1347
+ const view = d3$1.select(this);
1348
+ const circle = view.select("circle");
1349
+ const diagonalLine = view.select(".vzb-diagonal-line");
1350
+
1351
+ const valueX = d[_this._alias("x")];
1352
+ const valueY = d[_this._alias("y")];
1353
+ const valueS = d.size;
1354
+ const valueC = d.color;
1355
+
1356
+ //d.hidden = (!valueS && valueS !== 0) || valueX == null || valueY == null;
1357
+
1358
+ //view.classed("vzb-hidden", d.hidden);
1359
+ d.r = VizabiSharedComponents.LegacyUtils.areaToRadius(_this.sScale(valueS || 0));
1360
+ const scaledX = _this.xScale(valueX);
1361
+ const scaledY = _this.yScale(valueY);
1362
+ const scaledC = _this.__getColor(d[Symbol.for(isTrail ? "trailHeadKey" : "key")], valueC);
1363
+
1364
+ if (!duration || !headTrail) {
1365
+ circle
1366
+ .attr("r", d.r)
1367
+ .attr("fill", scaledC)
1368
+ .attr("cy", scaledY)
1369
+ .attr("cx", scaledX);
1370
+ //.transition(transition)
1371
+
1372
+ if(isExtrapolated)
1373
+ diagonalLine
1374
+ .attr("x1", scaledX + d.r/Math.sqrt(2))
1375
+ .attr("y1", scaledY + d.r/Math.sqrt(2))
1376
+ .attr("x2", scaledX - d.r/Math.sqrt(2))
1377
+ .attr("y2", scaledY - d.r/Math.sqrt(2));
1378
+ diagonalLine
1379
+ .classed("vzb-hidden", !isExtrapolated);
1380
+
1381
+ //trail line
1382
+ if (isTrail) {
1383
+ const trailLine = view.select(".vzb-trail-line");
1384
+
1385
+ const scaledX0 = _this.xScale(dataNext[_this._alias("x")]);
1386
+ const scaledY0 = _this.yScale(dataNext[_this._alias("y")]);
1387
+
1388
+ trailLine
1389
+ .attr("x1", scaledX)
1390
+ .attr("y1", scaledY)
1391
+ .attr("x2", scaledX0)
1392
+ .attr("y2", scaledY0)
1393
+ .attr("stroke-dasharray", Math.abs(scaledX - scaledX0) + Math.abs(scaledY - scaledY0))
1394
+ .attr("stroke-dashoffset", -d.r)
1395
+ .style("stroke", _this.MDL.color.scale.isPattern ? null : scaledC);
1396
+ }
1397
+ }
1398
+
1399
+ if (duration && !isTrail) {
1400
+ view
1401
+ .style("opacity", 0)
1402
+ .transition().duration(duration*0.9)
1403
+ .style("opacity", d[Symbol.for("opacity")]);
1404
+ }
1405
+
1406
+ if (!isTrail) {
1407
+ _this._updateLabel(d, valueX, valueY, duration, true, false);
1408
+ }
1409
+ }),
1410
+
1411
+ update => update
1412
+ .each(function(d, index) {
1413
+
1414
+ const isTrail = isTrailBubble(d);
1415
+ const isExtrapolated = d[Symbol.for("extrapolated")];
1416
+ const dataNext = data[index + 1] || {};
1417
+ const headTrail = isTrail && !dataNext[Symbol.for("trailHeadKey")];
1418
+
1419
+ const valueS = d.size;
1420
+ d.r = VizabiSharedComponents.LegacyUtils.areaToRadius(_this.sScale(valueS || 0));
1421
+ if (isTrail && d["frame"] < trailRedrawDate) return;
1422
+
1423
+ const valueX = d[_this._alias("x")];
1424
+ const valueY = d[_this._alias("y")];
1425
+ const valueC = d.color;
1426
+
1427
+ //d.hidden = (!valueS && valueS !== 0) || valueX == null || valueY == null;
1428
+
1429
+ //view.classed("vzb-hidden", d.hidden);
1430
+ const scaledX = _this.xScale(valueX);
1431
+ const scaledY = _this.yScale(valueY);
1432
+ const scaledC = _this.__getColor(d[Symbol.for(isTrail ? "trailHeadKey" : "key")], valueC);
1433
+
1434
+ const group = d3$1.select(this);
1435
+ if (!duration || !headTrail) {
1436
+ const circle = group.select("circle");
1437
+ if (duration && !isTrail) {
1438
+ circle.transition(transition)
1439
+ .attr("r", d.r)
1440
+ .attr("fill", scaledC)
1441
+ .attr("cy", scaledY)
1442
+ .attr("cx", scaledX);
1443
+ } else {
1444
+ circle.interrupt()
1445
+ .attr("r", d.r)
1446
+ .attr("fill", scaledC)
1447
+ .attr("cy", scaledY)
1448
+ .attr("cx", scaledX);
1449
+ }
1450
+
1451
+ const diagonalLine = group.select(".vzb-diagonal-line");
1452
+ diagonalLine
1453
+ .classed("vzb-hidden", !isExtrapolated);
1454
+ if(isExtrapolated){
1455
+ if (duration && !isTrail){
1456
+ diagonalLine.transition(transition)
1457
+ .attr("x1", scaledX + d.r/Math.sqrt(2))
1458
+ .attr("y1", scaledY + d.r/Math.sqrt(2))
1459
+ .attr("x2", scaledX - d.r/Math.sqrt(2))
1460
+ .attr("y2", scaledY - d.r/Math.sqrt(2));
1461
+ } else {
1462
+ diagonalLine.interrupt()
1463
+ .attr("x1", scaledX + d.r/Math.sqrt(2))
1464
+ .attr("y1", scaledY + d.r/Math.sqrt(2))
1465
+ .attr("x2", scaledX - d.r/Math.sqrt(2))
1466
+ .attr("y2", scaledY - d.r/Math.sqrt(2));
1467
+ }
1468
+ }
1469
+
1470
+ //trail line
1471
+ if (isTrail) {
1472
+ const trailLine = group.select(".vzb-trail-line");
1473
+ const scaledX0 = _this.xScale(dataNext[_this._alias("x")]);
1474
+ const scaledY0 = _this.yScale(dataNext[_this._alias("y")]);
1475
+
1476
+ trailLine
1477
+ .attr("x1", scaledX)
1478
+ .attr("y1", scaledY);
1479
+ if (duration && !data[index + 2][Symbol.for("trailHeadKey")]) {
1480
+ trailLine
1481
+ .attr("x2", scaledX)
1482
+ .attr("y2", scaledY)
1483
+ .transition(transition)
1484
+ .attr("x2", scaledX0)
1485
+ .attr("y2", scaledY0);
1486
+ } else {
1487
+ trailLine.interrupt()
1488
+ .attr("x2", scaledX0)
1489
+ .attr("y2", scaledY0);
1490
+ }
1491
+
1492
+ trailLine
1493
+ .style("stroke", _this.MDL.color.scale.isPattern ? null : scaledC)
1494
+ .attr("stroke-dasharray", Math.abs(scaledX - scaledX0) + Math.abs(scaledY - scaledY0))
1495
+ .attr("stroke-dashoffset", -d.r);
1496
+ }
1497
+ }
1498
+
1499
+ if (!isTrail)
1500
+ _this._updateLabel(d, valueX, valueY, duration, false, false);
1501
+ }),
1502
+
1503
+ exit => exit
1504
+ .each(function(d) {
1505
+ const isTrail = isTrailBubble(d);
1506
+
1507
+ const view = duration && !isTrail ?
1508
+ d3$1.select(this).transition(transition)
1509
+ .duration(duration*0.9)
1510
+ .style("opacity", 0)
1511
+ :
1512
+ d3$1.select(this).interrupt();
1513
+
1514
+ view
1515
+ .remove();
1516
+
1517
+ if (!isTrail)
1518
+ _this._updateLabel(d, d[_this._alias("x")], d[_this._alias("y")], duration, true, true);
1519
+ })
1520
+ )
1521
+ .order();
1522
+
1523
+ }
1524
+
1525
+
1526
+ redrawData(duration) {
1527
+ //this.services.layout.size;
1528
+ //this.MDL.x.scale.type;
1529
+ //this.MDL.y.scale.type;
1530
+ this.MDL.color.scale.type;
1531
+ this.MDL.size.scale.type;
1532
+ this.MDL.size.scale.extent;
1533
+
1534
+ const _this = this;
1535
+ const data = this.__dataProcessed;
1536
+ const transition = this._getTransition(duration);
1537
+
1538
+ if (this.bubbles) this.bubbles.each(function(d, index) {
1539
+ const isTrail = isTrailBubble(d);
1540
+ const dataNext = data[index + 1] || {};
1541
+ const headTrail = isTrail && !dataNext[Symbol.for("trailHeadKey")];
1542
+ const isExtrapolated = d[Symbol.for("extrapolated")];
1543
+
1544
+ const valueX = d[_this._alias("x")];
1545
+ const valueY = d[_this._alias("y")];
1546
+ const valueS = d.size;
1547
+ const valueC = d.color;
1548
+
1549
+ d.r = VizabiSharedComponents.LegacyUtils.areaToRadius(_this.sScale(valueS || 0));
1550
+ const scaledX = _this.xScale(valueX);
1551
+ const scaledY = _this.yScale(valueY);
1552
+ const scaledC = _this.__getColor(d[Symbol.for(isTrail ? "trailHeadKey" : "key")], valueC);
1553
+
1554
+ const group = d3$1.select(this);
1555
+
1556
+ if (duration && headTrail) {
1557
+ group.style("opacity", 0)
1558
+ .transition().delay(duration).duration(0)
1559
+ .style("opacity", d[Symbol.for("opacity")]);
1560
+ }
1561
+
1562
+ const circle = group.select("circle");
1563
+ if (duration && !headTrail) {
1564
+ circle.transition(transition)
1565
+ .attr("r", d.r)
1566
+ .attr("fill", scaledC)
1567
+ .attr("cy", scaledY)
1568
+ .attr("cx", scaledX);
1569
+ } else {
1570
+ circle.interrupt()
1571
+ .attr("r", d.r)
1572
+ .attr("fill", scaledC)
1573
+ .attr("cy", scaledY)
1574
+ .attr("cx", scaledX);
1575
+ }
1576
+
1577
+ const diagonalLine = group.select(".vzb-diagonal-line");
1578
+ diagonalLine
1579
+ .classed("vzb-hidden", !isExtrapolated);
1580
+ if(isExtrapolated){
1581
+ if (duration && !headTrail){
1582
+ diagonalLine.transition(transition)
1583
+ .attr("x1", scaledX + d.r/Math.sqrt(2))
1584
+ .attr("y1", scaledY + d.r/Math.sqrt(2))
1585
+ .attr("x2", scaledX - d.r/Math.sqrt(2))
1586
+ .attr("y2", scaledY - d.r/Math.sqrt(2));
1587
+ } else {
1588
+ diagonalLine.interrupt()
1589
+ .attr("x1", scaledX + d.r/Math.sqrt(2))
1590
+ .attr("y1", scaledY + d.r/Math.sqrt(2))
1591
+ .attr("x2", scaledX - d.r/Math.sqrt(2))
1592
+ .attr("y2", scaledY - d.r/Math.sqrt(2));
1593
+ }
1594
+ }
1595
+
1596
+
1597
+ if (isTrail) {
1598
+ const trailLine = (duration && !headTrail) ?
1599
+ group.select(".vzb-trail-line")
1600
+ .transition(transition)
1601
+ : group.select(".vzb-trail-line").interrupt();
1602
+
1603
+ const dataNext = data[index + 1];
1604
+ const scaledX0 = _this.xScale(dataNext[_this._alias("x")]);
1605
+ const scaledY0 = _this.yScale(dataNext[_this._alias("y")]);
1606
+
1607
+ trailLine
1608
+ .attr("x1", scaledX)
1609
+ .attr("y1", scaledY)
1610
+ .attr("x2", scaledX0)
1611
+ .attr("y2", scaledY0)
1612
+ .style("stroke", _this.MDL.color.scale.isPattern ? null : scaledC)
1613
+ .attr("stroke-dasharray", Math.abs(scaledX - scaledX0) + Math.abs(scaledY - scaledY0))
1614
+ .attr("stroke-dashoffset", -d.r);
1615
+ }
1616
+ });
1617
+
1618
+ mobx.runInAction(() => _this._updateLabels(duration));
1619
+ }
1620
+
1621
+ __getZoomed(type, zoomed, domain) {
1622
+ //const zoomed = values[`zoomed${type}`];
1623
+ return d3[type.toLowerCase()](zoomed !== null ? zoomed : domain);
1624
+ }
1625
+
1626
+ __getZoomedMin(values, domain) {
1627
+ return this.__getZoomed("Min", values, domain);
1628
+ }
1629
+
1630
+ __getZoomedMax(values, domain) {
1631
+ return this.__getZoomed("Max", values, domain);
1632
+ }
1633
+
1634
+ /*
1635
+ * Zoom to the min and max values given in the URL axes markers.
1636
+ */
1637
+ _zoomToMarkerMaxMin() {
1638
+ this.services.layout.size;
1639
+ this.MDL.x.scale.type;
1640
+ this.MDL.y.scale.type;
1641
+
1642
+ const panzoom = //this.ui.panzoom;
1643
+ {
1644
+ x: this.MDL.x.scale.zoomed,
1645
+ y: this.MDL.y.scale.zoomed
1646
+ };
1647
+
1648
+ const xDomain = this.MDL.x.data.domain;
1649
+ const yDomain = this.MDL.y.data.domain;
1650
+
1651
+ if (this.draggingNow) return;
1652
+
1653
+ /*
1654
+ * Reset just the zoom values without triggering a zoom event. This ensures
1655
+ * a clean zoom state for the subsequent zoom event.
1656
+ */
1657
+ this._panZoom.resetZoomState();
1658
+
1659
+ this.yScale.range(this._rangeBump([this.height, 0]));
1660
+ this.xScale.range(this._rangeBump([0, this.width]));
1661
+
1662
+ /*
1663
+ * The axes may return null when there is no value given for the zoomed
1664
+ * min and max values. In that case, fall back to the axes' domain values.
1665
+ */
1666
+ const zoomedMinX = this.__getZoomedMin(panzoom.x, xDomain);
1667
+ const zoomedMaxX = this.__getZoomedMax(panzoom.x, xDomain);
1668
+ const zoomedMinY = this.__getZoomedMin(panzoom.y, yDomain);
1669
+ const zoomedMaxY = this.__getZoomedMax(panzoom.y, yDomain);
1670
+
1671
+ //by default this will apply no transition and feed values back to state
1672
+ mobx.runInAction(() => {
1673
+ this._panZoom.zoomToMaxMin(zoomedMinX, zoomedMaxX, zoomedMinY, zoomedMaxY, 0, "don't feed these zoom values back to state");
1674
+ });
1675
+ }
1676
+
1677
+ _resetZoomMinMaxXReaction() {
1678
+ return { concept: this.MDL.x.data.concept };
1679
+ }
1680
+
1681
+ _resetZoomMinMaxX() {
1682
+ this.ui.panzoom.x = {zoomedMin: null, zoomedMax: null};
1683
+ }
1684
+
1685
+ _resetZoomMinMaxYReaction() {
1686
+ return { concept: this.MDL.y.data.concept };
1687
+ }
1688
+
1689
+ _resetZoomMinMaxY() {
1690
+ this.ui.panzoom.y = {zoomedMin: null, zoomedMax: null};
1691
+ }
1692
+
1693
+ _drawForecastOverlay() {
1694
+ this.DOM.forecastOverlay.classed("vzb-hidden",
1695
+ !this.ui.showForecast ||
1696
+ !this.ui.showForecastOverlay ||
1697
+ !this.ui.endBeforeForecast ||
1698
+ (this.MDL.frame.value <= this.MDL.frame.parseValue(this.ui.endBeforeForecast))
1699
+ );
1700
+ }
1701
+
1702
+ _updateLayoutProfile(){
1703
+ this.services.layout.size;
1704
+
1705
+ this.elementHeight = (this.element.node().clientHeight) || 0;
1706
+ this.elementWidth = (this.element.node().clientWidth) || 0;
1707
+
1708
+ this.profileConstants = this.services.layout.getProfileConstants(
1709
+ PROFILE_CONSTANTS(this.elementWidth, this.elementHeight),
1710
+ PROFILE_CONSTANTS_FOR_PROJECTOR(this.elementWidth, this.elementHeight)
1711
+ );
1712
+
1713
+ if (!this.elementHeight || !this.elementWidth) return VizabiSharedComponents.LegacyUtils.warn("Chart _updateProfile() abort: container is too little or has display:none");
1714
+ }
1715
+
1716
+ _getDuration() {
1717
+ return this.MDL.frame.playing ? this.MDL.frame.speed || 0 : 0;
1718
+ }
1719
+
1720
+ _updateScales() {
1721
+ this.yScale = this.MDL.y.scale.d3Scale.copy();
1722
+ this.xScale = this.MDL.x.scale.d3Scale.copy();
1723
+ this._labels.setScales(this.xScale, this.yScale);
1724
+ }
1725
+
1726
+ get cScale() {
1727
+ return this.MDL.color.scale.d3Scale;
1728
+ }
1729
+
1730
+ updateUIStrings() {
1731
+ const {
1732
+ y, x, size, color
1733
+ } = this.MDL;
1734
+
1735
+ this.strings = {
1736
+ title: {
1737
+ Y: VizabiSharedComponents.Utils.getConceptName(y, this.localise),
1738
+ X: VizabiSharedComponents.Utils.getConceptName(x, this.localise),
1739
+ S: VizabiSharedComponents.Utils.getConceptName(size, this.localise),
1740
+ C: VizabiSharedComponents.Utils.getConceptName(color, this.localise)
1741
+ },
1742
+ title_short: {
1743
+ Y: VizabiSharedComponents.Utils.getConceptShortName(y, this.localise),
1744
+ X: VizabiSharedComponents.Utils.getConceptShortName(x, this.localise),
1745
+ S: VizabiSharedComponents.Utils.getConceptShortName(size, this.localise),
1746
+ C: VizabiSharedComponents.Utils.getConceptShortName(color, this.localise)
1747
+ },
1748
+ subtitle: {
1749
+ Y: VizabiSharedComponents.Utils.getConceptNameMinusShortName(y, this.localise),
1750
+ X: VizabiSharedComponents.Utils.getConceptNameMinusShortName(x, this.localise)
1751
+ },
1752
+ unit: {
1753
+ Y: VizabiSharedComponents.Utils.getConceptUnit(y),
1754
+ X: VizabiSharedComponents.Utils.getConceptUnit(x),
1755
+ S: VizabiSharedComponents.Utils.getConceptUnit(size),
1756
+ C: VizabiSharedComponents.Utils.getConceptUnit(color)
1757
+ }
1758
+ };
1759
+
1760
+ Promise.all([
1761
+ VizabiSharedComponents.Utils.getConceptNameCompliment(y),
1762
+ VizabiSharedComponents.Utils.getConceptNameCompliment(x),
1763
+ VizabiSharedComponents.Utils.getConceptNameCompliment(size),
1764
+ VizabiSharedComponents.Utils.getConceptNameCompliment(color)
1765
+ ]).then(mobx.action(response => {
1766
+ [
1767
+ this.axisTitleComplimentStrings.Y,
1768
+ this.axisTitleComplimentStrings.X,
1769
+ this.axisTitleComplimentStrings.S,
1770
+ this.axisTitleComplimentStrings.C
1771
+ ] = response;
1772
+ }));
1773
+ }
1774
+
1775
+ updateTreemenu(){
1776
+ const treemenu = this.root.findChild({type: "TreeMenu"});
1777
+
1778
+ this.DOM.yTitleEl
1779
+ .classed("vzb-disabled", treemenu.state.ownReadiness !== VizabiSharedComponents.Utils.STATUS.READY)
1780
+ .on("click", () => {
1781
+ treemenu
1782
+ .encoding(this._alias("y"))
1783
+ .alignX(this.services.locale.isRTL() ? "right" : "left")
1784
+ .alignY("top")
1785
+ .updateView()
1786
+ .toggle();
1787
+ });
1788
+
1789
+ this.DOM.xTitleEl
1790
+ .classed("vzb-disabled", treemenu.state.ownReadiness !== VizabiSharedComponents.Utils.STATUS.READY)
1791
+ .on("click", () => {
1792
+ treemenu
1793
+ .encoding(this._alias("x"))
1794
+ .alignX(this.services.locale.isRTL() ? "right" : "left")
1795
+ .alignY("bottom")
1796
+ .updateView()
1797
+ .toggle();
1798
+ });
1799
+ }
1800
+
1801
+ _updateSize() {
1802
+ this.services.layout.size;
1803
+
1804
+ const {
1805
+ x,
1806
+ y
1807
+ } = this.MDL;
1808
+
1809
+ const {
1810
+ graphAll,
1811
+ eventArea,
1812
+ bubbleContainerCropAll,
1813
+ labelsContainerCrop,
1814
+ xAxisElContainer,
1815
+ xAxisEl,
1816
+ yAxisElContainer,
1817
+ yAxisEl,
1818
+ projectionX,
1819
+ projectionY,
1820
+ sTitleEl,
1821
+ xTitleEl,
1822
+ yTitleEl,
1823
+ xSubTitleEl,
1824
+ ySubTitleEl,
1825
+ xAxisGroupsEl,
1826
+ } = this.DOM;
1827
+
1828
+ const _this = this;
1829
+
1830
+ const layoutProfile = this.services.layout.profile;
1831
+
1832
+ const margin = this.profileConstants.margin;
1833
+ const infoElHeight = this.profileConstants.infoElHeight;
1834
+ const xAxisTitleBottomMargin = this.profileConstants.xAxisTitleBottomMargin;
1835
+
1836
+ //stage
1837
+ const height = this.height = (this.elementHeight - margin.top - margin.bottom) || 0;
1838
+ const width = this.width = (this.elementWidth - margin.left * this.profileConstants.leftMarginRatio - margin.right) || 0;
1839
+
1840
+ // if (height <= 0 || width <= 0) {
1841
+ // height = 0;
1842
+ // width = 0;
1843
+ // utils.warn("Bubble chart updateSize(): vizabi container is too little or has display:none");
1844
+ // }
1845
+
1846
+ //graph group is shifted according to margins (while svg element is at 100 by 100%)
1847
+ graphAll
1848
+ .attr("transform", "translate(" + (margin.left * this.profileConstants.leftMarginRatio) + "," + margin.top + ")");
1849
+
1850
+ this._date.resizeText(width, height);
1851
+
1852
+ eventArea
1853
+ .attr("width", width)
1854
+ .attr("height", Math.max(0, height));
1855
+
1856
+ //update scales to the new range
1857
+ // // if (this.model.marker.y.scaleType !== "ordinal") {
1858
+ // // this.yScale.range(this._rangeBump([height, 0]));
1859
+ // // } else {
1860
+ // // this.yScale.rangePoints([height, 0], _this.profileConstants.padding).range();
1861
+ // // }
1862
+ // // if (this.model.marker.x.scaleType !== "ordinal") {
1863
+ // // this.xScale.range(this._rangeBump([0, width]));
1864
+ // // } else {
1865
+ // // this.xScale.rangePoints([0, width], _this.profileConstants.padding).range();
1866
+ // // }
1867
+ this.yScale.range(this._rangeBump([height, 0]));
1868
+ this.xScale.range(this._rangeBump([0, width]));
1869
+
1870
+ //apply scales to axes and redraw
1871
+ this.yAxis.scale(this.yScale)
1872
+ .tickSizeInner(-width)
1873
+ .tickSizeOuter(0)
1874
+ .tickPadding(6)
1875
+ .tickSizeMinor(-width, 0)
1876
+ .labelerOptions({
1877
+ scaleType: y.scale.type,
1878
+ toolMargin: margin,
1879
+ limitMaxTickNumber: 6,
1880
+ bump: this.profileConstants.maxRadiusPx / 2,
1881
+ viewportLength: height,
1882
+ formatter: this.localise
1883
+ });
1884
+
1885
+ this.xAxis.scale(this.xScale)
1886
+ .tickSizeInner(-height)
1887
+ .tickSizeOuter(0)
1888
+ .tickPadding(6)
1889
+ .tickSizeMinor(-height, 0)
1890
+ .labelerOptions({
1891
+ scaleType: x.scale.type,
1892
+ toolMargin: margin,
1893
+ bump: this.profileConstants.maxRadiusPx / 2,
1894
+ viewportLength: width,
1895
+ formatter: this.localise
1896
+ });
1897
+
1898
+
1899
+ bubbleContainerCropAll
1900
+ .attr("width", width)
1901
+ .attr("height", Math.max(0, height));
1902
+
1903
+ labelsContainerCrop
1904
+ .attr("width", width)
1905
+ .attr("height", Math.max(0, height));
1906
+
1907
+ xAxisElContainer
1908
+ .attr("width", width + 1)
1909
+ .attr("height", this.profileConstants.margin.bottom + height)
1910
+ .attr("y", -1)
1911
+ .attr("x", -1);
1912
+ xAxisEl
1913
+ .attr("transform", "translate(1," + (1 + height) + ")");
1914
+
1915
+ yAxisElContainer
1916
+ .attr("width", this.profileConstants.margin.left + width)
1917
+ .attr("height", Math.max(0, height))
1918
+ .attr("x", -this.profileConstants.margin.left);
1919
+ yAxisEl
1920
+ .attr("transform", "translate(" + (this.profileConstants.margin.left - 1) + "," + 0 + ")");
1921
+
1922
+ yAxisEl.call(this.yAxis);
1923
+ xAxisEl.call(this.xAxis);
1924
+
1925
+ const rangeBump = this.profileConstants.maxRadiusPx;
1926
+ projectionX.attr("y1", _this.yScale.range()[0] + rangeBump);
1927
+ projectionY.attr("x2", _this.xScale.range()[0] - rangeBump);
1928
+
1929
+
1930
+ // reduce font size if the caption doesn't fit
1931
+ this._updateSTitle();
1932
+ sTitleEl
1933
+ .attr("text-anchor", "end")
1934
+ .attr("transform", "translate(" + width + "," + 20 + ") rotate(-90)");
1935
+
1936
+ const compl = this.axisTitleComplimentStrings;
1937
+ if (layoutProfile !== "SMALL") {
1938
+ ySubTitleEl.select("text").attr("dy", infoElHeight * 0.6).text(this.strings.subtitle.Y);
1939
+ xSubTitleEl.select("text").attr("dy", -infoElHeight * 0.3).text(this.strings.subtitle.X);
1940
+
1941
+ yTitleEl.select("text").text(this.strings.title_short.Y + (compl.Y ? " · " + compl.Y : "") + " ")
1942
+ .append("tspan")
1943
+ .classed("vzb-noexport", true)
1944
+ .style("font-size", (infoElHeight * 0.7) + "px")
1945
+ .text("▼");
1946
+ xTitleEl.select("text").text(this.strings.title_short.X + (compl.X ? " · " + compl.X : "") + " ")
1947
+ .append("tspan")
1948
+ .classed("vzb-noexport", true)
1949
+ .style("font-size", (infoElHeight * 0.7) + "px")
1950
+ .text("▼");
1951
+ } else {
1952
+ ySubTitleEl.select("text").text("");
1953
+ xSubTitleEl.select("text").text("");
1954
+
1955
+ const yTitleText = yTitleEl.select("text").text(this.strings.title.Y + (compl.Y ? " · " + compl.Y : ""));
1956
+ if (yTitleText.node().getBBox().width > width) yTitleText.text(this.strings.title_short.Y + (compl.Y ? " · " + compl.Y : ""));
1957
+
1958
+ const xTitleText = xTitleEl.select("text").text(this.strings.title.X + (compl.X ? " · " + compl.X : ""));
1959
+ if (xTitleText.node().getBBox().width > width - 100) xTitleText.text(this.strings.title_short.X) + (compl.X ? " · " + compl.X : "");
1960
+ }
1961
+
1962
+ const isRTL = this.services.locale.isRTL();
1963
+ ySubTitleEl
1964
+ .style("font-size", (infoElHeight * 0.8) + "px")
1965
+ .attr("transform", "translate(" + 0 + "," + 0 + ") rotate(-90)");
1966
+ xSubTitleEl
1967
+ .style("font-size", (infoElHeight * 0.8) + "px")
1968
+ .attr("transform", "translate(" + width + "," + height + ")");
1969
+
1970
+ yTitleEl
1971
+ .style("font-size", infoElHeight + "px")
1972
+ .attr("transform", layoutProfile !== "SMALL" ?
1973
+ "translate(" + (-margin.left - this.profileConstants.yAxisTitleBottomMargin) + "," + (height * 0.5) + ") rotate(-90)"
1974
+ :
1975
+ "translate(" + (isRTL ? width : 10 - this.profileConstants.margin.left) + ", -" + this.profileConstants.yAxisTitleBottomMargin + ")");
1976
+
1977
+ xTitleEl
1978
+ .style("font-size", infoElHeight + "px")
1979
+ .attr("transform", layoutProfile !== "SMALL" ?
1980
+ "translate(" + (width * 0.5) + "," + (height + margin.bottom - xAxisTitleBottomMargin) + ")"
1981
+ :
1982
+ "translate(" + (isRTL ? width : 0) + "," + (height + margin.bottom - xAxisTitleBottomMargin) + ")");
1983
+
1984
+ xAxisGroupsEl
1985
+ .style("font-size", infoElHeight * 0.8 + "px");
1986
+
1987
+ this.root.findChild({type: "_DataWarning"}).setOptions({
1988
+ width: this.elementWidth,
1989
+ height: this.elementHeight,
1990
+ vertical: "bottom",
1991
+ horizontal: "right",
1992
+ right: margin.right,
1993
+ bottom: xAxisTitleBottomMargin,
1994
+ wLimit: (layoutProfile !== "SMALL" ? 0.5 : 1) *
1995
+ (this.elementWidth - xTitleEl.node().getBBox().width - infoElHeight * 3)
1996
+ });
1997
+
1998
+ //this.services.layout.setHGrid([this.elementWidth - marginRightAdjusted]);
1999
+ //this.ui.margin.set("left", margin.left * this.profileConstants.leftMarginRatio, false, false);
2000
+
2001
+ // (function(xMin, xMax, yMin, yMax) {
2002
+ // if ((xMin && xMax && yMin && yMax) === null) return;
2003
+ // _this._panZoom.zoomer.dontFeedToState = true;
2004
+ // _this._panZoom.rerun(); // includes redraw data points and trail resize
2005
+ // _this._panZoom.zoomToMaxMin(xMin, xMax, yMin, yMax, 0, true);
2006
+ // })(_this._zoomedXYMinMax.x.zoomedMin,
2007
+ // _this._zoomedXYMinMax.x.zoomedMax,
2008
+ // _this._zoomedXYMinMax.y.zoomedMin,
2009
+ // _this._zoomedXYMinMax.y.zoomedMax);
2010
+ }
2011
+
2012
+ updateInfoElements() {
2013
+ this.services.layout.size;
2014
+ this.axisTitleComplimentStrings.X;
2015
+ this.axisTitleComplimentStrings.Y;
2016
+
2017
+ const {xInfoEl, yInfoEl, xTitleEl, yTitleEl} = this.DOM;
2018
+ const {x, y} = this.MDL;
2019
+ const isRTL = this.services.locale.isRTL();
2020
+ const infoElHeight = this.profileConstants.infoElHeight;
2021
+ const layoutProfile = this.services.layout.profile;
2022
+
2023
+ if (yInfoEl.select("svg").node()) {
2024
+ const titleBBox = yTitleEl.node().getBBox();
2025
+ const t = VizabiSharedComponents.LegacyUtils.transform(yTitleEl.node());
2026
+ const hTranslate = isRTL ? (titleBBox.x + t.translateX - infoElHeight * 1.4) : (titleBBox.x + t.translateX + titleBBox.width + infoElHeight * 0.4);
2027
+ const vTranslate = isRTL ? (t.translateY + infoElHeight * 1.4 + titleBBox.width * 0.5) : (t.translateY - infoElHeight * 0.4 - titleBBox.width * 0.5);
2028
+ const conceptPropsY = y.data.conceptProps;
2029
+
2030
+ yInfoEl
2031
+ .classed("vzb-hidden", !conceptPropsY.description && !conceptPropsY.sourceLink || this.services.layout.projector)
2032
+ .attr("transform", layoutProfile !== "SMALL" ?
2033
+ `translate(${ t.translateX - infoElHeight * 0.8 }, ${ vTranslate }) rotate(-90)` :
2034
+ `translate(${ hTranslate },${ t.translateY - infoElHeight * 0.8 })`)
2035
+ .select("svg")
2036
+ .attr("width", infoElHeight + "px")
2037
+ .attr("height", infoElHeight + "px");
2038
+ }
2039
+
2040
+ if (xInfoEl.select("svg").node()) {
2041
+ const titleBBox = xTitleEl.node().getBBox();
2042
+ const t = VizabiSharedComponents.LegacyUtils.transform(xTitleEl.node());
2043
+ const hTranslate = isRTL ? (titleBBox.x + t.translateX - infoElHeight * 1.4) : (titleBBox.x + t.translateX + titleBBox.width + infoElHeight * 0.4);
2044
+ const conceptPropsX = x.data.conceptProps;
2045
+
2046
+ xInfoEl
2047
+ .classed("vzb-hidden", !conceptPropsX.description && !conceptPropsX.sourceLink || this.services.layout.projector)
2048
+ .attr("transform", `translate(${ hTranslate }, ${ t.translateY - infoElHeight * 0.8 })`)
2049
+ .select("svg")
2050
+ .attr("width", infoElHeight + "px")
2051
+ .attr("height", infoElHeight + "px");
2052
+ }
2053
+ }
2054
+
2055
+ _rangeBump(arg, undo) {
2056
+ const bump = this.profileConstants.maxRadiusPx;
2057
+ undo = undo ? -1 : 1;
2058
+ if (VizabiSharedComponents.LegacyUtils.isArray(arg) && arg.length > 1) {
2059
+ let z1 = arg[0];
2060
+ let z2 = arg[arg.length - 1];
2061
+
2062
+ //the sign of bump depends on the direction of the scale
2063
+ if (z1 < z2) {
2064
+ z1 += bump * undo;
2065
+ z2 -= bump * undo;
2066
+ // if the scale gets inverted because of bump, set it to avg between z1 and z2
2067
+ if (z1 > z2) z1 = z2 = (z1 + z2) / 2;
2068
+ } else if (z1 > z2) {
2069
+ z1 -= bump * undo;
2070
+ z2 += bump * undo;
2071
+ // if the scale gets inverted because of bump, set it to avg between z1 and z2
2072
+ if (z1 < z2) z1 = z2 = (z1 + z2) / 2;
2073
+ } else ;
2074
+ return [z1, z2];
2075
+ }
2076
+ VizabiSharedComponents.LegacyUtils.warn("rangeBump error: input is not an array or empty");
2077
+ }
2078
+
2079
+ _updateSTitle(titleS, titleC) {
2080
+ const { sTitleEl } = this.DOM;
2081
+ const {
2082
+ size,
2083
+ color
2084
+ } = this.MDL;
2085
+ const compl = this.axisTitleComplimentStrings;
2086
+ // vertical text about size and color
2087
+ if (this.profileConstants.hideSTitle
2088
+ && this.root.ui.dialogs.dialogs.sidebar.indexOf("colors") > -1
2089
+ && this.root.ui.dialogs.dialogs.sidebar.indexOf("size") > -1) {
2090
+ sTitleEl.classed("vzb-invisible", true);
2091
+ return;
2092
+ }
2093
+ if (sTitleEl.classed("vzb-invisible")) {
2094
+ sTitleEl.classed("vzb-invisible", false);
2095
+ }
2096
+ const sTitleContentON = !size.data.constant;
2097
+ const cTitleContentON = !color.data.constant;
2098
+ const sTitleText = sTitleEl.select("text")
2099
+ // reset font size to remove jumpy measurement
2100
+ .style("font-size", null)
2101
+ .text(
2102
+ (sTitleContentON ? this.localise("buttons/size") + ": " + (titleS ? titleS : this.strings.title.S) : "") +
2103
+ (compl.S ? " · " + compl.S : "") +
2104
+ (sTitleContentON && cTitleContentON ? ", " : "") +
2105
+ (cTitleContentON ? this.localise("buttons/colors") + ": " + (titleC ? titleC : this.strings.title.C) : "") +
2106
+ (compl.C ? " · " + compl.C : "")
2107
+ );
2108
+ const sTitleWidth = sTitleText.node().getBBox().width;
2109
+ const remainigHeight = this.height - 30;
2110
+ const font = parseInt(sTitleText.style("font-size")) * remainigHeight / sTitleWidth;
2111
+ sTitleText.style("font-size", sTitleWidth > remainigHeight ? font + "px" : null);
2112
+ }
2113
+
2114
+ processFrameData() {
2115
+ return this.__dataProcessed = this.model.dataArray;
2116
+ }
2117
+
2118
+ _getTransition(duration) {
2119
+ return duration ? d3$1.transition()
2120
+ .duration(duration)
2121
+ .ease(d3$1.easeLinear) : d3$1.transition();
2122
+ }
2123
+
2124
+ _bubblesInteract() {
2125
+ const _this = this;
2126
+
2127
+ return {
2128
+ mouseover(event, d) {
2129
+ _this.hoverBubble = true;
2130
+ _this.MDL.highlighted.data.filter.set(d);
2131
+ _this._labels.showCloseCross(d, true);
2132
+ },
2133
+
2134
+ mouseout(event, d) {
2135
+ _this.hoverBubble = false;
2136
+ _this.MDL.highlighted.data.filter.delete(d);
2137
+ //_this._setTooltip();
2138
+ _this._labels.showCloseCross(d, false);
2139
+ },
2140
+
2141
+ click(event, d) {
2142
+ if (_this.draggingNow) return;
2143
+ // // const isSelected = d.isSelected;
2144
+ if (!isTrailBubble(d)) _this.MDL.selected.data.filter.toggle(d);
2145
+ //_this.MDL.selected.data.filter.toggle(d);
2146
+ // // //return to highlighted state
2147
+ // // if (!utils.isTouchDevice()) {
2148
+ // // if (isSelected) _this.model.marker.highlightMarker(d);
2149
+ // // _this.highlightDataPoints();
2150
+ }
2151
+ };
2152
+ }
2153
+
2154
+
2155
+ _updateMarkerSizeLimits() {
2156
+ this.services.layout.size;
2157
+ this.MDL.size.scale.domain;
2158
+
2159
+ const {
2160
+ minRadiusPx,
2161
+ maxRadiusPx
2162
+ } = this.profileConstants;
2163
+
2164
+ //transfer min max radius to size dialog via root ui observable (probably a cleaner way is possible)
2165
+ this.root.ui.minMaxRadius = {min: minRadiusPx, max: maxRadiusPx};
2166
+
2167
+ const extent = this.MDL.size.scale.extent || [0, 1];
2168
+
2169
+ let minArea = VizabiSharedComponents.LegacyUtils.radiusToArea(Math.max(maxRadiusPx * extent[0], minRadiusPx));
2170
+ let maxArea = VizabiSharedComponents.LegacyUtils.radiusToArea(Math.max(maxRadiusPx * extent[1], minRadiusPx));
2171
+
2172
+ let range = minArea === maxArea? [minArea, maxArea] :
2173
+ d3$1.range(minArea, maxArea, (maxArea - minArea)/(this.sScale.domain().length - 1)).concat(maxArea);
2174
+
2175
+ this.sScale.range(range);
2176
+ }
2177
+
2178
+ _setTooltip(tooltipText, x, y, s, c, d) {
2179
+ if (tooltipText) {
2180
+ const labelValues = {};
2181
+ if (d) {
2182
+ labelValues.valueY = d[this._alias("y")];
2183
+ labelValues.valueX = d[this._alias("x")];
2184
+ labelValues.valueS = d.size;
2185
+ labelValues.valueC = d.color;
2186
+ labelValues.valueLST = d.size_label || null;
2187
+ labelValues.labelText = this.__labelWithoutFrame(d, this.localise);
2188
+ }
2189
+
2190
+ const tooltipCache = {};
2191
+ tooltipCache.labelX0 = this.xScale.invert(x);
2192
+ tooltipCache.labelY0 = this.yScale.invert(y);
2193
+ tooltipCache.scaledS0 = s;
2194
+ tooltipCache.scaledC0 = null;
2195
+
2196
+ this._labels.setTooltip(d, tooltipText, tooltipCache, labelValues);
2197
+ } else {
2198
+ this._labels.setTooltip();
2199
+ }
2200
+ }
2201
+
2202
+ _getLabelText(d) {
2203
+ return this.KEYS.map(key => d.label[key]).join(",");
2204
+ //// + (this.model.ui.chart.timeInTrails && time && (this.model.time.start - this.model.time.end !== 0) ? " " + time : "");
2205
+ }
2206
+
2207
+ _updateOpacity(selection) {
2208
+ //this.MDL.frame.value; //listen
2209
+
2210
+ const highlightedFilter = this.MDL.highlighted.data.filter;
2211
+ const selectedFilter = this.MDL.selected.data.filter;
2212
+
2213
+ this.__highlightedMarkers = new Map(highlightedFilter.markers);
2214
+ this.__selectedMarkers = new Map(selectedFilter.markers);
2215
+ this.__someSelected = this.__selectedMarkers.size != 0;
2216
+ this.__someHighlighted = this.__highlightedMarkers.size != 0;
2217
+
2218
+ const _selection = selection || this.bubbles;
2219
+ if (_selection) _selection.style("opacity", d => this._getBubbleOpacity(d, this.ui));
2220
+ }
2221
+
2222
+ _getBubbleOpacity(d) {
2223
+ const ui = this.ui;
2224
+
2225
+ if (this.__highlightedMarkers.has(d[Symbol.for("key")])) return ui.opacityHighlight;
2226
+ if (isTrailBubble(d)) return ui.opacityRegular;
2227
+ if (this.__selectedMarkers.has(d[Symbol.for("key")])) return ui.opacitySelect;
2228
+
2229
+ if (this.__someSelected) return ui.opacitySelectDim;
2230
+ if (this.__someHighlighted) return ui.opacityHighlightDim;
2231
+
2232
+ return ui.opacityRegular;
2233
+ }
2234
+
2235
+ _setBubbleCrown(x, y, r, glow, skipInnerFill) {
2236
+ const bubbleCrown = this.DOM.bubbleCrown;
2237
+ if (x != null) {
2238
+ bubbleCrown.classed("vzb-hidden", false);
2239
+ bubbleCrown.select(".vzb-crown")
2240
+ .attr("cx", x)
2241
+ .attr("cy", y)
2242
+ .attr("r", r)
2243
+ .attr("fill", skipInnerFill ? "none" : glow);
2244
+ bubbleCrown.selectAll(".vzb-crown-glow")
2245
+ .attr("cx", x)
2246
+ .attr("cy", y)
2247
+ .attr("r", r + 10)
2248
+ .attr("stroke", glow);
2249
+
2250
+ } else {
2251
+ bubbleCrown.classed("vzb-hidden", true);
2252
+ }
2253
+
2254
+ }
2255
+
2256
+ /*
2257
+ * Shows and hides axis projections
2258
+ */
2259
+ _axisProjections(d) {
2260
+ const {
2261
+ projectionX,
2262
+ projectionY,
2263
+ xAxisEl,
2264
+ yAxisEl
2265
+ } = this.DOM;
2266
+
2267
+ if (d != null) {
2268
+
2269
+ const valueY = d[this._alias("y")];
2270
+ const valueX = d[this._alias("x")];
2271
+ const radius = d.r;
2272
+
2273
+ //if (!valueY && valueY !== 0 || !valueX && valueX !== 0 || !valueS && valueS !== 0) return;
2274
+
2275
+ if (this.ui.whenHovering.showProjectionLineX
2276
+ && this.xScale(valueX) > 0 && this.xScale(valueX) < this.width
2277
+ && (this.yScale(valueY) + radius) < this.height) {
2278
+ projectionX
2279
+ .style("opacity", 1)
2280
+ .attr("y2", this.yScale(valueY) + radius)
2281
+ .attr("x1", this.xScale(valueX))
2282
+ .attr("x2", this.xScale(valueX));
2283
+ }
2284
+
2285
+ if (this.ui.whenHovering.showProjectionLineY
2286
+ && this.yScale(valueY) > 0 && this.yScale(valueY) < this.height
2287
+ && (this.xScale(valueX) - radius) > 0) {
2288
+ projectionY
2289
+ .style("opacity", 1)
2290
+ .attr("y1", this.yScale(valueY))
2291
+ .attr("y2", this.yScale(valueY))
2292
+ .attr("x1", this.xScale(valueX) - radius);
2293
+ }
2294
+
2295
+ if (this.ui.whenHovering.higlightValueX) xAxisEl.call(
2296
+ this.xAxis.highlightValue(valueX)
2297
+ );
2298
+
2299
+ if (this.ui.whenHovering.higlightValueY) yAxisEl.call(
2300
+ this.yAxis.highlightValue(valueY)
2301
+ );
2302
+
2303
+
2304
+ } else {
2305
+
2306
+ projectionX.style("opacity", 0);
2307
+ projectionY.style("opacity", 0);
2308
+ xAxisEl.call(this.xAxis.highlightValue("none"));
2309
+ yAxisEl.call(this.yAxis.highlightValue("none"));
2310
+
2311
+ }
2312
+
2313
+ }
2314
+
2315
+ /*
2316
+ * Highlights all hovered bubbles
2317
+ */
2318
+ _highlightDataPoints() {
2319
+ const _this = this;
2320
+
2321
+ const highlightedFilter = this.MDL.highlighted.data.filter;
2322
+ const selectedFilter = this.MDL.selected.data.filter;
2323
+ this.someHighlighted = highlightedFilter.any();
2324
+
2325
+ //this.updateBubbleOpacity();
2326
+ const trailShow = this.MDL.trail.show;
2327
+ const trailStarts = this.MDL.trail.starts;
2328
+ const trailGroupDim = this.MDL.trail.groupDim;
2329
+
2330
+ if (highlightedFilter.markers.size === 1) {
2331
+ const highlightedKey = highlightedFilter.markers.keys().next().value;
2332
+ const d = Object.assign(this.model.dataMap.getByStr(highlightedKey));
2333
+ const selectedKey = d[Symbol.for("trailHeadKey")] || d[Symbol.for("key")];
2334
+
2335
+ const x = _this.xScale(d[_this._alias("x")]);
2336
+ const y = _this.yScale(d[_this._alias("y")]);
2337
+ const s = d.r;
2338
+ const c = _this.__getColor(selectedKey, d.color);
2339
+ let entityOutOfView = false;
2340
+
2341
+ ////const titles = _this._formatSTitleValues(values.size[utils.getKey(d, dataKeys.size)], values.color[utils.getKey(d, dataKeys.color)]);
2342
+ ////_this._updateSTitle(titles[0], titles[1]);
2343
+ if (x + s < 0 || x - s > this.width || y + s < 0 || y - s > this.height) {
2344
+ entityOutOfView = true;
2345
+ }
2346
+
2347
+ //show tooltip
2348
+ // const trailShow = this.MDL.trail.show;
2349
+ // const trailStarts = this.MDL.trail.starts;
2350
+ // const trailGroupDim = this.MDL.trail.groupDim;
2351
+ const isSelected = selectedFilter.has(selectedKey);
2352
+ const isTailTrail = !(trailStarts[selectedKey] - d[trailGroupDim]);
2353
+ const isTrail = isTrailBubble(d);
2354
+
2355
+ let text = "";
2356
+
2357
+ text = isSelected ?
2358
+ !trailShow || isTailTrail || (!isTrail && !this.hoverBubble) ? "": this.localise(d[trailGroupDim])
2359
+ :
2360
+ this.__labelWithoutFrame(d);
2361
+
2362
+ _this._labels.highlight(null, false);
2363
+ _this._labels.highlight({ [Symbol.for("key")]: selectedKey }, true);
2364
+ if (isSelected) {
2365
+ const skipCrownInnerFill = !isTrail;
2366
+ //!d.trailStartTime || d.trailStartTime == _this.model.time.formatDate(_this.time);
2367
+ _this._setBubbleCrown(x, y, s, c, skipCrownInnerFill);
2368
+ }
2369
+
2370
+ if (!entityOutOfView) {
2371
+ _this._axisProjections(d);
2372
+ }
2373
+
2374
+ //set tooltip and show axis projections
2375
+ if (text && !entityOutOfView) {
2376
+ _this._setTooltip(text, x, y, s + 3, c, d);
2377
+ }
2378
+
2379
+ // // const selectedData = utils.find(_this.model.marker.select, f => utils.getKey(f, KEYS) == d[KEY]);
2380
+ // // if (selectedData) {
2381
+ // // const clonedSelectedData = utils.clone(selectedData);
2382
+ // // //change opacity to OPACITY_HIGHLT = 1.0;
2383
+ // // clonedSelectedData.opacity = 1.0;
2384
+ // // _this._trails.run(["opacityHandler"], clonedSelectedData);
2385
+ // // }
2386
+ } else {
2387
+ this._axisProjections();
2388
+ ////this._trails.run(["opacityHandler"]);
2389
+ //hide tooltip
2390
+ //this._updateSTitle();
2391
+ this._setTooltip();
2392
+ this._setBubbleCrown();
2393
+ this._labels.highlight(null, false);
2394
+ }
2395
+
2396
+ }
2397
+
2398
+ _blinkSuperHighlighted() {
2399
+ if (!this.MDL.superHighlighted) return;
2400
+
2401
+ const superHighlightFilter = this.MDL.superHighlighted.data.filter;
2402
+
2403
+ this.bubbles
2404
+ .classed("vzb-super-highlighted", d => superHighlightFilter.has(d));
2405
+ }
2406
+
2407
+ _selectDataPoints() {
2408
+ const _this = this;
2409
+ const selectedFilter = this.MDL.selected.data.filter;
2410
+
2411
+ if (VizabiSharedComponents.LegacyUtils.isTouchDevice()) {
2412
+ _this.MDL.highlighted.data.filter.clear();
2413
+ _this._labels.showCloseCross(null, false);
2414
+ } else {
2415
+ //hide tooltip
2416
+ _this._setTooltip();
2417
+ ////_this._setBubbleCrown();
2418
+ }
2419
+
2420
+ // utils.forEach(_this.bubbles.data(), d => {
2421
+ // d.isSelected = _this.model.marker.isSelected(d);
2422
+ // });
2423
+
2424
+ _this.someSelected = selectedFilter.any();
2425
+ _this.nonSelectedOpacityZero = false;
2426
+
2427
+ }
2428
+
2429
+ _setupCursorMode() {
2430
+ const svg = this.DOM.chartSvgAll;
2431
+ if (this.ui.cursorMode === "plus") {
2432
+ svg.classed("vzb-zoomin", true);
2433
+ svg.classed("vzb-zoomout", false);
2434
+ svg.classed("vzb-panhand", false);
2435
+ } else if (this.ui.cursorMode === "minus") {
2436
+ svg.classed("vzb-zoomin", false);
2437
+ svg.classed("vzb-zoomout", true);
2438
+ svg.classed("vzb-panhand", false);
2439
+ } else if (this.ui.cursorMode === "hand") {
2440
+ svg.classed("vzb-zoomin", false);
2441
+ svg.classed("vzb-zoomout", false);
2442
+ svg.classed("vzb-panhand", true);
2443
+ } else {
2444
+ svg.classed("vzb-zoomin", false);
2445
+ svg.classed("vzb-zoomout", false);
2446
+ svg.classed("vzb-panhand", false);
2447
+ }
2448
+ }
2449
+
2450
+ updateDecorations(){
2451
+ this.services.layout.size;
2452
+ this.MDL.x.scale.zoomed;
2453
+ this.MDL.y.scale.zoomed;
2454
+ this.decorations.update.bind(this)(this._getDuration());
2455
+ }
2456
+
2457
+ _updateLabel(d, x, y, duration, showhide, hidden) {
2458
+ const selectedMarkers = this.MDL.selected.data.filter.markers;
2459
+ const key = d[Symbol.for("key")];
2460
+ // only for selected markers
2461
+ if (selectedMarkers.has(key)) {
2462
+ const trail = this.MDL.trail;
2463
+
2464
+ const cache = {};
2465
+
2466
+ let labelText = "";
2467
+
2468
+ //if (showhide && hidden && trail.show && trailStartTime && (trailStartTime < _this.time)) showhide = false;
2469
+ if (hidden && !trail.show) showhide = true;
2470
+
2471
+ if (trail.show && key in trail.starts) {
2472
+ const trailStart = trail.starts[key];
2473
+ //console.log("trailstart", trailStart)
2474
+ // if this bubble is trail start bubble
2475
+ if (trailStart >= this.MDL.frame.value || showhide) {
2476
+ const trailData = this.model.getDataMapByFrameValue(trailStart).getByStr(key);
2477
+
2478
+ cache.labelText = labelText = this.__labelWithFrame(trailData);
2479
+ cache.labelX0 = trailData[this._alias("x")];
2480
+ cache.labelY0 = trailData[this._alias("y")];
2481
+ cache.scaledC0 = trailData.color != null ? this.cScale(trailData.color) : COLOR_WHITEISH,
2482
+ cache.scaledS0 = (trailData.size || trailData.size === 0) ? VizabiSharedComponents.LegacyUtils.areaToRadius(this.sScale(trailData.size)) : null;
2483
+ cache.valueS0 = trailData.size;
2484
+ trailData.hidden = hidden;
2485
+ this._labels.updateLabel(trailData, cache, cache.labelX0, cache.labelY0, trailData.size, trailData.color, labelText, trailData.size_label, duration, showhide);
2486
+ }
2487
+ } else {
2488
+ cache.labelText = labelText = this.__labelWithoutFrame(d);
2489
+ cache.labelX0 = x;
2490
+ cache.labelY0 = y;
2491
+ cache.scaledC0 = d.color != null ? this.cScale(d.color) : COLOR_WHITEISH,
2492
+ cache.scaledS0 = (d.size || d.size === 0) ? VizabiSharedComponents.LegacyUtils.areaToRadius(this.sScale(d.size)) : null;
2493
+ cache.valueS0 = d.size;
2494
+ d.hidden = hidden;
2495
+ this._labels.updateLabel(d, cache, x, y, d.size, d.color, labelText, d.size_label, duration, showhide);
2496
+ }
2497
+ }
2498
+ }
2499
+
2500
+ _updateLabels(duration) {
2501
+ //console.log("updateLabels");
2502
+
2503
+ const selectedFilter = this.MDL.selected.data.filter;
2504
+ const trail = this.MDL.trail;
2505
+
2506
+ for (const key of selectedFilter.markers.keys()) {
2507
+ if (!(key in trail.starts))
2508
+ continue;
2509
+
2510
+ if (!this._labels.cached[key]) this._labels.cached[key] = {};
2511
+ const cache = this._labels.cached[key];
2512
+
2513
+ const datamap = (trail.show ? this.model.getDataMapByFrameValue(trail.starts[key]) : this.model.dataMap);
2514
+ if (!datamap.hasByStr(key))
2515
+ continue;
2516
+
2517
+ const d = datamap.getByStr(key);
2518
+
2519
+ cache.labelText = this[(trail.show && this.ui.timeInTrails ? "__labelWithFrame" : "__labelWithoutFrame")](d);
2520
+ cache.labelX0 = d[this._alias("x")];
2521
+ cache.labelY0 = d[this._alias("y")];
2522
+ cache.scaledC0 = d.color != null ? this.cScale(d.color) : COLOR_WHITEISH,
2523
+ cache.scaledS0 = (d.size || d.size === 0) ? VizabiSharedComponents.LegacyUtils.areaToRadius(this.sScale(d.size)) : null;
2524
+ cache.valueS0 = d.size;
2525
+ cache.initTextBBox = null;
2526
+ cache.initFontSize = null;
2527
+ this._labels.updateLabel({ [Symbol.for("key")]: key }, null, null, null, null, null, null, d.size_label, duration);
2528
+ }
2529
+ }
2530
+
2531
+ __labelWithoutFrame(d) {
2532
+ if (typeof d.label == "object")
2533
+ return Object.entries(d.label)
2534
+ .filter(entry => entry[0] != this.MDL.frame.data.concept)
2535
+ .map(entry => VizabiSharedComponents.LegacyUtils.isNumber(entry[1]) ? (entry[0] + ": " + entry[1]) : entry[1])
2536
+ .join(", ");
2537
+ if (d.label != null) return "" + d.label;
2538
+ return d[Symbol.for("key")];
2539
+ }
2540
+
2541
+ __labelWithFrame(d) {
2542
+ const frameConcept = this.MDL.frame.data.concept;
2543
+ return this.__labelWithoutFrame(d) + " " + this.localise(d && d.label && d.label[frameConcept] || d && d.frame || this.MDL.frame.value);
2544
+ }
2545
+
2546
+ _alias(enc) {
2547
+ return this.state.alias?.[enc] || enc;
2548
+ }
2549
+ }
2550
+
2551
+ _VizabiBubbleChart.DEFAULT_UI = {
2552
+ show_ticks: true,
2553
+ showForecast: false,
2554
+ showForecastOverlay: true,
2555
+ pauseBeforeForecast: true,
2556
+ opacityHighlight: 1.0,
2557
+ opacitySelect: 1.0,
2558
+ opacityHighlightDim: 0.1,
2559
+ opacitySelectDim: 0.3,
2560
+ opacityRegular: 0.5,
2561
+ timeInBackground: true,
2562
+ timeInTrails: true,
2563
+ lockNonSelected: 0,
2564
+ numberFormatSIPrefix: true,
2565
+ panWithArrow: false,
2566
+ adaptMinMaxZoom: false,
2567
+ cursorMode: "arrow",
2568
+ zoomOnScrolling: true,
2569
+ decorations: {
2570
+ enabled: true,
2571
+ xAxisGroups: null //left to be set by external page
2572
+ },
2573
+ superhighlightOnMinimapHover: true,
2574
+ whenHovering: {
2575
+ showProjectionLineX: true,
2576
+ showProjectionLineY: true,
2577
+ higlightValueX: true,
2578
+ higlightValueY: true
2579
+ },
2580
+ labels: {
2581
+ enabled: true,
2582
+ dragging: true,
2583
+ removeLabelBox: false
2584
+ },
2585
+ margin: {
2586
+ left: 0,
2587
+ top: 0
2588
+ }
2589
+ };
2590
+
2591
+ //export default BubbleChart;
2592
+ const VizabiBubbleChart = mobx.decorate(_VizabiBubbleChart, {
2593
+ "MDL": mobx.computed,
2594
+ "axisTitleComplimentStrings": mobx.observable,
2595
+ "cScale": mobx.computed
2596
+ });
2597
+
2598
+ VizabiSharedComponents.Chart.add("bubblechart", VizabiBubbleChart);
2599
+
2600
+ class BubbleChart extends VizabiSharedComponents.BaseComponent {
2601
+
2602
+ constructor(config){
2603
+
2604
+ const markerName = config.options?.markerNames?.bubble || "bubble";
2605
+ const fullMarker = config.model.markers[markerName];
2606
+ config.Vizabi.utils.applyDefaults(fullMarker.config, BubbleChart.DEFAULT_CORE(markerName));
2607
+
2608
+ const frameType = config.Vizabi.stores.encodings.modelTypes.frame;
2609
+ const { marker, splashMarker } = frameType.splashMarker(fullMarker);
2610
+
2611
+ config.model.markers[markerName] = marker;
2612
+
2613
+ config.name = "bubblechart";
2614
+
2615
+ config.subcomponents = [{
2616
+ type: VizabiSharedComponents.Repeater,
2617
+ placeholder: ".vzb-repeater",
2618
+ model: marker,
2619
+ options: {
2620
+ repeatedComponent: VizabiBubbleChart,
2621
+ repeatedComponentCssClass: "vzb-bubblechart"
2622
+ },
2623
+ name: "chart",
2624
+ },{
2625
+ type: VizabiSharedComponents.TimeSlider,
2626
+ placeholder: ".vzb-timeslider",
2627
+ model: marker,
2628
+ name: "time-slider"
2629
+ },{
2630
+ type: VizabiSharedComponents.SteppedSlider,
2631
+ placeholder: ".vzb-speedslider",
2632
+ model: marker,
2633
+ name: "speed-slider"
2634
+ },{
2635
+ type: VizabiSharedComponents.TreeMenu,
2636
+ placeholder: ".vzb-treemenu",
2637
+ model: marker,
2638
+ name: "tree-menu"
2639
+ },{
2640
+ type: VizabiSharedComponents.DataWarning,
2641
+ placeholder: ".vzb-datawarning",
2642
+ options: {button: ".vzb-datawarning-button"},
2643
+ model: marker,
2644
+ name: "data-warning"
2645
+ },{
2646
+ type: VizabiSharedComponents.DataNotes,
2647
+ placeholder: ".vzb-datanotes",
2648
+ model: marker
2649
+ },{
2650
+ type: VizabiSharedComponents.Dialogs,
2651
+ placeholder: ".vzb-dialogs",
2652
+ model: marker,
2653
+ name: "dialogs"
2654
+ },{
2655
+ type: VizabiSharedComponents.ButtonList,
2656
+ placeholder: ".vzb-buttonlist",
2657
+ model: marker,
2658
+ name: "buttons"
2659
+ },{
2660
+ type: VizabiSharedComponents.SpaceConfig,
2661
+ placeholder: ".vzb-spaceconfig",
2662
+ options: {button: ".vzb-spaceconfig-button"},
2663
+ model: marker,
2664
+ name: "space-config"
2665
+ },{
2666
+ type: VizabiSharedComponents.ErrorMessage,
2667
+ placeholder: ".vzb-errormessage",
2668
+ model: marker,
2669
+ name: "error-message"
2670
+ }];
2671
+
2672
+ config.template = `
2673
+ <div class="vzb-repeater"></div>
2674
+ <div class="vzb-animationcontrols">
2675
+ <div class="vzb-timeslider"></div>
2676
+ <div class="vzb-speedslider"></div>
2677
+ </div>
2678
+ <div class="vzb-sidebar">
2679
+ <div class="vzb-dialogs"></div>
2680
+ <div class="vzb-buttonlist"></div>
2681
+ </div>
2682
+ <div class="vzb-treemenu"></div>
2683
+ <div class="vzb-datawarning"></div>
2684
+ <div class="vzb-spaceconfig"></div>
2685
+ <div class="vzb-datanotes"></div>
2686
+ <div class="vzb-errormessage"></div>
2687
+ `;
2688
+
2689
+ config.services = {
2690
+ Vizabi: new VizabiSharedComponents.CapitalVizabiService({Vizabi: config.Vizabi}),
2691
+ locale: new VizabiSharedComponents.LocaleService(config.locale),
2692
+ layout: new VizabiSharedComponents.LayoutService(config.layout)
2693
+ };
2694
+
2695
+ super(config);
2696
+
2697
+ this.splashMarker = splashMarker;
2698
+ }
2699
+ }
2700
+
2701
+ BubbleChart.DEFAULT_UI = {
2702
+ chart: {
2703
+ }
2704
+ };
2705
+
2706
+ BubbleChart.mainComponent = VizabiBubbleChart;
2707
+
2708
+ BubbleChart.DEFAULT_CORE = (markerName) => ({
2709
+ requiredEncodings: ["x", "y", "size"],
2710
+ encoding: {
2711
+ "selected": {
2712
+ modelType: "selection",
2713
+ data: {
2714
+ filter: {
2715
+ ref: `markers.${markerName}.encoding.trail.data.filter`
2716
+ }
2717
+ }
2718
+ },
2719
+ "highlighted": { modelType: "selection" },
2720
+ "superhighlighted": { modelType: "selection" },
2721
+ "x": { },
2722
+ "y": { },
2723
+ "order": { modelType: "order",
2724
+ data: {
2725
+ ref: `markers.${markerName}.encoding.size.data.config`
2726
+ }
2727
+ },
2728
+ "size": {
2729
+ scale: {
2730
+ modelType: "size",
2731
+ range: [0, 50]
2732
+ }
2733
+ },
2734
+ "color": { scale: { modelType: "color" } },
2735
+ "label": { data: { modelType: "entityPropertyDataConfig" } },
2736
+ "frame": { modelType: "frame" },
2737
+ "trail": { modelType: "trail" },
2738
+ "size_label": {
2739
+ data: {
2740
+ constant: "_default"
2741
+ },
2742
+ scale: {
2743
+ modelType: "size",
2744
+ allowedTypes: ["linear", "log", "genericLog", "pow", "point", "ordinal"]
2745
+ }
2746
+ },
2747
+ "repeat": {
2748
+ modelType: "repeat",
2749
+ allowEnc: ["y", "x"]
2750
+ }
2751
+ }
2752
+ });
2753
+
2754
+ BubbleChart.versionInfo = { version: "0.0.1", build: 1680085520205, package: {"homepage":"https://github.com/vizabi/bubblechart#readme","name":"@vizabi/bubblechart","description":"Vizabi bubble chart"}, sharedComponents: VizabiSharedComponents.versionInfo};
2755
+
2756
+ return BubbleChart;
2757
+
2758
+ }));
2759
+ //# sourceMappingURL=bubblechart.js.map