UNPKG

49.1 kBJavaScriptView Raw
1import { a as __extends } from './tslib.es6-863e3717.js';
2import { U as Utils, T as TooltipMeasureFormat } from './Utils-5f9f1f09.js';
3import { extent, select, event, line, easeExp, voronoi, mouse, axisBottom, axisLeft, scaleLinear } from 'd3';
4import { L as Legend } from './Legend-1be9e8e0.js';
5import { C as ChartComponentData } from './Grid-545284f2.js';
6import { C as ChartVisualizationComponent } from './ChartVisualizationComponent-84b467ae.js';
7import { T as Tooltip } from './Tooltip-4daae28d.js';
8import { G as GroupedBarChartData } from './GroupedBarChartData-4d21fe07.js';
9import { S as Slider } from './Slider-e28a6a90.js';
10
11var ScatterPlotData = /** @class */ (function (_super) {
12 __extends(ScatterPlotData, _super);
13 function ScatterPlotData() {
14 var _this = _super.call(this) || this;
15 _this.extents = {};
16 _this.extentsSet = false;
17 return _this;
18 }
19 /******** SETS EXTENT OF EACH DATA MEASURE -- MEASURES UPDATED WHEN RENDER CALLED OUTSIDE OF TEMPORAL ********/
20 ScatterPlotData.prototype.setExtents = function (measures, forceReset) {
21 var _this = this;
22 if (forceReset === void 0) { forceReset = false; }
23 if (!this.extentsSet || forceReset) {
24 // Reset extents
25 this.extents = {};
26 // Set axis extents
27 measures.forEach(function (measure) {
28 _this.extents[measure] = extent(_this.allValues, function (v) {
29 if (!v.measures)
30 return null;
31 return measure in v.measures ? v.measures[measure] : null;
32 });
33 });
34 this.extentsSet = true;
35 }
36 };
37 /******** UPDATE EXTENTS BASED ON VISIBLE DATA ********/
38 ScatterPlotData.prototype.updateExtents = function (measures) {
39 var _this = this;
40 var visibleData = [];
41 this.data.forEach(function (aggregate) {
42 var aggName = Object.keys(aggregate)[0];
43 var aggKey = aggregate.aggKey;
44 if (_this.displayState[aggKey].visible == true) {
45 Object.keys(aggregate[aggName]).forEach(function (splitBy) {
46 if (_this.displayState[aggKey].splitBys[splitBy].visible == true) {
47 visibleData.push(Object.values(aggregate[aggName][splitBy]));
48 }
49 });
50 }
51 });
52 visibleData = [].concat.apply([], visibleData);
53 measures.forEach(function (measure) {
54 _this.extents[measure] = extent(visibleData, function (v) {
55 return measure in v ? v[measure] : null;
56 });
57 });
58 };
59 /******** UPDATES CHART DATA, ALL TIMESTAMPS, AND VALUES AT THE CURRENT TIMESTAMP ********/
60 ScatterPlotData.prototype.mergeDataToDisplayStateAndTimeArrays = function (data, timestamp, aggregateExpressionOptions) {
61 if (aggregateExpressionOptions === void 0) { aggregateExpressionOptions = null; }
62 ChartComponentData.prototype.mergeDataToDisplayStateAndTimeArrays.call(this, data, aggregateExpressionOptions);
63 this.timestamp = (timestamp != undefined && this.allTimestampsArray.indexOf(timestamp) !== -1) ? timestamp : this.allTimestampsArray[0];
64 this.setValuesAtTimestamp();
65 this.setAllTimestampsArray();
66 };
67 /******** UPDATES DATA TO BE DRAWN -- IF SCATTER IS TEMPORAL, FLATTENS ALL TIMESTAMP DATA ********/
68 ScatterPlotData.prototype.updateTemporalDataArray = function (isTemporal) {
69 var _this = this;
70 this.temporalDataArray = [];
71 if (!isTemporal) {
72 this.allTimestampsArray.forEach(function (ts) {
73 _this.timestamp = ts;
74 _this.setValuesAtTimestamp();
75 _this.updateTemporal();
76 });
77 }
78 else {
79 this.updateTemporal();
80 }
81 };
82 /******** HELPER TO FETCH DATA AT THE CURRENT TIMESTAMP AND BUILD AN OBJECT FOR THAT TIMESTAMP ********/
83 ScatterPlotData.prototype.updateTemporal = function () {
84 var _this = this;
85 Object.keys(this.valuesAtTimestamp).forEach(function (aggKey) {
86 Object.keys(_this.valuesAtTimestamp[aggKey].splitBys).forEach(function (splitBy, splitByI) {
87 var measures = null, timestamp = null;
88 if (_this.getSplitByVisible(aggKey, splitBy) && _this.valuesAtTimestamp[aggKey].splitBys[splitBy].measurements != undefined) {
89 measures = _this.valuesAtTimestamp[aggKey].splitBys[splitBy].measurements;
90 timestamp = _this.valuesAtTimestamp[aggKey].splitBys[splitBy].timestamp;
91 }
92 _this.temporalDataArray.push({
93 aggregateKey: aggKey,
94 aggregateKeyI: _this.data.findIndex(function (datum) { return datum.aggKey === aggKey; }),
95 splitBy: splitBy,
96 measures: measures,
97 timestamp: timestamp,
98 splitByI: splitByI
99 });
100 });
101 });
102 };
103 /******** OVERRIDES GROUPEDBARCHARTDATA -- UPDATES VALUES AT TIMESTAMP WITH MEASURES & TIMESTAMP********/
104 ScatterPlotData.prototype.setValuesAtTimestamp = function () {
105 var _this = this;
106 var aggregateCounterMap = {};
107 this.valuesAtTimestamp = {};
108 this.data.forEach(function (aggregate, aggI) {
109 var aggName = Object.keys(aggregate)[0];
110 var aggKey;
111 if (aggregateCounterMap[aggName]) {
112 aggKey = Utils.createEntityKey(aggName, aggregateCounterMap[aggName]);
113 aggregateCounterMap[aggName] += 1;
114 }
115 else {
116 aggKey = Utils.createEntityKey(aggName, 0);
117 aggregateCounterMap[aggName] = 1;
118 }
119 _this.valuesAtTimestamp[aggKey] = {};
120 _this.valuesAtTimestamp[aggKey].splitBys = Object.keys(aggregate[aggName])
121 .reduce(function (aggSplitBys, splitBy, splitByI) {
122 aggSplitBys[splitBy] = {};
123 aggSplitBys[splitBy].measurements = aggregate[aggName][splitBy][_this.timestamp];
124 aggSplitBys[splitBy].timestamp = _this.timestamp;
125 return aggSplitBys;
126 }, {});
127 });
128 };
129 return ScatterPlotData;
130}(GroupedBarChartData));
131
132var ScatterPlot = /** @class */ (function (_super) {
133 __extends(ScatterPlot, _super);
134 function ScatterPlot(renderTarget) {
135 var _this = _super.call(this, renderTarget) || this;
136 _this.activeDot = null;
137 _this.focusedSite = null;
138 _this.lowOpacity = 0.15;
139 _this.standardOpacity = 0.6;
140 _this.focusOpacity = 0.8;
141 _this.standardStroke = 1;
142 _this.lowStroke = 0.3;
143 _this.chartComponentData = new ScatterPlotData();
144 /******** DRAW UPDATE FUNCTION ********/
145 _this.draw = function (isFromResize) {
146 if (isFromResize === void 0) { isFromResize = false; }
147 _this.activeDot = null;
148 _this.chartComponentData.updateTemporalDataArray(_this.chartOptions.isTemporal);
149 // Update extents to fit data if not temporal
150 _this.chartComponentData.updateExtents(_this.chartOptions.spMeasures);
151 _this.focus.attr("visibility", (_this.chartOptions.focusHidden) ? "hidden" : "visible");
152 // If only one data series visible, do not highlight on hover
153 var visibleSplitBys = 0;
154 Object.keys(_this.chartComponentData.displayState).forEach(function (aggKey) {
155 if (_this.chartComponentData.displayState[aggKey].visible)
156 Object.keys(_this.chartComponentData.displayState[aggKey].splitBys).forEach(function (splitBy) {
157 if (_this.chartComponentData.displayState[aggKey].splitBys[splitBy].visible)
158 visibleSplitBys++;
159 });
160 });
161 if (visibleSplitBys == 1)
162 _this.focusOpacity = _this.standardOpacity;
163 // Determine the number of timestamps present, add margin for slider
164 if (_this.chartComponentData.allTimestampsArray.length > 1 && _this.chartOptions.isTemporal) {
165 _this.chartMargins.bottom = 88;
166 }
167 else {
168 _this.chartMargins.bottom = 48;
169 }
170 _this.setWidthAndHeight(isFromResize);
171 _this.svgSelection
172 .attr("height", _this.height)
173 .style("width", _this.getSVGWidth() + "px");
174 _this.g
175 .attr("transform", "translate(" + _this.chartMargins.left + "," + _this.chartMargins.top + ")");
176 _this.voronoiGroup
177 .attr("width", _this.chartWidth)
178 .attr("height", _this.chartHeight);
179 _super.prototype.themify.call(_this, _this.targetElement, _this.chartOptions.theme);
180 // Draw control panel
181 if (!_this.chartOptions.hideChartControlPanel && _this.chartControlsPanel === null) {
182 _this.chartControlsPanel = Utils.createControlPanel(_this.renderTarget, _this.CONTROLSWIDTH, _this.chartMargins.top, _this.chartOptions);
183 }
184 else if (_this.chartOptions.hideChartControlPanel && _this.chartControlsPanel !== null) {
185 _this.removeControlPanel();
186 }
187 if (_this.chartControlsPanel !== null && _this.ellipsisItemsExist()) {
188 _this.drawEllipsisMenu();
189 _this.chartControlsPanel.style("top", Math.max((_this.chartMargins.top - 44), 0) + 'px');
190 }
191 else {
192 _this.removeEllipsisMenu();
193 }
194 // Resize focus line
195 _this.focus.select('.tsi-hLine').attr("x2", _this.chartWidth);
196 _this.focus.select('.tsi-vLine').attr("y2", _this.chartHeight);
197 _this.measures = _this.chartOptions.spMeasures;
198 _this.xMeasure = _this.measures[0];
199 _this.yMeasure = _this.measures[1];
200 _this.rMeasure = _this.measures[2] !== undefined ? _this.measures[2] : null;
201 var xExtentRange = _this.chartComponentData.extents[_this.xMeasure][1] - _this.chartComponentData.extents[_this.xMeasure][0];
202 var yExtentRange = _this.chartComponentData.extents[_this.yMeasure][1] - _this.chartComponentData.extents[_this.yMeasure][0];
203 // Pad extents
204 var xOffset = (20 / _this.chartWidth) * (xExtentRange == 0 ? 1 : xExtentRange);
205 var yOffset = (20 / _this.chartHeight) * (yExtentRange == 0 ? 1 : yExtentRange);
206 var rOffset = null;
207 if (_this.rMeasure) {
208 var rExtentRange = _this.chartComponentData.extents[_this.rMeasure][1] - _this.chartComponentData.extents[_this.rMeasure][0];
209 rOffset = (20 / _this.chartHeight) * (rExtentRange == 0 ? 1 : rExtentRange);
210 }
211 // Check measure validity
212 if (!_this.checkExtentValidity())
213 return;
214 // Init scales
215 _this.yScale = scaleLinear()
216 .range([_this.chartHeight, 0])
217 .domain([_this.chartComponentData.extents[_this.yMeasure][0] - yOffset, _this.chartComponentData.extents[_this.yMeasure][1] + yOffset]);
218 _this.xScale = scaleLinear()
219 .range([0, _this.chartWidth])
220 .domain([_this.chartComponentData.extents[_this.xMeasure][0] - xOffset, _this.chartComponentData.extents[_this.xMeasure][1] + xOffset]);
221 _this.rScale = scaleLinear()
222 .range(_this.chartOptions.scatterPlotRadius.slice(0, 2))
223 .domain(_this.rMeasure === null ? [0, 0] : [_this.chartComponentData.extents[_this.rMeasure][0] - rOffset, _this.chartComponentData.extents[_this.rMeasure][1] + rOffset]);
224 // Draw axis
225 _this.drawAxis();
226 // Draw axis labels
227 _this.drawAxisLabels();
228 // Draw connecting lines (if toggled on)
229 _this.drawConnectingLines();
230 // Draw data
231 var scatter = _this.pointWrapper.selectAll(".tsi-dot")
232 .data(_this.cleanData(_this.chartComponentData.temporalDataArray), function (d) {
233 if (_this.chartOptions.isTemporal) {
234 return d.aggregateKey + d.splitBy + d.splitByI;
235 }
236 else {
237 return d.aggregateKey + d.splitBy + d.timestamp;
238 }
239 });
240 scatter
241 .enter()
242 .append("circle")
243 .attr("class", "tsi-dot")
244 .attr("r", function (d) { return _this.rScale(d.measures[_this.rMeasure]); })
245 .attr("cx", function (d) { return _this.xScale(d.measures[_this.xMeasure]); })
246 .attr("cy", function (d) { return _this.yScale(d.measures[_this.yMeasure]); })
247 .merge(scatter)
248 .attr("id", function (d) { return _this.getClassHash(d.aggregateKey, d.splitBy, d.splitByI, d.timestamp); })
249 .transition()
250 .duration(_this.chartOptions.noAnimate ? 0 : _this.TRANSDURATION)
251 .ease(easeExp)
252 .attr("r", function (d) { return _this.rScale(d.measures[_this.rMeasure]); })
253 .attr("cx", function (d) { return _this.xScale(d.measures[_this.xMeasure]); })
254 .attr("cy", function (d) { return _this.yScale(d.measures[_this.yMeasure]); })
255 .attr("fill", function (d) { return Utils.colorSplitBy(_this.chartComponentData.displayState, d.splitByI, d.aggregateKey, _this.chartOptions.keepSplitByColor); })
256 .attr("stroke", function (d) { return Utils.colorSplitBy(_this.chartComponentData.displayState, d.splitByI, d.aggregateKey, _this.chartOptions.keepSplitByColor); })
257 .attr("stroke-opacity", _this.standardStroke)
258 .attr("fill-opacity", _this.standardOpacity)
259 .attr("stroke-width", "1px");
260 scatter.exit().remove();
261 // Draw voronoi
262 _this.drawVoronoi();
263 // Resize controls
264 _this.setControlsPanelWidth();
265 /******************** Temporal Slider ************************/
266 if (_this.chartComponentData.allTimestampsArray.length > 1 && _this.chartOptions.isTemporal) {
267 select(_this.renderTarget).select('.tsi-sliderWrapper').classed('tsi-hidden', false);
268 _this.slider.render(_this.chartComponentData.allTimestampsArray.map(function (ts) {
269 var action = function () {
270 _this.chartOptions.timestamp = ts;
271 _this.render(_this.chartComponentData.data, _this.chartOptions, _this.aggregateExpressionOptions, true);
272 };
273 return { label: Utils.timeFormat(_this.chartComponentData.usesSeconds, _this.chartComponentData.usesMillis, _this.chartOptions.offset, _this.chartOptions.is24HourTime, null, null, _this.chartOptions.dateLocale)(new Date(ts)), action: action };
274 }), _this.chartOptions, _this.getSliderWidth(), Utils.timeFormat(_this.chartComponentData.usesSeconds, _this.chartComponentData.usesMillis, _this.chartOptions.offset, _this.chartOptions.is24HourTime, null, null, _this.chartOptions.dateLocale)(new Date(_this.chartComponentData.timestamp)));
275 }
276 else {
277 if (_this.slider)
278 _this.slider.remove();
279 select(_this.renderTarget).select('.tsi-sliderWrapper').classed('tsi-hidden', true);
280 }
281 // Draw Legend
282 _this.legendObject.draw(_this.chartOptions.legend, _this.chartComponentData, _this.labelMouseOver.bind(_this), _this.svgSelection, _this.chartOptions, _this.labelMouseOut.bind(_this), _this.stickySeries);
283 _this.sliderWrapper
284 .style("width", _this.svgSelection.node().getBoundingClientRect().width + 10 + "px");
285 };
286 /******** UPDATE STICKY SPLITBY ********/
287 _this.stickySeries = function (aggregateKey, splitBy) {
288 if (splitBy === void 0) { splitBy = null; }
289 var filteredValues = _this.getVoronoiData(_this.chartComponentData.temporalDataArray);
290 if (filteredValues == null || filteredValues.length == 0)
291 return;
292 _this.chartComponentData.stickiedKey = {
293 aggregateKey: aggregateKey,
294 splitBy: (splitBy == null ? null : splitBy)
295 };
296 _this.legendObject.legendElement.selectAll('.tsi-splitByLabel').filter(function (filteredSplitBy) {
297 return (select(this.parentNode).datum() == aggregateKey) && (filteredSplitBy == splitBy);
298 }).classed("stickied", true);
299 _this.voronoiDiagram = _this.voronoi(_this.getVoronoiData(_this.chartComponentData.temporalDataArray));
300 };
301 _this.chartMargins = {
302 top: 40,
303 bottom: 48,
304 left: 70,
305 right: 60
306 };
307 return _this;
308 }
309 ScatterPlot.prototype.ScatterPlot = function () { };
310 ScatterPlot.prototype.render = function (data, options, aggregateExpressionOptions, fromSlider) {
311 var _this = this;
312 if (fromSlider === void 0) { fromSlider = false; }
313 _super.prototype.render.call(this, data, options, aggregateExpressionOptions);
314 // If measure options not set, or less than 2, return
315 if (this.chartOptions["spMeasures"] == null || (this.chartOptions["spMeasures"] != null && this.chartOptions["spMeasures"].length < 2)) {
316 var invalidMessage = "spMeasures not correctly specified or has length < 2: " + this.chartOptions["spMeasures"] +
317 "\n\nPlease add the following chartOption: {spMeasures: ['example_x_axis_measure', 'example_y_axis_measure', 'example_radius_measure']} " +
318 "where the measures correspond to the data key names.";
319 console.log(invalidMessage);
320 return;
321 }
322 this.chartMargins.top = (this.chartOptions.legend === 'compact') ? 84 : 40;
323 if (!this.chartOptions.hideChartControlPanel)
324 this.chartMargins.top += 20;
325 this.chartMargins.left = (this.chartOptions.spAxisLabels != null && this.chartOptions.spAxisLabels.length >= 2) ? 120 : 70;
326 this.chartComponentData.mergeDataToDisplayStateAndTimeArrays(this.data, this.chartOptions.timestamp, this.aggregateExpressionOptions);
327 this.chartComponentData.setExtents(this.chartOptions.spMeasures, !fromSlider);
328 // Check measure validity
329 if (!this.checkExtentValidity())
330 return;
331 this.controlsOffset = (this.chartOptions.legend == "shown" ? this.CONTROLSWIDTH : 0);
332 this.setWidthAndHeight();
333 /******** STATIC INITIALIZATION ********/
334 if (this.svgSelection == null) {
335 // Initialize extents
336 //this.chartComponentData.setExtents(this.chartOptions.spMeasures);
337 this.targetElement = select(this.renderTarget)
338 .classed("tsi-scatterPlot", true);
339 this.svgSelection = this.targetElement.append("svg")
340 .attr("class", "tsi-scatterPlotSVG tsi-chartSVG")
341 .attr('title', this.getString('Scatter plot'))
342 .attr("height", this.height);
343 this.g = this.svgSelection.append("g")
344 .classed("tsi-svgGroup", true);
345 this.lineWrapper = this.g.append("g")
346 .classed("tsi-lineWrapper", true);
347 this.pointWrapper = this.g.append("g")
348 .classed("tsi-pointWrapper", true);
349 // Create temporal slider div
350 this.sliderWrapper = select(this.renderTarget).append('div').classed('tsi-sliderWrapper', true);
351 this.tooltip = new Tooltip(select(this.renderTarget));
352 // Initialize voronoi
353 this.voronoiGroup = this.g.append("rect")
354 .attr("class", "tsi-voronoiWrap")
355 .attr("fill", "transparent");
356 // Initialize focus crosshair lines
357 this.focus = this.pointWrapper.append("g")
358 .attr("transform", "translate(-100,-100)")
359 .attr("class", "tsi-focus")
360 .style("display", "none");
361 this.focus.append("line")
362 .attr("class", "tsi-focusLine tsi-vLine")
363 .attr("x1", 0)
364 .attr("x2", 0)
365 .attr("y1", this.chartOptions.aggTopMargin)
366 .attr("y2", this.chartHeight);
367 this.focus.append("line")
368 .attr("class", "tsi-focusLine tsi-hLine")
369 .attr("x1", 0)
370 .attr("x2", this.chartWidth)
371 .attr("y1", 0)
372 .attr("y2", 0);
373 // Initialize focus axis data boxes
374 var hHoverG = this.focus.append("g")
375 .attr("class", 'hHoverG')
376 .style("pointer-events", "none")
377 .attr("transform", "translate(0," + (this.chartHeight + this.chartOptions.aggTopMargin) + ")");
378 hHoverG.append("rect")
379 .style("pointer-events", "none")
380 .attr("class", 'hHoverBox')
381 .attr("x", 0)
382 .attr("y", 4)
383 .attr("width", 0)
384 .attr("height", 0);
385 hHoverG.append("text")
386 .style("pointer-events", "none")
387 .attr("class", "hHoverText")
388 .attr("dy", ".71em")
389 .attr("transform", "translate(0,9)")
390 .text(function (d) { return d; });
391 var vHoverG = this.focus.append("g")
392 .attr("class", 'vHoverG')
393 .attr("transform", "translate(0," + (this.chartHeight + this.chartOptions.aggTopMargin) + ")");
394 vHoverG.append("rect")
395 .attr("class", 'vHoverBox')
396 .attr("x", -5)
397 .attr("y", 0)
398 .attr("width", 0)
399 .attr("height", 0);
400 vHoverG.append("text")
401 .attr("class", "vHoverText")
402 .attr("dy", ".32em")
403 .attr("x", -10)
404 .text(function (d) { return d; });
405 // Add Window Resize Listener
406 window.addEventListener("resize", function () {
407 if (!_this.chartOptions.suppressResizeListener) {
408 _this.draw();
409 }
410 });
411 // Temporal slider
412 this.slider = new Slider(select(this.renderTarget).select('.tsi-sliderWrapper').node());
413 // Legend
414 this.legendObject = new Legend(this.draw.bind(this), this.renderTarget, this.CONTROLSWIDTH);
415 }
416 // Draw scatter plot
417 this.draw();
418 this.gatedShowGrid();
419 select("html").on("click." + Utils.guid(), function () {
420 if (_this.ellipsisContainer && event.target != _this.ellipsisContainer.select(".tsi-ellipsisButton").node()) {
421 _this.ellipsisMenu.setMenuVisibility(false);
422 }
423 });
424 this.legendPostRenderProcess(this.chartOptions.legend, this.svgSelection, false);
425 };
426 ScatterPlot.prototype.getSliderWidth = function () {
427 return this.chartWidth + this.chartMargins.left + this.chartMargins.right - 16;
428 };
429 ScatterPlot.prototype.tooltipFormat = function (d, text, measureFormat, xyrMeasures) {
430 _super.prototype.tooltipFormat.call(this, d, text, measureFormat, xyrMeasures);
431 if (!this.chartOptions.isTemporal) {
432 var titleGroup = text.select('.tsi-tooltipTitleGroup');
433 if (d.timestamp) {
434 titleGroup.append('h4')
435 .attr('class', 'tsi-tooltipSubtitle tsi-tooltipTimeStamp')
436 .text(this.formatDate(d.timestamp, this.chartComponentData.getTemporalShiftMillis(d.aggregateKey)));
437 }
438 }
439 };
440 /******** DRAW CONNECTING LINES BETWEEN POINTS ********/
441 ScatterPlot.prototype.drawConnectingLines = function () {
442 var _this = this;
443 // Don't render connecting lines on temporal mode
444 if (this.chartOptions.isTemporal) {
445 this.lineWrapper.selectAll("*").remove();
446 return;
447 }
448 var dataSet = this.cleanData(this.chartComponentData.temporalDataArray);
449 var connectedSeriesMap = {};
450 // Find measure by which to connect series of points
451 var getPointConnectionMeasure = (function (point) {
452 var _a;
453 var pConMes = (_a = _this.aggregateExpressionOptions[point.aggregateKeyI]) === null || _a === void 0 ? void 0 : _a.pointConnectionMeasure;
454 return pConMes && pConMes in point.measures ? pConMes : null;
455 });
456 // Map data into groups of connected points, if connectedPoints enabled for agg
457 dataSet.forEach(function (point) {
458 if (point.aggregateKeyI !== null && point.aggregateKeyI < _this.aggregateExpressionOptions.length &&
459 _this.aggregateExpressionOptions[point.aggregateKeyI].connectPoints) {
460 var series = point.aggregateKey + "_" + point.splitBy;
461 if (series in connectedSeriesMap) {
462 connectedSeriesMap[series].data.push(point);
463 }
464 else {
465 connectedSeriesMap[series] = {
466 data: [point],
467 pointConnectionMeasure: getPointConnectionMeasure(point)
468 };
469 }
470 }
471 });
472 var _loop_1 = function (key) {
473 var sortMeasure = connectedSeriesMap[key].pointConnectionMeasure;
474 // If sort measure specified, sort by that measure
475 if (sortMeasure) {
476 connectedSeriesMap[key].data.sort(function (a, b) {
477 if (a.measures[sortMeasure] < b.measures[sortMeasure])
478 return -1;
479 if (a.measures[sortMeasure] > b.measures[sortMeasure])
480 return 1;
481 return 0;
482 });
483 }
484 };
485 // Sort connected series by pointConnectionMeasure
486 for (var _i = 0, _a = Object.keys(connectedSeriesMap); _i < _a.length; _i++) {
487 var key = _a[_i];
488 _loop_1(key);
489 }
490 var line$1 = line()
491 .x(function (d) { return _this.xScale(d.measures[_this.xMeasure]); })
492 .y(function (d) { return _this.yScale(d.measures[_this.yMeasure]); })
493 .curve(this.chartOptions.interpolationFunction); // apply smoothing to the line
494 // Group lines by aggregate
495 var connectedGroups = this.lineWrapper.selectAll(".tsi-lineSeries").data(Object.keys(connectedSeriesMap));
496 var self = this;
497 connectedGroups.enter()
498 .append("g")
499 .attr("class", 'tsi-lineSeries')
500 .merge(connectedGroups)
501 .each(function (seriesName) {
502 var series = select(this).selectAll(".tsi-line").data([connectedSeriesMap[seriesName].data], function (d) { return d[0].aggregateKeyI + d[0].splitBy; });
503 series.exit().remove();
504 series
505 .enter()
506 .append("path")
507 .attr("class", "tsi-line")
508 .merge(series)
509 .attr("fill", "none")
510 .transition()
511 .duration(self.chartOptions.noAnimate ? 0 : self.TRANSDURATION)
512 .ease(easeExp)
513 .attr("stroke", function (d) { return Utils.colorSplitBy(self.chartComponentData.displayState, d[0].splitByI, d[0].aggregateKey, self.chartOptions.keepSplitByColor); })
514 .attr("stroke-width", 2.5)
515 .attr("stroke-linejoin", "round")
516 .attr("stroke-linecap", "round")
517 .attr("d", line$1);
518 });
519 connectedGroups.exit().remove();
520 };
521 /******** CHECK VALIDITY OF EXTENTS ********/
522 ScatterPlot.prototype.checkExtentValidity = function () {
523 var _this = this;
524 if (this.chartComponentData.allValues == 0) {
525 return true;
526 }
527 var testExtent = {};
528 this.chartOptions.spMeasures.forEach(function (measure) {
529 testExtent[measure] = extent(_this.chartComponentData.allValues, function (v) {
530 if (!v.measures)
531 return null;
532 return measure in v.measures ? v.measures[measure] : null;
533 });
534 });
535 Object.keys(testExtent).forEach(function (extent) {
536 testExtent[extent].forEach(function (el) {
537 if (el == undefined) {
538 console.log("Undefined Measure: ", extent);
539 return false;
540 }
541 });
542 });
543 return true;
544 };
545 /******** CREATE VORONOI DIAGRAM FOR MOUSE EVENTS ********/
546 ScatterPlot.prototype.drawVoronoi = function () {
547 var _this = this;
548 var voronoiData = this.getVoronoiData(this.chartComponentData.temporalDataArray);
549 var self = this;
550 // Create random offset to solve colinear data issue
551 var getRandomInRange = function (min, max) {
552 return Math.random() * (max - min) + min;
553 };
554 var getOffset = function () { return (Math.random() < 0.5 ? -1 : 1) * getRandomInRange(0, .01); };
555 this.voronoi = voronoi()
556 .x(function (d) { return _this.xScale(d.measures[_this.xMeasure]) + getOffset(); })
557 .y(function (d) { return _this.yScale(d.measures[_this.yMeasure]) + getOffset(); })
558 .extent([[0, 0], [this.chartWidth, this.chartHeight]]);
559 this.voronoiDiagram = this.voronoi(voronoiData);
560 this.voronoiGroup
561 .on("mousemove", function () {
562 var mouseEvent = mouse(this);
563 self.voronoiMouseMove(mouseEvent);
564 })
565 .on("mouseover", function () {
566 var mouseEvent = mouse(this);
567 self.voronoiMouseMove(mouseEvent);
568 var site = self.voronoiDiagram.find(mouseEvent[0], mouseEvent[1]);
569 if (site != null)
570 self.labelMouseOver(site.data.aggregateKey, site.data.splitBy);
571 })
572 .on("mouseout", function () {
573 self.voronoiMouseOut();
574 })
575 .on("click", function () {
576 var mouseEvent = mouse(this);
577 self.voronoiClick(mouseEvent);
578 });
579 };
580 /******** STICKY/UNSTICKY DATA GROUPS ON VORONOI DIAGRAM CLICK ********/
581 ScatterPlot.prototype.voronoiClick = function (mouseEvent) {
582 var site = this.voronoiDiagram.find(mouseEvent[0], mouseEvent[1]);
583 if (site == null)
584 return;
585 // Unsticky all
586 this.legendObject.legendElement.selectAll('.tsi-splitByLabel').classed("stickied", false);
587 if (this.chartComponentData.stickiedKey != null) {
588 this.chartComponentData.stickiedKey = null;
589 // Recompute Voronoi
590 this.voronoiDiagram = this.voronoi(this.getVoronoiData(this.chartComponentData.temporalDataArray));
591 site = this.voronoiDiagram.find(mouseEvent[0], mouseEvent[1]);
592 this.voronoiMouseMove(mouseEvent);
593 this.chartOptions.onUnsticky(site.data.aggregateKey, site.data.splitBy);
594 return;
595 }
596 this.stickySeries(site.data.aggregateKey, site.data.splitBy);
597 this.chartOptions.onSticky(site.data.aggregateKey, site.data.splitBy);
598 };
599 /******** HIGHLIGHT DOT TARGETED BY CROSSHAIRS WITH BLACK / WHITE STROKE BORDER ********/
600 ScatterPlot.prototype.highlightDot = function (site) {
601 //If dot is active, unhighlight
602 this.unhighlightDot();
603 // Add highlight border to newly focused dot
604 var highlightColor = this.chartOptions.theme == "light" ? "black" : "white";
605 var idSelector = "#" + this.getClassHash(site.data.aggregateKey, site.data.splitBy, site.data.splitByI, site.data.timestamp);
606 this.activeDot = this.svgSelection.select(idSelector);
607 this.activeDot
608 .attr("stroke", highlightColor)
609 .attr("stroke-width", "2px")
610 // Raise active dot above crosshair
611 .raise().classed("active", true);
612 };
613 /******** GET UNIQUE STRING HASH ID FOR EACH DOT USING DATA ATTRIBUTES ********/
614 ScatterPlot.prototype.getClassHash = function (aggKey, splitBy, splitByI, timestamp) {
615 return String("dot" + Utils.hash(aggKey + splitBy + splitByI.toString() + timestamp));
616 };
617 /******** UNHIGHLIGHT ACTIVE DOT ********/
618 ScatterPlot.prototype.unhighlightDot = function () {
619 var _this = this;
620 if (this.activeDot) {
621 this.activeDot
622 .attr("stroke", function (d) { return Utils.colorSplitBy(_this.chartComponentData.displayState, d.splitByI, d.aggregateKey, _this.chartOptions.keepSplitByColor); })
623 .attr("stroke-width", "1px");
624 }
625 this.activeDot = null;
626 };
627 /******** EFFICIENTLY SWAP NEW FOCUSED GROUP WITH OLD FOCUSED GROUP ********/
628 ScatterPlot.prototype.labelMouseMove = function (aggKey, splitBy) {
629 if (aggKey !== this.focusedAggKey || splitBy !== this.focusedSplitBy) {
630 var selectedFilter = Utils.createValueFilter(aggKey, splitBy);
631 var oldFilter = Utils.createValueFilter(this.focusedAggKey, this.focusedSplitBy);
632 this.svgSelection.selectAll(".tsi-dot")
633 .filter(selectedFilter)
634 .attr("stroke-opacity", this.standardStroke)
635 .attr("fill-opacity", this.focusOpacity);
636 this.svgSelection.selectAll(".tsi-dot")
637 .filter(oldFilter)
638 .attr("stroke-opacity", this.lowStroke)
639 .attr("fill-opacity", this.lowOpacity);
640 var lineSelectedFilter_1 = function (d) {
641 return (d[0].aggregateKey === aggKey && d[0].splitBy === splitBy);
642 };
643 this.svgSelection.selectAll(".tsi-line")
644 .filter(function (d) { return lineSelectedFilter_1(d); })
645 .attr("stroke-opacity", this.standardStroke);
646 this.svgSelection.selectAll(".tsi-line")
647 .filter(function (d) { return !lineSelectedFilter_1(d); })
648 .attr("stroke-opacity", this.lowStroke);
649 this.focusedAggKey = aggKey;
650 this.focusedSplitBy = splitBy;
651 }
652 // Raise crosshair to top
653 this.focus.raise().classed("active", true);
654 // Raise highlighted dot above crosshairs
655 if (this.activeDot != null)
656 this.activeDot.raise().classed("active", true);
657 // Highlight legend group
658 (this.legendObject.legendElement.selectAll('.tsi-splitByLabel').filter(function (filteredSplitBy) {
659 return (select(this.parentNode).datum() == aggKey) && (filteredSplitBy == splitBy);
660 })).classed("inFocus", true);
661 };
662 /******** DRAW CROSSHAIRS, TOOLTIP, AND LEGEND FOCUS ********/
663 ScatterPlot.prototype.voronoiMouseMove = function (mouseEvent) {
664 var mouse_x = mouseEvent[0];
665 var mouse_y = mouseEvent[1];
666 var site = this.voronoiDiagram.find(mouse_x, mouse_y);
667 if (site == null)
668 return;
669 // Short circuit mouse move if focused site has not changed
670 if (this.focusedSite == null)
671 this.focusedSite = site;
672 else if (this.focusedSite == site)
673 return;
674 this.focusedSite = site;
675 this.drawTooltip(site.data, [site[0], site[1]]);
676 this.labelMouseMove(site.data.aggregateKey, site.data.splitBy);
677 this.highlightDot(site);
678 // Draw focus cross hair
679 this.focus.style("display", "block");
680 this.focus.attr("transform", "translate(" + site[0] + "," + site[1] + ")");
681 this.focus.select('.tsi-hLine').attr("transform", "translate(" + (-site[0]) + ",0)");
682 this.focus.select('.tsi-vLine').attr("transform", "translate(0," + (-site[1]) + ")");
683 // Draw horizontal hover box
684 this.focus.select('.hHoverG')
685 .attr("transform", "translate(0," + (this.chartHeight - site[1]) + ")")
686 .select("text")
687 .text((Utils.formatYAxisNumber(site.data.measures[this.xMeasure])));
688 var textElemDimensions = this.focus.select('.hHoverG').select("text")
689 .node().getBoundingClientRect();
690 this.focus.select(".hHoverG").select("rect")
691 .attr("x", -(textElemDimensions.width / 2) - 3)
692 .attr("width", textElemDimensions.width + 6)
693 .attr("height", textElemDimensions.height + 5);
694 // Draw vertical hover box
695 this.focus.select('.vHoverG')
696 .attr("transform", "translate(" + (-site[0]) + ",0)")
697 .select("text")
698 .text(Utils.formatYAxisNumber(site.data.measures[this.yMeasure]));
699 textElemDimensions = this.focus.select('.vHoverG').select("text")
700 .node().getBoundingClientRect();
701 this.focus.select(".vHoverG").select("rect")
702 .attr("x", -(textElemDimensions.width) - 13)
703 .attr("y", -(textElemDimensions.height / 2) - 3)
704 .attr("width", textElemDimensions.width + 6)
705 .attr("height", textElemDimensions.height + 4);
706 this.legendObject.triggerSplitByFocus(site.data.aggregateKey, site.data.splitBy);
707 };
708 /******** HIDE TOOLTIP AND CROSSHAIRS ********/
709 ScatterPlot.prototype.voronoiMouseOut = function () {
710 this.focusedSite = null;
711 this.focus.style("display", "none");
712 this.tooltip.hide();
713 this.labelMouseOut();
714 this.unhighlightDot();
715 };
716 /******** FILTER DATA BY VISIBLE AND STICKIED ********/
717 ScatterPlot.prototype.getVoronoiData = function (rawData) {
718 var _this = this;
719 var cleanData = this.cleanData(rawData);
720 var filteredValues = cleanData.filter(function (d) {
721 return (_this.chartComponentData.displayState[d.aggregateKey].visible &&
722 _this.chartComponentData.displayState[d.aggregateKey].splitBys[d.splitBy].visible);
723 });
724 if (this.chartComponentData.stickiedKey == null)
725 return filteredValues;
726 var stickiedValues = filteredValues.filter(function (d) {
727 return d.aggregateKey == _this.chartComponentData.stickiedKey.aggregateKey &&
728 ((_this.chartComponentData.stickiedKey.splitBy == null) ? true :
729 d.splitBy == _this.chartComponentData.stickiedKey.splitBy);
730 });
731 return stickiedValues;
732 };
733 /******** HIGHLIGHT FOCUSED GROUP ********/
734 ScatterPlot.prototype.labelMouseOver = function (aggKey, splitBy) {
735 if (splitBy === void 0) { splitBy = null; }
736 // Remove highlight on previous legend group
737 this.legendObject.legendElement.selectAll('.tsi-splitByLabel').classed("inFocus", false);
738 // Filter selected
739 var selectedFilter = function (d) {
740 var currAggKey = null, currSplitBy = null;
741 if (d.aggregateKey != null)
742 currAggKey = d.aggregateKey;
743 if (d.splitBy != null)
744 currSplitBy = d.splitBy;
745 if (splitBy == null)
746 return currAggKey == aggKey;
747 if (currAggKey == aggKey && currSplitBy == splitBy)
748 return false;
749 return true;
750 };
751 //Highlight active group
752 this.svgSelection.selectAll(".tsi-dot")
753 .filter(function (d) { return !selectedFilter(d); })
754 .attr("stroke-opacity", this.standardStroke)
755 .attr("fill-opacity", this.focusOpacity);
756 // Decrease opacity of unselected
757 this.svgSelection.selectAll(".tsi-dot")
758 .filter(selectedFilter)
759 .attr("stroke-opacity", this.lowStroke)
760 .attr("fill-opacity", this.lowOpacity);
761 // Decrease opacity of unselected line
762 this.svgSelection.selectAll(".tsi-line")
763 .filter(function (d) { return !(d[0].aggregateKey === aggKey && d[0].splitBy === splitBy); })
764 .attr("stroke-opacity", this.lowStroke);
765 };
766 /******** UNHIGHLIGHT FOCUSED GROUP ********/
767 ScatterPlot.prototype.labelMouseOut = function () {
768 var _this = this;
769 // Remove highlight on legend group
770 this.legendObject.legendElement.selectAll('.tsi-splitByLabel').classed("inFocus", false);
771 this.g.selectAll(".tsi-dot")
772 .attr("stroke-opacity", this.standardStroke)
773 .attr("fill-opacity", this.standardOpacity)
774 .attr("stroke", function (d) { return Utils.colorSplitBy(_this.chartComponentData.displayState, d.splitByI, d.aggregateKey, _this.chartOptions.keepSplitByColor); })
775 .attr("fill", function (d) { return Utils.colorSplitBy(_this.chartComponentData.displayState, d.splitByI, d.aggregateKey, _this.chartOptions.keepSplitByColor); })
776 .attr("stroke-width", "1px");
777 this.g.selectAll(".tsi-line")
778 .attr("stroke-opacity", this.standardStroke);
779 };
780 /******** FILTER DATA, ONLY KEEPING POINTS WITH ALL REQUIRED MEASURES ********/
781 ScatterPlot.prototype.cleanData = function (data) {
782 var _this = this;
783 // Exclude any data which does not contain the specified
784 // chart option measure
785 var filtered = data.filter(function (value) {
786 var valOk = true;
787 _this.chartOptions.spMeasures
788 .forEach(function (measure) {
789 if (value.measures == null)
790 valOk = false;
791 else if (!(measure in value.measures)) {
792 valOk = false;
793 }
794 });
795 return valOk;
796 });
797 return filtered;
798 };
799 /******** UPDATE CHART DIMENSIONS ********/
800 ScatterPlot.prototype.setWidthAndHeight = function (isFromResize) {
801 if (isFromResize === void 0) { isFromResize = false; }
802 this.height = Math.max(select(this.renderTarget).node().clientHeight, this.MINHEIGHT);
803 this.chartHeight = this.height - this.chartMargins.top - this.chartMargins.bottom;
804 this.width = this.getWidth();
805 if (!isFromResize) {
806 this.chartWidth = this.getChartWidth();
807 }
808 };
809 /******** SCALE AND DRAW AXIS ********/
810 ScatterPlot.prototype.drawAxis = function () {
811 // Draw dynamic x axis and label
812 this.xAxis = this.pointWrapper.selectAll(".xAxis").data([this.xScale]);
813 this.xAxis.enter()
814 .append("g")
815 .attr("class", "xAxis")
816 .merge(this.xAxis)
817 .attr("transform", "translate(0," + (this.chartHeight) + ")")
818 .call(axisBottom(this.xScale).ticks(Math.max(2, Math.floor(this.chartWidth / 150))));
819 this.xAxis.exit().remove();
820 // Draw dynamic y axis and label
821 this.yAxis = this.pointWrapper.selectAll(".yAxis").data([this.yScale]);
822 this.yAxis.enter()
823 .append("g")
824 .attr("class", "yAxis")
825 .merge(this.yAxis)
826 .call(axisLeft(this.yScale).ticks(Math.max(2, Math.floor(this.chartHeight / 90))));
827 this.yAxis.exit().remove();
828 };
829 /******** DRAW X AND Y AXIS LABELS ********/
830 ScatterPlot.prototype.drawAxisLabels = function () {
831 var self = this;
832 var xLabelData, yLabelData;
833 var truncateTextLength = function (textSelection, maxTextLengthPx) {
834 if (textSelection.node() && textSelection.node().getComputedTextLength) {
835 var textLength = textSelection.node().getComputedTextLength();
836 var text = textSelection.text();
837 while (textLength > maxTextLengthPx && text.length > 0) {
838 text = text.slice(0, -1);
839 textSelection.text(text + '...');
840 textLength = textSelection.node().getComputedTextLength();
841 }
842 }
843 };
844 // Associate axis label data
845 (this.chartOptions.spAxisLabels != null && this.chartOptions.spAxisLabels.length >= 1) ?
846 xLabelData = [this.chartOptions.spAxisLabels[0]] : xLabelData = [];
847 (this.chartOptions.spAxisLabels != null && this.chartOptions.spAxisLabels.length >= 2) ?
848 yLabelData = [this.chartOptions.spAxisLabels[1]] : yLabelData = [];
849 this.xAxisLabel = this.pointWrapper.selectAll('.tsi-xAxisLabel').data(xLabelData);
850 var xAxisLabel = this.xAxisLabel
851 .enter()
852 .append("text")
853 .attr("class", "tsi-xAxisLabel tsi-AxisLabel")
854 .merge(this.xAxisLabel)
855 .style("text-anchor", "middle")
856 .attr("transform", "translate(" + (this.chartWidth / 2) + " ," + (this.chartHeight + 42) + ")")
857 .text(null);
858 xAxisLabel.each(function (d) {
859 var label = select(this);
860 Utils.appendFormattedElementsFromString(label, d, { inSvg: true });
861 });
862 //text is either in tspans or just in text. Either truncate text directly or through tspan
863 if (xAxisLabel.selectAll("tspan").size() == 0)
864 truncateTextLength(xAxisLabel, this.chartWidth);
865 else {
866 xAxisLabel.selectAll("tspan").each(function () {
867 var tspanTextSelection = select(this);
868 truncateTextLength(tspanTextSelection, self.chartWidth / xAxisLabel.selectAll("tspan").size());
869 });
870 }
871 this.xAxisLabel.exit().remove();
872 this.yAxisLabel = this.pointWrapper.selectAll('.tsi-yAxisLabel').data(yLabelData);
873 var yAxisLabel = this.yAxisLabel
874 .enter()
875 .append("text")
876 .attr("class", "tsi-yAxisLabel tsi-AxisLabel")
877 .merge(this.yAxisLabel)
878 .style("text-anchor", "middle")
879 .attr("transform", "translate(" + (-70) + " ," + (this.chartHeight / 2) + ") rotate(-90)")
880 .text(null);
881 yAxisLabel.each(function (d) {
882 var label = select(this);
883 Utils.appendFormattedElementsFromString(label, d, { inSvg: true });
884 });
885 //text is either in tspans or just in text. Either truncate text directly or through tspan
886 if (yAxisLabel.selectAll("tspan").size() == 0)
887 truncateTextLength(yAxisLabel, this.chartHeight);
888 else {
889 yAxisLabel.selectAll("tspan").each(function () {
890 var tspanTextSelection = select(this);
891 truncateTextLength(tspanTextSelection, self.chartHeight / yAxisLabel.selectAll("tspan").size());
892 });
893 }
894 this.yAxisLabel.exit().remove();
895 };
896 /******** DRAW TOOLTIP IF ENABLED ********/
897 ScatterPlot.prototype.drawTooltip = function (d, mousePosition) {
898 var _this = this;
899 if (this.chartOptions.tooltip) {
900 var xPos = mousePosition[0];
901 var yPos = mousePosition[1];
902 var xyrMeasures_1 = [this.xMeasure, this.yMeasure];
903 if (this.rMeasure !== null) {
904 xyrMeasures_1.push(this.rMeasure);
905 }
906 this.tooltip.render(this.chartOptions.theme);
907 this.tooltip.draw(d, this.chartComponentData, xPos, yPos, this.chartMargins, function (text) {
908 d.aggregateName = _this.chartComponentData.displayState[d.aggregateKey].name;
909 _this.tooltipFormat(d, text, TooltipMeasureFormat.Scatter, xyrMeasures_1);
910 }, null, 20, 20, Utils.colorSplitBy(this.chartComponentData.displayState, d.splitByI, d.aggregateKey, this.chartOptions.keepSplitByColor));
911 }
912 };
913 /******** HELPERS TO FORMAT TIME DISPLAY ********/
914 ScatterPlot.prototype.labelFormatUsesSeconds = function () {
915 return !this.chartOptions.minutesForTimeLabels && this.chartComponentData.usesSeconds;
916 };
917 ScatterPlot.prototype.labelFormatUsesMillis = function () {
918 return !this.chartOptions.minutesForTimeLabels && this.chartComponentData.usesMillis;
919 };
920 return ScatterPlot;
921}(ChartVisualizationComponent));
922
923export { ScatterPlot as S };