scichart
Version:
Fast WebGL JavaScript Charting Library and Framework
547 lines (546 loc) • 26.7 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 __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataPointSelectionModifier = exports.ESelectionMode = void 0;
var classFactory_1 = require("../../Builder/classFactory");
var Deleter_1 = require("../../Core/Deleter");
var EventHandler_1 = require("../../Core/EventHandler");
var Rect_1 = require("../../Core/Rect");
var BaseType_1 = require("../../types/BaseType");
var ChartModifierType_1 = require("../../types/ChartModifierType");
var pointUtil_1 = require("../../utils/pointUtil");
var translate_1 = require("../../utils/translate");
var IDataSeries_1 = require("../Model/IDataSeries");
var IPointMetadata_1 = require("../Model/IPointMetadata");
var BaseHitTestProvider_1 = require("../Visuals/RenderableSeries/HitTest/BaseHitTestProvider");
var RubberBandSvgRect_1 = require("../Visuals/RubberBandSvgRect/RubberBandSvgRect");
var ChartModifierBase2D_1 = require("./ChartModifierBase2D");
var constants_1 = require("./constants");
var DataPointInfo_1 = require("./DataPointInfo");
var DataPointSelectionChangedArgs_1 = require("./DataPointSelectionChangedArgs");
var RubberBandXyZoomModifier_1 = require("./RubberBandXyZoomModifier");
/**
* Defines constants which represents different selection modes of {@link DataPointSelectionModifier}
*/
var ESelectionMode;
(function (ESelectionMode) {
/**
* Points which the user selects are combined with previously selected points.
*/
ESelectionMode["Union"] = "Union";
/**
* Points which the user selects become selected, Exclusive-Or (XOR) the current selection
*/
ESelectionMode["Inverse"] = "Inverse";
/**
* Points which the user selects become selected. Previously collected points are cleared or replaced by these.
*/
ESelectionMode["Replace"] = "Replace";
})(ESelectionMode = exports.ESelectionMode || (exports.ESelectionMode = {}));
var DataPointSelectionModifier = /** @class */ (function (_super) {
__extends(DataPointSelectionModifier, _super);
/**
* Creates an instances of DataPointSelectionModifier
* @param options Optional parameters of type {@link IDataPointSelectionModifierOptions} used to configure the modifier
*/
function DataPointSelectionModifier(options) {
var _this = this;
var _a, _b, _c;
_this = _super.call(this, options) || this;
_this.type = ChartModifierType_1.EChart2DModifierType.DataPointSelection;
/**
* A selection-changed EventHandler. See {@link EventHandler} for how to subscribe to and be
* notified when any {@link IRenderableSeries | Series} is selected or unselected
*/
_this.selectionChanged = new EventHandler_1.EventHandler();
_this.includedSeriesMap = new Map();
_this.selectedDataPointsMap = new Map();
_this.selectionHasChanged = false;
_this.allowClickSelect = (_a = options === null || options === void 0 ? void 0 : options.allowClickSelect) !== null && _a !== void 0 ? _a : true;
_this.allowDragSelect = (_b = options === null || options === void 0 ? void 0 : options.allowDragSelect) !== null && _b !== void 0 ? _b : true;
if (options === null || options === void 0 ? void 0 : options.selectionStroke) {
_this.selectionStroke = options.selectionStroke;
}
if (options === null || options === void 0 ? void 0 : options.selectionFill) {
_this.selectionFill = options === null || options === void 0 ? void 0 : options.selectionFill;
}
_this.selectionStrokeThicknessProperty = (_c = options === null || options === void 0 ? void 0 : options.selectionStrokeThickness) !== null && _c !== void 0 ? _c : 1;
if (options === null || options === void 0 ? void 0 : options.onSelectionChanged) {
if (typeof options.onSelectionChanged === "string") {
_this.typeMap.set("onSelectionChanged", options.onSelectionChanged);
// @ts-ignore
_this.selectionChanged.subscribe((0, classFactory_1.getFunction)(BaseType_1.EBaseType.OptionFunction, options.onSelectionChanged));
}
else {
// @ts-ignore
_this.selectionChanged.subscribe(options.onSelectionChanged);
}
}
if (options === null || options === void 0 ? void 0 : options.getSelectionMode) {
if (typeof options.getSelectionMode === "string") {
_this.typeMap.set("getSelectionMode", options.getSelectionMode);
// @ts-ignore
_this.getSelectionMode = (0, classFactory_1.getFunction)(BaseType_1.EBaseType.OptionFunction, options.getSelectionMode);
}
else {
// @ts-ignore
_this.getSelectionMode = options.getSelectionMode;
}
}
return _this;
}
/**
* @inheritDoc
*/
DataPointSelectionModifier.prototype.applyTheme = function (themeProvider) {
if (!this.testPropertyChanged(constants_1.PROPERTY.SELECTION_FILL)) {
this.selectionFill = themeProvider.rubberBandFillBrush;
}
if (!this.testPropertyChanged(constants_1.PROPERTY.SELECTION_STROKE)) {
this.selectionStroke = themeProvider.rubberBandStrokeBrush;
}
};
/**
* @inheritDoc
*/
DataPointSelectionModifier.prototype.onAttach = function () {
var _this = this;
var _a, _b;
_super.prototype.onAttach.call(this);
this.selectionRect = new RubberBandSvgRect_1.RubberBandSvgRect((_a = this.parentSurface) === null || _a === void 0 ? void 0 : _a.domSvgContainer, this.selectionFill, this.selectionStroke, this.selectionStrokeThickness);
this.clearSelectedDataPoints();
(_b = this.getAllSeries()) === null || _b === void 0 ? void 0 : _b.forEach(function (rs) { return _this.onAttachSeries(rs); });
};
/**
* @inheritDoc
*/
DataPointSelectionModifier.prototype.onDetach = function () {
var _this = this;
var _a;
_super.prototype.onDetach.call(this);
this.selectionRect = (0, Deleter_1.deleteSafe)(this.selectionRect);
this.clearSelectedDataPoints();
(_a = this.getAllSeries()) === null || _a === void 0 ? void 0 : _a.forEach(function (rs) { return _this.onDetachSeries(rs); });
};
Object.defineProperty(DataPointSelectionModifier.prototype, "selectedDataPoints", {
/**
* An array of currently selected series which can be observed by subscribing to the {@link selectionChanged} {@link EventHandler | event handler}
* @remarks See documentation for how to subscribe to changes
*/
get: function () {
return Array.from(this.selectedDataPointsMap.values());
},
enumerable: false,
configurable: true
});
/**
* @inheritDoc
*/
DataPointSelectionModifier.prototype.onAttachSeries = function (rs) {
_super.prototype.onAttachSeries.call(this, rs);
if (!rs.dataSeries ||
// TODO: Heatmap series not supported
rs.dataSeries.type === IDataSeries_1.EDataSeriesType.HeatmapUniform) {
return;
}
// Build selected points list from attached series
var baseDataSeries = rs.dataSeries;
// Add metadata to series
if (!baseDataSeries.hasMetadataGenerator()) {
baseDataSeries.setMetadataGenerator(new IPointMetadata_1.TemplateMetadataGenerator({ isSelected: false }));
}
for (var i = 0; i < baseDataSeries.getMetadataLength(); i++) {
var metadata = baseDataSeries.getMetadataAt(i);
if (metadata === null || metadata === void 0 ? void 0 : metadata.isSelected) {
var dataPoint = new DataPointInfo_1.DataPointInfo(rs, metadata, i);
this.addSelectedDataPoint(rs, i, dataPoint);
}
}
this.raiseSelectionChanged(false);
};
/**
* @inheritDoc
*/
DataPointSelectionModifier.prototype.onDetachSeries = function (rs) {
_super.prototype.onDetachSeries.call(this, rs);
if (!rs.dataSeries) {
return;
}
// Remove selected points from selected points list
this.removeSelectedDataPointsForSeries(rs);
this.raiseSelectionChanged(false);
};
/**
* @inheritDoc
*/
DataPointSelectionModifier.prototype.modifierMouseDown = function (args) {
_super.prototype.modifierMouseDown.call(this, args);
if (this.executeOn !== args.button) {
return;
}
if (!this.isAttached) {
throw new Error("Should not call DataPointSelectionModifier.modifierMouseDown if not attached");
}
// Point coordinates relative to series view rectangle.
var translatedPoint = (0, translate_1.translateFromCanvasToSeriesViewRect)(args.mousePoint, this.parentSurface.seriesViewRect);
if (!translatedPoint) {
return;
}
this.startPoint = translatedPoint;
this.isClicked = true;
args.handled = true;
};
/**
* @inheritDoc
*/
DataPointSelectionModifier.prototype.modifierMouseMove = function (args) {
_super.prototype.modifierMouseMove.call(this, args);
var seriesViewRect = this.parentSurface.seriesViewRect;
if (this.isClicked) {
this.endPoint = (0, translate_1.translateFromCanvasToSeriesViewRect)(Rect_1.Rect.clipPointToRect(args.mousePoint, seriesViewRect), seriesViewRect);
var _a = (0, RubberBandXyZoomModifier_1.getRubberBandRect)(this.startPoint, this.endPoint, this.xyDirection, this.parentSurface.seriesViewRect), x = _a.x, right = _a.right, y = _a.y, bottom = _a.bottom;
this.selectionRect.isHidden = !this.allowDragSelect;
this.selectionRect.x1 = (0, translate_1.translateToNotScaled)(x);
this.selectionRect.x2 = (0, translate_1.translateToNotScaled)(right);
this.selectionRect.y1 = (0, translate_1.translateToNotScaled)(y);
this.selectionRect.y2 = (0, translate_1.translateToNotScaled)(bottom);
}
};
/**
* @inheritDoc
*/
DataPointSelectionModifier.prototype.modifierMouseUp = function (args) {
_super.prototype.modifierMouseUp.call(this, args);
if (this.executeOn !== args.button) {
return;
}
if (!this.isAttached) {
throw new Error("Should not call DataPointSelectionModifier.modifierMouseUp if not attached");
}
if (this.isClicked) {
var seriesViewRect = this.parentSurface.seriesViewRect;
this.endPoint = (0, translate_1.translateFromCanvasToSeriesViewRect)(Rect_1.Rect.clipPointToRect(args.mousePoint, seriesViewRect), seriesViewRect);
var rect = (0, RubberBandXyZoomModifier_1.getRubberBandRect)(this.startPoint, this.endPoint, this.xyDirection, this.parentSurface.seriesViewRect);
var isAreaSelection = rect.width >= 3 && rect.height >= 3 && this.allowDragSelect;
var selectionMode = this.getSelectionMode(args, isAreaSelection);
// Process selection
if (isAreaSelection && this.allowDragSelect) {
this.selectManyPoints(rect, selectionMode);
}
else if (this.allowClickSelect) {
this.selectSinglePoint(args.mousePoint, selectionMode);
}
this.isClicked = false;
this.selectionRect.isHidden = true;
}
};
Object.defineProperty(DataPointSelectionModifier.prototype, "selectionStrokeThickness", {
/**
* Gets or sets the strokeThickness of the selection rect when the user drags on the chart
*/
get: function () {
return this.selectionStrokeThicknessProperty;
},
/**
* Gets or sets the strokeThickness of the selection rect when the user drags on the chart
*/
set: function (selectionStrokeThickness) {
this.selectionStrokeThicknessProperty = selectionStrokeThickness;
this.notifyPropertyChanged(constants_1.PROPERTY.SELECTION_STROKE_THICKNESS);
},
enumerable: false,
configurable: true
});
Object.defineProperty(DataPointSelectionModifier.prototype, "selectionStroke", {
/**
* Gets or sets the stroke of the selection rect when the user drags on the chart
*/
get: function () {
return this.selectionStrokeProperty;
},
/**
* Gets or sets the stroke of the selection rect when the user drags on the chart
*/
set: function (selectionStroke) {
this.selectionStrokeProperty = selectionStroke;
this.notifyPropertyChanged(constants_1.PROPERTY.SELECTION_STROKE);
},
enumerable: false,
configurable: true
});
Object.defineProperty(DataPointSelectionModifier.prototype, "selectionFill", {
/**
* Gets or sets the fill of the selection rect when the user drags on the chart
*/
get: function () {
return this.selectionFillProperty;
},
/**
* Gets or sets the fill of the selection rect when the user drags on the chart
*/
set: function (selectionFill) {
this.selectionFillProperty = selectionFill;
this.notifyPropertyChanged(constants_1.PROPERTY.SELECTION_FILL);
},
enumerable: false,
configurable: true
});
/**
* @inheritDoc
*/
DataPointSelectionModifier.prototype.getIncludedRenderableSeries = function () {
var _this = this;
return this.getAllSeries().filter(function (rs) { return _this.includedSeriesMap.get(rs) !== false; });
};
/**
* @inheritDoc
*/
DataPointSelectionModifier.prototype.includeSeries = function (series, isIncluded) {
if (!isIncluded) {
this.includedSeriesMap.set(series, isIncluded);
}
if (isIncluded) {
this.includedSeriesMap.delete(series);
}
};
Object.defineProperty(DataPointSelectionModifier.prototype, "includedSeries", {
/**
* Used internally for tests. Gets a Map of included series
* @remarks Series include flag set to false means excluded. Series not present or flag=true means included
*/
get: function () {
return this.includedSeriesMap;
},
enumerable: false,
configurable: true
});
/**
* Gets the current {@link ESelectionMode} to use - e.g. Union, Replace - depending on {@link TModifierKeys}
* and if the selection is area selection or not. This function can be overridden by the
* {@link IDataPointSelectionModifierOptions.getSelectionMode}
* @remarks Default behaviour is {@link ESelectionMode.Replace}, or {@link ESelectionMode.Union} when CTRL pressed,
* or {@link ESelectionMode.Inverse} when Shift pressed
* @param modifierKeys The {@link TModifierKeys} e.g. if Ctrl, Shift or Alt are pressed
* @param isAreaSelection When true, the user has selected a rectangle or area, not clicked a single point
* @protected
*/
DataPointSelectionModifier.prototype.getSelectionMode = function (modifierKeys, isAreaSelection) {
if (modifierKeys.ctrlKey) {
// Union when area selection and CTRL else Inverse
return ESelectionMode.Union;
}
else if (modifierKeys.shiftKey) {
// When shift Inverse
return ESelectionMode.Inverse;
}
// Default mode is Replace
return ESelectionMode.Replace;
};
DataPointSelectionModifier.prototype.toJSON = function () {
var json = _super.prototype.toJSON.call(this);
var options = {
allowClickSelect: this.allowClickSelect,
allowDragSelect: this.allowDragSelect,
getSelectionMode: this.typeMap.get("getSelectionMode"),
onSelectionChanged: this.typeMap.get("onSelectionChanged"),
selectionFill: this.selectionFill,
selectionStroke: this.selectionStroke,
selectionStrokeThickness: this.selectionStrokeThickness
};
Object.assign(json.options, options);
return json;
};
DataPointSelectionModifier.prototype.delete = function () {
this.selectionChanged.unsubscribeAll();
this.selectionRect = (0, Deleter_1.deleteSafe)(this.selectionRect);
_super.prototype.delete.call(this);
};
/**
* Selects all points inside the {@link Rect}, according to the {@link ESelectionMode} passed in
* @param rect
* @param selectionMode
* @protected
*/
DataPointSelectionModifier.prototype.selectManyPoints = function (rect, selectionMode) {
var _this = this;
if (this.parentSurface) {
var multiSelect = selectionMode !== ESelectionMode.Replace;
if (!multiSelect) {
this.deselectAllPoints(false);
}
// Perform an area selection on all series
this.getIncludedRenderableSeries()
.filter(function (rs) { return rs.isVisible && rs.dataSeries; })
.forEach(function (rs, index) {
var xCalc = rs.xAxis.getCurrentCoordinateCalculator();
var yCalc = rs.yAxis.getCurrentCoordinateCalculator();
// Find the bounds of the data inside the rectangle
var leftXData, rightXData;
if (xCalc.getDataValue(rect.left) <= xCalc.getDataValue(rect.right)) {
leftXData = xCalc.getDataValue(rect.left);
rightXData = xCalc.getDataValue(rect.right);
}
else {
leftXData = xCalc.getDataValue(rect.right);
rightXData = xCalc.getDataValue(rect.left);
}
var bottomYData, topYData;
if (yCalc.getDataValue(rect.top) <= yCalc.getDataValue(rect.bottom)) {
bottomYData = yCalc.getDataValue(rect.top);
topYData = yCalc.getDataValue(rect.bottom);
}
else {
bottomYData = yCalc.getDataValue(rect.bottom);
topYData = yCalc.getDataValue(rect.top);
}
if (rs.dataSeries.type === IDataSeries_1.EDataSeriesType.HeatmapUniform) {
// TODO: Heatmap series
}
else {
var baseDataSeries = rs.dataSeries;
for (var i = 0; i < baseDataSeries.count(); i++) {
var x = baseDataSeries.getNativeXValues().get(i);
var y = baseDataSeries.getNativeYValues().get(i);
if ((0, pointUtil_1.testIsInBounds)(x, y, leftXData, topYData, rightXData, bottomYData)) {
var metadata = baseDataSeries.getMetadataAt(i);
if (selectionMode !== ESelectionMode.Inverse) {
metadata.isSelected = true;
_this.addSelectedDataPoint(rs, i, new DataPointInfo_1.DataPointInfo(rs, metadata, i));
}
else {
if (metadata.isSelected) {
metadata.isSelected = false;
_this.removeSelectedDataPoint(rs, i);
}
else {
metadata.isSelected = true;
_this.addSelectedDataPoint(rs, i, new DataPointInfo_1.DataPointInfo(rs, metadata, i));
}
}
}
}
}
});
this.raiseSelectionChanged(true);
}
};
/**
* Performs selection of a single point with the desired {@link ESelectionMode}
* @param point
* @param selectionMode
* @protected
*/
DataPointSelectionModifier.prototype.selectSinglePoint = function (point, selectionMode) {
var _this = this;
if (this.parentSurface) {
// Perform hit-test at the x-y point
var hitTestResults = this.getIncludedRenderableSeries()
.filter(function (rs) { return rs.isVisible && rs.dataSeries; }) // todo && included series
.map(function (rs) { return ({
renderableSeries: rs,
hitTestInfo: rs.hitTestProvider.hitTestForDataPointSelectionModifier(point.x, point.y, BaseHitTestProvider_1.BaseHitTestProvider.DEFAULT_HIT_TEST_RADIUS)
}); });
var multiSelect = selectionMode !== ESelectionMode.Replace;
if (!multiSelect) {
this.deselectAllPoints(false);
}
hitTestResults.forEach(function (htResult) {
var ht = htResult.hitTestInfo;
var rs = htResult.renderableSeries;
if (!ht.isHit) {
return;
}
// Metadata can't be auto-created if the renderableSeries is added to the surface without a dataSeries
if (!ht.metadata) {
console.warn("Cannot select datapoint for series ".concat(ht.dataSeriesName, " as it does not have metadata. To solve this either:\nConfigure metadata when you create the series or when you add data eg renderableSeries.dataSeries = new XyDataSeries(wasmContext, { metadata: { isSelected: false }}), or:\nAdd the DataPointSelectionModifier after all series have had their dataSeries set."));
return;
}
if (selectionMode === ESelectionMode.Union) {
// Always select in union
ht.metadata.isSelected = true;
var newDataPointInfo = new DataPointInfo_1.DataPointInfo(ht.associatedSeries, ht.metadata, ht.dataSeriesIndex);
_this.addSelectedDataPoint(ht.associatedSeries, ht.dataSeriesIndex, newDataPointInfo);
}
else {
// Toggle selection
ht.metadata.isSelected = !ht.metadata.isSelected;
if (ht.metadata.isSelected) {
_this.addSelectedDataPoint(rs, ht.dataSeriesIndex, new DataPointInfo_1.DataPointInfo(ht.associatedSeries, ht.metadata, ht.dataSeriesIndex));
}
else {
_this.removeSelectedDataPoint(rs, ht.dataSeriesIndex);
}
}
});
this.raiseSelectionChanged(true);
}
};
/**
* Deselects all points
* @param invalidate When true (default=true) raise {@link selectionChanged} event and redraw the parent {@link SciChartSurface}
* @protected
*/
DataPointSelectionModifier.prototype.deselectAllPoints = function (invalidate) {
if (invalidate === void 0) { invalidate = true; }
// Deselect all datapoints
this.selectedDataPoints.forEach(function (dp) {
if (dp.renderableSeries && dp.metadata) {
dp.metadata.isSelected = false;
}
});
this.clearSelectedDataPoints();
if (invalidate) {
this.raiseSelectionChanged(true);
}
};
DataPointSelectionModifier.prototype.addSelectedDataPoint = function (rs, index, value) {
this.selectedDataPointsMap.set(getKey(rs, index), value);
this.selectionHasChanged = true;
};
DataPointSelectionModifier.prototype.removeSelectedDataPoint = function (rs, index) {
this.selectedDataPointsMap.delete(getKey(rs, index));
this.selectionHasChanged = true;
};
DataPointSelectionModifier.prototype.clearSelectedDataPoints = function () {
if (this.selectedDataPointsMap.size > 0) {
this.selectionHasChanged = true;
}
this.selectedDataPointsMap.clear();
};
DataPointSelectionModifier.prototype.removeSelectedDataPointsForSeries = function (rs) {
var _this = this;
this.selectedDataPointsMap.forEach(function (dp, key) {
if (dp.renderableSeries === rs) {
_this.selectedDataPointsMap.delete(key);
_this.selectionHasChanged = true;
}
});
};
DataPointSelectionModifier.prototype.raiseSelectionChanged = function (invalidate) {
var _a;
if (this.selectionHasChanged) {
this.selectionChanged.raiseEvent(new DataPointSelectionChangedArgs_1.DataPointSelectionChangedArgs(this, this.selectedDataPoints));
this.selectionHasChanged = false;
if (invalidate) {
(_a = this.parentSurface) === null || _a === void 0 ? void 0 : _a.invalidateElement();
}
}
};
return DataPointSelectionModifier;
}(ChartModifierBase2D_1.ChartModifierBase2D));
exports.DataPointSelectionModifier = DataPointSelectionModifier;
var getKey = function (rs, index) { return "".concat(rs.id, "_").concat(index); };