scichart
Version:
Fast WebGL JavaScript Charting Library and Framework
626 lines (625 loc) • 26.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseHeatmapDataSeries = void 0;
var classFactory_1 = require("../../Builder/classFactory");
var Deleter_1 = require("../../Core/Deleter");
var EventHandler_1 = require("../../Core/EventHandler");
var Guard_1 = require("../../Core/Guard");
var NumberRange_1 = require("../../Core/NumberRange");
var BaseType_1 = require("../../types/BaseType");
var appendDoubleVectorFromJsArray_1 = require("../../utils/ccall/appendDoubleVectorFromJsArray");
var guid_1 = require("../../utils/guid");
var isRealNumber_1 = require("../../utils/isRealNumber");
var IDataSeries_1 = require("./IDataSeries");
var IPointMetadata_1 = require("./IPointMetadata");
/**
* The base class for Heatmap-style DataSeries in SciChart's
* {@link https://www.scichart.com/javascript-chart-features | JavaScript Charts}
* @remarks
* A DataSeries stores the data to render. This is independent from the {@link IRenderableSeries | RenderableSeries}
* which defines how that data should be rendered.
*
* See derived types of {@link BaseHeatmapDataSeries} to find out what Heatmap data-series are available.
* See {@link UniformHeatmapRenderableSeries} to see the class for rendering a 2D JavaScript Heatmap Chart.
*/
var BaseHeatmapDataSeries = /** @class */ (function () {
/**
* Creates an instance of {@link BaseHeatmapDataSeries}
* @param webAssemblyContext the {@link TSciChart | SciChart WebAssembly Context} containing native methods
* and access to our underlying WebGL2 rendering engine
* @param options the {@link IBaseHeatmapSeriesOptions} which can be passed to configure the DataSeries at construct time
*/
function BaseHeatmapDataSeries(webAssemblyContext, options) {
var _a, _b, _c, _d;
/** @inheritDoc */
this.dataChanged = new EventHandler_1.EventHandler();
/** @inheritDoc */
this.minXSpacing = 0;
/**
* Gets the width of the 2-dimensional array of {@link getZValues | Z-Values} where array is ranked [width][height]
*/
this.arrayWidth = 0;
/**
* Gets the height of the 2-dimensional array of {@link getZValues | Z-Values} where array is ranked [width][height]
*/
this.arrayHeight = 0;
/**
* When true, the {@link BaseHeatmapDataSeries} has data changes and requires redrawing
*/
this.hasDataChangesProperty = false;
this.hasNaNsProperty = false;
this.lastZMin = -1;
this.lastZMax = -1;
this.lastFillValuesOutOfRange = undefined;
this.metadataGeneratorProperty = undefined;
this.changeCountProperty = 0;
this.webAssemblyContext = webAssemblyContext;
this.id = (_a = options === null || options === void 0 ? void 0 : options.id) !== null && _a !== void 0 ? _a : (0, guid_1.generateGuid)();
this.dataSeriesNameProperty = (_b = options === null || options === void 0 ? void 0 : options.dataSeriesName) !== null && _b !== void 0 ? _b : this.dataSeriesNameProperty;
this.hasNaNsProperty = (_c = options === null || options === void 0 ? void 0 : options.containsNaN) !== null && _c !== void 0 ? _c : this.hasNaNsProperty;
this.normalizedVector = new this.webAssemblyContext.SCRTFloatVector();
if (options === null || options === void 0 ? void 0 : options.metadata) {
if ("type" in options.metadata) {
this.metadataGeneratorProperty = (0, classFactory_1.createType)(BaseType_1.EBaseType.MetadataGenerator, options.metadata.type, webAssemblyContext, options.metadata.data);
options.metadata = (_d = this.metadataGeneratorProperty) === null || _d === void 0 ? void 0 : _d.getMetadata();
}
else if (!Array.isArray(options === null || options === void 0 ? void 0 : options.metadata)) {
this.metadataGeneratorProperty = new IPointMetadata_1.TemplateMetadataGenerator(options === null || options === void 0 ? void 0 : options.metadata);
options.metadata = undefined;
}
}
// Copy zValues
this.setZValues(options === null || options === void 0 ? void 0 : options.zValues, options === null || options === void 0 ? void 0 : options.metadata);
}
Object.defineProperty(BaseHeatmapDataSeries.prototype, "isSorted", {
/** @inheritDoc */
get: function () {
return true;
},
/** @inheritDoc */
set: function (value) {
throw new Error("setting isSorted on a heatmap series is not supported");
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "isEvenlySpaced", {
/** @inheritDoc */
get: function () {
return true;
},
/** @inheritDoc */
set: function (value) {
throw new Error("setting isEvenlySpaced on a heatmap series is not supported");
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "containsNaN", {
/** @inheritDoc */
get: function () {
return false;
},
/** @inheritDoc */
set: function (value) {
throw new Error("setting containsNaN is not supported on BaseHeatmapDataSeries");
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "dataDistributionCalculator", {
/** @inheritDoc */
get: function () {
throw new Error("dataDistributionCalculator is not supported on BaseHeatmapDataSeries");
},
enumerable: false,
configurable: true
});
/** @inheritDoc */
BaseHeatmapDataSeries.prototype.getNativeValue = function (values, index) {
throw new Error("getNativeValue not supported for HeatmapDataSeries");
};
Object.defineProperty(BaseHeatmapDataSeries.prototype, "hasDataChanges", {
/**
* Returns true if the Heatmap DataSeries has data changes.
* This flag is set to true when notifyDataChanged is called, and reset to false after
*/
get: function () {
return this.hasDataChangesProperty;
},
enumerable: false,
configurable: true
});
/**
* Gets a readonly collection of Z-values which can be read in the format zValues[y][x]
* Note that changes or manipulation of the 2D array will not update the Heatmap. Set it back via setZValues()
* to see changes to the chart
*/
BaseHeatmapDataSeries.prototype.getZValues = function () {
return this.zValuesProperty;
};
/**
* Sets a 2D array of zValues. Input is in the format zValues[y][x] where Y is 0 to height and X is 0 to Width
* @param zValues
* @param metadata The array of arrays of point metadata
*/
BaseHeatmapDataSeries.prototype.setZValues = function (zValues, metadata) {
if (!zValues || !zValues[0]) {
this.zValuesProperty = undefined;
this.arrayHeight = 0;
this.arrayWidth = 0;
this.size = 0;
return;
}
var firstRowLength = zValues[0].length;
zValues.forEach(function (zRow) {
if (!zRow) {
throw new Error("Each row in zValues must be defined. See how to declare a 2D array in Javascript here https://stackoverflow.com/a/966234/303612");
}
if (zRow.length !== firstRowLength) {
throw new Error("Each row in zValues must be the same length, so that the overall 2D array is square");
}
});
if (metadata) {
guardSameLengthZValuesAndMetadata(zValues, metadata);
}
var w = zValues[0].length;
var h = zValues.length;
// const metadata2 = fillMetadata(w, h, metadata);
this.arrayWidth = w;
this.arrayHeight = h;
this.size = w * h;
this.zValuesProperty = zValues;
this.setMetadata(metadata);
this.notifyDataChanged(IDataSeries_1.EDataChangeType.Append);
};
/**
* Gets the ZValue at the specific Y,X index where Y must be within 0-arrayHeight and X must be within 0-arrayWidth
* @param yIndex the y-index from 0 to arrayHeight
* @param xIndex the x-index from 0 to arrayWidth
*/
BaseHeatmapDataSeries.prototype.getZValue = function (yIndex, xIndex) {
return this.zValuesProperty[yIndex][xIndex];
};
/**
* Sets the ZValue at the specific Y,X index where Y must be within 0-arrayHeight and X must be within 0-arrayWidth
* @param yIndex the y-index from 0 to arrayHeight
* @param xIndex the x-index from 0 to arrayWidth
* @param zValue the new Z-value
* @param metadata The point metadata
*/
BaseHeatmapDataSeries.prototype.setZValue = function (yIndex, xIndex, zValue, metadata) {
this.zValuesProperty[yIndex][xIndex] = zValue;
this.setMetadataAt(yIndex, xIndex, metadata);
this.notifyDataChanged(IDataSeries_1.EDataChangeType.Update, xIndex, yIndex);
};
/** @inheritDoc */
BaseHeatmapDataSeries.prototype.clear = function () {
if (!this.getIsDeleted()) {
this.setZValues(undefined);
this.setMetadata(undefined);
this.notifyDataChanged(IDataSeries_1.EDataChangeType.Clear, null, null);
}
};
Object.defineProperty(BaseHeatmapDataSeries.prototype, "xMin", {
/**
* Gets the minimum X-value for this heatmap, which controls where it is displayed on a cartesian chart
*/
get: function () {
return this.xRange.min;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "xMax", {
/**
* Gets the maximum X-value for this heatmap, which controls where it is displayed on a cartesian chart
*/
get: function () {
return this.xRange.max;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "yMin", {
/**
* Gets the minimum Y-value for this heatmap, which controls where it is displayed on a cartesian chart
*/
get: function () {
return this.yRange.min;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "yMax", {
/**
* Gets the maximum Y-value for this heatmap, which controls where it is displayed on a cartesian chart
*/
get: function () {
return this.yRange.max;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "zMin", {
/**
* Computes the minimum Z-value for this heatmap
* @remarks
* Be aware for performance reasons, every call to zMin will result in a recalculation
*/
get: function () {
return this.zRange.min;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "zMax", {
/**
* Computes the maximum Z-value for this heatmap
* @remarks
* Be aware for performance reasons, every call to zMax will result in a recalculation
*/
get: function () {
return this.zRange.max;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "xRange", {
/**
* Gets the XRange for this heatmap, which controls where it is displayed on a cartesian chart
*/
get: function () {
return this.getXRange();
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "yRange", {
/**
* Gets the YRange for this heatmap, which controls where it is displayed on a cartesian chart
*/
get: function () {
return this.getYRange();
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "zRange", {
/**
* Computes the ZRange for this heatmap, which controls where it is displayed on a cartesian chart
* @remarks
* Be aware for performance reasons, every call to zRange will result in a recalculation
*/
get: function () {
return this.getZRange();
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "dataSeriesName", {
/**
* @inheritDoc
*/
get: function () {
return this.dataSeriesNameProperty;
},
/**
* @inheritDoc
*/
set: function (dataSeriesName) {
this.dataSeriesNameProperty = dataSeriesName;
this.notifyDataChanged(IDataSeries_1.EDataChangeType.Property, null, null, "dataSeriesName");
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "hasValues", {
/**
* Gets whether this Heatmap has values to display
*/
get: function () {
return this.arrayWidth > 0 && this.arrayHeight > 0;
},
enumerable: false,
configurable: true
});
Object.defineProperty(BaseHeatmapDataSeries.prototype, "hasNaNs", {
/**
* Gets/sets whether this Heatmap has NaN value, to display them as transparent tiles
*/
get: function () {
return this.hasNaNsProperty;
},
/**
* Gets/sets whether this Heatmap has NaN value, to display them as transparent tiles
*/
set: function (value) {
this.hasNaNsProperty = value;
this.notifyDataChanged(IDataSeries_1.EDataChangeType.Property, null, null, "hasNaNs");
},
enumerable: false,
configurable: true
});
/**
* Gets the number of heatmap cells
*/
BaseHeatmapDataSeries.prototype.count = function () {
return this.arrayWidth * this.arrayHeight;
};
/**
* Sets a function that will be used to generate metadata for values when they are appended/inserted, if no explicit metadata is supplied.
* @param generator
*/
BaseHeatmapDataSeries.prototype.setMetadataGenerator = function (generator) {
var shouldGenerate = typeof this.metadataGeneratorProperty === "undefined";
this.metadataGeneratorProperty = generator;
if (shouldGenerate) {
this.setMetadata(null);
}
};
/**
* @inheritDoc
*/
BaseHeatmapDataSeries.prototype.delete = function () {
this.isDeleted = true;
this.normalizedVector = (0, Deleter_1.deleteSafe)(this.normalizedVector);
};
/**
* @inheritDoc
*/
BaseHeatmapDataSeries.prototype.getIsDeleted = function () {
return this.isDeleted;
};
/**
* @inheritDoc
*/
BaseHeatmapDataSeries.prototype.getNativeIndexes = function () {
throw new Error("getNativeIndexes is invalid for heatmap type series. Try getting or setting zValues instead");
};
/**
* @inheritDoc
*/
BaseHeatmapDataSeries.prototype.getNativeXValues = function () {
throw new Error("getNativeXValues is invalid for heatmap type series. Try getting or setting zValues instead");
};
/**
* @inheritDoc
*/
BaseHeatmapDataSeries.prototype.getNativeYValues = function () {
throw new Error("getNativeYValues is invalid for heatmap type series. Try getting or setting zValues instead");
};
/**
* @inheritDoc
*/
BaseHeatmapDataSeries.prototype.getWindowedYRange = function (xRange, getPositiveRange, isCategoryAxis) {
return this.yRange;
};
/**
* Notify subscribers to dataChanged that data has changed. Also sets internal flags.
* This will trigger a redraw on a parent SciChartSurface
*/
BaseHeatmapDataSeries.prototype.notifyDataChanged = function (changeType, xIndex, yIndex, name) {
this.changeCountProperty++;
this.hasDataChangesProperty = true;
this.dataChanged.raiseEvent({ changeType: changeType, index: xIndex, yIndex: yIndex, name: name });
};
/**
* Returns a FloatVector with normalized values based on the color map passed in
* @param colorMap the {@link IColorMapParams} provides properties used to map heatmap Z-values into colors
* for rendering in SciChart's {@link https://www.scichart.com/javascript-chart-features | Realtime JavaScript Charts}
*/
BaseHeatmapDataSeries.prototype.getNormalizedVector = function (colorMap, fillValuesOutOfRange) {
Guard_1.Guard.notNull(colorMap, "colorMap");
Guard_1.Guard.argumentIsRealNumber(colorMap.minimum, "colorMap.minimum");
Guard_1.Guard.argumentIsRealNumber(colorMap.maximum, "colorMap.maximum");
var size = this.arrayWidth * this.arrayHeight;
if (this.hasDataChangesProperty ||
size !== this.normalizedVector.size() ||
colorMap.minimum !== this.lastZMin ||
colorMap.maximum !== this.lastZMax ||
fillValuesOutOfRange !== this.lastFillValuesOutOfRange) {
this.recreateNormalizedVector(colorMap.minimum, colorMap.maximum, fillValuesOutOfRange);
this.lastZMin = colorMap.minimum;
this.lastZMax = colorMap.maximum;
this.lastFillValuesOutOfRange = fillValuesOutOfRange;
this.hasDataChangesProperty = false;
}
return this.normalizedVector;
};
/**
* Recreates the normalized vector (internally used for drawing heatmap) according to zMin and zMax values
* @param zMin
* @param zMax
*/
BaseHeatmapDataSeries.prototype.recreateNormalizedVector = function (zMin, zMax, fillValuesOutOfRange) {
var size = this.arrayWidth * this.arrayHeight;
this.normalizedVector.clear();
this.normalizedVector.resizeFast(size);
// We need to offset newMinValue which is being used for NaN values
// for the double distance of the texture, which is 256
var newZMin = this.hasNaNs ? zMin - (zMax - zMin) / 128 : zMin;
if (!(0, isRealNumber_1.isRealNumber)(newZMin)) {
throw Error("Can not create newZMin for try to use different zMin and zMax values");
}
var index = 0;
var normalizationFactor = 1.0 / (zMax - newZMin);
var rowArray = new Float32Array(this.arrayWidth);
for (var y = 0; y < this.arrayHeight; y++) {
for (var x = 0; x < this.arrayWidth; x++) {
// normalized value from 0..1 = (zValue - zMin) / ((zMax - zMin))
var zValueRaw = this.zValuesProperty[y][x];
var zValue = zValueRaw - newZMin;
// if value !== value is a simple but fast isNaN check
// equivalent to isNaN(zValueRaw)
if (zValueRaw !== zValueRaw) {
zValue = 0;
}
else if (zValue < zMin - newZMin) {
zValue = fillValuesOutOfRange ? zMin - newZMin : 0;
}
else if (zValue > zMax - newZMin) {
zValue = fillValuesOutOfRange ? zMax - newZMin : 0;
}
var normalizedZValue = zValue * normalizationFactor;
rowArray[x] = normalizedZValue;
}
(0, appendDoubleVectorFromJsArray_1.memCopyFloat32)(this.webAssemblyContext, rowArray, this.normalizedVector, index);
index += this.arrayWidth;
}
};
/**
* Gets the metadata by Y and X indexes
* @param yIndex The Y index
* @param xIndex The X index
*/
BaseHeatmapDataSeries.prototype.getMetadataAt = function (yIndex, xIndex) {
this.validateIndexes(yIndex, xIndex);
if (!this.metadataProperty) {
return undefined;
}
return this.metadataProperty[yIndex][xIndex];
};
/**
* Gets the metadata matrix height
*/
BaseHeatmapDataSeries.prototype.getMetadataHeight = function () {
if (!this.metadataProperty) {
return this.arrayHeight;
}
return this.metadataProperty.length;
};
/**
* Gets the metadata matrix width
*/
BaseHeatmapDataSeries.prototype.getMetadataWidth = function () {
if (!this.metadataProperty) {
return this.arrayWidth;
}
return this.metadataProperty[0].length;
};
BaseHeatmapDataSeries.prototype.toJSON = function (excludeData) {
if (excludeData === void 0) { excludeData = false; }
var options = this.getOptions(excludeData);
return { type: this.type, options: options };
};
/** @inheritDoc */
BaseHeatmapDataSeries.prototype.getIndicesRange = function (visibleRange, isCategoryData, downSearchMode, upSearchMode) {
return undefined;
};
Object.defineProperty(BaseHeatmapDataSeries.prototype, "changeCount", {
/** @inheritDoc */
get: function () {
return this.changeCountProperty;
},
enumerable: false,
configurable: true
});
BaseHeatmapDataSeries.prototype.getOptions = function (excludeData) {
if (excludeData === void 0) { excludeData = false; }
var options = {
id: this.id,
containsNaN: this.hasNaNs,
dataSeriesName: this.dataSeriesName,
zValues: excludeData ? undefined : this.zValuesProperty,
metadata: this.metadataGeneratorProperty
? this.metadataGeneratorProperty.toJSON()
: excludeData
? undefined
: this.metadataProperty
};
return options;
};
/**
* Computes the range in the Z-direction for this DataSeries
* @remarks
* Be aware for performance reasons, every call to getZRange will result in a recalculation
* @protected
*/
BaseHeatmapDataSeries.prototype.getZRange = function () {
var zValues = this.getZValues();
if (zValues) {
var zMin = Number.MAX_VALUE;
var zMax = -Number.MAX_VALUE;
for (var y = 0; y < this.arrayHeight; ++y) {
for (var x = 0; x < this.arrayWidth; x++) {
var zValue = zValues[y][x];
if (zValue < zMin)
zMin = zValue;
if (zValue > zMax)
zMax = zValue;
}
}
return new NumberRange_1.NumberRange(zMin, zMax);
}
return undefined;
};
BaseHeatmapDataSeries.prototype.validateIndexes = function (yIndex, xIndex) {
if (Math.round(yIndex) !== yIndex) {
throw Error("yIndex must be an integer");
}
if (Math.round(xIndex) !== xIndex) {
throw Error("xIndex must be an integer");
}
if (yIndex < 0 || yIndex >= this.arrayHeight) {
throw new Error("yIndex is out of range");
}
if (xIndex < 0 || xIndex >= this.arrayWidth) {
throw new Error("xIndex is out of range");
}
};
BaseHeatmapDataSeries.prototype.setMetadata = function (metadata) {
if (!metadata && this.metadataGeneratorProperty) {
metadata = [];
for (var h = 0; h < this.arrayHeight; h++) {
metadata[h] = [];
for (var w = 0; w < this.arrayWidth; w++) {
metadata[h][w] = this.metadataGeneratorProperty.getSingleMetadata();
}
}
}
this.metadataProperty = metadata;
};
BaseHeatmapDataSeries.prototype.setMetadataAt = function (yIndex, xIndex, metadata) {
if (!metadata) {
return;
}
this.validateIndexes(yIndex, xIndex);
this.fillMetadataIfUndefined();
this.metadataProperty[yIndex][xIndex] = metadata;
};
BaseHeatmapDataSeries.prototype.fillMetadataIfUndefined = function () {
if (this.metadataProperty === undefined) {
var metadata = Array(this.arrayHeight).fill(undefined);
for (var i = 0; i < this.arrayHeight; i++) {
metadata[i] = Array(this.arrayWidth).fill(undefined);
}
}
};
return BaseHeatmapDataSeries;
}());
exports.BaseHeatmapDataSeries = BaseHeatmapDataSeries;
// @ignore
var guardSameLengthZValuesAndMetadata = function (zValues, metadata) {
Guard_1.Guard.arraysSameLengthArr([
{ arg: zValues, name: "zValues" },
{ arg: metadata, name: "metadata" }
]);
for (var i = 0; i < zValues.length; i++) {
Guard_1.Guard.arraysSameLengthArr([
{ arg: zValues[i], name: "zValues[".concat(i, "]") },
{ arg: metadata[i], name: "metadata[".concat(i, "]") }
]);
}
};
// @ignore
var fillMetadata = function (width, height, metadata) {
if (metadata) {
return metadata;
}
var metadata2 = Array(height).fill(undefined);
for (var i = 0; i < height; i++) {
metadata2[i] = Array(width).fill(undefined);
}
return metadata2;
};