scichart
Version:
Fast WebGL JavaScript Charting Library and Framework
411 lines (410 loc) • 20.8 kB
JavaScript
"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;