scichart
Version:
Fast WebGL JavaScript Charting Library and Framework
491 lines (490 loc) • 24.6 kB
JavaScript
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.WebGlRenderContext2D = exports.ELineDrawMode = exports.calculateAbsoluteRenderLayer = void 0;
var app_1 = require("../../constants/app");
var Guard_1 = require("../../Core/Guard");
var Rect_1 = require("../../Core/Rect");
var parseColor_1 = require("../../utils/parseColor");
var createNativeRect_1 = require("../Visuals/Helpers/createNativeRect");
var NativeObject_1 = require("../Visuals/Helpers/NativeObject");
var WebGlBrush_1 = require("./WebGlBrush");
var WebGlPen_1 = require("./WebGlPen");
var DeletableEntity_1 = require("../../Core/DeletableEntity");
var perfomance_1 = require("../../utils/perfomance");
var Point_1 = require("../../Core/Point");
var AnchorPoint_1 = require("../../types/AnchorPoint");
var TextPosition_1 = require("../../types/TextPosition");
var colorUtil_1 = require("../../utils/colorUtil");
/**
*
* @param step specifies the capacity of layers that could be potentially added between the default chart layers
* @param offset layer z-order offset of the surface
* @param relativeRenderLayer layer number relative to the specific surface layers
* @returns absolute order of the layer on the chart (considering parent chart and previous subChart surface layers)
*/
var calculateAbsoluteRenderLayer = function (offset, step, relativeRenderLayer) {
return offset + relativeRenderLayer * step;
};
exports.calculateAbsoluteRenderLayer = calculateAbsoluteRenderLayer;
/**
* Defines enumeration constants for Line Drawing modes
*/
var ELineDrawMode;
(function (ELineDrawMode) {
/**
* Points provided define a poly-line (continuous line)
*/
ELineDrawMode[ELineDrawMode["PolyLine"] = 0] = "PolyLine";
/**
* Points provided define discontinuous lines, e.g. x1y1 x2y2 is one line, x3y3 x4y4 is the next
*/
ELineDrawMode[ELineDrawMode["DiscontinuousLine"] = 1] = "DiscontinuousLine";
})(ELineDrawMode = exports.ELineDrawMode || (exports.ELineDrawMode = {}));
/**
* The WebGlRenderContext2D provides methods for drawing to a WebGL2 / WebAssembly canvas powered by SciChart's Visual Xccelerator engine.
* This context class is used in SciChart's High Performance Realtime {@link https://www.scichart.com/javascript-chart-features | JavaScript Charts}
* to draw shapes, lines, fills, images and more
*/
var WebGlRenderContext2D = /** @class */ (function (_super) {
__extends(WebGlRenderContext2D, _super);
/**
* Creates an instance of the WebGlRenderContext2D
* @param webAssemblyContext The {@link TSciChart | SciChart WebAssembly Context} containing native methods and access to our WebGL2 Engine
* @param viewportSize The Viewport {@link Size}
*/
function WebGlRenderContext2D(webAssemblyContext, viewportSize, canvasId) {
var _this = _super.call(this) || this;
_this.layers = new Map();
_this.effects = [];
_this.webAssemblyContext = webAssemblyContext;
_this.viewportSize = viewportSize;
_this.canvasId = canvasId;
return _this;
}
/**
* Get the native {@link SCRTRenderContext} for direct access to SciChart's WebAssembly Visual Xccelerator engine
*/
WebGlRenderContext2D.prototype.getNativeContext = function () {
if (!this.nativeContext) {
this.nativeContext = this.webAssemblyContext.SCRTGetMainRenderContext2D();
}
return this.nativeContext;
};
/**
* Draw lines: grid lines, etc.
* @param vertices
* @param pen
* @param lineDrawMode
* @param left - offset in pixels from left, typically used for axes
* @param top - offset in pixels from top, typically used for axes
*/
WebGlRenderContext2D.prototype.drawLinesNative = function (vertices, pen, lineDrawMode, left, top) {
if (left === void 0) { left = 0; }
if (top === void 0) { top = 0; }
var isStrips = lineDrawMode === ELineDrawMode.PolyLine;
var nativeContext = this.getNativeContext();
nativeContext.PushMatrix();
nativeContext.Translate(left, top);
nativeContext.DrawLinesBatchVec(isStrips, vertices, pen);
nativeContext.PopMatrix();
};
/**
* Draw rectangles: grid bands, etc.
* @param vertices
* @param brush
* @param left - offset in pixels from left, typically used for axes
* @param top - offset in pixels from top, typically used for axes
*/
WebGlRenderContext2D.prototype.drawRects = function (vertices, brush, left, top) {
if (left === void 0) { left = 0; }
if (top === void 0) { top = 0; }
var anchorParams = (0, NativeObject_1.getVector4)(this.webAssemblyContext, 0, 0, 0, 0);
var nativeContext = this.getNativeContext();
nativeContext.PushMatrix();
nativeContext.Translate(left, top);
nativeContext.DrawRectsBatchVec(vertices, brush, anchorParams);
nativeContext.PopMatrix();
};
/**
* Enqueues a draw operation to the specified layer. Use in combination with {@link drawLayers} to flush layered draws
* @param drawFunction the {@link TDrawFunction | Draw Function} to enqueue
* @param layer the {@link ERenderLayer | Layer} to draw to
*/
WebGlRenderContext2D.prototype.enqueueLayeredDraw = function (drawFunction, layer) {
if (!this.layers.has(layer)) {
this.layers.set(layer, []);
}
this.layers.get(layer).push(drawFunction);
};
/**
* Flushes the {@link layers} which have been enqueued with drawing operations in order.
* Use this in combination with {@link enqueueLayeredDraw} to draw in layers
*/
WebGlRenderContext2D.prototype.drawLayers = function () {
var keys = Array.from(this.layers.keys()).sort(function (a, b) { return a - b; });
for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) {
var key = keys_1[_i];
for (var _a = 0, _b = this.layers.get(key); _a < _b.length; _a++) {
var drawFunc = _b[_a];
drawFunc();
}
}
};
/**
* Applies a {@link ShaderEffect} to the rendering pipeline. Calling {@link WebGL2RenderingContext.popShaderEffect} pops the effect from the stack
* reverting to normal drawing
* @param effect the {@link ShaderEffect} to apply to subsequent draw operations
*/
WebGlRenderContext2D.prototype.pushShaderEffect = function (effect) {
if (effect) {
this.effects.push(effect);
if (!app_1.IS_TEST_ENV) {
this.getNativeContext().AddSeriesEffect(effect.getNativeEffect());
}
}
};
/**
* Pops a {@link ShaderEffect} from the rendering pipeline. Call {@link WebGL2RenderingContext.pushShaderEffect} to apply an effect
*/
WebGlRenderContext2D.prototype.popShaderEffect = function () {
var effect = this.effects.pop();
if (effect && !app_1.IS_TEST_ENV) {
this.getNativeContext().RemoveSeriesEffect(effect.getNativeEffect());
}
};
/**
* @inheritDoc
*/
WebGlRenderContext2D.prototype.createPen = function (stroke, strokeThickness, strokeDashArray, antiAliased) {
if (strokeDashArray === void 0) { strokeDashArray = []; }
if (antiAliased === void 0) { antiAliased = true; }
throw new Error("Use Pen2DCache for creating pens instead!");
if (app_1.IS_TEST_ENV || !stroke) {
return undefined;
}
var colorInt = (0, parseColor_1.parseColorToUIntArgb)(stroke);
if (isNaN(colorInt)) {
throw new Error("Color code ".concat(stroke, " cannot be converted to an ARGB integer"));
}
var strokeDashFloatVector = new this.webAssemblyContext.FloatVector();
if (strokeDashArray.length) {
strokeDashArray.forEach(function (item) { return strokeDashFloatVector.push_back(item); });
}
return new WebGlPen_1.WebGlPen(this.webAssemblyContext.SCRTCreateDahedPen(colorInt, strokeThickness, antiAliased, strokeDashFloatVector));
};
/**
* @inheritDoc
*/
WebGlRenderContext2D.prototype.createSolidBrush = function (fill, opacity) {
throw new Error("Use BrushCache for creating brushes instead!");
if (app_1.IS_TEST_ENV || !fill) {
return undefined;
}
var colorInt = (0, parseColor_1.parseColorToUIntArgb)(fill, opacity);
var isTransparent = true;
if (isNaN(colorInt)) {
throw new Error("Color code ".concat(fill, " cannot be converted to an ARGB integer"));
}
return new WebGlBrush_1.WebGlBrush(new this.webAssemblyContext.SCRTSolidBrush(colorInt, isTransparent));
};
/**
* @inheritDoc
*/
WebGlRenderContext2D.prototype.delete = function () {
// Todo: Any cached items delete here
};
/**
* @inheritDoc
*/
WebGlRenderContext2D.prototype.drawLine = function (x1, y1, x2, y2, pen, viewRect) {
if (!x1 && !y1 && !x2 && !y2) {
return;
}
// TODO: pass left and top from seriesViewRect
var left = 0;
var top = 0;
Guard_1.Guard.notNull(pen, "pen");
Guard_1.Guard.isTrue(pen.getPenType() === "WasmPen", "pen must be an instance of WasmPen to be used with the WasmRenderContext");
var scrtPen = pen.scrtPen;
Guard_1.Guard.notNull(scrtPen, "WasmPen.scrtPen");
var isStrips = false;
var nativeContext = this.getNativeContext();
var vertices = (0, NativeObject_1.getVectorColorVertex)(this.webAssemblyContext);
var vertex0 = (0, NativeObject_1.getVertex)(this.webAssemblyContext, x1, y1);
vertices.push_back(vertex0);
var vertex1 = (0, NativeObject_1.getVertex)(this.webAssemblyContext, x2, y2);
vertices.push_back(vertex1);
nativeContext.PushMatrix();
nativeContext.PushState();
nativeContext.Translate(viewRect.x, viewRect.y);
nativeContext.SetClipRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height);
nativeContext.DrawLinesBatchVec(isStrips, vertices, scrtPen);
nativeContext.PopMatrix();
nativeContext.PopState();
};
/**
* @inheritDoc
*/
WebGlRenderContext2D.prototype.drawLines = function (xyValues, strokePen, viewRect, lineDrawMode) {
if (lineDrawMode === void 0) { lineDrawMode = ELineDrawMode.PolyLine; }
Guard_1.Guard.notNull(strokePen, "pen");
Guard_1.Guard.notNull(xyValues, "xyValues");
Guard_1.Guard.isTrue(strokePen.getPenType() === "WasmPen", "pen must be an instance of WasmPen to be used with the WasmRenderContext");
Guard_1.Guard.isTrue(xyValues.length > 0 && xyValues.length % 2 === 0, "xyValues length must be a multiple of 2, values arranged as x0y0 x1y1 x2y2...");
var scrtPen = strokePen.scrtPen;
Guard_1.Guard.notNull(scrtPen, "WebGlPen.scrtPen");
var isStrips = lineDrawMode === ELineDrawMode.PolyLine;
var nativeContext = this.getNativeContext();
var vertices = (0, NativeObject_1.getVectorColorVertex)(this.webAssemblyContext);
for (var i = 0; i < xyValues.length; i += 2) {
var vertex = (0, NativeObject_1.getVertex)(this.webAssemblyContext, xyValues[i], xyValues[i + 1]);
// Setting a sub-property directly MUST BE AVOIDED because it creates a copy of an object and leaks the memory
// Never do this: vertex.m_vPosition.z = 0;
vertices.push_back(vertex);
}
nativeContext.PushMatrix();
nativeContext.PushState();
nativeContext.Translate(viewRect.x, viewRect.y);
nativeContext.SetClipRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height);
nativeContext.DrawLinesBatchVec(isStrips, vertices, scrtPen);
nativeContext.PopMatrix();
nativeContext.PopState();
};
/**
* @inheritDoc
*/
WebGlRenderContext2D.prototype.drawRect = function (rect, viewRect, strokePen, fillBrush) {
if (fillBrush) {
var nativeContext = this.getNativeContext();
var scrtBrush = fillBrush.scrtBrush;
Guard_1.Guard.notNull(scrtBrush, "WebGlBrush.scrtBrush");
var vertices = (0, NativeObject_1.getVectorRectVertex)(this.webAssemblyContext);
var anchorParams = (0, NativeObject_1.getVector4)(this.webAssemblyContext, 0, 0, 0, 0);
var nativeRect = (0, createNativeRect_1.createNativeRect)(this.webAssemblyContext, rect.x, rect.y, rect.right, rect.bottom);
vertices.push_back(nativeRect);
nativeContext.PushMatrix();
nativeContext.PushState();
nativeContext.Translate(viewRect.x, viewRect.y);
nativeContext.SetClipRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height);
nativeContext.DrawRectsBatchVec(vertices, scrtBrush, anchorParams);
nativeContext.PopMatrix();
nativeContext.PopState();
}
if (strokePen) {
this.drawLines([
rect.right,
rect.top,
rect.right,
rect.bottom,
rect.left,
rect.bottom,
rect.left,
rect.top,
rect.right,
rect.top
], strokePen, viewRect, ELineDrawMode.PolyLine);
}
};
WebGlRenderContext2D.prototype.drawTextBackground = function (textWidth, textHeight, rotation, rotationCenter, seriesViewRect, verticalAnchorPoint, horizontalAnchorPoint, padding, fillBrush) {
var nativeContext = this.getNativeContext();
nativeContext.PushMatrix();
nativeContext.Translate(rotationCenter.x, rotationCenter.y);
nativeContext.Rotate(rotation);
var fromCenterToTop;
var fromCenterToBottom;
switch (verticalAnchorPoint) {
case AnchorPoint_1.EVerticalAnchorPoint.Bottom:
fromCenterToTop = textHeight + padding.top + padding.bottom;
fromCenterToBottom = 0;
break;
case AnchorPoint_1.EVerticalAnchorPoint.Top:
fromCenterToTop = 0;
fromCenterToBottom = textHeight + padding.bottom + padding.top;
break;
default:
fromCenterToTop = textHeight / 2 + padding.top;
fromCenterToBottom = textHeight / 2 + padding.bottom;
}
var fromCenterToLeft;
var fromCenterToRight;
switch (horizontalAnchorPoint) {
case AnchorPoint_1.EHorizontalAnchorPoint.Right:
fromCenterToLeft = textWidth + padding.left + padding.right;
fromCenterToRight = 0;
break;
case AnchorPoint_1.EHorizontalAnchorPoint.Left:
fromCenterToLeft = 0;
fromCenterToRight = textWidth + padding.right + padding.left;
break;
default:
fromCenterToLeft = textWidth / 2 + padding.left;
fromCenterToRight = textWidth / 2 + padding.right;
}
var rect = Rect_1.Rect.createWithPoints(new Point_1.Point(-fromCenterToLeft, -fromCenterToTop), new Point_1.Point(fromCenterToRight, fromCenterToBottom));
this.drawRect(rect, seriesViewRect, undefined, fillBrush);
nativeContext.PopMatrix();
};
WebGlRenderContext2D.prototype.drawNativeText = function (rotationRadians, xCoord, yCoord, seriesViewRect, chartViewRect, fontFamily, fontSize, textColor, textColorOpacity, backgroundFillBrush, verticalAnchorPoint, horizontalAnchorPoint, text, padding, drawImmediately, scale) {
if (drawImmediately === void 0) { drawImmediately = false; }
if (scale === void 0) { scale = 1; }
var colorNum = (0, parseColor_1.parseColorToUIntArgb)(textColor);
var colorNumWithOpacity = (0, colorUtil_1.uintArgbColorOverrideOpacity)(colorNum, textColorOpacity);
var style = {
fontFamily: fontFamily,
fontSize: fontSize
};
var font = this.getFont(style, false, false);
var textBounds = (0, NativeObject_1.getTextBounds)(this.webAssemblyContext);
var defaultSpacing = 3;
font.CalculateStringBounds(text !== null && text !== void 0 ? text : "", textBounds, defaultSpacing);
var rotationCenterX = xCoord + seriesViewRect.x;
var rotationCenterY = yCoord + seriesViewRect.y;
var textPositionX = xCoord;
var textPositionY = yCoord;
if (horizontalAnchorPoint === AnchorPoint_1.EHorizontalAnchorPoint.Center) {
textPositionX -= textBounds.m_fWidth / 2;
}
else if (horizontalAnchorPoint === AnchorPoint_1.EHorizontalAnchorPoint.Right) {
textPositionX -= textBounds.m_fWidth + padding.right;
}
else if (horizontalAnchorPoint === AnchorPoint_1.EHorizontalAnchorPoint.Left) {
textPositionX += padding.left;
}
if (verticalAnchorPoint === AnchorPoint_1.EVerticalAnchorPoint.Center) {
textPositionY -= textBounds.m_fHeight / 2 - textBounds.GetLineBounds(0).m_fHeight;
}
else if (verticalAnchorPoint === AnchorPoint_1.EVerticalAnchorPoint.Top) {
textPositionY += textBounds.GetLineBounds(0).m_fHeight + padding.top;
}
else if (verticalAnchorPoint === AnchorPoint_1.EVerticalAnchorPoint.Bottom) {
textPositionY -= padding.bottom;
}
var textWidth = textBounds.m_fWidth;
var textHeight = textBounds.m_fHeight;
var alignMode = (0, TextPosition_1.convertMultiLineAlignment)(TextPosition_1.EMultiLineAlignment.Center, this.webAssemblyContext);
var oldScale = font.GetScale();
var scaleChanged = false;
if (oldScale !== scale) {
font.SetScale(scale);
scaleChanged = true;
}
var x = textPositionX + seriesViewRect.x;
var y = textPositionY + seriesViewRect.y;
if (backgroundFillBrush) {
this.drawTextBackground(textWidth, textHeight, -((rotationRadians * 180) / Math.PI), new Point_1.Point(rotationCenterX, rotationCenterY), chartViewRect, verticalAnchorPoint, horizontalAnchorPoint, padding, backgroundFillBrush);
}
var rotationVector = (0, NativeObject_1.getVector4)(this.webAssemblyContext, rotationCenterX, rotationCenterY, rotationRadians, 0);
font.DrawStringAdvanced(text, colorNumWithOpacity, Math.round(x), Math.round(y), rotationVector, alignMode, defaultSpacing);
if (scaleChanged) {
font.SetScale(oldScale);
}
if (drawImmediately) {
font.End();
}
};
WebGlRenderContext2D.prototype.printBlendMode = function () {
var blendMode = this.getNativeContext().GetBlendMode();
switch (blendMode) {
case this.webAssemblyContext.eSCRTBlendMode.BlendDefault:
console.log("BlendDefault");
break;
case this.webAssemblyContext.eSCRTBlendMode.BlendAdditiveOneAlpha:
console.log("BlendAdditiveOneAlpha");
break;
case this.webAssemblyContext.eSCRTBlendMode.BlendAdditiveColor:
console.log("BlendAdditiveColor");
break;
case this.webAssemblyContext.eSCRTBlendMode.BlendAdditiveAlpha:
console.log("BlendAdditiveAlpha");
break;
case this.webAssemblyContext.eSCRTBlendMode.BlendDisabled:
console.log("BlendDisabled");
break;
default:
throw new Error("Unhandled blendmode ");
}
};
/**
* Get a native font. Fonts are cached and shared within webassembly so there is no need to cache them in JS.
* Set advanced: true if you are planning to rotate or scale the text.
* Set drawEarly: true if you are planning to call font.End() early. Otherwise all native text will be drawn at the end of the render cycle.
*/
WebGlRenderContext2D.prototype.getFont = function (labelStyle, advanced, drawEarly) {
var _this = this;
if (advanced === void 0) { advanced = false; }
if (drawEarly === void 0) { drawEarly = false; }
var fontKey = (0, NativeObject_1.getFontKey)(this.webAssemblyContext, labelStyle, advanced, drawEarly);
var nativeContext = this.getNativeContext();
var nativeFont = nativeContext.AquireFont(fontKey);
if (!nativeFont) {
throw new Error("Could not create font " + fontKey.m_strName);
}
else {
if (!nativeFont.m_isDrawing) {
nativeFont.Begin();
}
}
var currentFontName = nativeFont.GetFaceName();
if (currentFontName === "SCRT_Loading") {
setTimeout(function () {
var _a;
perfomance_1.PerformanceDebugHelper.mark(perfomance_1.EPerformanceMarkType.Invalidate, { parentContextId: _this.canvasId });
_this.webAssemblyContext.TSRRequestCanvasDraw((_a = _this.canvasId) !== null && _a !== void 0 ? _a : "undefinedCanvasId");
}, 100);
}
else if (currentFontName !== fontKey.m_strName) {
// @ts-ignore
if (!fontKey.warned) {
console.warn("Font ".concat(fontKey.m_strName, " could not be found on the server and has not been registered. Falling back to Arial.\n Use await scichartSurface.registerFont if you need to load the font from a remote url"));
// @ts-ignore
fontKey.warned = true;
}
}
return nativeFont;
};
/** End all fonts, causing text to be drawn */
WebGlRenderContext2D.prototype.endFonts = function (force) {
var _a;
if (force === void 0) { force = false; }
var mark = perfomance_1.PerformanceDebugHelper.mark(perfomance_1.EPerformanceMarkType.DrawNativeTextStart, {
contextId: this.canvasId,
level: perfomance_1.EPerformanceDebugLevel.Verbose
});
var nativeContext = this.getNativeContext();
var keys = (0, NativeObject_1.getAllFontKeys)(this.webAssemblyContext);
for (var _i = 0, keys_2 = keys; _i < keys_2.length; _i++) {
var fontKey = keys_2[_i];
var nativeFont = nativeContext.AquireFont(fontKey);
if (nativeFont && nativeFont.m_isDrawing) {
nativeFont.End();
}
}
perfomance_1.PerformanceDebugHelper.mark(perfomance_1.EPerformanceMarkType.DrawNativeTextEnd, {
contextId: this.canvasId,
relatedId: (_a = mark === null || mark === void 0 ? void 0 : mark.detail) === null || _a === void 0 ? void 0 : _a.relatedId,
level: perfomance_1.EPerformanceDebugLevel.Verbose
});
};
/**
* Should store references to all cached WebGlResources {@link ICacheable}
* Is used to invalidate the resources when the WebGL context is lost.
*/
WebGlRenderContext2D.webGlResourcesRefs = new Set();
return WebGlRenderContext2D;
}(DeletableEntity_1.DeletableEntity));
exports.WebGlRenderContext2D = WebGlRenderContext2D;
;