UNPKG

scichart

Version:

Fast WebGL JavaScript Charting Library and Framework

411 lines (410 loc) 20.8 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SeriesSelectionModifier3D = void 0; var classFactory_1 = require("../../Builder/classFactory"); var EventHandler_1 = require("../../Core/EventHandler"); var Point_1 = require("../../Core/Point"); var array_1 = require("../../utils/array"); var BaseType_1 = require("../../types/BaseType"); var ExecuteOn_1 = require("../../types/ChartModifiers/ExecuteOn"); var ChartModifierType_1 = require("../../types/ChartModifierType"); var ChartModifierBase3D_1 = require("./ChartModifierBase3D"); var HoveredChangedArgs3D_1 = require("../Visuals/RenderableSeries/HoveredChangedArgs3D"); var SelectionChangedArgs3D_1 = require("../Visuals/RenderableSeries/SelectionChangedArgs3D"); /** * The SeriesSelectionModifier3D provides series selection and hover behavior on a 3D {@link SciChart3DSurface} */ var SeriesSelectionModifier3D = /** @class */ (function (_super) { __extends(SeriesSelectionModifier3D, _super); function SeriesSelectionModifier3D(options) { var _this = this; var _a, _b, _c, _d; _this = _super.call(this, options) || this; _this.type = ChartModifierType_1.EChart3DModifierType.SeriesSelection; /** * An array of currently selected series which can be observed by subscribing to the {@link selectionChanged} {@link EventHandler | event handler} */ _this.selectedSeries = []; /** * An array of currently hovered series which can be observed by subscribing to the {@link hoverChanged} {@link EventHandler | event handler} */ _this.hoveredSeries = []; /** * A selection-changed EventHandler. This is raised whenever any included series is selected or unselected. */ _this.selectionChanged = new EventHandler_1.EventHandler(); /** * A hover-changed EventHandler. This is raised whenever any included series is hovered or unhovered. */ _this.hoverChanged = new EventHandler_1.EventHandler(); _this.enableSelection = (_a = options === null || options === void 0 ? void 0 : options.enableSelection) !== null && _a !== void 0 ? _a : true; _this.enableHover = (_b = options === null || options === void 0 ? void 0 : options.enableHover) !== null && _b !== void 0 ? _b : false; _this.executeCondition = (_c = options === null || options === void 0 ? void 0 : options.executeCondition) !== null && _c !== void 0 ? _c : { button: ExecuteOn_1.EExecuteOn.MouseLeftButton }; _this.hitTestRadius = _this.getHitTestRadius(options === null || options === void 0 ? void 0 : options.hitTestRadius); _this.prioritizeClosestToCamera = (_d = options === null || options === void 0 ? void 0 : options.prioritizeClosestToCamera) !== null && _d !== void 0 ? _d : false; _this.preventReentrancy = false; if (options === null || options === void 0 ? void 0 : options.onSelectionChanged) { if (typeof options.onSelectionChanged === "string") { _this.typeMap.set("onSelectionChanged", options.onSelectionChanged); var onSelectionChangedCallback = (0, classFactory_1.getFunction)(BaseType_1.EBaseType.OptionFunction, options.onSelectionChanged); _this.selectionChanged.subscribe(onSelectionChangedCallback); } else { _this.selectionChanged.subscribe(options.onSelectionChanged); } } if (options === null || options === void 0 ? void 0 : options.onHoverChanged) { if (typeof options.onHoverChanged === "string") { _this.typeMap.set("onHoverChanged", options.onHoverChanged); var onHoverChangedCallback = (0, classFactory_1.getFunction)(BaseType_1.EBaseType.OptionFunction, options.onHoverChanged); _this.hoverChanged.subscribe(onHoverChangedCallback); } else { _this.hoverChanged.subscribe(options.onHoverChanged); } } _this.updateSeriesSelected = _this.updateSeriesSelected.bind(_this); _this.updateSeriesHovered = _this.updateSeriesHovered.bind(_this); _this.onRenderableSeriesCollectionChanged = _this.onRenderableSeriesCollectionChanged.bind(_this); return _this; } /** @inheritDoc */ SeriesSelectionModifier3D.prototype.onAttach = function () { var _this = this; var _a; _super.prototype.onAttach.call(this); this.selectedSeries = []; this.hoveredSeries = []; if (this.parentSurface) { this.parentSurface.isHitTestEnabled = true; this.parentSurface.renderableSeries.collectionChanged.subscribe(this.onRenderableSeriesCollectionChanged); } (_a = this.getIncludedRenderableSeries()) === null || _a === void 0 ? void 0 : _a.forEach(function (rs) { return _this.onAttachSeries(rs); }); }; /** @inheritDoc */ SeriesSelectionModifier3D.prototype.onDetach = function () { var _this = this; var _a, _b; (_a = this.parentSurface) === null || _a === void 0 ? void 0 : _a.renderableSeries.asArray().forEach(function (rs) { return _this.onDetachSeries(rs); }); this.selectedSeries = []; this.hoveredSeries = []; (_b = this.parentSurface) === null || _b === void 0 ? void 0 : _b.renderableSeries.collectionChanged.unsubscribe(this.onRenderableSeriesCollectionChanged); _super.prototype.onDetach.call(this); }; /** @inheritDoc */ SeriesSelectionModifier3D.prototype.onAttachSeries = function (rs) { var series = rs; if (!series) { return; } if (series.isSelected && !this.selectedSeries.includes(series)) { this.selectedSeries.push(series); } series.selected.unsubscribe(this.updateSeriesSelected); series.selected.subscribe(this.updateSeriesSelected); if (series.isHovered && !this.hoveredSeries.includes(series)) { this.hoveredSeries.push(series); } series.hovered.unsubscribe(this.updateSeriesHovered); series.hovered.subscribe(this.updateSeriesHovered); }; /** @inheritDoc */ SeriesSelectionModifier3D.prototype.onDetachSeries = function (rs) { var series = rs; if (!series) { return; } this.selectedSeries = (0, array_1.arrayRemove)(this.selectedSeries, series); series.selected.unsubscribe(this.updateSeriesSelected); this.hoveredSeries = (0, array_1.arrayRemove)(this.hoveredSeries, series); series.hovered.unsubscribe(this.updateSeriesHovered); }; /** @inheritDoc */ SeriesSelectionModifier3D.prototype.modifierMouseDown = function (args) { _super.prototype.modifierMouseDown.call(this, args); if (!this.checkExecuteConditions(args).isPrimary) return; if (!this.isAttached) { throw new Error("Should not call SeriesSelectionModifier3D.modifierMouseDown if not attached"); } }; /** @inheritDoc */ SeriesSelectionModifier3D.prototype.modifierMouseMove = function (args) { var _a; _super.prototype.modifierMouseMove.call(this, args); if (!this.enableHover) { return; } this.updateHoverState((_a = this.mousePoint) !== null && _a !== void 0 ? _a : args.mousePoint); }; /** @inheritDoc */ SeriesSelectionModifier3D.prototype.onParentSurfaceRendered = function () { _super.prototype.onParentSurfaceRendered.call(this); if (!this.enableHover || !this.mousePoint) { return; } this.updateHoverState(this.mousePoint); }; /** @inheritDoc */ SeriesSelectionModifier3D.prototype.modifierMouseLeave = function (args) { var _a; _super.prototype.modifierMouseLeave.call(this, args); this.mousePoint = undefined; var allSeries = this.getIncludedRenderableSeries(); var prevHovered = __spreadArray([], this.hoveredSeries, true); var nearestHitTestInfo = undefined; if (prevHovered.length > 0) { this.preventReentrancy = true; try { this.hoveredSeries = []; allSeries.forEach(function (s) { return (s.isHovered = false); }); (_a = this.hoverChanged) === null || _a === void 0 ? void 0 : _a.raiseEvent(new HoveredChangedArgs3D_1.HoveredChangedArgs3D(this, this.hoveredSeries, allSeries, nearestHitTestInfo)); } finally { this.preventReentrancy = false; } } }; /** @inheritDoc */ SeriesSelectionModifier3D.prototype.modifierPointerCancel = function (args) { this.modifierMouseLeave(args); }; /** @inheritDoc */ SeriesSelectionModifier3D.prototype.modifierMouseUp = function (args) { var _a; _super.prototype.modifierMouseUp.call(this, args); if (!this.checkExecuteConditions(args).isPrimary) return; if (!this.enableSelection) { return; } if (!this.isAttached) { throw new Error("Should not call SeriesSelectionModifier3D.modifierMouseUp if not attached"); } var allSeries = this.getIncludedRenderableSeries(); this.preventReentrancy = true; try { this.selectedSeries = []; allSeries.forEach(function (rs) { return (rs.isSelected = false); }); var nearestHitTestInfo = this.findFirstHit(allSeries, args.mousePoint, this.hitTestRadius); if (nearestHitTestInfo === null || nearestHitTestInfo === void 0 ? void 0 : nearestHitTestInfo.isHit) { nearestHitTestInfo.renderableSeries.isSelected = true; this.selectedSeries.push(nearestHitTestInfo.renderableSeries); } (_a = this.selectionChanged) === null || _a === void 0 ? void 0 : _a.raiseEvent(new SelectionChangedArgs3D_1.SelectionChangedArgs3D(this, this.selectedSeries, allSeries, nearestHitTestInfo)); } finally { this.preventReentrancy = false; } }; SeriesSelectionModifier3D.prototype.toJSON = function () { var json = _super.prototype.toJSON.call(this); var options = { enableHover: this.enableHover, enableSelection: this.enableSelection, hitTestRadius: this.hitTestRadius, onHoverChanged: this.typeMap.get("onHoverChanged"), onSelectionChanged: this.typeMap.get("onSelectionChanged"), prioritizeClosestToCamera: this.prioritizeClosestToCamera }; Object.assign(json.options, options); return json; }; SeriesSelectionModifier3D.prototype.onRenderableSeriesCollectionChanged = function (args) { var _this = this; var _a, _b; (_a = args.getOldItems()) === null || _a === void 0 ? void 0 : _a.forEach(function (rs) { return _this.onDetachSeries(rs); }); (_b = args.getNewItems()) === null || _b === void 0 ? void 0 : _b.filter(function (rs) { return _this.testIsIncludedSeries(rs); }).forEach(function (rs) { return _this.onAttachSeries(rs); }); }; SeriesSelectionModifier3D.prototype.updateHoverState = function (mousePoint) { var _this = this; var _a; var allSeries = this.getIncludedRenderableSeries(); this.preventReentrancy = true; try { var prevHovered = __spreadArray([], this.hoveredSeries, true); this.hoveredSeries = []; var nearestHitTestInfo = this.findFirstHit(allSeries, mousePoint, this.hitTestRadius); var hoveredSeries_1 = (nearestHitTestInfo === null || nearestHitTestInfo === void 0 ? void 0 : nearestHitTestInfo.isHit) ? nearestHitTestInfo.renderableSeries : undefined; allSeries.forEach(function (s) { return (s.isHovered = s === hoveredSeries_1); }); if (hoveredSeries_1) { this.hoveredSeries.push(hoveredSeries_1); } if (prevHovered.length !== this.hoveredSeries.length || prevHovered.some(function (s) { return !_this.hoveredSeries.includes(s); })) { (_a = this.hoverChanged) === null || _a === void 0 ? void 0 : _a.raiseEvent(new HoveredChangedArgs3D_1.HoveredChangedArgs3D(this, this.hoveredSeries, allSeries, nearestHitTestInfo)); } } finally { this.preventReentrancy = false; } }; /** * Performs point-based hit-testing with a small pixel tolerance. * This improves hover detection for thin 3D lines without affecting click selection behavior. */ SeriesSelectionModifier3D.prototype.findFirstHit = function (allSeries, mousePoint, searchRadius) { if (this.prioritizeClosestToCamera) { return this.findClosestHitToCamera(allSeries, mousePoint, searchRadius); } var centerHit = this.hitTestAtPoint(allSeries, mousePoint); if ((centerHit === null || centerHit === void 0 ? void 0 : centerHit.isHit) || searchRadius <= 0) { return centerHit; } for (var radius = 1; radius <= searchRadius; radius++) { var testPoints = [ new Point_1.Point(mousePoint.x + radius, mousePoint.y), new Point_1.Point(mousePoint.x - radius, mousePoint.y), new Point_1.Point(mousePoint.x, mousePoint.y + radius), new Point_1.Point(mousePoint.x, mousePoint.y - radius), new Point_1.Point(mousePoint.x + radius, mousePoint.y + radius), new Point_1.Point(mousePoint.x + radius, mousePoint.y - radius), new Point_1.Point(mousePoint.x - radius, mousePoint.y + radius), new Point_1.Point(mousePoint.x - radius, mousePoint.y - radius) ]; for (var _i = 0, testPoints_1 = testPoints; _i < testPoints_1.length; _i++) { var point = testPoints_1[_i]; var hit = this.hitTestAtPoint(allSeries, point); if (hit === null || hit === void 0 ? void 0 : hit.isHit) { return hit; } } } return undefined; }; /** * Samples multiple screen positions around the mouse point up to searchRadius and * returns the hit whose world coordinate is closest to camera. */ SeriesSelectionModifier3D.prototype.findClosestHitToCamera = function (allSeries, mousePoint, searchRadius) { var _this = this; var nearestHit; var nearestDistance = Number.POSITIVE_INFINITY; var updateNearest = function (hit) { if (!(hit === null || hit === void 0 ? void 0 : hit.isHit)) { return; } var distance = _this.distanceSquaredToCamera(hit); if (distance < nearestDistance) { nearestHit = hit; nearestDistance = distance; } }; // test center first this.hitTestAtPointAllSeries(allSeries, mousePoint).forEach(updateNearest); if (searchRadius <= 0) { return nearestHit; } // Sample 16 radial directions var directions = [ [1, 0], [-1, 0], [0, 1], [0, -1], [1, 1], [1, -1], [-1, 1], [-1, -1], [2, 1], [2, -1], [-2, 1], [-2, -1], [1, 2], [1, -2], [-1, 2], [-1, -2] ]; for (var radius = 1; radius <= searchRadius; radius++) { for (var _i = 0, directions_1 = directions; _i < directions_1.length; _i++) { var _a = directions_1[_i], dx = _a[0], dy = _a[1]; var length_1 = Math.sqrt(dx * dx + dy * dy); var point = new Point_1.Point(mousePoint.x + Math.round((dx / length_1) * radius), mousePoint.y + Math.round((dy / length_1) * radius)); this.hitTestAtPointAllSeries(allSeries, point).forEach(updateNearest); } } return nearestHit; }; SeriesSelectionModifier3D.prototype.hitTestAtPoint = function (allSeries, point) { return this.hitTestAtPointAllSeries(allSeries, point)[0]; }; SeriesSelectionModifier3D.prototype.hitTestAtPointAllSeries = function (allSeries, point) { return allSeries.map(function (rs) { return rs.hitTest(point); }).filter(function (ht) { return ht === null || ht === void 0 ? void 0 : ht.isHit; }); }; SeriesSelectionModifier3D.prototype.distanceSquaredToCamera = function (hitInfo) { var _a, _b; var cameraPosition = (_b = (_a = this.parentSurface) === null || _a === void 0 ? void 0 : _a.camera) === null || _b === void 0 ? void 0 : _b.position; var hitWorldCoords = hitInfo === null || hitInfo === void 0 ? void 0 : hitInfo.hitWorldCoords; if (!cameraPosition || !hitWorldCoords) { return Number.POSITIVE_INFINITY; } var dx = hitWorldCoords.x - cameraPosition.x; var dy = hitWorldCoords.y - cameraPosition.y; var dz = hitWorldCoords.z - cameraPosition.z; return dx * dx + dy * dy + dz * dz; }; SeriesSelectionModifier3D.prototype.getHitTestRadius = function (hitTestRadius) { if (hitTestRadius === undefined || !Number.isFinite(hitTestRadius)) { return 2; } return Math.max(0, Math.floor(hitTestRadius)); }; /** * This function called when the user sets series.isSelected = true elsewhere in code and we want to sync the modifier */ SeriesSelectionModifier3D.prototype.updateSeriesSelected = function (arg) { var _a; if (this.preventReentrancy) { return; } if (arg.isSelected) { if (!this.selectedSeries.includes(arg.sourceSeries)) { this.selectedSeries.push(arg.sourceSeries); } } else { this.selectedSeries = (0, array_1.arrayRemove)(this.selectedSeries, arg.sourceSeries); } (_a = this.selectionChanged) === null || _a === void 0 ? void 0 : _a.raiseEvent(new SelectionChangedArgs3D_1.SelectionChangedArgs3D(this, this.selectedSeries, this.getIncludedRenderableSeries(), undefined)); }; SeriesSelectionModifier3D.prototype.updateSeriesHovered = function (arg) { var _a; if (this.preventReentrancy) { return; } if (arg.hovered) { if (!this.hoveredSeries.includes(arg.sourceSeries)) { this.hoveredSeries.push(arg.sourceSeries); } } else { this.hoveredSeries = (0, array_1.arrayRemove)(this.hoveredSeries, arg.sourceSeries); } (_a = this.hoverChanged) === null || _a === void 0 ? void 0 : _a.raiseEvent(new HoveredChangedArgs3D_1.HoveredChangedArgs3D(this, this.hoveredSeries, this.getIncludedRenderableSeries(), undefined)); }; return SeriesSelectionModifier3D; }(ChartModifierBase3D_1.ChartModifierBase3D)); exports.SeriesSelectionModifier3D = SeriesSelectionModifier3D;