@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.
- package/build/assets/cursors/grab.cur +0 -0
- package/build/assets/cursors/grabbing.cur +0 -0
- package/build/assets/cursors/zoom-in.cur +0 -0
- package/build/assets/cursors/zoom-out.cur +0 -0
- package/build/bubblechart.css +559 -0
- package/build/bubblechart.js +2759 -0
- package/build/bubblechart.js.map +1 -0
- package/build/stats.html +4034 -0
- package/package.json +58 -0
|
@@ -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
|