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 ?? ""}`, `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 ?? ""] = 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 };