UNPKG

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