Files
sigpro-grid/node_modules/ag-charts-community/dist/package/integrated-charts-scene.esm.mjs
2026-03-17 08:44:54 +01:00

7203 lines
227 KiB
JavaScript
Executable File

var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
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/chart/caption.ts
import {
BaseProperties as BaseProperties2,
FONT_SIZE,
Property as Property2,
ProxyPropertyOnWrite,
createId as createId2,
isArray as isArray2,
isSegmentTruncated,
isTextTruncated,
toPlainText as toPlainText2,
toTextString as toTextString2,
wrapText,
wrapTextSegments
} from "ag-charts-core";
// packages/ag-charts-community/src/scene/node.ts
import {
DeclaredSceneChangeDetection,
Logger,
assignIfNotStrictlyEqual,
createId,
createSvgElement,
objectsEqual
} from "ag-charts-core";
// packages/ag-charts-community/src/scene/bbox.ts
import { boxContains, boxesEqual, clamp, nearestSquared } from "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 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 boxesEqual(this, other);
}
containsPoint(x, y) {
return 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 - clamp(this.x, x, end(this.x, this.width));
const dy = y - 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
import { SceneChangeDetection } from "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 = 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 = 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 = 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;
}
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 {
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 {
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) {
Logger.logGroup(
`Property changed multiple times before render: ${this.constructor.name}.${property} (${sources.length}x)`,
() => {
for (const source of sources) {
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([
DeclaredSceneChangeDetection()
], _Node.prototype, "visible", 2);
__decorateClass([
DeclaredSceneChangeDetection({
equals: objectsEqual,
changeCb: _Node.handleNodeZIndexChange
})
], _Node.prototype, "zIndex", 2);
var Node = _Node;
// packages/ag-charts-community/src/scene/shape/text.ts
import {
Debug as Debug3,
LineSplitter,
SceneRefChangeDetection,
cachedTextMeasurer,
createSvgElement as createSvgElement10,
isArray,
measureTextSegments,
toFontString,
toPlainText,
toTextString
} from "ag-charts-core";
// packages/ag-charts-community/src/scene/group.ts
import { clamp as clamp5, toIterable } from "ag-charts-core";
// packages/ag-charts-community/src/scene/canvas/hdpiOffscreenCanvas.ts
import { getOffscreenCanvas } from "ag-charts-core";
// packages/ag-charts-community/src/scene/canvas/canvasUtil.ts
import { Debug } from "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 (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 = 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 = 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
import {
DeclaredSceneChangeDetection as DeclaredSceneChangeDetection2,
DeclaredSceneObjectChangeDetection,
SceneArrayChangeDetection,
SceneObjectChangeDetection,
TRIPLE_EQ,
boxesEqual as boxesEqual2,
clamp as clamp4,
generateUUID,
isGradientFill,
isImageFill,
isPatternFill,
isString,
objectsEqual as objectsEqual2
} from "ag-charts-core";
// packages/ag-charts-community/src/scene/gradient/conicGradient.ts
import { createSvgElement as createSvgElement3, normalizeAngle360FromDegrees } from "ag-charts-core";
// packages/ag-charts-community/src/scene/gradient/gradient.ts
import { createSvgElement as createSvgElement2 } from "ag-charts-core";
// packages/ag-charts-community/src/scale/colorScale.ts
import { Color, Logger as Logger2, clamp as clamp3 } from "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
import { clamp as clamp2, readIntegratedWrappedValue } from "ag-charts-core";
function visibleTickRange(ticks, reversed, visibleRange) {
if (visibleRange == null || visibleRange[0] === 0 && visibleRange[1] === 1)
return;
const vt0 = clamp2(0, Math.floor(visibleRange[0] * ticks.length), ticks.length);
const vt1 = clamp2(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 = readIntegratedWrappedValue(domain.at(0));
const max = 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 = Color.fromString(v);
const [l, c, h] = 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 = clamp3(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 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) {
Logger2.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) {
Logger2.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) {
Logger2.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 = createSvgElement2("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 = 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 createSvgElement3("linearGradient");
}
};
// packages/ag-charts-community/src/scene/gradient/linearGradient.ts
import { createSvgElement as createSvgElement4, normalizeAngle360FromDegrees as normalizeAngle360FromDegrees2 } from "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 = normalizeAngle360FromDegrees2(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 = createSvgElement4("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
import { createSvgElement as createSvgElement5 } from "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 = createSvgElement5("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
import { BaseProperties, Logger as Logger3, Property } from "ag-charts-core";
var StopProperties = class extends BaseProperties {
constructor() {
super(...arguments);
this.color = "black";
}
};
__decorateClass([
Property
], StopProperties.prototype, "stop", 2);
__decorateClass([
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)) {
Logger3.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
import {
Logger as Logger4,
createSvgElement as createSvgElement6,
getDOMMatrix,
normalizeAngle360FromDegrees as normalizeAngle360FromDegrees3
} from "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) {
Logger4.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 = normalizeAngle360FromDegrees3(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 = 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 = createSvgElement6("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 = createSvgElement6("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 = createSvgElement6("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
import {
Logger as Logger6,
createSvgElement as createSvgElement7,
getDOMMatrix as getDOMMatrix2,
normalizeAngle360FromDegrees as normalizeAngle360FromDegrees4
} from "ag-charts-core";
// packages/ag-charts-community/src/scene/extendedPath2D.ts
import {
bezier2DDistance,
bezier2DExtrema,
evaluateBezier,
getPath2D,
lineDistanceSquared,
normalizeAngle360
} from "ag-charts-core";
// packages/ag-charts-community/src/util/svg.ts
import { Logger as Logger5 } from "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) {
Logger5.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 {
Logger5.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 = 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 ? -normalizeAngle360(sAngle - eAngle) : 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 = 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 = 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 = bezier2DDistance(cp0x, cp0y, cp1x, cp1y, cp2x, cp2y, cx, cy, x, y) ** 2;
break;
}
case 3 /* ClosePath */:
best = 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 = bezier2DExtrema(cp0x, cp0y, cp1x, cp1y, cp2x, cp2y, cp3x, cp3y);
for (const t of ts) {
const px = evaluateBezier(cp0x, cp1x, cp2x, cp3x, t);
const py = 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
import { toRadians } from "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, toRadians(130), toRadians(330));
path.arc(x + r, y - r, r, toRadians(220), 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) {
Logger6.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 = normalizeAngle360FromDegrees4(this.rotation);
const scale = 1 / pixelRatio;
const cos = Math.cos(angle) * scale;
const sin = Math.sin(angle) * scale;
const DOMMatrixCtor = getDOMMatrix2();
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 = createSvgElement7("pattern");
pattern.setAttribute("viewBox", `0 0 ${width} ${height}`);
pattern.setAttribute("width", String(width));
pattern.setAttribute("height", String(height));
pattern.setAttribute("patternUnits", "userSpaceOnUse");
const rect = createSvgElement7("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 = createSvgElement7("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 (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 (isPatternFill(fill))
return this.createPattern(fill);
}
createPattern(fill) {
return new Pattern(fill);
}
getImage(fill) {
if (isImageFill(fill))
return this.createImage(fill);
}
createImage(fill) {
return new Image(this.imageLoader, fill);
}
onFillChange() {
if (typeof this.fill === "object") {
if (objectsEqual2(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 (isGradientFill(fill) && this.fillGradient) {
defs ?? (defs = []);
const gradient = this.fillGradient.toSvg(this.fillBBox ?? this.getBBox());
const id = generateUUID();
gradient.setAttribute("id", id);
defs.push(gradient);
element.setAttribute("fill", `url(#${id})`);
} else if (isPatternFill(fill) && this.fillPattern) {
defs ?? (defs = []);
const pattern = this.fillPattern.toSvg();
const id = generateUUID();
pattern.setAttribute("id", id);
defs.push(pattern);
element.setAttribute("fill", `url(#${id})`);
} else if (isImageFill(fill) && this.fillImage) {
defs ?? (defs = []);
const pixelRatio = this.layerManager?.canvas?.pixelRatio ?? 1;
const pattern = this.fillImage.toSvg(this.getBBox(), pixelRatio);
const id = 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: 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 || !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([
DeclaredSceneChangeDetection2()
], _Shape.prototype, "drawingMode", 2);
__decorateClass([
DeclaredSceneChangeDetection2()
], _Shape.prototype, "fillOpacity", 2);
__decorateClass([
DeclaredSceneChangeDetection2()
], _Shape.prototype, "strokeOpacity", 2);
__decorateClass([
DeclaredSceneObjectChangeDetection({
equals: objectsEqual2,
changeCb: _Shape.handleFillChange
})
], _Shape.prototype, "fill", 2);
__decorateClass([
SceneObjectChangeDetection({ equals: objectsEqual2, changeCb: _Shape.handleStrokeChange })
], _Shape.prototype, "stroke", 2);
__decorateClass([
DeclaredSceneChangeDetection2()
], _Shape.prototype, "strokeWidth", 2);
__decorateClass([
SceneArrayChangeDetection()
], _Shape.prototype, "lineDash", 2);
__decorateClass([
DeclaredSceneChangeDetection2()
], _Shape.prototype, "lineDashOffset", 2);
__decorateClass([
DeclaredSceneChangeDetection2()
], _Shape.prototype, "lineCap", 2);
__decorateClass([
DeclaredSceneChangeDetection2()
], _Shape.prototype, "lineJoin", 2);
__decorateClass([
DeclaredSceneChangeDetection2()
], _Shape.prototype, "miterLimit", 2);
__decorateClass([
DeclaredSceneChangeDetection2({ convertor: (v) => clamp4(0, v ?? 1, 1) })
], _Shape.prototype, "opacity", 2);
__decorateClass([
SceneObjectChangeDetection({ equals: TRIPLE_EQ, checkDirtyOnAssignment: true })
], _Shape.prototype, "fillShadow", 2);
__decorateClass([
DeclaredSceneObjectChangeDetection({ equals: boxesEqual2, changeCb: (s) => s.onFillChange() })
], _Shape.prototype, "fillBBox", 2);
__decorateClass([
DeclaredSceneObjectChangeDetection({ equals: objectsEqual2, changeCb: (s) => s.onFillChange() })
], _Shape.prototype, "fillParams", 2);
var Shape = _Shape;
// packages/ag-charts-community/src/scene/transformable.ts
import { createSvgElement as createSvgElement8 } from "ag-charts-core";
// packages/ag-charts-community/src/scene/matrix.ts
import { isNumberEqual } from "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 isNumberEqual(e[0], 1) && isNumberEqual(e[1], 0) && isNumberEqual(e[2], 0) && isNumberEqual(e[3], 1) && isNumberEqual(e[4], 0) && 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 = createSvgElement8("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([
SceneChangeDetection()
], RotatableInternal.prototype, "rotationCenterX", 2);
__decorateClass([
SceneChangeDetection()
], RotatableInternal.prototype, "rotationCenterY", 2);
__decorateClass([
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([
SceneChangeDetection()
], ScalableInternal.prototype, "scalingX", 2);
__decorateClass([
SceneChangeDetection()
], ScalableInternal.prototype, "scalingY", 2);
__decorateClass([
SceneChangeDetection()
], ScalableInternal.prototype, "scalingCenterX", 2);
__decorateClass([
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([
SceneChangeDetection()
], TranslatableInternal.prototype, "translationX", 2);
__decorateClass([
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 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([
SceneChangeDetection({ convertor: (v) => clamp5(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
import { Debug as Debug2, DebugMetrics, Logger as Logger7, TextMeasurer, getWindow, isString as isString2, toArray } from "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 (!Debug2.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;
Logger7.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 (!Debug2.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 = Debug2.check("scene:stats:verbose" /* SCENE_STATS_VERBOSE */);
const memUsage = detailedStats ? memoryUsage() : null;
const metrics = detailedStats ? 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(isString2);
const measurer = new 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) {
Logger7.warnOnce("Error during debug stats rendering", e);
} finally {
ctx.restore();
}
}
function prepareSceneNodeHighlight(ctx) {
const config = toArray(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) {
Logger7.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) {
Logger7.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 (!Debug2.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
import { DeclaredSceneChangeDetection as DeclaredSceneChangeDetection3, boxesEqual as boxesEqual3, isNumberEqual as isNumberEqual2 } from "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
import { SceneChangeDetection as SceneChangeDetection2, createSvgElement as createSvgElement9 } from "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 = createSvgElement9("path");
element.setAttribute("d", this.svgPathData());
const defs = this.applySvgFillAttributes(element, []);
this.applySvgStrokeAttributes(element);
return {
elements: [element],
defs
};
}
};
Path.className = "Path";
__decorateClass([
SceneChangeDetection2()
], Path.prototype, "clip", 2);
__decorateClass([
SceneChangeDetection2()
], Path.prototype, "clipX", 1);
__decorateClass([
SceneChangeDetection2()
], 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 (isNumberEqual2(leading0, 0)) {
leading0 = 0;
}
if (trailing1 > trailingEdge) {
trailingClipped = true;
trailing0 = trailingInset - Math.sqrt(Math.max(cornerRadius ** 2 - (leadingInset - trailingEdge) ** 2));
trailing1 = trailingEdge;
} else if (isNumberEqual2(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([
DeclaredSceneChangeDetection3()
], Rect.prototype, "x", 2);
__decorateClass([
DeclaredSceneChangeDetection3()
], Rect.prototype, "y", 2);
__decorateClass([
DeclaredSceneChangeDetection3()
], Rect.prototype, "width", 2);
__decorateClass([
DeclaredSceneChangeDetection3()
], Rect.prototype, "height", 2);
__decorateClass([
DeclaredSceneChangeDetection3()
], Rect.prototype, "topLeftCornerRadius", 2);
__decorateClass([
DeclaredSceneChangeDetection3()
], Rect.prototype, "topRightCornerRadius", 2);
__decorateClass([
DeclaredSceneChangeDetection3()
], Rect.prototype, "bottomRightCornerRadius", 2);
__decorateClass([
DeclaredSceneChangeDetection3()
], Rect.prototype, "bottomLeftCornerRadius", 2);
__decorateClass([
DeclaredSceneChangeDetection3({ equals: boxesEqual3 })
], Rect.prototype, "clipBBox", 2);
__decorateClass([
DeclaredSceneChangeDetection3()
], 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 (isArray(this.text)) {
this.lines = [];
this.richText ?? (this.richText = new Group());
this.richText.setScene(this.scene);
this.richText.append(
this.text.flatMap((s) => toTextString(s.text).split(LineSplitter)).filter(Boolean).map(() => new _Text({ trimText: false }))
);
} else {
const lines = toTextString(this.text).split(LineSplitter);
this.lines = this.trimText ? lines.map((line) => line.trim()) : lines;
}
}
get font() {
this.fontCache ?? (this.fontCache = toFontString(this));
return this.fontCache;
}
static measureBBox(text, x, y, options) {
if (isArray(text)) {
const { font, lineHeight, textAlign, textBaseline } = options;
const { width, height, lineMetrics } = 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(toTextString(text).split(LineSplitter), x, y, options);
}
}
static computeBBox(lines, x, y, opts) {
const { font, lineHeight, textAlign, textBaseline } = opts;
const { width, height, lineMetrics } = 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, 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 || !isArray(this.text))
return bbox;
const { height, lineMetrics } = 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 toPlainText(this.text);
}
isPointInPath(x, y) {
return this.getBBox()?.containsPoint(x, y) ?? false;
}
setScene(scene) {
this.richText?.setScene(scene);
super.setScene(scene);
}
generateTextMap() {
if (!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 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 (isArray(this.text) && this.richText) {
this.generateTextMap();
const richTextBBox = this.richText.getBBox();
const { width, height, lineMetrics } = 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 = 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 = createSvgElement10("text");
if (isArray(text)) {
for (const segment of text) {
const segmentElement = createSvgElement10("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 = 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 = toTextString(text);
}
return { elements: [element] };
}
hasRenderableText() {
const { text } = this;
if (text == null) {
return false;
}
return isArray(text) ? true : toTextString(text) !== "";
}
};
_Text.className = "Text";
_Text.debug = Debug3.create(true, "scene:text" /* SCENE_TEXT */);
_Text.defaultFontSize = 10;
__decorateClass([
SceneChangeDetection()
], _Text.prototype, "x", 2);
__decorateClass([
SceneChangeDetection()
], _Text.prototype, "y", 2);
__decorateClass([
SceneRefChangeDetection({
changeCb: (o) => o.onTextChange()
})
], _Text.prototype, "text", 2);
__decorateClass([
SceneChangeDetection({
changeCb: (o) => {
o.fontCache = void 0;
}
})
], _Text.prototype, "fontStyle", 2);
__decorateClass([
SceneChangeDetection({
changeCb: (o) => {
o.fontCache = void 0;
}
})
], _Text.prototype, "fontWeight", 2);
__decorateClass([
SceneChangeDetection({
changeCb: (o) => {
o.fontCache = void 0;
}
})
], _Text.prototype, "fontSize", 2);
__decorateClass([
SceneChangeDetection({
changeCb: (o) => {
o.fontCache = void 0;
}
})
], _Text.prototype, "fontFamily", 2);
__decorateClass([
SceneChangeDetection()
], _Text.prototype, "textAlign", 2);
__decorateClass([
SceneChangeDetection()
], _Text.prototype, "textBaseline", 2);
__decorateClass([
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 BaseProperties2 {
constructor() {
super(...arguments);
this.id = createId2(this);
this.node = new RotatableText({ zIndex: 1 }).setProperties({
textAlign: "center",
pointerEvents: 1 /* None */
});
this.enabled = false;
this.textAlign = "center";
this.fontSize = 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 (isArray2(text)) {
wrappedText = wrapTextSegments(text, options);
this.truncated = wrappedText.some(isSegmentTruncated);
} else {
wrappedText = wrapText(toTextString2(text), options);
this.truncated = 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 = toPlainText2(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: toPlainText2(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([
Property2,
ProxyPropertyOnWrite("node", "visible")
], Caption.prototype, "enabled", 2);
__decorateClass([
Property2,
ProxyPropertyOnWrite("node")
], Caption.prototype, "text", 2);
__decorateClass([
Property2,
ProxyPropertyOnWrite("node")
], Caption.prototype, "textAlign", 2);
__decorateClass([
Property2,
ProxyPropertyOnWrite("node")
], Caption.prototype, "fontStyle", 2);
__decorateClass([
Property2,
ProxyPropertyOnWrite("node")
], Caption.prototype, "fontWeight", 2);
__decorateClass([
Property2,
ProxyPropertyOnWrite("node")
], Caption.prototype, "fontSize", 2);
__decorateClass([
Property2,
ProxyPropertyOnWrite("node")
], Caption.prototype, "fontFamily", 2);
__decorateClass([
Property2,
ProxyPropertyOnWrite("node", "fill")
], Caption.prototype, "color", 2);
__decorateClass([
Property2
], Caption.prototype, "spacing", 2);
__decorateClass([
Property2
], Caption.prototype, "maxWidth", 2);
__decorateClass([
Property2
], Caption.prototype, "maxHeight", 2);
__decorateClass([
Property2
], Caption.prototype, "wrapping", 2);
__decorateClass([
Property2
], Caption.prototype, "padding", 2);
__decorateClass([
Property2
], Caption.prototype, "layoutStyle", 2);
// packages/ag-charts-community/src/chart/marker/marker.ts
import { DeclaredSceneChangeDetection as DeclaredSceneChangeDetection4, DeclaredSceneObjectChangeDetection as DeclaredSceneObjectChangeDetection2, TRIPLE_EQ as TRIPLE_EQ2 } from "ag-charts-core";
// packages/ag-charts-community/src/chart/marker/shapes.ts
import { toRadians as toRadians2 } from "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, toRadians2(130), toRadians2(330));
path.arc(x + r, y - r, r, toRadians2(220), toRadians2(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([
DeclaredSceneObjectChangeDetection2({ equals: TRIPLE_EQ2 })
], InternalMarker.prototype, "shape", 2);
__decorateClass([
DeclaredSceneChangeDetection4()
], InternalMarker.prototype, "x", 2);
__decorateClass([
DeclaredSceneChangeDetection4()
], InternalMarker.prototype, "y", 2);
__decorateClass([
DeclaredSceneChangeDetection4({ 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
import { clamp as clamp7, dateToNumber, previousPowerOf2 } from "ag-charts-core";
// packages/ag-charts-community/src/scale/bandScale.ts
import { Logger as Logger8, clamp as clamp6 } from "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 = clamp6(0, value, 1);
this._paddingInner = value;
this._paddingOuter = value;
}
get padding() {
return this._paddingInner;
}
set paddingInner(value) {
this.invalid = true;
this._paddingInner = clamp6(0, value, 1);
}
get paddingInner() {
return this._paddingInner;
}
set paddingOuter(value) {
this.invalid = true;
this._paddingOuter = clamp6(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) {
Logger8.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 clamp6(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 = previousPowerOf2(step);
if (step <= 1) {
return filterVisibleTicks(domain, false, visibleRange);
}
tickCount = Math.trunc(bands.length / step);
const span = step * tickCount;
const inset = previousPowerOf2(Math.trunc((bands.length - span) / 2));
const vt0 = clamp7(0, Math.floor((visibleRange?.[0] ?? 0) * bands.length), bands.length);
const vt1 = clamp7(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(dateToNumber(domain[i]), i);
}
this.indexInitialized = true;
}
return index.get(dateToNumber(value));
}
};
function deduplicateCategories(d) {
let domain;
const uniqueValues = /* @__PURE__ */ new Set();
for (const value of d) {
const key = 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
import { createTicks, isDenseInterval, niceTicksDomain, range, tickStep } from "ag-charts-core";
// packages/ag-charts-community/src/scale/continuousScale.ts
import { findMinMax } from "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] = 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 ?? 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 (!isDenseInterval((d1 - d0) / step, this.getPixelRange())) {
return range(d0, d1, step, visibleRange);
}
}
return 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] = 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
import { CleanupRegistry, Debug as Debug5, EventEmitter as EventEmitter2, Logger as Logger9, createId as createId3, downloadUrl } from "ag-charts-core";
// packages/ag-charts-community/src/scene/canvas/hdpiCanvas.ts
import { ObserveChanges, createElement, getWindow as getWindow2 } from "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 ?? getWindow2("devicePixelRatio") ?? 1;
this.element = canvasElement ?? 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([
ObserveChanges((target) => target.onEnabledChange())
], HdpiCanvas.prototype, "enabled", 2);
// packages/ag-charts-community/src/scene/image/imageLoader.ts
import { EventEmitter, getImage } from "ag-charts-core";
var ImageLoader = class extends 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 = 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
import { Debug as Debug4 } from "ag-charts-core";
var LayersManager = class {
constructor(canvas) {
this.canvas = canvas;
this.debug = Debug4.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 EventEmitter2 {
constructor(canvasOptions) {
super();
this.debug = Debug5.create(true, "scene" /* SCENE */);
this.id = createId3(this);
this.imageLoader = new ImageLoader();
this.root = null;
this.pendingSize = null;
this.isDirty = false;
this.cleanup = new 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 }) => {
Logger9.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() {
Debug5.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) {
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 = Debug5.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 (Debug5.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 && Debug5.check("scene:dirtyTree" /* SCENE_DIRTY_TREE */)) {
const { dirtyTree, paths } = buildDirtyTree(root);
Debug5.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
import { SceneChangeDetection as SceneChangeDetection3, isNumberEqual as isNumberEqual3, normalizeAngle360 as normalizeAngle3602 } from "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 isNumberEqual3(normalizeAngle3602(this.startAngle), normalizeAngle3602(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([
SceneChangeDetection3()
], Arc.prototype, "centerX", 2);
__decorateClass([
SceneChangeDetection3()
], Arc.prototype, "centerY", 2);
__decorateClass([
SceneChangeDetection3()
], Arc.prototype, "radius", 2);
__decorateClass([
SceneChangeDetection3()
], Arc.prototype, "startAngle", 2);
__decorateClass([
SceneChangeDetection3()
], Arc.prototype, "endAngle", 2);
__decorateClass([
SceneChangeDetection3()
], Arc.prototype, "counterClockwise", 2);
__decorateClass([
SceneChangeDetection3()
], Arc.prototype, "type", 2);
// packages/ag-charts-community/src/scene/shape/line.ts
import { createSvgElement as createSvgElement11, lineDistanceSquared as lineDistanceSquared2 } from "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 lineDistanceSquared2(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 = createSvgElement11("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([
SceneChangeDetection()
], Line.prototype, "x1", 2);
__decorateClass([
SceneChangeDetection()
], Line.prototype, "y1", 2);
__decorateClass([
SceneChangeDetection()
], Line.prototype, "x2", 2);
__decorateClass([
SceneChangeDetection()
], Line.prototype, "y2", 2);
// packages/ag-charts-community/src/scene/shape/radialColumnShape.ts
import { SceneChangeDetection as SceneChangeDetection4, angleBetween, isNumberEqual as isNumberEqual4, normalizeAngle360 as normalizeAngle3603 } from "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 = angleBetween(startAngle, endAngle);
return normalizeAngle3603(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 = isNumberEqual4(innerRadius, axisInnerRadius);
const isTouchingOuter = isNumberEqual4(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([
SceneChangeDetection4()
], RadialColumnShape.prototype, "isBeveled", 2);
__decorateClass([
SceneChangeDetection4()
], RadialColumnShape.prototype, "columnWidth", 2);
__decorateClass([
SceneChangeDetection4()
], RadialColumnShape.prototype, "startAngle", 2);
__decorateClass([
SceneChangeDetection4()
], RadialColumnShape.prototype, "endAngle", 2);
__decorateClass([
SceneChangeDetection4()
], RadialColumnShape.prototype, "outerRadius", 2);
__decorateClass([
SceneChangeDetection4()
], RadialColumnShape.prototype, "innerRadius", 2);
__decorateClass([
SceneChangeDetection4()
], RadialColumnShape.prototype, "axisInnerRadius", 2);
__decorateClass([
SceneChangeDetection4()
], RadialColumnShape.prototype, "axisOuterRadius", 2);
function getRadialColumnWidth(startAngle, endAngle, axisOuterRadius, columnWidthRatio, maxColumnWidthRatio) {
const rotation = 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
import { SceneChangeDetection as SceneChangeDetection5, SceneObjectChangeDetection as SceneObjectChangeDetection2 } from "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
import { angleBetween as angleBetween2, isBetweenAngles, normalizeAngle180, normalizeAngle360 as normalizeAngle3604 } from "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 (isBetweenAngles(0, startAngle, endAngle)) {
addPoint(outerRadius, 0);
}
if (isBetweenAngles(Math.PI * 0.5, startAngle, endAngle)) {
addPoint(0, outerRadius);
}
if (isBetweenAngles(Math.PI, startAngle, endAngle)) {
addPoint(-outerRadius, 0);
}
if (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 = normalizeAngle180(sector.startAngle);
const endAngle = 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 (angleBetween2(angle, relativeToStartAngle) < delta2) {
return relativeToStartAngle;
} else {
return normalizeAngle3604(angle - relativeToStartAngle) + relativeToStartAngle;
}
}
function clockwiseAngles(startAngle, endAngle, relativeToStartAngle = 0) {
const fullPie = Math.abs(endAngle - startAngle) >= 2 * Math.PI;
const sweepAngle = fullPie ? 2 * Math.PI : normalizeAngle3604(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([
SceneChangeDetection5()
], Sector.prototype, "centerX", 2);
__decorateClass([
SceneChangeDetection5()
], Sector.prototype, "centerY", 2);
__decorateClass([
SceneChangeDetection5()
], Sector.prototype, "innerRadius", 2);
__decorateClass([
SceneChangeDetection5()
], Sector.prototype, "outerRadius", 2);
__decorateClass([
SceneChangeDetection5()
], Sector.prototype, "startAngle", 2);
__decorateClass([
SceneChangeDetection5()
], Sector.prototype, "endAngle", 2);
__decorateClass([
SceneObjectChangeDetection2({ equals: (lhs, rhs) => lhs.equals(rhs) })
], Sector.prototype, "clipSector", 2);
__decorateClass([
SceneChangeDetection5()
], Sector.prototype, "concentricEdgeInset", 2);
__decorateClass([
SceneChangeDetection5()
], Sector.prototype, "radialEdgeInset", 2);
__decorateClass([
SceneChangeDetection5()
], Sector.prototype, "startOuterCornerRadius", 2);
__decorateClass([
SceneChangeDetection5()
], Sector.prototype, "endOuterCornerRadius", 2);
__decorateClass([
SceneChangeDetection5()
], Sector.prototype, "startInnerCornerRadius", 2);
__decorateClass([
SceneChangeDetection5()
], Sector.prototype, "endInnerCornerRadius", 2);
// packages/ag-charts-community/src/integrated-charts-scene.ts
import { toRadians as toRadians3 } from "ag-charts-core";
export {
Arc,
BBox,
Caption,
CategoryScale,
Group,
Line,
LinearScale,
Marker,
Path,
RadialColumnShape,
Rect,
Scene,
Sector,
Shape,
TranslatableGroup,
getRadialColumnWidth,
toRadians3 as toRadians
};