7161 lines
234 KiB
JavaScript
Executable File
7161 lines
234 KiB
JavaScript
Executable File
"use strict";
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
if (decorator = decorators[i])
|
|
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
if (kind && result)
|
|
__defProp(target, key, result);
|
|
return result;
|
|
};
|
|
|
|
// packages/ag-charts-community/src/integrated-charts-scene.ts
|
|
var integrated_charts_scene_exports = {};
|
|
__export(integrated_charts_scene_exports, {
|
|
Arc: () => Arc,
|
|
BBox: () => BBox,
|
|
Caption: () => Caption,
|
|
CategoryScale: () => CategoryScale,
|
|
Group: () => Group,
|
|
Line: () => Line,
|
|
LinearScale: () => LinearScale,
|
|
Marker: () => Marker,
|
|
Path: () => Path,
|
|
RadialColumnShape: () => RadialColumnShape,
|
|
Rect: () => Rect,
|
|
Scene: () => Scene,
|
|
Sector: () => Sector,
|
|
Shape: () => Shape,
|
|
TranslatableGroup: () => TranslatableGroup,
|
|
getRadialColumnWidth: () => getRadialColumnWidth,
|
|
toRadians: () => import_ag_charts_core42.toRadians
|
|
});
|
|
module.exports = __toCommonJS(integrated_charts_scene_exports);
|
|
|
|
// packages/ag-charts-community/src/chart/caption.ts
|
|
var import_ag_charts_core26 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scene/node.ts
|
|
var import_ag_charts_core2 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scene/bbox.ts
|
|
var import_ag_charts_core = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/util/interpolating.ts
|
|
var interpolate = Symbol("interpolate");
|
|
|
|
// packages/ag-charts-community/src/scene/bbox.ts
|
|
var _BBox = class _BBox {
|
|
constructor(x, y, width, height) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width;
|
|
this.height = height;
|
|
}
|
|
static fromObject({ x, y, width, height }) {
|
|
return new _BBox(x, y, width, height);
|
|
}
|
|
static merge(boxes) {
|
|
let left = Infinity;
|
|
let top = Infinity;
|
|
let right = -Infinity;
|
|
let bottom = -Infinity;
|
|
for (const box of boxes) {
|
|
if (box.x < left) {
|
|
left = box.x;
|
|
}
|
|
if (box.y < top) {
|
|
top = box.y;
|
|
}
|
|
if (end(box.x, box.width) > right) {
|
|
right = end(box.x, box.width);
|
|
}
|
|
if (end(box.y, box.height) > bottom) {
|
|
bottom = end(box.y, box.height);
|
|
}
|
|
}
|
|
return new _BBox(left, top, right - left, bottom - top);
|
|
}
|
|
static nearestBox(x, y, boxes) {
|
|
return (0, import_ag_charts_core.nearestSquared)(x, y, boxes);
|
|
}
|
|
toDOMRect() {
|
|
return {
|
|
x: this.x,
|
|
y: this.y,
|
|
width: this.width,
|
|
height: this.height,
|
|
top: this.y,
|
|
left: this.x,
|
|
right: end(this.x, this.width),
|
|
bottom: end(this.y, this.height),
|
|
toJSON() {
|
|
return {};
|
|
}
|
|
};
|
|
}
|
|
clone() {
|
|
const { x, y, width, height } = this;
|
|
return new _BBox(x, y, width, height);
|
|
}
|
|
equals(other) {
|
|
return (0, import_ag_charts_core.boxesEqual)(this, other);
|
|
}
|
|
containsPoint(x, y) {
|
|
return (0, import_ag_charts_core.boxContains)(this, x, y);
|
|
}
|
|
intersection(other) {
|
|
const x0 = Math.max(this.x, other.x);
|
|
const y0 = Math.max(this.y, other.y);
|
|
const x1 = Math.min(end(this.x, this.width), end(other.x, other.width));
|
|
const y1 = Math.min(end(this.y, this.height), end(other.y, other.height));
|
|
if (x0 > x1 || y0 > y1)
|
|
return;
|
|
return new _BBox(x0, y0, x1 - x0, y1 - y0);
|
|
}
|
|
collidesBBox(other) {
|
|
return this.x < end(other.x, other.width) && end(this.x, this.width) > other.x && this.y < end(other.y, other.height) && end(this.y, this.height) > other.y;
|
|
}
|
|
computeCenter() {
|
|
return { x: this.x + this.width / 2, y: this.y + this.height / 2 };
|
|
}
|
|
isFinite() {
|
|
return Number.isFinite(this.x) && Number.isFinite(this.y) && Number.isFinite(this.width) && Number.isFinite(this.height);
|
|
}
|
|
distanceSquared(x, y) {
|
|
if (this.containsPoint(x, y)) {
|
|
return 0;
|
|
}
|
|
const dx = x - (0, import_ag_charts_core.clamp)(this.x, x, end(this.x, this.width));
|
|
const dy = y - (0, import_ag_charts_core.clamp)(this.y, y, end(this.y, this.height));
|
|
return dx * dx + dy * dy;
|
|
}
|
|
shrink(amount, position) {
|
|
if (typeof amount === "number") {
|
|
this.applyMargin(amount, position);
|
|
} else {
|
|
for (const key of Object.keys(amount)) {
|
|
const value = amount[key];
|
|
if (typeof value === "number") {
|
|
this.applyMargin(value, key);
|
|
}
|
|
}
|
|
}
|
|
if (this.width < 0) {
|
|
this.width = 0;
|
|
}
|
|
if (this.height < 0) {
|
|
this.height = 0;
|
|
}
|
|
return this;
|
|
}
|
|
grow(amount, position) {
|
|
if (typeof amount === "number") {
|
|
this.applyMargin(-amount, position);
|
|
} else {
|
|
for (const key of Object.keys(amount)) {
|
|
const value = amount[key];
|
|
if (typeof value === "number") {
|
|
this.applyMargin(-value, key);
|
|
}
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
applyMargin(value, position) {
|
|
switch (position) {
|
|
case "top":
|
|
this.y += value;
|
|
case "bottom":
|
|
this.height -= value;
|
|
break;
|
|
case "left":
|
|
this.x += value;
|
|
case "right":
|
|
this.width -= value;
|
|
break;
|
|
case "vertical":
|
|
this.y += value;
|
|
this.height -= value * 2;
|
|
break;
|
|
case "horizontal":
|
|
this.x += value;
|
|
this.width -= value * 2;
|
|
break;
|
|
case void 0:
|
|
this.x += value;
|
|
this.y += value;
|
|
this.width -= value * 2;
|
|
this.height -= value * 2;
|
|
break;
|
|
}
|
|
}
|
|
translate(x, y) {
|
|
this.x += x;
|
|
this.y += y;
|
|
return this;
|
|
}
|
|
[interpolate](other, d) {
|
|
return new _BBox(
|
|
this.x * (1 - d) + other.x * d,
|
|
this.y * (1 - d) + other.y * d,
|
|
this.width * (1 - d) + other.width * d,
|
|
this.height * (1 - d) + other.height * d
|
|
);
|
|
}
|
|
};
|
|
_BBox.zero = Object.freeze(new _BBox(0, 0, 0, 0));
|
|
_BBox.NaN = Object.freeze(new _BBox(Number.NaN, Number.NaN, Number.NaN, Number.NaN));
|
|
var BBox = _BBox;
|
|
function end(x, width) {
|
|
if (x === -Infinity && width === Infinity)
|
|
return Infinity;
|
|
return x + width;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/zIndex.ts
|
|
var cmp = (a, b) => Math.sign(a - b);
|
|
function compareZIndex(a, b) {
|
|
if (typeof a === "number" && typeof b === "number") {
|
|
return cmp(a, b);
|
|
}
|
|
const aArray = typeof a === "number" ? [a] : a;
|
|
const bArray = typeof b === "number" ? [b] : b;
|
|
const length = Math.min(aArray.length, bArray.length);
|
|
for (let i = 0; i < length; i += 1) {
|
|
const diff = cmp(aArray[i], bArray[i]);
|
|
if (diff !== 0)
|
|
return diff;
|
|
}
|
|
return cmp(aArray.length, bArray.length);
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/node.ts
|
|
var import_ag_charts_core3 = require("ag-charts-core");
|
|
var MAX_ERROR_COUNT = 5;
|
|
var _Node = class _Node {
|
|
constructor(options) {
|
|
/** Unique number to allow creation order to be easily determined. */
|
|
this.serialNumber = _Node._nextSerialNumber++;
|
|
this.childNodeCounts = { groups: 0, nonGroups: 0, thisComplexity: 0, complexity: 0 };
|
|
/** Unique node ID in the form `ClassName-NaturalNumber`. */
|
|
this.id = (0, import_ag_charts_core2.createId)(this);
|
|
this.name = void 0;
|
|
this.transitionOut = void 0;
|
|
this.pointerEvents = 0 /* All */;
|
|
this._datum = void 0;
|
|
this._previousDatum = void 0;
|
|
this.scene = void 0;
|
|
this._debugDirtyProperties = void 0;
|
|
this.parentNode = void 0;
|
|
this.cachedBBox = void 0;
|
|
/**
|
|
* To simplify the type system (especially in Selections) we don't have the `Parent` node
|
|
* (one that has children). Instead, we mimic HTML DOM, where any node can have children.
|
|
* But we still need to distinguish regular leaf nodes from container leafs somehow.
|
|
*/
|
|
this.isContainerNode = false;
|
|
this.visible = true;
|
|
this.zIndex = 0;
|
|
this.batchLevel = 0;
|
|
this.batchDirty = false;
|
|
this.name = options?.name;
|
|
this.tag = options?.tag ?? Number.NaN;
|
|
this.zIndex = options?.zIndex ?? 0;
|
|
this.scene = options?.scene;
|
|
if (options?.debugDirty ?? _Node._debugEnabled) {
|
|
this._debugDirtyProperties = /* @__PURE__ */ new Map([["__first__", []]]);
|
|
}
|
|
}
|
|
static toSVG(node, width, height) {
|
|
const svg = node?.toSVG();
|
|
if (svg == null || !svg.elements.length && !svg.defs?.length)
|
|
return;
|
|
const root = (0, import_ag_charts_core2.createSvgElement)("svg");
|
|
root.setAttribute("width", String(width));
|
|
root.setAttribute("height", String(height));
|
|
root.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
root.setAttribute("overflow", "visible");
|
|
if (svg.defs?.length) {
|
|
const defs = (0, import_ag_charts_core2.createSvgElement)("defs");
|
|
defs.append(...svg.defs);
|
|
root.append(defs);
|
|
}
|
|
root.append(...svg.elements);
|
|
return root.outerHTML;
|
|
}
|
|
static *extractBBoxes(nodes, skipInvisible) {
|
|
for (const n of nodes) {
|
|
if (!skipInvisible || n.visible && !n.transitionOut) {
|
|
const bbox = n.getBBox();
|
|
if (bbox)
|
|
yield bbox;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Some arbitrary data bound to the node.
|
|
*/
|
|
get datum() {
|
|
return this._datum;
|
|
}
|
|
set datum(datum) {
|
|
if (this._datum !== datum) {
|
|
this._previousDatum = this._datum;
|
|
this._datum = datum;
|
|
}
|
|
}
|
|
get previousDatum() {
|
|
return this._previousDatum;
|
|
}
|
|
get layerManager() {
|
|
return this.scene?.layersManager;
|
|
}
|
|
get imageLoader() {
|
|
return this.scene?.imageLoader;
|
|
}
|
|
closestDatum() {
|
|
for (const { datum } of this.traverseUp(true)) {
|
|
if (datum != null) {
|
|
return datum;
|
|
}
|
|
}
|
|
}
|
|
/** Perform any pre-rendering initialization. */
|
|
preRender(_renderCtx, thisComplexity = 1) {
|
|
this.childNodeCounts.groups = 0;
|
|
this.childNodeCounts.nonGroups = 1;
|
|
this.childNodeCounts.complexity = thisComplexity;
|
|
this.childNodeCounts.thisComplexity = thisComplexity;
|
|
if (this.batchLevel > 0 || this.batchDirty) {
|
|
throw new Error("AG Charts - illegal rendering state; batched update in progress");
|
|
}
|
|
return this.childNodeCounts;
|
|
}
|
|
/** Guaranteed isolated render - if there is any failure, the Canvas2D context is returned to its prior state. */
|
|
isolatedRender(renderCtx) {
|
|
renderCtx.ctx.save();
|
|
try {
|
|
this.render(renderCtx);
|
|
} catch (e) {
|
|
const errorCount = e.errorCount ?? 1;
|
|
if (errorCount >= MAX_ERROR_COUNT) {
|
|
e.errorCount = errorCount;
|
|
throw e;
|
|
}
|
|
import_ag_charts_core2.Logger.warnOnce("Error during rendering", e, e.stack);
|
|
} finally {
|
|
renderCtx.ctx.restore();
|
|
}
|
|
}
|
|
render(renderCtx) {
|
|
const { stats } = renderCtx;
|
|
this.debugDirtyProperties();
|
|
if (renderCtx.debugNodeSearch) {
|
|
const idOrName = this.name ?? this.id;
|
|
if (renderCtx.debugNodeSearch.some((v) => typeof v === "string" ? v === idOrName : v.test(idOrName))) {
|
|
renderCtx.debugNodes[this.name ?? this.id] = this;
|
|
}
|
|
}
|
|
if (stats) {
|
|
stats.nodesRendered++;
|
|
stats.opsPerformed += this.childNodeCounts.thisComplexity;
|
|
}
|
|
}
|
|
setScene(scene) {
|
|
this.scene = scene;
|
|
}
|
|
*traverseUp(includeSelf) {
|
|
if (includeSelf) {
|
|
yield this;
|
|
}
|
|
let node = this.parentNode;
|
|
while (node) {
|
|
yield node;
|
|
node = node.parentNode;
|
|
}
|
|
}
|
|
/**
|
|
* Checks if the node is the root (has no parent).
|
|
*/
|
|
isRoot() {
|
|
return !this.parentNode;
|
|
}
|
|
removeChild(node) {
|
|
throw new Error(
|
|
`AG Charts - internal error, unknown child node ${node.name ?? node.id} in $${this.name ?? this.id}`
|
|
);
|
|
}
|
|
remove() {
|
|
this.parentNode?.removeChild(this);
|
|
}
|
|
destroy() {
|
|
if (this.parentNode) {
|
|
this.remove();
|
|
}
|
|
}
|
|
batchedUpdate(fn) {
|
|
this.batchLevel++;
|
|
try {
|
|
fn();
|
|
} finally {
|
|
this.batchLevel--;
|
|
if (this.batchLevel === 0 && this.batchDirty) {
|
|
this.markDirty();
|
|
this.batchDirty = false;
|
|
}
|
|
}
|
|
}
|
|
setProperties(styles) {
|
|
this.batchLevel++;
|
|
try {
|
|
(0, import_ag_charts_core2.assignIfNotStrictlyEqual)(this, styles);
|
|
} finally {
|
|
this.batchLevel--;
|
|
if (this.batchLevel === 0 && this.batchDirty) {
|
|
this.markDirty();
|
|
this.batchDirty = false;
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
setPropertiesWithKeys(styles, keys) {
|
|
this.batchLevel++;
|
|
try {
|
|
(0, import_ag_charts_core2.assignIfNotStrictlyEqual)(this, styles, keys);
|
|
} finally {
|
|
this.batchLevel--;
|
|
if (this.batchLevel === 0 && this.batchDirty) {
|
|
this.markDirty();
|
|
this.batchDirty = false;
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
containsPoint(_x, _y) {
|
|
return false;
|
|
}
|
|
pickNode(x, y) {
|
|
if (this.containsPoint(x, y)) {
|
|
return this;
|
|
}
|
|
}
|
|
pickNodes(x, y, into = []) {
|
|
if (this.containsPoint(x, y)) {
|
|
into.push(this);
|
|
}
|
|
return into;
|
|
}
|
|
getBBox() {
|
|
this.cachedBBox ?? (this.cachedBBox = Object.freeze(this.computeBBox()));
|
|
return this.cachedBBox;
|
|
}
|
|
computeBBox() {
|
|
return;
|
|
}
|
|
onChangeDetection(property) {
|
|
this.markDirty(property);
|
|
}
|
|
markDirtyChildrenOrder() {
|
|
this.cachedBBox = void 0;
|
|
}
|
|
markDirty(property) {
|
|
if (this.batchLevel > 0) {
|
|
this.batchDirty = true;
|
|
return;
|
|
}
|
|
if (property != null && this._debugDirtyProperties) {
|
|
this.markDebugProperties(property);
|
|
}
|
|
this.cachedBBox = void 0;
|
|
this.parentNode?.markDirty();
|
|
}
|
|
markDebugProperties(property) {
|
|
const sources = this._debugDirtyProperties?.get(property) ?? [];
|
|
const caller = new Error("Stack trace for property change tracking").stack?.split("\n").filter((line) => {
|
|
return line !== "Error" && !line.includes(".markDebugProperties") && !line.includes(".markDirty") && !line.includes("Object.assign ") && !line.includes(`${this.constructor.name}.`);
|
|
}) ?? "unknown";
|
|
sources.push(caller[0].replace(" at ", "").trim());
|
|
this._debugDirtyProperties?.set(property, sources);
|
|
}
|
|
debugDirtyProperties() {
|
|
if (this._debugDirtyProperties == null)
|
|
return;
|
|
if (!this._debugDirtyProperties.has("__first__")) {
|
|
for (const [property, sources] of this._debugDirtyProperties.entries()) {
|
|
if (sources.length > 1) {
|
|
import_ag_charts_core2.Logger.logGroup(
|
|
`Property changed multiple times before render: ${this.constructor.name}.${property} (${sources.length}x)`,
|
|
() => {
|
|
for (const source of sources) {
|
|
import_ag_charts_core2.Logger.log(source);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
}
|
|
this._debugDirtyProperties.clear();
|
|
}
|
|
static handleNodeZIndexChange(target) {
|
|
target.onZIndexChange();
|
|
}
|
|
onZIndexChange() {
|
|
this.parentNode?.markDirtyChildrenOrder();
|
|
}
|
|
toSVG() {
|
|
return;
|
|
}
|
|
};
|
|
_Node.className = "AbstractNode";
|
|
_Node._nextSerialNumber = 0;
|
|
// eslint-disable-next-line sonarjs/public-static-readonly
|
|
_Node._debugEnabled = false;
|
|
__decorateClass([
|
|
(0, import_ag_charts_core2.DeclaredSceneChangeDetection)()
|
|
], _Node.prototype, "visible", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core2.DeclaredSceneChangeDetection)({
|
|
equals: import_ag_charts_core2.objectsEqual,
|
|
changeCb: _Node.handleNodeZIndexChange
|
|
})
|
|
], _Node.prototype, "zIndex", 2);
|
|
var Node = _Node;
|
|
|
|
// packages/ag-charts-community/src/scene/shape/text.ts
|
|
var import_ag_charts_core25 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scene/group.ts
|
|
var import_ag_charts_core21 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scene/canvas/hdpiOffscreenCanvas.ts
|
|
var import_ag_charts_core5 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scene/canvas/canvasUtil.ts
|
|
var import_ag_charts_core4 = require("ag-charts-core");
|
|
function clearContext({
|
|
context,
|
|
pixelRatio,
|
|
width,
|
|
height
|
|
}) {
|
|
context.save();
|
|
try {
|
|
context.resetTransform();
|
|
context.clearRect(0, 0, Math.ceil(width * pixelRatio), Math.ceil(height * pixelRatio));
|
|
} finally {
|
|
context.restore();
|
|
}
|
|
}
|
|
function debugContext(ctx) {
|
|
if (import_ag_charts_core4.Debug.check("canvas")) {
|
|
const save = ctx.save.bind(ctx);
|
|
const restore = ctx.restore.bind(ctx);
|
|
let depth = 0;
|
|
Object.assign(ctx, {
|
|
save() {
|
|
save();
|
|
depth++;
|
|
},
|
|
restore() {
|
|
if (depth === 0) {
|
|
throw new Error("AG Charts - Unable to restore() past depth 0");
|
|
}
|
|
restore();
|
|
depth--;
|
|
},
|
|
verifyDepthZero() {
|
|
if (depth !== 0) {
|
|
throw new Error(`AG Charts - Save/restore depth is non-zero: ${depth}`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/canvas/hdpiOffscreenCanvas.ts
|
|
function canvasDimensions(width, height, pixelRatio) {
|
|
return [Math.floor(width * pixelRatio), Math.floor(height * pixelRatio)];
|
|
}
|
|
var fallbackCanvas;
|
|
function getFallbackCanvas() {
|
|
const OffscreenCanvasCtor = (0, import_ag_charts_core5.getOffscreenCanvas)();
|
|
fallbackCanvas ?? (fallbackCanvas = new OffscreenCanvasCtor(1, 1));
|
|
return fallbackCanvas;
|
|
}
|
|
var HdpiOffscreenCanvas = class {
|
|
constructor(options) {
|
|
const { width, height, pixelRatio, willReadFrequently = false } = options;
|
|
this.width = width;
|
|
this.height = height;
|
|
this.pixelRatio = pixelRatio;
|
|
const [canvasWidth, canvasHeight] = canvasDimensions(width, height, pixelRatio);
|
|
const OffscreenCanvasCtor = (0, import_ag_charts_core5.getOffscreenCanvas)();
|
|
this.canvas = new OffscreenCanvasCtor(canvasWidth, canvasHeight);
|
|
this.context = this.canvas.getContext("2d", { willReadFrequently });
|
|
this.context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
|
debugContext(this.context);
|
|
}
|
|
drawImage(context, dx = 0, dy = 0) {
|
|
return context.drawImage(this.canvas, dx, dy);
|
|
}
|
|
transferToImageBitmap() {
|
|
if (this.canvas.width < 1 || this.canvas.height < 1) {
|
|
return getFallbackCanvas().transferToImageBitmap();
|
|
}
|
|
return this.canvas.transferToImageBitmap();
|
|
}
|
|
resize(width, height, pixelRatio) {
|
|
if (!(width > 0 && height > 0))
|
|
return;
|
|
const { canvas, context } = this;
|
|
if (width !== this.width || height !== this.height || pixelRatio !== this.pixelRatio) {
|
|
const [canvasWidth, canvasHeight] = canvasDimensions(width, height, pixelRatio);
|
|
canvas.width = canvasWidth;
|
|
canvas.height = canvasHeight;
|
|
}
|
|
context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
|
this.width = width;
|
|
this.height = height;
|
|
this.pixelRatio = pixelRatio;
|
|
}
|
|
clear() {
|
|
clearContext(this);
|
|
}
|
|
destroy() {
|
|
this.canvas.width = 0;
|
|
this.canvas.height = 0;
|
|
this.context.clearRect(0, 0, 0, 0);
|
|
this.canvas = null;
|
|
this.context = null;
|
|
Object.freeze(this);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/shape/shape.ts
|
|
var import_ag_charts_core18 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scene/gradient/conicGradient.ts
|
|
var import_ag_charts_core9 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scene/gradient/gradient.ts
|
|
var import_ag_charts_core8 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scale/colorScale.ts
|
|
var import_ag_charts_core7 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scale/abstractScale.ts
|
|
var AbstractScale = class {
|
|
ticks(_ticks, _domain, _visibleRange) {
|
|
return void 0;
|
|
}
|
|
niceDomain(_ticks, domain = this.domain) {
|
|
return domain;
|
|
}
|
|
get bandwidth() {
|
|
return void 0;
|
|
}
|
|
get step() {
|
|
return void 0;
|
|
}
|
|
get inset() {
|
|
return void 0;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scale/invalidating.ts
|
|
var Invalidating = (target, propertyKey) => {
|
|
const mappedProperty = Symbol(String(propertyKey));
|
|
target[mappedProperty] = void 0;
|
|
Object.defineProperty(target, propertyKey, {
|
|
get() {
|
|
return this[mappedProperty];
|
|
},
|
|
set(newValue) {
|
|
const oldValue = this[mappedProperty];
|
|
if (oldValue !== newValue) {
|
|
this[mappedProperty] = newValue;
|
|
this.invalid = true;
|
|
}
|
|
},
|
|
enumerable: true,
|
|
configurable: false
|
|
});
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scale/scaleUtil.ts
|
|
var import_ag_charts_core6 = require("ag-charts-core");
|
|
function visibleTickRange(ticks, reversed, visibleRange) {
|
|
if (visibleRange == null || visibleRange[0] === 0 && visibleRange[1] === 1)
|
|
return;
|
|
const vt0 = (0, import_ag_charts_core6.clamp)(0, Math.floor(visibleRange[0] * ticks.length), ticks.length);
|
|
const vt1 = (0, import_ag_charts_core6.clamp)(0, Math.ceil(visibleRange[1] * ticks.length), ticks.length);
|
|
const t0 = reversed ? ticks.length - vt1 : vt0;
|
|
const t1 = reversed ? ticks.length - vt0 : vt1;
|
|
return [t0, t1];
|
|
}
|
|
function filterVisibleTicks(ticks, reversed, visibleRange) {
|
|
const tickRange = visibleTickRange(ticks, reversed, visibleRange);
|
|
if (tickRange == null)
|
|
return { ticks, count: ticks.length, firstTickIndex: 0 };
|
|
const [t0, t1] = tickRange;
|
|
return {
|
|
ticks: ticks.slice(t0, t1),
|
|
count: ticks.length,
|
|
firstTickIndex: t0
|
|
};
|
|
}
|
|
function unpackDomainMinMax(domain) {
|
|
const min = (0, import_ag_charts_core6.readIntegratedWrappedValue)(domain.at(0));
|
|
const max = (0, import_ag_charts_core6.readIntegratedWrappedValue)(domain.at(-1));
|
|
return min != void 0 && max != void 0 ? [min, max] : [void 0, void 0];
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scale/colorScale.ts
|
|
var convertColorStringToOklcha = (v) => {
|
|
const color = import_ag_charts_core7.Color.fromString(v);
|
|
const [l, c, h] = import_ag_charts_core7.Color.RGBtoOKLCH(color.r, color.g, color.b);
|
|
return { l, c, h, a: color.a };
|
|
};
|
|
var delta = 1e-6;
|
|
var isAchromatic = (x) => x.c < delta || x.l < delta || x.l > 1 - delta;
|
|
var interpolateOklch = (x, y, d) => {
|
|
d = (0, import_ag_charts_core7.clamp)(0, d, 1);
|
|
let h;
|
|
if (isAchromatic(x)) {
|
|
h = y.h;
|
|
} else if (isAchromatic(y)) {
|
|
h = x.h;
|
|
} else {
|
|
const xH = x.h;
|
|
let yH = y.h;
|
|
const deltaH = y.h - x.h;
|
|
if (deltaH > 180) {
|
|
yH -= 360;
|
|
} else if (deltaH < -180) {
|
|
yH += 360;
|
|
}
|
|
h = xH * (1 - d) + yH * d;
|
|
}
|
|
const c = x.c * (1 - d) + y.c * d;
|
|
const l = x.l * (1 - d) + y.l * d;
|
|
const a = x.a * (1 - d) + y.a * d;
|
|
return import_ag_charts_core7.Color.fromOKLCH(l, c, h, a);
|
|
};
|
|
var ColorScale = class extends AbstractScale {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "color";
|
|
this.defaultTickCount = 0;
|
|
this.invalid = true;
|
|
this.domain = [0, 1];
|
|
this.range = ["red", "blue"];
|
|
this.parsedRange = this.range.map(convertColorStringToOklcha);
|
|
}
|
|
update() {
|
|
const { domain, range: range2 } = this;
|
|
if (domain.length < 2) {
|
|
import_ag_charts_core7.Logger.warnOnce("`colorDomain` should have at least 2 values.");
|
|
if (domain.length === 0) {
|
|
domain.push(0, 1);
|
|
} else if (domain.length === 1) {
|
|
domain.push(domain[0] + 1);
|
|
}
|
|
}
|
|
for (let i = 1; i < domain.length; i++) {
|
|
const a = domain[i - 1];
|
|
const b = domain[i];
|
|
if (a > b) {
|
|
import_ag_charts_core7.Logger.warnOnce("`colorDomain` values should be supplied in ascending order.");
|
|
domain.sort((a2, b2) => a2 - b2);
|
|
break;
|
|
}
|
|
}
|
|
if (range2.length < domain.length) {
|
|
for (let i = range2.length; i < domain.length; i++) {
|
|
range2.push(range2.length > 0 ? range2[0] : "black");
|
|
}
|
|
}
|
|
this.parsedRange = this.range.map(convertColorStringToOklcha);
|
|
}
|
|
normalizeDomains(...domains) {
|
|
return { domain: domains.map((d) => d.domain).flat(), animatable: true };
|
|
}
|
|
toDomain() {
|
|
return;
|
|
}
|
|
convert(x) {
|
|
this.refresh();
|
|
const { domain, range: range2, parsedRange } = this;
|
|
const d0 = domain[0];
|
|
const d1 = domain.at(-1);
|
|
const r0 = range2[0];
|
|
const r1 = range2.at(-1);
|
|
if (x <= d0) {
|
|
return r0;
|
|
}
|
|
if (x >= d1) {
|
|
return r1;
|
|
}
|
|
let index;
|
|
let q;
|
|
if (domain.length === 2) {
|
|
const t = (x - d0) / (d1 - d0);
|
|
const step = 1 / (range2.length - 1);
|
|
index = range2.length <= 2 ? 0 : Math.min(Math.floor(t * (range2.length - 1)), range2.length - 2);
|
|
q = (t - index * step) / step;
|
|
} else {
|
|
for (index = 0; index < domain.length - 2; index++) {
|
|
if (x < domain[index + 1]) {
|
|
break;
|
|
}
|
|
}
|
|
const a = domain[index];
|
|
const b = domain[index + 1];
|
|
q = (x - a) / (b - a);
|
|
}
|
|
const c0 = parsedRange[index];
|
|
const c1 = parsedRange[index + 1];
|
|
return interpolateOklch(c0, c1, q).toRgbaString();
|
|
}
|
|
invert() {
|
|
return;
|
|
}
|
|
getDomainMinMax() {
|
|
return unpackDomainMinMax(this.domain);
|
|
}
|
|
refresh() {
|
|
if (!this.invalid)
|
|
return;
|
|
this.invalid = false;
|
|
this.update();
|
|
if (this.invalid) {
|
|
import_ag_charts_core7.Logger.warnOnce("Expected update to not invalidate scale");
|
|
}
|
|
}
|
|
};
|
|
__decorateClass([
|
|
Invalidating
|
|
], ColorScale.prototype, "domain", 2);
|
|
__decorateClass([
|
|
Invalidating
|
|
], ColorScale.prototype, "range", 2);
|
|
|
|
// packages/ag-charts-community/src/scene/gradient/gradient.ts
|
|
var Gradient = class {
|
|
constructor(colorSpace, stops = [], bbox) {
|
|
this.colorSpace = colorSpace;
|
|
this.stops = stops;
|
|
this.bbox = bbox;
|
|
this._cache = void 0;
|
|
}
|
|
createGradient(ctx, shapeBbox, params) {
|
|
const bbox = this.bbox ?? shapeBbox;
|
|
if (!bbox.isFinite()) {
|
|
return;
|
|
}
|
|
if (this._cache?.ctx === ctx && this._cache.bbox.equals(bbox)) {
|
|
return this._cache.gradient;
|
|
}
|
|
const { stops, colorSpace } = this;
|
|
if (stops.length === 0)
|
|
return;
|
|
if (stops.length === 1)
|
|
return stops[0].color;
|
|
let gradient = this.createCanvasGradient(ctx, bbox, params);
|
|
if (gradient == null)
|
|
return;
|
|
const isOkLch = colorSpace === "oklch";
|
|
const step = 0.05;
|
|
let c0 = stops[0];
|
|
gradient.addColorStop(c0.stop, c0.color);
|
|
for (let i = 1; i < stops.length; i += 1) {
|
|
const c1 = stops[i];
|
|
if (isOkLch) {
|
|
const scale = new ColorScale();
|
|
scale.domain = [c0.stop, c1.stop];
|
|
scale.range = [c0.color, c1.color];
|
|
for (let stop = c0.stop + step; stop < c1.stop; stop += step) {
|
|
gradient.addColorStop(stop, scale.convert(stop) ?? "transparent");
|
|
}
|
|
}
|
|
gradient.addColorStop(c1.stop, c1.color);
|
|
c0 = c1;
|
|
}
|
|
if ("createPattern" in gradient) {
|
|
gradient = gradient.createPattern();
|
|
}
|
|
this._cache = { ctx, bbox, gradient };
|
|
return gradient;
|
|
}
|
|
toSvg(shapeBbox) {
|
|
const bbox = this.bbox ?? shapeBbox;
|
|
const gradient = this.createSvgGradient(bbox);
|
|
for (const { stop: offset, color } of this.stops) {
|
|
const stop = (0, import_ag_charts_core8.createSvgElement)("stop");
|
|
stop.setAttribute("offset", `${offset}`);
|
|
stop.setAttribute("stop-color", `${color}`);
|
|
gradient.appendChild(stop);
|
|
}
|
|
return gradient;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/gradient/conicGradient.ts
|
|
var ConicGradient = class extends Gradient {
|
|
constructor(colorSpace, stops, angle = 0, bbox) {
|
|
super(colorSpace, stops, bbox);
|
|
this.angle = angle;
|
|
}
|
|
createCanvasGradient(ctx, bbox, params) {
|
|
const angleOffset = -90;
|
|
const { angle } = this;
|
|
const radians = (0, import_ag_charts_core9.normalizeAngle360FromDegrees)(angle + angleOffset);
|
|
const cx = params?.centerX ?? bbox.x + bbox.width * 0.5;
|
|
const cy = params?.centerY ?? bbox.y + bbox.height * 0.5;
|
|
return ctx.createConicGradient(radians, cx, cy);
|
|
}
|
|
createSvgGradient(_bbox) {
|
|
return (0, import_ag_charts_core9.createSvgElement)("linearGradient");
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/gradient/linearGradient.ts
|
|
var import_ag_charts_core10 = require("ag-charts-core");
|
|
var LinearGradient = class extends Gradient {
|
|
constructor(colorSpace, stops, angle = 0, bbox) {
|
|
super(colorSpace, stops, bbox);
|
|
this.angle = angle;
|
|
}
|
|
getGradientPoints(bbox) {
|
|
const angleOffset = 90;
|
|
const { angle } = this;
|
|
const radians = (0, import_ag_charts_core10.normalizeAngle360FromDegrees)(angle + angleOffset);
|
|
const cos = Math.cos(radians);
|
|
const sin = Math.sin(radians);
|
|
const w = bbox.width;
|
|
const h = bbox.height;
|
|
const cx = bbox.x + w * 0.5;
|
|
const cy = bbox.y + h * 0.5;
|
|
const diagonal = Math.hypot(h, w) / 2;
|
|
const diagonalAngle = Math.atan2(h, w);
|
|
let quarteredAngle;
|
|
if (radians < Math.PI / 2) {
|
|
quarteredAngle = radians;
|
|
} else if (radians < Math.PI) {
|
|
quarteredAngle = Math.PI - radians;
|
|
} else if (radians < 1.5 * Math.PI) {
|
|
quarteredAngle = radians - Math.PI;
|
|
} else {
|
|
quarteredAngle = 2 * Math.PI - radians;
|
|
}
|
|
const l = diagonal * Math.abs(Math.cos(quarteredAngle - diagonalAngle));
|
|
return { x0: cx + cos * l, y0: cy + sin * l, x1: cx - cos * l, y1: cy - sin * l };
|
|
}
|
|
createCanvasGradient(ctx, bbox) {
|
|
const { x0, y0, x1, y1 } = this.getGradientPoints(bbox);
|
|
if (Number.isNaN(x0) || Number.isNaN(y0) || Number.isNaN(x1) || Number.isNaN(y1)) {
|
|
return void 0;
|
|
}
|
|
return ctx.createLinearGradient(x0, y0, x1, y1);
|
|
}
|
|
createSvgGradient(bbox) {
|
|
const { x0, y0, x1, y1 } = this.getGradientPoints(bbox);
|
|
const gradient = (0, import_ag_charts_core10.createSvgElement)("linearGradient");
|
|
gradient.setAttribute("x1", String(x0));
|
|
gradient.setAttribute("y1", String(y0));
|
|
gradient.setAttribute("x2", String(x1));
|
|
gradient.setAttribute("y2", String(y1));
|
|
gradient.setAttribute("gradientUnits", "userSpaceOnUse");
|
|
return gradient;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/gradient/radialGradient.ts
|
|
var import_ag_charts_core11 = require("ag-charts-core");
|
|
var RadialGradient = class extends Gradient {
|
|
constructor(colorSpace, stops, bbox) {
|
|
super(colorSpace, stops, bbox);
|
|
}
|
|
createCanvasGradient(ctx, bbox, params) {
|
|
const cx = params?.centerX ?? bbox.x + bbox.width * 0.5;
|
|
const cy = params?.centerY ?? bbox.y + bbox.height * 0.5;
|
|
const innerRadius = params?.innerRadius ?? 0;
|
|
const outerRadius = params?.outerRadius ?? Math.hypot(bbox.width * 0.5, bbox.height * 0.5) / Math.SQRT2;
|
|
return ctx.createRadialGradient(cx, cy, innerRadius, cx, cy, outerRadius);
|
|
}
|
|
createSvgGradient(bbox) {
|
|
const cx = bbox.x + bbox.width * 0.5;
|
|
const cy = bbox.y + bbox.height * 0.5;
|
|
const gradient = (0, import_ag_charts_core11.createSvgElement)("radialGradient");
|
|
gradient.setAttribute("cx", String(cx));
|
|
gradient.setAttribute("cy", String(cy));
|
|
gradient.setAttribute("r", String(Math.hypot(bbox.width * 0.5, bbox.height * 0.5) / Math.SQRT2));
|
|
gradient.setAttribute("gradientUnits", "userSpaceOnUse");
|
|
return gradient;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/gradient/stops.ts
|
|
var import_ag_charts_core12 = require("ag-charts-core");
|
|
var StopProperties = class extends import_ag_charts_core12.BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.color = "black";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
import_ag_charts_core12.Property
|
|
], StopProperties.prototype, "stop", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core12.Property
|
|
], StopProperties.prototype, "color", 2);
|
|
function stopsAreAscending(fills) {
|
|
let currentStop;
|
|
for (const fill of fills) {
|
|
if (fill?.stop == null)
|
|
continue;
|
|
if (currentStop != null && fill.stop < currentStop) {
|
|
return false;
|
|
}
|
|
currentStop = fill.stop;
|
|
}
|
|
return true;
|
|
}
|
|
function discreteColorStops(colorStops) {
|
|
return colorStops.flatMap((colorStop, i) => {
|
|
const { stop } = colorStop;
|
|
const nextColor = colorStops.at(i + 1)?.color;
|
|
return nextColor == null ? [colorStop] : [colorStop, { stop, color: nextColor }];
|
|
});
|
|
}
|
|
function getDefaultColorStops(defaultColorStops, fillMode) {
|
|
const stopOffset = fillMode === "discrete" ? 1 : 0;
|
|
const colorStops = defaultColorStops.map(
|
|
(color, index, { length }) => ({
|
|
stop: (index + stopOffset) / (length - 1 + stopOffset),
|
|
color
|
|
})
|
|
);
|
|
return fillMode === "discrete" ? discreteColorStops(colorStops) : colorStops;
|
|
}
|
|
function getColorStops(baseFills, defaultColorStops, domain, fillMode = "continuous") {
|
|
const fills = baseFills.map((fill) => typeof fill === "string" ? { color: fill } : fill);
|
|
if (fills.length === 0) {
|
|
return getDefaultColorStops(defaultColorStops, fillMode);
|
|
} else if (!stopsAreAscending(fills)) {
|
|
import_ag_charts_core12.Logger.warnOnce(`[fills] must have the stops defined in ascending order`);
|
|
return [];
|
|
}
|
|
const d0 = Math.min(...domain);
|
|
const d1 = Math.max(...domain);
|
|
const isDiscrete = fillMode === "discrete";
|
|
const stops = new Float64Array(fills.length);
|
|
let previousDefinedStopIndex = 0;
|
|
let nextDefinedStopIndex = -1;
|
|
for (let i = 0; i < fills.length; i += 1) {
|
|
const colorStop = fills[i];
|
|
if (i >= nextDefinedStopIndex) {
|
|
nextDefinedStopIndex = fills.length - 1;
|
|
for (let j = i + 1; j < fills.length; j += 1) {
|
|
if (fills[j]?.stop != null) {
|
|
nextDefinedStopIndex = j;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
let stop = colorStop?.stop;
|
|
if (stop == null) {
|
|
const stop0 = fills[previousDefinedStopIndex]?.stop;
|
|
const stop1 = fills[nextDefinedStopIndex]?.stop;
|
|
const value0 = stop0 ?? d0;
|
|
const value1 = stop1 ?? d1;
|
|
const stopOffset = isDiscrete && stop0 == null ? 1 : 0;
|
|
stop = value0 + (value1 - value0) * (i - previousDefinedStopIndex + stopOffset) / (nextDefinedStopIndex - previousDefinedStopIndex + stopOffset);
|
|
} else {
|
|
previousDefinedStopIndex = i;
|
|
}
|
|
stops[i] = Math.max(0, Math.min(1, (stop - d0) / (d1 - d0)));
|
|
}
|
|
let lastDefinedColor = fills.find((c) => c.color != null)?.color;
|
|
let colorScale;
|
|
const colorStops = fills.map((fill, i) => {
|
|
let color = fill?.color;
|
|
const stop = stops[i];
|
|
if (color != null) {
|
|
lastDefinedColor = color;
|
|
} else if (lastDefinedColor == null) {
|
|
if (colorScale == null) {
|
|
colorScale = new ColorScale();
|
|
colorScale.domain = [0, 1];
|
|
colorScale.range = defaultColorStops;
|
|
}
|
|
color = colorScale.convert(stop);
|
|
} else {
|
|
color = lastDefinedColor;
|
|
}
|
|
return { stop, color };
|
|
});
|
|
return fillMode === "discrete" ? discreteColorStops(colorStops) : colorStops;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/image/image.ts
|
|
var import_ag_charts_core13 = require("ag-charts-core");
|
|
var Image = class {
|
|
constructor(imageLoader, imageOptions) {
|
|
this.imageLoader = imageLoader;
|
|
this._cache = void 0;
|
|
this.url = imageOptions.url;
|
|
this.backgroundFill = imageOptions.backgroundFill ?? "black";
|
|
this.backgroundFillOpacity = imageOptions.backgroundFillOpacity ?? 1;
|
|
this.repeat = imageOptions.repeat ?? "no-repeat";
|
|
this.width = imageOptions.width;
|
|
this.height = imageOptions.height;
|
|
this.fit = imageOptions.fit ?? "stretch";
|
|
this.rotation = imageOptions.rotation ?? 0;
|
|
}
|
|
createCanvasImage(ctx, image, width, height) {
|
|
if (!image)
|
|
return null;
|
|
const [renderedWidth, renderedHeight] = this.getSize(image.width, image.height, width, height);
|
|
if (renderedWidth < 1 || renderedHeight < 1) {
|
|
import_ag_charts_core13.Logger.warnOnce("Image fill is too small to render, ignoring.");
|
|
return null;
|
|
}
|
|
return ctx.createPattern(image, this.repeat);
|
|
}
|
|
getSize(imageWidth, imageHeight, width, height) {
|
|
const { fit } = this;
|
|
let dw = imageWidth;
|
|
let dh = imageHeight;
|
|
let scale = 1;
|
|
const shapeAspectRatio = width / height;
|
|
const imageAspectRatio = imageWidth / imageHeight;
|
|
if (fit === "stretch" || imageWidth === 0 || imageHeight === 0) {
|
|
dw = width;
|
|
dh = height;
|
|
} else if (fit === "contain") {
|
|
scale = imageAspectRatio > shapeAspectRatio ? width / imageWidth : height / imageHeight;
|
|
} else if (fit === "cover") {
|
|
scale = imageAspectRatio > shapeAspectRatio ? height / imageHeight : width / imageWidth;
|
|
}
|
|
return [Math.max(1, dw * scale), Math.max(1, dh * scale)];
|
|
}
|
|
setImageTransform(pattern, bbox) {
|
|
if (typeof pattern === "string")
|
|
return;
|
|
const { url, rotation, width, height } = this;
|
|
const image = this.imageLoader?.loadImage(url);
|
|
if (!image) {
|
|
return;
|
|
}
|
|
const angle = (0, import_ag_charts_core13.normalizeAngle360FromDegrees)(rotation);
|
|
const cos = Math.cos(angle);
|
|
const sin = Math.sin(angle);
|
|
const [renderedWidth, renderedHeight] = this.getSize(
|
|
image.width,
|
|
image.height,
|
|
width ?? bbox.width,
|
|
height ?? bbox.height
|
|
);
|
|
const widthScale = renderedWidth / image.width;
|
|
const heightScale = renderedHeight / image.height;
|
|
const bboxCenterX = bbox.x + bbox.width / 2;
|
|
const bboxCenterY = bbox.y + bbox.height / 2;
|
|
const rotatedW = cos * renderedWidth - sin * renderedHeight;
|
|
const rotatedH = sin * renderedWidth + cos * renderedHeight;
|
|
const shapeCenterX = rotatedW / 2;
|
|
const shapeCenterY = rotatedH / 2;
|
|
const DOMMatrixCtor = (0, import_ag_charts_core13.getDOMMatrix)();
|
|
pattern?.setTransform(
|
|
new DOMMatrixCtor([
|
|
cos * widthScale,
|
|
sin * heightScale,
|
|
-sin * widthScale,
|
|
cos * heightScale,
|
|
bboxCenterX - shapeCenterX,
|
|
bboxCenterY - shapeCenterY
|
|
])
|
|
);
|
|
}
|
|
createPattern(ctx, shapeWidth, shapeHeight, node) {
|
|
const width = this.width ?? shapeWidth;
|
|
const height = this.height ?? shapeHeight;
|
|
const cache = this._cache;
|
|
if (cache?.ctx === ctx && cache.width === width && cache.height === height) {
|
|
return cache.pattern;
|
|
}
|
|
const image = this.imageLoader?.loadImage(this.url, node);
|
|
const pattern = this.createCanvasImage(ctx, image, width, height);
|
|
if (pattern == null)
|
|
return;
|
|
this._cache = { ctx, pattern, width, height };
|
|
return pattern;
|
|
}
|
|
toSvg(bbox, pixelRatio) {
|
|
const { url, rotation, backgroundFill, backgroundFillOpacity } = this;
|
|
const { x, y, width, height } = bbox;
|
|
const pattern = (0, import_ag_charts_core13.createSvgElement)("pattern");
|
|
pattern.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
pattern.setAttribute("x", String(x));
|
|
pattern.setAttribute("y", String(y));
|
|
pattern.setAttribute("width", String(width));
|
|
pattern.setAttribute("height", String(height));
|
|
pattern.setAttribute("patternUnits", "userSpaceOnUse");
|
|
const rect = (0, import_ag_charts_core13.createSvgElement)("rect");
|
|
rect.setAttribute("x", "0");
|
|
rect.setAttribute("y", "0");
|
|
rect.setAttribute("width", String(width));
|
|
rect.setAttribute("height", String(height));
|
|
rect.setAttribute("fill", backgroundFill);
|
|
rect.setAttribute("fill-opacity", String(backgroundFillOpacity));
|
|
pattern.appendChild(rect);
|
|
const image = (0, import_ag_charts_core13.createSvgElement)("image");
|
|
image.setAttribute("href", url);
|
|
image.setAttribute("x", "0");
|
|
image.setAttribute("y", "0");
|
|
image.setAttribute("width", String(width));
|
|
image.setAttribute("height", String(height));
|
|
image.setAttribute("preserveAspectRatio", "none");
|
|
image.setAttribute("transform", `scale(${1 / pixelRatio}) rotate(${rotation}, ${width / 2}, ${height / 2})`);
|
|
pattern.appendChild(image);
|
|
return pattern;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/pattern/pattern.ts
|
|
var import_ag_charts_core17 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scene/extendedPath2D.ts
|
|
var import_ag_charts_core15 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/util/svg.ts
|
|
var import_ag_charts_core14 = require("ag-charts-core");
|
|
var commandEx = /^[\t\n\f\r ]*([achlmqstvz])[\t\n\f\r ]*/i;
|
|
var coordinateEx = /^[+-]?((\d*\.\d+)|(\d+\.)|(\d+))(e[+-]?\d+)?/i;
|
|
var commaEx = /[\t\n\f\r ]*,?[\t\n\f\r ]*/;
|
|
var flagEx = /^[01]/;
|
|
var pathParams = {
|
|
z: [],
|
|
h: [coordinateEx],
|
|
v: [coordinateEx],
|
|
m: [coordinateEx, coordinateEx],
|
|
l: [coordinateEx, coordinateEx],
|
|
t: [coordinateEx, coordinateEx],
|
|
s: [coordinateEx, coordinateEx, coordinateEx, coordinateEx],
|
|
q: [coordinateEx, coordinateEx, coordinateEx, coordinateEx],
|
|
c: [coordinateEx, coordinateEx, coordinateEx, coordinateEx, coordinateEx, coordinateEx],
|
|
a: [coordinateEx, coordinateEx, coordinateEx, flagEx, flagEx, coordinateEx, coordinateEx]
|
|
};
|
|
function parseSvg(d) {
|
|
if (!d)
|
|
return;
|
|
const segments = [];
|
|
let i = 0;
|
|
let currentCommand;
|
|
while (i < d.length) {
|
|
const commandMatch = commandEx.exec(d.slice(i));
|
|
let command;
|
|
if (commandMatch == null) {
|
|
if (!currentCommand) {
|
|
import_ag_charts_core14.Logger.warnOnce(`Invalid SVG path, error at index ${i}: Missing command.`);
|
|
return;
|
|
}
|
|
command = currentCommand;
|
|
} else {
|
|
command = commandMatch[1];
|
|
i += commandMatch[0].length;
|
|
}
|
|
const segment = parseSegment(command, d, i);
|
|
if (!segment)
|
|
return;
|
|
i = segment[0];
|
|
currentCommand = command;
|
|
segments.push(segment[1]);
|
|
}
|
|
return segments;
|
|
}
|
|
function parseSegment(command, d, index) {
|
|
const params = pathParams[command.toLocaleLowerCase()];
|
|
const pathSeg = { command, params: [] };
|
|
for (const regex of params) {
|
|
const segment = d.slice(index);
|
|
const match = regex.exec(segment);
|
|
if (match != null) {
|
|
pathSeg.params.push(Number.parseFloat(match[0]));
|
|
index += match[0].length;
|
|
const next = commaEx.exec(segment.slice(match[0].length));
|
|
if (next != null) {
|
|
index += next[0].length;
|
|
}
|
|
} else if (pathSeg.params.length === 1) {
|
|
return [index, pathSeg];
|
|
} else {
|
|
import_ag_charts_core14.Logger.warnOnce(
|
|
`Invalid SVG path, error at index ${index}: No path segment parameters for command [${command}]`
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
return [index, pathSeg];
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/polyRoots.ts
|
|
function linearRoot(a, b) {
|
|
const t = -b / a;
|
|
return a !== 0 && t >= 0 && t <= 1 ? [t] : [];
|
|
}
|
|
function quadraticRoots(a, b, c, delta3 = 1e-6) {
|
|
if (Math.abs(a) < delta3) {
|
|
return linearRoot(b, c);
|
|
}
|
|
const D = b * b - 4 * a * c;
|
|
const roots = [];
|
|
if (Math.abs(D) < delta3) {
|
|
const t = -b / (2 * a);
|
|
if (t >= 0 && t <= 1) {
|
|
roots.push(t);
|
|
}
|
|
} else if (D > 0) {
|
|
const rD = Math.sqrt(D);
|
|
const t1 = (-b - rD) / (2 * a);
|
|
const t2 = (-b + rD) / (2 * a);
|
|
if (t1 >= 0 && t1 <= 1) {
|
|
roots.push(t1);
|
|
}
|
|
if (t2 >= 0 && t2 <= 1) {
|
|
roots.push(t2);
|
|
}
|
|
}
|
|
return roots;
|
|
}
|
|
function cubicRoots(a, b, c, d, delta3 = 1e-6) {
|
|
if (Math.abs(a) < delta3) {
|
|
return quadraticRoots(b, c, d, delta3);
|
|
}
|
|
const A = b / a;
|
|
const B = c / a;
|
|
const C = d / a;
|
|
const Q = (3 * B - A * A) / 9;
|
|
const R = (9 * A * B - 27 * C - 2 * A * A * A) / 54;
|
|
const D = Q * Q * Q + R * R;
|
|
const third = 1 / 3;
|
|
const roots = [];
|
|
if (D >= 0) {
|
|
const rD = Math.sqrt(D);
|
|
const S = Math.sign(R + rD) * Math.pow(Math.abs(R + rD), third);
|
|
const T = Math.sign(R - rD) * Math.pow(Math.abs(R - rD), third);
|
|
const Im = Math.abs(Math.sqrt(3) * (S - T) / 2);
|
|
const t = -third * A + (S + T);
|
|
if (t >= 0 && t <= 1) {
|
|
roots.push(t);
|
|
}
|
|
if (Math.abs(Im) < delta3) {
|
|
const t2 = -third * A - (S + T) / 2;
|
|
if (t2 >= 0 && t2 <= 1) {
|
|
roots.push(t2);
|
|
}
|
|
}
|
|
} else {
|
|
const theta = Math.acos(R / Math.sqrt(-Q * Q * Q));
|
|
const thirdA = third * A;
|
|
const twoSqrtQ = 2 * Math.sqrt(-Q);
|
|
const t1 = twoSqrtQ * Math.cos(third * theta) - thirdA;
|
|
const t2 = twoSqrtQ * Math.cos(third * (theta + 2 * Math.PI)) - thirdA;
|
|
const t3 = twoSqrtQ * Math.cos(third * (theta + 4 * Math.PI)) - thirdA;
|
|
if (t1 >= 0 && t1 <= 1) {
|
|
roots.push(t1);
|
|
}
|
|
if (t2 >= 0 && t2 <= 1) {
|
|
roots.push(t2);
|
|
}
|
|
if (t3 >= 0 && t3 <= 1) {
|
|
roots.push(t3);
|
|
}
|
|
}
|
|
return roots;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/intersection.ts
|
|
function segmentIntersection(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) {
|
|
const d = (ax2 - ax1) * (by2 - by1) - (ay2 - ay1) * (bx2 - bx1);
|
|
if (d === 0) {
|
|
return 0;
|
|
}
|
|
const ua = ((bx2 - bx1) * (ay1 - by1) - (ax1 - bx1) * (by2 - by1)) / d;
|
|
const ub = ((ax2 - ax1) * (ay1 - by1) - (ay2 - ay1) * (ax1 - bx1)) / d;
|
|
if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
function cubicSegmentIntersections(px1, py1, px2, py2, px3, py3, px4, py4, x1, y1, x2, y2) {
|
|
let intersections = 0;
|
|
const A = y1 - y2;
|
|
const B = x2 - x1;
|
|
const C = x1 * (y2 - y1) - y1 * (x2 - x1);
|
|
const bx = bezierCoefficients(px1, px2, px3, px4);
|
|
const by = bezierCoefficients(py1, py2, py3, py4);
|
|
const a = A * bx[0] + B * by[0];
|
|
const b = A * bx[1] + B * by[1];
|
|
const c = A * bx[2] + B * by[2];
|
|
const d = A * bx[3] + B * by[3] + C;
|
|
const roots = cubicRoots(a, b, c, d);
|
|
for (const t of roots) {
|
|
const tt = t * t;
|
|
const ttt = t * tt;
|
|
const x = bx[0] * ttt + bx[1] * tt + bx[2] * t + bx[3];
|
|
const y = by[0] * ttt + by[1] * tt + by[2] * t + by[3];
|
|
let s;
|
|
if (x1 === x2) {
|
|
s = (y - y1) / (y2 - y1);
|
|
} else {
|
|
s = (x - x1) / (x2 - x1);
|
|
}
|
|
if (s >= 0 && s <= 1) {
|
|
intersections++;
|
|
}
|
|
}
|
|
return intersections;
|
|
}
|
|
function bezierCoefficients(P1, P2, P3, P4) {
|
|
return [
|
|
// Bézier expressed as matrix operations:
|
|
// |-1 3 -3 1| |P1|
|
|
// [t^3 t^2 t 1] | 3 -6 3 0| |P2|
|
|
// |-3 3 0 0| |P3|
|
|
// | 1 0 0 0| |P4|
|
|
-P1 + 3 * P2 - 3 * P3 + P4,
|
|
3 * P1 - 6 * P2 + 3 * P3,
|
|
-3 * P1 + 3 * P2,
|
|
P1
|
|
];
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/extendedPath2D.ts
|
|
var ExtendedPath2D = class {
|
|
constructor() {
|
|
this.previousCommands = [];
|
|
this.previousParams = [];
|
|
this.previousClosedPath = false;
|
|
this.commands = [];
|
|
this.params = [];
|
|
this.commandsLength = 0;
|
|
this.paramsLength = 0;
|
|
this.cx = Number.NaN;
|
|
this.cy = Number.NaN;
|
|
this.sx = Number.NaN;
|
|
this.sy = Number.NaN;
|
|
this.openedPath = false;
|
|
this.closedPath = false;
|
|
const Path2DCtor = (0, import_ag_charts_core15.getPath2D)();
|
|
this.path2d = new Path2DCtor();
|
|
}
|
|
isEmpty() {
|
|
return this.commandsLength === 0;
|
|
}
|
|
isDirty() {
|
|
return this.closedPath !== this.previousClosedPath || this.previousCommands.length !== this.commandsLength || this.previousParams.length !== this.paramsLength || this.previousCommands.toString() !== this.commands.slice(0, this.commandsLength).toString() || this.previousParams.toString() !== this.params.slice(0, this.paramsLength).toString();
|
|
}
|
|
getPath2D() {
|
|
return this.path2d;
|
|
}
|
|
moveTo(x, y) {
|
|
this.openedPath = true;
|
|
this.sx = x;
|
|
this.sy = y;
|
|
this.cx = x;
|
|
this.cy = y;
|
|
this.path2d.moveTo(x, y);
|
|
this.commands[this.commandsLength++] = 0 /* Move */;
|
|
this.params[this.paramsLength++] = x;
|
|
this.params[this.paramsLength++] = y;
|
|
}
|
|
lineTo(x, y) {
|
|
if (this.openedPath) {
|
|
this.cx = x;
|
|
this.cy = y;
|
|
this.path2d.lineTo(x, y);
|
|
this.commands[this.commandsLength++] = 1 /* Line */;
|
|
this.params[this.paramsLength++] = x;
|
|
this.params[this.paramsLength++] = y;
|
|
} else {
|
|
this.moveTo(x, y);
|
|
}
|
|
}
|
|
cubicCurveTo(cx1, cy1, cx2, cy2, x, y) {
|
|
if (!this.openedPath) {
|
|
this.moveTo(cx1, cy1);
|
|
}
|
|
this.path2d.bezierCurveTo(cx1, cy1, cx2, cy2, x, y);
|
|
this.commands[this.commandsLength++] = 2 /* Curve */;
|
|
this.params[this.paramsLength++] = cx1;
|
|
this.params[this.paramsLength++] = cy1;
|
|
this.params[this.paramsLength++] = cx2;
|
|
this.params[this.paramsLength++] = cy2;
|
|
this.params[this.paramsLength++] = x;
|
|
this.params[this.paramsLength++] = y;
|
|
}
|
|
closePath() {
|
|
if (this.openedPath) {
|
|
this.cx = this.sx;
|
|
this.cy = this.sy;
|
|
this.sx = Number.NaN;
|
|
this.sy = Number.NaN;
|
|
this.path2d.closePath();
|
|
this.commands[this.commandsLength++] = 3 /* ClosePath */;
|
|
this.openedPath = false;
|
|
this.closedPath = true;
|
|
}
|
|
}
|
|
rect(x, y, width, height) {
|
|
this.moveTo(x, y);
|
|
this.lineTo(x + width, y);
|
|
this.lineTo(x + width, y + height);
|
|
this.lineTo(x, y + height);
|
|
this.closePath();
|
|
}
|
|
roundRect(x, y, width, height, radii) {
|
|
radii = Math.min(radii, width / 2, height / 2);
|
|
this.moveTo(x, y + radii);
|
|
this.arc(x + radii, y + radii, radii, Math.PI, 1.5 * Math.PI);
|
|
this.lineTo(x + radii, y);
|
|
this.lineTo(x + width - radii, y);
|
|
this.arc(x + width - radii, y + radii, radii, 1.5 * Math.PI, 2 * Math.PI);
|
|
this.lineTo(x + width, y + radii);
|
|
this.lineTo(x + width, y + height - radii);
|
|
this.arc(x + width - radii, y + height - radii, radii, 0, Math.PI / 2);
|
|
this.lineTo(x + width - radii, y + height);
|
|
this.lineTo(x + radii, y + height);
|
|
this.arc(x + +radii, y + height - radii, radii, Math.PI / 2, Math.PI);
|
|
this.lineTo(x, y + height - radii);
|
|
this.closePath();
|
|
}
|
|
ellipse(cx, cy, rx, ry, rotation, sAngle, eAngle, counterClockwise = false) {
|
|
const r = rx;
|
|
const scaleY = ry / rx;
|
|
const mxx = Math.cos(rotation);
|
|
const myx = Math.sin(rotation);
|
|
const mxy = -scaleY * myx;
|
|
const myy = scaleY * mxx;
|
|
const x0 = r * Math.cos(sAngle);
|
|
const y0 = r * Math.sin(sAngle);
|
|
const sx = cx + mxx * x0 + mxy * y0;
|
|
const sy = cy + myx * x0 + myy * y0;
|
|
const distanceSquared = (sx - this.cx) ** 2 + (sy - this.cy) ** 2;
|
|
if (!this.openedPath) {
|
|
this.moveTo(sx, sy);
|
|
} else if (distanceSquared > 1e-6) {
|
|
this.lineTo(sx, sy);
|
|
}
|
|
let sweep = counterClockwise ? -(0, import_ag_charts_core15.normalizeAngle360)(sAngle - eAngle) : (0, import_ag_charts_core15.normalizeAngle360)(eAngle - sAngle);
|
|
if (Math.abs(Math.abs(eAngle - sAngle) - 2 * Math.PI) < 1e-6 && sweep < 2 * Math.PI) {
|
|
sweep += 2 * Math.PI * (counterClockwise ? -1 : 1);
|
|
}
|
|
const arcSections = Math.max(Math.ceil(Math.abs(sweep) / (Math.PI / 2)), 1);
|
|
const step = sweep / arcSections;
|
|
const h = 4 / 3 * Math.tan(step / 4);
|
|
for (let i = 0; i < arcSections; i += 1) {
|
|
const a0 = sAngle + step * (i + 0);
|
|
const a1 = sAngle + step * (i + 1);
|
|
const rSinStart = r * Math.sin(a0);
|
|
const rCosStart = r * Math.cos(a0);
|
|
const rSinEnd = r * Math.sin(a1);
|
|
const rCosEnd = r * Math.cos(a1);
|
|
const cp1x = rCosStart - h * rSinStart;
|
|
const cp1y = rSinStart + h * rCosStart;
|
|
const cp2x = rCosEnd + h * rSinEnd;
|
|
const cp2y = rSinEnd - h * rCosEnd;
|
|
const cp3x = rCosEnd;
|
|
const cp3y = rSinEnd;
|
|
this.cubicCurveTo(
|
|
cx + mxx * cp1x + mxy * cp1y,
|
|
cy + myx * cp1x + myy * cp1y,
|
|
cx + mxx * cp2x + mxy * cp2y,
|
|
cy + myx * cp2x + myy * cp2y,
|
|
cx + mxx * cp3x + mxy * cp3y,
|
|
cy + myx * cp3x + myy * cp3y
|
|
);
|
|
}
|
|
}
|
|
arc(x, y, r, sAngle, eAngle, counterClockwise) {
|
|
this.ellipse(x, y, r, r, 0, sAngle, eAngle, counterClockwise);
|
|
}
|
|
appendSvg(svg) {
|
|
const parts = parseSvg(svg);
|
|
if (parts == null)
|
|
return false;
|
|
let sx = 0;
|
|
let sy = 0;
|
|
let cx;
|
|
let cy;
|
|
let cpx = 0;
|
|
let cpy = 0;
|
|
for (const { command, params } of parts) {
|
|
cx ?? (cx = params[0]);
|
|
cy ?? (cy = params[1]);
|
|
const relative = command === command.toLowerCase();
|
|
const dx = relative ? cx : 0;
|
|
const dy = relative ? cy : 0;
|
|
switch (command.toLowerCase()) {
|
|
case "m":
|
|
this.moveTo(dx + params[0], dy + params[1]);
|
|
cx = dx + params[0];
|
|
cy = dy + params[1];
|
|
sx = cx;
|
|
sy = cy;
|
|
break;
|
|
case "c":
|
|
this.cubicCurveTo(
|
|
dx + params[0],
|
|
dy + params[1],
|
|
dx + params[2],
|
|
dy + params[3],
|
|
dx + params[4],
|
|
dy + params[5]
|
|
);
|
|
cpx = dx + params[2];
|
|
cpy = dy + params[3];
|
|
cx = dx + params[4];
|
|
cy = dy + params[5];
|
|
break;
|
|
case "s":
|
|
this.cubicCurveTo(
|
|
cx + cx - cpx,
|
|
cy + cy - cpy,
|
|
dx + params[0],
|
|
dy + params[1],
|
|
dx + params[2],
|
|
dy + params[3]
|
|
);
|
|
cpx = dx + params[0];
|
|
cpy = dy + params[1];
|
|
cx = dx + params[2];
|
|
cy = dy + params[3];
|
|
break;
|
|
case "q":
|
|
this.cubicCurveTo(
|
|
(dx + 2 * params[0]) / 3,
|
|
(dy + 2 * params[1]) / 3,
|
|
(2 * params[0] + params[2]) / 3,
|
|
(2 * params[1] + params[3]) / 3,
|
|
params[2],
|
|
params[3]
|
|
);
|
|
cpx = params[0];
|
|
cpy = params[1];
|
|
cx = params[2];
|
|
cy = params[3];
|
|
break;
|
|
case "t":
|
|
this.cubicCurveTo(
|
|
(cx + 2 * (cx + cx - cpx)) / 3,
|
|
(cy + 2 * (cy + cy - cpy)) / 3,
|
|
(2 * (cx + cx - cpx) + params[0]) / 3,
|
|
(2 * (cy + cy - cpy) + params[1]) / 3,
|
|
params[0],
|
|
params[1]
|
|
);
|
|
cpx = cx + cx - cpx;
|
|
cpy = cy + cy - cpy;
|
|
cx = params[0];
|
|
cy = params[1];
|
|
break;
|
|
case "a":
|
|
this.svgEllipse(
|
|
cx,
|
|
cy,
|
|
params[0],
|
|
params[1],
|
|
params[2] * Math.PI / 180,
|
|
params[3],
|
|
params[4],
|
|
dx + params[5],
|
|
dy + params[6]
|
|
);
|
|
cx = dx + params[5];
|
|
cy = dy + params[6];
|
|
break;
|
|
case "h":
|
|
this.lineTo(dx + params[0], cy);
|
|
cx = dx + params[0];
|
|
break;
|
|
case "l":
|
|
this.lineTo(dx + params[0], dy + params[1]);
|
|
cx = dx + params[0];
|
|
cy = dy + params[1];
|
|
break;
|
|
case "v":
|
|
this.lineTo(cx, dy + params[0]);
|
|
cy = dy + params[0];
|
|
break;
|
|
case "z":
|
|
this.closePath();
|
|
cx = sx;
|
|
cy = sy;
|
|
break;
|
|
default:
|
|
throw new Error(`Could not translate command '${command}' with '${params.join(" ")}'`);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
svgEllipse(x1, y1, rx, ry, rotation, fA, fS, x2, y2) {
|
|
rx = Math.abs(rx);
|
|
ry = Math.abs(ry);
|
|
const dx = (x1 - x2) / 2;
|
|
const dy = (y1 - y2) / 2;
|
|
const sin = Math.sin(rotation);
|
|
const cos = Math.cos(rotation);
|
|
const rotX = cos * dx + sin * dy;
|
|
const rotY = -sin * dx + cos * dy;
|
|
const normX = rotX / rx;
|
|
const normY = rotY / ry;
|
|
let scale = normX * normX + normY * normY;
|
|
let cx = (x1 + x2) / 2;
|
|
let cy = (y1 + y2) / 2;
|
|
let cpx = 0;
|
|
let cpy = 0;
|
|
if (scale >= 1) {
|
|
scale = Math.sqrt(scale);
|
|
rx *= scale;
|
|
ry *= scale;
|
|
} else {
|
|
scale = Math.sqrt(1 / scale - 1);
|
|
if (fA === fS)
|
|
scale = -scale;
|
|
cpx = scale * rx * normY;
|
|
cpy = -scale * ry * normX;
|
|
cx += cos * cpx - sin * cpy;
|
|
cy += sin * cpx + cos * cpy;
|
|
}
|
|
const sAngle = Math.atan2((rotY - cpy) / ry, (rotX - cpx) / rx);
|
|
const deltaTheta = Math.atan2((-rotY - cpy) / ry, (-rotX - cpx) / rx) - sAngle;
|
|
const eAngle = sAngle + deltaTheta;
|
|
const counterClockwise = !!(1 - fS);
|
|
this.ellipse(cx, cy, rx, ry, rotation, sAngle, eAngle, counterClockwise);
|
|
}
|
|
clear(trackChanges) {
|
|
if (trackChanges) {
|
|
this.previousCommands = this.commands.slice(0, this.commandsLength);
|
|
this.previousParams = this.params.slice(0, this.paramsLength);
|
|
this.previousClosedPath = this.closedPath;
|
|
this.commands = [];
|
|
this.params = [];
|
|
this.commandsLength = 0;
|
|
this.paramsLength = 0;
|
|
} else {
|
|
this.commandsLength = 0;
|
|
this.paramsLength = 0;
|
|
}
|
|
const Path2DCtor = (0, import_ag_charts_core15.getPath2D)();
|
|
this.path2d = new Path2DCtor();
|
|
this.openedPath = false;
|
|
this.closedPath = false;
|
|
}
|
|
isPointInPath(x, y) {
|
|
const commands = this.commands;
|
|
const params = this.params;
|
|
const cn = this.commandsLength;
|
|
const ox = -1e4;
|
|
const oy = -1e4;
|
|
let sx = Number.NaN;
|
|
let sy = Number.NaN;
|
|
let px = 0;
|
|
let py = 0;
|
|
let intersectionCount = 0;
|
|
for (let ci = 0, pi = 0; ci < cn; ci++) {
|
|
switch (commands[ci]) {
|
|
case 0 /* Move */:
|
|
intersectionCount += segmentIntersection(sx, sy, px, py, ox, oy, x, y);
|
|
px = params[pi++];
|
|
sx = px;
|
|
py = params[pi++];
|
|
sy = py;
|
|
break;
|
|
case 1 /* Line */:
|
|
intersectionCount += segmentIntersection(px, py, params[pi++], params[pi++], ox, oy, x, y);
|
|
px = params[pi - 2];
|
|
py = params[pi - 1];
|
|
break;
|
|
case 2 /* Curve */:
|
|
intersectionCount += cubicSegmentIntersections(
|
|
px,
|
|
py,
|
|
params[pi++],
|
|
params[pi++],
|
|
params[pi++],
|
|
params[pi++],
|
|
params[pi++],
|
|
params[pi++],
|
|
ox,
|
|
oy,
|
|
x,
|
|
y
|
|
);
|
|
px = params[pi - 2];
|
|
py = params[pi - 1];
|
|
break;
|
|
case 3 /* ClosePath */:
|
|
intersectionCount += segmentIntersection(sx, sy, px, py, ox, oy, x, y);
|
|
break;
|
|
}
|
|
}
|
|
return intersectionCount % 2 === 1;
|
|
}
|
|
distanceSquared(x, y) {
|
|
let best = Infinity;
|
|
const commands = this.commands;
|
|
const params = this.params;
|
|
const cn = this.commandsLength;
|
|
let sx = Number.NaN;
|
|
let sy = Number.NaN;
|
|
let cx = 0;
|
|
let cy = 0;
|
|
for (let ci = 0, pi = 0; ci < cn; ci++) {
|
|
switch (commands[ci]) {
|
|
case 0 /* Move */:
|
|
cx = sx = params[pi++];
|
|
cy = sy = params[pi++];
|
|
break;
|
|
case 1 /* Line */: {
|
|
const x0 = cx;
|
|
const y0 = cy;
|
|
cx = params[pi++];
|
|
cy = params[pi++];
|
|
best = (0, import_ag_charts_core15.lineDistanceSquared)(x, y, x0, y0, cx, cy, best);
|
|
break;
|
|
}
|
|
case 2 /* Curve */: {
|
|
const cp0x = cx;
|
|
const cp0y = cy;
|
|
const cp1x = params[pi++];
|
|
const cp1y = params[pi++];
|
|
const cp2x = params[pi++];
|
|
const cp2y = params[pi++];
|
|
cx = params[pi++];
|
|
cy = params[pi++];
|
|
best = (0, import_ag_charts_core15.bezier2DDistance)(cp0x, cp0y, cp1x, cp1y, cp2x, cp2y, cx, cy, x, y) ** 2;
|
|
break;
|
|
}
|
|
case 3 /* ClosePath */:
|
|
best = (0, import_ag_charts_core15.lineDistanceSquared)(x, y, cx, cy, sx, sy, best);
|
|
break;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
|
|
toSVG(transform = (x, y) => ({ x, y })) {
|
|
const buffer = [];
|
|
const { commands, params } = this;
|
|
const addCommand = (command, count) => {
|
|
buffer.push(command);
|
|
for (let i = 0; i < count; i += 2) {
|
|
const { x, y } = transform(params[pi++], params[pi++]);
|
|
buffer.push(x, y);
|
|
}
|
|
};
|
|
let pi = 0;
|
|
for (let ci = 0; ci < this.commandsLength; ci++) {
|
|
const command = commands[ci];
|
|
switch (command) {
|
|
case 0 /* Move */:
|
|
addCommand("M", 2);
|
|
break;
|
|
case 1 /* Line */:
|
|
addCommand("L", 2);
|
|
break;
|
|
case 2 /* Curve */:
|
|
addCommand("C", 6);
|
|
break;
|
|
case 3 /* ClosePath */:
|
|
addCommand("Z", 0);
|
|
break;
|
|
}
|
|
}
|
|
return buffer.join(" ");
|
|
}
|
|
computeBBox() {
|
|
const { commands, params } = this;
|
|
let [top, left, right, bot] = [Infinity, Infinity, -Infinity, -Infinity];
|
|
let [cx, cy] = [Number.NaN, Number.NaN];
|
|
let [sx, sy] = [Number.NaN, Number.NaN];
|
|
const joinPoint = (x, y) => {
|
|
top = Math.min(y, top);
|
|
left = Math.min(x, left);
|
|
right = Math.max(x, right);
|
|
bot = Math.max(y, bot);
|
|
cx = x;
|
|
cy = y;
|
|
};
|
|
let pi = 0;
|
|
for (let ci = 0; ci < this.commandsLength; ci++) {
|
|
const command = commands[ci];
|
|
switch (command) {
|
|
case 0 /* Move */:
|
|
joinPoint(params[pi++], params[pi++]);
|
|
sx = cx;
|
|
sy = cy;
|
|
break;
|
|
case 1 /* Line */:
|
|
joinPoint(params[pi++], params[pi++]);
|
|
break;
|
|
case 2 /* Curve */: {
|
|
const cp0x = cx;
|
|
const cp0y = cy;
|
|
const cp1x = params[pi++];
|
|
const cp1y = params[pi++];
|
|
const cp2x = params[pi++];
|
|
const cp2y = params[pi++];
|
|
const cp3x = params[pi++];
|
|
const cp3y = params[pi++];
|
|
const ts = (0, import_ag_charts_core15.bezier2DExtrema)(cp0x, cp0y, cp1x, cp1y, cp2x, cp2y, cp3x, cp3y);
|
|
for (const t of ts) {
|
|
const px = (0, import_ag_charts_core15.evaluateBezier)(cp0x, cp1x, cp2x, cp3x, t);
|
|
const py = (0, import_ag_charts_core15.evaluateBezier)(cp0y, cp1y, cp2y, cp3y, t);
|
|
joinPoint(px, py);
|
|
}
|
|
joinPoint(cp3x, cp3y);
|
|
break;
|
|
}
|
|
case 3 /* ClosePath */:
|
|
joinPoint(sx, sy);
|
|
sx = Number.NaN;
|
|
sy = Number.NaN;
|
|
break;
|
|
}
|
|
}
|
|
return new BBox(left, top, right - left, bot - top);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/pattern/patterns.ts
|
|
var import_ag_charts_core16 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scene/util/pixel.ts
|
|
function align(pixelRatio, start, length) {
|
|
const alignedStart = Math.round(start * pixelRatio) / pixelRatio;
|
|
if (length == null) {
|
|
return alignedStart;
|
|
} else if (length === 0) {
|
|
return 0;
|
|
} else if (length < 1) {
|
|
return Math.ceil(length * pixelRatio) / pixelRatio;
|
|
}
|
|
return Math.round((length + start) * pixelRatio) / pixelRatio - alignedStart;
|
|
}
|
|
function alignBefore(pixelRatio, start) {
|
|
return Math.floor(start * pixelRatio) / pixelRatio;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/pattern/patterns.ts
|
|
function drawPatternUnitPolygon(path, params, moves) {
|
|
const { width, height, padding, strokeWidth } = params;
|
|
const x0 = width / 2;
|
|
const y0 = height / 2;
|
|
const w = Math.max(1, width - padding - strokeWidth / 2);
|
|
const h = Math.max(1, height - padding - strokeWidth / 2);
|
|
let didMove = false;
|
|
for (const [dx, dy] of moves) {
|
|
const x = x0 + (dx - 0.5) * w;
|
|
const y = y0 + (dy - 0.5) * h;
|
|
if (didMove) {
|
|
path.lineTo(x, y);
|
|
} else {
|
|
path.moveTo(x, y);
|
|
}
|
|
didMove = true;
|
|
}
|
|
path.closePath();
|
|
}
|
|
var PATTERNS = {
|
|
circles(path, { width, strokeWidth, padding }) {
|
|
const c = width / 2;
|
|
const r = Math.max(1, c - padding - strokeWidth / 2);
|
|
path.arc(c, c, r, 0, Math.PI * 2);
|
|
},
|
|
squares(path, { width, height, pixelRatio, padding, strokeWidth }) {
|
|
const offset = padding + strokeWidth / 2;
|
|
path.moveTo(align(pixelRatio, offset), align(pixelRatio, offset));
|
|
path.lineTo(align(pixelRatio, width - offset), align(pixelRatio, offset));
|
|
path.lineTo(align(pixelRatio, width - offset), align(pixelRatio, height - offset));
|
|
path.lineTo(align(pixelRatio, offset), align(pixelRatio, height - offset));
|
|
path.closePath();
|
|
},
|
|
triangles(path, params) {
|
|
drawPatternUnitPolygon(path, params, [
|
|
[0.5, 0],
|
|
[1, 1],
|
|
[0, 1]
|
|
]);
|
|
},
|
|
diamonds(path, params) {
|
|
drawPatternUnitPolygon(path, params, [
|
|
[0.5, 0],
|
|
[1, 0.5],
|
|
[0.5, 1],
|
|
[0, 0.5]
|
|
]);
|
|
},
|
|
stars(path, { width, height, padding }) {
|
|
const spikes = 5;
|
|
const outerRadius = Math.max(1, (width - padding) / 2);
|
|
const innerRadius = outerRadius / 2;
|
|
const rotation = Math.PI / 2;
|
|
for (let i = 0; i < spikes * 2; i++) {
|
|
const radius = i % 2 === 0 ? outerRadius : innerRadius;
|
|
const angle = i * Math.PI / spikes - rotation;
|
|
const xCoordinate = width / 2 + Math.cos(angle) * radius;
|
|
const yCoordinate = height / 2 + Math.sin(angle) * radius;
|
|
path.lineTo(xCoordinate, yCoordinate);
|
|
}
|
|
path.closePath();
|
|
},
|
|
hearts(path, { width, height, padding }) {
|
|
const r = Math.max(1, width / 4 - padding / 2);
|
|
const x = width / 2;
|
|
const y = height / 2 + r / 2;
|
|
path.arc(x - r, y - r, r, (0, import_ag_charts_core16.toRadians)(130), (0, import_ag_charts_core16.toRadians)(330));
|
|
path.arc(x + r, y - r, r, (0, import_ag_charts_core16.toRadians)(220), (0, import_ag_charts_core16.toRadians)(50));
|
|
path.lineTo(x, y + r);
|
|
path.closePath();
|
|
},
|
|
crosses(path, params) {
|
|
drawPatternUnitPolygon(path, params, [
|
|
[0.25, 0],
|
|
[0.5, 0.25],
|
|
[0.75, 0],
|
|
[1, 0.25],
|
|
[0.75, 0.5],
|
|
[1, 0.75],
|
|
[0.75, 1],
|
|
[0.5, 0.75],
|
|
[0.25, 1],
|
|
[0, 0.75],
|
|
[0.25, 0.5],
|
|
[0, 0.25]
|
|
]);
|
|
},
|
|
"vertical-lines"(path, { width, height, pixelRatio, strokeWidth }) {
|
|
const x = align(pixelRatio, width / 2) - strokeWidth % 2 / 2;
|
|
path.moveTo(x, 0);
|
|
path.lineTo(x, height);
|
|
},
|
|
"horizontal-lines"(path, { width, height, pixelRatio, strokeWidth }) {
|
|
const y = align(pixelRatio, height / 2) - strokeWidth % 2 / 2;
|
|
path.moveTo(0, y);
|
|
path.lineTo(width, y);
|
|
},
|
|
"forward-slanted-lines"(path, { width, height, strokeWidth }) {
|
|
const angle = Math.atan2(height, width);
|
|
const insetX = strokeWidth * Math.cos(angle);
|
|
const insetY = strokeWidth * Math.sin(angle);
|
|
path.moveTo(-insetX, insetY);
|
|
path.lineTo(insetX, -insetY);
|
|
path.moveTo(-insetX, height + insetY);
|
|
path.lineTo(width + insetX, -insetY);
|
|
path.moveTo(width - insetX, height + insetY);
|
|
path.lineTo(width + insetX, height - insetY);
|
|
},
|
|
"backward-slanted-lines"(path, { width, height, strokeWidth }) {
|
|
const angle = Math.atan2(height, width);
|
|
const insetX = strokeWidth * Math.cos(angle);
|
|
const insetY = strokeWidth * Math.sin(angle);
|
|
path.moveTo(width - insetX, -insetY);
|
|
path.lineTo(width + insetX, insetY);
|
|
path.moveTo(-insetX, -insetY);
|
|
path.lineTo(width + insetX, height + insetY);
|
|
path.moveTo(-insetX, height - insetY);
|
|
path.lineTo(insetX, height + insetY);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/pattern/pattern.ts
|
|
var Pattern = class {
|
|
constructor(patternOptions) {
|
|
this._cache = void 0;
|
|
this.width = Math.max(patternOptions?.width ?? 10, 1);
|
|
this.height = Math.max(patternOptions?.height ?? 10, 1);
|
|
this.fill = patternOptions.fill ?? "none";
|
|
this.fillOpacity = patternOptions.fillOpacity ?? 1;
|
|
this.backgroundFill = patternOptions.backgroundFill ?? "none";
|
|
this.backgroundFillOpacity = patternOptions.backgroundFillOpacity ?? 1;
|
|
this.stroke = patternOptions.stroke ?? "black";
|
|
this.strokeOpacity = patternOptions.strokeOpacity ?? 1;
|
|
this.strokeWidth = patternOptions.strokeWidth ?? 1;
|
|
this.padding = patternOptions.padding ?? 1;
|
|
this.pattern = patternOptions.pattern ?? "forward-slanted-lines";
|
|
this.rotation = patternOptions.rotation ?? 0;
|
|
this.scale = patternOptions.scale ?? 1;
|
|
this.path = patternOptions.path;
|
|
}
|
|
getPath(pixelRatio) {
|
|
const { pattern, width, height, padding, strokeWidth, path: svgPath } = this;
|
|
const path = new ExtendedPath2D();
|
|
let renderPattern = PATTERNS[pattern] != null;
|
|
if (svgPath) {
|
|
renderPattern && (renderPattern = !path.appendSvg(svgPath));
|
|
}
|
|
if (renderPattern) {
|
|
PATTERNS[pattern](path, { width, height, pixelRatio, strokeWidth, padding });
|
|
}
|
|
return path;
|
|
}
|
|
renderStroke(path2d, ctx) {
|
|
const { stroke, strokeWidth, strokeOpacity } = this;
|
|
if (!strokeWidth)
|
|
return;
|
|
ctx.strokeStyle = stroke;
|
|
ctx.lineWidth = strokeWidth;
|
|
ctx.globalAlpha = strokeOpacity;
|
|
ctx.stroke(path2d);
|
|
}
|
|
renderFill(path2d, ctx) {
|
|
const { fill, fillOpacity } = this;
|
|
if (fill === "none") {
|
|
return;
|
|
}
|
|
ctx.fillStyle = fill;
|
|
ctx.globalAlpha = fillOpacity;
|
|
ctx.fill(path2d);
|
|
}
|
|
createCanvasPattern(ctx, pixelRatio) {
|
|
const { width, height, scale, backgroundFill, backgroundFillOpacity } = this;
|
|
if (width * scale < 1 || height * scale < 1) {
|
|
import_ag_charts_core17.Logger.warnOnce("Pattern fill is too small to render, ignoring.");
|
|
return null;
|
|
}
|
|
const offscreenPattern = new HdpiOffscreenCanvas({ width, height, pixelRatio: pixelRatio * scale });
|
|
const offscreenPatternCtx = offscreenPattern.context;
|
|
if (backgroundFill !== "none") {
|
|
offscreenPatternCtx.fillStyle = backgroundFill;
|
|
offscreenPatternCtx.globalAlpha = backgroundFillOpacity;
|
|
offscreenPatternCtx.fillRect(0, 0, width, height);
|
|
}
|
|
const path2d = this.getPath(pixelRatio).getPath2D();
|
|
this.renderFill(path2d, offscreenPatternCtx);
|
|
this.renderStroke(path2d, offscreenPatternCtx);
|
|
const pattern = ctx.createPattern(offscreenPattern.canvas, "repeat");
|
|
this.setPatternTransform(pattern, pixelRatio);
|
|
offscreenPattern.destroy();
|
|
return pattern;
|
|
}
|
|
setPatternTransform(pattern, pixelRatio, tx = 0, ty = 0) {
|
|
if (pattern == null)
|
|
return;
|
|
const angle = (0, import_ag_charts_core17.normalizeAngle360FromDegrees)(this.rotation);
|
|
const scale = 1 / pixelRatio;
|
|
const cos = Math.cos(angle) * scale;
|
|
const sin = Math.sin(angle) * scale;
|
|
const DOMMatrixCtor = (0, import_ag_charts_core17.getDOMMatrix)();
|
|
pattern.setTransform(new DOMMatrixCtor([cos, sin, -sin, cos, tx, ty]));
|
|
}
|
|
createPattern(ctx, pixelRatio) {
|
|
if (this._cache?.ctx === ctx && this._cache.pixelRatio === pixelRatio) {
|
|
return this._cache.pattern;
|
|
}
|
|
const pattern = this.createCanvasPattern(ctx, pixelRatio);
|
|
if (pattern == null)
|
|
return;
|
|
this._cache = { ctx, pattern, pixelRatio };
|
|
return pattern;
|
|
}
|
|
toSvg() {
|
|
const {
|
|
width,
|
|
height,
|
|
fill,
|
|
fillOpacity,
|
|
backgroundFill,
|
|
backgroundFillOpacity,
|
|
stroke,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
rotation,
|
|
scale
|
|
} = this;
|
|
const pattern = (0, import_ag_charts_core17.createSvgElement)("pattern");
|
|
pattern.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
|
pattern.setAttribute("width", String(width));
|
|
pattern.setAttribute("height", String(height));
|
|
pattern.setAttribute("patternUnits", "userSpaceOnUse");
|
|
const rect = (0, import_ag_charts_core17.createSvgElement)("rect");
|
|
rect.setAttribute("x", "0");
|
|
rect.setAttribute("y", "0");
|
|
rect.setAttribute("width", String(width));
|
|
rect.setAttribute("height", String(height));
|
|
rect.setAttribute("fill", backgroundFill);
|
|
rect.setAttribute("fill-opacity", String(backgroundFillOpacity));
|
|
pattern.appendChild(rect);
|
|
const path = (0, import_ag_charts_core17.createSvgElement)("path");
|
|
path.setAttribute("fill", fill);
|
|
path.setAttribute("fill-opacity", String(fillOpacity));
|
|
path.setAttribute("stroke-opacity", String(strokeOpacity));
|
|
path.setAttribute("stroke", stroke);
|
|
path.setAttribute("stroke-width", String(strokeWidth));
|
|
path.setAttribute("transform", `rotate(${rotation}) scale(${scale})`);
|
|
path.setAttribute("d", this.getPath(1).toSVG());
|
|
pattern.appendChild(path);
|
|
return pattern;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/shape/svgUtils.ts
|
|
function setSvgFontAttributes(element, options) {
|
|
const { fontStyle, fontWeight, fontSize, fontFamily } = options;
|
|
if (fontStyle)
|
|
element.setAttribute("font-style", fontStyle);
|
|
if (fontWeight)
|
|
element.setAttribute("font-weight", String(fontWeight));
|
|
if (fontSize != null)
|
|
element.setAttribute("font-size", String(fontSize));
|
|
if (fontFamily)
|
|
element.setAttribute("font-family", fontFamily);
|
|
}
|
|
function setSvgStrokeAttributes(element, options) {
|
|
const { stroke, strokeWidth, strokeOpacity } = options;
|
|
if (stroke)
|
|
element.setAttribute("stroke", stroke);
|
|
if (strokeWidth != null)
|
|
element.setAttribute("stroke-width", String(strokeWidth));
|
|
if (strokeOpacity != null)
|
|
element.setAttribute("stroke-opacity", String(strokeOpacity));
|
|
}
|
|
function setSvgLineDashAttributes(element, options) {
|
|
const { lineDash, lineDashOffset } = options;
|
|
if (lineDash?.some((d) => d !== 0)) {
|
|
const lineDashArray = lineDash.length % 2 === 1 ? [...lineDash, ...lineDash] : lineDash;
|
|
element.setAttribute("stroke-dasharray", lineDashArray.join(" "));
|
|
if (lineDashOffset != null)
|
|
element.setAttribute("stroke-dashoffset", String(lineDashOffset));
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/shape/shape.ts
|
|
var _Shape = class _Shape extends Node {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.drawingMode = "overlay";
|
|
this.fillOpacity = 1;
|
|
this.strokeOpacity = 1;
|
|
this.fill = "black";
|
|
this.strokeWidth = 0;
|
|
this.lineDashOffset = 0;
|
|
this.opacity = 1;
|
|
}
|
|
// optimised field accessor
|
|
getGradient(fill) {
|
|
if ((0, import_ag_charts_core18.isGradientFill)(fill))
|
|
return this.createGradient(fill);
|
|
}
|
|
createGradient(fill) {
|
|
const { colorSpace = "rgb", gradient = "linear", colorStops, rotation = 0, reverse = false } = fill;
|
|
if (colorStops == null)
|
|
return;
|
|
let stops = getColorStops(colorStops, ["black"], [0, 1]);
|
|
if (reverse) {
|
|
stops = stops.map((s) => ({ color: s.color, stop: 1 - s.stop })).reverse();
|
|
}
|
|
switch (gradient) {
|
|
case "linear":
|
|
return new LinearGradient(colorSpace, stops, rotation);
|
|
case "radial":
|
|
return new RadialGradient(colorSpace, stops);
|
|
case "conic":
|
|
return new ConicGradient(colorSpace, stops, rotation);
|
|
}
|
|
}
|
|
getPattern(fill) {
|
|
if ((0, import_ag_charts_core18.isPatternFill)(fill))
|
|
return this.createPattern(fill);
|
|
}
|
|
createPattern(fill) {
|
|
return new Pattern(fill);
|
|
}
|
|
getImage(fill) {
|
|
if ((0, import_ag_charts_core18.isImageFill)(fill))
|
|
return this.createImage(fill);
|
|
}
|
|
createImage(fill) {
|
|
return new Image(this.imageLoader, fill);
|
|
}
|
|
onFillChange() {
|
|
if (typeof this.fill === "object") {
|
|
if ((0, import_ag_charts_core18.objectsEqual)(this._cachedFill ?? {}, this.fill)) {
|
|
return;
|
|
}
|
|
}
|
|
this.fillGradient = this.getGradient(this.fill);
|
|
this.fillPattern = this.getPattern(this.fill);
|
|
this.fillImage = this.getImage(this.fill);
|
|
this._cachedFill = this.fill;
|
|
}
|
|
// optimised field accessor
|
|
onStrokeChange() {
|
|
this.strokeGradient = this.getGradient(this.stroke);
|
|
}
|
|
// optimised field accessor
|
|
/**
|
|
* Returns a device-pixel aligned coordinate (or length if length is supplied).
|
|
*
|
|
* NOTE: Not suitable for strokes, since the stroke needs to be offset to the middle
|
|
* of a device pixel.
|
|
*/
|
|
align(start, length) {
|
|
return align(this.layerManager?.canvas?.pixelRatio ?? 1, start, length);
|
|
}
|
|
markDirty(property) {
|
|
super.markDirty(property);
|
|
this.cachedDefaultGradientFillBBox = void 0;
|
|
}
|
|
fillStroke(ctx, path) {
|
|
if (this.__drawingMode === "cutout") {
|
|
ctx.globalCompositeOperation = "destination-out";
|
|
this.executeFill(ctx, path);
|
|
ctx.globalCompositeOperation = "source-over";
|
|
}
|
|
this.renderFill(ctx, path);
|
|
this.renderStroke(ctx, path);
|
|
}
|
|
renderFill(ctx, path) {
|
|
const { __fill: fill, __fillOpacity: fillOpacity = 1, fillImage } = this;
|
|
if (fill != null && fill !== "none" && fillOpacity > 0) {
|
|
const globalAlpha = ctx.globalAlpha;
|
|
if (fillImage) {
|
|
ctx.globalAlpha = fillImage.backgroundFillOpacity;
|
|
ctx.fillStyle = fillImage.backgroundFill;
|
|
this.executeFill(ctx, path);
|
|
ctx.globalAlpha = globalAlpha;
|
|
}
|
|
this.applyFillAndAlpha(ctx);
|
|
this.applyShadow(ctx);
|
|
this.executeFill(ctx, path);
|
|
ctx.globalAlpha = globalAlpha;
|
|
if (this.fillShadow?.enabled) {
|
|
ctx.shadowColor = "rgba(0, 0, 0, 0)";
|
|
}
|
|
}
|
|
}
|
|
executeFill(ctx, path) {
|
|
if (path) {
|
|
ctx.fill(path);
|
|
} else {
|
|
ctx.fill();
|
|
}
|
|
}
|
|
applyFillAndAlpha(ctx) {
|
|
const {
|
|
__fill: fill,
|
|
fillGradient,
|
|
fillPattern,
|
|
fillImage,
|
|
__fillOpacity: fillOpacity = 1,
|
|
__opacity: opacity = 1
|
|
} = this;
|
|
const combinedOpacity = opacity * fillOpacity;
|
|
if (combinedOpacity !== 1) {
|
|
ctx.globalAlpha *= combinedOpacity;
|
|
}
|
|
if (fillGradient) {
|
|
const { fillBBox = this.getDefaultGradientFillBBox() ?? this.getBBox(), fillParams } = this;
|
|
ctx.fillStyle = fillGradient.createGradient(ctx, fillBBox, fillParams) ?? "black";
|
|
} else if (fillPattern) {
|
|
const { x, y } = this.getBBox();
|
|
const pixelRatio = this.layerManager?.canvas?.pixelRatio ?? 1;
|
|
const pattern = fillPattern.createPattern(ctx, pixelRatio);
|
|
fillPattern.setPatternTransform(pattern, pixelRatio, x, y);
|
|
if (pattern) {
|
|
ctx.fillStyle = pattern;
|
|
} else {
|
|
ctx.fillStyle = fillPattern.fill;
|
|
ctx.globalAlpha *= fillPattern.fillOpacity;
|
|
}
|
|
} else if (fillImage) {
|
|
const bbox = this.getBBox();
|
|
const image = fillImage.createPattern(ctx, bbox.width, bbox.height, this);
|
|
fillImage.setImageTransform(image, bbox);
|
|
ctx.fillStyle = image ?? "transparent";
|
|
} else {
|
|
ctx.fillStyle = typeof fill === "string" ? fill : "black";
|
|
}
|
|
}
|
|
applyStrokeAndAlpha(ctx) {
|
|
const { __stroke: stroke, __strokeOpacity: strokeOpacity = 1, strokeGradient, __opacity: opacity = 1 } = this;
|
|
ctx.strokeStyle = strokeGradient?.createGradient(ctx, this.getBBox()) ?? (typeof stroke === "string" ? stroke : void 0) ?? "black";
|
|
const combinedOpacity = opacity * strokeOpacity;
|
|
if (combinedOpacity !== 1) {
|
|
ctx.globalAlpha *= combinedOpacity;
|
|
}
|
|
}
|
|
applyShadow(ctx) {
|
|
const pixelRatio = this.layerManager?.canvas.pixelRatio ?? 1;
|
|
const { __fillShadow: fillShadow } = this;
|
|
if (fillShadow?.enabled) {
|
|
ctx.shadowColor = fillShadow.color;
|
|
ctx.shadowOffsetX = fillShadow.xOffset * pixelRatio;
|
|
ctx.shadowOffsetY = fillShadow.yOffset * pixelRatio;
|
|
ctx.shadowBlur = fillShadow.blur * pixelRatio;
|
|
}
|
|
}
|
|
renderStroke(ctx, path) {
|
|
const {
|
|
__stroke: stroke,
|
|
__strokeWidth: strokeWidth = 0,
|
|
__strokeOpacity: strokeOpacity = 1,
|
|
__lineDash: lineDash,
|
|
__lineDashOffset: lineDashOffset,
|
|
__lineCap: lineCap,
|
|
__lineJoin: lineJoin,
|
|
__miterLimit: miterLimit
|
|
} = this;
|
|
if (stroke != null && stroke !== "none" && strokeWidth > 0 && strokeOpacity > 0) {
|
|
const { globalAlpha } = ctx;
|
|
this.applyStrokeAndAlpha(ctx);
|
|
ctx.lineWidth = strokeWidth;
|
|
if (lineDash) {
|
|
ctx.setLineDash(lineDash);
|
|
}
|
|
if (lineDashOffset) {
|
|
ctx.lineDashOffset = lineDashOffset;
|
|
}
|
|
if (lineCap) {
|
|
ctx.lineCap = lineCap;
|
|
}
|
|
if (lineJoin) {
|
|
ctx.lineJoin = lineJoin;
|
|
}
|
|
if (miterLimit != null) {
|
|
ctx.miterLimit = miterLimit;
|
|
}
|
|
this.executeStroke(ctx, path);
|
|
ctx.globalAlpha = globalAlpha;
|
|
}
|
|
}
|
|
executeStroke(ctx, path) {
|
|
if (path) {
|
|
ctx.stroke(path);
|
|
} else {
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
getDefaultGradientFillBBox() {
|
|
this.cachedDefaultGradientFillBBox ?? (this.cachedDefaultGradientFillBBox = Object.freeze(this.computeDefaultGradientFillBBox()));
|
|
return this.cachedDefaultGradientFillBBox;
|
|
}
|
|
computeDefaultGradientFillBBox() {
|
|
return;
|
|
}
|
|
containsPoint(x, y) {
|
|
return this.isPointInPath(x, y);
|
|
}
|
|
applySvgFillAttributes(element, defs) {
|
|
const { fill, fillOpacity } = this;
|
|
if (typeof fill === "string") {
|
|
element.setAttribute("fill", fill);
|
|
} else if ((0, import_ag_charts_core18.isGradientFill)(fill) && this.fillGradient) {
|
|
defs ?? (defs = []);
|
|
const gradient = this.fillGradient.toSvg(this.fillBBox ?? this.getBBox());
|
|
const id = (0, import_ag_charts_core18.generateUUID)();
|
|
gradient.setAttribute("id", id);
|
|
defs.push(gradient);
|
|
element.setAttribute("fill", `url(#${id})`);
|
|
} else if ((0, import_ag_charts_core18.isPatternFill)(fill) && this.fillPattern) {
|
|
defs ?? (defs = []);
|
|
const pattern = this.fillPattern.toSvg();
|
|
const id = (0, import_ag_charts_core18.generateUUID)();
|
|
pattern.setAttribute("id", id);
|
|
defs.push(pattern);
|
|
element.setAttribute("fill", `url(#${id})`);
|
|
} else if ((0, import_ag_charts_core18.isImageFill)(fill) && this.fillImage) {
|
|
defs ?? (defs = []);
|
|
const pixelRatio = this.layerManager?.canvas?.pixelRatio ?? 1;
|
|
const pattern = this.fillImage.toSvg(this.getBBox(), pixelRatio);
|
|
const id = (0, import_ag_charts_core18.generateUUID)();
|
|
pattern.setAttribute("id", id);
|
|
defs.push(pattern);
|
|
element.setAttribute("fill", `url(#${id})`);
|
|
} else {
|
|
element.setAttribute("fill", "none");
|
|
}
|
|
element.setAttribute("fill-opacity", String(fillOpacity));
|
|
return defs;
|
|
}
|
|
applySvgStrokeAttributes(element) {
|
|
const { stroke, strokeOpacity, strokeWidth, lineDash, lineDashOffset } = this;
|
|
setSvgStrokeAttributes(element, { stroke: (0, import_ag_charts_core18.isString)(stroke) ? stroke : void 0, strokeOpacity, strokeWidth });
|
|
setSvgLineDashAttributes(element, { lineDash, lineDashOffset });
|
|
}
|
|
static handleFillChange(shape) {
|
|
shape.onFillChange();
|
|
}
|
|
static handleStrokeChange(shape) {
|
|
shape.onStrokeChange();
|
|
}
|
|
/**
|
|
* Sets style properties on the shape, optimizing by writing directly to __ prefix fields
|
|
* where possible to avoid setter overhead.
|
|
*/
|
|
setStyleProperties(style, fillBBox, fillParams) {
|
|
const opacity = style?.opacity ?? 1;
|
|
const fill = style?.fill;
|
|
const computedFillOpacity = (style?.fillOpacity ?? 1) * opacity;
|
|
const computedStrokeOpacity = (style?.strokeOpacity ?? 1) * opacity;
|
|
const computedStrokeWidth = style?.strokeWidth ?? 0;
|
|
const computedLineDashOffset = style?.lineDashOffset ?? 0;
|
|
let hasDirectChanges = false;
|
|
if (this.__fillOpacity !== computedFillOpacity) {
|
|
this.__fillOpacity = computedFillOpacity;
|
|
hasDirectChanges = true;
|
|
}
|
|
if (this.__strokeOpacity !== computedStrokeOpacity) {
|
|
this.__strokeOpacity = computedStrokeOpacity;
|
|
hasDirectChanges = true;
|
|
}
|
|
if (this.__strokeWidth !== computedStrokeWidth) {
|
|
this.__strokeWidth = computedStrokeWidth;
|
|
hasDirectChanges = true;
|
|
}
|
|
if (this.__lineDashOffset !== computedLineDashOffset) {
|
|
this.__lineDashOffset = computedLineDashOffset;
|
|
hasDirectChanges = true;
|
|
}
|
|
if (this.__lineDash !== style?.lineDash) {
|
|
this.__lineDash = style?.lineDash;
|
|
hasDirectChanges = true;
|
|
}
|
|
this.setFillProperties(fill, fillBBox, fillParams);
|
|
if (fill !== this.fill) {
|
|
this.fill = fill;
|
|
}
|
|
if (style?.stroke !== this.stroke) {
|
|
this.stroke = style?.stroke;
|
|
}
|
|
if (hasDirectChanges) {
|
|
this.markDirty();
|
|
}
|
|
}
|
|
/**
|
|
* Sets fill-related properties (fillBBox and fillParams) on the shape.
|
|
* Used for gradient fills that need bounding box information.
|
|
*/
|
|
setFillProperties(fill, fillBBox, fillParams) {
|
|
const computedFillBBox = fillBBox == null || !(0, import_ag_charts_core18.isGradientFill)(fill) || fill.bounds == null || fill.bounds === "item" ? void 0 : fillBBox[fill.bounds];
|
|
let hasDirectChanges = false;
|
|
if (this.__fillBBox !== computedFillBBox) {
|
|
this.__fillBBox = computedFillBBox;
|
|
hasDirectChanges = true;
|
|
}
|
|
if (this.__fillParams !== fillParams) {
|
|
this.__fillParams = fillParams;
|
|
hasDirectChanges = true;
|
|
}
|
|
if (hasDirectChanges) {
|
|
this.onFillChange();
|
|
this.markDirty();
|
|
}
|
|
}
|
|
};
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.DeclaredSceneChangeDetection)()
|
|
], _Shape.prototype, "drawingMode", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.DeclaredSceneChangeDetection)()
|
|
], _Shape.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.DeclaredSceneChangeDetection)()
|
|
], _Shape.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.DeclaredSceneObjectChangeDetection)({
|
|
equals: import_ag_charts_core18.objectsEqual,
|
|
changeCb: _Shape.handleFillChange
|
|
})
|
|
], _Shape.prototype, "fill", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.SceneObjectChangeDetection)({ equals: import_ag_charts_core18.objectsEqual, changeCb: _Shape.handleStrokeChange })
|
|
], _Shape.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.DeclaredSceneChangeDetection)()
|
|
], _Shape.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.SceneArrayChangeDetection)()
|
|
], _Shape.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.DeclaredSceneChangeDetection)()
|
|
], _Shape.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.DeclaredSceneChangeDetection)()
|
|
], _Shape.prototype, "lineCap", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.DeclaredSceneChangeDetection)()
|
|
], _Shape.prototype, "lineJoin", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.DeclaredSceneChangeDetection)()
|
|
], _Shape.prototype, "miterLimit", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.DeclaredSceneChangeDetection)({ convertor: (v) => (0, import_ag_charts_core18.clamp)(0, v ?? 1, 1) })
|
|
], _Shape.prototype, "opacity", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.SceneObjectChangeDetection)({ equals: import_ag_charts_core18.TRIPLE_EQ, checkDirtyOnAssignment: true })
|
|
], _Shape.prototype, "fillShadow", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.DeclaredSceneObjectChangeDetection)({ equals: import_ag_charts_core18.boxesEqual, changeCb: (s) => s.onFillChange() })
|
|
], _Shape.prototype, "fillBBox", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core18.DeclaredSceneObjectChangeDetection)({ equals: import_ag_charts_core18.objectsEqual, changeCb: (s) => s.onFillChange() })
|
|
], _Shape.prototype, "fillParams", 2);
|
|
var Shape = _Shape;
|
|
|
|
// packages/ag-charts-community/src/scene/transformable.ts
|
|
var import_ag_charts_core20 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scene/matrix.ts
|
|
var import_ag_charts_core19 = require("ag-charts-core");
|
|
var IDENTITY_MATRIX_ELEMENTS = [1, 0, 0, 1, 0, 0];
|
|
var Matrix = class _Matrix {
|
|
get e() {
|
|
return [...this.elements];
|
|
}
|
|
constructor(elements = IDENTITY_MATRIX_ELEMENTS) {
|
|
this.elements = [...elements];
|
|
}
|
|
setElements(elements) {
|
|
const e = this.elements;
|
|
e[0] = elements[0];
|
|
e[1] = elements[1];
|
|
e[2] = elements[2];
|
|
e[3] = elements[3];
|
|
e[4] = elements[4];
|
|
e[5] = elements[5];
|
|
return this;
|
|
}
|
|
get identity() {
|
|
const e = this.elements;
|
|
return (0, import_ag_charts_core19.isNumberEqual)(e[0], 1) && (0, import_ag_charts_core19.isNumberEqual)(e[1], 0) && (0, import_ag_charts_core19.isNumberEqual)(e[2], 0) && (0, import_ag_charts_core19.isNumberEqual)(e[3], 1) && (0, import_ag_charts_core19.isNumberEqual)(e[4], 0) && (0, import_ag_charts_core19.isNumberEqual)(e[5], 0);
|
|
}
|
|
/**
|
|
* Performs the AxB matrix multiplication and saves the result
|
|
* to `C`, if given, or to `A` otherwise.
|
|
*/
|
|
AxB(A, B, C) {
|
|
const a = A[0] * B[0] + A[2] * B[1], b = A[1] * B[0] + A[3] * B[1], c = A[0] * B[2] + A[2] * B[3], d = A[1] * B[2] + A[3] * B[3], e = A[0] * B[4] + A[2] * B[5] + A[4], f = A[1] * B[4] + A[3] * B[5] + A[5];
|
|
C = C ?? A;
|
|
C[0] = a;
|
|
C[1] = b;
|
|
C[2] = c;
|
|
C[3] = d;
|
|
C[4] = e;
|
|
C[5] = f;
|
|
}
|
|
/**
|
|
* The `other` matrix gets post-multiplied to the current matrix.
|
|
* Returns the current matrix.
|
|
* @param other
|
|
*/
|
|
multiplySelf(other) {
|
|
this.AxB(this.elements, other.elements);
|
|
return this;
|
|
}
|
|
/**
|
|
* The `other` matrix gets post-multiplied to the current matrix.
|
|
* Returns a new matrix.
|
|
* @param other
|
|
*/
|
|
multiply(other) {
|
|
const elements = [Number.NaN, Number.NaN, Number.NaN, Number.NaN, Number.NaN, Number.NaN];
|
|
if (other instanceof _Matrix) {
|
|
this.AxB(this.elements, other.elements, elements);
|
|
} else {
|
|
this.AxB(this.elements, [other.a, other.b, other.c, other.d, other.e, other.f], elements);
|
|
}
|
|
return new _Matrix(elements);
|
|
}
|
|
preMultiplySelf(other) {
|
|
this.AxB(other.elements, this.elements, this.elements);
|
|
return this;
|
|
}
|
|
/**
|
|
* Returns the inverse of this matrix as a new matrix.
|
|
*/
|
|
inverse() {
|
|
const el = this.elements;
|
|
let a = el[0], b = el[1], c = el[2], d = el[3];
|
|
const e = el[4], f = el[5];
|
|
const rD = 1 / (a * d - b * c);
|
|
a *= rD;
|
|
b *= rD;
|
|
c *= rD;
|
|
d *= rD;
|
|
return new _Matrix([d, -b, -c, a, c * f - d * e, b * e - a * f]);
|
|
}
|
|
invertSelf() {
|
|
const el = this.elements;
|
|
let a = el[0], b = el[1], c = el[2], d = el[3];
|
|
const e = el[4], f = el[5];
|
|
const rD = 1 / (a * d - b * c);
|
|
a *= rD;
|
|
b *= rD;
|
|
c *= rD;
|
|
d *= rD;
|
|
el[0] = d;
|
|
el[1] = -b;
|
|
el[2] = -c;
|
|
el[3] = a;
|
|
el[4] = c * f - d * e;
|
|
el[5] = b * e - a * f;
|
|
return this;
|
|
}
|
|
transformPoint(x, y) {
|
|
const e = this.elements;
|
|
return {
|
|
x: x * e[0] + y * e[2] + e[4],
|
|
y: x * e[1] + y * e[3] + e[5]
|
|
};
|
|
}
|
|
transformBBox(bbox, target) {
|
|
const el = this.elements;
|
|
const xx = el[0];
|
|
const xy = el[1];
|
|
const yx = el[2];
|
|
const yy = el[3];
|
|
const h_w = bbox.width * 0.5;
|
|
const h_h = bbox.height * 0.5;
|
|
const cx = bbox.x + h_w;
|
|
const cy = bbox.y + h_h;
|
|
const w = Math.abs(h_w * xx) + Math.abs(h_h * yx);
|
|
const h = Math.abs(h_w * xy) + Math.abs(h_h * yy);
|
|
target ?? (target = new BBox(0, 0, 0, 0));
|
|
target.x = cx * xx + cy * yx + el[4] - w;
|
|
target.y = cx * xy + cy * yy + el[5] - h;
|
|
target.width = w + w;
|
|
target.height = h + h;
|
|
return target;
|
|
}
|
|
toContext(ctx) {
|
|
if (this.identity) {
|
|
return;
|
|
}
|
|
const e = this.elements;
|
|
ctx.transform(e[0], e[1], e[2], e[3], e[4], e[5]);
|
|
}
|
|
static updateTransformMatrix(matrix, scalingX, scalingY, rotation, translationX, translationY, opts) {
|
|
const sx = scalingX;
|
|
const sy = scalingY;
|
|
let scx;
|
|
let scy;
|
|
if (sx === 1 && sy === 1) {
|
|
scx = 0;
|
|
scy = 0;
|
|
} else {
|
|
scx = opts?.scalingCenterX ?? 0;
|
|
scy = opts?.scalingCenterY ?? 0;
|
|
}
|
|
const r = rotation;
|
|
const cos = Math.cos(r);
|
|
const sin = Math.sin(r);
|
|
let rcx;
|
|
let rcy;
|
|
if (r === 0) {
|
|
rcx = 0;
|
|
rcy = 0;
|
|
} else {
|
|
rcx = opts?.rotationCenterX ?? 0;
|
|
rcy = opts?.rotationCenterY ?? 0;
|
|
}
|
|
const tx = translationX;
|
|
const ty = translationY;
|
|
const tx4 = scx * (1 - sx) - rcx;
|
|
const ty4 = scy * (1 - sy) - rcy;
|
|
matrix.setElements([
|
|
cos * sx,
|
|
sin * sx,
|
|
-sin * sy,
|
|
cos * sy,
|
|
cos * tx4 - sin * ty4 + rcx + tx,
|
|
sin * tx4 + cos * ty4 + rcy + ty
|
|
]);
|
|
return matrix;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/transformable.ts
|
|
function isMatrixTransform(node) {
|
|
return isMatrixTransformType(node.constructor);
|
|
}
|
|
var MATRIX_TRANSFORM_TYPE = Symbol("isMatrixTransform");
|
|
function isMatrixTransformType(cstr) {
|
|
return cstr[MATRIX_TRANSFORM_TYPE] === true;
|
|
}
|
|
function MatrixTransform(Parent) {
|
|
var _a, _b;
|
|
const ParentNode = Parent;
|
|
if (isMatrixTransformType(Parent)) {
|
|
return Parent;
|
|
}
|
|
const TRANSFORM_MATRIX = Symbol("matrix_combined_transform");
|
|
class MatrixTransformInternal extends ParentNode {
|
|
constructor() {
|
|
super(...arguments);
|
|
this[_b] = new Matrix();
|
|
this._dirtyTransform = true;
|
|
}
|
|
onChangeDetection(property) {
|
|
super.onChangeDetection(property);
|
|
this._dirtyTransform = true;
|
|
if (this.batchLevel > 0) {
|
|
return;
|
|
}
|
|
this.markDirty("transform");
|
|
}
|
|
updateMatrix(_matrix) {
|
|
}
|
|
computeTransformMatrix() {
|
|
if (!this._dirtyTransform)
|
|
return;
|
|
this[TRANSFORM_MATRIX].setElements(IDENTITY_MATRIX_ELEMENTS);
|
|
this.updateMatrix(this[TRANSFORM_MATRIX]);
|
|
this._dirtyTransform = false;
|
|
}
|
|
toParent(bbox) {
|
|
this.computeTransformMatrix();
|
|
if (this[TRANSFORM_MATRIX].identity)
|
|
return bbox.clone();
|
|
return this[TRANSFORM_MATRIX].transformBBox(bbox);
|
|
}
|
|
toParentPoint(x, y) {
|
|
this.computeTransformMatrix();
|
|
if (this[TRANSFORM_MATRIX].identity)
|
|
return { x, y };
|
|
return this[TRANSFORM_MATRIX].transformPoint(x, y);
|
|
}
|
|
fromParent(bbox) {
|
|
this.computeTransformMatrix();
|
|
if (this[TRANSFORM_MATRIX].identity)
|
|
return bbox.clone();
|
|
return this[TRANSFORM_MATRIX].inverse().transformBBox(bbox);
|
|
}
|
|
fromParentPoint(x, y) {
|
|
this.computeTransformMatrix();
|
|
if (this[TRANSFORM_MATRIX].identity)
|
|
return { x, y };
|
|
return this[TRANSFORM_MATRIX].inverse().transformPoint(x, y);
|
|
}
|
|
computeBBox() {
|
|
const bbox = super.computeBBox();
|
|
if (!bbox)
|
|
return bbox;
|
|
return this.toParent(bbox);
|
|
}
|
|
computeBBoxWithoutTransforms() {
|
|
return super.computeBBox();
|
|
}
|
|
pickNode(x, y) {
|
|
({ x, y } = this.fromParentPoint(x, y));
|
|
return super.pickNode(x, y);
|
|
}
|
|
pickNodes(x, y, into) {
|
|
({ x, y } = this.fromParentPoint(x, y));
|
|
return super.pickNodes(x, y, into);
|
|
}
|
|
render(renderCtx) {
|
|
this.computeTransformMatrix();
|
|
const { ctx } = renderCtx;
|
|
const matrix = this[TRANSFORM_MATRIX];
|
|
let performRestore = false;
|
|
try {
|
|
if (!matrix.identity) {
|
|
ctx.save();
|
|
performRestore = true;
|
|
matrix.toContext(ctx);
|
|
}
|
|
super.render(renderCtx);
|
|
} finally {
|
|
if (performRestore) {
|
|
ctx.restore();
|
|
}
|
|
}
|
|
}
|
|
toSVG() {
|
|
this.computeTransformMatrix();
|
|
const svg = super.toSVG();
|
|
const matrix = this[TRANSFORM_MATRIX];
|
|
if (matrix.identity || svg == null)
|
|
return svg;
|
|
const g = (0, import_ag_charts_core20.createSvgElement)("g");
|
|
g.append(...svg.elements);
|
|
const [a, b, c, d, e, f] = matrix.e;
|
|
g.setAttribute("transform", `matrix(${a} ${b} ${c} ${d} ${e} ${f})`);
|
|
return {
|
|
elements: [g],
|
|
defs: svg.defs
|
|
};
|
|
}
|
|
}
|
|
_a = MATRIX_TRANSFORM_TYPE, _b = TRANSFORM_MATRIX;
|
|
MatrixTransformInternal[_a] = true;
|
|
return MatrixTransformInternal;
|
|
}
|
|
function Rotatable(Parent) {
|
|
var _a;
|
|
const ParentNode = Parent;
|
|
const ROTATABLE_MATRIX = Symbol("matrix_rotation");
|
|
class RotatableInternal extends MatrixTransform(ParentNode) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this[_a] = new Matrix();
|
|
this.rotationCenterX = 0;
|
|
this.rotationCenterY = 0;
|
|
this.rotation = 0;
|
|
}
|
|
updateMatrix(matrix) {
|
|
super.updateMatrix(matrix);
|
|
const { rotation, rotationCenterX, rotationCenterY } = this;
|
|
if (rotation === 0)
|
|
return;
|
|
Matrix.updateTransformMatrix(this[ROTATABLE_MATRIX], 1, 1, rotation, 0, 0, {
|
|
rotationCenterX,
|
|
rotationCenterY
|
|
});
|
|
matrix.multiplySelf(this[ROTATABLE_MATRIX]);
|
|
}
|
|
}
|
|
_a = ROTATABLE_MATRIX;
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], RotatableInternal.prototype, "rotationCenterX", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], RotatableInternal.prototype, "rotationCenterY", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], RotatableInternal.prototype, "rotation", 2);
|
|
return RotatableInternal;
|
|
}
|
|
function Scalable(Parent) {
|
|
var _a;
|
|
const ParentNode = Parent;
|
|
const SCALABLE_MATRIX = Symbol("matrix_scale");
|
|
class ScalableInternal extends MatrixTransform(ParentNode) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this[_a] = new Matrix();
|
|
this.scalingX = 1;
|
|
this.scalingY = 1;
|
|
this.scalingCenterX = 0;
|
|
this.scalingCenterY = 0;
|
|
}
|
|
// optimised field accessor
|
|
updateMatrix(matrix) {
|
|
super.updateMatrix(matrix);
|
|
const { scalingX, scalingY, scalingCenterX, scalingCenterY } = this;
|
|
if (scalingX === 1 && scalingY === 1)
|
|
return;
|
|
Matrix.updateTransformMatrix(this[SCALABLE_MATRIX], scalingX, scalingY, 0, 0, 0, {
|
|
scalingCenterX,
|
|
scalingCenterY
|
|
});
|
|
matrix.multiplySelf(this[SCALABLE_MATRIX]);
|
|
}
|
|
/**
|
|
* Optimised reset for animation hot paths.
|
|
* Bypasses SceneChangeDetection decorators by writing directly to backing fields.
|
|
*/
|
|
resetScalingProperties(scalingX, scalingY, scalingCenterX, scalingCenterY) {
|
|
this.__scalingX = scalingX;
|
|
this.__scalingY = scalingY;
|
|
this.__scalingCenterX = scalingCenterX;
|
|
this.__scalingCenterY = scalingCenterY;
|
|
this.onChangeDetection("scaling");
|
|
}
|
|
}
|
|
_a = SCALABLE_MATRIX;
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], ScalableInternal.prototype, "scalingX", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], ScalableInternal.prototype, "scalingY", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], ScalableInternal.prototype, "scalingCenterX", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], ScalableInternal.prototype, "scalingCenterY", 2);
|
|
return ScalableInternal;
|
|
}
|
|
function Translatable(Parent) {
|
|
var _a;
|
|
const ParentNode = Parent;
|
|
const TRANSLATABLE_MATRIX = Symbol("matrix_translation");
|
|
class TranslatableInternal extends MatrixTransform(ParentNode) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this[_a] = new Matrix();
|
|
this.translationX = 0;
|
|
this.translationY = 0;
|
|
}
|
|
updateMatrix(matrix) {
|
|
super.updateMatrix(matrix);
|
|
const { translationX, translationY } = this;
|
|
if (translationX === 0 && translationY === 0)
|
|
return;
|
|
Matrix.updateTransformMatrix(this[TRANSLATABLE_MATRIX], 1, 1, 0, translationX, translationY);
|
|
matrix.multiplySelf(this[TRANSLATABLE_MATRIX]);
|
|
}
|
|
}
|
|
_a = TRANSLATABLE_MATRIX;
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], TranslatableInternal.prototype, "translationX", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], TranslatableInternal.prototype, "translationY", 2);
|
|
return TranslatableInternal;
|
|
}
|
|
var Transformable = class {
|
|
/**
|
|
* Converts a BBox from canvas coordinate space into the coordinate space of the given Node.
|
|
*/
|
|
static fromCanvas(node, bbox) {
|
|
const parents = [];
|
|
for (const parent of node.traverseUp()) {
|
|
if (isMatrixTransform(parent)) {
|
|
parents.unshift(parent);
|
|
}
|
|
}
|
|
for (const parent of parents) {
|
|
bbox = parent.fromParent(bbox);
|
|
}
|
|
if (isMatrixTransform(node)) {
|
|
bbox = node.fromParent(bbox);
|
|
}
|
|
return bbox;
|
|
}
|
|
/**
|
|
* Converts a Nodes BBox (or an arbitrary BBox if supplied) from local Node coordinate space
|
|
* into the Canvas coordinate space.
|
|
*/
|
|
static toCanvas(node, bbox) {
|
|
if (bbox == null) {
|
|
bbox = node.getBBox();
|
|
} else if (isMatrixTransform(node)) {
|
|
bbox = node.toParent(bbox);
|
|
}
|
|
for (const parent of node.traverseUp()) {
|
|
if (isMatrixTransform(parent)) {
|
|
bbox = parent.toParent(bbox);
|
|
}
|
|
}
|
|
return bbox;
|
|
}
|
|
/**
|
|
* Converts a point from canvas coordinate space into the coordinate space of the given Node.
|
|
*/
|
|
static fromCanvasPoint(node, x, y) {
|
|
const parents = [];
|
|
for (const parent of node.traverseUp()) {
|
|
if (isMatrixTransform(parent)) {
|
|
parents.unshift(parent);
|
|
}
|
|
}
|
|
for (const parent of parents) {
|
|
({ x, y } = parent.fromParentPoint(x, y));
|
|
}
|
|
if (isMatrixTransform(node)) {
|
|
({ x, y } = node.fromParentPoint(x, y));
|
|
}
|
|
return { x, y };
|
|
}
|
|
/**
|
|
* Converts a point from a Nodes local coordinate space into the Canvas coordinate space.
|
|
*/
|
|
static toCanvasPoint(node, x, y) {
|
|
if (isMatrixTransform(node)) {
|
|
({ x, y } = node.toParentPoint(x, y));
|
|
}
|
|
for (const parent of node.traverseUp()) {
|
|
if (isMatrixTransform(parent)) {
|
|
({ x, y } = parent.toParentPoint(x, y));
|
|
}
|
|
}
|
|
return { x, y };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/group.ts
|
|
var sharedOffscreenCanvas;
|
|
var _Group = class _Group extends Node {
|
|
// optimizeForInfrequentRedraws: true
|
|
constructor(opts) {
|
|
super(opts);
|
|
this.childNodes = /* @__PURE__ */ new Set();
|
|
this.dirty = false;
|
|
this.dirtyZIndex = false;
|
|
this.clipRect = void 0;
|
|
this.opacity = 1;
|
|
// Used when renderToOffscreenCanvas: true
|
|
this.layer = void 0;
|
|
// optimizeForInfrequentRedraws: false
|
|
this.image = void 0;
|
|
this._lastWidth = Number.NaN;
|
|
this._lastHeight = Number.NaN;
|
|
this._lastDevicePixelRatio = Number.NaN;
|
|
this.isContainerNode = true;
|
|
this.renderToOffscreenCanvas = opts?.renderToOffscreenCanvas === true;
|
|
this.optimizeForInfrequentRedraws = opts?.optimizeForInfrequentRedraws === true;
|
|
}
|
|
static is(value) {
|
|
return value instanceof _Group;
|
|
}
|
|
static computeChildrenBBox(nodes, skipInvisible = true) {
|
|
return BBox.merge(Node.extractBBoxes(nodes, skipInvisible));
|
|
}
|
|
static compareChildren(a, b) {
|
|
return compareZIndex(a.__zIndex, b.__zIndex) || a.serialNumber - b.serialNumber;
|
|
}
|
|
// We consider a group to be boundless, thus any point belongs to it.
|
|
containsPoint(_x, _y) {
|
|
return true;
|
|
}
|
|
computeBBox() {
|
|
return _Group.computeChildrenBBox(this.children());
|
|
}
|
|
computeSafeClippingBBox(pixelRatio) {
|
|
const bbox = this.computeBBox();
|
|
if (bbox?.isFinite() !== true)
|
|
return;
|
|
let strokeWidth = 0;
|
|
const strokeMiterAmount = 4;
|
|
for (const child of this.descendants()) {
|
|
if (child instanceof Shape) {
|
|
strokeWidth = Math.max(strokeWidth, child.strokeWidth);
|
|
}
|
|
}
|
|
const padding = Math.max(
|
|
// Account for anti-aliasing artefacts
|
|
1,
|
|
// Account for strokes (incl. miters) - this may not be the best place to include this
|
|
strokeWidth / 2 * strokeMiterAmount
|
|
);
|
|
const { x: originX, y: originY } = Transformable.toCanvasPoint(this, 0, 0);
|
|
const x = alignBefore(pixelRatio, originX + bbox.x - padding) - originX;
|
|
const y = alignBefore(pixelRatio, originY + bbox.y - padding) - originY;
|
|
const width = Math.ceil(bbox.x + bbox.width - x + padding);
|
|
const height = Math.ceil(bbox.y + bbox.height - y + padding);
|
|
return new BBox(x, y, width, height);
|
|
}
|
|
prepareSharedCanvas(width, height, pixelRatio) {
|
|
if (sharedOffscreenCanvas?.pixelRatio === pixelRatio) {
|
|
sharedOffscreenCanvas.resize(width, height, pixelRatio);
|
|
} else {
|
|
sharedOffscreenCanvas = new HdpiOffscreenCanvas({ width, height, pixelRatio });
|
|
}
|
|
return sharedOffscreenCanvas;
|
|
}
|
|
setScene(scene) {
|
|
const previousScene = this.scene;
|
|
super.setScene(scene);
|
|
if (this.layer && previousScene && previousScene !== scene) {
|
|
previousScene.layersManager.removeLayer(this.layer);
|
|
this.layer = void 0;
|
|
}
|
|
for (const child of this.children()) {
|
|
child.setScene(scene);
|
|
}
|
|
}
|
|
markDirty(property) {
|
|
this.dirty = true;
|
|
super.markDirty(property);
|
|
}
|
|
markDirtyChildrenOrder() {
|
|
super.markDirtyChildrenOrder();
|
|
this.dirtyZIndex = true;
|
|
this.markDirty();
|
|
}
|
|
/**
|
|
* Appends one or more new node instances to this parent.
|
|
* If one needs to:
|
|
* - move a child to the end of the list of children
|
|
* - move a child from one parent to another (including parents in other scenes)
|
|
* one should use the {@link insertBefore} method instead.
|
|
* @param nodes A node or nodes to append.
|
|
*/
|
|
append(nodes) {
|
|
for (const node of (0, import_ag_charts_core21.toIterable)(nodes)) {
|
|
node.remove();
|
|
this.childNodes.add(node);
|
|
node.parentNode = this;
|
|
node.setScene(this.scene);
|
|
}
|
|
this.markDirtyChildrenOrder();
|
|
this.markDirty();
|
|
}
|
|
appendChild(node) {
|
|
this.append(node);
|
|
return node;
|
|
}
|
|
removeChild(node) {
|
|
if (!this.childNodes?.delete(node)) {
|
|
throw new Error(
|
|
`AG Charts - internal error, unknown child node ${node.name ?? node.id} in $${this.name ?? this.id}`
|
|
);
|
|
}
|
|
node.parentNode = void 0;
|
|
node.setScene();
|
|
this.markDirtyChildrenOrder();
|
|
this.markDirty();
|
|
}
|
|
clear() {
|
|
for (const child of this.children()) {
|
|
delete child.parentNode;
|
|
child.setScene();
|
|
}
|
|
this.childNodes?.clear();
|
|
this.markDirty();
|
|
}
|
|
/**
|
|
* Hit testing method.
|
|
* Recursively checks if the given point is inside this node or any of its children.
|
|
* Returns the first matching node or `undefined`.
|
|
* Nodes that render later (show on top) are hit tested first.
|
|
*/
|
|
pickNode(x, y) {
|
|
if (!this.visible || this.pointerEvents === 1 /* None */ || !this.containsPoint(x, y)) {
|
|
return;
|
|
}
|
|
if (this.childNodes != null && this.childNodes.size !== 0) {
|
|
const children = [...this.children()];
|
|
for (let i = children.length - 1; i >= 0; i--) {
|
|
const child = children[i];
|
|
const hit = child.pickNode(x, y);
|
|
if (hit != null) {
|
|
return hit;
|
|
}
|
|
}
|
|
} else if (!this.isContainerNode) {
|
|
return this;
|
|
}
|
|
}
|
|
pickNodes(x, y, into = []) {
|
|
if (!this.visible || this.pointerEvents === 1 /* None */ || !this.containsPoint(x, y)) {
|
|
return into;
|
|
}
|
|
if (!this.isContainerNode) {
|
|
into.push(this);
|
|
}
|
|
for (const child of this.children()) {
|
|
child.pickNodes(x, y, into);
|
|
}
|
|
return into;
|
|
}
|
|
isDirty(renderCtx) {
|
|
const { width, height, devicePixelRatio } = renderCtx;
|
|
const { dirty, layer } = this;
|
|
const layerResized = layer != null && (this._lastWidth !== width || this._lastHeight !== height);
|
|
const pixelRatioChanged = this._lastDevicePixelRatio !== devicePixelRatio;
|
|
this._lastWidth = width;
|
|
this._lastHeight = height;
|
|
this._lastDevicePixelRatio = devicePixelRatio;
|
|
return dirty || layerResized || pixelRatioChanged;
|
|
}
|
|
preRender(renderCtx) {
|
|
let counts;
|
|
if (this.dirty) {
|
|
counts = super.preRender(renderCtx, 0);
|
|
for (const child of this.children()) {
|
|
const childCounts = child.preRender(renderCtx);
|
|
counts.groups += childCounts.groups;
|
|
counts.nonGroups += childCounts.nonGroups;
|
|
counts.complexity += childCounts.complexity;
|
|
}
|
|
counts.groups += 1;
|
|
counts.nonGroups -= 1;
|
|
} else {
|
|
counts = this.childNodeCounts;
|
|
}
|
|
if (this.renderToOffscreenCanvas && !this.optimizeForInfrequentRedraws && counts.nonGroups > 0 && this.getVisibility()) {
|
|
this.layer ?? (this.layer = this.layerManager?.addLayer({ name: this.name }));
|
|
} else if (this.layer != null) {
|
|
this.layerManager?.removeLayer(this.layer);
|
|
this.layer = void 0;
|
|
}
|
|
return counts;
|
|
}
|
|
render(renderCtx) {
|
|
const { layer, renderToOffscreenCanvas } = this;
|
|
const childRenderCtx = { ...renderCtx };
|
|
const dirty = this.isDirty(renderCtx);
|
|
this.dirty = false;
|
|
if (!renderToOffscreenCanvas) {
|
|
this.renderInContext(childRenderCtx);
|
|
super.render(childRenderCtx);
|
|
return;
|
|
}
|
|
const { ctx, stats, devicePixelRatio: pixelRatio } = renderCtx;
|
|
let { image } = this;
|
|
if (dirty) {
|
|
image?.bitmap.close();
|
|
image = void 0;
|
|
const bbox = layer ? void 0 : this.computeSafeClippingBBox(pixelRatio);
|
|
const renderOffscreen = (offscreenCanvas, ...transform) => {
|
|
const offscreenCtx = offscreenCanvas.context;
|
|
childRenderCtx.ctx = offscreenCtx;
|
|
offscreenCanvas.clear();
|
|
offscreenCtx.save();
|
|
try {
|
|
offscreenCtx.setTransform(...transform);
|
|
offscreenCtx.globalAlpha = 1;
|
|
this.renderInContext(childRenderCtx);
|
|
} finally {
|
|
offscreenCtx.restore();
|
|
offscreenCtx.verifyDepthZero?.();
|
|
}
|
|
};
|
|
if (layer) {
|
|
renderOffscreen(layer, ctx.getTransform());
|
|
} else if (bbox) {
|
|
const { x, y, width, height } = bbox;
|
|
const scaledWidth = Math.floor(width * pixelRatio);
|
|
const scaledHeight = Math.floor(height * pixelRatio);
|
|
if (scaledWidth > 0 && scaledHeight > 0) {
|
|
const canvas = this.prepareSharedCanvas(width, height, pixelRatio);
|
|
renderOffscreen(canvas, pixelRatio, 0, 0, pixelRatio, -x * pixelRatio, -y * pixelRatio);
|
|
image = { bitmap: canvas.transferToImageBitmap(), x, y, width, height };
|
|
}
|
|
}
|
|
this.image = image;
|
|
if (stats)
|
|
stats.layersRendered++;
|
|
} else if (stats) {
|
|
stats.layersSkipped++;
|
|
}
|
|
const { globalAlpha } = ctx;
|
|
ctx.globalAlpha = globalAlpha * this.opacity;
|
|
if (layer) {
|
|
ctx.save();
|
|
try {
|
|
ctx.resetTransform();
|
|
layer.drawImage(ctx);
|
|
} finally {
|
|
ctx.restore();
|
|
}
|
|
} else if (image) {
|
|
const { bitmap, x, y, width, height } = image;
|
|
ctx.drawImage(bitmap, 0, 0, width * pixelRatio, height * pixelRatio, x, y, width, height);
|
|
}
|
|
ctx.globalAlpha = globalAlpha;
|
|
super.render(childRenderCtx);
|
|
}
|
|
applyClip(ctx, clipRect) {
|
|
const { x, y, width, height } = clipRect;
|
|
ctx.beginPath();
|
|
ctx.rect(x, y, width, height);
|
|
ctx.clip();
|
|
}
|
|
renderInContext(childRenderCtx) {
|
|
const { ctx, stats } = childRenderCtx;
|
|
if (this.dirtyZIndex) {
|
|
this.sortChildren(_Group.compareChildren);
|
|
this.dirtyZIndex = false;
|
|
}
|
|
ctx.save();
|
|
try {
|
|
ctx.globalAlpha *= this.opacity;
|
|
if (this.clipRect != null) {
|
|
this.applyClip(ctx, this.clipRect);
|
|
childRenderCtx.clipBBox = Transformable.toCanvas(this, this.clipRect);
|
|
}
|
|
for (const child of this.children()) {
|
|
if (!child.visible) {
|
|
if (stats) {
|
|
stats.nodesSkipped += child.childNodeCounts.nonGroups + child.childNodeCounts.groups;
|
|
stats.opsSkipped += child.childNodeCounts.complexity;
|
|
}
|
|
continue;
|
|
}
|
|
child.isolatedRender(childRenderCtx);
|
|
}
|
|
} finally {
|
|
ctx.restore();
|
|
}
|
|
}
|
|
sortChildren(compareFn) {
|
|
if (!this.childNodes)
|
|
return;
|
|
const sortedChildren = [...this.childNodes].sort(compareFn);
|
|
this.childNodes.clear();
|
|
for (const child of sortedChildren) {
|
|
this.childNodes.add(child);
|
|
}
|
|
}
|
|
*children() {
|
|
yield* this.childNodes;
|
|
}
|
|
*excludeChildren(exclude) {
|
|
for (const child of this.children()) {
|
|
if (exclude.instance && !(child instanceof exclude.instance) || exclude.name && child.name !== exclude.name) {
|
|
yield child;
|
|
}
|
|
}
|
|
}
|
|
*descendants() {
|
|
for (const child of this.children()) {
|
|
yield child;
|
|
if (child instanceof _Group) {
|
|
yield* child.descendants();
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Transforms bbox given in the canvas coordinate space to bbox in this group's coordinate space and
|
|
* sets this group's clipRect to the transformed bbox.
|
|
* @param bbox clipRect bbox in the canvas coordinate space.
|
|
*/
|
|
setClipRect(bbox) {
|
|
this.clipRect = bbox ? Transformable.fromCanvas(this, bbox) : void 0;
|
|
}
|
|
/**
|
|
* Set the clip rect within the canvas coordinate space.
|
|
* @param bbox clipRect bbox in the canvas coordinate space.
|
|
*/
|
|
setClipRectCanvasSpace(bbox) {
|
|
this.clipRect = bbox;
|
|
}
|
|
getVisibility() {
|
|
for (const node of this.traverseUp(true)) {
|
|
if (!node.visible) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
toSVG() {
|
|
if (!this.visible)
|
|
return;
|
|
const defs = [];
|
|
const elements = [];
|
|
for (const child of this.children()) {
|
|
const svg = child.toSVG();
|
|
if (svg != null) {
|
|
elements.push(...svg.elements);
|
|
if (svg.defs != null) {
|
|
defs.push(...svg.defs);
|
|
}
|
|
}
|
|
}
|
|
return { elements, defs };
|
|
}
|
|
};
|
|
_Group.className = "Group";
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)({ convertor: (v) => (0, import_ag_charts_core21.clamp)(0, v, 1) })
|
|
], _Group.prototype, "opacity", 2);
|
|
var Group = _Group;
|
|
var ScalableGroup = class extends Scalable(Group) {
|
|
};
|
|
var RotatableGroup = class extends Rotatable(Group) {
|
|
};
|
|
var TranslatableGroup = class extends Translatable(Group) {
|
|
};
|
|
var TransformableGroup = class extends Rotatable(Translatable(Group)) {
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/sceneDebug.ts
|
|
var import_ag_charts_core22 = require("ag-charts-core");
|
|
var StatsAccumulator = class {
|
|
// Log every 10 seconds
|
|
constructor() {
|
|
this.stats = /* @__PURE__ */ new Map();
|
|
this.lastLogTime = Date.now();
|
|
this.LOG_INTERVAL_MS = 1e4;
|
|
this.startPeriodicLogging();
|
|
}
|
|
startPeriodicLogging() {
|
|
if (!import_ag_charts_core22.Debug.check("scene:stats" /* SCENE_STATS */, "scene:stats:verbose" /* SCENE_STATS_VERBOSE */)) {
|
|
return;
|
|
}
|
|
this.stopPeriodicLogging();
|
|
this.intervalId = setInterval(() => {
|
|
this.logAccumulatedStats();
|
|
}, this.LOG_INTERVAL_MS);
|
|
}
|
|
stopPeriodicLogging() {
|
|
if (this.intervalId) {
|
|
clearInterval(this.intervalId);
|
|
this.intervalId = void 0;
|
|
}
|
|
}
|
|
recordTiming(category, duration) {
|
|
const existing = this.stats.get(category);
|
|
if (existing) {
|
|
existing.min = Math.min(existing.min, duration);
|
|
existing.max = Math.max(existing.max, duration);
|
|
existing.sum += duration;
|
|
existing.count += 1;
|
|
} else {
|
|
this.stats.set(category, {
|
|
min: duration,
|
|
max: duration,
|
|
sum: duration,
|
|
count: 1
|
|
});
|
|
}
|
|
}
|
|
recordTimings(durations) {
|
|
for (const [category, duration] of Object.entries(durations)) {
|
|
if (category !== "start" && typeof duration === "number") {
|
|
this.recordTiming(category, duration);
|
|
}
|
|
}
|
|
}
|
|
logAccumulatedStats() {
|
|
if (this.stats.size === 0)
|
|
return;
|
|
const timeSinceLastLog = (Date.now() - this.lastLogTime) / 1e3;
|
|
const categories = Array.from(this.stats.keys()).sort((a, b) => {
|
|
if (a === "\u23F1\uFE0F")
|
|
return -1;
|
|
if (b === "\u23F1\uFE0F")
|
|
return 1;
|
|
return a.localeCompare(b);
|
|
});
|
|
const parts = [];
|
|
for (const category of categories) {
|
|
const stats = this.stats.get(category);
|
|
const avg = stats.sum / stats.count;
|
|
parts.push(`${category}[${stats.min.toFixed(1)}/${avg.toFixed(1)}/${stats.max.toFixed(1)}]ms`);
|
|
}
|
|
const totalStats = this.stats.get("\u23F1\uFE0F");
|
|
const count = totalStats?.count ?? 0;
|
|
import_ag_charts_core22.Logger.log(`\u{1F4CA} Stats (${timeSinceLastLog.toFixed(0)}s, ${count} renders): ${parts.join(" ")}`);
|
|
this.stats.clear();
|
|
this.lastLogTime = Date.now();
|
|
}
|
|
destroy() {
|
|
this.stopPeriodicLogging();
|
|
this.stats.clear();
|
|
}
|
|
};
|
|
var globalStatsAccumulator;
|
|
var statsAccumulatorConsumers = 0;
|
|
function getStatsAccumulator() {
|
|
globalStatsAccumulator ?? (globalStatsAccumulator = new StatsAccumulator());
|
|
return globalStatsAccumulator;
|
|
}
|
|
function registerDebugStatsConsumer() {
|
|
statsAccumulatorConsumers++;
|
|
let released = false;
|
|
return () => {
|
|
if (released || statsAccumulatorConsumers === 0)
|
|
return;
|
|
released = true;
|
|
statsAccumulatorConsumers--;
|
|
if (statsAccumulatorConsumers === 0) {
|
|
cleanupDebugStats();
|
|
}
|
|
};
|
|
}
|
|
function formatBytes(value) {
|
|
for (const unit of ["B", "KB", "MB", "GB"]) {
|
|
if (value < 1536) {
|
|
return `${value.toFixed(1)}${unit}`;
|
|
}
|
|
value /= 1024;
|
|
}
|
|
return `${value.toFixed(1)}TB}`;
|
|
}
|
|
function memoryUsage() {
|
|
if (!("memory" in performance))
|
|
return;
|
|
const { totalJSHeapSize, usedJSHeapSize, jsHeapSizeLimit } = performance.memory;
|
|
const result = [];
|
|
for (const amount of [usedJSHeapSize, totalJSHeapSize, jsHeapSizeLimit]) {
|
|
if (typeof amount !== "number")
|
|
continue;
|
|
result.push(formatBytes(amount));
|
|
}
|
|
return `Heap ${result.join(" / ")}`;
|
|
}
|
|
function debugStats(layersManager, debugSplitTimes, ctx, renderCtxStats, extraDebugStats = {}, seriesRect = BBox.zero, colors) {
|
|
if (!import_ag_charts_core22.Debug.check("scene:stats" /* SCENE_STATS */, "scene:stats:verbose" /* SCENE_STATS_VERBOSE */))
|
|
return;
|
|
const {
|
|
layersRendered = 0,
|
|
layersSkipped = 0,
|
|
nodesRendered = 0,
|
|
nodesSkipped = 0,
|
|
opsPerformed = 0,
|
|
opsSkipped = 0
|
|
} = renderCtxStats ?? {};
|
|
const end2 = performance.now();
|
|
const { start, ...durations } = debugSplitTimes;
|
|
const totalTime = end2 - start;
|
|
const statsAccumulator = getStatsAccumulator();
|
|
statsAccumulator.recordTimings(durations);
|
|
statsAccumulator.recordTiming("\u23F1\uFE0F", totalTime);
|
|
const splits = Object.entries(durations).map(([n, t]) => time(n, t)).filter((v) => v != null).join(" + ");
|
|
const extras = Object.entries(extraDebugStats).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(" ; ");
|
|
const detailedStats = import_ag_charts_core22.Debug.check("scene:stats:verbose" /* SCENE_STATS_VERBOSE */);
|
|
const memUsage = detailedStats ? memoryUsage() : null;
|
|
const metrics = detailedStats ? import_ag_charts_core22.DebugMetrics.flush() : {};
|
|
const metricsEntries = Object.entries(metrics);
|
|
const aggregationMetrics = [];
|
|
const nodeDataMetrics = [];
|
|
for (const [k, v] of metricsEntries) {
|
|
if (k.endsWith(":aggregation") && Array.isArray(v)) {
|
|
aggregationMetrics.push(`${k.replace(":aggregation", "")}(${v.join(",")})`);
|
|
} else if (k.endsWith(":nodeData") && typeof v === "number") {
|
|
nodeDataMetrics.push(`${k.replace(":nodeData", "")}(${v})`);
|
|
}
|
|
}
|
|
const aggregationText = aggregationMetrics.length > 0 ? `Aggregation: ${aggregationMetrics.join(", ")}` : null;
|
|
const nodeDataText = nodeDataMetrics.length > 0 ? `NodeData: ${nodeDataMetrics.join(", ")}` : null;
|
|
const stats = [
|
|
`${time("\u23F1\uFE0F", start, end2)} (${splits})`,
|
|
`${extras}`,
|
|
aggregationText,
|
|
nodeDataText,
|
|
`Layers: ${detailedStats ? pct(layersRendered, layersSkipped) : layersManager.size}`,
|
|
detailedStats ? `Nodes: ${pct(nodesRendered, nodesSkipped)}` : null,
|
|
detailedStats ? `Ops: ${pct(opsPerformed, opsSkipped)}` : null,
|
|
memUsage
|
|
].filter(import_ag_charts_core22.isString);
|
|
const measurer = new import_ag_charts_core22.TextMeasurer(ctx);
|
|
const statsSize = new Map(stats.map((t) => [t, measurer.measureText(t)]));
|
|
const width = Math.max(...Array.from(statsSize.values(), (s) => s.width));
|
|
const height = accumulate(statsSize.values(), (s) => s.height);
|
|
const x = 2 + seriesRect.x;
|
|
ctx.save();
|
|
try {
|
|
ctx.fillStyle = colors?.background ?? "white";
|
|
ctx.fillRect(x, 0, width, height);
|
|
ctx.fillStyle = colors?.foreground ?? "black";
|
|
let y = 0;
|
|
for (const [stat, size] of statsSize.entries()) {
|
|
y += size.height;
|
|
ctx.fillText(stat, x, y);
|
|
}
|
|
} catch (e) {
|
|
import_ag_charts_core22.Logger.warnOnce("Error during debug stats rendering", e);
|
|
} finally {
|
|
ctx.restore();
|
|
}
|
|
}
|
|
function prepareSceneNodeHighlight(ctx) {
|
|
const config = (0, import_ag_charts_core22.toArray)((0, import_ag_charts_core22.getWindow)("agChartsSceneDebug"));
|
|
const result = [];
|
|
for (const name of config) {
|
|
if (name === "layout") {
|
|
result.push("seriesRoot", "legend", "root", /.*Axis-\d+-axis.*/);
|
|
} else {
|
|
result.push(name);
|
|
}
|
|
}
|
|
ctx.debugNodeSearch = result;
|
|
}
|
|
function debugSceneNodeHighlight(ctx, debugNodes) {
|
|
ctx.save();
|
|
try {
|
|
for (const [name, node] of Object.entries(debugNodes)) {
|
|
const bbox = Transformable.toCanvas(node);
|
|
if (!bbox) {
|
|
import_ag_charts_core22.Logger.log(`Scene.render() - no bbox for debugged node [${name}].`);
|
|
continue;
|
|
}
|
|
ctx.globalAlpha = 0.8;
|
|
ctx.strokeStyle = "red";
|
|
ctx.lineWidth = 1;
|
|
ctx.strokeRect(bbox.x, bbox.y, bbox.width, bbox.height);
|
|
ctx.fillStyle = "red";
|
|
ctx.strokeStyle = "white";
|
|
ctx.font = "16px sans-serif";
|
|
ctx.textBaseline = "top";
|
|
ctx.textAlign = "left";
|
|
ctx.lineWidth = 2;
|
|
ctx.strokeText(name, bbox.x, bbox.y, bbox.width);
|
|
ctx.fillText(name, bbox.x, bbox.y, bbox.width);
|
|
}
|
|
} catch (e) {
|
|
import_ag_charts_core22.Logger.warnOnce("Error during debug rendering", e);
|
|
} finally {
|
|
ctx.restore();
|
|
}
|
|
}
|
|
var skippedProperties = /* @__PURE__ */ new Set();
|
|
var allowedProperties = /* @__PURE__ */ new Set([
|
|
"gradient",
|
|
// '_datum',
|
|
"zIndex",
|
|
"clipRect",
|
|
"cachedBBox",
|
|
"childNodeCounts",
|
|
"path",
|
|
"__zIndex",
|
|
"name",
|
|
"__scalingCenterX",
|
|
"__scalingCenterY",
|
|
"__rotationCenterX",
|
|
"__rotationCenterY",
|
|
"_previousDatum",
|
|
"__fill",
|
|
"__lineDash",
|
|
"borderPath",
|
|
"borderClipPath",
|
|
"_clipPath"
|
|
]);
|
|
function nodeProps(node) {
|
|
const { ...allProps } = node;
|
|
for (const prop of Object.keys(allProps)) {
|
|
if (allowedProperties.has(prop))
|
|
continue;
|
|
if (typeof allProps[prop] === "number")
|
|
continue;
|
|
if (typeof allProps[prop] === "string")
|
|
continue;
|
|
if (typeof allProps[prop] === "boolean")
|
|
continue;
|
|
skippedProperties.add(prop);
|
|
delete allProps[prop];
|
|
}
|
|
return allProps;
|
|
}
|
|
function buildTree(node, mode) {
|
|
if (!import_ag_charts_core22.Debug.check(true, "scene" /* SCENE */)) {
|
|
return {};
|
|
}
|
|
let order = 0;
|
|
return {
|
|
node: mode === "json" ? nodeProps(node) : node,
|
|
name: node.name ?? node.id,
|
|
dirty: node instanceof Group ? node.dirty : void 0,
|
|
...Array.from(node instanceof Group ? node.children() : [], (c) => buildTree(c, mode)).reduce((result, childTree) => {
|
|
let { name: treeNodeName } = childTree;
|
|
const {
|
|
node: { visible, opacity, zIndex, translationX, translationY, rotation, scalingX, scalingY },
|
|
node: childNode
|
|
} = childTree;
|
|
if (!visible || opacity <= 0) {
|
|
treeNodeName = `(${treeNodeName})`;
|
|
}
|
|
if (Group.is(childNode) && childNode.renderToOffscreenCanvas) {
|
|
treeNodeName = `*${treeNodeName}*`;
|
|
}
|
|
const zIndexString = Array.isArray(zIndex) ? `(${zIndex.join(", ")})` : zIndex;
|
|
const key = [
|
|
`${(order++).toString().padStart(3, "0")}|`,
|
|
`${treeNodeName ?? "<unknown>"}`,
|
|
`z: ${zIndexString}`,
|
|
translationX && `x: ${translationX}`,
|
|
translationY && `y: ${translationY}`,
|
|
rotation && `r: ${rotation}`,
|
|
scalingX != null && scalingX !== 1 && `sx: ${scalingX}`,
|
|
scalingY != null && scalingY !== 1 && `sy: ${scalingY}`
|
|
].filter((v) => !!v).join(" ");
|
|
let selectedKey = key;
|
|
let index = 1;
|
|
while (result[selectedKey] != null && index < 100) {
|
|
selectedKey = `${key} (${index++})`;
|
|
}
|
|
result[selectedKey] = childTree;
|
|
return result;
|
|
}, {})
|
|
};
|
|
}
|
|
function buildDirtyTree(node) {
|
|
const nodeDirty = node instanceof Group ? node.dirty : void 0;
|
|
if (!nodeDirty) {
|
|
return { dirtyTree: {}, paths: [] };
|
|
}
|
|
const childrenDirtyTree = Array.from(node instanceof Group ? node.children() : [], (c) => buildDirtyTree(c)).filter(
|
|
(c) => c.paths.length > 0
|
|
);
|
|
const name = Group.is(node) ? node.name ?? node.id : node.id;
|
|
const paths = childrenDirtyTree.length ? childrenDirtyTree.flatMap((c) => c.paths).map((p) => `${name}.${p}`) : [name];
|
|
return {
|
|
dirtyTree: {
|
|
name,
|
|
node,
|
|
dirty: nodeDirty,
|
|
...childrenDirtyTree.map((c) => c.dirtyTree).filter((t) => t.dirty != null).reduce((result, childTree) => {
|
|
result[childTree.name ?? "<unknown>"] = childTree;
|
|
return result;
|
|
}, {})
|
|
},
|
|
paths
|
|
};
|
|
}
|
|
function pct(rendered, skipped) {
|
|
const total = rendered + skipped;
|
|
return `${rendered} / ${total} (${Math.round(100 * rendered / total)}%)`;
|
|
}
|
|
function time(name, start, end2) {
|
|
const duration = end2 == null ? start : end2 - start;
|
|
return `${name}: ${Math.round(duration * 100) / 100}ms`;
|
|
}
|
|
function accumulate(iterator, mapper) {
|
|
let sum = 0;
|
|
for (const item of iterator) {
|
|
sum += mapper(item);
|
|
}
|
|
return sum;
|
|
}
|
|
function cleanupDebugStats(force = false) {
|
|
if (!globalStatsAccumulator) {
|
|
if (force) {
|
|
statsAccumulatorConsumers = 0;
|
|
}
|
|
return;
|
|
}
|
|
if (!force && statsAccumulatorConsumers > 0) {
|
|
return;
|
|
}
|
|
globalStatsAccumulator.destroy();
|
|
globalStatsAccumulator = void 0;
|
|
if (force) {
|
|
statsAccumulatorConsumers = 0;
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/shape/rect.ts
|
|
var import_ag_charts_core24 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scene/util/corner.ts
|
|
var drawCorner = (path, { x0, y0, x1, y1, cx, cy }, cornerRadius, move) => {
|
|
if (move) {
|
|
path.moveTo(x0, y0);
|
|
}
|
|
if (x0 !== x1 || y0 !== y1) {
|
|
const r0 = Math.atan2(y0 - cy, x0 - cx);
|
|
const r1 = Math.atan2(y1 - cy, x1 - cx);
|
|
path.arc(cx, cy, cornerRadius, r0, r1);
|
|
} else {
|
|
path.lineTo(x0, y0);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/shape/path.ts
|
|
var import_ag_charts_core23 = require("ag-charts-core");
|
|
var Path = class extends Shape {
|
|
constructor() {
|
|
super(...arguments);
|
|
/**
|
|
* Declare a path to retain for later rendering and hit testing
|
|
* using custom Path2D class. Think of it as a TypeScript version
|
|
* of the native Path2D (with some differences) that works in all browsers.
|
|
*/
|
|
this.path = new ExtendedPath2D();
|
|
this._clipX = Number.NaN;
|
|
this._clipY = Number.NaN;
|
|
this.clip = false;
|
|
/**
|
|
* The path only has to be updated when certain attributes change.
|
|
* For example, if transform attributes (such as `translationX`)
|
|
* are changed, we don't have to update the path. The `dirtyPath` flag
|
|
* is how we keep track if the path has to be updated or not.
|
|
*/
|
|
this._dirtyPath = true;
|
|
this.lastPixelRatio = Number.NaN;
|
|
}
|
|
set clipX(value) {
|
|
this._clipX = value;
|
|
this.dirtyPath = true;
|
|
}
|
|
set clipY(value) {
|
|
this._clipY = value;
|
|
this.dirtyPath = true;
|
|
}
|
|
set dirtyPath(value) {
|
|
if (this._dirtyPath !== value) {
|
|
this._dirtyPath = value;
|
|
if (value) {
|
|
this.markDirty("path");
|
|
}
|
|
}
|
|
}
|
|
get dirtyPath() {
|
|
return this._dirtyPath;
|
|
}
|
|
checkPathDirty() {
|
|
if (this._dirtyPath) {
|
|
return;
|
|
}
|
|
this.dirtyPath = this.path.isDirty() || (this.fillShadow?.isDirty() ?? false) || (this._clipPath?.isDirty() ?? false);
|
|
}
|
|
resetPathDirty() {
|
|
this.path.clear(true);
|
|
this._dirtyPath = false;
|
|
}
|
|
isPathDirty() {
|
|
return this.path.isDirty();
|
|
}
|
|
onChangeDetection(property) {
|
|
if (!this._dirtyPath) {
|
|
this._dirtyPath = true;
|
|
super.onChangeDetection(property);
|
|
}
|
|
}
|
|
computeBBox() {
|
|
this.updatePathIfDirty();
|
|
return this.path.computeBBox();
|
|
}
|
|
isPointInPath(x, y) {
|
|
this.updatePathIfDirty();
|
|
return this.path.closedPath && this.path.isPointInPath(x, y);
|
|
}
|
|
distanceSquared(x, y) {
|
|
return this.distanceSquaredTransformedPoint(x, y);
|
|
}
|
|
svgPathData(transform) {
|
|
this.updatePathIfDirty();
|
|
return this.path.toSVG(transform);
|
|
}
|
|
distanceSquaredTransformedPoint(x, y) {
|
|
this.updatePathIfDirty();
|
|
if (this.path.closedPath && this.path.isPointInPath(x, y)) {
|
|
return 0;
|
|
}
|
|
return this.path.distanceSquared(x, y);
|
|
}
|
|
isDirtyPath() {
|
|
return false;
|
|
}
|
|
updatePath() {
|
|
}
|
|
updatePathIfDirty() {
|
|
if (this.dirtyPath || this.isDirtyPath()) {
|
|
this.updatePath();
|
|
this.dirtyPath = false;
|
|
}
|
|
}
|
|
preRender(renderCtx) {
|
|
if (renderCtx.devicePixelRatio !== this.lastPixelRatio) {
|
|
this.dirtyPath = true;
|
|
}
|
|
this.lastPixelRatio = renderCtx.devicePixelRatio;
|
|
this.updatePathIfDirty();
|
|
return super.preRender(renderCtx, this.path.commands.length);
|
|
}
|
|
render(renderCtx) {
|
|
const { ctx } = renderCtx;
|
|
if (this.clip && !Number.isNaN(this._clipX) && !Number.isNaN(this._clipY)) {
|
|
ctx.save();
|
|
try {
|
|
const margin = this.strokeWidth / 2;
|
|
this._clipPath ?? (this._clipPath = new ExtendedPath2D());
|
|
this._clipPath.clear();
|
|
this._clipPath.rect(-margin, -margin, this._clipX + margin, this._clipY + margin + margin);
|
|
ctx.clip(this._clipPath?.getPath2D());
|
|
if (this._clipX > 0 && this._clipY > 0) {
|
|
this.drawPath(ctx);
|
|
}
|
|
} finally {
|
|
ctx.restore();
|
|
}
|
|
} else {
|
|
this._clipPath = void 0;
|
|
this.drawPath(ctx);
|
|
}
|
|
this.fillShadow?.markClean();
|
|
super.render(renderCtx);
|
|
}
|
|
drawPath(ctx) {
|
|
this.fillStroke(ctx, this.path.getPath2D());
|
|
}
|
|
toSVG() {
|
|
if (!this.visible)
|
|
return;
|
|
const element = (0, import_ag_charts_core23.createSvgElement)("path");
|
|
element.setAttribute("d", this.svgPathData());
|
|
const defs = this.applySvgFillAttributes(element, []);
|
|
this.applySvgStrokeAttributes(element);
|
|
return {
|
|
elements: [element],
|
|
defs
|
|
};
|
|
}
|
|
};
|
|
Path.className = "Path";
|
|
__decorateClass([
|
|
(0, import_ag_charts_core23.SceneChangeDetection)()
|
|
], Path.prototype, "clip", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core23.SceneChangeDetection)()
|
|
], Path.prototype, "clipX", 1);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core23.SceneChangeDetection)()
|
|
], Path.prototype, "clipY", 1);
|
|
|
|
// packages/ag-charts-community/src/scene/shape/rect.ts
|
|
function cornerEdges(leadingEdge, trailingEdge, leadingInset, trailingInset, cornerRadius) {
|
|
let leadingClipped = false;
|
|
let trailingClipped = false;
|
|
let leading0 = trailingInset - Math.sqrt(Math.max(cornerRadius ** 2 - leadingInset ** 2, 0));
|
|
let leading1 = 0;
|
|
let trailing0 = 0;
|
|
let trailing1 = leadingInset - Math.sqrt(Math.max(cornerRadius ** 2 - trailingInset ** 2, 0));
|
|
if (leading0 > leadingEdge) {
|
|
leadingClipped = true;
|
|
leading0 = leadingEdge;
|
|
leading1 = leadingInset - Math.sqrt(Math.max(cornerRadius ** 2 - (trailingInset - leadingEdge) ** 2));
|
|
} else if ((0, import_ag_charts_core24.isNumberEqual)(leading0, 0)) {
|
|
leading0 = 0;
|
|
}
|
|
if (trailing1 > trailingEdge) {
|
|
trailingClipped = true;
|
|
trailing0 = trailingInset - Math.sqrt(Math.max(cornerRadius ** 2 - (leadingInset - trailingEdge) ** 2));
|
|
trailing1 = trailingEdge;
|
|
} else if ((0, import_ag_charts_core24.isNumberEqual)(trailing1, 0)) {
|
|
trailing1 = 0;
|
|
}
|
|
return { leading0, leading1, trailing0, trailing1, leadingClipped, trailingClipped };
|
|
}
|
|
function clippedRoundRect(path, x, y, width, height, cornerRadii, clipBBox) {
|
|
let {
|
|
topLeft: topLeftCornerRadius,
|
|
topRight: topRightCornerRadius,
|
|
bottomRight: bottomRightCornerRadius,
|
|
bottomLeft: bottomLeftCornerRadius
|
|
} = cornerRadii;
|
|
const maxVerticalCornerRadius = Math.max(
|
|
topLeftCornerRadius + bottomLeftCornerRadius,
|
|
topRightCornerRadius + bottomRightCornerRadius
|
|
);
|
|
const maxHorizontalCornerRadius = Math.max(
|
|
topLeftCornerRadius + topRightCornerRadius,
|
|
bottomLeftCornerRadius + bottomRightCornerRadius
|
|
);
|
|
if (maxVerticalCornerRadius <= 0 && maxHorizontalCornerRadius <= 0) {
|
|
if (clipBBox == null) {
|
|
path.rect(x, y, width, height);
|
|
} else {
|
|
const x0 = Math.max(x, clipBBox.x);
|
|
const x1 = Math.min(x + width, clipBBox.x + clipBBox.width);
|
|
const y0 = Math.max(y, clipBBox.y);
|
|
const y1 = Math.min(y + height, clipBBox.y + clipBBox.height);
|
|
path.rect(x0, y0, x1 - x0, y1 - y0);
|
|
}
|
|
return;
|
|
} else if (clipBBox == null && topLeftCornerRadius === topRightCornerRadius && topLeftCornerRadius === bottomRightCornerRadius && topLeftCornerRadius === bottomLeftCornerRadius) {
|
|
path.roundRect(x, y, width, height, topLeftCornerRadius);
|
|
return;
|
|
}
|
|
if (width < 0) {
|
|
x += width;
|
|
width = Math.abs(width);
|
|
}
|
|
if (height < 0) {
|
|
y += height;
|
|
height = Math.abs(height);
|
|
}
|
|
if (width <= 0 || height <= 0)
|
|
return;
|
|
if (clipBBox == null) {
|
|
clipBBox = new BBox(x, y, width, height);
|
|
} else {
|
|
const x0 = Math.max(x, clipBBox.x);
|
|
const x1 = Math.min(x + width, clipBBox.x + clipBBox.width);
|
|
const y0 = Math.max(y, clipBBox.y);
|
|
const y1 = Math.min(y + height, clipBBox.y + clipBBox.height);
|
|
clipBBox = new BBox(x0, y0, x1 - x0, y1 - y0);
|
|
}
|
|
const borderScale = Math.max(maxVerticalCornerRadius / height, maxHorizontalCornerRadius / width, 1);
|
|
if (borderScale > 1) {
|
|
topLeftCornerRadius /= borderScale;
|
|
topRightCornerRadius /= borderScale;
|
|
bottomRightCornerRadius /= borderScale;
|
|
bottomLeftCornerRadius /= borderScale;
|
|
}
|
|
let drawTopLeftCorner = true;
|
|
let drawTopRightCorner = true;
|
|
let drawBottomRightCorner = true;
|
|
let drawBottomLeftCorner = true;
|
|
let topLeftCorner;
|
|
let topRightCorner;
|
|
let bottomRightCorner;
|
|
let bottomLeftCorner;
|
|
if (drawTopLeftCorner) {
|
|
const nodes = cornerEdges(
|
|
clipBBox.height,
|
|
clipBBox.width,
|
|
Math.max(x + topLeftCornerRadius - clipBBox.x, 0),
|
|
Math.max(y + topLeftCornerRadius - clipBBox.y, 0),
|
|
topLeftCornerRadius
|
|
);
|
|
if (nodes.leadingClipped)
|
|
drawBottomLeftCorner = false;
|
|
if (nodes.trailingClipped)
|
|
drawTopRightCorner = false;
|
|
const x0 = Math.max(clipBBox.x + nodes.leading1, clipBBox.x);
|
|
const y0 = Math.max(clipBBox.y + nodes.leading0, clipBBox.y);
|
|
const x1 = Math.max(clipBBox.x + nodes.trailing1, clipBBox.x);
|
|
const y1 = Math.max(clipBBox.y + nodes.trailing0, clipBBox.y);
|
|
const cx = x + topLeftCornerRadius;
|
|
const cy = y + topLeftCornerRadius;
|
|
topLeftCorner = { x0, y0, x1, y1, cx, cy };
|
|
}
|
|
if (drawTopRightCorner) {
|
|
const nodes = cornerEdges(
|
|
clipBBox.width,
|
|
clipBBox.height,
|
|
Math.max(y + topRightCornerRadius - clipBBox.y, 0),
|
|
Math.max(clipBBox.x + clipBBox.width - (x + width - topRightCornerRadius), 0),
|
|
topRightCornerRadius
|
|
);
|
|
if (nodes.leadingClipped)
|
|
drawTopLeftCorner = false;
|
|
if (nodes.trailingClipped)
|
|
drawBottomRightCorner = false;
|
|
const x0 = Math.min(clipBBox.x + clipBBox.width - nodes.leading0, clipBBox.x + clipBBox.width);
|
|
const y0 = Math.max(clipBBox.y + nodes.leading1, clipBBox.y);
|
|
const x1 = Math.min(clipBBox.x + clipBBox.width - nodes.trailing0, clipBBox.x + clipBBox.width);
|
|
const y1 = Math.max(clipBBox.y + nodes.trailing1, clipBBox.y);
|
|
const cx = x + width - topRightCornerRadius;
|
|
const cy = y + topRightCornerRadius;
|
|
topRightCorner = { x0, y0, x1, y1, cx, cy };
|
|
}
|
|
if (drawBottomRightCorner) {
|
|
const nodes = cornerEdges(
|
|
clipBBox.height,
|
|
clipBBox.width,
|
|
Math.max(clipBBox.x + clipBBox.width - (x + width - bottomRightCornerRadius), 0),
|
|
Math.max(clipBBox.y + clipBBox.height - (y + height - bottomRightCornerRadius), 0),
|
|
bottomRightCornerRadius
|
|
);
|
|
if (nodes.leadingClipped)
|
|
drawTopRightCorner = false;
|
|
if (nodes.trailingClipped)
|
|
drawBottomLeftCorner = false;
|
|
const x0 = Math.min(clipBBox.x + clipBBox.width - nodes.leading1, clipBBox.x + clipBBox.width);
|
|
const y0 = Math.min(clipBBox.y + clipBBox.height - nodes.leading0, clipBBox.y + clipBBox.height);
|
|
const x1 = Math.min(clipBBox.x + clipBBox.width - nodes.trailing1, clipBBox.x + clipBBox.width);
|
|
const y1 = Math.min(clipBBox.y + clipBBox.height - nodes.trailing0, clipBBox.y + clipBBox.height);
|
|
const cx = x + width - bottomRightCornerRadius;
|
|
const cy = y + height - bottomRightCornerRadius;
|
|
bottomRightCorner = { x0, y0, x1, y1, cx, cy };
|
|
}
|
|
if (drawBottomLeftCorner) {
|
|
const nodes = cornerEdges(
|
|
clipBBox.width,
|
|
clipBBox.height,
|
|
Math.max(clipBBox.y + clipBBox.height - (y + height - bottomLeftCornerRadius), 0),
|
|
Math.max(x + bottomLeftCornerRadius - clipBBox.x, 0),
|
|
bottomLeftCornerRadius
|
|
);
|
|
if (nodes.leadingClipped)
|
|
drawBottomRightCorner = false;
|
|
if (nodes.trailingClipped)
|
|
drawTopLeftCorner = false;
|
|
const x0 = Math.max(clipBBox.x + nodes.leading0, clipBBox.x);
|
|
const y0 = Math.min(clipBBox.y + clipBBox.height - nodes.leading1, clipBBox.y + clipBBox.height);
|
|
const x1 = Math.max(clipBBox.x + nodes.trailing0, clipBBox.x);
|
|
const y1 = Math.min(clipBBox.y + clipBBox.height - nodes.trailing1, clipBBox.y + clipBBox.height);
|
|
const cx = x + bottomLeftCornerRadius;
|
|
const cy = y + height - bottomLeftCornerRadius;
|
|
bottomLeftCorner = { x0, y0, x1, y1, cx, cy };
|
|
}
|
|
let didMove = false;
|
|
if (drawTopLeftCorner && topLeftCorner != null) {
|
|
drawCorner(path, topLeftCorner, topLeftCornerRadius, !didMove);
|
|
didMove || (didMove = true);
|
|
}
|
|
if (drawTopRightCorner && topRightCorner != null) {
|
|
drawCorner(path, topRightCorner, topRightCornerRadius, !didMove);
|
|
didMove || (didMove = true);
|
|
}
|
|
if (drawBottomRightCorner && bottomRightCorner != null) {
|
|
drawCorner(path, bottomRightCorner, bottomRightCornerRadius, !didMove);
|
|
didMove || (didMove = true);
|
|
}
|
|
if (drawBottomLeftCorner && bottomLeftCorner != null) {
|
|
drawCorner(path, bottomLeftCorner, bottomLeftCornerRadius, !didMove);
|
|
}
|
|
path.closePath();
|
|
}
|
|
var Rect = class extends Path {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.borderPath = new ExtendedPath2D();
|
|
this.x = 0;
|
|
this.y = 0;
|
|
this.width = 10;
|
|
this.height = 10;
|
|
this.topLeftCornerRadius = 0;
|
|
this.topRightCornerRadius = 0;
|
|
this.bottomRightCornerRadius = 0;
|
|
this.bottomLeftCornerRadius = 0;
|
|
this.clipBBox = void 0;
|
|
this.crisp = false;
|
|
this.lastUpdatePathStrokeWidth = this.__strokeWidth;
|
|
this.effectiveStrokeWidth = this.__strokeWidth;
|
|
this.hittester = super.isPointInPath.bind(this);
|
|
this.distanceCalculator = super.distanceSquaredTransformedPoint.bind(this);
|
|
/**
|
|
* When the rectangle's width or height is less than a pixel
|
|
* and crisp mode is on, the rectangle will still fit into the pixel,
|
|
* but will be less opaque to make an effect of holding less space.
|
|
*/
|
|
this.microPixelEffectOpacity = 1;
|
|
}
|
|
// optimised field accessor
|
|
set cornerRadius(cornerRadius) {
|
|
this.topLeftCornerRadius = cornerRadius;
|
|
this.topRightCornerRadius = cornerRadius;
|
|
this.bottomRightCornerRadius = cornerRadius;
|
|
this.bottomLeftCornerRadius = cornerRadius;
|
|
}
|
|
isDirtyPath() {
|
|
return this.lastUpdatePathStrokeWidth !== this.__strokeWidth || Boolean(this.path.isDirty() || this.borderPath.isDirty());
|
|
}
|
|
updatePath() {
|
|
const {
|
|
path,
|
|
borderPath,
|
|
__crisp: crisp,
|
|
__topLeftCornerRadius: topLeft,
|
|
__topRightCornerRadius: topRight,
|
|
__bottomRightCornerRadius: bottomRight,
|
|
__bottomLeftCornerRadius: bottomLeft
|
|
} = this;
|
|
let { __x: x, __y: y, __width: w, __height: h, __strokeWidth: strokeWidth, __clipBBox: clipBBox } = this;
|
|
const pixelRatio = this.layerManager?.canvas.pixelRatio ?? 1;
|
|
const pixelSize = 1 / pixelRatio;
|
|
let microPixelEffectOpacity = 1;
|
|
path.clear();
|
|
borderPath.clear();
|
|
if (w === 0 || h === 0) {
|
|
this.effectiveStrokeWidth = 0;
|
|
this.lastUpdatePathStrokeWidth = 0;
|
|
this.microPixelEffectOpacity = 0;
|
|
return;
|
|
}
|
|
if (crisp) {
|
|
if (w <= pixelSize) {
|
|
microPixelEffectOpacity *= w / pixelSize;
|
|
}
|
|
if (h <= pixelSize) {
|
|
microPixelEffectOpacity *= h / pixelSize;
|
|
}
|
|
w = this.align(x, w);
|
|
h = this.align(y, h);
|
|
x = this.align(x);
|
|
y = this.align(y);
|
|
clipBBox = clipBBox == null ? void 0 : new BBox(
|
|
this.align(clipBBox.x),
|
|
this.align(clipBBox.y),
|
|
this.align(clipBBox.x, clipBBox.width),
|
|
this.align(clipBBox.y, clipBBox.height)
|
|
);
|
|
}
|
|
if (strokeWidth) {
|
|
if (w < pixelSize) {
|
|
const lx = x + pixelSize / 2;
|
|
borderPath.moveTo(lx, y);
|
|
borderPath.lineTo(lx, y + h);
|
|
strokeWidth = pixelSize;
|
|
this.borderClipPath = void 0;
|
|
} else if (h < pixelSize) {
|
|
const ly = y + pixelSize / 2;
|
|
borderPath.moveTo(x, ly);
|
|
borderPath.lineTo(x + w, ly);
|
|
strokeWidth = pixelSize;
|
|
this.borderClipPath = void 0;
|
|
} else if (strokeWidth < w && strokeWidth < h) {
|
|
const halfStrokeWidth = strokeWidth / 2;
|
|
x += halfStrokeWidth;
|
|
y += halfStrokeWidth;
|
|
w -= strokeWidth;
|
|
h -= strokeWidth;
|
|
const adjustedClipBBox = clipBBox?.clone().shrink(halfStrokeWidth);
|
|
const cornerRadii = {
|
|
topLeft: topLeft > 0 ? topLeft - strokeWidth : 0,
|
|
topRight: topRight > 0 ? topRight - strokeWidth : 0,
|
|
bottomRight: bottomRight > 0 ? bottomRight - strokeWidth : 0,
|
|
bottomLeft: bottomLeft > 0 ? bottomLeft - strokeWidth : 0
|
|
};
|
|
this.borderClipPath = void 0;
|
|
if (w > 0 && h > 0 && (adjustedClipBBox == null || adjustedClipBBox?.width > 0 && adjustedClipBBox?.height > 0)) {
|
|
clippedRoundRect(path, x, y, w, h, cornerRadii, adjustedClipBBox);
|
|
clippedRoundRect(borderPath, x, y, w, h, cornerRadii, adjustedClipBBox);
|
|
}
|
|
} else {
|
|
this.borderClipPath = this.borderClipPath ?? new ExtendedPath2D();
|
|
this.borderClipPath.clear();
|
|
this.borderClipPath.rect(x, y, w, h);
|
|
borderPath.rect(x, y, w, h);
|
|
}
|
|
} else {
|
|
const cornerRadii = { topLeft, topRight, bottomRight, bottomLeft };
|
|
this.borderClipPath = void 0;
|
|
clippedRoundRect(path, x, y, w, h, cornerRadii, clipBBox);
|
|
}
|
|
if ([topLeft, topRight, bottomRight, bottomLeft].every(areCornersZero)) {
|
|
let distanceSquaredFromRect2 = function(hitX, hitY) {
|
|
return rectInstance.getBBox().distanceSquared(hitX, hitY);
|
|
};
|
|
var distanceSquaredFromRect = distanceSquaredFromRect2;
|
|
const bbox = this.getBBox();
|
|
this.hittester = bbox.containsPoint.bind(bbox);
|
|
const rectInstance = this;
|
|
this.distanceSquared = distanceSquaredFromRect2;
|
|
} else {
|
|
this.hittester = super.isPointInPath;
|
|
this.distanceCalculator = super.distanceSquaredTransformedPoint;
|
|
}
|
|
this.effectiveStrokeWidth = strokeWidth;
|
|
this.lastUpdatePathStrokeWidth = strokeWidth;
|
|
this.microPixelEffectOpacity = microPixelEffectOpacity;
|
|
}
|
|
computeBBox() {
|
|
const { __x: x, __y: y, __width: width, __height: height, __clipBBox: clipBBox } = this;
|
|
return clipBBox?.clone() ?? new BBox(x, y, width, height);
|
|
}
|
|
isPointInPath(x, y) {
|
|
return this.hittester(x, y);
|
|
}
|
|
get midPoint() {
|
|
return { x: this.__x + this.__width / 2, y: this.__y + this.__height / 2 };
|
|
}
|
|
/**
|
|
* High-performance static property setter that bypasses the decorator system entirely.
|
|
* Writes directly to backing fields (__propertyName) to avoid:
|
|
* - Decorator setter chains and equality checks
|
|
* - Multiple onChangeDetection calls per property
|
|
* - Object.keys() iteration in assignIfNotStrictlyEqual
|
|
* - Object allocation overhead
|
|
*
|
|
* A single markDirty() call at the end ensures the scene graph is properly invalidated.
|
|
* WARNING: Only use for hot paths where performance is critical and properties don't need
|
|
* individual change detection (e.g., when updating many nodes in a loop).
|
|
*/
|
|
setStaticProperties(drawingMode, topLeftCornerRadius, topRightCornerRadius, bottomRightCornerRadius, bottomLeftCornerRadius, visible, crisp, fillShadow) {
|
|
this.__drawingMode = drawingMode;
|
|
this.__topLeftCornerRadius = topLeftCornerRadius;
|
|
this.__topRightCornerRadius = topRightCornerRadius;
|
|
this.__bottomRightCornerRadius = bottomRightCornerRadius;
|
|
this.__bottomLeftCornerRadius = bottomLeftCornerRadius;
|
|
this.__visible = visible;
|
|
this.__crisp = crisp;
|
|
this.__fillShadow = fillShadow;
|
|
this.dirtyPath = true;
|
|
this.markDirty();
|
|
}
|
|
/**
|
|
* High-performance animation reset that bypasses the decorator system entirely.
|
|
* Writes directly to backing fields (__x, __y, etc.) to avoid:
|
|
* - Decorator setter chains and equality checks
|
|
* - Multiple onChangeDetection calls
|
|
* - Object.keys() iteration
|
|
*
|
|
* A single markDirty() call at the end ensures the scene graph is properly invalidated.
|
|
* WARNING: Only use for animation hot paths where performance is critical.
|
|
*/
|
|
resetAnimationProperties(x, y, width, height, opacity, clipBBox) {
|
|
this.__x = x;
|
|
this.__y = y;
|
|
this.__width = width;
|
|
this.__height = height;
|
|
this.__opacity = opacity;
|
|
this.__clipBBox = clipBBox;
|
|
this.dirtyPath = true;
|
|
this.markDirty();
|
|
}
|
|
distanceSquared(x, y) {
|
|
return this.distanceCalculator(x, y);
|
|
}
|
|
applyFillAndAlpha(ctx) {
|
|
super.applyFillAndAlpha(ctx);
|
|
ctx.globalAlpha *= this.microPixelEffectOpacity;
|
|
}
|
|
applyStrokeAndAlpha(ctx) {
|
|
super.applyStrokeAndAlpha(ctx);
|
|
ctx.globalAlpha *= this.microPixelEffectOpacity;
|
|
}
|
|
renderStroke(ctx) {
|
|
const { stroke, effectiveStrokeWidth } = this;
|
|
if (stroke && effectiveStrokeWidth) {
|
|
const { globalAlpha } = ctx;
|
|
const { lineDash, lineDashOffset, lineCap, lineJoin, borderPath, borderClipPath } = this;
|
|
if (borderClipPath) {
|
|
ctx.clip(borderClipPath.getPath2D());
|
|
}
|
|
this.applyStrokeAndAlpha(ctx);
|
|
ctx.lineWidth = effectiveStrokeWidth;
|
|
if (lineDash) {
|
|
ctx.setLineDash(lineDash);
|
|
}
|
|
if (lineDashOffset) {
|
|
ctx.lineDashOffset = lineDashOffset;
|
|
}
|
|
if (lineCap) {
|
|
ctx.lineCap = lineCap;
|
|
}
|
|
if (lineJoin) {
|
|
ctx.lineJoin = lineJoin;
|
|
}
|
|
ctx.stroke(borderPath.getPath2D());
|
|
ctx.globalAlpha = globalAlpha;
|
|
}
|
|
}
|
|
};
|
|
Rect.className = "Rect";
|
|
__decorateClass([
|
|
(0, import_ag_charts_core24.DeclaredSceneChangeDetection)()
|
|
], Rect.prototype, "x", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core24.DeclaredSceneChangeDetection)()
|
|
], Rect.prototype, "y", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core24.DeclaredSceneChangeDetection)()
|
|
], Rect.prototype, "width", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core24.DeclaredSceneChangeDetection)()
|
|
], Rect.prototype, "height", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core24.DeclaredSceneChangeDetection)()
|
|
], Rect.prototype, "topLeftCornerRadius", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core24.DeclaredSceneChangeDetection)()
|
|
], Rect.prototype, "topRightCornerRadius", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core24.DeclaredSceneChangeDetection)()
|
|
], Rect.prototype, "bottomRightCornerRadius", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core24.DeclaredSceneChangeDetection)()
|
|
], Rect.prototype, "bottomLeftCornerRadius", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core24.DeclaredSceneChangeDetection)({ equals: import_ag_charts_core24.boxesEqual })
|
|
], Rect.prototype, "clipBBox", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core24.DeclaredSceneChangeDetection)()
|
|
], Rect.prototype, "crisp", 2);
|
|
function areCornersZero(cornerRadius) {
|
|
return cornerRadius === 0;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/shape/text.ts
|
|
var _Text = class _Text extends Shape {
|
|
constructor(options) {
|
|
super(options);
|
|
this.x = 0;
|
|
this.y = 0;
|
|
this.lines = [];
|
|
this.text = void 0;
|
|
this.fontCache = void 0;
|
|
this.fontSize = _Text.defaultFontSize;
|
|
this.fontFamily = "sans-serif";
|
|
this.textAlign = "start";
|
|
this.textBaseline = "alphabetic";
|
|
this.boxPadding = 0;
|
|
this.trimText = options?.trimText ?? true;
|
|
}
|
|
onTextChange() {
|
|
this.richText?.clear();
|
|
this.textMap?.clear();
|
|
if ((0, import_ag_charts_core25.isArray)(this.text)) {
|
|
this.lines = [];
|
|
this.richText ?? (this.richText = new Group());
|
|
this.richText.setScene(this.scene);
|
|
this.richText.append(
|
|
this.text.flatMap((s) => (0, import_ag_charts_core25.toTextString)(s.text).split(import_ag_charts_core25.LineSplitter)).filter(Boolean).map(() => new _Text({ trimText: false }))
|
|
);
|
|
} else {
|
|
const lines = (0, import_ag_charts_core25.toTextString)(this.text).split(import_ag_charts_core25.LineSplitter);
|
|
this.lines = this.trimText ? lines.map((line) => line.trim()) : lines;
|
|
}
|
|
}
|
|
get font() {
|
|
this.fontCache ?? (this.fontCache = (0, import_ag_charts_core25.toFontString)(this));
|
|
return this.fontCache;
|
|
}
|
|
static measureBBox(text, x, y, options) {
|
|
if ((0, import_ag_charts_core25.isArray)(text)) {
|
|
const { font, lineHeight, textAlign, textBaseline } = options;
|
|
const { width, height, lineMetrics } = (0, import_ag_charts_core25.measureTextSegments)(text, font);
|
|
const totalHeight = lineHeight ? lineHeight * lineMetrics.length : height;
|
|
const offsetTop = _Text.calcTopOffset(totalHeight, lineMetrics[0], textBaseline);
|
|
const offsetLeft = _Text.calcLeftOffset(width, textAlign);
|
|
return new BBox(x - offsetLeft, y - offsetTop, width, totalHeight);
|
|
} else {
|
|
return _Text.computeBBox((0, import_ag_charts_core25.toTextString)(text).split(import_ag_charts_core25.LineSplitter), x, y, options);
|
|
}
|
|
}
|
|
static computeBBox(lines, x, y, opts) {
|
|
const { font, lineHeight, textAlign, textBaseline } = opts;
|
|
const { width, height, lineMetrics } = (0, import_ag_charts_core25.cachedTextMeasurer)(font).measureLines(lines);
|
|
const totalHeight = lineHeight ? lineHeight * lineMetrics.length : height;
|
|
const offsetTop = _Text.calcTopOffset(totalHeight, lineMetrics[0], textBaseline);
|
|
const offsetLeft = _Text.calcLeftOffset(width, textAlign);
|
|
return new BBox(x - offsetLeft, y - offsetTop, width, totalHeight);
|
|
}
|
|
static calcTopOffset(height, textMetrics, textBaseline) {
|
|
switch (textBaseline) {
|
|
case "alphabetic":
|
|
return textMetrics?.ascent ?? 0;
|
|
case "middle":
|
|
return height / 2;
|
|
case "bottom":
|
|
return height;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
static calcSegmentedTopOffset(height, lineMetrics, textBaseline) {
|
|
switch (textBaseline) {
|
|
case "alphabetic":
|
|
return lineMetrics[0]?.ascent ?? 0;
|
|
case "middle":
|
|
return lineMetrics.length === 1 ? lineMetrics[0].ascent + lineMetrics[0].segments.reduce(
|
|
(offsetY, segment) => Math.min(offsetY, (0, import_ag_charts_core25.cachedTextMeasurer)(segment).baselineDistance("middle")),
|
|
0
|
|
) : height / 2;
|
|
case "bottom":
|
|
return height;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
static calcLeftOffset(width, textAlign) {
|
|
let offset = 0;
|
|
switch (textAlign) {
|
|
case "center":
|
|
offset = 0.5;
|
|
break;
|
|
case "right":
|
|
case "end":
|
|
offset = 1;
|
|
}
|
|
return width * offset;
|
|
}
|
|
getBBox() {
|
|
const bbox = super.getBBox();
|
|
if (!this.textMap?.size || !(0, import_ag_charts_core25.isArray)(this.text))
|
|
return bbox;
|
|
const { height, lineMetrics } = (0, import_ag_charts_core25.measureTextSegments)(this.text, this);
|
|
const offsetTop = _Text.calcSegmentedTopOffset(height, lineMetrics, this.textBaseline);
|
|
const y = this.y - offsetTop;
|
|
if (bbox.y === y)
|
|
return bbox;
|
|
return new BBox(bbox.x, y, bbox.width, bbox.height);
|
|
}
|
|
computeBBox() {
|
|
this.generateTextMap();
|
|
if (this.textMap?.size) {
|
|
const bbox = BBox.merge(this.textMap.values());
|
|
bbox.x = this.x - _Text.calcLeftOffset(bbox.width, this.textAlign);
|
|
bbox.y = this.y;
|
|
return bbox;
|
|
}
|
|
const { x, y, lines, textBaseline, textAlign } = this;
|
|
const measuredTextBounds = _Text.computeBBox(lines, x, y, { font: this, textBaseline, textAlign });
|
|
if (this.boxing != null)
|
|
measuredTextBounds.grow(this.boxPadding);
|
|
return measuredTextBounds;
|
|
}
|
|
getTextMeasureBBox() {
|
|
return this.computeBBox();
|
|
}
|
|
getPlainText() {
|
|
return (0, import_ag_charts_core25.toPlainText)(this.text);
|
|
}
|
|
isPointInPath(x, y) {
|
|
return this.getBBox()?.containsPoint(x, y) ?? false;
|
|
}
|
|
setScene(scene) {
|
|
this.richText?.setScene(scene);
|
|
super.setScene(scene);
|
|
}
|
|
generateTextMap() {
|
|
if (!(0, import_ag_charts_core25.isArray)(this.text) || this.textMap?.size)
|
|
return;
|
|
this.textMap ?? (this.textMap = /* @__PURE__ */ new Map());
|
|
let offsetY = 0;
|
|
const textNodes = this.richText.children();
|
|
for (const { width, height, ascent, segments } of (0, import_ag_charts_core25.measureTextSegments)(this.text, this).lineMetrics) {
|
|
let offsetX = 0;
|
|
for (const { color, textMetrics, ...segment } of segments) {
|
|
const textNode = textNodes.next().value;
|
|
textNode.x = this.x - width / 2 + offsetX;
|
|
textNode.y = ascent + offsetY;
|
|
textNode.setProperties({ ...segment, fill: color ?? this.fill });
|
|
const textBBox = textNode.getBBox();
|
|
this.textMap.set(textNode, textBBox);
|
|
offsetX += textMetrics.width;
|
|
}
|
|
offsetY += height;
|
|
}
|
|
}
|
|
render(renderCtx) {
|
|
const { ctx, stats } = renderCtx;
|
|
if (!this.layerManager || !this.hasRenderableText()) {
|
|
if (stats)
|
|
stats.nodesSkipped += 1;
|
|
return super.render(renderCtx);
|
|
}
|
|
if ((0, import_ag_charts_core25.isArray)(this.text) && this.richText) {
|
|
this.generateTextMap();
|
|
const richTextBBox = this.richText.getBBox();
|
|
const { width, height, lineMetrics } = (0, import_ag_charts_core25.measureTextSegments)(this.text, this);
|
|
let translateX = 0;
|
|
switch (this.textAlign) {
|
|
case "left":
|
|
case "start":
|
|
translateX = width / 2;
|
|
break;
|
|
case "right":
|
|
case "end":
|
|
translateX = width / -2;
|
|
}
|
|
const translateY = this.y - _Text.calcSegmentedTopOffset(height, lineMetrics, this.textBaseline);
|
|
this.renderBoxing(renderCtx, richTextBBox.clone().translate(translateX, translateY));
|
|
ctx.save();
|
|
ctx.translate(translateX, translateY);
|
|
this.richText.opacity = this.opacity;
|
|
this.richText.render(renderCtx);
|
|
ctx.restore();
|
|
} else {
|
|
this.renderText(renderCtx);
|
|
}
|
|
if (_Text.debug.check()) {
|
|
const bbox = this.getBBox();
|
|
ctx.lineWidth = this.textMap?.size ? 2 : 1;
|
|
ctx.strokeStyle = this.textMap?.size ? "blue" : "red";
|
|
ctx.strokeRect(bbox.x, bbox.y, bbox.width, bbox.height);
|
|
}
|
|
super.render(renderCtx);
|
|
}
|
|
markDirty(property) {
|
|
this.textMap?.clear();
|
|
return super.markDirty(property);
|
|
}
|
|
renderText(renderCtx) {
|
|
const { fill, stroke, strokeWidth, font, textAlign } = this;
|
|
if (!fill && !(stroke && strokeWidth) || !this.layerManager) {
|
|
return super.render(renderCtx);
|
|
}
|
|
const { ctx } = renderCtx;
|
|
if (ctx.font !== font) {
|
|
ctx.font = font;
|
|
}
|
|
ctx.textAlign = textAlign;
|
|
this.renderBoxing(renderCtx);
|
|
this.fillStroke(ctx);
|
|
}
|
|
renderBoxing(renderCtx, bbox) {
|
|
if (!this.boxing)
|
|
return;
|
|
const textBBox = bbox ?? _Text.computeBBox(this.lines, this.x, this.y, this);
|
|
if (textBBox.width === 0 || textBBox.height === 0)
|
|
return;
|
|
const { x, y, width, height } = textBBox.grow(this.boxPadding);
|
|
this.boxing.opacity = this.opacity;
|
|
this.boxing.x = x;
|
|
this.boxing.y = y;
|
|
this.boxing.width = width;
|
|
this.boxing.height = height;
|
|
this.boxing.preRender(renderCtx);
|
|
this.boxing.render(renderCtx);
|
|
}
|
|
executeFill(ctx) {
|
|
this.renderLines((line, x, y) => ctx.fillText(line, x, y));
|
|
}
|
|
executeStroke(ctx) {
|
|
this.renderLines((line, x, y) => ctx.strokeText(line, x, y));
|
|
}
|
|
renderLines(renderCallback) {
|
|
const { x, y, lines } = this;
|
|
if (!Number.isFinite(x) || !Number.isFinite(y))
|
|
return;
|
|
const measurer = (0, import_ag_charts_core25.cachedTextMeasurer)(this);
|
|
const { lineMetrics } = measurer.measureLines(lines);
|
|
const { textBaseline, lineHeight = measurer.lineHeight() } = this;
|
|
let offsetY = 0;
|
|
if (textBaseline === "top") {
|
|
offsetY = lineMetrics[0].ascent;
|
|
} else if (textBaseline === "middle" || textBaseline === "bottom") {
|
|
offsetY = lineHeight * (1 - lines.length);
|
|
if (textBaseline === "middle") {
|
|
offsetY /= 2;
|
|
offsetY -= measurer.baselineDistance(textBaseline);
|
|
} else {
|
|
offsetY -= lineMetrics[0].descent;
|
|
}
|
|
}
|
|
for (const line of lineMetrics) {
|
|
renderCallback(line.text, x, y + offsetY);
|
|
offsetY += lineHeight;
|
|
}
|
|
}
|
|
setFont(props) {
|
|
this.fontFamily = props.fontFamily;
|
|
this.fontSize = props.fontSize;
|
|
this.fontStyle = props.fontStyle;
|
|
this.fontWeight = props.fontWeight;
|
|
}
|
|
setAlign(props) {
|
|
this.textAlign = props.textAlign;
|
|
this.textBaseline = props.textBaseline;
|
|
}
|
|
setBoxing(props) {
|
|
const stroke = props.border?.enabled ? props.border?.stroke : void 0;
|
|
if (props.fill != null || stroke != null) {
|
|
this.boxing ?? (this.boxing = new Rect({ scene: this.scene }));
|
|
this.boxing.fill = props.fill;
|
|
this.boxing.fillOpacity = props.fillOpacity ?? 1;
|
|
this.boxing.cornerRadius = props.cornerRadius ?? 0;
|
|
this.boxing.stroke = stroke;
|
|
this.boxing.strokeWidth = props.border?.strokeWidth ?? 0;
|
|
this.boxing.strokeOpacity = props.border?.strokeOpacity ?? 1;
|
|
this.boxPadding = props.padding ?? 0;
|
|
} else if (this.boxing) {
|
|
this.boxing.destroy();
|
|
this.boxing = void 0;
|
|
}
|
|
}
|
|
getBoxingProperties() {
|
|
const { fill, fillOpacity, cornerRadius, stroke, strokeWidth, strokeOpacity } = this.boxing ?? {};
|
|
return {
|
|
border: { enabled: stroke != null, stroke, strokeWidth, strokeOpacity },
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
padding: this.boxPadding
|
|
};
|
|
}
|
|
toSVG() {
|
|
if (!this.visible || !this.hasRenderableText())
|
|
return;
|
|
const text = this.text;
|
|
if (text == null)
|
|
return;
|
|
const element = (0, import_ag_charts_core25.createSvgElement)("text");
|
|
if ((0, import_ag_charts_core25.isArray)(text)) {
|
|
for (const segment of text) {
|
|
const segmentElement = (0, import_ag_charts_core25.createSvgElement)("tspan");
|
|
setSvgFontAttributes(segmentElement, {
|
|
fontSize: segment.fontSize ?? this.fontSize,
|
|
fontFamily: segment.fontFamily ?? this.fontFamily,
|
|
fontWeight: segment.fontWeight ?? this.fontWeight,
|
|
fontStyle: segment.fontStyle ?? this.fontStyle
|
|
});
|
|
this.applySvgFillAttributes(segmentElement);
|
|
segmentElement.textContent = (0, import_ag_charts_core25.toTextString)(segment.text);
|
|
element.append(segmentElement);
|
|
}
|
|
} else {
|
|
this.applySvgFillAttributes(element);
|
|
setSvgFontAttributes(element, this);
|
|
element.setAttribute(
|
|
"text-anchor",
|
|
{
|
|
center: "middle",
|
|
left: "start",
|
|
right: "end",
|
|
start: "start",
|
|
end: "end"
|
|
}[this.textAlign ?? "start"]
|
|
);
|
|
element.setAttribute("alignment-baseline", this.textBaseline);
|
|
element.setAttribute("x", String(this.x));
|
|
element.setAttribute("y", String(this.y));
|
|
element.textContent = (0, import_ag_charts_core25.toTextString)(text);
|
|
}
|
|
return { elements: [element] };
|
|
}
|
|
hasRenderableText() {
|
|
const { text } = this;
|
|
if (text == null) {
|
|
return false;
|
|
}
|
|
return (0, import_ag_charts_core25.isArray)(text) ? true : (0, import_ag_charts_core25.toTextString)(text) !== "";
|
|
}
|
|
};
|
|
_Text.className = "Text";
|
|
_Text.debug = import_ag_charts_core25.Debug.create(true, "scene:text" /* SCENE_TEXT */);
|
|
_Text.defaultFontSize = 10;
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], _Text.prototype, "x", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], _Text.prototype, "y", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core25.SceneRefChangeDetection)({
|
|
changeCb: (o) => o.onTextChange()
|
|
})
|
|
], _Text.prototype, "text", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)({
|
|
changeCb: (o) => {
|
|
o.fontCache = void 0;
|
|
}
|
|
})
|
|
], _Text.prototype, "fontStyle", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)({
|
|
changeCb: (o) => {
|
|
o.fontCache = void 0;
|
|
}
|
|
})
|
|
], _Text.prototype, "fontWeight", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)({
|
|
changeCb: (o) => {
|
|
o.fontCache = void 0;
|
|
}
|
|
})
|
|
], _Text.prototype, "fontSize", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)({
|
|
changeCb: (o) => {
|
|
o.fontCache = void 0;
|
|
}
|
|
})
|
|
], _Text.prototype, "fontFamily", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], _Text.prototype, "textAlign", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], _Text.prototype, "textBaseline", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], _Text.prototype, "lineHeight", 2);
|
|
var Text = _Text;
|
|
var RotatableText = class extends Rotatable(Text) {
|
|
};
|
|
var TransformableText = class extends Rotatable(Translatable(Text)) {
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/caption.ts
|
|
var Caption = class extends import_ag_charts_core26.BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.id = (0, import_ag_charts_core26.createId)(this);
|
|
this.node = new RotatableText({ zIndex: 1 }).setProperties({
|
|
textAlign: "center",
|
|
pointerEvents: 1 /* None */
|
|
});
|
|
this.enabled = false;
|
|
this.textAlign = "center";
|
|
this.fontSize = import_ag_charts_core26.FONT_SIZE.SMALLER;
|
|
this.fontFamily = "sans-serif";
|
|
this.wrapping = "always";
|
|
this.padding = 0;
|
|
this.layoutStyle = "block";
|
|
this.truncated = false;
|
|
}
|
|
registerInteraction(moduleCtx, where) {
|
|
return moduleCtx.eventsHub.on("layout:complete", () => this.updateA11yText(moduleCtx, where));
|
|
}
|
|
computeTextWrap(containerWidth, containerHeight) {
|
|
const { text, padding, wrapping } = this;
|
|
const maxWidth = Math.min(this.maxWidth ?? Infinity, containerWidth) - padding * 2;
|
|
const maxHeight = this.maxHeight ?? containerHeight - padding * 2;
|
|
const options = { maxWidth, maxHeight, font: this, textWrap: wrapping };
|
|
if (!Number.isFinite(maxWidth) && !Number.isFinite(maxHeight)) {
|
|
this.node.text = text;
|
|
return;
|
|
}
|
|
let wrappedText;
|
|
if ((0, import_ag_charts_core26.isArray)(text)) {
|
|
wrappedText = (0, import_ag_charts_core26.wrapTextSegments)(text, options);
|
|
this.truncated = wrappedText.some(import_ag_charts_core26.isSegmentTruncated);
|
|
} else {
|
|
wrappedText = (0, import_ag_charts_core26.wrapText)((0, import_ag_charts_core26.toTextString)(text), options);
|
|
this.truncated = (0, import_ag_charts_core26.isTextTruncated)(wrappedText);
|
|
}
|
|
this.node.text = wrappedText;
|
|
}
|
|
updateA11yText(moduleCtx, where) {
|
|
const { proxyInteractionService } = moduleCtx;
|
|
if (!this.enabled || !this.text) {
|
|
this.destroyProxyText();
|
|
return;
|
|
}
|
|
const bbox = Transformable.toCanvas(this.node);
|
|
if (!bbox)
|
|
return;
|
|
const { id: domManagerId } = this;
|
|
if (this.proxyText == null) {
|
|
this.proxyText = proxyInteractionService.createProxyElement({ type: "text", domManagerId, where });
|
|
this.proxyTextListeners = [
|
|
this.proxyText.addListener("mousemove", (ev) => this.handleMouseMove(moduleCtx, ev)),
|
|
this.proxyText.addListener("mouseleave", (ev) => this.handleMouseLeave(moduleCtx, ev))
|
|
];
|
|
}
|
|
const textContent = (0, import_ag_charts_core26.toPlainText)(this.text);
|
|
if (textContent !== this.lastProxyTextContent) {
|
|
this.proxyText.textContent = textContent;
|
|
this.lastProxyTextContent = textContent;
|
|
}
|
|
const { lastProxyBBox } = this;
|
|
if (lastProxyBBox == null || bbox.x !== lastProxyBBox.x || bbox.y !== lastProxyBBox.y || bbox.width !== lastProxyBBox.width || bbox.height !== lastProxyBBox.height) {
|
|
this.proxyText.setBounds(bbox);
|
|
this.lastProxyBBox = { x: bbox.x, y: bbox.y, width: bbox.width, height: bbox.height };
|
|
}
|
|
}
|
|
handleMouseMove(moduleCtx, event) {
|
|
if (event != null && this.enabled && this.truncated) {
|
|
const { x, y } = Transformable.toCanvas(this.node);
|
|
const canvasX = event.sourceEvent.offsetX + x;
|
|
const canvasY = event.sourceEvent.offsetY + y;
|
|
moduleCtx.tooltipManager.updateTooltip(this.id, { canvasX, canvasY, showArrow: false }, [
|
|
{ type: "structured", title: (0, import_ag_charts_core26.toPlainText)(this.text) }
|
|
]);
|
|
}
|
|
}
|
|
handleMouseLeave(moduleCtx, _event) {
|
|
moduleCtx.tooltipManager.removeTooltip(this.id, void 0, true);
|
|
}
|
|
destroy() {
|
|
this.destroyProxyText();
|
|
}
|
|
destroyProxyText() {
|
|
if (this.proxyText == null)
|
|
return;
|
|
for (const cleanup of this.proxyTextListeners ?? []) {
|
|
cleanup();
|
|
}
|
|
this.proxyTextListeners = void 0;
|
|
this.proxyText.destroy();
|
|
this.proxyText = void 0;
|
|
this.lastProxyTextContent = void 0;
|
|
this.lastProxyBBox = void 0;
|
|
}
|
|
};
|
|
Caption.className = "Caption";
|
|
Caption.SMALL_PADDING = 10;
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property,
|
|
(0, import_ag_charts_core26.ProxyPropertyOnWrite)("node", "visible")
|
|
], Caption.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property,
|
|
(0, import_ag_charts_core26.ProxyPropertyOnWrite)("node")
|
|
], Caption.prototype, "text", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property,
|
|
(0, import_ag_charts_core26.ProxyPropertyOnWrite)("node")
|
|
], Caption.prototype, "textAlign", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property,
|
|
(0, import_ag_charts_core26.ProxyPropertyOnWrite)("node")
|
|
], Caption.prototype, "fontStyle", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property,
|
|
(0, import_ag_charts_core26.ProxyPropertyOnWrite)("node")
|
|
], Caption.prototype, "fontWeight", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property,
|
|
(0, import_ag_charts_core26.ProxyPropertyOnWrite)("node")
|
|
], Caption.prototype, "fontSize", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property,
|
|
(0, import_ag_charts_core26.ProxyPropertyOnWrite)("node")
|
|
], Caption.prototype, "fontFamily", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property,
|
|
(0, import_ag_charts_core26.ProxyPropertyOnWrite)("node", "fill")
|
|
], Caption.prototype, "color", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property
|
|
], Caption.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property
|
|
], Caption.prototype, "maxWidth", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property
|
|
], Caption.prototype, "maxHeight", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property
|
|
], Caption.prototype, "wrapping", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property
|
|
], Caption.prototype, "padding", 2);
|
|
__decorateClass([
|
|
import_ag_charts_core26.Property
|
|
], Caption.prototype, "layoutStyle", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/marker/marker.ts
|
|
var import_ag_charts_core28 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/chart/marker/shapes.ts
|
|
var import_ag_charts_core27 = require("ag-charts-core");
|
|
function drawMarkerUnitPolygon(params, moves) {
|
|
const { path, size } = params;
|
|
const { x: x0, y: y0 } = params;
|
|
path.clear();
|
|
let didMove = false;
|
|
for (const [dx, dy] of moves) {
|
|
const x = x0 + (dx - 0.5) * size;
|
|
const y = y0 + (dy - 0.5) * size;
|
|
if (didMove) {
|
|
path.lineTo(x, y);
|
|
} else {
|
|
path.moveTo(x, y);
|
|
}
|
|
didMove = true;
|
|
}
|
|
path.closePath();
|
|
}
|
|
var MARKER_SHAPES = {
|
|
circle({ path, x, y, size }) {
|
|
const r = size / 2;
|
|
path.arc(x, y, r, 0, Math.PI * 2);
|
|
path.closePath();
|
|
},
|
|
cross(params) {
|
|
drawMarkerUnitPolygon(params, [
|
|
[0.25, 0],
|
|
[0.5, 0.25],
|
|
[0.75, 0],
|
|
[1, 0.25],
|
|
[0.75, 0.5],
|
|
[1, 0.75],
|
|
[0.75, 1],
|
|
[0.5, 0.75],
|
|
[0.25, 1],
|
|
[0, 0.75],
|
|
[0.25, 0.5],
|
|
[0, 0.25]
|
|
]);
|
|
},
|
|
diamond(params) {
|
|
drawMarkerUnitPolygon(params, [
|
|
[0.5, 0],
|
|
[1, 0.5],
|
|
[0.5, 1],
|
|
[0, 0.5]
|
|
]);
|
|
},
|
|
heart({ path, x, y, size }) {
|
|
const r = size / 4;
|
|
y = y + r / 2;
|
|
path.arc(x - r, y - r, r, (0, import_ag_charts_core27.toRadians)(130), (0, import_ag_charts_core27.toRadians)(330));
|
|
path.arc(x + r, y - r, r, (0, import_ag_charts_core27.toRadians)(220), (0, import_ag_charts_core27.toRadians)(50));
|
|
path.lineTo(x, y + r);
|
|
path.closePath();
|
|
},
|
|
pin({ path, x, y, size: s }) {
|
|
const cx = 0.5;
|
|
const cy = 0.5;
|
|
path.moveTo(x + (0.891 - cx) * s, y + (0.391 - cy) * s);
|
|
path.cubicCurveTo(
|
|
x + (0.891 - cx) * s,
|
|
y + (0.606 - cy) * s,
|
|
x + (0.5 - cx) * s,
|
|
y + (1 - cy) * s,
|
|
x + (0.5 - cx) * s,
|
|
y + (1 - cy) * s
|
|
);
|
|
path.cubicCurveTo(
|
|
x + (0.5 - cx) * s,
|
|
y + (1 - cy) * s,
|
|
x + (0.109 - cx) * s,
|
|
y + (0.606 - cy) * s,
|
|
x + (0.109 - cx) * s,
|
|
y + (0.391 - cy) * s
|
|
);
|
|
path.cubicCurveTo(
|
|
x + (0.109 - cx) * s,
|
|
y + (0.175 - cy) * s,
|
|
x + (0.284 - cx) * s,
|
|
y + (0 - cy) * s,
|
|
x + (0.5 - cx) * s,
|
|
y + (0 - cy) * s
|
|
);
|
|
path.cubicCurveTo(
|
|
x + (0.716 - cx) * s,
|
|
y + (0 - cy) * s,
|
|
x + (0.891 - cx) * s,
|
|
y + (0.175 - cy) * s,
|
|
x + (0.891 - cx) * s,
|
|
y + (0.391 - cy) * s
|
|
);
|
|
path.closePath();
|
|
},
|
|
plus(params) {
|
|
drawMarkerUnitPolygon(params, [
|
|
[1 / 3, 0],
|
|
[2 / 3, 0],
|
|
[2 / 3, 1 / 3],
|
|
[1, 1 / 3],
|
|
[1, 2 / 3],
|
|
[2 / 3, 2 / 3],
|
|
[2 / 3, 1],
|
|
[1 / 3, 1],
|
|
[1 / 3, 2 / 3],
|
|
[0, 2 / 3],
|
|
[0, 1 / 3],
|
|
[1 / 3, 1 / 3]
|
|
]);
|
|
},
|
|
square({ path, x, y, size, pixelRatio }) {
|
|
const hs = size / 2;
|
|
path.moveTo(align(pixelRatio, x - hs), align(pixelRatio, y - hs));
|
|
path.lineTo(align(pixelRatio, x + hs), align(pixelRatio, y - hs));
|
|
path.lineTo(align(pixelRatio, x + hs), align(pixelRatio, y + hs));
|
|
path.lineTo(align(pixelRatio, x - hs), align(pixelRatio, y + hs));
|
|
path.closePath();
|
|
},
|
|
star({ path, x, y, size }) {
|
|
const spikes = 5;
|
|
const outerRadius = size / 2;
|
|
const innerRadius = outerRadius / 2;
|
|
const rotation = Math.PI / 2;
|
|
for (let i = 0; i < spikes * 2; i++) {
|
|
const radius = i % 2 === 0 ? outerRadius : innerRadius;
|
|
const angle = i * Math.PI / spikes - rotation;
|
|
const xCoordinate = x + Math.cos(angle) * radius;
|
|
const yCoordinate = y + Math.sin(angle) * radius;
|
|
path.lineTo(xCoordinate, yCoordinate);
|
|
}
|
|
path.closePath();
|
|
},
|
|
triangle(params) {
|
|
drawMarkerUnitPolygon(params, [
|
|
[0.5, 0],
|
|
[1, 0.87],
|
|
[0, 0.87]
|
|
]);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/marker/marker.ts
|
|
var InternalMarker = class extends Path {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.shape = "square";
|
|
this.x = 0;
|
|
this.y = 0;
|
|
this.size = 12;
|
|
}
|
|
// optimised field accessor
|
|
isPointInPath(x, y) {
|
|
return this.distanceSquared(x, y) <= 0;
|
|
}
|
|
get midPoint() {
|
|
return { x: this.x, y: this.y };
|
|
}
|
|
distanceSquared(x, y) {
|
|
const anchor = Marker.anchor(this.shape);
|
|
const dx = x - this.x + (anchor.x - 0.5) * this.size;
|
|
const dy = y - this.y + (anchor.y - 0.5) * this.size;
|
|
const radius = this.size / 2;
|
|
return Math.max(dx * dx + dy * dy - radius * radius, 0);
|
|
}
|
|
updatePath() {
|
|
const { path, shape, x, y, size } = this;
|
|
const pixelRatio = this.layerManager?.canvas?.pixelRatio ?? 1;
|
|
const anchor = Marker.anchor(shape);
|
|
const drawParams = {
|
|
path,
|
|
x: x - (anchor.x - 0.5) * size,
|
|
y: y - (anchor.y - 0.5) * size,
|
|
size,
|
|
pixelRatio
|
|
};
|
|
path.clear();
|
|
if (typeof shape === "string") {
|
|
MARKER_SHAPES[shape](drawParams);
|
|
} else if (typeof shape === "function") {
|
|
shape(drawParams);
|
|
}
|
|
}
|
|
computeBBox() {
|
|
const { x, y, size } = this;
|
|
const anchor = Marker.anchor(this.shape);
|
|
return new BBox(x - size * anchor.x, y - size * anchor.y, size, size);
|
|
}
|
|
executeFill(ctx, path) {
|
|
if (!path)
|
|
return;
|
|
return super.executeFill(ctx, path);
|
|
}
|
|
executeStroke(ctx, path) {
|
|
if (!path)
|
|
return;
|
|
return super.executeStroke(ctx, path);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
(0, import_ag_charts_core28.DeclaredSceneObjectChangeDetection)({ equals: import_ag_charts_core28.TRIPLE_EQ })
|
|
], InternalMarker.prototype, "shape", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core28.DeclaredSceneChangeDetection)()
|
|
], InternalMarker.prototype, "x", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core28.DeclaredSceneChangeDetection)()
|
|
], InternalMarker.prototype, "y", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core28.DeclaredSceneChangeDetection)({ convertor: Math.abs })
|
|
], InternalMarker.prototype, "size", 2);
|
|
var Marker = class extends Rotatable(Scalable(Translatable(InternalMarker))) {
|
|
static anchor(shape) {
|
|
if (shape === "pin") {
|
|
return { x: 0.5, y: 1 };
|
|
} else if (typeof shape === "function" && "anchor" in shape) {
|
|
return shape.anchor;
|
|
}
|
|
return { x: 0.5, y: 0.5 };
|
|
}
|
|
constructor(options) {
|
|
super(options);
|
|
if (options?.shape != null) {
|
|
this.shape = options.shape;
|
|
}
|
|
}
|
|
/**
|
|
* Optimised reset for animation hot paths.
|
|
* Bypasses SceneChangeDetection decorators by writing directly to backing fields.
|
|
*
|
|
* This avoids per-property overhead from:
|
|
* - Equality checks (comparing old vs new values)
|
|
* - Change callbacks (triggering downstream updates)
|
|
* - Object.keys() iteration
|
|
*
|
|
* A single markDirty() call at the end ensures the scene graph is properly invalidated.
|
|
* WARNING: Only use for animation hot paths where performance is critical.
|
|
*/
|
|
resetAnimationProperties(x, y, size, opacity, scalingX, scalingY) {
|
|
this.__x = x;
|
|
this.__y = y;
|
|
this.__size = size;
|
|
this.__opacity = opacity;
|
|
this.resetScalingProperties(scalingX, scalingY, x, y);
|
|
this.dirtyPath = true;
|
|
this.markDirty();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scale/categoryScale.ts
|
|
var import_ag_charts_core30 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scale/bandScale.ts
|
|
var import_ag_charts_core29 = require("ag-charts-core");
|
|
var _BandScale = class _BandScale extends AbstractScale {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.invalid = true;
|
|
this.range = [0, 1];
|
|
this.round = false;
|
|
this._bandwidth = 1;
|
|
this._step = 1;
|
|
this._inset = 1;
|
|
this._rawBandwidth = 1;
|
|
/**
|
|
* The ratio of the range that is reserved for space between bands.
|
|
*/
|
|
this._paddingInner = 0;
|
|
/**
|
|
* The ratio of the range that is reserved for space before the first
|
|
* and after the last band.
|
|
*/
|
|
this._paddingOuter = 0;
|
|
}
|
|
static is(value) {
|
|
return value instanceof _BandScale;
|
|
}
|
|
get bandwidth() {
|
|
this.refresh();
|
|
return this._bandwidth;
|
|
}
|
|
get step() {
|
|
this.refresh();
|
|
return this._step;
|
|
}
|
|
get inset() {
|
|
this.refresh();
|
|
return this._inset;
|
|
}
|
|
get rawBandwidth() {
|
|
this.refresh();
|
|
return this._rawBandwidth;
|
|
}
|
|
set padding(value) {
|
|
value = (0, import_ag_charts_core29.clamp)(0, value, 1);
|
|
this._paddingInner = value;
|
|
this._paddingOuter = value;
|
|
}
|
|
get padding() {
|
|
return this._paddingInner;
|
|
}
|
|
set paddingInner(value) {
|
|
this.invalid = true;
|
|
this._paddingInner = (0, import_ag_charts_core29.clamp)(0, value, 1);
|
|
}
|
|
get paddingInner() {
|
|
return this._paddingInner;
|
|
}
|
|
set paddingOuter(value) {
|
|
this.invalid = true;
|
|
this._paddingOuter = (0, import_ag_charts_core29.clamp)(0, value, 1);
|
|
}
|
|
get paddingOuter() {
|
|
return this._paddingOuter;
|
|
}
|
|
/** Override in subclass to provide band count without triggering full band materialization */
|
|
getBandCountForUpdate() {
|
|
return this.bands.length;
|
|
}
|
|
refresh() {
|
|
if (!this.invalid)
|
|
return;
|
|
this.invalid = false;
|
|
this.update();
|
|
if (this.invalid) {
|
|
import_ag_charts_core29.Logger.warnOnce("Expected update to not invalidate scale");
|
|
}
|
|
}
|
|
convert(d, options) {
|
|
this.refresh();
|
|
const i = this.findIndex(d, options?.alignment);
|
|
if (i == null || i < 0 || i >= this.getBandCountForUpdate()) {
|
|
return Number.NaN;
|
|
}
|
|
return this.ordinalRange(i);
|
|
}
|
|
getDomainMinMax() {
|
|
return unpackDomainMinMax(this.domain);
|
|
}
|
|
invertNearestIndex(position) {
|
|
this.refresh();
|
|
const bandCount = this.getBandCountForUpdate();
|
|
if (bandCount === 0)
|
|
return -1;
|
|
let low = 0;
|
|
let high = bandCount - 1;
|
|
let closestDistance = Infinity;
|
|
let closestIndex = 0;
|
|
while (low <= high) {
|
|
const mid = Math.trunc((high + low) / 2);
|
|
const p = this.ordinalRange(mid);
|
|
const distance = Math.abs(p - position);
|
|
if (distance === 0)
|
|
return mid;
|
|
if (distance < closestDistance) {
|
|
closestDistance = distance;
|
|
closestIndex = mid;
|
|
}
|
|
if (p < position) {
|
|
low = mid + 1;
|
|
} else {
|
|
high = mid - 1;
|
|
}
|
|
}
|
|
return closestIndex;
|
|
}
|
|
update() {
|
|
const [r0, r1] = this.range;
|
|
let { _paddingInner: paddingInner } = this;
|
|
const { _paddingOuter: paddingOuter } = this;
|
|
const bandCount = this.getBandCountForUpdate();
|
|
if (bandCount === 0)
|
|
return;
|
|
const rangeDistance = r1 - r0;
|
|
let rawStep;
|
|
if (bandCount === 1) {
|
|
paddingInner = 0;
|
|
rawStep = rangeDistance * (1 - paddingOuter * 2);
|
|
} else {
|
|
rawStep = rangeDistance / Math.max(1, bandCount - paddingInner + paddingOuter * 2);
|
|
}
|
|
const round = this.round && Math.floor(rawStep) > 0;
|
|
const step = round ? Math.floor(rawStep) : rawStep;
|
|
let inset = r0 + (rangeDistance - step * (bandCount - paddingInner)) / 2;
|
|
let bandwidth = step * (1 - paddingInner);
|
|
if (round) {
|
|
inset = Math.round(inset);
|
|
bandwidth = Math.round(bandwidth);
|
|
}
|
|
this._step = step;
|
|
this._inset = inset;
|
|
this._bandwidth = bandwidth;
|
|
this._rawBandwidth = rawStep * (1 - paddingInner);
|
|
}
|
|
ordinalRange(i) {
|
|
const { _inset: inset, _step: step, range: range2 } = this;
|
|
const min = Math.min(range2[0], range2[1]);
|
|
const max = Math.max(range2[0], range2[1]);
|
|
return (0, import_ag_charts_core29.clamp)(min, inset + step * i, max);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
Invalidating
|
|
], _BandScale.prototype, "range", 2);
|
|
__decorateClass([
|
|
Invalidating
|
|
], _BandScale.prototype, "round", 2);
|
|
var BandScale = _BandScale;
|
|
|
|
// packages/ag-charts-community/src/scale/categoryScale.ts
|
|
var CategoryScale = class _CategoryScale extends BandScale {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "category";
|
|
this.defaultTickCount = 0;
|
|
/**
|
|
* Maps datum to its index in the {@link domain} array.
|
|
* Used to check for duplicate data (not allowed).
|
|
*/
|
|
this.index = /* @__PURE__ */ new Map();
|
|
this.indexInitialized = false;
|
|
/**
|
|
* Contains unique data only.
|
|
*/
|
|
this._domain = [];
|
|
}
|
|
static is(value) {
|
|
return value instanceof _CategoryScale;
|
|
}
|
|
set domain(values) {
|
|
if (this._domain === values)
|
|
return;
|
|
this.invalid = true;
|
|
this._domain = values;
|
|
this.index.clear();
|
|
this.indexInitialized = false;
|
|
}
|
|
get domain() {
|
|
return this._domain;
|
|
}
|
|
get bands() {
|
|
return this._domain;
|
|
}
|
|
normalizeDomains(...domains) {
|
|
let normalizedDomain = void 0;
|
|
const seenDomains = /* @__PURE__ */ new Set();
|
|
let animatable = true;
|
|
for (const input of domains) {
|
|
const domain = input.domain;
|
|
if (seenDomains.has(domain))
|
|
continue;
|
|
seenDomains.add(domain);
|
|
if (normalizedDomain == null) {
|
|
normalizedDomain = deduplicateCategories(domain);
|
|
} else {
|
|
animatable && (animatable = domainOrderedToNormalizedDomain(domain, normalizedDomain));
|
|
normalizedDomain = deduplicateCategories([...normalizedDomain, ...domain]);
|
|
}
|
|
}
|
|
normalizedDomain ?? (normalizedDomain = []);
|
|
return { domain: normalizedDomain, animatable };
|
|
}
|
|
toDomain(_value) {
|
|
return void 0;
|
|
}
|
|
invert(position, nearest = false) {
|
|
this.refresh();
|
|
const offset = nearest ? this.bandwidth / 2 : 0;
|
|
const index = this.invertNearestIndex(Math.max(0, position - offset));
|
|
const matches = nearest || position === this.ordinalRange(index);
|
|
return matches ? this.domain[index] : void 0;
|
|
}
|
|
ticks(params, domain = this.domain, visibleRange) {
|
|
const { bands } = this;
|
|
let { tickCount } = params;
|
|
if (tickCount === 0) {
|
|
const firstTickIndex2 = bands.length > 1 ? 1 : 0;
|
|
const ticks2 = bands[firstTickIndex2] ? [bands[firstTickIndex2]] : [];
|
|
return { ticks: ticks2, count: void 0, firstTickIndex: firstTickIndex2 };
|
|
}
|
|
let step = tickCount != null && tickCount !== 0 ? Math.trunc(bands.length / tickCount) : 1;
|
|
step = (0, import_ag_charts_core30.previousPowerOf2)(step);
|
|
if (step <= 1) {
|
|
return filterVisibleTicks(domain, false, visibleRange);
|
|
}
|
|
tickCount = Math.trunc(bands.length / step);
|
|
const span = step * tickCount;
|
|
const inset = (0, import_ag_charts_core30.previousPowerOf2)(Math.trunc((bands.length - span) / 2));
|
|
const vt0 = (0, import_ag_charts_core30.clamp)(0, Math.floor((visibleRange?.[0] ?? 0) * bands.length), bands.length);
|
|
const vt1 = (0, import_ag_charts_core30.clamp)(0, Math.ceil((visibleRange?.[1] ?? 1) * bands.length), bands.length);
|
|
const i0 = Math.floor((vt0 - inset) / step) * step + inset;
|
|
const i1 = Math.ceil((vt1 - inset) / step) * step + inset;
|
|
const ticks = [];
|
|
for (let i = i0; i < i1; i += step) {
|
|
if (i >= 0 && i < bands.length) {
|
|
ticks.push(bands[i]);
|
|
}
|
|
}
|
|
let firstTickIndex = ticks.length > 0 ? this.findIndex(ticks[0]) : void 0;
|
|
if (firstTickIndex != null) {
|
|
firstTickIndex = Math.floor((firstTickIndex - inset) / step);
|
|
}
|
|
return { ticks, count: void 0, firstTickIndex };
|
|
}
|
|
findIndex(value) {
|
|
const { index, indexInitialized } = this;
|
|
if (!indexInitialized) {
|
|
const { domain } = this;
|
|
for (let i = 0; i < domain.length; i++) {
|
|
index.set((0, import_ag_charts_core30.dateToNumber)(domain[i]), i);
|
|
}
|
|
this.indexInitialized = true;
|
|
}
|
|
return index.get((0, import_ag_charts_core30.dateToNumber)(value));
|
|
}
|
|
};
|
|
function deduplicateCategories(d) {
|
|
let domain;
|
|
const uniqueValues = /* @__PURE__ */ new Set();
|
|
for (const value of d) {
|
|
const key = (0, import_ag_charts_core30.dateToNumber)(value);
|
|
const lastSize = uniqueValues.size;
|
|
uniqueValues.add(key);
|
|
const isUniqueValue = uniqueValues.size !== lastSize;
|
|
if (isUniqueValue) {
|
|
domain?.push(value);
|
|
} else {
|
|
domain ?? (domain = d.slice(0, uniqueValues.size));
|
|
}
|
|
}
|
|
return domain ?? d;
|
|
}
|
|
function domainOrderedToNormalizedDomain(domain, normalizedDomain) {
|
|
let normalizedIndex = -1;
|
|
for (const value of domain) {
|
|
const normalizedNextIndex = normalizedDomain.indexOf(value);
|
|
if (normalizedNextIndex === -1) {
|
|
normalizedIndex = Infinity;
|
|
} else if (normalizedNextIndex <= normalizedIndex) {
|
|
return false;
|
|
} else {
|
|
normalizedIndex = normalizedNextIndex;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scale/linearScale.ts
|
|
var import_ag_charts_core32 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scale/continuousScale.ts
|
|
var import_ag_charts_core31 = require("ag-charts-core");
|
|
var _ContinuousScale = class _ContinuousScale extends AbstractScale {
|
|
constructor(domain = [], range2 = []) {
|
|
super();
|
|
this.range = range2;
|
|
this.defaultTickCount = _ContinuousScale.defaultTickCount;
|
|
this.defaultClamp = false;
|
|
// Domain caching to avoid repeated valueOf() calls in hot paths
|
|
this._domain = [];
|
|
this.domainNeedsValueOf = true;
|
|
// Safe default
|
|
this.d0Cache = Number.NaN;
|
|
this.d1Cache = Number.NaN;
|
|
this.domain = domain;
|
|
}
|
|
static is(value) {
|
|
return value instanceof _ContinuousScale;
|
|
}
|
|
get domain() {
|
|
return this._domain;
|
|
}
|
|
set domain(values) {
|
|
this._domain = values;
|
|
if (values && values.length >= 2) {
|
|
const sample = values[0];
|
|
this.domainNeedsValueOf = sample != null && typeof sample === "object";
|
|
if (this.domainNeedsValueOf) {
|
|
this.d0Cache = values[0].valueOf();
|
|
this.d1Cache = values[1].valueOf();
|
|
} else {
|
|
this.d0Cache = values[0];
|
|
this.d1Cache = values[1];
|
|
}
|
|
} else {
|
|
this.d0Cache = Number.NaN;
|
|
this.d1Cache = Number.NaN;
|
|
}
|
|
}
|
|
normalizeDomains(...domains) {
|
|
return normalizeContinuousDomains(...domains);
|
|
}
|
|
calcBandwidth(smallestInterval = 1, minWidth = 1) {
|
|
const { domain } = this;
|
|
const rangeDistance = this.getPixelRange();
|
|
if (domain.length === 0)
|
|
return rangeDistance;
|
|
const intervals = Math.abs(this.d1Cache - this.d0Cache) / smallestInterval + 1;
|
|
let bands = intervals;
|
|
if (minWidth !== 0) {
|
|
const maxBands = Math.floor(rangeDistance);
|
|
bands = Math.min(bands, maxBands);
|
|
}
|
|
return rangeDistance / Math.max(1, bands);
|
|
}
|
|
convert(value, options) {
|
|
const { domain } = this;
|
|
if (!domain || domain.length < 2 || value == null) {
|
|
return Number.NaN;
|
|
}
|
|
const { range: range2 } = this;
|
|
const clamp8 = options?.clamp ?? this.defaultClamp;
|
|
let d0 = this.d0Cache;
|
|
let d1 = this.d1Cache;
|
|
let x = typeof value === "number" ? value : value.valueOf();
|
|
if (this.transform) {
|
|
d0 = this.transform(d0);
|
|
d1 = this.transform(d1);
|
|
x = this.transform(x);
|
|
}
|
|
if (clamp8) {
|
|
const [start, stop] = (0, import_ag_charts_core31.findMinMax)([d0, d1]);
|
|
if (x < start) {
|
|
return range2[0];
|
|
} else if (x > stop) {
|
|
return range2[1];
|
|
}
|
|
}
|
|
if (d0 === d1) {
|
|
return (range2[0] + range2[1]) / 2;
|
|
} else if (x === d0) {
|
|
return range2[0];
|
|
} else if (x === d1) {
|
|
return range2[1];
|
|
}
|
|
const r0 = range2[0];
|
|
return r0 + (x - d0) / (d1 - d0) * (range2[1] - r0);
|
|
}
|
|
invert(x, _nearest) {
|
|
const { domain } = this;
|
|
if (domain.length < 2)
|
|
return;
|
|
let d0 = this.d0Cache;
|
|
let d1 = this.d1Cache;
|
|
if (this.transform) {
|
|
d0 = this.transform(d0);
|
|
d1 = this.transform(d1);
|
|
}
|
|
const { range: range2 } = this;
|
|
const [r0, r1] = range2;
|
|
let d;
|
|
if (r0 === r1) {
|
|
d = this.toDomain((d0 + d1) / 2);
|
|
} else {
|
|
d = this.toDomain(d0 + (x - r0) / (r1 - r0) * (d1 - d0));
|
|
}
|
|
return this.transformInvert ? this.transformInvert(d) : d;
|
|
}
|
|
getDomainMinMax() {
|
|
return unpackDomainMinMax(this.domain);
|
|
}
|
|
getPixelRange() {
|
|
const [a, b] = this.range;
|
|
return Math.abs(b - a);
|
|
}
|
|
};
|
|
_ContinuousScale.defaultTickCount = 5;
|
|
var ContinuousScale = _ContinuousScale;
|
|
function normalizeContinuousDomains(...domains) {
|
|
let min;
|
|
let minValue = Infinity;
|
|
let max;
|
|
let maxValue = -Infinity;
|
|
for (const input of domains) {
|
|
const domain = input.domain;
|
|
for (const d of domain) {
|
|
const value = d.valueOf();
|
|
if (value < minValue) {
|
|
minValue = value;
|
|
min = d;
|
|
}
|
|
if (value > maxValue) {
|
|
maxValue = value;
|
|
max = d;
|
|
}
|
|
}
|
|
}
|
|
if (min != null && max != null) {
|
|
const domain = [min, max];
|
|
return { domain, animatable: true };
|
|
} else {
|
|
return { domain: [], animatable: false };
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scale/linearScale.ts
|
|
var LinearScale = class _LinearScale extends ContinuousScale {
|
|
constructor() {
|
|
super([0, 1], [0, 1]);
|
|
this.type = "number";
|
|
}
|
|
static is(value) {
|
|
return value instanceof _LinearScale;
|
|
}
|
|
static getTickStep(start, stop, ticks) {
|
|
const { interval, tickCount = ContinuousScale.defaultTickCount, minTickCount, maxTickCount } = ticks;
|
|
return interval ?? (0, import_ag_charts_core32.tickStep)(start, stop, tickCount, minTickCount, maxTickCount);
|
|
}
|
|
toDomain(d) {
|
|
return d;
|
|
}
|
|
ticks({ interval, tickCount = ContinuousScale.defaultTickCount, minTickCount, maxTickCount }, domain = this.domain, visibleRange) {
|
|
if (!domain || domain.length < 2 || tickCount < 1 || !domain.every(Number.isFinite)) {
|
|
return { ticks: [], count: 0, firstTickIndex: 0 };
|
|
}
|
|
const [d0, d1] = domain;
|
|
if (interval) {
|
|
const step = Math.abs(interval);
|
|
if (!(0, import_ag_charts_core32.isDenseInterval)((d1 - d0) / step, this.getPixelRange())) {
|
|
return (0, import_ag_charts_core32.range)(d0, d1, step, visibleRange);
|
|
}
|
|
}
|
|
return (0, import_ag_charts_core32.createTicks)(d0, d1, tickCount, minTickCount, maxTickCount, visibleRange);
|
|
}
|
|
niceDomain(ticks, domain = this.domain) {
|
|
if (domain.length < 2)
|
|
return [];
|
|
const { tickCount = ContinuousScale.defaultTickCount } = ticks;
|
|
let [start, stop] = domain;
|
|
if (tickCount === 1) {
|
|
[start, stop] = (0, import_ag_charts_core32.niceTicksDomain)(start, stop);
|
|
} else if (tickCount > 1) {
|
|
const roundStart = start > stop ? Math.ceil : Math.floor;
|
|
const roundStop = start > stop ? Math.floor : Math.ceil;
|
|
const maxAttempts = 4;
|
|
for (let i = 0; i < maxAttempts; i++) {
|
|
const prev0 = start;
|
|
const prev1 = stop;
|
|
const step = _LinearScale.getTickStep(start, stop, ticks);
|
|
const [d0, d1] = domain;
|
|
start = roundStart(d0 / step) * step;
|
|
stop = roundStop(d1 / step) * step;
|
|
if (start === prev0 && stop === prev1)
|
|
break;
|
|
}
|
|
}
|
|
return [ticks.nice[0] ? start : domain[0], ticks.nice[1] ? stop : domain[1]];
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/scene.ts
|
|
var import_ag_charts_core36 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scene/canvas/hdpiCanvas.ts
|
|
var import_ag_charts_core33 = require("ag-charts-core");
|
|
var HdpiCanvas = class {
|
|
constructor(options) {
|
|
this.enabled = true;
|
|
this.width = 600;
|
|
this.height = 300;
|
|
const { width, height, canvasElement, willReadFrequently = false } = options;
|
|
this.pixelRatio = options.pixelRatio ?? (0, import_ag_charts_core33.getWindow)("devicePixelRatio") ?? 1;
|
|
this.element = canvasElement ?? (0, import_ag_charts_core33.createElement)("canvas");
|
|
this.element.style.display = "block";
|
|
this.element.style.width = (width ?? this.width) + "px";
|
|
this.element.style.height = (height ?? this.height) + "px";
|
|
this.element.width = Math.round((width ?? this.width) * this.pixelRatio);
|
|
this.element.height = Math.round((height ?? this.height) * this.pixelRatio);
|
|
this.context = this.element.getContext("2d", { willReadFrequently });
|
|
this.onEnabledChange();
|
|
this.resize(width ?? 0, height ?? 0, this.pixelRatio);
|
|
debugContext(this.context);
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents -- OffscreenCanvasRenderingContext2D is intentionally `any` for Angular 13+ compatibility (AG-6969)
|
|
drawImage(context, dx = 0, dy = 0) {
|
|
return context.drawImage(this.context.canvas, dx, dy);
|
|
}
|
|
toDataURL(type) {
|
|
return this.element.toDataURL(type);
|
|
}
|
|
resize(width, height, pixelRatio) {
|
|
if (!(width > 0 && height > 0))
|
|
return;
|
|
const { element, context } = this;
|
|
element.width = Math.round(width * pixelRatio);
|
|
element.height = Math.round(height * pixelRatio);
|
|
context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
|
element.style.width = width + "px";
|
|
element.style.height = height + "px";
|
|
this.width = width;
|
|
this.height = height;
|
|
this.pixelRatio = pixelRatio;
|
|
}
|
|
clear() {
|
|
clearContext(this);
|
|
}
|
|
destroy() {
|
|
this.element.remove();
|
|
this.element.width = 0;
|
|
this.element.height = 0;
|
|
this.context.clearRect(0, 0, 0, 0);
|
|
Object.freeze(this);
|
|
}
|
|
reset() {
|
|
this.context.reset();
|
|
this.context.verifyDepthZero?.();
|
|
}
|
|
onEnabledChange() {
|
|
if (this.element) {
|
|
this.element.style.display = this.enabled ? "" : "none";
|
|
}
|
|
}
|
|
};
|
|
__decorateClass([
|
|
(0, import_ag_charts_core33.ObserveChanges)((target) => target.onEnabledChange())
|
|
], HdpiCanvas.prototype, "enabled", 2);
|
|
|
|
// packages/ag-charts-community/src/scene/image/imageLoader.ts
|
|
var import_ag_charts_core34 = require("ag-charts-core");
|
|
var ImageLoader = class extends import_ag_charts_core34.EventEmitter {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.cache = /* @__PURE__ */ new Map();
|
|
this.imageLoadingCount = 0;
|
|
}
|
|
loadImage(uri, affectedNode) {
|
|
const entry = this.cache.get(uri);
|
|
if (entry?.image) {
|
|
return entry.image;
|
|
} else if (entry != null && affectedNode) {
|
|
entry.nodes.add(affectedNode);
|
|
return;
|
|
}
|
|
if (!affectedNode) {
|
|
return;
|
|
}
|
|
const nextEntry = { image: void 0, nodes: /* @__PURE__ */ new Set([affectedNode]) };
|
|
const ImageCtor = (0, import_ag_charts_core34.getImage)();
|
|
const image = new ImageCtor();
|
|
this.imageLoadingCount++;
|
|
image.onload = () => {
|
|
nextEntry.image = image;
|
|
for (const node of nextEntry.nodes) {
|
|
node.markDirty();
|
|
}
|
|
nextEntry.nodes.clear();
|
|
this.imageLoadingCount--;
|
|
this.emit("image-loaded", { uri });
|
|
};
|
|
image.onerror = () => {
|
|
this.imageLoadingCount--;
|
|
nextEntry.nodes.clear();
|
|
this.emit("image-error", { uri });
|
|
};
|
|
image.src = uri;
|
|
this.cache.set(uri, nextEntry);
|
|
return nextEntry.image;
|
|
}
|
|
waitingToLoad() {
|
|
return this.imageLoadingCount > 0;
|
|
}
|
|
destroy() {
|
|
for (const entry of this.cache.values()) {
|
|
entry.nodes.clear();
|
|
}
|
|
this.cache.clear();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/layersManager.ts
|
|
var import_ag_charts_core35 = require("ag-charts-core");
|
|
var LayersManager = class {
|
|
constructor(canvas) {
|
|
this.canvas = canvas;
|
|
this.debug = import_ag_charts_core35.Debug.create(true, "scene");
|
|
this.layersMap = /* @__PURE__ */ new Map();
|
|
this.nextLayerId = 0;
|
|
}
|
|
get size() {
|
|
return this.layersMap.size;
|
|
}
|
|
resize(width, height, pixelRatio) {
|
|
this.canvas.resize(width, height, pixelRatio);
|
|
for (const { canvas } of this.layersMap.values()) {
|
|
canvas.resize(width, height, pixelRatio);
|
|
}
|
|
}
|
|
addLayer(opts) {
|
|
const { width, height, pixelRatio } = this.canvas;
|
|
const { name } = opts;
|
|
const canvas = new HdpiOffscreenCanvas({ width, height, pixelRatio });
|
|
this.layersMap.set(canvas, {
|
|
id: this.nextLayerId++,
|
|
name,
|
|
canvas
|
|
});
|
|
this.debug("Scene.addLayer() - layers", this.layersMap);
|
|
return canvas;
|
|
}
|
|
removeLayer(canvas) {
|
|
if (this.layersMap.has(canvas)) {
|
|
this.layersMap.delete(canvas);
|
|
canvas.destroy();
|
|
this.debug("Scene.removeLayer() - layers", this.layersMap);
|
|
}
|
|
}
|
|
clear() {
|
|
for (const layer of this.layersMap.values()) {
|
|
layer.canvas.destroy();
|
|
}
|
|
this.layersMap.clear();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/scene.ts
|
|
var Scene = class extends import_ag_charts_core36.EventEmitter {
|
|
constructor(canvasOptions) {
|
|
super();
|
|
this.debug = import_ag_charts_core36.Debug.create(true, "scene" /* SCENE */);
|
|
this.id = (0, import_ag_charts_core36.createId)(this);
|
|
this.imageLoader = new ImageLoader();
|
|
this.root = null;
|
|
this.pendingSize = null;
|
|
this.isDirty = false;
|
|
this.cleanup = new import_ag_charts_core36.CleanupRegistry();
|
|
this.updateDebugFlags();
|
|
this.canvas = new HdpiCanvas(canvasOptions);
|
|
this.layersManager = new LayersManager(this.canvas);
|
|
this.cleanup.register(
|
|
this.imageLoader.on("image-loaded", () => {
|
|
this.emit("scene-changed", {});
|
|
}),
|
|
this.imageLoader.on("image-error", ({ uri }) => {
|
|
import_ag_charts_core36.Logger.warnOnce(`Unable to load image ${uri}`);
|
|
})
|
|
);
|
|
}
|
|
waitingForUpdate() {
|
|
return this.imageLoader?.waitingToLoad() ?? false;
|
|
}
|
|
get width() {
|
|
return this.pendingSize?.[0] ?? this.canvas.width;
|
|
}
|
|
get height() {
|
|
return this.pendingSize?.[1] ?? this.canvas.height;
|
|
}
|
|
get pixelRatio() {
|
|
return this.pendingSize?.[2] ?? this.canvas.pixelRatio;
|
|
}
|
|
/**
|
|
* @deprecated v10.2.0 Only used by AG Grid Sparklines + Mini Charts
|
|
*
|
|
* DO NOT REMOVE WITHOUT FIXING THE GRID DEPENDENCIES.
|
|
*/
|
|
setContainer(value) {
|
|
const { element } = this.canvas;
|
|
element.remove();
|
|
value.appendChild(element);
|
|
return this;
|
|
}
|
|
setRoot(node) {
|
|
if (this.root === node) {
|
|
return this;
|
|
}
|
|
this.isDirty = true;
|
|
this.root?.setScene();
|
|
this.root = node;
|
|
if (node) {
|
|
node.visible = true;
|
|
node.setScene(this);
|
|
}
|
|
return this;
|
|
}
|
|
updateDebugFlags() {
|
|
import_ag_charts_core36.Debug.inDevelopmentMode(() => Node._debugEnabled = true);
|
|
}
|
|
clearCanvas() {
|
|
this.canvas.clear();
|
|
}
|
|
attachNode(node) {
|
|
this.appendChild(node);
|
|
return () => node.remove();
|
|
}
|
|
appendChild(node) {
|
|
this.root?.appendChild(node);
|
|
return this;
|
|
}
|
|
removeChild(node) {
|
|
node.remove();
|
|
return this;
|
|
}
|
|
download(fileName, fileFormat) {
|
|
(0, import_ag_charts_core36.downloadUrl)(this.canvas.toDataURL(fileFormat), fileName?.trim() ?? "image");
|
|
}
|
|
/** NOTE: Integrated Charts undocumented image download method. */
|
|
getDataURL(fileFormat) {
|
|
return this.canvas.toDataURL(fileFormat);
|
|
}
|
|
resize(width, height, pixelRatio) {
|
|
width = Math.round(width);
|
|
height = Math.round(height);
|
|
pixelRatio ?? (pixelRatio = this.pixelRatio);
|
|
if (width > 0 && height > 0 && (width !== this.width || height !== this.height || pixelRatio !== this.pixelRatio)) {
|
|
this.pendingSize = [width, height, pixelRatio];
|
|
this.isDirty = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
applyPendingResize() {
|
|
if (this.pendingSize) {
|
|
this.layersManager.resize(...this.pendingSize);
|
|
this.pendingSize = null;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
render(opts) {
|
|
const { debugSplitTimes = { start: performance.now() }, extraDebugStats, seriesRect, debugColors } = opts ?? {};
|
|
const { canvas, canvas: { context: ctx } = {}, root, width, height, pixelRatio: devicePixelRatio } = this;
|
|
if (!ctx) {
|
|
return;
|
|
}
|
|
const statsEnabled = import_ag_charts_core36.Debug.check("scene:stats" /* SCENE_STATS */, "scene:stats:verbose" /* SCENE_STATS_VERBOSE */);
|
|
if (statsEnabled) {
|
|
this.ensureDebugStatsRegistration();
|
|
}
|
|
const renderStartTime = performance.now();
|
|
const resized = this.applyPendingResize();
|
|
if (root && !root.visible) {
|
|
this.isDirty = false;
|
|
return;
|
|
}
|
|
let rootDirty;
|
|
if (root instanceof Group) {
|
|
rootDirty = root.dirty;
|
|
}
|
|
if (root != null && rootDirty === false && !this.isDirty) {
|
|
if (this.debug.check()) {
|
|
this.debug("Scene.render() - no-op", {
|
|
tree: buildTree(root, "console")
|
|
});
|
|
}
|
|
if (statsEnabled) {
|
|
debugStats(
|
|
this.layersManager,
|
|
debugSplitTimes,
|
|
ctx,
|
|
void 0,
|
|
extraDebugStats,
|
|
seriesRect,
|
|
debugColors
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
const renderCtx = {
|
|
ctx,
|
|
width,
|
|
height,
|
|
devicePixelRatio,
|
|
debugNodes: {}
|
|
};
|
|
if (import_ag_charts_core36.Debug.check("scene:stats:verbose" /* SCENE_STATS_VERBOSE */)) {
|
|
renderCtx.stats = {
|
|
layersRendered: 0,
|
|
layersSkipped: 0,
|
|
nodesRendered: 0,
|
|
nodesSkipped: 0,
|
|
opsPerformed: 0,
|
|
opsSkipped: 0
|
|
};
|
|
}
|
|
prepareSceneNodeHighlight(renderCtx);
|
|
let canvasCleared = false;
|
|
if (rootDirty !== false || resized) {
|
|
canvasCleared = true;
|
|
canvas.clear();
|
|
}
|
|
if (root && import_ag_charts_core36.Debug.check("scene:dirtyTree" /* SCENE_DIRTY_TREE */)) {
|
|
const { dirtyTree, paths } = buildDirtyTree(root);
|
|
import_ag_charts_core36.Debug.create("scene:dirtyTree" /* SCENE_DIRTY_TREE */)("Scene.render() - dirtyTree", { dirtyTree, paths });
|
|
}
|
|
if (root && canvasCleared) {
|
|
if (root.visible) {
|
|
root.preRender(renderCtx);
|
|
}
|
|
if (this.debug.check()) {
|
|
const tree = buildTree(root, "console");
|
|
this.debug("Scene.render() - before", {
|
|
canvasCleared,
|
|
tree
|
|
});
|
|
}
|
|
if (root.visible) {
|
|
try {
|
|
ctx.save();
|
|
root.render(renderCtx);
|
|
ctx.restore();
|
|
} catch (e) {
|
|
this.canvas.reset();
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
debugSplitTimes["\u270D\uFE0F"] = performance.now() - renderStartTime;
|
|
ctx.verifyDepthZero?.();
|
|
this.isDirty = false;
|
|
if (statsEnabled) {
|
|
debugStats(
|
|
this.layersManager,
|
|
debugSplitTimes,
|
|
ctx,
|
|
renderCtx.stats,
|
|
extraDebugStats,
|
|
seriesRect,
|
|
debugColors
|
|
);
|
|
}
|
|
debugSceneNodeHighlight(ctx, renderCtx.debugNodes);
|
|
if (root && this.debug.check()) {
|
|
this.debug("Scene.render() - after", {
|
|
tree: buildTree(root, "console"),
|
|
canvasCleared
|
|
});
|
|
}
|
|
}
|
|
ensureDebugStatsRegistration() {
|
|
if (this.releaseDebugStats)
|
|
return;
|
|
const release = registerDebugStatsConsumer();
|
|
const cleanup = () => {
|
|
release();
|
|
this.releaseDebugStats = void 0;
|
|
};
|
|
this.releaseDebugStats = cleanup;
|
|
this.cleanup.register(cleanup);
|
|
}
|
|
toSVG() {
|
|
const { root, width, height } = this;
|
|
if (root == null)
|
|
return;
|
|
return Node.toSVG(root, width, height);
|
|
}
|
|
/** Alternative to destroy() that preserves re-usable resources. */
|
|
strip() {
|
|
const { context, pixelRatio } = this.canvas;
|
|
context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
|
this.layersManager.clear();
|
|
this.setRoot(null);
|
|
this.isDirty = false;
|
|
this.clear();
|
|
}
|
|
destroy() {
|
|
this.strip();
|
|
this.canvas.destroy();
|
|
this.imageLoader.destroy();
|
|
this.cleanup.flush();
|
|
cleanupDebugStats();
|
|
Object.assign(this, { canvas: void 0 });
|
|
}
|
|
};
|
|
Scene.className = "Scene";
|
|
|
|
// packages/ag-charts-community/src/scene/shape/arc.ts
|
|
var import_ag_charts_core37 = require("ag-charts-core");
|
|
var Arc = class extends Path {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.centerX = 0;
|
|
this.centerY = 0;
|
|
this.radius = 10;
|
|
this.startAngle = 0;
|
|
this.endAngle = Math.PI * 2;
|
|
this.counterClockwise = false;
|
|
this.type = 0 /* Open */;
|
|
}
|
|
get fullPie() {
|
|
return (0, import_ag_charts_core37.isNumberEqual)((0, import_ag_charts_core37.normalizeAngle360)(this.startAngle), (0, import_ag_charts_core37.normalizeAngle360)(this.endAngle));
|
|
}
|
|
updatePath() {
|
|
const path = this.path;
|
|
path.clear();
|
|
path.arc(this.centerX, this.centerY, this.radius, this.startAngle, this.endAngle, this.counterClockwise);
|
|
if (this.type === 1 /* Chord */) {
|
|
path.closePath();
|
|
} else if (this.type === 2 /* Round */ && !this.fullPie) {
|
|
path.lineTo(this.centerX, this.centerY);
|
|
path.closePath();
|
|
}
|
|
}
|
|
computeBBox() {
|
|
return new BBox(this.centerX - this.radius, this.centerY - this.radius, this.radius * 2, this.radius * 2);
|
|
}
|
|
isPointInPath(x, y) {
|
|
const bbox = this.getBBox();
|
|
return this.type !== 0 /* Open */ && bbox.containsPoint(x, y) && this.path.isPointInPath(x, y);
|
|
}
|
|
};
|
|
Arc.className = "Arc";
|
|
__decorateClass([
|
|
(0, import_ag_charts_core37.SceneChangeDetection)()
|
|
], Arc.prototype, "centerX", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core37.SceneChangeDetection)()
|
|
], Arc.prototype, "centerY", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core37.SceneChangeDetection)()
|
|
], Arc.prototype, "radius", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core37.SceneChangeDetection)()
|
|
], Arc.prototype, "startAngle", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core37.SceneChangeDetection)()
|
|
], Arc.prototype, "endAngle", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core37.SceneChangeDetection)()
|
|
], Arc.prototype, "counterClockwise", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core37.SceneChangeDetection)()
|
|
], Arc.prototype, "type", 2);
|
|
|
|
// packages/ag-charts-community/src/scene/shape/line.ts
|
|
var import_ag_charts_core38 = require("ag-charts-core");
|
|
var Line = class extends Shape {
|
|
constructor(opts = {}) {
|
|
super(opts);
|
|
this.x1 = 0;
|
|
this.y1 = 0;
|
|
this.x2 = 0;
|
|
this.y2 = 0;
|
|
this.fill = void 0;
|
|
this.strokeWidth = 1;
|
|
}
|
|
set x(value) {
|
|
this.x1 = value;
|
|
this.x2 = value;
|
|
}
|
|
set y(value) {
|
|
this.y1 = value;
|
|
this.y2 = value;
|
|
}
|
|
get midPoint() {
|
|
return { x: (this.x1 + this.x2) / 2, y: (this.y1 + this.y2) / 2 };
|
|
}
|
|
computeBBox() {
|
|
return new BBox(
|
|
Math.min(this.x1, this.x2),
|
|
Math.min(this.y1, this.y2),
|
|
Math.abs(this.x2 - this.x1),
|
|
Math.abs(this.y2 - this.y1)
|
|
);
|
|
}
|
|
isPointInPath(x, y) {
|
|
if (this.x1 === this.x2 || this.y1 === this.y2) {
|
|
return this.getBBox().clone().grow(this.strokeWidth / 2).containsPoint(x, y);
|
|
}
|
|
return false;
|
|
}
|
|
distanceSquared(px, py) {
|
|
const { x1, y1, x2, y2 } = this;
|
|
return (0, import_ag_charts_core38.lineDistanceSquared)(px, py, x1, y1, x2, y2, Infinity);
|
|
}
|
|
render(renderCtx) {
|
|
const { ctx, devicePixelRatio } = renderCtx;
|
|
let { x1, y1, x2, y2 } = this;
|
|
if (x1 === x2) {
|
|
const { strokeWidth } = this;
|
|
const x = Math.round(x1 * devicePixelRatio) / devicePixelRatio + Math.trunc(strokeWidth * devicePixelRatio) % 2 / (devicePixelRatio * 2);
|
|
x1 = x;
|
|
x2 = x;
|
|
} else if (y1 === y2) {
|
|
const { strokeWidth } = this;
|
|
const y = Math.round(y1 * devicePixelRatio) / devicePixelRatio + Math.trunc(strokeWidth * devicePixelRatio) % 2 / (devicePixelRatio * 2);
|
|
y1 = y;
|
|
y2 = y;
|
|
}
|
|
ctx.beginPath();
|
|
ctx.moveTo(x1, y1);
|
|
ctx.lineTo(x2, y2);
|
|
this.fillStroke(ctx);
|
|
this.fillShadow?.markClean();
|
|
super.render(renderCtx);
|
|
}
|
|
toSVG() {
|
|
if (!this.visible)
|
|
return;
|
|
const element = (0, import_ag_charts_core38.createSvgElement)("line");
|
|
element.setAttribute("x1", String(this.x1));
|
|
element.setAttribute("y1", String(this.y1));
|
|
element.setAttribute("x2", String(this.x2));
|
|
element.setAttribute("y2", String(this.y2));
|
|
this.applySvgStrokeAttributes(element);
|
|
return {
|
|
elements: [element]
|
|
};
|
|
}
|
|
};
|
|
Line.className = "Line";
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], Line.prototype, "x1", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], Line.prototype, "y1", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], Line.prototype, "x2", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core3.SceneChangeDetection)()
|
|
], Line.prototype, "y2", 2);
|
|
|
|
// packages/ag-charts-community/src/scene/shape/radialColumnShape.ts
|
|
var import_ag_charts_core39 = require("ag-charts-core");
|
|
function rotatePoint(x, y, rotation) {
|
|
const radius = Math.hypot(x, y);
|
|
const angle = Math.atan2(y, x);
|
|
const rotated = angle + rotation;
|
|
return {
|
|
x: Math.cos(rotated) * radius,
|
|
y: Math.sin(rotated) * radius
|
|
};
|
|
}
|
|
var RadialColumnShape = class extends Path {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.isBeveled = true;
|
|
this.columnWidth = 0;
|
|
this.startAngle = 0;
|
|
this.endAngle = 0;
|
|
this.outerRadius = 0;
|
|
this.innerRadius = 0;
|
|
this.axisInnerRadius = 0;
|
|
this.axisOuterRadius = 0;
|
|
}
|
|
set cornerRadius(_value) {
|
|
}
|
|
computeBBox() {
|
|
const { columnWidth } = this;
|
|
const [innerRadius, outerRadius] = this.normalizeRadii(this.innerRadius, this.outerRadius);
|
|
const rotation = this.getRotation();
|
|
const left = -columnWidth / 2;
|
|
const right = columnWidth / 2;
|
|
const top = -outerRadius;
|
|
const bottom = -innerRadius;
|
|
let x0 = Infinity;
|
|
let y0 = Infinity;
|
|
let x1 = -Infinity;
|
|
let y1 = -Infinity;
|
|
for (let i = 0; i < 4; i += 1) {
|
|
const { x, y } = rotatePoint(i % 2 === 0 ? left : right, i < 2 ? top : bottom, rotation);
|
|
x0 = Math.min(x, x0);
|
|
y0 = Math.min(y, y0);
|
|
x1 = Math.max(x, x1);
|
|
y1 = Math.max(y, y1);
|
|
}
|
|
return new BBox(x0, y0, x1 - x0, y1 - y0);
|
|
}
|
|
getRotation() {
|
|
const { startAngle, endAngle } = this;
|
|
const midAngle = (0, import_ag_charts_core39.angleBetween)(startAngle, endAngle);
|
|
return (0, import_ag_charts_core39.normalizeAngle360)(startAngle + midAngle / 2 + Math.PI / 2);
|
|
}
|
|
normalizeRadii(innerRadius, outerRadius) {
|
|
if (innerRadius > outerRadius) {
|
|
return [outerRadius, innerRadius];
|
|
}
|
|
return [innerRadius, outerRadius];
|
|
}
|
|
updatePath() {
|
|
const { isBeveled } = this;
|
|
if (isBeveled) {
|
|
this.updateBeveledPath();
|
|
} else {
|
|
this.updateRectangularPath();
|
|
}
|
|
this.checkPathDirty();
|
|
}
|
|
updateRectangularPath() {
|
|
const { columnWidth, path } = this;
|
|
const [innerRadius, outerRadius] = this.normalizeRadii(this.innerRadius, this.outerRadius);
|
|
const left = -columnWidth / 2;
|
|
const right = columnWidth / 2;
|
|
const top = -outerRadius;
|
|
const bottom = -innerRadius;
|
|
const rotation = this.getRotation();
|
|
const points = [
|
|
[left, bottom],
|
|
[left, top],
|
|
[right, top],
|
|
[right, bottom]
|
|
].map(([x, y]) => rotatePoint(x, y, rotation));
|
|
path.clear(true);
|
|
path.moveTo(points[0].x, points[0].y);
|
|
path.lineTo(points[1].x, points[1].y);
|
|
path.lineTo(points[2].x, points[2].y);
|
|
path.lineTo(points[3].x, points[3].y);
|
|
path.closePath();
|
|
}
|
|
calculateCircleIntersection(x, radiusSquared) {
|
|
const xSquared = x * x;
|
|
if (radiusSquared < xSquared) {
|
|
return null;
|
|
}
|
|
const y = -Math.sqrt(radiusSquared - xSquared);
|
|
const angle = Math.atan2(y, x);
|
|
return { y, angle };
|
|
}
|
|
calculateBothIntersections(left, right, radius) {
|
|
const radiusSquared = radius * radius;
|
|
const leftInt = this.calculateCircleIntersection(left, radiusSquared);
|
|
const rightInt = this.calculateCircleIntersection(right, radiusSquared);
|
|
if (!leftInt || !rightInt) {
|
|
return null;
|
|
}
|
|
return { left: leftInt, right: rightInt };
|
|
}
|
|
calculateAxisOuterIntersections(left, right, axisOuterRadius) {
|
|
const axisOuterRadiusSquared = axisOuterRadius * axisOuterRadius;
|
|
const axisOuterLeft = this.calculateCircleIntersection(left, axisOuterRadiusSquared);
|
|
const axisOuterRight = this.calculateCircleIntersection(right, axisOuterRadiusSquared);
|
|
if (!axisOuterLeft || !axisOuterRight) {
|
|
return null;
|
|
}
|
|
return {
|
|
left: axisOuterLeft,
|
|
right: axisOuterRight,
|
|
radiusSquared: axisOuterRadiusSquared
|
|
};
|
|
}
|
|
moveToRotated(x, y, rotation) {
|
|
const point = rotatePoint(x, y, rotation);
|
|
this.path.moveTo(point.x, point.y);
|
|
}
|
|
lineToRotated(x, y, rotation) {
|
|
const point = rotatePoint(x, y, rotation);
|
|
this.path.lineTo(point.x, point.y);
|
|
}
|
|
renderTopWithCornerClipping(axisOuterRadius, axisOuter, geometry) {
|
|
const { path } = this;
|
|
const { right, top, rotation } = geometry;
|
|
const topSquared = top * top;
|
|
const topIntersectionSquared = axisOuter.radiusSquared - topSquared;
|
|
if (topIntersectionSquared <= 0) {
|
|
this.lineToRotated(right, axisOuter.right.y, rotation);
|
|
path.arc(0, 0, axisOuterRadius, rotation + axisOuter.right.angle, rotation + axisOuter.left.angle, true);
|
|
} else {
|
|
const topIntersectionX = Math.sqrt(topIntersectionSquared);
|
|
const topRightAngle = Math.atan2(top, topIntersectionX);
|
|
const topLeftAngle = Math.atan2(top, -topIntersectionX);
|
|
this.lineToRotated(right, axisOuter.right.y, rotation);
|
|
path.arc(0, 0, axisOuterRadius, rotation + axisOuter.right.angle, rotation + topRightAngle, true);
|
|
this.lineToRotated(-topIntersectionX, top, rotation);
|
|
path.arc(0, 0, axisOuterRadius, rotation + topLeftAngle, rotation + axisOuter.left.angle, true);
|
|
}
|
|
}
|
|
updateBeveledPath() {
|
|
const { columnWidth, path, axisInnerRadius, axisOuterRadius } = this;
|
|
const [innerRadius, outerRadius] = this.normalizeRadii(this.innerRadius, this.outerRadius);
|
|
const left = -columnWidth / 2;
|
|
const right = columnWidth / 2;
|
|
const top = -outerRadius;
|
|
const bottom = -innerRadius;
|
|
const rotation = this.getRotation();
|
|
const isTouchingInner = (0, import_ag_charts_core39.isNumberEqual)(innerRadius, axisInnerRadius);
|
|
const isTouchingOuter = (0, import_ag_charts_core39.isNumberEqual)(outerRadius, axisOuterRadius);
|
|
const topCornersBreach = Math.hypot(left, top) > axisOuterRadius || Math.hypot(right, top) > axisOuterRadius;
|
|
if (!isTouchingInner && !isTouchingOuter && !topCornersBreach) {
|
|
this.updateRectangularPath();
|
|
return;
|
|
}
|
|
const inner = isTouchingInner ? this.calculateBothIntersections(left, right, innerRadius) : null;
|
|
const outer = isTouchingOuter ? this.calculateBothIntersections(left, right, outerRadius) : null;
|
|
const axisOuter = topCornersBreach ? this.calculateAxisOuterIntersections(left, right, axisOuterRadius) : null;
|
|
if (isTouchingInner && !inner || isTouchingOuter && !outer || topCornersBreach && !axisOuter) {
|
|
this.updateRectangularPath();
|
|
return;
|
|
}
|
|
path.clear(true);
|
|
const geometry = { left, right, top, bottom, rotation };
|
|
if (inner) {
|
|
this.moveToRotated(left, inner.left.y, rotation);
|
|
} else {
|
|
this.moveToRotated(left, bottom, rotation);
|
|
}
|
|
if (inner) {
|
|
path.arc(0, 0, innerRadius, rotation + inner.left.angle, rotation + inner.right.angle, false);
|
|
} else {
|
|
this.lineToRotated(right, bottom, rotation);
|
|
}
|
|
if (outer) {
|
|
this.lineToRotated(right, outer.right.y, rotation);
|
|
path.arc(0, 0, outerRadius, rotation + outer.right.angle, rotation + outer.left.angle, true);
|
|
} else if (axisOuter) {
|
|
this.renderTopWithCornerClipping(axisOuterRadius, axisOuter, geometry);
|
|
} else {
|
|
this.lineToRotated(right, top, rotation);
|
|
this.lineToRotated(left, top, rotation);
|
|
}
|
|
path.closePath();
|
|
}
|
|
};
|
|
RadialColumnShape.className = "RadialColumnShape";
|
|
__decorateClass([
|
|
(0, import_ag_charts_core39.SceneChangeDetection)()
|
|
], RadialColumnShape.prototype, "isBeveled", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core39.SceneChangeDetection)()
|
|
], RadialColumnShape.prototype, "columnWidth", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core39.SceneChangeDetection)()
|
|
], RadialColumnShape.prototype, "startAngle", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core39.SceneChangeDetection)()
|
|
], RadialColumnShape.prototype, "endAngle", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core39.SceneChangeDetection)()
|
|
], RadialColumnShape.prototype, "outerRadius", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core39.SceneChangeDetection)()
|
|
], RadialColumnShape.prototype, "innerRadius", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core39.SceneChangeDetection)()
|
|
], RadialColumnShape.prototype, "axisInnerRadius", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core39.SceneChangeDetection)()
|
|
], RadialColumnShape.prototype, "axisOuterRadius", 2);
|
|
function getRadialColumnWidth(startAngle, endAngle, axisOuterRadius, columnWidthRatio, maxColumnWidthRatio) {
|
|
const rotation = (0, import_ag_charts_core39.angleBetween)(startAngle, endAngle);
|
|
const pad = rotation * (1 - columnWidthRatio) / 2;
|
|
startAngle += pad;
|
|
endAngle -= pad;
|
|
if (rotation < 1e-3) {
|
|
return 2 * axisOuterRadius * maxColumnWidthRatio;
|
|
}
|
|
if (rotation >= 2 * Math.PI) {
|
|
const midAngle = startAngle + rotation / 2;
|
|
startAngle = midAngle - Math.PI;
|
|
endAngle = midAngle + Math.PI;
|
|
}
|
|
const startX = axisOuterRadius * Math.cos(startAngle);
|
|
const startY = axisOuterRadius * Math.sin(startAngle);
|
|
const endX = axisOuterRadius * Math.cos(endAngle);
|
|
const endY = axisOuterRadius * Math.sin(endAngle);
|
|
const colWidth = Math.floor(Math.hypot(startX - endX, startY - endY));
|
|
const maxWidth = 2 * axisOuterRadius * maxColumnWidthRatio;
|
|
return Math.max(1, Math.min(maxWidth, colWidth));
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/shape/sector.ts
|
|
var import_ag_charts_core41 = require("ag-charts-core");
|
|
|
|
// packages/ag-charts-community/src/scene/sectorBox.ts
|
|
var SectorBox = class _SectorBox {
|
|
constructor(startAngle, endAngle, innerRadius, outerRadius) {
|
|
this.startAngle = startAngle;
|
|
this.endAngle = endAngle;
|
|
this.innerRadius = innerRadius;
|
|
this.outerRadius = outerRadius;
|
|
}
|
|
clone() {
|
|
const { startAngle, endAngle, innerRadius, outerRadius } = this;
|
|
return new _SectorBox(startAngle, endAngle, innerRadius, outerRadius);
|
|
}
|
|
equals(other) {
|
|
return this.startAngle === other.startAngle && this.endAngle === other.endAngle && this.innerRadius === other.innerRadius && this.outerRadius === other.outerRadius;
|
|
}
|
|
[interpolate](other, d) {
|
|
return new _SectorBox(
|
|
this.startAngle * (1 - d) + other.startAngle * d,
|
|
this.endAngle * (1 - d) + other.endAngle * d,
|
|
this.innerRadius * (1 - d) + other.innerRadius * d,
|
|
this.outerRadius * (1 - d) + other.outerRadius * d
|
|
);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/util/sector.ts
|
|
var import_ag_charts_core40 = require("ag-charts-core");
|
|
function sectorBox({ startAngle, endAngle, innerRadius, outerRadius }) {
|
|
let x0 = Infinity;
|
|
let y0 = Infinity;
|
|
let x1 = -Infinity;
|
|
let y1 = -Infinity;
|
|
const addPoint = (x, y) => {
|
|
x0 = Math.min(x, x0);
|
|
y0 = Math.min(y, y0);
|
|
x1 = Math.max(x, x1);
|
|
y1 = Math.max(y, y1);
|
|
};
|
|
addPoint(innerRadius * Math.cos(startAngle), innerRadius * Math.sin(startAngle));
|
|
addPoint(innerRadius * Math.cos(endAngle), innerRadius * Math.sin(endAngle));
|
|
addPoint(outerRadius * Math.cos(startAngle), outerRadius * Math.sin(startAngle));
|
|
addPoint(outerRadius * Math.cos(endAngle), outerRadius * Math.sin(endAngle));
|
|
if ((0, import_ag_charts_core40.isBetweenAngles)(0, startAngle, endAngle)) {
|
|
addPoint(outerRadius, 0);
|
|
}
|
|
if ((0, import_ag_charts_core40.isBetweenAngles)(Math.PI * 0.5, startAngle, endAngle)) {
|
|
addPoint(0, outerRadius);
|
|
}
|
|
if ((0, import_ag_charts_core40.isBetweenAngles)(Math.PI, startAngle, endAngle)) {
|
|
addPoint(-outerRadius, 0);
|
|
}
|
|
if ((0, import_ag_charts_core40.isBetweenAngles)(Math.PI * 1.5, startAngle, endAngle)) {
|
|
addPoint(0, -outerRadius);
|
|
}
|
|
return new BBox(x0, y0, x1 - x0, y1 - y0);
|
|
}
|
|
function isPointInSector(x, y, sector) {
|
|
const radius = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
|
const { innerRadius, outerRadius } = sector;
|
|
if (sector.startAngle === sector.endAngle || radius < Math.min(innerRadius, outerRadius) || radius > Math.max(innerRadius, outerRadius)) {
|
|
return false;
|
|
}
|
|
const startAngle = (0, import_ag_charts_core40.normalizeAngle180)(sector.startAngle);
|
|
const endAngle = (0, import_ag_charts_core40.normalizeAngle180)(sector.endAngle);
|
|
const angle = Math.atan2(y, x);
|
|
return startAngle < endAngle ? angle <= endAngle && angle >= startAngle : angle <= endAngle && angle >= -Math.PI || angle >= startAngle && angle <= Math.PI;
|
|
}
|
|
function radiiScalingFactor(r, sweep, a, b) {
|
|
if (a === 0 && b === 0)
|
|
return 0;
|
|
const fs1 = Math.asin(Math.abs(1 * a) / (r + 1 * a)) + Math.asin(Math.abs(1 * b) / (r + 1 * b)) - sweep;
|
|
if (fs1 < 0)
|
|
return 1;
|
|
let start = 0;
|
|
let end2 = 1;
|
|
for (let i = 0; i < 8; i += 1) {
|
|
const s = (start + end2) / 2;
|
|
const fs = Math.asin(Math.abs(s * a) / (r + s * a)) + Math.asin(Math.abs(s * b) / (r + s * b)) - sweep;
|
|
if (fs < 0) {
|
|
start = s;
|
|
} else {
|
|
end2 = s;
|
|
}
|
|
}
|
|
return start;
|
|
}
|
|
var delta2 = 1e-6;
|
|
function clockwiseAngle(angle, relativeToStartAngle) {
|
|
if ((0, import_ag_charts_core40.angleBetween)(angle, relativeToStartAngle) < delta2) {
|
|
return relativeToStartAngle;
|
|
} else {
|
|
return (0, import_ag_charts_core40.normalizeAngle360)(angle - relativeToStartAngle) + relativeToStartAngle;
|
|
}
|
|
}
|
|
function clockwiseAngles(startAngle, endAngle, relativeToStartAngle = 0) {
|
|
const fullPie = Math.abs(endAngle - startAngle) >= 2 * Math.PI;
|
|
const sweepAngle = fullPie ? 2 * Math.PI : (0, import_ag_charts_core40.normalizeAngle360)(endAngle - startAngle);
|
|
startAngle = clockwiseAngle(startAngle, relativeToStartAngle);
|
|
endAngle = startAngle + sweepAngle;
|
|
return { startAngle, endAngle };
|
|
}
|
|
function arcRadialLineIntersectionAngle(cx, cy, r, startAngle, endAngle, clipAngle) {
|
|
const sinA = Math.sin(clipAngle);
|
|
const cosA = Math.cos(clipAngle);
|
|
const c = cx ** 2 + cy ** 2 - r ** 2;
|
|
let p0x;
|
|
let p0y;
|
|
let p1x;
|
|
let p1y;
|
|
if (cosA > 0.5) {
|
|
const tanA = sinA / cosA;
|
|
const a = 1 + tanA ** 2;
|
|
const b = -2 * (cx + cy * tanA);
|
|
const d = b ** 2 - 4 * a * c;
|
|
if (d < 0)
|
|
return;
|
|
const x0 = (-b + Math.sqrt(d)) / (2 * a);
|
|
const x1 = (-b - Math.sqrt(d)) / (2 * a);
|
|
p0x = x0;
|
|
p0y = x0 * tanA;
|
|
p1x = x1;
|
|
p1y = x1 * tanA;
|
|
} else {
|
|
const cotA = cosA / sinA;
|
|
const a = 1 + cotA ** 2;
|
|
const b = -2 * (cy + cx * cotA);
|
|
const d = b ** 2 - 4 * a * c;
|
|
if (d < 0)
|
|
return;
|
|
const y0 = (-b + Math.sqrt(d)) / (2 * a);
|
|
const y1 = (-b - Math.sqrt(d)) / (2 * a);
|
|
p0x = y0 * cotA;
|
|
p0y = y0;
|
|
p1x = y1 * cotA;
|
|
p1y = y1;
|
|
}
|
|
const normalisedX = cosA;
|
|
const normalisedY = sinA;
|
|
const p0DotNormalized = p0x * normalisedX + p0y * normalisedY;
|
|
const p1DotNormalized = p1x * normalisedX + p1y * normalisedY;
|
|
const a0 = p0DotNormalized > 0 ? clockwiseAngle(Math.atan2(p0y - cy, p0x - cx), startAngle) : Number.NaN;
|
|
const a1 = p1DotNormalized > 0 ? clockwiseAngle(Math.atan2(p1y - cy, p1x - cx), startAngle) : Number.NaN;
|
|
if (a0 >= startAngle && a0 <= endAngle) {
|
|
return a0;
|
|
} else if (a1 >= startAngle && a1 <= endAngle) {
|
|
return a1;
|
|
}
|
|
}
|
|
function arcCircleIntersectionAngle(cx, cy, r, startAngle, endAngle, circleR) {
|
|
const d = Math.hypot(cx, cy);
|
|
const d1 = (d ** 2 - r ** 2 + circleR ** 2) / (2 * d);
|
|
const d2 = d - d1;
|
|
const theta = Math.atan2(cy, cx);
|
|
const deltaTheta = Math.acos(-d2 / r);
|
|
const a0 = clockwiseAngle(theta + deltaTheta, startAngle);
|
|
const a1 = clockwiseAngle(theta - deltaTheta, startAngle);
|
|
if (a0 >= startAngle && a0 <= endAngle) {
|
|
return a0;
|
|
} else if (a1 >= startAngle && a1 <= endAngle) {
|
|
return a1;
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/shape/sector.ts
|
|
var Arc2 = class {
|
|
constructor(cx, cy, r, a0, a1) {
|
|
this.cx = cx;
|
|
this.cy = cy;
|
|
this.r = r;
|
|
this.a0 = a0;
|
|
this.a1 = a1;
|
|
if (this.a0 >= this.a1) {
|
|
this.a0 = Number.NaN;
|
|
this.a1 = Number.NaN;
|
|
}
|
|
}
|
|
isValid() {
|
|
return Number.isFinite(this.a0) && Number.isFinite(this.a1);
|
|
}
|
|
pointAt(a) {
|
|
return {
|
|
x: this.cx + this.r * Math.cos(a),
|
|
y: this.cy + this.r * Math.sin(a)
|
|
};
|
|
}
|
|
clipStart(a) {
|
|
if (a == null || !this.isValid() || a < this.a0)
|
|
return;
|
|
this.a0 = a;
|
|
if (Number.isNaN(a) || this.a0 >= this.a1) {
|
|
this.a0 = Number.NaN;
|
|
this.a1 = Number.NaN;
|
|
}
|
|
}
|
|
clipEnd(a) {
|
|
if (a == null || !this.isValid() || a > this.a1)
|
|
return;
|
|
this.a1 = a;
|
|
if (Number.isNaN(a) || this.a0 >= this.a1) {
|
|
this.a0 = Number.NaN;
|
|
this.a1 = Number.NaN;
|
|
}
|
|
}
|
|
};
|
|
var Sector = class extends Path {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.centerX = 0;
|
|
this.centerY = 0;
|
|
this.innerRadius = 10;
|
|
this.outerRadius = 20;
|
|
this.startAngle = 0;
|
|
this.endAngle = Math.PI * 2;
|
|
this.clipSector = void 0;
|
|
this.concentricEdgeInset = 0;
|
|
this.radialEdgeInset = 0;
|
|
this.startOuterCornerRadius = 0;
|
|
this.endOuterCornerRadius = 0;
|
|
this.startInnerCornerRadius = 0;
|
|
this.endInnerCornerRadius = 0;
|
|
}
|
|
set inset(value) {
|
|
this.concentricEdgeInset = value;
|
|
this.radialEdgeInset = value;
|
|
}
|
|
set cornerRadius(value) {
|
|
this.startOuterCornerRadius = value;
|
|
this.endOuterCornerRadius = value;
|
|
this.startInnerCornerRadius = value;
|
|
this.endInnerCornerRadius = value;
|
|
}
|
|
computeBBox() {
|
|
return sectorBox(this).translate(this.centerX, this.centerY);
|
|
}
|
|
normalizedRadii() {
|
|
const { concentricEdgeInset } = this;
|
|
let { innerRadius, outerRadius } = this;
|
|
innerRadius = innerRadius > 0 ? innerRadius + concentricEdgeInset : 0;
|
|
outerRadius = Math.max(outerRadius - concentricEdgeInset, 0);
|
|
return { innerRadius, outerRadius };
|
|
}
|
|
normalizedClipSector() {
|
|
const { clipSector } = this;
|
|
if (clipSector == null)
|
|
return;
|
|
const { startAngle, endAngle } = clockwiseAngles(this.startAngle, this.endAngle);
|
|
const { innerRadius, outerRadius } = this.normalizedRadii();
|
|
const clipAngles = clockwiseAngles(clipSector.startAngle, clipSector.endAngle, startAngle);
|
|
return new SectorBox(
|
|
Math.max(startAngle, clipAngles.startAngle),
|
|
Math.min(endAngle, clipAngles.endAngle),
|
|
Math.max(innerRadius, clipSector.innerRadius),
|
|
Math.min(outerRadius, clipSector.outerRadius)
|
|
);
|
|
}
|
|
getAngleOffset(radius) {
|
|
return radius > 0 ? this.radialEdgeInset / radius : 0;
|
|
}
|
|
arc(r, angleSweep, a0, a1, outerArc, innerArc, start, inner) {
|
|
if (r <= 0)
|
|
return;
|
|
const { startAngle, endAngle } = clockwiseAngles(this.startAngle, this.endAngle);
|
|
const { innerRadius, outerRadius } = this.normalizedRadii();
|
|
const clipSector = this.normalizedClipSector();
|
|
if (inner && innerRadius <= 0)
|
|
return;
|
|
const angleOffset = inner ? this.getAngleOffset(innerRadius + r) : this.getAngleOffset(outerRadius - r);
|
|
const angle = start ? startAngle + angleOffset + angleSweep : endAngle - angleOffset - angleSweep;
|
|
const radius = inner ? innerRadius + r : outerRadius - r;
|
|
const cx = radius * Math.cos(angle);
|
|
const cy = radius * Math.sin(angle);
|
|
if (clipSector != null) {
|
|
const delta3 = 1e-6;
|
|
if (!start && !(angle >= startAngle - delta3 && angle <= clipSector.endAngle - delta3))
|
|
return;
|
|
if (start && !(angle >= clipSector.startAngle + delta3 && angle <= endAngle - delta3))
|
|
return;
|
|
if (inner && radius < clipSector.innerRadius - delta3)
|
|
return;
|
|
if (!inner && radius > clipSector.outerRadius + delta3)
|
|
return;
|
|
}
|
|
const arc = new Arc2(cx, cy, r, a0, a1);
|
|
if (clipSector != null) {
|
|
if (inner) {
|
|
arc.clipStart(arcRadialLineIntersectionAngle(cx, cy, r, a0, a1, clipSector.endAngle));
|
|
arc.clipEnd(arcRadialLineIntersectionAngle(cx, cy, r, a0, a1, clipSector.startAngle));
|
|
} else {
|
|
arc.clipStart(arcRadialLineIntersectionAngle(cx, cy, r, a0, a1, clipSector.startAngle));
|
|
arc.clipEnd(arcRadialLineIntersectionAngle(cx, cy, r, a0, a1, clipSector.endAngle));
|
|
}
|
|
let circleClipStart;
|
|
let circleClipEnd;
|
|
if (start) {
|
|
circleClipStart = arcCircleIntersectionAngle(cx, cy, r, a0, a1, clipSector.innerRadius);
|
|
circleClipEnd = arcCircleIntersectionAngle(cx, cy, r, a0, a1, clipSector.outerRadius);
|
|
} else {
|
|
circleClipStart = arcCircleIntersectionAngle(cx, cy, r, a0, a1, clipSector.outerRadius);
|
|
circleClipEnd = arcCircleIntersectionAngle(cx, cy, r, a0, a1, clipSector.innerRadius);
|
|
}
|
|
arc.clipStart(circleClipStart);
|
|
arc.clipEnd(circleClipEnd);
|
|
if (circleClipStart != null) {
|
|
const { x: x2, y: y2 } = arc.pointAt(circleClipStart);
|
|
const theta2 = clockwiseAngle(Math.atan2(y2, x2), startAngle);
|
|
if (start) {
|
|
innerArc?.clipStart(theta2);
|
|
} else {
|
|
outerArc.clipEnd(theta2);
|
|
}
|
|
}
|
|
if (circleClipEnd != null) {
|
|
const { x: x2, y: y2 } = arc.pointAt(circleClipEnd);
|
|
const theta2 = clockwiseAngle(Math.atan2(y2, x2), startAngle);
|
|
if (start) {
|
|
outerArc.clipStart(theta2);
|
|
} else {
|
|
innerArc?.clipEnd(theta2);
|
|
}
|
|
}
|
|
}
|
|
if (clipSector != null) {
|
|
const { x: x2, y: y2 } = arc.pointAt((arc.a0 + arc.a1) / 2);
|
|
if (!isPointInSector(x2, y2, clipSector))
|
|
return;
|
|
}
|
|
const { x, y } = arc.pointAt(start === inner ? arc.a0 : arc.a1);
|
|
const theta = clockwiseAngle(Math.atan2(y, x), startAngle);
|
|
const radialArc = inner ? innerArc : outerArc;
|
|
if (start) {
|
|
radialArc?.clipStart(theta);
|
|
} else {
|
|
radialArc?.clipEnd(theta);
|
|
}
|
|
return arc;
|
|
}
|
|
updatePath() {
|
|
const delta3 = 1e-6;
|
|
const { path, centerX, centerY, concentricEdgeInset, radialEdgeInset } = this;
|
|
let { startOuterCornerRadius, endOuterCornerRadius, startInnerCornerRadius, endInnerCornerRadius } = this;
|
|
const { startAngle, endAngle } = clockwiseAngles(this.startAngle, this.endAngle);
|
|
const { innerRadius, outerRadius } = this.normalizedRadii();
|
|
const clipSector = this.normalizedClipSector();
|
|
const sweepAngle = endAngle - startAngle;
|
|
const fullPie = sweepAngle >= 2 * Math.PI - delta3;
|
|
path.clear();
|
|
const innerAngleOffset = this.getAngleOffset(innerRadius);
|
|
const adjustedSweep = sweepAngle - 2 * innerAngleOffset;
|
|
const radialLength = outerRadius - innerRadius;
|
|
const innerCornerDistance = innerRadius > 0 && adjustedSweep > 0 ? 2 * innerRadius * Math.sin(adjustedSweep / 2) : 0;
|
|
const outerCornerDistance = outerRadius > 0 && adjustedSweep > 0 ? 2 * outerRadius * Math.sin(adjustedSweep / 2) : 0;
|
|
startOuterCornerRadius = Math.floor(
|
|
Math.max(0, Math.min(startOuterCornerRadius, outerCornerDistance / 2, radialLength / 2))
|
|
);
|
|
endOuterCornerRadius = Math.floor(
|
|
Math.max(0, Math.min(endOuterCornerRadius, outerCornerDistance / 2, radialLength / 2))
|
|
);
|
|
startInnerCornerRadius = Math.floor(
|
|
Math.max(0, Math.min(startInnerCornerRadius, innerCornerDistance / 2, radialLength / 2))
|
|
);
|
|
endInnerCornerRadius = Math.floor(
|
|
Math.max(0, Math.min(endInnerCornerRadius, innerCornerDistance / 2, radialLength / 2))
|
|
);
|
|
const isInvalid = innerRadius === 0 && outerRadius === 0 || innerRadius > outerRadius || innerCornerDistance < 0 || outerCornerDistance <= 0;
|
|
if (isInvalid) {
|
|
return;
|
|
} else if ((clipSector?.startAngle ?? startAngle) === (clipSector?.endAngle ?? endAngle)) {
|
|
return;
|
|
} else if (fullPie && this.clipSector == null && startOuterCornerRadius === 0 && endOuterCornerRadius === 0 && startInnerCornerRadius === 0 && endInnerCornerRadius === 0) {
|
|
path.moveTo(centerX + outerRadius * Math.cos(startAngle), centerY + outerRadius * Math.sin(startAngle));
|
|
path.arc(centerX, centerY, outerRadius, startAngle, endAngle);
|
|
if (innerRadius > concentricEdgeInset) {
|
|
path.moveTo(centerX + innerRadius * Math.cos(endAngle), centerY + innerRadius * Math.sin(endAngle));
|
|
path.arc(centerX, centerY, innerRadius, endAngle, startAngle, true);
|
|
}
|
|
path.closePath();
|
|
return;
|
|
} else if (this.clipSector == null && Math.abs(innerRadius - outerRadius) < 1e-6) {
|
|
path.arc(centerX, centerY, outerRadius, startAngle, endAngle, false);
|
|
path.arc(centerX, centerY, outerRadius, endAngle, startAngle, true);
|
|
path.closePath();
|
|
return;
|
|
}
|
|
const outerAngleOffset = this.getAngleOffset(outerRadius);
|
|
const outerAngleExceeded = sweepAngle < 2 * outerAngleOffset;
|
|
if (outerAngleExceeded)
|
|
return;
|
|
const hasInnerSweep = (clipSector?.innerRadius ?? innerRadius) > concentricEdgeInset;
|
|
const innerAngleExceeded = innerRadius < concentricEdgeInset || sweepAngle < 2 * innerAngleOffset;
|
|
const maxRadialLength = Math.max(
|
|
startOuterCornerRadius,
|
|
startInnerCornerRadius,
|
|
endOuterCornerRadius,
|
|
endInnerCornerRadius
|
|
);
|
|
const initialScalingFactor = maxRadialLength > 0 ? Math.min(radialLength / maxRadialLength, 1) : 1;
|
|
startOuterCornerRadius *= initialScalingFactor;
|
|
endOuterCornerRadius *= initialScalingFactor;
|
|
startInnerCornerRadius *= initialScalingFactor;
|
|
endInnerCornerRadius *= initialScalingFactor;
|
|
const outerScalingFactor = radiiScalingFactor(
|
|
outerRadius,
|
|
sweepAngle - 2 * outerAngleOffset,
|
|
-startOuterCornerRadius,
|
|
-endOuterCornerRadius
|
|
);
|
|
startOuterCornerRadius *= outerScalingFactor;
|
|
endOuterCornerRadius *= outerScalingFactor;
|
|
if (!innerAngleExceeded && hasInnerSweep) {
|
|
const innerScalingFactor = radiiScalingFactor(
|
|
innerRadius,
|
|
sweepAngle - 2 * innerAngleOffset,
|
|
startInnerCornerRadius,
|
|
endInnerCornerRadius
|
|
);
|
|
startInnerCornerRadius *= innerScalingFactor;
|
|
endInnerCornerRadius *= innerScalingFactor;
|
|
} else {
|
|
startInnerCornerRadius = 0;
|
|
endInnerCornerRadius = 0;
|
|
}
|
|
const maxCombinedRadialLength = Math.max(
|
|
startOuterCornerRadius + startInnerCornerRadius,
|
|
endOuterCornerRadius + endInnerCornerRadius
|
|
);
|
|
const edgesScalingFactor = maxCombinedRadialLength > 0 ? Math.min(radialLength / maxCombinedRadialLength, 1) : 1;
|
|
startOuterCornerRadius *= edgesScalingFactor;
|
|
endOuterCornerRadius *= edgesScalingFactor;
|
|
startInnerCornerRadius *= edgesScalingFactor;
|
|
endInnerCornerRadius *= edgesScalingFactor;
|
|
let startOuterCornerRadiusAngleSweep = 0;
|
|
let endOuterCornerRadiusAngleSweep = 0;
|
|
const startOuterCornerRadiusSweep = startOuterCornerRadius / (outerRadius - startOuterCornerRadius);
|
|
const endOuterCornerRadiusSweep = endOuterCornerRadius / (outerRadius - endOuterCornerRadius);
|
|
if (startOuterCornerRadiusSweep >= 0 && startOuterCornerRadiusSweep < 1 - delta3) {
|
|
startOuterCornerRadiusAngleSweep = Math.asin(startOuterCornerRadiusSweep);
|
|
} else {
|
|
startOuterCornerRadiusAngleSweep = sweepAngle / 2;
|
|
const maxStartOuterCornerRadius = outerRadius / (1 / Math.sin(startOuterCornerRadiusAngleSweep) + 1);
|
|
startOuterCornerRadius = Math.min(maxStartOuterCornerRadius, startOuterCornerRadius);
|
|
}
|
|
if (endOuterCornerRadiusSweep >= 0 && endOuterCornerRadiusSweep < 1 - delta3) {
|
|
endOuterCornerRadiusAngleSweep = Math.asin(endOuterCornerRadiusSweep);
|
|
} else {
|
|
endOuterCornerRadiusAngleSweep = sweepAngle / 2;
|
|
const maxEndOuterCornerRadius = outerRadius / (1 / Math.sin(endOuterCornerRadiusAngleSweep) + 1);
|
|
endOuterCornerRadius = Math.min(maxEndOuterCornerRadius, endOuterCornerRadius);
|
|
}
|
|
const startInnerCornerRadiusAngleSweep = Math.asin(
|
|
startInnerCornerRadius / (innerRadius + startInnerCornerRadius)
|
|
);
|
|
const endInnerCornerRadiusAngleSweep = Math.asin(endInnerCornerRadius / (innerRadius + endInnerCornerRadius));
|
|
const outerArcRadius = clipSector?.outerRadius ?? outerRadius;
|
|
const outerArcRadiusOffset = this.getAngleOffset(outerArcRadius);
|
|
const outerArc = new Arc2(
|
|
0,
|
|
0,
|
|
outerArcRadius,
|
|
startAngle + outerArcRadiusOffset,
|
|
endAngle - outerArcRadiusOffset
|
|
);
|
|
const innerArcRadius = clipSector?.innerRadius ?? innerRadius;
|
|
const innerArcRadiusOffset = this.getAngleOffset(innerArcRadius);
|
|
const innerArc = hasInnerSweep ? new Arc2(0, 0, innerArcRadius, startAngle + innerArcRadiusOffset, endAngle - innerArcRadiusOffset) : void 0;
|
|
if (clipSector != null) {
|
|
outerArc.clipStart(clipSector.startAngle);
|
|
outerArc.clipEnd(clipSector.endAngle);
|
|
innerArc?.clipStart(clipSector.startAngle);
|
|
innerArc?.clipEnd(clipSector.endAngle);
|
|
}
|
|
const startOuterArc = this.arc(
|
|
startOuterCornerRadius,
|
|
startOuterCornerRadiusAngleSweep,
|
|
startAngle - Math.PI * 0.5,
|
|
startAngle + startOuterCornerRadiusAngleSweep,
|
|
outerArc,
|
|
innerArc,
|
|
true,
|
|
false
|
|
);
|
|
const endOuterArc = this.arc(
|
|
endOuterCornerRadius,
|
|
endOuterCornerRadiusAngleSweep,
|
|
endAngle - endOuterCornerRadiusAngleSweep,
|
|
endAngle + Math.PI * 0.5,
|
|
outerArc,
|
|
innerArc,
|
|
false,
|
|
false
|
|
);
|
|
const endInnerArc = this.arc(
|
|
endInnerCornerRadius,
|
|
endInnerCornerRadiusAngleSweep,
|
|
endAngle + Math.PI * 0.5,
|
|
endAngle + Math.PI - endInnerCornerRadiusAngleSweep,
|
|
outerArc,
|
|
innerArc,
|
|
false,
|
|
true
|
|
);
|
|
const startInnerArc = this.arc(
|
|
startInnerCornerRadius,
|
|
startInnerCornerRadiusAngleSweep,
|
|
startAngle + Math.PI + startInnerCornerRadiusAngleSweep,
|
|
startAngle + Math.PI * 1.5,
|
|
outerArc,
|
|
innerArc,
|
|
true,
|
|
true
|
|
);
|
|
if (innerAngleExceeded && hasInnerSweep) {
|
|
} else if (innerAngleExceeded) {
|
|
const x = sweepAngle < Math.PI * 0.5 ? radialEdgeInset * (1 + Math.cos(sweepAngle)) / Math.sin(sweepAngle) : Number.NaN;
|
|
let r;
|
|
if (x > 0 && x < outerRadius) {
|
|
r = Math.max(Math.hypot(radialEdgeInset, x), innerRadius);
|
|
} else {
|
|
r = radialEdgeInset;
|
|
}
|
|
r = Math.max(r, innerRadius);
|
|
const midAngle = startAngle + sweepAngle * 0.5;
|
|
path.moveTo(centerX + r * Math.cos(midAngle), centerY + r * Math.sin(midAngle));
|
|
} else if (startInnerArc?.isValid() === true || innerArc?.isValid() === true) {
|
|
} else {
|
|
const midAngle = startAngle + sweepAngle / 2;
|
|
const cx = innerRadius * Math.cos(midAngle);
|
|
const cy = innerRadius * Math.sin(midAngle);
|
|
path.moveTo(centerX + cx, centerY + cy);
|
|
}
|
|
if (startOuterArc?.isValid() === true) {
|
|
const { cx, cy, r, a0, a1 } = startOuterArc;
|
|
path.arc(centerX + cx, centerY + cy, r, a0, a1);
|
|
}
|
|
if (outerArc.isValid()) {
|
|
const { r, a0, a1 } = outerArc;
|
|
path.arc(centerX, centerY, r, a0, a1);
|
|
}
|
|
if (endOuterArc?.isValid() === true) {
|
|
const { cx, cy, r, a0, a1 } = endOuterArc;
|
|
path.arc(centerX + cx, centerY + cy, r, a0, a1);
|
|
}
|
|
if (!innerAngleExceeded) {
|
|
if (endInnerArc?.isValid() === true) {
|
|
const { cx, cy, r, a0, a1 } = endInnerArc;
|
|
path.arc(centerX + cx, centerY + cy, r, a0, a1);
|
|
}
|
|
if (innerArc?.isValid() === true) {
|
|
const { r, a0, a1 } = innerArc;
|
|
path.arc(centerX, centerY, r, a1, a0, true);
|
|
}
|
|
if (startInnerArc?.isValid() === true) {
|
|
const { cx, cy, r, a0, a1 } = startInnerArc;
|
|
path.arc(centerX + cx, centerY + cy, r, a0, a1);
|
|
}
|
|
}
|
|
path.closePath();
|
|
}
|
|
isPointInPath(x, y) {
|
|
const { startAngle, endAngle, innerRadius, outerRadius } = this.clipSector ?? this;
|
|
return isPointInSector(x - this.centerX, y - this.centerY, {
|
|
startAngle,
|
|
endAngle,
|
|
innerRadius: Math.min(innerRadius, outerRadius),
|
|
outerRadius: Math.max(innerRadius, outerRadius)
|
|
});
|
|
}
|
|
};
|
|
Sector.className = "Sector";
|
|
__decorateClass([
|
|
(0, import_ag_charts_core41.SceneChangeDetection)()
|
|
], Sector.prototype, "centerX", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core41.SceneChangeDetection)()
|
|
], Sector.prototype, "centerY", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core41.SceneChangeDetection)()
|
|
], Sector.prototype, "innerRadius", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core41.SceneChangeDetection)()
|
|
], Sector.prototype, "outerRadius", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core41.SceneChangeDetection)()
|
|
], Sector.prototype, "startAngle", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core41.SceneChangeDetection)()
|
|
], Sector.prototype, "endAngle", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core41.SceneObjectChangeDetection)({ equals: (lhs, rhs) => lhs.equals(rhs) })
|
|
], Sector.prototype, "clipSector", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core41.SceneChangeDetection)()
|
|
], Sector.prototype, "concentricEdgeInset", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core41.SceneChangeDetection)()
|
|
], Sector.prototype, "radialEdgeInset", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core41.SceneChangeDetection)()
|
|
], Sector.prototype, "startOuterCornerRadius", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core41.SceneChangeDetection)()
|
|
], Sector.prototype, "endOuterCornerRadius", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core41.SceneChangeDetection)()
|
|
], Sector.prototype, "startInnerCornerRadius", 2);
|
|
__decorateClass([
|
|
(0, import_ag_charts_core41.SceneChangeDetection)()
|
|
], Sector.prototype, "endInnerCornerRadius", 2);
|
|
|
|
// packages/ag-charts-community/src/integrated-charts-scene.ts
|
|
var import_ag_charts_core42 = require("ag-charts-core");
|