UNPKG

tsiclient

Version:

--- [//]: <> (This content is similar to https://github.com/MicrosoftDocs/azure-docs/edit/main/includes/tsi-retirement.md)

925 lines (921 loc) 49.2 kB
import { a as __extends } from './tslib.es6-f952ba6f.js'; import { U as Utils, T as TooltipMeasureFormat } from './Utils-38a0872e.js'; import { extent, select, line, easeExp, pointer, axisBottom, axisLeft, scaleLinear } from 'd3'; import { voronoi } from 'd3-voronoi'; import { L as Legend } from './Legend-a9a3b412.js'; import { C as ChartComponentData } from './Grid-170eaa9c.js'; import { C as ChartVisualizationComponent } from './ChartVisualizationComponent-295c909a.js'; import { T as Tooltip } from './Tooltip-f293336f.js'; import { G as GroupedBarChartData } from './GroupedBarChartData-57ae441e.js'; import { S as Slider } from './Slider-e643fd4a.js'; var ScatterPlotData = /** @class */ (function (_super) { __extends(ScatterPlotData, _super); function ScatterPlotData() { var _this = _super.call(this) || this; _this.extents = {}; _this.extentsSet = false; return _this; } /******** SETS EXTENT OF EACH DATA MEASURE -- MEASURES UPDATED WHEN RENDER CALLED OUTSIDE OF TEMPORAL ********/ ScatterPlotData.prototype.setExtents = function (measures, forceReset) { var _this = this; if (forceReset === void 0) { forceReset = false; } if (!this.extentsSet || forceReset) { // Reset extents this.extents = {}; // Set axis extents measures.forEach(function (measure) { _this.extents[measure] = extent(_this.allValues, function (v) { if (!v.measures) return null; return measure in v.measures ? v.measures[measure] : null; }); }); this.extentsSet = true; } }; /******** UPDATE EXTENTS BASED ON VISIBLE DATA ********/ ScatterPlotData.prototype.updateExtents = function (measures) { var _this = this; var visibleData = []; this.data.forEach(function (aggregate) { var aggName = Object.keys(aggregate)[0]; var aggKey = aggregate.aggKey; if (_this.displayState[aggKey].visible == true) { Object.keys(aggregate[aggName]).forEach(function (splitBy) { if (_this.displayState[aggKey].splitBys[splitBy].visible == true) { visibleData.push(Object.values(aggregate[aggName][splitBy])); } }); } }); visibleData = [].concat.apply([], visibleData); measures.forEach(function (measure) { _this.extents[measure] = extent(visibleData, function (v) { return measure in v ? v[measure] : null; }); }); }; /******** UPDATES CHART DATA, ALL TIMESTAMPS, AND VALUES AT THE CURRENT TIMESTAMP ********/ ScatterPlotData.prototype.mergeDataToDisplayStateAndTimeArrays = function (data, timestamp, aggregateExpressionOptions) { if (aggregateExpressionOptions === void 0) { aggregateExpressionOptions = null; } ChartComponentData.prototype.mergeDataToDisplayStateAndTimeArrays.call(this, data, aggregateExpressionOptions); this.timestamp = (timestamp != undefined && this.allTimestampsArray.indexOf(timestamp) !== -1) ? timestamp : this.allTimestampsArray[0]; this.setValuesAtTimestamp(); this.setAllTimestampsArray(); }; /******** UPDATES DATA TO BE DRAWN -- IF SCATTER IS TEMPORAL, FLATTENS ALL TIMESTAMP DATA ********/ ScatterPlotData.prototype.updateTemporalDataArray = function (isTemporal) { var _this = this; this.temporalDataArray = []; if (!isTemporal) { this.allTimestampsArray.forEach(function (ts) { _this.timestamp = ts; _this.setValuesAtTimestamp(); _this.updateTemporal(); }); } else { this.updateTemporal(); } }; /******** HELPER TO FETCH DATA AT THE CURRENT TIMESTAMP AND BUILD AN OBJECT FOR THAT TIMESTAMP ********/ ScatterPlotData.prototype.updateTemporal = function () { var _this = this; Object.keys(this.valuesAtTimestamp).forEach(function (aggKey) { Object.keys(_this.valuesAtTimestamp[aggKey].splitBys).forEach(function (splitBy, splitByI) { var measures = null, timestamp = null; if (_this.getSplitByVisible(aggKey, splitBy) && _this.valuesAtTimestamp[aggKey].splitBys[splitBy].measurements != undefined) { measures = _this.valuesAtTimestamp[aggKey].splitBys[splitBy].measurements; timestamp = _this.valuesAtTimestamp[aggKey].splitBys[splitBy].timestamp; } _this.temporalDataArray.push({ aggregateKey: aggKey, aggregateKeyI: _this.data.findIndex(function (datum) { return datum.aggKey === aggKey; }), splitBy: splitBy, measures: measures, timestamp: timestamp, splitByI: splitByI }); }); }); }; /******** OVERRIDES GROUPEDBARCHARTDATA -- UPDATES VALUES AT TIMESTAMP WITH MEASURES & TIMESTAMP********/ ScatterPlotData.prototype.setValuesAtTimestamp = function () { var _this = this; var aggregateCounterMap = {}; this.valuesAtTimestamp = {}; this.data.forEach(function (aggregate, aggI) { var aggName = Object.keys(aggregate)[0]; var aggKey; if (aggregateCounterMap[aggName]) { aggKey = Utils.createEntityKey(aggName, aggregateCounterMap[aggName]); aggregateCounterMap[aggName] += 1; } else { aggKey = Utils.createEntityKey(aggName, 0); aggregateCounterMap[aggName] = 1; } _this.valuesAtTimestamp[aggKey] = {}; _this.valuesAtTimestamp[aggKey].splitBys = Object.keys(aggregate[aggName]) .reduce(function (aggSplitBys, splitBy, splitByI) { aggSplitBys[splitBy] = {}; aggSplitBys[splitBy].measurements = aggregate[aggName][splitBy][_this.timestamp]; aggSplitBys[splitBy].timestamp = _this.timestamp; return aggSplitBys; }, {}); }); }; return ScatterPlotData; }(GroupedBarChartData)); var ScatterPlot = /** @class */ (function (_super) { __extends(ScatterPlot, _super); function ScatterPlot(renderTarget) { var _this = _super.call(this, renderTarget) || this; _this.activeDot = null; _this.focusedSite = null; _this.lowOpacity = 0.15; _this.standardOpacity = 0.6; _this.focusOpacity = 0.8; _this.standardStroke = 1; _this.lowStroke = 0.3; _this.chartComponentData = new ScatterPlotData(); /******** DRAW UPDATE FUNCTION ********/ _this.draw = function (isFromResize, event) { if (isFromResize === void 0) { isFromResize = false; } _this.activeDot = null; _this.chartComponentData.updateTemporalDataArray(_this.chartOptions.isTemporal); // Update extents to fit data if not temporal _this.chartComponentData.updateExtents(_this.chartOptions.spMeasures); _this.focus.attr("visibility", (_this.chartOptions.focusHidden) ? "hidden" : "visible"); // If only one data series visible, do not highlight on hover var visibleSplitBys = 0; Object.keys(_this.chartComponentData.displayState).forEach(function (aggKey) { if (_this.chartComponentData.displayState[aggKey].visible) Object.keys(_this.chartComponentData.displayState[aggKey].splitBys).forEach(function (splitBy) { if (_this.chartComponentData.displayState[aggKey].splitBys[splitBy].visible) visibleSplitBys++; }); }); if (visibleSplitBys == 1) _this.focusOpacity = _this.standardOpacity; // Determine the number of timestamps present, add margin for slider if (_this.chartComponentData.allTimestampsArray.length > 1 && _this.chartOptions.isTemporal) { _this.chartMargins.bottom = 88; } else { _this.chartMargins.bottom = 48; } _this.setWidthAndHeight(isFromResize); _this.svgSelection .attr("height", _this.height) .style("width", _this.getSVGWidth() + "px"); _this.g .attr("transform", "translate(" + _this.chartMargins.left + "," + _this.chartMargins.top + ")"); _this.voronoiGroup .attr("width", _this.chartWidth) .attr("height", _this.chartHeight); _super.prototype.themify.call(_this, _this.targetElement, _this.chartOptions.theme); // Draw control panel if (!_this.chartOptions.hideChartControlPanel && _this.chartControlsPanel === null) { _this.chartControlsPanel = Utils.createControlPanel(_this.renderTarget, _this.CONTROLSWIDTH, _this.chartMargins.top, _this.chartOptions); } else if (_this.chartOptions.hideChartControlPanel && _this.chartControlsPanel !== null) { _this.removeControlPanel(); } if (_this.chartControlsPanel !== null && _this.ellipsisItemsExist()) { _this.drawEllipsisMenu(); _this.chartControlsPanel.style("top", Math.max((_this.chartMargins.top - 44), 0) + 'px'); } else { _this.removeEllipsisMenu(); } // Resize focus line _this.focus.select('.tsi-hLine').attr("x2", _this.chartWidth); _this.focus.select('.tsi-vLine').attr("y2", _this.chartHeight); _this.measures = _this.chartOptions.spMeasures; _this.xMeasure = _this.measures[0]; _this.yMeasure = _this.measures[1]; _this.rMeasure = _this.measures[2] !== undefined ? _this.measures[2] : null; var xExtentRange = _this.chartComponentData.extents[_this.xMeasure][1] - _this.chartComponentData.extents[_this.xMeasure][0]; var yExtentRange = _this.chartComponentData.extents[_this.yMeasure][1] - _this.chartComponentData.extents[_this.yMeasure][0]; // Pad extents var xOffset = (20 / _this.chartWidth) * (xExtentRange == 0 ? 1 : xExtentRange); var yOffset = (20 / _this.chartHeight) * (yExtentRange == 0 ? 1 : yExtentRange); var rOffset = null; if (_this.rMeasure) { var rExtentRange = _this.chartComponentData.extents[_this.rMeasure][1] - _this.chartComponentData.extents[_this.rMeasure][0]; rOffset = (20 / _this.chartHeight) * (rExtentRange == 0 ? 1 : rExtentRange); } // Check measure validity if (!_this.checkExtentValidity()) return; // Init scales _this.yScale = scaleLinear() .range([_this.chartHeight, 0]) .domain([_this.chartComponentData.extents[_this.yMeasure][0] - yOffset, _this.chartComponentData.extents[_this.yMeasure][1] + yOffset]); _this.xScale = scaleLinear() .range([0, _this.chartWidth]) .domain([_this.chartComponentData.extents[_this.xMeasure][0] - xOffset, _this.chartComponentData.extents[_this.xMeasure][1] + xOffset]); _this.rScale = scaleLinear() .range(_this.chartOptions.scatterPlotRadius.slice(0, 2)) .domain(_this.rMeasure === null ? [0, 0] : [_this.chartComponentData.extents[_this.rMeasure][0] - rOffset, _this.chartComponentData.extents[_this.rMeasure][1] + rOffset]); // Draw axis _this.drawAxis(); // Draw axis labels _this.drawAxisLabels(); // Draw connecting lines (if toggled on) _this.drawConnectingLines(); // Draw data var scatter = _this.pointWrapper.selectAll(".tsi-dot") .data(_this.cleanData(_this.chartComponentData.temporalDataArray), function (d) { if (_this.chartOptions.isTemporal) { return d.aggregateKey + d.splitBy + d.splitByI; } else { return d.aggregateKey + d.splitBy + d.timestamp; } }); scatter .enter() .append("circle") .attr("class", "tsi-dot") .attr("r", function (d) { return _this.rScale(d.measures[_this.rMeasure]); }) .attr("cx", function (d) { return _this.xScale(d.measures[_this.xMeasure]); }) .attr("cy", function (d) { return _this.yScale(d.measures[_this.yMeasure]); }) .merge(scatter) .attr("id", function (d) { return _this.getClassHash(d.aggregateKey, d.splitBy, d.splitByI, d.timestamp); }) .transition() .duration(_this.chartOptions.noAnimate ? 0 : _this.TRANSDURATION) .ease(easeExp) .attr("r", function (d) { return _this.rScale(d.measures[_this.rMeasure]); }) .attr("cx", function (d) { return _this.xScale(d.measures[_this.xMeasure]); }) .attr("cy", function (d) { return _this.yScale(d.measures[_this.yMeasure]); }) .attr("fill", function (d) { return Utils.colorSplitBy(_this.chartComponentData.displayState, d.splitByI, d.aggregateKey, _this.chartOptions.keepSplitByColor); }) .attr("stroke", function (d) { return Utils.colorSplitBy(_this.chartComponentData.displayState, d.splitByI, d.aggregateKey, _this.chartOptions.keepSplitByColor); }) .attr("stroke-opacity", _this.standardStroke) .attr("fill-opacity", _this.standardOpacity) .attr("stroke-width", "1px"); scatter.exit().remove(); // Draw voronoi _this.drawVoronoi(); // Resize controls _this.setControlsPanelWidth(); /******************** Temporal Slider ************************/ if (_this.chartComponentData.allTimestampsArray.length > 1 && _this.chartOptions.isTemporal) { select(_this.renderTarget).select('.tsi-sliderWrapper').classed('tsi-hidden', false); _this.slider.render(_this.chartComponentData.allTimestampsArray.map(function (ts) { var action = function () { _this.chartOptions.timestamp = ts; _this.render(_this.chartComponentData.data, _this.chartOptions, _this.aggregateExpressionOptions, true); }; 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 }; }), _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))); } else { if (_this.slider) _this.slider.remove(); select(_this.renderTarget).select('.tsi-sliderWrapper').classed('tsi-hidden', true); } // Draw Legend _this.legendObject.draw(_this.chartOptions.legend, _this.chartComponentData, _this.labelMouseOver.bind(_this), _this.svgSelection, _this.chartOptions, _this.labelMouseOut.bind(_this), _this.stickySeries, event); _this.sliderWrapper .style("width", _this.svgSelection.node().getBoundingClientRect().width + 10 + "px"); }; /******** UPDATE STICKY SPLITBY ********/ _this.stickySeries = function (aggregateKey, splitBy) { if (splitBy === void 0) { splitBy = null; } var filteredValues = _this.getVoronoiData(_this.chartComponentData.temporalDataArray); if (filteredValues == null || filteredValues.length == 0) return; _this.chartComponentData.stickiedKey = { aggregateKey: aggregateKey, splitBy: (splitBy == null ? null : splitBy) }; _this.legendObject.legendElement.selectAll('.tsi-splitByLabel').filter(function (filteredSplitBy) { return (select(this.parentNode).datum() == aggregateKey) && (filteredSplitBy == splitBy); }).classed("stickied", true); _this.voronoiDiagram = _this.voronoi(_this.getVoronoiData(_this.chartComponentData.temporalDataArray)); }; _this.chartMargins = { top: 40, bottom: 48, left: 70, right: 60 }; return _this; } ScatterPlot.prototype.ScatterPlot = function () { }; ScatterPlot.prototype.render = function (data, options, aggregateExpressionOptions, fromSlider) { var _this = this; if (fromSlider === void 0) { fromSlider = false; } _super.prototype.render.call(this, data, options, aggregateExpressionOptions); // If measure options not set, or less than 2, return if (this.chartOptions["spMeasures"] == null || (this.chartOptions["spMeasures"] != null && this.chartOptions["spMeasures"].length < 2)) { var invalidMessage = "spMeasures not correctly specified or has length < 2: " + this.chartOptions["spMeasures"] + "\n\nPlease add the following chartOption: {spMeasures: ['example_x_axis_measure', 'example_y_axis_measure', 'example_radius_measure']} " + "where the measures correspond to the data key names."; console.log(invalidMessage); return; } this.chartMargins.top = (this.chartOptions.legend === 'compact') ? 84 : 40; if (!this.chartOptions.hideChartControlPanel) this.chartMargins.top += 20; this.chartMargins.left = (this.chartOptions.spAxisLabels != null && this.chartOptions.spAxisLabels.length >= 2) ? 120 : 70; this.chartComponentData.mergeDataToDisplayStateAndTimeArrays(this.data, this.chartOptions.timestamp, this.aggregateExpressionOptions); this.chartComponentData.setExtents(this.chartOptions.spMeasures, !fromSlider); // Check measure validity if (!this.checkExtentValidity()) return; this.controlsOffset = (this.chartOptions.legend == "shown" ? this.CONTROLSWIDTH : 0); this.setWidthAndHeight(); /******** STATIC INITIALIZATION ********/ if (this.svgSelection == null) { // Initialize extents //this.chartComponentData.setExtents(this.chartOptions.spMeasures); this.targetElement = select(this.renderTarget) .classed("tsi-scatterPlot", true); this.svgSelection = this.targetElement.append("svg") .attr("class", "tsi-scatterPlotSVG tsi-chartSVG") .attr('title', this.getString('Scatter plot')) .attr("height", this.height); this.g = this.svgSelection.append("g") .classed("tsi-svgGroup", true); this.lineWrapper = this.g.append("g") .classed("tsi-lineWrapper", true); this.pointWrapper = this.g.append("g") .classed("tsi-pointWrapper", true); // Create temporal slider div this.sliderWrapper = select(this.renderTarget).append('div').classed('tsi-sliderWrapper', true); this.tooltip = new Tooltip(select(this.renderTarget)); // Initialize voronoi this.voronoiGroup = this.g.append("rect") .attr("class", "tsi-voronoiWrap") .attr("fill", "transparent"); // Initialize focus crosshair lines this.focus = this.pointWrapper.append("g") .attr("transform", "translate(-100,-100)") .attr("class", "tsi-focus") .style("display", "none"); this.focus.append("line") .attr("class", "tsi-focusLine tsi-vLine") .attr("x1", 0) .attr("x2", 0) .attr("y1", this.chartOptions.aggTopMargin) .attr("y2", this.chartHeight); this.focus.append("line") .attr("class", "tsi-focusLine tsi-hLine") .attr("x1", 0) .attr("x2", this.chartWidth) .attr("y1", 0) .attr("y2", 0); // Initialize focus axis data boxes var hHoverG = this.focus.append("g") .attr("class", 'hHoverG') .style("pointer-events", "none") .attr("transform", "translate(0," + (this.chartHeight + this.chartOptions.aggTopMargin) + ")"); hHoverG.append("rect") .style("pointer-events", "none") .attr("class", 'hHoverBox') .attr("x", 0) .attr("y", 4) .attr("width", 0) .attr("height", 0); hHoverG.append("text") .style("pointer-events", "none") .attr("class", "hHoverText") .attr("dy", ".71em") .attr("transform", "translate(0,9)") .text(function (d) { return d; }); var vHoverG = this.focus.append("g") .attr("class", 'vHoverG') .attr("transform", "translate(0," + (this.chartHeight + this.chartOptions.aggTopMargin) + ")"); vHoverG.append("rect") .attr("class", 'vHoverBox') .attr("x", -5) .attr("y", 0) .attr("width", 0) .attr("height", 0); vHoverG.append("text") .attr("class", "vHoverText") .attr("dy", ".32em") .attr("x", -10) .text(function (d) { return d; }); // Add Window Resize Listener window.addEventListener("resize", function (event) { if (!_this.chartOptions.suppressResizeListener) { _this.draw(true, event); } }); // Temporal slider this.slider = new Slider(select(this.renderTarget).select('.tsi-sliderWrapper').node()); // Legend this.legendObject = new Legend(this.draw.bind(this), this.renderTarget, this.CONTROLSWIDTH); } // Draw scatter plot this.draw(); this.gatedShowGrid(); select("html").on("click." + Utils.guid(), function (event) { if (_this.ellipsisContainer && event.target != _this.ellipsisContainer.select(".tsi-ellipsisButton").node()) { _this.ellipsisMenu.setMenuVisibility(false); } }); this.legendPostRenderProcess(this.chartOptions.legend, this.svgSelection, false); }; ScatterPlot.prototype.getSliderWidth = function () { return this.chartWidth + this.chartMargins.left + this.chartMargins.right - 16; }; ScatterPlot.prototype.tooltipFormat = function (d, text, measureFormat, xyrMeasures) { _super.prototype.tooltipFormat.call(this, d, text, measureFormat, xyrMeasures); if (!this.chartOptions.isTemporal) { var titleGroup = text.select('.tsi-tooltipTitleGroup'); if (d.timestamp) { titleGroup.append('h4') .attr('class', 'tsi-tooltipSubtitle tsi-tooltipTimeStamp') .text(this.formatDate(d.timestamp, this.chartComponentData.getTemporalShiftMillis(d.aggregateKey))); } } }; /******** DRAW CONNECTING LINES BETWEEN POINTS ********/ ScatterPlot.prototype.drawConnectingLines = function () { var _this = this; // Don't render connecting lines on temporal mode if (this.chartOptions.isTemporal) { this.lineWrapper.selectAll("*").remove(); return; } var dataSet = this.cleanData(this.chartComponentData.temporalDataArray); var connectedSeriesMap = {}; // Find measure by which to connect series of points var getPointConnectionMeasure = (function (point) { var _a; var pConMes = (_a = _this.aggregateExpressionOptions[point.aggregateKeyI]) === null || _a === void 0 ? void 0 : _a.pointConnectionMeasure; return pConMes && pConMes in point.measures ? pConMes : null; }); // Map data into groups of connected points, if connectedPoints enabled for agg dataSet.forEach(function (point) { if (point.aggregateKeyI !== null && point.aggregateKeyI < _this.aggregateExpressionOptions.length && _this.aggregateExpressionOptions[point.aggregateKeyI].connectPoints) { var series = point.aggregateKey + "_" + point.splitBy; if (series in connectedSeriesMap) { connectedSeriesMap[series].data.push(point); } else { connectedSeriesMap[series] = { data: [point], pointConnectionMeasure: getPointConnectionMeasure(point) }; } } }); var _loop_1 = function (key) { var sortMeasure = connectedSeriesMap[key].pointConnectionMeasure; // If sort measure specified, sort by that measure if (sortMeasure) { connectedSeriesMap[key].data.sort(function (a, b) { if (a.measures[sortMeasure] < b.measures[sortMeasure]) return -1; if (a.measures[sortMeasure] > b.measures[sortMeasure]) return 1; return 0; }); } }; // Sort connected series by pointConnectionMeasure for (var _i = 0, _a = Object.keys(connectedSeriesMap); _i < _a.length; _i++) { var key = _a[_i]; _loop_1(key); } var line$1 = line() .x(function (d) { return _this.xScale(d.measures[_this.xMeasure]); }) .y(function (d) { return _this.yScale(d.measures[_this.yMeasure]); }) .curve(this.chartOptions.interpolationFunction); // apply smoothing to the line // Group lines by aggregate var connectedGroups = this.lineWrapper.selectAll(".tsi-lineSeries").data(Object.keys(connectedSeriesMap)); var self = this; connectedGroups.enter() .append("g") .attr("class", 'tsi-lineSeries') .merge(connectedGroups) .each(function (seriesName) { var series = select(this).selectAll(".tsi-line").data([connectedSeriesMap[seriesName].data], function (d) { return d[0].aggregateKeyI + d[0].splitBy; }); series.exit().remove(); series .enter() .append("path") .attr("class", "tsi-line") .merge(series) .attr("fill", "none") .transition() .duration(self.chartOptions.noAnimate ? 0 : self.TRANSDURATION) .ease(easeExp) .attr("stroke", function (d) { return Utils.colorSplitBy(self.chartComponentData.displayState, d[0].splitByI, d[0].aggregateKey, self.chartOptions.keepSplitByColor); }) .attr("stroke-width", 2.5) .attr("stroke-linejoin", "round") .attr("stroke-linecap", "round") .attr("d", line$1); }); connectedGroups.exit().remove(); }; /******** CHECK VALIDITY OF EXTENTS ********/ ScatterPlot.prototype.checkExtentValidity = function () { var _this = this; if (this.chartComponentData.allValues == 0) { return true; } var testExtent = {}; this.chartOptions.spMeasures.forEach(function (measure) { testExtent[measure] = extent(_this.chartComponentData.allValues, function (v) { if (!v.measures) return null; return measure in v.measures ? v.measures[measure] : null; }); }); Object.keys(testExtent).forEach(function (extent) { testExtent[extent].forEach(function (el) { if (el == undefined) { console.log("Undefined Measure: ", extent); return false; } }); }); return true; }; /******** CREATE VORONOI DIAGRAM FOR MOUSE EVENTS ********/ ScatterPlot.prototype.drawVoronoi = function () { var _this = this; var voronoiData = this.getVoronoiData(this.chartComponentData.temporalDataArray); var self = this; // Create random offset to solve colinear data issue var getRandomInRange = function (min, max) { return Math.random() * (max - min) + min; }; var getOffset = function () { return (Math.random() < 0.5 ? -1 : 1) * getRandomInRange(0, .01); }; this.voronoi = voronoi() .x(function (d) { return _this.xScale(d.measures[_this.xMeasure]) + getOffset(); }) .y(function (d) { return _this.yScale(d.measures[_this.yMeasure]) + getOffset(); }) .extent([[0, 0], [this.chartWidth, this.chartHeight]]); this.voronoiDiagram = this.voronoi(voronoiData); this.voronoiGroup .on("mousemove", function (event) { var mouseEvent = pointer(event); self.voronoiMouseMove(mouseEvent); }) .on("mouseover", function (event) { var mouseEvent = pointer(event); self.voronoiMouseMove(mouseEvent); var site = self.voronoiDiagram.find(mouseEvent[0], mouseEvent[1]); if (site != null) self.labelMouseOver(site.data.aggregateKey, site.data.splitBy); }) .on("mouseout", function () { self.voronoiMouseOut(); }) .on("click", function (event) { var mouseEvent = pointer(event); self.voronoiClick(mouseEvent); }); }; /******** STICKY/UNSTICKY DATA GROUPS ON VORONOI DIAGRAM CLICK ********/ ScatterPlot.prototype.voronoiClick = function (mouseEvent) { var site = this.voronoiDiagram.find(mouseEvent[0], mouseEvent[1]); if (site == null) return; // Unsticky all this.legendObject.legendElement.selectAll('.tsi-splitByLabel').classed("stickied", false); if (this.chartComponentData.stickiedKey != null) { this.chartComponentData.stickiedKey = null; // Recompute Voronoi this.voronoiDiagram = this.voronoi(this.getVoronoiData(this.chartComponentData.temporalDataArray)); site = this.voronoiDiagram.find(mouseEvent[0], mouseEvent[1]); this.voronoiMouseMove(mouseEvent); this.chartOptions.onUnsticky(site.data.aggregateKey, site.data.splitBy); return; } this.stickySeries(site.data.aggregateKey, site.data.splitBy); this.chartOptions.onSticky(site.data.aggregateKey, site.data.splitBy); }; /******** HIGHLIGHT DOT TARGETED BY CROSSHAIRS WITH BLACK / WHITE STROKE BORDER ********/ ScatterPlot.prototype.highlightDot = function (site) { //If dot is active, unhighlight this.unhighlightDot(); // Add highlight border to newly focused dot var highlightColor = this.chartOptions.theme == "light" ? "black" : "white"; var idSelector = "#" + this.getClassHash(site.data.aggregateKey, site.data.splitBy, site.data.splitByI, site.data.timestamp); this.activeDot = this.svgSelection.select(idSelector); this.activeDot .attr("stroke", highlightColor) .attr("stroke-width", "2px") // Raise active dot above crosshair .raise().classed("active", true); }; /******** GET UNIQUE STRING HASH ID FOR EACH DOT USING DATA ATTRIBUTES ********/ ScatterPlot.prototype.getClassHash = function (aggKey, splitBy, splitByI, timestamp) { return String("dot" + Utils.hash(aggKey + splitBy + splitByI.toString() + timestamp)); }; /******** UNHIGHLIGHT ACTIVE DOT ********/ ScatterPlot.prototype.unhighlightDot = function () { var _this = this; if (this.activeDot) { this.activeDot .attr("stroke", function (d) { return Utils.colorSplitBy(_this.chartComponentData.displayState, d.splitByI, d.aggregateKey, _this.chartOptions.keepSplitByColor); }) .attr("stroke-width", "1px"); } this.activeDot = null; }; /******** EFFICIENTLY SWAP NEW FOCUSED GROUP WITH OLD FOCUSED GROUP ********/ ScatterPlot.prototype.labelMouseMove = function (aggKey, splitBy) { if (aggKey !== this.focusedAggKey || splitBy !== this.focusedSplitBy) { var selectedFilter = Utils.createValueFilter(aggKey, splitBy); var oldFilter = Utils.createValueFilter(this.focusedAggKey, this.focusedSplitBy); this.svgSelection.selectAll(".tsi-dot") .filter(selectedFilter) .attr("stroke-opacity", this.standardStroke) .attr("fill-opacity", this.focusOpacity); this.svgSelection.selectAll(".tsi-dot") .filter(oldFilter) .attr("stroke-opacity", this.lowStroke) .attr("fill-opacity", this.lowOpacity); var lineSelectedFilter_1 = function (d) { return (d[0].aggregateKey === aggKey && d[0].splitBy === splitBy); }; this.svgSelection.selectAll(".tsi-line") .filter(function (d) { return lineSelectedFilter_1(d); }) .attr("stroke-opacity", this.standardStroke); this.svgSelection.selectAll(".tsi-line") .filter(function (d) { return !lineSelectedFilter_1(d); }) .attr("stroke-opacity", this.lowStroke); this.focusedAggKey = aggKey; this.focusedSplitBy = splitBy; } // Raise crosshair to top this.focus.raise().classed("active", true); // Raise highlighted dot above crosshairs if (this.activeDot != null) this.activeDot.raise().classed("active", true); // Highlight legend group (this.legendObject.legendElement.selectAll('.tsi-splitByLabel').filter(function (filteredSplitBy) { return (select(this.parentNode).datum() == aggKey) && (filteredSplitBy == splitBy); })).classed("inFocus", true); }; /******** DRAW CROSSHAIRS, TOOLTIP, AND LEGEND FOCUS ********/ ScatterPlot.prototype.voronoiMouseMove = function (mouseEvent) { var mouse_x = mouseEvent[0]; var mouse_y = mouseEvent[1]; var site = this.voronoiDiagram.find(mouse_x, mouse_y); if (site == null) return; // Short circuit mouse move if focused site has not changed if (this.focusedSite == null) this.focusedSite = site; else if (this.focusedSite == site) return; this.focusedSite = site; this.drawTooltip(site.data, [site[0], site[1]]); this.labelMouseMove(site.data.aggregateKey, site.data.splitBy); this.highlightDot(site); // Draw focus cross hair this.focus.style("display", "block"); this.focus.attr("transform", "translate(" + site[0] + "," + site[1] + ")"); this.focus.select('.tsi-hLine').attr("transform", "translate(" + (-site[0]) + ",0)"); this.focus.select('.tsi-vLine').attr("transform", "translate(0," + (-site[1]) + ")"); // Draw horizontal hover box this.focus.select('.hHoverG') .attr("transform", "translate(0," + (this.chartHeight - site[1]) + ")") .select("text") .text((Utils.formatYAxisNumber(site.data.measures[this.xMeasure]))); var textElemDimensions = this.focus.select('.hHoverG').select("text") .node().getBoundingClientRect(); this.focus.select(".hHoverG").select("rect") .attr("x", -(textElemDimensions.width / 2) - 3) .attr("width", textElemDimensions.width + 6) .attr("height", textElemDimensions.height + 5); // Draw vertical hover box this.focus.select('.vHoverG') .attr("transform", "translate(" + (-site[0]) + ",0)") .select("text") .text(Utils.formatYAxisNumber(site.data.measures[this.yMeasure])); textElemDimensions = this.focus.select('.vHoverG').select("text") .node().getBoundingClientRect(); this.focus.select(".vHoverG").select("rect") .attr("x", -(textElemDimensions.width) - 13) .attr("y", -(textElemDimensions.height / 2) - 3) .attr("width", textElemDimensions.width + 6) .attr("height", textElemDimensions.height + 4); this.legendObject.triggerSplitByFocus(site.data.aggregateKey, site.data.splitBy); }; /******** HIDE TOOLTIP AND CROSSHAIRS ********/ ScatterPlot.prototype.voronoiMouseOut = function () { this.focusedSite = null; this.focus.style("display", "none"); this.tooltip.hide(); this.labelMouseOut(); this.unhighlightDot(); }; /******** FILTER DATA BY VISIBLE AND STICKIED ********/ ScatterPlot.prototype.getVoronoiData = function (rawData) { var _this = this; var cleanData = this.cleanData(rawData); var filteredValues = cleanData.filter(function (d) { return (_this.chartComponentData.displayState[d.aggregateKey].visible && _this.chartComponentData.displayState[d.aggregateKey].splitBys[d.splitBy].visible); }); if (this.chartComponentData.stickiedKey == null) return filteredValues; var stickiedValues = filteredValues.filter(function (d) { return d.aggregateKey == _this.chartComponentData.stickiedKey.aggregateKey && ((_this.chartComponentData.stickiedKey.splitBy == null) ? true : d.splitBy == _this.chartComponentData.stickiedKey.splitBy); }); return stickiedValues; }; /******** HIGHLIGHT FOCUSED GROUP ********/ ScatterPlot.prototype.labelMouseOver = function (aggKey, splitBy) { if (splitBy === void 0) { splitBy = null; } // Remove highlight on previous legend group this.legendObject.legendElement.selectAll('.tsi-splitByLabel').classed("inFocus", false); // Filter selected var selectedFilter = function (d) { var currAggKey = null, currSplitBy = null; if (d.aggregateKey != null) currAggKey = d.aggregateKey; if (d.splitBy != null) currSplitBy = d.splitBy; if (splitBy == null) return currAggKey == aggKey; if (currAggKey == aggKey && currSplitBy == splitBy) return false; return true; }; //Highlight active group this.svgSelection.selectAll(".tsi-dot") .filter(function (d) { return !selectedFilter(d); }) .attr("stroke-opacity", this.standardStroke) .attr("fill-opacity", this.focusOpacity); // Decrease opacity of unselected this.svgSelection.selectAll(".tsi-dot") .filter(selectedFilter) .attr("stroke-opacity", this.lowStroke) .attr("fill-opacity", this.lowOpacity); // Decrease opacity of unselected line this.svgSelection.selectAll(".tsi-line") .filter(function (d) { return !(d[0].aggregateKey === aggKey && d[0].splitBy === splitBy); }) .attr("stroke-opacity", this.lowStroke); }; /******** UNHIGHLIGHT FOCUSED GROUP ********/ ScatterPlot.prototype.labelMouseOut = function () { var _this = this; // Remove highlight on legend group this.legendObject.legendElement.selectAll('.tsi-splitByLabel').classed("inFocus", false); this.g.selectAll(".tsi-dot") .attr("stroke-opacity", this.standardStroke) .attr("fill-opacity", this.standardOpacity) .attr("stroke", function (d) { return Utils.colorSplitBy(_this.chartComponentData.displayState, d.splitByI, d.aggregateKey, _this.chartOptions.keepSplitByColor); }) .attr("fill", function (d) { return Utils.colorSplitBy(_this.chartComponentData.displayState, d.splitByI, d.aggregateKey, _this.chartOptions.keepSplitByColor); }) .attr("stroke-width", "1px"); this.g.selectAll(".tsi-line") .attr("stroke-opacity", this.standardStroke); }; /******** FILTER DATA, ONLY KEEPING POINTS WITH ALL REQUIRED MEASURES ********/ ScatterPlot.prototype.cleanData = function (data) { var _this = this; // Exclude any data which does not contain the specified // chart option measure var filtered = data.filter(function (value) { var valOk = true; _this.chartOptions.spMeasures .forEach(function (measure) { if (value.measures == null) valOk = false; else if (!(measure in value.measures)) { valOk = false; } }); return valOk; }); return filtered; }; /******** UPDATE CHART DIMENSIONS ********/ ScatterPlot.prototype.setWidthAndHeight = function (isFromResize) { if (isFromResize === void 0) { isFromResize = false; } this.height = Math.max(select(this.renderTarget).node().clientHeight, this.MINHEIGHT); this.chartHeight = this.height - this.chartMargins.top - this.chartMargins.bottom; this.width = this.getWidth(); if (!isFromResize) { this.chartWidth = this.getChartWidth(); } }; /******** SCALE AND DRAW AXIS ********/ ScatterPlot.prototype.drawAxis = function () { // Draw dynamic x axis and label this.xAxis = this.pointWrapper.selectAll(".xAxis").data([this.xScale]); this.xAxis.enter() .append("g") .attr("class", "xAxis") .merge(this.xAxis) .attr("transform", "translate(0," + (this.chartHeight) + ")") .call(axisBottom(this.xScale).ticks(Math.max(2, Math.floor(this.chartWidth / 150)))); this.xAxis.exit().remove(); // Draw dynamic y axis and label this.yAxis = this.pointWrapper.selectAll(".yAxis").data([this.yScale]); this.yAxis.enter() .append("g") .attr("class", "yAxis") .merge(this.yAxis) .call(axisLeft(this.yScale).ticks(Math.max(2, Math.floor(this.chartHeight / 90)))); this.yAxis.exit().remove(); }; /******** DRAW X AND Y AXIS LABELS ********/ ScatterPlot.prototype.drawAxisLabels = function () { var self = this; var xLabelData, yLabelData; var truncateTextLength = function (textSelection, maxTextLengthPx) { if (textSelection.node() && textSelection.node().getComputedTextLength) { var textLength = textSelection.node().getComputedTextLength(); var text = textSelection.text(); while (textLength > maxTextLengthPx && text.length > 0) { text = text.slice(0, -1); textSelection.text(text + '...'); textLength = textSelection.node().getComputedTextLength(); } } }; // Associate axis label data (this.chartOptions.spAxisLabels != null && this.chartOptions.spAxisLabels.length >= 1) ? xLabelData = [this.chartOptions.spAxisLabels[0]] : xLabelData = []; (this.chartOptions.spAxisLabels != null && this.chartOptions.spAxisLabels.length >= 2) ? yLabelData = [this.chartOptions.spAxisLabels[1]] : yLabelData = []; this.xAxisLabel = this.pointWrapper.selectAll('.tsi-xAxisLabel').data(xLabelData); var xAxisLabel = this.xAxisLabel .enter() .append("text") .attr("class", "tsi-xAxisLabel tsi-AxisLabel") .merge(this.xAxisLabel) .style("text-anchor", "middle") .attr("transform", "translate(" + (this.chartWidth / 2) + " ," + (this.chartHeight + 42) + ")") .text(null); xAxisLabel.each(function (d) { var label = select(this); Utils.appendFormattedElementsFromString(label, d, { inSvg: true }); }); //text is either in tspans or just in text. Either truncate text directly or through tspan if (xAxisLabel.selectAll("tspan").size() == 0) truncateTextLength(xAxisLabel, this.chartWidth); else { xAxisLabel.selectAll("tspan").each(function () { var tspanTextSelection = select(this); truncateTextLength(tspanTextSelection, self.chartWidth / xAxisLabel.selectAll("tspan").size()); }); } this.xAxisLabel.exit().remove(); this.yAxisLabel = this.pointWrapper.selectAll('.tsi-yAxisLabel').data(yLabelData); var yAxisLabel = this.yAxisLabel .enter() .append("text") .attr("class", "tsi-yAxisLabel tsi-AxisLabel") .merge(this.yAxisLabel) .style("text-anchor", "middle") .attr("transform", "translate(" + (-70) + " ," + (this.chartHeight / 2) + ") rotate(-90)") .text(null); yAxisLabel.each(function (d) { var label = select(this); Utils.appendFormattedElementsFromString(label, d, { inSvg: true }); }); //text is either in tspans or just in text. Either truncate text directly or through tspan if (yAxisLabel.selectAll("tspan").size() == 0) truncateTextLength(yAxisLabel, this.chartHeight); else { yAxisLabel.selectAll("tspan").each(function () { var tspanTextSelection = select(this); truncateTextLength(tspanTextSelection, self.chartHeight / yAxisLabel.selectAll("tspan").size()); }); } this.yAxisLabel.exit().remove(); }; /******** DRAW TOOLTIP IF ENABLED ********/ ScatterPlot.prototype.drawTooltip = function (d, mousePosition) { var _this = this; if (this.chartOptions.tooltip) { var xPos = mousePosition[0]; var yPos = mousePosition[1]; var xyrMeasures_1 = [this.xMeasure, this.yMeasure]; if (this.rMeasure !== null) { xyrMeasures_1.push(this.rMeasure); } this.tooltip.render(this.chartOptions.theme); this.tooltip.draw(d, this.chartComponentData, xPos, yPos, this.chartMargins, function (text) { d.aggregateName = _this.chartComponentData.displayState[d.aggregateKey].name; _this.tooltipFormat(d, text, TooltipMeasureFormat.Scatter, xyrMeasures_1); }, null, 20, 20, Utils.colorSplitBy(this.chartComponentData.displayState, d.splitByI, d.aggregateKey, this.chartOptions.keepSplitByColor)); } }; /******** HELPERS TO FORMAT TIME DISPLAY ********/ ScatterPlot.prototype.labelFormatUsesSeconds = function () { return !this.chartOptions.minutesForTimeLabels && this.chartComponentData.usesSeconds; }; ScatterPlot.prototype.labelFormatUsesMillis = function () { return !this.chartOptions.minutesForTimeLabels && this.chartComponentData.usesMillis; }; return ScatterPlot; }(ChartVisualizationComponent)); export { ScatterPlot as S };