114789 lines
3.9 MiB
Executable File
114789 lines
3.9 MiB
Executable File
(function (g, f) {
|
|
if ("object" == typeof exports && "object" == typeof module) {
|
|
module.exports = f();
|
|
} else if ("function" == typeof define && define.amd) {
|
|
define("agCharts", [], f);
|
|
} else if ("object" == typeof exports) {
|
|
exports["agCharts"] = f();
|
|
} else {
|
|
g["agCharts"] = f();
|
|
}
|
|
}(this, () => {
|
|
var exports = {};
|
|
var module = { exports };
|
|
if (typeof require === 'undefined') {
|
|
function require(name) {
|
|
|
|
throw new Error('Unknown module: ' + name);
|
|
}
|
|
}
|
|
|
|
"use strict";
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from3, except, desc) => {
|
|
if (from3 && typeof from3 === "object" || typeof from3 === "function") {
|
|
for (let key of __getOwnPropNames(from3))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from3[key], enumerable: !(desc = __getOwnPropDesc(from3, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
if (decorator = decorators[i])
|
|
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
if (kind && result)
|
|
__defProp(target, key, result);
|
|
return result;
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/main-umd.ts
|
|
var main_umd_exports = {};
|
|
__export(main_umd_exports, {
|
|
AG_CHARTS_LOCALE_EN_US: () => AG_CHARTS_LOCALE_EN_US,
|
|
AgCharts: () => AgCharts,
|
|
AgChartsCommunityModule: () => AgChartsCommunityModule,
|
|
AgChartsEnterpriseModule: () => AgChartsEnterpriseModule,
|
|
AgTooltipAnchorToType: () => AgTooltipAnchorToType,
|
|
AgTooltipPlacementType: () => AgTooltipPlacementType,
|
|
AllCartesianAxesModule: () => AllCartesianAxesModule2,
|
|
AllCartesianModule: () => AllCartesianModule2,
|
|
AllCartesianSeriesModule: () => AllCartesianSeriesModule2,
|
|
AllCommunityModule: () => AllCommunityModule,
|
|
AllEnterpriseModule: () => AllEnterpriseModule,
|
|
AllGaugeModule: () => AllGaugeModule,
|
|
AllMapSeriesModule: () => AllMapSeriesModule,
|
|
AllPolarModule: () => AllPolarModule2,
|
|
AngleCategoryAxisModule: () => AngleCategoryAxisModule,
|
|
AngleNumberAxisModule: () => AngleNumberAxisModule,
|
|
AnimationModule: () => AnimationModule,
|
|
AnnotationsModule: () => AnnotationsModule,
|
|
AreaSeriesModule: () => AreaSeriesModule,
|
|
BandHighlightModule: () => BandHighlightModule,
|
|
BarSeriesModule: () => BarSeriesModule,
|
|
BoxPlotSeriesModule: () => BoxPlotSeriesModule,
|
|
BubbleSeriesModule: () => BubbleSeriesModule,
|
|
CandlestickSeriesModule: () => CandlestickSeriesModule,
|
|
CartesianChartModule: () => CartesianChartModule,
|
|
CategoryAxisModule: () => CategoryAxisModule,
|
|
ChartToolbarModule: () => ChartToolbarModule,
|
|
ChordSeriesModule: () => ChordSeriesModule,
|
|
ConeFunnelSeriesModule: () => ConeFunnelSeriesModule,
|
|
ContextMenuModule: () => ContextMenuModule,
|
|
CrosshairModule: () => CrosshairModule,
|
|
DataSourceModule: () => DataSourceModule,
|
|
DonutSeriesModule: () => DonutSeriesModule,
|
|
ErrorBarsModule: () => ErrorBarsModule,
|
|
FinancialChartModule: () => FinancialChartModule,
|
|
FlashOnUpdateModule: () => FlashOnUpdateModule,
|
|
FunnelSeriesModule: () => FunnelSeriesModule,
|
|
GradientLegendModule: () => GradientLegendModule,
|
|
GroupedCategoryAxisModule: () => GroupedCategoryAxisModule,
|
|
HeatmapSeriesModule: () => HeatmapSeriesModule,
|
|
HistogramSeriesModule: () => HistogramSeriesModule,
|
|
LegendModule: () => LegendModule,
|
|
LicenseManager: () => LicenseManager2,
|
|
LineSeriesModule: () => LineSeriesModule,
|
|
LinearGaugeModule: () => LinearGaugeModule,
|
|
LocaleModule: () => LocaleModule,
|
|
LogAxisModule: () => LogAxisModule,
|
|
MapLineBackgroundSeriesModule: () => MapLineBackgroundSeriesModule,
|
|
MapLineSeriesModule: () => MapLineSeriesModule,
|
|
MapMarkerSeriesModule: () => MapMarkerSeriesModule,
|
|
MapShapeBackgroundSeriesModule: () => MapShapeBackgroundSeriesModule,
|
|
MapShapeSeriesModule: () => MapShapeSeriesModule,
|
|
ModuleRegistry: () => moduleRegistry_exports,
|
|
NavigatorModule: () => NavigatorModule,
|
|
NightingaleSeriesModule: () => NightingaleSeriesModule,
|
|
NumberAxisModule: () => NumberAxisModule,
|
|
OhlcSeriesModule: () => OhlcSeriesModule,
|
|
OrdinalTimeAxisModule: () => OrdinalTimeAxisModule,
|
|
PieSeriesModule: () => PieSeriesModule,
|
|
PolarChartModule: () => PolarChartModule,
|
|
PyramidSeriesModule: () => PyramidSeriesModule,
|
|
RadarAreaSeriesModule: () => RadarAreaSeriesModule,
|
|
RadarLineSeriesModule: () => RadarLineSeriesModule,
|
|
RadialBarSeriesModule: () => RadialBarSeriesModule,
|
|
RadialColumnSeriesModule: () => RadialColumnSeriesModule,
|
|
RadialGaugeModule: () => RadialGaugeModule,
|
|
RadiusCategoryAxisModule: () => RadiusCategoryAxisModule,
|
|
RadiusNumberAxisModule: () => RadiusNumberAxisModule,
|
|
RangeAreaSeriesModule: () => RangeAreaSeriesModule,
|
|
RangeBarSeriesModule: () => RangeBarSeriesModule,
|
|
RangesModule: () => RangesModule,
|
|
SankeySeriesModule: () => SankeySeriesModule,
|
|
ScatterSeriesModule: () => ScatterSeriesModule,
|
|
ScrollbarModule: () => ScrollbarModule,
|
|
SparklinePresetModule: () => SparklinePresetModule,
|
|
StatusBarModule: () => StatusBarModule,
|
|
SunburstSeriesModule: () => SunburstSeriesModule,
|
|
SyncModule: () => SyncModule,
|
|
TimeAxisModule: () => TimeAxisModule,
|
|
TreemapSeriesModule: () => TreemapSeriesModule,
|
|
UnitTimeAxisModule: () => UnitTimeAxisModule,
|
|
VERSION: () => VERSION,
|
|
WaterfallSeriesModule: () => WaterfallSeriesModule,
|
|
ZoomModule: () => ZoomModule,
|
|
_ModuleSupport: () => module_support_exports,
|
|
_Scene: () => integrated_charts_scene_exports,
|
|
_Theme: () => integrated_charts_theme_exports,
|
|
_Util: () => integrated_charts_util_exports,
|
|
_Widget: () => exports_exports,
|
|
time: () => time
|
|
});
|
|
module.exports = __toCommonJS(main_umd_exports);
|
|
|
|
// packages/ag-charts-core/src/main.ts
|
|
var main_exports = {};
|
|
__export(main_exports, {
|
|
AGGREGATION_INDEX_UNSET: () => AGGREGATION_INDEX_UNSET,
|
|
AGGREGATION_INDEX_X_MAX: () => AGGREGATION_INDEX_X_MAX,
|
|
AGGREGATION_INDEX_X_MIN: () => AGGREGATION_INDEX_X_MIN,
|
|
AGGREGATION_INDEX_Y_MAX: () => AGGREGATION_INDEX_Y_MAX,
|
|
AGGREGATION_INDEX_Y_MIN: () => AGGREGATION_INDEX_Y_MIN,
|
|
AGGREGATION_MAX_POINTS: () => AGGREGATION_MAX_POINTS,
|
|
AGGREGATION_MIN_RANGE: () => AGGREGATION_MIN_RANGE,
|
|
AGGREGATION_SPAN: () => AGGREGATION_SPAN,
|
|
AGGREGATION_THRESHOLD: () => AGGREGATION_THRESHOLD,
|
|
AbstractModuleInstance: () => AbstractModuleInstance,
|
|
ActionOnSet: () => ActionOnSet,
|
|
AdjacencyListGraph: () => AdjacencyListGraph,
|
|
AsyncAwaitQueue: () => AsyncAwaitQueue,
|
|
BASE_FONT_SIZE: () => BASE_FONT_SIZE,
|
|
BREAK_TRANSFORM_CHAIN: () => BREAK_TRANSFORM_CHAIN,
|
|
BaseProperties: () => BaseProperties,
|
|
Border: () => Border,
|
|
CANVAS_HEIGHT: () => CANVAS_HEIGHT,
|
|
CANVAS_TO_BUFFER_DEFAULTS: () => CANVAS_TO_BUFFER_DEFAULTS,
|
|
CANVAS_WIDTH: () => CANVAS_WIDTH,
|
|
CARTESIAN_AXIS_TYPE: () => CARTESIAN_AXIS_TYPE,
|
|
CARTESIAN_POSITION: () => CARTESIAN_POSITION,
|
|
CallbackCache: () => CallbackCache,
|
|
ChangeDetectableProperties: () => ChangeDetectableProperties,
|
|
ChartAxisDirection: () => ChartAxisDirection,
|
|
ChartUpdateType: () => ChartUpdateType,
|
|
CleanupRegistry: () => CleanupRegistry,
|
|
Color: () => Color,
|
|
ConfiguredCanvasMixin: () => ConfiguredCanvasMixin,
|
|
DEFAULT_ANNOTATION_HANDLE_FILL: () => DEFAULT_ANNOTATION_HANDLE_FILL,
|
|
DEFAULT_ANNOTATION_STATISTICS_COLOR: () => DEFAULT_ANNOTATION_STATISTICS_COLOR,
|
|
DEFAULT_ANNOTATION_STATISTICS_DIVIDER_STROKE: () => DEFAULT_ANNOTATION_STATISTICS_DIVIDER_STROKE,
|
|
DEFAULT_ANNOTATION_STATISTICS_DOWN_FILL: () => DEFAULT_ANNOTATION_STATISTICS_DOWN_FILL,
|
|
DEFAULT_ANNOTATION_STATISTICS_DOWN_STROKE: () => DEFAULT_ANNOTATION_STATISTICS_DOWN_STROKE,
|
|
DEFAULT_ANNOTATION_STATISTICS_FILL: () => DEFAULT_ANNOTATION_STATISTICS_FILL,
|
|
DEFAULT_ANNOTATION_STATISTICS_STROKE: () => DEFAULT_ANNOTATION_STATISTICS_STROKE,
|
|
DEFAULT_CAPTION_ALIGNMENT: () => DEFAULT_CAPTION_ALIGNMENT,
|
|
DEFAULT_CAPTION_LAYOUT_STYLE: () => DEFAULT_CAPTION_LAYOUT_STYLE,
|
|
DEFAULT_FIBONACCI_STROKES: () => DEFAULT_FIBONACCI_STROKES,
|
|
DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL: () => DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL,
|
|
DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR: () => DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR,
|
|
DEFAULT_POLAR_SERIES_STROKE: () => DEFAULT_POLAR_SERIES_STROKE,
|
|
DEFAULT_SHADOW_COLOUR: () => DEFAULT_SHADOW_COLOUR,
|
|
DEFAULT_SPARKLINE_CROSSHAIR_STROKE: () => DEFAULT_SPARKLINE_CROSSHAIR_STROKE,
|
|
DEFAULT_TEXTBOX_COLOR: () => DEFAULT_TEXTBOX_COLOR,
|
|
DEFAULT_TEXTBOX_FILL: () => DEFAULT_TEXTBOX_FILL,
|
|
DEFAULT_TEXTBOX_STROKE: () => DEFAULT_TEXTBOX_STROKE,
|
|
DEFAULT_TEXT_ANNOTATION_COLOR: () => DEFAULT_TEXT_ANNOTATION_COLOR,
|
|
DEFAULT_TOOLBAR_POSITION: () => DEFAULT_TOOLBAR_POSITION,
|
|
DIRECTION_SWAP_AXES: () => DIRECTION_SWAP_AXES,
|
|
Debug: () => debugLogger_exports,
|
|
DebugMetrics: () => debugMetrics_exports,
|
|
DeclaredSceneChangeDetection: () => DeclaredSceneChangeDetection,
|
|
DeclaredSceneObjectChangeDetection: () => DeclaredSceneObjectChangeDetection,
|
|
Deprecated: () => Deprecated,
|
|
DeprecatedAndRenamedTo: () => DeprecatedAndRenamedTo,
|
|
EllipsisChar: () => EllipsisChar,
|
|
ErrorType: () => ErrorType,
|
|
EventEmitter: () => EventEmitter,
|
|
FILL_GRADIENT_BLANK_DEFAULTS: () => FILL_GRADIENT_BLANK_DEFAULTS,
|
|
FILL_GRADIENT_CONIC_SERIES_DEFAULTS: () => FILL_GRADIENT_CONIC_SERIES_DEFAULTS,
|
|
FILL_GRADIENT_LINEAR_DEFAULTS: () => FILL_GRADIENT_LINEAR_DEFAULTS,
|
|
FILL_GRADIENT_LINEAR_HIERARCHY_DEFAULTS: () => FILL_GRADIENT_LINEAR_HIERARCHY_DEFAULTS,
|
|
FILL_GRADIENT_LINEAR_KEYED_DEFAULTS: () => FILL_GRADIENT_LINEAR_KEYED_DEFAULTS,
|
|
FILL_GRADIENT_LINEAR_SINGLE_DEFAULTS: () => FILL_GRADIENT_LINEAR_SINGLE_DEFAULTS,
|
|
FILL_GRADIENT_RADIAL_DEFAULTS: () => FILL_GRADIENT_RADIAL_DEFAULTS,
|
|
FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS: () => FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS,
|
|
FILL_GRADIENT_RADIAL_REVERSED_SERIES_DEFAULTS: () => FILL_GRADIENT_RADIAL_REVERSED_SERIES_DEFAULTS,
|
|
FILL_GRADIENT_RADIAL_SERIES_DEFAULTS: () => FILL_GRADIENT_RADIAL_SERIES_DEFAULTS,
|
|
FILL_IMAGE_BLANK_DEFAULTS: () => FILL_IMAGE_BLANK_DEFAULTS,
|
|
FILL_IMAGE_DEFAULTS: () => FILL_IMAGE_DEFAULTS,
|
|
FILL_PATTERN_BLANK_DEFAULTS: () => FILL_PATTERN_BLANK_DEFAULTS,
|
|
FILL_PATTERN_DEFAULTS: () => FILL_PATTERN_DEFAULTS,
|
|
FILL_PATTERN_HIERARCHY_DEFAULTS: () => FILL_PATTERN_HIERARCHY_DEFAULTS,
|
|
FILL_PATTERN_KEYED_DEFAULTS: () => FILL_PATTERN_KEYED_DEFAULTS,
|
|
FILL_PATTERN_SINGLE_DEFAULTS: () => FILL_PATTERN_SINGLE_DEFAULTS,
|
|
FONT_SIZE: () => FONT_SIZE,
|
|
FONT_SIZE_RATIO: () => FONT_SIZE_RATIO,
|
|
IS_DARK_THEME: () => IS_DARK_THEME,
|
|
InterpolationProperties: () => InterpolationProperties,
|
|
LABEL_BOXING_DEFAULTS: () => LABEL_BOXING_DEFAULTS,
|
|
LEGEND_CONTAINER_THEME: () => LEGEND_CONTAINER_THEME,
|
|
LRUCache: () => LRUCache,
|
|
LineSplitter: () => LineSplitter,
|
|
Logger: () => logger_exports,
|
|
MARKER_SERIES_HIGHLIGHT_STYLE: () => MARKER_SERIES_HIGHLIGHT_STYLE,
|
|
MULTI_SERIES_HIGHLIGHT_STYLE: () => MULTI_SERIES_HIGHLIGHT_STYLE,
|
|
MementoCaretaker: () => MementoCaretaker,
|
|
ModuleRegistry: () => moduleRegistry_exports,
|
|
ModuleType: () => ModuleType,
|
|
ObserveChanges: () => ObserveChanges,
|
|
PALETTE_ALT_DOWN_FILL: () => PALETTE_ALT_DOWN_FILL,
|
|
PALETTE_ALT_DOWN_STROKE: () => PALETTE_ALT_DOWN_STROKE,
|
|
PALETTE_ALT_NEUTRAL_FILL: () => PALETTE_ALT_NEUTRAL_FILL,
|
|
PALETTE_ALT_NEUTRAL_STROKE: () => PALETTE_ALT_NEUTRAL_STROKE,
|
|
PALETTE_ALT_UP_FILL: () => PALETTE_ALT_UP_FILL,
|
|
PALETTE_ALT_UP_STROKE: () => PALETTE_ALT_UP_STROKE,
|
|
PALETTE_DOWN_FILL: () => PALETTE_DOWN_FILL,
|
|
PALETTE_DOWN_STROKE: () => PALETTE_DOWN_STROKE,
|
|
PALETTE_NEUTRAL_FILL: () => PALETTE_NEUTRAL_FILL,
|
|
PALETTE_NEUTRAL_STROKE: () => PALETTE_NEUTRAL_STROKE,
|
|
PALETTE_UP_FILL: () => PALETTE_UP_FILL,
|
|
PALETTE_UP_STROKE: () => PALETTE_UP_STROKE,
|
|
PART_WHOLE_HIGHLIGHT_STYLE: () => PART_WHOLE_HIGHLIGHT_STYLE,
|
|
POLAR_AXIS_SHAPE: () => POLAR_AXIS_SHAPE,
|
|
POLAR_AXIS_TYPE: () => POLAR_AXIS_TYPE,
|
|
PREV_NEXT_KEYS: () => PREV_NEXT_KEYS,
|
|
Padding: () => Padding,
|
|
ParallelStateMachine: () => ParallelStateMachine,
|
|
PolarZIndexMap: () => PolarZIndexMap,
|
|
PropertiesArray: () => PropertiesArray,
|
|
Property: () => addFakeTransformToInstanceProperty,
|
|
ProxyOnWrite: () => ProxyOnWrite,
|
|
ProxyProperty: () => ProxyProperty,
|
|
ProxyPropertyOnWrite: () => ProxyPropertyOnWrite,
|
|
SAFE_FILLS_OPERATION: () => SAFE_FILLS_OPERATION,
|
|
SAFE_FILL_OPERATION: () => SAFE_FILL_OPERATION,
|
|
SAFE_RANGE2_OPERATION: () => SAFE_RANGE2_OPERATION,
|
|
SAFE_STROKE_FILL_OPERATION: () => SAFE_STROKE_FILL_OPERATION,
|
|
SEGMENTATION_DEFAULTS: () => SEGMENTATION_DEFAULTS,
|
|
SINGLE_SERIES_HIGHLIGHT_STYLE: () => SINGLE_SERIES_HIGHLIGHT_STYLE,
|
|
SKIP_JS_BUILTINS: () => SKIP_JS_BUILTINS,
|
|
ScaleAlignment: () => ScaleAlignment,
|
|
SceneArrayChangeDetection: () => SceneArrayChangeDetection,
|
|
SceneChangeDetection: () => SceneChangeDetection,
|
|
SceneObjectChangeDetection: () => SceneObjectChangeDetection,
|
|
SceneRefChangeDetection: () => SceneRefChangeDetection,
|
|
SeriesContentZIndexMap: () => SeriesContentZIndexMap,
|
|
SeriesZIndexMap: () => SeriesZIndexMap,
|
|
SimpleCache: () => SimpleCache,
|
|
SpanJoin: () => SpanJoin,
|
|
StateMachine: () => StateMachine,
|
|
StateMachineProperty: () => StateMachineProperty,
|
|
TRIPLE_EQ: () => TRIPLE_EQ,
|
|
TextMeasurer: () => TextMeasurer,
|
|
TickIntervals: () => TickIntervals,
|
|
TrimCharsRegex: () => TrimCharsRegex,
|
|
TrimEdgeGuard: () => TrimEdgeGuard,
|
|
UNIT_MAX: () => UNIT_MAX,
|
|
UNIT_MIN: () => UNIT_MIN,
|
|
UnknownError: () => UnknownError,
|
|
ValidationError: () => ValidationError,
|
|
Vec2: () => vector_exports,
|
|
Vec4: () => vector4_exports,
|
|
Vertex: () => Vertex,
|
|
WeakCache: () => WeakCache,
|
|
ZIndexMap: () => ZIndexMap,
|
|
addEscapeEventListener: () => addEscapeEventListener,
|
|
addFakeTransformToInstanceProperty: () => addFakeTransformToInstanceProperty,
|
|
addMouseCloseListener: () => addMouseCloseListener,
|
|
addObserverToInstanceProperty: () => addObserverToInstanceProperty,
|
|
addOverrideFocusVisibleEventListener: () => addOverrideFocusVisibleEventListener,
|
|
addTouchCloseListener: () => addTouchCloseListener,
|
|
addTransformToInstanceProperty: () => addTransformToInstanceProperty,
|
|
aggregationBucketForDatum: () => aggregationBucketForDatum,
|
|
aggregationDatumMatchesIndex: () => aggregationDatumMatchesIndex,
|
|
aggregationDomain: () => aggregationDomain,
|
|
aggregationIndexForXRatio: () => aggregationIndexForXRatio,
|
|
aggregationRangeFittingPoints: () => aggregationRangeFittingPoints,
|
|
aggregationXRatioForDatumIndex: () => aggregationXRatioForDatumIndex,
|
|
aggregationXRatioForXValue: () => aggregationXRatioForXValue,
|
|
and: () => and,
|
|
angleBetween: () => angleBetween,
|
|
angularPadding: () => angularPadding,
|
|
appendEllipsis: () => appendEllipsis,
|
|
applySkiaPatches: () => applySkiaPatches,
|
|
arcDistanceSquared: () => arcDistanceSquared,
|
|
areScalingEqual: () => areScalingEqual,
|
|
array: () => array,
|
|
arrayLength: () => arrayLength,
|
|
arrayOf: () => arrayOf,
|
|
arrayOfDefs: () => arrayOfDefs,
|
|
arraysEqual: () => arraysEqual,
|
|
assignIfNotStrictlyEqual: () => assignIfNotStrictlyEqual,
|
|
attachDescription: () => attachDescription,
|
|
attachListener: () => attachListener,
|
|
autoSizedLabelOptionsDefs: () => autoSizedLabelOptionsDefs,
|
|
barHighlightOptionsDef: () => barHighlightOptionsDef,
|
|
bezier2DDistance: () => bezier2DDistance,
|
|
bezier2DExtrema: () => bezier2DExtrema,
|
|
boolean: () => boolean,
|
|
borderOptionsDef: () => borderOptionsDef,
|
|
boxCollides: () => boxCollides,
|
|
boxContains: () => boxContains,
|
|
boxEmpty: () => boxEmpty,
|
|
boxesEqual: () => boxesEqual,
|
|
buildDateFormatter: () => buildDateFormatter,
|
|
cachedTextMeasurer: () => cachedTextMeasurer,
|
|
calcLineHeight: () => calcLineHeight,
|
|
calculatePlacement: () => calculatePlacement,
|
|
callWithContext: () => callWithContext,
|
|
callback: () => callback,
|
|
callbackDefs: () => callbackDefs,
|
|
callbackOf: () => callbackOf,
|
|
ceilTo: () => ceilTo,
|
|
checkDatum: () => checkDatum,
|
|
circularSliceArray: () => circularSliceArray,
|
|
clamp: () => clamp,
|
|
clampArray: () => clampArray,
|
|
clipLines: () => clipLines,
|
|
clipSpanX: () => clipSpanX,
|
|
collapseSpanToPoint: () => collapseSpanToPoint,
|
|
collectAggregationLevels: () => collectAggregationLevels,
|
|
color: () => color,
|
|
colorStopsOrderValidator: () => colorStopsOrderValidator,
|
|
colorUnion: () => colorUnion,
|
|
commonChartOptionsDefs: () => commonChartOptionsDefs,
|
|
commonSeriesOptionsDefs: () => commonSeriesOptionsDefs,
|
|
commonSeriesThemeableOptionsDefs: () => commonSeriesThemeableOptionsDefs,
|
|
compactAggregationIndices: () => compactAggregationIndices,
|
|
compareDates: () => compareDates,
|
|
computeExtremesAggregation: () => computeExtremesAggregation,
|
|
computeExtremesAggregationPartial: () => computeExtremesAggregationPartial,
|
|
constant: () => constant,
|
|
contextMenuItemsArray: () => contextMenuItemsArray,
|
|
countFractionDigits: () => countFractionDigits,
|
|
countLines: () => countLines,
|
|
createAggregationIndices: () => createAggregationIndices,
|
|
createButton: () => createButton,
|
|
createCanvasContext: () => createCanvasContext,
|
|
createCheckbox: () => createCheckbox,
|
|
createDeprecationWarning: () => createDeprecationWarning,
|
|
createElement: () => createElement,
|
|
createElementId: () => createElementId,
|
|
createIcon: () => createIcon,
|
|
createId: () => createId,
|
|
createIdsGenerator: () => createIdsGenerator,
|
|
createNumberFormatter: () => createNumberFormatter,
|
|
createSelect: () => createSelect,
|
|
createSvgElement: () => createSvgElement,
|
|
createTextArea: () => createTextArea,
|
|
createTicks: () => createTicks,
|
|
date: () => date,
|
|
dateToNumber: () => dateToNumber,
|
|
dateTruncationForDomain: () => dateTruncationForDomain,
|
|
datesSortOrder: () => datesSortOrder,
|
|
debounce: () => debounce,
|
|
decodeIntervalValue: () => decodeIntervalValue,
|
|
deepClone: () => deepClone,
|
|
deepFreeze: () => deepFreeze,
|
|
defaultEpoch: () => defaultEpoch,
|
|
defined: () => defined,
|
|
definedZoomState: () => definedZoomState,
|
|
diffArrays: () => diffArrays,
|
|
distribute: () => distribute,
|
|
downloadUrl: () => downloadUrl,
|
|
dropFirstWhile: () => dropFirstWhile,
|
|
dropLastWhile: () => dropLastWhile,
|
|
durationDay: () => durationDay,
|
|
durationHour: () => durationHour,
|
|
durationMinute: () => durationMinute,
|
|
durationMonth: () => durationMonth,
|
|
durationSecond: () => durationSecond,
|
|
durationWeek: () => durationWeek,
|
|
durationYear: () => durationYear,
|
|
easeIn: () => easeIn,
|
|
easeInOut: () => easeInOut,
|
|
easeInOutQuad: () => easeInOutQuad,
|
|
easeInQuad: () => easeInQuad,
|
|
easeOut: () => easeOut,
|
|
easeOutQuad: () => easeOutQuad,
|
|
encodedToTimestamp: () => encodedToTimestamp,
|
|
enterpriseRegistry: () => enterpriseRegistry,
|
|
entries: () => entries,
|
|
errorBarOptionsDefs: () => errorBarOptionsDefs,
|
|
errorBarThemeableOptionsDefs: () => errorBarThemeableOptionsDefs,
|
|
estimateTickCount: () => estimateTickCount,
|
|
evaluateBezier: () => evaluateBezier,
|
|
every: () => every,
|
|
expandLegendPosition: () => expandLegendPosition,
|
|
extent: () => extent,
|
|
extractDecoratedProperties: () => extractDecoratedProperties,
|
|
extractDomain: () => extractDomain,
|
|
fillGradientDefaults: () => fillGradientDefaults,
|
|
fillImageDefaults: () => fillImageDefaults,
|
|
fillOptionsDef: () => fillOptionsDef,
|
|
fillPatternDefaults: () => fillPatternDefaults,
|
|
findMaxIndex: () => findMaxIndex,
|
|
findMaxValue: () => findMaxValue,
|
|
findMinIndex: () => findMinIndex,
|
|
findMinMax: () => findMinMax,
|
|
findMinValue: () => findMinValue,
|
|
findRangeExtent: () => findRangeExtent,
|
|
first: () => first,
|
|
flush: () => flush,
|
|
focusCursorAtEnd: () => focusCursorAtEnd,
|
|
fontFamilyFull: () => fontFamilyFull,
|
|
fontOptionsDef: () => fontOptionsDef,
|
|
fontWeight: () => fontWeight,
|
|
formatNumber: () => formatNumber,
|
|
formatObjectValidator: () => formatObjectValidator,
|
|
formatPercent: () => formatPercent,
|
|
formatValue: () => formatValue,
|
|
fromPairs: () => fromPairs,
|
|
generateUUID: () => generateUUID,
|
|
geoJson: () => geoJson,
|
|
getAngleRatioRadians: () => getAngleRatioRadians,
|
|
getAttribute: () => getAttribute,
|
|
getDOMMatrix: () => getDOMMatrix,
|
|
getDocument: () => getDocument,
|
|
getElementBBox: () => getElementBBox,
|
|
getIconClassNames: () => getIconClassNames,
|
|
getImage: () => getImage,
|
|
getLastFocus: () => getLastFocus,
|
|
getMaxInnerRectSize: () => getMaxInnerRectSize,
|
|
getMidpointsForIndices: () => getMidpointsForIndices,
|
|
getMinOuterRectSize: () => getMinOuterRectSize,
|
|
getOffscreenCanvas: () => getOffscreenCanvas,
|
|
getPath: () => getPath,
|
|
getPath2D: () => getPath2D,
|
|
getResizeObserver: () => getResizeObserver,
|
|
getSequentialColors: () => getSequentialColors,
|
|
getTickTimeInterval: () => getTickTimeInterval,
|
|
getWindow: () => getWindow,
|
|
googleFont: () => googleFont,
|
|
gradientColorStops: () => gradientColorStops,
|
|
gradientStrict: () => gradientStrict,
|
|
greaterThan: () => greaterThan,
|
|
groupBy: () => groupBy,
|
|
guardTextEdges: () => guardTextEdges,
|
|
hasNoModifiers: () => hasNoModifiers,
|
|
hasRequiredInPath: () => hasRequiredInPath,
|
|
highlightOptionsDef: () => highlightOptionsDef,
|
|
htmlElement: () => htmlElement,
|
|
inRange: () => inRange,
|
|
initRovingTabIndex: () => initRovingTabIndex,
|
|
insertListItemsSorted: () => insertListItemsSorted,
|
|
instanceOf: () => instanceOf,
|
|
interpolationOptionsDefs: () => interpolationOptionsDefs,
|
|
intervalCeil: () => intervalCeil,
|
|
intervalEpoch: () => intervalEpoch,
|
|
intervalExtent: () => intervalExtent,
|
|
intervalFloor: () => intervalFloor,
|
|
intervalHierarchy: () => intervalHierarchy,
|
|
intervalMilliseconds: () => intervalMilliseconds,
|
|
intervalNext: () => intervalNext,
|
|
intervalPrevious: () => intervalPrevious,
|
|
intervalRange: () => intervalRange,
|
|
intervalRangeCount: () => intervalRangeCount,
|
|
intervalRangeNumeric: () => intervalRangeNumeric,
|
|
intervalRangeStartIndex: () => intervalRangeStartIndex,
|
|
intervalStep: () => intervalStep,
|
|
intervalUnit: () => intervalUnit,
|
|
inverseEaseOut: () => inverseEaseOut,
|
|
isArray: () => isArray,
|
|
isBetweenAngles: () => isBetweenAngles,
|
|
isBoolean: () => isBoolean,
|
|
isButtonClickEvent: () => isButtonClickEvent,
|
|
isColor: () => isColor,
|
|
isContinuous: () => isContinuous,
|
|
isDate: () => isDate,
|
|
isDecoratedObject: () => isDecoratedObject,
|
|
isDefined: () => isDefined,
|
|
isDenseInterval: () => isDenseInterval,
|
|
isDocumentFragment: () => isDocumentFragment,
|
|
isElement: () => isElement,
|
|
isEmptyObject: () => isEmptyObject,
|
|
isEnumKey: () => isEnumKey,
|
|
isEnumValue: () => isEnumValue,
|
|
isFiniteNumber: () => isFiniteNumber,
|
|
isFunction: () => isFunction,
|
|
isGradientFill: () => isGradientFill,
|
|
isGradientFillArray: () => isGradientFillArray,
|
|
isGradientOrPatternFill: () => isGradientOrPatternFill,
|
|
isHTMLElement: () => isHTMLElement,
|
|
isHtmlElement: () => isHtmlElement,
|
|
isImageFill: () => isImageFill,
|
|
isInputPending: () => isInputPending,
|
|
isInteger: () => isInteger,
|
|
isKeyOf: () => isKeyOf,
|
|
isNegative: () => isNegative,
|
|
isNode: () => isNode,
|
|
isNumber: () => isNumber,
|
|
isNumberEqual: () => isNumberEqual,
|
|
isNumberObject: () => isNumberObject,
|
|
isObject: () => isObject,
|
|
isObjectLike: () => isObjectLike,
|
|
isObjectWithProperty: () => isObjectWithProperty,
|
|
isObjectWithStringProperty: () => isObjectWithStringProperty,
|
|
isPatternFill: () => isPatternFill,
|
|
isPlainObject: () => isPlainObject,
|
|
isPointLabelDatum: () => isPointLabelDatum,
|
|
isProperties: () => isProperties,
|
|
isRegExp: () => isRegExp,
|
|
isScaleValid: () => isScaleValid,
|
|
isSegmentTruncated: () => isSegmentTruncated,
|
|
isString: () => isString,
|
|
isStringFillArray: () => isStringFillArray,
|
|
isStringObject: () => isStringObject,
|
|
isSymbol: () => isSymbol,
|
|
isTextTruncated: () => isTextTruncated,
|
|
isTruncated: () => isTruncated,
|
|
isUnitTimeCategoryScaling: () => isUnitTimeCategoryScaling,
|
|
isValidDate: () => isValidDate,
|
|
isValidNumberFormat: () => isValidNumberFormat,
|
|
iterate: () => iterate,
|
|
joinFormatted: () => joinFormatted,
|
|
jsonApply: () => jsonApply,
|
|
jsonDiff: () => jsonDiff,
|
|
jsonPropertyCompare: () => jsonPropertyCompare,
|
|
jsonWalk: () => jsonWalk,
|
|
kebabCase: () => kebabCase,
|
|
labelBoxOptionsDef: () => labelBoxOptionsDef,
|
|
legendPositionValidator: () => legendPositionValidator,
|
|
lessThan: () => lessThan,
|
|
lessThanOrEqual: () => lessThanOrEqual,
|
|
levenshteinDistance: () => levenshteinDistance,
|
|
lineDashOptionsDef: () => lineDashOptionsDef,
|
|
lineDistanceSquared: () => lineDistanceSquared,
|
|
lineHighlightOptionsDef: () => lineHighlightOptionsDef,
|
|
lineSegmentOptions: () => lineSegmentOptions,
|
|
lineSegmentation: () => lineSegmentation,
|
|
linear: () => linear,
|
|
linearGaugeSeriesOptionsDef: () => linearGaugeSeriesOptionsDef,
|
|
linearGaugeSeriesThemeableOptionsDef: () => linearGaugeSeriesThemeableOptionsDef,
|
|
linearGaugeTargetOptionsDef: () => linearGaugeTargetOptionsDef,
|
|
linearPoints: () => linearPoints,
|
|
listDecoratedProperties: () => listDecoratedProperties,
|
|
lowestGranularityForInterval: () => lowestGranularityForInterval,
|
|
lowestGranularityUnitForTicks: () => lowestGranularityUnitForTicks,
|
|
lowestGranularityUnitForValue: () => lowestGranularityUnitForValue,
|
|
makeAccessibleClickListener: () => makeAccessibleClickListener,
|
|
mapValues: () => mapValues,
|
|
markerOptionsDefs: () => markerOptionsDefs,
|
|
markerStyleOptionsDefs: () => markerStyleOptionsDefs,
|
|
measureTextSegments: () => measureTextSegments,
|
|
memo: () => memo,
|
|
merge: () => merge,
|
|
mergeArrayDefaults: () => mergeArrayDefaults,
|
|
mergeDefaults: () => mergeDefaults,
|
|
modulus: () => modulus,
|
|
multiSeriesHighlightOptionsDef: () => multiSeriesHighlightOptionsDef,
|
|
nearestSquared: () => nearestSquared,
|
|
nearestSquaredInContainer: () => nearestSquaredInContainer,
|
|
nextPowerOf2: () => nextPowerOf2,
|
|
niceTicksDomain: () => niceTicksDomain,
|
|
normalisedExtentWithMetadata: () => normalisedExtentWithMetadata,
|
|
normalisedTimeExtentWithMetadata: () => normalisedTimeExtentWithMetadata,
|
|
normalizeAngle180: () => normalizeAngle180,
|
|
normalizeAngle360: () => normalizeAngle360,
|
|
normalizeAngle360FromDegrees: () => normalizeAngle360FromDegrees,
|
|
normalizeAngle360Inclusive: () => normalizeAngle360Inclusive,
|
|
number: () => number,
|
|
numberFormatValidator: () => numberFormatValidator,
|
|
numberMin: () => numberMin,
|
|
numberRange: () => numberRange,
|
|
object: () => object,
|
|
objectsEqual: () => objectsEqual,
|
|
objectsEqualWith: () => objectsEqualWith,
|
|
optionsDefs: () => optionsDefs,
|
|
or: () => or,
|
|
padding: () => padding,
|
|
paddingOptions: () => paddingOptions,
|
|
parseNumberFormat: () => parseNumberFormat,
|
|
partialAssign: () => partialAssign,
|
|
pause: () => pause,
|
|
pick: () => pick,
|
|
placeLabels: () => placeLabels,
|
|
positiveNumber: () => positiveNumber,
|
|
positiveNumberNonZero: () => positiveNumberNonZero,
|
|
previousPowerOf2: () => previousPowerOf2,
|
|
radialGaugeSeriesOptionsDef: () => radialGaugeSeriesOptionsDef,
|
|
radialGaugeSeriesThemeableOptionsDef: () => radialGaugeSeriesThemeableOptionsDef,
|
|
radialGaugeTargetOptionsDef: () => radialGaugeTargetOptionsDef,
|
|
range: () => range,
|
|
rangeValidator: () => rangeValidator,
|
|
ratio: () => ratio,
|
|
readIntegratedWrappedValue: () => readIntegratedWrappedValue,
|
|
record: () => record,
|
|
required: () => required,
|
|
rescaleSpan: () => rescaleSpan,
|
|
rescaleVisibleRange: () => rescaleVisibleRange,
|
|
resetIds: () => resetIds,
|
|
rotatePoint: () => rotatePoint,
|
|
roundTo: () => roundTo,
|
|
safeCall: () => safeCall,
|
|
seriesLabelOptionsDefs: () => seriesLabelOptionsDefs,
|
|
seriesTooltipRangeValidator: () => seriesTooltipRangeValidator,
|
|
setAttribute: () => setAttribute,
|
|
setAttributes: () => setAttributes,
|
|
setDocument: () => setDocument,
|
|
setElementBBox: () => setElementBBox,
|
|
setElementStyle: () => setElementStyle,
|
|
setElementStyles: () => setElementStyles,
|
|
setPath: () => setPath,
|
|
setWindow: () => setWindow,
|
|
shadowOptionsDefs: () => shadowOptionsDefs,
|
|
shallowClone: () => shallowClone,
|
|
shapeHighlightOptionsDef: () => shapeHighlightOptionsDef,
|
|
shapeSegmentOptions: () => shapeSegmentOptions,
|
|
shapeSegmentation: () => shapeSegmentation,
|
|
shapeValidator: () => shapeValidator,
|
|
simpleMemorize: () => simpleMemorize,
|
|
simpleMemorize2: () => simpleMemorize2,
|
|
smoothPoints: () => smoothPoints,
|
|
solveBezier: () => solveBezier,
|
|
sortAndUniqueDates: () => sortAndUniqueDates,
|
|
sortBasedOnArray: () => sortBasedOnArray,
|
|
spanRange: () => spanRange,
|
|
splitBezier2D: () => splitBezier2D,
|
|
stepPoints: () => stepPoints,
|
|
stopPageScrolling: () => stopPageScrolling,
|
|
strictObjectKeys: () => strictObjectKeys,
|
|
strictUnion: () => strictUnion,
|
|
string: () => string,
|
|
stringLength: () => stringLength,
|
|
stringifyValue: () => stringifyValue,
|
|
strokeOptionsDef: () => strokeOptionsDef,
|
|
textOrSegments: () => textOrSegments,
|
|
themeOperator: () => themeOperator,
|
|
throttle: () => throttle,
|
|
tickFormat: () => tickFormat,
|
|
tickStep: () => tickStep,
|
|
toArray: () => toArray,
|
|
toDegrees: () => toDegrees,
|
|
toFontString: () => toFontString,
|
|
toIterable: () => toIterable,
|
|
toPlainText: () => toPlainText,
|
|
toRadians: () => toRadians,
|
|
toTextString: () => toTextString,
|
|
toolbarButtonOptionsDefs: () => toolbarButtonOptionsDefs,
|
|
tooltipOptionsDefs: () => tooltipOptionsDefs,
|
|
tooltipOptionsDefsWithArea: () => tooltipOptionsDefsWithArea,
|
|
transformIntegratedCategoryValue: () => transformIntegratedCategoryValue,
|
|
truncateLine: () => truncateLine,
|
|
typeUnion: () => typeUnion,
|
|
undocumented: () => undocumented,
|
|
unguardTextEdges: () => unguardTextEdges,
|
|
union: () => union,
|
|
unionSymbol: () => unionSymbol,
|
|
unique: () => unique,
|
|
validate: () => validate,
|
|
withTimeout: () => withTimeout,
|
|
without: () => without,
|
|
wrapLines: () => wrapLines,
|
|
wrapText: () => wrapText,
|
|
wrapTextOrSegments: () => wrapTextOrSegments,
|
|
wrapTextSegments: () => wrapTextSegments
|
|
});
|
|
|
|
// packages/ag-charts-core/src/modules/moduleDefinition.ts
|
|
var ModuleType = /* @__PURE__ */ ((ModuleType2) => {
|
|
ModuleType2["Chart"] = "chart";
|
|
ModuleType2["Axis"] = "axis";
|
|
ModuleType2["Series"] = "series";
|
|
ModuleType2["Plugin"] = "plugin";
|
|
ModuleType2["AxisPlugin"] = "axis:plugin";
|
|
ModuleType2["SeriesPlugin"] = "series:plugin";
|
|
ModuleType2["Preset"] = "preset";
|
|
return ModuleType2;
|
|
})(ModuleType || {});
|
|
|
|
// packages/ag-charts-core/src/types/scales.ts
|
|
function extractDomain(value) {
|
|
return value.domain;
|
|
}
|
|
var ScaleAlignment = /* @__PURE__ */ ((ScaleAlignment2) => {
|
|
ScaleAlignment2[ScaleAlignment2["Leading"] = 0] = "Leading";
|
|
ScaleAlignment2[ScaleAlignment2["Trailing"] = 1] = "Trailing";
|
|
ScaleAlignment2[ScaleAlignment2["Interpolate"] = 2] = "Interpolate";
|
|
return ScaleAlignment2;
|
|
})(ScaleAlignment || {});
|
|
|
|
// packages/ag-charts-core/src/structures/eventEmitter.ts
|
|
var EventEmitter = class {
|
|
constructor() {
|
|
this.events = /* @__PURE__ */ new Map();
|
|
}
|
|
/**
|
|
* Registers an event listener.
|
|
* @param eventName The event name to listen for.
|
|
* @param listener The callback to be invoked on the event.
|
|
* @returns A function to unregister the listener.
|
|
*/
|
|
on(eventName, listener) {
|
|
if (!this.events.has(eventName)) {
|
|
this.events.set(eventName, /* @__PURE__ */ new Set());
|
|
}
|
|
this.events.get(eventName)?.add(listener);
|
|
return () => this.off(eventName, listener);
|
|
}
|
|
/**
|
|
* Unregisters an event listener.
|
|
* @param eventName The event name to stop listening for.
|
|
* @param listener The callback to be removed.
|
|
*/
|
|
off(eventName, listener) {
|
|
const eventListeners = this.events.get(eventName);
|
|
if (eventListeners) {
|
|
eventListeners.delete(listener);
|
|
if (eventListeners.size === 0) {
|
|
this.events.delete(eventName);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Emits an event to all registered listeners.
|
|
* @param eventName The name of the event to emit.
|
|
* @param event The event payload.
|
|
*/
|
|
emit(eventName, event) {
|
|
const listeners = this.events.get(eventName);
|
|
if (listeners) {
|
|
for (const callback2 of listeners) {
|
|
callback2(event);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Clears all listeners for a specific event or all events if no event name is provided.
|
|
* @param eventName (Optional) The name of the event to clear listeners for. If not provided, all listeners for all events are cleared.
|
|
*/
|
|
clear(eventName) {
|
|
if (eventName) {
|
|
this.events.delete(eventName);
|
|
} else {
|
|
this.events.clear();
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-core/src/structures/lruCache.ts
|
|
var LRUCache = class {
|
|
constructor(maxCacheSize) {
|
|
this.maxCacheSize = maxCacheSize;
|
|
this.store = /* @__PURE__ */ new Map();
|
|
if (maxCacheSize <= 0) {
|
|
throw new Error("LRUCache size must be greater than 0");
|
|
}
|
|
}
|
|
get(key) {
|
|
if (!this.store.has(key))
|
|
return;
|
|
const value = this.store.get(key);
|
|
this.store.delete(key);
|
|
this.store.set(key, value);
|
|
return value;
|
|
}
|
|
has(key) {
|
|
return this.store.has(key);
|
|
}
|
|
set(key, value) {
|
|
this.store.set(key, value);
|
|
if (this.store.size > this.maxCacheSize) {
|
|
this.store.delete(this.store.keys().next().value);
|
|
}
|
|
return value;
|
|
}
|
|
clear() {
|
|
this.store.clear();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-core/src/logging/debugLogger.ts
|
|
var debugLogger_exports = {};
|
|
__export(debugLogger_exports, {
|
|
Time: () => Time,
|
|
check: () => check,
|
|
create: () => create,
|
|
inDevelopmentMode: () => inDevelopmentMode
|
|
});
|
|
|
|
// packages/ag-charts-core/src/utils/data/arrays.ts
|
|
function toArray(value) {
|
|
if (value === void 0) {
|
|
return [];
|
|
}
|
|
return Array.isArray(value) ? value : [value];
|
|
}
|
|
function unique(array2) {
|
|
return Array.from(new Set(array2));
|
|
}
|
|
function groupBy(array2, iteratee) {
|
|
return array2.reduce((result, item) => {
|
|
const groupKey = iteratee(item);
|
|
result[groupKey] ?? (result[groupKey] = []);
|
|
result[groupKey].push(item);
|
|
return result;
|
|
}, {});
|
|
}
|
|
function arraysEqual(a, b) {
|
|
if (a == null || b == null || a.length !== b.length) {
|
|
return false;
|
|
}
|
|
for (let i = 0; i < a.length; i++) {
|
|
if (Array.isArray(a[i]) && Array.isArray(b[i])) {
|
|
if (!arraysEqual(a[i], b[i])) {
|
|
return false;
|
|
}
|
|
} else if (a[i] !== b[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function circularSliceArray(data, size, offset = 0) {
|
|
if (data.length === 0) {
|
|
return [];
|
|
}
|
|
const result = [];
|
|
for (let i = 0; i < size; i++) {
|
|
result.push(data.at((i + offset) % data.length));
|
|
}
|
|
return result;
|
|
}
|
|
function sortBasedOnArray(baseArray, orderArray) {
|
|
const orderMap = /* @__PURE__ */ new Map();
|
|
for (const [index, item] of orderArray.entries()) {
|
|
orderMap.set(item, index);
|
|
}
|
|
return baseArray.sort((a, b) => {
|
|
const indexA = orderMap.get(a) ?? Infinity;
|
|
const indexB = orderMap.get(b) ?? Infinity;
|
|
return indexA - indexB;
|
|
});
|
|
}
|
|
function dropFirstWhile(array2, cond) {
|
|
let i = 0;
|
|
while (i < array2.length && cond(array2[i])) {
|
|
i += 1;
|
|
}
|
|
const deleteCount = i;
|
|
if (deleteCount !== 0)
|
|
array2.splice(0, deleteCount);
|
|
}
|
|
function dropLastWhile(array2, cond) {
|
|
let i = array2.length - 1;
|
|
while (i >= 0 && cond(array2[i])) {
|
|
i -= 1;
|
|
}
|
|
const deleteCount = array2.length - 1 - i;
|
|
if (deleteCount !== 0)
|
|
array2.splice(array2.length - deleteCount, deleteCount);
|
|
}
|
|
function distribute(min, max, maxCount) {
|
|
const values = [min];
|
|
const step = Math.round((max - min) / (maxCount - 1));
|
|
if (step > 0) {
|
|
for (let i = min + step; i < max; i += step) {
|
|
const length2 = values.push(i);
|
|
if (length2 >= maxCount - 1)
|
|
break;
|
|
}
|
|
}
|
|
values.push(max);
|
|
return values;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/dom/globalsProxy.ts
|
|
var verifiedGlobals = {};
|
|
if (typeof globalThis.window !== "undefined") {
|
|
verifiedGlobals.window = globalThis.window;
|
|
}
|
|
if (typeof document !== "undefined") {
|
|
verifiedGlobals.document = document;
|
|
} else if (typeof globalThis.global !== "undefined") {
|
|
verifiedGlobals.document = globalThis.document;
|
|
}
|
|
function getDocument(propertyName) {
|
|
return propertyName ? verifiedGlobals.document?.[propertyName] : verifiedGlobals.document;
|
|
}
|
|
function getWindow(propertyName) {
|
|
return propertyName ? verifiedGlobals.window?.[propertyName] : verifiedGlobals.window;
|
|
}
|
|
function setDocument(document2) {
|
|
verifiedGlobals.document = document2;
|
|
}
|
|
function setWindow(window) {
|
|
verifiedGlobals.window = window;
|
|
}
|
|
function getOffscreenCanvas() {
|
|
return verifiedGlobals.window?.OffscreenCanvas ?? globalThis.OffscreenCanvas;
|
|
}
|
|
function getPath2D() {
|
|
return verifiedGlobals.window?.Path2D ?? globalThis.Path2D;
|
|
}
|
|
function getDOMMatrix() {
|
|
return verifiedGlobals.window?.DOMMatrix ?? globalThis.DOMMatrix;
|
|
}
|
|
function getImage() {
|
|
return verifiedGlobals.window?.Image ?? globalThis.Image;
|
|
}
|
|
function getResizeObserver() {
|
|
return verifiedGlobals.window?.ResizeObserver ?? globalThis.ResizeObserver;
|
|
}
|
|
var ELEMENT_NODE = 1;
|
|
var DOCUMENT_FRAGMENT_NODE = 11;
|
|
function isNode(obj) {
|
|
return obj != null && typeof obj.nodeType === "number";
|
|
}
|
|
function isElement(obj) {
|
|
return obj != null && obj.nodeType === ELEMENT_NODE;
|
|
}
|
|
function isDocumentFragment(obj) {
|
|
return obj != null && obj.nodeType === DOCUMENT_FRAGMENT_NODE;
|
|
}
|
|
function isHTMLElement(obj) {
|
|
return obj != null && obj.nodeType === ELEMENT_NODE && "style" in obj;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/logging/logger.ts
|
|
var logger_exports = {};
|
|
__export(logger_exports, {
|
|
error: () => error,
|
|
errorOnce: () => errorOnce,
|
|
log: () => log,
|
|
logGroup: () => logGroup,
|
|
reset: () => reset,
|
|
table: () => table,
|
|
warn: () => warn,
|
|
warnOnce: () => warnOnce
|
|
});
|
|
var doOnceCache = /* @__PURE__ */ new Set();
|
|
function log(...logContent) {
|
|
console.log(...logContent);
|
|
}
|
|
function warn(message, ...logContent) {
|
|
console.warn(`AG Charts - ${message}`, ...logContent);
|
|
}
|
|
function error(message, ...logContent) {
|
|
if (typeof message === "object") {
|
|
console.error(`AG Charts error`, message, ...logContent);
|
|
} else {
|
|
console.error(`AG Charts - ${message}`, ...logContent);
|
|
}
|
|
}
|
|
function table(...logContent) {
|
|
console.table(...logContent);
|
|
}
|
|
function guardOnce(messageOrError, prefix, cb) {
|
|
let message;
|
|
if (messageOrError instanceof Error) {
|
|
message = messageOrError.message;
|
|
} else if (typeof messageOrError === "string") {
|
|
message = messageOrError;
|
|
} else if (typeof messageOrError === "object") {
|
|
message = JSON.stringify(messageOrError);
|
|
} else {
|
|
message = String(messageOrError);
|
|
}
|
|
const cacheKey = `${prefix}: ${message}`;
|
|
if (doOnceCache.has(cacheKey))
|
|
return;
|
|
cb(messageOrError);
|
|
doOnceCache.add(cacheKey);
|
|
}
|
|
function warnOnce(messageOrError, ...logContent) {
|
|
guardOnce(messageOrError, "Logger.warn", (message) => warn(message, ...logContent));
|
|
}
|
|
function errorOnce(messageOrError, ...logContent) {
|
|
guardOnce(messageOrError, "Logger.error", (message) => error(message, ...logContent));
|
|
}
|
|
function reset() {
|
|
doOnceCache.clear();
|
|
}
|
|
function logGroup(name, cb) {
|
|
console.groupCollapsed(name);
|
|
try {
|
|
return cb();
|
|
} finally {
|
|
console.groupEnd();
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-core/src/logging/debugLogger.ts
|
|
var LongTimePeriodThreshold = 2e3;
|
|
var timeOfLastLog = Date.now();
|
|
function logTimeGap() {
|
|
const timeSinceLastLog = Date.now() - timeOfLastLog;
|
|
if (timeSinceLastLog > LongTimePeriodThreshold) {
|
|
const prettyDuration = (Math.floor(timeSinceLastLog / 100) / 10).toFixed(1);
|
|
log(`**** ${prettyDuration}s since last log message ****`);
|
|
}
|
|
timeOfLastLog = Date.now();
|
|
}
|
|
function create(...debugSelectors) {
|
|
const resultFn = (...logContent) => {
|
|
if (check(...debugSelectors)) {
|
|
if (typeof logContent[0] === "function") {
|
|
logContent = toArray(logContent[0]());
|
|
}
|
|
logTimeGap();
|
|
log(...logContent);
|
|
}
|
|
};
|
|
return Object.assign(resultFn, {
|
|
check: () => check(...debugSelectors),
|
|
group: (name, cb) => {
|
|
if (check(...debugSelectors)) {
|
|
return logGroup(name, cb);
|
|
}
|
|
return cb();
|
|
}
|
|
});
|
|
}
|
|
function check(...debugSelectors) {
|
|
if (debugSelectors.length === 0) {
|
|
debugSelectors.push(true);
|
|
}
|
|
const chartDebug = toArray(getWindow("agChartsDebug"));
|
|
return chartDebug.some((selector) => debugSelectors.includes(selector));
|
|
}
|
|
function inDevelopmentMode(fn) {
|
|
if (check("dev")) {
|
|
return fn();
|
|
}
|
|
}
|
|
function Time(name, opts = {}) {
|
|
const { logResult = true, logStack = false, logArgs = false, logData } = opts;
|
|
return function(_target, _propertyKey, descriptor) {
|
|
const method = descriptor.value;
|
|
descriptor.value = function(...args) {
|
|
const start2 = performance.now();
|
|
const result = method.apply(this, args);
|
|
const duration = performance.now() - start2;
|
|
const logMessage = { duration };
|
|
if (logResult)
|
|
logMessage.result = result;
|
|
if (logArgs)
|
|
logMessage.args = args;
|
|
if (logStack)
|
|
logMessage.stack = new Error("Stack trace for timing debug").stack;
|
|
if (logData)
|
|
logMessage.logData = logData(this);
|
|
log(name, logMessage);
|
|
return result;
|
|
};
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-core/src/logging/debugMetrics.ts
|
|
var debugMetrics_exports = {};
|
|
__export(debugMetrics_exports, {
|
|
flush: () => flush,
|
|
record: () => record
|
|
});
|
|
var metrics = /* @__PURE__ */ new Map();
|
|
function record(key, value) {
|
|
if (!check("scene:stats:verbose"))
|
|
return;
|
|
metrics.set(key, value);
|
|
}
|
|
function flush() {
|
|
const result = Object.fromEntries(metrics);
|
|
metrics.clear();
|
|
return result;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/modules/enterpriseRegistry.ts
|
|
var enterpriseRegistry = {};
|
|
|
|
// packages/ag-charts-core/src/modules/moduleRegistry.ts
|
|
var moduleRegistry_exports = {};
|
|
__export(moduleRegistry_exports, {
|
|
RegistryMode: () => RegistryMode,
|
|
getAxisModule: () => getAxisModule,
|
|
getChartModule: () => getChartModule,
|
|
getPresetModule: () => getPresetModule,
|
|
getSeriesModule: () => getSeriesModule,
|
|
hasModule: () => hasModule,
|
|
isEnterprise: () => isEnterprise,
|
|
isIntegrated: () => isIntegrated,
|
|
isModuleType: () => isModuleType,
|
|
isUmd: () => isUmd,
|
|
listModules: () => listModules,
|
|
listModulesByType: () => listModulesByType,
|
|
register: () => register,
|
|
registerModules: () => registerModules,
|
|
reset: () => reset2,
|
|
setRegistryMode: () => setRegistryMode
|
|
});
|
|
var RegistryMode = /* @__PURE__ */ ((RegistryMode2) => {
|
|
RegistryMode2["Enterprise"] = "enterprise";
|
|
RegistryMode2["Integrated"] = "integrated";
|
|
RegistryMode2["UMD"] = "umd";
|
|
return RegistryMode2;
|
|
})(RegistryMode || {});
|
|
var registeredModes = /* @__PURE__ */ new Set();
|
|
var registeredModules = /* @__PURE__ */ new Map();
|
|
function registerModuleDefinition(def) {
|
|
registeredModules.set(def.name, def);
|
|
if (def.dependencies) {
|
|
for (const dependency of def.dependencies) {
|
|
register(dependency);
|
|
}
|
|
}
|
|
}
|
|
function register(def) {
|
|
const existingDefinition = registeredModules.get(def.name);
|
|
if (!existingDefinition) {
|
|
registerModuleDefinition(def);
|
|
return;
|
|
}
|
|
if (existingDefinition.version === def.version) {
|
|
if (!existingDefinition.enterprise && def.enterprise) {
|
|
registerModuleDefinition(def);
|
|
}
|
|
return;
|
|
}
|
|
throw new Error(
|
|
[
|
|
`AG Charts - Module '${def.name}' already registered with different version:`,
|
|
`${existingDefinition.version} vs ${def.version}`,
|
|
``,
|
|
`Check your package.json for conflicting dependencies - depending on your package manager`,
|
|
`one of these commands may help:`,
|
|
`- npm ls ag-charts-community`,
|
|
`- yarn why ag-charts-community`
|
|
].join("\n")
|
|
);
|
|
}
|
|
function registerModules(definitions) {
|
|
for (const definition of definitions.flat()) {
|
|
register(definition);
|
|
}
|
|
}
|
|
function reset2() {
|
|
registeredModes.clear();
|
|
registeredModules.clear();
|
|
}
|
|
function hasModule(moduleName) {
|
|
return registeredModules.has(moduleName);
|
|
}
|
|
function* listModules() {
|
|
for (const definition of registeredModules.values()) {
|
|
yield definition;
|
|
}
|
|
}
|
|
function* listModulesByType(moduleType) {
|
|
for (const definition of registeredModules.values()) {
|
|
if (isModuleType(moduleType, definition)) {
|
|
yield definition;
|
|
}
|
|
}
|
|
}
|
|
function getAxisModule(moduleName) {
|
|
const definition = registeredModules.get(moduleName);
|
|
if (isModuleType("axis" /* Axis */, definition)) {
|
|
return definition;
|
|
}
|
|
}
|
|
function getChartModule(moduleName) {
|
|
const definition = registeredModules.get(moduleName);
|
|
if (isModuleType("chart" /* Chart */, definition)) {
|
|
return definition;
|
|
}
|
|
throw new Error(
|
|
`AG Charts - Unknown chart type; Check options are correctly structured and series types are specified`
|
|
);
|
|
}
|
|
function getPresetModule(moduleName) {
|
|
const definition = registeredModules.get(moduleName);
|
|
if (isModuleType("preset" /* Preset */, definition)) {
|
|
return definition;
|
|
}
|
|
}
|
|
function getSeriesModule(moduleName) {
|
|
const definition = registeredModules.get(moduleName);
|
|
if (isModuleType("series" /* Series */, definition)) {
|
|
return definition;
|
|
}
|
|
}
|
|
function setRegistryMode(registryFlag) {
|
|
registeredModes.add(registryFlag);
|
|
}
|
|
function isEnterprise() {
|
|
return registeredModes.has("enterprise" /* Enterprise */);
|
|
}
|
|
function isIntegrated() {
|
|
return registeredModes.has("integrated" /* Integrated */);
|
|
}
|
|
function isUmd() {
|
|
return registeredModes.has("umd" /* UMD */);
|
|
}
|
|
function isModuleType(moduleType, definition) {
|
|
return definition?.type === moduleType;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/state/cleanupRegistry.ts
|
|
var CleanupRegistry = class {
|
|
constructor() {
|
|
this.callbacks = /* @__PURE__ */ new Set();
|
|
}
|
|
flush() {
|
|
for (const cb of this.callbacks) {
|
|
cb();
|
|
}
|
|
this.callbacks.clear();
|
|
}
|
|
merge(registry) {
|
|
for (const cb of registry.callbacks) {
|
|
this.callbacks.add(cb);
|
|
}
|
|
}
|
|
register(...callbacks) {
|
|
for (const cb of callbacks) {
|
|
if (!cb)
|
|
continue;
|
|
this.callbacks.add(cb);
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-core/src/modules/moduleInstance.ts
|
|
var AbstractModuleInstance = class {
|
|
constructor() {
|
|
this.cleanup = new CleanupRegistry();
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-types/src/chart/navigatorOptions.ts
|
|
var __MINI_CHART_SERIES_OPTIONS = void 0;
|
|
var __VERIFY_MINI_CHART_SERIES_OPTIONS = void 0;
|
|
__VERIFY_MINI_CHART_SERIES_OPTIONS = __MINI_CHART_SERIES_OPTIONS;
|
|
|
|
// packages/ag-charts-types/src/chart/themeOptions.ts
|
|
var __THEME_OVERRIDES = void 0;
|
|
var __VERIFY_THEME_OVERRIDES = void 0;
|
|
__VERIFY_THEME_OVERRIDES = __THEME_OVERRIDES;
|
|
|
|
// packages/ag-charts-types/src/chart/tooltipOptions.ts
|
|
var AgTooltipAnchorToType = /* @__PURE__ */ ((AgTooltipAnchorToType2) => {
|
|
AgTooltipAnchorToType2["POINTER"] = "pointer";
|
|
AgTooltipAnchorToType2["NODE"] = "node";
|
|
AgTooltipAnchorToType2["CHART"] = "chart";
|
|
return AgTooltipAnchorToType2;
|
|
})(AgTooltipAnchorToType || {});
|
|
var AgTooltipPlacementType = /* @__PURE__ */ ((AgTooltipPlacementType2) => {
|
|
AgTooltipPlacementType2["TOP"] = "top";
|
|
AgTooltipPlacementType2["RIGHT"] = "right";
|
|
AgTooltipPlacementType2["BOTTOM"] = "bottom";
|
|
AgTooltipPlacementType2["LEFT"] = "left";
|
|
AgTooltipPlacementType2["TOP_RIGHT"] = "top-right";
|
|
AgTooltipPlacementType2["BOTTOM_RIGHT"] = "bottom-right";
|
|
AgTooltipPlacementType2["BOTTOM_LEFT"] = "bottom-left";
|
|
AgTooltipPlacementType2["TOP_LEFT"] = "top-left";
|
|
AgTooltipPlacementType2["CENTER"] = "center";
|
|
return AgTooltipPlacementType2;
|
|
})(AgTooltipPlacementType || {});
|
|
|
|
// packages/ag-charts-types/src/presets/gauge/commonOptions.ts
|
|
var __THEMEABLE_OPTIONS = void 0;
|
|
var __VERIFY_THEMEABLE_OPTIONS = void 0;
|
|
__VERIFY_THEMEABLE_OPTIONS = __THEMEABLE_OPTIONS;
|
|
var __AXIS_LABEL_OPTIONS = void 0;
|
|
var __VERIFY_AXIS_LABEL_OPTIONS = void 0;
|
|
__VERIFY_AXIS_LABEL_OPTIONS = __AXIS_LABEL_OPTIONS;
|
|
|
|
// packages/ag-charts-core/src/types/text.ts
|
|
var EllipsisChar = "\u2026";
|
|
var LineSplitter = /\r?\n/g;
|
|
var TrimEdgeGuard = "\u200B";
|
|
var TrimCharsRegex = /[\s.,;:-]{1,5}$/;
|
|
|
|
// packages/ag-charts-core/src/utils/dom/domUtils.ts
|
|
var style;
|
|
function parseColor(color2) {
|
|
if (style == null) {
|
|
const OptionConstructor = getWindow("Option");
|
|
style = new OptionConstructor().style;
|
|
}
|
|
style.color = color2;
|
|
const result = style.color || null;
|
|
style.color = "";
|
|
return result;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/types/typeGuards.ts
|
|
function isDefined(val) {
|
|
return val != null;
|
|
}
|
|
function isArray(value) {
|
|
return Array.isArray(value);
|
|
}
|
|
function isBoolean(value) {
|
|
return typeof value === "boolean";
|
|
}
|
|
function isDate(value) {
|
|
return value instanceof Date;
|
|
}
|
|
function isValidDate(value) {
|
|
return isDate(value) && !Number.isNaN(Number(value));
|
|
}
|
|
function isRegExp(value) {
|
|
return value instanceof RegExp;
|
|
}
|
|
function isFunction(value) {
|
|
return typeof value === "function";
|
|
}
|
|
function isObject(value) {
|
|
return typeof value === "object" && value !== null && !isArray(value);
|
|
}
|
|
function isObjectLike(value) {
|
|
return isArray(value) || isPlainObject(value);
|
|
}
|
|
function isPlainObject(value) {
|
|
return typeof value === "object" && value !== null && value.constructor?.name === "Object";
|
|
}
|
|
function isEmptyObject(value) {
|
|
if (typeof value !== "object" || value === null)
|
|
return false;
|
|
for (const _ in value) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function isString(value) {
|
|
return typeof value === "string";
|
|
}
|
|
function isNumber(value) {
|
|
return typeof value === "number";
|
|
}
|
|
function isFiniteNumber(value) {
|
|
return Number.isFinite(value);
|
|
}
|
|
function isHtmlElement(value) {
|
|
return value != null && value.nodeType === 1 && "style" in value;
|
|
}
|
|
function isEnumKey(enumObject, enumKey) {
|
|
return isString(enumKey) && Object.keys(enumObject).includes(enumKey);
|
|
}
|
|
function isEnumValue(enumObject, enumValue) {
|
|
return Object.values(enumObject).includes(enumValue);
|
|
}
|
|
function isSymbol(value) {
|
|
return typeof value === "symbol";
|
|
}
|
|
function isColor(value) {
|
|
return isString(value) && (value === "none" || parseColor(value) != null);
|
|
}
|
|
function isKeyOf(value, container) {
|
|
return value in container;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/text/textUtils.ts
|
|
function toFontString({ fontSize, fontStyle, fontWeight: fontWeight2, fontFamily }) {
|
|
let fontString = "";
|
|
if (fontStyle && fontStyle !== "normal") {
|
|
fontString += `${fontStyle} `;
|
|
}
|
|
if (fontWeight2 && fontWeight2 !== "normal" && fontWeight2 !== 400) {
|
|
fontString += `${fontWeight2} `;
|
|
}
|
|
fontString += `${fontSize}px`;
|
|
fontString += ` ${fontFamily}`;
|
|
return fontString;
|
|
}
|
|
function calcLineHeight(fontSize, lineHeightRatio = 1.15) {
|
|
return Math.round(fontSize * lineHeightRatio);
|
|
}
|
|
function toTextString(value) {
|
|
return String(value ?? "");
|
|
}
|
|
function appendEllipsis(text2) {
|
|
return text2.replace(TrimCharsRegex, "") + EllipsisChar;
|
|
}
|
|
function guardTextEdges(str) {
|
|
return TrimEdgeGuard + str + TrimEdgeGuard;
|
|
}
|
|
function unguardTextEdges(str) {
|
|
return str.replaceAll(TrimEdgeGuard, "");
|
|
}
|
|
function isTruncated(value) {
|
|
return isArray(value) ? isSegmentTruncated(value.at(-1)) : isTextTruncated(toTextString(value));
|
|
}
|
|
function isTextTruncated(str) {
|
|
return str.endsWith(EllipsisChar);
|
|
}
|
|
function isSegmentTruncated(segment) {
|
|
return toTextString(segment?.text).endsWith(EllipsisChar);
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/data/strings.ts
|
|
function joinFormatted(values, conjunction = "and", format = String, maxItems = Infinity) {
|
|
if (values.length === 0) {
|
|
return "";
|
|
} else if (values.length === 1) {
|
|
return format(values[0]);
|
|
}
|
|
values = values.map(format);
|
|
const lastValue = values.pop();
|
|
if (values.length >= maxItems) {
|
|
const remainingCount = values.length - (maxItems - 1);
|
|
return `${values.slice(0, maxItems - 1).join(", ")}, and ${remainingCount} more ${conjunction} ${lastValue}`;
|
|
}
|
|
return `${values.join(", ")} ${conjunction} ${lastValue}`;
|
|
}
|
|
function stringifyValue(value, maxLength = Infinity) {
|
|
if (typeof value === "number") {
|
|
if (Number.isNaN(value)) {
|
|
return "NaN";
|
|
} else if (value === Infinity) {
|
|
return "Infinity";
|
|
} else if (value === -Infinity) {
|
|
return "-Infinity";
|
|
}
|
|
}
|
|
const strValue = JSON.stringify(value) ?? typeof value;
|
|
if (strValue.length > maxLength) {
|
|
return `${strValue.slice(0, maxLength)}... (+${strValue.length - maxLength} characters)`;
|
|
}
|
|
return strValue;
|
|
}
|
|
function countLines(text2) {
|
|
let count = 1;
|
|
for (let i = 0; i < text2.length; i++) {
|
|
if (text2.codePointAt(i) === 10) {
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
function levenshteinDistance(a, b) {
|
|
if (a === b)
|
|
return 0;
|
|
const [shorter, longer] = a.length < b.length ? [a, b] : [b, a];
|
|
const m = shorter.length;
|
|
const n = longer.length;
|
|
let prevRow = new Array(m + 1).fill(0).map((_, i) => i);
|
|
let currRow = new Array(m + 1);
|
|
for (let i = 1; i <= n; i++) {
|
|
currRow[0] = i;
|
|
for (let j = 1; j <= m; j++) {
|
|
const cost = longer[i - 1] === shorter[j - 1] ? 0 : 1;
|
|
currRow[j] = Math.min(
|
|
prevRow[j] + 1,
|
|
// Deletion
|
|
currRow[j - 1] + 1,
|
|
// Insertion
|
|
prevRow[j - 1] + cost
|
|
// Substitution
|
|
);
|
|
}
|
|
[prevRow, currRow] = [currRow, prevRow];
|
|
}
|
|
return prevRow[m];
|
|
}
|
|
function kebabCase(a) {
|
|
return a.replaceAll(KEBAB_CASE_REGEX, (match, offset) => (offset > 0 ? "-" : "") + match.toLowerCase());
|
|
}
|
|
var KEBAB_CASE_REGEX = /[A-Z]+(?![a-z])|[A-Z]/g;
|
|
function toPlainText(text2, fallback = "") {
|
|
if (text2 == null) {
|
|
return fallback;
|
|
} else if (isArray(text2)) {
|
|
return text2.map((segment) => toTextString(segment.text)).join("");
|
|
} else if (isString(text2)) {
|
|
return text2;
|
|
} else {
|
|
return String(text2);
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/functions.ts
|
|
function debounce(callback2, waitMs = 0, options) {
|
|
const { leading = false, trailing = true, maxWait = Infinity } = options ?? {};
|
|
let timerId;
|
|
let startTime;
|
|
if (maxWait < waitMs) {
|
|
throw new Error("Value of maxWait cannot be lower than waitMs.");
|
|
}
|
|
function debounceCallback(...args) {
|
|
if (leading && !startTime) {
|
|
startTime = Date.now();
|
|
timerId = setTimeout(() => startTime = null, waitMs);
|
|
callback2(...args);
|
|
return;
|
|
}
|
|
let adjustedWaitMs = waitMs;
|
|
if (maxWait !== Infinity && startTime) {
|
|
const elapsedTime = Date.now() - startTime;
|
|
if (waitMs > maxWait - elapsedTime) {
|
|
adjustedWaitMs = maxWait - elapsedTime;
|
|
}
|
|
}
|
|
clearTimeout(timerId);
|
|
startTime ?? (startTime = Date.now());
|
|
timerId = setTimeout(() => {
|
|
startTime = null;
|
|
if (trailing) {
|
|
callback2(...args);
|
|
}
|
|
}, adjustedWaitMs);
|
|
}
|
|
return Object.assign(debounceCallback, {
|
|
cancel() {
|
|
clearTimeout(timerId);
|
|
startTime = null;
|
|
}
|
|
});
|
|
}
|
|
function throttle(callback2, waitMs, options) {
|
|
const { leading = true, trailing = true } = options ?? {};
|
|
let timerId;
|
|
let lastArgs;
|
|
let shouldWait = false;
|
|
function timeoutHandler() {
|
|
if (trailing && lastArgs) {
|
|
timerId = setTimeout(timeoutHandler, waitMs);
|
|
callback2(...lastArgs);
|
|
} else {
|
|
shouldWait = false;
|
|
}
|
|
lastArgs = null;
|
|
}
|
|
function throttleCallback(...args) {
|
|
if (shouldWait) {
|
|
lastArgs = args;
|
|
} else {
|
|
shouldWait = true;
|
|
timerId = setTimeout(timeoutHandler, waitMs);
|
|
if (leading) {
|
|
callback2(...args);
|
|
} else {
|
|
lastArgs = args;
|
|
}
|
|
}
|
|
}
|
|
return Object.assign(throttleCallback, {
|
|
cancel() {
|
|
clearTimeout(timerId);
|
|
shouldWait = false;
|
|
lastArgs = null;
|
|
}
|
|
});
|
|
}
|
|
function safeCall(callback2, args, errorPath = "") {
|
|
try {
|
|
return callback2(...args);
|
|
} catch (error2) {
|
|
const postfix = errorPath ? ` \`${errorPath}\`` : "";
|
|
warnOnce(`Uncaught exception in user callback${postfix}`, error2);
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-core/src/state/validation.ts
|
|
var descriptionSymbol = Symbol("description");
|
|
var requiredSymbol = Symbol("required");
|
|
var markedSymbol = Symbol("marked");
|
|
var undocumentedSymbol = Symbol("undocumented");
|
|
var unionSymbol = Symbol("union");
|
|
var similarOptionsMap = [
|
|
["placement", "position"],
|
|
["padding", "spacing", "gap"],
|
|
["color", "fill", "stroke"],
|
|
["whisker", "wick"],
|
|
["src", "url"],
|
|
["width", "thickness"]
|
|
].reduce((map, words) => {
|
|
for (const word of words) {
|
|
map.set(word.toLowerCase(), new Set(words.filter((w) => w !== word)));
|
|
}
|
|
return map;
|
|
}, /* @__PURE__ */ new Map());
|
|
var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
|
|
ErrorType2["Invalid"] = "invalid";
|
|
ErrorType2["Required"] = "required";
|
|
ErrorType2["Unknown"] = "unknown";
|
|
return ErrorType2;
|
|
})(ErrorType || {});
|
|
function extendPath(path, key) {
|
|
if (isFiniteNumber(key)) {
|
|
return `${path}[${key}]`;
|
|
}
|
|
return path ? `${path}.${key}` : key;
|
|
}
|
|
var ValidationError = class {
|
|
constructor(type, description, value, path, key) {
|
|
this.type = type;
|
|
this.description = description;
|
|
this.value = value;
|
|
this.path = path;
|
|
this.key = key;
|
|
}
|
|
setUnionType(unionType, path) {
|
|
if (this.path.startsWith(path)) {
|
|
const suffix = this.path.slice(path.length);
|
|
this.altPath = `${path}[type=${unionType}]${suffix}`;
|
|
}
|
|
}
|
|
getPrefix() {
|
|
const { altPath: path = this.path, key } = this;
|
|
if (!path && !key)
|
|
return "Value";
|
|
return `Option \`${key ? extendPath(path, key) : path}\``;
|
|
}
|
|
toString() {
|
|
const { description = "unknown", type, value } = this;
|
|
if (type === "required" /* Required */ && value == null) {
|
|
return `${this.getPrefix()} is required and has not been provided; expecting ${description}, ignoring.`;
|
|
}
|
|
return `${this.getPrefix()} cannot be set to \`${stringifyValue(value, 50)}\`; expecting ${description}, ignoring.`;
|
|
}
|
|
};
|
|
var UnknownError = class extends ValidationError {
|
|
constructor(suggestions, value, path, key) {
|
|
super("unknown" /* Unknown */, void 0, value, path, key);
|
|
this.suggestions = suggestions;
|
|
this.key = key;
|
|
}
|
|
getPrefix() {
|
|
return `Unknown option \`${extendPath(this.altPath ?? this.path, this.key)}\``;
|
|
}
|
|
getPostfix() {
|
|
const suggestions = joinFormatted(findSuggestions(this.key, this.suggestions), "or", (val) => `\`${val}\``);
|
|
return suggestions ? `; Did you mean ${suggestions}? Ignoring.` : ", ignoring.";
|
|
}
|
|
toString() {
|
|
return `${this.getPrefix()}${this.getPostfix()}`;
|
|
}
|
|
};
|
|
function validate(options, optionsDefs2, path = "") {
|
|
if (!isObject(options)) {
|
|
return { cleared: null, invalid: [new ValidationError("required" /* Required */, "an object", options, path)] };
|
|
}
|
|
const cleared = {};
|
|
const invalid = [];
|
|
const optionsKeys = new Set(Object.keys(options));
|
|
const unusedKeys = [];
|
|
if (unionSymbol in optionsDefs2) {
|
|
const validTypes = Object.keys(optionsDefs2);
|
|
const defaultType = optionsDefs2[unionSymbol];
|
|
if (options.type != null && validTypes.includes(options.type) || options.type == null && defaultType != null) {
|
|
const { type = defaultType, ...rest } = options;
|
|
const nestedResult = validate(rest, optionsDefs2[type], path);
|
|
Object.assign(cleared, { type }, nestedResult.cleared);
|
|
for (const error2 of nestedResult.invalid) {
|
|
error2.setUnionType(type, path);
|
|
}
|
|
invalid.push(...nestedResult.invalid);
|
|
} else {
|
|
const keywords = joinFormatted(validTypes, "or", (val) => `'${val}'`);
|
|
invalid.push(
|
|
new ValidationError("required" /* Required */, `a keyword such as ${keywords}`, options.type, path, "type")
|
|
);
|
|
}
|
|
return { cleared, invalid };
|
|
}
|
|
for (const key of Object.keys(optionsDefs2)) {
|
|
const validatorOrDefs = optionsDefs2[key];
|
|
const required3 = validatorOrDefs[requiredSymbol];
|
|
const value = options[key];
|
|
optionsKeys.delete(key);
|
|
if (value === void 0) {
|
|
if (!validatorOrDefs[undocumentedSymbol]) {
|
|
unusedKeys.push(key);
|
|
}
|
|
if (!required3)
|
|
continue;
|
|
}
|
|
const keyPath = extendPath(path, key);
|
|
if (isFunction(validatorOrDefs)) {
|
|
const context = { options, path: keyPath };
|
|
const validatorResult = validatorOrDefs(value, context);
|
|
const objectResult = typeof validatorResult === "object";
|
|
if (objectResult) {
|
|
invalid.push(...validatorResult.invalid);
|
|
if (validatorResult.valid) {
|
|
cleared[key] = validatorResult.cleared;
|
|
continue;
|
|
} else if (hasRequiredInPath(validatorResult.invalid, keyPath)) {
|
|
continue;
|
|
}
|
|
} else if (validatorResult) {
|
|
cleared[key] = value;
|
|
continue;
|
|
}
|
|
invalid.push(
|
|
new ValidationError(
|
|
required3 ? "required" /* Required */ : "invalid" /* Invalid */,
|
|
validatorOrDefs[descriptionSymbol],
|
|
value,
|
|
path,
|
|
key
|
|
)
|
|
);
|
|
} else {
|
|
const nestedResult = validate(value, validatorOrDefs, keyPath);
|
|
if (nestedResult.cleared != null) {
|
|
cleared[key] = nestedResult.cleared;
|
|
}
|
|
invalid.push(...nestedResult.invalid);
|
|
}
|
|
}
|
|
for (const key of optionsKeys) {
|
|
const value = options[key];
|
|
if (value === void 0)
|
|
continue;
|
|
invalid.push(new UnknownError(unusedKeys, value, path, key));
|
|
}
|
|
return { cleared, invalid };
|
|
}
|
|
function findSuggestions(value, suggestions, maxDistance = 2) {
|
|
const lowerCaseValue = value.toLowerCase();
|
|
const similarValues = similarOptionsMap.get(lowerCaseValue);
|
|
return suggestions.filter((key) => {
|
|
const lowerCaseKey = key.toLowerCase();
|
|
return similarValues?.has(key) === true || lowerCaseKey.includes(lowerCaseValue) || levenshteinDistance(lowerCaseValue, lowerCaseKey) <= maxDistance;
|
|
});
|
|
}
|
|
function attachDescription(validatorOrDefs, description) {
|
|
if (isFunction(validatorOrDefs)) {
|
|
let clonedValidator2 = function(value, context) {
|
|
return validatorOrDefs(value, context);
|
|
};
|
|
var clonedValidator = clonedValidator2;
|
|
clonedValidator2[descriptionSymbol] = description;
|
|
return clonedValidator2;
|
|
} else {
|
|
return { ...validatorOrDefs, [descriptionSymbol]: description };
|
|
}
|
|
}
|
|
function required(validatorOrDefs) {
|
|
return Object.assign(
|
|
isFunction(validatorOrDefs) ? (value, context) => validatorOrDefs(value, context) : optionsDefs(validatorOrDefs),
|
|
{ [requiredSymbol]: true, [descriptionSymbol]: validatorOrDefs[descriptionSymbol] }
|
|
);
|
|
}
|
|
function undocumented(validatorOrDefs) {
|
|
return Object.assign(
|
|
isFunction(validatorOrDefs) ? (value, context) => validatorOrDefs(value, context) : optionsDefs(validatorOrDefs),
|
|
{ [undocumentedSymbol]: true, [descriptionSymbol]: validatorOrDefs[descriptionSymbol] }
|
|
);
|
|
}
|
|
var optionsDefs = (defs, description = "an object", failAll = false) => attachDescription((value, context) => {
|
|
const result = validate(value, defs, context.path);
|
|
const valid = !hasRequiredInPath(result.invalid, context.path);
|
|
return { valid, cleared: valid || !failAll ? result.cleared : null, invalid: result.invalid };
|
|
}, description);
|
|
var typeUnion = (defs, description, defaultType) => ({
|
|
...defs,
|
|
[descriptionSymbol]: description,
|
|
[unionSymbol]: defaultType
|
|
});
|
|
var and = (...validators) => attachDescription(
|
|
(value, context) => {
|
|
const invalid = [];
|
|
for (const validator of validators) {
|
|
const result = validator(value, context);
|
|
if (typeof result === "object") {
|
|
invalid.push(...result.invalid);
|
|
if (!result.valid) {
|
|
return { valid: false, cleared: value, invalid };
|
|
}
|
|
value = result.cleared;
|
|
} else if (!result) {
|
|
return false;
|
|
}
|
|
}
|
|
return { valid: true, cleared: value, invalid };
|
|
},
|
|
joinFormatted(
|
|
validators.filter((v) => !v[undocumentedSymbol]).map((v) => v[descriptionSymbol]).filter(isDefined),
|
|
"and"
|
|
)
|
|
);
|
|
var or = (...validators) => attachDescription(
|
|
(value, context) => {
|
|
for (const validator of validators) {
|
|
const result = validator(value, context);
|
|
if (typeof result === "object" ? result.valid : result) {
|
|
return result;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
joinFormatted(
|
|
validators.filter((v) => !v[undocumentedSymbol]).map((v) => v[descriptionSymbol]).filter(isDefined),
|
|
"or"
|
|
)
|
|
);
|
|
var isComparable = (value) => isFiniteNumber(value) || isValidDate(value);
|
|
var isValidDateValue = (value) => isDate(value) || (isFiniteNumber(value) || isString(value)) && isValidDate(new Date(value));
|
|
var array = attachDescription(isArray, "an array");
|
|
var boolean = attachDescription(isBoolean, "a boolean");
|
|
var callback = attachDescription(isFunction, "a function");
|
|
var color = attachDescription(isColor, "a color string");
|
|
var date = attachDescription(isValidDateValue, "a date");
|
|
var defined = attachDescription(isDefined, "a defined value");
|
|
var number = attachDescription(isFiniteNumber, "a number");
|
|
var object = attachDescription(isObject, "an object");
|
|
var string = attachDescription(isString, "a string");
|
|
var htmlElement = attachDescription(isHtmlElement, "an html element");
|
|
var arrayLength = (minLength, maxLength = Infinity) => {
|
|
let message;
|
|
if (maxLength === Infinity) {
|
|
message = `an array of at least ${minLength} items`;
|
|
} else if (minLength === maxLength) {
|
|
message = `an array of exactly ${minLength} items`;
|
|
} else if (minLength === 0) {
|
|
message = `an array of no more than ${maxLength} items`;
|
|
} else {
|
|
message = `an array of at least ${minLength} and no more than ${maxLength} items`;
|
|
}
|
|
return attachDescription(
|
|
(value) => isArray(value) && value.length >= minLength && value.length <= maxLength,
|
|
message
|
|
);
|
|
};
|
|
var stringLength = (minLength, maxLength = Infinity) => {
|
|
let message;
|
|
if (maxLength === Infinity) {
|
|
message = `a string of at least ${minLength} characters`;
|
|
} else if (minLength === maxLength) {
|
|
message = `an string of exactly ${minLength} characters`;
|
|
} else if (minLength === 0) {
|
|
message = `an string of no more than ${maxLength} characters`;
|
|
} else {
|
|
message = `an string of at least ${minLength} and no more than ${maxLength} characters`;
|
|
}
|
|
return attachDescription(
|
|
(value) => isString(value) && value.length >= minLength && value.length <= maxLength,
|
|
message
|
|
);
|
|
};
|
|
var numberMin = (min, inclusive = true) => attachDescription(
|
|
(value) => isFiniteNumber(value) && (value > min || inclusive && value === min),
|
|
`a number greater than ${inclusive ? "or equal to " : ""}${min}`
|
|
);
|
|
var numberRange = (min, max) => attachDescription(
|
|
(value) => isFiniteNumber(value) && value >= min && value <= max,
|
|
`a number between ${min} and ${max} inclusive`
|
|
);
|
|
var positiveNumber = numberMin(0);
|
|
var positiveNumberNonZero = numberMin(0, false);
|
|
var ratio = numberRange(0, 1);
|
|
var lessThan = (otherField) => attachDescription(
|
|
(value, { options }) => !isComparable(value) || !isComparable(options[otherField]) || value < options[otherField],
|
|
`the value to be less than \`${otherField}\``
|
|
);
|
|
var lessThanOrEqual = (otherField) => attachDescription(
|
|
(value, { options }) => !isComparable(value) || !isComparable(options[otherField]) || value <= options[otherField],
|
|
`the value to be less than or equal to \`${otherField}\``
|
|
);
|
|
var greaterThan = (otherField) => attachDescription(
|
|
(value, { options }) => !isComparable(value) || !isComparable(options[otherField]) || value > options[otherField],
|
|
`the value to be greater than \`${otherField}\``
|
|
);
|
|
function union(...allowed) {
|
|
if (isObject(allowed[0])) {
|
|
allowed = Object.values(allowed[0]);
|
|
}
|
|
const keywords = joinFormatted(allowed, "or", (value) => `'${value}'`);
|
|
return attachDescription((value) => allowed.includes(value), `a keyword such as ${keywords}`);
|
|
}
|
|
function strictUnion() {
|
|
return union;
|
|
}
|
|
var constant = (allowed) => attachDescription((value) => allowed === value, `the value ${JSON.stringify(allowed)}`);
|
|
var instanceOf = (instanceType, description) => attachDescription((value) => value instanceof instanceType, description ?? `an instance of ${instanceType.name}`);
|
|
var arrayOf = (validator, description, strict = true) => attachDescription(
|
|
(value, context) => {
|
|
if (!isArray(value))
|
|
return false;
|
|
let valid = strict;
|
|
const cleared = [];
|
|
const invalid = [];
|
|
const updateValidity = (result) => {
|
|
valid = strict ? valid && result : valid || result;
|
|
};
|
|
if (value.length === 0) {
|
|
return { valid: true, cleared, invalid };
|
|
}
|
|
for (let i = 0; i < value.length; i++) {
|
|
const options = value[i];
|
|
const result = validator(options, { options, path: `${context.path}[${i}]` });
|
|
if (typeof result === "object") {
|
|
updateValidity(result.valid);
|
|
invalid.push(...result.invalid);
|
|
if (result.cleared != null) {
|
|
cleared.push(result.cleared);
|
|
}
|
|
} else {
|
|
updateValidity(result);
|
|
if (result) {
|
|
cleared.push(options);
|
|
}
|
|
}
|
|
}
|
|
return { valid, cleared: valid || !strict ? cleared : null, invalid };
|
|
},
|
|
description ?? `${validator[descriptionSymbol]} array`
|
|
);
|
|
var arrayOfDefs = (defs, description = "an object array") => attachDescription((value, context) => {
|
|
if (!isArray(value))
|
|
return false;
|
|
const cleared = [];
|
|
const invalid = [];
|
|
for (let i = 0; i < value.length; i++) {
|
|
const indexPath = `${context.path}[${i}]`;
|
|
const result = validate(value[i], defs, indexPath);
|
|
if (!hasRequiredInPath(result.invalid, indexPath)) {
|
|
cleared.push(result.cleared);
|
|
}
|
|
invalid.push(...result.invalid);
|
|
}
|
|
return { valid: true, cleared, invalid };
|
|
}, description);
|
|
var callbackOf = (validator, description) => attachDescription((value, context) => {
|
|
if (!isFunction(value))
|
|
return false;
|
|
if (markedSymbol in value)
|
|
return true;
|
|
const validatorDescription = description ?? validator[descriptionSymbol];
|
|
const cbWithValidation = Object.assign(
|
|
(...args) => {
|
|
const result = safeCall(value, args);
|
|
if (result == null)
|
|
return;
|
|
const validatorResult = validator(result, { options: result, path: "" });
|
|
if (typeof validatorResult === "object") {
|
|
warnCallbackErrors(validatorResult, context, validatorDescription, result);
|
|
if (validatorResult.valid) {
|
|
return validatorResult.cleared;
|
|
}
|
|
} else if (validatorResult) {
|
|
return result;
|
|
} else {
|
|
warnOnce(
|
|
`Callback \`${context.path}\` returned an invalid value \`${stringifyValue(result, 50)}\`; expecting ${validatorDescription}, ignoring.`
|
|
);
|
|
}
|
|
},
|
|
{ [markedSymbol]: true }
|
|
);
|
|
return { valid: true, cleared: cbWithValidation, invalid: [] };
|
|
}, "a function");
|
|
var callbackDefs = (defs, description = "an object") => attachDescription((value, context) => {
|
|
if (!isFunction(value))
|
|
return false;
|
|
if (markedSymbol in value)
|
|
return true;
|
|
const validatorDescription = description;
|
|
const cbWithValidation = Object.assign(
|
|
(...args) => {
|
|
const result = safeCall(value, args, context.path);
|
|
if (result == null)
|
|
return;
|
|
const validatorResult = validate(result, defs);
|
|
warnCallbackErrors(validatorResult, context, validatorDescription, result);
|
|
return validatorResult.cleared;
|
|
},
|
|
{ [markedSymbol]: true }
|
|
);
|
|
return { valid: true, cleared: cbWithValidation, invalid: [] };
|
|
}, "a function");
|
|
function hasRequiredInPath(errors, rootPath) {
|
|
return errors.some((error2) => error2.type === "required" /* Required */ && error2.path === rootPath);
|
|
}
|
|
function warnCallbackErrors(validatorResult, context, description, result) {
|
|
if (validatorResult.invalid.length === 0)
|
|
return;
|
|
if (isArray(result)) {
|
|
const expectedDescription = description ?? validatorResult.invalid[0]?.description ?? "a valid value";
|
|
return warnOnce(
|
|
`Callback \`${context.path}\` returned an invalid value \`${stringifyValue(result, 50)}\`; expecting ${expectedDescription}, ignoring.`
|
|
);
|
|
}
|
|
for (const error2 of validatorResult.invalid) {
|
|
if (error2 instanceof UnknownError) {
|
|
return warnOnce(
|
|
`Callback \`${context.path}\` returned an unknown property \`${extendPath(error2.path, error2.key)}\`${error2.getPostfix()}`
|
|
);
|
|
}
|
|
const errorValue = stringifyValue(error2.value, 50);
|
|
warnOnce(
|
|
error2.key ? `Callback \`${context.path}\` returned an invalid property \`${extendPath(error2.path, error2.key)}\`: \`${errorValue}\`; expecting ${error2.description}, ignoring.` : `Callback \`${context.path}\` returned an invalid value \`${errorValue}\`; expecting ${description ?? error2.description}, ignoring.`
|
|
);
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/data/numbers.ts
|
|
function clamp(min, value, max) {
|
|
return Math.min(max, Math.max(min, value));
|
|
}
|
|
function inRange(value, range3, epsilon = 1e-10) {
|
|
return value >= range3[0] - epsilon && value <= range3[1] + epsilon;
|
|
}
|
|
function isNumberEqual(a, b, epsilon = 1e-10) {
|
|
return a === b || Math.abs(a - b) < epsilon;
|
|
}
|
|
function isNegative(value) {
|
|
return Math.sign(value) === -1 || Object.is(value, -0);
|
|
}
|
|
function isInteger(value) {
|
|
return value % 1 === 0;
|
|
}
|
|
function roundTo(value, decimals = 2) {
|
|
const base = 10 ** decimals;
|
|
return Math.round(value * base) / base;
|
|
}
|
|
function ceilTo(value, decimals = 2) {
|
|
const base = 10 ** decimals;
|
|
return Math.ceil(value * base) / base;
|
|
}
|
|
function modulus(n, m) {
|
|
return Math.floor(n % m + (n < 0 ? Math.abs(m) : 0));
|
|
}
|
|
function countFractionDigits(value) {
|
|
if (Math.floor(value) === value) {
|
|
return 0;
|
|
}
|
|
let valueString = String(value);
|
|
let exponent = 0;
|
|
if (value < 1e-6 || value >= 1e21) {
|
|
let exponentString;
|
|
[valueString, exponentString] = valueString.split("e");
|
|
if (exponentString != null) {
|
|
exponent = Number(exponentString);
|
|
}
|
|
}
|
|
const decimalPlaces2 = valueString.split(".")[1]?.length ?? 0;
|
|
return Math.max(decimalPlaces2 - exponent, 0);
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/format/numberFormat.ts
|
|
var formatRegEx = /^(?:(.)?([<>=^]))?([+\-( ])?([$€£¥₣₹#])?(0)?(\d+)?(,)?(?:\.(\d+))?(~)?([%a-z])?$/i;
|
|
var surroundedRegEx = /^((?:[^#]|#[^{])*)#{([^}]+)}(.*)$/;
|
|
function isValidNumberFormat(value) {
|
|
if (!isString(value))
|
|
return false;
|
|
const match = surroundedRegEx.exec(value);
|
|
return formatRegEx.test(match ? match[2] : value);
|
|
}
|
|
function parseNumberFormat(format) {
|
|
let prefix;
|
|
let suffix;
|
|
const surrounded = surroundedRegEx.exec(format);
|
|
if (surrounded) {
|
|
[, prefix, format, suffix] = surrounded;
|
|
}
|
|
const match = formatRegEx.exec(format);
|
|
if (!match) {
|
|
warnOnce(`The number formatter is invalid: ${format}`);
|
|
return;
|
|
}
|
|
const [, fill, align2, sign, symbol, zero, width2, comma, precision, trim, type] = match;
|
|
return {
|
|
fill,
|
|
align: align2,
|
|
sign,
|
|
symbol,
|
|
zero,
|
|
width: Number.parseInt(width2),
|
|
comma,
|
|
precision: Number.parseInt(precision),
|
|
trim: Boolean(trim),
|
|
type,
|
|
prefix,
|
|
suffix
|
|
};
|
|
}
|
|
function createNumberFormatter(format) {
|
|
const options = typeof format === "string" ? parseNumberFormat(format) : format;
|
|
if (options == null)
|
|
return;
|
|
const { fill, align: align2, sign = "-", symbol, zero, width: width2, comma, type, prefix = "", suffix = "", precision } = options;
|
|
let { trim } = options;
|
|
const precisionIsNaN = precision == null || Number.isNaN(precision);
|
|
let formatBody;
|
|
if (!type) {
|
|
formatBody = decimalTypes["g"];
|
|
trim = true;
|
|
} else if (type in decimalTypes && type in integerTypes) {
|
|
formatBody = precisionIsNaN ? integerTypes[type] : decimalTypes[type];
|
|
} else if (type in decimalTypes) {
|
|
formatBody = decimalTypes[type];
|
|
} else if (type in integerTypes) {
|
|
formatBody = integerTypes[type];
|
|
} else {
|
|
throw new Error(`The number formatter type is invalid: ${type}`);
|
|
}
|
|
const defaultFormatterPrecision = type ? 6 : 12;
|
|
let formatterPrecision;
|
|
if (!precisionIsNaN) {
|
|
formatterPrecision = precision;
|
|
}
|
|
let padAlign = align2;
|
|
let padFill = fill;
|
|
if (zero) {
|
|
padFill ?? (padFill = "0");
|
|
padAlign ?? (padAlign = "=");
|
|
}
|
|
return (n, fractionDigits) => {
|
|
let effectivePrecision;
|
|
if (formatterPrecision != null) {
|
|
effectivePrecision = formatterPrecision;
|
|
} else if (type === "f" || type === "%") {
|
|
effectivePrecision = fractionDigits ?? defaultFormatterPrecision;
|
|
} else if (type) {
|
|
effectivePrecision = defaultFormatterPrecision;
|
|
} else {
|
|
effectivePrecision = fractionDigits ?? defaultFormatterPrecision;
|
|
}
|
|
let result = formatBody(n, effectivePrecision);
|
|
if (trim) {
|
|
result = removeTrailingZeros(result);
|
|
}
|
|
if (comma) {
|
|
result = insertSeparator(result, comma);
|
|
}
|
|
const symbolPrefix = getSymbolPrefix(symbol, type);
|
|
const symbolPrefixLength = symbolPrefix?.length ?? 0;
|
|
if (symbolPrefix) {
|
|
result = `${symbolPrefix}${result}`;
|
|
}
|
|
if (type === "s") {
|
|
result = `${result}${getSIPrefix(n)}`;
|
|
}
|
|
if (type === "%" || type === "p") {
|
|
result = `${result}%`;
|
|
}
|
|
const { value: signedResult, prefixLength: signPrefixLength } = addSign(n, result, sign);
|
|
const totalPrefixLength = signPrefixLength + symbolPrefixLength;
|
|
let output = signedResult;
|
|
if (width2 != null && !Number.isNaN(width2)) {
|
|
output = addPadding(output, width2, padFill ?? " ", padAlign, totalPrefixLength);
|
|
}
|
|
output = `${prefix}${output}${suffix}`;
|
|
return output;
|
|
};
|
|
}
|
|
var integerTypes = {
|
|
b: (n) => absFloor(n).toString(2),
|
|
c: (n) => String.fromCodePoint(n),
|
|
d: (n) => Math.round(Math.abs(n)).toFixed(0),
|
|
o: (n) => absFloor(n).toString(8),
|
|
x: (n) => absFloor(n).toString(16),
|
|
X: (n) => integerTypes.x(n).toUpperCase(),
|
|
n: (n) => integerTypes.d(n),
|
|
"%": (n) => `${absFloor(n * 100).toFixed(0)}`
|
|
};
|
|
var decimalTypes = {
|
|
e: (n, f) => Math.abs(n).toExponential(f),
|
|
E: (n, f) => decimalTypes.e(n, f).toUpperCase(),
|
|
f: (n, f) => Math.abs(n).toFixed(f),
|
|
F: (n, f) => decimalTypes.f(n, f).toUpperCase(),
|
|
g: (n, f) => {
|
|
if (n === 0) {
|
|
return "0";
|
|
}
|
|
const a = Math.abs(n);
|
|
const p = Math.floor(Math.log10(a));
|
|
if (p >= -4 && p < f) {
|
|
return a.toFixed(f - 1 - p);
|
|
}
|
|
return a.toExponential(f - 1);
|
|
},
|
|
G: (n, f) => decimalTypes.g(n, f).toUpperCase(),
|
|
n: (n, f) => decimalTypes.g(n, f),
|
|
p: (n, f) => decimalTypes.r(n * 100, f),
|
|
r: (n, f) => {
|
|
if (n === 0) {
|
|
return "0";
|
|
}
|
|
const a = Math.abs(n);
|
|
const p = Math.floor(Math.log10(a));
|
|
const q = p - (f - 1);
|
|
if (q <= 0) {
|
|
return a.toFixed(-q);
|
|
}
|
|
const x = 10 ** q;
|
|
return (Math.round(a / x) * x).toFixed();
|
|
},
|
|
s: (n, f) => {
|
|
const p = getSIPrefixPower(n);
|
|
return decimalTypes.r(n / 10 ** p, f);
|
|
},
|
|
"%": (n, f) => decimalTypes.f(n * 100, f)
|
|
};
|
|
var minSIPrefix = -24;
|
|
var maxSIPrefix = 24;
|
|
var siPrefixes = {
|
|
[minSIPrefix]: "y",
|
|
[-21]: "z",
|
|
[-18]: "a",
|
|
[-15]: "f",
|
|
[-12]: "p",
|
|
[-9]: "n",
|
|
[-6]: "\xB5",
|
|
[-3]: "m",
|
|
[0]: "",
|
|
[3]: "k",
|
|
[6]: "M",
|
|
[9]: "G",
|
|
[12]: "T",
|
|
[15]: "P",
|
|
[18]: "E",
|
|
[21]: "Z",
|
|
[maxSIPrefix]: "Y"
|
|
};
|
|
var minusSign = "\u2212";
|
|
function absFloor(n) {
|
|
return Math.floor(Math.abs(n));
|
|
}
|
|
function removeTrailingZeros(numString) {
|
|
if (!numString.endsWith("0") || !numString.includes("."))
|
|
return numString;
|
|
let endIndex = numString.length - 1;
|
|
while (endIndex > 0) {
|
|
if (numString[endIndex] == "0") {
|
|
endIndex -= 1;
|
|
} else if (numString[endIndex] == ".") {
|
|
endIndex -= 1;
|
|
break;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return numString.substring(0, endIndex + 1);
|
|
}
|
|
function insertSeparator(numString, separator) {
|
|
let dotIndex = numString.indexOf(".");
|
|
if (dotIndex < 0) {
|
|
dotIndex = numString.length;
|
|
}
|
|
const integerChars = numString.substring(0, dotIndex).split("");
|
|
const fractionalPart = numString.substring(dotIndex);
|
|
for (let i = integerChars.length - 3; i > 0; i -= 3) {
|
|
integerChars.splice(i, 0, separator);
|
|
}
|
|
return `${integerChars.join("")}${fractionalPart}`;
|
|
}
|
|
function getSIPrefix(n) {
|
|
return siPrefixes[getSIPrefixPower(n)];
|
|
}
|
|
function getSIPrefixPower(n) {
|
|
return clamp(minSIPrefix, n ? Math.floor(Math.log10(Math.abs(n)) / 3) * 3 : 0, maxSIPrefix);
|
|
}
|
|
function addSign(num, numString, signType = "") {
|
|
if (signType === "(") {
|
|
if (num >= 0) {
|
|
return { value: numString, prefixLength: 0 };
|
|
}
|
|
return { value: `(${numString})`, prefixLength: 1 };
|
|
}
|
|
let signPrefix = "";
|
|
if (num < 0) {
|
|
signPrefix = minusSign;
|
|
} else if (signType === "+") {
|
|
signPrefix = "+";
|
|
} else if (signType === " ") {
|
|
signPrefix = " ";
|
|
}
|
|
return { value: `${signPrefix}${numString}`, prefixLength: signPrefix.length };
|
|
}
|
|
function addPadding(numString, width2, fill = " ", align2 = ">", prefixLength = 0) {
|
|
const padSize = width2 - numString.length;
|
|
if (padSize <= 0) {
|
|
return numString;
|
|
}
|
|
const padding2 = fill.repeat(padSize);
|
|
if (align2 === "=") {
|
|
const clampedPrefix = Math.min(Math.max(prefixLength, 0), numString.length);
|
|
const start2 = numString.slice(0, clampedPrefix);
|
|
const rest = numString.slice(clampedPrefix);
|
|
return `${start2}${padding2}${rest}`;
|
|
}
|
|
if (align2 === ">" || !align2) {
|
|
return padding2 + numString;
|
|
} else if (align2 === "<") {
|
|
return `${numString}${padding2}`;
|
|
} else if (align2 === "^") {
|
|
const padLeft = Math.ceil(padSize / 2);
|
|
const padRight = Math.floor(padSize / 2);
|
|
return `${fill.repeat(padLeft)}${numString}${fill.repeat(padRight)}`;
|
|
}
|
|
return padding2 + numString;
|
|
}
|
|
function getSymbolPrefix(symbol, type) {
|
|
if (symbol === "#") {
|
|
switch (type) {
|
|
case "b":
|
|
return "0b";
|
|
case "o":
|
|
return "0o";
|
|
case "x":
|
|
return "0x";
|
|
case "X":
|
|
return "0X";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
return symbol ?? "";
|
|
}
|
|
|
|
// packages/ag-charts-core/src/config/optionsDefaults.ts
|
|
var themeOperator = (value) => {
|
|
if (!isObject(value))
|
|
return false;
|
|
const keys = Object.keys(value);
|
|
return keys.length === 1 && keys[0].startsWith("$");
|
|
};
|
|
var colorStop = optionsDefs({ color, stop: ratio }, "");
|
|
var colorStopsOrderValidator = attachDescription((value) => {
|
|
let lastStop = -Infinity;
|
|
for (const item of value) {
|
|
if (item?.stop != null) {
|
|
if (item.stop < lastStop) {
|
|
return false;
|
|
}
|
|
lastStop = item.stop;
|
|
}
|
|
}
|
|
return true;
|
|
}, "colour stops to be defined in ascending order");
|
|
var gradientColorStops = and(arrayLength(2), arrayOf(colorStop), colorStopsOrderValidator);
|
|
var gradientBounds = union("axis", "item", "series");
|
|
var gradientStrictDefs = {
|
|
type: required(constant("gradient")),
|
|
colorStops: required(gradientColorStops),
|
|
rotation: number,
|
|
// @ts-expect-error undocumented option
|
|
gradient: undocumented(union("linear", "radial", "conic")),
|
|
bounds: undocumented(gradientBounds),
|
|
reverse: undocumented(boolean),
|
|
colorSpace: undocumented(union("rgb", "oklch"))
|
|
};
|
|
var gradientStrict = optionsDefs(
|
|
gradientStrictDefs,
|
|
"a gradient object with colour stops"
|
|
);
|
|
var strokeOptionsDef = {
|
|
stroke: color,
|
|
strokeWidth: positiveNumber,
|
|
strokeOpacity: ratio
|
|
};
|
|
var fillGradientDefaults = optionsDefs({
|
|
type: required(constant("gradient")),
|
|
gradient: required(union("linear", "radial", "conic")),
|
|
bounds: required(gradientBounds),
|
|
colorStops: required(or(gradientColorStops, and(arrayLength(2), arrayOf(color)))),
|
|
rotation: required(number),
|
|
reverse: required(boolean),
|
|
colorSpace: required(union("rgb", "oklch"))
|
|
});
|
|
var fillPatternDefaults = optionsDefs({
|
|
type: required(constant("pattern")),
|
|
pattern: required(
|
|
union(
|
|
"vertical-lines",
|
|
"horizontal-lines",
|
|
"forward-slanted-lines",
|
|
"backward-slanted-lines",
|
|
"circles",
|
|
"squares",
|
|
"triangles",
|
|
"diamonds",
|
|
"stars",
|
|
"hearts",
|
|
"crosses"
|
|
)
|
|
),
|
|
path: stringLength(2),
|
|
width: required(positiveNumber),
|
|
height: required(positiveNumber),
|
|
fill: required(color),
|
|
fillOpacity: required(ratio),
|
|
backgroundFill: required(color),
|
|
backgroundFillOpacity: required(ratio),
|
|
padding: required(positiveNumber),
|
|
rotation: required(number),
|
|
scale: required(positiveNumber),
|
|
stroke: required(color),
|
|
strokeWidth: required(positiveNumber),
|
|
strokeOpacity: required(ratio)
|
|
});
|
|
var fillImageDefaults = optionsDefs({
|
|
type: required(constant("image")),
|
|
url: string,
|
|
width: positiveNumber,
|
|
height: positiveNumber,
|
|
rotation: required(number),
|
|
backgroundFill: required(color),
|
|
backgroundFillOpacity: ratio,
|
|
fit: required(union("stretch", "contain", "cover")),
|
|
repeat: required(union("repeat", "repeat-x", "repeat-y", "no-repeat"))
|
|
});
|
|
var colorObjectDefs = {
|
|
// @ts-expect-error undocumented option
|
|
gradient: {
|
|
colorStops: gradientColorStops,
|
|
rotation: number,
|
|
gradient: undocumented(union("linear", "radial", "conic")),
|
|
bounds: undocumented(gradientBounds),
|
|
reverse: undocumented(boolean),
|
|
colorSpace: undocumented(union("rgb", "oklch"))
|
|
},
|
|
pattern: {
|
|
pattern: union(
|
|
"vertical-lines",
|
|
"horizontal-lines",
|
|
"forward-slanted-lines",
|
|
"backward-slanted-lines",
|
|
"circles",
|
|
"squares",
|
|
"triangles",
|
|
"diamonds",
|
|
"stars",
|
|
"hearts",
|
|
"crosses"
|
|
),
|
|
path: stringLength(2),
|
|
width: positiveNumber,
|
|
height: positiveNumber,
|
|
rotation: number,
|
|
scale: positiveNumber,
|
|
fill: color,
|
|
fillOpacity: ratio,
|
|
backgroundFill: color,
|
|
backgroundFillOpacity: ratio,
|
|
...strokeOptionsDef,
|
|
padding: undocumented(positiveNumber)
|
|
},
|
|
image: {
|
|
url: required(string),
|
|
backgroundFill: color,
|
|
backgroundFillOpacity: ratio,
|
|
width: positiveNumber,
|
|
height: positiveNumber,
|
|
fit: union("stretch", "contain", "cover", "none"),
|
|
repeat: union("repeat", "repeat-x", "repeat-y", "no-repeat"),
|
|
rotation: number
|
|
}
|
|
};
|
|
var colorObject = typeUnion(colorObjectDefs, "a color object");
|
|
var colorUnion = or(color, optionsDefs(colorObject, "a color object"));
|
|
var fillOptionsDef = {
|
|
fill: colorUnion,
|
|
fillOpacity: ratio
|
|
};
|
|
fillOptionsDef.fillGradientDefaults = undocumented(fillGradientDefaults);
|
|
fillOptionsDef.fillPatternDefaults = undocumented(fillPatternDefaults);
|
|
fillOptionsDef.fillImageDefaults = undocumented(fillImageDefaults);
|
|
var lineDashOptionsDef = {
|
|
lineDash: arrayOf(positiveNumber),
|
|
lineDashOffset: number
|
|
};
|
|
var barHighlightOptionsDef = {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
opacity: ratio,
|
|
cornerRadius: positiveNumber
|
|
};
|
|
var lineHighlightOptionsDef = {
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
opacity: ratio
|
|
};
|
|
var shapeHighlightOptionsDef = {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
opacity: ratio
|
|
};
|
|
function highlightOptionsDef(itemHighlightOptionsDef) {
|
|
return {
|
|
enabled: boolean,
|
|
range: union("tooltip", "node"),
|
|
highlightedItem: itemHighlightOptionsDef,
|
|
unhighlightedItem: itemHighlightOptionsDef
|
|
};
|
|
}
|
|
function multiSeriesHighlightOptionsDef(itemHighlightOptionsDef, seriesHighlightOptionsDef) {
|
|
return {
|
|
enabled: boolean,
|
|
range: union("tooltip", "node"),
|
|
highlightedItem: itemHighlightOptionsDef,
|
|
unhighlightedItem: itemHighlightOptionsDef,
|
|
highlightedSeries: seriesHighlightOptionsDef,
|
|
unhighlightedSeries: seriesHighlightOptionsDef,
|
|
bringToFront: boolean
|
|
};
|
|
}
|
|
var shapeSegmentOptions = {
|
|
start: defined,
|
|
stop: defined,
|
|
...strokeOptionsDef,
|
|
...fillOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var lineSegmentOptions = {
|
|
start: defined,
|
|
stop: defined,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var shapeSegmentation = optionsDefs(
|
|
{
|
|
enabled: boolean,
|
|
key: required(union("x", "y")),
|
|
segments: arrayOfDefs(shapeSegmentOptions, "path segments array")
|
|
},
|
|
"a segmentation object",
|
|
true
|
|
);
|
|
var lineSegmentation = optionsDefs(
|
|
{
|
|
enabled: boolean,
|
|
key: required(union("x", "y")),
|
|
segments: arrayOfDefs(lineSegmentOptions, "path segments array")
|
|
},
|
|
"a segmentation object",
|
|
true
|
|
);
|
|
var googleFont = optionsDefs({ googleFont: string }, "google font");
|
|
var fontFamilyFull = or(string, themeOperator, googleFont, arrayOf(or(string, googleFont)));
|
|
var fontWeight = or(positiveNumber, union("normal", "bold", "bolder", "lighter"));
|
|
var fontOptionsDef = {
|
|
color,
|
|
fontFamily: fontFamilyFull,
|
|
fontSize: positiveNumber,
|
|
fontStyle: union("normal", "italic", "oblique"),
|
|
fontWeight
|
|
};
|
|
var paddingOptions = optionsDefs(
|
|
{ top: number, right: number, bottom: number, left: number },
|
|
"padding object"
|
|
);
|
|
var padding = or(number, paddingOptions);
|
|
var borderOptionsDef = {
|
|
enabled: boolean,
|
|
stroke: color,
|
|
strokeWidth: positiveNumber,
|
|
strokeOpacity: ratio
|
|
};
|
|
var labelBoxOptionsDef = {
|
|
border: borderOptionsDef,
|
|
cornerRadius: number,
|
|
padding,
|
|
...fillOptionsDef
|
|
};
|
|
|
|
// packages/ag-charts-core/src/config/chartDefaults.ts
|
|
var legendPlacementLiterals = [
|
|
"top",
|
|
"top-right",
|
|
"top-left",
|
|
"bottom",
|
|
"bottom-right",
|
|
"bottom-left",
|
|
"right",
|
|
"right-top",
|
|
"right-bottom",
|
|
"left",
|
|
"left-top",
|
|
"left-bottom"
|
|
];
|
|
var legendPositionOptionsDef = {
|
|
floating: boolean,
|
|
placement: union(...legendPlacementLiterals),
|
|
xOffset: number,
|
|
yOffset: number
|
|
};
|
|
var legendPositionValidator = attachDescription(
|
|
(value, context) => {
|
|
let result;
|
|
if (typeof value === "string") {
|
|
const allowedValues = legendPlacementLiterals;
|
|
if (allowedValues.includes(value)) {
|
|
result = true;
|
|
} else {
|
|
result = { valid: false, invalid: [], cleared: null };
|
|
result.invalid.push(
|
|
new ValidationError(
|
|
"invalid" /* Invalid */,
|
|
`a legend placement string: ["${legendPlacementLiterals.join('", "')}"]`,
|
|
value,
|
|
context.path
|
|
)
|
|
);
|
|
}
|
|
} else {
|
|
const { cleared, invalid } = validate(value, legendPositionOptionsDef);
|
|
result = { valid: invalid.length === 0, cleared, invalid };
|
|
}
|
|
return result;
|
|
},
|
|
`a legend position object or placement string`
|
|
);
|
|
var shapeValidator = or(
|
|
union("circle", "cross", "diamond", "heart", "plus", "pin", "square", "star", "triangle"),
|
|
callback
|
|
);
|
|
var textWrapValidator = union("never", "always", "hyphenate", "on-space");
|
|
var tooltipPlacementValidator = union(
|
|
"top",
|
|
"right",
|
|
"bottom",
|
|
"left",
|
|
"top-right",
|
|
"bottom-right",
|
|
"bottom-left",
|
|
"top-left",
|
|
"center"
|
|
);
|
|
var rangeValidator = or(positiveNumber, union("exact", "nearest", "area"));
|
|
var seriesTooltipRangeValidator = or(positiveNumber, union("exact", "nearest"));
|
|
var textOrSegments = or(
|
|
string,
|
|
number,
|
|
date,
|
|
arrayOfDefs(
|
|
{
|
|
text: required(string),
|
|
...fontOptionsDef
|
|
},
|
|
"text segments array"
|
|
)
|
|
);
|
|
var chartCaptionOptionsDefs = {
|
|
enabled: boolean,
|
|
text: textOrSegments,
|
|
textAlign: union("left", "center", "right"),
|
|
wrapping: union("never", "always", "hyphenate", "on-space"),
|
|
spacing: positiveNumber,
|
|
maxWidth: positiveNumber,
|
|
maxHeight: positiveNumber,
|
|
...fontOptionsDef
|
|
};
|
|
chartCaptionOptionsDefs.padding = undocumented(positiveNumber);
|
|
var chartOverlayOptionsDefs = {
|
|
enabled: boolean,
|
|
text: textOrSegments,
|
|
renderer: callbackOf(or(string, htmlElement))
|
|
};
|
|
var contextMenuItemLiterals = [
|
|
"defaults",
|
|
"download",
|
|
"zoom-to-cursor",
|
|
"pan-to-cursor",
|
|
"reset-zoom",
|
|
"toggle-series-visibility",
|
|
"toggle-other-series",
|
|
"separator"
|
|
];
|
|
var contextMenuItemObjectDef = {
|
|
type: union("action", "separator"),
|
|
showOn: union("always", "series-area", "series-node", "legend-item"),
|
|
label: required(string),
|
|
enabled: boolean,
|
|
action: callback,
|
|
items: (value, context) => contextMenuItemsArray(value, context)
|
|
};
|
|
contextMenuItemObjectDef.iconUrl = undocumented(string);
|
|
var contextMenuItemObjectValidator = optionsDefs(contextMenuItemObjectDef);
|
|
var contextMenuItemValidator = attachDescription(
|
|
(value, context) => {
|
|
let result;
|
|
if (typeof value === "string") {
|
|
const allowedValues = contextMenuItemLiterals;
|
|
if (allowedValues.includes(value)) {
|
|
result = true;
|
|
} else {
|
|
result = { valid: false, invalid: [], cleared: null };
|
|
result.invalid.push(
|
|
new ValidationError(
|
|
"invalid" /* Invalid */,
|
|
`a context menu item string alias: ["${contextMenuItemLiterals.join('", "')}"]`,
|
|
value,
|
|
context.path
|
|
)
|
|
);
|
|
}
|
|
} else {
|
|
result = contextMenuItemObjectValidator(value, context);
|
|
}
|
|
return result;
|
|
},
|
|
`a context menu item object or string alias: [${contextMenuItemLiterals.join(", ")}]`
|
|
);
|
|
var contextMenuItemsArray = arrayOf(contextMenuItemValidator, "a menu items array", false);
|
|
var toolbarButtonOptionsDefs = {
|
|
label: string,
|
|
ariaLabel: string,
|
|
tooltip: string,
|
|
icon: union(
|
|
"align-center",
|
|
"align-left",
|
|
"align-right",
|
|
"arrow-drawing",
|
|
"arrow-down-drawing",
|
|
"arrow-up-drawing",
|
|
"callout-annotation",
|
|
"candlestick-series",
|
|
"close",
|
|
"comment-annotation",
|
|
"date-range-drawing",
|
|
"date-price-range-drawing",
|
|
"delete",
|
|
"disjoint-channel-drawing",
|
|
"drag-handle",
|
|
"fill-color",
|
|
"line-style-solid",
|
|
"line-style-dashed",
|
|
"line-style-dotted",
|
|
"high-low-series",
|
|
"hlc-series",
|
|
"hollow-candlestick-series",
|
|
"horizontal-line-drawing",
|
|
"line-color",
|
|
"line-series",
|
|
"line-with-markers-series",
|
|
"locked",
|
|
"measurer-drawing",
|
|
"note-annotation",
|
|
"ohlc-series",
|
|
"pan-end",
|
|
"pan-left",
|
|
"pan-right",
|
|
"pan-start",
|
|
"parallel-channel-drawing",
|
|
"position-bottom",
|
|
"position-center",
|
|
"position-top",
|
|
"price-label-annotation",
|
|
"price-range-drawing",
|
|
"reset",
|
|
"settings",
|
|
"step-line-series",
|
|
"text-annotation",
|
|
"trend-line-drawing",
|
|
"fibonacci-retracement-drawing",
|
|
"fibonacci-retracement-trend-based-drawing",
|
|
"unlocked",
|
|
"vertical-line-drawing",
|
|
"zoom-in",
|
|
"zoom-out"
|
|
)
|
|
};
|
|
var formatter = or(string, callbackOf(textOrSegments));
|
|
var formatObjectValidator = optionsDefs({
|
|
x: formatter,
|
|
y: formatter,
|
|
angle: formatter,
|
|
radius: formatter,
|
|
size: formatter,
|
|
color: formatter,
|
|
label: formatter,
|
|
secondaryLabel: formatter,
|
|
sectorLabel: formatter,
|
|
calloutLabel: formatter,
|
|
legendItem: formatter
|
|
});
|
|
var numberFormatValidator = attachDescription(isValidNumberFormat, "a valid number format string");
|
|
var commonChartOptionsDefs = {
|
|
width: positiveNumber,
|
|
height: positiveNumber,
|
|
minWidth: positiveNumber,
|
|
minHeight: positiveNumber,
|
|
suppressFieldDotNotation: boolean,
|
|
title: chartCaptionOptionsDefs,
|
|
subtitle: chartCaptionOptionsDefs,
|
|
footnote: chartCaptionOptionsDefs,
|
|
padding: {
|
|
top: positiveNumber,
|
|
right: positiveNumber,
|
|
bottom: positiveNumber,
|
|
left: positiveNumber
|
|
},
|
|
seriesArea: {
|
|
border: borderOptionsDef,
|
|
clip: boolean,
|
|
cornerRadius: number,
|
|
padding
|
|
},
|
|
legend: {
|
|
enabled: boolean,
|
|
position: legendPositionValidator,
|
|
orientation: union("horizontal", "vertical"),
|
|
maxWidth: positiveNumber,
|
|
maxHeight: positiveNumber,
|
|
spacing: positiveNumber,
|
|
border: borderOptionsDef,
|
|
cornerRadius: number,
|
|
padding,
|
|
fill: colorUnion,
|
|
fillOpacity: ratio,
|
|
preventHidingAll: boolean,
|
|
reverseOrder: boolean,
|
|
toggleSeries: boolean,
|
|
item: {
|
|
marker: {
|
|
size: positiveNumber,
|
|
shape: shapeValidator,
|
|
padding: positiveNumber,
|
|
strokeWidth: positiveNumber
|
|
},
|
|
line: {
|
|
length: positiveNumber,
|
|
strokeWidth: positiveNumber
|
|
},
|
|
label: {
|
|
maxLength: positiveNumber,
|
|
formatter: callback,
|
|
...fontOptionsDef
|
|
},
|
|
maxWidth: positiveNumber,
|
|
paddingX: positiveNumber,
|
|
paddingY: positiveNumber,
|
|
showSeriesStroke: boolean
|
|
},
|
|
pagination: {
|
|
marker: {
|
|
size: positiveNumber,
|
|
shape: shapeValidator,
|
|
padding: positiveNumber
|
|
},
|
|
activeStyle: {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef
|
|
},
|
|
inactiveStyle: {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef
|
|
},
|
|
highlightStyle: {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef
|
|
},
|
|
label: fontOptionsDef
|
|
},
|
|
listeners: {
|
|
legendItemClick: callback,
|
|
legendItemDoubleClick: callback
|
|
}
|
|
},
|
|
gradientLegend: {
|
|
enabled: boolean,
|
|
position: legendPositionValidator,
|
|
spacing: positiveNumber,
|
|
reverseOrder: boolean,
|
|
border: borderOptionsDef,
|
|
cornerRadius: number,
|
|
padding,
|
|
fill: colorUnion,
|
|
fillOpacity: ratio,
|
|
gradient: {
|
|
preferredLength: positiveNumber,
|
|
thickness: positiveNumber
|
|
},
|
|
scale: {
|
|
label: {
|
|
...fontOptionsDef,
|
|
minSpacing: positiveNumber,
|
|
format: numberFormatValidator,
|
|
formatter: callback
|
|
},
|
|
padding: positiveNumber,
|
|
interval: {
|
|
step: number,
|
|
values: array,
|
|
minSpacing: and(positiveNumber, lessThan("maxSpacing")),
|
|
maxSpacing: and(positiveNumber, greaterThan("minSpacing"))
|
|
}
|
|
}
|
|
},
|
|
listeners: {
|
|
seriesNodeClick: callback,
|
|
seriesNodeDoubleClick: callback,
|
|
seriesVisibilityChange: callback,
|
|
activeChange: callback,
|
|
click: callback,
|
|
doubleClick: callback,
|
|
annotations: callback,
|
|
zoom: callback
|
|
},
|
|
loadGoogleFonts: boolean,
|
|
highlight: {
|
|
drawingMode: union("overlay", "cutout"),
|
|
range: union("tooltip", "node")
|
|
},
|
|
overlays: {
|
|
loading: chartOverlayOptionsDefs,
|
|
noData: chartOverlayOptionsDefs,
|
|
noVisibleSeries: chartOverlayOptionsDefs,
|
|
unsupportedBrowser: chartOverlayOptionsDefs
|
|
},
|
|
tooltip: {
|
|
enabled: boolean,
|
|
showArrow: boolean,
|
|
pagination: boolean,
|
|
delay: positiveNumber,
|
|
range: rangeValidator,
|
|
wrapping: textWrapValidator,
|
|
mode: union("single", "shared", "compact"),
|
|
position: {
|
|
anchorTo: union("pointer", "node", "chart"),
|
|
placement: or(tooltipPlacementValidator, arrayOf(tooltipPlacementValidator)),
|
|
xOffset: number,
|
|
yOffset: number
|
|
}
|
|
},
|
|
animation: defined,
|
|
contextMenu: defined,
|
|
context: () => true,
|
|
dataSource: {
|
|
getData: callback
|
|
},
|
|
keyboard: {
|
|
enabled: boolean,
|
|
tabIndex: number
|
|
},
|
|
touch: {
|
|
dragAction: union("none", "drag", "hover")
|
|
},
|
|
ranges: {
|
|
enabled: boolean,
|
|
buttons: arrayOfDefs(
|
|
{
|
|
...toolbarButtonOptionsDefs,
|
|
value: or(number, and(arrayOf(or(number, date)), arrayLength(2, 2)), callback)
|
|
},
|
|
"range button options array"
|
|
)
|
|
},
|
|
// modules
|
|
locale: {
|
|
localeText: object,
|
|
getLocaleText: callbackOf(string)
|
|
},
|
|
background: {
|
|
visible: boolean,
|
|
fill: color,
|
|
// enterprise
|
|
image: {
|
|
url: required(string),
|
|
top: number,
|
|
right: number,
|
|
bottom: number,
|
|
left: number,
|
|
width: positiveNumber,
|
|
height: positiveNumber,
|
|
opacity: ratio
|
|
}
|
|
},
|
|
styleNonce: string,
|
|
sync: defined,
|
|
zoom: defined,
|
|
scrollbar: defined,
|
|
formatter: or(callbackOf(textOrSegments), formatObjectValidator)
|
|
};
|
|
commonChartOptionsDefs.flashOnUpdate = undocumented(defined);
|
|
commonChartOptionsDefs.dataSource.requestThrottle = undocumented(positiveNumber);
|
|
commonChartOptionsDefs.dataSource.updateThrottle = undocumented(positiveNumber);
|
|
commonChartOptionsDefs.dataSource.updateDuringInteraction = undocumented(boolean);
|
|
commonChartOptionsDefs.statusBar = undocumented(defined);
|
|
commonChartOptionsDefs.foreground = undocumented({
|
|
visible: boolean,
|
|
text: string,
|
|
image: {
|
|
url: string,
|
|
top: number,
|
|
right: number,
|
|
bottom: number,
|
|
left: number,
|
|
width: positiveNumber,
|
|
height: positiveNumber,
|
|
opacity: ratio
|
|
},
|
|
...fillOptionsDef
|
|
});
|
|
commonChartOptionsDefs.overrideDevicePixelRatio = undocumented(number);
|
|
commonChartOptionsDefs.sync.domainMode = undocumented(union("direction", "position", "key"));
|
|
commonChartOptionsDefs.displayNullData = undocumented(boolean);
|
|
var commonSeriesThemeableOptionsDefs = {
|
|
cursor: string,
|
|
context: () => true,
|
|
showInLegend: boolean,
|
|
nodeClickRange: rangeValidator,
|
|
listeners: {
|
|
seriesNodeClick: callback,
|
|
seriesNodeDoubleClick: callback
|
|
},
|
|
highlight: highlightOptionsDef(shapeHighlightOptionsDef)
|
|
};
|
|
commonSeriesThemeableOptionsDefs.allowNullKeys = undocumented(boolean);
|
|
var commonSeriesOptionsDefs = {
|
|
...commonSeriesThemeableOptionsDefs,
|
|
id: string,
|
|
visible: boolean,
|
|
context: () => true,
|
|
data: array
|
|
};
|
|
commonSeriesOptionsDefs.seriesGrouping = undocumented(defined);
|
|
var markerStyleOptionsDefs = {
|
|
shape: shapeValidator,
|
|
size: positiveNumber,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var markerOptionsDefs = {
|
|
enabled: boolean,
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
shape: shapeValidator,
|
|
size: positiveNumber
|
|
}),
|
|
...markerStyleOptionsDefs
|
|
};
|
|
var seriesLabelOptionsDefs = {
|
|
enabled: boolean,
|
|
formatter: callbackOf(textOrSegments),
|
|
format: numberFormatValidator,
|
|
itemStyler: callbackDefs({
|
|
enabled: boolean,
|
|
...labelBoxOptionsDef,
|
|
...fontOptionsDef
|
|
}),
|
|
...labelBoxOptionsDef,
|
|
...fontOptionsDef
|
|
};
|
|
var autoSizedLabelOptionsDefs = {
|
|
...seriesLabelOptionsDefs,
|
|
lineHeight: positiveNumber,
|
|
minimumFontSize: positiveNumber,
|
|
wrapping: textWrapValidator,
|
|
overflowStrategy: union("ellipsis", "hide")
|
|
};
|
|
var errorBarThemeableOptionsDefs = {
|
|
visible: boolean,
|
|
cap: {
|
|
visible: boolean,
|
|
length: positiveNumber,
|
|
lengthRatio: ratio,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var errorBarOptionsDefs = {
|
|
...errorBarThemeableOptionsDefs,
|
|
xLowerKey: string,
|
|
xUpperKey: string,
|
|
yLowerKey: string,
|
|
yUpperKey: string,
|
|
xLowerName: string,
|
|
xUpperName: string,
|
|
yLowerName: string,
|
|
yUpperName: string,
|
|
itemStyler: callbackDefs({
|
|
visible: boolean,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
cap: {
|
|
visible: boolean,
|
|
length: positiveNumber,
|
|
lengthRatio: ratio,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
}
|
|
})
|
|
};
|
|
var tooltipOptionsDefs = {
|
|
enabled: boolean,
|
|
showArrow: boolean,
|
|
range: seriesTooltipRangeValidator,
|
|
renderer: callbackOf(
|
|
or(
|
|
string,
|
|
number,
|
|
date,
|
|
optionsDefs(
|
|
{
|
|
heading: string,
|
|
title: string,
|
|
symbol: {
|
|
marker: {
|
|
enabled: boolean,
|
|
shape: shapeValidator,
|
|
...fillOptionsDef,
|
|
stroke: color,
|
|
strokeOpacity: ratio,
|
|
strokeWidth: positiveNumber,
|
|
...lineDashOptionsDef
|
|
},
|
|
line: {
|
|
enabled: boolean,
|
|
stroke: color,
|
|
strokeWidth: positiveNumber,
|
|
strokeOpacity: ratio,
|
|
...lineDashOptionsDef
|
|
}
|
|
},
|
|
data: arrayOfDefs({
|
|
label: required(string),
|
|
value: required(or(string, number, date))
|
|
})
|
|
},
|
|
"tooltip renderer result object"
|
|
)
|
|
)
|
|
),
|
|
position: {
|
|
anchorTo: union("node", "pointer", "chart"),
|
|
placement: or(tooltipPlacementValidator, arrayOf(tooltipPlacementValidator)),
|
|
xOffset: number,
|
|
yOffset: number
|
|
},
|
|
interaction: {
|
|
enabled: boolean
|
|
}
|
|
};
|
|
var tooltipOptionsDefsWithArea = {
|
|
...tooltipOptionsDefs,
|
|
range: rangeValidator
|
|
};
|
|
var shadowOptionsDefs = {
|
|
enabled: boolean,
|
|
xOffset: number,
|
|
yOffset: number,
|
|
blur: positiveNumber,
|
|
color
|
|
};
|
|
var interpolationOptionsDefs = typeUnion(
|
|
{
|
|
linear: {},
|
|
smooth: {
|
|
tension: ratio
|
|
},
|
|
step: {
|
|
position: union("start", "middle", "end")
|
|
}
|
|
},
|
|
"interpolation line options"
|
|
);
|
|
|
|
// packages/ag-charts-core/src/utils/types/decorator.ts
|
|
var BREAK_TRANSFORM_CHAIN = Symbol("BREAK");
|
|
var CONFIG_KEY = "__decorator_config";
|
|
var ACCESSORS_KEY = "__decorator_accessors";
|
|
function addFakeTransformToInstanceProperty(target, propertyKeyOrSymbol) {
|
|
initialiseConfig(target, propertyKeyOrSymbol).optional = true;
|
|
}
|
|
function initialiseConfig(target, propertyKeyOrSymbol) {
|
|
if (Object.getOwnPropertyDescriptor(target, CONFIG_KEY) == null) {
|
|
Object.defineProperty(target, CONFIG_KEY, { value: {} });
|
|
}
|
|
if (Object.getOwnPropertyDescriptor(target, ACCESSORS_KEY) == null) {
|
|
const parentAccessors = Object.getPrototypeOf(target)?.[ACCESSORS_KEY];
|
|
const accessors = parentAccessors?.slice() ?? [];
|
|
Object.defineProperty(target, ACCESSORS_KEY, { value: accessors });
|
|
}
|
|
const config = target[CONFIG_KEY];
|
|
const propertyKey = propertyKeyOrSymbol.toString();
|
|
if (config[propertyKey] != null) {
|
|
return config[propertyKey];
|
|
}
|
|
config[propertyKey] = { setters: [], getters: [], observers: [] };
|
|
const descriptor = Object.getOwnPropertyDescriptor(target, propertyKeyOrSymbol);
|
|
let prevGet = descriptor?.get;
|
|
let prevSet = descriptor?.set;
|
|
if (prevGet == null || prevSet == null) {
|
|
const accessors = target[ACCESSORS_KEY];
|
|
let index = accessors.indexOf(propertyKeyOrSymbol);
|
|
if (index === -1) {
|
|
index = accessors.push(propertyKeyOrSymbol) - 1;
|
|
}
|
|
prevGet ?? (prevGet = function() {
|
|
let accessorValues = this.__accessors;
|
|
if (accessorValues == null) {
|
|
accessorValues = accessors.slice().fill(void 0);
|
|
Object.defineProperty(this, "__accessors", { value: accessorValues });
|
|
}
|
|
return accessorValues[index];
|
|
});
|
|
prevSet ?? (prevSet = function(value) {
|
|
let accessorValues = this.__accessors;
|
|
if (accessorValues == null) {
|
|
accessorValues = accessors.slice().fill(void 0);
|
|
Object.defineProperty(this, "__accessors", { value: accessorValues });
|
|
}
|
|
accessorValues[index] = value;
|
|
});
|
|
}
|
|
const getter = function() {
|
|
let value = prevGet.call(this);
|
|
for (const transformFn of config[propertyKey].getters) {
|
|
value = transformFn(this, propertyKeyOrSymbol, value);
|
|
if (value === BREAK_TRANSFORM_CHAIN) {
|
|
return;
|
|
}
|
|
}
|
|
return value;
|
|
};
|
|
const setter = function(value) {
|
|
const { setters, observers } = config[propertyKey];
|
|
let oldValue;
|
|
if (setters.some((f) => f.length > 2)) {
|
|
oldValue = prevGet.call(this);
|
|
}
|
|
for (const transformFn of setters) {
|
|
value = transformFn(this, propertyKeyOrSymbol, value, oldValue);
|
|
if (value === BREAK_TRANSFORM_CHAIN) {
|
|
return;
|
|
}
|
|
}
|
|
prevSet.call(this, value);
|
|
for (const observerFn of observers) {
|
|
observerFn(this, value, oldValue);
|
|
}
|
|
};
|
|
Object.defineProperty(target, propertyKeyOrSymbol, {
|
|
set: setter,
|
|
get: getter,
|
|
enumerable: true,
|
|
configurable: false
|
|
});
|
|
return config[propertyKey];
|
|
}
|
|
function addTransformToInstanceProperty(setTransform, getTransform, configMetadata) {
|
|
return (target, propertyKeyOrSymbol) => {
|
|
const config = initialiseConfig(target, propertyKeyOrSymbol);
|
|
config.setters.push(setTransform);
|
|
if (getTransform) {
|
|
config.getters.unshift(getTransform);
|
|
}
|
|
if (configMetadata) {
|
|
Object.assign(config, configMetadata);
|
|
}
|
|
};
|
|
}
|
|
function addObserverToInstanceProperty(setObserver) {
|
|
return (target, propertyKeyOrSymbol) => {
|
|
initialiseConfig(target, propertyKeyOrSymbol).observers.push(setObserver);
|
|
};
|
|
}
|
|
function isDecoratedObject(target) {
|
|
return target !== void 0 && CONFIG_KEY in target;
|
|
}
|
|
function listDecoratedProperties(target) {
|
|
const targets = /* @__PURE__ */ new Set();
|
|
while (isDecoratedObject(target)) {
|
|
targets.add(target?.[CONFIG_KEY]);
|
|
target = Object.getPrototypeOf(target);
|
|
}
|
|
return Array.from(targets).flatMap((configMap) => Object.keys(configMap));
|
|
}
|
|
function extractDecoratedProperties(target) {
|
|
return listDecoratedProperties(target).reduce((result, key) => {
|
|
result[String(key)] = target[key] ?? null;
|
|
return result;
|
|
}, {});
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/data/iterators.ts
|
|
function* iterate(...items) {
|
|
for (const item of items) {
|
|
if (item == null)
|
|
continue;
|
|
if (item[Symbol.iterator]) {
|
|
yield* item;
|
|
} else {
|
|
yield item;
|
|
}
|
|
}
|
|
}
|
|
function toIterable(value) {
|
|
return value != null && typeof value === "object" && Symbol.iterator in value ? value : [value];
|
|
}
|
|
function first(iterable) {
|
|
for (const value of iterable) {
|
|
return value;
|
|
}
|
|
throw new Error("AG Charts - no first() value found");
|
|
}
|
|
function* entries(obj) {
|
|
const resultTuple = [void 0, void 0];
|
|
for (const key of Object.keys(obj)) {
|
|
resultTuple[0] = key;
|
|
resultTuple[1] = obj[key];
|
|
yield resultTuple;
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/data/object.ts
|
|
function strictObjectKeys(o) {
|
|
return Object.keys(o);
|
|
}
|
|
function objectsEqual(a, b) {
|
|
if (Array.isArray(a)) {
|
|
if (!Array.isArray(b))
|
|
return false;
|
|
if (a.length !== b.length)
|
|
return false;
|
|
return a.every((av, i) => objectsEqual(av, b[i]));
|
|
} else if (isPlainObject(a)) {
|
|
if (!isPlainObject(b))
|
|
return false;
|
|
return objectsEqualWith(a, b, objectsEqual);
|
|
}
|
|
return a === b;
|
|
}
|
|
function objectsEqualWith(a, b, cmp2) {
|
|
if (Object.is(a, b))
|
|
return true;
|
|
for (const key of Object.keys(b)) {
|
|
if (!(key in a))
|
|
return false;
|
|
}
|
|
for (const key of Object.keys(a)) {
|
|
if (!(key in b))
|
|
return false;
|
|
if (!cmp2(a[key], b[key]))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function mergeDefaults(...sources) {
|
|
const target = {};
|
|
for (const source of sources) {
|
|
if (!isObject(source))
|
|
continue;
|
|
const keys = isDecoratedObject(source) ? listDecoratedProperties(source) : Object.keys(source);
|
|
for (const key of keys) {
|
|
if (isPlainObject(target[key]) && isPlainObject(source[key])) {
|
|
target[key] = mergeDefaults(target[key], source[key]);
|
|
} else {
|
|
target[key] ?? (target[key] = source[key]);
|
|
}
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
function merge(...sources) {
|
|
const target = {};
|
|
for (const source of sources) {
|
|
if (!isObject(source))
|
|
continue;
|
|
const keys = isDecoratedObject(source) ? listDecoratedProperties(source) : Object.keys(source);
|
|
for (const key of keys) {
|
|
if (isPlainObject(target[key]) && isPlainObject(source[key])) {
|
|
target[key] = merge(target[key], source[key]);
|
|
} else if (!(key in target)) {
|
|
target[key] ?? (target[key] = source[key]);
|
|
}
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
function mergeArrayDefaults(dataArray, ...itemDefaults) {
|
|
if (itemDefaults && isArray(dataArray)) {
|
|
return dataArray.map((item) => mergeDefaults(item, ...itemDefaults));
|
|
}
|
|
return dataArray;
|
|
}
|
|
function mapValues(object2, mapper) {
|
|
const result = {};
|
|
for (const [key, value] of entries(object2)) {
|
|
result[key] = mapper(value, key, object2);
|
|
}
|
|
return result;
|
|
}
|
|
function without(object2, keys) {
|
|
const clone2 = { ...object2 };
|
|
for (const key of keys) {
|
|
delete clone2[key];
|
|
}
|
|
return clone2;
|
|
}
|
|
function pick(object2, keys) {
|
|
if (object2 == null)
|
|
return;
|
|
const picked = {};
|
|
for (const key of keys) {
|
|
if (Object.hasOwn(object2, key)) {
|
|
picked[key] = object2[key];
|
|
}
|
|
}
|
|
return picked;
|
|
}
|
|
function every(object2, fn) {
|
|
if (object2 == null)
|
|
return true;
|
|
for (const [key, value] of entries(object2)) {
|
|
if (!fn(key, value))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function fromPairs(pairs) {
|
|
const object2 = {};
|
|
if (pairs == null)
|
|
return object2;
|
|
for (const [key, value] of pairs) {
|
|
object2[key] = value;
|
|
}
|
|
return object2;
|
|
}
|
|
function getPath(object2, path) {
|
|
const pathArray = isArray(path) ? path : path.split(".");
|
|
return pathArray.reduce((value, pathKey) => value[pathKey], object2);
|
|
}
|
|
var SKIP_JS_BUILTINS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
function setPath(object2, path, newValue) {
|
|
const pathArray = isArray(path) ? path.slice() : path.split(".");
|
|
const lastKey = pathArray.pop();
|
|
if (pathArray.some((p) => SKIP_JS_BUILTINS.has(p)))
|
|
return;
|
|
const lastObject = pathArray.reduce((value, pathKey) => value[pathKey], object2);
|
|
lastObject[lastKey] = newValue;
|
|
return lastObject[lastKey];
|
|
}
|
|
function partialAssign(keysToCopy, target, source) {
|
|
if (source === void 0) {
|
|
return target;
|
|
}
|
|
for (const key of keysToCopy) {
|
|
const value = source[key];
|
|
if (value !== void 0) {
|
|
target[key] = value;
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
function assignIfNotStrictlyEqual(target, source, keys) {
|
|
const sourceKeys = keys ?? Object.keys(source);
|
|
for (let i = 0, len = sourceKeys.length; i < len; i++) {
|
|
const key = sourceKeys[i];
|
|
const newValue = source[key];
|
|
if (target[key] !== newValue) {
|
|
target[key] = newValue;
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
function deepFreeze(obj) {
|
|
if (obj == null || typeof obj !== "object" || !isPlainObject(obj)) {
|
|
return obj;
|
|
}
|
|
Object.freeze(obj);
|
|
for (const prop of Object.getOwnPropertyNames(obj)) {
|
|
const value = obj[prop];
|
|
if (value !== null && (typeof value === "object" || typeof value === "function") && !Object.isFrozen(value)) {
|
|
deepFreeze(value);
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
function isObjectWithProperty(obj, key) {
|
|
return isPlainObject(obj) && key in obj;
|
|
}
|
|
function isObjectWithStringProperty(obj, key) {
|
|
return isObjectWithProperty(obj, key) && typeof obj[key] === "string";
|
|
}
|
|
|
|
// packages/ag-charts-core/src/config/gaugePreset.ts
|
|
var fillsOptionsDef = {
|
|
fills: and(
|
|
arrayLength(2),
|
|
arrayOf(optionsDefs({ color, stop: number }, "")),
|
|
colorStopsOrderValidator
|
|
),
|
|
fillMode: union("continuous", "discrete")
|
|
};
|
|
var linearGaugeTargetOptionsDef = {
|
|
value: required(number),
|
|
text: string,
|
|
shape: or(
|
|
union("circle", "cross", "diamond", "heart", "plus", "pin", "square", "star", "triangle", "line"),
|
|
callback
|
|
),
|
|
placement: union("before", "after", "middle"),
|
|
spacing: positiveNumber,
|
|
size: positiveNumber,
|
|
rotation: number,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var radialGaugeTargetOptionsDef = {
|
|
value: required(number),
|
|
text: string,
|
|
shape: or(
|
|
union("circle", "cross", "diamond", "heart", "plus", "pin", "square", "star", "triangle", "line"),
|
|
callback
|
|
),
|
|
placement: union("inside", "outside", "middle"),
|
|
spacing: positiveNumber,
|
|
size: positiveNumber,
|
|
rotation: number,
|
|
label: {
|
|
...seriesLabelOptionsDefs,
|
|
spacing: positiveNumber
|
|
},
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var linearGaugeSeriesThemeableOptionsDef = {
|
|
...without(commonSeriesThemeableOptionsDefs, ["listeners"]),
|
|
direction: union("horizontal", "vertical"),
|
|
cornerMode: union("container", "item"),
|
|
cornerRadius: positiveNumber,
|
|
thickness: positiveNumber,
|
|
segmentation: {
|
|
enabled: boolean,
|
|
spacing: positiveNumber,
|
|
interval: {
|
|
values: arrayOf(number),
|
|
step: number,
|
|
count: number
|
|
}
|
|
},
|
|
bar: {
|
|
enabled: boolean,
|
|
thickness: positiveNumber,
|
|
thicknessRatio: ratio,
|
|
...fillsOptionsDef,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
label: {
|
|
...autoSizedLabelOptionsDefs,
|
|
text: string,
|
|
spacing: positiveNumber,
|
|
avoidCollisions: boolean,
|
|
placement: union(
|
|
"inside-start",
|
|
"outside-start",
|
|
"inside-end",
|
|
"outside-end",
|
|
"inside-center",
|
|
"bar-inside",
|
|
"bar-inside-end",
|
|
"bar-outside-end",
|
|
"bar-end"
|
|
)
|
|
},
|
|
tooltip: tooltipOptionsDefs
|
|
};
|
|
var linearGaugeSeriesOptionsDef = {
|
|
...without(commonSeriesOptionsDefs, ["listeners"]),
|
|
...linearGaugeSeriesThemeableOptionsDef,
|
|
type: required(constant("linear-gauge")),
|
|
value: required(number),
|
|
scale: {
|
|
min: and(number, lessThan("max")),
|
|
max: and(number, greaterThan("min")),
|
|
label: {
|
|
enabled: boolean,
|
|
formatter: callback,
|
|
rotation: number,
|
|
spacing: positiveNumber,
|
|
minSpacing: positiveNumber,
|
|
placement: union("before", "after"),
|
|
avoidCollisions: boolean,
|
|
format: numberFormatValidator,
|
|
...fontOptionsDef
|
|
},
|
|
interval: {
|
|
values: arrayOf(number),
|
|
step: number
|
|
},
|
|
...fillsOptionsDef,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
targets: arrayOfDefs(linearGaugeTargetOptionsDef, "target options array")
|
|
};
|
|
linearGaugeSeriesOptionsDef.margin = undocumented(number);
|
|
linearGaugeSeriesOptionsDef.defaultColorRange = undocumented(arrayOf(color));
|
|
linearGaugeSeriesOptionsDef.defaultTarget = undocumented({
|
|
...linearGaugeTargetOptionsDef,
|
|
value: number,
|
|
label: {
|
|
...seriesLabelOptionsDefs,
|
|
spacing: number
|
|
}
|
|
});
|
|
linearGaugeSeriesOptionsDef.defaultScale = undocumented(linearGaugeSeriesOptionsDef.scale);
|
|
linearGaugeSeriesOptionsDef.scale.defaultFill = undocumented(color);
|
|
var radialGaugeSeriesThemeableOptionsDef = {
|
|
...without(commonSeriesThemeableOptionsDefs, ["listeners"]),
|
|
outerRadius: positiveNumber,
|
|
innerRadius: positiveNumber,
|
|
outerRadiusRatio: ratio,
|
|
innerRadiusRatio: ratio,
|
|
startAngle: number,
|
|
endAngle: number,
|
|
spacing: positiveNumber,
|
|
cornerMode: union("container", "item"),
|
|
cornerRadius: positiveNumber,
|
|
scale: {
|
|
min: and(number, lessThan("max")),
|
|
max: and(number, greaterThan("min")),
|
|
label: {
|
|
enabled: boolean,
|
|
formatter: callback,
|
|
rotation: number,
|
|
spacing: positiveNumber,
|
|
minSpacing: positiveNumber,
|
|
avoidCollisions: boolean,
|
|
format: numberFormatValidator,
|
|
...fontOptionsDef
|
|
},
|
|
interval: {
|
|
values: arrayOf(number),
|
|
step: number
|
|
},
|
|
...fillsOptionsDef,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
segmentation: {
|
|
enabled: boolean,
|
|
spacing: positiveNumber,
|
|
interval: {
|
|
values: arrayOf(number),
|
|
step: number,
|
|
count: number
|
|
}
|
|
},
|
|
bar: {
|
|
enabled: boolean,
|
|
...fillsOptionsDef,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
needle: {
|
|
enabled: boolean,
|
|
spacing: positiveNumber,
|
|
radiusRatio: ratio,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
label: {
|
|
text: string,
|
|
spacing: positiveNumber,
|
|
...autoSizedLabelOptionsDefs
|
|
},
|
|
secondaryLabel: {
|
|
text: string,
|
|
...autoSizedLabelOptionsDefs
|
|
},
|
|
tooltip: tooltipOptionsDefs
|
|
};
|
|
var radialGaugeSeriesOptionsDef = {
|
|
...without(commonSeriesOptionsDefs, ["listeners"]),
|
|
...radialGaugeSeriesThemeableOptionsDef,
|
|
type: required(constant("radial-gauge")),
|
|
value: required(number),
|
|
targets: arrayOfDefs(radialGaugeTargetOptionsDef, "target options array")
|
|
};
|
|
radialGaugeSeriesOptionsDef.defaultColorRange = undocumented(arrayOf(color));
|
|
radialGaugeSeriesOptionsDef.defaultTarget = undocumented({
|
|
...radialGaugeTargetOptionsDef,
|
|
value: number,
|
|
label: {
|
|
...seriesLabelOptionsDefs,
|
|
spacing: number
|
|
}
|
|
});
|
|
radialGaugeSeriesOptionsDef.scale.defaultFill = undocumented(color);
|
|
|
|
// packages/ag-charts-core/src/types/themeConstants.ts
|
|
var FONT_SIZE = /* @__PURE__ */ ((FONT_SIZE2) => {
|
|
FONT_SIZE2[FONT_SIZE2["SMALLEST"] = 8] = "SMALLEST";
|
|
FONT_SIZE2[FONT_SIZE2["SMALLER"] = 10] = "SMALLER";
|
|
FONT_SIZE2[FONT_SIZE2["SMALL"] = 12] = "SMALL";
|
|
FONT_SIZE2[FONT_SIZE2["MEDIUM"] = 13] = "MEDIUM";
|
|
FONT_SIZE2[FONT_SIZE2["LARGE"] = 14] = "LARGE";
|
|
FONT_SIZE2[FONT_SIZE2["LARGEST"] = 17] = "LARGEST";
|
|
return FONT_SIZE2;
|
|
})(FONT_SIZE || {});
|
|
var BASE_FONT_SIZE = 12 /* SMALL */;
|
|
var FONT_SIZE_RATIO = {
|
|
SMALLEST: 8 /* SMALLEST */ / BASE_FONT_SIZE,
|
|
SMALLER: 10 /* SMALLER */ / BASE_FONT_SIZE,
|
|
SMALL: 12 /* SMALL */ / BASE_FONT_SIZE,
|
|
MEDIUM: 13 /* MEDIUM */ / BASE_FONT_SIZE,
|
|
LARGE: 14 /* LARGE */ / BASE_FONT_SIZE,
|
|
LARGEST: 17 /* LARGEST */ / BASE_FONT_SIZE
|
|
};
|
|
var CARTESIAN_POSITION = /* @__PURE__ */ ((CARTESIAN_POSITION2) => {
|
|
CARTESIAN_POSITION2["TOP"] = "top";
|
|
CARTESIAN_POSITION2["TOP_RIGHT"] = "top-right";
|
|
CARTESIAN_POSITION2["TOP_LEFT"] = "top-left";
|
|
CARTESIAN_POSITION2["RIGHT"] = "right";
|
|
CARTESIAN_POSITION2["RIGHT_TOP"] = "right-top";
|
|
CARTESIAN_POSITION2["RIGHT_BOTTOM"] = "right-bottom";
|
|
CARTESIAN_POSITION2["BOTTOM"] = "bottom";
|
|
CARTESIAN_POSITION2["BOTTOM_RIGHT"] = "bottom-right";
|
|
CARTESIAN_POSITION2["BOTTOM_LEFT"] = "bottom-left";
|
|
CARTESIAN_POSITION2["LEFT"] = "left";
|
|
CARTESIAN_POSITION2["LEFT_TOP"] = "left-top";
|
|
CARTESIAN_POSITION2["LEFT_BOTTOM"] = "left-bottom";
|
|
return CARTESIAN_POSITION2;
|
|
})(CARTESIAN_POSITION || {});
|
|
var CARTESIAN_AXIS_TYPE = /* @__PURE__ */ ((CARTESIAN_AXIS_TYPE2) => {
|
|
CARTESIAN_AXIS_TYPE2["CATEGORY"] = "category";
|
|
CARTESIAN_AXIS_TYPE2["GROUPED_CATEGORY"] = "grouped-category";
|
|
CARTESIAN_AXIS_TYPE2["ORDINAL_TIME"] = "ordinal-time";
|
|
CARTESIAN_AXIS_TYPE2["UNIT_TIME"] = "unit-time";
|
|
CARTESIAN_AXIS_TYPE2["TIME"] = "time";
|
|
CARTESIAN_AXIS_TYPE2["NUMBER"] = "number";
|
|
CARTESIAN_AXIS_TYPE2["LOG"] = "log";
|
|
return CARTESIAN_AXIS_TYPE2;
|
|
})(CARTESIAN_AXIS_TYPE || {});
|
|
var POLAR_AXIS_TYPE = /* @__PURE__ */ ((POLAR_AXIS_TYPE2) => {
|
|
POLAR_AXIS_TYPE2["ANGLE_CATEGORY"] = "angle-category";
|
|
POLAR_AXIS_TYPE2["ANGLE_NUMBER"] = "angle-number";
|
|
POLAR_AXIS_TYPE2["RADIUS_CATEGORY"] = "radius-category";
|
|
POLAR_AXIS_TYPE2["RADIUS_NUMBER"] = "radius-number";
|
|
return POLAR_AXIS_TYPE2;
|
|
})(POLAR_AXIS_TYPE || {});
|
|
var POLAR_AXIS_SHAPE = /* @__PURE__ */ ((POLAR_AXIS_SHAPE2) => {
|
|
POLAR_AXIS_SHAPE2["CIRCLE"] = "circle";
|
|
POLAR_AXIS_SHAPE2["POLYGON"] = "polygon";
|
|
return POLAR_AXIS_SHAPE2;
|
|
})(POLAR_AXIS_SHAPE || {});
|
|
|
|
// packages/ag-charts-core/src/utils/format/color.ts
|
|
var lerp = (x, y, t) => x * (1 - t) + y * t;
|
|
var srgbToLinear = (value) => {
|
|
const sign = value < 0 ? -1 : 1;
|
|
const abs = Math.abs(value);
|
|
if (abs <= 0.04045)
|
|
return value / 12.92;
|
|
return sign * ((abs + 0.055) / 1.055) ** 2.4;
|
|
};
|
|
var srgbFromLinear = (value) => {
|
|
const sign = value < 0 ? -1 : 1;
|
|
const abs = Math.abs(value);
|
|
if (abs > 31308e-7) {
|
|
return sign * (1.055 * abs ** (1 / 2.4) - 0.055);
|
|
}
|
|
return 12.92 * value;
|
|
};
|
|
var _Color = class _Color {
|
|
/**
|
|
* Every color component should be in the [0, 1] range.
|
|
* Some easing functions (such as elastic easing) can overshoot the target value by some amount.
|
|
* So, when animating colors, if the source or target color components are already near
|
|
* or at the edge of the allowed [0, 1] range, it is possible for the intermediate color
|
|
* component value to end up outside of that range mid-animation. For this reason the constructor
|
|
* performs range checking/constraining.
|
|
* @param r Red component.
|
|
* @param g Green component.
|
|
* @param b Blue component.
|
|
* @param a Alpha (opacity) component.
|
|
*/
|
|
constructor(r, g, b, a = 1) {
|
|
this.r = clamp(0, r || 0, 1);
|
|
this.g = clamp(0, g || 0, 1);
|
|
this.b = clamp(0, b || 0, 1);
|
|
this.a = clamp(0, a || 0, 1);
|
|
}
|
|
/**
|
|
* A color string can be in one of the following formats to be valid:
|
|
* - #rgb
|
|
* - #rrggbb
|
|
* - rgb(r, g, b)
|
|
* - rgba(r, g, b, a)
|
|
* - CSS color name such as 'white', 'orange', 'cyan', etc.
|
|
*/
|
|
static validColorString(str) {
|
|
if (str.includes("#")) {
|
|
return !!_Color.parseHex(str);
|
|
}
|
|
if (str.includes("rgb")) {
|
|
return !!_Color.stringToRgba(str);
|
|
}
|
|
return _Color.nameToHex.has(str.toLowerCase());
|
|
}
|
|
/**
|
|
* The given string can be in one of the following formats:
|
|
* - #rgb
|
|
* - #rrggbb
|
|
* - rgb(r, g, b)
|
|
* - rgba(r, g, b, a)
|
|
* - CSS color name such as 'white', 'orange', 'cyan', etc.
|
|
* @param str
|
|
*/
|
|
static fromString(str) {
|
|
if (str.includes("#")) {
|
|
return _Color.fromHexString(str);
|
|
}
|
|
const hex = _Color.nameToHex.get(str.toLowerCase());
|
|
if (hex) {
|
|
return _Color.fromHexString(hex);
|
|
}
|
|
if (str.includes("rgb")) {
|
|
return _Color.fromRgbaString(str);
|
|
}
|
|
throw new Error(`Invalid color string: '${str}'`);
|
|
}
|
|
// See https://drafts.csswg.org/css-color/#hex-notation
|
|
static parseHex(input) {
|
|
input = input.replaceAll(" ", "").slice(1);
|
|
let parts;
|
|
switch (input.length) {
|
|
case 6:
|
|
case 8:
|
|
parts = [];
|
|
for (let i = 0; i < input.length; i += 2) {
|
|
parts.push(Number.parseInt(`${input[i]}${input[i + 1]}`, 16));
|
|
}
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
parts = input.split("").map((p) => Number.parseInt(p, 16)).map((p) => p + p * 16);
|
|
break;
|
|
}
|
|
if (parts?.length >= 3 && parts.every((p) => p >= 0)) {
|
|
if (parts.length === 3) {
|
|
parts.push(255);
|
|
}
|
|
return parts;
|
|
}
|
|
}
|
|
static fromHexString(str) {
|
|
const values = _Color.parseHex(str);
|
|
if (values) {
|
|
const [r, g, b, a] = values;
|
|
return new _Color(r / 255, g / 255, b / 255, a / 255);
|
|
}
|
|
throw new Error(`Malformed hexadecimal color string: '${str}'`);
|
|
}
|
|
static stringToRgba(str) {
|
|
let po = -1;
|
|
let pc = -1;
|
|
for (let i = 0; i < str.length; i++) {
|
|
const c = str[i];
|
|
if (po === -1 && c === "(") {
|
|
po = i;
|
|
} else if (c === ")") {
|
|
pc = i;
|
|
break;
|
|
}
|
|
}
|
|
if (po === -1 || pc === -1)
|
|
return;
|
|
const contents = str.substring(po + 1, pc);
|
|
const parts = contents.split(",");
|
|
const rgba = [];
|
|
for (let i = 0; i < parts.length; i++) {
|
|
const part = parts[i];
|
|
let value = Number.parseFloat(part);
|
|
if (!Number.isFinite(value)) {
|
|
return;
|
|
}
|
|
if (part.includes("%")) {
|
|
value = clamp(0, value, 100);
|
|
value /= 100;
|
|
} else if (i === 3) {
|
|
value = clamp(0, value, 1);
|
|
} else {
|
|
value = clamp(0, value, 255);
|
|
value /= 255;
|
|
}
|
|
rgba.push(value);
|
|
}
|
|
return rgba;
|
|
}
|
|
static fromRgbaString(str) {
|
|
const rgba = _Color.stringToRgba(str);
|
|
if (rgba) {
|
|
if (rgba.length === 3) {
|
|
return new _Color(rgba[0], rgba[1], rgba[2]);
|
|
} else if (rgba.length === 4) {
|
|
return new _Color(rgba[0], rgba[1], rgba[2], rgba[3]);
|
|
}
|
|
}
|
|
throw new Error(`Malformed rgb/rgba color string: '${str}'`);
|
|
}
|
|
static fromArray(arr) {
|
|
if (arr.length === 4) {
|
|
return new _Color(arr[0], arr[1], arr[2], arr[3]);
|
|
}
|
|
if (arr.length === 3) {
|
|
return new _Color(arr[0], arr[1], arr[2]);
|
|
}
|
|
throw new Error("The given array should contain 3 or 4 color components (numbers).");
|
|
}
|
|
static fromHSB(h, s, b, alpha = 1) {
|
|
const rgb = _Color.HSBtoRGB(h, s, b);
|
|
return new _Color(rgb[0], rgb[1], rgb[2], alpha);
|
|
}
|
|
static fromHSL(h, s, l, alpha = 1) {
|
|
const rgb = _Color.HSLtoRGB(h, s, l);
|
|
return new _Color(rgb[0], rgb[1], rgb[2], alpha);
|
|
}
|
|
static fromOKLCH(l, c, h, alpha = 1) {
|
|
const rgb = _Color.OKLCHtoRGB(l, c, h);
|
|
return new _Color(rgb[0], rgb[1], rgb[2], alpha);
|
|
}
|
|
static padHex(str) {
|
|
return str.length === 1 ? "0" + str : str;
|
|
}
|
|
toHexString() {
|
|
let hex = "#" + _Color.padHex(Math.round(this.r * 255).toString(16)) + _Color.padHex(Math.round(this.g * 255).toString(16)) + _Color.padHex(Math.round(this.b * 255).toString(16));
|
|
if (this.a < 1) {
|
|
hex += _Color.padHex(Math.round(this.a * 255).toString(16));
|
|
}
|
|
return hex;
|
|
}
|
|
toRgbaString(fractionDigits = 3) {
|
|
const components = [Math.round(this.r * 255), Math.round(this.g * 255), Math.round(this.b * 255)];
|
|
const k = Math.pow(10, fractionDigits);
|
|
if (this.a !== 1) {
|
|
components.push(Math.round(this.a * k) / k);
|
|
return `rgba(${components.join(", ")})`;
|
|
}
|
|
return `rgb(${components.join(", ")})`;
|
|
}
|
|
toString() {
|
|
if (this.a === 1) {
|
|
return this.toHexString();
|
|
}
|
|
return this.toRgbaString();
|
|
}
|
|
toHSB() {
|
|
return _Color.RGBtoHSB(this.r, this.g, this.b);
|
|
}
|
|
static RGBtoOKLCH(r, g, b) {
|
|
const LSRGB0 = srgbToLinear(r);
|
|
const LSRGB1 = srgbToLinear(g);
|
|
const LSRGB2 = srgbToLinear(b);
|
|
const LMS0 = Math.cbrt(0.4122214708 * LSRGB0 + 0.5363325363 * LSRGB1 + 0.0514459929 * LSRGB2);
|
|
const LMS1 = Math.cbrt(0.2119034982 * LSRGB0 + 0.6806995451 * LSRGB1 + 0.1073969566 * LSRGB2);
|
|
const LMS2 = Math.cbrt(0.0883024619 * LSRGB0 + 0.2817188376 * LSRGB1 + 0.6299787005 * LSRGB2);
|
|
const OKLAB0 = 0.2104542553 * LMS0 + 0.793617785 * LMS1 - 0.0040720468 * LMS2;
|
|
const OKLAB1 = 1.9779984951 * LMS0 - 2.428592205 * LMS1 + 0.4505937099 * LMS2;
|
|
const OKLAB2 = 0.0259040371 * LMS0 + 0.7827717662 * LMS1 - 0.808675766 * LMS2;
|
|
const hue = Math.atan2(OKLAB2, OKLAB1) * 180 / Math.PI;
|
|
const OKLCH0 = OKLAB0;
|
|
const OKLCH1 = Math.hypot(OKLAB1, OKLAB2);
|
|
const OKLCH2 = hue >= 0 ? hue : hue + 360;
|
|
return [OKLCH0, OKLCH1, OKLCH2];
|
|
}
|
|
static OKLCHtoRGB(l, c, h) {
|
|
const OKLAB0 = l;
|
|
const OKLAB1 = c * Math.cos(h * Math.PI / 180);
|
|
const OKLAB2 = c * Math.sin(h * Math.PI / 180);
|
|
const LMS0 = (OKLAB0 + 0.3963377774 * OKLAB1 + 0.2158037573 * OKLAB2) ** 3;
|
|
const LMS1 = (OKLAB0 - 0.1055613458 * OKLAB1 - 0.0638541728 * OKLAB2) ** 3;
|
|
const LMS2 = (OKLAB0 - 0.0894841775 * OKLAB1 - 1.291485548 * OKLAB2) ** 3;
|
|
const LSRGB0 = 4.0767416621 * LMS0 - 3.3077115913 * LMS1 + 0.2309699292 * LMS2;
|
|
const LSRGB1 = -1.2684380046 * LMS0 + 2.6097574011 * LMS1 - 0.3413193965 * LMS2;
|
|
const LSRGB2 = -0.0041960863 * LMS0 - 0.7034186147 * LMS1 + 1.707614701 * LMS2;
|
|
const SRGB0 = srgbFromLinear(LSRGB0);
|
|
const SRGB1 = srgbFromLinear(LSRGB1);
|
|
const SRGB2 = srgbFromLinear(LSRGB2);
|
|
return [SRGB0, SRGB1, SRGB2];
|
|
}
|
|
static RGBtoHSL(r, g, b) {
|
|
const min = Math.min(r, g, b);
|
|
const max = Math.max(r, g, b);
|
|
const l = (max + min) / 2;
|
|
let h;
|
|
let s;
|
|
if (max === min) {
|
|
h = 0;
|
|
s = 0;
|
|
} else {
|
|
const delta5 = max - min;
|
|
s = l > 0.5 ? delta5 / (2 - max - min) : delta5 / (max + min);
|
|
if (max === r) {
|
|
h = (g - b) / delta5 + (g < b ? 6 : 0);
|
|
} else if (max === g) {
|
|
h = (b - r) / delta5 + 2;
|
|
} else {
|
|
h = (r - g) / delta5 + 4;
|
|
}
|
|
h *= 360 / 6;
|
|
}
|
|
return [h, s, l];
|
|
}
|
|
static HSLtoRGB(h, s, l) {
|
|
h = (h % 360 + 360) % 360;
|
|
if (s === 0) {
|
|
return [l, l, l];
|
|
}
|
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
const p = 2 * l - q;
|
|
function hueToRgb(t) {
|
|
if (t < 0)
|
|
t += 1;
|
|
if (t > 1)
|
|
t -= 1;
|
|
if (t < 1 / 6)
|
|
return p + (q - p) * 6 * t;
|
|
if (t < 1 / 2)
|
|
return q;
|
|
if (t < 2 / 3)
|
|
return p + (q - p) * (2 / 3 - t) * 6;
|
|
return p;
|
|
}
|
|
const r = hueToRgb(h / 360 + 1 / 3);
|
|
const g = hueToRgb(h / 360);
|
|
const b = hueToRgb(h / 360 - 1 / 3);
|
|
return [r, g, b];
|
|
}
|
|
/**
|
|
* Converts the given RGB triple to an array of HSB (HSV) components.
|
|
*/
|
|
static RGBtoHSB(r, g, b) {
|
|
const min = Math.min(r, g, b);
|
|
const max = Math.max(r, g, b);
|
|
const S = max === 0 ? 0 : (max - min) / max;
|
|
let H = 0;
|
|
if (min !== max) {
|
|
const delta5 = max - min;
|
|
const rc = (max - r) / delta5;
|
|
const gc = (max - g) / delta5;
|
|
const bc = (max - b) / delta5;
|
|
if (r === max) {
|
|
H = bc - gc;
|
|
} else if (g === max) {
|
|
H = 2 + rc - bc;
|
|
} else {
|
|
H = 4 + gc - rc;
|
|
}
|
|
H /= 6;
|
|
if (H < 0) {
|
|
H = H + 1;
|
|
}
|
|
}
|
|
return [H * 360, S, max];
|
|
}
|
|
/**
|
|
* Converts the given HSB (HSV) triple to an array of RGB components.
|
|
*/
|
|
static HSBtoRGB(H, S, B) {
|
|
H = (H % 360 + 360) % 360 / 360;
|
|
let r = 0;
|
|
let g = 0;
|
|
let b = 0;
|
|
if (S === 0) {
|
|
r = g = b = B;
|
|
} else {
|
|
const h = (H - Math.floor(H)) * 6;
|
|
const f = h - Math.floor(h);
|
|
const p = B * (1 - S);
|
|
const q = B * (1 - S * f);
|
|
const t = B * (1 - S * (1 - f));
|
|
switch (Math.trunc(h)) {
|
|
case 0:
|
|
r = B;
|
|
g = t;
|
|
b = p;
|
|
break;
|
|
case 1:
|
|
r = q;
|
|
g = B;
|
|
b = p;
|
|
break;
|
|
case 2:
|
|
r = p;
|
|
g = B;
|
|
b = t;
|
|
break;
|
|
case 3:
|
|
r = p;
|
|
g = q;
|
|
b = B;
|
|
break;
|
|
case 4:
|
|
r = t;
|
|
g = p;
|
|
b = B;
|
|
break;
|
|
case 5:
|
|
r = B;
|
|
g = p;
|
|
b = q;
|
|
break;
|
|
}
|
|
}
|
|
return [r, g, b];
|
|
}
|
|
static mix(c0, c1, t) {
|
|
return new _Color(lerp(c0.r, c1.r, t), lerp(c0.g, c1.g, t), lerp(c0.b, c1.b, t), lerp(c0.a, c1.a, t));
|
|
}
|
|
static lighten(c, t) {
|
|
const oklch = _Color.RGBtoOKLCH(c.r, c.g, c.b);
|
|
return _Color.fromOKLCH(clamp(0, oklch[0] + t, 1), oklch[1], oklch[2]);
|
|
}
|
|
static darken(c, t) {
|
|
const oklch = _Color.RGBtoOKLCH(c.r, c.g, c.b);
|
|
return _Color.fromOKLCH(clamp(0, oklch[0] - t, 1), oklch[1], oklch[2]);
|
|
}
|
|
static interpolate(colors, count) {
|
|
const step = 1 / (colors.length - 1);
|
|
const oklchColors = colors.map((c) => _Color.RGBtoOKLCH(c.r, c.g, c.b));
|
|
return Array.from({ length: count }, (_, i) => {
|
|
const t = i / (count - 1);
|
|
const index = colors.length <= 2 ? 0 : Math.min(Math.floor(t * (colors.length - 1)), colors.length - 2);
|
|
const q = (t - index * step) / step;
|
|
const c0 = oklchColors[index];
|
|
const c1 = oklchColors[index + 1];
|
|
return _Color.fromOKLCH(lerp(c0[0], c1[0], q), lerp(c0[1], c1[1], q), lerp(c0[2], c1[2], q));
|
|
});
|
|
}
|
|
};
|
|
/**
|
|
* CSS Color Module Level 4:
|
|
* https://drafts.csswg.org/css-color/#named-colors
|
|
*/
|
|
_Color.nameToHex = /* @__PURE__ */ new Map([
|
|
["aliceblue", "#F0F8FF"],
|
|
["antiquewhite", "#FAEBD7"],
|
|
["aqua", "#00FFFF"],
|
|
["aquamarine", "#7FFFD4"],
|
|
["azure", "#F0FFFF"],
|
|
["beige", "#F5F5DC"],
|
|
["bisque", "#FFE4C4"],
|
|
["black", "#000000"],
|
|
["blanchedalmond", "#FFEBCD"],
|
|
["blue", "#0000FF"],
|
|
["blueviolet", "#8A2BE2"],
|
|
["brown", "#A52A2A"],
|
|
["burlywood", "#DEB887"],
|
|
["cadetblue", "#5F9EA0"],
|
|
["chartreuse", "#7FFF00"],
|
|
["chocolate", "#D2691E"],
|
|
["coral", "#FF7F50"],
|
|
["cornflowerblue", "#6495ED"],
|
|
["cornsilk", "#FFF8DC"],
|
|
["crimson", "#DC143C"],
|
|
["cyan", "#00FFFF"],
|
|
["darkblue", "#00008B"],
|
|
["darkcyan", "#008B8B"],
|
|
["darkgoldenrod", "#B8860B"],
|
|
["darkgray", "#A9A9A9"],
|
|
["darkgreen", "#006400"],
|
|
["darkgrey", "#A9A9A9"],
|
|
["darkkhaki", "#BDB76B"],
|
|
["darkmagenta", "#8B008B"],
|
|
["darkolivegreen", "#556B2F"],
|
|
["darkorange", "#FF8C00"],
|
|
["darkorchid", "#9932CC"],
|
|
["darkred", "#8B0000"],
|
|
["darksalmon", "#E9967A"],
|
|
["darkseagreen", "#8FBC8F"],
|
|
["darkslateblue", "#483D8B"],
|
|
["darkslategray", "#2F4F4F"],
|
|
["darkslategrey", "#2F4F4F"],
|
|
["darkturquoise", "#00CED1"],
|
|
["darkviolet", "#9400D3"],
|
|
["deeppink", "#FF1493"],
|
|
["deepskyblue", "#00BFFF"],
|
|
["dimgray", "#696969"],
|
|
["dimgrey", "#696969"],
|
|
["dodgerblue", "#1E90FF"],
|
|
["firebrick", "#B22222"],
|
|
["floralwhite", "#FFFAF0"],
|
|
["forestgreen", "#228B22"],
|
|
["fuchsia", "#FF00FF"],
|
|
["gainsboro", "#DCDCDC"],
|
|
["ghostwhite", "#F8F8FF"],
|
|
["gold", "#FFD700"],
|
|
["goldenrod", "#DAA520"],
|
|
["gray", "#808080"],
|
|
["green", "#008000"],
|
|
["greenyellow", "#ADFF2F"],
|
|
["grey", "#808080"],
|
|
["honeydew", "#F0FFF0"],
|
|
["hotpink", "#FF69B4"],
|
|
["indianred", "#CD5C5C"],
|
|
["indigo", "#4B0082"],
|
|
["ivory", "#FFFFF0"],
|
|
["khaki", "#F0E68C"],
|
|
["lavender", "#E6E6FA"],
|
|
["lavenderblush", "#FFF0F5"],
|
|
["lawngreen", "#7CFC00"],
|
|
["lemonchiffon", "#FFFACD"],
|
|
["lightblue", "#ADD8E6"],
|
|
["lightcoral", "#F08080"],
|
|
["lightcyan", "#E0FFFF"],
|
|
["lightgoldenrodyellow", "#FAFAD2"],
|
|
["lightgray", "#D3D3D3"],
|
|
["lightgreen", "#90EE90"],
|
|
["lightgrey", "#D3D3D3"],
|
|
["lightpink", "#FFB6C1"],
|
|
["lightsalmon", "#FFA07A"],
|
|
["lightseagreen", "#20B2AA"],
|
|
["lightskyblue", "#87CEFA"],
|
|
["lightslategray", "#778899"],
|
|
["lightslategrey", "#778899"],
|
|
["lightsteelblue", "#B0C4DE"],
|
|
["lightyellow", "#FFFFE0"],
|
|
["lime", "#00FF00"],
|
|
["limegreen", "#32CD32"],
|
|
["linen", "#FAF0E6"],
|
|
["magenta", "#FF00FF"],
|
|
["maroon", "#800000"],
|
|
["mediumaquamarine", "#66CDAA"],
|
|
["mediumblue", "#0000CD"],
|
|
["mediumorchid", "#BA55D3"],
|
|
["mediumpurple", "#9370DB"],
|
|
["mediumseagreen", "#3CB371"],
|
|
["mediumslateblue", "#7B68EE"],
|
|
["mediumspringgreen", "#00FA9A"],
|
|
["mediumturquoise", "#48D1CC"],
|
|
["mediumvioletred", "#C71585"],
|
|
["midnightblue", "#191970"],
|
|
["mintcream", "#F5FFFA"],
|
|
["mistyrose", "#FFE4E1"],
|
|
["moccasin", "#FFE4B5"],
|
|
["navajowhite", "#FFDEAD"],
|
|
["navy", "#000080"],
|
|
["oldlace", "#FDF5E6"],
|
|
["olive", "#808000"],
|
|
["olivedrab", "#6B8E23"],
|
|
["orange", "#FFA500"],
|
|
["orangered", "#FF4500"],
|
|
["orchid", "#DA70D6"],
|
|
["palegoldenrod", "#EEE8AA"],
|
|
["palegreen", "#98FB98"],
|
|
["paleturquoise", "#AFEEEE"],
|
|
["palevioletred", "#DB7093"],
|
|
["papayawhip", "#FFEFD5"],
|
|
["peachpuff", "#FFDAB9"],
|
|
["peru", "#CD853F"],
|
|
["pink", "#FFC0CB"],
|
|
["plum", "#DDA0DD"],
|
|
["powderblue", "#B0E0E6"],
|
|
["purple", "#800080"],
|
|
["rebeccapurple", "#663399"],
|
|
["red", "#FF0000"],
|
|
["rosybrown", "#BC8F8F"],
|
|
["royalblue", "#4169E1"],
|
|
["saddlebrown", "#8B4513"],
|
|
["salmon", "#FA8072"],
|
|
["sandybrown", "#F4A460"],
|
|
["seagreen", "#2E8B57"],
|
|
["seashell", "#FFF5EE"],
|
|
["sienna", "#A0522D"],
|
|
["silver", "#C0C0C0"],
|
|
["skyblue", "#87CEEB"],
|
|
["slateblue", "#6A5ACD"],
|
|
["slategray", "#708090"],
|
|
["slategrey", "#708090"],
|
|
["snow", "#FFFAFA"],
|
|
["springgreen", "#00FF7F"],
|
|
["steelblue", "#4682B4"],
|
|
["tan", "#D2B48C"],
|
|
["teal", "#008080"],
|
|
["thistle", "#D8BFD8"],
|
|
["tomato", "#FF6347"],
|
|
["transparent", "#00000000"],
|
|
["turquoise", "#40E0D0"],
|
|
["violet", "#EE82EE"],
|
|
["wheat", "#F5DEB3"],
|
|
["white", "#FFFFFF"],
|
|
["whitesmoke", "#F5F5F5"],
|
|
["yellow", "#FFFF00"],
|
|
["yellowgreen", "#9ACD32"]
|
|
]);
|
|
var Color = _Color;
|
|
|
|
// packages/ag-charts-core/src/config/themeUtil.ts
|
|
var DIRECTION_SWAP_AXES = {
|
|
x: {
|
|
position: "bottom" /* BOTTOM */,
|
|
type: {
|
|
$if: [
|
|
{ $eq: [{ $path: ["/series/0/direction", void 0] }, "horizontal"] },
|
|
"number" /* NUMBER */,
|
|
"category" /* CATEGORY */
|
|
]
|
|
}
|
|
},
|
|
y: {
|
|
position: "left" /* LEFT */,
|
|
type: {
|
|
$if: [
|
|
{ $eq: [{ $path: ["/series/0/direction", void 0] }, "horizontal"] },
|
|
"category" /* CATEGORY */,
|
|
"number" /* NUMBER */
|
|
]
|
|
}
|
|
}
|
|
};
|
|
var SAFE_FILL_OPERATION = {
|
|
$if: [
|
|
{
|
|
$or: [
|
|
{ $isGradient: { $palette: "fill" } },
|
|
{ $isPattern: { $palette: "fill" } },
|
|
{ $isImage: { $value: "$1" } }
|
|
]
|
|
},
|
|
{ $palette: "fillFallback" },
|
|
{ $palette: "fill" }
|
|
]
|
|
};
|
|
var SAFE_FILLS_OPERATION = {
|
|
$if: [
|
|
{
|
|
$or: [
|
|
{ $isGradient: { $palette: "fill" } },
|
|
{ $isPattern: { $palette: "fill" } },
|
|
{ $isImage: { $value: "$1" } }
|
|
]
|
|
},
|
|
{ $palette: "fillsFallback" },
|
|
{ $palette: "fills" }
|
|
]
|
|
};
|
|
var SAFE_STROKE_FILL_OPERATION = {
|
|
$if: [
|
|
{ $isGradient: { $palette: "fill" } },
|
|
{ $palette: "fillFallback" },
|
|
{
|
|
$if: [
|
|
{ $isPattern: { $palette: "fill" } },
|
|
{ $path: ["/stroke", { $palette: "fillFallback" }, { $palette: "fill" }] },
|
|
{ $palette: "fill" }
|
|
]
|
|
}
|
|
]
|
|
};
|
|
var SAFE_RANGE2_OPERATION = {
|
|
$if: [
|
|
{
|
|
$or: [
|
|
{ $isGradient: { $palette: "fill" } },
|
|
{ $isPattern: { $palette: "fill" } },
|
|
{ $isImage: { $value: "$1" } }
|
|
]
|
|
},
|
|
[{ $palette: "fillFallback" }, { $palette: "fillFallback" }],
|
|
{ $palette: "range2" }
|
|
]
|
|
};
|
|
var FILL_GRADIENT_BLANK_DEFAULTS = {
|
|
type: "gradient",
|
|
gradient: "linear",
|
|
bounds: "item",
|
|
colorStops: [{ color: "black" }],
|
|
rotation: 0,
|
|
reverse: false,
|
|
colorSpace: "rgb"
|
|
};
|
|
var FILL_GRADIENT_LINEAR_DEFAULTS = {
|
|
type: "gradient",
|
|
gradient: "linear",
|
|
bounds: "item",
|
|
colorStops: { $shallow: { $map: [{ color: { $value: "$1" } }, { $palette: "gradient" }] } },
|
|
rotation: 0,
|
|
reverse: false,
|
|
colorSpace: "rgb"
|
|
};
|
|
var FILL_GRADIENT_LINEAR_HIERARCHY_DEFAULTS = {
|
|
...FILL_GRADIENT_LINEAR_DEFAULTS,
|
|
colorStops: {
|
|
$shallow: [
|
|
{
|
|
color: {
|
|
$mix: [{ $path: ["/1", { $palette: "fill" }, { $palette: "hierarchyColors" }] }, "black", 0.15]
|
|
}
|
|
},
|
|
{
|
|
color: {
|
|
$mix: [{ $path: ["/1", { $palette: "fill" }, { $palette: "hierarchyColors" }] }, "white", 0.15]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
};
|
|
var FILL_GRADIENT_LINEAR_SINGLE_DEFAULTS = {
|
|
...FILL_GRADIENT_LINEAR_DEFAULTS,
|
|
colorStops: {
|
|
$map: [{ color: { $value: "$1" } }, { $path: ["/0", void 0, { $palette: "gradients" }] }]
|
|
}
|
|
};
|
|
var FILL_GRADIENT_LINEAR_KEYED_DEFAULTS = (key) => ({
|
|
...FILL_GRADIENT_LINEAR_DEFAULTS,
|
|
colorStops: {
|
|
$shallow: {
|
|
$if: [
|
|
{
|
|
$or: [
|
|
{ $isGradient: { $palette: `${key}.fill` } },
|
|
{ $isPattern: { $palette: `${key}.fill` } },
|
|
{ $isImage: { $palette: `${key}.fill` } }
|
|
]
|
|
},
|
|
{ $path: ["/colorStops", void 0, { $palette: `${key}.fill` }] },
|
|
[
|
|
{ color: { $mix: [{ $palette: `${key}.fill` }, "black", 0.15] } },
|
|
{ color: { $mix: [{ $palette: `${key}.fill` }, "white", 0.15] } }
|
|
]
|
|
]
|
|
}
|
|
}
|
|
});
|
|
var FILL_GRADIENT_RADIAL_DEFAULTS = {
|
|
type: "gradient",
|
|
gradient: "radial",
|
|
bounds: "item",
|
|
colorStops: { $shallow: { $map: [{ color: { $value: "$1" } }, { $palette: "gradient" }] } },
|
|
rotation: 0,
|
|
reverse: false,
|
|
colorSpace: "rgb"
|
|
};
|
|
var FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS = {
|
|
...FILL_GRADIENT_RADIAL_DEFAULTS,
|
|
reverse: true
|
|
};
|
|
var FILL_GRADIENT_RADIAL_SERIES_DEFAULTS = {
|
|
...FILL_GRADIENT_RADIAL_DEFAULTS,
|
|
bounds: "series"
|
|
};
|
|
var FILL_GRADIENT_RADIAL_REVERSED_SERIES_DEFAULTS = {
|
|
...FILL_GRADIENT_RADIAL_DEFAULTS,
|
|
bounds: "series",
|
|
reverse: true
|
|
};
|
|
var FILL_GRADIENT_CONIC_SERIES_DEFAULTS = {
|
|
type: "gradient",
|
|
gradient: "conic",
|
|
bounds: "series",
|
|
colorStops: { $map: [{ color: { $value: "$1" } }, { $palette: "gradient" }] },
|
|
rotation: 0,
|
|
reverse: false,
|
|
colorSpace: "rgb"
|
|
};
|
|
var FILL_PATTERN_DEFAULTS = {
|
|
type: "pattern",
|
|
pattern: "forward-slanted-lines",
|
|
width: { $isUserOption: ["./height", { $path: "./height" }, 10] },
|
|
height: { $isUserOption: ["./width", { $path: "./width" }, 10] },
|
|
padding: 2,
|
|
fill: {
|
|
$if: [
|
|
{
|
|
$or: [{ $isGradient: { $palette: "fill" } }, { $isImage: { $palette: "fill" } }]
|
|
},
|
|
{ $palette: "fillFallback" },
|
|
{
|
|
$if: [
|
|
{ $isPattern: { $palette: "fill" } },
|
|
{ $path: ["/fill", { $palette: "fillFallback" }, { $palette: "fill" }] },
|
|
{ $palette: "fill" }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
fillOpacity: 1,
|
|
stroke: SAFE_STROKE_FILL_OPERATION,
|
|
strokeOpacity: 1,
|
|
strokeWidth: {
|
|
$switch: [
|
|
{ $path: "./pattern" },
|
|
0,
|
|
[["backward-slanted-lines", "forward-slanted-lines", "horizontal-lines", "vertical-lines"], 4]
|
|
]
|
|
},
|
|
backgroundFill: "none",
|
|
backgroundFillOpacity: 1,
|
|
rotation: 0,
|
|
scale: 1
|
|
};
|
|
var FILL_PATTERN_SINGLE_DEFAULTS = {
|
|
...FILL_PATTERN_DEFAULTS,
|
|
stroke: {
|
|
$if: [
|
|
{ $isGradient: { $palette: "fill" } },
|
|
{ $path: ["/0", void 0, { $palette: "fillsFallback" }] },
|
|
{
|
|
$if: [
|
|
{ $isPattern: { $palette: "fill" } },
|
|
{
|
|
$path: [
|
|
"/stroke",
|
|
{ $path: ["/0", void 0, { $palette: "fillsFallback" }] },
|
|
{ $path: ["/0", void 0, { $palette: "fills" }] }
|
|
]
|
|
},
|
|
{ $path: ["/0", void 0, { $palette: "fills" }] }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
fill: {
|
|
$if: [
|
|
{
|
|
$or: [{ $isGradient: { $palette: "fill" } }, { $isImage: { $palette: "fill" } }]
|
|
},
|
|
{ $path: ["/0", void 0, { $palette: "fillsFallback" }] },
|
|
{
|
|
$if: [
|
|
{ $isPattern: { $palette: "fill" } },
|
|
{
|
|
$path: [
|
|
"/fill",
|
|
{ $path: ["/0", void 0, { $palette: "fillsFallback" }] },
|
|
{ $path: ["/0", void 0, { $palette: "fills" }] }
|
|
]
|
|
},
|
|
{ $path: ["/0", void 0, { $palette: "fills" }] }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
};
|
|
var FILL_PATTERN_BLANK_DEFAULTS = {
|
|
type: "pattern",
|
|
pattern: "forward-slanted-lines",
|
|
width: 8,
|
|
height: 8,
|
|
padding: 1,
|
|
fill: "black",
|
|
fillOpacity: 1,
|
|
backgroundFill: "white",
|
|
backgroundFillOpacity: 1,
|
|
stroke: "black",
|
|
strokeOpacity: 1,
|
|
strokeWidth: 1,
|
|
rotation: 0,
|
|
scale: 1
|
|
};
|
|
var FILL_PATTERN_HIERARCHY_DEFAULTS = {
|
|
...FILL_PATTERN_DEFAULTS,
|
|
fill: { $path: ["/1", { $palette: "fill" }, { $palette: "hierarchyColors" }] },
|
|
stroke: { $path: ["/1", { $palette: "fill" }, { $palette: "hierarchyColors" }] }
|
|
};
|
|
var FILL_PATTERN_KEYED_DEFAULTS = (key) => ({
|
|
...FILL_PATTERN_DEFAULTS,
|
|
stroke: {
|
|
$if: [
|
|
{ $isGradient: { $palette: `${key}.fill` } },
|
|
{ $palette: "fillFallback" },
|
|
{
|
|
$if: [
|
|
{ $isPattern: { $palette: `${key}.fill` } },
|
|
{ $path: ["/stroke", { $palette: "fillFallback" }, { $palette: `${key}.fill` }] },
|
|
{ $palette: `${key}.fill` }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
});
|
|
var FILL_IMAGE_DEFAULTS = {
|
|
type: "image",
|
|
backgroundFill: { $palette: "fillFallback" },
|
|
backgroundFillOpacity: 1,
|
|
repeat: "no-repeat",
|
|
fit: "contain",
|
|
rotation: 0
|
|
};
|
|
var FILL_IMAGE_BLANK_DEFAULTS = {
|
|
type: "image",
|
|
backgroundFill: "black",
|
|
backgroundFillOpacity: 1,
|
|
rotation: 0,
|
|
repeat: "no-repeat",
|
|
fit: "contain",
|
|
width: 8,
|
|
height: 8
|
|
};
|
|
function getSequentialColors(colors) {
|
|
return mapValues(colors, (value) => {
|
|
const color2 = Color.fromString(value);
|
|
return [Color.darken(color2, 0.15).toString(), value, Color.lighten(color2, 0.15).toString()];
|
|
});
|
|
}
|
|
var LABEL_BOXING_DEFAULTS = {
|
|
padding: 8,
|
|
cornerRadius: 4,
|
|
fill: {
|
|
$if: [
|
|
{
|
|
$and: [
|
|
{ $eq: [{ $path: "./fill/type" }, "image"] },
|
|
{ $isUserOption: ["./fill/backgroundFill", false, true] }
|
|
]
|
|
},
|
|
{ backgroundFill: "transparent" },
|
|
void 0
|
|
]
|
|
},
|
|
border: {
|
|
enabled: { $isUserOption: ["../border", true, false] },
|
|
strokeWidth: 1,
|
|
stroke: { $foregroundOpacity: 0.08 }
|
|
}
|
|
};
|
|
var MULTI_SERIES_HIGHLIGHT_STYLE = {
|
|
enabled: true,
|
|
unhighlightedItem: {
|
|
opacity: 0.6
|
|
},
|
|
unhighlightedSeries: {
|
|
opacity: 0.2
|
|
}
|
|
};
|
|
var MARKER_SERIES_HIGHLIGHT_STYLE = {
|
|
enabled: true,
|
|
unhighlightedSeries: {
|
|
opacity: 0.2
|
|
}
|
|
};
|
|
var PART_WHOLE_HIGHLIGHT_STYLE = {
|
|
enabled: true,
|
|
unhighlightedItem: {
|
|
opacity: 0.2
|
|
},
|
|
unhighlightedSeries: {
|
|
opacity: 0.2
|
|
}
|
|
};
|
|
var SINGLE_SERIES_HIGHLIGHT_STYLE = {
|
|
enabled: true,
|
|
unhighlightedItem: {
|
|
opacity: 0.2
|
|
}
|
|
};
|
|
var LEGEND_CONTAINER_THEME = {
|
|
border: {
|
|
enabled: false,
|
|
stroke: { $foregroundBackgroundMix: 0.25 },
|
|
strokeOpacity: 1,
|
|
strokeWidth: 1
|
|
},
|
|
cornerRadius: 4,
|
|
fillOpacity: 1,
|
|
padding: {
|
|
$if: [{ $eq: [{ $path: "./border/enabled" }, true] }, 5, { $isUserOption: ["./fill", 5, 0] }]
|
|
}
|
|
};
|
|
var SEGMENTATION_DEFAULTS = {
|
|
enabled: false,
|
|
key: "x",
|
|
segments: {
|
|
$apply: {
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $path: "../../../fill" },
|
|
["gradient", FILL_GRADIENT_LINEAR_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: { $path: "../../../stroke" },
|
|
fillOpacity: { $path: "../../../fillOpacity" },
|
|
strokeWidth: {
|
|
$isUserOption: [
|
|
"./stroke",
|
|
{
|
|
$isUserOption: [
|
|
"../../../strokeWidth",
|
|
{ $path: "../../../strokeWidth" },
|
|
{
|
|
$if: [
|
|
{ $greaterThan: [{ $path: "../../../strokeWidth" }, 0] },
|
|
{ $path: "../../../strokeWidth" },
|
|
2
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{ $path: "../../../strokeWidth" }
|
|
]
|
|
},
|
|
strokeOpacity: { $path: "../../../strokeOpacity" },
|
|
lineDash: { $path: "../../../lineDash" },
|
|
lineDashOffset: { $path: "../../../lineDashOffset" }
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-core/src/state/memento.ts
|
|
var MementoCaretaker = class _MementoCaretaker {
|
|
constructor(version) {
|
|
this.version = version.split("-")[0];
|
|
}
|
|
save(...originators) {
|
|
const packet = { version: this.version };
|
|
for (const originator of Object.values(originators)) {
|
|
packet[originator.mementoOriginatorKey] = this.encode(originator, originator.createMemento());
|
|
}
|
|
return packet;
|
|
}
|
|
restore(blob, ...originators) {
|
|
if (typeof blob !== "object") {
|
|
warnOnce(`Could not restore data of type [${typeof blob}], expecting an object, ignoring.`);
|
|
return;
|
|
}
|
|
if (blob == null) {
|
|
warnOnce(`Could not restore data of type [null], expecting an object, ignoring.`);
|
|
return;
|
|
}
|
|
if (!("version" in blob) || typeof blob.version !== "string") {
|
|
warnOnce(`Could not restore data, missing [version] string in object, ignoring.`);
|
|
return;
|
|
}
|
|
for (const originator of originators) {
|
|
const memento = this.decode(originator, blob[originator.mementoOriginatorKey]);
|
|
const messages = [];
|
|
if (!originator.guardMemento(memento, messages)) {
|
|
const messagesString = messages.length > 0 ? `
|
|
|
|
${messages.join("\n\n")}
|
|
|
|
` : "";
|
|
warnOnce(
|
|
`Could not restore [${originator.mementoOriginatorKey}] data, value was invalid, ignoring.${messagesString}`,
|
|
memento
|
|
);
|
|
return;
|
|
}
|
|
originator.restoreMemento(this.version, blob.version, memento);
|
|
}
|
|
}
|
|
/**
|
|
* Encode a memento as a serializable object, encoding any non-serializble types.
|
|
*/
|
|
encode(originator, memento) {
|
|
try {
|
|
return JSON.parse(JSON.stringify(memento, _MementoCaretaker.encodeTypes));
|
|
} catch (error2) {
|
|
throw new Error(`Failed to encode [${originator.mementoOriginatorKey}] value [${error2}].`, {
|
|
cause: error2
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* Decode an encoded memento, decoding any non-serializable types.
|
|
*/
|
|
decode(originator, encoded) {
|
|
if (encoded == null)
|
|
return encoded;
|
|
try {
|
|
return JSON.parse(JSON.stringify(encoded), _MementoCaretaker.decodeTypes);
|
|
} catch (error2) {
|
|
throw new Error(`Failed to decode [${originator.mementoOriginatorKey}] value [${error2}].`, {
|
|
cause: error2
|
|
});
|
|
}
|
|
}
|
|
static encodeTypes(key, value) {
|
|
if (isDate(this[key])) {
|
|
return { __type: "date", value: this[key].toISOString() };
|
|
}
|
|
return value;
|
|
}
|
|
static decodeTypes(key, value) {
|
|
if (isObject(this[key]) && "__type" in this[key] && this[key].__type === "date") {
|
|
return new Date(this[key].value);
|
|
}
|
|
return value;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-core/src/types/axisDirection.ts
|
|
var ChartAxisDirection = /* @__PURE__ */ ((ChartAxisDirection4) => {
|
|
ChartAxisDirection4["X"] = "x";
|
|
ChartAxisDirection4["Y"] = "y";
|
|
ChartAxisDirection4["Angle"] = "angle";
|
|
ChartAxisDirection4["Radius"] = "radius";
|
|
return ChartAxisDirection4;
|
|
})(ChartAxisDirection || {});
|
|
|
|
// packages/ag-charts-core/src/types/updateType.ts
|
|
var ChartUpdateType = /* @__PURE__ */ ((ChartUpdateType2) => {
|
|
ChartUpdateType2[ChartUpdateType2["FULL"] = 0] = "FULL";
|
|
ChartUpdateType2[ChartUpdateType2["UPDATE_DATA"] = 1] = "UPDATE_DATA";
|
|
ChartUpdateType2[ChartUpdateType2["PROCESS_DATA"] = 2] = "PROCESS_DATA";
|
|
ChartUpdateType2[ChartUpdateType2["PROCESS_DOMAIN"] = 3] = "PROCESS_DOMAIN";
|
|
ChartUpdateType2[ChartUpdateType2["PROCESS_RANGE"] = 4] = "PROCESS_RANGE";
|
|
ChartUpdateType2[ChartUpdateType2["PERFORM_LAYOUT"] = 5] = "PERFORM_LAYOUT";
|
|
ChartUpdateType2[ChartUpdateType2["PRE_SERIES_UPDATE"] = 6] = "PRE_SERIES_UPDATE";
|
|
ChartUpdateType2[ChartUpdateType2["SERIES_UPDATE"] = 7] = "SERIES_UPDATE";
|
|
ChartUpdateType2[ChartUpdateType2["PRE_SCENE_RENDER"] = 8] = "PRE_SCENE_RENDER";
|
|
ChartUpdateType2[ChartUpdateType2["SCENE_RENDER"] = 9] = "SCENE_RENDER";
|
|
ChartUpdateType2[ChartUpdateType2["NONE"] = 10] = "NONE";
|
|
return ChartUpdateType2;
|
|
})(ChartUpdateType || {});
|
|
|
|
// packages/ag-charts-core/src/types/zIndexMap.ts
|
|
var ZIndexMap = /* @__PURE__ */ ((ZIndexMap2) => {
|
|
ZIndexMap2[ZIndexMap2["CHART_BACKGROUND"] = 0] = "CHART_BACKGROUND";
|
|
ZIndexMap2[ZIndexMap2["AXIS_BAND_HIGHLIGHT"] = 1] = "AXIS_BAND_HIGHLIGHT";
|
|
ZIndexMap2[ZIndexMap2["AXIS_GRID"] = 2] = "AXIS_GRID";
|
|
ZIndexMap2[ZIndexMap2["AXIS"] = 3] = "AXIS";
|
|
ZIndexMap2[ZIndexMap2["SERIES_AREA_CONTAINER"] = 4] = "SERIES_AREA_CONTAINER";
|
|
ZIndexMap2[ZIndexMap2["ZOOM_SELECTION"] = 5] = "ZOOM_SELECTION";
|
|
ZIndexMap2[ZIndexMap2["SERIES_CROSSLINE_RANGE"] = 6] = "SERIES_CROSSLINE_RANGE";
|
|
ZIndexMap2[ZIndexMap2["SERIES_LAYER"] = 7] = "SERIES_LAYER";
|
|
ZIndexMap2[ZIndexMap2["AXIS_FOREGROUND"] = 8] = "AXIS_FOREGROUND";
|
|
ZIndexMap2[ZIndexMap2["SERIES_CROSSHAIR"] = 9] = "SERIES_CROSSHAIR";
|
|
ZIndexMap2[ZIndexMap2["SERIES_CROSSLINE_LINE"] = 10] = "SERIES_CROSSLINE_LINE";
|
|
ZIndexMap2[ZIndexMap2["SERIES_ANNOTATION"] = 11] = "SERIES_ANNOTATION";
|
|
ZIndexMap2[ZIndexMap2["CHART_ANNOTATION"] = 12] = "CHART_ANNOTATION";
|
|
ZIndexMap2[ZIndexMap2["CHART_ANNOTATION_FOCUSED"] = 13] = "CHART_ANNOTATION_FOCUSED";
|
|
ZIndexMap2[ZIndexMap2["STATUS_BAR"] = 14] = "STATUS_BAR";
|
|
ZIndexMap2[ZIndexMap2["SERIES_LABEL"] = 15] = "SERIES_LABEL";
|
|
ZIndexMap2[ZIndexMap2["LEGEND"] = 16] = "LEGEND";
|
|
ZIndexMap2[ZIndexMap2["NAVIGATOR"] = 17] = "NAVIGATOR";
|
|
ZIndexMap2[ZIndexMap2["FOREGROUND"] = 18] = "FOREGROUND";
|
|
return ZIndexMap2;
|
|
})(ZIndexMap || {});
|
|
var SeriesZIndexMap = /* @__PURE__ */ ((SeriesZIndexMap2) => {
|
|
SeriesZIndexMap2[SeriesZIndexMap2["BACKGROUND"] = 0] = "BACKGROUND";
|
|
SeriesZIndexMap2[SeriesZIndexMap2["ANY_CONTENT"] = 1] = "ANY_CONTENT";
|
|
return SeriesZIndexMap2;
|
|
})(SeriesZIndexMap || {});
|
|
var SeriesContentZIndexMap = /* @__PURE__ */ ((SeriesContentZIndexMap2) => {
|
|
SeriesContentZIndexMap2[SeriesContentZIndexMap2["FOREGROUND"] = 0] = "FOREGROUND";
|
|
SeriesContentZIndexMap2[SeriesContentZIndexMap2["HIGHLIGHT"] = 1] = "HIGHLIGHT";
|
|
SeriesContentZIndexMap2[SeriesContentZIndexMap2["LABEL"] = 2] = "LABEL";
|
|
return SeriesContentZIndexMap2;
|
|
})(SeriesContentZIndexMap || {});
|
|
var PolarZIndexMap = /* @__PURE__ */ ((PolarZIndexMap2) => {
|
|
PolarZIndexMap2[PolarZIndexMap2["BACKGROUND"] = 0] = "BACKGROUND";
|
|
PolarZIndexMap2[PolarZIndexMap2["FOREGROUND"] = 1] = "FOREGROUND";
|
|
PolarZIndexMap2[PolarZIndexMap2["HIGHLIGHT"] = 2] = "HIGHLIGHT";
|
|
PolarZIndexMap2[PolarZIndexMap2["LABEL"] = 3] = "LABEL";
|
|
return PolarZIndexMap2;
|
|
})(PolarZIndexMap || {});
|
|
|
|
// packages/ag-charts-core/src/chart/legendUtil.ts
|
|
function expandLegendPosition(position) {
|
|
const {
|
|
placement = "bottom",
|
|
floating = false,
|
|
xOffset = 0,
|
|
yOffset = 0
|
|
} = typeof position === "string" ? { placement: position, floating: false } : position;
|
|
return { placement, floating, xOffset, yOffset };
|
|
}
|
|
|
|
// packages/ag-charts-core/src/state/properties.ts
|
|
var BaseProperties = class {
|
|
handleUnknownProperties(_unknownKeys, _properties) {
|
|
}
|
|
set(properties) {
|
|
const { className = this.constructor.name } = this.constructor;
|
|
if (properties == null) {
|
|
this.clear();
|
|
return this;
|
|
}
|
|
if (typeof properties !== "object") {
|
|
warn(`unable to set ${className} - expecting a properties object`);
|
|
return this;
|
|
}
|
|
const keys = new Set(Object.keys(properties));
|
|
for (const propertyKey of listDecoratedProperties(this)) {
|
|
if (keys.has(propertyKey)) {
|
|
const value = properties[propertyKey];
|
|
const self = this;
|
|
if (isProperties(self[propertyKey])) {
|
|
if (self[propertyKey] instanceof PropertiesArray) {
|
|
const array2 = self[propertyKey].reset(value);
|
|
if (array2 == null) {
|
|
warn(`unable to set [${String(propertyKey)}] - expecting a properties array`);
|
|
} else {
|
|
self[propertyKey] = array2;
|
|
}
|
|
} else {
|
|
self[propertyKey].set(value);
|
|
}
|
|
} else if (isPlainObject(value)) {
|
|
self[propertyKey] = merge(value, self[propertyKey] ?? {});
|
|
} else {
|
|
self[propertyKey] = value;
|
|
}
|
|
keys.delete(propertyKey);
|
|
}
|
|
}
|
|
this.handleUnknownProperties(keys, properties);
|
|
for (const unknownKey of keys) {
|
|
warn(`unable to set [${String(unknownKey)}] in ${className} - property is unknown`);
|
|
}
|
|
return this;
|
|
}
|
|
clear() {
|
|
for (const propertyKey of listDecoratedProperties(this)) {
|
|
const currentValue = this[propertyKey];
|
|
if (isProperties(currentValue)) {
|
|
currentValue.clear();
|
|
} else {
|
|
this[propertyKey] = void 0;
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
toJson() {
|
|
return listDecoratedProperties(this).reduce((object2, propertyKey) => {
|
|
const propertyValue = this[propertyKey];
|
|
object2[String(propertyKey)] = isProperties(propertyValue) ? propertyValue.toJson() : propertyValue;
|
|
return object2;
|
|
}, {});
|
|
}
|
|
};
|
|
var PropertiesArray = class _PropertiesArray extends Array {
|
|
constructor(itemFactory, ...properties) {
|
|
super(properties.length);
|
|
const isConstructor = (value2) => Boolean(value2?.prototype?.constructor?.name);
|
|
const value = isConstructor(itemFactory) ? (params) => new itemFactory().set(params) : itemFactory;
|
|
Object.defineProperty(this, "itemFactory", { value, enumerable: false, configurable: false });
|
|
this.set(properties);
|
|
}
|
|
set(properties) {
|
|
if (isArray(properties)) {
|
|
this.length = properties.length;
|
|
for (let i = 0; i < properties.length; i++) {
|
|
this[i] = this.itemFactory(properties[i]);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
reset(properties) {
|
|
if (Array.isArray(properties)) {
|
|
return new _PropertiesArray(this.itemFactory, ...properties);
|
|
}
|
|
}
|
|
toJson() {
|
|
return this.map((value) => value?.toJson?.() ?? value);
|
|
}
|
|
};
|
|
function isProperties(value) {
|
|
return value instanceof BaseProperties || value instanceof PropertiesArray;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/chart/interpolationProperties.ts
|
|
var InterpolationProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "linear";
|
|
this.tension = 1;
|
|
this.position = "end";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], InterpolationProperties.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], InterpolationProperties.prototype, "tension", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], InterpolationProperties.prototype, "position", 2);
|
|
|
|
// packages/ag-charts-core/src/utils/data/numberArray.ts
|
|
function clampArray(value, array2) {
|
|
const [min, max] = findMinMax(array2);
|
|
return clamp(min, value, max);
|
|
}
|
|
function findMinMax(array2) {
|
|
if (array2.length === 0)
|
|
return [];
|
|
const result = [Infinity, -Infinity];
|
|
for (const val of array2) {
|
|
if (val < result[0])
|
|
result[0] = val;
|
|
if (val > result[1])
|
|
result[1] = val;
|
|
}
|
|
return result;
|
|
}
|
|
function findRangeExtent(array2) {
|
|
const [min, max] = findMinMax(array2);
|
|
return max - min;
|
|
}
|
|
function nextPowerOf2(value) {
|
|
value = Math.trunc(value);
|
|
if (value <= 0)
|
|
return 1;
|
|
if (value === 1)
|
|
return 2;
|
|
return 1 << 32 - Math.clz32(value - 1);
|
|
}
|
|
function previousPowerOf2(value) {
|
|
value = Math.trunc(value);
|
|
if (value <= 0)
|
|
return 0;
|
|
if (value === 1)
|
|
return 1;
|
|
return 1 << 31 - Math.clz32(value);
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/aggregation.ts
|
|
var AGGREGATION_INDEX_X_MIN = 0;
|
|
var AGGREGATION_INDEX_X_MAX = 1;
|
|
var AGGREGATION_INDEX_Y_MIN = 2;
|
|
var AGGREGATION_INDEX_Y_MAX = 3;
|
|
var AGGREGATION_SPAN = 4;
|
|
var AGGREGATION_THRESHOLD = 1e3;
|
|
var AGGREGATION_MAX_POINTS = 10;
|
|
var AGGREGATION_MIN_RANGE = 64;
|
|
var AGGREGATION_INDEX_UNSET = 4294967295;
|
|
var SMALLEST_INTERVAL_MIN_RECURSE = 3;
|
|
var SMALLEST_INTERVAL_RECURSE_LIMIT = 20;
|
|
var SMALLEST_INTERVAL_MAX_INDEX_ADJUSTMENTS = 100;
|
|
function estimateSmallestPixelIntervalIter(xValues, d0, d1, startDatumIndex, endDatumIndex, currentSmallestInterval, depth, xNeedsValueOf) {
|
|
let indexAdjustments = 0;
|
|
while (indexAdjustments < SMALLEST_INTERVAL_MAX_INDEX_ADJUSTMENTS && xValues[startDatumIndex] == null && startDatumIndex < endDatumIndex) {
|
|
startDatumIndex += 1;
|
|
indexAdjustments += 1;
|
|
}
|
|
while (indexAdjustments < SMALLEST_INTERVAL_MAX_INDEX_ADJUSTMENTS && xValues[endDatumIndex] == null && endDatumIndex > startDatumIndex) {
|
|
endDatumIndex -= 1;
|
|
indexAdjustments += 1;
|
|
}
|
|
if (indexAdjustments >= SMALLEST_INTERVAL_MAX_INDEX_ADJUSTMENTS || startDatumIndex >= endDatumIndex) {
|
|
return currentSmallestInterval;
|
|
}
|
|
const ratio2 = Number.isFinite(d0) ? aggregationXRatioForXValue(xValues[endDatumIndex], d0, d1, xNeedsValueOf) - aggregationXRatioForXValue(xValues[startDatumIndex], d0, d1, xNeedsValueOf) : aggregationXRatioForDatumIndex(endDatumIndex, xValues.length) - aggregationXRatioForDatumIndex(startDatumIndex, xValues.length);
|
|
if (ratio2 === 0 || !Number.isFinite(ratio2))
|
|
return currentSmallestInterval;
|
|
const currentInterval = Math.abs(ratio2) / (endDatumIndex - startDatumIndex);
|
|
let recurse;
|
|
if (depth < SMALLEST_INTERVAL_MIN_RECURSE) {
|
|
recurse = true;
|
|
} else if (depth > SMALLEST_INTERVAL_RECURSE_LIMIT) {
|
|
recurse = false;
|
|
} else {
|
|
recurse = currentInterval <= currentSmallestInterval;
|
|
}
|
|
currentSmallestInterval = Math.min(currentSmallestInterval, currentInterval);
|
|
if (!recurse)
|
|
return currentSmallestInterval;
|
|
const midIndex = Math.floor((startDatumIndex + endDatumIndex) / 2);
|
|
const leadingInterval = estimateSmallestPixelIntervalIter(
|
|
xValues,
|
|
d0,
|
|
d1,
|
|
startDatumIndex,
|
|
midIndex,
|
|
currentSmallestInterval,
|
|
depth + 1,
|
|
xNeedsValueOf
|
|
);
|
|
const trailingInterval = estimateSmallestPixelIntervalIter(
|
|
xValues,
|
|
d0,
|
|
d1,
|
|
midIndex + 1,
|
|
endDatumIndex,
|
|
currentSmallestInterval,
|
|
depth + 1,
|
|
xNeedsValueOf
|
|
);
|
|
return Math.min(leadingInterval, trailingInterval, currentSmallestInterval);
|
|
}
|
|
function estimateSmallestPixelInterval(xValues, d0, d1, xNeedsValueOf) {
|
|
return estimateSmallestPixelIntervalIter(
|
|
xValues,
|
|
d0,
|
|
d1,
|
|
0,
|
|
xValues.length - 1,
|
|
1 / (xValues.length - 1),
|
|
0,
|
|
xNeedsValueOf
|
|
);
|
|
}
|
|
function aggregationRangeFittingPoints(xValues, d0, d1, opts) {
|
|
if (Number.isFinite(d0)) {
|
|
const smallestKeyInterval = opts?.smallestKeyInterval;
|
|
const xNeedsValueOf = opts?.xNeedsValueOf ?? true;
|
|
const smallestPixelInterval = smallestKeyInterval == null ? estimateSmallestPixelInterval(xValues, d0, d1, xNeedsValueOf) : smallestKeyInterval / (d1 - d0);
|
|
return nextPowerOf2(Math.trunc(1 / smallestPixelInterval)) >> 3;
|
|
} else {
|
|
let power = Math.ceil(Math.log2(xValues.length)) - 1;
|
|
power = Math.min(Math.max(power, 0), 24);
|
|
return Math.trunc(2 ** power);
|
|
}
|
|
}
|
|
function aggregationDomain(scale2, domainInput) {
|
|
const { domain, sortMetadata } = domainInput;
|
|
switch (scale2) {
|
|
case "category":
|
|
return [Number.NaN, Number.NaN];
|
|
case "number":
|
|
case "time":
|
|
case "ordinal-time":
|
|
case "unit-time": {
|
|
if (domain.length === 0)
|
|
return [Infinity, -Infinity];
|
|
if (sortMetadata?.sortOrder === 1) {
|
|
return [Number(domain[0]), Number(domain.at(-1))];
|
|
}
|
|
if (sortMetadata?.sortOrder === -1) {
|
|
return [Number(domain.at(-1)), Number(domain[0])];
|
|
}
|
|
let min = Infinity;
|
|
let max = -Infinity;
|
|
for (const d of domain) {
|
|
const value = Number(d);
|
|
min = Math.min(min, value);
|
|
max = Math.max(max, value);
|
|
}
|
|
return [min, max];
|
|
}
|
|
case "color":
|
|
case "log":
|
|
case "mercator":
|
|
return [0, 0];
|
|
}
|
|
}
|
|
function aggregationXRatioForDatumIndex(datumIndex, domainCount) {
|
|
return datumIndex / domainCount;
|
|
}
|
|
function aggregationXRatioForXValue(xValue, d0, d1, xNeedsValueOf) {
|
|
if (xNeedsValueOf) {
|
|
return (xValue.valueOf() - d0) / (d1 - d0);
|
|
}
|
|
return (xValue - d0) / (d1 - d0);
|
|
}
|
|
function aggregationIndexForXRatio(xRatio, maxRange) {
|
|
return Math.trunc(Math.min(Math.floor(xRatio * maxRange), maxRange - 1) * AGGREGATION_SPAN);
|
|
}
|
|
function aggregationBucketForDatum(xValues, d0, d1, maxRange, datumIndex, { xNeedsValueOf = true, xValuesLength } = {}) {
|
|
const xValue = xValues[datumIndex];
|
|
if (xValue == null)
|
|
return -1;
|
|
const length2 = xValuesLength ?? xValues.length;
|
|
const xRatio = Number.isFinite(d0) ? aggregationXRatioForXValue(xValue, d0, d1, xNeedsValueOf) : aggregationXRatioForDatumIndex(datumIndex, length2);
|
|
return aggregationIndexForXRatio(xRatio, maxRange);
|
|
}
|
|
function aggregationDatumMatchesIndex(indexData, aggIndex, datumIndex, offsets) {
|
|
for (const offset of offsets) {
|
|
if (datumIndex === indexData[aggIndex + offset]) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function createAggregationIndices(xValues, yMaxValues, yMinValues, d0, d1, maxRange, {
|
|
positive,
|
|
split = false,
|
|
xNeedsValueOf = true,
|
|
yNeedsValueOf = true,
|
|
// Optional pre-allocated arrays to reuse (must be correct size: maxRange * AGGREGATION_SPAN)
|
|
reuseIndexData,
|
|
reuseValueData,
|
|
reuseNegativeIndexData,
|
|
reuseNegativeValueData
|
|
} = {}) {
|
|
const nan = Number.NaN;
|
|
const requiredSize = maxRange * AGGREGATION_SPAN;
|
|
const indexData = reuseIndexData?.length === requiredSize ? reuseIndexData : new Uint32Array(requiredSize);
|
|
const valueData = reuseValueData?.length === requiredSize ? reuseValueData : new Float64Array(requiredSize);
|
|
let negativeIndexData;
|
|
let negativeValueData;
|
|
if (split) {
|
|
if (reuseNegativeIndexData?.length === requiredSize) {
|
|
negativeIndexData = reuseNegativeIndexData;
|
|
} else {
|
|
negativeIndexData = new Uint32Array(requiredSize);
|
|
}
|
|
if (reuseNegativeValueData?.length === requiredSize) {
|
|
negativeValueData = reuseNegativeValueData;
|
|
} else {
|
|
negativeValueData = new Float64Array(requiredSize);
|
|
}
|
|
}
|
|
const continuous = Number.isFinite(d0) && Number.isFinite(d1);
|
|
const domainCount = xValues.length;
|
|
if (continuous) {
|
|
valueData.fill(nan);
|
|
indexData.fill(AGGREGATION_INDEX_UNSET);
|
|
if (split) {
|
|
negativeValueData.fill(nan);
|
|
negativeIndexData.fill(AGGREGATION_INDEX_UNSET);
|
|
}
|
|
}
|
|
const scaleFactor = continuous ? maxRange / (d1 - d0) : maxRange * (1 / domainCount);
|
|
let lastAggIndex = -1;
|
|
let cachedXMinIndex = -1;
|
|
let cachedXMinValue = nan;
|
|
let cachedXMaxIndex = -1;
|
|
let cachedXMaxValue = nan;
|
|
let cachedYMinIndex = -1;
|
|
let cachedYMinValue = nan;
|
|
let cachedYMaxIndex = -1;
|
|
let cachedYMaxValue = nan;
|
|
let negLastAggIndex = -1;
|
|
let negCachedXMinIndex = -1;
|
|
let negCachedXMinValue = nan;
|
|
let negCachedXMaxIndex = -1;
|
|
let negCachedXMaxValue = nan;
|
|
let negCachedYMinIndex = -1;
|
|
let negCachedYMinValue = nan;
|
|
let negCachedYMaxIndex = -1;
|
|
let negCachedYMaxValue = nan;
|
|
const xValuesLength = xValues.length;
|
|
const yArraysSame = yMaxValues === yMinValues;
|
|
for (let datumIndex = 0; datumIndex < xValuesLength; datumIndex++) {
|
|
const xValue = xValues[datumIndex];
|
|
if (xValue == null)
|
|
continue;
|
|
const yMaxValue = yMaxValues[datumIndex];
|
|
const yMinValue = yArraysSame ? yMaxValue : yMinValues[datumIndex];
|
|
let yMax;
|
|
let yMin;
|
|
if (yNeedsValueOf) {
|
|
yMax = yMaxValue == null ? nan : yMaxValue.valueOf();
|
|
yMin = yMinValue == null ? nan : yMinValue.valueOf();
|
|
} else {
|
|
yMax = yMaxValue ?? nan;
|
|
yMin = yMinValue ?? nan;
|
|
}
|
|
let isPositiveDatum = true;
|
|
if (split) {
|
|
isPositiveDatum = yMax >= 0;
|
|
} else if (positive != null && yMax >= 0 !== positive) {
|
|
continue;
|
|
}
|
|
let scaledX;
|
|
if (continuous) {
|
|
if (xNeedsValueOf) {
|
|
scaledX = (xValue.valueOf() - d0) * scaleFactor;
|
|
} else {
|
|
scaledX = (xValue - d0) * scaleFactor;
|
|
}
|
|
} else {
|
|
scaledX = datumIndex * scaleFactor;
|
|
}
|
|
const bucketIndex = Math.floor(scaledX);
|
|
const aggIndex = (bucketIndex < maxRange ? bucketIndex : maxRange - 1) << 2;
|
|
if (isPositiveDatum) {
|
|
if (aggIndex !== lastAggIndex) {
|
|
if (lastAggIndex !== -1) {
|
|
indexData[lastAggIndex] = cachedXMinIndex;
|
|
indexData[lastAggIndex + 1] = cachedXMaxIndex;
|
|
indexData[lastAggIndex + 2] = cachedYMinIndex;
|
|
indexData[lastAggIndex + 3] = cachedYMaxIndex;
|
|
valueData[lastAggIndex] = cachedXMinValue;
|
|
valueData[lastAggIndex + 1] = cachedXMaxValue;
|
|
valueData[lastAggIndex + 2] = cachedYMinValue;
|
|
valueData[lastAggIndex + 3] = cachedYMaxValue;
|
|
}
|
|
lastAggIndex = aggIndex;
|
|
cachedXMinIndex = -1;
|
|
cachedXMinValue = nan;
|
|
cachedXMaxIndex = -1;
|
|
cachedXMaxValue = nan;
|
|
cachedYMinIndex = -1;
|
|
cachedYMinValue = nan;
|
|
cachedYMaxIndex = -1;
|
|
cachedYMaxValue = nan;
|
|
}
|
|
const yMinValid = yMin === yMin;
|
|
const yMaxValid = yMax === yMax;
|
|
if (cachedXMinIndex === -1) {
|
|
cachedXMinIndex = datumIndex;
|
|
cachedXMinValue = scaledX;
|
|
cachedXMaxIndex = datumIndex;
|
|
cachedXMaxValue = scaledX;
|
|
if (yMinValid) {
|
|
cachedYMinIndex = datumIndex;
|
|
cachedYMinValue = yMin;
|
|
}
|
|
if (yMaxValid) {
|
|
cachedYMaxIndex = datumIndex;
|
|
cachedYMaxValue = yMax;
|
|
}
|
|
} else {
|
|
if (scaledX < cachedXMinValue) {
|
|
cachedXMinIndex = datumIndex;
|
|
cachedXMinValue = scaledX;
|
|
}
|
|
if (scaledX > cachedXMaxValue) {
|
|
cachedXMaxIndex = datumIndex;
|
|
cachedXMaxValue = scaledX;
|
|
}
|
|
if (yMinValid && yMin < cachedYMinValue) {
|
|
cachedYMinIndex = datumIndex;
|
|
cachedYMinValue = yMin;
|
|
}
|
|
if (yMaxValid && yMax > cachedYMaxValue) {
|
|
cachedYMaxIndex = datumIndex;
|
|
cachedYMaxValue = yMax;
|
|
}
|
|
}
|
|
} else {
|
|
if (aggIndex !== negLastAggIndex) {
|
|
if (negLastAggIndex !== -1) {
|
|
negativeIndexData[negLastAggIndex] = negCachedXMinIndex;
|
|
negativeIndexData[negLastAggIndex + 1] = negCachedXMaxIndex;
|
|
negativeIndexData[negLastAggIndex + 2] = negCachedYMinIndex;
|
|
negativeIndexData[negLastAggIndex + 3] = negCachedYMaxIndex;
|
|
negativeValueData[negLastAggIndex] = negCachedXMinValue;
|
|
negativeValueData[negLastAggIndex + 1] = negCachedXMaxValue;
|
|
negativeValueData[negLastAggIndex + 2] = negCachedYMinValue;
|
|
negativeValueData[negLastAggIndex + 3] = negCachedYMaxValue;
|
|
}
|
|
negLastAggIndex = aggIndex;
|
|
negCachedXMinIndex = -1;
|
|
negCachedXMinValue = nan;
|
|
negCachedXMaxIndex = -1;
|
|
negCachedXMaxValue = nan;
|
|
negCachedYMinIndex = -1;
|
|
negCachedYMinValue = nan;
|
|
negCachedYMaxIndex = -1;
|
|
negCachedYMaxValue = nan;
|
|
}
|
|
const yMinValid = yMin === yMin;
|
|
const yMaxValid = yMax === yMax;
|
|
if (negCachedXMinIndex === -1) {
|
|
negCachedXMinIndex = datumIndex;
|
|
negCachedXMinValue = scaledX;
|
|
negCachedXMaxIndex = datumIndex;
|
|
negCachedXMaxValue = scaledX;
|
|
if (yMinValid) {
|
|
negCachedYMinIndex = datumIndex;
|
|
negCachedYMinValue = yMin;
|
|
}
|
|
if (yMaxValid) {
|
|
negCachedYMaxIndex = datumIndex;
|
|
negCachedYMaxValue = yMax;
|
|
}
|
|
} else {
|
|
if (scaledX < negCachedXMinValue) {
|
|
negCachedXMinIndex = datumIndex;
|
|
negCachedXMinValue = scaledX;
|
|
}
|
|
if (scaledX > negCachedXMaxValue) {
|
|
negCachedXMaxIndex = datumIndex;
|
|
negCachedXMaxValue = scaledX;
|
|
}
|
|
if (yMinValid && yMin < negCachedYMinValue) {
|
|
negCachedYMinIndex = datumIndex;
|
|
negCachedYMinValue = yMin;
|
|
}
|
|
if (yMaxValid && yMax > negCachedYMaxValue) {
|
|
negCachedYMaxIndex = datumIndex;
|
|
negCachedYMaxValue = yMax;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (lastAggIndex !== -1) {
|
|
indexData[lastAggIndex] = cachedXMinIndex;
|
|
indexData[lastAggIndex + 1] = cachedXMaxIndex;
|
|
indexData[lastAggIndex + 2] = cachedYMinIndex;
|
|
indexData[lastAggIndex + 3] = cachedYMaxIndex;
|
|
valueData[lastAggIndex] = cachedXMinValue;
|
|
valueData[lastAggIndex + 1] = cachedXMaxValue;
|
|
valueData[lastAggIndex + 2] = cachedYMinValue;
|
|
valueData[lastAggIndex + 3] = cachedYMaxValue;
|
|
}
|
|
if (split && negLastAggIndex !== -1) {
|
|
negativeIndexData[negLastAggIndex] = negCachedXMinIndex;
|
|
negativeIndexData[negLastAggIndex + 1] = negCachedXMaxIndex;
|
|
negativeIndexData[negLastAggIndex + 2] = negCachedYMinIndex;
|
|
negativeIndexData[negLastAggIndex + 3] = negCachedYMaxIndex;
|
|
negativeValueData[negLastAggIndex] = negCachedXMinValue;
|
|
negativeValueData[negLastAggIndex + 1] = negCachedXMaxValue;
|
|
negativeValueData[negLastAggIndex + 2] = negCachedYMinValue;
|
|
negativeValueData[negLastAggIndex + 3] = negCachedYMaxValue;
|
|
}
|
|
return { indexData, valueData, negativeIndexData, negativeValueData };
|
|
}
|
|
function compactAggregationIndices(indexData, valueData, maxRange, {
|
|
inPlace = false,
|
|
midpointData,
|
|
reuseIndexData,
|
|
reuseValueData
|
|
} = {}) {
|
|
const nextMaxRange = Math.trunc(maxRange / 2);
|
|
const requiredSize = nextMaxRange * AGGREGATION_SPAN;
|
|
let nextIndexData;
|
|
if (inPlace) {
|
|
nextIndexData = indexData;
|
|
} else if (reuseIndexData?.length === requiredSize) {
|
|
nextIndexData = reuseIndexData;
|
|
} else {
|
|
nextIndexData = new Uint32Array(requiredSize);
|
|
}
|
|
let nextValueData;
|
|
if (inPlace) {
|
|
nextValueData = valueData;
|
|
} else if (reuseValueData?.length === requiredSize) {
|
|
nextValueData = reuseValueData;
|
|
} else {
|
|
nextValueData = new Float64Array(requiredSize);
|
|
}
|
|
const nextMidpointData = midpointData ?? new Uint32Array(nextMaxRange);
|
|
for (let i = 0; i < nextMaxRange; i += 1) {
|
|
const aggIndex = Math.trunc(i * AGGREGATION_SPAN);
|
|
const index0 = Math.trunc(aggIndex * 2);
|
|
const index1 = Math.trunc(index0 + AGGREGATION_SPAN);
|
|
const index1Unset = indexData[index1 + AGGREGATION_INDEX_X_MIN] === AGGREGATION_INDEX_UNSET;
|
|
const xMinAggIndex = index1Unset || valueData[index0 + AGGREGATION_INDEX_X_MIN] < valueData[index1 + AGGREGATION_INDEX_X_MIN] ? index0 : index1;
|
|
const xMinIndex = indexData[xMinAggIndex + AGGREGATION_INDEX_X_MIN];
|
|
nextIndexData[aggIndex + AGGREGATION_INDEX_X_MIN] = xMinIndex;
|
|
nextValueData[aggIndex + AGGREGATION_INDEX_X_MIN] = valueData[xMinAggIndex + AGGREGATION_INDEX_X_MIN];
|
|
const xMaxAggIndex = index1Unset || valueData[index0 + AGGREGATION_INDEX_X_MAX] > valueData[index1 + AGGREGATION_INDEX_X_MAX] ? index0 : index1;
|
|
const xMaxIndex = indexData[xMaxAggIndex + AGGREGATION_INDEX_X_MAX];
|
|
nextIndexData[aggIndex + AGGREGATION_INDEX_X_MAX] = xMaxIndex;
|
|
nextValueData[aggIndex + AGGREGATION_INDEX_X_MAX] = valueData[xMaxAggIndex + AGGREGATION_INDEX_X_MAX];
|
|
nextMidpointData[i] = xMinIndex + xMaxIndex >> 1;
|
|
const yMinAggIndex = index1Unset || valueData[index0 + AGGREGATION_INDEX_Y_MIN] < valueData[index1 + AGGREGATION_INDEX_Y_MIN] ? index0 : index1;
|
|
nextIndexData[aggIndex + AGGREGATION_INDEX_Y_MIN] = indexData[yMinAggIndex + AGGREGATION_INDEX_Y_MIN];
|
|
nextValueData[aggIndex + AGGREGATION_INDEX_Y_MIN] = valueData[yMinAggIndex + AGGREGATION_INDEX_Y_MIN];
|
|
const yMaxAggIndex = index1Unset || valueData[index0 + AGGREGATION_INDEX_Y_MAX] > valueData[index1 + AGGREGATION_INDEX_Y_MAX] ? index0 : index1;
|
|
nextIndexData[aggIndex + AGGREGATION_INDEX_Y_MAX] = indexData[yMaxAggIndex + AGGREGATION_INDEX_Y_MAX];
|
|
nextValueData[aggIndex + AGGREGATION_INDEX_Y_MAX] = valueData[yMaxAggIndex + AGGREGATION_INDEX_Y_MAX];
|
|
}
|
|
return {
|
|
maxRange: nextMaxRange,
|
|
indexData: nextIndexData,
|
|
valueData: nextValueData,
|
|
midpointData: nextMidpointData
|
|
};
|
|
}
|
|
function getMidpointsForIndices(maxRange, indexData, reuseMidpointData, xMinOffset = AGGREGATION_INDEX_X_MIN, xMaxOffset = AGGREGATION_INDEX_X_MAX, invalidSentinel = -1) {
|
|
const midpoints = reuseMidpointData?.length === maxRange ? reuseMidpointData : new Uint32Array(maxRange);
|
|
for (let i = 0, offset = 0; i < maxRange; i += 1, offset += AGGREGATION_SPAN) {
|
|
const xMin = indexData[offset + xMinOffset];
|
|
const xMax = indexData[offset + xMaxOffset];
|
|
midpoints[i] = xMin === invalidSentinel ? invalidSentinel : xMin + xMax >> 1;
|
|
}
|
|
return midpoints;
|
|
}
|
|
function collectAggregationLevels(state, {
|
|
collectLevel,
|
|
shouldContinue,
|
|
minRange = AGGREGATION_MIN_RANGE,
|
|
compactInPlace = false
|
|
}) {
|
|
let aggregationState = state;
|
|
let level = collectLevel(aggregationState);
|
|
const levels = [level];
|
|
while (aggregationState.maxRange > minRange && shouldContinue(level, aggregationState)) {
|
|
const compacted = compactAggregationIndices(
|
|
aggregationState.indexData,
|
|
aggregationState.valueData,
|
|
aggregationState.maxRange,
|
|
{ inPlace: compactInPlace }
|
|
);
|
|
aggregationState = {
|
|
maxRange: compacted.maxRange,
|
|
indexData: compacted.indexData,
|
|
valueData: compacted.valueData,
|
|
midpointData: compacted.midpointData
|
|
};
|
|
level = collectLevel(aggregationState);
|
|
levels.push(level);
|
|
}
|
|
levels.reverse();
|
|
return levels;
|
|
}
|
|
function computeExtremesAggregation(domain, xValues, highValues, lowValues, options) {
|
|
if (xValues.length < AGGREGATION_THRESHOLD)
|
|
return;
|
|
const [d0, d1] = domain;
|
|
const { smallestKeyInterval, xNeedsValueOf, yNeedsValueOf, existingFilters } = options;
|
|
let maxRange = aggregationRangeFittingPoints(xValues, d0, d1, { smallestKeyInterval, xNeedsValueOf });
|
|
const existingFilter = existingFilters?.find((f) => f.maxRange === maxRange);
|
|
let { indexData, valueData } = createAggregationIndices(xValues, highValues, lowValues, d0, d1, maxRange, {
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
reuseIndexData: existingFilter?.indexData,
|
|
reuseValueData: existingFilter?.valueData
|
|
});
|
|
let midpointIndices = getMidpointsForIndices(maxRange, indexData, existingFilter?.midpointIndices);
|
|
const filters = [
|
|
{
|
|
maxRange,
|
|
indexData,
|
|
valueData,
|
|
midpointIndices
|
|
}
|
|
];
|
|
while (maxRange > AGGREGATION_MIN_RANGE) {
|
|
const currentMaxRange = maxRange;
|
|
const nextMaxRange = Math.trunc(currentMaxRange / 2);
|
|
const nextExistingFilter = existingFilters?.find((f) => f.maxRange === nextMaxRange);
|
|
const compacted = compactAggregationIndices(indexData, valueData, currentMaxRange, {
|
|
reuseIndexData: nextExistingFilter?.indexData,
|
|
reuseValueData: nextExistingFilter?.valueData
|
|
});
|
|
maxRange = compacted.maxRange;
|
|
indexData = compacted.indexData;
|
|
valueData = compacted.valueData;
|
|
midpointIndices = compacted.midpointData ?? getMidpointsForIndices(maxRange, indexData, nextExistingFilter?.midpointIndices);
|
|
filters.push({
|
|
maxRange,
|
|
indexData,
|
|
valueData,
|
|
midpointIndices
|
|
});
|
|
}
|
|
filters.reverse();
|
|
return filters;
|
|
}
|
|
function computeExtremesAggregationPartial(domain, xValues, highValues, lowValues, options) {
|
|
if (xValues.length < AGGREGATION_THRESHOLD)
|
|
return;
|
|
const [d0, d1] = domain;
|
|
const { smallestKeyInterval, targetRange, xNeedsValueOf, yNeedsValueOf, existingFilters } = options;
|
|
const finestMaxRange = aggregationRangeFittingPoints(xValues, d0, d1, { smallestKeyInterval, xNeedsValueOf });
|
|
const targetMaxRange = Math.min(finestMaxRange, nextPowerOf2(Math.max(targetRange, AGGREGATION_MIN_RANGE)));
|
|
const existingFilter = existingFilters?.find((f) => f.maxRange === targetMaxRange);
|
|
const { indexData, valueData } = createAggregationIndices(xValues, highValues, lowValues, d0, d1, targetMaxRange, {
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
reuseIndexData: existingFilter?.indexData,
|
|
reuseValueData: existingFilter?.valueData
|
|
});
|
|
const midpointIndices = getMidpointsForIndices(targetMaxRange, indexData, existingFilter?.midpointIndices);
|
|
const immediateLevel = {
|
|
maxRange: targetMaxRange,
|
|
indexData,
|
|
valueData,
|
|
midpointIndices
|
|
};
|
|
function computeRemaining() {
|
|
const allLevels = computeExtremesAggregation([d0, d1], xValues, highValues, lowValues, {
|
|
smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
existingFilters
|
|
});
|
|
return allLevels?.filter((level) => level.maxRange !== targetMaxRange) ?? [];
|
|
}
|
|
return { immediate: [immediateLevel], computeRemaining };
|
|
}
|
|
|
|
// packages/ag-charts-core/src/types/themeSymbols.ts
|
|
var IS_DARK_THEME = Symbol("is-dark-theme");
|
|
var DEFAULT_SHADOW_COLOUR = Symbol("default-shadow-colour");
|
|
var DEFAULT_CAPTION_LAYOUT_STYLE = Symbol("default-caption-layout-style");
|
|
var DEFAULT_CAPTION_ALIGNMENT = Symbol("default-caption-alignment");
|
|
var PALETTE_UP_STROKE = Symbol("palette-up-stroke");
|
|
var PALETTE_DOWN_STROKE = Symbol("palette-down-stroke");
|
|
var PALETTE_UP_FILL = Symbol("palette-up-fill");
|
|
var PALETTE_DOWN_FILL = Symbol("palette-down-fill");
|
|
var PALETTE_NEUTRAL_STROKE = Symbol("palette-neutral-stroke");
|
|
var PALETTE_NEUTRAL_FILL = Symbol("palette-neutral-fill");
|
|
var PALETTE_ALT_UP_STROKE = Symbol("palette-alt-up-stroke");
|
|
var PALETTE_ALT_DOWN_STROKE = Symbol("palette-alt-down-stroke");
|
|
var PALETTE_ALT_UP_FILL = Symbol("palette-alt-up-fill");
|
|
var PALETTE_ALT_DOWN_FILL = Symbol("palette-alt-down-fill");
|
|
var PALETTE_ALT_NEUTRAL_FILL = Symbol("palette-gray-fill");
|
|
var PALETTE_ALT_NEUTRAL_STROKE = Symbol("palette-gray-stroke");
|
|
var DEFAULT_POLAR_SERIES_STROKE = Symbol("default-polar-series-stroke");
|
|
var DEFAULT_SPARKLINE_CROSSHAIR_STROKE = Symbol("default-sparkline-crosshair-stroke");
|
|
var DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR = Symbol(
|
|
"default-financial-charts-annotation-stroke"
|
|
);
|
|
var DEFAULT_FIBONACCI_STROKES = Symbol("default-hierarchy-strokes");
|
|
var DEFAULT_TEXT_ANNOTATION_COLOR = Symbol("default-text-annotation-color");
|
|
var DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL = Symbol(
|
|
"default-financial-charts-annotation-background-fill"
|
|
);
|
|
var DEFAULT_ANNOTATION_HANDLE_FILL = Symbol("default-annotation-handle-fill");
|
|
var DEFAULT_ANNOTATION_STATISTICS_FILL = Symbol("default-annotation-statistics-fill");
|
|
var DEFAULT_ANNOTATION_STATISTICS_STROKE = Symbol("default-annotation-statistics-stroke");
|
|
var DEFAULT_ANNOTATION_STATISTICS_COLOR = Symbol("default-annotation-statistics-color");
|
|
var DEFAULT_ANNOTATION_STATISTICS_DIVIDER_STROKE = Symbol(
|
|
"default-annotation-statistics-divider-stroke"
|
|
);
|
|
var DEFAULT_ANNOTATION_STATISTICS_DOWN_FILL = Symbol(
|
|
"default-annotation-statistics-fill"
|
|
);
|
|
var DEFAULT_ANNOTATION_STATISTICS_DOWN_STROKE = Symbol(
|
|
"default-annotation-statistics-stroke"
|
|
);
|
|
var DEFAULT_TEXTBOX_FILL = Symbol("default-textbox-fill");
|
|
var DEFAULT_TEXTBOX_STROKE = Symbol("default-textbox-stroke");
|
|
var DEFAULT_TEXTBOX_COLOR = Symbol("default-textbox-color");
|
|
var DEFAULT_TOOLBAR_POSITION = Symbol("default-toolbar-position");
|
|
|
|
// packages/ag-charts-core/src/state/callbackCache.ts
|
|
function needsContext(caller, _params) {
|
|
return "context" in caller;
|
|
}
|
|
function maybeSetContext(caller, params) {
|
|
if (caller != null && needsContext(caller, params)) {
|
|
if (params != null && typeof params === "object" && params.context === void 0) {
|
|
params.context = caller.context;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function callWithContext(callers, fn, params) {
|
|
if (Array.isArray(callers)) {
|
|
for (const caller of callers) {
|
|
if (maybeSetContext(caller, params)) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
maybeSetContext(callers, params);
|
|
}
|
|
return fn(params);
|
|
}
|
|
var CallbackCache = class {
|
|
constructor() {
|
|
this.cache = /* @__PURE__ */ new WeakMap();
|
|
}
|
|
call(callers, fn, params) {
|
|
let serialisedParams;
|
|
let paramCache = this.cache.get(fn);
|
|
try {
|
|
serialisedParams = JSON.stringify(params);
|
|
} catch {
|
|
return this.invoke(callers, fn, paramCache, void 0, params);
|
|
}
|
|
if (paramCache == null) {
|
|
paramCache = /* @__PURE__ */ new Map();
|
|
this.cache.set(fn, paramCache);
|
|
}
|
|
if (!paramCache.has(serialisedParams)) {
|
|
return this.invoke(callers, fn, paramCache, serialisedParams, params);
|
|
}
|
|
return paramCache.get(serialisedParams);
|
|
}
|
|
invoke(callers, fn, paramCache, serialisedParams, params) {
|
|
try {
|
|
const result = callWithContext(callers, fn, params);
|
|
if (paramCache && serialisedParams != null) {
|
|
paramCache.set(serialisedParams, result);
|
|
}
|
|
return result;
|
|
} catch (e) {
|
|
warnOnce(`User callback errored, ignoring`, e);
|
|
return;
|
|
}
|
|
}
|
|
invalidateCache() {
|
|
this.cache = /* @__PURE__ */ new WeakMap();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-core/src/utils/dom/domUtil.ts
|
|
function setElementBBox(element2, bbox) {
|
|
if (!element2)
|
|
return;
|
|
const { x, y, width: width2, height: height2 } = normalizeBounds(bbox);
|
|
setPixelValue(element2.style, "width", width2);
|
|
setPixelValue(element2.style, "height", height2);
|
|
setPixelValue(element2.style, "left", x);
|
|
setPixelValue(element2.style, "top", y);
|
|
}
|
|
function getElementBBox(element2) {
|
|
const styleWidth = Number.parseFloat(element2.style.width);
|
|
const styleHeight = Number.parseFloat(element2.style.height);
|
|
const styleX = Number.parseFloat(element2.style.left);
|
|
const styleY = Number.parseFloat(element2.style.top);
|
|
const width2 = Number.isFinite(styleWidth) ? styleWidth : element2.offsetWidth;
|
|
const height2 = Number.isFinite(styleHeight) ? styleHeight : element2.offsetHeight;
|
|
const x = Number.isFinite(styleX) ? styleX : element2.offsetLeft;
|
|
const y = Number.isFinite(styleY) ? styleY : element2.offsetTop;
|
|
return { x, y, width: width2, height: height2 };
|
|
}
|
|
function focusCursorAtEnd(element2) {
|
|
element2.focus({ preventScroll: true });
|
|
if (element2.lastChild?.textContent == null)
|
|
return;
|
|
const range3 = getDocument().createRange();
|
|
range3.setStart(element2.lastChild, element2.lastChild.textContent.length);
|
|
range3.setEnd(element2.lastChild, element2.lastChild.textContent.length);
|
|
const selection = getWindow().getSelection();
|
|
selection?.removeAllRanges();
|
|
selection?.addRange(range3);
|
|
}
|
|
function isInputPending() {
|
|
const navigator = getWindow("navigator");
|
|
if ("scheduling" in navigator) {
|
|
const scheduling = navigator.scheduling;
|
|
if ("isInputPending" in scheduling) {
|
|
return scheduling.isInputPending({ includeContinuous: true });
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function getIconClassNames(icon) {
|
|
return `ag-charts-icon ag-charts-icon-${icon}`;
|
|
}
|
|
function normalizeBounds(bbox) {
|
|
let { x, y, width: width2, height: height2 } = bbox;
|
|
if ((width2 == null || width2 > 0) && (height2 == null || height2 > 0)) {
|
|
return bbox;
|
|
}
|
|
if (x != null && width2 != null && width2 < 0) {
|
|
width2 = -width2;
|
|
x = x - width2;
|
|
}
|
|
if (y != null && height2 != null && height2 < 0) {
|
|
height2 = -height2;
|
|
y = y - height2;
|
|
}
|
|
return { x, y, width: width2, height: height2 };
|
|
}
|
|
function setPixelValue(style2, key, value) {
|
|
if (value == null) {
|
|
style2.removeProperty(key);
|
|
} else {
|
|
style2.setProperty(key, `${value}px`);
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/math/shapeUtils.ts
|
|
function getMaxInnerRectSize(rotationDeg, containerWidth, containerHeight = Infinity) {
|
|
const W = containerWidth;
|
|
const H = containerHeight;
|
|
const angle2 = rotationDeg % 180 * (Math.PI / 180);
|
|
const sin = Math.abs(Math.sin(angle2));
|
|
const cos = Math.abs(Math.cos(angle2));
|
|
if (sin === 0)
|
|
return { width: W, height: H };
|
|
if (cos === 0)
|
|
return { width: H, height: W };
|
|
if (!Number.isFinite(H)) {
|
|
const r = cos / sin;
|
|
const width2 = W / (cos + r * sin);
|
|
return { width: width2, height: r * width2 };
|
|
}
|
|
const denominator = cos * cos - sin * sin;
|
|
if (denominator === 0) {
|
|
const side = Math.min(W, H) / Math.SQRT2;
|
|
return { width: side, height: side };
|
|
}
|
|
return {
|
|
width: Math.abs((W * cos - H * sin) / denominator),
|
|
height: Math.abs((H * cos - W * sin) / denominator)
|
|
};
|
|
}
|
|
function getMinOuterRectSize(rotationDeg, innerWidth, innerHeight = Infinity) {
|
|
const w = innerWidth;
|
|
const h = innerHeight;
|
|
const angle2 = rotationDeg % 180 * (Math.PI / 180);
|
|
const sin = Math.abs(Math.sin(angle2));
|
|
const cos = Math.abs(Math.cos(angle2));
|
|
if (sin === 0)
|
|
return { width: w, height: h };
|
|
if (cos === 0)
|
|
return { width: h, height: w };
|
|
return {
|
|
width: w * cos + h * sin,
|
|
height: w * sin + h * cos
|
|
};
|
|
}
|
|
function rotatePoint(x, y, angle2, originX = 0, originY = 0) {
|
|
const cos = Math.cos(angle2);
|
|
const sin = Math.sin(angle2);
|
|
const dx2 = x - originX;
|
|
const dy2 = y - originY;
|
|
return {
|
|
x: originX + dx2 * cos - dy2 * sin,
|
|
y: originY + dx2 * sin + dy2 * cos
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/angle.ts
|
|
var twoPi = Math.PI * 2;
|
|
var halfPi = Math.PI / 2;
|
|
function normalizeAngle360(radians) {
|
|
radians %= twoPi;
|
|
radians += twoPi;
|
|
radians %= twoPi;
|
|
return radians;
|
|
}
|
|
function normalizeAngle360Inclusive(radians) {
|
|
radians %= twoPi;
|
|
radians += twoPi;
|
|
if (radians !== twoPi) {
|
|
radians %= twoPi;
|
|
}
|
|
return radians;
|
|
}
|
|
function normalizeAngle180(radians) {
|
|
radians %= twoPi;
|
|
if (radians < -Math.PI) {
|
|
radians += twoPi;
|
|
} else if (radians >= Math.PI) {
|
|
radians -= twoPi;
|
|
}
|
|
return radians;
|
|
}
|
|
function isBetweenAngles(targetAngle, startAngle, endAngle) {
|
|
const t = normalizeAngle360(targetAngle);
|
|
const a0 = normalizeAngle360(startAngle);
|
|
const a1 = normalizeAngle360(endAngle);
|
|
if (a0 < a1) {
|
|
return a0 <= t && t <= a1;
|
|
} else if (a0 > a1) {
|
|
return a0 <= t || t <= a1;
|
|
} else {
|
|
return startAngle !== endAngle;
|
|
}
|
|
}
|
|
function toRadians(degrees) {
|
|
return degrees / 180 * Math.PI;
|
|
}
|
|
function toDegrees(radians) {
|
|
return radians / Math.PI * 180;
|
|
}
|
|
function angleBetween(angle0, angle1) {
|
|
angle0 = normalizeAngle360(angle0);
|
|
angle1 = normalizeAngle360(angle1);
|
|
return angle1 - angle0 + (angle0 > angle1 ? twoPi : 0);
|
|
}
|
|
function getAngleRatioRadians(angle2) {
|
|
const normalizedAngle = normalizeAngle360(angle2);
|
|
if (normalizedAngle <= halfPi) {
|
|
return normalizedAngle / halfPi;
|
|
} else if (normalizedAngle <= Math.PI) {
|
|
return (Math.PI - normalizedAngle) / halfPi;
|
|
} else if (normalizedAngle <= 1.5 * Math.PI) {
|
|
return (normalizedAngle - Math.PI) / halfPi;
|
|
} else {
|
|
return (twoPi - normalizedAngle) / halfPi;
|
|
}
|
|
}
|
|
function angularPadding(hPadding, vPadding, angle2) {
|
|
const angleRatio = getAngleRatioRadians(angle2);
|
|
return hPadding * angleRatio + vPadding * Math.abs(1 - angleRatio);
|
|
}
|
|
function normalizeAngle360FromDegrees(degrees) {
|
|
return degrees ? normalizeAngle360(toRadians(degrees)) : 0;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/async.ts
|
|
var AsyncAwaitQueue = class {
|
|
constructor() {
|
|
this.queue = [];
|
|
}
|
|
/** Await another async process to call notify(). */
|
|
waitForCompletion(timeout = 50) {
|
|
const queue = this.queue;
|
|
function createCompletionPromise(resolve) {
|
|
function successFn() {
|
|
clearTimeout(timeoutHandle);
|
|
resolve(true);
|
|
}
|
|
function timeoutFn() {
|
|
const queueIndex = queue.indexOf(successFn);
|
|
if (queueIndex < 0)
|
|
return;
|
|
queue.splice(queueIndex, 1);
|
|
resolve(false);
|
|
}
|
|
const timeoutHandle = setTimeout(timeoutFn, timeout);
|
|
queue.push(successFn);
|
|
}
|
|
return new Promise(createCompletionPromise);
|
|
}
|
|
/** Trigger any await()ing async processes to continue. */
|
|
notify() {
|
|
for (const cb of this.queue.splice(0)) {
|
|
cb();
|
|
}
|
|
}
|
|
};
|
|
function pause(delayMilliseconds = 0) {
|
|
function resolveAfterDelay(resolve) {
|
|
setTimeout(resolve, delayMilliseconds);
|
|
}
|
|
return new Promise(resolveAfterDelay);
|
|
}
|
|
async function withTimeout(promise, timeoutMs, errorMessage = `Timeout after ${timeoutMs}ms`) {
|
|
let timer;
|
|
const timeoutPromise = new Promise((_, reject) => {
|
|
timer = setTimeout(() => reject(new Error(errorMessage)), timeoutMs);
|
|
});
|
|
try {
|
|
return await Promise.race([promise, timeoutPromise]);
|
|
} finally {
|
|
clearTimeout(timer);
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/dom/attributeUtil.ts
|
|
function booleanParser(value) {
|
|
return value === "true";
|
|
}
|
|
function numberParser(value) {
|
|
return Number(value);
|
|
}
|
|
function stringParser(value) {
|
|
return value;
|
|
}
|
|
var AttributeTypeParsers = {
|
|
role: stringParser,
|
|
"aria-checked": booleanParser,
|
|
"aria-controls": stringParser,
|
|
"aria-describedby": stringParser,
|
|
"aria-disabled": booleanParser,
|
|
"aria-expanded": booleanParser,
|
|
"aria-haspopup": stringParser,
|
|
"aria-hidden": booleanParser,
|
|
"aria-label": stringParser,
|
|
"aria-labelledby": stringParser,
|
|
"aria-live": stringParser,
|
|
"aria-orientation": stringParser,
|
|
"aria-selected": booleanParser,
|
|
"data-focus-override": booleanParser,
|
|
"data-focus-visible-override": booleanParser,
|
|
"data-preventdefault": booleanParser,
|
|
class: stringParser,
|
|
for: stringParser,
|
|
id: stringParser,
|
|
tabindex: numberParser,
|
|
title: stringParser,
|
|
placeholder: stringParser
|
|
};
|
|
function setAttribute(e, qualifiedName, value) {
|
|
if (value == null || value === "" || value === "") {
|
|
e?.removeAttribute(qualifiedName);
|
|
} else {
|
|
e?.setAttribute(qualifiedName, value.toString());
|
|
}
|
|
}
|
|
function setAttributes(e, attrs) {
|
|
if (attrs == null)
|
|
return;
|
|
for (const [key, value] of entries(attrs)) {
|
|
if (key === "class")
|
|
continue;
|
|
setAttribute(e, key, value);
|
|
}
|
|
}
|
|
function getAttribute(e, qualifiedName, defaultValue) {
|
|
if (!isHTMLElement(e))
|
|
return void 0;
|
|
const value = e.getAttribute(qualifiedName);
|
|
if (value === null)
|
|
return defaultValue;
|
|
return AttributeTypeParsers[qualifiedName]?.(value) ?? void 0;
|
|
}
|
|
function setElementStyle(e, property, value) {
|
|
if (e == null)
|
|
return;
|
|
if (value == null) {
|
|
e.style.removeProperty(property);
|
|
} else {
|
|
e.style.setProperty(property, value.toString());
|
|
}
|
|
}
|
|
function setElementStyles(e, styles) {
|
|
for (const [key, value] of entries(styles)) {
|
|
setElementStyle(e, key, value);
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-core/src/state/proxy.ts
|
|
function ProxyProperty(proxyPath, configMetadata) {
|
|
const pathArray = Array.isArray(proxyPath) ? proxyPath : proxyPath.split(".");
|
|
if (pathArray.length === 1) {
|
|
const [property] = pathArray;
|
|
return addTransformToInstanceProperty(
|
|
(target, _, value) => target[property] = value,
|
|
(target) => target[property],
|
|
configMetadata
|
|
);
|
|
}
|
|
return addTransformToInstanceProperty(
|
|
(target, _, value) => setPath(target, pathArray, value),
|
|
(target) => getPath(target, pathArray),
|
|
configMetadata
|
|
);
|
|
}
|
|
function ProxyOnWrite(proxyProperty) {
|
|
return addTransformToInstanceProperty((target, _, value) => target[proxyProperty] = value);
|
|
}
|
|
function ProxyPropertyOnWrite(childName, childProperty) {
|
|
return addTransformToInstanceProperty((target, key, value) => target[childName][childProperty ?? key] = value);
|
|
}
|
|
function ActionOnSet(opts) {
|
|
const { newValue: newValueFn, oldValue: oldValueFn, changeValue: changeValueFn } = opts;
|
|
return addTransformToInstanceProperty((target, _, newValue, oldValue) => {
|
|
if (newValue !== oldValue) {
|
|
if (oldValue !== void 0) {
|
|
oldValueFn?.call(target, oldValue);
|
|
}
|
|
if (newValue !== void 0) {
|
|
newValueFn?.call(target, newValue);
|
|
}
|
|
changeValueFn?.call(target, newValue, oldValue);
|
|
}
|
|
return newValue;
|
|
});
|
|
}
|
|
function ObserveChanges(observerFn) {
|
|
return addObserverToInstanceProperty(observerFn);
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/border.ts
|
|
var Border = class extends BaseProperties {
|
|
constructor(node) {
|
|
super();
|
|
this.node = node;
|
|
this.enabled = false;
|
|
this.stroke = "black";
|
|
this.strokeOpacity = 1;
|
|
this.strokeWidth = 1;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue(newValue) {
|
|
if (newValue) {
|
|
this.node.strokeWidth = this.strokeWidth;
|
|
} else {
|
|
this.node.strokeWidth = 0;
|
|
}
|
|
}
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], Border.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
ProxyPropertyOnWrite("node", "stroke"),
|
|
addFakeTransformToInstanceProperty
|
|
], Border.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
ProxyPropertyOnWrite("node", "strokeOpacity"),
|
|
addFakeTransformToInstanceProperty
|
|
], Border.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue(newValue) {
|
|
if (this.enabled) {
|
|
this.node.strokeWidth = newValue;
|
|
} else {
|
|
this.node.strokeWidth = 0;
|
|
}
|
|
}
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], Border.prototype, "strokeWidth", 2);
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/boxBounds.ts
|
|
function boxCollides(b, x, y, w, h) {
|
|
return x < b.x + b.width && x + w > b.x && y < b.y + b.height && y + h > b.y;
|
|
}
|
|
function boxContains(b, x, y, w = 0, h = 0) {
|
|
return x >= b.x && x + w <= b.x + b.width && y >= b.y && y + h <= b.y + b.height;
|
|
}
|
|
function boxEmpty(b) {
|
|
return b == null || b.height === 0 || b.width === 0 || Number.isNaN(b.height) || Number.isNaN(b.width);
|
|
}
|
|
function boxesEqual(a, b) {
|
|
if (a === b)
|
|
return true;
|
|
if (a == null || b == null)
|
|
return false;
|
|
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/data/binarySearch.ts
|
|
function findMaxIndex(min, max, iteratee) {
|
|
if (min > max)
|
|
return;
|
|
let found;
|
|
while (max >= min) {
|
|
const index = Math.floor((max + min) / 2);
|
|
const value = iteratee(index);
|
|
if (value) {
|
|
found = index;
|
|
min = index + 1;
|
|
} else {
|
|
max = index - 1;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
function findMinIndex(min, max, iteratee) {
|
|
if (min > max)
|
|
return;
|
|
let found;
|
|
while (max >= min) {
|
|
const index = Math.floor((max + min) / 2);
|
|
const value = iteratee(index);
|
|
if (value) {
|
|
found = index;
|
|
max = index - 1;
|
|
} else {
|
|
min = index + 1;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
function findMaxValue(min, max, iteratee) {
|
|
if (min > max)
|
|
return;
|
|
let found;
|
|
while (max >= min) {
|
|
const index = Math.floor((max + min) / 2);
|
|
const value = iteratee(index);
|
|
if (value == null) {
|
|
max = index - 1;
|
|
} else {
|
|
found = value;
|
|
min = index + 1;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
function findMinValue(min, max, iteratee) {
|
|
if (min > max)
|
|
return;
|
|
let found;
|
|
while (max >= min) {
|
|
const index = Math.floor((max + min) / 2);
|
|
const value = iteratee(index);
|
|
if (value == null) {
|
|
min = index + 1;
|
|
} else {
|
|
found = value;
|
|
max = index - 1;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/canvas.ts
|
|
function createCanvasContext(width2 = 0, height2 = 0) {
|
|
const OffscreenCanvasCtor = getOffscreenCanvas();
|
|
return new OffscreenCanvasCtor(width2, height2).getContext("2d");
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/configuredCanvasMixin.ts
|
|
var CANVAS_WIDTH = 800;
|
|
var CANVAS_HEIGHT = 600;
|
|
var CANVAS_TO_BUFFER_DEFAULTS = { quality: 1 };
|
|
function ConfiguredCanvasMixin(Base) {
|
|
const ConfiguredCanvasClass = class ConfiguredCanvas extends Base {
|
|
constructor(...args) {
|
|
super(...args);
|
|
this.gpu = false;
|
|
}
|
|
toBuffer(format, options) {
|
|
return super.toBuffer(format, { ...options, msaa: false });
|
|
}
|
|
transferToImageBitmap() {
|
|
const { width: width2, height: height2 } = this;
|
|
const bitmap = new ConfiguredCanvasClass(Math.max(1, width2), Math.max(1, height2));
|
|
if (width2 > 0 && height2 > 0) {
|
|
bitmap.getContext("2d").drawCanvas(this, 0, 0, width2, height2);
|
|
}
|
|
Object.defineProperty(bitmap, "close", {
|
|
value: () => {
|
|
}
|
|
});
|
|
return bitmap;
|
|
}
|
|
};
|
|
return ConfiguredCanvasClass;
|
|
}
|
|
var patchesApplied = false;
|
|
function applySkiaPatches(CanvasRenderingContext2D, DOMMatrix) {
|
|
if (patchesApplied)
|
|
return;
|
|
patchesApplied = true;
|
|
const superCreateConicGradient = CanvasRenderingContext2D.prototype.createConicGradient;
|
|
Object.defineProperty(CanvasRenderingContext2D.prototype, "createConicGradient", {
|
|
value: function(angle2, x, y) {
|
|
return superCreateConicGradient.call(this, angle2 + Math.PI / 2, x, y);
|
|
},
|
|
writable: true,
|
|
configurable: true
|
|
});
|
|
Object.defineProperty(CanvasRenderingContext2D.prototype, "fillText", {
|
|
value: function(text2, x, y) {
|
|
let path2d = this.outlineText(text2);
|
|
path2d = path2d.transform(new DOMMatrix([1, 0, 0, 1, x, y]));
|
|
this.fill(path2d);
|
|
},
|
|
writable: true,
|
|
configurable: true
|
|
});
|
|
}
|
|
|
|
// packages/ag-charts-core/src/state/caching.ts
|
|
var SimpleCache = class {
|
|
constructor(getter) {
|
|
this.getter = getter;
|
|
}
|
|
get() {
|
|
this.result ?? (this.result = this.getter());
|
|
return this.result;
|
|
}
|
|
clear() {
|
|
this.result = void 0;
|
|
}
|
|
};
|
|
var WeakCache = class {
|
|
constructor(getter) {
|
|
this.getter = getter;
|
|
}
|
|
get() {
|
|
let result = this.result?.deref();
|
|
if (result)
|
|
return result;
|
|
result = this.getter();
|
|
this.result = new WeakRef(result);
|
|
return result;
|
|
}
|
|
clear() {
|
|
this.result = void 0;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-core/src/utils/time/date.ts
|
|
function compareDates(a, b) {
|
|
return a.valueOf() - b.valueOf();
|
|
}
|
|
function deduplicateSortedArray(values) {
|
|
let v0 = Number.NaN;
|
|
const out = [];
|
|
for (const v of values) {
|
|
const v1 = v.valueOf();
|
|
if (v0 !== v1)
|
|
out.push(v);
|
|
v0 = v1;
|
|
}
|
|
return out;
|
|
}
|
|
function sortAndUniqueDates(values) {
|
|
const sortedValues = values.slice().sort(compareDates);
|
|
return datesSortOrder(sortedValues) == null ? deduplicateSortedArray(sortedValues) : sortedValues;
|
|
}
|
|
function datesSortOrder(d) {
|
|
if (d.length === 0)
|
|
return 1;
|
|
const sign = Number(d.at(-1)) > Number(d[0]) ? 1 : -1;
|
|
let v0 = -Infinity * sign;
|
|
for (const v of d) {
|
|
const v1 = v.valueOf();
|
|
if (Math.sign(v1 - v0) !== sign)
|
|
return;
|
|
v0 = v1;
|
|
}
|
|
return sign;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/deprecation.ts
|
|
function createDeprecationWarning() {
|
|
return (key, message) => {
|
|
const msg = [`Property [${key}] is deprecated.`, message].filter(Boolean).join(" ");
|
|
warnOnce(msg);
|
|
};
|
|
}
|
|
function Deprecated(message, opts) {
|
|
const warnDeprecated = createDeprecationWarning();
|
|
const def = opts?.default;
|
|
return addTransformToInstanceProperty((_, key, value) => {
|
|
if (value !== def) {
|
|
warnDeprecated(key.toString(), message);
|
|
}
|
|
return value;
|
|
});
|
|
}
|
|
function DeprecatedAndRenamedTo(newPropName, mapValue) {
|
|
const warnDeprecated = createDeprecationWarning();
|
|
return addTransformToInstanceProperty(
|
|
(target, key, value) => {
|
|
if (value !== target[newPropName]) {
|
|
warnDeprecated(key.toString(), `Use [${newPropName}] instead.`);
|
|
setPath(target, newPropName, mapValue ? mapValue(value) : value);
|
|
}
|
|
return BREAK_TRANSFORM_CHAIN;
|
|
},
|
|
(target, key) => {
|
|
warnDeprecated(key.toString(), `Use [${newPropName}] instead.`);
|
|
return getPath(target, newPropName);
|
|
}
|
|
);
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/data/diff.ts
|
|
function diffArrays(previous, current) {
|
|
const size = Math.max(previous.length, current.length);
|
|
const added = /* @__PURE__ */ new Set();
|
|
const removed = /* @__PURE__ */ new Set();
|
|
for (let i = 0; i < size; i++) {
|
|
const prev = previous[i];
|
|
const curr = current[i];
|
|
if (prev === curr)
|
|
continue;
|
|
if (removed.has(curr)) {
|
|
removed.delete(curr);
|
|
} else if (curr) {
|
|
added.add(curr);
|
|
}
|
|
if (added.has(prev)) {
|
|
added.delete(prev);
|
|
} else if (prev) {
|
|
removed.add(prev);
|
|
}
|
|
}
|
|
return { changed: added.size > 0 || removed.size > 0, added, removed };
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/distance.ts
|
|
function pointsDistanceSquared(x1, y1, x2, y2) {
|
|
const dx2 = x1 - x2;
|
|
const dy2 = y1 - y2;
|
|
return dx2 * dx2 + dy2 * dy2;
|
|
}
|
|
function lineDistanceSquared(x, y, x1, y1, x2, y2, best) {
|
|
if (x1 === x2 && y1 === y2) {
|
|
return Math.min(best, pointsDistanceSquared(x, y, x1, y1));
|
|
}
|
|
const dx2 = x2 - x1;
|
|
const dy2 = y2 - y1;
|
|
const t = Math.max(0, Math.min(1, ((x - x1) * dx2 + (y - y1) * dy2) / (dx2 * dx2 + dy2 * dy2)));
|
|
const ix = x1 + t * dx2;
|
|
const iy = y1 + t * dy2;
|
|
return Math.min(best, pointsDistanceSquared(x, y, ix, iy));
|
|
}
|
|
function arcDistanceSquared(x, y, cx, cy, radius, startAngle, endAngle, counterClockwise, best) {
|
|
if (counterClockwise) {
|
|
[endAngle, startAngle] = [startAngle, endAngle];
|
|
}
|
|
const angle2 = Math.atan2(y - cy, x - cx);
|
|
if (!isBetweenAngles(angle2, startAngle, endAngle)) {
|
|
const startX = cx + Math.cos(startAngle) * radius;
|
|
const startY = cy + Math.sin(startAngle) * radius;
|
|
const endX = cx + Math.cos(startAngle) * radius;
|
|
const endY = cy + Math.sin(startAngle) * radius;
|
|
return Math.min(best, pointsDistanceSquared(x, y, startX, startY), pointsDistanceSquared(x, y, endX, endY));
|
|
}
|
|
const distToArc = radius - Math.sqrt(pointsDistanceSquared(x, y, cx, cy));
|
|
return Math.min(best, distToArc * distToArc);
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/data/extent.ts
|
|
function extent(values, sortOrder) {
|
|
if (values.length === 0) {
|
|
return null;
|
|
}
|
|
if (sortOrder !== void 0) {
|
|
const first2 = values.at(0);
|
|
const last = values.at(-1);
|
|
const v0 = first2 instanceof Date ? first2.getTime() : first2;
|
|
const v1 = last instanceof Date ? last.getTime() : last;
|
|
if (typeof v0 === "number" && typeof v1 === "number") {
|
|
return sortOrder === 1 ? [v0, v1] : [v1, v0];
|
|
}
|
|
}
|
|
let min = Infinity;
|
|
let max = -Infinity;
|
|
for (const n of values) {
|
|
const v = n instanceof Date ? n.getTime() : n;
|
|
if (typeof v !== "number")
|
|
continue;
|
|
if (v < min) {
|
|
min = v;
|
|
}
|
|
if (v > max) {
|
|
max = v;
|
|
}
|
|
}
|
|
const result = [min, max];
|
|
return result.every(Number.isFinite) ? result : null;
|
|
}
|
|
function normalisedExtentWithMetadata(d, min, max, preferredMin, preferredMax, toValue, sortOrder) {
|
|
let clipped = false;
|
|
const domainExtentNumbers = extent(d, sortOrder);
|
|
const domainExtent = domainExtentNumbers && toValue ? [toValue(domainExtentNumbers[0]), toValue(domainExtentNumbers[1])] : domainExtentNumbers;
|
|
if (domainExtent == null) {
|
|
let nullExtent;
|
|
if (min != null && max != null && min <= max) {
|
|
nullExtent = [min, max];
|
|
} else if (preferredMin != null && preferredMax != null && preferredMin <= preferredMax) {
|
|
nullExtent = [preferredMin, preferredMax];
|
|
}
|
|
return { extent: nullExtent ?? [], clipped: false };
|
|
}
|
|
let [d0, d1] = domainExtent;
|
|
if (min != null) {
|
|
clipped || (clipped = min > d0);
|
|
d0 = min;
|
|
} else if (preferredMin != null && preferredMin < d0) {
|
|
d0 = preferredMin;
|
|
}
|
|
if (max != null) {
|
|
clipped || (clipped = max < d1);
|
|
d1 = max;
|
|
} else if (preferredMax != null && preferredMax > d1) {
|
|
d1 = preferredMax;
|
|
}
|
|
if (d0 > d1) {
|
|
return { extent: [], clipped: false };
|
|
}
|
|
return { extent: [d0, d1], clipped };
|
|
}
|
|
function normalisedTimeExtentWithMetadata(input, min, max, preferredMin, preferredMax) {
|
|
const { extent: e, clipped } = normalisedExtentWithMetadata(
|
|
input.domain,
|
|
isNumber(min) ? new Date(min) : min,
|
|
isNumber(max) ? new Date(max) : max,
|
|
isNumber(preferredMin) ? new Date(preferredMin) : preferredMin,
|
|
isNumber(preferredMax) ? new Date(preferredMax) : preferredMax,
|
|
(x) => new Date(x),
|
|
input.sortMetadata?.sortOrder
|
|
);
|
|
return { extent: e.map((x) => new Date(x)), clipped };
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/format/format.util.ts
|
|
var percentFormatter = new Intl.NumberFormat("en-US", { style: "percent" });
|
|
function formatValue(value, maximumFractionDigits = 2) {
|
|
if (typeof value === "number") {
|
|
return formatNumber(value, maximumFractionDigits);
|
|
}
|
|
return typeof value === "string" ? value : String(value ?? "");
|
|
}
|
|
function formatPercent(value) {
|
|
return percentFormatter.format(value);
|
|
}
|
|
var numberFormatters = (/* @__PURE__ */ new Map()).set(
|
|
2,
|
|
new Intl.NumberFormat("en-US", { maximumFractionDigits: 2, useGrouping: false })
|
|
);
|
|
function formatNumber(value, maximumFractionDigits) {
|
|
let formatter2 = numberFormatters.get(maximumFractionDigits);
|
|
if (!formatter2) {
|
|
formatter2 = new Intl.NumberFormat("en-US", { maximumFractionDigits, useGrouping: false });
|
|
numberFormatters.set(maximumFractionDigits, formatter2);
|
|
}
|
|
return formatter2.format(value);
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/geojson.ts
|
|
function isValidCoordinate(value) {
|
|
return Array.isArray(value) && value.length >= 2 && value.every(isFiniteNumber);
|
|
}
|
|
function isValidCoordinates(value) {
|
|
return Array.isArray(value) && value.length >= 2 && value.every(isValidCoordinate);
|
|
}
|
|
function hasSameStartEndPoint(c) {
|
|
const start2 = c[0];
|
|
const end3 = c.at(-1);
|
|
if (end3 === void 0)
|
|
return false;
|
|
return isNumberEqual(start2[0], end3[0], 1e-3) && isNumberEqual(start2[1], end3[1], 1e-3);
|
|
}
|
|
function isValidPolygon(value) {
|
|
return Array.isArray(value) && value.every(isValidCoordinates) && value.every(hasSameStartEndPoint);
|
|
}
|
|
function isValidGeometry(value) {
|
|
if (value === null)
|
|
return true;
|
|
if (!isObject(value) || value.type == null)
|
|
return false;
|
|
const { type, coordinates } = value;
|
|
switch (type) {
|
|
case "GeometryCollection":
|
|
return Array.isArray(value.geometries) && value.geometries.every(isValidGeometry);
|
|
case "MultiPolygon":
|
|
return Array.isArray(coordinates) && coordinates.every(isValidPolygon);
|
|
case "Polygon":
|
|
return isValidPolygon(coordinates);
|
|
case "MultiLineString":
|
|
return Array.isArray(coordinates) && coordinates.every(isValidCoordinates);
|
|
case "LineString":
|
|
return isValidCoordinates(coordinates);
|
|
case "MultiPoint":
|
|
return isValidCoordinates(coordinates);
|
|
case "Point":
|
|
return isValidCoordinate(coordinates);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
function isValidFeature(value) {
|
|
return isObject(value) && value.type === "Feature" && isValidGeometry(value.geometry);
|
|
}
|
|
function isValidFeatureCollection(value) {
|
|
return isObject(value) && value.type === "FeatureCollection" && Array.isArray(value.features) && value.features.every(isValidFeature);
|
|
}
|
|
var geoJson = attachDescription(isValidFeatureCollection, "a GeoJSON object");
|
|
|
|
// packages/ag-charts-core/src/structures/graph.ts
|
|
var AdjacencyListGraph = class {
|
|
constructor(cachedNeighboursEdge, processedEdge, singleValueEdges) {
|
|
this._vertexCount = 0;
|
|
this._edgeCount = 0;
|
|
this.pendingProcessingEdgesFrom = [];
|
|
this.pendingProcessingEdgesTo = [];
|
|
this.cachedNeighboursEdge = cachedNeighboursEdge;
|
|
this.processedEdge = processedEdge;
|
|
this.singleValueEdges = singleValueEdges;
|
|
}
|
|
clear() {
|
|
this._vertexCount = 0;
|
|
this._edgeCount = 0;
|
|
this.pendingProcessingEdgesFrom = [];
|
|
this.pendingProcessingEdgesTo = [];
|
|
this.singleValueEdges?.clear();
|
|
}
|
|
getVertexCount() {
|
|
return this._vertexCount;
|
|
}
|
|
getEdgeCount() {
|
|
return this._edgeCount;
|
|
}
|
|
addVertex(value) {
|
|
const vertex = new Vertex(value);
|
|
this._vertexCount++;
|
|
return vertex;
|
|
}
|
|
addEdge(from3, to, edge) {
|
|
if (edge === this.cachedNeighboursEdge) {
|
|
from3.updateCachedNeighbours().set(to.value, to);
|
|
} else if (edge === this.processedEdge) {
|
|
this.pendingProcessingEdgesFrom.push(from3);
|
|
this.pendingProcessingEdgesTo.push(to);
|
|
}
|
|
const { edges } = from3;
|
|
const vertices = edges.get(edge);
|
|
if (!vertices) {
|
|
edges.set(edge, [to]);
|
|
this._edgeCount++;
|
|
} else if (!vertices.includes(to)) {
|
|
if (this.singleValueEdges?.has(edge)) {
|
|
edges.set(edge, [to]);
|
|
} else {
|
|
vertices.push(to);
|
|
this._edgeCount++;
|
|
}
|
|
}
|
|
}
|
|
removeVertex(vertex) {
|
|
this._vertexCount--;
|
|
const edges = vertex.edges;
|
|
if (!edges)
|
|
return;
|
|
for (const [, adjacentVertices] of edges) {
|
|
this._vertexCount -= adjacentVertices.length;
|
|
}
|
|
vertex.clear();
|
|
}
|
|
removeEdge(from3, to, edge) {
|
|
const neighbours = from3.edges.get(edge);
|
|
if (!neighbours)
|
|
return;
|
|
const index = neighbours.indexOf(to);
|
|
if (index === -1)
|
|
return;
|
|
neighbours.splice(index, 1);
|
|
if (neighbours.length === 0) {
|
|
from3.edges.delete(edge);
|
|
}
|
|
this._edgeCount--;
|
|
if (edge === this.cachedNeighboursEdge) {
|
|
from3.readCachedNeighbours()?.delete(to.value);
|
|
}
|
|
}
|
|
removeEdges(from3, edgeValue) {
|
|
from3.edges.delete(edgeValue);
|
|
}
|
|
getVertexValue(vertex) {
|
|
return vertex.value;
|
|
}
|
|
// Iterate all the neighbours of a given vertex.
|
|
*neighbours(from3) {
|
|
for (const [, adjacentVertices] of from3.edges) {
|
|
for (const adjacentVertex of adjacentVertices) {
|
|
yield adjacentVertex;
|
|
}
|
|
}
|
|
}
|
|
// Iterate all the neighbours and their edges of a given vertex
|
|
*neighboursAndEdges(from3) {
|
|
for (const [edge, adjacentVertices] of from3.edges) {
|
|
for (const adjacentVertex of adjacentVertices) {
|
|
yield [adjacentVertex, edge];
|
|
}
|
|
}
|
|
}
|
|
// Get the set of neighbours along a given edge.
|
|
neighboursWithEdgeValue(from3, edgeValue) {
|
|
return from3.edges.get(edgeValue);
|
|
}
|
|
// Find the first neighbour along the given edge.
|
|
findNeighbour(from3, edgeValue) {
|
|
return from3.edges.get(edgeValue)?.[0];
|
|
}
|
|
// Find the value of the first neighbour along the given edge.
|
|
findNeighbourValue(from3, edgeValue) {
|
|
const neighbour = this.findNeighbour(from3, edgeValue);
|
|
if (!neighbour)
|
|
return;
|
|
return this.getVertexValue(neighbour);
|
|
}
|
|
// Find the first neighbour with a given value, optionally along a given edge.
|
|
findNeighbourWithValue(from3, value, edgeValue) {
|
|
const neighbours = edgeValue == null ? this.neighbours(from3) : this.neighboursWithEdgeValue(from3, edgeValue);
|
|
if (!neighbours)
|
|
return;
|
|
for (const neighbour of neighbours) {
|
|
if (this.getVertexValue(neighbour) === value) {
|
|
return neighbour;
|
|
}
|
|
}
|
|
}
|
|
// Find a vertex by iterating an array of vertex values along a given edge.
|
|
findVertexAlongEdge(from3, findValues, edgeValue) {
|
|
if (edgeValue === this.cachedNeighboursEdge) {
|
|
let found2;
|
|
for (const value of findValues) {
|
|
found2 = (found2 ?? from3).readCachedNeighbours()?.get(value);
|
|
if (!found2)
|
|
return;
|
|
}
|
|
return found2;
|
|
}
|
|
if (findValues.length === 0)
|
|
return;
|
|
let found = from3;
|
|
for (const value of findValues) {
|
|
const neighbours = found ? this.neighboursWithEdgeValue(found, edgeValue) : void 0;
|
|
if (!neighbours)
|
|
return;
|
|
found = neighbours.find((n) => n.value === value);
|
|
}
|
|
return found;
|
|
}
|
|
adjacent(from3, to) {
|
|
for (const [, adjacentVertices] of from3.edges) {
|
|
if (adjacentVertices.includes(to))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
var Vertex = class {
|
|
constructor(value) {
|
|
this.value = value;
|
|
this.edges = /* @__PURE__ */ new Map();
|
|
}
|
|
readCachedNeighbours() {
|
|
return this._cachedNeighbours;
|
|
}
|
|
updateCachedNeighbours() {
|
|
this._cachedNeighbours ?? (this._cachedNeighbours = /* @__PURE__ */ new Map());
|
|
return this._cachedNeighbours;
|
|
}
|
|
clear() {
|
|
this.edges.clear();
|
|
this._cachedNeighbours?.clear();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-core/src/utils/data/json.ts
|
|
var CLASS_INSTANCE_TYPE = "class-instance";
|
|
function jsonDiff(source, target, shallow) {
|
|
if (isArray(target)) {
|
|
if (!isArray(source) || source.length !== target.length || target.some((v, i) => jsonDiff(source[i], v, shallow) != null)) {
|
|
return target;
|
|
}
|
|
} else if (isPlainObject(target)) {
|
|
if (!isPlainObject(source)) {
|
|
return target;
|
|
}
|
|
const result = {};
|
|
const allKeys = /* @__PURE__ */ new Set([
|
|
...Object.keys(source),
|
|
...Object.keys(target)
|
|
]);
|
|
for (const key of allKeys) {
|
|
if (source[key] === target[key]) {
|
|
continue;
|
|
} else if (shallow?.has(key)) {
|
|
result[key] = target[key];
|
|
} else if (typeof source[key] === typeof target[key]) {
|
|
const diff9 = jsonDiff(source[key], target[key], shallow);
|
|
if (diff9 !== null) {
|
|
result[key] = diff9;
|
|
}
|
|
} else {
|
|
result[key] = target[key];
|
|
}
|
|
}
|
|
return Object.keys(result).length ? result : null;
|
|
} else if (source !== target) {
|
|
return target;
|
|
}
|
|
return null;
|
|
}
|
|
function jsonPropertyCompare(source, target) {
|
|
for (const key of Object.keys(source)) {
|
|
if (source[key] === target?.[key])
|
|
continue;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function deepClone(source, opts) {
|
|
if (isArray(source)) {
|
|
return cloneArray(source, opts);
|
|
}
|
|
if (isPlainObject(source)) {
|
|
return clonePlainObject(source, opts);
|
|
}
|
|
if (source instanceof Map) {
|
|
return new Map(deepClone(Array.from(source)));
|
|
}
|
|
return shallowClone(source);
|
|
}
|
|
function cloneArray(source, opts) {
|
|
const result = [];
|
|
const seen = opts?.seen;
|
|
for (const item of source) {
|
|
if (typeof item === "object" && seen?.includes(item)) {
|
|
warn("cycle detected in array", item);
|
|
continue;
|
|
}
|
|
seen?.push(item);
|
|
result.push(deepClone(item, opts));
|
|
seen?.pop();
|
|
}
|
|
return result;
|
|
}
|
|
function clonePlainObject(source, opts) {
|
|
const target = {};
|
|
for (const key of Object.keys(source)) {
|
|
if (opts?.assign?.has(key)) {
|
|
target[key] = source[key];
|
|
} else if (opts?.shallow?.has(key)) {
|
|
target[key] = shallowClone(source[key]);
|
|
} else {
|
|
target[key] = deepClone(source[key], opts);
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
function shallowClone(source) {
|
|
if (isArray(source)) {
|
|
return source.slice(0);
|
|
}
|
|
if (isPlainObject(source)) {
|
|
return { ...source };
|
|
}
|
|
if (isDate(source)) {
|
|
return new Date(source);
|
|
}
|
|
if (isRegExp(source)) {
|
|
return new RegExp(source.source, source.flags);
|
|
}
|
|
return source;
|
|
}
|
|
function jsonWalk(json, visit, skip, parallelJson, ctx, acc) {
|
|
if (isArray(json)) {
|
|
acc = visit(json, parallelJson, ctx, acc);
|
|
let index = 0;
|
|
for (const node of json) {
|
|
acc = jsonWalk(node, visit, skip, parallelJson?.[index], ctx, acc);
|
|
index++;
|
|
}
|
|
} else if (isPlainObject(json)) {
|
|
acc = visit(json, parallelJson, ctx, acc);
|
|
for (const key of Object.keys(json)) {
|
|
if (skip?.has(key)) {
|
|
continue;
|
|
}
|
|
const value = json[key];
|
|
acc = jsonWalk(value, visit, skip, parallelJson?.[key], ctx, acc);
|
|
}
|
|
}
|
|
return acc;
|
|
}
|
|
function jsonApply(target, source, params = {}) {
|
|
const { path, matcherPath = path?.replace(/(\[[0-9+]+])/i, "[]"), skip = [] } = params;
|
|
if (target == null) {
|
|
throw new Error(`AG Charts - target is uninitialised: ${path ?? "<root>"}`);
|
|
}
|
|
if (source == null) {
|
|
return target;
|
|
}
|
|
if (isProperties(target)) {
|
|
return target.set(source);
|
|
}
|
|
const targetAny = target;
|
|
const targetType = classify(target);
|
|
for (const property of Object.keys(source)) {
|
|
if (SKIP_JS_BUILTINS.has(property))
|
|
continue;
|
|
const propertyMatcherPath = `${matcherPath ? matcherPath + "." : ""}${property}`;
|
|
if (skip.includes(propertyMatcherPath))
|
|
continue;
|
|
const newValue = source[property];
|
|
const propertyPath = `${path ? path + "." : ""}${property}`;
|
|
const targetClass = targetAny.constructor;
|
|
const currentValue = targetAny[property];
|
|
try {
|
|
const currentValueType = classify(currentValue);
|
|
const newValueType = classify(newValue);
|
|
if (targetType === CLASS_INSTANCE_TYPE && !(property in target || property === "context")) {
|
|
if (newValue === void 0)
|
|
continue;
|
|
warn(`unable to set [${propertyPath}] in ${targetClass?.name} - property is unknown`);
|
|
continue;
|
|
}
|
|
if (currentValueType != null && newValueType != null && newValueType !== currentValueType && (currentValueType !== CLASS_INSTANCE_TYPE || newValueType !== "object")) {
|
|
warn(
|
|
`unable to set [${propertyPath}] in ${targetClass?.name} - can't apply type of [${newValueType}], allowed types are: [${currentValueType}]`
|
|
);
|
|
continue;
|
|
}
|
|
if (isProperties(currentValue)) {
|
|
if (newValue === void 0) {
|
|
currentValue.clear();
|
|
} else {
|
|
currentValue.set(newValue);
|
|
}
|
|
} else if (newValueType === "object" && property !== "context") {
|
|
if (!(property in targetAny)) {
|
|
warn(`unable to set [${propertyPath}] in ${targetClass?.name} - property is unknown`);
|
|
continue;
|
|
}
|
|
if (currentValue == null) {
|
|
targetAny[property] = newValue;
|
|
} else {
|
|
jsonApply(currentValue, newValue, {
|
|
...params,
|
|
path: propertyPath,
|
|
matcherPath: propertyMatcherPath
|
|
});
|
|
}
|
|
} else {
|
|
targetAny[property] = newValue;
|
|
}
|
|
} catch (error2) {
|
|
warn(`unable to set [${propertyPath}] in [${targetClass?.name}]; nested error is: ${error2.message}`);
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
function classify(value) {
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (isHtmlElement(value) || isDate(value)) {
|
|
return "primitive";
|
|
}
|
|
if (isArray(value)) {
|
|
return "array";
|
|
}
|
|
if (isObject(value)) {
|
|
return isPlainObject(value) ? "object" : CLASS_INSTANCE_TYPE;
|
|
}
|
|
if (isFunction(value)) {
|
|
return "function";
|
|
}
|
|
return "primitive";
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/dom/domEvents.ts
|
|
function attachListener(element2, eventName, handler, options) {
|
|
element2.addEventListener(eventName, handler, options);
|
|
return () => element2.removeEventListener(eventName, handler, options);
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/dom/keynavUtil.ts
|
|
function addEscapeEventListener(elem, onEscape, keyCodes = ["Escape"]) {
|
|
return attachListener(elem, "keydown", (event) => {
|
|
if (matchesKey(event, ...keyCodes)) {
|
|
onEscape(event);
|
|
}
|
|
});
|
|
}
|
|
function addMouseCloseListener(menu, hideCallback) {
|
|
const removeEvent = attachListener(getWindow(), "mousedown", (event) => {
|
|
if ([0, 2].includes(event.button) && !containsEvent(menu, event)) {
|
|
hideCallback();
|
|
removeEvent();
|
|
}
|
|
});
|
|
return removeEvent;
|
|
}
|
|
function addTouchCloseListener(menu, hideCallback) {
|
|
const removeEvent = attachListener(getWindow(), "touchstart", (event) => {
|
|
const touches = Array.from(event.targetTouches);
|
|
if (touches.some((touch) => !containsEvent(menu, touch))) {
|
|
hideCallback();
|
|
removeEvent();
|
|
}
|
|
});
|
|
return removeEvent;
|
|
}
|
|
function containsEvent(container, event) {
|
|
if (isElement(event.target) && event.target.shadowRoot != null) {
|
|
return true;
|
|
}
|
|
return isNode(event.target) && container.contains(event.target);
|
|
}
|
|
function addOverrideFocusVisibleEventListener(menu, buttons, overrideFocusVisible) {
|
|
const setFocusVisible = (value) => {
|
|
for (const btn of buttons) {
|
|
setAttribute(btn, "data-focus-visible-override", value);
|
|
}
|
|
};
|
|
setFocusVisible(overrideFocusVisible);
|
|
return attachListener(menu, "keydown", () => setFocusVisible(true), { once: true });
|
|
}
|
|
function hasNoModifiers(event) {
|
|
return !(event.shiftKey || event.altKey || event.ctrlKey || event.metaKey);
|
|
}
|
|
function matchesKey(event, ...keys) {
|
|
return hasNoModifiers(event) && keys.includes(event.key);
|
|
}
|
|
function linkTwoButtons(src, dst, key) {
|
|
return attachListener(src, "keydown", (event) => {
|
|
if (matchesKey(event, key)) {
|
|
dst.focus();
|
|
}
|
|
});
|
|
}
|
|
var PREV_NEXT_KEYS = {
|
|
horizontal: { nextKey: "ArrowRight", prevKey: "ArrowLeft" },
|
|
vertical: { nextKey: "ArrowDown", prevKey: "ArrowUp" }
|
|
};
|
|
function initRovingTabIndex(opts) {
|
|
const { orientation, buttons, wrapAround = false, onEscape, onFocus, onBlur } = opts;
|
|
const { nextKey, prevKey } = PREV_NEXT_KEYS[orientation];
|
|
const setTabIndices = (event) => {
|
|
if (event.target && "tabIndex" in event.target) {
|
|
for (const b of buttons) {
|
|
b.tabIndex = -1;
|
|
}
|
|
event.target.tabIndex = 0;
|
|
}
|
|
};
|
|
const [c, m] = wrapAround ? [buttons.length, buttons.length] : [0, Infinity];
|
|
const cleanup = new CleanupRegistry();
|
|
for (let i = 0; i < buttons.length; i++) {
|
|
const prev = buttons[(c + i - 1) % m];
|
|
const curr = buttons[i];
|
|
const next = buttons[(c + i + 1) % m];
|
|
cleanup.register(
|
|
attachListener(curr, "focus", setTabIndices),
|
|
onFocus && attachListener(curr, "focus", onFocus),
|
|
onBlur && attachListener(curr, "blur", onBlur),
|
|
onEscape && addEscapeEventListener(curr, onEscape),
|
|
prev && linkTwoButtons(curr, prev, prevKey),
|
|
next && linkTwoButtons(curr, next, nextKey),
|
|
attachListener(curr, "keydown", (event) => {
|
|
if (matchesKey(event, nextKey, prevKey)) {
|
|
event.preventDefault();
|
|
}
|
|
})
|
|
);
|
|
curr.tabIndex = i === 0 ? 0 : -1;
|
|
}
|
|
return cleanup;
|
|
}
|
|
function makeAccessibleClickListener(element2, onclick) {
|
|
return (event) => {
|
|
if (element2.ariaDisabled === "true") {
|
|
return event.preventDefault();
|
|
}
|
|
onclick(event);
|
|
};
|
|
}
|
|
function isButtonClickEvent(event) {
|
|
if ("button" in event) {
|
|
return event.button === 0;
|
|
}
|
|
return hasNoModifiers(event) && (event.code === "Space" || event.key === "Enter");
|
|
}
|
|
function getLastFocus(sourceEvent) {
|
|
const target = sourceEvent?.target;
|
|
if (isElement(target) && "tabindex" in target.attributes) {
|
|
return target;
|
|
}
|
|
return void 0;
|
|
}
|
|
function stopPageScrolling(element2) {
|
|
return attachListener(element2, "keydown", (event) => {
|
|
if (event.defaultPrevented)
|
|
return;
|
|
const shouldPrevent = getAttribute(event.target, "data-preventdefault", true);
|
|
if (shouldPrevent && matchesKey(event, "ArrowRight", "ArrowLeft", "ArrowDown", "ArrowUp")) {
|
|
event.preventDefault();
|
|
}
|
|
});
|
|
}
|
|
|
|
// packages/ag-charts-core/src/identity/id.ts
|
|
var ID_MAP = /* @__PURE__ */ new Map();
|
|
var nextElementID = 1;
|
|
function resetIds() {
|
|
ID_MAP.clear();
|
|
nextElementID = 1;
|
|
}
|
|
function createId(instance) {
|
|
const constructor = instance.constructor;
|
|
let className = Object.hasOwn(constructor, "className") ? constructor.className : constructor.name;
|
|
inDevelopmentMode(() => {
|
|
if (!className) {
|
|
throw new Error(`The ${String(constructor)} is missing the 'className' property.`);
|
|
}
|
|
});
|
|
className ?? (className = "Unknown");
|
|
const nextId = (ID_MAP.get(className) ?? 0) + 1;
|
|
ID_MAP.set(className, nextId);
|
|
return `${className}-${nextId}`;
|
|
}
|
|
function createElementId() {
|
|
return `ag-charts-${nextElementID++}`;
|
|
}
|
|
function generateUUID() {
|
|
return crypto.randomUUID?.() ?? generateUUIDv4();
|
|
}
|
|
function generateUUIDv4() {
|
|
const uuidArray = new Uint8Array(16);
|
|
crypto.getRandomValues(uuidArray);
|
|
uuidArray[6] = uuidArray[6] & 15 | 64;
|
|
uuidArray[8] = uuidArray[8] & 63 | 128;
|
|
let uuid = "";
|
|
for (let i = 0; i < uuidArray.length; i++) {
|
|
if (i === 4 || i === 6 || i === 8 || i === 10) {
|
|
uuid += "-";
|
|
}
|
|
uuid += uuidArray[i].toString(16).padStart(2, "0");
|
|
}
|
|
return uuid;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/data/linkedList.ts
|
|
function insertListItemsSorted(list, items, cmp2) {
|
|
let head = list;
|
|
let current = head;
|
|
for (const value of items) {
|
|
if (head == null || cmp2(head.value, value) > 0) {
|
|
head = { value, next: head };
|
|
current = head;
|
|
} else {
|
|
current = current;
|
|
while (current.next != null && cmp2(current.next.value, value) <= 0) {
|
|
current = current.next;
|
|
}
|
|
current.next = { value, next: current.next };
|
|
}
|
|
}
|
|
return head;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/state/memo.ts
|
|
var memorizedFns = /* @__PURE__ */ new WeakMap();
|
|
function memo(params, fnGenerator) {
|
|
const serialisedParams = JSON.stringify(params, null, 0);
|
|
if (!memorizedFns.has(fnGenerator)) {
|
|
memorizedFns.set(fnGenerator, /* @__PURE__ */ new Map());
|
|
}
|
|
if (!memorizedFns.get(fnGenerator)?.has(serialisedParams)) {
|
|
memorizedFns.get(fnGenerator)?.set(serialisedParams, fnGenerator(params));
|
|
}
|
|
return memorizedFns.get(fnGenerator)?.get(serialisedParams);
|
|
}
|
|
var MemoizeNode = class {
|
|
constructor() {
|
|
this.weak = /* @__PURE__ */ new WeakMap();
|
|
this.strong = /* @__PURE__ */ new Map();
|
|
this.set = false;
|
|
this.value = void 0;
|
|
}
|
|
};
|
|
function simpleMemorize2(fn, cacheCallback2) {
|
|
let root = new MemoizeNode();
|
|
const memoised = (...p) => {
|
|
let current = root;
|
|
for (const param of p) {
|
|
const target = typeof param === "object" || typeof param === "symbol" ? current.weak : current.strong;
|
|
let next = target.get(param);
|
|
if (next == null) {
|
|
next = new MemoizeNode();
|
|
target.set(param, next);
|
|
}
|
|
current = next;
|
|
}
|
|
if (current.set) {
|
|
cacheCallback2?.("hit", fn, p);
|
|
return current.value;
|
|
} else {
|
|
const out = fn(...p);
|
|
current.set = true;
|
|
current.value = out;
|
|
cacheCallback2?.("miss", fn, p);
|
|
return out;
|
|
}
|
|
};
|
|
memoised.reset = () => {
|
|
root = new MemoizeNode();
|
|
};
|
|
return memoised;
|
|
}
|
|
function simpleMemorize(fn, cacheCallback2) {
|
|
const primitiveCache = /* @__PURE__ */ new Map();
|
|
const paramsToKeys = (...params) => {
|
|
return params.map((v) => {
|
|
if (typeof v === "object")
|
|
return v;
|
|
if (typeof v === "symbol")
|
|
return v;
|
|
if (!primitiveCache.has(v)) {
|
|
primitiveCache.set(v, { v });
|
|
}
|
|
return primitiveCache.get(v);
|
|
});
|
|
};
|
|
const empty = {};
|
|
const cache = /* @__PURE__ */ new WeakMap();
|
|
return (...p) => {
|
|
const keys = p.length === 0 ? [empty] : paramsToKeys(...p);
|
|
let currentCache = cache;
|
|
for (const key of keys.slice(0, -1)) {
|
|
if (!currentCache.has(key)) {
|
|
currentCache.set(key, /* @__PURE__ */ new WeakMap());
|
|
}
|
|
currentCache = currentCache.get(key);
|
|
}
|
|
const finalKey = keys.at(-1);
|
|
let cachedValue = currentCache.get(finalKey);
|
|
if (cachedValue) {
|
|
cacheCallback2?.("hit", fn, p);
|
|
} else {
|
|
cachedValue = fn(...p);
|
|
currentCache.set(finalKey, cachedValue);
|
|
cacheCallback2?.("miss", fn, p);
|
|
}
|
|
return cachedValue;
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/data/nearest.ts
|
|
function nearestSquared(x, y, objects, maxDistanceSquared = Infinity) {
|
|
const result = { nearest: void 0, distanceSquared: maxDistanceSquared };
|
|
for (const obj of objects) {
|
|
const thisDistance = obj.distanceSquared(x, y);
|
|
if (thisDistance === 0) {
|
|
return { nearest: obj, distanceSquared: 0 };
|
|
} else if (thisDistance < result.distanceSquared) {
|
|
result.nearest = obj;
|
|
result.distanceSquared = thisDistance;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
function nearestSquaredInContainer(x, y, container, maxDistanceSquared = Infinity) {
|
|
const { x: tx = x, y: ty = y } = container.transformPoint?.(x, y) ?? {};
|
|
const result = { nearest: void 0, distanceSquared: maxDistanceSquared };
|
|
for (const child of container.children) {
|
|
const { nearest, distanceSquared: distanceSquared2 } = child.nearestSquared(tx, ty, result.distanceSquared);
|
|
if (distanceSquared2 === 0) {
|
|
return { nearest, distanceSquared: distanceSquared2 };
|
|
} else if (distanceSquared2 < result.distanceSquared) {
|
|
result.nearest = nearest;
|
|
result.distanceSquared = distanceSquared2;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/padding.ts
|
|
var Padding = class extends BaseProperties {
|
|
constructor(top = 0, right = top, bottom = top, left = right) {
|
|
super();
|
|
this.top = top;
|
|
this.right = right;
|
|
this.bottom = bottom;
|
|
this.left = left;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Padding.prototype, "top", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Padding.prototype, "right", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Padding.prototype, "bottom", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Padding.prototype, "left", 2);
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/placement.ts
|
|
function calculatePlacement(naturalWidth, naturalHeight, container, bounds) {
|
|
let { top, right, bottom, left, width: width2, height: height2 } = bounds;
|
|
if (left != null) {
|
|
if (width2 != null) {
|
|
right = container.width - left + width2;
|
|
} else if (right != null) {
|
|
width2 = container.width - left - right;
|
|
}
|
|
} else if (right != null && width2 != null) {
|
|
left = container.width - right - width2;
|
|
}
|
|
if (top != null) {
|
|
if (height2 != null) {
|
|
bottom = container.height - top - height2;
|
|
} else if (bottom != null) {
|
|
height2 = container.height - bottom - top;
|
|
}
|
|
} else if (bottom != null && height2 != null) {
|
|
top = container.height - bottom - height2;
|
|
}
|
|
if (width2 == null) {
|
|
if (height2 == null) {
|
|
height2 = naturalHeight;
|
|
width2 = naturalWidth;
|
|
} else {
|
|
width2 = Math.ceil(naturalWidth * height2 / naturalHeight);
|
|
}
|
|
} else {
|
|
height2 ?? (height2 = Math.ceil(naturalHeight * width2 / naturalWidth));
|
|
}
|
|
if (left == null) {
|
|
if (right == null) {
|
|
left = Math.floor((container.width - width2) / 2);
|
|
} else {
|
|
left = container.width - right - width2;
|
|
}
|
|
}
|
|
if (top == null) {
|
|
if (bottom == null) {
|
|
top = Math.floor((container.height - height2) / 2);
|
|
} else {
|
|
top = container.height - height2 - bottom;
|
|
}
|
|
}
|
|
return { x: left, y: top, width: width2, height: height2 };
|
|
}
|
|
|
|
// packages/ag-charts-core/src/state/stateMachine.ts
|
|
var debugColor = "color: green";
|
|
var debugQuietColor = "color: grey";
|
|
function StateMachineProperty() {
|
|
return addObserverToInstanceProperty(() => {
|
|
});
|
|
}
|
|
function applyProperties(parentState, childState) {
|
|
const childProperties = listDecoratedProperties(childState);
|
|
if (childProperties.length === 0)
|
|
return;
|
|
const properties = extractDecoratedProperties(parentState);
|
|
for (const property of childProperties) {
|
|
if (property in properties) {
|
|
childState[property] = properties[property];
|
|
}
|
|
}
|
|
}
|
|
var AbstractStateMachine = class {
|
|
transitionRoot(event, data) {
|
|
if (this.parent) {
|
|
this.parent.transitionRoot(event, data);
|
|
} else {
|
|
this.transition(event, data);
|
|
}
|
|
}
|
|
};
|
|
var _StateMachine = class _StateMachine extends AbstractStateMachine {
|
|
constructor(defaultState, states, enterEach) {
|
|
super();
|
|
this.defaultState = defaultState;
|
|
this.states = states;
|
|
this.enterEach = enterEach;
|
|
this.debug = create(true, "animation");
|
|
this.state = defaultState;
|
|
this.debug(`%c${this.constructor.name} | init -> ${defaultState}`, debugColor);
|
|
}
|
|
// TODO: handle events which do not require data without requiring `undefined` to be passed as as parameter, while
|
|
// also still requiring data to be passed to those events which do require it.
|
|
transition(event, data) {
|
|
const shouldTransitionSelf = this.transitionChild(event, data);
|
|
if (!shouldTransitionSelf || this.state === _StateMachine.child || this.state === _StateMachine.parent) {
|
|
return;
|
|
}
|
|
const currentState = this.state;
|
|
const currentStateConfig = this.states[this.state];
|
|
let destination = currentStateConfig[event];
|
|
const debugPrefix = `%c${this.constructor.name} | ${this.state} -> ${event} ->`;
|
|
if (Array.isArray(destination)) {
|
|
destination = destination.find((transition) => {
|
|
if (!transition.guard)
|
|
return true;
|
|
const valid = transition.guard(data);
|
|
if (!valid) {
|
|
this.debug(`${debugPrefix} (guarded)`, transition.target, debugQuietColor);
|
|
}
|
|
return valid;
|
|
});
|
|
} else if (typeof destination === "object" && !(destination instanceof _StateMachine) && destination.guard && !destination.guard(data)) {
|
|
this.debug(`${debugPrefix} (guarded)`, destination.target, debugQuietColor);
|
|
return;
|
|
}
|
|
if (!destination) {
|
|
this.debug(`${debugPrefix} ${this.state}`, debugQuietColor);
|
|
return;
|
|
}
|
|
const destinationState = this.getDestinationState(destination);
|
|
const exitFn = destinationState === this.state ? void 0 : currentStateConfig.onExit;
|
|
this.debug(`${debugPrefix} ${destinationState}`, debugColor);
|
|
this.state = destinationState;
|
|
if (typeof destination === "function") {
|
|
destination(data);
|
|
} else if (typeof destination === "object" && !(destination instanceof _StateMachine)) {
|
|
destination.action?.(data);
|
|
}
|
|
exitFn?.();
|
|
this.enterEach?.(currentState, destinationState);
|
|
if (destinationState !== currentState && destinationState !== _StateMachine.child && destinationState !== _StateMachine.parent) {
|
|
this.states[destinationState].onEnter?.(currentState, data);
|
|
}
|
|
}
|
|
transitionAsync(event, data) {
|
|
setTimeout(() => {
|
|
this.transition(event, data);
|
|
}, 0);
|
|
}
|
|
is(value) {
|
|
if (this.state === _StateMachine.child && this.childState) {
|
|
return this.childState.is(value);
|
|
}
|
|
return this.state === value;
|
|
}
|
|
resetHierarchy() {
|
|
this.debug(
|
|
`%c${this.constructor.name} | ${this.state} -> [resetHierarchy] -> ${this.defaultState}`,
|
|
"color: green"
|
|
);
|
|
this.state = this.defaultState;
|
|
}
|
|
transitionChild(event, data) {
|
|
if (this.state !== _StateMachine.child || !this.childState)
|
|
return true;
|
|
applyProperties(this, this.childState);
|
|
this.childState.transition(event, data);
|
|
if (!this.childState.is(_StateMachine.parent))
|
|
return true;
|
|
this.debug(`%c${this.constructor.name} | ${this.state} -> ${event} -> ${this.defaultState}`, debugColor);
|
|
this.state = this.defaultState;
|
|
this.states[this.state].onEnter?.();
|
|
this.childState.resetHierarchy();
|
|
return false;
|
|
}
|
|
getDestinationState(destination) {
|
|
let state = this.state;
|
|
if (typeof destination === "string") {
|
|
state = destination;
|
|
} else if (destination instanceof _StateMachine) {
|
|
this.childState = destination;
|
|
this.childState.parent = this;
|
|
state = _StateMachine.child;
|
|
} else if (typeof destination === "object") {
|
|
if (destination.target instanceof _StateMachine) {
|
|
this.childState = destination.target;
|
|
this.childState.parent = this;
|
|
state = _StateMachine.child;
|
|
} else if (destination.target != null) {
|
|
state = destination.target;
|
|
}
|
|
}
|
|
return state;
|
|
}
|
|
};
|
|
_StateMachine.child = "__child";
|
|
_StateMachine.parent = "__parent";
|
|
var StateMachine = _StateMachine;
|
|
var ParallelStateMachine = class extends AbstractStateMachine {
|
|
constructor(...stateMachines) {
|
|
super();
|
|
this.stateMachines = stateMachines;
|
|
for (const stateMachine of stateMachines) {
|
|
stateMachine.parent = this;
|
|
}
|
|
}
|
|
transition(event, data) {
|
|
for (const stateMachine of this.stateMachines) {
|
|
applyProperties(this, stateMachine);
|
|
stateMachine.transition(event, data);
|
|
}
|
|
}
|
|
transitionAsync(event, data) {
|
|
for (const stateMachine of this.stateMachines) {
|
|
applyProperties(this, stateMachine);
|
|
stateMachine.transitionAsync(event, data);
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-core/src/rendering/textMeasurer.ts
|
|
var TextMeasurer = class {
|
|
constructor(ctx, measureTextCached) {
|
|
this.ctx = ctx;
|
|
this.measureTextCached = measureTextCached;
|
|
this.baselineMap = /* @__PURE__ */ new Map();
|
|
this.charMap = /* @__PURE__ */ new Map();
|
|
this.lineHeightCache = null;
|
|
}
|
|
baselineDistance(textBaseline) {
|
|
if (textBaseline === "alphabetic")
|
|
return 0;
|
|
if (this.baselineMap.has(textBaseline)) {
|
|
return this.baselineMap.get(textBaseline);
|
|
}
|
|
this.ctx.textBaseline = textBaseline;
|
|
const { alphabeticBaseline } = this.ctx.measureText("");
|
|
this.baselineMap.set(textBaseline, alphabeticBaseline);
|
|
this.ctx.textBaseline = "alphabetic";
|
|
return alphabeticBaseline;
|
|
}
|
|
lineHeight() {
|
|
this.lineHeightCache ?? (this.lineHeightCache = this.measureText("").height);
|
|
return this.lineHeightCache;
|
|
}
|
|
measureText(text2) {
|
|
const m = this.measureTextCached?.(text2) ?? this.ctx.measureText(text2);
|
|
const {
|
|
width: width2,
|
|
// Apply fallbacks for environments like `node-canvas` where some metrics may be missing.
|
|
fontBoundingBoxAscent: ascent = m.emHeightAscent,
|
|
fontBoundingBoxDescent: descent = m.emHeightDescent
|
|
} = m;
|
|
const height2 = ascent + descent;
|
|
return { width: width2, height: height2, ascent, descent };
|
|
}
|
|
measureLines(text2) {
|
|
const lines = typeof text2 === "string" ? text2.split(LineSplitter) : text2;
|
|
let width2 = 0;
|
|
let height2 = 0;
|
|
const lineMetrics = lines.map((line) => {
|
|
const b = this.measureText(line);
|
|
if (width2 < b.width) {
|
|
width2 = b.width;
|
|
}
|
|
height2 += b.height;
|
|
return { text: line, ...b };
|
|
});
|
|
return { width: width2, height: height2, lineMetrics };
|
|
}
|
|
textWidth(text2, estimate) {
|
|
if (estimate) {
|
|
let estimatedWidth = 0;
|
|
for (let i = 0; i < text2.length; i++) {
|
|
estimatedWidth += this.textWidth(text2.charAt(i));
|
|
}
|
|
return estimatedWidth;
|
|
}
|
|
if (text2.length > 1) {
|
|
return this.ctx.measureText(text2).width;
|
|
}
|
|
return this.charMap.get(text2) ?? this.charWidth(text2);
|
|
}
|
|
charWidth(char) {
|
|
const { width: width2 } = this.ctx.measureText(char);
|
|
this.charMap.set(char, width2);
|
|
return width2;
|
|
}
|
|
};
|
|
var instanceMap = new LRUCache(50);
|
|
function cachedTextMeasurer(font3) {
|
|
if (typeof font3 === "object") {
|
|
font3 = toFontString(font3);
|
|
}
|
|
let cachedMeasurer = instanceMap.get(font3);
|
|
if (cachedMeasurer)
|
|
return cachedMeasurer;
|
|
const cachedTextMetrics = new LRUCache(1e4);
|
|
const ctx = createCanvasContext();
|
|
ctx.font = font3;
|
|
cachedMeasurer = new TextMeasurer(ctx, (text2) => {
|
|
let textMetrics = cachedTextMetrics.get(text2);
|
|
if (textMetrics)
|
|
return textMetrics;
|
|
textMetrics = ctx.measureText(text2);
|
|
cachedTextMetrics.set(text2, textMetrics);
|
|
return textMetrics;
|
|
});
|
|
instanceMap.set(font3, cachedMeasurer);
|
|
return cachedMeasurer;
|
|
}
|
|
cachedTextMeasurer.clear = () => instanceMap.clear();
|
|
function measureTextSegments(textSegments, defaultFont) {
|
|
let currentLine = { segments: [], width: 0, height: 0, ascent: 0, descent: 0 };
|
|
const lineMetrics = [currentLine];
|
|
for (const segment of textSegments) {
|
|
const {
|
|
text: text2,
|
|
fontSize = defaultFont.fontSize,
|
|
fontStyle = defaultFont.fontStyle,
|
|
fontWeight: fontWeight2 = defaultFont.fontWeight,
|
|
fontFamily = defaultFont.fontFamily,
|
|
...rest
|
|
} = segment;
|
|
const font3 = { fontSize, fontStyle, fontWeight: fontWeight2, fontFamily };
|
|
const measurer3 = cachedTextMeasurer(font3);
|
|
const textLines = toTextString(text2).split(LineSplitter);
|
|
for (let i = 0; i < textLines.length; i++) {
|
|
const textLine = textLines[i];
|
|
const textMetrics = measurer3.measureText(textLine);
|
|
if (i > 0) {
|
|
currentLine = { segments: [], width: 0, height: 0, ascent: 0, descent: 0 };
|
|
lineMetrics.push(currentLine);
|
|
}
|
|
if (textLine) {
|
|
currentLine.width += textMetrics.width;
|
|
currentLine.ascent = Math.max(currentLine.ascent, textMetrics.ascent);
|
|
currentLine.descent = Math.max(currentLine.descent, textMetrics.descent);
|
|
currentLine.height = Math.max(currentLine.height, currentLine.ascent + currentLine.descent);
|
|
currentLine.segments.push({ ...font3, ...rest, text: textLine, textMetrics });
|
|
}
|
|
}
|
|
}
|
|
let maxWidth = 0;
|
|
let totalHeight = 0;
|
|
for (const line of lineMetrics) {
|
|
maxWidth = Math.max(maxWidth, line.width);
|
|
totalHeight += line.height;
|
|
}
|
|
return { width: maxWidth, height: totalHeight, lineMetrics };
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/dom/domElements.ts
|
|
function createElement(tagName, className, style2) {
|
|
const element2 = getDocument().createElement(tagName);
|
|
if (typeof className === "object") {
|
|
style2 = className;
|
|
className = void 0;
|
|
}
|
|
if (className) {
|
|
for (const name of className.split(" ")) {
|
|
element2.classList.add(name);
|
|
}
|
|
}
|
|
if (style2) {
|
|
Object.assign(element2.style, style2);
|
|
}
|
|
return element2;
|
|
}
|
|
function createSvgElement(elementName) {
|
|
return getDocument().createElementNS("http://www.w3.org/2000/svg", elementName);
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/dom/domDownload.ts
|
|
function downloadUrl(dataUrl, fileName) {
|
|
const body = getDocument("body");
|
|
const element2 = createElement("a", { display: "none" });
|
|
element2.href = dataUrl;
|
|
element2.download = fileName;
|
|
body.appendChild(element2);
|
|
element2.click();
|
|
setTimeout(() => element2.remove());
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/text/textWrapper.ts
|
|
function shouldHideOverflow(clippedResult, options) {
|
|
return options.overflow === "hide" && clippedResult.some(isTextTruncated);
|
|
}
|
|
function wrapTextOrSegments(input, options) {
|
|
return isArray(input) ? wrapTextSegments(input, options) : wrapLines(toTextString(input), options).join("\n");
|
|
}
|
|
function wrapText(text2, options) {
|
|
return wrapLines(text2, options).join("\n");
|
|
}
|
|
function wrapLines(text2, options) {
|
|
return textWrap(text2, options);
|
|
}
|
|
function truncateLine(text2, measurer3, maxWidth, ellipsisForce) {
|
|
const ellipsisWidth = measurer3.textWidth(EllipsisChar);
|
|
let estimatedWidth = 0;
|
|
let i = 0;
|
|
for (; i < text2.length; i++) {
|
|
const charWidth = measurer3.textWidth(text2.charAt(i));
|
|
if (estimatedWidth + charWidth > maxWidth)
|
|
break;
|
|
estimatedWidth += charWidth;
|
|
}
|
|
if (text2.length === i && (!ellipsisForce || estimatedWidth + ellipsisWidth <= maxWidth)) {
|
|
return ellipsisForce ? appendEllipsis(text2) : text2;
|
|
}
|
|
text2 = text2.slice(0, i).trimEnd();
|
|
while (text2.length && measurer3.textWidth(text2) + ellipsisWidth > maxWidth) {
|
|
text2 = text2.slice(0, -1).trimEnd();
|
|
}
|
|
return appendEllipsis(text2);
|
|
}
|
|
function textWrap(text2, options, widthOffset = 0) {
|
|
const lines = text2.split(LineSplitter);
|
|
const measurer3 = cachedTextMeasurer(options.font);
|
|
const result = [];
|
|
if (options.textWrap === "never") {
|
|
for (const line of lines) {
|
|
const truncatedLine = truncateLine(line.trimEnd(), measurer3, Math.max(0, options.maxWidth - widthOffset));
|
|
if (!truncatedLine)
|
|
break;
|
|
result.push(truncatedLine);
|
|
widthOffset = 0;
|
|
}
|
|
return shouldHideOverflow(result, options) ? [] : result;
|
|
}
|
|
const wrapHyphenate = options.textWrap === "hyphenate";
|
|
const wrapOnSpace = options.textWrap == null || options.textWrap === "on-space";
|
|
for (const untrimmedLine of lines) {
|
|
let line = untrimmedLine.trimEnd();
|
|
if (line === "") {
|
|
result.push(line);
|
|
continue;
|
|
}
|
|
let i = 0;
|
|
let estimatedWidth = 0;
|
|
let lastSpaceIndex = 0;
|
|
if (!result.length) {
|
|
estimatedWidth = widthOffset;
|
|
}
|
|
while (i < line.length) {
|
|
const char = line.charAt(i);
|
|
if (char === " ") {
|
|
lastSpaceIndex = i;
|
|
}
|
|
estimatedWidth += measurer3.textWidth(char);
|
|
if (estimatedWidth > options.maxWidth) {
|
|
if (i === 0) {
|
|
line = "";
|
|
break;
|
|
}
|
|
let actualWidth = measurer3.textWidth(line.slice(0, i + 1));
|
|
if (!result.length) {
|
|
actualWidth += widthOffset;
|
|
}
|
|
if (actualWidth <= options.maxWidth) {
|
|
estimatedWidth = actualWidth;
|
|
i++;
|
|
continue;
|
|
}
|
|
if (lastSpaceIndex) {
|
|
const nextWord = getWordAt(line, lastSpaceIndex + 1);
|
|
const textWidth = measurer3.textWidth(nextWord);
|
|
if (textWidth <= options.maxWidth) {
|
|
result.push(line.slice(0, lastSpaceIndex).trimEnd());
|
|
line = line.slice(lastSpaceIndex).trimStart();
|
|
i = 0;
|
|
estimatedWidth = 0;
|
|
lastSpaceIndex = 0;
|
|
continue;
|
|
} else if (wrapOnSpace && textWidth > options.maxWidth) {
|
|
result.push(
|
|
line.slice(0, lastSpaceIndex).trimEnd(),
|
|
truncateLine(line.slice(lastSpaceIndex).trimStart(), measurer3, options.maxWidth, true)
|
|
);
|
|
}
|
|
} else if (wrapOnSpace) {
|
|
const newLine2 = truncateLine(line, measurer3, options.maxWidth, true);
|
|
if (newLine2) {
|
|
result.push(newLine2);
|
|
}
|
|
}
|
|
if (wrapOnSpace) {
|
|
line = "";
|
|
break;
|
|
}
|
|
const postfix = wrapHyphenate ? "-" : "";
|
|
let newLine = line.slice(0, i).trim();
|
|
while (newLine.length && measurer3.textWidth(newLine + postfix) > options.maxWidth) {
|
|
newLine = newLine.slice(0, -1).trimEnd();
|
|
}
|
|
if (newLine && newLine !== TrimEdgeGuard) {
|
|
result.push(newLine + postfix);
|
|
} else {
|
|
line = "";
|
|
break;
|
|
}
|
|
line = line.slice(newLine.length).trimStart();
|
|
i = -1;
|
|
estimatedWidth = 0;
|
|
lastSpaceIndex = 0;
|
|
}
|
|
i++;
|
|
}
|
|
if (line) {
|
|
result.push(line);
|
|
}
|
|
}
|
|
avoidOrphans(result, measurer3, options);
|
|
const clippedResult = clipLines(result, measurer3, options);
|
|
return shouldHideOverflow(clippedResult, options) ? [] : clippedResult;
|
|
}
|
|
function getWordAt(text2, position) {
|
|
const nextSpaceIndex = text2.indexOf(" ", position);
|
|
return nextSpaceIndex === -1 ? text2.slice(position) : text2.slice(position, nextSpaceIndex);
|
|
}
|
|
function clipLines(lines, measurer3, options) {
|
|
if (!isFiniteNumber(options.maxHeight)) {
|
|
return lines;
|
|
}
|
|
const { height: height2, lineMetrics } = measurer3.measureLines(lines);
|
|
if (height2 <= options.maxHeight) {
|
|
return lines;
|
|
}
|
|
for (let i = 0, cumulativeHeight = 0; i < lineMetrics.length; i++) {
|
|
cumulativeHeight += lineMetrics[i].height;
|
|
if (cumulativeHeight > options.maxHeight) {
|
|
if (options.overflow === "hide" || i === 0)
|
|
return [];
|
|
const clippedResults = lines.slice(0, i);
|
|
const lastLine = clippedResults.pop();
|
|
return clippedResults.concat(
|
|
isTextTruncated(lastLine) ? lastLine : truncateLine(lastLine, measurer3, options.maxWidth, true)
|
|
);
|
|
}
|
|
}
|
|
return lines;
|
|
}
|
|
function avoidOrphans(lines, measurer3, options) {
|
|
if (options.avoidOrphans === false || lines.length < 2)
|
|
return;
|
|
const { length: length2 } = lines;
|
|
const lastLine = lines[length2 - 1];
|
|
const beforeLast = lines[length2 - 2];
|
|
if (beforeLast.length < lastLine.length)
|
|
return;
|
|
const lastSpaceIndex = beforeLast.lastIndexOf(" ");
|
|
if (lastSpaceIndex === -1 || lastSpaceIndex === beforeLast.indexOf(" ") || lastLine.includes(" "))
|
|
return;
|
|
const lastWord = beforeLast.slice(lastSpaceIndex + 1);
|
|
if (measurer3.textWidth(lastLine + lastWord) <= options.maxWidth) {
|
|
lines[length2 - 2] = beforeLast.slice(0, lastSpaceIndex);
|
|
lines[length2 - 1] = lastWord + " " + lastLine;
|
|
}
|
|
}
|
|
function wrapTextSegments(textSegments, options) {
|
|
const { maxHeight = Infinity } = options;
|
|
const result = [];
|
|
let lineWidth = 0;
|
|
let totalHeight = 0;
|
|
function truncateLastSegment() {
|
|
const lastSegment = result.pop();
|
|
if (!lastSegment)
|
|
return;
|
|
const measurer3 = cachedTextMeasurer(lastSegment);
|
|
const truncatedText = truncateLine(lastSegment.text, measurer3, options.maxWidth, true);
|
|
const textMetrics = measurer3.measureText(truncatedText);
|
|
result.push({ ...lastSegment, text: truncatedText, textMetrics });
|
|
}
|
|
for (const { width: width2, height: height2, segments } of measureTextSegments(textSegments, options.font).lineMetrics) {
|
|
if (totalHeight + height2 > maxHeight) {
|
|
if (result.length) {
|
|
truncateLastSegment();
|
|
}
|
|
break;
|
|
}
|
|
if (lineWidth + width2 <= options.maxWidth) {
|
|
lineWidth += width2;
|
|
totalHeight += height2;
|
|
result.push(...segments);
|
|
continue;
|
|
}
|
|
for (const segment of segments) {
|
|
if (lineWidth + segment.textMetrics.width <= options.maxWidth) {
|
|
lineWidth += segment.textMetrics.width;
|
|
result.push(segment);
|
|
continue;
|
|
}
|
|
const measurer3 = cachedTextMeasurer(segment);
|
|
const guardedText = guardTextEdges(segment.text);
|
|
const wrapOptions = { ...options, font: segment, maxHeight: maxHeight - totalHeight };
|
|
let wrappedLines = textWrap(guardedText, { ...wrapOptions, overflow: "hide" }, lineWidth);
|
|
if (wrappedLines.length === 0) {
|
|
if (options.textWrap === "never") {
|
|
wrappedLines = textWrap(guardedText, wrapOptions, lineWidth);
|
|
} else {
|
|
wrappedLines = textWrap(guardedText, wrapOptions);
|
|
const lastSegment = result.at(-1);
|
|
if (lastSegment) {
|
|
lastSegment.text += "\n";
|
|
lineWidth = 0;
|
|
}
|
|
}
|
|
}
|
|
if (wrappedLines.length === 0) {
|
|
truncateLastSegment();
|
|
break;
|
|
}
|
|
const truncationIndex = wrappedLines.findIndex(isTextTruncated);
|
|
if (truncationIndex !== -1) {
|
|
wrappedLines = wrappedLines.slice(0, truncationIndex + 1);
|
|
}
|
|
const lastLine = wrappedLines.at(-1);
|
|
for (const wrappedLine of wrappedLines) {
|
|
const cleanLine = unguardTextEdges(wrappedLine);
|
|
const textMetrics = measurer3.measureText(cleanLine);
|
|
const subSegment = { ...segment, text: cleanLine, textMetrics };
|
|
if (wrappedLine === lastLine) {
|
|
lineWidth += textMetrics.width;
|
|
} else {
|
|
subSegment.text += "\n";
|
|
lineWidth = 0;
|
|
}
|
|
totalHeight += textMetrics.height;
|
|
result.push(subSegment);
|
|
}
|
|
if (truncationIndex !== -1)
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/data/visibleRange.ts
|
|
function rescaleVisibleRange(visibleRange, [s0, s1], [d0, d1]) {
|
|
const dr = d1 - d0;
|
|
const vr = s1 - s0;
|
|
const vd0 = s0 + vr * visibleRange[0];
|
|
const vd1 = s0 + vr * visibleRange[1];
|
|
return [(vd0 - d0) / dr, (vd1 - d0) / dr];
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/time/time/duration.ts
|
|
var durationSecond = 1e3;
|
|
var durationMinute = durationSecond * 60;
|
|
var durationHour = durationMinute * 60;
|
|
var durationDay = durationHour * 24;
|
|
var durationWeek = durationDay * 7;
|
|
var durationMonth = durationDay * 30;
|
|
var durationYear = durationDay * 365;
|
|
|
|
// packages/ag-charts-core/src/utils/time/time/encoding.ts
|
|
var tzOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset() * durationMinute;
|
|
var unitEncoding = {
|
|
millisecond: {
|
|
milliseconds: 1,
|
|
hierarchy: "day",
|
|
encode(date2) {
|
|
return date2.getTime();
|
|
},
|
|
decode(encoded) {
|
|
return new Date(encoded);
|
|
}
|
|
},
|
|
second: {
|
|
milliseconds: durationSecond,
|
|
hierarchy: "day",
|
|
encode(date2, utc) {
|
|
const offset = utc ? 0 : tzOffset;
|
|
return Math.floor((date2.getTime() - offset) / durationSecond);
|
|
},
|
|
decode(encoded, utc) {
|
|
const offset = utc ? 0 : tzOffset;
|
|
return new Date(offset + encoded * durationSecond);
|
|
}
|
|
},
|
|
minute: {
|
|
milliseconds: durationMinute,
|
|
hierarchy: "day",
|
|
encode(date2, utc) {
|
|
const offset = utc ? 0 : tzOffset;
|
|
return Math.floor((date2.getTime() - offset) / durationMinute);
|
|
},
|
|
decode(encoded, utc) {
|
|
const offset = utc ? 0 : tzOffset;
|
|
return new Date(offset + encoded * durationMinute);
|
|
}
|
|
},
|
|
hour: {
|
|
milliseconds: durationHour,
|
|
hierarchy: "day",
|
|
encode(date2, utc) {
|
|
const offset = utc ? 0 : tzOffset;
|
|
return Math.floor((date2.getTime() - offset) / durationHour);
|
|
},
|
|
decode(encoded, utc) {
|
|
const offset = utc ? 0 : tzOffset;
|
|
return new Date(offset + encoded * durationHour);
|
|
}
|
|
},
|
|
day: {
|
|
milliseconds: durationDay,
|
|
hierarchy: "month",
|
|
encode(date2, utc) {
|
|
const tzOffsetMs2 = utc ? 0 : date2.getTimezoneOffset() * durationMinute;
|
|
return Math.floor((date2.getTime() - tzOffsetMs2) / durationDay);
|
|
},
|
|
decode(encoded, utc) {
|
|
let d;
|
|
if (utc) {
|
|
d = /* @__PURE__ */ new Date(0);
|
|
d.setUTCDate(d.getUTCDate() + encoded);
|
|
d.setUTCHours(0, 0, 0, 0);
|
|
} else {
|
|
d = new Date(1970, 0, 1);
|
|
d.setDate(d.getDate() + encoded);
|
|
}
|
|
return d;
|
|
}
|
|
},
|
|
month: {
|
|
milliseconds: durationMonth,
|
|
hierarchy: "year",
|
|
encode(date2, utc) {
|
|
if (utc) {
|
|
return date2.getUTCFullYear() * 12 + date2.getUTCMonth();
|
|
} else {
|
|
return date2.getFullYear() * 12 + date2.getMonth();
|
|
}
|
|
},
|
|
decode(encoded, utc) {
|
|
if (utc) {
|
|
const year = Math.floor(encoded / 12);
|
|
const m = encoded - year * 12;
|
|
return new Date(Date.UTC(year, m, 1));
|
|
} else {
|
|
const y = Math.floor(encoded / 12);
|
|
const month = encoded - y * 12;
|
|
return new Date(y, month, 1);
|
|
}
|
|
}
|
|
},
|
|
year: {
|
|
milliseconds: durationYear,
|
|
hierarchy: void 0,
|
|
encode(date2, utc) {
|
|
if (utc) {
|
|
return date2.getUTCFullYear();
|
|
} else {
|
|
return date2.getFullYear();
|
|
}
|
|
},
|
|
decode(encoded, utc) {
|
|
let d;
|
|
if (utc) {
|
|
d = /* @__PURE__ */ new Date();
|
|
d.setUTCFullYear(encoded);
|
|
d.setUTCMonth(0, 1);
|
|
d.setUTCHours(0, 0, 0, 0);
|
|
} else {
|
|
d = new Date(encoded, 0, 1, 0, 0, 0, 0);
|
|
}
|
|
return d;
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-core/src/utils/time/time/range.ts
|
|
function timeInterval(interval) {
|
|
return typeof interval === "string" ? { unit: interval, step: 1, epoch: void 0, utc: false } : {
|
|
unit: interval.unit,
|
|
step: interval.step ?? 1,
|
|
epoch: interval.epoch,
|
|
utc: interval.utc ?? false
|
|
};
|
|
}
|
|
function getOffset(unit, step, epoch, utc) {
|
|
if (epoch == null)
|
|
return 0;
|
|
const encoding = unitEncoding[unit];
|
|
return Math.floor(encoding.encode(new Date(epoch), utc)) % step;
|
|
}
|
|
function encode(d, unit, step, utc, offset) {
|
|
const encoding = unitEncoding[unit];
|
|
return Math.floor((encoding.encode(new Date(d), utc) - offset) / step);
|
|
}
|
|
function decode(encoded, unit, step, utc, offset) {
|
|
const encoding = unitEncoding[unit];
|
|
return encoding.decode(encoded * step + offset, utc);
|
|
}
|
|
function encodingFloor(date2, unit, step, utc, offset) {
|
|
const d = new Date(date2);
|
|
const e = encode(d, unit, step, utc, offset);
|
|
return decode(e, unit, step, utc, offset);
|
|
}
|
|
function encodingCeil(date2, unit, step, utc, offset) {
|
|
const d = new Date(Number(date2) - 1);
|
|
const e = encode(d, unit, step, utc, offset);
|
|
return decode(e + 1, unit, step, utc, offset);
|
|
}
|
|
function intervalFloor(interval, date2) {
|
|
const { unit, step, epoch, utc } = timeInterval(interval);
|
|
const offset = getOffset(unit, step, epoch, utc);
|
|
return encodingFloor(date2, unit, step, utc, offset);
|
|
}
|
|
function intervalCeil(interval, date2) {
|
|
const { unit, step, epoch, utc } = timeInterval(interval);
|
|
const offset = getOffset(unit, step, epoch, utc);
|
|
return encodingCeil(date2, unit, step, utc, offset);
|
|
}
|
|
function intervalPrevious(interval, date2) {
|
|
const { unit, step, epoch, utc } = timeInterval(interval);
|
|
const offset = getOffset(unit, step, epoch, utc);
|
|
return decode(
|
|
encode(encodingCeil(date2, unit, step, utc, offset), unit, step, utc, offset) - 1,
|
|
unit,
|
|
step,
|
|
utc,
|
|
offset
|
|
);
|
|
}
|
|
function intervalNext(interval, date2) {
|
|
const { unit, step, epoch, utc } = timeInterval(interval);
|
|
const offset = getOffset(unit, step, epoch, utc);
|
|
return decode(
|
|
encode(encodingFloor(date2, unit, step, utc, offset), unit, step, utc, offset) + 1,
|
|
unit,
|
|
step,
|
|
utc,
|
|
offset
|
|
);
|
|
}
|
|
function intervalExtent(start2, stop, visibleRange) {
|
|
if (start2.valueOf() > stop.valueOf()) {
|
|
[start2, stop] = [stop, start2];
|
|
if (visibleRange != null) {
|
|
visibleRange = [1 - visibleRange[1], 1 - visibleRange[0]];
|
|
}
|
|
}
|
|
if (visibleRange != null) {
|
|
const delta5 = stop.valueOf() - start2.valueOf();
|
|
const t0 = start2.valueOf();
|
|
start2 = new Date(t0 + visibleRange[0] * delta5);
|
|
stop = new Date(t0 + visibleRange[1] * delta5);
|
|
}
|
|
return [new Date(start2), new Date(stop)];
|
|
}
|
|
function rangeData(interval, start2, stop, { extend = false, visibleRange = [0, 1], limit, defaultAlignment = "start" } = {}) {
|
|
const params = timeInterval(interval);
|
|
const { unit, step, utc } = params;
|
|
let epoch;
|
|
if (params.epoch != null) {
|
|
epoch = params.epoch;
|
|
} else if (defaultAlignment === "interval") {
|
|
epoch = void 0;
|
|
} else if (start2.valueOf() > stop.valueOf()) {
|
|
epoch = stop;
|
|
} else {
|
|
epoch = start2;
|
|
}
|
|
const offset = getOffset(params.unit, params.step, epoch, params.utc);
|
|
let [d0, d1] = intervalExtent(start2, stop, visibleRange);
|
|
d0 = extend ? encodingFloor(d0, unit, step, utc, offset) : encodingCeil(d0, unit, step, utc, offset);
|
|
d1 = extend ? encodingCeil(d1, unit, step, utc, offset) : encodingFloor(d1, unit, step, utc, offset);
|
|
const e0 = encode(d0, unit, step, utc, offset);
|
|
let e1 = encode(d1, unit, step, utc, offset);
|
|
if (limit != null && e1 - e0 > limit) {
|
|
e1 = e0 + limit;
|
|
}
|
|
return {
|
|
range: [e0, e1],
|
|
unit,
|
|
step,
|
|
utc,
|
|
offset
|
|
};
|
|
}
|
|
function intervalRangeCount(interval, start2, stop, params) {
|
|
const {
|
|
range: [e0, e1]
|
|
} = rangeData(interval, start2, stop, params);
|
|
return Math.abs(e1 - e0);
|
|
}
|
|
function intervalRange(interval, start2, stop, params) {
|
|
const {
|
|
range: [e0, e1],
|
|
unit,
|
|
step,
|
|
utc,
|
|
offset
|
|
} = rangeData(interval, start2, stop, params);
|
|
const values = [];
|
|
for (let e = e0; e <= e1; e += 1) {
|
|
const d = decode(e, unit, step, utc, offset);
|
|
values.push(d);
|
|
}
|
|
return values;
|
|
}
|
|
function intervalRangeNumeric(interval, start2, stop, params) {
|
|
const {
|
|
range: [e0, e1],
|
|
unit,
|
|
step,
|
|
utc,
|
|
offset
|
|
} = rangeData(interval, start2, stop, params);
|
|
const count = Math.max(0, e1 - e0 + 1);
|
|
const encodedValues = new Array(count);
|
|
for (let i = 0; i < count; i++) {
|
|
encodedValues[i] = e0 + i;
|
|
}
|
|
return {
|
|
encodedValues,
|
|
encodingParams: { unit, step, utc, offset }
|
|
};
|
|
}
|
|
function decodeIntervalValue(encoded, encodingParams) {
|
|
return decode(encoded, encodingParams.unit, encodingParams.step, encodingParams.utc, encodingParams.offset);
|
|
}
|
|
var tzOffsetMs = (/* @__PURE__ */ new Date()).getTimezoneOffset() * 6e4;
|
|
var DURATION_SECOND = 1e3;
|
|
var DURATION_MINUTE = 6e4;
|
|
var DURATION_HOUR = 36e5;
|
|
function encodedToTimestamp(encoded, encodingParams) {
|
|
const { unit, step, utc, offset } = encodingParams;
|
|
const rawEncoded = encoded * step + offset;
|
|
switch (unit) {
|
|
case "millisecond":
|
|
return rawEncoded;
|
|
case "second": {
|
|
const tzOffset2 = utc ? 0 : tzOffsetMs;
|
|
return tzOffset2 + rawEncoded * DURATION_SECOND;
|
|
}
|
|
case "minute": {
|
|
const tzOffset2 = utc ? 0 : tzOffsetMs;
|
|
return tzOffset2 + rawEncoded * DURATION_MINUTE;
|
|
}
|
|
case "hour": {
|
|
const tzOffset2 = utc ? 0 : tzOffsetMs;
|
|
return tzOffset2 + rawEncoded * DURATION_HOUR;
|
|
}
|
|
default: {
|
|
const encoding = unitEncoding[unit];
|
|
return encoding.decode(rawEncoded, utc).valueOf();
|
|
}
|
|
}
|
|
}
|
|
function intervalRangeStartIndex(interval, start2, stop, { extend, visibleRange, limit, defaultAlignment } = {}) {
|
|
const {
|
|
range: [s]
|
|
} = rangeData(interval, start2, stop, { extend, visibleRange, limit, defaultAlignment });
|
|
const {
|
|
range: [s0]
|
|
} = rangeData(interval, start2, stop, { extend, limit, defaultAlignment });
|
|
return s - s0;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/time/time/index.ts
|
|
function intervalUnit(interval) {
|
|
return typeof interval === "string" ? interval : interval.unit;
|
|
}
|
|
function intervalStep(interval) {
|
|
return typeof interval === "string" ? 1 : interval.step ?? 1;
|
|
}
|
|
function intervalEpoch(interval) {
|
|
return typeof interval === "string" ? void 0 : interval.epoch;
|
|
}
|
|
function intervalHierarchy(interval) {
|
|
return unitEncoding[intervalUnit(interval)].hierarchy;
|
|
}
|
|
function intervalMilliseconds(interval) {
|
|
const step = intervalStep(interval);
|
|
return step * unitEncoding[intervalUnit(interval)].milliseconds;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/time/ticks.ts
|
|
var tInterval = (timeInterval3, step) => ({
|
|
duration: intervalMilliseconds(timeInterval3) * step,
|
|
timeInterval: timeInterval3,
|
|
step
|
|
});
|
|
var TickIntervals = [
|
|
tInterval({ unit: "second" }, 1),
|
|
tInterval({ unit: "second" }, 5),
|
|
tInterval({ unit: "second" }, 15),
|
|
tInterval({ unit: "second" }, 30),
|
|
tInterval({ unit: "minute" }, 1),
|
|
tInterval({ unit: "minute" }, 5),
|
|
tInterval({ unit: "minute" }, 15),
|
|
tInterval({ unit: "minute" }, 30),
|
|
tInterval({ unit: "hour" }, 1),
|
|
tInterval({ unit: "hour" }, 3),
|
|
tInterval({ unit: "hour" }, 6),
|
|
tInterval({ unit: "hour" }, 12),
|
|
tInterval({ unit: "day" }, 1),
|
|
tInterval({ unit: "day" }, 2),
|
|
tInterval({ unit: "day", step: 7 }, 1),
|
|
tInterval({ unit: "day", step: 7 }, 2),
|
|
tInterval({ unit: "day", step: 7 }, 3),
|
|
tInterval({ unit: "month" }, 1),
|
|
tInterval({ unit: "month" }, 2),
|
|
tInterval({ unit: "month" }, 3),
|
|
tInterval({ unit: "month" }, 4),
|
|
tInterval({ unit: "month" }, 6),
|
|
tInterval({ unit: "year" }, 1)
|
|
];
|
|
var TickMultipliers = [1, 2, 5, 10];
|
|
function isCloseToInteger(n, delta5) {
|
|
return Math.abs(Math.round(n) - n) < delta5;
|
|
}
|
|
function countTicks(d0, d1, step) {
|
|
const extent2 = Math.abs(d1 - d0);
|
|
return extent2 >= step ? Math.abs(d1 - d0) / step + 1 : 1;
|
|
}
|
|
function createTicks(start2, stop, count, minCount, maxCount, visibleRange) {
|
|
if (start2 === stop)
|
|
return { ticks: [start2], count: 1, firstTickIndex: 0 };
|
|
if (count < 2)
|
|
return { ticks: [start2, stop], count: 2, firstTickIndex: 0 };
|
|
const step = tickStep(start2, stop, count, minCount, maxCount);
|
|
if (!Number.isFinite(step))
|
|
return { ticks: [], count: 0, firstTickIndex: void 0 };
|
|
let d0 = start2;
|
|
let d1 = stop;
|
|
if (!isCloseToInteger(d0 / step, 1e-12)) {
|
|
d0 = Math.ceil(d0 / step) * step;
|
|
}
|
|
if (!isCloseToInteger(d1 / step, 1e-12)) {
|
|
d1 = Math.floor(d1 / step) * step;
|
|
}
|
|
if (visibleRange != null) {
|
|
visibleRange = rescaleVisibleRange(visibleRange, [start2, stop], [d0, d1]);
|
|
}
|
|
const { ticks } = range(d0, d1, step, visibleRange);
|
|
const firstTick = ticks.at(0);
|
|
return {
|
|
ticks,
|
|
count: countTicks(d0, d1, step),
|
|
firstTickIndex: firstTick == null ? void 0 : Math.round((firstTick - d0) / step)
|
|
};
|
|
}
|
|
var minPrimaryTickRatio = Math.floor(2 * durationWeek / durationMonth * 10) / 10;
|
|
function isPrimaryTickInterval({ timeInterval: timeInterval3, step }) {
|
|
const milliseconds = intervalMilliseconds(timeInterval3) * step;
|
|
const hierarchy = intervalHierarchy(timeInterval3);
|
|
const hierarchyMilliseconds = hierarchy ? intervalMilliseconds(hierarchy) : void 0;
|
|
return milliseconds <= (hierarchyMilliseconds ?? Infinity) * minPrimaryTickRatio;
|
|
}
|
|
function defaultEpoch(timeInterval3, { weekStart }) {
|
|
if (timeInterval3.unit === "day" && timeInterval3.step === 7) {
|
|
return weekStart;
|
|
}
|
|
}
|
|
function getTickTimeInterval(start2, stop, count, minCount, maxCount, {
|
|
weekStart,
|
|
primaryOnly = false,
|
|
targetInterval
|
|
}) {
|
|
if (count <= 0)
|
|
return;
|
|
const target = targetInterval ?? Math.abs(stop - start2) / Math.max(count, 1);
|
|
const i0 = TickIntervals.findLast((t) => (!primaryOnly || isPrimaryTickInterval(t)) && target > t.duration);
|
|
const i1 = TickIntervals.find((t) => (!primaryOnly || isPrimaryTickInterval(t)) && target <= t.duration);
|
|
if (i0 == null) {
|
|
const step2 = Math.max(tickStep(start2, stop, count, minCount, maxCount), 1);
|
|
return { unit: "millisecond", step: step2 };
|
|
} else if (i1 == null) {
|
|
const step2 = targetInterval == null ? tickStep(start2 / durationYear, stop / durationYear, count, minCount, maxCount) : 1;
|
|
return { unit: "year", step: step2 };
|
|
}
|
|
const { timeInterval: timeInterval3, step } = target - i0.duration < i1.duration - target ? i0 : i1;
|
|
return {
|
|
unit: timeInterval3.unit,
|
|
step: intervalStep(timeInterval3) * step,
|
|
epoch: defaultEpoch(timeInterval3, { weekStart })
|
|
};
|
|
}
|
|
function tickStep(start2, end3, count, minCount = 0, maxCount = Infinity) {
|
|
if (start2 === end3) {
|
|
return clamp(1, minCount, maxCount);
|
|
} else if (count < 1) {
|
|
return Number.NaN;
|
|
}
|
|
const extent2 = Math.abs(end3 - start2);
|
|
const step = 10 ** Math.floor(Math.log10(extent2 / count));
|
|
let m = Number.NaN, minDiff = Infinity, isInBounds = false;
|
|
for (const multiplier of TickMultipliers) {
|
|
const c = Math.ceil(extent2 / (multiplier * step));
|
|
const validBounds = c >= minCount && c <= maxCount;
|
|
if (isInBounds && !validBounds)
|
|
continue;
|
|
const diffCount = Math.abs(c - count);
|
|
if (minDiff > diffCount || isInBounds !== validBounds) {
|
|
isInBounds || (isInBounds = validBounds);
|
|
minDiff = diffCount;
|
|
m = multiplier;
|
|
}
|
|
}
|
|
return m * step;
|
|
}
|
|
function decimalPlaces(decimal) {
|
|
for (let i = decimal.length - 1; i >= 0; i -= 1) {
|
|
if (decimal[i] !== "0") {
|
|
return i + 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
function tickFormat(ticks, format) {
|
|
const options = parseNumberFormat(format ?? ",f");
|
|
if (options == null)
|
|
return;
|
|
if (options.precision == null || Number.isNaN(options.precision)) {
|
|
if (!options.type || "eEFgGnprs".includes(options.type)) {
|
|
options.precision = Math.max(
|
|
...ticks.map((x) => {
|
|
if (!Number.isFinite(x))
|
|
return 0;
|
|
const [integer, decimal] = x.toExponential((options.type ? 6 : 12) - 1).split(/[.e]/g);
|
|
return (integer !== "1" && integer !== "-1" ? 1 : 0) + decimalPlaces(decimal) + 1;
|
|
})
|
|
);
|
|
} else if ("f%".includes(options.type)) {
|
|
options.precision = Math.max(
|
|
...ticks.map((x) => {
|
|
if (!Number.isFinite(x) || x === 0)
|
|
return 0;
|
|
const l = Math.floor(Math.log10(Math.abs(x)));
|
|
const digits = options.type ? 6 : 12;
|
|
const decimal = x.toExponential(digits - 1).split(/[.e]/g)[1];
|
|
const decimalLength = decimalPlaces(decimal);
|
|
return Math.max(0, decimalLength - l);
|
|
})
|
|
);
|
|
}
|
|
}
|
|
const formatter2 = createNumberFormatter(options);
|
|
return (n) => formatter2(Number(n));
|
|
}
|
|
function range(start2, end3, step, visibleRange) {
|
|
if (!Number.isFinite(step) || step <= 0) {
|
|
return { ticks: [], count: 0, firstTickIndex: void 0 };
|
|
} else if (start2 === end3) {
|
|
return { ticks: [start2], count: 1, firstTickIndex: 0 };
|
|
}
|
|
const f = 10 ** countFractionDigits(step);
|
|
const d0 = Math.min(start2, end3);
|
|
const d1 = Math.max(start2, end3);
|
|
let vd0;
|
|
let vd1;
|
|
if (visibleRange != null && (visibleRange[0] !== 0 || visibleRange[1] !== 1)) {
|
|
const rangeExtent = end3 - start2;
|
|
const adjustedStart = start2 + rangeExtent * visibleRange[0];
|
|
const adjustedEnd = end3 - rangeExtent * (1 - visibleRange[1]);
|
|
vd0 = Math.min(adjustedStart, adjustedEnd);
|
|
vd1 = Math.max(adjustedStart, adjustedEnd);
|
|
} else {
|
|
vd0 = d0;
|
|
vd1 = d1;
|
|
}
|
|
vd0 = Math.floor(vd0 * f) / f;
|
|
vd1 = Math.ceil(vd1 * f) / f;
|
|
const ticks = [];
|
|
for (let i = 0; ; i += 1) {
|
|
const p = Math.round((d0 + step * i) * f) / f;
|
|
if (p > d1)
|
|
break;
|
|
if (p >= vd0 && p <= vd1) {
|
|
ticks.push(p);
|
|
}
|
|
}
|
|
const firstTick = ticks.at(0);
|
|
return {
|
|
ticks,
|
|
count: countTicks(d0, d1, step),
|
|
firstTickIndex: firstTick == null ? void 0 : Math.round((firstTick - d0) / step)
|
|
};
|
|
}
|
|
function isDenseInterval(count, availableRange) {
|
|
if (count >= availableRange) {
|
|
warnOnce(
|
|
`the configured interval results in more than 1 item per pixel, ignoring. Supply a larger interval or omit this configuration`
|
|
);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function niceTicksDomain(start2, end3) {
|
|
const extent2 = Math.abs(end3 - start2);
|
|
const step = 10 ** Math.floor(Math.log10(extent2));
|
|
let minError = Infinity, ticks = [start2, end3];
|
|
for (const multiplier of TickMultipliers) {
|
|
const m = multiplier * step;
|
|
const d0 = Math.floor(start2 / m) * m;
|
|
const d1 = Math.ceil(end3 / m) * m;
|
|
const error2 = 1 - extent2 / Math.abs(d1 - d0);
|
|
if (minError > error2) {
|
|
minError = error2;
|
|
ticks = [d0, d1];
|
|
}
|
|
}
|
|
return ticks;
|
|
}
|
|
function estimateTickCount(rangeExtent, zoomExtent, minSpacing, maxSpacing, defaultTickCount, defaultMinSpacing) {
|
|
if (rangeExtent <= 0) {
|
|
return { minTickCount: 0, maxTickCount: 0, tickCount: 0 };
|
|
}
|
|
defaultMinSpacing = Math.max(defaultMinSpacing, rangeExtent / (defaultTickCount + 1));
|
|
minSpacing ?? (minSpacing = defaultMinSpacing);
|
|
maxSpacing ?? (maxSpacing = rangeExtent);
|
|
if (minSpacing > maxSpacing) {
|
|
if (minSpacing === defaultMinSpacing) {
|
|
minSpacing = maxSpacing;
|
|
} else {
|
|
maxSpacing = minSpacing;
|
|
}
|
|
}
|
|
minSpacing = Math.max(minSpacing, 1);
|
|
const maxTickCount = Math.max(1, Math.floor(rangeExtent / (zoomExtent * minSpacing)));
|
|
const minTickCount = Math.min(maxTickCount, Math.ceil(rangeExtent / (zoomExtent * maxSpacing)));
|
|
const tickCount = clamp(minTickCount, Math.floor(defaultTickCount / zoomExtent), maxTickCount);
|
|
return { minTickCount, maxTickCount, tickCount };
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/time/timeFormatDefaults.ts
|
|
function dateToNumber(value) {
|
|
return value instanceof Date ? value.getTime() : value;
|
|
}
|
|
function lowestGranularityForInterval(interval) {
|
|
if (interval < durationSecond) {
|
|
return "millisecond";
|
|
} else if (interval < durationMinute) {
|
|
return "second";
|
|
} else if (interval < durationHour) {
|
|
return "minute";
|
|
} else if (interval < durationHour * 23) {
|
|
return "hour";
|
|
} else if (interval < 28 * durationDay) {
|
|
return "day";
|
|
} else if (interval < durationYear) {
|
|
return "month";
|
|
} else {
|
|
return "year";
|
|
}
|
|
}
|
|
function lowestGranularityUnitForTicks(ticks) {
|
|
if (ticks.length === 0) {
|
|
return "millisecond";
|
|
} else if (ticks.length === 1) {
|
|
return lowestGranularityUnitForValue(ticks[0]);
|
|
}
|
|
let minInterval = Infinity;
|
|
for (let i = 1; i < ticks.length; i++) {
|
|
minInterval = Math.min(minInterval, Math.abs(ticks[i].valueOf() - ticks[i - 1].valueOf()));
|
|
}
|
|
return lowestGranularityForInterval(minInterval);
|
|
}
|
|
function lowestGranularityUnitForValue(value) {
|
|
if (intervalFloor("second", value) < value) {
|
|
return "millisecond";
|
|
} else if (intervalFloor("minute", value) < value) {
|
|
return "second";
|
|
} else if (intervalFloor("hour", value) < value) {
|
|
return "minute";
|
|
} else if (intervalFloor("day", value) < value) {
|
|
return "hour";
|
|
} else if (intervalFloor("month", value) < value) {
|
|
return "day";
|
|
} else if (intervalFloor("year", value) < value) {
|
|
return "month";
|
|
}
|
|
return "year";
|
|
}
|
|
function dateTruncationForDomain(domain) {
|
|
const [d0, d1] = domain.length === 0 ? [0, 0] : findMinMax([domain[0].valueOf(), domain.at(-1).valueOf()]);
|
|
const startYear = new Date(d0).getFullYear();
|
|
const stopYear = new Date(d1).getFullYear();
|
|
if (startYear !== stopYear)
|
|
return;
|
|
const startMonth = new Date(d0).getMonth();
|
|
const stopMonth = new Date(d1).getMonth();
|
|
if (startMonth !== stopMonth)
|
|
return "year";
|
|
const startDate = new Date(d0).getDate();
|
|
const stopDate = new Date(d1).getDate();
|
|
if (startDate !== stopDate)
|
|
return "month";
|
|
return "day";
|
|
}
|
|
|
|
// packages/ag-charts-core/src/identity/idGenerator.ts
|
|
function createIdsGenerator() {
|
|
const idsCounter = /* @__PURE__ */ new Map();
|
|
return (name) => {
|
|
const counter = idsCounter.get(name);
|
|
if (counter) {
|
|
idsCounter.set(name, counter + 1);
|
|
return `${name}_${counter}`;
|
|
}
|
|
idsCounter.set(name, 1);
|
|
return name;
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/data/value.ts
|
|
function isStringObject(value) {
|
|
return value != null && Object.hasOwn(value, "toString") && isString(value.toString());
|
|
}
|
|
function isNumberObject(value) {
|
|
return value != null && Object.hasOwn(value, "valueOf") && isFiniteNumber(value.valueOf());
|
|
}
|
|
function isContinuous(value) {
|
|
return isFiniteNumber(value) || isValidDate(value) || isNumberObject(value);
|
|
}
|
|
function checkDatum(value, isContinuousScale) {
|
|
return value != null && (!isContinuousScale || isContinuous(value));
|
|
}
|
|
function transformIntegratedCategoryValue(value) {
|
|
if (isStringObject(value) && Object.hasOwn(value, "id")) {
|
|
return value.id;
|
|
}
|
|
return value;
|
|
}
|
|
function readIntegratedWrappedValue(value) {
|
|
if (isStringObject(value) && Object.hasOwn(value, "value")) {
|
|
return value.value;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/vector.ts
|
|
var vector_exports = {};
|
|
__export(vector_exports, {
|
|
add: () => add,
|
|
angle: () => angle,
|
|
apply: () => apply,
|
|
distance: () => distance,
|
|
distanceSquared: () => distanceSquared,
|
|
equal: () => equal,
|
|
from: () => from,
|
|
gradient: () => gradient,
|
|
intercept: () => intercept,
|
|
intersectAtX: () => intersectAtX,
|
|
intersectAtY: () => intersectAtY,
|
|
length: () => length,
|
|
lengthSquared: () => lengthSquared,
|
|
multiply: () => multiply,
|
|
normalized: () => normalized,
|
|
origin: () => origin,
|
|
required: () => required2,
|
|
rotate: () => rotate,
|
|
round: () => round,
|
|
sub: () => sub
|
|
});
|
|
function add(a, b) {
|
|
if (typeof b === "number") {
|
|
return { x: a.x + b, y: a.y + b };
|
|
}
|
|
return { x: a.x + b.x, y: a.y + b.y };
|
|
}
|
|
function sub(a, b) {
|
|
if (typeof b === "number") {
|
|
return { x: a.x - b, y: a.y - b };
|
|
}
|
|
return { x: a.x - b.x, y: a.y - b.y };
|
|
}
|
|
function multiply(a, b) {
|
|
if (typeof b === "number") {
|
|
return { x: a.x * b, y: a.y * b };
|
|
}
|
|
return { x: a.x * b.x, y: a.y * b.y };
|
|
}
|
|
function length(a) {
|
|
return Math.hypot(a.x, a.y);
|
|
}
|
|
function lengthSquared(a) {
|
|
return a.x * a.x + a.y * a.y;
|
|
}
|
|
function distance(a, b) {
|
|
return length(sub(a, b));
|
|
}
|
|
function distanceSquared(a, b) {
|
|
return lengthSquared(sub(a, b));
|
|
}
|
|
function normalized(a) {
|
|
const l = length(a);
|
|
return { x: a.x / l, y: a.y / l };
|
|
}
|
|
function angle(a, b) {
|
|
if (b == null)
|
|
return Math.atan2(a.y, a.x);
|
|
return Math.atan2(a.y, a.x) - Math.atan2(b.y, b.x);
|
|
}
|
|
function rotate(a, theta, b = origin()) {
|
|
const l = length(a);
|
|
return { x: b.x + l * Math.cos(theta), y: b.y + l * Math.sin(theta) };
|
|
}
|
|
function gradient(a, b, reflection) {
|
|
const dx2 = b.x - a.x;
|
|
const dy2 = reflection == null ? b.y - a.y : reflection - b.y - (reflection - a.y);
|
|
return dy2 / dx2;
|
|
}
|
|
function intercept(a, gradient2, reflection) {
|
|
const y = reflection == null ? a.y : reflection - a.y;
|
|
return y - gradient2 * a.x;
|
|
}
|
|
function intersectAtY(gradient2, coefficient, y = 0, reflection) {
|
|
return {
|
|
x: gradient2 === Infinity ? Infinity : (y - coefficient) / gradient2,
|
|
y: reflection == null ? y : reflection - y
|
|
};
|
|
}
|
|
function intersectAtX(gradient2, coefficient, x = 0, reflection) {
|
|
const y = gradient2 === Infinity ? Infinity : gradient2 * x + coefficient;
|
|
return { x, y: reflection == null ? y : reflection - y };
|
|
}
|
|
function round(a, decimals = 2) {
|
|
return { x: roundTo(a.x, decimals), y: roundTo(a.y, decimals) };
|
|
}
|
|
function equal(a, b) {
|
|
return a.x === b.x && a.y === b.y;
|
|
}
|
|
function from(a, b) {
|
|
if (typeof a === "number") {
|
|
return { x: a, y: b };
|
|
}
|
|
if ("currentX" in a) {
|
|
return { x: a.currentX, y: a.currentY };
|
|
}
|
|
if ("offsetWidth" in a) {
|
|
return { x: a.offsetWidth, y: a.offsetHeight };
|
|
}
|
|
if ("width" in a) {
|
|
return [
|
|
{ x: a.x, y: a.y },
|
|
{ x: a.x + a.width, y: a.y + a.height }
|
|
];
|
|
}
|
|
if ("x1" in a) {
|
|
return [
|
|
{ x: a.x1, y: a.y1 },
|
|
{ x: a.x2, y: a.y2 }
|
|
];
|
|
}
|
|
throw new Error(`Values can not be converted into a vector: [${JSON.stringify(a)}] [${b}]`);
|
|
}
|
|
function apply(a, b) {
|
|
a.x = b.x;
|
|
a.y = b.y;
|
|
return a;
|
|
}
|
|
function required2(a) {
|
|
return { x: a?.x ?? 0, y: a?.y ?? 0 };
|
|
}
|
|
function origin() {
|
|
return { x: 0, y: 0 };
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/vector4.ts
|
|
var vector4_exports = {};
|
|
__export(vector4_exports, {
|
|
bottomCenter: () => bottomCenter,
|
|
center: () => center,
|
|
clone: () => clone,
|
|
collides: () => collides,
|
|
end: () => end,
|
|
from: () => from2,
|
|
height: () => height,
|
|
normalise: () => normalise,
|
|
origin: () => origin2,
|
|
round: () => round2,
|
|
start: () => start,
|
|
topCenter: () => topCenter,
|
|
width: () => width
|
|
});
|
|
function start(a) {
|
|
return { x: a.x1, y: a.y1 };
|
|
}
|
|
function end(a) {
|
|
return { x: a.x2, y: a.y2 };
|
|
}
|
|
function topCenter(a) {
|
|
return { x: (a.x1 + a.x2) / 2, y: Math.min(a.y1, a.y2) };
|
|
}
|
|
function center(a) {
|
|
return { x: (a.x1 + a.x2) / 2, y: (a.y1 + a.y2) / 2 };
|
|
}
|
|
function bottomCenter(a) {
|
|
return { x: (a.x1 + a.x2) / 2, y: Math.max(a.y1, a.y2) };
|
|
}
|
|
function width(a) {
|
|
return Math.abs(a.x2 - a.x1);
|
|
}
|
|
function height(a) {
|
|
return Math.abs(a.y2 - a.y1);
|
|
}
|
|
function round2(a) {
|
|
return { x1: Math.round(a.x1), y1: Math.round(a.y1), x2: Math.round(a.x2), y2: Math.round(a.y2) };
|
|
}
|
|
function clone(a) {
|
|
return { x1: a.x1, y1: a.y1, x2: a.x2, y2: a.y2 };
|
|
}
|
|
function collides(a, b) {
|
|
const an = normalise(a);
|
|
const bn = normalise(b);
|
|
return an.x1 <= bn.x2 && an.x2 >= bn.x1 && an.y1 <= bn.y2 && an.y2 >= bn.y1;
|
|
}
|
|
function normalise(a) {
|
|
return {
|
|
x1: Math.min(a.x1, a.x2),
|
|
x2: Math.max(a.x1, a.x2),
|
|
y1: Math.min(a.y1, a.y2),
|
|
y2: Math.max(a.y1, a.y2)
|
|
};
|
|
}
|
|
function from2(a, b, c, d) {
|
|
if (typeof a === "number") {
|
|
return { x1: a, y1: b, x2: c, y2: d };
|
|
}
|
|
if ("width" in a) {
|
|
return normalise({
|
|
x1: a.x,
|
|
y1: a.y,
|
|
x2: a.x + a.width,
|
|
y2: a.y + a.height
|
|
});
|
|
}
|
|
throw new Error(`Values can not be converted into a vector4: [${JSON.stringify(a)}] [${b}] [${c}] [${d}]`);
|
|
}
|
|
function origin2() {
|
|
return { x1: 0, y1: 0, x2: 0, y2: 0 };
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/fill.ts
|
|
function isGradientFill(fill) {
|
|
return isObject(fill) && fill.type == "gradient";
|
|
}
|
|
function isGradientFillArray(fills) {
|
|
return isArray(fills) && fills.every(isGradientFill);
|
|
}
|
|
function isStringFillArray(fills) {
|
|
return isArray(fills) && fills.every((fill) => typeof fill === "string");
|
|
}
|
|
function isPatternFill(fill) {
|
|
return fill !== null && isObject(fill) && fill.type == "pattern";
|
|
}
|
|
function isImageFill(fill) {
|
|
return fill !== null && isObject(fill) && fill.type == "image";
|
|
}
|
|
function isGradientOrPatternFill(fill) {
|
|
return isGradientFill(fill) || isPatternFill(fill);
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/bezier.ts
|
|
function evaluateBezier(p0, p1, p2, p3, t) {
|
|
return (1 - t) ** 3 * p0 + 3 * (1 - t) ** 2 * t * p1 + 3 * (1 - t) * t ** 2 * p2 + t ** 3 * p3;
|
|
}
|
|
function solveBezier(p0, p1, p2, p3, value) {
|
|
if (value <= Math.min(p0, p3)) {
|
|
return p0 < p3 ? 0 : 1;
|
|
} else if (value >= Math.max(p0, p3)) {
|
|
return p0 < p3 ? 1 : 0;
|
|
}
|
|
let t0 = 0;
|
|
let t1 = 1;
|
|
let t = Number.NaN;
|
|
for (let i = 0; i < 12; i += 1) {
|
|
t = (t0 + t1) / 2;
|
|
const curveValue = evaluateBezier(p0, p1, p2, p3, t);
|
|
if (curveValue < value) {
|
|
t0 = t;
|
|
} else {
|
|
t1 = t;
|
|
}
|
|
}
|
|
return t;
|
|
}
|
|
function splitBezier2D(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y, t) {
|
|
const x01 = (1 - t) * p0x + t * p1x;
|
|
const y01 = (1 - t) * p0y + t * p1y;
|
|
const x12 = (1 - t) * p1x + t * p2x;
|
|
const y12 = (1 - t) * p1y + t * p2y;
|
|
const x23 = (1 - t) * p2x + t * p3x;
|
|
const y23 = (1 - t) * p2y + t * p3y;
|
|
const x012 = (1 - t) * x01 + t * x12;
|
|
const y012 = (1 - t) * y01 + t * y12;
|
|
const x123 = (1 - t) * x12 + t * x23;
|
|
const y123 = (1 - t) * y12 + t * y23;
|
|
const x0123 = (1 - t) * x012 + t * x123;
|
|
const y0123 = (1 - t) * y012 + t * y123;
|
|
return [
|
|
[
|
|
{ x: p0x, y: p0y },
|
|
{ x: x01, y: y01 },
|
|
{ x: x012, y: y012 },
|
|
{ x: x0123, y: y0123 }
|
|
],
|
|
[
|
|
{ x: x0123, y: y0123 },
|
|
{ x: x123, y: y123 },
|
|
{ x: x23, y: y23 },
|
|
{ x: p3x, y: p3y }
|
|
]
|
|
];
|
|
}
|
|
function calculateDerivativeExtrema(p0, p1, p2, p3) {
|
|
const a = -p0 + 3 * p1 - 3 * p2 + p3;
|
|
const b = 2 * (p0 - 2 * p1 + p2);
|
|
const c = -p0 + p1;
|
|
if (a === 0) {
|
|
if (b !== 0) {
|
|
const t = -c / b;
|
|
if (t > 0 && t < 1) {
|
|
return [t];
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
const discriminant = b * b - 4 * a * c;
|
|
if (discriminant >= 0) {
|
|
const sqrtDiscriminant = Math.sqrt(discriminant);
|
|
const t1 = (-b + sqrtDiscriminant) / (2 * a);
|
|
const t2 = (-b - sqrtDiscriminant) / (2 * a);
|
|
return [t1, t2].filter((t) => t > 0 && t < 1);
|
|
}
|
|
return [];
|
|
}
|
|
function bezier2DExtrema(cp0x, cp0y, cp1x, cp1y, cp2x, cp2y, cp3x, cp3y) {
|
|
const tx = calculateDerivativeExtrema(cp0x, cp1x, cp2x, cp3x);
|
|
const ty = calculateDerivativeExtrema(cp0y, cp1y, cp2y, cp3y);
|
|
return [...tx, ...ty];
|
|
}
|
|
function bezierCandidate(points, x, y) {
|
|
const midX = evaluateBezier(points[0].x, points[1].x, points[2].x, points[3].x, 0.5);
|
|
const midY = evaluateBezier(points[0].y, points[1].y, points[2].y, points[3].y, 0.5);
|
|
const distance2 = Math.hypot(midX - x, midY - y);
|
|
const minDistance = Math.min(
|
|
Math.hypot(points[0].x - x, points[0].y - y),
|
|
Math.hypot(points[1].x - x, points[1].y - y),
|
|
Math.hypot(points[2].x - x, points[2].y - y),
|
|
Math.hypot(points[3].x - x, points[3].y - y)
|
|
);
|
|
return { points, distance: distance2, minDistance };
|
|
}
|
|
function bezier2DDistance(cp0x, cp0y, cp1x, cp1y, cp2x, cp2y, cp3x, cp3y, x, y, precision = 1) {
|
|
const points0 = [
|
|
{ x: cp0x, y: cp0y },
|
|
{ x: cp1x, y: cp1y },
|
|
{ x: cp2x, y: cp2y },
|
|
{ x: cp3x, y: cp3y }
|
|
];
|
|
let queue = {
|
|
value: bezierCandidate(points0, x, y),
|
|
next: null
|
|
};
|
|
let bestResult;
|
|
while (queue != null) {
|
|
const { points, distance: distance2, minDistance } = queue.value;
|
|
queue = queue.next;
|
|
if (bestResult == null || distance2 < bestResult.distance) {
|
|
bestResult = { distance: distance2, minDistance };
|
|
}
|
|
if (bestResult != null && bestResult.distance - minDistance <= precision) {
|
|
continue;
|
|
}
|
|
const [leftPoints, rightPoints] = splitBezier2D(
|
|
points[0].x,
|
|
points[0].y,
|
|
points[1].x,
|
|
points[1].y,
|
|
points[2].x,
|
|
points[2].y,
|
|
points[3].x,
|
|
points[3].y,
|
|
0.5
|
|
);
|
|
const newCandidates = [bezierCandidate(leftPoints, x, y), bezierCandidate(rightPoints, x, y)].sort(
|
|
bezierCandidateCmp
|
|
);
|
|
queue = insertListItemsSorted(queue, newCandidates, bezierCandidateCmp);
|
|
}
|
|
return bestResult?.distance ?? Infinity;
|
|
}
|
|
var bezierCandidateCmp = (a, b) => b.minDistance - a.minDistance;
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/labelPlacement.ts
|
|
function circleRectOverlap({ point: c, anchor: unitCenter }, x, y, w, h) {
|
|
if (c.size === 0) {
|
|
return false;
|
|
}
|
|
let cx = c.x;
|
|
let cy = c.y;
|
|
if (unitCenter != null) {
|
|
cx -= (unitCenter.x - 0.5) * c.size;
|
|
cy -= (unitCenter.y - 0.5) * c.size;
|
|
}
|
|
let edgeX = cx;
|
|
if (cx < x) {
|
|
edgeX = x;
|
|
} else if (cx > x + w) {
|
|
edgeX = x + w;
|
|
}
|
|
let edgeY = cy;
|
|
if (cy < y) {
|
|
edgeY = y;
|
|
} else if (cy > y + h) {
|
|
edgeY = y + h;
|
|
}
|
|
const dx2 = cx - edgeX;
|
|
const dy2 = cy - edgeY;
|
|
const d = Math.hypot(dx2, dy2);
|
|
return d <= c.size / 2;
|
|
}
|
|
function isPointLabelDatum(x) {
|
|
return x != null && typeof x.point === "object" && typeof x.label === "object";
|
|
}
|
|
var labelPlacements = {
|
|
top: { x: 0, y: -1 },
|
|
bottom: { x: 0, y: 1 },
|
|
left: { x: -1, y: 0 },
|
|
right: { x: 1, y: 0 },
|
|
"top-left": { x: -1, y: -1 },
|
|
"top-right": { x: 1, y: -1 },
|
|
"bottom-left": { x: -1, y: 1 },
|
|
"bottom-right": { x: 1, y: 1 }
|
|
};
|
|
function placeLabels(data, bounds, padding2 = 5) {
|
|
const result = /* @__PURE__ */ new Map();
|
|
const previousResults = [];
|
|
const sortedDataClone = new Map(
|
|
Array.from(data.entries(), ([k, d]) => [k, d.toSorted((a, b) => b.point.size - a.point.size)])
|
|
);
|
|
const dataValues = [...sortedDataClone.values()].flat();
|
|
for (const [seriesId, datums] of sortedDataClone.entries()) {
|
|
const labels = [];
|
|
if (!datums[0]?.label)
|
|
continue;
|
|
for (let index = 0, ln = datums.length; index < ln; index++) {
|
|
const d = datums[index];
|
|
const { point, label, anchor } = d;
|
|
const { text: text2, width: width2, height: height2 } = label;
|
|
const r = point.size / 2;
|
|
let dx2 = 0;
|
|
let dy2 = 0;
|
|
if (r > 0 && d.placement != null) {
|
|
const placement = labelPlacements[d.placement];
|
|
dx2 = (width2 / 2 + r + padding2) * placement.x;
|
|
dy2 = (height2 / 2 + r + padding2) * placement.y;
|
|
}
|
|
let x = point.x - width2 / 2 + dx2;
|
|
let y = point.y - height2 / 2 + dy2;
|
|
if (anchor) {
|
|
x -= (anchor.x - 0.5) * point.size;
|
|
y -= (anchor.y - 0.5) * point.size;
|
|
}
|
|
if (boxContains(bounds, x, y, width2, height2) && !dataValues.some((dataDatum) => circleRectOverlap(dataDatum, x, y, width2, height2)) && !previousResults.some((pr) => boxCollides(pr, x, y, width2, height2))) {
|
|
const resultDatum = { index, text: text2, x, y, width: width2, height: height2, datum: d };
|
|
labels.push(resultDatum);
|
|
previousResults.push(resultDatum);
|
|
}
|
|
}
|
|
result.set(seriesId, labels);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/scaling.ts
|
|
function isContinuousScaling(scaling) {
|
|
return scaling.type === "continuous" || scaling.type === "log";
|
|
}
|
|
function isCategoryScaling(scaling) {
|
|
return scaling.type === "category";
|
|
}
|
|
function isUnitTimeCategoryScaling(scaling) {
|
|
return "variant" in scaling && scaling.variant === "unit-time";
|
|
}
|
|
function isStandardCategoryScaling(scaling) {
|
|
return !("variant" in scaling);
|
|
}
|
|
function areScalingEqual(a, b) {
|
|
if (a === void 0 || b === void 0) {
|
|
return a !== void 0 || b !== void 0;
|
|
}
|
|
if (isContinuousScaling(a) && isContinuousScaling(b)) {
|
|
return a.type === b.type && arraysEqual(a.domain, b.domain) && arraysEqual(a.range, b.range);
|
|
}
|
|
if (isCategoryScaling(a) && isCategoryScaling(b)) {
|
|
if (isUnitTimeCategoryScaling(a) && isUnitTimeCategoryScaling(b)) {
|
|
return a.firstBandTime === b.firstBandTime && a.lastBandTime === b.lastBandTime && a.bandCount === b.bandCount && a.intervalMs === b.intervalMs && a.inset === b.inset && a.step === b.step;
|
|
}
|
|
if (isStandardCategoryScaling(a) && isStandardCategoryScaling(b)) {
|
|
return a.inset === b.inset && a.step === b.step && arraysEqual(a.domain, b.domain);
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
function isScaleValid(scale2) {
|
|
if (scale2 == null)
|
|
return false;
|
|
if (scale2.type === "category") {
|
|
if (isUnitTimeCategoryScaling(scale2)) {
|
|
return Number.isFinite(scale2.firstBandTime) && Number.isFinite(scale2.lastBandTime) && Number.isFinite(scale2.bandCount) && scale2.bandCount > 0;
|
|
}
|
|
return scale2.domain.every((v) => v != null);
|
|
}
|
|
return scale2.domain.every((v) => Number.isFinite(v) || v instanceof Date) && scale2.range.every((v) => Number.isFinite(v));
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/geometry/lineInterpolation.ts
|
|
function spanRange(span) {
|
|
switch (span.type) {
|
|
case "linear":
|
|
case "step":
|
|
case "multi-line":
|
|
return [
|
|
{ x: span.x0, y: span.y0 },
|
|
{ x: span.x1, y: span.y1 }
|
|
];
|
|
case "cubic":
|
|
return [
|
|
{ x: span.cp0x, y: span.cp0y },
|
|
{ x: span.cp3x, y: span.cp3y }
|
|
];
|
|
}
|
|
}
|
|
function spanRangeNormalized(span) {
|
|
const range3 = spanRange(span);
|
|
if (range3[0].x > range3[1].x) {
|
|
range3.reverse();
|
|
}
|
|
return range3;
|
|
}
|
|
function collapseSpanToPoint(span, point) {
|
|
const { x, y } = point;
|
|
switch (span.type) {
|
|
case "linear":
|
|
return {
|
|
type: "linear",
|
|
moveTo: span.moveTo,
|
|
x0: x,
|
|
y0: y,
|
|
x1: x,
|
|
y1: y
|
|
};
|
|
case "step":
|
|
return {
|
|
type: "step",
|
|
moveTo: span.moveTo,
|
|
x0: x,
|
|
y0: y,
|
|
x1: x,
|
|
y1: y,
|
|
stepX: x
|
|
};
|
|
case "cubic":
|
|
return {
|
|
type: "cubic",
|
|
moveTo: span.moveTo,
|
|
cp0x: x,
|
|
cp0y: y,
|
|
cp1x: x,
|
|
cp1y: y,
|
|
cp2x: x,
|
|
cp2y: y,
|
|
cp3x: x,
|
|
cp3y: y
|
|
};
|
|
case "multi-line":
|
|
return {
|
|
type: "multi-line",
|
|
moveTo: span.moveTo,
|
|
x0: x,
|
|
y0: y,
|
|
x1: x,
|
|
y1: y,
|
|
midPoints: span.midPoints.map(() => ({ x, y }))
|
|
};
|
|
}
|
|
}
|
|
function rescaleSpan(span, nextStart, nextEnd) {
|
|
const [prevStart, prevEnd] = spanRange(span);
|
|
const widthScale = prevEnd.x === prevStart.x ? 0 : (nextEnd.x - nextStart.x) / (prevEnd.x - prevStart.x);
|
|
const heightScale = prevEnd.y === prevStart.y ? 0 : (nextEnd.y - nextStart.y) / (prevEnd.y - prevStart.y);
|
|
switch (span.type) {
|
|
case "linear":
|
|
return {
|
|
type: "linear",
|
|
moveTo: span.moveTo,
|
|
x0: nextStart.x,
|
|
y0: nextStart.y,
|
|
x1: nextEnd.x,
|
|
y1: nextEnd.y
|
|
};
|
|
case "cubic":
|
|
return {
|
|
type: "cubic",
|
|
moveTo: span.moveTo,
|
|
cp0x: nextStart.x,
|
|
cp0y: nextStart.y,
|
|
cp1x: nextEnd.x - (span.cp2x - prevStart.x) * widthScale,
|
|
cp1y: nextEnd.y - (span.cp2y - prevStart.y) * heightScale,
|
|
cp2x: nextEnd.x - (span.cp1x - prevStart.x) * widthScale,
|
|
cp2y: nextEnd.y - (span.cp1y - prevStart.y) * heightScale,
|
|
cp3x: nextEnd.x,
|
|
cp3y: nextEnd.y
|
|
};
|
|
case "step":
|
|
return {
|
|
type: "step",
|
|
moveTo: span.moveTo,
|
|
x0: nextStart.x,
|
|
y0: nextStart.y,
|
|
x1: nextEnd.x,
|
|
y1: nextEnd.y,
|
|
stepX: nextEnd.x - (span.stepX - prevStart.x) * widthScale
|
|
};
|
|
case "multi-line":
|
|
return {
|
|
type: "multi-line",
|
|
moveTo: span.moveTo,
|
|
x0: nextStart.x,
|
|
y0: nextStart.y,
|
|
x1: nextEnd.x,
|
|
y1: nextEnd.y,
|
|
midPoints: span.midPoints.map((midPoint) => ({
|
|
x: nextStart.x + (midPoint.x - prevStart.x) * widthScale,
|
|
y: nextStart.y + (midPoint.y - prevStart.y) * heightScale
|
|
}))
|
|
};
|
|
}
|
|
}
|
|
function clipSpanX(span, x0, x1) {
|
|
const { moveTo: moveTo2 } = span;
|
|
const [start2, end3] = spanRangeNormalized(span);
|
|
const { x: spanX0, y: spanY0 } = start2;
|
|
const { x: spanX1, y: spanY1 } = end3;
|
|
if (x1 < spanX0) {
|
|
return rescaleSpan(span, start2, start2);
|
|
} else if (x0 > spanX1) {
|
|
return rescaleSpan(span, end3, end3);
|
|
}
|
|
switch (span.type) {
|
|
case "linear": {
|
|
const m = spanY0 === spanY1 ? void 0 : (spanY1 - spanY0) / (spanX1 - spanX0);
|
|
const y0 = m == null ? spanY0 : m * (x0 - spanX0) + spanY0;
|
|
const y1 = m == null ? spanY0 : m * (x1 - spanX0) + spanY0;
|
|
return { type: "linear", moveTo: moveTo2, x0, y0, x1, y1 };
|
|
}
|
|
case "step":
|
|
if (x1 <= span.stepX) {
|
|
const y = span.y0;
|
|
return { type: "step", moveTo: moveTo2, x0, y0: y, x1, y1: y, stepX: x1 };
|
|
} else if (x0 >= span.stepX) {
|
|
const y = span.y1;
|
|
return { type: "step", moveTo: moveTo2, x0, y0: y, x1, y1: y, stepX: x0 };
|
|
} else {
|
|
const { y0, y1, stepX } = span;
|
|
return { type: "step", moveTo: moveTo2, x0, y0, x1, y1, stepX };
|
|
}
|
|
case "cubic": {
|
|
const t0 = solveBezier(span.cp0x, span.cp1x, span.cp2x, span.cp3x, x0);
|
|
let [_unused, bezier] = splitBezier2D(
|
|
span.cp0x,
|
|
span.cp0y,
|
|
span.cp1x,
|
|
span.cp1y,
|
|
span.cp2x,
|
|
span.cp2y,
|
|
span.cp3x,
|
|
span.cp3y,
|
|
t0
|
|
);
|
|
const t1 = solveBezier(bezier[0].x, bezier[1].x, bezier[2].x, bezier[3].x, x1);
|
|
[bezier, _unused] = splitBezier2D(
|
|
bezier[0].x,
|
|
bezier[0].y,
|
|
bezier[1].x,
|
|
bezier[1].y,
|
|
bezier[2].x,
|
|
bezier[2].y,
|
|
bezier[3].x,
|
|
bezier[3].y,
|
|
t1
|
|
);
|
|
return {
|
|
type: "cubic",
|
|
moveTo: moveTo2,
|
|
cp0x: bezier[0].x,
|
|
cp0y: bezier[0].y,
|
|
cp1x: bezier[1].x,
|
|
cp1y: bezier[1].y,
|
|
cp2x: bezier[2].x,
|
|
cp2y: bezier[2].y,
|
|
cp3x: bezier[3].x,
|
|
cp3y: bezier[3].y
|
|
};
|
|
}
|
|
case "multi-line": {
|
|
const { midPoints } = span;
|
|
const midPointStartIndex = midPoints.findLastIndex((midPoint) => midPoint.x <= x0);
|
|
let midPointEndIndex = midPoints.findIndex((midPoint) => midPoint.x >= x1);
|
|
if (midPointEndIndex === -1)
|
|
midPointEndIndex = midPoints.length;
|
|
const startPoint = midPointStartIndex >= 0 ? midPoints[midPointStartIndex] : void 0;
|
|
const startX = startPoint?.x ?? spanX0;
|
|
const startY = startPoint?.y ?? spanY0;
|
|
const endPoint = midPointEndIndex < midPoints.length ? midPoints[midPointEndIndex] : void 0;
|
|
const endX = endPoint?.x ?? spanX1;
|
|
const endY = endPoint?.y ?? spanY1;
|
|
const m = startY === endY ? void 0 : (endY - startY) / (endX - startX);
|
|
const y0 = m == null ? startY : m * (startX - spanX0) + startY;
|
|
const y1 = m == null ? startY : m * (endX - spanX0) + startY;
|
|
return {
|
|
type: "multi-line",
|
|
moveTo: moveTo2,
|
|
x0,
|
|
y0,
|
|
x1,
|
|
y1,
|
|
midPoints: midPoints.slice(Math.max(midPointStartIndex, 0), midPointEndIndex)
|
|
};
|
|
}
|
|
}
|
|
}
|
|
var SpanJoin = /* @__PURE__ */ ((SpanJoin2) => {
|
|
SpanJoin2[SpanJoin2["MoveTo"] = 0] = "MoveTo";
|
|
SpanJoin2[SpanJoin2["LineTo"] = 1] = "LineTo";
|
|
SpanJoin2[SpanJoin2["Skip"] = 2] = "Skip";
|
|
return SpanJoin2;
|
|
})(SpanJoin || {});
|
|
function linearPoints(points) {
|
|
const spans = [];
|
|
let i = 0;
|
|
let x0 = Number.NaN;
|
|
let y0 = Number.NaN;
|
|
for (const { x: x1, y: y1 } of points) {
|
|
if (i > 0) {
|
|
const moveTo2 = i === 1;
|
|
spans.push({ type: "linear", moveTo: moveTo2, x0, y0, x1, y1 });
|
|
}
|
|
i += 1;
|
|
x0 = x1;
|
|
y0 = y1;
|
|
}
|
|
return spans;
|
|
}
|
|
var lineSteps = {
|
|
start: 0,
|
|
middle: 0.5,
|
|
end: 1
|
|
};
|
|
function stepPoints(points, position) {
|
|
const spans = [];
|
|
let i = 0;
|
|
let x0 = Number.NaN;
|
|
let y0 = Number.NaN;
|
|
const p0 = typeof position === "number" ? position : lineSteps[position];
|
|
for (const { x: x1, y: y1 } of points) {
|
|
if (i > 0) {
|
|
const moveTo2 = i === 1;
|
|
const stepX = x0 + (x1 - x0) * p0;
|
|
spans.push({ type: "step", moveTo: moveTo2, x0, y0, x1, y1, stepX });
|
|
}
|
|
i += 1;
|
|
x0 = x1;
|
|
y0 = y1;
|
|
}
|
|
return spans;
|
|
}
|
|
function smoothPoints(iPoints, tension) {
|
|
const points = Array.isArray(iPoints) ? iPoints : Array.from(iPoints);
|
|
if (points.length <= 1)
|
|
return [];
|
|
const flatnessRatio = 0.05;
|
|
const gradients = points.map((c, i) => {
|
|
const p = i === 0 ? c : points[i - 1];
|
|
const n = i === points.length - 1 ? c : points[i + 1];
|
|
const isTerminalPoint = i === 0 || i === points.length - 1;
|
|
if (Math.sign(p.y - c.y) === Math.sign(n.y - c.y)) {
|
|
return 0;
|
|
}
|
|
if (!isTerminalPoint) {
|
|
const range3 = Math.abs(p.y - n.y);
|
|
const prevRatio = Math.abs(c.y - p.y) / range3;
|
|
const nextRatio = Math.abs(c.y - n.y) / range3;
|
|
if (prevRatio <= flatnessRatio || 1 - prevRatio <= flatnessRatio || nextRatio <= flatnessRatio || 1 - nextRatio <= flatnessRatio) {
|
|
return 0;
|
|
}
|
|
}
|
|
return (n.y - p.y) / (n.x - p.x);
|
|
});
|
|
if (gradients[1] === 0) {
|
|
gradients[0] *= 2;
|
|
}
|
|
if (gradients.at(-2) === 0) {
|
|
gradients[gradients.length - 1] *= 2;
|
|
}
|
|
const spans = [];
|
|
for (let i = 1; i < points.length; i += 1) {
|
|
const prev = points[i - 1];
|
|
const prevM = gradients[i - 1];
|
|
const cur = points[i];
|
|
const curM = gradients[i];
|
|
const dx2 = cur.x - prev.x;
|
|
const dy2 = cur.y - prev.y;
|
|
let dcp1x = dx2 * tension / 3;
|
|
let dcp1y = dx2 * prevM * tension / 3;
|
|
let dcp2x = dx2 * tension / 3;
|
|
let dcp2y = dx2 * curM * tension / 3;
|
|
if (curM === 0 && Math.abs(dcp1y) > Math.abs(dy2)) {
|
|
dcp1x *= Math.abs(dy2 / dcp1y);
|
|
dcp1y = Math.sign(dcp1y) * Math.abs(dy2);
|
|
}
|
|
if (prevM === 0 && Math.abs(dcp2y) > Math.abs(dy2)) {
|
|
dcp2x *= Math.abs(dy2 / dcp2y);
|
|
dcp2y = Math.sign(dcp2y) * Math.abs(dy2);
|
|
}
|
|
spans.push({
|
|
type: "cubic",
|
|
moveTo: i === 1,
|
|
cp0x: prev.x,
|
|
cp0y: prev.y,
|
|
cp1x: prev.x + dcp1x,
|
|
cp1y: prev.y + dcp1y,
|
|
cp2x: cur.x - dcp2x,
|
|
cp2y: cur.y - dcp2y,
|
|
cp3x: cur.x,
|
|
cp3y: cur.y
|
|
});
|
|
}
|
|
return spans;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/utils/zoomUtils.ts
|
|
var UNIT_MIN = 0;
|
|
var UNIT_MAX = 1;
|
|
function definedZoomState(zoom) {
|
|
return {
|
|
x: { min: zoom?.x?.min ?? UNIT_MIN, max: zoom?.x?.max ?? UNIT_MAX },
|
|
y: { min: zoom?.y?.min ?? UNIT_MIN, max: zoom?.y?.max ?? UNIT_MAX }
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-core/src/rendering/changeDetectableProperties.ts
|
|
var ChangeDetectableProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this._dirty = true;
|
|
}
|
|
markDirty() {
|
|
this._dirty = true;
|
|
}
|
|
markClean(_opts) {
|
|
this._dirty = false;
|
|
}
|
|
isDirty() {
|
|
return this._dirty;
|
|
}
|
|
onChangeDetection(_property) {
|
|
this.markDirty();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-core/src/utils/format/timeFormat.ts
|
|
var CONSTANTS = {
|
|
periods: ["AM", "PM"],
|
|
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
|
|
shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
|
|
months: [
|
|
"January",
|
|
"February",
|
|
"March",
|
|
"April",
|
|
"May",
|
|
"June",
|
|
"July",
|
|
"August",
|
|
"September",
|
|
"October",
|
|
"November",
|
|
"December"
|
|
],
|
|
shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
|
};
|
|
function dayOfYear(date2, startOfYear = new Date(date2.getFullYear(), 0, 1)) {
|
|
const startOffset = date2.getTimezoneOffset() - startOfYear.getTimezoneOffset();
|
|
const timeDiff = date2.getTime() - startOfYear.getTime() + startOffset * 6e4;
|
|
const timeOneDay = 36e5 * 24;
|
|
return Math.floor(timeDiff / timeOneDay);
|
|
}
|
|
function weekOfYear(date2, startDay) {
|
|
const startOfYear = new Date(date2.getFullYear(), 0, 1);
|
|
const startOfYearDay = startOfYear.getDay();
|
|
const firstWeekStartOffset = (startDay - startOfYearDay + 7) % 7;
|
|
const startOffset = new Date(date2.getFullYear(), 0, firstWeekStartOffset + 1);
|
|
if (startOffset <= date2) {
|
|
return Math.floor(dayOfYear(date2, startOffset) / 7) + 1;
|
|
}
|
|
return 0;
|
|
}
|
|
var SUNDAY = 0;
|
|
var MONDAY = 1;
|
|
var THURSDAY = 4;
|
|
function isoWeekOfYear(date2, year = date2.getFullYear()) {
|
|
const firstOfYear = new Date(year, 0, 1);
|
|
const firstOfYearDay = firstOfYear.getDay();
|
|
const firstThursdayOffset = (THURSDAY - firstOfYearDay + 7) % 7;
|
|
const startOffset = new Date(year, 0, firstThursdayOffset - (THURSDAY - MONDAY) + 1);
|
|
if (startOffset <= date2) {
|
|
return Math.floor(dayOfYear(date2, startOffset) / 7) + 1;
|
|
}
|
|
return isoWeekOfYear(date2, year - 1);
|
|
}
|
|
function timezone(date2) {
|
|
const offset = date2.getTimezoneOffset();
|
|
const unsignedOffset = Math.abs(offset);
|
|
const sign = offset > 0 ? "-" : "+";
|
|
return `${sign}${pad(Math.floor(unsignedOffset / 60), 2, "0")}${pad(Math.floor(unsignedOffset % 60), 2, "0")}`;
|
|
}
|
|
var FORMATTERS = {
|
|
a: (d) => CONSTANTS.shortDays[d.getDay()],
|
|
A: (d) => CONSTANTS.days[d.getDay()],
|
|
b: (d) => CONSTANTS.shortMonths[d.getMonth()],
|
|
B: (d) => CONSTANTS.months[d.getMonth()],
|
|
c: "%x, %X",
|
|
d: (d, p) => pad(d.getDate(), 2, p ?? "0"),
|
|
e: "%_d",
|
|
f: (d, p) => pad(d.getMilliseconds() * 1e3, 6, p ?? "0"),
|
|
H: (d, p) => pad(d.getHours(), 2, p ?? "0"),
|
|
I: (d, p) => {
|
|
const hours = d.getHours() % 12;
|
|
return hours === 0 ? "12" : pad(hours, 2, p ?? "0");
|
|
},
|
|
j: (d, p) => pad(dayOfYear(d) + 1, 3, p ?? "0"),
|
|
m: (d, p) => pad(d.getMonth() + 1, 2, p ?? "0"),
|
|
M: (d, p) => pad(d.getMinutes(), 2, p ?? "0"),
|
|
L: (d, p) => pad(d.getMilliseconds(), 3, p ?? "0"),
|
|
p: (d) => d.getHours() < 12 ? "AM" : "PM",
|
|
Q: (d) => String(d.getTime()),
|
|
s: (d) => String(Math.floor(d.getTime() / 1e3)),
|
|
S: (d, p) => pad(d.getSeconds(), 2, p ?? "0"),
|
|
u: (d) => {
|
|
let day = d.getDay();
|
|
if (day < 1)
|
|
day += 7;
|
|
return String(day % 7);
|
|
},
|
|
U: (d, p) => pad(weekOfYear(d, SUNDAY), 2, p ?? "0"),
|
|
V: (d, p) => pad(isoWeekOfYear(d), 2, p ?? "0"),
|
|
w: (d, p) => pad(d.getDay(), 2, p ?? "0"),
|
|
W: (d, p) => pad(weekOfYear(d, MONDAY), 2, p ?? "0"),
|
|
x: "%-m/%-d/%Y",
|
|
X: "%-I:%M:%S %p",
|
|
y: (d, p) => pad(d.getFullYear() % 100, 2, p ?? "0"),
|
|
Y: (d, p) => pad(d.getFullYear(), 4, p ?? "0"),
|
|
Z: (d) => timezone(d),
|
|
"%": () => "%"
|
|
};
|
|
var PADS = {
|
|
_: " ",
|
|
"0": "0",
|
|
"-": ""
|
|
};
|
|
function pad(value, size, padChar) {
|
|
const output = String(Math.floor(value));
|
|
if (output.length >= size) {
|
|
return output;
|
|
}
|
|
return `${padChar.repeat(size - output.length)}${output}`;
|
|
}
|
|
function buildDateFormatter(formatString) {
|
|
const formatParts = [];
|
|
while (formatString.length > 0) {
|
|
let nextEscapeIdx = formatString.indexOf("%");
|
|
if (nextEscapeIdx !== 0) {
|
|
const literalPart = nextEscapeIdx > 0 ? formatString.substring(0, nextEscapeIdx) : formatString;
|
|
formatParts.push(literalPart);
|
|
}
|
|
if (nextEscapeIdx < 0)
|
|
break;
|
|
const maybePadSpecifier = formatString[nextEscapeIdx + 1];
|
|
const maybePad = PADS[maybePadSpecifier];
|
|
if (maybePad != null) {
|
|
nextEscapeIdx++;
|
|
}
|
|
const maybeFormatterSpecifier = formatString[nextEscapeIdx + 1];
|
|
const maybeFormatter = FORMATTERS[maybeFormatterSpecifier];
|
|
if (typeof maybeFormatter === "function") {
|
|
formatParts.push([maybeFormatter, maybePad]);
|
|
} else if (typeof maybeFormatter === "string") {
|
|
const formatter2 = buildDateFormatter(maybeFormatter);
|
|
formatParts.push([formatter2, maybePad]);
|
|
} else {
|
|
formatParts.push(`${maybePad ?? ""}${maybeFormatterSpecifier}`);
|
|
}
|
|
formatString = formatString.substring(nextEscapeIdx + 2);
|
|
}
|
|
return (dateTime) => {
|
|
const dateTimeAsDate = typeof dateTime === "number" ? new Date(dateTime) : dateTime;
|
|
return formatParts.map((c) => typeof c === "string" ? c : c[0](dateTimeAsDate, c[1])).join("");
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-core/src/rendering/domElements.ts
|
|
function createButton(options, attrs) {
|
|
const button = createElement("button", getClassName("ag-charts-input ag-charts-button", attrs));
|
|
if (options.label === void 0) {
|
|
button.append(createIcon(options.icon));
|
|
button.ariaLabel = options.altText;
|
|
} else {
|
|
button.append(options.label);
|
|
}
|
|
button.addEventListener("click", options.onPress);
|
|
setAttributes(button, attrs);
|
|
return button;
|
|
}
|
|
function createCheckbox(options, attrs) {
|
|
const checkbox = createElement("input", getClassName("ag-charts-input ag-charts-checkbox", attrs));
|
|
checkbox.type = "checkbox";
|
|
checkbox.checked = options.checked;
|
|
checkbox.addEventListener("change", (event) => options.onChange(checkbox.checked, event));
|
|
checkbox.addEventListener("keydown", (event) => {
|
|
if (isButtonClickEvent(event)) {
|
|
event.preventDefault();
|
|
checkbox.click();
|
|
}
|
|
});
|
|
setAttributes(checkbox, attrs);
|
|
return checkbox;
|
|
}
|
|
function createSelect(options, attrs) {
|
|
const select = createElement("select", getClassName("ag-charts-input ag-charts-select", attrs));
|
|
select.append(
|
|
...options.options.map((option) => {
|
|
const optionEl = createElement("option");
|
|
optionEl.value = option.value;
|
|
optionEl.textContent = option.label;
|
|
return optionEl;
|
|
})
|
|
);
|
|
setAttribute(select, "data-preventdefault", false);
|
|
select.value = options.value;
|
|
select.addEventListener("change", (event) => options.onChange(select.value, event));
|
|
setAttributes(select, attrs);
|
|
return select;
|
|
}
|
|
function createTextArea(options, attrs) {
|
|
const textArea = createElement("textarea", getClassName("ag-charts-input ag-charts-textarea", attrs));
|
|
textArea.value = options.value;
|
|
textArea.addEventListener("input", (event) => options.onChange(textArea.value, event));
|
|
setAttributes(textArea, attrs);
|
|
setAttribute(textArea, "data-preventdefault", false);
|
|
return textArea;
|
|
}
|
|
function createIcon(icon) {
|
|
const el = createElement("span", `ag-charts-icon ag-charts-icon-${icon}`);
|
|
setAttribute(el, "aria-hidden", true);
|
|
return el;
|
|
}
|
|
function getClassName(baseClass, attrs) {
|
|
if (attrs == null)
|
|
return baseClass;
|
|
return `${baseClass} ${attrs.class}`;
|
|
}
|
|
|
|
// packages/ag-charts-core/src/rendering/easing.ts
|
|
var linear = (n) => n;
|
|
var easeIn = (n) => 1 - Math.cos(n * Math.PI / 2);
|
|
var easeOut = (n) => Math.sin(n * Math.PI / 2);
|
|
var easeInOut = (n) => -(Math.cos(n * Math.PI) - 1) / 2;
|
|
var easeInQuad = (n) => n * n;
|
|
var easeOutQuad = (n) => 1 - (1 - n) ** 2;
|
|
var easeInOutQuad = (n) => n < 0.5 ? 2 * n * n : 1 - (-2 * n + 2) ** 2 / 2;
|
|
var inverseEaseOut = (x) => 2 * Math.asin(x) / Math.PI;
|
|
|
|
// packages/ag-charts-core/src/rendering/changeDetectable.ts
|
|
var TRIPLE_EQ = (lhs, rhs) => lhs === rhs;
|
|
function SceneChangeDetection(opts) {
|
|
return function(target, key) {
|
|
const privateKey = `__${key}`;
|
|
if (target[key])
|
|
return;
|
|
prepareGetSet(target, key, privateKey, opts);
|
|
};
|
|
}
|
|
function SceneRefChangeDetection(opts) {
|
|
return SceneChangeDetection(opts);
|
|
}
|
|
function SceneObjectChangeDetection(opts) {
|
|
return SceneChangeDetection(opts);
|
|
}
|
|
function SceneArrayChangeDetection(opts) {
|
|
const baseOpts = opts ?? {};
|
|
baseOpts.equals = arraysEqual;
|
|
return SceneChangeDetection(opts);
|
|
}
|
|
function DeclaredSceneChangeDetection(opts) {
|
|
return function(target, key) {
|
|
const privateKey = `__${key}`;
|
|
if (target[key])
|
|
return;
|
|
prepareGetSet(target, key, privateKey, opts);
|
|
};
|
|
}
|
|
function DeclaredSceneObjectChangeDetection(opts) {
|
|
return function(target, key) {
|
|
const privateKey = `__${key}`;
|
|
if (target[key])
|
|
return;
|
|
prepareGetSet(target, key, privateKey, opts);
|
|
};
|
|
}
|
|
function prepareGetSet(target, key, privateKey, opts) {
|
|
const { changeCb, convertor, checkDirtyOnAssignment = false } = opts ?? {};
|
|
const requiredOpts = { changeCb, checkDirtyOnAssignment, convertor };
|
|
const setter = buildCheckDirtyChain(
|
|
privateKey,
|
|
buildChangeCallbackChain(
|
|
buildConvertorChain(buildSetter(privateKey, requiredOpts), requiredOpts),
|
|
requiredOpts
|
|
),
|
|
requiredOpts
|
|
);
|
|
function propertyGetter() {
|
|
return this[privateKey];
|
|
}
|
|
Object.defineProperty(target, key, {
|
|
set: setter,
|
|
get: propertyGetter,
|
|
enumerable: true,
|
|
configurable: true
|
|
});
|
|
}
|
|
function buildConvertorChain(setterFn, opts) {
|
|
const { convertor } = opts;
|
|
if (convertor) {
|
|
let convertValueAndSet2 = function(value) {
|
|
setterFn.call(this, convertValue(value));
|
|
};
|
|
var convertValueAndSet = convertValueAndSet2;
|
|
const convertValue = convertor;
|
|
return convertValueAndSet2;
|
|
}
|
|
return setterFn;
|
|
}
|
|
var NO_CHANGE = Symbol("no-change");
|
|
function buildChangeCallbackChain(setterFn, opts) {
|
|
const { changeCb } = opts;
|
|
if (changeCb) {
|
|
let invokeChangeCallback2 = function(value) {
|
|
const change = setterFn.call(this, value);
|
|
if (change !== NO_CHANGE) {
|
|
changeCallback.call(this, this);
|
|
}
|
|
return change;
|
|
};
|
|
var invokeChangeCallback = invokeChangeCallback2;
|
|
const changeCallback = changeCb;
|
|
return invokeChangeCallback2;
|
|
}
|
|
return setterFn;
|
|
}
|
|
function buildCheckDirtyChain(privateKey, setterFn, opts) {
|
|
const { checkDirtyOnAssignment } = opts;
|
|
if (checkDirtyOnAssignment) {
|
|
let checkDirtyOnAssignmentFn2 = function(value) {
|
|
const change = setterFn.call(this, value);
|
|
if (value?._dirty === true) {
|
|
this.markDirty(privateKey);
|
|
}
|
|
return change;
|
|
};
|
|
var checkDirtyOnAssignmentFn = checkDirtyOnAssignmentFn2;
|
|
return checkDirtyOnAssignmentFn2;
|
|
}
|
|
return setterFn;
|
|
}
|
|
function buildSetter(privateKey, opts) {
|
|
const { equals = TRIPLE_EQ } = opts;
|
|
function setWithChangeDetection(value) {
|
|
const oldValue = this[privateKey];
|
|
if (!equals(value, oldValue)) {
|
|
this[privateKey] = value;
|
|
this.onChangeDetection(privateKey);
|
|
return value;
|
|
}
|
|
return NO_CHANGE;
|
|
}
|
|
return setWithChangeDetection;
|
|
}
|
|
|
|
// packages/ag-charts-locale/src/en-US.ts
|
|
var AG_CHARTS_LOCALE_EN_US = {
|
|
// Initial screen reader alt-text of the series area
|
|
ariaInitSeriesArea: "interactive chart",
|
|
// Screen reader announcement when focusing an item in the chart
|
|
ariaAnnounceHoverDatum: "${datum}",
|
|
// Screen reader announcement when focusing a chart
|
|
ariaAnnounceChart: "chart, ${seriesCount}[number] series",
|
|
// Screen reader announcement when focusing a standalone chart (gauges, pyramid)
|
|
ariaAnnounceStandaloneChart: "chart, ${caption}",
|
|
// Screen reader announcement when focusing a hierarchy chart
|
|
ariaAnnounceHierarchyChart: "hierarchy chart, ${caption}",
|
|
// Screen reader announcement when focusing a gauge chart
|
|
ariaAnnounceGaugeChart: "gauge chart, ${caption}",
|
|
// Screen reader announcement when focusing an item in a treemap or sunburst chart
|
|
ariaAnnounceHierarchyDatum: "level ${level}[number], ${count}[number] children, ${description}",
|
|
// Screen reader announcement when focusing a link in a Sankey or chord chart
|
|
ariaAnnounceFlowProportionLink: "link ${index} of ${count}, from ${from} to ${to}, ${sizeName} ${size}",
|
|
// Screen reader announcement when focusing a node in a Sankey or chord chart
|
|
ariaAnnounceFlowProportionNode: "node ${index} of ${count}, ${description}",
|
|
// Screen reader description for legend items
|
|
ariaDescriptionLegendItem: "Press Space or Enter to toggle visibility",
|
|
// Screen reader for the '+' horizontal line button on the Y-axis
|
|
ariaLabelAddHorizontalLine: "Add Horizontal Line",
|
|
// Screen reader text for annotations toolbar
|
|
ariaLabelAnnotationsToolbar: "Annotations",
|
|
// Screen reader text for annotation-options toolbar
|
|
ariaLabelAnnotationOptionsToolbar: "Annotation Options",
|
|
// Screen reader text for annotation-settings dialog
|
|
ariaLabelAnnotationSettingsDialog: "Annotation Settings",
|
|
// Screen reader text for the color-code label in the picker dialog
|
|
ariaLabelColor: "Color",
|
|
// Screen reader text for the color picker dialog
|
|
ariaLabelColorPicker: "Color picker",
|
|
// Screen reader text for the alpha-channel slider label
|
|
ariaLabelColorPickerAlpha: "Transparency",
|
|
// Screen reader text for the hue slider label
|
|
ariaLabelColorPickerHue: "Hue",
|
|
// Screen reader text for when the color-code is multi-colored
|
|
ariaLabelColorPickerMultiColor: "Multi Color",
|
|
// Screen reader text for the 2D palette slider label
|
|
ariaLabelColorPickerPalette: "Palette",
|
|
// Screen reader text for the financial charts toolbar
|
|
ariaLabelFinancialCharts: "Financial Charts",
|
|
// Screen reader text label for the gauge targets
|
|
ariaLabelGaugeTarget: "Target",
|
|
// Screen reader text label for the gauge values
|
|
ariaLabelGaugeValue: "Value",
|
|
// Screen reader text for the legend toolbar
|
|
ariaLabelLegend: "Legend",
|
|
// Screen reader text for the legend pagination button
|
|
ariaLabelLegendPagination: "Legend Pagination",
|
|
// Screen reader text for the previous legend page button
|
|
ariaLabelLegendPagePrevious: "Previous Legend Page",
|
|
// Screen reader text for the next legend page button
|
|
ariaLabelLegendPageNext: "Next Legend Page",
|
|
// Screen reader text for the an item in the legend
|
|
ariaLabelLegendItem: "${label}, Legend item ${index}[number] of ${count}[number]",
|
|
// Screen reader text for the an unknown item in the legend
|
|
ariaLabelLegendItemUnknown: "Unknown legend item",
|
|
// Screen reader text for the navigator element
|
|
ariaLabelNavigator: "Navigator",
|
|
// Screen reader text for an accessibility control that changes the position of the navigator's range
|
|
ariaLabelNavigatorRange: "Range",
|
|
// Screen reader text for the horizontal axis scrollbar
|
|
ariaLabelScrollbarHorizontal: "X-axis scrollbar",
|
|
// Screen reader text for the vertical axis scrollbar
|
|
ariaLabelScrollbarVertical: "Y-axis scrollbar",
|
|
// Screen reader text for an accessibility control that changes the start of the navigator's range
|
|
ariaLabelNavigatorMinimum: "Minimum",
|
|
// Screen reader text for an accessibility control that changes the end of the navigator's range
|
|
ariaLabelNavigatorMaximum: "Maximum",
|
|
// Screen reader text for ranges toolbar
|
|
ariaLabelRangesToolbar: "Ranges",
|
|
// Screen reader text for the settings dialog tab-bar
|
|
ariaLabelSettingsTabBar: "Settings",
|
|
// Screen reader text for zoom toolbar
|
|
ariaLabelZoomToolbar: "Zoom",
|
|
// Aria role description for a 2D role="slider"
|
|
ariaRoleDescription2DSlider: "2D slider",
|
|
// Screen reader text for color picker's 2D slider palette
|
|
ariaValueColorPalette: "s ${s}[percent0to2dp], v ${v}[percent0to2dp]",
|
|
// Screen reader text for color picker's 2D slider palette (when arrowing up or down)
|
|
ariaValueColorPaletteFirstV: "v ${v}[percent0to2dp], s ${s}[percent0to2dp]",
|
|
// Screen reader text for the value of the navigator's range
|
|
ariaValuePanRange: "${min}[percent0to2dp] to ${max}[percent0to2dp]",
|
|
// Alt-text for the solid line dash style menu item icon
|
|
iconAltTextLineStyleSolid: "Solid",
|
|
// Alt-text for the long-dashed line dash style menu item icon
|
|
iconAltTextLineStyleDashed: "Long-dashed",
|
|
// Alt-text for the short-dashed line dash style menu item icon
|
|
iconAltTextLineStyleDotted: "Short-dashed",
|
|
// Alt-text for the 'position-top' icon
|
|
iconAltTextPositionTop: "Top",
|
|
// Alt-text for the 'position-center' icon
|
|
iconAltTextPositionCenter: "Center",
|
|
// Alt-text for the 'position-bottom' icon
|
|
iconAltTextPositionBottom: "Bottom",
|
|
// Alt-text for the 'position-left' icon
|
|
iconAltTextAlignLeft: "Left",
|
|
// Alt-text for the 'align-center' icon
|
|
iconAltTextAlignCenter: "Center",
|
|
// Alt-text for the 'position-right' icon
|
|
iconAltTextAlignRight: "Right",
|
|
// Alt-text for the 'close' icon
|
|
iconAltTextClose: "Close",
|
|
// Default text for the 'loading data' overlay
|
|
overlayLoadingData: "Loading data...",
|
|
// Default text for the 'no data' overlay
|
|
overlayNoData: "No data to display",
|
|
// Default text for the 'no visible series' overlay
|
|
overlayNoVisibleSeries: "No visible series",
|
|
// Default text for the 'unsupported browser' overlay
|
|
overlayUnsupportedBrowser: "Incompatible browser version. Please upgrade your browser.",
|
|
// Text for frequency label in Histogram Series tooltip
|
|
seriesHistogramTooltipFrequency: "Frequency",
|
|
// Text for sum label in Histogram Series tooltip
|
|
seriesHistogramTooltipSum: "${yName} (sum)",
|
|
// Text for sum label in Histogram Series tooltip
|
|
seriesHistogramTooltipCount: "${yName} (count)",
|
|
// Text for sum label in Histogram Series tooltip
|
|
seriesHistogramTooltipMean: "${yName} (mean)",
|
|
// Text for the series type toolbar's chart type button
|
|
toolbarSeriesTypeDropdown: "Chart Type",
|
|
// Text for the series type toolbar's OHLC chart type button
|
|
toolbarSeriesTypeOHLC: "OHLC",
|
|
// Text for the series type toolbar's HLC chart type button
|
|
toolbarSeriesTypeHLC: "HLC",
|
|
// Text for the series type toolbar's high low chart type button
|
|
toolbarSeriesTypeHighLow: "High Low",
|
|
// Text for the series type toolbar's candles chart type button
|
|
toolbarSeriesTypeCandles: "Candles",
|
|
// Text for the series type toolbar's hollow candles chart type button
|
|
toolbarSeriesTypeHollowCandles: "Hollow Candles",
|
|
// Text for the series type toolbar's line chart type button
|
|
toolbarSeriesTypeLine: "Line",
|
|
// Text for the series type toolbar's line with markers chart type button
|
|
toolbarSeriesTypeLineWithMarkers: "Line with Markers",
|
|
// Text for the series type toolbar's line with step line chart type button
|
|
toolbarSeriesTypeStepLine: "Step Line",
|
|
// Text for the annotation toolbar's trend line button
|
|
toolbarAnnotationsTrendLine: "Trend Line",
|
|
// Text for the annotation toolbar's Fibonacci Retracement button
|
|
toolbarAnnotationsFibonacciRetracement: "Fib Retracement",
|
|
// Text for the annotation toolbar's Fibonacci Retracement Trend Based button
|
|
toolbarAnnotationsFibonacciRetracementTrendBased: "Fib Trend Based",
|
|
// Text for the annotation toolbar's horizontal line button
|
|
toolbarAnnotationsHorizontalLine: "Horizontal Line",
|
|
// Text for the annotation toolbar's vertical line button
|
|
toolbarAnnotationsVerticalLine: "Vertical Line",
|
|
// Text for the annotation toolbar's parallel channel button
|
|
toolbarAnnotationsParallelChannel: "Parallel Channel",
|
|
// Text for the annotation toolbar's disjoint channel button
|
|
toolbarAnnotationsDisjointChannel: "Disjoint Channel",
|
|
// Text for the annotation toolbar's clear all button
|
|
toolbarAnnotationsClearAll: "Clear All",
|
|
// Text for the annotation toolbar's fill color picker annotation button
|
|
toolbarAnnotationsFillColor: "Fill Color",
|
|
// Text for the annotation toolbar's line color picker annotation button
|
|
toolbarAnnotationsLineColor: "Line Color",
|
|
// Text for the annotation toolbar's line style type button
|
|
toolbarAnnotationsLineStyle: "Line Style",
|
|
// Text for the annotation toolbar's line stroke width button
|
|
toolbarAnnotationsLineStrokeWidth: "Line Stroke Width",
|
|
// Text for the annotation toolbar's settings annotation button
|
|
toolbarAnnotationsSettings: "Settings",
|
|
// Text for the annotation toolbar's text color picker annotation button
|
|
toolbarAnnotationsTextColor: "Text Color",
|
|
// Text for the annotation toolbar's text size picker annotation button
|
|
toolbarAnnotationsTextSize: "Text Size",
|
|
// Text for the annotation toolbar's lock annotation button
|
|
toolbarAnnotationsLock: "Lock",
|
|
// Text for the annotation toolbar's unlock annotation button
|
|
toolbarAnnotationsUnlock: "Unlock",
|
|
// Text for the annotation toolbar's delete annotation button
|
|
toolbarAnnotationsDelete: "Delete",
|
|
// Text for the annotation toolbar's drag handle
|
|
toolbarAnnotationsDragHandle: "Drag Toolbar",
|
|
// Text for the annotation toolbar's line drawings menu button
|
|
toolbarAnnotationsLineAnnotations: "Trend Lines",
|
|
// Text for the annotation toolbar's Fibonacci drawings menu button
|
|
toolbarAnnotationsFibonacciAnnotations: "Fibonacci",
|
|
// Text for the annotation toolbar's text annotations menu button
|
|
toolbarAnnotationsTextAnnotations: "Text Annotations",
|
|
// Text for the annotation toolbar's shapes menu button
|
|
toolbarAnnotationsShapeAnnotations: "Arrows",
|
|
// Text for the annotation toolbar's measurers menu button
|
|
toolbarAnnotationsMeasurerAnnotations: "Measurers",
|
|
// Text for the annotation toolbar's callout button
|
|
toolbarAnnotationsCallout: "Callout",
|
|
// Text for the annotation toolbar's comment button
|
|
toolbarAnnotationsComment: "Comment",
|
|
// Text for the annotation toolbar's note button
|
|
toolbarAnnotationsNote: "Note",
|
|
// Text for the annotation toolbar's text button
|
|
toolbarAnnotationsText: "Text",
|
|
// Text for the annotation toolbar's arrow button
|
|
toolbarAnnotationsArrow: "Arrow",
|
|
// Text for the annotation toolbar's arrow up button
|
|
toolbarAnnotationsArrowUp: "Arrow Up",
|
|
// Text for the annotation toolbar's arrow down button
|
|
toolbarAnnotationsArrowDown: "Arrow Down",
|
|
// Text for the annotation toolbar's date range button
|
|
toolbarAnnotationsDateRange: "Date Range",
|
|
// Text for the annotation toolbar's price range button
|
|
toolbarAnnotationsPriceRange: "Price Range",
|
|
// Text for the annotation toolbar's date and price range button
|
|
toolbarAnnotationsDatePriceRange: "Date and Price",
|
|
// Text for the annotation toolbar's quick date and price range button
|
|
toolbarAnnotationsQuickDatePriceRange: "Measure",
|
|
// Text for the range toolbar's 1 month button
|
|
toolbarRange1Month: "1M",
|
|
// Aria label for the range toolbar's 1 month button
|
|
toolbarRange1MonthAria: "1 month",
|
|
// Text for the range toolbar's 3 month button
|
|
toolbarRange3Months: "3M",
|
|
// Aria label for the range toolbar's 3 month button
|
|
toolbarRange3MonthsAria: "3 months",
|
|
// Text for the range toolbar's 6 month button
|
|
toolbarRange6Months: "6M",
|
|
// Aria label for the range toolbar's 6 month button
|
|
toolbarRange6MonthsAria: "6 months",
|
|
// Text for the range toolbar's year to date button
|
|
toolbarRangeYearToDate: "YTD",
|
|
// Aria label for the range toolbar's year to date month button
|
|
toolbarRangeYearToDateAria: "Year to date",
|
|
// Text for the range toolbar's 1 year button
|
|
toolbarRange1Year: "1Y",
|
|
// Aria label for the range toolbar's 1 year button
|
|
toolbarRange1YearAria: "1 year",
|
|
// Text for the range toolbar's full range button
|
|
toolbarRangeAll: "All",
|
|
// Aria label for the range toolbar's full range button
|
|
toolbarRangeAllAria: "All",
|
|
// Text for the zoom toolbar's zoom out button
|
|
toolbarZoomZoomOut: "Zoom out",
|
|
// Text for the zoom toolbar's zoom in button
|
|
toolbarZoomZoomIn: "Zoom in",
|
|
// Text for the zoom toolbar's pan left button
|
|
toolbarZoomPanLeft: "Pan left",
|
|
// Text for the zoom toolbar's pan right button
|
|
toolbarZoomPanRight: "Pan right",
|
|
// Text for the zoom toolbar's pan to the start button
|
|
toolbarZoomPanStart: "Pan to the start",
|
|
// Text for the zoom toolbar's pan to the end button
|
|
toolbarZoomPanEnd: "Pan to the end",
|
|
// Text for the zoom toolbar's pan reset button
|
|
toolbarZoomReset: "Reset the zoom",
|
|
// Text for the context menu's download button
|
|
contextMenuDownload: "Download",
|
|
// Text for the context menu's toggle series visibility button
|
|
contextMenuToggleSeriesVisibility: "Toggle Visibility",
|
|
// Text for the context menu's toggle other series visibility button
|
|
contextMenuToggleOtherSeries: "Toggle Other Series",
|
|
// Text for the context menu's zoom to point button
|
|
contextMenuZoomToCursor: "Zoom to here",
|
|
// Text for the context menu's pan to point button
|
|
contextMenuPanToCursor: "Pan to here",
|
|
// Text for the context menu's reset zoom button
|
|
contextMenuResetZoom: "Reset zoom",
|
|
// Text for the annotation dialog's header channel tab label
|
|
dialogHeaderChannel: "Channel",
|
|
// Text for the annotation dialog's header line tab label
|
|
dialogHeaderLine: "Line",
|
|
// Text for the annotation dialog's header fibonacci retracement line tab label
|
|
dialogHeaderFibonacciRange: "Fib Retracement",
|
|
// Text for the annotation dialog's header date range tab label
|
|
dialogHeaderDateRange: "Date Range",
|
|
// Text for the annotation dialog's header price range tab label
|
|
dialogHeaderPriceRange: "Price Range",
|
|
// Text for the annotation dialog's header date and price range tab label
|
|
dialogHeaderDatePriceRange: "Date and Price",
|
|
// Text for the annotation dialog's header text tab label
|
|
dialogHeaderText: "Text",
|
|
// Text for the annotation dialog's text alignment radio label
|
|
dialogInputAlign: "Align",
|
|
// Text for the annotation dialog's color picker label
|
|
dialogInputColorPicker: "Color",
|
|
// Text for the annotation dialog's color picker alt text
|
|
dialogInputColorPickerAltText: "Text Color",
|
|
// Text for the annotation dialog's fill color picker label
|
|
dialogInputFillColorPicker: "Fill",
|
|
// Text for the annotation dialog's fill color picker alt text
|
|
dialogInputFillColorPickerAltText: "Fill Color",
|
|
// Text for the annotation dialog's extend channel start checkbox
|
|
dialogInputExtendChannelStart: "Extend channel start",
|
|
// Text for the annotation dialog's extend channel end checkbox
|
|
dialogInputExtendChannelEnd: "Extend channel end",
|
|
// Text for the annotation dialog's extend line start checkbox
|
|
dialogInputExtendLineStart: "Extend line start",
|
|
// Text for the annotation dialog's extend line end checkbox
|
|
dialogInputExtendLineEnd: "Extend line end",
|
|
// Text for the annotation dialog's extend above checkbox
|
|
dialogInputExtendAbove: "Extend above",
|
|
// Text for the annotation dialog's extend below checkbox
|
|
dialogInputExtendBelow: "Extend below",
|
|
// Text for the annotation dialog's extend left checkbox
|
|
dialogInputExtendLeft: "Extend left",
|
|
// Text for the annotation dialog's extend right checkbox
|
|
dialogInputExtendRight: "Extend right",
|
|
// Text for the annotation dialog's reverse checkbox
|
|
dialogInputReverse: "Reverse",
|
|
// Text for the annotation dialog's show fill checkbox
|
|
dialogInputShowFill: "Show Fill",
|
|
// Text for the annotation dialog's font size select box label
|
|
dialogInputFontSize: "Size",
|
|
// Text for the annotation dialog's font size select box alt text
|
|
dialogInputFontSizeAltText: "Font Size",
|
|
// Text for the annotation dialog's line style radio label
|
|
dialogInputLineStyle: "Dash",
|
|
// Text for the annotation dialog's text position radio label
|
|
dialogInputPosition: "Position",
|
|
// Text for the annotation dialog's stroke width label
|
|
dialogInputStrokeWidth: "Weight",
|
|
// Text for the annotation dialog's stroke width label
|
|
dialogInputStrokeWidthAltText: "Line Weight",
|
|
// Text for the annotation dialog's Fibonacci bands label
|
|
dialogInputFibonacciBands: "Bands",
|
|
// Text for the annotation dialog's Fibonacci bands label
|
|
dialogInputFibonacciBandsAltText: "Fibonacci Bands",
|
|
// Text for text area input placeholders
|
|
inputTextareaPlaceholder: "Add Text",
|
|
// Text for the measurer statistics date range bars value
|
|
measurerDateRangeBars: "${value}[number] bars",
|
|
// Text for the measurer statistics price range value
|
|
measurerPriceRangeValue: "${value}[number]",
|
|
// Text for the measurer statistics price range percentage
|
|
measurerPriceRangePercent: "${value}[percent]",
|
|
// Text for the measurer statistics volume value
|
|
measurerVolume: "Vol ${value}",
|
|
// Status when multiple data are under the cursor, and the user can click to cycle through which one appears in the tooltip
|
|
tooltipPaginationStatus: "${index}[number] of ${count}[number]"
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/time-interop.ts
|
|
function createTimeInterval(unit, step, epoch, utc) {
|
|
return {
|
|
unit,
|
|
step,
|
|
epoch,
|
|
utc,
|
|
every(count) {
|
|
return createTimeInterval(this.unit, (this.step ?? 1) * count, this.epoch, this.utc);
|
|
}
|
|
};
|
|
}
|
|
var cachedInstances = {};
|
|
function getTimeInterval(unit, step = 1, epoch, utc = false) {
|
|
logger_exports.warnOnce("time import is deprecated, use object notation instead");
|
|
const key = `${unit}:${step}:${epoch?.getTime() ?? 0}:${utc}`;
|
|
let instance = cachedInstances[key];
|
|
if (instance == null) {
|
|
instance = createTimeInterval(unit, step, epoch, utc);
|
|
cachedInstances[key] = instance;
|
|
}
|
|
return instance;
|
|
}
|
|
var time = {
|
|
get millisecond() {
|
|
return getTimeInterval("millisecond");
|
|
},
|
|
get second() {
|
|
return getTimeInterval("second");
|
|
},
|
|
get minute() {
|
|
return getTimeInterval("minute");
|
|
},
|
|
get hour() {
|
|
return getTimeInterval("hour");
|
|
},
|
|
get day() {
|
|
return getTimeInterval("day");
|
|
},
|
|
get monday() {
|
|
return getTimeInterval("day", 7, new Date(1970, 0, 5));
|
|
},
|
|
get tuesday() {
|
|
return getTimeInterval("day", 7, new Date(1970, 0, 6));
|
|
},
|
|
get wednesday() {
|
|
return getTimeInterval("day", 7, new Date(1970, 0, 7));
|
|
},
|
|
get thursday() {
|
|
return getTimeInterval("day", 7, new Date(1970, 0, 1));
|
|
},
|
|
get friday() {
|
|
return getTimeInterval("day", 7, new Date(1970, 0, 2));
|
|
},
|
|
get saturday() {
|
|
return getTimeInterval("day", 7, new Date(1970, 0, 3));
|
|
},
|
|
get sunday() {
|
|
return getTimeInterval("day", 7, new Date(1970, 0, 4));
|
|
},
|
|
get month() {
|
|
return getTimeInterval("month");
|
|
},
|
|
get year() {
|
|
return getTimeInterval("year");
|
|
},
|
|
get utcMillisecond() {
|
|
return getTimeInterval("millisecond", 1, void 0, true);
|
|
},
|
|
get utcSecond() {
|
|
return getTimeInterval("second", 1, void 0, true);
|
|
},
|
|
get utcMinute() {
|
|
return getTimeInterval("minute", 1, void 0, true);
|
|
},
|
|
get utcHour() {
|
|
return getTimeInterval("hour", 1, void 0, true);
|
|
},
|
|
get utcDay() {
|
|
return getTimeInterval("day", 1, void 0, true);
|
|
},
|
|
get utcMonth() {
|
|
return getTimeInterval("month", 1, void 0, true);
|
|
},
|
|
get utcYear() {
|
|
return getTimeInterval("year", 1, void 0, true);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/interpolating.ts
|
|
var interpolate = Symbol("interpolate");
|
|
var isInterpolating = (x) => x[interpolate] != null;
|
|
|
|
// packages/ag-charts-community/src/scene/bbox.ts
|
|
var _BBox = class _BBox {
|
|
constructor(x, y, width2, height2) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width2;
|
|
this.height = height2;
|
|
}
|
|
static fromObject({ x, y, width: width2, height: height2 }) {
|
|
return new _BBox(x, y, width2, height2);
|
|
}
|
|
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 (end2(box.x, box.width) > right) {
|
|
right = end2(box.x, box.width);
|
|
}
|
|
if (end2(box.y, box.height) > bottom) {
|
|
bottom = end2(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: end2(this.x, this.width),
|
|
bottom: end2(this.y, this.height),
|
|
toJSON() {
|
|
return {};
|
|
}
|
|
};
|
|
}
|
|
clone() {
|
|
const { x, y, width: width2, height: height2 } = this;
|
|
return new _BBox(x, y, width2, height2);
|
|
}
|
|
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(end2(this.x, this.width), end2(other.x, other.width));
|
|
const y1 = Math.min(end2(this.y, this.height), end2(other.y, other.height));
|
|
if (x0 > x1 || y0 > y1)
|
|
return;
|
|
return new _BBox(x0, y0, x1 - x0, y1 - y0);
|
|
}
|
|
collidesBBox(other) {
|
|
return this.x < end2(other.x, other.width) && end2(this.x, this.width) > other.x && this.y < end2(other.y, other.height) && end2(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 dx2 = x - clamp(this.x, x, end2(this.x, this.width));
|
|
const dy2 = y - clamp(this.y, y, end2(this.y, this.height));
|
|
return dx2 * dx2 + dy2 * dy2;
|
|
}
|
|
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 end2(x, width2) {
|
|
if (x === -Infinity && width2 === Infinity)
|
|
return Infinity;
|
|
return x + width2;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/canvas/canvasUtil.ts
|
|
function clearContext({
|
|
context,
|
|
pixelRatio,
|
|
width: width2,
|
|
height: height2
|
|
}) {
|
|
context.save();
|
|
try {
|
|
context.resetTransform();
|
|
context.clearRect(0, 0, Math.ceil(width2 * pixelRatio), Math.ceil(height2 * pixelRatio));
|
|
} finally {
|
|
context.restore();
|
|
}
|
|
}
|
|
function debugContext(ctx) {
|
|
if (debugLogger_exports.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(width2, height2, pixelRatio) {
|
|
return [Math.floor(width2 * pixelRatio), Math.floor(height2 * pixelRatio)];
|
|
}
|
|
var fallbackCanvas;
|
|
function getFallbackCanvas() {
|
|
const OffscreenCanvasCtor = getOffscreenCanvas();
|
|
fallbackCanvas ?? (fallbackCanvas = new OffscreenCanvasCtor(1, 1));
|
|
return fallbackCanvas;
|
|
}
|
|
var HdpiOffscreenCanvas = class {
|
|
constructor(options) {
|
|
const { width: width2, height: height2, pixelRatio, willReadFrequently = false } = options;
|
|
this.width = width2;
|
|
this.height = height2;
|
|
this.pixelRatio = pixelRatio;
|
|
const [canvasWidth, canvasHeight] = canvasDimensions(width2, height2, 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, dx2 = 0, dy2 = 0) {
|
|
return context.drawImage(this.canvas, dx2, dy2);
|
|
}
|
|
transferToImageBitmap() {
|
|
if (this.canvas.width < 1 || this.canvas.height < 1) {
|
|
return getFallbackCanvas().transferToImageBitmap();
|
|
}
|
|
return this.canvas.transferToImageBitmap();
|
|
}
|
|
resize(width2, height2, pixelRatio) {
|
|
if (!(width2 > 0 && height2 > 0))
|
|
return;
|
|
const { canvas, context } = this;
|
|
if (width2 !== this.width || height2 !== this.height || pixelRatio !== this.pixelRatio) {
|
|
const [canvasWidth, canvasHeight] = canvasDimensions(width2, height2, pixelRatio);
|
|
canvas.width = canvasWidth;
|
|
canvas.height = canvasHeight;
|
|
}
|
|
context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
|
this.width = width2;
|
|
this.height = height2;
|
|
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/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 length2 = Math.min(aArray.length, bArray.length);
|
|
for (let i = 0; i < length2; i += 1) {
|
|
const diff9 = cmp(aArray[i], bArray[i]);
|
|
if (diff9 !== 0)
|
|
return diff9;
|
|
}
|
|
return cmp(aArray.length, bArray.length);
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/node.ts
|
|
var PointerEvents = /* @__PURE__ */ ((PointerEvents15) => {
|
|
PointerEvents15[PointerEvents15["All"] = 0] = "All";
|
|
PointerEvents15[PointerEvents15["None"] = 1] = "None";
|
|
return PointerEvents15;
|
|
})(PointerEvents || {});
|
|
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, width2, height2) {
|
|
const svg = node?.toSVG();
|
|
if (svg == null || !svg.elements.length && !svg.defs?.length)
|
|
return;
|
|
const root = createSvgElement("svg");
|
|
root.setAttribute("width", String(width2));
|
|
root.setAttribute("height", String(height2));
|
|
root.setAttribute("viewBox", `0 0 ${width2} ${height2}`);
|
|
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_exports.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_exports.logGroup(
|
|
`Property changed multiple times before render: ${this.constructor.name}.${property} (${sources.length}x)`,
|
|
() => {
|
|
for (const source of sources) {
|
|
logger_exports.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 Node2 = _Node;
|
|
|
|
// 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
|
|
function visibleTickRange(ticks, reversed, visibleRange) {
|
|
if (visibleRange == null || visibleRange[0] === 0 && visibleRange[1] === 1)
|
|
return;
|
|
const vt0 = clamp(0, Math.floor(visibleRange[0] * ticks.length), ticks.length);
|
|
const vt1 = clamp(0, Math.ceil(visibleRange[1] * ticks.length), ticks.length);
|
|
const t0 = reversed ? ticks.length - vt1 : vt0;
|
|
const t1 = reversed ? ticks.length - vt0 : vt1;
|
|
return [t0, t1];
|
|
}
|
|
function visibleTickSliceIndices(ticks, reversed, visibleRange) {
|
|
return visibleTickRange(ticks, reversed, visibleRange) ?? [0, ticks.length];
|
|
}
|
|
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 color2 = Color.fromString(v);
|
|
const [l, c, h] = Color.RGBtoOKLCH(color2.r, color2.g, color2.b);
|
|
return { l, c, h, a: color2.a };
|
|
};
|
|
var delta = 1e-6;
|
|
var isAchromatic = (x) => x.c < delta || x.l < delta || x.l > 1 - delta;
|
|
var interpolateOklch = (x, y, d) => {
|
|
d = clamp(0, d, 1);
|
|
let h;
|
|
if (isAchromatic(x)) {
|
|
h = y.h;
|
|
} else if (isAchromatic(y)) {
|
|
h = x.h;
|
|
} else {
|
|
const xH = x.h;
|
|
let yH = y.h;
|
|
const deltaH = y.h - x.h;
|
|
if (deltaH > 180) {
|
|
yH -= 360;
|
|
} else if (deltaH < -180) {
|
|
yH += 360;
|
|
}
|
|
h = xH * (1 - d) + yH * d;
|
|
}
|
|
const c = x.c * (1 - d) + y.c * d;
|
|
const l = x.l * (1 - d) + y.l * d;
|
|
const a = x.a * (1 - d) + y.a * d;
|
|
return 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: range3 } = this;
|
|
if (domain.length < 2) {
|
|
logger_exports.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) {
|
|
logger_exports.warnOnce("`colorDomain` values should be supplied in ascending order.");
|
|
domain.sort((a2, b2) => a2 - b2);
|
|
break;
|
|
}
|
|
}
|
|
if (range3.length < domain.length) {
|
|
for (let i = range3.length; i < domain.length; i++) {
|
|
range3.push(range3.length > 0 ? range3[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: range3, parsedRange } = this;
|
|
const d0 = domain[0];
|
|
const d1 = domain.at(-1);
|
|
const r0 = range3[0];
|
|
const r1 = range3.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 / (range3.length - 1);
|
|
index = range3.length <= 2 ? 0 : Math.min(Math.floor(t * (range3.length - 1)), range3.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) {
|
|
logger_exports.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 gradient2 = this.createCanvasGradient(ctx, bbox, params);
|
|
if (gradient2 == null)
|
|
return;
|
|
const isOkLch = colorSpace === "oklch";
|
|
const step = 0.05;
|
|
let c0 = stops[0];
|
|
gradient2.addColorStop(c0.stop, c0.color);
|
|
for (let i = 1; i < stops.length; i += 1) {
|
|
const c1 = stops[i];
|
|
if (isOkLch) {
|
|
const scale2 = new ColorScale();
|
|
scale2.domain = [c0.stop, c1.stop];
|
|
scale2.range = [c0.color, c1.color];
|
|
for (let stop = c0.stop + step; stop < c1.stop; stop += step) {
|
|
gradient2.addColorStop(stop, scale2.convert(stop) ?? "transparent");
|
|
}
|
|
}
|
|
gradient2.addColorStop(c1.stop, c1.color);
|
|
c0 = c1;
|
|
}
|
|
if ("createPattern" in gradient2) {
|
|
gradient2 = gradient2.createPattern();
|
|
}
|
|
this._cache = { ctx, bbox, gradient: gradient2 };
|
|
return gradient2;
|
|
}
|
|
toSvg(shapeBbox) {
|
|
const bbox = this.bbox ?? shapeBbox;
|
|
const gradient2 = this.createSvgGradient(bbox);
|
|
for (const { stop: offset, color: color2 } of this.stops) {
|
|
const stop = createSvgElement("stop");
|
|
stop.setAttribute("offset", `${offset}`);
|
|
stop.setAttribute("stop-color", `${color2}`);
|
|
gradient2.appendChild(stop);
|
|
}
|
|
return gradient2;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/gradient/conicGradient.ts
|
|
var ConicGradient = class extends Gradient {
|
|
constructor(colorSpace, stops, angle2 = 0, bbox) {
|
|
super(colorSpace, stops, bbox);
|
|
this.angle = angle2;
|
|
}
|
|
createCanvasGradient(ctx, bbox, params) {
|
|
const angleOffset = -90;
|
|
const { angle: angle2 } = this;
|
|
const radians = normalizeAngle360FromDegrees(angle2 + 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 createSvgElement("linearGradient");
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/gradient/linearGradient.ts
|
|
var LinearGradient = class extends Gradient {
|
|
constructor(colorSpace, stops, angle2 = 0, bbox) {
|
|
super(colorSpace, stops, bbox);
|
|
this.angle = angle2;
|
|
}
|
|
getGradientPoints(bbox) {
|
|
const angleOffset = 90;
|
|
const { angle: angle2 } = this;
|
|
const radians = normalizeAngle360FromDegrees(angle2 + 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 gradient2 = createSvgElement("linearGradient");
|
|
gradient2.setAttribute("x1", String(x0));
|
|
gradient2.setAttribute("y1", String(y0));
|
|
gradient2.setAttribute("x2", String(x1));
|
|
gradient2.setAttribute("y2", String(y1));
|
|
gradient2.setAttribute("gradientUnits", "userSpaceOnUse");
|
|
return gradient2;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/gradient/radialGradient.ts
|
|
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 gradient2 = createSvgElement("radialGradient");
|
|
gradient2.setAttribute("cx", String(cx));
|
|
gradient2.setAttribute("cy", String(cy));
|
|
gradient2.setAttribute("r", String(Math.hypot(bbox.width * 0.5, bbox.height * 0.5) / Math.SQRT2));
|
|
gradient2.setAttribute("gradientUnits", "userSpaceOnUse");
|
|
return gradient2;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/gradient/stops.ts
|
|
var StopProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.color = "black";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StopProperties.prototype, "stop", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], 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((colorStop2, i) => {
|
|
const { stop } = colorStop2;
|
|
const nextColor = colorStops.at(i + 1)?.color;
|
|
return nextColor == null ? [colorStop2] : [colorStop2, { stop, color: nextColor }];
|
|
});
|
|
}
|
|
function getDefaultColorStops(defaultColorStops, fillMode) {
|
|
const stopOffset = fillMode === "discrete" ? 1 : 0;
|
|
const colorStops = defaultColorStops.map(
|
|
(color2, index, { length: length2 }) => ({
|
|
stop: (index + stopOffset) / (length2 - 1 + stopOffset),
|
|
color: color2
|
|
})
|
|
);
|
|
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)) {
|
|
logger_exports.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 colorStop2 = 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 = colorStop2?.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 color2 = fill?.color;
|
|
const stop = stops[i];
|
|
if (color2 != null) {
|
|
lastDefinedColor = color2;
|
|
} else if (lastDefinedColor == null) {
|
|
if (colorScale == null) {
|
|
colorScale = new ColorScale();
|
|
colorScale.domain = [0, 1];
|
|
colorScale.range = defaultColorStops;
|
|
}
|
|
color2 = colorScale.convert(stop);
|
|
} else {
|
|
color2 = lastDefinedColor;
|
|
}
|
|
return { stop, color: color2 };
|
|
});
|
|
return fillMode === "discrete" ? discreteColorStops(colorStops) : colorStops;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/image/image.ts
|
|
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, width2, height2) {
|
|
if (!image)
|
|
return null;
|
|
const [renderedWidth, renderedHeight] = this.getSize(image.width, image.height, width2, height2);
|
|
if (renderedWidth < 1 || renderedHeight < 1) {
|
|
logger_exports.warnOnce("Image fill is too small to render, ignoring.");
|
|
return null;
|
|
}
|
|
return ctx.createPattern(image, this.repeat);
|
|
}
|
|
getSize(imageWidth, imageHeight, width2, height2) {
|
|
const { fit } = this;
|
|
let dw = imageWidth;
|
|
let dh = imageHeight;
|
|
let scale2 = 1;
|
|
const shapeAspectRatio = width2 / height2;
|
|
const imageAspectRatio = imageWidth / imageHeight;
|
|
if (fit === "stretch" || imageWidth === 0 || imageHeight === 0) {
|
|
dw = width2;
|
|
dh = height2;
|
|
} else if (fit === "contain") {
|
|
scale2 = imageAspectRatio > shapeAspectRatio ? width2 / imageWidth : height2 / imageHeight;
|
|
} else if (fit === "cover") {
|
|
scale2 = imageAspectRatio > shapeAspectRatio ? height2 / imageHeight : width2 / imageWidth;
|
|
}
|
|
return [Math.max(1, dw * scale2), Math.max(1, dh * scale2)];
|
|
}
|
|
setImageTransform(pattern, bbox) {
|
|
if (typeof pattern === "string")
|
|
return;
|
|
const { url, rotation, width: width2, height: height2 } = this;
|
|
const image = this.imageLoader?.loadImage(url);
|
|
if (!image) {
|
|
return;
|
|
}
|
|
const angle2 = normalizeAngle360FromDegrees(rotation);
|
|
const cos = Math.cos(angle2);
|
|
const sin = Math.sin(angle2);
|
|
const [renderedWidth, renderedHeight] = this.getSize(
|
|
image.width,
|
|
image.height,
|
|
width2 ?? bbox.width,
|
|
height2 ?? 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 width2 = this.width ?? shapeWidth;
|
|
const height2 = this.height ?? shapeHeight;
|
|
const cache = this._cache;
|
|
if (cache?.ctx === ctx && cache.width === width2 && cache.height === height2) {
|
|
return cache.pattern;
|
|
}
|
|
const image = this.imageLoader?.loadImage(this.url, node);
|
|
const pattern = this.createCanvasImage(ctx, image, width2, height2);
|
|
if (pattern == null)
|
|
return;
|
|
this._cache = { ctx, pattern, width: width2, height: height2 };
|
|
return pattern;
|
|
}
|
|
toSvg(bbox, pixelRatio) {
|
|
const { url, rotation, backgroundFill, backgroundFillOpacity } = this;
|
|
const { x, y, width: width2, height: height2 } = bbox;
|
|
const pattern = createSvgElement("pattern");
|
|
pattern.setAttribute("viewBox", `0 0 ${width2} ${height2}`);
|
|
pattern.setAttribute("x", String(x));
|
|
pattern.setAttribute("y", String(y));
|
|
pattern.setAttribute("width", String(width2));
|
|
pattern.setAttribute("height", String(height2));
|
|
pattern.setAttribute("patternUnits", "userSpaceOnUse");
|
|
const rect2 = createSvgElement("rect");
|
|
rect2.setAttribute("x", "0");
|
|
rect2.setAttribute("y", "0");
|
|
rect2.setAttribute("width", String(width2));
|
|
rect2.setAttribute("height", String(height2));
|
|
rect2.setAttribute("fill", backgroundFill);
|
|
rect2.setAttribute("fill-opacity", String(backgroundFillOpacity));
|
|
pattern.appendChild(rect2);
|
|
const image = createSvgElement("image");
|
|
image.setAttribute("href", url);
|
|
image.setAttribute("x", "0");
|
|
image.setAttribute("y", "0");
|
|
image.setAttribute("width", String(width2));
|
|
image.setAttribute("height", String(height2));
|
|
image.setAttribute("preserveAspectRatio", "none");
|
|
image.setAttribute("transform", `scale(${1 / pixelRatio}) rotate(${rotation}, ${width2 / 2}, ${height2 / 2})`);
|
|
pattern.appendChild(image);
|
|
return pattern;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/svg.ts
|
|
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) {
|
|
logger_exports.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 {
|
|
logger_exports.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, delta5 = 1e-6) {
|
|
if (Math.abs(a) < delta5) {
|
|
return linearRoot(b, c);
|
|
}
|
|
const D = b * b - 4 * a * c;
|
|
const roots = [];
|
|
if (Math.abs(D) < delta5) {
|
|
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, delta5 = 1e-6) {
|
|
if (Math.abs(a) < delta5) {
|
|
return quadraticRoots(b, c, d, delta5);
|
|
}
|
|
const A = b / a;
|
|
const B = c / a;
|
|
const C2 = d / a;
|
|
const Q = (3 * B - A * A) / 9;
|
|
const R = (9 * A * B - 27 * C2 - 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) < delta5) {
|
|
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 C2 = 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] + C2;
|
|
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, width2, height2) {
|
|
this.moveTo(x, y);
|
|
this.lineTo(x + width2, y);
|
|
this.lineTo(x + width2, y + height2);
|
|
this.lineTo(x, y + height2);
|
|
this.closePath();
|
|
}
|
|
roundRect(x, y, width2, height2, radii) {
|
|
radii = Math.min(radii, width2 / 2, height2 / 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 + width2 - radii, y);
|
|
this.arc(x + width2 - radii, y + radii, radii, 1.5 * Math.PI, 2 * Math.PI);
|
|
this.lineTo(x + width2, y + radii);
|
|
this.lineTo(x + width2, y + height2 - radii);
|
|
this.arc(x + width2 - radii, y + height2 - radii, radii, 0, Math.PI / 2);
|
|
this.lineTo(x + width2 - radii, y + height2);
|
|
this.lineTo(x + radii, y + height2);
|
|
this.arc(x + +radii, y + height2 - radii, radii, Math.PI / 2, Math.PI);
|
|
this.lineTo(x, y + height2 - 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 distanceSquared2 = (sx - this.cx) ** 2 + (sy - this.cy) ** 2;
|
|
if (!this.openedPath) {
|
|
this.moveTo(sx, sy);
|
|
} else if (distanceSquared2 > 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 dx2 = relative ? cx : 0;
|
|
const dy2 = relative ? cy : 0;
|
|
switch (command.toLowerCase()) {
|
|
case "m":
|
|
this.moveTo(dx2 + params[0], dy2 + params[1]);
|
|
cx = dx2 + params[0];
|
|
cy = dy2 + params[1];
|
|
sx = cx;
|
|
sy = cy;
|
|
break;
|
|
case "c":
|
|
this.cubicCurveTo(
|
|
dx2 + params[0],
|
|
dy2 + params[1],
|
|
dx2 + params[2],
|
|
dy2 + params[3],
|
|
dx2 + params[4],
|
|
dy2 + params[5]
|
|
);
|
|
cpx = dx2 + params[2];
|
|
cpy = dy2 + params[3];
|
|
cx = dx2 + params[4];
|
|
cy = dy2 + params[5];
|
|
break;
|
|
case "s":
|
|
this.cubicCurveTo(
|
|
cx + cx - cpx,
|
|
cy + cy - cpy,
|
|
dx2 + params[0],
|
|
dy2 + params[1],
|
|
dx2 + params[2],
|
|
dy2 + params[3]
|
|
);
|
|
cpx = dx2 + params[0];
|
|
cpy = dy2 + params[1];
|
|
cx = dx2 + params[2];
|
|
cy = dy2 + params[3];
|
|
break;
|
|
case "q":
|
|
this.cubicCurveTo(
|
|
(dx2 + 2 * params[0]) / 3,
|
|
(dy2 + 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],
|
|
dx2 + params[5],
|
|
dy2 + params[6]
|
|
);
|
|
cx = dx2 + params[5];
|
|
cy = dy2 + params[6];
|
|
break;
|
|
case "h":
|
|
this.lineTo(dx2 + params[0], cy);
|
|
cx = dx2 + params[0];
|
|
break;
|
|
case "l":
|
|
this.lineTo(dx2 + params[0], dy2 + params[1]);
|
|
cx = dx2 + params[0];
|
|
cy = dy2 + params[1];
|
|
break;
|
|
case "v":
|
|
this.lineTo(cx, dy2 + params[0]);
|
|
cy = dy2 + 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 dx2 = (x1 - x2) / 2;
|
|
const dy2 = (y1 - y2) / 2;
|
|
const sin = Math.sin(rotation);
|
|
const cos = Math.cos(rotation);
|
|
const rotX = cos * dx2 + sin * dy2;
|
|
const rotY = -sin * dx2 + cos * dy2;
|
|
const normX = rotX / rx;
|
|
const normY = rotY / ry;
|
|
let scale2 = normX * normX + normY * normY;
|
|
let cx = (x1 + x2) / 2;
|
|
let cy = (y1 + y2) / 2;
|
|
let cpx = 0;
|
|
let cpy = 0;
|
|
if (scale2 >= 1) {
|
|
scale2 = Math.sqrt(scale2);
|
|
rx *= scale2;
|
|
ry *= scale2;
|
|
} else {
|
|
scale2 = Math.sqrt(1 / scale2 - 1);
|
|
if (fA === fS)
|
|
scale2 = -scale2;
|
|
cpx = scale2 * rx * normY;
|
|
cpy = -scale2 * 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/util/pixel.ts
|
|
function align(pixelRatio, start2, length2) {
|
|
const alignedStart = Math.round(start2 * pixelRatio) / pixelRatio;
|
|
if (length2 == null) {
|
|
return alignedStart;
|
|
} else if (length2 === 0) {
|
|
return 0;
|
|
} else if (length2 < 1) {
|
|
return Math.ceil(length2 * pixelRatio) / pixelRatio;
|
|
}
|
|
return Math.round((length2 + start2) * pixelRatio) / pixelRatio - alignedStart;
|
|
}
|
|
function alignBefore(pixelRatio, start2) {
|
|
return Math.floor(start2 * pixelRatio) / pixelRatio;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/pattern/patterns.ts
|
|
function drawPatternUnitPolygon(path, params, moves) {
|
|
const { width: width2, height: height2, padding: padding2, strokeWidth } = params;
|
|
const x0 = width2 / 2;
|
|
const y0 = height2 / 2;
|
|
const w = Math.max(1, width2 - padding2 - strokeWidth / 2);
|
|
const h = Math.max(1, height2 - padding2 - strokeWidth / 2);
|
|
let didMove = false;
|
|
for (const [dx2, dy2] of moves) {
|
|
const x = x0 + (dx2 - 0.5) * w;
|
|
const y = y0 + (dy2 - 0.5) * h;
|
|
if (didMove) {
|
|
path.lineTo(x, y);
|
|
} else {
|
|
path.moveTo(x, y);
|
|
}
|
|
didMove = true;
|
|
}
|
|
path.closePath();
|
|
}
|
|
var PATTERNS = {
|
|
circles(path, { width: width2, strokeWidth, padding: padding2 }) {
|
|
const c = width2 / 2;
|
|
const r = Math.max(1, c - padding2 - strokeWidth / 2);
|
|
path.arc(c, c, r, 0, Math.PI * 2);
|
|
},
|
|
squares(path, { width: width2, height: height2, pixelRatio, padding: padding2, strokeWidth }) {
|
|
const offset = padding2 + strokeWidth / 2;
|
|
path.moveTo(align(pixelRatio, offset), align(pixelRatio, offset));
|
|
path.lineTo(align(pixelRatio, width2 - offset), align(pixelRatio, offset));
|
|
path.lineTo(align(pixelRatio, width2 - offset), align(pixelRatio, height2 - offset));
|
|
path.lineTo(align(pixelRatio, offset), align(pixelRatio, height2 - 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: width2, height: height2, padding: padding2 }) {
|
|
const spikes = 5;
|
|
const outerRadius = Math.max(1, (width2 - padding2) / 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 angle2 = i * Math.PI / spikes - rotation;
|
|
const xCoordinate = width2 / 2 + Math.cos(angle2) * radius;
|
|
const yCoordinate = height2 / 2 + Math.sin(angle2) * radius;
|
|
path.lineTo(xCoordinate, yCoordinate);
|
|
}
|
|
path.closePath();
|
|
},
|
|
hearts(path, { width: width2, height: height2, padding: padding2 }) {
|
|
const r = Math.max(1, width2 / 4 - padding2 / 2);
|
|
const x = width2 / 2;
|
|
const y = height2 / 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: width2, height: height2, pixelRatio, strokeWidth }) {
|
|
const x = align(pixelRatio, width2 / 2) - strokeWidth % 2 / 2;
|
|
path.moveTo(x, 0);
|
|
path.lineTo(x, height2);
|
|
},
|
|
"horizontal-lines"(path, { width: width2, height: height2, pixelRatio, strokeWidth }) {
|
|
const y = align(pixelRatio, height2 / 2) - strokeWidth % 2 / 2;
|
|
path.moveTo(0, y);
|
|
path.lineTo(width2, y);
|
|
},
|
|
"forward-slanted-lines"(path, { width: width2, height: height2, strokeWidth }) {
|
|
const angle2 = Math.atan2(height2, width2);
|
|
const insetX = strokeWidth * Math.cos(angle2);
|
|
const insetY = strokeWidth * Math.sin(angle2);
|
|
path.moveTo(-insetX, insetY);
|
|
path.lineTo(insetX, -insetY);
|
|
path.moveTo(-insetX, height2 + insetY);
|
|
path.lineTo(width2 + insetX, -insetY);
|
|
path.moveTo(width2 - insetX, height2 + insetY);
|
|
path.lineTo(width2 + insetX, height2 - insetY);
|
|
},
|
|
"backward-slanted-lines"(path, { width: width2, height: height2, strokeWidth }) {
|
|
const angle2 = Math.atan2(height2, width2);
|
|
const insetX = strokeWidth * Math.cos(angle2);
|
|
const insetY = strokeWidth * Math.sin(angle2);
|
|
path.moveTo(width2 - insetX, -insetY);
|
|
path.lineTo(width2 + insetX, insetY);
|
|
path.moveTo(-insetX, -insetY);
|
|
path.lineTo(width2 + insetX, height2 + insetY);
|
|
path.moveTo(-insetX, height2 - insetY);
|
|
path.lineTo(insetX, height2 + 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: width2, height: height2, padding: padding2, 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: width2, height: height2, pixelRatio, strokeWidth, padding: padding2 });
|
|
}
|
|
return path;
|
|
}
|
|
renderStroke(path2d, ctx) {
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity } = this;
|
|
if (!strokeWidth)
|
|
return;
|
|
ctx.strokeStyle = stroke3;
|
|
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: width2, height: height2, scale: scale2, backgroundFill, backgroundFillOpacity } = this;
|
|
if (width2 * scale2 < 1 || height2 * scale2 < 1) {
|
|
logger_exports.warnOnce("Pattern fill is too small to render, ignoring.");
|
|
return null;
|
|
}
|
|
const offscreenPattern = new HdpiOffscreenCanvas({ width: width2, height: height2, pixelRatio: pixelRatio * scale2 });
|
|
const offscreenPatternCtx = offscreenPattern.context;
|
|
if (backgroundFill !== "none") {
|
|
offscreenPatternCtx.fillStyle = backgroundFill;
|
|
offscreenPatternCtx.globalAlpha = backgroundFillOpacity;
|
|
offscreenPatternCtx.fillRect(0, 0, width2, height2);
|
|
}
|
|
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 angle2 = normalizeAngle360FromDegrees(this.rotation);
|
|
const scale2 = 1 / pixelRatio;
|
|
const cos = Math.cos(angle2) * scale2;
|
|
const sin = Math.sin(angle2) * scale2;
|
|
const DOMMatrixCtor = getDOMMatrix();
|
|
pattern.setTransform(new DOMMatrixCtor([cos, sin, -sin, cos, tx, ty]));
|
|
}
|
|
createPattern(ctx, pixelRatio) {
|
|
if (this._cache?.ctx === ctx && this._cache.pixelRatio === pixelRatio) {
|
|
return this._cache.pattern;
|
|
}
|
|
const pattern = this.createCanvasPattern(ctx, pixelRatio);
|
|
if (pattern == null)
|
|
return;
|
|
this._cache = { ctx, pattern, pixelRatio };
|
|
return pattern;
|
|
}
|
|
toSvg() {
|
|
const {
|
|
width: width2,
|
|
height: height2,
|
|
fill,
|
|
fillOpacity,
|
|
backgroundFill,
|
|
backgroundFillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
rotation,
|
|
scale: scale2
|
|
} = this;
|
|
const pattern = createSvgElement("pattern");
|
|
pattern.setAttribute("viewBox", `0 0 ${width2} ${height2}`);
|
|
pattern.setAttribute("width", String(width2));
|
|
pattern.setAttribute("height", String(height2));
|
|
pattern.setAttribute("patternUnits", "userSpaceOnUse");
|
|
const rect2 = createSvgElement("rect");
|
|
rect2.setAttribute("x", "0");
|
|
rect2.setAttribute("y", "0");
|
|
rect2.setAttribute("width", String(width2));
|
|
rect2.setAttribute("height", String(height2));
|
|
rect2.setAttribute("fill", backgroundFill);
|
|
rect2.setAttribute("fill-opacity", String(backgroundFillOpacity));
|
|
pattern.appendChild(rect2);
|
|
const path = createSvgElement("path");
|
|
path.setAttribute("fill", fill);
|
|
path.setAttribute("fill-opacity", String(fillOpacity));
|
|
path.setAttribute("stroke-opacity", String(strokeOpacity));
|
|
path.setAttribute("stroke", stroke3);
|
|
path.setAttribute("stroke-width", String(strokeWidth));
|
|
path.setAttribute("transform", `rotate(${rotation}) scale(${scale2})`);
|
|
path.setAttribute("d", this.getPath(1).toSVG());
|
|
pattern.appendChild(path);
|
|
return pattern;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/shape/svgUtils.ts
|
|
function setSvgFontAttributes(element2, options) {
|
|
const { fontStyle, fontWeight: fontWeight2, fontSize, fontFamily } = options;
|
|
if (fontStyle)
|
|
element2.setAttribute("font-style", fontStyle);
|
|
if (fontWeight2)
|
|
element2.setAttribute("font-weight", String(fontWeight2));
|
|
if (fontSize != null)
|
|
element2.setAttribute("font-size", String(fontSize));
|
|
if (fontFamily)
|
|
element2.setAttribute("font-family", fontFamily);
|
|
}
|
|
function setSvgStrokeAttributes(element2, options) {
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity } = options;
|
|
if (stroke3)
|
|
element2.setAttribute("stroke", stroke3);
|
|
if (strokeWidth != null)
|
|
element2.setAttribute("stroke-width", String(strokeWidth));
|
|
if (strokeOpacity != null)
|
|
element2.setAttribute("stroke-opacity", String(strokeOpacity));
|
|
}
|
|
function setSvgLineDashAttributes(element2, options) {
|
|
const { lineDash, lineDashOffset } = options;
|
|
if (lineDash?.some((d) => d !== 0)) {
|
|
const lineDashArray = lineDash.length % 2 === 1 ? [...lineDash, ...lineDash] : lineDash;
|
|
element2.setAttribute("stroke-dasharray", lineDashArray.join(" "));
|
|
if (lineDashOffset != null)
|
|
element2.setAttribute("stroke-dashoffset", String(lineDashOffset));
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/shape/shape.ts
|
|
var _Shape = class _Shape extends Node2 {
|
|
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: gradient2 = "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 (gradient2) {
|
|
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 (objectsEqual(this._cachedFill ?? {}, this.fill)) {
|
|
return;
|
|
}
|
|
}
|
|
this.fillGradient = this.getGradient(this.fill);
|
|
this.fillPattern = this.getPattern(this.fill);
|
|
this.fillImage = this.getImage(this.fill);
|
|
this._cachedFill = this.fill;
|
|
}
|
|
// optimised field accessor
|
|
onStrokeChange() {
|
|
this.strokeGradient = this.getGradient(this.stroke);
|
|
}
|
|
// optimised field accessor
|
|
/**
|
|
* Returns a device-pixel aligned coordinate (or length if length is supplied).
|
|
*
|
|
* NOTE: Not suitable for strokes, since the stroke needs to be offset to the middle
|
|
* of a device pixel.
|
|
*/
|
|
align(start2, length2) {
|
|
return align(this.layerManager?.canvas?.pixelRatio ?? 1, start2, length2);
|
|
}
|
|
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: stroke3, __strokeOpacity: strokeOpacity = 1, strokeGradient, __opacity: opacity = 1 } = this;
|
|
ctx.strokeStyle = strokeGradient?.createGradient(ctx, this.getBBox()) ?? (typeof stroke3 === "string" ? stroke3 : 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: stroke3,
|
|
__strokeWidth: strokeWidth = 0,
|
|
__strokeOpacity: strokeOpacity = 1,
|
|
__lineDash: lineDash,
|
|
__lineDashOffset: lineDashOffset,
|
|
__lineCap: lineCap,
|
|
__lineJoin: lineJoin,
|
|
__miterLimit: miterLimit
|
|
} = this;
|
|
if (stroke3 != null && stroke3 !== "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(element2, defs) {
|
|
const { fill, fillOpacity } = this;
|
|
if (typeof fill === "string") {
|
|
element2.setAttribute("fill", fill);
|
|
} else if (isGradientFill(fill) && this.fillGradient) {
|
|
defs ?? (defs = []);
|
|
const gradient2 = this.fillGradient.toSvg(this.fillBBox ?? this.getBBox());
|
|
const id = generateUUID();
|
|
gradient2.setAttribute("id", id);
|
|
defs.push(gradient2);
|
|
element2.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);
|
|
element2.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);
|
|
element2.setAttribute("fill", `url(#${id})`);
|
|
} else {
|
|
element2.setAttribute("fill", "none");
|
|
}
|
|
element2.setAttribute("fill-opacity", String(fillOpacity));
|
|
return defs;
|
|
}
|
|
applySvgStrokeAttributes(element2) {
|
|
const { stroke: stroke3, strokeOpacity, strokeWidth, lineDash, lineDashOffset } = this;
|
|
setSvgStrokeAttributes(element2, { stroke: isString(stroke3) ? stroke3 : void 0, strokeOpacity, strokeWidth });
|
|
setSvgLineDashAttributes(element2, { 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(style2, fillBBox, fillParams) {
|
|
const opacity = style2?.opacity ?? 1;
|
|
const fill = style2?.fill;
|
|
const computedFillOpacity = (style2?.fillOpacity ?? 1) * opacity;
|
|
const computedStrokeOpacity = (style2?.strokeOpacity ?? 1) * opacity;
|
|
const computedStrokeWidth = style2?.strokeWidth ?? 0;
|
|
const computedLineDashOffset = style2?.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 !== style2?.lineDash) {
|
|
this.__lineDash = style2?.lineDash;
|
|
hasDirectChanges = true;
|
|
}
|
|
this.setFillProperties(fill, fillBBox, fillParams);
|
|
if (fill !== this.fill) {
|
|
this.fill = fill;
|
|
}
|
|
if (style2?.stroke !== this.stroke) {
|
|
this.stroke = style2?.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([
|
|
DeclaredSceneChangeDetection()
|
|
], _Shape.prototype, "drawingMode", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], _Shape.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], _Shape.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
DeclaredSceneObjectChangeDetection({
|
|
equals: objectsEqual,
|
|
changeCb: _Shape.handleFillChange
|
|
})
|
|
], _Shape.prototype, "fill", 2);
|
|
__decorateClass([
|
|
SceneObjectChangeDetection({ equals: objectsEqual, changeCb: _Shape.handleStrokeChange })
|
|
], _Shape.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], _Shape.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
SceneArrayChangeDetection()
|
|
], _Shape.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], _Shape.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], _Shape.prototype, "lineCap", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], _Shape.prototype, "lineJoin", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], _Shape.prototype, "miterLimit", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection({ convertor: (v) => clamp(0, v ?? 1, 1) })
|
|
], _Shape.prototype, "opacity", 2);
|
|
__decorateClass([
|
|
SceneObjectChangeDetection({ equals: TRIPLE_EQ, checkDirtyOnAssignment: true })
|
|
], _Shape.prototype, "fillShadow", 2);
|
|
__decorateClass([
|
|
DeclaredSceneObjectChangeDetection({ equals: boxesEqual, changeCb: (s) => s.onFillChange() })
|
|
], _Shape.prototype, "fillBBox", 2);
|
|
__decorateClass([
|
|
DeclaredSceneObjectChangeDetection({ equals: objectsEqual, changeCb: (s) => s.onFillChange() })
|
|
], _Shape.prototype, "fillParams", 2);
|
|
var Shape = _Shape;
|
|
|
|
// packages/ag-charts-community/src/scene/matrix.ts
|
|
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, C2) {
|
|
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];
|
|
C2 = C2 ?? A;
|
|
C2[0] = a;
|
|
C2[1] = b;
|
|
C2[2] = c;
|
|
C2[3] = d;
|
|
C2[4] = e;
|
|
C2[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 = createSvgElement("g");
|
|
g.append(...svg.elements);
|
|
const [a, b, c, d, e, f] = matrix.e;
|
|
g.setAttribute("transform", `matrix(${a} ${b} ${c} ${d} ${e} ${f})`);
|
|
return {
|
|
elements: [g],
|
|
defs: svg.defs
|
|
};
|
|
}
|
|
}
|
|
_a = MATRIX_TRANSFORM_TYPE, _b = TRANSFORM_MATRIX;
|
|
MatrixTransformInternal[_a] = true;
|
|
return MatrixTransformInternal;
|
|
}
|
|
function Rotatable(Parent) {
|
|
var _a;
|
|
const ParentNode = Parent;
|
|
const ROTATABLE_MATRIX = Symbol("matrix_rotation");
|
|
class RotatableInternal extends MatrixTransform(ParentNode) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this[_a] = new Matrix();
|
|
this.rotationCenterX = 0;
|
|
this.rotationCenterY = 0;
|
|
this.rotation = 0;
|
|
}
|
|
updateMatrix(matrix) {
|
|
super.updateMatrix(matrix);
|
|
const { rotation, rotationCenterX, rotationCenterY } = this;
|
|
if (rotation === 0)
|
|
return;
|
|
Matrix.updateTransformMatrix(this[ROTATABLE_MATRIX], 1, 1, rotation, 0, 0, {
|
|
rotationCenterX,
|
|
rotationCenterY
|
|
});
|
|
matrix.multiplySelf(this[ROTATABLE_MATRIX]);
|
|
}
|
|
}
|
|
_a = ROTATABLE_MATRIX;
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], RotatableInternal.prototype, "rotationCenterX", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], RotatableInternal.prototype, "rotationCenterY", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], RotatableInternal.prototype, "rotation", 2);
|
|
return RotatableInternal;
|
|
}
|
|
function isScalable(node) {
|
|
return "scalingX" in node && "scalingY" in node && "scalingCenterX" in node && "scalingCenterY" in node;
|
|
}
|
|
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 Node2 {
|
|
// 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(Node2.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 padding2 = 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 - padding2) - originX;
|
|
const y = alignBefore(pixelRatio, originY + bbox.y - padding2) - originY;
|
|
const width2 = Math.ceil(bbox.x + bbox.width - x + padding2);
|
|
const height2 = Math.ceil(bbox.y + bbox.height - y + padding2);
|
|
return new BBox(x, y, width2, height2);
|
|
}
|
|
prepareSharedCanvas(width2, height2, pixelRatio) {
|
|
if (sharedOffscreenCanvas?.pixelRatio === pixelRatio) {
|
|
sharedOffscreenCanvas.resize(width2, height2, pixelRatio);
|
|
} else {
|
|
sharedOffscreenCanvas = new HdpiOffscreenCanvas({ width: width2, height: height2, 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: width2, height: height2, devicePixelRatio } = renderCtx;
|
|
const { dirty, layer } = this;
|
|
const layerResized = layer != null && (this._lastWidth !== width2 || this._lastHeight !== height2);
|
|
const pixelRatioChanged = this._lastDevicePixelRatio !== devicePixelRatio;
|
|
this._lastWidth = width2;
|
|
this._lastHeight = height2;
|
|
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: width2, height: height2 } = bbox;
|
|
const scaledWidth = Math.floor(width2 * pixelRatio);
|
|
const scaledHeight = Math.floor(height2 * pixelRatio);
|
|
if (scaledWidth > 0 && scaledHeight > 0) {
|
|
const canvas = this.prepareSharedCanvas(width2, height2, pixelRatio);
|
|
renderOffscreen(canvas, pixelRatio, 0, 0, pixelRatio, -x * pixelRatio, -y * pixelRatio);
|
|
image = { bitmap: canvas.transferToImageBitmap(), x, y, width: width2, height: height2 };
|
|
}
|
|
}
|
|
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: width2, height: height2 } = image;
|
|
ctx.drawImage(bitmap, 0, 0, width2 * pixelRatio, height2 * pixelRatio, x, y, width2, height2);
|
|
}
|
|
ctx.globalAlpha = globalAlpha;
|
|
super.render(childRenderCtx);
|
|
}
|
|
applyClip(ctx, clipRect) {
|
|
const { x, y, width: width2, height: height2 } = clipRect;
|
|
ctx.beginPath();
|
|
ctx.rect(x, y, width2, height2);
|
|
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) => clamp(0, v, 1) })
|
|
], _Group.prototype, "opacity", 2);
|
|
var Group = _Group;
|
|
var ScalableGroup = class extends Scalable(Group) {
|
|
};
|
|
var RotatableGroup = class extends Rotatable(Group) {
|
|
};
|
|
var TranslatableGroup = class extends Translatable(Group) {
|
|
};
|
|
var TransformableGroup = class extends Rotatable(Translatable(Group)) {
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/mutex.ts
|
|
var Mutex = class {
|
|
constructor() {
|
|
this.available = true;
|
|
this.acquireQueue = [];
|
|
}
|
|
acquire(cb) {
|
|
return new Promise((resolve, reject) => {
|
|
this.acquireQueue.push([cb, resolve, reject]);
|
|
if (this.available) {
|
|
this.dispatchNext().catch(reject);
|
|
}
|
|
});
|
|
}
|
|
async acquireImmediately(cb) {
|
|
if (!this.available) {
|
|
return false;
|
|
}
|
|
await this.acquire(cb);
|
|
return true;
|
|
}
|
|
async waitForClearAcquireQueue() {
|
|
return this.acquire(() => Promise.resolve(void 0));
|
|
}
|
|
async dispatchNext() {
|
|
this.available = false;
|
|
let [next, done, reject] = this.acquireQueue.shift() ?? [];
|
|
while (next) {
|
|
try {
|
|
await next();
|
|
done?.();
|
|
} catch (error2) {
|
|
reject?.(error2);
|
|
}
|
|
[next, done, reject] = this.acquireQueue.shift() ?? [];
|
|
}
|
|
this.available = true;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/observable.ts
|
|
var Observable = class {
|
|
constructor() {
|
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
}
|
|
addEventListener(eventType, listener) {
|
|
if (typeof listener !== "function") {
|
|
throw new TypeError("AG Charts - listener must be a Function");
|
|
}
|
|
const eventTypeListeners = this.eventListeners.get(eventType);
|
|
if (eventTypeListeners) {
|
|
eventTypeListeners.add(listener);
|
|
} else {
|
|
this.eventListeners.set(eventType, /* @__PURE__ */ new Set([listener]));
|
|
}
|
|
}
|
|
removeEventListener(type, listener) {
|
|
const listeners = this.eventListeners.get(type);
|
|
if (listeners == null)
|
|
return;
|
|
listeners.delete(listener);
|
|
if (listeners.size === 0) {
|
|
this.eventListeners.delete(type);
|
|
}
|
|
}
|
|
hasEventListener(type) {
|
|
return this.eventListeners.has(type);
|
|
}
|
|
clearEventListeners() {
|
|
this.eventListeners.clear();
|
|
}
|
|
fireEvent(event) {
|
|
const listeners = this.eventListeners.get(event.type);
|
|
if (listeners) {
|
|
for (const listener of listeners) {
|
|
listener(event);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/render.ts
|
|
function debouncedAnimationFrame(cb) {
|
|
const window = getWindow();
|
|
function scheduleWithAnimationFrame(innerCb, _delayMs) {
|
|
return window.requestAnimationFrame(innerCb);
|
|
}
|
|
function cancelWithAnimationFrame(id) {
|
|
window.cancelAnimationFrame(id);
|
|
}
|
|
return buildScheduler(scheduleWithAnimationFrame, cb, cancelWithAnimationFrame);
|
|
}
|
|
function debouncedCallback(cb) {
|
|
function scheduleWithDelay(innerCb, delayMs = 0) {
|
|
if (delayMs === 0) {
|
|
queueMicrotask(innerCb);
|
|
return void 0;
|
|
}
|
|
return setTimeout(innerCb, delayMs);
|
|
}
|
|
function cancelWithTimeout(id) {
|
|
clearTimeout(id);
|
|
}
|
|
return buildScheduler(scheduleWithDelay, cb, cancelWithTimeout);
|
|
}
|
|
function buildScheduler(scheduleFn, cb, cancelFn) {
|
|
let scheduleCount = 0;
|
|
let promiseRunning = false;
|
|
let awaitingPromise;
|
|
let awaitingDone;
|
|
let scheduledId;
|
|
function busy() {
|
|
return promiseRunning;
|
|
}
|
|
function done() {
|
|
promiseRunning = false;
|
|
scheduledId = void 0;
|
|
awaitingDone?.();
|
|
awaitingDone = void 0;
|
|
awaitingPromise = void 0;
|
|
if (scheduleCount > 0) {
|
|
scheduledId = scheduleFn(scheduleCallback);
|
|
}
|
|
}
|
|
function scheduleCallback() {
|
|
const count = scheduleCount;
|
|
scheduleCount = 0;
|
|
promiseRunning = true;
|
|
const maybePromise = cb({ count });
|
|
if (!maybePromise) {
|
|
done();
|
|
return;
|
|
}
|
|
maybePromise.then(done, done);
|
|
}
|
|
function schedule(delayMs) {
|
|
if (scheduleCount === 0 && !busy()) {
|
|
scheduledId = scheduleFn(scheduleCallback, delayMs);
|
|
}
|
|
scheduleCount++;
|
|
}
|
|
function cancel() {
|
|
if (scheduledId != null && cancelFn) {
|
|
cancelFn(scheduledId);
|
|
scheduledId = void 0;
|
|
scheduleCount = 0;
|
|
}
|
|
}
|
|
async function waitForCompletion() {
|
|
if (!busy()) {
|
|
return;
|
|
}
|
|
awaitingPromise ?? (awaitingPromise = new Promise(resolveAwaitingPromise));
|
|
while (busy()) {
|
|
await awaitingPromise;
|
|
}
|
|
}
|
|
function resolveAwaitingPromise(resolve) {
|
|
awaitingDone = resolve;
|
|
}
|
|
return {
|
|
schedule,
|
|
cancel,
|
|
waitForCompletion
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-community/src/widget/widgetEvents.ts
|
|
var WIDGET_HTML_EVENTS = [
|
|
"blur",
|
|
"change",
|
|
"contextmenu",
|
|
"focus",
|
|
"keydown",
|
|
"keyup",
|
|
"click",
|
|
"dblclick",
|
|
"mouseenter",
|
|
"mousemove",
|
|
"mouseleave",
|
|
"wheel",
|
|
"touchstart",
|
|
"touchmove",
|
|
"touchend",
|
|
"touchcancel"
|
|
];
|
|
function allocMouseEvent(type, sourceEvent, current) {
|
|
const { offsetX, offsetY, clientX, clientY } = sourceEvent;
|
|
const { currentX, currentY } = WidgetEventUtil.calcCurrentXY(current, sourceEvent);
|
|
return { type, device: "mouse", offsetX, offsetY, clientX, clientY, currentX, currentY, sourceEvent };
|
|
}
|
|
function allocTouchEvent(type, sourceEvent, _current) {
|
|
return { type, sourceEvent };
|
|
}
|
|
var WidgetAllocators = {
|
|
blur: (sourceEvent) => {
|
|
return { type: "blur", sourceEvent };
|
|
},
|
|
change: (sourceEvent) => {
|
|
return { type: "change", sourceEvent };
|
|
},
|
|
contextmenu: (sourceEvent, current) => {
|
|
return allocMouseEvent("contextmenu", sourceEvent, current);
|
|
},
|
|
focus: (sourceEvent) => {
|
|
return { type: "focus", sourceEvent };
|
|
},
|
|
keydown: (sourceEvent) => {
|
|
return { type: "keydown", sourceEvent };
|
|
},
|
|
keyup: (sourceEvent) => {
|
|
return { type: "keyup", sourceEvent };
|
|
},
|
|
click: (sourceEvent, current) => {
|
|
return allocMouseEvent("click", sourceEvent, current);
|
|
},
|
|
dblclick: (sourceEvent, current) => {
|
|
return allocMouseEvent("dblclick", sourceEvent, current);
|
|
},
|
|
mouseenter: (sourceEvent, current) => {
|
|
return allocMouseEvent("mouseenter", sourceEvent, current);
|
|
},
|
|
mousemove: (sourceEvent, current) => {
|
|
return allocMouseEvent("mousemove", sourceEvent, current);
|
|
},
|
|
mouseleave: (sourceEvent, current) => {
|
|
return allocMouseEvent("mouseleave", sourceEvent, current);
|
|
},
|
|
wheel: (sourceEvent) => {
|
|
const { offsetX, offsetY, clientX, clientY } = sourceEvent;
|
|
const factor = sourceEvent.deltaMode === 0 ? 0.01 : 1;
|
|
const deltaX = sourceEvent.deltaX * factor;
|
|
const deltaY = sourceEvent.deltaY * factor;
|
|
return { type: "wheel", offsetX, offsetY, clientX, clientY, deltaX, deltaY, sourceEvent };
|
|
},
|
|
touchstart: (sourceEvent, current) => {
|
|
return allocTouchEvent("touchstart", sourceEvent, current);
|
|
},
|
|
touchmove: (sourceEvent, current) => {
|
|
return allocTouchEvent("touchmove", sourceEvent, current);
|
|
},
|
|
touchend: (sourceEvent, current) => {
|
|
return allocTouchEvent("touchend", sourceEvent, current);
|
|
},
|
|
touchcancel: (sourceEvent, current) => {
|
|
return allocTouchEvent("touchcancel", sourceEvent, current);
|
|
}
|
|
};
|
|
var WidgetEventUtil = class {
|
|
static alloc(type, sourceEvent, current) {
|
|
return WidgetAllocators[type](sourceEvent, current);
|
|
}
|
|
static isHTMLEvent(type) {
|
|
const htmlTypes = WIDGET_HTML_EVENTS;
|
|
return htmlTypes.includes(type);
|
|
}
|
|
static calcCurrentXY(current, event) {
|
|
const currentRect = current.getBoundingClientRect();
|
|
return { currentX: event.clientX - currentRect.x, currentY: event.clientY - currentRect.y };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/widget/widgetListenerHTML.ts
|
|
var WidgetListenerHTML = class {
|
|
constructor() {
|
|
this.widgetListeners = {};
|
|
this.sourceListeners = {};
|
|
}
|
|
initSourceHandler(type, handler) {
|
|
this.sourceListeners ?? (this.sourceListeners = {});
|
|
this.sourceListeners[type] = handler;
|
|
}
|
|
lazyGetWidgetListeners(type, target) {
|
|
var _a;
|
|
if (!(type in (this.sourceListeners ?? {}))) {
|
|
const sourceHandler = (sourceEvent) => {
|
|
const widgetEvent = WidgetEventUtil.alloc(type, sourceEvent, target.getElement());
|
|
this.dispatch(type, target, widgetEvent);
|
|
};
|
|
const opts = {};
|
|
if (type.startsWith("touch") || type === "wheel") {
|
|
opts.passive = false;
|
|
}
|
|
this.initSourceHandler(type, sourceHandler);
|
|
target.getElement().addEventListener(type, sourceHandler, opts);
|
|
}
|
|
this.widgetListeners ?? (this.widgetListeners = {});
|
|
(_a = this.widgetListeners)[type] ?? (_a[type] = []);
|
|
return this.widgetListeners[type];
|
|
}
|
|
add(type, target, handler) {
|
|
const listeners = this.lazyGetWidgetListeners(type, target);
|
|
listeners.push(handler);
|
|
}
|
|
remove(type, target, handler) {
|
|
const listeners = this.lazyGetWidgetListeners(type, target);
|
|
const index = listeners.indexOf(handler);
|
|
if (index > -1)
|
|
listeners.splice(index, 1);
|
|
}
|
|
destroy(target) {
|
|
this.widgetListeners = void 0;
|
|
if (this.sourceListeners) {
|
|
for (const [key, sourceHandler] of entries(this.sourceListeners)) {
|
|
target.getElement().removeEventListener(key, sourceHandler);
|
|
}
|
|
this.sourceListeners = void 0;
|
|
}
|
|
}
|
|
dispatch(type, target, event) {
|
|
for (const widgetListener of this.widgetListeners?.[type] ?? []) {
|
|
widgetListener(event, target);
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/widget/mouseDragger.ts
|
|
var MouseDragger = class {
|
|
constructor(glob, self, myCallbacks, downEvent) {
|
|
this.glob = glob;
|
|
this.self = self;
|
|
this.window = getWindow();
|
|
this.cleanup = new CleanupRegistry();
|
|
this.mousegeneral = (generalEvent) => {
|
|
generalEvent.stopPropagation();
|
|
generalEvent.stopImmediatePropagation();
|
|
};
|
|
this.mousemove = (moveEvent) => {
|
|
moveEvent.stopPropagation();
|
|
moveEvent.stopImmediatePropagation();
|
|
this.glob.globalMouseDragCallbacks?.mousemove(moveEvent);
|
|
};
|
|
this.mouseup = (upEvent) => {
|
|
if (upEvent.button === 0) {
|
|
upEvent.stopPropagation();
|
|
upEvent.stopImmediatePropagation();
|
|
this.glob.globalMouseDragCallbacks?.mouseup(upEvent);
|
|
this.destroy();
|
|
}
|
|
};
|
|
const { window, mousegeneral, mousemove, mouseup } = this;
|
|
this.cleanup.register(
|
|
attachListener(window, "mousedown", mousegeneral, { capture: true }),
|
|
attachListener(window, "mouseenter", mousegeneral, { capture: true }),
|
|
attachListener(window, "mouseleave", mousegeneral, { capture: true }),
|
|
attachListener(window, "mouseout", mousegeneral, { capture: true }),
|
|
attachListener(window, "mouseover", mousegeneral, { capture: true }),
|
|
attachListener(window, "mousemove", mousemove, { capture: true }),
|
|
attachListener(window, "mouseup", mouseup, { capture: true })
|
|
);
|
|
self.mouseDragger = this;
|
|
glob.globalMouseDragCallbacks = myCallbacks;
|
|
glob.globalMouseDragCallbacks.mousedown(downEvent);
|
|
downEvent.stopPropagation();
|
|
downEvent.stopImmediatePropagation();
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
this.glob.globalMouseDragCallbacks = void 0;
|
|
this.self.mouseDragger = void 0;
|
|
}
|
|
};
|
|
function startMouseDrag(glob, self, myCallbacks, downEvent) {
|
|
if (glob.globalMouseDragCallbacks != null)
|
|
return void 0;
|
|
return new MouseDragger(glob, self, myCallbacks, downEvent);
|
|
}
|
|
|
|
// packages/ag-charts-community/src/widget/touchDragger.ts
|
|
var LONG_TAP_DURATION_MS = 500;
|
|
var LONG_TAP_INTERRUPT_MIN_TOUCHMOVE_PXPX = 100;
|
|
function deltaClientSquared(a, b) {
|
|
const dx2 = a.clientX - b.clientX;
|
|
const dy2 = a.clientY - b.clientY;
|
|
return dx2 * dx2 + dy2 * dy2;
|
|
}
|
|
var gIsInLongTap = false;
|
|
var TouchDragger = class {
|
|
constructor(glob, self, myCallbacks, initialTouch, target) {
|
|
this.glob = glob;
|
|
this.self = self;
|
|
this.initialTouch = initialTouch;
|
|
this.target = target;
|
|
this.cleanup = new CleanupRegistry();
|
|
this.longTapInterrupted = false;
|
|
this.longtap = () => {
|
|
const { target, initialTouch } = this;
|
|
if (!this.longTapInterrupted) {
|
|
const cleanup = new CleanupRegistry();
|
|
target.dispatchEvent(new TouchEvent("touchcancel", { touches: [initialTouch], bubbles: true }));
|
|
gIsInLongTap = true;
|
|
const longTapMove = (e) => e.preventDefault();
|
|
const longTapEnd = (e) => {
|
|
gIsInLongTap = false;
|
|
e.preventDefault();
|
|
cleanup.flush();
|
|
};
|
|
cleanup.register(
|
|
attachListener(target, "touchmove", longTapMove, { passive: false }),
|
|
attachListener(target, "touchend", longTapEnd, { passive: false }),
|
|
attachListener(target, "touchcancel", longTapEnd, { passive: false })
|
|
);
|
|
const { clientX, clientY } = initialTouch;
|
|
const contextMenuEvent = new PointerEvent("contextmenu", {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
view: getWindow(),
|
|
clientX,
|
|
clientY,
|
|
pointerType: "touch"
|
|
});
|
|
target.dispatchEvent(contextMenuEvent);
|
|
}
|
|
};
|
|
this.touchmove = (moveEvent) => {
|
|
const { glob, self, initialTouch } = this;
|
|
const touch = this.findInitialFinger(moveEvent.targetTouches);
|
|
if (touch != null) {
|
|
this.longTapInterrupted = this.longTapInterrupted || deltaClientSquared(initialTouch, touch) > LONG_TAP_INTERRUPT_MIN_TOUCHMOVE_PXPX;
|
|
if (self.dragTouchEnabled) {
|
|
glob.globalTouchDragCallbacks?.touchmove(moveEvent, touch);
|
|
}
|
|
}
|
|
};
|
|
this.touchend = (endEvent) => {
|
|
this.longTapInterrupted = true;
|
|
const touch = this.findInitialFinger(endEvent.changedTouches, endEvent.touches);
|
|
if (touch != null) {
|
|
this.glob.globalTouchDragCallbacks?.touchend(endEvent, touch);
|
|
}
|
|
this.destroy();
|
|
};
|
|
this.longtapTimer = setTimeout(this.longtap, LONG_TAP_DURATION_MS);
|
|
const { touchmove, touchend } = this;
|
|
this.cleanup.register(
|
|
attachListener(target, "touchmove", touchmove, { passive: false }),
|
|
attachListener(target, "touchstart", touchend, { passive: false }),
|
|
attachListener(target, "touchend", touchend, { passive: false }),
|
|
attachListener(target, "touchcancel", touchend, { passive: false })
|
|
);
|
|
self.touchDragger = this;
|
|
glob.globalTouchDragCallbacks = myCallbacks;
|
|
}
|
|
destroy() {
|
|
clearTimeout(this.longtapTimer);
|
|
this.cleanup.flush();
|
|
this.glob.globalTouchDragCallbacks = void 0;
|
|
this.self.touchDragger = void 0;
|
|
}
|
|
findInitialFinger(...touchLists) {
|
|
const touches = touchLists.flatMap((touchList) => Array.from(touchList));
|
|
return Array.from(touches).find((v) => v.identifier === this.initialTouch.identifier);
|
|
}
|
|
};
|
|
function startOneFingerTouch(glob, self, myCallbacks, initialTouch, target) {
|
|
if (glob.globalTouchDragCallbacks != null || gIsInLongTap)
|
|
return void 0;
|
|
return new TouchDragger(glob, self, myCallbacks, initialTouch, target);
|
|
}
|
|
|
|
// packages/ag-charts-community/src/widget/widgetListenerInternal.ts
|
|
function makeMouseDrag(current, type, origin3, sourceEvent) {
|
|
const { currentX, currentY } = WidgetEventUtil.calcCurrentXY(current.getElement(), sourceEvent);
|
|
const originDeltaX = sourceEvent.pageX - origin3.pageX;
|
|
const originDeltaY = sourceEvent.pageY - origin3.pageY;
|
|
return {
|
|
type,
|
|
device: "mouse",
|
|
offsetX: origin3.offsetX + originDeltaX,
|
|
offsetY: origin3.offsetY + originDeltaY,
|
|
clientX: sourceEvent.clientX,
|
|
clientY: sourceEvent.clientY,
|
|
currentX,
|
|
currentY,
|
|
originDeltaX,
|
|
originDeltaY,
|
|
sourceEvent
|
|
};
|
|
}
|
|
function getTouchOffsets(current, { pageX, pageY }) {
|
|
const { x, y } = current.getElement().getBoundingClientRect();
|
|
return { offsetX: pageX - x, offsetY: pageY - y };
|
|
}
|
|
function makeTouchDrag(current, type, origin3, sourceEvent, touch) {
|
|
const { currentX, currentY } = WidgetEventUtil.calcCurrentXY(current.getElement(), touch);
|
|
const originDeltaX = touch.pageX - origin3.pageX;
|
|
const originDeltaY = touch.pageY - origin3.pageY;
|
|
return {
|
|
type,
|
|
device: "touch",
|
|
offsetX: origin3.offsetX + originDeltaX,
|
|
offsetY: origin3.offsetY + originDeltaY,
|
|
clientX: touch.clientX,
|
|
clientY: touch.clientY,
|
|
currentX,
|
|
currentY,
|
|
originDeltaX,
|
|
originDeltaY,
|
|
sourceEvent
|
|
};
|
|
}
|
|
var GlobalCallbacks = {};
|
|
var WidgetListenerInternal = class {
|
|
constructor(dispatchCallback) {
|
|
this.dispatchCallback = dispatchCallback;
|
|
this.dragTouchEnabled = true;
|
|
}
|
|
destroy() {
|
|
this.dragTriggerRemover?.();
|
|
this.dragTriggerRemover = void 0;
|
|
this.listeners?.clear();
|
|
this.mouseDragger?.destroy();
|
|
this.touchDragger?.destroy();
|
|
}
|
|
getListenerSet(type) {
|
|
this.listeners ?? (this.listeners = /* @__PURE__ */ new Map());
|
|
let result = this.listeners.get(type);
|
|
if (result === void 0) {
|
|
result = /* @__PURE__ */ new Set();
|
|
this.listeners.set(type, result);
|
|
}
|
|
return result;
|
|
}
|
|
add(type, target, handler) {
|
|
this.getListenerSet(type).add(handler);
|
|
switch (type) {
|
|
case "drag-start":
|
|
case "drag-move":
|
|
case "drag-end": {
|
|
this.registerDragTrigger(target);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
remove(type, _target, handler) {
|
|
this.getListenerSet(type).delete(handler);
|
|
}
|
|
registerDragTrigger(target) {
|
|
if (this.dragTriggerRemover == null) {
|
|
const element2 = target.getElement();
|
|
const cleanup = new CleanupRegistry();
|
|
cleanup.register(
|
|
attachListener(element2, "mousedown", (event) => this.triggerMouseDrag(target, event)),
|
|
attachListener(element2, "touchstart", (event) => this.triggerTouchDrag(target, event), {
|
|
passive: false
|
|
})
|
|
);
|
|
this.dragTriggerRemover = () => cleanup.flush();
|
|
}
|
|
}
|
|
triggerMouseDrag(current, downEvent) {
|
|
if (downEvent.button === 0) {
|
|
this.startMouseDrag(current, downEvent);
|
|
}
|
|
}
|
|
startMouseDrag(current, initialDownEvent) {
|
|
const origin3 = { pageX: Number.NaN, pageY: Number.NaN, offsetX: Number.NaN, offsetY: Number.NaN };
|
|
partialAssign(["pageX", "pageY", "offsetX", "offsetY"], origin3, initialDownEvent);
|
|
const dragCallbacks = {
|
|
mousedown: (downEvent) => {
|
|
const dragStartEvent = makeMouseDrag(current, "drag-start", origin3, downEvent);
|
|
this.dispatch("drag-start", current, dragStartEvent);
|
|
},
|
|
mousemove: (moveEvent) => {
|
|
const dragMoveEvent = makeMouseDrag(current, "drag-move", origin3, moveEvent);
|
|
this.dispatch("drag-move", current, dragMoveEvent);
|
|
},
|
|
mouseup: (upEvent) => {
|
|
const dragEndEvent = makeMouseDrag(current, "drag-end", origin3, upEvent);
|
|
this.dispatch("drag-end", current, dragEndEvent);
|
|
this.endDrag(current, dragEndEvent);
|
|
}
|
|
};
|
|
this.mouseDragger = startMouseDrag(GlobalCallbacks, this, dragCallbacks, initialDownEvent);
|
|
}
|
|
endDrag(target, { sourceEvent, clientX, clientY }) {
|
|
const elem = target.getElement();
|
|
const rect2 = elem.getBoundingClientRect();
|
|
if (!boxContains(rect2, clientX, clientY)) {
|
|
elem.dispatchEvent(new MouseEvent("mouseleave", sourceEvent));
|
|
sourceEvent.target?.dispatchEvent(new MouseEvent("mouseenter", sourceEvent));
|
|
}
|
|
}
|
|
triggerTouchDrag(current, startEvent) {
|
|
const touch = startEvent.targetTouches[0];
|
|
if (startEvent.targetTouches.length === 1 && touch != null) {
|
|
this.startOneFingerTouch(current, startEvent, touch);
|
|
}
|
|
}
|
|
startOneFingerTouch(current, initialEvent, initialTouch) {
|
|
const origin3 = { pageX: Number.NaN, pageY: Number.NaN, ...getTouchOffsets(current, initialTouch) };
|
|
partialAssign(["pageX", "pageY"], origin3, initialTouch);
|
|
const dragCallbacks = {
|
|
touchmove: (moveEvent, touch) => {
|
|
const dragMoveEvent = makeTouchDrag(current, "drag-move", origin3, moveEvent, touch);
|
|
this.dispatch("drag-move", current, dragMoveEvent);
|
|
},
|
|
touchend: (cancelEvent, touch) => {
|
|
const dragMoveEvent = makeTouchDrag(current, "drag-end", origin3, cancelEvent, touch);
|
|
this.dispatch("drag-end", current, dragMoveEvent);
|
|
}
|
|
};
|
|
const target = current.getElement();
|
|
this.touchDragger = startOneFingerTouch(GlobalCallbacks, this, dragCallbacks, initialTouch, target);
|
|
const dragStartEvent = makeTouchDrag(current, "drag-start", origin3, initialEvent, initialTouch);
|
|
this.dispatch("drag-start", current, dragStartEvent);
|
|
}
|
|
dispatch(type, current, event) {
|
|
for (const handler of this.getListenerSet(type)) {
|
|
handler(event, current);
|
|
}
|
|
this.dispatchCallback(type, event);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/widget/widget.ts
|
|
var WidgetBounds = class {
|
|
constructor(elem) {
|
|
this.elem = elem;
|
|
}
|
|
setBounds(bounds) {
|
|
setElementBBox(this.elemContainer ?? this.elem, bounds);
|
|
}
|
|
getBounds() {
|
|
return getElementBBox(this.elemContainer ?? this.elem);
|
|
}
|
|
static setElementContainer(widget, elemContainer) {
|
|
const currentBounds = widget.getBounds();
|
|
setElementBBox(elemContainer, currentBounds);
|
|
setElementStyles(widget.elem, { width: "100%", height: "100%" });
|
|
widget.elem.remove();
|
|
widget.elemContainer = elemContainer;
|
|
widget.elemContainer.replaceChildren(widget.elem);
|
|
}
|
|
};
|
|
var Widget = class extends WidgetBounds {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.index = Number.NaN;
|
|
this.children = [];
|
|
}
|
|
set id(elementId) {
|
|
setAttribute(this.elem, "id", elementId);
|
|
}
|
|
get id() {
|
|
return getAttribute(this.elem, "id");
|
|
}
|
|
getElement() {
|
|
return this.elem;
|
|
}
|
|
getBoundingClientRect() {
|
|
return this.elem.getBoundingClientRect();
|
|
}
|
|
get clientWidth() {
|
|
return this.elem.clientWidth;
|
|
}
|
|
get clientHeight() {
|
|
return this.elem.clientHeight;
|
|
}
|
|
destroy() {
|
|
this.destroyListener?.();
|
|
this.destroyListener = void 0;
|
|
this.remove();
|
|
for (const child of this.children) {
|
|
child.parent = void 0;
|
|
child.destroy();
|
|
}
|
|
this.children.length = 0;
|
|
this.destructor();
|
|
this.remove();
|
|
this.internalListener?.destroy();
|
|
this.htmlListener?.destroy(this);
|
|
}
|
|
remove() {
|
|
this.elem.remove();
|
|
this.elemContainer?.remove();
|
|
}
|
|
setHidden(hidden) {
|
|
setElementStyle(this.elem, "display", hidden ? "none" : void 0);
|
|
}
|
|
isHidden() {
|
|
return getWindow()?.getComputedStyle?.(this.elem).display === "none";
|
|
}
|
|
setCursor(cursor) {
|
|
setElementStyle(this.elem, "cursor", cursor);
|
|
}
|
|
setTextContent(textContent) {
|
|
this.elem.textContent = textContent ?? null;
|
|
}
|
|
setAriaDescribedBy(ariaDescribedBy) {
|
|
setAttribute(this.elem, "aria-describedby", ariaDescribedBy);
|
|
}
|
|
setAriaHidden(ariaHidden) {
|
|
setAttribute(this.elem, "aria-hidden", ariaHidden);
|
|
}
|
|
setAriaLabel(ariaLabel) {
|
|
setAttribute(this.elem, "aria-label", ariaLabel);
|
|
}
|
|
setAriaExpanded(ariaExpanded) {
|
|
setAttribute(this.elem, "aria-expanded", ariaExpanded);
|
|
}
|
|
setAriaControls(ariaControls) {
|
|
setAttribute(this.elem, "aria-controls", ariaControls);
|
|
}
|
|
setAriaHasPopup(ariaHasPopup) {
|
|
setAttribute(this.elem, "aria-haspopup", ariaHasPopup);
|
|
}
|
|
setInnerHTML(html) {
|
|
this.elem.innerHTML = html;
|
|
}
|
|
setPointerEvents(pointerEvents) {
|
|
setElementStyle(this.elem, "pointer-events", pointerEvents);
|
|
}
|
|
setCSSVariable(key, value) {
|
|
this.elem.style.setProperty(key, value);
|
|
}
|
|
isDisabled() {
|
|
return getAttribute(this.elem, "aria-disabled", false);
|
|
}
|
|
hasPopup() {
|
|
const ariaHasPopup = getAttribute(this.elem, "aria-haspopup");
|
|
return ariaHasPopup !== void 0 && ariaHasPopup !== "false";
|
|
}
|
|
parseFloat(s) {
|
|
return s === "" ? 0 : Number.parseFloat(s);
|
|
}
|
|
cssLeft() {
|
|
return this.parseFloat(this.elem.style.left);
|
|
}
|
|
cssTop() {
|
|
return this.parseFloat(this.elem.style.top);
|
|
}
|
|
cssWidth() {
|
|
return this.parseFloat(this.elem.style.width);
|
|
}
|
|
cssHeight() {
|
|
return this.parseFloat(this.elem.style.height);
|
|
}
|
|
focus(opts) {
|
|
this.elem.focus(opts);
|
|
}
|
|
setFocusOverride(focus) {
|
|
setAttribute(this.elem, "data-focus-override", focus);
|
|
}
|
|
setPreventsDefault(preventDefault) {
|
|
setAttribute(this.elem, "data-preventdefault", preventDefault);
|
|
}
|
|
setTabIndex(tabIndex) {
|
|
setAttribute(this.elem, "tabindex", tabIndex);
|
|
}
|
|
addChild(child) {
|
|
this.addChildToDOM(child, this.getBefore(child));
|
|
this.children.push(child);
|
|
child.index = this.children.length - 1;
|
|
child.parent = this;
|
|
this.onChildAdded(child);
|
|
}
|
|
removeChild(child) {
|
|
const i = this.children.indexOf(child);
|
|
this.children.splice(i, 1);
|
|
this.removeChildFromDOM(child);
|
|
this.onChildRemoved(child);
|
|
}
|
|
moveChild(child, domIndex) {
|
|
if (child.domIndex === domIndex)
|
|
return;
|
|
child.domIndex = domIndex;
|
|
this.removeChildFromDOM(child);
|
|
this.addChildToDOM(child, this.getBefore(child));
|
|
}
|
|
addClass(...tokens) {
|
|
this.elem.classList.add(...tokens);
|
|
}
|
|
removeClass(...tokens) {
|
|
this.elem.classList.remove(...tokens);
|
|
}
|
|
toggleClass(token, force) {
|
|
this.elem.classList.toggle(token, force);
|
|
}
|
|
appendOrInsert(child, before) {
|
|
if (before) {
|
|
before.getElement().insertAdjacentElement("beforebegin", child);
|
|
} else {
|
|
this.elem.appendChild(child);
|
|
}
|
|
}
|
|
addChildToDOM(child, before) {
|
|
this.appendOrInsert(child.getElement(), before);
|
|
}
|
|
removeChildFromDOM(child) {
|
|
child.getElement().remove();
|
|
}
|
|
onChildAdded(_child) {
|
|
}
|
|
onChildRemoved(_child) {
|
|
}
|
|
getBefore({ domIndex }) {
|
|
if (domIndex === void 0)
|
|
return void 0;
|
|
return this.children.filter((child) => child.domIndex !== void 0 && child.domIndex > domIndex).reduce((prev, curr) => !prev || curr.domIndex < prev.domIndex ? curr : prev, void 0);
|
|
}
|
|
addListener(type, listener) {
|
|
if (WidgetEventUtil.isHTMLEvent(type)) {
|
|
this.htmlListener ?? (this.htmlListener = new WidgetListenerHTML());
|
|
this.htmlListener.add(type, this, listener);
|
|
} else {
|
|
this.internalListener ?? (this.internalListener = new WidgetListenerInternal(this.onDispatch.bind(this)));
|
|
this.internalListener.add(type, this, listener);
|
|
}
|
|
return () => this.removeListener(type, listener);
|
|
}
|
|
removeListener(type, listener) {
|
|
if (WidgetEventUtil.isHTMLEvent(type)) {
|
|
this.htmlListener?.remove(type, this, listener);
|
|
} else if (this.htmlListener != null) {
|
|
this.internalListener?.remove(type, this, listener);
|
|
}
|
|
}
|
|
setDragTouchEnabled(dragTouchEnabled) {
|
|
this.internalListener ?? (this.internalListener = new WidgetListenerInternal(this.onDispatch.bind(this)));
|
|
this.internalListener.dragTouchEnabled = dragTouchEnabled;
|
|
}
|
|
onDispatch(type, event) {
|
|
if (!event.sourceEvent?.bubbles)
|
|
return;
|
|
let { parent } = this;
|
|
while (parent != null) {
|
|
const { internalListener } = parent;
|
|
if (internalListener != null) {
|
|
const parentEvent = { ...event, ...WidgetEventUtil.calcCurrentXY(parent.getElement(), event) };
|
|
internalListener.dispatch(type, parent, parentEvent);
|
|
}
|
|
parent = parent.parent;
|
|
}
|
|
}
|
|
static addWindowEvent(_type, listener) {
|
|
const pagehideHandler = (event) => {
|
|
if (event.persisted) {
|
|
return;
|
|
}
|
|
listener();
|
|
};
|
|
return attachListener(getWindow(), "pagehide", pagehideHandler);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/util/corner.ts
|
|
var drawCorner = (path, { x0, y0, x1, y1, cx, cy }, cornerRadius, move) => {
|
|
if (move) {
|
|
path.moveTo(x0, y0);
|
|
}
|
|
if (x0 !== x1 || y0 !== y1) {
|
|
const r0 = Math.atan2(y0 - cy, x0 - cx);
|
|
const r1 = Math.atan2(y1 - cy, x1 - cx);
|
|
path.arc(cx, cy, cornerRadius, r0, r1);
|
|
} else {
|
|
path.lineTo(x0, y0);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/shape/path.ts
|
|
var 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 element2 = createSvgElement("path");
|
|
element2.setAttribute("d", this.svgPathData());
|
|
const defs = this.applySvgFillAttributes(element2, []);
|
|
this.applySvgStrokeAttributes(element2);
|
|
return {
|
|
elements: [element2],
|
|
defs
|
|
};
|
|
}
|
|
};
|
|
Path.className = "Path";
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Path.prototype, "clip", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Path.prototype, "clipX", 1);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Path.prototype, "clipY", 1);
|
|
|
|
// packages/ag-charts-community/src/scene/shape/rect.ts
|
|
function cornerEdges(leadingEdge, trailingEdge, leadingInset, trailingInset, cornerRadius) {
|
|
let leadingClipped = false;
|
|
let trailingClipped = false;
|
|
let leading0 = trailingInset - Math.sqrt(Math.max(cornerRadius ** 2 - leadingInset ** 2, 0));
|
|
let leading1 = 0;
|
|
let trailing0 = 0;
|
|
let trailing1 = leadingInset - Math.sqrt(Math.max(cornerRadius ** 2 - trailingInset ** 2, 0));
|
|
if (leading0 > leadingEdge) {
|
|
leadingClipped = true;
|
|
leading0 = leadingEdge;
|
|
leading1 = leadingInset - Math.sqrt(Math.max(cornerRadius ** 2 - (trailingInset - leadingEdge) ** 2));
|
|
} else if (isNumberEqual(leading0, 0)) {
|
|
leading0 = 0;
|
|
}
|
|
if (trailing1 > trailingEdge) {
|
|
trailingClipped = true;
|
|
trailing0 = trailingInset - Math.sqrt(Math.max(cornerRadius ** 2 - (leadingInset - trailingEdge) ** 2));
|
|
trailing1 = trailingEdge;
|
|
} else if (isNumberEqual(trailing1, 0)) {
|
|
trailing1 = 0;
|
|
}
|
|
return { leading0, leading1, trailing0, trailing1, leadingClipped, trailingClipped };
|
|
}
|
|
function clippedRoundRect(path, x, y, width2, height2, 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, width2, height2);
|
|
} else {
|
|
const x0 = Math.max(x, clipBBox.x);
|
|
const x1 = Math.min(x + width2, clipBBox.x + clipBBox.width);
|
|
const y0 = Math.max(y, clipBBox.y);
|
|
const y1 = Math.min(y + height2, 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, width2, height2, topLeftCornerRadius);
|
|
return;
|
|
}
|
|
if (width2 < 0) {
|
|
x += width2;
|
|
width2 = Math.abs(width2);
|
|
}
|
|
if (height2 < 0) {
|
|
y += height2;
|
|
height2 = Math.abs(height2);
|
|
}
|
|
if (width2 <= 0 || height2 <= 0)
|
|
return;
|
|
if (clipBBox == null) {
|
|
clipBBox = new BBox(x, y, width2, height2);
|
|
} else {
|
|
const x0 = Math.max(x, clipBBox.x);
|
|
const x1 = Math.min(x + width2, clipBBox.x + clipBBox.width);
|
|
const y0 = Math.max(y, clipBBox.y);
|
|
const y1 = Math.min(y + height2, clipBBox.y + clipBBox.height);
|
|
clipBBox = new BBox(x0, y0, x1 - x0, y1 - y0);
|
|
}
|
|
const borderScale = Math.max(maxVerticalCornerRadius / height2, maxHorizontalCornerRadius / width2, 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 + width2 - 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 + width2 - 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 + width2 - bottomRightCornerRadius), 0),
|
|
Math.max(clipBBox.y + clipBBox.height - (y + height2 - 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 + width2 - bottomRightCornerRadius;
|
|
const cy = y + height2 - bottomRightCornerRadius;
|
|
bottomRightCorner = { x0, y0, x1, y1, cx, cy };
|
|
}
|
|
if (drawBottomLeftCorner) {
|
|
const nodes = cornerEdges(
|
|
clipBBox.width,
|
|
clipBBox.height,
|
|
Math.max(clipBBox.y + clipBBox.height - (y + height2 - 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 + height2 - 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: width2, __height: height2, __clipBBox: clipBBox } = this;
|
|
return clipBBox?.clone() ?? new BBox(x, y, width2, height2);
|
|
}
|
|
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, width2, height2, opacity, clipBBox) {
|
|
this.__x = x;
|
|
this.__y = y;
|
|
this.__width = width2;
|
|
this.__height = height2;
|
|
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: stroke3, effectiveStrokeWidth } = this;
|
|
if (stroke3 && 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([
|
|
DeclaredSceneChangeDetection()
|
|
], Rect.prototype, "x", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], Rect.prototype, "y", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], Rect.prototype, "width", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], Rect.prototype, "height", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], Rect.prototype, "topLeftCornerRadius", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], Rect.prototype, "topRightCornerRadius", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], Rect.prototype, "bottomRightCornerRadius", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], Rect.prototype, "bottomLeftCornerRadius", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection({ equals: boxesEqual })
|
|
], Rect.prototype, "clipBBox", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], Rect.prototype, "crisp", 2);
|
|
function areCornersZero(cornerRadius) {
|
|
return cornerRadius === 0;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/sceneDebug.ts
|
|
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 (!debugLogger_exports.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;
|
|
logger_exports.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 (!debugLogger_exports.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 end3 = performance.now();
|
|
const { start: start2, ...durations } = debugSplitTimes;
|
|
const totalTime = end3 - start2;
|
|
const statsAccumulator = getStatsAccumulator();
|
|
statsAccumulator.recordTimings(durations);
|
|
statsAccumulator.recordTiming("\u23F1\uFE0F", totalTime);
|
|
const splits = Object.entries(durations).map(([n, t]) => time2(n, t)).filter((v) => v != null).join(" + ");
|
|
const extras = Object.entries(extraDebugStats).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(" ; ");
|
|
const detailedStats = debugLogger_exports.check("scene:stats:verbose" /* SCENE_STATS_VERBOSE */);
|
|
const memUsage = detailedStats ? memoryUsage() : null;
|
|
const metrics2 = detailedStats ? debugMetrics_exports.flush() : {};
|
|
const metricsEntries = Object.entries(metrics2);
|
|
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 = [
|
|
`${time2("\u23F1\uFE0F", start2, end3)} (${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(isString);
|
|
const measurer3 = new TextMeasurer(ctx);
|
|
const statsSize = new Map(stats.map((t) => [t, measurer3.measureText(t)]));
|
|
const width2 = Math.max(...Array.from(statsSize.values(), (s) => s.width));
|
|
const height2 = accumulate(statsSize.values(), (s) => s.height);
|
|
const x = 2 + seriesRect.x;
|
|
ctx.save();
|
|
try {
|
|
ctx.fillStyle = colors?.background ?? "white";
|
|
ctx.fillRect(x, 0, width2, height2);
|
|
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) {
|
|
logger_exports.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) {
|
|
logger_exports.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) {
|
|
logger_exports.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 (!debugLogger_exports.check(true, "scene" /* SCENE */)) {
|
|
return {};
|
|
}
|
|
let order = 0;
|
|
return {
|
|
node: mode === "json" ? nodeProps(node) : node,
|
|
name: node.name ?? node.id,
|
|
dirty: node instanceof Group ? node.dirty : void 0,
|
|
...Array.from(node instanceof Group ? node.children() : [], (c) => buildTree(c, mode)).reduce((result, childTree) => {
|
|
let { name: treeNodeName } = childTree;
|
|
const {
|
|
node: { visible, opacity, zIndex, translationX, translationY, rotation, scalingX, scalingY },
|
|
node: childNode
|
|
} = childTree;
|
|
if (!visible || opacity <= 0) {
|
|
treeNodeName = `(${treeNodeName})`;
|
|
}
|
|
if (Group.is(childNode) && childNode.renderToOffscreenCanvas) {
|
|
treeNodeName = `*${treeNodeName}*`;
|
|
}
|
|
const zIndexString = Array.isArray(zIndex) ? `(${zIndex.join(", ")})` : zIndex;
|
|
const key = [
|
|
`${(order++).toString().padStart(3, "0")}|`,
|
|
`${treeNodeName ?? "<unknown>"}`,
|
|
`z: ${zIndexString}`,
|
|
translationX && `x: ${translationX}`,
|
|
translationY && `y: ${translationY}`,
|
|
rotation && `r: ${rotation}`,
|
|
scalingX != null && scalingX !== 1 && `sx: ${scalingX}`,
|
|
scalingY != null && scalingY !== 1 && `sy: ${scalingY}`
|
|
].filter((v) => !!v).join(" ");
|
|
let selectedKey = key;
|
|
let index = 1;
|
|
while (result[selectedKey] != null && index < 100) {
|
|
selectedKey = `${key} (${index++})`;
|
|
}
|
|
result[selectedKey] = childTree;
|
|
return result;
|
|
}, {})
|
|
};
|
|
}
|
|
function buildDirtyTree(node) {
|
|
const nodeDirty = node instanceof Group ? node.dirty : void 0;
|
|
if (!nodeDirty) {
|
|
return { dirtyTree: {}, paths: [] };
|
|
}
|
|
const childrenDirtyTree = Array.from(node instanceof Group ? node.children() : [], (c) => buildDirtyTree(c)).filter(
|
|
(c) => c.paths.length > 0
|
|
);
|
|
const name = Group.is(node) ? node.name ?? node.id : node.id;
|
|
const paths = childrenDirtyTree.length ? childrenDirtyTree.flatMap((c) => c.paths).map((p) => `${name}.${p}`) : [name];
|
|
return {
|
|
dirtyTree: {
|
|
name,
|
|
node,
|
|
dirty: nodeDirty,
|
|
...childrenDirtyTree.map((c) => c.dirtyTree).filter((t) => t.dirty != null).reduce((result, childTree) => {
|
|
result[childTree.name ?? "<unknown>"] = childTree;
|
|
return result;
|
|
}, {})
|
|
},
|
|
paths
|
|
};
|
|
}
|
|
function pct(rendered, skipped) {
|
|
const total = rendered + skipped;
|
|
return `${rendered} / ${total} (${Math.round(100 * rendered / total)}%)`;
|
|
}
|
|
function time2(name, start2, end3) {
|
|
const duration = end3 == null ? start2 : end3 - start2;
|
|
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/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(text2, x, y, options) {
|
|
if (isArray(text2)) {
|
|
const { font: font3, lineHeight, textAlign, textBaseline } = options;
|
|
const { width: width2, height: height2, lineMetrics } = measureTextSegments(text2, font3);
|
|
const totalHeight = lineHeight ? lineHeight * lineMetrics.length : height2;
|
|
const offsetTop = _Text.calcTopOffset(totalHeight, lineMetrics[0], textBaseline);
|
|
const offsetLeft = _Text.calcLeftOffset(width2, textAlign);
|
|
return new BBox(x - offsetLeft, y - offsetTop, width2, totalHeight);
|
|
} else {
|
|
return _Text.computeBBox(toTextString(text2).split(LineSplitter), x, y, options);
|
|
}
|
|
}
|
|
static computeBBox(lines, x, y, opts) {
|
|
const { font: font3, lineHeight, textAlign, textBaseline } = opts;
|
|
const { width: width2, height: height2, lineMetrics } = cachedTextMeasurer(font3).measureLines(lines);
|
|
const totalHeight = lineHeight ? lineHeight * lineMetrics.length : height2;
|
|
const offsetTop = _Text.calcTopOffset(totalHeight, lineMetrics[0], textBaseline);
|
|
const offsetLeft = _Text.calcLeftOffset(width2, textAlign);
|
|
return new BBox(x - offsetLeft, y - offsetTop, width2, totalHeight);
|
|
}
|
|
static calcTopOffset(height2, textMetrics, textBaseline) {
|
|
switch (textBaseline) {
|
|
case "alphabetic":
|
|
return textMetrics?.ascent ?? 0;
|
|
case "middle":
|
|
return height2 / 2;
|
|
case "bottom":
|
|
return height2;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
static calcSegmentedTopOffset(height2, 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
|
|
) : height2 / 2;
|
|
case "bottom":
|
|
return height2;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
static calcLeftOffset(width2, textAlign) {
|
|
let offset = 0;
|
|
switch (textAlign) {
|
|
case "center":
|
|
offset = 0.5;
|
|
break;
|
|
case "right":
|
|
case "end":
|
|
offset = 1;
|
|
}
|
|
return width2 * offset;
|
|
}
|
|
getBBox() {
|
|
const bbox = super.getBBox();
|
|
if (!this.textMap?.size || !isArray(this.text))
|
|
return bbox;
|
|
const { height: height2, lineMetrics } = measureTextSegments(this.text, this);
|
|
const offsetTop = _Text.calcSegmentedTopOffset(height2, 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: width2, height: height2, ascent, segments } of measureTextSegments(this.text, this).lineMetrics) {
|
|
let offsetX = 0;
|
|
for (const { color: color2, textMetrics, ...segment } of segments) {
|
|
const textNode = textNodes.next().value;
|
|
textNode.x = this.x - width2 / 2 + offsetX;
|
|
textNode.y = ascent + offsetY;
|
|
textNode.setProperties({ ...segment, fill: color2 ?? this.fill });
|
|
const textBBox = textNode.getBBox();
|
|
this.textMap.set(textNode, textBBox);
|
|
offsetX += textMetrics.width;
|
|
}
|
|
offsetY += height2;
|
|
}
|
|
}
|
|
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: width2, height: height2, lineMetrics } = measureTextSegments(this.text, this);
|
|
let translateX = 0;
|
|
switch (this.textAlign) {
|
|
case "left":
|
|
case "start":
|
|
translateX = width2 / 2;
|
|
break;
|
|
case "right":
|
|
case "end":
|
|
translateX = width2 / -2;
|
|
}
|
|
const translateY = this.y - _Text.calcSegmentedTopOffset(height2, 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: stroke3, strokeWidth, font: font3, textAlign } = this;
|
|
if (!fill && !(stroke3 && strokeWidth) || !this.layerManager) {
|
|
return super.render(renderCtx);
|
|
}
|
|
const { ctx } = renderCtx;
|
|
if (ctx.font !== font3) {
|
|
ctx.font = font3;
|
|
}
|
|
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: width2, height: height2 } = textBBox.grow(this.boxPadding);
|
|
this.boxing.opacity = this.opacity;
|
|
this.boxing.x = x;
|
|
this.boxing.y = y;
|
|
this.boxing.width = width2;
|
|
this.boxing.height = height2;
|
|
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 measurer3 = cachedTextMeasurer(this);
|
|
const { lineMetrics } = measurer3.measureLines(lines);
|
|
const { textBaseline, lineHeight = measurer3.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 -= measurer3.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 stroke3 = props.border?.enabled ? props.border?.stroke : void 0;
|
|
if (props.fill != null || stroke3 != 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 = stroke3;
|
|
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: stroke3, strokeWidth, strokeOpacity } = this.boxing ?? {};
|
|
return {
|
|
border: { enabled: stroke3 != null, stroke: stroke3, strokeWidth, strokeOpacity },
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
padding: this.boxPadding
|
|
};
|
|
}
|
|
toSVG() {
|
|
if (!this.visible || !this.hasRenderableText())
|
|
return;
|
|
const text2 = this.text;
|
|
if (text2 == null)
|
|
return;
|
|
const element2 = createSvgElement("text");
|
|
if (isArray(text2)) {
|
|
for (const segment of text2) {
|
|
const segmentElement = createSvgElement("tspan");
|
|
setSvgFontAttributes(segmentElement, {
|
|
fontSize: segment.fontSize ?? this.fontSize,
|
|
fontFamily: segment.fontFamily ?? this.fontFamily,
|
|
fontWeight: segment.fontWeight ?? this.fontWeight,
|
|
fontStyle: segment.fontStyle ?? this.fontStyle
|
|
});
|
|
this.applySvgFillAttributes(segmentElement);
|
|
segmentElement.textContent = toTextString(segment.text);
|
|
element2.append(segmentElement);
|
|
}
|
|
} else {
|
|
this.applySvgFillAttributes(element2);
|
|
setSvgFontAttributes(element2, this);
|
|
element2.setAttribute(
|
|
"text-anchor",
|
|
{
|
|
center: "middle",
|
|
left: "start",
|
|
right: "end",
|
|
start: "start",
|
|
end: "end"
|
|
}[this.textAlign ?? "start"]
|
|
);
|
|
element2.setAttribute("alignment-baseline", this.textBaseline);
|
|
element2.setAttribute("x", String(this.x));
|
|
element2.setAttribute("y", String(this.y));
|
|
element2.textContent = toTextString(text2);
|
|
}
|
|
return { elements: [element2] };
|
|
}
|
|
hasRenderableText() {
|
|
const { text: text2 } = this;
|
|
if (text2 == null) {
|
|
return false;
|
|
}
|
|
return isArray(text2) ? true : toTextString(text2) !== "";
|
|
}
|
|
};
|
|
_Text.className = "Text";
|
|
_Text.debug = debugLogger_exports.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/background/background.ts
|
|
var Background = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.rectNode = new Rect();
|
|
this.textNode = new Text();
|
|
this.fill = "white";
|
|
this.node = this.createNode();
|
|
this.node.append([this.rectNode, this.textNode]);
|
|
this.visible = true;
|
|
this.cleanup.register(
|
|
ctx.scene.attachNode(this.node),
|
|
ctx.eventsHub.on("layout:complete", (e) => this.onLayoutComplete(e))
|
|
);
|
|
}
|
|
createNode() {
|
|
return new Group({ name: "background", zIndex: 0 /* CHART_BACKGROUND */ });
|
|
}
|
|
onLayoutComplete(e) {
|
|
const { width: width2, height: height2 } = e.chart;
|
|
this.rectNode.width = width2;
|
|
this.rectNode.height = height2;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("node", "visible")
|
|
], Background.prototype, "visible", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("rectNode", "fill")
|
|
], Background.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Background.prototype, "image", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("textNode")
|
|
], Background.prototype, "text", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/caption.ts
|
|
var Caption = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.id = createId(this);
|
|
this.node = new RotatableText({ zIndex: 1 }).setProperties({
|
|
textAlign: "center",
|
|
pointerEvents: 1 /* None */
|
|
});
|
|
this.enabled = false;
|
|
this.textAlign = "center";
|
|
this.fontSize = 10 /* 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: text2, padding: padding2, wrapping } = this;
|
|
const maxWidth = Math.min(this.maxWidth ?? Infinity, containerWidth) - padding2 * 2;
|
|
const maxHeight = this.maxHeight ?? containerHeight - padding2 * 2;
|
|
const options = { maxWidth, maxHeight, font: this, textWrap: wrapping };
|
|
if (!Number.isFinite(maxWidth) && !Number.isFinite(maxHeight)) {
|
|
this.node.text = text2;
|
|
return;
|
|
}
|
|
let wrappedText;
|
|
if (isArray(text2)) {
|
|
wrappedText = wrapTextSegments(text2, options);
|
|
this.truncated = wrappedText.some(isSegmentTruncated);
|
|
} else {
|
|
wrappedText = wrapText(toTextString(text2), 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 = toPlainText(this.text);
|
|
if (textContent !== this.lastProxyTextContent) {
|
|
this.proxyText.textContent = textContent;
|
|
this.lastProxyTextContent = textContent;
|
|
}
|
|
const { lastProxyBBox } = this;
|
|
if (lastProxyBBox == null || bbox.x !== lastProxyBBox.x || bbox.y !== lastProxyBBox.y || bbox.width !== lastProxyBBox.width || bbox.height !== lastProxyBBox.height) {
|
|
this.proxyText.setBounds(bbox);
|
|
this.lastProxyBBox = { x: bbox.x, y: bbox.y, width: bbox.width, height: bbox.height };
|
|
}
|
|
}
|
|
handleMouseMove(moduleCtx, event) {
|
|
if (event != null && this.enabled && this.truncated) {
|
|
const { x, y } = Transformable.toCanvas(this.node);
|
|
const canvasX = event.sourceEvent.offsetX + x;
|
|
const canvasY = event.sourceEvent.offsetY + y;
|
|
moduleCtx.tooltipManager.updateTooltip(this.id, { canvasX, canvasY, showArrow: false }, [
|
|
{ type: "structured", title: toPlainText(this.text) }
|
|
]);
|
|
}
|
|
}
|
|
handleMouseLeave(moduleCtx, _event) {
|
|
moduleCtx.tooltipManager.removeTooltip(this.id, void 0, true);
|
|
}
|
|
destroy() {
|
|
this.destroyProxyText();
|
|
}
|
|
destroyProxyText() {
|
|
if (this.proxyText == null)
|
|
return;
|
|
for (const cleanup of this.proxyTextListeners ?? []) {
|
|
cleanup();
|
|
}
|
|
this.proxyTextListeners = void 0;
|
|
this.proxyText.destroy();
|
|
this.proxyText = void 0;
|
|
this.lastProxyTextContent = void 0;
|
|
this.lastProxyBBox = void 0;
|
|
}
|
|
};
|
|
Caption.className = "Caption";
|
|
Caption.SMALL_PADDING = 10;
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("node", "visible")
|
|
], Caption.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("node")
|
|
], Caption.prototype, "text", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("node")
|
|
], Caption.prototype, "textAlign", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("node")
|
|
], Caption.prototype, "fontStyle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("node")
|
|
], Caption.prototype, "fontWeight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("node")
|
|
], Caption.prototype, "fontSize", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("node")
|
|
], Caption.prototype, "fontFamily", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("node", "fill")
|
|
], Caption.prototype, "color", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Caption.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Caption.prototype, "maxWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Caption.prototype, "maxHeight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Caption.prototype, "wrapping", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Caption.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Caption.prototype, "layoutStyle", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/chartAxes.ts
|
|
var ChartAxes = class extends Array {
|
|
destroy() {
|
|
for (const axis of this) {
|
|
axis.destroy();
|
|
}
|
|
this.length = 0;
|
|
}
|
|
findById(id) {
|
|
return this.find((a) => a.id === id);
|
|
}
|
|
matches(comparison) {
|
|
return this.length === Object.keys(comparison).length && every(
|
|
comparison,
|
|
(id, object2) => isObject(object2) && "type" in object2 && this.findById(id)?.type === object2.type
|
|
);
|
|
}
|
|
getById(id) {
|
|
const axis = this.findById(id);
|
|
if (!axis)
|
|
throw new Error(`Could not find axis by id [${id}].`);
|
|
return axis;
|
|
}
|
|
};
|
|
var CartesianChartAxes = class extends ChartAxes {
|
|
get ["x" /* X */]() {
|
|
return this.getById("x" /* X */);
|
|
}
|
|
get ["y" /* Y */]() {
|
|
return this.getById("y" /* Y */);
|
|
}
|
|
perpendicular(to) {
|
|
const direction = to.direction === "x" /* X */ ? "y" /* Y */ : "x" /* X */;
|
|
return this[direction];
|
|
}
|
|
};
|
|
var PolarChartAxes = class extends ChartAxes {
|
|
get ["angle" /* Angle */]() {
|
|
return this.getById("angle" /* Angle */);
|
|
}
|
|
get ["radius" /* Radius */]() {
|
|
return this.getById("radius" /* Radius */);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/chartCaptions.ts
|
|
var ChartCaptions = class {
|
|
constructor() {
|
|
this.title = new Caption();
|
|
this.subtitle = new Caption();
|
|
this.footnote = new Caption();
|
|
}
|
|
positionCaptions({ layoutBox }) {
|
|
const { title, subtitle, footnote } = this;
|
|
const maxHeight = layoutBox.height / 10;
|
|
if (title.enabled) {
|
|
this.positionCaption("top", title, layoutBox, maxHeight);
|
|
this.shrinkLayoutByCaption("top", title, layoutBox);
|
|
}
|
|
if (subtitle.enabled) {
|
|
this.positionCaption("top", subtitle, layoutBox, maxHeight);
|
|
this.shrinkLayoutByCaption("top", subtitle, layoutBox);
|
|
}
|
|
if (footnote.enabled) {
|
|
this.positionCaption("bottom", footnote, layoutBox, maxHeight);
|
|
this.shrinkLayoutByCaption("bottom", footnote, layoutBox);
|
|
}
|
|
}
|
|
positionAbsoluteCaptions(ctx) {
|
|
const { title, subtitle, footnote } = this;
|
|
const { rect: rect2 } = ctx.series;
|
|
for (const caption of [title, subtitle, footnote]) {
|
|
if (caption.layoutStyle !== "overlay")
|
|
continue;
|
|
if (caption.textAlign === "left") {
|
|
caption.node.x = rect2.x + caption.padding;
|
|
} else if (caption.textAlign === "right") {
|
|
const bbox = caption.node.getBBox();
|
|
caption.node.x = rect2.x + rect2.width - bbox.width - caption.padding;
|
|
}
|
|
}
|
|
}
|
|
computeX(align2, layoutBox) {
|
|
if (align2 === "left") {
|
|
return layoutBox.x;
|
|
} else if (align2 === "right") {
|
|
return layoutBox.x + layoutBox.width;
|
|
}
|
|
return layoutBox.x + layoutBox.width / 2;
|
|
}
|
|
positionCaption(vAlign, caption, layoutBox, maxHeight) {
|
|
if (!caption.text)
|
|
return;
|
|
const { lineMetrics } = isArray(caption.text) ? measureTextSegments(caption.text, caption) : cachedTextMeasurer(caption).measureLines(toTextString(caption.text));
|
|
const containerHeight = Math.max(lineMetrics[0].height, maxHeight);
|
|
caption.node.x = this.computeX(caption.textAlign, layoutBox) + caption.padding;
|
|
caption.node.y = layoutBox.y + (vAlign === "top" ? 0 : layoutBox.height) + caption.padding;
|
|
caption.node.textBaseline = vAlign;
|
|
caption.computeTextWrap(layoutBox.width, containerHeight);
|
|
}
|
|
shrinkLayoutByCaption(vAlign, caption, layoutBox) {
|
|
if (caption.layoutStyle === "block") {
|
|
const bbox = caption.node.getBBox().clone();
|
|
const { spacing = 0 } = caption;
|
|
if (vAlign === "bottom" && isArray(caption.text)) {
|
|
bbox.y -= bbox.height;
|
|
}
|
|
layoutBox.shrink(
|
|
vAlign === "top" ? Math.ceil(bbox.y - layoutBox.y + bbox.height + spacing) : Math.ceil(layoutBox.y + layoutBox.height - bbox.y + spacing),
|
|
vAlign
|
|
);
|
|
}
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChartCaptions.prototype, "title", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChartCaptions.prototype, "subtitle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChartCaptions.prototype, "footnote", 2);
|
|
|
|
// packages/ag-charts-community/src/api/preset/chartTypeOriginator.ts
|
|
var chartTypes = [
|
|
"candlestick",
|
|
"hollow-candlestick",
|
|
"ohlc",
|
|
"line",
|
|
"step-line",
|
|
"hlc",
|
|
"high-low"
|
|
];
|
|
var ChartTypeOriginator = class {
|
|
constructor(chartService) {
|
|
this.chartService = chartService;
|
|
this.mementoOriginatorKey = "chartType";
|
|
}
|
|
createMemento() {
|
|
let chartType = this.chartService.publicApi?.getOptions()?.chartType;
|
|
chartType ?? (chartType = "candlestick");
|
|
return chartType;
|
|
}
|
|
guardMemento(blob) {
|
|
return blob == null || chartTypes.includes(blob);
|
|
}
|
|
restoreMemento(_version, _mementoVersion, memento) {
|
|
if (memento == null)
|
|
return;
|
|
const options = { chartType: memento };
|
|
this.chartService.publicApi?.updateDelta(options).catch((e) => logger_exports.error("error restoring state", e));
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/version.ts
|
|
var VERSION = "13.1.0";
|
|
|
|
// packages/ag-charts-community/src/api/state/historyManager.ts
|
|
var NOT_FOUND = Symbol("previous-memento-not-found");
|
|
var HistoryManager = class {
|
|
constructor(eventsHub) {
|
|
this.history = [];
|
|
this.historyIndex = -1;
|
|
this.originators = /* @__PURE__ */ new Map();
|
|
this.clearState = /* @__PURE__ */ new Map();
|
|
this.maxHistoryLength = 100;
|
|
this.debug = debugLogger_exports.create(true, "history");
|
|
this.cleanup = new CleanupRegistry();
|
|
this.cleanup.register(
|
|
eventsHub.on("series:undo", this.undo.bind(this)),
|
|
eventsHub.on("series:redo", this.redo.bind(this))
|
|
);
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
}
|
|
addMementoOriginator(originator) {
|
|
this.originators.set(originator.mementoOriginatorKey, originator);
|
|
this.clearState.set(originator.mementoOriginatorKey, originator.createMemento());
|
|
this.debugEvent("History add originator:", originator.mementoOriginatorKey);
|
|
}
|
|
clear() {
|
|
this.debug(`History clear:`, Object.keys(this.originators));
|
|
this.history = [];
|
|
this.historyIndex = -1;
|
|
for (const [mementoOriginatorKey, originator] of this.originators.entries()) {
|
|
this.clearState.set(mementoOriginatorKey, originator.createMemento());
|
|
}
|
|
}
|
|
record(label, ...originators) {
|
|
if (this.historyIndex < this.history.length - 1) {
|
|
this.history = this.history.slice(0, this.historyIndex + 1);
|
|
}
|
|
if (this.history.length > this.maxHistoryLength) {
|
|
this.history = this.history.slice(-this.maxHistoryLength);
|
|
}
|
|
const mementos = /* @__PURE__ */ new Map();
|
|
for (const originator of originators) {
|
|
if (!this.originators.has(originator.mementoOriginatorKey)) {
|
|
throw new Error(
|
|
`Originator [${originator.mementoOriginatorKey}] has not been added to the HistoryManager.`
|
|
);
|
|
}
|
|
mementos.set(originator.mementoOriginatorKey, originator.createMemento());
|
|
}
|
|
this.history.push({ label, mementos });
|
|
this.historyIndex = this.history.length - 1;
|
|
this.debugEvent(`History record: [${label}]`);
|
|
}
|
|
undo() {
|
|
const undoAction = this.history[this.historyIndex];
|
|
if (!undoAction)
|
|
return;
|
|
for (const mementoOriginatorKey of undoAction.mementos.keys()) {
|
|
const previousMemento = this.findPreviousMemento(mementoOriginatorKey);
|
|
if (previousMemento === NOT_FOUND) {
|
|
throw new Error(`Could not find previous memento for [${mementoOriginatorKey}].`);
|
|
}
|
|
this.restoreMemento(mementoOriginatorKey, previousMemento);
|
|
}
|
|
this.historyIndex -= 1;
|
|
this.debugEvent(`History undo: [${undoAction.label}]`);
|
|
}
|
|
redo() {
|
|
const redoAction = this.history[this.historyIndex + 1];
|
|
if (!redoAction)
|
|
return;
|
|
for (const [mementoOriginatorKey, memento] of redoAction.mementos.entries()) {
|
|
this.restoreMemento(mementoOriginatorKey, memento);
|
|
}
|
|
this.historyIndex += 1;
|
|
this.debugEvent(`History redo: [${redoAction.label}]`);
|
|
}
|
|
findPreviousMemento(mementoOriginatorKey) {
|
|
for (let i = this.historyIndex - 1; i >= 0; i--) {
|
|
if (this.history[i].mementos.has(mementoOriginatorKey)) {
|
|
return this.history[i].mementos.get(mementoOriginatorKey);
|
|
}
|
|
}
|
|
if (this.clearState.has(mementoOriginatorKey)) {
|
|
return this.clearState.get(mementoOriginatorKey);
|
|
}
|
|
return NOT_FOUND;
|
|
}
|
|
restoreMemento(mementoOriginatorKey, memento) {
|
|
this.originators.get(mementoOriginatorKey)?.restoreMemento(VERSION, VERSION, memento);
|
|
}
|
|
debugEvent(...logContent) {
|
|
this.debug(
|
|
...logContent,
|
|
this.history.map((action, index) => index === this.historyIndex ? `** ${action.label} **` : action.label)
|
|
);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/api/state/stateManager.ts
|
|
var StateManager = class {
|
|
constructor() {
|
|
this.caretaker = new MementoCaretaker(VERSION);
|
|
this.state = /* @__PURE__ */ new Map();
|
|
}
|
|
setState(originator, value) {
|
|
if (objectsEqual(this.state.get(originator.mementoOriginatorKey), value)) {
|
|
return;
|
|
}
|
|
this.setStateAndRestore(originator, value);
|
|
}
|
|
setStateAndRestore(originator, value) {
|
|
this.state.set(originator.mementoOriginatorKey, value);
|
|
this.restoreState(originator);
|
|
}
|
|
restoreState(originator) {
|
|
const { caretaker, state } = this;
|
|
if (!state.has(originator.mementoOriginatorKey))
|
|
return;
|
|
const value = state.get(originator.mementoOriginatorKey);
|
|
caretaker.restore({ version: caretaker.version, [originator.mementoOriginatorKey]: value }, originator);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/styles.css
|
|
var styles_default = '.ag-charts-wrapper,.ag-charts-wrapper:after,.ag-charts-wrapper:before,.ag-charts-wrapper *,.ag-charts-wrapper *:after,.ag-charts-wrapper *:before{box-sizing:border-box}.ag-charts-wrapper{--align-items: center;--justify-content: center;position:relative;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.ag-charts-wrapper--safe-horizontal{--justify-content: flex-start}.ag-charts-wrapper--safe-vertical{--align-items: flex-start}.ag-charts-tab-guard{width:0%;height:0%;position:absolute;pointer-events:none}.ag-charts-canvas-background{position:absolute}.ag-charts-canvas-center{width:100%;height:100%;position:absolute;touch-action:auto;pointer-events:auto;display:flex;align-items:var(--align-items);justify-content:var(--justify-content)}.ag-charts-canvas-container,.ag-charts-canvas{position:relative;user-select:none;-webkit-user-select:none}.ag-charts-canvas-container>*,.ag-charts-canvas>*{pointer-events:none}.ag-charts-canvas canvas{display:block}.ag-charts-series-area{outline:none;pointer-events:auto;position:absolute}.ag-charts-swapchain{top:0;left:0;outline:none;opacity:0;pointer-events:none;position:absolute;width:100%;height:100%}.ag-charts-swapchain:focus-visible{opacity:1}.ag-charts-canvas-proxy,.ag-charts-canvas-overlay{inset:0;pointer-events:none;position:absolute;user-select:none;-webkit-user-select:none}.ag-charts-canvas-overlay>*{position:absolute;pointer-events:auto}.ag-charts-theme-default,.ag-charts-theme-default-dark{--ag-charts-accent-color: #2196f3;--ag-charts-background-color: #fff;--ag-charts-border-color: #dddddd;--ag-charts-border-radius: 4px;--ag-charts-chart-background-color: #fff;--ag-charts-chart-padding: 20px;--ag-charts-focus-shadow: 0 0 0 3px #2196f3;--ag-charts-foreground-color: #181d1f;--ag-charts-font-family: Verdana, sans-serif;--ag-charts-font-size: 12px;--ag-charts-font-weight: 400;--ag-charts-popup-shadow: 0 0 16px rgba(0, 0, 0, .15);--ag-charts-subtle-text-color: #8c8c8c;--ag-charts-text-color: #181d1f;--ag-charts-chrome-background-color: #fafafa;--ag-charts-chrome-font-family: Verdana, sans-serif;--ag-charts-chrome-font-size: 12px;--ag-charts-chrome-font-weight: 400;--ag-charts-chrome-subtle-text-color: #8c8c8c;--ag-charts-chrome-text-color: #181d1f;--ag-charts-button-background-color: #fff;--ag-charts-button-border: 1px solid #dddddd;--ag-charts-button-font-weight: normal;--ag-charts-button-text-color: inherit;--ag-charts-input-background-color: #fff;--ag-charts-input-border: 1px solid #dddddd;--ag-charts-input-text-color: #181d1f;--ag-charts-menu-background-color: #fafafa;--ag-charts-menu-border: 1px solid #dddddd;--ag-charts-menu-text-color: #181d1f;--ag-charts-panel-background-color: #fafafa;--ag-charts-panel-text-color: #181d1f;--ag-charts-tooltip-background-color: #fafafa;--ag-charts-tooltip-border: 1px solid #dddddd;--ag-charts-tooltip-text-color: #181d1f;--ag-charts-tooltip-subtle-text-color: #8c8c8c;--ag-charts-crosshair-label-background-color: #fafafa;--ag-charts-crosshair-label-text-color: #181d1f;--ag-charts-spacing: 4px;--ag-charts-icon-size: 16px;--ag-charts-focus-color: color-mix(in srgb, var(--ag-charts-background-color), var(--ag-charts-accent-color) 12%);--ag-charts-input-border-radius: var(--ag-charts-border-radius);--ag-charts-input-focus-border-color: var(--ag-charts-accent-color);--ag-charts-input-focus-text-color: var(--ag-charts-accent-color);--ag-charts-input-disabled-background-color: color-mix( in srgb, var(--ag-charts-chrome-background-color), var(--ag-charts-foreground-color) 6% );--ag-charts-input-disabled-border-color: var(--ag-charts-border-color);--ag-charts-input-disabled-text-color: color-mix( in srgb, var(--ag-charts-chrome-background-color), var(--ag-charts-input-text-color) 50% );--ag-charts-input-placeholder-text-color: color-mix( in srgb, var(--ag-charts-input-background-color), var(--ag-charts-input-text-color) 60% );--ag-charts-button-border-radius: var(--ag-charts-border-radius);--ag-charts-button-focus-background-color: color-mix( in srgb, var(--ag-charts-button-background-color), var(--ag-charts-accent-color) 12% );--ag-charts-button-focus-border-color: var(--ag-charts-accent-color);--ag-charts-button-focus-text-color: var(--ag-charts-accent-color);--ag-charts-button-disabled-background-color: color-mix( in srgb, var(--ag-charts-chrome-background-color), var(--ag-charts-foreground-color) 6% );--ag-charts-button-disabled-border-color: var(--ag-charts-border-color);--ag-charts-button-disabled-text-color: color-mix( in srgb, var(--ag-charts-chrome-background-color), var(--ag-charts-chrome-text-color) 50% );--ag-charts-checkbox-background-color: color-mix( in srgb, var(--ag-charts-background-color), var(--ag-charts-foreground-color) 35% );--ag-charts-checkbox-checked-background-color: var(--ag-charts-accent-color);--ag-charts-tooltip-border-radius: var(--ag-charts-border-radius);--ag-charts-menu-border-radius: var(--ag-charts-border-radius);--ag-charts-chrome-font-size-small: var(--ag-charts-chrome-font-size);--ag-charts-chrome-font-size-medium: calc(var(--ag-charts-chrome-font-size) * (13 / 12));--ag-charts-chrome-font-size-large: calc(var(--ag-charts-chrome-font-size) * (14 / 12));--ag-charts-border: 1px solid var(--ag-charts-border-color);--ag-charts-focus-border: 1px solid var(--ag-charts-accent-color);--ag-charts-focus-border-shadow: 0 0 0 3px color-mix(in srgb, transparent, var(--ag-charts-accent-color) 20%);--ag-charts-layer-menu: 6;--ag-charts-layer-ui-overlay: 5;--ag-charts-layer-tooltip: 4;--ag-charts-layer-toolbar: 3;--ag-charts-layer-crosshair: 2;--ag-charts-layer-annotations: 1}.ag-charts-theme-default-dark{--ag-charts-focus-color: color-mix(in srgb, var(--ag-charts-background-color), var(--ag-charts-accent-color) 22%)}.ag-chart-canvas-wrapper .ag-charts-theme-default{--ag-charts-border-radius: var(--ag-border-radius, 4px);--ag-charts-border: var(--ag-borders-critical, solid 1px) var(--ag-charts-border-color);--ag-charts-focus-shadow: var(--ag-focus-shadow, 0 0 0 3px var(--ag-charts-accent-color));--ag-charts-focus-border-shadow: var( --ag-focus-shadow, 0 0 0 3px color-mix(in srgb, transparent, var(--ag-charts-accent-color) 20%) )}.ag-charts-icon{display:block;width:20px;height:20px;speak:none;speak:never;mask:var(--icon) center / contain no-repeat;background-color:currentColor;transition:background-color .25s ease-in-out}.ag-charts-icon-align-center{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJNNyAxMGg2djFIN3pNNCA3aDEydjFINHptMSA2aDEwdjFINXoiLz48L3N2Zz4=)}.ag-charts-icon-align-left{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJNNCAxMGg2djFINHptMC0zaDEydjFINHptMCA2aDEwdjFINHoiLz48L3N2Zz4=)}.ag-charts-icon-align-right{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJNMTAgMTBoNnYxaC02ek00IDdoMTJ2MUg0em0yIDZoMTB2MUg2eiIvPjwvc3ZnPg==)}.ag-charts-icon-arrow-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTE1LjI5MyA0LjVIMTIuNXYtMUgxN3Y0aC0xVjUuMjA3bC05LjY0NiA5LjY0Ny0uNzA4LS43MDh6IiBmaWxsPSIjMDAwIi8+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik03IDE2YTIuNSAyLjUgMCAxIDEtNSAwIDIuNSAyLjUgMCAwIDEgNSAwbS0yLjUgMS41YTEuNSAxLjUgMCAxIDAgMC0zIDEuNSAxLjUgMCAwIDAgMCAzIiBmaWxsPSIjMDAwIi8+PC9zdmc+)}.ag-charts-icon-arrow-down-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik02IDhMMS41IDhMMTAgMThMMTguNSA4TDE0IDhMMTQgM0w2IDNMNiA4Wk03IDRMNyA5SDMuNjYyNDRMMTAgMTYuNDU2TDE2LjMzNzYgOUwxMyA5TDEzIDRMNyA0WiIgZmlsbD0iYmxhY2siLz4KPC9zdmc+Cg==)}.ag-charts-icon-arrow-up-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNCAxMkgxOC41TDEwIDJMMS41IDEySDZMNi4wMDAwMiAxN0gxNFYxMlpNMTMgMTZWMTFIMTYuMzM3NkwxMCAzLjU0NDA1TDMuNjYyNDQgMTFIN0w3LjAwMDAyIDE2SDEzWiIgZmlsbD0iYmxhY2siLz4KPC9zdmc+Cg==)}.ag-charts-icon-callout-annotation{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMyA0LjVBMS41IDEuNSAwIDAgMSA0LjUgM2gxMUExLjUgMS41IDAgMCAxIDE3IDQuNXY4YTEuNSAxLjUgMCAwIDEtMS41IDEuNWgtNC41MTRhMjYgMjYgMCAwIDAtMi4wMTcgMS41NGwtLjMxNC4yNmMtLjU1LjQ1Ny0xLjExNS45MjYtMS43NiAxLjQtLjY2OS40OTEtMS41NjItLjAxMi0xLjU2Mi0uOFYxNEg0LjVBMS41IDEuNSAwIDAgMSAzIDEyLjV6TTQuNSA0YS41LjUgMCAwIDAtLjUuNXY4YS41LjUgMCAwIDAgLjUuNWgxLjgzM3YzLjM3MmEzNiAzNiAwIDAgMCAxLjY3OC0xLjMzOGwuMzItLjI2NWEyNiAyNiAwIDAgMSAyLjIyNS0xLjY4NWwuMTI2LS4wODRIMTUuNWEuNS41IDAgMCAwIC41LS41di04YS41LjUgMCAwIDAtLjUtLjV6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=)}.ag-charts-icon-candlestick-series{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzEzMTcyMiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNNyAxdjNoMnYxMkg3djNINnYtM0g0VjRoMlYxek01IDVoM3YxMEg1ek0xMSAxNFY2aDJWMy4yNWgxVjZoMnY4aC0ydjIuNzVoLTFWMTR6bTEtN2gzdjZoLTN6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=)}.ag-charts-icon-close{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNSA1IDEwIDEwTTUgMTUgMTUgNSIgc3Ryb2tlPSIjMDAwIi8+PC9zdmc+)}.ag-charts-icon-comment-annotation{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNNy41MTMgMy45OTVhNi41IDYuNSAwIDAgMSA2LjA5OCAxMS40MWMtLjU4OC4zOTMtMS4yMTcuNTM2LTEuODI5LjU4NWExMyAxMyAwIDAgMS0xLjI3LjAxN0EyNyAyNyAwIDAgMCAxMCAxNkg0LjVhLjUuNSAwIDAgMS0uNS0uNVYxMHEwLS4yNDctLjAwNy0uNTEzYy0uMDA4LS40MTYtLjAxNi0uODU3LjAxNy0xLjI2OS4wNS0uNjEyLjE5Mi0xLjI0LjU4NS0xLjgzYTYuNSA2LjUgMCAwIDEgMi45MTgtMi4zOTNtMy41Ni42MWE1LjUgNS41IDAgMCAwLTUuNjQ2IDIuMzRjLS4yNjYuMzk3LS4zNzkuODQyLS40MiAxLjM1NC0uMDMuMzYtLjAyMi43MTgtLjAxNSAxLjEwOFE1IDkuNjg5IDUgMTB2NWg1cS4zMTEuMDAxLjU5My4wMDhjLjM5LjAwNy43NDcuMDE1IDEuMTA4LS4wMTUuNTEyLS4wNDEuOTU3LS4xNTQgMS4zNTUtLjQyYTUuNSA1LjUgMCAwIDAtMS45ODMtOS45NjciIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==)}.ag-charts-icon-crosshair-add-line{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZD0iTTEwIDUuNWEuNS41IDAgMCAxIC41LjV2My41aDMuODc1YS41LjUgMCAwIDEgMCAxSDEwLjV2NC4yNWEuNS41IDAgMSAxLTEgMFYxMC41SDUuNjI1YS41LjUgMCAxIDEgMC0xSDkuNVY2YS41LjUgMCAwIDEgLjUtLjUiLz48L3N2Zz4=)}.ag-charts-icon-date-range-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMiAyaDF2MTZIMnptMTUgMGgxdjE2aC0xeiIgZmlsbD0iIzE4MUQxRiIvPjxwYXRoIGQ9Ik0xMy4xNTcgMTFINXYtMWg3Ljc5M0wxMSA4LjIwN2wuNzA3LS43MDcgMy4xODIgMy4xODItMy4xODIgMy4xODItLjcwNy0uNzA3eiIgZmlsbD0iIzAwMCIvPjwvc3ZnPg==)}.ag-charts-icon-date-price-range-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMyAySDJ2MTZoMXptMy41MDcgNC44OUw4LjUgNC44OTVWMTBINXYxaDMuNXY3aDF2LTdoNS4wODhsLTEuOTU3IDEuOTU3LjcwNy43MDcgMy4xODItMy4xODJMMTMuMzM4IDcuM2wtLjcwNy43MDdMMTQuNjI0IDEwSDkuNVY0LjkzMmwxLjk1NyAxLjk1Ny43MDctLjcwN0w4Ljk4MiAzIDUuOCA2LjE4MnoiIGZpbGw9IiMxODFEMUYiLz48L3N2Zz4=)}.ag-charts-icon-delete{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzEzMTcyMiIgZD0iTTguNDk2IDguOTk2QS41LjUgMCAwIDEgOSA5LjQ5MnY0YS41LjUgMCAxIDEtMSAuMDA4di00YS41LjUgMCAwIDEgLjQ5Ni0uNTA0TTEyIDkuNWEuNS41IDAgMCAwLTEgMHY0YS41LjUgMCAwIDAgMSAweiIvPjxwYXRoIGZpbGw9IiMxMzE3MjIiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTYgNVYzLjVBMi41IDIuNSAwIDAgMSA4LjUgMWgzQTIuNSAyLjUgMCAwIDEgMTQgMy41VjVoMi44MzNhLjUuNSAwIDAgMSAwIDFIMTV2MTAuMjVjMCAuNDE1LS4wNjYuODYzLS4zIDEuMjIxLS4yNTcuMzk0LS42NzIuNjEyLTEuMi42MTJoLTdjLS41MjggMC0uOTQzLS4yMTgtMS4yLS42MTItLjIzNC0uMzU4LS4zLS44MDYtLjMtMS4yMjFWNkgzLjMzM2EuNS41IDAgMCAxIDAtMXptMS0xLjVBMS41IDEuNSAwIDAgMSA4LjUgMmgzQTEuNSAxLjUgMCAwIDEgMTMgMy41VjVIN3pNNiAxNi4yNVY2aDh2MTAuMjVjMCAuMzM1LS4wNTkuNTU0LS4xMzguNjc1LS4wNTUuMDg1LS4xNC4xNTgtLjM2Mi4xNThoLTdjLS4yMjIgMC0uMzA3LS4wNzMtLjM2Mi0uMTU4LS4wOC0uMTIxLS4xMzgtLjM0LS4xMzgtLjY3NSIgY2xpcC1ydWxlPSJldmVub2RkIi8+PC9zdmc+)}.ag-charts-icon-disjoint-channel,.ag-charts-icon-disjoint-channel-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzEzMTcyMiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTkuMDI4IDE3LjQ2YTIuMjUgMi4yNSAwIDAgMC00LjA5Mi0xLjg1bC05LjUxMS0yLjM3OGEyLjI1IDIuMjUgMCAxIDAtLjIyNS45NzRsOS40NzUgMi4zNjlhMi4yNTEgMi4yNTEgMCAwIDAgNC4zNTMuODg2bS0xLjY2Mi0xLjk2NWExLjI1IDEuMjUgMCAxIDEtLjg4NSAyLjMzOCAxLjI1IDEuMjUgMCAwIDEgLjg4NS0yLjMzOE00LjM0MyAxMy42NjlhMS4yNSAxLjI1IDAgMSAwLTIuMzM4LS44ODUgMS4yNSAxLjI1IDAgMCAwIDIuMzM4Ljg4NU0zLjk3IDguNzY5YTIuMjUgMi4yNSAwIDAgMCAxLjQ1NS0yLjExbDkuNTExLTIuMzc4YTIuMjUgMi4yNSAwIDEgMC0uMjYtLjk2NUw1LjIgNS42ODVhMi4yNSAyLjI1IDAgMSAwLTEuMjMgMy4wODRtLjM3My0yLjU0N2ExLjI1IDEuMjUgMCAxIDEtMi4zMzguODg1IDEuMjUgMS4yNSAwIDAgMSAyLjMzOC0uODg1bTEzLjc1LTMuNDM4YTEuMjUgMS4yNSAwIDEgMS0yLjMzOC44ODUgMS4yNSAxLjI1IDAgMCAxIDIuMzM4LS44ODUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==)}.ag-charts-icon-drag-handle{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48Y2lyY2xlIGN4PSI1Ljc1IiBjeT0iNy43NSIgcj0iLjc1IiBmaWxsPSIjMDAwIiBmaWxsLW9wYWNpdHk9Ii41Ii8+PGNpcmNsZSBjeD0iOS43NSIgY3k9IjcuNzUiIHI9Ii43NSIgZmlsbD0iIzAwMCIgZmlsbC1vcGFjaXR5PSIuNSIvPjxjaXJjbGUgY3g9IjEzLjc1IiBjeT0iNy43NSIgcj0iLjc1IiBmaWxsPSIjMDAwIiBmaWxsLW9wYWNpdHk9Ii41Ii8+PGNpcmNsZSBjeD0iMTMuNzUiIGN5PSIxMS43NSIgcj0iLjc1IiBmaWxsPSIjMDAwIiBmaWxsLW9wYWNpdHk9Ii41Ii8+PGNpcmNsZSBjeD0iOS43NSIgY3k9IjExLjc1IiByPSIuNzUiIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iLjUiLz48Y2lyY2xlIGN4PSI1Ljc1IiBjeT0iMTEuNzUiIHI9Ii43NSIgZmlsbD0iIzAwMCIgZmlsbC1vcGFjaXR5PSIuNSIvPjwvc3ZnPg==)}.ag-charts-icon-fibonacci-retracement-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJNMiA1aDEydjFIMnoiLz48Y2lyY2xlIGN4PSIxNS43NSIgY3k9IjUuNSIgcj0iMS43NSIgc3Ryb2tlPSIjMDAwIi8+PGNpcmNsZSBjeD0iNC4yNSIgY3k9IjE0LjUiIHI9IjEuNzUiIHN0cm9rZT0iIzAwMCIvPjxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik0xOCAxNUg2di0xaDEyem0wLTQuNUgydi0xaDE2eiIvPjwvc3ZnPg==)}.ag-charts-icon-fibonacci-retracement-trend-based-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJtNC45OTYgMTIuNjc0IDMuMjkxLTUuNzQzLjg2OC40OTctMy4yOTEgNS43NDN6Ii8+PGNpcmNsZSBjeD0iOS43NSIgY3k9IjUuNSIgcj0iMS43NSIgc3Ryb2tlPSIjMDAwIi8+PGNpcmNsZSBjeD0iNC4zNTEiIGN5PSIxNC41IiByPSIxLjc1IiBzdHJva2U9IiMwMDAiLz48cGF0aCBmaWxsPSIjMDAwIiBkPSJNMTggNmgtN1Y1aDd6bTAgNC41aC03di0xaDd6bTAgNC41SDZ2LTFoMTJ6Ii8+PC9zdmc+)}.ag-charts-icon-fill-color{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJtOC4wNzEgNC4wNi0uOTI0LS45MjQuNzA3LS43MDcgNy4yODggNy4yODgtNC45NSA0Ljk1YTMuNSAzLjUgMCAwIDEtNC45NSAwbC0xLjQxNC0xLjQxNGEzLjUgMy41IDAgMCAxIDAtNC45NXptLjcwNy43MDhMNC41MzYgOS4wMWEyLjUgMi41IDAgMCAwIDAgMy41MzZMNS45NSAxMy45NmEyLjUgMi41IDAgMCAwIDMuNTM1IDBsNC4yNDMtNC4yNDN6bTYuOSA3LjIwMi0uMzQ1LjM2My0uMzQ0LS4zNjNhLjUuNSAwIDAgMSAuNjg4IDBtLS4zNDUgMS4wOGE4IDggMCAwIDAtLjI4LjMyMyA0LjMgNC4zIDAgMCAwLS40MDkuNTgyYy0uMTEzLjIwMS0uMTQ0LjMyNi0uMTQ0LjM3OGEuODMzLjgzMyAwIDAgMCAxLjY2NyAwYzAtLjA1Mi0uMDMxLS4xNzctLjE0NC0uMzc4YTQuMyA0LjMgMCAwIDAtLjQxLS41ODIgOCA4IDAgMCAwLS4yOC0uMzIybS0uMzQ0LTEuMDguMzQ0LjM2My4zNDQtLjM2My4wMDIuMDAyLjAwNC4wMDQuMDEzLjAxMmE2IDYgMCAwIDEgLjIwNi4yMDhjLjEzMS4xMzYuMzA4LjMyNy40ODUuNTQ1LjE3Ni4yMTUuMzYzLjQ2Ny41MDcuNzI0LjEzNy4yNDMuMjczLjU1My4yNzMuODY4YTEuODMzIDEuODMzIDAgMSAxLTMuNjY3IDBjMC0uMzE1LjEzNi0uNjI1LjI3My0uODY4LjE0NC0uMjU3LjMzLS41MDkuNTA3LS43MjRhOSA5IDAgMCAxIC42NDUtLjcwOGwuMDQ2LS4wNDUuMDEzLS4wMTIuMDA0LS4wMDR6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=)}.ag-charts-icon-hollow-candlestick-series{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzEzMTcyMiIgZmlsbC1vcGFjaXR5PSIuMTUiIGQ9Ik01IDVoM3YxMEg1eiIvPjxwYXRoIGZpbGw9IiMxMzE3MjIiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTcgMXYzaDJ2MTJIN3YzSDZ2LTNINFY0aDJWMXpNNSA1aDN2MTBINXptNyAyaDN2NmgtM3ptLTEgN1Y2aDJWMy4yNWgxVjZoMnY4aC0ydjIuNzVoLTFWMTR6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=)}.ag-charts-icon-horizontal-line,.ag-charts-icon-horizontal-line-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzEzMTcyMiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNLjUgOS41aDcuMzA2YTIuMjUgMi4yNSAwIDAgMSA0LjM4OCAwSDE5LjV2MWgtNy4zMDZhMi4yNSAyLjI1IDAgMCAxLTQuMzg4IDBILjV6bTkuNSAxLjc1YTEuMjUgMS4yNSAwIDEgMCAwLTIuNSAxLjI1IDEuMjUgMCAwIDAgMCAyLjUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==)}.ag-charts-icon-line-color{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTQuMjQyIDIuNzIyYy0uNjEyIDAtMS4yLjI0My0xLjYzMi42NzVsLTEuMzQzIDEuMzQ0YS41LjUgMCAwIDAtLjExMi4xMTJMNC4wNSAxMS45NTljLS4yMDcuMjA3LS4zNi40Ni0uNDQ2Ljc0di4wMDFsLS42OSAyLjc2N3YuMDAyYS44Mi44MiAwIDAgMCAxLjAyMiAxLjAyMWguMDAybDIuNjM0LS44MjJjLjI4LS4wODUuNTM0LS4yMzcuNzQtLjQ0M2w3LjEwNy03LjEwOGEuNS41IDAgMCAwIC4xMTItLjExMmwxLjM0My0xLjM0M2EyLjMwOCAyLjMwOCAwIDAgMC0xLjYzMi0zLjk0TTE0LjEyMiA3bDEuMDQ0LTEuMDQ1YTEuMzA4IDEuMzA4IDAgMSAwLTEuODQ5LTEuODVMMTIuMjcxIDUuMTV6bS0yLjU1OC0xLjE0Mi02LjgwNyA2LjgwOWEuOC44IDAgMCAwLS4xOTYuMzI1bC0uNzUgMi40NjggMi40Ny0uNzQ5YS44LjggMCAwIDAgLjMyNS0uMTk0bDYuODA4LTYuODF6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=)}.ag-charts-icon-line-series{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzEzMTcyMiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJtMTcuMzYyIDQuODczLTQuNTk0IDYuNjU0LTQuODUtMy4zMTctNC4yNTEgNi45NzctLjg1NC0uNTJMNy42MTIgNi43OWw0Ljg5OSAzLjM1IDQuMDI4LTUuODM2eiIgY2xpcC1ydWxlPSJldmVub2RkIi8+PC9zdmc+)}.ag-charts-icon-line-style-dashed{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJNMiA5aDR2MUgyem0xMiAwaDR2MWgtNHpNOCA5aDR2MUg4eiIvPjwvc3ZnPg==)}.ag-charts-icon-line-style-dotted{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48Y2lyY2xlIGN4PSIyLjUiIGN5PSI5LjUiIHI9Ii41IiBmaWxsPSIjMDAwIi8+PGNpcmNsZSBjeD0iNC41IiBjeT0iOS41IiByPSIuNSIgZmlsbD0iIzAwMCIvPjxjaXJjbGUgY3g9IjYuNSIgY3k9IjkuNSIgcj0iLjUiIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSI4LjUiIGN5PSI5LjUiIHI9Ii41IiBmaWxsPSIjMDAwIi8+PGNpcmNsZSBjeD0iMTAuNSIgY3k9IjkuNSIgcj0iLjUiIGZpbGw9IiMwMDAiLz48Y2lyY2xlIGN4PSIxMi41IiBjeT0iOS41IiByPSIuNSIgZmlsbD0iIzAwMCIvPjxjaXJjbGUgY3g9IjE0LjUiIGN5PSI5LjUiIHI9Ii41IiBmaWxsPSIjMDAwIi8+PGNpcmNsZSBjeD0iMTYuNSIgY3k9IjkuNSIgcj0iLjUiIGZpbGw9IiMwMDAiLz48L3N2Zz4=)}.ag-charts-icon-line-style-solid{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJNMiA5aDE2djFIMnoiLz48L3N2Zz4=)}.ag-charts-icon-line-with-markers-series{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzEzMTcyMiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJtMTguMTk4IDQuODg4LTMuNTU2IDQuOTE4YTIuMjUgMi4yNSAwIDEgMS0zLjg2Ni43NWwtMS40MzItLjlhMi4yNCAyLjI0IDAgMCAxLTIuMDA5LjQzNWwtMy44MjggNi40MjgtLjg2LS41MTJMNi40NSA5LjYyM2EyLjI1IDIuMjUgMCAxIDEgMy41MS0uNzYxbDEuMzI5LjgzNWEyLjI0IDIuMjQgMCAwIDEgMi41NTctLjQ5N2wzLjU0Mi00Ljg5OHptLTQuOTYgNS4xNTNhMS4yNSAxLjI1IDAgMSAwLS42NCAyLjQxOSAxLjI1IDEuMjUgMCAwIDAgLjY0LTIuNDE5TTkuMSA4LjMyMXEuMDY2LS4xOTIuMDY3LS40MDRhMS4yNSAxLjI1IDAgMSAwLS4wNjcuNDA0IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=)}.ag-charts-icon-lock,.ag-charts-icon-locked{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTAuMjA3IDMuNzY0YTIuODk0IDIuODk0IDAgMCAwLTIuODk1IDIuODk0VjloNS43ODlWNi42NThhMi44OTQgMi44OTQgMCAwIDAtMi44OTUtMi44OTRNMTQuMSA5VjYuNjU4YTMuODk0IDMuODk0IDAgMSAwLTcuNzg5IDB2Mi4zNDlBMi41IDIuNSAwIDAgMCA0IDExLjV2M0EyLjUgMi41IDAgMCAwIDYuNSAxN2g4YTIuNSAyLjUgMCAwIDAgMi41LTIuNXYtM0EyLjUgMi41IDAgMCAwIDE0LjUgOXpNNi41IDEwQTEuNSAxLjUgMCAwIDAgNSAxMS41djNBMS41IDEuNSAwIDAgMCA2LjUgMTZoOGExLjUgMS41IDAgMCAwIDEuNS0xLjV2LTNhMS41IDEuNSAwIDAgMC0xLjUtMS41eiIgY2xpcC1ydWxlPSJldmVub2RkIi8+PC9zdmc+)}.ag-charts-icon-measurer-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0ibTQuNDYxIDEyLjcxIDEuNTMyLTEuNTMxIDEuNDE0IDEuNDE0LjcwNy0uNzA3TDYuNyAxMC40NzJsMS41MzItMS41MzMgMiAyIC43MDctLjcwNy0yLTIgNi4wMS02LjAxIDIuODMgMi44MjhMNS4wNSAxNy43NzggMi4yMjIgMTQuOTVsMS41MzItMS41MzIgMS40MTQgMS40MTQuNzA3LS43MDd6TS44MDggMTQuOTVsLjcwNy0uNzA3TDE0LjI0MyAxLjUxNWwuNzA3LS43MDcuNzA3LjcwNyAyLjgyOCAyLjgyOC43MDcuNzA3LS43MDcuNzA3TDUuNzU3IDE4LjQ4NWwtLjcwNy43MDctLjcwNy0uNzA3LTIuODI4LTIuODI4em0xMS4wNzgtNi44MzVMMTAuNDcgNi43bC43MDctLjcwNyAxLjQxNSAxLjQxNHptLjgyNC0zLjY1NCAxIDEgLjcwOC0uNzA3LTEtMXoiIGZpbGw9IiMxODFEMUYiLz48L3N2Zz4=)}.ag-charts-icon-note-annotation{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMyA0LjVBMS41IDEuNSAwIDAgMSA0LjUgM2gxMUExLjUgMS41IDAgMCAxIDE3IDQuNXY4YTEuNSAxLjUgMCAwIDEtMS41IDEuNWgtMy4yMWwtMS40NjkgMi41N2ExIDEgMCAwIDEtMS42ODIuMDg1TDcuMjQzIDE0SDQuNUExLjUgMS41IDAgMCAxIDMgMTIuNXpNNC41IDRhLjUuNSAwIDAgMC0uNS41djhhLjUuNSAwIDAgMCAuNS41aDMuMjU3bDIuMTk2IDMuMDc0TDExLjcxIDEzaDMuNzlhLjUuNSAwIDAgMCAuNS0uNXYtOGEuNS41IDAgMCAwLS41LS41eiIgY2xpcC1ydWxlPSJldmVub2RkIi8+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNNi41IDYuNUEuNS41IDAgMCAxIDcgNmg2YS41LjUgMCAwIDEgMCAxSDdhLjUuNSAwIDAgMS0uNS0uNU02LjUgOS41QS41LjUgMCAwIDEgNyA5aDZhLjUuNSAwIDAgMSAwIDFIN2EuNS41IDAgMCAxLS41LS41IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=)}.ag-charts-icon-ohlc-series{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzEzMTcyMiIgZD0iTTEzIDExaC0zdi0xaDNWM2gxdjJoNHYxaC00djExaC0xek02IDE3di0yaDN2LTFINlY0SDV2MUgydjFoM3YxMXoiLz48L3N2Zz4=)}.ag-charts-icon-pan-end{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZD0ibTYuNjQ2IDEzLjgxMy0uMzUzLjM1NC43MDcuNzA3LjM1NC0uMzU0ek0xMS4xNjYgMTBsLjM1NC4zNTQuMzU0LS4zNTQtLjM1NC0uMzU0ek03LjM1NSA1LjQ4IDcgNS4xMjZsLS43MDcuNzA3LjM1My4zNTR6bTAgOS4wNCA0LjE2Ni00LjE2Ni0uNzA3LS43MDgtNC4xNjcgNC4xNjd6bTQuMTY2LTQuODc0TDcuMzU0IDUuNDhsLS43MDguNzA3IDQuMTY3IDQuMTY3ek0xMy4wODMgNXYxMGgxVjV6Ii8+PC9zdmc+)}.ag-charts-icon-pan-left{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTIuNzkgNS44MzMgOC42MjUgMTBsNC4xNjYgNC4xNjctLjcwNy43MDdMNy4yMSAxMGw0Ljg3My00Ljg3NHoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==)}.ag-charts-icon-pan-right{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNNy4yMSAxNC4xNjcgMTEuMzc2IDEwIDcuMjEgNS44MzNsLjcwNy0uNzA3TDEyLjc5IDEwbC00Ljg3MyA0Ljg3NHoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==)}.ag-charts-icon-pan-start{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZD0iTTYgNXYxMGgxVjV6TTkuNjI0IDEwbDQuMTY2LTQuMTY3LS43MDctLjcwN0w4LjIxIDEwbDQuODc0IDQuODc0LjcwNy0uNzA3eiIvPjwvc3ZnPg==)}.ag-charts-icon-parallel-channel,.ag-charts-icon-parallel-channel-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzEzMTcyMiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTcuNzIgNS4zMzFBMi4yNSAyLjI1IDAgMSAwIDE0LjcwNSAzLjZsLTkuNDkgNC41NjJhMi4yNSAyLjI1IDAgMSAwIC4yMDkgMS4wMWw5LjY2Mi00LjY0NmEyLjI1IDIuMjUgMCAwIDAgMi42MzQuODA1bS4zNzMtMi41NDdhMS4yNSAxLjI1IDAgMSAxLTIuMzM4Ljg4NSAxLjI1IDEuMjUgMCAwIDEgMi4zMzgtLjg4NU00LjM0MyA4LjY3YTEuMjUgMS4yNSAwIDEgMS0yLjMzOC44ODUgMS4yNSAxLjI1IDAgMCAxIDIuMzM4LS44ODVNNS4zMDcgMTYuNzI4YTIuMjUgMi4yNSAwIDEgMS0uNTI1LS44NThsOS45MjMtNC43N2EyLjI1IDIuMjUgMCAxIDEgLjM4MS45MjZ6bS0uOTY0LjI3NGExLjI1IDEuMjUgMCAxIDEtMi4zMzguODg1IDEuMjUgMS4yNSAwIDAgMSAyLjMzOC0uODg1bTEzLjAyMy01LjEwNmExLjI1IDEuMjUgMCAxIDAtLjg4NS0yLjMzOSAxLjI1IDEuMjUgMCAwIDAgLjg4NSAyLjMzOSIgY2xpcC1ydWxlPSJldmVub2RkIi8+PC9zdmc+)}.ag-charts-icon-position-bottom{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjMDAwIiBmaWxsLW9wYWNpdHk9Ii4yNSIgZD0iTTMgMTBoMTR2MUgzem0zLTNoOHYxSDZ6Ii8+PHBhdGggZmlsbD0iIzAwMCIgZD0iTTYgMTNoOHYxSDZ6Ii8+PC9zdmc+)}.ag-charts-icon-position-center{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjMDAwIiBkPSJNMyAxMGgxNHYxSDN6Ii8+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1vcGFjaXR5PSIuMjUiIGQ9Ik02IDdoOHYxSDZ6bTAgNmg4djFINnoiLz48L3N2Zz4=)}.ag-charts-icon-position-top{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjMDAwIiBmaWxsLW9wYWNpdHk9Ii4yNSIgZD0iTTMgMTBoMTR2MUgzeiIvPjxwYXRoIGZpbGw9IiMwMDAiIGQ9Ik02IDdoOHYxSDZ6Ii8+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1vcGFjaXR5PSIuMjUiIGQ9Ik02IDEzaDh2MUg2eiIvPjwvc3ZnPg==)}.ag-charts-icon-price-label-annotation{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNNC41IDNBMS41IDEuNSAwIDAgMCAzIDQuNVYxM2ExLjUgMS41IDAgMCAwIDEuNSAxLjVoLjgzM3YuMDU3Yy4yNDItLjI5OS41OTctLjUwMyAxLS41NDhWMTMuNUg0LjVBLjUuNSAwIDAgMSA0IDEzVjQuNWEuNS41IDAgMCAxIC41LS41aDExYS41LjUgMCAwIDEgLjUuNXY4YS41LjUgMCAwIDEtLjUuNWgtNC44MThsLS4xMjYuMDg0YTI2IDI2IDAgMCAwLTIuMjI1IDEuNjg1bC0uMzIuMjY1LS4wNjguMDU2YTEuNSAxLjUgMCAwIDEtMi42MDkgMS4zNTRjLjAzMy43NjMuOTA1IDEuMjM4IDEuNTYuNzU2LjY0Ni0uNDc0IDEuMjEtLjk0MyAxLjc2MS0xLjRsLjMxMy0uMjZBMjYgMjYgMCAwIDEgMTAuOTg2IDE0SDE1LjVhMS41IDEuNSAwIDAgMCAxLjUtMS41di04QTEuNSAxLjUgMCAwIDAgMTUuNSAzeiIgY2xpcC1ydWxlPSJldmVub2RkIi8+PHBhdGggZmlsbD0iIzEzMTcyMiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNOC43MTYgMTQuODE1YTIuMjUgMi4yNSAwIDEgMS00LjIxIDEuNTkzIDIuMjUgMi4yNSAwIDAgMSA0LjIxLTEuNTkzbS0xLjY2MiAxLjk2NmExLjI1IDEuMjUgMCAxIDAtLjg4NS0yLjMzOSAxLjI1IDEuMjUgMCAwIDAgLjg4NSAyLjMzOSIgY2xpcC1ydWxlPSJldmVub2RkIi8+PC9zdmc+)}.ag-charts-icon-price-range-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNOS4wNDYgMTVWNS44NzdoLjk0MlYxNXoiIGZpbGw9IiMxODFEMUYiLz48cGF0aCBkPSJNOS4wNDYgMTVWNS44NzdoLjk0MlYxNXoiIGZpbGw9IiMxODFEMUYiLz48cGF0aCBkPSJNOS41IDYuMjI4IDcuMTY3IDguMzc2IDYuNSA3Ljc2MiA5LjUgNWwzIDIuNzYyLS42NjcuNjE0eiIgZmlsbD0iIzAwMCIvPjxwYXRoIGQ9Ik0yIDE4di0xaDE2djF6TTIgM1YyaDE2djF6IiBmaWxsPSIjMTgxRDFGIi8+PC9zdmc+)}.ag-charts-icon-reset{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTIuMDQgNC40NDVhNS44MSA1LjgxIDAgMCAwLTcuMjU3IDIuNDUzLjUuNSAwIDAgMS0uODY1LS41MDJBNi44MSA2LjgxIDAgMSAxIDMgOS44MTNhLjUuNSAwIDAgMSAxIDAgNS44MSA1LjgxIDAgMSAwIDguMDQtNS4zNjgiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiMwMDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTQuMjg5IDMuMDAyYS41LjUgMCAwIDEgLjUuNXYyLjY1NWgyLjY1NWEuNS41IDAgMCAxIDAgMUg0LjI5YS41LjUgMCAwIDEtLjUtLjVWMy41MDJhLjUuNSAwIDAgMSAuNS0uNSIgY2xpcC1ydWxlPSJldmVub2RkIi8+PC9zdmc+)}.ag-charts-icon-settings{--icon: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgc3R5bGU9ImZpbGwtcnVsZTpldmVub2RkIj48cGF0aCBkPSJNMTAgMTNhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2bTAtMWEyIDIgMCAxIDEtLjAwMS0zLjk5OUEyIDIgMCAwIDEgMTAgMTIiLz48cGF0aCBkPSJNMi4zMSAxNC4zNDVjLS44MTctMS40OTEuMDI3LTIuNDk5LjQ3NC0yLjg2NS41MzEtLjQzNC45NjktLjM2NS45NzItMS40OC0uMDAzLTEuMTE1LS40NDEtMS4wNDYtLjk3Mi0xLjQ4MS0uNDU0LS4zNzEtMS4zMTctMS40MDUtLjQzNC0yLjkzNmwuMDA1LS4wMDljLjg4NC0xLjUyIDIuMjA3LTEuMjkgMi43NTUtMS4wODMuNjQxLjI0My44MDEuNjU2IDEuNzY4LjEwMS45NjQtLjU2LjY4Ni0uOTA0Ljc5Ni0xLjU4Mi4wOTQtLjU3OC41NTktMS44NDMgMi4zMjYtMS44NDNoLjAxYzEuNzU5LjAwNSAyLjIyMiAxLjI2NiAyLjMxNiAxLjg0My4xMS42NzgtLjE2OCAxLjAyMi43OTYgMS41ODIuOTY3LjU1NSAxLjEyNy4xNDIgMS43NjgtLjEwMS41NDktLjIwOCAxLjg3Ni0uNDM4IDIuNzYgMS4wOTJzLjAyIDIuNTY1LS40MzQgMi45MzZjLS41MzEuNDM1LS45NjkuMzY2LS45NzIgMS40ODEuMDAzIDEuMTE1LjQ0MSAxLjA0Ni45NzIgMS40OC40NTQuMzcyIDEuMzE3IDEuNDA2LjQzNCAyLjkzN2wtLjAwNS4wMDljLS44ODQgMS41Mi0yLjIwNyAxLjI5LTIuNzU1IDEuMDgzLS42NDEtLjI0My0uODAxLS42NTYtMS43NjgtLjEwMS0uOTY0LjU2LS42ODYuOTA0LS43OTYgMS41ODEtLjA5NC41NzktLjU1OSAxLjg0NC0yLjMyNiAxLjg0NGgtLjAxYy0xLjc1OS0uMDA1LTIuMjIyLTEuMjY2LTIuMzE2LTEuODQ0LS4xMS0uNjc3LjE2OC0xLjAyMS0uNzk2LTEuNTgxLS45NjctLjU1NS0xLjEyNy0uMTQyLTEuNzY4LjEwMS0uNTQ5LjIwOC0xLjg3Ni40MzgtMi43Ni0xLjA5MmwtLjAyLS4wMzZ6TTkuOTg0IDIuMTYySDEwYzEuMzU1IDAgMS4zNDIgMS4wMzkgMS4zNTMgMS40MjUuMDA4LjMxMi4wNCAxLjE2IDEuMjU5IDEuODcybC4wMTUuMDA4YzEuMjI1LjcgMS45NzYuMzA0IDIuMjUxLjE1NS4zMzctLjE4MyAxLjIyNi0uNzExIDEuOTAyLjQ0NWwuMDA4LjAxNGMuNjc4IDEuMTczLS4yMjkgMS42ODItLjU1OCAxLjg4NC0uMjY2LjE2My0uOTg0LjYxNS0uOTkxIDIuMDI3di4wMTZjLjAwNyAxLjQxMi43MjUgMS44NjQuOTkxIDIuMDI3LjMyOC4yMDEgMS4yMjkuNzA3LjU2NiAxLjg3bC0uMDA4LjAxNGMtLjY3NyAxLjE3NC0xLjU3MS42NDMtMS45MS40NTktLjI3NS0uMTQ5LTEuMDI2LS41NDUtMi4yNTEuMTU0bC0uMDE1LjAwOWMtMS4yMTkuNzEyLTEuMjUxIDEuNTYtMS4yNTkgMS44NzItLjAxMS4zODYuMDAyIDEuNDI1LTEuMzUzIDEuNDI1cy0xLjM0Mi0xLjAzOS0xLjM1My0xLjQyNWMtLjAwOC0uMzEyLS4wNC0xLjE2LTEuMjU5LTEuODcybC0uMDE1LS4wMDljLTEuMjI1LS42OTktMS45NzYtLjMwMy0yLjI1MS0uMTU0LS4zMzYuMTgzLTEuMjE5LjcwNi0xLjg5NC0uNDMybC0uMDE2LS4wMjdjLS42NzgtMS4xNzQuMjI5LTEuNjgyLjU1OC0xLjg4NC4yNjYtLjE2My45ODQtLjYxNS45OTEtMi4wMjd2LS4wMTZjLS4wMDctMS40MTItLjcyNS0xLjg2NC0uOTkxLTIuMDI3LS4zMjgtLjIwMS0xLjIyOS0uNzA3LS41NjYtMS44N2wuMDA4LS4wMTRjLjY3Ny0xLjE3NCAxLjU3MS0uNjQzIDEuOTEtLjQ1OS4yNzUuMTQ5IDEuMDI2LjU0NSAyLjI1MS0uMTU1bC4wMTUtLjAwOGMxLjIxOS0uNzEyIDEuMjUxLTEuNTYgMS4yNTktMS44NzIuMDEtLjM4NC0uMDAyLTEuNDE3IDEuMzM3LTEuNDI1Ii8+PC9zdmc+)}.ag-charts-icon-step-line-series{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzE4MUQxRiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNNiA0aDV2OGgzVjhoNXYxaC00djRoLTVWNUg3djEwSDJ2LTFoNHoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==)}.ag-charts-icon-text-annotation{--icon: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdCb3g9IjAgMCAyMCAyMCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik00IDRIMTZWN0gxNVY1SDEwLjVWMTVIMTRWMTZINlYxNUg5LjVWNUg1VjdINFY0WiIgZmlsbD0iYmxhY2siLz4KPC9zdmc+Cg==)}.ag-charts-icon-trend-line,.ag-charts-icon-trend-line-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzEzMTcyMiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNNS4zMTQgMTAuOTM4YTIuMjUgMi4yNSAwIDEgMSAuMDEtMWg5LjM1MmEyLjI1IDIuMjUgMCAxIDEgLjAxIDF6bS0yLjE4OS43MjlhMS4yNSAxLjI1IDAgMSAwIDAtMi41IDEuMjUgMS4yNSAwIDAgMCAwIDIuNW0xMy43NSAwYTEuMjUgMS4yNSAwIDEgMCAwLTIuNSAxLjI1IDEuMjUgMCAwIDAgMCAyLjUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==)}.ag-charts-icon-unlock,.ag-charts-icon-unlocked{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTAuNjUxIDMuNWEyLjg5NCAyLjg5NCAwIDAgMC0yLjg5NCAyLjg5NFY5SDE0LjVhMi41IDIuNSAwIDAgMSAyLjUgMi41djNhMi41IDIuNSAwIDAgMS0yLjUgMi41aC04QTIuNSAyLjUgMCAwIDEgNCAxNC41di0zQTIuNSAyLjUgMCAwIDEgNi41IDloLjI1N1Y2LjM5NGEzLjg5NCAzLjg5NCAwIDEgMSA3Ljc4OSAwIC41LjUgMCAwIDEtMSAwQTIuODk0IDIuODk0IDAgMCAwIDEwLjY1IDMuNU02LjUgMTBBMS41IDEuNSAwIDAgMCA1IDExLjV2M0ExLjUgMS41IDAgMCAwIDYuNSAxNmg4YTEuNSAxLjUgMCAwIDAgMS41LTEuNXYtM2ExLjUgMS41IDAgMCAwLTEuNS0xLjV6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=)}.ag-charts-icon-vertical-line,.ag-charts-icon-vertical-line-drawing{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzEzMTcyMiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTAuNSA3LjgwNmEyLjI1IDIuMjUgMCAwIDEgMCA0LjM4OFYxOS41aC0xdi03LjMwNmEyLjI1IDIuMjUgMCAwIDEgMC00LjM4OFYuNWgxem0tLjUuOTQ0YTEuMjUgMS4yNSAwIDEgMSAwIDIuNSAxLjI1IDEuMjUgMCAwIDEgMC0yLjUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==)}.ag-charts-icon-zoom-in{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZD0iTTEwIDUuNWEuNS41IDAgMCAxIC41LjV2My41aDMuODc1YS41LjUgMCAwIDEgMCAxSDEwLjV2NC4yNWEuNS41IDAgMSAxLTEgMFYxMC41SDUuNjI1YS41LjUgMCAxIDEgMC0xSDkuNVY2YS41LjUgMCAwIDEgLjUtLjUiLz48L3N2Zz4=)}.ag-charts-icon-zoom-out{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNNS41IDEwYS41LjUgMCAwIDEgLjUtLjVoOGEuNS41IDAgMCAxIDAgMUg2YS41LjUgMCAwIDEtLjUtLjUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==)}.ag-charts-icon-high-low-series{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzEzMTcyMiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNNyA0aDJ2MTJINFY0aDNNNSA1aDN2MTBINXpNMTEgMTRWNmg1djhoLTVtMS03aDN2NmgtM3oiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvc3ZnPg==)}.ag-charts-icon-hlc-series{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgZmlsbD0ibm9uZSI+PHBhdGggZmlsbD0iIzEzMTcyMiIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJtMTguMTYzIDEuODM3LTUuMzM0IDExLjYyMUw2Ljk1NyA4LjEybC00LjE5OSA5LjYyMi0uOTE2LS40IDQuNzU2LTEwLjlMMTIuNDkgMTEuOCAxNy4yNTQgMS40MnoiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iLjQiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTUuODI1IDIuNzA0LjU1IDEzLjc4NWwuOTAyLjQzIDQuNzI0LTkuOTE5IDYuMDM0IDUuMDI5IDMuMjU1LTguMTQtLjkyOC0uMzctMi43NDUgNi44NnptNy44NTIgMTQuNjM2IDUuNzgtMTMuMTM5LS45MTUtLjQwMi01LjIxOSAxMS44Ni02LjAwNS01LjUwNC0zLjI3OCA3LjY0OC45Mi4zOTQgMi43MjItNi4zNTJ6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiLz48L3N2Zz4=)}.ag-charts-icon-chevron-right{--icon: url(data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjAgMjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik03LjQ3IDUuNDdhLjc1Ljc1IDAgMCAxIDEuMDYgMGw0IDRhLjc1Ljc1IDAgMCAxIDAgMS4wNmwtNCA0YS43NS43NSAwIDAgMS0xLjA2LTEuMDZMMTAuOTQgMTAgNy40NyA2LjUzYS43NS43NSAwIDAgMSAwLTEuMDYiIGZpbGw9IiMwMDAiLz48L3N2Zz4=)}.ag-charts-icon-zoom-in-alt{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXpvb20taW4iPjxjaXJjbGUgY3g9IjExIiBjeT0iMTEiIHI9IjgiLz48bGluZSB4MT0iMjEiIHgyPSIxNi42NSIgeTE9IjIxIiB5Mj0iMTYuNjUiLz48bGluZSB4MT0iMTEiIHgyPSIxMSIgeTE9IjgiIHkyPSIxNCIvPjxsaW5lIHgxPSI4IiB4Mj0iMTQiIHkxPSIxMSIgeTI9IjExIi8+PC9zdmc+)}.ag-charts-icon-zoom-out-alt{--icon: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXpvb20tb3V0Ij48Y2lyY2xlIGN4PSIxMSIgY3k9IjExIiByPSI4Ii8+PGxpbmUgeDE9IjIxIiB4Mj0iMTYuNjUiIHkxPSIyMSIgeTI9IjE2LjY1Ii8+PGxpbmUgeDE9IjgiIHgyPSIxNCIgeTE9IjExIiB5Mj0iMTEiLz48L3N2Zz4=)}.ag-charts-input{--input-layer-active: 1;--input-layer-focus: 2;--input-padding: calc(var(--ag-charts-spacing) * 2);--input-padding-large: calc(var(--ag-charts-spacing) * 2.5);color:var(--ag-charts-input-text-color);font-family:var(--ag-charts-chrome-font-family);font-size:var(--ag-charts-chrome-font-size-large);transition-duration:.25s;transition-property:none;transition-timing-function:ease-out}.ag-charts-input:focus-visible{outline:var(--ag-charts-focus-border);box-shadow:var(--ag-charts-focus-border-shadow);z-index:var(--input-layer-focus)}.ag-charts-button{background:var(--ag-charts-button-background-color);border:var(--ag-charts-button-border);border-radius:var(--ag-charts-button-border-radius);color:var(--ag-charts-button-text-color);cursor:pointer;padding:var(--input-padding);transition-property:background,border-color}.ag-charts-button:hover{background:var(--ag-charts-focus-color)}.ag-charts-button:has(.ag-charts-icon){padding:2px}.ag-charts-checkbox{--checkbox-transition-duration: .1s;appearance:none;background:var(--ag-charts-checkbox-background-color);border-radius:calc(var(--ag-charts-border-radius) * 9);cursor:pointer;height:18px;margin:0;transition-duration:var(--checkbox-transition-duration);transition-property:margin;width:29px}.ag-charts-checkbox:before{display:block;background:var(--ag-charts-input-background-color);border-radius:calc(var(--ag-charts-border-radius) * 7);content:" ";height:14px;margin:2px;transition-duration:var(--checkbox-transition-duration);transition-property:margin;transition-timing-function:var(--ag-charts-input-transition-easing);width:14px}.ag-charts-checkbox:checked{background:var(--ag-charts-checkbox-checked-background-color)}.ag-charts-checkbox:checked:before{margin-left:13px}.ag-charts-select{background:var(--ag-charts-input-background-color);border:var(--ag-charts-input-border);border-radius:var(--ag-charts-input-border-radius);padding:3px 2px 4px;font-size:inherit}.ag-charts-textarea{--textarea-line-height: 1.38;background:var(--ag-charts-input-background-color);border:var(--ag-charts-input-border);border-radius:var(--ag-charts-input-border-radius);line-height:var(--textarea-line-height);font-family:var(--ag-charts-chrome-font-family);font-size:var(--ag-charts-chrome-font-size-large);padding:var(--input-padding-large) var(--input-padding)}.ag-charts-textarea::placeholder{color:var(--ag-charts-input-placeholder-text-color)}.ag-charts-proxy-container{pointer-events:none;position:absolute}.ag-charts-proxy-legend-toolbar{pointer-events:auto}.ag-charts-proxy-legend-toolbar>div[role=listitem]{pointer-events:none}.ag-charts-proxy-elem{-webkit-appearance:none;appearance:none;background:none;border:none;color:#0000;overflow:hidden;pointer-events:auto;position:absolute}.ag-charts-proxy-elem::-moz-range-thumb,.ag-charts-proxy-elem::-moz-range-track{opacity:0}.ag-charts-proxy-elem::-webkit-slider-runnable-track,.ag-charts-proxy-elem::-webkit-slider-thumb{opacity:0}.ag-charts-proxy-elem:focus-visible{outline:var(--ag-charts-focus-border);box-shadow:var(--ag-charts-focus-border-shadow)}.ag-charts-proxy-elem svg{display:block}.ag-charts-proxy-scrollbar-slider:focus-visible{outline:none;box-shadow:none}.ag-charts-proxy-scrollbar-thumb-focus{border:var(--ag-charts-focus-border);box-shadow:var(--ag-charts-focus-border-shadow);box-sizing:border-box;opacity:0;pointer-events:none}.ag-charts-proxy-scrollbar-slider:focus-visible~.ag-charts-proxy-scrollbar-thumb-focus{opacity:1}.ag-charts-focus-indicator{position:absolute;display:block;pointer-events:none;user-select:none;-webkit-user-select:none;width:100%;height:100%}.ag-charts-focus-indicator>div{position:absolute;outline:solid 1px var(--ag-charts-chrome-background-color);box-shadow:var(--ag-charts-focus-shadow)}.ag-charts-focus-indicator>svg{width:100%;height:100%;fill:none;overflow:visible}.ag-charts-focus-svg-outer-path{stroke:var(--ag-charts-chrome-background-color);stroke-width:4px}.ag-charts-focus-svg-inner-path{stroke:var(--ag-charts-accent-color);stroke-width:2px}.ag-charts-overlay{color:#181d1f;pointer-events:none}.ag-charts-overlay.ag-charts-dark-overlay{color:#fff}.ag-charts-overlay--loading{color:#8c8c8c}.ag-charts-overlay__loading-background{background:#fff;pointer-events:none}.ag-charts-overlay.ag-charts-dark-overlay .ag-charts-overlay__loading-background{background:#192232}.ag-charts-tooltip{--tooltip-arrow-size: 8px;--tooltip-row-spacing: 8px;--tooltip-column-spacing: 16px;position:fixed;inset:unset;margin:0;padding:0;overflow:visible;top:var(--top, 0px);left:var(--left, 0px);width:max-content;max-width:100%;font-family:var(--ag-charts-chrome-font-family);font-size:var(--ag-charts-chrome-font-size);font-weight:var(--ag-charts-chrome-font-weight);color:var(--ag-charts-tooltip-text-color);background:var(--ag-charts-tooltip-background-color);border:var(--ag-charts-tooltip-border);border-radius:var(--ag-charts-tooltip-border-radius);box-shadow:var(--ag-charts-popup-shadow)}.ag-charts-tooltip--compact .ag-charts-tooltip-content{--tooltip-row-spacing: 2px;--tooltip-column-spacing: 8px;padding:3px 6px}.ag-charts-tooltip--arrow-top:before,.ag-charts-tooltip--arrow-right:before,.ag-charts-tooltip--arrow-bottom:before,.ag-charts-tooltip--arrow-left:before{content:"";position:absolute;display:block;width:var(--tooltip-arrow-size);height:var(--tooltip-arrow-size);border:inherit;border-bottom-color:transparent;border-right-color:transparent;background:inherit;clip-path:polygon(0 0,100% 0,100% 1px,1px 100%,0 100%)}.ag-charts-tooltip--arrow-top:before{bottom:100%;left:50%;transform:translate(-50%) translateY(calc(var(--tooltip-arrow-size) * .5)) rotate(45deg)}.ag-charts-tooltip--arrow-bottom:before{top:100%;left:50%;transform:translate(-50%) translateY(calc(var(--tooltip-arrow-size) * -.5)) rotate(225deg)}.ag-charts-tooltip--arrow-left:before{right:100%;top:50%;transform:translateY(-50%) translate(calc(var(--tooltip-arrow-size) * .5)) rotate(315deg)}.ag-charts-tooltip--arrow-right:before{left:100%;top:50%;transform:translateY(-50%) translate(calc(var(--tooltip-arrow-size) * -.5)) rotate(135deg)}.ag-charts-tooltip--no-interaction{pointer-events:none;user-select:none;-webkit-user-select:none}.ag-charts-tooltip--wrap-always{overflow-wrap:break-word;word-break:break-word;hyphens:none}.ag-charts-tooltip--wrap-hyphenate{overflow-wrap:break-word;word-break:break-word;hyphens:auto}.ag-charts-tooltip--wrap-on-space{overflow-wrap:normal;word-break:normal}.ag-charts-tooltip--wrap-never{white-space:nowrap}.ag-charts-tooltip-heading,.ag-charts-tooltip-title,.ag-charts-tooltip-label,.ag-charts-tooltip-value{overflow:hidden;text-overflow:ellipsis}.ag-charts-tooltip-content{display:grid;grid:auto-flow minmax(1em,auto) / 1fr;padding:8px 12px;gap:var(--tooltip-row-spacing)}.ag-charts-tooltip-content:has(.ag-charts-tooltip-symbol){grid:auto-flow minmax(1em,auto) / auto 1fr}.ag-charts-tooltip-heading{grid-column:1 / -1}.ag-charts-tooltip-symbol{grid-column:1 / 2;place-self:center}.ag-charts-tooltip-symbol svg{display:block}.ag-charts-tooltip-title{grid-column:-2 / -1}.ag-charts-tooltip-row{grid-column:1 / -1;display:flex;gap:var(--tooltip-column-spacing);align-items:baseline;justify-content:space-between;overflow:hidden}.ag-charts-tooltip-row--inline{grid-column:-2 / -1}.ag-charts-tooltip-label{flex:1;min-width:0}.ag-charts-tooltip-value{min-width:0}.ag-charts-tooltip-footer{grid-column:1 / -1;color:var(--ag-charts-tooltip-subtle-text-color);text-align:center}.ag-charts-popover{position:absolute;border:var(--ag-charts-border);border-radius:var(--ag-charts-border-radius);background:var(--ag-charts-panel-background-color);color:var(--ag-charts-chrome-text-color);font-family:var(--ag-charts-chrome-font-family);font-size:var(--ag-charts-chrome-font-size);font-weight:var(--ag-charts-chrome-font-weight);box-shadow:var(--ag-charts-popup-shadow);z-index:var(--ag-charts-layer-ui-overlay)}.ag-charts-menu{--item-padding: 6px 12px;--icon-color: var(--ag-charts-menu-text-color);display:grid;grid:auto-flow auto / 1fr;column-gap:12px;font-size:var(--ag-charts-chrome-font-size)}.ag-charts-menu:has(.ag-charts-menu__icon,.ag-charts-menu__row--stroke-width-visible){grid:auto-flow auto / auto 1fr}.ag-charts-menu__row--stroke-width-visible:before{content:"";height:var(--strokeWidth);width:12px;background:var(--icon-color)}.ag-charts-menu__row--stroke-width-visible[aria-disabled=true]:before{filter:grayscale(1);opacity:.5}.ag-charts-menu__row{display:grid;grid-column:1 / -1;grid-template-columns:subgrid;align-items:center;padding:var(--item-padding)}.ag-charts-menu__row:not(.ag-charts-menu__row--active){cursor:pointer}.ag-charts-menu__row:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.ag-charts-menu__row:last-child{border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.ag-charts-menu__row:focus{background:var(--ag-charts-focus-color)}.ag-charts-menu__row:focus-visible{outline:var(--ag-charts-focus-border);box-shadow:var(--ag-charts-focus-border-shadow);z-index:var(--ag-charts-layer-menu)}.ag-charts-menu__row--active{--icon-color: var(--ag-charts-accent-color);background:var(--ag-charts-focus-color);color:var(--ag-charts-accent-color)}.ag-charts-menu__label{grid-column:-1 / span 1}.ag-charts-toolbar{--toolbar-gap: calc(var(--ag-charts-spacing) * 2);--toolbar-size: 34px;--toolbar-button-padding: 6px;align-items:center;display:flex;flex-wrap:nowrap;position:absolute}.ag-charts-toolbar__button{align-items:center;background:var(--ag-charts-chrome-background-color);border:var(--ag-charts-button-border);color:var(--ag-charts-button-text-color);cursor:pointer;display:flex;font-family:var(--ag-charts-chrome-font-family);font-size:var(--ag-charts-chrome-font-size-medium);font-weight:var(--ag-charts-chrome-font-weight);justify-content:center;min-height:var(--toolbar-size);min-width:var(--toolbar-size);padding:var(--toolbar-button-padding);position:relative;transition:background-color .25s ease-in-out,border-color .25s ease-in-out,color .25s ease-in-out;white-space:nowrap}.ag-charts-toolbar__button:hover{background:var(--ag-charts-focus-color);z-index:1}.ag-charts-toolbar__button:focus-visible{outline:var(--ag-charts-focus-border);box-shadow:var(--ag-charts-focus-border-shadow);z-index:calc(var(--ag-charts-layer-ui-overlay) + 1)}.ag-charts-toolbar__button--active{background:var(--ag-charts-focus-color);border-color:var(--ag-charts-accent-color);color:var(--ag-charts-accent-color);z-index:2;+.ag-charts-toolbar__button{border-left-color:var(--ag-charts-accent-color)}}.ag-charts-toolbar__button[aria-disabled=true]{background:var(--ag-charts-button-disabled-background-color);color:var(--ag-charts-button-disabled-text-color);cursor:default}.ag-charts-toolbar--horizontal{flex-direction:row;.ag-charts-toolbar__button{border-right-width:0}.ag-charts-toolbar__button--first{border-bottom-left-radius:var(--ag-charts-border-radius);border-top-left-radius:var(--ag-charts-border-radius);margin:0}.ag-charts-toolbar__button--last{border-bottom-right-radius:var(--ag-charts-border-radius);border-top-right-radius:var(--ag-charts-border-radius);border-right-width:1px}}.ag-charts-toolbar--vertical{flex-direction:column;.ag-charts-toolbar__button{margin:-1px 0 0;max-width:100%}.ag-charts-toolbar__button--first{border-top-left-radius:var(--ag-charts-border-radius);border-top-right-radius:var(--ag-charts-border-radius);margin:0}.ag-charts-toolbar__button--last{border-bottom-left-radius:var(--ag-charts-border-radius);border-bottom-right-radius:var(--ag-charts-border-radius)}}.ag-charts-toolbar__icon+.ag-charts-toolbar__label{margin-left:var(--toolbar-gap)}.ag-charts-toolbar__icon,.ag-charts-toolbar__label{pointer-events:none}.ag-charts-floating-toolbar{border:none;display:flex;.ag-charts-toolbar{align-items:unset;position:unset}}.ag-charts-floating-toolbar__drag-handle{align-items:center;background:var(--ag-charts-chrome-background-color);border:var(--ag-charts-border);border-bottom-left-radius:var(--ag-charts-border-radius);border-top-left-radius:var(--ag-charts-border-radius);border-right-width:0;cursor:grab;display:flex;justify-content:center;min-width:24px;padding-left:0;padding-right:0}.ag-charts-floating-toolbar__drag-handle--dragging{cursor:grabbing}\n';
|
|
|
|
// packages/ag-charts-community/src/util/baseManager.ts
|
|
var BaseManager = class {
|
|
constructor() {
|
|
this.cleanup = new CleanupRegistry();
|
|
this.destroyed = false;
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
this.destroyed = true;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/guardedElement.ts
|
|
var GuardedElement = class _GuardedElement {
|
|
constructor(element2, topTabGuard, bottomTabGuard) {
|
|
this.element = element2;
|
|
this.topTabGuard = topTabGuard;
|
|
this.bottomTabGuard = bottomTabGuard;
|
|
this.cleanup = new CleanupRegistry();
|
|
this.guardTabIndex = 0;
|
|
this.hasFocus = false;
|
|
this.initTabGuard(this.topTabGuard, false);
|
|
this.initTabGuard(this.bottomTabGuard, true);
|
|
this.element.addEventListener("focus", () => this.onFocus(), { capture: true });
|
|
this.element.addEventListener("blur", (ev) => this.onBlur(ev), { capture: true });
|
|
}
|
|
set tabIndex(index) {
|
|
this.guardTabIndex = index;
|
|
if (this.guardTabIndex === 0) {
|
|
this.setGuardIndices(void 0);
|
|
} else if (!this.hasFocus) {
|
|
this.setGuardIndices(this.guardTabIndex);
|
|
}
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
}
|
|
initTabGuard(guard, reverse) {
|
|
this.cleanup.register(attachListener(guard, "focus", () => this.onTab(guard, reverse)));
|
|
}
|
|
setGuardIndices(index) {
|
|
const tabindex = index;
|
|
setAttribute(this.topTabGuard, "tabindex", tabindex);
|
|
setAttribute(this.bottomTabGuard, "tabindex", tabindex);
|
|
}
|
|
onFocus() {
|
|
this.hasFocus = true;
|
|
if (this.guardTabIndex !== 0) {
|
|
this.setGuardIndices(0);
|
|
}
|
|
}
|
|
onBlur({ relatedTarget }) {
|
|
const { topTabGuard: top, bottomTabGuard: bot } = this;
|
|
this.hasFocus = false;
|
|
if (this.guardTabIndex !== 0 && relatedTarget !== top && relatedTarget !== bot) {
|
|
this.setGuardIndices(this.guardTabIndex);
|
|
}
|
|
}
|
|
onTab(guard, reverse) {
|
|
if (this.guardTabIndex !== 0) {
|
|
let focusTarget;
|
|
if (guard.tabIndex === 0) {
|
|
focusTarget = this.findExitTarget(!reverse);
|
|
this.setGuardIndices(this.guardTabIndex);
|
|
} else {
|
|
focusTarget = this.findEnterTarget(reverse);
|
|
}
|
|
focusTarget?.focus();
|
|
}
|
|
}
|
|
static queryFocusable(element2, selectors) {
|
|
const myWindow = getWindow();
|
|
return Array.from(element2.querySelectorAll(selectors)).filter((e) => {
|
|
if (isHTMLElement(e)) {
|
|
const style2 = myWindow.getComputedStyle(e);
|
|
return style2.display !== "none" && style2.visibility !== "none";
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
findEnterTarget(reverse) {
|
|
const focusables = _GuardedElement.queryFocusable(this.element, '[tabindex="0"]');
|
|
const index = reverse ? focusables.length - 1 : 0;
|
|
return focusables[index];
|
|
}
|
|
findExitTarget(reverse) {
|
|
const focusables = _GuardedElement.queryFocusable(getDocument(), "[tabindex]").filter((e) => e.tabIndex > 0).sort((a, b) => a.tabIndex - b.tabIndex);
|
|
const { before, after } = _GuardedElement.findBeforeAndAfter(focusables, this.guardTabIndex);
|
|
return reverse ? before : after;
|
|
}
|
|
static findBeforeAndAfter(elements, targetTabIndex) {
|
|
let left = 0;
|
|
let right = elements.length - 1;
|
|
let before = void 0;
|
|
let after = void 0;
|
|
while (left <= right) {
|
|
const mid = Math.floor((left + right) / 2);
|
|
const currentTabIndex = elements[mid].tabIndex;
|
|
if (currentTabIndex === targetTabIndex) {
|
|
before = elements[mid - 1] || void 0;
|
|
after = elements[mid + 1] || void 0;
|
|
break;
|
|
} else if (currentTabIndex < targetTabIndex) {
|
|
before = elements[mid];
|
|
left = mid + 1;
|
|
} else {
|
|
after = elements[mid];
|
|
right = mid - 1;
|
|
}
|
|
}
|
|
return { before, after };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/pixelRatioObserver.ts
|
|
var PixelRatioObserver = class {
|
|
constructor(callback2) {
|
|
this.callback = callback2;
|
|
this.devicePixelRatio = getWindow("devicePixelRatio") ?? 1;
|
|
this.devicePixelRatioMediaQuery = void 0;
|
|
this.devicePixelRatioListener = (e) => {
|
|
if (e.matches)
|
|
return;
|
|
this.devicePixelRatio = getWindow("devicePixelRatio") ?? 1;
|
|
this.unregisterDevicePixelRatioListener();
|
|
this.registerDevicePixelRatioListener();
|
|
this.callback(this.pixelRatio);
|
|
};
|
|
}
|
|
get pixelRatio() {
|
|
return this.devicePixelRatio;
|
|
}
|
|
observe() {
|
|
this.registerDevicePixelRatioListener();
|
|
}
|
|
disconnect() {
|
|
this.unregisterDevicePixelRatioListener();
|
|
}
|
|
unregisterDevicePixelRatioListener() {
|
|
this.devicePixelRatioMediaQuery?.removeEventListener("change", this.devicePixelRatioListener);
|
|
this.devicePixelRatioMediaQuery = void 0;
|
|
}
|
|
registerDevicePixelRatioListener() {
|
|
const devicePixelRatioMediaQuery = getWindow("matchMedia")?.(`(resolution: ${this.pixelRatio}dppx)`);
|
|
devicePixelRatioMediaQuery?.addEventListener("change", this.devicePixelRatioListener);
|
|
this.devicePixelRatioMediaQuery = devicePixelRatioMediaQuery;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/sizeMonitor.ts
|
|
var SizeMonitor = class {
|
|
constructor() {
|
|
this.elements = /* @__PURE__ */ new Map();
|
|
this.documentReady = false;
|
|
this.queuedObserveRequests = [];
|
|
this.onLoad = () => {
|
|
this.documentReady = true;
|
|
for (const [el, cb] of this.queuedObserveRequests) {
|
|
this.observe(el, cb);
|
|
}
|
|
this.queuedObserveRequests = [];
|
|
this.observeWindow();
|
|
};
|
|
const ResizeObserverCtor = getResizeObserver();
|
|
if (ResizeObserverCtor !== void 0) {
|
|
this.resizeObserver = new ResizeObserverCtor((entries2) => {
|
|
for (const {
|
|
target,
|
|
contentRect: { width: width2, height: height2 }
|
|
} of entries2) {
|
|
const entry = this.elements.get(target);
|
|
this.checkSize(entry, target, width2, height2);
|
|
}
|
|
});
|
|
}
|
|
let animationFrame;
|
|
this.pixelRatioObserver = new PixelRatioObserver(() => {
|
|
clearTimeout(animationFrame);
|
|
animationFrame = setTimeout(() => this.checkPixelRatio(), 0);
|
|
});
|
|
this.documentReady = getDocument("readyState") === "complete";
|
|
if (this.documentReady) {
|
|
this.observeWindow();
|
|
} else {
|
|
getWindow()?.addEventListener("load", this.onLoad);
|
|
}
|
|
}
|
|
destroy() {
|
|
getWindow()?.removeEventListener("load", this.onLoad);
|
|
this.resizeObserver?.disconnect();
|
|
this.resizeObserver = void 0;
|
|
this.pixelRatioObserver?.disconnect();
|
|
this.pixelRatioObserver = void 0;
|
|
}
|
|
observeWindow() {
|
|
this.pixelRatioObserver?.observe();
|
|
}
|
|
checkPixelRatio() {
|
|
const pixelRatio = this.pixelRatioObserver?.pixelRatio ?? 1;
|
|
for (const [element2, entry] of this.elements) {
|
|
if (entry.size != null && entry.size.pixelRatio !== pixelRatio) {
|
|
const { width: width2, height: height2 } = entry.size;
|
|
entry.size = { width: width2, height: height2, pixelRatio };
|
|
entry.cb(entry.size, element2);
|
|
}
|
|
}
|
|
}
|
|
checkSize(entry, element2, width2, height2) {
|
|
if (!entry)
|
|
return;
|
|
if (width2 !== entry.size?.width || height2 !== entry.size?.height) {
|
|
const pixelRatio = this.pixelRatioObserver?.pixelRatio ?? 1;
|
|
entry.size = { width: width2, height: height2, pixelRatio };
|
|
entry.cb(entry.size, element2);
|
|
}
|
|
}
|
|
// Only a single callback is supported.
|
|
observe(element2, cb) {
|
|
if (!this.documentReady) {
|
|
this.queuedObserveRequests.push([element2, cb]);
|
|
return;
|
|
}
|
|
if (this.elements.has(element2)) {
|
|
this.removeFromQueue(element2);
|
|
} else {
|
|
this.resizeObserver?.observe(element2);
|
|
}
|
|
const entry = { cb };
|
|
this.elements.set(element2, entry);
|
|
}
|
|
unobserve(element2) {
|
|
this.resizeObserver?.unobserve(element2);
|
|
this.elements.delete(element2);
|
|
this.removeFromQueue(element2);
|
|
if (!this.elements.size) {
|
|
this.destroy();
|
|
}
|
|
}
|
|
removeFromQueue(element2) {
|
|
this.queuedObserveRequests = this.queuedObserveRequests.filter(([el]) => el !== element2);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/stateTracker.ts
|
|
var StateTracker = class extends Map {
|
|
constructor(defaultValue, defaultState) {
|
|
super();
|
|
this.defaultValue = defaultValue;
|
|
this.defaultState = defaultState;
|
|
}
|
|
set(key, value) {
|
|
this.delete(key);
|
|
if (value !== void 0) {
|
|
super.set(key, value);
|
|
}
|
|
delete this.cachedState;
|
|
delete this.cachedValue;
|
|
return this;
|
|
}
|
|
delete(key) {
|
|
delete this.cachedState;
|
|
delete this.cachedValue;
|
|
return super.delete(key);
|
|
}
|
|
stateId() {
|
|
this.cachedState ?? (this.cachedState = Array.from(this.keys()).pop() ?? this.defaultState);
|
|
return this.cachedState;
|
|
}
|
|
stateValue() {
|
|
this.cachedValue ?? (this.cachedValue = Array.from(this.values()).pop() ?? this.defaultValue);
|
|
return this.cachedValue;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/dom/domLayout.html
|
|
var domLayout_default = '<div role="presentation" class="ag-charts-wrapper ag-charts-styles" data-ag-charts><div role="presentation" class="ag-charts-canvas-center"><div role="presentation" class="ag-charts-canvas-container"><div role="presentation" class="ag-charts-canvas-background" aria-hidden="true"></div><div role="presentation" class="ag-charts-canvas" aria-hidden="true"></div><div role="figure" class="ag-charts-canvas-proxy"><div role="presentation" class="ag-charts-series-area"></div></div><div role="presentation" class="ag-charts-canvas-overlay ag-charts-tooltip-container"></div></div></div></div>';
|
|
|
|
// packages/ag-charts-community/src/dom/domManager.ts
|
|
var DOM_ELEMENT_CLASSES = [
|
|
"styles",
|
|
"canvas",
|
|
"canvas-background",
|
|
"canvas-center",
|
|
"canvas-container",
|
|
"canvas-overlay",
|
|
"canvas-proxy",
|
|
"series-area",
|
|
"tooltip-container"
|
|
];
|
|
var MINIMAL_DOM_ELEMENT_ROLES = /* @__PURE__ */ new Set(["styles", "canvas-container", "canvas", "tooltip-container"]);
|
|
var CONTAINER_MODIFIERS = {
|
|
safeHorizontal: "ag-charts-wrapper--safe-horizontal",
|
|
safeVertical: "ag-charts-wrapper--safe-vertical"
|
|
};
|
|
var domElementConfig = /* @__PURE__ */ new Map([
|
|
["styles", { childElementType: "style" }],
|
|
["canvas", { childElementType: "canvas" }],
|
|
["canvas-proxy", { childElementType: "div" }],
|
|
["canvas-overlay", { childElementType: "div" }],
|
|
["canvas-center", { childElementType: "div" }],
|
|
["series-area", { childElementType: "div" }],
|
|
["tooltip-container", { childElementType: "div" }]
|
|
]);
|
|
function setupObserver(element2, cb) {
|
|
if (typeof IntersectionObserver === "undefined")
|
|
return;
|
|
const observer = new IntersectionObserver(
|
|
(observedEntries) => {
|
|
for (const entry of observedEntries) {
|
|
if (entry.target === element2) {
|
|
cb(entry.intersectionRatio);
|
|
}
|
|
}
|
|
},
|
|
{ root: element2 }
|
|
);
|
|
observer.observe(element2);
|
|
return observer;
|
|
}
|
|
var NULL_DOMRECT = {
|
|
x: 0,
|
|
y: 0,
|
|
width: 0,
|
|
height: 0,
|
|
top: 0,
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
toJSON() {
|
|
return NULL_DOMRECT;
|
|
}
|
|
};
|
|
function createTabGuardElement(guardedElem, where) {
|
|
const div = createElement("div");
|
|
div.className = "ag-charts-tab-guard";
|
|
guardedElem.insertAdjacentElement(where, div);
|
|
return div;
|
|
}
|
|
var _DOMManager = class _DOMManager extends BaseManager {
|
|
constructor(eventsHub, chart, initialContainer, styleContainer, skipCss, mode = "normal") {
|
|
super();
|
|
this.eventsHub = eventsHub;
|
|
this.chart = chart;
|
|
this.styleContainer = styleContainer;
|
|
this.skipCss = skipCss;
|
|
this.mode = mode;
|
|
this.anchorName = `--${createId(this)}`;
|
|
this.styles = /* @__PURE__ */ new Map();
|
|
this.pendingContainer = void 0;
|
|
this.container = void 0;
|
|
this.documentRoot = void 0;
|
|
this.initiallyConnected = void 0;
|
|
this.containerSize = void 0;
|
|
this.sizeMonitor = new SizeMonitor();
|
|
this.cursorState = new StateTracker("default");
|
|
this.minWidth = 0;
|
|
this.minHeight = 0;
|
|
this.element = this.initDOM();
|
|
this.rootElements = this.initRootElements();
|
|
this.rootElements["canvas"].element.style.setProperty("anchor-name", this.anchorName);
|
|
let hidden = false;
|
|
this.observer = setupObserver(this.element, (intersectionRatio) => {
|
|
if (intersectionRatio === 0 && !hidden) {
|
|
this.eventsHub.emit("dom:hidden", null);
|
|
}
|
|
hidden = intersectionRatio === 0;
|
|
});
|
|
this.setSizeOptions();
|
|
this.updateContainerSize();
|
|
this.addStyles("ag-charts-community", styles_default);
|
|
this.setContainer(initialContainer);
|
|
this.cleanup.register(stopPageScrolling(this.element));
|
|
if (this.mode === "normal") {
|
|
const guardedElement = this.rootElements["canvas-center"].element;
|
|
if (guardedElement == null)
|
|
throw new Error("Error initializing tab guards");
|
|
const topGuard = createTabGuardElement(guardedElement, "beforebegin");
|
|
const botGuard = createTabGuardElement(guardedElement, "afterend");
|
|
this.tabGuards = new GuardedElement(guardedElement, topGuard, botGuard);
|
|
}
|
|
}
|
|
initDOM() {
|
|
if (this.mode === "normal") {
|
|
const templateEl = createElement("div");
|
|
templateEl.innerHTML = domLayout_default;
|
|
return templateEl.firstChild;
|
|
}
|
|
const element2 = createElement("div");
|
|
element2.role = "presentation";
|
|
element2.dataset.agCharts = "";
|
|
element2.classList.add("ag-charts-wrapper");
|
|
const seriesArea = createElement("div");
|
|
element2.appendChild(seriesArea);
|
|
seriesArea.role = "presentation";
|
|
seriesArea.classList.add("ag-charts-series-area");
|
|
return element2;
|
|
}
|
|
initRootElements() {
|
|
const { mode, element: element2 } = this;
|
|
const rootElements = {};
|
|
for (const domElement of DOM_ELEMENT_CLASSES) {
|
|
const className = `ag-charts-${domElement}`;
|
|
let el;
|
|
if (mode === "normal") {
|
|
el = element2.classList.contains(className) ? element2 : element2.getElementsByClassName(className)[0];
|
|
} else if (MINIMAL_DOM_ELEMENT_ROLES.has(domElement)) {
|
|
el = element2;
|
|
} else {
|
|
el = element2.getElementsByClassName(className)[0] ?? createElement("div");
|
|
}
|
|
if (el == null) {
|
|
throw new Error(`AG Charts - unable to find DOM element ${className}`);
|
|
}
|
|
rootElements[domElement] = {
|
|
element: el,
|
|
children: /* @__PURE__ */ new Map(),
|
|
listeners: []
|
|
};
|
|
}
|
|
return rootElements;
|
|
}
|
|
destroy() {
|
|
super.destroy();
|
|
this.observer?.unobserve(this.element);
|
|
if (this.container) {
|
|
this.sizeMonitor.unobserve(this.container);
|
|
}
|
|
this.pendingContainer = void 0;
|
|
for (const el of Object.values(this.rootElements)) {
|
|
for (const c of el.children.values()) {
|
|
c.remove();
|
|
}
|
|
el.element.remove();
|
|
}
|
|
this.element.remove();
|
|
}
|
|
postRenderUpdate() {
|
|
this.updateStylesLocation();
|
|
if (this.mode === "minimal")
|
|
return;
|
|
if (this.pendingContainer == null || this.pendingContainer === this.container)
|
|
return;
|
|
if (_DOMManager.batchedUpdateContainer.length === 0) {
|
|
getWindow().setTimeout(this.applyBatchedUpdateContainer.bind(this), 0);
|
|
}
|
|
_DOMManager.batchedUpdateContainer.push(this);
|
|
}
|
|
applyBatchedUpdateContainer() {
|
|
for (const manager of _DOMManager.batchedUpdateContainer) {
|
|
if (!manager.destroyed) {
|
|
manager.updateContainer();
|
|
}
|
|
}
|
|
_DOMManager.batchedUpdateContainer.splice(0);
|
|
}
|
|
updateStylesLocation() {
|
|
if (this.initiallyConnected === true || this.container?.isConnected === false)
|
|
return;
|
|
this.documentRoot = this.getShadowDocumentRoot(this.container);
|
|
this.initiallyConnected = true;
|
|
for (const id of this.rootElements["styles"].children.keys()) {
|
|
this.removeChild("styles", id);
|
|
}
|
|
for (const [id, styles] of this.styles) {
|
|
this.addStyles(id, styles);
|
|
}
|
|
}
|
|
setSizeOptions(minWidth = 300, minHeight = 300, optionsWidth, optionsHeight) {
|
|
const { style: style2 } = this.element;
|
|
style2.width = `${optionsWidth ?? minWidth}px`;
|
|
style2.height = `${optionsHeight ?? minHeight}px`;
|
|
this.minWidth = optionsWidth ?? minWidth;
|
|
this.minHeight = optionsHeight ?? minHeight;
|
|
this.updateContainerClassName();
|
|
}
|
|
updateContainerSize() {
|
|
const { style: centerStyle } = this.rootElements["canvas-center"].element;
|
|
centerStyle.visibility = this.containerSize == null ? "hidden" : "";
|
|
if (this.containerSize) {
|
|
centerStyle.width = `${this.containerSize.width ?? 0}px`;
|
|
centerStyle.height = `${this.containerSize.height ?? 0}px`;
|
|
} else {
|
|
centerStyle.width = "";
|
|
centerStyle.height = "";
|
|
}
|
|
this.updateContainerClassName();
|
|
}
|
|
setTabGuardIndex(tabIndex) {
|
|
if (!this.tabGuards)
|
|
return;
|
|
this.tabGuards.tabIndex = tabIndex;
|
|
}
|
|
setContainer(newContainer) {
|
|
if (newContainer === this.container)
|
|
return;
|
|
this.pendingContainer = newContainer;
|
|
if (this.mode === "minimal" || this.container == null) {
|
|
this.updateContainer();
|
|
}
|
|
}
|
|
updateContainer() {
|
|
const { pendingContainer } = this;
|
|
if (pendingContainer == null || pendingContainer === this.container)
|
|
return;
|
|
if (this.container) {
|
|
this.element.remove();
|
|
this.sizeMonitor.unobserve(this.container);
|
|
}
|
|
if (this.documentRoot != null) {
|
|
for (const id of this.rootElements["styles"].children.keys()) {
|
|
this.removeChild("styles", id);
|
|
}
|
|
}
|
|
this.container = pendingContainer;
|
|
this.pendingContainer = void 0;
|
|
this.documentRoot = this.getShadowDocumentRoot(pendingContainer);
|
|
this.initiallyConnected = pendingContainer.isConnected;
|
|
for (const [id, styles] of this.styles) {
|
|
this.addStyles(id, styles);
|
|
}
|
|
pendingContainer.appendChild(this.element);
|
|
this.sizeMonitor.observe(pendingContainer, (size) => {
|
|
this.containerSize = size;
|
|
this.updateContainerSize();
|
|
this.eventsHub.emit("dom:resize", null);
|
|
});
|
|
this.eventsHub.emit("dom:container-change", null);
|
|
}
|
|
setThemeClass(themeClassName) {
|
|
const themeClassNamePrefix = "ag-charts-theme-";
|
|
for (const className of Array.from(this.element.classList)) {
|
|
if (className.startsWith(themeClassNamePrefix) && className !== themeClassName) {
|
|
this.element.classList.remove(className);
|
|
}
|
|
}
|
|
this.element.classList.add(themeClassName);
|
|
}
|
|
setThemeParameters(params) {
|
|
for (const [key, value] of entries(params)) {
|
|
let formattedValue = `${value}`;
|
|
if (key.endsWith("Size") || key.endsWith("Radius")) {
|
|
formattedValue = `${value}px`;
|
|
} else if (key.endsWith("Border") && typeof value === "boolean") {
|
|
formattedValue = value ? "var(--ag-charts-border)" : "none";
|
|
}
|
|
this.element.style.setProperty(`--ag-charts-${kebabCase(key)}`, formattedValue);
|
|
}
|
|
}
|
|
updateCanvasLabel(ariaLabel) {
|
|
setAttribute(this.rootElements["canvas-proxy"].element, "aria-label", ariaLabel);
|
|
}
|
|
getEventElement(defaultElem, eventType) {
|
|
const events = ["focus", "blur", "keydown", "keyup"];
|
|
return events.includes(eventType) ? this.rootElements["series-area"].element : defaultElem;
|
|
}
|
|
addEventListener(type, listener, options) {
|
|
const element2 = this.getEventElement(this.element, type);
|
|
return attachListener(element2, type, listener, options);
|
|
}
|
|
removeEventListener(type, listener, options) {
|
|
this.getEventElement(this.element, type).removeEventListener(type, listener, options);
|
|
}
|
|
/** Get the main chart area client bound rect. */
|
|
getBoundingClientRect() {
|
|
return this.rootElements["canvas"].element.getBoundingClientRect();
|
|
}
|
|
/**
|
|
* Get the client bounding rect for overlay elements that might float outside the bounds of the
|
|
* main chart area.
|
|
*/
|
|
getOverlayClientRect() {
|
|
const window = getWindow();
|
|
const windowBBox = new BBox(0, 0, window.innerWidth, window.innerHeight);
|
|
const containerBBox = this.getRawOverlayClientRect();
|
|
return windowBBox.intersection(containerBBox)?.toDOMRect() ?? NULL_DOMRECT;
|
|
}
|
|
getRawOverlayClientRect() {
|
|
let element2 = this.element;
|
|
const fullScreenElement = this.element.getRootNode()?.fullscreenElement;
|
|
while (element2 != null) {
|
|
let isContainer;
|
|
if (fullScreenElement != null && element2 === fullScreenElement) {
|
|
isContainer = true;
|
|
} else {
|
|
const styleMap = element2.computedStyleMap?.();
|
|
const overflowY = styleMap?.get("overflow-y")?.toString();
|
|
isContainer = overflowY === "auto" || overflowY === "scroll";
|
|
}
|
|
if (isContainer) {
|
|
return BBox.fromObject(element2.getBoundingClientRect());
|
|
}
|
|
element2 = element2.parentElement;
|
|
}
|
|
if (this.documentRoot != null)
|
|
return BBox.fromObject(this.documentRoot.getBoundingClientRect());
|
|
const { innerWidth, innerHeight } = getWindow();
|
|
return new BBox(0, 0, innerWidth, innerHeight);
|
|
}
|
|
getShadowDocumentRoot(current = this.container) {
|
|
const docRoot = current?.ownerDocument?.body ?? getDocument("body");
|
|
while (current != null) {
|
|
if (current === docRoot) {
|
|
return void 0;
|
|
}
|
|
if (isDocumentFragment(current.parentNode)) {
|
|
return current;
|
|
}
|
|
current = current.parentNode;
|
|
}
|
|
}
|
|
getParent(domElementClass) {
|
|
return this.rootElements[domElementClass].element;
|
|
}
|
|
getChildBoundingClientRect(type) {
|
|
const { children } = this.rootElements[type];
|
|
const childRects = [];
|
|
for (const child of children.values()) {
|
|
childRects.push(BBox.fromObject(child.getBoundingClientRect()));
|
|
}
|
|
return BBox.merge(childRects);
|
|
}
|
|
isManagedChildDOMElement(el, domElementClass, id) {
|
|
const { children } = this.rootElements[domElementClass];
|
|
const search = children?.get(id);
|
|
return search != null && el.contains(search);
|
|
}
|
|
contains(element2, domElementClass) {
|
|
if (domElementClass == null)
|
|
return this.element.contains(element2);
|
|
return this.rootElements[domElementClass].element.contains(element2);
|
|
}
|
|
addStyles(id, styles) {
|
|
const dataAttribute = "data-ag-charts";
|
|
this.styles.set(id, styles);
|
|
if (this.container == null)
|
|
return;
|
|
if (this.skipCss)
|
|
return;
|
|
const checkId = (el) => {
|
|
return el.getAttribute(dataAttribute) === id;
|
|
};
|
|
const addStyleElement = (el) => {
|
|
const metaElements = /* @__PURE__ */ new Set(["TITLE", "META"]);
|
|
let skippingMetaElements = true;
|
|
let insertAfterEl;
|
|
for (const child of el.children) {
|
|
if (skippingMetaElements && metaElements.has(child.tagName)) {
|
|
insertAfterEl = child;
|
|
continue;
|
|
}
|
|
skippingMetaElements = false;
|
|
if (checkId(child))
|
|
return;
|
|
if (child.hasAttribute(dataAttribute)) {
|
|
insertAfterEl = child;
|
|
}
|
|
}
|
|
const styleEl = createElement("style");
|
|
if (this.chart.styleNonce != null) {
|
|
styleEl.nonce = this.chart.styleNonce;
|
|
}
|
|
if (insertAfterEl == null) {
|
|
el.prepend(styleEl);
|
|
} else {
|
|
el.insertBefore(styleEl, insertAfterEl.nextSibling);
|
|
}
|
|
return styleEl;
|
|
};
|
|
let styleElement;
|
|
if (this.styleContainer) {
|
|
styleElement = addStyleElement(this.styleContainer);
|
|
} else if (this.initiallyConnected === false) {
|
|
styleElement = this.addChild("styles", id);
|
|
} else if (this.documentRoot == null && !_DOMManager.headStyles.has(id)) {
|
|
styleElement = addStyleElement(getDocument("head"));
|
|
_DOMManager.headStyles.add(id);
|
|
} else if (this.documentRoot != null) {
|
|
styleElement = this.addChild("styles", id);
|
|
}
|
|
if (styleElement == null || checkId(styleElement))
|
|
return;
|
|
styleElement.setAttribute(dataAttribute, id);
|
|
styleElement.innerHTML = styles;
|
|
}
|
|
removeStyles(id) {
|
|
this.removeChild("styles", id);
|
|
}
|
|
updateCursor(callerId, style2) {
|
|
this.cursorState.set(callerId, style2);
|
|
this.element.style.cursor = this.cursorState.stateValue();
|
|
}
|
|
getCursor() {
|
|
return this.element.style.cursor;
|
|
}
|
|
addChild(domElementClass, id, child, insert) {
|
|
const { element: element2, children, listeners } = this.rootElements[domElementClass];
|
|
if (!children) {
|
|
throw new Error("AG Charts - unable to create DOM elements after destroy()");
|
|
}
|
|
if (children.has(id)) {
|
|
return children.get(id);
|
|
}
|
|
const { childElementType = "div" } = domElementConfig.get(domElementClass) ?? {};
|
|
if (child && child.tagName.toLowerCase() !== childElementType.toLowerCase()) {
|
|
throw new Error("AG Charts - mismatching DOM element type");
|
|
}
|
|
const newChild = child ?? createElement(childElementType);
|
|
for (const [type, fn, opts] of listeners) {
|
|
newChild.addEventListener(type, fn, opts);
|
|
}
|
|
children.set(id, newChild);
|
|
if (childElementType === "style" && this.chart.styleNonce != null) {
|
|
newChild.nonce = this.chart.styleNonce;
|
|
}
|
|
if (insert) {
|
|
const queryResult = element2.querySelector(insert.query);
|
|
if (queryResult == null) {
|
|
throw new Error(`AG Charts - addChild query failed ${insert.query}`);
|
|
}
|
|
queryResult.insertAdjacentElement(insert.where, newChild);
|
|
} else {
|
|
element2?.appendChild(newChild);
|
|
}
|
|
return newChild;
|
|
}
|
|
removeChild(domElementClass, id) {
|
|
const { children } = this.rootElements[domElementClass];
|
|
if (!children)
|
|
return;
|
|
children.get(id)?.remove();
|
|
children.delete(id);
|
|
}
|
|
incrementDataCounter(name) {
|
|
const { dataset } = this.element;
|
|
dataset[name] ?? (dataset[name] = "0");
|
|
dataset[name] = String(Number(dataset[name]) + 1);
|
|
}
|
|
setDataBoolean(name, value) {
|
|
this.element.dataset[name] = String(value);
|
|
}
|
|
setDataNumber(name, value) {
|
|
this.element.dataset[name] = String(value);
|
|
}
|
|
updateContainerClassName() {
|
|
const { element: element2, containerSize, minWidth, minHeight } = this;
|
|
element2.classList.toggle(CONTAINER_MODIFIERS.safeHorizontal, minWidth >= (containerSize?.width ?? Infinity));
|
|
element2.classList.toggle(CONTAINER_MODIFIERS.safeVertical, minHeight >= (containerSize?.height ?? Infinity));
|
|
}
|
|
};
|
|
_DOMManager.className = "DOMManager";
|
|
_DOMManager.batchedUpdateContainer = [];
|
|
_DOMManager.headStyles = /* @__PURE__ */ new Set();
|
|
var DOMManager = _DOMManager;
|
|
|
|
// packages/ag-charts-community/src/widget/boundedTextWidget.ts
|
|
var BoundedTextWidget = class extends Widget {
|
|
constructor() {
|
|
super(createElement("div"));
|
|
this.textElement = createSvgElement("text");
|
|
this.textElement.role = "presentation";
|
|
this.svgElement = createSvgElement("svg");
|
|
this.svgElement.appendChild(this.textElement);
|
|
this.svgElement.style.width = "100%";
|
|
this.svgElement.style.opacity = "0";
|
|
this.svgElement.role = "presentation";
|
|
this.elem.appendChild(this.svgElement);
|
|
this.elem.role = "presentation";
|
|
}
|
|
set textContent(text2) {
|
|
this.textElement.textContent = text2;
|
|
const bboxCalculator = this.textElement;
|
|
const bbox = bboxCalculator.getBBox?.();
|
|
if (bbox) {
|
|
this.svgElement.setAttribute("viewBox", `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`);
|
|
}
|
|
}
|
|
get textContent() {
|
|
return this.textElement.textContent;
|
|
}
|
|
destructor() {
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/widget/expansionControllerImpl.ts
|
|
var ExpansionControllerImpl = class {
|
|
constructor(controller, getDispatcher) {
|
|
this.getDispatcher = getDispatcher;
|
|
this.onExpanded = () => {
|
|
this.controller.setAriaExpanded(true);
|
|
const dispatcher = this.getDispatcher();
|
|
if (dispatcher && this.controls) {
|
|
const event = {
|
|
type: "expand-controlled-widget",
|
|
controlled: this.controls
|
|
};
|
|
dispatcher.dispatch("expand-controlled-widget", this.controller, event);
|
|
}
|
|
};
|
|
this.onCollapsed = (e) => {
|
|
this.controller.setAriaExpanded(false);
|
|
if (e.mode === "0" /* CLOSE */) {
|
|
this.controller.focus();
|
|
}
|
|
};
|
|
controller.setAriaExpanded(false);
|
|
this.controller = controller;
|
|
}
|
|
destroy() {
|
|
this.controls?.collapse({ mode: "2" /* DESTROY */ });
|
|
this.setControlled(void 0);
|
|
}
|
|
setControlled(controls) {
|
|
if (this.controls) {
|
|
this.controls.removeListener("expand-widget", this.onExpanded);
|
|
this.controls.removeListener("collapse-widget", this.onCollapsed);
|
|
}
|
|
this.controls = controls;
|
|
if (this.controls) {
|
|
this.controller.setAriaControls(this.controls.id);
|
|
this.controls.addListener("expand-widget", this.onExpanded);
|
|
this.controls.addListener("collapse-widget", this.onCollapsed);
|
|
}
|
|
}
|
|
getControlled() {
|
|
return this.controls;
|
|
}
|
|
expandControlled(opts) {
|
|
if (!this.controller.isDisabled()) {
|
|
this.controls?.expand({
|
|
controller: this.controller,
|
|
sourceEvent: void 0,
|
|
overrideFocusVisible: opts?.overrideFocusVisible
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/widget/abstractButtonWidget.ts
|
|
var AbstractButtonWidget = class extends Widget {
|
|
constructor(element2, role) {
|
|
super(element2);
|
|
setAttribute(this.elem, "role", role);
|
|
this.setEnabled(true);
|
|
this.addListener("keydown", ({ sourceEvent }) => {
|
|
if (isButtonClickEvent(sourceEvent)) {
|
|
sourceEvent.preventDefault();
|
|
this.htmlListener?.dispatch("click", this, { type: "click", device: "keyboard", sourceEvent });
|
|
}
|
|
});
|
|
}
|
|
lazyControllerImpl() {
|
|
this.controllerImpl ?? (this.controllerImpl = new ExpansionControllerImpl(this, () => this.internalListener));
|
|
return this.controllerImpl;
|
|
}
|
|
destructor() {
|
|
this.controllerImpl?.destroy();
|
|
}
|
|
setEnabled(enabled) {
|
|
setAttribute(this.elem, "aria-disabled", !enabled);
|
|
}
|
|
setControlled(controls) {
|
|
return this.lazyControllerImpl().setControlled(controls);
|
|
}
|
|
getControlled() {
|
|
return this.lazyControllerImpl().getControlled();
|
|
}
|
|
expandControlled(opts) {
|
|
return this.lazyControllerImpl().expandControlled(opts);
|
|
}
|
|
addListener(type, listener) {
|
|
return super.addListener(type, (ev, current) => {
|
|
if ((type === "click" || type === "dblclick") && this.isDisabled())
|
|
return;
|
|
listener(ev, current);
|
|
});
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/widget/buttonWidget.ts
|
|
var ButtonWidget = class extends AbstractButtonWidget {
|
|
constructor() {
|
|
super(createElement("button"));
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/widget/groupWidget.ts
|
|
var GroupWidget = class extends Widget {
|
|
constructor() {
|
|
super(createElement("div"));
|
|
setAttribute(this.elem, "role", "group");
|
|
}
|
|
destructor() {
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/widget/rovingTabContainerWidget.ts
|
|
var RovingTabContainerWidget = class extends Widget {
|
|
constructor(initialOrientation, role) {
|
|
super(createElement("div"));
|
|
this.focusedChildIndex = 0;
|
|
this.onChildFocus = (_event, child) => {
|
|
const oldFocus = this.children[this.focusedChildIndex];
|
|
this.focusedChildIndex = child.index;
|
|
oldFocus?.setTabIndex(-1);
|
|
child.setTabIndex(0);
|
|
};
|
|
this.onChildKeyDown = (event, child) => {
|
|
const rovingOrientation = this.orientation;
|
|
const [primaryKeys, secondaryKeys] = rovingOrientation === "both" ? [PREV_NEXT_KEYS["horizontal"], PREV_NEXT_KEYS["vertical"]] : [PREV_NEXT_KEYS[rovingOrientation], void 0];
|
|
let targetIndex = -1;
|
|
if (hasNoModifiers(event.sourceEvent)) {
|
|
const key = event.sourceEvent.key;
|
|
if (key === primaryKeys.nextKey || key === secondaryKeys?.nextKey) {
|
|
targetIndex = child.index + 1;
|
|
} else if (key === primaryKeys.prevKey || key === secondaryKeys?.prevKey) {
|
|
targetIndex = child.index - 1;
|
|
}
|
|
}
|
|
this.children[targetIndex]?.focus();
|
|
};
|
|
setAttribute(this.elem, "role", role);
|
|
this.orientation = initialOrientation;
|
|
}
|
|
get orientation() {
|
|
return getAttribute(this.elem, "aria-orientation") ?? "both";
|
|
}
|
|
set orientation(orientation) {
|
|
setAttribute(this.elem, "aria-orientation", orientation === "both" ? void 0 : orientation);
|
|
}
|
|
focus() {
|
|
this.children[this.focusedChildIndex]?.focus();
|
|
}
|
|
clear() {
|
|
this.focusedChildIndex = 0;
|
|
for (const child of this.children) {
|
|
this.removeChildListeners(child);
|
|
child.parent = void 0;
|
|
}
|
|
this.elem.textContent = "";
|
|
this.children.length = 0;
|
|
}
|
|
addChildListeners(child) {
|
|
child.addListener("focus", this.onChildFocus);
|
|
child.addListener("keydown", this.onChildKeyDown);
|
|
}
|
|
removeChildListeners(child) {
|
|
child.removeListener("focus", this.onChildFocus);
|
|
child.removeListener("keydown", this.onChildKeyDown);
|
|
}
|
|
onChildAdded(child) {
|
|
this.addChildListeners(child);
|
|
child.setTabIndex(this.children.length === 1 ? 0 : -1);
|
|
}
|
|
onChildRemoved(removedChild) {
|
|
this.removeChildListeners(removedChild);
|
|
const { focusedChildIndex, children } = this;
|
|
const removedFocusedChild = focusedChildIndex === removedChild.index;
|
|
for (let i = 0; i < children.length; i++) {
|
|
const child = children[i];
|
|
if (child.index === focusedChildIndex) {
|
|
this.focusedChildIndex = i;
|
|
}
|
|
child.index = i;
|
|
}
|
|
if (removedFocusedChild) {
|
|
const newFocusChild = children[focusedChildIndex] ?? children[focusedChildIndex - 1];
|
|
if (newFocusChild) {
|
|
this.focusedChildIndex = newFocusChild.index;
|
|
newFocusChild.setTabIndex(0);
|
|
} else {
|
|
this.focusedChildIndex = 0;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/widget/listWidget.ts
|
|
var ListWidget = class extends RovingTabContainerWidget {
|
|
constructor() {
|
|
super("both", "list");
|
|
this.setHidden(true);
|
|
}
|
|
destructor() {
|
|
for (const c of this.children) {
|
|
c.getElement().parentElement.remove();
|
|
}
|
|
}
|
|
addChildToDOM(child, before) {
|
|
const listItem = createElement("div");
|
|
setAttribute(listItem, "role", "listitem");
|
|
setElementStyle(listItem, "position", "absolute");
|
|
Widget.setElementContainer(child, listItem);
|
|
this.appendOrInsert(listItem, before);
|
|
this.setHidden(false);
|
|
}
|
|
removeChildFromDOM(child) {
|
|
child.getElement().parentElement.remove();
|
|
this.setHidden(this.children.length === 0);
|
|
}
|
|
setHidden(hidden) {
|
|
if (this.children.length === 0) {
|
|
hidden = true;
|
|
}
|
|
super.setHidden(hidden);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/widget/nativeWidget.ts
|
|
var NativeWidget = class extends Widget {
|
|
constructor(elem) {
|
|
super(elem);
|
|
}
|
|
destructor() {
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/widget/sliderWidget.ts
|
|
var _SliderWidget = class _SliderWidget extends Widget {
|
|
constructor() {
|
|
super(createElement("input"));
|
|
this._step = _SliderWidget.STEP_ONE;
|
|
this.orientation = "both";
|
|
}
|
|
get step() {
|
|
return this._step;
|
|
}
|
|
set step(step) {
|
|
this._step = step;
|
|
this.getElement().step = step.attributeValue;
|
|
}
|
|
get keyboardStep() {
|
|
return this._keyboardStep?.step ?? this._step;
|
|
}
|
|
set keyboardStep(step) {
|
|
if (step === this._keyboardStep?.step)
|
|
return;
|
|
if (this._keyboardStep !== void 0) {
|
|
this.removeListener("keydown", this._keyboardStep.onKeyDown);
|
|
this.removeListener("keyup", this._keyboardStep.onKeyUp);
|
|
this.removeListener("blur", this._keyboardStep.onBlur);
|
|
this._keyboardStep = void 0;
|
|
}
|
|
if (step !== void 0) {
|
|
const onKeyDown = () => this.getElement().step = step.attributeValue;
|
|
const resetStep = () => this.getElement().step = this._step.attributeValue;
|
|
this._keyboardStep = { step, onKeyDown, onKeyUp: resetStep, onBlur: resetStep };
|
|
this.addListener("keydown", this._keyboardStep.onKeyDown);
|
|
this.addListener("keyup", this._keyboardStep.onKeyUp);
|
|
this.addListener("blur", this._keyboardStep.onBlur);
|
|
}
|
|
}
|
|
get orientation() {
|
|
return getAttribute(this.elem, "aria-orientation") ?? "both";
|
|
}
|
|
set orientation(orientation) {
|
|
setAttribute(this.elem, "aria-orientation", orientation === "both" ? void 0 : orientation);
|
|
_SliderWidget.registerDefaultPreventers(this, orientation);
|
|
}
|
|
destructor() {
|
|
}
|
|
clampValueRatio(clampMin, clampMax) {
|
|
const ratio2 = this.getValueRatio();
|
|
const clampedRatio = clamp(clampMin, ratio2, clampMax);
|
|
if (clampedRatio !== ratio2) {
|
|
this.setValueRatio(clampedRatio);
|
|
}
|
|
return clampedRatio;
|
|
}
|
|
setValueRatio(ratio2, opts) {
|
|
const { divider } = this.step;
|
|
const value = Math.round(ratio2 * 1e4) / divider;
|
|
const { ariaValueText = formatPercent(value / divider) } = opts ?? {};
|
|
const elem = this.getElement();
|
|
elem.value = `${value}`;
|
|
elem.ariaValueText = ariaValueText;
|
|
elem.ariaValueNow = `${value}`;
|
|
}
|
|
getValueRatio() {
|
|
return this.getElement().valueAsNumber / this.step.divider;
|
|
}
|
|
static registerDefaultPreventers(target, orientation) {
|
|
if (orientation === "both") {
|
|
target.removeListener("keydown", _SliderWidget.onKeyDown);
|
|
} else {
|
|
target.addListener("keydown", _SliderWidget.onKeyDown);
|
|
}
|
|
}
|
|
static onKeyDown(ev, current) {
|
|
let ignoredKeys = [];
|
|
const { orientation } = current;
|
|
if (orientation === "horizontal") {
|
|
ignoredKeys = ["ArrowUp", "ArrowDown"];
|
|
} else if (orientation === "vertical") {
|
|
ignoredKeys = ["ArrowLeft", "ArrowRight"];
|
|
}
|
|
if (ignoredKeys.includes(ev.sourceEvent.code)) {
|
|
ev.sourceEvent.preventDefault();
|
|
}
|
|
}
|
|
};
|
|
_SliderWidget.STEP_ONE = { attributeValue: "1", divider: 1 };
|
|
_SliderWidget.STEP_HUNDRETH = { attributeValue: "0.01", divider: 100 };
|
|
var SliderWidget = _SliderWidget;
|
|
|
|
// packages/ag-charts-community/src/widget/switchWidget.ts
|
|
var SwitchWidget = class extends ButtonWidget {
|
|
constructor() {
|
|
super();
|
|
setAttribute(this.elem, "role", "switch");
|
|
this.setChecked(false);
|
|
}
|
|
setChecked(checked) {
|
|
setAttribute(this.elem, "aria-checked", checked);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/widget/toolbarWidget.ts
|
|
var ToolbarWidget = class extends RovingTabContainerWidget {
|
|
constructor(orientation = "horizontal") {
|
|
super(orientation, "toolbar");
|
|
}
|
|
destructor() {
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/dom/proxyInteractionService.ts
|
|
function checkType(type, meta) {
|
|
return meta.params?.type === type;
|
|
}
|
|
function allocateResult(type) {
|
|
if ("button" === type) {
|
|
return new ButtonWidget();
|
|
} else if ("slider" === type) {
|
|
return new SliderWidget();
|
|
} else if ("toolbar" === type) {
|
|
return new ToolbarWidget();
|
|
} else if ("group" === type) {
|
|
return new GroupWidget();
|
|
} else if ("list" === type) {
|
|
return new ListWidget();
|
|
} else if ("region" === type) {
|
|
return new NativeWidget(createElement("div"));
|
|
} else if ("text" === type) {
|
|
return new BoundedTextWidget();
|
|
} else if ("listswitch" === type) {
|
|
return new SwitchWidget();
|
|
} else {
|
|
throw new Error("AG Charts - error allocating meta");
|
|
}
|
|
}
|
|
function allocateMeta(params) {
|
|
const meta = { params, result: void 0 };
|
|
meta.result = allocateResult(meta.params.type);
|
|
return meta;
|
|
}
|
|
var ProxyInteractionService = class {
|
|
constructor(eventsHub, localeManager, domManager) {
|
|
this.eventsHub = eventsHub;
|
|
this.localeManager = localeManager;
|
|
this.domManager = domManager;
|
|
this.cleanup = new CleanupRegistry();
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
}
|
|
addLocalisation(fn) {
|
|
fn();
|
|
this.cleanup.register(this.eventsHub.on("locale:change", fn));
|
|
}
|
|
createProxyContainer(args) {
|
|
const meta = allocateMeta(args);
|
|
const { params, result } = meta;
|
|
const div = result.getElement();
|
|
this.domManager.addChild("canvas-proxy", params.domManagerId, div);
|
|
div.classList.add(...params.classList, "ag-charts-proxy-container");
|
|
div.role = params.role ?? params.type;
|
|
if (checkType("toolbar", meta)) {
|
|
meta.result.orientation = meta.params.orientation;
|
|
}
|
|
const { ariaLabel } = params;
|
|
if (ariaLabel) {
|
|
this.addLocalisation(() => {
|
|
div.ariaLabel = this.localeManager.t(ariaLabel.id, ariaLabel.params);
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
createProxyElement(args) {
|
|
const meta = allocateMeta(args);
|
|
if (checkType("button", meta)) {
|
|
const { params, result } = meta;
|
|
const button = result.getElement();
|
|
this.initInteract(params, result);
|
|
if (typeof params.textContent === "string") {
|
|
button.textContent = params.textContent;
|
|
} else {
|
|
const { textContent } = params;
|
|
this.addLocalisation(() => {
|
|
button.textContent = this.localeManager.t(textContent.id, textContent.params);
|
|
});
|
|
}
|
|
this.setParent(meta.params, meta.result);
|
|
}
|
|
if (checkType("slider", meta)) {
|
|
const { params, result } = meta;
|
|
const slider = result.getElement();
|
|
this.initInteract(params, result);
|
|
slider.type = "range";
|
|
slider.role = params.role ?? "presentation";
|
|
slider.style.margin = "0px";
|
|
this.addLocalisation(() => {
|
|
slider.ariaLabel = this.localeManager.t(params.ariaLabel.id, params.ariaLabel.params);
|
|
});
|
|
this.setParent(meta.params, meta.result);
|
|
}
|
|
if (checkType("text", meta)) {
|
|
const { params, result } = meta;
|
|
this.initElement(params, result);
|
|
this.setParent(meta.params, meta.result);
|
|
}
|
|
if (checkType("listswitch", meta)) {
|
|
const { params, result: button } = meta;
|
|
this.initInteract(params, button);
|
|
button.setTextContent(params.textContent);
|
|
button.setChecked(params.ariaChecked);
|
|
button.setAriaDescribedBy(params.ariaDescribedBy);
|
|
this.setParent(meta.params, meta.result);
|
|
}
|
|
if (checkType("region", meta)) {
|
|
const { params, result } = meta;
|
|
const region = result.getElement();
|
|
this.initInteract(params, result);
|
|
region.role = params.role ?? "region";
|
|
this.setParent(meta.params, meta.result);
|
|
}
|
|
return meta.result;
|
|
}
|
|
initElement(params, widget) {
|
|
const element2 = widget.getElement();
|
|
setElementStyle(element2, "cursor", params.cursor);
|
|
element2.classList.toggle("ag-charts-proxy-elem", true);
|
|
if (params.classList?.length) {
|
|
element2.classList.add(...params.classList);
|
|
}
|
|
return element2;
|
|
}
|
|
initInteract(params, widget) {
|
|
const { tabIndex, domIndex } = params;
|
|
const element2 = this.initElement(params, widget);
|
|
if (tabIndex !== void 0) {
|
|
element2.tabIndex = tabIndex;
|
|
}
|
|
if (domIndex !== void 0) {
|
|
widget.domIndex = domIndex;
|
|
}
|
|
}
|
|
setParent(params, element2) {
|
|
if ("parent" in params) {
|
|
params.parent?.addChild(element2);
|
|
} else {
|
|
const insert = { where: params.where, query: ".ag-charts-series-area" };
|
|
this.domManager.addChild("canvas-proxy", params.domManagerId, element2.getElement(), insert);
|
|
element2.destroyListener = () => {
|
|
this.domManager.removeChild("canvas-proxy", params.domManagerId);
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/locale/defaultMessageFormatter.ts
|
|
var messageRegExp = /\$\{(\w+)}(?:\[(\w+)])?/gi;
|
|
var formatters = {
|
|
number: new Intl.NumberFormat("en-US"),
|
|
percent: new Intl.NumberFormat("en-US", { style: "percent", minimumFractionDigits: 2, maximumFractionDigits: 2 }),
|
|
percent0to2dp: new Intl.NumberFormat("en-US", {
|
|
style: "percent",
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: 2
|
|
}),
|
|
date: new Intl.DateTimeFormat("en-US", { dateStyle: "full" }),
|
|
time: new Intl.DateTimeFormat("en-US", { timeStyle: "full" }),
|
|
datetime: new Intl.DateTimeFormat("en-US", { dateStyle: "full", timeStyle: "full" })
|
|
};
|
|
var defaultMessageFormatter = ({ defaultValue, variables }) => {
|
|
return defaultValue?.replaceAll(messageRegExp, (_, match, format) => {
|
|
const value = variables[match];
|
|
const formatter2 = format == null ? null : formatters[format];
|
|
if (format != null && formatter2 == null) {
|
|
logger_exports.warnOnce(`Format style [${format}] is not supported`);
|
|
}
|
|
if (formatter2 != null) {
|
|
return formatter2.format(value);
|
|
} else if (typeof value === "number") {
|
|
return formatters.number.format(value);
|
|
} else if (value instanceof Date) {
|
|
return formatters.datetime.format(value);
|
|
}
|
|
return String(value);
|
|
});
|
|
};
|
|
|
|
// packages/ag-charts-community/src/locale/localeManager.ts
|
|
var LocaleManager = class {
|
|
constructor(eventsHub) {
|
|
this.eventsHub = eventsHub;
|
|
this.localeText = void 0;
|
|
this.getLocaleText = void 0;
|
|
}
|
|
setLocaleText(localeText) {
|
|
if (this.localeText !== localeText) {
|
|
this.localeText = localeText;
|
|
this.eventsHub.emit("locale:change", null);
|
|
}
|
|
}
|
|
setLocaleTextFormatter(getLocaleText) {
|
|
this.getLocaleText = getLocaleText;
|
|
if (this.getLocaleText !== getLocaleText) {
|
|
this.getLocaleText = getLocaleText;
|
|
this.eventsHub.emit("locale:change", null);
|
|
}
|
|
}
|
|
t(key, variables = {}) {
|
|
const { localeText = AG_CHARTS_LOCALE_EN_US, getLocaleText } = this;
|
|
const defaultValue = localeText[key];
|
|
return String(
|
|
getLocaleText?.({ key, defaultValue, variables }) ?? defaultMessageFormatter({ key, defaultValue, variables }) ?? key
|
|
);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/canvas/hdpiCanvas.ts
|
|
var HdpiCanvas = class {
|
|
constructor(options) {
|
|
this.enabled = true;
|
|
this.width = 600;
|
|
this.height = 300;
|
|
const { width: width2, height: height2, canvasElement, willReadFrequently = false } = options;
|
|
this.pixelRatio = options.pixelRatio ?? getWindow("devicePixelRatio") ?? 1;
|
|
this.element = canvasElement ?? createElement("canvas");
|
|
this.element.style.display = "block";
|
|
this.element.style.width = (width2 ?? this.width) + "px";
|
|
this.element.style.height = (height2 ?? this.height) + "px";
|
|
this.element.width = Math.round((width2 ?? this.width) * this.pixelRatio);
|
|
this.element.height = Math.round((height2 ?? this.height) * this.pixelRatio);
|
|
this.context = this.element.getContext("2d", { willReadFrequently });
|
|
this.onEnabledChange();
|
|
this.resize(width2 ?? 0, height2 ?? 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, dx2 = 0, dy2 = 0) {
|
|
return context.drawImage(this.context.canvas, dx2, dy2);
|
|
}
|
|
toDataURL(type) {
|
|
return this.element.toDataURL(type);
|
|
}
|
|
resize(width2, height2, pixelRatio) {
|
|
if (!(width2 > 0 && height2 > 0))
|
|
return;
|
|
const { element: element2, context } = this;
|
|
element2.width = Math.round(width2 * pixelRatio);
|
|
element2.height = Math.round(height2 * pixelRatio);
|
|
context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
|
|
element2.style.width = width2 + "px";
|
|
element2.style.height = height2 + "px";
|
|
this.width = width2;
|
|
this.height = height2;
|
|
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
|
|
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
|
|
var LayersManager = class {
|
|
constructor(canvas) {
|
|
this.canvas = canvas;
|
|
this.debug = debugLogger_exports.create(true, "scene");
|
|
this.layersMap = /* @__PURE__ */ new Map();
|
|
this.nextLayerId = 0;
|
|
}
|
|
get size() {
|
|
return this.layersMap.size;
|
|
}
|
|
resize(width2, height2, pixelRatio) {
|
|
this.canvas.resize(width2, height2, pixelRatio);
|
|
for (const { canvas } of this.layersMap.values()) {
|
|
canvas.resize(width2, height2, pixelRatio);
|
|
}
|
|
}
|
|
addLayer(opts) {
|
|
const { width: width2, height: height2, pixelRatio } = this.canvas;
|
|
const { name } = opts;
|
|
const canvas = new HdpiOffscreenCanvas({ width: width2, height: height2, 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 EventEmitter {
|
|
constructor(canvasOptions) {
|
|
super();
|
|
this.debug = debugLogger_exports.create(true, "scene" /* SCENE */);
|
|
this.id = createId(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 }) => {
|
|
logger_exports.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: element2 } = this.canvas;
|
|
element2.remove();
|
|
value.appendChild(element2);
|
|
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() {
|
|
debugLogger_exports.inDevelopmentMode(() => Node2._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(width2, height2, pixelRatio) {
|
|
width2 = Math.round(width2);
|
|
height2 = Math.round(height2);
|
|
pixelRatio ?? (pixelRatio = this.pixelRatio);
|
|
if (width2 > 0 && height2 > 0 && (width2 !== this.width || height2 !== this.height || pixelRatio !== this.pixelRatio)) {
|
|
this.pendingSize = [width2, height2, 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: width2, height: height2, pixelRatio: devicePixelRatio } = this;
|
|
if (!ctx) {
|
|
return;
|
|
}
|
|
const statsEnabled = debugLogger_exports.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: width2,
|
|
height: height2,
|
|
devicePixelRatio,
|
|
debugNodes: {}
|
|
};
|
|
if (debugLogger_exports.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 && debugLogger_exports.check("scene:dirtyTree" /* SCENE_DIRTY_TREE */)) {
|
|
const { dirtyTree, paths } = buildDirtyTree(root);
|
|
debugLogger_exports.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: width2, height: height2 } = this;
|
|
if (root == null)
|
|
return;
|
|
return Node2.toSVG(root, width2, height2);
|
|
}
|
|
/** 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/chart/annotation/annotationManager.ts
|
|
var AnnotationManager = class {
|
|
constructor(eventsHub, annotationRoot, fireChartEvent) {
|
|
this.eventsHub = eventsHub;
|
|
this.annotationRoot = annotationRoot;
|
|
this.fireChartEvent = fireChartEvent;
|
|
this.mementoOriginatorKey = "annotations";
|
|
this.annotations = [];
|
|
}
|
|
createMemento() {
|
|
return this.annotations;
|
|
}
|
|
guardMemento(blob) {
|
|
return blob == null || isArray(blob);
|
|
}
|
|
restoreMemento(_version, _mementoVersion, memento) {
|
|
this.annotations = this.cleanData(memento ?? []).map((annotation) => {
|
|
const annotationTheme = this.getAnnotationTypeStyles(annotation.type);
|
|
return mergeDefaults(annotation, annotationTheme);
|
|
});
|
|
this.eventsHub.emit("annotations:restore", { annotations: this.annotations });
|
|
}
|
|
updateData(annotations) {
|
|
this.annotations = this.cleanData(annotations ?? []);
|
|
}
|
|
fireChangedEvent() {
|
|
this.fireChartEvent({ type: "annotations", annotations: deepClone([...this.annotations]) });
|
|
}
|
|
attachNode(node) {
|
|
this.annotationRoot.append(node);
|
|
return () => {
|
|
node.remove();
|
|
return this;
|
|
};
|
|
}
|
|
setAnnotationStyles(styles) {
|
|
this.styles = styles;
|
|
}
|
|
getAnnotationTypeStyles(type) {
|
|
return this.styles?.[type];
|
|
}
|
|
cleanData(annotations) {
|
|
for (const annotation of annotations) {
|
|
if ("textAlign" in annotation)
|
|
delete annotation.textAlign;
|
|
}
|
|
return annotations;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/axis/axisManager.ts
|
|
var AxisManager = class {
|
|
constructor(eventsHub, sceneRoot) {
|
|
this.eventsHub = eventsHub;
|
|
this.sceneRoot = sceneRoot;
|
|
this.axes = /* @__PURE__ */ new Map();
|
|
this.axisGridGroup = new Group({ name: "Axes-Grids", zIndex: 2 /* AXIS_GRID */ });
|
|
this.axisGroup = new Group({ name: "Axes", zIndex: 3 /* AXIS */ });
|
|
this.axisLabelGroup = new Group({ name: "Axes-Labels", zIndex: 15 /* SERIES_LABEL */ });
|
|
this.axisCrosslineRangeGroup = new Group({
|
|
name: "Axes-Crosslines-Range",
|
|
zIndex: 6 /* SERIES_CROSSLINE_RANGE */
|
|
});
|
|
this.axisCrosslineLineGroup = new Group({
|
|
name: "Axes-Crosslines-Line",
|
|
zIndex: 10 /* SERIES_CROSSLINE_LINE */
|
|
});
|
|
this.axisCrosslineLabelGroup = new Group({
|
|
name: "Axes-Crosslines-Label",
|
|
zIndex: 15 /* SERIES_LABEL */
|
|
});
|
|
this.sceneRoot.appendChild(this.axisGroup);
|
|
this.sceneRoot.appendChild(this.axisGridGroup);
|
|
this.sceneRoot.appendChild(this.axisLabelGroup);
|
|
this.sceneRoot.appendChild(this.axisCrosslineRangeGroup);
|
|
this.sceneRoot.appendChild(this.axisCrosslineLineGroup);
|
|
this.sceneRoot.appendChild(this.axisCrosslineLabelGroup);
|
|
}
|
|
updateAxes(oldAxes, newAxes) {
|
|
const axisNodes = {
|
|
axisNode: this.axisGroup,
|
|
gridNode: this.axisGridGroup,
|
|
labelNode: this.axisLabelGroup,
|
|
crossLineRangeNode: this.axisCrosslineRangeGroup,
|
|
crossLineLineNode: this.axisCrosslineLineGroup,
|
|
crossLineLabelNode: this.axisCrosslineLabelGroup
|
|
};
|
|
for (const axis of oldAxes) {
|
|
if (newAxes.includes(axis))
|
|
continue;
|
|
axis.detachAxis(axisNodes);
|
|
axis.destroy();
|
|
}
|
|
for (const axis of newAxes) {
|
|
if (oldAxes?.includes(axis))
|
|
continue;
|
|
axis.attachAxis(axisNodes);
|
|
}
|
|
this.axes.clear();
|
|
for (const axis of newAxes) {
|
|
const ctx = axis.createAxisContext();
|
|
if (this.axes.has(ctx.direction)) {
|
|
this.axes.get(ctx.direction)?.push(ctx);
|
|
} else {
|
|
this.axes.set(ctx.direction, [ctx]);
|
|
}
|
|
}
|
|
this.eventsHub.emit("axis:change", null);
|
|
}
|
|
getAxisIdContext(id) {
|
|
for (const [, contextsInThisDir] of this.axes) {
|
|
for (const ctx of contextsInThisDir) {
|
|
if (ctx.axisId === id)
|
|
return ctx;
|
|
}
|
|
}
|
|
}
|
|
getAxisContext(direction) {
|
|
return this.axes.get(direction) ?? [];
|
|
}
|
|
destroy() {
|
|
this.axes.clear();
|
|
this.axisGroup.remove();
|
|
this.axisGridGroup.remove();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/dataService.ts
|
|
var DataService = class {
|
|
constructor(eventsHub, caller, animationManager) {
|
|
this.eventsHub = eventsHub;
|
|
this.caller = caller;
|
|
this.animationManager = animationManager;
|
|
this.dispatchOnlyLatest = true;
|
|
this.dispatchThrottle = 0;
|
|
this.requestThrottle = 300;
|
|
this.isLoadingInitialData = false;
|
|
this.isLoadingData = false;
|
|
this.freshRequests = [];
|
|
this.requestCounter = 0;
|
|
this.pendingData = void 0;
|
|
this.debug = debugLogger_exports.create(true, "data-model", "data-source");
|
|
this.throttledFetch = this.createThrottledFetch(this.requestThrottle);
|
|
this.throttledDispatch = this.createThrottledDispatch(this.dispatchThrottle);
|
|
}
|
|
updateCallback(dataSourceCallback) {
|
|
if (typeof dataSourceCallback !== "function")
|
|
return;
|
|
this.debug("DataService - updated data source callback");
|
|
this.dataSourceCallback = dataSourceCallback;
|
|
this.isLoadingInitialData = true;
|
|
this.animationManager.skip();
|
|
this.eventsHub.emit("data:source-change", null);
|
|
}
|
|
clearCallback() {
|
|
this.dataSourceCallback = void 0;
|
|
}
|
|
load(params) {
|
|
const { pendingData } = this;
|
|
if (pendingData != null && (pendingData.params.windowStart == null && pendingData.params.windowEnd == null || pendingData.params.windowStart?.valueOf() === params.windowStart?.valueOf() && pendingData.params.windowEnd?.valueOf() === params.windowEnd?.valueOf())) {
|
|
const id = this.requestCounter++;
|
|
this.isLoadingInitialData = false;
|
|
this.dispatch(id, pendingData.data);
|
|
return;
|
|
}
|
|
this.isLoadingData = true;
|
|
this.throttledFetch(params);
|
|
}
|
|
isLazy() {
|
|
return this.dataSourceCallback != null;
|
|
}
|
|
isLoading() {
|
|
return this.isLazy() && (this.isLoadingInitialData || this.isLoadingData);
|
|
}
|
|
async getData() {
|
|
const { latestRequest } = this;
|
|
if (!latestRequest)
|
|
return;
|
|
const { params, fetchRequest } = latestRequest;
|
|
const data = await fetchRequest;
|
|
return { params, data };
|
|
}
|
|
restoreData(data) {
|
|
this.pendingData = data;
|
|
}
|
|
createThrottledFetch(requestThrottle) {
|
|
return throttle(
|
|
(params) => this.fetch(params).catch((e) => logger_exports.error("callback failed", e)),
|
|
requestThrottle,
|
|
{ leading: false, trailing: true }
|
|
);
|
|
}
|
|
createThrottledDispatch(dispatchThrottle) {
|
|
return throttle((id, data) => this.dispatch(id, data), dispatchThrottle, {
|
|
leading: true,
|
|
trailing: true
|
|
});
|
|
}
|
|
dispatch(id, data) {
|
|
this.debug(`DataService - dispatching 'data-load' | ${id}`);
|
|
this.eventsHub.emit("data:load", { data });
|
|
}
|
|
async fetch(params) {
|
|
if ("context" in this.caller) {
|
|
params.context = this.caller.context;
|
|
}
|
|
const fetchRequest = Promise.resolve().then(async () => {
|
|
if (!this.dataSourceCallback) {
|
|
throw new Error("DataService - [dataSource.getData] callback not initialised");
|
|
}
|
|
const start2 = performance.now();
|
|
const id = this.requestCounter++;
|
|
this.debug(`DataService - requesting | ${id}`);
|
|
let response;
|
|
try {
|
|
response = await this.dataSourceCallback(params);
|
|
this.debug(`DataService - response | ${performance.now() - start2}ms | ${id}`);
|
|
} catch (error2) {
|
|
this.debug(`DataService - request failed | ${id}`);
|
|
logger_exports.errorOnce(`DataService - request failed | [${error2}]`);
|
|
}
|
|
this.isLoadingInitialData = false;
|
|
const requestIndex = this.freshRequests.indexOf(fetchRequest);
|
|
if (requestIndex === -1 || this.dispatchOnlyLatest && requestIndex !== this.freshRequests.length - 1) {
|
|
this.debug(`DataService - discarding stale request | ${id}`);
|
|
return response;
|
|
}
|
|
this.freshRequests = this.freshRequests.slice(requestIndex + 1);
|
|
if (this.freshRequests.length === 0) {
|
|
this.isLoadingData = false;
|
|
}
|
|
if (Array.isArray(response)) {
|
|
this.throttledDispatch(id, response);
|
|
} else {
|
|
this.eventsHub.emit("data:error", null);
|
|
}
|
|
return response;
|
|
});
|
|
this.latestRequest = { params, fetchRequest };
|
|
this.freshRequests.push(fetchRequest);
|
|
await fetchRequest;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(dispatchThrottle) {
|
|
this.throttledDispatch = this.createThrottledDispatch(dispatchThrottle);
|
|
}
|
|
})
|
|
], DataService.prototype, "dispatchThrottle", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(requestThrottle) {
|
|
this.throttledFetch = this.createThrottledFetch(requestThrottle);
|
|
}
|
|
})
|
|
], DataService.prototype, "requestThrottle", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/fonts/fontManager.ts
|
|
var FontManager = class {
|
|
constructor(domManager, updateService) {
|
|
this.domManager = domManager;
|
|
this.updateService = updateService;
|
|
this.observers = [];
|
|
}
|
|
updateFonts(fonts) {
|
|
if (!fonts || fonts.size === 0)
|
|
return;
|
|
this.loadFonts(fonts);
|
|
for (const font3 of fonts) {
|
|
this.observeFontStatus(font3);
|
|
}
|
|
}
|
|
destroy() {
|
|
for (const observer of this.observers) {
|
|
observer.disconnect();
|
|
}
|
|
this.observers = [];
|
|
}
|
|
loadFonts(fonts) {
|
|
const fontStrings = Array.from(fonts).map((font3) => encodeURIComponent(font3));
|
|
const fontStyle = ":wght@100;200;300;400;500;600;700;800;900";
|
|
const joinString = `${fontStyle}&family=`;
|
|
const css = `@import url('https://fonts.googleapis.com/css2?family=${fontStrings.join(joinString)}${fontStyle}&display=swap');
|
|
`;
|
|
this.domManager.addStyles(`google-font-${fontStrings.join("-")}`, css);
|
|
}
|
|
observeFontStatus(font3) {
|
|
const ResizeObserverCtor = getResizeObserver();
|
|
if (ResizeObserverCtor === void 0)
|
|
return;
|
|
const doc = getDocument();
|
|
if (!doc)
|
|
return;
|
|
const fontCheckElement = doc.createElement("div");
|
|
fontCheckElement.style.setProperty("position", "absolute");
|
|
fontCheckElement.style.setProperty("top", "0");
|
|
fontCheckElement.style.setProperty("margin", "0");
|
|
fontCheckElement.style.setProperty("padding", "0");
|
|
fontCheckElement.style.setProperty("overflow", "hidden");
|
|
fontCheckElement.style.setProperty("visibility", "hidden");
|
|
fontCheckElement.style.setProperty("width", "auto");
|
|
fontCheckElement.style.setProperty("max-width", "none");
|
|
fontCheckElement.style.setProperty("font-synthesis", "none");
|
|
fontCheckElement.style.setProperty("font-family", font3);
|
|
fontCheckElement.style.setProperty("font-size", "16px");
|
|
fontCheckElement.style.setProperty("white-space", "nowrap");
|
|
fontCheckElement.textContent = "UVWxyz";
|
|
this.domManager.addChild("canvas-container", `font-check-${encodeURIComponent(font3)}`, fontCheckElement);
|
|
const fontCheckObserver = new ResizeObserverCtor((entries2) => {
|
|
const width2 = entries2?.at(0)?.contentBoxSize.at(0)?.inlineSize;
|
|
if (width2 != null && width2 > 0) {
|
|
cachedTextMeasurer.clear();
|
|
this.updateService.update(5 /* PERFORM_LAYOUT */);
|
|
}
|
|
});
|
|
fontCheckObserver.observe(fontCheckElement);
|
|
this.observers.push(fontCheckObserver);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/listeners.ts
|
|
var Listeners = class {
|
|
constructor() {
|
|
this.registeredListeners = /* @__PURE__ */ new Map();
|
|
}
|
|
addListener(eventType, handler) {
|
|
const record2 = { symbol: Symbol(eventType), handler };
|
|
if (this.registeredListeners.has(eventType)) {
|
|
this.registeredListeners.get(eventType).push(record2);
|
|
} else {
|
|
this.registeredListeners.set(eventType, [record2]);
|
|
}
|
|
return () => this.removeListener(record2.symbol);
|
|
}
|
|
removeListener(eventSymbol) {
|
|
for (const [type, listeners] of this.registeredListeners.entries()) {
|
|
const matchIndex = listeners.findIndex((listener) => listener.symbol === eventSymbol);
|
|
if (matchIndex >= 0) {
|
|
listeners.splice(matchIndex, 1);
|
|
if (listeners.length === 0) {
|
|
this.registeredListeners.delete(type);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
dispatch(eventType, ...params) {
|
|
for (const listener of this.getListenersByType(eventType)) {
|
|
try {
|
|
listener.handler(...params);
|
|
} catch (e) {
|
|
logger_exports.errorOnce(e);
|
|
}
|
|
}
|
|
}
|
|
getListenersByType(eventType) {
|
|
return this.registeredListeners.get(eventType) ?? [];
|
|
}
|
|
destroy() {
|
|
this.registeredListeners.clear();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/axis/timeFormatUtil.ts
|
|
var defaultTimeFormats = {
|
|
millisecond: "%H:%M:%S.%L",
|
|
second: "%H:%M:%S",
|
|
minute: "%H:%M",
|
|
hour: "%H:%M",
|
|
day: "%e",
|
|
month: "%b",
|
|
year: "%Y"
|
|
};
|
|
var hardCodedTimeFormats = {
|
|
millisecond: "%Y %b %e %H:%M:%S.%L",
|
|
second: "%Y %b %e %H:%M:%S",
|
|
minute: "%Y %b %e %H:%M",
|
|
hour: "%Y %b %e %H:%M",
|
|
day: "%Y %b %e",
|
|
month: "%Y %b",
|
|
year: "%Y"
|
|
};
|
|
var FORMAT_ORDERS = {
|
|
year: 0,
|
|
month: 1,
|
|
day: 2,
|
|
hour: 3,
|
|
minute: 4,
|
|
second: 5,
|
|
millisecond: 6
|
|
};
|
|
var MILLISECOND_FORMAT = /%[-_0]?L/;
|
|
var SECOND_FORMAT = /%[-_0]?S/;
|
|
var MINUTE_FORMAT = /%[-_0]?M/;
|
|
var HOUR_FORMAT = /%[-_0]?[HI]/;
|
|
var DAY_FORMAT = /^%[-_0]?[de]$/;
|
|
var MONTH_FORMAT = /^%[-_0]?[Bbm]$/;
|
|
var YEAR_FORMAT = /^%[-_0]?[Yy]$/;
|
|
function deriveTimeSpecifier(format, unit, truncateDate) {
|
|
if (typeof format === "string")
|
|
return format;
|
|
format ?? (format = defaultTimeFormats);
|
|
const {
|
|
millisecond = defaultTimeFormats.millisecond,
|
|
second = defaultTimeFormats.second,
|
|
minute = defaultTimeFormats.minute,
|
|
hour = defaultTimeFormats.hour,
|
|
day = defaultTimeFormats.day,
|
|
month = defaultTimeFormats.month,
|
|
year = defaultTimeFormats.year
|
|
} = format;
|
|
const formatOrder = FORMAT_ORDERS[unit];
|
|
const hardcodedTimeFormat = hardCodedTimeFormats[unit];
|
|
const truncationOrder = truncateDate ? FORMAT_ORDERS[truncateDate] : -1;
|
|
if (truncationOrder < FORMAT_ORDERS.year && formatOrder >= FORMAT_ORDERS.year && !YEAR_FORMAT.test(year) || truncationOrder < FORMAT_ORDERS.month && formatOrder >= FORMAT_ORDERS.month && !MONTH_FORMAT.test(month) || truncationOrder < FORMAT_ORDERS.day && formatOrder >= FORMAT_ORDERS.day && !DAY_FORMAT.test(day)) {
|
|
return hardcodedTimeFormat;
|
|
}
|
|
let timeFormat;
|
|
switch (unit) {
|
|
case "year":
|
|
return year;
|
|
case "month":
|
|
return truncationOrder < FORMAT_ORDERS.year ? `${month} ${year}` : month;
|
|
case "day":
|
|
return truncationOrder < FORMAT_ORDERS.year ? `${month} ${day} ${year}` : `${month} ${day}`;
|
|
case "hour":
|
|
timeFormat = hour;
|
|
break;
|
|
case "minute":
|
|
timeFormat = minute;
|
|
break;
|
|
case "second":
|
|
timeFormat = second;
|
|
break;
|
|
case "millisecond":
|
|
timeFormat = millisecond;
|
|
break;
|
|
default:
|
|
return hardcodedTimeFormat;
|
|
}
|
|
if (formatOrder >= FORMAT_ORDERS.hour && !HOUR_FORMAT.test(timeFormat) || formatOrder >= FORMAT_ORDERS.minute && !MINUTE_FORMAT.test(timeFormat) || formatOrder >= FORMAT_ORDERS.second && !SECOND_FORMAT.test(timeFormat) || formatOrder >= FORMAT_ORDERS.millisecond && !MILLISECOND_FORMAT.test(timeFormat)) {
|
|
return hardcodedTimeFormat;
|
|
}
|
|
let dateFormat;
|
|
if (truncationOrder < FORMAT_ORDERS.year) {
|
|
dateFormat = `${month} ${day} ${year}`;
|
|
} else if (truncationOrder < FORMAT_ORDERS.month) {
|
|
dateFormat = `${month} ${day}`;
|
|
}
|
|
return dateFormat ? `${timeFormat} ${dateFormat}` : timeFormat;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/formatter/formatManager.ts
|
|
var FormatManager = class _FormatManager extends Listeners {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.formats = /* @__PURE__ */ new Map();
|
|
this.dateFormatter = simpleMemorize2(
|
|
(propertyFormatter, specifier, unit, style2, truncateDate) => {
|
|
const mergedFormatter = _FormatManager.mergeSpecifiers(propertyFormatter, specifier) ?? defaultTimeFormats;
|
|
return _FormatManager.getFormatter("date", mergedFormatter, unit, style2, { truncateDate });
|
|
}
|
|
);
|
|
this.formatter = void 0;
|
|
}
|
|
static mergeSpecifiers(...specifiers) {
|
|
let out;
|
|
for (const specifier of specifiers) {
|
|
if (isPlainObject(specifier) && isPlainObject(out)) {
|
|
out = { ...out, ...specifier };
|
|
} else {
|
|
out = specifier;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
static getFormatter(type, specifier, unit, style2 = "long", { truncateDate } = {}) {
|
|
if (isPlainObject(specifier)) {
|
|
if (type !== "date") {
|
|
logger_exports.warn("Date formatter configuration is not supported for non-date types.");
|
|
return;
|
|
}
|
|
unit ?? (unit = "millisecond");
|
|
const fullFormat = style2 === "component" ? specifier?.[unit] ?? defaultTimeFormats[unit] : deriveTimeSpecifier(specifier, unit, truncateDate);
|
|
return buildDateFormatter(fullFormat);
|
|
}
|
|
switch (type) {
|
|
case "number": {
|
|
const options = parseNumberFormat(specifier);
|
|
if (options == null)
|
|
return;
|
|
return createNumberFormatter(options);
|
|
}
|
|
case "date":
|
|
return buildDateFormatter(specifier);
|
|
case "category":
|
|
return (value) => specifier.replace("%s", String(value));
|
|
}
|
|
}
|
|
setFormatter(formatter2) {
|
|
if (this.formatter !== formatter2) {
|
|
this.formatter = formatter2;
|
|
this.formats.clear();
|
|
this.dateFormatter.reset();
|
|
this.dispatch("format-changed");
|
|
}
|
|
}
|
|
format(formatInContext, params, { specifier, truncateDate, allowNull } = {}) {
|
|
if (params.value == null && !allowNull)
|
|
return;
|
|
const { formatter: formatter2 } = this;
|
|
if (formatter2 == null)
|
|
return;
|
|
if (typeof formatter2 === "function") {
|
|
const value = formatInContext(formatter2, params);
|
|
return value == null ? void 0 : String(value);
|
|
}
|
|
const propertyFormatter = formatter2[params.property];
|
|
if (propertyFormatter == null)
|
|
return;
|
|
if (typeof propertyFormatter === "function") {
|
|
const value = formatInContext(propertyFormatter, params);
|
|
return value == null ? value : toTextString(value);
|
|
} else if (params.type === "date") {
|
|
const { unit, style: style2 } = params;
|
|
const dateFormatter = this.dateFormatter(propertyFormatter, specifier, unit, style2, truncateDate);
|
|
return dateFormatter?.(params.value);
|
|
}
|
|
const valueSpecifier = specifier ?? propertyFormatter;
|
|
if (typeof valueSpecifier !== "string")
|
|
return;
|
|
let valueFormatter = this.formats.get(valueSpecifier);
|
|
if (valueFormatter == null) {
|
|
valueFormatter = _FormatManager.getFormatter(params.type, valueSpecifier);
|
|
this.formats.set(valueSpecifier, valueFormatter);
|
|
}
|
|
return valueFormatter?.(params.value, params.type === "number" ? params.fractionDigits : void 0);
|
|
}
|
|
defaultFormat(params, { specifier, truncateDate } = {}) {
|
|
const { formatter: formatter2 } = this;
|
|
const propertyFormatter = typeof formatter2 === "function" ? void 0 : formatter2?.[params.property];
|
|
switch (params.type) {
|
|
case "date": {
|
|
const { unit, style: style2 } = params;
|
|
const propertySpecifier = propertyFormatter != null && typeof propertyFormatter !== "function" ? propertyFormatter : void 0;
|
|
const dateFormatter = this.dateFormatter(propertySpecifier, specifier, unit, style2, truncateDate);
|
|
return dateFormatter?.(params.value) ?? String(params.value);
|
|
}
|
|
case "number":
|
|
return formatValue(params.value, params.fractionDigits);
|
|
case "category":
|
|
if (params.value == null) {
|
|
return "";
|
|
} else if (Array.isArray(params.value)) {
|
|
return params.value.join(" - ");
|
|
} else if (typeof params.value === "string") {
|
|
return params.value;
|
|
} else if (typeof params.value === "number") {
|
|
return formatValue(params.value);
|
|
} else {
|
|
return String(params.value);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/chartOptionsDefs.ts
|
|
var initialStatePickedOptionsDef = {
|
|
activeItem: {
|
|
type: required(strictUnion()("series-node", "legend")),
|
|
seriesId: string,
|
|
itemId: required(or(string, positiveNumber))
|
|
}
|
|
};
|
|
initialStatePickedOptionsDef.frozen = undocumented(boolean);
|
|
var commonChartOptions = {
|
|
mode: undocumented(union("integrated", "standalone")),
|
|
container: htmlElement,
|
|
context: () => true,
|
|
theme: defined,
|
|
series: array,
|
|
annotations: defined,
|
|
navigator: defined,
|
|
scrollbar: defined,
|
|
initialState: {
|
|
active: initialStatePickedOptionsDef,
|
|
chartType: string,
|
|
annotations: defined,
|
|
legend: arrayOfDefs(
|
|
{
|
|
visible: boolean,
|
|
seriesId: string,
|
|
itemId: string,
|
|
legendItemName: string
|
|
},
|
|
"legend state array"
|
|
),
|
|
zoom: defined
|
|
}
|
|
};
|
|
var cartesianChartOptionsDefs = {
|
|
...commonChartOptionsDefs,
|
|
...commonChartOptions,
|
|
axes: object,
|
|
data: array
|
|
};
|
|
var polarChartOptionsDefs = {
|
|
...commonChartOptionsDefs,
|
|
...commonChartOptions,
|
|
axes: object,
|
|
data: array
|
|
};
|
|
var topologyChartOptionsDefs = {
|
|
...commonChartOptionsDefs,
|
|
...commonChartOptions,
|
|
data: array,
|
|
topology: geoJson
|
|
};
|
|
var standaloneChartOptionsDefs = {
|
|
...commonChartOptionsDefs,
|
|
...commonChartOptions,
|
|
data: array
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/interaction/interactionManager.ts
|
|
var InteractionState = /* @__PURE__ */ ((InteractionState8) => {
|
|
InteractionState8[InteractionState8["Default"] = 64] = "Default";
|
|
InteractionState8[InteractionState8["ZoomDrag"] = 32] = "ZoomDrag";
|
|
InteractionState8[InteractionState8["Annotations"] = 16] = "Annotations";
|
|
InteractionState8[InteractionState8["ContextMenu"] = 8] = "ContextMenu";
|
|
InteractionState8[InteractionState8["Animation"] = 4] = "Animation";
|
|
InteractionState8[InteractionState8["AnnotationsSelected"] = 2] = "AnnotationsSelected";
|
|
InteractionState8[InteractionState8["Frozen"] = 1] = "Frozen";
|
|
InteractionState8[InteractionState8["Clickable"] = 82] = "Clickable";
|
|
InteractionState8[InteractionState8["Focusable"] = 68] = "Focusable";
|
|
InteractionState8[InteractionState8["Keyable"] = 86] = "Keyable";
|
|
InteractionState8[InteractionState8["ContextMenuable"] = 72] = "ContextMenuable";
|
|
InteractionState8[InteractionState8["AnnotationsMoveable"] = 18] = "AnnotationsMoveable";
|
|
InteractionState8[InteractionState8["AnnotationsDraggable"] = 114] = "AnnotationsDraggable";
|
|
InteractionState8[InteractionState8["ZoomDraggable"] = 100] = "ZoomDraggable";
|
|
InteractionState8[InteractionState8["ZoomClickable"] = 68] = "ZoomClickable";
|
|
InteractionState8[InteractionState8["ZoomWheelable"] = 118] = "ZoomWheelable";
|
|
InteractionState8[InteractionState8["All"] = 126] = "All";
|
|
return InteractionState8;
|
|
})(InteractionState || {});
|
|
var InteractionManager = class {
|
|
constructor() {
|
|
this.stateQueue = 64 /* Default */ | 4 /* Animation */;
|
|
}
|
|
pushState(state) {
|
|
this.stateQueue |= state;
|
|
}
|
|
popState(state) {
|
|
this.stateQueue &= ~state;
|
|
}
|
|
isState(allowedStates) {
|
|
return !!(this.stateQueue & -this.stateQueue & allowedStates);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/interaction/activeManager.ts
|
|
var ActiveManager = class {
|
|
constructor(chartService, eventsHub, updateService, interactionManager, fireEvent) {
|
|
this.chartService = chartService;
|
|
this.eventsHub = eventsHub;
|
|
this.interactionManager = interactionManager;
|
|
this.fireEvent = fireEvent;
|
|
this.mementoOriginatorKey = "active";
|
|
this.updateable = true;
|
|
// FIXME: same pattern as `ZoomManager`. Perhaps an architectural rewrite is warranted.
|
|
this.didLayout = false;
|
|
this.pendingMemento = void 0;
|
|
const removeListener = updateService.addListener("pre-scene-render", () => {
|
|
this.didLayout = true;
|
|
const { pendingMemento } = this;
|
|
if (pendingMemento) {
|
|
this.restoreMemento(pendingMemento.version, pendingMemento.mementoVersion, pendingMemento.memento);
|
|
this.pendingMemento = void 0;
|
|
}
|
|
removeListener();
|
|
});
|
|
}
|
|
isFrozen() {
|
|
return this.interactionManager.isState(1 /* Frozen */);
|
|
}
|
|
clear() {
|
|
this.update(void 0, void 0);
|
|
}
|
|
update(newItemState, nodeDatum) {
|
|
this.performUpdate("user-interaction", newItemState, nodeDatum, false);
|
|
}
|
|
performUpdate(source, newItemState, nodeDatum, frozenChanged) {
|
|
if (!this.updateable)
|
|
return;
|
|
const oldItemState = this.currentItem;
|
|
this.currentItem = newItemState;
|
|
this.eventsHub.emit("active:update", newItemState);
|
|
if (frozenChanged || !objectsEqual(oldItemState, newItemState)) {
|
|
const { activeItem } = this.createMemento();
|
|
const { datum } = nodeDatum ?? {};
|
|
this.fireEvent({ type: "activeChange", source, activeItem, datum });
|
|
}
|
|
}
|
|
createMemento() {
|
|
switch (this.currentItem?.type) {
|
|
case "series-node":
|
|
case "legend": {
|
|
const { type, seriesId, itemId } = this.currentItem;
|
|
return { activeItem: { type, seriesId, itemId } };
|
|
}
|
|
default:
|
|
this.currentItem?.type;
|
|
return {};
|
|
}
|
|
}
|
|
guardMemento(blob, messages) {
|
|
if (blob == void 0)
|
|
return true;
|
|
const validationResult = validate(blob, commonChartOptions.initialState.active);
|
|
messages.push(...validationResult.invalid.map((err) => err.toString()));
|
|
return validationResult.invalid.length === 0;
|
|
}
|
|
restoreMemento(version, mementoVersion, memento) {
|
|
if (!this.didLayout) {
|
|
this.pendingMemento = { version, mementoVersion, memento };
|
|
return;
|
|
}
|
|
this.updateable = false;
|
|
const [activeItem, nodeDatum] = this.performRestoration(memento?.activeItem);
|
|
this.updateable = true;
|
|
const oldFrozen = this.isFrozen();
|
|
const newFrozen = memento?.frozen;
|
|
const frozenChanged = newFrozen === void 0 ? false : oldFrozen !== newFrozen;
|
|
if (newFrozen === true) {
|
|
this.interactionManager.pushState(1 /* Frozen */);
|
|
} else if (newFrozen === false) {
|
|
this.interactionManager.popState(1 /* Frozen */);
|
|
} else {
|
|
newFrozen;
|
|
}
|
|
this.performUpdate("state-change", activeItem, nodeDatum, frozenChanged);
|
|
}
|
|
performRestoration(activeItem) {
|
|
let rejection = false;
|
|
const reject = () => rejection = true;
|
|
let nodeDatum = void 0;
|
|
const setDatum = (d) => nodeDatum = d;
|
|
const initialState = this.pendingMemento !== void 0;
|
|
const chartId = this.chartService.id;
|
|
this.eventsHub.emit("active:load-memento", { initialState, chartId, activeItem, reject, setDatum });
|
|
return rejection ? [void 0, void 0] : [activeItem, nodeDatum];
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/interpolate.ts
|
|
function interpolateNumber(a, b) {
|
|
return (d) => Number(a) * (1 - d) + Number(b) * d;
|
|
}
|
|
function interpolateColor(a, b) {
|
|
if (typeof a === "string") {
|
|
try {
|
|
a = Color.fromString(a);
|
|
} catch {
|
|
a = Color.fromArray([0, 0, 0]);
|
|
}
|
|
}
|
|
if (typeof b === "string") {
|
|
try {
|
|
b = Color.fromString(b);
|
|
} catch {
|
|
b = Color.fromArray([0, 0, 0]);
|
|
}
|
|
}
|
|
return (d) => Color.mix(a, b, d).toRgbaString();
|
|
}
|
|
|
|
// packages/ag-charts-community/src/motion/animation.ts
|
|
var QUICK_TRANSITION = 0.2;
|
|
var PHASE_ORDER = ["initial", "remove", "update", "add", "trailing", "end", "none"];
|
|
var PHASE_METADATA = {
|
|
initial: {
|
|
animationDuration: 1,
|
|
animationDelay: 0
|
|
},
|
|
add: {
|
|
animationDuration: 0.25,
|
|
animationDelay: 0.75
|
|
},
|
|
remove: {
|
|
animationDuration: 0.25,
|
|
animationDelay: 0
|
|
},
|
|
update: {
|
|
animationDuration: 0.5,
|
|
animationDelay: 0.25
|
|
},
|
|
trailing: {
|
|
animationDuration: QUICK_TRANSITION,
|
|
animationDelay: 1,
|
|
skipIfNoEarlierAnimations: true
|
|
},
|
|
end: {
|
|
animationDelay: 1 + QUICK_TRANSITION,
|
|
animationDuration: 0,
|
|
skipIfNoEarlierAnimations: true
|
|
},
|
|
none: {
|
|
animationDuration: 0,
|
|
animationDelay: 0
|
|
}
|
|
};
|
|
function isNodeArray(array2) {
|
|
return array2.every((n) => n instanceof Node2);
|
|
}
|
|
function deconstructSelectionsOrNodes(selectionsOrNodes) {
|
|
return isNodeArray(selectionsOrNodes) ? { nodes: selectionsOrNodes, selections: [] } : { nodes: [], selections: selectionsOrNodes };
|
|
}
|
|
function animationValuesEqual(a, b) {
|
|
if (a === b) {
|
|
return true;
|
|
} else if (Array.isArray(a) && Array.isArray(b)) {
|
|
return a.length === b.length && a.every((v, i) => animationValuesEqual(v, b[i]));
|
|
} else if (isInterpolating(a) && isInterpolating(b)) {
|
|
return a.equals(b);
|
|
} else if (isPlainObject(a) && isPlainObject(b)) {
|
|
return objectsEqualWith(a, b, animationValuesEqual);
|
|
}
|
|
return false;
|
|
}
|
|
var Animation = class {
|
|
constructor(opts) {
|
|
this.isComplete = false;
|
|
this.elapsed = 0;
|
|
this.iteration = 0;
|
|
this.isPlaying = false;
|
|
this.isReverse = false;
|
|
this.id = opts.id;
|
|
this.groupId = opts.groupId;
|
|
this.autoplay = opts.autoplay ?? true;
|
|
this.ease = opts.ease ?? linear;
|
|
this.phase = opts.phase;
|
|
const durationProportion = opts.duration ?? PHASE_METADATA[this.phase].animationDuration;
|
|
this.duration = durationProportion * opts.defaultDuration;
|
|
this.delay = (opts.delay ?? 0) * opts.defaultDuration;
|
|
this.onComplete = opts.onComplete;
|
|
this.onPlay = opts.onPlay;
|
|
this.onStop = opts.onStop;
|
|
this.onUpdate = opts.onUpdate;
|
|
this.interpolate = this.createInterpolator(opts.from, opts.to);
|
|
this.from = opts.from;
|
|
if (opts.skip === true) {
|
|
this.onUpdate?.(opts.to, false, this);
|
|
this.onStop?.(this);
|
|
this.onComplete?.(this);
|
|
this.isComplete = true;
|
|
}
|
|
if (opts.collapsable !== false) {
|
|
this.duration = this.checkCollapse(opts, this.duration);
|
|
}
|
|
}
|
|
checkCollapse(opts, calculatedDuration) {
|
|
return animationValuesEqual(opts.from, opts.to) ? 0 : calculatedDuration;
|
|
}
|
|
play(initialUpdate = false) {
|
|
if (this.isPlaying || this.isComplete)
|
|
return;
|
|
this.isPlaying = true;
|
|
this.onPlay?.(this);
|
|
if (!this.autoplay)
|
|
return;
|
|
this.autoplay = false;
|
|
if (!initialUpdate)
|
|
return;
|
|
this.onUpdate?.(this.from, true, this);
|
|
}
|
|
stop() {
|
|
this.isPlaying = false;
|
|
if (!this.isComplete) {
|
|
this.isComplete = true;
|
|
this.onStop?.(this);
|
|
}
|
|
}
|
|
update(time3) {
|
|
if (this.isComplete)
|
|
return time3;
|
|
if (!this.isPlaying && this.autoplay) {
|
|
this.play(true);
|
|
}
|
|
const previousElapsed = this.elapsed;
|
|
this.elapsed += time3;
|
|
if (this.delay > this.elapsed)
|
|
return 0;
|
|
const value = this.interpolate(this.isReverse ? 1 - this.delta : this.delta);
|
|
this.onUpdate?.(value, false, this);
|
|
const totalDuration = this.delay + this.duration;
|
|
if (this.elapsed >= totalDuration) {
|
|
this.stop();
|
|
this.isComplete = true;
|
|
this.onComplete?.(this);
|
|
return time3 - (totalDuration - previousElapsed);
|
|
}
|
|
return 0;
|
|
}
|
|
get delta() {
|
|
return this.ease(clamp(0, (this.elapsed - this.delay) / this.duration, 1));
|
|
}
|
|
createInterpolator(from3, to) {
|
|
if (typeof to !== "object" || isInterpolating(to)) {
|
|
return this.interpolateValue(from3, to);
|
|
} else if (Array.isArray(to)) {
|
|
const interpolatorValues = [];
|
|
for (let i = 0; i < to.length; i++) {
|
|
const interpolator = this.createInterpolator(from3[i], to[i]);
|
|
if (interpolator != null) {
|
|
interpolatorValues.push(interpolator);
|
|
}
|
|
}
|
|
return (d) => {
|
|
const out = [];
|
|
for (const interpolator of interpolatorValues) {
|
|
out.push(interpolator(d));
|
|
}
|
|
return out;
|
|
};
|
|
}
|
|
const interpolatorEntries = [];
|
|
for (const key of Object.keys(to)) {
|
|
const interpolator = this.createInterpolator(from3[key], to[key]);
|
|
if (interpolator != null) {
|
|
interpolatorEntries.push([key, interpolator]);
|
|
}
|
|
}
|
|
return (d) => {
|
|
const result = {};
|
|
for (const [key, interpolator] of interpolatorEntries) {
|
|
result[key] = interpolator(d);
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
interpolateValue(a, b) {
|
|
if (a == null || b == null) {
|
|
return;
|
|
} else if (isInterpolating(a)) {
|
|
return (d) => a[interpolate](b, d);
|
|
}
|
|
try {
|
|
switch (typeof a) {
|
|
case "number":
|
|
return interpolateNumber(a, b);
|
|
case "string":
|
|
return interpolateColor(a, b);
|
|
case "boolean":
|
|
if (a === b) {
|
|
return () => a;
|
|
}
|
|
break;
|
|
case "object":
|
|
return () => a;
|
|
default:
|
|
throw new Error(`Unable to interpolate values: ${a}, ${b}`);
|
|
}
|
|
} catch {
|
|
}
|
|
throw new Error(`Unable to interpolate values: ${a}, ${b}`);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/data-model/utils/bandedStructure.ts
|
|
function adjustBandForInsertion(band, insertIndex, insertCount, isLastBand) {
|
|
if (insertIndex < band.startIndex) {
|
|
band.startIndex += insertCount;
|
|
band.endIndex += insertCount;
|
|
return false;
|
|
} else if (insertIndex < band.endIndex || insertIndex === band.endIndex && isLastBand) {
|
|
band.endIndex += insertCount;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function adjustBandForRemoval(band, removeIndex, removeCount) {
|
|
const removeEnd = removeIndex + removeCount;
|
|
if (removeEnd <= band.startIndex) {
|
|
band.startIndex = Math.max(0, band.startIndex - removeCount);
|
|
band.endIndex = Math.max(band.startIndex, band.endIndex - removeCount);
|
|
return false;
|
|
} else if (removeIndex >= band.endIndex) {
|
|
return false;
|
|
} else {
|
|
if (removeIndex <= band.startIndex && removeEnd >= band.endIndex) {
|
|
band.startIndex = removeIndex;
|
|
band.endIndex = removeIndex;
|
|
} else if (removeIndex <= band.startIndex) {
|
|
const deletedFromBand = removeEnd - band.startIndex;
|
|
const oldBandSize = band.endIndex - band.startIndex;
|
|
band.startIndex = removeIndex;
|
|
band.endIndex = band.startIndex + Math.max(0, oldBandSize - deletedFromBand);
|
|
} else if (removeEnd >= band.endIndex) {
|
|
band.endIndex = Math.max(band.startIndex, removeIndex);
|
|
} else {
|
|
band.endIndex = Math.max(band.startIndex, band.endIndex - removeCount);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
function calculateTargetBandCount(dataSize, minBandCount) {
|
|
const derivedCount = Math.ceil(dataSize / 1e3);
|
|
return Math.max(minBandCount, derivedCount);
|
|
}
|
|
function calculateIdealBandSize(dataSize, targetBandCount) {
|
|
return Math.max(1, Math.ceil(dataSize / targetBandCount));
|
|
}
|
|
function filterEmptyBands(bands) {
|
|
return bands.filter((band) => band.endIndex > band.startIndex);
|
|
}
|
|
function initializeBandArray(dataSize, config, bandFactory) {
|
|
if (!config.enableBanding || dataSize < config.minDataSizeForBanding) {
|
|
return [bandFactory(0, dataSize)];
|
|
}
|
|
const targetBandCount = calculateTargetBandCount(dataSize, config.targetBandCount);
|
|
const bandSize = calculateIdealBandSize(dataSize, targetBandCount);
|
|
const bands = [];
|
|
for (let startIndex = 0; startIndex < dataSize; startIndex += bandSize) {
|
|
const endIndex = Math.min(startIndex + bandSize, dataSize);
|
|
bands.push(bandFactory(startIndex, endIndex));
|
|
}
|
|
return bands;
|
|
}
|
|
function applySpliceOperations(bandHandler, spliceOps) {
|
|
for (const op of spliceOps) {
|
|
if (op.insertCount > 0) {
|
|
bandHandler.handleInsertion(op.index, op.insertCount);
|
|
}
|
|
if (op.deleteCount > 0) {
|
|
bandHandler.handleRemoval(op.index, op.deleteCount);
|
|
}
|
|
}
|
|
}
|
|
function markUpdatedIndices(bandHandler, updatedIndices) {
|
|
for (const index of updatedIndices) {
|
|
bandHandler.handleInsertion(index, 0);
|
|
}
|
|
}
|
|
function applyIndexMapToBandHandler(bandHandler, indexMap) {
|
|
applySpliceOperations(bandHandler, indexMap.spliceOps);
|
|
if (indexMap.updatedIndices.size > 0) {
|
|
markUpdatedIndices(bandHandler, indexMap.updatedIndices);
|
|
}
|
|
}
|
|
var DEFAULT_MIN_DATA_SIZE_FOR_BANDING = 1e3;
|
|
var DEFAULT_TARGET_BAND_COUNT = 10;
|
|
var BandedStructure = class {
|
|
constructor(config = {}) {
|
|
this.bands = [];
|
|
this.dataSize = 0;
|
|
this.config = {
|
|
minDataSizeForBanding: config.minDataSizeForBanding ?? DEFAULT_MIN_DATA_SIZE_FOR_BANDING,
|
|
targetBandCount: config.targetBandCount ?? DEFAULT_TARGET_BAND_COUNT,
|
|
maxBandSize: config.maxBandSize ?? Infinity,
|
|
enableBanding: config.enableBanding ?? true
|
|
};
|
|
}
|
|
applyIndexMap(indexMap) {
|
|
applyIndexMapToBandHandler(this, indexMap);
|
|
}
|
|
/**
|
|
* Initializes or rebalances bands based on current data size.
|
|
*/
|
|
initializeBands(dataSize) {
|
|
this.dataSize = Math.max(0, dataSize);
|
|
this.bands = initializeBandArray(
|
|
this.dataSize,
|
|
this.config,
|
|
(startIndex, endIndex) => this.createBand(startIndex, endIndex)
|
|
);
|
|
}
|
|
/**
|
|
* Returns the number of bands currently in this structure.
|
|
* Useful for checking if bands need initialization.
|
|
*/
|
|
getBandCount() {
|
|
return this.bands.length;
|
|
}
|
|
/**
|
|
* Handles insertion of new data by adjusting band indices.
|
|
* Uses proactive band splitting to maintain optimal band sizes.
|
|
*/
|
|
handleInsertion(insertIndex, insertCount) {
|
|
this.dataSize += insertCount;
|
|
if (this.bands.length === 0) {
|
|
this.initializeBands(this.dataSize);
|
|
return;
|
|
}
|
|
const targetBandCount = calculateTargetBandCount(this.dataSize, this.config.targetBandCount);
|
|
const idealBandSize = calculateIdealBandSize(this.dataSize, targetBandCount);
|
|
const maxBandSize = Math.ceil(idealBandSize * 1.1);
|
|
for (let i = 0; i < this.bands.length; i++) {
|
|
const band = this.bands[i];
|
|
const isLastBand = i === this.bands.length - 1;
|
|
if (insertIndex === band.endIndex && isLastBand && insertCount > 0) {
|
|
const currentBandSize = band.endIndex - band.startIndex;
|
|
if (currentBandSize >= idealBandSize) {
|
|
this.bands.push(this.createBand(insertIndex, insertIndex + insertCount));
|
|
} else {
|
|
band.endIndex += insertCount;
|
|
band.isDirty = true;
|
|
}
|
|
break;
|
|
}
|
|
const wasDirty = adjustBandForInsertion(band, insertIndex, insertCount, isLastBand);
|
|
if (wasDirty) {
|
|
band.isDirty = true;
|
|
if (insertCount > 0 && insertIndex < band.endIndex) {
|
|
const bandSize = band.endIndex - band.startIndex;
|
|
if (bandSize > maxBandSize) {
|
|
this.splitBand(i, idealBandSize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Handles removal of data by adjusting band indices.
|
|
* Uses shared utilities for consistent band manipulation.
|
|
*/
|
|
handleRemoval(removeIndex, removeCount) {
|
|
if (removeCount <= 0 || this.bands.length === 0)
|
|
return;
|
|
const effectiveRemoveCount = Math.min(removeCount, Math.max(0, this.dataSize - removeIndex));
|
|
if (effectiveRemoveCount <= 0)
|
|
return;
|
|
this.dataSize = Math.max(0, this.dataSize - effectiveRemoveCount);
|
|
for (const band of this.bands) {
|
|
const wasDirty = adjustBandForRemoval(band, removeIndex, effectiveRemoveCount);
|
|
if (wasDirty) {
|
|
band.isDirty = true;
|
|
}
|
|
}
|
|
this.bands = filterEmptyBands(this.bands);
|
|
}
|
|
/**
|
|
* Split an oversized band into two smaller bands.
|
|
* Called when a band exceeds maxBandSize during insertion.
|
|
*
|
|
* Strategy:
|
|
* - Split the band as evenly as possible
|
|
* - Both halves marked as dirty (need recalculation)
|
|
* - No cache preservation (splitting indicates data changed)
|
|
*/
|
|
splitBand(bandIndex, idealSize) {
|
|
const band = this.bands[bandIndex];
|
|
const bandSize = band.endIndex - band.startIndex;
|
|
const firstHalfSize = Math.min(idealSize, Math.floor(bandSize / 2));
|
|
const splitPoint = band.startIndex + firstHalfSize;
|
|
const band1 = this.createBand(band.startIndex, splitPoint);
|
|
const band2 = this.createBand(splitPoint, band.endIndex);
|
|
this.bands.splice(bandIndex, 1, band1, band2);
|
|
}
|
|
/**
|
|
* Returns statistics about the banded structure for debugging.
|
|
* Subclasses can override to add domain-specific stats.
|
|
*/
|
|
getStats() {
|
|
const dirtyBands = this.bands.filter((band) => band.isDirty);
|
|
return {
|
|
totalBands: this.bands.length,
|
|
dirtyBands: dirtyBands.length,
|
|
dataSize: this.dataSize
|
|
};
|
|
}
|
|
markRangeDirty(startIndex, endIndex) {
|
|
for (const band of this.bands) {
|
|
if (startIndex < band.endIndex && endIndex > band.startIndex) {
|
|
band.isDirty = true;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/dataDomain.ts
|
|
var DiscreteDomain = class _DiscreteDomain {
|
|
constructor() {
|
|
// Set-based storage (default mode)
|
|
this.domain = /* @__PURE__ */ new Set();
|
|
this.dateTimestamps = /* @__PURE__ */ new Set();
|
|
this.hasDateValues = false;
|
|
// Sorted array storage (optimized mode for sorted unique data)
|
|
this.sortedValues = null;
|
|
this.sortOrder = void 0;
|
|
this.isSortedUnique = false;
|
|
}
|
|
static is(value) {
|
|
return value instanceof _DiscreteDomain;
|
|
}
|
|
/**
|
|
* Configure domain for sorted unique mode.
|
|
* When enabled, uses a single array for O(1) append.
|
|
* Call this before extending with data.
|
|
*/
|
|
setSortedUniqueMode(sortOrder, isUnique) {
|
|
if (isUnique) {
|
|
this.isSortedUnique = true;
|
|
this.sortOrder = sortOrder;
|
|
this.sortedValues = [];
|
|
}
|
|
}
|
|
extend(val) {
|
|
if (this.isSortedUnique && this.sortedValues) {
|
|
this.sortedValues.push(val);
|
|
if (val instanceof Date) {
|
|
this.hasDateValues = true;
|
|
}
|
|
} else if (val instanceof Date) {
|
|
this.hasDateValues = true;
|
|
this.dateTimestamps.add(val.valueOf());
|
|
} else {
|
|
this.domain.add(val);
|
|
}
|
|
}
|
|
getDomain() {
|
|
if (this.isSortedUnique && this.sortedValues) {
|
|
let hasSeenInvalid = false;
|
|
return this.sortedValues.filter((v) => {
|
|
if (v == null) {
|
|
if (hasSeenInvalid)
|
|
return false;
|
|
hasSeenInvalid = true;
|
|
return true;
|
|
}
|
|
if (v instanceof Date && Number.isNaN(v.valueOf())) {
|
|
if (hasSeenInvalid)
|
|
return false;
|
|
hasSeenInvalid = true;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
if (this.hasDateValues) {
|
|
const dates = Array.from(this.dateTimestamps, (ts) => new Date(ts));
|
|
if (this.domain.size > 0) {
|
|
return [...dates, ...Array.from(this.domain)];
|
|
}
|
|
return dates;
|
|
}
|
|
return Array.from(this.domain);
|
|
}
|
|
/** Returns true if this domain contains Date values stored as timestamps */
|
|
isDateDomain() {
|
|
return this.hasDateValues;
|
|
}
|
|
/** Returns true if this domain is in sorted unique mode */
|
|
isSortedUniqueMode() {
|
|
return this.isSortedUnique;
|
|
}
|
|
/** Returns the sort order if in sorted mode, undefined otherwise */
|
|
getSortOrder() {
|
|
return this.sortOrder;
|
|
}
|
|
/** Merges another DiscreteDomain's values into this one */
|
|
mergeFrom(other) {
|
|
if (this.isSortedUnique && other.isSortedUnique && this.sortOrder === other.sortOrder && this.sortOrder !== void 0 && other.sortedValues) {
|
|
if (other.hasDateValues) {
|
|
this.hasDateValues = true;
|
|
}
|
|
this.sortedValues ?? (this.sortedValues = []);
|
|
this.sortedValues.push(...other.sortedValues);
|
|
return;
|
|
}
|
|
this.convertToSetMode();
|
|
if (other.hasDateValues) {
|
|
this.hasDateValues = true;
|
|
}
|
|
if (other.isSortedUnique && other.sortedValues) {
|
|
for (const val of other.sortedValues) {
|
|
if (val instanceof Date) {
|
|
this.dateTimestamps.add(val.valueOf());
|
|
} else {
|
|
this.domain.add(val);
|
|
}
|
|
}
|
|
} else {
|
|
for (const ts of other.dateTimestamps) {
|
|
this.dateTimestamps.add(ts);
|
|
}
|
|
for (const val of other.domain) {
|
|
this.domain.add(val);
|
|
}
|
|
}
|
|
}
|
|
/** Converts from sorted array mode to Set mode (one-way transition) */
|
|
convertToSetMode() {
|
|
if (!this.isSortedUnique)
|
|
return;
|
|
if (this.sortedValues) {
|
|
for (const val of this.sortedValues) {
|
|
if (val instanceof Date) {
|
|
this.dateTimestamps.add(val.valueOf());
|
|
} else {
|
|
this.domain.add(val);
|
|
}
|
|
}
|
|
this.sortedValues = null;
|
|
}
|
|
this.isSortedUnique = false;
|
|
this.sortOrder = void 0;
|
|
}
|
|
};
|
|
var ContinuousDomain = class _ContinuousDomain {
|
|
constructor() {
|
|
this.domain = [Infinity, -Infinity];
|
|
}
|
|
static is(value) {
|
|
return value instanceof _ContinuousDomain;
|
|
}
|
|
static extendDomain(values, domain = [Infinity, -Infinity]) {
|
|
for (const value of values) {
|
|
if (typeof value !== "number") {
|
|
continue;
|
|
}
|
|
if (domain[0] > value) {
|
|
domain[0] = value;
|
|
}
|
|
if (domain[1] < value) {
|
|
domain[1] = value;
|
|
}
|
|
}
|
|
return domain;
|
|
}
|
|
extend(value) {
|
|
if (typeof value !== "number" && !(value instanceof Date)) {
|
|
return;
|
|
}
|
|
if (this.domain[0] > value) {
|
|
this.domain[0] = value;
|
|
}
|
|
if (this.domain[1] < value) {
|
|
this.domain[1] = value;
|
|
}
|
|
}
|
|
getDomain() {
|
|
return [...this.domain];
|
|
}
|
|
};
|
|
var BandedDomain = class extends BandedStructure {
|
|
constructor(domainFactory, config = {}, isDiscrete = false) {
|
|
super(config);
|
|
this.fullDomainCache = null;
|
|
// Sort order metadata for optimization (set from KEY_SORT_ORDERS)
|
|
this.sortOrder = void 0;
|
|
this.isUnique = false;
|
|
this.domainFactory = domainFactory;
|
|
this.isDiscrete = isDiscrete;
|
|
}
|
|
/**
|
|
* Set sort order metadata from KEY_SORT_ORDERS.
|
|
* When data is sorted and unique, enables fast array concatenation in getDomain().
|
|
*/
|
|
setSortOrderMetadata(sortOrder, isUnique) {
|
|
this.sortOrder = sortOrder;
|
|
this.isUnique = isUnique;
|
|
}
|
|
/**
|
|
* Creates a new domain band with its own sub-domain instance.
|
|
* Configures sub-domain for sorted mode if applicable.
|
|
*/
|
|
createBand(startIndex, endIndex) {
|
|
const subDomain = this.domainFactory();
|
|
if (this.isDiscrete && this.sortOrder !== void 0 && this.isUnique) {
|
|
if (DiscreteDomain.is(subDomain)) {
|
|
subDomain.setSortedUniqueMode(this.sortOrder, this.isUnique);
|
|
}
|
|
}
|
|
return {
|
|
startIndex,
|
|
endIndex,
|
|
subDomain,
|
|
isDirty: true
|
|
};
|
|
}
|
|
/**
|
|
* Initializes bands and clears the full domain cache.
|
|
*/
|
|
initializeBands(dataSize) {
|
|
super.initializeBands(dataSize);
|
|
this.fullDomainCache = null;
|
|
}
|
|
/**
|
|
* Handles insertion and clears the full domain cache.
|
|
*/
|
|
handleInsertion(insertIndex, insertCount) {
|
|
super.handleInsertion(insertIndex, insertCount);
|
|
this.fullDomainCache = null;
|
|
}
|
|
/**
|
|
* Handles removal and clears the full domain cache.
|
|
*/
|
|
handleRemoval(removeIndex, removeCount) {
|
|
super.handleRemoval(removeIndex, removeCount);
|
|
this.fullDomainCache = null;
|
|
}
|
|
/**
|
|
* Split an oversized band into two smaller bands.
|
|
* Override to handle band splitting for large datasets where banding is beneficial.
|
|
*/
|
|
splitBand(bandIndex, idealSize) {
|
|
if (this.bands.length > 1) {
|
|
super.splitBand(bandIndex, idealSize);
|
|
}
|
|
}
|
|
/**
|
|
* Marks bands as dirty that need rescanning.
|
|
*/
|
|
markBandsDirty(startIndex, endIndex) {
|
|
this.markRangeDirty(startIndex, endIndex);
|
|
this.fullDomainCache = null;
|
|
}
|
|
/**
|
|
* Marks all bands as dirty, forcing a full rescan.
|
|
*/
|
|
markAllBandsDirty() {
|
|
for (const band of this.bands) {
|
|
band.isDirty = true;
|
|
}
|
|
this.fullDomainCache = null;
|
|
}
|
|
/**
|
|
* Extends the domain with values from specified bands.
|
|
* This is called after dirty bands have been rescanned.
|
|
*/
|
|
extendBandsFromData(data, invalidData) {
|
|
const dataLength = data.length;
|
|
for (const band of this.bands) {
|
|
if (!band.isDirty)
|
|
continue;
|
|
const subDomain = this.domainFactory();
|
|
if (this.isDiscrete && this.sortOrder !== void 0 && this.isUnique) {
|
|
if (DiscreteDomain.is(subDomain)) {
|
|
subDomain.setSortedUniqueMode(this.sortOrder, this.isUnique);
|
|
}
|
|
}
|
|
band.subDomain = subDomain;
|
|
const { startIndex, endIndex } = band;
|
|
for (let i = startIndex; i < endIndex && i < dataLength; i++) {
|
|
if (invalidData?.[i])
|
|
continue;
|
|
band.subDomain.extend(data[i]);
|
|
}
|
|
band.isDirty = false;
|
|
}
|
|
this.fullDomainCache = null;
|
|
}
|
|
/**
|
|
* Gets the bands that need rescanning.
|
|
*/
|
|
getDirtyBands() {
|
|
return this.bands.filter((band) => band.isDirty);
|
|
}
|
|
/**
|
|
* Standard IDataDomain interface - extends domain with a single value.
|
|
* Note: This is less efficient than batch operations with bands.
|
|
*/
|
|
extend(_value) {
|
|
this.markAllBandsDirty();
|
|
this.fullDomainCache = null;
|
|
}
|
|
/**
|
|
* Check if all sub-domains support fast sorted concatenation.
|
|
*/
|
|
canUseSortedConcatenation() {
|
|
if (!this.sortOrder || !this.isUnique || !this.isDiscrete)
|
|
return false;
|
|
for (const band of this.bands) {
|
|
if (!DiscreteDomain.is(band.subDomain))
|
|
return false;
|
|
if (!band.subDomain.isSortedUniqueMode())
|
|
return false;
|
|
if (band.subDomain.getSortOrder() !== this.sortOrder)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
/**
|
|
* Concatenate sorted domains efficiently.
|
|
* Only valid when canUseSortedConcatenation() returns true.
|
|
*/
|
|
concatenateSortedDomains() {
|
|
const combined = new DiscreteDomain();
|
|
combined.setSortedUniqueMode(this.sortOrder, this.isUnique);
|
|
for (const band of this.bands) {
|
|
if (DiscreteDomain.is(band.subDomain)) {
|
|
combined.mergeFrom(band.subDomain);
|
|
}
|
|
}
|
|
return combined.getDomain();
|
|
}
|
|
/**
|
|
* Deduplicate nulls and Invalid Dates in a domain result array.
|
|
* These represent invalid data and may appear from multiple bands.
|
|
*/
|
|
deduplicateNulls(result) {
|
|
let hasSeenInvalid = false;
|
|
return result.filter((v) => {
|
|
if (v == null) {
|
|
if (hasSeenInvalid)
|
|
return false;
|
|
hasSeenInvalid = true;
|
|
return true;
|
|
}
|
|
if (v instanceof Date && Number.isNaN(v.valueOf())) {
|
|
if (hasSeenInvalid)
|
|
return false;
|
|
hasSeenInvalid = true;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
/**
|
|
* Combines all band sub-domains to get the overall domain.
|
|
*/
|
|
getDomain() {
|
|
if (this.fullDomainCache !== null) {
|
|
return this.fullDomainCache;
|
|
}
|
|
if (this.bands.length === 0) {
|
|
this.fullDomainCache = [];
|
|
return [];
|
|
}
|
|
if (this.bands.length === 1) {
|
|
const result = this.bands[0].subDomain.getDomain();
|
|
this.fullDomainCache = this.isDiscrete ? this.deduplicateNulls(result) : result;
|
|
return this.fullDomainCache;
|
|
}
|
|
if (this.isDiscrete) {
|
|
const firstBand = this.bands[0].subDomain;
|
|
if (DiscreteDomain.is(firstBand)) {
|
|
if (this.canUseSortedConcatenation()) {
|
|
this.fullDomainCache = this.deduplicateNulls(this.concatenateSortedDomains());
|
|
} else {
|
|
const combined = new DiscreteDomain();
|
|
for (const band of this.bands) {
|
|
if (DiscreteDomain.is(band.subDomain)) {
|
|
combined.mergeFrom(band.subDomain);
|
|
}
|
|
}
|
|
this.fullDomainCache = this.deduplicateNulls(combined.getDomain());
|
|
}
|
|
} else {
|
|
const combined = /* @__PURE__ */ new Set();
|
|
for (const band of this.bands) {
|
|
for (const value of band.subDomain.getDomain()) {
|
|
combined.add(value);
|
|
}
|
|
}
|
|
this.fullDomainCache = Array.from(combined);
|
|
}
|
|
} else {
|
|
let min;
|
|
let max;
|
|
for (const band of this.bands) {
|
|
const bandDomain = band.subDomain.getDomain();
|
|
if (bandDomain.length === 2) {
|
|
const [bandMin, bandMax] = bandDomain;
|
|
if (min === void 0 || bandMin != null && min != null && bandMin < min) {
|
|
min = bandMin;
|
|
}
|
|
if (max === void 0 || bandMax != null && max != null && bandMax > max) {
|
|
max = bandMax;
|
|
}
|
|
}
|
|
}
|
|
if (min !== void 0 && max !== void 0) {
|
|
this.fullDomainCache = [min, max];
|
|
} else {
|
|
this.fullDomainCache = [];
|
|
}
|
|
}
|
|
return this.fullDomainCache;
|
|
}
|
|
/**
|
|
* Returns statistics about the banded domain for debugging.
|
|
*/
|
|
getStats() {
|
|
const dirtyCount = this.bands.filter((b) => b.isDirty).length;
|
|
const totalSize = this.bands.reduce((sum, b) => sum + (b.endIndex - b.startIndex), 0);
|
|
return {
|
|
bandCount: this.bands.length,
|
|
dirtyBandCount: dirtyCount,
|
|
averageBandSize: this.bands.length > 0 ? totalSize / this.bands.length : 0,
|
|
dataSize: this.dataSize
|
|
};
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/aggregateFunctions.ts
|
|
function sumValues(values, accumulator = [0, 0]) {
|
|
for (const value of values) {
|
|
if (typeof value !== "number") {
|
|
continue;
|
|
}
|
|
if (value < 0) {
|
|
accumulator[0] += value;
|
|
}
|
|
if (value > 0) {
|
|
accumulator[1] += value;
|
|
}
|
|
}
|
|
return accumulator;
|
|
}
|
|
function groupSum(id, opts) {
|
|
const visible = opts?.visible ?? true;
|
|
return {
|
|
id,
|
|
type: "aggregate",
|
|
matchGroupIds: opts?.matchGroupId ? [opts?.matchGroupId] : void 0,
|
|
aggregateFunction: (values) => sumValues(values),
|
|
groupAggregateFunction: (next, acc = [0, 0]) => {
|
|
if (visible) {
|
|
acc[0] += next?.[0] ?? 0;
|
|
acc[1] += next?.[1] ?? 0;
|
|
}
|
|
return acc;
|
|
}
|
|
};
|
|
}
|
|
function range2(id, matchGroupId) {
|
|
const result = {
|
|
id,
|
|
matchGroupIds: [matchGroupId],
|
|
type: "aggregate",
|
|
aggregateFunction: (values) => ContinuousDomain.extendDomain(values)
|
|
};
|
|
return result;
|
|
}
|
|
function groupCount(id, opts) {
|
|
const visible = opts?.visible ?? true;
|
|
return {
|
|
id,
|
|
type: "aggregate",
|
|
aggregateFunction: () => [0, 1],
|
|
groupAggregateFunction: (next, acc = [0, 0]) => {
|
|
if (visible) {
|
|
acc[0] += next?.[0] ?? 0;
|
|
acc[1] += next?.[1] ?? 0;
|
|
}
|
|
return acc;
|
|
}
|
|
};
|
|
}
|
|
function groupAverage(id, opts) {
|
|
const visible = opts?.visible ?? true;
|
|
const def = {
|
|
id,
|
|
matchGroupIds: opts?.matchGroupId ? [opts?.matchGroupId] : void 0,
|
|
type: "aggregate",
|
|
aggregateFunction: (values) => sumValues(values),
|
|
groupAggregateFunction: (next, acc = [0, 0, -1]) => {
|
|
if (visible) {
|
|
acc[0] += next?.[0] ?? 0;
|
|
acc[2]++;
|
|
acc[1] += next?.[1] ?? 0;
|
|
}
|
|
return acc;
|
|
},
|
|
finalFunction: (acc = [0, 0, 0]) => {
|
|
const result = acc[0] + acc[1];
|
|
if (result >= 0) {
|
|
return [0, result / acc[2]];
|
|
}
|
|
return [result / acc[2], 0];
|
|
}
|
|
};
|
|
return def;
|
|
}
|
|
function area(id, aggFn, matchGroupId) {
|
|
const result = {
|
|
id,
|
|
matchGroupIds: matchGroupId ? [matchGroupId] : void 0,
|
|
type: "aggregate",
|
|
aggregateFunction: (values, keyRange = []) => {
|
|
const keyWidth = keyRange[1] - keyRange[0];
|
|
return aggFn.aggregateFunction(values).map((v) => v / keyWidth);
|
|
}
|
|
};
|
|
if (aggFn.groupAggregateFunction) {
|
|
result.groupAggregateFunction = aggFn.groupAggregateFunction;
|
|
}
|
|
return result;
|
|
}
|
|
function accumulatedValue(onlyPositive) {
|
|
return () => {
|
|
let value = 0;
|
|
return (datum) => {
|
|
if (!isFiniteNumber(datum)) {
|
|
return datum;
|
|
}
|
|
value += onlyPositive ? Math.max(0, datum) : datum;
|
|
return value;
|
|
};
|
|
};
|
|
}
|
|
function trailingAccumulatedValue() {
|
|
return () => {
|
|
let value = 0;
|
|
return (datum) => {
|
|
if (!isFiniteNumber(datum)) {
|
|
return datum;
|
|
}
|
|
const trailingValue = value;
|
|
value += datum;
|
|
return trailingValue;
|
|
};
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/data/data-model/utils/helpers.ts
|
|
var NULL_KEY_STRING = "\0__AG_NULL__\0";
|
|
var UNDEFINED_KEY_STRING = "\0__AG_UNDEFINED__\0";
|
|
function keyToString(key) {
|
|
if (key === null)
|
|
return NULL_KEY_STRING;
|
|
if (key === void 0)
|
|
return UNDEFINED_KEY_STRING;
|
|
if (Array.isArray(key)) {
|
|
return "[" + key.map(keyToString).join(",") + "]";
|
|
}
|
|
return isObject(key) ? JSON.stringify(key) : String(key);
|
|
}
|
|
function toKeyString(keys) {
|
|
return keys.map(keyToString).join("-");
|
|
}
|
|
function fixNumericExtent(extent2) {
|
|
const numberExtent = extent2?.map(Number);
|
|
return numberExtent?.every(Number.isFinite) ? numberExtent : [];
|
|
}
|
|
function getMissCount(scopeProvider, missMap) {
|
|
return missMap?.get(scopeProvider.id) ?? 0;
|
|
}
|
|
function isScoped(obj) {
|
|
return "scopes" in obj && Array.isArray(obj.scopes);
|
|
}
|
|
function createArray(length2, value) {
|
|
const out = [];
|
|
for (let i = 0; i < length2; i += 1) {
|
|
out[i] = value;
|
|
}
|
|
return out;
|
|
}
|
|
function uniqueChangeDescriptions(scopeChanges) {
|
|
const deduped = /* @__PURE__ */ new Set();
|
|
for (const changeDesc of scopeChanges.values()) {
|
|
if (changeDesc) {
|
|
deduped.add(changeDesc);
|
|
}
|
|
}
|
|
return deduped;
|
|
}
|
|
function datumKeys(keys, datumIndex, allowNull = false) {
|
|
const out = [];
|
|
for (const k of keys) {
|
|
const key = k?.[datumIndex];
|
|
if (key == null && !allowNull)
|
|
return;
|
|
out.push(key);
|
|
}
|
|
return out;
|
|
}
|
|
function getPathComponents(path) {
|
|
const components = [];
|
|
let matchIndex = 0;
|
|
let matchGroup;
|
|
const regExp = /((?:(?:^|\.)\s*\w+|\[\s*(?:'(?:[^']|(?<!\\)\\')*'|"(?:[^"]|(?<!\\)\\")*"|-?\d+)\s*\])\s*)/g;
|
|
while (matchGroup = regExp.exec(path)) {
|
|
if (matchGroup.index !== matchIndex) {
|
|
return;
|
|
}
|
|
matchIndex = matchGroup.index + matchGroup[0].length;
|
|
const match = matchGroup[1].trim();
|
|
if (match.startsWith(".")) {
|
|
components.push(match.slice(1).trim());
|
|
} else if (match.startsWith("[")) {
|
|
const accessor = match.slice(1, -1).trim();
|
|
if (accessor.startsWith(`'`)) {
|
|
components.push(accessor.slice(1, -1).replaceAll(/(?<!\\)\\'/g, `'`));
|
|
} else if (accessor.startsWith(`"`)) {
|
|
components.push(accessor.slice(1, -1).replaceAll(/(?<!\\)\\"/g, `"`));
|
|
} else {
|
|
components.push(accessor);
|
|
}
|
|
} else {
|
|
components.push(match);
|
|
}
|
|
}
|
|
if (matchIndex !== path.length)
|
|
return;
|
|
return components;
|
|
}
|
|
function createPathAccessor(components) {
|
|
return (datum) => {
|
|
let current = datum;
|
|
for (const component of components) {
|
|
current = current[component];
|
|
}
|
|
return current;
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/data/data-model/aggregation/aggregator.ts
|
|
var Aggregator = class {
|
|
constructor(ctx, scopeCacheManager, resolvers) {
|
|
this.ctx = ctx;
|
|
this.scopeCacheManager = scopeCacheManager;
|
|
this.resolvers = resolvers;
|
|
}
|
|
/**
|
|
* Aggregates data for ungrouped datasets.
|
|
* Each datum gets its own aggregation result.
|
|
*/
|
|
aggregateUngroupedData(processedData) {
|
|
const domainAggValues = this.ctx.aggregates.map(() => [Infinity, -Infinity]);
|
|
processedData.domain.aggValues = domainAggValues;
|
|
const { columns, dataSources } = processedData;
|
|
const onlyScope = first(dataSources.keys());
|
|
const keys = processedData.keys.map((k) => k.get(onlyScope));
|
|
const rawData = dataSources.get(onlyScope)?.data ?? [];
|
|
const allowNull = this.ctx.keys.some((keyDef) => keyDef.allowNullKey === true);
|
|
processedData.aggregation = rawData?.map((_, datumIndex) => {
|
|
const aggregation = [];
|
|
for (const [index, def] of this.ctx.aggregates.entries()) {
|
|
const indices = this.valueGroupIdxLookup(def);
|
|
let groupAggValues = def.groupAggregateFunction?.() ?? [Infinity, -Infinity];
|
|
const valuesToAgg = indices.map((columnIndex) => columns[columnIndex][datumIndex]);
|
|
const k = datumKeys(keys, datumIndex, allowNull);
|
|
const valuesAgg = k == null ? void 0 : def.aggregateFunction(valuesToAgg, k);
|
|
if (valuesAgg) {
|
|
groupAggValues = def.groupAggregateFunction?.(valuesAgg, groupAggValues) ?? ContinuousDomain.extendDomain(valuesAgg, groupAggValues);
|
|
}
|
|
const finalValues = def.finalFunction?.(groupAggValues) ?? groupAggValues;
|
|
aggregation[index] = finalValues;
|
|
ContinuousDomain.extendDomain(finalValues, domainAggValues[index]);
|
|
}
|
|
return aggregation;
|
|
});
|
|
}
|
|
/**
|
|
* Aggregates data for grouped datasets.
|
|
* Multiple datums in a group share aggregation results.
|
|
*/
|
|
aggregateGroupedData(processedData) {
|
|
const domainAggValues = this.ctx.aggregates.map(() => [Infinity, -Infinity]);
|
|
processedData.domain.aggValues = domainAggValues;
|
|
const { columns } = processedData;
|
|
for (const [index, def] of this.ctx.aggregates.entries()) {
|
|
const indices = this.valueGroupIdxLookup(def);
|
|
for (let groupIndex = 0; groupIndex < processedData.groups.length; groupIndex++) {
|
|
const group = processedData.groups[groupIndex];
|
|
group.aggregation ?? (group.aggregation = []);
|
|
const groupKeys = group.keys;
|
|
let groupAggValues = def.groupAggregateFunction?.() ?? [Infinity, -Infinity];
|
|
const maxDatumIndex = Math.max(
|
|
...indices.map((columnIndex) => group.datumIndices[columnIndex]?.length ?? 0)
|
|
);
|
|
for (let datumIndex = 0; datumIndex < maxDatumIndex; datumIndex++) {
|
|
const valuesToAgg = indices.map((columnIndex) => {
|
|
const relativeDatumIndex = group.datumIndices[columnIndex]?.[datumIndex];
|
|
if (relativeDatumIndex == null) {
|
|
return void 0;
|
|
}
|
|
const absoluteDatumIndex = this.resolvers.resolveAbsoluteIndex(groupIndex, relativeDatumIndex);
|
|
return columns[columnIndex][absoluteDatumIndex];
|
|
});
|
|
const valuesAgg = def.aggregateFunction(valuesToAgg, groupKeys);
|
|
if (valuesAgg) {
|
|
groupAggValues = def.groupAggregateFunction?.(valuesAgg, groupAggValues) ?? ContinuousDomain.extendDomain(valuesAgg, groupAggValues);
|
|
}
|
|
}
|
|
const finalValues = def.finalFunction?.(groupAggValues) ?? groupAggValues;
|
|
group.aggregation[index] = finalValues;
|
|
ContinuousDomain.extendDomain(finalValues, domainAggValues[index]);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Post-processes groups after grouping is complete.
|
|
* Applies group value processors to adjust group values and recompute domains.
|
|
*/
|
|
postProcessGroups(processedData) {
|
|
const { groupProcessors } = this.ctx;
|
|
const { columnScopes, columns, invalidData } = processedData;
|
|
for (const processor of groupProcessors) {
|
|
const valueIndexes = this.valueGroupIdxLookup(processor);
|
|
const adjustFn = processor.adjust()();
|
|
for (let groupIndex = 0; groupIndex < processedData.groups.length; groupIndex++) {
|
|
const dataGroup = processedData.groups[groupIndex];
|
|
adjustFn(columns, valueIndexes, dataGroup, groupIndex);
|
|
}
|
|
for (const valueIndex of valueIndexes) {
|
|
const valueDef = this.ctx.values[valueIndex];
|
|
const isDiscrete = valueDef.valueType === "category";
|
|
const column = columns[valueIndex];
|
|
const columnScope = first(columnScopes[valueIndex]);
|
|
const invalidDatums = invalidData?.get(columnScope);
|
|
const domain = isDiscrete ? new DiscreteDomain() : new ContinuousDomain();
|
|
for (let datumIndex = 0; datumIndex < column.length; datumIndex += 1) {
|
|
if (invalidDatums?.[datumIndex] === true)
|
|
continue;
|
|
domain.extend(column[datumIndex]);
|
|
}
|
|
processedData.domain.values[valueIndex] = domain.getDomain();
|
|
}
|
|
}
|
|
}
|
|
valueGroupIdxLookup(selector) {
|
|
return this.scopeCacheManager.valueGroupIdxLookup(selector);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/data-model/domain/domainInitializer.ts
|
|
var DomainInitializer = class {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
}
|
|
/**
|
|
* Sets up the appropriate domain type for a property definition.
|
|
* Returns a BandedDomain wrapping DiscreteDomain for category values,
|
|
* or a BandedDomain wrapping ContinuousDomain for continuous values.
|
|
* Falls back to non-banded domains when banding is disabled.
|
|
*
|
|
* @param sortOrderEntry Optional sort order metadata from KEY_SORT_ORDERS.
|
|
* When data is sorted and unique, enables fast array concatenation optimization.
|
|
*/
|
|
setupDomainForDefinition(def, bandedDomains, sortOrderEntry) {
|
|
const isDiscrete = def.valueType === "category";
|
|
let domain = bandedDomains.get(def);
|
|
if (!domain && this.ctx.bandingConfig?.enableBanding !== false) {
|
|
domain = new BandedDomain(
|
|
isDiscrete ? () => new DiscreteDomain() : () => new ContinuousDomain(),
|
|
this.ctx.bandingConfig,
|
|
isDiscrete
|
|
);
|
|
bandedDomains.set(def, domain);
|
|
}
|
|
if (domain && isDiscrete) {
|
|
domain.setSortOrderMetadata(
|
|
sortOrderEntry?.sortOrder,
|
|
sortOrderEntry?.isUnique ?? false
|
|
);
|
|
}
|
|
if (domain) {
|
|
return domain;
|
|
}
|
|
return isDiscrete ? new DiscreteDomain() : new ContinuousDomain();
|
|
}
|
|
/**
|
|
* Extends a domain from data array, using banded optimization if available.
|
|
* Note: For BandedDomain, bands should already be initialized before calling this method.
|
|
*/
|
|
extendDomainFromData(domain, data, invalidData) {
|
|
if (domain instanceof BandedDomain) {
|
|
domain.extendBandsFromData(data, invalidData);
|
|
} else {
|
|
for (let i = 0; i < data.length; i++) {
|
|
if (invalidData?.[i] === true)
|
|
continue;
|
|
domain.extend(data[i]);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Initializes a banded domain if needed based on data size and state.
|
|
* This is a memory optimization that divides large datasets into bands.
|
|
*/
|
|
initializeBandedDomain(domain, dataSize, propertyName) {
|
|
if (!(domain instanceof BandedDomain))
|
|
return;
|
|
const stats = domain.getStats();
|
|
const shouldReinit = stats.bandCount === 0 || stats.dataSize !== dataSize;
|
|
if (this.ctx.debug.check() && shouldReinit && propertyName) {
|
|
this.ctx.debug(
|
|
`Reinitializing bands for ${propertyName}: bandCount=${stats.bandCount}, dataSize=${stats.dataSize}, dataLength=${dataSize}`
|
|
);
|
|
}
|
|
if (shouldReinit) {
|
|
domain.initializeBands(dataSize);
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/dataModelTypes.ts
|
|
var KEY_SORT_ORDERS = Symbol("key-sort-orders");
|
|
var COLUMN_SORT_ORDERS = Symbol("column-sort-orders");
|
|
var DOMAIN_RANGES = Symbol("domain-ranges");
|
|
var DOMAIN_BANDS = Symbol("domain-bands");
|
|
var REDUCER_BANDS = Symbol("reducer-bands");
|
|
var SHARED_ZERO_INDICES = Object.freeze([0]);
|
|
|
|
// packages/ag-charts-community/src/chart/data/data-model/domain/processValueFactory.ts
|
|
function trackMissingValue(missing, valueScopes) {
|
|
if (typeof valueScopes === "string") {
|
|
missing.set(valueScopes, (missing.get(valueScopes) ?? 0) + 1);
|
|
} else {
|
|
for (const scope of valueScopes) {
|
|
missing.set(scope, (missing.get(scope) ?? 0) + 1);
|
|
}
|
|
}
|
|
}
|
|
function handleInvalidValue(meta, value) {
|
|
meta.reusableResult.valid = false;
|
|
if (meta.hasInvalidValue) {
|
|
meta.reusableResult.value = meta.invalidValue;
|
|
meta.domain.extend(meta.invalidValue);
|
|
return;
|
|
}
|
|
if (meta.mode !== "integrated") {
|
|
logger_exports.warnOnce(
|
|
`invalid value of type [${typeof value}] for [${meta.def.scopes} / ${meta.def.id}] ignored:`,
|
|
`[${value}]`
|
|
);
|
|
}
|
|
meta.reusableResult.value = void 0;
|
|
}
|
|
function processValidationCheck(validation, valueInDatum, value, datum, idx, meta) {
|
|
if (validation && valueInDatum && validation(value, datum, idx) === false) {
|
|
meta.reusableResult.missing = false;
|
|
handleInvalidValue(meta, value);
|
|
return meta.reusableResult;
|
|
}
|
|
return null;
|
|
}
|
|
function handleMissingTracking(valueInDatum, hasMissingValue, missing, valueScopes, reusableResult) {
|
|
reusableResult.missing = !valueInDatum;
|
|
if (!valueInDatum && !hasMissingValue) {
|
|
trackMissingValue(missing, valueScopes);
|
|
}
|
|
}
|
|
function setValidResult(value, reusableResult, domain) {
|
|
reusableResult.valid = true;
|
|
reusableResult.value = value;
|
|
domain.extend(value);
|
|
return reusableResult;
|
|
}
|
|
var ProcessValueFactory = class {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
}
|
|
createProcessValueFn(def, accessor, domain, reusableResult, processorFns, domainMode) {
|
|
const context = {
|
|
def,
|
|
accessor,
|
|
domain,
|
|
reusableResult,
|
|
processorFns,
|
|
mode: this.ctx.mode
|
|
};
|
|
const specializedFn = domainMode === "extend" ? this.createSpecializedProcessValue(context, def.validation) : null;
|
|
return specializedFn ?? this.createGenericProcessValue(context, domainMode);
|
|
}
|
|
createSpecializedProcessValue(context, validation) {
|
|
if (context.def.forceValue != null) {
|
|
return this.createSpecializedProcessValueForceValue(context);
|
|
}
|
|
if (context.def.processor) {
|
|
return this.createSpecializedProcessValueProcessor(context, validation);
|
|
}
|
|
if (validation) {
|
|
return context.def.type === "key" ? this.createSpecializedProcessValueKeyValidation(context, validation) : this.createSpecializedProcessValueValueValidation(context, validation);
|
|
}
|
|
return null;
|
|
}
|
|
createValidationMeta(context) {
|
|
const { def, domain, reusableResult, mode } = context;
|
|
return {
|
|
reusableResult,
|
|
hasInvalidValue: "invalidValue" in def,
|
|
invalidValue: def.invalidValue,
|
|
domain,
|
|
def,
|
|
mode
|
|
};
|
|
}
|
|
/**
|
|
* Creates a specialized processValue function optimized for key properties with validation.
|
|
* Eliminates all branching for the most common key property case (~30% of calls).
|
|
*/
|
|
createSpecializedProcessValueKeyValidation(context, validation) {
|
|
const { def, accessor, domain, reusableResult } = context;
|
|
const property = def.property;
|
|
const hasMissingValue = "missingValue" in def;
|
|
const missingValue = def.missingValue;
|
|
const missing = def.missing;
|
|
const allowNullKey = def.allowNullKey ?? false;
|
|
const validationMeta = this.createValidationMeta(context);
|
|
if (accessor) {
|
|
const accessorFn = accessor;
|
|
return function processValueKeyValidationAccessor(datum, idx, valueScopes) {
|
|
let value;
|
|
try {
|
|
value = accessorFn(datum);
|
|
} catch {
|
|
}
|
|
const valueInDatum = value != null || allowNullKey && value == null;
|
|
const nullInvalid = !allowNullKey && value == null;
|
|
if (!valueInDatum || nullInvalid || validation(value, datum, idx) === false) {
|
|
reusableResult.missing = !valueInDatum;
|
|
if (!valueInDatum && !hasMissingValue) {
|
|
trackMissingValue(missing, valueScopes);
|
|
}
|
|
handleInvalidValue(validationMeta, value);
|
|
return reusableResult;
|
|
}
|
|
reusableResult.missing = false;
|
|
reusableResult.valid = true;
|
|
reusableResult.value = value;
|
|
domain.extend(value);
|
|
return reusableResult;
|
|
};
|
|
}
|
|
return function processValueKeyValidationDirect(datum, idx, valueScopes) {
|
|
const valueInDatum = property in datum;
|
|
const value = valueInDatum ? datum[property] : missingValue;
|
|
const nullInvalid = !allowNullKey && value == null;
|
|
if (!valueInDatum || nullInvalid || validation(value, datum, idx) === false) {
|
|
reusableResult.missing = !valueInDatum;
|
|
if (!valueInDatum && !hasMissingValue) {
|
|
trackMissingValue(missing, valueScopes);
|
|
}
|
|
handleInvalidValue(validationMeta, value);
|
|
return reusableResult;
|
|
}
|
|
reusableResult.missing = false;
|
|
reusableResult.valid = true;
|
|
reusableResult.value = value;
|
|
domain.extend(value);
|
|
return reusableResult;
|
|
};
|
|
}
|
|
/**
|
|
* Creates a specialized processValue function optimized for value properties with validation.
|
|
* Eliminates branching for the most common value property case (~50% of calls).
|
|
*/
|
|
createSpecializedProcessValueValueValidation(context, validation) {
|
|
const { def, accessor, domain, reusableResult } = context;
|
|
const property = def.property;
|
|
const hasMissingValue = "missingValue" in def;
|
|
const missingValue = def.missingValue;
|
|
const missing = def.missing;
|
|
const validationMeta = this.createValidationMeta(context);
|
|
if (accessor) {
|
|
const accessorFn = accessor;
|
|
return function processValueValueValidationAccessor(datum, idx, valueScopes) {
|
|
let value;
|
|
try {
|
|
value = accessorFn(datum);
|
|
} catch {
|
|
}
|
|
const valueInDatum = value != null;
|
|
const validationFailed = processValidationCheck(
|
|
validation,
|
|
valueInDatum,
|
|
value,
|
|
datum,
|
|
idx,
|
|
validationMeta
|
|
);
|
|
if (validationFailed !== null)
|
|
return validationFailed;
|
|
handleMissingTracking(valueInDatum, hasMissingValue, missing, valueScopes, reusableResult);
|
|
return setValidResult(value, reusableResult, domain);
|
|
};
|
|
}
|
|
return function processValueValueValidationDirect(datum, idx, valueScopes) {
|
|
const valueInDatum = property in datum;
|
|
const value = valueInDatum ? datum[property] : missingValue;
|
|
const validationFailed = processValidationCheck(
|
|
validation,
|
|
valueInDatum,
|
|
value,
|
|
datum,
|
|
idx,
|
|
validationMeta
|
|
);
|
|
if (validationFailed !== null)
|
|
return validationFailed;
|
|
handleMissingTracking(valueInDatum, hasMissingValue, missing, valueScopes, reusableResult);
|
|
return setValidResult(value, reusableResult, domain);
|
|
};
|
|
}
|
|
/**
|
|
* Creates a specialized processValue function for properties with forceValue.
|
|
* Optimized for invisible series (~5-10% of calls).
|
|
*/
|
|
createSpecializedProcessValueForceValue(context) {
|
|
const { def, accessor, domain, reusableResult } = context;
|
|
const property = def.property;
|
|
const forceValue = def.forceValue;
|
|
if (accessor) {
|
|
const accessorFn = accessor;
|
|
return function processValueForceValueAccessor(datum, _idx, _valueScopes) {
|
|
let value;
|
|
try {
|
|
value = accessorFn(datum);
|
|
} catch {
|
|
}
|
|
const valueInDatum = value != null;
|
|
const valueNegative = valueInDatum && isNegative(value);
|
|
const forcedValue = valueNegative ? -1 * forceValue : forceValue;
|
|
reusableResult.missing = false;
|
|
reusableResult.valid = true;
|
|
reusableResult.value = forcedValue;
|
|
domain.extend(forcedValue);
|
|
return reusableResult;
|
|
};
|
|
}
|
|
return function processValueForceValueDirect(datum, _idx, _valueScopes) {
|
|
const valueInDatum = property in datum;
|
|
const value = valueInDatum ? datum[property] : void 0;
|
|
const valueNegative = valueInDatum && isNegative(value);
|
|
const forcedValue = valueNegative ? -1 * forceValue : forceValue;
|
|
reusableResult.missing = false;
|
|
reusableResult.valid = true;
|
|
reusableResult.value = forcedValue;
|
|
domain.extend(forcedValue);
|
|
return reusableResult;
|
|
};
|
|
}
|
|
/**
|
|
* Creates a specialized processValue function for properties with processors.
|
|
* Optimized for data transformations (~5-10% of calls).
|
|
*/
|
|
createSpecializedProcessValueProcessor(context, validation) {
|
|
const { def, accessor, domain, reusableResult, processorFns } = context;
|
|
const property = def.property;
|
|
const hasMissingValue = "missingValue" in def;
|
|
const missingValue = def.missingValue;
|
|
const missing = def.missing;
|
|
const processor = def.processor;
|
|
const validationMeta = this.createValidationMeta(context);
|
|
if (accessor) {
|
|
const accessorFn = accessor;
|
|
return function processValueProcessorAccessor(datum, idx, valueScopes) {
|
|
let value;
|
|
try {
|
|
value = accessorFn(datum);
|
|
} catch {
|
|
}
|
|
const valueInDatum = value != null;
|
|
const validationFailed = processValidationCheck(
|
|
validation,
|
|
valueInDatum,
|
|
value,
|
|
datum,
|
|
idx,
|
|
validationMeta
|
|
);
|
|
if (validationFailed !== null)
|
|
return validationFailed;
|
|
handleMissingTracking(valueInDatum, hasMissingValue, missing, valueScopes, reusableResult);
|
|
let processorFn = processorFns.get(def);
|
|
if (processorFn == null) {
|
|
processorFn = processor();
|
|
processorFns.set(def, processorFn);
|
|
}
|
|
value = processorFn(value, idx);
|
|
return setValidResult(value, reusableResult, domain);
|
|
};
|
|
}
|
|
return function processValueProcessorDirect(datum, idx, valueScopes) {
|
|
const valueInDatum = property in datum;
|
|
let value = valueInDatum ? datum[property] : missingValue;
|
|
const validationFailed = processValidationCheck(
|
|
validation,
|
|
valueInDatum,
|
|
value,
|
|
datum,
|
|
idx,
|
|
validationMeta
|
|
);
|
|
if (validationFailed !== null)
|
|
return validationFailed;
|
|
handleMissingTracking(valueInDatum, hasMissingValue, missing, valueScopes, reusableResult);
|
|
let processorFn = processorFns.get(def);
|
|
if (processorFn == null) {
|
|
processorFn = processor();
|
|
processorFns.set(def, processorFn);
|
|
}
|
|
value = processorFn(value, idx);
|
|
return setValidResult(value, reusableResult, domain);
|
|
};
|
|
}
|
|
/**
|
|
* Creates the generic fallback processValue implementation used for edge cases
|
|
* and for skip mode (no domain extension). Generates a per-definition function
|
|
* so callers can resolve it once and reuse it without additional branching.
|
|
*/
|
|
createGenericProcessValue(context, domainMode) {
|
|
const { def, accessor, domain, processorFns } = context;
|
|
const property = def.property;
|
|
const hasMissingValue = "missingValue" in def;
|
|
const missingValue = def.missingValue;
|
|
const missing = def.missing;
|
|
const shouldExtendDomain = domainMode === "extend";
|
|
const reusableResult = context.reusableResult;
|
|
const validationMeta = this.createValidationMeta(context);
|
|
return function processValueGeneric(datum, idx, valueScopes) {
|
|
let value;
|
|
let valueInDatum;
|
|
if (accessor) {
|
|
try {
|
|
value = accessor(datum);
|
|
} catch {
|
|
}
|
|
valueInDatum = value != null;
|
|
} else {
|
|
valueInDatum = property in datum;
|
|
value = valueInDatum ? datum[property] : missingValue;
|
|
}
|
|
if (def.forceValue != null) {
|
|
const valueNegative = valueInDatum && isNegative(value);
|
|
value = valueNegative ? -1 * def.forceValue : def.forceValue;
|
|
valueInDatum = true;
|
|
}
|
|
handleMissingTracking(valueInDatum, hasMissingValue, missing, valueScopes, reusableResult);
|
|
const allowNullKey = def.allowNullKey ?? false;
|
|
const isKeyWithNullValue = def.type === "key" && value == null && !allowNullKey;
|
|
if (isKeyWithNullValue) {
|
|
handleInvalidValue(validationMeta, value);
|
|
return reusableResult;
|
|
}
|
|
const validationFailed = processValidationCheck(
|
|
def.validation,
|
|
valueInDatum,
|
|
value,
|
|
datum,
|
|
idx,
|
|
validationMeta
|
|
);
|
|
if (validationFailed !== null)
|
|
return validationFailed;
|
|
reusableResult.valid = true;
|
|
if (def.processor) {
|
|
let processor = processorFns.get(def);
|
|
if (processor == null) {
|
|
processor = def.processor();
|
|
processorFns.set(def, processor);
|
|
}
|
|
value = processor(value, idx);
|
|
}
|
|
if (shouldExtendDomain) {
|
|
domain.extend(value);
|
|
}
|
|
reusableResult.value = value;
|
|
return reusableResult;
|
|
};
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/data-model/domain/domainManager.ts
|
|
function scopesOverlap(scopes1, scopes2) {
|
|
if (!scopes1 || !scopes2 || scopes1.length === 0 || scopes2.length === 0) {
|
|
return true;
|
|
}
|
|
return scopes1.some((s) => scopes2.includes(s));
|
|
}
|
|
function findMatchingKeyDef(valueDef, keyDefs) {
|
|
if (valueDef.valueType !== "category")
|
|
return void 0;
|
|
for (const keyDef of keyDefs) {
|
|
if (keyDef.property !== valueDef.property)
|
|
continue;
|
|
if (keyDef.valueType !== valueDef.valueType)
|
|
continue;
|
|
if (!scopesOverlap(keyDef.scopes, valueDef.scopes))
|
|
continue;
|
|
if (keyDef.validation !== valueDef.validation)
|
|
continue;
|
|
return keyDef;
|
|
}
|
|
return void 0;
|
|
}
|
|
var DomainManager = class {
|
|
constructor(ctx, initializer, scopeCacheManager) {
|
|
this.ctx = ctx;
|
|
this.initializer = initializer;
|
|
this.scopeCacheManager = scopeCacheManager;
|
|
this.processValueFactory = new ProcessValueFactory(ctx);
|
|
}
|
|
/**
|
|
* Recomputes all domains from processed data.
|
|
* Uses BandedDomain optimization for continuous domains to avoid full rescans.
|
|
* Shares domains between keys and values when they reference the same property.
|
|
*/
|
|
recomputeDomains(processedData) {
|
|
const startTime = this.ctx.debug.check() ? performance.now() : 0;
|
|
const bandedDomains = processedData[DOMAIN_BANDS];
|
|
let bandStats;
|
|
const keySortOrders = processedData[KEY_SORT_ORDERS];
|
|
const keyDomains = this.setupDefinitionDomains(this.ctx.keys, bandedDomains, keySortOrders);
|
|
const valueToKeyDef = /* @__PURE__ */ new Map();
|
|
for (const valueDef of this.ctx.values) {
|
|
const matchingKeyDef = findMatchingKeyDef(valueDef, this.ctx.keys);
|
|
if (matchingKeyDef) {
|
|
valueToKeyDef.set(valueDef, matchingKeyDef);
|
|
}
|
|
}
|
|
const valueDomains = this.setupValueDomainsWithSharing(
|
|
this.ctx.values,
|
|
bandedDomains,
|
|
keyDomains,
|
|
valueToKeyDef
|
|
);
|
|
const sharedDomains = /* @__PURE__ */ new Set();
|
|
for (const [, keyDef] of valueToKeyDef) {
|
|
const sharedDomain = keyDomains.get(keyDef);
|
|
if (sharedDomain) {
|
|
sharedDomains.add(sharedDomain);
|
|
}
|
|
}
|
|
this.initializeDomainBands(
|
|
this.ctx.keys,
|
|
keyDomains,
|
|
(defIndex) => {
|
|
const keysMap = processedData.keys[defIndex];
|
|
return Math.max(...Array.from(keysMap.values()).map((keys) => keys.length));
|
|
},
|
|
(def) => String(def.property)
|
|
);
|
|
this.initializeDomainBands(
|
|
this.ctx.values,
|
|
valueDomains,
|
|
(defIndex) => processedData.columns[defIndex].length,
|
|
(def) => String(def.property)
|
|
);
|
|
const preScanDomainStats = /* @__PURE__ */ new Map();
|
|
if (bandedDomains.size > 0) {
|
|
bandStats = {
|
|
totalBands: 0,
|
|
dirtyBands: 0,
|
|
totalData: 0
|
|
};
|
|
for (const domain of bandedDomains.values()) {
|
|
if (domain instanceof BandedDomain) {
|
|
const stats = domain.getStats();
|
|
preScanDomainStats.set(domain, stats);
|
|
bandStats.totalBands += stats.bandCount;
|
|
bandStats.dirtyBands += stats.dirtyBandCount;
|
|
bandStats.totalData = Math.max(bandStats.totalData, stats.dataSize);
|
|
}
|
|
}
|
|
}
|
|
this.extendDomainsFromData(
|
|
this.ctx.keys,
|
|
keyDomains,
|
|
(defIndex, scope) => processedData.keys[defIndex]?.get(scope),
|
|
(def) => def.scopes ?? [],
|
|
(scope) => processedData.invalidKeys?.get(scope)
|
|
);
|
|
this.extendDomainsFromData(
|
|
this.ctx.values,
|
|
valueDomains,
|
|
(defIndex, _scope) => processedData.columns[defIndex],
|
|
(def) => [first(def.scopes)],
|
|
(scope) => processedData.invalidKeys?.get(scope),
|
|
sharedDomains
|
|
);
|
|
processedData.domain.keys = this.ctx.keys.map(function mapDomainKeys(keyDef) {
|
|
const domain = keyDomains.get(keyDef);
|
|
const result = domain.getDomain();
|
|
if (ContinuousDomain.is(domain) && result[0] > result[1]) {
|
|
return [];
|
|
}
|
|
return result;
|
|
});
|
|
processedData.domain.values = this.ctx.values.map(function mapDomainValues(valueDef) {
|
|
const domain = valueDomains.get(valueDef);
|
|
const result = domain.getDomain();
|
|
if (ContinuousDomain.is(domain) && result[0] > result[1]) {
|
|
return [];
|
|
}
|
|
return result;
|
|
});
|
|
if (processedData.type === "grouped") {
|
|
processedData.domain.groups = processedData.groups.map((group) => group.keys);
|
|
}
|
|
this.collectDomainBandingMetadata(processedData, keyDomains, valueDomains, bandedDomains, preScanDomainStats);
|
|
if (this.ctx.debug.check() && startTime > 0) {
|
|
const endTime = performance.now();
|
|
const duration = endTime - startTime;
|
|
if (bandStats && bandStats.totalBands > 0) {
|
|
const scanRatio = bandStats.dirtyBands / bandStats.totalBands;
|
|
const dataScanned = Math.round(scanRatio * bandStats.totalData);
|
|
this.ctx.debug(
|
|
`recomputeDomains with banding: ${duration.toFixed(2)}ms, bands: ${bandStats.dirtyBands}/${bandStats.totalBands} dirty, data scanned: ~${dataScanned}/${bandStats.totalData} (${(scanRatio * 100).toFixed(1)}%)`
|
|
);
|
|
} else {
|
|
this.ctx.debug(`recomputeDomains: ${duration.toFixed(2)}ms (no banding)`);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Creates domain instances for the given definitions, reusing banded domains when available.
|
|
* For key definitions, passes KEY_SORT_ORDERS metadata to enable fast array concatenation.
|
|
*/
|
|
setupDefinitionDomains(defs, bandedDomains, keySortOrders) {
|
|
const domains = /* @__PURE__ */ new Map();
|
|
for (const [defIndex, def] of defs.entries()) {
|
|
const sortOrderEntry = keySortOrders?.get(defIndex);
|
|
domains.set(def, this.initializer.setupDomainForDefinition(def, bandedDomains, sortOrderEntry));
|
|
}
|
|
return domains;
|
|
}
|
|
/**
|
|
* Initializes banded domains for each definition using the provided data length accessor.
|
|
*/
|
|
initializeDomainBands(defs, domains, getDataLength, getPropertyName) {
|
|
for (const [defIndex, def] of defs.entries()) {
|
|
const domain = domains.get(def);
|
|
if (!domain)
|
|
continue;
|
|
const dataLength = getDataLength(defIndex);
|
|
this.initializer.initializeBandedDomain(domain, dataLength, getPropertyName(def));
|
|
}
|
|
}
|
|
/**
|
|
* Extends domains from data sources using a shared traversal.
|
|
* @param skipDomains Optional set of domains to skip (already extended via shared key processing)
|
|
*/
|
|
extendDomainsFromData(defs, domains, getData, getScopes, getInvalid, skipDomains) {
|
|
for (const [defIndex, def] of defs.entries()) {
|
|
const domain = domains.get(def);
|
|
if (!domain)
|
|
continue;
|
|
if (skipDomains?.has(domain))
|
|
continue;
|
|
for (const scope of getScopes(def)) {
|
|
if (!scope)
|
|
continue;
|
|
const data = getData(defIndex, scope);
|
|
if (!data)
|
|
continue;
|
|
const invalid = getInvalid(scope);
|
|
this.initializer.extendDomainFromData(domain, data, invalid);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Sets up value domains, reusing key domains where properties match.
|
|
* This avoids duplicate domain computation for properties that appear as both key and value.
|
|
*/
|
|
setupValueDomainsWithSharing(defs, bandedDomains, keyDomains, valueToKeyDef) {
|
|
const domains = /* @__PURE__ */ new Map();
|
|
for (const def of defs) {
|
|
const matchingKeyDef = valueToKeyDef.get(def);
|
|
if (matchingKeyDef) {
|
|
const keyDomain = keyDomains.get(matchingKeyDef);
|
|
if (keyDomain) {
|
|
domains.set(def, keyDomain);
|
|
continue;
|
|
}
|
|
}
|
|
domains.set(def, this.initializer.setupDomainForDefinition(def, bandedDomains));
|
|
}
|
|
return domains;
|
|
}
|
|
/**
|
|
* Initializes domain processor for value processing during data transformation.
|
|
* Returns domain maps and processing functions used during data extraction.
|
|
* Uses specialized functions per property definition to eliminate branching in hot paths.
|
|
* Shares domains between keys and values when they reference the same property.
|
|
*/
|
|
initDataDomainProcessor(domainMode) {
|
|
const { keys: keyDefs, values: valueDefs } = this.ctx;
|
|
const scopes = /* @__PURE__ */ new Set();
|
|
for (const valueDef of valueDefs) {
|
|
if (!valueDef.scopes)
|
|
continue;
|
|
for (const scope of valueDef.scopes) {
|
|
scopes.add(scope);
|
|
}
|
|
}
|
|
const dataDomain = /* @__PURE__ */ new Map();
|
|
const processorFns = /* @__PURE__ */ new Map();
|
|
let allScopesHaveSameDefs = true;
|
|
const initDataDomain = () => {
|
|
for (const def of keyDefs) {
|
|
if (def.valueType === "category") {
|
|
dataDomain.set(def, new DiscreteDomain());
|
|
} else {
|
|
dataDomain.set(def, new ContinuousDomain());
|
|
}
|
|
}
|
|
for (const def of valueDefs) {
|
|
const matchingKeyDef = findMatchingKeyDef(def, keyDefs);
|
|
if (matchingKeyDef) {
|
|
const keyDomain = dataDomain.get(matchingKeyDef);
|
|
if (keyDomain) {
|
|
dataDomain.set(def, keyDomain);
|
|
allScopesHaveSameDefs && (allScopesHaveSameDefs = (def.scopes?.length ?? 0) === scopes.size);
|
|
continue;
|
|
}
|
|
}
|
|
if (def.valueType === "category") {
|
|
dataDomain.set(def, new DiscreteDomain());
|
|
} else {
|
|
dataDomain.set(def, new ContinuousDomain());
|
|
allScopesHaveSameDefs && (allScopesHaveSameDefs = (def.scopes?.length ?? 0) === scopes.size);
|
|
}
|
|
}
|
|
};
|
|
initDataDomain();
|
|
const accessors = this.scopeCacheManager.buildAccessors(iterate(keyDefs, valueDefs));
|
|
const processValueFns = /* @__PURE__ */ new WeakMap();
|
|
for (const def of iterate(keyDefs, valueDefs)) {
|
|
const accessor = accessors.get(def.property);
|
|
const domain = dataDomain.get(def);
|
|
const reusableResult = {
|
|
value: void 0,
|
|
missing: false,
|
|
valid: false
|
|
};
|
|
const processFn = this.processValueFactory.createProcessValueFn(
|
|
def,
|
|
accessor,
|
|
domain,
|
|
reusableResult,
|
|
processorFns,
|
|
domainMode
|
|
);
|
|
processValueFns.set(def, processFn);
|
|
}
|
|
function getProcessValue(def) {
|
|
const processFn = processValueFns.get(def);
|
|
if (!processFn) {
|
|
throw new Error("AG Charts - missing processValue function for definition");
|
|
}
|
|
return processFn;
|
|
}
|
|
function processValue(def, datum, idx, valueScopes) {
|
|
return getProcessValue(def)(datum, idx, valueScopes);
|
|
}
|
|
return { dataDomain, processValue, getProcessValue, initDataDomain, scopes, allScopesHaveSameDefs };
|
|
}
|
|
/**
|
|
* Collects metadata about banded domain optimization for debugging and testing.
|
|
* Stores statistics about domain banding per key and value definition.
|
|
*/
|
|
collectDomainBandingMetadata(processedData, keyDomains, valueDomains, bandedDomains, preScanDomainStats) {
|
|
processedData.optimizations ?? (processedData.optimizations = {});
|
|
const collectDefs = (defs, domains) => {
|
|
return defs.map((def) => {
|
|
const domain = domains.get(def);
|
|
const bandedDomain = bandedDomains.get(def);
|
|
const isBanded = domain instanceof BandedDomain;
|
|
let reason;
|
|
if (!isBanded) {
|
|
reason = def.valueType === "category" ? "discrete domain" : "not configured";
|
|
}
|
|
let stats;
|
|
if (isBanded && bandedDomain) {
|
|
const domainStats = preScanDomainStats.get(bandedDomain) ?? bandedDomain.getStats();
|
|
const scanRatio = domainStats.bandCount > 0 ? domainStats.dirtyBandCount / domainStats.bandCount : 0;
|
|
stats = {
|
|
totalBands: domainStats.bandCount,
|
|
dirtyBands: domainStats.dirtyBandCount,
|
|
dataSize: domainStats.dataSize,
|
|
scanRatio
|
|
};
|
|
}
|
|
return {
|
|
property: String(def.property),
|
|
applied: isBanded,
|
|
reason,
|
|
stats
|
|
};
|
|
});
|
|
};
|
|
const keyDefs = collectDefs(this.ctx.keys, keyDomains);
|
|
const valueDefs = collectDefs(this.ctx.values, valueDomains);
|
|
processedData.optimizations.domainBanding = {
|
|
keyDefs,
|
|
valueDefs
|
|
};
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/data-model/extraction/dataExtractor.ts
|
|
function createKeyTracker() {
|
|
return { lastValue: void 0, sortOrder: 0, isUnique: true, isOrdered: true };
|
|
}
|
|
function updateKeyTracker(tracker, value) {
|
|
const numericValue = typeof value === "number" ? value : value?.valueOf?.();
|
|
if (typeof numericValue !== "number" || !Number.isFinite(numericValue))
|
|
return;
|
|
if (tracker.lastValue === void 0) {
|
|
tracker.lastValue = numericValue;
|
|
return;
|
|
}
|
|
const diff9 = numericValue - tracker.lastValue;
|
|
if (diff9 === 0) {
|
|
tracker.isUnique = false;
|
|
} else if (tracker.isOrdered) {
|
|
const direction = diff9 > 0 ? 1 : -1;
|
|
if (tracker.sortOrder === 0) {
|
|
tracker.sortOrder = direction;
|
|
} else if (tracker.sortOrder !== direction) {
|
|
tracker.isOrdered = false;
|
|
}
|
|
}
|
|
tracker.lastValue = numericValue;
|
|
}
|
|
function trackerToSortOrderEntry(tracker) {
|
|
return {
|
|
sortOrder: tracker.isOrdered && tracker.sortOrder !== 0 ? tracker.sortOrder : void 0,
|
|
isUnique: tracker.isUnique,
|
|
isDirty: false
|
|
};
|
|
}
|
|
var DataExtractor = class {
|
|
constructor(ctx, domainManager) {
|
|
this.ctx = ctx;
|
|
this.domainManager = domainManager;
|
|
this.markScopeDatumInvalid = function(scopes, data, datumIndex, invalidData, invalidDataCount) {
|
|
for (const scope of scopes) {
|
|
if (!invalidData.has(scope)) {
|
|
invalidData.set(scope, createArray(data.length, false));
|
|
invalidDataCount.set(scope, 0);
|
|
}
|
|
const scopeInvalidData = invalidData.get(scope);
|
|
if (!scopeInvalidData[datumIndex]) {
|
|
scopeInvalidData[datumIndex] = true;
|
|
invalidDataCount.set(scope, invalidDataCount.get(scope) + 1);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
extractData(sources) {
|
|
const { dataDomain, getProcessValue, allScopesHaveSameDefs } = this.domainManager.initDataDomainProcessor("extend");
|
|
const { keys: keyDefs, values: valueDefs } = this.ctx;
|
|
const { invalidData, invalidKeys, invalidKeyCount, invalidDataCount, allKeyMappings, keySortOrders } = this.extractKeys(keyDefs, sources, getProcessValue);
|
|
const { columns, columnScopes, columnNeedValueOf, partialValidDataCount, maxDataLength } = this.extractValues(
|
|
invalidData,
|
|
invalidDataCount,
|
|
valueDefs,
|
|
sources,
|
|
invalidKeys,
|
|
getProcessValue
|
|
);
|
|
const propertyDomain = (def) => {
|
|
const defDomain = dataDomain.get(def);
|
|
const result = defDomain.getDomain();
|
|
if (ContinuousDomain.is(defDomain) && result[0] > result[1]) {
|
|
return [];
|
|
}
|
|
return result;
|
|
};
|
|
return {
|
|
type: "ungrouped",
|
|
input: { count: maxDataLength },
|
|
scopes: new Set(sources.keys()),
|
|
dataSources: sources,
|
|
aggregation: void 0,
|
|
keys: [...allKeyMappings.values()],
|
|
columns,
|
|
columnScopes,
|
|
columnNeedValueOf,
|
|
invalidKeys,
|
|
invalidKeyCount,
|
|
invalidData,
|
|
invalidDataCount,
|
|
domain: {
|
|
keys: keyDefs.map(propertyDomain),
|
|
values: valueDefs.map(propertyDomain)
|
|
},
|
|
defs: {
|
|
allScopesHaveSameDefs,
|
|
keys: keyDefs,
|
|
values: valueDefs
|
|
},
|
|
partialValidDataCount,
|
|
time: 0,
|
|
version: 0,
|
|
[DOMAIN_RANGES]: /* @__PURE__ */ new Map(),
|
|
[KEY_SORT_ORDERS]: keySortOrders,
|
|
[COLUMN_SORT_ORDERS]: /* @__PURE__ */ new Map(),
|
|
[DOMAIN_BANDS]: /* @__PURE__ */ new Map(),
|
|
[REDUCER_BANDS]: /* @__PURE__ */ new Map()
|
|
};
|
|
}
|
|
extractKeys(keyDefs, sources, getProcessValue) {
|
|
const invalidKeys = /* @__PURE__ */ new Map();
|
|
const invalidData = /* @__PURE__ */ new Map();
|
|
const invalidKeyCount = /* @__PURE__ */ new Map();
|
|
const invalidDataCount = /* @__PURE__ */ new Map();
|
|
const allKeys = /* @__PURE__ */ new Map();
|
|
const keySortOrders = /* @__PURE__ */ new Map();
|
|
let keyDefKeys;
|
|
let scopeDataProcessed;
|
|
const keyProcessors = keyDefs.map((def) => getProcessValue(def));
|
|
const cloneScope = (source, target) => {
|
|
const sourceScope = scopeDataProcessed.get(source);
|
|
keyDefKeys.set(target, keyDefKeys.get(sourceScope));
|
|
if (invalidKeys.has(sourceScope)) {
|
|
invalidKeys.set(target, invalidKeys.get(sourceScope));
|
|
invalidData.set(target, invalidData.get(sourceScope));
|
|
invalidDataCount.set(target, invalidDataCount.get(sourceScope));
|
|
}
|
|
};
|
|
for (const [keyDefIndex, keyDef] of keyDefs.entries()) {
|
|
const { invalidValue, scopes: keyScopes } = keyDef;
|
|
const processKeyValue = keyProcessors[keyDefIndex];
|
|
keyDefKeys = /* @__PURE__ */ new Map();
|
|
scopeDataProcessed = /* @__PURE__ */ new Map();
|
|
allKeys.set(keyDef, keyDefKeys);
|
|
const tracker = createKeyTracker();
|
|
for (const scope of keyScopes ?? []) {
|
|
const data = sources.get(scope)?.data ?? [];
|
|
if (scopeDataProcessed.has(data)) {
|
|
cloneScope(data, scope);
|
|
continue;
|
|
}
|
|
const keys = [];
|
|
keyDefKeys.set(scope, keys);
|
|
scopeDataProcessed.set(data, scope);
|
|
let invalidScopeKeys;
|
|
let invalidScopeData;
|
|
let missingKeys = 0;
|
|
for (let datumIndex = 0; datumIndex < data.length; datumIndex++) {
|
|
if (data[datumIndex] == null || typeof data[datumIndex] !== "object") {
|
|
invalidScopeKeys ?? (invalidScopeKeys = createArray(data.length, false));
|
|
invalidScopeData ?? (invalidScopeData = createArray(data.length, false));
|
|
missingKeys += 1;
|
|
invalidScopeKeys[datumIndex] = true;
|
|
invalidScopeData[datumIndex] = true;
|
|
keys.push(invalidValue);
|
|
continue;
|
|
}
|
|
const result = processKeyValue(data[datumIndex], datumIndex, scope);
|
|
if (result.valid) {
|
|
keys.push(result.value);
|
|
updateKeyTracker(tracker, result.value);
|
|
continue;
|
|
}
|
|
keys.push(invalidValue);
|
|
invalidScopeKeys ?? (invalidScopeKeys = createArray(data.length, false));
|
|
invalidScopeData ?? (invalidScopeData = createArray(data.length, false));
|
|
missingKeys += 1;
|
|
invalidScopeKeys[datumIndex] = true;
|
|
invalidScopeData[datumIndex] = true;
|
|
}
|
|
if (invalidScopeKeys && invalidScopeData) {
|
|
invalidKeys.set(scope, invalidScopeKeys);
|
|
invalidData.set(scope, invalidScopeData);
|
|
invalidKeyCount.set(scope, missingKeys);
|
|
invalidDataCount.set(scope, missingKeys);
|
|
}
|
|
}
|
|
keySortOrders.set(keyDefIndex, trackerToSortOrderEntry(tracker));
|
|
}
|
|
return { invalidData, invalidKeys, invalidKeyCount, invalidDataCount, allKeyMappings: allKeys, keySortOrders };
|
|
}
|
|
extractValues(invalidData, invalidDataCount, valueDefs, sources, scopeInvalidKeys, getProcessValue) {
|
|
let partialValidDataCount = 0;
|
|
const columns = [];
|
|
const allColumnScopes = [];
|
|
const columnNeedValueOf = [];
|
|
let maxDataLength = 0;
|
|
const valueProcessors = valueDefs.map((def) => getProcessValue(def));
|
|
for (const [valueDefIndex, def] of valueDefs.entries()) {
|
|
const { invalidValue } = def;
|
|
const processValueForDef = valueProcessors[valueDefIndex];
|
|
const valueSources = new Set(def.scopes.map((s) => sources.get(s)));
|
|
if (valueSources.size > 1) {
|
|
throw new Error(`AG Charts - more than one data source for: ${JSON.stringify(def)}`);
|
|
}
|
|
const columnScopes = new Set(def.scopes);
|
|
const columnScope = first(def.scopes);
|
|
const columnSource = sources.get(columnScope)?.data ?? [];
|
|
const column = new Array();
|
|
const invalidKeys = scopeInvalidKeys.get(columnScope);
|
|
let needsValueOf = false;
|
|
for (let datumIndex = 0; datumIndex < columnSource.length; datumIndex++) {
|
|
if (columnSource[datumIndex] == null || typeof columnSource[datumIndex] !== "object") {
|
|
this.markScopeDatumInvalid(def.scopes, columnSource, datumIndex, invalidData, invalidDataCount);
|
|
column[datumIndex] = invalidValue;
|
|
continue;
|
|
}
|
|
const valueDatum = columnSource[datumIndex];
|
|
const invalidKey = invalidKeys == null ? false : invalidKeys[datumIndex];
|
|
const result = processValueForDef(valueDatum, datumIndex, def.scopes);
|
|
let value = result.value;
|
|
if (invalidKey || !result.valid) {
|
|
this.markScopeDatumInvalid(def.scopes, columnSource, datumIndex, invalidData, invalidDataCount);
|
|
}
|
|
if (invalidKey) {
|
|
value = invalidValue;
|
|
} else if (!result.valid) {
|
|
partialValidDataCount += 1;
|
|
value = invalidValue;
|
|
}
|
|
if (!needsValueOf && value != null && typeof value === "object") {
|
|
needsValueOf = true;
|
|
}
|
|
column[datumIndex] = value;
|
|
}
|
|
columns.push(column);
|
|
allColumnScopes.push(columnScopes);
|
|
columnNeedValueOf.push(needsValueOf);
|
|
maxDataLength = Math.max(maxDataLength, column.length);
|
|
}
|
|
return { columns, columnScopes: allColumnScopes, columnNeedValueOf, partialValidDataCount, maxDataLength };
|
|
}
|
|
warnDataMissingProperties(sources) {
|
|
if (sources.size === 0)
|
|
return;
|
|
for (const def of iterate(this.ctx.keys, this.ctx.values)) {
|
|
for (const [scope, missCount] of def.missing) {
|
|
if (missCount < (sources.get(scope)?.data.length ?? Infinity))
|
|
continue;
|
|
const scopeHint = scope == null ? "" : ` for ${scope}`;
|
|
logger_exports.warnOnce(`the key '${def.property}' was not found in any data element${scopeHint}.`);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/data-model/grouping/dataGrouper.ts
|
|
var DataGrouper = class {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
}
|
|
/**
|
|
* Groups data by keys or custom grouping function.
|
|
*
|
|
* GROUPED DATA STRUCTURE AND INVARIANTS:
|
|
*
|
|
* When groupsUnique=true (each datum has distinct keys):
|
|
* - groups.length === columns[i].length for all columns
|
|
* - groups[i] corresponds to datum at columns[j][i]
|
|
* - All datumIndices arrays contain [0] (shared memory optimization)
|
|
* - Relative indexing: datumIndices contains offsets from group start
|
|
* - Absolute indexing: groupIndex + relativeDatumIndex gives column position
|
|
*
|
|
* When groupsUnique=false (data is aggregated):
|
|
* - groups.length <= columns[i].length
|
|
* - Multiple datums may map to same group
|
|
* - datumIndices contain actual relative offsets
|
|
*
|
|
* This design optimizes memory usage for high-frequency data updates
|
|
* where each datum typically has unique keys (e.g., time series data).
|
|
*/
|
|
groupData(data, customGroupingFn) {
|
|
var _a;
|
|
const { keys: dataKeys, columns: allColumns, columnScopes, invalidKeys, invalidData } = data;
|
|
const allScopes = data.scopes;
|
|
const resultGroups = [];
|
|
const resultData = [];
|
|
const groups = allScopes.size !== 1 || customGroupingFn != null ? /* @__PURE__ */ new Map() : void 0;
|
|
let groupsUnique = true;
|
|
let groupIndex = 0;
|
|
const rawBatchCount = allScopes.size;
|
|
const columnBatches = this.groupBatches(
|
|
allScopes,
|
|
allColumns,
|
|
columnScopes,
|
|
dataKeys,
|
|
invalidData,
|
|
invalidKeys
|
|
);
|
|
const mergedBatchCount = columnBatches.length;
|
|
if (this.ctx.debug?.check() && !data.optimizations) {
|
|
data.optimizations = {};
|
|
}
|
|
if (this.ctx.debug?.check()) {
|
|
const mergeRatio = rawBatchCount > 0 ? 1 - mergedBatchCount / rawBatchCount : 0;
|
|
data.optimizations.batchMerging = {
|
|
originalBatchCount: rawBatchCount,
|
|
mergedBatchCount,
|
|
mergeRatio
|
|
};
|
|
}
|
|
const singleBatch = columnBatches.length === 1;
|
|
const allZeroDatumIndices = Object.freeze(createArray(columnBatches[0][1].length, SHARED_ZERO_INDICES));
|
|
for (const [
|
|
scopes,
|
|
scopeColumnIndexes,
|
|
scopeKeys,
|
|
siblingScopes,
|
|
scopeInvalidData,
|
|
scopeInvalidKeys
|
|
] of columnBatches) {
|
|
const firstColumn = allColumns[first(scopeColumnIndexes)];
|
|
for (let datumIndex = 0; datumIndex < firstColumn.length; datumIndex++) {
|
|
if (scopeInvalidKeys?.[datumIndex] === true)
|
|
continue;
|
|
const keys = scopeKeys.map((k) => k[datumIndex]);
|
|
if (keys == null || keys.length === 0) {
|
|
throw new Error("AG Charts - no keys found for scope(s): " + scopes.join(", "));
|
|
}
|
|
const group = customGroupingFn?.(keys) ?? keys;
|
|
const groupStr = groups == null ? void 0 : toKeyString(group);
|
|
let outputGroup = groups?.get(groupStr);
|
|
let currentGroup;
|
|
let currentGroupIndex;
|
|
let isNewGroup = false;
|
|
if (outputGroup == null) {
|
|
currentGroup = {
|
|
keys: group,
|
|
datumIndices: [],
|
|
aggregation: [],
|
|
validScopes: allScopes
|
|
};
|
|
currentGroupIndex = groupIndex++;
|
|
outputGroup = [currentGroupIndex, currentGroup];
|
|
isNewGroup = true;
|
|
groups?.set(groupStr, outputGroup);
|
|
resultGroups.push(currentGroup.keys);
|
|
resultData.push(currentGroup);
|
|
} else {
|
|
[currentGroupIndex, currentGroup] = outputGroup;
|
|
groupsUnique = false;
|
|
}
|
|
if (scopeInvalidData?.[datumIndex] === true) {
|
|
if (currentGroup.validScopes === allScopes) {
|
|
currentGroup.validScopes = new Set(allScopes.values());
|
|
}
|
|
for (const invalidScope of siblingScopes) {
|
|
currentGroup.validScopes.delete(invalidScope);
|
|
}
|
|
}
|
|
if (isNewGroup && datumIndex === currentGroupIndex && singleBatch) {
|
|
currentGroup.datumIndices = allZeroDatumIndices;
|
|
} else {
|
|
if (!isNewGroup && currentGroup.datumIndices === allZeroDatumIndices) {
|
|
currentGroup.datumIndices = allZeroDatumIndices.map((arr) => [...arr]);
|
|
}
|
|
for (const columnIdx of scopeColumnIndexes) {
|
|
(_a = currentGroup.datumIndices)[columnIdx] ?? (_a[columnIdx] = []);
|
|
currentGroup.datumIndices[columnIdx].push(datumIndex - currentGroupIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return {
|
|
...data,
|
|
type: "grouped",
|
|
domain: {
|
|
...data.domain,
|
|
groups: resultGroups
|
|
},
|
|
groups: resultData,
|
|
groupsUnique,
|
|
optimizations: data.optimizations,
|
|
[DOMAIN_BANDS]: data[DOMAIN_BANDS],
|
|
[REDUCER_BANDS]: data[REDUCER_BANDS]
|
|
};
|
|
}
|
|
/**
|
|
* Groups and merges column batches for efficient processing.
|
|
*
|
|
* BATCH MERGING OPTIMIZATION:
|
|
* - Identifies columns that share the same data characteristics
|
|
* - Merges compatible batches to reduce iteration overhead
|
|
* - Can reduce processing iterations by 30-50% for multi-scope datasets
|
|
*
|
|
* Compatibility criteria:
|
|
* - Same keys arrays (by reference)
|
|
* - Same invalidity arrays (by reference)
|
|
* - Scopes can be safely processed together
|
|
*/
|
|
groupBatches(allScopes, allColumns, columnScopes, dataKeys, invalidData, invalidKeys) {
|
|
const columnBatches = [];
|
|
const processedColumnIndexes = /* @__PURE__ */ new Set();
|
|
for (const scope of allScopes) {
|
|
const scopeColumnIndexes = allColumns.map((_, idx) => idx).filter((idx) => !processedColumnIndexes.has(idx) && columnScopes[idx].has(scope));
|
|
if (scopeColumnIndexes.length === 0)
|
|
continue;
|
|
for (const idx of scopeColumnIndexes) {
|
|
processedColumnIndexes.add(idx);
|
|
}
|
|
const siblingScopes = /* @__PURE__ */ new Set();
|
|
for (const columnIdx of scopeColumnIndexes) {
|
|
for (const columnScope of columnScopes[columnIdx]) {
|
|
siblingScopes.add(columnScope);
|
|
}
|
|
}
|
|
const scopeKeys = dataKeys.map((k) => k.get(scope)).filter((k) => k != null);
|
|
const scopeInvalidData = invalidData?.get(scope);
|
|
const scopeInvalidKeys = invalidKeys?.get(scope);
|
|
columnBatches.push([
|
|
scope,
|
|
scopeColumnIndexes,
|
|
scopeKeys,
|
|
siblingScopes,
|
|
scopeInvalidData,
|
|
scopeInvalidKeys
|
|
]);
|
|
}
|
|
return this.mergeCompatibleBatches(columnBatches);
|
|
}
|
|
/**
|
|
* Checks if two column batches can be merged based on shared data characteristics.
|
|
*/
|
|
areBatchesCompatible(batch1, batch2) {
|
|
const [, , keys1, , invalidData1, invalidKeys1] = batch1;
|
|
const [, , keys2, , invalidData2, invalidKeys2] = batch2;
|
|
return keys1.every((k, i) => k === keys2[i]) && invalidKeys1 === invalidKeys2 && invalidData1 === invalidData2;
|
|
}
|
|
mergeCompatibleBatches(columnBatches) {
|
|
const merged = [];
|
|
const processed = /* @__PURE__ */ new Set();
|
|
for (let i = 0; i < columnBatches.length; i++) {
|
|
if (processed.has(i))
|
|
continue;
|
|
const [scope, columnIndexes, keys, siblingScopes, invalidData, invalidKeys] = columnBatches[i];
|
|
const mergedBatch = [
|
|
[scope],
|
|
[...columnIndexes],
|
|
keys,
|
|
new Set(siblingScopes),
|
|
invalidData,
|
|
invalidKeys
|
|
];
|
|
this.findAndMergeCompatibleBatches(columnBatches, i, mergedBatch, processed);
|
|
merged.push(mergedBatch);
|
|
processed.add(i);
|
|
}
|
|
return merged;
|
|
}
|
|
findAndMergeCompatibleBatches(columnBatches, startIndex, mergedBatch, processed) {
|
|
const firstBatch = columnBatches[startIndex];
|
|
for (let j = startIndex + 1; j < columnBatches.length; j++) {
|
|
if (processed.has(j))
|
|
continue;
|
|
const otherBatch = columnBatches[j];
|
|
const [scope, otherColumnIndexes, , otherSiblingScopes] = otherBatch;
|
|
if (!this.areBatchesCompatible(firstBatch, otherBatch))
|
|
continue;
|
|
mergedBatch[0].push(scope);
|
|
mergedBatch[1].push(...otherColumnIndexes);
|
|
for (const siblingScope of otherSiblingScopes) {
|
|
mergedBatch[3].add(siblingScope);
|
|
}
|
|
processed.add(j);
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/dataChangeDescription.ts
|
|
function isAppendOnly(indexMap) {
|
|
return indexMap.removedIndices.size === 0 && indexMap.totalPrependCount === 0 && indexMap.totalAppendCount > 0;
|
|
}
|
|
function isPrependOnly(indexMap) {
|
|
return indexMap.removedIndices.size === 0 && indexMap.totalAppendCount === 0 && indexMap.totalPrependCount > 0;
|
|
}
|
|
function hasNoRemovals(indexMap) {
|
|
return indexMap.removedIndices.size === 0;
|
|
}
|
|
function isUpdateOnly(indexMap) {
|
|
return indexMap.removedIndices.size === 0 && indexMap.totalPrependCount === 0 && indexMap.totalAppendCount === 0 && indexMap.spliceOps.every((op) => op.insertCount === 0 && op.deleteCount === 0);
|
|
}
|
|
function hasOnlyRemovals(indexMap) {
|
|
return indexMap.removedIndices.size > 0 && indexMap.totalPrependCount === 0 && indexMap.totalAppendCount === 0 && indexMap.spliceOps.every((op) => op.insertCount === 0);
|
|
}
|
|
function hasContiguousRemovalsAtStart(indexMap) {
|
|
const { removedIndices } = indexMap;
|
|
if (removedIndices.size === 0)
|
|
return false;
|
|
const sorted = Array.from(removedIndices).sort((a, b) => a - b);
|
|
if (sorted[0] !== 0)
|
|
return false;
|
|
for (let i = 0; i < sorted.length; i++) {
|
|
if (sorted[i] !== i)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function isRollingWindow(indexMap) {
|
|
return hasContiguousRemovalsAtStart(indexMap) && indexMap.totalAppendCount > 0 && indexMap.totalPrependCount === 0;
|
|
}
|
|
var DataChangeDescription = class {
|
|
constructor(indexMap, insertions) {
|
|
this.indexMap = indexMap;
|
|
this.prependValues = insertions.prependValues;
|
|
this.appendValues = insertions.appendValues;
|
|
this.insertionValues = insertions.insertionValues;
|
|
}
|
|
/**
|
|
* Get all indices that were removed from the original array, sorted ascending.
|
|
*
|
|
* @returns Array of removed indices (e.g., [2, 5, 8])
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const removed = changeDesc.getRemovedIndices();
|
|
* console.log(`Removed ${removed.length} items at indices: ${removed}`);
|
|
* ```
|
|
*/
|
|
getRemovedIndices() {
|
|
return Array.from(this.indexMap.removedIndices).sort((a, b) => a - b);
|
|
}
|
|
/**
|
|
* Get all indices that were updated in the final array, sorted ascending.
|
|
*
|
|
* @returns Array of updated indices (e.g., [1, 3, 7])
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const updated = changeDesc.getUpdatedIndices();
|
|
* console.log(`Updated ${updated.length} items at indices: ${updated}`);
|
|
* ```
|
|
*/
|
|
getUpdatedIndices() {
|
|
return Array.from(this.indexMap.updatedIndices).sort((a, b) => a - b);
|
|
}
|
|
/**
|
|
* Iterate over preserved elements, mapping source index to destination index.
|
|
* Only calls callback for elements that were NOT removed.
|
|
*
|
|
* **Use this for:**
|
|
* - Tracking which elements moved (when sourceIndex !== destIndex)
|
|
* - Generating diff metadata (added/removed/moved items)
|
|
* - Understanding index shifts caused by prepends/removes
|
|
*
|
|
* @param callback - Called for each preserved element with (sourceIndex, destIndex)
|
|
*
|
|
* @example Detecting moved items
|
|
* ```typescript
|
|
* const movedItems = new Set<number>();
|
|
* changeDesc.forEachPreservedIndex((srcIdx, destIdx) => {
|
|
* if (srcIdx !== destIdx) {
|
|
* movedItems.add(destIdx);
|
|
* }
|
|
* });
|
|
* ```
|
|
*/
|
|
forEachPreservedIndex(callback2) {
|
|
const { originalLength, removedIndices, totalPrependCount } = this.indexMap;
|
|
let removalsBeforeCount = 0;
|
|
const sortedRemovals = Array.from(removedIndices).sort((a, b) => a - b);
|
|
let removalIdx = 0;
|
|
for (let srcIdx = 0; srcIdx < originalLength; srcIdx++) {
|
|
while (removalIdx < sortedRemovals.length && sortedRemovals[removalIdx] < srcIdx) {
|
|
removalsBeforeCount++;
|
|
removalIdx++;
|
|
}
|
|
if (!removedIndices.has(srcIdx)) {
|
|
const destIdx = srcIdx + totalPrependCount - removalsBeforeCount;
|
|
callback2(srcIdx, destIdx);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Get the values that were prepended to the beginning of the array.
|
|
*
|
|
* These values are stored during change description construction and can be used
|
|
* to avoid reprocessing prepended data.
|
|
*
|
|
* @returns Array of prepended values in order
|
|
*
|
|
* @example Processing prepended data
|
|
* ```typescript
|
|
* const prependedData = changeDesc.getPrependedValues<DataRow>();
|
|
* for (const row of prependedData) {
|
|
* processRow(row);
|
|
* }
|
|
* ```
|
|
*/
|
|
getPrependedValues() {
|
|
return this.prependValues;
|
|
}
|
|
/**
|
|
* Get the values that were appended to the end of the array.
|
|
*
|
|
* These values are stored during change description construction and can be used
|
|
* to avoid reprocessing appended data.
|
|
*
|
|
* @returns Array of appended values in order
|
|
*
|
|
* @example Processing appended data
|
|
* ```typescript
|
|
* const appendedData = changeDesc.getAppendedValues<DataRow>();
|
|
* for (const row of appendedData) {
|
|
* processRow(row);
|
|
* }
|
|
* ```
|
|
*/
|
|
getAppendedValues() {
|
|
return this.appendValues;
|
|
}
|
|
/**
|
|
* Get the values that were inserted at arbitrary indices.
|
|
*
|
|
* These values are stored during change description construction and can be used
|
|
* to avoid reprocessing inserted data.
|
|
*
|
|
* @returns Array of insertion values in the order they appear in splice operations
|
|
*
|
|
* @example Processing inserted data
|
|
* ```typescript
|
|
* const insertedData = changeDesc.getInsertionValues<DataRow>();
|
|
* for (const row of insertedData) {
|
|
* processRow(row);
|
|
* }
|
|
* ```
|
|
*/
|
|
getInsertionValues() {
|
|
return this.insertionValues;
|
|
}
|
|
/**
|
|
* Applies the transformation to an array in-place using native Array operations.
|
|
* This is a zero-copy operation that mutates the array directly.
|
|
*
|
|
* **Use this for:**
|
|
* - Transforming processed data arrays (keys, columns, invalidity)
|
|
* - Applying prepends, removals, and appends in a single pass
|
|
* - Maintaining synchronization between data and processed arrays
|
|
*
|
|
* **How it works:**
|
|
* 1. Applies splice operations in order (prepends, removals, appends)
|
|
* 2. Calls processInsertion callback for each inserted element
|
|
* 3. Mutates the array in-place for zero-copy efficiency
|
|
*
|
|
* @param array - The array to transform in-place (will be mutated)
|
|
* @param processInsertion - Callback to generate values for inserted indices
|
|
*
|
|
* @example Transforming a column array
|
|
* ```typescript
|
|
* // Transform processed column to match new data layout
|
|
* const insertionCache = new Map(); // Pre-computed processed values
|
|
* changeDesc.applyToArray(columnArray, (destIndex) => {
|
|
* return insertionCache.get(destIndex) ?? defaultValue;
|
|
* });
|
|
* ```
|
|
*
|
|
* @example Transforming an invalidity array
|
|
* ```typescript
|
|
* // Transform invalidity flags to match new data
|
|
* changeDesc.applyToArray(invalidityArray, (destIndex) => {
|
|
* const cached = insertionCache.get(destIndex);
|
|
* return cached?.hasInvalidKey ?? false;
|
|
* });
|
|
* ```
|
|
*/
|
|
applyToArray(array2, processInsertion, onRemove) {
|
|
const { spliceOps, finalLength, originalLength } = this.indexMap;
|
|
if (originalLength === finalLength && spliceOps.length === 0) {
|
|
return;
|
|
}
|
|
for (const op of spliceOps) {
|
|
const insertElements = op.insertCount > 0 ? Array.from({ length: op.insertCount }, function processOpInsertion(_, j) {
|
|
return processInsertion(op.index + j);
|
|
}) : [];
|
|
const removed = array2.splice(op.index, op.deleteCount, ...insertElements);
|
|
if (onRemove && removed.length > 0) {
|
|
onRemove(removed, op);
|
|
}
|
|
}
|
|
if (array2.length !== finalLength) {
|
|
array2.length = finalLength;
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/data-model/reducers/bandedReducer.ts
|
|
var BandedReducer = class extends BandedStructure {
|
|
constructor(config = {}) {
|
|
super(config);
|
|
this.lastDirtyBandCount = 0;
|
|
this.lastScanRatio = 0;
|
|
this.statsCaptured = false;
|
|
}
|
|
/**
|
|
* Creates a new reducer band with undefined cached result.
|
|
*/
|
|
createBand(startIndex, endIndex) {
|
|
return {
|
|
startIndex,
|
|
endIndex,
|
|
cachedResult: void 0,
|
|
isDirty: true
|
|
};
|
|
}
|
|
/**
|
|
* Initializes bands and resets stats capture flag.
|
|
*/
|
|
initializeBands(dataSize) {
|
|
super.initializeBands(dataSize);
|
|
this.statsCaptured = false;
|
|
}
|
|
/**
|
|
* Gets the array of bands for direct access.
|
|
* For production code, prefer using evaluateFromData() and getResult() for symmetry with BandedDomain.
|
|
* This method is primarily for testing and debugging band structure.
|
|
*/
|
|
getBands() {
|
|
return this.bands;
|
|
}
|
|
/**
|
|
* Evaluates a reducer across all bands, reusing cached results for clean bands.
|
|
* Symmetrical to BandedDomain.extendBandsFromData().
|
|
*
|
|
* @param def Reducer definition with reducer function, initial value, and overlap settings
|
|
* @param context Reducer context containing raw data and key columns
|
|
* @param reuseCleanBands Whether to reuse cached results for clean bands (default: false)
|
|
*/
|
|
evaluateFromData(def, context, reuseCleanBands = false) {
|
|
const reducerFn = def.reducer();
|
|
for (const band of this.bands) {
|
|
if (reuseCleanBands && !band.isDirty) {
|
|
continue;
|
|
}
|
|
const startIndex = def.needsOverlap && band.startIndex > 0 ? Math.max(0, band.startIndex - 1) : band.startIndex;
|
|
const result = this.evaluateRange(def, reducerFn, context, startIndex, band.endIndex);
|
|
band.cachedResult = result;
|
|
band.isDirty = false;
|
|
}
|
|
}
|
|
/**
|
|
* Combines all band results to get the final aggregated value.
|
|
* Symmetrical to BandedDomain.getDomain().
|
|
*
|
|
* @param def Reducer definition with combineResults function
|
|
* @returns Combined result from all bands
|
|
*/
|
|
getResult(def) {
|
|
const bandResults = this.bands.map((band) => band.cachedResult);
|
|
return def.combineResults(bandResults);
|
|
}
|
|
/**
|
|
* Evaluates a reducer over a specific range of data indices.
|
|
* Symmetrical to BandedDomain's band scanning loop in extendBandsFromData().
|
|
*
|
|
* @param def Reducer definition with initial value
|
|
* @param reducer Reducer function to apply
|
|
* @param context Reducer context with data and keys
|
|
* @param startIndex Starting index (inclusive)
|
|
* @param endIndex Ending index (exclusive)
|
|
* @returns Accumulated reducer result for the range
|
|
*/
|
|
evaluateRange(def, reducer, context, startIndex, endIndex) {
|
|
let accValue = def.initialValue;
|
|
const { keyColumns, keysParam, rawData } = context;
|
|
const clampedEnd = Math.min(endIndex, rawData.length);
|
|
for (let datumIndex = startIndex; datumIndex < clampedEnd; datumIndex += 1) {
|
|
for (let keyIdx = 0; keyIdx < keysParam.length; keyIdx++) {
|
|
keysParam[keyIdx] = keyColumns[keyIdx]?.[datumIndex];
|
|
}
|
|
accValue = reducer(accValue, keysParam);
|
|
}
|
|
return accValue;
|
|
}
|
|
/**
|
|
* Capture the current dirty state before processing.
|
|
* Call this before marking bands as clean to preserve stats for reporting.
|
|
*/
|
|
captureStatsBeforeProcessing() {
|
|
const dirtyBands = this.bands.filter((band) => band.isDirty);
|
|
const dirtySpan = dirtyBands.reduce((sum, band) => sum + (band.endIndex - band.startIndex), 0);
|
|
this.lastDirtyBandCount = dirtyBands.length;
|
|
this.lastScanRatio = this.dataSize > 0 ? dirtySpan / this.dataSize : 0;
|
|
this.statsCaptured = true;
|
|
}
|
|
/**
|
|
* Returns reducer-specific statistics including cache hits and scan ratio.
|
|
*/
|
|
getStats() {
|
|
const cleanBands = this.bands.filter((band) => !band.isDirty && band.cachedResult !== void 0);
|
|
let dirtyBands;
|
|
let scanRatio;
|
|
if (this.statsCaptured) {
|
|
dirtyBands = this.lastDirtyBandCount;
|
|
scanRatio = this.lastScanRatio;
|
|
} else {
|
|
const currentDirtyBands = this.bands.filter((band) => band.isDirty);
|
|
const dirtySpan = currentDirtyBands.reduce((sum, band) => sum + (band.endIndex - band.startIndex), 0);
|
|
dirtyBands = currentDirtyBands.length;
|
|
scanRatio = this.dataSize > 0 ? dirtySpan / this.dataSize : 0;
|
|
}
|
|
return {
|
|
totalBands: this.bands.length,
|
|
dirtyBands,
|
|
dataSize: this.dataSize,
|
|
scanRatio,
|
|
cacheHits: cleanBands.length
|
|
};
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/data-model/reducers/reducerManager.ts
|
|
var ReducerManager = class {
|
|
constructor(bandingConfig = {}) {
|
|
this.bandingConfig = bandingConfig;
|
|
}
|
|
/**
|
|
* Evaluates a reducer over a specific range of data indices.
|
|
* Used for non-banded reducer evaluation (fallback path).
|
|
*
|
|
* This is a static utility method for cases where banding is not applicable
|
|
* (e.g., grouped data, small datasets, reducers that don't support banding).
|
|
*
|
|
* @param def Reducer definition with initial value
|
|
* @param reducer Reducer function to apply
|
|
* @param context Reducer context with data and keys
|
|
* @param startIndex Starting index (inclusive)
|
|
* @param endIndex Ending index (exclusive)
|
|
* @returns Accumulated reducer result for the range
|
|
*/
|
|
static evaluateRange(def, reducer, context, startIndex, endIndex) {
|
|
let accValue = def.initialValue;
|
|
const { keyColumns, keysParam, rawData } = context;
|
|
const clampedEnd = Math.min(endIndex, rawData.length);
|
|
for (let datumIndex = startIndex; datumIndex < clampedEnd; datumIndex += 1) {
|
|
for (let keyIdx = 0; keyIdx < keysParam.length; keyIdx++) {
|
|
keysParam[keyIdx] = keyColumns[keyIdx]?.[datumIndex];
|
|
}
|
|
accValue = reducer(accValue, keysParam);
|
|
}
|
|
return accValue;
|
|
}
|
|
/**
|
|
* Evaluates a banded reducer and returns the aggregated result.
|
|
* Symmetrical to DomainManager.recomputeDomains().
|
|
*
|
|
* @param def Reducer definition
|
|
* @param processedData Processed data containing raw data and keys
|
|
* @param options Evaluation options including band reuse settings
|
|
* @returns Aggregated reducer result
|
|
*/
|
|
evaluate(def, processedData, options = {}) {
|
|
var _a;
|
|
const context = this.createContext(def, processedData);
|
|
if (!context) {
|
|
return void 0;
|
|
}
|
|
processedData[_a = REDUCER_BANDS] ?? (processedData[_a] = /* @__PURE__ */ new Map());
|
|
const reducerBands = processedData[REDUCER_BANDS];
|
|
const property = def.property;
|
|
let bandManager = reducerBands.get(property);
|
|
if (!bandManager) {
|
|
bandManager = new BandedReducer(this.bandingConfig);
|
|
reducerBands.set(property, bandManager);
|
|
}
|
|
if (bandManager.getBandCount() === 0) {
|
|
bandManager.initializeBands(context.rawData.length);
|
|
}
|
|
options.beforeEvaluate?.(bandManager, context);
|
|
bandManager.captureStatsBeforeProcessing();
|
|
bandManager.evaluateFromData(def, context, options.reuseCleanBands ?? false);
|
|
return bandManager.getResult(def);
|
|
}
|
|
/**
|
|
* Applies index map transformations to all reducer bands.
|
|
* Symmetrical to DomainManager's band update logic.
|
|
*
|
|
* @param processedData Processed data containing reducer bands
|
|
* @param indexMap Index map with splice operations and updated indices
|
|
*/
|
|
applyIndexMap(processedData, indexMap) {
|
|
const reducerBands = processedData[REDUCER_BANDS];
|
|
if (!reducerBands)
|
|
return;
|
|
for (const bandManager of reducerBands.values()) {
|
|
bandManager.applyIndexMap(indexMap);
|
|
}
|
|
}
|
|
/**
|
|
* Creates a reducer context from processed data.
|
|
* Extracts raw data and key columns for the appropriate scope.
|
|
* Symmetrical to domain context creation in DomainManager.
|
|
*
|
|
* @param def Reducer definition
|
|
* @param processedData Processed data
|
|
* @returns Reducer context with scope information, or undefined if not applicable
|
|
*/
|
|
createContext(def, processedData) {
|
|
if (processedData.type !== "ungrouped") {
|
|
return void 0;
|
|
}
|
|
const scopeId = isScoped(def) ? def.scopes[0] : first(processedData.dataSources.keys());
|
|
if (scopeId == null) {
|
|
return void 0;
|
|
}
|
|
const rawData = processedData.dataSources.get(scopeId)?.data ?? [];
|
|
const keyColumns = processedData.keys.map((column) => column.get(scopeId)).filter((column) => column != null);
|
|
const keysParam = keyColumns.map(() => void 0);
|
|
return { scopeId, rawData, keyColumns, keysParam };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/data-model/incremental/incrementalProcessor.ts
|
|
var IncrementalProcessor = class {
|
|
constructor(ctx, reducerManager) {
|
|
this.ctx = ctx;
|
|
this.reducerManager = reducerManager;
|
|
}
|
|
/**
|
|
* Checks if incremental reprocessing is supported for the given data configuration.
|
|
*/
|
|
isReprocessingSupported(processedData) {
|
|
if (processedData.type === "grouped") {
|
|
if (!processedData.groupsUnique)
|
|
return false;
|
|
const uniqueDataSets = this.getUniqueDataSets(processedData);
|
|
if (uniqueDataSets.size !== 1)
|
|
return false;
|
|
const scope = first(processedData.scopes);
|
|
const invalidKeys = processedData.invalidKeys?.get(scope);
|
|
if (invalidKeys?.some((invalid) => invalid))
|
|
return false;
|
|
}
|
|
if (this.ctx.aggregates.length > 0)
|
|
return false;
|
|
const hasUnsupportedReducers = this.ctx.reducers.some(
|
|
(reducer) => reducer.supportsBanding !== true || typeof reducer.combineResults !== "function"
|
|
);
|
|
if (hasUnsupportedReducers)
|
|
return false;
|
|
const hasUnsupportedProcessors = this.ctx.processors.some(
|
|
(processor) => processor.incrementalCalculate === void 0
|
|
);
|
|
if (hasUnsupportedProcessors)
|
|
return false;
|
|
if (this.ctx.propertyProcessors.length > 0)
|
|
return false;
|
|
return this.ctx.groupProcessors.every((p) => p.supportsReprocessing ?? false);
|
|
}
|
|
/**
|
|
* Performs incremental reprocessing of data based on change descriptions.
|
|
*/
|
|
reprocessData(processedData, dataSets, getProcessValue, reprocessGroupProcessorsFn, recomputeDomainsFn, collectOptimizationMetadataFn) {
|
|
const start2 = performance.now();
|
|
const scopeChanges = this.collectScopeChanges(processedData, dataSets);
|
|
if (scopeChanges.size === 0) {
|
|
return processedData;
|
|
}
|
|
this.commitPendingTransactions(processedData);
|
|
const keyProcessors = this.buildDefinitionProcessors(this.ctx.keys, getProcessValue);
|
|
const valueProcessors = this.buildDefinitionProcessors(this.ctx.values, getProcessValue);
|
|
const insertionCaches = this.processAllInsertions(processedData, scopeChanges, keyProcessors, valueProcessors);
|
|
this.processAllUpdates(processedData, scopeChanges, keyProcessors, valueProcessors, insertionCaches);
|
|
this.updateBandsForChanges(processedData, scopeChanges);
|
|
const removedKeys = this.transformKeysArrays(processedData, scopeChanges, insertionCaches);
|
|
this.transformColumnsArrays(processedData, scopeChanges, insertionCaches);
|
|
this.transformInvalidityArrays(processedData, scopeChanges, insertionCaches);
|
|
this.reprocessBandedReducers(processedData, scopeChanges);
|
|
if (processedData.type === "grouped") {
|
|
this.transformGroupsArray(processedData, scopeChanges, insertionCaches);
|
|
if (this.ctx.groupProcessors.length > 0) {
|
|
reprocessGroupProcessorsFn(processedData, scopeChanges);
|
|
}
|
|
}
|
|
this.invalidateSortOrdersForChanges(processedData, scopeChanges);
|
|
recomputeDomainsFn(processedData);
|
|
this.reprocessProcessors(processedData);
|
|
if (processedData.reduced?.diff != null && scopeChanges.size > 0) {
|
|
this.generateDiffMetadata(processedData, scopeChanges, removedKeys);
|
|
}
|
|
this.updateProcessedDataMetadata(processedData);
|
|
const end3 = performance.now();
|
|
processedData.time = end3 - start2;
|
|
processedData.version += 1;
|
|
collectOptimizationMetadataFn(processedData, "reprocess");
|
|
const uniqueChanges = uniqueChangeDescriptions(scopeChanges);
|
|
processedData.changeDescription = uniqueChanges.size === 1 ? uniqueChanges.values().next().value : void 0;
|
|
return processedData;
|
|
}
|
|
/**
|
|
* Updates banded domains based on pending changes.
|
|
*
|
|
* BANDING OPTIMIZATION:
|
|
* - Divides large datasets into bands (default ~100 bands)
|
|
* - Tracks which bands are "dirty" and need recalculation
|
|
* - During updates, only dirty bands are reprocessed
|
|
* - Significantly reduces domain calculation overhead for large datasets
|
|
*
|
|
* Example: 1M data points → 100 bands of 10K points each
|
|
* Adding 1000 points only dirties 1-2 bands instead of scanning all 1M points
|
|
*
|
|
* This optimizes domain recalculation by only marking affected bands as dirty.
|
|
* Deduplicates change descriptions to avoid processing the same changes multiple times
|
|
* when multiple scopes share the same DataSet.
|
|
*/
|
|
updateBandsForChanges(processedData, scopeChanges) {
|
|
const bandedDomains = processedData[DOMAIN_BANDS];
|
|
if (bandedDomains.size === 0)
|
|
return;
|
|
const processedChangeDescs = uniqueChangeDescriptions(scopeChanges);
|
|
for (const changeDesc of processedChangeDescs) {
|
|
const { indexMap } = changeDesc;
|
|
for (const domain of bandedDomains.values()) {
|
|
domain.applyIndexMap(indexMap);
|
|
}
|
|
}
|
|
}
|
|
reprocessBandedReducers(processedData, scopeChanges) {
|
|
if (processedData.type !== "ungrouped")
|
|
return;
|
|
const bandedReducers = this.ctx.reducers.filter(
|
|
(reducer) => reducer.supportsBanding && typeof reducer.combineResults === "function"
|
|
);
|
|
if (bandedReducers.length === 0)
|
|
return;
|
|
processedData.reduced ?? (processedData.reduced = {});
|
|
for (const def of bandedReducers) {
|
|
const result = this.reducerManager.evaluate(def, processedData, {
|
|
reuseCleanBands: true,
|
|
beforeEvaluate: (bandManager, context) => {
|
|
if (!context.scopeId)
|
|
return;
|
|
const changeDesc = scopeChanges.get(context.scopeId);
|
|
if (changeDesc) {
|
|
bandManager.applyIndexMap(changeDesc.indexMap);
|
|
}
|
|
}
|
|
});
|
|
if (result !== void 0) {
|
|
processedData.reduced[def.property] = result;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Collects change descriptions from all DataSets before committing.
|
|
*/
|
|
collectScopeChanges(processedData, dataSets) {
|
|
const scopeChanges = /* @__PURE__ */ new Map();
|
|
for (const [scopeId, dataSet] of processedData.dataSources) {
|
|
const changeDesc = dataSets?.get(dataSet) ?? dataSet.getChangeDescription();
|
|
if (changeDesc) {
|
|
scopeChanges.set(scopeId, changeDesc);
|
|
}
|
|
}
|
|
return scopeChanges;
|
|
}
|
|
/**
|
|
* Commits all pending transactions to the data arrays.
|
|
* Deduplicates DataSets to avoid committing the same DataSet multiple times
|
|
* when multiple scopes share the same DataSet.
|
|
*/
|
|
commitPendingTransactions(processedData) {
|
|
const uniqueDataSets = this.getUniqueDataSets(processedData);
|
|
for (const dataSet of uniqueDataSets) {
|
|
dataSet.commitPendingTransactions();
|
|
}
|
|
}
|
|
buildDefinitionProcessors(defs, getProcessValue) {
|
|
return defs.map((def, index) => ({
|
|
def,
|
|
index,
|
|
processValue: getProcessValue(def)
|
|
}));
|
|
}
|
|
/**
|
|
* Pre-processes all insertions once per scope to avoid redundant computation.
|
|
*/
|
|
processAllInsertions(processedData, scopeChanges, keyProcessors, valueProcessors) {
|
|
const insertionCaches = /* @__PURE__ */ new Map();
|
|
for (const [scope, changeDesc] of scopeChanges) {
|
|
const dataSet = processedData.dataSources.get(scope);
|
|
if (!dataSet)
|
|
continue;
|
|
const cache = this.processInsertionsOnce(scope, changeDesc, dataSet, keyProcessors, valueProcessors);
|
|
insertionCaches.set(scope, cache);
|
|
}
|
|
return insertionCaches;
|
|
}
|
|
/**
|
|
* Processes all updated items once per scope, adding them to the insertion cache.
|
|
* This ensures updated values are available when transforming columns/keys arrays.
|
|
*/
|
|
processAllUpdates(processedData, scopeChanges, keyProcessors, valueProcessors, insertionCaches) {
|
|
for (const [scope, changeDesc] of scopeChanges) {
|
|
const dataSet = processedData.dataSources.get(scope);
|
|
if (!dataSet)
|
|
continue;
|
|
const updatedIndices = changeDesc.getUpdatedIndices();
|
|
if (updatedIndices.length === 0)
|
|
continue;
|
|
let cache = insertionCaches.get(scope);
|
|
if (!cache) {
|
|
cache = /* @__PURE__ */ new Map();
|
|
insertionCaches.set(scope, cache);
|
|
}
|
|
for (const destIndex of updatedIndices) {
|
|
if (destIndex < 0 || destIndex >= dataSet.data.length) {
|
|
continue;
|
|
}
|
|
const processed = this.processDatum(dataSet, destIndex, scope, keyProcessors, valueProcessors);
|
|
if (processed) {
|
|
cache.set(destIndex, processed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Processes all insertions for a given scope once, caching the results.
|
|
* Returns a map from ADJUSTED destIndex to processed values for all keys and values.
|
|
* The adjusted destIndex accounts for out-of-bounds insertions that need to be shifted.
|
|
*/
|
|
processInsertionsOnce(scope, changeDesc, dataSet, keyProcessors, valueProcessors) {
|
|
const cache = /* @__PURE__ */ new Map();
|
|
const { finalLength } = changeDesc.indexMap;
|
|
for (const op of changeDesc.indexMap.spliceOps) {
|
|
if (op.insertCount <= 0)
|
|
continue;
|
|
for (let i = 0; i < op.insertCount; i++) {
|
|
const destIndex = op.index + i;
|
|
if (destIndex < 0 || destIndex >= finalLength) {
|
|
continue;
|
|
}
|
|
const processed = this.processDatum(dataSet, destIndex, scope, keyProcessors, valueProcessors);
|
|
if (processed) {
|
|
cache.set(destIndex, processed);
|
|
}
|
|
}
|
|
}
|
|
return cache;
|
|
}
|
|
/**
|
|
* Processes a single datum for the given scope, returning cached key/value results.
|
|
* Shared between insert and update paths to keep behaviour consistent.
|
|
*/
|
|
processDatum(dataSet, destIndex, scope, keyProcessors, valueProcessors) {
|
|
const datum = dataSet.data[destIndex];
|
|
const keys = /* @__PURE__ */ new Map();
|
|
const values = /* @__PURE__ */ new Map();
|
|
let hasInvalidKey = false;
|
|
let hasInvalidValue = false;
|
|
if (datum == null || typeof datum !== "object") {
|
|
hasInvalidKey = true;
|
|
hasInvalidValue = true;
|
|
} else {
|
|
for (const { index: keyDefIndex, def: keyDef, processValue: processKeyValue } of keyProcessors) {
|
|
if (!keyDef.scopes?.includes(scope))
|
|
continue;
|
|
const result = processKeyValue(datum, destIndex, scope);
|
|
keys.set(keyDefIndex, { value: result.value, valid: result.valid });
|
|
if (!result.valid) {
|
|
hasInvalidKey = true;
|
|
}
|
|
}
|
|
for (const { index: valueDefIndex, def: valueDef, processValue: processValueForDef } of valueProcessors) {
|
|
if (!valueDef.scopes?.includes(scope))
|
|
continue;
|
|
const result = processValueForDef(datum, destIndex, valueDef.scopes);
|
|
values.set(valueDefIndex, { value: result.value, valid: result.valid });
|
|
if (!result.valid) {
|
|
hasInvalidValue = true;
|
|
}
|
|
}
|
|
}
|
|
return { keys, values, hasInvalidKey, hasInvalidValue };
|
|
}
|
|
/**
|
|
* Generic utility to transform arrays using cached insertion results.
|
|
* This reduces duplication across transformKeysArrays, transformColumnsArrays, and transformInvalidityArrays.
|
|
*/
|
|
transformArraysWithCache(definitions, scopeChanges, insertionCaches, getArray, getScopes, extractValue) {
|
|
for (const [defIndex, def] of definitions.entries()) {
|
|
for (const scope of getScopes(def)) {
|
|
const changeDesc = scopeChanges.get(scope);
|
|
if (!changeDesc)
|
|
continue;
|
|
const array2 = getArray(defIndex, scope);
|
|
if (!array2)
|
|
continue;
|
|
const insertionCache = insertionCaches.get(scope);
|
|
this.applyChangeDescWithCache(
|
|
changeDesc,
|
|
array2,
|
|
insertionCache,
|
|
(cached, _destIndex) => extractValue(cached, def, defIndex)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Transforms keys arrays using cached insertion results.
|
|
*/
|
|
transformKeysArrays(processedData, scopeChanges, insertionCaches) {
|
|
const removedByScope = /* @__PURE__ */ new Map();
|
|
const ensureRemovedMetadata = (scope) => {
|
|
let metadata = removedByScope.get(scope);
|
|
if (!metadata) {
|
|
metadata = { tuples: [] };
|
|
removedByScope.set(scope, metadata);
|
|
}
|
|
return metadata;
|
|
};
|
|
const processedArrays = /* @__PURE__ */ new WeakSet();
|
|
for (const [defIndex, def] of this.ctx.keys.entries()) {
|
|
for (const scope of def.scopes ?? []) {
|
|
const changeDesc = scopeChanges.get(scope);
|
|
if (!changeDesc)
|
|
continue;
|
|
const keysArray = processedData.keys[defIndex]?.get(scope);
|
|
if (!keysArray)
|
|
continue;
|
|
if (processedArrays.has(keysArray)) {
|
|
const sourceScope = Array.from(processedData.keys[defIndex].entries()).find(
|
|
([_, arr]) => arr === keysArray
|
|
)?.[0];
|
|
if (sourceScope && sourceScope !== scope && removedByScope.has(sourceScope)) {
|
|
removedByScope.set(scope, removedByScope.get(sourceScope));
|
|
}
|
|
continue;
|
|
}
|
|
processedArrays.add(keysArray);
|
|
const insertionCache = insertionCaches.get(scope);
|
|
const removedMetadata = ensureRemovedMetadata(scope);
|
|
let removalCursor = 0;
|
|
this.applyChangeDescWithCache(
|
|
changeDesc,
|
|
keysArray,
|
|
insertionCache,
|
|
(cached) => {
|
|
const keyResult = cached?.keys.get(defIndex);
|
|
return keyResult?.valid ? keyResult.value : def.invalidValue;
|
|
},
|
|
(removedValues) => {
|
|
for (const value of removedValues) {
|
|
if (!removedMetadata.tuples[removalCursor]) {
|
|
removedMetadata.tuples[removalCursor] = new Array(this.ctx.keys.length);
|
|
}
|
|
removedMetadata.tuples[removalCursor][defIndex] = value;
|
|
removalCursor += 1;
|
|
}
|
|
}
|
|
);
|
|
}
|
|
}
|
|
const removedKeyStrings = /* @__PURE__ */ new Map();
|
|
for (const [scope, { tuples }] of removedByScope) {
|
|
if (tuples.length === 0)
|
|
continue;
|
|
const scopeSet = /* @__PURE__ */ new Set();
|
|
for (const tuple of tuples) {
|
|
const keyValues = [];
|
|
for (const [defIndex, value] of tuple.entries()) {
|
|
const keyDef = this.ctx.keys[defIndex];
|
|
if (!keyDef.scopes?.includes(scope))
|
|
continue;
|
|
keyValues.push(value);
|
|
}
|
|
if (keyValues.length > 0) {
|
|
scopeSet.add(toKeyString(keyValues));
|
|
}
|
|
}
|
|
removedKeyStrings.set(scope, scopeSet);
|
|
}
|
|
return removedKeyStrings;
|
|
}
|
|
/**
|
|
* Transforms columns arrays using cached insertion results.
|
|
*/
|
|
transformColumnsArrays(processedData, scopeChanges, insertionCaches) {
|
|
this.transformArraysWithCache(
|
|
this.ctx.values,
|
|
scopeChanges,
|
|
insertionCaches,
|
|
(defIndex) => processedData.columns[defIndex],
|
|
(def) => [first(def.scopes)],
|
|
(cached, def, defIndex) => {
|
|
if (cached) {
|
|
if (cached.hasInvalidKey) {
|
|
return def.invalidValue;
|
|
}
|
|
const valueResult = cached.values.get(defIndex);
|
|
return valueResult?.valid ? valueResult.value : def.invalidValue;
|
|
}
|
|
return def.invalidValue;
|
|
}
|
|
);
|
|
}
|
|
/**
|
|
* Helper to transform a scope-based invalidity map.
|
|
*/
|
|
transformInvalidityMap(invalidityMap, scopeChanges, insertionCaches, extractValue) {
|
|
const processedArrays = /* @__PURE__ */ new Set();
|
|
for (const [scope, changeDesc] of scopeChanges) {
|
|
let array2 = invalidityMap.get(scope);
|
|
if (!array2) {
|
|
const insertionCache2 = insertionCaches.get(scope);
|
|
const hasAnyInvalid = insertionCache2 && Array.from(insertionCache2.values()).some(extractValue);
|
|
if (hasAnyInvalid) {
|
|
array2 = createArray(changeDesc.indexMap.originalLength, false);
|
|
invalidityMap.set(scope, array2);
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
if (processedArrays.has(array2))
|
|
continue;
|
|
processedArrays.add(array2);
|
|
const insertionCache = insertionCaches.get(scope);
|
|
this.applyChangeDescWithCache(
|
|
changeDesc,
|
|
array2,
|
|
insertionCache,
|
|
(cached, _destIndex) => extractValue(cached)
|
|
);
|
|
}
|
|
}
|
|
/**
|
|
* Transforms invalidity arrays using cached insertion results.
|
|
*/
|
|
transformInvalidityArrays(processedData, scopeChanges, insertionCaches) {
|
|
if (processedData.invalidKeys) {
|
|
this.transformInvalidityMap(
|
|
processedData.invalidKeys,
|
|
scopeChanges,
|
|
insertionCaches,
|
|
(cached) => cached?.hasInvalidKey ?? false
|
|
);
|
|
}
|
|
if (processedData.invalidData) {
|
|
this.transformInvalidityMap(
|
|
processedData.invalidData,
|
|
scopeChanges,
|
|
insertionCaches,
|
|
(cached) => cached ? cached.hasInvalidKey || cached.hasInvalidValue : false
|
|
);
|
|
}
|
|
}
|
|
/**
|
|
* Applies a change description to an array using the provided cache-aware extractor.
|
|
* Shared by array transformation helpers to keep update logic consistent.
|
|
*/
|
|
applyChangeDescWithCache(changeDesc, target, insertionCache, extractValue, onRemove) {
|
|
changeDesc.applyToArray(
|
|
target,
|
|
(destIndex) => {
|
|
const cached = insertionCache?.get(destIndex);
|
|
return extractValue(cached, destIndex);
|
|
},
|
|
onRemove
|
|
);
|
|
const updatedIndices = changeDesc.getUpdatedIndices();
|
|
if (updatedIndices.length === 0)
|
|
return;
|
|
for (const destIndex of updatedIndices) {
|
|
if (destIndex < 0 || destIndex >= target.length) {
|
|
continue;
|
|
}
|
|
const cached = insertionCache?.get(destIndex);
|
|
target[destIndex] = extractValue(cached, destIndex);
|
|
}
|
|
}
|
|
/**
|
|
* Transforms the groups array for grouped data during reprocessing.
|
|
* Only called when groupsUnique=true and no invalid keys exist.
|
|
*
|
|
* This maintains the invariant: groups[i] corresponds to datum at columns[i].
|
|
*/
|
|
transformGroupsArray(processedData, scopeChanges, insertionCaches) {
|
|
const scope = first(processedData.scopes);
|
|
const changeDesc = scopeChanges.get(scope);
|
|
if (!changeDesc)
|
|
return;
|
|
const insertionCache = insertionCaches.get(scope);
|
|
for (const [, cached] of insertionCache ?? []) {
|
|
if (cached.hasInvalidKey) {
|
|
throw new Error(
|
|
"AG Charts - reprocessing grouped data with invalid keys not supported. This typically indicates a data quality issue that requires full reprocessing."
|
|
);
|
|
}
|
|
}
|
|
changeDesc.applyToArray(processedData.groups, (destIndex) => {
|
|
return this.createDataGroupForInsertion(destIndex, processedData, scope, insertionCache);
|
|
});
|
|
const updatedIndices = changeDesc.getUpdatedIndices();
|
|
if (updatedIndices.length > 0) {
|
|
for (const destIndex of updatedIndices) {
|
|
if (destIndex < 0 || destIndex >= processedData.groups.length) {
|
|
continue;
|
|
}
|
|
processedData.groups[destIndex] = this.createDataGroupForInsertion(
|
|
destIndex,
|
|
processedData,
|
|
scope,
|
|
insertionCache
|
|
);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Creates a new DataGroup for an inserted datum during reprocessing.
|
|
*
|
|
* When groupsUnique=true and no invalid keys exist, each datum has:
|
|
* - A unique set of keys
|
|
* - datumIndices[columnIdx] = [0] (relative offset is always 0)
|
|
* - All scopes are valid initially (unless invalid value detected)
|
|
*/
|
|
createDataGroupForInsertion(datumIndex, processedData, scope, insertionCache) {
|
|
const keys = [];
|
|
for (const keysMap of processedData.keys) {
|
|
const scopeKeys = keysMap.get(scope);
|
|
if (scopeKeys) {
|
|
keys.push(scopeKeys[datumIndex]);
|
|
}
|
|
}
|
|
const firstGroup = processedData.groups[0];
|
|
const allZeroDatumIndices = () => Object.freeze(createArray(processedData.columnScopes.length, SHARED_ZERO_INDICES));
|
|
const datumIndices = firstGroup?.datumIndices ?? allZeroDatumIndices();
|
|
const cached = insertionCache?.get(datumIndex);
|
|
const hasInvalidValue = cached?.hasInvalidValue ?? false;
|
|
let validScopes;
|
|
if (hasInvalidValue) {
|
|
validScopes = new Set(processedData.scopes);
|
|
validScopes.delete(scope);
|
|
} else {
|
|
validScopes = processedData.scopes;
|
|
}
|
|
return {
|
|
keys,
|
|
datumIndices,
|
|
aggregation: [],
|
|
// Empty - we don't support aggregates in reprocessing yet
|
|
validScopes
|
|
};
|
|
}
|
|
/**
|
|
* Generates diff metadata for animations and incremental rendering.
|
|
* This is an opt-in feature - only runs if diff tracking is already initialized.
|
|
*/
|
|
generateDiffMetadata(processedData, scopeChanges, removedKeys) {
|
|
const getKeyString = (scope, datumIndex) => {
|
|
const keys = [];
|
|
for (const keysMap of processedData.keys) {
|
|
const scopeKeys = keysMap.get(scope);
|
|
if (!scopeKeys)
|
|
return void 0;
|
|
keys.push(scopeKeys[datumIndex]);
|
|
}
|
|
return keys.length > 0 ? toKeyString(keys) : void 0;
|
|
};
|
|
for (const [scope, changeDesc] of scopeChanges) {
|
|
const diff9 = {
|
|
changed: true,
|
|
added: /* @__PURE__ */ new Set(),
|
|
removed: removedKeys.get(scope) ?? /* @__PURE__ */ new Set(),
|
|
updated: /* @__PURE__ */ new Set(),
|
|
moved: /* @__PURE__ */ new Set()
|
|
};
|
|
for (const op of changeDesc.indexMap.spliceOps) {
|
|
if (op.insertCount > 0) {
|
|
for (let i = 0; i < op.insertCount; i++) {
|
|
const datumIndex = op.index + i;
|
|
const keyStr = getKeyString(scope, datumIndex);
|
|
if (keyStr) {
|
|
diff9.added.add(keyStr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const { originalLength, totalPrependCount } = changeDesc.indexMap;
|
|
if (isAppendOnly(changeDesc.indexMap)) {
|
|
} else if (isPrependOnly(changeDesc.indexMap) && originalLength > 0) {
|
|
for (let destIndex = totalPrependCount; destIndex < totalPrependCount + originalLength; destIndex++) {
|
|
const keyStr = getKeyString(scope, destIndex);
|
|
if (keyStr)
|
|
diff9.moved.add(keyStr);
|
|
}
|
|
} else if (hasNoRemovals(changeDesc.indexMap) && totalPrependCount > 0) {
|
|
for (let sourceIndex = 0; sourceIndex < originalLength; sourceIndex++) {
|
|
const destIndex = sourceIndex + totalPrependCount;
|
|
const keyStr = getKeyString(scope, destIndex);
|
|
if (keyStr)
|
|
diff9.moved.add(keyStr);
|
|
}
|
|
} else {
|
|
changeDesc.forEachPreservedIndex((sourceIndex, destIndex) => {
|
|
if (sourceIndex !== destIndex) {
|
|
const keyStr = getKeyString(scope, destIndex);
|
|
if (keyStr)
|
|
diff9.moved.add(keyStr);
|
|
}
|
|
});
|
|
}
|
|
processedData.reduced.diff[scope] = diff9;
|
|
}
|
|
}
|
|
/**
|
|
* Updates metadata after array transformations.
|
|
* Uses intelligent cache management based on change patterns.
|
|
*/
|
|
updateProcessedDataMetadata(processedData) {
|
|
let maxDataLength = 0;
|
|
for (const dataSet of processedData.dataSources.values()) {
|
|
maxDataLength = Math.max(maxDataLength, dataSet.data.length);
|
|
}
|
|
processedData.input.count = maxDataLength;
|
|
let partialValidDataCount = 0;
|
|
for (const [scope, invalidData] of processedData.invalidData ?? /* @__PURE__ */ new Map()) {
|
|
const invalidKeys = processedData.invalidKeys?.get(scope);
|
|
for (let i = 0; i < invalidData.length; i++) {
|
|
if (invalidData[i] && !invalidKeys?.[i]) {
|
|
partialValidDataCount += 1;
|
|
}
|
|
}
|
|
}
|
|
processedData.partialValidDataCount = partialValidDataCount;
|
|
this.recountInvalid(processedData.invalidKeys, processedData.invalidKeyCount);
|
|
this.recountInvalid(processedData.invalidData, processedData.invalidDataCount);
|
|
this.invalidateCachesForChanges(processedData);
|
|
}
|
|
/**
|
|
* Updates sort order entry incrementally for appended values.
|
|
* Checks if new values maintain the existing ordering/uniqueness.
|
|
*/
|
|
updateSortOrderForAppend(entry, lastExistingValue, appendedValues) {
|
|
if (appendedValues.length === 0)
|
|
return;
|
|
const toNumeric = (v) => {
|
|
if (typeof v === "number")
|
|
return v;
|
|
if (v instanceof Date)
|
|
return v.valueOf();
|
|
return void 0;
|
|
};
|
|
let lastValue = toNumeric(lastExistingValue);
|
|
const existingSortOrder = entry.sortOrder;
|
|
for (const value of appendedValues) {
|
|
const numericValue = toNumeric(value);
|
|
if (numericValue === void 0)
|
|
continue;
|
|
if (lastValue === void 0) {
|
|
lastValue = numericValue;
|
|
continue;
|
|
}
|
|
const diff9 = numericValue - lastValue;
|
|
if (diff9 === 0) {
|
|
entry.isUnique = false;
|
|
}
|
|
if (entry.sortOrder !== void 0) {
|
|
let direction = 0;
|
|
if (diff9 > 0) {
|
|
direction = 1;
|
|
} else if (diff9 < 0) {
|
|
direction = -1;
|
|
}
|
|
if (direction !== 0 && direction !== existingSortOrder) {
|
|
entry.sortOrder = void 0;
|
|
}
|
|
}
|
|
lastValue = numericValue;
|
|
}
|
|
}
|
|
/**
|
|
* Updates KEY_SORT_ORDERS incrementally after an append operation.
|
|
*/
|
|
updateKeySortOrdersForAppend(processedData, originalLength) {
|
|
for (const [keyDefIndex, keysMap] of processedData.keys.entries()) {
|
|
const sortOrderEntry = processedData[KEY_SORT_ORDERS].get(keyDefIndex);
|
|
if (!sortOrderEntry)
|
|
continue;
|
|
const keysArray = first(keysMap.values());
|
|
if (!keysArray || keysArray.length <= originalLength)
|
|
continue;
|
|
const lastExistingValue = originalLength > 0 ? keysArray[originalLength - 1] : void 0;
|
|
const appendedValues = keysArray.slice(originalLength);
|
|
this.updateSortOrderForAppend(sortOrderEntry, lastExistingValue, appendedValues);
|
|
}
|
|
}
|
|
/**
|
|
* Invalidates sort order metadata BEFORE domain recomputation.
|
|
*
|
|
* This must be called BEFORE recomputeDomains() so that BandedDomain.setSortOrderMetadata()
|
|
* receives the correct (possibly cleared) metadata. Without this, rolling window operations
|
|
* would see stale sort order data and incorrectly configure sub-domains for sorted mode.
|
|
*
|
|
* @param anyKeyChanged - Whether any key values changed during update processing
|
|
*/
|
|
invalidateSortOrdersForChanges(processedData, scopeChanges) {
|
|
const changeDescs = uniqueChangeDescriptions(scopeChanges);
|
|
let preserveSortOrders = true;
|
|
let hasAppendOnly = false;
|
|
let hasRollingWindow = false;
|
|
let appendOriginalLength;
|
|
let rollingWindowInfo;
|
|
for (const changeDesc of changeDescs) {
|
|
const { indexMap } = changeDesc;
|
|
if (isUpdateOnly(indexMap)) {
|
|
} else if (isAppendOnly(indexMap)) {
|
|
hasAppendOnly = true;
|
|
appendOriginalLength = indexMap.originalLength;
|
|
} else if (hasOnlyRemovals(indexMap)) {
|
|
} else if (isRollingWindow(indexMap)) {
|
|
hasRollingWindow = true;
|
|
rollingWindowInfo = {
|
|
originalLength: indexMap.originalLength,
|
|
removedCount: indexMap.removedIndices.size
|
|
};
|
|
} else {
|
|
preserveSortOrders = false;
|
|
}
|
|
}
|
|
if (!preserveSortOrders) {
|
|
processedData[KEY_SORT_ORDERS].clear();
|
|
processedData[COLUMN_SORT_ORDERS].clear();
|
|
} else if (hasAppendOnly && appendOriginalLength !== void 0) {
|
|
this.updateKeySortOrdersForAppend(processedData, appendOriginalLength);
|
|
} else if (hasRollingWindow && rollingWindowInfo) {
|
|
this.updateKeySortOrdersForRollingWindow(processedData, rollingWindowInfo);
|
|
}
|
|
}
|
|
/**
|
|
* Updates KEY_SORT_ORDERS incrementally after a rolling window operation.
|
|
* Rolling window = contiguous removals at start + appends at end.
|
|
*/
|
|
updateKeySortOrdersForRollingWindow(processedData, info) {
|
|
const { originalLength, removedCount } = info;
|
|
for (const [keyDefIndex, keysMap] of processedData.keys.entries()) {
|
|
const sortOrderEntry = processedData[KEY_SORT_ORDERS].get(keyDefIndex);
|
|
if (!sortOrderEntry)
|
|
continue;
|
|
const keysArray = first(keysMap.values());
|
|
if (!keysArray || keysArray.length === 0)
|
|
continue;
|
|
const appendStartIndex = originalLength - removedCount;
|
|
const lastRemainingValue = appendStartIndex > 0 ? keysArray[appendStartIndex - 1] : void 0;
|
|
const appendedValues = keysArray.slice(appendStartIndex);
|
|
this.updateSortOrderForAppend(sortOrderEntry, lastRemainingValue, appendedValues);
|
|
}
|
|
}
|
|
/**
|
|
* Invalidates domain range caches after domain recomputation.
|
|
*
|
|
* Called AFTER recomputeDomains() to mark domain ranges as dirty for lazy rebuild.
|
|
* Sort order invalidation is handled separately by invalidateSortOrdersForChanges().
|
|
*/
|
|
invalidateCachesForChanges(processedData) {
|
|
this.markDomainRangesDirty(processedData[DOMAIN_RANGES]);
|
|
}
|
|
/**
|
|
* Marks all RangeLookup entries as dirty for lazy rebuild.
|
|
*/
|
|
markDomainRangesDirty(domainRanges) {
|
|
for (const rangeLookup of domainRanges.values()) {
|
|
rangeLookup.isDirty = true;
|
|
}
|
|
}
|
|
/**
|
|
* Recounts invalid entries for the given map into the provided counts map.
|
|
*/
|
|
recountInvalid(invalidMap, counts) {
|
|
if (!invalidMap || !counts)
|
|
return;
|
|
for (const [scope, invalidArray] of invalidMap) {
|
|
const invalidCount = invalidArray.filter(Boolean).length;
|
|
if (invalidCount === 0) {
|
|
invalidMap.delete(scope);
|
|
counts.delete(scope);
|
|
} else {
|
|
counts.set(scope, invalidCount);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Recomputes processor outputs using their incrementalCalculate hook when available.
|
|
* Falls back to calculate to avoid stale reducer outputs if a processor lacks the hook.
|
|
*/
|
|
reprocessProcessors(processedData) {
|
|
if (this.ctx.processors.length === 0)
|
|
return;
|
|
processedData.reduced ?? (processedData.reduced = {});
|
|
for (const def of this.ctx.processors) {
|
|
const previousValue = processedData.reduced[def.property];
|
|
const nextValue = def.incrementalCalculate?.(processedData, previousValue) ?? def.calculate(processedData, previousValue);
|
|
processedData.reduced[def.property] = nextValue;
|
|
}
|
|
}
|
|
/**
|
|
* Helper to get unique DataSets from processed data.
|
|
*/
|
|
getUniqueDataSets(processedData) {
|
|
return new Set(processedData.dataSources.values());
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/rangeLookup.ts
|
|
var MIN = 0;
|
|
var MAX = 1;
|
|
var SPAN = 2;
|
|
var RangeLookup = class _RangeLookup {
|
|
constructor(allValues) {
|
|
/** When true, the lookup needs to be rebuilt before use */
|
|
this.isDirty = false;
|
|
const dataLength = allValues.reduce((acc, v) => Math.max(acc, v.length), 0);
|
|
const { maxLevelSize, buffer } = _RangeLookup.createBuffer(dataLength);
|
|
this.maxLevelSize = maxLevelSize;
|
|
this.buffer = buffer;
|
|
this.dataLength = dataLength;
|
|
this.populateBuffer(allValues);
|
|
}
|
|
static computeMaxLevelSize(dataLength) {
|
|
const sizePower = 32 - Math.clz32(dataLength);
|
|
let maxLevelSize = 1 << sizePower;
|
|
if (dataLength === maxLevelSize / 2) {
|
|
maxLevelSize = maxLevelSize >>> 1;
|
|
}
|
|
return maxLevelSize;
|
|
}
|
|
static createBuffer(dataLength) {
|
|
const maxLevelSize = _RangeLookup.computeMaxLevelSize(dataLength);
|
|
const buffer = new Float64Array((maxLevelSize * 2 - 1) * 2).fill(Number.NaN);
|
|
return { maxLevelSize, buffer };
|
|
}
|
|
populateBuffer(allValues) {
|
|
const { maxLevelSize, buffer } = this;
|
|
const leafOffset = maxLevelSize - 1;
|
|
for (const values of allValues) {
|
|
const valuesLength = values.length;
|
|
for (let i = 0; i < valuesLength; i += 1) {
|
|
const value = Number(values[i]);
|
|
if (value !== value)
|
|
continue;
|
|
const bufferOffset = leafOffset + i << 1;
|
|
const prevMin = buffer[bufferOffset];
|
|
const prevMax = buffer[bufferOffset + 1];
|
|
if (prevMin !== prevMin || value < prevMin) {
|
|
buffer[bufferOffset] = value;
|
|
}
|
|
if (prevMax !== prevMax || value > prevMax) {
|
|
buffer[bufferOffset + 1] = value;
|
|
}
|
|
}
|
|
}
|
|
for (let size = maxLevelSize >>> 1; size >= 1; size >>>= 1) {
|
|
const start2 = size - 1;
|
|
const childStart = start2 + size << 1;
|
|
let nodeOffset = start2 << 1;
|
|
let leftOffset = childStart;
|
|
for (let i = 0; i < size; i += 1) {
|
|
const rightOffset = leftOffset + 2;
|
|
const aMin = buffer[leftOffset];
|
|
const bMin = buffer[rightOffset];
|
|
buffer[nodeOffset] = bMin !== bMin || aMin < bMin ? aMin : bMin;
|
|
const aMax = buffer[leftOffset + 1];
|
|
const bMax = buffer[rightOffset + 1];
|
|
buffer[nodeOffset + 1] = bMax !== bMax || aMax > bMax ? aMax : bMax;
|
|
nodeOffset += 2;
|
|
leftOffset += 4;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Rebuild the segment tree with new values, reusing the buffer if possible.
|
|
* Only allocates a new buffer if the data length requires a different maxLevelSize.
|
|
*/
|
|
rebuild(allValues) {
|
|
const dataLength = allValues.reduce((acc, v) => Math.max(acc, v.length), 0);
|
|
const requiredMaxLevelSize = _RangeLookup.computeMaxLevelSize(dataLength);
|
|
if (requiredMaxLevelSize === this.maxLevelSize) {
|
|
this.buffer.fill(Number.NaN);
|
|
} else {
|
|
const { maxLevelSize, buffer } = _RangeLookup.createBuffer(dataLength);
|
|
this.maxLevelSize = maxLevelSize;
|
|
this.buffer = buffer;
|
|
}
|
|
this.dataLength = dataLength;
|
|
this.populateBuffer(allValues);
|
|
}
|
|
/**
|
|
* Update values at a specific data index - O(k log n) where k is number of columns.
|
|
* After updating the leaf, propagates changes up to the root.
|
|
*
|
|
* @param dataIndex - Index in the data array (0-based)
|
|
* @param newValues - New values for this index from all columns
|
|
*/
|
|
updateValue(dataIndex, newValues) {
|
|
const { maxLevelSize, buffer } = this;
|
|
const bufferIndex = maxLevelSize + dataIndex - 1;
|
|
const bufferMinIndex = Math.trunc(bufferIndex * SPAN) + MIN;
|
|
const bufferMaxIndex = Math.trunc(bufferIndex * SPAN) + MAX;
|
|
buffer[bufferMinIndex] = Number.NaN;
|
|
buffer[bufferMaxIndex] = Number.NaN;
|
|
for (const value of newValues) {
|
|
const numValue = Number(value);
|
|
const prevMin = buffer[bufferMinIndex];
|
|
const prevMax = buffer[bufferMaxIndex];
|
|
if (!Number.isFinite(prevMin) || numValue < prevMin) {
|
|
buffer[bufferMinIndex] = numValue;
|
|
}
|
|
if (!Number.isFinite(prevMax) || numValue > prevMax) {
|
|
buffer[bufferMaxIndex] = numValue;
|
|
}
|
|
}
|
|
this.propagateUp(bufferIndex);
|
|
}
|
|
/**
|
|
* Batch update multiple values - O(k log n) per update.
|
|
* More efficient than individual updateValue calls when tracking dirty nodes.
|
|
*
|
|
* @param updates - Array of {index, values} pairs to update
|
|
*/
|
|
updateValues(updates) {
|
|
for (const { index, values } of updates) {
|
|
this.updateValue(index, values);
|
|
}
|
|
}
|
|
/**
|
|
* Propagate min/max changes from a leaf up to the root.
|
|
* Each level recalculates its min/max from its children.
|
|
*/
|
|
propagateUp(bufferIndex) {
|
|
const { buffer } = this;
|
|
while (bufferIndex > 0) {
|
|
const parentIndex = Math.trunc((bufferIndex - 1) / 2);
|
|
const leftChild = 2 * parentIndex + 1;
|
|
const rightChild = 2 * parentIndex + 2;
|
|
const leftMin = buffer[Math.trunc(leftChild * SPAN) + MIN];
|
|
const leftMax = buffer[Math.trunc(leftChild * SPAN) + MAX];
|
|
const rightMin = buffer[Math.trunc(rightChild * SPAN) + MIN];
|
|
const rightMax = buffer[Math.trunc(rightChild * SPAN) + MAX];
|
|
buffer[Math.trunc(parentIndex * SPAN) + MIN] = !Number.isFinite(rightMin) || leftMin < rightMin ? leftMin : rightMin;
|
|
buffer[Math.trunc(parentIndex * SPAN) + MAX] = !Number.isFinite(rightMax) || leftMax > rightMax ? leftMax : rightMax;
|
|
bufferIndex = parentIndex;
|
|
}
|
|
}
|
|
computeRangeInto(buffer, start2, end3, bufferIndex, currentStart, step, into) {
|
|
const currentEnd = currentStart + step - 1;
|
|
if (currentEnd < start2 || currentStart >= end3)
|
|
return into;
|
|
if (currentStart >= start2 && currentEnd < end3) {
|
|
const min = buffer[Math.trunc(bufferIndex * SPAN) + MIN];
|
|
const max = buffer[Math.trunc(bufferIndex * SPAN) + MAX];
|
|
if (Number.isFinite(min))
|
|
into[0] = Math.min(into[0], min);
|
|
if (Number.isFinite(max))
|
|
into[1] = Math.max(into[1], max);
|
|
} else if (step > 1) {
|
|
bufferIndex = Math.trunc(bufferIndex * 2);
|
|
step = Math.trunc(step / 2);
|
|
this.computeRangeInto(buffer, start2, end3, Math.trunc(bufferIndex + 1), currentStart, step, into);
|
|
this.computeRangeInto(buffer, start2, end3, Math.trunc(bufferIndex + 2), currentStart + step, step, into);
|
|
}
|
|
return into;
|
|
}
|
|
rangeBetween(start2, end3, into) {
|
|
const result = into ?? [0, 0];
|
|
if (start2 > end3) {
|
|
result[0] = Number.NaN;
|
|
result[1] = Number.NaN;
|
|
return result;
|
|
}
|
|
const { maxLevelSize, buffer } = this;
|
|
result[0] = Infinity;
|
|
result[1] = -Infinity;
|
|
this.computeRangeInto(buffer, start2, end3, 0, 0, maxLevelSize, result);
|
|
return result;
|
|
}
|
|
getRange(into) {
|
|
const { buffer } = this;
|
|
const result = into ?? [0, 0];
|
|
result[0] = buffer[MIN];
|
|
result[1] = buffer[MAX];
|
|
return result;
|
|
}
|
|
get range() {
|
|
const { buffer } = this;
|
|
return [buffer[MIN], buffer[MAX]];
|
|
}
|
|
/** The number of data elements in the segment tree */
|
|
get length() {
|
|
return this.dataLength;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/sortOrder.ts
|
|
function valuesSortOrder(values, needsValueOf) {
|
|
const valuesLength = values.length;
|
|
if (values.length <= 1)
|
|
return 1;
|
|
let order = 0;
|
|
let v0 = values[0];
|
|
for (let i = 1; i < valuesLength; i++) {
|
|
const v1 = values[i];
|
|
if (v1 == null)
|
|
continue;
|
|
const primitive = needsValueOf ? v1.valueOf() : v1;
|
|
if (typeof primitive !== "number")
|
|
return;
|
|
const diff9 = Math.sign(v1 - v0);
|
|
if (diff9 !== 0) {
|
|
if (order !== 0 && order !== diff9)
|
|
return;
|
|
order = diff9;
|
|
}
|
|
v0 = v1;
|
|
}
|
|
return order === 0 ? 1 : order;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/data/data-model/utils/resolvers.ts
|
|
var DataModelResolvers = class {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
this.rangeBetweenBuffer = [0, 0];
|
|
}
|
|
resolveMissingDataCount(scope) {
|
|
let missingDataCount = 0;
|
|
for (const value of this.ctx.values) {
|
|
missingDataCount = Math.max(missingDataCount, value.missing.get(scope.id) ?? 0);
|
|
}
|
|
return missingDataCount;
|
|
}
|
|
resolveProcessedDataDefById(scope, searchId) {
|
|
const def = this.ctx.scopeCache.get(scope.id)?.get(searchId);
|
|
if (!def) {
|
|
throw new Error(`AG Charts - didn't find property definition for [${searchId}, ${scope.id}]`);
|
|
}
|
|
return { index: def.index, def };
|
|
}
|
|
resolveProcessedDataIndexById(scope, searchId) {
|
|
return this.resolveProcessedDataDefById(scope, searchId).index;
|
|
}
|
|
resolveKeysById(scope, searchId, processedData) {
|
|
const index = this.resolveProcessedDataIndexById(scope, searchId);
|
|
const keys = processedData.keys[index];
|
|
if (keys == null) {
|
|
throw new Error(`AG Charts - didn't find keys for [${searchId}, ${scope.id}]`);
|
|
}
|
|
return keys.get(scope.id);
|
|
}
|
|
hasColumnById(scope, searchId) {
|
|
return this.ctx.scopeCache.get(scope.id)?.get(searchId) != null;
|
|
}
|
|
resolveColumnById(scope, searchId, processedData) {
|
|
const index = this.resolveProcessedDataIndexById(scope, searchId);
|
|
const column = processedData.columns?.[index];
|
|
if (column == null) {
|
|
throw new Error(`AG Charts - didn't find column for [${searchId}, ${scope.id}]`);
|
|
}
|
|
return column;
|
|
}
|
|
resolveColumnNeedsValueOf(scope, searchId, processedData) {
|
|
const index = this.resolveProcessedDataIndexById(scope, searchId);
|
|
return processedData.columnNeedValueOf?.[index] ?? true;
|
|
}
|
|
/**
|
|
* Converts a relative datum index to an absolute column index.
|
|
*
|
|
* INDEXING STRATEGY:
|
|
* - Relative index: Offset from the start of a group (stored in datumIndices)
|
|
* - Absolute index: Position in the full column array
|
|
* - Conversion: absoluteIndex = groupIndex + relativeIndex
|
|
*
|
|
* When groupsUnique=true, relativeIndex is always 0, making this a simple
|
|
* identity mapping. This optimization reduces memory usage significantly
|
|
* for large datasets with unique keys.
|
|
*
|
|
* @param groupIndex index of the group in ProcessedData.groups
|
|
* @param relativeDatumIndex relative index stored in group.datumIndices
|
|
* @returns absolute index for accessing columns
|
|
*/
|
|
resolveAbsoluteIndex(groupIndex, relativeDatumIndex) {
|
|
return groupIndex + relativeDatumIndex;
|
|
}
|
|
getDomain(scope, searchId, type, processedData) {
|
|
const domains = this.getDomainsByType(type ?? "value", processedData);
|
|
return domains?.[this.resolveProcessedDataIndexById(scope, searchId)] ?? [];
|
|
}
|
|
getDomainBetweenRange(scope, searchIds, [i0, i1], processedData) {
|
|
const columnIndices = searchIds.map((searchId) => this.resolveProcessedDataIndexById(scope, searchId));
|
|
const dataLength = processedData.input.count;
|
|
if (i0 <= 0 && i1 >= dataLength) {
|
|
const domains = processedData.domain.values;
|
|
let min = Infinity;
|
|
let max = -Infinity;
|
|
for (const columnIndex of columnIndices) {
|
|
const domain = domains[columnIndex];
|
|
if (domain != null) {
|
|
if (domain[0] < min)
|
|
min = domain[0];
|
|
if (domain[1] > max)
|
|
max = domain[1];
|
|
}
|
|
}
|
|
this.rangeBetweenBuffer[0] = min;
|
|
this.rangeBetweenBuffer[1] = max;
|
|
return this.rangeBetweenBuffer;
|
|
}
|
|
const cacheKey = columnIndices.join(":");
|
|
const domainRanges = processedData[DOMAIN_RANGES];
|
|
const values = columnIndices.map((columnIndex) => processedData.columns[columnIndex]);
|
|
let rangeLookup = domainRanges.get(cacheKey);
|
|
if (rangeLookup == null) {
|
|
rangeLookup = new RangeLookup(values);
|
|
domainRanges.set(cacheKey, rangeLookup);
|
|
} else if (rangeLookup.isDirty) {
|
|
rangeLookup.rebuild(values);
|
|
rangeLookup.isDirty = false;
|
|
}
|
|
return rangeLookup.rangeBetween(i0, i1, this.rangeBetweenBuffer);
|
|
}
|
|
getSortOrder(values, index, sortOrders, needsValueOf) {
|
|
const entry = sortOrders.get(index);
|
|
if (entry == null || entry.isDirty) {
|
|
const newEntry = { sortOrder: valuesSortOrder(values, needsValueOf) };
|
|
sortOrders.set(index, newEntry);
|
|
return newEntry.sortOrder;
|
|
}
|
|
return entry.sortOrder;
|
|
}
|
|
getKeySortOrder(scope, searchId, processedData) {
|
|
const columnIndex = this.resolveProcessedDataIndexById(scope, searchId);
|
|
const keys = processedData.keys[columnIndex]?.get(scope.id);
|
|
return keys ? this.getSortOrder(keys, columnIndex, processedData[KEY_SORT_ORDERS], true) : void 0;
|
|
}
|
|
getKeySortEntry(scope, searchId, processedData) {
|
|
const columnIndex = this.resolveProcessedDataIndexById(scope, searchId);
|
|
const entry = processedData[KEY_SORT_ORDERS].get(columnIndex);
|
|
return entry?.isDirty ? void 0 : entry;
|
|
}
|
|
getColumnSortOrder(scope, searchId, processedData) {
|
|
const columnIndex = this.resolveProcessedDataIndexById(scope, searchId);
|
|
const needsValueOf = processedData.columnNeedValueOf?.[columnIndex] ?? true;
|
|
return this.getSortOrder(
|
|
processedData.columns[columnIndex],
|
|
columnIndex,
|
|
processedData[COLUMN_SORT_ORDERS],
|
|
needsValueOf
|
|
);
|
|
}
|
|
getDomainsByType(type, processedData) {
|
|
switch (type) {
|
|
case "key":
|
|
return processedData.domain.keys;
|
|
case "value":
|
|
return processedData.domain.values;
|
|
case "aggregate":
|
|
return processedData.domain.aggValues;
|
|
case "group-value-processor":
|
|
return processedData.domain.groups;
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/data-model/utils/scopeCache.ts
|
|
var ScopeCacheManager = class {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
}
|
|
processScopeCache() {
|
|
this.ctx.scopeCache.clear();
|
|
for (const def of iterate(this.ctx.keys, this.ctx.values, this.ctx.aggregates)) {
|
|
if (!def.idsMap)
|
|
continue;
|
|
for (const [scope, ids] of def.idsMap) {
|
|
for (const id of ids) {
|
|
if (!this.ctx.scopeCache.has(scope)) {
|
|
this.ctx.scopeCache.set(scope, /* @__PURE__ */ new Map([[id, def]]));
|
|
} else if (this.ctx.scopeCache.get(scope)?.has(id)) {
|
|
throw new Error("duplicate definition ids on the same scope are not allowed.");
|
|
} else {
|
|
this.ctx.scopeCache.get(scope).set(id, def);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
valueGroupIdxLookup({ matchGroupIds }) {
|
|
const result = [];
|
|
for (const [index, def] of this.ctx.values.entries()) {
|
|
if (!matchGroupIds || def.groupId && matchGroupIds.includes(def.groupId)) {
|
|
result.push(index);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
valueIdxLookup(scopes, prop) {
|
|
const noScopesToMatch = scopes == null || scopes.length === 0;
|
|
const propId = typeof prop === "string" ? prop : prop.id;
|
|
const hasMatchingScopeId = (def) => {
|
|
if (def.idsMap) {
|
|
for (const [scope, ids] of def.idsMap) {
|
|
if (scopes?.includes(scope) && ids.has(propId)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
const result = this.ctx.values.reduce((res, def, index) => {
|
|
const validDefScopes = def.scopes == null || noScopesToMatch && !def.scopes.length || def.scopes.some((s) => scopes?.includes(s));
|
|
if (validDefScopes && (def.property === propId || def.id === propId || hasMatchingScopeId(def))) {
|
|
res.push(index);
|
|
}
|
|
return res;
|
|
}, []);
|
|
if (result.length === 0) {
|
|
throw new Error(
|
|
`AG Charts - configuration error, unknown property ${JSON.stringify(prop)} in scope(s) ${JSON.stringify(
|
|
scopes
|
|
)}`
|
|
);
|
|
}
|
|
return result;
|
|
}
|
|
buildAccessors(defs) {
|
|
const result = /* @__PURE__ */ new Map();
|
|
if (this.ctx.suppressFieldDotNotation) {
|
|
return result;
|
|
}
|
|
for (const def of defs) {
|
|
const isPath = def.property.includes(".") || def.property.includes("[");
|
|
if (!isPath)
|
|
continue;
|
|
const components = getPathComponents(def.property);
|
|
if (components == null) {
|
|
logger_exports.warnOnce("Invalid property path [%s]", def.property);
|
|
continue;
|
|
}
|
|
const accessor = createPathAccessor(components);
|
|
result.set(def.property, accessor);
|
|
}
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/data/dataModel.ts
|
|
var DataModel = class {
|
|
constructor(opts, mode = "standalone", suppressFieldDotNotation = false, eventsHub) {
|
|
this.opts = opts;
|
|
this.mode = mode;
|
|
this.suppressFieldDotNotation = suppressFieldDotNotation;
|
|
this.eventsHub = eventsHub;
|
|
this.debug = debugLogger_exports.create(true, "data-model");
|
|
this.scopeCache = /* @__PURE__ */ new Map();
|
|
this.keys = [];
|
|
this.values = [];
|
|
this.aggregates = [];
|
|
this.groupProcessors = [];
|
|
this.propertyProcessors = [];
|
|
this.reducers = [];
|
|
this.processors = [];
|
|
let keys = true;
|
|
for (const next of opts.props) {
|
|
if (next.type === "key" && !keys) {
|
|
throw new Error("AG Charts - internal config error: keys must come before values.");
|
|
}
|
|
if (next.type === "value" && keys) {
|
|
keys = false;
|
|
}
|
|
}
|
|
const verifyMatchGroupId = ({ matchGroupIds = [] }) => {
|
|
for (const matchGroupId of matchGroupIds) {
|
|
if (this.values.every((def) => def.groupId !== matchGroupId)) {
|
|
throw new Error(
|
|
`AG Charts - internal config error: matchGroupIds properties must match defined groups (${matchGroupId}).`
|
|
);
|
|
}
|
|
}
|
|
};
|
|
const keyScopes = /* @__PURE__ */ new Set();
|
|
const valueScopes = /* @__PURE__ */ new Set();
|
|
for (const def of opts.props) {
|
|
const scopes = def.type === "key" ? keyScopes : valueScopes;
|
|
if (isScoped(def)) {
|
|
if (def.scopes) {
|
|
for (const s of def.scopes) {
|
|
scopes.add(s);
|
|
}
|
|
}
|
|
}
|
|
switch (def.type) {
|
|
case "key":
|
|
this.keys.push({ ...def, index: this.keys.length, missing: /* @__PURE__ */ new Map() });
|
|
break;
|
|
case "value":
|
|
if (def.property == null) {
|
|
throw new Error(
|
|
`AG Charts - internal config error: no properties specified for value definitions: ${JSON.stringify(
|
|
def
|
|
)}`
|
|
);
|
|
}
|
|
this.values.push({ ...def, index: this.values.length, missing: /* @__PURE__ */ new Map() });
|
|
break;
|
|
case "aggregate":
|
|
verifyMatchGroupId(def);
|
|
this.aggregates.push({ ...def, index: this.aggregates.length });
|
|
break;
|
|
case "group-value-processor":
|
|
verifyMatchGroupId(def);
|
|
this.groupProcessors.push({ ...def, index: this.groupProcessors.length });
|
|
break;
|
|
case "property-value-processor":
|
|
this.propertyProcessors.push({ ...def, index: this.propertyProcessors.length });
|
|
break;
|
|
case "reducer":
|
|
this.reducers.push({ ...def, index: this.reducers.length });
|
|
break;
|
|
case "processor":
|
|
this.processors.push({ ...def, index: this.processors.length });
|
|
break;
|
|
}
|
|
}
|
|
if (!!this.opts.groupByKeys || this.opts.groupByFn != null) {
|
|
const ungroupedScopes = new Set(valueScopes.values());
|
|
for (const s of keyScopes) {
|
|
ungroupedScopes.delete(s);
|
|
}
|
|
if (ungroupedScopes.size > 0) {
|
|
throw new Error(
|
|
`AG Charts - scopes missing key for grouping, illegal configuration: ${[...ungroupedScopes.values()]}`
|
|
);
|
|
}
|
|
}
|
|
const ctx = {
|
|
keys: this.keys,
|
|
values: this.values,
|
|
aggregates: this.aggregates,
|
|
groupProcessors: this.groupProcessors,
|
|
propertyProcessors: this.propertyProcessors,
|
|
reducers: this.reducers,
|
|
processors: this.processors,
|
|
debug: this.debug,
|
|
mode: this.mode,
|
|
bandingConfig: this.opts.domainBandingConfig,
|
|
suppressFieldDotNotation: this.suppressFieldDotNotation,
|
|
scopeCache: this.scopeCache
|
|
};
|
|
this.resolvers = new DataModelResolvers(ctx);
|
|
this.scopeCacheManager = new ScopeCacheManager(ctx);
|
|
this.domainInitializer = new DomainInitializer(ctx);
|
|
this.domainManager = new DomainManager(ctx, this.domainInitializer, this.scopeCacheManager);
|
|
this.reducerManager = new ReducerManager(this.opts.domainBandingConfig);
|
|
this.dataExtractor = new DataExtractor(ctx, this.domainManager);
|
|
this.dataGrouper = new DataGrouper(ctx);
|
|
this.aggregator = new Aggregator(ctx, this.scopeCacheManager, this.resolvers);
|
|
this.incrementalProcessor = new IncrementalProcessor(ctx, this.reducerManager);
|
|
}
|
|
resolveProcessedDataDefById(scope, searchId) {
|
|
return this.resolvers.resolveProcessedDataDefById(scope, searchId);
|
|
}
|
|
resolveProcessedDataIndexById(scope, searchId) {
|
|
return this.resolvers.resolveProcessedDataIndexById(scope, searchId);
|
|
}
|
|
resolveKeysById(scope, searchId, processedData) {
|
|
return this.resolvers.resolveKeysById(scope, searchId, processedData);
|
|
}
|
|
hasColumnById(scope, searchId) {
|
|
return this.resolvers.hasColumnById(scope, searchId);
|
|
}
|
|
resolveColumnById(scope, searchId, processedData) {
|
|
return this.resolvers.resolveColumnById(scope, searchId, processedData);
|
|
}
|
|
resolveColumnNeedsValueOf(scope, searchId, processedData) {
|
|
return this.resolvers.resolveColumnNeedsValueOf(scope, searchId, processedData);
|
|
}
|
|
resolveMissingDataCount(scope) {
|
|
return this.resolvers.resolveMissingDataCount(scope);
|
|
}
|
|
/**
|
|
* Provides a convenience iterator to iterate over all of the extract datum values in a
|
|
* specific DataGroup.
|
|
*
|
|
* @param scope to which datums should belong
|
|
* @param group containing the datums
|
|
* @param processedData containing the group
|
|
* @param groupIndex index of the group in processedData.groups
|
|
*/
|
|
*forEachDatum(scope, processedData, group, groupIndex) {
|
|
const columnIndex = processedData.columnScopes.findIndex((s) => s.has(scope.id));
|
|
for (const relativeDatumIndex of group.datumIndices[columnIndex] ?? []) {
|
|
const absoluteDatumIndex = this.resolvers.resolveAbsoluteIndex(groupIndex, relativeDatumIndex);
|
|
yield processedData.columns[columnIndex][absoluteDatumIndex];
|
|
}
|
|
}
|
|
getUniqueDataSets(processedData) {
|
|
return new Set(processedData.dataSources.values());
|
|
}
|
|
/**
|
|
* Provides a convenience iterator to iterate over all of the extracted datum values in a
|
|
* GroupedData.
|
|
*
|
|
* @param scope to which datums should belong
|
|
* @param processedData to iterate through
|
|
*/
|
|
*forEachGroupDatum(scope, processedData) {
|
|
const columnIndex = processedData.columnScopes.findIndex((s) => s.has(scope.id));
|
|
const output = {
|
|
groupIndex: 0,
|
|
columnIndex
|
|
};
|
|
const empty = [];
|
|
for (const group of processedData.groups) {
|
|
output.group = group;
|
|
for (const relativeDatumIndex of group.datumIndices[columnIndex] ?? empty) {
|
|
output.datumIndex = this.resolvers.resolveAbsoluteIndex(output.groupIndex, relativeDatumIndex);
|
|
yield output;
|
|
}
|
|
output.groupIndex++;
|
|
}
|
|
}
|
|
getDomain(scope, searchId, type, processedData) {
|
|
const domain = this.resolvers.getDomain(scope, searchId, type, processedData);
|
|
if (type === "key" && domain.length > 0) {
|
|
const sortMetadata = this.getKeySortMetadata(scope, searchId, processedData);
|
|
return { domain, sortMetadata };
|
|
}
|
|
return { domain };
|
|
}
|
|
getDomainBetweenRange(scope, searchIds, [i0, i1], processedData) {
|
|
return this.resolvers.getDomainBetweenRange(scope, searchIds, [i0, i1], processedData);
|
|
}
|
|
getKeySortOrder(scope, searchId, processedData) {
|
|
return this.resolvers.getKeySortOrder(scope, searchId, processedData);
|
|
}
|
|
getColumnSortOrder(scope, searchId, processedData) {
|
|
return this.resolvers.getColumnSortOrder(scope, searchId, processedData);
|
|
}
|
|
/**
|
|
* Get sort metadata for a key column if available.
|
|
* Returns undefined if metadata is not available, is dirty, or data is unsorted.
|
|
*/
|
|
getKeySortMetadata(scope, searchId, processedData) {
|
|
const entry = this.resolvers.getKeySortEntry(scope, searchId, processedData);
|
|
if (entry?.sortOrder != null) {
|
|
return { sortOrder: entry.sortOrder, isUnique: entry.isUnique };
|
|
}
|
|
return void 0;
|
|
}
|
|
processData(sources) {
|
|
const {
|
|
opts: { groupByKeys, groupByFn },
|
|
aggregates,
|
|
groupProcessors,
|
|
reducers,
|
|
processors,
|
|
propertyProcessors
|
|
} = this;
|
|
const start2 = performance.now();
|
|
if (groupByKeys && this.keys.length === 0) {
|
|
return;
|
|
}
|
|
let processedData = this.extractData(sources);
|
|
if (groupByKeys) {
|
|
processedData = this.dataGrouper.groupData(processedData);
|
|
} else if (groupByFn) {
|
|
processedData = this.dataGrouper.groupData(processedData, groupByFn(processedData));
|
|
}
|
|
if (groupProcessors.length > 0 && processedData.type === "grouped") {
|
|
this.aggregator.postProcessGroups(processedData);
|
|
}
|
|
if (aggregates.length > 0 && processedData.type === "ungrouped") {
|
|
this.aggregator.aggregateUngroupedData(processedData);
|
|
} else if (aggregates.length > 0 && processedData.type === "grouped") {
|
|
this.aggregator.aggregateGroupedData(processedData);
|
|
}
|
|
if (propertyProcessors.length > 0) {
|
|
this.postProcessProperties(processedData);
|
|
}
|
|
if (reducers.length > 0) {
|
|
this.reduceData(processedData);
|
|
}
|
|
if (processors.length > 0) {
|
|
this.postProcessData(processedData);
|
|
}
|
|
this.warnDataMissingProperties(sources);
|
|
const end3 = performance.now();
|
|
processedData.time = end3 - start2;
|
|
processedData.version += 1;
|
|
this.collectOptimizationMetadata(processedData, "full-process");
|
|
if (this.debug.check()) {
|
|
logProcessedData(processedData);
|
|
}
|
|
this.processScopeCache();
|
|
return processedData;
|
|
}
|
|
/**
|
|
* Determines if incremental reprocessing is supported for the given data.
|
|
*
|
|
* Reprocessing is supported when:
|
|
* - For ungrouped data: No aggregates, reducers, processors, or property processors
|
|
* - For grouped data: Additionally requires:
|
|
* - groupsUnique=true (each datum has unique keys)
|
|
* - Single data source (all scopes share same DataSet)
|
|
* - No invalid keys (to maintain groups.length === columns.length invariant)
|
|
* - All group processors support reprocessing
|
|
*
|
|
* When unsupported, falls back to full reprocessing automatically.
|
|
*
|
|
* @returns true if incremental reprocessing can be used, false otherwise
|
|
*/
|
|
isReprocessingSupported(processedData) {
|
|
return this.incrementalProcessor.isReprocessingSupported(processedData);
|
|
}
|
|
reprocessData(processedData, dataSets) {
|
|
if (!this.isReprocessingSupported(processedData)) {
|
|
if (this.debug.check()) {
|
|
this.debug("Falling back to full reprocessing - incremental not supported for current configuration");
|
|
}
|
|
const uniqueDataSets = this.getUniqueDataSets(processedData);
|
|
for (const dataSet of uniqueDataSets) {
|
|
dataSet.commitPendingTransactions();
|
|
}
|
|
return this.processData(processedData.dataSources);
|
|
}
|
|
const { getProcessValue } = this.initDataDomainProcessor("skip");
|
|
return this.incrementalProcessor.reprocessData(
|
|
processedData,
|
|
dataSets,
|
|
getProcessValue,
|
|
this.reprocessGroupProcessors.bind(this),
|
|
this.recomputeDomains.bind(this),
|
|
this.collectOptimizationMetadata.bind(this)
|
|
);
|
|
}
|
|
/**
|
|
* Recomputes domains from transformed arrays.
|
|
* Uses BandedDomain optimization for continuous domains to avoid full rescans.
|
|
*/
|
|
recomputeDomains(processedData) {
|
|
this.domainManager.recomputeDomains(processedData);
|
|
}
|
|
warnDataMissingProperties(sources) {
|
|
this.dataExtractor.warnDataMissingProperties(sources);
|
|
}
|
|
processScopeCache() {
|
|
this.scopeCacheManager.processScopeCache();
|
|
}
|
|
valueGroupIdxLookup(selector) {
|
|
return this.scopeCacheManager.valueGroupIdxLookup(selector);
|
|
}
|
|
valueIdxLookup(scopes, prop) {
|
|
return this.scopeCacheManager.valueIdxLookup(scopes, prop);
|
|
}
|
|
extractData(sources) {
|
|
return this.dataExtractor.extractData(sources);
|
|
}
|
|
/**
|
|
* Reprocesses group processors for incremental updates.
|
|
* Only processes newly inserted groups to avoid double-processing.
|
|
* This is safe only when all group processors support reprocessing.
|
|
* Deduplicates change descriptions to avoid processing the same groups multiple times
|
|
* when multiple scopes share the same DataSet.
|
|
*/
|
|
reprocessGroupProcessors(processedData, scopeChanges) {
|
|
const { groupProcessors } = this;
|
|
const { columns } = processedData;
|
|
for (const processor of groupProcessors) {
|
|
if (!processor.supportsReprocessing) {
|
|
throw new Error(
|
|
"AG Charts - attempted to reprocess group processor that does not support reprocessing. This is an internal error that should not occur."
|
|
);
|
|
}
|
|
}
|
|
const processedChangeDescs = uniqueChangeDescriptions(scopeChanges);
|
|
for (const processor of groupProcessors) {
|
|
const valueIndexes = this.valueGroupIdxLookup(processor);
|
|
const adjustFn = processor.adjust()();
|
|
for (const changeDesc of processedChangeDescs) {
|
|
const { indexMap } = changeDesc;
|
|
for (const op of indexMap.spliceOps) {
|
|
if (op.insertCount > 0) {
|
|
for (let i = 0; i < op.insertCount; i++) {
|
|
const groupIndex = op.index + i;
|
|
const dataGroup = processedData.groups[groupIndex];
|
|
adjustFn(columns, valueIndexes, dataGroup, groupIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
postProcessProperties(processedData) {
|
|
for (const { adjust, property, scopes } of this.propertyProcessors) {
|
|
for (const idx of this.valueIdxLookup(scopes, property)) {
|
|
adjust()(processedData, idx);
|
|
}
|
|
}
|
|
}
|
|
reduceData(processedData) {
|
|
processedData.reduced ?? (processedData.reduced = {});
|
|
for (const def of this.reducers) {
|
|
if (this.shouldUseReducerBanding(def, processedData)) {
|
|
processedData.reduced[def.property] = this.reduceWithBands(
|
|
def,
|
|
processedData
|
|
);
|
|
} else {
|
|
processedData.reduced[def.property] = this.reduceStandard(
|
|
def,
|
|
processedData
|
|
);
|
|
}
|
|
}
|
|
}
|
|
shouldUseReducerBanding(def, processedData) {
|
|
return processedData.type === "ungrouped" && def.supportsBanding === true && typeof def.combineResults === "function";
|
|
}
|
|
reduceWithBands(def, processedData) {
|
|
const result = this.reducerManager.evaluate(def, processedData, {
|
|
reuseCleanBands: false
|
|
});
|
|
if (result === void 0) {
|
|
return this.reduceStandard(def, processedData);
|
|
}
|
|
return result;
|
|
}
|
|
reduceStandard(def, processedData) {
|
|
const reducer = def.reducer();
|
|
if (processedData.type === "grouped") {
|
|
let accValue = def.initialValue;
|
|
for (const group of processedData.groups) {
|
|
accValue = reducer(accValue, group.keys);
|
|
}
|
|
return accValue;
|
|
}
|
|
const scopeId = isScoped(def) ? def.scopes[0] : first(processedData.scopes);
|
|
if (scopeId == null) {
|
|
return def.initialValue;
|
|
}
|
|
const rawData = processedData.dataSources.get(scopeId)?.data ?? [];
|
|
const keyColumns = processedData.keys.map((column) => column.get(scopeId)).filter((column) => column != null);
|
|
const keysParam = keyColumns.map(() => void 0);
|
|
return ReducerManager.evaluateRange(def, reducer, { rawData, keyColumns, keysParam }, 0, rawData.length);
|
|
}
|
|
postProcessData(processedData) {
|
|
processedData.reduced ?? (processedData.reduced = {});
|
|
for (const def of this.processors) {
|
|
processedData.reduced[def.property] = def.calculate(
|
|
processedData,
|
|
processedData.reduced[def.property]
|
|
);
|
|
}
|
|
const { diff: diff9 } = processedData.reduced;
|
|
if (diff9) {
|
|
this.eventsHub?.emit("datamodel:diff", { diff: diff9 });
|
|
}
|
|
}
|
|
initDataDomainProcessor(domainMode) {
|
|
return this.domainManager.initDataDomainProcessor(domainMode);
|
|
}
|
|
/**
|
|
* Collects optimization metadata for debugging purposes.
|
|
* Only called when debug mode is enabled.
|
|
*/
|
|
collectOptimizationMetadata(processedData, pathTaken) {
|
|
const existingDomainBanding = processedData.optimizations?.domainBanding;
|
|
const reducerBands = processedData[REDUCER_BANDS];
|
|
if (this.reducers.length > 0 && reducerBands) {
|
|
this.collectReducerBandingMetadata(processedData, reducerBands);
|
|
}
|
|
const reducerBanding = processedData.optimizations?.reducerBanding;
|
|
processedData.optimizations = {
|
|
performance: {
|
|
processingTime: processedData.time,
|
|
pathTaken
|
|
},
|
|
...existingDomainBanding && { domainBanding: existingDomainBanding },
|
|
...reducerBanding && { reducerBanding }
|
|
};
|
|
const reprocessingSupported = this.isReprocessingSupported(processedData);
|
|
const reprocessingApplied = pathTaken === "reprocess";
|
|
let reprocessingReason;
|
|
if (!reprocessingSupported) {
|
|
const reasons = [];
|
|
if (processedData.type === "grouped") {
|
|
if (!processedData.groupsUnique) {
|
|
reasons.push("groupsUnique=false");
|
|
}
|
|
const uniqueDataSets = this.getUniqueDataSets(processedData);
|
|
if (uniqueDataSets.size !== 1) {
|
|
reasons.push("multiple data sources");
|
|
}
|
|
const scope = first(processedData.scopes);
|
|
const invalidKeys = processedData.invalidKeys?.get(scope);
|
|
if (invalidKeys?.some((invalid) => invalid)) {
|
|
reasons.push("has invalid keys");
|
|
}
|
|
}
|
|
if (this.aggregates.length > 0) {
|
|
reasons.push("has aggregates");
|
|
}
|
|
if (this.reducers.filter((r) => !r.supportsBanding).length > 0) {
|
|
reasons.push("has reducers");
|
|
}
|
|
if (this.processors.filter((p) => p.incrementalCalculate === void 0).length > 0) {
|
|
reasons.push("has processors");
|
|
}
|
|
if (this.propertyProcessors.length > 0) {
|
|
reasons.push("has property processors");
|
|
}
|
|
reprocessingReason = reasons.length > 0 ? reasons.join(", ") : void 0;
|
|
}
|
|
processedData.optimizations.reprocessing = {
|
|
applied: reprocessingApplied,
|
|
reason: reprocessingReason
|
|
};
|
|
if (processedData.type === "grouped") {
|
|
let sharedGroupCount = 0;
|
|
const firstGroup = processedData.groups[0];
|
|
if (firstGroup) {
|
|
const sharedDatumIndices = firstGroup.datumIndices;
|
|
for (const group of processedData.groups) {
|
|
if (group.datumIndices === sharedDatumIndices) {
|
|
sharedGroupCount++;
|
|
}
|
|
}
|
|
}
|
|
processedData.optimizations.sharedDatumIndices = {
|
|
applied: sharedGroupCount > 0,
|
|
sharedGroupCount,
|
|
totalGroupCount: processedData.groups.length
|
|
};
|
|
}
|
|
}
|
|
/**
|
|
* Collects reducer banding metadata for debugging purposes.
|
|
* Tracks which reducers used banding and their performance stats.
|
|
*/
|
|
collectReducerBandingMetadata(processedData, reducerBands) {
|
|
if (this.reducers.length === 0)
|
|
return;
|
|
processedData.optimizations ?? (processedData.optimizations = {});
|
|
const reducerMetadata = [];
|
|
for (const def of this.reducers) {
|
|
const bandManager = reducerBands.get(def.property);
|
|
const isBanded = this.shouldUseReducerBanding(def, processedData) && bandManager != null;
|
|
let reason;
|
|
if (!isBanded) {
|
|
if (def.supportsBanding !== true) {
|
|
reason = "reducer does not support banding";
|
|
} else if (processedData.type !== "ungrouped") {
|
|
reason = "grouped data not supported";
|
|
} else if (def.combineResults === void 0) {
|
|
reason = "missing combineResults function";
|
|
} else {
|
|
reason = "banding not applied";
|
|
}
|
|
}
|
|
let stats;
|
|
if (isBanded && bandManager) {
|
|
stats = bandManager.getStats();
|
|
}
|
|
reducerMetadata.push({
|
|
property: String(def.property),
|
|
applied: isBanded,
|
|
reason,
|
|
stats
|
|
});
|
|
}
|
|
processedData.optimizations.reducerBanding = {
|
|
reducers: reducerMetadata
|
|
};
|
|
}
|
|
buildAccessors(defs) {
|
|
return this.scopeCacheManager.buildAccessors(defs);
|
|
}
|
|
};
|
|
function logProcessedData(processedData) {
|
|
const logValues = (name, data) => {
|
|
if (data.length > 0) {
|
|
logger_exports.log(`DataModel.processData() - ${name}`);
|
|
logger_exports.table(data);
|
|
}
|
|
};
|
|
logger_exports.log("DataModel.processData() - processedData", processedData);
|
|
logValues("Key Domains", processedData.domain.keys);
|
|
logValues("Value Domains", processedData.domain.values);
|
|
logValues("Aggregate Domains", processedData.domain.aggValues ?? []);
|
|
if (processedData.optimizations) {
|
|
logger_exports.log("DataModel.processData() - Optimization Summary");
|
|
const opt = processedData.optimizations;
|
|
if (opt.performance) {
|
|
logger_exports.log(` Performance: ${opt.performance.processingTime.toFixed(2)}ms (${opt.performance.pathTaken})`);
|
|
}
|
|
if (opt.reprocessing) {
|
|
const symbol = opt.reprocessing.applied ? "\u2713" : "\u2717";
|
|
const reason = opt.reprocessing.reason ? ` (${opt.reprocessing.reason})` : "";
|
|
logger_exports.log(` Reprocessing: ${symbol}${reason}`);
|
|
}
|
|
if (opt.domainBanding) {
|
|
const keyStats = opt.domainBanding.keyDefs.filter((d) => d.applied);
|
|
const valueStats = opt.domainBanding.valueDefs.filter((d) => d.applied);
|
|
const totalApplied = keyStats.length + valueStats.length;
|
|
const totalDefs = opt.domainBanding.keyDefs.length + opt.domainBanding.valueDefs.length;
|
|
if (totalApplied > 0) {
|
|
logger_exports.log(` Domain Banding: \u2713 (${totalApplied}/${totalDefs} definitions)`);
|
|
for (const def of [...keyStats, ...valueStats]) {
|
|
if (def.stats) {
|
|
const pct2 = (def.stats.scanRatio * 100).toFixed(1);
|
|
logger_exports.log(
|
|
` ${def.property}: scanned ${def.stats.dirtyBands}/${def.stats.totalBands} bands (${pct2}%)`
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
const reasons = [
|
|
...opt.domainBanding.keyDefs.filter((d) => !d.applied).map((d) => d.reason),
|
|
...opt.domainBanding.valueDefs.filter((d) => !d.applied).map((d) => d.reason)
|
|
];
|
|
const uniqueReasons = [...new Set(reasons)].join(", ");
|
|
logger_exports.log(` Domain Banding: \u2717 (${uniqueReasons})`);
|
|
}
|
|
}
|
|
if (opt.sharedDatumIndices) {
|
|
const symbol = opt.sharedDatumIndices.applied ? "\u2713" : "\u2717";
|
|
const ratio2 = `${opt.sharedDatumIndices.sharedGroupCount}/${opt.sharedDatumIndices.totalGroupCount}`;
|
|
logger_exports.log(` Shared DatumIndices: ${symbol} (${ratio2} groups)`);
|
|
}
|
|
if (opt.batchMerging) {
|
|
const pct2 = (opt.batchMerging.mergeRatio * 100).toFixed(0);
|
|
const reduction = `${opt.batchMerging.originalBatchCount} \u2192 ${opt.batchMerging.mergedBatchCount}`;
|
|
logger_exports.log(` Batch Merging: ${reduction} (${pct2}% reduction)`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/data/processors.ts
|
|
var MAX_ANIMATABLE_NODES = 1e3;
|
|
function combineIntervalBandResults(bandResults, fallback, combiner) {
|
|
const validResults = bandResults.filter(
|
|
(result) => typeof result === "number" && Number.isFinite(result)
|
|
);
|
|
return validResults.length > 0 ? combiner(validResults) : fallback;
|
|
}
|
|
function processedDataIsAnimatable(processedData) {
|
|
return processedData.input.count <= MAX_ANIMATABLE_NODES;
|
|
}
|
|
function basicContinuousCheckDatumValidation(value) {
|
|
return value != null && isContinuous(value);
|
|
}
|
|
function basicDiscreteCheckDatumValidation(value) {
|
|
return value != null;
|
|
}
|
|
function basicDiscreteCheckDatumValidationAllowNull(_value) {
|
|
return true;
|
|
}
|
|
function getValidationFn(scaleType, allowNullKey) {
|
|
switch (scaleType) {
|
|
case "number":
|
|
case "log":
|
|
case "time":
|
|
case "unit-time":
|
|
case "ordinal-time":
|
|
case "color":
|
|
return basicContinuousCheckDatumValidation;
|
|
default:
|
|
return allowNullKey ? basicDiscreteCheckDatumValidationAllowNull : basicDiscreteCheckDatumValidation;
|
|
}
|
|
}
|
|
function getValueType(scaleType) {
|
|
switch (scaleType) {
|
|
case "number":
|
|
case "log":
|
|
case "time":
|
|
case "color":
|
|
return "range";
|
|
default:
|
|
return "category";
|
|
}
|
|
}
|
|
function keyProperty(propName, scaleType, opts = {}) {
|
|
const allowNullKey = opts.allowNullKey ?? false;
|
|
const result = {
|
|
property: propName,
|
|
type: "key",
|
|
valueType: getValueType(scaleType),
|
|
validation: opts.validation ?? getValidationFn(scaleType, allowNullKey),
|
|
...opts
|
|
};
|
|
return result;
|
|
}
|
|
function valueProperty(propName, scaleType, opts = {}) {
|
|
const allowNullKey = opts.allowNullKey ?? false;
|
|
const result = {
|
|
property: propName,
|
|
type: "value",
|
|
valueType: getValueType(scaleType),
|
|
validation: opts.validation ?? getValidationFn(scaleType, allowNullKey),
|
|
...opts
|
|
};
|
|
return result;
|
|
}
|
|
function rowCountProperty(propName, opts = {}) {
|
|
const result = {
|
|
property: propName,
|
|
type: "value",
|
|
valueType: "range",
|
|
missingValue: 1,
|
|
processor: function rowCountResetFn() {
|
|
return function rowCountGroupResetFn() {
|
|
return 1;
|
|
};
|
|
},
|
|
...opts
|
|
};
|
|
return result;
|
|
}
|
|
var noopProcessor = function(v) {
|
|
return v;
|
|
};
|
|
function processorChain(...chain) {
|
|
const filteredChain = chain.filter((fn) => fn != null);
|
|
if (filteredChain.length === 0) {
|
|
return () => noopProcessor;
|
|
}
|
|
if (filteredChain.length === 1) {
|
|
return filteredChain[0];
|
|
}
|
|
return function processorChainFn() {
|
|
const processorInstances = filteredChain.map((fn) => fn());
|
|
return function processorChainResultFn(value, index) {
|
|
return processorInstances.reduce((r, p) => p(r, index), value);
|
|
};
|
|
};
|
|
}
|
|
function rangedValueProperty(propName, opts = {}) {
|
|
const { min = -Infinity, max = Infinity, processor, ...defOpts } = opts;
|
|
return {
|
|
type: "value",
|
|
property: propName,
|
|
valueType: "range",
|
|
validation: basicContinuousCheckDatumValidation,
|
|
processor: processorChain(processor, function clampFnBuilder() {
|
|
return function clampFn(datum) {
|
|
return isFiniteNumber(datum) ? clamp(min, datum, max) : datum;
|
|
};
|
|
}),
|
|
...defOpts
|
|
};
|
|
}
|
|
function accumulativeValueProperty(propName, scaleType, opts = {}) {
|
|
const { onlyPositive, processor, ...defOpts } = opts;
|
|
const result = {
|
|
...valueProperty(propName, scaleType, defOpts),
|
|
processor: processorChain(processor, accumulatedValue(onlyPositive))
|
|
};
|
|
return result;
|
|
}
|
|
function trailingAccumulatedValueProperty(propName, scaleType, opts = {}) {
|
|
const result = {
|
|
...valueProperty(propName, scaleType, opts),
|
|
processor: trailingAccumulatedValue()
|
|
};
|
|
return result;
|
|
}
|
|
function groupAccumulativeValueProperty(propName, mode, opts, scaleType) {
|
|
return [
|
|
valueProperty(propName, scaleType, opts),
|
|
accumulateGroup(opts.groupId, mode, opts.separateNegative),
|
|
...opts.rangeId == null ? [] : [range2(opts.rangeId, opts.groupId)]
|
|
];
|
|
}
|
|
var SMALLEST_KEY_INTERVAL = {
|
|
type: "reducer",
|
|
property: "smallestKeyInterval",
|
|
initialValue: Infinity,
|
|
reducer() {
|
|
let prevX = Number.NaN;
|
|
return function smallestKeyIntervalReducerFn(smallestSoFar, keys) {
|
|
const key = keys[0];
|
|
const nextX = typeof key === "number" ? key : Number(key);
|
|
if (!Number.isFinite(nextX))
|
|
return smallestSoFar;
|
|
const prevX2 = prevX;
|
|
prevX = nextX;
|
|
if (!Number.isFinite(prevX))
|
|
return smallestSoFar;
|
|
const interval = Math.abs(nextX - prevX2);
|
|
const currentSmallest = smallestSoFar ?? Infinity;
|
|
if (interval > 0 && interval < currentSmallest) {
|
|
return interval;
|
|
}
|
|
return currentSmallest;
|
|
};
|
|
},
|
|
supportsBanding: true,
|
|
combineResults(bandResults) {
|
|
return combineIntervalBandResults(bandResults, Infinity, (values) => Math.min(...values));
|
|
},
|
|
needsOverlap: true
|
|
};
|
|
var LARGEST_KEY_INTERVAL = {
|
|
type: "reducer",
|
|
property: "largestKeyInterval",
|
|
initialValue: -Infinity,
|
|
reducer() {
|
|
let prevX = Number.NaN;
|
|
return function largestKeyIntervalReducerFn(largestSoFar, keys) {
|
|
const key = keys[0];
|
|
const nextX = typeof key === "number" ? key : Number(key);
|
|
if (!Number.isFinite(nextX))
|
|
return largestSoFar;
|
|
const prevX2 = prevX;
|
|
prevX = nextX;
|
|
if (!Number.isFinite(prevX))
|
|
return largestSoFar;
|
|
const interval = Math.abs(nextX - prevX2);
|
|
const currentLargest = largestSoFar ?? -Infinity;
|
|
if (interval > 0 && interval > currentLargest) {
|
|
return interval;
|
|
}
|
|
return currentLargest;
|
|
};
|
|
},
|
|
supportsBanding: true,
|
|
combineResults(bandResults) {
|
|
return combineIntervalBandResults(bandResults, -Infinity, (values) => Math.max(...values));
|
|
},
|
|
needsOverlap: true
|
|
};
|
|
var SORT_DOMAIN_GROUPS = {
|
|
type: "processor",
|
|
property: "sortedGroupDomain",
|
|
calculate: function sortedGroupDomainFn({ domain: { groups } }) {
|
|
return groups?.slice().sort((a, b) => {
|
|
for (let i = 0; i < a.length; i++) {
|
|
const result = a[i] - b[i];
|
|
if (result !== 0) {
|
|
return result;
|
|
}
|
|
}
|
|
return 0;
|
|
});
|
|
}
|
|
};
|
|
function normaliseFnBuilder({ normaliseTo }) {
|
|
const normalise2 = (val, extent2) => {
|
|
if (extent2 === 0)
|
|
return 0;
|
|
const result = (val ?? 0) * normaliseTo / extent2;
|
|
if (result >= 0) {
|
|
return Math.min(normaliseTo, result);
|
|
}
|
|
return Math.max(-normaliseTo, result);
|
|
};
|
|
return () => () => (columns, valueIndexes, dataGroup, groupIndex) => {
|
|
const extent2 = normaliseFindExtent(columns, valueIndexes, dataGroup, groupIndex);
|
|
for (const valueIdx of valueIndexes) {
|
|
const datumIndices = dataGroup.datumIndices[valueIdx];
|
|
if (datumIndices == null)
|
|
continue;
|
|
for (const relativeDatumIndex of datumIndices) {
|
|
const datumIndex = groupIndex + relativeDatumIndex;
|
|
const column = columns[valueIdx];
|
|
const value = column[datumIndex];
|
|
if (value == null) {
|
|
column[datumIndex] = void 0;
|
|
continue;
|
|
}
|
|
column[datumIndex] = normalise2(value, extent2);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
function normaliseFindExtent(columns, valueIndexes, dataGroup, groupIndex) {
|
|
const valuesExtent = [0, 0];
|
|
for (const valueIdx of valueIndexes) {
|
|
const column = columns[valueIdx];
|
|
const datumIndices = dataGroup.datumIndices[valueIdx];
|
|
if (datumIndices == null)
|
|
continue;
|
|
for (const relativeDatumIndex of datumIndices) {
|
|
const datumIndex = groupIndex + relativeDatumIndex;
|
|
const value = column[datumIndex];
|
|
if (value == null)
|
|
continue;
|
|
const valueExtent = typeof value === "number" ? value : Math.max(...value.map((v) => v ?? 0));
|
|
const valIdx = valueExtent < 0 ? 0 : 1;
|
|
if (valIdx === 0) {
|
|
valuesExtent[valIdx] = Math.min(valuesExtent[valIdx], valueExtent);
|
|
} else {
|
|
valuesExtent[valIdx] = Math.max(valuesExtent[valIdx], valueExtent);
|
|
}
|
|
}
|
|
}
|
|
return Math.max(Math.abs(valuesExtent[0]), valuesExtent[1]);
|
|
}
|
|
function normaliseGroupTo(matchGroupIds, normaliseTo) {
|
|
return {
|
|
type: "group-value-processor",
|
|
matchGroupIds,
|
|
adjust: memo({ normaliseTo }, normaliseFnBuilder)
|
|
};
|
|
}
|
|
function normalisePropertyFnBuilder({
|
|
normaliseTo,
|
|
zeroDomain,
|
|
rangeMin,
|
|
rangeMax
|
|
}) {
|
|
const normaliseSpan = normaliseTo[1] - normaliseTo[0];
|
|
const normalise2 = function normaliseFn(val, start2, span) {
|
|
const result = normaliseTo[0] + (val - start2) / span * normaliseSpan;
|
|
if (span === 0) {
|
|
return zeroDomain;
|
|
} else if (result >= normaliseTo[1]) {
|
|
return normaliseTo[1];
|
|
} else if (result < normaliseTo[0]) {
|
|
return normaliseTo[0];
|
|
}
|
|
return result;
|
|
};
|
|
return function normalisePropertyResetFn() {
|
|
return function normalisePropertyResultFn(pData, pIdx) {
|
|
let [start2, end3] = pData.domain.values[pIdx];
|
|
if (rangeMin != null)
|
|
start2 = rangeMin;
|
|
if (rangeMax != null)
|
|
end3 = rangeMax;
|
|
const span = end3 - start2;
|
|
pData.domain.values[pIdx] = [normaliseTo[0], normaliseTo[1]];
|
|
const column = pData.columns[pIdx];
|
|
for (let datumIndex = 0; datumIndex < column.length; datumIndex += 1) {
|
|
column[datumIndex] = normalise2(column[datumIndex], start2, span);
|
|
}
|
|
};
|
|
};
|
|
}
|
|
function normalisePropertyTo(property, normaliseTo, zeroDomain, rangeMin, rangeMax) {
|
|
return {
|
|
type: "property-value-processor",
|
|
property,
|
|
adjust: memo({ normaliseTo, rangeMin, rangeMax, zeroDomain }, normalisePropertyFnBuilder)
|
|
};
|
|
}
|
|
var ANIMATION_VALIDATION_UNIQUE_KEYS = 1;
|
|
var ANIMATION_VALIDATION_ORDERED_KEYS = 2;
|
|
function animationValidationProcessValue(def, domainValues, column) {
|
|
let validation = ANIMATION_VALIDATION_UNIQUE_KEYS | ANIMATION_VALIDATION_ORDERED_KEYS;
|
|
if (def.valueType === "category") {
|
|
if (domainValues.length < column.length)
|
|
validation &= ~ANIMATION_VALIDATION_UNIQUE_KEYS;
|
|
return validation;
|
|
}
|
|
let lastValue = column[0]?.valueOf();
|
|
for (let d = 1; validation !== 0 && d < column.length; d++) {
|
|
const keyValue = column[d]?.valueOf();
|
|
if (!Number.isFinite(keyValue) || lastValue > keyValue)
|
|
validation &= ~ANIMATION_VALIDATION_ORDERED_KEYS;
|
|
if (Number.isFinite(keyValue) && lastValue === keyValue)
|
|
validation &= ~ANIMATION_VALIDATION_UNIQUE_KEYS;
|
|
lastValue = keyValue;
|
|
}
|
|
return validation;
|
|
}
|
|
function buildAnimationValidationFn(valueKeyIds) {
|
|
return function calculate(result, _previousValue) {
|
|
if (!processedDataIsAnimatable(result))
|
|
return;
|
|
const { keys: keysDefs, values: valuesDef } = result.defs;
|
|
const {
|
|
input,
|
|
domain: { values: domainValues },
|
|
columns
|
|
} = result;
|
|
let uniqueKeys = true;
|
|
let orderedKeys = true;
|
|
if (input.count !== 0) {
|
|
const keySortOrders = result[KEY_SORT_ORDERS];
|
|
for (let i = 0; (uniqueKeys || orderedKeys) && i < keysDefs.length; i++) {
|
|
const def = keysDefs[i];
|
|
const entry = keySortOrders.get(i);
|
|
if (def.valueType === "category") {
|
|
const domainSize = result.domain.keys[i]?.length ?? 0;
|
|
const dataSize = result.keys[i]?.values().next().value?.length ?? 0;
|
|
if (domainSize < dataSize)
|
|
uniqueKeys = false;
|
|
} else if (entry) {
|
|
if (entry.isUnique === false)
|
|
uniqueKeys = false;
|
|
if (entry.sortOrder !== 1)
|
|
orderedKeys = false;
|
|
}
|
|
}
|
|
if (valueKeyIds && valueKeyIds.length > 0) {
|
|
let validation = ANIMATION_VALIDATION_UNIQUE_KEYS | ANIMATION_VALIDATION_ORDERED_KEYS;
|
|
for (let i = 0; validation !== 0 && i < valuesDef.length; i++) {
|
|
const value = valuesDef[i];
|
|
if (!valueKeyIds.includes(value.id))
|
|
continue;
|
|
validation &= animationValidationProcessValue(value, domainValues[i], columns[i]);
|
|
}
|
|
if ((validation & ANIMATION_VALIDATION_UNIQUE_KEYS) === 0)
|
|
uniqueKeys = false;
|
|
if ((validation & ANIMATION_VALIDATION_ORDERED_KEYS) === 0)
|
|
orderedKeys = false;
|
|
}
|
|
}
|
|
return { uniqueKeys, orderedKeys };
|
|
};
|
|
}
|
|
function incrementalCalculateAnimationValidation() {
|
|
return {
|
|
uniqueKeys: true,
|
|
orderedKeys: false
|
|
};
|
|
}
|
|
function animationValidation(valueKeyIds) {
|
|
const calculate = memo(valueKeyIds, buildAnimationValidationFn);
|
|
return {
|
|
type: "processor",
|
|
property: "animationValidation",
|
|
calculate,
|
|
incrementalCalculate: incrementalCalculateAnimationValidation
|
|
};
|
|
}
|
|
function buildGroupAccFn({ mode, separateNegative }) {
|
|
return function buildGroupAccFnResetFn() {
|
|
return function buildGroupAccFnGroupResetFn() {
|
|
return function buildGroupAccFnResultFn(columns, valueIndexes, dataGroup, groupIndex) {
|
|
const acc = [0, 0];
|
|
for (const valueIdx of valueIndexes) {
|
|
const datumIndices = dataGroup.datumIndices[valueIdx];
|
|
if (datumIndices == null)
|
|
continue;
|
|
const stackNegative = acc[0];
|
|
const stackPositive = acc[1];
|
|
const column = columns[valueIdx];
|
|
let didAccumulate = false;
|
|
for (const relativeDatumIndex of datumIndices) {
|
|
const datumIndex = groupIndex + relativeDatumIndex;
|
|
const currentVal = column[datumIndex];
|
|
if (!isFiniteNumber(currentVal))
|
|
continue;
|
|
const useNegative = separateNegative && isNegative(currentVal);
|
|
const accValue = useNegative ? stackNegative : stackPositive;
|
|
if (mode === "normal") {
|
|
column[datumIndex] = accValue + currentVal;
|
|
} else {
|
|
column[datumIndex] = accValue;
|
|
}
|
|
if (!didAccumulate) {
|
|
const accIndex = useNegative ? 0 : 1;
|
|
acc[accIndex] = accValue + currentVal;
|
|
didAccumulate = true;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
};
|
|
};
|
|
}
|
|
function accumulateGroup(matchGroupId, mode, separateNegative = false) {
|
|
const adjust = memo({ mode, separateNegative }, buildGroupAccFn);
|
|
return {
|
|
type: "group-value-processor",
|
|
matchGroupIds: [matchGroupId],
|
|
adjust,
|
|
supportsReprocessing: true
|
|
};
|
|
}
|
|
function valueIdentifier(value) {
|
|
return value.id ?? value.property;
|
|
}
|
|
function valueIndices(id, previousData, processedData) {
|
|
const properties = /* @__PURE__ */ new Map();
|
|
const previousValues = previousData.defs.values;
|
|
for (let previousIndex = 0; previousIndex < previousValues.length; previousIndex += 1) {
|
|
const previousValue = previousValues[previousIndex];
|
|
if (previousValue.scopes?.includes(id) === false)
|
|
continue;
|
|
const valueId = valueIdentifier(previousValue);
|
|
if (properties.has(valueId))
|
|
return;
|
|
properties.set(valueId, previousIndex);
|
|
}
|
|
const indices = [];
|
|
const nextValues = processedData.defs.values;
|
|
for (let nextIndex = 0; nextIndex < nextValues.length; nextIndex += 1) {
|
|
const nextValue = nextValues[nextIndex];
|
|
if (nextValue.scopes?.includes(id) === false)
|
|
continue;
|
|
const valueId = valueIdentifier(nextValue);
|
|
const previousIndex = properties.get(valueId);
|
|
if (previousIndex == null)
|
|
return;
|
|
properties.delete(valueId);
|
|
indices.push({ previousIndex, nextIndex });
|
|
}
|
|
if (properties.size !== 0)
|
|
return;
|
|
return indices;
|
|
}
|
|
function columnsEqual(previousColumns, nextColumns, indices, previousDatumIndex, nextDatumIndex) {
|
|
for (const { previousIndex, nextIndex } of indices) {
|
|
const previousColumn = previousColumns[previousIndex];
|
|
const nextColumn = nextColumns[nextIndex];
|
|
const previousValue = previousColumn[previousDatumIndex];
|
|
const nextValue = nextColumn[nextDatumIndex];
|
|
if (previousValue !== nextValue) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function diff(id, previousData, updateMovedData = true) {
|
|
return {
|
|
type: "processor",
|
|
property: "diff",
|
|
calculate(processedData, previousValue) {
|
|
if (!processedDataIsAnimatable(processedData))
|
|
return;
|
|
const moved = /* @__PURE__ */ new Map();
|
|
const added = /* @__PURE__ */ new Map();
|
|
const updated = /* @__PURE__ */ new Map();
|
|
const removed = /* @__PURE__ */ new Map();
|
|
const previousKeys = previousData.keys.map((keyMap) => keyMap.get(id));
|
|
const keys = processedData.keys.map((keyMap) => keyMap.get(id));
|
|
const previousColumns = previousData.columns;
|
|
const columns = processedData.columns;
|
|
const indices = valueIndices(id, previousData, processedData);
|
|
if (indices == null)
|
|
return previousValue;
|
|
const length2 = Math.max(previousData.input.count, processedData.input.count);
|
|
const allowNull = processedData.defs.keys.some((def) => def.allowNullKey === true);
|
|
for (let i = 0; i < length2; i++) {
|
|
const hasPreviousDatum = i < previousData.input.count;
|
|
const hasDatum = i < processedData.input.count;
|
|
const prevKeys = hasPreviousDatum ? datumKeys(previousKeys, i, allowNull) : void 0;
|
|
const prevId = prevKeys == null ? "" : createDatumId(...prevKeys);
|
|
const dKeys = hasDatum ? datumKeys(keys, i, allowNull) : void 0;
|
|
const datumId = dKeys == null ? "" : createDatumId(...dKeys);
|
|
if (hasDatum && hasPreviousDatum && prevId === datumId) {
|
|
if (!columnsEqual(previousColumns, columns, indices, i, i)) {
|
|
updated.set(datumId, i);
|
|
}
|
|
continue;
|
|
}
|
|
const removedIndex = removed.get(datumId);
|
|
if (removedIndex != null) {
|
|
if (updateMovedData || !columnsEqual(previousColumns, columns, indices, removedIndex, i)) {
|
|
updated.set(datumId, i);
|
|
moved.set(datumId, i);
|
|
}
|
|
removed.delete(datumId);
|
|
} else if (hasDatum) {
|
|
added.set(datumId, i);
|
|
}
|
|
const addedIndex = added.get(prevId);
|
|
if (addedIndex != null) {
|
|
if (updateMovedData || !columnsEqual(previousColumns, columns, indices, addedIndex, i)) {
|
|
updated.set(prevId, i);
|
|
moved.set(prevId, i);
|
|
}
|
|
added.delete(prevId);
|
|
} else if (hasPreviousDatum) {
|
|
updated.delete(prevId);
|
|
removed.set(prevId, i);
|
|
}
|
|
}
|
|
const changed = added.size > 0 || updated.size > 0 || removed.size > 0;
|
|
const value = {
|
|
changed,
|
|
added: new Set(added.keys()),
|
|
updated: new Set(updated.keys()),
|
|
removed: new Set(removed.keys()),
|
|
moved: new Set(moved.keys())
|
|
};
|
|
return {
|
|
...previousValue,
|
|
[id]: value
|
|
};
|
|
}
|
|
};
|
|
}
|
|
function createDatumId(...keys) {
|
|
if (keys.length === 1) {
|
|
const key = transformIntegratedCategoryValue(keys[0]);
|
|
if (key === null)
|
|
return NULL_KEY_STRING;
|
|
if (key === void 0)
|
|
return UNDEFINED_KEY_STRING;
|
|
const isPrimitive = typeof key === "boolean" || typeof key === "number" || typeof key === "string";
|
|
if (isPrimitive)
|
|
return key;
|
|
}
|
|
return keys.map((key) => {
|
|
const transformed = transformIntegratedCategoryValue(key);
|
|
if (transformed === null)
|
|
return NULL_KEY_STRING;
|
|
if (transformed === void 0)
|
|
return UNDEFINED_KEY_STRING;
|
|
return transformed;
|
|
}).join("___");
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/interaction/animationBatch.ts
|
|
var AnimationBatch = class {
|
|
constructor(maxAnimationTime) {
|
|
this.maxAnimationTime = maxAnimationTime;
|
|
this.debug = debugLogger_exports.create(true, "animation");
|
|
this.controllers = /* @__PURE__ */ new Map();
|
|
this.stoppedCbs = /* @__PURE__ */ new Set();
|
|
this.currentPhase = 0;
|
|
this.phases = new Map(PHASE_ORDER.map((p) => [p, []]));
|
|
this.skipAnimations = false;
|
|
this.animationTimeConsumed = 0;
|
|
/** Guard against premature animation execution. */
|
|
this.isReady = false;
|
|
}
|
|
get size() {
|
|
return this.controllers.size;
|
|
}
|
|
get consumedTimeMs() {
|
|
return this.animationTimeConsumed;
|
|
}
|
|
isActive() {
|
|
return this.controllers.size > 0;
|
|
}
|
|
getActiveControllers() {
|
|
return this.phases.get(PHASE_ORDER[this.currentPhase]) ?? [];
|
|
}
|
|
checkOverlappingId(id) {
|
|
if (id != null && this.controllers.has(id)) {
|
|
this.controllers.get(id).stop();
|
|
this.debug(`Skipping animation batch due to update of existing animation: ${id}`);
|
|
this.skip();
|
|
}
|
|
}
|
|
addAnimation(animation) {
|
|
if (animation.isComplete)
|
|
return;
|
|
const animationPhaseIdx = PHASE_ORDER.indexOf(animation.phase);
|
|
if (animationPhaseIdx < this.currentPhase) {
|
|
this.debug(`Skipping animation due to being for an earlier phase`, animation.id);
|
|
animation.stop();
|
|
return;
|
|
}
|
|
this.controllers.set(animation.id, animation);
|
|
this.phases.get(animation.phase)?.push(animation);
|
|
}
|
|
removeAnimation(animation) {
|
|
this.controllers.delete(animation.id);
|
|
const phase = this.phases.get(animation.phase);
|
|
const index = phase?.indexOf(animation);
|
|
if (index != null && index >= 0) {
|
|
phase?.splice(index, 1);
|
|
}
|
|
}
|
|
progress(deltaTime) {
|
|
if (!this.isReady)
|
|
return;
|
|
let unusedTime = deltaTime === 0 ? 0.01 : deltaTime;
|
|
const refresh = () => {
|
|
const phase2 = PHASE_ORDER[this.currentPhase];
|
|
return {
|
|
phaseControllers: [...this.getActiveControllers()],
|
|
phase: phase2,
|
|
phaseMeta: PHASE_METADATA[phase2]
|
|
};
|
|
};
|
|
let { phase, phaseControllers, phaseMeta } = refresh();
|
|
const arePhasesComplete = () => PHASE_ORDER[this.currentPhase] == null;
|
|
const progressPhase = () => {
|
|
({ phase, phaseControllers, phaseMeta } = refresh());
|
|
while (!arePhasesComplete() && phaseControllers.length === 0) {
|
|
this.currentPhase++;
|
|
({ phase, phaseControllers, phaseMeta } = refresh());
|
|
this.debug(`AnimationBatch - phase changing to ${phase}`, { unusedTime }, phaseControllers);
|
|
}
|
|
};
|
|
const total = this.controllers.size;
|
|
this.debug(`AnimationBatch - ${deltaTime}ms; phase ${phase} with ${phaseControllers?.length} of ${total}`);
|
|
do {
|
|
const phaseDeltaTime = unusedTime;
|
|
const skipPhase = phaseMeta.skipIfNoEarlierAnimations && this.animationTimeConsumed === 0;
|
|
let completeCount = 0;
|
|
for (const controller of phaseControllers) {
|
|
if (skipPhase) {
|
|
controller.stop();
|
|
} else {
|
|
unusedTime = Math.min(controller.update(phaseDeltaTime), unusedTime);
|
|
}
|
|
if (controller.isComplete) {
|
|
completeCount++;
|
|
this.removeAnimation(controller);
|
|
}
|
|
}
|
|
this.animationTimeConsumed += phaseDeltaTime - unusedTime;
|
|
this.debug(
|
|
`AnimationBatch - updated ${phaseControllers.length} controllers; ${completeCount} completed`,
|
|
phaseControllers
|
|
);
|
|
this.debug(`AnimationBatch - animationTimeConsumed: ${this.animationTimeConsumed}`);
|
|
progressPhase();
|
|
} while (unusedTime > 0 && !arePhasesComplete());
|
|
if (this.animationTimeConsumed > this.maxAnimationTime) {
|
|
this.debug(`Animation batch exceeded max animation time, skipping`, [...this.controllers]);
|
|
this.stop();
|
|
}
|
|
}
|
|
ready() {
|
|
if (this.isReady)
|
|
return;
|
|
this.isReady = true;
|
|
this.debug(`AnimationBatch - ready; skipped: ${this.skipAnimations}`, [...this.controllers]);
|
|
let skipAll = true;
|
|
for (const [, controller] of this.controllers) {
|
|
if (controller.duration > 0 && PHASE_METADATA[controller.phase].skipIfNoEarlierAnimations !== true) {
|
|
skipAll = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!skipAll) {
|
|
for (const [, controller] of this.controllers) {
|
|
if (controller.autoplay) {
|
|
controller.play(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
skip(skip = true) {
|
|
if (this.skipAnimations === false && skip === true) {
|
|
for (const controller of this.controllers.values()) {
|
|
controller.stop();
|
|
}
|
|
this.controllers.clear();
|
|
}
|
|
this.skipAnimations = skip;
|
|
}
|
|
play() {
|
|
for (const controller of this.controllers.values()) {
|
|
controller.play();
|
|
}
|
|
}
|
|
stop() {
|
|
for (const controller of this.controllers.values()) {
|
|
try {
|
|
controller.stop();
|
|
this.removeAnimation(controller);
|
|
} catch (error2) {
|
|
logger_exports.error("Error during animation stop", error2);
|
|
}
|
|
}
|
|
this.dispatchStopped();
|
|
}
|
|
stopByAnimationId(id) {
|
|
if (id != null && this.controllers.has(id)) {
|
|
const controller = this.controllers.get(id);
|
|
if (controller) {
|
|
controller.stop();
|
|
this.removeAnimation(controller);
|
|
}
|
|
}
|
|
}
|
|
stopByAnimationGroupId(id) {
|
|
for (const controller of this.controllers.values()) {
|
|
if (controller.groupId === id) {
|
|
this.stopByAnimationId(controller.id);
|
|
}
|
|
}
|
|
}
|
|
dispatchStopped() {
|
|
for (const cb of this.stoppedCbs) {
|
|
cb();
|
|
}
|
|
this.stoppedCbs.clear();
|
|
}
|
|
isSkipped() {
|
|
return this.skipAnimations;
|
|
}
|
|
getRemainingTime(restrictPhase) {
|
|
if (!this.isActive())
|
|
return 0;
|
|
let total = 0;
|
|
for (const [phase, controllers] of this.phases) {
|
|
if (controllers.length === 0)
|
|
continue;
|
|
if (restrictPhase != null && restrictPhase !== phase)
|
|
continue;
|
|
total += Math.max(...controllers.map((c) => c.isComplete ? 0 : c.delay + c.duration - (c.elapsed ?? 0)));
|
|
}
|
|
return total;
|
|
}
|
|
destroy() {
|
|
this.stop();
|
|
this.controllers.clear();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/interaction/animationManager.ts
|
|
function validAnimationDuration(testee) {
|
|
if (testee == null)
|
|
return true;
|
|
return !Number.isNaN(testee) && testee >= 0 && testee <= 2;
|
|
}
|
|
var AnimationManager = class {
|
|
constructor(interactionManager, chartUpdateMutex) {
|
|
this.interactionManager = interactionManager;
|
|
this.chartUpdateMutex = chartUpdateMutex;
|
|
this.defaultDuration = 1e3;
|
|
this.maxAnimatableItems = MAX_ANIMATABLE_NODES;
|
|
this.batch = new AnimationBatch(this.defaultDuration * 1.5);
|
|
this.debug = debugLogger_exports.create(true, "animation");
|
|
this.events = new EventEmitter();
|
|
this.rafAvailable = typeof requestAnimationFrame !== "undefined";
|
|
this.isPlaying = true;
|
|
this.requestId = null;
|
|
this.skipAnimations = true;
|
|
this.currentAnonymousAnimationId = 0;
|
|
this.cumulativeAnimationTime = 0;
|
|
}
|
|
addListener(eventName, listener) {
|
|
return this.events.on(eventName, listener);
|
|
}
|
|
/**
|
|
* Create an animation to tween a value between the `from` and `to` properties. If an animation already exists
|
|
* with the same `id`, immediately stop it.
|
|
*/
|
|
animate(opts) {
|
|
const batch = this.batch;
|
|
try {
|
|
batch.checkOverlappingId(opts.id);
|
|
} catch (error2) {
|
|
this.failsafeOnError(error2);
|
|
return;
|
|
}
|
|
let { id } = opts;
|
|
if (id == null) {
|
|
id = `__${this.currentAnonymousAnimationId}`;
|
|
this.currentAnonymousAnimationId += 1;
|
|
}
|
|
const skip = this.isSkipped() || opts.phase === "none";
|
|
if (skip) {
|
|
this.debug("AnimationManager - skipping animation");
|
|
}
|
|
const { delay, duration } = opts;
|
|
if (!validAnimationDuration(delay)) {
|
|
throw new Error(`Animation delay of ${delay} is unsupported (${id})`);
|
|
}
|
|
if (!validAnimationDuration(duration)) {
|
|
throw new Error(`Animation duration of ${duration} is unsupported (${id})`);
|
|
}
|
|
const animation = new Animation({
|
|
...opts,
|
|
id,
|
|
skip,
|
|
autoplay: this.isPlaying ? opts.autoplay : false,
|
|
phase: opts.phase,
|
|
defaultDuration: this.defaultDuration
|
|
});
|
|
if (this.forceTimeJump(animation, this.defaultDuration)) {
|
|
return;
|
|
}
|
|
this.batch.addAnimation(animation);
|
|
return animation;
|
|
}
|
|
play() {
|
|
if (this.isPlaying) {
|
|
return;
|
|
}
|
|
this.isPlaying = true;
|
|
this.debug("AnimationManager.play()");
|
|
try {
|
|
this.batch.play();
|
|
} catch (error2) {
|
|
this.failsafeOnError(error2);
|
|
}
|
|
this.requestAnimation();
|
|
}
|
|
stop() {
|
|
this.isPlaying = false;
|
|
this.cancelAnimation();
|
|
this.debug("AnimationManager.stop()");
|
|
this.batch.stop();
|
|
}
|
|
stopByAnimationId(id) {
|
|
try {
|
|
this.batch.stopByAnimationId(id);
|
|
} catch (error2) {
|
|
this.failsafeOnError(error2);
|
|
}
|
|
}
|
|
stopByAnimationGroupId(id) {
|
|
try {
|
|
this.batch.stopByAnimationGroupId(id);
|
|
} catch (error2) {
|
|
this.failsafeOnError(error2);
|
|
}
|
|
}
|
|
reset() {
|
|
if (this.isPlaying) {
|
|
this.stop();
|
|
this.play();
|
|
} else {
|
|
this.stop();
|
|
}
|
|
}
|
|
skip(skip = true) {
|
|
this.skipAnimations = skip;
|
|
}
|
|
isSkipped() {
|
|
return !this.rafAvailable || this.skipAnimations || this.batch.isSkipped();
|
|
}
|
|
isActive() {
|
|
return this.isPlaying && this.batch.isActive();
|
|
}
|
|
getRemainingTime(phase) {
|
|
return this.batch.getRemainingTime(phase);
|
|
}
|
|
getCumulativeAnimationTime() {
|
|
return this.cumulativeAnimationTime;
|
|
}
|
|
skipCurrentBatch() {
|
|
if (this.debug.check()) {
|
|
this.debug(`AnimationManager - skipCurrentBatch()`, {
|
|
stack: new Error("Stack trace for animation skip tracking").stack
|
|
});
|
|
}
|
|
this.batch.skip();
|
|
}
|
|
/** Mocking point for tests to guarantee that animation updates happen. */
|
|
isSkippingFrames() {
|
|
return true;
|
|
}
|
|
/** Mocking point for tests to capture requestAnimationFrame callbacks. */
|
|
scheduleAnimationFrame(cb) {
|
|
this.requestId = getWindow().requestAnimationFrame((t) => {
|
|
cb(t).catch((e) => logger_exports.error(e));
|
|
});
|
|
}
|
|
/** Mocking point for tests to skip animations to a specific point in time. */
|
|
forceTimeJump(_animation, _defaultDuration) {
|
|
return false;
|
|
}
|
|
requestAnimation() {
|
|
if (!this.rafAvailable)
|
|
return;
|
|
if (!this.batch.isActive() || this.requestId !== null)
|
|
return;
|
|
let prevTime;
|
|
const onAnimationFrame = async (time3) => {
|
|
await this.debug.group("AnimationManager.onAnimationFrame()", async () => {
|
|
const executeAnimationFrame = () => {
|
|
const deltaTime = time3 - (prevTime ?? time3);
|
|
prevTime = time3;
|
|
this.debug("AnimationManager", {
|
|
controllersCount: this.batch.size,
|
|
deltaTime
|
|
});
|
|
this.interactionManager.pushState(4 /* Animation */);
|
|
try {
|
|
this.batch.progress(deltaTime);
|
|
this.cumulativeAnimationTime += deltaTime;
|
|
} catch (error2) {
|
|
this.failsafeOnError(error2);
|
|
}
|
|
this.events.emit("animation-frame", {
|
|
type: "animation-frame",
|
|
deltaMs: deltaTime
|
|
});
|
|
};
|
|
if (this.isSkippingFrames()) {
|
|
await this.chartUpdateMutex.acquireImmediately(executeAnimationFrame);
|
|
} else {
|
|
await this.chartUpdateMutex.acquire(executeAnimationFrame);
|
|
}
|
|
if (this.batch.isActive()) {
|
|
this.scheduleAnimationFrame(onAnimationFrame);
|
|
} else {
|
|
this.batch.stop();
|
|
this.events.emit("animation-stop", {
|
|
type: "animation-stop",
|
|
deltaMs: this.batch.consumedTimeMs
|
|
});
|
|
}
|
|
});
|
|
};
|
|
this.events.emit("animation-start", {
|
|
type: "animation-start",
|
|
deltaMs: 0
|
|
});
|
|
this.scheduleAnimationFrame(onAnimationFrame);
|
|
}
|
|
cancelAnimation() {
|
|
if (this.requestId === null)
|
|
return;
|
|
cancelAnimationFrame(this.requestId);
|
|
this.events.emit("animation-stop", {
|
|
type: "animation-stop",
|
|
deltaMs: this.batch.consumedTimeMs
|
|
});
|
|
this.requestId = null;
|
|
this.startBatch();
|
|
}
|
|
failsafeOnError(error2, cancelAnimation = true) {
|
|
logger_exports.error("Error during animation, skipping animations", error2);
|
|
if (cancelAnimation) {
|
|
this.cancelAnimation();
|
|
}
|
|
}
|
|
startBatch(skipAnimations) {
|
|
this.debug(`AnimationManager - startBatch() with skipAnimations=${skipAnimations}.`);
|
|
this.reset();
|
|
this.batch.destroy();
|
|
this.batch = new AnimationBatch(this.defaultDuration * 1.5);
|
|
if (skipAnimations === true) {
|
|
this.batch.skip();
|
|
}
|
|
}
|
|
endBatch() {
|
|
if (this.batch.isActive()) {
|
|
this.batch.ready();
|
|
this.requestAnimation();
|
|
} else {
|
|
this.interactionManager.popState(4 /* Animation */);
|
|
if (this.batch.isSkipped()) {
|
|
this.batch.skip(false);
|
|
}
|
|
}
|
|
}
|
|
onBatchStop(cb) {
|
|
this.batch.stoppedCbs.add(cb);
|
|
}
|
|
destroy() {
|
|
this.stop();
|
|
this.events.clear();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/interaction/contextMenuTypes.ts
|
|
var ContextMenuBuiltinItems = class {
|
|
constructor() {
|
|
this.download = {
|
|
type: "action",
|
|
showOn: "always",
|
|
label: "contextMenuDownload",
|
|
enabled: true,
|
|
action: void 0,
|
|
items: void 0
|
|
};
|
|
this["zoom-to-cursor"] = {
|
|
type: "action",
|
|
showOn: "series-area",
|
|
label: "contextMenuZoomToCursor",
|
|
enabled: true,
|
|
action: void 0,
|
|
items: void 0
|
|
};
|
|
this["pan-to-cursor"] = {
|
|
type: "action",
|
|
showOn: "series-area",
|
|
label: "contextMenuPanToCursor",
|
|
enabled: true,
|
|
action: void 0,
|
|
items: void 0
|
|
};
|
|
this["reset-zoom"] = {
|
|
type: "action",
|
|
showOn: "series-area",
|
|
label: "contextMenuResetZoom",
|
|
enabled: true,
|
|
action: void 0,
|
|
items: void 0
|
|
};
|
|
this["toggle-series-visibility"] = {
|
|
type: "action",
|
|
showOn: "legend-item",
|
|
label: "contextMenuToggleSeriesVisibility",
|
|
enabled: true,
|
|
action: void 0,
|
|
items: void 0
|
|
};
|
|
this["toggle-other-series"] = {
|
|
type: "action",
|
|
showOn: "legend-item",
|
|
label: "contextMenuToggleOtherSeries",
|
|
enabled: true,
|
|
action: void 0,
|
|
items: void 0
|
|
};
|
|
this["separator"] = {
|
|
type: "separator",
|
|
showOn: "always",
|
|
label: "separator",
|
|
enabled: true,
|
|
action: void 0,
|
|
items: void 0
|
|
};
|
|
}
|
|
};
|
|
var ContextMenuBuiltinItemLists = class {
|
|
constructor() {
|
|
this.defaults = [
|
|
"download",
|
|
"zoom-to-cursor",
|
|
"pan-to-cursor",
|
|
"reset-zoom",
|
|
"toggle-series-visibility",
|
|
"toggle-other-series"
|
|
];
|
|
}
|
|
};
|
|
var ContextMenuBuiltins = class {
|
|
constructor() {
|
|
this.items = new ContextMenuBuiltinItems();
|
|
this.lists = new ContextMenuBuiltinItemLists();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/interaction/contextMenuRegistry.ts
|
|
var ContextMenuRegistry = class {
|
|
constructor(eventsHub) {
|
|
this.eventsHub = eventsHub;
|
|
this.builtins = new ContextMenuBuiltins();
|
|
this.hiddenActions = /* @__PURE__ */ new Set();
|
|
this.toggle("zoom-to-cursor", "hide");
|
|
this.toggle("pan-to-cursor", "hide");
|
|
this.toggle("reset-zoom", "hide");
|
|
}
|
|
static check(showOn, event) {
|
|
return event.showOn == showOn;
|
|
}
|
|
static checkCallback(desiredShowOn, showOn, _callback) {
|
|
return desiredShowOn === showOn;
|
|
}
|
|
dispatchContext(showOn, pointerEvent, context, position) {
|
|
const { widgetEvent } = pointerEvent;
|
|
if (widgetEvent.sourceEvent.defaultPrevented) {
|
|
return;
|
|
}
|
|
const x = position?.x ?? pointerEvent.canvasX;
|
|
const y = position?.y ?? pointerEvent.canvasY;
|
|
const event = { showOn, x, y, context, widgetEvent };
|
|
this.eventsHub.emit("context-menu:setup", event);
|
|
this.eventsHub.emit("context-menu:complete", event);
|
|
}
|
|
isVisible(id) {
|
|
return !this.hiddenActions.has(id);
|
|
}
|
|
toggle(id, action) {
|
|
action ?? (action = this.isVisible(id) ? "hide" : "show");
|
|
switch (action) {
|
|
case "show":
|
|
this.hiddenActions.delete(id);
|
|
break;
|
|
case "hide":
|
|
this.hiddenActions.add(id);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/interaction/highlightManager.ts
|
|
var _HighlightManager = class _HighlightManager {
|
|
constructor(eventsHub) {
|
|
this.eventsHub = eventsHub;
|
|
this.highlightStates = new StateTracker();
|
|
// Track pending unhighlights per caller
|
|
this.pendingUnhighlights = /* @__PURE__ */ new Map();
|
|
// Configurable delay (hardcoded for POC, will be user-configurable later)
|
|
this.unhighlightDelay = 100;
|
|
}
|
|
updateHighlight(callerId, highlightedDatum, delayed = false) {
|
|
if (highlightedDatum?.series?.isHighlightEnabled() === false) {
|
|
highlightedDatum = void 0;
|
|
}
|
|
const previousHighlight = this.getActiveHighlight();
|
|
if (highlightedDatum == null && delayed && this.unhighlightDelay > 0) {
|
|
if (!this.pendingUnhighlights.has(callerId)) {
|
|
const scheduler = debouncedCallback(() => {
|
|
this.applyPendingUnhighlight(callerId);
|
|
});
|
|
this.pendingUnhighlights.set(callerId, { scheduler });
|
|
scheduler.schedule(this.unhighlightDelay);
|
|
}
|
|
return;
|
|
}
|
|
const pending = this.pendingUnhighlights.get(callerId);
|
|
if (pending) {
|
|
pending.scheduler.cancel();
|
|
this.pendingUnhighlights.delete(callerId);
|
|
}
|
|
if (highlightedDatum) {
|
|
this.highlightStates.set(callerId, highlightedDatum);
|
|
} else {
|
|
this.highlightStates.delete(callerId);
|
|
}
|
|
this.maybeEmitChange(callerId, previousHighlight);
|
|
}
|
|
maybeEmitChange(callerId, previousHighlight) {
|
|
const currentHighlight = this.getActiveHighlight();
|
|
if (!this.isEqual(currentHighlight, previousHighlight)) {
|
|
this.eventsHub.emit(_HighlightManager.HIGHLIGHT_CHANGE_EVENT, {
|
|
callerId,
|
|
currentHighlight,
|
|
previousHighlight
|
|
});
|
|
}
|
|
}
|
|
applyPendingUnhighlight(callerId) {
|
|
if (!this.pendingUnhighlights.has(callerId)) {
|
|
return;
|
|
}
|
|
this.pendingUnhighlights.delete(callerId);
|
|
const previousHighlight = this.getActiveHighlight();
|
|
this.highlightStates.delete(callerId);
|
|
const currentHighlight = this.getActiveHighlight();
|
|
if (!this.isEqual(currentHighlight, previousHighlight)) {
|
|
this.eventsHub.emit(_HighlightManager.HIGHLIGHT_CHANGE_EVENT, {
|
|
callerId,
|
|
currentHighlight,
|
|
previousHighlight
|
|
});
|
|
}
|
|
}
|
|
getActiveHighlight() {
|
|
return this.highlightStates.stateValue();
|
|
}
|
|
destroy() {
|
|
for (const { scheduler } of this.pendingUnhighlights.values()) {
|
|
scheduler.cancel();
|
|
}
|
|
this.pendingUnhighlights.clear();
|
|
}
|
|
isEqual(a, b) {
|
|
return a === b || a != null && b != null && a.series === b.series && this.idsMatch(a, b) && a.datum === b.datum;
|
|
}
|
|
idsMatch(a, b) {
|
|
return a.itemId != null && b.itemId != null && a.itemId === b.itemId || a.datumIndex != null && b.datumIndex != null && objectsEqual(a.datumIndex, b.datumIndex);
|
|
}
|
|
};
|
|
// milliseconds
|
|
_HighlightManager.HIGHLIGHT_CHANGE_EVENT = "highlight:change";
|
|
var HighlightManager = _HighlightManager;
|
|
|
|
// packages/ag-charts-community/src/chart/series/seriesProperties.ts
|
|
var HighlightState = /* @__PURE__ */ ((HighlightState9) => {
|
|
HighlightState9[HighlightState9["None"] = 0] = "None";
|
|
HighlightState9[HighlightState9["Item"] = 1] = "Item";
|
|
HighlightState9[HighlightState9["Series"] = 2] = "Series";
|
|
HighlightState9[HighlightState9["OtherSeries"] = 3] = "OtherSeries";
|
|
HighlightState9[HighlightState9["OtherItem"] = 4] = "OtherItem";
|
|
return HighlightState9;
|
|
})(HighlightState || {});
|
|
var highlightStates = [
|
|
0 /* None */,
|
|
1 /* Item */,
|
|
2 /* Series */,
|
|
3 /* OtherSeries */,
|
|
4 /* OtherItem */
|
|
];
|
|
function getHighlightStyleOptionKeys(highlightState) {
|
|
switch (highlightState) {
|
|
case 1 /* Item */:
|
|
return ["highlightedItem", "highlightedSeries"];
|
|
case 4 /* OtherItem */:
|
|
return ["unhighlightedItem", "highlightedSeries"];
|
|
case 2 /* Series */:
|
|
return ["highlightedSeries"];
|
|
case 3 /* OtherSeries */:
|
|
return ["unhighlightedSeries"];
|
|
case 0 /* None */:
|
|
return [];
|
|
}
|
|
}
|
|
function toHighlightString(state) {
|
|
const unreachable = (a) => a;
|
|
switch (state) {
|
|
case 1 /* Item */:
|
|
return "highlighted-item";
|
|
case 4 /* OtherItem */:
|
|
return "unhighlighted-item";
|
|
case 2 /* Series */:
|
|
return "highlighted-series";
|
|
case 3 /* OtherSeries */:
|
|
return "unhighlighted-series";
|
|
case 0 /* None */:
|
|
return "none";
|
|
default:
|
|
return unreachable(state);
|
|
}
|
|
}
|
|
var SeriesItemHighlightStyle = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesItemHighlightStyle.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesItemHighlightStyle.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesItemHighlightStyle.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesItemHighlightStyle.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesItemHighlightStyle.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesItemHighlightStyle.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesItemHighlightStyle.prototype, "lineDashOffset", 2);
|
|
var HighlightProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = true;
|
|
this.range = "tooltip";
|
|
this.bringToFront = true;
|
|
this.highlightedItem = {};
|
|
this.unhighlightedItem = {};
|
|
this.highlightedSeries = {};
|
|
this.unhighlightedSeries = {};
|
|
}
|
|
getStyle(highlightState) {
|
|
const keys = getHighlightStyleOptionKeys(highlightState);
|
|
if (keys.length === 0)
|
|
return {};
|
|
return mergeDefaults(...keys.map((key) => this[key]));
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HighlightProperties.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HighlightProperties.prototype, "range", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HighlightProperties.prototype, "bringToFront", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HighlightProperties.prototype, "highlightedItem", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HighlightProperties.prototype, "unhighlightedItem", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HighlightProperties.prototype, "highlightedSeries", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HighlightProperties.prototype, "unhighlightedSeries", 2);
|
|
var SegmentOptions = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = "#c16068";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "#874349";
|
|
this.strokeWidth = 2;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SegmentOptions.prototype, "start", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SegmentOptions.prototype, "stop", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SegmentOptions.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SegmentOptions.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SegmentOptions.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SegmentOptions.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SegmentOptions.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SegmentOptions.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SegmentOptions.prototype, "lineDashOffset", 2);
|
|
var Segmentation = class {
|
|
constructor() {
|
|
this.key = "x";
|
|
this.segments = new PropertiesArray(SegmentOptions);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Segmentation.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Segmentation.prototype, "key", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Segmentation.prototype, "segments", 2);
|
|
var FillGradientDefaults = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "gradient";
|
|
this.colorStops = [];
|
|
this.bounds = "item";
|
|
this.gradient = "linear";
|
|
this.rotation = 0;
|
|
this.reverse = false;
|
|
this.colorSpace = "rgb";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillGradientDefaults.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillGradientDefaults.prototype, "colorStops", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillGradientDefaults.prototype, "bounds", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillGradientDefaults.prototype, "gradient", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillGradientDefaults.prototype, "rotation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillGradientDefaults.prototype, "reverse", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillGradientDefaults.prototype, "colorSpace", 2);
|
|
var FillPatternDefaults = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "pattern";
|
|
this.colorStops = [];
|
|
this.bounds = "item";
|
|
this.gradient = "linear";
|
|
this.rotation = 0;
|
|
this.scale = 1;
|
|
this.reverse = false;
|
|
this.pattern = "forward-slanted-lines";
|
|
this.width = 26;
|
|
this.height = 26;
|
|
this.padding = 6;
|
|
this.fill = "black";
|
|
this.fillOpacity = 1;
|
|
this.backgroundFill = "white";
|
|
this.backgroundFillOpacity = 1;
|
|
this.stroke = "black";
|
|
this.strokeOpacity = 1;
|
|
this.strokeWidth = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "colorStops", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "bounds", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "gradient", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "rotation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "scale", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "reverse", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "path", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "pattern", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "width", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "height", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "backgroundFill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "backgroundFillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillPatternDefaults.prototype, "strokeWidth", 2);
|
|
var FillImageDefaults = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "image";
|
|
this.url = "";
|
|
this.rotation = 0;
|
|
this.scale = 1;
|
|
this.backgroundFill = "black";
|
|
this.backgroundFillOpacity = 1;
|
|
this.repeat = "no-repeat";
|
|
this.fit = "contain";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillImageDefaults.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillImageDefaults.prototype, "url", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillImageDefaults.prototype, "rotation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillImageDefaults.prototype, "scale", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillImageDefaults.prototype, "backgroundFill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillImageDefaults.prototype, "backgroundFillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillImageDefaults.prototype, "repeat", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillImageDefaults.prototype, "fit", 2);
|
|
var SeriesProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.visible = true;
|
|
this.focusPriority = Infinity;
|
|
this.showInLegend = true;
|
|
this.cursor = "default";
|
|
this.nodeClickRange = "exact";
|
|
this.highlight = new HighlightProperties();
|
|
}
|
|
handleUnknownProperties(unknownKeys, properties) {
|
|
if ("context" in properties) {
|
|
this.context = properties.context;
|
|
unknownKeys.delete("context");
|
|
}
|
|
if ("allowNullKeys" in properties) {
|
|
this.allowNullKeys = properties.allowNullKeys;
|
|
unknownKeys.delete("allowNullKeys");
|
|
}
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesProperties.prototype, "id", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesProperties.prototype, "visible", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesProperties.prototype, "focusPriority", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesProperties.prototype, "showInLegend", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesProperties.prototype, "cursor", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesProperties.prototype, "nodeClickRange", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesProperties.prototype, "highlight", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/series/util.ts
|
|
function datumBoundaryPoints(datum, domain) {
|
|
if (domain.length === 0) {
|
|
return [false, false];
|
|
}
|
|
const d0 = domain[0];
|
|
const d1 = domain.at(-1);
|
|
if (typeof d0 === "string" || d0 === null || d0 === void 0) {
|
|
return [datum === d0, datum === d1];
|
|
}
|
|
if (datum == null) {
|
|
return [false, false];
|
|
}
|
|
const datumValue = datum.valueOf();
|
|
if (d0 == null || d1 == null) {
|
|
return [false, false];
|
|
}
|
|
let min = d0.valueOf();
|
|
let max = d1.valueOf();
|
|
if (min > max) {
|
|
[min, max] = [max, min];
|
|
}
|
|
return [datumValue === min, datumValue === max];
|
|
}
|
|
function datumStylerProperties(xValue, yValue, xKey, yKey, xDomain, yDomain) {
|
|
const [min, max] = datumBoundaryPoints(yValue, yDomain);
|
|
const [first2, last] = datumBoundaryPoints(xValue, xDomain);
|
|
return {
|
|
xKey,
|
|
yKey,
|
|
xValue,
|
|
yValue,
|
|
first: first2,
|
|
last,
|
|
min,
|
|
max
|
|
};
|
|
}
|
|
function visibleRangeIndices(sortOrder, length2, [range0, range1], xRange) {
|
|
let xMinIndex = findMinIndex(0, length2 - 1, (i) => {
|
|
const index = sortOrder === 1 ? i : length2 - i;
|
|
const x1 = xRange(index)?.[1] ?? Number.NaN;
|
|
return !Number.isFinite(x1) || x1 >= range0;
|
|
}) ?? 0;
|
|
let xMaxIndex = findMaxIndex(0, length2 - 1, (i) => {
|
|
const index = sortOrder === 1 ? i : length2 - i;
|
|
const x0 = xRange(index)?.[0] ?? Number.NaN;
|
|
return !Number.isFinite(x0) || x0 <= range1;
|
|
}) ?? length2 - 1;
|
|
if (sortOrder === -1) {
|
|
[xMinIndex, xMaxIndex] = [length2 - xMaxIndex, length2 - xMinIndex];
|
|
}
|
|
xMinIndex = Math.max(xMinIndex, 0);
|
|
xMaxIndex = Math.min(xMaxIndex + 1, length2);
|
|
return [xMinIndex, xMaxIndex];
|
|
}
|
|
function getDatumRefPoint(series, datum, movedBounds) {
|
|
if (movedBounds) {
|
|
const { x, y, width: width2, height: height2 } = movedBounds;
|
|
return { canvasX: x + width2 / 2, canvasY: y + height2 / 2 };
|
|
}
|
|
const refPoint = datum.yBar?.upperPoint ?? datum.midPoint ?? series.datumMidPoint?.(datum);
|
|
if (refPoint) {
|
|
const { x, y } = Transformable.toCanvasPoint(series.contentGroup, refPoint.x, refPoint.y);
|
|
return { canvasX: Math.round(x), canvasY: Math.round(y) };
|
|
}
|
|
}
|
|
function countExpandingSearch(min, max, start2, countUntil, iteratee) {
|
|
let i = -1;
|
|
let count = 0;
|
|
let shift = 0;
|
|
let reachedAnEnd = false;
|
|
while (count < countUntil && i <= max - min) {
|
|
i += 1;
|
|
const index = start2 + shift;
|
|
if (!reachedAnEnd)
|
|
shift *= -1;
|
|
if (shift >= 0)
|
|
shift += 1;
|
|
if (reachedAnEnd && shift < 0)
|
|
shift -= 1;
|
|
if (index < min || index > max) {
|
|
reachedAnEnd = true;
|
|
continue;
|
|
}
|
|
if (iteratee(index))
|
|
count += 1;
|
|
}
|
|
return count;
|
|
}
|
|
function getItemStyles(getItemStyle2) {
|
|
const result = {};
|
|
for (const state of highlightStates) {
|
|
result[state] = getItemStyle2(void 0, false, state);
|
|
}
|
|
return result;
|
|
}
|
|
function getItemStylesPerItemId(getItemStyle2, ...itemIds) {
|
|
const result = {};
|
|
for (const itemId of itemIds ?? ["default"]) {
|
|
for (const state of highlightStates) {
|
|
const states = result[itemId] ?? (result[itemId] = {});
|
|
states[state] = getItemStyle2(void 0, false, state, itemId);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
function hasDimmedOpacity(style2) {
|
|
return (style2?.opacity ?? 1) < 1 || (style2?.fillOpacity ?? 1) < 1 || (style2?.strokeOpacity ?? 1) < 1;
|
|
}
|
|
var opaqueMarkerFillCache = /* @__PURE__ */ new Map();
|
|
function isOpaqueMarkerFillStyle(style2) {
|
|
if (style2 == null)
|
|
return false;
|
|
const fill = style2.fill;
|
|
if (!isString(fill))
|
|
return false;
|
|
const fillString = fill.trim();
|
|
const fillLower = fillString.toLowerCase();
|
|
if (fillLower === "transparent" || fillLower === "none")
|
|
return false;
|
|
let cached = opaqueMarkerFillCache.get(fillString);
|
|
if (cached == null) {
|
|
try {
|
|
cached = Color.fromString(fillString).a === 1;
|
|
} catch {
|
|
cached = false;
|
|
}
|
|
opaqueMarkerFillCache.set(fillString, cached);
|
|
}
|
|
return cached;
|
|
}
|
|
function resolveMarkerDrawingMode(baseDrawingMode, style2) {
|
|
if (baseDrawingMode !== "cutout")
|
|
return baseDrawingMode;
|
|
return isOpaqueMarkerFillStyle(style2) ? "cutout" : "overlay";
|
|
}
|
|
function findNodeDatumInArray(itemIdOrIndex, nodeData) {
|
|
for (const node of nodeData ?? []) {
|
|
switch (typeof itemIdOrIndex) {
|
|
case "string":
|
|
if (node.itemId === itemIdOrIndex) {
|
|
return node;
|
|
}
|
|
break;
|
|
case "number":
|
|
if (node.datumIndex === itemIdOrIndex) {
|
|
return node;
|
|
}
|
|
break;
|
|
default:
|
|
return itemIdOrIndex;
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/interaction/tooltipManager.ts
|
|
var TooltipManager = class {
|
|
constructor(eventsHub, localeManager, domManager, tooltip) {
|
|
this.domManager = domManager;
|
|
this.tooltip = tooltip;
|
|
this.stateTracker = new StateTracker();
|
|
this.suppressState = new StateTracker(false);
|
|
this.appliedState = null;
|
|
// Track pending removals per caller
|
|
this.pendingRemovals = /* @__PURE__ */ new Map();
|
|
// Configurable delay (match highlights at 100ms)
|
|
this.removeDelay = 100;
|
|
// milliseconds
|
|
this.cleanup = new CleanupRegistry();
|
|
this.cleanup.register(
|
|
tooltip.setup(localeManager, domManager),
|
|
eventsHub.on("dom:hidden", () => this.tooltip.hide())
|
|
);
|
|
}
|
|
destroy() {
|
|
for (const { scheduler } of this.pendingRemovals.values()) {
|
|
scheduler.cancel();
|
|
}
|
|
this.pendingRemovals.clear();
|
|
this.cleanup.flush();
|
|
}
|
|
updateTooltip(callerId, meta, content, pagination) {
|
|
const pending = this.pendingRemovals.get(callerId);
|
|
if (pending) {
|
|
pending.scheduler.cancel();
|
|
this.pendingRemovals.delete(callerId);
|
|
}
|
|
content ?? (content = this.stateTracker.get(callerId)?.content);
|
|
this.stateTracker.set(callerId, { meta, content, pagination });
|
|
this.applyStates();
|
|
}
|
|
removeTooltip(callerId, meta, delayed = false) {
|
|
if (delayed && this.removeDelay > 0) {
|
|
const existingPending = this.pendingRemovals.get(callerId);
|
|
if (existingPending) {
|
|
if (meta) {
|
|
existingPending.lastMeta = meta;
|
|
}
|
|
return;
|
|
}
|
|
const scheduler = debouncedCallback(() => {
|
|
this.applyPendingRemoval(callerId);
|
|
});
|
|
this.pendingRemovals.set(callerId, { scheduler, lastMeta: meta });
|
|
scheduler.schedule(this.removeDelay);
|
|
return;
|
|
}
|
|
const pending = this.pendingRemovals.get(callerId);
|
|
if (pending) {
|
|
pending.scheduler.cancel();
|
|
this.pendingRemovals.delete(callerId);
|
|
}
|
|
this.stateTracker.delete(callerId);
|
|
this.applyStates();
|
|
}
|
|
suppressTooltip(callerId) {
|
|
this.suppressState.set(callerId, true);
|
|
}
|
|
unsuppressTooltip(callerId) {
|
|
this.suppressState.delete(callerId);
|
|
}
|
|
applyPendingRemoval(callerId) {
|
|
if (!this.pendingRemovals.has(callerId)) {
|
|
return;
|
|
}
|
|
this.pendingRemovals.delete(callerId);
|
|
this.stateTracker.delete(callerId);
|
|
this.applyStates();
|
|
}
|
|
applyStates() {
|
|
const id = this.stateTracker.stateId();
|
|
const state = id ? this.stateTracker.get(id) : void 0;
|
|
if (this.suppressState.stateValue() || state?.meta == null || state?.content == null) {
|
|
this.appliedState = null;
|
|
this.tooltip.hide();
|
|
return;
|
|
}
|
|
const canvasRect = this.domManager.getBoundingClientRect();
|
|
const boundingRect = this.tooltip.bounds === "extended" ? this.domManager.getOverlayClientRect() : canvasRect;
|
|
if (objectsEqual(this.appliedState?.content, state?.content) && objectsEqual(this.appliedState?.pagination, state?.pagination)) {
|
|
const renderInstantly = this.tooltip.isVisible();
|
|
this.tooltip.show(boundingRect, canvasRect, state?.meta, null, void 0, renderInstantly);
|
|
} else {
|
|
this.tooltip.show(boundingRect, canvasRect, state?.meta, state?.content, state?.pagination);
|
|
}
|
|
this.appliedState = state;
|
|
}
|
|
static makeTooltipMeta(event, series, datum, movedBounds) {
|
|
const { canvasX, canvasY } = event;
|
|
const tooltip = series.properties.tooltip;
|
|
const { placement, anchorTo, xOffset, yOffset } = tooltip.position;
|
|
const refPoint = getDatumRefPoint(series, datum, movedBounds);
|
|
const meta = {
|
|
canvasX,
|
|
canvasY,
|
|
nodeCanvasX: refPoint?.canvasX ?? canvasX,
|
|
nodeCanvasY: refPoint?.canvasY ?? canvasY,
|
|
enableInteraction: tooltip.interaction?.enabled ?? false,
|
|
showArrow: tooltip.showArrow,
|
|
position: {
|
|
placement,
|
|
anchorTo,
|
|
xOffset,
|
|
yOffset
|
|
}
|
|
};
|
|
return meta;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/interaction/dragInterpreter.ts
|
|
var DRAG_THRESHOLD_PX = 3;
|
|
var DOUBLE_TAP_TIMER_MS = 505;
|
|
var DOUBLE_TAP_THRESHOLD_PX = 30;
|
|
function makeSynthetic(type, event) {
|
|
const { device, offsetX, offsetY, clientX, clientY, currentX, currentY, sourceEvent } = event;
|
|
return { type, device, offsetX, offsetY, clientX, clientY, currentX, currentY, sourceEvent };
|
|
}
|
|
function checkDragDistance(dx2, dy2) {
|
|
const distanceSquared2 = dx2 * dx2 + dy2 * dy2;
|
|
const thresholdSquared = DRAG_THRESHOLD_PX * DRAG_THRESHOLD_PX;
|
|
return distanceSquared2 >= thresholdSquared;
|
|
}
|
|
function checkDoubleTapDistance(t1, t2) {
|
|
const dx2 = t1.clientX - t2.clientX;
|
|
const dy2 = t1.clientY - t2.clientY;
|
|
const distanceSquared2 = dx2 * dx2 + dy2 * dy2;
|
|
const thresholdSquared = DOUBLE_TAP_THRESHOLD_PX * DOUBLE_TAP_THRESHOLD_PX;
|
|
return distanceSquared2 < thresholdSquared;
|
|
}
|
|
var DragInterpreter = class {
|
|
constructor(widget) {
|
|
this.cleanup = new CleanupRegistry();
|
|
this.events = new EventEmitter();
|
|
this.isDragging = false;
|
|
this.touch = { distanceTravelledX: 0, distanceTravelledY: 0, clientX: 0, clientY: 0 };
|
|
this.cleanup.register(
|
|
widget.addListener("touchstart", this.onTouchStart.bind(this)),
|
|
widget.addListener("touchmove", this.onTouchMove.bind(this)),
|
|
widget.addListener("touchend", this.onTouchEnd.bind(this)),
|
|
widget.addListener("mousemove", this.onMouseMove.bind(this)),
|
|
widget.addListener("dblclick", this.onDblClick.bind(this)),
|
|
widget.addListener("drag-start", this.onDragStart.bind(this)),
|
|
widget.addListener("drag-move", this.onDragMove.bind(this)),
|
|
widget.addListener("drag-end", this.onDragEnd.bind(this))
|
|
);
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
}
|
|
onTouchStart(e) {
|
|
const { clientX, clientY } = e.sourceEvent.targetTouches[0] ?? { clientX: Infinity, clientY: Infinity };
|
|
this.touch.distanceTravelledX = 0;
|
|
this.touch.distanceTravelledY = 0;
|
|
this.touch.clientX = clientX;
|
|
this.touch.clientY = clientY;
|
|
}
|
|
onTouchMove(e) {
|
|
const { clientX, clientY } = e.sourceEvent.targetTouches[0] ?? { clientX: Infinity, clientY: Infinity };
|
|
this.touch.distanceTravelledX += Math.abs(this.touch.clientX - clientX);
|
|
this.touch.distanceTravelledY += Math.abs(this.touch.clientY - clientY);
|
|
this.touch.clientX = clientX;
|
|
this.touch.clientY = clientY;
|
|
}
|
|
onTouchEnd(event) {
|
|
event.sourceEvent.preventDefault();
|
|
}
|
|
onMouseMove(event) {
|
|
this.events.emit("mousemove", event);
|
|
}
|
|
onDblClick(event) {
|
|
this.events.emit("dblclick", event);
|
|
}
|
|
onDragStart(event) {
|
|
this.dragStartEvent = event;
|
|
}
|
|
onDragMove(event) {
|
|
if (this.dragStartEvent != null) {
|
|
if (checkDragDistance(event.originDeltaX, event.originDeltaY)) {
|
|
this.events.emit("drag-start", this.dragStartEvent);
|
|
this.events.emit("drag-move", { ...this.dragStartEvent, type: "drag-move" });
|
|
this.dragStartEvent = void 0;
|
|
this.isDragging = true;
|
|
}
|
|
}
|
|
if (this.isDragging) {
|
|
this.events.emit("drag-move", event);
|
|
}
|
|
}
|
|
onDragEnd(event) {
|
|
if (this.isDragging) {
|
|
this.events.emit("drag-end", event);
|
|
this.isDragging = false;
|
|
return;
|
|
}
|
|
if (event.device === "mouse") {
|
|
const click = makeSynthetic("click", event);
|
|
this.events.emit("click", click);
|
|
} else if (event.sourceEvent.type === "touchend") {
|
|
if (checkDragDistance(this.touch.distanceTravelledX, this.touch.distanceTravelledY)) {
|
|
return;
|
|
}
|
|
const click = makeSynthetic("click", event);
|
|
this.events.emit("click", click);
|
|
const now = Date.now();
|
|
if (this.lastClick !== void 0 && now - this.lastClick.time <= DOUBLE_TAP_TIMER_MS && checkDoubleTapDistance(this.lastClick, event)) {
|
|
const dblClick = makeSynthetic("dblclick", event);
|
|
this.events.emit("dblclick", dblClick);
|
|
this.lastClick = void 0;
|
|
} else {
|
|
this.lastClick = { time: now, clientX: event.clientX, clientY: event.clientY };
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/interaction/widgetSet.ts
|
|
var DOMManagerWidget = class extends NativeWidget {
|
|
constructor(elem) {
|
|
super(elem);
|
|
}
|
|
addChildToDOM() {
|
|
}
|
|
removeChildFromDOM() {
|
|
}
|
|
};
|
|
var WidgetSet = class {
|
|
constructor(domManager, opts) {
|
|
this.seriesWidget = new DOMManagerWidget(domManager.getParent("series-area"));
|
|
this.chartWidget = new DOMManagerWidget(domManager.getParent("canvas-proxy"));
|
|
this.containerWidget = new DOMManagerWidget(domManager.getParent("canvas-container"));
|
|
this.containerWidget.addChild(this.chartWidget);
|
|
this.chartWidget.addChild(this.seriesWidget);
|
|
if (opts.withDragInterpretation) {
|
|
this.seriesDragInterpreter = new DragInterpreter(this.seriesWidget);
|
|
}
|
|
}
|
|
destroy() {
|
|
this.seriesDragInterpreter?.destroy();
|
|
this.seriesWidget.destroy();
|
|
this.chartWidget.destroy();
|
|
this.containerWidget.destroy();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scale/continuousScale.ts
|
|
var _ContinuousScale = class _ContinuousScale extends AbstractScale {
|
|
constructor(domain = [], range3 = []) {
|
|
super();
|
|
this.range = range3;
|
|
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: range3 } = this;
|
|
const clamp2 = 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 (clamp2) {
|
|
const [start2, stop] = findMinMax([d0, d1]);
|
|
if (x < start2) {
|
|
return range3[0];
|
|
} else if (x > stop) {
|
|
return range3[1];
|
|
}
|
|
}
|
|
if (d0 === d1) {
|
|
return (range3[0] + range3[1]) / 2;
|
|
} else if (x === d0) {
|
|
return range3[0];
|
|
} else if (x === d1) {
|
|
return range3[1];
|
|
}
|
|
const r0 = range3[0];
|
|
return r0 + (x - d0) / (d1 - d0) * (range3[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: range3 } = this;
|
|
const [r0, r1] = range3;
|
|
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/bandScale.ts
|
|
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 = clamp(0, value, 1);
|
|
this._paddingInner = value;
|
|
this._paddingOuter = value;
|
|
}
|
|
get padding() {
|
|
return this._paddingInner;
|
|
}
|
|
set paddingInner(value) {
|
|
this.invalid = true;
|
|
this._paddingInner = clamp(0, value, 1);
|
|
}
|
|
get paddingInner() {
|
|
return this._paddingInner;
|
|
}
|
|
set paddingOuter(value) {
|
|
this.invalid = true;
|
|
this._paddingOuter = clamp(0, value, 1);
|
|
}
|
|
get paddingOuter() {
|
|
return this._paddingOuter;
|
|
}
|
|
/** Override in subclass to provide band count without triggering full band materialization */
|
|
getBandCountForUpdate() {
|
|
return this.bands.length;
|
|
}
|
|
refresh() {
|
|
if (!this.invalid)
|
|
return;
|
|
this.invalid = false;
|
|
this.update();
|
|
if (this.invalid) {
|
|
logger_exports.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 distance2 = Math.abs(p - position);
|
|
if (distance2 === 0)
|
|
return mid;
|
|
if (distance2 < closestDistance) {
|
|
closestDistance = distance2;
|
|
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 round4 = this.round && Math.floor(rawStep) > 0;
|
|
const step = round4 ? Math.floor(rawStep) : rawStep;
|
|
let inset = r0 + (rangeDistance - step * (bandCount - paddingInner)) / 2;
|
|
let bandwidth = step * (1 - paddingInner);
|
|
if (round4) {
|
|
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: range3 } = this;
|
|
const min = Math.min(range3[0], range3[1]);
|
|
const max = Math.max(range3[0], range3[1]);
|
|
return clamp(min, inset + step * i, max);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
Invalidating
|
|
], _BandScale.prototype, "range", 2);
|
|
__decorateClass([
|
|
Invalidating
|
|
], _BandScale.prototype, "round", 2);
|
|
var BandScale = _BandScale;
|
|
|
|
// packages/ag-charts-community/src/scale/discreteTimeScale.ts
|
|
var APPROXIMATE_THRESHOLD = 1e3;
|
|
var SAMPLE_POINTS = 20;
|
|
function checkUniformityBySampling(bands, startIdx = 0, endIdx = bands.length - 1) {
|
|
const n = endIdx - startIdx + 1;
|
|
if (n < 2)
|
|
return { isUniform: false };
|
|
const indices = Array.from(
|
|
{ length: SAMPLE_POINTS },
|
|
(_, i) => startIdx + Math.floor(i * (n - 1) / (SAMPLE_POINTS - 1))
|
|
);
|
|
const samples = indices.map((i) => bands[i].valueOf());
|
|
const expectedInterval = (samples.at(-1) - samples[0]) / (n - 1);
|
|
if (!Number.isFinite(expectedInterval) || expectedInterval === 0) {
|
|
return { isUniform: false };
|
|
}
|
|
const tolerance = Math.abs(expectedInterval * 0.01);
|
|
for (let i = 1; i < samples.length; i++) {
|
|
const indexGap = indices[i] - indices[i - 1];
|
|
const actualInterval = (samples[i] - samples[i - 1]) / indexGap;
|
|
if (Math.abs(actualInterval - expectedInterval) > tolerance) {
|
|
return { isUniform: false };
|
|
}
|
|
}
|
|
return { isUniform: true, interval: expectedInterval };
|
|
}
|
|
var DiscreteTimeScale = class _DiscreteTimeScale extends BandScale {
|
|
static is(value) {
|
|
return value instanceof _DiscreteTimeScale;
|
|
}
|
|
toDomain(value) {
|
|
return new Date(value);
|
|
}
|
|
get reversed() {
|
|
const { domain } = this;
|
|
return domain.length > 0 && domain[0].valueOf() > domain.at(-1).valueOf();
|
|
}
|
|
/** Cached numeric band values for efficient binary search. Subclasses should override with a cached version. */
|
|
get numericBands() {
|
|
return this.bands.map((d) => d.valueOf());
|
|
}
|
|
convert(value, options) {
|
|
this.refresh();
|
|
if (!(value instanceof Date))
|
|
value = new Date(value);
|
|
const { domain, reversed } = this;
|
|
const numericBands = this.numericBands;
|
|
const bandCount = numericBands.length;
|
|
if (domain.length <= 0)
|
|
return Number.NaN;
|
|
const r0 = this.ordinalRange(0);
|
|
const r1 = this.ordinalRange(bandCount - 1);
|
|
if (bandCount === 0)
|
|
return r0;
|
|
if (options?.clamp === true) {
|
|
const { range: range3 } = this;
|
|
if (value.valueOf() < numericBands[0])
|
|
return range3[0];
|
|
if (value.valueOf() > numericBands.at(-1))
|
|
return range3[1];
|
|
}
|
|
const alignment = options?.alignment ?? 0 /* Leading */;
|
|
if (alignment !== 2 /* Interpolate */) {
|
|
const r2 = super.convert(value, options);
|
|
return reversed ? r1 - (r2 - r0) : r2;
|
|
}
|
|
const v = value.valueOf();
|
|
let bandIndex = this.findIndex(value) ?? 0;
|
|
let dIndex;
|
|
if (reversed) {
|
|
bandIndex = Math.min(Math.max(bandIndex, 1), bandCount - 1);
|
|
dIndex = -1;
|
|
} else {
|
|
bandIndex = Math.min(Math.max(bandIndex, 0), bandCount - 2);
|
|
dIndex = 1;
|
|
}
|
|
const v0 = numericBands[bandIndex];
|
|
const v1 = numericBands[bandIndex + dIndex];
|
|
const vr0 = this.ordinalRange(bandIndex);
|
|
const vr1 = this.ordinalRange(bandIndex + dIndex);
|
|
const ratio2 = (v - v0) / (v1 - v0);
|
|
const r = ratio2 * (vr1 - vr0) + vr0;
|
|
return reversed ? r1 - (r - r0) : r;
|
|
}
|
|
invert(position, nearest = false) {
|
|
this.refresh();
|
|
const { domain } = this;
|
|
if (domain.length <= 0)
|
|
return;
|
|
const bands = this.bands;
|
|
const bandCount = this.getBandCountForUpdate();
|
|
const reversed = domain[0].valueOf() > domain.at(-1).valueOf();
|
|
let index;
|
|
if (nearest) {
|
|
index = this.invertNearestIndex(position - this.bandwidth / 2);
|
|
} else {
|
|
const closestIndex = findMinIndex(0, bandCount - 1, (i) => {
|
|
const p = this.ordinalRange(i);
|
|
return p >= position;
|
|
});
|
|
index = closestIndex ?? bandCount - 1;
|
|
}
|
|
return bands[reversed ? bandCount - 1 - index : index];
|
|
}
|
|
/** Override in subclass to provide cached uniformity check result */
|
|
getUniformityCache(_visibleRange) {
|
|
return void 0;
|
|
}
|
|
findIndex(value, alignment = 0 /* Leading */) {
|
|
if (value == null)
|
|
return void 0;
|
|
const numericBands = this.numericBands;
|
|
const n = numericBands.length;
|
|
if (n === 0)
|
|
return void 0;
|
|
if (n === 1)
|
|
return 0;
|
|
const target = value.valueOf();
|
|
if (alignment === 1 /* Trailing */) {
|
|
return findMinIndex(0, n - 1, (index) => numericBands[index] >= target);
|
|
}
|
|
return findMaxIndex(0, n - 1, (index) => numericBands[index] <= target);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/panToBBox.ts
|
|
function normalize(screenMin, min, screenMax, max, target) {
|
|
return min + (max - min) * ((target - screenMin) / (screenMax - screenMin));
|
|
}
|
|
function unnormalize(screenMin, min, screenMax, max, ratio2) {
|
|
return screenMin + (ratio2 - min) * ((screenMax - screenMin) / (max - min));
|
|
}
|
|
function calcWorldAxis(viewportMin, viewportMax, ratio2) {
|
|
return [
|
|
unnormalize(viewportMin, ratio2.min, viewportMax, ratio2.max, 0),
|
|
unnormalize(viewportMin, ratio2.min, viewportMax, ratio2.max, 1)
|
|
];
|
|
}
|
|
function calcWorldVec4(viewport, ratioX, ratioY) {
|
|
const [x1, x2] = calcWorldAxis(viewport.x1, viewport.x2, ratioX);
|
|
const [y1, y2] = calcWorldAxis(viewport.y1, viewport.y2, ratioY);
|
|
return { x1, x2, y1, y2 };
|
|
}
|
|
function panAxesUnnormalized(worldMin, worldMax, viewportMin, viewportMax, targetMin, targetMax) {
|
|
if (viewportMin <= targetMin && targetMax <= viewportMax)
|
|
return viewportMin;
|
|
const minDiff = targetMin - viewportMin;
|
|
const maxDiff = targetMax - viewportMax;
|
|
const diff9 = Math.abs(minDiff) < Math.abs(maxDiff) ? minDiff : maxDiff;
|
|
return clamp(worldMin, viewportMin + diff9, worldMax);
|
|
}
|
|
function calcPanToBBoxRatios(viewportBBox, ratios, targetBBox) {
|
|
const { x: ratioX = { min: 0, max: 1 }, y: ratioY = { min: 0, max: 1 } } = ratios;
|
|
const target = vector4_exports.from(targetBBox);
|
|
const viewport = vector4_exports.from(viewportBBox);
|
|
const world = calcWorldVec4(viewport, ratioX, ratioY);
|
|
const x = panAxesUnnormalized(world.x1, world.x2, viewport.x1, viewport.x2, target.x1, target.x2);
|
|
const y = panAxesUnnormalized(world.y1, world.y2, viewport.y1, viewport.y2, target.y1, target.y2);
|
|
const result = {
|
|
x: {
|
|
min: normalize(viewport.x1, ratioX.min, viewport.x2, ratioX.max, x),
|
|
max: normalize(viewport.x1, ratioX.min, viewport.x2, ratioX.max, x + viewportBBox.width)
|
|
},
|
|
y: {
|
|
min: normalize(viewport.y1, ratioY.min, viewport.y2, ratioY.max, y),
|
|
max: normalize(viewport.y1, ratioY.min, viewport.y2, ratioY.max, y + viewportBBox.height)
|
|
}
|
|
};
|
|
const diffX = result.x.max - result.x.min;
|
|
const diffY = result.y.max - result.y.min;
|
|
result.x.min = clamp(0, result.x.min, 1 - diffX);
|
|
result.x.max = result.x.min + diffX;
|
|
result.y.min = clamp(0, result.y.min, 1 - diffY);
|
|
result.y.max = result.y.min + diffY;
|
|
return result;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/rangeAlignment.ts
|
|
function rangeAlignment(start2, end3) {
|
|
const startValue = start2?.valueOf();
|
|
const endValue = end3?.valueOf();
|
|
if (typeof startValue !== "number" || typeof endValue !== "number")
|
|
return [void 0, void 0];
|
|
return startValue < endValue ? [0 /* Leading */, 1 /* Trailing */] : [1 /* Trailing */, 0 /* Leading */];
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/interaction/zoomManager.ts
|
|
var rangeValidator2 = (axis) => attachDescription((value, { options }) => {
|
|
if (!ContinuousScale.is(axis?.scale) && !DiscreteTimeScale.is(axis?.scale))
|
|
return true;
|
|
if (value == null || options.end == null)
|
|
return true;
|
|
return value < options.end;
|
|
}, `to be less than end`);
|
|
function validateChanges(changes) {
|
|
for (const axisId of strictObjectKeys(changes)) {
|
|
const zoom = changes[axisId];
|
|
if (!zoom)
|
|
continue;
|
|
const { min, max } = zoom;
|
|
if (min < 0 || max > 1) {
|
|
logger_exports.warnOnce(
|
|
`Attempted to update axis (${axisId}) zoom to an invalid ratio of [{ min: ${min}, max: ${max} }], expecting a ratio of 0 to 1. Ignoring.`
|
|
);
|
|
delete changes[axisId];
|
|
}
|
|
}
|
|
}
|
|
function refreshCoreState(nextAxes, state) {
|
|
const result = {};
|
|
for (const { id, direction } of nextAxes) {
|
|
const { min, max } = state[id] ?? { min: 0, max: 1 };
|
|
result[id] = { min, max, direction };
|
|
}
|
|
return result;
|
|
}
|
|
function areEqualCoreZooms(p, q) {
|
|
const pKeys = strictObjectKeys(p);
|
|
const qKeys = strictObjectKeys(q);
|
|
if (pKeys.length !== qKeys.length)
|
|
return false;
|
|
for (const k of pKeys)
|
|
if (!qKeys.includes(k))
|
|
return false;
|
|
for (const k of pKeys) {
|
|
const pVal = p[k];
|
|
const qVal = q[k];
|
|
if (pVal === qVal) {
|
|
continue;
|
|
} else if (pVal == void 0 || qVal == void 0 || pVal.direction !== qVal.direction || pVal.min !== qVal.min || pVal.max !== qVal.max) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function userInteraction(sourceDetail) {
|
|
return { source: "user-interaction", sourceDetail };
|
|
}
|
|
var ZoomManager = class extends BaseManager {
|
|
constructor(eventsHub, updateService, fireChartEvent) {
|
|
super();
|
|
this.eventsHub = eventsHub;
|
|
this.fireChartEvent = fireChartEvent;
|
|
this.mementoOriginatorKey = "zoom";
|
|
this.state = {};
|
|
this.axes = [];
|
|
this.didLayoutAxes = false;
|
|
this.lastRestoredState = {};
|
|
this.independentAxes = false;
|
|
this.navigatorModule = false;
|
|
this.zoomModule = false;
|
|
// The initial state memento can not be restored until the chart has performed its first layout. Instead save it as
|
|
// pending and restore then delete it on the first layout.
|
|
this.pendingMemento = void 0;
|
|
this.cleanup.register(
|
|
eventsHub.on("zoom:change-request", (event) => {
|
|
this.constrainZoomToRequiredWidth(event);
|
|
}),
|
|
updateService.addListener("pre-series-update", ({ requiredRangeRatio, requiredRangeDirection }) => {
|
|
this.didLayoutAxes = true;
|
|
const { pendingMemento } = this;
|
|
if (pendingMemento) {
|
|
this.restoreMemento(pendingMemento.version, pendingMemento.mementoVersion, pendingMemento.memento);
|
|
} else {
|
|
this.restoreRequiredRange(requiredRangeRatio, requiredRangeDirection);
|
|
}
|
|
this.updateZoom({
|
|
source: "chart-update",
|
|
// FIXME(AG-16412): this is "probably" what caused, but we don't really know
|
|
sourceDetail: "unspecified"
|
|
});
|
|
}),
|
|
updateService.addListener("update-complete", ({ wasShortcut }) => {
|
|
if (wasShortcut)
|
|
return;
|
|
if (this.pendingZoomEventSource) {
|
|
const source = this.pendingZoomEventSource;
|
|
this.fireChartEvent({ type: "zoom", source, ...this.getMementoRanges() });
|
|
this.pendingZoomEventSource = void 0;
|
|
}
|
|
})
|
|
);
|
|
}
|
|
// FIXME: should be private
|
|
toCoreZoomState(axisZoom) {
|
|
const result = {};
|
|
let ids;
|
|
const { state } = this;
|
|
if (this.independentAxes) {
|
|
const xId = this.getPrimaryAxisId("x" /* X */);
|
|
const yId = this.getPrimaryAxisId("y" /* Y */);
|
|
ids = [];
|
|
if (xId)
|
|
ids.push(xId);
|
|
if (yId)
|
|
ids.push(yId);
|
|
} else {
|
|
ids = strictObjectKeys(state);
|
|
}
|
|
for (const id of ids) {
|
|
const { direction } = state[id] ?? {};
|
|
if (direction != void 0) {
|
|
const zoom = axisZoom[direction];
|
|
if (zoom) {
|
|
const { min, max } = zoom;
|
|
result[id] = { min, max, direction };
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
// FIXME: should be private
|
|
toZoomState(coreZoom) {
|
|
let x;
|
|
let y;
|
|
for (const id of strictObjectKeys(coreZoom)) {
|
|
const { min, max, direction } = coreZoom[id];
|
|
if (direction === "x" /* X */) {
|
|
x ?? (x = { min, max });
|
|
} else if (direction === "y" /* Y */) {
|
|
y ?? (y = { min, max });
|
|
}
|
|
}
|
|
if (x || y) {
|
|
return { x, y };
|
|
}
|
|
}
|
|
createMemento() {
|
|
return this.getMementoRanges();
|
|
}
|
|
guardMemento(blob, messages) {
|
|
if (blob == null)
|
|
return true;
|
|
if (!isObject(blob))
|
|
return false;
|
|
const primaryX = this.getPrimaryAxis("x" /* X */);
|
|
const primaryY = this.getPrimaryAxis("y" /* Y */);
|
|
const zoomMementoDefs = {
|
|
rangeX: { start: rangeValidator2(primaryX), end: defined },
|
|
rangeY: { start: rangeValidator2(primaryY), end: defined },
|
|
ratioX: { start: defined, end: defined },
|
|
ratioY: { start: defined, end: defined },
|
|
autoScaledAxes: defined
|
|
};
|
|
const { invalid } = validate(blob, zoomMementoDefs);
|
|
if (invalid.length > 0) {
|
|
messages.push(...invalid.map(String));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
restoreMemento(version, mementoVersion, memento) {
|
|
if (!this.axes || !this.didLayoutAxes) {
|
|
this.pendingMemento = { version, mementoVersion, memento };
|
|
return;
|
|
}
|
|
this.pendingMemento = void 0;
|
|
const zoom = definedZoomState(this.getZoom());
|
|
if (memento?.rangeX) {
|
|
zoom.x = this.rangeToRatioDirection("x" /* X */, memento.rangeX) ?? { min: 0, max: 1 };
|
|
} else if (memento?.ratioX) {
|
|
zoom.x = {
|
|
min: memento.ratioX.start ?? 0,
|
|
max: memento.ratioX.end ?? 1
|
|
};
|
|
} else {
|
|
zoom.x = { min: 0, max: 1 };
|
|
}
|
|
const { navigatorModule, zoomModule } = this;
|
|
this.eventsHub.emit("zoom:load-memento", { zoom, memento, navigatorModule, zoomModule });
|
|
const changes = this.toCoreZoomState(zoom);
|
|
this.lastRestoredState = deepFreeze(deepClone(changes));
|
|
this.updateChanges({
|
|
source: "state-change",
|
|
sourceDetail: "internal-restoreMemento",
|
|
changes,
|
|
isReset: false
|
|
});
|
|
}
|
|
findAxis(axisId) {
|
|
for (const a of this.axes) {
|
|
if (a.id === axisId)
|
|
return a;
|
|
}
|
|
}
|
|
getAxes() {
|
|
return this.axes;
|
|
}
|
|
setAxes(nextAxes) {
|
|
const { axes } = this;
|
|
axes.length = 0;
|
|
for (const axis of nextAxes) {
|
|
if ("range" in axis) {
|
|
axes.push(axis);
|
|
}
|
|
}
|
|
const oldState = this.state;
|
|
const changes = refreshCoreState(nextAxes, oldState);
|
|
this.state = changes;
|
|
this.lastRestoredState = refreshCoreState(nextAxes, this.lastRestoredState);
|
|
this.updateChanges({ source: "chart-update", sourceDetail: "internal-setAxes", changes, isReset: false });
|
|
}
|
|
setIndependentAxes(independent = true) {
|
|
this.independentAxes = independent;
|
|
}
|
|
setNavigatorEnabled(enabled = true) {
|
|
this.navigatorModule = enabled;
|
|
}
|
|
setZoomModuleEnabled(enabled = true) {
|
|
this.zoomModule = enabled;
|
|
}
|
|
isNavigatorEnabled() {
|
|
return this.navigatorModule;
|
|
}
|
|
isZoomEnabled() {
|
|
return this.zoomModule;
|
|
}
|
|
updateZoom({ source, sourceDetail }, newZoom) {
|
|
const changes = this.toCoreZoomState(newZoom ?? {});
|
|
return this.updateChanges({ source, sourceDetail, changes, isReset: false });
|
|
}
|
|
computeChangedAxesIds(newState) {
|
|
const result = [];
|
|
const oldState = this.state;
|
|
for (const id of strictObjectKeys(newState)) {
|
|
const newAxisState = newState[id] ?? { min: 0, max: 1 };
|
|
const oldAxisState = oldState[id];
|
|
if (oldAxisState == void 0 || oldAxisState.min !== newAxisState.min || oldAxisState.max !== newAxisState.max) {
|
|
result.push(id);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
updateChanges(params) {
|
|
const { source, sourceDetail, isReset, changes } = params;
|
|
validateChanges(changes);
|
|
const changedAxes = this.computeChangedAxesIds(changes);
|
|
const oldState = deepClone(this.state);
|
|
const newState = deepClone(this.state);
|
|
for (const id of changedAxes) {
|
|
const axis = newState[id];
|
|
if (axis != void 0) {
|
|
axis.min = changes[id]?.min ?? 0;
|
|
axis.max = changes[id]?.max ?? 1;
|
|
}
|
|
}
|
|
this.state = newState;
|
|
return this.dispatch(source, sourceDetail, changedAxes, isReset, oldState);
|
|
}
|
|
resetZoom({ source, sourceDetail }) {
|
|
this.updateChanges({ source, sourceDetail, changes: this.getRestoredZoom(), isReset: true });
|
|
}
|
|
resetAxisZoom({ source, sourceDetail }, axisId) {
|
|
this.updateChanges({
|
|
source,
|
|
sourceDetail,
|
|
changes: { [axisId]: this.getRestoredZoom()[axisId] },
|
|
isReset: true
|
|
});
|
|
}
|
|
panToBBox(seriesRect, target) {
|
|
if (!this.isZoomEnabled() && !this.isNavigatorEnabled())
|
|
return false;
|
|
const zoom = this.getZoom();
|
|
if (zoom === void 0 || !zoom.x && !zoom.y)
|
|
return false;
|
|
const panIsPossible = seriesRect.width > 0 && seriesRect.height > 0 && Math.abs(target.width) <= Math.abs(seriesRect.width) && Math.abs(target.height) <= Math.abs(seriesRect.height);
|
|
if (!panIsPossible) {
|
|
logger_exports.warnOnce(`cannot pan to target BBox - chart too small?`);
|
|
return false;
|
|
}
|
|
const newZoom = calcPanToBBoxRatios(seriesRect, zoom, target);
|
|
const changes = this.toCoreZoomState(newZoom);
|
|
return this.updateChanges({
|
|
source: "user-interaction",
|
|
sourceDetail: "internal-panToBBox",
|
|
changes,
|
|
isReset: false
|
|
});
|
|
}
|
|
// Fire this event to signal to listeners that the view is changing through a zoom and/or pan change.
|
|
fireZoomPanStartEvent(callerId) {
|
|
this.eventsHub.emit("zoom:pan-start", { callerId });
|
|
}
|
|
extendToEnd(sourcing, direction, extent2) {
|
|
return this.extendWith(sourcing, direction, (end3) => Number(end3) - extent2);
|
|
}
|
|
extendWith({ source, sourceDetail }, direction, fn) {
|
|
const axis = this.getPrimaryAxis(direction);
|
|
if (!axis)
|
|
return;
|
|
const extents = this.getDomainExtents(axis);
|
|
if (!extents)
|
|
return;
|
|
const [, end3] = extents;
|
|
const start2 = fn(end3);
|
|
const ratio2 = this.rangeToRatioAxis(axis, { start: start2 });
|
|
if (!ratio2)
|
|
return;
|
|
this.updateChanges({ source, sourceDetail, changes: { [direction]: ratio2 }, isReset: false });
|
|
}
|
|
updateWith({ source, sourceDetail }, direction, fn) {
|
|
const axis = this.getPrimaryAxis(direction);
|
|
if (!axis)
|
|
return;
|
|
const extents = this.getDomainExtents(axis);
|
|
if (!extents)
|
|
return;
|
|
let [start2, end3] = extents;
|
|
[start2, end3] = fn(start2, end3);
|
|
const ratio2 = this.rangeToRatioAxis(axis, { start: start2, end: end3 });
|
|
if (!ratio2)
|
|
return;
|
|
this.updateChanges({ source, sourceDetail, changes: { [direction]: ratio2 }, isReset: false });
|
|
}
|
|
getZoom() {
|
|
return this.toZoomState(this.state);
|
|
}
|
|
getAxisZoom(axisId) {
|
|
return this.state[axisId] ?? { min: 0, max: 1 };
|
|
}
|
|
getAxisZooms() {
|
|
return this.state;
|
|
}
|
|
getCoreZoom() {
|
|
return this.state;
|
|
}
|
|
getRestoredZoom() {
|
|
return this.lastRestoredState;
|
|
}
|
|
getPrimaryAxisId(direction) {
|
|
return this.getPrimaryAxis(direction)?.id;
|
|
}
|
|
getBoundSeries() {
|
|
const xAxis = this.getPrimaryAxis("x" /* X */);
|
|
const yAxis = this.getPrimaryAxis("y" /* Y */);
|
|
let boundSeries;
|
|
if (this.independentAxes) {
|
|
const xBoundSeries = new Set(xAxis?.boundSeries ?? []);
|
|
const yBoundSeries = new Set(yAxis?.boundSeries ?? []);
|
|
boundSeries = /* @__PURE__ */ new Set();
|
|
for (const series of xBoundSeries) {
|
|
if (yBoundSeries.has(series)) {
|
|
boundSeries.add(series);
|
|
}
|
|
}
|
|
} else {
|
|
boundSeries = /* @__PURE__ */ new Set([...xAxis?.boundSeries ?? [], ...yAxis?.boundSeries ?? []]);
|
|
}
|
|
return boundSeries;
|
|
}
|
|
constrainZoomToItemCount(zoom, minVisibleItems, shouldAutoscale) {
|
|
let xVisibleRange = [zoom.x.min, zoom.x.max];
|
|
let yVisibleRange = shouldAutoscale ? void 0 : [zoom.y.min, zoom.y.max];
|
|
for (const series of this.getBoundSeries()) {
|
|
const nextZoom = series.getZoomRangeFittingItems(xVisibleRange, yVisibleRange, minVisibleItems);
|
|
if (nextZoom == null)
|
|
continue;
|
|
xVisibleRange = nextZoom.x;
|
|
yVisibleRange = nextZoom.y;
|
|
}
|
|
const x = { min: xVisibleRange[0], max: xVisibleRange[1] };
|
|
const y = yVisibleRange ? { min: yVisibleRange[0], max: yVisibleRange[1] } : void 0;
|
|
return definedZoomState({ x, y });
|
|
}
|
|
isVisibleItemsCountAtLeast(zoom, minVisibleItems, opts) {
|
|
const boundSeries = this.getBoundSeries();
|
|
const xVisibleRange = [zoom.x.min, zoom.x.max];
|
|
const yVisibleRange = !opts.includeYVisibleRange && opts.autoScaleYAxis ? void 0 : [zoom.y.min, zoom.y.max];
|
|
let visibleItemsCount = 0;
|
|
for (const series of boundSeries) {
|
|
const remainingItems = minVisibleItems - (visibleItemsCount ?? 0);
|
|
const seriesVisibleItems = series.getVisibleItems(xVisibleRange, yVisibleRange, remainingItems);
|
|
visibleItemsCount += seriesVisibleItems;
|
|
if (visibleItemsCount >= minVisibleItems)
|
|
return true;
|
|
}
|
|
return boundSeries.size === 0;
|
|
}
|
|
getMementoRanges() {
|
|
const zoom = definedZoomState(this.getZoom());
|
|
const memento = {
|
|
rangeX: this.getRangeDirection("x" /* X */, zoom.x),
|
|
rangeY: this.getRangeDirection("y" /* Y */, zoom.y),
|
|
ratioX: { start: zoom.x.min, end: zoom.x.max },
|
|
ratioY: { start: zoom.y.min, end: zoom.y.max },
|
|
autoScaledAxes: void 0
|
|
};
|
|
this.eventsHub.emit("zoom:save-memento", { memento });
|
|
return memento;
|
|
}
|
|
restoreRequiredRange(requiredRangeRatio, requiredRangeDirection) {
|
|
const { lastRestoredRequiredRange, lastRestoredRequiredRangeDirection } = this;
|
|
const directionInvalid = requiredRangeDirection !== "x" /* X */ && requiredRangeDirection !== "y" /* Y */;
|
|
const requiredRangeUnchanged = lastRestoredRequiredRangeDirection === requiredRangeDirection && lastRestoredRequiredRange === requiredRangeRatio;
|
|
const requiredRangeUnset = requiredRangeRatio === 0 && (lastRestoredRequiredRange == null || lastRestoredRequiredRange === 0);
|
|
if (directionInvalid || requiredRangeUnchanged || requiredRangeUnset)
|
|
return;
|
|
const crossAxisId = this.getPrimaryAxisId(requiredRangeDirection);
|
|
if (!crossAxisId)
|
|
return;
|
|
const crossAxisZoom = this.getAxisZoom(crossAxisId);
|
|
const requiredZoom = Math.min(1, 1 / requiredRangeRatio);
|
|
let min = 0;
|
|
let max = 1;
|
|
if (requiredRangeDirection === "x" /* X */) {
|
|
min = clamp(0, 1 - requiredZoom, crossAxisZoom.min);
|
|
max = clamp(0, min + requiredZoom, 1);
|
|
} else {
|
|
max = Math.min(1, crossAxisZoom.max);
|
|
min = max - requiredZoom;
|
|
if (min < 0) {
|
|
max -= min;
|
|
min = 0;
|
|
}
|
|
min = clamp(0, min, 1);
|
|
max = clamp(0, max, 1);
|
|
}
|
|
this.lastRestoredRequiredRange = requiredRangeRatio;
|
|
this.lastRestoredRequiredRangeDirection = requiredRangeDirection;
|
|
const zoom = { [requiredRangeDirection]: { min, max } };
|
|
const changes = this.toCoreZoomState(zoom);
|
|
this.lastRestoredState = deepFreeze(deepClone(changes));
|
|
this.updateChanges({
|
|
source: "state-change",
|
|
sourceDetail: "internal-requiredWidth",
|
|
changes,
|
|
isReset: false
|
|
});
|
|
}
|
|
constrainZoomToRequiredWidth(event) {
|
|
if (this.lastRestoredRequiredRange == null || this.lastRestoredRequiredRangeDirection == null)
|
|
return;
|
|
const axis = this.lastRestoredRequiredRangeDirection;
|
|
const crossAxisId = this.getPrimaryAxisId(this.lastRestoredRequiredRangeDirection);
|
|
if (!crossAxisId)
|
|
return;
|
|
const zoom = event.stateAsDefinedZoom();
|
|
const oldState = event.oldState[crossAxisId];
|
|
const delta5 = zoom[axis].max - zoom[axis].min;
|
|
const minDelta = 1 / this.lastRestoredRequiredRange;
|
|
if (delta5 <= minDelta)
|
|
return;
|
|
event.constrainZoom({
|
|
...zoom,
|
|
[axis]: { min: oldState.min, max: oldState.min + minDelta }
|
|
});
|
|
}
|
|
dispatch(source, sourceDetail, changedAxes, isReset, oldState) {
|
|
const { x, y } = this.getZoom() ?? {};
|
|
const state = this.state;
|
|
let constrainedState;
|
|
const zoomManager = this;
|
|
const event = {
|
|
source,
|
|
sourceDetail,
|
|
isReset,
|
|
changedAxes,
|
|
state,
|
|
oldState,
|
|
x,
|
|
y,
|
|
stateAsDefinedZoom() {
|
|
return definedZoomState(zoomManager.toZoomState(event.state));
|
|
},
|
|
constrainZoom(restrictions) {
|
|
this.constrainChanges(zoomManager.toCoreZoomState(restrictions));
|
|
},
|
|
constrainChanges(restrictions) {
|
|
constrainedState ?? (constrainedState = deepClone(state));
|
|
for (const id of strictObjectKeys(restrictions)) {
|
|
const src = restrictions[id];
|
|
const dst = constrainedState[id];
|
|
if (src && dst) {
|
|
dst.min = src.min;
|
|
dst.max = src.max;
|
|
}
|
|
}
|
|
event.state = constrainedState;
|
|
}
|
|
};
|
|
this.eventsHub.emit("zoom:change-request", event);
|
|
let wasChangeConstrained = false;
|
|
if (constrainedState && !areEqualCoreZooms(state, constrainedState)) {
|
|
wasChangeConstrained = true;
|
|
this.state = constrainedState;
|
|
}
|
|
const changeAccepted = changedAxes.length > 0 || wasChangeConstrained;
|
|
if (changeAccepted) {
|
|
const acceptedZoom = this.getZoom() ?? {};
|
|
this.eventsHub.emit("zoom:change-complete", { source, sourceDetail, x: acceptedZoom.x });
|
|
this.pendingZoomEventSource = source;
|
|
}
|
|
return changeAccepted;
|
|
}
|
|
getRange(axisId, ratio2) {
|
|
return this.getRangeAxis(this.findAxis(axisId), ratio2);
|
|
}
|
|
getRangeDirection(direction, ratio2) {
|
|
return this.getRangeAxis(this.getPrimaryAxis(direction), ratio2);
|
|
}
|
|
getRangeAxis(axis, ratio2) {
|
|
if (!axis || !ratio2 || !ContinuousScale.is(axis.scale) && !DiscreteTimeScale.is(axis.scale))
|
|
return;
|
|
const extents = this.getDomainPixelExtents(axis);
|
|
if (!extents)
|
|
return;
|
|
const [d0, d1] = extents;
|
|
let start2;
|
|
let end3;
|
|
if (d0 <= d1) {
|
|
start2 = axis.scale.invert(0);
|
|
end3 = axis.scale.invert(d0 + (d1 - d0) * ratio2.max);
|
|
} else {
|
|
start2 = axis.scale.invert(d0 - (d0 - d1) * ratio2.min);
|
|
end3 = axis.scale.invert(0);
|
|
}
|
|
return { start: start2, end: end3 };
|
|
}
|
|
rangeToRatio(axisId, range3) {
|
|
return this.rangeToRatioAxis(this.findAxis(axisId), range3);
|
|
}
|
|
rangeToRatioDirection(direction, range3) {
|
|
return this.rangeToRatioAxis(this.getPrimaryAxis(direction), range3);
|
|
}
|
|
rangeToRatioAxis(axis, range3) {
|
|
if (!axis)
|
|
return;
|
|
const extents = this.getDomainPixelExtents(axis);
|
|
if (!extents)
|
|
return;
|
|
const [d0, d1] = extents;
|
|
const { scale: scale2 } = axis;
|
|
const { start: start2, end: end3 } = range3;
|
|
const [startAlignment = 0 /* Leading */, endAlignment = 1 /* Trailing */] = rangeAlignment(
|
|
start2,
|
|
end3
|
|
);
|
|
const r0 = start2 == null ? d0 : scale2.convert(start2, { alignment: startAlignment });
|
|
const r1 = end3 == null ? d1 : scale2.convert(end3, { alignment: endAlignment }) + (scale2.bandwidth ?? 0);
|
|
if (!isFiniteNumber(r0) || !isFiniteNumber(r1))
|
|
return;
|
|
const [dMin, dMax] = [Math.min(d0, d1), Math.max(d0, d1)];
|
|
if (r0 < dMin || r0 > dMax) {
|
|
logger_exports.warnOnce(
|
|
`Invalid range start [${start2}], expecting a value between [${scale2.invert(d0)}] and [${scale2.invert(d1)}], ignoring.`
|
|
);
|
|
return;
|
|
}
|
|
if (r1 < dMin || r1 > dMax) {
|
|
logger_exports.warnOnce(
|
|
`Invalid range end [${end3}], expecting a value between [${scale2.invert(d0)}] and [${scale2.invert(d1)}], ignoring.`
|
|
);
|
|
return;
|
|
}
|
|
const diff9 = d1 - d0;
|
|
if (diff9 === 0)
|
|
return;
|
|
const min = Math.abs((r0 - d0) / diff9);
|
|
const max = Math.abs((r1 - d0) / diff9);
|
|
if (min >= max)
|
|
return;
|
|
return { min, max };
|
|
}
|
|
getPrimaryAxis(direction) {
|
|
return this.axes?.find((a) => a.direction === direction);
|
|
}
|
|
getDomainExtents(axis) {
|
|
const { domain } = axis.scale;
|
|
const d0 = domain.at(0);
|
|
const d1 = domain.at(-1);
|
|
if (d0 == null || d1 == null)
|
|
return;
|
|
return [d0, d1];
|
|
}
|
|
getDomainPixelExtents(axis) {
|
|
const [d0, d1] = axis.scale.range;
|
|
if (!isFiniteNumber(d0) || !isFiniteNumber(d1))
|
|
return;
|
|
return [d0, d1];
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/layout/layoutManager.ts
|
|
var LayoutElement = /* @__PURE__ */ ((LayoutElement8) => {
|
|
LayoutElement8[LayoutElement8["Caption"] = 0] = "Caption";
|
|
LayoutElement8[LayoutElement8["Legend"] = 1] = "Legend";
|
|
LayoutElement8[LayoutElement8["ToolbarLeft"] = 2] = "ToolbarLeft";
|
|
LayoutElement8[LayoutElement8["ToolbarBottom"] = 3] = "ToolbarBottom";
|
|
LayoutElement8[LayoutElement8["Scrollbar"] = 4] = "Scrollbar";
|
|
LayoutElement8[LayoutElement8["Navigator"] = 5] = "Navigator";
|
|
LayoutElement8[LayoutElement8["Overlay"] = 6] = "Overlay";
|
|
return LayoutElement8;
|
|
})(LayoutElement || {});
|
|
var LayoutManager = class {
|
|
constructor(eventsHub) {
|
|
this.eventsHub = eventsHub;
|
|
this.elements = /* @__PURE__ */ new Map();
|
|
}
|
|
registerElement(element2, listener) {
|
|
if (this.elements.has(element2)) {
|
|
this.elements.get(element2).add(listener);
|
|
} else {
|
|
this.elements.set(element2, /* @__PURE__ */ new Set([listener]));
|
|
}
|
|
return () => this.elements.get(element2)?.delete(listener);
|
|
}
|
|
createContext(width2, height2) {
|
|
const context = { width: width2, height: height2, layoutBox: new BBox(0, 0, width2, height2), scrollbars: {} };
|
|
for (const element2 of Object.values(LayoutElement)) {
|
|
if (typeof element2 !== "number")
|
|
continue;
|
|
const listeners = this.elements.get(element2);
|
|
if (listeners) {
|
|
for (const listener of listeners) {
|
|
listener(context);
|
|
}
|
|
}
|
|
}
|
|
return context;
|
|
}
|
|
emitLayoutComplete({ width: width2, height: height2 }, options) {
|
|
this.eventsHub.emit("layout:complete", {
|
|
axes: options.axes ?? {},
|
|
chart: { width: width2, height: height2 },
|
|
clipSeries: options.clipSeries ?? false,
|
|
series: options.series
|
|
});
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/layout/seriesLabelLayoutManager.ts
|
|
var SeriesLabelLayoutManager = class {
|
|
constructor() {
|
|
this.labelData = /* @__PURE__ */ new Map();
|
|
}
|
|
updateLabels(placedLabelSeries, padding2, seriesRect = BBox.zero) {
|
|
const bounds = {
|
|
x: -padding2.left,
|
|
y: -padding2.top,
|
|
width: seriesRect.width + padding2.left + padding2.right,
|
|
height: seriesRect.height + padding2.top + padding2.bottom
|
|
};
|
|
const expectedSeriesId = new Set(placedLabelSeries.map((s) => s.id));
|
|
for (const seriesId of this.labelData.keys()) {
|
|
if (!expectedSeriesId.has(seriesId)) {
|
|
this.labelData.delete(seriesId);
|
|
}
|
|
}
|
|
for (const series of placedLabelSeries) {
|
|
const labelData = series.getLabelData();
|
|
if (labelData.every(isPointLabelDatum)) {
|
|
this.labelData.set(series.id, labelData);
|
|
}
|
|
}
|
|
const placedLabels = placeLabels(this.labelData, bounds, 5);
|
|
for (const series of placedLabelSeries) {
|
|
series.updatePlacedLabelData?.(placedLabels.get(series.id) ?? []);
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/legend/legendManager.ts
|
|
var LegendManager = class {
|
|
constructor(eventsHub) {
|
|
this.eventsHub = eventsHub;
|
|
this.mementoOriginatorKey = "legend";
|
|
this.legendDataMap = /* @__PURE__ */ new Map();
|
|
}
|
|
createMemento() {
|
|
return this.getData().filter(({ hideInLegend, isFixed }) => !hideInLegend && !isFixed).map(({ enabled, seriesId, itemId, legendItemName }) => ({
|
|
visible: enabled,
|
|
seriesId,
|
|
itemId,
|
|
legendItemName
|
|
}));
|
|
}
|
|
guardMemento(blob) {
|
|
return blob == null || isArray(blob);
|
|
}
|
|
restoreMemento(_version, _mementoVersion, memento) {
|
|
if (memento) {
|
|
for (const datum of memento) {
|
|
const { seriesId, data } = this.getRestoredData(datum) ?? {};
|
|
if (!seriesId || !data) {
|
|
continue;
|
|
}
|
|
this.updateData(seriesId, data);
|
|
}
|
|
}
|
|
this.update();
|
|
}
|
|
getRestoredData(datum) {
|
|
const { seriesId, itemId, legendItemName, visible } = datum;
|
|
if (seriesId) {
|
|
const legendData = this.legendDataMap.get(seriesId) ?? [];
|
|
const data = legendData.map((d) => {
|
|
const match = d.seriesId === seriesId && (!itemId || d.itemId === itemId);
|
|
if (match && d.isFixed) {
|
|
this.warnFixed(d.seriesId, d.itemId);
|
|
}
|
|
return !d.isFixed && match ? { ...d, enabled: visible } : d;
|
|
});
|
|
return { seriesId, data };
|
|
}
|
|
if (itemId == null && legendItemName == null) {
|
|
return;
|
|
}
|
|
for (const legendDatum of this.getData()) {
|
|
if (itemId != null && legendDatum.itemId !== itemId || legendItemName != null && legendDatum.legendItemName !== legendItemName) {
|
|
continue;
|
|
}
|
|
if (legendDatum.isFixed) {
|
|
this.warnFixed(legendDatum.seriesId, itemId);
|
|
return;
|
|
}
|
|
const seriesLegendData = (this.legendDataMap.get(legendDatum.seriesId) ?? []).map(
|
|
(d) => d.itemId === itemId || d.legendItemName === legendItemName ? { ...d, enabled: visible } : d
|
|
);
|
|
return {
|
|
seriesId: legendDatum.seriesId,
|
|
data: seriesLegendData
|
|
};
|
|
}
|
|
}
|
|
warnFixed(seriesId, itemId) {
|
|
logger_exports.warnOnce(
|
|
`The legend item with seriesId [${seriesId}] and itemId [${itemId}] is not configurable, this series item cannot be toggled through the legend.`
|
|
);
|
|
}
|
|
update(data) {
|
|
this.eventsHub.emit("legend:change", {
|
|
legendData: data ?? this.getData()
|
|
});
|
|
}
|
|
updateData(seriesId, data = []) {
|
|
this.eventsHub.emit("legend:change-partial", { seriesId, legendData: data });
|
|
this.legendDataMap.set(seriesId, data);
|
|
}
|
|
clearData() {
|
|
this.legendDataMap.clear();
|
|
}
|
|
toggleItem(enabled, seriesId, itemId, legendItemName) {
|
|
if (legendItemName) {
|
|
for (const datum of this.getData()) {
|
|
const newData = (this.legendDataMap.get(datum.seriesId) ?? []).map(
|
|
(d) => d.legendItemName === legendItemName ? { ...d, enabled } : d
|
|
);
|
|
this.updateData(datum.seriesId, newData);
|
|
}
|
|
return;
|
|
}
|
|
const seriesLegendData = this.getData(seriesId);
|
|
const singleLegendItem = seriesLegendData.length === 1;
|
|
const data = seriesLegendData.map(
|
|
(datum) => itemId == null && singleLegendItem || datum.itemId === itemId ? { ...datum, enabled } : datum
|
|
);
|
|
this.updateData(seriesId, data);
|
|
}
|
|
getData(seriesId) {
|
|
if (seriesId) {
|
|
return this.legendDataMap.get(seriesId) ?? [];
|
|
}
|
|
return [...this.legendDataMap].reduce(
|
|
(data, [_, legendData]) => data.concat(legendData),
|
|
[]
|
|
);
|
|
}
|
|
getDatum({ seriesId, itemId } = {}) {
|
|
return this.getData(seriesId).find((datum) => datum.itemId === itemId);
|
|
}
|
|
getSeriesEnabled(seriesId) {
|
|
const data = this.getData(seriesId);
|
|
if (data.length > 0) {
|
|
return data.some((d) => d.enabled);
|
|
}
|
|
}
|
|
getItemEnabled({ seriesId, itemId } = {}) {
|
|
return this.getDatum({ seriesId, itemId })?.enabled ?? true;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/optionsGraphService.ts
|
|
var OptionsGraphService = class {
|
|
updateCallback(resolvePartialCallback) {
|
|
this.resolvePartialCallback = resolvePartialCallback;
|
|
}
|
|
resolvePartial(path, partialOptions, resolveOptions) {
|
|
return this.resolvePartialCallback?.(path, partialOptions, resolveOptions);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scale/irregularBandScale.ts
|
|
var IrregularBandScale = class extends BandScale {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "category";
|
|
// TODO: 'irregular-band'?
|
|
this.defaultTickCount = 0;
|
|
this._hasFixedWidth = false;
|
|
this._paddingInnerWidth = 0;
|
|
this._domain = [];
|
|
this._bandRanges = /* @__PURE__ */ new Map();
|
|
}
|
|
set domain(values) {
|
|
if (this._domain === values)
|
|
return;
|
|
if (values.length === 0) {
|
|
this._bandRanges.clear();
|
|
this._hasFixedWidth = false;
|
|
}
|
|
this.invalid = true;
|
|
this._domain = values;
|
|
}
|
|
get domain() {
|
|
return this._domain;
|
|
}
|
|
get bands() {
|
|
return this.domain;
|
|
}
|
|
addBand(groupIndex, stackIndex, value) {
|
|
this._domain.push(this.getDomainValue(groupIndex, stackIndex));
|
|
if (!this._bandRanges.has(groupIndex)) {
|
|
this._bandRanges.set(groupIndex, /* @__PURE__ */ new Map());
|
|
}
|
|
this._bandRanges.get(groupIndex).set(stackIndex, value);
|
|
this._hasFixedWidth || (this._hasFixedWidth = value != null);
|
|
this.invalid = true;
|
|
}
|
|
getDomainValue(groupIndex, stackIndex) {
|
|
return `${groupIndex}-${stackIndex}`;
|
|
}
|
|
findIndex(value) {
|
|
let index = 0;
|
|
for (const key of this._bandRanges.keys()) {
|
|
if (key === value)
|
|
return index;
|
|
index++;
|
|
}
|
|
}
|
|
convert(domainValue) {
|
|
const { _bandwidth, _bandRanges, _inset, _paddingInnerWidth } = this;
|
|
let value = _inset;
|
|
const valueDs = domainValue.split("-");
|
|
const valueGroupIndex = Number(valueDs[0]);
|
|
if (!this._hasFixedWidth) {
|
|
return super.convert(valueGroupIndex);
|
|
}
|
|
for (let i = 0; i < valueGroupIndex; i++) {
|
|
const stacks = _bandRanges.get(i);
|
|
if (!stacks) {
|
|
value += _paddingInnerWidth;
|
|
continue;
|
|
}
|
|
let maxStackWidth = 0;
|
|
for (const width2 of stacks.values()) {
|
|
maxStackWidth = Math.max(maxStackWidth, width2 == null ? _bandwidth : width2);
|
|
}
|
|
value += maxStackWidth + _paddingInnerWidth;
|
|
}
|
|
return value;
|
|
}
|
|
invert(_value, _nearest) {
|
|
return;
|
|
}
|
|
getBandCountForUpdate() {
|
|
return this._bandRanges.size;
|
|
}
|
|
update() {
|
|
if (!this._hasFixedWidth) {
|
|
return super.update();
|
|
}
|
|
const [r0, r1] = this.range;
|
|
let { paddingInner } = this;
|
|
const bandCount = this.getBandCountForUpdate();
|
|
if (bandCount === 0)
|
|
return;
|
|
let totalBandRange = 0;
|
|
let bandCountWithUnfixedWidths = bandCount;
|
|
let bandCountWithOnlyFixedWidths = bandCount;
|
|
for (const stacks of this._bandRanges.values()) {
|
|
let maxStackWidth = 0;
|
|
let hasUnfixed = false;
|
|
for (const width2 of stacks.values()) {
|
|
if (width2 == null) {
|
|
hasUnfixed = true;
|
|
continue;
|
|
}
|
|
maxStackWidth = Math.max(maxStackWidth, width2);
|
|
}
|
|
if (hasUnfixed) {
|
|
bandCountWithOnlyFixedWidths -= 1;
|
|
} else {
|
|
bandCountWithUnfixedWidths -= 1;
|
|
totalBandRange += maxStackWidth;
|
|
}
|
|
}
|
|
if (bandCount === 1) {
|
|
paddingInner = 0;
|
|
}
|
|
const targetRangeDistance = r1 - r0;
|
|
const paddingInnerWidth = targetRangeDistance / bandCount * paddingInner;
|
|
const actualRangeDistance = totalBandRange + paddingInnerWidth * (bandCount - 1);
|
|
const rangeDiff = targetRangeDistance - actualRangeDistance;
|
|
let inset = r0;
|
|
let rawBandwidth = bandCountWithUnfixedWidths > 0 && rangeDiff >= 0 ? rangeDiff / bandCountWithUnfixedWidths : targetRangeDistance / bandCount;
|
|
let bandwidth = rawBandwidth;
|
|
if (bandCountWithOnlyFixedWidths === bandCount && rangeDiff > 0) {
|
|
inset += rangeDiff / 2;
|
|
}
|
|
const round4 = this.round && Math.floor(bandwidth) > 0;
|
|
if (round4) {
|
|
inset = Math.round(inset);
|
|
bandwidth = Math.round(bandwidth);
|
|
}
|
|
if (rangeDiff < 0) {
|
|
rawBandwidth = 0;
|
|
bandwidth = 0;
|
|
}
|
|
this._inset = inset;
|
|
this._bandwidth = bandwidth;
|
|
this._rawBandwidth = rawBandwidth;
|
|
this._paddingInnerWidth = paddingInnerWidth;
|
|
}
|
|
normalizeDomains(..._domains) {
|
|
return { domain: [], animatable: false };
|
|
}
|
|
toDomain(_value) {
|
|
return void 0;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/seriesStateManager.ts
|
|
var SeriesStateManager = class {
|
|
constructor() {
|
|
this.groups = /* @__PURE__ */ new Map();
|
|
this.groupScales = /* @__PURE__ */ new Map();
|
|
}
|
|
registerSeries({ internalId, seriesGrouping, visible, width: width2, type }) {
|
|
if (!seriesGrouping)
|
|
return;
|
|
let group = this.groups.get(type);
|
|
if (group == null) {
|
|
group = /* @__PURE__ */ new Map();
|
|
this.groups.set(type, group);
|
|
}
|
|
group.set(internalId, { grouping: seriesGrouping, visible, width: width2 });
|
|
}
|
|
updateSeries({ internalId, seriesGrouping, visible, width: width2, type }) {
|
|
if (!seriesGrouping)
|
|
return;
|
|
const entry = this.groups.get(type)?.get(internalId);
|
|
if (entry) {
|
|
entry.grouping = seriesGrouping;
|
|
entry.width = width2;
|
|
entry.visible = visible;
|
|
}
|
|
}
|
|
deregisterSeries({ internalId, type }) {
|
|
const group = this.groups.get(type);
|
|
if (group == null)
|
|
return;
|
|
group.delete(internalId);
|
|
if (group.size === 0) {
|
|
this.groups.delete(type);
|
|
}
|
|
}
|
|
getVisiblePeerGroupIndex({ type, seriesGrouping, visible }) {
|
|
if (!seriesGrouping) {
|
|
return { visibleGroupCount: visible ? 1 : 0, visibleSameStackCount: visible ? 1 : 0, index: 0 };
|
|
}
|
|
const visibleGroupsSet = /* @__PURE__ */ new Set();
|
|
const visibleSameStackSet = /* @__PURE__ */ new Set();
|
|
const group = this.groups.get(type);
|
|
for (const entry of group?.values() ?? []) {
|
|
if (!entry.visible)
|
|
continue;
|
|
visibleGroupsSet.add(entry.grouping.groupIndex);
|
|
if (entry.grouping.groupIndex === seriesGrouping.groupIndex) {
|
|
visibleSameStackSet.add(entry.grouping.stackIndex);
|
|
}
|
|
}
|
|
const visibleGroups = Array.from(visibleGroupsSet);
|
|
visibleGroups.sort((a, b) => a - b);
|
|
return {
|
|
visibleGroupCount: visibleGroups.length,
|
|
visibleSameStackCount: visibleSameStackSet.size,
|
|
index: visibleGroups.indexOf(seriesGrouping.groupIndex)
|
|
};
|
|
}
|
|
updateGroupScale({ type }, bandwidth, axis) {
|
|
const groupScale = this.groupScales.get(type) ?? new IrregularBandScale();
|
|
this.groupScales.set(type, groupScale);
|
|
groupScale.domain = [];
|
|
const group = this.groups.get(type);
|
|
for (const entry of group?.values() ?? []) {
|
|
if (!entry.visible)
|
|
continue;
|
|
groupScale.addBand(entry.grouping.groupIndex, entry.grouping.stackIndex, entry.width);
|
|
}
|
|
if (groupScale.domain.length === 0) {
|
|
groupScale.addBand(0, 0, void 0);
|
|
}
|
|
groupScale.range = [0, bandwidth];
|
|
if (axis.type === "grouped-category") {
|
|
groupScale.paddingInner = axis.groupPaddingInner;
|
|
} else if (axis.type === "category" || axis.type === "unit-time") {
|
|
groupScale.paddingInner = axis.groupPaddingInner;
|
|
groupScale.round = groupScale.padding !== 0;
|
|
} else {
|
|
groupScale.padding = 0;
|
|
}
|
|
groupScale.update();
|
|
}
|
|
getGroupScale({ type }) {
|
|
return this.groupScales.get(type);
|
|
}
|
|
getGroupOffset(series) {
|
|
const { seriesGrouping } = series;
|
|
if (!seriesGrouping)
|
|
return 0;
|
|
const groupScale = this.getGroupScale(series);
|
|
if (!groupScale)
|
|
return 0;
|
|
const domainValue = groupScale.getDomainValue(seriesGrouping.groupIndex, seriesGrouping.stackIndex);
|
|
return groupScale.convert(domainValue);
|
|
}
|
|
getStackOffset(series, barWidth) {
|
|
const { seriesGrouping } = series;
|
|
if (!seriesGrouping)
|
|
return 0;
|
|
const group = this.groups.get(series.type);
|
|
if (!group)
|
|
return 0;
|
|
const scale2 = this.getGroupScale(series);
|
|
if (!scale2)
|
|
return 0;
|
|
const stackCount = seriesGrouping.stackCount ?? 0;
|
|
if (stackCount < 1)
|
|
return 0;
|
|
let maxStackWidth = 0;
|
|
for (const entry of group.values()) {
|
|
if (!entry.visible)
|
|
continue;
|
|
if (entry.grouping.groupIndex !== seriesGrouping.groupIndex)
|
|
continue;
|
|
maxStackWidth = Math.max(maxStackWidth, entry.width ?? scale2.bandwidth);
|
|
}
|
|
if (maxStackWidth === 0)
|
|
return 0;
|
|
return maxStackWidth / 2 - barWidth / 2;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/updateService.ts
|
|
var UpdateService = class {
|
|
constructor(updateCallback) {
|
|
this.updateCallback = updateCallback;
|
|
this.events = new EventEmitter();
|
|
}
|
|
addListener(eventName, listener) {
|
|
return this.events.on(eventName, listener);
|
|
}
|
|
destroy() {
|
|
this.events.clear();
|
|
}
|
|
update(type = 0 /* FULL */, options) {
|
|
this.updateCallback(type, options);
|
|
}
|
|
dispatchUpdateComplete(apiUpdate, wasShortcut) {
|
|
this.events.emit("update-complete", { type: "update-complete", apiUpdate, wasShortcut });
|
|
}
|
|
dispatchPreDomUpdate() {
|
|
this.events.emit("pre-dom-update", { type: "pre-dom-update" });
|
|
}
|
|
dispatchPreSeriesUpdate(requiredRangeRatio, requiredRangeDirection) {
|
|
this.events.emit("pre-series-update", {
|
|
type: "pre-series-update",
|
|
requiredRangeRatio,
|
|
requiredRangeDirection
|
|
});
|
|
}
|
|
dispatchPreSceneRender() {
|
|
this.events.emit("pre-scene-render", { type: "pre-scene-render" });
|
|
}
|
|
dispatchProcessData({ series }) {
|
|
this.events.emit("process-data", { type: "process-data", series });
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/chartContext.ts
|
|
var ChartContext = class {
|
|
constructor(chart, vars) {
|
|
this.eventsHub = new EventEmitter();
|
|
this.callbackCache = new CallbackCache();
|
|
this.highlightManager = new HighlightManager(this.eventsHub);
|
|
this.formatManager = new FormatManager();
|
|
this.layoutManager = new LayoutManager(this.eventsHub);
|
|
this.localeManager = new LocaleManager(this.eventsHub);
|
|
this.seriesStateManager = new SeriesStateManager();
|
|
this.stateManager = new StateManager();
|
|
this.seriesLabelLayoutManager = new SeriesLabelLayoutManager();
|
|
this.cleanup = new CleanupRegistry();
|
|
const {
|
|
scene,
|
|
root,
|
|
syncManager,
|
|
container,
|
|
fireEvent,
|
|
updateCallback,
|
|
updateMutex,
|
|
styleContainer,
|
|
skipCss,
|
|
chartType,
|
|
domMode,
|
|
withDragInterpretation
|
|
} = vars;
|
|
this.chartService = chart;
|
|
this.syncManager = syncManager;
|
|
this.domManager = new DOMManager(
|
|
this.eventsHub,
|
|
this.chartService,
|
|
container,
|
|
styleContainer,
|
|
skipCss,
|
|
domMode
|
|
);
|
|
this.widgets = new WidgetSet(this.domManager, { withDragInterpretation });
|
|
const canvasElement = this.domManager.addChild(
|
|
"canvas",
|
|
"scene-canvas",
|
|
scene?.canvas.element
|
|
);
|
|
this.scene = scene ?? new Scene({ canvasElement });
|
|
this.scene.setRoot(root);
|
|
this.cleanup.register(
|
|
this.scene.on("scene-changed", () => {
|
|
this.updateService.update(9 /* SCENE_RENDER */);
|
|
})
|
|
);
|
|
this.axisManager = new AxisManager(this.eventsHub, root);
|
|
this.legendManager = new LegendManager(this.eventsHub);
|
|
this.annotationManager = new AnnotationManager(this.eventsHub, chart.annotationRoot, fireEvent);
|
|
this.chartTypeOriginator = new ChartTypeOriginator(chart);
|
|
this.interactionManager = new InteractionManager();
|
|
this.contextMenuRegistry = new ContextMenuRegistry(this.eventsHub);
|
|
this.optionsGraphService = new OptionsGraphService();
|
|
this.updateService = new UpdateService(updateCallback);
|
|
this.activeManager = new ActiveManager(
|
|
this.chartService,
|
|
this.eventsHub,
|
|
this.updateService,
|
|
this.interactionManager,
|
|
fireEvent
|
|
);
|
|
this.proxyInteractionService = new ProxyInteractionService(this.eventsHub, this.localeManager, this.domManager);
|
|
this.fontManager = new FontManager(this.domManager, this.updateService);
|
|
this.historyManager = new HistoryManager(this.eventsHub);
|
|
this.animationManager = new AnimationManager(this.interactionManager, updateMutex);
|
|
this.dataService = new DataService(this.eventsHub, chart, this.animationManager);
|
|
this.tooltipManager = new TooltipManager(this.eventsHub, this.localeManager, this.domManager, chart.tooltip);
|
|
this.zoomManager = new ZoomManager(this.eventsHub, this.updateService, fireEvent);
|
|
for (const module2 of moduleRegistry_exports.listModulesByType("plugin" /* Plugin */)) {
|
|
if (!module2.chartType || module2.chartType === chartType) {
|
|
module2.patchContext?.(this);
|
|
}
|
|
}
|
|
}
|
|
destroy() {
|
|
this.animationManager.destroy();
|
|
this.axisManager.destroy();
|
|
this.callbackCache.invalidateCache();
|
|
this.domManager.destroy();
|
|
this.fontManager.destroy();
|
|
this.proxyInteractionService.destroy();
|
|
this.tooltipManager.destroy();
|
|
this.zoomManager.destroy();
|
|
this.widgets.destroy();
|
|
this.cleanup.flush();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/chartHighlight.ts
|
|
var ChartHighlight = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.range = "tooltip";
|
|
this.drawingMode = "cutout";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChartHighlight.prototype, "range", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChartHighlight.prototype, "drawingMode", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/data/caching.ts
|
|
function setsEqual(a, b) {
|
|
if (a.size !== b.size)
|
|
return false;
|
|
for (const value of a) {
|
|
if (!b.has(value))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function idsMapEqual(a, b) {
|
|
if (a == null || b == null)
|
|
return a === b;
|
|
if (a.size !== b.size)
|
|
return false;
|
|
for (const [key, aValue] of a) {
|
|
const bValue = b.get(key);
|
|
if (bValue == null)
|
|
return false;
|
|
if (!setsEqual(aValue, bValue))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function propsEqual(a, b) {
|
|
if (a.length !== b.length)
|
|
return false;
|
|
for (let i = 0; i < a.length; i += 1) {
|
|
const { type: typeA, idsMap: idsMapA, scopes: scopesA, data: dataA, ...propA } = a[i];
|
|
const { type: typeB, idsMap: idsMapB, scopes: scopesB, data: dataB, ...propB } = b[i];
|
|
if (typeA !== typeB)
|
|
return false;
|
|
if (scopesA && scopesB && !arraysEqual(scopesA, scopesB))
|
|
return false;
|
|
if (dataA && dataB && dataA !== dataB)
|
|
return false;
|
|
if (!objectsEqual(propA, propB) || !idsMapEqual(idsMapA, idsMapB))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function optsEqual(a, b) {
|
|
const { props: propsA, ...restA } = a;
|
|
const { props: propsB, ...restB } = b;
|
|
return objectsEqual(restA, restB) && propsEqual(propsA, propsB);
|
|
}
|
|
function canReuseCachedData(cachedDataItem, dataSet, ids, opts) {
|
|
if (dataSet !== cachedDataItem.dataSet) {
|
|
return false;
|
|
}
|
|
return arraysEqual(ids, cachedDataItem.ids) && optsEqual(opts, cachedDataItem.opts);
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/data/dataController.ts
|
|
function getPropertyKeys(props) {
|
|
return props.filter((p) => p.type === "key").map((p) => p.property).join(";");
|
|
}
|
|
var _DataController = class _DataController {
|
|
constructor(mode, suppressFieldDotNotation, eventsHub) {
|
|
this.mode = mode;
|
|
this.suppressFieldDotNotation = suppressFieldDotNotation;
|
|
this.eventsHub = eventsHub;
|
|
this.debug = debugLogger_exports.create(true, "data-model");
|
|
this.requested = [];
|
|
this.status = "setup";
|
|
}
|
|
async request(id, dataSet, opts) {
|
|
if (this.status !== "setup") {
|
|
throw new Error(`AG Charts - data request after data setup phase.`);
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
this.requested.push({ id, opts, dataSet, resolve, reject });
|
|
});
|
|
}
|
|
execute(cachedData) {
|
|
if (this.status !== "setup") {
|
|
throw new Error(`AG Charts - data request after data setup phase.`);
|
|
}
|
|
this.status = "executed";
|
|
const dataSets = /* @__PURE__ */ new Map();
|
|
for (const request of this.requested) {
|
|
if (request.dataSet.hasPendingTransactions()) {
|
|
dataSets.set(request.dataSet, request.dataSet.getChangeDescription());
|
|
}
|
|
request.dataSet.commitPendingTransactions();
|
|
}
|
|
this.debug("DataController.execute() - requested", this.requested);
|
|
const valid = this.validateRequests(this.requested);
|
|
this.debug("DataController.execute() - validated", valid);
|
|
const merged = this.mergeRequested(valid);
|
|
this.debug("DataController.execute() - merged", merged);
|
|
if (this.debug.check()) {
|
|
getWindow().processedData = [];
|
|
}
|
|
const nextCachedData = [];
|
|
for (const { dataSet, ids, opts, resolves, rejects } of merged) {
|
|
let cachePredicateFn2 = function(cacheItem) {
|
|
return canReuseCachedData(cacheItem, dataSet, ids, opts);
|
|
};
|
|
var cachePredicateFn = cachePredicateFn2;
|
|
const reusableCache = cachedData?.find(cachePredicateFn2);
|
|
const resolveResult = (dataModel2, processedData2) => {
|
|
if (this.debug.check()) {
|
|
getWindow("processedData").push(processedData2);
|
|
}
|
|
if (processedData2 == null) {
|
|
for (const cb of rejects) {
|
|
cb(new Error(`AG Charts - no processed data generated`));
|
|
}
|
|
return;
|
|
}
|
|
nextCachedData.push({ opts, dataSet, dataLength: dataSet.data.length, ids, dataModel: dataModel2, processedData: processedData2 });
|
|
for (const resolve of resolves) {
|
|
resolve({ dataModel: dataModel2, processedData: processedData2 });
|
|
}
|
|
};
|
|
const fullReprocess = () => {
|
|
try {
|
|
const dataModel2 = new DataModel(
|
|
opts,
|
|
this.mode,
|
|
this.suppressFieldDotNotation,
|
|
this.eventsHub
|
|
);
|
|
const sources = new Map(valid.map((v) => [v.id, v.dataSet]));
|
|
const processedData2 = dataModel2.processData(sources);
|
|
resolveResult(dataModel2, processedData2);
|
|
return dataModel2;
|
|
} catch (error2) {
|
|
for (const cb of rejects) {
|
|
cb(error2);
|
|
}
|
|
}
|
|
};
|
|
if (reusableCache == null) {
|
|
fullReprocess();
|
|
continue;
|
|
}
|
|
const { dataModel, processedData } = reusableCache;
|
|
const changeDescription = dataSets.get(dataSet);
|
|
if (processedData && changeDescription && dataModel.isReprocessingSupported(processedData)) {
|
|
this.debug("DataController.execute() - reprocessing data", processedData, dataSet);
|
|
dataModel.reprocessData(processedData, dataSets);
|
|
if (debugLogger_exports.check("data-model:reprocess-diff")) {
|
|
const baselineModel = new DataModel(
|
|
opts,
|
|
this.mode,
|
|
this.suppressFieldDotNotation,
|
|
this.eventsHub
|
|
);
|
|
const sources = new Map(valid.map((v) => [v.id, v.dataSet]));
|
|
const baselineData = baselineModel.processData(sources);
|
|
const reprocessedJson = JSON.parse(JSON.stringify(processedData, _DataController.jsonReplacer));
|
|
const baselineJson = JSON.parse(JSON.stringify(baselineData, _DataController.jsonReplacer));
|
|
delete reprocessedJson.time;
|
|
delete reprocessedJson.optimizations;
|
|
delete baselineJson.time;
|
|
delete baselineJson.optimizations;
|
|
const diff9 = jsonDiff(baselineJson, reprocessedJson);
|
|
if (diff9) {
|
|
logger_exports.log("\u26A0\uFE0F DATA-MODEL REPROCESS DIFF DETECTED \u26A0\uFE0F");
|
|
logger_exports.log("Difference between incremental update and full reprocess:");
|
|
logger_exports.log("");
|
|
logger_exports.log("BASELINE (full reprocess):");
|
|
logger_exports.log(JSON.stringify(baselineJson, null, 2));
|
|
logger_exports.log("");
|
|
logger_exports.log("REPROCESSED (incremental update):");
|
|
logger_exports.log(JSON.stringify(reprocessedJson, null, 2));
|
|
logger_exports.log("");
|
|
logger_exports.log("DIFF (what changed):");
|
|
logger_exports.log(JSON.stringify(diff9, null, 2));
|
|
} else {
|
|
logger_exports.log("\u2705 Data-model reprocess matches baseline (no diff)");
|
|
}
|
|
}
|
|
resolveResult(dataModel, processedData);
|
|
continue;
|
|
}
|
|
fullReprocess();
|
|
}
|
|
return nextCachedData;
|
|
}
|
|
validateRequests(requested) {
|
|
const valid = [];
|
|
for (const [index, request] of requested.entries()) {
|
|
if (index > 0 && request.dataSet.data.length !== requested[0].dataSet.data.length && request.opts.groupByData === false && request.opts.groupByKeys === false) {
|
|
request.reject(
|
|
new Error("all series[].data arrays must be of the same length and have matching keys.")
|
|
);
|
|
} else {
|
|
valid.push(request);
|
|
}
|
|
}
|
|
return valid;
|
|
}
|
|
mergeRequested(requested) {
|
|
const grouped = [];
|
|
for (const request of requested) {
|
|
const match = grouped.find(_DataController.groupMatch(request));
|
|
if (match) {
|
|
match.push(request);
|
|
} else {
|
|
grouped.push([request]);
|
|
}
|
|
}
|
|
return grouped.map(_DataController.mergeRequests);
|
|
}
|
|
static groupMatch({ dataSet, opts }) {
|
|
const { groupByData, groupByKeys = false, groupByFn, props } = opts;
|
|
const propsKeys = getPropertyKeys(props);
|
|
return ([group]) => (groupByData === false || group.dataSet === dataSet) && (group.opts.groupByKeys ?? false) === groupByKeys && group.opts.groupByFn === groupByFn && getPropertyKeys(group.opts.props) === propsKeys;
|
|
}
|
|
static mergeRequests(requests) {
|
|
const result = {
|
|
ids: [],
|
|
rejects: [],
|
|
resolves: [],
|
|
dataSet: requests[0].dataSet,
|
|
opts: { ...requests[0].opts, props: [] }
|
|
};
|
|
const optsByTypeAndDataId = /* @__PURE__ */ new Map();
|
|
const dataIds = /* @__PURE__ */ new Map();
|
|
let nextDataId = 0;
|
|
for (const request of requests) {
|
|
const {
|
|
id,
|
|
dataSet,
|
|
resolve,
|
|
reject,
|
|
opts: { props, ...opts }
|
|
} = request;
|
|
result.ids.push(id);
|
|
result.rejects.push(reject);
|
|
result.resolves.push(resolve);
|
|
result.dataSet ?? (result.dataSet = dataSet);
|
|
result.opts ?? (result.opts = { ...opts, props: [] });
|
|
for (const prop of props) {
|
|
const clone2 = { ...prop, scopes: [id], data: dataSet.data };
|
|
_DataController.createIdsMap(id, clone2);
|
|
let dataId;
|
|
if (_DataController.crossScopeMergableTypes.has(clone2.type)) {
|
|
dataId = -1;
|
|
} else if (dataIds.has(dataSet.data)) {
|
|
dataId = dataIds.get(dataSet.data);
|
|
} else {
|
|
dataId = nextDataId++;
|
|
dataIds.set(dataSet.data, dataId);
|
|
}
|
|
const matchKey = `${clone2.type}-${dataId}-${clone2.groupId}`;
|
|
const matches = optsByTypeAndDataId.get(matchKey);
|
|
const match = matches?.find((existing) => _DataController.deepEqual(existing, clone2));
|
|
if (matches == null) {
|
|
result.opts.props.push(clone2);
|
|
optsByTypeAndDataId.set(matchKey, [clone2]);
|
|
continue;
|
|
} else if (match == null) {
|
|
result.opts.props.push(clone2);
|
|
matches.push(clone2);
|
|
continue;
|
|
}
|
|
if (clone2.scopes != null) {
|
|
match.scopes ?? (match.scopes = []);
|
|
match.scopes.push(...clone2.scopes);
|
|
}
|
|
if ((match.type === "key" || match.type === "value") && clone2.idsMap?.size) {
|
|
match.idsMap ?? (match.idsMap = /* @__PURE__ */ new Map());
|
|
_DataController.mergeIdsMap(clone2.idsMap, match.idsMap);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
static mergeIdsMap(fromMap, toMap) {
|
|
for (const [scope, ids] of fromMap) {
|
|
const toMapValue = toMap.get(scope);
|
|
if (toMapValue == null) {
|
|
toMap.set(scope, new Set(ids));
|
|
} else {
|
|
for (const id of ids) {
|
|
toMapValue.add(id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
static createIdsMap(scope, prop) {
|
|
if (prop.id == null)
|
|
return;
|
|
prop.idsMap ?? (prop.idsMap = /* @__PURE__ */ new Map());
|
|
if (prop.idsMap.has(scope)) {
|
|
prop.idsMap.get(scope).add(prop.id);
|
|
} else {
|
|
prop.idsMap.set(scope, /* @__PURE__ */ new Set([prop.id]));
|
|
}
|
|
}
|
|
static deepEqual(a, b) {
|
|
if (a === b) {
|
|
return true;
|
|
}
|
|
if (a && b && typeof a == "object" && typeof b == "object") {
|
|
if (a.constructor !== b.constructor) {
|
|
return false;
|
|
}
|
|
let i, length2;
|
|
if (Array.isArray(a)) {
|
|
length2 = a.length;
|
|
if (length2 !== b.length) {
|
|
return false;
|
|
}
|
|
for (i = length2 - 1; i >= 0; i--) {
|
|
if (!_DataController.deepEqual(a[i], b[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
const keys = Object.keys(a);
|
|
length2 = keys.length;
|
|
if (length2 !== Object.keys(b).length) {
|
|
return false;
|
|
}
|
|
for (i = length2 - 1; i >= 0; i--) {
|
|
const key = keys[i];
|
|
if (!_DataController.skipKeys.has(key) && (!Object.hasOwn(b, key) || !_DataController.deepEqual(a[key], b[key]))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/** JSON replacer for serializing non-JSON-serializable objects like Map and Set */
|
|
static jsonReplacer(_key, value) {
|
|
if (value instanceof Map) {
|
|
return { __type: "Map", value: Array.from(value.entries()) };
|
|
}
|
|
if (value instanceof Set) {
|
|
return { __type: "Set", value: Array.from(value) };
|
|
}
|
|
return value;
|
|
}
|
|
};
|
|
_DataController.crossScopeMergableTypes = /* @__PURE__ */ new Set(["key", "group-value-processor"]);
|
|
// optimized version of deep equality for `mergeRequests` which can potentially loop over 1M times
|
|
_DataController.skipKeys = /* @__PURE__ */ new Set(["id", "idsMap", "type", "scopes", "data"]);
|
|
var DataController = _DataController;
|
|
|
|
// packages/ag-charts-community/src/chart/data/dataSet.ts
|
|
var DataSet = class _DataSet {
|
|
constructor(data) {
|
|
this.data = data;
|
|
this.pendingTransactions = [];
|
|
}
|
|
/**
|
|
* Creates an empty DataSet.
|
|
*/
|
|
static empty() {
|
|
return new _DataSet([]);
|
|
}
|
|
/**
|
|
* Wraps existing data in a DataSet.
|
|
*/
|
|
static wrap(data) {
|
|
return new _DataSet(data);
|
|
}
|
|
netSize() {
|
|
if (!this.hasPendingTransactions()) {
|
|
return this.data.length;
|
|
}
|
|
const changeDesc = this.getChangeDescription();
|
|
return changeDesc ? changeDesc.indexMap.finalLength : this.data.length;
|
|
}
|
|
/**
|
|
* Queues a transaction (applied on commit).
|
|
* Normalizes AG Grid-compatible format (add/addIndex) to internal format (prepend/append).
|
|
*/
|
|
addTransaction(transaction) {
|
|
const normalized2 = this.normalizeTransaction(transaction);
|
|
this.pendingTransactions.push(normalized2);
|
|
this.cachedChangeDescription = void 0;
|
|
}
|
|
/**
|
|
* @returns A deep clone of the DataSet.
|
|
*/
|
|
deepClone() {
|
|
return new _DataSet([...this.data]);
|
|
}
|
|
/**
|
|
* Converts AG Grid-compatible transaction format to internal format.
|
|
* Maps `add` + `addIndex` to prepend, append, or arbitrary insertion based on the index.
|
|
*/
|
|
normalizeTransaction(transaction) {
|
|
const { add: add2, addIndex, prepend, append, remove, update } = transaction;
|
|
if (add2 === void 0) {
|
|
return transaction;
|
|
}
|
|
const result = { remove, update };
|
|
if (prepend)
|
|
result.prepend = prepend;
|
|
if (append)
|
|
result.append = append;
|
|
if (add2 && add2.length > 0) {
|
|
const currentSize = this.netSize();
|
|
if (addIndex === void 0 || addIndex >= currentSize) {
|
|
result.append = append ? [...append, ...add2] : add2;
|
|
} else if (addIndex === 0) {
|
|
result.prepend = prepend ? [...add2, ...prepend] : add2;
|
|
} else {
|
|
result.insertions = [{ index: addIndex, items: add2 }];
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
hasPendingTransactions() {
|
|
return this.pendingTransactions.length > 0;
|
|
}
|
|
getPendingTransactionCount() {
|
|
return this.pendingTransactions.length;
|
|
}
|
|
/** Applies all pending transactions to the data array. */
|
|
commitPendingTransactions() {
|
|
if (!this.hasPendingTransactions()) {
|
|
return false;
|
|
}
|
|
const changeDescription = this.getChangeDescription();
|
|
if (!changeDescription) {
|
|
return false;
|
|
}
|
|
const prependedValues = changeDescription.getPrependedValues();
|
|
const insertionValues = changeDescription.getInsertionValues();
|
|
const appendedValues = changeDescription.getAppendedValues();
|
|
const allInsertionValues = [...prependedValues, ...insertionValues, ...appendedValues];
|
|
let insertionValueIndex = 0;
|
|
changeDescription.applyToArray(this.data, function applyToArrayResultFn(destIndex) {
|
|
if (insertionValueIndex >= allInsertionValues.length) {
|
|
throw new Error(`AG Charts - Internal error: No insertion value found for index ${destIndex}`);
|
|
}
|
|
return allInsertionValues[insertionValueIndex++];
|
|
});
|
|
this.pendingTransactions = [];
|
|
this.cachedChangeDescription = void 0;
|
|
this.updateItemToIndexCache(changeDescription, appendedValues, prependedValues, insertionValues);
|
|
return true;
|
|
}
|
|
/** Updates item→index cache incrementally, or invalidates for complex changes. */
|
|
updateItemToIndexCache(changeDescription, appendedValues, prependedValues, insertionValues) {
|
|
if (!this.itemToIndexCache)
|
|
return;
|
|
const { indexMap } = changeDescription;
|
|
const { totalPrependCount, totalAppendCount, removedIndices } = indexMap;
|
|
const hasRemovals = removedIndices.size > 0;
|
|
const hasArbitraryInsertions = insertionValues.length > 0;
|
|
if (!hasRemovals && totalPrependCount === 0 && totalAppendCount === 0 && !hasArbitraryInsertions) {
|
|
return;
|
|
}
|
|
if (hasArbitraryInsertions) {
|
|
this.itemToIndexCache = void 0;
|
|
return;
|
|
}
|
|
let removalsAreContiguousAtStart = false;
|
|
let contiguousRemovalCount = 0;
|
|
if (hasRemovals) {
|
|
const sortedRemovals = Array.from(removedIndices).sort((a, b) => a - b);
|
|
removalsAreContiguousAtStart = sortedRemovals[0] === 0;
|
|
if (removalsAreContiguousAtStart) {
|
|
for (let i = 0; i < sortedRemovals.length; i++) {
|
|
if (sortedRemovals[i] !== i) {
|
|
removalsAreContiguousAtStart = false;
|
|
break;
|
|
}
|
|
}
|
|
if (removalsAreContiguousAtStart) {
|
|
contiguousRemovalCount = sortedRemovals.length;
|
|
}
|
|
}
|
|
}
|
|
if (hasRemovals && !removalsAreContiguousAtStart) {
|
|
this.itemToIndexCache = void 0;
|
|
return;
|
|
}
|
|
const cache = this.itemToIndexCache;
|
|
const indexShift = totalPrependCount - contiguousRemovalCount;
|
|
if (indexShift !== 0) {
|
|
for (const [item, oldIndex] of cache) {
|
|
if (removedIndices.has(oldIndex)) {
|
|
cache.delete(item);
|
|
} else {
|
|
cache.set(item, oldIndex + indexShift);
|
|
}
|
|
}
|
|
} else if (hasRemovals) {
|
|
for (const [item, oldIndex] of cache) {
|
|
if (removedIndices.has(oldIndex)) {
|
|
cache.delete(item);
|
|
}
|
|
}
|
|
}
|
|
for (let i = 0; i < prependedValues.length; i++) {
|
|
const item = prependedValues[i];
|
|
if (!cache.has(item)) {
|
|
cache.set(item, i);
|
|
}
|
|
}
|
|
const appendStartIndex = indexMap.finalLength - totalAppendCount;
|
|
for (let i = 0; i < appendedValues.length; i++) {
|
|
const item = appendedValues[i];
|
|
if (!cache.has(item)) {
|
|
cache.set(item, appendStartIndex + i);
|
|
}
|
|
}
|
|
}
|
|
clearPendingTransactions() {
|
|
const count = this.pendingTransactions.length;
|
|
this.pendingTransactions = [];
|
|
this.cachedChangeDescription = void 0;
|
|
return count;
|
|
}
|
|
getPendingTransactions() {
|
|
return [...this.pendingTransactions];
|
|
}
|
|
/** Custom JSON serialization (avoids snapshot bloat). */
|
|
toJSON() {
|
|
return this.data;
|
|
}
|
|
/** Builds a DataChangeDescription from pending transactions (does not modify data). */
|
|
getChangeDescription() {
|
|
if (!this.hasPendingTransactions()) {
|
|
return void 0;
|
|
}
|
|
if (this.cachedChangeDescription) {
|
|
return this.cachedChangeDescription;
|
|
}
|
|
const { indexMap, prependValues, appendValues, insertionValues } = this.buildIndexMap();
|
|
const changeDescription = new DataChangeDescription(indexMap, {
|
|
prependValues,
|
|
appendValues,
|
|
insertionValues
|
|
});
|
|
this.cachedChangeDescription = changeDescription;
|
|
return changeDescription;
|
|
}
|
|
/**
|
|
* Helper method to remove items from a list of groups.
|
|
* Mutates the groups in-place and removes found items from toRemove set.
|
|
* @param groups List of groups to search and remove from
|
|
* @param toRemove Set of items to remove (modified in-place)
|
|
*/
|
|
removeFromGroups(groups, toRemove) {
|
|
for (const group of groups) {
|
|
let i = 0;
|
|
while (i < group.length && toRemove.size > 0) {
|
|
if (toRemove.has(group[i])) {
|
|
toRemove.delete(group[i]);
|
|
group.splice(i, 1);
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
if (toRemove.size === 0)
|
|
break;
|
|
}
|
|
}
|
|
/**
|
|
* Builds the index transformation map by sequentially applying all pending transactions.
|
|
* Optimized to:
|
|
* - Track operation boundaries instead of individual items
|
|
* - Only scan for values that are actually being removed
|
|
* - Stop scanning early when all removed values are found
|
|
* - Support arbitrary insertions at any index
|
|
* - Track updated items by referential equality
|
|
*/
|
|
buildIndexMap() {
|
|
const originalLength = this.data.length;
|
|
const effects = this.collectTransactionEffects();
|
|
const survivingPrepends = effects.prependsList.flat();
|
|
const survivingAppends = effects.appendsList.flat();
|
|
const survivingInsertions = effects.insertionsList.flat();
|
|
const totalPrependCount = survivingPrepends.length;
|
|
const totalAppendCount = survivingAppends.length;
|
|
const totalInsertionCount = survivingInsertions.length;
|
|
const survivingOriginalCount = originalLength - effects.removedOriginalIndices.size;
|
|
const finalLength = totalPrependCount + survivingOriginalCount + totalInsertionCount + totalAppendCount;
|
|
const sortedRemoved = effects.removedOriginalIndices.size > 0 ? this.getSortedRemovedIndices(effects.removedOriginalIndices) : void 0;
|
|
const spliceOps = this.buildSpliceOperations(
|
|
totalPrependCount,
|
|
totalInsertionCount,
|
|
totalAppendCount,
|
|
survivingOriginalCount,
|
|
effects.trackedInsertions,
|
|
sortedRemoved?.desc,
|
|
sortedRemoved?.asc
|
|
);
|
|
const updatedFinalIndices = this.resolveUpdatedIndices(
|
|
totalPrependCount,
|
|
totalInsertionCount,
|
|
survivingOriginalCount,
|
|
effects.updateTracking,
|
|
sortedRemoved?.asc,
|
|
effects.updatedOriginalIndices,
|
|
effects.trackedInsertions
|
|
);
|
|
const indexMap = {
|
|
originalLength,
|
|
finalLength,
|
|
spliceOps,
|
|
removedIndices: effects.removedOriginalIndices,
|
|
updatedIndices: updatedFinalIndices,
|
|
totalPrependCount,
|
|
totalAppendCount
|
|
};
|
|
return {
|
|
indexMap,
|
|
prependValues: survivingPrepends,
|
|
appendValues: survivingAppends,
|
|
insertionValues: survivingInsertions
|
|
};
|
|
}
|
|
getSortedRemovedIndices(removedOriginalIndices) {
|
|
const asc = Array.from(removedOriginalIndices).sort((a, b) => a - b);
|
|
return { asc, desc: [...asc].reverse() };
|
|
}
|
|
collectTransactionEffects() {
|
|
const state = {
|
|
prependsList: [],
|
|
appendsList: [],
|
|
insertionsList: [],
|
|
trackedInsertions: [],
|
|
removedOriginalIndices: /* @__PURE__ */ new Set(),
|
|
updatedOriginalIndices: /* @__PURE__ */ new Set(),
|
|
virtualLength: this.data.length
|
|
};
|
|
for (const transaction of this.pendingTransactions) {
|
|
const { prepend, append, insertions, remove, update } = transaction;
|
|
this.applyPrepends(prepend, state);
|
|
this.applyInsertions(insertions, state);
|
|
this.applyAppends(append, state);
|
|
this.applyRemovals(remove, state);
|
|
this.applyUpdates(update, state);
|
|
}
|
|
return {
|
|
prependsList: state.prependsList,
|
|
appendsList: state.appendsList,
|
|
insertionsList: state.insertionsList,
|
|
trackedInsertions: state.trackedInsertions,
|
|
removedOriginalIndices: state.removedOriginalIndices,
|
|
updatedOriginalIndices: state.updatedOriginalIndices,
|
|
updateTracking: state.updateTracking
|
|
};
|
|
}
|
|
applyPrepends(prepend, state) {
|
|
if (!Array.isArray(prepend) || prepend.length === 0) {
|
|
return;
|
|
}
|
|
state.prependsList.unshift([...prepend]);
|
|
state.virtualLength += prepend.length;
|
|
}
|
|
applyInsertions(insertions, state) {
|
|
if (!Array.isArray(insertions)) {
|
|
return;
|
|
}
|
|
for (const { index, items } of insertions) {
|
|
if (index >= 0 && index <= state.virtualLength && items.length > 0) {
|
|
state.trackedInsertions.push({
|
|
virtualIndex: index,
|
|
items: [...items]
|
|
});
|
|
state.insertionsList.push([...items]);
|
|
state.virtualLength += items.length;
|
|
}
|
|
}
|
|
}
|
|
applyAppends(append, state) {
|
|
if (!Array.isArray(append) || append.length === 0) {
|
|
return;
|
|
}
|
|
state.appendsList.push([...append]);
|
|
state.virtualLength += append.length;
|
|
}
|
|
applyRemovals(remove, state) {
|
|
if (!Array.isArray(remove) || remove.length === 0) {
|
|
return;
|
|
}
|
|
const toRemove = new Set(remove);
|
|
this.removeFromGroups(state.prependsList, toRemove);
|
|
if (toRemove.size > 0) {
|
|
this.removeFromGroups(state.insertionsList, toRemove);
|
|
}
|
|
if (state.trackedInsertions.length > 0) {
|
|
this.removeFromTrackedInsertions(remove, state);
|
|
}
|
|
if (toRemove.size > 0) {
|
|
this.removeFromGroups(state.appendsList, toRemove);
|
|
}
|
|
if (toRemove.size > 0) {
|
|
for (let i = 0; i < this.data.length && toRemove.size > 0; i++) {
|
|
const value = this.data[i];
|
|
if (toRemove.has(value)) {
|
|
state.removedOriginalIndices.add(i);
|
|
toRemove.delete(value);
|
|
state.virtualLength--;
|
|
}
|
|
}
|
|
}
|
|
if (toRemove.size > 0) {
|
|
logger_exports.warnOnce(
|
|
"applyTransaction() remove includes items not present in current data; ignoring missing items."
|
|
);
|
|
}
|
|
}
|
|
applyUpdates(update, state) {
|
|
if (!Array.isArray(update) || update.length === 0) {
|
|
return;
|
|
}
|
|
const toUpdate = new Set(update);
|
|
const updatedPrependsIndices = this.collectUpdatedIndicesFromGroups(state.prependsList, toUpdate);
|
|
const updatedInsertionsIndices = toUpdate.size > 0 ? this.collectUpdatedIndicesFromGroups(state.insertionsList, toUpdate) : [];
|
|
const updatedAppendsIndices = toUpdate.size > 0 ? this.collectUpdatedIndicesFromGroups(state.appendsList, toUpdate) : [];
|
|
if (toUpdate.size > 0) {
|
|
this.collectUpdatedOriginalIndices(toUpdate, state);
|
|
}
|
|
state.updateTracking = {
|
|
updatedPrependsIndices,
|
|
updatedAppendsIndices,
|
|
updatedInsertionsIndices
|
|
};
|
|
if (toUpdate.size > 0) {
|
|
logger_exports.warnOnce(
|
|
"applyTransaction() update includes items not present in current data; ignoring missing items."
|
|
);
|
|
}
|
|
}
|
|
// Flattens grouped inserts to find updated item offsets while consuming the lookup set.
|
|
collectUpdatedIndicesFromGroups(groups, toUpdate) {
|
|
if (toUpdate.size === 0 || groups.length === 0) {
|
|
return [];
|
|
}
|
|
const updatedIndices = [];
|
|
let flatIndex = 0;
|
|
for (const group of groups) {
|
|
for (const item of group) {
|
|
if (toUpdate.has(item)) {
|
|
updatedIndices.push(flatIndex);
|
|
toUpdate.delete(item);
|
|
}
|
|
flatIndex++;
|
|
}
|
|
if (toUpdate.size === 0) {
|
|
break;
|
|
}
|
|
}
|
|
return updatedIndices;
|
|
}
|
|
/** Lazy item→index map for O(1) lookups. */
|
|
getItemToIndexMap() {
|
|
if (this.itemToIndexCache === void 0) {
|
|
this.itemToIndexCache = /* @__PURE__ */ new Map();
|
|
for (let i = 0; i < this.data.length; i++) {
|
|
if (!this.itemToIndexCache.has(this.data[i])) {
|
|
this.itemToIndexCache.set(this.data[i], i);
|
|
}
|
|
}
|
|
}
|
|
return this.itemToIndexCache;
|
|
}
|
|
collectUpdatedOriginalIndices(toUpdate, state) {
|
|
const indexMap = this.getItemToIndexMap();
|
|
for (const item of [...toUpdate]) {
|
|
const idx = indexMap.get(item);
|
|
if (idx !== void 0 && !state.removedOriginalIndices.has(idx)) {
|
|
state.updatedOriginalIndices.add(idx);
|
|
toUpdate.delete(item);
|
|
}
|
|
}
|
|
}
|
|
removeFromTrackedInsertions(removeValues, state) {
|
|
for (let trackedIdx = 0; trackedIdx < state.trackedInsertions.length; trackedIdx++) {
|
|
const tracked = state.trackedInsertions[trackedIdx];
|
|
const previousLength = tracked.items.length;
|
|
const removedOffsets = [];
|
|
let itemIndex = 0;
|
|
while (itemIndex < tracked.items.length) {
|
|
if (removeValues.includes(tracked.items[itemIndex])) {
|
|
removedOffsets.push(itemIndex + removedOffsets.length);
|
|
tracked.items.splice(itemIndex, 1);
|
|
state.virtualLength--;
|
|
} else {
|
|
itemIndex++;
|
|
}
|
|
}
|
|
if (removedOffsets.length > 0) {
|
|
this.adjustLaterInsertionsAfterRemoval(
|
|
state.trackedInsertions,
|
|
trackedIdx,
|
|
tracked,
|
|
previousLength,
|
|
removedOffsets
|
|
);
|
|
}
|
|
}
|
|
}
|
|
adjustLaterInsertionsAfterRemoval(trackedInsertions, trackedIdx, tracked, previousLength, removedOffsets) {
|
|
const removedCount = removedOffsets.length;
|
|
for (let j = trackedIdx + 1; j < trackedInsertions.length; j++) {
|
|
const later = trackedInsertions[j];
|
|
if (later.virtualIndex <= tracked.virtualIndex) {
|
|
continue;
|
|
}
|
|
const relativeInsertionPosition = Math.min(
|
|
Math.max(later.virtualIndex - tracked.virtualIndex, 0),
|
|
previousLength
|
|
);
|
|
let removedBeforeInsertion = 0;
|
|
for (const offset of removedOffsets) {
|
|
if (offset < relativeInsertionPosition) {
|
|
removedBeforeInsertion++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (relativeInsertionPosition === previousLength) {
|
|
removedBeforeInsertion = removedCount;
|
|
}
|
|
if (removedBeforeInsertion > 0) {
|
|
later.virtualIndex -= removedBeforeInsertion;
|
|
}
|
|
}
|
|
}
|
|
buildSpliceOperations(totalPrependCount, totalInsertionCount, totalAppendCount, survivingOriginalCount, trackedInsertions, sortedRemovedDesc, sortedRemovedAsc) {
|
|
const spliceOps = [];
|
|
if (totalPrependCount > 0) {
|
|
spliceOps.push({
|
|
index: 0,
|
|
deleteCount: 0,
|
|
insertCount: totalPrependCount
|
|
});
|
|
}
|
|
if (sortedRemovedDesc && sortedRemovedDesc.length > 0) {
|
|
let currentGroupStart = sortedRemovedDesc[0];
|
|
let currentGroupCount = 1;
|
|
for (let i = 1; i < sortedRemovedDesc.length; i++) {
|
|
const currentIndex = sortedRemovedDesc[i];
|
|
const prevIndex = sortedRemovedDesc[i - 1];
|
|
if (prevIndex - currentIndex === 1) {
|
|
currentGroupCount++;
|
|
} else {
|
|
spliceOps.push({
|
|
index: currentGroupStart - currentGroupCount + 1 + totalPrependCount,
|
|
deleteCount: currentGroupCount,
|
|
insertCount: 0
|
|
});
|
|
currentGroupStart = currentIndex;
|
|
currentGroupCount = 1;
|
|
}
|
|
}
|
|
spliceOps.push({
|
|
index: currentGroupStart - currentGroupCount + 1 + totalPrependCount,
|
|
deleteCount: currentGroupCount,
|
|
insertCount: 0
|
|
});
|
|
}
|
|
if (trackedInsertions.length > 0) {
|
|
for (const insertion of trackedInsertions) {
|
|
const removalsBeforeInsertion = this.countRemovalsBeforeIndex(
|
|
sortedRemovedAsc,
|
|
totalPrependCount,
|
|
insertion.virtualIndex
|
|
);
|
|
const adjustedIndex = insertion.virtualIndex - removalsBeforeInsertion;
|
|
spliceOps.push({
|
|
index: adjustedIndex,
|
|
deleteCount: 0,
|
|
insertCount: insertion.items.length
|
|
});
|
|
}
|
|
}
|
|
if (totalAppendCount > 0) {
|
|
spliceOps.push({
|
|
index: totalPrependCount + survivingOriginalCount + totalInsertionCount,
|
|
deleteCount: 0,
|
|
insertCount: totalAppendCount
|
|
});
|
|
}
|
|
return spliceOps;
|
|
}
|
|
countRemovalsBeforeIndex(sortedRemovedAsc, totalPrependCount, insertionVirtualIndex) {
|
|
if (!sortedRemovedAsc || sortedRemovedAsc.length === 0) {
|
|
return 0;
|
|
}
|
|
let removalsBeforeInsertion = 0;
|
|
for (const removedIndex of sortedRemovedAsc) {
|
|
const virtualIndexOfRemoval = removedIndex + totalPrependCount;
|
|
if (virtualIndexOfRemoval < insertionVirtualIndex) {
|
|
removalsBeforeInsertion++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return removalsBeforeInsertion;
|
|
}
|
|
resolveUpdatedIndices(totalPrependCount, totalInsertionCount, survivingOriginalCount, updateTracking, sortedRemovedAsc, updatedOriginalIndices, trackedInsertions) {
|
|
const updatedFinalIndices = /* @__PURE__ */ new Set();
|
|
if (updateTracking) {
|
|
for (const prependIdx of updateTracking.updatedPrependsIndices) {
|
|
updatedFinalIndices.add(prependIdx);
|
|
}
|
|
}
|
|
if (updatedOriginalIndices.size > 0) {
|
|
const sortedUpdatedOriginals = Array.from(updatedOriginalIndices).sort((a, b) => a - b);
|
|
let removalPtr = 0;
|
|
for (const originalIdx of sortedUpdatedOriginals) {
|
|
if (sortedRemovedAsc) {
|
|
while (removalPtr < sortedRemovedAsc.length && sortedRemovedAsc[removalPtr] < originalIdx) {
|
|
removalPtr++;
|
|
}
|
|
}
|
|
const removalsBeforeCount = sortedRemovedAsc ? removalPtr : 0;
|
|
const virtualPosOfOriginal = originalIdx + totalPrependCount;
|
|
let insertionsBeforeCount = 0;
|
|
for (const insertion of trackedInsertions) {
|
|
if (insertion.virtualIndex <= virtualPosOfOriginal) {
|
|
insertionsBeforeCount += insertion.items.length;
|
|
}
|
|
}
|
|
const finalIdx = originalIdx + totalPrependCount - removalsBeforeCount + insertionsBeforeCount;
|
|
updatedFinalIndices.add(finalIdx);
|
|
}
|
|
}
|
|
if (updateTracking) {
|
|
const appendStartIdx = totalPrependCount + survivingOriginalCount + totalInsertionCount;
|
|
for (const appendIdx of updateTracking.updatedAppendsIndices) {
|
|
updatedFinalIndices.add(appendStartIdx + appendIdx);
|
|
}
|
|
if (updateTracking.updatedInsertionsIndices.length > 0 && trackedInsertions.length > 0) {
|
|
let flatIdx = 0;
|
|
for (const insertion of trackedInsertions) {
|
|
const removalsBeforeInsertion = this.countRemovalsBeforeIndex(
|
|
sortedRemovedAsc,
|
|
totalPrependCount,
|
|
insertion.virtualIndex
|
|
);
|
|
const insertionFinalIdx = insertion.virtualIndex - removalsBeforeInsertion;
|
|
for (let itemOffset = 0; itemOffset < insertion.items.length; itemOffset++) {
|
|
if (updateTracking.updatedInsertionsIndices.includes(flatIdx)) {
|
|
updatedFinalIndices.add(insertionFinalIdx + itemOffset);
|
|
}
|
|
flatIdx++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return updatedFinalIndices;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/interaction/syncManager.ts
|
|
var _SyncManager = class _SyncManager {
|
|
constructor(chart) {
|
|
this.chart = chart;
|
|
}
|
|
subscribe(groupId = _SyncManager.DEFAULT_GROUP) {
|
|
let syncGroup = this.get(groupId);
|
|
if (!syncGroup) {
|
|
syncGroup = { members: /* @__PURE__ */ new Set() };
|
|
_SyncManager.chartsGroups.set(groupId, syncGroup);
|
|
}
|
|
syncGroup.members.add(this.chart);
|
|
return this;
|
|
}
|
|
unsubscribe(groupId = _SyncManager.DEFAULT_GROUP) {
|
|
const groupState = this.get(groupId);
|
|
groupState?.members.delete(this.chart);
|
|
delete groupState?.domains?.x?.sources?.[this.chart.id];
|
|
delete groupState?.domains?.y?.sources?.[this.chart.id];
|
|
return this;
|
|
}
|
|
getChart() {
|
|
return this.chart;
|
|
}
|
|
getGroupState(groupId = _SyncManager.DEFAULT_GROUP) {
|
|
return this.get(groupId);
|
|
}
|
|
getGroupMembers(groupId = _SyncManager.DEFAULT_GROUP) {
|
|
const syncGroup = this.get(groupId);
|
|
return syncGroup ? Array.from(syncGroup.members) : [];
|
|
}
|
|
getGroupSiblings(groupId = _SyncManager.DEFAULT_GROUP) {
|
|
return this.getGroupMembers(groupId).filter((chart) => chart !== this.chart);
|
|
}
|
|
getGroupSyncMode(groupId = _SyncManager.DEFAULT_GROUP) {
|
|
if (this.getGroupMembers(groupId).some((c) => c.series.length > 1)) {
|
|
return "multi-series";
|
|
}
|
|
return "single-series";
|
|
}
|
|
get(groupId) {
|
|
return _SyncManager.chartsGroups.get(groupId);
|
|
}
|
|
};
|
|
_SyncManager.chartsGroups = /* @__PURE__ */ new Map();
|
|
_SyncManager.DEFAULT_GROUP = Symbol("sync-group-default");
|
|
var SyncManager = _SyncManager;
|
|
|
|
// packages/ag-charts-community/src/chart/keyboard.ts
|
|
var Keyboard = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Keyboard.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Keyboard.prototype, "tabIndex", 2);
|
|
|
|
// packages/ag-charts-community/src/motion/fromToMotion.ts
|
|
var fromToMotion_exports = {};
|
|
__export(fromToMotion_exports, {
|
|
NODE_UPDATE_STATE_TO_PHASE_MAPPING: () => NODE_UPDATE_STATE_TO_PHASE_MAPPING,
|
|
fromToMotion: () => fromToMotion,
|
|
staticFromToMotion: () => staticFromToMotion
|
|
});
|
|
var NODE_UPDATE_STATE_TO_PHASE_MAPPING = {
|
|
added: "add",
|
|
updated: "update",
|
|
removed: "remove",
|
|
unknown: "initial",
|
|
"no-op": "none"
|
|
};
|
|
function fromToMotion(groupId, subId, animationManager, selectionsOrNodes, fns, getDatumId, diff9) {
|
|
const { fromFn, toFn, applyFn = (node, props) => node.setProperties(props) } = fns;
|
|
const { nodes, selections } = deconstructSelectionsOrNodes(selectionsOrNodes);
|
|
const processNodes = (liveNodes, subNodes) => {
|
|
let prevFromProps;
|
|
let liveNodeIndex = 0;
|
|
let nodeIndex = 0;
|
|
for (const node of subNodes) {
|
|
const isLive = liveNodes[liveNodeIndex] === node;
|
|
const ctx = {
|
|
last: nodeIndex >= subNodes.length - 1,
|
|
lastLive: liveNodeIndex >= liveNodes.length - 1,
|
|
prev: subNodes[nodeIndex - 1],
|
|
prevFromProps,
|
|
prevLive: liveNodes[liveNodeIndex - 1],
|
|
next: subNodes[nodeIndex + 1],
|
|
nextLive: liveNodes[liveNodeIndex + (isLive ? 1 : 0)]
|
|
};
|
|
const animationId = `${groupId}_${subId}_${node.id}`;
|
|
animationManager.stopByAnimationId(animationId);
|
|
let status = "unknown";
|
|
if (!isLive) {
|
|
status = "removed";
|
|
} else if (getDatumId && diff9) {
|
|
status = calculateStatus(node, node.datum, getDatumId, diff9);
|
|
}
|
|
node.transitionOut = status === "removed";
|
|
const { phase, start: start2, finish, delay, duration, ...from3 } = fromFn(node, node.datum, status, ctx);
|
|
const {
|
|
phase: toPhase,
|
|
start: toStart,
|
|
finish: toFinish,
|
|
delay: toDelay,
|
|
duration: toDuration,
|
|
...to
|
|
} = toFn(node, node.datum, status, ctx);
|
|
const collapsable = finish == null;
|
|
animationManager.animate({
|
|
id: animationId,
|
|
groupId,
|
|
phase: phase ?? toPhase ?? "update",
|
|
duration: duration ?? toDuration,
|
|
delay: delay ?? toDelay,
|
|
from: from3,
|
|
to,
|
|
ease: easeOut,
|
|
collapsable,
|
|
onPlay: () => {
|
|
const startProps = { ...start2, ...toStart, ...from3 };
|
|
applyFn(node, startProps, "start");
|
|
},
|
|
onUpdate(props) {
|
|
applyFn(node, props, "update");
|
|
},
|
|
onStop: () => {
|
|
const endProps = {
|
|
...start2,
|
|
...toStart,
|
|
...from3,
|
|
...to,
|
|
...finish,
|
|
...toFinish
|
|
};
|
|
applyFn(node, endProps, "end");
|
|
}
|
|
});
|
|
if (isLive) {
|
|
liveNodeIndex++;
|
|
}
|
|
nodeIndex++;
|
|
prevFromProps = from3;
|
|
}
|
|
};
|
|
let selectionIndex = 0;
|
|
for (const selection of selections) {
|
|
const selectionNodes = selection.nodes();
|
|
const liveNodes = selectionNodes.filter((n) => !selection.isGarbage(n));
|
|
processNodes(liveNodes, selectionNodes);
|
|
animationManager.animate({
|
|
id: `${groupId}_${subId}_selection_${selectionIndex}`,
|
|
groupId,
|
|
phase: "end",
|
|
from: 0,
|
|
to: 1,
|
|
ease: easeOut,
|
|
onStop() {
|
|
selection.cleanup();
|
|
}
|
|
});
|
|
selectionIndex++;
|
|
}
|
|
processNodes(nodes, nodes);
|
|
}
|
|
function staticFromToMotion(groupId, subId, animationManager, selectionsOrNodes, from3, to, extraOpts) {
|
|
const { nodes, selections } = deconstructSelectionsOrNodes(selectionsOrNodes);
|
|
const { start: start2, finish, phase } = extraOpts;
|
|
animationManager.animate({
|
|
id: `${groupId}_${subId}`,
|
|
groupId,
|
|
phase: phase ?? "update",
|
|
from: from3,
|
|
to,
|
|
ease: easeOut,
|
|
onPlay: () => {
|
|
if (!start2)
|
|
return;
|
|
for (const node of nodes) {
|
|
node.setProperties(start2);
|
|
}
|
|
for (const selection of selections) {
|
|
const selectionNodes = selection.nodes();
|
|
selection.batchedUpdate(function staticMotionPlay() {
|
|
for (const node of selectionNodes) {
|
|
node.setProperties(start2);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
onUpdate(props) {
|
|
for (const node of nodes) {
|
|
node.setProperties(props);
|
|
}
|
|
for (const selection of selections) {
|
|
const selectionNodes = selection.nodes();
|
|
selection.batchedUpdate(function staticMotionUpdate() {
|
|
for (const node of selectionNodes) {
|
|
node.setProperties(props);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
onStop: () => {
|
|
for (const node of nodes) {
|
|
node.setProperties({ ...to, ...finish });
|
|
}
|
|
for (const selection of selections) {
|
|
const selectionNodes = selection.nodes();
|
|
selection.batchedUpdate(function staticMotionStop() {
|
|
for (const node of selectionNodes) {
|
|
node.setProperties({ ...to, ...finish });
|
|
}
|
|
selection.cleanup();
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function calculateStatus(node, datum, getDatumId, diff9) {
|
|
const id = getDatumId(node, datum);
|
|
if (diff9.added.has(id)) {
|
|
return "added";
|
|
}
|
|
if (diff9.removed.has(id)) {
|
|
return "removed";
|
|
}
|
|
if (node.previousDatum == null && node.datum != null) {
|
|
return "added";
|
|
}
|
|
if (node.previousDatum != null && node.datum == null) {
|
|
return "removed";
|
|
}
|
|
return "updated";
|
|
}
|
|
|
|
// packages/ag-charts-community/src/motion/resetMotion.ts
|
|
var resetMotion_exports = {};
|
|
__export(resetMotion_exports, {
|
|
resetMotion: () => resetMotion
|
|
});
|
|
function resetMotion(selectionsOrNodes, propsFn) {
|
|
const { nodes, selections } = deconstructSelectionsOrNodes(selectionsOrNodes);
|
|
for (const selection of selections) {
|
|
const selectionNodes = selection.nodes();
|
|
selection.batchedUpdate(function resetMotionNodes() {
|
|
for (const node of selectionNodes) {
|
|
const from3 = propsFn(node, node.datum);
|
|
node.setProperties(from3);
|
|
}
|
|
selection.cleanup();
|
|
});
|
|
}
|
|
for (const node of nodes) {
|
|
const from3 = propsFn(node, node.datum);
|
|
node.setProperties(from3);
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/selection.ts
|
|
var Selection = class _Selection {
|
|
constructor(parentNode, classOrFactory, autoCleanup = true) {
|
|
this.parentNode = parentNode;
|
|
this.autoCleanup = autoCleanup;
|
|
this.garbageBin = /* @__PURE__ */ new Set();
|
|
this._nodesMap = /* @__PURE__ */ new Map();
|
|
this._nodes = [];
|
|
this.data = [];
|
|
this.debug = debugLogger_exports.create(true, "scene", "scene:selections");
|
|
this.nodeFactory = Object.prototype.isPrototypeOf.call(Node2, classOrFactory) ? () => new classOrFactory() : classOrFactory;
|
|
}
|
|
static select(parent, classOrFactory, garbageCollection = true) {
|
|
return new _Selection(parent, classOrFactory, garbageCollection);
|
|
}
|
|
static selectAll(parent, predicate) {
|
|
const results = [];
|
|
const traverse = (node) => {
|
|
if (predicate(node)) {
|
|
results.push(node);
|
|
}
|
|
if (node instanceof Group) {
|
|
for (const child of node.children()) {
|
|
traverse(child);
|
|
}
|
|
}
|
|
};
|
|
traverse(parent);
|
|
return results;
|
|
}
|
|
static selectByClass(node, ...Classes) {
|
|
return _Selection.selectAll(node, (n) => Classes.some((C2) => n instanceof C2));
|
|
}
|
|
static selectByTag(node, tag) {
|
|
return _Selection.selectAll(node, (n) => n.tag === tag);
|
|
}
|
|
createNode(datum, initializer, idx) {
|
|
const node = this.nodeFactory(datum);
|
|
node.datum = datum;
|
|
initializer?.(node);
|
|
if (idx == null) {
|
|
this._nodes.push(node);
|
|
} else {
|
|
this._nodes.splice(idx, 0, node);
|
|
}
|
|
this.parentNode.appendChild(node);
|
|
return node;
|
|
}
|
|
/**
|
|
* Update the data in a selection. If an `getDatumId()` function is provided, maintain a list of ids related to
|
|
* the nodes. Otherwise, take the more efficient route of simply creating and destroying nodes at the end
|
|
* of the array.
|
|
*/
|
|
update(data, initializer, getDatumId) {
|
|
if (this.garbageBin.size > 0) {
|
|
this.debug(`Selection - update() called with pending garbage`, data);
|
|
}
|
|
if (getDatumId && this._nodesMap.size === 0 && this._nodes.length > 0) {
|
|
for (const node of this._nodes) {
|
|
this.garbageBin.add(node);
|
|
}
|
|
}
|
|
if (!getDatumId && this._nodesMap.size > 0) {
|
|
this._nodesMap.clear();
|
|
}
|
|
if (getDatumId) {
|
|
const dataMap = /* @__PURE__ */ new Map();
|
|
const duplicateMap = /* @__PURE__ */ new Map();
|
|
for (let idx = 0; idx < data.length; idx++) {
|
|
const datum = data[idx];
|
|
let id = getDatumId(datum);
|
|
if (dataMap.has(id)) {
|
|
const index = (duplicateMap.get(id) ?? 0) + 1;
|
|
duplicateMap.set(id, index);
|
|
id = `${id}:${index}`;
|
|
}
|
|
dataMap.set(id, idx);
|
|
}
|
|
for (const [node, datumId] of this._nodesMap.entries()) {
|
|
const idx = dataMap.get(datumId);
|
|
if (idx == null) {
|
|
this.garbageBin.add(node);
|
|
} else {
|
|
node.datum = data[idx];
|
|
this.garbageBin.delete(node);
|
|
dataMap.delete(datumId);
|
|
}
|
|
}
|
|
for (const [datumId, idx] of dataMap.entries()) {
|
|
const datum = data[idx];
|
|
this._nodesMap.set(this.createNode(datum, initializer, idx), datumId);
|
|
}
|
|
} else {
|
|
const maxLength = Math.max(data.length, this.data.length);
|
|
for (let i = 0; i < maxLength; i++) {
|
|
if (i >= data.length) {
|
|
this.garbageBin.add(this._nodes[i]);
|
|
} else if (i >= this._nodes.length) {
|
|
this.createNode(data[i], initializer);
|
|
} else {
|
|
this._nodes[i].datum = data[i];
|
|
this.garbageBin.delete(this._nodes[i]);
|
|
}
|
|
}
|
|
}
|
|
this.data = data.slice();
|
|
if (this.autoCleanup) {
|
|
this.cleanup();
|
|
}
|
|
return this;
|
|
}
|
|
cleanup() {
|
|
if (this.garbageBin.size === 0) {
|
|
return this;
|
|
}
|
|
const selection = this;
|
|
function removeGarbage(node) {
|
|
if (selection.garbageBin.has(node)) {
|
|
selection._nodesMap.delete(node);
|
|
selection.garbageBin.delete(node);
|
|
node.destroy();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
this._nodes = this._nodes.filter(removeGarbage);
|
|
return this;
|
|
}
|
|
clear() {
|
|
this.update([]);
|
|
for (const node of this._nodesMap.keys()) {
|
|
this.garbageBin.add(node);
|
|
}
|
|
this._nodesMap.clear();
|
|
return this;
|
|
}
|
|
isGarbage(node) {
|
|
return this.garbageBin.has(node);
|
|
}
|
|
each(iterate2) {
|
|
const nodes = this._nodes;
|
|
this.parentNode.batchedUpdate(function selectionEach() {
|
|
for (const entry of nodes.entries()) {
|
|
iterate2(entry[1], entry[1].datum, entry[0]);
|
|
}
|
|
});
|
|
return this;
|
|
}
|
|
*[Symbol.iterator]() {
|
|
for (let index = 0; index < this._nodes.length; index++) {
|
|
const node = this._nodes[index];
|
|
yield { node, datum: node.datum, index };
|
|
}
|
|
}
|
|
select(predicate) {
|
|
return _Selection.selectAll(this.parentNode, predicate);
|
|
}
|
|
selectByClass(Class) {
|
|
return _Selection.selectByClass(this.parentNode, Class);
|
|
}
|
|
selectByTag(tag) {
|
|
return _Selection.selectByTag(this.parentNode, tag);
|
|
}
|
|
nodes() {
|
|
return this._nodes;
|
|
}
|
|
at(index) {
|
|
return this._nodes.at(index);
|
|
}
|
|
get length() {
|
|
return this._nodes.length;
|
|
}
|
|
batchedUpdate(fn) {
|
|
this.parentNode.batchedUpdate(fn);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/shape/line.ts
|
|
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 lineDistanceSquared(px, py, x1, y1, x2, y2, Infinity);
|
|
}
|
|
render(renderCtx) {
|
|
const { ctx, devicePixelRatio } = renderCtx;
|
|
let { x1, y1, x2, y2 } = this;
|
|
if (x1 === x2) {
|
|
const { strokeWidth } = this;
|
|
const x = Math.round(x1 * devicePixelRatio) / devicePixelRatio + Math.trunc(strokeWidth * devicePixelRatio) % 2 / (devicePixelRatio * 2);
|
|
x1 = x;
|
|
x2 = x;
|
|
} else if (y1 === y2) {
|
|
const { strokeWidth } = this;
|
|
const y = Math.round(y1 * devicePixelRatio) / devicePixelRatio + Math.trunc(strokeWidth * devicePixelRatio) % 2 / (devicePixelRatio * 2);
|
|
y1 = y;
|
|
y2 = y;
|
|
}
|
|
ctx.beginPath();
|
|
ctx.moveTo(x1, y1);
|
|
ctx.lineTo(x2, y2);
|
|
this.fillStroke(ctx);
|
|
this.fillShadow?.markClean();
|
|
super.render(renderCtx);
|
|
}
|
|
toSVG() {
|
|
if (!this.visible)
|
|
return;
|
|
const element2 = createSvgElement("line");
|
|
element2.setAttribute("x1", String(this.x1));
|
|
element2.setAttribute("y1", String(this.y1));
|
|
element2.setAttribute("x2", String(this.x2));
|
|
element2.setAttribute("y2", String(this.y2));
|
|
this.applySvgStrokeAttributes(element2);
|
|
return {
|
|
elements: [element2]
|
|
};
|
|
}
|
|
};
|
|
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/chart/label.ts
|
|
var LabelBorder = class {
|
|
constructor() {
|
|
this.enabled = true;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelBorder.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelBorder.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelBorder.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelBorder.prototype, "strokeOpacity", 2);
|
|
var LabelStyle = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.border = new LabelBorder();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelStyle.prototype, "border", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelStyle.prototype, "color", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelStyle.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelStyle.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelStyle.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelStyle.prototype, "fontStyle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelStyle.prototype, "fontWeight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelStyle.prototype, "fontSize", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelStyle.prototype, "fontFamily", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelStyle.prototype, "padding", 2);
|
|
var Label = class extends LabelStyle {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = false;
|
|
this._cachedFormatter = void 0;
|
|
}
|
|
formatValue(formatWithContext2, type, value, params) {
|
|
const { formatter: formatter2, format } = this;
|
|
let result;
|
|
if (formatter2 != null) {
|
|
result ?? (result = formatWithContext2(formatter2, params));
|
|
}
|
|
if (format != null) {
|
|
let cachedFormatter = this._cachedFormatter;
|
|
if (cachedFormatter?.type !== type || cachedFormatter?.format !== format) {
|
|
cachedFormatter = {
|
|
type,
|
|
format,
|
|
formatter: FormatManager.getFormatter(type, format)
|
|
};
|
|
this._cachedFormatter = cachedFormatter;
|
|
}
|
|
result ?? (result = cachedFormatter.formatter?.(value));
|
|
}
|
|
return result == null || isArray(result) ? result : String(result);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Label.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Label.prototype, "formatter", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Label.prototype, "format", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Label.prototype, "itemStyler", 2);
|
|
function expandLabelPadding(label) {
|
|
const { enabled: borderEnabled = false, stroke: borderStroke } = label?.border ?? {};
|
|
const hasBoxing = label?.fill != null || borderEnabled && borderStroke != null;
|
|
const padding2 = hasBoxing ? label?.padding : null;
|
|
if (padding2 == null) {
|
|
return { bottom: 0, left: 0, right: 0, top: 0 };
|
|
} else if (typeof padding2 === "number") {
|
|
return { bottom: padding2, left: padding2, right: padding2, top: padding2 };
|
|
} else {
|
|
const { bottom = 0, left = 0, right = 0, top = 0 } = padding2;
|
|
return { bottom, left, right, top };
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/module/moduleMap.ts
|
|
var ModuleMap = class {
|
|
constructor() {
|
|
this.moduleMap = /* @__PURE__ */ new Map();
|
|
}
|
|
modules() {
|
|
return this.moduleMap.values();
|
|
}
|
|
addModule(moduleName, moduleInstance) {
|
|
if (this.moduleMap.has(moduleName)) {
|
|
throw new Error(`AG Charts - module already initialised: ${moduleName}`);
|
|
}
|
|
this.moduleMap.set(moduleName, moduleInstance);
|
|
}
|
|
removeModule(moduleName) {
|
|
this.moduleMap.get(moduleName)?.destroy?.();
|
|
this.moduleMap.delete(moduleName);
|
|
}
|
|
getModule(moduleName) {
|
|
return this.moduleMap.get(moduleName);
|
|
}
|
|
isEnabled(moduleName) {
|
|
return this.moduleMap.has(moduleName);
|
|
}
|
|
mapModules(callback2) {
|
|
return Array.from(this.moduleMap.values(), callback2);
|
|
}
|
|
destroy() {
|
|
for (const moduleInstance of this.moduleMap.values()) {
|
|
moduleInstance?.destroy?.();
|
|
}
|
|
this.moduleMap.clear();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/shape/range.ts
|
|
var Range = class extends Shape {
|
|
constructor(opts = {}) {
|
|
super(opts);
|
|
this.x1 = 0;
|
|
this.y1 = 0;
|
|
this.x2 = 0;
|
|
this.y2 = 0;
|
|
this.startLine = false;
|
|
this.endLine = false;
|
|
this.horizontal = false;
|
|
this.strokeWidth = 1;
|
|
}
|
|
computeBBox() {
|
|
return new BBox(this.x1, this.y1, this.x2 - this.x1, this.y2 - this.y1);
|
|
}
|
|
isPointInPath(_x, _y) {
|
|
return false;
|
|
}
|
|
render(renderCtx) {
|
|
const { ctx } = renderCtx;
|
|
let { x1, y1, x2, y2 } = this;
|
|
x1 = this.align(x1);
|
|
y1 = this.align(y1);
|
|
x2 = this.align(x2);
|
|
y2 = this.align(y2);
|
|
const { fill, horizontal } = this;
|
|
const { globalAlpha } = ctx;
|
|
if (fill != null) {
|
|
this.applyFillAndAlpha(ctx);
|
|
ctx.beginPath();
|
|
ctx.moveTo(x1, y1);
|
|
ctx.lineTo(x2, y1);
|
|
ctx.lineTo(x2, y2);
|
|
ctx.lineTo(x1, y2);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
ctx.globalAlpha = globalAlpha;
|
|
}
|
|
const { stroke: stroke3, strokeWidth, startLine, endLine } = this;
|
|
const strokeActive = !!((startLine || endLine) && stroke3 && strokeWidth);
|
|
if (strokeActive) {
|
|
const { lineDash, lineDashOffset, lineCap, lineJoin } = this;
|
|
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;
|
|
}
|
|
ctx.beginPath();
|
|
if (startLine) {
|
|
ctx.moveTo(x1, y1);
|
|
if (horizontal) {
|
|
ctx.lineTo(x1, y2);
|
|
} else {
|
|
ctx.lineTo(x2, y1);
|
|
}
|
|
}
|
|
if (endLine) {
|
|
ctx.moveTo(x2, y2);
|
|
if (horizontal) {
|
|
ctx.lineTo(x2, y1);
|
|
} else {
|
|
ctx.lineTo(x1, y2);
|
|
}
|
|
}
|
|
ctx.stroke();
|
|
ctx.globalAlpha = globalAlpha;
|
|
}
|
|
this.fillShadow?.markClean();
|
|
super.render(renderCtx);
|
|
}
|
|
};
|
|
Range.className = "Range";
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Range.prototype, "x1", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Range.prototype, "y1", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Range.prototype, "x2", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Range.prototype, "y2", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Range.prototype, "startLine", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Range.prototype, "endLine", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Range.prototype, "horizontal", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/crossline/crossLine.ts
|
|
function getCrossLineValue(crossLine) {
|
|
switch (crossLine.type) {
|
|
case "line":
|
|
return crossLine.value;
|
|
case "range":
|
|
return crossLine.range;
|
|
}
|
|
}
|
|
function validateCrossLineValue(crossLine, scale2) {
|
|
const value = getCrossLineValue(crossLine);
|
|
if (value == null) {
|
|
return false;
|
|
}
|
|
const isContinuous2 = ContinuousScale.is(scale2) || DiscreteTimeScale.is(scale2);
|
|
const validValue = (val) => checkDatum(val, isContinuous2) && !Number.isNaN(scale2.convert(val, { clamp: true }));
|
|
if (crossLine.type === "range") {
|
|
const [start2, end3] = value;
|
|
return validValue(start2) && validValue(end3);
|
|
} else {
|
|
return validValue(value);
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/crossline/cartesianCrossLine.ts
|
|
var horizontalLineAnchors = {
|
|
top: { rangeH: 0, rangeV: -1, labelH: 0, labelV: 1 },
|
|
"inside-top": { rangeH: 0, rangeV: -1, labelH: 0, labelV: 1 },
|
|
"top-left": { rangeH: -1, rangeV: -1, labelH: -1, labelV: 1 },
|
|
"inside-top-left": { rangeH: -1, rangeV: -1, labelH: -1, labelV: 1 },
|
|
left: { rangeH: -1, rangeV: 0, labelH: 1, labelV: 0 },
|
|
"inside-left": { rangeH: -1, rangeV: 0, labelH: -1, labelV: 0 },
|
|
"bottom-left": { rangeH: -1, rangeV: 1, labelH: -1, labelV: -1 },
|
|
"inside-bottom-left": { rangeH: -1, rangeV: 1, labelH: -1, labelV: -1 },
|
|
bottom: { rangeH: 0, rangeV: 1, labelH: 0, labelV: -1 },
|
|
"inside-bottom": { rangeH: 0, rangeV: 1, labelH: 0, labelV: -1 },
|
|
"bottom-right": { rangeH: 1, rangeV: 1, labelH: 1, labelV: -1 },
|
|
"inside-bottom-right": { rangeH: 1, rangeV: 1, labelH: 1, labelV: -1 },
|
|
right: { rangeH: 1, rangeV: 0, labelH: -1, labelV: 0 },
|
|
"inside-right": { rangeH: 1, rangeV: 0, labelH: 1, labelV: 0 },
|
|
"top-right": { rangeH: 1, rangeV: -1, labelH: 1, labelV: 1 },
|
|
"inside-top-right": { rangeH: 1, rangeV: -1, labelH: 1, labelV: 1 },
|
|
inside: { rangeH: 0, rangeV: 0, labelH: 0, labelV: 0 }
|
|
};
|
|
var verticalLineAnchors = {
|
|
top: { rangeH: 0, rangeV: -1, labelH: 0, labelV: 1 },
|
|
"inside-top": { rangeH: 0, rangeV: -1, labelH: 0, labelV: -1 },
|
|
"top-left": { rangeH: -1, rangeV: -1, labelH: 1, labelV: -1 },
|
|
"inside-top-left": { rangeH: -1, rangeV: -1, labelH: 1, labelV: -1 },
|
|
left: { rangeH: -1, rangeV: 0, labelH: 1, labelV: 0 },
|
|
"inside-left": { rangeH: -1, rangeV: 0, labelH: 1, labelV: 0 },
|
|
"bottom-left": { rangeH: -1, rangeV: 1, labelH: 1, labelV: 1 },
|
|
"inside-bottom-left": { rangeH: -1, rangeV: 1, labelH: 1, labelV: 1 },
|
|
bottom: { rangeH: 0, rangeV: 1, labelH: 0, labelV: -1 },
|
|
"inside-bottom": { rangeH: 0, rangeV: 1, labelH: 0, labelV: 1 },
|
|
"bottom-right": { rangeH: 1, rangeV: 1, labelH: -1, labelV: 1 },
|
|
"inside-bottom-right": { rangeH: 1, rangeV: 1, labelH: -1, labelV: 1 },
|
|
right: { rangeH: 1, rangeV: 0, labelH: -1, labelV: 0 },
|
|
"inside-right": { rangeH: 1, rangeV: 0, labelH: -1, labelV: 0 },
|
|
"top-right": { rangeH: 1, rangeV: -1, labelH: -1, labelV: -1 },
|
|
"inside-top-right": { rangeH: -1, rangeV: -1, labelH: -1, labelV: -1 },
|
|
inside: { rangeH: 0, rangeV: 0, labelH: 0, labelV: 0 }
|
|
};
|
|
var horizontalRangeAnchors = {
|
|
top: { rangeH: 0, rangeV: -1, labelH: 0, labelV: 1 },
|
|
"inside-top": { rangeH: 0, rangeV: -1, labelH: 0, labelV: -1 },
|
|
"top-left": { rangeH: -1, rangeV: -1, labelH: -1, labelV: 1 },
|
|
"inside-top-left": { rangeH: -1, rangeV: -1, labelH: -1, labelV: -1 },
|
|
left: { rangeH: -1, rangeV: 0, labelH: 1, labelV: 0 },
|
|
"inside-left": { rangeH: -1, rangeV: 0, labelH: -1, labelV: 0 },
|
|
"bottom-left": { rangeH: -1, rangeV: 1, labelH: -1, labelV: -1 },
|
|
"inside-bottom-left": { rangeH: -1, rangeV: 1, labelH: -1, labelV: 1 },
|
|
bottom: { rangeH: 0, rangeV: 1, labelH: 0, labelV: -1 },
|
|
"inside-bottom": { rangeH: 0, rangeV: 1, labelH: 0, labelV: 1 },
|
|
"bottom-right": { rangeH: 1, rangeV: 1, labelH: 1, labelV: -1 },
|
|
"inside-bottom-right": { rangeH: 1, rangeV: 1, labelH: 1, labelV: 1 },
|
|
right: { rangeH: 1, rangeV: 0, labelH: -1, labelV: 0 },
|
|
"inside-right": { rangeH: 1, rangeV: 0, labelH: 1, labelV: 0 },
|
|
"top-right": { rangeH: 1, rangeV: -1, labelH: 1, labelV: 1 },
|
|
"inside-top-right": { rangeH: 1, rangeV: -1, labelH: 1, labelV: -1 },
|
|
inside: { rangeH: 0, rangeV: 0, labelH: 0, labelV: 0 }
|
|
};
|
|
var verticalRangeAnchors = {
|
|
top: { rangeH: 0, rangeV: -1, labelH: 0, labelV: 1 },
|
|
"inside-top": { rangeH: 0, rangeV: -1, labelH: 0, labelV: -1 },
|
|
"top-left": { rangeH: -1, rangeV: -1, labelH: 1, labelV: -1 },
|
|
"inside-top-left": { rangeH: -1, rangeV: -1, labelH: -1, labelV: -1 },
|
|
left: { rangeH: -1, rangeV: 0, labelH: 1, labelV: 0 },
|
|
"inside-left": { rangeH: -1, rangeV: 0, labelH: -1, labelV: 0 },
|
|
"bottom-left": { rangeH: -1, rangeV: 1, labelH: 1, labelV: 1 },
|
|
"inside-bottom-left": { rangeH: -1, rangeV: 1, labelH: -1, labelV: 1 },
|
|
bottom: { rangeH: 0, rangeV: 1, labelH: 0, labelV: -1 },
|
|
"inside-bottom": { rangeH: 0, rangeV: 1, labelH: 0, labelV: 1 },
|
|
"bottom-right": { rangeH: 1, rangeV: 1, labelH: -1, labelV: 1 },
|
|
"inside-bottom-right": { rangeH: 1, rangeV: 1, labelH: 1, labelV: 1 },
|
|
right: { rangeH: 1, rangeV: 0, labelH: -1, labelV: 0 },
|
|
"inside-right": { rangeH: 1, rangeV: 0, labelH: 1, labelV: 0 },
|
|
"top-right": { rangeH: 1, rangeV: -1, labelH: -1, labelV: -1 },
|
|
"inside-top-right": { rangeH: 1, rangeV: -1, labelH: 1, labelV: -1 },
|
|
inside: { rangeH: 0, rangeV: 0, labelH: 0, labelV: 0 }
|
|
};
|
|
var CartesianCrossLineLabel = class extends LabelStyle {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = void 0;
|
|
this.padding = 5;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLineLabel.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLineLabel.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLineLabel.prototype, "text", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLineLabel.prototype, "position", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLineLabel.prototype, "rotation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLineLabel.prototype, "parallel", 2);
|
|
var CartesianCrossLine = class extends BaseProperties {
|
|
constructor() {
|
|
super();
|
|
this.id = createId(this);
|
|
this.defaultColorRange = [];
|
|
this.fill = "#c16068";
|
|
this.label = new CartesianCrossLineLabel();
|
|
this.scale = void 0;
|
|
this.clippedRange = [-Infinity, Infinity];
|
|
this.gridLength = 0;
|
|
this.position = "top";
|
|
this.rangeGroup = new Group({ name: this.id });
|
|
this.lineGroup = new Group({ name: this.id });
|
|
this.labelGroup = new Group({ name: this.id });
|
|
this.crossLineRange = this.lineGroup.appendChild(new Range());
|
|
this.crossLineLabel = this.labelGroup.appendChild(new TransformableText());
|
|
this.data = void 0;
|
|
this.startLine = false;
|
|
this.endLine = false;
|
|
this._isRange = void 0;
|
|
this.crossLineRange.pointerEvents = 1 /* None */;
|
|
}
|
|
get defaultLabelPosition() {
|
|
return "top";
|
|
}
|
|
update(visible) {
|
|
const { enabled, type, data, scale: scale2 } = this;
|
|
if (!scale2 || !enabled || !visible || !validateCrossLineValue(this, scale2) || data == null) {
|
|
this.rangeGroup.visible = false;
|
|
this.lineGroup.visible = false;
|
|
this.labelGroup.visible = false;
|
|
return;
|
|
}
|
|
this.rangeGroup.visible = visible;
|
|
this.lineGroup.visible = visible;
|
|
this.labelGroup.visible = visible;
|
|
this.updateNodes();
|
|
const isRange = type === "range";
|
|
if (isRange !== this._isRange) {
|
|
if (isRange) {
|
|
this.rangeGroup.appendChild(this.crossLineRange);
|
|
} else {
|
|
this.lineGroup.appendChild(this.crossLineRange);
|
|
}
|
|
}
|
|
this._isRange = isRange;
|
|
}
|
|
calculateLayout(visible) {
|
|
this.data = void 0;
|
|
if (!visible)
|
|
return;
|
|
const { type, range: range3, value, scale: scale2, clippedRange, strokeWidth = 0 } = this;
|
|
if (!scale2)
|
|
return;
|
|
const bandwidth = scale2.bandwidth ?? 0;
|
|
const step = scale2.step ?? 0;
|
|
const rangePadding = scale2 instanceof BandScale ? (step - bandwidth) / 2 : 0;
|
|
let [clippedRange0, clippedRange1] = findMinMax(clippedRange);
|
|
clippedRange0 -= bandwidth;
|
|
clippedRange1 += bandwidth;
|
|
let yStart;
|
|
let yEnd;
|
|
let clampedYStart;
|
|
let clampedYEnd;
|
|
if (type === "line") {
|
|
const offset = bandwidth / 2;
|
|
yStart = scale2.convert(value) + offset;
|
|
yEnd = Number.NaN;
|
|
clampedYStart = scale2.convert(value, { clamp: true }) + offset;
|
|
clampedYEnd = Number.NaN;
|
|
if (clampedYStart >= clippedRange1 || clampedYStart <= clippedRange0) {
|
|
return;
|
|
}
|
|
} else if (range3) {
|
|
const [r0, r1] = range3;
|
|
const [startAlignment, endAlignment] = rangeAlignment(r0, r1);
|
|
yStart = scale2.convert(r0, { alignment: startAlignment });
|
|
yEnd = scale2.convert(r1, { alignment: endAlignment });
|
|
clampedYStart = scale2.convert(r0, { clamp: true, alignment: startAlignment });
|
|
clampedYEnd = scale2.convert(r1, { clamp: true, alignment: endAlignment });
|
|
if (clampedYStart > clampedYEnd) {
|
|
[clampedYStart, clampedYEnd] = [clampedYEnd, clampedYStart];
|
|
[yStart, yEnd] = [yEnd, yStart];
|
|
}
|
|
if (clampedYStart >= clippedRange1 || clampedYEnd <= clippedRange0) {
|
|
return;
|
|
}
|
|
if (Number.isFinite(yStart)) {
|
|
clampedYStart -= rangePadding;
|
|
}
|
|
if (Number.isFinite(yEnd)) {
|
|
yEnd += bandwidth;
|
|
clampedYEnd += bandwidth + rangePadding;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
clampedYStart = clampArray(clampedYStart, clippedRange);
|
|
clampedYEnd = clampArray(clampedYEnd, clippedRange);
|
|
if (yStart - rangePadding >= clampedYStart)
|
|
yStart -= rangePadding;
|
|
if (yEnd + rangePadding <= clampedYEnd)
|
|
yEnd += rangePadding;
|
|
this.startLine = strokeWidth > 0 && yStart >= clampedYStart && yStart <= clampedYStart + rangePadding;
|
|
this.endLine = strokeWidth > 0 && yEnd >= clampedYEnd - bandwidth - rangePadding && yEnd <= clampedYEnd;
|
|
this.data = [clampedYStart, clampedYEnd];
|
|
if (this.label.enabled === false || !this.label.text)
|
|
return;
|
|
}
|
|
updateNodes() {
|
|
const { position, data: [r0, r1] = [0, 0], gridLength } = this;
|
|
const dr = Number.isFinite(r1) ? r1 - r0 : 0;
|
|
let bounds;
|
|
switch (position) {
|
|
case "top":
|
|
case "bottom":
|
|
bounds = new BBox(r0, position === "top" ? 0 : -gridLength, dr, gridLength);
|
|
break;
|
|
case "left":
|
|
case "right":
|
|
bounds = new BBox(position === "left" ? 0 : -gridLength, r0, gridLength, dr);
|
|
}
|
|
this.updateRangeNode(bounds);
|
|
const { label } = this;
|
|
if (label.enabled !== false && label.text) {
|
|
this.updateLabel();
|
|
this.positionLabel(bounds);
|
|
}
|
|
}
|
|
updateRangeNode(bounds) {
|
|
const {
|
|
type,
|
|
position,
|
|
crossLineRange,
|
|
startLine,
|
|
endLine,
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash
|
|
} = this;
|
|
crossLineRange.x1 = bounds.x;
|
|
crossLineRange.x2 = bounds.x + bounds.width;
|
|
crossLineRange.y1 = bounds.y;
|
|
crossLineRange.y2 = bounds.y + bounds.height;
|
|
crossLineRange.horizontal = position === "top" || position === "bottom";
|
|
crossLineRange.startLine = startLine;
|
|
crossLineRange.endLine = endLine;
|
|
crossLineRange.fill = type === "range" ? fill : void 0;
|
|
crossLineRange.fillOpacity = fillOpacity ?? 1;
|
|
crossLineRange.stroke = stroke3;
|
|
crossLineRange.strokeWidth = strokeWidth ?? 1;
|
|
crossLineRange.strokeOpacity = strokeOpacity ?? 1;
|
|
crossLineRange.lineDash = lineDash;
|
|
}
|
|
updateLabel() {
|
|
const { crossLineLabel, label } = this;
|
|
if (!label.text)
|
|
return;
|
|
crossLineLabel.fill = label.color;
|
|
crossLineLabel.text = label.text;
|
|
crossLineLabel.textAlign = "center";
|
|
crossLineLabel.textBaseline = "middle";
|
|
crossLineLabel.setFont(label);
|
|
crossLineLabel.setBoxing(label);
|
|
}
|
|
get anchor() {
|
|
const horizontal = this.position === "left" || this.position === "right";
|
|
const range3 = this.type === "range";
|
|
const { position = this.defaultLabelPosition } = this.label;
|
|
if (range3) {
|
|
const anchors = horizontal ? horizontalRangeAnchors : verticalRangeAnchors;
|
|
return anchors[position];
|
|
} else {
|
|
const anchors = horizontal ? horizontalLineAnchors : verticalLineAnchors;
|
|
return anchors[position];
|
|
}
|
|
}
|
|
positionLabel(bounds) {
|
|
const { crossLineLabel, label, anchor } = this;
|
|
crossLineLabel.rotation = toRadians(label.rotation ?? 0);
|
|
const bbox = crossLineLabel.getBBox();
|
|
if (!bbox)
|
|
return;
|
|
const { width: width2, height: height2 } = bbox;
|
|
const xOffset = label.padding + width2 / 2;
|
|
const yOffset = label.padding + height2 / 2;
|
|
const x = bounds.x + bounds.width * (anchor.rangeH + 1) / 2 - xOffset * anchor.labelH;
|
|
const y = bounds.y + bounds.height * (anchor.rangeV + 1) / 2 - yOffset * anchor.labelV;
|
|
crossLineLabel.x = x;
|
|
crossLineLabel.y = y;
|
|
crossLineLabel.rotationCenterX = x;
|
|
crossLineLabel.rotationCenterY = y;
|
|
}
|
|
computeLabelSize() {
|
|
const { label } = this;
|
|
if (label.enabled === false || !label.text)
|
|
return;
|
|
const tempText = new TransformableText();
|
|
tempText.fontFamily = label.fontFamily;
|
|
tempText.fontSize = label.fontSize;
|
|
tempText.fontStyle = label.fontStyle;
|
|
tempText.fontWeight = label.fontWeight;
|
|
tempText.text = label.text;
|
|
tempText.rotation = toRadians(label.rotation ?? 0);
|
|
tempText.textBaseline = "middle";
|
|
tempText.textAlign = "center";
|
|
const bbox = tempText.getBBox();
|
|
if (!bbox)
|
|
return;
|
|
const { width: width2, height: height2 } = bbox;
|
|
return { width: width2, height: height2 };
|
|
}
|
|
calculatePadding(into) {
|
|
const { label, anchor } = this;
|
|
const size = this.computeLabelSize();
|
|
if (!size)
|
|
return;
|
|
const { width: width2, height: height2 } = size;
|
|
const xOffset = label.padding + width2;
|
|
const yOffset = label.padding + height2;
|
|
const horizontal = this.position === "left" || this.position === "right";
|
|
if (horizontal) {
|
|
if (anchor.rangeH === -1 && anchor.labelH === 1) {
|
|
into.left = Math.max(into.left ?? 0, xOffset);
|
|
} else if (anchor.rangeH === 1 && anchor.labelH === -1) {
|
|
into.right = Math.max(into.right ?? 0, xOffset);
|
|
}
|
|
}
|
|
if (!horizontal) {
|
|
if (anchor.rangeV === -1 && anchor.labelV === 1) {
|
|
into.top = Math.max(into.top ?? 0, yOffset);
|
|
} else if (anchor.rangeV === 1 && anchor.labelV === -1) {
|
|
into.bottom = Math.max(into.bottom ?? 0, yOffset);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
CartesianCrossLine.className = "CrossLine";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLine.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLine.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLine.prototype, "range", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLine.prototype, "value", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLine.prototype, "defaultColorRange", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLine.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLine.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLine.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLine.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLine.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLine.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianCrossLine.prototype, "label", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/axis/axisGridLine.ts
|
|
var AxisGridLine = class {
|
|
constructor() {
|
|
this.enabled = true;
|
|
this.width = 1;
|
|
this.style = [
|
|
{
|
|
fill: void 0,
|
|
fillOpacity: 1,
|
|
stroke: void 0,
|
|
strokeWidth: void 0,
|
|
lineDash: []
|
|
}
|
|
];
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisGridLine.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisGridLine.prototype, "width", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisGridLine.prototype, "style", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/axis/axisInterval.ts
|
|
var AxisInterval = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisInterval.prototype, "placement", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisInterval.prototype, "step", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisInterval.prototype, "values", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisInterval.prototype, "minSpacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisInterval.prototype, "maxSpacing", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/axis/axisLabel.ts
|
|
var AxisLabel = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = true;
|
|
this.border = new LabelBorder();
|
|
this.wrapping = "never";
|
|
this.truncate = false;
|
|
this.spacing = 5;
|
|
this.color = "#575757";
|
|
this.avoidCollisions = true;
|
|
this.mirrored = false;
|
|
this.parallel = false;
|
|
this._formatters = {
|
|
"component:year": void 0,
|
|
"component:month": void 0,
|
|
"component:day": void 0,
|
|
"component:none": void 0,
|
|
"long:year": void 0,
|
|
"long:month": void 0,
|
|
"long:day": void 0,
|
|
"long:none": void 0
|
|
};
|
|
}
|
|
/**
|
|
* The side of the axis line to position the labels on.
|
|
* -1 = left (default)
|
|
* 1 = right
|
|
*/
|
|
getSideFlag() {
|
|
return this.mirrored ? 1 : -1;
|
|
}
|
|
formatValue(callWithContext2, params, index, options) {
|
|
const { formatter: formatter2, format } = this;
|
|
const { type, value, domain, boundSeries } = params;
|
|
const fractionDigits = params.type === "number" ? params.fractionDigits : void 0;
|
|
const unit = params.type === "date" ? params.unit : void 0;
|
|
let result;
|
|
if (formatter2 != null) {
|
|
const step = params.type === "date" ? params.step : void 0;
|
|
const visibleDomain = params.type === "number" ? params.visibleDomain : void 0;
|
|
result = callWithContext2(formatter2, {
|
|
value,
|
|
index,
|
|
domain,
|
|
fractionDigits,
|
|
unit,
|
|
step,
|
|
boundSeries,
|
|
visibleDomain
|
|
});
|
|
}
|
|
if (format != null && result == null) {
|
|
const { specifier, dateStyle = "long", truncateDate } = options ?? {};
|
|
const cacheKey = `${dateStyle}:${truncateDate ?? "none"}`;
|
|
let valueFormatter = this._formatters[cacheKey];
|
|
const mergedFormat = FormatManager.mergeSpecifiers(specifier, format);
|
|
if (valueFormatter?.type !== type || valueFormatter?.unit !== unit || !objectsEqual(valueFormatter?.mergedFormat, mergedFormat)) {
|
|
valueFormatter = {
|
|
type,
|
|
mergedFormat,
|
|
unit,
|
|
formatter: FormatManager.getFormatter(type, mergedFormat, unit, dateStyle, { truncateDate })
|
|
};
|
|
this._formatters[cacheKey] = valueFormatter;
|
|
}
|
|
result = valueFormatter.formatter?.(value, fractionDigits);
|
|
}
|
|
return result == null || isArray(result) ? result : String(result);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "border", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "fontStyle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "fontWeight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "fontSize", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "fontFamily", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "wrapping", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "truncate", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "minSpacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "color", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "rotation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "avoidCollisions", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "mirrored", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "parallel", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "formatter", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabel.prototype, "format", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/axis/axisLine.ts
|
|
var AxisLine = class {
|
|
constructor() {
|
|
this.enabled = true;
|
|
this.width = 1;
|
|
this.stroke = void 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLine.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLine.prototype, "width", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLine.prototype, "stroke", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/axis/axisTick.ts
|
|
var AxisTick = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = true;
|
|
this.width = 1;
|
|
this.size = 6;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTick.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTick.prototype, "width", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTick.prototype, "size", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTick.prototype, "stroke", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/axis/axisTitle.ts
|
|
var AxisTitle = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.caption = new Caption();
|
|
this.enabled = false;
|
|
this.spacing = Caption.SMALL_PADDING;
|
|
this.fontSize = 10 /* SMALLER */;
|
|
this.fontFamily = "sans-serif";
|
|
this.wrapping = "always";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTitle.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTitle.prototype, "text", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTitle.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTitle.prototype, "fontStyle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTitle.prototype, "fontWeight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTitle.prototype, "fontSize", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTitle.prototype, "fontFamily", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTitle.prototype, "color", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTitle.prototype, "wrapping", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisTitle.prototype, "formatter", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/axis/axisUtil.ts
|
|
var NiceMode = /* @__PURE__ */ ((NiceMode3) => {
|
|
NiceMode3[NiceMode3["TickAndDomain"] = 0] = "TickAndDomain";
|
|
NiceMode3[NiceMode3["TicksOnly"] = 1] = "TicksOnly";
|
|
NiceMode3[NiceMode3["Off"] = 2] = "Off";
|
|
return NiceMode3;
|
|
})(NiceMode || {});
|
|
function prepareAxisAnimationContext(axis) {
|
|
const [requestedRangeMin, requestedRangeMax] = findMinMax(axis.range);
|
|
const min = Math.floor(requestedRangeMin);
|
|
const max = Math.ceil(requestedRangeMax);
|
|
return { min, max, visible: min !== max };
|
|
}
|
|
var fullCircle = Math.PI * 2;
|
|
var halfCircle = fullCircle / 2;
|
|
function normaliseEndRotation(start2, end3) {
|
|
const directDistance = Math.abs(end3 - start2);
|
|
if (directDistance < halfCircle) {
|
|
return end3;
|
|
} else if (start2 > end3) {
|
|
return end3 + fullCircle;
|
|
}
|
|
return end3 - fullCircle;
|
|
}
|
|
function prepareAxisAnimationFunctions(ctx) {
|
|
const { min, max } = ctx;
|
|
const outOfBounds = (y) => {
|
|
return y < min || y > max;
|
|
};
|
|
const tick = {
|
|
fromFn(node, datum, status) {
|
|
let { x1, x2, y1, y2 } = node;
|
|
let opacity = node.opacity;
|
|
if (status === "added" || outOfBounds(datum.offset)) {
|
|
({ x1, x2, y1, y2 } = datum);
|
|
opacity = 0;
|
|
}
|
|
return {
|
|
x1,
|
|
x2,
|
|
y1,
|
|
y2,
|
|
opacity,
|
|
phase: NODE_UPDATE_STATE_TO_PHASE_MAPPING[status]
|
|
};
|
|
},
|
|
toFn(_node, datum, status) {
|
|
const { x1, x2, y1, y2 } = datum;
|
|
let opacity = 1;
|
|
if (status === "removed") {
|
|
opacity = 0;
|
|
}
|
|
return { x1, x2, y1, y2, opacity };
|
|
},
|
|
applyFn(node, props) {
|
|
node.setProperties(props);
|
|
node.visible = !outOfBounds(node.y);
|
|
}
|
|
};
|
|
const label = {
|
|
fromFn(node, newDatum, status) {
|
|
const datum = node.previousDatum ?? newDatum;
|
|
let { x, y, rotationCenterX, rotationCenterY, rotation } = datum;
|
|
let opacity = node.opacity;
|
|
if (status === "removed" || outOfBounds(datum.y)) {
|
|
rotation = newDatum.rotation;
|
|
} else if (status === "added" || outOfBounds(node.datum.y)) {
|
|
({ x, y, rotationCenterX, rotationCenterY, rotation } = newDatum);
|
|
opacity = 0;
|
|
}
|
|
return {
|
|
x,
|
|
y,
|
|
rotationCenterX,
|
|
rotationCenterY,
|
|
rotation,
|
|
opacity,
|
|
phase: NODE_UPDATE_STATE_TO_PHASE_MAPPING[status]
|
|
};
|
|
},
|
|
toFn(node, datum, status) {
|
|
const { x, y, rotationCenterX, rotationCenterY } = datum;
|
|
let rotation = 0;
|
|
let opacity = 1;
|
|
if (status === "added") {
|
|
rotation = datum.rotation;
|
|
} else if (status === "removed") {
|
|
opacity = 0;
|
|
rotation = datum.rotation;
|
|
} else {
|
|
rotation = normaliseEndRotation(node.previousDatum?.rotation ?? datum.rotation, datum.rotation);
|
|
}
|
|
return {
|
|
x,
|
|
y,
|
|
rotationCenterX,
|
|
rotationCenterY,
|
|
rotation,
|
|
opacity,
|
|
finish: { rotation: datum.rotation }
|
|
};
|
|
}
|
|
};
|
|
const line = {
|
|
fromFn(node, datum) {
|
|
const { x1, x2, y1, y2 } = node.previousDatum ?? datum;
|
|
return {
|
|
x1,
|
|
x2,
|
|
y1,
|
|
y2,
|
|
phase: NODE_UPDATE_STATE_TO_PHASE_MAPPING["updated"]
|
|
};
|
|
},
|
|
toFn(_node, datum) {
|
|
const { x1, x2, y1, y2 } = datum;
|
|
return { x1, x2, y1, y2 };
|
|
}
|
|
};
|
|
const group = {
|
|
fromFn(node, _datum) {
|
|
const { translationX, translationY } = node;
|
|
return {
|
|
translationX,
|
|
translationY,
|
|
phase: NODE_UPDATE_STATE_TO_PHASE_MAPPING["updated"]
|
|
};
|
|
},
|
|
toFn(_node, datum) {
|
|
const { translationX, translationY } = datum;
|
|
return { translationX, translationY };
|
|
}
|
|
};
|
|
return { tick, line, label, group };
|
|
}
|
|
function resetAxisGroupFn() {
|
|
return (_node, datum) => {
|
|
return {
|
|
translationX: datum.translationX,
|
|
translationY: datum.translationY
|
|
};
|
|
};
|
|
}
|
|
function resetAxisLabelSelectionFn() {
|
|
return (_node, datum) => {
|
|
return {
|
|
x: datum.x,
|
|
y: datum.y,
|
|
rotationCenterX: datum.rotationCenterX,
|
|
rotationCenterY: datum.rotationCenterY,
|
|
rotation: datum.rotation
|
|
};
|
|
};
|
|
}
|
|
function resetAxisLineSelectionFn() {
|
|
return (_node, datum) => {
|
|
const { x1, x2, y1, y2 } = datum;
|
|
return { x1, x2, y1, y2 };
|
|
};
|
|
}
|
|
function resetAxisFillSelectionFn() {
|
|
return (_node, datum) => {
|
|
const { x1, x2, y1, y2 } = datum;
|
|
return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/axis/axis.ts
|
|
var AxisGroupZIndexMap = /* @__PURE__ */ ((AxisGroupZIndexMap3) => {
|
|
AxisGroupZIndexMap3[AxisGroupZIndexMap3["TickLines"] = 0] = "TickLines";
|
|
AxisGroupZIndexMap3[AxisGroupZIndexMap3["AxisLine"] = 1] = "AxisLine";
|
|
AxisGroupZIndexMap3[AxisGroupZIndexMap3["TickLabels"] = 2] = "TickLabels";
|
|
return AxisGroupZIndexMap3;
|
|
})(AxisGroupZIndexMap || {});
|
|
function tickLayoutCacheValid(a, b) {
|
|
return a.domain === b.domain && a.rangeExtent === b.rangeExtent && a.nice[0] === b.nice[0] && a.nice[1] === b.nice[1] && a.gridLength === b.gridLength && a.visibleRange[0] === b.visibleRange[0] && a.visibleRange[1] === b.visibleRange[1] && a.scrollbarKey === b.scrollbarKey && a.initialPrimaryTickCount?.unzoomed === b.initialPrimaryTickCount?.unzoomed && a.initialPrimaryTickCount?.zoomed === b.initialPrimaryTickCount?.zoomed;
|
|
}
|
|
function computeBand(scale2, range3, value) {
|
|
const bandwidth = scale2.bandwidth ?? 0;
|
|
const step = scale2.step ?? 0;
|
|
const offset = (step - bandwidth) / 2;
|
|
const position = scale2.convert(value);
|
|
const start2 = position - offset;
|
|
const end3 = position + bandwidth + offset;
|
|
return [position, clampArray(start2, range3), clampArray(end3, range3)];
|
|
}
|
|
var _Axis = class _Axis {
|
|
constructor(moduleCtx, scale2) {
|
|
this.moduleCtx = moduleCtx;
|
|
this.scale = scale2;
|
|
this.id = "unknown";
|
|
this._crossLines = [];
|
|
this.nice = true;
|
|
this.reverse = false;
|
|
this.interval = new AxisInterval();
|
|
this.dataDomain = { domain: [], clipped: false };
|
|
this.allowNull = false;
|
|
this.title = new AxisTitle();
|
|
this.gridLength = 0;
|
|
/**
|
|
* The distance between the grid ticks and the axis ticks.
|
|
*/
|
|
this.gridPadding = 0;
|
|
/**
|
|
* Is used to avoid collisions between axis labels and series.
|
|
*/
|
|
this.seriesAreaPadding = 0;
|
|
this.layoutConstraints = {
|
|
stacked: true,
|
|
align: "justify",
|
|
width: 100,
|
|
unit: "percent"
|
|
};
|
|
this.boundSeries = [];
|
|
this.includeInvisibleDomains = false;
|
|
this.interactionEnabled = true;
|
|
this.axisGroup = new Group({ name: `${this.id}-axis` });
|
|
// Order is important to apply the correct z-index.
|
|
this.tickLineGroup = this.axisGroup.appendChild(
|
|
new TransformableGroup({ name: `${this.id}-Axis-tick-lines`, zIndex: 0 /* TickLines */ })
|
|
);
|
|
this.tickLabelGroup = this.axisGroup.appendChild(
|
|
new TransformableGroup({ name: `${this.id}-Axis-tick-labels`, zIndex: 2 /* TickLabels */ })
|
|
);
|
|
this.labelGroup = new Group({
|
|
name: `${this.id}-Labels`,
|
|
zIndex: 11 /* SERIES_ANNOTATION */
|
|
});
|
|
this.gridGroup = new TranslatableGroup({ name: `${this.id}-Axis-grid`, zIndex: 2 /* AXIS_GRID */ });
|
|
this.gridFillGroup = this.gridGroup.appendChild(new Group({ name: `${this.id}-gridFills` }));
|
|
this.gridLineGroup = this.gridGroup.appendChild(new Group({ name: `${this.id}-gridLines` }));
|
|
this.crossLineRangeGroup = new TransformableGroup({
|
|
name: `${this.id}-CrossLines-Range`,
|
|
zIndex: 6 /* SERIES_CROSSLINE_RANGE */
|
|
});
|
|
this.crossLineLineGroup = new TransformableGroup({
|
|
name: `${this.id}-CrossLines-Line`,
|
|
zIndex: 10 /* SERIES_CROSSLINE_LINE */
|
|
});
|
|
this.crossLineLabelGroup = new TransformableGroup({
|
|
name: `${this.id}-CrossLines-Label`,
|
|
zIndex: 15 /* SERIES_LABEL */
|
|
});
|
|
this.tickLabelGroupSelection = Selection.select(
|
|
this.tickLabelGroup,
|
|
TransformableText,
|
|
false
|
|
);
|
|
this.line = new AxisLine();
|
|
this.tick = new AxisTick();
|
|
this.gridLine = new AxisGridLine();
|
|
this.label = this.createLabel();
|
|
this.defaultTickMinSpacing = _Axis.defaultTickMinSpacing;
|
|
this.translation = { x: 0, y: 0 };
|
|
this.layout = {
|
|
label: {
|
|
fractionDigits: 0,
|
|
spacing: this.label.spacing,
|
|
format: this.label.format
|
|
},
|
|
labelThickness: 0
|
|
};
|
|
this.axisContext = void 0;
|
|
this.cleanup = new CleanupRegistry();
|
|
// AG-15360 Avoid calling removeTooltip() if no tooltip is shown. This avoid a laggy tooltips caused by interference
|
|
// with SeriesAreaManager's tooltip updates.
|
|
this.isHovering = false;
|
|
this.range = [0, 1];
|
|
this.visibleRange = [0, 1];
|
|
this.animatable = true;
|
|
this.tickLayout = void 0;
|
|
this.formatterBoundSeries = new WeakCache(() => {
|
|
const { direction, boundSeries } = this;
|
|
return deepFreeze(boundSeries.flatMap((series) => series.getFormatterContext(direction)));
|
|
});
|
|
this.moduleMap = new ModuleMap();
|
|
this.range = this.scale.range.slice();
|
|
for (const crossLine of this.crossLines) {
|
|
this.initCrossLine(crossLine);
|
|
}
|
|
this.cleanup.register(
|
|
this.moduleCtx.widgets.containerWidget.addListener("mousemove", (e) => this.onMouseMove(e)),
|
|
this.moduleCtx.widgets.containerWidget.addListener("mouseleave", () => this.endHovering())
|
|
);
|
|
}
|
|
set crossLines(value) {
|
|
const { CrossLineConstructor } = this.constructor;
|
|
for (const crossLine of this._crossLines) {
|
|
this.detachCrossLine(crossLine);
|
|
}
|
|
this._crossLines = value.map((crossLine) => {
|
|
const instance = new CrossLineConstructor();
|
|
instance.set(crossLine);
|
|
return instance;
|
|
});
|
|
for (const crossLine of this._crossLines) {
|
|
this.attachCrossLine(crossLine);
|
|
this.initCrossLine(crossLine);
|
|
}
|
|
}
|
|
get crossLines() {
|
|
return this._crossLines;
|
|
}
|
|
get type() {
|
|
return this.constructor.type ?? "";
|
|
}
|
|
get primaryLabel() {
|
|
return void 0;
|
|
}
|
|
get primaryTick() {
|
|
return void 0;
|
|
}
|
|
isCategoryLike() {
|
|
return false;
|
|
}
|
|
resetAnimation(_phase) {
|
|
}
|
|
onMouseMove(event) {
|
|
const node = this.tickLabelGroup.pickNode(event.currentX, event.currentY);
|
|
const datum = node?.datum;
|
|
const { textUntruncated: title = void 0 } = datum ?? {};
|
|
if (title) {
|
|
this.moduleCtx.tooltipManager.updateTooltip(
|
|
this.id,
|
|
{ canvasX: event.currentX, canvasY: event.currentY, showArrow: false },
|
|
[{ type: "structured", title }]
|
|
);
|
|
this.isHovering = true;
|
|
} else {
|
|
this.endHovering();
|
|
}
|
|
}
|
|
endHovering() {
|
|
if (this.isHovering) {
|
|
this.moduleCtx.tooltipManager.removeTooltip(this.id, void 0, true);
|
|
this.isHovering = false;
|
|
}
|
|
}
|
|
attachCrossLine(crossLine) {
|
|
this.crossLineRangeGroup.appendChild(crossLine.rangeGroup);
|
|
this.crossLineLineGroup.appendChild(crossLine.lineGroup);
|
|
this.crossLineLabelGroup.appendChild(crossLine.labelGroup);
|
|
}
|
|
detachCrossLine(crossLine) {
|
|
crossLine.rangeGroup.remove();
|
|
crossLine.lineGroup.remove();
|
|
crossLine.labelGroup.remove();
|
|
}
|
|
destroy() {
|
|
this.moduleMap.destroy();
|
|
this.cleanup.flush();
|
|
}
|
|
setScaleRange(visibleRange) {
|
|
const { range: rr, scale: scale2 } = this;
|
|
const span = (rr[1] - rr[0]) / (visibleRange[1] - visibleRange[0]);
|
|
const shift = span * visibleRange[0];
|
|
const start2 = rr[0] - shift;
|
|
scale2.range = [start2, start2 + span];
|
|
}
|
|
updateScale() {
|
|
const {
|
|
range: [r0, r1]
|
|
} = this;
|
|
this.setScaleRange(this.visibleRange);
|
|
for (const crossLine of this.crossLines) {
|
|
crossLine.clippedRange = [r0, r1];
|
|
}
|
|
}
|
|
setCrossLinesVisible(visible) {
|
|
this.crossLineRangeGroup.visible = visible;
|
|
this.crossLineLineGroup.visible = visible;
|
|
this.crossLineLabelGroup.visible = visible;
|
|
}
|
|
attachAxis(groups) {
|
|
groups.gridNode.appendChild(this.gridGroup);
|
|
groups.axisNode.appendChild(this.axisGroup);
|
|
groups.labelNode.appendChild(this.labelGroup);
|
|
groups.crossLineRangeNode.appendChild(this.crossLineRangeGroup);
|
|
groups.crossLineLineNode.appendChild(this.crossLineLineGroup);
|
|
groups.crossLineLabelNode.appendChild(this.crossLineLabelGroup);
|
|
}
|
|
detachAxis() {
|
|
this.gridGroup.remove();
|
|
this.axisGroup.remove();
|
|
this.labelGroup.remove();
|
|
this.crossLineRangeGroup.remove();
|
|
this.crossLineLineGroup.remove();
|
|
this.crossLineLabelGroup.remove();
|
|
}
|
|
attachLabel(axisLabelNode) {
|
|
this.labelGroup.append(axisLabelNode);
|
|
}
|
|
/**
|
|
* Checks if a point or an object is in range.
|
|
* @param value A point (or object's starting point).
|
|
* @param tolerance Expands the range on both ends by this amount.
|
|
*/
|
|
inRange(value, tolerance = 0) {
|
|
const [min, max] = findMinMax(this.range);
|
|
return value >= min - tolerance && value <= max + tolerance;
|
|
}
|
|
/**
|
|
* Get a point's overflow on the range, expanded to include the non-visible range.
|
|
* @param value Point
|
|
* @returns Overflow
|
|
*/
|
|
getRangeOverflow(value) {
|
|
const { range: rr, visibleRange: vr } = this;
|
|
const size = (rr[1] - rr[0]) / (vr[1] - vr[0]);
|
|
const [min, max] = findMinMax([rr[0] - size * vr[0], rr[0] - size * vr[0] + size]);
|
|
if (value < min)
|
|
return value - min;
|
|
if (value > max)
|
|
return value - max;
|
|
return 0;
|
|
}
|
|
onGridLengthChange(value, prevValue) {
|
|
if (prevValue ^ value) {
|
|
this.onGridVisibilityChange();
|
|
}
|
|
for (const crossLine of this.crossLines) {
|
|
this.initCrossLine(crossLine);
|
|
}
|
|
}
|
|
onGridVisibilityChange() {
|
|
}
|
|
createLabel() {
|
|
return new AxisLabel();
|
|
}
|
|
/**
|
|
* Creates/removes/updates the scene graph nodes that constitute the axis.
|
|
*/
|
|
update() {
|
|
this.formatterBoundSeries.clear();
|
|
this.updatePosition();
|
|
this.updateSelections();
|
|
this.gridLineGroup.visible = this.gridLine.enabled;
|
|
this.updateLabels();
|
|
this.updateCrossLines();
|
|
}
|
|
getLabelStyles(params, additionalStyles, label = this.label) {
|
|
const defaultStyle = {
|
|
border: label.border,
|
|
color: label.color,
|
|
cornerRadius: label.cornerRadius,
|
|
fill: label.fill,
|
|
fillOpacity: label.fillOpacity,
|
|
fontFamily: label.fontFamily,
|
|
fontSize: label.fontSize,
|
|
fontStyle: label.fontStyle,
|
|
fontWeight: label.fontWeight,
|
|
padding: label.padding,
|
|
spacing: label.spacing
|
|
};
|
|
let stylerOutput;
|
|
if (label.itemStyler) {
|
|
stylerOutput = this.cachedCallWithContext(label.itemStyler, {
|
|
...params,
|
|
...defaultStyle
|
|
});
|
|
}
|
|
const merged = mergeDefaults(stylerOutput, additionalStyles, defaultStyle);
|
|
return {
|
|
border: merged.border,
|
|
color: merged.color,
|
|
cornerRadius: merged.cornerRadius,
|
|
fill: merged.fill,
|
|
fillOpacity: merged.fillOpacity,
|
|
fontFamily: merged.fontFamily,
|
|
fontSize: merged.fontSize,
|
|
fontStyle: merged.fontStyle,
|
|
fontWeight: merged.fontWeight,
|
|
padding: merged.padding,
|
|
spacing: merged.spacing
|
|
};
|
|
}
|
|
getTickSize(tick = this.tick) {
|
|
return tick.enabled ? tick.size : 0;
|
|
}
|
|
getTickSpacing(tick = this.tick) {
|
|
if (!tick.enabled)
|
|
return 0;
|
|
const scrollbar = this.chartLayout?.scrollbars?.[this.id];
|
|
if (!scrollbar?.enabled || scrollbar.placement !== "inner")
|
|
return 0;
|
|
return scrollbar.tickSpacing ?? 0;
|
|
}
|
|
processData() {
|
|
this.invalidateLayoutCache();
|
|
const { includeInvisibleDomains, boundSeries, direction } = this;
|
|
const visibleSeries = includeInvisibleDomains ? boundSeries : boundSeries.filter((s) => s.isEnabled());
|
|
const domains = visibleSeries.map((series) => series.getDomain(direction));
|
|
this.setDomains(...domains);
|
|
}
|
|
getDomainExtentsNice() {
|
|
return [this.nice, this.nice];
|
|
}
|
|
setDomains(...domains) {
|
|
let normalizedDomain;
|
|
let animatable;
|
|
if (domains.length > 0) {
|
|
const result = this.scale.normalizeDomains(...domains);
|
|
normalizedDomain = { domain: result.domain, sortMetadata: { sortOrder: 1 } };
|
|
animatable = result.animatable;
|
|
} else {
|
|
normalizedDomain = { domain: [] };
|
|
animatable = true;
|
|
}
|
|
this.dataDomain = this.normaliseDataDomain(normalizedDomain);
|
|
this.allowNull = this.dataDomain.domain.some(function(v) {
|
|
return v == null;
|
|
});
|
|
if (this.reverse) {
|
|
this.dataDomain.domain.reverse();
|
|
}
|
|
this.animatable = animatable;
|
|
}
|
|
calculateDomain(initialPrimaryTickCount, scrollbarKey = "none") {
|
|
const {
|
|
dataDomain: { domain },
|
|
range: range3,
|
|
scale: scale2,
|
|
gridLength
|
|
} = this;
|
|
const rangeExtent = findRangeExtent(range3);
|
|
const visibleRange = [0, 1];
|
|
const nice = this.getDomainExtentsNice();
|
|
this.updateScale();
|
|
const { unzoomedTickLayoutCache } = this;
|
|
let unzoomedTickLayout;
|
|
if (unzoomedTickLayoutCache == null || !tickLayoutCacheValid(unzoomedTickLayoutCache, {
|
|
domain,
|
|
rangeExtent,
|
|
nice,
|
|
gridLength,
|
|
visibleRange,
|
|
initialPrimaryTickCount,
|
|
scrollbarKey
|
|
})) {
|
|
const scaleRange = scale2.range;
|
|
this.setScaleRange([0, 1]);
|
|
const niceMode = nice.map((n) => n ? 0 /* TickAndDomain */ : 2 /* Off */);
|
|
unzoomedTickLayout = this.calculateTickLayout(domain, niceMode, [0, 1], initialPrimaryTickCount);
|
|
scale2.range = scaleRange;
|
|
this.unzoomedTickLayoutCache = {
|
|
domain,
|
|
rangeExtent,
|
|
nice,
|
|
gridLength,
|
|
visibleRange,
|
|
initialPrimaryTickCount,
|
|
scrollbarKey,
|
|
tickLayout: unzoomedTickLayout
|
|
};
|
|
} else {
|
|
unzoomedTickLayout = unzoomedTickLayoutCache.tickLayout;
|
|
}
|
|
this.updateScale();
|
|
scale2.domain = unzoomedTickLayout.niceDomain;
|
|
return { unzoomedTickLayout, domain: scale2.domain };
|
|
}
|
|
calculateLayout(initialPrimaryTickCount, chartLayout) {
|
|
this.chartLayout = chartLayout;
|
|
const scrollbarKey = this.getScrollbarLayoutCacheKey(chartLayout);
|
|
const { visibleRange } = this;
|
|
const unzoomed = visibleRange[0] === 0 && visibleRange[1] === 1;
|
|
const { unzoomedTickLayout, domain } = this.calculateDomain(initialPrimaryTickCount, scrollbarKey);
|
|
const nice = this.getDomainExtentsNice();
|
|
let tickLayout;
|
|
if (unzoomed) {
|
|
tickLayout = unzoomedTickLayout;
|
|
} else {
|
|
const { range: range3, gridLength } = this;
|
|
const rangeExtent = findRangeExtent(range3);
|
|
const niceMode = nice.map((n) => n ? 1 /* TicksOnly */ : 2 /* Off */);
|
|
const { tickLayoutCache } = this;
|
|
if (tickLayoutCache == null || !tickLayoutCacheValid(tickLayoutCache, {
|
|
domain,
|
|
rangeExtent,
|
|
nice,
|
|
gridLength,
|
|
visibleRange,
|
|
initialPrimaryTickCount,
|
|
scrollbarKey
|
|
})) {
|
|
tickLayout = this.calculateTickLayout(domain, niceMode, visibleRange, initialPrimaryTickCount);
|
|
this.tickLayoutCache = {
|
|
domain,
|
|
rangeExtent,
|
|
nice,
|
|
gridLength,
|
|
visibleRange,
|
|
initialPrimaryTickCount,
|
|
scrollbarKey,
|
|
tickLayout
|
|
};
|
|
} else {
|
|
tickLayout = tickLayoutCache.tickLayout;
|
|
}
|
|
}
|
|
const { rawTickCount: zoomedTickCount = 0, fractionDigits, bbox } = tickLayout;
|
|
const unzoomedTickCount = unzoomedTickLayout.rawTickCount ?? 0;
|
|
const primaryTickCount = zoomedTickCount !== 0 && unzoomedTickCount !== 0 ? { zoomed: zoomedTickCount, unzoomed: unzoomedTickCount } : void 0;
|
|
this.tickLayout = tickLayout.layout;
|
|
this.layout.label = {
|
|
fractionDigits,
|
|
spacing: this.label.spacing,
|
|
format: this.label.format
|
|
};
|
|
this.layoutCrossLines();
|
|
return { primaryTickCount, bbox };
|
|
}
|
|
invalidateLayoutCache() {
|
|
this.unzoomedTickLayoutCache = void 0;
|
|
this.tickLayoutCache = void 0;
|
|
this.tickLayout = void 0;
|
|
}
|
|
getScrollbarLayoutCacheKey(chartLayout) {
|
|
const scrollbar = chartLayout?.scrollbars?.[this.id];
|
|
if (!scrollbar?.enabled)
|
|
return "none";
|
|
return `${scrollbar.placement}:${scrollbar.spacing}:${scrollbar.thickness}:${scrollbar.tickSpacing}`;
|
|
}
|
|
updateCrossLines() {
|
|
const crosslinesVisible = this.hasDefinedDomain() || this.hasVisibleSeries();
|
|
for (const crossLine of this.crossLines) {
|
|
crossLine.update(crosslinesVisible);
|
|
}
|
|
}
|
|
updatePosition() {
|
|
const { crossLineRangeGroup, crossLineLineGroup, crossLineLabelGroup, gridGroup, translation } = this;
|
|
const translationX = Math.floor(translation.x);
|
|
const translationY = Math.floor(translation.y);
|
|
gridGroup.setProperties({ translationX, translationY });
|
|
crossLineRangeGroup.setProperties({ translationX, translationY });
|
|
crossLineLineGroup.setProperties({ translationX, translationY });
|
|
crossLineLabelGroup.setProperties({ translationX, translationY });
|
|
}
|
|
// For formatting (nice rounded) tick values.
|
|
tickFormatter(domain, ticks, primary, inputFractionDigits, inputTimeInterval, dateStyle = "long") {
|
|
const { moduleCtx, label } = this;
|
|
const { formatManager } = moduleCtx;
|
|
const primaryLabel = primary ? this.primaryLabel : void 0;
|
|
const tickFormatParams = this.tickFormatParams(domain, ticks, inputFractionDigits, inputTimeInterval);
|
|
const boundSeries = this.formatterBoundSeries.get();
|
|
let fractionDigits;
|
|
let timeInterval3;
|
|
let truncateDate;
|
|
if (tickFormatParams.type === "number") {
|
|
fractionDigits = tickFormatParams.fractionDigits;
|
|
} else if (tickFormatParams.type === "date") {
|
|
const { unit, step, epoch } = tickFormatParams;
|
|
timeInterval3 = { unit, step, epoch };
|
|
truncateDate = tickFormatParams.truncateDate;
|
|
}
|
|
const f = this.uncachedCallWithContext.bind(this);
|
|
const params = {
|
|
datum: void 0,
|
|
seriesId: void 0,
|
|
legendItemName: void 0,
|
|
key: void 0,
|
|
source: "axis-label",
|
|
property: this.getFormatterProperty(),
|
|
domain,
|
|
boundSeries
|
|
};
|
|
const currentLabel = primaryLabel ?? label;
|
|
const specifier = primary ? label.format : void 0;
|
|
const { allowNull } = this;
|
|
const options = {
|
|
specifier: FormatManager.mergeSpecifiers(primaryLabel?.format, label.format),
|
|
truncateDate,
|
|
allowNull
|
|
};
|
|
return (value, index) => {
|
|
const formatParams = this.datumFormatParams(value, params, fractionDigits, timeInterval3, dateStyle);
|
|
formatParams.value = value;
|
|
return currentLabel.formatValue(f, formatParams, index, { specifier, dateStyle, truncateDate }) ?? formatManager.format(f, formatParams, options) ?? formatManager.defaultFormat(formatParams, options);
|
|
};
|
|
}
|
|
formatDatum(contextProvider, input, source, seriesId, legendItemName, datum, key, domain, label, params, allowNull) {
|
|
if (input == null && !allowNull)
|
|
return "";
|
|
const { moduleCtx, dataDomain } = this;
|
|
domain ?? (domain = dataDomain.domain);
|
|
const { formatManager } = moduleCtx;
|
|
const boundSeries = this.formatterBoundSeries.get();
|
|
let inputFractionDigits;
|
|
switch (source) {
|
|
case "crosshair":
|
|
case "annotation-label":
|
|
inputFractionDigits = this.layout.label.fractionDigits + 1;
|
|
break;
|
|
case "series-label":
|
|
inputFractionDigits = 2;
|
|
break;
|
|
case "tooltip":
|
|
inputFractionDigits = 3;
|
|
break;
|
|
case "legend-label":
|
|
inputFractionDigits = void 0;
|
|
break;
|
|
}
|
|
const formatParams = this.datumFormatParams(
|
|
input,
|
|
{
|
|
source,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key,
|
|
property: this.getFormatterProperty(),
|
|
domain,
|
|
boundSeries
|
|
},
|
|
inputFractionDigits,
|
|
void 0,
|
|
"long"
|
|
);
|
|
const { type, value } = formatParams;
|
|
const f = this.createCallWithContext(contextProvider);
|
|
const result = label?.formatValue(f, type, value, params ?? formatParams) ?? formatManager.format(f, formatParams, { allowNull }) ?? this.label.formatValue(f, formatParams, Number.NaN) ?? formatManager.defaultFormat(formatParams);
|
|
return isArray(result) ? result : String(result);
|
|
}
|
|
getBBox() {
|
|
return this.axisGroup.getBBox();
|
|
}
|
|
initCrossLine(crossLine) {
|
|
crossLine.scale = this.scale;
|
|
crossLine.gridLength = this.gridLength;
|
|
}
|
|
hasVisibleSeries() {
|
|
return this.boundSeries.some((s) => s.isEnabled());
|
|
}
|
|
clipTickLines(x, y, width2, height2) {
|
|
this.tickLineGroup.setClipRect(new BBox(x, y, width2, height2));
|
|
}
|
|
clipGrid(x, y, width2, height2) {
|
|
this.gridGroup.setClipRect(new BBox(x, y, width2, height2));
|
|
}
|
|
getFormatterProperty() {
|
|
const { direction, boundSeries } = this;
|
|
let resolvedDirection = direction;
|
|
for (const series of boundSeries) {
|
|
const seriesResolvedDirection = series.resolveKeyDirection(direction);
|
|
if (seriesResolvedDirection !== direction) {
|
|
resolvedDirection = seriesResolvedDirection;
|
|
break;
|
|
}
|
|
}
|
|
return resolvedDirection;
|
|
}
|
|
getTitleFormatterParams(domain) {
|
|
const { direction } = this;
|
|
const boundSeries = this.formatterBoundSeries.get();
|
|
return { domain, direction, boundSeries, defaultValue: this.title?.text };
|
|
}
|
|
normaliseDataDomain(d) {
|
|
return { domain: [...d.domain], clipped: false };
|
|
}
|
|
getLayoutTranslation() {
|
|
return this.translation;
|
|
}
|
|
getLayoutState() {
|
|
return {
|
|
id: this.id,
|
|
rect: this.getBBox(),
|
|
translation: this.getLayoutTranslation(),
|
|
gridPadding: this.gridPadding,
|
|
seriesAreaPadding: this.seriesAreaPadding,
|
|
tickSize: this.getTickSize(),
|
|
direction: this.direction,
|
|
domain: this.dataDomain.domain,
|
|
scale: this.scale,
|
|
...this.layout
|
|
};
|
|
}
|
|
getModuleMap() {
|
|
return this.moduleMap;
|
|
}
|
|
getUpdateTypeOnResize() {
|
|
return 5 /* PERFORM_LAYOUT */;
|
|
}
|
|
createModuleContext() {
|
|
this.axisContext ?? (this.axisContext = this.createAxisContext());
|
|
return { ...this.moduleCtx, parent: this.axisContext };
|
|
}
|
|
createAxisContext() {
|
|
const { scale: scale2 } = this;
|
|
return {
|
|
axisId: this.id,
|
|
scale: this.scale,
|
|
direction: this.direction,
|
|
continuous: ContinuousScale.is(scale2) || DiscreteTimeScale.is(scale2),
|
|
getCanvasBounds: () => {
|
|
return Transformable.toCanvas(this.axisGroup);
|
|
},
|
|
seriesKeyProperties: () => this.boundSeries.reduce((keys, series) => {
|
|
const seriesKeys = series.getKeyProperties(this.direction);
|
|
for (const key of seriesKeys) {
|
|
keys.add(key);
|
|
}
|
|
return keys;
|
|
}, /* @__PURE__ */ new Set()),
|
|
seriesIds: () => this.boundSeries.map((series) => series.id),
|
|
scaleInvert: (val) => scale2.invert(val, true),
|
|
scaleInvertNearest: (val) => scale2.invert(val, true),
|
|
formatScaleValue: (value, source, label) => {
|
|
const { allowNull } = this;
|
|
return this.formatDatum(
|
|
void 0,
|
|
value,
|
|
source,
|
|
void 0,
|
|
void 0,
|
|
void 0,
|
|
void 0,
|
|
void 0,
|
|
label,
|
|
void 0,
|
|
allowNull
|
|
);
|
|
},
|
|
attachLabel: (node) => this.attachLabel(node),
|
|
inRange: (value, tolerance) => this.inRange(value, tolerance),
|
|
getRangeOverflow: (value) => this.getRangeOverflow(value),
|
|
pickBand: (point) => this.pickBand(point),
|
|
measureBand: (value) => this.measureBand(value)
|
|
};
|
|
}
|
|
pickBand(point) {
|
|
if (!BandScale.is(this.scale)) {
|
|
return;
|
|
}
|
|
const { scale: scale2, range: range3, id } = this;
|
|
const value = scale2.invert(this.isVertical() ? point.y : point.x, true);
|
|
const [position, start2, end3] = computeBand(scale2, range3, value);
|
|
return { id, value, band: [start2, end3], position };
|
|
}
|
|
measureBand(value) {
|
|
if (!BandScale.is(this.scale)) {
|
|
return;
|
|
}
|
|
const [, start2, end3] = computeBand(this.scale, this.range, value);
|
|
return { band: [start2, end3] };
|
|
}
|
|
isVertical() {
|
|
return this.direction === "y" /* Y */;
|
|
}
|
|
isReversed() {
|
|
return this.reverse;
|
|
}
|
|
cachedCallWithContext(fn, params) {
|
|
const { callbackCache, chartService } = this.moduleCtx;
|
|
return callbackCache.call([this, chartService], fn, params);
|
|
}
|
|
uncachedCallWithContext(fn, params) {
|
|
const { chartService } = this.moduleCtx;
|
|
return callWithContext([this, chartService], fn, params);
|
|
}
|
|
createCallWithContext(contextProvider) {
|
|
const { chartService } = this.moduleCtx;
|
|
return (fn, params) => callWithContext([contextProvider, this, chartService], fn, params);
|
|
}
|
|
};
|
|
_Axis.defaultTickMinSpacing = 50;
|
|
_Axis.CrossLineConstructor = CartesianCrossLine;
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Axis.prototype, "nice", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Axis.prototype, "reverse", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Axis.prototype, "interval", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Axis.prototype, "title", 2);
|
|
__decorateClass([
|
|
ObserveChanges((target, value, oldValue) => target.onGridLengthChange(value, oldValue))
|
|
], _Axis.prototype, "gridLength", 2);
|
|
var Axis = _Axis;
|
|
|
|
// packages/ag-charts-community/src/chart/axis/cartesianAxisLabel.ts
|
|
var CartesianAxisLabel = class extends AxisLabel {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.autoRotateAngle = 335;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianAxisLabel.prototype, "autoRotate", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianAxisLabel.prototype, "autoRotateAngle", 2);
|
|
|
|
// 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 = clamp(0, Math.floor((visibleRange?.[0] ?? 0) * bands.length), bands.length);
|
|
const vt1 = clamp(0, Math.ceil((visibleRange?.[1] ?? 1) * bands.length), bands.length);
|
|
const i0 = Math.floor((vt0 - inset) / step) * step + inset;
|
|
const i1 = Math.ceil((vt1 - inset) / step) * step + inset;
|
|
const ticks = [];
|
|
for (let i = i0; i < i1; i += step) {
|
|
if (i >= 0 && i < bands.length) {
|
|
ticks.push(bands[i]);
|
|
}
|
|
}
|
|
let firstTickIndex = ticks.length > 0 ? this.findIndex(ticks[0]) : void 0;
|
|
if (firstTickIndex != null) {
|
|
firstTickIndex = Math.floor((firstTickIndex - inset) / step);
|
|
}
|
|
return { ticks, count: void 0, firstTickIndex };
|
|
}
|
|
findIndex(value) {
|
|
const { index, indexInitialized } = this;
|
|
if (!indexInitialized) {
|
|
const { domain } = this;
|
|
for (let i = 0; i < domain.length; i++) {
|
|
index.set(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/timeScale.ts
|
|
var sunday = new Date(1970, 0, 4);
|
|
var TimeScale = class _TimeScale extends ContinuousScale {
|
|
constructor() {
|
|
super([], [0, 1]);
|
|
this.type = "time";
|
|
}
|
|
static is(value) {
|
|
return value instanceof _TimeScale;
|
|
}
|
|
toDomain(d) {
|
|
return new Date(d);
|
|
}
|
|
convert(value, options) {
|
|
return super.convert(typeof value === "number" ? value : value?.valueOf() ?? Number.NaN, options);
|
|
}
|
|
invert(value) {
|
|
return new Date(super.invert(value));
|
|
}
|
|
niceDomain(ticks, domain = this.domain) {
|
|
if (domain.length < 2)
|
|
return [];
|
|
let [d0, d1] = domain;
|
|
const maxAttempts = 4;
|
|
const availableRange = this.getPixelRange();
|
|
for (let i = 0; i < maxAttempts; i++) {
|
|
const [n0, n1] = updateNiceDomainIteration(d0, d1, ticks, availableRange);
|
|
if (dateToNumber(d0) === dateToNumber(n0) && dateToNumber(d1) === dateToNumber(n1)) {
|
|
break;
|
|
}
|
|
d0 = n0;
|
|
d1 = n1;
|
|
}
|
|
return [d0, d1];
|
|
}
|
|
/**
|
|
* Returns uniformly-spaced dates that represent the scale's domain.
|
|
*/
|
|
ticks(params, domain = this.domain, visibleRange = [0, 1], { extend = false } = {}) {
|
|
const { nice, interval, tickCount = ContinuousScale.defaultTickCount, minTickCount, maxTickCount } = params;
|
|
if (domain.length < 2)
|
|
return;
|
|
const timestamps = domain.map(dateToNumber);
|
|
const start2 = timestamps[0];
|
|
const stop = timestamps.at(-1);
|
|
if (interval != null) {
|
|
const availableRange = this.getPixelRange();
|
|
return {
|
|
ticks: getDateTicksForInterval({ start: start2, stop, interval, availableRange, visibleRange, extend }) ?? getDefaultDateTicks({ start: start2, stop, tickCount, minTickCount, maxTickCount, visibleRange, extend }),
|
|
count: void 0
|
|
};
|
|
} else if (nice.every(Boolean) && tickCount === 2) {
|
|
return { ticks: domain, count: void 0 };
|
|
} else if (nice.every(Boolean) && tickCount === 1) {
|
|
return { ticks: domain.slice(0, 1), count: void 0 };
|
|
}
|
|
const timeInterval3 = getTickTimeInterval(start2, stop, tickCount, minTickCount, maxTickCount, {
|
|
weekStart: sunday
|
|
});
|
|
if (timeInterval3 == null)
|
|
return;
|
|
const ticks = intervalRange(timeInterval3, new Date(start2), new Date(stop), { visibleRange, extend });
|
|
const firstTickIndex = intervalRangeStartIndex(timeInterval3, new Date(start2), new Date(stop), {
|
|
visibleRange,
|
|
extend
|
|
});
|
|
return {
|
|
ticks,
|
|
count: void 0,
|
|
firstTickIndex,
|
|
timeInterval: timeInterval3
|
|
};
|
|
}
|
|
};
|
|
function getDefaultDateTicks({
|
|
start: start2,
|
|
stop,
|
|
tickCount,
|
|
minTickCount,
|
|
maxTickCount,
|
|
visibleRange,
|
|
extend
|
|
}) {
|
|
const t = getTickTimeInterval(start2, stop, tickCount, minTickCount, maxTickCount, { weekStart: sunday });
|
|
return t ? intervalRange(t, new Date(start2), new Date(stop), { visibleRange, extend }) : [];
|
|
}
|
|
function getDateTicksForInterval({
|
|
start: start2,
|
|
stop,
|
|
interval,
|
|
availableRange,
|
|
visibleRange,
|
|
extend
|
|
}) {
|
|
if (!interval) {
|
|
return [];
|
|
}
|
|
if (isPlainObject(interval) || typeof interval === "string") {
|
|
const ticks2 = intervalRange(interval, new Date(start2), new Date(stop), { visibleRange, extend });
|
|
if (isDenseInterval(ticks2.length, availableRange)) {
|
|
return;
|
|
}
|
|
return ticks2;
|
|
}
|
|
const absInterval = Math.abs(interval);
|
|
if (isDenseInterval(Math.abs(stop - start2) / absInterval, availableRange))
|
|
return;
|
|
const tickInterval = TickIntervals.findLast((t) => absInterval % t.duration === 0);
|
|
if (tickInterval) {
|
|
const { timeInterval: timeInterval3, step, duration } = tickInterval;
|
|
const alignedInterval = {
|
|
...timeInterval3,
|
|
step: step * intervalStep(timeInterval3) * Math.round(absInterval / duration),
|
|
epoch: defaultEpoch(timeInterval3, { weekStart: sunday })
|
|
};
|
|
return intervalRange(alignedInterval, new Date(start2), new Date(stop), { visibleRange, extend });
|
|
}
|
|
let date2 = new Date(Math.min(start2, stop));
|
|
const stopDate = new Date(Math.max(start2, stop));
|
|
const ticks = [];
|
|
while (date2 <= stopDate) {
|
|
ticks.push(date2);
|
|
date2 = new Date(date2);
|
|
date2.setMilliseconds(date2.getMilliseconds() + absInterval);
|
|
}
|
|
return ticks;
|
|
}
|
|
function updateNiceDomainIteration(d0, d1, ticks, availableRange) {
|
|
const { interval } = ticks;
|
|
const start2 = Math.min(dateToNumber(d0), dateToNumber(d1));
|
|
const stop = Math.max(dateToNumber(d0), dateToNumber(d1));
|
|
let i;
|
|
if (isPlainObject(interval) || typeof interval === "string") {
|
|
i = interval;
|
|
} else {
|
|
let tickCount;
|
|
if (typeof interval === "number") {
|
|
tickCount = (stop - start2) / Math.max(interval, 1);
|
|
if (isDenseInterval(tickCount, availableRange)) {
|
|
tickCount = void 0;
|
|
}
|
|
}
|
|
tickCount ?? (tickCount = ticks.tickCount ?? ContinuousScale.defaultTickCount);
|
|
i = getTickTimeInterval(start2, stop, tickCount, ticks.minTickCount, ticks.maxTickCount, { weekStart: sunday });
|
|
}
|
|
if (i == null)
|
|
return [d0, d1];
|
|
const domain = intervalRange(i, new Date(start2), new Date(stop), { extend: true });
|
|
if (domain == null || domain.length < 2)
|
|
return [d0, d1];
|
|
const r0 = domain[0];
|
|
const r1 = domain.at(-1);
|
|
return d0 <= d1 ? [r0, r1] : [r1, r0];
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scale/ordinalTimeScale.ts
|
|
var APPROXIMATE_THRESHOLD2 = 1e3;
|
|
var OrdinalTimeScale = class _OrdinalTimeScale extends DiscreteTimeScale {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "ordinal-time";
|
|
this.defaultTickCount = ContinuousScale.defaultTickCount;
|
|
this._domain = [];
|
|
this.isReversed = false;
|
|
}
|
|
static is(value) {
|
|
return value instanceof _OrdinalTimeScale;
|
|
}
|
|
set domain(domain) {
|
|
if (domain === this._domain)
|
|
return;
|
|
this.invalid = true;
|
|
this._domain = domain;
|
|
this._bands = void 0;
|
|
this._numericBands = void 0;
|
|
this._uniformityCache = void 0;
|
|
this.isReversed = domainReversed(domain);
|
|
}
|
|
get domain() {
|
|
return this._domain;
|
|
}
|
|
get bands() {
|
|
this._bands ?? (this._bands = this.isReversed ? this.domain.slice().reverse() : this.domain);
|
|
return this._bands;
|
|
}
|
|
get numericBands() {
|
|
this._numericBands ?? (this._numericBands = this.bands.map((d) => d.valueOf()));
|
|
return this._numericBands;
|
|
}
|
|
getUniformityCache(visibleRange) {
|
|
const { bands } = this;
|
|
const n = bands.length;
|
|
if (!visibleRange || visibleRange[0] === 0 && visibleRange[1] === 1) {
|
|
if (n > APPROXIMATE_THRESHOLD2 && this._uniformityCache === void 0) {
|
|
this._uniformityCache = checkUniformityBySampling(bands);
|
|
}
|
|
return this._uniformityCache;
|
|
}
|
|
const startIdx = Math.floor(visibleRange[0] * n);
|
|
const endIdx = Math.min(Math.ceil(visibleRange[1] * n), n - 1);
|
|
return checkUniformityBySampling(bands, startIdx, endIdx);
|
|
}
|
|
normalizeDomains(...domains) {
|
|
const nonEmptyDomains = domains.filter((d) => d.domain.length > 0);
|
|
if (nonEmptyDomains.length === 0) {
|
|
return { domain: [], animatable: false };
|
|
}
|
|
const firstDomain = nonEmptyDomains[0].domain;
|
|
const allSame = nonEmptyDomains.every((d) => d.domain === firstDomain);
|
|
if (nonEmptyDomains.length === 1 || allSame) {
|
|
const input = nonEmptyDomains[0];
|
|
let domain = input.domain;
|
|
let sortOrder;
|
|
let isUnique = false;
|
|
if (input.sortMetadata?.sortOrder === void 0) {
|
|
sortOrder = datesSortOrder(domain);
|
|
} else {
|
|
sortOrder = input.sortMetadata.sortOrder;
|
|
isUnique = input.sortMetadata.isUnique ?? false;
|
|
}
|
|
if (sortOrder === -1) {
|
|
domain = domain.slice().reverse();
|
|
} else if (sortOrder == null) {
|
|
domain = isUnique ? domain.slice().sort((a, b) => a.valueOf() - b.valueOf()) : sortAndUniqueDates(domain.slice());
|
|
}
|
|
return { domain, animatable: true };
|
|
}
|
|
return {
|
|
domain: sortAndUniqueDates(nonEmptyDomains.map((d) => d.domain).flat()),
|
|
animatable: true
|
|
};
|
|
}
|
|
ticks(params, domain, visibleRange = [0, 1], { extend = false, dropInitial = false } = {}) {
|
|
const { interval, maxTickCount, tickCount = maxTickCount } = params;
|
|
const { bands, reversed } = this;
|
|
if (!bands.length)
|
|
return;
|
|
if (reversed) {
|
|
visibleRange = [1 - visibleRange[1], 1 - visibleRange[0]];
|
|
}
|
|
this.refresh();
|
|
if (interval == null) {
|
|
const { ticks: ticks2, tickOffset, tickEvery } = this.getDefaultTicks(domain, tickCount, visibleRange, extend);
|
|
let firstTickIndex = ticks2.length > 0 ? this.findIndex(ticks2[0]) : void 0;
|
|
firstTickIndex = firstTickIndex == null ? void 0 : Math.floor((firstTickIndex - tickOffset) / tickEvery);
|
|
return { ticks: ticks2, count: void 0, firstTickIndex };
|
|
}
|
|
let start2;
|
|
let stop;
|
|
if (domain && domain.length >= 2) {
|
|
start2 = domain[0].valueOf();
|
|
stop = domain.at(-1).valueOf();
|
|
} else {
|
|
start2 = bands[0].valueOf();
|
|
stop = bands.at(-1).valueOf();
|
|
}
|
|
const [r0, r1] = this.range;
|
|
const availableRange = Math.abs(r1 - r0);
|
|
const dateTicks = getDateTicksForInterval({ start: start2, stop, interval, availableRange, visibleRange, extend }) ?? this.getDefaultTicks(domain, tickCount, visibleRange, extend).ticks;
|
|
const ticks = [];
|
|
let lastIndex = -1;
|
|
for (const dateTick of dateTicks) {
|
|
const index = this.findIndex(dateTick, 1 /* Trailing */) ?? -1;
|
|
const duplicated = index === lastIndex;
|
|
lastIndex = index;
|
|
if (!(dropInitial && index === 0) && index !== -1 && !duplicated) {
|
|
ticks.push(bands[index]);
|
|
}
|
|
}
|
|
return { ticks, count: void 0, firstTickIndex: void 0 };
|
|
}
|
|
stepTicks(bandStep, domain, visibleRange = [0, 1], dropLast = true) {
|
|
const bandIndices = domain ? this.bandDomainIndices(domain) : void 0;
|
|
const ticks = this.ticksEvery(bandIndices, visibleRange, bandStep, 0, false);
|
|
const lastTick = ticks.at(-1);
|
|
const lastBandIndex = dropLast && bandStep > 1 ? bandIndices?.[1] : void 0;
|
|
const lastTickIndex = lastBandIndex != null && lastTick != null ? this.findIndex(lastTick) : void 0;
|
|
if (lastTickIndex != null && lastBandIndex != null && lastBandIndex - lastTickIndex < bandStep) {
|
|
ticks.pop();
|
|
}
|
|
return ticks;
|
|
}
|
|
bandCount(visibleRange = [0, 1]) {
|
|
const { domain } = this;
|
|
const startIndex = Math.floor(visibleRange[0] * domain.length);
|
|
const endIndex = Math.ceil(visibleRange[1] * domain.length);
|
|
return endIndex - startIndex;
|
|
}
|
|
getDefaultTicks(domain, maxTickCount, visibleRange, extend) {
|
|
const { bands } = this;
|
|
const tickEvery = Math.ceil(bands.length / maxTickCount);
|
|
const tickOffset = Math.floor(tickEvery / 2);
|
|
const bandIndices = domain ? this.bandDomainIndices(domain) : void 0;
|
|
return {
|
|
ticks: this.ticksEvery(bandIndices, visibleRange, tickEvery, tickOffset, extend),
|
|
tickOffset,
|
|
tickEvery
|
|
};
|
|
}
|
|
bandDomainIndices(domain) {
|
|
const isReversed = domainReversed(domain);
|
|
const i0 = this.findIndex(domain[isReversed ? domain.length - 1 : 0], 1 /* Trailing */) ?? 0;
|
|
const i1 = this.findIndex(domain[isReversed ? 0 : domain.length - 1], 1 /* Trailing */) ?? this.bands.length - 1;
|
|
return [i0, i1];
|
|
}
|
|
ticksEvery([i0, i1] = [0, this.bands.length], visibleRange, tickEvery, tickOffset, extend) {
|
|
const { bands } = this;
|
|
const offset = i0;
|
|
const span = i1 - i0 + 1;
|
|
let startIndex = offset + Math.floor(visibleRange[0] * span);
|
|
let endIndex = offset + Math.ceil(visibleRange[1] * span);
|
|
if (extend) {
|
|
startIndex -= tickEvery;
|
|
endIndex += tickEvery;
|
|
}
|
|
startIndex = Math.max(startIndex, 0);
|
|
endIndex = Math.min(endIndex, bands.length);
|
|
let ticks;
|
|
if (tickEvery <= 1) {
|
|
ticks = bands.slice(startIndex, endIndex);
|
|
} else {
|
|
ticks = [];
|
|
for (let index = startIndex; index < endIndex; index += 1) {
|
|
if ((index - offset + tickOffset) % tickEvery === 0) {
|
|
ticks.push(bands[index]);
|
|
}
|
|
}
|
|
}
|
|
return ticks;
|
|
}
|
|
};
|
|
function domainReversed(domain) {
|
|
return domain.length > 0 && domain[0] > domain.at(-1);
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scale/unitTimeScale.ts
|
|
var APPROXIMATE_THRESHOLD3 = 1e3;
|
|
var MAX_BANDS = 5e7;
|
|
var UnitTimeScale = class _UnitTimeScale extends DiscreteTimeScale {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "unit-time";
|
|
this.defaultTickCount = 12;
|
|
this._domain = [];
|
|
this._bands = void 0;
|
|
}
|
|
static is(value) {
|
|
return value instanceof _UnitTimeScale;
|
|
}
|
|
static supportsInterval(domain, interval) {
|
|
return supportsInterval(domain, interval);
|
|
}
|
|
set domain(domain) {
|
|
if (domain === this._domain)
|
|
return;
|
|
this._domain = domain;
|
|
this._bands = void 0;
|
|
this._numericBands = void 0;
|
|
this._uniformityCache = void 0;
|
|
this._domainBoundaries = void 0;
|
|
this._bandRangeCache = void 0;
|
|
this._encodedBands = void 0;
|
|
this._encodingParams = void 0;
|
|
this._linearParams = void 0;
|
|
}
|
|
get domain() {
|
|
return this._domain;
|
|
}
|
|
get interval() {
|
|
return this._interval;
|
|
}
|
|
set interval(interval) {
|
|
if (this._interval === interval)
|
|
return;
|
|
this._interval = interval;
|
|
this._bands = void 0;
|
|
this._numericBands = void 0;
|
|
this._uniformityCache = void 0;
|
|
this._domainBoundaries = void 0;
|
|
this._bandRangeCache = void 0;
|
|
this._encodedBands = void 0;
|
|
this._encodingParams = void 0;
|
|
this._linearParams = void 0;
|
|
}
|
|
get bands() {
|
|
if (this._bands === void 0) {
|
|
this.ensureEncodedBands();
|
|
if (this._encodedBands != null && this._encodingParams != null) {
|
|
const params = this._encodingParams;
|
|
this._bands = this._encodedBands.map((e) => decodeIntervalValue(e, params));
|
|
} else {
|
|
this._bands = [];
|
|
}
|
|
}
|
|
return this._bands;
|
|
}
|
|
get numericBands() {
|
|
if (this._numericBands === void 0) {
|
|
this.ensureEncodedBands();
|
|
if (this._encodedBands != null && this._encodingParams != null) {
|
|
const params = this._encodingParams;
|
|
this._numericBands = this._encodedBands.map((e) => encodedToTimestamp(e, params));
|
|
} else {
|
|
this._numericBands = [];
|
|
}
|
|
}
|
|
return this._numericBands;
|
|
}
|
|
/**
|
|
* Ensure encoded bands are computed. This is the numeric-first optimization:
|
|
* we compute just the encoded values (cheap numbers) and defer Date creation.
|
|
*/
|
|
ensureEncodedBands() {
|
|
if (this._encodedBands !== void 0)
|
|
return;
|
|
const { domain, interval } = this;
|
|
if (domain.length < 2 || interval == null) {
|
|
this._encodedBands = [];
|
|
return;
|
|
}
|
|
const bandRange = this.getCachedBandRange();
|
|
if (bandRange == null) {
|
|
this._encodedBands = [];
|
|
return;
|
|
}
|
|
const [start2, stop] = bandRange;
|
|
const rangeParams = { visibleRange: [0, 1], extend: false };
|
|
if (intervalRangeCount(interval, start2, stop, rangeParams) > MAX_BANDS) {
|
|
logger_exports.warnOnce(`the configured unit results in too many bands, ignoring. Supply a larger unit.`);
|
|
this._encodedBands = [];
|
|
return;
|
|
}
|
|
const { encodedValues, encodingParams } = intervalRangeNumeric(interval, start2, stop, rangeParams);
|
|
this._encodedBands = encodedValues;
|
|
this._encodingParams = encodingParams;
|
|
}
|
|
/** Override to return band count without triggering Date materialization */
|
|
getBandCountForUpdate() {
|
|
this.ensureEncodedBands();
|
|
return this._encodedBands?.length ?? 0;
|
|
}
|
|
getUniformityCache(visibleRange) {
|
|
const n = this.getBandCountForUpdate();
|
|
if (!visibleRange || visibleRange[0] === 0 && visibleRange[1] === 1) {
|
|
if (n > APPROXIMATE_THRESHOLD3 && this._uniformityCache === void 0) {
|
|
this.ensureEncodedBands();
|
|
if (this._encodingParams != null && this._encodedBands != null && this._encodedBands.length >= 2) {
|
|
const t0 = encodedToTimestamp(this._encodedBands[0], this._encodingParams);
|
|
const t1 = encodedToTimestamp(this._encodedBands[1], this._encodingParams);
|
|
this._uniformityCache = { isUniform: true, interval: t1 - t0 };
|
|
} else {
|
|
this._uniformityCache = { isUniform: false };
|
|
}
|
|
}
|
|
return this._uniformityCache;
|
|
}
|
|
this.ensureEncodedBands();
|
|
if (this._encodingParams != null && this._encodedBands != null && this._encodedBands.length >= 2) {
|
|
const t0 = encodedToTimestamp(this._encodedBands[0], this._encodingParams);
|
|
const t1 = encodedToTimestamp(this._encodedBands[1], this._encodingParams);
|
|
return { isUniform: true, interval: t1 - t0 };
|
|
}
|
|
return { isUniform: false };
|
|
}
|
|
normalizeDomains(...domains) {
|
|
return normalizeContinuousDomains(...domains);
|
|
}
|
|
getCachedBandRange() {
|
|
const { domain, interval } = this;
|
|
if (domain.length < 2 || interval == null)
|
|
return void 0;
|
|
this._bandRangeCache ?? (this._bandRangeCache = {
|
|
start: intervalFloor(interval, domain[0]),
|
|
stop: intervalFloor(interval, domain[1])
|
|
});
|
|
return [this._bandRangeCache.start, this._bandRangeCache.stop];
|
|
}
|
|
getDomainBoundaries() {
|
|
const { interval } = this;
|
|
if (interval == null)
|
|
return void 0;
|
|
if (this._domainBoundaries === void 0) {
|
|
const bandRange = this.getCachedBandRange();
|
|
if (bandRange == null)
|
|
return void 0;
|
|
const [start2, stop] = bandRange;
|
|
const d0 = Math.min(start2.valueOf(), stop.valueOf());
|
|
const d1 = Math.max(start2.valueOf(), stop.valueOf());
|
|
const dNext = intervalNext(interval, new Date(d1)).valueOf();
|
|
this._domainBoundaries = { d0, dNext };
|
|
}
|
|
return this._domainBoundaries;
|
|
}
|
|
/** Get linear params for O(1) index calculation and scaling metadata */
|
|
getLinearParams() {
|
|
if (this._linearParams === void 0) {
|
|
this.ensureEncodedBands();
|
|
if (this._encodedBands != null && this._encodingParams != null && this._encodedBands.length >= 2) {
|
|
const firstBandTime = encodedToTimestamp(this._encodedBands[0], this._encodingParams);
|
|
const secondBandTime = encodedToTimestamp(this._encodedBands[1], this._encodingParams);
|
|
this._linearParams = {
|
|
firstBandTime,
|
|
intervalMs: secondBandTime - firstBandTime
|
|
};
|
|
}
|
|
}
|
|
return this._linearParams;
|
|
}
|
|
/** Check if current encoding uses a linear unit (exact arithmetic, no DST issues) */
|
|
isLinearUnit() {
|
|
const unit = this._encodingParams?.unit;
|
|
return unit === "millisecond" || unit === "second" || unit === "minute" || unit === "hour";
|
|
}
|
|
/**
|
|
* O(1) findIndex for uniform bands.
|
|
* For linear units (ms/sec/min/hour), uses pure arithmetic without verification.
|
|
* For non-linear units (day/month/year), verifies against actual band values.
|
|
*/
|
|
findIndex(value, alignment = 0 /* Leading */) {
|
|
if (value == null)
|
|
return void 0;
|
|
const n = this.getBandCountForUpdate();
|
|
if (n === 0)
|
|
return void 0;
|
|
if (n === 1)
|
|
return 0;
|
|
const linearParams = this.getLinearParams();
|
|
if (linearParams == null || linearParams.intervalMs === 0) {
|
|
return super.findIndex(value, alignment);
|
|
}
|
|
const { firstBandTime, intervalMs } = linearParams;
|
|
const target = value.valueOf();
|
|
const rawIndex = (target - firstBandTime) / intervalMs;
|
|
let index = alignment === 1 /* Trailing */ ? Math.ceil(rawIndex) : Math.floor(rawIndex);
|
|
index = Math.max(0, Math.min(index, n - 1));
|
|
if (this.isLinearUnit()) {
|
|
if (alignment === 1 /* Trailing */) {
|
|
const bandTime = firstBandTime + index * intervalMs;
|
|
if (bandTime < target && index === n - 1)
|
|
return void 0;
|
|
} else {
|
|
const bandTime = firstBandTime + index * intervalMs;
|
|
if (bandTime > target && index === 0)
|
|
return void 0;
|
|
}
|
|
return index;
|
|
}
|
|
const numericBands = this.numericBands;
|
|
if (alignment === 1 /* Trailing */) {
|
|
while (index > 0 && numericBands[index - 1] >= target)
|
|
index--;
|
|
while (index < n - 1 && numericBands[index] < target)
|
|
index++;
|
|
if (numericBands[index] < target)
|
|
return void 0;
|
|
} else {
|
|
while (index < n - 1 && numericBands[index + 1] <= target)
|
|
index++;
|
|
while (index > 0 && numericBands[index] > target)
|
|
index--;
|
|
if (numericBands[index] > target)
|
|
return void 0;
|
|
}
|
|
return index;
|
|
}
|
|
/**
|
|
* Optimized convert for UnitTimeScale with O(1) boundary checks.
|
|
* Uses linear params for fast bounds checking while delegating actual
|
|
* conversion to parent for accuracy in edge cases.
|
|
*/
|
|
convert(value, options) {
|
|
this.refresh();
|
|
if (!(value instanceof Date))
|
|
value = new Date(value);
|
|
const { domain, interval } = this;
|
|
if (domain.length < 2)
|
|
return Number.NaN;
|
|
if (options?.clamp !== true && interval != null) {
|
|
const boundaries = this.getDomainBoundaries();
|
|
if (boundaries != null) {
|
|
const t = value.valueOf();
|
|
if (t < boundaries.d0 || t >= boundaries.dNext)
|
|
return Number.NaN;
|
|
}
|
|
}
|
|
return super.convert(value, options);
|
|
}
|
|
calculateBands(domain, visibleRange, extend = false) {
|
|
if (domain === this.domain && visibleRange[0] === 0 && visibleRange[1] === 1 && !extend && this._bands != null) {
|
|
return { bands: this._bands, firstBandIndex: 0 };
|
|
}
|
|
if (domain.length < 2)
|
|
return { bands: [], firstBandIndex: void 0 };
|
|
const { interval } = this;
|
|
if (interval == null)
|
|
return { bands: [], firstBandIndex: void 0 };
|
|
const rangeParams = { visibleRange, extend };
|
|
if (!supportsInterval(domain, interval, rangeParams))
|
|
return { bands: [], firstBandIndex: void 0 };
|
|
const bandRange = domain === this.domain ? this.getCachedBandRange() : calculateBandRange(domain, interval);
|
|
if (bandRange == null)
|
|
return { bands: [], firstBandIndex: void 0 };
|
|
const [start2, stop] = bandRange;
|
|
if (intervalRangeCount(interval, start2, stop, rangeParams) > MAX_BANDS) {
|
|
logger_exports.warnOnce(`the configured unit results in too many bands, ignoring. Supply a larger unit.`);
|
|
return { bands: [], firstBandIndex: void 0 };
|
|
}
|
|
const bands = intervalRange(interval, start2, stop, rangeParams);
|
|
const firstBandIndex = intervalRangeStartIndex(interval, start2, stop, rangeParams);
|
|
return { bands, firstBandIndex };
|
|
}
|
|
ticks({ interval }, domain = this.domain, visibleRange = [0, 1], { extend = false } = {}) {
|
|
if (domain.length < 2)
|
|
return;
|
|
let bands;
|
|
let firstBandIndex;
|
|
let bandsSliceIndices;
|
|
if (domain === this.domain && !extend) {
|
|
({ bands } = this.calculateBands(domain, [0, 1], false));
|
|
bandsSliceIndices = visibleTickSliceIndices(bands, false, visibleRange);
|
|
firstBandIndex = bandsSliceIndices[0];
|
|
} else {
|
|
({ bands, firstBandIndex } = this.calculateBands(domain, visibleRange, extend));
|
|
}
|
|
if (interval == null) {
|
|
return { ticks: bands, count: void 0, firstTickIndex: firstBandIndex };
|
|
}
|
|
const milliseconds = this.interval ? intervalMilliseconds(this.interval) : Infinity;
|
|
const d0 = Math.min(domain[0].valueOf(), domain[1].valueOf());
|
|
const d1 = Math.max(domain[0].valueOf(), domain[1].valueOf());
|
|
let intervalTicks;
|
|
let intervalStartIndex;
|
|
let intervalEndIndex;
|
|
if (isPlainObject(interval) || typeof interval === "string") {
|
|
intervalTicks = intervalRange(interval, domain[0], domain[1], { extend: true, visibleRange });
|
|
intervalStartIndex = 0;
|
|
intervalEndIndex = intervalTicks.length - 1;
|
|
} else {
|
|
const i0 = bandsSliceIndices ? bandsSliceIndices[0] : 0;
|
|
const i1 = bandsSliceIndices ? bandsSliceIndices[1] : bands.length - 1;
|
|
intervalTicks = bands;
|
|
intervalStartIndex = findMaxIndex(i0, i1, (index) => bands[index].valueOf() <= d0) ?? i0;
|
|
intervalEndIndex = findMaxIndex(i0, i1, (index) => bands[index].valueOf() <= d1) ?? i1;
|
|
}
|
|
const ticks = [];
|
|
let lastIndex;
|
|
for (let i = intervalStartIndex; i <= intervalEndIndex; i++) {
|
|
const intervalTickValue = intervalTicks[i].valueOf();
|
|
const bandIndex = findMaxIndex(0, bands.length - 1, (index) => bands[index].valueOf() <= intervalTickValue);
|
|
const tick = bandIndex != null && bandIndex != lastIndex ? bands[bandIndex] : void 0;
|
|
lastIndex = bandIndex;
|
|
if (tick != null && intervalTickValue - tick.getTime() <= milliseconds)
|
|
ticks.push(tick);
|
|
}
|
|
let bandStart;
|
|
let bandEnd;
|
|
if (this.interval) {
|
|
const bandRange = calculateBandRange([new Date(d0), new Date(d1)], this.interval);
|
|
bandStart = bandRange[0].valueOf();
|
|
bandEnd = bandRange[1].valueOf();
|
|
} else {
|
|
bandStart = d0;
|
|
bandEnd = d1;
|
|
}
|
|
let firstTickIndex = findMinIndex(0, ticks.length - 1, (i) => ticks[i].valueOf() >= bandStart) ?? 0;
|
|
let lastTickIndex = findMaxIndex(0, ticks.length - 1, (i) => ticks[i].valueOf() <= bandEnd) ?? ticks.length - 1;
|
|
if (extend) {
|
|
firstTickIndex = Math.max(firstTickIndex - 1, 0);
|
|
lastTickIndex = Math.min(lastTickIndex + 1, ticks.length - 1);
|
|
}
|
|
return {
|
|
ticks: ticks.slice(firstTickIndex, lastTickIndex + 1),
|
|
count: ticks.length,
|
|
firstTickIndex: firstBandIndex
|
|
};
|
|
}
|
|
};
|
|
function supportsInterval(domain, interval, rangeParams) {
|
|
const [start2, stop] = calculateBandRange(domain, interval);
|
|
return intervalRangeCount(interval, start2, stop, rangeParams) <= MAX_BANDS;
|
|
}
|
|
function calculateBandRange(domain, interval) {
|
|
const start2 = intervalFloor(interval, domain[0]);
|
|
const stop = intervalFloor(interval, domain[1]);
|
|
return [start2, stop];
|
|
}
|
|
|
|
// packages/ag-charts-community/src/util/secondaryAxisTicks.ts
|
|
function calculateNiceSecondaryAxis(scale2, domain, primaryTickCount, reverse, visibleRange) {
|
|
let [d0, d1] = findMinMax(domain.map(Number));
|
|
const unzoomedTickCount = Math.floor(primaryTickCount.unzoomed);
|
|
if (unzoomedTickCount <= 1) {
|
|
const [start3, stop2] = domainWithOddTickCount(d0, d1);
|
|
const tickCount = 5 * Math.pow(2, -Math.ceil(Math.log2(visibleRange[1] - visibleRange[0])));
|
|
const { ticks: ticks2 } = createTicks(start3, stop2, tickCount, void 0, void 0, visibleRange);
|
|
const d2 = [scale2.toDomain(start3), scale2.toDomain(stop2)];
|
|
if (reverse)
|
|
d2.reverse();
|
|
return { domain: d2, ticks: ticks2 };
|
|
}
|
|
if (d0 === d1) {
|
|
const order = Math.floor(Math.log10(d0));
|
|
const magnitude = Math.pow(10, order);
|
|
const rangeOffsetStep = Math.min(magnitude, 1);
|
|
const rangeOffset = unzoomedTickCount - 1;
|
|
d0 -= rangeOffsetStep * Math.floor(rangeOffset / 2);
|
|
d1 = d0 + rangeOffsetStep * rangeOffset;
|
|
}
|
|
let start2 = d0;
|
|
let stop = d1;
|
|
start2 = calculateNiceStart(start2, stop, unzoomedTickCount);
|
|
const baseStep = getTickStep(start2, stop, unzoomedTickCount);
|
|
const segments = unzoomedTickCount - 1;
|
|
stop = start2 + segments * baseStep;
|
|
const stepAlignedStart = Math.floor(start2 / baseStep) * baseStep;
|
|
const stepAlignedStop = Math.floor(stop / baseStep) * baseStep;
|
|
if (stepAlignedStart <= d0 && stepAlignedStop >= d1) {
|
|
start2 = stepAlignedStart;
|
|
stop = stepAlignedStop;
|
|
}
|
|
const d = [scale2.toDomain(start2), scale2.toDomain(stop)];
|
|
if (reverse)
|
|
d.reverse();
|
|
const step = baseStep * ((primaryTickCount.unzoomed - 1) / (primaryTickCount.zoomed - 1));
|
|
const ticks = getTicks(start2, step, Math.floor(primaryTickCount.zoomed));
|
|
return { domain: d, ticks };
|
|
}
|
|
function domainWithOddTickCount(d0, d1) {
|
|
let start2 = d0;
|
|
let stop = d1;
|
|
let iterations = 0;
|
|
do {
|
|
[start2, stop] = niceTicksDomain(start2, stop);
|
|
const { ticks } = createTicks(start2, stop, 5);
|
|
if (ticks.length % 2 === 1)
|
|
return [start2, stop];
|
|
start2 -= 1;
|
|
stop += 1;
|
|
} while (iterations++ < 10);
|
|
return [d0, d1];
|
|
}
|
|
function calculateNiceStart(a, b, count) {
|
|
a = Math.floor(a);
|
|
const rawStep = Math.abs(b - a) / (count - 1);
|
|
const order = Math.floor(Math.log10(rawStep));
|
|
const magnitude = Math.pow(10, order);
|
|
return Math.floor(a / magnitude) * magnitude;
|
|
}
|
|
function getTicks(start2, step, count) {
|
|
const fractionDigits = countFractionDigits(step);
|
|
const f = Math.pow(10, fractionDigits);
|
|
const ticks = [];
|
|
for (let i = 0; i < count; i++) {
|
|
const tick = start2 + step * i;
|
|
ticks.push(Math.round(tick * f) / f);
|
|
}
|
|
return ticks;
|
|
}
|
|
function getTickStep(start2, stop, count) {
|
|
const segments = count - 1;
|
|
const rawStep = (stop - start2) / segments;
|
|
return calculateNextNiceStep(rawStep);
|
|
}
|
|
function calculateNextNiceStep(rawStep) {
|
|
const order = Math.floor(Math.log10(rawStep));
|
|
const magnitude = Math.pow(10, order);
|
|
const step = rawStep / magnitude;
|
|
if (step > 0 && step <= 1)
|
|
return magnitude;
|
|
if (step > 1 && step <= 2)
|
|
return 2 * magnitude;
|
|
if (step > 2 && step <= 5)
|
|
return 5 * magnitude;
|
|
if (step > 5 && step <= 10)
|
|
return 10 * magnitude;
|
|
return rawStep;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/axis/generateTicksUtils.ts
|
|
var DENSE_TICK_COUNT = 18;
|
|
var TICK_STEP_VALUES = [1, 2, 3, 4, 6, 8, 9, 10, 12];
|
|
function axisLabelsOverlap(data, padding2 = 0) {
|
|
const result = [];
|
|
for (const datum of data) {
|
|
const { x, y, width: width2, height: height2 } = datum;
|
|
if (result.some((l) => boxCollides(l, x, y, width2 + padding2, height2 + padding2))) {
|
|
return true;
|
|
}
|
|
result.push(datum);
|
|
}
|
|
return false;
|
|
}
|
|
function createTimeScaleTicks(interval, domain, visibleRange, extend) {
|
|
if (interval == null) {
|
|
return domain;
|
|
}
|
|
if (typeof interval !== "number") {
|
|
const epoch = domain[0];
|
|
const alignedInterval = typeof interval === "string" ? { unit: interval, epoch } : { ...interval, epoch };
|
|
return intervalRange(alignedInterval, domain[0], domain[1], { visibleRange, extend });
|
|
}
|
|
const ticks = [];
|
|
const d0 = domain[0].valueOf();
|
|
const d1 = domain[1].valueOf();
|
|
for (let intervalTickTime = d0; intervalTickTime <= d1; intervalTickTime += interval) {
|
|
ticks.push(new Date(intervalTickTime));
|
|
}
|
|
return ticks;
|
|
}
|
|
function ticksEqual(a, b) {
|
|
if (a.length !== b.length) {
|
|
return false;
|
|
}
|
|
for (let i = 0; i < a.length; i += 1) {
|
|
if (a[i]?.valueOf() !== b[i]?.valueOf()) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function ticksSpacing(ticks) {
|
|
if (ticks.length < 2)
|
|
return Infinity;
|
|
let spacing = 0;
|
|
let y0 = ticks[0].translation;
|
|
for (let i = 1; i < ticks.length; i++) {
|
|
const y1 = ticks[i].translation;
|
|
const delta5 = Math.abs(y1 - y0);
|
|
spacing = Math.max(spacing, delta5);
|
|
y0 = y1;
|
|
}
|
|
return spacing;
|
|
}
|
|
function formatTicks(options, {
|
|
niceDomain,
|
|
rawTicks,
|
|
rawFirstTickIndex = 0,
|
|
generatePrimaryTicks,
|
|
primaryTicksIndices,
|
|
alignment,
|
|
fractionDigits,
|
|
timeInterval: timeInterval3
|
|
}) {
|
|
const { scale: scale2, label, tickFormatter, inRange: inRange2, isVertical, sizeLimit = Infinity } = options;
|
|
const isContinuous2 = TimeScale.is(scale2) || DiscreteTimeScale.is(scale2);
|
|
const measurer3 = cachedTextMeasurer(label);
|
|
const idGenerator = createIdsGenerator();
|
|
const ticks = [];
|
|
withTemporaryDomain(scale2, niceDomain, () => {
|
|
const maxBandwidth = BandScale.is(scale2) ? scale2.bandwidth ?? Infinity : Infinity;
|
|
const halfBandwidth = (scale2.bandwidth ?? 0) / 2;
|
|
const axisFormatter = axisTickFormatter(
|
|
label.enabled,
|
|
generatePrimaryTicks,
|
|
niceDomain,
|
|
rawTicks,
|
|
fractionDigits,
|
|
timeInterval3,
|
|
tickFormatter
|
|
);
|
|
let maxWidth = isVertical ? sizeLimit : maxBandwidth;
|
|
let maxHeight = isVertical ? maxBandwidth : sizeLimit;
|
|
if (label.rotation) {
|
|
const innerRect = getMaxInnerRectSize(label.rotation, maxWidth, maxHeight);
|
|
maxWidth = innerRect.width;
|
|
maxHeight = innerRect.height;
|
|
}
|
|
const wrapOptions = {
|
|
font: label,
|
|
maxWidth,
|
|
maxHeight,
|
|
overflow: label.truncate ? "ellipsis" : "hide",
|
|
textWrap: label.wrapping
|
|
};
|
|
for (let i = 0; i < rawTicks.length; i++) {
|
|
const tick = rawTicks[i];
|
|
const translation = scale2.convert(tick, { alignment }) + halfBandwidth;
|
|
if (inRange2 && !inRange2(translation))
|
|
continue;
|
|
const isPrimary = primaryTicksIndices?.has(i) ?? false;
|
|
const inputText = axisFormatter(isPrimary, tick, i);
|
|
let wrappedLabel = null;
|
|
if (label.avoidCollisions) {
|
|
wrappedLabel = wrapTextOrSegments(inputText, wrapOptions) || null;
|
|
}
|
|
const tickLabel = wrappedLabel ?? inputText;
|
|
const isSegmented = isArray(tickLabel);
|
|
const isTruncated2 = tickLabel !== inputText && (isSegmented ? isSegmentTruncated(tickLabel.at(-1)) : isTextTruncated(toTextString(tickLabel)));
|
|
let tickId;
|
|
if (isContinuous2) {
|
|
const tickValue = tick?.valueOf();
|
|
if (Number.isFinite(tickValue)) {
|
|
tickId = idGenerator(`v:${tickValue}`);
|
|
}
|
|
}
|
|
tickId ?? (tickId = idGenerator(`l:${isSegmented ? toPlainText(tickLabel.flat()) : tickLabel}`));
|
|
ticks.push({
|
|
tick,
|
|
tickId,
|
|
tickLabel,
|
|
isPrimary,
|
|
index: i + rawFirstTickIndex,
|
|
textUntruncated: isTruncated2 ? toPlainText(inputText) : void 0,
|
|
textMetrics: isSegmented ? measureTextSegments(tickLabel, label) : measurer3.measureLines(toTextString(tickLabel)),
|
|
translation: Math.floor(translation)
|
|
});
|
|
}
|
|
});
|
|
return ticks;
|
|
}
|
|
function withTemporaryDomain(scale2, temporaryDomain, callback2) {
|
|
const originalDomain = scale2.domain;
|
|
try {
|
|
scale2.domain = temporaryDomain;
|
|
callback2();
|
|
} finally {
|
|
scale2.domain = originalDomain;
|
|
}
|
|
}
|
|
function axisTickFormatter(labelEnabled, generatePrimaryTicks, niceDomain, rawTicks, fractionDigits, timeInterval3, tickFormatter) {
|
|
const dateStyle = generatePrimaryTicks ? "component" : "long";
|
|
const parentInterval = generatePrimaryTicks && timeInterval3 ? intervalHierarchy(timeInterval3) : void 0;
|
|
const primaryFormatter = generatePrimaryTicks ? tickFormatter(niceDomain, rawTicks, true, fractionDigits, parentInterval, dateStyle) : null;
|
|
const defaultFormatter = labelEnabled ? tickFormatter(niceDomain, rawTicks, false, fractionDigits, timeInterval3, dateStyle) : null;
|
|
return (isPrimary, tick, index) => {
|
|
const formatter2 = isPrimary ? primaryFormatter : defaultFormatter;
|
|
return formatter2?.(tick, index) ?? String(tick);
|
|
};
|
|
}
|
|
function getTimeIntervalTicks(scale2, visibleRange, tickCount, maxTickCount, tickParams, timeInterval3, reverse, minimumTimeGranularity) {
|
|
if (!TimeScale.is(scale2) && !DiscreteTimeScale.is(scale2))
|
|
return;
|
|
const parentInterval = intervalHierarchy(timeInterval3);
|
|
if (parentInterval == null)
|
|
return;
|
|
if (reverse) {
|
|
visibleRange = [1 - visibleRange[1], 1 - visibleRange[0]];
|
|
}
|
|
const dv0 = Math.min(scale2.domain[0].valueOf(), scale2.domain.at(-1).valueOf());
|
|
const dv1 = Math.max(scale2.domain[0].valueOf(), scale2.domain.at(-1).valueOf());
|
|
let [dp0, dp1] = intervalExtent(new Date(dv0), new Date(dv1), visibleRange);
|
|
dp0 = intervalFloor(parentInterval, dp0);
|
|
if (dp0.valueOf() >= dv0) {
|
|
dp0 = intervalPrevious(parentInterval, dp0);
|
|
}
|
|
dp1 = intervalCeil(parentInterval, dp1);
|
|
if (dp1.valueOf() <= dv1) {
|
|
dp1 = intervalNext(parentInterval, dp1);
|
|
}
|
|
const primaryTicks = intervalRange(parentInterval, dp0, dp1);
|
|
const milliseconds = intervalMilliseconds(timeInterval3);
|
|
const skipFirstPrimaryTick = OrdinalTimeScale.is(scale2);
|
|
const intervalTickParams = { ...tickParams, interval: timeInterval3 };
|
|
const ticks = [];
|
|
let primaryTicksIndices;
|
|
let parentLevelMode;
|
|
let alignment;
|
|
let ordinalTickStep = 0;
|
|
if (OrdinalTimeScale.is(scale2)) {
|
|
const timeIntervalGranularity = intervalUnit(timeInterval3);
|
|
parentLevelMode = minimumTimeGranularity != null && intervalMilliseconds(minimumTimeGranularity) >= intervalMilliseconds(timeIntervalGranularity) ? 2 /* OrdinalTimeStepTicks */ : 3 /* OrdinalTimeScaleTicks */;
|
|
alignment = 1 /* Trailing */;
|
|
const tickDensity = tickCount / maxTickCount;
|
|
const baseTickStep = scale2.bandCount(visibleRange) / (tickDensity * DENSE_TICK_COUNT);
|
|
ordinalTickStep = TICK_STEP_VALUES.findLast((t) => baseTickStep >= t) ?? 1;
|
|
} else if (UnitTimeScale.is(scale2) && (scale2.interval == null || intervalMilliseconds(scale2.interval) >= milliseconds)) {
|
|
parentLevelMode = 1 /* UnitTimeScaleTicks */;
|
|
} else {
|
|
parentLevelMode = 0 /* ContinuousTimeScaleTicks */;
|
|
alignment = 2 /* Interpolate */;
|
|
}
|
|
for (let i = 0; i < primaryTicks.length - 1; i++) {
|
|
const p0 = primaryTicks[i];
|
|
const p1 = primaryTicks[i + 1];
|
|
const first2 = i === 0;
|
|
const last = i === primaryTicks.length - 2;
|
|
const dp = p1.valueOf() - p0.valueOf();
|
|
const pVisibleRange = [
|
|
Math.max((dv0 - p0.valueOf()) / dp, 0),
|
|
Math.min((dv1 - p0.valueOf()) / dp, 1)
|
|
];
|
|
let intervalTicks;
|
|
switch (parentLevelMode) {
|
|
case 0 /* ContinuousTimeScaleTicks */:
|
|
intervalTicks = createTimeScaleTicks(intervalTickParams.interval, [p0, p1], pVisibleRange, true);
|
|
break;
|
|
case 1 /* UnitTimeScaleTicks */:
|
|
case 3 /* OrdinalTimeScaleTicks */: {
|
|
const scaleTicks = scale2.ticks(intervalTickParams, [p0, p1], pVisibleRange, {
|
|
extend: true,
|
|
dropInitial: true
|
|
});
|
|
intervalTicks = scaleTicks?.ticks ?? [];
|
|
break;
|
|
}
|
|
case 2 /* OrdinalTimeStepTicks */:
|
|
intervalTicks = scale2.stepTicks(
|
|
ordinalTickStep,
|
|
[p0, p1],
|
|
void 0,
|
|
!last
|
|
);
|
|
break;
|
|
}
|
|
dropFirstWhile(intervalTicks, (firstTick2) => firstTick2.valueOf() < p0.valueOf());
|
|
if (!last) {
|
|
dropLastWhile(intervalTicks, (lastTick) => {
|
|
switch (parentLevelMode) {
|
|
case 0 /* ContinuousTimeScaleTicks */:
|
|
case 3 /* OrdinalTimeScaleTicks */:
|
|
return lastTick.valueOf() + milliseconds > p1.valueOf();
|
|
case 1 /* UnitTimeScaleTicks */:
|
|
case 2 /* OrdinalTimeStepTicks */:
|
|
return lastTick.valueOf() >= p1.valueOf();
|
|
}
|
|
});
|
|
}
|
|
if (intervalTicks.length === 0)
|
|
continue;
|
|
const firstTick = intervalTicks[0];
|
|
const firstTickDiff = compareDates(firstTick, p0);
|
|
const firstPrimary = parentLevelMode === 0 /* ContinuousTimeScaleTicks */ ? firstTickDiff === 0 : firstTickDiff <= milliseconds;
|
|
if (firstPrimary && (!skipFirstPrimaryTick || !first2)) {
|
|
primaryTicksIndices ?? (primaryTicksIndices = /* @__PURE__ */ new Set());
|
|
primaryTicksIndices.add(ticks.length);
|
|
}
|
|
ticks.push(...intervalTicks);
|
|
}
|
|
if (primaryTicksIndices?.size === 1 && primaryTicksIndices.has(0)) {
|
|
primaryTicksIndices = void 0;
|
|
}
|
|
return { ticks, primaryTicksIndices, alignment };
|
|
}
|
|
function timeIntervalMaxLabelSize(label, primaryLabel, domain, timeInterval3, textMeasurer) {
|
|
const specifier = labelSpecifier(label.format, timeInterval3);
|
|
if (specifier == null) {
|
|
return { width: 0, height: 0 };
|
|
}
|
|
const labelFormatter = buildDateFormatter(specifier);
|
|
const hierarchy = timeInterval3 ? intervalHierarchy(timeInterval3) : void 0;
|
|
const primarySpecifier = labelSpecifier(primaryLabel?.format, hierarchy);
|
|
const primaryLabelFormatter = primarySpecifier ? buildDateFormatter(primarySpecifier) : labelFormatter;
|
|
const d0 = new Date(domain[0]);
|
|
const d1 = new Date(domain.at(-1));
|
|
const hierarchyRange = hierarchy ? intervalRange(hierarchy, new Date(domain[0]), new Date(domain.at(-1)), { extend: true }) : void 0;
|
|
let maxWidth = 0;
|
|
let maxHeight = 0;
|
|
if (labelFormatter != null) {
|
|
const padding2 = expandLabelPadding(label);
|
|
const xPadding = padding2.left + padding2.right;
|
|
const yPadding = padding2.top + padding2.bottom;
|
|
let l0;
|
|
let l1;
|
|
if (hierarchyRange != null && hierarchyRange.length > 1) {
|
|
l0 = hierarchyRange[0];
|
|
l1 = hierarchyRange[1];
|
|
} else {
|
|
l0 = d0;
|
|
l1 = d1;
|
|
}
|
|
const labelRange = intervalRange(timeInterval3, l0, l1, { limit: 50 });
|
|
for (const date2 of labelRange) {
|
|
const text2 = labelFormatter(date2);
|
|
const { width: width2, height: height2 } = textMeasurer.measureLines(text2);
|
|
maxWidth = Math.max(maxWidth, width2 + xPadding);
|
|
maxHeight = Math.max(maxHeight, height2 + yPadding);
|
|
}
|
|
}
|
|
if (primaryLabelFormatter != null && hierarchyRange != null) {
|
|
const padding2 = expandLabelPadding(primaryLabel);
|
|
const xPadding = padding2.left + padding2.right;
|
|
const yPadding = padding2.top + padding2.bottom;
|
|
for (const date2 of hierarchyRange) {
|
|
const text2 = primaryLabelFormatter(date2);
|
|
const { width: width2, height: height2 } = textMeasurer.measureLines(text2);
|
|
maxWidth = Math.max(maxWidth, width2 + xPadding);
|
|
maxHeight = Math.max(maxHeight, height2 + yPadding);
|
|
}
|
|
}
|
|
return {
|
|
width: Math.ceil(maxWidth),
|
|
height: Math.ceil(maxHeight)
|
|
};
|
|
}
|
|
function getTextBaseline(parallel, labelRotation, sideFlag, parallelFlipFlag) {
|
|
if (parallel && !labelRotation) {
|
|
return sideFlag * parallelFlipFlag === -1 ? "top" : "bottom";
|
|
}
|
|
return "middle";
|
|
}
|
|
function getTextAlign(parallel, labelRotation, labelAutoRotation, sideFlag, regularFlipFlag) {
|
|
const labelRotated = labelRotation > 0 && labelRotation <= Math.PI;
|
|
const labelAutoRotated = labelAutoRotation > 0 && labelAutoRotation <= Math.PI;
|
|
const alignFlag = labelRotated || labelAutoRotated ? -1 : 1;
|
|
if (parallel) {
|
|
if (labelRotation || labelAutoRotation) {
|
|
if (sideFlag * alignFlag === -1) {
|
|
return "end";
|
|
}
|
|
} else {
|
|
return "center";
|
|
}
|
|
} else if (sideFlag * regularFlipFlag === -1) {
|
|
return "end";
|
|
}
|
|
return "start";
|
|
}
|
|
function labelSpecifier(format, timeInterval3) {
|
|
if (format == null)
|
|
return;
|
|
if (typeof format === "string") {
|
|
return format;
|
|
} else if (isPlainObject(format) && timeInterval3 != null) {
|
|
return format[intervalUnit(timeInterval3)];
|
|
}
|
|
}
|
|
function calculateLabelRotation(rotation, parallel, axisRotation = 0) {
|
|
const configuredRotation = normalizeAngle360FromDegrees(rotation);
|
|
const parallelFlipFlag = !configuredRotation && axisRotation >= 0 && axisRotation <= Math.PI ? -1 : 1;
|
|
const regularFlipFlag = !configuredRotation && axisRotation - Math.PI / 2 >= 0 && axisRotation - Math.PI / 2 <= Math.PI ? -1 : 1;
|
|
const defaultRotation = parallel ? parallelFlipFlag * (Math.PI / 2) : 0;
|
|
return { configuredRotation, defaultRotation, parallelFlipFlag, regularFlipFlag };
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/axis/generateTicks.ts
|
|
var sunday2 = new Date(1970, 0, 4);
|
|
function generateTicks(options) {
|
|
const { label, domain, axisRotation, labelOffset, sideFlag } = options;
|
|
const { defaultRotation, configuredRotation, parallelFlipFlag, regularFlipFlag } = calculateLabelRotation(
|
|
label.rotation,
|
|
label.parallel,
|
|
axisRotation
|
|
);
|
|
const initialRotation = configuredRotation + defaultRotation;
|
|
const checkLabelOverlap = (tickData2, rotation2 = 0) => {
|
|
const labelSpacing = label.minSpacing ?? (configuredRotation === 0 && rotation2 === 0 ? 10 : 0);
|
|
const labelRotation = initialRotation + rotation2;
|
|
const labelPadding = expandLabelPadding(label);
|
|
return axisLabelsOverlap(createTimeLabelData(options, tickData2, labelRotation), labelSpacing) || axisLabelsOverlap(createLabelData(tickData2.ticks, labelOffset, labelRotation, labelPadding), labelSpacing);
|
|
};
|
|
const { maxTickCount } = estimateScaleTickCount(options);
|
|
const tickGenerationType = getTickGenerationType(options);
|
|
const avoidCollisions = label.enabled && label.avoidCollisions;
|
|
const maxIterations = Number.isFinite(maxTickCount) ? maxTickCount : 10;
|
|
const tryAutoRotate = avoidCollisions && label.autoRotate && label.rotation == null;
|
|
let index = 0;
|
|
let autoRotation = 0;
|
|
let labelOverlap = true;
|
|
let tickData = {
|
|
tickDomain: [],
|
|
niceDomain: domain,
|
|
ticks: [],
|
|
rawTicks: [],
|
|
rawTickCount: void 0,
|
|
timeInterval: void 0,
|
|
fractionDigits: 0
|
|
};
|
|
while (labelOverlap && index <= maxIterations) {
|
|
({ tickData, index } = buildTickData(options, tickGenerationType, tickData, index));
|
|
autoRotation = tryAutoRotate && checkLabelOverlap(tickData, 0) ? normalizeAngle360FromDegrees(label.autoRotateAngle) : 0;
|
|
labelOverlap = avoidCollisions && checkLabelOverlap(tickData, autoRotation);
|
|
}
|
|
const textAlign = getTextAlign(label.parallel, configuredRotation, autoRotation, sideFlag, regularFlipFlag);
|
|
const textBaseline = getTextBaseline(label.parallel, configuredRotation, sideFlag, parallelFlipFlag);
|
|
const rotation = configuredRotation + autoRotation;
|
|
return { tickData, textAlign, textBaseline, rotation };
|
|
}
|
|
function getTickGenerationType(options) {
|
|
if (options.interval?.values) {
|
|
return 2 /* VALUES */;
|
|
} else if (options.primaryTickCount != null) {
|
|
return 1 /* CREATE_SECONDARY */;
|
|
}
|
|
return 0 /* CREATE */;
|
|
}
|
|
function estimateScaleTickCount({
|
|
scale: scale2,
|
|
domain,
|
|
range: range3,
|
|
visibleRange,
|
|
label,
|
|
defaultTickMinSpacing,
|
|
interval: { minSpacing, maxSpacing }
|
|
}) {
|
|
const { defaultTickCount } = scale2;
|
|
const rangeExtent = findRangeExtent(range3);
|
|
const zoomExtent = findRangeExtent(visibleRange);
|
|
if (CategoryScale.is(scale2) || OrdinalTimeScale.is(scale2) && domain.length < 1e3) {
|
|
const maxTickCount = CategoryScale.is(scale2) ? domain.length : Math.min(domain.length, Math.max(1, Math.floor(rangeExtent / (zoomExtent * defaultTickMinSpacing))));
|
|
const estimatedTickCount = Math.ceil(rangeExtent / (zoomExtent * label.fontSize));
|
|
return {
|
|
tickCount: Math.min(estimatedTickCount, maxTickCount),
|
|
minTickCount: 0,
|
|
maxTickCount
|
|
};
|
|
}
|
|
return estimateTickCount(rangeExtent, zoomExtent, minSpacing, maxSpacing, defaultTickCount, defaultTickMinSpacing);
|
|
}
|
|
function buildTickData(options, tickGenerationType, previousTickData, index) {
|
|
const { step, values } = options.interval;
|
|
const { maxTickCount, minTickCount, tickCount } = estimateScaleTickCount(options);
|
|
const countTicks2 = (i) => Math.max(tickCount - i, minTickCount);
|
|
const regenerateTicks = step == null && values == null && countTicks2(index) > minTickCount;
|
|
const previousTicks = previousTickData.rawTicks;
|
|
const maxIterations = tickCount - minTickCount;
|
|
const countParams = { minTickCount, maxTickCount, tickCount: countTicks2(index) };
|
|
let nextTicks = calculateRawTicks(options, tickGenerationType, countParams);
|
|
if (regenerateTicks && ticksEqual(nextTicks.rawTicks, previousTicks)) {
|
|
let lowerBound = index;
|
|
let upperBound = maxIterations;
|
|
while (lowerBound <= upperBound) {
|
|
index = Math.trunc((lowerBound + upperBound) / 2);
|
|
countParams.tickCount = countTicks2(index);
|
|
const nextTicksCandidate = calculateRawTicks(options, tickGenerationType, countParams);
|
|
if (ticksEqual(nextTicksCandidate.rawTicks, previousTicks)) {
|
|
lowerBound = index + 1;
|
|
} else {
|
|
nextTicks = nextTicksCandidate;
|
|
upperBound = index - 1;
|
|
}
|
|
}
|
|
}
|
|
const {
|
|
tickDomain,
|
|
niceDomain,
|
|
rawTicks,
|
|
rawTickCount,
|
|
rawFirstTickIndex,
|
|
generatePrimaryTicks,
|
|
primaryTicksIndices,
|
|
alignment,
|
|
fractionDigits,
|
|
timeInterval: timeInterval3
|
|
} = nextTicks;
|
|
return {
|
|
tickData: {
|
|
tickDomain,
|
|
niceDomain,
|
|
rawTicks,
|
|
rawTickCount,
|
|
fractionDigits,
|
|
timeInterval: timeInterval3,
|
|
ticks: formatTicks(options, {
|
|
niceDomain,
|
|
rawTicks,
|
|
rawFirstTickIndex,
|
|
generatePrimaryTicks,
|
|
primaryTicksIndices,
|
|
alignment,
|
|
fractionDigits,
|
|
timeInterval: timeInterval3
|
|
})
|
|
},
|
|
index: index + 1
|
|
};
|
|
}
|
|
function calculateRawTicks(options, tickGenerationType, countParams) {
|
|
const {
|
|
domain,
|
|
reverse,
|
|
visibleRange,
|
|
scale: scale2,
|
|
interval,
|
|
primaryLabel,
|
|
niceMode,
|
|
primaryTickCount,
|
|
minimumTimeGranularity
|
|
} = options;
|
|
const domainParams = {
|
|
nice: niceMode.map((n) => n === 0 /* TickAndDomain */),
|
|
interval: interval.step,
|
|
...countParams
|
|
};
|
|
const tickParams = {
|
|
...domainParams,
|
|
nice: niceMode.map((n) => n === 0 /* TickAndDomain */ || n === 1 /* TicksOnly */)
|
|
};
|
|
let secondaryAxisTicks;
|
|
if (tickGenerationType === 1 /* CREATE_SECONDARY */ && primaryTickCount != null && ContinuousScale.is(scale2)) {
|
|
secondaryAxisTicks = calculateNiceSecondaryAxis(scale2, domain, primaryTickCount, reverse, visibleRange);
|
|
}
|
|
const niceDomain = niceMode.includes(0 /* TickAndDomain */) ? secondaryAxisTicks?.domain ?? scale2.niceDomain(domainParams, domain) : domain;
|
|
let tickDomain = niceDomain;
|
|
let rawTicks;
|
|
let rawTickCount;
|
|
let rawFirstTickIndex;
|
|
let timeInterval3;
|
|
let primaryTicksIndices;
|
|
let alignment;
|
|
const generatePrimaryTicks = primaryLabel?.enabled === true && tickParams.interval == null;
|
|
withTemporaryDomain(scale2, niceDomain, () => {
|
|
switch (tickGenerationType) {
|
|
case 2 /* VALUES */:
|
|
tickDomain = interval.values;
|
|
rawTicks = interval.values;
|
|
rawTickCount = rawTicks.length;
|
|
if (OrdinalTimeScale.is(scale2)) {
|
|
alignment = 1 /* Trailing */;
|
|
} else if (UnitTimeScale.is(scale2)) {
|
|
alignment = 2 /* Interpolate */;
|
|
}
|
|
if (ContinuousScale.is(scale2)) {
|
|
const [d0, d1] = findMinMax(niceDomain.map(Number));
|
|
rawTicks = rawTicks.filter((value) => Number(value) >= d0 && Number(value) <= d1).sort((a, b) => Number(a) - Number(b));
|
|
}
|
|
break;
|
|
case 1 /* CREATE_SECONDARY */:
|
|
if (secondaryAxisTicks) {
|
|
rawTicks = secondaryAxisTicks.ticks;
|
|
rawTickCount = secondaryAxisTicks.ticks.length;
|
|
} else {
|
|
const tickGeneration = scale2.ticks(tickParams, niceDomain, visibleRange);
|
|
rawTicks = tickGeneration?.ticks;
|
|
rawTickCount = tickGeneration?.count;
|
|
}
|
|
break;
|
|
default: {
|
|
const { tickCount, minTickCount, maxTickCount } = countParams;
|
|
if (niceDomain.length > 0 && tickParams.interval == null && (UnitTimeScale.is(scale2) || generatePrimaryTicks && (TimeScale.is(scale2) || OrdinalTimeScale.is(scale2)))) {
|
|
const dates = niceDomain;
|
|
const start2 = Math.min(dates[0].valueOf(), dates.at(-1).valueOf());
|
|
const end3 = Math.max(dates[0].valueOf(), dates.at(-1).valueOf());
|
|
timeInterval3 = getTickTimeInterval(start2, end3, tickCount, minTickCount, maxTickCount, {
|
|
weekStart: primaryLabel == null ? sunday2 : void 0,
|
|
primaryOnly: true
|
|
});
|
|
}
|
|
let minTimeInterval;
|
|
if (OrdinalTimeScale.is(scale2)) {
|
|
minTimeInterval = minimumTimeGranularity;
|
|
} else if (UnitTimeScale.is(scale2)) {
|
|
minTimeInterval = scale2.interval;
|
|
}
|
|
if (minTimeInterval != null && timeInterval3 != null && // Prefer UnitTimeAxis.unit over this interval, because the user may have defined an epoch
|
|
intervalMilliseconds(minTimeInterval) >= intervalMilliseconds(timeInterval3)) {
|
|
timeInterval3 = minTimeInterval;
|
|
}
|
|
const intervalTicks = timeInterval3 ? getTimeIntervalTicks(
|
|
scale2,
|
|
visibleRange,
|
|
tickCount,
|
|
maxTickCount,
|
|
tickParams,
|
|
timeInterval3,
|
|
reverse,
|
|
minimumTimeGranularity
|
|
) : void 0;
|
|
if (intervalTicks) {
|
|
({ ticks: rawTicks, primaryTicksIndices, alignment } = intervalTicks);
|
|
} else {
|
|
const intervalTickParams = UnitTimeScale.is(scale2) && tickParams.interval == null && timeInterval3 != null ? { ...tickParams, interval: timeInterval3 } : tickParams;
|
|
const tickGeneration = scale2.ticks(intervalTickParams, niceDomain, visibleRange);
|
|
rawTicks = tickGeneration?.ticks;
|
|
rawTickCount = tickGeneration?.count;
|
|
rawFirstTickIndex = tickGeneration?.firstTickIndex;
|
|
if (TimeScale.is(scale2) || DiscreteTimeScale.is(scale2)) {
|
|
const paramsInterval = typeof tickParams.interval === "number" ? lowestGranularityForInterval(tickParams.interval) : tickParams.interval;
|
|
timeInterval3 ?? (timeInterval3 = paramsInterval ?? tickGeneration?.timeInterval);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
rawTicks ?? (rawTicks = []);
|
|
let fractionDigits = 0;
|
|
for (const tick of rawTicks) {
|
|
if (typeof tick !== "number")
|
|
continue;
|
|
const value = countFractionDigits(tick);
|
|
if (value > fractionDigits) {
|
|
fractionDigits = value;
|
|
}
|
|
}
|
|
if (!generatePrimaryTicks) {
|
|
primaryTicksIndices = void 0;
|
|
}
|
|
return {
|
|
tickDomain,
|
|
niceDomain,
|
|
rawTicks,
|
|
rawTickCount,
|
|
rawFirstTickIndex,
|
|
generatePrimaryTicks,
|
|
primaryTicksIndices,
|
|
alignment,
|
|
fractionDigits,
|
|
timeInterval: timeInterval3
|
|
};
|
|
}
|
|
function createTimeLabelData(options, tickData, labelRotation) {
|
|
const { niceDomain, ticks, timeInterval: timeInterval3 } = tickData;
|
|
if (timeInterval3 == null)
|
|
return [];
|
|
const spacing = ticksSpacing(ticks);
|
|
const { label, labelOffset, primaryLabel, domain } = options;
|
|
const { width: width2, height: height2 } = timeIntervalMaxLabelSize(
|
|
label,
|
|
primaryLabel,
|
|
niceDomain ?? domain,
|
|
timeInterval3,
|
|
cachedTextMeasurer(label)
|
|
);
|
|
const labelData = [];
|
|
for (const translation of [0, spacing]) {
|
|
const { x, y } = rotatePoint(labelOffset, translation, labelRotation);
|
|
labelData.push({ x, y, width: width2, height: height2 });
|
|
}
|
|
return labelData;
|
|
}
|
|
function createLabelData(tickData, labelOffset, labelRotation, labelPadding) {
|
|
const labelData = [];
|
|
const xPadding = labelPadding.left + labelPadding.right;
|
|
const yPadding = labelPadding.top + labelPadding.bottom;
|
|
for (const { tickLabel, textMetrics, translation } of tickData) {
|
|
if (!tickLabel)
|
|
continue;
|
|
const { x, y } = rotatePoint(labelOffset, translation, labelRotation);
|
|
const width2 = textMetrics.width + xPadding;
|
|
const height2 = textMetrics.height + yPadding;
|
|
labelData.push({ x, y, width: width2, height: height2 });
|
|
}
|
|
return labelData;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/axis/cartesianAxis.ts
|
|
var _CartesianAxis = class _CartesianAxis extends Axis {
|
|
constructor(moduleCtx, scale2) {
|
|
super(moduleCtx, scale2);
|
|
this.maxThicknessRatio = 0.3;
|
|
this.crossAxisTranslation = { x: 0, y: 0 };
|
|
this.minimumTimeGranularity = void 0;
|
|
this.headingLabelGroup = this.axisGroup.appendChild(
|
|
new TranslatableGroup({ name: `${this.id}-Axis-heading` })
|
|
);
|
|
this.lineNodeGroup = this.axisGroup.appendChild(
|
|
new TranslatableGroup({ name: `${this.id}-Axis-line` })
|
|
);
|
|
this.lineNode = this.lineNodeGroup.appendChild(new Line({ zIndex: 1 /* AxisLine */ }));
|
|
this.tickLineGroupSelection = Selection.select(this.tickLineGroup, Line, false);
|
|
this.gridLineGroupSelection = Selection.select(this.gridLineGroup, Line, false);
|
|
this.gridFillGroupSelection = Selection.select(this.gridFillGroup, Rect, false);
|
|
this.tempText = new TransformableText({ debugDirty: false });
|
|
this.tempCaption = new Caption();
|
|
this.animationManager = moduleCtx.animationManager;
|
|
this.animationState = new StateMachine("empty", {
|
|
empty: {
|
|
update: {
|
|
target: "ready",
|
|
action: () => this.resetSelectionNodes()
|
|
},
|
|
reset: "empty"
|
|
},
|
|
ready: {
|
|
update: (data) => this.animateReadyUpdate(data),
|
|
resize: () => this.resetSelectionNodes(),
|
|
reset: "empty"
|
|
}
|
|
});
|
|
this.headingLabelGroup.appendChild(this.title.caption.node);
|
|
let previousSize = void 0;
|
|
this.cleanup.register(
|
|
moduleCtx.eventsHub.on("layout:complete", (e) => {
|
|
const size = [e.chart.width, e.chart.height];
|
|
if (previousSize != null && !arraysEqual(size, previousSize)) {
|
|
this.animationState.transition("resize");
|
|
}
|
|
previousSize = size;
|
|
}),
|
|
this.title.caption.registerInteraction(this.moduleCtx, "afterend")
|
|
);
|
|
}
|
|
static is(value) {
|
|
return value instanceof _CartesianAxis;
|
|
}
|
|
get horizontal() {
|
|
return this.position === "top" || this.position === "bottom";
|
|
}
|
|
onGridVisibilityChange() {
|
|
}
|
|
resetAnimation(phase) {
|
|
if (phase === "initial") {
|
|
this.animationState.transition("reset");
|
|
}
|
|
}
|
|
get direction() {
|
|
return this.position === "top" || this.position === "bottom" ? "x" /* X */ : "y" /* Y */;
|
|
}
|
|
createAxisContext() {
|
|
return { ...super.createAxisContext(), position: this.position };
|
|
}
|
|
createLabel() {
|
|
return new CartesianAxisLabel();
|
|
}
|
|
updateDirection() {
|
|
switch (this.position) {
|
|
case "top":
|
|
this.label.mirrored = true;
|
|
this.label.parallel = true;
|
|
break;
|
|
case "right":
|
|
this.label.mirrored = true;
|
|
this.label.parallel = false;
|
|
break;
|
|
case "bottom":
|
|
this.label.mirrored = false;
|
|
this.label.parallel = true;
|
|
break;
|
|
case "left":
|
|
this.label.mirrored = false;
|
|
this.label.parallel = false;
|
|
break;
|
|
}
|
|
if (this.axisContext) {
|
|
this.axisContext.position = this.position;
|
|
this.axisContext.direction = this.direction;
|
|
}
|
|
}
|
|
calculateLayout(primaryTickCount, chartLayout) {
|
|
this.updateDirection();
|
|
return super.calculateLayout(primaryTickCount, chartLayout);
|
|
}
|
|
layoutCrossLines() {
|
|
const crosslinesVisible = this.hasDefinedDomain() || this.hasVisibleSeries();
|
|
for (const crossLine of this.crossLines) {
|
|
crossLine.calculateLayout?.(crosslinesVisible);
|
|
}
|
|
}
|
|
calculateTickLayout(domain, niceMode, visibleRange, initialPrimaryTickCount) {
|
|
const sideFlag = this.label.getSideFlag();
|
|
const labelX = sideFlag * (this.getTickSize() + this.getTickSpacing() + this.label.spacing + this.seriesAreaPadding);
|
|
const scrollbar = this.chartLayout?.scrollbars?.[this.id];
|
|
const scrollbarThickness = this.getScrollbarThickness(scrollbar);
|
|
if (niceMode[0] === 2 /* Off */ && niceMode[1] === 2 /* Off */ && this.label.enabled === false && this.tick.enabled === false && this.gridLine.enabled === false) {
|
|
const { bbox: bbox2, spacing: spacing2 } = this.measureAxisLayout(domain, [], [], scrollbar, scrollbarThickness);
|
|
const layout2 = {
|
|
ticks: [],
|
|
tickLines: [],
|
|
gridLines: [],
|
|
gridFills: [],
|
|
labels: [],
|
|
spacing: spacing2
|
|
};
|
|
return {
|
|
ticks: [],
|
|
rawTickCount: 0,
|
|
tickDomain: domain,
|
|
niceDomain: domain,
|
|
fractionDigits: 0,
|
|
timeInterval: void 0,
|
|
bbox: bbox2,
|
|
layout: layout2
|
|
};
|
|
}
|
|
const { label, primaryLabel, scale: scale2, range: range3, interval, reverse, defaultTickMinSpacing, minimumTimeGranularity } = this;
|
|
const tickGenerationResult = generateTicks({
|
|
label,
|
|
scale: scale2,
|
|
interval,
|
|
primaryLabel,
|
|
domain,
|
|
range: range3,
|
|
reverse,
|
|
niceMode,
|
|
visibleRange,
|
|
defaultTickMinSpacing,
|
|
minimumTimeGranularity,
|
|
sideFlag,
|
|
labelOffset: labelX,
|
|
primaryTickCount: initialPrimaryTickCount,
|
|
axisRotation: this.horizontal ? Math.PI / -2 : 0,
|
|
isVertical: this.direction === "y" /* Y */,
|
|
sizeLimit: this.chartLayout?.sizeLimit,
|
|
inRange: (translation) => this.inRange(translation, 1e-3),
|
|
tickFormatter: (...args) => this.tickFormatter(...args)
|
|
});
|
|
const { tickData } = tickGenerationResult;
|
|
const removeOverflowLabels = this.label.avoidCollisions && this.horizontal && tickData.ticks.length > 2 && (ContinuousScale.is(this.scale) || DiscreteTimeScale.is(this.scale));
|
|
if (removeOverflowLabels) {
|
|
const removeOverflowThreshold = this.chartLayout?.padding.right ?? 0;
|
|
const lastTick = tickData.ticks.at(-1);
|
|
if (lastTick?.tickLabel != null && lastTick.translation + lastTick.textMetrics.width / 2 > range3[1] + removeOverflowThreshold) {
|
|
lastTick.tickLabel = void 0;
|
|
if (visibleRange[0] === 0 && visibleRange[1] === 1) {
|
|
tickData.ticks[0].tickLabel = void 0;
|
|
}
|
|
}
|
|
}
|
|
const { ticks, tickDomain, rawTicks, rawTickCount, fractionDigits, timeInterval: timeInterval3, niceDomain } = tickData;
|
|
const labels = ticks.map((d) => this.getTickLabelProps(d, tickGenerationResult, scrollbarThickness));
|
|
const { position, gridPadding, gridLength } = this;
|
|
const direction = position === "bottom" || position === "right" ? -1 : 1;
|
|
const p1 = direction * gridPadding;
|
|
const p2 = direction * (gridLength + gridPadding);
|
|
const gridLines = this.calculateGridLines(ticks, p1, p2);
|
|
const gridFills = this.calculateGridFills(ticks, p1, p2);
|
|
const tickLines = this.calculateTickLines(ticks, direction, scrollbarThickness);
|
|
const { bbox, spacing } = this.measureAxisLayout(tickDomain, ticks, labels, scrollbar, scrollbarThickness);
|
|
const layout = { ticks, gridLines, gridFills, tickLines, labels, spacing };
|
|
return { ticks: rawTicks, rawTickCount, tickDomain, niceDomain, fractionDigits, timeInterval: timeInterval3, bbox, layout };
|
|
}
|
|
calculateGridLines(ticks, p1, p2) {
|
|
return ticks.map((tick, index) => this.calculateGridLine(tick, index, p1, p2, ticks));
|
|
}
|
|
calculateGridLine({ index: tickIndex, tickId, translation: offset }, _index, p1, p2, _ticks) {
|
|
const { gridLine, horizontal } = this;
|
|
const [x1, y1, x2, y2] = horizontal ? [offset, p1, offset, p2] : [p1, offset, p2, offset];
|
|
const { style: style2 } = gridLine;
|
|
const { stroke: stroke3, strokeWidth = 0, lineDash } = style2[tickIndex % style2.length] ?? {};
|
|
return { tickId, offset, x1, y1, x2, y2, stroke: stroke3, strokeWidth, lineDash };
|
|
}
|
|
calculateGridFills(ticks, p1, p2) {
|
|
const { horizontal, range: range3, type } = this;
|
|
const gridFills = [];
|
|
if (ticks.length == 0)
|
|
return gridFills;
|
|
let gridFillIndexOffset = 0;
|
|
const isVerticalUnitTime = !horizontal && type === "unit-time";
|
|
const firstFillOffCanvas = isVerticalUnitTime && ticks[0].translation < range3[0] || !isVerticalUnitTime && ticks[0].translation > range3[0];
|
|
if (firstFillOffCanvas) {
|
|
const injectedTick = { tickId: `before:${ticks[0].tickId}`, translation: range3[0] };
|
|
gridFills.push(this.calculateGridFill(injectedTick, -1, ticks[0].index, p1, p2, ticks));
|
|
gridFillIndexOffset = 1;
|
|
}
|
|
gridFills.push(
|
|
...ticks.map(
|
|
(tick, index) => this.calculateGridFill(tick, index, tick.index + gridFillIndexOffset, p1, p2, ticks)
|
|
)
|
|
);
|
|
return gridFills;
|
|
}
|
|
calculateGridFill({ tickId, translation }, index, gridFillIndex, p1, p2, ticks) {
|
|
const { gridLine, horizontal, range: range3 } = this;
|
|
const nextTick = ticks[index + 1];
|
|
const startOffset = translation;
|
|
const endOffset = nextTick ? nextTick.translation : range3[1];
|
|
const [x1, y1, x2, y2] = horizontal ? [startOffset, Math.max(p1, p2), endOffset, Math.min(p1, p2)] : [Math.min(p1, p2), Math.min(startOffset, endOffset), Math.max(p1, p2), Math.max(startOffset, endOffset)];
|
|
const { fill, fillOpacity } = gridLine.style[gridFillIndex % gridLine.style.length] ?? {};
|
|
return { tickId, x1, y1, x2, y2, fill, fillOpacity };
|
|
}
|
|
calculateTickLines(ticks, direction, scrollbarThickness = 0) {
|
|
return ticks.map((tick) => this.calculateTickLine(tick, tick.index, direction, ticks, scrollbarThickness));
|
|
}
|
|
calculateTickLine({ isPrimary, tickId, translation: offset }, _index, direction, _ticks, scrollbarThickness = 0) {
|
|
const { horizontal, tick, primaryTick } = this;
|
|
const datumTick = isPrimary && primaryTick ? primaryTick : tick;
|
|
const tickSize = this.getTickSize(datumTick);
|
|
const tickSpacing = this.getTickSpacing(datumTick);
|
|
const tickOffset = -direction * (scrollbarThickness + tickSpacing);
|
|
const h = -direction * tickSize;
|
|
const [x1, y1, x2, y2] = horizontal ? [offset, tickOffset, offset, tickOffset + h] : [tickOffset, offset, tickOffset + h, offset];
|
|
const { stroke: stroke3, width: strokeWidth } = datumTick;
|
|
const lineDash = void 0;
|
|
return { tickId, offset, x1, y1, x2, y2, stroke: stroke3, strokeWidth, lineDash };
|
|
}
|
|
update() {
|
|
this.updateDirection();
|
|
const previousTicksIds = Array.from(this.tickLabelGroupSelection.nodes(), (node) => node.datum.tickId);
|
|
super.update();
|
|
const { tickLayout } = this;
|
|
this.updateTitle(this.scale.domain, tickLayout?.spacing ?? 0);
|
|
if (!this.animatable) {
|
|
this.moduleCtx.animationManager.skipCurrentBatch();
|
|
}
|
|
if (tickLayout) {
|
|
const { ticks } = tickLayout;
|
|
if (this.animationManager.isSkipped()) {
|
|
this.resetSelectionNodes();
|
|
} else {
|
|
const tickIds = ticks.map((datum) => datum.tickId);
|
|
const diff9 = diffArrays(previousTicksIds, tickIds);
|
|
this.animationState.transition("update", diff9);
|
|
}
|
|
}
|
|
const { enabled, stroke: stroke3, width: width2 } = this.line;
|
|
this.lineNode.setProperties({ stroke: stroke3, strokeWidth: enabled ? width2 : 0 });
|
|
this.updateTickLines();
|
|
this.updateGridLines();
|
|
this.updateGridFills();
|
|
}
|
|
getAxisTransform() {
|
|
return {
|
|
translationX: Math.floor(this.translation.x + this.crossAxisTranslation.x),
|
|
translationY: Math.floor(this.translation.y + this.crossAxisTranslation.y)
|
|
};
|
|
}
|
|
getLayoutTranslation() {
|
|
const { translationX, translationY } = this.getAxisTransform();
|
|
return { x: translationX, y: translationY };
|
|
}
|
|
getLayoutState() {
|
|
const layout = super.getLayoutState();
|
|
return { ...layout, position: this.position };
|
|
}
|
|
updatePosition() {
|
|
super.updatePosition();
|
|
const axisTransform = this.getAxisTransform();
|
|
this.tickLineGroup.datum = axisTransform;
|
|
this.tickLabelGroup.datum = axisTransform;
|
|
this.lineNodeGroup.datum = axisTransform;
|
|
this.headingLabelGroup.datum = axisTransform;
|
|
}
|
|
setAxisVisible(visible) {
|
|
this.tickLineGroup.visible = visible && (this.tick.enabled || (this.primaryTick?.enabled ?? false));
|
|
this.tickLabelGroup.visible = visible && (this.label.enabled || (this.primaryTick?.enabled ?? false));
|
|
this.lineNodeGroup.visible = visible;
|
|
this.headingLabelGroup.visible = visible;
|
|
}
|
|
getAxisLineCoordinates() {
|
|
const { horizontal } = this;
|
|
const [c1, c2] = findMinMax(this.lineRange ?? this.range);
|
|
return horizontal ? { x1: c1, x2: c2, y1: 0, y2: 0 } : { x1: 0, x2: 0, y1: c1, y2: c2 };
|
|
}
|
|
getTickLineBBox(datum, scrollbarThickness) {
|
|
const { translation } = datum;
|
|
const { position, primaryTick } = this;
|
|
let tickSize = this.getTickSize();
|
|
if (primaryTick?.enabled) {
|
|
tickSize = Math.max(tickSize, this.getTickSize(primaryTick));
|
|
}
|
|
const direction = position === "bottom" || position === "right" ? -1 : 1;
|
|
const tickSpacing = this.getTickSpacing(this.tick);
|
|
const tickOffset = -direction * (scrollbarThickness + tickSpacing);
|
|
const start2 = tickOffset;
|
|
const end3 = tickOffset - direction * (tickSize + tickSpacing);
|
|
const min = Math.min(start2, end3);
|
|
const max = Math.max(start2, end3);
|
|
switch (position) {
|
|
case "top":
|
|
return new BBox(translation, min, 0, max - min);
|
|
case "bottom":
|
|
return new BBox(translation, min, 0, max - min);
|
|
case "left":
|
|
return new BBox(min, translation, max - min, 0);
|
|
case "right":
|
|
return new BBox(min, translation, max - min, 0);
|
|
}
|
|
}
|
|
lineNodeBBox() {
|
|
const { position, seriesAreaPadding } = this;
|
|
const { y1, y2 } = this.getAxisLineCoordinates();
|
|
const dy2 = y2 - y1;
|
|
switch (position) {
|
|
case "top":
|
|
return new BBox(y1, -seriesAreaPadding, dy2, seriesAreaPadding);
|
|
case "bottom":
|
|
return new BBox(y1, 0, dy2, seriesAreaPadding);
|
|
case "left":
|
|
return new BBox(-seriesAreaPadding, y1, seriesAreaPadding, dy2);
|
|
case "right":
|
|
return new BBox(0, y1, seriesAreaPadding, dy2);
|
|
}
|
|
}
|
|
titleBBox(domain, spacing) {
|
|
const { tempCaption } = this;
|
|
tempCaption.node.setProperties(this.titleProps(tempCaption, domain, spacing));
|
|
return tempCaption.node.getBBox();
|
|
}
|
|
getScrollbarThickness(scrollbar) {
|
|
if (!scrollbar?.enabled)
|
|
return 0;
|
|
return scrollbar.placement === "inner" ? scrollbar.spacing + scrollbar.thickness : 0;
|
|
}
|
|
resolveScrollbarLayout(scrollbar, labelThickness) {
|
|
if (!scrollbar)
|
|
return void 0;
|
|
const { position } = this;
|
|
const direction = position === "top" || position === "left" ? -1 : 1;
|
|
if (scrollbar.placement === "inner") {
|
|
const offset2 = direction === 1 ? scrollbar.spacing : -scrollbar.spacing - scrollbar.thickness;
|
|
return { ...scrollbar, offset: offset2 };
|
|
}
|
|
const offset = direction === 1 ? labelThickness + scrollbar.spacing : -labelThickness - scrollbar.spacing - scrollbar.thickness;
|
|
return { ...scrollbar, offset };
|
|
}
|
|
applyScrollbarLayout(boxes, labelThickness, scrollbar) {
|
|
const scrollbarLayout = this.resolveScrollbarLayout(scrollbar, labelThickness);
|
|
let spacing = labelThickness;
|
|
if (scrollbarLayout) {
|
|
const { offset, thickness, placement } = scrollbarLayout;
|
|
if (placement === "outer") {
|
|
spacing += scrollbarLayout.spacing + thickness;
|
|
}
|
|
if (this.horizontal) {
|
|
boxes.push(new BBox(0, offset, 0, thickness));
|
|
} else {
|
|
boxes.push(new BBox(offset, 0, thickness, 0));
|
|
}
|
|
}
|
|
return { spacing, scrollbarLayout };
|
|
}
|
|
measureAxisLayout(domain, ticks, labels, scrollbar, scrollbarThickness) {
|
|
const { tick, primaryTick, label, primaryLabel, title, position, horizontal, seriesAreaPadding } = this;
|
|
const boxes = [];
|
|
boxes.push(this.lineNodeBBox());
|
|
if (tick.enabled || primaryTick?.enabled) {
|
|
for (const datum of ticks) {
|
|
boxes.push(this.getTickLineBBox(datum, scrollbarThickness));
|
|
}
|
|
}
|
|
const { tempText } = this;
|
|
if (label.enabled) {
|
|
for (const datum of labels) {
|
|
if (!datum.visible)
|
|
continue;
|
|
tempText.setProperties(datum);
|
|
const box = tempText.getBBox();
|
|
if (box) {
|
|
boxes.push(box);
|
|
}
|
|
}
|
|
}
|
|
if (primaryLabel?.enabled && position === "bottom") {
|
|
const inexactMeasurementPadding = 2;
|
|
boxes.push(
|
|
new BBox(
|
|
0,
|
|
calcLineHeight(label.fontSize) + inexactMeasurementPadding,
|
|
1,
|
|
this.getTickSize(tick) + this.getTickSpacing(tick) + label.spacing + seriesAreaPadding
|
|
)
|
|
);
|
|
if (primaryLabel.format != null) {
|
|
const { format } = primaryLabel;
|
|
const formats = isPlainObject(format) ? Object.values(format) : [format];
|
|
const maxLines = formats.reduce((m, f) => Math.max(m, countLines(f)), 0);
|
|
boxes.push(
|
|
new BBox(
|
|
0,
|
|
this.getTickSize(primaryTick ?? tick) + this.getTickSpacing(primaryTick ?? tick) + primaryLabel.spacing + seriesAreaPadding,
|
|
1,
|
|
maxLines * calcLineHeight(primaryLabel.fontSize) + inexactMeasurementPadding
|
|
)
|
|
);
|
|
}
|
|
}
|
|
const combined = BBox.merge(boxes);
|
|
const labelThickness = horizontal ? combined.height : combined.width;
|
|
const { spacing, scrollbarLayout } = this.applyScrollbarLayout(boxes, labelThickness, scrollbar);
|
|
this.layout.labelThickness = labelThickness;
|
|
this.layout.scrollbar = scrollbarLayout;
|
|
if (title.enabled) {
|
|
boxes.push(this.titleBBox(domain, spacing));
|
|
}
|
|
const bbox = BBox.merge(boxes);
|
|
return { bbox, spacing };
|
|
}
|
|
titleProps(caption, domain, spacing) {
|
|
const { title } = this;
|
|
if (!title.enabled) {
|
|
caption.enabled = false;
|
|
return {
|
|
visible: false,
|
|
text: "",
|
|
textBaseline: "bottom",
|
|
x: 0,
|
|
y: 0,
|
|
rotationCenterX: 0,
|
|
rotationCenterY: 0,
|
|
rotation: 0
|
|
};
|
|
}
|
|
caption.enabled = true;
|
|
caption.color = title.color;
|
|
caption.fontFamily = title.fontFamily;
|
|
caption.fontSize = title.fontSize;
|
|
caption.fontStyle = title.fontStyle;
|
|
caption.fontWeight = title.fontWeight;
|
|
caption.wrapping = title.wrapping;
|
|
const padding2 = (title.spacing ?? 0) + spacing;
|
|
const { range: range3 } = this;
|
|
const midOffset = (range3[0] + range3[1]) / 2;
|
|
let x;
|
|
let y;
|
|
let rotation;
|
|
let textBaseline;
|
|
switch (this.position) {
|
|
case "top":
|
|
x = midOffset;
|
|
y = -padding2;
|
|
rotation = 0;
|
|
textBaseline = "bottom";
|
|
break;
|
|
case "bottom":
|
|
x = midOffset;
|
|
y = padding2;
|
|
rotation = 0;
|
|
textBaseline = "top";
|
|
break;
|
|
case "left":
|
|
x = -padding2;
|
|
y = midOffset;
|
|
rotation = Math.PI / -2;
|
|
textBaseline = "bottom";
|
|
break;
|
|
case "right":
|
|
x = padding2;
|
|
y = midOffset;
|
|
rotation = Math.PI / 2;
|
|
textBaseline = "bottom";
|
|
break;
|
|
}
|
|
const { formatter: formatter2 = (p) => p.defaultValue } = title;
|
|
const text2 = this.cachedCallWithContext(formatter2, this.getTitleFormatterParams(domain));
|
|
caption.text = text2;
|
|
return {
|
|
visible: true,
|
|
text: text2,
|
|
textBaseline,
|
|
x,
|
|
y,
|
|
rotationCenterX: x,
|
|
rotationCenterY: y,
|
|
rotation
|
|
};
|
|
}
|
|
getTickLabelProps(datum, tickGenerationResult, scrollbarThickness) {
|
|
const { horizontal, primaryLabel, primaryTick, seriesAreaPadding, scale: scale2 } = this;
|
|
const { tickId, tickLabel: text2 = "", translation, isPrimary, textUntruncated } = datum;
|
|
const label = isPrimary && primaryLabel?.enabled ? primaryLabel : this.label;
|
|
const tick = isPrimary && primaryTick?.enabled ? primaryTick : this.tick;
|
|
const { rotation, textBaseline, textAlign } = tickGenerationResult;
|
|
const { range: range3 } = scale2;
|
|
const sideFlag = this.label.getSideFlag();
|
|
const borderOffset = expandLabelPadding(label)[this.position];
|
|
let labelOffset = sideFlag * (this.getTickSize(tick) + this.getTickSpacing(tick) + label.spacing + seriesAreaPadding) - borderOffset;
|
|
if (scrollbarThickness) {
|
|
labelOffset += sideFlag * scrollbarThickness;
|
|
}
|
|
const visible = text2 !== "";
|
|
const x = horizontal ? translation : labelOffset;
|
|
const y = horizontal ? -labelOffset : translation;
|
|
return {
|
|
...this.getLabelStyles({ value: datum.tick, formattedValue: text2 }, void 0, label),
|
|
tickId,
|
|
rotation,
|
|
text: text2,
|
|
textAlign,
|
|
textBaseline,
|
|
textUntruncated,
|
|
visible,
|
|
x,
|
|
y,
|
|
rotationCenterX: x,
|
|
rotationCenterY: y,
|
|
range: range3
|
|
};
|
|
}
|
|
updateSelections() {
|
|
if (!this.tickLayout)
|
|
return;
|
|
const lineData = this.getAxisLineCoordinates();
|
|
const { tickLines, gridLines, gridFills, labels } = this.tickLayout;
|
|
const getDatumId = (datum) => datum.tickId;
|
|
this.lineNode.datum = lineData;
|
|
this.gridLineGroupSelection.update(this.gridLine.enabled ? gridLines : [], void 0, getDatumId);
|
|
this.gridFillGroupSelection.update(this.gridLine.enabled ? gridFills : [], void 0, getDatumId);
|
|
this.tickLineGroupSelection.update(tickLines, void 0, getDatumId);
|
|
this.tickLabelGroupSelection.update(labels, void 0, getDatumId);
|
|
}
|
|
updateGridLines() {
|
|
this.gridLineGroupSelection.each((line, datum) => {
|
|
line.stroke = datum.stroke;
|
|
line.strokeWidth = datum.strokeWidth;
|
|
line.lineDash = datum.lineDash;
|
|
});
|
|
}
|
|
updateGridFills() {
|
|
this.gridFillGroupSelection.each((rect2, datum) => {
|
|
rect2.fill = datum.fill;
|
|
rect2.fillOpacity = datum.fillOpacity ?? 1;
|
|
});
|
|
}
|
|
updateTickLines() {
|
|
this.tickLineGroupSelection.each((line, datum) => {
|
|
line.stroke = datum.stroke;
|
|
line.strokeWidth = datum.strokeWidth;
|
|
line.lineDash = datum.lineDash;
|
|
});
|
|
}
|
|
updateTitle(domain, spacing) {
|
|
const { caption } = this.title;
|
|
const titleProps = this.titleProps(caption, domain, spacing);
|
|
caption.node.visible = titleProps.visible;
|
|
caption.node.text = titleProps.text;
|
|
caption.node.textBaseline = titleProps.textBaseline;
|
|
caption.node.datum = titleProps;
|
|
}
|
|
updateLabels() {
|
|
if (!this.label.enabled)
|
|
return;
|
|
this.tickLabelGroupSelection.each((node, datum) => {
|
|
node.fill = datum.color;
|
|
node.text = datum.text;
|
|
node.textBaseline = datum.textBaseline;
|
|
node.textAlign = datum.textAlign ?? "center";
|
|
node.pointerEvents = datum.textUntruncated == null ? 1 /* None */ : 0 /* All */;
|
|
node.setFont(datum);
|
|
node.setBoxing(datum);
|
|
});
|
|
}
|
|
animateReadyUpdate(diff9) {
|
|
const { animationManager } = this.moduleCtx;
|
|
const selectionCtx = prepareAxisAnimationContext(this);
|
|
const fns = prepareAxisAnimationFunctions(selectionCtx);
|
|
fromToMotion(
|
|
this.id,
|
|
"axis-group",
|
|
animationManager,
|
|
[this.lineNodeGroup, this.tickLabelGroup, this.tickLineGroup, this.headingLabelGroup],
|
|
fns.group
|
|
);
|
|
fromToMotion(this.id, "line", animationManager, [this.lineNode], fns.line);
|
|
fromToMotion(
|
|
this.id,
|
|
"line-paths",
|
|
animationManager,
|
|
[this.gridLineGroupSelection, this.tickLineGroupSelection],
|
|
fns.tick,
|
|
(_, d) => d.tickId,
|
|
diff9
|
|
);
|
|
fromToMotion(
|
|
this.id,
|
|
"tick-labels",
|
|
animationManager,
|
|
[this.tickLabelGroupSelection],
|
|
fns.label,
|
|
(_, d) => d.tickId,
|
|
diff9
|
|
);
|
|
fromToMotion(
|
|
this.id,
|
|
"title",
|
|
animationManager,
|
|
[this.title.caption.node],
|
|
fns.label,
|
|
(_, d) => d.tickId,
|
|
diff9
|
|
);
|
|
}
|
|
resetSelectionNodes() {
|
|
resetMotion(
|
|
[this.lineNodeGroup, this.tickLabelGroup, this.tickLineGroup, this.headingLabelGroup],
|
|
resetAxisGroupFn()
|
|
);
|
|
resetMotion([this.gridLineGroupSelection, this.tickLineGroupSelection], resetAxisLineSelectionFn());
|
|
resetMotion([this.gridFillGroupSelection], resetAxisFillSelectionFn());
|
|
resetMotion([this.tickLabelGroupSelection], resetAxisLabelSelectionFn());
|
|
resetMotion([this.title.caption.node], resetAxisLabelSelectionFn());
|
|
resetMotion([this.lineNode], resetAxisLineSelectionFn());
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _CartesianAxis.prototype, "thickness", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _CartesianAxis.prototype, "maxThicknessRatio", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _CartesianAxis.prototype, "position", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _CartesianAxis.prototype, "crossAt", 2);
|
|
var CartesianAxis = _CartesianAxis;
|
|
|
|
// packages/ag-charts-community/src/chart/mapping/prepareAxis.ts
|
|
var CartesianAxisPositions = ["right", "top", "left", "bottom"];
|
|
function isAxisPosition(position) {
|
|
return typeof position === "string" && CartesianAxisPositions.includes(position);
|
|
}
|
|
function guessInvalidPositions(axes) {
|
|
const invalidAxes = [];
|
|
const usedPositions = [];
|
|
const guesses = [...CartesianAxisPositions];
|
|
for (const axis of axes) {
|
|
if (axis instanceof CartesianAxis) {
|
|
if (isAxisPosition(axis.position)) {
|
|
usedPositions.push(axis.position);
|
|
} else {
|
|
invalidAxes.push(axis);
|
|
}
|
|
}
|
|
}
|
|
for (const axis of invalidAxes) {
|
|
let nextGuess;
|
|
do {
|
|
nextGuess = guesses.pop();
|
|
} while (nextGuess && usedPositions.includes(nextGuess));
|
|
if (nextGuess == null)
|
|
break;
|
|
axis.position = nextGuess;
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/mapping/prepareSeries.ts
|
|
var DEFAULT_MATCHING_KEYS = ["direction", "xKey", "yKey", "sizeKey", "angleKey", "radiusKey", "normalizedTo"];
|
|
function matchSeriesOptions(series, optSeries, oldOptsSeries) {
|
|
const matchingKeysCache = /* @__PURE__ */ new Map();
|
|
const getMatchingKeys = (type) => {
|
|
if (type === void 0) {
|
|
return DEFAULT_MATCHING_KEYS;
|
|
}
|
|
if (matchingKeysCache.has(type))
|
|
return matchingKeysCache.get(type);
|
|
const matchingKeys = moduleRegistry_exports.getSeriesModule(type)?.matchingKeys ?? DEFAULT_MATCHING_KEYS;
|
|
matchingKeysCache.set(type, matchingKeys);
|
|
return matchingKeys;
|
|
};
|
|
const generateKey = (type, i, opts) => {
|
|
const matchingKeys = getMatchingKeys(type);
|
|
const result = [type];
|
|
for (const key of matchingKeys) {
|
|
if (key in i && i[key] != null)
|
|
result.push(`${key}=${i[key]}`);
|
|
}
|
|
if (opts?.seriesGrouping) {
|
|
result.push(`seriesGrouping.groupId=${opts?.seriesGrouping.groupId}`);
|
|
}
|
|
return result.join(";");
|
|
};
|
|
const seriesMap = /* @__PURE__ */ new Map();
|
|
let idx = 0;
|
|
for (const s of series) {
|
|
const key = generateKey(s.type, s.properties, oldOptsSeries?.[idx]);
|
|
if (!seriesMap.has(key)) {
|
|
seriesMap.set(key, []);
|
|
}
|
|
seriesMap.get(key)?.push([s, idx++]);
|
|
}
|
|
const optsMap = /* @__PURE__ */ new Map();
|
|
idx = 0;
|
|
for (const o of optSeries) {
|
|
const key = generateKey(o.type, o, o);
|
|
if (!optsMap.has(key)) {
|
|
optsMap.set(key, []);
|
|
}
|
|
optsMap.get(key)?.push([o, idx++]);
|
|
}
|
|
const overlap = [...seriesMap.keys()].some((k) => optsMap.has(k));
|
|
if (!overlap) {
|
|
return { status: "no-overlap", oldKeys: seriesMap.keys(), newKeys: optsMap.keys() };
|
|
}
|
|
const changes = [];
|
|
for (const [key, optsTuples] of optsMap.entries()) {
|
|
for (const [opts, targetIdx] of optsTuples) {
|
|
const seriesArray = seriesMap.get(key);
|
|
if (seriesArray == null || seriesArray.length < 1) {
|
|
changes.push({ opts, targetIdx, idx: targetIdx, status: "add" });
|
|
seriesMap.delete(key);
|
|
continue;
|
|
}
|
|
const [outputSeries, currentIdx] = seriesArray.shift();
|
|
const previousOpts = oldOptsSeries?.[currentIdx] ?? {};
|
|
const diff9 = jsonDiff(previousOpts, opts ?? {});
|
|
const { groupIndex, stackIndex } = diff9?.seriesGrouping ?? {};
|
|
if (groupIndex != null || stackIndex != null) {
|
|
changes.push({
|
|
opts,
|
|
series: outputSeries,
|
|
diff: diff9,
|
|
targetIdx,
|
|
idx: currentIdx,
|
|
status: "series-grouping"
|
|
});
|
|
} else if (diff9) {
|
|
changes.push({
|
|
opts,
|
|
series: outputSeries,
|
|
diff: diff9,
|
|
targetIdx,
|
|
idx: currentIdx,
|
|
status: "update"
|
|
});
|
|
} else {
|
|
changes.push({ opts, series: outputSeries, targetIdx, idx: currentIdx, status: "no-op" });
|
|
}
|
|
if (seriesArray.length === 0) {
|
|
seriesMap.delete(key);
|
|
}
|
|
}
|
|
}
|
|
for (const seriesArray of seriesMap.values()) {
|
|
for (const [outputSeries, currentIdx] of seriesArray) {
|
|
changes.push({ series: outputSeries, idx: currentIdx, targetIdx: -1, status: "remove" });
|
|
}
|
|
}
|
|
return { status: "overlap", changes };
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/factory/expectedModules.ts
|
|
var ExpectedModules = new Map(
|
|
[
|
|
// Chart types
|
|
{
|
|
type: "chart",
|
|
name: "cartesian",
|
|
moduleId: "CartesianChartModule"
|
|
},
|
|
{
|
|
type: "chart",
|
|
name: "standalone",
|
|
moduleId: "StandaloneChartModule",
|
|
enterprise: true
|
|
},
|
|
{
|
|
type: "chart",
|
|
name: "polar",
|
|
moduleId: "PolarChartModule"
|
|
},
|
|
{
|
|
type: "chart",
|
|
name: "topology",
|
|
moduleId: "TopologyChartModule",
|
|
enterprise: true
|
|
},
|
|
// Axis types
|
|
{
|
|
type: "axis",
|
|
name: "number",
|
|
chartType: "cartesian",
|
|
moduleId: "NumberAxisModule"
|
|
},
|
|
{
|
|
type: "axis",
|
|
name: "log",
|
|
chartType: "cartesian",
|
|
moduleId: "LogAxisModule"
|
|
},
|
|
{
|
|
type: "axis",
|
|
name: "time",
|
|
chartType: "cartesian",
|
|
moduleId: "TimeAxisModule"
|
|
},
|
|
{
|
|
type: "axis",
|
|
name: "unit-time",
|
|
chartType: "cartesian",
|
|
moduleId: "UnitTimeAxisModule"
|
|
},
|
|
{
|
|
type: "axis",
|
|
name: "category",
|
|
chartType: "cartesian",
|
|
moduleId: "CategoryAxisModule"
|
|
},
|
|
{
|
|
type: "axis",
|
|
name: "grouped-category",
|
|
chartType: "cartesian",
|
|
moduleId: "GroupedCategoryAxisModule"
|
|
},
|
|
{
|
|
type: "axis",
|
|
name: "ordinal-time",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "OrdinalTimeAxisModule"
|
|
},
|
|
{
|
|
type: "axis",
|
|
name: "angle-category",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
moduleId: "AngleCategoryAxisModule"
|
|
},
|
|
{
|
|
type: "axis",
|
|
name: "angle-number",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
moduleId: "AngleNumberAxisModule"
|
|
},
|
|
{
|
|
type: "axis",
|
|
name: "radius-category",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
moduleId: "RadiusCategoryAxisModule"
|
|
},
|
|
{
|
|
type: "axis",
|
|
name: "radius-number",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
moduleId: "RadiusNumberAxisModule"
|
|
},
|
|
// Series types
|
|
{
|
|
type: "series",
|
|
name: "bar",
|
|
chartType: "cartesian",
|
|
moduleId: "BarSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "scatter",
|
|
chartType: "cartesian",
|
|
moduleId: "ScatterSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "bubble",
|
|
chartType: "cartesian",
|
|
moduleId: "BubbleSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "line",
|
|
chartType: "cartesian",
|
|
moduleId: "LineSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "area",
|
|
chartType: "cartesian",
|
|
moduleId: "AreaSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "pie",
|
|
chartType: "polar",
|
|
moduleId: "PieSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "donut",
|
|
chartType: "polar",
|
|
moduleId: "DonutSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "box-plot",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "BoxPlotSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "candlestick",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "CandlestickSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "cone-funnel",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "ConeFunnelSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "funnel",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "FunnelSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "ohlc",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "OhlcSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "heatmap",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "HeatmapSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "histogram",
|
|
chartType: "cartesian",
|
|
// enterprise: true,
|
|
moduleId: "HistogramSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "range-area",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "RangeAreaSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "range-bar",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "RangeBarSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "waterfall",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "WaterfallSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "nightingale",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
moduleId: "NightingaleSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "radar-area",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
moduleId: "RadarAreaSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "radar-line",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
moduleId: "RadarLineSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "radial-bar",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
moduleId: "RadialBarSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "radial-column",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
moduleId: "RadialColumnSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "map-shape",
|
|
chartType: "topology",
|
|
enterprise: true,
|
|
moduleId: "MapShapeSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "map-line",
|
|
chartType: "topology",
|
|
enterprise: true,
|
|
moduleId: "MapLineSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "map-marker",
|
|
chartType: "topology",
|
|
enterprise: true,
|
|
moduleId: "MapMarkerSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "map-shape-background",
|
|
chartType: "topology",
|
|
enterprise: true,
|
|
moduleId: "MapShapeBackgroundSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "map-line-background",
|
|
chartType: "topology",
|
|
enterprise: true,
|
|
moduleId: "MapLineBackgroundSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "pyramid",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
moduleId: "PyramidSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "linear-gauge",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
moduleId: "LinearGaugeModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "radial-gauge",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
moduleId: "RadialGaugeModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "sunburst",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
moduleId: "SunburstSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "treemap",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
moduleId: "TreemapSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "chord",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
moduleId: "ChordSeriesModule"
|
|
},
|
|
{
|
|
type: "series",
|
|
name: "sankey",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
moduleId: "SankeySeriesModule"
|
|
},
|
|
// Plugins
|
|
{
|
|
type: "plugin",
|
|
name: "animation",
|
|
enterprise: true,
|
|
moduleId: "AnimationModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "annotations",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "AnnotationsModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "legend",
|
|
moduleId: "LegendModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "locale",
|
|
moduleId: "LocaleModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "chartToolbar",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "ChartToolbarModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "contextMenu",
|
|
enterprise: true,
|
|
moduleId: "ContextMenuModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "statusBar",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "StatusBarModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "dataSource",
|
|
enterprise: true,
|
|
moduleId: "DataSourceModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "sync",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "SyncModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "ranges",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "RangesModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "zoom",
|
|
enterprise: true,
|
|
moduleId: "ZoomModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "flashOnUpdate",
|
|
enterprise: true,
|
|
moduleId: "FlashOnUpdateModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "gradientLegend",
|
|
enterprise: true,
|
|
moduleId: "GradientLegendModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "navigator",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "NavigatorModule"
|
|
},
|
|
{
|
|
type: "plugin",
|
|
name: "scrollbar",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "ScrollbarModule"
|
|
},
|
|
{
|
|
type: "axis:plugin",
|
|
name: "crosshair",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "CrosshairModule"
|
|
},
|
|
{
|
|
type: "axis:plugin",
|
|
name: "bandHighlight",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "BandHighlightModule"
|
|
},
|
|
{
|
|
type: "series:plugin",
|
|
name: "errorBar",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "ErrorBarsModule"
|
|
},
|
|
{
|
|
type: "preset",
|
|
name: "gauge-preset",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
moduleId: "GaugePresetModule"
|
|
},
|
|
{
|
|
type: "preset",
|
|
name: "price-volume",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
moduleId: "PriceVolumePresetModule"
|
|
},
|
|
{
|
|
type: "preset",
|
|
name: "sparkline",
|
|
moduleId: "SparklinePresetModule"
|
|
}
|
|
].map((m) => [m.name, m])
|
|
);
|
|
function getSeriesExpectedChartType(seriesName) {
|
|
const expectedModule = ExpectedModules.get(seriesName);
|
|
return expectedModule?.type === "series" /* Series */ ? expectedModule.chartType : void 0;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/mapping/types.ts
|
|
function detectChartType(input) {
|
|
const mainSeriesType = input.series?.[0]?.type ?? "line";
|
|
return moduleRegistry_exports.getSeriesModule(mainSeriesType)?.chartType ?? getSeriesExpectedChartType(mainSeriesType) ?? "unknown";
|
|
}
|
|
function isAgCartesianChartOptions(input) {
|
|
return detectChartType(input) === "cartesian";
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/modulesManager.ts
|
|
var ModulesManager = class extends ModuleMap {
|
|
*legends() {
|
|
for (const module2 of moduleRegistry_exports.listModulesByType("plugin" /* Plugin */)) {
|
|
if (module2.name === "legend" || module2.name === "gradientLegend") {
|
|
yield {
|
|
legendType: module2.name === "legend" ? "category" : "gradient",
|
|
legend: this.getModule(module2.name)
|
|
};
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/overlay/overlay.ts
|
|
var DEFAULT_OVERLAY_CLASS = "ag-charts-overlay";
|
|
var DEFAULT_OVERLAY_DARK_CLASS = "ag-charts-dark-overlay";
|
|
var Overlay = class extends BaseProperties {
|
|
constructor(className, defaultMessageId) {
|
|
super();
|
|
this.className = className;
|
|
this.defaultMessageId = defaultMessageId;
|
|
this.enabled = true;
|
|
}
|
|
getText(localeManager) {
|
|
if (isArray(this.text)) {
|
|
return toPlainText(this.text);
|
|
}
|
|
if (this.rendererAsText) {
|
|
return this.rendererAsText;
|
|
}
|
|
return localeManager.t(toTextString(this.text) || this.defaultMessageId);
|
|
}
|
|
getElement(callers, animationManager, localeManager, rect2) {
|
|
this.content?.remove();
|
|
this.rendererAsText = void 0;
|
|
this.focusBox = rect2;
|
|
if (this.renderer) {
|
|
const params = {};
|
|
const htmlContent = callWithContext(callers, this.renderer, params);
|
|
if (isHTMLElement(htmlContent)) {
|
|
this.content = htmlContent;
|
|
} else {
|
|
const tempDiv = createElement("div");
|
|
tempDiv.innerHTML = htmlContent;
|
|
const { firstElementChild } = tempDiv;
|
|
if (isHTMLElement(firstElementChild) && tempDiv.childElementCount === 1) {
|
|
this.content = firstElementChild;
|
|
} else {
|
|
this.content = tempDiv;
|
|
}
|
|
}
|
|
this.rendererAsText = this.content?.textContent?.trim() ?? void 0;
|
|
} else {
|
|
const content = createElement("div", {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
boxSizing: "border-box",
|
|
height: "100%",
|
|
margin: "8px",
|
|
fontFamily: "var(--ag-charts-font-family)",
|
|
fontSize: "var(--ag-charts-font-size)",
|
|
fontWeight: "var(--ag-charts-font-weight)"
|
|
});
|
|
if (isArray(this.text)) {
|
|
const container = createElement("div");
|
|
for (const segment of this.text) {
|
|
const el = createElement("span", {
|
|
color: segment.color,
|
|
fontSize: `${segment.fontSize}px`,
|
|
fontFamily: segment.fontFamily ?? "inherit",
|
|
fontWeight: String(segment.fontWeight),
|
|
fontStyle: segment.fontStyle
|
|
});
|
|
el.innerText = toTextString(segment.text);
|
|
container.appendChild(el);
|
|
}
|
|
content.appendChild(container);
|
|
} else {
|
|
content.innerText = this.getText(localeManager);
|
|
}
|
|
this.content = content;
|
|
this.content.classList.add(this.className);
|
|
animationManager?.animate({
|
|
from: 0,
|
|
to: 1,
|
|
id: "overlay",
|
|
phase: "add",
|
|
groupId: "opacity",
|
|
onUpdate(value) {
|
|
content.style.opacity = String(value);
|
|
},
|
|
onStop() {
|
|
content.style.opacity = "1";
|
|
}
|
|
});
|
|
}
|
|
return this.content;
|
|
}
|
|
removeElement(cleanup = () => this.content?.remove(), animationManager) {
|
|
if (!this.content)
|
|
return;
|
|
if (animationManager) {
|
|
const { content } = this;
|
|
animationManager.animate({
|
|
from: 1,
|
|
to: 0,
|
|
phase: "remove",
|
|
id: "overlay",
|
|
groupId: "opacity",
|
|
onUpdate(value) {
|
|
content.style.opacity = String(value);
|
|
},
|
|
onStop() {
|
|
cleanup?.();
|
|
}
|
|
});
|
|
} else {
|
|
cleanup?.();
|
|
}
|
|
this.content = void 0;
|
|
this.focusBox = void 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Overlay.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Overlay.prototype, "text", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Overlay.prototype, "renderer", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/overlay/chartOverlays.ts
|
|
var ChartOverlays = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.darkTheme = false;
|
|
this.loading = new Overlay("ag-charts-loading-overlay", "overlayLoadingData");
|
|
this.noData = new Overlay("ag-charts-no-data-overlay", "overlayNoData");
|
|
this.noVisibleSeries = new Overlay("ag-charts-no-visible-series", "overlayNoVisibleSeries");
|
|
this.unsupportedBrowser = new Overlay("ag-charts-unsupported-browser", "overlayUnsupportedBrowser");
|
|
}
|
|
getFocusInfo(localeManager) {
|
|
for (const overlay of [this.loading, this.noData, this.noVisibleSeries, this.unsupportedBrowser]) {
|
|
if (overlay.focusBox !== void 0) {
|
|
return { text: overlay.getText(localeManager), rect: overlay.focusBox };
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
destroy() {
|
|
this.loading.removeElement();
|
|
this.noData.removeElement();
|
|
this.noVisibleSeries.removeElement();
|
|
this.unsupportedBrowser.removeElement();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChartOverlays.prototype, "darkTheme", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChartOverlays.prototype, "loading", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChartOverlays.prototype, "noData", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChartOverlays.prototype, "noVisibleSeries", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChartOverlays.prototype, "unsupportedBrowser", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/overlay/loadingSpinner.ts
|
|
function getLoadingSpinner(text2, defaultDuration) {
|
|
const { animationDuration } = PHASE_METADATA["add"];
|
|
const duration = animationDuration * defaultDuration;
|
|
const container = createElement("div", `${DEFAULT_OVERLAY_CLASS}--loading`, {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
flexDirection: "column",
|
|
height: "100%",
|
|
boxSizing: "border-box",
|
|
font: "13px Verdana, sans-serif",
|
|
// FONT_SIZE.MEDIUM
|
|
userSelect: "none",
|
|
animation: `ag-charts-loading ${duration}ms linear 50ms both`
|
|
});
|
|
const matrix = createElement("span", {
|
|
width: "45px",
|
|
height: "40px",
|
|
backgroundImage: [
|
|
"linear-gradient(#0000 calc(1 * 100% / 6), #ccc 0 calc(3 * 100% / 6), #0000 0), ",
|
|
"linear-gradient(#0000 calc(2 * 100% / 6), #ccc 0 calc(4 * 100% / 6), #0000 0), ",
|
|
"linear-gradient(#0000 calc(3 * 100% / 6), #ccc 0 calc(5 * 100% / 6), #0000 0)"
|
|
].join(""),
|
|
backgroundSize: "10px 400%",
|
|
backgroundRepeat: "no-repeat",
|
|
animation: "ag-charts-loading-matrix 1s infinite linear"
|
|
});
|
|
const label = createElement("p", { marginTop: "1em" });
|
|
label.innerText = text2;
|
|
const background = createElement("div", `${DEFAULT_OVERLAY_CLASS}__loading-background`, {
|
|
position: "absolute",
|
|
inset: "0",
|
|
opacity: "0.5",
|
|
zIndex: "-1"
|
|
});
|
|
const animationStyles = createElement("style");
|
|
animationStyles.innerText = [
|
|
"@keyframes ag-charts-loading { from { opacity: 0 } to { opacity: 1 } }",
|
|
"@keyframes ag-charts-loading-matrix {",
|
|
"0% { background-position: 0% 0%, 50% 0%, 100% 0%; }",
|
|
"100% { background-position: 0% 100%, 50% 100%, 100% 100%; }",
|
|
"}"
|
|
].join(" ");
|
|
container.replaceChildren(animationStyles, matrix, label, background);
|
|
return container;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series-area/seriesArea.ts
|
|
var SeriesArea = class extends BaseProperties {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.rectNode = new Rect();
|
|
this.border = new Border(this.rectNode);
|
|
this.cornerRadius = 0;
|
|
this.padding = 0;
|
|
this.cleanup = new CleanupRegistry();
|
|
this.node = this.createNode();
|
|
this.node.append([this.rectNode]);
|
|
this.rectNode.fill = void 0;
|
|
this.cleanup.register(
|
|
ctx.scene.attachNode(this.node),
|
|
ctx.eventsHub.on("layout:complete", (e) => this.onLayoutComplete(e))
|
|
);
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
}
|
|
getPadding() {
|
|
const { border, padding: padding2 } = this;
|
|
const strokeWidth = border.enabled ? border.strokeWidth : 0;
|
|
if (typeof padding2 === "number") {
|
|
const total = padding2 + strokeWidth;
|
|
return { top: total, right: total, bottom: total, left: total };
|
|
}
|
|
return {
|
|
top: (padding2.top ?? 0) + strokeWidth,
|
|
right: (padding2.right ?? 0) + strokeWidth,
|
|
bottom: (padding2.bottom ?? 0) + strokeWidth,
|
|
left: (padding2.left ?? 0) + strokeWidth
|
|
};
|
|
}
|
|
createNode() {
|
|
return new Group({ name: "series-area-container", zIndex: 4 /* SERIES_AREA_CONTAINER */ });
|
|
}
|
|
onLayoutComplete(event) {
|
|
const { x, y, width: width2, height: height2 } = event.series.paddedRect;
|
|
this.rectNode.x = x;
|
|
this.rectNode.y = y;
|
|
this.rectNode.width = width2;
|
|
this.rectNode.height = height2;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesArea.prototype, "border", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesArea.prototype, "clip", 2);
|
|
__decorateClass([
|
|
ProxyPropertyOnWrite("rectNode", "cornerRadius"),
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesArea.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesArea.prototype, "padding", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/series/shapeUtil.ts
|
|
function getShapeFill(fill, defaultGradient, defaultPattern, defaultImage) {
|
|
if (isGradientFill(fill)) {
|
|
return {
|
|
type: "gradient",
|
|
gradient: fill.gradient ?? defaultGradient.gradient,
|
|
colorStops: fill.colorStops ?? defaultGradient.colorStops,
|
|
bounds: fill.bounds ?? defaultGradient.bounds,
|
|
rotation: fill.rotation ?? defaultGradient.rotation,
|
|
reverse: fill.reverse ?? defaultGradient.reverse,
|
|
colorSpace: fill.colorSpace ?? defaultGradient.colorSpace
|
|
};
|
|
}
|
|
if (isPatternFill(fill)) {
|
|
const pattern = fill.pattern ?? defaultPattern.pattern;
|
|
let strokeWidth = fill.strokeWidth;
|
|
if (pattern === "backward-slanted-lines" || pattern === "forward-slanted-lines" || pattern === "horizontal-lines" || pattern === "vertical-lines") {
|
|
strokeWidth ?? (strokeWidth = defaultPattern.strokeWidth);
|
|
} else {
|
|
strokeWidth ?? (strokeWidth = 0);
|
|
}
|
|
const width2 = fill.width ?? fill.height ?? defaultPattern.width;
|
|
const height2 = fill.height ?? fill.width ?? defaultPattern.height;
|
|
return {
|
|
type: "pattern",
|
|
pattern,
|
|
width: width2,
|
|
height: height2,
|
|
path: fill.path,
|
|
padding: fill.padding ?? defaultPattern.padding,
|
|
fill: fill.fill ?? defaultPattern.fill,
|
|
fillOpacity: fill.fillOpacity ?? defaultPattern.fillOpacity,
|
|
backgroundFill: fill.backgroundFill ?? defaultPattern.backgroundFill,
|
|
backgroundFillOpacity: fill.backgroundFillOpacity ?? defaultPattern.backgroundFillOpacity,
|
|
stroke: fill.stroke ?? defaultPattern.stroke,
|
|
strokeOpacity: fill.strokeOpacity ?? defaultPattern.strokeOpacity,
|
|
strokeWidth,
|
|
rotation: fill.rotation ?? defaultPattern.rotation,
|
|
scale: fill.scale ?? defaultPattern.scale
|
|
};
|
|
}
|
|
if (isImageFill(fill)) {
|
|
return {
|
|
type: "image",
|
|
url: fill.url,
|
|
width: fill.width,
|
|
height: fill.height,
|
|
backgroundFill: fill.backgroundFill ?? defaultImage.backgroundFill,
|
|
backgroundFillOpacity: fill.backgroundFillOpacity ?? defaultImage.backgroundFillOpacity,
|
|
rotation: fill.rotation ?? defaultImage.rotation,
|
|
repeat: fill.repeat ?? defaultImage.repeat,
|
|
fit: fill.fit ?? defaultImage.fit
|
|
};
|
|
}
|
|
return fill;
|
|
}
|
|
function getShapeStyle(style2, defaultGradient, defaultPattern, defaultImage) {
|
|
if (!isGradientFill(style2?.fill) && !isPatternFill(style2?.fill) && !isImageFill(style2?.fill))
|
|
return style2;
|
|
return {
|
|
...style2,
|
|
fill: getShapeFill(style2.fill, defaultGradient, defaultPattern, defaultImage)
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/series.ts
|
|
var SeriesNodePickMode = /* @__PURE__ */ ((SeriesNodePickMode18) => {
|
|
SeriesNodePickMode18[SeriesNodePickMode18["EXACT_SHAPE_MATCH"] = 0] = "EXACT_SHAPE_MATCH";
|
|
SeriesNodePickMode18[SeriesNodePickMode18["NEAREST_NODE"] = 1] = "NEAREST_NODE";
|
|
SeriesNodePickMode18[SeriesNodePickMode18["AXIS_ALIGNED"] = 2] = "AXIS_ALIGNED";
|
|
return SeriesNodePickMode18;
|
|
})(SeriesNodePickMode || {});
|
|
var CROSS_FILTER_MARKER_FILL_OPACITY_FACTOR = 0.25;
|
|
var CROSS_FILTER_MARKER_STROKE_OPACITY_FACTOR = 0.125;
|
|
var SeriesNodeEvent = class {
|
|
constructor(type, event, nodeDatum, series) {
|
|
this.type = type;
|
|
this.event = event;
|
|
this.defaultPrevented = false;
|
|
this.datum = nodeDatum.datum;
|
|
this.seriesId = series.id;
|
|
}
|
|
preventDefault() {
|
|
this.defaultPrevented = true;
|
|
}
|
|
};
|
|
var SeriesGroupingChangedEvent = class {
|
|
constructor(series, seriesGrouping) {
|
|
this.series = series;
|
|
this.seriesGrouping = seriesGrouping;
|
|
this.type = "groupingChanged";
|
|
}
|
|
};
|
|
function propertyAxisDirection(property) {
|
|
switch (property) {
|
|
case "x":
|
|
return "x" /* X */;
|
|
case "y":
|
|
return "y" /* Y */;
|
|
case "angle":
|
|
return "angle" /* Angle */;
|
|
case "radius":
|
|
return "radius" /* Radius */;
|
|
}
|
|
}
|
|
function axisDirectionProperty(direction) {
|
|
switch (direction) {
|
|
case "x" /* X */:
|
|
return "x";
|
|
case "y" /* Y */:
|
|
return "y";
|
|
case "angle" /* Angle */:
|
|
return "angle";
|
|
case "radius" /* Radius */:
|
|
return "radius";
|
|
default:
|
|
return "x";
|
|
}
|
|
}
|
|
var Series = class extends Observable {
|
|
constructor(seriesOpts) {
|
|
super();
|
|
this.cleanup = new CleanupRegistry();
|
|
this.usesPlacedLabels = false;
|
|
this.alwaysClip = false;
|
|
this.hasChangesOnHighlight = false;
|
|
this.seriesGrouping = void 0;
|
|
this.NodeEvent = SeriesNodeEvent;
|
|
this.internalId = createId(this);
|
|
// The group node that contains the series rendering in its default (non-highlighted) state.
|
|
this.contentGroup = new TranslatableGroup({
|
|
name: `${this.internalId}-content`,
|
|
zIndex: 1 /* ANY_CONTENT */
|
|
});
|
|
// The group node that contains all highlighted series items. This is a performance optimisation
|
|
// for large-scale data-sets, where the only thing that routinely varies is the currently
|
|
// highlighted node.
|
|
this.highlightGroup = new TranslatableGroup({
|
|
name: `${this.internalId}-highlight`,
|
|
zIndex: 1 /* ANY_CONTENT */
|
|
});
|
|
this.highlightNodeGroup = this.highlightGroup.appendChild(
|
|
new Group({ name: `${this.internalId}-highlight-node` })
|
|
);
|
|
this.highlightLabelGroup = this.highlightGroup.appendChild(
|
|
new Group({
|
|
name: `${this.internalId}-highlight-label`,
|
|
zIndex: 2 /* LABEL */
|
|
})
|
|
);
|
|
// Error bars etc.
|
|
this.annotationGroup = new TranslatableGroup({
|
|
name: `${this.internalId}-annotation`
|
|
});
|
|
// Lazily initialised labelGroup for label presentation.
|
|
this.labelGroup = new TranslatableGroup({
|
|
name: `${this.internalId}-series-labels`
|
|
});
|
|
this.axes = {};
|
|
this.directions = ["x" /* X */, "y" /* Y */];
|
|
// Flag to determine if we should recalculate node data.
|
|
this.nodeDataRefresh = true;
|
|
this.processedDataUpdated = true;
|
|
this.moduleMap = new ModuleMap();
|
|
this.datumCallbackCache = /* @__PURE__ */ new Map();
|
|
this.connectsToYAxis = false;
|
|
this.declarationOrder = -1;
|
|
this._broughtToFront = false;
|
|
this.events = new EventEmitter();
|
|
this._pickNodeCache = new LRUCache(5);
|
|
// Use a wrapper to comply with the @typescript-eslint/unbound-method rule.
|
|
this.fireEventWrapper = (event) => super.fireEvent(event);
|
|
const {
|
|
moduleCtx,
|
|
pickModes,
|
|
propertyKeys = {},
|
|
propertyNames = {},
|
|
canHaveAxes = false,
|
|
usesPlacedLabels = false,
|
|
alwaysClip = false
|
|
} = seriesOpts;
|
|
this.ctx = moduleCtx;
|
|
this.propertyKeys = propertyKeys;
|
|
this.propertyNames = propertyNames;
|
|
this.canHaveAxes = canHaveAxes;
|
|
this.usesPlacedLabels = usesPlacedLabels;
|
|
this.pickModes = pickModes;
|
|
this.alwaysClip = alwaysClip;
|
|
this.highlightLabelGroup.pointerEvents = 1 /* None */;
|
|
this.cleanup.register(
|
|
this.ctx.eventsHub.on("data:update", (data) => this.setChartData(data)),
|
|
this.ctx.eventsHub.on("highlight:change", (event) => this.onChangeHighlight(event))
|
|
);
|
|
}
|
|
get pickModeAxis() {
|
|
return "main";
|
|
}
|
|
get id() {
|
|
return this.properties?.id ?? this.internalId;
|
|
}
|
|
get type() {
|
|
return this.constructor.type ?? "";
|
|
}
|
|
get focusable() {
|
|
return true;
|
|
}
|
|
get data() {
|
|
return this._data ?? this._chartData;
|
|
}
|
|
set visible(newVisibility) {
|
|
this.properties.visible = newVisibility;
|
|
this.ctx.legendManager.toggleItem(newVisibility, this.id);
|
|
this.ctx.legendManager.update();
|
|
this.visibleMaybeChanged();
|
|
}
|
|
get visible() {
|
|
return this.ctx.legendManager.getSeriesEnabled(this.id) ?? this.properties.visible;
|
|
}
|
|
get hasData() {
|
|
const dataSet = this.data;
|
|
if (dataSet == null)
|
|
return false;
|
|
return dataSet.netSize() > 0;
|
|
}
|
|
get tooltipEnabled() {
|
|
return this.properties.tooltip?.enabled;
|
|
}
|
|
onDataChange() {
|
|
this.nodeDataRefresh = true;
|
|
this.processedDataUpdated = true;
|
|
this._pickNodeCache.clear();
|
|
}
|
|
setOptionsData(input) {
|
|
this._data = input;
|
|
this.onDataChange();
|
|
}
|
|
isHighlightEnabled() {
|
|
return this.properties.highlight.enabled;
|
|
}
|
|
setChartData(input) {
|
|
this._chartData = input;
|
|
if (this.data === input) {
|
|
this.onDataChange();
|
|
}
|
|
}
|
|
onSeriesGroupingChange(prev, next) {
|
|
const { internalId, type, visible } = this;
|
|
if (prev) {
|
|
this.ctx.seriesStateManager.deregisterSeries(this);
|
|
}
|
|
if (next) {
|
|
this.ctx.seriesStateManager.registerSeries({
|
|
internalId,
|
|
type,
|
|
visible,
|
|
seriesGrouping: next,
|
|
// TODO: is there a better way to pass width through here?
|
|
width: "width" in this.properties ? this.properties.width : 0
|
|
});
|
|
}
|
|
this.fireEvent(new SeriesGroupingChangedEvent(this, next));
|
|
}
|
|
getBandScalePadding() {
|
|
return { inner: 1, outer: 0 };
|
|
}
|
|
attachSeries(seriesContentNode, seriesNode, annotationNode) {
|
|
seriesContentNode.appendChild(this.contentGroup);
|
|
seriesNode.appendChild(this.highlightGroup);
|
|
seriesNode.appendChild(this.labelGroup);
|
|
annotationNode?.appendChild(this.annotationGroup);
|
|
}
|
|
detachSeries(_seriesContentNode, _seriesNode, _annotationNode) {
|
|
this.contentGroup.remove();
|
|
this.highlightGroup.remove();
|
|
this.labelGroup.remove();
|
|
this.annotationGroup.remove();
|
|
}
|
|
setSeriesIndex(index, forceUpdate = false) {
|
|
const bringToFront = this.bringToFront();
|
|
if (!forceUpdate && index === this.declarationOrder && bringToFront === this._broughtToFront)
|
|
return false;
|
|
this.declarationOrder = index;
|
|
this._broughtToFront = bringToFront;
|
|
this.setZIndex(bringToFront ? Number.MAX_VALUE : index);
|
|
this.fireEvent(new SeriesGroupingChangedEvent(this, this.seriesGrouping));
|
|
return true;
|
|
}
|
|
setZIndex(zIndex) {
|
|
this.contentGroup.zIndex = [1 /* ANY_CONTENT */, zIndex, 0 /* FOREGROUND */];
|
|
this.highlightGroup.zIndex = [1 /* ANY_CONTENT */, zIndex, 1 /* HIGHLIGHT */];
|
|
this.labelGroup.zIndex = [1 /* ANY_CONTENT */, zIndex, 2 /* LABEL */];
|
|
this.annotationGroup.zIndex = zIndex;
|
|
}
|
|
renderToOffscreenCanvas() {
|
|
return false;
|
|
}
|
|
hasHighlightOpacity() {
|
|
if (!this.properties.highlight.enabled)
|
|
return false;
|
|
if (this.ctx.highlightManager.getActiveHighlight() == null)
|
|
return false;
|
|
const { unhighlightedItem, unhighlightedSeries } = this.properties.highlight;
|
|
return hasDimmedOpacity(unhighlightedItem) || hasDimmedOpacity(unhighlightedSeries);
|
|
}
|
|
getDrawingMode(isHighlight, highlightDrawingMode = "cutout") {
|
|
if (isHighlight) {
|
|
return highlightDrawingMode;
|
|
}
|
|
return this.hasHighlightOpacity() ? this.ctx.chartService.highlight?.drawingMode ?? "overlay" : "overlay";
|
|
}
|
|
getAnimationDrawingModes() {
|
|
const drawingMode = this.getDrawingMode(false);
|
|
return {
|
|
start: { drawingMode: "overlay" },
|
|
finish: { drawingMode }
|
|
};
|
|
}
|
|
addEventListener(type, listener) {
|
|
return super.addEventListener(type, listener);
|
|
}
|
|
removeEventListener(type, listener) {
|
|
return super.removeEventListener(type, listener);
|
|
}
|
|
hasEventListener(type) {
|
|
return super.hasEventListener(type);
|
|
}
|
|
updatedDomains() {
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
this.resetDatumCallbackCache();
|
|
this.ctx.seriesStateManager.deregisterSeries(this);
|
|
}
|
|
getPropertyValues(property, properties) {
|
|
const direction = propertyAxisDirection(property);
|
|
const resolvedProperty = direction == null ? property : axisDirectionProperty(this.resolveKeyDirection(direction));
|
|
const keys = properties?.[resolvedProperty];
|
|
const values = [];
|
|
if (!keys) {
|
|
return values;
|
|
}
|
|
const addValues = (...items) => {
|
|
for (const value of items) {
|
|
if (Array.isArray(value)) {
|
|
addValues(...value);
|
|
} else if (typeof value === "object") {
|
|
addValues(...Object.values(value));
|
|
} else {
|
|
values.push(value);
|
|
}
|
|
}
|
|
};
|
|
addValues(...keys.map((key) => this.properties[key]));
|
|
return values;
|
|
}
|
|
getKeyAxis(_direction) {
|
|
return void 0;
|
|
}
|
|
getKeys(direction) {
|
|
return this.getPropertyValues(axisDirectionProperty(direction), this.propertyKeys);
|
|
}
|
|
getKeyProperties(direction) {
|
|
return this.propertyKeys[this.resolveKeyDirection(direction)] ?? [];
|
|
}
|
|
getNames(direction) {
|
|
return this.getPropertyValues(axisDirectionProperty(direction), this.propertyNames);
|
|
}
|
|
getFormatterContext(property) {
|
|
const { id: seriesId } = this;
|
|
const keys = this.getPropertyValues(property, this.propertyKeys);
|
|
const names = this.getPropertyValues(property, this.propertyNames);
|
|
const out = [];
|
|
for (let idx = 0; idx < keys.length; idx++) {
|
|
out.push({ seriesId, key: keys[idx], name: names[idx] });
|
|
}
|
|
return out;
|
|
}
|
|
resolveKeyDirection(direction) {
|
|
return direction;
|
|
}
|
|
// The union of the series domain ('community') and series-option domains ('enterprise').
|
|
getDomain(direction) {
|
|
const seriesDomain = this.getSeriesDomain(direction);
|
|
const moduleDomains = this.moduleMap.mapModules((module2) => module2.getDomain(direction)).flat();
|
|
if (moduleDomains.length === 0) {
|
|
return seriesDomain;
|
|
}
|
|
return { domain: seriesDomain.domain.concat(moduleDomains) };
|
|
}
|
|
getRange(direction, visibleRange) {
|
|
return this.getSeriesRange(direction, visibleRange);
|
|
}
|
|
getMinimumRangeSeries(_range) {
|
|
}
|
|
getMinimumRangeChart(_ranges) {
|
|
return 0;
|
|
}
|
|
getZoomRangeFittingItems(_xVisibleRange, _yVisibleRange, _minVisibleItems) {
|
|
return void 0;
|
|
}
|
|
getVisibleItems(_xVisibleRange, _yVisibleRange, _minVisibleItems) {
|
|
return Infinity;
|
|
}
|
|
toCanvasFromMidPoint(nodeDatum) {
|
|
const { x = 0, y = 0 } = nodeDatum.midPoint ?? {};
|
|
return Transformable.toCanvasPoint(this.contentGroup, x, y);
|
|
}
|
|
// Indicate that something external changed and we should recalculate nodeData.
|
|
markNodeDataDirty() {
|
|
this.nodeDataRefresh = true;
|
|
this._pickNodeCache.clear();
|
|
this.visibleMaybeChanged();
|
|
}
|
|
visibleMaybeChanged() {
|
|
const { internalId, seriesGrouping, type, visible } = this;
|
|
this.ctx.seriesStateManager.updateSeries({
|
|
internalId,
|
|
type,
|
|
visible,
|
|
seriesGrouping,
|
|
// TODO: is there a better way to pass width through here?
|
|
width: "width" in this.properties ? this.properties.width : 0
|
|
});
|
|
}
|
|
getOpacity() {
|
|
const defaultOpacity = 1;
|
|
if (!this.properties.highlight) {
|
|
return defaultOpacity;
|
|
}
|
|
const { opacity = defaultOpacity } = this.getHighlightStyle();
|
|
return opacity;
|
|
}
|
|
getHighlightState(highlightedDatum, isHighlight, datumIndex, legendItemValues) {
|
|
if (!this.properties.highlight.enabled) {
|
|
return 0 /* None */;
|
|
}
|
|
if (isHighlight) {
|
|
return 1 /* Item */;
|
|
}
|
|
if (highlightedDatum?.series == null) {
|
|
return 0 /* None */;
|
|
}
|
|
if (this.isSeriesHighlighted(highlightedDatum, legendItemValues)) {
|
|
const itemHighlighted = this.isItemHighlighted(highlightedDatum, datumIndex);
|
|
if (itemHighlighted == null) {
|
|
return 2 /* Series */;
|
|
}
|
|
return 4 /* OtherItem */;
|
|
}
|
|
return 3 /* OtherSeries */;
|
|
}
|
|
getHighlightStateString(datum, isHighlight, datumIndex, legendItemValues) {
|
|
return toHighlightString(this.getHighlightState(datum, isHighlight, datumIndex, legendItemValues));
|
|
}
|
|
onChangeHighlight(event) {
|
|
const previousHighlightedDatum = event.previousHighlight;
|
|
const currentHighlightedDatum = event.currentHighlight;
|
|
const currentHighlightState = this.getHighlightState(currentHighlightedDatum);
|
|
const previousHighlightState = this.getHighlightState(previousHighlightedDatum);
|
|
this.setSeriesIndex(this.declarationOrder);
|
|
const hasItemStylers = this.hasItemStylers();
|
|
if (!hasItemStylers && currentHighlightState === previousHighlightState) {
|
|
this.hasChangesOnHighlight = false;
|
|
return;
|
|
}
|
|
const { highlightedSeries, unhighlightedItem, unhighlightedSeries } = this.properties.highlight;
|
|
this.hasChangesOnHighlight = hasItemStylers || !isEmptyObject(highlightedSeries) || !isEmptyObject(unhighlightedItem) || !isEmptyObject(unhighlightedSeries);
|
|
}
|
|
bringToFront() {
|
|
return this.properties.highlight.enabled && this.properties.highlight.bringToFront && this.isSeriesHighlighted(this.ctx.highlightManager.getActiveHighlight());
|
|
}
|
|
isSeriesHighlighted(highlightedDatum, _legendItemValues) {
|
|
if (!this.properties.highlight.enabled) {
|
|
return false;
|
|
}
|
|
return highlightedDatum?.series === this;
|
|
}
|
|
isItemHighlighted(highlightedDatum, datumIndex) {
|
|
if (highlightedDatum?.datumIndex == null || datumIndex == null)
|
|
return;
|
|
return highlightedDatum.datumIndex === datumIndex;
|
|
}
|
|
getHighlightStyle(isHighlight, datumIndex, highlightState, legendItemValues) {
|
|
const highlightedDatum = this.ctx.highlightManager?.getActiveHighlight();
|
|
highlightState ?? (highlightState = this.getHighlightState(highlightedDatum, isHighlight, datumIndex, legendItemValues));
|
|
return this.properties.highlight.getStyle(highlightState);
|
|
}
|
|
resolveMarkerDrawingModeForState(drawingMode, style2) {
|
|
return resolveMarkerDrawingMode(drawingMode, style2);
|
|
}
|
|
filterItemStylerFillParams(fill) {
|
|
if (isGradientFill(fill)) {
|
|
return without(fill, ["bounds", "colorSpace", "gradient", "reverse"]);
|
|
} else if (isPatternFill(fill)) {
|
|
return without(fill, ["padding"]);
|
|
}
|
|
return fill;
|
|
}
|
|
getModuleTooltipParams() {
|
|
return this.moduleMap.mapModules((module2) => module2.getTooltipParams()).reduce((total, current) => Object.assign(total, current), {});
|
|
}
|
|
pickNodes(point, intent, exactMatchOnly = false) {
|
|
const { pickModes, pickModeAxis, visible, contentGroup } = this;
|
|
if (!visible || !contentGroup.visible)
|
|
return;
|
|
if (intent === "highlight" && !this.properties.highlight.enabled)
|
|
return;
|
|
if (intent === "highlight-tooltip" && !this.properties.highlight.enabled)
|
|
return;
|
|
let maxDistance = Infinity;
|
|
if (intent === "tooltip" || intent === "highlight-tooltip") {
|
|
const { tooltip } = this.properties;
|
|
maxDistance = typeof tooltip.range === "number" ? tooltip.range : Infinity;
|
|
exactMatchOnly || (exactMatchOnly = tooltip.range === "exact");
|
|
} else if (intent === "event" || intent === "context-menu") {
|
|
const { nodeClickRange } = this.properties;
|
|
maxDistance = typeof nodeClickRange === "number" ? nodeClickRange : Infinity;
|
|
exactMatchOnly || (exactMatchOnly = nodeClickRange === "exact");
|
|
}
|
|
const selectedPickModes = pickModes.filter(
|
|
(m) => !exactMatchOnly || m === 0 /* EXACT_SHAPE_MATCH */
|
|
);
|
|
const { x, y } = point;
|
|
const key = JSON.stringify({ x, y, maxDistance, selectedPickModes });
|
|
if (this._pickNodeCache.has(key)) {
|
|
return this._pickNodeCache.get(key);
|
|
}
|
|
for (const pickMode of selectedPickModes) {
|
|
let result;
|
|
switch (pickMode) {
|
|
case 0 /* EXACT_SHAPE_MATCH */: {
|
|
const exact = this.pickNodesExactShape(point);
|
|
result = exact.length === 0 ? void 0 : { datums: exact, distance: 0 };
|
|
break;
|
|
}
|
|
case 1 /* NEAREST_NODE */: {
|
|
const closest = this.pickNodeClosestDatum(point);
|
|
const exact = closest?.distance === 0 ? this.pickNodesExactShape(point) : void 0;
|
|
if (exact != null && exact.length !== 0) {
|
|
result = { datums: exact, distance: 0 };
|
|
} else if (closest) {
|
|
result = { datums: [closest.datum], distance: closest.distance };
|
|
} else {
|
|
result = void 0;
|
|
}
|
|
break;
|
|
}
|
|
case 2 /* AXIS_ALIGNED */: {
|
|
const closest = pickModeAxis == null ? void 0 : this.pickNodeMainAxisFirst(point, pickModeAxis === "main-category");
|
|
result = closest == null ? void 0 : { datums: [closest.datum], distance: closest.distance };
|
|
break;
|
|
}
|
|
}
|
|
if (result && result.distance <= maxDistance) {
|
|
return this._pickNodeCache.set(key, { pickMode, datums: result.datums, distance: result.distance });
|
|
}
|
|
}
|
|
return this._pickNodeCache.set(key, void 0);
|
|
}
|
|
pickNodesExactShape(point) {
|
|
const datums = [];
|
|
for (const node of this.contentGroup.pickNodes(point.x, point.y)) {
|
|
const datum = node.closestDatum();
|
|
if (datum != null && datum.missing !== true) {
|
|
datums.push(datum);
|
|
}
|
|
}
|
|
return datums;
|
|
}
|
|
pickNodeClosestDatum(_point) {
|
|
throw new Error("AG Charts - Series.pickNodeClosestDatum() not implemented");
|
|
}
|
|
pickNodeNearestDistantObject(point, items) {
|
|
const match = nearestSquared(point.x, point.y, items);
|
|
const datum = match.nearest?.closestDatum();
|
|
if (datum != null && datum.missing !== true) {
|
|
return { datum, distance: Math.sqrt(match.distanceSquared) };
|
|
}
|
|
}
|
|
pickNodeMainAxisFirst(_point, _requireCategoryAxis) {
|
|
throw new Error("AG Charts - Series.pickNodeMainAxisFirst() not implemented");
|
|
}
|
|
getLabelData() {
|
|
return [];
|
|
}
|
|
updatePlacedLabelData(_labels) {
|
|
return;
|
|
}
|
|
fireEvent(event) {
|
|
callWithContext([this.properties, this.ctx.chartService], this.fireEventWrapper, event);
|
|
}
|
|
fireNodeClickEvent(event, datum) {
|
|
const clickEvent = new this.NodeEvent("seriesNodeClick", event, datum, this);
|
|
this.fireEvent(clickEvent);
|
|
return !clickEvent.defaultPrevented;
|
|
}
|
|
fireNodeDoubleClickEvent(event, datum) {
|
|
const clickEvent = new this.NodeEvent("seriesNodeDoubleClick", event, datum, this);
|
|
this.fireEvent(clickEvent);
|
|
return !clickEvent.defaultPrevented;
|
|
}
|
|
createNodeContextMenuActionEvent(event, datum) {
|
|
return new this.NodeEvent("nodeContextMenuAction", event, datum, this);
|
|
}
|
|
onLegendInitialState(legendType, initialState) {
|
|
const { visible = true, itemId, legendItemName } = initialState ?? {};
|
|
this.toggleSeriesItem(visible, legendType, itemId, legendItemName);
|
|
}
|
|
onLegendItemClick(event) {
|
|
const { enabled, itemId, series, legendType } = event;
|
|
const legendItemName = "legendItemName" in this.properties ? this.properties.legendItemName : void 0;
|
|
const legendItemKey = "legendItemKey" in this.properties ? this.properties.legendItemKey : void 0;
|
|
const matchedLegendItemName = legendItemName != void 0 && legendItemName === event.legendItemName;
|
|
if (series.id === this.id || matchedLegendItemName || legendItemKey != void 0) {
|
|
this.toggleSeriesItem(enabled, legendType, itemId, legendItemName, event);
|
|
}
|
|
}
|
|
onLegendItemDoubleClick(event) {
|
|
const { enabled, itemId, series, numVisibleItems, legendType } = event;
|
|
const legendItemName = "legendItemName" in this.properties ? this.properties.legendItemName : void 0;
|
|
const legendItemKey = "legendItemKey" in this.properties ? this.properties.legendItemKey : void 0;
|
|
const matchedLegendItemName = legendItemName != void 0 && legendItemName === event.legendItemName;
|
|
if (series.id === this.id || matchedLegendItemName || legendItemKey != void 0) {
|
|
this.toggleSeriesItem(true, legendType, itemId, legendItemName, event);
|
|
} else if (enabled && numVisibleItems === 1) {
|
|
this.toggleSeriesItem(true, legendType, void 0, legendItemName);
|
|
} else {
|
|
this.toggleSeriesItem(false, legendType, void 0, legendItemName);
|
|
}
|
|
}
|
|
toggleSeriesItem(enabled, legendType, itemId, legendItemName, legendEvent) {
|
|
const seriesId = this.id;
|
|
if (enabled || legendType !== "category") {
|
|
this.visible = enabled;
|
|
}
|
|
this.nodeDataRefresh = true;
|
|
this._pickNodeCache.clear();
|
|
const event = {
|
|
type: "seriesVisibilityChange",
|
|
seriesId,
|
|
itemId,
|
|
legendItemName: legendEvent?.legendItemName ?? legendItemName,
|
|
visible: enabled
|
|
};
|
|
this.fireEvent(event);
|
|
this.ctx.legendManager.toggleItem(enabled, seriesId, itemId, legendItemName);
|
|
}
|
|
isEnabled() {
|
|
return this.visible;
|
|
}
|
|
getModuleMap() {
|
|
return this.moduleMap;
|
|
}
|
|
createModuleContext() {
|
|
return { ...this.ctx, series: this };
|
|
}
|
|
getAxisValueText(axis, source, value, datum, key, legendItemName, allowNull) {
|
|
const { id: seriesId, properties } = this;
|
|
return axis.formatDatum(
|
|
properties,
|
|
value,
|
|
source,
|
|
seriesId,
|
|
legendItemName,
|
|
datum,
|
|
key,
|
|
void 0,
|
|
void 0,
|
|
void 0,
|
|
allowNull
|
|
);
|
|
}
|
|
getLabelText(value, datum, key, property, domain, label, baseParams, allowNullValue = false) {
|
|
if (value == null && !allowNullValue)
|
|
return "";
|
|
const { axes, canHaveAxes, ctx, id: seriesId, properties } = this;
|
|
const source = "series-label";
|
|
const legendItemName = "legendItemName" in properties ? properties.legendItemName : void 0;
|
|
const params = {
|
|
seriesId: this.id,
|
|
...baseParams
|
|
};
|
|
const direction = canHaveAxes ? propertyAxisDirection(property) : void 0;
|
|
const axis = direction == null ? void 0 : axes[this.resolveKeyDirection(direction)];
|
|
if (axis != null) {
|
|
return axis.formatDatum(
|
|
properties,
|
|
value,
|
|
source,
|
|
seriesId,
|
|
legendItemName,
|
|
datum,
|
|
key,
|
|
domain,
|
|
label,
|
|
params,
|
|
allowNullValue
|
|
);
|
|
}
|
|
const { formatManager } = ctx;
|
|
const formatInContext = this.callWithContext.bind(this);
|
|
const format = (formatParams) => label.formatValue(formatInContext, formatParams.type, formatParams.value, params) ?? formatManager.format(formatInContext, formatParams) ?? (value == null ? "" : String(value));
|
|
const boundSeries = this.getFormatterContext(property);
|
|
switch (property) {
|
|
case "y":
|
|
case "color":
|
|
case "size": {
|
|
const fractionDigits = void 0;
|
|
return format({
|
|
type: "number",
|
|
value,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key,
|
|
source,
|
|
property,
|
|
domain,
|
|
boundSeries,
|
|
fractionDigits,
|
|
visibleDomain: void 0
|
|
});
|
|
}
|
|
case "x":
|
|
case "radius":
|
|
case "angle":
|
|
case "label":
|
|
case "secondaryLabel":
|
|
case "calloutLabel":
|
|
case "sectorLabel":
|
|
case "legendItem":
|
|
return format({
|
|
type: "category",
|
|
value,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key,
|
|
source,
|
|
property,
|
|
domain,
|
|
boundSeries
|
|
});
|
|
}
|
|
}
|
|
getMarkerStyle(marker, { datumIndex, datum, point }, params, opts, defaultOverrideStyle = { size: point?.size ?? marker.size ?? 0 }, inheritedStyle) {
|
|
const { itemStyler } = marker;
|
|
const {
|
|
highlightState,
|
|
isHighlight = false,
|
|
checkForHighlight = true,
|
|
resolveMarkerSubPath = ["marker"],
|
|
resolveStyler = false
|
|
} = opts ?? {};
|
|
const resolvePath2 = ["series", `${this.declarationOrder}`, ...resolveMarkerSubPath];
|
|
if (resolveStyler) {
|
|
const resolveOpt = { permissivePath: true };
|
|
const resolved = this.ctx.optionsGraphService.resolvePartial(resolvePath2, defaultOverrideStyle, resolveOpt);
|
|
if (resolved) {
|
|
defaultOverrideStyle = { ...resolved, size: resolved.size ?? defaultOverrideStyle.size };
|
|
}
|
|
}
|
|
const highlightStyle = checkForHighlight ? this.getHighlightStyle(isHighlight, datumIndex, highlightState) : void 0;
|
|
const baseStyle = mergeDefaults(highlightStyle, defaultOverrideStyle, marker.getStyle(), inheritedStyle);
|
|
let markerStyle = baseStyle;
|
|
if (itemStyler && params) {
|
|
const highlight5 = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightStateString = this.getHighlightStateString(highlight5, isHighlight, datumIndex);
|
|
const fill = this.filterItemStylerFillParams(markerStyle.fill);
|
|
const style2 = this.cachedCallWithContext(itemStyler, {
|
|
seriesId: this.id,
|
|
...markerStyle,
|
|
fill,
|
|
...params,
|
|
highlightState: highlightStateString,
|
|
datum
|
|
});
|
|
const resolved = this.ctx.optionsGraphService.resolvePartial(resolvePath2, style2);
|
|
markerStyle = mergeDefaults(resolved, markerStyle);
|
|
}
|
|
return markerStyle;
|
|
}
|
|
applyMarkerStyle(style2, markerNode, point, fillBBox, { applyTranslation = true, selected = true } = {}) {
|
|
const { shape, size = 0 } = style2;
|
|
const visible = this.visible && size > 0 && point && !Number.isNaN(point.x) && !Number.isNaN(point.y);
|
|
markerNode.setStyleProperties(style2, fillBBox);
|
|
if (applyTranslation) {
|
|
markerNode.setProperties({
|
|
visible,
|
|
shape,
|
|
size,
|
|
x: point?.x,
|
|
y: point?.y,
|
|
scalingCenterX: point?.x,
|
|
scalingCenterY: point?.y
|
|
});
|
|
} else {
|
|
markerNode.setProperties({ visible, shape, size });
|
|
}
|
|
if (!selected) {
|
|
markerNode.fillOpacity *= CROSS_FILTER_MARKER_FILL_OPACITY_FACTOR;
|
|
markerNode.strokeOpacity *= CROSS_FILTER_MARKER_STROKE_OPACITY_FACTOR;
|
|
}
|
|
if (typeof shape === "function" && !markerNode.dirtyPath) {
|
|
markerNode.path.clear(true);
|
|
markerNode.updatePath();
|
|
markerNode.checkPathDirty();
|
|
const bb = markerNode.getBBox();
|
|
if (point != null && bb.isFinite()) {
|
|
const center2 = bb.computeCenter();
|
|
const [dx2, dy2] = ["x", "y"].map(
|
|
(key) => (style2.strokeWidth ?? 0) + Math.abs(center2[key] - point[key])
|
|
);
|
|
point.focusSize = Math.max(bb.width + dx2, bb.height + dy2);
|
|
}
|
|
}
|
|
}
|
|
get nodeDataDependencies() {
|
|
return this._nodeDataDependencies ?? { seriesRectWidth: Number.NaN, seriesRectHeight: Number.NaN };
|
|
}
|
|
checkResize(newSeriesRect) {
|
|
const { width: seriesRectWidth, height: seriesRectHeight } = newSeriesRect ?? {
|
|
width: Number.NaN,
|
|
height: Number.NaN
|
|
};
|
|
const newNodeDataDependencies = newSeriesRect ? { seriesRectWidth, seriesRectHeight } : void 0;
|
|
const resize = jsonDiff(this.nodeDataDependencies, newNodeDataDependencies) != null;
|
|
if (resize) {
|
|
this._nodeDataDependencies = newNodeDataDependencies;
|
|
this.markNodeDataDirty();
|
|
}
|
|
return resize;
|
|
}
|
|
pickFocus(_opts) {
|
|
return void 0;
|
|
}
|
|
resetDatumCallbackCache() {
|
|
this.datumCallbackCache.clear();
|
|
}
|
|
cachedDatumCallback(id, fn) {
|
|
const { datumCallbackCache } = this;
|
|
const existing = datumCallbackCache.get(id);
|
|
if (existing != null)
|
|
return existing;
|
|
try {
|
|
const value = fn();
|
|
datumCallbackCache.set(id, value);
|
|
return value;
|
|
} catch (error2) {
|
|
logger_exports.error(String(error2));
|
|
}
|
|
}
|
|
cachedCallWithContext(fn, params) {
|
|
return this.ctx.callbackCache.call([this.properties, this.ctx.chartService], fn, params);
|
|
}
|
|
callWithContext(fn, params) {
|
|
return callWithContext([this.properties, this.ctx.chartService], fn, params);
|
|
}
|
|
formatTooltipWithContext(tooltip, content, params) {
|
|
return tooltip.formatTooltip([this.properties, this.ctx.chartService], content, params);
|
|
}
|
|
// @todo(AG-13777) - Remove this function (see CartesianSeries.ts)
|
|
minTimeInterval() {
|
|
return;
|
|
}
|
|
needsDataModelDiff() {
|
|
return !this.ctx.animationManager.isSkipped() || !!this.chart?.flashOnUpdateEnabled;
|
|
}
|
|
};
|
|
Series.className = "Series";
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue: function(newVal, oldVal) {
|
|
this.onSeriesGroupingChange(oldVal, newVal);
|
|
}
|
|
})
|
|
], Series.prototype, "seriesGrouping", 2);
|
|
|
|
// packages/ag-charts-community/src/dom/focusIndicator.ts
|
|
var FocusIndicator = class {
|
|
constructor(swapChain) {
|
|
this.swapChain = swapChain;
|
|
this.hasBeenActivated = false;
|
|
this.div = createElement("div");
|
|
this.svg = createSvgElement("svg");
|
|
this.outerPath = createSvgElement("path");
|
|
this.innerPath = createSvgElement("path");
|
|
this.svg.append(this.outerPath);
|
|
this.svg.append(this.innerPath);
|
|
this.outerPath.classList.add("ag-charts-focus-svg-outer-path");
|
|
this.innerPath.classList.add("ag-charts-focus-svg-inner-path");
|
|
this.element = createElement("div", "ag-charts-focus-indicator");
|
|
this.element.ariaHidden = "true";
|
|
this.element.append(this.svg);
|
|
this.swapChain.addListener("swap", (parent) => this.onSwap(parent));
|
|
}
|
|
clear() {
|
|
}
|
|
update(focus, rect2, clip) {
|
|
if (rect2 == null)
|
|
return;
|
|
if (focus instanceof Path) {
|
|
const transform = (localX, localY) => {
|
|
let { x, y } = Transformable.toCanvasPoint(focus, localX, localY);
|
|
x -= rect2.x ?? 0;
|
|
y -= rect2.y ?? 0;
|
|
return { x, y };
|
|
};
|
|
const d = focus.svgPathData(transform);
|
|
this.outerPath.setAttribute("d", d);
|
|
this.innerPath.setAttribute("d", d);
|
|
this.show(this.svg);
|
|
} else {
|
|
let bbox;
|
|
if (clip) {
|
|
const x0 = Math.max(focus.x - rect2.x, 0);
|
|
const y0 = Math.max(focus.y - rect2.y, 0);
|
|
const x1 = Math.min(focus.x + focus.width - rect2.x, rect2.width);
|
|
const y1 = Math.min(focus.y + focus.height - rect2.y, rect2.height);
|
|
bbox = new BBox(x0, y0, x1 - x0, y1 - y0);
|
|
} else {
|
|
bbox = new BBox(focus.x - rect2.x, focus.y - rect2.y, focus.width, focus.height);
|
|
}
|
|
setElementBBox(this.div, bbox);
|
|
this.show(this.div);
|
|
}
|
|
}
|
|
onSwap(newParent) {
|
|
if (newParent === this.element.parentElement)
|
|
return;
|
|
this.element.remove();
|
|
newParent.appendChild(this.element);
|
|
this.overrideFocusVisible(this.focusVisible);
|
|
}
|
|
show(child) {
|
|
this.hasBeenActivated = true;
|
|
this.element.innerHTML = "";
|
|
this.element.append(child);
|
|
}
|
|
overrideFocusVisible(focusVisible) {
|
|
this.focusVisible = focusVisible;
|
|
const opacity = { true: "1", false: "0", undefined: "" };
|
|
const parent = this.element.parentElement;
|
|
parent?.style.setProperty("opacity", opacity[`${focusVisible}`]);
|
|
}
|
|
// Get the `:focus-visible` CSS state.
|
|
isFocusVisible(force = false) {
|
|
if (!force && !this.hasBeenActivated)
|
|
return false;
|
|
const parent = this.element.parentElement;
|
|
return parent != null && getWindow().getComputedStyle(parent).opacity === "1";
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/dom/focusSwapChain.ts
|
|
var FocusSwapChain = class {
|
|
constructor(label1, label2, announcerRole, initialAltText) {
|
|
this.label1 = label1;
|
|
this.label2 = label2;
|
|
this.hasFocus = false;
|
|
this.skipDispatch = false;
|
|
this.listeners = {
|
|
blur: [],
|
|
focus: [],
|
|
swap: []
|
|
};
|
|
this.onBlur = (e) => {
|
|
setElementStyle(e.target, "pointer-events", void 0);
|
|
return !this.skipDispatch && this.dispatch("blur", e);
|
|
};
|
|
this.onFocus = (e) => {
|
|
setElementStyle(e.target, "pointer-events", "auto");
|
|
return !this.skipDispatch && this.dispatch("focus", e);
|
|
};
|
|
setAttribute(this.label1, "id", createElementId());
|
|
setAttribute(this.label2, "id", createElementId());
|
|
setElementStyle(this.label1, "display", "none");
|
|
setElementStyle(this.label2, "display", "none");
|
|
this.label1.textContent = initialAltText;
|
|
this.label2.textContent = initialAltText;
|
|
this.activeAnnouncer = this.createAnnouncer(announcerRole);
|
|
this.inactiveAnnouncer = this.createAnnouncer(announcerRole);
|
|
setAttribute(this.activeAnnouncer, "tabindex", 0);
|
|
this.label2.insertAdjacentElement("afterend", this.activeAnnouncer);
|
|
this.label2.insertAdjacentElement("afterend", this.inactiveAnnouncer);
|
|
this.swap(initialAltText);
|
|
}
|
|
createAnnouncer(role) {
|
|
const announcer = createElement("div");
|
|
announcer.role = role;
|
|
announcer.className = "ag-charts-swapchain";
|
|
announcer.addEventListener("blur", this.onBlur);
|
|
announcer.addEventListener("focus", this.onFocus);
|
|
return announcer;
|
|
}
|
|
destroy() {
|
|
for (const announcer of [this.activeAnnouncer, this.inactiveAnnouncer]) {
|
|
announcer.removeEventListener("blur", this.onBlur);
|
|
announcer.removeEventListener("focus", this.onFocus);
|
|
announcer.remove();
|
|
}
|
|
}
|
|
focus(opts) {
|
|
this.focusOptions = opts;
|
|
this.activeAnnouncer.focus(opts);
|
|
this.focusOptions = void 0;
|
|
}
|
|
update(newLabel) {
|
|
this.skipDispatch = true;
|
|
this.swap(newLabel);
|
|
if (this.hasFocus) {
|
|
this.activeAnnouncer.focus(this.focusOptions);
|
|
}
|
|
this.skipDispatch = false;
|
|
}
|
|
addListener(type, handler) {
|
|
this.listeners[type].push(handler);
|
|
if (type === "swap") {
|
|
const swapHandler = handler;
|
|
swapHandler(this.activeAnnouncer);
|
|
}
|
|
}
|
|
dispatch(type, param) {
|
|
if (type === "focus")
|
|
this.hasFocus = true;
|
|
else if (type === "blur")
|
|
this.hasFocus = false;
|
|
for (const fn of this.listeners[type]) {
|
|
fn(param);
|
|
}
|
|
}
|
|
swap(newLabel) {
|
|
const userTabIndex = this.activeAnnouncer.tabIndex;
|
|
this.label2.textContent = newLabel;
|
|
[this.inactiveAnnouncer, this.activeAnnouncer] = [this.activeAnnouncer, this.inactiveAnnouncer];
|
|
[this.label1, this.label2] = [this.label2, this.label1];
|
|
setAttributes(this.inactiveAnnouncer, {
|
|
"aria-labelledby": this.label1.id,
|
|
"aria-hidden": true,
|
|
tabindex: void 0
|
|
});
|
|
setAttributes(this.activeAnnouncer, {
|
|
"aria-labelledby": this.label1.id,
|
|
"aria-hidden": false,
|
|
tabindex: userTabIndex
|
|
});
|
|
this.dispatch("swap", this.activeAnnouncer);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/interaction/keyBindings.ts
|
|
var KEY_BINDINGS = {
|
|
arrowdown: { bindings: [{ code: "ArrowDown" }] },
|
|
arrowleft: { bindings: [{ code: "ArrowLeft" }] },
|
|
arrowright: { bindings: [{ code: "ArrowRight" }] },
|
|
arrowup: { bindings: [{ code: "ArrowUp" }] },
|
|
delete: { bindings: [{ key: "Backspace" }, { key: "Delete" }], activatesFocusIndicator: false },
|
|
redo: {
|
|
bindings: [
|
|
{ key: "y", ctrlOrMeta: true },
|
|
{ key: "z", ctrlOrMeta: true, shift: true }
|
|
],
|
|
activatesFocusIndicator: false
|
|
},
|
|
undo: { bindings: [{ key: "z", ctrlOrMeta: true }], activatesFocusIndicator: false },
|
|
submit: { bindings: [{ key: "Enter" }, { code: "Enter" }, { code: "Space" }] },
|
|
zoomin: { bindings: [{ key: "+" }, { code: "ZoomIn" }, { code: "Add" }], activatesFocusIndicator: false },
|
|
zoomout: { bindings: [{ key: "-" }, { code: "ZoomOut" }, { code: "Substract" }], activatesFocusIndicator: false }
|
|
};
|
|
function matchesKeyBinding(e, bindings) {
|
|
for (const kb of bindings) {
|
|
if ("code" in kb) {
|
|
if (kb.code === e.code)
|
|
return true;
|
|
} else {
|
|
const matches = kb.key === e.key && (kb.shift === void 0 || kb.shift === e.shiftKey) && (kb.ctrlOrMeta === void 0 || kb.ctrlOrMeta === e.ctrlKey || kb.ctrlOrMeta === e.metaKey);
|
|
if (matches)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
function mapKeyboardEventToAction(event) {
|
|
for (const [actionName, { activatesFocusIndicator = true, bindings }] of entries(KEY_BINDINGS)) {
|
|
if (matchesKeyBinding(event, bindings)) {
|
|
return { name: actionName, activatesFocusIndicator };
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/keyboardUtil.ts
|
|
function computeCenter(series, hoverRect, pick2) {
|
|
const refPoint = getDatumRefPoint(series, pick2.datum, pick2.movedBounds);
|
|
if (refPoint != null)
|
|
return { x: refPoint.canvasX, y: refPoint.canvasY };
|
|
const bboxOrPath = pick2.bounds;
|
|
if (bboxOrPath == null)
|
|
return;
|
|
if (bboxOrPath instanceof BBox) {
|
|
const { x: centerX, y: centerY } = bboxOrPath.computeCenter();
|
|
return {
|
|
x: hoverRect.x + centerX,
|
|
y: hoverRect.y + centerY
|
|
};
|
|
}
|
|
return Transformable.toCanvas(bboxOrPath).computeCenter();
|
|
}
|
|
function getPickedFocusBBox({ bounds }) {
|
|
if (bounds instanceof BBox)
|
|
return bounds;
|
|
if (bounds != null)
|
|
return Transformable.toCanvas(bounds);
|
|
return BBox.NaN;
|
|
}
|
|
function makeKeyboardPointerEvent(series, hoverRect, pick2) {
|
|
const { x: canvasX, y: canvasY } = computeCenter(series, hoverRect, pick2) ?? {};
|
|
if (canvasX !== void 0 && canvasY !== void 0) {
|
|
return { type: "keyboard", canvasX, canvasY };
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/tooltip/springAnimation.ts
|
|
var M = 0.1;
|
|
var K = 200;
|
|
var C = 12;
|
|
var DELTA = 0.5;
|
|
var SpringAnimation = class {
|
|
constructor() {
|
|
this.events = new EventEmitter();
|
|
this.x1 = Number.NaN;
|
|
this.y1 = Number.NaN;
|
|
this.x = Number.NaN;
|
|
this.y = Number.NaN;
|
|
this.vx = 0;
|
|
this.vy = 0;
|
|
this.t0 = Number.NaN;
|
|
this.animationFrameHandle = void 0;
|
|
}
|
|
reset() {
|
|
this.x = Number.NaN;
|
|
this.y = Number.NaN;
|
|
if (this.animationFrameHandle != null) {
|
|
cancelAnimationFrame(this.animationFrameHandle);
|
|
this.animationFrameHandle = void 0;
|
|
}
|
|
}
|
|
update(x, y) {
|
|
if (Number.isNaN(this.x) || Number.isNaN(this.y)) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.vx = 0;
|
|
this.vy = 0;
|
|
this.emitUpdate();
|
|
if (this.animationFrameHandle != null) {
|
|
cancelAnimationFrame(this.animationFrameHandle);
|
|
this.animationFrameHandle = void 0;
|
|
}
|
|
return;
|
|
}
|
|
this.x1 = x;
|
|
this.y1 = y;
|
|
this.t0 = Date.now();
|
|
this.animationFrameHandle ?? (this.animationFrameHandle = requestAnimationFrame(this.onFrame.bind(this)));
|
|
}
|
|
onFrame() {
|
|
this.animationFrameHandle = void 0;
|
|
const { x1, y1, t0 } = this;
|
|
const t1 = Date.now();
|
|
const dt = t1 - t0;
|
|
this.t0 = t1;
|
|
const stepT = 1e-3;
|
|
const iterations = Math.trunc(Math.ceil(dt / (stepT * 1e3)));
|
|
let { x, y, vx, vy } = this;
|
|
for (let i = 0; i < iterations; i += 1) {
|
|
const dx2 = x - x1;
|
|
const dy2 = y - y1;
|
|
const ax = -(K * dx2 + C * vx) / M;
|
|
const ay = -(K * dy2 + C * vy) / M;
|
|
vx += ax * stepT;
|
|
vy += ay * stepT;
|
|
x += vx * stepT;
|
|
y += vy * stepT;
|
|
}
|
|
if (Math.hypot(x - x1, y - y1) < DELTA) {
|
|
this.x = this.x1;
|
|
this.y = this.y1;
|
|
this.vx = 0;
|
|
this.vy = 0;
|
|
} else {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.vx = vx;
|
|
this.vy = vy;
|
|
this.animationFrameHandle = requestAnimationFrame(this.onFrame.bind(this));
|
|
}
|
|
this.emitUpdate();
|
|
}
|
|
emitUpdate() {
|
|
this.events.emit("update", { x: this.x, y: this.y });
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/sanitize.ts
|
|
var element = null;
|
|
function sanitizeHtml(text2) {
|
|
const plainText = toPlainText(text2);
|
|
if (plainText === "")
|
|
return "";
|
|
element ?? (element = createElement("div"));
|
|
element.textContent = plainText;
|
|
return element.innerHTML.replaceAll("\n", "<br>");
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/marker/shapes.ts
|
|
function drawMarkerUnitPolygon(params, moves) {
|
|
const { path, size } = params;
|
|
const { x: x0, y: y0 } = params;
|
|
path.clear();
|
|
let didMove = false;
|
|
for (const [dx2, dy2] of moves) {
|
|
const x = x0 + (dx2 - 0.5) * size;
|
|
const y = y0 + (dy2 - 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, toRadians(130), toRadians(330));
|
|
path.arc(x + r, y - r, r, toRadians(220), toRadians(50));
|
|
path.lineTo(x, y + r);
|
|
path.closePath();
|
|
},
|
|
pin({ path, x, y, size: s }) {
|
|
const cx = 0.5;
|
|
const cy = 0.5;
|
|
path.moveTo(x + (0.891 - cx) * s, y + (0.391 - cy) * s);
|
|
path.cubicCurveTo(
|
|
x + (0.891 - cx) * s,
|
|
y + (0.606 - cy) * s,
|
|
x + (0.5 - cx) * s,
|
|
y + (1 - cy) * s,
|
|
x + (0.5 - cx) * s,
|
|
y + (1 - cy) * s
|
|
);
|
|
path.cubicCurveTo(
|
|
x + (0.5 - cx) * s,
|
|
y + (1 - cy) * s,
|
|
x + (0.109 - cx) * s,
|
|
y + (0.606 - cy) * s,
|
|
x + (0.109 - cx) * s,
|
|
y + (0.391 - cy) * s
|
|
);
|
|
path.cubicCurveTo(
|
|
x + (0.109 - cx) * s,
|
|
y + (0.175 - cy) * s,
|
|
x + (0.284 - cx) * s,
|
|
y + (0 - cy) * s,
|
|
x + (0.5 - cx) * s,
|
|
y + (0 - cy) * s
|
|
);
|
|
path.cubicCurveTo(
|
|
x + (0.716 - cx) * s,
|
|
y + (0 - cy) * s,
|
|
x + (0.891 - cx) * s,
|
|
y + (0.175 - cy) * s,
|
|
x + (0.891 - cx) * s,
|
|
y + (0.391 - cy) * s
|
|
);
|
|
path.closePath();
|
|
},
|
|
plus(params) {
|
|
drawMarkerUnitPolygon(params, [
|
|
[1 / 3, 0],
|
|
[2 / 3, 0],
|
|
[2 / 3, 1 / 3],
|
|
[1, 1 / 3],
|
|
[1, 2 / 3],
|
|
[2 / 3, 2 / 3],
|
|
[2 / 3, 1],
|
|
[1 / 3, 1],
|
|
[1 / 3, 2 / 3],
|
|
[0, 2 / 3],
|
|
[0, 1 / 3],
|
|
[1 / 3, 1 / 3]
|
|
]);
|
|
},
|
|
square({ path, x, y, size, pixelRatio }) {
|
|
const hs = size / 2;
|
|
path.moveTo(align(pixelRatio, x - hs), align(pixelRatio, y - hs));
|
|
path.lineTo(align(pixelRatio, x + hs), align(pixelRatio, y - hs));
|
|
path.lineTo(align(pixelRatio, x + hs), align(pixelRatio, y + hs));
|
|
path.lineTo(align(pixelRatio, x - hs), align(pixelRatio, y + hs));
|
|
path.closePath();
|
|
},
|
|
star({ path, x, y, size }) {
|
|
const spikes = 5;
|
|
const outerRadius = size / 2;
|
|
const innerRadius = outerRadius / 2;
|
|
const rotation = Math.PI / 2;
|
|
for (let i = 0; i < spikes * 2; i++) {
|
|
const radius = i % 2 === 0 ? outerRadius : innerRadius;
|
|
const angle2 = i * Math.PI / spikes - rotation;
|
|
const xCoordinate = x + Math.cos(angle2) * radius;
|
|
const yCoordinate = y + Math.sin(angle2) * 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 dx2 = x - this.x + (anchor.x - 0.5) * this.size;
|
|
const dy2 = y - this.y + (anchor.y - 0.5) * this.size;
|
|
const radius = this.size / 2;
|
|
return Math.max(dx2 * dx2 + dy2 * dy2 - 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([
|
|
DeclaredSceneObjectChangeDetection({ equals: TRIPLE_EQ })
|
|
], InternalMarker.prototype, "shape", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], InternalMarker.prototype, "x", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], InternalMarker.prototype, "y", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection({ convertor: Math.abs })
|
|
], InternalMarker.prototype, "size", 2);
|
|
var Marker = class extends Rotatable(Scalable(Translatable(InternalMarker))) {
|
|
static anchor(shape) {
|
|
if (shape === "pin") {
|
|
return { x: 0.5, y: 1 };
|
|
} else if (typeof shape === "function" && "anchor" in shape) {
|
|
return shape.anchor;
|
|
}
|
|
return { x: 0.5, y: 0.5 };
|
|
}
|
|
constructor(options) {
|
|
super(options);
|
|
if (options?.shape != null) {
|
|
this.shape = options.shape;
|
|
}
|
|
}
|
|
/**
|
|
* Optimised reset for animation hot paths.
|
|
* Bypasses SceneChangeDetection decorators by writing directly to backing fields.
|
|
*
|
|
* This avoids per-property overhead from:
|
|
* - Equality checks (comparing old vs new values)
|
|
* - Change callbacks (triggering downstream updates)
|
|
* - Object.keys() iteration
|
|
*
|
|
* A single markDirty() call at the end ensures the scene graph is properly invalidated.
|
|
* WARNING: Only use for animation hot paths where performance is critical.
|
|
*/
|
|
resetAnimationProperties(x, y, size, opacity, scalingX, scalingY) {
|
|
this.__x = x;
|
|
this.__y = y;
|
|
this.__size = size;
|
|
this.__opacity = opacity;
|
|
this.resetScalingProperties(scalingX, scalingY, x, y);
|
|
this.dirtyPath = true;
|
|
this.markDirty();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/legend/legendSymbol.ts
|
|
function legendSymbolSvg(symbol, size, lineSize = size * (5 / 3)) {
|
|
const group = new Group();
|
|
const markerStrokeWidth = Math.min(symbol.marker.strokeWidth ?? 1, 2);
|
|
const lineStrokeWidth = symbol.line?.enabled ? Math.min(symbol.line.strokeWidth, 2) : 0;
|
|
const width2 = Math.max(symbol.marker.enabled === false ? 0 : size, symbol.line == null ? 0 : lineSize);
|
|
const height2 = Math.max(symbol.marker.enabled === false ? 0 : size, lineStrokeWidth);
|
|
if (symbol.line?.enabled) {
|
|
const { stroke: stroke3, strokeOpacity, lineDash } = symbol.line;
|
|
const line = new Line();
|
|
line.x1 = 0;
|
|
line.y1 = height2 / 2;
|
|
line.x2 = width2;
|
|
line.y2 = height2 / 2;
|
|
line.stroke = stroke3;
|
|
line.strokeOpacity = strokeOpacity;
|
|
line.strokeWidth = lineStrokeWidth;
|
|
line.lineDash = lineDash;
|
|
group.append(line);
|
|
}
|
|
if (symbol.marker.enabled !== false) {
|
|
const { shape, fill, fillOpacity, stroke: stroke3, strokeOpacity, lineDash, lineDashOffset } = symbol.marker;
|
|
const marker = new Marker();
|
|
marker.shape = shape ?? "square";
|
|
marker.size = size;
|
|
marker.fill = fill;
|
|
marker.fillOpacity = fillOpacity ?? 1;
|
|
marker.stroke = stroke3;
|
|
marker.strokeOpacity = strokeOpacity ?? 1;
|
|
marker.strokeWidth = markerStrokeWidth;
|
|
marker.lineDash = lineDash;
|
|
marker.lineDashOffset = lineDashOffset ?? 0;
|
|
const anchor = Marker.anchor(shape);
|
|
const x = width2 / 2 + (anchor.x - 0.5) * size;
|
|
const y = height2 / 2 + (anchor.y - 0.5) * size;
|
|
const scale2 = size / (size + markerStrokeWidth);
|
|
marker.x = x;
|
|
marker.y = y;
|
|
marker.scalingCenterX = x;
|
|
marker.scalingCenterY = y;
|
|
marker.scalingX = scale2;
|
|
marker.scalingY = scale2;
|
|
group.append(marker);
|
|
}
|
|
return Group.toSVG(group, width2, height2);
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/tooltip/tooltipContent.ts
|
|
var DEFAULT_TOOLTIP_CLASS = "ag-charts-tooltip";
|
|
var DEFAULT_TOOLTIP_DARK_CLASS = "ag-charts-tooltip--dark";
|
|
function textOrSegmentsIsDefined(value) {
|
|
if (value == null) {
|
|
return false;
|
|
} else if (Array.isArray(value)) {
|
|
return value.some((segment) => textOrSegmentsIsDefined(segment.text));
|
|
} else {
|
|
return toTextString(value).trim() !== "";
|
|
}
|
|
}
|
|
function isTooltipValueMissing(value, allowNull = false) {
|
|
if (value == null)
|
|
return !allowNull;
|
|
return typeof value === "number" && !Number.isFinite(value);
|
|
}
|
|
function hasAllMissingData(content) {
|
|
if (content.type === "raw")
|
|
return false;
|
|
if (!content.data || content.data.length === 0)
|
|
return false;
|
|
return content.data.every((datum) => datum.missing === true);
|
|
}
|
|
function aggregateTooltipContent(content) {
|
|
const out = [];
|
|
const groupedContents = /* @__PURE__ */ new Map();
|
|
for (const item of content) {
|
|
if (hasAllMissingData(item))
|
|
continue;
|
|
if (item.type === "structured") {
|
|
const { heading } = item;
|
|
const insertionTarget = textOrSegmentsIsDefined(heading) ? groupedContents.get(heading) : void 0;
|
|
const groupedItem = { type: "structured", heading, items: [item] };
|
|
if (insertionTarget == null) {
|
|
groupedContents.set(heading, groupedItem);
|
|
out.push(groupedItem);
|
|
} else {
|
|
insertionTarget.items.push(item);
|
|
}
|
|
} else {
|
|
out.push(item);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
function tooltipContentAriaLabel(ungroupedContent) {
|
|
const content = aggregateTooltipContent(ungroupedContent);
|
|
const ariaLabel = [];
|
|
for (const c of content) {
|
|
if (c.type === "raw") {
|
|
continue;
|
|
}
|
|
if (textOrSegmentsIsDefined(c.heading)) {
|
|
ariaLabel.push(toPlainText(c.heading));
|
|
}
|
|
for (const i of c.items) {
|
|
if (textOrSegmentsIsDefined(i.title)) {
|
|
ariaLabel.push(toPlainText(i.title));
|
|
}
|
|
if (i.data) {
|
|
for (const datum of i.data) {
|
|
if (datum.missing === true)
|
|
continue;
|
|
ariaLabel.push(datum.label ?? datum.fallbackLabel, toPlainText(datum.value));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ariaLabel.filter((s) => s !== "").join("; ");
|
|
}
|
|
function dataHtml(label, value, inline) {
|
|
let rowHtml = "";
|
|
if (textOrSegmentsIsDefined(label)) {
|
|
rowHtml += `<span class="${DEFAULT_TOOLTIP_CLASS}-label">${sanitizeHtml(label)}</span>`;
|
|
rowHtml += " ";
|
|
rowHtml += `<span class="${DEFAULT_TOOLTIP_CLASS}-value">${sanitizeHtml(value)}</span>`;
|
|
} else {
|
|
rowHtml += `<span class="${DEFAULT_TOOLTIP_CLASS}-label">${sanitizeHtml(value)}</span>`;
|
|
}
|
|
const rowClassNames = [`${DEFAULT_TOOLTIP_CLASS}-row`];
|
|
if (inline)
|
|
rowClassNames.push(`${DEFAULT_TOOLTIP_CLASS}-row--inline`);
|
|
rowHtml = `<div class="${rowClassNames.join(" ")}">${rowHtml}</div>`;
|
|
return rowHtml;
|
|
}
|
|
function tooltipRowContentHtml(content) {
|
|
let html = "";
|
|
if (content.data?.length && content.data.every((datum) => datum.missing === true)) {
|
|
return html;
|
|
}
|
|
const titleDefined = textOrSegmentsIsDefined(content.title);
|
|
const dataInline = !titleDefined && content.data?.length === 1;
|
|
const symbol = content.symbol == null ? void 0 : legendSymbolSvg(content.symbol, 12);
|
|
if (symbol != null && (titleDefined || content.data?.length)) {
|
|
html += `<span class="${DEFAULT_TOOLTIP_CLASS}-symbol">${symbol}</span>`;
|
|
}
|
|
if (titleDefined) {
|
|
html += `<span class="${DEFAULT_TOOLTIP_CLASS}-title">${sanitizeHtml(content.title)}</span>`;
|
|
html += " ";
|
|
}
|
|
if (content.data) {
|
|
for (const datum of content.data) {
|
|
if (datum.missing === true)
|
|
continue;
|
|
html += dataHtml(datum.label ?? datum.fallbackLabel, toPlainText(datum.value), dataInline);
|
|
html += " ";
|
|
}
|
|
}
|
|
return html;
|
|
}
|
|
function tooltipPaginationContentHtml(localeManager, pagination) {
|
|
if (localeManager == null || pagination.length === 1)
|
|
return;
|
|
const text2 = localeManager?.t("tooltipPaginationStatus", {
|
|
index: pagination.index + 1,
|
|
count: pagination.length
|
|
});
|
|
return `<div class="${DEFAULT_TOOLTIP_CLASS}-footer">${text2}</div>`;
|
|
}
|
|
function tooltipContentHtml(localeManager, content, mode, pagination) {
|
|
const singleItem = content.items.length === 1 ? content.items[0] : void 0;
|
|
let compact;
|
|
let compactTitle;
|
|
let compactFallbackLabel;
|
|
switch (mode) {
|
|
case "compact":
|
|
compact = true;
|
|
compactTitle = toPlainText(singleItem?.title);
|
|
break;
|
|
case "single":
|
|
const headingDefined = textOrSegmentsIsDefined(content.heading);
|
|
compact = singleItem != null && (!headingDefined || singleItem.title == null) && singleItem.data?.length === 1 && singleItem.data[0].label == null && singleItem.data[0].value != null;
|
|
compactFallbackLabel = toPlainText(headingDefined ? content.heading : singleItem?.title);
|
|
break;
|
|
case "shared":
|
|
compact = false;
|
|
}
|
|
let html = "";
|
|
if (compact && singleItem != null) {
|
|
if (textOrSegmentsIsDefined(compactTitle)) {
|
|
html += `<span class="${DEFAULT_TOOLTIP_CLASS}-title">${sanitizeHtml(compactTitle)}</span>`;
|
|
}
|
|
if (singleItem.data) {
|
|
for (const datum of singleItem.data) {
|
|
if (datum.missing === true)
|
|
continue;
|
|
html += dataHtml(datum.label ?? compactFallbackLabel, toPlainText(datum.value), false);
|
|
html += " ";
|
|
}
|
|
}
|
|
} else {
|
|
if (textOrSegmentsIsDefined(content.heading)) {
|
|
html += `<span class="${DEFAULT_TOOLTIP_CLASS}-heading">${sanitizeHtml(toPlainText(content.heading))}</span>`;
|
|
html += " ";
|
|
}
|
|
for (const item of content.items) {
|
|
html += tooltipRowContentHtml(item);
|
|
}
|
|
}
|
|
if (html.length === 0)
|
|
return;
|
|
const paginationContent = mode !== "compact" && pagination != null ? tooltipPaginationContentHtml(localeManager, pagination) : void 0;
|
|
if (paginationContent + null) {
|
|
html += paginationContent;
|
|
}
|
|
html = `<div class="${DEFAULT_TOOLTIP_CLASS}-content">${html.trimEnd()}</div>`;
|
|
return html;
|
|
}
|
|
function tooltipPaginationHtml(localeManager, pagination) {
|
|
const paginationContent = pagination == null ? void 0 : tooltipPaginationContentHtml(localeManager, pagination);
|
|
if (paginationContent == null)
|
|
return "";
|
|
return `<div class="${DEFAULT_TOOLTIP_CLASS}-content">${paginationContent}</div>`;
|
|
}
|
|
function tooltipHtml(localeManager, content, mode, pagination) {
|
|
const aggregatedContent = aggregateTooltipContent(content);
|
|
if (aggregatedContent.length === 0)
|
|
return;
|
|
if (aggregatedContent.length === 1 && aggregatedContent[0].type === "structured") {
|
|
return tooltipContentHtml(localeManager, aggregatedContent[0], mode, pagination);
|
|
} else {
|
|
const htmlRows = aggregatedContent.map((c) => {
|
|
return c.type === "structured" ? tooltipContentHtml(localeManager, c, mode) : c.rawHtmlString;
|
|
});
|
|
if (pagination != null) {
|
|
htmlRows.push(tooltipPaginationHtml(localeManager, pagination) ?? "");
|
|
}
|
|
return htmlRows.join("");
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/tooltip/tooltip.ts
|
|
var horizontalAlignments = {
|
|
left: -1,
|
|
"top-left": -1,
|
|
"bottom-left": -1,
|
|
top: 0,
|
|
center: 0,
|
|
bottom: 0,
|
|
right: 1,
|
|
"top-right": 1,
|
|
"bottom-right": 1
|
|
};
|
|
var verticalAlignments = {
|
|
"top-left": -1,
|
|
top: -1,
|
|
"top-right": -1,
|
|
left: 0,
|
|
center: 0,
|
|
right: 0,
|
|
"bottom-left": 1,
|
|
bottom: 1,
|
|
"bottom-right": 1
|
|
};
|
|
var arrowPositions = {
|
|
left: 3 /* Right */,
|
|
"top-left": void 0,
|
|
"bottom-left": void 0,
|
|
top: 2 /* Bottom */,
|
|
center: void 0,
|
|
bottom: 1 /* Top */,
|
|
right: 0 /* Left */,
|
|
"top-right": void 0,
|
|
"bottom-right": void 0
|
|
};
|
|
var directionChecks = {
|
|
top: 2 /* Vertical */,
|
|
bottom: 2 /* Vertical */,
|
|
left: 1 /* Horizontal */,
|
|
right: 1 /* Horizontal */,
|
|
"top-right": 3 /* Both */,
|
|
"top-left": 3 /* Both */,
|
|
"bottom-right": 3 /* Both */,
|
|
"bottom-left": 3 /* Both */,
|
|
center: 0 /* None */
|
|
};
|
|
var defaultPlacements = {
|
|
pointer: "top",
|
|
node: "top",
|
|
chart: "top-left"
|
|
};
|
|
var TooltipPosition = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
/** The horizontal offset in pixels for the position of the tooltip. */
|
|
this.xOffset = 0;
|
|
/** The vertical offset in pixels for the position of the tooltip. */
|
|
this.yOffset = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TooltipPosition.prototype, "xOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TooltipPosition.prototype, "yOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TooltipPosition.prototype, "anchorTo", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TooltipPosition.prototype, "placement", 2);
|
|
var Tooltip = class extends BaseProperties {
|
|
constructor() {
|
|
super();
|
|
this.enabled = true;
|
|
this.mode = "single";
|
|
this.delay = 0;
|
|
this.range = void 0;
|
|
this.wrapping = "hyphenate";
|
|
this.position = new TooltipPosition();
|
|
this.pagination = false;
|
|
this.darkTheme = false;
|
|
this.bounds = "extended";
|
|
this.cleanup = new CleanupRegistry();
|
|
this.springAnimation = new SpringAnimation();
|
|
this.enableInteraction = false;
|
|
this.wrapTypes = ["always", "hyphenate", "on-space", "never"];
|
|
this.sizeMonitor = new SizeMonitor();
|
|
// Reading the element size is expensive, so cache the result
|
|
this._elementSize = void 0;
|
|
this._showTimeout = void 0;
|
|
this.arrowPosition = void 0;
|
|
this._visible = false;
|
|
this.positionParams = void 0;
|
|
this.localeManager = void 0;
|
|
this.cleanup.register(this.springAnimation.events.on("update", this.updateTooltipPosition.bind(this)));
|
|
}
|
|
get interactive() {
|
|
return this.enableInteraction;
|
|
}
|
|
setup(localeManager, domManager) {
|
|
if ("togglePopover" in getWindow().HTMLElement.prototype) {
|
|
this.element = domManager.addChild("tooltip-container", DEFAULT_TOOLTIP_CLASS);
|
|
this.element.setAttribute("popover", "manual");
|
|
this.element.className = DEFAULT_TOOLTIP_CLASS;
|
|
this.element.style.positionAnchor = domManager.anchorName;
|
|
this.sizeMonitor.observe(this.element, (size) => {
|
|
this._elementSize = size;
|
|
this.updateTooltipPosition();
|
|
});
|
|
}
|
|
this.localeManager = localeManager;
|
|
return () => {
|
|
domManager.removeChild("tooltip-container", DEFAULT_TOOLTIP_CLASS);
|
|
this.cleanup.flush();
|
|
if (this.element) {
|
|
this.sizeMonitor.unobserve(this.element);
|
|
}
|
|
};
|
|
}
|
|
isVisible() {
|
|
return this._visible;
|
|
}
|
|
contains(node) {
|
|
return this.element?.contains(node) ?? false;
|
|
}
|
|
updateTooltipPosition() {
|
|
const { element: element2, _elementSize: elementSize, positionParams } = this;
|
|
if (element2 == null || elementSize == null || positionParams == null)
|
|
return;
|
|
const { canvasRect, relativeRect, meta } = positionParams;
|
|
const { x: canvasX, y: canvasY } = this.springAnimation;
|
|
const anchorTo = meta.position?.anchorTo ?? "pointer";
|
|
let placements2 = meta.position?.placement ?? defaultPlacements[anchorTo];
|
|
if (!Array.isArray(placements2)) {
|
|
placements2 = [placements2];
|
|
}
|
|
const xOffset = meta.position?.xOffset ?? 0;
|
|
const yOffset = meta.position?.yOffset ?? 0;
|
|
const minX = relativeRect.x;
|
|
const minY = relativeRect.y;
|
|
const maxX = relativeRect.width - elementSize.width - 1 + minX;
|
|
const maxY = relativeRect.height - elementSize.height + minY;
|
|
let i = 0;
|
|
let placement;
|
|
let position;
|
|
let constrained = false;
|
|
do {
|
|
placement = placements2[i];
|
|
i += 1;
|
|
const tooltipBounds = this.getTooltipBounds({
|
|
elementSize,
|
|
placement,
|
|
anchorTo,
|
|
canvasX,
|
|
canvasY,
|
|
yOffset,
|
|
xOffset,
|
|
canvasRect
|
|
});
|
|
position = calculatePlacement(elementSize.width, elementSize.height, relativeRect, tooltipBounds);
|
|
constrained = false;
|
|
if (directionChecks[placement] & 1 /* Horizontal */) {
|
|
constrained || (constrained = position.x < minX || position.x > maxX);
|
|
}
|
|
if (directionChecks[placement] & 2 /* Vertical */) {
|
|
constrained || (constrained = position.y < minY || position.y > maxY);
|
|
}
|
|
} while (i < placements2.length && constrained);
|
|
const left = clamp(minX, position.x, maxX);
|
|
const top = clamp(minY, position.y, maxY);
|
|
constrained || (constrained = left !== position.x || top !== position.y);
|
|
const defaultShowArrow = anchorTo !== "chart" && !constrained && !xOffset && !yOffset;
|
|
const showArrow = meta.showArrow ?? this.showArrow ?? defaultShowArrow;
|
|
this.arrowPosition = showArrow ? arrowPositions[placement] : void 0;
|
|
this.updateClassModifiers();
|
|
element2.style.translate = `${left}px ${top}px`;
|
|
}
|
|
/**
|
|
* Shows tooltip at the given event's coordinates.
|
|
* If the `html` parameter is missing, moves the existing tooltip to the new position.
|
|
*/
|
|
show(boundingRect, canvasRect, meta, content, pagination, instantly = false) {
|
|
const { element: element2 } = this;
|
|
if (element2 != null && content != null && content.length !== 0) {
|
|
const html = tooltipHtml(this.localeManager, content, this.mode, this.pagination ? pagination : void 0);
|
|
if (html == null) {
|
|
element2.innerHTML = "";
|
|
this.toggle(false);
|
|
return;
|
|
}
|
|
element2.innerHTML = html;
|
|
} else if (element2 == null || element2.innerHTML === "") {
|
|
this.toggle(false);
|
|
return;
|
|
}
|
|
const relativeRect = {
|
|
x: boundingRect.x - canvasRect.x,
|
|
y: boundingRect.y - canvasRect.y,
|
|
width: boundingRect.width,
|
|
height: boundingRect.height
|
|
};
|
|
this.positionParams = {
|
|
canvasRect,
|
|
relativeRect,
|
|
meta
|
|
};
|
|
const anchorTo = meta.position?.anchorTo ?? "pointer";
|
|
switch (anchorTo) {
|
|
case "node":
|
|
this.springAnimation.update(meta.nodeCanvasX ?? meta.canvasX, meta.nodeCanvasY ?? meta.canvasY);
|
|
break;
|
|
case "pointer":
|
|
this.springAnimation.update(meta.canvasX, meta.canvasY);
|
|
break;
|
|
case "chart":
|
|
this.springAnimation.reset();
|
|
}
|
|
if (meta.enableInteraction) {
|
|
this.enableInteraction = true;
|
|
element2.style.pointerEvents = "auto";
|
|
element2.removeAttribute("aria-hidden");
|
|
element2.tabIndex = -1;
|
|
} else {
|
|
this.enableInteraction = false;
|
|
element2.style.pointerEvents = "none";
|
|
element2.setAttribute("aria-hidden", "true");
|
|
element2.removeAttribute("tabindex");
|
|
}
|
|
element2.style.setProperty("--top", `${canvasRect.top}px`);
|
|
element2.style.setProperty("--left", `${canvasRect.left}px`);
|
|
this.updateClassModifiers();
|
|
this.toggle(true, instantly);
|
|
}
|
|
hide() {
|
|
this.toggle(false);
|
|
}
|
|
maybeEnterInteractiveTooltip({ relatedTarget }, callback2) {
|
|
const { interactive, interactiveLeave, enabled, element: element2 } = this;
|
|
if (element2 == null)
|
|
return false;
|
|
if (interactiveLeave)
|
|
return true;
|
|
const isEntering = interactive && enabled && this.isVisible() && isNode(relatedTarget) && this.contains(relatedTarget);
|
|
if (isEntering) {
|
|
this.interactiveLeave = {
|
|
callback: callback2,
|
|
listener: (popoverEvent) => {
|
|
const isLeaving = popoverEvent.relatedTarget == null || isNode(popoverEvent.relatedTarget) && !this.contains(popoverEvent.relatedTarget);
|
|
if (isLeaving) {
|
|
this.popInteractiveLeaveCallback();
|
|
}
|
|
}
|
|
};
|
|
element2.addEventListener("focusout", this.interactiveLeave.listener);
|
|
element2.addEventListener("mouseout", this.interactiveLeave.listener);
|
|
}
|
|
return isEntering;
|
|
}
|
|
popInteractiveLeaveCallback() {
|
|
const { interactiveLeave, element: element2 } = this;
|
|
this.interactiveLeave = void 0;
|
|
if (interactiveLeave) {
|
|
if (element2) {
|
|
element2.removeEventListener("focusout", interactiveLeave.listener);
|
|
element2.removeEventListener("mouseout", interactiveLeave.listener);
|
|
}
|
|
interactiveLeave.callback();
|
|
}
|
|
}
|
|
toggle(visible, instantly = false) {
|
|
const { delay } = this;
|
|
if (visible && delay > 0 && !instantly) {
|
|
this._showTimeout ?? (this._showTimeout = setTimeout(() => {
|
|
this._showTimeout = void 0;
|
|
this.toggleCallback(true);
|
|
}, delay));
|
|
} else {
|
|
clearTimeout(this._showTimeout);
|
|
this._showTimeout = void 0;
|
|
this.toggleCallback(visible);
|
|
}
|
|
}
|
|
toggleCallback(visible) {
|
|
if (!this.element?.isConnected)
|
|
return;
|
|
if (this._visible === visible)
|
|
return;
|
|
this._visible = visible;
|
|
this.element.togglePopover(visible);
|
|
if (visible) {
|
|
this.updateTooltipPosition();
|
|
} else {
|
|
this.springAnimation.reset();
|
|
this.popInteractiveLeaveCallback();
|
|
}
|
|
}
|
|
updateClassModifiers() {
|
|
if (!this.element?.isConnected)
|
|
return;
|
|
const { classList } = this.element;
|
|
const toggleClass = (name, include) => classList.toggle(`${DEFAULT_TOOLTIP_CLASS}--${name}`, include);
|
|
toggleClass("no-interaction", !this.enableInteraction);
|
|
toggleClass("arrow-top", this.arrowPosition === 1 /* Top */);
|
|
toggleClass("arrow-right", this.arrowPosition === 3 /* Right */);
|
|
toggleClass("arrow-bottom", this.arrowPosition === 2 /* Bottom */);
|
|
toggleClass("arrow-left", this.arrowPosition === 0 /* Left */);
|
|
toggleClass("compact", this.mode === "compact");
|
|
classList.toggle(DEFAULT_TOOLTIP_DARK_CLASS, this.darkTheme);
|
|
for (const wrapType of this.wrapTypes) {
|
|
classList.toggle(`${DEFAULT_TOOLTIP_CLASS}--wrap-${wrapType}`, wrapType === this.wrapping);
|
|
}
|
|
}
|
|
getTooltipBounds(opts) {
|
|
const { elementSize, anchorTo, placement, canvasX, canvasY, yOffset, xOffset, canvasRect } = opts;
|
|
const { width: tooltipWidth, height: tooltipHeight } = elementSize;
|
|
const bounds = { width: tooltipWidth, height: tooltipHeight };
|
|
if (anchorTo === "node" || anchorTo === "pointer") {
|
|
const horizontalAlignment = horizontalAlignments[placement];
|
|
const verticalAlignment = verticalAlignments[placement];
|
|
bounds.top = canvasY + yOffset + tooltipHeight * (verticalAlignment - 1) / 2 + 8 * verticalAlignment;
|
|
bounds.left = canvasX + xOffset + tooltipWidth * (horizontalAlignment - 1) / 2 + 8 * horizontalAlignment;
|
|
return bounds;
|
|
}
|
|
switch (placement) {
|
|
case "top": {
|
|
bounds.top = yOffset;
|
|
bounds.left = canvasRect.width / 2 - tooltipWidth / 2 + xOffset;
|
|
return bounds;
|
|
}
|
|
case "right": {
|
|
bounds.top = canvasRect.height / 2 - tooltipHeight / 2 + yOffset;
|
|
bounds.left = canvasRect.width - tooltipWidth + xOffset;
|
|
return bounds;
|
|
}
|
|
case "left": {
|
|
bounds.top = canvasRect.height / 2 - tooltipHeight / 2 + yOffset;
|
|
bounds.left = xOffset;
|
|
return bounds;
|
|
}
|
|
case "bottom": {
|
|
bounds.top = canvasRect.height - tooltipHeight + yOffset;
|
|
bounds.left = canvasRect.width / 2 - tooltipWidth / 2 + xOffset;
|
|
return bounds;
|
|
}
|
|
case "top-left": {
|
|
bounds.top = yOffset;
|
|
bounds.left = xOffset;
|
|
return bounds;
|
|
}
|
|
case "top-right": {
|
|
bounds.top = yOffset;
|
|
bounds.left = canvasRect.width - tooltipWidth + xOffset;
|
|
return bounds;
|
|
}
|
|
case "bottom-right": {
|
|
bounds.top = canvasRect.height - tooltipHeight + yOffset;
|
|
bounds.left = canvasRect.width - tooltipWidth + xOffset;
|
|
return bounds;
|
|
}
|
|
case "bottom-left": {
|
|
bounds.top = canvasRect.height - tooltipHeight + yOffset;
|
|
bounds.left = xOffset;
|
|
return bounds;
|
|
}
|
|
}
|
|
return bounds;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Tooltip.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Tooltip.prototype, "mode", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Tooltip.prototype, "showArrow", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Tooltip.prototype, "delay", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Tooltip.prototype, "range", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Tooltip.prototype, "wrapping", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Tooltip.prototype, "position", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Tooltip.prototype, "pagination", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Tooltip.prototype, "darkTheme", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Tooltip.prototype, "bounds", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/series/pickManager.ts
|
|
function getItemId(node) {
|
|
if (node.datum.itemId !== void 0) {
|
|
return node.datum.itemId;
|
|
} else if (typeof node.datum.datumIndex === "number") {
|
|
return node.datum.datumIndex;
|
|
} else {
|
|
return JSON.stringify(node.datum.datumIndex);
|
|
}
|
|
}
|
|
function pickedNodesEqual(a, b) {
|
|
return a.series === b.series && objectsEqual(a.datumIndex, b.datumIndex);
|
|
}
|
|
function indexOf(candidates, node) {
|
|
return node == void 0 ? -1 : candidates.findIndex((c) => pickedNodesEqual(c, node));
|
|
}
|
|
var PickManager = class {
|
|
constructor(activeManager, tooltipProperties, focusState) {
|
|
this.activeManager = activeManager;
|
|
this.tooltipProperties = tooltipProperties;
|
|
this.focusState = focusState;
|
|
this.candidates = [];
|
|
this.activeState = new StateTracker();
|
|
}
|
|
getActive() {
|
|
return this.activeState.stateValue();
|
|
}
|
|
clear() {
|
|
this.activeState.clear();
|
|
this.lastNotifiedActive = void 0;
|
|
this.candidates.length = 0;
|
|
this.pendingPickedNodes = void 0;
|
|
}
|
|
setSource(source, node) {
|
|
if (node === void 0) {
|
|
this.activeState.delete(source);
|
|
} else {
|
|
this.activeState.set(source, node);
|
|
}
|
|
this.syncActiveManager();
|
|
}
|
|
syncActiveManager() {
|
|
const resolved = this.getActive();
|
|
const prev = this.lastNotifiedActive;
|
|
if (resolved === prev)
|
|
return;
|
|
if (resolved !== void 0 && prev !== void 0 && pickedNodesEqual(resolved, prev))
|
|
return;
|
|
this.lastNotifiedActive = resolved;
|
|
if (resolved === void 0) {
|
|
this.activeManager.clear();
|
|
} else {
|
|
const seriesId = resolved.series.id;
|
|
const itemId = getItemId(resolved);
|
|
this.activeManager.update({ type: "series-node", seriesId, itemId }, resolved.datum);
|
|
}
|
|
}
|
|
popPendingPickedNodes() {
|
|
const result = this.pendingPickedNodes;
|
|
this.pendingPickedNodes = void 0;
|
|
return result;
|
|
}
|
|
// Some user interactive (e.g. mouseleave, blur) has cleared the active datum.
|
|
onClearUI() {
|
|
this.activeManager.clear();
|
|
this.clear();
|
|
}
|
|
// Active datum was cleared by ActiveManager (`setState` or legend).
|
|
onClearAPI() {
|
|
this.clear();
|
|
}
|
|
onPickedNodesHighlight(pickedNodes) {
|
|
if (pickedNodes !== void 0) {
|
|
const previousActive = this.getActive();
|
|
if (this.tooltipProperties.pagination && previousActive !== void 0) {
|
|
const tooltipMatch = pickedNodes.matches.find((m) => pickedNodesEqual(m, previousActive));
|
|
if (tooltipMatch) {
|
|
return tooltipMatch;
|
|
}
|
|
}
|
|
}
|
|
const node = pickedNodes?.matches[0];
|
|
this.setSource("highlight", node);
|
|
return node;
|
|
}
|
|
onPickedNodesTooltip(pickedNodes) {
|
|
if (pickedNodes !== void 0 && this.tooltipProperties.pagination) {
|
|
const previous = this.getActive();
|
|
const nextCandidates = pickedNodes.matches;
|
|
this.candidates = nextCandidates;
|
|
let nextIndex = indexOf(nextCandidates, previous);
|
|
if (nextIndex === -1)
|
|
nextIndex = 0;
|
|
const node2 = nextCandidates[nextIndex];
|
|
this.setSource("tooltip", node2);
|
|
const paginationState = { index: nextIndex, length: nextCandidates.length };
|
|
return { active: node2, paginationState };
|
|
}
|
|
const node = pickedNodes?.matches[0];
|
|
this.setSource("tooltip", node);
|
|
return { active: node };
|
|
}
|
|
onPickedNodesFocus(pickedFocus) {
|
|
const { series } = this.focusState;
|
|
this.clear();
|
|
if (series !== void 0 && pickedFocus !== void 0) {
|
|
const { datum, datumIndex } = pickedFocus;
|
|
this.setSource("focus", { series, datum, datumIndex });
|
|
}
|
|
}
|
|
onPickedNodesAPI(debouncedPickedNodes) {
|
|
this.pendingPickedNodes = debouncedPickedNodes;
|
|
return debouncedPickedNodes.matches[0];
|
|
}
|
|
onPickedNodesAPIDebounced() {
|
|
return { active: this.onPickedNodesHighlight(this.popPendingPickedNodes()) };
|
|
}
|
|
nextCandidate() {
|
|
if (this.tooltipProperties.pagination) {
|
|
const { candidates } = this;
|
|
const previous = this.getActive();
|
|
const hoverIndex = previous == null ? -1 : candidates.findIndex((c) => pickedNodesEqual(c, previous));
|
|
if (hoverIndex === -1)
|
|
return { active: void 0, paginationState: void 0 };
|
|
let nextIndex = hoverIndex + 1;
|
|
if (nextIndex >= candidates.length) {
|
|
nextIndex = 0;
|
|
}
|
|
const node = candidates[nextIndex];
|
|
this.setSource("tooltip", node);
|
|
const paginationState = { index: nextIndex, length: this.candidates.length };
|
|
return { active: node, paginationState };
|
|
}
|
|
return { active: this.getActive() };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/seriesAreaManager.ts
|
|
var SeriesAreaManager = class extends BaseManager {
|
|
constructor(chart) {
|
|
super();
|
|
this.chart = chart;
|
|
this.id = createId(this);
|
|
this.series = [];
|
|
this.announceMode = "when-changed";
|
|
this.highlight = {
|
|
/** Last received event that still needs to be applied. */
|
|
pendingHoverEvent: void 0,
|
|
/** Last applied event. */
|
|
appliedHoverEvent: void 0,
|
|
/** Last applied event, which has been temporarily stashed during the main chart update cycle. */
|
|
stashedHoverEvent: void 0
|
|
};
|
|
this.tooltip = {
|
|
lastHover: void 0
|
|
};
|
|
this.activeState = {
|
|
lastActive: void 0
|
|
};
|
|
/**
|
|
* A11y Requirements for Tooltip/Highlight (see AG-13051 for details):
|
|
*
|
|
* - When the series-area is blurred, allow the mouse to update the tooltip/highlight.
|
|
*
|
|
* - When the series-area receives a `focus` event, use `:focus-visible` to guess the input device.
|
|
* (this is decided by the browser).
|
|
*
|
|
* - For keyboard users, `focus` and `keydown` events always updates & shows the tooltip/highlight on
|
|
* the currently (or newly) focused datum.
|
|
*
|
|
* - For keyboard users, `mousemove` events update the tooltip/highlight iff `pickNode` finds a match
|
|
* for the mouse event offsets.
|
|
*/
|
|
this.hoverDevice = "pointer";
|
|
this.focus = {
|
|
sortedSeries: [],
|
|
series: void 0,
|
|
seriesIndex: 0,
|
|
datumIndex: 0,
|
|
datum: void 0
|
|
};
|
|
this.cachedTooltipContent = void 0;
|
|
this.hoverScheduler = debouncedAnimationFrame(() => {
|
|
if (this.hoverDevice === "setState") {
|
|
return this.handleHoverFromState();
|
|
}
|
|
if (!this.tooltip.lastHover && !this.highlight.pendingHoverEvent)
|
|
return;
|
|
if (this.chart.getUpdateType() <= 7 /* SERIES_UPDATE */) {
|
|
this.hoverScheduler.schedule();
|
|
return;
|
|
}
|
|
if (this.highlight.pendingHoverEvent) {
|
|
this.handleHoverHighlight(false);
|
|
}
|
|
if (this.tooltip.lastHover) {
|
|
this.handleHoverTooltip(this.tooltip.lastHover, false);
|
|
}
|
|
});
|
|
this.pickManager = new PickManager(chart.ctx.activeManager, chart.tooltip, this.focus);
|
|
const initialAltText = chart.ctx.localeManager.t("ariaInitSeriesArea");
|
|
const label1 = chart.ctx.domManager.addChild("series-area", "series-area-aria-label1");
|
|
const label2 = chart.ctx.domManager.addChild("series-area", "series-area-aria-label2");
|
|
this.swapChain = new FocusSwapChain(label1, label2, "img", initialAltText);
|
|
this.swapChain.addListener("blur", (event) => this.onBlur(event));
|
|
this.swapChain.addListener("focus", () => this.onFocus());
|
|
if (chart.ctx.domManager.mode === "normal") {
|
|
this.focusIndicator = new FocusIndicator(this.swapChain);
|
|
this.focusIndicator.overrideFocusVisible(chart.mode === "integrated" ? false : void 0);
|
|
}
|
|
const { seriesDragInterpreter, seriesWidget, containerWidget } = chart.ctx.widgets;
|
|
seriesWidget.setTabIndex(-1);
|
|
this.cleanup.register(
|
|
() => chart.ctx.domManager.removeChild("series-area", "series-area-aria-label1"),
|
|
() => chart.ctx.domManager.removeChild("series-area", "series-area-aria-label2"),
|
|
seriesWidget.addListener("focus", () => this.swapChain.focus({ preventScroll: true })),
|
|
seriesWidget.addListener("mousemove", (event) => this.onHover(event, seriesWidget)),
|
|
seriesWidget.addListener("wheel", (event) => this.onWheel(event)),
|
|
seriesWidget.addListener("mouseleave", (event) => this.onLeave(event)),
|
|
seriesWidget.addListener("keydown", (event) => this.onKeyDown(event)),
|
|
seriesWidget.addListener("contextmenu", (event, current) => this.onContextMenu(event, current)),
|
|
containerWidget.addListener("contextmenu", (event, current) => this.onContextMenu(event, current)),
|
|
containerWidget.addListener("click", (event, current) => this.onClick(event, current)),
|
|
containerWidget.addListener("dblclick", (event, current) => this.onClick(event, current)),
|
|
chart.ctx.animationManager.addListener("animation-start", () => this.onAnimationStart()),
|
|
chart.ctx.eventsHub.on("active:load-memento", (event) => this.onActiveLoadMemento(event)),
|
|
chart.ctx.eventsHub.on("active:update", (event) => this.onActiveUpdate(event)),
|
|
chart.ctx.eventsHub.on("dom:resize", () => this.clearAll()),
|
|
chart.ctx.eventsHub.on("highlight:change", (event) => this.changeHighlightDatum(event)),
|
|
chart.ctx.eventsHub.on("layout:complete", (event) => this.layoutComplete(event)),
|
|
chart.ctx.updateService.addListener("pre-scene-render", () => this.preSceneRender()),
|
|
chart.ctx.updateService.addListener("update-complete", () => this.updateComplete()),
|
|
chart.ctx.eventsHub.on("zoom:change-complete", () => this.clearAll()),
|
|
chart.ctx.eventsHub.on("zoom:pan-start", () => this.clearAll())
|
|
);
|
|
if (seriesDragInterpreter) {
|
|
this.cleanup.register(
|
|
seriesDragInterpreter.events.on("drag-move", (event) => this.onDragMove(event, seriesWidget)),
|
|
seriesDragInterpreter.events.on("click", (event) => this.onClick(event, seriesWidget)),
|
|
seriesDragInterpreter.events.on("dblclick", (event) => this.onClick(event, seriesWidget))
|
|
);
|
|
}
|
|
}
|
|
get bbox() {
|
|
return (this.seriesRect ?? BBox.zero).clone();
|
|
}
|
|
isState(allowedStates) {
|
|
return this.chart.ctx.interactionManager.isState(allowedStates);
|
|
}
|
|
isIgnoredTouch(event) {
|
|
if (event.device !== "touch" || event.type === "click")
|
|
return false;
|
|
if (this.chart.ctx.chartService.touch.dragAction === "hover")
|
|
return false;
|
|
if (this.chart.ctx.chartService.touch.dragAction === "drag") {
|
|
if (this.isState(18 /* AnnotationsMoveable */)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
dataChanged() {
|
|
var _a;
|
|
this.cachedTooltipContent = void 0;
|
|
if (this.highlight.appliedHoverEvent) {
|
|
(_a = this.highlight).stashedHoverEvent ?? (_a.stashedHoverEvent = this.highlight.appliedHoverEvent);
|
|
this.clearHighlight();
|
|
}
|
|
if (this.hoverDevice !== "setState") {
|
|
this.chart.ctx.tooltipManager.removeTooltip(this.id);
|
|
this.focusIndicator?.clear();
|
|
}
|
|
}
|
|
preSceneRender() {
|
|
if (this.highlight.stashedHoverEvent != null) {
|
|
this.highlight.pendingHoverEvent = this.tooltip.lastHover ?? this.highlight.stashedHoverEvent;
|
|
this.highlight.stashedHoverEvent = void 0;
|
|
this.handleHoverHighlight(true);
|
|
}
|
|
if (this.tooltip.lastHover != null) {
|
|
this.handleHoverTooltip(this.tooltip.lastHover, true);
|
|
}
|
|
if (this.hoverDevice === "setState") {
|
|
this.refreshSetState();
|
|
}
|
|
}
|
|
updateComplete() {
|
|
if (this.isState(68 /* Focusable */) && this.focusIndicator?.isFocusVisible()) {
|
|
if (this.announceMode !== "always") {
|
|
this.announceMode = "never";
|
|
}
|
|
this.handleFocus(0, 0);
|
|
}
|
|
}
|
|
update(type, opts) {
|
|
this.chart.ctx.updateService.update(type, opts);
|
|
}
|
|
seriesChanged(series) {
|
|
this.focus.sortedSeries = [...series].sort((a, b) => {
|
|
let fpA = a.properties.focusPriority ?? Infinity;
|
|
let fpB = b.properties.focusPriority ?? Infinity;
|
|
if (fpA === fpB) {
|
|
[fpA, fpB] = [a.declarationOrder, b.declarationOrder];
|
|
}
|
|
if (fpA < fpB) {
|
|
return -1;
|
|
} else if (fpA > fpB) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
this.series = series;
|
|
}
|
|
layoutComplete(event) {
|
|
this.seriesRect = event.series.rect;
|
|
this.hoverRect = event.series.rect;
|
|
this.chart.ctx.widgets.seriesWidget.setBounds(event.series.rect);
|
|
if (this.chart.ctx.domManager.mode === "normal") {
|
|
this.chart.ctx.widgets.chartWidget.setBounds(event.chart);
|
|
}
|
|
}
|
|
onAnimationStart() {
|
|
if (this.hoverDevice !== "setState") {
|
|
this.clearAll();
|
|
}
|
|
}
|
|
onContextMenu(event, current) {
|
|
const { sourceEvent } = event;
|
|
if (sourceEvent.currentTarget != current.getElement())
|
|
return;
|
|
if (current !== this.chart.ctx.widgets.seriesWidget) {
|
|
if (this.isState(72 /* ContextMenuable */)) {
|
|
const { currentX: canvasX2, currentY: canvasY2 } = event;
|
|
this.chart.ctx.contextMenuRegistry.dispatchContext(
|
|
"always",
|
|
{ widgetEvent: event, canvasX: canvasX2, canvasY: canvasY2 },
|
|
void 0
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
let pickedNode;
|
|
let position;
|
|
if (this.focusIndicator?.isFocusVisible()) {
|
|
pickedNode = this.chart.ctx.highlightManager.getActiveHighlight();
|
|
if (pickedNode && this.seriesRect && pickedNode.midPoint) {
|
|
position = Transformable.toCanvasPoint(
|
|
pickedNode.series.contentGroup,
|
|
pickedNode.midPoint.x,
|
|
pickedNode.midPoint.y
|
|
);
|
|
}
|
|
} else if (this.isState(72 /* ContextMenuable */)) {
|
|
const pick2 = this.pickNodes({ x: event.currentX, y: event.currentY }, "context-menu");
|
|
if (pick2) {
|
|
this.chart.ctx.highlightManager.updateHighlight(this.id);
|
|
pickedNode = pick2.matches[0].datum;
|
|
}
|
|
}
|
|
const pickedSeries = pickedNode?.series;
|
|
this.clearAll();
|
|
const canvasX = event.currentX + current.cssLeft();
|
|
const canvasY = event.currentY + current.cssTop();
|
|
const { datumIndex } = pickedNode ?? {};
|
|
if (pickedSeries && pickedNode && datumIndex != null) {
|
|
this.chart.ctx.contextMenuRegistry.dispatchContext(
|
|
"series-node",
|
|
{ widgetEvent: event, canvasX, canvasY },
|
|
{ pickedSeries, pickedNode: { ...pickedNode, datumIndex } },
|
|
position
|
|
);
|
|
} else {
|
|
this.chart.ctx.contextMenuRegistry.dispatchContext(
|
|
"series-area",
|
|
{ widgetEvent: event, canvasX, canvasY },
|
|
void 0,
|
|
position
|
|
);
|
|
}
|
|
}
|
|
onLeave(event) {
|
|
if (!this.isState(82 /* Clickable */))
|
|
return;
|
|
const relatedTarget = event.sourceEvent.relatedTarget;
|
|
if (relatedTarget?.className === "ag-charts-text-input__textarea") {
|
|
return;
|
|
}
|
|
if (this.maybeEnterInteractiveTooltip(event.sourceEvent)) {
|
|
return;
|
|
}
|
|
this.chart.ctx.domManager.updateCursor(this.id);
|
|
if (this.hoverDevice !== "keyboard")
|
|
this.clearAll(true);
|
|
}
|
|
onWheel(_event) {
|
|
if (!this.isState(82 /* Clickable */))
|
|
return;
|
|
this.focusIndicator?.overrideFocusVisible(false);
|
|
this.hoverDevice = "pointer";
|
|
}
|
|
onDragMove(event, current) {
|
|
if (!this.isState(82 /* Clickable */))
|
|
return;
|
|
this.focusIndicator?.overrideFocusVisible(false);
|
|
this.onHoverLikeEvent(event, current);
|
|
}
|
|
onHover(event, current) {
|
|
if (!this.isState(82 /* Clickable */))
|
|
return;
|
|
this.onHoverLikeEvent(event, current);
|
|
}
|
|
onHoverLikeEvent(event, current) {
|
|
if (this.isIgnoredTouch(event))
|
|
return;
|
|
if (event.device === "touch" && this.chart.ctx.chartService.touch.dragAction === "hover") {
|
|
event.sourceEvent.preventDefault();
|
|
}
|
|
if (current !== this.chart.ctx.widgets.seriesWidget)
|
|
return;
|
|
this.tooltip.lastHover = event;
|
|
this.hoverDevice = "pointer";
|
|
this.highlight.pendingHoverEvent = event;
|
|
this.hoverScheduler.schedule();
|
|
let pick2;
|
|
if (this.isState(64 /* Default */)) {
|
|
const { currentX: x, currentY: y } = event;
|
|
pick2 = this.pickNodes({ x, y }, "event");
|
|
const matches = pick2?.matches;
|
|
const found = matches?.[0];
|
|
if (found?.series.hasEventListener("seriesNodeClick") || found?.series.hasEventListener("seriesNodeDoubleClick") || matches != null && matches.length > 1 && this.chart.tooltip.pagination) {
|
|
this.chart.ctx.domManager.updateCursor(this.id, "pointer");
|
|
} else {
|
|
this.chart.ctx.domManager.updateCursor(this.id);
|
|
}
|
|
}
|
|
const consumed = Boolean(pick2?.matches.length);
|
|
this.emitSeriesAreaHoverEvent(event, consumed);
|
|
}
|
|
onClick(event, current) {
|
|
if (event.device === "keyboard") {
|
|
return;
|
|
}
|
|
if (current === this.chart.ctx.widgets.seriesWidget && this.chart.ctx.animationManager.isActive()) {
|
|
this.chart.ctx.animationManager.skipCurrentBatch();
|
|
}
|
|
if (event.device === "touch" && current === this.chart.ctx.widgets.seriesWidget) {
|
|
this.swapChain.focus({ preventScroll: true });
|
|
}
|
|
if (!this.isState(82 /* Clickable */))
|
|
return;
|
|
if (current === this.chart.ctx.widgets.seriesWidget) {
|
|
if (!current.getElement().contains(event.sourceEvent.target)) {
|
|
return;
|
|
}
|
|
} else if (event.sourceEvent.target != current.getElement()) {
|
|
return;
|
|
}
|
|
this.focusIndicator?.overrideFocusVisible(false);
|
|
this.onHoverLikeEvent(event, current);
|
|
const isSeriesWidget = current === this.chart.ctx.widgets.seriesWidget;
|
|
if (!this.isState(64 /* Default */)) {
|
|
if (isSeriesWidget) {
|
|
this.emitSeriesAreaClickEvent(event, false);
|
|
}
|
|
return;
|
|
}
|
|
if (isSeriesWidget) {
|
|
const consumed = this.checkSeriesNodeClick(event);
|
|
if (consumed) {
|
|
this.emitSeriesAreaClickEvent(event, true);
|
|
this.update(7 /* SERIES_UPDATE */);
|
|
event.sourceEvent.preventDefault();
|
|
return;
|
|
}
|
|
this.emitSeriesAreaClickEvent(event, false);
|
|
}
|
|
const newEvent = { type: event.type === "click" ? "click" : "doubleClick", event: event.sourceEvent };
|
|
this.chart.fireEvent(newEvent);
|
|
}
|
|
emitSeriesAreaHoverEvent(event, consumed) {
|
|
const { canvasX, canvasY } = this.toCanvasCoordinates(event);
|
|
const payload = { canvasX, canvasY, consumed, sourceEvent: event.sourceEvent };
|
|
this.chart.ctx.eventsHub.emit("series-area:hover", payload);
|
|
}
|
|
emitSeriesAreaClickEvent(event, consumed) {
|
|
if (!("currentX" in event))
|
|
return;
|
|
const { canvasX, canvasY } = this.toCanvasCoordinates(event);
|
|
const payload = { canvasX, canvasY, consumed, sourceEvent: event.sourceEvent };
|
|
this.chart.ctx.eventsHub.emit("series-area:click", payload);
|
|
}
|
|
toCanvasCoordinates(event) {
|
|
return {
|
|
canvasX: event.currentX + (this.hoverRect?.x ?? this.seriesRect?.x ?? 0),
|
|
canvasY: event.currentY + (this.hoverRect?.y ?? this.seriesRect?.y ?? 0)
|
|
};
|
|
}
|
|
onFocus() {
|
|
if (!this.isState(68 /* Focusable */))
|
|
return;
|
|
this.hoverDevice = this.focusIndicator?.isFocusVisible(true) ? "keyboard" : "pointer";
|
|
this.handleFocus(0, 0);
|
|
}
|
|
onBlur(event) {
|
|
if (!this.isState(68 /* Focusable */))
|
|
return;
|
|
this.hoverDevice = "pointer";
|
|
if (!this.maybeEnterInteractiveTooltip(event)) {
|
|
this.clearAll(true);
|
|
}
|
|
this.focusIndicator?.overrideFocusVisible(void 0);
|
|
}
|
|
onKeyDown(widgetEvent) {
|
|
if (!this.isState(86 /* Keyable */))
|
|
return;
|
|
const action = mapKeyboardEventToAction(widgetEvent.sourceEvent);
|
|
if (action?.activatesFocusIndicator === false) {
|
|
this.focusIndicator?.overrideFocusVisible(this.hoverDevice === "keyboard");
|
|
}
|
|
switch (action?.name) {
|
|
case "redo":
|
|
return this.chart.ctx.eventsHub.emit("series:redo", null);
|
|
case "undo":
|
|
return this.chart.ctx.eventsHub.emit("series:undo", null);
|
|
case "zoomin":
|
|
return this.chart.ctx.eventsHub.emit("series:keynav-zoom", { delta: 1, widgetEvent });
|
|
case "zoomout":
|
|
return this.chart.ctx.eventsHub.emit("series:keynav-zoom", { delta: -1, widgetEvent });
|
|
case "arrowup":
|
|
return this.onArrow(-1, 0, widgetEvent);
|
|
case "arrowdown":
|
|
return this.onArrow(1, 0, widgetEvent);
|
|
case "arrowleft":
|
|
return this.onArrow(0, -1, widgetEvent);
|
|
case "arrowright":
|
|
return this.onArrow(0, 1, widgetEvent);
|
|
case "submit":
|
|
return this.onSubmit(widgetEvent);
|
|
}
|
|
}
|
|
onArrow(seriesIndexDelta, datumIndexDelta, event) {
|
|
if (!this.isState(68 /* Focusable */))
|
|
return;
|
|
this.hoverDevice = "keyboard";
|
|
this.focusIndicator?.overrideFocusVisible(true);
|
|
this.focus.seriesIndex += seriesIndexDelta;
|
|
this.focus.datumIndex += datumIndexDelta;
|
|
this.handleFocus(seriesIndexDelta, datumIndexDelta);
|
|
event.sourceEvent.preventDefault();
|
|
this.chart.ctx.eventsHub.emit("series:focus-change", null);
|
|
}
|
|
onSubmit(event) {
|
|
if (!this.isState(68 /* Focusable */))
|
|
return;
|
|
const { series, datum } = this.focus;
|
|
const sourceEvent = event.sourceEvent;
|
|
if (series != null && datum != null) {
|
|
series.fireNodeClickEvent(sourceEvent, datum);
|
|
} else {
|
|
this.chart.fireEvent({
|
|
type: "click",
|
|
event: sourceEvent
|
|
});
|
|
}
|
|
sourceEvent.preventDefault();
|
|
}
|
|
checkSeriesNodeClick(event) {
|
|
var _a;
|
|
const pickedNodes = this.pickNodes({ x: event.currentX, y: event.currentY }, "event");
|
|
const updated = this.pickManager.onPickedNodesTooltip(pickedNodes);
|
|
if (pickedNodes === void 0 || updated.active === void 0)
|
|
return false;
|
|
const { series, datum } = updated.active;
|
|
const distance2 = updated.paginationState == null ? pickedNodes.distance : 0;
|
|
if (event.type === "click") {
|
|
const defaultBehavior = series.fireNodeClickEvent(event.sourceEvent, datum);
|
|
if (defaultBehavior) {
|
|
const next = this.pickManager.nextCandidate();
|
|
if (next.active !== void 0) {
|
|
const { canvasX, canvasY } = this.toCanvasCoordinates(event);
|
|
(_a = this.highlight).pendingHoverEvent ?? (_a.pendingHoverEvent = this.highlight.appliedHoverEvent);
|
|
this.handleHoverHighlight(false);
|
|
this.showTooltip(next.active, canvasX, canvasY, next.paginationState);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
if (event.type === "dblclick") {
|
|
event.preventZoomDblClick = distance2 === 0;
|
|
series.fireNodeDoubleClickEvent(event.sourceEvent, datum);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
handleFocus(seriesIndexDelta, datumIndexDelta) {
|
|
const overlayFocus = this.chart.overlays.getFocusInfo(this.chart.ctx.localeManager);
|
|
if (overlayFocus == null) {
|
|
if (this.handleSeriesFocus(seriesIndexDelta, datumIndexDelta) === 0 /* SUCCESS */) {
|
|
this.announceMode = "when-changed";
|
|
} else {
|
|
this.announceMode = "always";
|
|
}
|
|
} else {
|
|
this.focusIndicator?.update(overlayFocus.rect, this.seriesRect, false);
|
|
this.swapChain.update(overlayFocus.text);
|
|
this.announceMode = "always";
|
|
}
|
|
}
|
|
handleSeriesFocus(otherIndexDelta, datumIndexDelta) {
|
|
if (this.chart.chartType === "standalone") {
|
|
return this.handleSoloSeriesFocus(otherIndexDelta, datumIndexDelta);
|
|
}
|
|
const { focus } = this;
|
|
const visibleSeries = focus.sortedSeries.filter((s) => s.visible && s.focusable);
|
|
if (visibleSeries.length === 0)
|
|
return 1 /* SERIES_NOT_FOUND */;
|
|
const oldDatumIndex = focus.datumIndex - datumIndexDelta;
|
|
const oldOtherIndex = focus.seriesIndex - otherIndexDelta;
|
|
focus.seriesIndex = clamp(0, focus.seriesIndex, visibleSeries.length - 1);
|
|
focus.series = visibleSeries[focus.seriesIndex];
|
|
const datumIndex = this.focus.datumIndex;
|
|
const otherIndex = this.focus.seriesIndex;
|
|
return this.updatePickedFocus(
|
|
datumIndex,
|
|
datumIndexDelta,
|
|
oldDatumIndex,
|
|
otherIndex,
|
|
otherIndexDelta,
|
|
oldOtherIndex
|
|
);
|
|
}
|
|
handleSoloSeriesFocus(otherIndexDelta, datumIndexDelta) {
|
|
this.focus.series = this.focus.sortedSeries[0];
|
|
const datumIndex = this.focus.datumIndex;
|
|
const otherIndex = this.focus.seriesIndex;
|
|
const oldDatumIndex = this.focus.datumIndex - datumIndexDelta;
|
|
const oldOtherIndex = this.focus.seriesIndex - otherIndexDelta;
|
|
return this.updatePickedFocus(
|
|
datumIndex,
|
|
datumIndexDelta,
|
|
oldDatumIndex,
|
|
otherIndex,
|
|
otherIndexDelta,
|
|
oldOtherIndex
|
|
);
|
|
}
|
|
pickFocus(series, opts) {
|
|
const pick2 = series.pickFocus(opts);
|
|
if (this.hoverDevice === "keyboard") {
|
|
this.pickManager.onPickedNodesFocus(pick2);
|
|
}
|
|
return pick2;
|
|
}
|
|
updatePickedFocus(datumIndex, datumIndexDelta, oldDatumIndex, otherIndex, otherIndexDelta, oldOtherIndex) {
|
|
const { focus, hoverRect, seriesRect } = this;
|
|
if (focus.series == null || hoverRect == null)
|
|
return 1 /* SERIES_NOT_FOUND */;
|
|
const focusInputs = { datumIndex, datumIndexDelta, otherIndex, otherIndexDelta, seriesRect };
|
|
const pick2 = this.pickFocus(focus.series, focusInputs);
|
|
if (!pick2)
|
|
return 2 /* DATUM_NOT_FOUND */;
|
|
const { datum } = pick2;
|
|
focus.datum = datum;
|
|
focus.datumIndex = pick2.datumIndex;
|
|
if (pick2.otherIndex != null) {
|
|
focus.seriesIndex = pick2.otherIndex;
|
|
}
|
|
if (this.focusIndicator?.isFocusVisible()) {
|
|
this.chart.ctx.animationManager.reset();
|
|
const focusBBox = getPickedFocusBBox(pick2);
|
|
const { x, y } = focusBBox.computeCenter();
|
|
if (!hoverRect.containsPoint(x, y)) {
|
|
const panSuccess = this.chart.ctx.zoomManager.panToBBox(hoverRect, focusBBox);
|
|
if (panSuccess) {
|
|
return 3 /* PAN_REQUIRED */;
|
|
}
|
|
}
|
|
const { x1, x2, y1, y2 } = vector4_exports.from(focusBBox);
|
|
const nw = hoverRect.containsPoint(x1, y1);
|
|
const ne = hoverRect.containsPoint(x2, y1);
|
|
const sw = hoverRect.containsPoint(x1, y2);
|
|
const se = hoverRect.containsPoint(x2, y2);
|
|
if (!(nw || ne || sw || se)) {
|
|
const hoverBounds = vector4_exports.from(hoverRect);
|
|
pick2.movedBounds = focusBBox.clone();
|
|
if (x1 < hoverBounds.x1 && x2 < hoverBounds.x1) {
|
|
pick2.movedBounds.x = hoverBounds.x1 - 2;
|
|
pick2.movedBounds.width = 4;
|
|
} else if (x1 > hoverBounds.x2 && x2 > hoverBounds.x2) {
|
|
pick2.movedBounds.x = hoverBounds.x2 - 2;
|
|
pick2.movedBounds.width = 4;
|
|
}
|
|
if (y1 < hoverBounds.y1 && y2 < hoverBounds.y1) {
|
|
pick2.movedBounds.y = hoverBounds.y1 - 2;
|
|
pick2.movedBounds.height = 4;
|
|
} else if (y1 > hoverBounds.y2 && y2 > hoverBounds.y2) {
|
|
pick2.movedBounds.y = hoverBounds.y2 - 2;
|
|
pick2.movedBounds.height = 4;
|
|
}
|
|
}
|
|
}
|
|
this.focusIndicator?.update(pick2.movedBounds ?? pick2.bounds, this.seriesRect, pick2.clipFocusBox);
|
|
const tooltipContent = this.getTooltipContent(focus.series, datum.datumIndex, datum, "aria-label");
|
|
const keyboardEvent = makeKeyboardPointerEvent(focus.series, hoverRect, pick2);
|
|
if (keyboardEvent != null && this.hoverDevice === "keyboard") {
|
|
this.clearCachedEvents();
|
|
const meta = TooltipManager.makeTooltipMeta(keyboardEvent, focus.series, datum, pick2.movedBounds);
|
|
this.chart.ctx.highlightManager.updateHighlight(this.id, datum);
|
|
if (this.isTooltipEnabled(focus.series)) {
|
|
this.chart.ctx.tooltipManager.updateTooltip(this.id, meta, tooltipContent);
|
|
}
|
|
}
|
|
this.maybeAnnouncePickedFocus(
|
|
datumIndexDelta,
|
|
oldDatumIndex,
|
|
otherIndexDelta,
|
|
oldOtherIndex,
|
|
pick2,
|
|
tooltipContent
|
|
);
|
|
return 0 /* SUCCESS */;
|
|
}
|
|
maybeAnnouncePickedFocus(datumIndexDelta, oldDatumIndex, otherIndexDelta, oldOtherIndex, pick2, tooltipContent) {
|
|
const { focus } = this;
|
|
let mode;
|
|
if (this.announceMode === "when-changed") {
|
|
const shouldAnnouncePick = datumIndexDelta === 0 && otherIndexDelta === 0 || oldDatumIndex !== pick2.datumIndex || oldOtherIndex !== (pick2.otherIndex ?? focus.seriesIndex);
|
|
if (shouldAnnouncePick) {
|
|
mode = "always";
|
|
} else {
|
|
mode = "never";
|
|
}
|
|
} else {
|
|
mode = this.announceMode;
|
|
}
|
|
if (mode === "always") {
|
|
this.swapChain.update(this.getDatumAriaText(pick2.datum, tooltipContent));
|
|
}
|
|
}
|
|
getDatumAriaText(datum, tooltipContent) {
|
|
const description = tooltipContent == null ? "" : tooltipContentAriaLabel(tooltipContent);
|
|
return this.chart.ctx.localeManager.t("ariaAnnounceHoverDatum", {
|
|
datum: datum.series.getDatumAriaText?.(datum, description) ?? description
|
|
});
|
|
}
|
|
clearHighlight(delayed = false) {
|
|
this.highlight.pendingHoverEvent = void 0;
|
|
this.highlight.appliedHoverEvent = void 0;
|
|
this.chart.ctx.highlightManager.updateHighlight(this.id, void 0, delayed);
|
|
}
|
|
clearTooltip(delayed = false) {
|
|
this.chart.ctx.tooltipManager.removeTooltip(this.id, void 0, delayed);
|
|
this.tooltip.lastHover = void 0;
|
|
}
|
|
clearAll(delayed = false) {
|
|
this.pickManager.onClearUI();
|
|
this.clearHighlight(delayed);
|
|
this.clearTooltip(delayed);
|
|
this.focusIndicator?.clear();
|
|
}
|
|
clearCachedEvents() {
|
|
this.tooltip.lastHover = void 0;
|
|
this.highlight.appliedHoverEvent = void 0;
|
|
this.highlight.pendingHoverEvent = void 0;
|
|
this.highlight.stashedHoverEvent = void 0;
|
|
}
|
|
handleHoverFromState() {
|
|
const { active, paginationState } = this.pickManager.onPickedNodesAPIDebounced();
|
|
if (active === void 0)
|
|
return;
|
|
this.chart.ctx.highlightManager.updateHighlight(this.id, active.datum);
|
|
const refPoint = getDatumRefPoint(active.series, active.datum, void 0);
|
|
if (this.chart.tooltip.enabled) {
|
|
if (refPoint) {
|
|
const { canvasX, canvasY } = refPoint;
|
|
this.showTooltip(active, canvasX, canvasY, paginationState);
|
|
}
|
|
}
|
|
}
|
|
handleHoverHighlight(redisplay) {
|
|
this.highlight.appliedHoverEvent = this.highlight.pendingHoverEvent;
|
|
this.highlight.pendingHoverEvent = void 0;
|
|
const event = this.highlight.appliedHoverEvent;
|
|
if (!event || !this.isState(82 /* Clickable */))
|
|
return;
|
|
const { canvasX, canvasY } = this.toCanvasCoordinates(event);
|
|
if (redisplay ? this.chart.ctx.animationManager.isActive() : !this.hoverRect?.containsPoint(canvasX, canvasY)) {
|
|
this.clearHighlight();
|
|
return;
|
|
}
|
|
const { range: range3 } = this.chart.highlight;
|
|
const intent = range3 === "tooltip" ? "highlight-tooltip" : "highlight";
|
|
const pick2 = this.pickNodes({ x: event.currentX, y: event.currentY }, intent);
|
|
const active = this.pickManager.onPickedNodesHighlight(pick2);
|
|
if (active === void 0) {
|
|
this.chart.ctx.highlightManager.updateHighlight(this.id, void 0, true);
|
|
} else {
|
|
this.chart.ctx.highlightManager.updateHighlight(this.id, active.datum, false);
|
|
}
|
|
}
|
|
handleHoverTooltip(event, redisplay) {
|
|
if (!this.isState(82 /* Clickable */))
|
|
return;
|
|
const { canvasX, canvasY } = this.toCanvasCoordinates(event);
|
|
const targetElement = event.sourceEvent.target;
|
|
if (redisplay ? this.chart.ctx.animationManager.isActive() : !this.hoverRect?.containsPoint(canvasX, canvasY)) {
|
|
if (this.hoverDevice == "pointer")
|
|
this.clearTooltip();
|
|
return;
|
|
}
|
|
if (targetElement && this.chart.tooltip.interactive && this.chart.ctx.domManager.isManagedChildDOMElement(targetElement, "canvas-overlay", DEFAULT_TOOLTIP_CLASS)) {
|
|
return;
|
|
}
|
|
const pick2 = this.pickNodes({ x: event.currentX, y: event.currentY }, "tooltip");
|
|
const { active, paginationState } = this.pickManager.onPickedNodesTooltip(pick2);
|
|
if (active === void 0) {
|
|
if (this.hoverDevice == "pointer") {
|
|
this.clearTooltip(true);
|
|
}
|
|
} else {
|
|
this.showTooltip(active, canvasX, canvasY, paginationState);
|
|
}
|
|
}
|
|
showTooltip({ series, datum, datumIndex }, canvasX, canvasY, pagination) {
|
|
const tooltipContent = this.getTooltipContent(series, datumIndex, datum, "tooltip");
|
|
const shouldUpdateTooltip = tooltipContent != null;
|
|
if (shouldUpdateTooltip) {
|
|
const meta = TooltipManager.makeTooltipMeta(
|
|
{ type: "pointermove", canvasX, canvasY },
|
|
series,
|
|
datum,
|
|
void 0
|
|
);
|
|
this.chart.ctx.tooltipManager.updateTooltip(this.id, meta, tooltipContent, pagination);
|
|
} else {
|
|
this.chart.ctx.tooltipManager.removeTooltip(this.id, void 0, true);
|
|
}
|
|
}
|
|
maybeEnterInteractiveTooltip(event) {
|
|
return this.chart.tooltip.maybeEnterInteractiveTooltip(event, () => {
|
|
this.tooltip.lastHover = void 0;
|
|
this.chart.ctx.tooltipManager.removeTooltip(this.id);
|
|
this.chart.ctx.highlightManager.updateHighlight(this.id, void 0, true);
|
|
});
|
|
}
|
|
changeHighlightDatum(event) {
|
|
const lastSeries = event.previousHighlight?.series;
|
|
const newSeries = event.currentHighlight?.series;
|
|
if (lastSeries?.properties.cursor && event.previousHighlight?.datum) {
|
|
this.chart.ctx.domManager.updateCursor(lastSeries.id);
|
|
}
|
|
if (newSeries?.properties.cursor && newSeries.properties.cursor !== "default" && event.currentHighlight?.datum) {
|
|
this.chart.ctx.domManager.updateCursor(newSeries.id, newSeries.properties.cursor);
|
|
}
|
|
if (this.hoverDevice === "setState" || newSeries == null || lastSeries == null) {
|
|
this.update(7 /* SERIES_UPDATE */, { clearCallbackCache: true });
|
|
} else {
|
|
this.update(7 /* SERIES_UPDATE */, {
|
|
seriesToUpdate: new Set([lastSeries, newSeries].filter(Boolean)),
|
|
clearCallbackCache: true
|
|
});
|
|
}
|
|
}
|
|
pickNodes(point, intent, exactMatchOnly) {
|
|
const reverseSeries = [...this.series].reverse();
|
|
const clickIntent = intent === "event" || intent === "context-menu";
|
|
const tooltipIntent = intent === "tooltip" || intent === "highlight-tooltip";
|
|
const getIntentRange = (series) => {
|
|
if (clickIntent)
|
|
return series.properties.nodeClickRange;
|
|
if (tooltipIntent)
|
|
return series.properties.tooltip.range;
|
|
return void 0;
|
|
};
|
|
const { x, y } = point;
|
|
const seriesContainingPoint = /* @__PURE__ */ new Set();
|
|
for (const series of reverseSeries) {
|
|
if (series.visible && series.contentGroup.visible && getIntentRange(series) === "area") {
|
|
if (series.isPointInArea?.(x, y)) {
|
|
seriesContainingPoint.add(series);
|
|
}
|
|
}
|
|
}
|
|
const hasSeriesContainingPoint = seriesContainingPoint.size > 0;
|
|
let result;
|
|
for (const series of reverseSeries) {
|
|
if (!series.visible || !series.contentGroup.visible)
|
|
continue;
|
|
if (hasSeriesContainingPoint) {
|
|
const hasAreaHit = getIntentRange(series) === "area" && seriesContainingPoint.has(series);
|
|
if (!hasAreaHit)
|
|
continue;
|
|
}
|
|
const pick2 = series.pickNodes(point, intent, exactMatchOnly);
|
|
if (pick2 == null || pick2.datums.length === 0)
|
|
continue;
|
|
const { datums, distance: distance2 } = pick2;
|
|
if (pick2.datums.length === 0)
|
|
continue;
|
|
if (distance2 === 0) {
|
|
if (result?.distance !== 0) {
|
|
result = { matches: [], distance: 0 };
|
|
}
|
|
for (const datum of datums) {
|
|
const { datumIndex } = datum;
|
|
result.matches.push({ series, datum, datumIndex });
|
|
}
|
|
} else if (result == null || result.distance > distance2) {
|
|
const [datum] = datums;
|
|
const { datumIndex } = datum;
|
|
result = { matches: [{ series, datum, datumIndex }], distance: distance2 };
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
isTooltipEnabled(series) {
|
|
return series.tooltipEnabled ?? this.chart.tooltip.enabled;
|
|
}
|
|
getTooltipContent(series, datumIndex, datum, purpose) {
|
|
let result;
|
|
if (purpose === "aria-label" || this.isTooltipEnabled(series)) {
|
|
const { cachedTooltipContent } = this;
|
|
if (cachedTooltipContent?.series === series && cachedTooltipContent.datumIndex === datumIndex) {
|
|
result = cachedTooltipContent.content;
|
|
} else {
|
|
const content = this.chart.getTooltipContent(series, datumIndex, datum, purpose);
|
|
this.cachedTooltipContent = { series, datumIndex, content };
|
|
result = content;
|
|
}
|
|
purpose;
|
|
result;
|
|
} else {
|
|
this.cachedTooltipContent = void 0;
|
|
purpose;
|
|
result;
|
|
}
|
|
return result;
|
|
}
|
|
onActiveLoadMemento(event) {
|
|
switch (event.activeItem?.type) {
|
|
case void 0:
|
|
case "legend":
|
|
return this.onActiveClear();
|
|
case "series-node":
|
|
return this.onActiveDatum(event.activeItem, event);
|
|
default:
|
|
return event.activeItem?.type;
|
|
}
|
|
}
|
|
onActiveUpdate(activeItem) {
|
|
if (activeItem?.type === "legend") {
|
|
if (this.hoverDevice === "setState") {
|
|
this.clearHighlight();
|
|
this.clearTooltip();
|
|
}
|
|
this.activeState.lastActive = "legend";
|
|
}
|
|
}
|
|
onActiveClear() {
|
|
this.pickManager.onClearAPI();
|
|
this.hoverDevice = "setState";
|
|
this.activeState.lastActive = void 0;
|
|
this.clearHighlight();
|
|
this.clearTooltip();
|
|
}
|
|
refreshSetState() {
|
|
if (this.activeState.lastActive === void 0) {
|
|
this.clearAll();
|
|
} else if (this.activeState.lastActive !== "legend") {
|
|
const { seriesId, itemId } = this.activeState.lastActive;
|
|
const desiredPickedNodes = this.findPickedNodes(seriesId, itemId);
|
|
if (desiredPickedNodes) {
|
|
this.pickManager.onPickedNodesAPI(desiredPickedNodes);
|
|
this.hoverScheduler.schedule();
|
|
}
|
|
}
|
|
}
|
|
onActiveDatum(activeItem, event) {
|
|
const { seriesId, itemId } = activeItem;
|
|
const desiredPickedNodes = this.findPickedNodes(seriesId, itemId);
|
|
if (desiredPickedNodes === void 0) {
|
|
event.reject();
|
|
this.onActiveClear();
|
|
} else {
|
|
const picked = this.pickManager.onPickedNodesAPI(desiredPickedNodes);
|
|
event.setDatum(picked?.datum);
|
|
this.hoverDevice = "setState";
|
|
this.activeState.lastActive = { seriesId, itemId };
|
|
if (event.initialState) {
|
|
this.chart.ctx.scene.applyPendingResize();
|
|
this.handleHoverFromState();
|
|
} else {
|
|
this.clearCachedEvents();
|
|
this.hoverScheduler.schedule();
|
|
}
|
|
}
|
|
}
|
|
findPickedNodes(desiredSeriesId, desiredItemId) {
|
|
const desiredSeries = this.series.find((s) => s.id === desiredSeriesId);
|
|
if (desiredSeries == void 0) {
|
|
logger_exports.warn(`Cannot find seriesId: "${desiredSeriesId}"`);
|
|
return void 0;
|
|
}
|
|
const desiredDatum = desiredSeries.findNodeDatum(desiredItemId);
|
|
if (desiredDatum == void 0) {
|
|
logger_exports.warn(`Cannot find itemId: ${JSON.stringify(desiredItemId)}`);
|
|
return void 0;
|
|
}
|
|
const restoredPick = {
|
|
datum: desiredDatum,
|
|
datumIndex: desiredDatum.datumIndex,
|
|
series: desiredSeries
|
|
};
|
|
return { matches: [restoredPick], distance: 0 };
|
|
}
|
|
};
|
|
SeriesAreaManager.className = "SeriesAreaManager";
|
|
|
|
// packages/ag-charts-community/src/chart/series/seriesLayerManager.ts
|
|
var SERIES_THRESHOLD_FOR_AGGRESSIVE_LAYER_REDUCTION = 30;
|
|
var SeriesLayerManager = class {
|
|
constructor(seriesRoot) {
|
|
this.seriesRoot = seriesRoot;
|
|
this.groups = /* @__PURE__ */ new Map();
|
|
this.series = /* @__PURE__ */ new Map();
|
|
this.expectedSeriesCount = 1;
|
|
this.mode = "normal";
|
|
}
|
|
setSeriesCount(count) {
|
|
this.expectedSeriesCount = count;
|
|
}
|
|
getGroupIndex(seriesConfig) {
|
|
const { internalId, seriesGrouping } = seriesConfig;
|
|
return seriesGrouping?.groupIndex ?? internalId;
|
|
}
|
|
getGroupType(seriesConfig, bringToFront) {
|
|
return bringToFront ? "top" : seriesConfig.type;
|
|
}
|
|
requestGroup(seriesConfig) {
|
|
const { internalId, contentGroup: seriesContentGroup } = seriesConfig;
|
|
const bringToFront = seriesConfig.bringToFront();
|
|
const type = this.getGroupType(seriesConfig, bringToFront);
|
|
const groupIndex = this.getGroupIndex(seriesConfig);
|
|
const seriesInfo = this.series.get(internalId);
|
|
if (seriesInfo != null) {
|
|
throw new Error(`AG Charts - series already has an allocated layer: ${JSON.stringify(seriesInfo)}`);
|
|
}
|
|
if (this.series.size === 0) {
|
|
this.mode = this.expectedSeriesCount >= SERIES_THRESHOLD_FOR_AGGRESSIVE_LAYER_REDUCTION ? "aggressive-grouping" : "normal";
|
|
}
|
|
let group = this.groups.get(type);
|
|
if (group == null) {
|
|
group = /* @__PURE__ */ new Map();
|
|
this.groups.set(type, group);
|
|
}
|
|
const lookupIndex = this.lookupIdx(groupIndex);
|
|
let groupInfo = group.get(lookupIndex);
|
|
if (groupInfo == null) {
|
|
groupInfo = {
|
|
type,
|
|
id: lookupIndex,
|
|
seriesIds: [],
|
|
group: this.seriesRoot.appendChild(
|
|
new Group({
|
|
name: `${seriesConfig.contentGroup.name ?? type}-managed-layer`,
|
|
zIndex: seriesConfig.contentGroup.zIndex,
|
|
// Set in updateLayerCompositing
|
|
renderToOffscreenCanvas: false
|
|
})
|
|
)
|
|
};
|
|
group.set(lookupIndex, groupInfo);
|
|
}
|
|
this.series.set(internalId, { layerState: groupInfo, seriesConfig, bringToFront });
|
|
groupInfo.seriesIds.push(internalId);
|
|
groupInfo.group.appendChild(seriesContentGroup);
|
|
return groupInfo.group;
|
|
}
|
|
changeGroup(seriesConfig) {
|
|
const { internalId, contentGroup } = seriesConfig;
|
|
const bringToFront = seriesConfig.bringToFront();
|
|
const type = this.getGroupType(seriesConfig, bringToFront);
|
|
const oldGroup = this.series.get(internalId);
|
|
const oldType = oldGroup ? this.getGroupType(oldGroup.seriesConfig, oldGroup.bringToFront) : void 0;
|
|
const groupIndex = this.getGroupIndex(seriesConfig);
|
|
const lookupIndex = this.lookupIdx(groupIndex);
|
|
const groupInfo = this.groups.get(type)?.get(lookupIndex);
|
|
if (oldType === type && groupInfo?.seriesIds.includes(internalId) === true) {
|
|
return;
|
|
}
|
|
if (this.series.has(internalId)) {
|
|
this._releaseGroup({ internalId, contentGroup, type: oldType });
|
|
}
|
|
return this.requestGroup(seriesConfig);
|
|
}
|
|
releaseGroup(seriesConfig) {
|
|
const { internalId, contentGroup } = seriesConfig;
|
|
const type = this.getGroupType(seriesConfig, seriesConfig.bringToFront());
|
|
this._releaseGroup({ internalId, contentGroup, type });
|
|
}
|
|
_releaseGroup(seriesConfig) {
|
|
const { internalId, contentGroup, type } = seriesConfig;
|
|
if (!this.series.has(internalId)) {
|
|
throw new Error(`AG Charts - series doesn't have an allocated layer: ${internalId}`);
|
|
}
|
|
const groupInfo = this.series.get(internalId)?.layerState;
|
|
if (groupInfo) {
|
|
groupInfo.seriesIds = groupInfo.seriesIds.filter((v) => v !== internalId);
|
|
contentGroup.remove();
|
|
}
|
|
if (groupInfo?.seriesIds.length === 0) {
|
|
groupInfo.group.remove();
|
|
this.groups.get(groupInfo.type)?.delete(groupInfo.id);
|
|
this.groups.get(type)?.delete(internalId);
|
|
} else if (groupInfo != null && groupInfo.seriesIds.length > 0) {
|
|
groupInfo.group.zIndex = this.getLowestSeriesZIndex(groupInfo.seriesIds);
|
|
}
|
|
this.series.delete(internalId);
|
|
}
|
|
updateLayerCompositing() {
|
|
for (const groups of this.groups.values()) {
|
|
for (const groupInfo of groups.values()) {
|
|
const { group, seriesIds } = groupInfo;
|
|
let renderToOffscreenCanvas;
|
|
if (seriesIds.length === 0) {
|
|
renderToOffscreenCanvas = false;
|
|
} else if (seriesIds.length > 1) {
|
|
renderToOffscreenCanvas = true;
|
|
} else {
|
|
const series = this.series.get(seriesIds[0]);
|
|
renderToOffscreenCanvas = series?.seriesConfig.renderToOffscreenCanvas() === true;
|
|
}
|
|
group.renderToOffscreenCanvas = renderToOffscreenCanvas;
|
|
group.zIndex = this.getLowestSeriesZIndex(seriesIds);
|
|
}
|
|
}
|
|
}
|
|
lookupIdx(groupIndex) {
|
|
if (this.mode === "normal") {
|
|
return groupIndex;
|
|
}
|
|
if (typeof groupIndex === "string") {
|
|
groupIndex = Number(groupIndex.split("-").at(-1));
|
|
if (!Number.isFinite(groupIndex)) {
|
|
return 0;
|
|
}
|
|
}
|
|
return Math.floor(
|
|
clamp(0, groupIndex / this.expectedSeriesCount, 1) * SERIES_THRESHOLD_FOR_AGGRESSIVE_LAYER_REDUCTION
|
|
);
|
|
}
|
|
destroy() {
|
|
for (const groups of this.groups.values()) {
|
|
for (const groupInfo of groups.values()) {
|
|
groupInfo.group.remove();
|
|
}
|
|
}
|
|
this.groups.clear();
|
|
this.series.clear();
|
|
}
|
|
getLowestSeriesZIndex(seriesIds) {
|
|
let lowestSeriesZIndex = void 0;
|
|
for (const seriesId of seriesIds) {
|
|
const series = this.series.get(seriesId);
|
|
const zIndex = series?.seriesConfig.contentGroup.zIndex ?? 1 /* ANY_CONTENT */;
|
|
if (lowestSeriesZIndex == null || zIndex == null) {
|
|
lowestSeriesZIndex = zIndex;
|
|
continue;
|
|
}
|
|
lowestSeriesZIndex = compareZIndex(lowestSeriesZIndex, zIndex) <= 0 ? lowestSeriesZIndex : zIndex;
|
|
}
|
|
return lowestSeriesZIndex ?? 1 /* ANY_CONTENT */;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/touch.ts
|
|
var Touch = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.dragAction = "drag";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Touch.prototype, "dragAction", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/update/dataWindowProcessor.ts
|
|
var DataWindowProcessor = class {
|
|
constructor(chart, eventsHub, dataService, updateService, zoomManager, animationManager) {
|
|
this.chart = chart;
|
|
this.eventsHub = eventsHub;
|
|
this.dataService = dataService;
|
|
this.updateService = updateService;
|
|
this.zoomManager = zoomManager;
|
|
this.animationManager = animationManager;
|
|
this.dirtyZoom = false;
|
|
this.dirtyDataSource = false;
|
|
this.lastAxisZooms = /* @__PURE__ */ new Map();
|
|
this.cleanup = new CleanupRegistry();
|
|
this.cleanup.register(
|
|
this.eventsHub.on("data:source-change", () => this.onDataSourceChange()),
|
|
this.eventsHub.on("data:load", () => this.onDataLoad()),
|
|
this.eventsHub.on("data:error", () => this.onDataError()),
|
|
this.updateService.addListener("update-complete", (e) => this.onUpdateComplete(e)),
|
|
this.eventsHub.on("zoom:change-complete", () => this.onZoomChange())
|
|
);
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
}
|
|
onDataLoad() {
|
|
this.animationManager.skip();
|
|
this.updateService.update(1 /* UPDATE_DATA */);
|
|
}
|
|
onDataError() {
|
|
this.updateService.update(5 /* PERFORM_LAYOUT */);
|
|
}
|
|
onDataSourceChange() {
|
|
this.dirtyDataSource = true;
|
|
}
|
|
onUpdateComplete(event) {
|
|
if (!event.apiUpdate && !this.dirtyZoom && !this.dirtyDataSource)
|
|
return;
|
|
if (event.wasShortcut)
|
|
return;
|
|
this.updateWindow(event);
|
|
}
|
|
onZoomChange() {
|
|
this.dirtyZoom = true;
|
|
}
|
|
updateWindow(event) {
|
|
if (!this.dataService.isLazy())
|
|
return;
|
|
const axis = this.getValidAxis();
|
|
let window;
|
|
let shouldRefresh = true;
|
|
if (axis) {
|
|
const zoom = this.zoomManager.getAxisZoom(axis.id);
|
|
window = this.getAxisWindow(axis, zoom);
|
|
shouldRefresh = this.shouldRefresh(event, axis, zoom);
|
|
}
|
|
this.dirtyZoom = false;
|
|
this.dirtyDataSource = false;
|
|
if (!shouldRefresh)
|
|
return;
|
|
this.dataService.load({ windowStart: window?.min, windowEnd: window?.max });
|
|
}
|
|
getValidAxis() {
|
|
return this.chart.axes.find((axis) => axis.type === "time");
|
|
}
|
|
shouldRefresh(event, axis, zoom) {
|
|
if (event.apiUpdate)
|
|
return true;
|
|
if (this.dirtyDataSource)
|
|
return true;
|
|
if (!this.dirtyZoom)
|
|
return false;
|
|
const lastZoom = this.lastAxisZooms.get(axis.id);
|
|
if (lastZoom && zoom.min === lastZoom.min && zoom.max === lastZoom.max) {
|
|
return false;
|
|
}
|
|
this.lastAxisZooms.set(axis.id, zoom);
|
|
return true;
|
|
}
|
|
getAxisWindow(axis, zoom) {
|
|
const { domain } = axis.scale;
|
|
if (!zoom || domain.length === 0 || Number.isNaN(Number(domain[0])))
|
|
return;
|
|
const diff9 = Number(domain[1]) - Number(domain[0]);
|
|
const min = new Date(Number(domain[0]) + diff9 * zoom.min);
|
|
const max = new Date(Number(domain[0]) + diff9 * zoom.max);
|
|
return { min, max };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/browser.ts
|
|
var isSafariRegexp = /^((?!chrome|android).)*safari/i;
|
|
var safariVersionRegexp = /Version\/(\d+(\.\d+)?)/;
|
|
var isChromeRegexp = /Chrome/;
|
|
var chromeVersionRegexp = /Chrome\/(\d+)/;
|
|
var isEdge = /Edg/;
|
|
var isOpera = /OPR/;
|
|
function isUnsupportedBrowser() {
|
|
const { userAgent } = getWindow("navigator");
|
|
if (isSafariRegexp.test(userAgent)) {
|
|
const versionExec = safariVersionRegexp.exec(userAgent);
|
|
if (versionExec == null)
|
|
return false;
|
|
const version = Number.parseFloat(versionExec[1]);
|
|
const supported = Math.floor(version) > 16;
|
|
if (!supported) {
|
|
logger_exports.warnOnce(`Unsupported Safari version: ${version}; ${userAgent}`);
|
|
}
|
|
return !supported;
|
|
} else if (isChromeRegexp.test(userAgent) && !isEdge.test(userAgent) && !isOpera.test(userAgent)) {
|
|
const versionExec = chromeVersionRegexp.exec(userAgent);
|
|
if (versionExec == null)
|
|
return false;
|
|
const version = Number.parseInt(versionExec[1], 10);
|
|
const supported = version > 126;
|
|
if (!supported) {
|
|
logger_exports.warnOnce(`Unsupported Chrome version: ${version}; ${userAgent}`);
|
|
}
|
|
return !supported;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/update/overlaysProcessor.ts
|
|
var visibleIgnoredSeries = /* @__PURE__ */ new Set(["map-shape-background", "map-line-background"]);
|
|
var OverlaysProcessor = class {
|
|
constructor(chartLike, overlays, eventsHub, dataService, localeManager, animationManager, domManager) {
|
|
this.chartLike = chartLike;
|
|
this.overlays = overlays;
|
|
this.eventsHub = eventsHub;
|
|
this.dataService = dataService;
|
|
this.localeManager = localeManager;
|
|
this.animationManager = animationManager;
|
|
this.domManager = domManager;
|
|
this.cleanup = new CleanupRegistry();
|
|
this.overlayElem = this.domManager.addChild("canvas-overlay", "overlay");
|
|
this.overlayElem.role = "status";
|
|
this.overlayElem.ariaAtomic = "false";
|
|
this.overlayElem.ariaLive = "polite";
|
|
this.overlayElem.classList.toggle(DEFAULT_OVERLAY_CLASS);
|
|
this.cleanup.register(this.eventsHub.on("layout:complete", (e) => this.onLayoutComplete(e)));
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
this.domManager.removeChild("canvas-overlay", "overlay");
|
|
}
|
|
onLayoutComplete({ series: { rect: rect2 } }) {
|
|
const isLoading = this.dataService.isLoading();
|
|
const hasData = this.chartLike.series.some((s) => s.hasData);
|
|
const anySeriesVisible = this.chartLike.series.some((s) => s.visible && !visibleIgnoredSeries.has(s.type));
|
|
if (this.overlays.darkTheme) {
|
|
this.overlayElem.classList.add(DEFAULT_OVERLAY_DARK_CLASS);
|
|
} else {
|
|
this.overlayElem.classList.remove(DEFAULT_OVERLAY_DARK_CLASS);
|
|
}
|
|
this.overlayElem.style.left = `${rect2.x}px`;
|
|
this.overlayElem.style.top = `${rect2.y}px`;
|
|
this.overlayElem.style.width = `${rect2.width}px`;
|
|
this.overlayElem.style.height = `${rect2.height}px`;
|
|
const loadingShown = isLoading;
|
|
const noDataShown = !isLoading && !hasData;
|
|
const noVisibleSeriesShown = hasData && !anySeriesVisible;
|
|
const unsupportedBrowser = this.overlays.unsupportedBrowser.enabled && isUnsupportedBrowser();
|
|
if (loadingShown) {
|
|
this.showOverlay(this.overlays.loading, rect2);
|
|
} else {
|
|
this.hideOverlay(this.overlays.loading);
|
|
}
|
|
if (noDataShown) {
|
|
this.showOverlay(this.overlays.noData, rect2);
|
|
} else {
|
|
this.hideOverlay(this.overlays.noData);
|
|
}
|
|
if (noVisibleSeriesShown) {
|
|
this.showOverlay(this.overlays.noVisibleSeries, rect2);
|
|
} else {
|
|
this.hideOverlay(this.overlays.noVisibleSeries);
|
|
}
|
|
if (unsupportedBrowser) {
|
|
this.showOverlay(this.overlays.unsupportedBrowser, rect2);
|
|
} else {
|
|
this.hideOverlay(this.overlays.unsupportedBrowser);
|
|
}
|
|
const shown = loadingShown || noDataShown || noVisibleSeriesShown || unsupportedBrowser;
|
|
setAttribute(this.overlayElem, "aria-hidden", !shown);
|
|
}
|
|
showOverlay(overlay, seriesRect) {
|
|
if (!overlay.enabled)
|
|
return;
|
|
const element2 = overlay.getElement(this.chartLike, this.animationManager, this.localeManager, seriesRect);
|
|
this.overlayElem.appendChild(element2);
|
|
}
|
|
hideOverlay(overlay) {
|
|
overlay.removeElement(() => {
|
|
this.overlayElem.innerText = "\xA0";
|
|
}, this.animationManager);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/chart.ts
|
|
var debug = debugLogger_exports.create(true, "opts");
|
|
var _Chart = class _Chart extends Observable {
|
|
constructor(options, resources) {
|
|
var _a;
|
|
super();
|
|
this.id = createId(this);
|
|
this.seriesRoot = new TranslatableGroup({
|
|
name: `${this.id}-series-root`,
|
|
zIndex: 7 /* SERIES_LAYER */
|
|
});
|
|
this.annotationRoot = new TranslatableGroup({
|
|
name: `${this.id}-annotation-root`,
|
|
zIndex: 11 /* SERIES_ANNOTATION */
|
|
});
|
|
this.titleGroup = new Group({
|
|
name: "titles",
|
|
zIndex: 15 /* SERIES_LABEL */
|
|
});
|
|
this.debug = debugLogger_exports.create(true, "chart");
|
|
this.extraDebugStats = {};
|
|
this.data = DataSet.empty();
|
|
this._firstAutoSize = true;
|
|
this._autoSizeNotify = new AsyncAwaitQueue();
|
|
this._requiredRange = 0;
|
|
this._requiredRangeDirection = "x" /* X */;
|
|
this.chartCaptions = new ChartCaptions();
|
|
this.padding = new Padding(20);
|
|
this.keyboard = new Keyboard();
|
|
this.touch = new Touch();
|
|
this.mode = "standalone";
|
|
this.styleNonce = void 0;
|
|
this.formatter = void 0;
|
|
this.suppressFieldDotNotation = false;
|
|
this.loadGoogleFonts = false;
|
|
this.destroyed = false;
|
|
this.cleanup = new CleanupRegistry();
|
|
this.chartAnimationPhase = "initial";
|
|
this.modulesManager = new ModulesManager();
|
|
this.processors = [];
|
|
this.queuedUserOptions = [];
|
|
this.queuedChartOptions = [];
|
|
this.firstApply = true;
|
|
this.syncStatus = "init";
|
|
// Use a wrapper to comply with the @typescript-eslint/unbound-method rule.
|
|
this.fireEventWrapper = (event) => super.fireEvent(event);
|
|
this.apiUpdate = false;
|
|
this._pendingFactoryUpdatesCount = 0;
|
|
this._performUpdateSkipAnimations = false;
|
|
this._performUpdateNotify = new AsyncAwaitQueue();
|
|
this.performUpdateType = 10 /* NONE */;
|
|
this.runningUpdateType = 10 /* NONE */;
|
|
this.updateShortcutCount = 0;
|
|
this.seriesToUpdate = /* @__PURE__ */ new Set();
|
|
this.updateMutex = new Mutex();
|
|
this.clearCallbackCacheOnUpdate = false;
|
|
this.updateRequestors = {};
|
|
this.performUpdateTrigger = debouncedCallback(({ count }) => {
|
|
if (this.destroyed)
|
|
return;
|
|
this.updateMutex.acquire(this.tryPerformUpdate.bind(this, count)).catch((e) => logger_exports.errorOnce(e));
|
|
});
|
|
this._performUpdateSplits = {};
|
|
this._previousSplit = 0;
|
|
this.axes = this.createChartAxes();
|
|
this.series = [];
|
|
this._cachedData = void 0;
|
|
this.onSeriesNodeClick = (event) => {
|
|
this.fireEvent(event);
|
|
};
|
|
this.onSeriesNodeDoubleClick = (event) => {
|
|
this.fireEvent(event);
|
|
};
|
|
this.onSeriesVisibilityChange = (event) => {
|
|
this.fireEvent(event);
|
|
};
|
|
this.seriesGroupingChanged = (event) => {
|
|
if (!(event instanceof SeriesGroupingChangedEvent))
|
|
return;
|
|
const { series, seriesGrouping } = event;
|
|
if (series.contentGroup.isRoot())
|
|
return;
|
|
const seriesContentNode = this.seriesLayerManager.changeGroup({
|
|
internalId: series.internalId,
|
|
type: series.type,
|
|
contentGroup: series.contentGroup,
|
|
bringToFront: () => series.bringToFront(),
|
|
renderToOffscreenCanvas: () => series.renderToOffscreenCanvas(),
|
|
seriesGrouping
|
|
});
|
|
if (seriesContentNode != null) {
|
|
series.attachSeries(seriesContentNode, this.seriesRoot, this.annotationRoot);
|
|
}
|
|
};
|
|
this.chartOptions = options;
|
|
const scene = resources?.scene;
|
|
const container = resources?.container ?? options.processedOptions.container ?? void 0;
|
|
const styleContainer = resources?.styleContainer ?? options.specialOverrides.styleContainer;
|
|
const skipCss = options.specialOverrides.skipCss;
|
|
if (scene) {
|
|
this._firstAutoSize = false;
|
|
this._lastAutoSize = [scene.width, scene.height, scene.pixelRatio];
|
|
}
|
|
const root = new Group({ name: "root" });
|
|
root.visible = false;
|
|
root.append(this.seriesRoot);
|
|
root.append(this.annotationRoot);
|
|
root.append(this.titleGroup);
|
|
this.titleGroup.append(this.title.node);
|
|
this.titleGroup.append(this.subtitle.node);
|
|
this.titleGroup.append(this.footnote.node);
|
|
this.tooltip = new Tooltip();
|
|
this.seriesLayerManager = new SeriesLayerManager(this.seriesRoot);
|
|
this.mode = options.userOptions.mode ?? this.mode;
|
|
this.styleNonce = options.processedOptions.styleNonce;
|
|
const ctx = this.ctx = new ChartContext(this, {
|
|
chartType: this.getChartType(),
|
|
scene,
|
|
root,
|
|
container,
|
|
styleContainer,
|
|
skipCss,
|
|
domMode: options.optionMetadata.domMode,
|
|
withDragInterpretation: options.optionMetadata.withDragInterpretation ?? true,
|
|
syncManager: new SyncManager(this),
|
|
fireEvent: (event) => this.fireEvent(event),
|
|
updateCallback: (type, opts) => this.update(type, opts),
|
|
updateMutex: this.updateMutex
|
|
});
|
|
if (options.optionMetadata.presetType === "sparkline") {
|
|
ctx.highlightManager.unhighlightDelay = 0;
|
|
ctx.tooltipManager.removeDelay = 0;
|
|
}
|
|
this.cleanup.register(ctx.eventsHub.on("dom:resize", () => this.parentResize(ctx.domManager.containerSize)));
|
|
this.overlays = new ChartOverlays();
|
|
(_a = this.overlays.loading).renderer ?? (_a.renderer = () => getLoadingSpinner(this.overlays.loading.getText(ctx.localeManager), ctx.animationManager.defaultDuration));
|
|
this.processors = [
|
|
new DataWindowProcessor(
|
|
this,
|
|
ctx.eventsHub,
|
|
ctx.dataService,
|
|
ctx.updateService,
|
|
ctx.zoomManager,
|
|
ctx.animationManager
|
|
),
|
|
new OverlaysProcessor(
|
|
this,
|
|
this.overlays,
|
|
ctx.eventsHub,
|
|
ctx.dataService,
|
|
ctx.localeManager,
|
|
ctx.animationManager,
|
|
ctx.domManager
|
|
)
|
|
];
|
|
this.highlight = new ChartHighlight();
|
|
this.container = container;
|
|
const moduleContext = this.getModuleContext();
|
|
this.background = enterpriseRegistry.createBackground?.(moduleContext) ?? new Background(moduleContext);
|
|
this.foreground = enterpriseRegistry.createForeground?.(moduleContext);
|
|
this.seriesArea = new SeriesArea(moduleContext);
|
|
ctx.domManager.setDataBoolean("animating", false);
|
|
ctx.domManager.setDataNumber("animationTimeMs", 0);
|
|
this.seriesAreaManager = new SeriesAreaManager(this.initSeriesAreaDependencies());
|
|
this.cleanup.register(
|
|
ctx.layoutManager.registerElement(0 /* Caption */, (e) => {
|
|
e.layoutBox.shrink(this.padding.toJson());
|
|
this.chartCaptions.positionCaptions(e);
|
|
}),
|
|
ctx.eventsHub.on("layout:complete", (e) => this.chartCaptions.positionAbsoluteCaptions(e)),
|
|
ctx.eventsHub.on("data:load", (event) => {
|
|
this.data = new DataSet(event.data);
|
|
}),
|
|
this.title.registerInteraction(moduleContext, "beforebegin"),
|
|
this.subtitle.registerInteraction(moduleContext, "beforebegin"),
|
|
this.footnote.registerInteraction(moduleContext, "afterend"),
|
|
() => this.title.destroy(),
|
|
() => this.subtitle.destroy(),
|
|
() => this.footnote.destroy(),
|
|
Widget.addWindowEvent("page-left", () => this.destroy()),
|
|
ctx.animationManager.addListener("animation-frame", () => {
|
|
this.update(9 /* SCENE_RENDER */);
|
|
ctx.domManager.setDataNumber("animationTimeMs", ctx.animationManager.getCumulativeAnimationTime());
|
|
}),
|
|
ctx.animationManager.addListener("animation-start", () => ctx.domManager.setDataBoolean("animating", true)),
|
|
ctx.animationManager.addListener("animation-stop", () => {
|
|
ctx.domManager.setDataBoolean("animating", false);
|
|
ctx.domManager.setDataNumber("animationTimeMs", ctx.animationManager.getCumulativeAnimationTime());
|
|
}),
|
|
ctx.eventsHub.on("zoom:change-complete", () => {
|
|
for (const s of this.series) {
|
|
s.animationState?.transition("updateData");
|
|
}
|
|
const skipAnimations = this.chartAnimationPhase !== "initial";
|
|
this.update(5 /* PERFORM_LAYOUT */, { forceNodeDataRefresh: true, skipAnimations });
|
|
})
|
|
);
|
|
this.parentResize(ctx.domManager.containerSize);
|
|
}
|
|
static getInstance(element2) {
|
|
return _Chart.chartsInstances.get(element2);
|
|
}
|
|
/** NOTE: This is exposed for use by Integrated charts only. */
|
|
get canvasElement() {
|
|
return this.ctx.scene.canvas.element;
|
|
}
|
|
download(fileName, fileFormat) {
|
|
this.ctx.scene.download(fileName, fileFormat);
|
|
}
|
|
getCanvasDataURL(fileFormat) {
|
|
return this.ctx.scene.getDataURL(fileFormat);
|
|
}
|
|
toSVG() {
|
|
return this.ctx.scene.toSVG();
|
|
}
|
|
get seriesAreaBoundingBox() {
|
|
return this.seriesAreaManager.bbox;
|
|
}
|
|
getOptions() {
|
|
return this.queuedUserOptions.at(-1) ?? this.chartOptions.userOptions;
|
|
}
|
|
getChartOptions() {
|
|
return this.queuedChartOptions.at(-1) ?? this.chartOptions;
|
|
}
|
|
isDataTransactionSupported() {
|
|
return true;
|
|
}
|
|
overrideFocusVisible(visible) {
|
|
this.seriesAreaManager.focusIndicator?.overrideFocusVisible(visible);
|
|
}
|
|
fireEvent(event) {
|
|
callWithContext(this, this.fireEventWrapper, event);
|
|
}
|
|
initSeriesAreaDependencies() {
|
|
const { ctx, tooltip, highlight: highlight5, overlays, seriesRoot, mode } = this;
|
|
const chartType = this.getChartType();
|
|
const fireEvent = this.fireEvent.bind(this);
|
|
const getUpdateType = () => this.performUpdateType;
|
|
const getTooltipContent = (series, datumIndex, removeThisDatum, purpose) => this.getTooltipContent(series, datumIndex, removeThisDatum, purpose);
|
|
return {
|
|
fireEvent,
|
|
getUpdateType,
|
|
getTooltipContent,
|
|
chartType,
|
|
ctx,
|
|
tooltip,
|
|
highlight: highlight5,
|
|
overlays,
|
|
seriesRoot,
|
|
mode
|
|
};
|
|
}
|
|
getModuleContext() {
|
|
return this.ctx;
|
|
}
|
|
getTooltipContent(series, datumIndex, removeMeDatum, purpose) {
|
|
const useTooltip = purpose === "aria-label" || series.properties.tooltip.enabled !== false;
|
|
const baseTooltipContent = useTooltip ? series.getTooltipContent(datumIndex, removeMeDatum) : void 0;
|
|
const tooltipContent = baseTooltipContent == null ? [] : [baseTooltipContent];
|
|
if (this.tooltip.mode !== "shared" || this.series.length === 1) {
|
|
return tooltipContent;
|
|
}
|
|
const categoryValue = series.getCategoryValue(datumIndex);
|
|
if (categoryValue == null)
|
|
return tooltipContent;
|
|
return this.series.flatMap((s) => {
|
|
if (s === series)
|
|
return tooltipContent;
|
|
if (!s.isEnabled() || s.properties.tooltip.enabled === false)
|
|
return [];
|
|
const seriesDatumIndex = s.datumIndexForCategoryValue(categoryValue);
|
|
const seriesTooltipContent = seriesDatumIndex == null ? void 0 : s.getTooltipContent(seriesDatumIndex, void 0);
|
|
if (seriesTooltipContent == null)
|
|
return [];
|
|
return [seriesTooltipContent];
|
|
});
|
|
}
|
|
getCaptionText() {
|
|
return [this.title, this.subtitle, this.footnote].filter((caption) => caption.enabled && caption.text).map((caption) => caption.text).join(". ");
|
|
}
|
|
getAriaLabel() {
|
|
return this.ctx.localeManager.t("ariaAnnounceChart", { seriesCount: this.series.length });
|
|
}
|
|
refreshSeriesUserVisibility(outdatedOptions, seriesWithUserVisibility) {
|
|
for (let i = 0; i < this.series.length; i++) {
|
|
const src = this.series[i];
|
|
const dst = outdatedOptions.processedOptions.series?.[i];
|
|
if (seriesWithUserVisibility.identifiers.has(src.id) || seriesWithUserVisibility.indices.has(i)) {
|
|
if (dst !== void 0 && "visible" in dst) {
|
|
dst.visible = src.visible;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
resetAnimations() {
|
|
this.chartAnimationPhase = "initial";
|
|
for (const series of this.series) {
|
|
series.resetAnimation(this.chartAnimationPhase);
|
|
}
|
|
for (const axis of this.axes) {
|
|
axis.resetAnimation(this.chartAnimationPhase);
|
|
}
|
|
this.animationRect = void 0;
|
|
this.ctx.animationManager.reset();
|
|
}
|
|
skipAnimations() {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
this._performUpdateSkipAnimations = true;
|
|
}
|
|
detachAndClear() {
|
|
this.container = void 0;
|
|
this.ctx.scene.clearCanvas();
|
|
}
|
|
destroy(opts) {
|
|
if (this.destroyed) {
|
|
return;
|
|
}
|
|
const keepTransferableResources = opts?.keepTransferableResources;
|
|
let result;
|
|
this.performUpdateType = 10 /* NONE */;
|
|
this.cleanup.flush();
|
|
for (const p of this.processors) {
|
|
p.destroy();
|
|
}
|
|
this.overlays.destroy();
|
|
this.modulesManager.destroy();
|
|
this.background.destroy();
|
|
this.foreground?.destroy();
|
|
this.seriesArea.destroy();
|
|
if (keepTransferableResources) {
|
|
this.ctx.scene.strip();
|
|
result = {
|
|
container: this.container,
|
|
scene: this.ctx.scene
|
|
};
|
|
} else {
|
|
this.ctx.scene.destroy();
|
|
this.container = void 0;
|
|
}
|
|
this.destroySeries(this.series);
|
|
this.seriesLayerManager.destroy();
|
|
this.axes.destroy();
|
|
this.animationRect = void 0;
|
|
this.ctx.destroy();
|
|
this.destroyed = true;
|
|
Object.freeze(this);
|
|
return result;
|
|
}
|
|
requestFactoryUpdate(cb) {
|
|
if (this.destroyed)
|
|
return;
|
|
this._pendingFactoryUpdatesCount++;
|
|
this.updateMutex.acquire(async () => {
|
|
if (this.destroyed)
|
|
return;
|
|
try {
|
|
await cb(this);
|
|
} finally {
|
|
if (!this.destroyed) {
|
|
this._pendingFactoryUpdatesCount--;
|
|
}
|
|
}
|
|
}).catch((e) => logger_exports.errorOnce(e));
|
|
}
|
|
clearCallbackCache() {
|
|
this.ctx.callbackCache.invalidateCache();
|
|
for (const series of this.series) {
|
|
series.resetDatumCallbackCache();
|
|
}
|
|
}
|
|
update(type = 0 /* FULL */, opts) {
|
|
if (this.destroyed)
|
|
return;
|
|
const {
|
|
forceNodeDataRefresh = false,
|
|
skipAnimations,
|
|
seriesToUpdate = this.series,
|
|
newAnimationBatch,
|
|
apiUpdate = false,
|
|
clearCallbackCache = false
|
|
} = opts ?? {};
|
|
this.apiUpdate = apiUpdate;
|
|
this.ctx.widgets.seriesWidget.setDragTouchEnabled(this.touch.dragAction !== "none");
|
|
if (forceNodeDataRefresh) {
|
|
for (const series of this.series) {
|
|
series.markNodeDataDirty();
|
|
}
|
|
}
|
|
for (const series of seriesToUpdate) {
|
|
this.seriesToUpdate.add(series);
|
|
}
|
|
if (skipAnimations) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
this._performUpdateSkipAnimations = true;
|
|
}
|
|
if (newAnimationBatch && this.ctx.animationManager.isActive()) {
|
|
this._performUpdateSkipAnimations = true;
|
|
}
|
|
if (type === 0 /* FULL */ || clearCallbackCache) {
|
|
this.clearCallbackCacheOnUpdate = true;
|
|
}
|
|
if (this.debug.check()) {
|
|
let stack = new Error("Stack trace for update tracking").stack ?? "<unknown>";
|
|
stack = stack.replaceAll(/\([^)]*/g, "");
|
|
this.updateRequestors[stack] = type;
|
|
}
|
|
if (type < this.performUpdateType) {
|
|
this.performUpdateType = type;
|
|
this.ctx.domManager.setDataBoolean("updatePending", true);
|
|
this.performUpdateTrigger.schedule(opts?.backOffMs);
|
|
}
|
|
}
|
|
updateSplits(splitName) {
|
|
const splits = this._performUpdateSplits;
|
|
splits[splitName] ?? (splits[splitName] = 0);
|
|
splits[splitName] += performance.now() - this._previousSplit;
|
|
this._previousSplit = performance.now();
|
|
}
|
|
async tryPerformUpdate(count) {
|
|
try {
|
|
await this.performUpdate(count);
|
|
} catch (error2) {
|
|
logger_exports.error("update error", error2, error2.stack);
|
|
}
|
|
}
|
|
async performUpdate(count) {
|
|
const { performUpdateType, extraDebugStats, _performUpdateSplits: splits, ctx } = this;
|
|
const seriesToUpdate = [...this.seriesToUpdate];
|
|
if (this.clearCallbackCacheOnUpdate) {
|
|
this.clearCallbackCacheOnUpdate = false;
|
|
this.clearCallbackCache();
|
|
}
|
|
this.performUpdateType = 10 /* NONE */;
|
|
this.seriesToUpdate.clear();
|
|
this.runningUpdateType = performUpdateType;
|
|
if (this.updateShortcutCount === 0 && performUpdateType < 9 /* SCENE_RENDER */) {
|
|
ctx.animationManager.startBatch(this._performUpdateSkipAnimations);
|
|
ctx.animationManager.onBatchStop(() => this.chartAnimationPhase = "ready");
|
|
}
|
|
this.ctx.scene.updateDebugFlags();
|
|
this.debug("Chart.performUpdate() - start", ChartUpdateType[performUpdateType]);
|
|
this._previousSplit = performance.now();
|
|
splits.start ?? (splits.start = this._previousSplit);
|
|
switch (performUpdateType) {
|
|
case 0 /* FULL */:
|
|
if (this.checkUpdateShortcut(0 /* FULL */))
|
|
break;
|
|
this.ctx.updateService.dispatchPreDomUpdate();
|
|
this.updateDOM();
|
|
case 1 /* UPDATE_DATA */:
|
|
if (this.checkUpdateShortcut(1 /* UPDATE_DATA */))
|
|
break;
|
|
this.updateData();
|
|
this.updateSplits("\u2B07\uFE0F");
|
|
case 2 /* PROCESS_DATA */:
|
|
if (this.checkUpdateShortcut(2 /* PROCESS_DATA */))
|
|
break;
|
|
await this.processData();
|
|
this.seriesAreaManager.dataChanged();
|
|
if (this.pendingLocaleText) {
|
|
const localeModule = this.modulesManager.getModule("locale");
|
|
if (localeModule && "localeText" in localeModule) {
|
|
localeModule.localeText = this.pendingLocaleText;
|
|
}
|
|
this.pendingLocaleText = void 0;
|
|
}
|
|
this.updateSplits("\u{1F4CA}");
|
|
case 3 /* PROCESS_DOMAIN */:
|
|
if (this.checkUpdateShortcut(3 /* PROCESS_DOMAIN */))
|
|
break;
|
|
await this.processDomains();
|
|
this.updateSplits("\u26F0\uFE0F");
|
|
case 4 /* PROCESS_RANGE */:
|
|
if (this.checkUpdateShortcut(4 /* PROCESS_RANGE */))
|
|
break;
|
|
this.processRanges();
|
|
this.updateSplits("\u{1F4D0}");
|
|
case 5 /* PERFORM_LAYOUT */:
|
|
await this.checkFirstAutoSize();
|
|
if (this.checkUpdateShortcut(5 /* PERFORM_LAYOUT */))
|
|
break;
|
|
await this.processLayout();
|
|
this.updateSplits("\u2316");
|
|
case 6 /* PRE_SERIES_UPDATE */:
|
|
if (this.checkUpdateShortcut(6 /* PRE_SERIES_UPDATE */))
|
|
break;
|
|
this.preSeriesUpdate();
|
|
this.updateSplits("\u2753");
|
|
case 7 /* SERIES_UPDATE */: {
|
|
if (this.checkUpdateShortcut(7 /* SERIES_UPDATE */))
|
|
break;
|
|
this.seriesRoot.renderToOffscreenCanvas = this.highlight.drawingMode === "cutout";
|
|
await this.updateSeries(seriesToUpdate);
|
|
this.updateAriaLabels();
|
|
this.seriesLayerManager.updateLayerCompositing();
|
|
this.updateSplits("\u{1F914}");
|
|
}
|
|
case 8 /* PRE_SCENE_RENDER */:
|
|
if (this.checkUpdateShortcut(8 /* PRE_SCENE_RENDER */))
|
|
break;
|
|
ctx.updateService.dispatchPreSceneRender();
|
|
this.updateSplits("\u2196");
|
|
case 9 /* SCENE_RENDER */:
|
|
if (this.checkUpdateShortcut(9 /* SCENE_RENDER */))
|
|
break;
|
|
ctx.animationManager.endBatch();
|
|
extraDebugStats["updateShortcutCount"] = this.updateShortcutCount;
|
|
ctx.scene.render({
|
|
debugSplitTimes: splits,
|
|
extraDebugStats,
|
|
seriesRect: this.seriesRect,
|
|
debugColors: this.getDebugColors()
|
|
});
|
|
this.extraDebugStats = {};
|
|
for (const key of Object.keys(splits)) {
|
|
delete splits[key];
|
|
}
|
|
this.ctx.domManager.incrementDataCounter("sceneRenders");
|
|
this.ctx.domManager.postRenderUpdate();
|
|
case 10 /* NONE */:
|
|
this.updateShortcutCount = 0;
|
|
this.updateRequestors = {};
|
|
this._performUpdateSkipAnimations = false;
|
|
ctx.animationManager.endBatch();
|
|
}
|
|
if (!this.destroyed) {
|
|
ctx.updateService.dispatchUpdateComplete(this.apiUpdate, this.updateShortcutCount > 0);
|
|
this.apiUpdate = false;
|
|
this.ctx.domManager.setDataBoolean("updatePending", false);
|
|
this.runningUpdateType = 10 /* NONE */;
|
|
this.syncStatus = "ready";
|
|
}
|
|
this._performUpdateNotify.notify();
|
|
const end3 = performance.now();
|
|
this.debug("Chart.performUpdate() - end", {
|
|
chart: this,
|
|
durationMs: roundTo(end3 - splits["start"]),
|
|
count,
|
|
performUpdateType: ChartUpdateType[performUpdateType]
|
|
});
|
|
}
|
|
updateThemeClassName() {
|
|
const themeClassNamePrefix = "ag-charts-theme-";
|
|
const validThemeClassNames = [`${themeClassNamePrefix}default`, `${themeClassNamePrefix}default-dark`];
|
|
let themeClassName = validThemeClassNames[0];
|
|
let isDark = false;
|
|
let { theme } = this.chartOptions.processedOptions;
|
|
while (typeof theme !== "string" && theme != null) {
|
|
theme = theme.baseTheme;
|
|
}
|
|
if (typeof theme === "string") {
|
|
themeClassName = theme.replace("ag-", themeClassNamePrefix);
|
|
isDark = theme.includes("-dark");
|
|
}
|
|
if (!validThemeClassNames.includes(themeClassName)) {
|
|
themeClassName = isDark ? validThemeClassNames[1] : validThemeClassNames[0];
|
|
}
|
|
this.ctx.domManager.setThemeClass(themeClassName);
|
|
}
|
|
updateDOM() {
|
|
this.updateThemeClassName();
|
|
const { enabled, tabIndex } = this.keyboard;
|
|
this.ctx.domManager.setTabGuardIndex(enabled ? tabIndex ?? 0 : -1);
|
|
this.ctx.domManager.setThemeParameters(this.chartOptions.themeParameters);
|
|
}
|
|
updateAriaLabels() {
|
|
this.ctx.domManager.updateCanvasLabel(this.getAriaLabel());
|
|
}
|
|
checkUpdateShortcut(checkUpdateType) {
|
|
const maxShortcuts = 3;
|
|
if (this.destroyed)
|
|
return true;
|
|
if (this.updateShortcutCount > maxShortcuts) {
|
|
logger_exports.warn(
|
|
`exceeded the maximum number of simultaneous updates (${maxShortcuts + 1}), discarding changes and rendering`,
|
|
this.updateRequestors
|
|
);
|
|
return false;
|
|
}
|
|
if (this.performUpdateType <= checkUpdateType) {
|
|
this.debug("Chart.checkUpdateShortcut() - BLOCKED AT: ", ChartUpdateType[checkUpdateType]);
|
|
this.updateShortcutCount++;
|
|
return true;
|
|
}
|
|
this.debug("Chart.checkUpdateShortcut() - PROCEEDING TO: ", ChartUpdateType[checkUpdateType]);
|
|
return false;
|
|
}
|
|
async checkFirstAutoSize() {
|
|
if (this.width != null && this.height != null) {
|
|
} else if (!this._lastAutoSize) {
|
|
const success = await this._autoSizeNotify.waitForCompletion(500);
|
|
if (!success) {
|
|
this.debug("Chart.checkFirstAutoSize() - timeout for first size update.");
|
|
}
|
|
}
|
|
}
|
|
createChartAxes() {
|
|
return new ChartAxes();
|
|
}
|
|
onAxisChange(newValue, oldValue) {
|
|
if (oldValue == null && newValue.length === 0)
|
|
return;
|
|
this.ctx.axisManager.updateAxes(oldValue ?? [], newValue);
|
|
}
|
|
onSeriesChange(newValue, oldValue) {
|
|
const seriesToDestroy = oldValue?.filter((series) => !newValue.includes(series)) ?? [];
|
|
this.destroySeries(seriesToDestroy);
|
|
this.seriesLayerManager?.setSeriesCount(newValue.length);
|
|
for (const series of newValue) {
|
|
if (oldValue?.includes(series))
|
|
continue;
|
|
const seriesContentNode = this.seriesLayerManager.requestGroup(series);
|
|
series.attachSeries(seriesContentNode, this.seriesRoot, this.annotationRoot);
|
|
series.chart = {};
|
|
Object.defineProperty(series.chart, "mode", {
|
|
get: () => this.mode
|
|
});
|
|
Object.defineProperty(series.chart, "isMiniChart", {
|
|
get: () => false
|
|
});
|
|
Object.defineProperty(series.chart, "flashOnUpdateEnabled", {
|
|
get: () => !!this.modulesManager.getModule("flashOnUpdate")?.enabled
|
|
});
|
|
Object.defineProperty(series.chart, "seriesRect", {
|
|
get: () => this.seriesRect
|
|
});
|
|
series.resetAnimation(this.chartAnimationPhase);
|
|
this.addSeriesListeners(series);
|
|
}
|
|
this.seriesAreaManager?.seriesChanged(newValue);
|
|
}
|
|
destroySeries(allSeries) {
|
|
if (allSeries) {
|
|
for (const series of allSeries) {
|
|
series.removeEventListener("seriesNodeClick", this.onSeriesNodeClick);
|
|
series.removeEventListener("seriesNodeDoubleClick", this.onSeriesNodeDoubleClick);
|
|
series.removeEventListener("groupingChanged", this.seriesGroupingChanged);
|
|
series.destroy();
|
|
this.seriesLayerManager.releaseGroup(series);
|
|
series.detachSeries(void 0, this.seriesRoot, this.annotationRoot);
|
|
series.chart = void 0;
|
|
}
|
|
}
|
|
}
|
|
addSeriesListeners(series) {
|
|
if (this.hasEventListener("seriesNodeClick")) {
|
|
series.addEventListener("seriesNodeClick", this.onSeriesNodeClick);
|
|
}
|
|
if (this.hasEventListener("seriesNodeDoubleClick")) {
|
|
series.addEventListener("seriesNodeDoubleClick", this.onSeriesNodeDoubleClick);
|
|
}
|
|
if (this.hasEventListener("seriesVisibilityChange")) {
|
|
series.addEventListener("seriesVisibilityChange", this.onSeriesVisibilityChange);
|
|
}
|
|
series.addEventListener("groupingChanged", this.seriesGroupingChanged);
|
|
}
|
|
assignSeriesToAxes() {
|
|
for (const axis of this.axes) {
|
|
let seriesPredicateFn2 = function(s) {
|
|
return s.axes[axis.direction] === axis;
|
|
};
|
|
var seriesPredicateFn = seriesPredicateFn2;
|
|
axis.boundSeries = this.series.filter(seriesPredicateFn2);
|
|
}
|
|
}
|
|
assignAxesToSeries() {
|
|
for (const series of this.series) {
|
|
for (const direction of series.directions) {
|
|
const seriesAxisId = series.getKeyAxis(direction) ?? direction;
|
|
const newAxis = this.axes.findById(seriesAxisId);
|
|
if (!newAxis) {
|
|
logger_exports.warnOnce(
|
|
`no matching axis for direction [${direction}] and id [${seriesAxisId}]; check series and axes configuration.`
|
|
);
|
|
return;
|
|
}
|
|
series.axes[direction] = newAxis;
|
|
}
|
|
}
|
|
}
|
|
parentResize(size) {
|
|
if (size == null || this.width != null && this.height != null)
|
|
return;
|
|
let { width: width2, height: height2 } = size;
|
|
const { pixelRatio } = size;
|
|
width2 = Math.floor(width2);
|
|
height2 = Math.floor(height2);
|
|
if (width2 === 0 && height2 === 0)
|
|
return;
|
|
const [autoWidth = 0, autoHeight = 0, autoPixelRatio = 1] = this._lastAutoSize ?? [];
|
|
if (autoWidth === width2 && autoHeight === height2 && autoPixelRatio === pixelRatio)
|
|
return;
|
|
this._lastAutoSize = [width2, height2, pixelRatio];
|
|
this.resize("SizeMonitor", {});
|
|
}
|
|
resize(source, opts) {
|
|
const { scene, animationManager } = this.ctx;
|
|
const { inWidth, inHeight, inMinWidth, inMinHeight, inOverrideDevicePixelRatio } = opts;
|
|
this.ctx.domManager.setSizeOptions(
|
|
inMinWidth ?? this.minWidth,
|
|
inMinHeight ?? this.minHeight,
|
|
inWidth ?? this.width,
|
|
inHeight ?? this.height
|
|
);
|
|
const width2 = inWidth ?? this.width ?? this._lastAutoSize?.[0];
|
|
const height2 = inHeight ?? this.height ?? this._lastAutoSize?.[1];
|
|
const pixelRatio = inOverrideDevicePixelRatio ?? this.overrideDevicePixelRatio ?? this._lastAutoSize?.[2];
|
|
this.debug(`Chart.resize() from ${source}`, {
|
|
width: width2,
|
|
height: height2,
|
|
pixelRatio,
|
|
stack: new Error("Stack trace for resize tracking").stack
|
|
});
|
|
if (width2 == null || height2 == null || !isFiniteNumber(width2) || !isFiniteNumber(height2))
|
|
return;
|
|
if (scene.resize(width2, height2, pixelRatio)) {
|
|
animationManager.reset();
|
|
let skipAnimations = true;
|
|
if ((this.width == null || this.height == null) && this._firstAutoSize) {
|
|
skipAnimations = false;
|
|
this._firstAutoSize = false;
|
|
}
|
|
let updateType = 5 /* PERFORM_LAYOUT */;
|
|
for (const axis of this.axes) {
|
|
const axisUpdateType = axis.getUpdateTypeOnResize();
|
|
if (axisUpdateType < updateType) {
|
|
updateType = axisUpdateType;
|
|
}
|
|
}
|
|
this.update(updateType, { forceNodeDataRefresh: true, skipAnimations });
|
|
this._autoSizeNotify.notify();
|
|
}
|
|
}
|
|
updateData() {
|
|
this.ctx.eventsHub.emit("data:update", this.data);
|
|
}
|
|
async processData() {
|
|
if (this.series.some((s) => s.canHaveAxes)) {
|
|
this.assignAxesToSeries();
|
|
this.assignSeriesToAxes();
|
|
}
|
|
const dataController = new DataController(this.mode, this.suppressFieldDotNotation, this.ctx.eventsHub);
|
|
const promises = [];
|
|
for (const series of this.series) {
|
|
promises.push(series.processData(dataController) ?? Promise.resolve());
|
|
}
|
|
for (const module2 of this.modulesManager.modules()) {
|
|
if (module2?.processData) {
|
|
promises.push(module2.processData(dataController) ?? Promise.resolve());
|
|
}
|
|
}
|
|
this._cachedData = dataController.execute(this._cachedData);
|
|
this.updateSplits("\u{1F3ED}");
|
|
await Promise.all(promises);
|
|
this.updateLegends();
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
async processDomains() {
|
|
for (const axis of this.axes) {
|
|
axis.processData();
|
|
}
|
|
for (const series of this.series) {
|
|
series.updatedDomains();
|
|
}
|
|
}
|
|
processRanges() {
|
|
var _a;
|
|
const seriesRanges = {};
|
|
const chartRanges = {};
|
|
const seriesTypes = /* @__PURE__ */ new Map();
|
|
this._requiredRangeDirection = "x" /* X */;
|
|
for (const series of this.series) {
|
|
if (!series.visible)
|
|
continue;
|
|
seriesRanges[_a = series.type] ?? (seriesRanges[_a] = []);
|
|
series.getMinimumRangeSeries(seriesRanges[series.type]);
|
|
if (series.resolveKeyDirection("x" /* X */) === "y" /* Y */) {
|
|
this._requiredRangeDirection = "y" /* Y */;
|
|
}
|
|
if (!seriesTypes.has(series.type)) {
|
|
seriesTypes.set(series.type, series);
|
|
}
|
|
}
|
|
for (const [type, firstSeries] of seriesTypes) {
|
|
chartRanges[type] = firstSeries.getMinimumRangeChart(seriesRanges[type]);
|
|
}
|
|
if (Object.keys(chartRanges).length === 0) {
|
|
this._requiredRange = 0;
|
|
} else {
|
|
this._requiredRange = Math.ceil(Math.max(...Object.values(chartRanges)));
|
|
}
|
|
for (const axis of this.axes) {
|
|
axis.requiredRange = this._requiredRange;
|
|
}
|
|
}
|
|
updateLegends(initialStateLegend) {
|
|
for (const module2 of moduleRegistry_exports.listModulesByType("plugin" /* Plugin */)) {
|
|
switch (module2.name) {
|
|
case "legend":
|
|
this.setCategoryLegendData(initialStateLegend);
|
|
break;
|
|
case "gradientLegend":
|
|
const moduleInstance = this.modulesManager.getModule("gradientLegend");
|
|
moduleInstance.data = this.series.filter((s) => s.properties.showInLegend).flatMap((s) => s.getLegendData("gradient"));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
setCategoryLegendData(initialState) {
|
|
const { legendManager, stateManager } = this.ctx;
|
|
if (initialState) {
|
|
for (const s of this.series) {
|
|
const seriesState = initialState.find((init) => init.seriesId === s.id);
|
|
s.onLegendInitialState("category", seriesState);
|
|
}
|
|
}
|
|
const legendData = this.series.flatMap((s) => {
|
|
const seriesLegendData = s.getLegendData("category");
|
|
legendManager.updateData(s.id, seriesLegendData);
|
|
return seriesLegendData;
|
|
});
|
|
if (initialState) {
|
|
stateManager.setStateAndRestore(legendManager, initialState);
|
|
return;
|
|
}
|
|
if (this.mode !== "integrated") {
|
|
const seriesMarkerFills = {};
|
|
const seriesMap = new Map(this.series.map((s) => [s.id, s]));
|
|
for (const {
|
|
seriesId,
|
|
symbol: { marker },
|
|
label
|
|
} of legendData.filter((d) => !d.hideInLegend)) {
|
|
if (marker.fill == null)
|
|
continue;
|
|
const series = seriesMap.get(seriesId);
|
|
if (!series?.hasData)
|
|
continue;
|
|
const seriesType = series.type;
|
|
const markerFill = seriesMarkerFills[seriesType] ?? (seriesMarkerFills[seriesType] = /* @__PURE__ */ new Map());
|
|
if (markerFill.has(label.text)) {
|
|
if (markerFill.get(label.text) !== marker.fill) {
|
|
logger_exports.warnOnce(
|
|
`legend item '${toPlainText(label.text)}' has multiple fill colours, this may cause unexpected behaviour.`
|
|
);
|
|
}
|
|
} else {
|
|
markerFill.set(label.text, marker.fill);
|
|
}
|
|
}
|
|
}
|
|
legendManager.update();
|
|
}
|
|
async processLayout() {
|
|
const oldRect = this.animationRect;
|
|
const { width: width2, height: height2 } = this.ctx.scene;
|
|
const ctx = this.ctx.layoutManager.createContext(width2, height2);
|
|
await this.performLayout(ctx);
|
|
if (oldRect && !this.animationRect?.equals(oldRect)) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
}
|
|
this.debug("Chart.performUpdate() - seriesRect", this.seriesRect);
|
|
}
|
|
getDebugColors() {
|
|
const bg = this.background.fill;
|
|
if (!bg)
|
|
return void 0;
|
|
try {
|
|
const color2 = Color.fromString(bg);
|
|
const [lightness] = Color.RGBtoOKLCH(color2.r, color2.g, color2.b);
|
|
return { background: bg, foreground: lightness > 0.5 ? "black" : "white" };
|
|
} catch {
|
|
return { background: bg };
|
|
}
|
|
}
|
|
preSeriesUpdate() {
|
|
const { _requiredRange, seriesRect } = this;
|
|
if (seriesRect == null)
|
|
return;
|
|
const dimension = this._requiredRangeDirection === "x" /* X */ ? seriesRect.width : seriesRect.height;
|
|
const requiredRangeRatio = _requiredRange / dimension || 0;
|
|
this.ctx.updateService.dispatchPreSeriesUpdate(requiredRangeRatio, this._requiredRangeDirection);
|
|
}
|
|
async updateSeries(seriesToUpdate) {
|
|
const { seriesRect } = this;
|
|
function seriesUpdate(series) {
|
|
return series.update({ seriesRect });
|
|
}
|
|
await Promise.all(seriesToUpdate.map(seriesUpdate).filter((p) => p != null));
|
|
this.ctx.seriesLabelLayoutManager.updateLabels(
|
|
this.series.filter((s) => s.visible && s.usesPlacedLabels),
|
|
this.padding,
|
|
this.seriesRect
|
|
);
|
|
}
|
|
async waitForUpdate(timeoutMs, failOnTimeout) {
|
|
const agChartsDebugTimeout = getWindow("agChartsDebugTimeout");
|
|
if (agChartsDebugTimeout == null) {
|
|
timeoutMs ?? (timeoutMs = 1e4);
|
|
failOnTimeout ?? (failOnTimeout = false);
|
|
} else {
|
|
timeoutMs = agChartsDebugTimeout;
|
|
failOnTimeout ?? (failOnTimeout = true);
|
|
}
|
|
const start2 = performance.now();
|
|
while (this._pendingFactoryUpdatesCount > 0 || this.performUpdateType !== 10 /* NONE */ || this.runningUpdateType !== 10 /* NONE */ || this.ctx.scene.waitingForUpdate() || this.data.hasPendingTransactions()) {
|
|
if (this.destroyed)
|
|
break;
|
|
if (this._pendingFactoryUpdatesCount > 0) {
|
|
await this.updateMutex.waitForClearAcquireQueue();
|
|
}
|
|
if (this.performUpdateType !== 10 /* NONE */ || this.runningUpdateType !== 10 /* NONE */ || this.data.hasPendingTransactions()) {
|
|
await this._performUpdateNotify.waitForCompletion();
|
|
}
|
|
if (performance.now() - start2 > timeoutMs) {
|
|
const message = `Chart.waitForUpdate() timeout of ${timeoutMs} reached - first chart update taking too long.`;
|
|
if (failOnTimeout) {
|
|
throw new Error(message);
|
|
} else {
|
|
logger_exports.warnOnce(message);
|
|
}
|
|
}
|
|
if (isInputPending()) {
|
|
await pause();
|
|
}
|
|
if (this.ctx.scene.waitingForUpdate()) {
|
|
await pause(50);
|
|
}
|
|
}
|
|
}
|
|
filterMiniChartSeries(series) {
|
|
return series?.filter((s) => s.showInMiniChart !== false);
|
|
}
|
|
applyOptions(newChartOptions) {
|
|
if (newChartOptions.seriesWithUserVisibility) {
|
|
this.refreshSeriesUserVisibility(this.chartOptions, newChartOptions.seriesWithUserVisibility);
|
|
}
|
|
const minimumUpdateType = 5 /* PERFORM_LAYOUT */;
|
|
const deltaOptions = this.firstApply ? newChartOptions.processedOptions : newChartOptions.diffOptions(this.chartOptions);
|
|
if (deltaOptions == null || Object.keys(deltaOptions).length === 0) {
|
|
debug("Chart.applyOptions() - no delta, forcing re-layout", deltaOptions);
|
|
this.update(minimumUpdateType, { apiUpdate: true, newAnimationBatch: true });
|
|
return;
|
|
}
|
|
const oldOpts = this.firstApply ? {} : this.chartOptions.processedOptions;
|
|
const newOpts = newChartOptions.processedOptions;
|
|
debug("Chart.applyOptions() - applying delta", deltaOptions);
|
|
const modulesChanged = this.applyModules();
|
|
const skip = [
|
|
"type",
|
|
"data",
|
|
"series",
|
|
"listeners",
|
|
"preset",
|
|
"theme",
|
|
"legend.listeners",
|
|
"navigator.miniChart.series",
|
|
"navigator.miniChart.label",
|
|
"locale.localeText",
|
|
"axes",
|
|
"topology",
|
|
"nodes",
|
|
"initialState",
|
|
"styleContainer",
|
|
"formatter",
|
|
"displayNullData"
|
|
];
|
|
if ("listeners" in deltaOptions) {
|
|
this.registerListeners(this, deltaOptions.listeners);
|
|
}
|
|
jsonApply(this, deltaOptions, { skip });
|
|
let forceNodeDataRefresh = false;
|
|
let seriesStatus = "no-op";
|
|
if (deltaOptions.series != null) {
|
|
seriesStatus = this.applySeries(this, deltaOptions.series, oldOpts?.series);
|
|
forceNodeDataRefresh = true;
|
|
}
|
|
if (seriesStatus === "replaced") {
|
|
this.resetAnimations();
|
|
}
|
|
if (this.applyAxes(this, newOpts, oldOpts, seriesStatus, [])) {
|
|
forceNodeDataRefresh = true;
|
|
}
|
|
const { userDeltaKeys } = newChartOptions;
|
|
const userExplicitlyPassedData = userDeltaKeys === void 0 || userDeltaKeys.has("data");
|
|
if (deltaOptions.data && userExplicitlyPassedData) {
|
|
const suppliedData = deltaOptions.data;
|
|
const userOptionsData = newChartOptions.userOptions.data;
|
|
const needsClone = Array.isArray(suppliedData) && suppliedData !== userOptionsData;
|
|
const dataForDataSet = needsClone ? suppliedData.slice() : suppliedData;
|
|
this.data = new DataSet(dataForDataSet);
|
|
}
|
|
if ("legend" in deltaOptions && deltaOptions.legend && "listeners" in deltaOptions.legend && this.modulesManager.isEnabled("legend")) {
|
|
const legendListeners = deltaOptions.legend.listeners;
|
|
if (legendListeners) {
|
|
Object.assign(this.legend.listeners, legendListeners);
|
|
} else {
|
|
this.legend.listeners.clear();
|
|
}
|
|
}
|
|
if (deltaOptions.locale?.localeText) {
|
|
this.pendingLocaleText = deltaOptions.locale?.localeText;
|
|
}
|
|
this.chartOptions = newChartOptions;
|
|
const navigatorModule = this.modulesManager.getModule("navigator");
|
|
const zoomModule = this.modulesManager.getModule("zoom");
|
|
const scrollbarModule = this.modulesManager.getModule("scrollbar");
|
|
if (!navigatorModule?.enabled && !zoomModule?.enabled && !scrollbarModule?.enabled) {
|
|
this.ctx.zoomManager.updateZoom(
|
|
{ source: "chart-update", sourceDetail: "internal-applyOptions" },
|
|
{ x: { min: 0, max: 1 } }
|
|
);
|
|
}
|
|
const miniChart = navigatorModule?.miniChart;
|
|
const miniChartSeries = newOpts.navigator?.miniChart?.series ?? newOpts.series;
|
|
if (miniChart?.enabled === true && miniChartSeries != null) {
|
|
this.applyMiniChartOptions(miniChart, miniChartSeries, newOpts, oldOpts);
|
|
} else if (miniChart?.enabled === false) {
|
|
miniChart.series = [];
|
|
miniChart.axes = [];
|
|
}
|
|
this.ctx.annotationManager.setAnnotationStyles(newChartOptions.annotationThemes);
|
|
forceNodeDataRefresh || (forceNodeDataRefresh = this.shouldForceNodeDataRefresh(deltaOptions, seriesStatus));
|
|
const majorChange = forceNodeDataRefresh || modulesChanged;
|
|
const updateType = majorChange ? 0 /* FULL */ : minimumUpdateType;
|
|
this.maybeResetAnimations(seriesStatus);
|
|
if (this.shouldClearLegendData(newOpts, oldOpts, seriesStatus)) {
|
|
this.ctx.legendManager.clearData();
|
|
}
|
|
this.applyInitialState(newOpts);
|
|
this.ctx.formatManager.setFormatter(newOpts.formatter);
|
|
debug("Chart.applyOptions() - update type", ChartUpdateType[updateType], {
|
|
seriesStatus,
|
|
forceNodeDataRefresh
|
|
});
|
|
if (newChartOptions.optionsProcessingTime !== void 0) {
|
|
this._performUpdateSplits["\u2699\uFE0F"] = newChartOptions.optionsProcessingTime;
|
|
const optionsStartTime = performance.now() - newChartOptions.optionsProcessingTime;
|
|
this._performUpdateSplits.start = optionsStartTime;
|
|
}
|
|
this.update(updateType, {
|
|
apiUpdate: true,
|
|
forceNodeDataRefresh,
|
|
newAnimationBatch: true,
|
|
clearCallbackCache: true
|
|
});
|
|
this.firstApply = false;
|
|
}
|
|
applyInitialState(options) {
|
|
const { activeManager, annotationManager, chartTypeOriginator, historyManager, stateManager, zoomManager } = this.ctx;
|
|
const { initialState } = options;
|
|
if ("annotations" in options && options.annotations?.enabled && initialState?.annotations != null) {
|
|
const annotations = initialState.annotations.map((annotation) => {
|
|
const annotationTheme = annotationManager.getAnnotationTypeStyles(annotation.type);
|
|
return mergeDefaults(annotation, annotationTheme);
|
|
});
|
|
stateManager.setState(annotationManager, annotations);
|
|
}
|
|
if (initialState?.chartType != null) {
|
|
stateManager.setState(chartTypeOriginator, initialState.chartType);
|
|
}
|
|
if ((options.navigator?.enabled || options.zoom?.enabled || options.scrollbar?.enabled) && initialState?.zoom != null) {
|
|
stateManager.setState(zoomManager, initialState.zoom);
|
|
}
|
|
if (initialState?.active != null) {
|
|
stateManager.setState(activeManager, initialState.active);
|
|
}
|
|
if (initialState?.legend != null) {
|
|
this.updateLegends(initialState.legend);
|
|
}
|
|
if (initialState != null) {
|
|
historyManager.clear();
|
|
}
|
|
}
|
|
maybeResetAnimations(seriesStatus) {
|
|
if (this.mode !== "standalone")
|
|
return;
|
|
switch (seriesStatus) {
|
|
case "series-grouping-change":
|
|
case "replaced":
|
|
this.resetAnimations();
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
shouldForceNodeDataRefresh(deltaOptions, seriesStatus) {
|
|
const seriesDataUpdate = !!deltaOptions.data || seriesStatus === "data-change" || seriesStatus === "replaced";
|
|
const optionsHaveLegend = ["legend", "gradientLegend"].some(
|
|
(legendKey) => deltaOptions[legendKey] != null
|
|
);
|
|
const otherRefreshUpdate = deltaOptions.title != null && deltaOptions.subtitle != null || deltaOptions.formatter != null;
|
|
return seriesDataUpdate || optionsHaveLegend || otherRefreshUpdate;
|
|
}
|
|
shouldClearLegendData(options, oldOpts, seriesStatus) {
|
|
const seriesChanged = seriesStatus === "replaced" || seriesStatus === "series-count-changed" || seriesStatus === "series-grouping-change" || seriesStatus === "updated" && (options.series?.length !== oldOpts.series?.length || !options.series?.every((s, i) => s.type === oldOpts.series?.[i].type));
|
|
const legendRemoved = oldOpts.legend != null && oldOpts.legend.enabled !== false && (options.legend == null || options.legend.enabled === false);
|
|
return seriesChanged || legendRemoved;
|
|
}
|
|
applyMiniChartOptions(miniChart, miniChartSeries, completeOptions, oldOpts) {
|
|
const oldSeries = oldOpts?.navigator?.miniChart?.series ?? oldOpts?.series;
|
|
const miniChartSeriesStatus = this.applySeries(
|
|
miniChart,
|
|
this.filterMiniChartSeries(miniChartSeries),
|
|
this.filterMiniChartSeries(oldSeries)
|
|
);
|
|
this.applyAxes(miniChart, completeOptions, oldOpts, miniChartSeriesStatus, [
|
|
"tick",
|
|
"thickness",
|
|
"title",
|
|
"crosshair",
|
|
"gridLine",
|
|
"label"
|
|
]);
|
|
const series = miniChart.series;
|
|
for (const s of series) {
|
|
s.properties.id = void 0;
|
|
}
|
|
const axes = miniChart.axes;
|
|
const horizontalAxis = axes.find((axis) => axis.direction === "x" /* X */);
|
|
for (const axis of axes) {
|
|
axis.nice = false;
|
|
axis.gridLine.enabled = false;
|
|
axis.label.enabled = axis === horizontalAxis;
|
|
axis.tick.enabled = false;
|
|
axis.interactionEnabled = false;
|
|
}
|
|
if (horizontalAxis != null) {
|
|
const miniChartOpts = completeOptions.navigator?.miniChart;
|
|
const labelOptions2 = miniChartOpts?.label;
|
|
const intervalOptions = miniChartOpts?.label?.interval;
|
|
horizontalAxis.line.enabled = false;
|
|
horizontalAxis.label.set(
|
|
without(labelOptions2, [
|
|
"interval",
|
|
"autoRotate",
|
|
"autoRotateAngle",
|
|
"itemStyler",
|
|
"minSpacing",
|
|
"rotation"
|
|
])
|
|
);
|
|
if (horizontalAxis.type === "grouped-category") {
|
|
horizontalAxis.label.enabled = false;
|
|
horizontalAxis.label.rotation = 0;
|
|
const { depthOptions } = horizontalAxis;
|
|
if (depthOptions.length === 0) {
|
|
depthOptions.set([{ label: { enabled: true } }]);
|
|
} else {
|
|
for (let i = 1; i < depthOptions.length; i++) {
|
|
depthOptions[i].label.enabled = false;
|
|
}
|
|
}
|
|
} else if (horizontalAxis.type === "time" || horizontalAxis.type === "unit-time" || horizontalAxis.type === "ordinal-time") {
|
|
horizontalAxis.parentLevel.enabled = false;
|
|
}
|
|
horizontalAxis.interval.step = intervalOptions?.step;
|
|
horizontalAxis.interval.values = intervalOptions?.values;
|
|
horizontalAxis.interval.minSpacing = intervalOptions?.minSpacing;
|
|
horizontalAxis.interval.maxSpacing = intervalOptions?.maxSpacing;
|
|
}
|
|
}
|
|
applyModules() {
|
|
const { type: chartType } = this.constructor;
|
|
let modulesChanged = false;
|
|
for (const module2 of moduleRegistry_exports.listModulesByType("plugin" /* Plugin */)) {
|
|
const shouldBeEnabled = !module2.chartType || module2.chartType === chartType;
|
|
if (shouldBeEnabled === this.modulesManager.isEnabled(module2.name))
|
|
continue;
|
|
if (shouldBeEnabled) {
|
|
const moduleInstance = module2.create(this.getModuleContext());
|
|
this.modulesManager.addModule(module2.name, moduleInstance);
|
|
this[module2.name] = moduleInstance;
|
|
} else {
|
|
this.modulesManager.removeModule(module2.name);
|
|
delete this[module2.name];
|
|
}
|
|
modulesChanged = true;
|
|
}
|
|
return modulesChanged;
|
|
}
|
|
initSeriesDeclarationOrder(series) {
|
|
for (let idx = 0; idx < series.length; idx++) {
|
|
series[idx].setSeriesIndex(idx);
|
|
}
|
|
}
|
|
applySeries(chart, optSeries, oldOptSeries) {
|
|
if (!optSeries) {
|
|
return "no-change";
|
|
}
|
|
const matchResult = matchSeriesOptions(chart.series, optSeries, oldOptSeries);
|
|
if (matchResult.status === "no-overlap") {
|
|
debug(`Chart.applySeries() - creating new series instances, status: ${matchResult.status}`, matchResult);
|
|
const chartSeries = optSeries.map((opts) => this.createSeries(opts));
|
|
this.initSeriesDeclarationOrder(chartSeries);
|
|
chart.series = chartSeries;
|
|
return "replaced";
|
|
}
|
|
debug(`Chart.applySeries() - matchResult`, matchResult);
|
|
const seriesInstances = [];
|
|
let dataChanged = false;
|
|
let groupingChanged = false;
|
|
let isUpdated = false;
|
|
let seriesCountChanged = false;
|
|
const changes = matchResult.changes.toSorted((a, b) => a.targetIdx - b.targetIdx);
|
|
for (const change of changes) {
|
|
groupingChanged || (groupingChanged = change.status === "series-grouping");
|
|
dataChanged || (dataChanged = change.diff?.data != null);
|
|
isUpdated || (isUpdated = change.status !== "no-op");
|
|
seriesCountChanged || (seriesCountChanged = change.status === "add" || change.status === "remove");
|
|
switch (change.status) {
|
|
case "add": {
|
|
const newSeries = this.createSeries(change.opts);
|
|
seriesInstances.push(newSeries);
|
|
debug(`Chart.applySeries() - created new series`, newSeries);
|
|
break;
|
|
}
|
|
case "remove":
|
|
debug(`Chart.applySeries() - removing series at previous idx ${change.idx}`, change.series);
|
|
break;
|
|
case "no-op":
|
|
seriesInstances.push(change.series);
|
|
debug(`Chart.applySeries() - no change to series at previous idx ${change.idx}`, change.series);
|
|
break;
|
|
case "series-grouping":
|
|
case "update":
|
|
default: {
|
|
const { series, diff: diff9, idx } = change;
|
|
debug(`Chart.applySeries() - applying series diff previous idx ${idx}`, diff9, series);
|
|
this.applySeriesValues(series, diff9);
|
|
series.markNodeDataDirty();
|
|
seriesInstances.push(series);
|
|
}
|
|
}
|
|
}
|
|
this.initSeriesDeclarationOrder(seriesInstances);
|
|
debug(`Chart.applySeries() - final series instances`, seriesInstances);
|
|
chart.series = seriesInstances;
|
|
if (groupingChanged) {
|
|
return "series-grouping-change";
|
|
}
|
|
if (seriesCountChanged) {
|
|
return "series-count-changed";
|
|
}
|
|
if (dataChanged) {
|
|
return "data-change";
|
|
}
|
|
return isUpdated ? "updated" : "no-op";
|
|
}
|
|
applyAxes(chart, options, oldOpts, seriesStatus, skip = []) {
|
|
if (!("axes" in options) || !options.axes) {
|
|
return false;
|
|
}
|
|
skip = ["type", ...skip];
|
|
const axes = options.axes;
|
|
const forceRecreate = seriesStatus === "replaced";
|
|
const matchingTypes = !forceRecreate && chart.axes.matches(axes);
|
|
if (matchingTypes && isAgCartesianChartOptions(oldOpts)) {
|
|
for (const axis of chart.axes) {
|
|
const previousOpts = oldOpts.axes?.[axis.id] ?? {};
|
|
const axisDiff = jsonDiff(previousOpts, axes[axis.id]);
|
|
debug(`Chart.applyAxes() - applying axis diff idx ${axis.id}`, axisDiff);
|
|
jsonApply(axis, axisDiff, { skip });
|
|
}
|
|
return true;
|
|
}
|
|
debug(`Chart.applyAxes() - creating new axes instances; seriesStatus: ${seriesStatus}`);
|
|
chart.axes = this.createAxes(axes, skip);
|
|
return true;
|
|
}
|
|
createSeries(seriesOptions) {
|
|
const seriesModule = moduleRegistry_exports.getSeriesModule(seriesOptions.type);
|
|
const seriesInstance = seriesModule.create(this.getModuleContext());
|
|
this.applySeriesOptionModules(seriesInstance, seriesOptions);
|
|
this.applySeriesValues(seriesInstance, seriesOptions);
|
|
return seriesInstance;
|
|
}
|
|
applySeriesOptionModules(series, options) {
|
|
const moduleContext = series.createModuleContext();
|
|
const moduleMap = series.getModuleMap();
|
|
for (const module2 of moduleRegistry_exports.listModulesByType("series:plugin" /* SeriesPlugin */)) {
|
|
if (module2.name in options && (module2.seriesTypes?.includes(series.type) ?? true)) {
|
|
moduleMap.addModule(module2.name, module2.create(moduleContext));
|
|
}
|
|
}
|
|
}
|
|
applySeriesValues(target, options) {
|
|
const moduleMap = target.getModuleMap();
|
|
const { type, data, listeners, seriesGrouping, showInMiniChart, ...seriesOptions } = options;
|
|
for (const module2 of moduleRegistry_exports.listModulesByType("series:plugin" /* SeriesPlugin */)) {
|
|
if (module2.name in seriesOptions) {
|
|
const moduleInstance = moduleMap.getModule(module2.name);
|
|
if (moduleInstance) {
|
|
const moduleOptions = seriesOptions[module2.name];
|
|
moduleInstance.properties.set(moduleOptions);
|
|
delete seriesOptions[module2.name];
|
|
}
|
|
}
|
|
}
|
|
if (seriesOptions.visible != null) {
|
|
target.visible = seriesOptions.visible;
|
|
}
|
|
target.properties.set(seriesOptions);
|
|
if ("data" in options) {
|
|
target.setOptionsData(data == null ? void 0 : DataSet.wrap(data));
|
|
}
|
|
if ("listeners" in options) {
|
|
this.registerListeners(target, listeners);
|
|
if (this.series.includes(target)) {
|
|
this.addSeriesListeners(target);
|
|
}
|
|
}
|
|
if ("seriesGrouping" in options) {
|
|
if (seriesGrouping == null) {
|
|
target.seriesGrouping = void 0;
|
|
} else {
|
|
target.seriesGrouping = { ...target.seriesGrouping, ...seriesGrouping };
|
|
}
|
|
}
|
|
}
|
|
createAxes(options, skip) {
|
|
const newAxes = this.createChartAxes();
|
|
const moduleContext = this.getModuleContext();
|
|
for (const [id, axisOptions] of entries(options)) {
|
|
const axis = moduleRegistry_exports.getAxisModule(axisOptions.type).create(moduleContext);
|
|
axis.id = id;
|
|
this.applyAxisModules(axis, axisOptions);
|
|
jsonApply(axis, axisOptions, { skip });
|
|
newAxes.push(axis);
|
|
}
|
|
guessInvalidPositions(newAxes);
|
|
return newAxes;
|
|
}
|
|
applyAxisModules(axis, options) {
|
|
const moduleContext = axis.createModuleContext();
|
|
const moduleMap = axis.getModuleMap();
|
|
for (const module2 of moduleRegistry_exports.listModulesByType("axis:plugin" /* AxisPlugin */)) {
|
|
const shouldBeEnabled = options[module2.name] != null;
|
|
if (shouldBeEnabled === moduleMap.isEnabled(module2.name))
|
|
continue;
|
|
if (shouldBeEnabled) {
|
|
moduleMap.addModule(module2.name, module2.create(moduleContext));
|
|
axis[module2.name] = moduleMap.getModule(module2.name);
|
|
} else {
|
|
moduleMap.removeModule(module2.name);
|
|
delete axis[module2.name];
|
|
}
|
|
}
|
|
}
|
|
registerListeners(source, listeners) {
|
|
source.clearEventListeners();
|
|
if (listeners && typeof listeners === "object") {
|
|
for (const [property, listener] of entries(listeners)) {
|
|
if (listener == null) {
|
|
continue;
|
|
}
|
|
source.addEventListener(property, listener);
|
|
}
|
|
}
|
|
}
|
|
async applyTransaction(transaction) {
|
|
await this.updateMutex.acquire(() => {
|
|
this.data.addTransaction(transaction);
|
|
this.update(1 /* UPDATE_DATA */, {
|
|
apiUpdate: true,
|
|
skipAnimations: true
|
|
});
|
|
});
|
|
await this.waitForUpdate();
|
|
}
|
|
onSyncActiveClear() {
|
|
this.seriesAreaManager.onActiveClear();
|
|
}
|
|
};
|
|
_Chart.className = "Chart";
|
|
_Chart.chartsInstances = /* @__PURE__ */ new WeakMap();
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(value) {
|
|
if (this.destroyed)
|
|
return;
|
|
this.ctx.domManager.setContainer(value);
|
|
_Chart.chartsInstances.set(value, this);
|
|
},
|
|
oldValue(value) {
|
|
_Chart.chartsInstances.delete(value);
|
|
}
|
|
})
|
|
], _Chart.prototype, "container", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(value) {
|
|
this.resize("width option", { inWidth: value });
|
|
}
|
|
})
|
|
], _Chart.prototype, "width", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(value) {
|
|
this.resize("height option", { inHeight: value });
|
|
}
|
|
})
|
|
], _Chart.prototype, "height", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(value) {
|
|
this.resize("minWidth option", { inMinWidth: value });
|
|
}
|
|
})
|
|
], _Chart.prototype, "minWidth", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(value) {
|
|
this.resize("minHeight option", { inMinHeight: value });
|
|
}
|
|
})
|
|
], _Chart.prototype, "minHeight", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(value) {
|
|
this.resize("overrideDevicePixelRatio option", { inOverrideDevicePixelRatio: value });
|
|
}
|
|
})
|
|
], _Chart.prototype, "overrideDevicePixelRatio", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Chart.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Chart.prototype, "keyboard", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Chart.prototype, "touch", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Chart.prototype, "mode", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Chart.prototype, "styleNonce", 2);
|
|
__decorateClass([
|
|
ProxyProperty("chartCaptions.title")
|
|
], _Chart.prototype, "title", 2);
|
|
__decorateClass([
|
|
ProxyProperty("chartCaptions.subtitle")
|
|
], _Chart.prototype, "subtitle", 2);
|
|
__decorateClass([
|
|
ProxyProperty("chartCaptions.footnote")
|
|
], _Chart.prototype, "footnote", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Chart.prototype, "formatter", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Chart.prototype, "suppressFieldDotNotation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Chart.prototype, "loadGoogleFonts", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue(newValue, oldValue) {
|
|
this.onAxisChange(newValue, oldValue);
|
|
}
|
|
})
|
|
], _Chart.prototype, "axes", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue(newValue, oldValue) {
|
|
this.onSeriesChange(newValue, oldValue);
|
|
}
|
|
})
|
|
], _Chart.prototype, "series", 2);
|
|
var Chart = _Chart;
|
|
|
|
// packages/ag-charts-community/src/module/coreModulesTypes.ts
|
|
function paletteType(partial) {
|
|
if (partial?.up || partial?.down || partial?.neutral) {
|
|
return "user-full";
|
|
} else if (partial?.fills || partial?.strokes) {
|
|
return "user-indexed";
|
|
}
|
|
return "inbuilt";
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/themes/defaultColors.ts
|
|
var DEFAULT_FILLS = {
|
|
BLUE: "#5090dc",
|
|
ORANGE: "#ffa03a",
|
|
GREEN: "#459d55",
|
|
CYAN: "#34bfe1",
|
|
YELLOW: "#e1cc00",
|
|
VIOLET: "#9669cb",
|
|
GRAY: "#b5b5b5",
|
|
MAGENTA: "#bd5aa7",
|
|
BROWN: "#8a6224",
|
|
RED: "#ef5452"
|
|
};
|
|
var DEFAULT_STROKES = {
|
|
BLUE: "#2b5c95",
|
|
ORANGE: "#cc6f10",
|
|
GREEN: "#1e652e",
|
|
CYAN: "#18859e",
|
|
YELLOW: "#a69400",
|
|
VIOLET: "#603c88",
|
|
GRAY: "#575757",
|
|
MAGENTA: "#7d2f6d",
|
|
BROWN: "#4f3508",
|
|
RED: "#a82529"
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/themes/chartTheme.ts
|
|
var DEFAULT_BACKGROUND_FILL = "white";
|
|
var PRESET_OVERRIDES_TYPES = {
|
|
"radial-gauge": true,
|
|
"linear-gauge": true
|
|
};
|
|
function hasUserOptionLessThan1(key) {
|
|
return {
|
|
$some: [
|
|
{
|
|
$and: [
|
|
{
|
|
$or: [
|
|
{ $isSeriesType: "line" },
|
|
{ $isSeriesType: "scatter" },
|
|
{ $isSeriesType: "area" },
|
|
{ $isSeriesType: "radar" },
|
|
{ $isSeriesType: "rangeArea" }
|
|
]
|
|
},
|
|
{
|
|
$isUserOption: [
|
|
`/series/$index/${key}`,
|
|
{ $lessThan: [{ $path: `/series/$index/${key}` }, 1] },
|
|
false
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{ $path: "/series" }
|
|
]
|
|
};
|
|
}
|
|
function isPresetOverridesType(type) {
|
|
return PRESET_OVERRIDES_TYPES[type] === true;
|
|
}
|
|
var _ChartTheme = class _ChartTheme {
|
|
static getDefaultColors() {
|
|
return {
|
|
fills: DEFAULT_FILLS,
|
|
fillsFallback: Object.values(DEFAULT_FILLS),
|
|
strokes: DEFAULT_STROKES,
|
|
sequentialColors: getSequentialColors(DEFAULT_FILLS),
|
|
divergingColors: [DEFAULT_FILLS.ORANGE, DEFAULT_FILLS.YELLOW, DEFAULT_FILLS.GREEN],
|
|
hierarchyColors: ["#fff", "#e0e5ea", "#c1ccd5", "#a3b4c1", "#859cad"],
|
|
secondSequentialColors: Color.interpolate(
|
|
[
|
|
Color.fromHexString(DEFAULT_FILLS.BLUE),
|
|
Color.fromHexString("#cbdef5")
|
|
// TODO: Color.lighten(DEFAULT_FILLS.BLUE, ?)
|
|
],
|
|
8
|
|
).map((color2) => color2.toString()),
|
|
secondDivergingColors: [DEFAULT_FILLS.GREEN, DEFAULT_FILLS.YELLOW, DEFAULT_FILLS.RED],
|
|
secondHierarchyColors: ["#fff", "#c5cbd1", "#a4b1bd", "#8498a9", "#648096"],
|
|
up: { fill: DEFAULT_FILLS.GREEN, stroke: DEFAULT_STROKES.GREEN },
|
|
down: { fill: DEFAULT_FILLS.RED, stroke: DEFAULT_STROKES.RED },
|
|
neutral: { fill: DEFAULT_FILLS.GRAY, stroke: DEFAULT_STROKES.GRAY },
|
|
altUp: { fill: DEFAULT_FILLS.BLUE, stroke: DEFAULT_STROKES.BLUE },
|
|
altDown: { fill: DEFAULT_FILLS.ORANGE, stroke: DEFAULT_STROKES.ORANGE },
|
|
altNeutral: { fill: DEFAULT_FILLS.GRAY, stroke: DEFAULT_STROKES.GRAY }
|
|
};
|
|
}
|
|
static getDefaultPublicParameters() {
|
|
return {
|
|
accentColor: "#2196f3",
|
|
axisColor: { $foregroundBackgroundMix: 0.325 },
|
|
backgroundColor: DEFAULT_BACKGROUND_FILL,
|
|
borderColor: { $foregroundOpacity: 0.15 },
|
|
borderRadius: 4,
|
|
chartBackgroundColor: { $ref: "backgroundColor" },
|
|
chartPadding: 20,
|
|
focusShadow: "0 0 0 3px var(--ag-charts-accent-color)",
|
|
foregroundColor: "#181d1f",
|
|
fontFamily: "Verdana, sans-serif",
|
|
fontSize: BASE_FONT_SIZE,
|
|
fontWeight: 400,
|
|
gridLineColor: { $foregroundBackgroundMix: 0.1 },
|
|
popupShadow: "0 0 16px rgba(0, 0, 0, 0.15)",
|
|
subtleTextColor: { $mix: [{ $ref: "textColor" }, { $ref: "chartBackgroundColor" }, 0.38] },
|
|
textColor: { $ref: "foregroundColor" },
|
|
separationLinesColor: { $foregroundBackgroundMix: 0.17 },
|
|
chromeBackgroundColor: { $foregroundBackgroundMix: 0.02 },
|
|
chromeFontFamily: { $ref: "fontFamily" },
|
|
chromeFontSize: { $ref: "fontSize" },
|
|
chromeFontWeight: { $ref: "fontWeight" },
|
|
chromeTextColor: { $ref: "foregroundColor" },
|
|
chromeSubtleTextColor: { $mix: [{ $ref: "chromeTextColor" }, { $ref: "backgroundColor" }, 0.38] },
|
|
buttonBackgroundColor: { $ref: "backgroundColor" },
|
|
buttonBorder: true,
|
|
buttonFontWeight: 400,
|
|
buttonTextColor: { $ref: "textColor" },
|
|
inputBackgroundColor: { $ref: "backgroundColor" },
|
|
inputBorder: true,
|
|
inputTextColor: { $ref: "textColor" },
|
|
menuBackgroundColor: { $ref: "chromeBackgroundColor" },
|
|
menuBorder: true,
|
|
menuTextColor: { $ref: "chromeTextColor" },
|
|
panelBackgroundColor: { $ref: "chromeBackgroundColor" },
|
|
panelSubtleTextColor: { $ref: "chromeSubtleTextColor" },
|
|
tooltipBackgroundColor: { $ref: "chromeBackgroundColor" },
|
|
tooltipBorder: true,
|
|
tooltipTextColor: { $ref: "chromeTextColor" },
|
|
tooltipSubtleTextColor: { $ref: "chromeSubtleTextColor" },
|
|
crosshairLabelBackgroundColor: { $ref: "foregroundColor" },
|
|
crosshairLabelTextColor: { $ref: "chartBackgroundColor" }
|
|
};
|
|
}
|
|
static getAxisDefaults({ title, time: time3 }) {
|
|
return mergeDefaults(
|
|
title && {
|
|
title: {
|
|
enabled: false,
|
|
text: "Axis Title",
|
|
spacing: 25,
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $rem: FONT_SIZE_RATIO.MEDIUM },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
color: { $ref: "textColor" }
|
|
}
|
|
},
|
|
time3 && {
|
|
parentLevel: {
|
|
enabled: false,
|
|
label: {
|
|
// TODO: { $merge: [{ $path: '../../label' }, { fontWeight: 'bold' }]}
|
|
enabled: { $path: "../../label/enabled" },
|
|
border: {
|
|
enabled: {
|
|
$or: [
|
|
{ $isUserOption: ["../border", true, false] },
|
|
{ $path: "../../../label/border/enabled" }
|
|
]
|
|
},
|
|
strokeWidth: { $path: "../../../label/border/strokeWidth" },
|
|
stroke: { $path: "../../../label/border/stroke" }
|
|
},
|
|
fill: { $path: "../../label/fill" },
|
|
fontSize: { $path: "../../label/fontSize" },
|
|
fontFamily: { $path: "../../label/fontFamily" },
|
|
fontWeight: "bold",
|
|
spacing: { $path: "../../label/spacing" },
|
|
color: { $path: "../../label/color" },
|
|
cornerRadius: { $path: "../../label/cornerRadius" },
|
|
padding: { $path: "../../label/padding" },
|
|
avoidCollisions: { $path: "../../label/avoidCollisions" }
|
|
},
|
|
tick: {
|
|
enabled: { $path: "../../tick/enabled" },
|
|
width: { $path: "../../tick/width" },
|
|
size: { $path: "../../tick/size" },
|
|
stroke: { $path: "../../tick/stroke" }
|
|
}
|
|
}
|
|
},
|
|
{
|
|
label: {
|
|
enabled: true,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
spacing: 11,
|
|
color: { $ref: "textColor" },
|
|
avoidCollisions: true,
|
|
cornerRadius: 4,
|
|
border: {
|
|
enabled: { $isUserOption: ["../border", true, false] },
|
|
strokeWidth: 1,
|
|
stroke: { $foregroundOpacity: 0.08 }
|
|
},
|
|
padding: {
|
|
$if: [
|
|
{ $eq: [{ $path: "./border/enabled" }, true] },
|
|
{ left: 12, right: 12, top: 8, bottom: 8 },
|
|
void 0
|
|
]
|
|
}
|
|
},
|
|
line: {
|
|
enabled: true,
|
|
width: 1,
|
|
stroke: { $ref: "axisColor" }
|
|
},
|
|
tick: {
|
|
enabled: false,
|
|
size: 6,
|
|
width: 1,
|
|
stroke: { $ref: "axisColor" }
|
|
},
|
|
gridLine: {
|
|
enabled: true,
|
|
width: 1,
|
|
style: {
|
|
$apply: [
|
|
{
|
|
fillOpacity: 1,
|
|
stroke: { $ref: "gridLineColor" },
|
|
strokeWidth: { $path: "../../width" },
|
|
lineDash: []
|
|
},
|
|
[
|
|
{
|
|
fillOpacity: 1,
|
|
stroke: { $ref: "gridLineColor" },
|
|
strokeWidth: { $path: "../../width" },
|
|
lineDash: []
|
|
}
|
|
]
|
|
]
|
|
}
|
|
},
|
|
crossLines: {
|
|
$apply: [
|
|
{
|
|
enabled: true,
|
|
fill: { $ref: "foregroundColor" },
|
|
stroke: { $ref: "foregroundColor" },
|
|
fillOpacity: 0.08,
|
|
strokeWidth: 1,
|
|
label: {
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
padding: 5,
|
|
color: { $ref: "textColor" },
|
|
border: {
|
|
enabled: false,
|
|
stroke: { $ref: "foregroundColor" },
|
|
strokeOpacity: 1,
|
|
strokeWidth: { $isUserOption: ["./stroke", 1, 0] }
|
|
}
|
|
}
|
|
},
|
|
void 0,
|
|
// TODO: can we just infer this common path?
|
|
// `axisType` path is relative to the axis that is currently being resolved
|
|
// e.g. `/axes/x/crossLines/[variables]` + `../type` = `/axes/x/type`
|
|
{ $pathString: ["/common/axes/$axisType/crossLines", { axisType: { $path: ["../type"] } }] },
|
|
{
|
|
$pathString: [
|
|
"/$seriesType/axes/$axisType/crossLines",
|
|
{
|
|
seriesType: { $path: ["/series/0/type", "line"] },
|
|
axisType: { $path: ["../type"] }
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
);
|
|
}
|
|
getChartDefaults() {
|
|
return {
|
|
minHeight: 300,
|
|
minWidth: 300,
|
|
background: { visible: true, fill: { $ref: "chartBackgroundColor" } },
|
|
padding: {
|
|
top: { $ref: "chartPadding" },
|
|
right: { $ref: "chartPadding" },
|
|
bottom: { $ref: "chartPadding" },
|
|
left: { $ref: "chartPadding" }
|
|
},
|
|
seriesArea: {
|
|
border: {
|
|
enabled: false,
|
|
stroke: { $ref: "foregroundColor" },
|
|
strokeOpacity: 1,
|
|
strokeWidth: 1
|
|
},
|
|
cornerRadius: 4,
|
|
padding: { $if: [{ $eq: [{ $path: "./border/enabled" }, true] }, 5, 0] }
|
|
},
|
|
keyboard: { enabled: true },
|
|
title: {
|
|
enabled: false,
|
|
text: "Title",
|
|
spacing: { $if: [{ $path: "../subtitle/enabled" }, 10, 20] },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $rem: FONT_SIZE_RATIO.LARGEST },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
color: { $ref: "textColor" },
|
|
wrapping: "hyphenate",
|
|
layoutStyle: DEFAULT_CAPTION_LAYOUT_STYLE,
|
|
textAlign: DEFAULT_CAPTION_ALIGNMENT
|
|
},
|
|
subtitle: {
|
|
enabled: false,
|
|
text: "Subtitle",
|
|
spacing: 20,
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $rem: FONT_SIZE_RATIO.MEDIUM },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
color: { $ref: "subtleTextColor" },
|
|
wrapping: "hyphenate",
|
|
layoutStyle: DEFAULT_CAPTION_LAYOUT_STYLE,
|
|
textAlign: DEFAULT_CAPTION_ALIGNMENT
|
|
},
|
|
footnote: {
|
|
enabled: false,
|
|
text: "Footnote",
|
|
spacing: 20,
|
|
fontSize: { $rem: FONT_SIZE_RATIO.MEDIUM },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "subtleTextColor" },
|
|
wrapping: "hyphenate",
|
|
layoutStyle: DEFAULT_CAPTION_LAYOUT_STYLE,
|
|
textAlign: DEFAULT_CAPTION_ALIGNMENT
|
|
},
|
|
highlight: {
|
|
drawingMode: {
|
|
$if: [
|
|
{
|
|
$or: [
|
|
hasUserOptionLessThan1("highlight/highlightedItem/opacity"),
|
|
hasUserOptionLessThan1("highlight/unhighlightedItem/opacity"),
|
|
hasUserOptionLessThan1("highlight/highlightedSeries/opacity"),
|
|
hasUserOptionLessThan1("highlight/unhighlightedSeries/opacity"),
|
|
hasUserOptionLessThan1("fillOpacity"),
|
|
hasUserOptionLessThan1("marker/fillOpacity")
|
|
]
|
|
},
|
|
"overlap",
|
|
"cutout"
|
|
]
|
|
}
|
|
},
|
|
tooltip: {
|
|
enabled: true,
|
|
darkTheme: IS_DARK_THEME,
|
|
delay: 0,
|
|
pagination: false,
|
|
mode: {
|
|
$if: [
|
|
{
|
|
$or: [
|
|
{
|
|
$and: [
|
|
{ $isChartType: "cartesian" },
|
|
{ $not: { $hasSeriesType: "bubble" } },
|
|
{ $not: { $hasSeriesType: "scatter" } },
|
|
{ $greaterThan: [{ $size: { $path: "/series" } }, 1] },
|
|
{ $lessThan: [{ $size: { $path: "/series" } }, 4] }
|
|
]
|
|
},
|
|
{
|
|
$and: [
|
|
{ $isChartType: "polar" },
|
|
{ $greaterThan: [{ $size: { $path: "/series" } }, 1] },
|
|
{ $lessThan: [{ $size: { $path: "/series" } }, 4] }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
"shared",
|
|
"single"
|
|
]
|
|
}
|
|
},
|
|
overlays: { darkTheme: IS_DARK_THEME },
|
|
listeners: {},
|
|
// TODO: remove this
|
|
series: {
|
|
tooltip: {
|
|
range: {
|
|
$if: [
|
|
{ $eq: [{ $path: ["/tooltip/range", "exact"] }, "area"] },
|
|
"exact",
|
|
{ $path: ["/tooltip/range", "exact"] }
|
|
]
|
|
},
|
|
position: {
|
|
anchorTo: { $path: ["/tooltip/position/anchorTo", "pointer"] },
|
|
placement: { $path: ["/tooltip/position/placement", void 0] },
|
|
xOffset: { $path: ["/tooltip/position/xOffset", 0] },
|
|
yOffset: { $path: ["/tooltip/position/yOffset", 0] }
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
constructor(options = {}) {
|
|
const { overrides, palette, params } = deepClone(options);
|
|
const defaults = this.createChartConfigPerChartType(this.getDefaults());
|
|
const presets = {};
|
|
if (overrides) {
|
|
this.processOverrides(presets, overrides);
|
|
}
|
|
const { fills, strokes, sequentialColors, ...otherColors } = this.getDefaultColors();
|
|
this.palette = deepFreeze(
|
|
mergeDefaults(palette, {
|
|
fills: Object.values(fills),
|
|
strokes: Object.values(strokes),
|
|
sequentialColors: Object.values(sequentialColors),
|
|
...otherColors
|
|
})
|
|
);
|
|
this.paletteType = paletteType(palette);
|
|
this.params = mergeDefaults(params, this.getPublicParameters());
|
|
this.config = deepFreeze(deepClone(defaults));
|
|
this.overrides = deepFreeze(overrides);
|
|
this.presets = deepFreeze(presets);
|
|
}
|
|
processOverrides(presets, overrides) {
|
|
for (const s of moduleRegistry_exports.listModulesByType("series" /* Series */)) {
|
|
const seriesType = s.name;
|
|
const seriesOverrides = overrides[seriesType];
|
|
if (isPresetOverridesType(seriesType)) {
|
|
presets[seriesType] = seriesOverrides;
|
|
delete overrides[seriesType];
|
|
}
|
|
}
|
|
}
|
|
createChartConfigPerChartType(config) {
|
|
var _a;
|
|
for (const chartModule of moduleRegistry_exports.listModulesByType("chart" /* Chart */)) {
|
|
for (const seriesModule of moduleRegistry_exports.listModulesByType("series" /* Series */)) {
|
|
if (seriesModule.chartType !== chartModule.name)
|
|
continue;
|
|
config[_a = seriesModule.name] ?? (config[_a] = chartModule.themeTemplate);
|
|
}
|
|
}
|
|
return config;
|
|
}
|
|
getDefaults() {
|
|
const getOverridesByType = (chartType, seriesTypes) => {
|
|
const result = {};
|
|
const chartTypeDefaults = mergeDefaults(
|
|
{ axes: {} },
|
|
...Array.from(moduleRegistry_exports.listModulesByType("plugin" /* Plugin */), (p) => ({
|
|
[p.name]: p.themeTemplate
|
|
})),
|
|
moduleRegistry_exports.getChartModule(chartType)?.themeTemplate,
|
|
this.getChartDefaults()
|
|
);
|
|
for (const seriesType of seriesTypes) {
|
|
result[seriesType] = mergeDefaults(
|
|
getSeriesThemeTemplate(seriesType),
|
|
result[seriesType] ?? chartTypeDefaults
|
|
);
|
|
const { axes } = result[seriesType];
|
|
for (const axisModule of moduleRegistry_exports.listModulesByType("axis" /* Axis */)) {
|
|
axes[axisModule.name] = mergeDefaults(
|
|
axes[axisModule.name],
|
|
!axisModule.chartType || axisModule.chartType === chartType ? getAxisThemeTemplate(axisModule.name) : null,
|
|
_ChartTheme.axisDefault[axisModule.name]
|
|
);
|
|
}
|
|
if (seriesType === "map-shape-background" || seriesType === "map-line-background") {
|
|
delete result[seriesType].series.tooltip;
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
const seriesModules = [...moduleRegistry_exports.listModulesByType("series" /* Series */)];
|
|
const seriesByChartType = groupBy(seriesModules, (s) => s.chartType || "unknown");
|
|
return mergeDefaults(
|
|
...Object.keys(seriesByChartType).map(
|
|
(chartType) => getOverridesByType(chartType, seriesByChartType[chartType]?.map((s) => s.name) ?? [])
|
|
)
|
|
);
|
|
}
|
|
static applyTemplateTheme(node, _other, params) {
|
|
if (isArray(node)) {
|
|
for (let i = 0; i < node.length; i++) {
|
|
const symbol = node[i];
|
|
if (typeof symbol === "symbol" && params?.has(symbol)) {
|
|
node[i] = params.get(symbol);
|
|
}
|
|
}
|
|
} else {
|
|
for (const name of Object.keys(node)) {
|
|
const value = node[name];
|
|
if (typeof value === "symbol" && params?.has(value)) {
|
|
node[name] = params.get(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
templateTheme(themeTemplate10, clone2 = true) {
|
|
const themeInstance = clone2 ? deepClone(themeTemplate10) : themeTemplate10;
|
|
const params = this.getTemplateParameters();
|
|
jsonWalk(themeInstance, _ChartTheme.applyTemplateTheme, void 0, void 0, params);
|
|
return themeInstance;
|
|
}
|
|
getDefaultColors() {
|
|
return _ChartTheme.getDefaultColors();
|
|
}
|
|
getPublicParameters() {
|
|
return _ChartTheme.getDefaultPublicParameters();
|
|
}
|
|
// Private parameters that are not exposed in the themes API.
|
|
getTemplateParameters() {
|
|
const params = /* @__PURE__ */ new Map();
|
|
params.set(IS_DARK_THEME, false);
|
|
params.set(DEFAULT_SHADOW_COLOUR, "#00000080");
|
|
params.set(DEFAULT_SPARKLINE_CROSSHAIR_STROKE, "#aaa");
|
|
params.set(DEFAULT_CAPTION_LAYOUT_STYLE, "block");
|
|
params.set(DEFAULT_CAPTION_ALIGNMENT, "center");
|
|
params.set(DEFAULT_FIBONACCI_STROKES, [
|
|
"#797b86",
|
|
"#e24c4a",
|
|
"#f49d2d",
|
|
"#65ab58",
|
|
"#409682",
|
|
"#4db9d2",
|
|
"#5090dc",
|
|
"#3068f9",
|
|
"#e24c4a",
|
|
"#913aac",
|
|
"#d93e64"
|
|
]);
|
|
params.set(DEFAULT_POLAR_SERIES_STROKE, DEFAULT_BACKGROUND_FILL);
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR, DEFAULT_FILLS.BLUE);
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL, DEFAULT_FILLS.BLUE);
|
|
params.set(DEFAULT_TEXT_ANNOTATION_COLOR, DEFAULT_FILLS.BLUE);
|
|
params.set(DEFAULT_ANNOTATION_HANDLE_FILL, DEFAULT_BACKGROUND_FILL);
|
|
params.set(DEFAULT_ANNOTATION_STATISTICS_FILL, "#fafafa");
|
|
params.set(DEFAULT_ANNOTATION_STATISTICS_STROKE, "#ddd");
|
|
params.set(DEFAULT_ANNOTATION_STATISTICS_COLOR, "#000");
|
|
params.set(DEFAULT_ANNOTATION_STATISTICS_DIVIDER_STROKE, "#181d1f");
|
|
params.set(DEFAULT_ANNOTATION_STATISTICS_DOWN_FILL, "#e35c5c");
|
|
params.set(DEFAULT_ANNOTATION_STATISTICS_DOWN_STROKE, "#e35c5c");
|
|
params.set(DEFAULT_TEXTBOX_FILL, "#fafafa");
|
|
params.set(DEFAULT_TEXTBOX_STROKE, "#ddd");
|
|
params.set(DEFAULT_TEXTBOX_COLOR, "#000");
|
|
params.set(DEFAULT_TOOLBAR_POSITION, "top");
|
|
const defaultColors = this.getDefaultColors();
|
|
params.set(PALETTE_UP_STROKE, this.palette.up?.stroke ?? defaultColors.up.stroke);
|
|
params.set(PALETTE_UP_FILL, this.palette.up?.fill ?? defaultColors.up.fill);
|
|
params.set(PALETTE_DOWN_STROKE, this.palette.down?.stroke ?? defaultColors.down.stroke);
|
|
params.set(PALETTE_DOWN_FILL, this.palette.down?.fill ?? defaultColors.down.fill);
|
|
params.set(PALETTE_NEUTRAL_STROKE, this.palette.neutral?.stroke ?? defaultColors.neutral.stroke);
|
|
params.set(PALETTE_NEUTRAL_FILL, this.palette.neutral?.fill ?? defaultColors.neutral.fill);
|
|
params.set(PALETTE_ALT_UP_STROKE, this.palette.altUp?.stroke ?? defaultColors.up.stroke);
|
|
params.set(PALETTE_ALT_UP_FILL, this.palette.altUp?.fill ?? defaultColors.up.fill);
|
|
params.set(PALETTE_ALT_DOWN_STROKE, this.palette.altDown?.stroke ?? defaultColors.down.stroke);
|
|
params.set(PALETTE_ALT_DOWN_FILL, this.palette.altDown?.fill ?? defaultColors.down.fill);
|
|
params.set(PALETTE_ALT_NEUTRAL_FILL, this.palette.altNeutral?.fill ?? defaultColors.altNeutral.fill);
|
|
params.set(PALETTE_ALT_NEUTRAL_STROKE, this.palette.altNeutral?.stroke ?? defaultColors.altNeutral.stroke);
|
|
return params;
|
|
}
|
|
};
|
|
_ChartTheme.axisDefault = {
|
|
["number" /* NUMBER */]: _ChartTheme.getAxisDefaults({ title: true, time: false }),
|
|
["log" /* LOG */]: _ChartTheme.getAxisDefaults({ title: true, time: false }),
|
|
["category" /* CATEGORY */]: _ChartTheme.getAxisDefaults({ title: true, time: false }),
|
|
["grouped-category" /* GROUPED_CATEGORY */]: _ChartTheme.getAxisDefaults({ title: true, time: false }),
|
|
["time" /* TIME */]: _ChartTheme.getAxisDefaults({ title: true, time: true }),
|
|
["unit-time" /* UNIT_TIME */]: _ChartTheme.getAxisDefaults({ title: true, time: true }),
|
|
["ordinal-time" /* ORDINAL_TIME */]: _ChartTheme.getAxisDefaults({ title: true, time: true }),
|
|
["angle-category" /* ANGLE_CATEGORY */]: _ChartTheme.getAxisDefaults({ title: false, time: false }),
|
|
["angle-number" /* ANGLE_NUMBER */]: _ChartTheme.getAxisDefaults({ title: false, time: false }),
|
|
["radius-category" /* RADIUS_CATEGORY */]: _ChartTheme.getAxisDefaults({ title: true, time: false }),
|
|
["radius-number" /* RADIUS_NUMBER */]: _ChartTheme.getAxisDefaults({ title: true, time: false })
|
|
};
|
|
var ChartTheme = _ChartTheme;
|
|
function getAxisThemeTemplate(axisType) {
|
|
let themeTemplate10 = moduleRegistry_exports.getAxisModule(axisType)?.themeTemplate ?? {};
|
|
for (const module2 of moduleRegistry_exports.listModulesByType("axis:plugin" /* AxisPlugin */)) {
|
|
if (module2.axisTypes?.includes(axisType) ?? true) {
|
|
themeTemplate10 = mergeDefaults({ [module2.name]: module2.themeTemplate }, themeTemplate10);
|
|
}
|
|
}
|
|
return themeTemplate10;
|
|
}
|
|
function getSeriesThemeTemplate(seriesType) {
|
|
let themeTemplate10 = moduleRegistry_exports.getSeriesModule(seriesType)?.themeTemplate ?? {};
|
|
for (const module2 of moduleRegistry_exports.listModulesByType("series:plugin" /* SeriesPlugin */)) {
|
|
if (module2.seriesTypes?.includes(seriesType) ?? true) {
|
|
themeTemplate10 = mergeDefaults({ series: { [module2.name]: module2.themeTemplate } }, themeTemplate10);
|
|
}
|
|
}
|
|
return themeTemplate10;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/factory/processModuleOptions.ts
|
|
var SkippedModules = /* @__PURE__ */ new Set(["foreground"]);
|
|
function sanitizeThemeModules(theme) {
|
|
const missingModules = /* @__PURE__ */ new Map();
|
|
for (const [name, { type }] of ExpectedModules) {
|
|
if (moduleRegistry_exports.hasModule(name))
|
|
continue;
|
|
if (missingModules.has(type)) {
|
|
missingModules.get(type).add(name);
|
|
} else {
|
|
missingModules.set(type, /* @__PURE__ */ new Set([name]));
|
|
}
|
|
}
|
|
if (missingModules.size === 0)
|
|
return theme;
|
|
function prunePlugins(target) {
|
|
const missingPlugins = missingModules.get("plugin" /* Plugin */);
|
|
if (!isObject(target) || !missingPlugins)
|
|
return;
|
|
for (const pluginName of missingPlugins) {
|
|
if (pluginName in target && target[pluginName].enabled !== true) {
|
|
delete target[pluginName];
|
|
}
|
|
}
|
|
}
|
|
function pruneSeriesPlugins(target) {
|
|
const missingSeriesPlugins = missingModules.get("series:plugin" /* SeriesPlugin */);
|
|
if (!isObject(target) || !missingSeriesPlugins)
|
|
return;
|
|
for (const pluginName of missingSeriesPlugins) {
|
|
if (pluginName in target) {
|
|
delete target[pluginName];
|
|
}
|
|
}
|
|
}
|
|
function pruneAxisPlugins(target) {
|
|
const missingAxisPlugins = missingModules.get("axis:plugin" /* AxisPlugin */);
|
|
if (!isObject(target) || !missingAxisPlugins)
|
|
return;
|
|
for (const pluginName of missingAxisPlugins) {
|
|
if (pluginName in target && target[pluginName].enabled !== true) {
|
|
delete target[pluginName];
|
|
}
|
|
}
|
|
}
|
|
function pruneAxes(axes) {
|
|
if (!isObject(axes))
|
|
return;
|
|
for (const axisName of Object.keys(axes)) {
|
|
if (missingModules.get("axis" /* Axis */)?.has(axisName)) {
|
|
delete axes[axisName];
|
|
continue;
|
|
}
|
|
pruneAxisPlugins(axes[axisName]);
|
|
}
|
|
}
|
|
function pruneSeriesEntry(entry) {
|
|
if (!isObject(entry))
|
|
return;
|
|
pruneAxes(entry.axes);
|
|
prunePlugins(entry);
|
|
pruneSeriesPlugins(entry.series);
|
|
}
|
|
const config = deepClone(theme.config);
|
|
const overrides = deepClone(theme.overrides);
|
|
const presets = deepClone(theme.presets);
|
|
for (const seriesType of Object.keys(config)) {
|
|
if (missingModules.get("series" /* Series */)?.has(seriesType)) {
|
|
delete config[seriesType];
|
|
continue;
|
|
}
|
|
pruneSeriesEntry(config[seriesType]);
|
|
}
|
|
if (isObject(overrides)) {
|
|
const overridesObj = overrides;
|
|
if (isObject(overridesObj.common)) {
|
|
pruneAxes(overridesObj.common.axes);
|
|
prunePlugins(overridesObj.common);
|
|
}
|
|
for (const seriesType of Object.keys(overridesObj)) {
|
|
if (seriesType === "common")
|
|
continue;
|
|
if (missingModules.get("series" /* Series */)?.has(seriesType)) {
|
|
delete overridesObj[seriesType];
|
|
continue;
|
|
}
|
|
pruneSeriesEntry(overridesObj[seriesType]);
|
|
}
|
|
}
|
|
if (isObject(presets)) {
|
|
const presetsObj = presets;
|
|
for (const presetName of Object.keys(presetsObj)) {
|
|
if (missingModules.get("preset" /* Preset */)?.has(presetName) || missingModules.get("series" /* Series */)?.has(presetName)) {
|
|
delete presetsObj[presetName];
|
|
continue;
|
|
}
|
|
prunePlugins(presetsObj[presetName]);
|
|
pruneAxes(presetsObj[presetName]?.axes);
|
|
}
|
|
}
|
|
return Object.create(theme, {
|
|
config: { value: deepFreeze(config), enumerable: true },
|
|
overrides: { value: isObject(overrides) ? deepFreeze(overrides) : overrides, enumerable: true },
|
|
presets: { value: isObject(presets) ? deepFreeze(presets) : presets, enumerable: true }
|
|
});
|
|
}
|
|
function processModuleOptions(chartType, options, additionalMissingModules) {
|
|
const missingModules = unique(removeUnregisteredModuleOptions(chartType, options).concat(additionalMissingModules));
|
|
if (!missingModules.length)
|
|
return;
|
|
const installationReferenceUrl = moduleRegistry_exports.isIntegrated() ? "https://www.ag-grid.com/data-grid/integrated-charts-installation/" : "https://www.ag-grid.com/charts/r/module-registry/";
|
|
const missingOptions = groupBy(missingModules, (module2) => module2.enterprise ? "enterprise" : "community");
|
|
if (missingModules.length) {
|
|
const packageName = moduleRegistry_exports.isEnterprise() || missingOptions.enterprise?.length ? "enterprise" : "community";
|
|
logger_exports.errorOnce(
|
|
[
|
|
"required modules are not registered. Check if you have registered the modules:",
|
|
"",
|
|
moduleRegistry_exports.isUmd() ? `Install and register 'ag-charts-enterprise' before creating the chart.` : createRegistrySnippet(missingModules.map(formatMissingModuleName), packageName),
|
|
"",
|
|
`See ${installationReferenceUrl} for more details.`
|
|
].join("\n")
|
|
);
|
|
}
|
|
}
|
|
function formatMissingModuleName(module2) {
|
|
return module2.moduleId ?? module2.name;
|
|
}
|
|
function formatImportItem(name) {
|
|
return ` ${name},`;
|
|
}
|
|
function formatImports(imports, packageName) {
|
|
return imports.length ? `import {
|
|
${imports.map(formatImportItem).join("\n")}
|
|
} from 'ag-charts-${packageName}';` : null;
|
|
}
|
|
function createRegistrySnippet(moduleNames, packageName) {
|
|
const imports = formatImports(["ModuleRegistry"].concat(moduleNames), packageName);
|
|
const moduleList = moduleNames.map(formatImportItem).join("\n");
|
|
return `${imports}
|
|
|
|
ModuleRegistry.registerModules([
|
|
${moduleList}
|
|
]);`;
|
|
}
|
|
function removeUnregisteredModuleOptions(chartType, options) {
|
|
const missingModules = /* @__PURE__ */ new Map();
|
|
const optionsAxes = "axes" in options && isObject(options.axes) ? options.axes : {};
|
|
const axisTypesInOptions = new Set(
|
|
Object.values(optionsAxes).map((axis) => axis?.type).filter(isDefined)
|
|
);
|
|
const seriesTypesInOptions = new Set(options.series?.map((series) => series.type).filter(isDefined));
|
|
function addMissingModule(module2) {
|
|
missingModules.set(module2.name, module2);
|
|
}
|
|
for (const module2 of ExpectedModules.values()) {
|
|
if (moduleRegistry_exports.hasModule(module2.name))
|
|
continue;
|
|
if (SkippedModules.has(module2.name))
|
|
continue;
|
|
if (chartType && module2.chartType && chartType !== module2.chartType)
|
|
continue;
|
|
switch (module2.type) {
|
|
case "chart":
|
|
break;
|
|
case "axis":
|
|
if (axisTypesInOptions.has(module2.name)) {
|
|
for (const key of Object.keys(optionsAxes)) {
|
|
if (optionsAxes?.[key].type === module2.name) {
|
|
delete optionsAxes[key];
|
|
}
|
|
}
|
|
addMissingModule(module2);
|
|
}
|
|
break;
|
|
case "series":
|
|
if (seriesTypesInOptions.has(module2.name)) {
|
|
options.series = options.series.filter((series) => series.type !== module2.name);
|
|
addMissingModule(module2);
|
|
}
|
|
break;
|
|
case "plugin":
|
|
const optionsKey = module2.name;
|
|
const pluginValue = options[optionsKey];
|
|
if (isObject(pluginValue)) {
|
|
if (pluginValue.enabled !== false) {
|
|
addMissingModule(module2);
|
|
}
|
|
delete options[optionsKey];
|
|
}
|
|
break;
|
|
case "axis:plugin":
|
|
for (const axis of Object.values(optionsAxes)) {
|
|
const axisModuleKey = module2.name;
|
|
if (axis?.[axisModuleKey]) {
|
|
if (axis[axisModuleKey].enabled !== false) {
|
|
addMissingModule(module2);
|
|
}
|
|
delete axis[axisModuleKey];
|
|
}
|
|
}
|
|
break;
|
|
case "series:plugin":
|
|
for (const series of options.series ?? []) {
|
|
if (series[module2.name]) {
|
|
delete series[module2.name];
|
|
addMissingModule(module2);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
for (const seriesType of seriesTypesInOptions) {
|
|
const expectedSeriesModule = ExpectedModules.get(seriesType);
|
|
if (expectedSeriesModule?.type === "series" /* Series */ && !moduleRegistry_exports.hasModule(expectedSeriesModule.name) && !missingModules.has(expectedSeriesModule.name)) {
|
|
options.series = options.series.filter((series) => series.type !== expectedSeriesModule.name);
|
|
addMissingModule(expectedSeriesModule);
|
|
}
|
|
}
|
|
return Array.from(missingModules.values());
|
|
}
|
|
function removeIncompatibleModuleOptions(chartType, options) {
|
|
const hasAxesOptions = "axes" in options && isObject(options.axes);
|
|
const hasSeriesOptions = "series" in options && isArray(options.series);
|
|
const matchChartType = (module2) => chartType == null || !module2.chartType || module2.chartType === chartType;
|
|
const incompatibleModules = [];
|
|
for (const module2 of moduleRegistry_exports.listModules()) {
|
|
if (moduleRegistry_exports.isModuleType("plugin" /* Plugin */, module2)) {
|
|
if (!matchChartType(module2)) {
|
|
delete options[module2.name];
|
|
incompatibleModules.push(module2.name);
|
|
}
|
|
} else if (moduleRegistry_exports.isModuleType("axis:plugin" /* AxisPlugin */, module2)) {
|
|
if (hasAxesOptions && !matchChartType(module2)) {
|
|
for (const axis of Object.values(options.axes)) {
|
|
delete axis[module2.name];
|
|
}
|
|
incompatibleModules.push(module2.name);
|
|
}
|
|
} else if (moduleRegistry_exports.isModuleType("series:plugin" /* SeriesPlugin */, module2)) {
|
|
if (hasSeriesOptions && !matchChartType(module2)) {
|
|
for (const series of options.series) {
|
|
delete series[module2.name];
|
|
}
|
|
incompatibleModules.push(module2.name);
|
|
}
|
|
}
|
|
}
|
|
return incompatibleModules;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/themes/darkTheme.ts
|
|
var DEFAULT_DARK_BACKGROUND_FILL = "#192232";
|
|
var DEFAULT_DARK_FILLS = {
|
|
BLUE: "#5090dc",
|
|
ORANGE: "#ffa03a",
|
|
GREEN: "#459d55",
|
|
CYAN: "#34bfe1",
|
|
YELLOW: "#e1cc00",
|
|
VIOLET: "#9669cb",
|
|
GRAY: "#b5b5b5",
|
|
MAGENTA: "#bd5aa7",
|
|
BROWN: "#8a6224",
|
|
RED: "#ef5452"
|
|
};
|
|
var DEFAULT_DARK_STROKES = {
|
|
BLUE: "#74a8e6",
|
|
ORANGE: "#ffbe70",
|
|
GREEN: "#6cb176",
|
|
CYAN: "#75d4ef",
|
|
YELLOW: "#f6e559",
|
|
VIOLET: "#aa86d8",
|
|
GRAY: "#a1a1a1",
|
|
MAGENTA: "#ce7ab9",
|
|
BROWN: "#997b52",
|
|
RED: "#ff7872"
|
|
};
|
|
var DarkTheme = class extends ChartTheme {
|
|
getDefaultColors() {
|
|
return {
|
|
fills: DEFAULT_DARK_FILLS,
|
|
fillsFallback: Object.values(DEFAULT_DARK_FILLS),
|
|
strokes: DEFAULT_DARK_STROKES,
|
|
sequentialColors: getSequentialColors(DEFAULT_DARK_FILLS),
|
|
divergingColors: [DEFAULT_DARK_FILLS.ORANGE, DEFAULT_DARK_FILLS.YELLOW, DEFAULT_DARK_FILLS.GREEN],
|
|
hierarchyColors: ["#192834", "#253746", "#324859", "#3f596c", "#4d6a80"],
|
|
secondSequentialColors: [
|
|
"#5090dc",
|
|
"#4882c6",
|
|
"#4073b0",
|
|
"#38659a",
|
|
"#305684",
|
|
"#28486e",
|
|
"#203a58",
|
|
"#182b42"
|
|
],
|
|
secondDivergingColors: [DEFAULT_DARK_FILLS.GREEN, DEFAULT_DARK_FILLS.YELLOW, DEFAULT_DARK_FILLS.RED],
|
|
secondHierarchyColors: ["#192834", "#3b5164", "#496275", "#577287", "#668399"],
|
|
up: { fill: DEFAULT_DARK_FILLS.GREEN, stroke: DEFAULT_DARK_STROKES.GREEN },
|
|
down: { fill: DEFAULT_DARK_FILLS.RED, stroke: DEFAULT_DARK_STROKES.RED },
|
|
neutral: { fill: DEFAULT_DARK_FILLS.GRAY, stroke: DEFAULT_DARK_STROKES.GRAY },
|
|
altUp: { fill: DEFAULT_DARK_FILLS.BLUE, stroke: DEFAULT_DARK_STROKES.BLUE },
|
|
altDown: { fill: DEFAULT_DARK_FILLS.ORANGE, stroke: DEFAULT_DARK_STROKES.ORANGE },
|
|
altNeutral: { fill: DEFAULT_DARK_FILLS.GRAY, stroke: DEFAULT_DARK_STROKES.GRAY }
|
|
};
|
|
}
|
|
getPublicParameters() {
|
|
return {
|
|
...super.getPublicParameters(),
|
|
axisColor: { $foregroundBackgroundMix: 0.737 },
|
|
backgroundColor: DEFAULT_DARK_BACKGROUND_FILL,
|
|
borderColor: { $foregroundBackgroundMix: 0.216 },
|
|
chromeBackgroundColor: { $foregroundBackgroundMix: 0.07 },
|
|
foregroundColor: "#fff",
|
|
gridLineColor: { $foregroundBackgroundMix: 0.257 },
|
|
popupShadow: "0 0 16px rgba(0, 0, 0, 0.33)",
|
|
subtleTextColor: { $mix: [{ $ref: "textColor" }, { $ref: "chartBackgroundColor" }, 0.57] },
|
|
separationLinesColor: { $foregroundBackgroundMix: 0.44 },
|
|
crosshairLabelBackgroundColor: { $foregroundBackgroundMix: 0.65 }
|
|
};
|
|
}
|
|
getTemplateParameters() {
|
|
const params = super.getTemplateParameters();
|
|
params.set(IS_DARK_THEME, true);
|
|
params.set(DEFAULT_POLAR_SERIES_STROKE, DEFAULT_DARK_BACKGROUND_FILL);
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR, DEFAULT_DARK_FILLS.BLUE);
|
|
params.set(DEFAULT_TEXT_ANNOTATION_COLOR, "#fff");
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL, DEFAULT_DARK_FILLS.BLUE);
|
|
params.set(DEFAULT_ANNOTATION_HANDLE_FILL, DEFAULT_DARK_BACKGROUND_FILL);
|
|
params.set(DEFAULT_ANNOTATION_STATISTICS_FILL, "#28313e");
|
|
params.set(DEFAULT_ANNOTATION_STATISTICS_STROKE, "#4b525d");
|
|
params.set(DEFAULT_ANNOTATION_STATISTICS_COLOR, "#fff");
|
|
params.set(DEFAULT_ANNOTATION_STATISTICS_DIVIDER_STROKE, "#fff");
|
|
params.set(DEFAULT_TEXTBOX_FILL, "#28313e");
|
|
params.set(DEFAULT_TEXTBOX_STROKE, "#4b525d");
|
|
params.set(DEFAULT_TEXTBOX_COLOR, "#fff");
|
|
return params;
|
|
}
|
|
constructor(options) {
|
|
super(options);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/themes/financialDark.ts
|
|
var FINANCIAL_DARK_FILLS = {
|
|
GREEN: "#089981",
|
|
RED: "#F23645",
|
|
BLUE: "#5090dc",
|
|
GRAY: "#A9A9A9"
|
|
};
|
|
var FINANCIAL_DARK_STROKES = {
|
|
GREEN: "#089981",
|
|
RED: "#F23645",
|
|
BLUE: "#5090dc",
|
|
GRAY: "#909090"
|
|
};
|
|
var FinancialDark = class extends DarkTheme {
|
|
getDefaultColors() {
|
|
return {
|
|
...super.getDefaultColors(),
|
|
fills: { ...FINANCIAL_DARK_FILLS },
|
|
fillsFallback: Object.values({ ...FINANCIAL_DARK_FILLS }),
|
|
strokes: { ...FINANCIAL_DARK_STROKES },
|
|
sequentialColors: getSequentialColors(FINANCIAL_DARK_FILLS),
|
|
divergingColors: [FINANCIAL_DARK_FILLS.GREEN, FINANCIAL_DARK_FILLS.BLUE, FINANCIAL_DARK_FILLS.RED],
|
|
// hierarchyColors: [],
|
|
secondSequentialColors: [
|
|
"#5090dc",
|
|
"#4882c6",
|
|
"#4073b0",
|
|
"#38659a",
|
|
"#305684",
|
|
"#28486e",
|
|
"#203a58",
|
|
"#182b42"
|
|
],
|
|
// secondDivergingColors: [],
|
|
// secondHierarchyColors: [],
|
|
up: { fill: FINANCIAL_DARK_FILLS.GREEN, stroke: FINANCIAL_DARK_STROKES.GREEN },
|
|
down: { fill: FINANCIAL_DARK_FILLS.RED, stroke: FINANCIAL_DARK_STROKES.RED },
|
|
neutral: { fill: FINANCIAL_DARK_FILLS.BLUE, stroke: FINANCIAL_DARK_STROKES.BLUE },
|
|
altUp: { fill: FINANCIAL_DARK_FILLS.GREEN, stroke: FINANCIAL_DARK_STROKES.GREEN },
|
|
altDown: { fill: FINANCIAL_DARK_FILLS.RED, stroke: FINANCIAL_DARK_STROKES.RED },
|
|
altNeutral: { fill: FINANCIAL_DARK_FILLS.GRAY, stroke: FINANCIAL_DARK_STROKES.GRAY }
|
|
};
|
|
}
|
|
getPublicParameters() {
|
|
return {
|
|
...super.getPublicParameters(),
|
|
chartPadding: 0,
|
|
gridLineColor: { $foregroundBackgroundMix: 0.12 }
|
|
};
|
|
}
|
|
getTemplateParameters() {
|
|
const params = super.getTemplateParameters();
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR, FINANCIAL_DARK_FILLS.BLUE);
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL, FINANCIAL_DARK_FILLS.BLUE);
|
|
params.set(DEFAULT_CAPTION_LAYOUT_STYLE, "overlay");
|
|
params.set(DEFAULT_CAPTION_ALIGNMENT, "left");
|
|
params.set(DEFAULT_TOOLBAR_POSITION, "bottom");
|
|
return params;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/themes/financialLight.ts
|
|
var FINANCIAL_LIGHT_FILLS = {
|
|
GREEN: "#089981",
|
|
RED: "#F23645",
|
|
BLUE: "#5090dc",
|
|
GRAY: "#A9A9A9"
|
|
};
|
|
var FINANCIAL_LIGHT_STROKES = {
|
|
GREEN: "#089981",
|
|
RED: "#F23645",
|
|
BLUE: "#5090dc",
|
|
GRAY: "#909090"
|
|
};
|
|
var FinancialLight = class extends ChartTheme {
|
|
getDefaultColors() {
|
|
return {
|
|
...super.getDefaultColors(),
|
|
fills: { ...FINANCIAL_LIGHT_FILLS },
|
|
fillsFallback: Object.values({ ...FINANCIAL_LIGHT_FILLS }),
|
|
strokes: { ...FINANCIAL_LIGHT_STROKES },
|
|
sequentialColors: getSequentialColors(FINANCIAL_LIGHT_FILLS),
|
|
divergingColors: [FINANCIAL_LIGHT_FILLS.GREEN, FINANCIAL_LIGHT_FILLS.BLUE, FINANCIAL_LIGHT_FILLS.RED],
|
|
// hierarchyColors: [],
|
|
// secondSequentialColors: [],
|
|
// secondDivergingColors: [],
|
|
// secondHierarchyColors: [],
|
|
up: { fill: FINANCIAL_LIGHT_FILLS.GREEN, stroke: FINANCIAL_LIGHT_STROKES.GREEN },
|
|
down: { fill: FINANCIAL_LIGHT_FILLS.RED, stroke: FINANCIAL_LIGHT_STROKES.RED },
|
|
neutral: { fill: FINANCIAL_LIGHT_FILLS.BLUE, stroke: FINANCIAL_LIGHT_STROKES.BLUE },
|
|
altUp: { fill: FINANCIAL_LIGHT_FILLS.GREEN, stroke: FINANCIAL_LIGHT_STROKES.GREEN },
|
|
altDown: { fill: FINANCIAL_LIGHT_FILLS.RED, stroke: FINANCIAL_LIGHT_STROKES.RED },
|
|
altNeutral: { fill: FINANCIAL_LIGHT_FILLS.GRAY, stroke: FINANCIAL_LIGHT_STROKES.GRAY }
|
|
};
|
|
}
|
|
getPublicParameters() {
|
|
return {
|
|
...super.getPublicParameters(),
|
|
chartPadding: 0,
|
|
gridLineColor: { $foregroundBackgroundMix: 0.06 }
|
|
};
|
|
}
|
|
getTemplateParameters() {
|
|
const params = super.getTemplateParameters();
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR, FINANCIAL_LIGHT_FILLS.BLUE);
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL, FINANCIAL_LIGHT_FILLS.BLUE);
|
|
params.set(DEFAULT_CAPTION_LAYOUT_STYLE, "overlay");
|
|
params.set(DEFAULT_CAPTION_ALIGNMENT, "left");
|
|
params.set(DEFAULT_TOOLBAR_POSITION, "bottom");
|
|
return params;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/themes/materialDark.ts
|
|
var MATERIAL_DARK_FILLS = {
|
|
BLUE: "#2196F3",
|
|
ORANGE: "#FF9800",
|
|
GREEN: "#4CAF50",
|
|
CYAN: "#00BCD4",
|
|
YELLOW: "#FFEB3B",
|
|
VIOLET: "#7E57C2",
|
|
GRAY: "#9E9E9E",
|
|
MAGENTA: "#F06292",
|
|
BROWN: "#795548",
|
|
RED: "#F44336"
|
|
};
|
|
var MATERIAL_DARK_STROKES = {
|
|
BLUE: "#90CAF9",
|
|
ORANGE: "#FFCC80",
|
|
GREEN: "#A5D6A7",
|
|
CYAN: "#80DEEA",
|
|
YELLOW: "#FFF9C4",
|
|
VIOLET: "#B39DDB",
|
|
GRAY: "#E0E0E0",
|
|
MAGENTA: "#F48FB1",
|
|
BROWN: "#A1887F",
|
|
RED: "#EF9A9A"
|
|
};
|
|
var MaterialDark = class extends DarkTheme {
|
|
getDefaultColors() {
|
|
return {
|
|
...super.getDefaultColors(),
|
|
fills: MATERIAL_DARK_FILLS,
|
|
fillsFallback: Object.values(MATERIAL_DARK_FILLS),
|
|
strokes: MATERIAL_DARK_STROKES,
|
|
sequentialColors: getSequentialColors(MATERIAL_DARK_FILLS),
|
|
divergingColors: [MATERIAL_DARK_FILLS.ORANGE, MATERIAL_DARK_FILLS.YELLOW, MATERIAL_DARK_FILLS.GREEN],
|
|
// hierarchyColors: [],
|
|
secondSequentialColors: [
|
|
"#2196f3",
|
|
// 500
|
|
"#208FEC",
|
|
// (interpolated)
|
|
"#1E88E5",
|
|
// 600
|
|
"#1C7FDC",
|
|
// (interpolated)
|
|
"#1976d2",
|
|
// 700
|
|
"#176EC9",
|
|
// (interpolated)
|
|
"#1565c0"
|
|
// 800
|
|
],
|
|
secondDivergingColors: [MATERIAL_DARK_FILLS.GREEN, MATERIAL_DARK_FILLS.YELLOW, MATERIAL_DARK_FILLS.RED],
|
|
// secondHierarchyColors: [],
|
|
up: { fill: MATERIAL_DARK_FILLS.GREEN, stroke: MATERIAL_DARK_STROKES.GREEN },
|
|
down: { fill: MATERIAL_DARK_FILLS.RED, stroke: MATERIAL_DARK_STROKES.RED },
|
|
neutral: { fill: MATERIAL_DARK_FILLS.GRAY, stroke: MATERIAL_DARK_STROKES.GRAY },
|
|
altUp: { fill: MATERIAL_DARK_FILLS.BLUE, stroke: MATERIAL_DARK_STROKES.BLUE },
|
|
altDown: { fill: MATERIAL_DARK_FILLS.RED, stroke: MATERIAL_DARK_STROKES.RED },
|
|
altNeutral: { fill: MATERIAL_DARK_FILLS.GRAY, stroke: MATERIAL_DARK_STROKES.GRAY }
|
|
};
|
|
}
|
|
getTemplateParameters() {
|
|
const params = super.getTemplateParameters();
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR, MATERIAL_DARK_FILLS.BLUE);
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL, MATERIAL_DARK_FILLS.BLUE);
|
|
return params;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/themes/materialLight.ts
|
|
var MATERIAL_LIGHT_FILLS = {
|
|
BLUE: "#2196F3",
|
|
ORANGE: "#FF9800",
|
|
GREEN: "#4CAF50",
|
|
CYAN: "#00BCD4",
|
|
YELLOW: "#FFEB3B",
|
|
VIOLET: "#7E57C2",
|
|
GRAY: "#9E9E9E",
|
|
MAGENTA: "#F06292",
|
|
BROWN: "#795548",
|
|
RED: "#F44336"
|
|
};
|
|
var MATERIAL_LIGHT_STROKES = {
|
|
BLUE: "#1565C0",
|
|
ORANGE: "#E65100",
|
|
GREEN: "#2E7D32",
|
|
CYAN: "#00838F",
|
|
YELLOW: "#F9A825",
|
|
VIOLET: "#4527A0",
|
|
GRAY: "#616161",
|
|
MAGENTA: "#C2185B",
|
|
BROWN: "#4E342E",
|
|
RED: "#B71C1C"
|
|
};
|
|
var MaterialLight = class extends ChartTheme {
|
|
getDefaultColors() {
|
|
return {
|
|
...super.getDefaultColors(),
|
|
fills: MATERIAL_LIGHT_FILLS,
|
|
fillsFallback: Object.values(MATERIAL_LIGHT_FILLS),
|
|
strokes: MATERIAL_LIGHT_STROKES,
|
|
sequentialColors: getSequentialColors(MATERIAL_LIGHT_FILLS),
|
|
divergingColors: [MATERIAL_LIGHT_FILLS.ORANGE, MATERIAL_LIGHT_FILLS.YELLOW, MATERIAL_LIGHT_FILLS.GREEN],
|
|
// hierarchyColors: [],
|
|
secondSequentialColors: [
|
|
"#2196f3",
|
|
// 500
|
|
"#329EF4",
|
|
// (interpolated)
|
|
"#42a5f5",
|
|
// 400
|
|
"#53ADF6",
|
|
// (interpolated)
|
|
"#64b5f6",
|
|
// 300
|
|
"#7AC0F8",
|
|
// (interpolated)
|
|
"#90caf9"
|
|
// 200
|
|
],
|
|
secondDivergingColors: [MATERIAL_LIGHT_FILLS.GREEN, MATERIAL_LIGHT_FILLS.YELLOW, MATERIAL_LIGHT_FILLS.RED],
|
|
// secondHierarchyColors: [],
|
|
up: { fill: MATERIAL_LIGHT_FILLS.GREEN, stroke: MATERIAL_LIGHT_STROKES.GREEN },
|
|
down: { fill: MATERIAL_LIGHT_FILLS.RED, stroke: MATERIAL_LIGHT_STROKES.RED },
|
|
neutral: { fill: MATERIAL_LIGHT_FILLS.GRAY, stroke: MATERIAL_LIGHT_STROKES.GRAY },
|
|
altUp: { fill: MATERIAL_LIGHT_FILLS.BLUE, stroke: MATERIAL_LIGHT_STROKES.BLUE },
|
|
altDown: { fill: MATERIAL_LIGHT_FILLS.RED, stroke: MATERIAL_LIGHT_STROKES.RED },
|
|
altNeutral: { fill: MATERIAL_LIGHT_FILLS.GRAY, stroke: MATERIAL_LIGHT_STROKES.GRAY }
|
|
};
|
|
}
|
|
getTemplateParameters() {
|
|
const params = super.getTemplateParameters();
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR, MATERIAL_LIGHT_FILLS.BLUE);
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL, MATERIAL_LIGHT_FILLS.BLUE);
|
|
return params;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/themes/polychromaDark.ts
|
|
var POLYCHROMA_DARK_FILLS = {
|
|
BLUE: "#436ff4",
|
|
PURPLE: "#9a7bff",
|
|
MAGENTA: "#d165d2",
|
|
PINK: "#f0598b",
|
|
RED: "#f47348",
|
|
ORANGE: "#f2a602",
|
|
YELLOW: "#e9e201",
|
|
GREEN: "#21b448",
|
|
CYAN: "#00b9a2",
|
|
MODERATE_BLUE: "#00aee4",
|
|
GRAY: "#bbbbbb"
|
|
};
|
|
var POLYCHROMA_DARK_STROKES = {
|
|
BLUE: "#6698ff",
|
|
PURPLE: "#c0a3ff",
|
|
MAGENTA: "#fc8dfc",
|
|
PINK: "#ff82b1",
|
|
RED: "#ff9b70",
|
|
ORANGE: "#ffcf4e",
|
|
YELLOW: "#ffff58",
|
|
GREEN: "#58dd70",
|
|
CYAN: "#51e2c9",
|
|
MODERATE_BLUE: "#4fd7ff",
|
|
GRAY: "#eeeeee"
|
|
};
|
|
var PolychromaDark = class extends DarkTheme {
|
|
getDefaultColors() {
|
|
return {
|
|
fills: POLYCHROMA_DARK_FILLS,
|
|
fillsFallback: Object.values(POLYCHROMA_DARK_FILLS),
|
|
strokes: POLYCHROMA_DARK_STROKES,
|
|
sequentialColors: getSequentialColors(POLYCHROMA_DARK_FILLS),
|
|
divergingColors: [POLYCHROMA_DARK_FILLS.BLUE, POLYCHROMA_DARK_FILLS.RED],
|
|
hierarchyColors: [],
|
|
secondSequentialColors: [
|
|
POLYCHROMA_DARK_FILLS.BLUE,
|
|
POLYCHROMA_DARK_FILLS.PURPLE,
|
|
POLYCHROMA_DARK_FILLS.MAGENTA,
|
|
POLYCHROMA_DARK_FILLS.PINK,
|
|
POLYCHROMA_DARK_FILLS.RED,
|
|
POLYCHROMA_DARK_FILLS.ORANGE,
|
|
POLYCHROMA_DARK_FILLS.YELLOW,
|
|
POLYCHROMA_DARK_FILLS.GREEN
|
|
],
|
|
secondDivergingColors: [POLYCHROMA_DARK_FILLS.BLUE, POLYCHROMA_DARK_FILLS.RED],
|
|
secondHierarchyColors: [],
|
|
up: { fill: POLYCHROMA_DARK_FILLS.GREEN, stroke: POLYCHROMA_DARK_STROKES.GREEN },
|
|
down: { fill: POLYCHROMA_DARK_FILLS.RED, stroke: POLYCHROMA_DARK_STROKES.RED },
|
|
neutral: { fill: POLYCHROMA_DARK_FILLS.GRAY, stroke: POLYCHROMA_DARK_STROKES.GRAY },
|
|
altUp: { fill: POLYCHROMA_DARK_FILLS.BLUE, stroke: POLYCHROMA_DARK_STROKES.BLUE },
|
|
altDown: { fill: POLYCHROMA_DARK_FILLS.RED, stroke: POLYCHROMA_DARK_STROKES.RED },
|
|
altNeutral: { fill: POLYCHROMA_DARK_FILLS.GRAY, stroke: POLYCHROMA_DARK_STROKES.GRAY }
|
|
};
|
|
}
|
|
getTemplateParameters() {
|
|
const params = super.getTemplateParameters();
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR, POLYCHROMA_DARK_FILLS.BLUE);
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL, POLYCHROMA_DARK_FILLS.BLUE);
|
|
return params;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/themes/polychromaLight.ts
|
|
var POLYCHROMA_LIGHT_FILLS = {
|
|
BLUE: "#436ff4",
|
|
PURPLE: "#9a7bff",
|
|
MAGENTA: "#d165d2",
|
|
PINK: "#f0598b",
|
|
RED: "#f47348",
|
|
ORANGE: "#f2a602",
|
|
YELLOW: "#e9e201",
|
|
GREEN: "#21b448",
|
|
CYAN: "#00b9a2",
|
|
MODERATE_BLUE: "#00aee4",
|
|
GRAY: "#bbbbbb"
|
|
};
|
|
var POLYCHROMA_LIGHT_STROKES = {
|
|
BLUE: "#2346c9",
|
|
PURPLE: "#7653d4",
|
|
MAGENTA: "#a73da9",
|
|
PINK: "#c32d66",
|
|
RED: "#c84b1c",
|
|
ORANGE: "#c87f00",
|
|
YELLOW: "#c1b900",
|
|
GREEN: "#008c1c",
|
|
CYAN: "#00927c",
|
|
MODERATE_BLUE: "#0087bb",
|
|
GRAY: "#888888"
|
|
};
|
|
var PolychromaLight = class extends ChartTheme {
|
|
getDefaultColors() {
|
|
return {
|
|
...super.getDefaultColors(),
|
|
fills: POLYCHROMA_LIGHT_FILLS,
|
|
fillsFallback: Object.values(POLYCHROMA_LIGHT_FILLS),
|
|
strokes: POLYCHROMA_LIGHT_STROKES,
|
|
sequentialColors: getSequentialColors(POLYCHROMA_LIGHT_FILLS),
|
|
divergingColors: [POLYCHROMA_LIGHT_FILLS.BLUE, POLYCHROMA_LIGHT_FILLS.RED],
|
|
hierarchyColors: [],
|
|
secondSequentialColors: [
|
|
POLYCHROMA_LIGHT_FILLS.BLUE,
|
|
POLYCHROMA_LIGHT_FILLS.PURPLE,
|
|
POLYCHROMA_LIGHT_FILLS.MAGENTA,
|
|
POLYCHROMA_LIGHT_FILLS.PINK,
|
|
POLYCHROMA_LIGHT_FILLS.RED,
|
|
POLYCHROMA_LIGHT_FILLS.ORANGE,
|
|
POLYCHROMA_LIGHT_FILLS.YELLOW,
|
|
POLYCHROMA_LIGHT_FILLS.GREEN
|
|
],
|
|
secondDivergingColors: [POLYCHROMA_LIGHT_FILLS.BLUE, POLYCHROMA_LIGHT_FILLS.RED],
|
|
secondHierarchyColors: [],
|
|
up: { fill: POLYCHROMA_LIGHT_FILLS.GREEN, stroke: POLYCHROMA_LIGHT_STROKES.GREEN },
|
|
down: { fill: POLYCHROMA_LIGHT_FILLS.RED, stroke: POLYCHROMA_LIGHT_STROKES.RED },
|
|
neutral: { fill: POLYCHROMA_LIGHT_FILLS.GRAY, stroke: POLYCHROMA_LIGHT_STROKES.GRAY },
|
|
altUp: { fill: POLYCHROMA_LIGHT_FILLS.BLUE, stroke: POLYCHROMA_LIGHT_STROKES.BLUE },
|
|
altDown: { fill: POLYCHROMA_LIGHT_FILLS.RED, stroke: POLYCHROMA_LIGHT_STROKES.RED },
|
|
altNeutral: { fill: POLYCHROMA_LIGHT_FILLS.GRAY, stroke: POLYCHROMA_LIGHT_STROKES.GRAY }
|
|
};
|
|
}
|
|
getTemplateParameters() {
|
|
const params = super.getTemplateParameters();
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR, POLYCHROMA_LIGHT_FILLS.BLUE);
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL, POLYCHROMA_LIGHT_FILLS.BLUE);
|
|
return params;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/themes/sheetsDark.ts
|
|
var SHEETS_DARK_FILLS = {
|
|
BLUE: "#4472C4",
|
|
ORANGE: "#ED7D31",
|
|
GRAY: "#A5A5A5",
|
|
YELLOW: "#FFC000",
|
|
MODERATE_BLUE: "#5B9BD5",
|
|
GREEN: "#70AD47",
|
|
DARK_GRAY: "#7B7B7B",
|
|
DARK_BLUE: "#264478",
|
|
VERY_DARK_GRAY: "#636363",
|
|
DARK_YELLOW: "#997300"
|
|
};
|
|
var SHEETS_DARK_STROKES = {
|
|
BLUE: "#6899ee",
|
|
ORANGE: "#ffa55d",
|
|
GRAY: "#cdcdcd",
|
|
YELLOW: "#ffea53",
|
|
MODERATE_BLUE: "#82c3ff",
|
|
GREEN: "#96d56f",
|
|
DARK_GRAY: "#a1a1a1",
|
|
DARK_BLUE: "#47689f",
|
|
VERY_DARK_GRAY: "#878787",
|
|
DARK_YELLOW: "#c0993d"
|
|
};
|
|
var SheetsDark = class extends DarkTheme {
|
|
getDefaultColors() {
|
|
return {
|
|
...super.getDefaultColors(),
|
|
fills: { ...SHEETS_DARK_FILLS, RED: SHEETS_DARK_FILLS.ORANGE },
|
|
fillsFallback: Object.values({ ...SHEETS_DARK_FILLS, RED: SHEETS_DARK_FILLS.ORANGE }),
|
|
strokes: { ...SHEETS_DARK_STROKES, RED: SHEETS_DARK_STROKES.ORANGE },
|
|
sequentialColors: getSequentialColors({ ...SHEETS_DARK_FILLS, RED: SHEETS_DARK_FILLS.ORANGE }),
|
|
divergingColors: [SHEETS_DARK_FILLS.ORANGE, SHEETS_DARK_FILLS.YELLOW, SHEETS_DARK_FILLS.GREEN],
|
|
// hierarchyColors: [],
|
|
secondSequentialColors: [
|
|
"#5090dc",
|
|
"#4882c6",
|
|
"#4073b0",
|
|
"#38659a",
|
|
"#305684",
|
|
"#28486e",
|
|
"#203a58",
|
|
"#182b42"
|
|
],
|
|
secondDivergingColors: [SHEETS_DARK_FILLS.GREEN, SHEETS_DARK_FILLS.YELLOW, SHEETS_DARK_FILLS.ORANGE],
|
|
// secondHierarchyColors: [],
|
|
up: { fill: SHEETS_DARK_FILLS.GREEN, stroke: SHEETS_DARK_STROKES.GREEN },
|
|
down: { fill: SHEETS_DARK_FILLS.ORANGE, stroke: SHEETS_DARK_STROKES.ORANGE },
|
|
neutral: { fill: SHEETS_DARK_FILLS.GRAY, stroke: SHEETS_DARK_STROKES.GRAY },
|
|
altUp: { fill: SHEETS_DARK_FILLS.BLUE, stroke: SHEETS_DARK_STROKES.BLUE },
|
|
altDown: { fill: SHEETS_DARK_FILLS.ORANGE, stroke: SHEETS_DARK_STROKES.ORANGE },
|
|
altNeutral: { fill: SHEETS_DARK_FILLS.GRAY, stroke: SHEETS_DARK_STROKES.GRAY }
|
|
};
|
|
}
|
|
getTemplateParameters() {
|
|
const params = super.getTemplateParameters();
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR, SHEETS_DARK_FILLS.BLUE);
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL, SHEETS_DARK_FILLS.BLUE);
|
|
return params;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/themes/sheetsLight.ts
|
|
var SHEETS_LIGHT_FILLS = {
|
|
BLUE: "#5281d5",
|
|
ORANGE: "#ff8d44",
|
|
GRAY: "#b5b5b5",
|
|
YELLOW: "#ffd02f",
|
|
MODERATE_BLUE: "#6aabe6",
|
|
GREEN: "#7fbd57",
|
|
DARK_GRAY: "#8a8a8a",
|
|
DARK_BLUE: "#335287",
|
|
VERY_DARK_GRAY: "#717171",
|
|
DARK_YELLOW: "#a98220"
|
|
};
|
|
var SHEETS_LIGHT_STROKES = {
|
|
BLUE: "#214d9b",
|
|
ORANGE: "#c25600",
|
|
GRAY: "#7f7f7f",
|
|
YELLOW: "#d59800",
|
|
MODERATE_BLUE: "#3575ac",
|
|
GREEN: "#4b861a",
|
|
DARK_GRAY: "#575757",
|
|
DARK_BLUE: "#062253",
|
|
VERY_DARK_GRAY: "#414141",
|
|
DARK_YELLOW: "#734f00"
|
|
};
|
|
var SheetsLight = class extends ChartTheme {
|
|
getDefaultColors() {
|
|
return {
|
|
...super.getDefaultColors(),
|
|
fills: { ...SHEETS_LIGHT_FILLS, RED: SHEETS_LIGHT_FILLS.ORANGE },
|
|
fillsFallback: Object.values({ ...SHEETS_LIGHT_FILLS, RED: SHEETS_LIGHT_FILLS.ORANGE }),
|
|
strokes: { ...SHEETS_LIGHT_STROKES, RED: SHEETS_LIGHT_STROKES.ORANGE },
|
|
sequentialColors: getSequentialColors({ ...SHEETS_LIGHT_FILLS, RED: SHEETS_LIGHT_FILLS.ORANGE }),
|
|
divergingColors: [SHEETS_LIGHT_FILLS.ORANGE, SHEETS_LIGHT_FILLS.YELLOW, SHEETS_LIGHT_FILLS.GREEN],
|
|
// hierarchyColors: [],
|
|
secondSequentialColors: [
|
|
"#5090dc",
|
|
"#629be0",
|
|
"#73a6e3",
|
|
"#85b1e7",
|
|
"#96bcea",
|
|
"#a8c8ee",
|
|
"#b9d3f1",
|
|
"#cbdef5"
|
|
],
|
|
secondDivergingColors: [SHEETS_LIGHT_FILLS.GREEN, SHEETS_LIGHT_FILLS.YELLOW, SHEETS_LIGHT_FILLS.ORANGE],
|
|
secondHierarchyColors: [],
|
|
up: { fill: SHEETS_LIGHT_FILLS.GREEN, stroke: SHEETS_LIGHT_STROKES.GREEN },
|
|
down: { fill: SHEETS_LIGHT_FILLS.ORANGE, stroke: SHEETS_LIGHT_STROKES.ORANGE },
|
|
neutral: { fill: SHEETS_LIGHT_STROKES.GRAY, stroke: SHEETS_LIGHT_STROKES.GRAY },
|
|
altUp: { fill: SHEETS_LIGHT_FILLS.BLUE, stroke: SHEETS_LIGHT_STROKES.BLUE },
|
|
altDown: { fill: SHEETS_LIGHT_FILLS.ORANGE, stroke: SHEETS_LIGHT_STROKES.ORANGE },
|
|
altNeutral: { fill: SHEETS_LIGHT_FILLS.GRAY, stroke: SHEETS_LIGHT_STROKES.GRAY }
|
|
};
|
|
}
|
|
getTemplateParameters() {
|
|
const params = super.getTemplateParameters();
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR, SHEETS_LIGHT_FILLS.BLUE);
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL, SHEETS_LIGHT_FILLS.BLUE);
|
|
return params;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/axesOptionsDefs.ts
|
|
var timeIntervalUnit = union("millisecond", "second", "minute", "hour", "day", "month", "year");
|
|
var timeIntervalDefs = {
|
|
unit: required(timeIntervalUnit),
|
|
step: positiveNumberNonZero,
|
|
epoch: date,
|
|
utc: boolean
|
|
};
|
|
timeIntervalDefs.every = callback;
|
|
var timeInterval2 = optionsDefs(timeIntervalDefs, "a time interval object");
|
|
var commonCrossLineLabelOptionsDefs = {
|
|
enabled: boolean,
|
|
text: string,
|
|
padding: number,
|
|
border: borderOptionsDef,
|
|
cornerRadius: number,
|
|
...fontOptionsDef,
|
|
...fillOptionsDef
|
|
};
|
|
var commonCrossLineOptionsDefs = attachDescription(
|
|
{
|
|
enabled: boolean,
|
|
type: required(union("line", "range")),
|
|
range: and(
|
|
attachDescription((_, { options }) => options.type === "range", "crossLine type to be 'range'"),
|
|
arrayOf(defined),
|
|
arrayLength(2, 2)
|
|
),
|
|
value: and(
|
|
attachDescription((_, { options }) => options.type === "line", "crossLine type to be 'line'"),
|
|
defined
|
|
),
|
|
label: commonCrossLineLabelOptionsDefs,
|
|
fill: color,
|
|
fillOpacity: ratio,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
"cross-line options"
|
|
);
|
|
var cartesianCrossLineOptionsDefs = {
|
|
...commonCrossLineOptionsDefs,
|
|
label: {
|
|
...commonCrossLineLabelOptionsDefs,
|
|
position: union(
|
|
"top",
|
|
"left",
|
|
"right",
|
|
"bottom",
|
|
"top-left",
|
|
"top-right",
|
|
"bottom-left",
|
|
"bottom-right",
|
|
"inside",
|
|
"inside-left",
|
|
"inside-right",
|
|
"inside-top",
|
|
"inside-bottom",
|
|
"inside-top-left",
|
|
"inside-bottom-left",
|
|
"inside-top-right",
|
|
"inside-bottom-right"
|
|
),
|
|
rotation: number
|
|
}
|
|
};
|
|
var commonAxisLabelOptionsDefs = {
|
|
enabled: boolean,
|
|
rotation: number,
|
|
avoidCollisions: boolean,
|
|
minSpacing: positiveNumber,
|
|
spacing: positiveNumber,
|
|
formatter: callbackOf(textOrSegments),
|
|
itemStyler: callbackDefs({
|
|
...fontOptionsDef,
|
|
...labelBoxOptionsDef,
|
|
spacing: number
|
|
}),
|
|
...fontOptionsDef,
|
|
...labelBoxOptionsDef
|
|
};
|
|
var cartesianAxisLabelOptionsDefs = {
|
|
autoRotate: boolean,
|
|
autoRotateAngle: number,
|
|
wrapping: union("never", "always", "hyphenate", "on-space"),
|
|
truncate: boolean,
|
|
...commonAxisLabelOptionsDefs
|
|
};
|
|
var cartesianNumericAxisLabel = {
|
|
format: numberFormatValidator,
|
|
...cartesianAxisLabelOptionsDefs
|
|
};
|
|
var cartesianTimeAxisLabel = {
|
|
format: or(string, object),
|
|
...cartesianAxisLabelOptionsDefs
|
|
};
|
|
var cartesianAxisTick = {
|
|
enabled: boolean,
|
|
width: positiveNumber,
|
|
size: positiveNumber,
|
|
stroke: color
|
|
};
|
|
var cartesianTimeAxisParentLevel = {
|
|
enabled: boolean,
|
|
label: cartesianTimeAxisLabel,
|
|
tick: cartesianAxisTick
|
|
};
|
|
var commonAxisIntervalOptionsDefs = {
|
|
values: arrayOf(defined),
|
|
minSpacing: positiveNumber
|
|
};
|
|
var commonAxisOptionsDefs = {
|
|
reverse: boolean,
|
|
gridLine: {
|
|
enabled: boolean,
|
|
width: positiveNumber,
|
|
style: arrayOfDefs(
|
|
{
|
|
fill: color,
|
|
fillOpacity: positiveNumber,
|
|
stroke: or(color, themeOperator),
|
|
strokeWidth: positiveNumber,
|
|
lineDash: arrayOf(positiveNumber)
|
|
},
|
|
"a grid-line style object array"
|
|
)
|
|
},
|
|
interval: commonAxisIntervalOptionsDefs,
|
|
label: commonAxisLabelOptionsDefs,
|
|
line: {
|
|
enabled: boolean,
|
|
width: positiveNumber,
|
|
stroke: color
|
|
},
|
|
tick: cartesianAxisTick,
|
|
context: () => true
|
|
};
|
|
commonAxisOptionsDefs.layoutConstraints = undocumented({
|
|
stacked: required(boolean),
|
|
align: required(union("start", "end")),
|
|
unit: required(union("percent", "px")),
|
|
width: required(positiveNumber)
|
|
});
|
|
var cartesianAxisOptionsDefs = {
|
|
...commonAxisOptionsDefs,
|
|
crossAt: {
|
|
value: required(or(number, date, string, arrayOf(string))),
|
|
sticky: boolean
|
|
},
|
|
crossLines: arrayOfDefs(cartesianCrossLineOptionsDefs, "a cross-line options array"),
|
|
position: union("top", "right", "bottom", "left"),
|
|
thickness: positiveNumber,
|
|
maxThicknessRatio: ratio,
|
|
title: {
|
|
enabled: boolean,
|
|
text: textOrSegments,
|
|
spacing: positiveNumber,
|
|
formatter: callbackOf(textOrSegments),
|
|
...fontOptionsDef
|
|
}
|
|
};
|
|
cartesianAxisOptionsDefs.title._enabledFromTheme = undocumented(boolean);
|
|
var cartesianAxisBandHighlightOptions = {
|
|
enabled: boolean,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
function cartesianAxisCrosshairOptions(canFormat, timeFormat) {
|
|
const baseCrosshairLabel = {
|
|
enabled: boolean,
|
|
xOffset: number,
|
|
yOffset: number,
|
|
formatter: callbackOf(string),
|
|
renderer: callbackOf(
|
|
or(
|
|
string,
|
|
optionsDefs(
|
|
{
|
|
text: string,
|
|
color,
|
|
backgroundColor: color,
|
|
opacity: ratio
|
|
},
|
|
"crosshair label renderer result object"
|
|
)
|
|
)
|
|
)
|
|
};
|
|
let crosshairLabel;
|
|
if (canFormat) {
|
|
crosshairLabel = {
|
|
...baseCrosshairLabel,
|
|
format: timeFormat ? or(
|
|
string,
|
|
optionsDefs({
|
|
millisecond: string,
|
|
second: string,
|
|
hour: string,
|
|
day: string,
|
|
month: string,
|
|
year: string
|
|
})
|
|
) : string
|
|
};
|
|
}
|
|
return {
|
|
enabled: boolean,
|
|
snap: boolean,
|
|
label: crosshairLabel ?? baseCrosshairLabel,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
}
|
|
function continuousAxisOptions(validDatum, supportTimeInterval) {
|
|
return {
|
|
min: and(validDatum, lessThan("max")),
|
|
max: and(validDatum, greaterThan("min")),
|
|
preferredMin: and(validDatum, lessThan("preferredMax"), lessThan("max")),
|
|
preferredMax: and(validDatum, greaterThan("preferredMin"), greaterThan("min")),
|
|
nice: boolean,
|
|
interval: {
|
|
step: supportTimeInterval ? or(positiveNumberNonZero, timeIntervalUnit, timeInterval2) : positiveNumberNonZero,
|
|
values: arrayOf(validDatum),
|
|
minSpacing: and(positiveNumber, lessThan("maxSpacing")),
|
|
maxSpacing: and(positiveNumber, greaterThan("minSpacing"))
|
|
}
|
|
};
|
|
}
|
|
var discreteTimeAxisIntervalOptionsDefs = {
|
|
step: or(positiveNumberNonZero, timeIntervalUnit, timeInterval2),
|
|
values: arrayOf(or(number, date)),
|
|
minSpacing: and(positiveNumber, lessThan("maxSpacing")),
|
|
maxSpacing: and(positiveNumber, greaterThan("minSpacing")),
|
|
placement: union("on", "between")
|
|
};
|
|
var categoryAxisOptionsDefs = {
|
|
...cartesianAxisOptionsDefs,
|
|
type: constant("category"),
|
|
label: cartesianAxisLabelOptionsDefs,
|
|
paddingInner: ratio,
|
|
paddingOuter: ratio,
|
|
groupPaddingInner: ratio,
|
|
crosshair: cartesianAxisCrosshairOptions(),
|
|
bandAlignment: union("justify", "start", "center", "end"),
|
|
bandHighlight: cartesianAxisBandHighlightOptions,
|
|
interval: {
|
|
...commonAxisIntervalOptionsDefs,
|
|
placement: union("on", "between")
|
|
}
|
|
};
|
|
var groupedCategoryAxisOptionsDefs = {
|
|
...cartesianAxisOptionsDefs,
|
|
type: constant("grouped-category"),
|
|
label: cartesianAxisLabelOptionsDefs,
|
|
crosshair: cartesianAxisCrosshairOptions(),
|
|
bandHighlight: cartesianAxisBandHighlightOptions,
|
|
paddingInner: ratio,
|
|
groupPaddingInner: ratio,
|
|
depthOptions: arrayOfDefs(
|
|
{
|
|
label: {
|
|
enabled: boolean,
|
|
avoidCollisions: boolean,
|
|
wrapping: union("never", "always", "hyphenate", "on-space"),
|
|
truncate: boolean,
|
|
rotation: number,
|
|
spacing: number,
|
|
...fontOptionsDef,
|
|
...labelBoxOptionsDef
|
|
},
|
|
tick: {
|
|
enabled: boolean,
|
|
stroke: color,
|
|
width: positiveNumber
|
|
}
|
|
},
|
|
"depth options objects array"
|
|
)
|
|
};
|
|
var numberAxisOptionsDefs = {
|
|
...cartesianAxisOptionsDefs,
|
|
...continuousAxisOptions(number),
|
|
type: constant("number"),
|
|
label: cartesianNumericAxisLabel,
|
|
crosshair: cartesianAxisCrosshairOptions(true)
|
|
};
|
|
var logAxisOptionsDefs = {
|
|
...cartesianAxisOptionsDefs,
|
|
...continuousAxisOptions(number),
|
|
type: constant("log"),
|
|
base: and(
|
|
positiveNumberNonZero,
|
|
attachDescription((value) => value !== 1, "not equal to 1")
|
|
),
|
|
label: cartesianNumericAxisLabel,
|
|
crosshair: cartesianAxisCrosshairOptions(true)
|
|
};
|
|
var timeAxisOptionsDefs = {
|
|
...cartesianAxisOptionsDefs,
|
|
...continuousAxisOptions(or(number, date), true),
|
|
type: constant("time"),
|
|
label: cartesianTimeAxisLabel,
|
|
parentLevel: cartesianTimeAxisParentLevel,
|
|
crosshair: cartesianAxisCrosshairOptions(true, true)
|
|
};
|
|
var unitTimeAxisOptionsDefs = {
|
|
...cartesianAxisOptionsDefs,
|
|
type: constant("unit-time"),
|
|
unit: or(timeInterval2, timeIntervalUnit),
|
|
label: cartesianTimeAxisLabel,
|
|
parentLevel: cartesianTimeAxisParentLevel,
|
|
paddingInner: ratio,
|
|
paddingOuter: ratio,
|
|
groupPaddingInner: ratio,
|
|
crosshair: cartesianAxisCrosshairOptions(true, true),
|
|
bandAlignment: union("justify", "start", "center", "end"),
|
|
bandHighlight: cartesianAxisBandHighlightOptions,
|
|
min: and(or(number, date), lessThan("max")),
|
|
max: and(or(number, date), greaterThan("min")),
|
|
preferredMin: and(or(number, date), lessThan("preferredMax"), lessThan("max")),
|
|
preferredMax: and(or(number, date), greaterThan("preferredMin"), greaterThan("min")),
|
|
interval: discreteTimeAxisIntervalOptionsDefs
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/axesOptionsEnterpriseDefs.ts
|
|
var ordinalTimeAxisOptionsDefs = {
|
|
...cartesianAxisOptionsDefs,
|
|
type: constant("ordinal-time"),
|
|
paddingInner: ratio,
|
|
paddingOuter: ratio,
|
|
groupPaddingInner: ratio,
|
|
label: cartesianTimeAxisLabel,
|
|
parentLevel: cartesianTimeAxisParentLevel,
|
|
interval: discreteTimeAxisIntervalOptionsDefs,
|
|
crosshair: cartesianAxisCrosshairOptions(true, true),
|
|
bandHighlight: cartesianAxisBandHighlightOptions,
|
|
bandAlignment: union("justify", "start", "center", "end")
|
|
};
|
|
var angleNumberAxisOptionsDefs = {
|
|
...commonAxisOptionsDefs,
|
|
...continuousAxisOptions(number),
|
|
type: constant("angle-number"),
|
|
crossLines: arrayOfDefs(commonCrossLineOptionsDefs),
|
|
startAngle: number,
|
|
endAngle: number,
|
|
label: {
|
|
...commonAxisLabelOptionsDefs,
|
|
orientation: union("fixed", "parallel", "perpendicular"),
|
|
format: numberFormatValidator
|
|
}
|
|
};
|
|
var angleCategoryAxisOptionsDefs = {
|
|
...commonAxisOptionsDefs,
|
|
type: constant("angle-category"),
|
|
shape: union("polygon", "circle"),
|
|
crossLines: arrayOfDefs(commonCrossLineOptionsDefs),
|
|
startAngle: number,
|
|
endAngle: number,
|
|
paddingInner: ratio,
|
|
groupPaddingInner: ratio,
|
|
label: {
|
|
...commonAxisLabelOptionsDefs,
|
|
orientation: union("fixed", "parallel", "perpendicular")
|
|
}
|
|
};
|
|
angleCategoryAxisOptionsDefs.innerRadiusRatio = ratio;
|
|
var radiusNumberAxisOptionsDefs = {
|
|
...commonAxisOptionsDefs,
|
|
...continuousAxisOptions(number),
|
|
type: constant("radius-number"),
|
|
shape: union("polygon", "circle"),
|
|
positionAngle: number,
|
|
innerRadiusRatio: ratio,
|
|
crossLines: arrayOfDefs(
|
|
{
|
|
...commonCrossLineOptionsDefs,
|
|
label: {
|
|
...commonCrossLineLabelOptionsDefs,
|
|
positionAngle: number
|
|
}
|
|
},
|
|
"cross-line options"
|
|
),
|
|
title: {
|
|
enabled: boolean,
|
|
text: textOrSegments,
|
|
spacing: positiveNumber,
|
|
formatter: callbackOf(textOrSegments),
|
|
...fontOptionsDef
|
|
},
|
|
label: {
|
|
...commonAxisLabelOptionsDefs,
|
|
format: numberFormatValidator
|
|
}
|
|
};
|
|
var radiusCategoryAxisOptionsDefs = {
|
|
...commonAxisOptionsDefs,
|
|
type: constant("radius-category"),
|
|
positionAngle: number,
|
|
innerRadiusRatio: ratio,
|
|
paddingInner: ratio,
|
|
paddingOuter: ratio,
|
|
groupPaddingInner: ratio,
|
|
label: commonAxisLabelOptionsDefs,
|
|
crossLines: arrayOfDefs(
|
|
{
|
|
...commonCrossLineOptionsDefs,
|
|
label: {
|
|
...commonCrossLineLabelOptionsDefs,
|
|
positionAngle: number
|
|
}
|
|
},
|
|
"cross-line options"
|
|
),
|
|
title: {
|
|
enabled: boolean,
|
|
text: textOrSegments,
|
|
spacing: positiveNumber,
|
|
formatter: callbackOf(textOrSegments),
|
|
...fontOptionsDef
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/areaSeriesOptionsDef.ts
|
|
var highlight = multiSeriesHighlightOptionsDef(shapeHighlightOptionsDef, shapeHighlightOptionsDef);
|
|
var areaStyler = callbackDefs({
|
|
...strokeOptionsDef,
|
|
...fillOptionsDef,
|
|
...lineDashOptionsDef,
|
|
marker: markerStyleOptionsDefs
|
|
});
|
|
var areaSeriesThemeableOptionsDef = {
|
|
showInMiniChart: boolean,
|
|
connectMissingData: boolean,
|
|
interpolation: interpolationOptionsDefs,
|
|
label: seriesLabelOptionsDefs,
|
|
styler: areaStyler,
|
|
marker: markerOptionsDefs,
|
|
tooltip: tooltipOptionsDefsWithArea,
|
|
shadow: shadowOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
highlight,
|
|
segmentation: shapeSegmentation
|
|
};
|
|
var areaSeriesOptionsDef = {
|
|
...areaSeriesThemeableOptionsDef,
|
|
...commonSeriesOptionsDefs,
|
|
highlight,
|
|
type: required(constant("area")),
|
|
xKey: required(string),
|
|
yKey: required(string),
|
|
xKeyAxis: string,
|
|
yKeyAxis: string,
|
|
xName: string,
|
|
yName: string,
|
|
legendItemName: string,
|
|
stacked: boolean,
|
|
stackGroup: string,
|
|
normalizedTo: number
|
|
};
|
|
areaSeriesOptionsDef.yFilterKey = undocumented(string);
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/barSeriesOptionsDef.ts
|
|
var highlight2 = multiSeriesHighlightOptionsDef(barHighlightOptionsDef, barHighlightOptionsDef);
|
|
var barStyler = callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
cornerRadius: positiveNumber
|
|
});
|
|
var barSeriesThemeableOptionsDef = {
|
|
direction: union("horizontal", "vertical"),
|
|
showInMiniChart: boolean,
|
|
cornerRadius: positiveNumber,
|
|
styler: barStyler,
|
|
itemStyler: barStyler,
|
|
crisp: boolean,
|
|
label: {
|
|
...seriesLabelOptionsDefs,
|
|
placement: union("inside-center", "inside-start", "inside-end", "outside-start", "outside-end"),
|
|
spacing: positiveNumber
|
|
},
|
|
errorBar: errorBarThemeableOptionsDefs,
|
|
shadow: shadowOptionsDefs,
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
highlight: highlight2,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
segmentation: shapeSegmentation,
|
|
width: positiveNumberNonZero,
|
|
widthRatio: ratio
|
|
};
|
|
barSeriesThemeableOptionsDef.sparklineMode = undocumented(boolean);
|
|
var barSeriesOptionsDef = {
|
|
...barSeriesThemeableOptionsDef,
|
|
...commonSeriesOptionsDefs,
|
|
highlight: highlight2,
|
|
type: required(constant("bar")),
|
|
xKey: required(string),
|
|
yKey: required(string),
|
|
xKeyAxis: string,
|
|
yKeyAxis: string,
|
|
xName: string,
|
|
yName: string,
|
|
direction: union("horizontal", "vertical"),
|
|
grouped: boolean,
|
|
stacked: boolean,
|
|
stackGroup: string,
|
|
normalizedTo: number,
|
|
legendItemName: string,
|
|
errorBar: errorBarOptionsDefs
|
|
};
|
|
barSeriesOptionsDef.yFilterKey = undocumented(string);
|
|
barSeriesOptionsDef.pickOutsideVisibleMinorAxis = undocumented(boolean);
|
|
barSeriesOptionsDef.focusPriority = undocumented(number);
|
|
barSeriesOptionsDef.simpleItemStyler = undocumented(callback);
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/bubbleSeriesOptionsDef.ts
|
|
var bubbleSeriesThemeableOptionsDef = {
|
|
title: string,
|
|
domain: arrayOf(number),
|
|
maxSize: positiveNumber,
|
|
showInMiniChart: boolean,
|
|
label: {
|
|
placement: union("top", "right", "bottom", "left"),
|
|
...seriesLabelOptionsDefs
|
|
},
|
|
tooltip: tooltipOptionsDefs,
|
|
styler: callbackDefs({
|
|
...markerOptionsDefs,
|
|
maxSize: positiveNumber
|
|
}),
|
|
maxRenderedItems: number,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...without(markerOptionsDefs, ["enabled"]),
|
|
highlight: multiSeriesHighlightOptionsDef(shapeHighlightOptionsDef, shapeHighlightOptionsDef)
|
|
};
|
|
var bubbleSeriesOptionsDef = {
|
|
...bubbleSeriesThemeableOptionsDef,
|
|
...commonSeriesOptionsDefs,
|
|
type: required(constant("bubble")),
|
|
xKey: required(string),
|
|
yKey: required(string),
|
|
sizeKey: required(string),
|
|
labelKey: string,
|
|
xName: string,
|
|
yName: string,
|
|
sizeName: string,
|
|
labelName: string,
|
|
legendItemName: string,
|
|
xKeyAxis: string,
|
|
yKeyAxis: string,
|
|
highlight: multiSeriesHighlightOptionsDef(shapeHighlightOptionsDef, shapeHighlightOptionsDef)
|
|
};
|
|
bubbleSeriesOptionsDef.xFilterKey = undocumented(string);
|
|
bubbleSeriesOptionsDef.yFilterKey = undocumented(string);
|
|
bubbleSeriesOptionsDef.sizeFilterKey = undocumented(string);
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/histogramSeriesOptionsDef.ts
|
|
var histogramSeriesThemeableOptionsDef = {
|
|
showInMiniChart: boolean,
|
|
cornerRadius: positiveNumber,
|
|
label: seriesLabelOptionsDefs,
|
|
tooltip: tooltipOptionsDefs,
|
|
shadow: shadowOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
highlight: multiSeriesHighlightOptionsDef(shapeHighlightOptionsDef, shapeHighlightOptionsDef),
|
|
areaPlot: boolean,
|
|
aggregation: union("count", "sum", "mean"),
|
|
bins: arrayOf(arrayOf(number)),
|
|
binCount: positiveNumber
|
|
};
|
|
var histogramSeriesOptionsDef = {
|
|
...commonSeriesOptionsDefs,
|
|
...histogramSeriesThemeableOptionsDef,
|
|
type: required(constant("histogram")),
|
|
xKey: required(string),
|
|
yKey: string,
|
|
xKeyAxis: string,
|
|
yKeyAxis: string,
|
|
xName: string,
|
|
yName: string
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/lineSeriesOptionsDef.ts
|
|
var highlight3 = multiSeriesHighlightOptionsDef(shapeHighlightOptionsDef, lineHighlightOptionsDef);
|
|
var lineStyler = callbackDefs({
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
marker: markerStyleOptionsDefs
|
|
});
|
|
var lineSeriesThemeableOptionsDef = {
|
|
title: string,
|
|
showInMiniChart: boolean,
|
|
connectMissingData: boolean,
|
|
interpolation: interpolationOptionsDefs,
|
|
label: seriesLabelOptionsDefs,
|
|
styler: lineStyler,
|
|
marker: markerOptionsDefs,
|
|
tooltip: tooltipOptionsDefs,
|
|
errorBar: errorBarThemeableOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
highlight: highlight3,
|
|
segmentation: lineSegmentation
|
|
};
|
|
lineSeriesThemeableOptionsDef.sparklineMode = undocumented(boolean);
|
|
var lineSeriesOptionsDef = {
|
|
...lineSeriesThemeableOptionsDef,
|
|
...commonSeriesOptionsDefs,
|
|
highlight: highlight3,
|
|
type: required(constant("line")),
|
|
xKey: required(string),
|
|
yKey: required(string),
|
|
xKeyAxis: string,
|
|
yKeyAxis: string,
|
|
xName: string,
|
|
yName: string,
|
|
stacked: boolean,
|
|
stackGroup: string,
|
|
normalizedTo: number,
|
|
legendItemName: string,
|
|
errorBar: errorBarOptionsDefs
|
|
};
|
|
lineSeriesOptionsDef.yFilterKey = undocumented(string);
|
|
lineSeriesOptionsDef.pickOutsideVisibleMinorAxis = undocumented(boolean);
|
|
lineSeriesOptionsDef.focusPriority = undocumented(number);
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/scatterSeriesOptionsDef.ts
|
|
var scatterSeriesThemeableOptionsDef = {
|
|
title: string,
|
|
showInMiniChart: boolean,
|
|
label: {
|
|
placement: union("top", "right", "bottom", "left"),
|
|
...seriesLabelOptionsDefs
|
|
},
|
|
tooltip: tooltipOptionsDefs,
|
|
errorBar: errorBarThemeableOptionsDefs,
|
|
styler: callbackDefs(markerOptionsDefs),
|
|
maxRenderedItems: number,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...without(markerOptionsDefs, ["enabled"]),
|
|
highlight: multiSeriesHighlightOptionsDef(shapeHighlightOptionsDef, shapeHighlightOptionsDef)
|
|
};
|
|
var scatterSeriesOptionsDef = {
|
|
...scatterSeriesThemeableOptionsDef,
|
|
...commonSeriesOptionsDefs,
|
|
type: required(constant("scatter")),
|
|
xKey: required(string),
|
|
yKey: required(string),
|
|
labelKey: string,
|
|
xName: string,
|
|
yName: string,
|
|
labelName: string,
|
|
legendItemName: string,
|
|
xKeyAxis: string,
|
|
yKeyAxis: string,
|
|
errorBar: errorBarOptionsDefs,
|
|
highlight: multiSeriesHighlightOptionsDef(shapeHighlightOptionsDef, shapeHighlightOptionsDef)
|
|
};
|
|
scatterSeriesOptionsDef.xFilterKey = undocumented(string);
|
|
scatterSeriesOptionsDef.yFilterKey = undocumented(string);
|
|
scatterSeriesOptionsDef.sizeFilterKey = undocumented(string);
|
|
|
|
// packages/ag-charts-community/src/chart/series/polar/pieSeriesOptionsDef.ts
|
|
var highlight4 = multiSeriesHighlightOptionsDef(shapeHighlightOptionsDef, shapeHighlightOptionsDef);
|
|
var pieSeriesThemeableOptionsDef = {
|
|
...commonSeriesThemeableOptionsDefs,
|
|
radiusMin: positiveNumber,
|
|
radiusMax: positiveNumber,
|
|
rotation: number,
|
|
outerRadiusOffset: number,
|
|
outerRadiusRatio: ratio,
|
|
hideZeroValueSectorsInLegend: boolean,
|
|
sectorSpacing: positiveNumber,
|
|
cornerRadius: positiveNumber,
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
cornerRadius: positiveNumber
|
|
}),
|
|
title: {
|
|
enabled: boolean,
|
|
text: string,
|
|
showInLegend: boolean,
|
|
spacing: positiveNumber,
|
|
...fontOptionsDef
|
|
},
|
|
calloutLabel: {
|
|
enabled: boolean,
|
|
offset: number,
|
|
minAngle: positiveNumber,
|
|
avoidCollisions: boolean,
|
|
formatter: callbackOf(textOrSegments),
|
|
format: string,
|
|
itemStyler: callbackDefs({
|
|
enabled: boolean,
|
|
...labelBoxOptionsDef,
|
|
...fontOptionsDef
|
|
}),
|
|
...labelBoxOptionsDef,
|
|
...fontOptionsDef
|
|
},
|
|
sectorLabel: {
|
|
enabled: boolean,
|
|
positionOffset: number,
|
|
positionRatio: ratio,
|
|
formatter: callbackOf(textOrSegments),
|
|
format: string,
|
|
itemStyler: callbackDefs({
|
|
enabled: boolean,
|
|
...labelBoxOptionsDef,
|
|
...fontOptionsDef
|
|
}),
|
|
...labelBoxOptionsDef,
|
|
...fontOptionsDef
|
|
},
|
|
calloutLine: {
|
|
colors: arrayOf(color),
|
|
length: positiveNumber,
|
|
strokeWidth: positiveNumber,
|
|
itemStyler: callbackDefs({
|
|
color,
|
|
length: positiveNumber,
|
|
strokeWidth: positiveNumber
|
|
})
|
|
},
|
|
fills: arrayOf(colorUnion),
|
|
strokes: arrayOf(color),
|
|
tooltip: tooltipOptionsDefs,
|
|
shadow: shadowOptionsDefs,
|
|
highlight: highlight4,
|
|
...lineDashOptionsDef,
|
|
...without(fillOptionsDef, ["fill"]),
|
|
...without(strokeOptionsDef, ["stroke"])
|
|
};
|
|
var pieSeriesOptionsDef = {
|
|
...pieSeriesThemeableOptionsDef,
|
|
...commonSeriesOptionsDefs,
|
|
type: required(constant("pie")),
|
|
angleKey: required(string),
|
|
radiusKey: string,
|
|
calloutLabelKey: string,
|
|
sectorLabelKey: string,
|
|
legendItemKey: string,
|
|
angleName: string,
|
|
radiusName: string,
|
|
calloutLabelName: string,
|
|
sectorLabelName: string,
|
|
highlight: highlight4
|
|
};
|
|
pieSeriesOptionsDef.angleFilterKey = undocumented(string);
|
|
pieSeriesOptionsDef.defaultColorRange = undocumented(arrayOf(arrayOf(color)));
|
|
pieSeriesOptionsDef.defaultPatternFills = undocumented(arrayOf(color));
|
|
pieSeriesOptionsDef.title._enabledFromTheme = undocumented(boolean);
|
|
pieSeriesOptionsDef.calloutLabel._enabledFromTheme = undocumented(boolean);
|
|
pieSeriesOptionsDef.sectorLabel._enabledFromTheme = undocumented(boolean);
|
|
pieSeriesOptionsDef.angleKeyAxis = undocumented(string);
|
|
pieSeriesOptionsDef.radiusKeyAxis = undocumented(string);
|
|
|
|
// packages/ag-charts-community/src/chart/series/polar/donutSeriesOptionsDef.ts
|
|
var donutSeriesThemeableOptionsDef = {
|
|
...pieSeriesThemeableOptionsDef,
|
|
innerRadiusOffset: number,
|
|
innerRadiusRatio: ratio,
|
|
innerCircle: {
|
|
fill: string,
|
|
fillOpacity: ratio
|
|
},
|
|
innerLabels: {
|
|
spacing: positiveNumber,
|
|
...fontOptionsDef,
|
|
...labelBoxOptionsDef
|
|
}
|
|
};
|
|
var donutSeriesOptionsDef = {
|
|
...donutSeriesThemeableOptionsDef,
|
|
...pieSeriesOptionsDef,
|
|
type: required(constant("donut")),
|
|
innerLabels: arrayOfDefs(
|
|
{
|
|
text: required(string),
|
|
spacing: positiveNumber,
|
|
...fontOptionsDef,
|
|
...labelBoxOptionsDef
|
|
},
|
|
"inner label options array"
|
|
)
|
|
};
|
|
donutSeriesOptionsDef.angleFilterKey = undocumented(string);
|
|
|
|
// packages/ag-charts-community/src/chart/themes/annotationOptionsDef.ts
|
|
var annotationLineOptionsDef = {
|
|
lineStyle: union("solid", "dashed", "dotted"),
|
|
...lineDashOptionsDef
|
|
};
|
|
var annotationHandleStylesDefs = {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var annotationTextStylesDef = {
|
|
visible: boolean,
|
|
locked: boolean,
|
|
readOnly: boolean,
|
|
handle: annotationHandleStylesDefs,
|
|
...fontOptionsDef
|
|
};
|
|
var annotationLineTextDefs = {
|
|
position: union("top", "center", "bottom"),
|
|
alignment: union("left", "center", "right"),
|
|
...fontOptionsDef
|
|
};
|
|
var annotationChannelTextDefs = {
|
|
position: union("top", "inside", "bottom"),
|
|
alignment: union("left", "center", "right"),
|
|
...fontOptionsDef
|
|
};
|
|
var annotationAxisLabelOptionsDef = {
|
|
enabled: boolean,
|
|
cornerRadius: positiveNumber,
|
|
...fontOptionsDef,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var annotationChannelMiddleDefs = {
|
|
visible: boolean,
|
|
...annotationLineOptionsDef,
|
|
...strokeOptionsDef
|
|
};
|
|
var annotationMeasurerStatisticsOptionsDefs = {
|
|
divider: strokeOptionsDef,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...fontOptionsDef
|
|
};
|
|
var annotationQuickMeasurerDirectionStylesDefs = {
|
|
handle: annotationHandleStylesDefs,
|
|
statistics: annotationMeasurerStatisticsOptionsDefs,
|
|
...annotationLineOptionsDef,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef
|
|
};
|
|
var annotationLineStyleDefs = {
|
|
visible: boolean,
|
|
locked: boolean,
|
|
readOnly: boolean,
|
|
extendStart: boolean,
|
|
extendEnd: boolean,
|
|
handle: annotationHandleStylesDefs,
|
|
text: annotationLineTextDefs,
|
|
...annotationLineOptionsDef,
|
|
...strokeOptionsDef
|
|
};
|
|
var annotationCrossLineStyleDefs = {
|
|
visible: boolean,
|
|
locked: boolean,
|
|
readOnly: boolean,
|
|
axisLabel: annotationAxisLabelOptionsDef,
|
|
handle: annotationHandleStylesDefs,
|
|
text: annotationLineTextDefs,
|
|
...annotationLineOptionsDef,
|
|
...strokeOptionsDef
|
|
};
|
|
var annotationChannelStyleDefs = {
|
|
visible: boolean,
|
|
locked: boolean,
|
|
readOnly: boolean,
|
|
extendStart: boolean,
|
|
extendEnd: boolean,
|
|
handle: annotationHandleStylesDefs,
|
|
text: annotationChannelTextDefs,
|
|
background: fillOptionsDef,
|
|
...annotationLineOptionsDef,
|
|
...strokeOptionsDef
|
|
};
|
|
var annotationDisjointChannelStyleDefs = {
|
|
...annotationChannelStyleDefs
|
|
};
|
|
var annotationParallelChannelStyleDefs = {
|
|
...annotationChannelStyleDefs,
|
|
middle: annotationChannelMiddleDefs
|
|
};
|
|
var annotationFibonacciStylesDefs = {
|
|
label: fontOptionsDef,
|
|
showFill: boolean,
|
|
isMultiColor: boolean,
|
|
strokes: arrayOf(color),
|
|
rangeStroke: color,
|
|
bands: union(4, 6, 10),
|
|
...annotationLineStyleDefs
|
|
};
|
|
var annotationCalloutStylesDefs = {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...annotationTextStylesDef
|
|
};
|
|
var annotationCommentStylesDefs = {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...annotationTextStylesDef
|
|
};
|
|
var annotationNoteStylesDefs = {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...annotationTextStylesDef,
|
|
background: {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef
|
|
}
|
|
};
|
|
var annotationShapeStylesDefs = {
|
|
visible: boolean,
|
|
locked: boolean,
|
|
readOnly: boolean,
|
|
handle: annotationHandleStylesDefs,
|
|
...fillOptionsDef
|
|
};
|
|
var annotationMeasurerStylesDefs = {
|
|
visible: boolean,
|
|
locked: boolean,
|
|
readOnly: boolean,
|
|
extendStart: boolean,
|
|
extendEnd: boolean,
|
|
handle: annotationHandleStylesDefs,
|
|
text: annotationLineTextDefs,
|
|
background: fillOptionsDef,
|
|
statistics: annotationMeasurerStatisticsOptionsDefs,
|
|
...annotationLineOptionsDef,
|
|
...strokeOptionsDef
|
|
};
|
|
var annotationQuickMeasurerStylesDefs = {
|
|
visible: boolean,
|
|
up: annotationQuickMeasurerDirectionStylesDefs,
|
|
down: annotationQuickMeasurerDirectionStylesDefs
|
|
};
|
|
var annotationOptionsDef = {
|
|
enabled: boolean,
|
|
axesButtons: {
|
|
enabled: boolean,
|
|
axes: union("x", "y", "xy")
|
|
},
|
|
toolbar: {
|
|
enabled: boolean,
|
|
padding: positiveNumber,
|
|
buttons: arrayOfDefs(
|
|
{
|
|
...toolbarButtonOptionsDefs,
|
|
value: union(
|
|
"line-menu",
|
|
"fibonacci-menu",
|
|
"text-menu",
|
|
"shape-menu",
|
|
"measurer-menu",
|
|
"line",
|
|
"horizontal-line",
|
|
"vertical-line",
|
|
"parallel-channel",
|
|
"disjoint-channel",
|
|
"fibonacci-retracement",
|
|
"fibonacci-retracement-trend-based",
|
|
"text",
|
|
"comment",
|
|
"callout",
|
|
"note",
|
|
"clear"
|
|
)
|
|
},
|
|
"annotation toolbar buttons array"
|
|
)
|
|
},
|
|
optionsToolbar: {
|
|
enabled: boolean,
|
|
buttons: arrayOf(
|
|
or(
|
|
optionsDefs({
|
|
...toolbarButtonOptionsDefs,
|
|
value: required(
|
|
union(
|
|
"line-stroke-width",
|
|
"line-style-type",
|
|
"line-color",
|
|
"fill-color",
|
|
"text-color",
|
|
"text-size",
|
|
"delete",
|
|
"settings"
|
|
)
|
|
)
|
|
}),
|
|
optionsDefs({
|
|
...toolbarButtonOptionsDefs,
|
|
value: required(union("lock")),
|
|
checkedOverrides: toolbarButtonOptionsDefs
|
|
})
|
|
)
|
|
)
|
|
}
|
|
};
|
|
annotationOptionsDef.data = undocumented(array);
|
|
annotationOptionsDef.xKey = undocumented(string);
|
|
annotationOptionsDef.volumeKey = undocumented(string);
|
|
annotationOptionsDef.snap = undocumented(boolean);
|
|
|
|
// packages/ag-charts-community/src/chart/themes/enterpriseThemeableOptionsDef.ts
|
|
var hierarchyHighlightStyleOptionsDef = {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
opacity: ratio
|
|
};
|
|
var boxPlotStyleOptionsDef = {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
cornerRadius: positiveNumber,
|
|
whisker: {
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
cap: {
|
|
lengthRatio: ratio
|
|
}
|
|
};
|
|
var boxPlotHighlightStyleOptionsDef = {
|
|
...boxPlotStyleOptionsDef,
|
|
opacity: ratio
|
|
};
|
|
var boxPlotStyler = callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
cornerRadius: positiveNumber,
|
|
whisker: {
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
cap: {
|
|
lengthRatio: ratio
|
|
}
|
|
});
|
|
var boxPlotSeriesThemeableOptionsDef = {
|
|
direction: union("horizontal", "vertical"),
|
|
showInMiniChart: boolean,
|
|
styler: boxPlotStyler,
|
|
itemStyler: boxPlotStyler,
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...boxPlotStyleOptionsDef,
|
|
highlight: multiSeriesHighlightOptionsDef(boxPlotHighlightStyleOptionsDef, boxPlotHighlightStyleOptionsDef),
|
|
segmentation: shapeSegmentation,
|
|
width: positiveNumberNonZero,
|
|
widthRatio: ratio
|
|
};
|
|
var candlestickSeriesItemOptionsDef = {
|
|
cornerRadius: positiveNumber,
|
|
wick: {
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var candlestickHighlightStyleOptionsDef = {
|
|
...candlestickSeriesItemOptionsDef,
|
|
opacity: ratio
|
|
};
|
|
var candlestickSeriesThemeableOptionsDef = {
|
|
item: {
|
|
up: candlestickSeriesItemOptionsDef,
|
|
down: candlestickSeriesItemOptionsDef
|
|
},
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
cornerRadius: positiveNumber,
|
|
wick: {
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
}
|
|
}),
|
|
showInMiniChart: boolean,
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
highlight: multiSeriesHighlightOptionsDef(candlestickHighlightStyleOptionsDef, candlestickHighlightStyleOptionsDef)
|
|
};
|
|
var chordSeriesThemeableOptionsDef = {
|
|
fills: arrayOf(colorUnion),
|
|
strokes: arrayOf(color),
|
|
label: {
|
|
spacing: positiveNumber,
|
|
maxWidth: positiveNumber,
|
|
...seriesLabelOptionsDefs
|
|
},
|
|
link: {
|
|
tension: ratio,
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
tension: ratio
|
|
}),
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
node: {
|
|
width: positiveNumber,
|
|
spacing: positiveNumber,
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
}),
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs
|
|
};
|
|
var coneFunnelSeriesThemeableOptionsDef = {
|
|
direction: union("horizontal", "vertical"),
|
|
fills: arrayOf(colorUnion),
|
|
strokes: arrayOf(color),
|
|
label: {
|
|
spacing: positiveNumber,
|
|
placement: union("before", "middle", "after"),
|
|
...seriesLabelOptionsDefs
|
|
},
|
|
stageLabel: {
|
|
placement: union("before", "after"),
|
|
format: numberFormatValidator,
|
|
...commonAxisLabelOptionsDefs
|
|
},
|
|
tooltip: tooltipOptionsDefs,
|
|
...without(commonSeriesThemeableOptionsDefs, ["showInLegend"]),
|
|
...without(fillOptionsDef, ["fill"]),
|
|
...without(strokeOptionsDef, ["stroke"]),
|
|
...lineDashOptionsDef,
|
|
highlight: highlightOptionsDef(lineHighlightOptionsDef)
|
|
};
|
|
var funnelSeriesThemeableOptionsDef = {
|
|
direction: union("horizontal", "vertical"),
|
|
fills: arrayOf(colorUnion),
|
|
strokes: arrayOf(color),
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
}),
|
|
spacingRatio: ratio,
|
|
crisp: boolean,
|
|
dropOff: {
|
|
enabled: boolean,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
stageLabel: {
|
|
placement: union("before", "after"),
|
|
format: numberFormatValidator,
|
|
...commonAxisLabelOptionsDefs
|
|
},
|
|
label: seriesLabelOptionsDefs,
|
|
tooltip: tooltipOptionsDefs,
|
|
shadow: shadowOptionsDefs,
|
|
...without(commonSeriesThemeableOptionsDefs, ["showInLegend"]),
|
|
...without(fillOptionsDef, ["fill"]),
|
|
...without(strokeOptionsDef, ["stroke"]),
|
|
...lineDashOptionsDef
|
|
};
|
|
var heatmapSeriesThemeableOptionsDef = {
|
|
title: string,
|
|
textAlign: union("left", "center", "right"),
|
|
verticalAlign: union("top", "middle", "bottom"),
|
|
itemPadding: positiveNumber,
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef
|
|
}),
|
|
showInMiniChart: boolean,
|
|
label: autoSizedLabelOptionsDefs,
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...strokeOptionsDef
|
|
};
|
|
var ohlcSeriesThemeableOptionsDef = {
|
|
showInMiniChart: boolean,
|
|
itemStyler: callbackDefs({
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
}),
|
|
item: {
|
|
up: {
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
down: {
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
}
|
|
},
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
highlight: multiSeriesHighlightOptionsDef(lineHighlightOptionsDef, lineHighlightOptionsDef)
|
|
};
|
|
var mapLineSeriesThemeableOptionsDef = {
|
|
maxStrokeWidth: positiveNumber,
|
|
itemStyler: callbackDefs({
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
}),
|
|
sizeDomain: arrayOf(positiveNumber),
|
|
label: seriesLabelOptionsDefs,
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
highlight: multiSeriesHighlightOptionsDef(lineHighlightOptionsDef, lineHighlightOptionsDef)
|
|
};
|
|
var mapLineBackgroundSeriesThemeableOptionsDef = {
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var mapMarkerSeriesThemeableOptionsDef = {
|
|
colorRange: arrayOf(color),
|
|
maxSize: positiveNumber,
|
|
sizeDomain: arrayOf(positiveNumber),
|
|
label: {
|
|
placement: union("top", "bottom", "left", "right"),
|
|
...seriesLabelOptionsDefs
|
|
},
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...without(markerOptionsDefs, ["enabled"]),
|
|
highlight: multiSeriesHighlightOptionsDef(shapeHighlightOptionsDef, shapeHighlightOptionsDef)
|
|
};
|
|
var mapShapeSeriesThemeableOptionsDef = {
|
|
colorRange: arrayOf(color),
|
|
padding: positiveNumber,
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
}),
|
|
label: autoSizedLabelOptionsDefs,
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
highlight: multiSeriesHighlightOptionsDef(shapeHighlightOptionsDef, shapeHighlightOptionsDef)
|
|
};
|
|
var mapShapeBackgroundSeriesThemeableOptionsDef = {
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var radialSeriesStylerDef = callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
cornerRadius: positiveNumber
|
|
});
|
|
var nightingaleSeriesThemeableOptionsDef = {
|
|
cornerRadius: positiveNumber,
|
|
styler: radialSeriesStylerDef,
|
|
itemStyler: radialSeriesStylerDef,
|
|
label: seriesLabelOptionsDefs,
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
highlight: multiSeriesHighlightOptionsDef(barHighlightOptionsDef, barHighlightOptionsDef)
|
|
};
|
|
var pyramidSeriesThemeableOptionsDef = {
|
|
direction: union("horizontal", "vertical"),
|
|
aspectRatio: positiveNumber,
|
|
spacing: positiveNumber,
|
|
reverse: boolean,
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
}),
|
|
fills: arrayOf(colorUnion),
|
|
strokes: arrayOf(color),
|
|
label: seriesLabelOptionsDefs,
|
|
stageLabel: {
|
|
spacing: positiveNumber,
|
|
placement: union("before", "after"),
|
|
...seriesLabelOptionsDefs
|
|
},
|
|
tooltip: tooltipOptionsDefs,
|
|
shadow: shadowOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...without(fillOptionsDef, ["fill"]),
|
|
...without(strokeOptionsDef, ["stroke"]),
|
|
...lineDashOptionsDef
|
|
};
|
|
var radarAreaSeriesThemeableOptionsDef = {
|
|
connectMissingData: boolean,
|
|
marker: markerOptionsDefs,
|
|
styler: callbackDefs({
|
|
marker: markerStyleOptionsDefs,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
}),
|
|
label: seriesLabelOptionsDefs,
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
highlight: multiSeriesHighlightOptionsDef(shapeHighlightOptionsDef, shapeHighlightOptionsDef)
|
|
};
|
|
var radarLineSeriesThemeableOptionsDef = {
|
|
connectMissingData: boolean,
|
|
marker: markerOptionsDefs,
|
|
styler: callbackDefs({
|
|
marker: markerStyleOptionsDefs,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
}),
|
|
label: seriesLabelOptionsDefs,
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
highlight: multiSeriesHighlightOptionsDef(shapeHighlightOptionsDef, lineHighlightOptionsDef)
|
|
};
|
|
var radialBarSeriesThemeableOptionsDef = {
|
|
cornerRadius: positiveNumber,
|
|
styler: radialSeriesStylerDef,
|
|
itemStyler: radialSeriesStylerDef,
|
|
label: seriesLabelOptionsDefs,
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
highlight: multiSeriesHighlightOptionsDef(barHighlightOptionsDef, barHighlightOptionsDef)
|
|
};
|
|
var radialColumnSeriesThemeableOptionsDef = {
|
|
cornerRadius: positiveNumber,
|
|
columnWidthRatio: ratio,
|
|
maxColumnWidthRatio: ratio,
|
|
styler: radialSeriesStylerDef,
|
|
itemStyler: radialSeriesStylerDef,
|
|
label: seriesLabelOptionsDefs,
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
highlight: multiSeriesHighlightOptionsDef(barHighlightOptionsDef, barHighlightOptionsDef)
|
|
};
|
|
var rangeAreaSeriesLineThemeableOptionsDef = {
|
|
marker: markerOptionsDefs,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var rangeAreaSeriesItemLineThemeableOptionsDef = {
|
|
marker: {
|
|
enabled: boolean,
|
|
...markerStyleOptionsDefs
|
|
},
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var rangeAreaSeriesLineStyleDef = {
|
|
marker: markerStyleOptionsDefs,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var rangeAreaSeriesThemeableOptionsDef = {
|
|
showInMiniChart: boolean,
|
|
connectMissingData: boolean,
|
|
interpolation: interpolationOptionsDefs,
|
|
label: {
|
|
...seriesLabelOptionsDefs,
|
|
placement: union("inside", "outside"),
|
|
spacing: positiveNumber
|
|
},
|
|
tooltip: tooltipOptionsDefs,
|
|
shadow: shadowOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...fillOptionsDef,
|
|
...rangeAreaSeriesLineThemeableOptionsDef,
|
|
item: {
|
|
low: { ...rangeAreaSeriesItemLineThemeableOptionsDef },
|
|
high: { ...rangeAreaSeriesItemLineThemeableOptionsDef }
|
|
},
|
|
styler: callbackDefs({
|
|
...fillOptionsDef,
|
|
item: {
|
|
low: { ...rangeAreaSeriesLineStyleDef },
|
|
high: { ...rangeAreaSeriesLineStyleDef }
|
|
}
|
|
}),
|
|
highlight: multiSeriesHighlightOptionsDef(shapeHighlightOptionsDef, shapeHighlightOptionsDef),
|
|
segmentation: shapeSegmentation,
|
|
invertedStyle: {
|
|
enabled: boolean,
|
|
...fillOptionsDef
|
|
}
|
|
};
|
|
var rangeBarStyleCallback = callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
cornerRadius: positiveNumber
|
|
});
|
|
var rangeBarSeriesThemeableOptionsDef = {
|
|
direction: union("horizontal", "vertical"),
|
|
grouped: boolean,
|
|
showInMiniChart: boolean,
|
|
cornerRadius: positiveNumber,
|
|
styler: rangeBarStyleCallback,
|
|
itemStyler: rangeBarStyleCallback,
|
|
label: {
|
|
...seriesLabelOptionsDefs,
|
|
placement: union("inside", "outside"),
|
|
spacing: positiveNumber
|
|
},
|
|
tooltip: tooltipOptionsDefs,
|
|
shadow: shadowOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
highlight: multiSeriesHighlightOptionsDef(barHighlightOptionsDef, barHighlightOptionsDef),
|
|
segmentation: shapeSegmentation,
|
|
width: positiveNumberNonZero,
|
|
widthRatio: ratio
|
|
};
|
|
var sankeySeriesThemeableOptionsDef = {
|
|
fills: arrayOf(colorUnion),
|
|
strokes: arrayOf(color),
|
|
label: {
|
|
...seriesLabelOptionsDefs,
|
|
spacing: positiveNumber,
|
|
placement: union("left", "right", "center"),
|
|
edgePlacement: union("inside", "outside")
|
|
},
|
|
link: {
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
}),
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
node: {
|
|
width: positiveNumber,
|
|
spacing: positiveNumber,
|
|
minSpacing: and(positiveNumber, lessThanOrEqual("spacing")),
|
|
alignment: union("left", "center", "right", "justify"),
|
|
verticalAlignment: union("top", "bottom", "center"),
|
|
sort: union("data", "ascending", "descending", "auto"),
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
}),
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
tooltip: tooltipOptionsDefs,
|
|
...commonSeriesThemeableOptionsDefs
|
|
};
|
|
var sunburstSeriesThemeableOptionsDef = {
|
|
fills: arrayOf(colorUnion),
|
|
strokes: arrayOf(color),
|
|
colorRange: arrayOf(color),
|
|
sectorSpacing: positiveNumber,
|
|
cornerRadius: positiveNumber,
|
|
padding: positiveNumber,
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef
|
|
}),
|
|
label: {
|
|
spacing: positiveNumber,
|
|
...autoSizedLabelOptionsDefs
|
|
},
|
|
secondaryLabel: autoSizedLabelOptionsDefs,
|
|
tooltip: tooltipOptionsDefs,
|
|
...without(commonSeriesThemeableOptionsDefs, ["highlight", "showInLegend"]),
|
|
...without(fillOptionsDef, ["fill"]),
|
|
...without(strokeOptionsDef, ["stroke"]),
|
|
highlight: {
|
|
highlightedItem: hierarchyHighlightStyleOptionsDef,
|
|
highlightedBranch: hierarchyHighlightStyleOptionsDef,
|
|
unhighlightedItem: hierarchyHighlightStyleOptionsDef,
|
|
unhighlightedBranch: hierarchyHighlightStyleOptionsDef
|
|
}
|
|
};
|
|
var treemapSeriesThemeableOptionsDef = {
|
|
fills: arrayOf(colorUnion),
|
|
strokes: arrayOf(color),
|
|
colorRange: arrayOf(color),
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef
|
|
}),
|
|
group: {
|
|
gap: positiveNumber,
|
|
padding: positiveNumber,
|
|
cornerRadius: positiveNumber,
|
|
textAlign: union("left", "center", "right"),
|
|
interactive: boolean,
|
|
highlight: {
|
|
highlightedItem: hierarchyHighlightStyleOptionsDef,
|
|
unhighlightedItem: hierarchyHighlightStyleOptionsDef
|
|
},
|
|
label: {
|
|
...seriesLabelOptionsDefs,
|
|
spacing: positiveNumber
|
|
},
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef
|
|
},
|
|
tile: {
|
|
gap: positiveNumber,
|
|
padding: positiveNumber,
|
|
cornerRadius: positiveNumber,
|
|
textAlign: union("left", "center", "right"),
|
|
verticalAlign: union("top", "middle", "bottom"),
|
|
label: {
|
|
...seriesLabelOptionsDefs,
|
|
spacing: positiveNumber,
|
|
lineHeight: positiveNumber,
|
|
minimumFontSize: positiveNumber,
|
|
wrapping: union("never", "always", "hyphenate", "on-space"),
|
|
overflowStrategy: union("ellipsis", "hide")
|
|
},
|
|
secondaryLabel: {
|
|
...seriesLabelOptionsDefs,
|
|
lineHeight: positiveNumber,
|
|
minimumFontSize: positiveNumber,
|
|
wrapping: union("never", "always", "hyphenate", "on-space"),
|
|
overflowStrategy: union("ellipsis", "hide")
|
|
},
|
|
highlight: {
|
|
highlightedItem: hierarchyHighlightStyleOptionsDef,
|
|
highlightedBranch: hierarchyHighlightStyleOptionsDef,
|
|
unhighlightedItem: hierarchyHighlightStyleOptionsDef,
|
|
unhighlightedBranch: hierarchyHighlightStyleOptionsDef
|
|
},
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef
|
|
},
|
|
tooltip: tooltipOptionsDefs,
|
|
...without(commonSeriesThemeableOptionsDefs, ["highlight", "showInLegend"])
|
|
};
|
|
var waterfallSeriesItemOptionsDef = {
|
|
name: string,
|
|
cornerRadius: positiveNumber,
|
|
itemStyler: callbackDefs({
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
cornerRadius: positiveNumber
|
|
}),
|
|
label: {
|
|
...seriesLabelOptionsDefs,
|
|
placement: union("inside-start", "inside-center", "inside-end", "outside-start", "outside-end"),
|
|
spacing: positiveNumber
|
|
},
|
|
tooltip: tooltipOptionsDefs,
|
|
shadow: shadowOptionsDefs,
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
};
|
|
var waterfallSeriesThemeableOptionsDef = {
|
|
direction: union("horizontal", "vertical"),
|
|
showInMiniChart: boolean,
|
|
item: {
|
|
positive: waterfallSeriesItemOptionsDef,
|
|
negative: waterfallSeriesItemOptionsDef,
|
|
total: waterfallSeriesItemOptionsDef
|
|
},
|
|
line: {
|
|
enabled: boolean,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
tooltip: tooltipOptionsDefs,
|
|
width: positiveNumberNonZero,
|
|
widthRatio: ratio,
|
|
...commonSeriesThemeableOptionsDefs
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/themes/themeOptionsDef.ts
|
|
var serializableDate = optionsDefs(
|
|
{
|
|
__type: required(constant("date")),
|
|
value: or(string, number)
|
|
},
|
|
"a serializable date object"
|
|
);
|
|
var navigatorHandleOptionsDef = {
|
|
width: positiveNumber,
|
|
height: positiveNumber,
|
|
grip: boolean,
|
|
fill: color,
|
|
stroke: color,
|
|
strokeWidth: positiveNumber,
|
|
cornerRadius: positiveNumber
|
|
};
|
|
var navigatorOptionsDef = {
|
|
enabled: boolean,
|
|
height: positiveNumber,
|
|
spacing: positiveNumber,
|
|
cornerRadius: number,
|
|
mask: {
|
|
fill: color,
|
|
fillOpacity: ratio,
|
|
stroke: color,
|
|
strokeWidth: positiveNumber
|
|
},
|
|
minHandle: navigatorHandleOptionsDef,
|
|
maxHandle: navigatorHandleOptionsDef,
|
|
miniChart: {
|
|
enabled: boolean,
|
|
padding: {
|
|
top: positiveNumber,
|
|
bottom: positiveNumber
|
|
},
|
|
label: {
|
|
enabled: boolean,
|
|
avoidCollisions: boolean,
|
|
spacing: positiveNumber,
|
|
format: numberFormatValidator,
|
|
formatter: callbackOf(textOrSegments),
|
|
interval: {
|
|
minSpacing: positiveNumber,
|
|
maxSpacing: positiveNumber,
|
|
values: array,
|
|
step: number
|
|
},
|
|
...fontOptionsDef
|
|
},
|
|
series: defined
|
|
}
|
|
};
|
|
var scrollbarTrackOptionsDef = {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
cornerRadius: positiveNumber,
|
|
opacity: ratio
|
|
};
|
|
var scrollbarThumbOptionsDef = {
|
|
...scrollbarTrackOptionsDef,
|
|
minSize: positiveNumber,
|
|
hoverStyle: {
|
|
fill: fillOptionsDef.fill,
|
|
stroke: strokeOptionsDef.stroke
|
|
}
|
|
};
|
|
var scrollbarBaseOptionsDef = {
|
|
enabled: boolean,
|
|
thickness: positiveNumber,
|
|
spacing: positiveNumber,
|
|
tickSpacing: positiveNumber,
|
|
visible: union("auto", "always", "never"),
|
|
placement: union("outer", "inner"),
|
|
track: scrollbarTrackOptionsDef,
|
|
thumb: scrollbarThumbOptionsDef
|
|
};
|
|
var scrollbarHorizontalOrientationOptionsDef = {
|
|
...scrollbarBaseOptionsDef,
|
|
position: union("top", "bottom")
|
|
};
|
|
var scrollbarVerticalOrientationOptionsDef = {
|
|
...scrollbarBaseOptionsDef,
|
|
position: union("left", "right")
|
|
};
|
|
var scrollbarOptionsDef = {
|
|
enabled: boolean,
|
|
thickness: positiveNumber,
|
|
spacing: positiveNumber,
|
|
tickSpacing: positiveNumber,
|
|
visible: union("auto", "always", "never"),
|
|
placement: union("outer", "inner"),
|
|
track: scrollbarTrackOptionsDef,
|
|
thumb: scrollbarThumbOptionsDef,
|
|
horizontal: scrollbarHorizontalOrientationOptionsDef,
|
|
vertical: scrollbarVerticalOrientationOptionsDef
|
|
};
|
|
var cartesianCrossLineThemeableOptionsDefs = without(cartesianCrossLineOptionsDefs, ["type", "value", "range"]);
|
|
var cartesianAxesThemeDef = {
|
|
number: {
|
|
...without(numberAxisOptionsDefs, ["type", "crossLines"]),
|
|
top: without(numberAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
right: without(numberAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
bottom: without(numberAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
left: without(numberAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
crossLines: cartesianCrossLineThemeableOptionsDefs
|
|
},
|
|
log: {
|
|
...without(logAxisOptionsDefs, ["type", "crossLines"]),
|
|
top: without(logAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
right: without(logAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
bottom: without(logAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
left: without(logAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
crossLines: cartesianCrossLineThemeableOptionsDefs
|
|
},
|
|
category: {
|
|
...without(categoryAxisOptionsDefs, ["type", "crossLines"]),
|
|
top: without(categoryAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
right: without(categoryAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
bottom: without(categoryAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
left: without(categoryAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
crossLines: cartesianCrossLineThemeableOptionsDefs
|
|
},
|
|
time: {
|
|
...without(timeAxisOptionsDefs, ["type", "crossLines"]),
|
|
top: without(timeAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
right: without(timeAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
bottom: without(timeAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
left: without(timeAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
crossLines: cartesianCrossLineThemeableOptionsDefs
|
|
},
|
|
"unit-time": {
|
|
...without(unitTimeAxisOptionsDefs, ["type", "crossLines"]),
|
|
top: without(unitTimeAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
right: without(unitTimeAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
bottom: without(unitTimeAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
left: without(unitTimeAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
crossLines: cartesianCrossLineThemeableOptionsDefs
|
|
},
|
|
"grouped-category": {
|
|
...without(groupedCategoryAxisOptionsDefs, ["type"]),
|
|
top: without(groupedCategoryAxisOptionsDefs, ["type", "position"]),
|
|
right: without(groupedCategoryAxisOptionsDefs, ["type", "position"]),
|
|
bottom: without(groupedCategoryAxisOptionsDefs, ["type", "position"]),
|
|
left: without(groupedCategoryAxisOptionsDefs, ["type", "position"]),
|
|
crossLines: cartesianCrossLineThemeableOptionsDefs
|
|
},
|
|
"ordinal-time": {
|
|
...without(ordinalTimeAxisOptionsDefs, ["type", "crossLines"]),
|
|
top: without(ordinalTimeAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
right: without(ordinalTimeAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
bottom: without(ordinalTimeAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
left: without(ordinalTimeAxisOptionsDefs, ["type", "crossLines", "position"]),
|
|
crossLines: cartesianCrossLineThemeableOptionsDefs
|
|
}
|
|
};
|
|
var polarAxesThemeDef = {
|
|
"angle-category": {
|
|
...without(angleCategoryAxisOptionsDefs, ["type", "crossLines"]),
|
|
crossLines: without(commonCrossLineOptionsDefs, ["type"])
|
|
},
|
|
"angle-number": {
|
|
...without(angleNumberAxisOptionsDefs, ["type", "crossLines"]),
|
|
crossLines: without(commonCrossLineOptionsDefs, ["type"])
|
|
},
|
|
"radius-category": {
|
|
...without(radiusCategoryAxisOptionsDefs, ["type", "crossLines"]),
|
|
crossLines: {
|
|
...without(commonCrossLineOptionsDefs, ["type"]),
|
|
label: {
|
|
...commonCrossLineLabelOptionsDefs,
|
|
positionAngle: number
|
|
}
|
|
}
|
|
},
|
|
"radius-number": {
|
|
...without(radiusNumberAxisOptionsDefs, ["type", "crossLines"]),
|
|
crossLines: {
|
|
...without(commonCrossLineOptionsDefs, ["type"]),
|
|
label: {
|
|
...commonCrossLineLabelOptionsDefs,
|
|
positionAngle: number
|
|
}
|
|
}
|
|
}
|
|
};
|
|
var undocumentedSeriesOptionsDef = {
|
|
visible: undocumented(boolean)
|
|
};
|
|
var themeOverridesOptionsDef = {
|
|
common: {
|
|
...commonChartOptionsDefs,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
axes: {
|
|
...cartesianAxesThemeDef,
|
|
...polarAxesThemeDef
|
|
},
|
|
annotations: {
|
|
...annotationOptionsDef,
|
|
line: annotationLineStyleDefs,
|
|
"horizontal-line": annotationCrossLineStyleDefs,
|
|
"vertical-line": annotationCrossLineStyleDefs,
|
|
"disjoint-channel": annotationDisjointChannelStyleDefs,
|
|
"parallel-channel": annotationParallelChannelStyleDefs,
|
|
"fibonacci-retracement": annotationFibonacciStylesDefs,
|
|
"fibonacci-retracement-trend-based": annotationFibonacciStylesDefs,
|
|
callout: annotationCalloutStylesDefs,
|
|
comment: annotationCommentStylesDefs,
|
|
note: annotationNoteStylesDefs,
|
|
text: annotationTextStylesDef,
|
|
arrow: annotationLineStyleDefs,
|
|
"arrow-up": annotationShapeStylesDefs,
|
|
"arrow-down": annotationShapeStylesDefs,
|
|
"date-range": annotationMeasurerStylesDefs,
|
|
"price-range": annotationMeasurerStylesDefs,
|
|
"date-price-range": annotationMeasurerStylesDefs,
|
|
"quick-date-price-range": annotationQuickMeasurerStylesDefs
|
|
},
|
|
chartToolbar: {
|
|
enabled: boolean
|
|
},
|
|
initialState: {
|
|
legend: arrayOfDefs(
|
|
{
|
|
visible: boolean,
|
|
seriesId: string,
|
|
itemId: string,
|
|
legendItemName: string
|
|
},
|
|
"legend state array"
|
|
),
|
|
zoom: {
|
|
rangeX: {
|
|
start: or(number, serializableDate),
|
|
end: or(number, serializableDate)
|
|
},
|
|
rangeY: {
|
|
start: or(number, serializableDate),
|
|
end: or(number, serializableDate)
|
|
},
|
|
ratioX: {
|
|
start: ratio,
|
|
end: ratio
|
|
},
|
|
ratioY: {
|
|
start: ratio,
|
|
end: ratio
|
|
},
|
|
autoScaledAxes: arrayOf(constant("y"))
|
|
}
|
|
}
|
|
},
|
|
line: {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: lineSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
scatter: {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: scatterSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef,
|
|
// @ts-expect-error undocumented option - required by grid
|
|
paired: undocumented(boolean)
|
|
},
|
|
bubble: {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: bubbleSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
area: {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: areaSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
bar: {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: barSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"box-plot": {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: boxPlotSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
candlestick: {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: candlestickSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"cone-funnel": {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: coneFunnelSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
funnel: {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: funnelSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
ohlc: {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: ohlcSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
histogram: {
|
|
...commonChartOptionsDefs,
|
|
axes: without(cartesianAxesThemeDef, ["category", "grouped-category", "unit-time", "ordinal-time"]),
|
|
series: histogramSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
heatmap: {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: heatmapSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
waterfall: {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: waterfallSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"range-bar": {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: rangeBarSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"range-area": {
|
|
...commonChartOptionsDefs,
|
|
axes: cartesianAxesThemeDef,
|
|
series: rangeAreaSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
scrollbar: scrollbarOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
donut: {
|
|
...commonChartOptionsDefs,
|
|
series: donutSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
pie: {
|
|
...commonChartOptionsDefs,
|
|
series: pieSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"radar-line": {
|
|
...commonChartOptionsDefs,
|
|
axes: polarAxesThemeDef,
|
|
series: radarLineSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"radar-area": {
|
|
...commonChartOptionsDefs,
|
|
axes: polarAxesThemeDef,
|
|
series: radarAreaSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"radial-bar": {
|
|
...commonChartOptionsDefs,
|
|
axes: polarAxesThemeDef,
|
|
series: radialBarSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"radial-column": {
|
|
...commonChartOptionsDefs,
|
|
axes: polarAxesThemeDef,
|
|
series: radialColumnSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
nightingale: {
|
|
...commonChartOptionsDefs,
|
|
axes: polarAxesThemeDef,
|
|
series: nightingaleSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
sunburst: {
|
|
...commonChartOptionsDefs,
|
|
series: sunburstSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
treemap: {
|
|
...commonChartOptionsDefs,
|
|
series: treemapSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"map-shape": {
|
|
...commonChartOptionsDefs,
|
|
series: mapShapeSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"map-line": {
|
|
...commonChartOptionsDefs,
|
|
series: mapLineSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"map-marker": {
|
|
...commonChartOptionsDefs,
|
|
series: mapMarkerSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"map-shape-background": {
|
|
...commonChartOptionsDefs,
|
|
series: mapShapeBackgroundSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"map-line-background": {
|
|
...commonChartOptionsDefs,
|
|
series: mapLineBackgroundSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
sankey: {
|
|
...commonChartOptionsDefs,
|
|
series: sankeySeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
chord: {
|
|
...commonChartOptionsDefs,
|
|
series: chordSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
pyramid: {
|
|
...commonChartOptionsDefs,
|
|
series: pyramidSeriesThemeableOptionsDef,
|
|
navigator: navigatorOptionsDef,
|
|
...undocumentedSeriesOptionsDef
|
|
},
|
|
"radial-gauge": {
|
|
...commonChartOptionsDefs,
|
|
...radialGaugeSeriesThemeableOptionsDef,
|
|
targets: without(radialGaugeTargetOptionsDef, ["value"]),
|
|
tooltip: {
|
|
...radialGaugeSeriesThemeableOptionsDef.tooltip,
|
|
...commonChartOptionsDefs.tooltip
|
|
}
|
|
},
|
|
"linear-gauge": {
|
|
...commonChartOptionsDefs,
|
|
...linearGaugeSeriesThemeableOptionsDef,
|
|
targets: without(linearGaugeTargetOptionsDef, ["value"]),
|
|
tooltip: {
|
|
...linearGaugeSeriesThemeableOptionsDef.tooltip,
|
|
...commonChartOptionsDefs.tooltip
|
|
}
|
|
}
|
|
};
|
|
function mapValues2(object2, mapper) {
|
|
const result = {};
|
|
for (const key of Reflect.ownKeys(object2)) {
|
|
result[key] = mapper(object2[key], key, object2);
|
|
}
|
|
return result;
|
|
}
|
|
var themeOverridesOptionsWithOperatorsDef = mapValues2(
|
|
themeOverridesOptionsDef,
|
|
function themeOperatorMapper(value, key) {
|
|
if (isSymbol(key))
|
|
return value;
|
|
if (isFunction(value)) {
|
|
return or(value, themeOperator, isSymbol);
|
|
} else if (isObject(value)) {
|
|
return or(
|
|
optionsDefs(
|
|
unionSymbol in value ? mapValues2(value, (val) => isObject(val) ? mapValues2(val, themeOperatorMapper) : val) : mapValues2(value, themeOperatorMapper)
|
|
),
|
|
themeOperator,
|
|
isSymbol
|
|
);
|
|
}
|
|
throw new Error(`Invalid theme override value: ${String(value)}`);
|
|
}
|
|
);
|
|
|
|
// packages/ag-charts-community/src/chart/themes/vividDark.ts
|
|
var VIVID_DARK_FILLS = {
|
|
BLUE: "#0083ff",
|
|
ORANGE: "#ff6600",
|
|
GREEN: "#00af00",
|
|
CYAN: "#00ccff",
|
|
YELLOW: "#f7c700",
|
|
VIOLET: "#ac26ff",
|
|
GRAY: "#a7a7b7",
|
|
MAGENTA: "#e800c5",
|
|
BROWN: "#b54300",
|
|
RED: "#ff0000"
|
|
};
|
|
var VIVID_DARK_STROKES = {
|
|
BLUE: "#67b7ff",
|
|
ORANGE: "#ffc24d",
|
|
GREEN: "#5cc86f",
|
|
CYAN: "#54ebff",
|
|
VIOLET: "#fff653",
|
|
YELLOW: "#c18aff",
|
|
GRAY: "#aeaeae",
|
|
MAGENTA: "#f078d4",
|
|
BROWN: "#ba8438",
|
|
RED: "#ff726e"
|
|
};
|
|
var VividDark = class extends DarkTheme {
|
|
getDefaultColors() {
|
|
return {
|
|
fills: VIVID_DARK_FILLS,
|
|
fillsFallback: Object.values(VIVID_DARK_FILLS),
|
|
strokes: VIVID_DARK_STROKES,
|
|
sequentialColors: getSequentialColors(VIVID_DARK_FILLS),
|
|
divergingColors: [VIVID_DARK_FILLS.ORANGE, VIVID_DARK_FILLS.YELLOW, VIVID_DARK_FILLS.GREEN],
|
|
hierarchyColors: [],
|
|
secondSequentialColors: [
|
|
"#0083ff",
|
|
"#0076e6",
|
|
"#0069cc",
|
|
"#005cb3",
|
|
"#004f99",
|
|
"#004280",
|
|
"#003466",
|
|
"#00274c"
|
|
],
|
|
secondDivergingColors: [VIVID_DARK_FILLS.GREEN, VIVID_DARK_FILLS.YELLOW, VIVID_DARK_FILLS.RED],
|
|
secondHierarchyColors: [],
|
|
up: { fill: VIVID_DARK_FILLS.GREEN, stroke: VIVID_DARK_STROKES.GREEN },
|
|
down: { fill: VIVID_DARK_FILLS.RED, stroke: VIVID_DARK_STROKES.RED },
|
|
neutral: { fill: VIVID_DARK_FILLS.GRAY, stroke: VIVID_DARK_STROKES.GRAY },
|
|
altUp: { fill: VIVID_DARK_FILLS.BLUE, stroke: VIVID_DARK_STROKES.BLUE },
|
|
altDown: { fill: VIVID_DARK_FILLS.ORANGE, stroke: VIVID_DARK_STROKES.ORANGE },
|
|
altNeutral: { fill: VIVID_DARK_FILLS.GRAY, stroke: VIVID_DARK_STROKES.GRAY }
|
|
};
|
|
}
|
|
getTemplateParameters() {
|
|
const params = super.getTemplateParameters();
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR, VIVID_DARK_FILLS.BLUE);
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL, VIVID_DARK_FILLS.BLUE);
|
|
return params;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/themes/vividLight.ts
|
|
var VIVID_FILLS = {
|
|
BLUE: "#0083ff",
|
|
ORANGE: "#ff6600",
|
|
GREEN: "#00af00",
|
|
CYAN: "#00ccff",
|
|
YELLOW: "#f7c700",
|
|
VIOLET: "#ac26ff",
|
|
GRAY: "#a7a7b7",
|
|
MAGENTA: "#e800c5",
|
|
BROWN: "#b54300",
|
|
RED: "#ff0000"
|
|
};
|
|
var VIVID_STROKES = {
|
|
BLUE: "#0f68c0",
|
|
ORANGE: "#d47100",
|
|
GREEN: "#007922",
|
|
CYAN: "#009ac2",
|
|
VIOLET: "#bca400",
|
|
YELLOW: "#753cac",
|
|
GRAY: "#646464",
|
|
MAGENTA: "#9b2685",
|
|
BROWN: "#6c3b00",
|
|
RED: "#cb0021"
|
|
};
|
|
var VividLight = class extends ChartTheme {
|
|
getDefaultColors() {
|
|
return {
|
|
...super.getDefaultColors(),
|
|
fills: VIVID_FILLS,
|
|
fillsFallback: Object.values(VIVID_FILLS),
|
|
strokes: VIVID_STROKES,
|
|
sequentialColors: getSequentialColors(VIVID_FILLS),
|
|
divergingColors: [VIVID_FILLS.ORANGE, VIVID_FILLS.YELLOW, VIVID_FILLS.GREEN],
|
|
hierarchyColors: [],
|
|
secondSequentialColors: [
|
|
"#0083ff",
|
|
"#1a8fff",
|
|
"#339cff",
|
|
"#4da8ff",
|
|
"#66b5ff",
|
|
"#80c1ff",
|
|
"#99cdff",
|
|
"#b3daff"
|
|
],
|
|
secondDivergingColors: [VIVID_FILLS.GREEN, VIVID_FILLS.YELLOW, VIVID_FILLS.RED],
|
|
secondHierarchyColors: [],
|
|
up: { fill: VIVID_FILLS.GREEN, stroke: VIVID_STROKES.GREEN },
|
|
down: { fill: VIVID_FILLS.RED, stroke: VIVID_STROKES.RED },
|
|
neutral: { fill: VIVID_FILLS.GRAY, stroke: VIVID_STROKES.GRAY },
|
|
altUp: { fill: VIVID_FILLS.BLUE, stroke: VIVID_STROKES.BLUE },
|
|
altDown: { fill: VIVID_FILLS.ORANGE, stroke: VIVID_STROKES.ORANGE },
|
|
altNeutral: { fill: VIVID_FILLS.GRAY, stroke: VIVID_STROKES.GRAY }
|
|
};
|
|
}
|
|
getTemplateParameters() {
|
|
const params = super.getTemplateParameters();
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR, VIVID_FILLS.BLUE);
|
|
params.set(DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL, VIVID_FILLS.BLUE);
|
|
return params;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/mapping/themes.ts
|
|
var lightTheme = simpleMemorize(() => new ChartTheme());
|
|
var themeCacheDebug = debugLogger_exports.create(true, "perf");
|
|
var cacheCallback = (status, fn, keys) => {
|
|
themeCacheDebug(`[CACHE] ChartTheme`, status, fn.name, keys);
|
|
};
|
|
var themes = {
|
|
// darkThemes,
|
|
"ag-default-dark": simpleMemorize(() => new DarkTheme()),
|
|
"ag-sheets-dark": simpleMemorize(() => new SheetsDark(), cacheCallback),
|
|
"ag-polychroma-dark": simpleMemorize(() => new PolychromaDark(), cacheCallback),
|
|
"ag-vivid-dark": simpleMemorize(() => new VividDark(), cacheCallback),
|
|
"ag-material-dark": simpleMemorize(() => new MaterialDark(), cacheCallback),
|
|
"ag-financial-dark": simpleMemorize(() => new FinancialDark(), cacheCallback),
|
|
// lightThemes,
|
|
"ag-default": lightTheme,
|
|
"ag-sheets": simpleMemorize(() => new SheetsLight(), cacheCallback),
|
|
"ag-polychroma": simpleMemorize(() => new PolychromaLight(), cacheCallback),
|
|
"ag-vivid": simpleMemorize(() => new VividLight(), cacheCallback),
|
|
"ag-material": simpleMemorize(() => new MaterialLight(), cacheCallback),
|
|
"ag-financial": simpleMemorize(() => new FinancialLight(), cacheCallback)
|
|
};
|
|
var getChartTheme = simpleMemorize(createChartTheme, cacheCallback);
|
|
function createChartTheme(value) {
|
|
if (value instanceof ChartTheme) {
|
|
return value;
|
|
} else if (!validateStructure(value)) {
|
|
return lightTheme();
|
|
}
|
|
if (value == null || typeof value === "string") {
|
|
const stockTheme = themes[value ?? "ag-default"];
|
|
if (stockTheme) {
|
|
return stockTheme();
|
|
}
|
|
throw new Error(`Cannot find theme \`${value}\`.`);
|
|
}
|
|
const { cleared, invalid } = validate(reduceThemeOptions(value), themeOptionsDef, "theme");
|
|
for (const error2 of invalid) {
|
|
logger_exports.warnOnce(String(error2));
|
|
}
|
|
const baseTheme = cleared?.baseTheme ? getChartTheme(cleared.baseTheme) : lightTheme();
|
|
return cleared ? new baseTheme.constructor(cleared) : baseTheme;
|
|
}
|
|
function reduceThemeOptions(options) {
|
|
if (!isObject(options) || !isObject(options.baseTheme))
|
|
return options;
|
|
let maybeNested = options;
|
|
let palette;
|
|
let params;
|
|
const overrides = [];
|
|
while (typeof maybeNested === "object") {
|
|
palette ?? (palette = maybeNested.palette);
|
|
params ?? (params = maybeNested.params);
|
|
if (maybeNested.overrides) {
|
|
overrides.push(maybeNested.overrides);
|
|
}
|
|
maybeNested = maybeNested.baseTheme;
|
|
}
|
|
return {
|
|
baseTheme: maybeNested,
|
|
overrides: mergeDefaults(...overrides),
|
|
params,
|
|
palette
|
|
};
|
|
}
|
|
var themeOptionsDef = {
|
|
baseTheme: or(string, object),
|
|
overrides: themeOverridesOptionsWithOperatorsDef,
|
|
params: {
|
|
accentColor: color,
|
|
axisColor: color,
|
|
backgroundColor: color,
|
|
borderColor: color,
|
|
borderRadius: number,
|
|
chartBackgroundColor: color,
|
|
chartPadding: number,
|
|
focusShadow: string,
|
|
foregroundColor: color,
|
|
fontFamily: fontFamilyFull,
|
|
fontSize: number,
|
|
fontWeight,
|
|
gridLineColor: color,
|
|
popupShadow: string,
|
|
subtleTextColor: color,
|
|
textColor: color,
|
|
separationLinesColor: color,
|
|
chromeBackgroundColor: color,
|
|
chromeFontFamily: fontFamilyFull,
|
|
chromeFontSize: number,
|
|
chromeFontWeight: fontWeight,
|
|
chromeSubtleTextColor: color,
|
|
chromeTextColor: color,
|
|
buttonBackgroundColor: color,
|
|
buttonBorder: boolean,
|
|
buttonFontWeight: fontWeight,
|
|
buttonTextColor: color,
|
|
inputBackgroundColor: color,
|
|
inputBorder: boolean,
|
|
inputTextColor: color,
|
|
menuBackgroundColor: color,
|
|
menuBorder: boolean,
|
|
menuTextColor: color,
|
|
panelBackgroundColor: color,
|
|
panelSubtleTextColor: color,
|
|
tooltipBackgroundColor: color,
|
|
tooltipBorder: boolean,
|
|
tooltipTextColor: color,
|
|
tooltipSubtleTextColor: color,
|
|
crosshairLabelBackgroundColor: color,
|
|
crosshairLabelTextColor: color
|
|
},
|
|
palette: {
|
|
fills: arrayOf(colorUnion),
|
|
strokes: arrayOf(color),
|
|
up: { fill: or(color, gradientStrict), stroke: color },
|
|
down: { fill: or(color, gradientStrict), stroke: color },
|
|
neutral: { fill: or(color, gradientStrict), stroke: color }
|
|
}
|
|
};
|
|
var themeNameValidator = union(
|
|
"ag-default",
|
|
"ag-default-dark",
|
|
"ag-sheets",
|
|
"ag-sheets-dark",
|
|
"ag-polychroma",
|
|
"ag-polychroma-dark",
|
|
"ag-vivid",
|
|
"ag-vivid-dark",
|
|
"ag-material",
|
|
"ag-material-dark",
|
|
"ag-financial",
|
|
"ag-financial-dark"
|
|
);
|
|
function validateStructure(value) {
|
|
const { invalid } = validate(
|
|
{ theme: value },
|
|
{ theme: or(themeNameValidator, object) }
|
|
);
|
|
for (const error2 of invalid) {
|
|
logger_exports.warnOnce(String(error2));
|
|
}
|
|
return invalid.length === 0;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/module/optionsGraphUtils.ts
|
|
var PATH_EDGE = "path";
|
|
var PATH_ARRAY_EDGE = "pathArray";
|
|
var DEFAULTS_EDGE = "default";
|
|
var OVERRIDES_EDGE = "override";
|
|
var USER_OPTIONS_EDGE = "user";
|
|
var USER_PARTIAL_OPTIONS_EDGE = "userPartial";
|
|
var OPERATION_EDGE = "operation";
|
|
var OPERATION_VALUE_EDGE = "operationValue";
|
|
var DEPENDENCY_EDGE = "dependency";
|
|
var AUTO_ENABLE_EDGE = "autoEnable";
|
|
var AUTO_ENABLE_VALUE_EDGE = "autoEnableValue";
|
|
var PRUNE_EDGE = "prune";
|
|
var CHILDREN_SOURCE_EDGE = "childrenSource";
|
|
function isRatio(value) {
|
|
return isNumber(value) && value >= 0 && value <= 1;
|
|
}
|
|
function hasPathSafe(object2, path) {
|
|
let result = object2;
|
|
for (const part of path) {
|
|
const isPartKey = typeof part === "string" && result != null && (typeof result === "object" || Array.isArray(result)) && part in result;
|
|
if (!isPartKey)
|
|
return false;
|
|
result = result[part];
|
|
}
|
|
return true;
|
|
}
|
|
function getPathSafe(object2, path) {
|
|
let result = object2;
|
|
for (const part of path) {
|
|
const isPartKey = typeof part === "string" && result != null && (typeof result === "object" || Array.isArray(result)) && part in result;
|
|
if (!isPartKey)
|
|
return;
|
|
result = result[part];
|
|
}
|
|
return result;
|
|
}
|
|
function setPathSafe(object2, path, value) {
|
|
const pathLength = path.length;
|
|
if (pathLength === 0)
|
|
return;
|
|
let result = object2;
|
|
const lastIndex = pathLength - 1;
|
|
const lastPart = path[lastIndex];
|
|
for (let i = 0; i < lastIndex; i++) {
|
|
const part = path[i];
|
|
const nextPart = path[i + 1];
|
|
let currentValue = result[part];
|
|
if (currentValue == null || !isObjectLike(currentValue)) {
|
|
currentValue = Number.isNaN(Number(nextPart)) ? {} : [];
|
|
result[part] = currentValue;
|
|
}
|
|
result = currentValue;
|
|
}
|
|
result[lastPart] = value;
|
|
}
|
|
var DIGITS_ONLY_REGEX = /^\d+$/;
|
|
function getPathLastIndexIndex(pathArray, offset = 0) {
|
|
let count = 0;
|
|
for (let i = pathArray.length - 1; i >= 0; i--) {
|
|
const part = pathArray[i];
|
|
if (DIGITS_ONLY_REGEX.test(part)) {
|
|
count++;
|
|
if (count > offset)
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
function getPathLastIndex(pathArray, offset = 0) {
|
|
const indexIndex = getPathLastIndexIndex(pathArray, offset);
|
|
return Number(pathArray[indexIndex]);
|
|
}
|
|
function resolvePath(currentPath, path, variables) {
|
|
const relativePathParts = path.split("/");
|
|
let resolvedPath = [...currentPath];
|
|
if (path.startsWith("/")) {
|
|
resolvedPath = [];
|
|
relativePathParts.shift();
|
|
}
|
|
let prevPartWasTwoDots = false;
|
|
for (const part of relativePathParts) {
|
|
if (part === "..") {
|
|
resolvedPath.pop();
|
|
if (!prevPartWasTwoDots)
|
|
resolvedPath.pop();
|
|
} else if (part === ".") {
|
|
resolvedPath.pop();
|
|
} else if (part === "$index") {
|
|
const index = getPathLastIndex(currentPath);
|
|
if (Number.isNaN(index))
|
|
return UNRESOLVABLE_PATH;
|
|
resolvedPath.push(`${index}`);
|
|
} else if (part === "$prevIndex") {
|
|
const index = getPathLastIndex(currentPath);
|
|
if (Number.isNaN(index) || Number(index) <= 0)
|
|
return UNRESOLVABLE_PATH;
|
|
resolvedPath.push(`${Number(index) - 1}`);
|
|
} else if (part.startsWith("$")) {
|
|
const variable = variables?.[part.slice(1)];
|
|
if (variable == null)
|
|
return UNRESOLVABLE_PATH;
|
|
resolvedPath.push(variable);
|
|
} else if (part.length !== 0) {
|
|
resolvedPath.push(part);
|
|
}
|
|
prevPartWasTwoDots = part === "..";
|
|
}
|
|
return resolvedPath;
|
|
}
|
|
var UNRESOLVABLE_PATH = Symbol("unresolvable-path");
|
|
var RESOLVED_TO_BRANCH = Symbol("resolved-to-branch");
|
|
|
|
// packages/ag-charts-community/src/module/optionsGraphOperations.ts
|
|
function getOperation(value, keys) {
|
|
if (value == null || typeof value !== "object" || Array.isArray(value))
|
|
return;
|
|
keys ?? (keys = Object.keys(value));
|
|
if (keys.length === 0)
|
|
return;
|
|
const operation = keys[0];
|
|
if (!operationTypes.has(operation))
|
|
return;
|
|
return {
|
|
operation,
|
|
values: Array.isArray(value[operation]) ? value[operation] : [value[operation]]
|
|
};
|
|
}
|
|
function getOperationTargetVertex(graph, vertex, valueVertex) {
|
|
const operation = getOperation(graph.getVertexValue(valueVertex));
|
|
switch (operation?.operation) {
|
|
case "$path" /* Path */: {
|
|
const [relativePath] = operation.values;
|
|
const pathArray = graph.getPathArray(vertex);
|
|
const path = resolvePath(pathArray, relativePath);
|
|
if (path === UNRESOLVABLE_PATH)
|
|
return;
|
|
return graph.findVertexAtPath(path);
|
|
}
|
|
case "$value" /* Value */:
|
|
return vertex;
|
|
}
|
|
}
|
|
var cacheOperations = {
|
|
$cacheMax: cacheMaxOperation
|
|
};
|
|
function cacheMaxOperation(graph, vertex, values) {
|
|
const [valueVertex] = values;
|
|
const pathArray = graph.getPathArray(vertex);
|
|
const cached = graph.getCachedValue(pathArray, "$cacheMax" /* CacheMax */);
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
if (typeof value !== "number")
|
|
return cached;
|
|
if (typeof cached !== "number") {
|
|
graph.setCachedValue(pathArray, "$cacheMax" /* CacheMax */, value);
|
|
return value;
|
|
}
|
|
const maxValue = Math.max(cached, value);
|
|
graph.setCachedValue(pathArray, "$cacheMax" /* CacheMax */, maxValue);
|
|
return maxValue;
|
|
}
|
|
var chartOperations = {
|
|
$hasSeriesType: { dependencies: seriesTypeDependencyFactory, resolve: hasSeriesTypeOperation },
|
|
$isChartType: { dependencies: seriesTypeDependencyFactory, resolve: isChartTypeOperation },
|
|
$isSeriesType: { dependencies: seriesTypeDependencyFactory, resolve: isSeriesTypeOperation }
|
|
};
|
|
function seriesTypeDependencyFactory(graph, vertex, _values) {
|
|
const dependencyVertex = graph.findVertexAtPath(["series", "0", "type"]);
|
|
if (dependencyVertex) {
|
|
graph.addEdge(vertex, dependencyVertex, DEPENDENCY_EDGE);
|
|
}
|
|
}
|
|
function hasSeriesTypeOperation(graph, vertex, values) {
|
|
const [valueVertex] = values;
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
const series = graph.getResolvedPath(["series"]);
|
|
if (!Array.isArray(series))
|
|
return false;
|
|
for (const s of series) {
|
|
if (s.type === value)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function isSeriesTypeOperation(graph, vertex, values) {
|
|
const [valueVertex] = values;
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
const seriesType = graph.getResolvedPath(["series", "0", "type"]);
|
|
return seriesType === value;
|
|
}
|
|
function isChartTypeOperation(graph, vertex, values) {
|
|
const [valueVertex] = values;
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
const seriesType = graph.getResolvedPath(["series", "0", "type"]);
|
|
if (typeof seriesType !== "string")
|
|
return false;
|
|
const seriesModule = moduleRegistry_exports.getSeriesModule(seriesType);
|
|
if (seriesModule == null)
|
|
return false;
|
|
switch (value) {
|
|
case "cartesian":
|
|
return seriesModule.chartType === "cartesian";
|
|
case "polar":
|
|
return seriesModule.chartType === "polar";
|
|
case "standalone":
|
|
return seriesModule.chartType === "standalone";
|
|
}
|
|
return false;
|
|
}
|
|
var colorOperations = {
|
|
$foregroundBackgroundMix: foregroundBackgroundMixOperation,
|
|
$foregroundOpacity: foregroundOpacityOperation,
|
|
$interpolate: interpolateOperation,
|
|
$isGradient: isGradientOperation,
|
|
$isImage: isImageOperation,
|
|
$isPattern: isPatternOperation,
|
|
$mix: mixOperation
|
|
};
|
|
function foregroundBackgroundMixOperation(graph, vertex, values) {
|
|
const [foregroundRatioVertex] = values;
|
|
const foregroundRatio = graph.resolveVertexValue(vertex, foregroundRatioVertex);
|
|
const foregroundColor = graph.getParamValue("foregroundColor");
|
|
const backgroundColor = graph.getParamValue("backgroundColor");
|
|
if (typeof foregroundColor === "string" && typeof backgroundColor === "string" && isRatio(foregroundRatio)) {
|
|
return Color.mix(
|
|
Color.fromString(foregroundColor),
|
|
Color.fromString(backgroundColor),
|
|
1 - foregroundRatio
|
|
).toString();
|
|
}
|
|
debugLogger_exports.inDevelopmentMode(
|
|
() => logger_exports.warnOnce(
|
|
`\`$foregroundBackgroundMix\` json operation failed on [${String(foregroundRatio)}}}] at [${graph.getPathArray(vertex).join(".")}], expecting a number between 0 and 1.`
|
|
)
|
|
);
|
|
}
|
|
function foregroundOpacityOperation(graph, vertex, values) {
|
|
const [opacityVertex] = values;
|
|
const opacity = graph.resolveVertexValue(vertex, opacityVertex);
|
|
const foregroundColor = graph.getParamValue("foregroundColor");
|
|
if (typeof foregroundColor === "string" && isRatio(opacity)) {
|
|
const color2 = Color.fromString(foregroundColor);
|
|
return new Color(color2.r, color2.g, color2.b, opacity).toString();
|
|
}
|
|
debugLogger_exports.inDevelopmentMode(
|
|
() => logger_exports.warnOnce(
|
|
`\`$foregroundOpacity\` json operation failed on [${String(opacity)}}}] at [${graph.getPathArray(vertex).join(".")}], expecting a number between 0 and 1.`
|
|
)
|
|
);
|
|
}
|
|
function interpolateOperation(graph, vertex, values) {
|
|
const [colorsVertex, countVertex] = values;
|
|
const colors = graph.resolveVertexValue(vertex, colorsVertex);
|
|
const count = graph.resolveVertexValue(vertex, countVertex);
|
|
if (!isArray(colors) || !isNumber(count))
|
|
return;
|
|
return Color.interpolate(
|
|
colors.map((color2) => Color.fromString(color2)),
|
|
count
|
|
).map((color2) => color2.toString());
|
|
}
|
|
function isGradientOperation(graph, vertex, values) {
|
|
const [valueVertex] = values;
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
return isGradientFill(value);
|
|
}
|
|
function isImageOperation(graph, vertex, values) {
|
|
const [valueVertex] = values;
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
return isImageFill(value);
|
|
}
|
|
function isPatternOperation(graph, vertex, values) {
|
|
const [valueVertex] = values;
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
return isPatternFill(value);
|
|
}
|
|
function mixOperation(graph, vertex, values) {
|
|
const [colorAVertex, colorBVertex, ratioVertex] = values;
|
|
const colorA = graph.resolveVertexValue(vertex, colorAVertex);
|
|
const colorB = graph.resolveVertexValue(vertex, colorBVertex);
|
|
const ratio2 = graph.resolveVertexValue(vertex, ratioVertex);
|
|
const pathArray = graph.getPathArray(vertex);
|
|
const warningPrefix = `\`$mix\` json operation failed on [${String(colorA)}, ${String(colorB)}, ${String(ratio2)}] at [${pathArray.join(".")}], expecting`;
|
|
const warningMessage = `${warningPrefix} two colors and a number between 0 and 1.`;
|
|
if (typeof colorB !== "string" || !isRatio(ratio2)) {
|
|
debugLogger_exports.inDevelopmentMode(() => logger_exports.warnOnce(warningMessage));
|
|
return;
|
|
}
|
|
if (typeof colorA === "string") {
|
|
try {
|
|
return Color.mix(Color.fromString(colorA), Color.fromString(colorB), ratio2).toString();
|
|
} catch {
|
|
debugLogger_exports.inDevelopmentMode(() => logger_exports.warnOnce(warningMessage));
|
|
return;
|
|
}
|
|
}
|
|
if (!isGradientFill(colorA)) {
|
|
debugLogger_exports.inDevelopmentMode(() => logger_exports.warnOnce(warningMessage));
|
|
return;
|
|
}
|
|
let colorStops = colorA.colorStops;
|
|
try {
|
|
colorStops = colorStops?.map((value) => {
|
|
let color2;
|
|
if (typeof value.color === "string") {
|
|
color2 = Color.mix(Color.fromString(value.color), Color.fromString(colorB), ratio2).toString();
|
|
}
|
|
return { ...value, color: color2 };
|
|
});
|
|
} catch {
|
|
debugLogger_exports.inDevelopmentMode(
|
|
() => logger_exports.warnOnce(`${warningPrefix} a gradient, a color and a number between 0 and 1.`)
|
|
);
|
|
return;
|
|
}
|
|
return { ...colorA, colorStops };
|
|
}
|
|
var fontOperations = {
|
|
$rem: remOperation
|
|
};
|
|
function remOperation(graph, vertex, values) {
|
|
const [valueVertex] = values;
|
|
const value = graph.getVertexValue(valueVertex);
|
|
const fontSize = graph.getParamValue("fontSize");
|
|
if (typeof fontSize === "number" && typeof value === "number") {
|
|
return Math.round(value * fontSize);
|
|
}
|
|
debugLogger_exports.inDevelopmentMode(
|
|
() => logger_exports.warnOnce(
|
|
`\`$rem\` json operation failed on [${String(value)}] at [${graph.getPathArray(vertex).join(".")}], expecting a number.`
|
|
)
|
|
);
|
|
}
|
|
var logicOperations = {
|
|
$and: andOperation,
|
|
$eq: eqOperation,
|
|
$every: everyOperation,
|
|
$greaterThan: greaterThanOperation,
|
|
$if: ifOperation,
|
|
$lessThan: lessThanOperation,
|
|
$not: notOperation,
|
|
$or: orOperation,
|
|
$some: someOperation,
|
|
$switch: switchOperation
|
|
};
|
|
function andOperation(graph, vertex, values) {
|
|
for (const valueVertex of values) {
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
if (values.length === 1 && Array.isArray(value)) {
|
|
return value.every((v) => Boolean(v));
|
|
}
|
|
if (!value)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function eqOperation(graph, vertex, values) {
|
|
let compare;
|
|
let first2 = true;
|
|
for (const valueVertex of values) {
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
if (first2) {
|
|
compare = value;
|
|
first2 = false;
|
|
} else if (value !== compare) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function everyOperation(graph, vertex, values) {
|
|
const [mapOperationVertex, mapValuesVertex] = values;
|
|
const mapOperationValue = graph.getVertexValue(mapOperationVertex);
|
|
const mapValues3 = graph.resolveVertexValue(vertex, mapValuesVertex);
|
|
if (!Array.isArray(mapValues3))
|
|
return;
|
|
let index = 0;
|
|
for (const value of mapValues3) {
|
|
const resolved = graph.graftAndResolveOrphanValue(vertex, `${index}`, mapOperationValue, value);
|
|
if (!resolved)
|
|
return false;
|
|
index++;
|
|
}
|
|
return true;
|
|
}
|
|
function greaterThanOperation(graph, vertex, values) {
|
|
const [value, compare] = values;
|
|
return graph.resolveVertexValue(vertex, value) > graph.resolveVertexValue(vertex, compare);
|
|
}
|
|
function ifOperation(graph, vertex, values) {
|
|
const [conditionVertex, thenVertex, elseVertex] = values;
|
|
const condition = graph.resolveVertexValue(vertex, conditionVertex);
|
|
const valueVertex = condition ? thenVertex : elseVertex;
|
|
const neighbours = graph.neighboursWithEdgeValue(valueVertex, PATH_EDGE);
|
|
if (neighbours) {
|
|
for (const neighbour of neighbours) {
|
|
graph.addEdge(vertex, neighbour, PATH_EDGE);
|
|
}
|
|
}
|
|
return graph.resolveVertexValue(vertex, valueVertex);
|
|
}
|
|
function lessThanOperation(graph, vertex, values) {
|
|
const [value, compare] = values;
|
|
return graph.resolveVertexValue(vertex, value) < graph.resolveVertexValue(vertex, compare);
|
|
}
|
|
function notOperation(graph, vertex, values) {
|
|
const [valueVertex] = values;
|
|
if (!valueVertex)
|
|
return;
|
|
return !graph.resolveVertexValue(vertex, valueVertex);
|
|
}
|
|
function orOperation(graph, vertex, values) {
|
|
for (const valueVertex of values) {
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
if (values.length === 1 && Array.isArray(value)) {
|
|
return value.some((v) => Boolean(v));
|
|
}
|
|
if (value)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function someOperation(graph, vertex, values) {
|
|
const [mapOperationVertex, mapValuesVertex] = values;
|
|
const mapOperationValue = graph.getVertexValue(mapOperationVertex);
|
|
const mapValues3 = graph.resolveVertexValue(vertex, mapValuesVertex);
|
|
if (!Array.isArray(mapValues3))
|
|
return;
|
|
let index = 0;
|
|
for (const value of mapValues3) {
|
|
const resolved = graph.graftAndResolveOrphanValue(vertex, `${index}`, mapOperationValue, value);
|
|
if (resolved)
|
|
return true;
|
|
index++;
|
|
}
|
|
return false;
|
|
}
|
|
function switchOperation(graph, vertex, values) {
|
|
const [conditionValueVertex, defaultValueVertex, ...caseVertices] = values;
|
|
const conditionValue = graph.resolveVertexValue(vertex, conditionValueVertex);
|
|
for (const caseVertex of caseVertices) {
|
|
const caseValue = graph.getVertexValue(caseVertex);
|
|
if (!Array.isArray(caseValue))
|
|
continue;
|
|
const [caseConditionValue, caseResultValue] = caseValue;
|
|
if (conditionValue === caseConditionValue || Array.isArray(caseConditionValue) && caseConditionValue.includes(conditionValue)) {
|
|
return caseResultValue;
|
|
}
|
|
}
|
|
return graph.resolveVertexValue(vertex, defaultValueVertex);
|
|
}
|
|
var locationOperations = {
|
|
$isUserOption: isUserOptionOperation,
|
|
$palette: paletteOperation,
|
|
$mapPalette: mapPaletteOperation,
|
|
$path: {
|
|
dependencies: pathOperationDependenciesFactory,
|
|
resolve: pathOperation
|
|
},
|
|
$pathString: {
|
|
dependencies: pathOperationDependenciesFactory,
|
|
resolve: pathStringOperation
|
|
},
|
|
$ref: refOperation
|
|
};
|
|
function isUserOptionOperation(graph, vertex, values) {
|
|
const [relativePathVertices, thenVertex, elseVertex] = values;
|
|
const children = graph.neighboursWithEdgeValue(relativePathVertices, PATH_EDGE);
|
|
if (children) {
|
|
for (const child of children) {
|
|
const relativePathVertex = graph.findNeighbour(child, DEFAULTS_EDGE);
|
|
if (relativePathVertex && isUserOptionCheck(graph, vertex, relativePathVertex)) {
|
|
return graph.resolveVertexValue(vertex, thenVertex);
|
|
}
|
|
}
|
|
} else if (isUserOptionCheck(graph, vertex, relativePathVertices)) {
|
|
return graph.resolveVertexValue(vertex, thenVertex);
|
|
}
|
|
return graph.resolveVertexValue(vertex, elseVertex);
|
|
}
|
|
function isUserOptionCheck(graph, vertex, relativePathVertex) {
|
|
const relativePath = graph.resolveVertexValue(vertex, relativePathVertex);
|
|
if (!isString(relativePath)) {
|
|
throw new Error(`\`$isUserOption\` json operation failed on [${String(relativePath)}], expecting a string.`);
|
|
}
|
|
const pathArray = graph.getPathArray(vertex);
|
|
const path = resolvePath(pathArray, relativePath);
|
|
if (path === UNRESOLVABLE_PATH)
|
|
return false;
|
|
return graph.hasUserOption(path);
|
|
}
|
|
var PALETTE_INDEX_KEYS = /* @__PURE__ */ new Set(["fill", "fillFallback", "stroke", "gradient", "range2"]);
|
|
function paletteOperation(graph, vertex, values) {
|
|
const [keyVertex] = values;
|
|
const key = graph.resolveVertexValue(vertex, keyVertex);
|
|
if (!isString(key))
|
|
return;
|
|
if (PALETTE_INDEX_KEYS.has(key)) {
|
|
const pathArray = graph.getPathArray(vertex);
|
|
const index = getPathLastIndex(pathArray);
|
|
if (Number.isNaN(index))
|
|
return;
|
|
switch (key) {
|
|
case "fill":
|
|
return circularSliceArray(graph.palette.fills, 1, index)[0];
|
|
case "fillFallback":
|
|
return circularSliceArray(graph.palette.fillsFallback, 1, index)[0];
|
|
case "stroke":
|
|
return circularSliceArray(graph.palette.strokes, 1, index)[0];
|
|
case "gradient":
|
|
return circularSliceArray(graph.palette.sequentialColors, 1, index)[0];
|
|
case "range2":
|
|
return circularSliceArray(graph.palette.fills, 2, index);
|
|
}
|
|
return;
|
|
}
|
|
if (key === "gradients") {
|
|
return graph.palette.sequentialColors;
|
|
}
|
|
if (key === "type") {
|
|
return graph.paletteType;
|
|
}
|
|
const value = getPathSafe(graph.palette, key.split("."));
|
|
if (Array.isArray(value))
|
|
return [...value];
|
|
if (typeof value === "object")
|
|
return { ...value };
|
|
return value;
|
|
}
|
|
function mapPaletteOperation(graph, vertex, values) {
|
|
const [keyVertex] = values;
|
|
const key = graph.resolveVertexValue(vertex, keyVertex);
|
|
if (!isString(key))
|
|
return;
|
|
if (PALETTE_INDEX_KEYS.has(key)) {
|
|
const pathArray = graph.getPathArray(vertex);
|
|
let index = getPathLastIndex(pathArray);
|
|
let ignoreIndexOffset = 0;
|
|
const path = ["series", "0", "type"];
|
|
for (let i = 0; i < index; i++) {
|
|
path[1] = `${i}`;
|
|
const siblingSeriesType = graph.getResolvedPath(path);
|
|
if ("map-shape-background" === siblingSeriesType || "map-line-background" === siblingSeriesType) {
|
|
ignoreIndexOffset++;
|
|
}
|
|
}
|
|
index -= ignoreIndexOffset;
|
|
if (Number.isNaN(index))
|
|
return;
|
|
switch (key) {
|
|
case "fill":
|
|
return circularSliceArray(graph.palette.fills, 1, index)[0];
|
|
case "fillFallback":
|
|
return circularSliceArray(graph.palette.fillsFallback, 1, index)[0];
|
|
case "stroke":
|
|
return circularSliceArray(graph.palette.strokes, 1, index)[0];
|
|
case "gradient":
|
|
return circularSliceArray(graph.palette.sequentialColors, 1, index)[0];
|
|
case "range2":
|
|
return circularSliceArray(graph.palette.fills, 2, index);
|
|
}
|
|
return;
|
|
}
|
|
if (key === "gradients") {
|
|
return graph.palette.sequentialColors;
|
|
}
|
|
if (key === "type") {
|
|
return graph.paletteType;
|
|
}
|
|
const value = getPathSafe(graph.palette, key.split("."));
|
|
if (Array.isArray(value))
|
|
return [...value];
|
|
if (typeof value === "object")
|
|
return { ...value };
|
|
return value;
|
|
}
|
|
function pathOperationDependenciesFactory(graph, vertex, values) {
|
|
const [relativePathVertex] = values;
|
|
const relativePath = graph.getVertexValue(relativePathVertex);
|
|
if (isString(relativePath)) {
|
|
const pathArray = graph.getPathArray(vertex);
|
|
const path = resolvePath(pathArray, relativePath);
|
|
if (path === UNRESOLVABLE_PATH) {
|
|
return;
|
|
}
|
|
const dependencyVertex = graph.findVertexAtPath(path);
|
|
if (dependencyVertex) {
|
|
graph.addEdge(vertex, dependencyVertex, DEPENDENCY_EDGE);
|
|
}
|
|
}
|
|
}
|
|
function pathOperation(graph, vertex, values) {
|
|
const hasDefaultValue = values.length > 1;
|
|
const hasCustomBranch = values.length > 2;
|
|
const [relativePathVertex, defaultValueVertex, customBranchVertex] = values;
|
|
const relativePath = graph.resolveVertexValue(vertex, relativePathVertex);
|
|
const customBranch = hasCustomBranch ? graph.resolveVertexValue(vertex, customBranchVertex) : null;
|
|
if (!isString(relativePath)) {
|
|
throw new Error(`\`$path\` json operation failed on [${String(relativePath)}], expecting a string.`);
|
|
}
|
|
const pathArray = graph.getPathArray(vertex);
|
|
const path = resolvePath(pathArray, relativePath);
|
|
if (path === UNRESOLVABLE_PATH) {
|
|
return;
|
|
}
|
|
const resolved = customBranch ? getPathSafe(customBranch, path) : graph.getResolvedPath(path);
|
|
if (resolved != null) {
|
|
return resolved;
|
|
}
|
|
if (hasDefaultValue) {
|
|
return graph.resolveVertexValue(vertex, defaultValueVertex);
|
|
}
|
|
}
|
|
function pathStringOperation(graph, vertex, values) {
|
|
const [relativePathVertex, variablesVertex] = values;
|
|
const relativePath = graph.resolveVertexValue(vertex, relativePathVertex);
|
|
if (!isString(relativePath)) {
|
|
throw new Error(`\`$path\` json operation failed on [${String(relativePath)}], expecting a string.`);
|
|
}
|
|
let variables;
|
|
if (variablesVertex) {
|
|
variables = graph.graftAndResolveOrphan(vertex, variablesVertex);
|
|
}
|
|
const pathArray = graph.getPathArray(vertex);
|
|
const path = resolvePath(pathArray, relativePath, variables);
|
|
if (path === UNRESOLVABLE_PATH) {
|
|
throw new Error(`Unresolvable path [${relativePath}] at [${pathArray.join(".")}]`);
|
|
}
|
|
return path;
|
|
}
|
|
function refOperation(graph, _vertex, values) {
|
|
const [value] = values;
|
|
const paramKey = graph.getVertexValue(value);
|
|
return graph.getParamValue(paramKey);
|
|
}
|
|
var transformOperations = {
|
|
$apply: applyOperation,
|
|
$applyCycle: applyCycleOperation,
|
|
$applySwitch: applySwitchOperation,
|
|
$applyTheme: applyThemeOperation,
|
|
$clone: cloneOperation,
|
|
$findFirstSiblingNotOperation: findFirstSiblingNotOperationOperation,
|
|
$map: mapOperation,
|
|
$merge: mergeOperation,
|
|
$omit: omitOperation,
|
|
$size: sizeOperation,
|
|
$shallow: shallowOperation,
|
|
$shallowSimple: shallowSimpleOperation,
|
|
$value: valueOperation
|
|
};
|
|
function applyOperation(graph, vertex, values) {
|
|
const [objectVertex, defaultValueVertex, overridesPathVertex1, overridesPathVertex2] = values;
|
|
const object2 = graph.getVertexValue(objectVertex);
|
|
if (!isPlainObject(object2))
|
|
return;
|
|
const defaultValue = defaultValueVertex ? graph.getVertexValue(defaultValueVertex) : void 0;
|
|
const children = graph.neighboursWithEdgeValue(vertex, PATH_EDGE);
|
|
const hasChildren = children && children.length > 0;
|
|
if (!hasChildren && defaultValue == null) {
|
|
return RESOLVED_TO_BRANCH;
|
|
}
|
|
const overridesPath1 = overridesPathVertex1 ? graph.resolveVertexValue(vertex, overridesPathVertex1) : void 0;
|
|
const overridesPath2 = overridesPathVertex2 ? graph.resolveVertexValue(vertex, overridesPathVertex2) : void 0;
|
|
if (!hasChildren && defaultValue != null) {
|
|
if (getOperation(defaultValue)) {
|
|
const resolvedDefaultValue = graph.resolveVertexValue(vertex, defaultValueVertex);
|
|
if (isPlainObject(resolvedDefaultValue)) {
|
|
graph.graftObject(vertex, resolvedDefaultValue, [overridesPath1, overridesPath2]);
|
|
}
|
|
} else {
|
|
graph.graftObject(vertex, defaultValue, [overridesPath1, overridesPath2]);
|
|
}
|
|
}
|
|
if (!hasChildren)
|
|
return RESOLVED_TO_BRANCH;
|
|
for (const child of children) {
|
|
const childNeighbours = graph.neighboursWithEdgeValue(child, PATH_EDGE);
|
|
if (!childNeighbours || childNeighbours.length === 0) {
|
|
const stubVertex = graph.addVertex({});
|
|
graph.addEdge(child, stubVertex, DEFAULTS_EDGE);
|
|
} else {
|
|
graph.graftObject(child, object2, [overridesPath1, overridesPath2]);
|
|
}
|
|
}
|
|
return RESOLVED_TO_BRANCH;
|
|
}
|
|
function applyCycleOperation(graph, vertex, values) {
|
|
const [sizeVertex, defaultValuesVertex, operationVertex] = values;
|
|
const size = graph.resolveVertexValue(vertex, sizeVertex);
|
|
if (typeof size !== "number")
|
|
return;
|
|
const pathArray = graph.getPathArray(vertex);
|
|
const userOption = graph.dangerouslyGetUserOption(pathArray);
|
|
const hasThemeOverride = graph.hasThemeOverride(pathArray);
|
|
const graftEdge = userOption == null ? void 0 : USER_OPTIONS_EDGE;
|
|
const cycledValues = userOption ?? graph.resolveVertexValue(vertex, defaultValuesVertex);
|
|
if (!Array.isArray(cycledValues))
|
|
return;
|
|
const operation = operationVertex ? graph.getVertexValue(operationVertex) : void 0;
|
|
for (let index = 0; index < size; index++) {
|
|
const value = cycledValues[index % cycledValues.length];
|
|
if (value == null)
|
|
continue;
|
|
if (userOption || !hasThemeOverride) {
|
|
graph.graftValue(vertex, `${index}`, value, void 0, graftEdge);
|
|
}
|
|
if (operation) {
|
|
graph.graftValue(vertex, `${index}`, operation, value, graftEdge);
|
|
}
|
|
}
|
|
return RESOLVED_TO_BRANCH;
|
|
}
|
|
function applySwitchOperation(graph, vertex, values) {
|
|
const [conditionValueVertex, defaultValueVertex, ...caseVertices] = values;
|
|
const conditionValue = graph.resolveVertexValue(vertex, conditionValueVertex);
|
|
for (const caseVertex of caseVertices) {
|
|
const caseValue = graph.getVertexValue(caseVertex);
|
|
if (!Array.isArray(caseValue))
|
|
continue;
|
|
const [caseConditionValue, caseResultValue] = caseValue;
|
|
if (conditionValue === caseConditionValue || Array.isArray(caseConditionValue) && caseConditionValue.includes(conditionValue)) {
|
|
graph.graftObject(vertex, caseResultValue, [], DEFAULTS_EDGE);
|
|
return RESOLVED_TO_BRANCH;
|
|
}
|
|
}
|
|
return graph.resolveVertexValue(vertex, defaultValueVertex);
|
|
}
|
|
function applyThemeOperation(graph, vertex, values) {
|
|
const [fromPathVertex, variablesVertex, ignorePathsVertex] = values;
|
|
let fromPaths = graph.getVertexValue(fromPathVertex);
|
|
if (typeof fromPaths === "string") {
|
|
fromPaths = [fromPaths];
|
|
}
|
|
if (!Array.isArray(fromPaths))
|
|
return;
|
|
const children = graph.neighboursWithEdgeValue(vertex, PATH_EDGE);
|
|
const ignorePathsValue = ignorePathsVertex ? graph.getVertexValue(ignorePathsVertex) : [];
|
|
const ignorePaths = Array.isArray(ignorePathsValue) ? new Set(ignorePathsValue) : /* @__PURE__ */ new Set();
|
|
if (!children)
|
|
return RESOLVED_TO_BRANCH;
|
|
for (const child of children) {
|
|
const variables = graph.graftAndResolveOrphan(child, variablesVertex);
|
|
for (const fromPath of fromPaths) {
|
|
const fromPathResolved = resolvePath([], fromPath, variables);
|
|
if (fromPathResolved === UNRESOLVABLE_PATH) {
|
|
continue;
|
|
}
|
|
graph.graftConfig(child, fromPathResolved, ignorePaths);
|
|
}
|
|
}
|
|
return RESOLVED_TO_BRANCH;
|
|
}
|
|
function cloneOperation(graph, vertex, values) {
|
|
const [valueVertex] = values;
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
if (!isPlainObject(value))
|
|
return;
|
|
graph.graftObject(vertex, value, void 0, USER_OPTIONS_EDGE);
|
|
return RESOLVED_TO_BRANCH;
|
|
}
|
|
function findFirstSiblingNotOperationOperation(graph, vertex, values) {
|
|
const [defaultValueVertex] = values;
|
|
const pathArray = graph.getPathArray(vertex);
|
|
const parentPathArray = resolvePath(pathArray, "..");
|
|
if (parentPathArray === UNRESOLVABLE_PATH)
|
|
return;
|
|
const parentVertex = graph.findVertexAtPath(parentPathArray);
|
|
if (!parentVertex) {
|
|
return graph.resolveVertexValue(vertex, defaultValueVertex);
|
|
}
|
|
const siblings = graph.neighboursWithEdgeValue(parentVertex, PATH_EDGE);
|
|
if (siblings) {
|
|
for (const sibling of siblings) {
|
|
const siblingPathArray = graph.getPathArray(sibling);
|
|
if (siblingPathArray[parentPathArray.length] === pathArray[parentPathArray.length])
|
|
continue;
|
|
const siblingChildPathArray = siblingPathArray.concat(pathArray.slice(parentPathArray.length + 1));
|
|
const siblingChildVertex = graph.findVertexAtPath(siblingChildPathArray);
|
|
if (!siblingChildVertex)
|
|
continue;
|
|
const siblingChildUserOptionsValue = graph.findNeighbourValue(siblingChildVertex, USER_OPTIONS_EDGE);
|
|
if (siblingChildUserOptionsValue != null) {
|
|
return siblingChildUserOptionsValue;
|
|
}
|
|
const siblingChildOverridesValue = graph.findNeighbourValue(siblingChildVertex, OVERRIDES_EDGE);
|
|
if (siblingChildOverridesValue != null) {
|
|
return siblingChildOverridesValue;
|
|
}
|
|
}
|
|
}
|
|
return graph.resolveVertexValue(vertex, defaultValueVertex);
|
|
}
|
|
function mapOperation(graph, vertex, values) {
|
|
const [mapOperationVertex, mapValuesVertex] = values;
|
|
const mapOperationValue = graph.getVertexValue(mapOperationVertex);
|
|
const mapValues3 = graph.resolveVertexValue(vertex, mapValuesVertex);
|
|
if (!Array.isArray(mapValues3))
|
|
return;
|
|
const neighbours = graph.neighboursWithEdgeValue(vertex, PATH_EDGE);
|
|
if (neighbours && neighbours.length > 0) {
|
|
return;
|
|
}
|
|
let index = 0;
|
|
for (const value of mapValues3) {
|
|
graph.graftValue(vertex, `${index}`, mapOperationValue, value);
|
|
index++;
|
|
}
|
|
return RESOLVED_TO_BRANCH;
|
|
}
|
|
function mergeOperation(graph, vertex, values) {
|
|
for (const valueVertex of values) {
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
if (!isPlainObject(value))
|
|
continue;
|
|
graph.graftObject(vertex, value);
|
|
}
|
|
return RESOLVED_TO_BRANCH;
|
|
}
|
|
function omitOperation(graph, vertex, values) {
|
|
const [keysVertex, objectVertex] = values;
|
|
let keys = graph.getVertexValue(keysVertex);
|
|
if (!Array.isArray(keys)) {
|
|
const targetVertex = getOperationTargetVertex(graph, vertex, objectVertex);
|
|
if (!targetVertex)
|
|
return;
|
|
keys = graph.resolveVertexValue(targetVertex, keysVertex);
|
|
}
|
|
const object2 = graph.resolveVertexValue(vertex, objectVertex);
|
|
if (!Array.isArray(keys) || !isPlainObject(object2))
|
|
return;
|
|
return without(object2, keys);
|
|
}
|
|
function sizeOperation(graph, vertex, values) {
|
|
const [valueVertex] = values;
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
if (!isObjectLike(value))
|
|
return 0;
|
|
if ("length" in value)
|
|
return value.length;
|
|
return Object.keys(value).length;
|
|
}
|
|
function shallowSimpleOperation(graph, _vertex, values) {
|
|
const shallowValues = [];
|
|
for (const valueVertex of values) {
|
|
shallowValues.push(graph.getVertexValue(valueVertex));
|
|
}
|
|
return shallowValues;
|
|
}
|
|
function shallowOperation(graph, vertex, values) {
|
|
const pathArray = graph.getPathArray(vertex);
|
|
const hasUserOption = graph.hasUserOption(pathArray);
|
|
if (!hasUserOption && values.length === 1) {
|
|
return graph.resolveVertexValue(vertex, values[0]);
|
|
}
|
|
const shallowValues = [];
|
|
for (const valueVertex of values) {
|
|
shallowValues.push(graph.getVertexValue(valueVertex));
|
|
}
|
|
if (hasUserOption) {
|
|
graph.prune(vertex, [OVERRIDES_EDGE, DEFAULTS_EDGE]);
|
|
return RESOLVED_TO_BRANCH;
|
|
}
|
|
graph.graftObject(vertex, shallowValues);
|
|
return RESOLVED_TO_BRANCH;
|
|
}
|
|
function valueOperation(graph, vertex, values) {
|
|
const [valueVertex] = values;
|
|
const value = graph.getVertexValue(valueVertex);
|
|
const pathArray = graph.getPathArray(vertex);
|
|
if (value === "$index") {
|
|
return getPathLastIndex(pathArray);
|
|
}
|
|
if (value === "$parentIndex") {
|
|
return getPathLastIndex(pathArray, 1);
|
|
}
|
|
if (value === "$1") {
|
|
return graph.resolveValue$1(pathArray);
|
|
}
|
|
}
|
|
var numericOperations = {
|
|
$isEven: isEvenOperation
|
|
};
|
|
function isEvenOperation(graph, vertex, values) {
|
|
const [valueVertex] = values;
|
|
const value = graph.resolveVertexValue(vertex, valueVertex);
|
|
if (Number.isNaN(Number(value)))
|
|
return false;
|
|
return Number(value) % 2 === 0;
|
|
}
|
|
var operations = {
|
|
...cacheOperations,
|
|
...chartOperations,
|
|
...colorOperations,
|
|
...fontOperations,
|
|
...locationOperations,
|
|
...logicOperations,
|
|
...numericOperations,
|
|
...transformOperations
|
|
};
|
|
var operationTypes = new Set(Object.keys(operations));
|
|
function isOperation(value) {
|
|
return operationTypes.has(value);
|
|
}
|
|
|
|
// packages/ag-charts-community/src/module/optionsGraph.ts
|
|
var debug2 = debugLogger_exports.create("opts", "options-graph");
|
|
var createOptionsGraph = simpleMemorize(createOptionsGraphFn);
|
|
function createOptionsGraphFn(theme, options) {
|
|
return debug2.group("OptionsGraph.constructor()", () => {
|
|
const optionsGraph = new OptionsGraph(
|
|
theme.config,
|
|
options,
|
|
theme.params,
|
|
theme.palette,
|
|
theme.overrides,
|
|
theme.getTemplateParameters()
|
|
);
|
|
return {
|
|
resolve() {
|
|
return optionsGraph.resolve();
|
|
},
|
|
resolveParams() {
|
|
return optionsGraph.resolveParams();
|
|
},
|
|
resolveAnnotationThemes() {
|
|
return optionsGraph.resolveAnnotationThemes();
|
|
},
|
|
resolvePartial(path, partialOptions, resolveOptions) {
|
|
return optionsGraph.resolvePartial(path, partialOptions, resolveOptions);
|
|
},
|
|
clearSafe() {
|
|
return optionsGraph.clearSafe();
|
|
}
|
|
};
|
|
});
|
|
}
|
|
var _OptionsGraph = class _OptionsGraph extends AdjacencyListGraph {
|
|
constructor(config = {}, userOptions = {}, params = void 0, palette = {}, overrides = void 0, internalParams = /* @__PURE__ */ new Map()) {
|
|
super(PATH_EDGE, OPERATION_EDGE, /* @__PURE__ */ new Set([USER_PARTIAL_OPTIONS_EDGE, USER_OPTIONS_EDGE]));
|
|
this.config = config;
|
|
this.userOptions = userOptions;
|
|
this.palette = palette;
|
|
this.overrides = overrides;
|
|
this.internalParams = internalParams;
|
|
// The current priority order in which to resolve options values.
|
|
this.edgePriority = [..._OptionsGraph.EDGE_PRIORITY];
|
|
// The edge value to use when grafting new branches onto the graph from operations.
|
|
this.graftEdge = _OptionsGraph.GRAFT_EDGE;
|
|
this.resolvedParams = {};
|
|
this.resolvedAnnotations = {};
|
|
// The current value referenced by operations that use `$1`.
|
|
this.value$1 = /* @__PURE__ */ new Map();
|
|
this.cachedPathVertices = /* @__PURE__ */ new Map();
|
|
this.hasUnsafeClearKeys = false;
|
|
this.rollbackVertices = [];
|
|
this.rollbackEdgesFrom = [];
|
|
this.rollbackEdgesTo = [];
|
|
this.rollbackEdgesValue = [];
|
|
this.isRollingBack = false;
|
|
// Records the already resolved root ancestors, i.e. vertices with a path of a single segment
|
|
this.resolvedRootAncestorsPaths = /* @__PURE__ */ new Set();
|
|
this.EMPTY_PATH_ARRAY_VERTEX = this.addVertex([]);
|
|
this.root = this.addVertex("root");
|
|
this.params = this.addVertex("params");
|
|
this.annotations = this.addVertex("annotations");
|
|
this.paletteType = isObject(userOptions?.theme) ? paletteType(userOptions.theme?.palette) : "inbuilt";
|
|
const seriesType = userOptions.series?.[0]?.type ?? "line";
|
|
debug2("build user");
|
|
this.buildGraphFromObject(this.root, USER_OPTIONS_EDGE, without(userOptions, ["theme"]));
|
|
debug2("build defaults");
|
|
this.buildGraphFromObject(this.root, DEFAULTS_EDGE, without(config[seriesType], _OptionsGraph.COMPLEX_KEYS));
|
|
const seriesOverrides = overrides ? without(overrides[seriesType], _OptionsGraph.COMPLEX_KEYS) : {};
|
|
if (Object.keys(seriesOverrides).length > 0) {
|
|
debug2("build series overrides");
|
|
this.buildGraphFromObject(this.root, OVERRIDES_EDGE, seriesOverrides);
|
|
}
|
|
const commonOverrides = overrides ? without(overrides.common, _OptionsGraph.COMPLEX_KEYS) : {};
|
|
if (Object.keys(commonOverrides).length > 0) {
|
|
debug2("build common overrides");
|
|
this.buildGraphFromObject(
|
|
this.root,
|
|
OVERRIDES_EDGE,
|
|
moduleRegistry_exports.getSeriesModule(seriesType)?.chartType === "cartesian" ? commonOverrides : without(commonOverrides, ["zoom", "navigator"])
|
|
);
|
|
}
|
|
if (params) {
|
|
debug2("build params");
|
|
this.buildGraphFromObject(this.params, DEFAULTS_EDGE, params);
|
|
}
|
|
const axesVertex = this.findNeighbourWithValue(this.root, "axes", PATH_EDGE);
|
|
const seriesVertex = this.findNeighbourWithValue(this.root, "series", PATH_EDGE);
|
|
if (axesVertex) {
|
|
debug2("build axes");
|
|
this.buildGraphFromObject(axesVertex, DEFAULTS_EDGE, {
|
|
$applyTheme: [
|
|
["/$seriesType/axes/$axisType/$position", "/$seriesType/axes/$axisType"],
|
|
{
|
|
seriesType: { $path: ["/series/0/type", "line"] },
|
|
axisType: { $path: ["./type", "category"] },
|
|
position: { $path: ["./position"] }
|
|
},
|
|
["top", "right", "bottom", "left"]
|
|
]
|
|
});
|
|
}
|
|
if (seriesVertex) {
|
|
debug2("build series");
|
|
this.buildGraphFromObject(seriesVertex, DEFAULTS_EDGE, {
|
|
$applyTheme: ["/$seriesType/series", { seriesType: { $path: ["./type", "line"] } }]
|
|
});
|
|
}
|
|
const annotationsTypeConfig = without(
|
|
config[seriesType]?.annotations ?? {},
|
|
_OptionsGraph.ANNOTATIONS_OPTIONS_KEYS
|
|
);
|
|
if (Object.keys(annotationsTypeConfig).length > 0) {
|
|
debug2("build annotations type config");
|
|
this.buildGraphFromObject(this.annotations, DEFAULTS_EDGE, annotationsTypeConfig);
|
|
}
|
|
const annotationsTypeOverrides = without(
|
|
overrides?.common?.annotations ?? {},
|
|
_OptionsGraph.ANNOTATIONS_OPTIONS_KEYS
|
|
);
|
|
if (Object.keys(annotationsTypeOverrides).length > 0) {
|
|
debug2("build annotations type overrides");
|
|
this.buildGraphFromObject(this.annotations, OVERRIDES_EDGE, annotationsTypeOverrides);
|
|
}
|
|
const annotationsConfig = pick(config[seriesType]?.annotations ?? {}, _OptionsGraph.ANNOTATIONS_OPTIONS_KEYS);
|
|
if (Object.keys(annotationsConfig).length > 0) {
|
|
debug2("build annotations config");
|
|
this.buildGraphFromObject(this.root, DEFAULTS_EDGE, { annotations: annotationsConfig });
|
|
}
|
|
const annotationsOverrides = pick(overrides?.common?.annotations ?? {}, _OptionsGraph.ANNOTATIONS_OPTIONS_KEYS);
|
|
if (Object.keys(annotationsOverrides).length > 0) {
|
|
debug2("build annotations overrides");
|
|
this.buildGraphFromObject(this.root, OVERRIDES_EDGE, { annotations: annotationsOverrides });
|
|
}
|
|
this.buildDependencyGraph();
|
|
}
|
|
static clearValueCache() {
|
|
_OptionsGraph.valueCache.clear();
|
|
}
|
|
clear() {
|
|
debug2.group("OptionsGraph.clear()", () => {
|
|
super.clear();
|
|
this.cachedPathVertices.clear();
|
|
this.root = void 0;
|
|
this.params = void 0;
|
|
this.annotations = void 0;
|
|
debug2("cleared");
|
|
});
|
|
}
|
|
clearSafe() {
|
|
if (this.hasUnsafeClearKeys)
|
|
return;
|
|
this.clear();
|
|
}
|
|
resolve() {
|
|
return debug2.group("OptionsGraph.resolve()", () => {
|
|
this.resolved = {};
|
|
this.resolvedParams = {};
|
|
this.resolvedAnnotations = {};
|
|
debug2("resolve params");
|
|
this.resolveVertex(this.params, this.resolvedParams);
|
|
debug2("resolve annotations");
|
|
this.resolveVertex(this.annotations, this.resolvedAnnotations);
|
|
debug2("resolve root");
|
|
this.resolveVertex(this.root);
|
|
debug2("resolved root", this.resolved);
|
|
debug2("vertex count", this.getVertexCount());
|
|
debug2("edge count", this.getEdgeCount());
|
|
return this.resolved;
|
|
});
|
|
}
|
|
resolveParams() {
|
|
return this.resolvedParams;
|
|
}
|
|
resolveAnnotationThemes() {
|
|
return this.resolvedAnnotations;
|
|
}
|
|
addVertex(value) {
|
|
const vertex = super.addVertex(value);
|
|
if (this.isRollingBack) {
|
|
this.rollbackVertices.push(vertex);
|
|
}
|
|
return vertex;
|
|
}
|
|
addEdge(from3, to, edge) {
|
|
const hasEdge = (this.neighboursWithEdgeValue(from3, edge)?.indexOf(to) ?? -1) !== -1;
|
|
if (this.isRollingBack && !hasEdge) {
|
|
this.rollbackEdgesFrom.push(from3);
|
|
this.rollbackEdgesTo.push(to);
|
|
this.rollbackEdgesValue.push(edge);
|
|
}
|
|
super.addEdge(from3, to, edge);
|
|
}
|
|
/**
|
|
* Resolve partial options against the existing graph at a given path without overriding the existing user values.
|
|
* Returns an object with only those keys that were also present within `partialOptions`.
|
|
*/
|
|
resolvePartial(path, partialOptions, resolveOptions) {
|
|
if (!partialOptions)
|
|
return;
|
|
if (!this.root)
|
|
return;
|
|
const { permissivePath, proxyPaths } = resolveOptions ?? {};
|
|
const partialKeys = Object.keys(partialOptions);
|
|
if (debug2.check()) {
|
|
console.groupCollapsed(`OptionsGraph.resolvePartial() - ${path.join(".")} [${partialKeys}]`);
|
|
}
|
|
if (partialKeys.length === 0)
|
|
return {};
|
|
const parentVertex = this.findVertexAtPath(path);
|
|
if (!parentVertex) {
|
|
if (permissivePath) {
|
|
return void 0;
|
|
} else {
|
|
throw new Error(`Could not find vertex in OptionsGraph at path [${path.join(".")}].`);
|
|
}
|
|
}
|
|
const pathArrayVertex = this.findNeighbour(parentVertex, PATH_ARRAY_EDGE);
|
|
this.userPartialOptions = {};
|
|
setPathSafe(this.userPartialOptions, path, partialOptions);
|
|
if (proxyPaths) {
|
|
for (const proxyFrom of Object.keys(proxyPaths)) {
|
|
const proxyTo = proxyPaths[proxyFrom];
|
|
const proxyValue = getPathSafe(partialOptions, [proxyFrom]);
|
|
if (proxyValue != null) {
|
|
setPathSafe(partialOptions, proxyTo, proxyValue);
|
|
setPathSafe(this.userPartialOptions, [...path, ...proxyTo], proxyValue);
|
|
delete partialOptions[proxyFrom];
|
|
delete this.userPartialOptions[proxyFrom];
|
|
}
|
|
}
|
|
}
|
|
this.graftEdge = USER_PARTIAL_OPTIONS_EDGE;
|
|
this.edgePriority = [USER_PARTIAL_OPTIONS_EDGE, ..._OptionsGraph.EDGE_PRIORITY];
|
|
this.snapshot();
|
|
this.buildGraphFromObject(parentVertex, USER_PARTIAL_OPTIONS_EDGE, partialOptions, pathArrayVertex);
|
|
for (const key of partialKeys) {
|
|
const childVertex = proxyPaths?.[key] ? this.findVertexAtPath([...path, ...proxyPaths[key]]) : this.findNeighbourWithValue(parentVertex, key, PATH_EDGE);
|
|
if (childVertex) {
|
|
this.refreshPendingProcessingEdges(childVertex);
|
|
}
|
|
}
|
|
this.buildDependencyGraph();
|
|
const resolved = {};
|
|
this.resolveVertex(parentVertex, resolved);
|
|
this.rollback();
|
|
this.graftEdge = _OptionsGraph.GRAFT_EDGE;
|
|
this.edgePriority = _OptionsGraph.EDGE_PRIORITY;
|
|
this.userPartialOptions = void 0;
|
|
if (proxyPaths) {
|
|
for (const proxyFrom of Object.keys(proxyPaths)) {
|
|
const proxyTo = proxyPaths[proxyFrom];
|
|
const proxyValue = getPathSafe(resolved, [...path, ...proxyTo]);
|
|
setPathSafe(resolved, [...path, proxyFrom], proxyValue);
|
|
}
|
|
}
|
|
const pathed = getPathSafe(resolved, path);
|
|
const shouldPick = resolveOptions?.pick ?? true;
|
|
const partial = shouldPick ? pick(getPathSafe(resolved, path), partialKeys) : pathed;
|
|
debug2("vertex count", this.getVertexCount());
|
|
debug2("edge count", this.getEdgeCount());
|
|
debug2("resolved partial", partial);
|
|
if (debug2.check()) {
|
|
console.groupEnd();
|
|
}
|
|
return partial;
|
|
}
|
|
findVertexAtPath(path) {
|
|
const key = path.join(".");
|
|
if (this.cachedPathVertices.has(key)) {
|
|
return this.cachedPathVertices.get(key);
|
|
}
|
|
const vertex = this.findVertexAlongEdge(this.root, path, PATH_EDGE);
|
|
if (!vertex)
|
|
return;
|
|
this.cachedPathVertices.set(key, vertex);
|
|
return vertex;
|
|
}
|
|
hasUserOption(path) {
|
|
const hasUserOptionSimple = hasPathSafe(this.userOptions, path);
|
|
if (hasUserOptionSimple)
|
|
return true;
|
|
const pathVertex = this.findVertexAtPath(path);
|
|
if (pathVertex) {
|
|
if (this.findNeighbour(pathVertex, USER_OPTIONS_EDGE) != null)
|
|
return true;
|
|
if (this.findNeighbour(pathVertex, USER_PARTIAL_OPTIONS_EDGE) != null)
|
|
return true;
|
|
const childrenSource = this.findNeighbourValue(pathVertex, CHILDREN_SOURCE_EDGE);
|
|
return childrenSource === USER_OPTIONS_EDGE || childrenSource === USER_PARTIAL_OPTIONS_EDGE;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Get the value from the user options at the given path. This method is dangerous since it does not resolve
|
|
* through the graph, however is useful for operations that operate on their own path where attempting to
|
|
* resolve would cause an infinite loop.
|
|
*/
|
|
dangerouslyGetUserOption(path) {
|
|
if (this.userPartialOptions) {
|
|
const value = getPathSafe(this.userPartialOptions, path);
|
|
if (value != null)
|
|
return value;
|
|
}
|
|
return getPathSafe(this.userOptions, path);
|
|
}
|
|
hasThemeOverride(path) {
|
|
if (this.overrides == null)
|
|
return false;
|
|
if (path[0] === "axes" && path.length > 1) {
|
|
const axisType = this.getResolvedPath(["axes", path[1], "type"]);
|
|
if (hasPathSafe(this.overrides, ["common", "axes", axisType, ...path.slice(2)])) {
|
|
return true;
|
|
}
|
|
const seriesType = this.getResolvedPath(["series", "0", "type"]);
|
|
return hasPathSafe(this.overrides, [seriesType, "axes", axisType, ...path.slice(2)]);
|
|
}
|
|
if (path[0] === "series" && path.length > 1) {
|
|
const seriesType = this.getResolvedPath(["series", path[1], "type"]);
|
|
return hasPathSafe(this.overrides, [seriesType, "series", ...path.slice(2)]);
|
|
}
|
|
return hasPathSafe(this.overrides, path);
|
|
}
|
|
getParamValue(path) {
|
|
if (this.resolvedParams[path] != null) {
|
|
return this.resolvedParams[path];
|
|
}
|
|
const paramVertex = this.findVertexAlongEdge(this.params, [path], PATH_EDGE);
|
|
if (!paramVertex)
|
|
return;
|
|
const defaultValueVertex = this.findNeighbour(paramVertex, DEFAULTS_EDGE);
|
|
if (!defaultValueVertex)
|
|
return;
|
|
const resolved = this.resolveVertexValue(paramVertex, defaultValueVertex);
|
|
this.resolvedParams[path] = resolved;
|
|
return resolved;
|
|
}
|
|
getPathArray(vertex) {
|
|
return this.findNeighbourValue(vertex, PATH_ARRAY_EDGE) ?? [];
|
|
}
|
|
getResolvedPath(path) {
|
|
return getPathSafe(this.resolved, path);
|
|
}
|
|
getCachedValue(path, key) {
|
|
const cacheKey = [...path, key].join(".");
|
|
return _OptionsGraph.valueCache.get(cacheKey);
|
|
}
|
|
setCachedValue(path, key, value) {
|
|
const cacheKey = [...path, key].join(".");
|
|
_OptionsGraph.valueCache.set(cacheKey, value);
|
|
}
|
|
prune(vertex, edges) {
|
|
this.addEdge(vertex, this.addVertex(edges), PRUNE_EDGE);
|
|
}
|
|
resolveVertexValue(vertex, valueVertex) {
|
|
this.resolveVertexDependencies(valueVertex);
|
|
const operation = this.findNeighbourValue(valueVertex, OPERATION_EDGE);
|
|
if (operation && isOperation(operation)) {
|
|
const operationValues = this.neighboursWithEdgeValue(valueVertex, OPERATION_VALUE_EDGE);
|
|
const operator = operations[operation];
|
|
const operatorFn = typeof operator === "function" ? operator : operator.resolve;
|
|
const resolved = operatorFn?.(this, vertex, operationValues ?? []);
|
|
return resolved === RESOLVED_TO_BRANCH ? void 0 : resolved;
|
|
}
|
|
let value = this.getVertexValue(valueVertex);
|
|
if (Array.isArray(value)) {
|
|
const object2 = {};
|
|
this.resolveVertexChildren(valueVertex, object2);
|
|
value = getPathSafe(object2, this.getPathArray(vertex));
|
|
}
|
|
return this.resolveValueOrSymbol(value);
|
|
}
|
|
/**
|
|
* Resolve the value currently referenced by `$1` by the nearest self-or-ancestor that has a defined value.
|
|
*/
|
|
resolveValue$1(pathArray) {
|
|
for (let i = pathArray.length; i >= 0; i--) {
|
|
const key = pathArray.slice(0, i).join(".");
|
|
const resolvedValue = this.value$1.get(key);
|
|
if (resolvedValue != void 0) {
|
|
return resolvedValue;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Graft a branch of the theme config onto the target vertex.
|
|
*/
|
|
graftConfig(target, configPathArray, ignorePaths) {
|
|
const targetConfigObject = getPathSafe(this.config, configPathArray);
|
|
const targetPathArrayVertex = this.findNeighbour(target, PATH_ARRAY_EDGE);
|
|
if (isObject(targetConfigObject)) {
|
|
this.buildGraphFromObject(
|
|
target,
|
|
DEFAULTS_EDGE,
|
|
targetConfigObject,
|
|
targetPathArrayVertex,
|
|
void 0,
|
|
ignorePaths
|
|
);
|
|
}
|
|
if (this.overrides) {
|
|
const targetOverridesObject = getPathSafe(this.overrides, configPathArray);
|
|
if (isObject(targetOverridesObject)) {
|
|
this.buildGraphFromObject(
|
|
target,
|
|
OVERRIDES_EDGE,
|
|
targetOverridesObject,
|
|
targetPathArrayVertex,
|
|
void 0,
|
|
ignorePaths
|
|
);
|
|
}
|
|
const commonOverridesObject = getPathSafe(this.overrides, ["common", ...configPathArray.slice(1)]);
|
|
if (isObject(commonOverridesObject)) {
|
|
this.buildGraphFromObject(
|
|
target,
|
|
OVERRIDES_EDGE,
|
|
commonOverridesObject,
|
|
targetPathArrayVertex,
|
|
void 0,
|
|
ignorePaths
|
|
);
|
|
}
|
|
}
|
|
this.buildDependencyGraph();
|
|
}
|
|
/**
|
|
* Graft a given object onto the target vertex.
|
|
*/
|
|
graftObject(target, object2, overridesPathArrays, edgeValue = this.graftEdge) {
|
|
const pathArrayVertex = this.findNeighbour(target, PATH_ARRAY_EDGE);
|
|
this.buildGraphFromObject(target, edgeValue, object2, pathArrayVertex);
|
|
if (this.overrides && overridesPathArrays) {
|
|
for (const overridePathArray of overridesPathArrays) {
|
|
if (overridePathArray == null)
|
|
continue;
|
|
const overrides = getPathSafe(this.overrides, overridePathArray);
|
|
if (overrides) {
|
|
this.buildGraphFromObject(target, OVERRIDES_EDGE, overrides, pathArrayVertex);
|
|
}
|
|
}
|
|
}
|
|
this.buildDependencyGraph();
|
|
}
|
|
/**
|
|
* Graft a given operation and value onto `path` child of the target vertex. The `ontoObject` value is built onto
|
|
* the graph each time this function is called, at the given path, while `value` is used for value$1 where
|
|
* `ontoObject` is an operation that invokes value$1.
|
|
*/
|
|
graftValue(target, path, ontoObject, value, edgeValue = this.graftEdge) {
|
|
const pathArray = [...this.getPathArray(target), path];
|
|
const pathVertex = this.findVertexAtPath(pathArray) ?? this.addVertex(path);
|
|
this.value$1.set(pathArray.join("."), value);
|
|
this.buildGraphFromValue(target, pathVertex, edgeValue, pathArray, ontoObject);
|
|
this.buildDependencyGraph();
|
|
}
|
|
/**
|
|
* Resolve a branch as if it were a child of the context vertex, but without attaching it to the resolved root.
|
|
*/
|
|
graftAndResolveOrphan(context, branch) {
|
|
const orphan = {};
|
|
const orphanVertex = this.addVertex(orphan);
|
|
const contextPathArray = this.getPathArray(context);
|
|
this.graftAndResolveChildren(branch, orphanVertex, contextPathArray, []);
|
|
this.resolveVertex(orphanVertex, orphan);
|
|
return getPathSafe(orphan, contextPathArray);
|
|
}
|
|
/**
|
|
* Resolve a value as if it were a child of the context vertex, but without attaching it to the resolved root.
|
|
*/
|
|
graftAndResolveOrphanValue(context, path, ontoObject, value, edgeValue = this.graftEdge) {
|
|
const orphan = {};
|
|
const orphanVertex = this.addVertex(orphan);
|
|
const contextPathArray = this.getPathArray(context);
|
|
const pathArray = [...contextPathArray, path];
|
|
const pathVertex = this.findVertexAtPath(pathArray) ?? this.addVertex(path);
|
|
this.value$1.set(pathArray.join("."), value);
|
|
this.buildGraphFromValue(orphanVertex, pathVertex, edgeValue, pathArray, ontoObject);
|
|
this.resolveVertex(orphanVertex, orphan);
|
|
return getPathSafe(orphan, pathArray);
|
|
}
|
|
buildGraphFromObject(parentVertex, edgeValue, object2, pathArrayVertex, shallowPaths = _OptionsGraph.SHALLOW_KEYS, ignorePaths) {
|
|
const keys = Object.keys(object2);
|
|
const operation = getOperation(object2, keys);
|
|
if (operation) {
|
|
const valueVertex = this.addVertex(object2);
|
|
this.addEdge(parentVertex, valueVertex, edgeValue);
|
|
this.buildGraphFromOperation(valueVertex, edgeValue, operation, pathArrayVertex);
|
|
return;
|
|
}
|
|
if (keys.length === 0) {
|
|
this.addEdge(parentVertex, this.addVertex(Array.isArray(object2) ? [] : {}), edgeValue);
|
|
this.buildGraphAutoEnable(parentVertex, edgeValue, object2, void 0);
|
|
return;
|
|
}
|
|
const pathVertices = this.getVertexChildrenByKey(parentVertex);
|
|
const pathArray = pathArrayVertex ? this.getVertexValue(pathArrayVertex) : [];
|
|
let enabledVertex;
|
|
if (Array.isArray(object2)) {
|
|
this.addEdge(parentVertex, this.addVertex(edgeValue), CHILDREN_SOURCE_EDGE);
|
|
}
|
|
const childPathArray = [...pathArray];
|
|
const pathArrayLength = pathArray.length;
|
|
for (const key of keys) {
|
|
if (ignorePaths?.has(key))
|
|
continue;
|
|
const childPathVertex = pathVertices?.get(key) ?? this.addVertex(key);
|
|
childPathArray[pathArrayLength] = key;
|
|
if (shallowPaths?.has(key)) {
|
|
this.buildShallowGraphFromValue(parentVertex, childPathVertex, edgeValue, childPathArray, object2[key]);
|
|
} else {
|
|
this.buildGraphFromValue(
|
|
parentVertex,
|
|
childPathVertex,
|
|
edgeValue,
|
|
childPathArray,
|
|
object2[key],
|
|
shallowPaths
|
|
);
|
|
}
|
|
if (key === "enabled") {
|
|
enabledVertex = childPathVertex;
|
|
}
|
|
}
|
|
this.buildGraphAutoEnable(parentVertex, edgeValue, object2, enabledVertex);
|
|
}
|
|
buildGraphAutoEnable(parentVertex, edgeValue, object2, enabledVertex) {
|
|
if (edgeValue === DEFAULTS_EDGE && !enabledVertex)
|
|
return;
|
|
if (edgeValue === USER_OPTIONS_EDGE && enabledVertex)
|
|
return;
|
|
if (edgeValue !== DEFAULTS_EDGE && edgeValue !== USER_OPTIONS_EDGE && edgeValue !== USER_PARTIAL_OPTIONS_EDGE && edgeValue !== OVERRIDES_EDGE)
|
|
return;
|
|
let autoEnableVertex = this.findNeighbour(parentVertex, AUTO_ENABLE_EDGE);
|
|
if (!autoEnableVertex) {
|
|
autoEnableVertex = this.addVertex(AUTO_ENABLE_EDGE);
|
|
this.addEdge(parentVertex, autoEnableVertex, AUTO_ENABLE_EDGE);
|
|
}
|
|
if (enabledVertex) {
|
|
this.addEdge(enabledVertex, autoEnableVertex, AUTO_ENABLE_VALUE_EDGE);
|
|
}
|
|
const { enabled, _enabledFromTheme } = object2;
|
|
this.addEdge(
|
|
autoEnableVertex,
|
|
this.addVertex({ enabled, _enabledFromTheme, keys: Object.keys(object2).length }),
|
|
edgeValue
|
|
);
|
|
}
|
|
getVertexChildrenByKey(vertex) {
|
|
const pathNeighbours = this.neighboursWithEdgeValue(vertex, PATH_EDGE);
|
|
if (!pathNeighbours)
|
|
return;
|
|
const pathVertices = /* @__PURE__ */ new Map();
|
|
for (const neighbour of pathNeighbours) {
|
|
pathVertices.set(this.getVertexValue(neighbour), neighbour);
|
|
}
|
|
return pathVertices;
|
|
}
|
|
buildGraphFromValue(parentVertex, pathVertex, edgeValue, pathArray, value, shallowPaths) {
|
|
this.addEdge(parentVertex, pathVertex, PATH_EDGE);
|
|
let pathArrayVertex = this.findNeighbour(pathVertex, PATH_ARRAY_EDGE);
|
|
if (!pathArrayVertex) {
|
|
pathArrayVertex = this.addVertex([...pathArray]);
|
|
this.addEdge(pathVertex, pathArrayVertex, PATH_ARRAY_EDGE);
|
|
}
|
|
const operation = getOperation(value);
|
|
if (operation) {
|
|
const valueVertex = this.addVertex(value);
|
|
this.addEdge(pathVertex, valueVertex, edgeValue);
|
|
this.addEdge(valueVertex, pathArrayVertex, PATH_ARRAY_EDGE);
|
|
this.buildGraphFromOperation(valueVertex, edgeValue, operation, pathArrayVertex);
|
|
} else if (isObjectLike(value)) {
|
|
this.buildGraphFromObject(pathVertex, edgeValue, value, pathArrayVertex, shallowPaths);
|
|
} else {
|
|
const neighbour = this.findNeighbour(pathVertex, edgeValue);
|
|
if (neighbour && this.getVertexValue(neighbour) === value) {
|
|
return;
|
|
}
|
|
const valueVertex = this.addVertex(value);
|
|
this.addEdge(pathVertex, valueVertex, edgeValue);
|
|
}
|
|
}
|
|
buildShallowGraphFromValue(parentVertex, pathVertex, edgeValue, pathArray, value) {
|
|
this.addEdge(parentVertex, pathVertex, PATH_EDGE);
|
|
let pathArrayVertex = this.findNeighbour(pathVertex, PATH_ARRAY_EDGE);
|
|
if (!pathArrayVertex) {
|
|
pathArrayVertex = this.addVertex([...pathArray]);
|
|
this.addEdge(pathVertex, pathArrayVertex, PATH_ARRAY_EDGE);
|
|
}
|
|
const valueVertex = this.addVertex(value);
|
|
this.addEdge(pathVertex, valueVertex, edgeValue);
|
|
}
|
|
buildGraphFromOperation(valueVertex, edgeValue, operation, pathArrayVertex) {
|
|
const operationVertex = this.addVertex(operation.operation);
|
|
this.addEdge(valueVertex, operationVertex, OPERATION_EDGE);
|
|
for (const operationValue of operation.values) {
|
|
this.buildGraphFromOperationValue(valueVertex, operationValue, edgeValue, pathArrayVertex);
|
|
}
|
|
}
|
|
buildGraphFromOperationValue(valueVertex, operationValue, edgeValue, pathArrayVertex = this.EMPTY_PATH_ARRAY_VERTEX) {
|
|
const operationValueVertex = this.addVertex(operationValue);
|
|
this.addEdge(valueVertex, pathArrayVertex, PATH_ARRAY_EDGE);
|
|
this.addEdge(valueVertex, operationValueVertex, OPERATION_VALUE_EDGE);
|
|
const innerOperation = getOperation(operationValue);
|
|
if (innerOperation) {
|
|
this.buildGraphFromOperation(operationValueVertex, edgeValue, innerOperation, pathArrayVertex);
|
|
} else if (isObjectLike(operationValue)) {
|
|
this.buildGraphFromObject(operationValueVertex, edgeValue, operationValue, pathArrayVertex);
|
|
}
|
|
}
|
|
/**
|
|
* Add dependency edges on operations that require other vertices to be resolved before the operation can be
|
|
* resolved. Then clear the list of pending edges.
|
|
*/
|
|
buildDependencyGraph() {
|
|
for (let i = 0; i < this.pendingProcessingEdgesFrom.length; i++) {
|
|
const valueVertex = this.pendingProcessingEdgesFrom[i];
|
|
const operationKeyVertex = this.pendingProcessingEdgesTo[i];
|
|
const operation = this.getVertexValue(operationKeyVertex);
|
|
if (!isOperation(operation))
|
|
continue;
|
|
const operationValues = this.neighboursWithEdgeValue(valueVertex, OPERATION_VALUE_EDGE);
|
|
const operator = operations[operation];
|
|
const dependenciesFn = typeof operator === "function" ? void 0 : operator.dependencies;
|
|
dependenciesFn?.(this, valueVertex, operationValues ?? []);
|
|
}
|
|
this.pendingProcessingEdgesFrom = [];
|
|
this.pendingProcessingEdgesTo = [];
|
|
}
|
|
/**
|
|
* Within the branch starting at the given vertex, reassign any value vertices and their operation key vertices to
|
|
* the pending processing edges. These can then be built with `this.buildDependencyGraph()`.
|
|
*/
|
|
refreshPendingProcessingEdges(vertex) {
|
|
const defaultValueVertex = this.findNeighbour(vertex, DEFAULTS_EDGE);
|
|
const valueVertex = defaultValueVertex ?? vertex;
|
|
const operationVertex = this.findNeighbour(valueVertex, OPERATION_EDGE);
|
|
if (operationVertex) {
|
|
this.pendingProcessingEdgesFrom.push(valueVertex);
|
|
this.pendingProcessingEdgesTo.push(operationVertex);
|
|
const neighbours2 = this.neighboursWithEdgeValue(valueVertex, OPERATION_VALUE_EDGE);
|
|
if (neighbours2) {
|
|
for (const neighbour of neighbours2) {
|
|
this.refreshPendingProcessingEdges(neighbour);
|
|
}
|
|
}
|
|
}
|
|
const neighbours = this.neighboursWithEdgeValue(vertex, PATH_EDGE);
|
|
if (neighbours) {
|
|
for (const neighbour of neighbours) {
|
|
this.refreshPendingProcessingEdges(neighbour);
|
|
}
|
|
}
|
|
}
|
|
resolveVertex(vertex, object2 = this.resolved, prune) {
|
|
const pathArray = this.getPathArray(vertex);
|
|
const rootAncestorPath = pathArray[0];
|
|
if (pathArray.length === 1) {
|
|
this.resolvedRootAncestorsPaths.add(rootAncestorPath);
|
|
}
|
|
if (pathArray.length > 1 && !this.resolvedRootAncestorsPaths.has(rootAncestorPath)) {
|
|
const rootAncestorVertex = this.findVertexAtPath([rootAncestorPath]);
|
|
if (rootAncestorVertex) {
|
|
this.resolveVertex(rootAncestorVertex, object2, prune);
|
|
return;
|
|
}
|
|
}
|
|
if (this.userPartialOptions == null && object2 === this.resolved && pathArray.length > 0) {
|
|
const resolvedVertexValue = getPathSafe(object2, pathArray);
|
|
if (resolvedVertexValue != null && !isPlainObject(resolvedVertexValue)) {
|
|
return;
|
|
}
|
|
}
|
|
this.resolveVertexInEdgePriority(vertex, object2, pathArray, prune);
|
|
this.resolveVertexAutoEnable(vertex, object2, pathArray);
|
|
this.resolveVertexChildren(vertex, object2, prune);
|
|
}
|
|
resolveVertexInEdgePriority(vertex, object2, pathArray, prune) {
|
|
const children = this.neighboursWithEdgeValue(vertex, PATH_EDGE);
|
|
const [highestPriority] = this.edgePriority;
|
|
for (const edgeValue of this.edgePriority) {
|
|
const valueVertex = this.findNeighbour(vertex, edgeValue);
|
|
if (valueVertex == null)
|
|
continue;
|
|
const value = this.resolveVertexValueInternal(vertex, valueVertex);
|
|
if (value == null && edgeValue !== highestPriority)
|
|
continue;
|
|
if (children && children.length > 0 && edgeValue !== highestPriority)
|
|
continue;
|
|
if (Array.isArray(prune) && prune.includes(edgeValue))
|
|
continue;
|
|
this.hasUnsafeClearKeys || (this.hasUnsafeClearKeys = value != null && _OptionsGraph.UNSAFE_CLEAR_KEYS.has(pathArray.at(-1)));
|
|
if (pathArray.length === 0) {
|
|
if (value == null)
|
|
continue;
|
|
this.resolved = value;
|
|
} else {
|
|
setPathSafe(object2, pathArray, value);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
resolveVertexValueInternal(vertex, valueVertex) {
|
|
this.resolveVertexDependencies(valueVertex);
|
|
const operation = this.findNeighbourValue(valueVertex, OPERATION_EDGE);
|
|
if (operation && isOperation(operation)) {
|
|
const operationValues = this.neighboursWithEdgeValue(valueVertex, OPERATION_VALUE_EDGE);
|
|
const operator = operations[operation];
|
|
const operatorFn = typeof operator === "function" ? operator : operator.resolve;
|
|
const resolved = operatorFn?.(this, vertex, operationValues ?? []);
|
|
return resolved === RESOLVED_TO_BRANCH ? void 0 : resolved;
|
|
}
|
|
return this.resolveValueOrSymbol(this.getVertexValue(valueVertex));
|
|
}
|
|
resolveVertexAutoEnable(vertex, object2, pathArray) {
|
|
const autoEnableValueVertex = this.neighboursWithEdgeValue(vertex, AUTO_ENABLE_VALUE_EDGE)?.[0];
|
|
if (!autoEnableValueVertex)
|
|
return;
|
|
const pathVertex = this.findVertexAtPath(pathArray);
|
|
const defaultsEnabled = this.findNeighbourValue(autoEnableValueVertex, DEFAULTS_EDGE);
|
|
const overridesEnabled = this.findNeighbourValue(autoEnableValueVertex, OVERRIDES_EDGE);
|
|
const userOptionsEnabled = this.findNeighbourValue(autoEnableValueVertex, USER_OPTIONS_EDGE);
|
|
const hasUserOptionEnabled = pathVertex && this.findNeighbour(pathVertex, USER_OPTIONS_EDGE) != null;
|
|
const userPartialOptionsEnabled = hasUserOptionEnabled ? void 0 : this.findNeighbourValue(autoEnableValueVertex, USER_PARTIAL_OPTIONS_EDGE);
|
|
const isUserEnabled = userOptionsEnabled != null && userOptionsEnabled.enabled == null || userPartialOptionsEnabled != null && userPartialOptionsEnabled.enabled == null;
|
|
if (isUserEnabled && !defaultsEnabled?._enabledFromTheme && !overridesEnabled?._enabledFromTheme) {
|
|
setPathSafe(object2, pathArray, true);
|
|
}
|
|
}
|
|
resolveVertexChildren(vertex, object2, prune) {
|
|
const children = this.neighboursWithEdgeValue(vertex, PATH_EDGE);
|
|
if (!children)
|
|
return;
|
|
prune ?? (prune = this.findNeighbourValue(vertex, PRUNE_EDGE));
|
|
for (const child of children) {
|
|
const path = this.getVertexValue(child);
|
|
if (children.length > 1 && isOperation(path))
|
|
continue;
|
|
if (path === "_enabledFromTheme")
|
|
continue;
|
|
this.resolveVertex(child, object2, prune);
|
|
}
|
|
}
|
|
resolveVertexDependencies(vertex) {
|
|
const dependencies = this.neighboursWithEdgeValue(vertex, DEPENDENCY_EDGE);
|
|
if (!dependencies)
|
|
return;
|
|
for (const dependency of dependencies) {
|
|
this.resolveVertex(dependency);
|
|
}
|
|
}
|
|
graftAndResolveChildren(remoteBranch, orphanBranch, contextPathArray, orphanPathArray) {
|
|
const remoteChildren = this.neighboursWithEdgeValue(remoteBranch, PATH_EDGE);
|
|
if (!remoteChildren)
|
|
return;
|
|
for (const remoteChild of remoteChildren) {
|
|
const remoteChildPath = this.getVertexValue(remoteChild);
|
|
const childContextPathArray = [...contextPathArray, remoteChildPath];
|
|
const childOrphanPathArray = [...orphanPathArray, remoteChildPath];
|
|
const orphanChildPathVertex = this.addVertex(remoteChildPath);
|
|
const defaultValue = this.findNeighbourValue(remoteChild, DEFAULTS_EDGE);
|
|
this.addEdge(orphanBranch, orphanChildPathVertex, PATH_EDGE);
|
|
const orphanChildPathArrayVertex = this.addVertex(childContextPathArray);
|
|
this.addEdge(orphanChildPathVertex, orphanChildPathArrayVertex, PATH_ARRAY_EDGE);
|
|
if (isObject(defaultValue)) {
|
|
this.buildGraphFromObject(
|
|
orphanChildPathVertex,
|
|
DEFAULTS_EDGE,
|
|
defaultValue,
|
|
orphanChildPathArrayVertex
|
|
);
|
|
const orphanChildValueVertex = this.findNeighbour(orphanChildPathVertex, DEFAULTS_EDGE);
|
|
this.addEdge(orphanChildValueVertex, this.addVertex(childContextPathArray), PATH_ARRAY_EDGE);
|
|
const operation = this.findNeighbourValue(orphanChildValueVertex, OPERATION_EDGE);
|
|
if (isOperation(operation)) {
|
|
const operationValues = this.neighboursWithEdgeValue(orphanChildValueVertex, OPERATION_VALUE_EDGE);
|
|
const operator = operations[operation];
|
|
const dependenciesFn = typeof operator === "function" ? void 0 : operator.dependencies;
|
|
dependenciesFn?.(this, orphanChildValueVertex, operationValues ?? []);
|
|
}
|
|
}
|
|
this.graftAndResolveChildren(
|
|
remoteChild,
|
|
orphanChildPathVertex,
|
|
childContextPathArray,
|
|
childOrphanPathArray
|
|
);
|
|
}
|
|
}
|
|
resolveValueOrSymbol(value) {
|
|
return typeof value === "symbol" && this.internalParams?.has(value) ? this.internalParams.get(value) : value;
|
|
}
|
|
snapshot() {
|
|
debug2(`snapshot`);
|
|
this.isRollingBack = true;
|
|
}
|
|
rollback() {
|
|
debug2(`rollback ${this.rollbackEdgesFrom.length} edges and ${this.rollbackVertices.length} vertices`);
|
|
for (let i = 0; i < this.rollbackEdgesFrom.length; i++) {
|
|
const from3 = this.rollbackEdgesFrom[i];
|
|
const to = this.rollbackEdgesTo[i];
|
|
const edgeValue = this.rollbackEdgesValue[i];
|
|
this.removeEdge(from3, to, edgeValue);
|
|
}
|
|
for (const vertex of this.rollbackVertices) {
|
|
this.removeVertex(vertex);
|
|
}
|
|
this.cachedPathVertices.clear();
|
|
this.rollbackVertices = [];
|
|
this.rollbackEdgesFrom = [];
|
|
this.rollbackEdgesTo = [];
|
|
this.rollbackEdgesValue = [];
|
|
this.isRollingBack = false;
|
|
}
|
|
/**
|
|
* Console log a flowchart diagram of the graph at the given path.
|
|
*/
|
|
diagram(pathArray, maxDepth = 2) {
|
|
this.diagramKeys = /* @__PURE__ */ new Map();
|
|
this.diagramEdges = /* @__PURE__ */ new Map();
|
|
const vertex = this.findVertexAtPath(pathArray);
|
|
const diagram = [
|
|
"---",
|
|
"config:",
|
|
" layout: elk",
|
|
" look: neo",
|
|
" theme: redux",
|
|
"---",
|
|
"flowchart TB"
|
|
];
|
|
if (vertex) {
|
|
this.diagramVertex(diagram, vertex, 1, maxDepth);
|
|
}
|
|
diagram.push("classDef UO fill: #e8f5e8, stroke: #4caf50");
|
|
diagram.push("classDef DE fill: #e3f2fd, stroke: #2196f3");
|
|
diagram.push("classDef DEP fill: #ffe0fd, stroke: #ff00f2");
|
|
diagram.push("classDef OP fill: #fff3e0, stroke: #ff9800");
|
|
diagram.push("classDef OPV fill: #fff3e0, stroke: #ff9800, stroke-width: 1px");
|
|
diagram.push("classDef OV fill: #e8f5ee, stroke: #4caf87");
|
|
console.log(diagram.join("\n"));
|
|
}
|
|
diagramKey(path) {
|
|
let diagramKey = this.diagramKeys.get(path);
|
|
if (!diagramKey) {
|
|
diagramKey = `${this.diagramKeys.size}`;
|
|
this.diagramKeys.set(path, diagramKey);
|
|
}
|
|
return diagramKey;
|
|
}
|
|
diagramLabel(path, vertex, edge) {
|
|
let diagramKey = this.diagramKeys.get(path);
|
|
if (diagramKey)
|
|
return diagramKey;
|
|
diagramKey = this.diagramKey(path);
|
|
const classNames = {
|
|
[USER_OPTIONS_EDGE]: "UO",
|
|
[DEFAULTS_EDGE]: "DE",
|
|
[DEPENDENCY_EDGE]: "DEP",
|
|
[OPERATION_EDGE]: "OP",
|
|
[OPERATION_VALUE_EDGE]: "OPV",
|
|
[OVERRIDES_EDGE]: "OV"
|
|
};
|
|
let className = edge ? classNames[edge] ?? void 0 : void 0;
|
|
className = className ? `:::${className}` : "";
|
|
if (typeof vertex.value === "symbol") {
|
|
return `${diagramKey}[/"[symbol]"\\]${className}`;
|
|
} else if (Array.isArray(vertex.value)) {
|
|
return `${diagramKey}[/"[array]"\\]${className}`;
|
|
} else if (typeof vertex.value === "object") {
|
|
return `${diagramKey}[/"[object]"\\]${className}`;
|
|
} else if (edge === DEFAULTS_EDGE || edge === USER_OPTIONS_EDGE || edge === OVERRIDES_EDGE) {
|
|
return `${diagramKey}("${vertex.value}")${className}`;
|
|
} else {
|
|
return `${diagramKey}["${vertex.value}"]${className}`;
|
|
}
|
|
}
|
|
diagramVertex(diagram, vertex, depth, maxDepth) {
|
|
const pathArray = this.getPathArray(vertex);
|
|
const path = pathArray.length > 0 ? pathArray.join(".") : "root";
|
|
this.diagramNeighbours(diagram, path, vertex, depth + 1, maxDepth);
|
|
let diagramKey = this.diagramKeys.get(path);
|
|
if (!diagramKey) {
|
|
diagramKey = this.diagramKey(path);
|
|
diagram.push(` ${diagramKey}["${vertex.value}"]`);
|
|
}
|
|
}
|
|
diagramNeighbours(diagram, path, vertex, depth, maxDepth) {
|
|
for (const neighbour of this.neighboursWithEdgeValue(vertex, PATH_EDGE) ?? []) {
|
|
const neighbourPathArray = this.getPathArray(neighbour);
|
|
const neighbourPath = neighbourPathArray.length > 0 ? neighbourPathArray.join(".") : "root";
|
|
if (depth < maxDepth) {
|
|
this.diagramVertex(diagram, neighbour, depth + 1, maxDepth);
|
|
}
|
|
this.diagramChild(diagram, PATH_EDGE, path, vertex, neighbourPath, vertex);
|
|
}
|
|
const userValues = this.neighboursWithEdgeValue(vertex, USER_OPTIONS_EDGE) ?? [];
|
|
let index = 0;
|
|
for (const userValue of userValues) {
|
|
this.diagramChild(
|
|
diagram,
|
|
USER_OPTIONS_EDGE,
|
|
path,
|
|
vertex,
|
|
`${path}.${USER_OPTIONS_EDGE}.${index}`,
|
|
userValue
|
|
);
|
|
index++;
|
|
}
|
|
const defaultValues = this.neighboursWithEdgeValue(vertex, DEFAULTS_EDGE) ?? [];
|
|
index = 0;
|
|
for (const defaultValue of defaultValues) {
|
|
this.diagramChildWithNeighbours(
|
|
diagram,
|
|
DEFAULTS_EDGE,
|
|
path,
|
|
vertex,
|
|
`${path}.${DEFAULTS_EDGE}.${index}`,
|
|
defaultValue,
|
|
depth + 1,
|
|
maxDepth
|
|
);
|
|
index++;
|
|
}
|
|
const operationVertices = this.neighboursWithEdgeValue(vertex, OPERATION_EDGE) ?? [];
|
|
index = 0;
|
|
const [operation] = operationVertices;
|
|
if (operation) {
|
|
this.diagramChildWithNeighbours(
|
|
diagram,
|
|
OPERATION_EDGE,
|
|
path,
|
|
vertex,
|
|
`${path}.${OPERATION_EDGE}.${index}`,
|
|
operation,
|
|
depth + 1,
|
|
maxDepth
|
|
);
|
|
index++;
|
|
}
|
|
const operationValueVertices = this.neighboursWithEdgeValue(vertex, OPERATION_VALUE_EDGE) ?? [];
|
|
index = 0;
|
|
for (const operationValue of operationValueVertices) {
|
|
this.diagramChildWithNeighbours(
|
|
diagram,
|
|
OPERATION_VALUE_EDGE,
|
|
path,
|
|
vertex,
|
|
`${path}.${OPERATION_VALUE_EDGE}.${index}`,
|
|
operationValue,
|
|
depth + 1,
|
|
maxDepth
|
|
);
|
|
index++;
|
|
}
|
|
const dependencyVertices = this.neighboursWithEdgeValue(vertex, DEPENDENCY_EDGE) ?? [];
|
|
index = 0;
|
|
for (const dependency of dependencyVertices) {
|
|
this.diagramChildWithNeighbours(
|
|
diagram,
|
|
DEPENDENCY_EDGE,
|
|
path,
|
|
vertex,
|
|
this.getPathArray(dependency).join("."),
|
|
dependency,
|
|
depth + 1,
|
|
maxDepth
|
|
);
|
|
index++;
|
|
}
|
|
}
|
|
diagramChild(diagram, edge, parentPath, parentVertex, childPath, childVertex) {
|
|
let edges = this.diagramEdges.get(parentPath);
|
|
if (edges?.has(childPath))
|
|
return;
|
|
if (!edges) {
|
|
edges = /* @__PURE__ */ new Set();
|
|
this.diagramEdges.set(parentPath, edges);
|
|
}
|
|
edges.add(childPath);
|
|
const edgeString = edge === PATH_EDGE ? "" : `|${edge}|`;
|
|
diagram.push(
|
|
` ${this.diagramLabel(parentPath, parentVertex)} -->${edgeString} ${this.diagramLabel(childPath, childVertex, edge)}`
|
|
);
|
|
}
|
|
diagramChildWithNeighbours(diagram, edge, parentPath, parentVertex, childPath, childVertex, depth, maxDepth) {
|
|
this.diagramChild(diagram, edge, parentPath, parentVertex, childPath, childVertex);
|
|
this.diagramNeighbours(diagram, childPath, childVertex, depth + 1, maxDepth);
|
|
}
|
|
};
|
|
// The default priority order in which to resolve options values.
|
|
_OptionsGraph.EDGE_PRIORITY = [USER_OPTIONS_EDGE, OVERRIDES_EDGE, DEFAULTS_EDGE];
|
|
_OptionsGraph.GRAFT_EDGE = DEFAULTS_EDGE;
|
|
// These keys must be stored as shallow objects in the graph and not manipulated.
|
|
_OptionsGraph.SHALLOW_KEYS = /* @__PURE__ */ new Set(["context", "data", "topology"]);
|
|
// These keys must be excluded when building the graph, they are instead resolved separately since they are objects
|
|
// that must be applied to arrays.
|
|
_OptionsGraph.COMPLEX_KEYS = ["annotations", "axes", "series"];
|
|
_OptionsGraph.ANNOTATIONS_OPTIONS_KEYS = [
|
|
"axesButtons",
|
|
"data",
|
|
"enabled",
|
|
"optionsToolbar",
|
|
"snap",
|
|
"toolbar",
|
|
"xKey",
|
|
"volumeKey"
|
|
];
|
|
// If any of these keys are present in the resolved object then calling `clearSafe()` will not clear the graph.
|
|
_OptionsGraph.UNSAFE_CLEAR_KEYS = /* @__PURE__ */ new Set(["itemStyler", "styler"]);
|
|
// A cache of values that persists between chart updates, use sparingly.
|
|
_OptionsGraph.valueCache = /* @__PURE__ */ new Map();
|
|
var OptionsGraph = _OptionsGraph;
|
|
|
|
// packages/ag-charts-community/src/module/optionsModule.ts
|
|
var stringFormat = (value) => `'${value}'`;
|
|
var AXIS_ID_PREFIX = "__AXIS_ID_";
|
|
var POSITION_DIRECTIONS = {
|
|
top: "x" /* X */,
|
|
bottom: "x" /* X */,
|
|
left: "y" /* Y */,
|
|
right: "y" /* Y */
|
|
};
|
|
var _ChartOptions = class _ChartOptions {
|
|
constructor(currentUserOptions, newUserOptions, processedOverrides, specialOverrides, metadata, deltaOptions, stripSymbols = false, apiStartTime) {
|
|
this.themeParameters = {};
|
|
this.optionMetadata = metadata ?? {};
|
|
this.processedOverrides = processedOverrides ?? {};
|
|
let baseChartOptions = null;
|
|
if (currentUserOptions instanceof _ChartOptions) {
|
|
baseChartOptions = currentUserOptions;
|
|
this.specialOverrides = baseChartOptions.specialOverrides;
|
|
if (deltaOptions) {
|
|
this.userDeltaKeys = new Set(Object.keys(deltaOptions));
|
|
}
|
|
deltaOptions ?? (deltaOptions = jsonDiff(
|
|
baseChartOptions.userOptions,
|
|
newUserOptions,
|
|
_ChartOptions.JSON_DIFF_OPTS
|
|
));
|
|
this.userOptions = deepClone(merge(deltaOptions, baseChartOptions.userOptions), {
|
|
..._ChartOptions.OPTIONS_CLONE_OPTS_SLOW,
|
|
seen: []
|
|
});
|
|
} else {
|
|
this.userOptions = deepClone(currentUserOptions ?? newUserOptions, {
|
|
..._ChartOptions.OPTIONS_CLONE_OPTS_SLOW,
|
|
seen: []
|
|
});
|
|
this.specialOverrides = this.specialOverridesDefaults({ ...specialOverrides });
|
|
}
|
|
this.findSeriesWithUserVisiblity(newUserOptions, deltaOptions);
|
|
if (stripSymbols) {
|
|
this.removeLeftoverSymbols(this.userOptions);
|
|
}
|
|
const dataChangedLength = currentUserOptions instanceof _ChartOptions && deltaOptions?.data !== void 0 && deltaOptions?.data?.length !== currentUserOptions.userOptions.data?.length;
|
|
let activeTheme, processedOptions, fastDelta, themeParameters, annotationThemes, googleFonts, optionsGraph;
|
|
if (!stripSymbols && this.seriesWithUserVisibility == void 0 && deltaOptions !== void 0 && _ChartOptions.isFastPathDelta(deltaOptions) && baseChartOptions != null && !dataChangedLength) {
|
|
({ activeTheme, processedOptions, fastDelta } = this.fastSetup(deltaOptions, baseChartOptions));
|
|
themeParameters = baseChartOptions.themeParameters;
|
|
annotationThemes = baseChartOptions.annotationThemes;
|
|
} else {
|
|
_ChartOptions.perfDebug(`ChartOptions.slowSetup()`);
|
|
({ activeTheme, processedOptions, themeParameters, annotationThemes, googleFonts, optionsGraph } = this.slowSetup(processedOverrides, deltaOptions, stripSymbols));
|
|
}
|
|
this.activeTheme = activeTheme;
|
|
this.processedOptions = processedOptions;
|
|
this.fastDelta = fastDelta ?? void 0;
|
|
this.themeParameters = themeParameters;
|
|
this.annotationThemes = annotationThemes;
|
|
this.googleFonts = googleFonts;
|
|
this.optionsGraph = optionsGraph;
|
|
if (apiStartTime !== void 0 && typeof apiStartTime === "number" && !Number.isNaN(apiStartTime)) {
|
|
const endTime = performance.now();
|
|
this.optionsProcessingTime = endTime - apiStartTime;
|
|
}
|
|
debugLogger_exports.inDevelopmentMode(() => deepFreeze(this));
|
|
}
|
|
static isFastPathDelta(deltaOptions) {
|
|
for (const key of Object.keys(deltaOptions ?? {})) {
|
|
if (!this.FAST_PATH_OPTIONS.has(key)) {
|
|
_ChartOptions.perfDebug("ChartOptions.isFastPathDelta() - slow path required due to presence of: ", key);
|
|
return false;
|
|
}
|
|
}
|
|
_ChartOptions.perfDebug(`ChartOptions.isFastPathDelta() - fast path possible.`);
|
|
return true;
|
|
}
|
|
findSeriesWithUserVisiblity(newUserOptions, deltaOptions) {
|
|
for (const o of [newUserOptions, deltaOptions]) {
|
|
const series = o?.series;
|
|
if (!Array.isArray(series))
|
|
continue;
|
|
for (let index = 0; index < series.length; index++) {
|
|
const s = series[index];
|
|
if (!("visible" in s))
|
|
continue;
|
|
this.seriesWithUserVisibility ?? (this.seriesWithUserVisibility = {
|
|
identifiers: /* @__PURE__ */ new Set(),
|
|
indices: /* @__PURE__ */ new Set()
|
|
});
|
|
if (s.id) {
|
|
this.seriesWithUserVisibility.identifiers.add(s.id);
|
|
} else {
|
|
this.seriesWithUserVisibility.indices.add(index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fastSetup(deltaOptions, baseChartOptions) {
|
|
const { activeTheme, processedOptions: baseOptions } = baseChartOptions;
|
|
const { presetType } = this.optionMetadata;
|
|
if (presetType != null && deltaOptions?.data != null) {
|
|
const presetDef = moduleRegistry_exports.getPresetModule(presetType);
|
|
if (presetDef?.processData) {
|
|
const { series, data } = presetDef.processData(deltaOptions.data);
|
|
deltaOptions = mergeDefaults({ series, data }, deltaOptions);
|
|
}
|
|
}
|
|
this.fastSeriesSetup(deltaOptions, baseOptions);
|
|
const processedOptions = mergeDefaults(deltaOptions, baseOptions);
|
|
_ChartOptions.debug("ChartOptions.fastSetup() - processed options", processedOptions);
|
|
return { activeTheme, processedOptions, fastDelta: deltaOptions };
|
|
}
|
|
fastSeriesSetup(deltaOptions, baseOptions) {
|
|
if (!deltaOptions?.series)
|
|
return;
|
|
if (deltaOptions.series?.every((s, i) => jsonPropertyCompare(s, baseOptions.series?.[i] ?? {}))) {
|
|
delete deltaOptions["series"];
|
|
} else {
|
|
deltaOptions.series = deltaOptions.series.map((s, i) => {
|
|
return merge(s, baseOptions.series?.[i] ?? {});
|
|
});
|
|
}
|
|
}
|
|
slowSetup(processedOverrides, deltaOptions, stripSymbols = false) {
|
|
let options = deepClone(this.userOptions, _ChartOptions.OPTIONS_CLONE_OPTS_FAST);
|
|
if (deltaOptions) {
|
|
options = mergeDefaults(deltaOptions, options);
|
|
if (stripSymbols) {
|
|
this.removeLeftoverSymbols(options);
|
|
}
|
|
}
|
|
let activeTheme = sanitizeThemeModules(getChartTheme(options.theme));
|
|
const { presetType } = this.optionMetadata;
|
|
if (presetType != null) {
|
|
const presetDef = moduleRegistry_exports.getPresetModule(presetType);
|
|
if (presetDef) {
|
|
const { validate: validatePreset = validate } = presetDef;
|
|
const presetParams = options;
|
|
const presetSubType = options.type;
|
|
const presetTheme = presetSubType == null ? void 0 : activeTheme.presets[presetSubType];
|
|
const { cleared, invalid } = validatePreset(presetParams, presetDef.options, "");
|
|
for (const error2 of invalid) {
|
|
logger_exports.warn(error2);
|
|
}
|
|
if (hasRequiredInPath(invalid, "")) {
|
|
options = {};
|
|
} else {
|
|
_ChartOptions.debug(">>> AgCharts.createOrUpdate() - applying preset", cleared);
|
|
options = presetDef.create(cleared, presetTheme, () => this.activeTheme);
|
|
activeTheme = sanitizeThemeModules(getChartTheme(options.theme));
|
|
}
|
|
}
|
|
}
|
|
this.soloSeriesIntegrity(options);
|
|
if (presetType != null) {
|
|
activeTheme.templateTheme(options, false);
|
|
}
|
|
removeIncompatibleModuleOptions(void 0, options);
|
|
const missingSeriesModules = this.validateSeriesOptions(options);
|
|
const chartType = detectChartType(options);
|
|
this.chartDef = moduleRegistry_exports.getChartModule(chartType);
|
|
if (!this.chartDef.placeholder) {
|
|
const { validate: validateChart = validate } = this.chartDef;
|
|
const { cleared, invalid } = validateChart(options, this.chartDef.options, "");
|
|
for (const error2 of invalid) {
|
|
logger_exports.warn(error2);
|
|
}
|
|
options = cleared;
|
|
}
|
|
this.validateAxesOptions(options);
|
|
this.removeDisabledOptions(options);
|
|
let googleFonts = this.processFonts(activeTheme.params);
|
|
googleFonts = this.processFonts(options, googleFonts);
|
|
this.processSeriesOptions(options);
|
|
const unmappedAxisKeys = this.processAxesOptions(options, chartType);
|
|
const optionsGraph = createOptionsGraph(activeTheme, options);
|
|
const resolvedOptions = optionsGraph.resolve();
|
|
const themeParameters = optionsGraph.resolveParams();
|
|
const annotationThemes = optionsGraph.resolveAnnotationThemes();
|
|
optionsGraph.clearSafe();
|
|
const processedOptions = mergeDefaults(processedOverrides, resolvedOptions);
|
|
removeIncompatibleModuleOptions(this.chartDef.name, processedOptions);
|
|
processModuleOptions(this.chartDef.name, processedOptions, missingSeriesModules);
|
|
this.validateSeriesOptions(processedOptions);
|
|
this.validateAxesOptions(processedOptions, unmappedAxisKeys);
|
|
this.validatePluginOptions(processedOptions);
|
|
this.processMiniChartSeriesOptions(processedOptions);
|
|
if (!processedOptions.loadGoogleFonts) {
|
|
googleFonts.clear();
|
|
}
|
|
_ChartOptions.debug(() => ["ChartOptions.slowSetup() - processed options", deepClone(processedOptions)]);
|
|
return { activeTheme, processedOptions, themeParameters, annotationThemes, googleFonts, optionsGraph };
|
|
}
|
|
validatePluginOptions(options) {
|
|
for (const pluginDef of moduleRegistry_exports.listModulesByType("plugin" /* Plugin */)) {
|
|
const pluginKey = pluginDef.name;
|
|
if (pluginKey in options && pluginDef.options != null && (!pluginDef.chartType || pluginDef.chartType === this.chartDef?.name)) {
|
|
const { cleared, invalid } = validate(options[pluginKey], pluginDef.options, pluginDef.name);
|
|
for (const error2 of invalid) {
|
|
logger_exports.warn(error2);
|
|
}
|
|
options[pluginKey] = cleared;
|
|
}
|
|
}
|
|
}
|
|
validateSeriesOptions(options) {
|
|
const chartType = this.chartDef?.name;
|
|
const validatedSeriesOptions = [];
|
|
const seriesCount = options.series?.length ?? 0;
|
|
const missingModules = [];
|
|
let validSeriesTypes;
|
|
for (let index = 0; index < seriesCount; index++) {
|
|
const keyPath = `series[${index}]`;
|
|
const seriesOptions = options.series[index];
|
|
const seriesDef = moduleRegistry_exports.getSeriesModule(seriesOptions.type);
|
|
if (seriesDef == null) {
|
|
const isEnterprise2 = moduleRegistry_exports.isEnterprise();
|
|
validSeriesTypes ?? (validSeriesTypes = joinFormatted(
|
|
Array.from(ExpectedModules.values()).filter(
|
|
(def) => def.type === "series" /* Series */ && (isEnterprise2 || !def.enterprise) && (!chartType || def.chartType === chartType)
|
|
).map((def) => def.name),
|
|
"or",
|
|
stringFormat
|
|
));
|
|
const modulePlaceholder = ExpectedModules.get(seriesOptions.type);
|
|
if (seriesOptions.type != null && modulePlaceholder?.type === "series" /* Series */) {
|
|
missingModules.push(modulePlaceholder);
|
|
continue;
|
|
}
|
|
logger_exports.warn(
|
|
seriesOptions.type == null ? `Option \`${keyPath}.type\` is required and has not been provided; expecting ${validSeriesTypes}, ignoring.` : `Unknown type \`${seriesOptions.type}\` at \`${keyPath}.type\`; expecting ${validSeriesTypes}, ignoring.`
|
|
);
|
|
continue;
|
|
} else if (chartType && seriesDef.chartType !== chartType) {
|
|
logger_exports.warn(
|
|
`Series type \`${seriesDef.name}\` at \`${keyPath}.type\` is not supported by chart type \`${chartType}\`, ignoring.`
|
|
);
|
|
continue;
|
|
}
|
|
if (seriesDef.options == null) {
|
|
validatedSeriesOptions.push(seriesOptions);
|
|
continue;
|
|
}
|
|
const { validate: validateSeries = validate } = seriesDef;
|
|
const { cleared, invalid } = validateSeries(seriesOptions, seriesDef.options, keyPath);
|
|
for (const error2 of invalid) {
|
|
logger_exports.warn(error2);
|
|
}
|
|
if (!hasRequiredInPath(invalid, keyPath)) {
|
|
validatedSeriesOptions.push(cleared);
|
|
}
|
|
}
|
|
options.series = validatedSeriesOptions;
|
|
return missingModules;
|
|
}
|
|
validateAxesOptions(options, unmappedAxisKeys) {
|
|
if (!("axes" in options) || !options.axes)
|
|
return;
|
|
const chartType = this.chartDef?.name;
|
|
const validatedAxesOptions = {};
|
|
let validAxesTypes;
|
|
for (const [key, axisOptions] of entries(options.axes)) {
|
|
if (!axisOptions)
|
|
continue;
|
|
if (axisOptions.type == null) {
|
|
validatedAxesOptions[key] = axisOptions;
|
|
continue;
|
|
}
|
|
const keyPath = `axes.${unmappedAxisKeys?.get(key) ?? key}`;
|
|
const axisDef = moduleRegistry_exports.getAxisModule(axisOptions.type);
|
|
if (axisDef == null) {
|
|
const isEnterprise2 = moduleRegistry_exports.isEnterprise();
|
|
validAxesTypes ?? (validAxesTypes = joinFormatted(
|
|
Array.from(ExpectedModules.values()).filter(
|
|
(def) => def.type === "axis" /* Axis */ && (isEnterprise2 || !def.enterprise) && def.chartType === chartType
|
|
).map((def) => def.name),
|
|
"or",
|
|
stringFormat
|
|
));
|
|
const modulePlaceholder = ExpectedModules.get(axisOptions.type);
|
|
if (modulePlaceholder?.type !== "axis" /* Axis */) {
|
|
logger_exports.warn(
|
|
`Unknown type \`${axisOptions.type}\` at \`${keyPath}.type\`; expecting one of ${validAxesTypes}, ignoring.`
|
|
);
|
|
}
|
|
continue;
|
|
} else if (axisDef.chartType !== chartType) {
|
|
logger_exports.warn(
|
|
`Axis type \`${axisDef.name}\` at \`${keyPath}.type\` is not supported by chart type \`${chartType}\`, ignoring.`
|
|
);
|
|
break;
|
|
}
|
|
const { validate: validateAxis = validate } = axisDef;
|
|
const { cleared, invalid } = validateAxis(axisOptions, axisDef.options, keyPath);
|
|
for (const error2 of invalid) {
|
|
logger_exports.warn(error2);
|
|
}
|
|
if (!hasRequiredInPath(invalid, keyPath)) {
|
|
validatedAxesOptions[key] = cleared;
|
|
}
|
|
}
|
|
options.axes = validatedAxesOptions;
|
|
}
|
|
diffOptions(other) {
|
|
if (this === other)
|
|
return {};
|
|
if (other == null)
|
|
return this.processedOptions;
|
|
return this.fastDelta ?? jsonDiff(other.processedOptions, this.processedOptions, _ChartOptions.JSON_DIFF_OPTS);
|
|
}
|
|
optionsType(options) {
|
|
return options.series?.[0]?.type ?? "line";
|
|
}
|
|
processSeriesOptions(options) {
|
|
const displayNullData = options.displayNullData;
|
|
const processedSeries = options.series?.map((series) => {
|
|
const seriesDef = moduleRegistry_exports.getSeriesModule(series.type);
|
|
const visibleDefined = Boolean(seriesDef?.options?.visible);
|
|
const seriesDefaults = {};
|
|
if (visibleDefined) {
|
|
seriesDefaults.visible = true;
|
|
}
|
|
if (displayNullData !== void 0 && series.allowNullKeys === void 0) {
|
|
seriesDefaults.allowNullKeys = displayNullData;
|
|
}
|
|
return mergeDefaults(this.getSeriesGroupingOptions(series), series, seriesDefaults);
|
|
});
|
|
options.series = this.setSeriesGroupingOptions(processedSeries ?? []);
|
|
}
|
|
/**
|
|
* Collates axis keys from the axis and series options to determine the full set of axis keys, defaults series to
|
|
* the primary axes and renames the primary axes to the internal direction-based names.
|
|
*/
|
|
processAxesOptions(options, chartType) {
|
|
const directions2 = chartType === "polar" ? ["angle" /* Angle */, "radius" /* Radius */] : ["x" /* X */, "y" /* Y */];
|
|
const hasAxes = "axes" in options && Object.keys(options.axes ?? {}).length > 0;
|
|
const nonDefaultSeriesAxisKeysCount = this.countNonDefaultSeriesAxisKeys(options, directions2);
|
|
const hasNonDefaultSeriesAxisKeys = nonDefaultSeriesAxisKeysCount > 0;
|
|
const hasExtraImplicitDefaultSeriesAxisKeys = hasNonDefaultSeriesAxisKeys && nonDefaultSeriesAxisKeysCount < (options?.series?.length ?? 0);
|
|
const primarySeriesOptions = options.series?.[0];
|
|
const seriesType = this.optionsType(options);
|
|
const defaultAxes = this.predictAxes(seriesType, directions2, primarySeriesOptions, options.data) ?? this.cloneDefaultAxes(seriesType);
|
|
const isPrimarySeriesFlipped = isObject(primarySeriesOptions) && "direction" in primarySeriesOptions && primarySeriesOptions.direction === "horizontal" && moduleRegistry_exports.getSeriesModule(primarySeriesOptions.type)?.axisKeysFlipped != null;
|
|
if (!hasAxes && !hasNonDefaultSeriesAxisKeys && !isPrimarySeriesFlipped) {
|
|
options.axes = defaultAxes;
|
|
return;
|
|
}
|
|
const axisKeys = "axes" in options ? new Set(Object.keys(options.axes ?? {})) : /* @__PURE__ */ new Set();
|
|
const primaryAxisKeys = this.getPrimaryAxisKeys(options, directions2, axisKeys, hasNonDefaultSeriesAxisKeys);
|
|
const remappedAxisKeys = this.getRemappedAxisKeys(
|
|
axisKeys,
|
|
primaryAxisKeys,
|
|
directions2,
|
|
hasExtraImplicitDefaultSeriesAxisKeys
|
|
);
|
|
const newAxes = {};
|
|
const unmappedAxisKeys = /* @__PURE__ */ new Map();
|
|
for (const [fromAxisKey, toAxisKey] of remappedAxisKeys) {
|
|
newAxes[toAxisKey] = "axes" in options ? shallowClone(options.axes?.[fromAxisKey]) : void 0;
|
|
unmappedAxisKeys.set(toAxisKey, fromAxisKey);
|
|
}
|
|
this.remapSeriesAxisKeys(
|
|
options,
|
|
directions2,
|
|
newAxes,
|
|
remappedAxisKeys,
|
|
defaultAxes,
|
|
hasExtraImplicitDefaultSeriesAxisKeys
|
|
);
|
|
this.predictAxesMissingTypesAndPositions(options, directions2, newAxes, defaultAxes);
|
|
this.alternateSecondaryAxisPositions(options, newAxes, unmappedAxisKeys);
|
|
options.axes = newAxes;
|
|
return unmappedAxisKeys;
|
|
}
|
|
/**
|
|
* These keys are used to map a series to an axis for each direction. They are retrieved per-series to allow a
|
|
* mixture of flipped and non-flipped series within the same chart.
|
|
*/
|
|
getSeriesDirectionAxisKey(seriesOptions, direction) {
|
|
const seriesModule = moduleRegistry_exports.getSeriesModule(seriesOptions.type);
|
|
if (!seriesModule)
|
|
return;
|
|
const isFlipped = "direction" in seriesOptions && seriesOptions.direction === "horizontal";
|
|
return isFlipped && seriesModule.axisKeysFlipped ? seriesModule.axisKeysFlipped[direction] : seriesModule.axisKeys?.[direction];
|
|
}
|
|
/**
|
|
* Check if any of the series' axis keys have values that do not match the expected value, i.e. the direction.
|
|
*/
|
|
countNonDefaultSeriesAxisKeys(options, directions2) {
|
|
let count = 0;
|
|
for (const seriesOptions of options.series ?? []) {
|
|
for (const direction of directions2) {
|
|
const directionAxisKey = this.getSeriesDirectionAxisKey(seriesOptions, direction);
|
|
if (!directionAxisKey || !isKeyOf(directionAxisKey, seriesOptions))
|
|
continue;
|
|
if (seriesOptions[directionAxisKey] === direction)
|
|
continue;
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
/**
|
|
* The primary axes are defined, for each direction, as those first in the `axes` object or referenced by a series.
|
|
* This is irregardless of the specific key of the axis, e.g. an axis with the key `y` may have the position `top`,
|
|
* and would therefore be classified as the primary `x` axis.
|
|
*/
|
|
getPrimaryAxisKeys(options, directions2, axisKeys, hasNonDefaultSeriesAxisKeys) {
|
|
const primaryAxisKeys = /* @__PURE__ */ new Map();
|
|
for (const direction of directions2) {
|
|
let foundPrimaryAxisKey = false;
|
|
if (
|
|
// has axes
|
|
"axes" in options && options.axes && // does not have standard-name primary axis without position
|
|
!(direction in options.axes && isObject(options.axes[direction]) && !("position" in options.axes[direction]))
|
|
) {
|
|
for (const [axisKey, axisOptions] of entries(options.axes)) {
|
|
if ("position" in axisOptions && axisOptions.position && direction === POSITION_DIRECTIONS[axisOptions.position]) {
|
|
primaryAxisKeys.set(direction, axisKey);
|
|
foundPrimaryAxisKey = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (foundPrimaryAxisKey)
|
|
continue;
|
|
if (!hasNonDefaultSeriesAxisKeys)
|
|
continue;
|
|
for (const seriesOptions of options.series ?? []) {
|
|
const directionAxisKey = this.getSeriesDirectionAxisKey(seriesOptions, direction);
|
|
if (!directionAxisKey)
|
|
continue;
|
|
const seriesAxisKey = seriesOptions[directionAxisKey];
|
|
if (axisKeys.has(seriesAxisKey))
|
|
continue;
|
|
if (!seriesAxisKey) {
|
|
primaryAxisKeys.set(direction, direction);
|
|
break;
|
|
}
|
|
primaryAxisKeys.set(direction, seriesAxisKey);
|
|
break;
|
|
}
|
|
}
|
|
if (axisKeys.size === 0 || !("axes" in options) || !options.axes)
|
|
return primaryAxisKeys;
|
|
if (primaryAxisKeys.size === 0) {
|
|
for (const direction of directions2) {
|
|
if (direction in options.axes) {
|
|
primaryAxisKeys.set(direction, direction);
|
|
}
|
|
}
|
|
}
|
|
if (primaryAxisKeys.size === 0) {
|
|
for (const direction of directions2) {
|
|
for (const [axisKey, axisOptions] of entries(options.axes)) {
|
|
if (axisOptions.type?.startsWith(direction)) {
|
|
primaryAxisKeys.set(direction, axisKey);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (primaryAxisKeys.size === 0 && (options.series?.length ?? 0) > 0) {
|
|
for (const direction of directions2) {
|
|
for (const seriesOptions of options.series) {
|
|
const directionAxisKey = this.getSeriesDirectionAxisKey(seriesOptions, direction);
|
|
if (!directionAxisKey)
|
|
continue;
|
|
const seriesAxisKey = seriesOptions[directionAxisKey];
|
|
if (!axisKeys.has(seriesAxisKey))
|
|
continue;
|
|
primaryAxisKeys.set(direction, seriesAxisKey);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (primaryAxisKeys.size < 2) {
|
|
const primaryAxisIdsFound = new Set(primaryAxisKeys.values());
|
|
for (const [axisKey, axisOptions] of entries(options.axes)) {
|
|
if (primaryAxisIdsFound.has(axisKey) || "position" in axisOptions)
|
|
continue;
|
|
for (const direction of directions2) {
|
|
if (primaryAxisKeys.has(direction))
|
|
continue;
|
|
primaryAxisKeys.set(direction, axisKey);
|
|
primaryAxisIdsFound.add(axisKey);
|
|
break;
|
|
}
|
|
if (primaryAxisKeys.size === 2)
|
|
break;
|
|
}
|
|
}
|
|
return primaryAxisKeys;
|
|
}
|
|
getRemappedAxisKeys(axisKeys, primaryAxisKeys, directions2, hasExtraImplicitDefaultSeriesAxisKeys) {
|
|
const remappedAxisKeys = /* @__PURE__ */ new Map();
|
|
for (const [direction, axisKey] of primaryAxisKeys) {
|
|
remappedAxisKeys.set(axisKey, direction);
|
|
}
|
|
for (const axisKey of axisKeys) {
|
|
if (remappedAxisKeys.has(axisKey))
|
|
continue;
|
|
remappedAxisKeys.set(axisKey, `${AXIS_ID_PREFIX}${remappedAxisKeys.size}`);
|
|
}
|
|
if (hasExtraImplicitDefaultSeriesAxisKeys) {
|
|
for (const direction of directions2) {
|
|
if (!remappedAxisKeys.has(direction)) {
|
|
remappedAxisKeys.set(direction, `${AXIS_ID_PREFIX}${remappedAxisKeys.size}`);
|
|
}
|
|
}
|
|
}
|
|
return remappedAxisKeys;
|
|
}
|
|
/**
|
|
* Update each series' axis keys to match the name used internally, such as the direction or a constant suffixed by
|
|
* the index for secondary axes.
|
|
*/
|
|
remapSeriesAxisKeys(options, directions2, newAxes, remappedAxisKeys, defaultAxes, hasExtraImplicitDefaultSeriesAxisKeys) {
|
|
for (const seriesOptions of options.series ?? []) {
|
|
for (const direction of directions2) {
|
|
const directionAxisKey = this.getSeriesDirectionAxisKey(seriesOptions, direction);
|
|
if (!directionAxisKey)
|
|
continue;
|
|
newAxes[direction] ?? (newAxes[direction] = shallowClone(defaultAxes[direction]));
|
|
let remappedSeriesAxisKey = direction;
|
|
if (directionAxisKey in seriesOptions) {
|
|
const seriesAxisKey = seriesOptions[directionAxisKey];
|
|
if (remappedAxisKeys.has(seriesAxisKey)) {
|
|
remappedSeriesAxisKey = remappedAxisKeys.get(seriesAxisKey);
|
|
} else {
|
|
remappedSeriesAxisKey = `${AXIS_ID_PREFIX}${remappedAxisKeys.size}`;
|
|
remappedAxisKeys.set(seriesAxisKey, remappedSeriesAxisKey);
|
|
newAxes[remappedSeriesAxisKey] = shallowClone(defaultAxes[direction]);
|
|
}
|
|
} else if (remappedAxisKeys.has(direction) && hasExtraImplicitDefaultSeriesAxisKeys) {
|
|
remappedSeriesAxisKey = remappedAxisKeys.get(direction);
|
|
newAxes[remappedSeriesAxisKey] ?? (newAxes[remappedSeriesAxisKey] = shallowClone(defaultAxes[direction]));
|
|
}
|
|
seriesOptions[directionAxisKey] = remappedSeriesAxisKey;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Attempt to predict the axes for each direction based on a subset of the data. Each series has its own prediction
|
|
* algorithm.
|
|
*/
|
|
predictAxes(seriesType, directions2, userSeriesOptions, data) {
|
|
if (!userSeriesOptions)
|
|
return;
|
|
const seriesData = userSeriesOptions?.data ?? data;
|
|
if (!seriesData?.length)
|
|
return;
|
|
const predictAxis = moduleRegistry_exports.getSeriesModule(seriesType)?.predictAxis;
|
|
if (!predictAxis)
|
|
return;
|
|
const axes = /* @__PURE__ */ new Map();
|
|
const indices = distribute(0, seriesData.length - 1, 5);
|
|
for (const index of indices) {
|
|
const datum = seriesData[index];
|
|
for (const direction of directions2) {
|
|
const axis = predictAxis(direction, datum, userSeriesOptions);
|
|
if (!axes.has(direction)) {
|
|
axes.set(direction, axis);
|
|
continue;
|
|
}
|
|
const prevAxis = axes.get(direction);
|
|
if (!axis && !prevAxis)
|
|
continue;
|
|
if (!axis || !prevAxis)
|
|
return;
|
|
for (const key of Object.keys(prevAxis)) {
|
|
if (prevAxis[key] !== axis[key])
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
for (const [direction, axis] of axes) {
|
|
if (!axis)
|
|
axes.delete(direction);
|
|
}
|
|
if (axes.size === 0)
|
|
return;
|
|
if (axes.size === 1) {
|
|
const [predictedAxis] = axes.values();
|
|
const defaultAxes = this.cloneDefaultAxes(seriesType);
|
|
if (!("position" in predictedAxis))
|
|
return;
|
|
return mapValues(defaultAxes, (axis) => {
|
|
if (!("position" in axis))
|
|
return axis;
|
|
return axis.position === predictedAxis.position ? predictedAxis : axis;
|
|
});
|
|
}
|
|
return Object.fromEntries(axes);
|
|
}
|
|
cloneDefaultAxes(seriesType) {
|
|
const seriesModule = moduleRegistry_exports.getSeriesModule(seriesType);
|
|
return seriesModule?.defaultAxes ? deepClone(seriesModule.defaultAxes) : {};
|
|
}
|
|
predictAxesMissingTypesAndPositions(options, directions2, newAxes, defaultAxes) {
|
|
for (const [key, axis] of entries(newAxes)) {
|
|
if (!isPlainObject(axis))
|
|
continue;
|
|
if ("type" in axis && "position" in axis)
|
|
continue;
|
|
if (key in defaultAxes) {
|
|
axis.type ?? (axis.type = defaultAxes[key].type);
|
|
axis.position ?? (axis.position = defaultAxes[key].position);
|
|
continue;
|
|
}
|
|
const predictedType = this.predictAxisMissingTypeFromPosition(axis, defaultAxes);
|
|
if (predictedType)
|
|
continue;
|
|
this.predictAxisMissingTypeAndPositionFromSeries(options, directions2, key, axis, defaultAxes);
|
|
if (!("type" in axis)) {
|
|
delete newAxes[key];
|
|
}
|
|
}
|
|
}
|
|
predictAxisMissingTypeFromPosition(axis, defaultAxes) {
|
|
if (!("position" in axis) || !isKeyOf(axis.position, POSITION_DIRECTIONS)) {
|
|
return false;
|
|
}
|
|
for (const defaultAxis of Object.values(defaultAxes)) {
|
|
if (isKeyOf(defaultAxis.position, POSITION_DIRECTIONS) && POSITION_DIRECTIONS[axis.position] === POSITION_DIRECTIONS[defaultAxis.position]) {
|
|
axis.type = defaultAxis.type;
|
|
return true;
|
|
}
|
|
}
|
|
for (const [position, positionDirection] of entries(POSITION_DIRECTIONS)) {
|
|
if (axis.position !== position && positionDirection === POSITION_DIRECTIONS[axis.position]) {
|
|
axis.type = defaultAxes[positionDirection].type;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
predictAxisMissingTypeAndPositionFromSeries(options, directions2, axisKey, axis, defaultAxes) {
|
|
for (const seriesOptions of options.series ?? []) {
|
|
for (const direction of directions2) {
|
|
const directionAxisKey = this.getSeriesDirectionAxisKey(seriesOptions, direction);
|
|
if (!directionAxisKey || !isKeyOf(directionAxisKey, seriesOptions))
|
|
continue;
|
|
if (seriesOptions[directionAxisKey] !== axisKey)
|
|
continue;
|
|
axis.type ?? (axis.type = defaultAxes[direction].type);
|
|
axis.position ?? (axis.position = defaultAxes[direction].position);
|
|
return direction === "y" /* Y */;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* If the first secondary axis in either direction does not have a specified position, it will be placed in the
|
|
* alternate position to the primary axis (i.e. right or top).
|
|
*/
|
|
alternateSecondaryAxisPositions(options, newAxes, unmappedAxisKeys) {
|
|
let xAxisCount = 0;
|
|
let yAxisCount = 0;
|
|
for (const [axisKey, axis] of entries(newAxes)) {
|
|
if (!isPlainObject(axis) || !("position" in axis))
|
|
continue;
|
|
const unmappedAxisKey = unmappedAxisKeys.get(axisKey);
|
|
const unmappedAxis = "axes" in options && options.axes && unmappedAxisKey && unmappedAxisKey in options.axes ? options.axes[unmappedAxisKey] : void 0;
|
|
const unmappedAxisPosition = unmappedAxis && "position" in unmappedAxis ? unmappedAxis.position : void 0;
|
|
if (axis.position === "top" || axis.position === "bottom") {
|
|
xAxisCount += 1;
|
|
if (xAxisCount === 2 && unmappedAxisPosition == null) {
|
|
axis.position = "top";
|
|
}
|
|
} else if (axis.position === "left" || axis.position === "right") {
|
|
yAxisCount += 1;
|
|
if (yAxisCount === 2 && unmappedAxisPosition == null) {
|
|
axis.position = "right";
|
|
}
|
|
}
|
|
if (xAxisCount > 1 && yAxisCount > 1)
|
|
break;
|
|
}
|
|
}
|
|
processMiniChartSeriesOptions(options) {
|
|
const miniChartSeries = options.navigator?.miniChart?.series;
|
|
if (miniChartSeries == null)
|
|
return;
|
|
options.navigator.miniChart.series = this.setSeriesGroupingOptions(
|
|
miniChartSeries
|
|
);
|
|
}
|
|
getSeriesGroupingOptions(series) {
|
|
const { groupable, stackable, stackedByDefault = false } = moduleRegistry_exports.getSeriesModule(series.type);
|
|
if (series.grouped && !groupable) {
|
|
logger_exports.warnOnce(`unsupported grouping of series type "${series.type}".`);
|
|
}
|
|
if ((series.stacked || series.stackGroup) && !stackable) {
|
|
logger_exports.warnOnce(`unsupported stacking of series type "${series.type}".`);
|
|
}
|
|
let { grouped, stacked } = series;
|
|
stacked ?? (stacked = (stackedByDefault || series.stackGroup != null) && !(groupable && grouped));
|
|
grouped ?? (grouped = true);
|
|
return {
|
|
stacked: stackable && stacked,
|
|
grouped: groupable && grouped && !(stackable && stacked)
|
|
};
|
|
}
|
|
setSeriesGroupingOptions(allSeries) {
|
|
const seriesGroups = this.getSeriesGrouping(allSeries);
|
|
_ChartOptions.debug("ChartOptions.setSeriesGroupingOptions() - series grouping: ", seriesGroups);
|
|
const groupIdx = {};
|
|
const groupCount2 = seriesGroups.reduce((countMap, seriesGroup) => {
|
|
var _a;
|
|
if (seriesGroup.groupType === "default" /* DEFAULT */) {
|
|
return countMap;
|
|
}
|
|
countMap[_a = seriesGroup.seriesType] ?? (countMap[_a] = 0);
|
|
countMap[seriesGroup.seriesType] += seriesGroup.groupType === "stack" /* STACK */ ? 1 : seriesGroup.series.length;
|
|
return countMap;
|
|
}, {});
|
|
return seriesGroups.flatMap((seriesGroup) => {
|
|
var _a;
|
|
groupIdx[_a = seriesGroup.seriesType] ?? (groupIdx[_a] = 0);
|
|
switch (seriesGroup.groupType) {
|
|
case "stack" /* STACK */: {
|
|
const groupIndex = groupIdx[seriesGroup.seriesType]++;
|
|
return seriesGroup.series.map(
|
|
(series, stackIndex) => Object.assign(series, {
|
|
seriesGrouping: {
|
|
groupId: seriesGroup.groupId,
|
|
groupIndex,
|
|
groupCount: groupCount2[seriesGroup.seriesType],
|
|
stackIndex,
|
|
stackCount: seriesGroup.series.length
|
|
}
|
|
})
|
|
);
|
|
}
|
|
case "group" /* GROUP */:
|
|
return seriesGroup.series.map(
|
|
(series) => Object.assign(series, {
|
|
seriesGrouping: {
|
|
groupId: seriesGroup.groupId,
|
|
groupIndex: groupIdx[seriesGroup.seriesType]++,
|
|
groupCount: groupCount2[seriesGroup.seriesType],
|
|
stackIndex: 0,
|
|
stackCount: 0
|
|
}
|
|
})
|
|
);
|
|
}
|
|
return seriesGroup.series;
|
|
}).map(({ stacked: _, grouped: __, ...seriesOptions }) => seriesOptions);
|
|
}
|
|
getSeriesGroupId(series) {
|
|
return [series.type, series.xKey, series.stacked ? series.stackGroup ?? "stacked" : "grouped"].filter(Boolean).join("-");
|
|
}
|
|
getSeriesGrouping(allSeries) {
|
|
const groupMap = /* @__PURE__ */ new Map();
|
|
return allSeries.reduce((result, series) => {
|
|
const seriesType = series.type;
|
|
if (!series.stacked && !series.grouped) {
|
|
result.push({ groupType: "default" /* DEFAULT */, seriesType, series: [series], groupId: "__default__" });
|
|
} else {
|
|
const groupId = this.getSeriesGroupId(series);
|
|
if (!groupMap.has(groupId)) {
|
|
const groupType = series.stacked ? "stack" /* STACK */ : "group" /* GROUP */;
|
|
const record2 = { groupType, seriesType, series: [], groupId };
|
|
groupMap.set(groupId, record2);
|
|
result.push(record2);
|
|
}
|
|
groupMap.get(groupId).series.push(series);
|
|
}
|
|
return result;
|
|
}, []);
|
|
}
|
|
soloSeriesIntegrity(options) {
|
|
if (!isArray(options.series))
|
|
return;
|
|
const isSolo = (seriesType) => moduleRegistry_exports.getSeriesModule(seriesType)?.solo ?? false;
|
|
const allSeries = options.series;
|
|
if (allSeries && allSeries.length > 1 && allSeries.some((series) => isSolo(series.type))) {
|
|
const mainSeriesType = this.optionsType(options);
|
|
if (isSolo(mainSeriesType)) {
|
|
logger_exports.warn(
|
|
`series[0] of type '${mainSeriesType}' is incompatible with other series types. Only processing series[0]`
|
|
);
|
|
options.series = allSeries.slice(0, 1);
|
|
} else {
|
|
const { solo, nonSolo } = groupBy(allSeries, (s) => isSolo(s.type) ? "solo" : "nonSolo");
|
|
const rejects = unique(solo.map((s) => s.type)).join(", ");
|
|
logger_exports.warn(`Unable to mix these series types with the lead series type: ${rejects}`);
|
|
options.series = nonSolo;
|
|
}
|
|
}
|
|
}
|
|
static processFontOptions(node, _, __, googleFonts = /* @__PURE__ */ new Set()) {
|
|
if (typeof node === "object" && "fontFamily" in node) {
|
|
if (Array.isArray(node.fontFamily)) {
|
|
const fontFamily = [];
|
|
for (const font3 of node.fontFamily) {
|
|
if (typeof font3 === "object" && "googleFont" in font3) {
|
|
fontFamily.push(font3.googleFont);
|
|
googleFonts?.add(font3.googleFont);
|
|
} else {
|
|
fontFamily.push(font3);
|
|
}
|
|
}
|
|
node.fontFamily = fontFamily.join(", ");
|
|
} else if (typeof node.fontFamily === "object" && "googleFont" in node.fontFamily) {
|
|
node.fontFamily = node.fontFamily.googleFont;
|
|
googleFonts?.add(node.fontFamily);
|
|
}
|
|
}
|
|
return googleFonts;
|
|
}
|
|
processFonts(options, googleFonts = /* @__PURE__ */ new Set()) {
|
|
return jsonWalk(
|
|
options,
|
|
_ChartOptions.processFontOptions,
|
|
/* @__PURE__ */ new Set(["data", "theme"]),
|
|
void 0,
|
|
void 0,
|
|
googleFonts
|
|
);
|
|
}
|
|
static removeDisabledOptionJson(optionsNode) {
|
|
if ("enabled" in optionsNode && optionsNode.enabled === false) {
|
|
for (const key of Object.keys(optionsNode)) {
|
|
if (key === "enabled")
|
|
continue;
|
|
delete optionsNode[key];
|
|
}
|
|
}
|
|
}
|
|
removeDisabledOptions(options) {
|
|
jsonWalk(options, _ChartOptions.removeDisabledOptionJson, /* @__PURE__ */ new Set(["data", "theme", "contextMenu"]));
|
|
}
|
|
static removeLeftoverSymbolsJson(optionsNode) {
|
|
if (!optionsNode || !isObject(optionsNode))
|
|
return;
|
|
for (const key of Object.keys(optionsNode)) {
|
|
const value = optionsNode[key];
|
|
if (isSymbol(value)) {
|
|
delete optionsNode[key];
|
|
}
|
|
}
|
|
}
|
|
removeLeftoverSymbols(options) {
|
|
jsonWalk(options, _ChartOptions.removeLeftoverSymbolsJson, /* @__PURE__ */ new Set(["data"]));
|
|
}
|
|
specialOverridesDefaults(options) {
|
|
if (options.window == null) {
|
|
options.window = getWindow();
|
|
} else {
|
|
setWindow(options.window);
|
|
}
|
|
if (options.document == null) {
|
|
options.document = getDocument();
|
|
} else {
|
|
setDocument(options.document);
|
|
}
|
|
if (options.window == null) {
|
|
throw new Error("AG Charts - unable to resolve global window");
|
|
}
|
|
if (options.document == null) {
|
|
throw new Error("AG Charts - unable to resolve global document");
|
|
}
|
|
return options;
|
|
}
|
|
};
|
|
_ChartOptions.OPTIONS_CLONE_OPTS_SLOW = {
|
|
shallow: /* @__PURE__ */ new Set(["data", "container"]),
|
|
assign: /* @__PURE__ */ new Set(["context", "theme"])
|
|
};
|
|
_ChartOptions.OPTIONS_CLONE_OPTS_FAST = {
|
|
shallow: /* @__PURE__ */ new Set(["container"]),
|
|
assign: /* @__PURE__ */ new Set(["data", "context", "theme"])
|
|
};
|
|
_ChartOptions.JSON_DIFF_OPTS = /* @__PURE__ */ new Set(["data", "localeText"]);
|
|
_ChartOptions.perfDebug = debugLogger_exports.create(true, "perf");
|
|
_ChartOptions.FAST_PATH_OPTIONS = /* @__PURE__ */ new Set(["data", "width", "height", "container"]);
|
|
// AG-16389: Track keys the user passed in deltaOptions
|
|
_ChartOptions.debug = debugLogger_exports.create(true, "opts");
|
|
var ChartOptions = _ChartOptions;
|
|
|
|
// packages/ag-charts-community/src/chart/chartProxy.ts
|
|
var debug3 = debugLogger_exports.create(true, "opts");
|
|
var DESTROYED_ERROR = "AG Charts - Chart was destroyed, cannot perform request.";
|
|
var _AgChartInstanceProxy = class _AgChartInstanceProxy {
|
|
constructor(chart, factoryApi, licenseManager) {
|
|
this.factoryApi = factoryApi;
|
|
this.licenseManager = licenseManager;
|
|
this.chart = chart;
|
|
}
|
|
async update(options) {
|
|
if (!this.chart)
|
|
throw new Error(DESTROYED_ERROR);
|
|
return debug3.group("AgChartInstance.update()", async () => {
|
|
const apiStartTime = debugLogger_exports.check("scene:stats", "scene:stats:verbose") ? performance.now() : void 0;
|
|
this.factoryApi.update(options, this, void 0, apiStartTime);
|
|
await this.chart?.waitForUpdate();
|
|
});
|
|
}
|
|
async updateDelta(deltaOptions) {
|
|
if (!this.chart)
|
|
throw new Error(DESTROYED_ERROR);
|
|
return debug3.group("AgChartInstance.updateDelta()", async () => {
|
|
const apiStartTime = debugLogger_exports.check("scene:stats", "scene:stats:verbose") ? performance.now() : void 0;
|
|
this.factoryApi.updateUserDelta(this, deltaOptions, apiStartTime);
|
|
await this.chart?.waitForUpdate();
|
|
});
|
|
}
|
|
getOptions() {
|
|
if (!this.chart)
|
|
throw new Error(DESTROYED_ERROR);
|
|
const options = deepClone(this.chart.getOptions(), ChartOptions.OPTIONS_CLONE_OPTS_FAST);
|
|
for (const key of Object.keys(options)) {
|
|
if (key.startsWith("_")) {
|
|
delete options[key];
|
|
}
|
|
}
|
|
return options;
|
|
}
|
|
waitForUpdate() {
|
|
if (!this.chart)
|
|
throw new Error(DESTROYED_ERROR);
|
|
return this.chart.waitForUpdate();
|
|
}
|
|
applyTransaction(transaction) {
|
|
const { chart } = this;
|
|
if (!chart)
|
|
throw new Error(DESTROYED_ERROR);
|
|
if (transaction == null || typeof transaction !== "object") {
|
|
throw new Error("AG Charts - applyTransaction expects a transaction object.");
|
|
}
|
|
const { add: add2, addIndex, remove, update } = transaction;
|
|
if (add2 != null && !Array.isArray(add2)) {
|
|
throw new Error('AG Charts - transaction "add" must be an array.');
|
|
}
|
|
if (addIndex != null) {
|
|
if (typeof addIndex !== "number" || !Number.isSafeInteger(addIndex) || addIndex < 0) {
|
|
throw new Error(
|
|
'AG Charts - transaction "addIndex" must be a safe non-negative integer (0 to 9007199254740991).'
|
|
);
|
|
}
|
|
if (add2 == null || add2.length === 0) {
|
|
throw new Error('AG Charts - transaction "addIndex" requires a non-empty "add" array.');
|
|
}
|
|
}
|
|
if (remove != null && !Array.isArray(remove)) {
|
|
throw new Error('AG Charts - transaction "remove" must be an array.');
|
|
}
|
|
if (update != null && !Array.isArray(update)) {
|
|
throw new Error('AG Charts - transaction "update" must be an array.');
|
|
}
|
|
return debug3.group("AgChartInstance.applyTransaction()", async () => {
|
|
if (!chart.isDataTransactionSupported()) {
|
|
const dataSet = chart.data.deepClone();
|
|
dataSet.addTransaction(transaction);
|
|
dataSet.commitPendingTransactions();
|
|
return this.updateDelta({ data: dataSet.data });
|
|
}
|
|
debug3("transaction", transaction);
|
|
return await this.chart?.applyTransaction(transaction);
|
|
});
|
|
}
|
|
async download(opts) {
|
|
if (!this.chart)
|
|
throw new Error(DESTROYED_ERROR);
|
|
const clone2 = await this.prepareResizedChart(this, this.chart, opts);
|
|
try {
|
|
clone2.chart?.download(opts?.fileName, opts?.fileFormat);
|
|
} finally {
|
|
clone2.destroy();
|
|
}
|
|
}
|
|
async __toSVG(opts) {
|
|
if (!this.chart)
|
|
throw new Error(DESTROYED_ERROR);
|
|
const clone2 = await this.prepareResizedChart(this, this.chart, { width: 600, height: 300, ...opts });
|
|
try {
|
|
return clone2?.chart?.toSVG();
|
|
} finally {
|
|
clone2?.destroy();
|
|
}
|
|
}
|
|
async getImageDataURL(opts) {
|
|
if (!this.chart)
|
|
throw new Error(DESTROYED_ERROR);
|
|
const clone2 = await this.prepareResizedChart(this, this.chart, opts);
|
|
try {
|
|
return clone2.chart.getCanvasDataURL(opts?.fileFormat);
|
|
} finally {
|
|
clone2.destroy();
|
|
}
|
|
}
|
|
getState() {
|
|
return this.factoryApi.caretaker.save(...this.getEnabledOriginators());
|
|
}
|
|
async setState(state) {
|
|
const { chart } = this;
|
|
if (!chart)
|
|
return;
|
|
const originators = this.getEnabledOriginators();
|
|
if (!originators.includes(chart.ctx.legendManager)) {
|
|
await this.setStateOriginators(state, originators);
|
|
return;
|
|
}
|
|
await this.setStateOriginators(
|
|
state,
|
|
originators.filter((originator) => originator !== chart.ctx.zoomManager)
|
|
);
|
|
await this.setStateOriginators(state, [chart.ctx.zoomManager]);
|
|
}
|
|
resetAnimations() {
|
|
this.chart?.resetAnimations();
|
|
}
|
|
skipAnimations() {
|
|
this.chart?.skipAnimations();
|
|
}
|
|
destroy() {
|
|
if (this.releaseChart) {
|
|
this.releaseChart();
|
|
this.releaseChart = void 0;
|
|
} else if (this.chart) {
|
|
this.chart.publicApi = void 0;
|
|
this.chart.destroy();
|
|
}
|
|
this.chart = void 0;
|
|
}
|
|
async prepareResizedChart(proxy, chart, opts = {}) {
|
|
const width2 = opts.width ?? chart.width ?? chart.ctx.scene.canvas.width;
|
|
const height2 = opts.height ?? chart.height ?? chart.ctx.scene.canvas.height;
|
|
const state = proxy.getState();
|
|
const processedOverrides = {
|
|
...chart.chartOptions.processedOverrides,
|
|
container: getDocument().createElement("div"),
|
|
width: width2,
|
|
height: height2
|
|
};
|
|
if (opts.width != null && opts.height != null) {
|
|
processedOverrides.overrideDevicePixelRatio = 1;
|
|
}
|
|
const userOptions = chart.getOptions();
|
|
if (moduleRegistry_exports.isEnterprise()) {
|
|
processedOverrides.animation = { enabled: false };
|
|
const foreground = this.licenseManager?.getWatermarkForegroundConfigForBrowser();
|
|
if (foreground) {
|
|
processedOverrides.foreground = foreground;
|
|
}
|
|
}
|
|
const specialOverrides = { ...chart.chartOptions.specialOverrides };
|
|
const optionsMetadata = { ...chart.chartOptions.optionMetadata };
|
|
const data = await this.chart?.ctx.dataService.getData();
|
|
const cloneProxy = this.factoryApi.create(
|
|
userOptions,
|
|
processedOverrides,
|
|
specialOverrides,
|
|
optionsMetadata,
|
|
data
|
|
);
|
|
if (state.legend) {
|
|
this.syncLegend(chart, cloneProxy, state);
|
|
}
|
|
cloneProxy.chart?.update(0 /* FULL */, { forceNodeDataRefresh: true });
|
|
await cloneProxy.waitForUpdate();
|
|
await cloneProxy.setState(state);
|
|
const sourcing = { source: "chart-update", sourceDetail: "internal-prepareResizedChart" };
|
|
cloneProxy.chart?.ctx.zoomManager.updateZoom(sourcing, chart.ctx.zoomManager.getZoom());
|
|
cloneProxy.chart?.update(0 /* FULL */, { forceNodeDataRefresh: true });
|
|
await cloneProxy.waitForUpdate();
|
|
const legendPages = [];
|
|
for (const legend of chart.modulesManager.legends()) {
|
|
legendPages.push(legend.legend.pagination?.currentPage ?? 0);
|
|
}
|
|
for (const legend of cloneProxy.chart.modulesManager.legends()) {
|
|
const page = legendPages.shift() ?? 0;
|
|
if (!legend.legend.pagination)
|
|
continue;
|
|
legend.legend.pagination.setPage(page);
|
|
}
|
|
cloneProxy.chart?.update(0 /* FULL */, { forceNodeDataRefresh: true });
|
|
await cloneProxy.waitForUpdate();
|
|
return cloneProxy;
|
|
}
|
|
syncLegend(chart, cloneProxy, state) {
|
|
const seriesIdMap = /* @__PURE__ */ new Map();
|
|
for (const [index, series] of chart.series.entries()) {
|
|
const cloneSeries = cloneProxy.chart?.series[index];
|
|
if (!cloneSeries)
|
|
continue;
|
|
seriesIdMap.set(series.id, cloneSeries.id);
|
|
}
|
|
state.legend = state.legend?.map((legend) => {
|
|
return {
|
|
...legend,
|
|
seriesId: seriesIdMap.get(legend.seriesId ?? "") ?? legend.seriesId
|
|
};
|
|
});
|
|
}
|
|
getEnabledOriginators() {
|
|
if (!this.chart)
|
|
return [];
|
|
const {
|
|
chartOptions: { processedOptions, optionMetadata },
|
|
ctx: { annotationManager, chartTypeOriginator, zoomManager, legendManager },
|
|
modulesManager
|
|
} = this.chart;
|
|
const originators = [];
|
|
if ("annotations" in processedOptions && processedOptions.annotations?.enabled) {
|
|
originators.push(annotationManager);
|
|
}
|
|
const isFinancialChart = optionMetadata.presetType === "price-volume";
|
|
if (isFinancialChart) {
|
|
originators.push(chartTypeOriginator);
|
|
}
|
|
if (processedOptions.navigator?.enabled || processedOptions.zoom?.enabled) {
|
|
originators.push(zoomManager);
|
|
}
|
|
const legendEnabled = modulesManager.isEnabled("legend") && processedOptions.legend?.enabled !== false;
|
|
if (legendEnabled) {
|
|
originators.push(legendManager);
|
|
}
|
|
originators.push(this.chart.ctx.activeManager);
|
|
return originators;
|
|
}
|
|
async setStateOriginators(state, originators) {
|
|
this.factoryApi.caretaker.restore(state, ...originators);
|
|
this.chart?.ctx.updateService.update(2 /* PROCESS_DATA */, { forceNodeDataRefresh: true });
|
|
await this.chart?.waitForUpdate();
|
|
}
|
|
};
|
|
_AgChartInstanceProxy.chartInstances = /* @__PURE__ */ new WeakMap();
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
oldValue(chart) {
|
|
if (!chart.destroyed) {
|
|
chart.publicApi = void 0;
|
|
}
|
|
_AgChartInstanceProxy.chartInstances.delete(chart);
|
|
},
|
|
newValue(chart) {
|
|
if (!chart)
|
|
return;
|
|
chart.publicApi = this;
|
|
_AgChartInstanceProxy.chartInstances.set(chart, this);
|
|
}
|
|
})
|
|
], _AgChartInstanceProxy.prototype, "chart", 2);
|
|
var AgChartInstanceProxy = _AgChartInstanceProxy;
|
|
|
|
// packages/ag-charts-community/src/util/pool.ts
|
|
var CLEANUP_TIMEOUT_MS = 1e3;
|
|
var _Pool = class _Pool {
|
|
constructor(name, buildItem, releaseItem, destroyItem, maxPoolSize, cleanupTimeMs = CLEANUP_TIMEOUT_MS) {
|
|
this.name = name;
|
|
this.buildItem = buildItem;
|
|
this.releaseItem = releaseItem;
|
|
this.destroyItem = destroyItem;
|
|
this.maxPoolSize = maxPoolSize;
|
|
this.cleanupTimeMs = cleanupTimeMs;
|
|
this.freePool = [];
|
|
this.busyPool = /* @__PURE__ */ new Set();
|
|
}
|
|
static getPool(name, buildItem, releaseItem, destroyItem, maxPoolSize) {
|
|
if (!this.pools.has(name)) {
|
|
this.pools.set(name, new _Pool(name, buildItem, releaseItem, destroyItem, maxPoolSize));
|
|
}
|
|
return this.pools.get(name);
|
|
}
|
|
isFull() {
|
|
return this.freePool.length + this.busyPool.size >= this.maxPoolSize;
|
|
}
|
|
hasFree() {
|
|
return this.freePool.length > 0;
|
|
}
|
|
obtain(params) {
|
|
if (!this.hasFree() && this.isFull()) {
|
|
throw new Error("AG Charts - pool exhausted");
|
|
}
|
|
let nextFree = this.freePool.pop();
|
|
if (nextFree == null) {
|
|
nextFree = this.buildItem(params);
|
|
_Pool.debug(() => [
|
|
`Pool[name=${this.name}]: Created instance (${this.freePool.length} / ${this.busyPool.size + 1} / ${this.maxPoolSize})`,
|
|
nextFree
|
|
]);
|
|
} else {
|
|
_Pool.debug(() => [
|
|
`Pool[name=${this.name}]: Re-used instance (${this.freePool.length} / ${this.busyPool.size + 1} / ${this.maxPoolSize})`,
|
|
nextFree
|
|
]);
|
|
}
|
|
this.busyPool.add(nextFree);
|
|
return { item: nextFree, release: () => this.release(nextFree) };
|
|
}
|
|
obtainFree() {
|
|
const nextFree = this.freePool.pop();
|
|
if (nextFree == null) {
|
|
throw new Error("AG Charts - pool has no free instances");
|
|
}
|
|
_Pool.debug(() => [
|
|
`Pool[name=${this.name}]: Re-used instance (${this.freePool.length} / ${this.busyPool.size + 1} / ${this.maxPoolSize})`,
|
|
nextFree
|
|
]);
|
|
this.busyPool.add(nextFree);
|
|
return { item: nextFree, release: () => this.release(nextFree) };
|
|
}
|
|
release(item) {
|
|
if (!this.busyPool.has(item)) {
|
|
throw new Error("AG Charts - cannot free item from pool which is not tracked as busy.");
|
|
}
|
|
_Pool.debug(() => [
|
|
`Pool[name=${this.name}]: Releasing instance (${this.freePool.length} / ${this.busyPool.size} / ${this.maxPoolSize})`,
|
|
item
|
|
]);
|
|
this.releaseItem(item);
|
|
this.busyPool.delete(item);
|
|
this.freePool.push(item);
|
|
_Pool.debug(() => [
|
|
`Pool[name=${this.name}]: Returned instance to free pool (${this.freePool.length} / ${this.busyPool.size} / ${this.maxPoolSize})`,
|
|
item
|
|
]);
|
|
const now = Date.now();
|
|
const earliestClean = now + this.cleanupTimeMs * 0.5;
|
|
if (this.cleanPoolTimer && (this.cleanPoolDue ?? Infinity) < earliestClean) {
|
|
clearTimeout(this.cleanPoolTimer);
|
|
this.cleanPoolTimer = void 0;
|
|
}
|
|
if (!this.cleanPoolTimer) {
|
|
this.cleanPoolDue = now + this.cleanupTimeMs;
|
|
this.cleanPoolTimer = setTimeout(this.cleanPool.bind(this), this.cleanupTimeMs);
|
|
}
|
|
}
|
|
cleanPool() {
|
|
const itemsToFree = this.freePool.splice(0);
|
|
for (const item of itemsToFree) {
|
|
this.destroyItem(item);
|
|
}
|
|
_Pool.debug(() => [
|
|
`Pool[name=${this.name}]: Cleaned pool of ${itemsToFree.length} items (${this.freePool.length} / ${this.busyPool.size} / ${this.maxPoolSize})`
|
|
]);
|
|
}
|
|
destroy() {
|
|
this.cleanPool();
|
|
for (const item of this.busyPool.values()) {
|
|
this.destroyItem(item);
|
|
}
|
|
this.busyPool.clear();
|
|
}
|
|
};
|
|
_Pool.pools = /* @__PURE__ */ new Map();
|
|
_Pool.debug = debugLogger_exports.create(true, "pool");
|
|
var Pool = _Pool;
|
|
|
|
// packages/ag-charts-community/src/api/agCharts.ts
|
|
var debug4 = debugLogger_exports.create(true, "opts");
|
|
var AgCharts = class {
|
|
static licenseCheck(options) {
|
|
if (this.licenseChecked)
|
|
return;
|
|
this.licenseManager = enterpriseRegistry.licenseManager?.(options);
|
|
this.licenseManager?.validateLicense();
|
|
this.licenseChecked = true;
|
|
}
|
|
static getLicenseDetails(licenseKey) {
|
|
return enterpriseRegistry.licenseManager?.({}).getLicenseDetails(licenseKey);
|
|
}
|
|
/**
|
|
* Returns the `AgChartInstance` for a DOM node, if there is one.
|
|
*/
|
|
static getInstance(element2) {
|
|
return AgChartsInternal.getInstance(element2);
|
|
}
|
|
/**
|
|
* Create a new `AgChartInstance` based upon the given configuration options.
|
|
*/
|
|
static create(userOptions, optionsMetadata) {
|
|
const apiStartTime = debugLogger_exports.check("scene:stats", "scene:stats:verbose") ? performance.now() : void 0;
|
|
return debug4.group("AgCharts.create()", () => {
|
|
userOptions = debugLogger_exports.inDevelopmentMode(() => deepFreeze(deepClone(userOptions))) ?? userOptions;
|
|
this.licenseCheck(userOptions);
|
|
const chart = AgChartsInternal.createOrUpdate({
|
|
userOptions,
|
|
licenseManager: this.licenseManager,
|
|
optionsMetadata,
|
|
apiStartTime
|
|
});
|
|
if (this.licenseManager?.isDisplayWatermark()) {
|
|
enterpriseRegistry.injectWatermark?.(
|
|
chart.chart.ctx.domManager,
|
|
this.licenseManager.getWatermarkMessage()
|
|
);
|
|
}
|
|
return chart;
|
|
});
|
|
}
|
|
static createFinancialChart(options) {
|
|
return debug4.group("AgCharts.createFinancialChart()", () => {
|
|
return this.create(options, { presetType: "price-volume" });
|
|
});
|
|
}
|
|
static createGauge(options) {
|
|
return debug4.group("AgCharts.createGauge()", () => {
|
|
return this.create(options, { presetType: "gauge-preset" });
|
|
});
|
|
}
|
|
static __createSparkline(options) {
|
|
return debug4.group("AgCharts.__createSparkline()", () => {
|
|
const { pool, ...normalOptions } = options;
|
|
return this.create(normalOptions, {
|
|
presetType: "sparkline",
|
|
pool: pool ?? true,
|
|
domMode: "minimal",
|
|
withDragInterpretation: false
|
|
});
|
|
});
|
|
}
|
|
};
|
|
AgCharts.licenseChecked = false;
|
|
var _AgChartsInternal = class _AgChartsInternal {
|
|
static getInstance(element2) {
|
|
const chart = Chart.getInstance(element2);
|
|
return chart ? AgChartInstanceProxy.chartInstances.get(chart) : void 0;
|
|
}
|
|
static createOrUpdate(opts) {
|
|
let { proxy } = opts;
|
|
const {
|
|
userOptions,
|
|
licenseManager,
|
|
processedOverrides = proxy?.chart?.chartOptions.processedOverrides ?? {},
|
|
specialOverrides = proxy?.chart?.chartOptions.specialOverrides ?? {},
|
|
optionsMetadata = proxy?.chart?.chartOptions.optionMetadata ?? {},
|
|
deltaOptions,
|
|
data,
|
|
stripSymbols = false,
|
|
apiStartTime
|
|
} = opts;
|
|
const styles = enterpriseRegistry.styles == null ? [] : [["ag-charts-enterprise", enterpriseRegistry.styles]];
|
|
if (moduleRegistry_exports.listModules().next().done) {
|
|
throw new Error(
|
|
[
|
|
"AG Charts - No modules have been registered.",
|
|
"",
|
|
"Call ModuleRegistry.registerModules(...) with the modules you need before using AgCharts.create().",
|
|
"",
|
|
"See https://www.ag-grid.com/charts/r/module-registry/ for more details."
|
|
].join("\n")
|
|
);
|
|
}
|
|
debug4(() => [">>> AgCharts.createOrUpdate() user options", deepClone(userOptions)]);
|
|
const { presetType } = optionsMetadata;
|
|
let mutableOptions = userOptions;
|
|
if (AgCharts.optionsMutationFn && mutableOptions) {
|
|
mutableOptions = AgCharts.optionsMutationFn(
|
|
deepClone(mutableOptions, ChartOptions.OPTIONS_CLONE_OPTS_FAST),
|
|
presetType
|
|
);
|
|
debug4(() => [">>> AgCharts.createOrUpdate() MUTATED user options", deepClone(mutableOptions)]);
|
|
}
|
|
const pool = this.getPool(optionsMetadata);
|
|
let create2 = false;
|
|
let poolResult;
|
|
let chart = proxy?.chart;
|
|
if (chart == null && pool?.hasFree()) {
|
|
poolResult = pool.obtainFree();
|
|
chart = poolResult.item;
|
|
}
|
|
const { document: document2, window: userWindow, styleContainer, skipCss, ...options } = mutableOptions ?? {};
|
|
const baseOptions = chart?.getChartOptions();
|
|
const chartOptions = new ChartOptions(
|
|
baseOptions,
|
|
options,
|
|
processedOverrides,
|
|
{
|
|
...specialOverrides,
|
|
document: document2,
|
|
window: userWindow,
|
|
styleContainer,
|
|
skipCss
|
|
},
|
|
optionsMetadata,
|
|
deltaOptions,
|
|
stripSymbols,
|
|
apiStartTime
|
|
);
|
|
if (chart == null || detectChartType(chartOptions.processedOptions) !== detectChartType(chart.chartOptions.processedOptions)) {
|
|
poolResult?.release();
|
|
poolResult = this.getPool(chartOptions.optionMetadata)?.obtain(chartOptions);
|
|
if (poolResult) {
|
|
chart = poolResult.item;
|
|
} else {
|
|
create2 = true;
|
|
chart = _AgChartsInternal.createChartInstance(chartOptions, chart);
|
|
}
|
|
}
|
|
if (chartOptions.optionsGraph) {
|
|
chart.ctx.optionsGraphService.updateCallback((path, partialOptions, resolveOptions) => {
|
|
return chartOptions.optionsGraph?.resolvePartial(path, partialOptions, resolveOptions);
|
|
});
|
|
}
|
|
for (const [id, css] of styles) {
|
|
chart.ctx.domManager.addStyles(id, css);
|
|
}
|
|
chart.ctx.fontManager.updateFonts(chartOptions.googleFonts);
|
|
if (data != null) {
|
|
chart.ctx.dataService.restoreData(data);
|
|
}
|
|
if (proxy == null) {
|
|
proxy = new AgChartInstanceProxy(chart, _AgChartsInternal.callbackApi, licenseManager);
|
|
proxy.releaseChart = poolResult?.release;
|
|
} else if (poolResult || create2) {
|
|
proxy.releaseChart?.();
|
|
proxy.chart = chart;
|
|
proxy.releaseChart = poolResult?.release;
|
|
}
|
|
if (debug4.check() && typeof globalThis.window !== "undefined") {
|
|
globalThis.agChartInstances ?? (globalThis.agChartInstances = {});
|
|
globalThis.agChartInstances[chart.id] = chart;
|
|
}
|
|
chart.queuedUserOptions.push(chartOptions.userOptions);
|
|
chart.queuedChartOptions.push(chartOptions);
|
|
chart.requestFactoryUpdate((chartRef) => {
|
|
debug4.group(">>>> Chart.applyOptions()", () => {
|
|
chartRef.applyOptions(chartOptions);
|
|
const queueIdx = chartRef.queuedUserOptions.indexOf(chartOptions.userOptions) + 1;
|
|
chartRef.queuedUserOptions.splice(0, queueIdx);
|
|
chartRef.queuedChartOptions.splice(0, queueIdx);
|
|
});
|
|
});
|
|
return proxy;
|
|
}
|
|
static updateUserDelta(proxy, deltaOptions, apiStartTime) {
|
|
deltaOptions = deepClone(deltaOptions, ChartOptions.OPTIONS_CLONE_OPTS_FAST);
|
|
const stripSymbols = jsonWalk(
|
|
deltaOptions,
|
|
_AgChartsInternal.markRemovedProperties,
|
|
/* @__PURE__ */ new Set(["data"]),
|
|
void 0,
|
|
void 0,
|
|
false
|
|
);
|
|
debug4(() => [">>> AgCharts.updateUserDelta() user delta", deepClone(deltaOptions)]);
|
|
_AgChartsInternal.createOrUpdate({
|
|
proxy,
|
|
deltaOptions,
|
|
stripSymbols,
|
|
apiStartTime
|
|
});
|
|
}
|
|
static createChartInstance(options, oldChart) {
|
|
const transferableResource = oldChart?.destroy({ keepTransferableResources: true });
|
|
const chartType = detectChartType(options.processedOptions);
|
|
const chartDef = moduleRegistry_exports.getChartModule(chartType);
|
|
return chartDef.create(options, transferableResource);
|
|
}
|
|
static getPool(optionMetadata) {
|
|
if (optionMetadata.pool !== true)
|
|
return;
|
|
return Pool.getPool(
|
|
optionMetadata.presetType ?? "default",
|
|
this.createChartInstance,
|
|
this.detachAndClear,
|
|
this.destroy,
|
|
Infinity
|
|
// AG-13480 - Prevent Grid exhausting pool during sorting.
|
|
);
|
|
}
|
|
};
|
|
_AgChartsInternal.caretaker = new MementoCaretaker(VERSION);
|
|
_AgChartsInternal.callbackApi = {
|
|
caretaker: _AgChartsInternal.caretaker,
|
|
create(userOptions, processedOverrides, specialOverrides, optionsMetadata, data) {
|
|
return _AgChartsInternal.createOrUpdate({
|
|
userOptions,
|
|
processedOverrides,
|
|
specialOverrides,
|
|
optionsMetadata,
|
|
data
|
|
});
|
|
},
|
|
update(opts, chart, specialOverrides, apiStartTime) {
|
|
return _AgChartsInternal.createOrUpdate({
|
|
userOptions: opts,
|
|
proxy: chart,
|
|
specialOverrides,
|
|
apiStartTime
|
|
});
|
|
},
|
|
updateUserDelta(chart, deltaOptions, apiStartTime) {
|
|
return _AgChartsInternal.updateUserDelta(chart, deltaOptions, apiStartTime);
|
|
}
|
|
};
|
|
// CRT-1018 Use `Parameters` and `unknown` to strictly enforce type-safety
|
|
_AgChartsInternal.markRemovedProperties = (node, _parallelNode, _ctx, previousModified) => {
|
|
let modified = previousModified ?? false;
|
|
if (typeof node !== "object" || node == null)
|
|
return modified;
|
|
for (const key of strictObjectKeys(node)) {
|
|
const value = node[key];
|
|
if (value === void 0) {
|
|
Object.assign(node, { [key]: Symbol("UNSET") });
|
|
modified || (modified = true);
|
|
}
|
|
}
|
|
return modified;
|
|
};
|
|
_AgChartsInternal.detachAndClear = (chart) => chart.detachAndClear();
|
|
_AgChartsInternal.destroy = (chart) => chart.destroy();
|
|
var AgChartsInternal = _AgChartsInternal;
|
|
|
|
// packages/ag-charts-community/src/module-support.ts
|
|
var module_support_exports = {};
|
|
__export(module_support_exports, {
|
|
APPROXIMATE_THRESHOLD: () => APPROXIMATE_THRESHOLD,
|
|
AbstractBarSeries: () => AbstractBarSeries,
|
|
AbstractBarSeriesProperties: () => AbstractBarSeriesProperties,
|
|
AggregationManager: () => AggregationManager,
|
|
AnchoredPopover: () => AnchoredPopover,
|
|
ApproximateOrdinalTimeScale: () => ApproximateOrdinalTimeScale,
|
|
Arc: () => Arc,
|
|
Axis: () => Axis,
|
|
AxisGroupZIndexMap: () => AxisGroupZIndexMap,
|
|
AxisInterval: () => AxisInterval,
|
|
AxisLabel: () => AxisLabel,
|
|
AxisTick: () => AxisTick,
|
|
BBox: () => BBox,
|
|
Background: () => Background,
|
|
BandScale: () => BandScale,
|
|
BaseToolbar: () => BaseToolbar,
|
|
Caption: () => Caption,
|
|
CartesianAxis: () => CartesianAxis,
|
|
CartesianCrossLine: () => CartesianCrossLine,
|
|
CartesianSeries: () => CartesianSeries,
|
|
CartesianSeriesNodeEvent: () => CartesianSeriesNodeEvent,
|
|
CartesianSeriesProperties: () => CartesianSeriesProperties,
|
|
CategoryAxis: () => CategoryAxis,
|
|
CategoryScale: () => CategoryScale,
|
|
Chart: () => Chart,
|
|
ChartAxes: () => ChartAxes,
|
|
ChartOptions: () => ChartOptions,
|
|
CollapseMode: () => CollapseMode,
|
|
ColorScale: () => ColorScale,
|
|
ContextMenuRegistry: () => ContextMenuRegistry,
|
|
ContinuousScale: () => ContinuousScale,
|
|
DEFAULT_CARTESIAN_DIRECTION_KEYS: () => DEFAULT_CARTESIAN_DIRECTION_KEYS,
|
|
DEFAULT_CARTESIAN_DIRECTION_NAMES: () => DEFAULT_CARTESIAN_DIRECTION_NAMES,
|
|
DEFAULT_POLAR_DIRECTION_KEYS: () => DEFAULT_POLAR_DIRECTION_KEYS,
|
|
DEFAULT_POLAR_DIRECTION_NAMES: () => DEFAULT_POLAR_DIRECTION_NAMES,
|
|
DOMManager: () => DOMManager,
|
|
DataController: () => DataController,
|
|
DataModel: () => DataModel,
|
|
DataModelSeries: () => DataModelSeries,
|
|
DataSet: () => DataSet,
|
|
DiscreteTimeAxis: () => DiscreteTimeAxis,
|
|
DraggablePopover: () => DraggablePopover,
|
|
DropShadow: () => DropShadow,
|
|
ExtendedPath2D: () => ExtendedPath2D,
|
|
FillGradientDefaults: () => FillGradientDefaults,
|
|
FillImageDefaults: () => FillImageDefaults,
|
|
FillPatternDefaults: () => FillPatternDefaults,
|
|
FloatingToolbar: () => FloatingToolbar,
|
|
FormatManager: () => FormatManager,
|
|
Group: () => Group,
|
|
GroupWidget: () => GroupWidget,
|
|
HierarchyHighlightState: () => HierarchyHighlightState,
|
|
HierarchyNode: () => HierarchyNode,
|
|
HierarchySeries: () => HierarchySeries,
|
|
HierarchySeriesProperties: () => HierarchySeriesProperties,
|
|
HighlightManager: () => HighlightManager,
|
|
HighlightProperties: () => HighlightProperties,
|
|
HighlightState: () => HighlightState,
|
|
Image: () => Image2,
|
|
InteractionManager: () => InteractionManager,
|
|
InteractionState: () => InteractionState,
|
|
LARGEST_KEY_INTERVAL: () => LARGEST_KEY_INTERVAL,
|
|
Label: () => Label,
|
|
LabelStyle: () => LabelStyle,
|
|
LayoutElement: () => LayoutElement,
|
|
Line: () => Line,
|
|
LinearScale: () => LinearScale,
|
|
LogScale: () => LogScale,
|
|
Marker: () => Marker,
|
|
Menu: () => Menu,
|
|
MercatorScale: () => MercatorScale,
|
|
NODE_UPDATE_STATE_TO_PHASE_MAPPING: () => NODE_UPDATE_STATE_TO_PHASE_MAPPING,
|
|
NativeWidget: () => NativeWidget,
|
|
NiceMode: () => NiceMode,
|
|
Node: () => Node2,
|
|
OrdinalTimeScale: () => OrdinalTimeScale,
|
|
Path: () => Path,
|
|
PointerEvents: () => PointerEvents,
|
|
PolarAxis: () => PolarAxis,
|
|
PolarSeries: () => PolarSeries,
|
|
QuadtreeNearest: () => QuadtreeNearest,
|
|
RadialColumnShape: () => RadialColumnShape,
|
|
Range: () => Range,
|
|
Rect: () => Rect,
|
|
Rotatable: () => Rotatable,
|
|
RotatableText: () => RotatableText,
|
|
SMALLEST_KEY_INTERVAL: () => SMALLEST_KEY_INTERVAL,
|
|
Scalable: () => Scalable,
|
|
ScalableGroup: () => ScalableGroup,
|
|
Scene: () => Scene,
|
|
Sector: () => Sector,
|
|
SectorBox: () => SectorBox,
|
|
SegmentedPath: () => SegmentedPath,
|
|
Selection: () => Selection,
|
|
Series: () => Series,
|
|
SeriesItemHighlightStyle: () => SeriesItemHighlightStyle,
|
|
SeriesMarker: () => SeriesMarker,
|
|
SeriesNodeEvent: () => SeriesNodeEvent,
|
|
SeriesNodePickMode: () => SeriesNodePickMode,
|
|
SeriesProperties: () => SeriesProperties,
|
|
SeriesTooltip: () => SeriesTooltip,
|
|
Shape: () => Shape,
|
|
SliderWidget: () => SliderWidget,
|
|
StopProperties: () => StopProperties,
|
|
SvgPath: () => SvgPath,
|
|
Text: () => Text,
|
|
TimeAxisParentLevel: () => TimeAxisParentLevel,
|
|
TimeScale: () => TimeScale,
|
|
Toolbar: () => Toolbar,
|
|
ToolbarButtonWidget: () => ToolbarButtonWidget,
|
|
ToolbarWidget: () => ToolbarWidget,
|
|
TooltipManager: () => TooltipManager,
|
|
Transformable: () => Transformable,
|
|
TransformableGroup: () => TransformableGroup,
|
|
TransformableText: () => TransformableText,
|
|
Translatable: () => Translatable,
|
|
TranslatableGroup: () => TranslatableGroup,
|
|
TranslatableSvgPath: () => TranslatableSvgPath,
|
|
UnitTimeScale: () => UnitTimeScale,
|
|
ZoomManager: () => ZoomManager,
|
|
accumulativeValueProperty: () => accumulativeValueProperty,
|
|
addHitTestersToQuadtree: () => addHitTestersToQuadtree,
|
|
adjustLabelPlacement: () => adjustLabelPlacement,
|
|
angleCategoryAxisOptionsDefs: () => angleCategoryAxisOptionsDefs,
|
|
angleNumberAxisOptionsDefs: () => angleNumberAxisOptionsDefs,
|
|
animationValidation: () => animationValidation,
|
|
annotationCalloutStylesDefs: () => annotationCalloutStylesDefs,
|
|
annotationChannelTextDefs: () => annotationChannelTextDefs,
|
|
annotationCommentStylesDefs: () => annotationCommentStylesDefs,
|
|
annotationCrossLineStyleDefs: () => annotationCrossLineStyleDefs,
|
|
annotationDisjointChannelStyleDefs: () => annotationDisjointChannelStyleDefs,
|
|
annotationFibonacciStylesDefs: () => annotationFibonacciStylesDefs,
|
|
annotationLineStyleDefs: () => annotationLineStyleDefs,
|
|
annotationLineTextDefs: () => annotationLineTextDefs,
|
|
annotationMeasurerStylesDefs: () => annotationMeasurerStylesDefs,
|
|
annotationNoteStylesDefs: () => annotationNoteStylesDefs,
|
|
annotationOptionsDef: () => annotationOptionsDef,
|
|
annotationParallelChannelStyleDefs: () => annotationParallelChannelStyleDefs,
|
|
annotationQuickMeasurerStylesDefs: () => annotationQuickMeasurerStylesDefs,
|
|
annotationShapeStylesDefs: () => annotationShapeStylesDefs,
|
|
annotationTextStylesDef: () => annotationTextStylesDef,
|
|
boxPlotSeriesThemeableOptionsDef: () => boxPlotSeriesThemeableOptionsDef,
|
|
buildResetPathFn: () => buildResetPathFn,
|
|
calculateDataDiff: () => calculateDataDiff,
|
|
calculateLabelTranslation: () => calculateLabelTranslation,
|
|
calculateSegments: () => calculateSegments,
|
|
candlestickSeriesThemeableOptionsDef: () => candlestickSeriesThemeableOptionsDef,
|
|
checkCrisp: () => checkCrisp,
|
|
chordSeriesThemeableOptionsDef: () => chordSeriesThemeableOptionsDef,
|
|
clippedRoundRect: () => clippedRoundRect,
|
|
collapsedStartingBarPosition: () => collapsedStartingBarPosition,
|
|
computeBarFocusBounds: () => computeBarFocusBounds,
|
|
computeMarkerFocusBounds: () => computeMarkerFocusBounds,
|
|
coneFunnelSeriesThemeableOptionsDef: () => coneFunnelSeriesThemeableOptionsDef,
|
|
createDatumId: () => createDatumId,
|
|
diff: () => diff,
|
|
drawCorner: () => drawCorner,
|
|
drawMarkerUnitPolygon: () => drawMarkerUnitPolygon,
|
|
findNodeDatumInArray: () => findNodeDatumInArray,
|
|
findQuadtreeMatch: () => findQuadtreeMatch,
|
|
fixNumericExtent: () => fixNumericExtent,
|
|
fromToMotion: () => fromToMotion,
|
|
funnelSeriesThemeableOptionsDef: () => funnelSeriesThemeableOptionsDef,
|
|
generateTicks: () => generateTicks,
|
|
getColorStops: () => getColorStops,
|
|
getCrossLineValue: () => getCrossLineValue,
|
|
getItemStyles: () => getItemStyles,
|
|
getItemStylesPerItemId: () => getItemStylesPerItemId,
|
|
getLabelStyles: () => getLabelStyles,
|
|
getMarkerStyles: () => getMarkerStyles,
|
|
getMissCount: () => getMissCount,
|
|
getRadialColumnWidth: () => getRadialColumnWidth,
|
|
getShapeFill: () => getShapeFill,
|
|
getShapeStyle: () => getShapeStyle,
|
|
groupAccumulativeValueProperty: () => groupAccumulativeValueProperty,
|
|
hasDimmedOpacity: () => hasDimmedOpacity,
|
|
heatmapSeriesThemeableOptionsDef: () => heatmapSeriesThemeableOptionsDef,
|
|
initialStatePickedOptionsDef: () => initialStatePickedOptionsDef,
|
|
interpolatePoints: () => interpolatePoints,
|
|
isTooltipValueMissing: () => isTooltipValueMissing,
|
|
keyProperty: () => keyProperty,
|
|
makeSeriesTooltip: () => makeSeriesTooltip,
|
|
mapLineBackgroundSeriesThemeableOptionsDef: () => mapLineBackgroundSeriesThemeableOptionsDef,
|
|
mapLineSeriesThemeableOptionsDef: () => mapLineSeriesThemeableOptionsDef,
|
|
mapMarkerSeriesThemeableOptionsDef: () => mapMarkerSeriesThemeableOptionsDef,
|
|
mapShapeBackgroundSeriesThemeableOptionsDef: () => mapShapeBackgroundSeriesThemeableOptionsDef,
|
|
mapShapeSeriesThemeableOptionsDef: () => mapShapeSeriesThemeableOptionsDef,
|
|
markerEnabled: () => markerEnabled,
|
|
markerFadeInAnimation: () => markerFadeInAnimation,
|
|
markerSwipeScaleInAnimation: () => markerSwipeScaleInAnimation,
|
|
midpointStartingBarPosition: () => midpointStartingBarPosition,
|
|
minimumTimeAxisDatumGranularity: () => minimumTimeAxisDatumGranularity,
|
|
motion: () => motion,
|
|
nightingaleSeriesThemeableOptionsDef: () => nightingaleSeriesThemeableOptionsDef,
|
|
normaliseGroupTo: () => normaliseGroupTo,
|
|
ohlcSeriesThemeableOptionsDef: () => ohlcSeriesThemeableOptionsDef,
|
|
ordinalTimeAxisOptionsDefs: () => ordinalTimeAxisOptionsDefs,
|
|
pairUpSpans: () => pairUpSpans,
|
|
pathFadeInAnimation: () => pathFadeInAnimation,
|
|
pathMotion: () => pathMotion,
|
|
pathSwipeInAnimation: () => pathSwipeInAnimation,
|
|
plotAreaPathFill: () => plotAreaPathFill,
|
|
plotInterpolatedLinePathStroke: () => plotInterpolatedLinePathStroke,
|
|
plotLinePathStroke: () => plotLinePathStroke,
|
|
predictCartesianFinancialAxis: () => predictCartesianFinancialAxis,
|
|
predictCartesianNonPrimitiveAxis: () => predictCartesianNonPrimitiveAxis,
|
|
prepareAreaFillAnimationFns: () => prepareAreaFillAnimationFns,
|
|
prepareBarAnimationFunctions: () => prepareBarAnimationFunctions,
|
|
prepareLinePathPropertyAnimation: () => prepareLinePathPropertyAnimation,
|
|
processedDataIsAnimatable: () => processedDataIsAnimatable,
|
|
pyramidSeriesThemeableOptionsDef: () => pyramidSeriesThemeableOptionsDef,
|
|
radarAreaSeriesThemeableOptionsDef: () => radarAreaSeriesThemeableOptionsDef,
|
|
radarLineSeriesThemeableOptionsDef: () => radarLineSeriesThemeableOptionsDef,
|
|
radialBarSeriesThemeableOptionsDef: () => radialBarSeriesThemeableOptionsDef,
|
|
radialColumnSeriesThemeableOptionsDef: () => radialColumnSeriesThemeableOptionsDef,
|
|
radiusCategoryAxisOptionsDefs: () => radiusCategoryAxisOptionsDefs,
|
|
radiusNumberAxisOptionsDefs: () => radiusNumberAxisOptionsDefs,
|
|
rangeAreaSeriesThemeableOptionsDef: () => rangeAreaSeriesThemeableOptionsDef,
|
|
rangeBarSeriesThemeableOptionsDef: () => rangeBarSeriesThemeableOptionsDef,
|
|
resetAxisLabelSelectionFn: () => resetAxisLabelSelectionFn,
|
|
resetBarSelectionsDirect: () => resetBarSelectionsDirect,
|
|
resetBarSelectionsFn: () => resetBarSelectionsFn,
|
|
resetLabelFn: () => resetLabelFn,
|
|
resetMarkerFn: () => resetMarkerFn,
|
|
resetMarkerPositionFn: () => resetMarkerPositionFn,
|
|
resetMarkerSelectionsDirect: () => resetMarkerSelectionsDirect,
|
|
resetMotion: () => resetMotion,
|
|
sankeySeriesThemeableOptionsDef: () => sankeySeriesThemeableOptionsDef,
|
|
sectorBox: () => sectorBox,
|
|
seriesLabelFadeInAnimation: () => seriesLabelFadeInAnimation,
|
|
seriesLabelFadeOutAnimation: () => seriesLabelFadeOutAnimation,
|
|
stackCartesianSeries: () => stackCartesianSeries,
|
|
standaloneChartOptionsDefs: () => standaloneChartOptionsDefs,
|
|
sunburstSeriesThemeableOptionsDef: () => sunburstSeriesThemeableOptionsDef,
|
|
toHierarchyHighlightString: () => toHierarchyHighlightString,
|
|
toHighlightString: () => toHighlightString,
|
|
topologyChartOptionsDefs: () => topologyChartOptionsDefs,
|
|
trailingAccumulatedValueProperty: () => trailingAccumulatedValueProperty,
|
|
treemapSeriesThemeableOptionsDef: () => treemapSeriesThemeableOptionsDef,
|
|
updateClipPath: () => updateClipPath,
|
|
updateLabelNode: () => updateLabelNode,
|
|
upsertNodeDatum: () => upsertNodeDatum,
|
|
userInteraction: () => userInteraction,
|
|
validateCrossLineValue: () => validateCrossLineValue,
|
|
valueProperty: () => valueProperty,
|
|
visibleRangeIndices: () => visibleRangeIndices,
|
|
waterfallSeriesThemeableOptionsDef: () => waterfallSeriesThemeableOptionsDef
|
|
});
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/util.ts
|
|
function isAxisReversed(axis) {
|
|
return axis.isReversed() !== axis.range[1] < axis.range[0];
|
|
}
|
|
function calculateSegments(segmentation, xAxis, yAxis, seriesRect, chartSize, applyOffset = true) {
|
|
if (xAxis.scale.domain.length === 0 || yAxis.scale.domain.length === 0) {
|
|
return;
|
|
}
|
|
const axis = segmentation.key === "x" /* X */ ? xAxis : yAxis;
|
|
const { scale: scale2, direction } = axis;
|
|
const isXDirection = direction === "x" /* X */;
|
|
const bandwidth = scale2.bandwidth ?? 0;
|
|
const offset = applyOffset ? ((scale2.step ?? 0) - bandwidth) / 2 : 0;
|
|
const horizontalMargin = Math.max(seriesRect.x, chartSize.width - (seriesRect.x + seriesRect.width));
|
|
const verticalMargin = Math.max(seriesRect.y, chartSize.height - (seriesRect.y + seriesRect.height));
|
|
const getDefaultStart = () => {
|
|
if (isAxisReversed(isXDirection ? xAxis : yAxis)) {
|
|
return isXDirection ? seriesRect.width + horizontalMargin : seriesRect.height + verticalMargin;
|
|
}
|
|
return isXDirection ? -horizontalMargin : -verticalMargin;
|
|
};
|
|
const getDefaultStop = () => {
|
|
if (isAxisReversed(isXDirection ? xAxis : yAxis)) {
|
|
return isXDirection ? -horizontalMargin : -verticalMargin;
|
|
}
|
|
return isXDirection ? seriesRect.width + horizontalMargin : seriesRect.height + verticalMargin;
|
|
};
|
|
const getSegments = (segments) => {
|
|
const result = [];
|
|
let previousDefinedStopIndex = -1;
|
|
for (let i = 0; i < segments.length; i++) {
|
|
const segment = segments[i];
|
|
if (isEmptyObject(segment)) {
|
|
continue;
|
|
}
|
|
const { start: start2, stop, ...styles } = segments[i];
|
|
const startFallback = segments[previousDefinedStopIndex]?.stop;
|
|
const stopFallback = segments.slice(i + 1).find((s) => s.start != null)?.start;
|
|
let startPosition = scale2.convert(start2 ?? startFallback) - offset;
|
|
let stopPosition = scale2.convert(stop ?? stopFallback) + 2 * offset;
|
|
const invalidStart = start2 != null && Number.isNaN(startPosition);
|
|
const invalidStop = stop != null && Number.isNaN(stopPosition);
|
|
if (invalidStart || invalidStop) {
|
|
continue;
|
|
}
|
|
if (Number.isNaN(startPosition))
|
|
startPosition = getDefaultStart();
|
|
if (Number.isNaN(stopPosition))
|
|
stopPosition = getDefaultStop();
|
|
if (stop != null) {
|
|
previousDefinedStopIndex = i;
|
|
}
|
|
result.push({ start: startPosition, stop: stopPosition, ...styles });
|
|
}
|
|
return result;
|
|
};
|
|
return getSegments(segmentation.segments).map(({ stop, start: start2, ...style2 }) => {
|
|
const x0 = isXDirection ? start2 : -horizontalMargin;
|
|
const y0 = isXDirection ? -verticalMargin : start2;
|
|
const x1 = isXDirection ? stop + bandwidth : seriesRect.width + horizontalMargin;
|
|
const y1 = isXDirection ? seriesRect.height + verticalMargin : stop + bandwidth;
|
|
return { clipRect: { x0, y0, x1, y1 }, ...style2 };
|
|
});
|
|
}
|
|
var TIME_AXIS_KEYS = /* @__PURE__ */ new Set(["time", "timestamp", "date", "datetime"]);
|
|
function predictCartesianAxis(direction, datum, seriesOptions, { allowPrimitiveTypes = true } = {}) {
|
|
if (direction !== "x" /* X */ && direction !== "y" /* Y */)
|
|
return;
|
|
if (!isObject(datum))
|
|
return;
|
|
const key = getSeriesOptionsKey(direction, seriesOptions);
|
|
if (key == null || !(key in datum))
|
|
return;
|
|
const value = datum[key];
|
|
const position = getAxisPosition(direction, seriesOptions);
|
|
const groupedCategory = predictGroupedCategoryAxisType(value);
|
|
if (groupedCategory) {
|
|
return { type: groupedCategory, position };
|
|
}
|
|
const timeAxis = predictTimeAxisType(key, value);
|
|
if (timeAxis) {
|
|
return { type: timeAxis, position };
|
|
}
|
|
if (!allowPrimitiveTypes)
|
|
return;
|
|
if (typeof value === "number") {
|
|
return {
|
|
type: "number" /* NUMBER */,
|
|
position
|
|
};
|
|
}
|
|
return {
|
|
type: "category" /* CATEGORY */,
|
|
position
|
|
};
|
|
}
|
|
function predictCartesianNonPrimitiveAxis(direction, datum, seriesOptions) {
|
|
return predictCartesianAxis(direction, datum, seriesOptions, { allowPrimitiveTypes: false });
|
|
}
|
|
function predictCartesianFinancialAxis(direction, datum, seriesOptions) {
|
|
if (direction !== "x" /* X */)
|
|
return;
|
|
if (!isObject(datum))
|
|
return;
|
|
const key = getSeriesOptionsKey(direction, seriesOptions);
|
|
if (key == null || !(key in datum))
|
|
return;
|
|
const value = datum[key];
|
|
const position = getAxisPosition(direction, seriesOptions);
|
|
const ordinalTimeAxis = predictOrdinalTimeAxisType(key, value);
|
|
if (ordinalTimeAxis) {
|
|
return { type: ordinalTimeAxis, position };
|
|
}
|
|
if (isString(value)) {
|
|
return { type: "category", position };
|
|
}
|
|
}
|
|
function predictGroupedCategoryAxisType(value) {
|
|
if (isArray(value) && value.every((v) => isString(v) || v === null)) {
|
|
return "grouped-category" /* GROUPED_CATEGORY */;
|
|
}
|
|
}
|
|
function predictTimeAxisType(key, value) {
|
|
if (isDate(value) || TIME_AXIS_KEYS.has(key) && isNumber(value)) {
|
|
return "time" /* TIME */;
|
|
}
|
|
}
|
|
function predictOrdinalTimeAxisType(key, value) {
|
|
if (isDate(value) || TIME_AXIS_KEYS.has(key) && isNumber(value)) {
|
|
return "ordinal-time" /* ORDINAL_TIME */;
|
|
}
|
|
}
|
|
function getSeriesOptionsKey(direction, seriesOptions) {
|
|
if (direction === "x" /* X */ && "xKey" in seriesOptions) {
|
|
return seriesOptions.xKey;
|
|
} else if (direction === "y" /* Y */ && "yKey" in seriesOptions) {
|
|
return seriesOptions.yKey;
|
|
}
|
|
}
|
|
function getAxisPosition(direction, seriesOptions) {
|
|
if ("direction" in seriesOptions && seriesOptions.direction === "horizontal") {
|
|
return direction === "x" /* X */ ? "left" /* LEFT */ : "bottom" /* BOTTOM */;
|
|
}
|
|
return direction === "x" /* X */ ? "bottom" /* BOTTOM */ : "left" /* LEFT */;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scale/logScale.ts
|
|
var logFunctions = {
|
|
2: (_base, x) => Math.log2(x),
|
|
[Math.E]: (_base, x) => Math.log(x),
|
|
10: (_base, x) => Math.log10(x)
|
|
};
|
|
var DEFAULT_LOG = (base, x) => Math.log(x) / Math.log(base);
|
|
function log2(base, domain, x) {
|
|
const start2 = Math.min(...domain);
|
|
const fn = logFunctions[base] ?? DEFAULT_LOG;
|
|
return start2 >= 0 ? fn(base, x) : -fn(base, -x);
|
|
}
|
|
var powFunctions = {
|
|
[Math.E]: (_base, x) => Math.exp(x),
|
|
10: (_base, x) => x >= 0 ? 10 ** x : 1 / 10 ** -x
|
|
};
|
|
var DEFAULT_POW = (base, x) => base ** x;
|
|
function pow(base, domain, x) {
|
|
const start2 = Math.min(...domain);
|
|
const fn = powFunctions[base] ?? DEFAULT_POW;
|
|
return start2 >= 0 ? fn(base, x) : -fn(base, -x);
|
|
}
|
|
var LogScale = class _LogScale extends ContinuousScale {
|
|
constructor(d = [1, 10], r = [0, 1]) {
|
|
super(d, r);
|
|
this.type = "log";
|
|
// Handling <1 and crossing 0 cases is tricky, easiest solution is to default to clamping.
|
|
this.defaultClamp = true;
|
|
this.base = 10;
|
|
this.log = (x) => log2(this.base, this.domain, x);
|
|
this.pow = (x) => pow(this.base, this.domain, x);
|
|
}
|
|
static is(value) {
|
|
return value instanceof _LogScale;
|
|
}
|
|
transform(x) {
|
|
const [min, max] = findMinMax(this.domain);
|
|
if (min >= 0 !== max >= 0)
|
|
return Number.NaN;
|
|
return min >= 0 ? Math.log(x) : -Math.log(-x);
|
|
}
|
|
transformInvert(x) {
|
|
const [min, max] = findMinMax(this.domain);
|
|
if (min >= 0 !== max >= 0)
|
|
return Number.NaN;
|
|
return min >= 0 ? Math.exp(x) : -Math.exp(-x);
|
|
}
|
|
toDomain(d) {
|
|
return d;
|
|
}
|
|
niceDomain(ticks, domain = this.domain) {
|
|
if (domain.length < 2)
|
|
return [];
|
|
const { base } = this;
|
|
const [d0, d1] = domain;
|
|
const roundStart = d0 > d1 ? Math.ceil : Math.floor;
|
|
const roundStop = d0 > d1 ? Math.floor : Math.ceil;
|
|
const n0 = pow(base, domain, roundStart(log2(base, domain, d0)));
|
|
const n1 = pow(base, domain, roundStop(log2(base, domain, d1)));
|
|
return [ticks.nice[0] ? n0 : domain[0], ticks.nice[1] ? n1 : domain[1]];
|
|
}
|
|
ticks({ interval, tickCount = ContinuousScale.defaultTickCount }, domain = this.domain, visibleRange) {
|
|
if (!domain || domain.length < 2 || tickCount < 1) {
|
|
return;
|
|
}
|
|
const base = this.base;
|
|
const [d0, d1] = domain;
|
|
const start2 = Math.min(d0, d1);
|
|
const stop = Math.max(d0, d1);
|
|
let p0 = this.log(start2);
|
|
let p1 = this.log(stop);
|
|
if (interval) {
|
|
const inBounds = (tick) => tick >= start2 && tick <= stop;
|
|
const step = Math.min(Math.abs(interval), Math.abs(p1 - p0));
|
|
const { ticks: rangeTicks, count, firstTickIndex } = range(p0, p1, step, visibleRange);
|
|
const ticks2 = rangeTicks.map(this.pow).filter(inBounds);
|
|
if (!isDenseInterval(ticks2.length, this.getPixelRange())) {
|
|
return { ticks: ticks2, count, firstTickIndex };
|
|
}
|
|
}
|
|
if (!isInteger(base) || p1 - p0 >= tickCount) {
|
|
const step = Math.min(p1 - p0, tickCount);
|
|
const { ticks: ticks2, count, firstTickIndex } = createTicks(p0, p1, step, void 0, void 0, visibleRange);
|
|
return {
|
|
ticks: ticks2.map(this.pow),
|
|
count,
|
|
firstTickIndex
|
|
};
|
|
}
|
|
const ticks = [];
|
|
const isPositive = start2 > 0;
|
|
p0 = Math.floor(p0) - 1;
|
|
p1 = Math.round(p1) + 1;
|
|
const availableSpacing = findRangeExtent(this.range) / tickCount;
|
|
let lastTickPosition = Infinity;
|
|
for (let p = p0; p <= p1; p++) {
|
|
const nextMagnitudeTickPosition = this.convert(this.pow(p + 1));
|
|
for (let k = 1; k < base; k++) {
|
|
const q = isPositive ? k : base - k + 1;
|
|
const t = this.pow(p) * q;
|
|
const tickPosition = this.convert(t);
|
|
const prevSpacing = Math.abs(lastTickPosition - tickPosition);
|
|
const nextSpacing = Math.abs(tickPosition - nextMagnitudeTickPosition);
|
|
const fits = prevSpacing >= availableSpacing && nextSpacing >= availableSpacing;
|
|
if (t >= start2 && t <= stop && (k === 1 || fits || ticks.length === 0)) {
|
|
ticks.push(t);
|
|
lastTickPosition = tickPosition;
|
|
}
|
|
}
|
|
}
|
|
return filterVisibleTicks(ticks, isPositive, visibleRange);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/shape/segmentedPath.ts
|
|
var SegmentedPath = class extends Path {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.segmentPath = new Path();
|
|
}
|
|
drawPath(ctx) {
|
|
if (!this.segments || this.segments.length === 0) {
|
|
super.drawPath(ctx);
|
|
return;
|
|
}
|
|
ctx.save();
|
|
const Path2DCtor = getPath2D();
|
|
const inverse = new Path2DCtor();
|
|
rect(inverse, { x0: 0, y0: 0, x1: ctx.canvas.width, y1: ctx.canvas.height }, false);
|
|
for (const s of this.segments) {
|
|
rect(inverse, s.clipRect);
|
|
}
|
|
ctx.clip(inverse);
|
|
super.drawPath(ctx);
|
|
ctx.restore();
|
|
const { segmentPath } = this;
|
|
segmentPath.setProperties({
|
|
opacity: this.opacity,
|
|
visible: this.visible,
|
|
lineCap: this.lineCap,
|
|
lineJoin: this.lineJoin,
|
|
pointerEvents: this.pointerEvents
|
|
});
|
|
for (const { clipRect, fill, stroke: stroke3, ...styles } of this.segments) {
|
|
ctx.save();
|
|
segmentPath.path = this.path;
|
|
segmentPath.setProperties(styles);
|
|
segmentPath.fill = this.fill == null ? "none" : fill;
|
|
segmentPath.stroke = this.stroke == null ? "none" : stroke3;
|
|
const clipPath = new Path2DCtor();
|
|
rect(clipPath, clipRect);
|
|
ctx.clip(clipPath);
|
|
segmentPath.drawPath(ctx);
|
|
ctx.restore();
|
|
}
|
|
}
|
|
};
|
|
__decorateClass([
|
|
SceneRefChangeDetection()
|
|
], SegmentedPath.prototype, "segments", 2);
|
|
function rect(path, { x0, y0, x1, y1 }, clockwise = true) {
|
|
const minX = Math.min(x0, x1);
|
|
const minY = Math.min(y0, y1);
|
|
const maxX = Math.max(x0, x1);
|
|
const maxY = Math.max(y0, y1);
|
|
path.moveTo(minX, minY);
|
|
if (clockwise) {
|
|
path.lineTo(maxX, minY);
|
|
path.lineTo(maxX, maxY);
|
|
path.lineTo(minX, maxY);
|
|
} else {
|
|
path.lineTo(minX, maxY);
|
|
path.lineTo(maxX, maxY);
|
|
path.lineTo(maxX, minY);
|
|
}
|
|
path.closePath();
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/segmentedGroup.ts
|
|
var SegmentedGroup = class extends TranslatableGroup {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.segments = [];
|
|
this.scalablePath = new (Scalable(Path))();
|
|
}
|
|
renderInContext(childRenderCtx) {
|
|
if (!this.visible)
|
|
return;
|
|
const { ctx } = childRenderCtx;
|
|
if (!this.segments || this.segments?.length === 0) {
|
|
return super.renderInContext(childRenderCtx);
|
|
}
|
|
ctx.save();
|
|
const Path2DCtor = getPath2D();
|
|
const inverse = new Path2DCtor();
|
|
rect(inverse, { x0: 0, y0: 0, x1: ctx.canvas.width, y1: ctx.canvas.height }, false);
|
|
for (const s of this.segments) {
|
|
rect(inverse, s.clipRect);
|
|
}
|
|
ctx.clip(inverse);
|
|
for (const child of this.children()) {
|
|
if (!child.visible)
|
|
continue;
|
|
child.render(childRenderCtx);
|
|
}
|
|
ctx.restore();
|
|
const { scalablePath } = this;
|
|
for (const { clipRect, ...styles } of this.segments) {
|
|
ctx.save();
|
|
const clipPath = new Path2DCtor();
|
|
rect(clipPath, clipRect);
|
|
ctx.clip(clipPath);
|
|
scalablePath.setProperties(styles);
|
|
for (const child of this.children()) {
|
|
if (!child.visible || !(child instanceof Path))
|
|
continue;
|
|
scalablePath.path = child.path;
|
|
scalablePath.setProperties({
|
|
opacity: child.opacity,
|
|
lineCap: child.lineCap,
|
|
lineJoin: child.lineJoin,
|
|
...isScalable(child) && {
|
|
scalingX: child.scalingX,
|
|
scalingY: child.scalingY,
|
|
scalingCenterX: child.scalingCenterX,
|
|
scalingCenterY: child.scalingCenterY
|
|
}
|
|
});
|
|
scalablePath.render(childRenderCtx);
|
|
}
|
|
ctx.restore();
|
|
}
|
|
}
|
|
};
|
|
__decorateClass([
|
|
SceneRefChangeDetection()
|
|
], SegmentedGroup.prototype, "segments", 2);
|
|
|
|
// packages/ag-charts-community/src/scene/util/quadtree.ts
|
|
var QuadtreeNearest = class {
|
|
constructor(capacity, maxdepth, boundary) {
|
|
this.root = new QuadtreeNodeNearest(capacity, maxdepth, boundary);
|
|
}
|
|
clear(boundary) {
|
|
this.root.clear(boundary);
|
|
}
|
|
addValue(hitTester, value) {
|
|
const elem = {
|
|
hitTester,
|
|
value,
|
|
distanceSquared: (x, y) => {
|
|
return hitTester.distanceSquared(x, y);
|
|
}
|
|
};
|
|
this.root.addElem(elem);
|
|
}
|
|
find(x, y) {
|
|
const arg = { best: { nearest: void 0, distanceSquared: Infinity } };
|
|
this.root.find(x, y, arg);
|
|
return arg.best;
|
|
}
|
|
};
|
|
var QuadtreeSubdivisions = class {
|
|
constructor(nw, ne, sw, se) {
|
|
this.nw = nw;
|
|
this.ne = ne;
|
|
this.sw = sw;
|
|
this.se = se;
|
|
}
|
|
addElem(elem) {
|
|
this.nw.addElem(elem);
|
|
this.ne.addElem(elem);
|
|
this.sw.addElem(elem);
|
|
this.se.addElem(elem);
|
|
}
|
|
find(x, y, arg) {
|
|
this.nw.find(x, y, arg);
|
|
this.ne.find(x, y, arg);
|
|
this.sw.find(x, y, arg);
|
|
this.se.find(x, y, arg);
|
|
}
|
|
};
|
|
var QuadtreeNode = class {
|
|
constructor(capacity, maxdepth, boundary) {
|
|
this.capacity = capacity;
|
|
this.maxdepth = maxdepth;
|
|
this.boundary = boundary ?? BBox.NaN;
|
|
this.elems = [];
|
|
this.subdivisions = void 0;
|
|
}
|
|
clear(boundary) {
|
|
this.elems.length = 0;
|
|
this.boundary = boundary;
|
|
this.subdivisions = void 0;
|
|
}
|
|
addElem(e) {
|
|
if (this.addCondition(e)) {
|
|
if (this.subdivisions === void 0) {
|
|
if (this.maxdepth === 0 || this.elems.length < this.capacity) {
|
|
this.elems.push(e);
|
|
} else {
|
|
this.subdivide(e);
|
|
}
|
|
} else {
|
|
this.subdivisions.addElem(e);
|
|
}
|
|
}
|
|
}
|
|
find(x, y, arg) {
|
|
if (this.findCondition(x, y, arg)) {
|
|
if (this.subdivisions === void 0) {
|
|
this.findAction(x, y, arg);
|
|
} else {
|
|
this.subdivisions.find(x, y, arg);
|
|
}
|
|
}
|
|
}
|
|
subdivide(newElem) {
|
|
this.subdivisions = this.makeSubdivisions();
|
|
for (const e of this.elems) {
|
|
this.subdivisions.addElem(e);
|
|
}
|
|
this.subdivisions.addElem(newElem);
|
|
this.elems.length = 0;
|
|
}
|
|
makeSubdivisions() {
|
|
const { x, y, width: width2, height: height2 } = this.boundary;
|
|
const { capacity } = this;
|
|
const depth = this.maxdepth - 1;
|
|
const halfWidth = width2 / 2;
|
|
const halfHeight = height2 / 2;
|
|
const nwBoundary = new BBox(x, y, halfWidth, halfHeight);
|
|
const neBoundary = new BBox(x + halfWidth, y, halfWidth, halfHeight);
|
|
const swBoundary = new BBox(x, y + halfHeight, halfWidth, halfHeight);
|
|
const seBoundary = new BBox(x + halfWidth, y + halfHeight, halfWidth, halfHeight);
|
|
return new QuadtreeSubdivisions(
|
|
this.child(capacity, depth, nwBoundary),
|
|
this.child(capacity, depth, neBoundary),
|
|
this.child(capacity, depth, swBoundary),
|
|
this.child(capacity, depth, seBoundary)
|
|
);
|
|
}
|
|
};
|
|
var QuadtreeNodeNearest = class _QuadtreeNodeNearest extends QuadtreeNode {
|
|
addCondition(e) {
|
|
const { x, y } = e.hitTester.midPoint;
|
|
return this.boundary.containsPoint(x, y);
|
|
}
|
|
findCondition(x, y, arg) {
|
|
const { best } = arg;
|
|
return best.distanceSquared !== 0 && this.boundary.distanceSquared(x, y) < best.distanceSquared;
|
|
}
|
|
findAction(x, y, arg) {
|
|
const other = nearestSquared(x, y, this.elems, arg.best.distanceSquared);
|
|
if (other.nearest !== void 0 && other.distanceSquared < arg.best.distanceSquared) {
|
|
arg.best = other;
|
|
}
|
|
}
|
|
child(capacity, depth, boundary) {
|
|
return new _QuadtreeNodeNearest(capacity, depth, boundary);
|
|
}
|
|
};
|
|
|
|
// 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(start2, stop, ticks) {
|
|
const { interval, tickCount = ContinuousScale.defaultTickCount, minTickCount, maxTickCount } = ticks;
|
|
return interval ?? tickStep(start2, 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 [start2, stop] = domain;
|
|
if (tickCount === 1) {
|
|
[start2, stop] = niceTicksDomain(start2, stop);
|
|
} else if (tickCount > 1) {
|
|
const roundStart = start2 > stop ? Math.ceil : Math.floor;
|
|
const roundStop = start2 > stop ? Math.floor : Math.ceil;
|
|
const maxAttempts = 4;
|
|
for (let i = 0; i < maxAttempts; i++) {
|
|
const prev0 = start2;
|
|
const prev1 = stop;
|
|
const step = _LinearScale.getTickStep(start2, stop, ticks);
|
|
const [d0, d1] = domain;
|
|
start2 = roundStart(d0 / step) * step;
|
|
stop = roundStop(d1 / step) * step;
|
|
if (start2 === prev0 && stop === prev1)
|
|
break;
|
|
}
|
|
}
|
|
return [ticks.nice[0] ? start2 : domain[0], ticks.nice[1] ? stop : domain[1]];
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/axis/numberAxis.ts
|
|
var NumberAxis = class extends CartesianAxis {
|
|
constructor(moduleCtx, scale2 = new LinearScale()) {
|
|
super(moduleCtx, scale2);
|
|
}
|
|
hasDefinedDomain() {
|
|
const { min, max } = this;
|
|
return min != null && max != null && min < max;
|
|
}
|
|
normaliseDataDomain(d) {
|
|
const { min, max, preferredMin, preferredMax } = this;
|
|
const { extent: extent2, clipped } = normalisedExtentWithMetadata(
|
|
d.domain,
|
|
min,
|
|
max,
|
|
preferredMin,
|
|
preferredMax,
|
|
void 0,
|
|
d.sortMetadata?.sortOrder
|
|
);
|
|
return { domain: extent2, clipped };
|
|
}
|
|
getDomainExtentsNice() {
|
|
return [this.min == null && this.nice, this.max == null && this.nice];
|
|
}
|
|
getVisibleDomain(domain) {
|
|
const [d0, d1] = domain;
|
|
const [r0, r1] = this.visibleRange;
|
|
const length2 = d1 - d0;
|
|
return [d0 + r0 * length2, d1 - (1 - r1) * length2];
|
|
}
|
|
tickFormatParams(domain, _ticks, fractionDigits) {
|
|
return { type: "number", visibleDomain: this.getVisibleDomain(domain), fractionDigits };
|
|
}
|
|
datumFormatParams(value, params, fractionDigits) {
|
|
const { datum, seriesId, legendItemName, key, source, property, domain, boundSeries } = params;
|
|
const visibleDomain = this.getVisibleDomain(domain);
|
|
return {
|
|
type: "number",
|
|
value,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key,
|
|
source,
|
|
property,
|
|
domain,
|
|
visibleDomain,
|
|
boundSeries,
|
|
fractionDigits
|
|
};
|
|
}
|
|
};
|
|
NumberAxis.className = "NumberAxis";
|
|
NumberAxis.type = "number";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], NumberAxis.prototype, "min", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], NumberAxis.prototype, "max", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], NumberAxis.prototype, "preferredMin", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], NumberAxis.prototype, "preferredMax", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/axis/timeAxis.ts
|
|
var TimeAxisParentLevel = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = false;
|
|
this.label = new AxisLabel();
|
|
this.tick = new AxisTick();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TimeAxisParentLevel.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TimeAxisParentLevel.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TimeAxisParentLevel.prototype, "tick", 2);
|
|
var TimeAxis = class extends CartesianAxis {
|
|
constructor(moduleCtx) {
|
|
super(moduleCtx, new TimeScale());
|
|
this.parentLevel = new TimeAxisParentLevel();
|
|
this.min = void 0;
|
|
this.max = void 0;
|
|
this.preferredMin = void 0;
|
|
this.preferredMax = void 0;
|
|
}
|
|
// eslint-disable-next-line sonarjs/use-type-alias
|
|
get _unit() {
|
|
return void 0;
|
|
}
|
|
set _unit(_unit) {
|
|
logger_exports.warnOnce(`To use 'unit', use an axis with type 'unit-time' instead of 'time'.`);
|
|
}
|
|
hasDefinedDomain() {
|
|
const { min, max } = this;
|
|
return min != null && max != null && min < max;
|
|
}
|
|
isCategoryLike() {
|
|
return false;
|
|
}
|
|
get primaryLabel() {
|
|
return this.parentLevel.enabled ? this.parentLevel.label : void 0;
|
|
}
|
|
get primaryTick() {
|
|
return this.parentLevel.enabled ? this.parentLevel.tick : void 0;
|
|
}
|
|
normaliseDataDomain(d) {
|
|
const { extent: extent2, clipped } = normalisedTimeExtentWithMetadata(
|
|
d,
|
|
this.min,
|
|
this.max,
|
|
this.preferredMin,
|
|
this.preferredMax
|
|
);
|
|
return { domain: extent2, clipped };
|
|
}
|
|
processData() {
|
|
super.processData();
|
|
const { boundSeries, direction, min, max } = this;
|
|
this.minimumTimeGranularity = minimumTimeAxisDatumGranularity(boundSeries, direction, min, max);
|
|
}
|
|
tickFormatParams(domain, ticks, _fractionDigits, timeInterval3) {
|
|
timeInterval3 ?? (timeInterval3 = lowestGranularityUnitForTicks(ticks));
|
|
const truncateDate = dateTruncationForDomain(domain);
|
|
const unit = intervalUnit(timeInterval3);
|
|
const step = intervalStep(timeInterval3);
|
|
const epoch = intervalEpoch(timeInterval3);
|
|
return { type: "date", unit, step, epoch, truncateDate };
|
|
}
|
|
datumFormatParams(value, params, _fractionDigits, timeInterval3, style2) {
|
|
if (typeof value === "number") {
|
|
value = new Date(value);
|
|
}
|
|
if (timeInterval3 == null) {
|
|
const { minimumTimeGranularity } = this;
|
|
const datumGranularity = lowestGranularityUnitForValue(value);
|
|
if (minimumTimeGranularity != null && intervalMilliseconds(minimumTimeGranularity) < intervalMilliseconds(datumGranularity)) {
|
|
timeInterval3 = minimumTimeGranularity;
|
|
} else {
|
|
timeInterval3 = datumGranularity;
|
|
}
|
|
}
|
|
const { datum, seriesId, legendItemName, key, source, property, domain, boundSeries } = params;
|
|
const unit = intervalUnit(timeInterval3);
|
|
const step = intervalStep(timeInterval3);
|
|
const epoch = intervalEpoch(timeInterval3);
|
|
return {
|
|
type: "date",
|
|
value,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key,
|
|
source,
|
|
property,
|
|
domain,
|
|
boundSeries,
|
|
unit,
|
|
step,
|
|
epoch,
|
|
style: style2
|
|
};
|
|
}
|
|
};
|
|
TimeAxis.className = "TimeAxis";
|
|
TimeAxis.type = "time";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TimeAxis.prototype, "parentLevel", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TimeAxis.prototype, "min", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TimeAxis.prototype, "max", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TimeAxis.prototype, "preferredMin", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TimeAxis.prototype, "preferredMax", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("_unit")
|
|
], TimeAxis.prototype, "unit", 2);
|
|
function minimumTimeAxisDatumGranularity(boundSeries, direction, min, max) {
|
|
const minTimeInterval = boundSeries.reduce((t, series) => {
|
|
return Math.min(series.minTimeInterval() ?? Infinity, t);
|
|
}, Infinity);
|
|
if (Number.isFinite(minTimeInterval)) {
|
|
return lowestGranularityForInterval(minTimeInterval);
|
|
} else {
|
|
return calculateDefaultUnit(boundSeries, direction, min, max)?.unit;
|
|
}
|
|
}
|
|
function calculateDefaultUnit(boundSeries, direction, min, max) {
|
|
let start2 = Infinity;
|
|
let end3 = -Infinity;
|
|
let interval;
|
|
let maxDataCount = 0;
|
|
const domainValues = [];
|
|
for (const series of boundSeries) {
|
|
if (!series.visible)
|
|
continue;
|
|
const { extent: domain } = normalisedTimeExtentWithMetadata(series.getDomain(direction));
|
|
if (domain.length === 0)
|
|
continue;
|
|
const d0 = domain[0].valueOf();
|
|
const d1 = domain.at(-1).valueOf();
|
|
domainValues.push(d0, d1);
|
|
start2 = Math.min(start2 ?? Infinity, d0, d1);
|
|
end3 = Math.max(end3 ?? -Infinity, d0, d1);
|
|
const domainExtent = Math.abs(d1 - d0);
|
|
if (domainExtent === 0)
|
|
continue;
|
|
const dataCount = series.dataCount();
|
|
maxDataCount = Math.max(maxDataCount, dataCount);
|
|
if (dataCount <= 1)
|
|
continue;
|
|
const i = domainExtent / (dataCount - 1);
|
|
interval = Math.min(interval ?? Infinity, i);
|
|
}
|
|
start2 = Math.min(start2, min?.valueOf() ?? Infinity, max?.valueOf() ?? Infinity);
|
|
end3 = Math.max(end3, min?.valueOf() ?? -Infinity, max?.valueOf() ?? -Infinity);
|
|
if (!Number.isFinite(start2) || !Number.isFinite(end3))
|
|
return;
|
|
interval ?? (interval = Math.abs(end3 - start2));
|
|
interval = Math.min(interval, minNonZeroDifference(domainValues));
|
|
const unit = lowestGranularityForInterval(interval);
|
|
let step = interval / intervalMilliseconds(unit);
|
|
if (maxDataCount <= 2) {
|
|
step = Math.floor(step);
|
|
} else {
|
|
step = Math.round(step);
|
|
}
|
|
step = Math.max(step, 1);
|
|
const epoch = step === 1 ? void 0 : intervalFloor(unit, start2);
|
|
return { unit, step, epoch };
|
|
}
|
|
function minNonZeroDifference(values) {
|
|
values.sort((a, b) => a - b);
|
|
let minDiff = Infinity;
|
|
for (let i = 1; i < values.length; i++) {
|
|
const d0 = values[i - 1];
|
|
const d1 = values[i];
|
|
const delta5 = d1 - d0;
|
|
if (delta5 > 0) {
|
|
minDiff = Math.min(minDiff, Math.abs(d1 - d0));
|
|
}
|
|
}
|
|
return minDiff;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/dataModelSeries.ts
|
|
var DataModelSeries = class extends Series {
|
|
constructor({ clipFocusBox, categoryKey, ...seriesOpts }) {
|
|
super(seriesOpts);
|
|
this.categoryKey = categoryKey;
|
|
this.clipFocusBox = clipFocusBox ?? true;
|
|
}
|
|
dataCount() {
|
|
return this.processedData?.dataSources?.get(this.id)?.data?.length ?? 0;
|
|
}
|
|
invalidDataCount() {
|
|
return this.processedData?.invalidDataCount?.get(this.id) ?? 0;
|
|
}
|
|
missingDataCount() {
|
|
return this.dataModel?.resolveMissingDataCount(this) ?? 0;
|
|
}
|
|
get hasData() {
|
|
return Math.max(0, this.dataCount() - this.invalidDataCount() - this.missingDataCount()) > 0;
|
|
}
|
|
getScaleInformation({
|
|
xScale,
|
|
yScale
|
|
}) {
|
|
const isContinuousX = ContinuousScale.is(xScale);
|
|
const isContinuousY = ContinuousScale.is(yScale);
|
|
return { isContinuousX, isContinuousY, xScaleType: xScale?.type, yScaleType: yScale?.type };
|
|
}
|
|
getModulePropertyDefinitions() {
|
|
const xScale = this.axes["x" /* X */]?.scale;
|
|
const yScale = this.axes["y" /* Y */]?.scale;
|
|
return this.moduleMap.mapModules((m) => m.getPropertyDefinitions(this.getScaleInformation({ xScale, yScale }))).flat();
|
|
}
|
|
// Request data, but with message dispatching to series-options (modules).
|
|
async requestDataModel(dataController, dataSet, opts) {
|
|
opts.props.push(...this.getModulePropertyDefinitions());
|
|
const { dataModel, processedData } = await dataController.request(
|
|
this.id,
|
|
dataSet ?? DataSet.empty(),
|
|
opts
|
|
);
|
|
this.dataModel = dataModel;
|
|
this.processedData = processedData;
|
|
this.events.emit("data-processed", { dataModel, processedData });
|
|
return { dataModel, processedData };
|
|
}
|
|
isProcessedDataAnimatable() {
|
|
const { processedData, ctx } = this;
|
|
if (!processedData)
|
|
return false;
|
|
const nodeData = this.getNodeData();
|
|
if (nodeData != null && nodeData.length > ctx.animationManager.maxAnimatableItems)
|
|
return false;
|
|
const validationResults = processedData.reduced?.animationValidation;
|
|
if (!validationResults)
|
|
return true;
|
|
const { orderedKeys, uniqueKeys } = validationResults;
|
|
return orderedKeys && uniqueKeys;
|
|
}
|
|
checkProcessedDataAnimatable() {
|
|
if (!this.isProcessedDataAnimatable()) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
}
|
|
}
|
|
findNodeDatum(itemId) {
|
|
return findNodeDatumInArray(itemId, this.getNodeData());
|
|
}
|
|
pickFocus(opts) {
|
|
const nodeData = this.getNodeData();
|
|
if (nodeData === void 0 || nodeData.length === 0) {
|
|
return;
|
|
}
|
|
const datumIndex = this.computeFocusDatumIndex(opts, nodeData);
|
|
if (datumIndex === void 0) {
|
|
return;
|
|
}
|
|
const { clipFocusBox } = this;
|
|
const datum = nodeData[datumIndex];
|
|
const derivedOpts = { ...opts, datumIndex };
|
|
const bounds = this.computeFocusBounds(derivedOpts);
|
|
if (bounds !== void 0) {
|
|
return { bounds, clipFocusBox, datum, datumIndex };
|
|
}
|
|
}
|
|
pickNodesExactShape(point) {
|
|
const datums = super.pickNodesExactShape(point);
|
|
datums.sort((a, b) => a.datumIndex - b.datumIndex);
|
|
return datums;
|
|
}
|
|
isDatumEnabled(nodeData, datumIndex) {
|
|
const { missing = false, enabled = true, focusable = true } = nodeData[datumIndex];
|
|
return !missing && enabled && focusable;
|
|
}
|
|
computeFocusDatumIndex(opts, nodeData) {
|
|
const searchBackward = (datumIndex2, delta5) => {
|
|
while (datumIndex2 >= 0 && !this.isDatumEnabled(nodeData, datumIndex2)) {
|
|
datumIndex2 += delta5;
|
|
}
|
|
return datumIndex2 === -1 ? void 0 : datumIndex2;
|
|
};
|
|
const searchForward = (datumIndex2, delta5) => {
|
|
while (datumIndex2 < nodeData.length && !this.isDatumEnabled(nodeData, datumIndex2)) {
|
|
datumIndex2 += delta5;
|
|
}
|
|
return datumIndex2 === nodeData.length ? void 0 : datumIndex2;
|
|
};
|
|
let datumIndex;
|
|
const clampedIndex = clamp(0, opts.datumIndex, nodeData.length - 1);
|
|
if (opts.datumIndexDelta < 0) {
|
|
datumIndex = searchBackward(clampedIndex, opts.datumIndexDelta);
|
|
} else if (opts.datumIndexDelta > 0) {
|
|
datumIndex = searchForward(clampedIndex, opts.datumIndexDelta);
|
|
} else {
|
|
datumIndex = searchForward(clampedIndex, 1) ?? searchBackward(clampedIndex, -1);
|
|
}
|
|
if (datumIndex === void 0) {
|
|
if (opts.datumIndexDelta === 0) {
|
|
return;
|
|
} else {
|
|
return opts.datumIndex - opts.datumIndexDelta;
|
|
}
|
|
} else {
|
|
return datumIndex;
|
|
}
|
|
}
|
|
// Workaround - it would be nice if this difference didn't exist
|
|
dataModelPropertyIsKey(key) {
|
|
const { processedData } = this;
|
|
if (!processedData)
|
|
return false;
|
|
return processedData.defs.keys.some((def) => def.id === key && def.idsMap?.get(this.id)?.has(key) === true);
|
|
}
|
|
keysOrValues(xKey) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData)
|
|
return [];
|
|
return this.dataModelPropertyIsKey(xKey) ? dataModel.resolveKeysById(this, xKey, processedData) : dataModel.resolveColumnById(this, xKey, processedData);
|
|
}
|
|
sortOrder(xKey) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData)
|
|
return;
|
|
return this.dataModelPropertyIsKey(xKey) ? dataModel.getKeySortOrder(this, xKey, processedData) : dataModel.getColumnSortOrder(this, xKey, processedData);
|
|
}
|
|
getCategoryKey() {
|
|
return this.categoryKey;
|
|
}
|
|
getCategoryValue(datumIndex) {
|
|
const { processedData, dataModel } = this;
|
|
const categoryKey = this.getCategoryKey();
|
|
if (!processedData || !dataModel || !categoryKey)
|
|
return;
|
|
const invalid = processedData.invalidData?.get(this.id)?.[datumIndex] ?? false;
|
|
return invalid ? void 0 : this.keysOrValues(categoryKey)[datumIndex];
|
|
}
|
|
datumIndexForCategoryValue(categoryValue) {
|
|
const { processedData, dataModel } = this;
|
|
const categoryKey = this.getCategoryKey();
|
|
if (!processedData || !dataModel || !categoryKey)
|
|
return;
|
|
categoryValue = categoryValue.valueOf();
|
|
const invalidValues = processedData.invalidData?.get(this.id);
|
|
const xValues = this.keysOrValues(categoryKey);
|
|
for (let datumIndex = 0; datumIndex < xValues.length; datumIndex += 1) {
|
|
if (invalidValues?.[datumIndex] === true)
|
|
continue;
|
|
const xValue = xValues[datumIndex]?.valueOf();
|
|
if (objectsEqual(categoryValue, xValue))
|
|
return datumIndex;
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/cartesianSeries.ts
|
|
var DEFAULT_CARTESIAN_DIRECTION_KEYS = {
|
|
["x" /* X */]: ["xKey"],
|
|
["y" /* Y */]: ["yKey"]
|
|
};
|
|
var DEFAULT_CARTESIAN_DIRECTION_NAMES = {
|
|
["x" /* X */]: ["xName"],
|
|
["y" /* Y */]: ["yName"]
|
|
};
|
|
var CartesianSeriesNodeEvent = class extends SeriesNodeEvent {
|
|
constructor(type, nativeEvent, datum, series) {
|
|
super(type, nativeEvent, datum, series);
|
|
this.xKey = series.properties.xKey;
|
|
this.yKey = series.properties.yKey;
|
|
}
|
|
};
|
|
var CartesianSeriesProperties = class extends SeriesProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.xKeyAxis = "x";
|
|
this.yKeyAxis = "y";
|
|
this.pickOutsideVisibleMinorAxis = false;
|
|
this.segmentation = new Segmentation();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianSeriesProperties.prototype, "xKeyAxis", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianSeriesProperties.prototype, "yKeyAxis", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianSeriesProperties.prototype, "legendItemName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianSeriesProperties.prototype, "pickOutsideVisibleMinorAxis", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CartesianSeriesProperties.prototype, "segmentation", 2);
|
|
var RENDER_TO_OFFSCREEN_CANVAS_THRESHOLD = 100;
|
|
var CartesianSeries = class extends DataModelSeries {
|
|
constructor({
|
|
pathsPerSeries = ["path"],
|
|
pathsZIndexSubOrderOffset = [],
|
|
datumSelectionGarbageCollection = true,
|
|
animationAlwaysUpdateSelections = false,
|
|
animationAlwaysPopulateNodeData = false,
|
|
segmentedDataNodes = true,
|
|
animationResetFns,
|
|
propertyKeys,
|
|
propertyNames,
|
|
...otherOpts
|
|
}) {
|
|
super({
|
|
propertyKeys,
|
|
propertyNames,
|
|
canHaveAxes: true,
|
|
...otherOpts
|
|
});
|
|
this.NodeEvent = CartesianSeriesNodeEvent;
|
|
this.dataNodeGroup = this.contentGroup.appendChild(
|
|
new SegmentedGroup({ name: `${this.id}-series-dataNodes`, zIndex: 1 })
|
|
);
|
|
this.labelGroup = this.contentGroup.appendChild(
|
|
new TranslatableGroup({ name: `${this.id}-series-labels` })
|
|
);
|
|
this.labelSelection = Selection.select(this.labelGroup, Text);
|
|
this.highlightSelection = Selection.select(this.highlightNodeGroup, () => this.nodeFactory());
|
|
this.highlightLabelSelection = Selection.select(
|
|
this.highlightLabelGroup,
|
|
Text
|
|
);
|
|
this.annotationSelections = /* @__PURE__ */ new Set();
|
|
this.seriesBelowStackContext = void 0;
|
|
this.debug = debugLogger_exports.create();
|
|
if (!propertyKeys || !propertyNames)
|
|
throw new Error(`Unable to initialise series type ${this.type}`);
|
|
this.opts = {
|
|
pathsPerSeries,
|
|
pathsZIndexSubOrderOffset,
|
|
propertyKeys,
|
|
propertyNames,
|
|
animationResetFns,
|
|
animationAlwaysUpdateSelections,
|
|
animationAlwaysPopulateNodeData,
|
|
datumSelectionGarbageCollection,
|
|
segmentedDataNodes
|
|
};
|
|
this.paths = pathsPerSeries.map((path) => {
|
|
return new SegmentedPath({ name: `${this.id}-${path}` });
|
|
});
|
|
this.datumSelection = Selection.select(
|
|
this.dataNodeGroup,
|
|
() => this.nodeFactory(),
|
|
datumSelectionGarbageCollection
|
|
);
|
|
this.animationState = new StateMachine(
|
|
"empty",
|
|
{
|
|
empty: {
|
|
update: {
|
|
target: "ready",
|
|
action: (data) => this.animateEmptyUpdateReady(data)
|
|
},
|
|
reset: "empty",
|
|
skip: "ready",
|
|
disable: "disabled"
|
|
},
|
|
ready: {
|
|
updateData: "waiting",
|
|
clear: "clearing",
|
|
highlight: (data) => this.animateReadyHighlight(data),
|
|
resize: (data) => this.animateReadyResize(data),
|
|
reset: "empty",
|
|
skip: "ready",
|
|
disable: "disabled"
|
|
},
|
|
waiting: {
|
|
update: {
|
|
target: "ready",
|
|
action: (data) => {
|
|
if (this.ctx.animationManager.isSkipped()) {
|
|
this.resetAllAnimation(data);
|
|
} else {
|
|
this.animateWaitingUpdateReady(data);
|
|
}
|
|
}
|
|
},
|
|
reset: "empty",
|
|
skip: "ready",
|
|
disable: "disabled"
|
|
},
|
|
disabled: {
|
|
update: (data) => this.resetAllAnimation(data),
|
|
reset: "empty"
|
|
},
|
|
clearing: {
|
|
update: {
|
|
target: "empty",
|
|
action: (data) => this.animateClearingUpdateEmpty(data)
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
}
|
|
},
|
|
this.checkProcessedDataAnimatable.bind(this)
|
|
);
|
|
this.cleanup.register(
|
|
this.ctx.eventsHub.on("legend:item-click", (event) => this.onLegendItemClick(event)),
|
|
this.ctx.eventsHub.on("legend:item-double-click", (event) => this.onLegendItemDoubleClick(event))
|
|
);
|
|
}
|
|
get contextNodeData() {
|
|
return this._contextNodeData;
|
|
}
|
|
getNodeData() {
|
|
return this.contextNodeData?.nodeData;
|
|
}
|
|
getKeyAxis(direction) {
|
|
if (this.shouldFlipXY()) {
|
|
if (direction === "x" /* X */)
|
|
return this.properties.yKeyAxis;
|
|
if (direction === "y" /* Y */)
|
|
return this.properties.xKeyAxis;
|
|
}
|
|
if (direction === "x" /* X */)
|
|
return this.properties.xKeyAxis;
|
|
if (direction === "y" /* Y */)
|
|
return this.properties.yKeyAxis;
|
|
}
|
|
attachSeries(seriesContentNode, seriesNode, annotationNode) {
|
|
super.attachSeries(seriesContentNode, seriesNode, annotationNode);
|
|
this.attachPaths(this.paths);
|
|
}
|
|
detachSeries(seriesContentNode, seriesNode, annotationNode) {
|
|
super.detachSeries(seriesContentNode, seriesNode, annotationNode);
|
|
this.detachPaths(this.paths);
|
|
}
|
|
updatedDomains() {
|
|
this.animationState.transition("updateData");
|
|
}
|
|
attachPaths(paths) {
|
|
for (const path of paths) {
|
|
this.contentGroup.appendChild(path);
|
|
}
|
|
}
|
|
detachPaths(paths) {
|
|
for (const path of paths) {
|
|
path.remove();
|
|
}
|
|
}
|
|
renderToOffscreenCanvas() {
|
|
const nodeData = this.getNodeData();
|
|
return nodeData != null && nodeData.length > RENDER_TO_OFFSCREEN_CANVAS_THRESHOLD;
|
|
}
|
|
resetAnimation(phase) {
|
|
if (phase === "initial") {
|
|
this.animationState.transition("reset");
|
|
} else if (phase === "ready") {
|
|
this.animationState.transition("skip");
|
|
} else if (phase === "disabled") {
|
|
this.animationState.transition("disable");
|
|
}
|
|
}
|
|
destroy() {
|
|
super.destroy();
|
|
this._contextNodeData = void 0;
|
|
}
|
|
isSeriesHighlighted(highlightedDatum) {
|
|
if (!this.properties.highlight.enabled) {
|
|
return false;
|
|
}
|
|
const { series, legendItemName: activeLegendItemName } = highlightedDatum ?? {};
|
|
const { legendItemName } = this.properties;
|
|
return series === this || legendItemName != null && legendItemName === activeLegendItemName;
|
|
}
|
|
strokewidthChange() {
|
|
const unhighlightedStrokeWidth = ("strokeWidth" in this.properties && this.properties.strokeWidth) ?? 0;
|
|
const highlightedSeriesStrokeWidth = this.properties.highlight.highlightedSeries.strokeWidth ?? unhighlightedStrokeWidth;
|
|
const highlightedItemStrokeWidth = this.properties.highlight.highlightedItem?.strokeWidth ?? unhighlightedStrokeWidth;
|
|
return unhighlightedStrokeWidth > highlightedItemStrokeWidth || highlightedSeriesStrokeWidth > highlightedItemStrokeWidth;
|
|
}
|
|
update({ seriesRect }) {
|
|
const { _contextNodeData: previousContextData } = this;
|
|
const resize = this.checkResize(seriesRect);
|
|
const itemHighlighted = this.updateHighlightSelection();
|
|
const series = this;
|
|
this.contentGroup.batchedUpdate(function updateSelections() {
|
|
const dataChanged = series.updateSelections();
|
|
const segments = series.contextNodeData?.segments;
|
|
if (series.opts.segmentedDataNodes) {
|
|
series.dataNodeGroup.segments = segments ?? series.dataNodeGroup.segments;
|
|
} else {
|
|
series.dataNodeGroup.segments = void 0;
|
|
}
|
|
series.updateNodes(itemHighlighted, resize || dataChanged);
|
|
});
|
|
const animationData = this.getAnimationData(seriesRect, previousContextData);
|
|
if (!animationData)
|
|
return;
|
|
if (resize) {
|
|
this.animationState.transition("resize", animationData);
|
|
}
|
|
this.animationState.transition("update", animationData);
|
|
this.processedDataUpdated = false;
|
|
}
|
|
createStackContext() {
|
|
return void 0;
|
|
}
|
|
// ============================================================================
|
|
// createNodeData() Pattern Helpers
|
|
// ============================================================================
|
|
// These methods support the optimized createNodeData() pattern with incremental updates.
|
|
// See series-performance-optimization.md for detailed documentation.
|
|
/**
|
|
* Determines if incremental node updates are possible based on common criteria.
|
|
*
|
|
* Returns true if existing nodeData exists AND either:
|
|
* - processedData has a changeDescription (partial update), OR
|
|
* - animation is disabled (large dataset)
|
|
*
|
|
* Series may extend with additional conditions by OR'ing additional checks:
|
|
* ```
|
|
* const canIncrementallyUpdate =
|
|
* this.canIncrementallyUpdateNodes() || (existingNodeData && seriesSpecificCondition);
|
|
* ```
|
|
*
|
|
* @param additionalCondition - Optional series-specific condition to OR with base conditions
|
|
*/
|
|
canIncrementallyUpdateNodes(additionalCondition = false) {
|
|
const existingNodeData = this.contextNodeData?.nodeData;
|
|
if (existingNodeData == null)
|
|
return false;
|
|
const { processedData } = this;
|
|
if (!processedData)
|
|
return false;
|
|
return processedData.changeDescription != null || !processedDataIsAnimatable(processedData) || additionalCondition;
|
|
}
|
|
/**
|
|
* Trims the node array after incremental updates.
|
|
*
|
|
* Call at the end of createNodeData() when using incremental updates:
|
|
* ```
|
|
* if (ctx.canIncrementallyUpdate) {
|
|
* this.trimIncrementalNodeArray(ctx.nodes, ctx.nodeIndex);
|
|
* }
|
|
* ```
|
|
*
|
|
* @param nodes - The node array to trim
|
|
* @param nodeIndex - The final write position (new array length)
|
|
*/
|
|
trimIncrementalNodeArray(nodes, nodeIndex) {
|
|
if (nodeIndex < nodes.length) {
|
|
nodes.length = nodeIndex;
|
|
}
|
|
}
|
|
// ============================================================================
|
|
// createNodeData() Template Method Pattern
|
|
// ============================================================================
|
|
// The template method pattern pulls the common createNodeData() flow into the
|
|
// base class. Subclasses implement hooks to customize behaviour.
|
|
//
|
|
// Subclasses that have been migrated implement:
|
|
// - createNodeDatumContext() - cache expensive lookups
|
|
// - populateNodeData() - iterate and create/update datums
|
|
// - initializeResult() - create result object shell
|
|
//
|
|
// And optionally override:
|
|
// - validateCreateNodeDataPreconditions() - custom axis validation
|
|
// - finalizeNodeData() - custom cleanup (multiple arrays, sorting)
|
|
// - assembleResult() - add computed fields (segments)
|
|
/**
|
|
* Template method for creating node data.
|
|
*
|
|
* This implementation provides the common algorithm skeleton:
|
|
* 1. Validate preconditions (axes, dataModel, processedData)
|
|
* 2. Create context with cached values
|
|
* 3. Initialise result object
|
|
* 4. Populate node data (strategy selection inside)
|
|
* 5. Finalize (trim arrays, post-processing)
|
|
* 6. Assemble and return result
|
|
*
|
|
* Subclasses that have been migrated to the template pattern should NOT
|
|
* override this method. Instead, implement the abstract hooks.
|
|
*
|
|
* Subclasses that haven't been migrated yet can override this method entirely.
|
|
*/
|
|
createNodeData() {
|
|
const validation = this.validateCreateNodeDataPreconditions();
|
|
if (!validation)
|
|
return void 0;
|
|
const { xAxis, yAxis } = validation;
|
|
const ctx = this.createNodeDatumContext(xAxis, yAxis);
|
|
if (!ctx)
|
|
return this.getEmptyResult();
|
|
const result = this.initializeResult(ctx);
|
|
if (!this.visible && (this.seriesGrouping == null && !this.opts.animationAlwaysPopulateNodeData || !ctx.animationEnabled))
|
|
return result;
|
|
this.populateNodeData(ctx);
|
|
this.finalizeNodeData(ctx);
|
|
return this.assembleResult(ctx, result);
|
|
}
|
|
// ============================================================================
|
|
// Template Method Hooks (Subclasses MUST implement when NOT overriding createNodeData)
|
|
// ============================================================================
|
|
// Series that override createNodeData() entirely don't need these hooks.
|
|
// Series using the template method MUST implement createNodeDatumContext,
|
|
// populateNodeData, and initializeResult.
|
|
/**
|
|
* Creates the shared context for datum creation/update operations.
|
|
* Called once per createNodeData() invocation.
|
|
*
|
|
* MUST return an object extending CartesianCreateNodeDataContext with:
|
|
* - Cached data arrays (resolved from dataModel)
|
|
* - Cached scales (from axes)
|
|
* - Pre-computed positioning values
|
|
* - Property key lookups
|
|
* - Incremental update state (canIncrementallyUpdate, nodes, nodeIndex)
|
|
*
|
|
* @returns Context object or undefined if context cannot be created
|
|
*/
|
|
createNodeDatumContext(_xAxis, _yAxis) {
|
|
throw new Error(
|
|
`${this.constructor.name}: createNodeDatumContext() must be implemented when using the template method pattern`
|
|
);
|
|
}
|
|
/**
|
|
* Populates the node data array by iterating over data.
|
|
*
|
|
* Strategy selection happens inside this method:
|
|
* - Simple iteration for ungrouped data
|
|
* - Grouped iteration for grouped data
|
|
* - Aggregation iteration for large datasets
|
|
*
|
|
* Implementations should use the upsert pattern:
|
|
* - prepareNodeDatumState() → validate and compute state
|
|
* - upsertNodeDatum() → create or update node in place
|
|
*/
|
|
populateNodeData(_ctx) {
|
|
throw new Error(
|
|
`${this.constructor.name}: populateNodeData() must be implemented when using the template method pattern`
|
|
);
|
|
}
|
|
/**
|
|
* Initializes the result context object that will be returned.
|
|
* Called before populate phase to allow early return for invisible series.
|
|
*
|
|
* Should create the context with:
|
|
* - itemId
|
|
* - nodeData (reference to ctx.nodes)
|
|
* - labelData (reference or separate array)
|
|
* - scales
|
|
* - visible
|
|
* - styles
|
|
*/
|
|
initializeResult(_ctx) {
|
|
throw new Error(
|
|
`${this.constructor.name}: initializeResult() must be implemented when using the template method pattern`
|
|
);
|
|
}
|
|
// ============================================================================
|
|
// Virtual Hooks (Subclasses MAY Override)
|
|
// ============================================================================
|
|
/**
|
|
* Validates preconditions for createNodeData.
|
|
* Default: checks axes, dataModel, and processedData exist.
|
|
*
|
|
* Override for specialized axis requirements (e.g., category vs value axis in bar series).
|
|
*/
|
|
validateCreateNodeDataPreconditions() {
|
|
const xAxis = this.axes["x" /* X */];
|
|
const yAxis = this.axes["y" /* Y */];
|
|
if (!xAxis || !yAxis || !this.dataModel || !this.processedData) {
|
|
return void 0;
|
|
}
|
|
return { xAxis, yAxis };
|
|
}
|
|
/**
|
|
* Returns empty result when context creation fails.
|
|
* Default: returns undefined.
|
|
*/
|
|
getEmptyResult() {
|
|
return void 0;
|
|
}
|
|
/**
|
|
* Finalizes node data after population phase.
|
|
* Default: trims incremental node arrays.
|
|
*
|
|
* Override to add post-processing (sorting, additional cleanup, multiple arrays).
|
|
*/
|
|
finalizeNodeData(ctx) {
|
|
if (ctx.canIncrementallyUpdate) {
|
|
this.trimIncrementalNodeArray(ctx.nodes, ctx.nodeIndex);
|
|
}
|
|
}
|
|
/**
|
|
* Assembles final result from context and initialized result.
|
|
* Default: returns the initialized result unchanged.
|
|
*
|
|
* Override to add computed fields (segments, groupScale).
|
|
*/
|
|
assembleResult(_ctx, result) {
|
|
return result;
|
|
}
|
|
updateSelections() {
|
|
var _a;
|
|
const animationSkipUpdate = !this.opts.animationAlwaysUpdateSelections && this.ctx.animationManager.isSkipped();
|
|
if (!this.visible && animationSkipUpdate) {
|
|
return false;
|
|
}
|
|
const { nodeDataRefresh } = this;
|
|
if (!nodeDataRefresh && !this.isPathOrSelectionDirty()) {
|
|
return false;
|
|
}
|
|
if (nodeDataRefresh) {
|
|
this.nodeDataRefresh = false;
|
|
this.debug(`CartesianSeries.updateSelections() - calling createNodeData() for`, this.id);
|
|
this.markQuadtreeDirty();
|
|
this._contextNodeData = this.createNodeData();
|
|
const animationValid = this.isProcessedDataAnimatable();
|
|
if (this._contextNodeData) {
|
|
(_a = this._contextNodeData).animationValid ?? (_a.animationValid = animationValid);
|
|
const nodeDataSize = this._contextNodeData.nodeData?.length;
|
|
if (nodeDataSize != null) {
|
|
debugMetrics_exports.record(`${this.type}:nodeData`, nodeDataSize);
|
|
}
|
|
}
|
|
const { dataModel, processedData } = this;
|
|
if (dataModel !== void 0 && processedData !== void 0) {
|
|
this.events.emit("data-update", { dataModel, processedData });
|
|
}
|
|
this.updateSeriesSelections();
|
|
}
|
|
return nodeDataRefresh;
|
|
}
|
|
updateSeriesSelections() {
|
|
const { datumSelection, labelSelection, paths } = this;
|
|
const contextData = this._contextNodeData;
|
|
if (!contextData)
|
|
return;
|
|
const { nodeData, labelData, itemId } = contextData;
|
|
this.updatePaths({ itemId, contextData, paths });
|
|
this.datumSelection = this.updateDatumSelection({ nodeData, datumSelection });
|
|
this.labelGroup.batchedUpdate(() => {
|
|
this.labelSelection = this.updateLabelSelection({ labelData, labelSelection }) ?? labelSelection;
|
|
});
|
|
}
|
|
getShapeFillBBox() {
|
|
const { axes } = this;
|
|
const xAxis = axes["x" /* X */];
|
|
const yAxis = axes["y" /* Y */];
|
|
const [axisX1, axisX2] = findMinMax(xAxis?.range ?? [0, 1]);
|
|
const [axisY1, axisY2] = findMinMax(yAxis?.range ?? [0, 1]);
|
|
const xSeriesDomain = extractDomain(this.getSeriesDomain("x" /* X */));
|
|
const xSeriesRange = [xAxis?.scale.convert(xSeriesDomain.at(0)), xAxis?.scale.convert(xSeriesDomain.at(-1))];
|
|
const ySeriesDomain = extractDomain(this.getSeriesDomain("y" /* Y */));
|
|
const ySeriesRange = [yAxis?.scale.convert(ySeriesDomain.at(0)), yAxis?.scale.convert(ySeriesDomain.at(-1))];
|
|
const [seriesX1, seriesX2] = findMinMax(xSeriesRange);
|
|
const [seriesY1, seriesY2] = findMinMax(ySeriesRange);
|
|
return {
|
|
axis: new BBox(axisX1, axisY1, axisX2 - axisX1, axisY2 - axisY1),
|
|
series: new BBox(seriesX1, seriesY1, seriesX2 - seriesX1, seriesY2 - seriesY1)
|
|
};
|
|
}
|
|
updateNodes(itemHighlighted, nodeRefresh) {
|
|
const { highlightSelection, datumSelection } = this;
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
const visible = this.visible && this._contextNodeData != null;
|
|
this.contentGroup.visible = animationEnabled || visible;
|
|
this.highlightGroup.visible = (animationEnabled || visible) && itemHighlighted;
|
|
this.updateDatumStyles({ datumSelection: highlightSelection, isHighlight: true });
|
|
const drawingMode = this.ctx.chartService.highlight?.drawingMode ?? "overlay";
|
|
this.updateDatumNodes({
|
|
datumSelection: highlightSelection,
|
|
isHighlight: true,
|
|
drawingMode
|
|
});
|
|
this.highlightLabelGroup.batchedUpdate(() => {
|
|
this.updateLabelNodes({ labelSelection: this.highlightLabelSelection, isHighlight: true });
|
|
});
|
|
this.animationState.transition("highlight", highlightSelection);
|
|
const { dataNodeGroup, labelSelection, paths, labelGroup } = this;
|
|
const { itemId } = this.contextNodeData ?? {};
|
|
this.updatePathNodes({ itemId, paths, visible, animationEnabled });
|
|
dataNodeGroup.visible = animationEnabled || visible;
|
|
labelGroup.visible = visible;
|
|
if (!dataNodeGroup.visible) {
|
|
return;
|
|
}
|
|
if (this.hasItemStylers()) {
|
|
this.updateDatumStyles({ datumSelection, isHighlight: false });
|
|
}
|
|
const redrawAll = this.strokewidthChange() || this.hasChangesOnHighlight;
|
|
if (nodeRefresh || redrawAll) {
|
|
this.updateDatumNodes({ datumSelection, isHighlight: false, drawingMode: "overlay" });
|
|
if (!this.usesPlacedLabels) {
|
|
this.labelGroup.batchedUpdate(() => {
|
|
this.updateLabelNodes({ labelSelection, isHighlight: false });
|
|
});
|
|
}
|
|
}
|
|
}
|
|
getHighlightData(_nodeData, highlightedItem) {
|
|
return highlightedItem ? [{ ...highlightedItem }] : void 0;
|
|
}
|
|
getHighlightLabelData(labelData, highlightedItem) {
|
|
const labelItems = labelData.filter(
|
|
(ld) => ld.datum === highlightedItem.datum && ld.itemId === highlightedItem.itemId
|
|
);
|
|
return labelItems.length === 0 ? void 0 : labelItems;
|
|
}
|
|
updateHighlightSelection() {
|
|
const { highlightSelection, highlightLabelSelection, _contextNodeData: contextNodeData } = this;
|
|
if (!contextNodeData)
|
|
return false;
|
|
const highlightedDatum = this.ctx.highlightManager?.getActiveHighlight();
|
|
const seriesHighlighted = this.isSeriesHighlighted(highlightedDatum);
|
|
const item = seriesHighlighted && highlightedDatum?.datum ? highlightedDatum : void 0;
|
|
if (item == null)
|
|
return false;
|
|
const { nodeData, labelData } = contextNodeData;
|
|
const highlightItems = this.getHighlightData(nodeData, item);
|
|
this.highlightSelection = this.updateHighlightSelectionItem({
|
|
items: highlightItems,
|
|
highlightSelection
|
|
});
|
|
const highlightLabelItems = this.getHighlightLabelData(labelData, item) ?? [];
|
|
this.highlightLabelSelection = this.updateLabelSelection({
|
|
labelData: highlightLabelItems,
|
|
labelSelection: highlightLabelSelection
|
|
}) ?? highlightLabelSelection;
|
|
return true;
|
|
}
|
|
markQuadtreeDirty() {
|
|
this.quadtree = void 0;
|
|
}
|
|
*datumNodesIter() {
|
|
for (const { node } of this.datumSelection) {
|
|
if (node.datum.missing === true)
|
|
continue;
|
|
yield node;
|
|
}
|
|
}
|
|
getQuadTree() {
|
|
if (this.quadtree === void 0) {
|
|
const canvas = this.ctx.scene?.canvas ?? { width: 0, height: 0 };
|
|
const canvasRect = new BBox(0, 0, canvas.width, canvas.height);
|
|
this.quadtree = new QuadtreeNearest(100, 10, canvasRect);
|
|
this.initQuadTree(this.quadtree);
|
|
}
|
|
return this.quadtree;
|
|
}
|
|
initQuadTree(_quadtree) {
|
|
}
|
|
pickNodeDataExactShape(point) {
|
|
const { x, y } = point;
|
|
const { dataNodeGroup } = this;
|
|
const matches = dataNodeGroup.pickNodes(x, y).filter((match) => match.datum.missing !== true);
|
|
if (matches.length !== 0) {
|
|
const datums = matches.map((match) => match.datum);
|
|
return datums;
|
|
}
|
|
}
|
|
pickModulesExactShape(point) {
|
|
for (const mod of this.moduleMap.modules()) {
|
|
const { datum } = mod.pickNodeExact(point) ?? {};
|
|
if (datum == null)
|
|
continue;
|
|
if (datum?.missing === true)
|
|
continue;
|
|
return [datum];
|
|
}
|
|
}
|
|
pickNodesExactShape(point) {
|
|
const result = super.pickNodesExactShape(point);
|
|
if (result.length !== 0) {
|
|
return result;
|
|
}
|
|
return this.pickNodeDataExactShape(point) ?? this.pickModulesExactShape(point) ?? [];
|
|
}
|
|
pickNodeDataClosestDatum(point) {
|
|
const { x, y } = point;
|
|
const { axes, _contextNodeData: contextNodeData } = this;
|
|
if (!contextNodeData)
|
|
return;
|
|
const xAxis = axes["x" /* X */];
|
|
const yAxis = axes["y" /* Y */];
|
|
const hitPoint = { x, y };
|
|
let minDistanceSquared = Infinity;
|
|
let closestDatum;
|
|
for (const datum of contextNodeData.nodeData) {
|
|
const { point: { x: datumX = Number.NaN, y: datumY = Number.NaN } = {} } = datum;
|
|
if (Number.isNaN(datumX) || Number.isNaN(datumY)) {
|
|
continue;
|
|
}
|
|
const isInRange = xAxis?.inRange(datumX) && yAxis?.inRange(datumY);
|
|
if (!isInRange) {
|
|
continue;
|
|
}
|
|
const distanceSquared2 = Math.max((hitPoint.x - datumX) ** 2 + (hitPoint.y - datumY) ** 2, 0);
|
|
if (distanceSquared2 < minDistanceSquared) {
|
|
minDistanceSquared = distanceSquared2;
|
|
closestDatum = datum;
|
|
}
|
|
}
|
|
if (minDistanceSquared != null) {
|
|
return { datum: closestDatum, distance: Math.sqrt(minDistanceSquared) };
|
|
}
|
|
}
|
|
pickModulesClosestDatum(point) {
|
|
let minDistanceSquared = Infinity;
|
|
let closestDatum;
|
|
for (const mod of this.moduleMap.modules()) {
|
|
const modPick = mod.pickNodeNearest(point);
|
|
if (modPick !== void 0 && modPick.distanceSquared < minDistanceSquared) {
|
|
minDistanceSquared = modPick.distanceSquared;
|
|
closestDatum = modPick.datum;
|
|
}
|
|
}
|
|
if (minDistanceSquared != null) {
|
|
return { datum: closestDatum, distance: Math.sqrt(minDistanceSquared) };
|
|
}
|
|
}
|
|
pickNodeClosestDatum(point) {
|
|
let minDistance = Infinity;
|
|
let closestDatum;
|
|
const pick2 = this.pickNodeDataClosestDatum(point);
|
|
if (pick2 != null && pick2.distance < minDistance) {
|
|
minDistance = pick2.distance;
|
|
closestDatum = pick2.datum;
|
|
}
|
|
const modPick = this.pickModulesClosestDatum(point);
|
|
if (modPick != null && modPick.distance < minDistance) {
|
|
minDistance = modPick.distance;
|
|
closestDatum = modPick.datum;
|
|
}
|
|
if (closestDatum) {
|
|
const distance2 = Math.max(minDistance - (closestDatum.point?.size ?? 0) / 2, 0);
|
|
return { datum: closestDatum, distance: distance2 };
|
|
}
|
|
}
|
|
pickNodeMainAxisFirst(point, requireCategoryAxis) {
|
|
const { x, y } = point;
|
|
const { axes, _contextNodeData: contextNodeData } = this;
|
|
const { pickOutsideVisibleMinorAxis } = this.properties;
|
|
if (!contextNodeData)
|
|
return;
|
|
const xAxis = axes["x" /* X */];
|
|
const yAxis = axes["y" /* Y */];
|
|
if (xAxis == null || yAxis == null)
|
|
return;
|
|
const directions2 = [xAxis, yAxis].filter((axis) => axis.isCategoryLike()).map((a) => a.direction);
|
|
if (requireCategoryAxis && directions2.length === 0)
|
|
return;
|
|
const [majorDirection = "x" /* X */] = directions2;
|
|
const hitPointCoords = [x, y];
|
|
if (majorDirection !== "x" /* X */)
|
|
hitPointCoords.reverse();
|
|
const minDistance = [Infinity, Infinity];
|
|
let closestDatum;
|
|
for (const datum of contextNodeData.nodeData) {
|
|
const { x: datumX = Number.NaN, y: datumY = Number.NaN } = datum.point ?? datum.midPoint ?? {};
|
|
if (Number.isNaN(datumX) || Number.isNaN(datumY) || datum.missing === true)
|
|
continue;
|
|
const visible = [xAxis?.inRange(datumX, 1), yAxis?.inRange(datumY, 1)];
|
|
if (majorDirection !== "x" /* X */) {
|
|
visible.reverse();
|
|
}
|
|
if (!visible[0] || !pickOutsideVisibleMinorAxis && !visible[1])
|
|
continue;
|
|
const datumPoint = [datumX, datumY];
|
|
if (majorDirection !== "x" /* X */) {
|
|
datumPoint.reverse();
|
|
}
|
|
let newMinDistance = true;
|
|
for (let i = 0; i < datumPoint.length; i++) {
|
|
const dist = Math.abs(datumPoint[i] - hitPointCoords[i]);
|
|
if (dist > minDistance[i]) {
|
|
newMinDistance = false;
|
|
break;
|
|
} else if (dist < minDistance[i]) {
|
|
minDistance[i] = dist;
|
|
minDistance.fill(Infinity, i + 1, minDistance.length);
|
|
}
|
|
}
|
|
if (newMinDistance) {
|
|
closestDatum = datum;
|
|
}
|
|
}
|
|
if (closestDatum) {
|
|
let closestDistanceSquared = Math.max(
|
|
minDistance[0] ** 2 + minDistance[1] ** 2 - (closestDatum.point?.size ?? 0),
|
|
0
|
|
);
|
|
for (const mod of this.moduleMap.modules()) {
|
|
const modPick = mod.pickNodeMainAxisFirst(point, majorDirection);
|
|
if (modPick != null && modPick.distanceSquared < closestDistanceSquared) {
|
|
closestDatum = modPick.datum;
|
|
closestDistanceSquared = modPick.distanceSquared;
|
|
break;
|
|
}
|
|
}
|
|
return {
|
|
datum: closestDatum,
|
|
distance: Math.sqrt(closestDistanceSquared)
|
|
};
|
|
}
|
|
}
|
|
isPathOrSelectionDirty() {
|
|
return false;
|
|
}
|
|
shouldFlipXY() {
|
|
return false;
|
|
}
|
|
visibleRangeIndices(axisKey, visibleRange, indices, sortOrderParams) {
|
|
let sortOrder;
|
|
if (sortOrderParams == null) {
|
|
const { processedData, dataModel } = this;
|
|
sortOrder = dataModel.getColumnSortOrder(this, axisKey, processedData) ?? 1;
|
|
} else {
|
|
sortOrder = sortOrderParams.sortOrder;
|
|
}
|
|
const xValues = this.keysOrValues(axisKey);
|
|
const pixelSize = 0;
|
|
const [start2, end3] = visibleRangeIndices(
|
|
sortOrder,
|
|
indices?.length ?? xValues.length,
|
|
visibleRange,
|
|
(topIndex) => {
|
|
const datumIndex = indices?.[topIndex] ?? topIndex;
|
|
return this.xCoordinateRange(xValues[datumIndex], pixelSize, datumIndex);
|
|
}
|
|
);
|
|
return start2 < end3 ? [start2, end3] : [end3, start2];
|
|
}
|
|
domainForVisibleRange(_direction, axisKeys, crossAxisKey, visibleRange, indices) {
|
|
const { processedData, dataModel } = this;
|
|
const [r0, r1] = visibleRange;
|
|
const crossAxisValues = this.keysOrValues(crossAxisKey);
|
|
const sortOrder = this.sortOrder(crossAxisKey);
|
|
if (sortOrder != null) {
|
|
const crossAxisRange = this.visibleRangeIndices(crossAxisKey, visibleRange, indices, { sortOrder });
|
|
return dataModel.getDomainBetweenRange(this, axisKeys, crossAxisRange, processedData);
|
|
}
|
|
const allAxisValues = axisKeys.map((axisKey) => this.keysOrValues(axisKey));
|
|
let axisMin = Infinity;
|
|
let axisMax = -Infinity;
|
|
for (const [i, crossAxisValue] of crossAxisValues.entries()) {
|
|
const [x0, x1] = this.xCoordinateRange(crossAxisValue, 0, i);
|
|
if (x1 < r0 || x0 > r1)
|
|
continue;
|
|
for (let j = 0; j < axisKeys.length; j++) {
|
|
const axisValue = allAxisValues[j][i];
|
|
axisMin = Math.min(axisMin, axisValue);
|
|
axisMax = Math.max(axisMax, axisValue);
|
|
}
|
|
}
|
|
return axisMin > axisMax ? [Number.NaN, Number.NaN] : [axisMin, axisMax];
|
|
}
|
|
domainForClippedRange(direction, axisKeys, crossAxisKey) {
|
|
const { processedData, dataModel, axes } = this;
|
|
const crossDirection = direction === "x" /* X */ ? "y" /* Y */ : "x" /* X */;
|
|
const crossAxisRange = axisExtent(axes[crossDirection]);
|
|
if (!crossAxisRange) {
|
|
return axisKeys.flatMap((axisKey) => dataModel.getDomain(this, axisKey, "value", processedData).domain);
|
|
}
|
|
const crossAxisValues = this.keysOrValues(crossAxisKey);
|
|
const sortOrder = dataModel.getColumnSortOrder(this, crossAxisKey, processedData);
|
|
if (sortOrder != null) {
|
|
const crossRange = clippedRangeIndices(
|
|
sortOrder,
|
|
crossAxisValues.length,
|
|
crossAxisRange,
|
|
(index) => crossAxisValues[index]
|
|
);
|
|
return dataModel.getDomainBetweenRange(this, axisKeys, crossRange, processedData);
|
|
}
|
|
const allAxisValues = axisKeys.map((axisKey) => this.keysOrValues(axisKey));
|
|
const range0 = crossAxisRange[0].valueOf();
|
|
const range1 = crossAxisRange[1].valueOf();
|
|
const axisValues = [];
|
|
for (const [i, crossAxisValue] of crossAxisValues.entries()) {
|
|
const c = crossAxisValue.valueOf();
|
|
if (c < range0 || c > range1)
|
|
continue;
|
|
const values = allAxisValues.map((v) => v[i]);
|
|
if (c >= range0) {
|
|
axisValues.push(...values);
|
|
}
|
|
if (c <= range1) {
|
|
axisValues.push(...values);
|
|
}
|
|
}
|
|
return axisValues;
|
|
}
|
|
zoomFittingVisibleItems(crossAxisKey, _axisKeys, xVisibleRange, yVisibleRange, minVisibleItems) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData)
|
|
return;
|
|
const crossAxis = this.axes["x" /* X */];
|
|
if (yVisibleRange != null)
|
|
return;
|
|
const sortOrder = this.sortOrder(crossAxisKey);
|
|
if (sortOrder == null)
|
|
return;
|
|
const xValues = this.keysOrValues(crossAxisKey);
|
|
if (minVisibleItems > xValues.length) {
|
|
return { x: [0, 1], y: void 0 };
|
|
}
|
|
const crossScale = crossAxis.scale;
|
|
const crossScaleRange = crossScale.range;
|
|
crossScale.range = [0, 1];
|
|
let [r0, r1] = this.visibleRangeIndices(crossAxisKey, xVisibleRange, void 0, { sortOrder });
|
|
r1 -= 1;
|
|
const pixelSize = 0;
|
|
if (this.xCoordinateRange(xValues[r0], pixelSize, r0)[0] < xVisibleRange[0]) {
|
|
r0 += 1;
|
|
}
|
|
if (this.xCoordinateRange(xValues[r1], pixelSize, r1)[1] > xVisibleRange[1]) {
|
|
r1 -= 1;
|
|
}
|
|
let xZoom;
|
|
if (Math.abs(r1 - r0) >= minVisibleItems - 1) {
|
|
xZoom = xVisibleRange;
|
|
} else {
|
|
const midPoint = (xVisibleRange[0] + xVisibleRange[1]) / 2;
|
|
while (Math.abs(r1 - r0) < minVisibleItems - 1 && (r0 > 0 || r1 < xValues.length - 1)) {
|
|
if (r0 === 0) {
|
|
r1 += 1;
|
|
} else if (r1 === xValues.length - 1) {
|
|
r0 -= 1;
|
|
} else {
|
|
const nextR0X = this.xCoordinateRange(xValues[r0 - 1], pixelSize, r0 - 1)[0];
|
|
const nextR1X = this.xCoordinateRange(xValues[r1 + 1], pixelSize, r1 + 1)[1];
|
|
if (Math.abs(nextR0X - midPoint) < Math.abs(nextR1X - midPoint)) {
|
|
r0 -= 1;
|
|
} else {
|
|
r1 += 1;
|
|
}
|
|
}
|
|
}
|
|
const x0 = this.xCoordinateRange(xValues[r0], pixelSize, r0)[0];
|
|
const x1 = this.xCoordinateRange(xValues[r1], pixelSize, r1)[1];
|
|
xZoom = [Math.min(xVisibleRange[0], x0), Math.max(xVisibleRange[1], x1)];
|
|
}
|
|
crossScale.range = crossScaleRange;
|
|
return { x: xZoom, y: void 0 };
|
|
}
|
|
countVisibleItems(crossAxisKey, axisKeys, xVisibleRange, yVisibleRange, minVisibleItems) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData)
|
|
return Infinity;
|
|
const crossValues = this.keysOrValues(crossAxisKey);
|
|
const allAxisValues = axisKeys.map((axisKey) => dataModel.resolveColumnById(this, axisKey, processedData));
|
|
const shouldFlipXY = this.shouldFlipXY();
|
|
const crossAxis = shouldFlipXY ? this.axes["y" /* Y */] : this.axes["x" /* X */];
|
|
const axis = shouldFlipXY ? this.axes["x" /* X */] : this.axes["y" /* Y */];
|
|
const crossVisibleRange = shouldFlipXY ? yVisibleRange ?? [0, 1] : xVisibleRange;
|
|
const axisVisibleRange = shouldFlipXY ? xVisibleRange : yVisibleRange ?? [0, 1];
|
|
if (yVisibleRange == null) {
|
|
const sortOrder = this.sortOrder(crossAxisKey);
|
|
if (sortOrder != null) {
|
|
const crossScale = crossAxis.scale;
|
|
const crossScaleRange = crossScale.range;
|
|
crossScale.range = [0, 1];
|
|
const xValues = this.keysOrValues(crossAxisKey);
|
|
let [r0, r1] = this.visibleRangeIndices(crossAxisKey, crossVisibleRange, void 0, { sortOrder });
|
|
r1 -= 1;
|
|
if (r1 < r0)
|
|
return 0;
|
|
const pixelSize2 = 0;
|
|
if (this.xCoordinateRange(xValues[r0], pixelSize2, r0)[0] < crossVisibleRange[0]) {
|
|
r0 += 1;
|
|
}
|
|
if (this.xCoordinateRange(xValues[r1], pixelSize2, r1)[1] > crossVisibleRange[1]) {
|
|
r1 -= 1;
|
|
}
|
|
const xItemsVisible = Math.abs(r1 - r0) + 1;
|
|
crossScale.range = crossScaleRange;
|
|
return xItemsVisible;
|
|
}
|
|
}
|
|
const convert2 = (d, r, v) => {
|
|
return d[0] + (v - r[0]) / (r[1] - r[0]) * (d[1] - d[0]);
|
|
};
|
|
const crossAxisRange = crossAxis.range.toSorted();
|
|
const axisRange = axis.range.toSorted();
|
|
const crossMin = convert2(crossAxisRange, crossAxis.visibleRange, crossVisibleRange[0]);
|
|
const crossMax = convert2(crossAxisRange, crossAxis.visibleRange, crossVisibleRange[1]);
|
|
const axisMin = convert2(axisRange, axis.visibleRange, Math.min(...axisVisibleRange));
|
|
const axisMax = convert2(axisRange, axis.visibleRange, Math.max(...axisVisibleRange));
|
|
const startIndex = Math.round(
|
|
(crossVisibleRange[0] + (crossVisibleRange[1] - crossVisibleRange[0]) / 2) * crossValues.length
|
|
);
|
|
const pixelSize = 0;
|
|
return countExpandingSearch(0, crossValues.length - 1, startIndex, minVisibleItems, (index) => {
|
|
const [cross0, cross1] = this.xCoordinateRange(crossValues[index], pixelSize, index);
|
|
const [axis0, axis1] = this.yCoordinateRange(
|
|
allAxisValues.map((axisValues) => axisValues[index]),
|
|
pixelSize,
|
|
index
|
|
);
|
|
if (!isFiniteNumber(cross0) || !isFiniteNumber(cross1) || !isFiniteNumber(axis0) || !isFiniteNumber(axis1)) {
|
|
return false;
|
|
}
|
|
return cross0 >= crossMin && cross1 <= crossMax && axis0 >= axisMin && axis1 <= axisMax;
|
|
});
|
|
}
|
|
// @todo(AG-13777) - Remove this function.
|
|
// We need data model updates to know if a data set is sorted & unique - and at the same time
|
|
// it should generate the equivalent of `SMALLEST_KEY_INTERVAL`. We'll use that value here
|
|
minTimeInterval() {
|
|
let xValues;
|
|
try {
|
|
xValues = this.keysOrValues("xValue");
|
|
} catch {
|
|
}
|
|
if (xValues == null || xValues.length > 1e3)
|
|
return;
|
|
let minInterval = Infinity;
|
|
let x0 = xValues[0];
|
|
let sortOrder;
|
|
for (let i = 1; i < xValues.length; i++) {
|
|
const x1 = xValues[i];
|
|
if (x1 != null && x0 != null) {
|
|
const interval = x1.valueOf() - x0.valueOf();
|
|
const sign = Math.sign(interval);
|
|
if (sign === 0)
|
|
continue;
|
|
if (sortOrder !== void 0 && sign !== sortOrder)
|
|
return;
|
|
minInterval = Math.min(minInterval, Math.abs(interval));
|
|
sortOrder = sign;
|
|
}
|
|
x0 = x1;
|
|
}
|
|
if (Number.isFinite(minInterval))
|
|
return minInterval;
|
|
}
|
|
updateHighlightSelectionItem(opts) {
|
|
const { items, highlightSelection } = opts;
|
|
const nodeData = items ?? [];
|
|
return this.updateDatumSelection({
|
|
nodeData,
|
|
datumSelection: highlightSelection
|
|
});
|
|
}
|
|
updateDatumSelection(opts) {
|
|
return opts.datumSelection;
|
|
}
|
|
updateDatumNodes(_opts) {
|
|
}
|
|
updateDatumStyles(_opts) {
|
|
}
|
|
updatePaths(opts) {
|
|
for (const p of opts.paths) {
|
|
p.visible = false;
|
|
}
|
|
}
|
|
updatePathNodes(opts) {
|
|
const { paths, visible } = opts;
|
|
for (const path of paths) {
|
|
path.visible = visible;
|
|
}
|
|
}
|
|
resetPathAnimation(data) {
|
|
const { path } = this.opts?.animationResetFns ?? {};
|
|
if (path) {
|
|
for (const paths of data.paths) {
|
|
resetMotion([paths], path);
|
|
}
|
|
}
|
|
}
|
|
resetDatumAnimation(data) {
|
|
const { datum } = this.opts?.animationResetFns ?? {};
|
|
if (datum) {
|
|
resetMotion([data.datumSelection], datum);
|
|
}
|
|
}
|
|
resetLabelAnimation(data) {
|
|
const { label } = this.opts?.animationResetFns ?? {};
|
|
if (label) {
|
|
resetMotion([data.labelSelection], label);
|
|
}
|
|
}
|
|
resetAllAnimation(data) {
|
|
this.ctx.animationManager.stopByAnimationGroupId(this.id);
|
|
this.resetPathAnimation(data);
|
|
this.resetDatumAnimation(data);
|
|
this.resetLabelAnimation(data);
|
|
if (data.contextData?.animationValid === false) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
}
|
|
}
|
|
animateEmptyUpdateReady(data) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
this.resetAllAnimation(data);
|
|
}
|
|
animateWaitingUpdateReady(data) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
this.resetAllAnimation(data);
|
|
}
|
|
animateReadyHighlight(data) {
|
|
const { datum } = this.opts?.animationResetFns ?? {};
|
|
if (datum) {
|
|
resetMotion([data], datum);
|
|
}
|
|
}
|
|
animateReadyResize(data) {
|
|
this.resetAllAnimation(data);
|
|
}
|
|
animateClearingUpdateEmpty(data) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
this.resetAllAnimation(data);
|
|
}
|
|
getAnimationData(seriesRect, previousContextData) {
|
|
const { _contextNodeData: contextData } = this;
|
|
if (!contextData)
|
|
return;
|
|
const animationData = {
|
|
datumSelection: this.datumSelection,
|
|
labelSelection: this.labelSelection,
|
|
annotationSelections: [...this.annotationSelections],
|
|
contextData,
|
|
previousContextData,
|
|
paths: this.paths,
|
|
seriesRect
|
|
};
|
|
return animationData;
|
|
}
|
|
updateLabelSelection(opts) {
|
|
return opts.labelSelection;
|
|
}
|
|
getScaling(scale2) {
|
|
if (scale2 instanceof LogScale) {
|
|
const { range: range3, domain } = scale2;
|
|
return {
|
|
type: "log",
|
|
convert: (d) => scale2.convert(d),
|
|
domain: [domain[0], domain[1]],
|
|
range: [range3[0], range3[1]]
|
|
};
|
|
} else if (scale2 instanceof ContinuousScale) {
|
|
const { range: range3, domain } = scale2;
|
|
return {
|
|
type: "continuous",
|
|
domain: [domain[0], domain[1]],
|
|
range: [range3[0], range3[1]]
|
|
};
|
|
} else if (scale2 instanceof BandScale) {
|
|
if (scale2 instanceof UnitTimeScale) {
|
|
const linearParams = scale2.getLinearParams();
|
|
const bandCount = scale2.getBandCountForUpdate();
|
|
if (linearParams != null && bandCount > 0) {
|
|
return {
|
|
type: "category",
|
|
variant: "unit-time",
|
|
firstBandTime: linearParams.firstBandTime,
|
|
lastBandTime: linearParams.firstBandTime + (bandCount - 1) * linearParams.intervalMs,
|
|
bandCount,
|
|
intervalMs: linearParams.intervalMs,
|
|
inset: scale2.inset,
|
|
step: scale2.step
|
|
};
|
|
}
|
|
}
|
|
return {
|
|
type: "category",
|
|
domain: scale2.domain,
|
|
inset: scale2.inset,
|
|
step: scale2.step
|
|
};
|
|
}
|
|
}
|
|
calculateScaling() {
|
|
const result = {};
|
|
for (const direction of Object.values(ChartAxisDirection)) {
|
|
const axis = this.axes[direction];
|
|
if (!axis)
|
|
continue;
|
|
const scalingResult = this.getScaling(axis.scale);
|
|
if (scalingResult != null) {
|
|
result[direction] = scalingResult;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
};
|
|
function axisExtent(axis) {
|
|
let min;
|
|
let max;
|
|
if (axis instanceof NumberAxis || axis instanceof TimeAxis) {
|
|
({ min, max } = axis);
|
|
}
|
|
if (min == null && max == null)
|
|
return;
|
|
min ?? (min = -Infinity);
|
|
max ?? (max = Infinity);
|
|
return [min, max];
|
|
}
|
|
function clippedRangeIndices(sortOrder, length2, range3, xValue) {
|
|
const range0 = range3[0].valueOf();
|
|
const range1 = range3[1].valueOf();
|
|
let xMinIndex = findMinIndex(0, length2 - 1, (i) => {
|
|
const index = sortOrder === 1 ? i : length2 - i;
|
|
const x = xValue(index)?.valueOf();
|
|
return !Number.isFinite(x) || x >= range0;
|
|
});
|
|
let xMaxIndex = findMaxIndex(0, length2 - 1, (i) => {
|
|
const index = sortOrder === 1 ? i : length2 - i;
|
|
const x = xValue(index)?.valueOf();
|
|
return !Number.isFinite(x) || x <= range1;
|
|
});
|
|
if (xMinIndex == null || xMaxIndex == null)
|
|
return [0, 0];
|
|
if (sortOrder === -1) {
|
|
[xMinIndex, xMaxIndex] = [length2 - xMaxIndex, length2 - xMinIndex];
|
|
}
|
|
xMinIndex = Math.max(xMinIndex, 0);
|
|
xMaxIndex = Math.min(xMaxIndex + 1, length2);
|
|
return [xMinIndex, xMaxIndex];
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/cartesianUtil.ts
|
|
function stackCartesianSeries(series) {
|
|
const seriesGroups = /* @__PURE__ */ new Map();
|
|
for (const s of series) {
|
|
if (!(s instanceof CartesianSeries))
|
|
continue;
|
|
const stackCount = s.seriesGrouping?.stackCount ?? 0;
|
|
const groupIndex = stackCount > 0 ? s.seriesGrouping?.groupIndex : void 0;
|
|
if (groupIndex == null) {
|
|
s.seriesBelowStackContext = void 0;
|
|
s.createStackContext();
|
|
continue;
|
|
}
|
|
const groupKey = `${s.type}-${groupIndex}`;
|
|
let group = seriesGroups.get(groupKey);
|
|
if (group == null) {
|
|
group = [];
|
|
seriesGroups.set(groupKey, group);
|
|
}
|
|
group.push(s);
|
|
}
|
|
for (const group of seriesGroups.values()) {
|
|
group.sort((a, b) => (a.seriesGrouping?.stackIndex ?? 0) - (b.seriesGrouping?.stackIndex ?? 0));
|
|
let seriesBelowStackContext;
|
|
for (const s of group) {
|
|
s.seriesBelowStackContext = seriesBelowStackContext;
|
|
seriesBelowStackContext = s.createStackContext();
|
|
}
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/labelUtil.ts
|
|
function getLabelStyles(series, nodeDatum, params, label, isHighlight, activeHighlight, labelPath = ["series", `${series.declarationOrder}`, "label"]) {
|
|
if (series.visible && label.itemStyler) {
|
|
const highlightState = series.getHighlightStateString(
|
|
activeHighlight,
|
|
isHighlight || nodeDatum != null && activeHighlight?.series === nodeDatum.series && activeHighlight?.datumIndex === nodeDatum.datumIndex,
|
|
nodeDatum?.datumIndex
|
|
);
|
|
const itemId = typeof nodeDatum?.datumIndex === "number" ? nodeDatum.datumIndex : nodeDatum?.itemId;
|
|
const styleParams = {
|
|
border: label.border,
|
|
color: label.color,
|
|
cornerRadius: label.cornerRadius,
|
|
datum: nodeDatum?.datum,
|
|
enabled: label.enabled,
|
|
fill: label.fill,
|
|
fillOpacity: label.fillOpacity,
|
|
fontFamily: label.fontFamily,
|
|
fontSize: label.fontSize,
|
|
fontStyle: label.fontStyle,
|
|
fontWeight: label.fontWeight,
|
|
itemId,
|
|
itemType: nodeDatum?.itemType,
|
|
seriesId: series.id,
|
|
padding: label.padding,
|
|
highlightState
|
|
};
|
|
const stylerResult = series.ctx.optionsGraphService.resolvePartial(
|
|
labelPath,
|
|
series.cachedCallWithContext(label.itemStyler, { ...params, ...styleParams }),
|
|
{ pick: false }
|
|
) ?? {};
|
|
return mergeDefaults(stylerResult, styleParams);
|
|
}
|
|
return label;
|
|
}
|
|
function updateLabelNode(series, textNode, params, label, labelDatum, isHighlight, activeHighlight) {
|
|
if (series.visible && label.enabled && labelDatum) {
|
|
const style2 = getLabelStyles(series, textNode.datum, params, label, isHighlight, activeHighlight);
|
|
textNode.visible = true;
|
|
textNode.x = labelDatum.x;
|
|
textNode.y = labelDatum.y;
|
|
textNode.text = labelDatum.text;
|
|
textNode.fill = style2.color;
|
|
textNode.setAlign(labelDatum);
|
|
textNode.setFont(style2);
|
|
textNode.setBoxing(style2);
|
|
} else {
|
|
textNode.visible = false;
|
|
}
|
|
}
|
|
var placements = {
|
|
"inside-start": { inside: true, direction: -1, textAlignment: 1 },
|
|
"inside-end": { inside: true, direction: 1, textAlignment: -1 },
|
|
"outside-start": { inside: false, direction: -1, textAlignment: -1 },
|
|
"outside-end": { inside: false, direction: 1, textAlignment: 1 }
|
|
};
|
|
function adjustLabelPlacement({
|
|
isUpward,
|
|
isVertical,
|
|
placement,
|
|
spacing = 0,
|
|
rect: rect2
|
|
}) {
|
|
let x = rect2.x + rect2.width / 2;
|
|
let y = rect2.y + rect2.height / 2;
|
|
let textAlign = "center";
|
|
let textBaseline = "middle";
|
|
if (placement !== "inside-center") {
|
|
const barDirection = (isUpward ? 1 : -1) * (isVertical ? -1 : 1);
|
|
const { direction, textAlignment } = placements[placement];
|
|
const displacementRatio = (direction + 1) * 0.5;
|
|
if (isVertical) {
|
|
const y0 = isUpward ? rect2.y + rect2.height : rect2.y;
|
|
const height2 = rect2.height * barDirection;
|
|
y = y0 + height2 * displacementRatio + spacing * textAlignment * barDirection;
|
|
textBaseline = textAlignment === barDirection ? "top" : "bottom";
|
|
} else {
|
|
const x0 = isUpward ? rect2.x : rect2.x + rect2.width;
|
|
const width2 = rect2.width * barDirection;
|
|
x = x0 + width2 * displacementRatio + spacing * textAlignment * barDirection;
|
|
textAlign = textAlignment === barDirection ? "left" : "right";
|
|
}
|
|
}
|
|
return { x, y, textAlign, textBaseline };
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/seriesLabelUtil.ts
|
|
function seriesLabelFadeInAnimation({ id }, subId, animationManager, ...labelSelections) {
|
|
for (const labelSelection of labelSelections) {
|
|
labelSelection.cleanup();
|
|
}
|
|
staticFromToMotion(
|
|
id,
|
|
subId,
|
|
animationManager,
|
|
labelSelections,
|
|
{ opacity: 0 },
|
|
{ opacity: 1 },
|
|
{ phase: "trailing" }
|
|
);
|
|
}
|
|
function seriesLabelFadeOutAnimation({ id }, subId, animationManager, ...labelSelections) {
|
|
staticFromToMotion(
|
|
id,
|
|
subId,
|
|
animationManager,
|
|
labelSelections,
|
|
{ opacity: 1 },
|
|
{ opacity: 0 },
|
|
{ phase: "remove" }
|
|
);
|
|
}
|
|
function resetLabelFn(_node) {
|
|
return { opacity: 1 };
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/seriesMarker.ts
|
|
var SeriesMarker = class extends ChangeDetectableProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = true;
|
|
this.shape = "circle";
|
|
this.size = 0;
|
|
this.fillOpacity = 1;
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
}
|
|
getStyle() {
|
|
const { size, shape, fill, fillOpacity, stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = this;
|
|
return {
|
|
size,
|
|
shape,
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
};
|
|
}
|
|
getDiameter() {
|
|
return this.size + this.strokeWidth;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], SeriesMarker.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneObjectChangeDetection({ equals: TRIPLE_EQ })
|
|
], SeriesMarker.prototype, "shape", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], SeriesMarker.prototype, "size", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneObjectChangeDetection({ equals: objectsEqual })
|
|
], SeriesMarker.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], SeriesMarker.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], SeriesMarker.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], SeriesMarker.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], SeriesMarker.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesMarker.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesMarker.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneObjectChangeDetection({ equals: TRIPLE_EQ })
|
|
], SeriesMarker.prototype, "itemStyler", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/series/seriesTooltip.ts
|
|
function buildLineWithMarkerDefaults(line, marker) {
|
|
if (line == null)
|
|
return void 0;
|
|
return {
|
|
enabled: line.enabled ?? true,
|
|
stroke: line.stroke ?? marker?.stroke ?? "transparent",
|
|
strokeWidth: line.strokeWidth ?? marker?.strokeWidth ?? 1,
|
|
strokeOpacity: line.strokeOpacity ?? marker?.strokeOpacity ?? 1,
|
|
lineDash: line.lineDash ?? marker?.lineDash ?? []
|
|
};
|
|
}
|
|
var SeriesTooltipInteraction = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesTooltipInteraction.prototype, "enabled", 2);
|
|
var SeriesTooltip = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.interaction = new SeriesTooltipInteraction();
|
|
this.position = new TooltipPosition();
|
|
this.range = void 0;
|
|
this.class = void 0;
|
|
}
|
|
formatTooltip(callers, content, params) {
|
|
const overrides = this.renderer == null ? void 0 : callWithContext(callers, this.renderer, params);
|
|
if (isString(overrides) || isNumber(overrides) || isDate(overrides)) {
|
|
return { type: "raw", rawHtmlString: toTextString(overrides) };
|
|
}
|
|
if (overrides != null) {
|
|
const mergedMarker = mergeDefaults(overrides.symbol?.marker, content.symbol?.marker);
|
|
const mergedLineInput = overrides.symbol?.line ?? content.symbol?.line ? mergeDefaults(overrides.symbol?.line, content.symbol?.line) : void 0;
|
|
const symbol = content.symbol || overrides.symbol ? {
|
|
marker: mergedMarker,
|
|
line: buildLineWithMarkerDefaults(mergedLineInput, mergedMarker)
|
|
} : void 0;
|
|
return { type: "structured", ...content, ...overrides, symbol };
|
|
}
|
|
return { type: "structured", ...content };
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesTooltip.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesTooltip.prototype, "showArrow", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesTooltip.prototype, "renderer", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesTooltip.prototype, "interaction", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesTooltip.prototype, "position", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesTooltip.prototype, "range", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SeriesTooltip.prototype, "class", 2);
|
|
function makeSeriesTooltip() {
|
|
return new SeriesTooltip();
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/axis/categoryAxis.ts
|
|
var _CategoryAxis = class _CategoryAxis extends CartesianAxis {
|
|
constructor(moduleCtx, scale2 = new CategoryScale(), includeInvisibleDomains = true) {
|
|
super(moduleCtx, scale2);
|
|
this.groupPaddingInner = 0.1;
|
|
this.includeInvisibleDomains = includeInvisibleDomains;
|
|
this.nice = false;
|
|
}
|
|
static is(value) {
|
|
return value instanceof _CategoryAxis;
|
|
}
|
|
isCategoryLike() {
|
|
return true;
|
|
}
|
|
hasDefinedDomain() {
|
|
return false;
|
|
}
|
|
normaliseDataDomain(d) {
|
|
return { domain: d.domain, clipped: false };
|
|
}
|
|
getUpdateTypeOnResize() {
|
|
if (this.bandAlignment == null || this.bandAlignment === "justify") {
|
|
return super.getUpdateTypeOnResize();
|
|
}
|
|
return 3 /* PROCESS_DOMAIN */;
|
|
}
|
|
updateScale() {
|
|
super.updateScale();
|
|
let { paddingInner, paddingOuter } = this;
|
|
if (!isFiniteNumber(paddingInner) || !isFiniteNumber(paddingOuter)) {
|
|
const padding2 = this.reduceBandScalePadding();
|
|
paddingInner ?? (paddingInner = padding2.inner);
|
|
paddingOuter ?? (paddingOuter = padding2.outer);
|
|
}
|
|
this.scale.paddingInner = paddingInner ?? 0;
|
|
this.scale.paddingOuter = paddingOuter ?? 0;
|
|
}
|
|
calculateGridLines(ticks, p1, p2) {
|
|
const gridLines = super.calculateGridLines(ticks, p1, p2);
|
|
if (this.interval.placement === "between" && ticks.length > 0) {
|
|
gridLines.push(
|
|
super.calculateGridLine(
|
|
{
|
|
index: ticks.at(-1).index + 1,
|
|
tickId: `after:${ticks.at(-1).tickId}`,
|
|
translation: this.range[1]
|
|
},
|
|
ticks.length,
|
|
p1,
|
|
p2,
|
|
ticks
|
|
)
|
|
);
|
|
}
|
|
return gridLines;
|
|
}
|
|
calculateGridLine({ index: tickIndex, tickId, translation }, index, p1, p2, ticks) {
|
|
const { gridLine, horizontal, interval, scale: scale2 } = this;
|
|
if (interval.placement !== "between") {
|
|
return super.calculateGridLine({ index: tickIndex, tickId, translation }, index, p1, p2, ticks);
|
|
}
|
|
const halfStep = translation < scale2.step ? Math.floor(scale2.step / 2) : scale2.step / 2;
|
|
const offset = translation - halfStep;
|
|
const [x1, y1, x2, y2] = horizontal ? [offset, Math.max(p1, p2), offset, Math.min(p1, p2)] : [Math.min(p1, p2), offset, Math.max(p1, p2), offset];
|
|
const { style: style2 } = gridLine;
|
|
const { stroke: stroke3, strokeWidth = 0, lineDash } = style2[tickIndex % style2.length] ?? {};
|
|
return { tickId, offset, x1, y1, x2, y2, stroke: stroke3, strokeWidth, lineDash };
|
|
}
|
|
calculateGridFills(ticks, p1, p2) {
|
|
const { horizontal, range: range3, scale: scale2 } = this;
|
|
if (this.interval.placement !== "between") {
|
|
return super.calculateGridFills(ticks, p1, p2);
|
|
}
|
|
const gridFills = [];
|
|
if (ticks.length == 0)
|
|
return gridFills;
|
|
const firstTick = ticks[0];
|
|
const firstFillOffCanvas = firstTick.translation > range3[0] + scale2.step / 2;
|
|
const lastTick = ticks.at(-1);
|
|
const lastFillOffCanvas = horizontal && lastTick.translation < range3[1] - scale2.step / 2;
|
|
if (firstFillOffCanvas) {
|
|
const tick = { tickId: `before:${firstTick.tickId}`, translation: firstTick.translation - scale2.step };
|
|
gridFills.push(this.calculateGridFill(tick, -1, firstTick.index - 1, p1, p2, ticks));
|
|
}
|
|
gridFills.push(...ticks.map((tick, index) => this.calculateGridFill(tick, index, tick.index, p1, p2, ticks)));
|
|
if (lastFillOffCanvas) {
|
|
const tick = { tickId: `after:${lastTick.tickId}`, translation: lastTick.translation + scale2.step };
|
|
gridFills.push(this.calculateGridFill(tick, ticks.length, lastTick.index + 1, p1, p2, ticks));
|
|
}
|
|
return gridFills;
|
|
}
|
|
calculateGridFill({ tickId, translation }, index, gridFillIndex, p1, p2, ticks) {
|
|
const { gridLine, horizontal, interval, scale: scale2 } = this;
|
|
if (interval.placement !== "between") {
|
|
return super.calculateGridFill({ tickId, translation }, index, gridFillIndex, p1, p2, ticks);
|
|
}
|
|
const startOffset = translation - scale2.step / 2;
|
|
const endOffset = translation + scale2.step / 2;
|
|
const [x1, y1, x2, y2] = horizontal ? [startOffset, Math.max(p1, p2), endOffset, Math.min(p1, p2)] : [Math.min(p1, p2), startOffset, Math.max(p1, p2), endOffset];
|
|
const { fill, fillOpacity } = gridLine.style[gridFillIndex % gridLine.style.length] ?? {};
|
|
return { tickId, x1, y1, x2, y2, fill, fillOpacity };
|
|
}
|
|
calculateTickLines(ticks, direction, scrollbarThickness = 0) {
|
|
const tickLines = super.calculateTickLines(ticks, direction, scrollbarThickness);
|
|
if (this.interval.placement === "between" && ticks.length > 0) {
|
|
tickLines.push(
|
|
super.calculateTickLine(
|
|
{ isPrimary: false, tickId: `after:${ticks.at(-1)?.tickId}`, translation: this.range[1] },
|
|
ticks.length,
|
|
direction,
|
|
ticks,
|
|
scrollbarThickness
|
|
)
|
|
);
|
|
}
|
|
return tickLines;
|
|
}
|
|
calculateTickLine({ isPrimary, tickId, translation }, index, direction, ticks, scrollbarThickness = 0) {
|
|
const { horizontal, interval, primaryTick, scale: scale2, tick } = this;
|
|
if (interval.placement !== "between") {
|
|
return super.calculateTickLine(
|
|
{ isPrimary, tickId, translation },
|
|
index,
|
|
direction,
|
|
ticks,
|
|
scrollbarThickness
|
|
);
|
|
}
|
|
const datumTick = isPrimary && primaryTick?.enabled ? primaryTick : tick;
|
|
const h = -direction * this.getTickSize(datumTick);
|
|
const halfStep = translation < scale2.step ? Math.floor(scale2.step / 2) : scale2.step / 2;
|
|
const offset = translation - halfStep;
|
|
const tickOffset = -direction * (scrollbarThickness + this.getTickSpacing(datumTick));
|
|
const [x1, y1, x2, y2] = horizontal ? [offset, tickOffset, offset, tickOffset + h] : [tickOffset, offset, tickOffset + h, offset];
|
|
const { stroke: stroke3, width: strokeWidth } = datumTick;
|
|
const lineDash = void 0;
|
|
return { tickId, offset, x1, y1, x2, y2, stroke: stroke3, strokeWidth, lineDash };
|
|
}
|
|
reduceBandScalePadding() {
|
|
return this.boundSeries.reduce(
|
|
(result, series) => {
|
|
const padding2 = series.getBandScalePadding?.();
|
|
if (padding2) {
|
|
if (result.inner > padding2.inner) {
|
|
result.inner = padding2.inner;
|
|
}
|
|
if (result.outer < padding2.outer) {
|
|
result.outer = padding2.outer;
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
{ inner: Infinity, outer: -Infinity }
|
|
);
|
|
}
|
|
tickFormatParams(_domain, _ticks, _fractionDigits, _timeInterval) {
|
|
return { type: "category" };
|
|
}
|
|
datumFormatParams(value, params, _fractionDigits, _timeInterval, _style) {
|
|
const { datum, seriesId, legendItemName, key, source, property, domain, boundSeries } = params;
|
|
if (Array.isArray(value) && value.some((v) => typeof v !== "string")) {
|
|
value = value.map(String);
|
|
}
|
|
return { type: "category", value, datum, seriesId, legendItemName, key, source, property, domain, boundSeries };
|
|
}
|
|
};
|
|
_CategoryAxis.className = "CategoryAxis";
|
|
_CategoryAxis.type = "category";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _CategoryAxis.prototype, "groupPaddingInner", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _CategoryAxis.prototype, "paddingInner", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _CategoryAxis.prototype, "paddingOuter", 2);
|
|
__decorateClass([
|
|
ProxyPropertyOnWrite("layoutConstraints", "align")
|
|
], _CategoryAxis.prototype, "bandAlignment", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(value) {
|
|
if (value == null || value <= 0) {
|
|
this.layoutConstraints.width = 100;
|
|
this.layoutConstraints.unit = "percent";
|
|
} else {
|
|
this.layoutConstraints.width = value;
|
|
this.layoutConstraints.unit = "px";
|
|
this.animationManager.skipCurrentBatch();
|
|
}
|
|
}
|
|
})
|
|
], _CategoryAxis.prototype, "requiredRange", 2);
|
|
var CategoryAxis = _CategoryAxis;
|
|
|
|
// packages/ag-charts-community/src/scale/groupedCategoryScale.ts
|
|
var MAX_ANIMATABLE_NODES2 = 1e3;
|
|
var GroupedCategoryScale = class _GroupedCategoryScale extends CategoryScale {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.previousDomainJson = void 0;
|
|
/** Whether the current domain update is animatable (initial load or no domain change). */
|
|
this.animatable = true;
|
|
}
|
|
static is(value) {
|
|
return value instanceof _GroupedCategoryScale;
|
|
}
|
|
set domain(values) {
|
|
if (values.length <= MAX_ANIMATABLE_NODES2) {
|
|
const currentDomainJson = JSON.stringify(values);
|
|
this.animatable = this.previousDomainJson === void 0 || this.previousDomainJson === currentDomainJson;
|
|
this.previousDomainJson = currentDomainJson;
|
|
} else {
|
|
this.animatable = this.previousDomainJson === void 0;
|
|
this.previousDomainJson = "";
|
|
}
|
|
super.domain = values;
|
|
}
|
|
get domain() {
|
|
return super.domain;
|
|
}
|
|
normalizeDomains(...domains) {
|
|
const { domain } = super.normalizeDomains(...domains);
|
|
return { domain, animatable: false };
|
|
}
|
|
findIndex(value) {
|
|
return super.findIndex(value) ?? this.getMatchIndex(value);
|
|
}
|
|
getMatchIndex(value) {
|
|
const key = JSON.stringify(value);
|
|
const match = this._domain.find((d) => JSON.stringify(d) === key);
|
|
if (match != null) {
|
|
return super.findIndex(match);
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/axis/tree.ts
|
|
var Dimensions = class {
|
|
constructor() {
|
|
this.top = Infinity;
|
|
this.right = -Infinity;
|
|
this.bottom = -Infinity;
|
|
this.left = Infinity;
|
|
}
|
|
update(x, y) {
|
|
if (x > this.right) {
|
|
this.right = x;
|
|
}
|
|
if (x < this.left) {
|
|
this.left = x;
|
|
}
|
|
if (y > this.bottom) {
|
|
this.bottom = y;
|
|
}
|
|
if (y < this.top) {
|
|
this.top = y;
|
|
}
|
|
}
|
|
};
|
|
var TreeNode = class _TreeNode {
|
|
constructor(label = "", parent, refId) {
|
|
this.label = label;
|
|
this.parent = parent;
|
|
this.refId = refId;
|
|
this.position = 0;
|
|
this.subtreeLeft = Number.NaN;
|
|
this.subtreeRight = Number.NaN;
|
|
this.children = [];
|
|
this.leafCount = 0;
|
|
this.prelim = 0;
|
|
this.mod = 0;
|
|
this.ancestor = this;
|
|
this.change = 0;
|
|
this.shift = 0;
|
|
this.index = 0;
|
|
// screen is meant to be recomputed from (layout) when the tree is resized (without performing another layout)
|
|
this.screen = 0;
|
|
this.depth = parent ? parent.depth + 1 : 0;
|
|
}
|
|
insertTick(tick, index) {
|
|
let current = this;
|
|
let endNode;
|
|
for (let i = 0; i < tick.length; i++) {
|
|
const pathPart = tick[i];
|
|
const isNotLeaf = i !== tick.length - 1;
|
|
const { children } = current;
|
|
const existingNode = children.find((child) => child.label === pathPart);
|
|
if (existingNode && isNotLeaf) {
|
|
current = existingNode;
|
|
endNode = existingNode;
|
|
} else {
|
|
const node = new _TreeNode(pathPart, current, index);
|
|
node.index = children.length;
|
|
children.push(node);
|
|
if (isNotLeaf) {
|
|
current = node;
|
|
}
|
|
endNode = node;
|
|
}
|
|
}
|
|
return endNode;
|
|
}
|
|
getLeftSibling() {
|
|
return this.index > 0 ? this.parent?.children[this.index - 1] : void 0;
|
|
}
|
|
getLeftmostSibling() {
|
|
return this.index > 0 ? this.parent?.children[0] : void 0;
|
|
}
|
|
// traverse the left contour of a subtree, return the successor of v on this contour
|
|
nextLeft() {
|
|
return this.children[0];
|
|
}
|
|
// traverse the right contour of a subtree, return the successor of v on this contour
|
|
nextRight() {
|
|
return this.children.at(-1);
|
|
}
|
|
getSiblings() {
|
|
return this.parent?.children.filter((_, i) => i !== this.index) ?? [];
|
|
}
|
|
};
|
|
function ticksToTree(ticks) {
|
|
const maxDepth = ticks.reduce((depth, tick) => Math.max(depth, tick.length), 0);
|
|
const root = new TreeNode();
|
|
const tickNodes = /* @__PURE__ */ new Map();
|
|
for (let i = 0; i < ticks.length; i++) {
|
|
const tick = ticks[i];
|
|
while (tick.length < maxDepth) {
|
|
tick.push("");
|
|
}
|
|
const node = root.insertTick(tick, i);
|
|
if (node != null) {
|
|
tickNodes.set(tick, node);
|
|
}
|
|
}
|
|
return { root, tickNodes };
|
|
}
|
|
function moveSubtree(wm, wp, shift) {
|
|
const subtrees = wp.index - wm.index;
|
|
const ratio2 = shift / subtrees;
|
|
wp.change -= ratio2;
|
|
wp.shift += shift;
|
|
wm.change += ratio2;
|
|
wp.prelim += shift;
|
|
wp.mod += shift;
|
|
}
|
|
function ancestor(vim, v, defaultAncestor) {
|
|
return v.getSiblings().includes(vim.ancestor) ? vim.ancestor : defaultAncestor;
|
|
}
|
|
function executeShifts({ children }) {
|
|
let shift = 0;
|
|
let change = 0;
|
|
for (let i = children.length - 1; i >= 0; i--) {
|
|
const w = children[i];
|
|
w.prelim += shift;
|
|
w.mod += shift;
|
|
change += w.change;
|
|
shift += w.shift + change;
|
|
}
|
|
}
|
|
function apportion(v, defaultAncestor) {
|
|
const w = v.getLeftSibling();
|
|
if (w) {
|
|
let vop = v;
|
|
let vip = v;
|
|
let vim = w;
|
|
let vom = vip.getLeftmostSibling();
|
|
let sip = vip.mod;
|
|
let sop = vop.mod;
|
|
let sim = vim.mod;
|
|
let som = vom.mod;
|
|
while (vim.nextRight() && vip.nextLeft()) {
|
|
vim = vim.nextRight();
|
|
vip = vip.nextLeft();
|
|
vom = vom.nextLeft();
|
|
vop = vop.nextRight();
|
|
vop.ancestor = v;
|
|
const shift = vim.prelim + sim - (vip.prelim + sip) + 1;
|
|
if (shift > 0) {
|
|
moveSubtree(ancestor(vim, v, defaultAncestor), v, shift);
|
|
sip += shift;
|
|
sop += shift;
|
|
}
|
|
sim += vim.mod;
|
|
sip += vip.mod;
|
|
som += vom.mod;
|
|
sop += vop.mod;
|
|
}
|
|
if (vim.nextRight() && !vop.nextRight()) {
|
|
vop.mod += sim - sop;
|
|
} else {
|
|
if (vip.nextLeft() && !vom.nextLeft()) {
|
|
vom.mod += sip - som;
|
|
}
|
|
defaultAncestor = v;
|
|
}
|
|
}
|
|
return defaultAncestor;
|
|
}
|
|
function firstWalk(node) {
|
|
const { children } = node;
|
|
if (children.length) {
|
|
let [defaultAncestor] = children;
|
|
for (const child of children) {
|
|
firstWalk(child);
|
|
defaultAncestor = apportion(child, defaultAncestor);
|
|
}
|
|
executeShifts(node);
|
|
const midpoint = (children[0].prelim + children.at(-1).prelim) / 2;
|
|
const leftSibling = node.getLeftSibling();
|
|
if (leftSibling) {
|
|
node.prelim = leftSibling.prelim + 1;
|
|
node.mod = node.prelim - midpoint;
|
|
} else {
|
|
node.prelim = midpoint;
|
|
}
|
|
} else {
|
|
const leftSibling = node.getLeftSibling();
|
|
node.prelim = leftSibling ? leftSibling.prelim + 1 : 0;
|
|
}
|
|
}
|
|
function secondWalk(v, m, layout) {
|
|
v.position = v.prelim + m;
|
|
layout.insertNode(v);
|
|
for (const w of v.children) {
|
|
secondWalk(w, m + v.mod, layout);
|
|
}
|
|
}
|
|
function thirdWalk(v) {
|
|
const { children } = v;
|
|
let leafCount = 0;
|
|
for (const w of children) {
|
|
thirdWalk(w);
|
|
if (w.children.length) {
|
|
leafCount += w.leafCount;
|
|
} else {
|
|
leafCount++;
|
|
}
|
|
}
|
|
v.leafCount = leafCount;
|
|
if (children.length) {
|
|
v.subtreeLeft = children[0].subtreeLeft;
|
|
v.subtreeRight = children.at(-1).subtreeRight;
|
|
v.position = (v.subtreeLeft + v.subtreeRight) / 2;
|
|
} else {
|
|
v.subtreeLeft = v.position;
|
|
v.subtreeRight = v.position;
|
|
}
|
|
}
|
|
function treeLayout(ticks) {
|
|
const layout = new TreeLayout();
|
|
const { root, tickNodes } = ticksToTree(ticks);
|
|
firstWalk(root);
|
|
secondWalk(root, -root.prelim, layout);
|
|
thirdWalk(root);
|
|
return { layout, tickNodes };
|
|
}
|
|
var TreeLayout = class {
|
|
constructor() {
|
|
this.dimensions = new Dimensions();
|
|
this.nodes = [];
|
|
this.depth = 0;
|
|
}
|
|
insertNode(node) {
|
|
if (this.depth < node.depth) {
|
|
this.depth = node.depth;
|
|
}
|
|
this.dimensions.update(node.position, node.depth);
|
|
this.nodes.push(node);
|
|
}
|
|
scaling(extent2, flip) {
|
|
let scaling = 1;
|
|
if (extent2 > 0) {
|
|
const { left, right } = this.dimensions;
|
|
if (right !== left) {
|
|
scaling = extent2 / (right - left);
|
|
}
|
|
}
|
|
if (flip) {
|
|
scaling *= -1;
|
|
}
|
|
return scaling;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/axis/groupedCategoryAxis.ts
|
|
var MIN_CATEGORY_SPACING = 5;
|
|
var DepthLabelProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = true;
|
|
this.border = new LabelBorder();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "avoidCollisions", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "border", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "color", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "rotation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "wrapping", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "truncate", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "fontStyle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "fontWeight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "fontSize", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "fontFamily", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthLabelProperties.prototype, "padding", 2);
|
|
var DepthTickProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = true;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthTickProperties.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthTickProperties.prototype, "width", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthTickProperties.prototype, "stroke", 2);
|
|
var DepthProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.label = new DepthLabelProperties();
|
|
this.tick = new DepthTickProperties();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DepthProperties.prototype, "tick", 2);
|
|
var GroupedCategoryAxis = class extends CategoryAxis {
|
|
constructor(moduleCtx) {
|
|
super(moduleCtx, new GroupedCategoryScale());
|
|
// Label scale (labels are positioned between ticks, tick count = label count + 1).
|
|
// We don't call is `labelScale` for consistency with other axes.
|
|
this.tickScale = new GroupedCategoryScale();
|
|
this.computedLayout = void 0;
|
|
this.tickTreeLayout = void 0;
|
|
this.tickNodes = void 0;
|
|
this.depthOptions = new PropertiesArray(DepthProperties);
|
|
this.includeInvisibleDomains = true;
|
|
this.tickScale.paddingInner = 1;
|
|
this.tickScale.paddingOuter = 0;
|
|
}
|
|
resizeTickTree() {
|
|
if (!this.tickTreeLayout)
|
|
return;
|
|
const { nodes } = this.tickTreeLayout;
|
|
const { range: range3, step, inset, bandwidth } = this.scale;
|
|
const width2 = Math.abs(range3[1] - range3[0]) - step;
|
|
const scaling = this.tickTreeLayout.scaling(width2, range3[0] > range3[1]);
|
|
const shift = inset + bandwidth / 2;
|
|
let offset = 0;
|
|
for (const node of nodes) {
|
|
const screen = node.position * scaling;
|
|
if (offset > screen) {
|
|
offset = screen;
|
|
}
|
|
node.screen = screen + shift;
|
|
}
|
|
for (const node of nodes) {
|
|
node.screen -= offset;
|
|
}
|
|
}
|
|
getDepthOptionsMap(maxDepth) {
|
|
const optionsMap = [];
|
|
const { depthOptions, label } = this;
|
|
const defaultNonLeafRotation = this.horizontal ? 0 : -90;
|
|
for (let i = 0; i < maxDepth; i++) {
|
|
optionsMap.push(
|
|
depthOptions[i]?.label.enabled ?? label.enabled ? {
|
|
enabled: true,
|
|
spacing: depthOptions[i]?.label.spacing ?? label.spacing,
|
|
wrapping: depthOptions[i]?.label.wrapping ?? label.wrapping,
|
|
truncate: depthOptions[i]?.label.truncate ?? label.truncate,
|
|
rotation: depthOptions[i]?.label.rotation ?? (i ? defaultNonLeafRotation : label.rotation),
|
|
// Default top-level label rotation only applies to label leaves
|
|
avoidCollisions: depthOptions[i]?.label.avoidCollisions ?? label.avoidCollisions
|
|
} : { enabled: false, spacing: 0, rotation: 0, avoidCollisions: false }
|
|
);
|
|
}
|
|
return optionsMap;
|
|
}
|
|
updateCategoryLabels() {
|
|
if (!this.computedLayout)
|
|
return;
|
|
this.tickLabelGroupSelection.update(this.computedLayout.tickLabelLayout).each((node, datum) => {
|
|
node.fill = datum.color;
|
|
node.text = datum.text;
|
|
node.textBaseline = datum.textBaseline;
|
|
node.textAlign = datum.textAlign ?? "center";
|
|
node.pointerEvents = datum.textUntruncated == null ? 1 /* None */ : 0 /* All */;
|
|
node.setFont(datum);
|
|
node.setBoxing(datum);
|
|
});
|
|
}
|
|
updateAxisLine() {
|
|
if (!this.computedLayout)
|
|
return;
|
|
this.lineNode.visible = this.line.enabled;
|
|
this.lineNode.stroke = this.line.stroke;
|
|
this.lineNode.strokeWidth = this.line.width;
|
|
}
|
|
computeLayout() {
|
|
this.updateDirection();
|
|
this.updateScale();
|
|
const { step } = this.scale;
|
|
const { title, label, range: range3, depthOptions, horizontal, line } = this;
|
|
const scrollbar = this.chartLayout?.scrollbars?.[this.id];
|
|
const scrollbarThickness = this.getScrollbarThickness(scrollbar);
|
|
this.lineNode.datum = horizontal ? { x1: range3[0], x2: range3[1], y1: 0, y2: 0 } : { x1: 0, x2: 0, y1: range3[0], y2: range3[1] };
|
|
this.lineNode.setProperties({ stroke: line.stroke, strokeWidth: line.enabled ? line.width : 0 });
|
|
this.resizeTickTree();
|
|
if (!this.tickTreeLayout?.depth) {
|
|
return { bbox: BBox.zero, spacing: 0, depthLabelMaxSize: {}, tickLabelLayout: [] };
|
|
}
|
|
const { depth: maxDepth, nodes: treeLabels } = this.tickTreeLayout;
|
|
const sideFlag = horizontal ? -label.getSideFlag() : label.getSideFlag();
|
|
const tickLabelLayout = [];
|
|
const labelBBoxes = /* @__PURE__ */ new Map();
|
|
const truncatedLabelText = /* @__PURE__ */ new Map();
|
|
const tempText = new TransformableText();
|
|
const optionsMap = this.getDepthOptionsMap(maxDepth);
|
|
const labelSpacing = sideFlag * (optionsMap[0].spacing + this.getTickSpacing() + scrollbarThickness);
|
|
const tickFormatter = this.tickFormatter(this.scale.domain, this.scale.domain, false);
|
|
const setLabelProps = (datum, index) => {
|
|
const depth = maxDepth - datum.depth;
|
|
if (!optionsMap[depth]?.enabled || !inRange(datum.screen, range3))
|
|
return false;
|
|
let maxWidth = (datum.leafCount || 1) * step;
|
|
if (maxWidth < MIN_CATEGORY_SPACING)
|
|
return false;
|
|
const inputText = tickFormatter(datum.label, index - 1);
|
|
let text2 = inputText;
|
|
const labelStyles = this.getLabelStyles(
|
|
{ value: datum.index, formattedValue: text2, depth },
|
|
depthOptions[depth]?.label
|
|
);
|
|
if (label.avoidCollisions) {
|
|
const rotation = optionsMap[depth].rotation;
|
|
let maxHeight = this.thickness;
|
|
if (rotation != null) {
|
|
const innerRect = getMaxInnerRectSize(rotation, maxWidth, maxHeight);
|
|
maxWidth = innerRect.width;
|
|
maxHeight = innerRect.height;
|
|
}
|
|
const wrapOptions = {
|
|
font: labelStyles,
|
|
textWrap: optionsMap[depth].wrapping,
|
|
overflow: optionsMap[depth].truncate ? "ellipsis" : "hide",
|
|
maxWidth,
|
|
maxHeight
|
|
};
|
|
text2 = wrapTextOrSegments(text2, wrapOptions) || text2;
|
|
}
|
|
if (text2 !== inputText && isTruncated(text2)) {
|
|
truncatedLabelText.set(index, toPlainText(inputText));
|
|
} else {
|
|
truncatedLabelText.delete(index);
|
|
}
|
|
tempText.x = horizontal ? datum.screen : labelSpacing;
|
|
tempText.y = horizontal ? labelSpacing : datum.screen;
|
|
tempText.rotation = 0;
|
|
tempText.fill = labelStyles.color;
|
|
tempText.text = text2;
|
|
tempText.textAlign = "center";
|
|
tempText.textBaseline = label.parallel ? "top" : "bottom";
|
|
tempText.setFont(labelStyles);
|
|
tempText.setBoxing(labelStyles);
|
|
return true;
|
|
};
|
|
const depthLabelMaxSize = {};
|
|
for (const [index, datum] of treeLabels.entries()) {
|
|
const depth = maxDepth - datum.depth;
|
|
depthLabelMaxSize[depth] ?? (depthLabelMaxSize[depth] = 0);
|
|
const isLeaf = !datum.children.length;
|
|
if (isLeaf && step < MIN_CATEGORY_SPACING)
|
|
continue;
|
|
const isVisible = setLabelProps(datum, index);
|
|
if (!isVisible || !tempText.getBBox())
|
|
continue;
|
|
labelBBoxes.set(index, tempText.getBBox());
|
|
tempText.rotation = normalizeAngle360FromDegrees(optionsMap[depth]?.rotation);
|
|
const { width: width2, height: height2 } = tempText.getBBox();
|
|
const labelSize = horizontal ? height2 : width2;
|
|
if (depthLabelMaxSize[depth] < labelSize) {
|
|
depthLabelMaxSize[depth] = labelSize;
|
|
}
|
|
}
|
|
const idGenerator = createIdsGenerator();
|
|
const nestedPadding = (d) => {
|
|
if (d === 0)
|
|
return 0;
|
|
let v = depthLabelMaxSize[0];
|
|
for (let i = 1; i <= d; i++) {
|
|
v += optionsMap[i].spacing;
|
|
if (i !== d) {
|
|
v += depthLabelMaxSize[i];
|
|
}
|
|
}
|
|
return v;
|
|
};
|
|
for (const [index, datum] of treeLabels.entries()) {
|
|
if (index === 0)
|
|
continue;
|
|
const visible = setLabelProps(datum, index);
|
|
const isLeaf = !datum.children.length;
|
|
const depth = maxDepth - datum.depth;
|
|
if (isLeaf && step < MIN_CATEGORY_SPACING)
|
|
continue;
|
|
if (!visible)
|
|
continue;
|
|
const labelRotation = normalizeAngle360FromDegrees(optionsMap[depth].rotation);
|
|
const labelBBox = labelBBoxes.get(index);
|
|
if (!labelBBox)
|
|
continue;
|
|
const { width: w, height: h } = labelBBox;
|
|
const depthPadding = nestedPadding(depth);
|
|
tempText.textAlign = "center";
|
|
tempText.textBaseline = "middle";
|
|
tempText.rotation = labelRotation;
|
|
if (horizontal) {
|
|
tempText.y += (depthPadding + angularPadding(w / 2, h / 2, labelRotation)) * sideFlag;
|
|
tempText.rotationCenterX = datum.screen;
|
|
tempText.rotationCenterY = tempText.y;
|
|
} else {
|
|
tempText.x += depthPadding * sideFlag + angularPadding(
|
|
(optionsMap[depth].spacing * sideFlag + w) / 2,
|
|
label.mirrored ? w : 0,
|
|
labelRotation
|
|
) - w / 2;
|
|
tempText.rotationCenterX = tempText.x;
|
|
tempText.rotationCenterY = datum.screen;
|
|
}
|
|
if (optionsMap[depth].avoidCollisions) {
|
|
const { width: width2, height: height2 } = tempText.getBBox();
|
|
const labelSize = horizontal ? width2 : height2;
|
|
const availableRange = isLeaf ? step : datum.leafCount * step;
|
|
if (labelSize > availableRange) {
|
|
labelBBoxes.delete(index);
|
|
continue;
|
|
}
|
|
}
|
|
const text2 = tempText.getPlainText();
|
|
const boxing = tempText.getBoxingProperties();
|
|
tickLabelLayout.push({
|
|
text: text2,
|
|
textUntruncated: truncatedLabelText.get(index),
|
|
visible: true,
|
|
tickId: idGenerator(text2),
|
|
range: this.scale.range,
|
|
border: boxing.border,
|
|
color: tempText.fill,
|
|
cornerRadius: boxing.cornerRadius,
|
|
fill: boxing.fill,
|
|
fontFamily: tempText.fontFamily,
|
|
fontSize: tempText.fontSize,
|
|
fontStyle: tempText.fontStyle,
|
|
fontWeight: tempText.fontWeight,
|
|
padding: boxing.padding,
|
|
rotation: tempText.rotation,
|
|
rotationCenterX: tempText.rotationCenterX,
|
|
rotationCenterY: tempText.rotationCenterY,
|
|
textAlign: tempText.textAlign,
|
|
textBaseline: tempText.textBaseline,
|
|
x: tempText.x,
|
|
y: tempText.y
|
|
});
|
|
labelBBoxes.set(index, Transformable.toCanvas(tempText));
|
|
}
|
|
let maxTickSize = depthLabelMaxSize[0];
|
|
for (let i = 0; i < maxDepth; i++) {
|
|
maxTickSize += optionsMap[i].spacing;
|
|
if (i !== 0) {
|
|
maxTickSize += depthLabelMaxSize[i];
|
|
}
|
|
}
|
|
const maxTickSizeWithScrollbar = maxTickSize + scrollbarThickness;
|
|
const bboxes = [
|
|
this.lineNodeBBox(),
|
|
BBox.merge(labelBBoxes.values()),
|
|
new BBox(0, 0, maxTickSizeWithScrollbar * sideFlag, 0)
|
|
];
|
|
const combined = BBox.merge(bboxes);
|
|
const labelThickness = horizontal ? combined.height : combined.width;
|
|
const { spacing, scrollbarLayout } = this.applyScrollbarLayout(bboxes, labelThickness, scrollbar);
|
|
this.layout.labelThickness = labelThickness;
|
|
this.layout.scrollbar = scrollbarLayout;
|
|
if (title.enabled) {
|
|
bboxes.push(this.titleBBox(this.scale.domain, spacing));
|
|
}
|
|
const mergedBBox = BBox.merge(bboxes);
|
|
this.layoutCrossLines();
|
|
return { bbox: mergedBBox, spacing, depthLabelMaxSize, tickLabelLayout };
|
|
}
|
|
/**
|
|
* Creates/removes/updates the scene graph nodes that constitute the axis.
|
|
* Supposed to be called _manually_ after changing _any_ of the axis properties.
|
|
* This allows to bulk set axis properties before updating the nodes.
|
|
* The node changes made by this method are rendered on the next animation frame.
|
|
* We could schedule this method call automatically on the next animation frame
|
|
* when any of the axis properties change (the way we do when properties of scene graph's
|
|
* nodes change), but this will mean that we first wait for the next animation
|
|
* frame to make changes to the nodes of the axis, then wait for another animation
|
|
* frame to render those changes. It's nice to have everything update automatically,
|
|
* but this extra level of async indirection will not just introduce an unwanted delay,
|
|
* it will also make it harder to reason about the program.
|
|
*/
|
|
update() {
|
|
if (!this.computedLayout)
|
|
return;
|
|
if (!this.scale.animatable) {
|
|
this.moduleCtx.animationManager.skipCurrentBatch();
|
|
}
|
|
const { tickScale, tick, gridLine, gridLength, visibleRange, tickTreeLayout } = this;
|
|
if (!tickTreeLayout)
|
|
return;
|
|
const { depthLabelMaxSize, spacing } = this.computedLayout;
|
|
const { depth: maxDepth } = tickTreeLayout;
|
|
const optionsMap = this.getDepthOptionsMap(maxDepth);
|
|
const scrollbar = this.chartLayout?.scrollbars?.[this.id];
|
|
const scrollbarThickness = this.getScrollbarThickness(scrollbar);
|
|
const { position, horizontal, gridPadding } = this;
|
|
const direction = position === "bottom" || position === "right" ? -1 : 1;
|
|
const p1 = gridPadding;
|
|
const p2 = direction * gridLength - gridPadding;
|
|
const tickParams = {
|
|
nice: [false, false],
|
|
interval: void 0,
|
|
tickCount: void 0,
|
|
minTickCount: 0,
|
|
maxTickCount: Infinity
|
|
};
|
|
const { ticks: allTicks } = tickScale.ticks(tickParams, void 0, visibleRange);
|
|
const { tickInfos: allTickInfos, minSpacingByDepth } = buildTickInfos(
|
|
allTicks,
|
|
this.tickNodes,
|
|
tickScale,
|
|
maxDepth
|
|
);
|
|
const minDepthToShow = getMinDepthToShow(minSpacingByDepth);
|
|
const visibleTickInfos = selectVisibleTickInfos(allTickInfos, minDepthToShow, maxDepth, minSpacingByDepth);
|
|
const gridLineData = visibleTickInfos.map(
|
|
({ tickLabel, position: tickPosition }, index) => ({
|
|
index: tickScale.findIndex(tickLabel),
|
|
tickId: createDatumId(index, ...tickLabel),
|
|
translation: Math.round(tickPosition)
|
|
})
|
|
);
|
|
this.gridLineGroupSelection.update(
|
|
gridLine.enabled && gridLength ? this.calculateGridLines(gridLineData, p1, p2) : []
|
|
);
|
|
this.gridFillGroupSelection.update(
|
|
gridLine.enabled && gridLength ? this.calculateGridFills(gridLineData, p1, p2) : []
|
|
);
|
|
this.tickLineGroupSelection.update(
|
|
tick.enabled ? visibleTickInfos.map(({ depth }, index) => {
|
|
const { tickId, translation: offset } = gridLineData[index];
|
|
const tickOptions = this.depthOptions[depth]?.tick;
|
|
let tickSize = depthLabelMaxSize[0];
|
|
for (let i = 0; i <= depth; i++) {
|
|
tickSize += optionsMap[i].spacing;
|
|
if (i !== 0) {
|
|
tickSize += depthLabelMaxSize[i];
|
|
}
|
|
}
|
|
const stroke3 = tickOptions?.stroke ?? tick.stroke;
|
|
const strokeWidth = tickOptions?.enabled === false ? 0 : tickOptions?.width ?? tick.width;
|
|
const h = -direction * tickSize;
|
|
const tickOffset = -direction * (scrollbarThickness + this.getTickSpacing());
|
|
const [x1, y1, x2, y2] = horizontal ? [offset, tickOffset, offset, tickOffset + h] : [tickOffset, offset, tickOffset + h, offset];
|
|
const lineDash = void 0;
|
|
return { tickId, offset, x1, y1, x2, y2, stroke: stroke3, strokeWidth, lineDash };
|
|
}) : []
|
|
);
|
|
this.updatePosition();
|
|
this.updateCategoryLabels();
|
|
this.updateAxisLine();
|
|
this.updateGridLines();
|
|
this.updateGridFills();
|
|
this.updateTickLines();
|
|
this.updateTitle(this.scale.domain, spacing);
|
|
this.updateCrossLines();
|
|
this.resetSelectionNodes();
|
|
}
|
|
calculateLayout(_primaryTickCount, chartLayout) {
|
|
this.chartLayout = chartLayout;
|
|
const { depthLabelMaxSize, tickLabelLayout, spacing, bbox } = this.computeLayout();
|
|
this.computedLayout = { depthLabelMaxSize, tickLabelLayout, spacing };
|
|
return { bbox, niceDomain: this.scale.domain };
|
|
}
|
|
/**
|
|
* The length of the grid. The grid is only visible in case of a non-zero value.
|
|
*/
|
|
onGridVisibilityChange() {
|
|
super.onGridVisibilityChange();
|
|
this.tickLabelGroupSelection.clear();
|
|
}
|
|
updateScale() {
|
|
super.updateScale();
|
|
this.tickScale.range = this.scale.range;
|
|
this.scale.paddingOuter = this.scale.paddingInner / 2;
|
|
}
|
|
processData() {
|
|
const { direction } = this;
|
|
const flatDomains = this.boundSeries.filter((s) => s.visible).flatMap((series) => extractDomain(series.getDomain(direction)));
|
|
this.dataDomain = { domain: extent(flatDomains) ?? this.filterDuplicateArrays(flatDomains), clipped: false };
|
|
if (this.isReversed()) {
|
|
this.dataDomain.domain.reverse();
|
|
}
|
|
const domain = this.dataDomain.domain.map(convertIntegratedCategoryValue);
|
|
const { layout, tickNodes } = treeLayout(domain);
|
|
this.tickTreeLayout = layout;
|
|
this.tickNodes = tickNodes;
|
|
const orderedDomain = [];
|
|
for (const node of this.tickTreeLayout.nodes) {
|
|
if (node.leafCount || node.refId == null)
|
|
continue;
|
|
orderedDomain.push(this.dataDomain.domain[node.refId]);
|
|
}
|
|
const sortedDomain = sortBasedOnArray(this.dataDomain.domain, orderedDomain);
|
|
this.scale.domain = sortedDomain;
|
|
const tickScaleDomain = sortedDomain.map(convertIntegratedCategoryValue);
|
|
tickScaleDomain.push([""]);
|
|
this.tickScale.domain = tickScaleDomain;
|
|
}
|
|
filterDuplicateArrays(array2) {
|
|
const seen = /* @__PURE__ */ new Set();
|
|
return array2.filter((item) => {
|
|
const key = isArray(item) ? JSON.stringify(item) : item;
|
|
if (seen.has(key))
|
|
return false;
|
|
seen.add(key);
|
|
return true;
|
|
});
|
|
}
|
|
};
|
|
GroupedCategoryAxis.className = "GroupedCategoryAxis";
|
|
GroupedCategoryAxis.type = "grouped-category";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GroupedCategoryAxis.prototype, "depthOptions", 2);
|
|
function separatorDepth2(node) {
|
|
let depth = 0;
|
|
let current = node;
|
|
while (current?.index === 0) {
|
|
depth += 1;
|
|
current = current.parent;
|
|
}
|
|
return depth;
|
|
}
|
|
function buildTickInfos(ticks, tickNodes, tickScale, maxDepth) {
|
|
const tickInfos = new Array(ticks.length);
|
|
const minSpacingByDepth = new Array(maxDepth).fill(Infinity);
|
|
const lastPositionByDepth = new Array(maxDepth).fill(Number.NaN);
|
|
for (let i = 0; i < ticks.length; i++) {
|
|
const tickLabel = ticks[i];
|
|
const node = tickNodes?.get(tickLabel);
|
|
const depth = node == null ? maxDepth - 1 : Math.min(separatorDepth2(node), maxDepth - 1);
|
|
const position = tickScale.convert(tickLabel);
|
|
tickInfos[i] = { tickLabel, depth, position };
|
|
if (!Number.isFinite(position))
|
|
continue;
|
|
for (let d = 0; d <= depth; d++) {
|
|
const lastPosition = lastPositionByDepth[d];
|
|
if (Number.isFinite(lastPosition)) {
|
|
minSpacingByDepth[d] = Math.min(minSpacingByDepth[d], Math.abs(position - lastPosition));
|
|
}
|
|
lastPositionByDepth[d] = position;
|
|
}
|
|
}
|
|
return { tickInfos, minSpacingByDepth };
|
|
}
|
|
function getMinDepthToShow(minSpacingByDepth) {
|
|
for (let depth = 0; depth < minSpacingByDepth.length; depth++) {
|
|
const minSpacing = minSpacingByDepth[depth];
|
|
if (!Number.isFinite(minSpacing) || minSpacing >= MIN_CATEGORY_SPACING) {
|
|
return depth;
|
|
}
|
|
}
|
|
return minSpacingByDepth.length;
|
|
}
|
|
function getTickStepForSpacing(minSpacing) {
|
|
if (!Number.isFinite(minSpacing) || minSpacing <= 0) {
|
|
return 1;
|
|
}
|
|
return Math.max(1, Math.ceil(MIN_CATEGORY_SPACING / minSpacing));
|
|
}
|
|
function selectVisibleTickInfos(allTickInfos, minDepthToShow, maxDepth, minSpacingByDepth) {
|
|
if (minDepthToShow <= 0) {
|
|
return allTickInfos;
|
|
}
|
|
const removedDepth = Math.min(minDepthToShow - 1, maxDepth - 1);
|
|
if (removedDepth < 0) {
|
|
return allTickInfos;
|
|
}
|
|
const tickStep2 = getTickStepForSpacing(minSpacingByDepth[removedDepth]);
|
|
const visibleTickInfos = [];
|
|
let removedIndex = 0;
|
|
for (const info of allTickInfos) {
|
|
if (info.depth >= minDepthToShow) {
|
|
visibleTickInfos.push(info);
|
|
continue;
|
|
}
|
|
if (info.depth !== removedDepth)
|
|
continue;
|
|
if (removedIndex % tickStep2 === 0) {
|
|
visibleTickInfos.push(info);
|
|
}
|
|
removedIndex++;
|
|
}
|
|
return visibleTickInfos;
|
|
}
|
|
function convertIntegratedCategoryValue(datum) {
|
|
return toArray(isObject(datum) && "value" in datum ? datum.value : datum);
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/quadtreeUtil.ts
|
|
function addHitTestersToQuadtree(quadtree, hitTesters) {
|
|
for (const node of hitTesters) {
|
|
const datum = node.datum;
|
|
if (datum === void 0) {
|
|
logger_exports.error("undefined datum");
|
|
} else {
|
|
quadtree.addValue(node, datum);
|
|
}
|
|
}
|
|
}
|
|
function findQuadtreeMatch(series, point) {
|
|
const { x, y } = point;
|
|
const { nearest, distanceSquared: distanceSquared2 } = series.getQuadTree().find(x, y);
|
|
if (nearest !== void 0) {
|
|
return { datum: nearest.value, distance: Math.sqrt(distanceSquared2) };
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/abstractBarSeries.ts
|
|
var AbstractBarSeriesProperties = class extends CartesianSeriesProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.direction = "vertical";
|
|
this.width = void 0;
|
|
this.widthRatio = void 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AbstractBarSeriesProperties.prototype, "direction", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AbstractBarSeriesProperties.prototype, "width", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AbstractBarSeriesProperties.prototype, "widthRatio", 2);
|
|
var AbstractBarSeries = class extends CartesianSeries {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.smallestDataInterval = void 0;
|
|
this.largestDataInterval = void 0;
|
|
}
|
|
padBandExtent(keys, alignStart) {
|
|
const ratio2 = typeof alignStart === "boolean" ? 1 : 0.5;
|
|
const scalePadding = isFiniteNumber(this.smallestDataInterval) ? this.smallestDataInterval * ratio2 : 0;
|
|
const keysExtent = extent(keys) ?? [Number.NaN, Number.NaN];
|
|
if (typeof alignStart === "boolean") {
|
|
keysExtent[alignStart ? 0 : 1] -= (alignStart ? 1 : -1) * scalePadding;
|
|
} else {
|
|
keysExtent[0] -= scalePadding;
|
|
keysExtent[1] += scalePadding;
|
|
}
|
|
return fixNumericExtent(keysExtent);
|
|
}
|
|
getBandScalePadding() {
|
|
return { inner: 0.3, outer: 0.15 };
|
|
}
|
|
shouldFlipXY() {
|
|
return !this.isVertical();
|
|
}
|
|
isVertical() {
|
|
return this.properties.direction === "vertical";
|
|
}
|
|
getBarDirection() {
|
|
return this.shouldFlipXY() ? "x" /* X */ : "y" /* Y */;
|
|
}
|
|
getCategoryDirection() {
|
|
return this.shouldFlipXY() ? "y" /* Y */ : "x" /* X */;
|
|
}
|
|
getValueAxis() {
|
|
const direction = this.getBarDirection();
|
|
return this.axes[direction];
|
|
}
|
|
getCategoryAxis() {
|
|
const direction = this.getCategoryDirection();
|
|
return this.axes[direction];
|
|
}
|
|
getMinimumRangeSeries(ranges) {
|
|
const { width: width2 } = this.properties;
|
|
if (width2 == null)
|
|
return;
|
|
const axis = this.getCategoryAxis();
|
|
if (!axis)
|
|
return;
|
|
const { index } = this.ctx.seriesStateManager.getVisiblePeerGroupIndex(this);
|
|
ranges[index] = Math.max(ranges[index] ?? 0, width2);
|
|
}
|
|
getMinimumRangeChart(ranges) {
|
|
if (ranges.length === 0)
|
|
return 0;
|
|
const axis = this.getCategoryAxis();
|
|
if (!(axis instanceof GroupedCategoryAxis || axis instanceof CategoryAxis))
|
|
return 0;
|
|
const dataSize = this.data?.netSize() ?? 0;
|
|
if (dataSize === 0)
|
|
return 0;
|
|
const defaultPadding = this.getBandScalePadding();
|
|
const { paddingInner = defaultPadding.inner, paddingOuter = defaultPadding.outer, groupPaddingInner } = axis;
|
|
const width2 = ranges.reduce((sum, range3) => sum + range3, 0);
|
|
const averageWidth = width2 / ranges.length;
|
|
const { visibleGroupCount } = this.ctx.seriesStateManager.getVisiblePeerGroupIndex(this);
|
|
const bandWidth = width2 + groupPaddingInner * averageWidth * (visibleGroupCount - 1);
|
|
const paddingFactor = (dataSize - paddingInner + paddingOuter * 2) / (1 - paddingInner);
|
|
return bandWidth * paddingFactor;
|
|
}
|
|
/**
|
|
* Override to use bar-specific axis resolution (category/value vs X/Y).
|
|
* Bar series can be horizontal or vertical, so we use getCategoryAxis/getValueAxis.
|
|
*/
|
|
validateCreateNodeDataPreconditions() {
|
|
const xAxis = this.getCategoryAxis();
|
|
const yAxis = this.getValueAxis();
|
|
if (!xAxis || !yAxis || !this.dataModel || !this.processedData) {
|
|
return void 0;
|
|
}
|
|
return { xAxis, yAxis };
|
|
}
|
|
getBandwidth(xAxis, minWidth) {
|
|
return ContinuousScale.is(xAxis.scale) ? xAxis.scale.calcBandwidth(this.smallestDataInterval, minWidth) : xAxis.scale.bandwidth;
|
|
}
|
|
xCoordinateRange(xValue) {
|
|
const xAxis = this.axes[this.getCategoryDirection()];
|
|
const xScale = xAxis.scale;
|
|
const bandWidth = this.getBandwidth(xAxis, 0) ?? 0;
|
|
const barOffset = ContinuousScale.is(xScale) ? bandWidth * -0.5 : 0;
|
|
const x = xScale.convert(xValue) + barOffset;
|
|
return [x, x + bandWidth];
|
|
}
|
|
yCoordinateRange(yValues) {
|
|
const yAxis = this.axes[this.getBarDirection()];
|
|
const yScale = yAxis.scale;
|
|
const ys = yValues.map((yValue) => yScale.convert(yValue));
|
|
if (ys.length === 1) {
|
|
const y0 = yScale.convert(0);
|
|
return [Math.min(ys[0], y0), Math.max(ys[0], y0)];
|
|
}
|
|
return [Math.min(...ys), Math.max(...ys)];
|
|
}
|
|
getBarDimensions() {
|
|
const categoryAxis = this.getCategoryAxis();
|
|
const bandwidth = this.getBandwidth(categoryAxis) ?? 0;
|
|
this.ctx.seriesStateManager.updateGroupScale(this, bandwidth, categoryAxis);
|
|
const groupOffset = this.getGroupOffset();
|
|
const barWidth = this.getBarWidth();
|
|
const barOffset = this.getBarOffset(barWidth);
|
|
return { groupOffset, barOffset, barWidth };
|
|
}
|
|
getGroupOffset() {
|
|
return this.ctx.seriesStateManager.getGroupOffset(this);
|
|
}
|
|
getBarOffset(barWidth) {
|
|
const groupScale = this.ctx.seriesStateManager.getGroupScale(this);
|
|
const xAxis = this.getCategoryAxis();
|
|
let barOffset = 0;
|
|
if (ContinuousScale.is(xAxis.scale)) {
|
|
barOffset = -barWidth / 2;
|
|
} else if (this.seriesGrouping == null && groupScale) {
|
|
const rangeWidth = this.getGroupScaleRangeWidth(groupScale);
|
|
barOffset = (rangeWidth - barWidth) / 2;
|
|
} else if (groupScale && this.properties.widthRatio != null) {
|
|
barOffset = (groupScale.bandwidth - barWidth) / 2;
|
|
}
|
|
const stackOffset = this.ctx.seriesStateManager.getStackOffset(this, barWidth);
|
|
return barOffset + stackOffset;
|
|
}
|
|
getBarWidth() {
|
|
const { seriesGrouping } = this;
|
|
const { width: width2 } = this.properties;
|
|
let { widthRatio } = this.properties;
|
|
const groupScale = this.ctx.seriesStateManager.getGroupScale(this);
|
|
const bandwidth = groupScale?.bandwidth ?? 0;
|
|
if (seriesGrouping == null) {
|
|
widthRatio ?? (widthRatio = 1);
|
|
}
|
|
if (widthRatio != null) {
|
|
let relativeWidth = width2;
|
|
if (seriesGrouping == null && relativeWidth == null && groupScale) {
|
|
relativeWidth = this.getGroupScaleRangeWidth(groupScale);
|
|
}
|
|
if (relativeWidth == null && bandwidth < 1 && groupScale) {
|
|
return groupScale.rawBandwidth;
|
|
}
|
|
return (relativeWidth ?? bandwidth) * widthRatio;
|
|
}
|
|
if (width2 != null) {
|
|
return width2;
|
|
}
|
|
if (bandwidth < 1 && groupScale) {
|
|
return groupScale.rawBandwidth;
|
|
}
|
|
return bandwidth;
|
|
}
|
|
getGroupScaleRangeWidth(groupScale) {
|
|
let rangeWidth = groupScale.range[1] - groupScale.range[0];
|
|
if (groupScale.round && rangeWidth > 0)
|
|
rangeWidth = Math.floor(rangeWidth);
|
|
return rangeWidth;
|
|
}
|
|
resolveKeyDirection(direction) {
|
|
if (this.getBarDirection() === "x" /* X */) {
|
|
if (direction === "x" /* X */) {
|
|
return "y" /* Y */;
|
|
}
|
|
return "x" /* X */;
|
|
}
|
|
return direction;
|
|
}
|
|
initQuadTree(quadtree) {
|
|
addHitTestersToQuadtree(quadtree, this.datumNodesIter());
|
|
}
|
|
pickNodeClosestDatum(point) {
|
|
return findQuadtreeMatch(this, point);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/lineInterpolationPlotting.ts
|
|
function lerp2(a, b, ratio2) {
|
|
return (b - a) * ratio2 + a;
|
|
}
|
|
function linearSupertype(span, stepX) {
|
|
const { x0, y0, x1, y1 } = span;
|
|
const m = (y1 - y0) / (x1 - x0);
|
|
const stepY = m * (stepX - x0) + y0;
|
|
return {
|
|
leftCp1x: x0,
|
|
leftCp1y: y0,
|
|
leftCp2x: stepX,
|
|
leftCp2y: stepY,
|
|
stepX,
|
|
stepY0: stepY,
|
|
stepY1: stepY,
|
|
rightCp1x: stepX,
|
|
rightCp1y: stepY,
|
|
rightCp2x: x1,
|
|
rightCp2y: y1
|
|
};
|
|
}
|
|
function bezierSupertype(span, stepX) {
|
|
const { cp0x, cp0y, cp1x, cp1y, cp2x, cp2y, cp3x, cp3y } = span;
|
|
const t = solveBezier(cp0x, cp1x, cp2x, cp3x, stepX);
|
|
const [left, right] = splitBezier2D(cp0x, cp0y, cp1x, cp1y, cp2x, cp2y, cp3x, cp3y, t);
|
|
const stepY = left[3].y;
|
|
return {
|
|
leftCp1x: left[1].x,
|
|
leftCp1y: left[1].y,
|
|
leftCp2x: left[2].x,
|
|
leftCp2y: left[2].y,
|
|
stepX,
|
|
stepY0: stepY,
|
|
stepY1: stepY,
|
|
rightCp1x: right[1].x,
|
|
rightCp1y: right[1].y,
|
|
rightCp2x: right[2].x,
|
|
rightCp2y: right[2].y
|
|
};
|
|
}
|
|
function stepSupertype(span) {
|
|
const { x0, y0, x1, y1, stepX } = span;
|
|
return {
|
|
leftCp1x: (x0 + stepX) / 2,
|
|
leftCp1y: y0,
|
|
leftCp2x: (x0 + stepX) / 2,
|
|
leftCp2y: y0,
|
|
stepX,
|
|
stepY0: y0,
|
|
stepY1: y1,
|
|
rightCp1x: (stepX + x1) / 2,
|
|
rightCp1y: y1,
|
|
rightCp2x: (stepX + x1) / 2,
|
|
rightCp2y: y1
|
|
};
|
|
}
|
|
function spanSupertype(span, stepX) {
|
|
if (span.type === "linear") {
|
|
return linearSupertype(span, stepX);
|
|
} else if (span.type === "cubic") {
|
|
return bezierSupertype(span, stepX);
|
|
} else if (span.type === "step") {
|
|
return stepSupertype(span);
|
|
} else {
|
|
return linearSupertype(span, stepX);
|
|
}
|
|
}
|
|
function plotStart(path, moveTo2, x0, y0, x1, y1, reversed) {
|
|
switch (moveTo2) {
|
|
case 0 /* MoveTo */:
|
|
if (reversed) {
|
|
path.moveTo(x1, y1);
|
|
} else {
|
|
path.moveTo(x0, y0);
|
|
}
|
|
break;
|
|
case 1 /* LineTo */:
|
|
if (reversed) {
|
|
path.lineTo(x1, y1);
|
|
} else {
|
|
path.lineTo(x0, y0);
|
|
}
|
|
break;
|
|
case 2 /* Skip */:
|
|
break;
|
|
}
|
|
}
|
|
function plotLinear(path, x0, y0, x1, y1, reversed) {
|
|
if (reversed) {
|
|
path.lineTo(x0, y0);
|
|
} else {
|
|
path.lineTo(x1, y1);
|
|
}
|
|
}
|
|
function plotCubic(path, cp0x, cp0y, cp1x, cp1y, cp2x, cp2y, cp3x, cp3y, reversed) {
|
|
if (reversed) {
|
|
path.cubicCurveTo(cp2x, cp2y, cp1x, cp1y, cp0x, cp0y);
|
|
} else {
|
|
path.cubicCurveTo(cp1x, cp1y, cp2x, cp2y, cp3x, cp3y);
|
|
}
|
|
}
|
|
function plotStep(path, x0, y0, x1, y1, stepX, reversed) {
|
|
if (reversed) {
|
|
path.lineTo(stepX, y1);
|
|
path.lineTo(stepX, y0);
|
|
path.lineTo(x0, y0);
|
|
} else {
|
|
path.lineTo(stepX, y0);
|
|
path.lineTo(stepX, y1);
|
|
path.lineTo(x1, y1);
|
|
}
|
|
}
|
|
function plotMultiLine(path, x0, y0, x1, y1, midPoints, reversed) {
|
|
if (reversed) {
|
|
for (let i = midPoints.length - 1; i >= 0; i--) {
|
|
const { x, y } = midPoints[i];
|
|
path.lineTo(x, y);
|
|
}
|
|
path.lineTo(x0, y0);
|
|
} else {
|
|
for (const { x, y } of midPoints) {
|
|
path.lineTo(x, y);
|
|
}
|
|
path.lineTo(x1, y1);
|
|
}
|
|
}
|
|
function plotSpan(path, span, moveTo2, reversed) {
|
|
const [start2, end3] = spanRange(span);
|
|
plotStart(path, moveTo2, start2.x, start2.y, end3.x, end3.y, reversed);
|
|
switch (span.type) {
|
|
case "linear":
|
|
plotLinear(path, span.x0, span.y0, span.x1, span.y1, reversed);
|
|
break;
|
|
case "cubic":
|
|
plotCubic(
|
|
path,
|
|
span.cp0x,
|
|
span.cp0y,
|
|
span.cp1x,
|
|
span.cp1y,
|
|
span.cp2x,
|
|
span.cp2y,
|
|
span.cp3x,
|
|
span.cp3y,
|
|
reversed
|
|
);
|
|
break;
|
|
case "step":
|
|
plotStep(path, span.x0, span.y0, span.x1, span.y1, span.stepX, reversed);
|
|
break;
|
|
case "multi-line":
|
|
plotMultiLine(path, span.x0, span.y0, span.x1, span.y1, span.midPoints, reversed);
|
|
break;
|
|
}
|
|
}
|
|
function interpolatedSpanRange(a, b, ratio2) {
|
|
const [aStart, aEnd] = spanRange(a);
|
|
const [bStart, bEnd] = spanRange(b);
|
|
const x0 = lerp2(aStart.x, bStart.x, ratio2);
|
|
const y0 = lerp2(aStart.y, bStart.y, ratio2);
|
|
const x1 = lerp2(aEnd.x, bEnd.x, ratio2);
|
|
const y1 = lerp2(aEnd.y, bEnd.y, ratio2);
|
|
return [
|
|
{ x: x0, y: y0 },
|
|
{ x: x1, y: y1 }
|
|
];
|
|
}
|
|
function plotInterpolatedSpans(path, a, b, ratio2, moveTo2, reversed) {
|
|
const [{ x: x0, y: y0 }, { x: x1, y: y1 }] = interpolatedSpanRange(a, b, ratio2);
|
|
plotStart(path, moveTo2, x0, y0, x1, y1, reversed);
|
|
if (a.type === "cubic" && b.type === "cubic") {
|
|
const cp1x = lerp2(a.cp1x, b.cp1x, ratio2);
|
|
const cp1y = lerp2(a.cp1y, b.cp1y, ratio2);
|
|
const cp2x = lerp2(a.cp2x, b.cp2x, ratio2);
|
|
const cp2y = lerp2(a.cp2y, b.cp2y, ratio2);
|
|
plotCubic(path, x0, y0, cp1x, cp1y, cp2x, cp2y, x1, y1, reversed);
|
|
} else if (a.type === "step" && b.type === "step") {
|
|
const stepX = lerp2(a.stepX, b.stepX, ratio2);
|
|
plotStep(path, x0, y0, x1, y1, stepX, reversed);
|
|
} else if (a.type === "linear" && b.type === "linear") {
|
|
plotLinear(path, x0, y0, x1, y1, reversed);
|
|
} else {
|
|
let defaultStepX;
|
|
if (a.type === "step") {
|
|
defaultStepX = a.stepX;
|
|
} else if (b.type === "step") {
|
|
defaultStepX = b.stepX;
|
|
} else {
|
|
defaultStepX = (x0 + x1) / 2;
|
|
}
|
|
const as = spanSupertype(a, defaultStepX);
|
|
const bs = spanSupertype(b, defaultStepX);
|
|
const leftCp1x = lerp2(as.leftCp1x, bs.leftCp1x, ratio2);
|
|
const leftCp1y = lerp2(as.leftCp1y, bs.leftCp1y, ratio2);
|
|
const leftCp2x = lerp2(as.leftCp2x, bs.leftCp2x, ratio2);
|
|
const leftCp2y = lerp2(as.leftCp2y, bs.leftCp2y, ratio2);
|
|
const stepX = lerp2(as.stepX, bs.stepX, ratio2);
|
|
const stepY0 = lerp2(as.stepY0, bs.stepY0, ratio2);
|
|
const stepY1 = lerp2(as.stepY1, bs.stepY1, ratio2);
|
|
const rightCp1x = lerp2(as.rightCp1x, bs.rightCp1x, ratio2);
|
|
const rightCp1y = lerp2(as.rightCp1y, bs.rightCp1y, ratio2);
|
|
const rightCp2x = lerp2(as.rightCp2x, bs.rightCp2x, ratio2);
|
|
const rightCp2y = lerp2(as.rightCp2y, bs.rightCp2y, ratio2);
|
|
if (reversed) {
|
|
path.cubicCurveTo(rightCp2x, rightCp2y, rightCp1x, rightCp1y, stepX, stepY1);
|
|
path.lineTo(stepX, stepY0);
|
|
path.cubicCurveTo(leftCp2x, leftCp2y, leftCp1x, leftCp1y, x0, y0);
|
|
} else {
|
|
path.cubicCurveTo(leftCp1x, leftCp1y, leftCp2x, leftCp2y, stepX, stepY0);
|
|
path.lineTo(stepX, stepY1);
|
|
path.cubicCurveTo(rightCp1x, rightCp1y, rightCp2x, rightCp2y, x1, y1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/lineInterpolationUtil.ts
|
|
var MAX_CATEGORIES = 1e3;
|
|
var CollapseMode = /* @__PURE__ */ ((CollapseMode3) => {
|
|
CollapseMode3[CollapseMode3["Zero"] = 0] = "Zero";
|
|
CollapseMode3[CollapseMode3["Split"] = 1] = "Split";
|
|
return CollapseMode3;
|
|
})(CollapseMode || {});
|
|
function integratedCategoryMatch(a, b) {
|
|
if (a == null || b == null)
|
|
return false;
|
|
if (typeof a !== "object" || typeof b !== "object")
|
|
return false;
|
|
if ("id" in a && "id" in b) {
|
|
return a.id === b.id;
|
|
}
|
|
return a.toString() === b.toString();
|
|
}
|
|
function toAxisValue(value) {
|
|
return transformIntegratedCategoryValue(value).valueOf();
|
|
}
|
|
function scale(val, scaling) {
|
|
if (!scaling)
|
|
return Number.NaN;
|
|
if (val instanceof Date) {
|
|
val = val.getTime();
|
|
}
|
|
if (scaling.type === "continuous" && typeof val === "number") {
|
|
const domainRatio = (val - scaling.domain[0]) / (scaling.domain[1] - scaling.domain[0]);
|
|
return domainRatio * (scaling.range[1] - scaling.range[0]) + scaling.range[0];
|
|
}
|
|
if (scaling.type === "log" && typeof val === "number") {
|
|
return scaling.convert(val);
|
|
}
|
|
if (scaling.type !== "category")
|
|
return Number.NaN;
|
|
if (isUnitTimeCategoryScaling(scaling)) {
|
|
if (typeof val === "number") {
|
|
const { firstBandTime, intervalMs, bandCount, inset, step } = scaling;
|
|
const matchingIndex2 = Math.round((val - firstBandTime) / intervalMs);
|
|
if (matchingIndex2 >= 0 && matchingIndex2 < bandCount) {
|
|
return inset + step * matchingIndex2;
|
|
}
|
|
}
|
|
return Number.NaN;
|
|
}
|
|
const axisValue = toAxisValue(val);
|
|
let matchingIndex = scaling.domain.findIndex((d) => toAxisValue(d) === axisValue);
|
|
if (matchingIndex === -1) {
|
|
matchingIndex = scaling.domain.findIndex((d) => integratedCategoryMatch(val, d));
|
|
}
|
|
if (matchingIndex >= 0) {
|
|
return scaling.inset + scaling.step * matchingIndex;
|
|
}
|
|
return Number.NaN;
|
|
}
|
|
function getAxisIndices({ data }, values) {
|
|
return data.map((datum, datumIndex) => ({
|
|
xValue0Index: values.indexOf(toAxisValue(datum.xValue0)),
|
|
xValue1Index: values.indexOf(toAxisValue(datum.xValue1)),
|
|
datumIndex
|
|
}));
|
|
}
|
|
function isValidScaling(data) {
|
|
return Object.values(data.scales).every((s) => {
|
|
if (s.type === "category") {
|
|
if (isUnitTimeCategoryScaling(s)) {
|
|
return s.bandCount < MAX_CATEGORIES;
|
|
}
|
|
return s.domain.length < MAX_CATEGORIES;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
function validateCategorySorting(newData, oldData) {
|
|
const oldScale = oldData.scales.x;
|
|
const newScale = newData.scales.x;
|
|
if (oldScale?.type !== "category" || newScale?.type !== "category")
|
|
return true;
|
|
if (isUnitTimeCategoryScaling(oldScale) || isUnitTimeCategoryScaling(newScale)) {
|
|
return true;
|
|
}
|
|
let x0 = -Infinity;
|
|
for (const oldValue of oldScale.domain) {
|
|
const x = scale(oldValue, newScale);
|
|
if (!Number.isFinite(x))
|
|
continue;
|
|
if (x < x0) {
|
|
return false;
|
|
} else {
|
|
x0 = x;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function validateAxisEntriesOrder(axisValues, data) {
|
|
let x0 = -Infinity;
|
|
for (const axisValue of axisValues) {
|
|
const x = scale(axisValue.value, data.scales.x);
|
|
if (!Number.isFinite(x))
|
|
continue;
|
|
if (x < x0) {
|
|
return false;
|
|
} else {
|
|
x0 = x;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
function spanAxisContext(newData, oldData) {
|
|
const allAxisEntries = /* @__PURE__ */ new Map();
|
|
for (const { xValue0, xValue1 } of newData.data) {
|
|
const xValue0Value = toAxisValue(xValue0);
|
|
const xValue1Value = toAxisValue(xValue1);
|
|
allAxisEntries.set(xValue0Value, xValue0).set(xValue1Value, xValue1);
|
|
}
|
|
const newAxisEntries = Array.from(allAxisEntries, ([axisValue, value]) => ({ axisValue, value }));
|
|
newAxisEntries.sort((a, b) => {
|
|
return scale(a.value, newData.scales.x) - scale(b.value, newData.scales.x);
|
|
});
|
|
const exclusivelyOldAxisEntries = [];
|
|
for (const { xValue0, xValue1 } of oldData.data) {
|
|
const xValue0Value = toAxisValue(xValue0);
|
|
const xValue1Value = toAxisValue(xValue1);
|
|
if (!allAxisEntries.has(xValue0Value)) {
|
|
allAxisEntries.set(xValue0Value, xValue0);
|
|
exclusivelyOldAxisEntries.push({ axisValue: xValue0Value, value: xValue0 });
|
|
}
|
|
if (!allAxisEntries.has(xValue1Value)) {
|
|
allAxisEntries.set(xValue1Value, xValue1);
|
|
exclusivelyOldAxisEntries.push({ axisValue: xValue1Value, value: xValue1 });
|
|
}
|
|
}
|
|
exclusivelyOldAxisEntries.sort((a, b) => {
|
|
return scale(a.value, oldData.scales.x) - scale(b.value, oldData.scales.x);
|
|
});
|
|
const axisEntries = newAxisEntries;
|
|
let insertionIndex = 0;
|
|
for (const oldAxisEntry of exclusivelyOldAxisEntries) {
|
|
for (let i = axisEntries.length - 1; i >= insertionIndex; i -= 1) {
|
|
const oldValueX = scale(oldAxisEntry.value, oldData.scales.x);
|
|
const newValueX = scale(axisEntries[i].value, oldData.scales.x);
|
|
if (oldValueX > newValueX) {
|
|
insertionIndex = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
axisEntries.splice(insertionIndex, 0, oldAxisEntry);
|
|
insertionIndex += 1;
|
|
}
|
|
if (!validateAxisEntriesOrder(axisEntries, oldData))
|
|
return;
|
|
const axisValues = axisEntries.map((axisEntry) => axisEntry.axisValue);
|
|
const oldDataAxisIndices = getAxisIndices(oldData, axisValues);
|
|
const newDataAxisIndices = getAxisIndices(newData, axisValues);
|
|
return { axisValues, oldDataAxisIndices, newDataAxisIndices };
|
|
}
|
|
function clipSpan(span, data, axisValues, xValue0Index, xIndices) {
|
|
if (xIndices.xValue1Index === xIndices.xValue0Index + 1)
|
|
return span;
|
|
const range3 = spanRange(span);
|
|
let start2;
|
|
let end3;
|
|
if (data.scales.x?.type === "category") {
|
|
const step = (range3[1].x - range3[0].x) / (xIndices.xValue1Index - xIndices.xValue0Index);
|
|
start2 = range3[0].x + (xValue0Index - xIndices.xValue0Index) * step;
|
|
end3 = start2 + step;
|
|
} else {
|
|
const xValue0 = axisValues[xValue0Index];
|
|
const xValue1 = axisValues[xValue0Index + 1];
|
|
start2 = scale(xValue0, data.scales.x);
|
|
end3 = scale(xValue1, data.scales.x);
|
|
}
|
|
return clipSpanX(span, start2, end3);
|
|
}
|
|
function axisZeroSpan(span, data) {
|
|
const [r0, r1] = spanRange(span);
|
|
const y0 = scale(0, data.scales.y);
|
|
return rescaleSpan(span, { x: r0.x, y: y0 }, { x: r1.x, y: y0 });
|
|
}
|
|
function collapseSpanToMidpoint(span) {
|
|
const [r0, r1] = spanRange(span);
|
|
return collapseSpanToPoint(span, {
|
|
x: (r0.x + r1.x) / 2,
|
|
y: (r0.y + r1.y) / 2
|
|
});
|
|
}
|
|
function collapseSpan(span, collapseMode, data, axisIndices, indices, range3) {
|
|
let xValue;
|
|
let yValue;
|
|
if (indices.xValue0Index >= range3.xValue1Index) {
|
|
const datumIndex = axisIndices.findLast((i) => i.xValue1Index <= range3.xValue1Index)?.datumIndex;
|
|
const datum = datumIndex == null ? void 0 : data.data[datumIndex];
|
|
xValue = datum?.xValue1;
|
|
yValue = datum?.yValue1;
|
|
} else if (indices.xValue0Index <= range3.xValue0Index) {
|
|
const datumIndex = axisIndices.find((i) => i.xValue0Index >= range3.xValue0Index)?.datumIndex;
|
|
const datum = datumIndex == null ? void 0 : data.data[datumIndex];
|
|
xValue = datum?.xValue0;
|
|
yValue = datum?.yValue0;
|
|
}
|
|
if (xValue == null || yValue == null) {
|
|
switch (collapseMode) {
|
|
case 0 /* Zero */:
|
|
return axisZeroSpan(span, data);
|
|
case 1 /* Split */:
|
|
return collapseSpanToMidpoint(span);
|
|
}
|
|
}
|
|
const x = scale(xValue, data.scales.x);
|
|
const y = scale(yValue, data.scales.y);
|
|
const point = { x, y };
|
|
return rescaleSpan(span, point, point);
|
|
}
|
|
function zeroDataSpan(spanDatum, zeroData) {
|
|
if (zeroData == null)
|
|
return;
|
|
const newSpanXValue0 = toAxisValue(spanDatum.xValue0);
|
|
const newSpanXValue1 = toAxisValue(spanDatum.xValue1);
|
|
return zeroData.find(
|
|
(zeroSpanDatum) => toAxisValue(zeroSpanDatum.xValue0) === newSpanXValue0 && toAxisValue(zeroSpanDatum.xValue1) === newSpanXValue1
|
|
)?.span;
|
|
}
|
|
function addSpan(newData, collapseMode, newAxisIndices, newIndices, oldZeroData, range3, out) {
|
|
const newSpanDatum = newData.data[newIndices.datumIndex];
|
|
const newSpan = newSpanDatum.span;
|
|
const zeroSpan = zeroDataSpan(newSpanDatum, oldZeroData);
|
|
if (zeroSpan == null) {
|
|
const oldSpan = collapseSpan(newSpan, collapseMode, newData, newAxisIndices, newIndices, range3);
|
|
out.added.push({ from: oldSpan, to: newSpan });
|
|
} else {
|
|
out.removed.push({ from: zeroSpan, to: zeroSpan });
|
|
out.moved.push({ from: zeroSpan, to: newSpan });
|
|
out.added.push({ from: newSpan, to: newSpan });
|
|
}
|
|
}
|
|
function removeSpan(oldData, collapseMode, oldAxisIndices, oldIndices, newZeroData, range3, out) {
|
|
const oldSpanDatum = oldData.data[oldIndices.datumIndex];
|
|
const oldSpan = oldSpanDatum.span;
|
|
const zeroSpan = zeroDataSpan(oldSpanDatum, newZeroData);
|
|
if (zeroSpan == null) {
|
|
const newSpan = collapseSpan(oldSpan, collapseMode, oldData, oldAxisIndices, oldIndices, range3);
|
|
out.removed.push({ from: oldSpan, to: newSpan });
|
|
} else {
|
|
out.removed.push({ from: oldSpan, to: oldSpan });
|
|
out.moved.push({ from: oldSpan, to: zeroSpan });
|
|
out.added.push({ from: zeroSpan, to: zeroSpan });
|
|
}
|
|
}
|
|
function alignSpanToContainingSpan(span, axisValues, preData, postData, postSpanIndices) {
|
|
const xScale = postData.scales.x;
|
|
const startXValue0 = axisValues[postSpanIndices.xValue0Index];
|
|
const endXValue1 = axisValues[postSpanIndices.xValue1Index];
|
|
let startDatum;
|
|
let endDatum;
|
|
if (xScale?.type === "continuous" || xScale?.type === "log") {
|
|
startDatum = preData.data.findLast((spanDatum) => toAxisValue(spanDatum.xValue0) <= startXValue0);
|
|
endDatum = preData.data.find((spanDatum) => toAxisValue(spanDatum.xValue1) >= endXValue1);
|
|
} else {
|
|
startDatum = preData.data.find((spanDatum) => toAxisValue(spanDatum.xValue0) === startXValue0);
|
|
endDatum = preData.data.find((spanDatum) => toAxisValue(spanDatum.xValue1) === endXValue1);
|
|
}
|
|
if (startDatum == null || endDatum == null)
|
|
return;
|
|
const [{ x: x0 }, { x: x1 }] = spanRange(span);
|
|
const startX = scale(startDatum.xValue0, preData.scales.x);
|
|
const startY = scale(startDatum.yValue0, preData.scales.y);
|
|
const endX = scale(endDatum.xValue1, preData.scales.x);
|
|
const endY = scale(endDatum.yValue1, preData.scales.y);
|
|
let altSpan = postData.data[postSpanIndices.datumIndex].span;
|
|
altSpan = rescaleSpan(altSpan, { x: startX, y: startY }, { x: endX, y: endY });
|
|
altSpan = clipSpanX(altSpan, x0, x1);
|
|
return altSpan;
|
|
}
|
|
function appendSpanPhases(newData, oldData, collapseMode, axisValues, xValue0Index, newAxisIndices, oldAxisIndices, range3, out) {
|
|
const xValue1Index = xValue0Index + 1;
|
|
const oldIndices = oldAxisIndices.find((i) => i.xValue0Index <= xValue0Index && i.xValue1Index >= xValue1Index);
|
|
const newIndices = newAxisIndices.find((i) => i.xValue0Index <= xValue0Index && i.xValue1Index >= xValue1Index);
|
|
const oldZeroData = oldData.zeroData;
|
|
const newZeroData = newData.zeroData;
|
|
if (oldIndices == null && newIndices != null) {
|
|
addSpan(newData, collapseMode, newAxisIndices, newIndices, oldZeroData, range3, out);
|
|
return;
|
|
} else if (oldIndices != null && newIndices == null) {
|
|
removeSpan(oldData, collapseMode, oldAxisIndices, oldIndices, newZeroData, range3, out);
|
|
return;
|
|
} else if (oldIndices == null || newIndices == null) {
|
|
return;
|
|
}
|
|
let ordering;
|
|
if (oldIndices.xValue0Index === newIndices.xValue0Index && oldIndices.xValue1Index === newIndices.xValue1Index) {
|
|
ordering = 0;
|
|
} else if (oldIndices.xValue0Index <= newIndices.xValue0Index && oldIndices.xValue1Index >= newIndices.xValue1Index) {
|
|
ordering = -1;
|
|
} else if (oldIndices.xValue0Index >= newIndices.xValue0Index && oldIndices.xValue1Index <= newIndices.xValue1Index) {
|
|
ordering = 1;
|
|
} else {
|
|
ordering = 0;
|
|
}
|
|
const oldSpanDatum = oldData.data[oldIndices.datumIndex];
|
|
const clippedOldSpanOldScale = clipSpan(oldSpanDatum.span, oldData, axisValues, xValue0Index, oldIndices);
|
|
const newSpanDatum = newData.data[newIndices.datumIndex];
|
|
const clippedNewSpanNewScale = clipSpan(newSpanDatum.span, newData, axisValues, xValue0Index, newIndices);
|
|
if (ordering === 1) {
|
|
const clippedPostRemoveOldSpanOldScale = alignSpanToContainingSpan(
|
|
clippedOldSpanOldScale,
|
|
axisValues,
|
|
oldData,
|
|
newData,
|
|
newIndices
|
|
);
|
|
if (clippedPostRemoveOldSpanOldScale == null) {
|
|
removeSpan(oldData, collapseMode, oldAxisIndices, oldIndices, newZeroData, range3, out);
|
|
} else {
|
|
out.removed.push({ from: clippedOldSpanOldScale, to: clippedPostRemoveOldSpanOldScale });
|
|
out.moved.push({ from: clippedPostRemoveOldSpanOldScale, to: clippedNewSpanNewScale });
|
|
out.added.push({ from: clippedNewSpanNewScale, to: clippedNewSpanNewScale });
|
|
}
|
|
} else if (ordering === -1) {
|
|
const clippedPreAddedNewSpanNewScale = alignSpanToContainingSpan(
|
|
clippedNewSpanNewScale,
|
|
axisValues,
|
|
newData,
|
|
oldData,
|
|
oldIndices
|
|
);
|
|
if (clippedPreAddedNewSpanNewScale == null) {
|
|
addSpan(newData, collapseMode, newAxisIndices, newIndices, oldZeroData, range3, out);
|
|
} else {
|
|
out.removed.push({ from: clippedOldSpanOldScale, to: clippedOldSpanOldScale });
|
|
out.moved.push({ from: clippedOldSpanOldScale, to: clippedPreAddedNewSpanNewScale });
|
|
out.added.push({ from: clippedPreAddedNewSpanNewScale, to: clippedNewSpanNewScale });
|
|
}
|
|
} else {
|
|
out.removed.push({ from: clippedOldSpanOldScale, to: clippedOldSpanOldScale });
|
|
out.moved.push({ from: clippedOldSpanOldScale, to: clippedNewSpanNewScale });
|
|
out.added.push({ from: clippedNewSpanNewScale, to: clippedNewSpanNewScale });
|
|
}
|
|
}
|
|
function phaseAnimation(axisContext, newData, oldData, collapseMode) {
|
|
const out = {
|
|
removed: [],
|
|
moved: [],
|
|
added: []
|
|
};
|
|
const { axisValues, oldDataAxisIndices, newDataAxisIndices } = axisContext;
|
|
const range3 = {
|
|
xValue0Index: Math.max(
|
|
oldDataAxisIndices.at(0)?.xValue0Index ?? -Infinity,
|
|
newDataAxisIndices.at(0)?.xValue0Index ?? -Infinity
|
|
),
|
|
xValue1Index: Math.min(
|
|
oldDataAxisIndices.at(-1)?.xValue1Index ?? Infinity,
|
|
newDataAxisIndices.at(-1)?.xValue1Index ?? Infinity
|
|
)
|
|
};
|
|
for (let xValue0Index = 0; xValue0Index < axisValues.length - 1; xValue0Index += 1) {
|
|
appendSpanPhases(
|
|
newData,
|
|
oldData,
|
|
collapseMode,
|
|
axisValues,
|
|
xValue0Index,
|
|
newDataAxisIndices,
|
|
oldDataAxisIndices,
|
|
range3,
|
|
out
|
|
);
|
|
}
|
|
return out;
|
|
}
|
|
function replotXAnimation(newData, oldData) {
|
|
const removed = [];
|
|
const moved = [];
|
|
const added = [];
|
|
for (let i = 0; i < oldData.data.length; i += 1) {
|
|
const oldSpan = oldData.data[i].span;
|
|
const newSpan = newData.data[i].span;
|
|
removed.push({ from: oldSpan, to: oldSpan });
|
|
moved.push({ from: oldSpan, to: newSpan });
|
|
added.push({ from: newSpan, to: newSpan });
|
|
}
|
|
return {
|
|
removed,
|
|
moved,
|
|
added
|
|
};
|
|
}
|
|
function resetSpan(data, spanDatum, collapseMode) {
|
|
const { span } = spanDatum;
|
|
switch (collapseMode) {
|
|
case 0 /* Zero */:
|
|
return zeroDataSpan(spanDatum, data.zeroData) ?? axisZeroSpan(span, data);
|
|
case 1 /* Split */:
|
|
return collapseSpanToMidpoint(span);
|
|
}
|
|
}
|
|
function resetAnimation(newData, oldData, collapseMode) {
|
|
const added = [];
|
|
const removed = [];
|
|
for (const oldSpanDatum of oldData.data) {
|
|
const oldSpan = oldSpanDatum.span;
|
|
const collapsedSpan = resetSpan(oldData, oldSpanDatum, collapseMode);
|
|
removed.push({ from: oldSpan, to: collapsedSpan });
|
|
}
|
|
for (const newSpanDatum of newData.data) {
|
|
const newSpan = newSpanDatum.span;
|
|
const collapsedSpan = resetSpan(newData, newSpanDatum, collapseMode);
|
|
added.push({ from: collapsedSpan, to: newSpan });
|
|
}
|
|
return {
|
|
removed,
|
|
moved: [],
|
|
added
|
|
};
|
|
}
|
|
function pairUpSpans(newData, oldData, collapseMode) {
|
|
if (!isValidScaling(newData) || !isValidScaling(oldData))
|
|
return;
|
|
if (!validateCategorySorting(newData, oldData))
|
|
return;
|
|
const axisContext = spanAxisContext(newData, oldData);
|
|
if (axisContext == null) {
|
|
return resetAnimation(newData, oldData, collapseMode);
|
|
} else if (axisContext.axisValues.length === axisContext.oldDataAxisIndices.length + axisContext.newDataAxisIndices.length + 2) {
|
|
return replotXAnimation(newData, oldData);
|
|
} else {
|
|
return phaseAnimation(axisContext, newData, oldData, collapseMode);
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/lineUtil.ts
|
|
function interpolatePoints(points, interpolation) {
|
|
const pointsIter = points.map((point) => point.point);
|
|
let spans = linearPoints(pointsIter);
|
|
switch (interpolation.type) {
|
|
case "linear":
|
|
break;
|
|
case "smooth":
|
|
spans = smoothPoints(pointsIter, interpolation.tension);
|
|
break;
|
|
case "step":
|
|
spans = stepPoints(pointsIter, interpolation.position);
|
|
break;
|
|
}
|
|
return spans.map(function spanToLinePathSpan(span, i) {
|
|
return {
|
|
span,
|
|
xValue0: points[i].xDatum,
|
|
yValue0: points[i].yDatum,
|
|
xValue1: points[i + 1].xDatum,
|
|
yValue1: points[i + 1].yDatum
|
|
};
|
|
});
|
|
}
|
|
function pointsEq(a, b, delta5 = 1e-3) {
|
|
return Math.abs(a.x - b.x) < delta5 && Math.abs(a.y - b.y) < delta5;
|
|
}
|
|
function plotLinePathStroke({ path }, spans) {
|
|
let lastPoint;
|
|
for (const { span } of spans) {
|
|
const [start2, end3] = spanRange(span);
|
|
const join = lastPoint != null && pointsEq(lastPoint, start2) ? 2 /* Skip */ : 0 /* MoveTo */;
|
|
plotSpan(path, span, join, false);
|
|
lastPoint = end3;
|
|
}
|
|
}
|
|
function plotInterpolatedLinePathStroke(ratio2, path, spans) {
|
|
let lastPoint;
|
|
for (const span of spans) {
|
|
const [start2, end3] = interpolatedSpanRange(span.from, span.to, ratio2);
|
|
const join = lastPoint != null && pointsEq(lastPoint, start2) ? 2 /* Skip */ : 0 /* MoveTo */;
|
|
plotInterpolatedSpans(path.path, span.from, span.to, ratio2, join, false);
|
|
lastPoint = end3;
|
|
}
|
|
}
|
|
function prepareLinePathStrokeAnimationFns(status, spans, visibleToggleMode, targetOpacity = 1) {
|
|
const removePhaseFn = (ratio2, path) => plotInterpolatedLinePathStroke(ratio2, path, spans.removed);
|
|
const updatePhaseFn = (ratio2, path) => plotInterpolatedLinePathStroke(ratio2, path, spans.moved);
|
|
const addPhaseFn = (ratio2, path) => plotInterpolatedLinePathStroke(ratio2, path, spans.added);
|
|
const pathProperties = prepareLinePathPropertyAnimation(status, visibleToggleMode, targetOpacity);
|
|
return { status, path: { addPhaseFn, updatePhaseFn, removePhaseFn }, pathProperties };
|
|
}
|
|
function prepareLinePathPropertyAnimation(status, visibleToggleMode, targetOpacity = 1) {
|
|
const phase = visibleToggleMode === "none" ? "updated" : status;
|
|
const result = {
|
|
fromFn(path, datum) {
|
|
const out = { phase: NODE_UPDATE_STATE_TO_PHASE_MAPPING[phase] };
|
|
const segments = path.previousDatum ?? datum;
|
|
if (segments != null) {
|
|
out.segments = segments;
|
|
}
|
|
if (status === "removed") {
|
|
out.finish = { visible: false };
|
|
} else if (status === "added") {
|
|
out.start = { visible: true };
|
|
}
|
|
return out;
|
|
},
|
|
toFn(_path, datum) {
|
|
const out = { phase: NODE_UPDATE_STATE_TO_PHASE_MAPPING[phase] };
|
|
const segments = datum;
|
|
if (segments != null) {
|
|
out.segments = segments;
|
|
}
|
|
return out;
|
|
}
|
|
};
|
|
if (visibleToggleMode === "fade") {
|
|
return {
|
|
fromFn(path, datum) {
|
|
const opacity = status === "added" ? 0 : targetOpacity;
|
|
const segments = status === "removed" ? path.previousDatum ?? datum : datum;
|
|
return { ...result.fromFn(path, datum), opacity, segments };
|
|
},
|
|
toFn(path, datum) {
|
|
const opacity = status === "removed" ? 0 : targetOpacity;
|
|
const segments = status === "removed" ? path.previousDatum ?? datum : datum;
|
|
return { ...result.toFn(path, datum), opacity, segments };
|
|
}
|
|
};
|
|
}
|
|
return result;
|
|
}
|
|
function prepareLinePathAnimation(newData, oldData, diff9, targetOpacity = 1) {
|
|
const isCategoryBased = newData.scales.x?.type === "category";
|
|
const wasCategoryBased = oldData.scales.x?.type === "category";
|
|
if (isCategoryBased !== wasCategoryBased || !isScaleValid(newData.scales.x) || !isScaleValid(oldData.scales.x)) {
|
|
return;
|
|
}
|
|
if (newData.strokeData == null || oldData.strokeData == null) {
|
|
return;
|
|
}
|
|
let status = "updated";
|
|
if (oldData.visible && !newData.visible) {
|
|
status = "removed";
|
|
} else if (!oldData.visible && newData.visible) {
|
|
status = "added";
|
|
}
|
|
const strokeSpans = pairUpSpans(
|
|
{ scales: newData.scales, data: newData.strokeData.spans },
|
|
{ scales: oldData.scales, data: oldData.strokeData.spans },
|
|
1 /* Split */
|
|
);
|
|
if (strokeSpans == null)
|
|
return;
|
|
const stroke3 = prepareLinePathStrokeAnimationFns(status, strokeSpans, "fade", targetOpacity);
|
|
const hasMotion = (diff9?.changed ?? true) || !areScalingEqual(newData.scales.x, oldData.scales.x) || !areScalingEqual(newData.scales.y, oldData.scales.y) || status !== "updated";
|
|
return { status, stroke: stroke3, hasMotion };
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/barUtil.ts
|
|
function checkCrisp(scale2, visibleRange, smallestDataInterval, largestDataInterval) {
|
|
if (visibleRange != null) {
|
|
const [visibleMin, visibleMax] = visibleRange;
|
|
const isZoomed = visibleMin !== 0 || visibleMax !== 1;
|
|
if (isZoomed)
|
|
return false;
|
|
}
|
|
if (ContinuousScale.is(scale2)) {
|
|
const spacing = scale2.calcBandwidth(largestDataInterval) - scale2.calcBandwidth(smallestDataInterval);
|
|
if (spacing > 0 && spacing < 1)
|
|
return false;
|
|
}
|
|
if (BandScale.is(scale2)) {
|
|
const { bandwidth, step } = scale2;
|
|
if (bandwidth > 0 && bandwidth < 1)
|
|
return false;
|
|
const spacing = step - bandwidth;
|
|
if (spacing > 0 && spacing < 1)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
var isDatumNegative = (datum) => {
|
|
return isNegative(datum.yValue ?? 0);
|
|
};
|
|
function collapsedStartingBarPosition(isVertical, axes, mode) {
|
|
const { startingX, startingY } = getStartingValues(isVertical, axes);
|
|
const calculate = (datum, prevDatum) => {
|
|
let x = isVertical ? datum.x : startingX;
|
|
let y = isVertical ? startingY : datum.y;
|
|
let width2 = isVertical ? datum.width : 0;
|
|
let height2 = isVertical ? 0 : datum.height;
|
|
const { opacity = 1 } = datum;
|
|
if (prevDatum && (Number.isNaN(x) || Number.isNaN(y))) {
|
|
({ x, y } = prevDatum);
|
|
width2 = isVertical ? prevDatum.width : 0;
|
|
height2 = isVertical ? 0 : prevDatum.height;
|
|
if (isVertical && !isDatumNegative(prevDatum)) {
|
|
y += prevDatum.height;
|
|
} else if (!isVertical && isDatumNegative(prevDatum)) {
|
|
x += prevDatum.width;
|
|
}
|
|
}
|
|
let clipBBox;
|
|
if (datum.clipBBox == null) {
|
|
clipBBox = void 0;
|
|
} else if (isDatumNegative(datum)) {
|
|
clipBBox = isVertical ? new BBox(x, y - height2, width2, height2) : new BBox(x - width2, y, width2, height2);
|
|
} else {
|
|
clipBBox = new BBox(x, y, width2, height2);
|
|
}
|
|
return { x, y, width: width2, height: height2, clipBBox, opacity };
|
|
};
|
|
return { isVertical, calculate, mode };
|
|
}
|
|
function midpointStartingBarPosition(isVertical, mode) {
|
|
return {
|
|
isVertical,
|
|
calculate: (datum) => {
|
|
return {
|
|
x: isVertical ? datum.x : datum.x + datum.width / 2,
|
|
y: isVertical ? datum.y + datum.height / 2 : datum.y,
|
|
width: isVertical ? datum.width : 0,
|
|
height: isVertical ? 0 : datum.height,
|
|
clipBBox: datum.clipBBox,
|
|
opacity: datum.opacity ?? 1
|
|
};
|
|
},
|
|
mode
|
|
};
|
|
}
|
|
function prepareBarAnimationFunctions(initPos, unknownStatus) {
|
|
const isRemoved = (datum) => datum == null || Number.isNaN(datum.x) || Number.isNaN(datum.y);
|
|
const fromFn = (rect2, datum, status) => {
|
|
if (status === "updated" && isRemoved(datum)) {
|
|
status = "removed";
|
|
} else if (status === "updated" && isRemoved(rect2.previousDatum)) {
|
|
status = "added";
|
|
}
|
|
let source;
|
|
if (status === "unknown" || status === "added") {
|
|
if (rect2.previousDatum == null && initPos.mode === "fade") {
|
|
source = {
|
|
...resetBarSelectionsFn(rect2, datum),
|
|
opacity: 0
|
|
};
|
|
} else {
|
|
source = initPos.calculate(datum, rect2.previousDatum);
|
|
}
|
|
if (status === "unknown") {
|
|
status = unknownStatus;
|
|
}
|
|
} else {
|
|
source = {
|
|
x: rect2.x,
|
|
y: rect2.y,
|
|
width: rect2.width,
|
|
height: rect2.height,
|
|
clipBBox: rect2.clipBBox,
|
|
opacity: rect2.opacity ?? 1
|
|
};
|
|
}
|
|
const phase = NODE_UPDATE_STATE_TO_PHASE_MAPPING[status];
|
|
return { ...source, phase };
|
|
};
|
|
const toFn = (rect2, datum, status) => {
|
|
if (status === "removed" && rect2.datum == null && initPos.mode === "fade") {
|
|
return { ...resetBarSelectionsFn(rect2, datum), opacity: 0 };
|
|
} else if (status === "removed" || isRemoved(datum)) {
|
|
return initPos.calculate(datum, rect2.previousDatum);
|
|
} else {
|
|
return {
|
|
x: datum.x,
|
|
y: datum.y,
|
|
width: datum.width,
|
|
height: datum.height,
|
|
clipBBox: datum.clipBBox,
|
|
opacity: datum.opacity ?? 1
|
|
};
|
|
}
|
|
};
|
|
const applyFn = (rect2, datum, status) => {
|
|
rect2.resetAnimationProperties(datum.x, datum.y, datum.width, datum.height, datum.opacity ?? 1, datum.clipBBox);
|
|
rect2.crisp = status === "end" && (rect2.datum?.crisp ?? false);
|
|
};
|
|
return { toFn, fromFn, applyFn };
|
|
}
|
|
function getStartingValues(isVertical, axes) {
|
|
const axis = axes[isVertical ? "y" /* Y */ : "x" /* X */];
|
|
let startingX = Infinity;
|
|
let startingY = 0;
|
|
if (!axis) {
|
|
return { startingX, startingY };
|
|
}
|
|
if (isVertical) {
|
|
startingY = axis.scale.convert(ContinuousScale.is(axis.scale) ? 0 : Math.max(...axis.range));
|
|
} else {
|
|
startingX = axis.scale.convert(ContinuousScale.is(axis.scale) ? 0 : Math.min(...axis.range));
|
|
}
|
|
return { startingX, startingY };
|
|
}
|
|
function resetBarSelectionsFn(rect2, { x, y, width: width2, height: height2, clipBBox, opacity = 1 }) {
|
|
return { x, y, width: width2, height: height2, clipBBox, opacity, crisp: rect2.datum?.crisp ?? false };
|
|
}
|
|
function resetBarSelectionsDirect(selections) {
|
|
for (const selection of selections) {
|
|
const nodes = selection.nodes();
|
|
selection.batchedUpdate(function resetBarNodes() {
|
|
for (const node of nodes) {
|
|
const datum = node.datum;
|
|
if (datum == null)
|
|
continue;
|
|
node.resetAnimationProperties(
|
|
datum.x,
|
|
datum.y,
|
|
datum.width,
|
|
datum.height,
|
|
datum.opacity ?? 1,
|
|
datum.clipBBox
|
|
);
|
|
node.crisp = datum.crisp ?? false;
|
|
}
|
|
selection.cleanup();
|
|
});
|
|
}
|
|
}
|
|
function computeBarFocusBounds(series, datum) {
|
|
if (datum === void 0)
|
|
return void 0;
|
|
const { x, y, width: width2, height: height2 } = datum;
|
|
return Transformable.toCanvas(series.contentGroup, new BBox(x, y, width2, height2));
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/cartesianSeriesUtil.ts
|
|
function upsertNodeDatum(ctx, params, createNode, updateNode) {
|
|
const canReuseNode = ctx.canIncrementallyUpdate && ctx.nodeIndex < ctx.nodes.length;
|
|
let node;
|
|
if (canReuseNode) {
|
|
node = ctx.nodes[ctx.nodeIndex];
|
|
updateNode(ctx, node, params);
|
|
} else {
|
|
node = createNode(ctx, params);
|
|
if (node != null) {
|
|
ctx.nodes.push(node);
|
|
}
|
|
}
|
|
ctx.nodeIndex++;
|
|
return node;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/areaUtil.ts
|
|
function plotAreaPathFill({ path }, { spans, phantomSpans }) {
|
|
let phantomSpanIndex = 0;
|
|
let sp = { x: Number.NaN, y: Number.NaN };
|
|
let pp = { x: Number.NaN, y: Number.NaN };
|
|
for (let i = 0; i < spans.length; i += 1) {
|
|
const { span } = spans[i];
|
|
const { span: phantomSpan } = phantomSpans[i];
|
|
const { 0: sp0, 1: sp1 } = spanRange(span);
|
|
const { 0: pp0, 1: pp1 } = spanRange(phantomSpan);
|
|
if (pointsEq(sp, sp0) && pointsEq(pp, pp0)) {
|
|
plotSpan(path, span, 2 /* Skip */, false);
|
|
} else {
|
|
for (let j = i - 1; j >= phantomSpanIndex; j -= 1) {
|
|
plotSpan(path, phantomSpans[j].span, 1 /* LineTo */, true);
|
|
}
|
|
path.closePath();
|
|
plotSpan(path, span, 0 /* MoveTo */, false);
|
|
phantomSpanIndex = i;
|
|
}
|
|
sp = sp1;
|
|
pp = pp1;
|
|
}
|
|
for (let j = spans.length - 1; j >= phantomSpanIndex; j -= 1) {
|
|
plotSpan(path, phantomSpans[j].span, 1 /* LineTo */, true);
|
|
}
|
|
path.closePath();
|
|
}
|
|
function plotInterpolatedAreaSeriesFillSpans(ratio2, { path }, spans, fillPhantomSpans) {
|
|
for (let i = 0; i < spans.length; i += 1) {
|
|
const span = spans[i];
|
|
const reversedPhantomSpan = fillPhantomSpans[i];
|
|
plotInterpolatedSpans(path, span.from, span.to, ratio2, 0 /* MoveTo */, false);
|
|
plotInterpolatedSpans(path, reversedPhantomSpan.from, reversedPhantomSpan.to, ratio2, 1 /* LineTo */, true);
|
|
path.closePath();
|
|
}
|
|
}
|
|
function prepareAreaFillAnimationFns(status, spans, fillPhantomSpans, visibleToggleMode) {
|
|
const removePhaseFn = (ratio2, path) => plotInterpolatedAreaSeriesFillSpans(ratio2, path, spans.removed, fillPhantomSpans.removed);
|
|
const updatePhaseFn = (ratio2, path) => plotInterpolatedAreaSeriesFillSpans(ratio2, path, spans.moved, fillPhantomSpans.moved);
|
|
const addPhaseFn = (ratio2, path) => plotInterpolatedAreaSeriesFillSpans(ratio2, path, spans.added, fillPhantomSpans.added);
|
|
const pathProperties = prepareLinePathPropertyAnimation(status, visibleToggleMode);
|
|
return { status, path: { addPhaseFn, updatePhaseFn, removePhaseFn }, pathProperties };
|
|
}
|
|
function prepareAreaPathAnimation(newData, oldData) {
|
|
const isCategoryBased = newData.scales.x?.type === "category";
|
|
const wasCategoryBased = oldData.scales.x?.type === "category";
|
|
if (isCategoryBased !== wasCategoryBased || !isScaleValid(newData.scales.x) || !isScaleValid(oldData.scales.x)) {
|
|
return;
|
|
}
|
|
let status = "updated";
|
|
if (oldData.visible && !newData.visible) {
|
|
status = "removed";
|
|
} else if (!oldData.visible && newData.visible) {
|
|
status = "added";
|
|
}
|
|
const fillSpans = pairUpSpans(
|
|
{ scales: newData.scales, data: newData.fillData.spans },
|
|
{ scales: oldData.scales, data: oldData.fillData.spans },
|
|
0 /* Zero */
|
|
);
|
|
if (fillSpans == null)
|
|
return;
|
|
const fillPhantomSpans = pairUpSpans(
|
|
{ scales: newData.scales, data: newData.fillData.phantomSpans },
|
|
{ scales: oldData.scales, data: oldData.fillData.phantomSpans },
|
|
0 /* Zero */
|
|
);
|
|
if (fillPhantomSpans == null)
|
|
return;
|
|
const strokeSpans = pairUpSpans(
|
|
{
|
|
scales: newData.scales,
|
|
data: newData.strokeData.spans,
|
|
zeroData: newData.fillData.phantomSpans
|
|
},
|
|
{
|
|
scales: oldData.scales,
|
|
data: oldData.strokeData.spans,
|
|
zeroData: oldData.fillData.phantomSpans
|
|
},
|
|
0 /* Zero */
|
|
);
|
|
if (strokeSpans == null)
|
|
return;
|
|
const fadeMode = "none";
|
|
const fill = prepareAreaFillAnimationFns(status, fillSpans, fillPhantomSpans, fadeMode);
|
|
const stroke3 = prepareLinePathStrokeAnimationFns(status, strokeSpans, fadeMode);
|
|
return { status, fill, stroke: stroke3 };
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/diffUtil.ts
|
|
function calculateDataDiff(seriesId, datumSelection, getDatumId, contextNodeData, previousContextNodeData, processedData, processedDataUpdated) {
|
|
let dataDiff = processedData?.reduced?.diff?.[seriesId];
|
|
if (dataDiff?.changed) {
|
|
return dataDiff;
|
|
}
|
|
if (!processedDataUpdated) {
|
|
return {
|
|
changed: false,
|
|
added: /* @__PURE__ */ new Set(),
|
|
updated: /* @__PURE__ */ new Set(),
|
|
removed: /* @__PURE__ */ new Set(),
|
|
moved: /* @__PURE__ */ new Set()
|
|
};
|
|
}
|
|
const scalingChanged = hasScalingChanged(contextNodeData, previousContextNodeData);
|
|
if (dataDiff == null && processedData?.reduced?.diff != null) {
|
|
dataDiff = {
|
|
changed: true,
|
|
added: /* @__PURE__ */ new Set(),
|
|
updated: /* @__PURE__ */ new Set(),
|
|
removed: /* @__PURE__ */ new Set(),
|
|
moved: /* @__PURE__ */ new Set()
|
|
};
|
|
if (scalingChanged) {
|
|
dataDiff.updated = new Set(Array.from(datumSelection, ({ datum }) => getDatumId(datum)));
|
|
} else {
|
|
dataDiff.added = new Set(Array.from(datumSelection, ({ datum }) => getDatumId(datum)));
|
|
}
|
|
} else if (scalingChanged) {
|
|
dataDiff = {
|
|
changed: true,
|
|
added: /* @__PURE__ */ new Set(),
|
|
updated: new Set(Array.from(datumSelection, ({ datum }) => getDatumId(datum))),
|
|
removed: /* @__PURE__ */ new Set(),
|
|
moved: /* @__PURE__ */ new Set()
|
|
};
|
|
}
|
|
return dataDiff;
|
|
}
|
|
function isGroupScaleContext(ctx) {
|
|
return typeof ctx === "object" && ctx !== null && "groupScale" in ctx;
|
|
}
|
|
function hasScalingChanged(contextNodeData, previousContextNodeData) {
|
|
if (!previousContextNodeData)
|
|
return false;
|
|
const scales = contextNodeData.scales;
|
|
const prevScales = previousContextNodeData.scales;
|
|
if (!areScalingEqual(scales.x, prevScales.x))
|
|
return true;
|
|
if (!areScalingEqual(scales.y, prevScales.y))
|
|
return true;
|
|
if (!isGroupScaleContext(contextNodeData) || !isGroupScaleContext(previousContextNodeData))
|
|
return false;
|
|
const groupScale = contextNodeData.groupScale;
|
|
const prevGroupScale = previousContextNodeData.groupScale;
|
|
return !areScalingEqual(groupScale, prevGroupScale);
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/markerUtil.ts
|
|
function markerFadeInAnimation({ id }, animationManager, status, options, ...markerSelections) {
|
|
const params = {
|
|
...options,
|
|
phase: options?.phase ?? (status ? NODE_UPDATE_STATE_TO_PHASE_MAPPING[status] : "trailing")
|
|
};
|
|
staticFromToMotion(id, "markers", animationManager, markerSelections, { opacity: 0 }, { opacity: 1 }, params);
|
|
for (const s of markerSelections) {
|
|
s.cleanup();
|
|
}
|
|
}
|
|
function markerScaleInAnimation({ id }, animationManager, ...markerSelections) {
|
|
staticFromToMotion(
|
|
id,
|
|
"markers",
|
|
animationManager,
|
|
markerSelections,
|
|
{ scalingX: 0, scalingY: 0 },
|
|
{ scalingX: 1, scalingY: 1 },
|
|
{ phase: "initial" }
|
|
);
|
|
for (const s of markerSelections) {
|
|
s.cleanup();
|
|
}
|
|
}
|
|
function markerSwipeScaleInAnimation({ id, nodeDataDependencies }, animationManager, options, ...markerSelections) {
|
|
const seriesWidth = nodeDataDependencies.seriesRectWidth;
|
|
const fromFn = (_, datum) => {
|
|
const x = datum.midPoint?.x ?? seriesWidth;
|
|
let delay = clamp(0, inverseEaseOut(x / seriesWidth), 1);
|
|
if (Number.isNaN(delay)) {
|
|
delay = 0;
|
|
}
|
|
return {
|
|
scalingX: 0,
|
|
scalingY: 0,
|
|
delay: options?.delay ?? delay,
|
|
duration: options?.duration ?? QUICK_TRANSITION,
|
|
phase: options?.phase ?? "initial",
|
|
start: options?.start,
|
|
finish: options?.finish
|
|
};
|
|
};
|
|
const toFn = () => {
|
|
return { scalingX: 1, scalingY: 1 };
|
|
};
|
|
fromToMotion(id, "markers", animationManager, markerSelections, { fromFn, toFn });
|
|
}
|
|
function resetMarkerFn(_node) {
|
|
return { opacity: 1, scalingX: 1, scalingY: 1 };
|
|
}
|
|
function resetMarkerSelectionsDirect(selections) {
|
|
for (const selection of selections) {
|
|
const nodes = selection.nodes();
|
|
selection.batchedUpdate(function resetMarkerNodes() {
|
|
for (const node of nodes) {
|
|
const datum = node.datum;
|
|
if (datum?.point == null)
|
|
continue;
|
|
const { x, y } = datum.point;
|
|
if (!Number.isFinite(x) || !Number.isFinite(y))
|
|
continue;
|
|
node.resetAnimationProperties(x, y, node.size, 1, 1, 1);
|
|
}
|
|
selection.cleanup();
|
|
});
|
|
}
|
|
}
|
|
function resetMarkerPositionFn(_node, datum) {
|
|
return {
|
|
x: datum.point?.x ?? Number.NaN,
|
|
y: datum.point?.y ?? Number.NaN,
|
|
scalingCenterX: datum.point?.x ?? Number.NaN,
|
|
scalingCenterY: datum.point?.y ?? Number.NaN
|
|
};
|
|
}
|
|
function computeMarkerFocusBounds(series, { datumIndex }) {
|
|
const nodeData = series.getNodeData();
|
|
if (nodeData === void 0)
|
|
return void 0;
|
|
const datum = nodeData[datumIndex];
|
|
const { point } = datum ?? {};
|
|
if (datum == null || point == null)
|
|
return void 0;
|
|
const style2 = series.getFormattedMarkerStyle(datum);
|
|
const anchor = Marker.anchor(style2.shape);
|
|
const size = point.focusSize ?? style2.size;
|
|
const paddedSize = 4 + size;
|
|
const paddedRadius = paddedSize / 2;
|
|
const anchorX = (anchor.x - 0.5) * size;
|
|
const anchorY = (anchor.y - 0.5) * size;
|
|
const x = datum.point.x - paddedRadius - anchorX;
|
|
const y = datum.point.y - paddedRadius - anchorY;
|
|
return Transformable.toCanvas(series.contentGroup, new BBox(x, y, paddedSize, paddedSize));
|
|
}
|
|
function markerEnabled(dataCount, scale2, marker, markerStyle = marker) {
|
|
const enabled = markerStyle.enabled ?? marker.enabled;
|
|
if (!enabled)
|
|
return false;
|
|
const minSpacing = 1;
|
|
const step = scale2.step ?? findRangeExtent(scale2.range) / Math.max(1, dataCount);
|
|
return step > minSpacing;
|
|
}
|
|
function getMarkerStyles(series, line, marker, inheritedStyle) {
|
|
inheritedStyle ?? (inheritedStyle = {
|
|
stroke: line.stroke,
|
|
strokeOpacity: line.strokeOpacity,
|
|
strokeWidth: line.strokeWidth
|
|
});
|
|
return highlightStates.reduce(
|
|
(styles, state) => {
|
|
styles[state] = series.getMarkerStyle(
|
|
marker,
|
|
{},
|
|
void 0,
|
|
{ highlightState: state },
|
|
void 0,
|
|
inheritedStyle
|
|
);
|
|
return styles;
|
|
},
|
|
{}
|
|
);
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/pathUtil.ts
|
|
function pathSwipeInAnimation({ id, visible, nodeDataDependencies }, animationManager, ...paths) {
|
|
const { seriesRectWidth: width2, seriesRectHeight: height2 } = nodeDataDependencies;
|
|
staticFromToMotion(
|
|
id,
|
|
"path_properties",
|
|
animationManager,
|
|
paths,
|
|
{ clipX: 0 },
|
|
{ clipX: width2 },
|
|
{
|
|
phase: "initial",
|
|
start: { clip: true, clipY: height2, visible },
|
|
finish: { clip: false, visible }
|
|
}
|
|
);
|
|
}
|
|
function pathFadeInAnimation({ id }, subId, animationManager, phase = "add", ...selection) {
|
|
staticFromToMotion(id, subId, animationManager, selection, { opacity: 0 }, { opacity: 1 }, { phase });
|
|
}
|
|
function buildResetPathFn(opts) {
|
|
return (_node) => ({
|
|
visible: opts.getVisible(),
|
|
opacity: opts.getOpacity(),
|
|
clipScalingX: 1,
|
|
clip: false
|
|
});
|
|
}
|
|
function updateClipPath({ nodeDataDependencies }, path) {
|
|
const toFinite = (value) => Number.isFinite(value) ? value : 0;
|
|
path.clipX = toFinite(nodeDataDependencies.seriesRectWidth);
|
|
path.clipY = toFinite(nodeDataDependencies.seriesRectHeight);
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/polar/polarSeries.ts
|
|
var DEFAULT_POLAR_DIRECTION_KEYS = {
|
|
["angle" /* Angle */]: ["angleKey"],
|
|
["radius" /* Radius */]: ["radiusKey"]
|
|
};
|
|
var DEFAULT_POLAR_DIRECTION_NAMES = {
|
|
["angle" /* Angle */]: ["angleName"],
|
|
["radius" /* Radius */]: ["radiusName"]
|
|
};
|
|
var PolarSeries = class extends DataModelSeries {
|
|
constructor({
|
|
categoryKey,
|
|
pickModes = [1 /* NEAREST_NODE */, 0 /* EXACT_SHAPE_MATCH */],
|
|
canHaveAxes = false,
|
|
animationResetFns,
|
|
...opts
|
|
}) {
|
|
super({
|
|
...opts,
|
|
categoryKey,
|
|
pickModes,
|
|
canHaveAxes
|
|
});
|
|
this.directions = ["angle" /* Angle */, "radius" /* Radius */];
|
|
this.itemGroup = this.contentGroup.appendChild(new Group({ name: "items" }));
|
|
this.nodeData = [];
|
|
this.itemSelection = Selection.select(
|
|
this.itemGroup,
|
|
() => this.nodeFactory(),
|
|
false
|
|
);
|
|
this.labelSelection = Selection.select(
|
|
this.labelGroup,
|
|
() => this.labelFactory(),
|
|
false
|
|
);
|
|
this.highlightSelection = Selection.select(
|
|
this.highlightNodeGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.highlightLabelSelection = Selection.select(
|
|
this.highlightLabelGroup,
|
|
() => this.labelFactory()
|
|
);
|
|
/**
|
|
* The center of the polar series (for example, the center of a pie).
|
|
* If the polar chart has multiple series, all of them will have their
|
|
* center set to the same value as a result of the polar chart layout.
|
|
* The center coordinates are not supposed to be set by the user.
|
|
*/
|
|
this.centerX = 0;
|
|
this.centerY = 0;
|
|
/**
|
|
* The maximum radius the series can use.
|
|
* This value is set automatically as a result of the polar chart layout
|
|
* and is not supposed to be set by the user.
|
|
*/
|
|
this.radius = 0;
|
|
/**
|
|
* The largest marker size of any series in the same chart. Used to determine the extent of pointer interaction
|
|
* with the series.
|
|
*/
|
|
this.maxChartMarkerSize = 0;
|
|
this.animationResetFns = animationResetFns;
|
|
this.animationState = new StateMachine(
|
|
"empty",
|
|
{
|
|
empty: {
|
|
update: {
|
|
target: "ready",
|
|
action: (data) => this.animateEmptyUpdateReady(data)
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
ready: {
|
|
updateData: "waiting",
|
|
clear: "clearing",
|
|
highlight: (data) => this.animateReadyHighlight(data),
|
|
highlightMarkers: (data) => this.animateReadyHighlightMarkers(data),
|
|
resize: (data) => this.animateReadyResize(data),
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
waiting: {
|
|
update: {
|
|
target: "ready",
|
|
action: (data) => this.animateWaitingUpdateReady(data)
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
clearing: {
|
|
update: {
|
|
target: "empty",
|
|
action: (data) => this.animateClearingUpdateEmpty(data)
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
}
|
|
},
|
|
() => this.checkProcessedDataAnimatable()
|
|
);
|
|
this.cleanup.register(
|
|
this.ctx.eventsHub.on("legend:item-click", (event) => this.onLegendItemClick(event)),
|
|
this.ctx.eventsHub.on("legend:item-double-click", (event) => this.onLegendItemDoubleClick(event))
|
|
);
|
|
}
|
|
getItemNodes() {
|
|
return [...this.itemGroup.children()];
|
|
}
|
|
getNodeData() {
|
|
return this.nodeData;
|
|
}
|
|
getKeyAxis(direction) {
|
|
if (direction === "angle" /* Angle */)
|
|
return this.properties.angleKeyAxis;
|
|
if (direction === "radius" /* Radius */)
|
|
return this.properties.radiusKeyAxis;
|
|
}
|
|
setZIndex(zIndex) {
|
|
super.setZIndex(zIndex);
|
|
this.contentGroup.zIndex = [zIndex, 1 /* FOREGROUND */];
|
|
this.highlightGroup.zIndex = [zIndex, 2 /* HIGHLIGHT */];
|
|
this.labelGroup.zIndex = [zIndex, 3 /* LABEL */];
|
|
}
|
|
resetAnimation(phase) {
|
|
if (phase === "initial") {
|
|
this.animationState.transition("reset");
|
|
} else if (phase === "ready") {
|
|
this.animationState.transition("skip");
|
|
}
|
|
}
|
|
labelFactory() {
|
|
const text2 = new Text();
|
|
text2.pointerEvents = 1 /* None */;
|
|
return text2;
|
|
}
|
|
getInnerRadius() {
|
|
return 0;
|
|
}
|
|
computeLabelsBBox(_options, _seriesRect) {
|
|
return null;
|
|
}
|
|
getShapeFillBBox() {
|
|
const outerRadius = this.radius;
|
|
return {
|
|
series: new BBox(-outerRadius, -outerRadius, outerRadius * 2, outerRadius * 2),
|
|
axis: new BBox(-outerRadius, -outerRadius, outerRadius * 2, outerRadius * 2)
|
|
};
|
|
}
|
|
resetAllAnimation() {
|
|
const { item, label } = this.animationResetFns ?? {};
|
|
this.ctx.animationManager.stopByAnimationGroupId(this.id);
|
|
if (item) {
|
|
resetMotion([this.itemSelection, this.highlightSelection], item);
|
|
}
|
|
if (label) {
|
|
resetMotion([this.labelSelection, this.highlightLabelSelection], label);
|
|
}
|
|
this.itemSelection.cleanup();
|
|
this.labelSelection.cleanup();
|
|
this.highlightSelection.cleanup();
|
|
this.highlightLabelSelection.cleanup();
|
|
}
|
|
animateEmptyUpdateReady(_data) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
this.resetAllAnimation();
|
|
}
|
|
animateWaitingUpdateReady(_data) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
this.resetAllAnimation();
|
|
}
|
|
animateReadyHighlight(_data) {
|
|
const { item, label } = this.animationResetFns ?? {};
|
|
if (item) {
|
|
resetMotion([this.highlightSelection], item);
|
|
}
|
|
if (label) {
|
|
resetMotion([this.highlightLabelSelection], label);
|
|
}
|
|
}
|
|
animateReadyHighlightMarkers(_data) {
|
|
}
|
|
animateReadyResize(_data) {
|
|
this.resetAllAnimation();
|
|
}
|
|
animateClearingUpdateEmpty(_data) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
this.resetAllAnimation();
|
|
}
|
|
computeFocusBounds(opts) {
|
|
const datum = this.getNodeData()?.[opts.datumIndex];
|
|
if (datum !== void 0) {
|
|
return this.itemSelection.select((node) => node instanceof Path && node.datum === datum)[0];
|
|
}
|
|
return void 0;
|
|
}
|
|
getSeriesRange() {
|
|
return [Number.NaN, Number.NaN];
|
|
}
|
|
isSeriesHighlighted(highlightedDatum, legendItemValues) {
|
|
if (!this.properties.highlight.enabled) {
|
|
return false;
|
|
}
|
|
const { series, legendItemName: activeLegendItemName, datumIndex } = highlightedDatum ?? {};
|
|
const legendItemName = typeof datumIndex === "number" ? legendItemValues?.[datumIndex] : void 0;
|
|
return series === this || legendItemName != null && legendItemName === activeLegendItemName;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/hierarchy/hierarchySeriesProperties.ts
|
|
var HierarchyHighlightState = /* @__PURE__ */ ((HierarchyHighlightState3) => {
|
|
HierarchyHighlightState3[HierarchyHighlightState3["None"] = 0] = "None";
|
|
HierarchyHighlightState3[HierarchyHighlightState3["Item"] = 1] = "Item";
|
|
HierarchyHighlightState3[HierarchyHighlightState3["OtherItem"] = 2] = "OtherItem";
|
|
HierarchyHighlightState3[HierarchyHighlightState3["Branch"] = 3] = "Branch";
|
|
HierarchyHighlightState3[HierarchyHighlightState3["OtherBranch"] = 4] = "OtherBranch";
|
|
return HierarchyHighlightState3;
|
|
})(HierarchyHighlightState || {});
|
|
function toHierarchyHighlightString(state) {
|
|
const unreachable = (a) => a;
|
|
switch (state) {
|
|
case 1 /* Item */:
|
|
return "highlighted-item";
|
|
case 2 /* OtherItem */:
|
|
return "unhighlighted-item";
|
|
case 3 /* Branch */:
|
|
return "highlighted-branch";
|
|
case 4 /* OtherBranch */:
|
|
return "unhighlighted-branch";
|
|
case 0 /* None */:
|
|
return "none";
|
|
default:
|
|
return unreachable(state);
|
|
}
|
|
}
|
|
var HierarchySeriesProperties = class extends SeriesProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.childrenKey = "children";
|
|
this.fills = Object.values(DEFAULT_FILLS);
|
|
this.strokes = Object.values(DEFAULT_STROKES);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HierarchySeriesProperties.prototype, "childrenKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HierarchySeriesProperties.prototype, "sizeKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HierarchySeriesProperties.prototype, "colorKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HierarchySeriesProperties.prototype, "colorName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HierarchySeriesProperties.prototype, "fills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HierarchySeriesProperties.prototype, "strokes", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HierarchySeriesProperties.prototype, "colorRange", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/series/hierarchy/hierarchySeries.ts
|
|
var _HierarchyNode = class _HierarchyNode {
|
|
constructor(series, itemId, datumIndex, datum, sizeValue, colorValue, sumSize, depth, parent, children, style2) {
|
|
this.series = series;
|
|
this.itemId = itemId;
|
|
this.datumIndex = datumIndex;
|
|
this.datum = datum;
|
|
this.sizeValue = sizeValue;
|
|
this.colorValue = colorValue;
|
|
this.sumSize = sumSize;
|
|
this.depth = depth;
|
|
this.parent = parent;
|
|
this.children = children;
|
|
this.style = style2;
|
|
this.midPoint = { x: 0, y: 0 };
|
|
}
|
|
get hasChildren() {
|
|
return this.children.length > 0;
|
|
}
|
|
walk(callback2, order = _HierarchyNode.Walk.PreOrder) {
|
|
if (order === _HierarchyNode.Walk.PreOrder) {
|
|
callback2(this);
|
|
}
|
|
for (const child of this.children) {
|
|
child.walk(callback2, order);
|
|
}
|
|
if (order === _HierarchyNode.Walk.PostOrder) {
|
|
callback2(this);
|
|
}
|
|
}
|
|
find(predicate) {
|
|
if (predicate(this)) {
|
|
return this;
|
|
}
|
|
for (const child of this.children) {
|
|
const childResult = child.find(predicate);
|
|
if (childResult != void 0) {
|
|
return childResult;
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
*[Symbol.iterator]() {
|
|
yield this;
|
|
for (const child of this.children) {
|
|
yield* child;
|
|
}
|
|
}
|
|
};
|
|
_HierarchyNode.Walk = {
|
|
PreOrder: 0,
|
|
PostOrder: 1
|
|
};
|
|
var HierarchyNode = _HierarchyNode;
|
|
var HierarchySeries = class extends Series {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
pickModes: [1 /* NEAREST_NODE */, 0 /* EXACT_SHAPE_MATCH */]
|
|
});
|
|
this.colorDomain = [0, 0];
|
|
this.maxDepth = 0;
|
|
this.colorScale = new ColorScale();
|
|
this.animationState = new StateMachine(
|
|
"empty",
|
|
{
|
|
empty: {
|
|
update: {
|
|
target: "ready",
|
|
action: (data) => this.animateEmptyUpdateReady(data)
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
ready: {
|
|
updateData: "waiting",
|
|
clear: "clearing",
|
|
highlight: (data) => this.animateReadyHighlight(data),
|
|
resize: (data) => this.animateReadyResize(data),
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
waiting: {
|
|
update: {
|
|
target: "ready",
|
|
action: (data) => this.animateWaitingUpdateReady(data)
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
clearing: {
|
|
update: {
|
|
target: "empty",
|
|
action: (data) => this.animateClearingUpdateEmpty(data)
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
}
|
|
},
|
|
() => this.checkProcessedDataAnimatable()
|
|
);
|
|
}
|
|
resetAnimation(phase) {
|
|
if (phase === "initial") {
|
|
this.animationState.transition("reset");
|
|
} else if (phase === "ready") {
|
|
this.animationState.transition("skip");
|
|
}
|
|
}
|
|
processData() {
|
|
const { NodeClass } = this;
|
|
const { childrenKey, sizeKey, colorKey, colorRange } = this.properties;
|
|
let maxDepth = 0;
|
|
let minColor = Infinity;
|
|
let maxColor = -Infinity;
|
|
const createNode = (datum, indexPath, parent) => {
|
|
const depth = parent.depth == null ? 0 : parent.depth + 1;
|
|
const children = childrenKey == null ? void 0 : datum[childrenKey];
|
|
const isLeaf = children == null || children.length === 0;
|
|
let sizeValue = sizeKey == null ? void 0 : datum[sizeKey];
|
|
if (Number.isFinite(sizeValue)) {
|
|
sizeValue = Math.max(sizeValue, 0);
|
|
} else {
|
|
sizeValue = isLeaf ? 1 : 0;
|
|
}
|
|
const sumSize = sizeValue;
|
|
maxDepth = Math.max(maxDepth, depth);
|
|
const colorValue = colorKey == null ? void 0 : datum[colorKey];
|
|
if (typeof colorValue === "number") {
|
|
minColor = Math.min(minColor, colorValue);
|
|
maxColor = Math.max(maxColor, colorValue);
|
|
}
|
|
const style2 = this.getItemStyle({ datumIndex: indexPath, datum, depth, colorValue }, isLeaf, false);
|
|
return appendChildren(
|
|
new NodeClass(
|
|
this,
|
|
createDatumId(indexPath.join(";")),
|
|
indexPath,
|
|
datum,
|
|
sizeValue,
|
|
colorValue,
|
|
sumSize,
|
|
depth,
|
|
parent,
|
|
[],
|
|
style2
|
|
),
|
|
children
|
|
);
|
|
};
|
|
const appendChildren = (node, data) => {
|
|
const { datumIndex } = node;
|
|
if (data) {
|
|
for (const [childIndex, datum] of data.entries()) {
|
|
const child = createNode(datum, datumIndex.concat(childIndex), node);
|
|
node.children.push(child);
|
|
node.sumSize += child.sumSize;
|
|
}
|
|
}
|
|
return node;
|
|
};
|
|
const rootNode = appendChildren(
|
|
new NodeClass(this, "root", [], void 0, 0, void 0, 0, void 0, void 0, [], {}),
|
|
this.data?.data
|
|
);
|
|
const colorDomain = [minColor, maxColor];
|
|
this.colorScale.domain = minColor < maxColor ? [minColor, maxColor] : [0, 1];
|
|
this.colorScale.range = colorRange ?? ["black"];
|
|
this.colorScale.update();
|
|
this.rootNode = rootNode;
|
|
this.maxDepth = maxDepth;
|
|
this.colorDomain = colorDomain;
|
|
}
|
|
update({ seriesRect }) {
|
|
this.updateSelections();
|
|
this.updateNodes();
|
|
const animationData = this.getAnimationData();
|
|
const resize = this.checkResize(seriesRect);
|
|
if (resize) {
|
|
this.animationState.transition("resize", animationData);
|
|
}
|
|
this.animationState.transition("update", animationData);
|
|
}
|
|
resetAllAnimation(_data) {
|
|
this.ctx.animationManager.stopByAnimationGroupId(this.id);
|
|
}
|
|
animateEmptyUpdateReady(data) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
this.resetAllAnimation(data);
|
|
}
|
|
animateWaitingUpdateReady(data) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
this.resetAllAnimation(data);
|
|
}
|
|
animateReadyHighlight(_data) {
|
|
}
|
|
animateReadyResize(data) {
|
|
this.resetAllAnimation(data);
|
|
}
|
|
animateClearingUpdateEmpty(data) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
this.resetAllAnimation(data);
|
|
}
|
|
getAnimationData() {
|
|
return {};
|
|
}
|
|
isProcessedDataAnimatable() {
|
|
return true;
|
|
}
|
|
checkProcessedDataAnimatable() {
|
|
if (!this.isProcessedDataAnimatable()) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
}
|
|
}
|
|
findNodeDatum(itemId) {
|
|
return this.rootNode?.find((n) => n.itemId === itemId);
|
|
}
|
|
dataCount() {
|
|
return Number.NaN;
|
|
}
|
|
getSeriesDomain() {
|
|
return { domain: [Number.NaN, Number.NaN] };
|
|
}
|
|
getSeriesRange() {
|
|
return [Number.NaN, Number.NaN];
|
|
}
|
|
getLegendData(legendType) {
|
|
const { colorKey, colorRange } = this.properties;
|
|
const {
|
|
id: seriesId,
|
|
ctx: { legendManager },
|
|
visible
|
|
} = this;
|
|
return legendType === "gradient" && colorKey != null && colorRange != null ? [
|
|
{
|
|
legendType: "gradient",
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId }),
|
|
seriesId,
|
|
series: this.getFormatterContext("color"),
|
|
colorRange,
|
|
colorDomain: this.colorDomain
|
|
}
|
|
] : [];
|
|
}
|
|
getDatumIdFromData(node) {
|
|
return node.datumIndex.join(":");
|
|
}
|
|
getDatumId(node) {
|
|
return this.getDatumIdFromData(node);
|
|
}
|
|
removeMeIndexPathForIndex(index) {
|
|
return this.datumSelection.at(index + 1)?.datum.datumIndex ?? [];
|
|
}
|
|
removeMeIndexForIndexPath(indexPath) {
|
|
for (const { index, datum } of this.datumSelection) {
|
|
if (arraysEqual(datum.datumIndex, indexPath)) {
|
|
return index - 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
pickFocus(opts) {
|
|
if (!this.rootNode?.children.length)
|
|
return void 0;
|
|
const index = clamp(0, opts.datumIndex - opts.datumIndexDelta, this.datumSelection.length - 1);
|
|
const { datumIndexDelta: childDelta, otherIndexDelta: depthDelta } = opts;
|
|
let path = this.removeMeIndexPathForIndex(index);
|
|
const currentNode = path.reduce((n, childIndex) => n.children[childIndex], this.rootNode);
|
|
if (depthDelta > 0 && currentNode.hasChildren) {
|
|
path = [...path, 0];
|
|
} else if (depthDelta < 0 && path.length > 1) {
|
|
path = path.slice(0, -1);
|
|
} else if (depthDelta === 0 && childDelta !== 0) {
|
|
const maxIndex = currentNode.parent.children.length - 1;
|
|
path = path.slice();
|
|
path[path.length - 1] = clamp(0, path.at(-1) + childDelta, maxIndex);
|
|
}
|
|
const nextNode = path.reduce((n, childIndex) => n.children[childIndex], this.rootNode);
|
|
const bounds = this.computeFocusBounds(this.datumSelection.at(index + 1));
|
|
if (bounds == null)
|
|
return;
|
|
return {
|
|
datum: nextNode,
|
|
datumIndex: this.removeMeIndexForIndexPath(path),
|
|
otherIndex: nextNode.depth,
|
|
bounds,
|
|
clipFocusBox: true
|
|
};
|
|
}
|
|
getDatumAriaText(datum, description) {
|
|
if (!(datum instanceof this.NodeClass)) {
|
|
logger_exports.error(`datum is not HierarchyNode: ${JSON.stringify(datum)}`);
|
|
return;
|
|
}
|
|
return this.ctx.localeManager.t("ariaAnnounceHierarchyDatum", {
|
|
level: (datum.depth ?? -1) + 1,
|
|
count: datum.children.length,
|
|
description
|
|
});
|
|
}
|
|
getCategoryValue(_datumIndex) {
|
|
return;
|
|
}
|
|
datumIndexForCategoryValue(_categoryValue) {
|
|
return;
|
|
}
|
|
getActiveHighlightNode() {
|
|
if (!this.properties.highlight.enabled) {
|
|
return void 0;
|
|
}
|
|
const highlightedNode = this.ctx.highlightManager?.getActiveHighlight();
|
|
if (highlightedNode?.series !== this) {
|
|
return void 0;
|
|
}
|
|
return highlightedNode;
|
|
}
|
|
getHierarchyHighlightState(isHighlight, highlightedNode, nodeDatum) {
|
|
if (isHighlight) {
|
|
return 1 /* Item */;
|
|
}
|
|
if (highlightedNode == null) {
|
|
return 0 /* None */;
|
|
}
|
|
const nodeRoot = nodeDatum.datumIndex?.[0];
|
|
const highlightRoot = highlightedNode.datumIndex?.[0];
|
|
if (nodeRoot == null || highlightRoot == null) {
|
|
return 0 /* None */;
|
|
}
|
|
return nodeRoot === highlightRoot ? 3 /* Branch */ : 4 /* OtherBranch */;
|
|
}
|
|
getHierarchyHighlightStyles(highlightState, highlight5) {
|
|
switch (highlightState) {
|
|
case 1 /* Item */:
|
|
return mergeDefaults(highlight5.highlightedItem, highlight5.highlightedBranch);
|
|
case 3 /* Branch */:
|
|
return mergeDefaults(highlight5.unhighlightedItem, highlight5.highlightedBranch);
|
|
case 4 /* OtherBranch */:
|
|
return highlight5.unhighlightedBranch;
|
|
default:
|
|
return void 0;
|
|
}
|
|
}
|
|
getHighlightStateString(_datum, isHighlight, datumIndex, _legendItemValues) {
|
|
if (!this.properties.highlight.enabled) {
|
|
return toHierarchyHighlightString(0 /* None */);
|
|
}
|
|
if (datumIndex == null) {
|
|
return toHierarchyHighlightString(0 /* None */);
|
|
}
|
|
const nodeDatum = datumIndex.reduce((node, idx) => node?.children[idx], this.rootNode);
|
|
const highlightedNode = this.getActiveHighlightNode();
|
|
if (nodeDatum == null) {
|
|
return toHierarchyHighlightString(0 /* None */);
|
|
}
|
|
const state = this.getHierarchyHighlightState(isHighlight ?? false, highlightedNode, nodeDatum);
|
|
return toHierarchyHighlightString(state);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/topology/mercatorScale.ts
|
|
var radsInDeg = Math.PI / 180;
|
|
var lonX = (lon) => lon * radsInDeg;
|
|
var latY = (lat) => -Math.log(Math.tan(Math.PI * 0.25 + lat * radsInDeg * 0.5));
|
|
var xLon = (x) => x / radsInDeg;
|
|
var yLat = (y) => (Math.atan(Math.exp(-y)) - Math.PI * 0.25) / (radsInDeg * 0.5);
|
|
var MercatorScale = class _MercatorScale extends AbstractScale {
|
|
constructor(domain, range3) {
|
|
super();
|
|
this.domain = domain;
|
|
this.range = range3;
|
|
this.type = "mercator";
|
|
this.defaultTickCount = 0;
|
|
this.bounds = _MercatorScale.bounds(domain);
|
|
}
|
|
static bounds(domain) {
|
|
const [[lon0, lat0], [lon1, lat1]] = domain;
|
|
const x0 = lonX(lon0);
|
|
const y0 = latY(lat0);
|
|
const x1 = lonX(lon1);
|
|
const y1 = latY(lat1);
|
|
return new BBox(Math.min(x0, x1), Math.min(y0, y1), Math.abs(x1 - x0), Math.abs(y1 - y0));
|
|
}
|
|
static fixedScale() {
|
|
return new _MercatorScale(
|
|
[
|
|
[xLon(0), yLat(0)],
|
|
[xLon(1), yLat(1)]
|
|
],
|
|
[
|
|
[0, 0],
|
|
[1, 1]
|
|
]
|
|
);
|
|
}
|
|
toDomain() {
|
|
return;
|
|
}
|
|
normalizeDomains(...domains) {
|
|
let x0 = -Infinity;
|
|
let x1 = Infinity;
|
|
let y0 = -Infinity;
|
|
let y1 = Infinity;
|
|
for (const input of domains) {
|
|
const domain = input.domain;
|
|
for (const [x, y] of domain) {
|
|
x0 = Math.min(x, x0);
|
|
x1 = Math.max(x, x1);
|
|
y0 = Math.min(y, y0);
|
|
y1 = Math.max(y, y1);
|
|
}
|
|
}
|
|
return {
|
|
domain: [
|
|
[x0, y0],
|
|
[x1, y1]
|
|
],
|
|
animatable: true
|
|
};
|
|
}
|
|
convert([lon, lat]) {
|
|
const [[x0, y0], [x1, y1]] = this.range;
|
|
const xScale = (x1 - x0) / this.bounds.width;
|
|
const yScale = (y1 - y0) / this.bounds.height;
|
|
return [(lonX(lon) - this.bounds.x) * xScale + x0, (latY(lat) - this.bounds.y) * yScale + y0];
|
|
}
|
|
invert([x, y]) {
|
|
const [[x0, y0], [x1, y1]] = this.range;
|
|
const xScale = (x1 - x0) / this.bounds.width;
|
|
const yScale = (y1 - y0) / this.bounds.height;
|
|
return [xLon((x - x0) / xScale + this.bounds.x), yLat((y - y0) / yScale + this.bounds.y)];
|
|
}
|
|
getDomainMinMax() {
|
|
return unpackDomainMinMax(this.domain);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/util/deferredExecutor.ts
|
|
var DeferredExecutor = class {
|
|
constructor(options) {
|
|
this.minimumDelay = options?.minimumDelay ?? 50;
|
|
this.timeout = options?.timeout ?? 100;
|
|
}
|
|
/**
|
|
* Schedule a computation for deferred execution.
|
|
* If something is already pending, it will be cancelled first.
|
|
*/
|
|
schedule(computation, onComplete) {
|
|
this.cancel();
|
|
this.pending = { computation, onComplete };
|
|
if (this.minimumDelay > 0) {
|
|
this.delayTimeoutId = setTimeout(() => {
|
|
this.delayTimeoutId = void 0;
|
|
this.scheduleIdleCallback();
|
|
}, this.minimumDelay);
|
|
} else {
|
|
this.scheduleIdleCallback();
|
|
}
|
|
}
|
|
/**
|
|
* Force immediate execution if pending.
|
|
* @returns The computation result, or undefined if nothing was pending.
|
|
*/
|
|
demand() {
|
|
if (!this.pending) {
|
|
return void 0;
|
|
}
|
|
this.cancelScheduled();
|
|
return this.execute();
|
|
}
|
|
/**
|
|
* Cancel any pending execution without running it.
|
|
*/
|
|
cancel() {
|
|
this.cancelScheduled();
|
|
this.pending = void 0;
|
|
}
|
|
/**
|
|
* Check if there's pending work.
|
|
*/
|
|
isPending() {
|
|
return this.pending != null;
|
|
}
|
|
scheduleIdleCallback() {
|
|
const window = getWindow();
|
|
const remainingTimeout = Math.max(0, this.timeout - this.minimumDelay);
|
|
if (typeof window.requestIdleCallback === "function") {
|
|
this.idleCallbackId = window.requestIdleCallback(this.execute.bind(this), { timeout: remainingTimeout });
|
|
} else {
|
|
this.idleCallbackId = setTimeout(() => this.execute(), remainingTimeout);
|
|
}
|
|
}
|
|
cancelScheduled() {
|
|
if (this.delayTimeoutId != null) {
|
|
clearTimeout(this.delayTimeoutId);
|
|
this.delayTimeoutId = void 0;
|
|
}
|
|
if (this.idleCallbackId == null) {
|
|
return;
|
|
}
|
|
const window = getWindow();
|
|
if (typeof window.cancelIdleCallback === "function") {
|
|
window.cancelIdleCallback(this.idleCallbackId);
|
|
} else {
|
|
clearTimeout(this.idleCallbackId);
|
|
}
|
|
this.idleCallbackId = void 0;
|
|
}
|
|
execute() {
|
|
const pending = this.pending;
|
|
if (!pending) {
|
|
return void 0;
|
|
}
|
|
this.pending = void 0;
|
|
this.delayTimeoutId = void 0;
|
|
this.idleCallbackId = void 0;
|
|
const result = pending.computation();
|
|
pending.onComplete(result);
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/aggregationManager.ts
|
|
var AggregationManager = class {
|
|
constructor() {
|
|
this._dataLength = 0;
|
|
this.executor = new DeferredExecutor();
|
|
}
|
|
get filters() {
|
|
return this._filters;
|
|
}
|
|
/**
|
|
* Perform aggregation with deferred computation of coarser levels.
|
|
* Returns immediate filters suitable for current zoom level.
|
|
*
|
|
* @param options.computePartial - Function to compute partial aggregation (receives existing filters for reuse)
|
|
* @param options.computeFull - Function to compute full aggregation (receives existing filters for reuse)
|
|
* @param options.targetRange - Current pixel range for determining which level to compute immediately
|
|
* @returns The computed filters (immediate level for partial, or all levels for full)
|
|
*/
|
|
aggregate(options) {
|
|
this.executor.cancel();
|
|
if (options.targetRange > 1 && options.computePartial) {
|
|
const partialResult = options.computePartial(this._filters);
|
|
if (partialResult) {
|
|
const { immediate, computeRemaining } = partialResult;
|
|
if (computeRemaining) {
|
|
this.executor.schedule(computeRemaining, (remaining) => {
|
|
this.mergeFilters(remaining);
|
|
});
|
|
}
|
|
this._filters = immediate;
|
|
return immediate;
|
|
}
|
|
}
|
|
this._filters = options.computeFull(this._filters);
|
|
return this._filters;
|
|
}
|
|
/**
|
|
* Ensure we have an aggregation level suitable for the given range.
|
|
* Forces deferred computation if needed.
|
|
*/
|
|
ensureLevelForRange(range3) {
|
|
const hasLevel = this._filters?.some((f) => f.maxRange > range3);
|
|
if (!hasLevel && this.executor.isPending()) {
|
|
const remaining = this.executor.demand();
|
|
if (remaining) {
|
|
this.mergeFilters(remaining);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Get the best filter for a given range.
|
|
*/
|
|
getFilterForRange(range3) {
|
|
return this._filters?.find((f) => f.maxRange > range3);
|
|
}
|
|
/**
|
|
* Cancel any pending deferred computation.
|
|
*/
|
|
cancel() {
|
|
this.executor.cancel();
|
|
}
|
|
/**
|
|
* Mark filters as stale or discard them based on data size change.
|
|
* When data size changes significantly (>10x), filters are discarded to prevent
|
|
* incorrect array reuse.
|
|
*/
|
|
markStale(dataLength) {
|
|
const ratio2 = this._dataLength > 0 ? dataLength / this._dataLength : 0;
|
|
if (ratio2 > 10 || ratio2 < 0.1 || this._dataLength === 0) {
|
|
this._filters = void 0;
|
|
this._dataLength = dataLength;
|
|
} else if (this._filters) {
|
|
for (const f of this._filters) {
|
|
f.stale = true;
|
|
}
|
|
} else {
|
|
this._dataLength = dataLength;
|
|
}
|
|
this.executor.cancel();
|
|
}
|
|
mergeFilters(deferredFilters) {
|
|
if (!this._filters || deferredFilters.length === 0)
|
|
return;
|
|
const allFilters = [...this._filters, ...deferredFilters];
|
|
allFilters.sort((a, b) => a.maxRange - b.maxRange);
|
|
this._filters = allFilters;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/axis/polarAxis.ts
|
|
var PolarAxis = class extends Axis {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.shape = "polygon";
|
|
this.innerRadiusRatio = 0;
|
|
this.defaultTickMinSpacing = 20;
|
|
}
|
|
update() {
|
|
super.update();
|
|
this.tickLineGroup.visible = this.tick.enabled;
|
|
this.tickLabelGroup.visible = this.label.enabled;
|
|
}
|
|
layoutCrossLines() {
|
|
const sideFlag = this.label.getSideFlag();
|
|
const crosslinesVisible = this.hasDefinedDomain() || this.hasVisibleSeries();
|
|
const { rotation, parallelFlipRotation, regularFlipRotation } = this.calculateRotations();
|
|
for (const crossLine of this.crossLines) {
|
|
crossLine.sideFlag = -sideFlag;
|
|
crossLine.direction = rotation === -Math.PI / 2 ? "angle" /* Angle */ : "radius" /* Radius */;
|
|
crossLine.parallelFlipRotation = parallelFlipRotation;
|
|
crossLine.regularFlipRotation = regularFlipRotation;
|
|
crossLine.calculateLayout?.(crosslinesVisible, this.reverse);
|
|
}
|
|
}
|
|
updatePosition() {
|
|
super.updatePosition();
|
|
const translationX = Math.floor(this.translation.x);
|
|
const translationY = Math.floor(this.translation.y);
|
|
this.tickLineGroup.translationX = translationX;
|
|
this.tickLineGroup.translationY = translationY;
|
|
this.tickLabelGroup.translationX = translationX;
|
|
this.tickLabelGroup.translationY = translationY;
|
|
this.crossLineRangeGroup.translationX = translationX;
|
|
this.crossLineRangeGroup.translationY = translationY;
|
|
this.crossLineLineGroup.translationX = translationX;
|
|
this.crossLineLineGroup.translationY = translationY;
|
|
this.crossLineLabelGroup.translationX = translationX;
|
|
this.crossLineLabelGroup.translationY = translationY;
|
|
this.tickLabelGroupSelection.each(resetAxisLabelSelectionFn());
|
|
}
|
|
computeLabelsBBox(_options, _seriesRect) {
|
|
return null;
|
|
}
|
|
computeRange() {
|
|
}
|
|
getAxisLinePoints() {
|
|
return void 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarAxis.prototype, "shape", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarAxis.prototype, "innerRadiusRatio", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/axis/discreteTimeAxis.ts
|
|
var DiscreteTimeAxis = class extends CategoryAxis {
|
|
calculateGridLine({ index: tickIndex, tickId, translation }, index, p1, p2, ticks) {
|
|
const { gridLine, horizontal, interval, range: range3 } = this;
|
|
if (interval.placement !== "between") {
|
|
return super.calculateGridLine({ index: tickIndex, tickId, translation }, index, p1, p2, ticks);
|
|
}
|
|
const prevTick = ticks[index - 1];
|
|
const offset = prevTick ? translation - (translation - prevTick.translation) / 2 : range3[0];
|
|
const [x1, y1, x2, y2] = horizontal ? [offset, Math.max(p1, p2), offset, Math.min(p1, p2)] : [Math.min(p1, p2), offset, Math.max(p1, p2), offset];
|
|
const { style: style2 } = gridLine;
|
|
const { stroke: stroke3, strokeWidth = 0, lineDash } = style2[tickIndex % style2.length] ?? {};
|
|
return { tickId, offset, x1, y1, x2, y2, stroke: stroke3, strokeWidth, lineDash };
|
|
}
|
|
calculateGridFills(ticks, p1, p2) {
|
|
if (this.interval.placement !== "between") {
|
|
return super.calculateGridFills(ticks, p1, p2);
|
|
}
|
|
return ticks.map((tick, index) => this.calculateGridFill(tick, index, tick.index, p1, p2, ticks));
|
|
}
|
|
calculateGridFill({ tickId, translation }, index, gridFillIndex, p1, p2, ticks) {
|
|
const { gridLine, horizontal, interval, range: range3 } = this;
|
|
if (interval.placement !== "between") {
|
|
return super.calculateGridFill({ tickId, translation }, index, gridFillIndex, p1, p2, ticks);
|
|
}
|
|
const prevTick = ticks[index - 1];
|
|
const nextTick = ticks[index + 1];
|
|
const startOffset = prevTick ? translation - (translation - prevTick.translation) / 2 : range3[0];
|
|
const endOffset = nextTick ? translation + (nextTick.translation - translation) / 2 : range3[1];
|
|
const [x1, y1, x2, y2] = horizontal ? [startOffset, Math.max(p1, p2), endOffset, Math.min(p1, p2)] : [Math.min(p1, p2), startOffset, Math.max(p1, p2), endOffset];
|
|
const { fill, fillOpacity } = gridLine.style[gridFillIndex % gridLine.style.length] ?? {};
|
|
return { tickId, x1, y1, x2, y2, fill, fillOpacity };
|
|
}
|
|
calculateTickLine({ isPrimary, tickId, translation }, index, direction, ticks, scrollbarThickness = 0) {
|
|
const { horizontal, interval, primaryTick, range: range3, tick } = this;
|
|
if (interval.placement !== "between") {
|
|
return super.calculateTickLine(
|
|
{ isPrimary, tickId, translation },
|
|
index,
|
|
direction,
|
|
ticks,
|
|
scrollbarThickness
|
|
);
|
|
}
|
|
const datumTick = isPrimary && primaryTick?.enabled ? primaryTick : tick;
|
|
const h = -direction * this.getTickSize(datumTick);
|
|
const prevTick = ticks[index - 1];
|
|
const offset = prevTick ? translation - (translation - prevTick.translation) / 2 : range3[0];
|
|
const tickOffset = -direction * (scrollbarThickness + this.getTickSpacing(datumTick));
|
|
const [x1, y1, x2, y2] = horizontal ? [offset, tickOffset, offset, tickOffset + h] : [tickOffset, offset, tickOffset + h, offset];
|
|
const { stroke: stroke3, width: strokeWidth } = datumTick;
|
|
const lineDash = void 0;
|
|
return { tickId, offset, x1, y1, x2, y2, stroke: stroke3, strokeWidth, lineDash };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/crossline/crossLineLabelPosition.ts
|
|
var horizontalCrosslineTranslationDirections = {
|
|
top: { xTranslationDirection: 0, yTranslationDirection: -1 },
|
|
bottom: { xTranslationDirection: 0, yTranslationDirection: 1 },
|
|
left: { xTranslationDirection: -1, yTranslationDirection: 0 },
|
|
right: { xTranslationDirection: 1, yTranslationDirection: 0 },
|
|
"top-left": { xTranslationDirection: 1, yTranslationDirection: -1 },
|
|
"top-right": { xTranslationDirection: -1, yTranslationDirection: -1 },
|
|
"bottom-left": { xTranslationDirection: 1, yTranslationDirection: 1 },
|
|
"bottom-right": { xTranslationDirection: -1, yTranslationDirection: 1 },
|
|
inside: { xTranslationDirection: 0, yTranslationDirection: 0 },
|
|
"inside-left": { xTranslationDirection: 1, yTranslationDirection: 0 },
|
|
"inside-right": { xTranslationDirection: -1, yTranslationDirection: 0 },
|
|
"inside-top": { xTranslationDirection: 0, yTranslationDirection: 1 },
|
|
"inside-bottom": { xTranslationDirection: 0, yTranslationDirection: -1 },
|
|
"inside-top-left": { xTranslationDirection: 1, yTranslationDirection: 1 },
|
|
"inside-bottom-left": { xTranslationDirection: 1, yTranslationDirection: -1 },
|
|
"inside-top-right": { xTranslationDirection: -1, yTranslationDirection: 1 },
|
|
"inside-bottom-right": { xTranslationDirection: -1, yTranslationDirection: -1 }
|
|
};
|
|
var verticalCrossLineTranslationDirections = {
|
|
top: { xTranslationDirection: 1, yTranslationDirection: 0 },
|
|
bottom: { xTranslationDirection: -1, yTranslationDirection: 0 },
|
|
left: { xTranslationDirection: 0, yTranslationDirection: -1 },
|
|
right: { xTranslationDirection: 0, yTranslationDirection: 1 },
|
|
"top-left": { xTranslationDirection: -1, yTranslationDirection: -1 },
|
|
"top-right": { xTranslationDirection: -1, yTranslationDirection: 1 },
|
|
"bottom-left": { xTranslationDirection: 1, yTranslationDirection: -1 },
|
|
"bottom-right": { xTranslationDirection: 1, yTranslationDirection: 1 },
|
|
inside: { xTranslationDirection: 0, yTranslationDirection: 0 },
|
|
"inside-left": { xTranslationDirection: 0, yTranslationDirection: 1 },
|
|
"inside-right": { xTranslationDirection: 0, yTranslationDirection: -1 },
|
|
"inside-top": { xTranslationDirection: -1, yTranslationDirection: 0 },
|
|
"inside-bottom": { xTranslationDirection: 1, yTranslationDirection: 0 },
|
|
"inside-top-left": { xTranslationDirection: -1, yTranslationDirection: 1 },
|
|
"inside-bottom-left": { xTranslationDirection: 1, yTranslationDirection: 1 },
|
|
"inside-top-right": { xTranslationDirection: -1, yTranslationDirection: -1 },
|
|
"inside-bottom-right": { xTranslationDirection: 1, yTranslationDirection: -1 }
|
|
};
|
|
function calculateLabelTranslation({
|
|
yDirection,
|
|
padding: padding2 = 0,
|
|
position = "top",
|
|
bbox
|
|
}) {
|
|
const crossLineTranslationDirections = yDirection ? horizontalCrosslineTranslationDirections : verticalCrossLineTranslationDirections;
|
|
const { xTranslationDirection, yTranslationDirection } = crossLineTranslationDirections[position];
|
|
const xTranslation = xTranslationDirection * (padding2 + bbox.width / 2);
|
|
const yTranslation = yTranslationDirection * (padding2 + bbox.height / 2);
|
|
return {
|
|
xTranslation,
|
|
yTranslation
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-community/src/motion/pathMotion.ts
|
|
function pathMotion(groupId, subId, animationManager, paths, fns) {
|
|
const animate = (phase, path, collapsable, updateFn) => {
|
|
animationManager.animate({
|
|
id: `${groupId}_${subId}_${path.id}_${phase}`,
|
|
groupId,
|
|
from: collapsable ? 1 : 0,
|
|
to: 1,
|
|
ease: easeOut,
|
|
collapsable,
|
|
onUpdate(ratio2, preInit) {
|
|
if (preInit && phase !== "removed")
|
|
return;
|
|
path.path.clear(true);
|
|
updateFn(ratio2, path);
|
|
path.checkPathDirty();
|
|
},
|
|
onStop() {
|
|
if (phase !== "added")
|
|
return;
|
|
path.path.clear(true);
|
|
updateFn(1, path);
|
|
path.checkPathDirty();
|
|
},
|
|
phase: NODE_UPDATE_STATE_TO_PHASE_MAPPING[phase]
|
|
});
|
|
};
|
|
const tempPath = new Path();
|
|
const resultsChange = (updateFn) => {
|
|
tempPath.resetPathDirty();
|
|
updateFn(0, tempPath);
|
|
tempPath.resetPathDirty();
|
|
updateFn(1, tempPath);
|
|
tempPath.checkPathDirty();
|
|
return tempPath.isPathDirty();
|
|
};
|
|
const { addPhaseFn, updatePhaseFn, removePhaseFn } = fns;
|
|
for (const path of paths) {
|
|
if (!animationManager.isSkipped()) {
|
|
animate("removed", path, !resultsChange(removePhaseFn), removePhaseFn);
|
|
animate("updated", path, !resultsChange(updatePhaseFn), updatePhaseFn);
|
|
}
|
|
animate("added", path, !resultsChange(addPhaseFn), addPhaseFn);
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/dropShadow.ts
|
|
var DropShadow = class extends ChangeDetectableProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = true;
|
|
this.color = "rgba(0, 0, 0, 0.5)";
|
|
this.xOffset = 0;
|
|
this.yOffset = 0;
|
|
this.blur = 5;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], DropShadow.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], DropShadow.prototype, "color", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], DropShadow.prototype, "xOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], DropShadow.prototype, "yOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], DropShadow.prototype, "blur", 2);
|
|
|
|
// packages/ag-charts-community/src/scene/util/sector.ts
|
|
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 angle2 = Math.atan2(y, x);
|
|
return startAngle < endAngle ? angle2 <= endAngle && angle2 >= startAngle : angle2 <= endAngle && angle2 >= -Math.PI || angle2 >= startAngle && angle2 <= Math.PI;
|
|
}
|
|
function arcIntersections(cx, cy, r, startAngle, endAngle, counterClockwise, x1, y1, x2, y2) {
|
|
if (Number.isNaN(cx) || Number.isNaN(cy)) {
|
|
return 0;
|
|
}
|
|
if (counterClockwise) {
|
|
[endAngle, startAngle] = [startAngle, endAngle];
|
|
}
|
|
const k = (y2 - y1) / (x2 - x1);
|
|
const y0 = y1 - k * x1;
|
|
const a = Math.pow(k, 2) + 1;
|
|
const b = 2 * (k * (y0 - cy) - cx);
|
|
const c = Math.pow(cx, 2) + Math.pow(y0 - cy, 2) - Math.pow(r, 2);
|
|
const d = Math.pow(b, 2) - 4 * a * c;
|
|
if (d < 0) {
|
|
return 0;
|
|
}
|
|
const i1x = (-b + Math.sqrt(d)) / 2 / a;
|
|
const i2x = (-b - Math.sqrt(d)) / 2 / a;
|
|
let intersections = 0;
|
|
for (const x of [i1x, i2x]) {
|
|
const isXInsideLine = x >= Math.min(x1, x2) && x <= Math.max(x1, x2);
|
|
if (!isXInsideLine) {
|
|
continue;
|
|
}
|
|
const y = k * x + y0;
|
|
const adjacent = x - cx;
|
|
const opposite = y - cy;
|
|
const angle2 = Math.atan2(opposite, adjacent);
|
|
if (isBetweenAngles(angle2, startAngle, endAngle)) {
|
|
intersections++;
|
|
}
|
|
}
|
|
return intersections;
|
|
}
|
|
function lineCollidesSector(line, sector) {
|
|
const { startAngle, endAngle, innerRadius, outerRadius } = sector;
|
|
const outerStart = { x: outerRadius * Math.cos(startAngle), y: outerRadius * Math.sin(startAngle) };
|
|
const outerEnd = { x: outerRadius * Math.cos(endAngle), y: outerRadius * Math.sin(endAngle) };
|
|
const innerStart = innerRadius === 0 ? { x: 0, y: 0 } : { x: innerRadius * Math.cos(startAngle), y: innerRadius * Math.sin(startAngle) };
|
|
const innerEnd = innerRadius === 0 ? { x: 0, y: 0 } : { x: innerRadius * Math.cos(endAngle), y: innerRadius * Math.sin(endAngle) };
|
|
return segmentIntersection(
|
|
line.start.x,
|
|
line.start.y,
|
|
line.end.x,
|
|
line.end.y,
|
|
outerStart.x,
|
|
outerStart.y,
|
|
innerStart.x,
|
|
innerStart.y
|
|
) || segmentIntersection(
|
|
line.start.x,
|
|
line.start.y,
|
|
line.end.x,
|
|
line.end.y,
|
|
outerEnd.x,
|
|
outerEnd.y,
|
|
innerEnd.x,
|
|
innerEnd.y
|
|
) || arcIntersections(
|
|
0,
|
|
0,
|
|
outerRadius,
|
|
startAngle,
|
|
endAngle,
|
|
true,
|
|
line.start.x,
|
|
line.start.y,
|
|
line.end.x,
|
|
line.end.y
|
|
);
|
|
}
|
|
function boxCollidesSector(box, sector) {
|
|
const topLeft = { x: box.x, y: box.y };
|
|
const topRight = { x: box.x + box.width, y: box.y };
|
|
const bottomLeft = { x: box.x, y: box.y + box.height };
|
|
const bottomRight = { x: box.x + box.width, y: box.y + box.height };
|
|
return lineCollidesSector({ start: topLeft, end: topRight }, sector) || lineCollidesSector({ start: bottomLeft, end: bottomRight }, sector);
|
|
}
|
|
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 start2 = 0;
|
|
let end3 = 1;
|
|
for (let i = 0; i < 8; i += 1) {
|
|
const s = (start2 + end3) / 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) {
|
|
start2 = s;
|
|
} else {
|
|
end3 = s;
|
|
}
|
|
}
|
|
return start2;
|
|
}
|
|
var delta2 = 1e-6;
|
|
function clockwiseAngle(angle2, relativeToStartAngle) {
|
|
if (angleBetween(angle2, relativeToStartAngle) < delta2) {
|
|
return relativeToStartAngle;
|
|
} else {
|
|
return normalizeAngle360(angle2 - relativeToStartAngle) + relativeToStartAngle;
|
|
}
|
|
}
|
|
function clockwiseAngles(startAngle, endAngle, relativeToStartAngle = 0) {
|
|
const fullPie = Math.abs(endAngle - startAngle) >= 2 * Math.PI;
|
|
const sweepAngle = fullPie ? 2 * Math.PI : normalizeAngle360(endAngle - startAngle);
|
|
startAngle = clockwiseAngle(startAngle, relativeToStartAngle);
|
|
endAngle = startAngle + sweepAngle;
|
|
return { startAngle, endAngle };
|
|
}
|
|
function arcRadialLineIntersectionAngle(cx, cy, r, startAngle, endAngle, clipAngle) {
|
|
const sinA = Math.sin(clipAngle);
|
|
const cosA = Math.cos(clipAngle);
|
|
const c = cx ** 2 + cy ** 2 - r ** 2;
|
|
let p0x;
|
|
let p0y;
|
|
let p1x;
|
|
let p1y;
|
|
if (cosA > 0.5) {
|
|
const tanA = sinA / cosA;
|
|
const a = 1 + tanA ** 2;
|
|
const b = -2 * (cx + cy * tanA);
|
|
const d = b ** 2 - 4 * a * c;
|
|
if (d < 0)
|
|
return;
|
|
const x0 = (-b + Math.sqrt(d)) / (2 * a);
|
|
const x1 = (-b - Math.sqrt(d)) / (2 * a);
|
|
p0x = x0;
|
|
p0y = x0 * tanA;
|
|
p1x = x1;
|
|
p1y = x1 * tanA;
|
|
} else {
|
|
const cotA = cosA / sinA;
|
|
const a = 1 + cotA ** 2;
|
|
const b = -2 * (cy + cx * cotA);
|
|
const d = b ** 2 - 4 * a * c;
|
|
if (d < 0)
|
|
return;
|
|
const y0 = (-b + Math.sqrt(d)) / (2 * a);
|
|
const y1 = (-b - Math.sqrt(d)) / (2 * a);
|
|
p0x = y0 * cotA;
|
|
p0y = y0;
|
|
p1x = y1 * cotA;
|
|
p1y = y1;
|
|
}
|
|
const normalisedX = cosA;
|
|
const normalisedY = sinA;
|
|
const p0DotNormalized = p0x * normalisedX + p0y * normalisedY;
|
|
const p1DotNormalized = p1x * normalisedX + p1y * normalisedY;
|
|
const a0 = p0DotNormalized > 0 ? clockwiseAngle(Math.atan2(p0y - cy, p0x - cx), startAngle) : Number.NaN;
|
|
const a1 = p1DotNormalized > 0 ? clockwiseAngle(Math.atan2(p1y - cy, p1x - cx), startAngle) : Number.NaN;
|
|
if (a0 >= startAngle && a0 <= endAngle) {
|
|
return a0;
|
|
} else if (a1 >= startAngle && a1 <= endAngle) {
|
|
return a1;
|
|
}
|
|
}
|
|
function arcCircleIntersectionAngle(cx, cy, r, startAngle, endAngle, circleR) {
|
|
const d = Math.hypot(cx, cy);
|
|
const d1 = (d ** 2 - r ** 2 + circleR ** 2) / (2 * d);
|
|
const d2 = d - d1;
|
|
const theta = Math.atan2(cy, cx);
|
|
const deltaTheta = Math.acos(-d2 / r);
|
|
const a0 = clockwiseAngle(theta + deltaTheta, startAngle);
|
|
const a1 = clockwiseAngle(theta - deltaTheta, startAngle);
|
|
if (a0 >= startAngle && a0 <= endAngle) {
|
|
return a0;
|
|
} else if (a1 >= startAngle && a1 <= endAngle) {
|
|
return a1;
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/shape/svgPath.ts
|
|
var SvgPath = class extends Path {
|
|
constructor(d = "") {
|
|
super();
|
|
this._d = "";
|
|
this.d = d;
|
|
}
|
|
get d() {
|
|
return this._d;
|
|
}
|
|
set d(d) {
|
|
if (d === this._d)
|
|
return;
|
|
this._d = d;
|
|
this.path.clear();
|
|
this.path.appendSvg(d);
|
|
this.checkPathDirty();
|
|
}
|
|
};
|
|
var TranslatableSvgPath = class extends Translatable(SvgPath) {
|
|
isPointInPath(x, y) {
|
|
return super.isPointInPath(x - this.translationX, y - this.translationY);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scale/approximateOrdinalTimeScale.ts
|
|
var ApproximateOrdinalTimeScale = class _ApproximateOrdinalTimeScale extends OrdinalTimeScale {
|
|
static is(value) {
|
|
return value instanceof _ApproximateOrdinalTimeScale;
|
|
}
|
|
/**
|
|
* Set the source scale that this approximate scale delegates to.
|
|
* All property access (domain, range, etc.) will be delegated to the source.
|
|
*/
|
|
setSourceScale(scale2) {
|
|
this._sourceScale = scale2;
|
|
const delegateProperty = (prop) => {
|
|
Object.defineProperty(this, prop, {
|
|
get: () => scale2[prop],
|
|
set: (value) => {
|
|
scale2[prop] = value;
|
|
},
|
|
configurable: true
|
|
});
|
|
};
|
|
delegateProperty("domain");
|
|
delegateProperty("range");
|
|
delegateProperty("paddingInner");
|
|
delegateProperty("paddingOuter");
|
|
delegateProperty("round");
|
|
const delegateReadOnly = (prop) => {
|
|
Object.defineProperty(this, prop, {
|
|
get: () => scale2[prop],
|
|
configurable: true
|
|
});
|
|
};
|
|
delegateReadOnly("bandwidth");
|
|
delegateReadOnly("step");
|
|
delegateReadOnly("inset");
|
|
delegateReadOnly("rawBandwidth");
|
|
}
|
|
// Delegate bands to source scale (read-only)
|
|
get bands() {
|
|
return this._sourceScale?.bands ?? super.bands;
|
|
}
|
|
// Delegate refresh to ensure source scale is up-to-date
|
|
refresh() {
|
|
this._sourceScale?.["refresh"]?.();
|
|
}
|
|
// Delegate ordinalRange to use source scale's computed values
|
|
ordinalRange(i) {
|
|
if (this._sourceScale) {
|
|
return this._sourceScale.ordinalRange(i);
|
|
}
|
|
return super.ordinalRange(i);
|
|
}
|
|
// Delegate convert to source scale but use our findIndex
|
|
convert(d, options) {
|
|
this.refresh();
|
|
const i = this.findIndex(d, options?.alignment);
|
|
if (i == null || i < 0 || i >= this.bands.length) {
|
|
return Number.NaN;
|
|
}
|
|
return this.ordinalRange(i);
|
|
}
|
|
findIndex(value, alignment = 0 /* Leading */) {
|
|
if (value == null) {
|
|
return void 0;
|
|
}
|
|
const { bands, reversed } = this;
|
|
const n = bands.length;
|
|
if (n === 0)
|
|
return void 0;
|
|
if (n === 1)
|
|
return 0;
|
|
const firstBand = bands[0];
|
|
const lastBand = bands[n - 1];
|
|
if (firstBand == null || lastBand == null) {
|
|
return this._sourceScale?.findIndex(value, alignment);
|
|
}
|
|
const target = value.valueOf();
|
|
const first2 = firstBand.valueOf();
|
|
const last = lastBand.valueOf();
|
|
const ratio2 = (target - first2) / (last - first2);
|
|
const rawIndex = reversed ? (1 - ratio2) * (n - 1) : ratio2 * (n - 1);
|
|
if (alignment === 0 /* Leading */) {
|
|
return Math.max(0, Math.min(n - 1, Math.floor(rawIndex)));
|
|
} else {
|
|
return Math.max(0, Math.min(n - 1, Math.ceil(rawIndex)));
|
|
}
|
|
}
|
|
};
|
|
|
|
// 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/image.ts
|
|
var Image2 = class extends Node2 {
|
|
constructor(sourceImage) {
|
|
super();
|
|
this.sourceImage = sourceImage;
|
|
this.x = 0;
|
|
this.y = 0;
|
|
this.width = 0;
|
|
this.height = 0;
|
|
this.opacity = 1;
|
|
}
|
|
render(renderCtx) {
|
|
const { ctx } = renderCtx;
|
|
const image = this.sourceImage;
|
|
if (image) {
|
|
ctx.globalAlpha = this.opacity;
|
|
ctx.drawImage(image, 0, 0, image.width, image.height, this.x, this.y, this.width, this.height);
|
|
}
|
|
super.render(renderCtx);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Image2.prototype, "x", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Image2.prototype, "y", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Image2.prototype, "width", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Image2.prototype, "height", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Image2.prototype, "opacity", 2);
|
|
|
|
// packages/ag-charts-community/src/scene/shape/arc.ts
|
|
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 isNumberEqual(normalizeAngle360(this.startAngle), normalizeAngle360(this.endAngle));
|
|
}
|
|
updatePath() {
|
|
const path = this.path;
|
|
path.clear();
|
|
path.arc(this.centerX, this.centerY, this.radius, this.startAngle, this.endAngle, this.counterClockwise);
|
|
if (this.type === 1 /* Chord */) {
|
|
path.closePath();
|
|
} else if (this.type === 2 /* Round */ && !this.fullPie) {
|
|
path.lineTo(this.centerX, this.centerY);
|
|
path.closePath();
|
|
}
|
|
}
|
|
computeBBox() {
|
|
return new BBox(this.centerX - this.radius, this.centerY - this.radius, this.radius * 2, this.radius * 2);
|
|
}
|
|
isPointInPath(x, y) {
|
|
const bbox = this.getBBox();
|
|
return this.type !== 0 /* Open */ && bbox.containsPoint(x, y) && this.path.isPointInPath(x, y);
|
|
}
|
|
};
|
|
Arc.className = "Arc";
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Arc.prototype, "centerX", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Arc.prototype, "centerY", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Arc.prototype, "radius", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Arc.prototype, "startAngle", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Arc.prototype, "endAngle", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Arc.prototype, "counterClockwise", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Arc.prototype, "type", 2);
|
|
|
|
// packages/ag-charts-community/src/scene/shape/radialColumnShape.ts
|
|
function rotatePoint2(x, y, rotation) {
|
|
const radius = Math.hypot(x, y);
|
|
const angle2 = Math.atan2(y, x);
|
|
const rotated = angle2 + 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 } = rotatePoint2(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 normalizeAngle360(startAngle + midAngle / 2 + Math.PI / 2);
|
|
}
|
|
normalizeRadii(innerRadius, outerRadius) {
|
|
if (innerRadius > outerRadius) {
|
|
return [outerRadius, innerRadius];
|
|
}
|
|
return [innerRadius, outerRadius];
|
|
}
|
|
updatePath() {
|
|
const { isBeveled } = this;
|
|
if (isBeveled) {
|
|
this.updateBeveledPath();
|
|
} else {
|
|
this.updateRectangularPath();
|
|
}
|
|
this.checkPathDirty();
|
|
}
|
|
updateRectangularPath() {
|
|
const { columnWidth, path } = this;
|
|
const [innerRadius, outerRadius] = this.normalizeRadii(this.innerRadius, this.outerRadius);
|
|
const left = -columnWidth / 2;
|
|
const right = columnWidth / 2;
|
|
const top = -outerRadius;
|
|
const bottom = -innerRadius;
|
|
const rotation = this.getRotation();
|
|
const points = [
|
|
[left, bottom],
|
|
[left, top],
|
|
[right, top],
|
|
[right, bottom]
|
|
].map(([x, y]) => rotatePoint2(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 angle2 = Math.atan2(y, x);
|
|
return { y, angle: angle2 };
|
|
}
|
|
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 = rotatePoint2(x, y, rotation);
|
|
this.path.moveTo(point.x, point.y);
|
|
}
|
|
lineToRotated(x, y, rotation) {
|
|
const point = rotatePoint2(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 = isNumberEqual(innerRadius, axisInnerRadius);
|
|
const isTouchingOuter = isNumberEqual(outerRadius, axisOuterRadius);
|
|
const topCornersBreach = Math.hypot(left, top) > axisOuterRadius || Math.hypot(right, top) > axisOuterRadius;
|
|
if (!isTouchingInner && !isTouchingOuter && !topCornersBreach) {
|
|
this.updateRectangularPath();
|
|
return;
|
|
}
|
|
const inner = isTouchingInner ? this.calculateBothIntersections(left, right, innerRadius) : null;
|
|
const outer = isTouchingOuter ? this.calculateBothIntersections(left, right, outerRadius) : null;
|
|
const axisOuter = topCornersBreach ? this.calculateAxisOuterIntersections(left, right, axisOuterRadius) : null;
|
|
if (isTouchingInner && !inner || isTouchingOuter && !outer || topCornersBreach && !axisOuter) {
|
|
this.updateRectangularPath();
|
|
return;
|
|
}
|
|
path.clear(true);
|
|
const geometry = { left, right, top, bottom, rotation };
|
|
if (inner) {
|
|
this.moveToRotated(left, inner.left.y, rotation);
|
|
} else {
|
|
this.moveToRotated(left, bottom, rotation);
|
|
}
|
|
if (inner) {
|
|
path.arc(0, 0, innerRadius, rotation + inner.left.angle, rotation + inner.right.angle, false);
|
|
} else {
|
|
this.lineToRotated(right, bottom, rotation);
|
|
}
|
|
if (outer) {
|
|
this.lineToRotated(right, outer.right.y, rotation);
|
|
path.arc(0, 0, outerRadius, rotation + outer.right.angle, rotation + outer.left.angle, true);
|
|
} else if (axisOuter) {
|
|
this.renderTopWithCornerClipping(axisOuterRadius, axisOuter, geometry);
|
|
} else {
|
|
this.lineToRotated(right, top, rotation);
|
|
this.lineToRotated(left, top, rotation);
|
|
}
|
|
path.closePath();
|
|
}
|
|
};
|
|
RadialColumnShape.className = "RadialColumnShape";
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], RadialColumnShape.prototype, "isBeveled", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], RadialColumnShape.prototype, "columnWidth", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], RadialColumnShape.prototype, "startAngle", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], RadialColumnShape.prototype, "endAngle", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], RadialColumnShape.prototype, "outerRadius", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], RadialColumnShape.prototype, "innerRadius", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], RadialColumnShape.prototype, "axisInnerRadius", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], RadialColumnShape.prototype, "axisOuterRadius", 2);
|
|
function getRadialColumnWidth(startAngle, endAngle, axisOuterRadius, columnWidthRatio, maxColumnWidthRatio) {
|
|
const rotation = angleBetween(startAngle, endAngle);
|
|
const pad2 = rotation * (1 - columnWidthRatio) / 2;
|
|
startAngle += pad2;
|
|
endAngle -= pad2;
|
|
if (rotation < 1e-3) {
|
|
return 2 * axisOuterRadius * maxColumnWidthRatio;
|
|
}
|
|
if (rotation >= 2 * Math.PI) {
|
|
const midAngle = startAngle + rotation / 2;
|
|
startAngle = midAngle - Math.PI;
|
|
endAngle = midAngle + Math.PI;
|
|
}
|
|
const startX = axisOuterRadius * Math.cos(startAngle);
|
|
const startY = axisOuterRadius * Math.sin(startAngle);
|
|
const endX = axisOuterRadius * Math.cos(endAngle);
|
|
const endY = axisOuterRadius * Math.sin(endAngle);
|
|
const colWidth = Math.floor(Math.hypot(startX - endX, startY - endY));
|
|
const maxWidth = 2 * axisOuterRadius * maxColumnWidthRatio;
|
|
return Math.max(1, Math.min(maxWidth, colWidth));
|
|
}
|
|
|
|
// packages/ag-charts-community/src/scene/shape/sector.ts
|
|
var 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, start2, 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 angle2 = start2 ? startAngle + angleOffset + angleSweep : endAngle - angleOffset - angleSweep;
|
|
const radius = inner ? innerRadius + r : outerRadius - r;
|
|
const cx = radius * Math.cos(angle2);
|
|
const cy = radius * Math.sin(angle2);
|
|
if (clipSector != null) {
|
|
const delta5 = 1e-6;
|
|
if (!start2 && !(angle2 >= startAngle - delta5 && angle2 <= clipSector.endAngle - delta5))
|
|
return;
|
|
if (start2 && !(angle2 >= clipSector.startAngle + delta5 && angle2 <= endAngle - delta5))
|
|
return;
|
|
if (inner && radius < clipSector.innerRadius - delta5)
|
|
return;
|
|
if (!inner && radius > clipSector.outerRadius + delta5)
|
|
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 (start2) {
|
|
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 (start2) {
|
|
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 (start2) {
|
|
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(start2 === inner ? arc.a0 : arc.a1);
|
|
const theta = clockwiseAngle(Math.atan2(y, x), startAngle);
|
|
const radialArc = inner ? innerArc : outerArc;
|
|
if (start2) {
|
|
radialArc?.clipStart(theta);
|
|
} else {
|
|
radialArc?.clipEnd(theta);
|
|
}
|
|
return arc;
|
|
}
|
|
updatePath() {
|
|
const delta5 = 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 - delta5;
|
|
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 - delta5) {
|
|
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 - delta5) {
|
|
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([
|
|
SceneChangeDetection()
|
|
], Sector.prototype, "centerX", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Sector.prototype, "centerY", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Sector.prototype, "innerRadius", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Sector.prototype, "outerRadius", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Sector.prototype, "startAngle", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Sector.prototype, "endAngle", 2);
|
|
__decorateClass([
|
|
SceneObjectChangeDetection({ equals: (lhs, rhs) => lhs.equals(rhs) })
|
|
], Sector.prototype, "clipSector", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Sector.prototype, "concentricEdgeInset", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Sector.prototype, "radialEdgeInset", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Sector.prototype, "startOuterCornerRadius", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Sector.prototype, "endOuterCornerRadius", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Sector.prototype, "startInnerCornerRadius", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], Sector.prototype, "endInnerCornerRadius", 2);
|
|
|
|
// packages/ag-charts-community/src/widget/menuItemWidget.ts
|
|
var MenuItemWidget = class extends AbstractButtonWidget {
|
|
constructor() {
|
|
super(createElement("div"), "menuitem");
|
|
}
|
|
};
|
|
var MenuItemRadioWidget = class extends AbstractButtonWidget {
|
|
constructor() {
|
|
super(createElement("div"), "menuitemradio");
|
|
}
|
|
setChecked(checked) {
|
|
setAttribute(this.elem, "aria-checked", checked);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/widget/menuWidget.ts
|
|
var closeKeys = ["Escape", "ArrowLeft"];
|
|
var MenuWidget = class _MenuWidget extends RovingTabContainerWidget {
|
|
constructor(orientation = "vertical") {
|
|
super(orientation, "menu");
|
|
this.handleMouseEnter = (ev, current) => {
|
|
if (!current.hasPopup()) {
|
|
this.expandSubMenu(ev, void 0);
|
|
}
|
|
};
|
|
this.handleMouseMove = (_ev, current) => {
|
|
current.focus({ preventScroll: true });
|
|
};
|
|
}
|
|
destructor() {
|
|
this.collapse({ mode: "2" /* DESTROY */ });
|
|
}
|
|
addSeparator() {
|
|
const sep = getDocument().createElement("div");
|
|
setAttribute(sep, "role", "separator");
|
|
this.elem.appendChild(sep);
|
|
return sep;
|
|
}
|
|
onChildAdded(child) {
|
|
super.onChildAdded(child);
|
|
child.addListener("mouseenter", this.handleMouseEnter);
|
|
child.addListener("mousemove", this.handleMouseMove);
|
|
}
|
|
onChildRemoved(child) {
|
|
super.onChildRemoved(child);
|
|
child.removeListener("mouseenter", this.handleMouseEnter);
|
|
child.removeListener("mousemove", this.handleMouseMove);
|
|
}
|
|
addSubMenu() {
|
|
const subMenuButton = new MenuItemWidget();
|
|
const subMenu = new _MenuWidget(this.orientation);
|
|
subMenu.id = createElementId();
|
|
const expand = () => {
|
|
this.collapseExpandedSubMenu(subMenu);
|
|
subMenuButton.expandControlled();
|
|
};
|
|
const arrowRightOpener = (ev) => {
|
|
if (hasNoModifiers(ev.sourceEvent) && ev.sourceEvent.code === "ArrowRight") {
|
|
this.collapseExpandedSubMenu(subMenu);
|
|
subMenuButton.expandControlled();
|
|
}
|
|
};
|
|
subMenuButton.setControlled(subMenu);
|
|
subMenuButton.setAriaHasPopup("menu");
|
|
subMenuButton.addListener("click", expand);
|
|
subMenuButton.addListener("mouseenter", expand);
|
|
subMenuButton.addListener("keydown", arrowRightOpener);
|
|
this.addChild(subMenuButton);
|
|
return { subMenuButton, subMenu };
|
|
}
|
|
expandSubMenu(ev, subMenu) {
|
|
const { expansionScope } = this;
|
|
if (!expansionScope)
|
|
return;
|
|
this.collapseExpandedSubMenu(subMenu);
|
|
subMenu?.expand(ev);
|
|
}
|
|
collapseExpandedSubMenu(newSubMenu) {
|
|
const { expansionScope } = this;
|
|
if (!expansionScope)
|
|
return;
|
|
expansionScope.expandedSubMenu?.collapse({ mode: "4" /* SIDLING_OPENED */ });
|
|
expansionScope.expandedSubMenu = newSubMenu;
|
|
}
|
|
expand(opts) {
|
|
if (this.expansionScope != null)
|
|
return;
|
|
this.expansionScope = {
|
|
lastFocus: getLastFocus(opts.sourceEvent),
|
|
expandedSubMenu: void 0,
|
|
abort: () => this.collapse({ mode: "1" /* ABORT */ }),
|
|
close: () => this.collapse({ mode: "0" /* CLOSE */ }),
|
|
removers: new CleanupRegistry()
|
|
};
|
|
const scope = this.expansionScope;
|
|
const buttons = this.children.map((value) => value.getElement());
|
|
setAttribute(scope.lastFocus, "aria-expanded", true);
|
|
scope.removers.register(
|
|
addMouseCloseListener(this.elem, scope.abort),
|
|
addTouchCloseListener(this.elem, scope.abort),
|
|
...this.children.map((child) => addEscapeEventListener(child.getElement(), scope.close, closeKeys)),
|
|
opts?.overrideFocusVisible && addOverrideFocusVisibleEventListener(this.elem, buttons, opts.overrideFocusVisible)
|
|
);
|
|
this.internalListener?.dispatch("expand-widget", this, { type: "expand-widget" });
|
|
this.children[0]?.focus({ preventScroll: true });
|
|
}
|
|
collapse(opts) {
|
|
const { mode = "0" /* CLOSE */ } = opts ?? {};
|
|
if (this.expansionScope === void 0)
|
|
return;
|
|
const { lastFocus, removers, expandedSubMenu } = this.expansionScope;
|
|
this.expansionScope = void 0;
|
|
expandedSubMenu?.collapse({ mode: "3" /* PARENT_CLOSED */ });
|
|
setAttribute(lastFocus, "aria-expanded", false);
|
|
if (mode === "0" /* CLOSE */) {
|
|
lastFocus?.focus({ preventScroll: true });
|
|
}
|
|
removers.flush();
|
|
this.internalListener?.dispatch("collapse-widget", this, { type: "collapse-widget", mode });
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/components/popover/popover.ts
|
|
var canvasOverlay = "canvas-overlay";
|
|
var Popover = class extends AbstractModuleInstance {
|
|
constructor(ctx, id, options) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.hideFns = [];
|
|
this.setOwnedWidget = /* @__PURE__ */ (() => {
|
|
let ownedWidget;
|
|
return (owns) => {
|
|
ownedWidget?.destroy();
|
|
ownedWidget = owns;
|
|
};
|
|
})();
|
|
this.moduleId = `popover-${id}`;
|
|
if (options?.detached) {
|
|
this.element = createElement("div");
|
|
} else {
|
|
this.element = ctx.domManager.addChild(canvasOverlay, this.moduleId);
|
|
}
|
|
this.element.setAttribute("role", "presentation");
|
|
this.hideFns.push(() => this.setOwnedWidget(void 0));
|
|
this.cleanup.register(() => ctx.domManager.removeChild(canvasOverlay, this.moduleId));
|
|
}
|
|
attachTo(popover) {
|
|
if (this.element.parentElement)
|
|
return;
|
|
popover.element.append(this.element);
|
|
}
|
|
hide(opts) {
|
|
const { lastFocus = this.lastFocus } = opts ?? {};
|
|
if (this.element.children.length === 0)
|
|
return;
|
|
for (const fn of this.hideFns) {
|
|
fn();
|
|
}
|
|
lastFocus?.focus();
|
|
this.lastFocus = void 0;
|
|
}
|
|
removeChildren() {
|
|
this.element.replaceChildren();
|
|
}
|
|
initPopoverElement(popover, options) {
|
|
if (!this.element.parentElement) {
|
|
throw new Error("Can not show popover that has not been attached to a parent.");
|
|
}
|
|
popover ?? (popover = createElement("div", "ag-charts-popover"));
|
|
popover.classList.toggle("ag-charts-popover", true);
|
|
if (options.ariaLabel != null) {
|
|
popover.setAttribute("aria-label", options.ariaLabel);
|
|
}
|
|
if (options.class != null) {
|
|
popover.classList.add(options.class);
|
|
}
|
|
this.element.replaceChildren(popover);
|
|
return popover;
|
|
}
|
|
showWidget(controller, owns, options) {
|
|
this.setOwnedWidget(owns);
|
|
this.initPopoverElement(owns.getElement(), options);
|
|
owns.addListener("collapse-widget", () => {
|
|
controller.setControlled(void 0);
|
|
this.setOwnedWidget(void 0);
|
|
});
|
|
controller.setControlled(owns);
|
|
controller.expandControlled();
|
|
}
|
|
showWithChildren(children, options) {
|
|
const popover = this.initPopoverElement(void 0, options);
|
|
popover.replaceChildren(...children);
|
|
this.hideFns.push(() => this.removeChildren());
|
|
if (options.onHide) {
|
|
this.hideFns.push(options.onHide);
|
|
}
|
|
if (options.initialFocus && options.sourceEvent) {
|
|
const lastFocus = getLastFocus(options.sourceEvent);
|
|
if (lastFocus !== void 0) {
|
|
this.lastFocus = lastFocus;
|
|
this.initialFocus = options.initialFocus;
|
|
}
|
|
}
|
|
return popover;
|
|
}
|
|
getPopoverElement() {
|
|
return this.element.firstElementChild;
|
|
}
|
|
updatePosition(position) {
|
|
const popover = this.getPopoverElement();
|
|
if (!popover)
|
|
return;
|
|
popover.style.setProperty("right", "unset");
|
|
popover.style.setProperty("bottom", "unset");
|
|
if (position.x != null)
|
|
popover.style.setProperty("left", `${Math.floor(position.x)}px`);
|
|
if (position.y != null)
|
|
popover.style.setProperty("top", `${Math.floor(position.y)}px`);
|
|
this.initialFocus?.focus();
|
|
this.initialFocus = void 0;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/components/popover/anchoredPopover.ts
|
|
var AnchoredPopover = class extends Popover {
|
|
setAnchor(anchor, fallbackAnchor) {
|
|
this.anchor = anchor;
|
|
this.fallbackAnchor = fallbackAnchor;
|
|
this.updatePosition(anchor);
|
|
this.repositionWithinBounds();
|
|
}
|
|
updateAnchor(options) {
|
|
const anchor = options.anchor ?? this.anchor;
|
|
const fallbackAnchor = options.fallbackAnchor ?? this.fallbackAnchor;
|
|
if (anchor) {
|
|
this.setAnchor(anchor, fallbackAnchor);
|
|
}
|
|
}
|
|
showWidget(controller, owns, options) {
|
|
super.showWidget(controller, owns, options);
|
|
this.updateAnchor(options);
|
|
}
|
|
showWithChildren(children, options) {
|
|
const popover = super.showWithChildren(children, options);
|
|
this.updateAnchor(options);
|
|
getWindow().requestAnimationFrame(() => {
|
|
this.repositionWithinBounds();
|
|
});
|
|
return popover;
|
|
}
|
|
repositionWithinBounds() {
|
|
const { anchor, ctx, fallbackAnchor } = this;
|
|
const popover = this.getPopoverElement();
|
|
if (!anchor || !popover)
|
|
return;
|
|
const canvasRect = ctx.domManager.getBoundingClientRect();
|
|
const { offsetWidth: width2, offsetHeight: height2 } = popover;
|
|
let x = clamp(0, anchor.x, canvasRect.width - width2);
|
|
let y = clamp(0, anchor.y, canvasRect.height - height2);
|
|
if (x !== anchor.x && fallbackAnchor?.x != null) {
|
|
x = clamp(0, fallbackAnchor.x - width2, canvasRect.width - width2);
|
|
}
|
|
if (y !== anchor.y && fallbackAnchor?.y != null) {
|
|
y = clamp(0, fallbackAnchor.y - height2, canvasRect.height - height2);
|
|
}
|
|
this.updatePosition({ x, y });
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/components/menu/menu.ts
|
|
var Menu = class extends AnchoredPopover {
|
|
show(controller, options) {
|
|
const menu = new MenuWidget("vertical");
|
|
for (const item of options.items) {
|
|
menu.addChild(this.createRow(options, item, menu));
|
|
}
|
|
menu.addClass("ag-charts-menu");
|
|
this.showWidget(controller, menu, options);
|
|
}
|
|
allocRow(options, item) {
|
|
if (options.menuItemRole == null || options.menuItemRole === "menuitem") {
|
|
return new MenuItemWidget();
|
|
} else {
|
|
options.menuItemRole;
|
|
const result = new MenuItemRadioWidget();
|
|
result.setChecked(options.value === item.value);
|
|
return result;
|
|
}
|
|
}
|
|
createRow(options, item, menu) {
|
|
const active = item.value === options.value;
|
|
const row = this.allocRow(options, item);
|
|
row.addClass("ag-charts-menu__row");
|
|
row.toggleClass(`ag-charts-menu__row--active`, active);
|
|
if (typeof item.value === "string") {
|
|
row.getElement().dataset.popoverId = item.value;
|
|
}
|
|
if (item.icon != null) {
|
|
const icon = createElement("span", `ag-charts-menu__icon ${getIconClassNames(item.icon)}`);
|
|
row.getElement().appendChild(icon);
|
|
}
|
|
const strokeWidthVisible = item.strokeWidth != null;
|
|
if (strokeWidthVisible) {
|
|
row.toggleClass(`ag-charts-menu__row--stroke-width-visible`, strokeWidthVisible);
|
|
row.setCSSVariable("--strokeWidth", strokeWidthVisible ? `${item.strokeWidth}px` : null);
|
|
}
|
|
if (item.label != null) {
|
|
const label = createElement("span", "ag-charts-menu__label");
|
|
label.textContent = this.ctx.localeManager.t(item.label);
|
|
row.getElement().appendChild(label);
|
|
}
|
|
if ("altText" in item) {
|
|
row.setAriaLabel(this.ctx.localeManager.t(item.altText));
|
|
}
|
|
row.addListener("click", ({ sourceEvent }) => {
|
|
options.onPress?.(item);
|
|
sourceEvent.preventDefault();
|
|
sourceEvent.stopPropagation();
|
|
menu.collapse();
|
|
});
|
|
return row;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/components/popover/draggablePopover.ts
|
|
var DraggablePopover = class extends Popover {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.dragged = false;
|
|
}
|
|
setDragHandle(dragHandle) {
|
|
dragHandle.addListener("drag-start", (event) => {
|
|
dragHandle.addClass(this.dragHandleDraggingClass);
|
|
this.onDragStart(event);
|
|
});
|
|
dragHandle.addListener("drag-move", this.onDragMove.bind(this));
|
|
dragHandle.addListener("drag-end", () => {
|
|
dragHandle.removeClass(this.dragHandleDraggingClass);
|
|
this.onDragEnd.bind(this);
|
|
});
|
|
}
|
|
onDragStart(event) {
|
|
const popover = this.getPopoverElement();
|
|
if (!popover)
|
|
return;
|
|
event.sourceEvent.preventDefault();
|
|
this.dragged = true;
|
|
this.dragStartState = {
|
|
client: vector_exports.from(event.clientX, event.clientY),
|
|
position: vector_exports.from(
|
|
Number(popover.style.getPropertyValue("left").replace("px", "")),
|
|
Number(popover.style.getPropertyValue("top").replace("px", ""))
|
|
)
|
|
};
|
|
}
|
|
onDragMove(event) {
|
|
const { dragStartState } = this;
|
|
const popover = this.getPopoverElement();
|
|
if (!dragStartState || !popover)
|
|
return;
|
|
const offset = vector_exports.sub(vector_exports.from(event.clientX, event.clientY), dragStartState.client);
|
|
const position = vector_exports.add(dragStartState.position, offset);
|
|
const bounds = this.ctx.domManager.getBoundingClientRect();
|
|
const partialPosition = {};
|
|
if (position.x >= 0 && position.x + popover.offsetWidth <= bounds.width) {
|
|
partialPosition.x = position.x;
|
|
}
|
|
if (position.y >= 0 && position.y + popover.offsetHeight <= bounds.height) {
|
|
partialPosition.y = position.y;
|
|
}
|
|
this.updatePosition(partialPosition);
|
|
}
|
|
onDragEnd() {
|
|
this.dragStartState = void 0;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/components/toolbar/toolbarButtonWidget.ts
|
|
var ARIA_HASPOPUP = {
|
|
"disjoint-channel": "false",
|
|
"fibonacci-menu": "menu",
|
|
"fibonacci-retracement": "false",
|
|
"fibonacci-retracement-trend-based": "false",
|
|
"fill-color": "dialog",
|
|
"horizontal-line": "false",
|
|
"line-color": "dialog",
|
|
"line-menu": "menu",
|
|
"line-stroke-width": "menu",
|
|
"line-style-type": "menu",
|
|
"measurer-menu": "menu",
|
|
"pan-end": "false",
|
|
"pan-left": "false",
|
|
"pan-right": "false",
|
|
"pan-start": "false",
|
|
"parallel-channel": "false",
|
|
"shape-menu": "menu",
|
|
"text-color": "dialog",
|
|
"text-menu": "menu",
|
|
"text-size": "menu",
|
|
"vertical-line": "false",
|
|
"zoom-in": "false",
|
|
"zoom-out": "false",
|
|
callout: "false",
|
|
clear: "false",
|
|
comment: "false",
|
|
delete: "false",
|
|
line: "false",
|
|
lock: "false",
|
|
menu: "menu",
|
|
note: "false",
|
|
reset: "false",
|
|
settings: "dialog",
|
|
text: "false"
|
|
};
|
|
function getAriaHasPopupOfValue(value) {
|
|
if (typeof value !== "string")
|
|
return "false";
|
|
return ARIA_HASPOPUP[value];
|
|
}
|
|
var ToolbarButtonWidget = class extends ButtonWidget {
|
|
constructor(localeManager) {
|
|
super();
|
|
this.localeManager = localeManager;
|
|
}
|
|
update(options) {
|
|
const { localeManager } = this;
|
|
if (options.tooltip) {
|
|
const tooltip = localeManager.t(options.tooltip);
|
|
if (tooltip !== this.lastTooltip) {
|
|
this.elem.title = tooltip;
|
|
this.lastTooltip = tooltip;
|
|
}
|
|
}
|
|
let innerHTML = "";
|
|
if (options.icon != null) {
|
|
innerHTML = `<span class="${getIconClassNames(options.icon)} ag-charts-toolbar__icon"></span>`;
|
|
}
|
|
if (options.label != null) {
|
|
const label = localeManager.t(options.label);
|
|
innerHTML = `${innerHTML}<span class="ag-charts-toolbar__label">${label}</span>`;
|
|
}
|
|
const haspopup = getAriaHasPopupOfValue(options.value);
|
|
if (haspopup == "false") {
|
|
this.setAriaHasPopup(void 0);
|
|
this.setAriaExpanded(void 0);
|
|
} else {
|
|
this.setAriaHasPopup(haspopup);
|
|
this.setAriaExpanded(false);
|
|
}
|
|
if (innerHTML !== this.lastInnerHTML) {
|
|
this.elem.innerHTML = innerHTML;
|
|
this.lastInnerHTML = innerHTML;
|
|
}
|
|
}
|
|
setChecked(checked) {
|
|
setAttribute(this.elem, "aria-checked", checked);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/components/toolbar/toolbar.ts
|
|
var BUTTON_ACTIVE_CLASS = "ag-charts-toolbar__button--active";
|
|
var BaseToolbar = class extends ToolbarWidget {
|
|
constructor({ eventsHub, localeManager }, ariaLabelId, orientation) {
|
|
super(orientation);
|
|
this.ariaLabelId = ariaLabelId;
|
|
this.horizontalSpacing = 10;
|
|
this.verticalSpacing = 10;
|
|
this.events = new Listeners();
|
|
this.hasPrefix = false;
|
|
this.buttonWidgets = [];
|
|
this.updateAriaLabel = () => this.setAriaLabel(this.localeManager.t(this.ariaLabelId));
|
|
this.eventsHub = eventsHub;
|
|
this.localeManager = localeManager;
|
|
this.addClass("ag-charts-toolbar");
|
|
this.toggleClass("ag-charts-toolbar--horizontal", orientation === "horizontal");
|
|
this.toggleClass("ag-charts-toolbar--vertical", orientation === "vertical");
|
|
this.eventsHub.on("locale:change", this.updateAriaLabel);
|
|
this.updateAriaLabel();
|
|
}
|
|
setAriaLabelId(ariaLabelId) {
|
|
this.ariaLabelId = ariaLabelId;
|
|
this.updateAriaLabel();
|
|
}
|
|
addToolbarListener(eventType, handler) {
|
|
return this.events.addListener(eventType, handler);
|
|
}
|
|
clearButtons() {
|
|
this.expanded?.collapse({ mode: "2" /* DESTROY */ });
|
|
for (const button of this.buttonWidgets) {
|
|
button.destroy();
|
|
}
|
|
this.buttonWidgets.splice(0);
|
|
}
|
|
updateButtons(buttons) {
|
|
const { buttonWidgets } = this;
|
|
for (const [index, button] of buttons.entries()) {
|
|
const buttonWidget = this.buttonWidgets.at(index) ?? this.createButton(index, button);
|
|
buttonWidget.update(button);
|
|
}
|
|
for (let index = buttons.length; index < buttonWidgets.length; index++) {
|
|
const button = this.buttonWidgets.at(index);
|
|
button?.destroy();
|
|
}
|
|
this.buttonWidgets.splice(buttons.length);
|
|
this.refreshButtonClasses();
|
|
}
|
|
updateButtonByIndex(index, button) {
|
|
this.buttonWidgets.at(index)?.update(button);
|
|
}
|
|
clearActiveButton() {
|
|
for (const button of this.buttonWidgets) {
|
|
button.toggleClass(BUTTON_ACTIVE_CLASS, false);
|
|
}
|
|
}
|
|
toggleActiveButtonByIndex(index) {
|
|
if (index === -1)
|
|
return;
|
|
for (const [buttonIndex, button] of this.buttonWidgets.entries()) {
|
|
button.toggleClass(BUTTON_ACTIVE_CLASS, index != null && index === buttonIndex);
|
|
}
|
|
}
|
|
toggleButtonEnabledByIndex(index, enabled) {
|
|
if (index === -1)
|
|
return;
|
|
this.buttonWidgets.at(index)?.setEnabled(enabled);
|
|
}
|
|
toggleSwitchCheckedByIndex(index, checked) {
|
|
if (index === -1)
|
|
return;
|
|
this.buttonWidgets.at(index)?.setChecked(checked);
|
|
}
|
|
getButtonBounds() {
|
|
return this.buttonWidgets.map((buttonWidget) => this.getButtonWidgetBounds(buttonWidget));
|
|
}
|
|
setButtonHiddenByIndex(index, hidden) {
|
|
this.buttonWidgets.at(index)?.setHidden(hidden);
|
|
}
|
|
getButtonWidgetBounds(buttonWidget) {
|
|
const parent = this.getBounds();
|
|
const bounds = buttonWidget.getBounds();
|
|
return new BBox(bounds.x + parent.x, bounds.y + parent.y, bounds.width, bounds.height);
|
|
}
|
|
refreshButtonClasses() {
|
|
const { buttonWidgets, hasPrefix } = this;
|
|
let first2;
|
|
let last;
|
|
let section;
|
|
for (const [index, buttonWidget] of buttonWidgets.entries()) {
|
|
first2 = !hasPrefix && index === 0 || section != buttonWidget.section;
|
|
last = index === buttonWidgets.length - 1 || buttonWidget.section != buttonWidgets.at(index + 1)?.section;
|
|
buttonWidget.toggleClass("ag-charts-toolbar__button--first", first2);
|
|
buttonWidget.toggleClass("ag-charts-toolbar__button--last", last);
|
|
buttonWidget.toggleClass("ag-charts-toolbar__button--gap", index > 0 && first2);
|
|
section = buttonWidget.section;
|
|
}
|
|
}
|
|
createButton(index, button) {
|
|
const buttonWidget = this.createButtonWidget();
|
|
buttonWidget.addClass("ag-charts-toolbar__button");
|
|
buttonWidget.addListener("click", (event) => {
|
|
const buttonOptions = { index, ...button instanceof BaseProperties ? button.toJson() : button };
|
|
const buttonBounds = this.getButtonWidgetBounds(buttonWidget);
|
|
const params = {
|
|
event,
|
|
button: buttonOptions,
|
|
buttonBounds,
|
|
buttonWidget
|
|
};
|
|
this.events.dispatch("button-pressed", params);
|
|
});
|
|
buttonWidget.addListener("focus", () => {
|
|
const params = { button: { index } };
|
|
this.events.dispatch("button-focused", params);
|
|
});
|
|
buttonWidget.addListener("expand-controlled-widget", (e) => {
|
|
this.expanded?.collapse({ mode: "4" /* SIDLING_OPENED */ });
|
|
this.expanded = e.controlled;
|
|
const removeListener = this.expanded.addListener("collapse-widget", () => {
|
|
this.expanded = void 0;
|
|
removeListener();
|
|
});
|
|
});
|
|
if (button.section) {
|
|
buttonWidget.section = button.section;
|
|
}
|
|
this.buttonWidgets.push(buttonWidget);
|
|
this.addChild(buttonWidget);
|
|
return buttonWidget;
|
|
}
|
|
};
|
|
var Toolbar = class extends BaseToolbar {
|
|
createButtonWidget() {
|
|
return new ToolbarButtonWidget(this.localeManager);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/components/toolbar/floatingToolbar.ts
|
|
var FloatingToolbarPopover = class extends DraggablePopover {
|
|
constructor(ctx, id, onPopoverMoved) {
|
|
super(ctx, id);
|
|
this.onPopoverMoved = onPopoverMoved;
|
|
this.dragHandleDraggingClass = "ag-charts-floating-toolbar__drag-handle--dragging";
|
|
}
|
|
show(children, options = {}) {
|
|
this.showWithChildren(children, {
|
|
...options,
|
|
class: "ag-charts-floating-toolbar"
|
|
});
|
|
}
|
|
hide() {
|
|
this.dragged = false;
|
|
super.hide();
|
|
}
|
|
getBounds() {
|
|
const element2 = this.getPopoverElement();
|
|
return new BBox(
|
|
element2?.offsetLeft ?? 0,
|
|
element2?.offsetTop ?? 0,
|
|
element2?.offsetWidth ?? 0,
|
|
element2?.offsetHeight ?? 0
|
|
);
|
|
}
|
|
hasBeenDragged() {
|
|
return this.dragged;
|
|
}
|
|
setAnchor(anchor, horizontalSpacing, verticalSpacing) {
|
|
const element2 = this.getPopoverElement();
|
|
if (!element2)
|
|
return;
|
|
const position = anchor.position ?? "above";
|
|
const { offsetWidth: width2, offsetHeight: height2 } = element2;
|
|
let top = anchor.y - height2 - verticalSpacing;
|
|
let left = anchor.x - width2 / 2;
|
|
if (position === "below") {
|
|
top = anchor.y + verticalSpacing;
|
|
} else if (position === "right") {
|
|
top = anchor.y - height2 / 2;
|
|
left = anchor.x + horizontalSpacing;
|
|
} else if (position === "above-left") {
|
|
left = anchor.x;
|
|
}
|
|
this.updatePosition({ x: left, y: top });
|
|
}
|
|
ignorePointerEvents() {
|
|
const element2 = this.getPopoverElement();
|
|
if (element2)
|
|
element2.style.pointerEvents = "none";
|
|
}
|
|
capturePointerEvents() {
|
|
const element2 = this.getPopoverElement();
|
|
if (element2)
|
|
element2.style.pointerEvents = "unset";
|
|
}
|
|
updatePosition(position) {
|
|
const bounds = this.getBounds();
|
|
const canvasRect = this.ctx.domManager.getBoundingClientRect();
|
|
position.x = Math.floor(clamp(0, position.x, canvasRect.width - bounds.width));
|
|
position.y = Math.floor(clamp(0, position.y, canvasRect.height - bounds.height));
|
|
super.updatePosition(position);
|
|
this.onPopoverMoved();
|
|
}
|
|
};
|
|
var FloatingToolbar = class extends BaseToolbar {
|
|
constructor(ctx, ariaLabelId, id) {
|
|
super(ctx, ariaLabelId, "horizontal");
|
|
this.hasPrefix = true;
|
|
this.popover = new FloatingToolbarPopover(ctx, id, this.onPopoverMoved.bind(this));
|
|
this.dragHandle = new DragHandleWidget(ctx.localeManager.t("toolbarAnnotationsDragHandle"));
|
|
this.popover.setDragHandle(this.dragHandle);
|
|
}
|
|
destroy() {
|
|
super.destroy();
|
|
this.popover.destroy();
|
|
}
|
|
show(options = {}) {
|
|
this.popover.show([this.dragHandle.getElement(), this.getElement()], options);
|
|
}
|
|
hide() {
|
|
this.popover.hide();
|
|
}
|
|
setAnchor(anchor) {
|
|
this.popover.setAnchor(anchor, this.horizontalSpacing, this.verticalSpacing);
|
|
}
|
|
hasBeenDragged() {
|
|
return this.popover.hasBeenDragged();
|
|
}
|
|
ignorePointerEvents() {
|
|
this.popover.ignorePointerEvents();
|
|
}
|
|
capturePointerEvents() {
|
|
this.popover.capturePointerEvents();
|
|
}
|
|
onPopoverMoved() {
|
|
const popoverBounds = this.popover.getBounds();
|
|
if (this.popoverBounds?.equals(popoverBounds))
|
|
return;
|
|
this.popoverBounds = popoverBounds.clone();
|
|
const buttonBounds = this.getButtonBounds();
|
|
this.events.dispatch("toolbar-moved", { popoverBounds, buttonBounds });
|
|
}
|
|
getButtonWidgetBounds(buttonWidget) {
|
|
const popoverBounds = this.popover.getBounds();
|
|
const bounds = super.getButtonWidgetBounds(buttonWidget);
|
|
const dragHandleBounds = this.dragHandle.getBounds();
|
|
return new BBox(
|
|
bounds.x + popoverBounds.x - dragHandleBounds.width,
|
|
bounds.y + popoverBounds.y,
|
|
bounds.width,
|
|
bounds.height
|
|
);
|
|
}
|
|
};
|
|
var DragHandleWidget = class extends NativeWidget {
|
|
constructor(title) {
|
|
super(createElement("div", "ag-charts-floating-toolbar__drag-handle"));
|
|
const icon = new NativeWidget(
|
|
createElement("span", `${getIconClassNames("drag-handle")} ag-charts-toolbar__icon`)
|
|
);
|
|
icon.setAriaHidden(true);
|
|
this.addChild(icon);
|
|
this.elem.title = title;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/module-support.ts
|
|
var motion = { ...fromToMotion_exports, ...resetMotion_exports };
|
|
|
|
// packages/ag-charts-community/src/widget/exports.ts
|
|
var exports_exports = {};
|
|
__export(exports_exports, {
|
|
ButtonWidget: () => ButtonWidget,
|
|
MenuItemRadioWidget: () => MenuItemRadioWidget,
|
|
MenuItemWidget: () => MenuItemWidget,
|
|
MenuWidget: () => MenuWidget,
|
|
NativeWidget: () => NativeWidget,
|
|
SliderWidget: () => SliderWidget,
|
|
ToolbarWidget: () => ToolbarWidget,
|
|
WIDGET_HTML_EVENTS: () => WIDGET_HTML_EVENTS,
|
|
Widget: () => Widget,
|
|
WidgetEventUtil: () => WidgetEventUtil
|
|
});
|
|
|
|
// packages/ag-charts-community/src/api/preset/sparkline.ts
|
|
var commonAxisProperties = {
|
|
title: {
|
|
enabled: false
|
|
},
|
|
label: {
|
|
enabled: false
|
|
},
|
|
line: {
|
|
enabled: false
|
|
},
|
|
gridLine: {
|
|
enabled: false
|
|
},
|
|
crosshair: {
|
|
enabled: false,
|
|
stroke: DEFAULT_SPARKLINE_CROSSHAIR_STROKE,
|
|
lineDash: [0],
|
|
label: {
|
|
enabled: false
|
|
}
|
|
}
|
|
};
|
|
var numericAxisProperties = {
|
|
...commonAxisProperties,
|
|
nice: false
|
|
};
|
|
var chartTooltipDefaults = {
|
|
mode: "compact",
|
|
position: {
|
|
anchorTo: "node",
|
|
placement: ["right", "left"]
|
|
},
|
|
showArrow: false
|
|
};
|
|
var barGridLineDefaults = {
|
|
style: [{ stroke: { $ref: "gridLineColor" } }],
|
|
width: 2
|
|
};
|
|
var barAxisDefaults = {
|
|
number: {
|
|
gridLine: barGridLineDefaults
|
|
},
|
|
time: {
|
|
gridLine: barGridLineDefaults
|
|
},
|
|
category: {
|
|
gridLine: barGridLineDefaults
|
|
}
|
|
};
|
|
var SPARKLINE_THEME = {
|
|
overrides: {
|
|
common: {
|
|
animation: { enabled: false },
|
|
contextMenu: { enabled: false },
|
|
keyboard: { enabled: false },
|
|
background: { visible: false },
|
|
navigator: { enabled: false },
|
|
padding: {
|
|
top: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
left: 0
|
|
},
|
|
axes: {
|
|
number: {
|
|
...numericAxisProperties,
|
|
interval: {
|
|
values: [0]
|
|
}
|
|
},
|
|
log: {
|
|
...numericAxisProperties
|
|
},
|
|
time: {
|
|
...numericAxisProperties
|
|
},
|
|
category: {
|
|
...commonAxisProperties
|
|
}
|
|
}
|
|
},
|
|
bar: {
|
|
series: {
|
|
crisp: false,
|
|
label: {
|
|
placement: "inside-end",
|
|
padding: 4
|
|
},
|
|
// @ts-expect-error undocumented option
|
|
sparklineMode: true
|
|
},
|
|
tooltip: {
|
|
...chartTooltipDefaults,
|
|
position: {
|
|
...chartTooltipDefaults.position,
|
|
anchorTo: "pointer"
|
|
},
|
|
range: "nearest"
|
|
},
|
|
axes: barAxisDefaults
|
|
},
|
|
line: {
|
|
seriesArea: {
|
|
padding: {
|
|
top: 2,
|
|
right: 2,
|
|
bottom: 2,
|
|
left: 2
|
|
}
|
|
},
|
|
series: {
|
|
// @ts-expect-error undocumented option
|
|
sparklineMode: true,
|
|
strokeWidth: 1,
|
|
marker: {
|
|
enabled: false,
|
|
size: 3
|
|
}
|
|
},
|
|
tooltip: chartTooltipDefaults
|
|
},
|
|
area: {
|
|
seriesArea: {
|
|
padding: {
|
|
top: 1,
|
|
right: 0,
|
|
bottom: 1,
|
|
left: 0
|
|
}
|
|
},
|
|
series: {
|
|
strokeWidth: 1,
|
|
fillOpacity: 0.4
|
|
},
|
|
tooltip: chartTooltipDefaults
|
|
}
|
|
}
|
|
};
|
|
var setInitialBaseTheme = simpleMemorize(createInitialBaseTheme);
|
|
function createInitialBaseTheme(baseTheme, initialBaseTheme) {
|
|
if (typeof baseTheme === "string") {
|
|
return {
|
|
...initialBaseTheme,
|
|
baseTheme
|
|
};
|
|
}
|
|
if (baseTheme != null) {
|
|
return {
|
|
...baseTheme,
|
|
// @ts-expect-error internal implementation
|
|
baseTheme: setInitialBaseTheme(baseTheme.baseTheme, initialBaseTheme)
|
|
};
|
|
}
|
|
return initialBaseTheme;
|
|
}
|
|
function sparklineDataPreset(data) {
|
|
if (Array.isArray(data) && data.length !== 0) {
|
|
const firstItem = data.find((v) => v != null);
|
|
if (typeof firstItem === "number") {
|
|
const mappedData = data.map((y, x) => ({ x, y }));
|
|
return { data: mappedData, series: [{ xKey: "x", yKey: "y" }], datumKey: "y" };
|
|
} else if (Array.isArray(firstItem)) {
|
|
const mappedData = data.map((datum) => ({ x: datum?.[0], y: datum?.[1], datum }));
|
|
return { data: mappedData, series: [{ xKey: "x", yKey: "y" }], datumKey: "datum" };
|
|
}
|
|
} else if (data?.length === 0) {
|
|
return { data, series: [{ xKey: "x", yKey: "y" }], datumKey: "y" };
|
|
}
|
|
return { data };
|
|
}
|
|
function axisPreset(opts) {
|
|
switch (opts?.type) {
|
|
case "number": {
|
|
const { reverse, min, max } = opts ?? {};
|
|
return {
|
|
type: "number",
|
|
reverse,
|
|
min,
|
|
max
|
|
};
|
|
}
|
|
case "time": {
|
|
const { reverse, min, max } = opts ?? {};
|
|
return {
|
|
type: "time",
|
|
reverse,
|
|
min,
|
|
max
|
|
};
|
|
}
|
|
case "category":
|
|
default: {
|
|
if (opts == null) {
|
|
return { type: "category" };
|
|
}
|
|
const { reverse, paddingInner, paddingOuter } = opts;
|
|
return {
|
|
type: "category",
|
|
reverse,
|
|
paddingInner,
|
|
paddingOuter
|
|
};
|
|
}
|
|
}
|
|
}
|
|
function gridLinePreset(opts, defaultEnabled, sparkOpts) {
|
|
const gridLineOpts = {};
|
|
if (opts?.stroke != null) {
|
|
gridLineOpts.style = [{ stroke: opts?.stroke }];
|
|
gridLineOpts.enabled ?? (gridLineOpts.enabled = true);
|
|
}
|
|
if (opts?.strokeWidth != null) {
|
|
gridLineOpts.width = opts?.strokeWidth;
|
|
gridLineOpts.enabled ?? (gridLineOpts.enabled = true);
|
|
}
|
|
if (sparkOpts.type === "bar" && sparkOpts.direction !== "horizontal") {
|
|
gridLineOpts.enabled ?? (gridLineOpts.enabled = true);
|
|
}
|
|
if (opts?.visible != null) {
|
|
gridLineOpts.enabled = opts.visible;
|
|
}
|
|
gridLineOpts.enabled ?? (gridLineOpts.enabled = defaultEnabled);
|
|
return gridLineOpts;
|
|
}
|
|
var tooltipRendererFn = simpleMemorize((context, tooltip, datumKey) => {
|
|
return (params) => {
|
|
const xValue = params.datum[params.xKey];
|
|
const yValue = params.datum[params.yKey];
|
|
const datum = datumKey == null ? params.datum : params.datum[datumKey];
|
|
const userContent = tooltip?.renderer?.({ context, datum, xValue, yValue });
|
|
if (isString(userContent) || isNumber(userContent) || isDate(userContent)) {
|
|
return toTextString(userContent);
|
|
}
|
|
const content = userContent?.content ?? yValue.toFixed(2);
|
|
return userContent?.title ? {
|
|
heading: void 0,
|
|
title: void 0,
|
|
data: [{ label: userContent.title, value: content }]
|
|
} : {
|
|
heading: void 0,
|
|
title: content,
|
|
data: []
|
|
};
|
|
};
|
|
});
|
|
function sparkline(opts) {
|
|
const {
|
|
background,
|
|
container,
|
|
foreground,
|
|
height: height2,
|
|
listeners,
|
|
locale,
|
|
minHeight,
|
|
minWidth,
|
|
overrideDevicePixelRatio,
|
|
padding: padding2,
|
|
width: width2,
|
|
theme: baseTheme,
|
|
data: baseData,
|
|
crosshair,
|
|
axis,
|
|
min,
|
|
max,
|
|
tooltip,
|
|
context,
|
|
styleNonce,
|
|
...seriesOptions
|
|
} = opts;
|
|
const chartOpts = {
|
|
background,
|
|
container,
|
|
foreground,
|
|
height: height2,
|
|
listeners,
|
|
locale,
|
|
minHeight,
|
|
minWidth,
|
|
overrideDevicePixelRatio,
|
|
padding: padding2,
|
|
width: width2,
|
|
styleNonce
|
|
};
|
|
const { data, series: [seriesOverrides] = [], datumKey } = sparklineDataPreset(baseData);
|
|
const seriesConfig = seriesOptions;
|
|
if (seriesOverrides != null) {
|
|
Object.assign(seriesConfig, seriesOverrides);
|
|
}
|
|
seriesConfig.tooltip = {
|
|
...tooltip,
|
|
renderer: tooltipRendererFn(context, tooltip, datumKey)
|
|
};
|
|
chartOpts.theme = setInitialBaseTheme(baseTheme, SPARKLINE_THEME);
|
|
chartOpts.data = data;
|
|
chartOpts.series = [seriesConfig];
|
|
const swapAxes = seriesConfig.type === "bar" && seriesConfig.direction === "horizontal";
|
|
const [crossAxisPosition, numberAxisPosition] = swapAxes ? ["left", "bottom"] : ["bottom", "left"];
|
|
const crossAxis = {
|
|
...axisPreset(axis),
|
|
position: crossAxisPosition,
|
|
...crosshair == null ? {} : { crosshair }
|
|
};
|
|
const numberAxis = {
|
|
type: "number",
|
|
gridLine: gridLinePreset(axis, false, opts),
|
|
position: numberAxisPosition,
|
|
...min == null ? {} : { min },
|
|
...max == null ? {} : { max }
|
|
};
|
|
chartOpts.axes = swapAxes ? { x: numberAxis, y: crossAxis } : { x: crossAxis, y: numberAxis };
|
|
return chartOpts;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/api/preset/presetModules.ts
|
|
var commonSparklineOmit = [
|
|
"showInLegend",
|
|
"showInMiniChart",
|
|
"grouped",
|
|
"stacked",
|
|
"stackGroup",
|
|
"tooltip",
|
|
"listeners",
|
|
"errorBar",
|
|
"xKey",
|
|
"yKey",
|
|
"type"
|
|
];
|
|
var commonSparklineAxisOptionsDef = {
|
|
visible: boolean,
|
|
reverse: boolean,
|
|
stroke: color,
|
|
strokeWidth: positiveNumber
|
|
};
|
|
var commonSparklineOptionsDef = {
|
|
context: () => true,
|
|
tooltip: defined,
|
|
theme: defined,
|
|
background: defined,
|
|
container: defined,
|
|
width: defined,
|
|
height: defined,
|
|
minWidth: defined,
|
|
minHeight: defined,
|
|
padding: defined,
|
|
listeners: defined,
|
|
locale: defined,
|
|
data: defined,
|
|
styleNonce: string,
|
|
axis: typeUnion(
|
|
{
|
|
number: {
|
|
...commonSparklineAxisOptionsDef,
|
|
min: and(number, lessThan("max")),
|
|
max: and(number, greaterThan("min"))
|
|
},
|
|
category: {
|
|
...commonSparklineAxisOptionsDef,
|
|
paddingInner: ratio,
|
|
paddingOuter: ratio
|
|
},
|
|
time: {
|
|
...commonSparklineAxisOptionsDef,
|
|
min: and(or(number, date), lessThan("max")),
|
|
max: and(or(number, date), greaterThan("min"))
|
|
}
|
|
},
|
|
"axis options",
|
|
"category"
|
|
),
|
|
min: and(number, lessThan("max")),
|
|
max: and(number, greaterThan("min")),
|
|
crosshair: {
|
|
enabled: boolean,
|
|
snap: boolean,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef
|
|
},
|
|
xKey: string,
|
|
yKey: string
|
|
};
|
|
commonSparklineOptionsDef.overrideDevicePixelRatio = undocumented(number);
|
|
commonSparklineOptionsDef.foreground = undocumented(defined);
|
|
var SparklinePresetModule = {
|
|
type: "preset",
|
|
name: "sparkline",
|
|
version: VERSION,
|
|
options: typeUnion(
|
|
{
|
|
area: {
|
|
...commonSparklineOptionsDef,
|
|
...without(areaSeriesOptionsDef, commonSparklineOmit)
|
|
},
|
|
bar: {
|
|
...commonSparklineOptionsDef,
|
|
...without(barSeriesOptionsDef, commonSparklineOmit)
|
|
},
|
|
line: {
|
|
...commonSparklineOptionsDef,
|
|
...without(lineSeriesOptionsDef, commonSparklineOmit)
|
|
}
|
|
},
|
|
"sparkline options"
|
|
),
|
|
create: sparkline,
|
|
processData: sparklineDataPreset
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/cartesianChart.ts
|
|
var directions = ["top", "right", "bottom", "left"];
|
|
var _CartesianChart = class _CartesianChart extends Chart {
|
|
constructor(options, resources) {
|
|
super(options, resources);
|
|
// TODO should come from theme
|
|
/** Integrated Charts feature state - not used in Standalone Charts. */
|
|
this.paired = true;
|
|
this.axes = this.createChartAxes();
|
|
this.lastUpdateClipRect = void 0;
|
|
this.lastLayoutWidth = Number.NaN;
|
|
this.lastLayoutHeight = Number.NaN;
|
|
}
|
|
createChartAxes() {
|
|
return new CartesianChartAxes();
|
|
}
|
|
onAxisChange(newValue, oldValue) {
|
|
super.onAxisChange(newValue, oldValue);
|
|
this.syncAxisChanges(newValue, oldValue);
|
|
if (this.ctx != null) {
|
|
this.ctx.zoomManager.setAxes(newValue);
|
|
}
|
|
}
|
|
destroySeries(series) {
|
|
super.destroySeries(series);
|
|
this.lastLayoutWidth = Number.NaN;
|
|
this.lastLayoutHeight = Number.NaN;
|
|
}
|
|
getChartType() {
|
|
return "cartesian";
|
|
}
|
|
setRootClipRects(clipRect) {
|
|
const { seriesRoot, annotationRoot } = this;
|
|
seriesRoot.setClipRect(clipRect);
|
|
annotationRoot.setClipRect(clipRect);
|
|
}
|
|
async processData() {
|
|
await super.processData();
|
|
if (this.syncStatus === "init") {
|
|
this.syncStatus = "domains-calculated";
|
|
}
|
|
this.ctx.updateService.dispatchProcessData({ series: { shouldFlipXY: this.shouldFlipXY() } });
|
|
}
|
|
async processDomains() {
|
|
await super.processDomains();
|
|
for (const axis of this.axes) {
|
|
const syncedDomain = await this.getSyncedDomain(axis);
|
|
if (syncedDomain != null) {
|
|
axis.setDomains({ domain: syncedDomain });
|
|
}
|
|
}
|
|
}
|
|
performLayout(ctx) {
|
|
const { seriesRoot, annotationRoot } = this;
|
|
const { clipSeries, seriesRect, visible } = this.updateAxes(ctx);
|
|
this.seriesRoot.visible = visible;
|
|
this.seriesRect = seriesRect;
|
|
this.animationRect = ctx.layoutBox;
|
|
const { x, y } = seriesRect;
|
|
if (ctx.width !== this.lastLayoutWidth || ctx.height !== this.lastLayoutHeight) {
|
|
for (const group of [seriesRoot, annotationRoot]) {
|
|
group.translationX = Math.floor(x);
|
|
group.translationY = Math.floor(y);
|
|
}
|
|
} else {
|
|
const { translationX, translationY } = seriesRoot;
|
|
staticFromToMotion(
|
|
this.id,
|
|
"seriesRect",
|
|
this.ctx.animationManager,
|
|
[seriesRoot, annotationRoot],
|
|
{ translationX, translationY },
|
|
{ translationX: Math.floor(x), translationY: Math.floor(y) },
|
|
{ phase: "update" }
|
|
);
|
|
}
|
|
this.lastLayoutWidth = ctx.width;
|
|
this.lastLayoutHeight = ctx.height;
|
|
const seriesPaddedRect = seriesRect.clone().grow(this.seriesArea.getPadding());
|
|
const alwaysClip = this.series.some((s) => s.alwaysClip);
|
|
const enableClip = alwaysClip || (this.seriesArea.clip ?? false) || clipSeries;
|
|
const clipRect = enableClip ? seriesPaddedRect : void 0;
|
|
const { lastUpdateClipRect } = this;
|
|
this.lastUpdateClipRect = clipRect;
|
|
if (this.ctx.animationManager.isActive() && lastUpdateClipRect != null) {
|
|
this.ctx.animationManager.animate({
|
|
id: this.id,
|
|
groupId: "clip-rect",
|
|
phase: "update",
|
|
from: lastUpdateClipRect,
|
|
to: seriesPaddedRect,
|
|
onUpdate: (interpolatedClipRect) => this.setRootClipRects(interpolatedClipRect),
|
|
onStop: () => this.setRootClipRects(clipRect),
|
|
onComplete: () => this.setRootClipRects(clipRect)
|
|
});
|
|
} else {
|
|
this.setRootClipRects(clipRect);
|
|
}
|
|
this.ctx.layoutManager.emitLayoutComplete(ctx, {
|
|
axes: fromPairs(this.axes.map((axis) => [axis.id, axis.getLayoutState()])),
|
|
series: {
|
|
visible,
|
|
rect: seriesRect,
|
|
paddedRect: seriesPaddedRect
|
|
},
|
|
clipSeries
|
|
});
|
|
stackCartesianSeries(this.series);
|
|
}
|
|
updateAxes(layoutContext) {
|
|
const { layoutBox, scrollbars } = layoutContext;
|
|
const { clipSeries, seriesRect, overflows } = this.resolveAxesLayout(layoutBox, scrollbars);
|
|
for (const axis of this.axes) {
|
|
axis.update();
|
|
axis.setCrossLinesVisible(!overflows);
|
|
this.clipAxis(axis, seriesRect, layoutBox);
|
|
}
|
|
return { clipSeries, seriesRect, visible: !overflows };
|
|
}
|
|
// Iteratively try to resolve axis widths - since X axis width affects Y axis range,
|
|
// and vice-versa, we need to iteratively try and find a fit for the axes and their
|
|
// ticks/labels.
|
|
resolveAxesLayout(layoutBox, scrollbars) {
|
|
let newState;
|
|
let prevState;
|
|
let iterations = 0;
|
|
const maxIterations = 10;
|
|
const crossAtAxes = this.axes.filter((axis) => axis.crossAt?.value != null);
|
|
do {
|
|
prevState = newState ?? this.getDefaultState();
|
|
newState = this.updateAxesPass(
|
|
new Map(prevState.axisAreaWidths),
|
|
layoutBox.clone(),
|
|
crossAtAxes,
|
|
scrollbars
|
|
);
|
|
if (iterations++ > maxIterations) {
|
|
logger_exports.warn("Max iterations reached. Unable to stabilize axes layout.");
|
|
break;
|
|
}
|
|
} while (!this.isLayoutStable(newState, prevState));
|
|
this.lastAreaWidths = newState.axisAreaWidths;
|
|
return newState;
|
|
}
|
|
updateAxesPass(axisAreaWidths, axisAreaBound, crossAtAxes, scrollbars) {
|
|
const axisWidths = /* @__PURE__ */ new Map();
|
|
const primaryTickCounts = {};
|
|
let overflows = false;
|
|
let clipSeries = false;
|
|
const seriesAreaPadding = this.seriesArea.getPadding();
|
|
for (const dir of directions) {
|
|
const padding2 = seriesAreaPadding[dir] ?? 0;
|
|
const axis = this.axes.findLast((a) => a.position === dir);
|
|
if (axis) {
|
|
axis.seriesAreaPadding = padding2;
|
|
} else {
|
|
axisAreaBound.shrink(padding2, dir);
|
|
}
|
|
}
|
|
const totalWidth = (axisAreaWidths.get("left") ?? 0) + (axisAreaWidths.get("right") ?? 0);
|
|
const totalHeight = (axisAreaWidths.get("top") ?? 0) + (axisAreaWidths.get("bottom") ?? 0);
|
|
const crossLinePadding = this.buildCrossLinePadding(axisAreaWidths);
|
|
const crossLineHPadding = crossLinePadding.left + crossLinePadding.right;
|
|
const crossLineVPadding = crossLinePadding.top + crossLinePadding.bottom;
|
|
if (axisAreaBound.width <= totalWidth + crossLineHPadding || axisAreaBound.height <= totalHeight + crossLineVPadding) {
|
|
overflows = true;
|
|
} else {
|
|
axisAreaBound.shrink(crossLinePadding);
|
|
}
|
|
const { scene } = this.ctx;
|
|
const seriesRect = axisAreaBound.clone().shrink(Object.fromEntries(axisAreaWidths));
|
|
for (const axis of this.axes) {
|
|
const { position = "left", direction } = axis;
|
|
const isVertical = direction === "y" /* Y */;
|
|
let axisWidth;
|
|
this.sizeAxis(axis, seriesRect, position);
|
|
if (axis.thickness == null) {
|
|
const availableSize = getSize(isVertical, scene);
|
|
axisWidth = availableSize * (axis.maxThicknessRatio ?? 1);
|
|
} else {
|
|
axisWidth = axis.thickness;
|
|
}
|
|
const chartLayout = {
|
|
sizeLimit: axisWidth - axis.label.spacing,
|
|
padding: this.padding,
|
|
scrollbars
|
|
};
|
|
const { primaryTickCount, bbox } = axis.calculateLayout(
|
|
axis.nice ? primaryTickCounts[direction] : void 0,
|
|
chartLayout
|
|
);
|
|
primaryTickCounts[direction] ?? (primaryTickCounts[direction] = primaryTickCount);
|
|
clipSeries || (clipSeries = axis.dataDomain.clipped || axis.visibleRange[0] > 0 || axis.visibleRange[1] < 1);
|
|
if (axis.thickness == null) {
|
|
axisWidth = Math.min(getSize(isVertical, bbox) ?? 0, axisWidth);
|
|
}
|
|
axisWidths.set(axis.id, Math.ceil(axisWidth));
|
|
}
|
|
let crossPositions;
|
|
if (crossAtAxes.length > 0) {
|
|
crossPositions = this.calculateAxesCrossPositions(axisWidths, seriesRect, crossAtAxes);
|
|
}
|
|
const axisGroups = groupBy(this.axes, (axis) => axis.position ?? "left");
|
|
const newAxisAreaWidths = /* @__PURE__ */ new Map();
|
|
const axisOffsets = /* @__PURE__ */ new Map();
|
|
for (const [position, axes] of entries(axisGroups)) {
|
|
let currentOffset = getSize(position !== "left" && position !== "right", scene) % scene.pixelRatio;
|
|
let totalAxisWidth = 0;
|
|
for (const axis of axes ?? []) {
|
|
axisOffsets.set(axis.id, currentOffset);
|
|
const axisThickness = axisWidths.get(axis.id) ?? 0;
|
|
totalAxisWidth = Math.max(totalAxisWidth, currentOffset + axisThickness);
|
|
if (axis.layoutConstraints.stacked) {
|
|
currentOffset += axisThickness + _CartesianChart.AxesPadding;
|
|
}
|
|
}
|
|
newAxisAreaWidths.set(position, Math.ceil(totalAxisWidth));
|
|
}
|
|
for (const [position, axes] of entries(axisGroups)) {
|
|
this.positionAxes({
|
|
axes: axes ?? [],
|
|
position,
|
|
axisWidths,
|
|
axisOffsets,
|
|
axisAreaWidths: newAxisAreaWidths,
|
|
axisBound: axisAreaBound,
|
|
seriesRect
|
|
});
|
|
}
|
|
if (crossPositions != null) {
|
|
this.applyAxisCrossing(seriesRect, crossPositions);
|
|
}
|
|
return { clipSeries, seriesRect, axisAreaWidths: newAxisAreaWidths, overflows };
|
|
}
|
|
calculateAxesCrossPositions(axisWidths, seriesRect, crossAtAxes) {
|
|
const crossPositions = /* @__PURE__ */ new Map();
|
|
for (const axis of crossAtAxes) {
|
|
const { crossPosition, visible } = this.calculateAxisCrossPosition(axis);
|
|
axis.setAxisVisible(visible);
|
|
this.adjustAxisWidth(axis, axisWidths, crossPosition, seriesRect, visible);
|
|
if (crossPosition == void 0)
|
|
continue;
|
|
crossPositions.set(axis.id, crossPosition);
|
|
}
|
|
return crossPositions;
|
|
}
|
|
calculateAxisCrossPosition(axis) {
|
|
const perpendicularAxis = this.axes.perpendicular(axis);
|
|
const {
|
|
scale: { domain, bandwidth },
|
|
range: range3
|
|
} = perpendicularAxis;
|
|
const halfBandwidth = (bandwidth ?? 0) / 2;
|
|
const crossPosition = perpendicularAxis.scale.convert(axis.crossAt?.value, { clamp: false }) + halfBandwidth;
|
|
if (perpendicularAxis.inRange(crossPosition))
|
|
return { crossPosition, visible: true };
|
|
if (axis.crossAt?.sticky === false) {
|
|
return { crossPosition: void 0, visible: false };
|
|
}
|
|
const clampedPosition = Number.isNaN(crossPosition) ? range3[domain[0]] : clampArray(crossPosition, range3);
|
|
return { crossPosition: clampedPosition, visible: true };
|
|
}
|
|
adjustAxisWidth(axis, axisWidths, crossPosition, seriesRect, visible) {
|
|
const crosshairModule = axis.getModuleMap().getModule("crosshair");
|
|
if (crosshairModule?.enabled)
|
|
return;
|
|
const annotationsModule = this.modulesManager.getModule("annotations");
|
|
const hasAnnotations = annotationsModule?.enabled === true || this.ctx.annotationManager.createMemento().some((annotation) => {
|
|
switch (annotation.type) {
|
|
case "vertical-line":
|
|
return axis.direction === "x" /* X */;
|
|
case "horizontal-line":
|
|
return axis.direction === "y" /* Y */;
|
|
}
|
|
});
|
|
if (hasAnnotations)
|
|
return;
|
|
const currentWidth = axisWidths.get(axis.id) ?? 0;
|
|
const adjustedWidth = visible ? this.calculateAxisBleedingWidth(axis, currentWidth, crossPosition, seriesRect) : 0;
|
|
axisWidths.set(axis.id, adjustedWidth);
|
|
}
|
|
calculateAxisBleedingWidth(axis, actualWidth, crossPosition, seriesRect) {
|
|
if (crossPosition == null)
|
|
return actualWidth;
|
|
switch (axis.position) {
|
|
case "left":
|
|
case "top":
|
|
return Math.max(0, actualWidth - crossPosition);
|
|
case "right":
|
|
return Math.max(0, crossPosition + actualWidth - seriesRect.width);
|
|
case "bottom":
|
|
return Math.max(0, crossPosition + actualWidth - seriesRect.height);
|
|
default:
|
|
return actualWidth;
|
|
}
|
|
}
|
|
applyAxisCrossing(seriesRect, crossPositions) {
|
|
for (const axis of this.axes) {
|
|
const crossPosition = crossPositions.get(axis.id);
|
|
if (crossPosition == null) {
|
|
axis.crossAxisTranslation.x = 0;
|
|
axis.crossAxisTranslation.y = 0;
|
|
continue;
|
|
}
|
|
const isXDirection = axis.direction === "x" /* X */;
|
|
axis.crossAxisTranslation.x = isXDirection ? 0 : seriesRect.x + crossPosition - axis.translation.x;
|
|
axis.crossAxisTranslation.y = isXDirection ? seriesRect.y + crossPosition - axis.translation.y : 0;
|
|
}
|
|
}
|
|
buildCrossLinePadding(axisAreaSize) {
|
|
var _a;
|
|
const crossLinePadding = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
for (const axis of this.axes) {
|
|
const { position, label } = axis;
|
|
if (axis.crossLines) {
|
|
for (const crossLine of axis.crossLines) {
|
|
if (crossLine instanceof CartesianCrossLine) {
|
|
crossLine.position = position ?? "top";
|
|
(_a = crossLine.label).parallel ?? (_a.parallel = label.parallel);
|
|
}
|
|
crossLine.calculatePadding?.(crossLinePadding);
|
|
}
|
|
}
|
|
}
|
|
for (const [side, padding2 = 0] of entries(crossLinePadding)) {
|
|
crossLinePadding[side] = Math.max(padding2 - (axisAreaSize.get(side) ?? 0), 0);
|
|
}
|
|
return crossLinePadding;
|
|
}
|
|
clampToOutsideSeriesRect(seriesRect, value, dimension, direction) {
|
|
const bound = dimension === "x" ? seriesRect.x : seriesRect.y;
|
|
const size = dimension === "x" ? seriesRect.width : seriesRect.height;
|
|
return direction === 1 ? Math.min(value, bound + size) : Math.max(value, bound);
|
|
}
|
|
async getSyncedDomain(axis) {
|
|
const syncModule = this.modulesManager.getModule("sync");
|
|
if (!syncModule?.enabled)
|
|
return;
|
|
return await syncModule.getSyncedDomain(axis);
|
|
}
|
|
syncAxisChanges(newValue, oldValue) {
|
|
const syncModule = this.modulesManager.getModule("sync");
|
|
if (!syncModule?.enabled)
|
|
return;
|
|
const removed = new Set(oldValue ?? []);
|
|
for (const axis of newValue) {
|
|
removed.delete(axis);
|
|
}
|
|
for (const removedAxis of removed) {
|
|
syncModule.removeAxis(removedAxis);
|
|
}
|
|
}
|
|
sizeAxis(axis, seriesRect, position) {
|
|
const isNumberAxis = axis instanceof NumberAxis;
|
|
const isLeftRight = position === "left" || position === "right";
|
|
const { width: width2, height: height2 } = seriesRect;
|
|
const maxEnd = isLeftRight ? height2 : width2;
|
|
let start2 = 0;
|
|
let end3 = maxEnd;
|
|
let { min, max } = this.ctx.zoomManager.getAxisZoom(axis.id);
|
|
const { width: axisWidth, unit, align: align2 } = axis.layoutConstraints;
|
|
if (unit === "px") {
|
|
end3 = start2 + axisWidth;
|
|
} else {
|
|
end3 = end3 * axisWidth / 100;
|
|
}
|
|
const size = end3 - start2;
|
|
if (align2 === "end") {
|
|
start2 = maxEnd - size;
|
|
end3 = maxEnd;
|
|
} else if (align2 === "center") {
|
|
const center2 = start2 + (maxEnd - start2) / 2;
|
|
start2 = center2 - size / 2;
|
|
end3 = center2 + size / 2;
|
|
} else if (align2 === "justify") {
|
|
end3 = maxEnd;
|
|
}
|
|
if (isLeftRight) {
|
|
if (isNumberAxis) {
|
|
[start2, end3] = [end3, start2];
|
|
} else {
|
|
[min, max] = [1 - max, 1 - min];
|
|
}
|
|
}
|
|
axis.range = [start2, end3];
|
|
axis.visibleRange = [min, max];
|
|
axis.gridLength = isLeftRight ? width2 : height2;
|
|
axis.lineRange = isLeftRight ? [height2, 0] : [0, width2];
|
|
}
|
|
positionAxes(opts) {
|
|
const { axes, axisBound, axisWidths, axisOffsets, axisAreaWidths, seriesRect, position } = opts;
|
|
const axisAreaWidth = axisAreaWidths.get(position) ?? 0;
|
|
let mainDimension = "x";
|
|
let minorDimension = "y";
|
|
let direction = 1;
|
|
if (position === "top" || position === "bottom") {
|
|
mainDimension = "y";
|
|
minorDimension = "x";
|
|
}
|
|
let axisBoundMainOffset = axisBound[mainDimension];
|
|
if (position === "right" || position === "bottom") {
|
|
direction = -1;
|
|
axisBoundMainOffset += mainDimension === "x" ? axisBound.width : axisBound.height;
|
|
}
|
|
for (const axis of axes) {
|
|
const minorOffset = axisAreaWidths.get(minorDimension === "x" ? "left" : "top") ?? 0;
|
|
const axisThickness = axisWidths.get(axis.id) ?? 0;
|
|
const axisOffset = axisOffsets.get(axis.id) ?? 0;
|
|
axis.gridPadding = axisAreaWidth - axisOffset - axisThickness;
|
|
axis.translation[minorDimension] = axisBound[minorDimension] + minorOffset;
|
|
axis.translation[mainDimension] = this.clampToOutsideSeriesRect(
|
|
seriesRect,
|
|
axisBoundMainOffset + direction * (axisOffset + axisThickness),
|
|
mainDimension,
|
|
direction
|
|
);
|
|
}
|
|
}
|
|
shouldFlipXY() {
|
|
return this.series.every((series) => series instanceof CartesianSeries && series.shouldFlipXY());
|
|
}
|
|
getDefaultState() {
|
|
const axisAreaWidths = /* @__PURE__ */ new Map();
|
|
if (this.lastAreaWidths) {
|
|
for (const { position = "left" } of this.axes) {
|
|
const areaWidth = this.lastAreaWidths.get(position);
|
|
if (areaWidth != null) {
|
|
axisAreaWidths.set(position, areaWidth);
|
|
}
|
|
}
|
|
}
|
|
return { axisAreaWidths, clipSeries: false, overflows: false };
|
|
}
|
|
isLayoutStable(newState, prevState) {
|
|
if (prevState.overflows !== newState.overflows || prevState.clipSeries !== newState.clipSeries) {
|
|
return false;
|
|
}
|
|
for (const key of newState.axisAreaWidths.keys()) {
|
|
if (!prevState.axisAreaWidths.has(key)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (const [p, w] of prevState.axisAreaWidths.entries()) {
|
|
const otherW = newState.axisAreaWidths.get(p);
|
|
if ((w != null || otherW != null) && w !== otherW) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
clipAxis(axis, seriesRect, layoutBBox) {
|
|
const gridLinePadding = Math.ceil(axis.gridLine?.width ?? 0);
|
|
const axisLinePadding = Math.ceil(axis.line?.width ?? 0);
|
|
let { width: width2, height: height2 } = seriesRect;
|
|
width2 += axis.direction === "x" /* X */ ? gridLinePadding : axisLinePadding;
|
|
height2 += axis.direction === "y" /* Y */ ? gridLinePadding : axisLinePadding;
|
|
axis.clipGrid(seriesRect.x, seriesRect.y, width2, height2);
|
|
switch (axis.position) {
|
|
case "left":
|
|
case "right":
|
|
axis.clipTickLines(
|
|
layoutBBox.x,
|
|
seriesRect.y - gridLinePadding,
|
|
layoutBBox.width + gridLinePadding,
|
|
seriesRect.height + gridLinePadding * 2
|
|
);
|
|
break;
|
|
case "top":
|
|
case "bottom":
|
|
axis.clipTickLines(
|
|
seriesRect.x - gridLinePadding,
|
|
layoutBBox.y,
|
|
seriesRect.width + gridLinePadding * 2,
|
|
layoutBBox.height + gridLinePadding
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
_CartesianChart.className = "CartesianChart";
|
|
_CartesianChart.type = "cartesian";
|
|
_CartesianChart.AxesPadding = 15;
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue(newValue, oldValue) {
|
|
this.onAxisChange(newValue, oldValue);
|
|
}
|
|
})
|
|
], _CartesianChart.prototype, "axes", 2);
|
|
var CartesianChart = _CartesianChart;
|
|
function getSize(isVertical, bounds) {
|
|
return isVertical ? bounds?.width : bounds?.height;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/cartesianChartModule.ts
|
|
var histogramAxisTypes = /* @__PURE__ */ new Set(["number", "log", "time"]);
|
|
var invalidHistogramAxis = (axis) => isObject(axis) && axis.type != null && !histogramAxisTypes.has(axis.type);
|
|
var CartesianChartModule = {
|
|
type: "chart",
|
|
name: "cartesian",
|
|
version: VERSION,
|
|
options: cartesianChartOptionsDefs,
|
|
create(options, resources) {
|
|
return new CartesianChart(options, resources);
|
|
},
|
|
validate(options, optionsDefs2, path) {
|
|
const additionalErrors = [];
|
|
if (options?.series?.[0]?.type === "histogram") {
|
|
if (Object.values(options?.axes ?? {}).some(invalidHistogramAxis)) {
|
|
additionalErrors.push(
|
|
new ValidationError(
|
|
"invalid",
|
|
"only continuous axis types when histogram series is used",
|
|
options.axes,
|
|
path,
|
|
"axes"
|
|
)
|
|
);
|
|
options = without(options, ["axes"]);
|
|
}
|
|
}
|
|
const result = validate(options, optionsDefs2, path);
|
|
result.invalid.push(...additionalErrors);
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/polarChart.ts
|
|
var PolarChart = class extends Chart {
|
|
constructor(options, resources) {
|
|
super(options, resources);
|
|
this.axes = this.createChartAxes();
|
|
this.padding = new Padding(40);
|
|
this.ctx.axisManager.axisGroup.zIndex = 8 /* AXIS_FOREGROUND */;
|
|
}
|
|
createChartAxes() {
|
|
return new PolarChartAxes();
|
|
}
|
|
getChartType() {
|
|
return "polar";
|
|
}
|
|
isDataTransactionSupported() {
|
|
return !this.series.some((s) => s.type === "pie" || s.type === "donut");
|
|
}
|
|
async performLayout(ctx) {
|
|
const seriesRect = ctx.layoutBox.clone().shrink(this.seriesArea.getPadding());
|
|
this.seriesRect = seriesRect;
|
|
this.animationRect = seriesRect;
|
|
this.seriesRoot.translationX = seriesRect.x;
|
|
this.seriesRoot.translationY = seriesRect.y;
|
|
await this.computeCircle(seriesRect);
|
|
for (const axis of this.axes) {
|
|
axis.update();
|
|
}
|
|
let maxMarkerSize = 0;
|
|
for (const series of this.series) {
|
|
maxMarkerSize = Math.max(maxMarkerSize, series.properties.marker?.size ?? 0);
|
|
}
|
|
for (const series of this.series.filter(isPolarSeries)) {
|
|
series.maxChartMarkerSize = maxMarkerSize;
|
|
}
|
|
this.ctx.layoutManager.emitLayoutComplete(ctx, {
|
|
series: { visible: true, rect: seriesRect, paddedRect: ctx.layoutBox }
|
|
});
|
|
}
|
|
updateAxes(seriesBox, cx, cy, radius) {
|
|
if (this.axes.length === 0)
|
|
return;
|
|
const angleAxis = this.axes["angle" /* Angle */];
|
|
const radiusAxis = this.axes["radius" /* Radius */];
|
|
const angleScale = angleAxis.scale;
|
|
const innerRadiusRatio = radiusAxis.innerRadiusRatio;
|
|
angleAxis.innerRadiusRatio = innerRadiusRatio;
|
|
angleAxis.computeRange();
|
|
angleAxis.gridLength = radius;
|
|
radiusAxis.gridAngles = angleScale.ticks({
|
|
nice: [angleAxis.nice, angleAxis.nice],
|
|
interval: void 0,
|
|
tickCount: void 0,
|
|
minTickCount: 0,
|
|
maxTickCount: Infinity
|
|
})?.ticks?.map((value) => angleScale.convert(value));
|
|
radiusAxis.gridRange = angleAxis.range;
|
|
radiusAxis.range = [radius, radius * innerRadiusRatio];
|
|
for (const axis of [angleAxis, radiusAxis]) {
|
|
axis.translation.x = seriesBox.x + cx;
|
|
axis.translation.y = seriesBox.y + cy;
|
|
axis.calculateLayout();
|
|
}
|
|
}
|
|
async computeCircle(seriesBox) {
|
|
const polarSeries = this.series.filter(isPolarSeries);
|
|
const setSeriesCircle = (cx, cy, r) => {
|
|
this.updateAxes(seriesBox, cx, cy, r);
|
|
for (const series of polarSeries) {
|
|
series.centerX = cx;
|
|
series.centerY = cy;
|
|
series.radius = r;
|
|
}
|
|
const pieSeries = polarSeries.filter((s) => s.type === "donut" || s.type === "pie");
|
|
if (pieSeries.length > 1) {
|
|
const innerRadii = pieSeries.map((series) => {
|
|
const innerRadius = series.getInnerRadius();
|
|
return { series, innerRadius };
|
|
}).sort((a, b) => a.innerRadius - b.innerRadius);
|
|
innerRadii.at(-1).series.surroundingRadius = void 0;
|
|
for (let i = 0; i < innerRadii.length - 1; i++) {
|
|
innerRadii[i].series.surroundingRadius = innerRadii[i + 1].innerRadius;
|
|
}
|
|
}
|
|
};
|
|
const centerX = seriesBox.width / 2;
|
|
const centerY = seriesBox.height / 2;
|
|
const initialRadius = Math.max(0, Math.min(seriesBox.width, seriesBox.height) / 2);
|
|
let radius = initialRadius;
|
|
setSeriesCircle(centerX, centerY, radius);
|
|
const shake = async ({ hideWhenNecessary = false } = {}) => {
|
|
const labelBoxes = [];
|
|
for (const series of iterate(this.axes, polarSeries)) {
|
|
const box = await series.computeLabelsBBox({ hideWhenNecessary }, seriesBox);
|
|
if (box) {
|
|
labelBoxes.push(box);
|
|
}
|
|
}
|
|
if (labelBoxes.length === 0) {
|
|
setSeriesCircle(centerX, centerY, initialRadius);
|
|
return;
|
|
}
|
|
const labelBox = BBox.merge(labelBoxes);
|
|
const refined = this.refineCircle(labelBox, radius, seriesBox);
|
|
setSeriesCircle(refined.centerX, refined.centerY, refined.radius);
|
|
radius = refined.radius;
|
|
};
|
|
await shake();
|
|
await shake();
|
|
await shake();
|
|
await shake({ hideWhenNecessary: true });
|
|
await shake({ hideWhenNecessary: true });
|
|
for (const series of iterate(this.axes, polarSeries)) {
|
|
await series.computeLabelsBBox({ hideWhenNecessary: true }, seriesBox);
|
|
}
|
|
return { radius, centerX, centerY };
|
|
}
|
|
refineCircle(labelsBox, radius, seriesBox) {
|
|
const minCircleRatio = 0.5;
|
|
const circleLeft = -radius;
|
|
const circleTop = -radius;
|
|
const circleRight = radius;
|
|
const circleBottom = radius;
|
|
let padLeft = Math.max(0, circleLeft - labelsBox.x);
|
|
let padTop = Math.max(0, circleTop - labelsBox.y);
|
|
let padRight = Math.max(0, labelsBox.x + labelsBox.width - circleRight);
|
|
let padBottom = Math.max(0, labelsBox.y + labelsBox.height - circleBottom);
|
|
padLeft = padRight = Math.max(padLeft, padRight);
|
|
padTop = padBottom = Math.max(padTop, padBottom);
|
|
const availCircleWidth = seriesBox.width - padLeft - padRight;
|
|
const availCircleHeight = seriesBox.height - padTop - padBottom;
|
|
let newRadius = Math.min(availCircleWidth, availCircleHeight) / 2;
|
|
const minHorizontalRadius = minCircleRatio * seriesBox.width / 2;
|
|
const minVerticalRadius = minCircleRatio * seriesBox.height / 2;
|
|
const minRadius = Math.min(minHorizontalRadius, minVerticalRadius);
|
|
if (newRadius < minRadius) {
|
|
newRadius = minRadius;
|
|
const horizontalPadding = padLeft + padRight;
|
|
const verticalPadding = padTop + padBottom;
|
|
if (2 * newRadius + verticalPadding > seriesBox.height) {
|
|
const padHeight = seriesBox.height - 2 * newRadius;
|
|
if (Math.min(padTop, padBottom) * 2 > padHeight) {
|
|
padTop = padHeight / 2;
|
|
padBottom = padHeight / 2;
|
|
} else if (padTop > padBottom) {
|
|
padTop = padHeight - padBottom;
|
|
} else {
|
|
padBottom = padHeight - padTop;
|
|
}
|
|
}
|
|
if (2 * newRadius + horizontalPadding > seriesBox.width) {
|
|
const padWidth = seriesBox.width - 2 * newRadius;
|
|
if (Math.min(padLeft, padRight) * 2 > padWidth) {
|
|
padLeft = padWidth / 2;
|
|
padRight = padWidth / 2;
|
|
} else if (padLeft > padRight) {
|
|
padLeft = padWidth - padRight;
|
|
} else {
|
|
padRight = padWidth - padLeft;
|
|
}
|
|
}
|
|
}
|
|
const newWidth = padLeft + 2 * newRadius + padRight;
|
|
const newHeight = padTop + 2 * newRadius + padBottom;
|
|
return {
|
|
centerX: (seriesBox.width - newWidth) / 2 + padLeft + newRadius,
|
|
centerY: (seriesBox.height - newHeight) / 2 + padTop + newRadius,
|
|
radius: newRadius
|
|
};
|
|
}
|
|
};
|
|
PolarChart.className = "PolarChart";
|
|
PolarChart.type = "polar";
|
|
function isPolarSeries(series) {
|
|
return series instanceof PolarSeries;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/polarChartModule.ts
|
|
var PolarChartModule = {
|
|
type: "chart",
|
|
name: "polar",
|
|
version: VERSION,
|
|
options: polarChartOptionsDefs,
|
|
create(options, resources) {
|
|
return new PolarChart(options, resources);
|
|
},
|
|
validate(options, optionsDefs2, path) {
|
|
const additionalErrors = [];
|
|
const baseType = options?.series?.[0]?.type;
|
|
if (baseType === "pie" || baseType === "donut") {
|
|
if (options?.axes) {
|
|
additionalErrors.push(new UnknownError([], options.axes, path, "axes"));
|
|
options = without(options, ["axes"]);
|
|
}
|
|
}
|
|
const result = validate(options, optionsDefs2, path);
|
|
result.invalid.push(...additionalErrors);
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/gridLayout.ts
|
|
function gridLayout({
|
|
orientation,
|
|
bboxes,
|
|
maxHeight,
|
|
maxWidth,
|
|
itemPaddingY = 0,
|
|
itemPaddingX = 0,
|
|
forceResult = false
|
|
}) {
|
|
const horizontal = orientation === "horizontal";
|
|
const primary = {
|
|
max: horizontal ? maxWidth : maxHeight,
|
|
fn: horizontal ? (b) => b.width : (b) => b.height,
|
|
padding: horizontal ? itemPaddingX : itemPaddingY
|
|
};
|
|
const secondary = {
|
|
max: horizontal ? maxHeight : maxWidth,
|
|
fn: horizontal ? (b) => b.height : (b) => b.width,
|
|
padding: horizontal ? itemPaddingY : itemPaddingX
|
|
};
|
|
let processedBBoxCount = 0;
|
|
const rawPages = [];
|
|
while (processedBBoxCount < bboxes.length) {
|
|
const unprocessedBBoxes = bboxes.slice(processedBBoxCount);
|
|
const result = processBBoxes(unprocessedBBoxes, processedBBoxCount, primary, secondary, forceResult);
|
|
if (!result) {
|
|
return;
|
|
}
|
|
processedBBoxCount += result.processedBBoxCount;
|
|
rawPages.push(result.pageIndices);
|
|
}
|
|
return buildPages(rawPages, orientation, bboxes, itemPaddingY, itemPaddingX);
|
|
}
|
|
function processBBoxes(bboxes, indexOffset, primary, secondary, forceResult) {
|
|
const minGuess = 1;
|
|
let startingGuess = estimateStartingGuess(bboxes, primary);
|
|
if (startingGuess < minGuess) {
|
|
if (!forceResult) {
|
|
return;
|
|
}
|
|
startingGuess = minGuess;
|
|
}
|
|
let guess = startingGuess;
|
|
while (guess >= minGuess) {
|
|
const pageIndices = calculatePage(bboxes, indexOffset, guess, primary, secondary, forceResult);
|
|
if (pageIndices == null && guess <= minGuess) {
|
|
return;
|
|
}
|
|
if (pageIndices == null) {
|
|
guess--;
|
|
continue;
|
|
}
|
|
if (typeof pageIndices === "number") {
|
|
if (pageIndices <= minGuess) {
|
|
return;
|
|
}
|
|
guess = pageIndices < guess && pageIndices > minGuess ? pageIndices : guess;
|
|
guess--;
|
|
continue;
|
|
}
|
|
const processedBBoxCount = pageIndices.length * pageIndices[0].length;
|
|
return { processedBBoxCount, pageIndices };
|
|
}
|
|
}
|
|
function calculatePage(bboxes, indexOffset, primaryCount, primary, secondary, forceResult) {
|
|
const result = [];
|
|
let sumSecondary = 0;
|
|
let currentMaxSecondary = 0;
|
|
let currentPrimaryIndices = [];
|
|
const maxPrimaryValues = [];
|
|
for (let bboxIndex = 0; bboxIndex < bboxes.length; bboxIndex++) {
|
|
const primaryValueIdx = (bboxIndex + primaryCount) % primaryCount;
|
|
if (primaryValueIdx === 0) {
|
|
sumSecondary += currentMaxSecondary;
|
|
currentMaxSecondary = 0;
|
|
if (currentPrimaryIndices.length > 0) {
|
|
result.push(currentPrimaryIndices);
|
|
}
|
|
currentPrimaryIndices = [];
|
|
}
|
|
const primaryValue = primary.fn(bboxes[bboxIndex]) + primary.padding;
|
|
maxPrimaryValues[primaryValueIdx] = Math.max(maxPrimaryValues[primaryValueIdx] ?? 0, primaryValue);
|
|
currentMaxSecondary = Math.max(currentMaxSecondary, secondary.fn(bboxes[bboxIndex]) + secondary.padding);
|
|
const currentSecondaryDimension = sumSecondary + currentMaxSecondary;
|
|
const returnResult = !forceResult || result.length > 0;
|
|
if (currentSecondaryDimension > secondary.max && returnResult) {
|
|
currentPrimaryIndices = [];
|
|
break;
|
|
}
|
|
const sumPrimary = maxPrimaryValues.reduce((sum, next) => sum + next, 0);
|
|
if (sumPrimary > primary.max && !forceResult) {
|
|
if (maxPrimaryValues.length < primaryCount) {
|
|
return maxPrimaryValues.length;
|
|
}
|
|
return;
|
|
}
|
|
currentPrimaryIndices.push(bboxIndex + indexOffset);
|
|
}
|
|
if (currentPrimaryIndices.length > 0) {
|
|
result.push(currentPrimaryIndices);
|
|
}
|
|
return result.length > 0 ? result : void 0;
|
|
}
|
|
function buildPages(rawPages, orientation, bboxes, itemPaddingY, itemPaddingX) {
|
|
let maxPageWidth = 0;
|
|
let maxPageHeight = 0;
|
|
const pages = rawPages.map((indices) => {
|
|
if (orientation === "horizontal") {
|
|
indices = transpose(indices);
|
|
}
|
|
let endIndex = 0;
|
|
const columns = indices.map((colIndices) => {
|
|
const colBBoxes = colIndices.map((bboxIndex) => {
|
|
endIndex = Math.max(bboxIndex, endIndex);
|
|
return bboxes[bboxIndex];
|
|
});
|
|
let columnHeight = 0;
|
|
let columnWidth = 0;
|
|
for (const bbox of colBBoxes) {
|
|
columnHeight += bbox.height + itemPaddingY;
|
|
columnWidth = Math.max(columnWidth, bbox.width + itemPaddingX);
|
|
}
|
|
return {
|
|
indices: colIndices,
|
|
bboxes: colBBoxes,
|
|
columnHeight: Math.ceil(columnHeight),
|
|
columnWidth: Math.ceil(columnWidth)
|
|
};
|
|
});
|
|
let pageWidth = 0;
|
|
let pageHeight = 0;
|
|
for (const column of columns) {
|
|
pageWidth += column.columnWidth;
|
|
pageHeight = Math.max(pageHeight, column.columnHeight);
|
|
}
|
|
maxPageWidth = Math.max(pageWidth, maxPageWidth);
|
|
maxPageHeight = Math.max(pageHeight, maxPageHeight);
|
|
return {
|
|
columns,
|
|
startIndex: indices[0][0],
|
|
endIndex,
|
|
pageWidth,
|
|
pageHeight
|
|
};
|
|
});
|
|
return { pages, maxPageWidth, maxPageHeight };
|
|
}
|
|
function transpose(data) {
|
|
const result = [];
|
|
for (const _ of data[0]) {
|
|
result.push([]);
|
|
}
|
|
for (const [dataIdx, innerData] of data.entries()) {
|
|
for (const [itemIdx, item] of innerData.entries()) {
|
|
result[itemIdx][dataIdx] = item;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
function estimateStartingGuess(bboxes, primary) {
|
|
const n = bboxes.length;
|
|
let primarySum = 0;
|
|
for (let bboxIndex = 0; bboxIndex < n; bboxIndex++) {
|
|
primarySum += primary.fn(bboxes[bboxIndex]) + primary.padding;
|
|
if (primarySum > primary.max) {
|
|
const ratio2 = n / bboxIndex;
|
|
if (ratio2 < 2) {
|
|
return Math.ceil(n / 2);
|
|
}
|
|
return bboxIndex;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/pagination/pagination.ts
|
|
var PaginationLabel = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.color = "black";
|
|
this.fontStyle = void 0;
|
|
this.fontWeight = void 0;
|
|
this.fontSize = 12 /* SMALL */;
|
|
this.fontFamily = "Verdana, sans-serif";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PaginationLabel.prototype, "color", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PaginationLabel.prototype, "fontStyle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PaginationLabel.prototype, "fontWeight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PaginationLabel.prototype, "fontSize", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PaginationLabel.prototype, "fontFamily", 2);
|
|
var PaginationMarkerStyle = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.size = 15;
|
|
this.fill = void 0;
|
|
this.fillOpacity = void 0;
|
|
this.stroke = void 0;
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PaginationMarkerStyle.prototype, "size", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PaginationMarkerStyle.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PaginationMarkerStyle.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PaginationMarkerStyle.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PaginationMarkerStyle.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PaginationMarkerStyle.prototype, "strokeOpacity", 2);
|
|
var PaginationMarker = class extends BaseProperties {
|
|
constructor(parent) {
|
|
super();
|
|
this.parent = parent;
|
|
this.shape = "triangle";
|
|
this.size = 15;
|
|
this.padding = 8;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue() {
|
|
if (this.parent.marker === this) {
|
|
this.parent.onMarkerShapeChange();
|
|
}
|
|
}
|
|
})
|
|
], PaginationMarker.prototype, "shape", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PaginationMarker.prototype, "size", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PaginationMarker.prototype, "padding", 2);
|
|
var Pagination = class extends BaseProperties {
|
|
constructor(chartUpdateCallback, pageUpdateCallback) {
|
|
super();
|
|
this.chartUpdateCallback = chartUpdateCallback;
|
|
this.pageUpdateCallback = pageUpdateCallback;
|
|
this.id = createId(this);
|
|
this.marker = new PaginationMarker(this);
|
|
this.activeStyle = new PaginationMarkerStyle();
|
|
this.inactiveStyle = new PaginationMarkerStyle();
|
|
this.highlightStyle = new PaginationMarkerStyle();
|
|
this.label = new PaginationLabel();
|
|
this.group = new TranslatableGroup({ name: "pagination" });
|
|
this.labelNode = new Text();
|
|
this.totalPages = 0;
|
|
this.currentPage = 0;
|
|
this.translationX = 0;
|
|
this.translationY = 0;
|
|
this.nextButtonDisabled = false;
|
|
this.previousButtonDisabled = false;
|
|
this._visible = true;
|
|
this._enabled = true;
|
|
this._orientation = "vertical";
|
|
this.nextButton = new Marker();
|
|
this.previousButton = new Marker();
|
|
this.labelNode.setProperties({
|
|
textBaseline: "middle",
|
|
fontSize: 12 /* SMALL */,
|
|
fontFamily: "Verdana, sans-serif",
|
|
fill: "black",
|
|
y: 1
|
|
});
|
|
this.group.append([this.nextButton, this.previousButton, this.labelNode]);
|
|
this.update();
|
|
this.updateMarkers();
|
|
}
|
|
set visible(value) {
|
|
this._visible = value;
|
|
this.updateGroupVisibility();
|
|
}
|
|
get visible() {
|
|
return this._visible;
|
|
}
|
|
set enabled(value) {
|
|
this._enabled = value;
|
|
this.updateGroupVisibility();
|
|
}
|
|
get enabled() {
|
|
return this._enabled;
|
|
}
|
|
updateGroupVisibility() {
|
|
this.group.visible = this.enabled && this.visible;
|
|
}
|
|
set orientation(value) {
|
|
this._orientation = value;
|
|
switch (value) {
|
|
case "horizontal": {
|
|
this.previousButton.rotation = -Math.PI / 2;
|
|
this.nextButton.rotation = Math.PI / 2;
|
|
break;
|
|
}
|
|
case "vertical":
|
|
default: {
|
|
this.previousButton.rotation = 0;
|
|
this.nextButton.rotation = Math.PI;
|
|
}
|
|
}
|
|
}
|
|
get orientation() {
|
|
return this._orientation;
|
|
}
|
|
update() {
|
|
this.updateLabel();
|
|
this.updatePositions();
|
|
this.enableOrDisableButtons();
|
|
}
|
|
updatePositions() {
|
|
this.group.translationX = this.translationX;
|
|
this.group.translationY = this.translationY;
|
|
this.updateLabelPosition();
|
|
this.updateNextButtonPosition();
|
|
}
|
|
updateLabelPosition() {
|
|
const { size: markerSize, padding: markerPadding } = this.marker;
|
|
this.nextButton.size = markerSize;
|
|
this.previousButton.size = markerSize;
|
|
this.labelNode.x = markerSize / 2 + markerPadding;
|
|
}
|
|
updateNextButtonPosition() {
|
|
const labelBBox = this.labelNode.getBBox();
|
|
this.nextButton.translationX = labelBBox.width + (this.marker.size / 2 + this.marker.padding) * 2;
|
|
}
|
|
updateLabel() {
|
|
const {
|
|
currentPage,
|
|
totalPages: pages,
|
|
labelNode,
|
|
label: { color: color2, fontStyle, fontWeight: fontWeight2, fontSize, fontFamily }
|
|
} = this;
|
|
labelNode.text = `${currentPage + 1} / ${pages}`;
|
|
labelNode.fill = color2;
|
|
labelNode.fontStyle = fontStyle;
|
|
labelNode.fontWeight = fontWeight2;
|
|
labelNode.fontSize = fontSize;
|
|
labelNode.fontFamily = fontFamily;
|
|
}
|
|
updateMarkers() {
|
|
const {
|
|
nextButton,
|
|
previousButton,
|
|
nextButtonDisabled,
|
|
previousButtonDisabled,
|
|
activeStyle,
|
|
inactiveStyle,
|
|
highlightStyle,
|
|
highlightActive
|
|
} = this;
|
|
const buttonStyle = (button, disabled) => {
|
|
if (disabled) {
|
|
return inactiveStyle;
|
|
} else if (button === highlightActive) {
|
|
return highlightStyle;
|
|
}
|
|
return activeStyle;
|
|
};
|
|
this.updateMarker(nextButton, buttonStyle("next", nextButtonDisabled));
|
|
this.updateMarker(previousButton, buttonStyle("previous", previousButtonDisabled));
|
|
}
|
|
updateMarker(marker, style2) {
|
|
const { shape, size } = this.marker;
|
|
marker.shape = shape;
|
|
marker.size = size;
|
|
marker.fill = style2.fill;
|
|
marker.fillOpacity = style2.fillOpacity ?? 1;
|
|
marker.stroke = style2.stroke;
|
|
marker.strokeWidth = style2.strokeWidth;
|
|
marker.strokeOpacity = style2.strokeOpacity;
|
|
}
|
|
enableOrDisableButtons() {
|
|
const { currentPage, totalPages } = this;
|
|
const zeroPagesToDisplay = totalPages === 0;
|
|
const onLastPage = currentPage === totalPages - 1;
|
|
const onFirstPage = currentPage === 0;
|
|
this.nextButtonDisabled = onLastPage || zeroPagesToDisplay;
|
|
this.previousButtonDisabled = onFirstPage || zeroPagesToDisplay;
|
|
}
|
|
setPage(pageNumber) {
|
|
pageNumber = clamp(0, pageNumber, Math.max(0, this.totalPages - 1));
|
|
if (this.currentPage !== pageNumber) {
|
|
this.currentPage = pageNumber;
|
|
this.onPaginationChanged();
|
|
}
|
|
}
|
|
getCursor(node) {
|
|
return { previous: this.previousButtonDisabled, next: this.nextButtonDisabled }[node] ? void 0 : "pointer";
|
|
}
|
|
onClick(event, node) {
|
|
event.preventDefault();
|
|
if (node === "next" && !this.nextButtonDisabled) {
|
|
this.incrementPage();
|
|
this.onPaginationChanged();
|
|
} else if (node === "previous" && !this.previousButtonDisabled) {
|
|
this.decrementPage();
|
|
this.onPaginationChanged();
|
|
}
|
|
}
|
|
onMouseHover(node) {
|
|
this.highlightActive = node;
|
|
this.updateMarkers();
|
|
this.chartUpdateCallback(9 /* SCENE_RENDER */);
|
|
}
|
|
onPaginationChanged() {
|
|
this.pageUpdateCallback(this.currentPage);
|
|
}
|
|
incrementPage() {
|
|
this.currentPage = Math.min(this.currentPage + 1, this.totalPages - 1);
|
|
}
|
|
decrementPage() {
|
|
this.currentPage = Math.max(this.currentPage - 1, 0);
|
|
}
|
|
onMarkerShapeChange() {
|
|
this.updatePositions();
|
|
this.updateMarkers();
|
|
this.chartUpdateCallback(9 /* SCENE_RENDER */);
|
|
}
|
|
attachPagination(node) {
|
|
node.append(this.group);
|
|
}
|
|
getBBox() {
|
|
return this.group.getBBox();
|
|
}
|
|
computeCSSBounds() {
|
|
const prev = Transformable.toCanvas(this.previousButton);
|
|
const next = Transformable.toCanvas(this.nextButton);
|
|
return { prev, next };
|
|
}
|
|
};
|
|
Pagination.className = "Pagination";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Pagination.prototype, "marker", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Pagination.prototype, "activeStyle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Pagination.prototype, "inactiveStyle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Pagination.prototype, "highlightStyle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Pagination.prototype, "label", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/legend/legendDOMProxy.ts
|
|
var LegendDOMProxy = class {
|
|
constructor(ctx, idPrefix) {
|
|
this.dirty = true;
|
|
this.itemList = ctx.proxyInteractionService.createProxyContainer({
|
|
type: "list",
|
|
domManagerId: `${idPrefix}-toolbar`,
|
|
classList: ["ag-charts-proxy-legend-toolbar"],
|
|
ariaLabel: { id: "ariaLabelLegend" }
|
|
});
|
|
this.paginationGroup = ctx.proxyInteractionService.createProxyContainer({
|
|
type: "group",
|
|
domManagerId: `${idPrefix}-pagination`,
|
|
classList: ["ag-charts-proxy-legend-pagination"],
|
|
ariaLabel: { id: "ariaLabelLegendPagination" }
|
|
});
|
|
this.itemDescription = createElement("p");
|
|
this.itemDescription.style.display = "none";
|
|
this.itemDescription.id = createElementId();
|
|
this.itemDescription.textContent = this.getItemAriaDescription(ctx.localeManager);
|
|
this.itemList.getElement().append(this.itemDescription);
|
|
}
|
|
initLegendList(params) {
|
|
if (!this.dirty)
|
|
return;
|
|
const { ctx, itemSelection, datumReader, itemListener } = params;
|
|
const lm = ctx.localeManager;
|
|
const count = itemSelection.length;
|
|
itemSelection.each((markerLabel, datum, index) => {
|
|
markerLabel.proxyButton?.destroy();
|
|
markerLabel.proxyButton = ctx.proxyInteractionService.createProxyElement({
|
|
type: "listswitch",
|
|
textContent: this.getItemAriaText(lm, toPlainText(datumReader.getItemLabel(datum)), index, count),
|
|
ariaChecked: !!markerLabel.datum.enabled,
|
|
ariaDescribedBy: this.itemDescription.id,
|
|
parent: this.itemList
|
|
});
|
|
const button = markerLabel.proxyButton;
|
|
button.addListener("click", (ev) => itemListener.onClick(ev.sourceEvent, markerLabel.datum, button));
|
|
button.addListener("dblclick", (ev) => itemListener.onDoubleClick(ev.sourceEvent, markerLabel.datum));
|
|
button.addListener("mouseenter", (ev) => itemListener.onHover(ev.sourceEvent, markerLabel));
|
|
button.addListener("mouseleave", () => itemListener.onLeave());
|
|
button.addListener("contextmenu", (ev) => itemListener.onContextClick(ev, markerLabel));
|
|
button.addListener("blur", () => itemListener.onLeave());
|
|
button.addListener("focus", (ev) => itemListener.onHover(ev.sourceEvent, markerLabel));
|
|
button.addListener("drag-start", () => {
|
|
});
|
|
});
|
|
this.dirty = false;
|
|
}
|
|
update(params) {
|
|
if (params.visible) {
|
|
this.initLegendList(params);
|
|
this.updateItemProxyButtons(params);
|
|
this.updatePaginationProxyButtons(params, true);
|
|
}
|
|
this.updateVisibility(params.visible);
|
|
}
|
|
updateVisibility(visible) {
|
|
this.itemList.setHidden(!visible);
|
|
this.paginationGroup.setHidden(!visible);
|
|
}
|
|
updateItemProxyButtons({ itemSelection, group, pagination, interactive }) {
|
|
const groupBBox = Transformable.toCanvas(group);
|
|
this.itemList.setBounds(groupBBox);
|
|
const maxHeight = Math.max(...itemSelection.nodes().map((l) => l.getTextMeasureBBox().height));
|
|
itemSelection.each((l, _datum) => {
|
|
if (l.proxyButton) {
|
|
const visible = l.pageIndex === pagination.currentPage;
|
|
const { x, y, height: height2, width: width2 } = Transformable.toCanvas(l, l.getTextMeasureBBox());
|
|
const margin = (maxHeight - height2) / 2;
|
|
const bbox = { x: x - groupBBox.x, y: y - margin - groupBBox.y, height: maxHeight, width: width2 };
|
|
const enabled = interactive && visible;
|
|
l.proxyButton.setCursor("pointer");
|
|
l.proxyButton.setEnabled(enabled);
|
|
l.proxyButton.setPointerEvents(enabled ? void 0 : "none");
|
|
l.proxyButton.setBounds(bbox);
|
|
}
|
|
});
|
|
}
|
|
updatePaginationProxyButtons(params, init) {
|
|
const { pagination } = params;
|
|
this.paginationGroup.setHidden(!pagination.visible);
|
|
if (init && "ctx" in params) {
|
|
const { oldPages, newPages } = params;
|
|
const oldNeedsButtons = (oldPages?.length ?? newPages.length) > 1;
|
|
const newNeedsButtons = newPages.length > 1;
|
|
if (oldNeedsButtons !== newNeedsButtons) {
|
|
if (newNeedsButtons) {
|
|
this.createPaginationButtons(params);
|
|
} else {
|
|
this.destroyPaginationButtons();
|
|
}
|
|
}
|
|
this.paginationGroup.setAriaHidden(newNeedsButtons ? void 0 : true);
|
|
}
|
|
if (this.prevButton && this.nextButton) {
|
|
const { prev, next } = pagination.computeCSSBounds();
|
|
const group = BBox.merge([prev, next]);
|
|
prev.x -= group.x;
|
|
prev.y -= group.y;
|
|
next.x -= group.x;
|
|
next.y -= group.y;
|
|
this.paginationGroup.setBounds(group);
|
|
this.prevButton.setBounds(prev);
|
|
this.nextButton.setBounds(next);
|
|
this.prevButton.setEnabled(pagination.currentPage !== 0);
|
|
this.nextButton.setEnabled(pagination.currentPage !== pagination.totalPages - 1);
|
|
this.nextButton.setCursor(pagination.getCursor("next"));
|
|
this.prevButton.setCursor(pagination.getCursor("previous"));
|
|
}
|
|
}
|
|
createPaginationButtons(params) {
|
|
const { ctx, pagination } = params;
|
|
if (!this.prevButton) {
|
|
this.prevButton = ctx.proxyInteractionService.createProxyElement({
|
|
type: "button",
|
|
textContent: { id: "ariaLabelLegendPagePrevious" },
|
|
tabIndex: 0,
|
|
parent: this.paginationGroup
|
|
});
|
|
this.prevButton.addListener("click", (ev) => this.onPageButton(params, ev, "previous"));
|
|
this.prevButton.addListener("mouseenter", () => pagination.onMouseHover("previous"));
|
|
this.prevButton.addListener("mouseleave", () => pagination.onMouseHover(void 0));
|
|
}
|
|
if (!this.nextButton) {
|
|
this.nextButton = ctx.proxyInteractionService.createProxyElement({
|
|
type: "button",
|
|
textContent: { id: "ariaLabelLegendPageNext" },
|
|
tabIndex: 0,
|
|
parent: this.paginationGroup
|
|
});
|
|
this.nextButton.addListener("click", (ev) => this.onPageButton(params, ev, "next"));
|
|
this.nextButton.addListener("mouseenter", () => pagination.onMouseHover("next"));
|
|
this.nextButton.addListener("mouseleave", () => pagination.onMouseHover(void 0));
|
|
}
|
|
}
|
|
destroyPaginationButtons() {
|
|
this.nextButton?.destroy();
|
|
this.prevButton?.destroy();
|
|
this.nextButton = void 0;
|
|
this.prevButton = void 0;
|
|
}
|
|
onPageButton(params, ev, node) {
|
|
params.pagination.onClick(ev.sourceEvent, node);
|
|
this.updatePaginationProxyButtons(params, false);
|
|
}
|
|
onDataUpdate(oldData, newData) {
|
|
this.dirty = oldData.length !== newData.length || oldData.some((_v, index, _a) => {
|
|
const [newValue, oldValue] = [newData[index], oldData[index]];
|
|
return newValue.id !== oldValue.id;
|
|
});
|
|
}
|
|
onLocaleChanged(localeManager, itemSelection, datumReader) {
|
|
const count = itemSelection.length;
|
|
itemSelection.each(({ proxyButton }, datum, index) => {
|
|
const button = proxyButton?.getElement();
|
|
if (button != null) {
|
|
const label = toPlainText(datumReader.getItemLabel(datum));
|
|
button.textContent = this.getItemAriaText(localeManager, label, index, count);
|
|
}
|
|
});
|
|
this.itemDescription.textContent = this.getItemAriaDescription(localeManager);
|
|
}
|
|
onPageChange(params) {
|
|
this.updateItemProxyButtons(params);
|
|
this.updatePaginationProxyButtons(params, false);
|
|
}
|
|
getItemAriaText(localeManager, label, index, count) {
|
|
if (index >= 0 && label) {
|
|
index++;
|
|
return localeManager.t("ariaLabelLegendItem", { label, index, count });
|
|
}
|
|
return localeManager.t("ariaLabelLegendItemUnknown");
|
|
}
|
|
getItemAriaDescription(localeManager) {
|
|
return localeManager.t("ariaDescriptionLegendItem");
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/legend/legendEvent.ts
|
|
function makeLegendItemEvent(type, { itemId, seriesId, label: { text: text2 } }, event) {
|
|
const result = {
|
|
defaultPrevented: false,
|
|
apiEvent: {
|
|
type,
|
|
itemId,
|
|
// FIXME: AG-16068
|
|
seriesId,
|
|
event,
|
|
text: toPlainText(text2),
|
|
preventDefault: () => result.defaultPrevented = true
|
|
}
|
|
};
|
|
return result;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/legend/legendMarkerLabel.ts
|
|
var LegendMarkerLabel = class extends TranslatableGroup {
|
|
constructor() {
|
|
super({ name: "markerLabelGroup" });
|
|
this.symbolsGroup = this.appendChild(
|
|
new Group({
|
|
name: "legend-markerLabel-symbols",
|
|
renderToOffscreenCanvas: true,
|
|
optimizeForInfrequentRedraws: true
|
|
})
|
|
);
|
|
this.label = this.appendChild(new Text());
|
|
this.enabled = true;
|
|
this.pageIndex = Number.NaN;
|
|
this.spacing = 0;
|
|
this.length = 0;
|
|
this.isCustomMarker = false;
|
|
this.marker = this.symbolsGroup.appendChild(new Marker({ zIndex: 1 }));
|
|
this.line = this.symbolsGroup.appendChild(new Line({ zIndex: 0 }));
|
|
this.line.visible = false;
|
|
this.label.textBaseline = "middle";
|
|
this.label.y = 1;
|
|
}
|
|
destroy() {
|
|
super.destroy();
|
|
this.proxyButton?.destroy();
|
|
}
|
|
setEnabled(enabled) {
|
|
this.enabled = enabled;
|
|
this.refreshVisibilities();
|
|
}
|
|
getTextMeasureBBox() {
|
|
this.layout();
|
|
return BBox.merge([this.symbolsGroup.getBBox(), this.label.getTextMeasureBBox()]);
|
|
}
|
|
refreshVisibilities() {
|
|
const opacity = this.enabled ? 1 : 0.5;
|
|
this.label.opacity = opacity;
|
|
this.opacity = opacity;
|
|
}
|
|
layout() {
|
|
const { marker, line, length: length2, isCustomMarker } = this;
|
|
let centerTranslateX = 0;
|
|
let centerTranslateY = 0;
|
|
if (marker.visible) {
|
|
const { size } = marker;
|
|
const anchor = Marker.anchor(marker.shape);
|
|
centerTranslateX = (anchor.x - 0.5) * size + length2 / 2;
|
|
centerTranslateY = (anchor.y - 0.5) * size;
|
|
if (isCustomMarker) {
|
|
marker.x = 0;
|
|
marker.y = 0;
|
|
marker.translationX = centerTranslateX;
|
|
marker.translationY = centerTranslateY;
|
|
} else {
|
|
marker.x = centerTranslateX;
|
|
marker.y = centerTranslateY;
|
|
marker.translationX = 0;
|
|
marker.translationY = 0;
|
|
}
|
|
}
|
|
if (line.visible) {
|
|
line.x1 = 0;
|
|
line.x2 = length2;
|
|
line.y1 = 0;
|
|
line.y2 = 0;
|
|
}
|
|
}
|
|
preRender(renderCtx) {
|
|
const out = super.preRender(renderCtx);
|
|
this.layout();
|
|
return out;
|
|
}
|
|
layoutLabel() {
|
|
const { length: length2, spacing } = this;
|
|
this.label.x = length2 + spacing;
|
|
}
|
|
computeBBox() {
|
|
this.layout();
|
|
return super.computeBBox();
|
|
}
|
|
};
|
|
LegendMarkerLabel.className = "MarkerLabel";
|
|
__decorateClass([
|
|
ProxyPropertyOnWrite("label")
|
|
], LegendMarkerLabel.prototype, "text", 2);
|
|
__decorateClass([
|
|
ProxyPropertyOnWrite("label")
|
|
], LegendMarkerLabel.prototype, "fontStyle", 2);
|
|
__decorateClass([
|
|
ProxyPropertyOnWrite("label")
|
|
], LegendMarkerLabel.prototype, "fontWeight", 2);
|
|
__decorateClass([
|
|
ProxyPropertyOnWrite("label")
|
|
], LegendMarkerLabel.prototype, "fontSize", 2);
|
|
__decorateClass([
|
|
ProxyPropertyOnWrite("label")
|
|
], LegendMarkerLabel.prototype, "fontFamily", 2);
|
|
__decorateClass([
|
|
ProxyPropertyOnWrite("label", "fill")
|
|
], LegendMarkerLabel.prototype, "color", 2);
|
|
__decorateClass([
|
|
ObserveChanges((target) => target.layoutLabel())
|
|
], LegendMarkerLabel.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
ObserveChanges((target) => target.layoutLabel())
|
|
], LegendMarkerLabel.prototype, "length", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], LegendMarkerLabel.prototype, "isCustomMarker", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/legend/legend.ts
|
|
function toHighlightNodeDatum(series, legendDatum) {
|
|
switch (typeof legendDatum.itemId) {
|
|
case "number":
|
|
return {
|
|
series,
|
|
itemId: void 0,
|
|
datum: void 0,
|
|
datumIndex: legendDatum.itemId,
|
|
legendItemName: legendDatum.legendItemName
|
|
};
|
|
case "string":
|
|
return {
|
|
series,
|
|
itemId: legendDatum.itemId,
|
|
datum: void 0,
|
|
datumIndex: void 0,
|
|
legendItemName: legendDatum.legendItemName
|
|
};
|
|
default:
|
|
return legendDatum.itemId;
|
|
}
|
|
}
|
|
var LegendLabel = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.maxLength = void 0;
|
|
this.color = "black";
|
|
this.fontStyle = void 0;
|
|
this.fontWeight = void 0;
|
|
this.fontSize = 12 /* SMALL */;
|
|
this.fontFamily = "Verdana, sans-serif";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendLabel.prototype, "maxLength", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendLabel.prototype, "color", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendLabel.prototype, "fontStyle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendLabel.prototype, "fontWeight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendLabel.prototype, "fontSize", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendLabel.prototype, "fontFamily", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendLabel.prototype, "formatter", 2);
|
|
var LegendMarker = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.shape = void 0;
|
|
this.size = 15;
|
|
this.padding = 8;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendMarker.prototype, "shape", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendMarker.prototype, "size", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendMarker.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendMarker.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendMarker.prototype, "enabled", 2);
|
|
var LegendLine = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendLine.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendLine.prototype, "length", 2);
|
|
var LegendItem = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.paddingX = 16;
|
|
this.paddingY = 8;
|
|
this.showSeriesStroke = false;
|
|
this.marker = new LegendMarker();
|
|
this.label = new LegendLabel();
|
|
this.line = new LegendLine();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendItem.prototype, "maxWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendItem.prototype, "paddingX", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendItem.prototype, "paddingY", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendItem.prototype, "showSeriesStroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendItem.prototype, "marker", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendItem.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendItem.prototype, "line", 2);
|
|
var LegendListeners = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendListeners.prototype, "legendItemClick", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LegendListeners.prototype, "legendItemDoubleClick", 2);
|
|
var fillGradientDefaults2 = {
|
|
type: "gradient",
|
|
bounds: "item",
|
|
gradient: "linear",
|
|
colorStops: [{ color: "black" }],
|
|
rotation: 0,
|
|
reverse: false,
|
|
colorSpace: "rgb"
|
|
};
|
|
var fillPatternDefaults2 = {
|
|
type: "pattern",
|
|
pattern: "forward-slanted-lines",
|
|
width: 8,
|
|
height: 8,
|
|
padding: 1,
|
|
fill: "black",
|
|
fillOpacity: 1,
|
|
backgroundFill: "white",
|
|
backgroundFillOpacity: 1,
|
|
stroke: "black",
|
|
strokeOpacity: 1,
|
|
strokeWidth: 1,
|
|
rotation: 0,
|
|
scale: 1
|
|
};
|
|
var fillImageDefaults2 = {
|
|
type: "image",
|
|
backgroundFill: "black",
|
|
backgroundFillOpacity: 1,
|
|
rotation: 0,
|
|
repeat: "no-repeat",
|
|
fit: "contain",
|
|
width: 8,
|
|
height: 8
|
|
};
|
|
var Legend = class extends BaseProperties {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.id = createId(this);
|
|
this.group = new TranslatableGroup({ name: "legend", zIndex: 16 /* LEGEND */ });
|
|
this.itemSelection = Selection.select(
|
|
this.group,
|
|
LegendMarkerLabel
|
|
);
|
|
this.containerNode = this.group.appendChild(new Rect({ name: "legend-container" }));
|
|
this.oldSize = [0, 0];
|
|
this.pages = [];
|
|
this.maxPageSize = [0, 0];
|
|
/** Item index to track on re-pagination, so current page updates appropriately. */
|
|
this.paginationTrackingIndex = 0;
|
|
this.truncatedItems = /* @__PURE__ */ new Set();
|
|
this._data = [];
|
|
this.toggleSeries = true;
|
|
this.item = new LegendItem();
|
|
this.listeners = new LegendListeners();
|
|
this.enabled = false;
|
|
this.position = "bottom";
|
|
this.border = new Border(this.containerNode);
|
|
this.cornerRadius = 0;
|
|
this.fillOpacity = 1;
|
|
this.padding = 4;
|
|
this.spacing = 0;
|
|
this.cleanup = new CleanupRegistry();
|
|
this.size = [0, 0];
|
|
this._visible = true;
|
|
this.pagination = new Pagination(
|
|
(type) => ctx.updateService.update(type),
|
|
(page) => this.updatePageNumber(page)
|
|
);
|
|
this.pagination.attachPagination(this.group);
|
|
const { items } = ctx.contextMenuRegistry.builtins;
|
|
items["toggle-series-visibility"].action = (params) => this.contextToggleVisibility(params);
|
|
items["toggle-other-series"].action = (params) => this.contextToggleOtherSeries(params);
|
|
this.cleanup.register(
|
|
ctx.eventsHub.on("active:load-memento", (event) => this.onActiveLoadMemento(event)),
|
|
ctx.eventsHub.on("active:update", (event) => this.onActiveUpdate(event)),
|
|
ctx.eventsHub.on("legend:change", this.onLegendDataChange.bind(this)),
|
|
ctx.eventsHub.on("legend:change-partial", this.onLegendDataChangePartial.bind(this)),
|
|
ctx.layoutManager.registerElement(1 /* Legend */, (e) => this.positionLegend(e)),
|
|
ctx.eventsHub.on("locale:change", () => this.onLocaleChanged()),
|
|
() => delete items["toggle-series-visibility"].action,
|
|
() => delete items["toggle-other-series"].action,
|
|
() => this.group.remove()
|
|
);
|
|
this.domProxy = new LegendDOMProxy(this.ctx, this.id);
|
|
this.ctx.historyManager.addMementoOriginator(ctx.legendManager);
|
|
}
|
|
set data(value) {
|
|
if (objectsEqual(value, this._data))
|
|
return;
|
|
this.domProxy.onDataUpdate(this._data, value);
|
|
this._data = value;
|
|
this.updateGroupVisibility();
|
|
}
|
|
get data() {
|
|
return this._data;
|
|
}
|
|
onLegendDataChange({ legendData = [] }) {
|
|
if (!this.enabled)
|
|
return;
|
|
this.data = legendData.filter((datum) => !datum.hideInLegend);
|
|
}
|
|
onLegendDataChangePartial(event) {
|
|
this.itemSelection.each(({ proxyButton }, { itemId }) => {
|
|
if (proxyButton == null)
|
|
return;
|
|
for (const eventElem of event.legendData) {
|
|
if (eventElem.itemId === itemId) {
|
|
proxyButton.setChecked(eventElem.enabled);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
destroy() {
|
|
this.ctx.domManager.removeChild("canvas-overlay", `${this.id}-toolbar`);
|
|
this.ctx.domManager.removeChild("canvas-overlay", `${this.id}-pagination`);
|
|
this.cleanup.flush();
|
|
this.itemSelection.clear();
|
|
}
|
|
getOrientation() {
|
|
return this.orientation ?? "horizontal";
|
|
}
|
|
set visible(value) {
|
|
this._visible = value;
|
|
this.updateGroupVisibility();
|
|
}
|
|
get visible() {
|
|
return this._visible;
|
|
}
|
|
updateGroupVisibility() {
|
|
this.group.visible = this.enabled && this.visible && this.data.length > 0;
|
|
}
|
|
updateItemSelection() {
|
|
const data = [...this.data];
|
|
if (this.reverseOrder) {
|
|
data.reverse();
|
|
}
|
|
this.itemSelection.update(data);
|
|
}
|
|
isInteractive() {
|
|
const {
|
|
toggleSeries,
|
|
listeners: { legendItemClick, legendItemDoubleClick }
|
|
} = this;
|
|
return toggleSeries || legendItemDoubleClick != null || legendItemClick != null;
|
|
}
|
|
checkInteractionState() {
|
|
return this.ctx.interactionManager.isState(1 /* Frozen */);
|
|
}
|
|
attachLegend(scene) {
|
|
scene.appendChild(this.group);
|
|
}
|
|
getItemLabel(datum) {
|
|
const { formatter: formatter2 } = this.item.label;
|
|
if (formatter2) {
|
|
const seriesDatum = datum.datum;
|
|
return this.cachedCallWithContext(formatter2, {
|
|
itemId: datum.itemId,
|
|
value: datum.label.text,
|
|
seriesId: datum.seriesId,
|
|
...seriesDatum && { datum: seriesDatum }
|
|
});
|
|
}
|
|
return datum.label.text;
|
|
}
|
|
/**
|
|
* The method is given the desired size of the legend, which only serves as a hint.
|
|
* The vertically oriented legend will take as much horizontal space as needed, but will
|
|
* respect the height constraints, and the horizontal legend will take as much vertical
|
|
* space as needed in an attempt not to exceed the given width.
|
|
* After the layout is done, the {@link size} will contain the actual size of the legend.
|
|
* If the actual size is not the same as the previous actual size, the legend will fire
|
|
* the 'layoutChange' event to communicate that another layout is needed, and the above
|
|
* process should be repeated.
|
|
* @param width
|
|
* @param height
|
|
*/
|
|
calcLayout(width2, height2) {
|
|
const {
|
|
paddingX,
|
|
paddingY,
|
|
label,
|
|
maxWidth,
|
|
label: { maxLength = Infinity, fontStyle, fontWeight: fontWeight2, fontSize, fontFamily }
|
|
} = this.item;
|
|
this.updateItemSelection();
|
|
const bboxes = [];
|
|
const measurer3 = cachedTextMeasurer(label);
|
|
const itemMaxWidthPercentage = 0.8;
|
|
const maxItemWidth = maxWidth ?? width2 * itemMaxWidthPercentage;
|
|
const { markerWidth, anyLineEnabled } = this.calculateMarkerWidth();
|
|
this.itemSelection.each((markerLabel, datum) => {
|
|
markerLabel.fontStyle = fontStyle;
|
|
markerLabel.fontWeight = fontWeight2;
|
|
markerLabel.fontSize = fontSize;
|
|
markerLabel.fontFamily = fontFamily;
|
|
const paddedSymbolWidth = this.updateMarkerLabel(markerLabel, datum, markerWidth, anyLineEnabled);
|
|
const id = datum.itemId ?? datum.id;
|
|
const labelText = this.getItemLabel(datum);
|
|
const text2 = toPlainText(labelText, "<unknown>").replace(LineSplitter, " ");
|
|
markerLabel.text = this.truncate(text2, maxLength, maxItemWidth, paddedSymbolWidth, measurer3, id);
|
|
bboxes.push(markerLabel.getTextMeasureBBox());
|
|
});
|
|
width2 = Math.max(1, width2);
|
|
height2 = Math.max(1, height2);
|
|
if (!Number.isFinite(width2)) {
|
|
return {};
|
|
}
|
|
[width2, height2] = this.updateContainer(width2, height2);
|
|
const size = this.size;
|
|
const oldSize = this.oldSize;
|
|
size[0] = width2;
|
|
size[1] = height2;
|
|
if (size[0] !== oldSize[0] || size[1] !== oldSize[1]) {
|
|
oldSize[0] = size[0];
|
|
oldSize[1] = size[1];
|
|
}
|
|
const { pages, maxPageHeight, maxPageWidth } = this.updatePagination(bboxes, width2, height2);
|
|
const oldPages = this.pages;
|
|
this.pages = pages;
|
|
this.maxPageSize = [maxPageWidth - paddingX, maxPageHeight - paddingY];
|
|
const pageNumber = this.pagination.currentPage;
|
|
const page = this.pages[pageNumber];
|
|
if (this.pages.length < 1 || !page) {
|
|
this.visible = false;
|
|
return { oldPages };
|
|
}
|
|
this.visible = true;
|
|
this.updatePositions(pageNumber);
|
|
this.update();
|
|
return { oldPages };
|
|
}
|
|
isCustomMarker(markerEnabled3, shape) {
|
|
return markerEnabled3 && shape !== void 0 && typeof shape !== "string";
|
|
}
|
|
calcSymbolsEnabled(symbol) {
|
|
const { showSeriesStroke, marker } = this.item;
|
|
const markerEnabled3 = !!marker.enabled || !showSeriesStroke || (symbol.marker.enabled ?? true);
|
|
const lineEnabled = !!(symbol.line && showSeriesStroke);
|
|
const isCustomMarker = this.isCustomMarker(markerEnabled3, symbol.marker.shape);
|
|
return { markerEnabled: markerEnabled3, lineEnabled, isCustomMarker };
|
|
}
|
|
calcSymbolsLengths(symbol, markerEnabled3, lineEnabled) {
|
|
const { marker, line } = this.item;
|
|
let customMarkerSize;
|
|
const { shape } = symbol.marker;
|
|
if (this.isCustomMarker(markerEnabled3, shape)) {
|
|
const tmpShape = new Marker();
|
|
tmpShape.shape = shape;
|
|
tmpShape.updatePath();
|
|
const bbox = tmpShape.getBBox();
|
|
customMarkerSize = Math.max(bbox.width, bbox.height);
|
|
}
|
|
const markerLength = markerEnabled3 ? marker.size : 0;
|
|
const lineLength = lineEnabled ? line.length ?? 25 : 0;
|
|
return { markerLength, lineLength, customMarkerSize };
|
|
}
|
|
calculateMarkerWidth() {
|
|
let markerWidth = 0;
|
|
let anyLineEnabled = false;
|
|
this.itemSelection.each((_, datum) => {
|
|
const { symbol } = datum;
|
|
const { lineEnabled, markerEnabled: markerEnabled3 } = this.calcSymbolsEnabled(symbol);
|
|
const {
|
|
markerLength,
|
|
lineLength,
|
|
customMarkerSize = -Infinity
|
|
} = this.calcSymbolsLengths(symbol, markerEnabled3, lineEnabled);
|
|
markerWidth = Math.max(markerWidth, lineLength, customMarkerSize, markerLength);
|
|
anyLineEnabled || (anyLineEnabled = lineEnabled);
|
|
});
|
|
return { markerWidth, anyLineEnabled };
|
|
}
|
|
updateMarkerLabel(markerLabel, datum, markerWidth, anyLineEnabled) {
|
|
const { marker: itemMarker, paddingX } = this.item;
|
|
const { symbol } = datum;
|
|
let paddedSymbolWidth = paddingX;
|
|
const { markerEnabled: markerEnabled3, isCustomMarker } = this.calcSymbolsEnabled(symbol);
|
|
const spacing = itemMarker.padding;
|
|
if (markerEnabled3 || anyLineEnabled) {
|
|
paddedSymbolWidth += spacing + markerWidth;
|
|
}
|
|
const { marker, line } = markerLabel;
|
|
marker.visible = markerEnabled3;
|
|
if (marker.visible) {
|
|
marker.shape = itemMarker.shape ?? symbol.marker.shape ?? "square";
|
|
marker.size = itemMarker.size;
|
|
marker.setStyleProperties(this.getMarkerStyles(deepClone(symbol)));
|
|
}
|
|
line.visible = anyLineEnabled;
|
|
if (line.visible) {
|
|
line.setStyleProperties(this.getLineStyles(symbol));
|
|
}
|
|
markerLabel.length = markerWidth;
|
|
markerLabel.spacing = spacing;
|
|
markerLabel.isCustomMarker = isCustomMarker;
|
|
return paddedSymbolWidth;
|
|
}
|
|
updateContainer(width2, height2) {
|
|
const containerStyles = this.getContainerStyles();
|
|
this.containerNode.width = 0;
|
|
this.containerNode.height = 0;
|
|
this.containerNode.setStyleProperties(containerStyles);
|
|
this.containerNode.cornerRadius = containerStyles.cornerRadius;
|
|
width2 -= containerStyles.strokeWidth * 2 + containerStyles.padding.left + containerStyles.padding.right;
|
|
height2 -= containerStyles.strokeWidth * 2 + containerStyles.padding.top + containerStyles.padding.bottom;
|
|
return [width2, height2];
|
|
}
|
|
truncate(text2, maxCharLength, maxItemWidth, paddedMarkerWidth, measurer3, id) {
|
|
let addEllipsis = false;
|
|
if (text2.length > maxCharLength) {
|
|
text2 = text2.substring(0, maxCharLength);
|
|
addEllipsis = true;
|
|
}
|
|
const result = truncateLine(text2, measurer3, maxItemWidth - paddedMarkerWidth, addEllipsis);
|
|
if (isTextTruncated(result)) {
|
|
this.truncatedItems.add(id);
|
|
} else {
|
|
this.truncatedItems.delete(id);
|
|
}
|
|
return result;
|
|
}
|
|
updatePagination(bboxes, width2, height2) {
|
|
const orientation = this.getOrientation();
|
|
const trackingIndex = Math.min(this.paginationTrackingIndex, bboxes.length);
|
|
this.pagination.orientation = orientation;
|
|
this.pagination.translationX = 0;
|
|
this.pagination.translationY = 0;
|
|
const { pages, maxPageHeight, maxPageWidth, paginationBBox, paginationVertical } = this.calculatePagination(
|
|
bboxes,
|
|
width2,
|
|
height2
|
|
);
|
|
const newCurrentPage = pages.findIndex((p) => p.endIndex >= trackingIndex);
|
|
this.pagination.currentPage = clamp(0, newCurrentPage, pages.length - 1);
|
|
const { paddingX: itemPaddingX, paddingY: itemPaddingY } = this.item;
|
|
const paginationComponentPadding = 8;
|
|
const legendItemsWidth = maxPageWidth - itemPaddingX;
|
|
const legendItemsHeight = maxPageHeight - itemPaddingY;
|
|
let paginationX = 0;
|
|
let paginationY = -paginationBBox.y - this.item.marker.size / 2;
|
|
if (paginationVertical) {
|
|
paginationY += legendItemsHeight + paginationComponentPadding;
|
|
} else {
|
|
paginationX += -paginationBBox.x + legendItemsWidth + paginationComponentPadding;
|
|
paginationY += (legendItemsHeight - paginationBBox.height) / 2;
|
|
}
|
|
this.pagination.translationX = paginationX;
|
|
this.pagination.translationY = paginationY;
|
|
this.pagination.update();
|
|
this.pagination.updateMarkers();
|
|
let pageIndex = 0;
|
|
this.itemSelection.each((markerLabel, _, nodeIndex) => {
|
|
if (nodeIndex > (pages[pageIndex]?.endIndex ?? Infinity)) {
|
|
pageIndex++;
|
|
}
|
|
markerLabel.pageIndex = pageIndex;
|
|
});
|
|
return {
|
|
maxPageHeight,
|
|
maxPageWidth,
|
|
pages
|
|
};
|
|
}
|
|
calculatePagination(bboxes, width2, height2) {
|
|
const { paddingX: itemPaddingX, paddingY: itemPaddingY } = this.item;
|
|
const vertPositions = [
|
|
"left",
|
|
"left-top",
|
|
"left-bottom",
|
|
"right",
|
|
"right-top",
|
|
"right-bottom"
|
|
];
|
|
const { placement } = expandLegendPosition(this.position);
|
|
const orientation = this.getOrientation();
|
|
const paginationVertical = vertPositions.includes(placement);
|
|
let paginationBBox = this.pagination.getBBox();
|
|
let lastPassPaginationBBox = new BBox(0, 0, 0, 0);
|
|
let pages = [];
|
|
let maxPageWidth = 0;
|
|
let maxPageHeight = 0;
|
|
let count = 0;
|
|
const stableOutput = (bbox) => {
|
|
return bbox.width === paginationBBox.width && bbox.height === paginationBBox.height;
|
|
};
|
|
const forceResult = this.maxWidth !== void 0 && this.maxHeight !== void 0;
|
|
do {
|
|
if (count++ > 10) {
|
|
logger_exports.warn("unable to find stable legend layout.");
|
|
break;
|
|
}
|
|
paginationBBox = lastPassPaginationBBox;
|
|
const maxWidth = width2 - (paginationVertical ? 0 : paginationBBox.width);
|
|
const maxHeight = height2 - (paginationVertical ? paginationBBox.height : 0);
|
|
const layout = gridLayout({
|
|
orientation,
|
|
bboxes,
|
|
maxHeight,
|
|
maxWidth,
|
|
itemPaddingY,
|
|
itemPaddingX,
|
|
forceResult
|
|
});
|
|
pages = layout?.pages ?? [];
|
|
maxPageWidth = layout?.maxPageWidth ?? 0;
|
|
maxPageHeight = layout?.maxPageHeight ?? 0;
|
|
const totalPages = pages.length;
|
|
this.pagination.visible = totalPages > 1;
|
|
this.pagination.totalPages = totalPages;
|
|
this.pagination.update();
|
|
this.pagination.updateMarkers();
|
|
lastPassPaginationBBox = this.pagination.getBBox();
|
|
if (!this.pagination.visible) {
|
|
break;
|
|
}
|
|
} while (!stableOutput(lastPassPaginationBBox));
|
|
return { maxPageWidth, maxPageHeight, pages, paginationBBox: lastPassPaginationBBox, paginationVertical };
|
|
}
|
|
updatePositions(pageNumber = 0) {
|
|
const {
|
|
item: { paddingY },
|
|
itemSelection,
|
|
pages
|
|
} = this;
|
|
if (pages.length < 1 || !pages[pageNumber]) {
|
|
return;
|
|
}
|
|
const { columns, startIndex: visibleStart, endIndex: visibleEnd } = pages[pageNumber];
|
|
let x = 0;
|
|
let y = 0;
|
|
const columnCount = columns.length;
|
|
const rowCount = columns[0].indices.length;
|
|
const horizontal = this.getOrientation() === "horizontal";
|
|
const itemHeight = columns[0].bboxes[0].height + paddingY;
|
|
const rowSumColumnWidths = [];
|
|
itemSelection.each((markerLabel, _, i) => {
|
|
if (i < visibleStart || i > visibleEnd) {
|
|
markerLabel.visible = false;
|
|
return;
|
|
}
|
|
const pageIndex = i - visibleStart;
|
|
let columnIndex;
|
|
let rowIndex;
|
|
if (horizontal) {
|
|
columnIndex = pageIndex % columnCount;
|
|
rowIndex = Math.floor(pageIndex / columnCount);
|
|
} else {
|
|
columnIndex = Math.floor(pageIndex / rowCount);
|
|
rowIndex = pageIndex % rowCount;
|
|
}
|
|
markerLabel.visible = true;
|
|
const column = columns[columnIndex];
|
|
if (!column) {
|
|
return;
|
|
}
|
|
y = Math.floor(itemHeight * rowIndex);
|
|
x = Math.floor(rowSumColumnWidths[rowIndex] ?? 0);
|
|
rowSumColumnWidths[rowIndex] = (rowSumColumnWidths[rowIndex] ?? 0) + column.columnWidth;
|
|
markerLabel.translationX = x;
|
|
markerLabel.translationY = y;
|
|
});
|
|
}
|
|
updatePageNumber(pageNumber) {
|
|
const { itemSelection, group, pagination, pages } = this;
|
|
const { startIndex, endIndex } = pages[pageNumber];
|
|
if (startIndex === 0) {
|
|
this.paginationTrackingIndex = 0;
|
|
} else if (pageNumber === pages.length - 1) {
|
|
this.paginationTrackingIndex = endIndex;
|
|
} else {
|
|
this.paginationTrackingIndex = Math.floor((startIndex + endIndex) / 2);
|
|
}
|
|
this.pagination.update();
|
|
this.pagination.updateMarkers();
|
|
this.updatePositions(pageNumber);
|
|
this.domProxy.onPageChange({ itemSelection, group, pagination, interactive: this.isInteractive() });
|
|
this.ctx.updateService.update(9 /* SCENE_RENDER */);
|
|
}
|
|
update() {
|
|
const {
|
|
label: { color: color2 }
|
|
} = this.item;
|
|
this.itemSelection.each((markerLabel, datum) => {
|
|
markerLabel.setEnabled(datum.enabled);
|
|
markerLabel.color = color2;
|
|
});
|
|
this.updateContextMenu();
|
|
}
|
|
updateContextMenu() {
|
|
const action = this.toggleSeries ? "show" : "hide";
|
|
this.ctx.contextMenuRegistry.toggle("toggle-series-visibility", action);
|
|
this.ctx.contextMenuRegistry.toggle("toggle-other-series", action);
|
|
}
|
|
getLineStyles(datum) {
|
|
const { stroke: stroke3, strokeOpacity = 1, strokeWidth, lineDash } = datum.line ?? {};
|
|
const defaultLineStrokeWidth = Math.min(2, strokeWidth ?? 1);
|
|
return {
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth: this.item.line.strokeWidth ?? defaultLineStrokeWidth,
|
|
lineDash
|
|
};
|
|
}
|
|
getMarkerStyles({ marker }) {
|
|
const { fill, stroke: stroke3, strokeOpacity = 1, fillOpacity = 1, strokeWidth, lineDash, lineDashOffset } = marker;
|
|
const defaultLineStrokeWidth = Math.min(2, strokeWidth ?? 1);
|
|
if (isPatternFill(fill)) {
|
|
fill.width = 8;
|
|
fill.height = 8;
|
|
fill.padding = 1;
|
|
fill.strokeWidth = Math.min(2, fill.strokeWidth ?? 2);
|
|
}
|
|
if (isImageFill(fill)) {
|
|
fill.fit = "contain";
|
|
fill.width = void 0;
|
|
fill.height = void 0;
|
|
fill.repeat = "no-repeat";
|
|
}
|
|
return getShapeStyle(
|
|
{
|
|
fill,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
fillOpacity,
|
|
strokeWidth: this.item.marker.strokeWidth ?? defaultLineStrokeWidth,
|
|
lineDash,
|
|
lineDashOffset
|
|
},
|
|
fillGradientDefaults2,
|
|
fillPatternDefaults2,
|
|
fillImageDefaults2
|
|
);
|
|
}
|
|
getContainerStyles() {
|
|
const { stroke: stroke3, strokeOpacity, strokeWidth } = this.border;
|
|
const { cornerRadius, fill, fillOpacity, padding: padding2 } = this;
|
|
const isPaddingNumber = typeof padding2 === "number";
|
|
return getShapeStyle(
|
|
{
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
padding: {
|
|
top: isPaddingNumber ? padding2 : padding2.top ?? 0,
|
|
right: isPaddingNumber ? padding2 : padding2.right ?? 0,
|
|
bottom: isPaddingNumber ? padding2 : padding2.bottom ?? 0,
|
|
left: isPaddingNumber ? padding2 : padding2.left ?? 0
|
|
},
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth: this.border.enabled ? strokeWidth : 0
|
|
},
|
|
fillGradientDefaults2,
|
|
fillPatternDefaults2,
|
|
fillImageDefaults2
|
|
);
|
|
}
|
|
computePagedBBox() {
|
|
const actualBBox = Group.computeChildrenBBox(this.group.excludeChildren({ name: "legend-container" }));
|
|
if (this.pages.length > 1) {
|
|
const [maxPageWidth, maxPageHeight] = this.maxPageSize;
|
|
actualBBox.height = Math.max(maxPageHeight, actualBBox.height);
|
|
actualBBox.width = Math.max(maxPageWidth, actualBBox.width);
|
|
}
|
|
const { strokeWidth, padding: padding2 } = this.getContainerStyles();
|
|
actualBBox.grow(padding2).grow(strokeWidth);
|
|
return actualBBox;
|
|
}
|
|
findNode(params) {
|
|
const { datum, proxyButton } = this.itemSelection.select((ml) => ml.datum?.itemId === params.itemId)[0] ?? {};
|
|
if (datum === void 0 || proxyButton === void 0) {
|
|
throw new Error(
|
|
`AG Charts - Missing required properties { datum: ${datum}, proxyButton: ${JSON.stringify(proxyButton)} }`
|
|
);
|
|
}
|
|
return { datum, proxyButton };
|
|
}
|
|
contextToggleVisibility(params) {
|
|
const { datum, proxyButton } = this.findNode(params);
|
|
this.doClick(params.event, datum, proxyButton);
|
|
this.clearHighlight();
|
|
}
|
|
contextToggleOtherSeries(params) {
|
|
this.doDoubleClick(params.event, this.findNode(params).datum);
|
|
this.clearHighlight();
|
|
}
|
|
onContextClick(widgetEvent, node) {
|
|
if (this.checkInteractionState())
|
|
return;
|
|
const { sourceEvent } = widgetEvent;
|
|
const legendItem = node.datum;
|
|
this.clearHighlight();
|
|
if (this.preventHidingAll && this.contextMenuDatum?.enabled && this.getVisibleItemCount() <= 1) {
|
|
this.ctx.contextMenuRegistry.builtins.items["toggle-series-visibility"].enabled = false;
|
|
} else {
|
|
this.ctx.contextMenuRegistry.builtins.items["toggle-series-visibility"].enabled = true;
|
|
}
|
|
const toggleOtherSeriesVisible = this.ctx.chartService.series.length > 1 && this.ctx.chartService.series[0]?.getLegendData("category")[0]?.hideToggleOtherSeries !== true;
|
|
const action = toggleOtherSeriesVisible ? "show" : "hide";
|
|
this.ctx.contextMenuRegistry.toggle("toggle-other-series", action);
|
|
const { offsetX, offsetY } = sourceEvent;
|
|
const { x: canvasX, y: canvasY } = Transformable.toCanvasPoint(node, offsetX, offsetY);
|
|
this.ctx.contextMenuRegistry.dispatchContext("legend-item", { widgetEvent, canvasX, canvasY }, { legendItem });
|
|
}
|
|
onClick(event, datum, proxyButton) {
|
|
if (this.checkInteractionState())
|
|
return;
|
|
if (this.doClick(event, datum, proxyButton)) {
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
getVisibleItemCount() {
|
|
return this.ctx.chartService.series.flatMap((s) => s.getLegendData("category")).filter((d) => d.enabled).length;
|
|
}
|
|
doClick(event, datum, proxyButton) {
|
|
const {
|
|
listeners: { legendItemClick },
|
|
ctx: { chartService },
|
|
preventHidingAll,
|
|
toggleSeries
|
|
} = this;
|
|
if (!datum) {
|
|
return false;
|
|
}
|
|
const { legendType, seriesId, itemId, enabled, legendItemName } = datum;
|
|
const series = chartService.series.find((s) => s.id === seriesId);
|
|
if (!series) {
|
|
return false;
|
|
}
|
|
let newEnabled = enabled;
|
|
const clickEvent = makeLegendItemEvent("click", datum, event);
|
|
if (legendItemClick) {
|
|
callWithContext([series.properties, this.ctx.chartService], legendItemClick, clickEvent.apiEvent);
|
|
}
|
|
if (clickEvent.defaultPrevented)
|
|
return true;
|
|
if (toggleSeries) {
|
|
newEnabled = !enabled;
|
|
if (preventHidingAll && !newEnabled) {
|
|
const numVisibleItems = this.getVisibleItemCount();
|
|
if (numVisibleItems < 2) {
|
|
newEnabled = true;
|
|
}
|
|
}
|
|
proxyButton.setChecked(newEnabled);
|
|
this.ctx.eventsHub.emit("legend:item-click", {
|
|
legendType,
|
|
series,
|
|
itemId,
|
|
enabled: newEnabled,
|
|
legendItemName
|
|
});
|
|
}
|
|
this.updateHighlight(newEnabled, datum, series);
|
|
this.ctx.legendManager.update();
|
|
this.ctx.updateService.update(2 /* PROCESS_DATA */, {
|
|
forceNodeDataRefresh: true,
|
|
skipAnimations: datum.skipAnimations ?? false
|
|
});
|
|
return true;
|
|
}
|
|
onDoubleClick(event, datum) {
|
|
if (this.checkInteractionState())
|
|
return;
|
|
if (this.doDoubleClick(event, datum)) {
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
doDoubleClick(event, datum) {
|
|
const {
|
|
listeners: { legendItemDoubleClick },
|
|
ctx: { chartService },
|
|
toggleSeries
|
|
} = this;
|
|
if (!datum) {
|
|
return false;
|
|
}
|
|
const { legendType, id, itemId, seriesId } = datum;
|
|
const series = chartService.series.find((s) => s.id === id);
|
|
if (!series) {
|
|
return false;
|
|
}
|
|
const doubleClickEvent = makeLegendItemEvent("dblclick", datum, event);
|
|
if (legendItemDoubleClick) {
|
|
callWithContext(
|
|
[series.properties, this.ctx.chartService],
|
|
legendItemDoubleClick,
|
|
doubleClickEvent.apiEvent
|
|
);
|
|
}
|
|
if (doubleClickEvent.defaultPrevented)
|
|
return true;
|
|
if (toggleSeries) {
|
|
const legendData = chartService.series.flatMap((s) => s.getLegendData("category"));
|
|
let numVisibleItems = 0;
|
|
const visibleLegendItemNames = /* @__PURE__ */ new Set();
|
|
for (const d of legendData) {
|
|
if (!d.enabled)
|
|
continue;
|
|
numVisibleItems += 1;
|
|
if (d.legendItemName != null) {
|
|
visibleLegendItemNames.add(d.legendItemName);
|
|
}
|
|
}
|
|
if (visibleLegendItemNames.size > 0) {
|
|
numVisibleItems = visibleLegendItemNames.size;
|
|
}
|
|
const clickedItem = legendData.find((d) => d.itemId === itemId && d.seriesId === seriesId);
|
|
this.ctx.eventsHub.emit("legend:item-double-click", {
|
|
legendType,
|
|
series,
|
|
itemId,
|
|
numVisibleItems,
|
|
enabled: clickedItem?.enabled ?? false,
|
|
legendItemName: clickedItem?.legendItemName
|
|
});
|
|
}
|
|
this.ctx.legendManager.update();
|
|
this.ctx.updateService.update(2 /* PROCESS_DATA */, { forceNodeDataRefresh: true });
|
|
return true;
|
|
}
|
|
toTooltipMeta(event, node) {
|
|
let point;
|
|
if (event instanceof FocusEvent) {
|
|
point = Transformable.toCanvas(node).computeCenter();
|
|
} else {
|
|
event.preventDefault();
|
|
point = Transformable.toCanvasPoint(node, event.offsetX, event.offsetY);
|
|
}
|
|
return { canvasX: point.x, canvasY: point.y, showArrow: false };
|
|
}
|
|
onHover(event, node) {
|
|
if (this.checkInteractionState())
|
|
return;
|
|
if (!this.enabled)
|
|
throw new Error("AG Charts - onHover handler called on disabled legend");
|
|
this.pagination.setPage(node.pageIndex);
|
|
const datum = node.datum;
|
|
const series = datum ? this.ctx.chartService.series.find((s) => s.id === datum?.id) : void 0;
|
|
if (datum && this.truncatedItems.has(datum.itemId ?? datum.id)) {
|
|
const meta = this.toTooltipMeta(event, node);
|
|
this.ctx.tooltipManager.updateTooltip(this.id, meta, [
|
|
{ type: "structured", title: this.getItemLabel(datum) }
|
|
]);
|
|
} else {
|
|
this.ctx.tooltipManager.removeTooltip(this.id, void 0, true);
|
|
}
|
|
this.updateHighlight(datum?.enabled, datum, series);
|
|
}
|
|
onLeave() {
|
|
if (this.checkInteractionState())
|
|
return;
|
|
this.ctx.tooltipManager.removeTooltip(this.id, void 0, true);
|
|
this.clearHighlight();
|
|
}
|
|
clearHighlight() {
|
|
this.updateHighlight(void 0, void 0, void 0);
|
|
}
|
|
updateHighlight(enabled, legendDatum, series, event) {
|
|
const updateManagers = (opts) => {
|
|
if (opts === void 0) {
|
|
this.ctx.activeManager.clear();
|
|
} else {
|
|
const seriesId = opts.nodeDatum.series.id;
|
|
const itemId = opts.itemId;
|
|
this.ctx.activeManager.update({ type: "legend", seriesId, itemId }, void 0);
|
|
}
|
|
this.ctx.highlightManager.updateHighlight(this.id, opts?.nodeDatum);
|
|
};
|
|
const highlightNodeDatum = (opts) => {
|
|
if (this.ctx.interactionManager.isState(64 /* Default */) || event?.initialState) {
|
|
updateManagers(opts);
|
|
} else if (this.ctx.interactionManager.isState(4 /* Animation */)) {
|
|
this.ctx.animationManager.onBatchStop(() => {
|
|
updateManagers(opts);
|
|
});
|
|
} else if (opts === void 0) {
|
|
updateManagers(opts);
|
|
}
|
|
};
|
|
if (enabled === true && series !== void 0 && legendDatum !== void 0) {
|
|
const itemId = legendDatum.itemId;
|
|
const nodeDatum = toHighlightNodeDatum(series, legendDatum);
|
|
highlightNodeDatum({ itemId, nodeDatum });
|
|
} else {
|
|
highlightNodeDatum(void 0);
|
|
}
|
|
}
|
|
onActiveUpdate(activeItem) {
|
|
if (activeItem?.type === "series-node") {
|
|
this.ctx.highlightManager.updateHighlight(this.id, void 0);
|
|
}
|
|
}
|
|
onActiveLoadMemento(event) {
|
|
const { activeItem } = event;
|
|
if (activeItem?.type !== "legend") {
|
|
return this.ctx.highlightManager.updateHighlight(this.id, void 0);
|
|
}
|
|
const datum = this.data.find((d) => d.seriesId === activeItem.seriesId && d.itemId === activeItem.itemId);
|
|
const series = this.ctx.chartService.series.find((s) => s.id === activeItem.seriesId);
|
|
if (series === void 0) {
|
|
logger_exports.warn(`Cannot find seriesId: "${activeItem.seriesId}"`);
|
|
event.reject();
|
|
} else if (datum === void 0) {
|
|
const json = JSON.stringify({ seriesId: activeItem.seriesId, itemId: activeItem.itemId });
|
|
logger_exports.warn(`cannot find legend item: ${json}`);
|
|
event.reject();
|
|
} else {
|
|
this.updateHighlight(datum.enabled, datum, series, event);
|
|
}
|
|
}
|
|
onLocaleChanged() {
|
|
this.updateItemSelection();
|
|
this.domProxy.onLocaleChanged(this.ctx.localeManager, this.itemSelection, this);
|
|
}
|
|
positionLegend(ctx) {
|
|
const oldPages = this.positionLegendScene(ctx);
|
|
this.positionLegendDOM(oldPages);
|
|
}
|
|
positionLegendScene(ctx) {
|
|
if (!this.enabled || !this.data.length)
|
|
return;
|
|
const { placement, floating, xOffset, yOffset } = expandLegendPosition(this.position);
|
|
const layoutBox = floating ? new BBox(0, 0, ctx.width, ctx.height) : ctx.layoutBox;
|
|
const { x, y, width: width2, height: height2 } = layoutBox;
|
|
const [legendWidth, legendHeight] = this.calculateLegendDimensions(layoutBox);
|
|
const { oldPages } = this.calcLayout(legendWidth, legendHeight);
|
|
const legendBBox = this.computePagedBBox();
|
|
if (this.visible) {
|
|
let unreachable2 = function(_a) {
|
|
return void 0;
|
|
};
|
|
var unreachable = unreachable2;
|
|
const legendSpacing = this.spacing;
|
|
let translationX;
|
|
let translationY;
|
|
switch (placement) {
|
|
case "top":
|
|
translationX = (width2 - legendBBox.width) / 2;
|
|
translationY = 0;
|
|
break;
|
|
case "bottom":
|
|
translationX = (width2 - legendBBox.width) / 2;
|
|
translationY = height2 - legendBBox.height;
|
|
break;
|
|
case "right":
|
|
translationX = width2 - legendBBox.width;
|
|
translationY = (height2 - legendBBox.height) / 2;
|
|
break;
|
|
case "left":
|
|
translationX = 0;
|
|
translationY = (height2 - legendBBox.height) / 2;
|
|
break;
|
|
case "top-right":
|
|
case "right-top":
|
|
translationX = width2 - legendBBox.width;
|
|
translationY = 0;
|
|
break;
|
|
case "top-left":
|
|
case "left-top":
|
|
translationX = 0;
|
|
translationY = 0;
|
|
break;
|
|
case "bottom-right":
|
|
case "right-bottom":
|
|
translationX = width2 - legendBBox.width;
|
|
translationY = height2 - legendBBox.height;
|
|
break;
|
|
case "bottom-left":
|
|
case "left-bottom":
|
|
translationX = 0;
|
|
translationY = height2 - legendBBox.height;
|
|
break;
|
|
default:
|
|
unreachable2(placement);
|
|
}
|
|
if (!floating) {
|
|
let shrinkAmount;
|
|
let shrinkDirection;
|
|
switch (placement) {
|
|
case "top":
|
|
case "top-right":
|
|
case "top-left":
|
|
shrinkAmount = legendBBox.height + legendSpacing;
|
|
shrinkDirection = "top";
|
|
break;
|
|
case "bottom":
|
|
case "bottom-right":
|
|
case "bottom-left":
|
|
shrinkAmount = legendBBox.height + legendSpacing;
|
|
shrinkDirection = "bottom";
|
|
break;
|
|
case "left":
|
|
case "left-top":
|
|
case "left-bottom":
|
|
shrinkAmount = legendBBox.width + legendSpacing;
|
|
shrinkDirection = "left";
|
|
break;
|
|
case "right":
|
|
case "right-top":
|
|
case "right-bottom":
|
|
shrinkAmount = legendBBox.width + legendSpacing;
|
|
shrinkDirection = "right";
|
|
break;
|
|
default:
|
|
unreachable2(placement);
|
|
}
|
|
layoutBox.shrink(shrinkAmount, shrinkDirection);
|
|
}
|
|
translationX += xOffset;
|
|
translationY += yOffset;
|
|
this.group.translationX = Math.floor(x + translationX - legendBBox.x);
|
|
this.group.translationY = Math.floor(y + translationY - legendBBox.y);
|
|
this.containerNode.x = legendBBox.x;
|
|
this.containerNode.y = legendBBox.y;
|
|
this.containerNode.width = legendBBox.width;
|
|
this.containerNode.height = legendBBox.height;
|
|
}
|
|
return oldPages;
|
|
}
|
|
positionLegendDOM(oldPages) {
|
|
const { ctx, itemSelection, pagination, pages: newPages, group } = this;
|
|
const visible = this.visible && this.enabled;
|
|
const interactive = this.isInteractive();
|
|
this.domProxy.update({
|
|
visible,
|
|
interactive,
|
|
ctx,
|
|
itemSelection,
|
|
group,
|
|
pagination,
|
|
oldPages,
|
|
newPages,
|
|
datumReader: this,
|
|
itemListener: this
|
|
});
|
|
}
|
|
calculateLegendDimensions(shrinkRect) {
|
|
const { width: width2, height: height2 } = shrinkRect;
|
|
const { placement } = expandLegendPosition(this.position);
|
|
const aspectRatio = width2 / height2;
|
|
const maxCoefficient = 0.5;
|
|
const minHeightCoefficient = 0.2;
|
|
const minWidthCoefficient = 0.25;
|
|
let legendWidth, legendHeight;
|
|
function unreachable(_a) {
|
|
return void 0;
|
|
}
|
|
switch (placement) {
|
|
case "top":
|
|
case "top-left":
|
|
case "top-right":
|
|
case "bottom":
|
|
case "bottom-left":
|
|
case "bottom-right": {
|
|
const heightCoefficient = aspectRatio < 1 ? Math.min(maxCoefficient, minHeightCoefficient * (1 / aspectRatio)) : minHeightCoefficient;
|
|
legendWidth = this.maxWidth ? Math.min(this.maxWidth, width2) : width2;
|
|
legendHeight = this.maxHeight ? Math.min(this.maxHeight, height2) : Math.round(height2 * heightCoefficient);
|
|
break;
|
|
}
|
|
case "left":
|
|
case "left-top":
|
|
case "left-bottom":
|
|
case "right":
|
|
case "right-top":
|
|
case "right-bottom": {
|
|
const widthCoefficient = aspectRatio > 1 ? Math.min(maxCoefficient, minWidthCoefficient * aspectRatio) : minWidthCoefficient;
|
|
legendWidth = this.maxWidth ? Math.min(this.maxWidth, width2) : Math.round(width2 * widthCoefficient);
|
|
legendHeight = this.maxHeight ? Math.min(this.maxHeight, height2) : height2;
|
|
break;
|
|
}
|
|
default:
|
|
unreachable(placement);
|
|
}
|
|
return [legendWidth, legendHeight];
|
|
}
|
|
cachedCallWithContext(fn, params) {
|
|
const { callbackCache, chartService } = this.ctx;
|
|
return callbackCache.call([this, chartService], fn, params);
|
|
}
|
|
};
|
|
Legend.className = "Legend";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "toggleSeries", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "pagination", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "item", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "listeners", 2);
|
|
__decorateClass([
|
|
ObserveChanges((target, newValue, oldValue) => {
|
|
target.updateGroupVisibility();
|
|
if (newValue === oldValue) {
|
|
return;
|
|
}
|
|
const {
|
|
ctx: { legendManager, stateManager }
|
|
} = target;
|
|
if (oldValue === false && newValue === true) {
|
|
stateManager.restoreState(legendManager);
|
|
}
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "position", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "maxWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "maxHeight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "reverseOrder", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "orientation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "preventHidingAll", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "border", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "xOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Legend.prototype, "yOffset", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/legend/legendModule.ts
|
|
var LegendModule = {
|
|
type: "plugin",
|
|
name: "legend",
|
|
version: VERSION,
|
|
// TODO fix missing behaviour
|
|
// removable: 'standalone-only',
|
|
options: {
|
|
enabled: boolean,
|
|
position: legendPositionValidator,
|
|
orientation: union("horizontal", "vertical"),
|
|
maxWidth: positiveNumber,
|
|
maxHeight: positiveNumber,
|
|
spacing: positiveNumber,
|
|
border: borderOptionsDef,
|
|
cornerRadius: number,
|
|
padding,
|
|
fill: colorUnion,
|
|
fillOpacity: ratio,
|
|
preventHidingAll: boolean,
|
|
reverseOrder: boolean,
|
|
toggleSeries: boolean,
|
|
item: {
|
|
marker: {
|
|
size: positiveNumber,
|
|
shape: shapeValidator,
|
|
padding: positiveNumber,
|
|
strokeWidth: positiveNumber
|
|
},
|
|
line: {
|
|
length: positiveNumber,
|
|
strokeWidth: positiveNumber
|
|
},
|
|
label: {
|
|
maxLength: positiveNumber,
|
|
formatter: callback,
|
|
...fontOptionsDef
|
|
},
|
|
maxWidth: positiveNumber,
|
|
paddingX: positiveNumber,
|
|
paddingY: positiveNumber,
|
|
showSeriesStroke: boolean
|
|
},
|
|
pagination: {
|
|
marker: {
|
|
size: positiveNumber,
|
|
shape: shapeValidator,
|
|
padding: positiveNumber
|
|
},
|
|
activeStyle: {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef
|
|
},
|
|
inactiveStyle: {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef
|
|
},
|
|
highlightStyle: {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef
|
|
},
|
|
label: fontOptionsDef
|
|
},
|
|
listeners: {
|
|
legendItemClick: callback,
|
|
legendItemDoubleClick: callback
|
|
}
|
|
},
|
|
themeTemplate: {
|
|
...LEGEND_CONTAINER_THEME,
|
|
enabled: {
|
|
$and: [
|
|
{ $greaterThan: [{ $size: { $path: "/series" } }, 1] },
|
|
{
|
|
$or: [
|
|
{ $isChartType: "cartesian" },
|
|
{ $isChartType: "standalone" },
|
|
{
|
|
$and: [
|
|
{ $isChartType: "polar" },
|
|
{ $not: { $isSeriesType: "pie" } },
|
|
{ $not: { $isSeriesType: "donut" } }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
position: "bottom" /* BOTTOM */,
|
|
orientation: {
|
|
$if: [
|
|
{
|
|
$or: [
|
|
{ $eq: [{ $path: "./position" }, "left" /* LEFT */] },
|
|
{ $eq: [{ $path: "./position" }, "left-top" /* LEFT_TOP */] },
|
|
{ $eq: [{ $path: "./position" }, "left-bottom" /* LEFT_BOTTOM */] },
|
|
{ $eq: [{ $path: "./position" }, "right" /* RIGHT */] },
|
|
{ $eq: [{ $path: "./position" }, "right-top" /* RIGHT_TOP */] },
|
|
{ $eq: [{ $path: "./position" }, "right-bottom" /* RIGHT_BOTTOM */] },
|
|
{ $eq: [{ $path: "./position/placement" }, "left" /* LEFT */] },
|
|
{ $eq: [{ $path: "./position/placement" }, "left-top" /* LEFT_TOP */] },
|
|
{ $eq: [{ $path: "./position/placement" }, "left-bottom" /* LEFT_BOTTOM */] },
|
|
{ $eq: [{ $path: "./position/placement" }, "right" /* RIGHT */] },
|
|
{ $eq: [{ $path: "./position/placement" }, "right-top" /* RIGHT_TOP */] },
|
|
{ $eq: [{ $path: "./position/placement" }, "right-bottom" /* RIGHT_BOTTOM */] }
|
|
]
|
|
},
|
|
"vertical",
|
|
"horizontal"
|
|
]
|
|
},
|
|
spacing: 30,
|
|
listeners: {},
|
|
toggleSeries: true,
|
|
item: {
|
|
paddingX: 16,
|
|
paddingY: 8,
|
|
marker: { size: 15, padding: 8 },
|
|
showSeriesStroke: true,
|
|
label: {
|
|
color: { $ref: "textColor" },
|
|
fontSize: { $rem: FONT_SIZE_RATIO.SMALL },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" }
|
|
}
|
|
},
|
|
reverseOrder: false,
|
|
pagination: {
|
|
marker: { size: 12 },
|
|
activeStyle: { fill: { $ref: "foregroundColor" } },
|
|
inactiveStyle: { fill: { $ref: "subtleTextColor" } },
|
|
highlightStyle: { fill: { $ref: "foregroundColor" } },
|
|
label: { color: { $ref: "textColor" } }
|
|
},
|
|
fill: {
|
|
$if: [{ $path: ["./position/floating", false] }, { $ref: "chartBackgroundColor" }, "transparent"]
|
|
}
|
|
},
|
|
create: (ctx) => {
|
|
const moduleInstance = new Legend(ctx);
|
|
moduleInstance.attachLegend(ctx.scene);
|
|
return moduleInstance;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/axis/logAxis.ts
|
|
var LogAxis = class extends NumberAxis {
|
|
getVisibleDomain(domain) {
|
|
const [d0, d1] = domain;
|
|
const [r0, r1] = this.visibleRange;
|
|
if (domain.length < 2) {
|
|
return [d0, d1];
|
|
}
|
|
const min = Math.min(d0, d1);
|
|
const max = Math.max(d0, d1);
|
|
if (min >= 0) {
|
|
const log0 = Math.log(d0);
|
|
const log1 = Math.log(d1);
|
|
const span = log1 - log0;
|
|
return [Math.exp(log0 + r0 * span), Math.exp(log0 + r1 * span)];
|
|
}
|
|
if (max <= 0) {
|
|
const log0 = -Math.log(-d0);
|
|
const log1 = -Math.log(-d1);
|
|
const span = log1 - log0;
|
|
return [-Math.exp(-(log0 + r0 * span)), -Math.exp(-(log0 + r1 * span))];
|
|
}
|
|
return [Number.NaN, Number.NaN];
|
|
}
|
|
normaliseDataDomain(d) {
|
|
const { min, max, preferredMin, preferredMax } = this;
|
|
const { extent: extent2, clipped } = normalisedExtentWithMetadata(
|
|
d.domain,
|
|
min,
|
|
max,
|
|
preferredMin,
|
|
preferredMax,
|
|
void 0,
|
|
d.sortMetadata?.sortOrder
|
|
);
|
|
if (extent2[0] < 0 && extent2[1] > 0 || d.domain[0] < 0 && d.domain[1] > 0) {
|
|
logger_exports.warn(
|
|
`The log axis domain crosses zero, the chart data cannot be rendered. See log axis documentation for more information.`
|
|
);
|
|
return { domain: [], clipped };
|
|
} else if (extent2[0] === 0 || extent2[1] === 0 || d.domain[0] === 0 || d.domain[1] === 0) {
|
|
logger_exports.warn(
|
|
`The log axis domain contains a value of 0, the chart data cannot be rendered. See log axis documentation for more information.`
|
|
);
|
|
return { domain: [], clipped };
|
|
}
|
|
return { domain: extent2, clipped };
|
|
}
|
|
set base(value) {
|
|
this.scale.base = value;
|
|
}
|
|
get base() {
|
|
return this.scale.base;
|
|
}
|
|
constructor(moduleCtx) {
|
|
super(moduleCtx, new LogScale());
|
|
}
|
|
};
|
|
LogAxis.className = "LogAxis";
|
|
LogAxis.type = "log";
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/areaAggregation.ts
|
|
var MAX_POINTS = 10;
|
|
function aggregationIndexType(xValues, d0, d1, indexData, maxRange, datumIndex, xNeedsValueOf) {
|
|
const xValue = xValues[datumIndex];
|
|
if (xValue === void 0)
|
|
return -1;
|
|
const xRatio = Number.isFinite(d0) ? aggregationXRatioForXValue(xValue, d0, d1, xNeedsValueOf) : aggregationXRatioForDatumIndex(datumIndex, xValues.length);
|
|
const aggIndex = aggregationIndexForXRatio(xRatio, maxRange);
|
|
if (datumIndex === indexData[aggIndex + AGGREGATION_INDEX_X_MIN] || datumIndex === indexData[aggIndex + AGGREGATION_INDEX_X_MAX] || datumIndex === indexData[aggIndex + AGGREGATION_INDEX_Y_MIN] || datumIndex === indexData[aggIndex + AGGREGATION_INDEX_Y_MAX]) {
|
|
return aggIndex;
|
|
}
|
|
return -1;
|
|
}
|
|
function buildIndicesFromAggregation(xValues, d0, d1, indexData, maxRange, xNeedsValueOf, xValuesLength, reuseIndices, reuseMetaIndices) {
|
|
let indicesCount = 0;
|
|
let metaIndicesCount = 0;
|
|
let currentGroup = -1;
|
|
for (let datumIndex = 0; datumIndex < xValuesLength; datumIndex++) {
|
|
const group = aggregationIndexType(xValues, d0, d1, indexData, maxRange, datumIndex, xNeedsValueOf);
|
|
if (group === -1)
|
|
continue;
|
|
indicesCount++;
|
|
if (group !== currentGroup) {
|
|
metaIndicesCount++;
|
|
currentGroup = group;
|
|
}
|
|
}
|
|
metaIndicesCount++;
|
|
const indices = reuseIndices?.length === indicesCount ? reuseIndices : new Uint32Array(indicesCount);
|
|
const metaIndices = reuseMetaIndices?.length === metaIndicesCount ? reuseMetaIndices : new Uint32Array(metaIndicesCount);
|
|
let indicesIdx = 0;
|
|
let metaIndicesIdx = 0;
|
|
currentGroup = -1;
|
|
for (let datumIndex = 0; datumIndex < xValuesLength; datumIndex++) {
|
|
const group = aggregationIndexType(xValues, d0, d1, indexData, maxRange, datumIndex, xNeedsValueOf);
|
|
if (group === -1)
|
|
continue;
|
|
if (group !== currentGroup) {
|
|
metaIndices[metaIndicesIdx++] = indicesIdx;
|
|
currentGroup = group;
|
|
}
|
|
indices[indicesIdx++] = datumIndex;
|
|
}
|
|
metaIndices[metaIndicesIdx] = indicesCount - 1;
|
|
return { indices, metaIndices };
|
|
}
|
|
function computeAreaAggregation(domain, xValues, yValues, options) {
|
|
const xValuesLength = xValues.length;
|
|
if (xValuesLength < AGGREGATION_THRESHOLD)
|
|
return;
|
|
const [d0, d1] = domain;
|
|
const { xNeedsValueOf, yNeedsValueOf, existingFilters } = options;
|
|
let maxRange = aggregationRangeFittingPoints(xValues, d0, d1, { xNeedsValueOf });
|
|
const existingFilter = existingFilters?.find((f) => f.maxRange === maxRange);
|
|
let { indexData, valueData } = createAggregationIndices(xValues, yValues, yValues, d0, d1, maxRange, {
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
reuseIndexData: existingFilter?.indexData,
|
|
reuseValueData: existingFilter?.valueData
|
|
});
|
|
let { indices, metaIndices } = buildIndicesFromAggregation(
|
|
xValues,
|
|
d0,
|
|
d1,
|
|
indexData,
|
|
maxRange,
|
|
xNeedsValueOf,
|
|
xValuesLength,
|
|
existingFilter?.indices,
|
|
existingFilter?.metaIndices
|
|
);
|
|
const filters = [{ maxRange, metaIndices, indices, indexData, valueData }];
|
|
while (indices.length > MAX_POINTS && maxRange > AGGREGATION_MIN_RANGE) {
|
|
const currentMaxRange = maxRange;
|
|
const nextMaxRange = Math.trunc(currentMaxRange / 2);
|
|
const nextExistingFilter = existingFilters?.find((f) => f.maxRange === nextMaxRange);
|
|
const compacted = compactAggregationIndices(indexData, valueData, currentMaxRange, {
|
|
reuseIndexData: nextExistingFilter?.indexData,
|
|
reuseValueData: nextExistingFilter?.valueData
|
|
});
|
|
maxRange = compacted.maxRange;
|
|
indexData = compacted.indexData;
|
|
valueData = compacted.valueData;
|
|
const previousIndices = indices;
|
|
let indicesCount = 0;
|
|
let metaIndicesCount = 0;
|
|
let currentGroup = -1;
|
|
for (const datumIndex of previousIndices) {
|
|
const group = aggregationIndexType(xValues, d0, d1, indexData, maxRange, datumIndex, xNeedsValueOf);
|
|
if (group === -1)
|
|
continue;
|
|
indicesCount++;
|
|
if (group !== currentGroup) {
|
|
metaIndicesCount++;
|
|
currentGroup = group;
|
|
}
|
|
}
|
|
metaIndicesCount++;
|
|
const newIndices = nextExistingFilter?.indices?.length === indicesCount ? nextExistingFilter.indices : new Uint32Array(indicesCount);
|
|
const newMetaIndices = nextExistingFilter?.metaIndices?.length === metaIndicesCount ? nextExistingFilter.metaIndices : new Uint32Array(metaIndicesCount);
|
|
let indicesIdx = 0;
|
|
let metaIndicesIdx = 0;
|
|
currentGroup = -1;
|
|
for (const datumIndex of previousIndices) {
|
|
const group = aggregationIndexType(xValues, d0, d1, indexData, maxRange, datumIndex, xNeedsValueOf);
|
|
if (group === -1)
|
|
continue;
|
|
if (group !== currentGroup) {
|
|
newMetaIndices[metaIndicesIdx++] = indicesIdx;
|
|
currentGroup = group;
|
|
}
|
|
newIndices[indicesIdx++] = datumIndex;
|
|
}
|
|
newMetaIndices[metaIndicesIdx] = indicesCount - 1;
|
|
indices = newIndices;
|
|
metaIndices = newMetaIndices;
|
|
filters.push({ maxRange, metaIndices, indices, indexData, valueData });
|
|
}
|
|
filters.reverse();
|
|
return filters;
|
|
}
|
|
function computeAreaAggregationPartial(domain, xValues, yValues, options) {
|
|
const xValuesLength = xValues.length;
|
|
if (xValuesLength < AGGREGATION_THRESHOLD)
|
|
return;
|
|
const [d0, d1] = domain;
|
|
const { xNeedsValueOf, yNeedsValueOf, targetRange, existingFilters } = options;
|
|
const finestMaxRange = aggregationRangeFittingPoints(xValues, d0, d1, { xNeedsValueOf });
|
|
const targetMaxRange = Math.min(finestMaxRange, nextPowerOf2(Math.max(targetRange, AGGREGATION_MIN_RANGE)));
|
|
const existingFilter = existingFilters?.find((f) => f.maxRange === targetMaxRange);
|
|
const { indexData, valueData } = createAggregationIndices(xValues, yValues, yValues, d0, d1, targetMaxRange, {
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
reuseIndexData: existingFilter?.indexData,
|
|
reuseValueData: existingFilter?.valueData
|
|
});
|
|
const { indices, metaIndices } = buildIndicesFromAggregation(
|
|
xValues,
|
|
d0,
|
|
d1,
|
|
indexData,
|
|
targetMaxRange,
|
|
xNeedsValueOf,
|
|
xValuesLength,
|
|
existingFilter?.indices,
|
|
existingFilter?.metaIndices
|
|
);
|
|
const immediateLevel = {
|
|
maxRange: targetMaxRange,
|
|
indices,
|
|
metaIndices,
|
|
indexData,
|
|
valueData
|
|
};
|
|
function computeRemaining() {
|
|
const allLevels = computeAreaAggregation([d0, d1], xValues, yValues, {
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
existingFilters
|
|
});
|
|
return allLevels?.filter((level) => level.maxRange !== targetMaxRange) ?? [];
|
|
}
|
|
return { immediate: [immediateLevel], computeRemaining };
|
|
}
|
|
function aggregateAreaData(scale2, xValues, yValues, domainInput, xNeedsValueOf, yNeedsValueOf) {
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeAreaAggregation([d0, d1], xValues, yValues, { xNeedsValueOf, yNeedsValueOf });
|
|
}
|
|
var memoizedAggregateAreaData = simpleMemorize2(aggregateAreaData);
|
|
function aggregateAreaDataFromDataModel(scale2, dataModel, processedData, yKey, series, existingFilters) {
|
|
const xValues = dataModel.resolveKeysById(series, "xValue", processedData);
|
|
const yValues = dataModel.resolveColumnById(series, yKey, processedData);
|
|
const domainInput = dataModel.getDomain(series, "xValue", "key", processedData);
|
|
const xNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "xValue", processedData);
|
|
const yNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, yKey, processedData);
|
|
if (existingFilters) {
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeAreaAggregation([d0, d1], xValues, yValues, {
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
existingFilters
|
|
});
|
|
}
|
|
return memoizedAggregateAreaData(scale2, xValues, yValues, domainInput, xNeedsValueOf, yNeedsValueOf);
|
|
}
|
|
function aggregateAreaDataFromDataModelPartial(scale2, dataModel, processedData, yKey, series, targetRange, existingFilters) {
|
|
const xValues = dataModel.resolveKeysById(series, "xValue", processedData);
|
|
const yValues = dataModel.resolveColumnById(series, yKey, processedData);
|
|
const domainInput = dataModel.getDomain(series, "xValue", "key", processedData);
|
|
const xNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "xValue", processedData);
|
|
const yNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, yKey, processedData);
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeAreaAggregationPartial([d0, d1], xValues, yValues, {
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
targetRange,
|
|
existingFilters
|
|
});
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/areaSeriesProperties.ts
|
|
var AreaSeriesProperties = class extends CartesianSeriesProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.xName = void 0;
|
|
this.fill = "#c16068";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "#874349";
|
|
this.strokeWidth = 2;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.interpolation = new InterpolationProperties();
|
|
this.shadow = new DropShadow();
|
|
this.marker = new SeriesMarker();
|
|
this.label = new Label();
|
|
this.tooltip = makeSeriesTooltip();
|
|
this.connectMissingData = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "xKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "xName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "yKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "yName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "yFilterKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "stackGroup", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "normalizedTo", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "interpolation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "styler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "shadow", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "marker", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "tooltip", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AreaSeriesProperties.prototype, "connectMissingData", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/areaSeries.ts
|
|
var CROSS_FILTER_AREA_FILL_OPACITY_FACTOR = 0.125;
|
|
var CROSS_FILTER_AREA_STROKE_OPACITY_FACTOR = 0.25;
|
|
var AreaSeries = class extends CartesianSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
propertyKeys: DEFAULT_CARTESIAN_DIRECTION_KEYS,
|
|
propertyNames: DEFAULT_CARTESIAN_DIRECTION_NAMES,
|
|
categoryKey: "xValue",
|
|
pathsPerSeries: ["fill", "stroke"],
|
|
pathsZIndexSubOrderOffset: [0, 1e3],
|
|
datumSelectionGarbageCollection: false,
|
|
segmentedDataNodes: false,
|
|
pickModes: [2 /* AXIS_ALIGNED */, 0 /* EXACT_SHAPE_MATCH */],
|
|
animationResetFns: {
|
|
path: buildResetPathFn({ getVisible: () => this.visible, getOpacity: () => this.getOpacity() }),
|
|
label: resetLabelFn,
|
|
datum: (node, datum) => ({ ...resetMarkerFn(node), ...resetMarkerPositionFn(node, datum) })
|
|
},
|
|
clipFocusBox: false
|
|
});
|
|
this.properties = new AreaSeriesProperties();
|
|
this.connectsToYAxis = true;
|
|
this.aggregationManager = new AggregationManager();
|
|
this.backgroundGroup = new Group({
|
|
name: `${this.id}-background`,
|
|
zIndex: 0 /* BACKGROUND */
|
|
});
|
|
this._isStacked = void 0;
|
|
this.fillSpans = [];
|
|
this.phantomSpans = [];
|
|
this.strokeSpans = [];
|
|
}
|
|
get pickModeAxis() {
|
|
return "main";
|
|
}
|
|
renderToOffscreenCanvas() {
|
|
const hasMarkers = (this.contextNodeData?.nodeData?.length ?? 0) > 0;
|
|
return super.renderToOffscreenCanvas() || hasMarkers && this.getDrawingMode(false) === "cutout" || this.contextNodeData != null && (this.contextNodeData.fillData.spans.length > RENDER_TO_OFFSCREEN_CANVAS_THRESHOLD || this.contextNodeData.strokeData.spans.length > RENDER_TO_OFFSCREEN_CANVAS_THRESHOLD);
|
|
}
|
|
attachSeries(seriesContentNode, seriesNode, annotationNode) {
|
|
super.attachSeries(seriesContentNode, seriesNode, annotationNode);
|
|
seriesContentNode.appendChild(this.backgroundGroup);
|
|
}
|
|
detachSeries(seriesContentNode, seriesNode, annotationNode) {
|
|
super.detachSeries(seriesContentNode, seriesNode, annotationNode);
|
|
this.backgroundGroup.remove();
|
|
}
|
|
attachPaths([fill, stroke3]) {
|
|
this.backgroundGroup.appendChild(fill);
|
|
this.contentGroup.appendChild(stroke3);
|
|
stroke3.zIndex = -1;
|
|
}
|
|
detachPaths([fill, stroke3]) {
|
|
fill.remove();
|
|
stroke3.remove();
|
|
}
|
|
isStacked() {
|
|
const stackCount = this.seriesGrouping?.stackCount ?? 1;
|
|
return stackCount > 1;
|
|
}
|
|
isNormalized() {
|
|
return this.properties.normalizedTo != null;
|
|
}
|
|
setSeriesIndex(index) {
|
|
const isStacked = this.isStacked();
|
|
const isStackedChanged = isStacked === this._isStacked;
|
|
this._isStacked = isStackedChanged;
|
|
return super.setSeriesIndex(index, isStackedChanged);
|
|
}
|
|
setZIndex(zIndex) {
|
|
super.setZIndex(zIndex);
|
|
if (this.isStacked()) {
|
|
this.backgroundGroup.zIndex = [0 /* BACKGROUND */, zIndex];
|
|
this.contentGroup.zIndex = [1 /* ANY_CONTENT */, zIndex, 0 /* FOREGROUND */];
|
|
} else {
|
|
this.backgroundGroup.zIndex = [1 /* ANY_CONTENT */, zIndex, 0 /* FOREGROUND */, 0];
|
|
this.contentGroup.zIndex = [1 /* ANY_CONTENT */, zIndex, 0 /* FOREGROUND */, 1];
|
|
}
|
|
}
|
|
async processData(dataController) {
|
|
if (this.data == null)
|
|
return;
|
|
const { data, visible, seriesGrouping: { groupIndex = this.id, stackCount = 1 } = {} } = this;
|
|
const { xKey, yKey, yFilterKey, connectMissingData, normalizedTo } = this.properties;
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
const xScale = this.axes["x" /* X */]?.scale;
|
|
const yScale = this.axes["y" /* Y */]?.scale;
|
|
const { xScaleType, yScaleType } = this.getScaleInformation({ xScale, yScale });
|
|
const stacked = stackCount > 1 || normalizedTo != null;
|
|
const idMap = {
|
|
value: `area-stack-${groupIndex}-yValue`,
|
|
marker: `area-stack-${groupIndex}-yValues-marker`
|
|
};
|
|
const common = { invalidValue: null };
|
|
if ((isDefined(normalizedTo) || connectMissingData) && stackCount > 1) {
|
|
common.invalidValue = 0;
|
|
}
|
|
if (!visible) {
|
|
common.forceValue = 0;
|
|
}
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
const props = [
|
|
keyProperty(xKey, xScaleType, { id: "xValue", allowNullKey }),
|
|
valueProperty(yKey, yScaleType, { id: `yValueRaw`, ...common }),
|
|
...yFilterKey == null ? [] : [valueProperty(yFilterKey, yScaleType, { id: "yFilterRaw" })]
|
|
];
|
|
if (stacked) {
|
|
props.push(
|
|
...groupAccumulativeValueProperty(
|
|
yKey,
|
|
"normal",
|
|
{ id: `yValueCumulative`, ...common, groupId: idMap.marker },
|
|
yScaleType
|
|
)
|
|
);
|
|
}
|
|
if (isDefined(normalizedTo)) {
|
|
props.push(
|
|
valueProperty(yKey, yScaleType, { id: `yValue`, ...common, groupId: idMap.value }),
|
|
normaliseGroupTo(Object.values(idMap), normalizedTo)
|
|
);
|
|
}
|
|
if (animationEnabled) {
|
|
props.push(animationValidation());
|
|
}
|
|
const { dataModel, processedData } = await this.requestDataModel(dataController, data, {
|
|
props,
|
|
groupByKeys: stacked,
|
|
groupByData: !stacked
|
|
});
|
|
this.aggregateData(dataModel, processedData);
|
|
this.animationState.transition("updateData");
|
|
}
|
|
xCoordinateRange(xValue, pixelSize) {
|
|
const { marker } = this.properties;
|
|
const x = this.axes["x" /* X */].scale.convert(xValue);
|
|
const r = marker.enabled ? 0.5 * marker.size * pixelSize : 0;
|
|
return [x - r, x + r];
|
|
}
|
|
yCoordinateRange(yValues, pixelSize) {
|
|
const { marker } = this.properties;
|
|
const y = this.axes["y" /* Y */].scale.convert(yValues[0]);
|
|
const r = marker.enabled ? 0.5 * marker.size * pixelSize : 0;
|
|
return [y - r, y + r];
|
|
}
|
|
yValueKey() {
|
|
return this.isNormalized() ? "yValue" : "yValueRaw";
|
|
}
|
|
yCumulativeKey(processData) {
|
|
return processData.type === "grouped" ? "yValueCumulative" : this.yValueKey();
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { dataModel, processedData, axes } = this;
|
|
if (!dataModel || !processedData)
|
|
return { domain: [] };
|
|
const yAxis = axes["y" /* Y */];
|
|
if (direction === "x" /* X */) {
|
|
const keyDef = dataModel.resolveProcessedDataDefById(this, `xValue`);
|
|
const keys = dataModel.getDomain(this, `xValue`, "key", processedData);
|
|
if (keyDef?.def.type === "key" && keyDef.def.valueType === "category") {
|
|
return keys;
|
|
}
|
|
return { domain: fixNumericExtent(extent(keys.domain)) };
|
|
}
|
|
const yExtent = this.domainForClippedRange(
|
|
"y" /* Y */,
|
|
[this.yCumulativeKey(processedData)],
|
|
"xValue"
|
|
);
|
|
if (yAxis instanceof NumberAxis && !(yAxis instanceof LogAxis)) {
|
|
const fixedYExtent = Number.isFinite(yExtent[1] - yExtent[0]) ? [Math.min(yExtent[0], 0), Math.max(yExtent[1], 0)] : [];
|
|
return { domain: fixNumericExtent(fixedYExtent) };
|
|
} else {
|
|
return { domain: fixNumericExtent(yExtent) };
|
|
}
|
|
}
|
|
getSeriesRange(_direction, visibleRange) {
|
|
const [y0, y1] = this.domainForVisibleRange(
|
|
"y" /* Y */,
|
|
[this.yCumulativeKey(this.processedData)],
|
|
"xValue",
|
|
visibleRange
|
|
);
|
|
return [Math.min(y0, 0), Math.max(y1, 0)];
|
|
}
|
|
getZoomRangeFittingItems(xVisibleRange, yVisibleRange, minVisibleItems) {
|
|
return this.zoomFittingVisibleItems(
|
|
"xValue",
|
|
[this.yCumulativeKey(this.processedData)],
|
|
xVisibleRange,
|
|
yVisibleRange,
|
|
minVisibleItems
|
|
);
|
|
}
|
|
getVisibleItems(xVisibleRange, yVisibleRange, minVisibleItems) {
|
|
return this.countVisibleItems(
|
|
"xValue",
|
|
[this.yCumulativeKey(this.processedData)],
|
|
xVisibleRange,
|
|
yVisibleRange,
|
|
minVisibleItems
|
|
);
|
|
}
|
|
aggregateData(dataModel, processedData) {
|
|
this.aggregationManager.markStale(processedData.input.count);
|
|
if (processedDataIsAnimatable(processedData))
|
|
return;
|
|
const xAxis = this.axes["x" /* X */];
|
|
if (xAxis == null)
|
|
return;
|
|
const targetRange = this.estimateTargetRange();
|
|
this.aggregationManager.aggregate({
|
|
computePartial: (existingFilters) => aggregateAreaDataFromDataModelPartial(
|
|
xAxis.scale.type,
|
|
dataModel,
|
|
processedData,
|
|
this.yCumulativeKey(processedData),
|
|
this,
|
|
targetRange,
|
|
existingFilters
|
|
),
|
|
computeFull: (existingFilters) => aggregateAreaDataFromDataModel(
|
|
xAxis.scale.type,
|
|
dataModel,
|
|
processedData,
|
|
this.yCumulativeKey(processedData),
|
|
this,
|
|
existingFilters
|
|
),
|
|
targetRange
|
|
});
|
|
const filters = this.aggregationManager.filters;
|
|
if (filters && filters.length > 0) {
|
|
debugMetrics_exports.record(
|
|
`${this.type}:aggregation`,
|
|
filters.map((f) => f.maxRange)
|
|
);
|
|
}
|
|
}
|
|
estimateTargetRange() {
|
|
const xAxis = this.axes["x" /* X */];
|
|
if (xAxis?.scale?.range) {
|
|
const [r0, r1] = xAxis.scale.range;
|
|
return Math.abs(r1 - r0);
|
|
}
|
|
return this.ctx.scene?.canvas?.width ?? 800;
|
|
}
|
|
stackAggregatedData(aggregation) {
|
|
const { indices, metaIndices } = aggregation;
|
|
const { visible, axes, dataModel, processedData, seriesBelowStackContext } = this;
|
|
const xAxis = axes["x" /* X */];
|
|
const yAxis = axes["y" /* Y */];
|
|
if (!visible) {
|
|
this.phantomSpans = [];
|
|
this.fillSpans = [];
|
|
this.strokeSpans = [];
|
|
return seriesBelowStackContext;
|
|
}
|
|
if (xAxis == null || yAxis == null || dataModel == null || processedData == null)
|
|
return;
|
|
const { scale: xScale } = xAxis;
|
|
const { scale: yScale } = yAxis;
|
|
const xOffset = (xScale.bandwidth ?? 0) / 2;
|
|
const connectMissingData = !this.isStacked() && this.properties.connectMissingData;
|
|
const invalidData = processedData.invalidData?.get(this.id);
|
|
const xValues = dataModel.resolveKeysById(this, "xValue", processedData);
|
|
const yValues = dataModel.resolveColumnById(this, this.yCumulativeKey(processedData), processedData);
|
|
let [m0, m1] = visibleRangeIndices(1, metaIndices.length - 1, xAxis.range, (metaIndex) => {
|
|
const startIndex = metaIndices[metaIndex];
|
|
const endIndex = metaIndices[metaIndex + 1];
|
|
const startDatumIndex = indices[startIndex];
|
|
const endDatumIndex = indices[endIndex];
|
|
const xValue0 = xValues[startDatumIndex];
|
|
const xValue1 = xValues[endDatumIndex];
|
|
const { 0: x0 } = this.xCoordinateRange(xValue0, 0);
|
|
const { 1: x1 } = this.xCoordinateRange(xValue1, 0);
|
|
return [x0, x1];
|
|
});
|
|
m0 = Math.max(m0 - 1, 0);
|
|
m1 = Math.min(m1 + 1, metaIndices.length - 1);
|
|
let phantomSpans = [];
|
|
if (seriesBelowStackContext?.fillSpans) {
|
|
phantomSpans = seriesBelowStackContext?.fillSpans;
|
|
} else {
|
|
for (let metaIndex = m0; metaIndex < m1; metaIndex += 1) {
|
|
const startIndex = metaIndices[metaIndex];
|
|
const endIndex = metaIndices[metaIndex + 1];
|
|
const startDatumIndex = indices[startIndex];
|
|
const endDatumIndex = indices[endIndex];
|
|
const xValue0 = xValues[startDatumIndex];
|
|
const xValue1 = xValues[endDatumIndex];
|
|
const span = {
|
|
type: "linear",
|
|
moveTo: false,
|
|
x0: xScale.convert(xValue0) + xOffset,
|
|
y0: yScale.convert(0),
|
|
x1: xScale.convert(xValue1) + xOffset,
|
|
y1: yScale.convert(0)
|
|
};
|
|
phantomSpans.push({
|
|
span,
|
|
xValue0,
|
|
xValue1,
|
|
yValue0: 0,
|
|
yValue1: 0
|
|
});
|
|
}
|
|
}
|
|
this.phantomSpans = phantomSpans;
|
|
const fillSpans = [];
|
|
const strokeSpans = [];
|
|
let phantomIndex = 0;
|
|
for (let metaIndex = m0; metaIndex < m1; metaIndex += 1) {
|
|
const startIndex = metaIndices[metaIndex];
|
|
const endIndex = metaIndices[metaIndex + 1];
|
|
const startDatumIndex = indices[startIndex];
|
|
const endDatumIndex = indices[endIndex];
|
|
const spanInvalid = !connectMissingData && this.hasInvalidDatumsInRange(invalidData, yValues, startDatumIndex, endDatumIndex);
|
|
const phantomSpanDatum = phantomSpans[phantomIndex++];
|
|
if (spanInvalid) {
|
|
fillSpans.push(phantomSpanDatum);
|
|
strokeSpans.push(phantomSpanDatum);
|
|
continue;
|
|
}
|
|
const bucketPoints = [];
|
|
for (let i = startIndex; i <= endIndex; i++) {
|
|
const datumIndex = indices[i];
|
|
if (invalidData?.[datumIndex])
|
|
continue;
|
|
const yValue = yValues[datumIndex];
|
|
if (!Number.isFinite(yValue))
|
|
continue;
|
|
const xDatum = xValues[datumIndex];
|
|
bucketPoints.push({
|
|
point: {
|
|
x: xScale.convert(xDatum) + xOffset,
|
|
y: yScale.convert(yValue)
|
|
},
|
|
xDatum,
|
|
yDatum: yValue
|
|
});
|
|
}
|
|
if (bucketPoints.length < 2) {
|
|
fillSpans.push(phantomSpanDatum);
|
|
strokeSpans.push(phantomSpanDatum);
|
|
continue;
|
|
}
|
|
const startPoint = bucketPoints[0];
|
|
const endPoint = bucketPoints.at(-1);
|
|
const midPoints = bucketPoints.slice(1, -1).map((p) => p.point);
|
|
const span = {
|
|
type: "multi-line",
|
|
moveTo: false,
|
|
x0: startPoint.point.x,
|
|
y0: startPoint.point.y,
|
|
x1: endPoint.point.x,
|
|
y1: endPoint.point.y,
|
|
midPoints
|
|
};
|
|
const spanDatum = {
|
|
span,
|
|
xValue0: startPoint.xDatum,
|
|
xValue1: endPoint.xDatum,
|
|
yValue0: startPoint.yDatum,
|
|
yValue1: endPoint.yDatum
|
|
};
|
|
fillSpans.push(spanDatum);
|
|
strokeSpans.push(spanDatum);
|
|
}
|
|
this.fillSpans = fillSpans;
|
|
this.strokeSpans = strokeSpans;
|
|
return {
|
|
stack: [],
|
|
fillSpans,
|
|
strokeSpans
|
|
};
|
|
}
|
|
hasInvalidDatumsInRange(invalidData, yValues, startIndex, endIndex) {
|
|
const rangeStart = Math.min(startIndex, endIndex);
|
|
const rangeEnd = Math.max(startIndex, endIndex);
|
|
for (let datumIndex = rangeStart; datumIndex <= rangeEnd; datumIndex++) {
|
|
if (invalidData?.[datumIndex]) {
|
|
return true;
|
|
}
|
|
const yValue = yValues[datumIndex];
|
|
if (!Number.isFinite(yValue)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
stackYValueData() {
|
|
const { visible, axes, dataModel, processedData, seriesBelowStackContext, properties } = this;
|
|
const xAxis = axes["x" /* X */];
|
|
const yAxis = axes["y" /* Y */];
|
|
if (xAxis == null || yAxis == null || dataModel == null || processedData == null)
|
|
return;
|
|
const { interpolation } = properties;
|
|
const { scale: xScale } = xAxis;
|
|
const { scale: yScale } = yAxis;
|
|
const xOffset = (xScale.bandwidth ?? 0) / 2;
|
|
let xValues = dataModel.resolveKeysById(this, "xValue", processedData);
|
|
let yValues = dataModel.resolveColumnById(this, this.yValueKey(), processedData);
|
|
const connectMissingData = !this.isStacked() && this.properties.connectMissingData;
|
|
const invalidKeys = processedData.invalidKeys?.get(this.id);
|
|
const invalidData = connectMissingData ? processedData.invalidData?.get(this.id) : void 0;
|
|
const indexFilter = invalidData ?? invalidKeys;
|
|
if (indexFilter != null) {
|
|
xValues = xValues.filter((_, datumIndex) => indexFilter[datumIndex] === false);
|
|
yValues = yValues.filter((_, datumIndex) => indexFilter[datumIndex] === false);
|
|
}
|
|
let [startIndex, endIndex] = visibleRangeIndices(
|
|
1,
|
|
xValues.length,
|
|
xAxis.range,
|
|
(datumIndex) => this.xCoordinateRange(xValues[datumIndex], 0)
|
|
);
|
|
startIndex = Math.max(startIndex - 2, 0);
|
|
endIndex = Math.min(endIndex + 2, xValues.length);
|
|
let phantomSpans;
|
|
if (seriesBelowStackContext?.fillSpans) {
|
|
phantomSpans = seriesBelowStackContext?.fillSpans;
|
|
} else {
|
|
const phantomSpanPoints = [];
|
|
for (let datumIndex = startIndex; datumIndex < endIndex; datumIndex += 1) {
|
|
const xDatum = xValues[datumIndex];
|
|
phantomSpanPoints.push({
|
|
point: {
|
|
x: xScale.convert(xDatum) + xOffset,
|
|
y: yScale.convert(0)
|
|
},
|
|
xDatum,
|
|
yDatum: 0
|
|
});
|
|
}
|
|
phantomSpans = interpolatePoints(phantomSpanPoints, { type: "linear" });
|
|
}
|
|
this.phantomSpans = phantomSpans;
|
|
if (!visible) {
|
|
this.fillSpans = phantomSpans;
|
|
this.strokeSpans = [];
|
|
return seriesBelowStackContext;
|
|
}
|
|
let bottomStack = seriesBelowStackContext?.stack;
|
|
if (bottomStack == null) {
|
|
bottomStack = [];
|
|
for (let datumIndex = startIndex; datumIndex < endIndex - 1; datumIndex += 1) {
|
|
bottomStack.push({ leading: 0, trailing: 0, dataValid: true, breakBefore: false });
|
|
}
|
|
}
|
|
const topStack = bottomStack.slice();
|
|
let trackingValidData = false;
|
|
for (let stackIndex = 0; stackIndex < topStack.length; stackIndex += 1) {
|
|
const leadingIndex = startIndex + stackIndex;
|
|
const trailingIndex = startIndex + stackIndex + 1;
|
|
let { leading, trailing, breakBefore } = bottomStack[stackIndex];
|
|
const leadingValue = yValues[leadingIndex];
|
|
const trailingValue = yValues[trailingIndex];
|
|
const missingLeading = !Number.isFinite(leadingValue);
|
|
const missingTrailing = !Number.isFinite(trailingValue);
|
|
const dataValid = !missingLeading && !missingTrailing;
|
|
if (dataValid) {
|
|
leading += leadingValue;
|
|
trailing += trailingValue;
|
|
}
|
|
if (stackIndex !== 0 && dataValid !== trackingValidData) {
|
|
breakBefore = true;
|
|
}
|
|
trackingValidData = dataValid;
|
|
topStack[stackIndex] = { leading, trailing, dataValid, breakBefore };
|
|
}
|
|
const fillSpans = [];
|
|
const strokeSpans = [];
|
|
const topSpanPoints = [];
|
|
for (let stackIndex = 0; stackIndex < topStack.length; stackIndex += 1) {
|
|
const { leading, dataValid, breakBefore } = topStack[stackIndex];
|
|
const leadingIndex = startIndex + stackIndex;
|
|
if (breakBefore) {
|
|
if (topSpanPoints.length !== 0) {
|
|
const previousStack = topStack[stackIndex - 1];
|
|
const previousPoint = {
|
|
point: {
|
|
x: xScale.convert(xValues[leadingIndex]) + xOffset,
|
|
y: yScale.convert(previousStack.trailing)
|
|
},
|
|
xDatum: xValues[leadingIndex],
|
|
yDatum: previousStack.trailing
|
|
};
|
|
topSpanPoints.push(previousPoint);
|
|
const spans = interpolatePoints(topSpanPoints, interpolation);
|
|
fillSpans.push(...spans);
|
|
strokeSpans.push(...spans);
|
|
}
|
|
topSpanPoints.length = 0;
|
|
}
|
|
if (dataValid) {
|
|
const leadingPoint = {
|
|
point: {
|
|
x: xScale.convert(xValues[leadingIndex]) + xOffset,
|
|
y: yScale.convert(leading)
|
|
},
|
|
xDatum: xValues[leadingIndex],
|
|
yDatum: leading
|
|
};
|
|
topSpanPoints.push(leadingPoint);
|
|
} else {
|
|
fillSpans.push(phantomSpans[stackIndex]);
|
|
}
|
|
}
|
|
if (topSpanPoints.length !== 0) {
|
|
const previousStack = topStack.at(-1);
|
|
const trailingIndex = startIndex + topStack.length;
|
|
const trailingPoint = {
|
|
point: {
|
|
x: xScale.convert(xValues[trailingIndex]) + xOffset,
|
|
y: yScale.convert(previousStack.trailing)
|
|
},
|
|
xDatum: xValues[trailingIndex],
|
|
yDatum: previousStack.trailing
|
|
};
|
|
topSpanPoints.push(trailingPoint);
|
|
const spans = interpolatePoints(topSpanPoints, interpolation);
|
|
fillSpans.push(...spans);
|
|
strokeSpans.push(...spans);
|
|
topSpanPoints.length = 0;
|
|
}
|
|
this.fillSpans = fillSpans;
|
|
this.strokeSpans = strokeSpans;
|
|
return {
|
|
stack: topStack,
|
|
fillSpans,
|
|
strokeSpans
|
|
};
|
|
}
|
|
createStackContext() {
|
|
const xAxis = this.axes["x" /* X */];
|
|
if (xAxis == null)
|
|
return;
|
|
const { scale: xScale } = xAxis;
|
|
const [r0, r1] = xScale.range;
|
|
const range3 = Math.abs(r1 - r0);
|
|
this.aggregationManager.ensureLevelForRange(range3);
|
|
const dataAggregationFilter = this.aggregationManager.getFilterForRange(range3);
|
|
if (dataAggregationFilter) {
|
|
return this.stackAggregatedData(dataAggregationFilter);
|
|
} else {
|
|
return this.stackYValueData();
|
|
}
|
|
}
|
|
/**
|
|
* Creates the context object with cached lookups for createNodeData().
|
|
* All expensive operations (data resolution, scale lookups) are performed once here.
|
|
*/
|
|
createNodeDatumContext(xAxis, yAxis) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData)
|
|
return void 0;
|
|
const {
|
|
xKey,
|
|
xName,
|
|
yFilterKey,
|
|
yKey,
|
|
yName,
|
|
legendItemName,
|
|
marker,
|
|
label,
|
|
fill: seriesFill,
|
|
stroke: seriesStroke,
|
|
normalizedTo
|
|
} = this.properties;
|
|
const xScale = xAxis.scale;
|
|
const yScale = yAxis.scale;
|
|
const { isContinuousY } = this.getScaleInformation({ xScale, yScale });
|
|
const stacked = processedData.type === "grouped";
|
|
const [r0, r1] = xScale.range;
|
|
const range3 = Math.abs(r1 - r0);
|
|
this.aggregationManager.ensureLevelForRange(range3);
|
|
const dataAggregationFilter = this.aggregationManager.getFilterForRange(range3);
|
|
const existingNodeData = this.contextNodeData?.nodeData;
|
|
const canIncrementallyUpdate = existingNodeData != null && this.canIncrementallyUpdateNodes(dataAggregationFilter != null);
|
|
return {
|
|
// Axes (from template method parameters)
|
|
xAxis,
|
|
yAxis,
|
|
// Data arrays (resolved once)
|
|
rawData: processedData.dataSources.get(this.id)?.data ?? [],
|
|
xValues: dataModel.resolveKeysById(this, "xValue", processedData),
|
|
yRawValues: dataModel.resolveColumnById(this, "yValueRaw", processedData),
|
|
yCumulativeValues: stacked ? dataModel.resolveColumnById(this, "yValueCumulative", processedData) : dataModel.resolveColumnById(this, "yValueRaw", processedData),
|
|
yFilterValues: yFilterKey == null ? void 0 : dataModel.resolveColumnById(this, "yFilterRaw", processedData),
|
|
invalidData: processedData.invalidData?.get(this.id),
|
|
// Scales (cached)
|
|
xScale,
|
|
yScale,
|
|
xOffset: (xScale.bandwidth ?? 0) / 2,
|
|
yOffset: 0,
|
|
// Aggregation
|
|
indices: dataAggregationFilter?.indices,
|
|
// Pre-computed flags
|
|
isContinuousY,
|
|
labelsEnabled: label.enabled,
|
|
normalizedTo,
|
|
canIncrementallyUpdate,
|
|
animationEnabled: !this.ctx.animationManager.isSkipped(),
|
|
// Property caches
|
|
xKey,
|
|
yKey,
|
|
xName,
|
|
yName,
|
|
legendItemName,
|
|
markerSize: marker.size,
|
|
markerFill: marker.fill ?? seriesFill,
|
|
markerStroke: marker.stroke ?? seriesStroke,
|
|
markerStrokeWidth: marker.strokeWidth ?? this.properties.strokeWidth,
|
|
yDomain: this.getSeriesDomain("y" /* Y */).domain,
|
|
// Mutable state (nodes instead of markerData to match base interface)
|
|
nodes: canIncrementallyUpdate ? existingNodeData : [],
|
|
labelData: [],
|
|
nodeIndex: 0,
|
|
crossFiltering: false
|
|
};
|
|
}
|
|
/**
|
|
* Computes the marker coordinate for a datum.
|
|
* Uses cached context values to avoid repeated lookups.
|
|
*/
|
|
computeMarkerCoordinate(ctx, scratch) {
|
|
let currY;
|
|
if (isDefined(ctx.normalizedTo) ? ctx.isContinuousY && isContinuous(scratch.yDatum) : !Number.isNaN(scratch.yDatum)) {
|
|
currY = scratch.yCumulative;
|
|
}
|
|
scratch.x = ctx.xScale.convert(scratch.xDatum) + ctx.xOffset;
|
|
scratch.y = ctx.yScale.convert(currY);
|
|
if (!Number.isFinite(scratch.x)) {
|
|
scratch.validPoint = false;
|
|
}
|
|
}
|
|
/**
|
|
* Processes a single datum and updates the context's marker/label data.
|
|
* Uses scratch object to avoid allocations in tight loops.
|
|
*/
|
|
handleDatum(ctx, scratch, datumIndex) {
|
|
scratch.xDatum = ctx.xValues[datumIndex];
|
|
if (scratch.xDatum === void 0 && !this.properties.allowNullKeys)
|
|
return;
|
|
scratch.datum = ctx.rawData[datumIndex];
|
|
scratch.yDatum = ctx.yRawValues[datumIndex];
|
|
scratch.yCumulative = +ctx.yCumulativeValues[datumIndex];
|
|
scratch.validPoint = Number.isFinite(scratch.yDatum) && ctx.invalidData?.[datumIndex] !== true;
|
|
this.computeMarkerCoordinate(ctx, scratch);
|
|
scratch.selected = ctx.yFilterValues == null ? void 0 : ctx.yFilterValues[datumIndex] === scratch.yDatum;
|
|
if (scratch.selected === false) {
|
|
ctx.crossFiltering = true;
|
|
}
|
|
if (scratch.validPoint) {
|
|
const canReuseNode = ctx.canIncrementallyUpdate && ctx.nodeIndex < ctx.nodes.length;
|
|
if (canReuseNode) {
|
|
const existingNode = ctx.nodes[ctx.nodeIndex];
|
|
existingNode.datum = scratch.datum;
|
|
existingNode.datumIndex = datumIndex;
|
|
existingNode.midPoint = { x: scratch.x, y: scratch.y };
|
|
existingNode.cumulativeValue = scratch.yCumulative;
|
|
existingNode.yValue = scratch.yDatum;
|
|
existingNode.xValue = scratch.xDatum;
|
|
existingNode.point = { x: scratch.x, y: scratch.y, size: ctx.markerSize };
|
|
existingNode.selected = scratch.selected;
|
|
} else {
|
|
ctx.nodes.push({
|
|
series: this,
|
|
datum: scratch.datum,
|
|
datumIndex,
|
|
midPoint: { x: scratch.x, y: scratch.y },
|
|
cumulativeValue: scratch.yCumulative,
|
|
yValue: scratch.yDatum,
|
|
xValue: scratch.xDatum,
|
|
yKey: ctx.yKey,
|
|
xKey: ctx.xKey,
|
|
point: { x: scratch.x, y: scratch.y, size: ctx.markerSize },
|
|
fill: ctx.markerFill,
|
|
stroke: ctx.markerStroke,
|
|
strokeWidth: ctx.markerStrokeWidth,
|
|
selected: scratch.selected
|
|
});
|
|
}
|
|
ctx.nodeIndex++;
|
|
}
|
|
if (ctx.labelsEnabled && scratch.validPoint) {
|
|
const labelText = this.getLabelText(
|
|
scratch.yDatum,
|
|
scratch.datum,
|
|
ctx.yKey,
|
|
"y",
|
|
ctx.yDomain,
|
|
this.properties.label,
|
|
{
|
|
value: scratch.yDatum,
|
|
datum: scratch.datum,
|
|
xKey: ctx.xKey,
|
|
yKey: ctx.yKey,
|
|
xName: ctx.xName,
|
|
yName: ctx.yName,
|
|
legendItemName: ctx.legendItemName
|
|
}
|
|
);
|
|
ctx.labelData.push({
|
|
series: this,
|
|
datum: scratch.datum,
|
|
datumIndex,
|
|
x: scratch.x,
|
|
y: scratch.y,
|
|
labelText
|
|
});
|
|
}
|
|
}
|
|
// ============================================================================
|
|
// Template Method Hooks
|
|
// ============================================================================
|
|
/**
|
|
* Populates the node data array by iterating over visible data.
|
|
*/
|
|
populateNodeData(ctx) {
|
|
const scratch = {
|
|
datum: void 0,
|
|
xDatum: void 0,
|
|
yDatum: void 0,
|
|
yCumulative: 0,
|
|
selected: void 0,
|
|
x: 0,
|
|
y: 0,
|
|
validPoint: false
|
|
};
|
|
let [startIndex, endIndex] = this.visibleRangeIndices("xValue", ctx.xAxis.range, ctx.indices);
|
|
startIndex = Math.max(startIndex - 2, 0);
|
|
endIndex = Math.min(endIndex + 2, ctx.indices?.length ?? ctx.xValues.length);
|
|
if (this.processedData.input.count < 1e3) {
|
|
startIndex = 0;
|
|
endIndex = this.processedData.input.count;
|
|
}
|
|
for (let i = startIndex; i < endIndex; i += 1) {
|
|
const datumIndex = ctx.indices?.[i] ?? i;
|
|
this.handleDatum(ctx, scratch, datumIndex);
|
|
}
|
|
}
|
|
/**
|
|
* Initializes the result context object with default values.
|
|
* Called before populate phase to allow early return for invisible series.
|
|
*
|
|
* Note: We use the actual fillSpans/strokeSpans/phantomSpans because createStackContext()
|
|
* runs BEFORE createNodeData() and populates these instance fields. They are valid
|
|
* even in the early return case when !visible.
|
|
*/
|
|
initializeResult(ctx) {
|
|
const { visibleSameStackCount } = this.ctx.seriesStateManager.getVisiblePeerGroupIndex(this);
|
|
return {
|
|
itemId: ctx.yKey,
|
|
// Use actual spans from createStackContext() - valid even for early return
|
|
fillData: { spans: this.fillSpans, phantomSpans: this.phantomSpans },
|
|
strokeData: { spans: this.strokeSpans },
|
|
labelData: ctx.labelData,
|
|
nodeData: ctx.nodes,
|
|
scales: this.calculateScaling(),
|
|
visible: this.visible,
|
|
stackVisible: visibleSameStackCount > 0,
|
|
crossFiltering: ctx.crossFiltering,
|
|
styles: getMarkerStyles(this, this.properties, this.properties.marker),
|
|
segments: void 0
|
|
};
|
|
}
|
|
/**
|
|
* Assembles the final result with computed fields.
|
|
* Note: fillData/strokeData are already set in initializeResult() from instance fields.
|
|
*/
|
|
assembleResult(ctx, result) {
|
|
result.segments = calculateSegments(
|
|
this.properties.segmentation,
|
|
ctx.xAxis,
|
|
ctx.yAxis,
|
|
this.chart.seriesRect,
|
|
this.ctx.scene,
|
|
false
|
|
);
|
|
return result;
|
|
}
|
|
isPathOrSelectionDirty() {
|
|
return this.properties.marker.isDirty();
|
|
}
|
|
updatePathNodes(opts) {
|
|
const {
|
|
paths: [fillPaths, strokePaths],
|
|
visible,
|
|
animationEnabled
|
|
} = opts;
|
|
const crossFiltering = this.contextNodeData?.crossFiltering === true;
|
|
const segments = this.contextNodeData?.segments;
|
|
const merged = mergeDefaults(this.getHighlightStyle(), this.getStyle());
|
|
const { strokeWidth, stroke: stroke3, strokeOpacity, lineDash, lineDashOffset, fill, fillOpacity, opacity } = merged;
|
|
strokePaths.setProperties({
|
|
segments,
|
|
fill: void 0,
|
|
lineCap: "round",
|
|
lineJoin: "round",
|
|
pointerEvents: 1 /* None */,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity: strokeOpacity * (crossFiltering ? CROSS_FILTER_AREA_STROKE_OPACITY_FACTOR : 1),
|
|
lineDash,
|
|
lineDashOffset,
|
|
opacity,
|
|
visible: visible || animationEnabled
|
|
});
|
|
strokePaths.datum = segments;
|
|
fillPaths.setStyleProperties(
|
|
{
|
|
fill,
|
|
stroke: void 0,
|
|
fillOpacity: fillOpacity * (crossFiltering ? CROSS_FILTER_AREA_FILL_OPACITY_FACTOR : 1)
|
|
},
|
|
this.getShapeFillBBox()
|
|
);
|
|
fillPaths.setProperties({
|
|
segments,
|
|
lineJoin: "round",
|
|
pointerEvents: 1 /* None */,
|
|
fillShadow: this.properties.shadow,
|
|
opacity,
|
|
visible: visible || animationEnabled
|
|
});
|
|
fillPaths.datum = segments;
|
|
updateClipPath(this, strokePaths);
|
|
updateClipPath(this, fillPaths);
|
|
}
|
|
updatePaths(opts) {
|
|
this.updateAreaPaths(opts.paths, opts.contextData);
|
|
}
|
|
updateAreaPaths(paths, contextData) {
|
|
for (const path of paths) {
|
|
path.visible = contextData.visible;
|
|
}
|
|
if (contextData.visible) {
|
|
this.updateFillPath(paths, contextData);
|
|
this.updateStrokePath(paths, contextData);
|
|
} else {
|
|
for (const path of paths) {
|
|
path.path.clear();
|
|
path.markDirty("AreaSeries");
|
|
}
|
|
}
|
|
}
|
|
updateFillPath(paths, contextData) {
|
|
const [fill] = paths;
|
|
fill.path.clear();
|
|
plotAreaPathFill(fill, contextData.fillData);
|
|
fill.markDirty("AreaSeries");
|
|
}
|
|
updateStrokePath(paths, contextData) {
|
|
const { spans } = contextData.strokeData;
|
|
const [, stroke3] = paths;
|
|
stroke3.path.clear();
|
|
plotLinePathStroke(stroke3, spans);
|
|
stroke3.markDirty("AreaSeries");
|
|
}
|
|
updateDatumSelection(opts) {
|
|
const { nodeData, datumSelection } = opts;
|
|
const { contextNodeData, processedData, axes, properties } = this;
|
|
const { marker, styler } = properties;
|
|
const markerStyle = styler ? this.getStyle().marker : void 0;
|
|
const markersEnabled = contextNodeData?.crossFiltering === true || markerEnabled(processedData.input.count, axes["x" /* X */].scale, marker, markerStyle);
|
|
if (marker.isDirty()) {
|
|
datumSelection.clear();
|
|
datumSelection.cleanup();
|
|
}
|
|
const data = markersEnabled ? nodeData : [];
|
|
if (!processedDataIsAnimatable(this.processedData)) {
|
|
return datumSelection.update(data);
|
|
}
|
|
return datumSelection.update(data, void 0, (datum) => createDatumId(datum.xValue));
|
|
}
|
|
updateDatumStyles(opts) {
|
|
const { datumSelection, isHighlight } = opts;
|
|
const { marker } = this.properties;
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
datumSelection.each((node, datum) => {
|
|
if (!datumSelection.isGarbage(node)) {
|
|
const highlightState = this.getHighlightState(highlightedDatum, opts.isHighlight, datum.datumIndex);
|
|
const stylerStyle = this.getStyle(highlightState);
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity } = stylerStyle;
|
|
const params = this.makeItemStylerParams(
|
|
this.dataModel,
|
|
this.processedData,
|
|
datum.datumIndex,
|
|
stylerStyle.marker
|
|
);
|
|
datum.style = this.getMarkerStyle(
|
|
marker,
|
|
datum,
|
|
params,
|
|
{ isHighlight, highlightState },
|
|
stylerStyle.marker,
|
|
{
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity
|
|
}
|
|
);
|
|
}
|
|
});
|
|
}
|
|
updateDatumNodes(opts) {
|
|
const { contextNodeData } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const { datumSelection, isHighlight } = opts;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
const drawingMode = this.getDrawingMode(isHighlight, opts.drawingMode);
|
|
datumSelection.each((node, datum) => {
|
|
const state = this.getHighlightState(highlightedDatum, isHighlight, datum.datumIndex);
|
|
const style2 = datum.style ?? contextNodeData.styles[state];
|
|
this.applyMarkerStyle(style2, node, datum.point, fillBBox, { selected: datum.selected });
|
|
node.drawingMode = this.resolveMarkerDrawingModeForState(drawingMode, style2);
|
|
});
|
|
if (!isHighlight) {
|
|
this.properties.marker.markClean();
|
|
}
|
|
}
|
|
updateLabelSelection(opts) {
|
|
return opts.labelSelection.update(this.isLabelEnabled() ? opts.labelData : []);
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const { isHighlight = false } = opts;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const params = this.makeLabelFormatterParams();
|
|
opts.labelSelection.each((text2, datum) => {
|
|
const style2 = getLabelStyles(this, datum, params, this.properties.label, isHighlight, activeHighlight);
|
|
const { enabled, fontStyle, fontWeight: fontWeight2, fontSize, fontFamily, color: color2 } = style2;
|
|
if (enabled && datum?.labelText) {
|
|
text2.fontStyle = fontStyle;
|
|
text2.fontWeight = fontWeight2;
|
|
text2.fontSize = fontSize;
|
|
text2.fontFamily = fontFamily;
|
|
text2.textAlign = "center";
|
|
text2.textBaseline = "bottom";
|
|
text2.text = datum.labelText;
|
|
text2.x = datum.x;
|
|
text2.y = datum.y - 10;
|
|
text2.fill = color2;
|
|
text2.visible = true;
|
|
text2.fillOpacity = this.getHighlightStyle(isHighlight, datum.datumIndex).opacity ?? 1;
|
|
text2.setBoxing(style2);
|
|
} else {
|
|
text2.visible = false;
|
|
}
|
|
});
|
|
}
|
|
makeStylerParams(highlightStateEnum) {
|
|
const { id: seriesId } = this;
|
|
const { marker, fill, fillOpacity, lineDash, lineDashOffset, stroke: stroke3, strokeOpacity, strokeWidth, xKey, yKey } = this.properties;
|
|
const highlightState = toHighlightString(highlightStateEnum ?? 0 /* None */);
|
|
return {
|
|
marker: {
|
|
fill: marker.fill,
|
|
fillOpacity: marker.fillOpacity,
|
|
size: marker.size,
|
|
shape: marker.shape,
|
|
stroke: marker.stroke,
|
|
strokeOpacity: marker.strokeOpacity,
|
|
strokeWidth: marker.strokeWidth,
|
|
lineDash: marker.lineDash,
|
|
lineDashOffset: marker.lineDashOffset
|
|
},
|
|
highlightState,
|
|
fill,
|
|
fillOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
seriesId,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
xKey,
|
|
yKey
|
|
};
|
|
}
|
|
makeItemStylerParams(dataModel, processedData, datumIndex, style2) {
|
|
const { xKey, yKey } = this.properties;
|
|
const xValue = dataModel.resolveKeysById(this, `xValue`, processedData)[datumIndex];
|
|
const yValue = dataModel.resolveColumnById(this, `yValueRaw`, processedData)[datumIndex];
|
|
const xDomain = dataModel.getDomain(this, `xValue`, "key", processedData).domain;
|
|
const yDomain = dataModel.getDomain(this, this.yCumulativeKey(processedData), "value", processedData).domain;
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
...datumStylerProperties(xValue, yValue, xKey, yKey, xDomain, yDomain),
|
|
xValue,
|
|
yValue,
|
|
...style2,
|
|
fill
|
|
};
|
|
}
|
|
makeLabelFormatterParams() {
|
|
const { xKey, xName, yKey, yName, legendItemName } = this.properties;
|
|
return { xKey, xName, yKey, yName, legendItemName };
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, axes, properties } = this;
|
|
const { xKey, xName, yKey, yName, tooltip, legendItemName } = properties;
|
|
const allowNullKeys = properties.allowNullKeys ?? false;
|
|
const xAxis = axes["x" /* X */];
|
|
const yAxis = axes["y" /* Y */];
|
|
if (!dataModel || !processedData || !xAxis || !yAxis)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data?.[datumIndex];
|
|
const xValue = dataModel.resolveKeysById(this, `xValue`, processedData)[datumIndex];
|
|
const yValue = dataModel.resolveColumnById(this, `yValueRaw`, processedData)[datumIndex];
|
|
if (xValue === void 0 && !allowNullKeys)
|
|
return;
|
|
const stylerStyle = this.getStyle();
|
|
const params = this.makeItemStylerParams(dataModel, processedData, datumIndex, stylerStyle.marker);
|
|
const format = this.getMarkerStyle(
|
|
this.properties.marker,
|
|
{ datumIndex, datum },
|
|
params,
|
|
{ isHighlight: false },
|
|
stylerStyle.marker
|
|
);
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading: this.getAxisValueText(xAxis, "tooltip", xValue, datum, xKey, legendItemName, allowNullKeys),
|
|
symbol: this.legendItemSymbol(),
|
|
data: [
|
|
{
|
|
label: yName,
|
|
fallbackLabel: yKey,
|
|
value: this.getAxisValueText(yAxis, "tooltip", yValue, datum, yKey, legendItemName),
|
|
missing: isTooltipValueMissing(yValue)
|
|
}
|
|
]
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: yName,
|
|
xKey,
|
|
xName,
|
|
yKey,
|
|
yName,
|
|
...format,
|
|
...this.getModuleTooltipParams()
|
|
}
|
|
);
|
|
}
|
|
legendItemSymbol() {
|
|
const { fill, stroke: stroke3, fillOpacity, strokeOpacity, strokeWidth, lineDash, marker } = this.getStyle();
|
|
const useAreaFill = !marker.enabled || marker.fill == null;
|
|
const legendMarkerFill = useAreaFill ? fill : marker.fill;
|
|
const markerStyle = this.getMarkerStyle(
|
|
this.properties.marker,
|
|
{},
|
|
void 0,
|
|
{ isHighlight: false, checkForHighlight: false },
|
|
{
|
|
size: marker.size,
|
|
shape: marker.shape,
|
|
fill: legendMarkerFill,
|
|
fillOpacity: useAreaFill ? fillOpacity : marker.fillOpacity,
|
|
stroke: marker.stroke
|
|
}
|
|
);
|
|
return {
|
|
marker: {
|
|
...markerStyle,
|
|
enabled: marker.enabled || strokeWidth <= 0
|
|
},
|
|
line: {
|
|
enabled: true,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
if (legendType !== "category") {
|
|
return [];
|
|
}
|
|
const {
|
|
id: seriesId,
|
|
ctx: { legendManager },
|
|
visible
|
|
} = this;
|
|
const { yKey: itemId, yName, legendItemName, showInLegend } = this.properties;
|
|
return [
|
|
{
|
|
legendType,
|
|
id: seriesId,
|
|
itemId,
|
|
legendItemName,
|
|
seriesId,
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId, itemId }),
|
|
label: {
|
|
text: legendItemName ?? yName ?? itemId
|
|
},
|
|
symbol: this.legendItemSymbol(),
|
|
hideInLegend: !showInLegend
|
|
}
|
|
];
|
|
}
|
|
resetDatumAnimation(data) {
|
|
resetMarkerSelectionsDirect([data.datumSelection]);
|
|
}
|
|
animateEmptyUpdateReady(animationData) {
|
|
const { datumSelection, labelSelection, contextData, paths } = animationData;
|
|
const { animationManager } = this.ctx;
|
|
this.updateAreaPaths(paths, contextData);
|
|
pathSwipeInAnimation(this, animationManager, ...paths);
|
|
resetMotion([datumSelection], resetMarkerPositionFn);
|
|
markerSwipeScaleInAnimation(
|
|
this,
|
|
animationManager,
|
|
{ ...this.getAnimationDrawingModes(), phase: "initial" },
|
|
datumSelection
|
|
);
|
|
seriesLabelFadeInAnimation(this, "labels", animationManager, labelSelection);
|
|
}
|
|
animateReadyResize(animationData) {
|
|
const { contextData, paths } = animationData;
|
|
this.updateAreaPaths(paths, contextData);
|
|
super.animateReadyResize(animationData);
|
|
}
|
|
animateWaitingUpdateReady(animationData) {
|
|
const { animationManager } = this.ctx;
|
|
const { datumSelection, labelSelection, contextData, paths, previousContextData } = animationData;
|
|
const [fill, stroke3] = paths;
|
|
if (contextData.visible === false && previousContextData?.visible === false)
|
|
return;
|
|
if (fill == null && stroke3 == null)
|
|
return;
|
|
this.resetDatumAnimation(animationData);
|
|
this.resetLabelAnimation(animationData);
|
|
const update = () => {
|
|
this.resetPathAnimation(animationData);
|
|
this.updateAreaPaths(paths, contextData);
|
|
};
|
|
const skip = () => {
|
|
animationManager.skipCurrentBatch();
|
|
update();
|
|
};
|
|
if (contextData == null || previousContextData == null) {
|
|
update();
|
|
markerFadeInAnimation(this, animationManager, "added", this.getAnimationDrawingModes(), datumSelection);
|
|
pathFadeInAnimation(this, "fill_path_properties", animationManager, "add", fill);
|
|
pathFadeInAnimation(this, "stroke_path_properties", animationManager, "add", stroke3);
|
|
seriesLabelFadeInAnimation(this, "labels", animationManager, labelSelection);
|
|
return;
|
|
}
|
|
if (contextData.crossFiltering !== previousContextData.crossFiltering) {
|
|
skip();
|
|
return;
|
|
}
|
|
const fns = prepareAreaPathAnimation(contextData, previousContextData);
|
|
if (fns === void 0) {
|
|
skip();
|
|
return;
|
|
} else if (fns.status === "no-op") {
|
|
return;
|
|
}
|
|
markerFadeInAnimation(this, animationManager, void 0, this.getAnimationDrawingModes(), datumSelection);
|
|
fromToMotion(this.id, "fill_path_properties", animationManager, [fill], fns.fill.pathProperties);
|
|
pathMotion(this.id, "fill_path_update", animationManager, [fill], fns.fill.path);
|
|
fromToMotion(this.id, "stroke_path_properties", animationManager, [stroke3], fns.stroke.pathProperties);
|
|
pathMotion(this.id, "stroke_path_update", animationManager, [stroke3], fns.stroke.path);
|
|
seriesLabelFadeInAnimation(this, "labels", animationManager, labelSelection);
|
|
this.ctx.animationManager.animate({
|
|
id: this.id,
|
|
groupId: "reset_after_animation",
|
|
phase: "trailing",
|
|
from: {},
|
|
to: {},
|
|
onComplete: () => this.updateAreaPaths(paths, contextData)
|
|
});
|
|
}
|
|
isLabelEnabled() {
|
|
return this.properties.label.enabled;
|
|
}
|
|
nodeFactory() {
|
|
return new Marker();
|
|
}
|
|
getStyle(highlightState) {
|
|
const { styler, marker, fill, fillOpacity, lineDash, lineDashOffset, stroke: stroke3, strokeOpacity, strokeWidth } = this.properties;
|
|
const { size, shape, fill: markerFill = "transparent", fillOpacity: markerFillOpacity } = marker;
|
|
let stylerResult = {};
|
|
if (styler) {
|
|
const stylerParams = this.makeStylerParams(highlightState);
|
|
const cbResult = this.cachedCallWithContext(styler, stylerParams) ?? {};
|
|
const resolved = this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`],
|
|
cbResult,
|
|
{ pick: false }
|
|
);
|
|
stylerResult = resolved ?? {};
|
|
}
|
|
stylerResult.marker ?? (stylerResult.marker = {});
|
|
return {
|
|
fill: stylerResult.fill ?? fill,
|
|
fillOpacity: stylerResult.fillOpacity ?? fillOpacity,
|
|
lineDash: stylerResult.lineDash ?? lineDash,
|
|
lineDashOffset: stylerResult.lineDashOffset ?? lineDashOffset,
|
|
stroke: stylerResult.stroke ?? stroke3,
|
|
strokeOpacity: stylerResult.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: stylerResult.strokeWidth ?? strokeWidth,
|
|
marker: {
|
|
enabled: stylerResult.marker.enabled ?? marker.enabled,
|
|
fill: stylerResult.marker.fill ?? markerFill,
|
|
fillOpacity: stylerResult.marker.fillOpacity ?? markerFillOpacity,
|
|
shape: stylerResult.marker.shape ?? shape,
|
|
size: stylerResult.marker.size ?? size,
|
|
lineDash: stylerResult.marker.lineDash ?? marker.lineDash ?? lineDash,
|
|
lineDashOffset: stylerResult.marker.lineDashOffset ?? marker.lineDashOffset ?? lineDashOffset,
|
|
stroke: stylerResult.marker.stroke ?? marker.stroke ?? stroke3,
|
|
strokeOpacity: stylerResult.marker.strokeOpacity ?? marker.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: stylerResult.marker.strokeWidth ?? marker.strokeWidth ?? strokeWidth
|
|
}
|
|
};
|
|
}
|
|
getFormattedMarkerStyle(datum) {
|
|
const stylerStyle = this.getStyle();
|
|
const params = this.makeItemStylerParams(
|
|
this.dataModel,
|
|
this.processedData,
|
|
datum.datumIndex,
|
|
stylerStyle.marker
|
|
);
|
|
return this.getMarkerStyle(
|
|
this.properties.marker,
|
|
datum,
|
|
params,
|
|
{ isHighlight: true },
|
|
void 0,
|
|
stylerStyle
|
|
);
|
|
}
|
|
isPointInArea(x, y) {
|
|
let fillPath;
|
|
for (const child of this.backgroundGroup.children()) {
|
|
if (child instanceof Path) {
|
|
fillPath = child;
|
|
break;
|
|
}
|
|
}
|
|
if (!fillPath?.getBBox().containsPoint(x, y)) {
|
|
return false;
|
|
}
|
|
return fillPath.isPointInPath(x, y);
|
|
}
|
|
computeFocusBounds(opts) {
|
|
return computeMarkerFocusBounds(this, opts);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.styler != null || this.properties.marker.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
AreaSeries.className = "AreaSeries";
|
|
AreaSeries.type = "area";
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/areaSeriesModule.ts
|
|
var themeTemplate = {
|
|
series: {
|
|
nodeClickRange: "nearest",
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_LINEAR_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: { $palette: "stroke" },
|
|
fillOpacity: 0.8,
|
|
strokeOpacity: 1,
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, 0] },
|
|
lineDash: [0],
|
|
lineDashOffset: 0,
|
|
shadow: {
|
|
enabled: false,
|
|
color: DEFAULT_SHADOW_COLOUR,
|
|
xOffset: 3,
|
|
yOffset: 3,
|
|
blur: 5
|
|
},
|
|
interpolation: {
|
|
type: "linear"
|
|
},
|
|
marker: {
|
|
enabled: false,
|
|
shape: "circle",
|
|
size: 7,
|
|
strokeWidth: { $isUserOption: ["./stroke", 1, 0] },
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: { $palette: "stroke" }
|
|
},
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" }
|
|
},
|
|
tooltip: {
|
|
range: { $path: ["/tooltip/range", "nearest"] },
|
|
position: {
|
|
anchorTo: { $path: ["/tooltip/position/anchorTo", "node"] }
|
|
}
|
|
},
|
|
highlight: MARKER_SERIES_HIGHLIGHT_STYLE,
|
|
segmentation: SEGMENTATION_DEFAULTS
|
|
}
|
|
};
|
|
var AreaSeriesModule = {
|
|
type: "series",
|
|
name: "area",
|
|
chartType: "cartesian",
|
|
stackable: true,
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: areaSeriesOptionsDef,
|
|
predictAxis: predictCartesianNonPrimitiveAxis,
|
|
defaultAxes: {
|
|
y: {
|
|
type: "number" /* NUMBER */,
|
|
position: "left" /* LEFT */
|
|
},
|
|
x: {
|
|
type: "category" /* CATEGORY */,
|
|
position: "bottom" /* BOTTOM */
|
|
}
|
|
},
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
themeTemplate,
|
|
create: (ctx) => new AreaSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/scene/shape/barShape.ts
|
|
var FEATHERED_THRESHOLD = 1e-3;
|
|
var BarShape = class extends Rect {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.direction = "x";
|
|
this.featherRatio = 0;
|
|
}
|
|
// optimised field accessor
|
|
/**
|
|
* 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, direction, featherRatio) {
|
|
this.__direction = direction ?? "x";
|
|
this.__featherRatio = featherRatio ?? 0;
|
|
super.setStaticProperties(
|
|
drawingMode,
|
|
topLeftCornerRadius,
|
|
topRightCornerRadius,
|
|
bottomRightCornerRadius,
|
|
bottomLeftCornerRadius,
|
|
visible,
|
|
crisp,
|
|
fillShadow
|
|
);
|
|
}
|
|
get feathered() {
|
|
return Math.abs(this.featherRatio) > FEATHERED_THRESHOLD;
|
|
}
|
|
isPointInPath(x, y) {
|
|
if (!this.feathered) {
|
|
return super.isPointInPath(x, y);
|
|
}
|
|
const bbox = this.getBBox();
|
|
return bbox.containsPoint(x, y);
|
|
}
|
|
updatePath() {
|
|
if (!this.feathered) {
|
|
super.updatePath();
|
|
return;
|
|
}
|
|
const {
|
|
path,
|
|
borderPath,
|
|
__direction: direction,
|
|
__featherRatio: featherRatio,
|
|
__x: x,
|
|
__y: y,
|
|
__width: width2,
|
|
__height: height2
|
|
} = this;
|
|
path.clear();
|
|
borderPath.clear();
|
|
if (direction === "x") {
|
|
const featherInsetX = Math.abs(featherRatio) * width2;
|
|
if (featherRatio > 0) {
|
|
path.moveTo(x, y);
|
|
path.lineTo(x + width2 - featherInsetX, y);
|
|
path.lineTo(x + width2, y + height2 / 2);
|
|
path.lineTo(x + width2 - featherInsetX, y + height2);
|
|
path.lineTo(x, y + height2);
|
|
path.closePath();
|
|
} else {
|
|
path.moveTo(x + featherInsetX, y);
|
|
path.lineTo(x + width2, y);
|
|
path.lineTo(x + width2, y + height2);
|
|
path.lineTo(x + featherInsetX, y + height2);
|
|
path.lineTo(x, y + height2 / 2);
|
|
path.closePath();
|
|
}
|
|
} else {
|
|
const featherInsetY = Math.abs(featherRatio) * height2;
|
|
if (featherRatio > 0) {
|
|
path.moveTo(x, y + featherInsetY);
|
|
path.lineTo(x + width2 / 2, y);
|
|
path.lineTo(x + width2, y + featherInsetY);
|
|
path.lineTo(x + width2, y + height2);
|
|
path.lineTo(x, y + height2);
|
|
path.closePath();
|
|
} else {
|
|
path.moveTo(x, y);
|
|
path.lineTo(x + width2, y);
|
|
path.lineTo(x + width2, y + height2 - featherInsetY);
|
|
path.lineTo(x + width2 / 2, y + height2);
|
|
path.lineTo(x, y + height2 - featherInsetY);
|
|
path.closePath();
|
|
}
|
|
}
|
|
}
|
|
renderStroke(ctx) {
|
|
if (!this.feathered) {
|
|
super.renderStroke(ctx);
|
|
return;
|
|
}
|
|
const {
|
|
__stroke: stroke3,
|
|
__strokeWidth: strokeWidth,
|
|
__lineDash: lineDash,
|
|
__lineDashOffset: lineDashOffset,
|
|
__lineCap: lineCap,
|
|
__lineJoin: lineJoin,
|
|
path
|
|
} = this;
|
|
if (stroke3 && strokeWidth) {
|
|
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;
|
|
}
|
|
ctx.stroke(path.getPath2D());
|
|
ctx.globalAlpha = globalAlpha;
|
|
}
|
|
}
|
|
};
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], BarShape.prototype, "direction", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], BarShape.prototype, "featherRatio", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/barAggregation.ts
|
|
function computeBarAggregation(domain, xValues, yStartValues, yEndValues, options) {
|
|
if (xValues.length < AGGREGATION_THRESHOLD)
|
|
return;
|
|
const [d0, d1] = domain;
|
|
const { smallestKeyInterval, xNeedsValueOf, yNeedsValueOf, existingFilters } = options;
|
|
let maxRange = aggregationRangeFittingPoints(xValues, d0, d1, { smallestKeyInterval, xNeedsValueOf });
|
|
const existingFilter = existingFilters?.find((f) => f.maxRange === maxRange);
|
|
let {
|
|
indexData: positiveIndexData,
|
|
valueData: positiveValueData,
|
|
negativeIndexData,
|
|
negativeValueData
|
|
} = createAggregationIndices(xValues, yEndValues, yStartValues ?? yEndValues, d0, d1, maxRange, {
|
|
split: true,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
reuseIndexData: existingFilter?.positiveIndexData,
|
|
reuseValueData: existingFilter?.positiveValueData,
|
|
reuseNegativeIndexData: existingFilter?.negativeIndexData,
|
|
reuseNegativeValueData: existingFilter?.negativeValueData
|
|
});
|
|
if (!negativeIndexData || !negativeValueData) {
|
|
throw new Error("Negative aggregation data missing in split mode");
|
|
}
|
|
let positiveIndices = getMidpointsForIndices(maxRange, positiveIndexData, existingFilter?.positiveIndices);
|
|
let negativeIndices = getMidpointsForIndices(maxRange, negativeIndexData, existingFilter?.negativeIndices);
|
|
const filters = [
|
|
{
|
|
maxRange,
|
|
positiveIndices,
|
|
positiveIndexData,
|
|
positiveValueData,
|
|
negativeIndices,
|
|
negativeIndexData,
|
|
negativeValueData
|
|
}
|
|
];
|
|
while (maxRange > 64) {
|
|
const currentMaxRange = maxRange;
|
|
const nextMaxRange = Math.trunc(currentMaxRange / 2);
|
|
const nextExistingFilter = existingFilters?.find((f) => f.maxRange === nextMaxRange);
|
|
const positiveCompacted = compactAggregationIndices(positiveIndexData, positiveValueData, currentMaxRange, {
|
|
reuseIndexData: nextExistingFilter?.positiveIndexData,
|
|
reuseValueData: nextExistingFilter?.positiveValueData
|
|
});
|
|
const negativeCompacted = compactAggregationIndices(negativeIndexData, negativeValueData, currentMaxRange, {
|
|
reuseIndexData: nextExistingFilter?.negativeIndexData,
|
|
reuseValueData: nextExistingFilter?.negativeValueData
|
|
});
|
|
maxRange = positiveCompacted.maxRange;
|
|
positiveIndexData = positiveCompacted.indexData;
|
|
positiveValueData = positiveCompacted.valueData;
|
|
positiveIndices = positiveCompacted.midpointData ?? getMidpointsForIndices(maxRange, positiveIndexData, nextExistingFilter?.positiveIndices);
|
|
negativeIndexData = negativeCompacted.indexData;
|
|
negativeValueData = negativeCompacted.valueData;
|
|
negativeIndices = negativeCompacted.midpointData ?? getMidpointsForIndices(maxRange, negativeIndexData, nextExistingFilter?.negativeIndices);
|
|
filters.push({
|
|
maxRange,
|
|
positiveIndices,
|
|
positiveIndexData,
|
|
positiveValueData,
|
|
negativeIndices,
|
|
negativeIndexData,
|
|
negativeValueData
|
|
});
|
|
}
|
|
filters.reverse();
|
|
return filters;
|
|
}
|
|
function computeBarAggregationPartial(domain, xValues, yStartValues, yEndValues, options) {
|
|
if (xValues.length < AGGREGATION_THRESHOLD)
|
|
return;
|
|
const [d0, d1] = domain;
|
|
const { smallestKeyInterval, xNeedsValueOf, yNeedsValueOf, targetRange, existingFilters } = options;
|
|
const finestMaxRange = aggregationRangeFittingPoints(xValues, d0, d1, { smallestKeyInterval, xNeedsValueOf });
|
|
const targetMaxRange = Math.min(finestMaxRange, nextPowerOf2(Math.max(targetRange, AGGREGATION_MIN_RANGE)));
|
|
const existingFilter = existingFilters?.find((f) => f.maxRange === targetMaxRange);
|
|
const {
|
|
indexData: positiveIndexData,
|
|
valueData: positiveValueData,
|
|
negativeIndexData,
|
|
negativeValueData
|
|
} = createAggregationIndices(xValues, yEndValues, yStartValues ?? yEndValues, d0, d1, targetMaxRange, {
|
|
split: true,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
reuseIndexData: existingFilter?.positiveIndexData,
|
|
reuseValueData: existingFilter?.positiveValueData,
|
|
reuseNegativeIndexData: existingFilter?.negativeIndexData,
|
|
reuseNegativeValueData: existingFilter?.negativeValueData
|
|
});
|
|
if (!negativeIndexData || !negativeValueData) {
|
|
throw new Error("Negative aggregation data missing in split mode");
|
|
}
|
|
const immediateLevel = {
|
|
maxRange: targetMaxRange,
|
|
positiveIndices: getMidpointsForIndices(targetMaxRange, positiveIndexData, existingFilter?.positiveIndices),
|
|
positiveIndexData,
|
|
positiveValueData,
|
|
negativeIndices: getMidpointsForIndices(targetMaxRange, negativeIndexData, existingFilter?.negativeIndices),
|
|
negativeIndexData,
|
|
negativeValueData
|
|
};
|
|
function computeRemaining() {
|
|
const allLevels = computeBarAggregation([d0, d1], xValues, yStartValues, yEndValues, {
|
|
smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
existingFilters
|
|
});
|
|
return allLevels?.filter((level) => level.maxRange !== targetMaxRange) ?? [];
|
|
}
|
|
return { immediate: [immediateLevel], computeRemaining };
|
|
}
|
|
function aggregateBarData(scale2, xValues, yStartValues, yEndValues, domainInput, smallestKeyInterval, xNeedsValueOf, yNeedsValueOf) {
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeBarAggregation([d0, d1], xValues, yStartValues, yEndValues, {
|
|
smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf
|
|
});
|
|
}
|
|
var memoizedAggregateBarData = simpleMemorize2(aggregateBarData);
|
|
function aggregateBarDataFromDataModel(scale2, dataModel, processedData, series, existingFilters) {
|
|
const xValues = dataModel.resolveKeysById(series, "xValue", processedData);
|
|
const isStacked = dataModel.hasColumnById(series, "yValue-start");
|
|
const yStartValues = isStacked ? dataModel.resolveColumnById(series, "yValue-start", processedData) : void 0;
|
|
const yEndValues = isStacked ? dataModel.resolveColumnById(series, "yValue-end", processedData) : dataModel.resolveColumnById(series, "yValue-raw", processedData);
|
|
const domainInput = dataModel.getDomain(series, "xValue", "key", processedData);
|
|
const xNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "xValue", processedData);
|
|
const yNeedsValueOf = dataModel.resolveColumnNeedsValueOf(
|
|
series,
|
|
isStacked ? "yValue-end" : "yValue-raw",
|
|
processedData
|
|
);
|
|
if (existingFilters) {
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeBarAggregation([d0, d1], xValues, yStartValues, yEndValues, {
|
|
smallestKeyInterval: processedData.reduced?.smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
existingFilters
|
|
});
|
|
}
|
|
return memoizedAggregateBarData(
|
|
scale2,
|
|
xValues,
|
|
yStartValues,
|
|
yEndValues,
|
|
domainInput,
|
|
processedData.reduced?.smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf
|
|
);
|
|
}
|
|
function aggregateBarDataFromDataModelPartial(scale2, dataModel, processedData, series, targetRange, existingFilters) {
|
|
const xValues = dataModel.resolveKeysById(series, "xValue", processedData);
|
|
const isStacked = dataModel.hasColumnById(series, "yValue-start");
|
|
const yStartValues = isStacked ? dataModel.resolveColumnById(series, "yValue-start", processedData) : void 0;
|
|
const yEndValues = isStacked ? dataModel.resolveColumnById(series, "yValue-end", processedData) : dataModel.resolveColumnById(series, "yValue-raw", processedData);
|
|
const domainInput = dataModel.getDomain(series, "xValue", "key", processedData);
|
|
const xNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "xValue", processedData);
|
|
const yNeedsValueOf = dataModel.resolveColumnNeedsValueOf(
|
|
series,
|
|
isStacked ? "yValue-end" : "yValue-raw",
|
|
processedData
|
|
);
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeBarAggregationPartial([d0, d1], xValues, yStartValues, yEndValues, {
|
|
smallestKeyInterval: processedData.reduced?.smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
targetRange,
|
|
existingFilters
|
|
});
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/barSeriesProperties.ts
|
|
var BarSeriesLabel = class extends Label {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.placement = "inside-center";
|
|
this.spacing = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesLabel.prototype, "placement", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesLabel.prototype, "spacing", 2);
|
|
var BarSeriesProperties = class extends AbstractBarSeriesProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = "#c16068";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "#874349";
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.cornerRadius = 0;
|
|
this.crisp = void 0;
|
|
this.shadow = new DropShadow();
|
|
this.label = new BarSeriesLabel();
|
|
this.tooltip = makeSeriesTooltip();
|
|
this.sparklineMode = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "xKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "xName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "yKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "yName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "yFilterKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "stackGroup", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "normalizedTo", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "crisp", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "styler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "simpleItemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "shadow", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "tooltip", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BarSeriesProperties.prototype, "sparklineMode", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/barSeries.ts
|
|
var BarSeries = class extends AbstractBarSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
propertyKeys: DEFAULT_CARTESIAN_DIRECTION_KEYS,
|
|
propertyNames: DEFAULT_CARTESIAN_DIRECTION_NAMES,
|
|
categoryKey: "xValue",
|
|
pickModes: [
|
|
2 /* AXIS_ALIGNED */,
|
|
// Only used in sparklineMode
|
|
1 /* NEAREST_NODE */,
|
|
0 /* EXACT_SHAPE_MATCH */
|
|
],
|
|
pathsPerSeries: [],
|
|
datumSelectionGarbageCollection: false,
|
|
animationAlwaysUpdateSelections: true,
|
|
animationResetFns: {
|
|
datum: resetBarSelectionsFn,
|
|
label: resetLabelFn
|
|
}
|
|
});
|
|
this.properties = new BarSeriesProperties();
|
|
this.connectsToYAxis = true;
|
|
this.aggregationManager = new AggregationManager();
|
|
this.phantomGroup = this.contentGroup.appendChild(new Group({ name: "phantom", zIndex: -1 }));
|
|
this.phantomSelection = Selection.select(
|
|
this.phantomGroup,
|
|
() => this.nodeFactory(),
|
|
false
|
|
);
|
|
this.phantomHighlightGroup = this.highlightGroup.appendChild(
|
|
new Group({ name: `${this.internalId}-highlight-node` })
|
|
);
|
|
this.phantomHighlightSelection = Selection.select(
|
|
this.phantomHighlightGroup,
|
|
() => this.nodeFactory(),
|
|
false
|
|
);
|
|
this.phantomGroup.opacity = 0.2;
|
|
this.phantomHighlightGroup.opacity = 0.2;
|
|
}
|
|
get pickModeAxis() {
|
|
return this.properties.sparklineMode ? "main" : void 0;
|
|
}
|
|
crossFilteringEnabled() {
|
|
return this.properties.yFilterKey != null && (this.seriesGrouping == null || this.seriesGrouping.stackIndex === 0);
|
|
}
|
|
async processData(dataController) {
|
|
if (!this.data)
|
|
return;
|
|
const { xKey, yKey, yFilterKey, normalizedTo } = this.properties;
|
|
const { seriesGrouping: { groupIndex = this.id } = {}, data } = this;
|
|
const stackCount = this.seriesGrouping?.stackCount ?? 0;
|
|
const stacked = stackCount > 1 || normalizedTo != null;
|
|
const grouped = stacked;
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
const xScale = this.getCategoryAxis()?.scale;
|
|
const yScale = this.getValueAxis()?.scale;
|
|
const { isContinuousX, xScaleType, yScaleType } = this.getScaleInformation({ xScale, yScale });
|
|
const stackGroupName = `bar-stack-${groupIndex}-yValues`;
|
|
const stackGroupTrailingName = `${stackGroupName}-trailing`;
|
|
const visibleProps = this.visible ? {} : { forceValue: 0 };
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
const props = [
|
|
keyProperty(xKey, xScaleType, { id: "xValue", allowNullKey }),
|
|
valueProperty(yKey, yScaleType, { id: `yValue-raw`, invalidValue: null, ...visibleProps })
|
|
];
|
|
if (this.crossFilteringEnabled()) {
|
|
props.push(
|
|
valueProperty(yFilterKey, yScaleType, {
|
|
id: `yFilterValue`,
|
|
invalidValue: null,
|
|
...visibleProps
|
|
})
|
|
);
|
|
}
|
|
if (stacked) {
|
|
props.push(
|
|
...groupAccumulativeValueProperty(
|
|
yKey,
|
|
"normal",
|
|
{
|
|
id: `yValue-end`,
|
|
rangeId: `yValue-range`,
|
|
invalidValue: null,
|
|
missingValue: 0,
|
|
groupId: stackGroupName,
|
|
separateNegative: true,
|
|
...visibleProps
|
|
},
|
|
yScaleType
|
|
),
|
|
...groupAccumulativeValueProperty(
|
|
yKey,
|
|
"trailing",
|
|
{
|
|
id: `yValue-start`,
|
|
invalidValue: null,
|
|
missingValue: 0,
|
|
groupId: stackGroupTrailingName,
|
|
separateNegative: true,
|
|
...visibleProps
|
|
},
|
|
yScaleType
|
|
)
|
|
);
|
|
}
|
|
if (isContinuousX) {
|
|
props.push(SMALLEST_KEY_INTERVAL, LARGEST_KEY_INTERVAL);
|
|
}
|
|
if (isFiniteNumber(normalizedTo)) {
|
|
props.push(normaliseGroupTo([stackGroupName, stackGroupTrailingName], Math.abs(normalizedTo)));
|
|
}
|
|
if (this.needsDataModelDiff() && this.processedData) {
|
|
props.push(diff(this.id, this.processedData));
|
|
}
|
|
if (animationEnabled || !grouped) {
|
|
props.push(animationValidation());
|
|
}
|
|
const { dataModel, processedData } = await this.requestDataModel(dataController, data, {
|
|
props,
|
|
groupByKeys: grouped,
|
|
groupByData: !grouped
|
|
});
|
|
this.aggregateData(dataModel, processedData);
|
|
this.smallestDataInterval = processedData.reduced?.smallestKeyInterval;
|
|
this.largestDataInterval = processedData.reduced?.largestKeyInterval;
|
|
this.animationState.transition("updateData");
|
|
}
|
|
yCumulativeKey(dataModel) {
|
|
return dataModel.hasColumnById(this, `yValue-end`) ? "yValue-end" : "yValue-raw";
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { processedData, dataModel } = this;
|
|
if (dataModel == null || processedData == null)
|
|
return { domain: [] };
|
|
if (direction === this.getCategoryDirection()) {
|
|
const keyDef = dataModel.resolveProcessedDataDefById(this, `xValue`);
|
|
const keys = dataModel.getDomain(this, `xValue`, "key", processedData);
|
|
if (keyDef?.def.type === "key" && keyDef.def.valueType === "category") {
|
|
return keys;
|
|
}
|
|
return { domain: this.padBandExtent(keys.domain) };
|
|
}
|
|
const yKey = this.yCumulativeKey(dataModel);
|
|
let yExtent = this.domainForClippedRange(direction, [yKey], "xValue");
|
|
const yFilterExtent = this.crossFilteringEnabled() ? dataModel.getDomain(this, `yFilterValue`, "value", processedData).domain : void 0;
|
|
if (yFilterExtent != null) {
|
|
yExtent = [Math.min(yExtent[0], yFilterExtent[0]), Math.max(yExtent[1], yFilterExtent[1])];
|
|
}
|
|
const yAxis = this.getValueAxis();
|
|
if (yAxis instanceof NumberAxis && !(yAxis instanceof LogAxis)) {
|
|
const fixedYExtent = Number.isFinite(yExtent[1] - yExtent[0]) ? [Math.min(0, yExtent[0]), Math.max(0, yExtent[1])] : [];
|
|
return { domain: fixNumericExtent(fixedYExtent) };
|
|
} else {
|
|
return { domain: fixNumericExtent(yExtent) };
|
|
}
|
|
}
|
|
getSeriesRange(direction, visibleRange) {
|
|
const selfDirection = this.properties.direction === "horizontal" ? "x" /* X */ : "y" /* Y */;
|
|
if (selfDirection !== direction)
|
|
return [];
|
|
const yKey = this.yCumulativeKey(this.dataModel);
|
|
const [y0, y1] = this.domainForVisibleRange("y" /* Y */, [yKey], "xValue", visibleRange);
|
|
return [Math.min(y0, 0), Math.max(y1, 0)];
|
|
}
|
|
getZoomRangeFittingItems(xVisibleRange, yVisibleRange, minVisibleItems) {
|
|
const yKey = this.yCumulativeKey(this.dataModel);
|
|
return this.zoomFittingVisibleItems("xValue", [yKey], xVisibleRange, yVisibleRange, minVisibleItems);
|
|
}
|
|
getVisibleItems(xVisibleRange, yVisibleRange, minVisibleItems) {
|
|
const yKey = this.yCumulativeKey(this.dataModel);
|
|
return this.countVisibleItems("xValue", [yKey], xVisibleRange, yVisibleRange, minVisibleItems);
|
|
}
|
|
aggregateData(dataModel, processedData) {
|
|
this.aggregationManager.markStale(processedData.input.count);
|
|
if (processedDataIsAnimatable(processedData))
|
|
return;
|
|
const xAxis = this.axes["x" /* X */];
|
|
if (xAxis == null)
|
|
return;
|
|
const targetRange = this.estimateTargetRange();
|
|
this.aggregationManager.aggregate({
|
|
computePartial: (existingFilters) => aggregateBarDataFromDataModelPartial(
|
|
xAxis.scale.type,
|
|
dataModel,
|
|
processedData,
|
|
this,
|
|
targetRange,
|
|
existingFilters
|
|
),
|
|
computeFull: (existingFilters) => aggregateBarDataFromDataModel(xAxis.scale.type, dataModel, processedData, this, existingFilters),
|
|
targetRange
|
|
});
|
|
const filters = this.aggregationManager.filters;
|
|
if (filters && filters.length > 0) {
|
|
debugMetrics_exports.record(
|
|
`${this.type}:aggregation`,
|
|
filters.map((f) => f.maxRange)
|
|
);
|
|
}
|
|
}
|
|
estimateTargetRange() {
|
|
const xAxis = this.axes["x" /* X */];
|
|
if (xAxis?.scale?.range) {
|
|
const [r0, r1] = xAxis.scale.range;
|
|
return Math.abs(r1 - r0);
|
|
}
|
|
return this.ctx.scene?.canvas?.width ?? 800;
|
|
}
|
|
/**
|
|
* Creates shared context for node datum creation/update operations.
|
|
* This context is instantiated once and reused across all datum operations
|
|
* to minimize memory allocations. Only caches values that are expensive to
|
|
* compute - cheap property lookups use `this` directly.
|
|
*/
|
|
createNodeDatumContext(xAxis, yAxis) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData)
|
|
return void 0;
|
|
const rawData = processedData.dataSources?.get(this.id);
|
|
if (rawData == null)
|
|
return void 0;
|
|
const xScale = xAxis.scale;
|
|
const yScale = yAxis.scale;
|
|
const range3 = Math.abs(xScale.range[1] - xScale.range[0]);
|
|
this.aggregationManager.ensureLevelForRange(range3);
|
|
const dataAggregationFilter = this.aggregationManager.getFilterForRange(range3);
|
|
const isStacked = dataModel.hasColumnById(this, `yValue-start`);
|
|
const { label } = this.properties;
|
|
const canIncrementallyUpdate = this.canIncrementallyUpdateNodes(dataAggregationFilter != null);
|
|
const { groupOffset, barOffset, barWidth } = this.getBarDimensions();
|
|
return {
|
|
dataSource: rawData,
|
|
rawData: rawData.data,
|
|
xValues: dataModel.resolveKeysById(this, `xValue`, processedData),
|
|
yRawValues: dataModel.resolveColumnById(this, `yValue-raw`, processedData),
|
|
yFilterValues: this.crossFilteringEnabled() ? dataModel.resolveColumnById(this, `yFilterValue`, processedData) : void 0,
|
|
yStartValues: isStacked ? dataModel.resolveColumnById(this, `yValue-start`, processedData) : void 0,
|
|
yEndValues: isStacked ? dataModel.resolveColumnById(this, `yValue-end`, processedData) : void 0,
|
|
xScale,
|
|
yScale,
|
|
xAxis,
|
|
yAxis,
|
|
groupOffset,
|
|
barOffset,
|
|
barWidth,
|
|
range: range3,
|
|
yReversed: yAxis.isReversed(),
|
|
bboxBottom: yScale.convert(0),
|
|
labelSpacing: label.spacing + (typeof label.padding === "number" ? label.padding : 0),
|
|
crisp: dataAggregationFilter == null && (this.properties.crisp ?? checkCrisp(xAxis?.scale, xAxis?.visibleRange, this.smallestDataInterval, this.largestDataInterval)),
|
|
isStacked,
|
|
animationEnabled: !this.ctx.animationManager.isSkipped(),
|
|
dataAggregationFilter,
|
|
canIncrementallyUpdate,
|
|
phantomNodes: canIncrementallyUpdate ? this.contextNodeData.phantomNodeData ?? [] : [],
|
|
nodes: canIncrementallyUpdate ? this.contextNodeData.nodeData : [],
|
|
labels: canIncrementallyUpdate ? this.contextNodeData.labelData : [],
|
|
nodeIndex: 0,
|
|
phantomIndex: 0,
|
|
barAlongX: this.getBarDirection() === "x" /* X */,
|
|
shouldFlipXY: this.shouldFlipXY(),
|
|
xKey: this.properties.xKey,
|
|
yKey: this.properties.yKey,
|
|
xName: this.properties.xName,
|
|
yName: this.properties.yName,
|
|
legendItemName: this.properties.legendItemName,
|
|
label,
|
|
yDomain: this.getSeriesDomain("y" /* Y */).domain
|
|
};
|
|
}
|
|
/**
|
|
* Computes the x position for a datum at the given index.
|
|
*/
|
|
computeXPosition(ctx, datumIndex) {
|
|
const x = ctx.xScale.convert(ctx.xValues[datumIndex]);
|
|
if (!Number.isFinite(x))
|
|
return Number.NaN;
|
|
return x + ctx.groupOffset + ctx.barOffset;
|
|
}
|
|
prepareNodeDatumState(ctx, nodeDatumScratch, datumIndex, yStart, yEnd) {
|
|
if (!Number.isFinite(yEnd)) {
|
|
return void 0;
|
|
}
|
|
const xValue = ctx.xValues[datumIndex];
|
|
if (xValue === void 0 && !this.properties.allowNullKeys) {
|
|
return void 0;
|
|
}
|
|
const datum = ctx.dataSource?.data[datumIndex];
|
|
const yRawValue = ctx.yRawValues[datumIndex];
|
|
const yFilterValue = ctx.yFilterValues == null ? void 0 : Number(ctx.yFilterValues[datumIndex]);
|
|
if (yFilterValue != null && !Number.isFinite(yFilterValue)) {
|
|
return void 0;
|
|
}
|
|
const labelText = ctx.label.enabled && yRawValue != null ? this.getLabelText(
|
|
yFilterValue ?? yRawValue,
|
|
datum,
|
|
ctx.yKey,
|
|
"y",
|
|
ctx.yDomain,
|
|
ctx.label,
|
|
{
|
|
datum,
|
|
value: yFilterValue ?? yRawValue,
|
|
xKey: ctx.xKey,
|
|
yKey: ctx.yKey,
|
|
xName: ctx.xName,
|
|
yName: ctx.yName,
|
|
legendItemName: ctx.legendItemName
|
|
}
|
|
) : void 0;
|
|
const isPositive = yRawValue >= 0 && !Object.is(yRawValue, -0);
|
|
nodeDatumScratch.datum = datum;
|
|
nodeDatumScratch.xValue = xValue;
|
|
nodeDatumScratch.yRawValue = yRawValue;
|
|
nodeDatumScratch.yFilterValue = yFilterValue;
|
|
nodeDatumScratch.labelText = labelText;
|
|
nodeDatumScratch.inset = yFilterValue != null && yFilterValue > yRawValue;
|
|
nodeDatumScratch.isPositive = isPositive;
|
|
nodeDatumScratch.precomputedBottomY = yFilterValue == null ? void 0 : ctx.yScale.convert(yStart);
|
|
nodeDatumScratch.precomputedIsUpward = yFilterValue == null ? void 0 : isPositive !== ctx.yReversed;
|
|
return nodeDatumScratch;
|
|
}
|
|
/**
|
|
* Creates a skeleton BarNodeDatum with minimal required fields.
|
|
* The node will be populated by updateNodeDatum.
|
|
*/
|
|
createSkeletonNodeDatum(ctx, params, phantom) {
|
|
const scratch = params.nodeDatumScratch;
|
|
return {
|
|
series: this,
|
|
datum: scratch.datum,
|
|
datumIndex: params.datumIndex,
|
|
cumulativeValue: 0,
|
|
// Will be updated by updateNodeDatum
|
|
phantom,
|
|
xValue: scratch.xValue ?? "",
|
|
yValue: 0,
|
|
// Will be updated by updateNodeDatum
|
|
yKey: ctx.yKey,
|
|
xKey: ctx.xKey,
|
|
capDefaults: {
|
|
lengthRatioMultiplier: 0,
|
|
// Will be updated by updateNodeDatum
|
|
lengthMax: 0
|
|
// Will be updated by updateNodeDatum
|
|
},
|
|
x: 0,
|
|
// Will be updated by updateNodeDatum
|
|
y: 0,
|
|
// Will be updated by updateNodeDatum
|
|
width: 0,
|
|
// Will be updated by updateNodeDatum
|
|
height: 0,
|
|
// Will be updated by updateNodeDatum
|
|
midPoint: { x: 0, y: 0 },
|
|
// Required - updated in place by updateNodeDatum
|
|
opacity: params.opacity,
|
|
featherRatio: params.featherRatio,
|
|
topLeftCornerRadius: false,
|
|
// Will be updated by updateNodeDatum
|
|
topRightCornerRadius: false,
|
|
// Will be updated by updateNodeDatum
|
|
bottomRightCornerRadius: false,
|
|
// Will be updated by updateNodeDatum
|
|
bottomLeftCornerRadius: false,
|
|
// Will be updated by updateNodeDatum
|
|
clipBBox: void 0,
|
|
// Will be created/updated by updateNodeDatum
|
|
crisp: ctx.crisp,
|
|
label: void 0,
|
|
// Will be created/updated by updateNodeDatum
|
|
missing: false,
|
|
// Will be updated by updateNodeDatum
|
|
focusable: !phantom
|
|
};
|
|
}
|
|
/**
|
|
* Creates a BarNodeDatum (and optionally a phantom node) for a single data point.
|
|
* Creates skeleton nodes and uses updateNodeDatum to populate them with calculated values.
|
|
*/
|
|
createNodeDatum(ctx, params) {
|
|
const prepared = this.prepareNodeDatumState(
|
|
ctx,
|
|
params.nodeDatumScratch,
|
|
params.datumIndex,
|
|
params.yStart,
|
|
params.yEnd
|
|
);
|
|
if (!prepared) {
|
|
return { nodeData: void 0, phantomNodeData: void 0 };
|
|
}
|
|
const nodeData = this.createSkeletonNodeDatum(ctx, params, false);
|
|
this.updateNodeDatum(ctx, nodeData, params, prepared);
|
|
let phantomNodeData;
|
|
if (prepared.yFilterValue != null) {
|
|
phantomNodeData = this.createSkeletonNodeDatum(ctx, params, true);
|
|
this.updateNodeDatum(ctx, phantomNodeData, params);
|
|
}
|
|
return { nodeData, phantomNodeData };
|
|
}
|
|
/**
|
|
* Updates an existing BarNodeDatum in-place for value-only changes.
|
|
* This is more efficient than recreating the entire node when only data values change
|
|
* but the structure (insertions/removals) remains the same.
|
|
*/
|
|
updateNodeDatum(ctx, node, params, prepared) {
|
|
prepared ?? (prepared = this.prepareNodeDatumState(
|
|
ctx,
|
|
params.nodeDatumScratch,
|
|
params.datumIndex,
|
|
params.yStart,
|
|
params.yEnd
|
|
));
|
|
if (!prepared) {
|
|
return;
|
|
}
|
|
const mutableNode = node;
|
|
const phantom = node.phantom;
|
|
const prevY = params.yStart;
|
|
const yValue = phantom ? prepared.yFilterValue : prepared.yFilterValue ?? prepared.yRawValue;
|
|
const cumulativeValue = phantom ? prepared.yFilterValue : prepared.yFilterValue ?? params.yEnd;
|
|
const nodeLabelText = phantom ? void 0 : prepared.labelText;
|
|
let currY;
|
|
if (phantom) {
|
|
currY = params.yEnd;
|
|
} else if (prepared.yFilterValue == null) {
|
|
currY = params.yEnd;
|
|
} else {
|
|
currY = params.yStart + prepared.yFilterValue;
|
|
}
|
|
let nodeYRange;
|
|
if (phantom) {
|
|
nodeYRange = params.yRange;
|
|
} else {
|
|
nodeYRange = Math.max(params.yStart + (prepared.yFilterValue ?? -Infinity), params.yRange);
|
|
}
|
|
let crossScale;
|
|
if (phantom) {
|
|
crossScale = void 0;
|
|
} else if (prepared.inset) {
|
|
crossScale = 0.6;
|
|
} else {
|
|
crossScale = void 0;
|
|
}
|
|
const isUpward = prepared.precomputedIsUpward ?? prepared.isPositive !== ctx.yReversed;
|
|
const y = ctx.yScale.convert(currY);
|
|
const bottomY = prepared.precomputedBottomY ?? ctx.yScale.convert(prevY);
|
|
const bboxHeight = ctx.yScale.convert(nodeYRange);
|
|
const xOffset = params.width * 0.5 * (1 - (crossScale ?? 1));
|
|
const rectX = ctx.barAlongX ? Math.min(y, bottomY) : params.x + xOffset;
|
|
const rectY = ctx.barAlongX ? params.x + xOffset : Math.min(y, bottomY);
|
|
const rectWidth = ctx.barAlongX ? Math.abs(bottomY - y) : params.width * (crossScale ?? 1);
|
|
const rectHeight = ctx.barAlongX ? params.width * (crossScale ?? 1) : Math.abs(bottomY - y);
|
|
const barRectX = ctx.barAlongX ? Math.min(ctx.bboxBottom, bboxHeight) : params.x + xOffset;
|
|
const barRectY = ctx.barAlongX ? params.x + xOffset : Math.min(ctx.bboxBottom, bboxHeight);
|
|
const barRectWidth = ctx.barAlongX ? Math.abs(ctx.bboxBottom - bboxHeight) : params.width * (crossScale ?? 1);
|
|
const barRectHeight = ctx.barAlongX ? params.width * (crossScale ?? 1) : Math.abs(ctx.bboxBottom - bboxHeight);
|
|
mutableNode.datum = prepared.datum;
|
|
mutableNode.datumIndex = params.datumIndex;
|
|
mutableNode.cumulativeValue = cumulativeValue;
|
|
mutableNode.xValue = prepared.xValue;
|
|
mutableNode.yValue = yValue;
|
|
mutableNode.x = barRectX;
|
|
mutableNode.y = barRectY;
|
|
mutableNode.width = barRectWidth;
|
|
mutableNode.height = barRectHeight;
|
|
const mutableMidPoint = mutableNode.midPoint;
|
|
mutableMidPoint.x = rectX + rectWidth / 2;
|
|
mutableMidPoint.y = rectY + rectHeight / 2;
|
|
const lengthRatioMultiplier = ctx.shouldFlipXY ? rectHeight : rectWidth;
|
|
mutableNode.capDefaults.lengthRatioMultiplier = lengthRatioMultiplier;
|
|
mutableNode.capDefaults.lengthMax = lengthRatioMultiplier;
|
|
mutableNode.opacity = params.opacity;
|
|
mutableNode.featherRatio = params.featherRatio;
|
|
mutableNode.topLeftCornerRadius = ctx.barAlongX !== isUpward;
|
|
mutableNode.topRightCornerRadius = isUpward;
|
|
mutableNode.bottomRightCornerRadius = ctx.barAlongX === isUpward;
|
|
mutableNode.bottomLeftCornerRadius = !isUpward;
|
|
const existingClipBBox = mutableNode.clipBBox;
|
|
if (existingClipBBox) {
|
|
existingClipBBox.x = rectX;
|
|
existingClipBBox.y = rectY;
|
|
existingClipBBox.width = rectWidth;
|
|
existingClipBBox.height = rectHeight;
|
|
} else {
|
|
mutableNode.clipBBox = new BBox(rectX, rectY, rectWidth, rectHeight);
|
|
}
|
|
mutableNode.crisp = ctx.crisp;
|
|
if (nodeLabelText == null) {
|
|
mutableNode.label = void 0;
|
|
} else {
|
|
const labelPlacement = adjustLabelPlacement({
|
|
isUpward,
|
|
isVertical: !ctx.barAlongX,
|
|
placement: ctx.label.placement,
|
|
spacing: ctx.labelSpacing,
|
|
rect: { x: rectX, y: rectY, width: rectWidth, height: rectHeight }
|
|
});
|
|
const existingLabel = mutableNode.label;
|
|
if (existingLabel) {
|
|
existingLabel.text = nodeLabelText;
|
|
existingLabel.x = labelPlacement.x;
|
|
existingLabel.y = labelPlacement.y;
|
|
existingLabel.textAlign = labelPlacement.textAlign;
|
|
existingLabel.textBaseline = labelPlacement.textBaseline;
|
|
} else {
|
|
mutableNode.label = {
|
|
text: nodeLabelText,
|
|
...labelPlacement
|
|
};
|
|
}
|
|
}
|
|
mutableNode.missing = isTooltipValueMissing(yValue);
|
|
}
|
|
/**
|
|
* Creates node data using aggregation filters for large datasets.
|
|
*/
|
|
createNodeDataWithAggregation(ctx, xPosition, nodeDatumParamsScratch) {
|
|
const sign = ctx.yReversed ? -1 : 1;
|
|
for (let p = 0; p < 2; p += 1) {
|
|
const positive = p === 0;
|
|
const indices = positive ? ctx.dataAggregationFilter.positiveIndices : ctx.dataAggregationFilter.negativeIndices;
|
|
const indexData = positive ? ctx.dataAggregationFilter.positiveIndexData : ctx.dataAggregationFilter.negativeIndexData;
|
|
const Y_MIN = positive ? AGGREGATION_INDEX_Y_MIN : AGGREGATION_INDEX_Y_MAX;
|
|
const Y_MAX = positive ? AGGREGATION_INDEX_Y_MAX : AGGREGATION_INDEX_Y_MIN;
|
|
const visibleRange = this.visibleRangeIndices("xValue", ctx.xAxis.range, indices);
|
|
const start2 = visibleRange[0];
|
|
const end3 = visibleRange[1];
|
|
for (let i = start2; i < end3; i += 1) {
|
|
const aggIndex = i * AGGREGATION_SPAN;
|
|
const xMinIndex = indexData[aggIndex + AGGREGATION_INDEX_X_MIN];
|
|
const xMaxIndex = indexData[aggIndex + AGGREGATION_INDEX_X_MAX];
|
|
const yMinIndex = indexData[aggIndex + Y_MIN];
|
|
const yMaxIndex = indexData[aggIndex + Y_MAX];
|
|
if (xMinIndex === AGGREGATION_INDEX_UNSET)
|
|
continue;
|
|
if (ctx.xValues[yMaxIndex] == null || ctx.xValues[yMinIndex] == null)
|
|
continue;
|
|
const x = xPosition(Math.trunc((xMinIndex + xMaxIndex) / 2));
|
|
const width2 = Math.abs(xPosition(xMaxIndex) - xPosition(xMinIndex)) + ctx.barWidth;
|
|
if (x - width2 < 0 || x > ctx.range)
|
|
continue;
|
|
const bandCount = Math.abs(xMaxIndex - xMinIndex) + 1;
|
|
const opacity = BandScale.is(ctx.xScale) ? Math.min(ctx.xScale.bandwidth * Math.max(bandCount - 1, 1) / (ctx.xScale.step * bandCount), 1) : 1;
|
|
nodeDatumParamsScratch.datumIndex = yMaxIndex;
|
|
nodeDatumParamsScratch.x = x;
|
|
nodeDatumParamsScratch.width = width2;
|
|
nodeDatumParamsScratch.opacity = opacity;
|
|
if (ctx.isStacked) {
|
|
nodeDatumParamsScratch.yStart = Number(ctx.yStartValues[yMinIndex]);
|
|
nodeDatumParamsScratch.yEnd = Number(ctx.yEndValues[yMaxIndex]);
|
|
nodeDatumParamsScratch.featherRatio = 0;
|
|
} else {
|
|
const yEndMax = Number(ctx.yRawValues[yMaxIndex]);
|
|
const yEndMin = Number(ctx.yRawValues[yMinIndex]);
|
|
nodeDatumParamsScratch.yStart = 0;
|
|
nodeDatumParamsScratch.yEnd = yEndMax;
|
|
nodeDatumParamsScratch.featherRatio = (positive ? 1 : -1) * sign * (1 - yEndMin / yEndMax);
|
|
}
|
|
nodeDatumParamsScratch.yRange = nodeDatumParamsScratch.yEnd;
|
|
this.upsertNodeDatum(ctx, nodeDatumParamsScratch);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Creates node data for grouped data processing.
|
|
*/
|
|
createNodeDataGrouped(ctx, xPosition, nodeDatumParamsScratch) {
|
|
const processedData = this.processedData;
|
|
const invalidData = processedData.invalidData?.get(this.id);
|
|
const width2 = ctx.barWidth;
|
|
const yRangeIndex = ctx.isStacked ? this.dataModel.resolveProcessedDataIndexById(this, `yValue-range`) : -1;
|
|
const columnIndex = processedData.columnScopes.findIndex((s) => s.has(this.id));
|
|
const groups = processedData.groups;
|
|
const visibleRange = visibleRangeIndices(1, groups.length, ctx.xAxis.range, (groupIndex) => {
|
|
const group = groups[groupIndex];
|
|
const xValue = group.keys[0];
|
|
return this.xCoordinateRange(xValue);
|
|
});
|
|
const start2 = visibleRange[0];
|
|
const end3 = visibleRange[1];
|
|
for (let groupIndex = start2; groupIndex < end3; groupIndex += 1) {
|
|
const group = groups[groupIndex];
|
|
const aggregation = group.aggregation;
|
|
const datumIndices = group.datumIndices[columnIndex];
|
|
if (datumIndices == null)
|
|
continue;
|
|
for (const relativeDatumIndex of datumIndices) {
|
|
const datumIndex = groupIndex + relativeDatumIndex;
|
|
const x = xPosition(datumIndex);
|
|
if (invalidData?.[datumIndex] === true)
|
|
continue;
|
|
const yRawValue = ctx.yRawValues[datumIndex];
|
|
if (yRawValue == null)
|
|
continue;
|
|
const isPositive = yRawValue >= 0 && !Object.is(yRawValue, -0);
|
|
const yStart = ctx.isStacked ? Number(ctx.yStartValues?.[datumIndex]) : 0;
|
|
const yEnd = ctx.isStacked ? Number(ctx.yEndValues?.[datumIndex]) : yRawValue;
|
|
let yRange = yEnd;
|
|
if (ctx.isStacked) {
|
|
yRange = aggregation[yRangeIndex][isPositive ? 1 : 0];
|
|
}
|
|
nodeDatumParamsScratch.datumIndex = datumIndex;
|
|
nodeDatumParamsScratch.x = x;
|
|
nodeDatumParamsScratch.width = width2;
|
|
nodeDatumParamsScratch.yStart = yStart;
|
|
nodeDatumParamsScratch.yEnd = yEnd;
|
|
nodeDatumParamsScratch.yRange = yRange;
|
|
nodeDatumParamsScratch.featherRatio = 0;
|
|
nodeDatumParamsScratch.opacity = 1;
|
|
this.upsertNodeDatum(ctx, nodeDatumParamsScratch);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Creates node data for simple (non-grouped) data processing.
|
|
*/
|
|
createNodeDataSimple(ctx, xPosition, nodeDatumParamsScratch) {
|
|
const invalidData = this.processedData.invalidData?.get(this.id);
|
|
const width2 = ctx.barWidth;
|
|
const visibleRange = this.visibleRangeIndices("xValue", ctx.xAxis.range);
|
|
let start2 = visibleRange[0];
|
|
let end3 = visibleRange[1];
|
|
if (this.processedData.input.count < 1e3) {
|
|
start2 = 0;
|
|
end3 = this.processedData.input.count;
|
|
}
|
|
for (let datumIndex = start2; datumIndex < end3; datumIndex += 1) {
|
|
if (invalidData?.[datumIndex] === true)
|
|
continue;
|
|
const yRawValue = ctx.yRawValues[datumIndex];
|
|
if (yRawValue == null)
|
|
continue;
|
|
const x = xPosition(datumIndex);
|
|
const yEnd = Number(yRawValue);
|
|
nodeDatumParamsScratch.datumIndex = datumIndex;
|
|
nodeDatumParamsScratch.x = x;
|
|
nodeDatumParamsScratch.width = width2;
|
|
nodeDatumParamsScratch.yStart = 0;
|
|
nodeDatumParamsScratch.yEnd = yEnd;
|
|
nodeDatumParamsScratch.yRange = yEnd;
|
|
nodeDatumParamsScratch.featherRatio = 0;
|
|
nodeDatumParamsScratch.opacity = 1;
|
|
this.upsertNodeDatum(ctx, nodeDatumParamsScratch);
|
|
}
|
|
}
|
|
/**
|
|
* Handles node creation/update - reuses existing nodes when possible for incremental updates.
|
|
* This method decides whether to update existing nodes in-place or create new ones.
|
|
*/
|
|
upsertNodeDatum(ctx, params) {
|
|
const canReuseNode = ctx.canIncrementallyUpdate && ctx.nodeIndex < ctx.nodes.length;
|
|
const needsPhantom = ctx.yFilterValues != null;
|
|
const canReusePhantom = needsPhantom && ctx.canIncrementallyUpdate && ctx.phantomIndex < ctx.phantomNodes.length;
|
|
let nodeData;
|
|
let phantomNodeData;
|
|
if (canReuseNode) {
|
|
nodeData = ctx.nodes[ctx.nodeIndex];
|
|
this.updateNodeDatum(ctx, nodeData, params);
|
|
if (ctx.nodeIndex >= ctx.labels.length) {
|
|
ctx.labels.push(nodeData);
|
|
}
|
|
} else {
|
|
const result = this.createNodeDatum(ctx, params);
|
|
if (result.nodeData) {
|
|
ctx.nodes.push(result.nodeData);
|
|
ctx.labels.push(result.nodeData);
|
|
}
|
|
phantomNodeData = result.phantomNodeData;
|
|
}
|
|
ctx.nodeIndex++;
|
|
if (!needsPhantom) {
|
|
return { nodeData: ctx.nodes[ctx.nodeIndex] };
|
|
}
|
|
if (canReusePhantom) {
|
|
phantomNodeData = ctx.phantomNodes[ctx.phantomIndex];
|
|
this.updateNodeDatum(ctx, phantomNodeData, params);
|
|
} else if (phantomNodeData) {
|
|
ctx.phantomNodes.push(phantomNodeData);
|
|
} else {
|
|
const result = this.createNodeDatum(ctx, params);
|
|
if (result.phantomNodeData) {
|
|
ctx.phantomNodes.push(result.phantomNodeData);
|
|
}
|
|
}
|
|
ctx.phantomIndex++;
|
|
return { nodeData, phantomNodeData };
|
|
}
|
|
// ============================================================================
|
|
// Template Method Hooks (createNodeData flow)
|
|
// ============================================================================
|
|
/**
|
|
* Populates node data by selecting the appropriate strategy based on data type.
|
|
* Creates scratch objects and delegates to strategy-specific methods.
|
|
*/
|
|
populateNodeData(ctx) {
|
|
const xPosition = (index) => this.computeXPosition(ctx, index);
|
|
const nodeDatumParamsScratch = {
|
|
nodeDatumScratch: {
|
|
datum: void 0,
|
|
xValue: void 0,
|
|
yRawValue: 0,
|
|
yFilterValue: void 0,
|
|
labelText: void 0,
|
|
inset: false,
|
|
isPositive: false,
|
|
precomputedBottomY: void 0,
|
|
precomputedIsUpward: void 0
|
|
},
|
|
datumIndex: 0,
|
|
x: 0,
|
|
width: 0,
|
|
yStart: 0,
|
|
yEnd: 0,
|
|
yRange: 0,
|
|
featherRatio: 0,
|
|
opacity: 1
|
|
};
|
|
if (ctx.dataAggregationFilter != null) {
|
|
this.createNodeDataWithAggregation(ctx, xPosition, nodeDatumParamsScratch);
|
|
} else if (this.processedData.type === "grouped") {
|
|
this.createNodeDataGrouped(ctx, xPosition, nodeDatumParamsScratch);
|
|
} else {
|
|
this.createNodeDataSimple(ctx, xPosition, nodeDatumParamsScratch);
|
|
}
|
|
}
|
|
/**
|
|
* Creates the initial result context object.
|
|
* Note: segments is undefined here - it's computed in assembleResult.
|
|
*/
|
|
initializeResult(ctx) {
|
|
return {
|
|
itemId: this.properties.yKey,
|
|
nodeData: ctx.nodes,
|
|
phantomNodeData: ctx.phantomNodes,
|
|
labelData: ctx.labels,
|
|
scales: this.calculateScaling(),
|
|
visible: this.visible || ctx.animationEnabled,
|
|
groupScale: this.getScaling(this.ctx.seriesStateManager.getGroupScale(this)),
|
|
styles: getItemStyles(this.getItemStyle.bind(this)),
|
|
segments: void 0
|
|
};
|
|
}
|
|
/**
|
|
* Finalizes node data by trimming incremental arrays.
|
|
* BarSeries has multiple arrays: nodes, phantomNodes, and labels.
|
|
*/
|
|
finalizeNodeData(ctx) {
|
|
if (ctx.canIncrementallyUpdate) {
|
|
this.trimIncrementalNodeArray(ctx.nodes, ctx.nodeIndex);
|
|
this.trimIncrementalNodeArray(ctx.phantomNodes, ctx.phantomIndex);
|
|
this.trimIncrementalNodeArray(ctx.labels, ctx.nodes.length);
|
|
}
|
|
}
|
|
/**
|
|
* Assembles the final result by computing segments.
|
|
*/
|
|
assembleResult(ctx, result) {
|
|
result.segments = calculateSegments(
|
|
this.properties.segmentation,
|
|
ctx.xAxis,
|
|
ctx.yAxis,
|
|
this.chart.seriesRect,
|
|
this.ctx.scene
|
|
);
|
|
return result;
|
|
}
|
|
nodeFactory() {
|
|
return new BarShape();
|
|
}
|
|
updateSeriesSelections() {
|
|
super.updateSeriesSelections();
|
|
this.phantomSelection = this.updateDatumSelection({
|
|
nodeData: this.contextNodeData?.phantomNodeData ?? [],
|
|
datumSelection: this.phantomSelection
|
|
});
|
|
}
|
|
updateHighlightSelectionItem(opts) {
|
|
const out = super.updateHighlightSelectionItem(opts);
|
|
const highlightedDatum = this.ctx.highlightManager?.getActiveHighlight();
|
|
const seriesHighlighted = this.isSeriesHighlighted(highlightedDatum);
|
|
const item = seriesHighlighted && highlightedDatum?.datum ? highlightedDatum : void 0;
|
|
this.phantomHighlightSelection = this.updateDatumSelection({
|
|
nodeData: item ? this.getHighlightData(this.contextNodeData?.phantomNodeData ?? [], item) ?? [] : [],
|
|
datumSelection: this.phantomHighlightSelection
|
|
});
|
|
return out;
|
|
}
|
|
updateNodes(itemHighlighted, nodeRefresh) {
|
|
super.updateNodes(itemHighlighted, nodeRefresh);
|
|
this.updateDatumNodes({
|
|
datumSelection: this.phantomSelection,
|
|
isHighlight: false,
|
|
drawingMode: "overlay"
|
|
});
|
|
this.updateDatumNodes({
|
|
datumSelection: this.phantomHighlightSelection,
|
|
isHighlight: true,
|
|
drawingMode: "overlay"
|
|
});
|
|
}
|
|
getHighlightData(nodeData, highlightedItem) {
|
|
const highlightItem = nodeData.find((nodeDatum) => nodeDatum.datum === highlightedItem.datum);
|
|
return highlightItem == null ? void 0 : [{ ...highlightItem }];
|
|
}
|
|
updateDatumSelection(opts) {
|
|
if (!processedDataIsAnimatable(this.processedData)) {
|
|
return opts.datumSelection.update(opts.nodeData);
|
|
}
|
|
return opts.datumSelection.update(opts.nodeData, void 0, this.getDatumId.bind(this));
|
|
}
|
|
makeStylerParams(highlightStateEnum) {
|
|
const { id: seriesId } = this;
|
|
const {
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
stackGroup,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
xKey,
|
|
yKey
|
|
} = this.properties;
|
|
const highlightState = toHighlightString(highlightStateEnum ?? 0 /* None */);
|
|
return {
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
highlightState,
|
|
lineDash,
|
|
lineDashOffset,
|
|
seriesId,
|
|
stackGroup,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
xKey,
|
|
yKey
|
|
};
|
|
}
|
|
makeItemStylerParams(dataModel, processedData, datumIndex, xValue, isHighlight, style2) {
|
|
const { id: seriesId } = this;
|
|
const { xKey, yKey, stackGroup } = this.properties;
|
|
const datum = processedData.dataSources.get(seriesId)?.data?.[datumIndex];
|
|
const yValue = dataModel.resolveColumnById(this, `yValue-raw`, processedData)[datumIndex];
|
|
const xDomain = dataModel.getDomain(this, `xValue`, "key", processedData).domain;
|
|
const yDomain = dataModel.getDomain(this, this.yCumulativeKey(dataModel), "value", processedData).domain;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightStateString = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
seriesId,
|
|
...datumStylerProperties(xValue, yValue, xKey, yKey, xDomain, yDomain),
|
|
datum,
|
|
xValue,
|
|
yValue,
|
|
stackGroup,
|
|
highlightState: highlightStateString,
|
|
...style2,
|
|
fill
|
|
};
|
|
}
|
|
getStyle(ignoreStylerCallback, highlightState) {
|
|
const {
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
styler
|
|
} = this.properties;
|
|
let stylerResult = {};
|
|
if (!ignoreStylerCallback && styler) {
|
|
const stylerParams = this.makeStylerParams(highlightState);
|
|
stylerResult = this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`],
|
|
this.cachedCallWithContext(styler, stylerParams) ?? {},
|
|
{ pick: false }
|
|
) ?? {};
|
|
}
|
|
return {
|
|
cornerRadius: stylerResult.cornerRadius ?? cornerRadius,
|
|
fill: stylerResult.fill ?? fill,
|
|
fillOpacity: stylerResult.fillOpacity ?? fillOpacity,
|
|
lineDash: stylerResult.lineDash ?? lineDash,
|
|
lineDashOffset: stylerResult.lineDashOffset ?? lineDashOffset,
|
|
opacity: 1,
|
|
stroke: stylerResult.stroke ?? stroke3,
|
|
strokeOpacity: stylerResult.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: stylerResult.strokeWidth ?? strokeWidth
|
|
};
|
|
}
|
|
getItemStyle(datumIndex, isHighlight, highlightState) {
|
|
const { properties, dataModel, processedData } = this;
|
|
const { itemStyler, simpleItemStyler } = properties;
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex, highlightState);
|
|
if (simpleItemStyler && processedData != null && datumIndex != null) {
|
|
const datum = processedData.dataSources.get(this.id)?.data?.[datumIndex];
|
|
const overrides = simpleItemStyler(datum);
|
|
return mergeDefaults(
|
|
overrides,
|
|
highlightStyle,
|
|
this.getStyle(false, highlightState)
|
|
);
|
|
}
|
|
let style2 = mergeDefaults(highlightStyle, this.getStyle(datumIndex === void 0, highlightState));
|
|
if (itemStyler && dataModel != null && processedData != null && datumIndex != null) {
|
|
const xValue = dataModel.resolveKeysById(this, `xValue`, processedData)[datumIndex];
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId(this.getDatumId({ xValue, phantom: false }), isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const params = this.makeItemStylerParams(
|
|
dataModel,
|
|
processedData,
|
|
datumIndex,
|
|
xValue,
|
|
isHighlight,
|
|
style2
|
|
);
|
|
return this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`],
|
|
this.callWithContext(itemStyler, params)
|
|
);
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(overrides, style2);
|
|
}
|
|
}
|
|
return style2;
|
|
}
|
|
updateDatumStyles(opts) {
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
const series = this;
|
|
function applyDatumStyle(node, datum) {
|
|
if (!opts.datumSelection.isGarbage(node)) {
|
|
const highlightState = series.getHighlightState(highlightedDatum, opts.isHighlight, datum.datumIndex);
|
|
datum.style = series.getItemStyle(datum.datumIndex, opts.isHighlight, highlightState);
|
|
}
|
|
}
|
|
opts.datumSelection.each(applyDatumStyle);
|
|
}
|
|
updateDatumNodes(opts) {
|
|
const { contextNodeData } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
const { shadow } = this.properties;
|
|
const categoryAlongX = this.getCategoryDirection() === "x" /* X */;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
const direction = this.getBarDirection();
|
|
const { drawingMode, isHighlight } = opts;
|
|
const series = this;
|
|
const contextStyles = contextNodeData.styles;
|
|
function updateDatumNode(rect2, datum) {
|
|
const style2 = datum.style ?? contextStyles[series.getHighlightState(highlightedDatum, isHighlight, datum.datumIndex)];
|
|
rect2.setStyleProperties(style2, fillBBox);
|
|
const cornerRadius = style2.cornerRadius ?? 0;
|
|
const visible = categoryAlongX ? (datum.clipBBox?.width ?? datum.width) > 0 : (datum.clipBBox?.height ?? datum.height) > 0;
|
|
rect2.setStaticProperties(
|
|
drawingMode,
|
|
datum.topLeftCornerRadius ? cornerRadius : 0,
|
|
datum.topRightCornerRadius ? cornerRadius : 0,
|
|
datum.bottomRightCornerRadius ? cornerRadius : 0,
|
|
datum.bottomLeftCornerRadius ? cornerRadius : 0,
|
|
visible,
|
|
datum.crisp,
|
|
shadow,
|
|
direction,
|
|
datum.featherRatio
|
|
);
|
|
}
|
|
opts.datumSelection.each(updateDatumNode);
|
|
}
|
|
updateLabelSelection(opts) {
|
|
const data = this.isLabelEnabled() ? opts.labelData : [];
|
|
return opts.labelSelection.update(data, (text2) => {
|
|
text2.pointerEvents = 1 /* None */;
|
|
});
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const { isHighlight = false } = opts;
|
|
const params = {
|
|
xKey: this.properties.xKey,
|
|
xName: this.properties.xName ?? this.properties.xKey,
|
|
yKey: this.properties.yKey,
|
|
yName: this.properties.yName ?? this.properties.yKey,
|
|
legendItemName: this.properties.legendItemName ?? this.properties.xName ?? this.properties.xKey
|
|
};
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
opts.labelSelection.each((textNode, datum) => {
|
|
textNode.fillOpacity = this.getHighlightStyle(isHighlight, datum?.datumIndex).opacity ?? 1;
|
|
updateLabelNode(this, textNode, params, this.properties.label, datum.label, isHighlight, activeHighlight);
|
|
});
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, properties } = this;
|
|
const { xKey, xName, yKey, yName, legendItemName, stackGroup, tooltip } = properties;
|
|
const allowNullKeys = properties.allowNullKeys ?? false;
|
|
const xAxis = this.getCategoryAxis();
|
|
const yAxis = this.getValueAxis();
|
|
if (!dataModel || !processedData || !xAxis || !yAxis)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data?.[datumIndex];
|
|
const xValue = dataModel.resolveKeysById(this, `xValue`, processedData)[datumIndex];
|
|
const yValue = dataModel.resolveColumnById(this, `yValue-raw`, processedData)[datumIndex];
|
|
if (xValue === void 0 && !allowNullKeys)
|
|
return;
|
|
const format = this.getItemStyle(datumIndex, false);
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading: this.getAxisValueText(xAxis, "tooltip", xValue, datum, xKey, legendItemName, allowNullKeys),
|
|
symbol: this.legendItemSymbol(),
|
|
data: [
|
|
{
|
|
label: yName,
|
|
fallbackLabel: yKey,
|
|
value: this.getAxisValueText(yAxis, "tooltip", yValue, datum, yKey, legendItemName),
|
|
missing: isTooltipValueMissing(yValue)
|
|
}
|
|
]
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: yName,
|
|
xKey,
|
|
xName,
|
|
yKey,
|
|
yName,
|
|
legendItemName,
|
|
stackGroup,
|
|
...format,
|
|
...this.getModuleTooltipParams()
|
|
}
|
|
);
|
|
}
|
|
legendItemSymbol() {
|
|
const { fill, stroke: stroke3, strokeWidth, fillOpacity, strokeOpacity, lineDash, lineDashOffset } = this.getStyle(
|
|
false,
|
|
0 /* None */
|
|
);
|
|
return {
|
|
marker: {
|
|
fill: fill ?? "rgba(0, 0, 0, 0)",
|
|
stroke: stroke3 ?? "rgba(0, 0, 0, 0)",
|
|
fillOpacity,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash,
|
|
lineDashOffset
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
const { showInLegend } = this.properties;
|
|
if (legendType !== "category") {
|
|
return [];
|
|
}
|
|
const {
|
|
id: seriesId,
|
|
ctx: { legendManager },
|
|
visible
|
|
} = this;
|
|
const { yKey: itemId, yName, legendItemName } = this.properties;
|
|
return [
|
|
{
|
|
legendType: "category",
|
|
id: seriesId,
|
|
itemId,
|
|
seriesId,
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId, itemId }),
|
|
label: { text: legendItemName ?? yName ?? itemId },
|
|
symbol: this.legendItemSymbol(),
|
|
legendItemName,
|
|
hideInLegend: !showInLegend
|
|
}
|
|
];
|
|
}
|
|
resetDatumAnimation(data) {
|
|
resetBarSelectionsDirect([data.datumSelection, this.phantomSelection]);
|
|
}
|
|
animateReadyHighlight(data) {
|
|
resetBarSelectionsDirect([data, this.phantomHighlightSelection]);
|
|
}
|
|
animateEmptyUpdateReady({ datumSelection, labelSelection, annotationSelections }) {
|
|
const { phantomSelection } = this;
|
|
const fns = prepareBarAnimationFunctions(
|
|
collapsedStartingBarPosition(this.isVertical(), this.axes, "normal"),
|
|
"unknown"
|
|
);
|
|
fromToMotion(this.id, "nodes", this.ctx.animationManager, [datumSelection, phantomSelection], fns);
|
|
seriesLabelFadeInAnimation(this, "labels", this.ctx.animationManager, labelSelection);
|
|
seriesLabelFadeInAnimation(this, "annotations", this.ctx.animationManager, ...annotationSelections);
|
|
}
|
|
animateWaitingUpdateReady(data) {
|
|
const { phantomSelection } = this;
|
|
const { datumSelection, labelSelection, annotationSelections, contextData, previousContextData } = data;
|
|
this.ctx.animationManager.stopByAnimationGroupId(this.id);
|
|
const dataDiff = calculateDataDiff(
|
|
this.id,
|
|
datumSelection,
|
|
this.getDatumId.bind(this),
|
|
data.contextData,
|
|
previousContextData,
|
|
this.processedData,
|
|
this.processedDataUpdated
|
|
);
|
|
const mode = previousContextData == null ? "fade" : "normal";
|
|
const fns = prepareBarAnimationFunctions(
|
|
collapsedStartingBarPosition(this.isVertical(), this.axes, mode),
|
|
"added"
|
|
);
|
|
fromToMotion(
|
|
this.id,
|
|
"nodes",
|
|
this.ctx.animationManager,
|
|
[datumSelection, phantomSelection],
|
|
fns,
|
|
(_, datum) => this.getDatumId(datum),
|
|
dataDiff
|
|
);
|
|
if (!dataDiff || dataDiff?.changed || !areScalingEqual(contextData.groupScale, previousContextData?.groupScale)) {
|
|
seriesLabelFadeInAnimation(this, "labels", this.ctx.animationManager, labelSelection);
|
|
seriesLabelFadeInAnimation(this, "annotations", this.ctx.animationManager, ...annotationSelections);
|
|
}
|
|
}
|
|
getDatumId(datum) {
|
|
return createDatumId(datum.xValue, datum.phantom);
|
|
}
|
|
isLabelEnabled() {
|
|
return this.properties.label.enabled;
|
|
}
|
|
computeFocusBounds({ datumIndex }) {
|
|
const datumBox = this.contextNodeData?.nodeData[datumIndex].clipBBox;
|
|
return computeBarFocusBounds(this, datumBox);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.styler != null || this.properties.itemStyler != null || this.properties.simpleItemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
BarSeries.className = "BarSeries";
|
|
BarSeries.type = "bar";
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/barSeriesModule.ts
|
|
var themeTemplate2 = {
|
|
series: {
|
|
direction: "vertical",
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_LINEAR_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: { $palette: "stroke" },
|
|
fillOpacity: 1,
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, 0] },
|
|
lineDash: [0],
|
|
lineDashOffset: 0,
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
padding: { $isUserOption: ["./spacing", 0, 8] },
|
|
// compatibility with old `padding` property (now named `spacing`).
|
|
enabled: false,
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
color: {
|
|
$if: [
|
|
{
|
|
$or: [
|
|
{ $eq: [{ $path: "./placement" }, "outside-start"] },
|
|
{ $eq: [{ $path: "./placement" }, "outside-end"] }
|
|
]
|
|
},
|
|
{ $ref: "textColor" },
|
|
{ $ref: "chartBackgroundColor" }
|
|
]
|
|
},
|
|
placement: "inside-center"
|
|
},
|
|
shadow: {
|
|
enabled: false,
|
|
color: DEFAULT_SHADOW_COLOUR,
|
|
xOffset: 3,
|
|
yOffset: 3,
|
|
blur: 5
|
|
},
|
|
highlight: MULTI_SERIES_HIGHLIGHT_STYLE,
|
|
segmentation: SEGMENTATION_DEFAULTS
|
|
}
|
|
};
|
|
var BarSeriesModule = {
|
|
type: "series",
|
|
name: "bar",
|
|
chartType: "cartesian",
|
|
stackable: true,
|
|
groupable: true,
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: barSeriesOptionsDef,
|
|
predictAxis: predictCartesianNonPrimitiveAxis,
|
|
defaultAxes: DIRECTION_SWAP_AXES,
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
axisKeysFlipped: { ["x" /* X */]: "yKeyAxis", ["y" /* Y */]: "xKeyAxis" },
|
|
themeTemplate: themeTemplate2,
|
|
create: (ctx) => new BarSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/bubbleAggregation.ts
|
|
var SIZE_QUANTIZATION = 3;
|
|
var FILTER_DATUM_THRESHOLD = 5;
|
|
var FILTER_RANGE_THRESHOLD = 0.05;
|
|
function getPrimaryDatumIndex(context, indices, bounds) {
|
|
const { xValues, yValues, xDomain, yDomain, xNeedsValueOf, yNeedsValueOf } = context;
|
|
const { x0, y0, x1, y1 } = bounds;
|
|
let currentIndex = 0;
|
|
let currentDistanceSquared = Infinity;
|
|
const midX = (x0 + x1) / 2;
|
|
const midY = (y0 + y1) / 2;
|
|
for (const datumIndex of indices) {
|
|
const xValue = xValues[datumIndex];
|
|
const yValue = yValues[datumIndex];
|
|
if (xValue == null || yValue == null)
|
|
continue;
|
|
const xRatio = aggregationXRatioForXValue(xValue, xDomain.min, xDomain.max, xNeedsValueOf);
|
|
const yRatio = aggregationXRatioForXValue(yValue, yDomain.min, yDomain.max, yNeedsValueOf);
|
|
const distanceSquared2 = (xRatio - midX) ** 2 + (yRatio - midY) ** 2;
|
|
if (distanceSquared2 < currentDistanceSquared) {
|
|
currentDistanceSquared = distanceSquared2;
|
|
currentIndex = datumIndex;
|
|
}
|
|
}
|
|
return currentIndex;
|
|
}
|
|
function countVisibleItems(context, indices, bounds) {
|
|
const { xValues, yValues, xDomain, yDomain, xNeedsValueOf, yNeedsValueOf } = context;
|
|
const { x0, y0, x1, y1 } = bounds;
|
|
let count = 0;
|
|
for (const datumIndex of indices) {
|
|
const xValue = xValues[datumIndex];
|
|
const yValue = yValues[datumIndex];
|
|
if (xValue == null || yValue == null)
|
|
continue;
|
|
const xRatio = aggregationXRatioForXValue(xValue, xDomain.min, xDomain.max, xNeedsValueOf);
|
|
const yRatio = aggregationXRatioForXValue(yValue, yDomain.min, yDomain.max, yNeedsValueOf);
|
|
if (xRatio >= x0 && xRatio <= x1 && yRatio >= y0 && yRatio <= y1) {
|
|
count += 1;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
function quadChildren(context, indices, bounds) {
|
|
const { xValues, yValues, xDomain, yDomain, xNeedsValueOf, yNeedsValueOf } = context;
|
|
const { x0, y0, x1, y1 } = bounds;
|
|
const childBuckets = [
|
|
{ x0: 1, y0: 1, x1: 0, y1: 0, indices: [] },
|
|
{ x0: 1, y0: 1, x1: 0, y1: 0, indices: [] },
|
|
{ x0: 1, y0: 1, x1: 0, y1: 0, indices: [] },
|
|
{ x0: 1, y0: 1, x1: 0, y1: 0, indices: [] }
|
|
];
|
|
const midX = (x0 + x1) / 2;
|
|
const midY = (y0 + y1) / 2;
|
|
for (const datumIndex of indices) {
|
|
const xValue = xValues[datumIndex];
|
|
const yValue = yValues[datumIndex];
|
|
if (xValue == null || yValue == null)
|
|
continue;
|
|
const xRatio = aggregationXRatioForXValue(xValue, xDomain.min, xDomain.max, xNeedsValueOf);
|
|
const yRatio = aggregationXRatioForXValue(yValue, yDomain.min, yDomain.max, yNeedsValueOf);
|
|
const childIndex = (xRatio > midX ? 1 : 0) + (yRatio > midY ? 2 : 0);
|
|
const childBucket = childBuckets[childIndex];
|
|
childBucket.indices.push(datumIndex);
|
|
childBucket.x0 = Math.min(childBucket.x0, xRatio);
|
|
childBucket.y0 = Math.min(childBucket.y0, yRatio);
|
|
childBucket.x1 = Math.max(childBucket.x1, xRatio);
|
|
childBucket.y1 = Math.max(childBucket.y1, yRatio);
|
|
}
|
|
const children = [];
|
|
for (const childBucket of childBuckets) {
|
|
const { indices: childIndices, x0: cx0, x1: cx1, y0: cy0, y1: cy1 } = childBucket;
|
|
if (childIndices.length === 0)
|
|
continue;
|
|
const child = aggregateQuad(context, childIndices, { x0: cx0, y0: cy0, x1: cx1, y1: cy1 });
|
|
children.push(child);
|
|
}
|
|
return children;
|
|
}
|
|
function aggregateQuad(context, indices, bounds) {
|
|
const { x0, y0, x1, y1 } = bounds;
|
|
const terminate = indices.length < FILTER_DATUM_THRESHOLD && x1 - x0 < FILTER_RANGE_THRESHOLD && y1 - y0 < FILTER_RANGE_THRESHOLD || x0 === x1 && y0 === y1;
|
|
let children = terminate ? null : quadChildren(context, indices, bounds);
|
|
if (children?.length === 1) {
|
|
return children[0];
|
|
} else if (children?.length === 0) {
|
|
children = null;
|
|
}
|
|
const scale2 = Math.hypot(x1 - x0, y1 - y0);
|
|
const primaryDatumIndex = getPrimaryDatumIndex(context, indices, bounds);
|
|
return { scale: scale2, x0, y0, x1, y1, indices, primaryDatumIndex, children };
|
|
}
|
|
function computeBubbleAggregation(xDomain, yDomain, xValues, yValues, sizeValues, sizeDomain, options) {
|
|
const [xd0, xd1] = xDomain;
|
|
const [yd0, yd1] = yDomain;
|
|
const [sd0, sd1] = sizeDomain;
|
|
const { xNeedsValueOf, yNeedsValueOf } = options;
|
|
const context = {
|
|
xValues,
|
|
yValues,
|
|
xDomain: { min: xd0, max: xd1 },
|
|
yDomain: { min: yd0, max: yd1 },
|
|
xNeedsValueOf,
|
|
yNeedsValueOf
|
|
};
|
|
const filters = [];
|
|
if (sizeValues != null && sd1 > sd0) {
|
|
const sizeIndices = Array.from({ length: SIZE_QUANTIZATION }, () => []);
|
|
for (let datumIndex = 0; datumIndex < sizeValues.length; datumIndex += 1) {
|
|
const sizeValue = sizeValues[datumIndex];
|
|
const sizeRatio = (sizeValue - sd0) / (sd1 - sd0);
|
|
const sizeIndex = Math.trunc(sizeRatio * SIZE_QUANTIZATION);
|
|
if (sizeIndex >= 0 && sizeIndex < SIZE_QUANTIZATION) {
|
|
sizeIndices[sizeIndex].push(datumIndex);
|
|
}
|
|
}
|
|
for (let i = 0; i < sizeIndices.length; i += 1) {
|
|
const indices = sizeIndices[i];
|
|
const node = aggregateQuad(context, indices, { x0: 0, y0: 0, x1: 1, y1: 1 });
|
|
if (node != null) {
|
|
const sizeRatio = i / SIZE_QUANTIZATION;
|
|
filters.push({ sizeRatio, node });
|
|
}
|
|
}
|
|
} else {
|
|
const indices = xValues.map((_, i) => i);
|
|
const node = aggregateQuad(context, indices, { x0: 0, y0: 0, x1: 1, y1: 1 });
|
|
if (node != null) {
|
|
filters.push({ sizeRatio: 0, node });
|
|
}
|
|
}
|
|
return filters.length > 0 ? { xValues, yValues, xd0, xd1, yd0, yd1, filters, xNeedsValueOf, yNeedsValueOf } : void 0;
|
|
}
|
|
function aggregateBubbleData(xScale, yScale, xValues, yValues, sizeValues, xDomainInput, yDomainInput, sizeDomain, xNeedsValueOf, yNeedsValueOf) {
|
|
const [xd0, xd1] = aggregationDomain(xScale, xDomainInput);
|
|
const [yd0, yd1] = aggregationDomain(yScale, yDomainInput);
|
|
return computeBubbleAggregation(
|
|
[xd0, xd1],
|
|
[yd0, yd1],
|
|
xValues,
|
|
yValues,
|
|
sizeValues,
|
|
[sizeDomain[0], sizeDomain[1]],
|
|
{ xNeedsValueOf, yNeedsValueOf }
|
|
);
|
|
}
|
|
function aggregateBubbleDataFromDataModel(xScale, yScale, dataModel, processedData, sizeScale, hasSizeKey, series) {
|
|
const xValues = dataModel.resolveColumnById(series, "xValue", processedData);
|
|
const yValues = dataModel.resolveColumnById(series, "yValue", processedData);
|
|
const sizeValues = hasSizeKey ? dataModel.resolveColumnById(series, "sizeValue", processedData) : void 0;
|
|
const xDomain = dataModel.getDomain(series, "xValue", "value", processedData);
|
|
const yDomain = dataModel.getDomain(series, "yValue", "value", processedData);
|
|
const sizeDomain = hasSizeKey ? sizeScale.domain : [0, 0];
|
|
const xNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "xValue", processedData);
|
|
const yNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "yValue", processedData);
|
|
return aggregateBubbleData(
|
|
xScale,
|
|
yScale,
|
|
xValues,
|
|
yValues,
|
|
sizeValues,
|
|
xDomain,
|
|
yDomain,
|
|
sizeDomain,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf
|
|
);
|
|
}
|
|
function computeBubbleAggregationCountIndices(dilation, dataAggregation, options, counter, groupedAggregation, singleDatumIndices) {
|
|
const {
|
|
xRange,
|
|
yRange,
|
|
xVisibleRange: [xvr0, xvr1],
|
|
yVisibleRange: [yvr0, yvr1],
|
|
minSize,
|
|
maxSize
|
|
} = options;
|
|
const { xValues, yValues, xd0, xd1, yd0, yd1, xNeedsValueOf, yNeedsValueOf } = dataAggregation;
|
|
const baseScalingFactor = 1 / Math.min(xRange / (xvr1 - xvr0), yRange / (yvr1 - yvr0));
|
|
const context = {
|
|
xValues,
|
|
yValues,
|
|
xDomain: { min: xd0, max: xd1 },
|
|
yDomain: { min: yd0, max: yd1 },
|
|
xNeedsValueOf,
|
|
yNeedsValueOf
|
|
};
|
|
for (const { sizeRatio, node } of dataAggregation.filters) {
|
|
const radius = 0.5 * (minSize + sizeRatio * (maxSize - minSize));
|
|
const baseMinScale = radius * baseScalingFactor;
|
|
const minScale = dilation * baseMinScale;
|
|
const x0 = xvr0 - radius / xRange;
|
|
const x1 = xvr1 + radius / xRange;
|
|
const y0 = yvr0 - radius / yRange;
|
|
const y1 = yvr1 + radius / yRange;
|
|
const queue = [node];
|
|
while (queue.length > 0) {
|
|
const item = queue.pop();
|
|
if (item.x1 < x0 || item.x0 > x1 || item.y1 < y0 || item.y0 > y1) {
|
|
continue;
|
|
}
|
|
if (dilation !== 1 && item.scale <= minScale) {
|
|
if (counter != null) {
|
|
counter.count += 1;
|
|
}
|
|
groupedAggregation?.push({
|
|
datumIndex: item.primaryDatumIndex,
|
|
count: item.indices.length,
|
|
area: (item.x1 - item.x0) * (item.y1 - item.y0),
|
|
dilation: clamp(1, item.scale / baseMinScale, dilation)
|
|
});
|
|
} else if (item.children == null) {
|
|
const { indices } = item;
|
|
if (counter != null) {
|
|
const fullyVisible = item.x0 >= xvr0 && item.x1 <= xvr1 && item.y0 >= yvr0 && item.y1 <= yvr1;
|
|
const itemCount = fullyVisible ? indices.length : countVisibleItems(context, indices, { x0: xvr0, y0: yvr0, x1: xvr1, y1: yvr1 });
|
|
counter.count += itemCount;
|
|
}
|
|
singleDatumIndices?.push(...indices);
|
|
} else {
|
|
queue.push(...item.children);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function computeBubbleAggregationCount(dilation, dataAggregation, options) {
|
|
const counter = { count: 0 };
|
|
computeBubbleAggregationCountIndices(dilation, dataAggregation, options, counter, void 0, void 0);
|
|
return counter.count;
|
|
}
|
|
var MAX_AGGREGATION_DILATION = 100;
|
|
var DILATION_ITERATIONS = 12;
|
|
function computeBubbleAggregationDilation(dataAggregation, aggregationOptions, maxRenderedItems) {
|
|
if (computeBubbleAggregationCount(1, dataAggregation, aggregationOptions) <= maxRenderedItems) {
|
|
return 1;
|
|
}
|
|
let minDilation = 1;
|
|
let maxDilation = 2;
|
|
while (computeBubbleAggregationCount(maxDilation, dataAggregation, aggregationOptions) > maxRenderedItems && maxDilation < MAX_AGGREGATION_DILATION) {
|
|
minDilation *= 2;
|
|
maxDilation *= 2;
|
|
}
|
|
for (let i = 0; i < DILATION_ITERATIONS; i += 1) {
|
|
const dilation = (maxDilation + minDilation) / 2;
|
|
const count = computeBubbleAggregationCount(dilation, dataAggregation, aggregationOptions);
|
|
if (count > maxRenderedItems) {
|
|
minDilation = dilation;
|
|
} else {
|
|
maxDilation = dilation;
|
|
}
|
|
}
|
|
return (minDilation + maxDilation) / 2;
|
|
}
|
|
function computeBubbleAggregationData(dilation, dataAggregation, options) {
|
|
const groupedAggregation = [];
|
|
const singleDatumIndices = [];
|
|
computeBubbleAggregationCountIndices(
|
|
dilation,
|
|
dataAggregation,
|
|
options,
|
|
void 0,
|
|
groupedAggregation,
|
|
singleDatumIndices
|
|
);
|
|
return { groupedAggregation, singleDatumIndices };
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/bubbleSeriesProperties.ts
|
|
var BubbleSeriesMarker = class extends SeriesMarker {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.maxSize = 30;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], BubbleSeriesMarker.prototype, "maxSize", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneArrayChangeDetection()
|
|
], BubbleSeriesMarker.prototype, "domain", 2);
|
|
var BubbleSeriesLabel = class extends Label {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.placement = "top";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesLabel.prototype, "placement", 2);
|
|
var BubbleSeriesProperties = class extends CartesianSeriesProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.label = new BubbleSeriesLabel();
|
|
this.tooltip = makeSeriesTooltip();
|
|
this.maxRenderedItems = Infinity;
|
|
// No validation. Not a part of the options contract.
|
|
this.marker = new BubbleSeriesMarker();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "xKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "yKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "sizeKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "labelKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "xFilterKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "yFilterKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "sizeFilterKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "xName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "yName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "sizeName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "labelName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "title", 2);
|
|
__decorateClass([
|
|
ProxyProperty("marker.shape")
|
|
], BubbleSeriesProperties.prototype, "shape", 2);
|
|
__decorateClass([
|
|
ProxyProperty("marker.size")
|
|
], BubbleSeriesProperties.prototype, "size", 2);
|
|
__decorateClass([
|
|
ProxyProperty("marker.maxSize")
|
|
], BubbleSeriesProperties.prototype, "maxSize", 2);
|
|
__decorateClass([
|
|
ProxyProperty("marker.domain")
|
|
], BubbleSeriesProperties.prototype, "domain", 2);
|
|
__decorateClass([
|
|
ProxyProperty("marker.fill")
|
|
], BubbleSeriesProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
ProxyProperty("marker.fillOpacity")
|
|
], BubbleSeriesProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
ProxyProperty("marker.stroke")
|
|
], BubbleSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
ProxyProperty("marker.strokeWidth")
|
|
], BubbleSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
ProxyProperty("marker.strokeOpacity")
|
|
], BubbleSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
ProxyProperty("marker.lineDash")
|
|
], BubbleSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
ProxyProperty("marker.lineDashOffset")
|
|
], BubbleSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
ProxyProperty("marker.itemStyler")
|
|
], BubbleSeriesProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "styler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "tooltip", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BubbleSeriesProperties.prototype, "maxRenderedItems", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/bubbleSeries.ts
|
|
var BubbleScatterSeriesNodeEvent = class extends CartesianSeriesNodeEvent {
|
|
constructor(type, nativeEvent, datum, series) {
|
|
super(type, nativeEvent, datum, series);
|
|
this.sizeKey = series.properties.sizeKey;
|
|
}
|
|
};
|
|
var BubbleSeries = class extends CartesianSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
propertyKeys: {
|
|
...DEFAULT_CARTESIAN_DIRECTION_KEYS,
|
|
label: ["labelKey"],
|
|
size: ["sizeKey"]
|
|
},
|
|
propertyNames: {
|
|
...DEFAULT_CARTESIAN_DIRECTION_NAMES,
|
|
label: ["labelName"],
|
|
size: ["sizeName"]
|
|
},
|
|
categoryKey: void 0,
|
|
pickModes: [
|
|
2 /* AXIS_ALIGNED */,
|
|
1 /* NEAREST_NODE */,
|
|
0 /* EXACT_SHAPE_MATCH */
|
|
],
|
|
pathsPerSeries: [],
|
|
datumSelectionGarbageCollection: false,
|
|
animationResetFns: {
|
|
label: resetLabelFn,
|
|
datum: resetMarkerFn
|
|
},
|
|
usesPlacedLabels: true,
|
|
clipFocusBox: false
|
|
});
|
|
this.NodeEvent = BubbleScatterSeriesNodeEvent;
|
|
this.properties = new BubbleSeriesProperties();
|
|
this.dataAggregation = void 0;
|
|
this.sizeScale = new LinearScale();
|
|
this.placedLabelData = [];
|
|
}
|
|
get pickModeAxis() {
|
|
return "main-category";
|
|
}
|
|
get type() {
|
|
return super.type;
|
|
}
|
|
async processData(dataController) {
|
|
if (this.data == null || !this.visible)
|
|
return;
|
|
const xScale = this.axes["x" /* X */]?.scale;
|
|
const yScale = this.axes["y" /* Y */]?.scale;
|
|
const { xScaleType, yScaleType } = this.getScaleInformation({ xScale, yScale });
|
|
const sizeScaleType = this.sizeScale.type;
|
|
const { xKey, yKey, sizeKey, xFilterKey, yFilterKey, sizeFilterKey, labelKey, marker } = this.properties;
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
const { dataModel, processedData } = await this.requestDataModel(dataController, this.data, {
|
|
props: [
|
|
valueProperty(xKey, xScaleType, { id: `xValue`, allowNullKey }),
|
|
valueProperty(yKey, yScaleType, { id: `yValue`, allowNullKey }),
|
|
...xFilterKey == null ? [] : [valueProperty(xFilterKey, xScaleType, { id: `xFilterValue` })],
|
|
...yFilterKey == null ? [] : [valueProperty(yFilterKey, yScaleType, { id: `yFilterValue` })],
|
|
...sizeFilterKey == null ? [] : [valueProperty(sizeFilterKey, sizeScaleType, { id: `sizeFilterValue` })],
|
|
...sizeKey ? [valueProperty(sizeKey, sizeScaleType, { id: `sizeValue` })] : [],
|
|
...labelKey ? [valueProperty(labelKey, "category", { id: `labelValue` })] : []
|
|
]
|
|
});
|
|
const sizeKeyIdx = sizeKey ? dataModel.resolveProcessedDataIndexById(this, `sizeValue`) : void 0;
|
|
const mutableMarkerDomain = marker.domain ? [marker.domain[0], marker.domain[1]] : void 0;
|
|
this.sizeScale.domain = mutableMarkerDomain ?? (sizeKeyIdx == null ? void 0 : processedData.domain.values[sizeKeyIdx]) ?? [];
|
|
this.dataAggregation = this.aggregateData(dataModel, processedData);
|
|
this.animationState.transition("updateData");
|
|
}
|
|
xCoordinateRange(xValue, pixelSize, index) {
|
|
const { properties, sizeScale } = this;
|
|
const { size, sizeKey } = properties;
|
|
const x = this.axes["x" /* X */].scale.convert(xValue);
|
|
const sizeValues = sizeKey == null ? void 0 : this.dataModel.resolveColumnById(this, `sizeValue`, this.processedData);
|
|
const sizeValue = sizeValues == null ? size : sizeScale.convert(sizeValues[index]);
|
|
const r = 0.5 * sizeValue * pixelSize;
|
|
return [x - r, x + r];
|
|
}
|
|
yCoordinateRange(yValues, pixelSize, index) {
|
|
const { properties, sizeScale } = this;
|
|
const { size, sizeKey } = properties;
|
|
const y = this.axes["y" /* Y */].scale.convert(yValues[0]);
|
|
const sizeValues = sizeKey == null ? void 0 : this.dataModel.resolveColumnById(this, `sizeValue`, this.processedData);
|
|
const sizeValue = sizeValues == null ? size : sizeScale.convert(sizeValues[index]);
|
|
const r = 0.5 * sizeValue * pixelSize;
|
|
return [y - r, y + r];
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { dataModel, processedData } = this;
|
|
if (!processedData || !dataModel)
|
|
return { domain: [] };
|
|
const dataValues = {
|
|
["x" /* X */]: "xValue",
|
|
["y" /* Y */]: "yValue"
|
|
};
|
|
const id = dataValues[direction];
|
|
const dataDef = dataModel.resolveProcessedDataDefById(this, id);
|
|
const domainData = dataModel.getDomain(this, id, "value", processedData);
|
|
if (dataDef?.def.type === "value" && dataDef?.def.valueType === "category") {
|
|
return { domain: domainData.domain };
|
|
}
|
|
const crossDirection = direction === "x" /* X */ ? "y" /* Y */ : "x" /* X */;
|
|
const crossId = dataValues[crossDirection];
|
|
const ext = this.domainForClippedRange(direction, [id], crossId);
|
|
return { domain: fixNumericExtent(extent(ext)) };
|
|
}
|
|
getSeriesRange(_direction, visibleRange) {
|
|
return this.domainForVisibleRange("y" /* Y */, ["yValue"], "xValue", visibleRange);
|
|
}
|
|
getVisibleItems(xVisibleRange, yVisibleRange, minVisibleItems) {
|
|
const { dataAggregation, axes } = this;
|
|
const xAxis = axes["x" /* X */];
|
|
const yAxis = axes["y" /* Y */];
|
|
if (dataAggregation == null || xAxis == null || yAxis == null) {
|
|
return this.countVisibleItems("xValue", ["yValue"], xVisibleRange, yVisibleRange, minVisibleItems);
|
|
}
|
|
const aggregationOptions = this.aggregationOptions(xAxis, yAxis, xVisibleRange, yVisibleRange ?? [0, 1]);
|
|
return computeBubbleAggregationCount(0, dataAggregation, aggregationOptions);
|
|
}
|
|
aggregateData(dataModel, processedData) {
|
|
if (processedData.type === "grouped")
|
|
return;
|
|
if (processedData.input.count <= this.properties.maxRenderedItems)
|
|
return;
|
|
const xAxis = this.axes["x" /* X */];
|
|
const yAxis = this.axes["y" /* Y */];
|
|
if (xAxis == null || yAxis == null)
|
|
return;
|
|
const xScale = xAxis.scale;
|
|
const yScale = yAxis.scale;
|
|
if (!ContinuousScale.is(xScale) || !ContinuousScale.is(yScale))
|
|
return;
|
|
return aggregateBubbleDataFromDataModel(
|
|
xScale.type,
|
|
yScale.type,
|
|
dataModel,
|
|
processedData,
|
|
this.sizeScale,
|
|
this.properties.sizeKey != null,
|
|
this
|
|
);
|
|
}
|
|
aggregationOptions(xAxis, yAxis, xVisibleRange = xAxis.visibleRange, yVisibleRange = yAxis.visibleRange) {
|
|
const { processedData, dataModel } = this;
|
|
const { sizeKey } = this.properties;
|
|
const [markerSize, markerMaxSize] = this.getSizeRange();
|
|
const xRange = Math.abs(xAxis.range[1] - xAxis.range[0]);
|
|
const yRange = Math.abs(yAxis.range[1] - yAxis.range[0]);
|
|
const minSize = Math.max(markerSize, 1);
|
|
const maxSize = sizeKey ? Math.max(markerMaxSize, 1) : minSize;
|
|
const xScale = xAxis.scale;
|
|
const yScale = yAxis.scale;
|
|
if (processedData != null && dataModel != null) {
|
|
if (ContinuousScale.is(xScale)) {
|
|
xVisibleRange = rescaleVisibleRange(
|
|
xVisibleRange,
|
|
xScale.domain.map(dateToNumber),
|
|
dataModel.getDomain(this, `xValue`, "value", processedData).domain.map(dateToNumber)
|
|
);
|
|
}
|
|
if (ContinuousScale.is(yScale)) {
|
|
yVisibleRange = rescaleVisibleRange(
|
|
yVisibleRange,
|
|
yScale.domain.map(dateToNumber),
|
|
dataModel.getDomain(this, `yValue`, "value", processedData).domain.map(dateToNumber)
|
|
);
|
|
}
|
|
}
|
|
return { xRange, yRange, minSize, maxSize, xVisibleRange, yVisibleRange };
|
|
}
|
|
/**
|
|
* Creates and returns a context object that caches expensive property lookups
|
|
* and scale conversions. Called once per createNodeData() invocation.
|
|
*/
|
|
createNodeDatumContext(xAxis, yAxis) {
|
|
const { dataModel, processedData, sizeScale, visible } = this;
|
|
if (!dataModel || !processedData)
|
|
return void 0;
|
|
const rawData = processedData.dataSources.get(this.id)?.data;
|
|
if (rawData == null)
|
|
return void 0;
|
|
const {
|
|
xKey,
|
|
yKey,
|
|
sizeKey,
|
|
xFilterKey,
|
|
yFilterKey,
|
|
sizeFilterKey,
|
|
labelKey,
|
|
xName,
|
|
yName,
|
|
sizeName,
|
|
labelName,
|
|
label,
|
|
legendItemName,
|
|
marker
|
|
} = this.properties;
|
|
const xScale = xAxis.scale;
|
|
const yScale = yAxis.scale;
|
|
const canIncrementallyUpdate = processedData.changeDescription != null && this.contextNodeData?.nodeData != null;
|
|
let labelTextDomain;
|
|
if (labelKey) {
|
|
labelTextDomain = [];
|
|
} else if (sizeKey) {
|
|
labelTextDomain = dataModel.getDomain(this, `sizeValue`, "value", processedData).domain;
|
|
} else {
|
|
labelTextDomain = [];
|
|
}
|
|
const xDataValues = dataModel.resolveColumnById(this, `xValue`, processedData);
|
|
return {
|
|
// Axes (from template method parameters)
|
|
xAxis,
|
|
yAxis,
|
|
// Data arrays
|
|
rawData,
|
|
xValues: xDataValues,
|
|
// Base interface field
|
|
xDataValues,
|
|
// BubbleSeries-specific alias
|
|
yDataValues: dataModel.resolveColumnById(this, `yValue`, processedData),
|
|
sizeDataValues: sizeKey == null ? void 0 : dataModel.resolveColumnById(this, `sizeValue`, processedData),
|
|
labelDataValues: labelKey == null ? void 0 : dataModel.resolveColumnById(this, `labelValue`, processedData),
|
|
xFilterDataValues: xFilterKey == null ? void 0 : dataModel.resolveColumnById(this, `xFilterValue`, processedData),
|
|
yFilterDataValues: yFilterKey == null ? void 0 : dataModel.resolveColumnById(this, `yFilterValue`, processedData),
|
|
sizeFilterDataValues: sizeFilterKey == null ? void 0 : dataModel.resolveColumnById(this, `sizeFilterValue`, processedData),
|
|
// Scales
|
|
xScale,
|
|
yScale,
|
|
sizeScale,
|
|
// Computed positioning
|
|
xOffset: (xScale.bandwidth ?? 0) / 2,
|
|
yOffset: (yScale.bandwidth ?? 0) / 2,
|
|
// Property lookups
|
|
xKey,
|
|
yKey,
|
|
sizeKey,
|
|
labelKey,
|
|
xName,
|
|
yName,
|
|
sizeName,
|
|
labelName,
|
|
legendItemName,
|
|
// Label properties
|
|
labelsEnabled: label.enabled,
|
|
labelPlacement: label.placement,
|
|
labelAnchor: Marker.anchor(marker.shape),
|
|
labelTextDomain,
|
|
labelPadding: expandLabelPadding(label),
|
|
labelTextMeasurer: cachedTextMeasurer(label),
|
|
label,
|
|
// Other state
|
|
animationEnabled: !this.ctx.animationManager.isSkipped(),
|
|
visible,
|
|
// Incremental update support
|
|
canIncrementallyUpdate,
|
|
nodes: canIncrementallyUpdate ? this.contextNodeData.nodeData : [],
|
|
nodeIndex: 0
|
|
};
|
|
}
|
|
// ============================================================================
|
|
// Template Method Hooks
|
|
// ============================================================================
|
|
/**
|
|
* Populates the node data array by iterating over visible data.
|
|
* Strategy selection happens inside: simple or aggregation path.
|
|
*/
|
|
populateNodeData(ctx) {
|
|
this.sizeScale.range = this.getSizeRange();
|
|
const scratch = {
|
|
datum: void 0,
|
|
xDatum: void 0,
|
|
yDatum: void 0,
|
|
sizeValue: void 0,
|
|
x: 0,
|
|
y: 0,
|
|
selected: void 0,
|
|
nodeLabel: { text: "", width: 0, height: 0 },
|
|
markerSize: 0,
|
|
count: 1,
|
|
dilation: 1,
|
|
area: 0
|
|
};
|
|
const { dataAggregation } = this;
|
|
if (dataAggregation == null) {
|
|
this.createNodeDataSimple(ctx, scratch);
|
|
} else {
|
|
this.createNodeDataWithAggregation(ctx, scratch, ctx.xAxis, ctx.yAxis, dataAggregation);
|
|
}
|
|
}
|
|
/**
|
|
* Initializes the result context object with default values.
|
|
* Called before populate phase to allow early return for invisible series.
|
|
*/
|
|
initializeResult(ctx) {
|
|
const { marker } = this.properties;
|
|
return {
|
|
itemId: ctx.yKey,
|
|
nodeData: ctx.nodes,
|
|
labelData: ctx.labelsEnabled ? ctx.nodes : [],
|
|
scales: this.calculateScaling(),
|
|
visible: this.visible || ctx.animationEnabled,
|
|
styles: getMarkerStyles(this, this.properties, marker)
|
|
};
|
|
}
|
|
/**
|
|
* Validates datum state and upserts node - centralizes duplicated upsert pattern.
|
|
*/
|
|
upsertBubbleNodeDatum(ctx, scratch, datumIndex) {
|
|
if (!this.prepareNodeDatumState(ctx, scratch, datumIndex))
|
|
return;
|
|
upsertNodeDatum(
|
|
ctx,
|
|
{ scratch, datumIndex },
|
|
(c, p) => {
|
|
const node = this.createSkeletonNodeDatum(c, p.scratch, p.datumIndex);
|
|
this.updateNodeDatum(c, node, p.scratch, p.datumIndex);
|
|
return node;
|
|
},
|
|
(c, n, p) => this.updateNodeDatum(c, n, p.scratch, p.datumIndex)
|
|
);
|
|
}
|
|
/**
|
|
* Simple iteration path for ungrouped data without aggregation.
|
|
*/
|
|
createNodeDataSimple(ctx, scratch) {
|
|
const dataLength = ctx.rawData.length;
|
|
for (let datumIndex = 0; datumIndex < dataLength; datumIndex++) {
|
|
scratch.count = 1;
|
|
scratch.dilation = 1;
|
|
scratch.area = 0;
|
|
this.upsertBubbleNodeDatum(ctx, scratch, datumIndex);
|
|
}
|
|
}
|
|
/**
|
|
* Aggregation path for large datasets using quadtree-based 2D spatial aggregation.
|
|
*/
|
|
createNodeDataWithAggregation(ctx, scratch, xAxis, yAxis, dataAggregation) {
|
|
const { maxRenderedItems } = this.properties;
|
|
const aggregationOptions = this.aggregationOptions(xAxis, yAxis);
|
|
const aggregationDilation = computeBubbleAggregationDilation(
|
|
dataAggregation,
|
|
aggregationOptions,
|
|
maxRenderedItems
|
|
);
|
|
const { groupedAggregation, singleDatumIndices } = computeBubbleAggregationData(
|
|
aggregationDilation,
|
|
dataAggregation,
|
|
aggregationOptions
|
|
);
|
|
for (const { datumIndex, count, dilation, area: area2 } of groupedAggregation) {
|
|
scratch.count = count;
|
|
scratch.dilation = dilation;
|
|
scratch.area = area2;
|
|
this.upsertBubbleNodeDatum(ctx, scratch, datumIndex);
|
|
}
|
|
for (const datumIndex of singleDatumIndices) {
|
|
scratch.count = 1;
|
|
scratch.dilation = 1;
|
|
scratch.area = 0;
|
|
this.upsertBubbleNodeDatum(ctx, scratch, datumIndex);
|
|
}
|
|
}
|
|
/**
|
|
* Validates and prepares state needed for node creation/update.
|
|
* Returns undefined if datum should be skipped (invalid data).
|
|
*/
|
|
prepareNodeDatumState(ctx, scratch, datumIndex) {
|
|
const datum = ctx.rawData[datumIndex];
|
|
const xDatum = ctx.xDataValues[datumIndex];
|
|
const yDatum = ctx.yDataValues[datumIndex];
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
if ((xDatum === void 0 || yDatum === void 0) && !allowNullKeys)
|
|
return void 0;
|
|
const sizeValue = ctx.sizeDataValues?.[datumIndex];
|
|
const x = ctx.xScale.convert(xDatum) + ctx.xOffset;
|
|
const y = ctx.yScale.convert(yDatum) + ctx.yOffset;
|
|
if (!Number.isFinite(x) || !Number.isFinite(y))
|
|
return void 0;
|
|
let selected;
|
|
if (ctx.xFilterDataValues != null && ctx.yFilterDataValues != null) {
|
|
selected = ctx.xFilterDataValues[datumIndex] === xDatum && ctx.yFilterDataValues[datumIndex] === yDatum;
|
|
if (ctx.sizeFilterDataValues != null) {
|
|
selected && (selected = ctx.sizeFilterDataValues[datumIndex] === sizeValue);
|
|
}
|
|
}
|
|
let nodeLabel;
|
|
if (ctx.labelsEnabled) {
|
|
nodeLabel = this.computeLabel(ctx, datum, yDatum, sizeValue, datumIndex);
|
|
} else {
|
|
nodeLabel = { text: "", width: 0, height: 0 };
|
|
}
|
|
const markerSize = sizeValue == null ? ctx.sizeScale.range[0] : ctx.sizeScale.convert(sizeValue);
|
|
scratch.datum = datum;
|
|
scratch.xDatum = xDatum;
|
|
scratch.yDatum = yDatum;
|
|
scratch.sizeValue = sizeValue;
|
|
scratch.x = x;
|
|
scratch.y = y;
|
|
scratch.selected = selected;
|
|
scratch.nodeLabel = nodeLabel;
|
|
scratch.markerSize = markerSize;
|
|
return scratch;
|
|
}
|
|
/**
|
|
* Computes label text and measurements for a datum.
|
|
* Separated to enable skipping when labels are disabled.
|
|
*/
|
|
computeLabel(ctx, datum, yDatum, sizeValue, datumIndex) {
|
|
let labelTextValue;
|
|
let labelTextKey;
|
|
let labelTextProperty;
|
|
if (ctx.labelKey && ctx.labelDataValues) {
|
|
labelTextValue = ctx.labelDataValues[datumIndex];
|
|
labelTextKey = ctx.labelKey;
|
|
labelTextProperty = "label";
|
|
} else if (ctx.sizeKey) {
|
|
labelTextValue = sizeValue;
|
|
labelTextKey = ctx.sizeKey;
|
|
labelTextProperty = "size";
|
|
} else {
|
|
labelTextValue = yDatum;
|
|
labelTextKey = ctx.yKey;
|
|
labelTextProperty = "y";
|
|
}
|
|
const labelText = this.getLabelText(
|
|
labelTextValue,
|
|
datum,
|
|
labelTextKey,
|
|
labelTextProperty,
|
|
ctx.labelTextDomain,
|
|
ctx.label,
|
|
{
|
|
value: labelTextValue,
|
|
datum,
|
|
xKey: ctx.xKey,
|
|
yKey: ctx.yKey,
|
|
// sizeKey may be undefined for ScatterSeries (which extends BubbleSeries)
|
|
sizeKey: ctx.sizeKey,
|
|
labelKey: ctx.labelKey,
|
|
xName: ctx.xName,
|
|
yName: ctx.yName,
|
|
sizeName: ctx.sizeName,
|
|
labelName: ctx.labelName,
|
|
legendItemName: ctx.legendItemName
|
|
}
|
|
);
|
|
let { width: width2, height: height2 } = isArray(labelText) ? measureTextSegments(labelText, ctx.label) : ctx.labelTextMeasurer.measureLines(String(labelText));
|
|
width2 += ctx.labelPadding.left + ctx.labelPadding.right;
|
|
height2 += ctx.labelPadding.bottom + ctx.labelPadding.top;
|
|
return { text: labelText, width: width2, height: height2 };
|
|
}
|
|
/**
|
|
* Creates a minimal skeleton node - actual values set by updateNodeDatum.
|
|
*/
|
|
createSkeletonNodeDatum(ctx, _scratch, _datumIndex) {
|
|
return {
|
|
series: this,
|
|
yKey: ctx.yKey,
|
|
xKey: ctx.xKey,
|
|
datum: void 0,
|
|
datumIndex: 0,
|
|
xValue: void 0,
|
|
yValue: void 0,
|
|
sizeValue: void 0,
|
|
capDefaults: { lengthRatioMultiplier: this.properties.marker.getDiameter(), lengthMax: Infinity },
|
|
point: { x: 0, y: 0, size: 0 },
|
|
midPoint: { x: 0, y: 0 },
|
|
label: { text: "", width: 0, height: 0 },
|
|
anchor: ctx.labelAnchor,
|
|
placement: ctx.labelPlacement,
|
|
count: 1,
|
|
dilation: 1,
|
|
area: 0,
|
|
selected: void 0
|
|
};
|
|
}
|
|
/**
|
|
* Updates node properties in-place.
|
|
* Shared by both create (skeleton + update) and incremental update paths.
|
|
*/
|
|
updateNodeDatum(ctx, node, scratch, datumIndex) {
|
|
const mutableNode = node;
|
|
const { x, y, markerSize, dilation } = scratch;
|
|
mutableNode.datum = scratch.datum;
|
|
mutableNode.datumIndex = datumIndex;
|
|
mutableNode.xValue = scratch.xDatum;
|
|
mutableNode.yValue = scratch.yDatum;
|
|
mutableNode.sizeValue = scratch.sizeValue;
|
|
mutableNode.selected = scratch.selected;
|
|
mutableNode.count = scratch.count;
|
|
mutableNode.dilation = scratch.dilation;
|
|
mutableNode.area = scratch.area;
|
|
mutableNode.label = scratch.nodeLabel;
|
|
mutableNode.anchor = ctx.labelAnchor;
|
|
mutableNode.placement = ctx.labelPlacement;
|
|
const mutablePoint = mutableNode.point;
|
|
mutablePoint.x = x;
|
|
mutablePoint.y = y;
|
|
mutablePoint.size = Math.sqrt(dilation) * markerSize;
|
|
const mutableMidPoint = mutableNode.midPoint;
|
|
mutableMidPoint.x = x;
|
|
mutableMidPoint.y = y;
|
|
}
|
|
isPathOrSelectionDirty() {
|
|
return this.properties.marker.isDirty();
|
|
}
|
|
getLabelData() {
|
|
if (!this.isLabelEnabled())
|
|
return [];
|
|
return this.contextNodeData?.labelData ?? [];
|
|
}
|
|
updateDatumSelection(opts) {
|
|
const { nodeData, datumSelection } = opts;
|
|
if (this.properties.marker.isDirty()) {
|
|
datumSelection.clear();
|
|
datumSelection.cleanup();
|
|
}
|
|
if (!processedDataIsAnimatable(this.processedData)) {
|
|
return datumSelection.update(nodeData);
|
|
}
|
|
const { sizeKey } = this.properties;
|
|
let getId;
|
|
if (sizeKey) {
|
|
getId = (datum) => createDatumId(datum.xValue, datum.yValue, datum.sizeValue, toPlainText(datum.label.text));
|
|
}
|
|
return datumSelection.update(nodeData, void 0, getId);
|
|
}
|
|
updateDatumStyles(opts) {
|
|
const { datumSelection, isHighlight } = opts;
|
|
const { xKey, yKey, sizeKey, labelKey, marker } = this.properties;
|
|
const params = { xKey, yKey, sizeKey, labelKey };
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
datumSelection.each((node, datum) => {
|
|
if (!datumSelection.isGarbage(node)) {
|
|
const highlightState = this.getHighlightState(highlightedDatum, opts.isHighlight, datum.datumIndex);
|
|
const stylerStyle = this.getStyle(highlightState);
|
|
datum.style = this.getMarkerStyle(
|
|
marker,
|
|
datum,
|
|
params,
|
|
{
|
|
isHighlight,
|
|
highlightState,
|
|
resolveMarkerSubPath: []
|
|
},
|
|
stylerStyle
|
|
);
|
|
}
|
|
});
|
|
}
|
|
updateDatumNodes(opts) {
|
|
const { contextNodeData } = this;
|
|
if (!contextNodeData)
|
|
return;
|
|
const { datumSelection, isHighlight, drawingMode } = opts;
|
|
this.sizeScale.range = this.getSizeRange();
|
|
const fillBBox = this.getShapeFillBBox();
|
|
const aggregated = this.dataAggregation != null;
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
datumSelection.each((node, datum, index) => {
|
|
const {
|
|
point: { size },
|
|
count,
|
|
area: area2,
|
|
dilation
|
|
} = datum;
|
|
const state = this.getHighlightState(highlightedDatum, isHighlight, datum.datumIndex);
|
|
const style2 = { ...datum.style ?? contextNodeData.styles[state] };
|
|
style2.size = size;
|
|
if (dilation > 1) {
|
|
const fillOpacity = style2.fillOpacity ?? 0;
|
|
const opacityScale = 0.269669 + 683e-6 * count + -37.534348 * area2 + 4449e-6 * count * area2 + -0 * count ** 2 + 44.428603 * area2 ** 2;
|
|
style2.fillOpacity = clamp(fillOpacity / dilation, fillOpacity / 0.1 * opacityScale, 1);
|
|
}
|
|
this.applyMarkerStyle(style2, node, datum.point, fillBBox, { selected: datum.selected });
|
|
node.drawingMode = this.resolveMarkerDrawingModeForState(drawingMode, style2);
|
|
node.zIndex = aggregated ? [-count, index] : 0;
|
|
});
|
|
if (!isHighlight) {
|
|
this.properties.marker.markClean();
|
|
}
|
|
}
|
|
updatePlacedLabelData(labelData) {
|
|
this.placedLabelData = labelData;
|
|
this.labelSelection.update(
|
|
labelData.map((v) => ({
|
|
...v.datum,
|
|
point: {
|
|
x: v.x,
|
|
y: v.y,
|
|
size: v.datum.point.size
|
|
}
|
|
})),
|
|
(text2) => {
|
|
text2.pointerEvents = 1 /* None */;
|
|
}
|
|
);
|
|
this.updateLabelNodes({ labelSelection: this.labelSelection });
|
|
this.updateHighlightLabelSelection();
|
|
}
|
|
updateHighlightLabelSelection() {
|
|
const highlightedDatum = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightItem = this.isSeriesHighlighted(highlightedDatum) && highlightedDatum?.datum ? highlightedDatum : void 0;
|
|
const highlightLabelData = highlightItem == null ? [] : this.placedLabelData.filter((label) => label.datum.datumIndex === highlightItem.datumIndex).map((label) => ({
|
|
...label.datum,
|
|
point: {
|
|
x: label.x,
|
|
y: label.y,
|
|
size: label.datum.point.size
|
|
}
|
|
}));
|
|
this.highlightLabelSelection = this.updateLabelSelection({
|
|
labelData: highlightLabelData,
|
|
labelSelection: this.highlightLabelSelection
|
|
}) ?? this.highlightLabelSelection;
|
|
this.highlightLabelGroup.visible = highlightLabelData.length > 0;
|
|
this.highlightLabelGroup.batchedUpdate(() => {
|
|
this.updateLabelNodes({ labelSelection: this.highlightLabelSelection, isHighlight: true });
|
|
});
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const { isHighlight = false } = opts;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const params = this.makeLabelFormatterParams();
|
|
opts.labelSelection.each((text2, datum) => {
|
|
const style2 = getLabelStyles(this, datum, params, this.properties.label, isHighlight, activeHighlight);
|
|
text2.text = datum.label.text;
|
|
text2.fill = style2.color;
|
|
text2.x = datum.point?.x ?? 0;
|
|
text2.y = datum.point?.y ?? 0;
|
|
text2.fontStyle = style2.fontStyle;
|
|
text2.fontWeight = style2.fontWeight;
|
|
text2.fontSize = style2.fontSize;
|
|
text2.fontFamily = style2.fontFamily;
|
|
text2.textBaseline = "top";
|
|
text2.fillOpacity = this.getHighlightStyle(isHighlight, datum.datumIndex).opacity ?? 1;
|
|
text2.setBoxing(style2);
|
|
});
|
|
}
|
|
updateLabelSelection(opts) {
|
|
const { labelData, labelSelection } = opts;
|
|
return labelSelection.update(labelData, (text2) => {
|
|
text2.pointerEvents = 1 /* None */;
|
|
});
|
|
}
|
|
makeStylerParams(highlightStateEnum) {
|
|
const {
|
|
id: seriesId,
|
|
properties: {
|
|
size,
|
|
maxSize,
|
|
shape,
|
|
fill,
|
|
fillOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
xKey,
|
|
yKey,
|
|
sizeKey,
|
|
labelKey
|
|
}
|
|
} = this;
|
|
const highlightState = toHighlightString(highlightStateEnum ?? 0 /* None */);
|
|
if (this.type === "bubble") {
|
|
return {
|
|
highlightState,
|
|
size,
|
|
maxSize,
|
|
shape,
|
|
fill,
|
|
fillOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
seriesId,
|
|
sizeKey,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
xKey,
|
|
yKey,
|
|
labelKey
|
|
};
|
|
} else if (this.type === "scatter") {
|
|
return {
|
|
highlightState,
|
|
size,
|
|
shape,
|
|
fill,
|
|
fillOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
seriesId,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
xKey,
|
|
yKey,
|
|
labelKey
|
|
};
|
|
} else {
|
|
return this.type;
|
|
}
|
|
}
|
|
makeLabelFormatterParams() {
|
|
const { xKey, xName, yKey, yName, sizeKey, sizeName, labelKey, labelName, legendItemName } = this.properties;
|
|
return {
|
|
xKey,
|
|
xName,
|
|
yKey,
|
|
yName,
|
|
sizeKey,
|
|
sizeName,
|
|
labelKey,
|
|
labelName,
|
|
legendItemName
|
|
};
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, axes, properties, ctx } = this;
|
|
const { formatManager } = ctx;
|
|
const {
|
|
xKey,
|
|
xName,
|
|
yKey,
|
|
yName,
|
|
sizeKey,
|
|
sizeName,
|
|
labelKey,
|
|
labelName,
|
|
title,
|
|
tooltip,
|
|
marker,
|
|
legendItemName
|
|
} = properties;
|
|
const xAxis = axes["x" /* X */];
|
|
const yAxis = axes["y" /* Y */];
|
|
if (!dataModel || !processedData || !xAxis || !yAxis)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data?.[datumIndex];
|
|
const xValue = dataModel.resolveColumnById(this, `xValue`, processedData)[datumIndex];
|
|
const yValue = dataModel.resolveColumnById(this, `yValue`, processedData)[datumIndex];
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
if (xValue === void 0 && !allowNullKeys)
|
|
return;
|
|
const data = [];
|
|
if (this.isLabelEnabled() && labelKey != null) {
|
|
const value = dataModel.resolveColumnById(this, `labelValue`, processedData)[datumIndex];
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "category",
|
|
value,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key: labelKey,
|
|
source: "tooltip",
|
|
property: "label",
|
|
domain: [],
|
|
boundSeries: this.getFormatterContext("label")
|
|
});
|
|
data.push({ label: labelName, fallbackLabel: labelKey, value: content ?? formatValue(value) });
|
|
}
|
|
data.push(
|
|
{
|
|
label: xName,
|
|
fallbackLabel: xKey,
|
|
value: this.getAxisValueText(xAxis, "tooltip", xValue, datum, xKey, legendItemName, allowNullKeys),
|
|
missing: isTooltipValueMissing(xValue, allowNullKeys)
|
|
},
|
|
{
|
|
label: yName,
|
|
fallbackLabel: yKey,
|
|
value: this.getAxisValueText(yAxis, "tooltip", yValue, datum, yKey, legendItemName, allowNullKeys),
|
|
missing: isTooltipValueMissing(yValue, allowNullKeys)
|
|
}
|
|
);
|
|
if (sizeKey != null) {
|
|
const value = dataModel.resolveColumnById(this, `sizeValue`, processedData)[datumIndex];
|
|
if (value != null) {
|
|
const domain = dataModel.getDomain(this, `sizeValue`, "value", processedData).domain;
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key: sizeKey,
|
|
source: "tooltip",
|
|
property: "size",
|
|
boundSeries: this.getFormatterContext("size"),
|
|
domain,
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
});
|
|
data.push({ label: sizeName, fallbackLabel: sizeKey, value: content ?? formatValue(value) });
|
|
}
|
|
}
|
|
const activeStyle = this.getMarkerStyle(
|
|
marker,
|
|
{ datum, datumIndex },
|
|
{ xKey, yKey, sizeKey, labelKey },
|
|
{ resolveMarkerSubPath: [] }
|
|
);
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
title,
|
|
symbol: this.legendItemSymbol(),
|
|
data
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: yKey,
|
|
xKey,
|
|
xName,
|
|
yKey,
|
|
yName,
|
|
sizeKey,
|
|
sizeName,
|
|
labelKey,
|
|
labelName,
|
|
legendItemName,
|
|
...activeStyle,
|
|
...this.getModuleTooltipParams()
|
|
}
|
|
);
|
|
}
|
|
legendItemSymbol() {
|
|
const style2 = this.getStyle();
|
|
const marker = this.getMarkerStyle(
|
|
this.properties.marker,
|
|
{},
|
|
void 0,
|
|
{
|
|
isHighlight: false,
|
|
checkForHighlight: false,
|
|
resolveMarkerSubPath: []
|
|
},
|
|
style2
|
|
);
|
|
return {
|
|
marker
|
|
};
|
|
}
|
|
getLegendData() {
|
|
const {
|
|
id: seriesId,
|
|
ctx: { legendManager },
|
|
visible
|
|
} = this;
|
|
const { yKey: itemId, yName, legendItemName, title, showInLegend } = this.properties;
|
|
return [
|
|
{
|
|
legendType: "category",
|
|
id: seriesId,
|
|
itemId,
|
|
seriesId,
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId, itemId }),
|
|
label: {
|
|
text: legendItemName ?? title ?? yName ?? itemId
|
|
},
|
|
symbol: this.legendItemSymbol(),
|
|
legendItemName,
|
|
hideInLegend: !showInLegend
|
|
}
|
|
];
|
|
}
|
|
animateEmptyUpdateReady({ datumSelection, labelSelection }) {
|
|
markerScaleInAnimation(this, this.ctx.animationManager, datumSelection);
|
|
seriesLabelFadeInAnimation(this, "labels", this.ctx.animationManager, labelSelection);
|
|
}
|
|
resetDatumAnimation(data) {
|
|
resetMarkerSelectionsDirect([data.datumSelection]);
|
|
}
|
|
isLabelEnabled() {
|
|
return this.properties.label.enabled;
|
|
}
|
|
nodeFactory() {
|
|
return new Marker();
|
|
}
|
|
getStyle(highlightState) {
|
|
const { properties } = this;
|
|
let stylerResult = {};
|
|
if (properties.styler) {
|
|
const stylerParams = this.makeStylerParams(highlightState);
|
|
const cbResult = this.cachedCallWithContext(properties.styler, stylerParams) ?? {};
|
|
const resolved = this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`],
|
|
cbResult,
|
|
{ pick: false }
|
|
);
|
|
stylerResult = resolved ?? {};
|
|
}
|
|
return {
|
|
fill: stylerResult.fill ?? properties.fill,
|
|
fillOpacity: stylerResult.fillOpacity ?? properties.fillOpacity,
|
|
lineDash: stylerResult.lineDash ?? properties.lineDash,
|
|
lineDashOffset: stylerResult.lineDashOffset ?? properties.lineDashOffset,
|
|
shape: stylerResult.shape ?? properties.shape,
|
|
size: stylerResult.size ?? properties.size,
|
|
maxSize: stylerResult.maxSize ?? properties.maxSize,
|
|
stroke: stylerResult.stroke ?? properties.stroke,
|
|
strokeOpacity: stylerResult.strokeOpacity ?? properties.strokeOpacity,
|
|
strokeWidth: stylerResult.strokeWidth ?? properties.strokeWidth
|
|
};
|
|
}
|
|
getSizeRange() {
|
|
const { size, maxSize } = this.getStyle();
|
|
return [size, maxSize];
|
|
}
|
|
getFormattedMarkerStyle(datum) {
|
|
const { xKey, yKey, sizeKey, labelKey, marker } = this.properties;
|
|
return this.getMarkerStyle(marker, datum, { xKey, yKey, sizeKey, labelKey }, { resolveMarkerSubPath: [] });
|
|
}
|
|
computeFocusBounds(opts) {
|
|
return computeMarkerFocusBounds(this, opts);
|
|
}
|
|
hasItemStylers() {
|
|
const { styler, itemStyler, marker, label } = this.properties;
|
|
return !!(styler ?? itemStyler ?? marker.itemStyler ?? label.itemStyler);
|
|
}
|
|
initQuadTree(quadtree) {
|
|
addHitTestersToQuadtree(quadtree, this.datumNodesIter());
|
|
}
|
|
pickNodeDataClosestDatum(point) {
|
|
return findQuadtreeMatch(this, point);
|
|
}
|
|
};
|
|
BubbleSeries.className = "BubbleSeries";
|
|
BubbleSeries.type = "bubble";
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/bubbleSeriesModule.ts
|
|
var themeTemplate3 = {
|
|
series: {
|
|
shape: "circle",
|
|
size: 7,
|
|
maxSize: 30,
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: { $palette: "stroke" },
|
|
fillOpacity: 0.8,
|
|
maxRenderedItems: 2e3,
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" }
|
|
},
|
|
tooltip: {
|
|
range: {
|
|
$if: [
|
|
{ $eq: [{ $path: ["/tooltip/range", "nearest"] }, "area"] },
|
|
"nearest",
|
|
{ $path: ["/tooltip/range", "nearest"] }
|
|
]
|
|
},
|
|
position: {
|
|
anchorTo: { $path: ["/tooltip/position/anchorTo", "node"] }
|
|
}
|
|
},
|
|
highlight: MULTI_SERIES_HIGHLIGHT_STYLE
|
|
}
|
|
};
|
|
var BubbleSeriesModule = {
|
|
type: "series",
|
|
name: "bubble",
|
|
chartType: "cartesian",
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: bubbleSeriesOptionsDef,
|
|
predictAxis: predictCartesianAxis,
|
|
defaultAxes: {
|
|
x: {
|
|
type: "number" /* NUMBER */,
|
|
position: "bottom" /* BOTTOM */
|
|
},
|
|
y: {
|
|
type: "number" /* NUMBER */,
|
|
position: "left" /* LEFT */
|
|
}
|
|
},
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
themeTemplate: themeTemplate3,
|
|
create: (ctx) => new BubbleSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/histogramSeriesProperties.ts
|
|
var HistogramSeriesProperties = class extends CartesianSeriesProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fillOpacity = 1;
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.cornerRadius = 0;
|
|
this.areaPlot = false;
|
|
this.aggregation = "sum";
|
|
this.shadow = new DropShadow();
|
|
this.label = new Label();
|
|
this.tooltip = makeSeriesTooltip();
|
|
}
|
|
getStyle() {
|
|
const { fill, fillOpacity, stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset, cornerRadius } = this;
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
cornerRadius,
|
|
opacity: 1
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "xKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "yKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "xName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "yName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "areaPlot", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "bins", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "aggregation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "binCount", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "shadow", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HistogramSeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/histogramSeries.ts
|
|
var defaultBinCount = 10;
|
|
var HistogramSeries = class extends CartesianSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
propertyKeys: DEFAULT_CARTESIAN_DIRECTION_KEYS,
|
|
propertyNames: DEFAULT_CARTESIAN_DIRECTION_NAMES,
|
|
categoryKey: void 0,
|
|
pickModes: [1 /* NEAREST_NODE */, 0 /* EXACT_SHAPE_MATCH */],
|
|
datumSelectionGarbageCollection: true,
|
|
animationAlwaysPopulateNodeData: true,
|
|
alwaysClip: true,
|
|
animationResetFns: {
|
|
datum: resetBarSelectionsFn,
|
|
label: resetLabelFn
|
|
}
|
|
});
|
|
this.properties = new HistogramSeriesProperties();
|
|
this.calculatedBins = [];
|
|
}
|
|
get hasData() {
|
|
return this.calculatedBins.length > 0;
|
|
}
|
|
// During processData phase, used to unify different ways of the user specifying
|
|
// the bins. Returns bins in format[[min1, max1], [min2, max2], ... ].
|
|
deriveBins(xDomain) {
|
|
const binStarts = createTicks(xDomain[0], xDomain[1], defaultBinCount).ticks;
|
|
const binSize = tickStep(xDomain[0], xDomain[1], defaultBinCount);
|
|
const [firstBinEnd] = binStarts;
|
|
const expandStartToBin = (n) => [n, n + binSize];
|
|
return [[firstBinEnd - binSize, firstBinEnd], ...binStarts.map(expandStartToBin)];
|
|
}
|
|
calculateNiceBins(domain, binCount) {
|
|
const startGuess = Math.floor(domain[0]);
|
|
const stop = domain[1];
|
|
const segments = binCount || 1;
|
|
const { start: start2, binSize } = this.calculateNiceStart(startGuess, stop, segments);
|
|
return this.getBins(start2, stop, binSize, segments);
|
|
}
|
|
getBins(start2, stop, step, count) {
|
|
const bins = [];
|
|
const precision = this.calculatePrecision(step);
|
|
for (let i = 0; i < count; i++) {
|
|
const a = Math.round((start2 + i * step) * precision) / precision;
|
|
let b = Math.round((start2 + (i + 1) * step) * precision) / precision;
|
|
if (i === count - 1) {
|
|
b = Math.max(b, stop);
|
|
}
|
|
bins[i] = [a, b];
|
|
}
|
|
return bins;
|
|
}
|
|
calculatePrecision(step) {
|
|
let precision = 10;
|
|
if (Number.isFinite(step) && step > 0) {
|
|
while (step < 1) {
|
|
precision *= 10;
|
|
step *= 10;
|
|
}
|
|
}
|
|
return precision;
|
|
}
|
|
calculateNiceStart(a, b, segments) {
|
|
const binSize = Math.abs(b - a) / segments;
|
|
const order = Math.floor(Math.log10(binSize));
|
|
const magnitude = Math.pow(10, order);
|
|
const start2 = Math.floor(a / magnitude) * magnitude;
|
|
return {
|
|
start: start2,
|
|
binSize
|
|
};
|
|
}
|
|
async processData(dataController) {
|
|
const { visible } = this;
|
|
const { xKey, yKey, areaPlot, aggregation } = this.properties;
|
|
const xScale = this.axes["x" /* X */]?.scale;
|
|
const yScale = this.axes["y" /* Y */]?.scale;
|
|
const { xScaleType, yScaleType } = this.getScaleInformation({ yScale, xScale });
|
|
const visibleProps = visible ? {} : { forceValue: 0 };
|
|
const props = [keyProperty(xKey, xScaleType), SORT_DOMAIN_GROUPS];
|
|
if (yKey) {
|
|
let aggProp = groupCount("groupAgg", { visible });
|
|
if (aggregation === "count") {
|
|
} else if (aggregation === "sum") {
|
|
aggProp = groupSum("groupAgg", { visible });
|
|
} else if (aggregation === "mean") {
|
|
aggProp = groupAverage("groupAgg", { visible });
|
|
}
|
|
if (areaPlot) {
|
|
aggProp = area("groupAgg", aggProp);
|
|
}
|
|
props.push(valueProperty(yKey, yScaleType, { invalidValue: void 0, ...visibleProps }), aggProp);
|
|
} else {
|
|
props.push(rowCountProperty("count"));
|
|
let aggProp = groupCount("groupAgg", { visible });
|
|
if (areaPlot) {
|
|
aggProp = area("groupAgg", aggProp);
|
|
}
|
|
props.push(aggProp);
|
|
}
|
|
let calculatedBinDomains = [];
|
|
const groupByFn = (dataSet) => {
|
|
const xExtent = fixNumericExtent(dataSet.domain.keys[0]);
|
|
if (xExtent.length === 0) {
|
|
dataSet.domain.groups = [];
|
|
return () => [];
|
|
}
|
|
const bins = isNumber(this.properties.binCount) ? this.calculateNiceBins(xExtent, this.properties.binCount) : this.properties.bins ?? this.deriveBins(xExtent);
|
|
const binCount = bins.length;
|
|
calculatedBinDomains = [...bins];
|
|
return (keys) => {
|
|
let xValue = keys[0];
|
|
if (isDate(xValue)) {
|
|
xValue = xValue.getTime();
|
|
}
|
|
if (!isNumber(xValue))
|
|
return [];
|
|
for (let i = 0; i < binCount; i++) {
|
|
const nextBin = bins[i];
|
|
if (xValue >= nextBin[0] && xValue < nextBin[1]) {
|
|
return nextBin;
|
|
}
|
|
if (i === binCount - 1 && xValue <= nextBin[1]) {
|
|
return nextBin;
|
|
}
|
|
}
|
|
return [];
|
|
};
|
|
};
|
|
const { dataModel, processedData: p } = await this.requestDataModel(dataController, this.data, {
|
|
props,
|
|
groupByFn
|
|
});
|
|
const processedData = p;
|
|
const groups = /* @__PURE__ */ new Map();
|
|
for (const [groupIndex, group] of processedData.groups.entries()) {
|
|
const domain = group.keys;
|
|
groups.set(createDatumId(...domain), { group, groupIndex });
|
|
}
|
|
this.calculatedBins = calculatedBinDomains.map((domain) => {
|
|
const g = groups.get(createDatumId(...domain));
|
|
if (g) {
|
|
const { group, groupIndex } = g;
|
|
const [[negativeAgg, positiveAgg] = [0, 0]] = group.aggregation;
|
|
const datum = [...dataModel.forEachDatum(this, processedData, group, groupIndex)];
|
|
const frequency = this.frequency(group);
|
|
const total = negativeAgg + positiveAgg;
|
|
return { domain, datum, groupIndex, frequency, total };
|
|
} else {
|
|
return { domain, datum: [], groupIndex: -1, frequency: 0, total: 0 };
|
|
}
|
|
});
|
|
this.animationState.transition("updateData");
|
|
}
|
|
xCoordinateRange() {
|
|
return [Number.NaN, Number.NaN];
|
|
}
|
|
yCoordinateRange() {
|
|
return [Number.NaN, Number.NaN];
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { processedData, dataModel } = this;
|
|
if (!processedData || !dataModel || !this.calculatedBins.length)
|
|
return { domain: [] };
|
|
const yDomain = dataModel.getDomain(this, `groupAgg`, "aggregate", processedData).domain;
|
|
const xDomainMin = this.calculatedBins[0].domain[0];
|
|
const xDomainMax = this.calculatedBins[(this.calculatedBins?.length ?? 0) - 1].domain[1];
|
|
if (direction === "x" /* X */) {
|
|
return { domain: fixNumericExtent([xDomainMin, xDomainMax]) };
|
|
}
|
|
return { domain: fixNumericExtent(yDomain) };
|
|
}
|
|
getSeriesRange(_direction, [r0, r1]) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || processedData?.type !== "grouped")
|
|
return [Number.NaN, Number.NaN];
|
|
const xScale = this.axes["x" /* X */].scale;
|
|
const yMin = 0;
|
|
let yMax = -Infinity;
|
|
for (const { keys, aggregation } of processedData.groups) {
|
|
const [[negativeAgg, positiveAgg] = [0, 0]] = aggregation;
|
|
const [xDomainMin, xDomainMax] = keys;
|
|
const [x0, x1] = findMinMax([xScale.convert(xDomainMin), xScale.convert(xDomainMax)]);
|
|
if (x1 >= r0 && x0 <= r1) {
|
|
const total = negativeAgg + positiveAgg;
|
|
yMax = Math.max(yMax, total);
|
|
}
|
|
}
|
|
if (yMin > yMax)
|
|
return [Number.NaN, Number.NaN];
|
|
return [yMin, yMax];
|
|
}
|
|
frequency(group) {
|
|
return group.datumIndices.reduce((acc, datumIndices) => acc + datumIndices.length, 0);
|
|
}
|
|
/**
|
|
* Creates the shared context for datum creation.
|
|
* Caches expensive lookups and computations that are constant across all datums.
|
|
*
|
|
* Note: rawData and xValues are empty arrays because HistogramSeries
|
|
* iterates over calculatedBins rather than raw data.
|
|
*/
|
|
createNodeDatumContext(xAxis, yAxis) {
|
|
const { xKey, yKey, xName, yName, label } = this.properties;
|
|
const { contextNodeData, processedData } = this;
|
|
const canIncrementallyUpdate = contextNodeData?.nodeData != null && processedData?.changeDescription != null;
|
|
return {
|
|
// Axes (from template method parameters)
|
|
xAxis,
|
|
yAxis,
|
|
// Scales
|
|
xScale: xAxis.scale,
|
|
yScale: yAxis.scale,
|
|
yAxisReversed: yAxis.isReversed(),
|
|
// Data source (empty arrays - histogram uses calculatedBins instead)
|
|
rawData: [],
|
|
xValues: [],
|
|
// Property lookups
|
|
xKey,
|
|
yKey,
|
|
xName,
|
|
yName,
|
|
label,
|
|
// Animation flag
|
|
animationEnabled: !this.ctx.animationManager.isSkipped(),
|
|
// Incremental update support
|
|
canIncrementallyUpdate,
|
|
nodes: canIncrementallyUpdate ? contextNodeData.nodeData : [],
|
|
nodeIndex: 0
|
|
};
|
|
}
|
|
/**
|
|
* Creates label data for a histogram bin if labels are enabled.
|
|
*/
|
|
createLabelData(ctx, bin, x, y, w, h) {
|
|
const { label, yKey, xKey, xName, yName } = ctx;
|
|
const { total, datum } = bin;
|
|
if (!label.enabled || total === 0) {
|
|
return void 0;
|
|
}
|
|
return {
|
|
x: x + w / 2,
|
|
y: y + h / 2,
|
|
text: this.getLabelText(total, datum, yKey, "y", [], label, {
|
|
value: total,
|
|
datum,
|
|
xKey,
|
|
yKey,
|
|
xName,
|
|
yName
|
|
})
|
|
};
|
|
}
|
|
/**
|
|
* Creates a skeleton HistogramNodeDatum with minimal required fields.
|
|
* The node will be populated by updateNodeDatum.
|
|
*/
|
|
createSkeletonNodeDatum(ctx, bin) {
|
|
const { xKey, yKey } = ctx;
|
|
const { domain, datum, groupIndex, frequency, total } = bin;
|
|
return {
|
|
series: this,
|
|
datumIndex: groupIndex,
|
|
datum,
|
|
aggregatedValue: total,
|
|
frequency,
|
|
domain,
|
|
yKey,
|
|
xKey,
|
|
x: 0,
|
|
y: 0,
|
|
xValue: 0,
|
|
yValue: 0,
|
|
width: 0,
|
|
height: 0,
|
|
midPoint: { x: 0, y: 0 },
|
|
topLeftCornerRadius: false,
|
|
topRightCornerRadius: false,
|
|
bottomRightCornerRadius: false,
|
|
bottomLeftCornerRadius: false,
|
|
label: void 0,
|
|
crisp: true
|
|
};
|
|
}
|
|
/**
|
|
* Updates an existing HistogramNodeDatum in-place.
|
|
* This is more efficient than recreating the entire node when only data values change.
|
|
*/
|
|
updateNodeDatum(ctx, node, bin) {
|
|
const { xScale, yScale, yAxisReversed } = ctx;
|
|
const { domain, datum, groupIndex, frequency, total } = bin;
|
|
const mutableNode = node;
|
|
const [xDomainMin, xDomainMax] = domain;
|
|
const xMinPx = xScale.convert(xDomainMin);
|
|
const xMaxPx = xScale.convert(xDomainMax);
|
|
const yZeroPx = yScale.convert(0);
|
|
const yMaxPx = yScale.convert(total);
|
|
const w = Math.abs(xMaxPx - xMinPx);
|
|
const h = Math.abs(yMaxPx - yZeroPx);
|
|
const x = Math.min(xMinPx, xMaxPx);
|
|
const y = Math.min(yZeroPx, yMaxPx);
|
|
mutableNode.datumIndex = groupIndex;
|
|
mutableNode.datum = datum;
|
|
mutableNode.aggregatedValue = total;
|
|
mutableNode.frequency = frequency;
|
|
mutableNode.domain = domain;
|
|
mutableNode.x = x;
|
|
mutableNode.y = y;
|
|
mutableNode.xValue = xMinPx;
|
|
mutableNode.yValue = yMaxPx;
|
|
mutableNode.width = w;
|
|
mutableNode.height = h;
|
|
if (mutableNode.midPoint) {
|
|
mutableNode.midPoint.x = x + w / 2;
|
|
mutableNode.midPoint.y = y + h / 2;
|
|
} else {
|
|
mutableNode.midPoint = { x: x + w / 2, y: y + h / 2 };
|
|
}
|
|
mutableNode.topLeftCornerRadius = !yAxisReversed;
|
|
mutableNode.topRightCornerRadius = !yAxisReversed;
|
|
mutableNode.bottomRightCornerRadius = yAxisReversed;
|
|
mutableNode.bottomLeftCornerRadius = yAxisReversed;
|
|
mutableNode.label = this.createLabelData(ctx, bin, x, y, w, h);
|
|
}
|
|
/**
|
|
* Creates a HistogramNodeDatum for a single bin.
|
|
* Creates a skeleton node and uses updateNodeDatum to populate it.
|
|
*/
|
|
createNodeDatum(ctx, bin) {
|
|
const node = this.createSkeletonNodeDatum(ctx, bin);
|
|
this.updateNodeDatum(ctx, node, bin);
|
|
return node;
|
|
}
|
|
/**
|
|
* Template method hook: Iterates over calculated bins and creates/updates node datums.
|
|
*/
|
|
populateNodeData(ctx) {
|
|
const { processedData } = this;
|
|
if (processedData?.type !== "grouped") {
|
|
return;
|
|
}
|
|
for (const bin of this.calculatedBins) {
|
|
upsertNodeDatum(
|
|
ctx,
|
|
bin,
|
|
(c, b) => this.createNodeDatum(c, b),
|
|
(c, n, b) => this.updateNodeDatum(c, n, b)
|
|
);
|
|
}
|
|
}
|
|
/**
|
|
* Template method hook: Creates the result object shell.
|
|
*/
|
|
initializeResult(ctx) {
|
|
return {
|
|
itemId: this.properties.yKey ?? this.id,
|
|
nodeData: ctx.nodes,
|
|
labelData: ctx.nodes,
|
|
scales: this.calculateScaling(),
|
|
animationValid: true,
|
|
visible: this.visible || ctx.animationEnabled,
|
|
styles: getItemStyles(this.getItemStyle.bind(this))
|
|
};
|
|
}
|
|
/**
|
|
* Template method hook: Trims arrays and sorts nodes for keyboard navigation.
|
|
*/
|
|
finalizeNodeData(ctx) {
|
|
super.finalizeNodeData(ctx);
|
|
ctx.nodes.sort((a, b) => a.x - b.x);
|
|
}
|
|
nodeFactory() {
|
|
return new Rect();
|
|
}
|
|
updateDatumSelection(opts) {
|
|
const { nodeData, datumSelection } = opts;
|
|
if (!processedDataIsAnimatable(this.processedData)) {
|
|
return datumSelection.update(nodeData);
|
|
}
|
|
return datumSelection.update(
|
|
nodeData,
|
|
void 0,
|
|
(datum) => createDatumId(...datum.domain)
|
|
);
|
|
}
|
|
getItemStyle(datumIndex, isHighlight, highlightState) {
|
|
const { properties } = this;
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex, highlightState);
|
|
return mergeDefaults(highlightStyle, properties.getStyle());
|
|
}
|
|
updateDatumStyles(opts) {
|
|
const { datumSelection, isHighlight } = opts;
|
|
datumSelection.each((node, datum) => {
|
|
if (!datumSelection.isGarbage(node)) {
|
|
datum.style = this.getItemStyle(datum.datumIndex, isHighlight);
|
|
}
|
|
});
|
|
}
|
|
updateDatumNodes(opts) {
|
|
const { contextNodeData } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
const { shadow } = this.properties;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
opts.datumSelection.each((rect2, datum) => {
|
|
const style2 = datum.style ?? contextNodeData.styles[this.getHighlightState(highlightedDatum, opts.isHighlight, datum.datumIndex)];
|
|
const { cornerRadius = 0 } = style2;
|
|
const { topLeftCornerRadius, topRightCornerRadius, bottomRightCornerRadius, bottomLeftCornerRadius } = datum;
|
|
rect2.setStyleProperties(style2, fillBBox);
|
|
rect2.topLeftCornerRadius = topLeftCornerRadius ? cornerRadius : 0;
|
|
rect2.topRightCornerRadius = topRightCornerRadius ? cornerRadius : 0;
|
|
rect2.bottomRightCornerRadius = bottomRightCornerRadius ? cornerRadius : 0;
|
|
rect2.bottomLeftCornerRadius = bottomLeftCornerRadius ? cornerRadius : 0;
|
|
rect2.crisp = datum.crisp;
|
|
rect2.fillShadow = shadow;
|
|
});
|
|
}
|
|
updateLabelSelection(opts) {
|
|
const { labelData, labelSelection } = opts;
|
|
return labelSelection.update(labelData, (text2) => {
|
|
text2.pointerEvents = 1 /* None */;
|
|
text2.textAlign = "center";
|
|
text2.textBaseline = "middle";
|
|
});
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const labelEnabled = this.isLabelEnabled();
|
|
const { isHighlight = false } = opts;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
opts.labelSelection.each((text2, datum) => {
|
|
const style2 = getLabelStyles(
|
|
this,
|
|
datum,
|
|
this.properties,
|
|
this.properties.label,
|
|
isHighlight,
|
|
activeHighlight
|
|
);
|
|
const { enabled, fontStyle, fontWeight: fontWeight2, fontSize, fontFamily, color: color2 } = style2;
|
|
if (enabled && labelEnabled && datum?.label) {
|
|
text2.text = datum.label.text;
|
|
text2.x = datum.label.x;
|
|
text2.y = datum.label.y;
|
|
text2.fontStyle = fontStyle;
|
|
text2.fontWeight = fontWeight2;
|
|
text2.fontFamily = fontFamily;
|
|
text2.fontSize = fontSize;
|
|
text2.fill = color2;
|
|
text2.visible = true;
|
|
text2.fillOpacity = this.getHighlightStyle(isHighlight, datum.datumIndex).opacity ?? 1;
|
|
text2.setBoxing(style2);
|
|
} else {
|
|
text2.visible = false;
|
|
}
|
|
});
|
|
}
|
|
initQuadTree(quadtree) {
|
|
const { value: childNode } = this.contentGroup.children().next();
|
|
if (childNode instanceof Group) {
|
|
addHitTestersToQuadtree(quadtree, childNode.children());
|
|
}
|
|
}
|
|
pickNodeClosestDatum(point) {
|
|
return findQuadtreeMatch(this, point);
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const {
|
|
id: seriesId,
|
|
dataModel,
|
|
processedData,
|
|
axes,
|
|
properties,
|
|
ctx: { localeManager }
|
|
} = this;
|
|
const { xKey, xName, yKey, yName, tooltip, legendItemName } = properties;
|
|
const xAxis = axes["x" /* X */];
|
|
const yAxis = axes["y" /* Y */];
|
|
if (!dataModel || processedData?.type !== "grouped" || !xAxis || !yAxis) {
|
|
return;
|
|
}
|
|
const group = processedData.groups[datumIndex];
|
|
const { aggregation, keys } = group;
|
|
const [[negativeAgg, positiveAgg] = [0, 0]] = aggregation;
|
|
const frequency = this.frequency(group);
|
|
const domain = keys;
|
|
const [rangeMin, rangeMax] = domain;
|
|
const aggregatedValue = negativeAgg + positiveAgg;
|
|
const datum = {
|
|
data: [...dataModel.forEachDatum(this, processedData, group, datumIndex)],
|
|
aggregatedValue,
|
|
frequency,
|
|
domain
|
|
};
|
|
const data = [
|
|
{
|
|
label: xName,
|
|
fallbackLabel: xKey,
|
|
value: `${this.getAxisValueText(xAxis, "tooltip", rangeMin, datum, xKey, legendItemName)} - ${this.getAxisValueText(xAxis, "tooltip", rangeMax, datum, xKey, legendItemName)}`
|
|
},
|
|
{
|
|
label: localeManager.t("seriesHistogramTooltipFrequency"),
|
|
value: this.getAxisValueText(yAxis, "tooltip", frequency, datum, yKey, legendItemName)
|
|
}
|
|
];
|
|
if (yKey != null) {
|
|
let label;
|
|
switch (properties.aggregation) {
|
|
case "sum":
|
|
label = localeManager.t("seriesHistogramTooltipSum", { yName: yName ?? yKey });
|
|
break;
|
|
case "mean":
|
|
label = localeManager.t("seriesHistogramTooltipMean", { yName: yName ?? yKey });
|
|
break;
|
|
case "count":
|
|
label = localeManager.t("seriesHistogramTooltipCount", { yName: yName ?? yKey });
|
|
break;
|
|
}
|
|
data.push({
|
|
label,
|
|
value: this.getAxisValueText(yAxis, "tooltip", aggregatedValue, datum, yKey, legendItemName)
|
|
});
|
|
}
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
symbol: this.legendItemSymbol(),
|
|
data
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: yName,
|
|
xKey,
|
|
// HistogramSeries is an outlier since it's callbacks don't use TDatum.
|
|
xName,
|
|
yKey,
|
|
// HistogramSeries is an outlier since it's callbacks don't use TDatum.
|
|
yName,
|
|
xRange: [rangeMin, rangeMax],
|
|
frequency,
|
|
...this.getItemStyle(datumIndex, false)
|
|
}
|
|
);
|
|
}
|
|
legendItemSymbol() {
|
|
const { fill, fillOpacity, stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = this.properties;
|
|
return {
|
|
marker: {
|
|
fill: deepClone(fill) ?? "rgba(0, 0, 0, 0)",
|
|
stroke: stroke3 ?? "rgba(0, 0, 0, 0)",
|
|
fillOpacity,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash,
|
|
lineDashOffset
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
if (legendType !== "category") {
|
|
return [];
|
|
}
|
|
const {
|
|
id: seriesId,
|
|
ctx: { legendManager },
|
|
visible
|
|
} = this;
|
|
const { xKey: itemId, yName, showInLegend } = this.properties;
|
|
return [
|
|
{
|
|
legendType: "category",
|
|
id: seriesId,
|
|
itemId,
|
|
seriesId,
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId, itemId }),
|
|
label: {
|
|
text: yName ?? itemId ?? "Frequency"
|
|
},
|
|
symbol: this.legendItemSymbol(),
|
|
hideInLegend: !showInLegend
|
|
}
|
|
];
|
|
}
|
|
resetDatumAnimation(data) {
|
|
resetBarSelectionsDirect([data.datumSelection]);
|
|
}
|
|
animateEmptyUpdateReady({ datumSelection, labelSelection }) {
|
|
const fns = prepareBarAnimationFunctions(collapsedStartingBarPosition(true, this.axes, "normal"), "unknown");
|
|
fromToMotion(this.id, "datums", this.ctx.animationManager, [datumSelection], fns);
|
|
seriesLabelFadeInAnimation(this, "labels", this.ctx.animationManager, labelSelection);
|
|
}
|
|
animateWaitingUpdateReady(data) {
|
|
const fns = prepareBarAnimationFunctions(collapsedStartingBarPosition(true, this.axes, "normal"), "added");
|
|
const dataDiff = {
|
|
changed: true,
|
|
added: /* @__PURE__ */ new Set(),
|
|
updated: /* @__PURE__ */ new Set(),
|
|
removed: /* @__PURE__ */ new Set(),
|
|
moved: /* @__PURE__ */ new Set()
|
|
};
|
|
fromToMotion(
|
|
this.id,
|
|
"datums",
|
|
this.ctx.animationManager,
|
|
[data.datumSelection],
|
|
fns,
|
|
(_, datum) => createDatumId(...datum.domain),
|
|
dataDiff
|
|
);
|
|
if (dataDiff?.changed) {
|
|
seriesLabelFadeInAnimation(this, "labels", this.ctx.animationManager, data.labelSelection);
|
|
}
|
|
}
|
|
isLabelEnabled() {
|
|
return this.properties.label.enabled;
|
|
}
|
|
computeFocusBounds({ datumIndex }) {
|
|
return computeBarFocusBounds(this, this.contextNodeData?.nodeData[datumIndex]);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
HistogramSeries.className = "HistogramSeries";
|
|
HistogramSeries.type = "histogram";
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/histogramSeriesModule.ts
|
|
var themeTemplate4 = {
|
|
series: {
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_LINEAR_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: { $palette: "stroke" },
|
|
strokeWidth: 1,
|
|
fillOpacity: 1,
|
|
strokeOpacity: 1,
|
|
lineDash: [0],
|
|
lineDashOffset: 0,
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "chartBackgroundColor" }
|
|
},
|
|
shadow: {
|
|
enabled: false,
|
|
color: DEFAULT_SHADOW_COLOUR,
|
|
xOffset: 3,
|
|
yOffset: 3,
|
|
blur: 5
|
|
},
|
|
highlight: MULTI_SERIES_HIGHLIGHT_STYLE
|
|
}
|
|
};
|
|
var HistogramSeriesModule = {
|
|
type: "series",
|
|
name: "histogram",
|
|
chartType: "cartesian",
|
|
// enterprise: true,
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: histogramSeriesOptionsDef,
|
|
predictAxis: predictCartesianNonPrimitiveAxis,
|
|
defaultAxes: {
|
|
x: {
|
|
type: "number" /* NUMBER */,
|
|
position: "bottom" /* BOTTOM */
|
|
},
|
|
y: {
|
|
type: "number" /* NUMBER */,
|
|
position: "left" /* LEFT */
|
|
}
|
|
},
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
themeTemplate: themeTemplate4,
|
|
create: (ctx) => new HistogramSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/lineAggregation.ts
|
|
var MAX_POINTS2 = 10;
|
|
function isIndexInAggregation(xValues, d0, d1, indexData, maxRange, datumIndex, xNeedsValueOf, xValuesLength) {
|
|
const xValue = xValues[datumIndex];
|
|
if (xValue === void 0)
|
|
return false;
|
|
const xRatio = Number.isFinite(d0) ? aggregationXRatioForXValue(xValue, d0, d1, xNeedsValueOf) : aggregationXRatioForDatumIndex(datumIndex, xValuesLength);
|
|
const aggIndex = aggregationIndexForXRatio(xRatio, maxRange);
|
|
return datumIndex === indexData[aggIndex + AGGREGATION_INDEX_X_MIN] || datumIndex === indexData[aggIndex + AGGREGATION_INDEX_X_MAX] || datumIndex === indexData[aggIndex + AGGREGATION_INDEX_Y_MIN] || datumIndex === indexData[aggIndex + AGGREGATION_INDEX_Y_MAX];
|
|
}
|
|
function buildIndicesFromAggregation2(xValues, d0, d1, indexData, maxRange, xNeedsValueOf, xValuesLength, reuseArray) {
|
|
let count = 0;
|
|
for (let datumIndex = 0; datumIndex < xValuesLength; datumIndex++) {
|
|
if (isIndexInAggregation(xValues, d0, d1, indexData, maxRange, datumIndex, xNeedsValueOf, xValuesLength)) {
|
|
count++;
|
|
}
|
|
}
|
|
const indices = reuseArray?.length === count ? reuseArray : new Uint32Array(count);
|
|
let idx = 0;
|
|
for (let datumIndex = 0; datumIndex < xValuesLength; datumIndex++) {
|
|
if (isIndexInAggregation(xValues, d0, d1, indexData, maxRange, datumIndex, xNeedsValueOf, xValuesLength)) {
|
|
indices[idx++] = datumIndex;
|
|
}
|
|
}
|
|
return indices;
|
|
}
|
|
function filterIndicesFromPrevious(prevIndices, xValues, d0, d1, indexData, maxRange, xNeedsValueOf, xValuesLength, reuseArray) {
|
|
let count = 0;
|
|
for (const datumIndex of prevIndices) {
|
|
if (isIndexInAggregation(xValues, d0, d1, indexData, maxRange, datumIndex, xNeedsValueOf, xValuesLength)) {
|
|
count++;
|
|
}
|
|
}
|
|
const indices = reuseArray?.length === count ? reuseArray : new Uint32Array(count);
|
|
let idx = 0;
|
|
for (const datumIndex of prevIndices) {
|
|
if (isIndexInAggregation(xValues, d0, d1, indexData, maxRange, datumIndex, xNeedsValueOf, xValuesLength)) {
|
|
indices[idx++] = datumIndex;
|
|
}
|
|
}
|
|
return indices;
|
|
}
|
|
function computeLineAggregation(domain, xValues, yValues, options) {
|
|
const xValuesLength = xValues.length;
|
|
if (xValuesLength < AGGREGATION_THRESHOLD)
|
|
return;
|
|
const [d0, d1] = domain;
|
|
const { xNeedsValueOf, yNeedsValueOf, existingFilters } = options;
|
|
let maxRange = aggregationRangeFittingPoints(xValues, d0, d1, { xNeedsValueOf });
|
|
const existingFilter = existingFilters?.find((f) => f.maxRange === maxRange);
|
|
let { indexData, valueData } = createAggregationIndices(xValues, yValues, yValues, d0, d1, maxRange, {
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
reuseIndexData: existingFilter?.indexData,
|
|
reuseValueData: existingFilter?.valueData
|
|
});
|
|
let indices = buildIndicesFromAggregation2(
|
|
xValues,
|
|
d0,
|
|
d1,
|
|
indexData,
|
|
maxRange,
|
|
xNeedsValueOf,
|
|
xValuesLength,
|
|
existingFilter?.indices
|
|
);
|
|
const filters = [{ maxRange, indices, indexData, valueData }];
|
|
while (indices.length > MAX_POINTS2 && maxRange > 64) {
|
|
const currentMaxRange = maxRange;
|
|
const nextMaxRange = Math.trunc(currentMaxRange / 2);
|
|
const nextExistingFilter = existingFilters?.find((f) => f.maxRange === nextMaxRange);
|
|
const compacted = compactAggregationIndices(indexData, valueData, currentMaxRange, {
|
|
reuseIndexData: nextExistingFilter?.indexData,
|
|
reuseValueData: nextExistingFilter?.valueData
|
|
});
|
|
maxRange = compacted.maxRange;
|
|
indexData = compacted.indexData;
|
|
valueData = compacted.valueData;
|
|
indices = filterIndicesFromPrevious(
|
|
indices,
|
|
xValues,
|
|
d0,
|
|
d1,
|
|
indexData,
|
|
maxRange,
|
|
xNeedsValueOf,
|
|
xValuesLength,
|
|
nextExistingFilter?.indices
|
|
);
|
|
filters.push({ maxRange, indices, indexData, valueData });
|
|
}
|
|
filters.reverse();
|
|
return filters;
|
|
}
|
|
function computeLineAggregationPartial(domain, xValues, yValues, options) {
|
|
const xValuesLength = xValues.length;
|
|
if (xValuesLength < AGGREGATION_THRESHOLD)
|
|
return;
|
|
const [d0, d1] = domain;
|
|
const { xNeedsValueOf, yNeedsValueOf, targetRange, existingFilters } = options;
|
|
const finestMaxRange = aggregationRangeFittingPoints(xValues, d0, d1, { xNeedsValueOf });
|
|
const targetMaxRange = Math.min(finestMaxRange, nextPowerOf2(Math.max(targetRange, AGGREGATION_MIN_RANGE)));
|
|
const existingFilter = existingFilters?.find((f) => f.maxRange === targetMaxRange);
|
|
const { indexData, valueData } = createAggregationIndices(xValues, yValues, yValues, d0, d1, targetMaxRange, {
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
reuseIndexData: existingFilter?.indexData,
|
|
reuseValueData: existingFilter?.valueData
|
|
});
|
|
const indices = buildIndicesFromAggregation2(
|
|
xValues,
|
|
d0,
|
|
d1,
|
|
indexData,
|
|
targetMaxRange,
|
|
xNeedsValueOf,
|
|
xValuesLength,
|
|
existingFilter?.indices
|
|
);
|
|
const immediateLevel = {
|
|
maxRange: targetMaxRange,
|
|
indices,
|
|
indexData,
|
|
valueData
|
|
};
|
|
function computeRemaining() {
|
|
const allLevels = computeLineAggregation([d0, d1], xValues, yValues, {
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
existingFilters
|
|
});
|
|
return allLevels?.filter((level) => level.maxRange !== targetMaxRange) ?? [];
|
|
}
|
|
return { immediate: [immediateLevel], computeRemaining };
|
|
}
|
|
function aggregateLineData(scale2, xValues, yValues, domainInput, xNeedsValueOf, yNeedsValueOf) {
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeLineAggregation([d0, d1], xValues, yValues, { xNeedsValueOf, yNeedsValueOf });
|
|
}
|
|
var memoizedAggregateLineData = simpleMemorize2(aggregateLineData);
|
|
function aggregateLineDataFromDataModel(scale2, dataModel, processedData, yKey, series, existingFilters) {
|
|
const xValues = dataModel.resolveColumnById(series, "xValue", processedData);
|
|
const yValues = dataModel.resolveColumnById(series, yKey, processedData);
|
|
const domainInput = dataModel.getDomain(series, "xValue", "value", processedData);
|
|
const xNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "xValue", processedData);
|
|
const yNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, yKey, processedData);
|
|
if (existingFilters) {
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeLineAggregation([d0, d1], xValues, yValues, {
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
existingFilters
|
|
});
|
|
}
|
|
return memoizedAggregateLineData(scale2, xValues, yValues, domainInput, xNeedsValueOf, yNeedsValueOf);
|
|
}
|
|
function aggregateLineDataFromDataModelPartial(scale2, dataModel, processedData, yKey, series, targetRange, existingFilters) {
|
|
const xValues = dataModel.resolveColumnById(series, "xValue", processedData);
|
|
const yValues = dataModel.resolveColumnById(series, yKey, processedData);
|
|
const domainInput = dataModel.getDomain(series, "xValue", "value", processedData);
|
|
const xNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "xValue", processedData);
|
|
const yNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, yKey, processedData);
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeLineAggregationPartial([d0, d1], xValues, yValues, {
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
targetRange,
|
|
existingFilters
|
|
});
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/lineSeriesProperties.ts
|
|
var LineSeriesProperties = class extends CartesianSeriesProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.stroke = "#874349";
|
|
this.strokeWidth = 2;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.interpolation = new InterpolationProperties();
|
|
this.marker = new SeriesMarker();
|
|
this.label = new Label();
|
|
this.tooltip = makeSeriesTooltip();
|
|
this.connectMissingData = false;
|
|
this.sparklineMode = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "xKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "yKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "xName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "yName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "yFilterKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "stackGroup", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "normalizedTo", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "title", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "interpolation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "styler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "marker", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "tooltip", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "connectMissingData", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineSeriesProperties.prototype, "sparklineMode", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/lineSeries.ts
|
|
var CROSS_FILTER_LINE_STROKE_OPACITY_FACTOR = 0.25;
|
|
var LineSeries = class extends CartesianSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
propertyKeys: DEFAULT_CARTESIAN_DIRECTION_KEYS,
|
|
propertyNames: DEFAULT_CARTESIAN_DIRECTION_NAMES,
|
|
categoryKey: "xValue",
|
|
pickModes: [
|
|
2 /* AXIS_ALIGNED */,
|
|
1 /* NEAREST_NODE */,
|
|
0 /* EXACT_SHAPE_MATCH */
|
|
],
|
|
datumSelectionGarbageCollection: false,
|
|
segmentedDataNodes: false,
|
|
animationResetFns: {
|
|
path: buildResetPathFn({ getVisible: () => this.visible, getOpacity: () => this.getOpacity() }),
|
|
label: resetLabelFn,
|
|
datum: (node, datum) => ({ ...resetMarkerFn(node), ...resetMarkerPositionFn(node, datum) })
|
|
},
|
|
clipFocusBox: false
|
|
});
|
|
this.properties = new LineSeriesProperties();
|
|
this.aggregationManager = new AggregationManager();
|
|
}
|
|
get pickModeAxis() {
|
|
return this.properties.sparklineMode ? "main" : "main-category";
|
|
}
|
|
isNormalized() {
|
|
return this.properties.normalizedTo != null;
|
|
}
|
|
renderToOffscreenCanvas() {
|
|
const hasMarkers = (this.contextNodeData?.nodeData?.length ?? 0) > 0;
|
|
return hasMarkers && this.getDrawingMode(false) === "cutout" || super.renderToOffscreenCanvas();
|
|
}
|
|
async processData(dataController) {
|
|
if (this.data == null)
|
|
return;
|
|
const { data, visible, seriesGrouping: { groupIndex = this.id, stackCount = 0 } = {} } = this;
|
|
const { xKey, yKey, yFilterKey, connectMissingData, normalizedTo } = this.properties;
|
|
const xScale = this.axes["x" /* X */]?.scale;
|
|
const yScale = this.axes["y" /* Y */]?.scale;
|
|
const { isContinuousX, xScaleType, yScaleType } = this.getScaleInformation({ xScale, yScale });
|
|
const stacked = stackCount > 1 || normalizedTo != null;
|
|
const common = { invalidValue: null };
|
|
if (connectMissingData && stacked) {
|
|
common.invalidValue = 0;
|
|
}
|
|
if (stacked && !visible) {
|
|
common.forceValue = 0;
|
|
}
|
|
const idMap = {
|
|
value: `area-stack-${groupIndex}-yValue`,
|
|
marker: `area-stack-${groupIndex}-yValues-marker`
|
|
};
|
|
const props = [];
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
if (!isContinuousX || stacked) {
|
|
props.push(keyProperty(xKey, xScaleType, { id: "xKey", allowNullKey }));
|
|
}
|
|
props.push(
|
|
valueProperty(xKey, xScaleType, { id: "xValue", allowNullKey }),
|
|
valueProperty(yKey, yScaleType, {
|
|
id: `yValueRaw`,
|
|
...common,
|
|
invalidValue: void 0
|
|
})
|
|
);
|
|
if (yFilterKey != null) {
|
|
props.push(valueProperty(yFilterKey, yScaleType, { id: "yFilterRaw" }));
|
|
}
|
|
if (stacked) {
|
|
props.push(
|
|
...groupAccumulativeValueProperty(
|
|
yKey,
|
|
"normal",
|
|
{ id: `yValueCumulative`, ...common, groupId: idMap.marker },
|
|
yScaleType
|
|
)
|
|
);
|
|
}
|
|
if (isDefined(normalizedTo)) {
|
|
props.push(
|
|
valueProperty(yKey, yScaleType, { id: `yValue`, ...common, groupId: idMap.value }),
|
|
normaliseGroupTo(Object.values(idMap), normalizedTo)
|
|
);
|
|
}
|
|
if (this.needsDataModelDiff()) {
|
|
props.push(animationValidation(isContinuousX ? ["xValue"] : void 0));
|
|
if (this.processedData) {
|
|
props.push(diff(this.id, this.processedData));
|
|
}
|
|
}
|
|
const { dataModel, processedData } = await this.requestDataModel(dataController, data, {
|
|
props,
|
|
groupByKeys: stacked,
|
|
groupByData: !stacked
|
|
});
|
|
this.aggregateData(dataModel, processedData);
|
|
this.animationState.transition("updateData");
|
|
}
|
|
yValueKey() {
|
|
return this.isNormalized() ? "yValue" : "yValueRaw";
|
|
}
|
|
yCumulativeKey(processData) {
|
|
return processData.type === "grouped" ? "yValueCumulative" : this.yValueKey();
|
|
}
|
|
xCoordinateRange(xValue, pixelSize) {
|
|
const { marker } = this.properties;
|
|
const x = this.axes["x" /* X */].scale.convert(xValue);
|
|
const r = marker.enabled ? 0.5 * marker.size * pixelSize : 0;
|
|
return [x - r, x + r];
|
|
}
|
|
yCoordinateRange(yValues, pixelSize) {
|
|
const { marker } = this.properties;
|
|
const y = this.axes["y" /* Y */].scale.convert(yValues[0]);
|
|
const r = marker.enabled ? 0.5 * marker.size * pixelSize : 0;
|
|
return [y - r, y + r];
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { dataModel, processedData, axes } = this;
|
|
if (!dataModel || !processedData)
|
|
return { domain: [] };
|
|
const yAxis = axes["y" /* Y */];
|
|
if (direction === "x" /* X */) {
|
|
const xDef = dataModel.resolveProcessedDataDefById(this, `xValue`);
|
|
const xDomain = dataModel.getDomain(this, `xValue`, "value", processedData);
|
|
if (xDef?.def.type === "value" && xDef.def.valueType === "category") {
|
|
const sortMetadata = dataModel.getKeySortMetadata(this, "xValue", processedData);
|
|
return { domain: xDomain.domain, sortMetadata };
|
|
}
|
|
return { domain: fixNumericExtent(extent(xDomain.domain)) };
|
|
}
|
|
const yExtent = this.domainForClippedRange(
|
|
"y" /* Y */,
|
|
[this.yCumulativeKey(processedData)],
|
|
"xValue"
|
|
);
|
|
if (this.isNormalized() && yAxis instanceof NumberAxis && !(yAxis instanceof LogAxis)) {
|
|
const fixedYExtent = Number.isFinite(yExtent[1] - yExtent[0]) ? [Math.min(yExtent[0], 0), Math.max(yExtent[1], 0)] : [];
|
|
return { domain: fixNumericExtent(fixedYExtent) };
|
|
} else {
|
|
return { domain: fixNumericExtent(yExtent) };
|
|
}
|
|
}
|
|
getSeriesRange(_direction, visibleRange) {
|
|
return this.domainForVisibleRange(
|
|
"y" /* Y */,
|
|
[this.yCumulativeKey(this.processedData)],
|
|
"xValue",
|
|
visibleRange
|
|
);
|
|
}
|
|
getZoomRangeFittingItems(xVisibleRange, yVisibleRange, minVisibleItems) {
|
|
return this.zoomFittingVisibleItems(
|
|
"xValue",
|
|
[this.yCumulativeKey(this.processedData)],
|
|
xVisibleRange,
|
|
yVisibleRange,
|
|
minVisibleItems
|
|
);
|
|
}
|
|
getVisibleItems(xVisibleRange, yVisibleRange, minVisibleItems) {
|
|
return this.countVisibleItems(
|
|
"xValue",
|
|
[this.yCumulativeKey(this.processedData)],
|
|
xVisibleRange,
|
|
yVisibleRange,
|
|
minVisibleItems
|
|
);
|
|
}
|
|
aggregateData(dataModel, processedData) {
|
|
this.aggregationManager.markStale(processedData.input.count);
|
|
if (processedData.type !== "ungrouped")
|
|
return;
|
|
if (processedDataIsAnimatable(processedData))
|
|
return;
|
|
const xAxis = this.axes["x" /* X */];
|
|
if (xAxis == null)
|
|
return;
|
|
const targetRange = this.estimateTargetRange();
|
|
this.aggregationManager.aggregate({
|
|
computePartial: (existingFilters) => aggregateLineDataFromDataModelPartial(
|
|
xAxis.scale.type,
|
|
dataModel,
|
|
processedData,
|
|
this.yCumulativeKey(processedData),
|
|
this,
|
|
targetRange,
|
|
existingFilters
|
|
),
|
|
computeFull: (existingFilters) => aggregateLineDataFromDataModel(
|
|
xAxis.scale.type,
|
|
dataModel,
|
|
processedData,
|
|
this.yCumulativeKey(processedData),
|
|
this,
|
|
existingFilters
|
|
),
|
|
targetRange
|
|
});
|
|
const filters = this.aggregationManager.filters;
|
|
if (filters && filters.length > 0) {
|
|
debugMetrics_exports.record(
|
|
`${this.type}:aggregation`,
|
|
filters.map((f) => f.maxRange)
|
|
);
|
|
}
|
|
}
|
|
estimateTargetRange() {
|
|
const xAxis = this.axes["x" /* X */];
|
|
if (xAxis?.scale?.range) {
|
|
const [r0, r1] = xAxis.scale.range;
|
|
return Math.abs(r1 - r0);
|
|
}
|
|
return this.ctx.scene?.canvas?.width ?? 800;
|
|
}
|
|
/**
|
|
* Creates the context object for efficient node datum creation.
|
|
* Caches expensive-to-compute values that are reused across all datum iterations
|
|
* to minimize memory allocations. Only caches values that are expensive to
|
|
* compute - cheap property lookups use `this` directly.
|
|
*/
|
|
createNodeDatumContext(xAxis, yAxis) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData)
|
|
return void 0;
|
|
const xScale = xAxis.scale;
|
|
const yScale = yAxis.scale;
|
|
const rawData = processedData.dataSources.get(this.id)?.data ?? [];
|
|
const [r0, r1] = xScale.range;
|
|
const range3 = Math.abs(r1 - r0);
|
|
this.aggregationManager.ensureLevelForRange(range3);
|
|
const dataAggregationFilter = this.aggregationManager.getFilterForRange(range3);
|
|
const canIncrementallyUpdate = this.canIncrementallyUpdateNodes(dataAggregationFilter != null);
|
|
return {
|
|
xAxis,
|
|
yAxis,
|
|
rawData,
|
|
xValues: dataModel.resolveColumnById(this, "xValue", processedData),
|
|
yRawValues: dataModel.resolveColumnById(this, "yValueRaw", processedData),
|
|
yCumulativeValues: dataModel.resolveColumnById(this, this.yCumulativeKey(processedData), processedData),
|
|
selectionValues: this.properties.yFilterKey ? dataModel.resolveColumnById(this, "yFilterRaw", processedData) : void 0,
|
|
xScale,
|
|
yScale,
|
|
xOffset: (xScale.bandwidth ?? 0) / 2,
|
|
yOffset: (yScale.bandwidth ?? 0) / 2,
|
|
size: this.properties.marker.enabled ? this.properties.marker.size : 0,
|
|
yDomain: this.getSeriesDomain("y" /* Y */).domain,
|
|
labelsEnabled: this.properties.label.enabled,
|
|
animationEnabled: !this.ctx.animationManager.isSkipped(),
|
|
canIncrementallyUpdate,
|
|
dataAggregationFilter,
|
|
range: range3,
|
|
xKey: this.properties.xKey,
|
|
yKey: this.properties.yKey,
|
|
xName: this.properties.xName,
|
|
yName: this.properties.yName,
|
|
legendItemName: this.properties.legendItemName,
|
|
connectMissingData: this.properties.connectMissingData,
|
|
capDefaults: {
|
|
lengthRatioMultiplier: this.properties.marker.getDiameter(),
|
|
lengthMax: Infinity
|
|
},
|
|
nodes: canIncrementallyUpdate ? this.contextNodeData.nodeData : [],
|
|
spanPoints: [],
|
|
nodeIndex: 0
|
|
};
|
|
}
|
|
/**
|
|
* Processes a single datum and updates the context's nodes and spanPoints arrays.
|
|
* Uses the scratch object to avoid per-iteration allocations.
|
|
*/
|
|
handleDatum(ctx, scratch, datumIndex) {
|
|
scratch.datum = ctx.rawData[datumIndex];
|
|
scratch.xDatum = ctx.xValues[datumIndex];
|
|
scratch.yDatum = ctx.yRawValues[datumIndex];
|
|
scratch.yCumulative = ctx.yCumulativeValues[datumIndex];
|
|
scratch.selected = ctx.selectionValues?.[datumIndex];
|
|
scratch.x = ctx.xScale.convert(scratch.xDatum) + ctx.xOffset;
|
|
scratch.y = ctx.yScale.convert(scratch.yCumulative) + ctx.yOffset;
|
|
if (!Number.isFinite(scratch.x))
|
|
return;
|
|
if (scratch.yDatum != null) {
|
|
const labelText = ctx.labelsEnabled ? this.getLabelText(
|
|
scratch.yDatum,
|
|
scratch.datum,
|
|
ctx.yKey,
|
|
"y",
|
|
ctx.yDomain,
|
|
this.properties.label,
|
|
{
|
|
value: scratch.yDatum,
|
|
datum: scratch.datum,
|
|
xKey: ctx.xKey,
|
|
yKey: ctx.yKey,
|
|
xName: ctx.xName,
|
|
yName: ctx.yName,
|
|
legendItemName: ctx.legendItemName
|
|
}
|
|
) : void 0;
|
|
const canReuseNode = ctx.canIncrementallyUpdate && ctx.nodeIndex < ctx.nodes.length;
|
|
if (canReuseNode) {
|
|
const existingNode = ctx.nodes[ctx.nodeIndex];
|
|
existingNode.datum = scratch.datum;
|
|
existingNode.datumIndex = datumIndex;
|
|
existingNode.point = { x: scratch.x, y: scratch.y, size: ctx.size };
|
|
existingNode.midPoint = { x: scratch.x, y: scratch.y };
|
|
existingNode.cumulativeValue = scratch.yCumulative;
|
|
existingNode.yValue = scratch.yDatum;
|
|
existingNode.xValue = scratch.xDatum;
|
|
existingNode.labelText = labelText;
|
|
existingNode.selected = scratch.selected;
|
|
} else {
|
|
ctx.nodes.push({
|
|
series: this,
|
|
datum: scratch.datum,
|
|
datumIndex,
|
|
yKey: ctx.yKey,
|
|
xKey: ctx.xKey,
|
|
point: { x: scratch.x, y: scratch.y, size: ctx.size },
|
|
midPoint: { x: scratch.x, y: scratch.y },
|
|
cumulativeValue: scratch.yCumulative,
|
|
yValue: scratch.yDatum,
|
|
xValue: scratch.xDatum,
|
|
capDefaults: ctx.capDefaults,
|
|
labelText,
|
|
selected: scratch.selected
|
|
});
|
|
}
|
|
ctx.nodeIndex++;
|
|
}
|
|
this.updateSpanPoints(ctx, scratch);
|
|
}
|
|
/**
|
|
* Updates span points array based on current scratch values.
|
|
*/
|
|
updateSpanPoints(ctx, scratch) {
|
|
const currentSpanPoints = ctx.spanPoints.at(-1);
|
|
if (scratch.yDatum != null) {
|
|
const spanPoint = {
|
|
point: { x: scratch.x, y: scratch.y },
|
|
xDatum: scratch.xDatum,
|
|
yDatum: scratch.yCumulative
|
|
};
|
|
if (Array.isArray(currentSpanPoints)) {
|
|
currentSpanPoints.push(spanPoint);
|
|
} else if (currentSpanPoints == null) {
|
|
ctx.spanPoints.push([spanPoint]);
|
|
} else {
|
|
currentSpanPoints.skip += 1;
|
|
ctx.spanPoints.push([spanPoint]);
|
|
}
|
|
} else if (!ctx.connectMissingData) {
|
|
if (Array.isArray(currentSpanPoints) || currentSpanPoints == null) {
|
|
ctx.spanPoints.push({ skip: 0 });
|
|
} else {
|
|
currentSpanPoints.skip += 1;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Populates node data by iterating over the visible range.
|
|
*/
|
|
populateNodeData(ctx) {
|
|
const scratch = {
|
|
datum: void 0,
|
|
xDatum: void 0,
|
|
yDatum: void 0,
|
|
yCumulative: 0,
|
|
selected: void 0,
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
const indices = ctx.dataAggregationFilter?.indices;
|
|
let [start2, end3] = this.visibleRangeIndices("xValue", ctx.xAxis.range, indices);
|
|
start2 = Math.max(start2 - 1, 0);
|
|
end3 = Math.min(end3 + 1, indices?.length ?? ctx.xValues.length);
|
|
if (this.processedData.input.count < 1e3) {
|
|
start2 = 0;
|
|
end3 = this.processedData.input.count;
|
|
}
|
|
for (let i = start2; i < end3; i += 1) {
|
|
this.handleDatum(ctx, scratch, indices?.[i] ?? i);
|
|
}
|
|
}
|
|
/**
|
|
* Creates the initial result context object.
|
|
* Note: strokeData and segments are computed in assembleResult, but we need valid defaults
|
|
* for the early return case (when !this.visible).
|
|
*/
|
|
initializeResult(ctx) {
|
|
return {
|
|
itemId: ctx.yKey,
|
|
nodeData: ctx.nodes,
|
|
labelData: ctx.nodes,
|
|
strokeData: { itemId: ctx.yKey, spans: [] },
|
|
// Default for early return
|
|
scales: this.calculateScaling(),
|
|
visible: this.visible,
|
|
crossFiltering: false,
|
|
styles: getMarkerStyles(this, this.properties, this.properties.marker),
|
|
segments: void 0
|
|
};
|
|
}
|
|
/**
|
|
* Assembles the final result by computing strokeData, crossFiltering, and segments.
|
|
*/
|
|
assembleResult(ctx, result) {
|
|
const strokeSpans = ctx.spanPoints.flatMap((p) => {
|
|
return Array.isArray(p) ? interpolatePoints(p, this.properties.interpolation) : [];
|
|
});
|
|
result.strokeData = { itemId: ctx.yKey, spans: strokeSpans };
|
|
result.crossFiltering = ctx.selectionValues?.some((selectionValue, index) => selectionValue === ctx.yRawValues[index]) ?? false;
|
|
result.segments = calculateSegments(
|
|
this.properties.segmentation,
|
|
ctx.xAxis,
|
|
ctx.yAxis,
|
|
this.chart.seriesRect,
|
|
this.ctx.scene,
|
|
false
|
|
);
|
|
return result;
|
|
}
|
|
isPathOrSelectionDirty() {
|
|
return this.properties.marker.isDirty();
|
|
}
|
|
updatePathNodes(opts) {
|
|
const {
|
|
paths: [lineNode],
|
|
visible,
|
|
animationEnabled
|
|
} = opts;
|
|
const crossFiltering = this.contextNodeData?.crossFiltering === true;
|
|
const merged = mergeDefaults(this.getHighlightStyle(), this.getStyle());
|
|
const { strokeWidth, stroke: stroke3, strokeOpacity, lineDash, lineDashOffset, opacity } = merged;
|
|
const segments = this.contextNodeData?.segments;
|
|
lineNode.setProperties({
|
|
segments,
|
|
fill: void 0,
|
|
lineJoin: "round",
|
|
pointerEvents: 1 /* None */,
|
|
opacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity: strokeOpacity * (crossFiltering ? CROSS_FILTER_LINE_STROKE_OPACITY_FACTOR : 1),
|
|
lineDash,
|
|
lineDashOffset
|
|
});
|
|
lineNode.datum = segments;
|
|
if (!animationEnabled) {
|
|
lineNode.visible = visible;
|
|
}
|
|
updateClipPath(this, lineNode);
|
|
}
|
|
updateDatumSelection(opts) {
|
|
let { nodeData } = opts;
|
|
const { datumSelection } = opts;
|
|
const { contextNodeData, processedData, axes, properties } = this;
|
|
const { marker } = properties;
|
|
const markersEnabled = contextNodeData?.crossFiltering === true || markerEnabled(processedData.input.count, axes["x" /* X */].scale, marker);
|
|
nodeData = markersEnabled ? nodeData : [];
|
|
if (marker.isDirty()) {
|
|
datumSelection.clear();
|
|
datumSelection.cleanup();
|
|
}
|
|
if (!processedDataIsAnimatable(this.processedData)) {
|
|
return datumSelection.update(nodeData);
|
|
}
|
|
return datumSelection.update(nodeData, void 0, (datum) => createDatumId(datum.xValue));
|
|
}
|
|
updateDatumStyles(opts) {
|
|
const { datumSelection, isHighlight } = opts;
|
|
const { marker } = this.properties;
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
datumSelection.each((node, datum) => {
|
|
if (!datumSelection.isGarbage(node)) {
|
|
const highlightState = this.getHighlightState(highlightedDatum, opts.isHighlight, datum.datumIndex);
|
|
const stylerStyle = this.getStyle(highlightState);
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity } = stylerStyle;
|
|
const params = this.makeItemStylerParams(
|
|
this.dataModel,
|
|
this.processedData,
|
|
datum.datumIndex,
|
|
stylerStyle.marker
|
|
);
|
|
datum.style = this.getMarkerStyle(
|
|
marker,
|
|
datum,
|
|
params,
|
|
{ isHighlight, highlightState },
|
|
stylerStyle.marker,
|
|
{
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity
|
|
}
|
|
);
|
|
}
|
|
});
|
|
}
|
|
updateDatumNodes(opts) {
|
|
const { contextNodeData } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const { datumSelection, isHighlight } = opts;
|
|
const applyTranslation = this.ctx.animationManager.isSkipped();
|
|
const fillBBox = this.getShapeFillBBox();
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
const drawingMode = this.getDrawingMode(isHighlight, opts.drawingMode);
|
|
datumSelection.each((node, datum) => {
|
|
const state = this.getHighlightState(highlightedDatum, isHighlight, datum.datumIndex);
|
|
const style2 = datum.style ?? contextNodeData.styles[state];
|
|
this.applyMarkerStyle(style2, node, datum.point, fillBBox, {
|
|
applyTranslation,
|
|
selected: datum.selected
|
|
});
|
|
node.drawingMode = this.resolveMarkerDrawingModeForState(drawingMode, style2);
|
|
});
|
|
if (!isHighlight) {
|
|
this.properties.marker.markClean();
|
|
}
|
|
}
|
|
updateLabelSelection(opts) {
|
|
return opts.labelSelection.update(this.isLabelEnabled() ? opts.labelData : []);
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const { isHighlight = false } = opts;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const params = this.makeLabelFormatterParams();
|
|
opts.labelSelection.each((text2, datum) => {
|
|
const style2 = getLabelStyles(this, datum, params, this.properties.label, isHighlight, activeHighlight);
|
|
const { enabled, fontStyle, fontWeight: fontWeight2, fontSize, fontFamily, color: color2 } = style2;
|
|
if (enabled && datum?.labelText) {
|
|
text2.fontStyle = fontStyle;
|
|
text2.fontWeight = fontWeight2;
|
|
text2.fontSize = fontSize;
|
|
text2.fontFamily = fontFamily;
|
|
text2.textAlign = "center";
|
|
text2.textBaseline = "bottom";
|
|
text2.text = datum.labelText;
|
|
text2.x = datum.point.x;
|
|
text2.y = datum.point.y - 10;
|
|
text2.fill = color2;
|
|
text2.visible = true;
|
|
text2.fillOpacity = this.getHighlightStyle(isHighlight, datum.datumIndex).opacity ?? 1;
|
|
text2.setBoxing(style2);
|
|
} else {
|
|
text2.visible = false;
|
|
}
|
|
});
|
|
}
|
|
makeStylerParams(highlightStateEnum) {
|
|
const { id: seriesId } = this;
|
|
const { marker, lineDash, lineDashOffset, stroke: stroke3, strokeOpacity, strokeWidth, xKey, yKey } = this.properties;
|
|
const highlightState = toHighlightString(highlightStateEnum ?? 0 /* None */);
|
|
return {
|
|
marker: {
|
|
fill: marker.fill,
|
|
fillOpacity: marker.fillOpacity,
|
|
size: marker.size,
|
|
shape: marker.shape,
|
|
stroke: marker.stroke,
|
|
strokeOpacity: marker.strokeOpacity,
|
|
strokeWidth: marker.strokeWidth,
|
|
lineDash: marker.lineDash,
|
|
lineDashOffset: marker.lineDashOffset
|
|
},
|
|
highlightState,
|
|
lineDash,
|
|
lineDashOffset,
|
|
seriesId,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
xKey,
|
|
yKey
|
|
};
|
|
}
|
|
makeItemStylerParams(dataModel, processedData, datumIndex, style2) {
|
|
const { xKey, yKey } = this.properties;
|
|
const xValue = dataModel.resolveColumnById(this, `xValue`, processedData)[datumIndex];
|
|
const yValue = dataModel.resolveColumnById(this, `yValueRaw`, processedData)[datumIndex];
|
|
const xDomain = dataModel.getDomain(this, `xValue`, "key", processedData).domain;
|
|
const yDomain = dataModel.getDomain(this, this.yCumulativeKey(processedData), "value", processedData).domain;
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
...datumStylerProperties(xValue, yValue, xKey, yKey, xDomain, yDomain),
|
|
xValue,
|
|
yValue,
|
|
...style2,
|
|
fill
|
|
};
|
|
}
|
|
makeLabelFormatterParams() {
|
|
const { xKey, xName, yKey, yName, legendItemName } = this.properties;
|
|
return { xKey, xName, yKey, yName, legendItemName };
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, axes, properties } = this;
|
|
const { xKey, xName, yKey, yName, tooltip, legendItemName } = properties;
|
|
const allowNullKeys = properties.allowNullKeys ?? false;
|
|
const xAxis = axes["x" /* X */];
|
|
const yAxis = axes["y" /* Y */];
|
|
if (!dataModel || !processedData || !xAxis || !yAxis)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data?.[datumIndex];
|
|
const xValue = dataModel.resolveColumnById(this, `xValue`, processedData)[datumIndex];
|
|
const yValue = dataModel.resolveColumnById(this, `yValueRaw`, processedData)[datumIndex];
|
|
if (xValue === void 0 && !allowNullKeys)
|
|
return;
|
|
const stylerStyle = this.getStyle();
|
|
const params = this.makeItemStylerParams(dataModel, processedData, datumIndex, stylerStyle.marker);
|
|
const format = this.getMarkerStyle(
|
|
this.properties.marker,
|
|
{ datumIndex, datum },
|
|
params,
|
|
{ isHighlight: false },
|
|
stylerStyle.marker
|
|
);
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading: this.getAxisValueText(xAxis, "tooltip", xValue, datum, xKey, legendItemName, allowNullKeys),
|
|
symbol: this.legendItemSymbol(),
|
|
data: [
|
|
{
|
|
label: yName,
|
|
fallbackLabel: yKey,
|
|
value: this.getAxisValueText(yAxis, "tooltip", yValue, datum, yKey, legendItemName),
|
|
missing: isTooltipValueMissing(yValue)
|
|
}
|
|
]
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: yName,
|
|
xKey,
|
|
xName,
|
|
yKey,
|
|
yName,
|
|
...format,
|
|
...this.getModuleTooltipParams()
|
|
}
|
|
);
|
|
}
|
|
legendItemSymbol() {
|
|
const { stroke: stroke3, strokeOpacity, strokeWidth, lineDash, marker } = this.getStyle();
|
|
const markerStyle = this.getMarkerStyle(
|
|
this.properties.marker,
|
|
{},
|
|
void 0,
|
|
{
|
|
isHighlight: false,
|
|
checkForHighlight: false
|
|
},
|
|
{
|
|
size: marker.size,
|
|
shape: marker.shape,
|
|
fill: marker.fill,
|
|
fillOpacity: marker.fillOpacity,
|
|
stroke: marker.stroke,
|
|
strokeOpacity: marker.strokeOpacity,
|
|
strokeWidth: marker.strokeWidth,
|
|
lineDash: marker.lineDash,
|
|
lineDashOffset: marker.lineDashOffset
|
|
}
|
|
);
|
|
return {
|
|
marker: {
|
|
...markerStyle,
|
|
enabled: this.properties.marker.enabled
|
|
},
|
|
line: {
|
|
enabled: true,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
if (legendType !== "category") {
|
|
return [];
|
|
}
|
|
const {
|
|
id: seriesId,
|
|
ctx: { legendManager },
|
|
visible
|
|
} = this;
|
|
const { yKey: itemId, yName, title, legendItemName, showInLegend } = this.properties;
|
|
return [
|
|
{
|
|
legendType,
|
|
id: seriesId,
|
|
itemId,
|
|
legendItemName,
|
|
seriesId,
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId, itemId }),
|
|
label: {
|
|
text: legendItemName ?? title ?? yName ?? itemId
|
|
},
|
|
symbol: this.legendItemSymbol(),
|
|
hideInLegend: !showInLegend
|
|
}
|
|
];
|
|
}
|
|
updatePaths(opts) {
|
|
this.updateLinePaths(opts.paths, opts.contextData);
|
|
}
|
|
updateLinePaths(paths, contextData) {
|
|
const spans = contextData.strokeData.spans;
|
|
const [lineNode] = paths;
|
|
lineNode.path.clear();
|
|
plotLinePathStroke(lineNode, spans);
|
|
lineNode.markDirty("LineSeries");
|
|
}
|
|
resetDatumAnimation(data) {
|
|
resetMarkerSelectionsDirect([data.datumSelection]);
|
|
}
|
|
animateEmptyUpdateReady(animationData) {
|
|
const { datumSelection, labelSelection, annotationSelections, contextData, paths } = animationData;
|
|
const { animationManager } = this.ctx;
|
|
this.updateLinePaths(paths, contextData);
|
|
pathSwipeInAnimation(this, animationManager, ...paths);
|
|
resetMotion([datumSelection], resetMarkerPositionFn);
|
|
markerSwipeScaleInAnimation(
|
|
this,
|
|
animationManager,
|
|
{ ...this.getAnimationDrawingModes(), phase: "initial" },
|
|
datumSelection
|
|
);
|
|
seriesLabelFadeInAnimation(this, "labels", animationManager, labelSelection);
|
|
seriesLabelFadeInAnimation(this, "annotations", animationManager, ...annotationSelections);
|
|
}
|
|
animateReadyResize(animationData) {
|
|
const { contextData, paths } = animationData;
|
|
this.updateLinePaths(paths, contextData);
|
|
super.animateReadyResize(animationData);
|
|
}
|
|
animateWaitingUpdateReady(animationData) {
|
|
const { animationManager } = this.ctx;
|
|
const {
|
|
datumSelection,
|
|
labelSelection: labelSelections,
|
|
annotationSelections,
|
|
contextData,
|
|
paths,
|
|
previousContextData
|
|
} = animationData;
|
|
const [path] = paths;
|
|
if (contextData.visible === false && previousContextData?.visible === false)
|
|
return;
|
|
this.resetDatumAnimation(animationData);
|
|
this.resetLabelAnimation(animationData);
|
|
const update = () => {
|
|
this.resetPathAnimation(animationData);
|
|
this.updateLinePaths(paths, contextData);
|
|
};
|
|
const skip = () => {
|
|
animationManager.skipCurrentBatch();
|
|
update();
|
|
};
|
|
if (contextData == null || previousContextData == null) {
|
|
update();
|
|
markerFadeInAnimation(this, animationManager, "added", this.getAnimationDrawingModes(), datumSelection);
|
|
staticFromToMotion(
|
|
this.id,
|
|
"path_properties",
|
|
animationManager,
|
|
[path],
|
|
{ opacity: 0 },
|
|
{ opacity: this.getOpacity() },
|
|
{ phase: "add" }
|
|
);
|
|
seriesLabelFadeInAnimation(this, "labels", animationManager, labelSelections);
|
|
seriesLabelFadeInAnimation(this, "annotations", animationManager, ...annotationSelections);
|
|
return;
|
|
}
|
|
if (contextData.crossFiltering !== previousContextData.crossFiltering) {
|
|
skip();
|
|
return;
|
|
}
|
|
const fns = prepareLinePathAnimation(
|
|
contextData,
|
|
previousContextData,
|
|
this.processedData?.reduced?.diff?.[this.id],
|
|
this.getOpacity()
|
|
);
|
|
if (fns === void 0) {
|
|
skip();
|
|
return;
|
|
} else if (fns.status === "no-op") {
|
|
return;
|
|
}
|
|
fromToMotion(this.id, "path_properties", animationManager, [path], fns.stroke.pathProperties);
|
|
if (fns.status === "added") {
|
|
this.updateLinePaths(paths, contextData);
|
|
} else if (fns.status === "removed") {
|
|
this.updateLinePaths(paths, previousContextData);
|
|
} else {
|
|
pathMotion(this.id, "path_update", animationManager, [path], fns.stroke.path);
|
|
}
|
|
if (fns.hasMotion) {
|
|
markerFadeInAnimation(this, animationManager, void 0, this.getAnimationDrawingModes(), datumSelection);
|
|
seriesLabelFadeInAnimation(this, "labels", animationManager, labelSelections);
|
|
seriesLabelFadeInAnimation(this, "annotations", animationManager, ...annotationSelections);
|
|
}
|
|
this.ctx.animationManager.animate({
|
|
id: this.id,
|
|
groupId: "reset_after_animation",
|
|
phase: "trailing",
|
|
from: {},
|
|
to: {},
|
|
onComplete: () => this.updateLinePaths(paths, contextData)
|
|
});
|
|
}
|
|
isLabelEnabled() {
|
|
return this.properties.label.enabled;
|
|
}
|
|
getBandScalePadding() {
|
|
return { inner: 1, outer: 0.1 };
|
|
}
|
|
nodeFactory() {
|
|
return new Marker();
|
|
}
|
|
getStyle(highlightState) {
|
|
const { styler, marker, lineDash, lineDashOffset, stroke: stroke3, strokeOpacity, strokeWidth } = this.properties;
|
|
const { size, shape, fill = "transparent", fillOpacity } = marker;
|
|
let stylerResult = {};
|
|
if (styler) {
|
|
const stylerParams = this.makeStylerParams(highlightState);
|
|
const cbResult = this.cachedCallWithContext(styler, stylerParams) ?? {};
|
|
const resolved = this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`],
|
|
cbResult,
|
|
{ pick: false }
|
|
);
|
|
stylerResult = resolved ?? {};
|
|
}
|
|
stylerResult.marker ?? (stylerResult.marker = {});
|
|
return {
|
|
lineDash: stylerResult.lineDash ?? lineDash,
|
|
lineDashOffset: stylerResult.lineDashOffset ?? lineDashOffset,
|
|
stroke: stylerResult.stroke ?? stroke3,
|
|
strokeOpacity: stylerResult.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: stylerResult.strokeWidth ?? strokeWidth,
|
|
marker: {
|
|
fill: stylerResult.marker.fill ?? fill,
|
|
fillOpacity: stylerResult.marker.fillOpacity ?? fillOpacity,
|
|
shape: stylerResult.marker.shape ?? shape,
|
|
size: stylerResult.marker.size ?? size,
|
|
lineDash: stylerResult.marker.lineDash ?? marker.lineDash ?? lineDash,
|
|
lineDashOffset: stylerResult.marker.lineDashOffset ?? marker.lineDashOffset ?? lineDashOffset,
|
|
stroke: stylerResult.marker.stroke ?? marker.stroke ?? stroke3,
|
|
strokeOpacity: stylerResult.marker.strokeOpacity ?? marker.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: stylerResult.marker.strokeWidth ?? marker.strokeWidth ?? strokeWidth
|
|
}
|
|
};
|
|
}
|
|
getFormattedMarkerStyle(datum) {
|
|
const stylerStyle = this.getStyle();
|
|
const params = this.makeItemStylerParams(
|
|
this.dataModel,
|
|
this.processedData,
|
|
datum.datumIndex,
|
|
stylerStyle.marker
|
|
);
|
|
return this.getMarkerStyle(
|
|
this.properties.marker,
|
|
datum,
|
|
params,
|
|
{ isHighlight: true },
|
|
void 0,
|
|
stylerStyle
|
|
);
|
|
}
|
|
computeFocusBounds(opts) {
|
|
return computeMarkerFocusBounds(this, opts);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.styler != null || this.properties.marker.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
LineSeries.className = "LineSeries";
|
|
LineSeries.type = "line";
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/lineSeriesModule.ts
|
|
var themeTemplate5 = {
|
|
series: {
|
|
stroke: SAFE_STROKE_FILL_OPERATION,
|
|
strokeWidth: 2,
|
|
strokeOpacity: 1,
|
|
lineDash: [0],
|
|
lineDashOffset: 0,
|
|
interpolation: {
|
|
type: "linear"
|
|
},
|
|
marker: {
|
|
shape: "circle",
|
|
size: 7,
|
|
strokeWidth: { $isUserOption: ["./stroke", 1, 0] },
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: { $palette: "stroke" }
|
|
},
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" }
|
|
},
|
|
tooltip: {
|
|
range: {
|
|
$if: [
|
|
{ $eq: [{ $path: ["/tooltip/range", "nearest"] }, "area"] },
|
|
"nearest",
|
|
{ $path: ["/tooltip/range", "nearest"] }
|
|
]
|
|
},
|
|
position: {
|
|
anchorTo: { $path: ["/tooltip/position/anchorTo", "node"] }
|
|
}
|
|
},
|
|
highlight: MARKER_SERIES_HIGHLIGHT_STYLE,
|
|
segmentation: SEGMENTATION_DEFAULTS
|
|
}
|
|
};
|
|
var LineSeriesModule = {
|
|
type: "series",
|
|
name: "line",
|
|
chartType: "cartesian",
|
|
stackable: true,
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: lineSeriesOptionsDef,
|
|
predictAxis: predictCartesianNonPrimitiveAxis,
|
|
defaultAxes: {
|
|
y: {
|
|
type: "number" /* NUMBER */,
|
|
position: "left" /* LEFT */
|
|
},
|
|
x: {
|
|
type: "category" /* CATEGORY */,
|
|
position: "bottom" /* BOTTOM */
|
|
}
|
|
},
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
themeTemplate: themeTemplate5,
|
|
create: (ctx) => new LineSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/scatterSeries.ts
|
|
var ScatterSeries = class extends BubbleSeries {
|
|
};
|
|
ScatterSeries.className = "ScatterSeries";
|
|
ScatterSeries.type = "scatter";
|
|
|
|
// packages/ag-charts-community/src/chart/series/cartesian/scatterSeriesModule.ts
|
|
var themeTemplate6 = {
|
|
series: {
|
|
shape: "circle",
|
|
size: 7,
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: { $palette: "stroke" },
|
|
fillOpacity: 0.8,
|
|
maxRenderedItems: 2e3,
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" }
|
|
},
|
|
tooltip: {
|
|
range: {
|
|
$if: [
|
|
{ $eq: [{ $path: ["/tooltip/range", "nearest"] }, "area"] },
|
|
"nearest",
|
|
{ $path: ["/tooltip/range", "nearest"] }
|
|
]
|
|
},
|
|
position: {
|
|
anchorTo: { $path: ["/tooltip/position/anchorTo", "node"] }
|
|
}
|
|
},
|
|
highlight: MULTI_SERIES_HIGHLIGHT_STYLE
|
|
}
|
|
};
|
|
var ScatterSeriesModule = {
|
|
type: "series",
|
|
name: "scatter",
|
|
chartType: "cartesian",
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: scatterSeriesOptionsDef,
|
|
predictAxis: predictCartesianAxis,
|
|
defaultAxes: {
|
|
x: {
|
|
type: "number" /* NUMBER */,
|
|
position: "bottom" /* BOTTOM */
|
|
},
|
|
y: {
|
|
type: "number" /* NUMBER */,
|
|
position: "left" /* LEFT */
|
|
}
|
|
},
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
themeTemplate: themeTemplate6,
|
|
create: (ctx) => new ScatterSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/polar/donutSeriesProperties.ts
|
|
var DonutTitle = class extends Caption {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.showInLegend = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutTitle.prototype, "showInLegend", 2);
|
|
var DonutInnerLabel = class extends Label {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.spacing = 2;
|
|
}
|
|
set(properties, _reset) {
|
|
return super.set(properties);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutInnerLabel.prototype, "text", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutInnerLabel.prototype, "spacing", 2);
|
|
var DonutInnerCircle = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = "transparent";
|
|
this.fillOpacity = 1;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutInnerCircle.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutInnerCircle.prototype, "fillOpacity", 2);
|
|
var DonutSeriesCalloutLabel = class extends Label {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.offset = 3;
|
|
this.minAngle = 0;
|
|
this.minSpacing = 4;
|
|
this.maxCollisionOffset = 50;
|
|
this.avoidCollisions = true;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesCalloutLabel.prototype, "offset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesCalloutLabel.prototype, "minAngle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesCalloutLabel.prototype, "minSpacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesCalloutLabel.prototype, "maxCollisionOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesCalloutLabel.prototype, "avoidCollisions", 2);
|
|
var DonutSeriesSectorLabel = class extends Label {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.positionOffset = 0;
|
|
this.positionRatio = 0.5;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesSectorLabel.prototype, "positionOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesSectorLabel.prototype, "positionRatio", 2);
|
|
var DonutSeriesCalloutLine = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.length = 10;
|
|
this.strokeWidth = 1;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesCalloutLine.prototype, "colors", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesCalloutLine.prototype, "length", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesCalloutLine.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesCalloutLine.prototype, "itemStyler", 2);
|
|
var DonutSeriesProperties = class extends SeriesProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.defaultColorRange = [];
|
|
this.defaultPatternFills = [];
|
|
this.fills = Object.values(DEFAULT_FILLS);
|
|
this.strokes = Object.values(DEFAULT_STROKES);
|
|
this.fillOpacity = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.cornerRadius = 0;
|
|
this.rotation = 0;
|
|
this.outerRadiusOffset = 0;
|
|
this.outerRadiusRatio = 1;
|
|
this.strokeWidth = 1;
|
|
this.sectorSpacing = 0;
|
|
this.hideZeroValueSectorsInLegend = false;
|
|
this.innerLabels = new PropertiesArray(DonutInnerLabel);
|
|
this.title = new DonutTitle();
|
|
this.innerCircle = new DonutInnerCircle();
|
|
this.shadow = new DropShadow();
|
|
this.calloutLabel = new DonutSeriesCalloutLabel();
|
|
this.sectorLabel = new DonutSeriesSectorLabel();
|
|
this.calloutLine = new DonutSeriesCalloutLine();
|
|
this.tooltip = makeSeriesTooltip();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "angleKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "angleName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "angleFilterKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "radiusKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "radiusName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "radiusMin", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "radiusMax", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "calloutLabelKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "calloutLabelName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "sectorLabelKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "sectorLabelName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "legendItemKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "defaultColorRange", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "defaultPatternFills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "fills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "strokes", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "rotation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "outerRadiusOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "outerRadiusRatio", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "innerRadiusOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "innerRadiusRatio", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "sectorSpacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "hideZeroValueSectorsInLegend", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "innerLabels", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "title", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "innerCircle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "shadow", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "calloutLabel", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "sectorLabel", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "calloutLine", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DonutSeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-community/src/chart/series/polar/pieUtil.ts
|
|
function preparePieSeriesAnimationFunctions(initialLoad, rotationDegrees, scaleFn, oldScaleFn) {
|
|
const scale2 = [scaleFn.convert(0), scaleFn.convert(1)];
|
|
const oldScale = [oldScaleFn.convert(0), oldScaleFn.convert(1)];
|
|
const rotation = Math.PI / -2 + toRadians(rotationDegrees);
|
|
const phase = initialLoad ? "initial" : "update";
|
|
const scaleToNewRadius = ({ radius }) => {
|
|
return { innerRadius: scale2[0], outerRadius: scale2[0] + (scale2[1] - scale2[0]) * radius };
|
|
};
|
|
const scaleToOldRadius = ({ radius }) => {
|
|
return { innerRadius: oldScale[0], outerRadius: oldScale[0] + (oldScale[1] - oldScale[0]) * radius };
|
|
};
|
|
const fromFn = (sect, datum, status, { prevFromProps }) => {
|
|
let { startAngle, endAngle, innerRadius, outerRadius } = sect;
|
|
let { fill, stroke: stroke3 } = datum.sectorFormat;
|
|
if (status === "updated" && sect.previousDatum == null) {
|
|
status = "added";
|
|
}
|
|
if (status === "unknown" || status === "added" && !prevFromProps) {
|
|
startAngle = rotation;
|
|
endAngle = rotation;
|
|
innerRadius = datum.innerRadius;
|
|
outerRadius = datum.outerRadius;
|
|
} else if (status === "added" && prevFromProps) {
|
|
startAngle = prevFromProps.endAngle ?? rotation;
|
|
endAngle = prevFromProps.endAngle ?? rotation;
|
|
innerRadius = prevFromProps.innerRadius ?? datum.innerRadius;
|
|
outerRadius = prevFromProps.outerRadius ?? datum.outerRadius;
|
|
}
|
|
if (status === "added" && !initialLoad) {
|
|
const radii = scaleToOldRadius(datum);
|
|
innerRadius = radii.innerRadius;
|
|
outerRadius = radii.outerRadius;
|
|
}
|
|
if (status === "updated") {
|
|
fill = sect.fill ?? fill;
|
|
stroke3 = (typeof sect.stroke === "string" ? sect.stroke : void 0) ?? stroke3;
|
|
}
|
|
const animatableFill = typeof fill === "string" ? { fill } : {};
|
|
return { startAngle, endAngle, innerRadius, outerRadius, stroke: stroke3, phase, ...animatableFill };
|
|
};
|
|
const toFn = (_sect, datum, status, { prevLive }) => {
|
|
let { startAngle, endAngle, innerRadius, outerRadius } = datum;
|
|
const { stroke: stroke3, fill } = datum.sectorFormat;
|
|
if (status === "removed" && prevLive) {
|
|
startAngle = prevLive.datum?.endAngle;
|
|
endAngle = prevLive.datum?.endAngle;
|
|
} else if (status === "removed" && !prevLive) {
|
|
startAngle = rotation;
|
|
endAngle = rotation;
|
|
}
|
|
if (status === "removed") {
|
|
const radii = scaleToNewRadius(datum);
|
|
innerRadius = radii.innerRadius;
|
|
outerRadius = radii.outerRadius;
|
|
}
|
|
const animatableFill = typeof fill === "string" ? { fill } : {};
|
|
return { startAngle, endAngle, outerRadius, innerRadius, stroke: stroke3, ...animatableFill };
|
|
};
|
|
const innerCircleFromFn = (node, _) => {
|
|
return { size: node.previousDatum?.radius ?? node.size ?? 0, phase };
|
|
};
|
|
const innerCircleToFn = (_, datum) => {
|
|
return { size: datum.radius ?? 0 };
|
|
};
|
|
return { nodes: { toFn, fromFn }, innerCircle: { fromFn: innerCircleFromFn, toFn: innerCircleToFn } };
|
|
}
|
|
function resetPieSelectionsFn(_node, datum) {
|
|
return {
|
|
startAngle: datum.startAngle,
|
|
endAngle: datum.endAngle,
|
|
innerRadius: datum.innerRadius,
|
|
outerRadius: datum.outerRadius,
|
|
stroke: datum.sectorFormat.stroke
|
|
};
|
|
}
|
|
function pickByMatchingAngle(series, point) {
|
|
const dy2 = point.y - series.centerY;
|
|
const dx2 = point.x - series.centerX;
|
|
const angle2 = Math.atan2(dy2, dx2);
|
|
const sectors = series.getItemNodes();
|
|
for (const sector of sectors) {
|
|
if (sector.datum.missing === true)
|
|
continue;
|
|
if (isBetweenAngles(angle2, sector.startAngle, sector.endAngle)) {
|
|
const radius = Math.hypot(dx2, dy2);
|
|
let distance2 = 0;
|
|
if (radius < sector.innerRadius) {
|
|
distance2 = sector.innerRadius - radius;
|
|
} else if (radius > sector.outerRadius) {
|
|
distance2 = radius - sector.outerRadius;
|
|
}
|
|
return { datum: sector.datum, distance: distance2 };
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/chart/series/polar/donutSeries.ts
|
|
var PieDonutSeriesNodeEvent = class extends SeriesNodeEvent {
|
|
constructor(type, nativeEvent, datum, series) {
|
|
super(type, nativeEvent, datum, series);
|
|
this.angleKey = series.properties.angleKey;
|
|
this.radiusKey = series.properties.radiusKey;
|
|
this.calloutLabelKey = series.properties.calloutLabelKey;
|
|
this.sectorLabelKey = series.properties.sectorLabelKey;
|
|
}
|
|
};
|
|
var DonutSeries = class extends PolarSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
categoryKey: void 0,
|
|
propertyKeys: {
|
|
...DEFAULT_POLAR_DIRECTION_KEYS,
|
|
sectorLabel: ["sectorLabelKey"],
|
|
calloutLabel: ["calloutLabelKey"]
|
|
},
|
|
propertyNames: {
|
|
...DEFAULT_POLAR_DIRECTION_NAMES,
|
|
sectorLabel: ["sectorLabelName"],
|
|
calloutLabel: ["calloutLabelName"]
|
|
},
|
|
pickModes: [1 /* NEAREST_NODE */, 0 /* EXACT_SHAPE_MATCH */],
|
|
animationResetFns: { item: resetPieSelectionsFn, label: resetLabelFn }
|
|
});
|
|
this.properties = new DonutSeriesProperties();
|
|
this.phantomNodeData = void 0;
|
|
this.backgroundGroup = new TranslatableGroup({
|
|
name: `${this.id}-background`,
|
|
zIndex: 0 /* BACKGROUND */
|
|
});
|
|
this.noVisibleData = false;
|
|
this.previousRadiusScale = new LinearScale();
|
|
this.radiusScale = new LinearScale();
|
|
this.phantomGroup = this.contentGroup.appendChild(new Group({ name: "phantom", zIndex: -1 }));
|
|
this.phantomSelection = Selection.select(
|
|
this.phantomGroup,
|
|
() => this.nodeFactory(),
|
|
false
|
|
);
|
|
this.phantomHighlightGroup = this.highlightGroup.appendChild(new Group({ name: "phantom", zIndex: -1 }));
|
|
this.phantomHighlightSelection = Selection.select(
|
|
this.phantomHighlightGroup,
|
|
() => this.nodeFactory(),
|
|
false
|
|
);
|
|
this.calloutLabelGroup = this.contentGroup.appendChild(new Group({ name: "pieCalloutLabels" }));
|
|
this.calloutLabelSelection = new Selection(
|
|
this.calloutLabelGroup,
|
|
Group
|
|
);
|
|
// AG-6193 If the sum of all datums is 0, then we'll draw 1 or 2 rings to represent the empty series.
|
|
this.zerosumRingsGroup = this.backgroundGroup.appendChild(new Group({ name: `${this.id}-zerosumRings` }));
|
|
this.zerosumOuterRing = this.zerosumRingsGroup.appendChild(new Marker({ shape: "circle" }));
|
|
this.zerosumInnerRing = this.zerosumRingsGroup.appendChild(new Marker({ shape: "circle" }));
|
|
this.innerLabelsGroup = this.contentGroup.appendChild(new Group({ name: "innerLabels" }));
|
|
this.innerCircleGroup = this.backgroundGroup.appendChild(new Group({ name: `${this.id}-innerCircle` }));
|
|
this.innerLabelsSelection = Selection.select(this.innerLabelsGroup, Text);
|
|
this.innerCircleSelection = Selection.select(
|
|
this.innerCircleGroup,
|
|
() => new Marker({ shape: "circle" })
|
|
);
|
|
this.surroundingRadius = void 0;
|
|
this.NodeEvent = PieDonutSeriesNodeEvent;
|
|
this.angleScale = new LinearScale();
|
|
this.angleScale.domain = [0, 1];
|
|
this.angleScale.range = [-Math.PI, Math.PI].map((angle2) => angle2 + Math.PI / 2);
|
|
this.phantomGroup.opacity = 0.2;
|
|
this.phantomHighlightGroup.opacity = 0.2;
|
|
this.innerLabelsGroup.pointerEvents = 1 /* None */;
|
|
}
|
|
get calloutNodeData() {
|
|
return this.phantomNodeData ?? this.nodeData;
|
|
}
|
|
attachSeries(seriesContentNode, seriesNode, annotationNode) {
|
|
super.attachSeries(seriesContentNode, seriesNode, annotationNode);
|
|
seriesContentNode?.appendChild(this.backgroundGroup);
|
|
}
|
|
detachSeries(seriesContentNode, seriesNode, annotationNode) {
|
|
super.detachSeries(seriesContentNode, seriesNode, annotationNode);
|
|
this.backgroundGroup.remove();
|
|
}
|
|
setZIndex(zIndex) {
|
|
super.setZIndex(zIndex);
|
|
this.backgroundGroup.zIndex = [0 /* BACKGROUND */, zIndex];
|
|
}
|
|
nodeFactory() {
|
|
const sector = new Sector();
|
|
sector.miterLimit = 1e9;
|
|
return sector;
|
|
}
|
|
getSeriesDomain(direction) {
|
|
if (direction === "angle" /* Angle */) {
|
|
return { domain: this.angleScale.domain };
|
|
} else {
|
|
return { domain: this.radiusScale.domain };
|
|
}
|
|
}
|
|
async processData(dataController) {
|
|
if (this.data == null)
|
|
return;
|
|
const {
|
|
visible,
|
|
id: seriesId,
|
|
ctx: { legendManager }
|
|
} = this;
|
|
const { angleKey, angleFilterKey, radiusKey, calloutLabelKey, sectorLabelKey, legendItemKey } = this.properties;
|
|
const processor = () => (value, index) => {
|
|
if (visible && legendManager.getItemEnabled({ seriesId, itemId: index })) {
|
|
return value;
|
|
}
|
|
return 0;
|
|
};
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
const extraKeyProps = [];
|
|
const extraProps = [];
|
|
if (legendItemKey) {
|
|
extraKeyProps.push(keyProperty(legendItemKey, "category", { id: `legendItemKey`, allowNullKey }));
|
|
} else if (calloutLabelKey) {
|
|
extraKeyProps.push(keyProperty(calloutLabelKey, "category", { id: `calloutLabelKey`, allowNullKey }));
|
|
} else if (sectorLabelKey) {
|
|
extraKeyProps.push(keyProperty(sectorLabelKey, "category", { id: `sectorLabelKey`, allowNullKey }));
|
|
}
|
|
const radiusScaleType = this.radiusScale.type;
|
|
const angleScaleType = this.angleScale.type;
|
|
if (radiusKey) {
|
|
extraProps.push(
|
|
rangedValueProperty(radiusKey, {
|
|
id: "radiusValue",
|
|
min: this.properties.radiusMin ?? 0,
|
|
max: this.properties.radiusMax,
|
|
missingValue: this.properties.radiusMax ?? 1,
|
|
processor
|
|
}),
|
|
valueProperty(radiusKey, radiusScaleType, { id: `radiusRaw`, processor }),
|
|
// Raw value pass-through.
|
|
normalisePropertyTo("radiusValue", [0, 1], 1, this.properties.radiusMin ?? 0, this.properties.radiusMax)
|
|
);
|
|
}
|
|
if (calloutLabelKey) {
|
|
extraProps.push(valueProperty(calloutLabelKey, "category", { id: `calloutLabelValue`, allowNullKey }));
|
|
}
|
|
if (sectorLabelKey) {
|
|
extraProps.push(valueProperty(sectorLabelKey, "category", { id: `sectorLabelValue`, allowNullKey }));
|
|
}
|
|
if (legendItemKey) {
|
|
extraProps.push(valueProperty(legendItemKey, "category", { id: `legendItemValue`, allowNullKey }));
|
|
}
|
|
if (angleFilterKey) {
|
|
extraProps.push(
|
|
accumulativeValueProperty(angleFilterKey, angleScaleType, {
|
|
id: `angleFilterValue`,
|
|
onlyPositive: true,
|
|
invalidValue: 0,
|
|
processor
|
|
}),
|
|
valueProperty(angleFilterKey, angleScaleType, { id: `angleFilterRaw` }),
|
|
normalisePropertyTo("angleFilterValue", [0, 1], 0, 0)
|
|
);
|
|
}
|
|
if (animationEnabled && this.processedData?.reduced?.animationValidation?.uniqueKeys && extraKeyProps.length > 0) {
|
|
extraProps.push(diff(this.id, this.processedData));
|
|
}
|
|
extraProps.push(animationValidation());
|
|
await this.requestDataModel(dataController, this.data, {
|
|
props: [
|
|
...extraKeyProps,
|
|
accumulativeValueProperty(angleKey, angleScaleType, {
|
|
id: `angleValue`,
|
|
onlyPositive: true,
|
|
invalidValue: 0,
|
|
processor
|
|
}),
|
|
valueProperty(angleKey, angleScaleType, { id: `angleRaw` }),
|
|
// Raw value pass-through.
|
|
normalisePropertyTo("angleValue", [0, 1], 0, 0),
|
|
...extraProps
|
|
]
|
|
});
|
|
for (const valueDef of this.processedData?.defs?.values ?? []) {
|
|
const { id, missing, property } = valueDef;
|
|
const missCount = getMissCount(this, missing);
|
|
if (id !== "angleRaw" && missCount > 0) {
|
|
logger_exports.warnOnce(
|
|
`no value was found for the key '${String(property)}' on ${missCount} data element${missCount > 1 ? "s" : ""}`
|
|
);
|
|
}
|
|
}
|
|
this.animationState.transition("updateData");
|
|
}
|
|
maybeRefreshNodeData() {
|
|
if (!this.nodeDataRefresh)
|
|
return;
|
|
const { nodeData = [], phantomNodeData } = this.createNodeData() ?? {};
|
|
this.nodeData = nodeData;
|
|
this.phantomNodeData = phantomNodeData;
|
|
if (nodeData.length > 0) {
|
|
debugMetrics_exports.record(`${this.type}:nodeData`, nodeData.length);
|
|
}
|
|
this.nodeDataRefresh = false;
|
|
}
|
|
getProcessedDataValues(dataModel, processedData) {
|
|
const angleValues = dataModel.resolveColumnById(this, `angleValue`, processedData);
|
|
const angleRawValues = dataModel.resolveColumnById(this, `angleRaw`, processedData);
|
|
const angleFilterValues = this.properties.angleFilterKey == null ? void 0 : dataModel.resolveColumnById(this, `angleFilterValue`, processedData);
|
|
const angleFilterRawValues = this.properties.angleFilterKey == null ? void 0 : dataModel.resolveColumnById(this, `angleFilterRaw`, processedData);
|
|
const radiusValues = this.properties.radiusKey ? dataModel.resolveColumnById(this, `radiusValue`, processedData) : void 0;
|
|
const radiusRawValues = this.properties.radiusKey ? dataModel.resolveColumnById(this, `radiusRaw`, processedData) : void 0;
|
|
const calloutLabelValues = this.properties.calloutLabelKey ? dataModel.resolveColumnById(this, `calloutLabelValue`, processedData) : void 0;
|
|
const sectorLabelValues = this.properties.sectorLabelKey ? dataModel.resolveColumnById(this, `sectorLabelValue`, processedData) : void 0;
|
|
const legendItemValues = this.properties.legendItemKey ? dataModel.resolveColumnById(this, `legendItemValue`, processedData) : void 0;
|
|
return {
|
|
angleValues,
|
|
angleRawValues,
|
|
angleFilterValues,
|
|
angleFilterRawValues,
|
|
radiusValues,
|
|
radiusRawValues,
|
|
calloutLabelValues,
|
|
sectorLabelValues,
|
|
legendItemValues
|
|
};
|
|
}
|
|
createNodeData() {
|
|
const {
|
|
id: seriesId,
|
|
processedData,
|
|
dataModel,
|
|
angleScale,
|
|
ctx: { legendManager },
|
|
visible
|
|
} = this;
|
|
const { rotation, innerRadiusRatio } = this.properties;
|
|
if (!dataModel || processedData?.type !== "ungrouped")
|
|
return;
|
|
const processedDataValues = this.getProcessedDataValues(dataModel, processedData);
|
|
const {
|
|
angleValues,
|
|
angleRawValues,
|
|
angleFilterValues,
|
|
angleFilterRawValues,
|
|
radiusValues,
|
|
radiusRawValues,
|
|
legendItemValues
|
|
} = processedDataValues;
|
|
const useFilterAngles = angleFilterRawValues?.some((filterRawValue, index) => {
|
|
return filterRawValue > angleRawValues[index];
|
|
}) ?? false;
|
|
let currentStart = 0;
|
|
let sum = 0;
|
|
const nodes = [];
|
|
const phantomNodes = angleFilterRawValues == null ? void 0 : [];
|
|
const rawData = processedData.dataSources.get(this.id)?.data ?? [];
|
|
const invalidData = processedData.invalidData?.get(this.id);
|
|
for (const [datumIndex, datum] of rawData.entries()) {
|
|
if (invalidData?.[datumIndex] === true)
|
|
continue;
|
|
const currentValue = useFilterAngles ? angleFilterValues[datumIndex] : angleValues[datumIndex];
|
|
const crossFilterScale = angleFilterRawValues != null && !useFilterAngles ? Math.sqrt(angleFilterRawValues[datumIndex] / angleRawValues[datumIndex]) : 1;
|
|
const startAngle = angleScale.convert(currentStart) + toRadians(rotation);
|
|
currentStart = currentValue;
|
|
sum += currentValue;
|
|
const endAngle = angleScale.convert(currentStart) + toRadians(rotation);
|
|
const span = Math.abs(endAngle - startAngle);
|
|
const midAngle = startAngle + span / 2;
|
|
const angleValue = angleRawValues[datumIndex];
|
|
const radiusRaw = radiusValues?.[datumIndex] ?? 1;
|
|
const radius = radiusRaw * crossFilterScale;
|
|
const radiusValue = radiusRawValues?.[datumIndex];
|
|
const legendItemValue = legendItemValues?.[datumIndex];
|
|
const nodeLabels = this.getLabels(datumIndex, datum, midAngle, span, processedDataValues);
|
|
const sectorFormat = this.getItemStyle({ datum, datumIndex }, false);
|
|
const node = {
|
|
series: this,
|
|
datum,
|
|
datumIndex,
|
|
angleValue,
|
|
midAngle,
|
|
midCos: Math.cos(midAngle),
|
|
midSin: Math.sin(midAngle),
|
|
startAngle,
|
|
endAngle,
|
|
radius,
|
|
innerRadius: Math.max(this.radiusScale.convert(0), 0),
|
|
outerRadius: Math.max(this.radiusScale.convert(radius), 0),
|
|
sectorFormat,
|
|
radiusValue,
|
|
legendItemValue,
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId, itemId: datumIndex }),
|
|
focusable: true,
|
|
...nodeLabels
|
|
};
|
|
nodes.push(node);
|
|
if (phantomNodes != null) {
|
|
phantomNodes.push({
|
|
...node,
|
|
radius: 1,
|
|
innerRadius: Math.max(this.radiusScale.convert(0), 0),
|
|
outerRadius: Math.max(this.radiusScale.convert(1), 0),
|
|
focusable: false
|
|
});
|
|
}
|
|
}
|
|
this.zerosumOuterRing.visible = sum === 0;
|
|
this.zerosumInnerRing.visible = sum === 0 && innerRadiusRatio != null && innerRadiusRatio !== 1 && innerRadiusRatio > 0;
|
|
return {
|
|
itemId: seriesId,
|
|
nodeData: nodes,
|
|
labelData: nodes,
|
|
phantomNodeData: phantomNodes
|
|
};
|
|
}
|
|
getLabelContent(datumIndex, datum, values) {
|
|
const { id: seriesId, ctx, properties } = this;
|
|
const { formatManager } = ctx;
|
|
const { calloutLabel, sectorLabel, calloutLabelKey, sectorLabelKey, legendItemKey } = properties;
|
|
const allowNullKeys = properties.allowNullKeys ?? false;
|
|
const calloutLabelValue = values.calloutLabelValues?.[datumIndex];
|
|
const sectorLabelValue = values.sectorLabelValues?.[datumIndex];
|
|
const legendItemValue = values.legendItemValues?.[datumIndex];
|
|
const labelFormatterParams = {
|
|
datum,
|
|
angleKey: this.properties.angleKey,
|
|
angleName: this.properties.angleName,
|
|
radiusKey: this.properties.radiusKey,
|
|
radiusName: this.properties.radiusName,
|
|
calloutLabelKey: this.properties.calloutLabelKey,
|
|
calloutLabelName: this.properties.calloutLabelName,
|
|
sectorLabelKey: this.properties.sectorLabelKey,
|
|
sectorLabelName: this.properties.sectorLabelName,
|
|
legendItemKey: this.properties.legendItemKey
|
|
};
|
|
const result = {
|
|
callout: void 0,
|
|
sector: void 0,
|
|
legendItem: void 0
|
|
};
|
|
if (calloutLabelKey) {
|
|
result.callout = this.getLabelText(
|
|
calloutLabelValue,
|
|
datum,
|
|
calloutLabelKey,
|
|
"calloutLabel",
|
|
[],
|
|
calloutLabel,
|
|
{ ...labelFormatterParams, value: calloutLabelValue },
|
|
allowNullKeys
|
|
);
|
|
}
|
|
if (sectorLabelKey) {
|
|
result.sector = this.getLabelText(
|
|
sectorLabelValue,
|
|
datum,
|
|
sectorLabelKey,
|
|
"sectorLabel",
|
|
[],
|
|
sectorLabel,
|
|
{ ...labelFormatterParams, value: sectorLabelValue },
|
|
allowNullKeys
|
|
);
|
|
}
|
|
if (legendItemKey != null && (legendItemValue != null || allowNullKeys)) {
|
|
const legendItemDisplay = legendItemValue ?? "";
|
|
result.legendItem = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "category",
|
|
value: allowNullKeys ? legendItemValue : legendItemDisplay,
|
|
datum,
|
|
seriesId,
|
|
legendItemName: void 0,
|
|
key: legendItemKey,
|
|
source: "legend-label",
|
|
property: "legendItem",
|
|
domain: [],
|
|
boundSeries: this.getFormatterContext("legendItem")
|
|
}) ?? legendItemDisplay;
|
|
}
|
|
return result;
|
|
}
|
|
getLabels(datumIndex, datum, midAngle, span, values) {
|
|
const { properties } = this;
|
|
const { calloutLabel, sectorLabel, legendItemKey } = properties;
|
|
const formats = this.getLabelContent(datumIndex, datum, values);
|
|
const result = {};
|
|
if (calloutLabel.enabled && formats.callout && span >= toRadians(calloutLabel.minAngle)) {
|
|
result.calloutLabel = {
|
|
...this.getTextAlignment(midAngle),
|
|
text: formats.callout,
|
|
hidden: false,
|
|
collisionTextAlign: void 0,
|
|
collisionOffsetY: 0,
|
|
box: void 0
|
|
};
|
|
}
|
|
if (sectorLabel.enabled && formats.sector) {
|
|
result.sectorLabel = { text: formats.sector };
|
|
}
|
|
if (legendItemKey && formats.legendItem) {
|
|
result.legendItem = { key: legendItemKey, text: formats.legendItem };
|
|
}
|
|
return result;
|
|
}
|
|
getTextAlignment(midAngle) {
|
|
const quadrantTextOpts = [
|
|
{ textAlign: "center", textBaseline: "bottom" },
|
|
{ textAlign: "left", textBaseline: "middle" },
|
|
{ textAlign: "center", textBaseline: "top" },
|
|
{ textAlign: "right", textBaseline: "middle" }
|
|
];
|
|
const midAngle180 = normalizeAngle180(midAngle);
|
|
const quadrantStart = -0.75 * Math.PI;
|
|
const quadrantOffset = midAngle180 - quadrantStart;
|
|
const quadrant = Math.floor(quadrantOffset / (Math.PI / 2));
|
|
const quadrantIndex = modulus(quadrant, quadrantTextOpts.length);
|
|
return quadrantTextOpts[quadrantIndex];
|
|
}
|
|
getFillParams(fill, innerRadius, outerRadius) {
|
|
if (!isGradientFill(fill) || fill.bounds === "item")
|
|
return;
|
|
return {
|
|
centerX: 0,
|
|
centerY: 0,
|
|
innerRadius,
|
|
outerRadius
|
|
};
|
|
}
|
|
getItemStyle({ datum, datumIndex }, isHighlight, highlightState, legendItemValues) {
|
|
const { fills, strokes, itemStyler } = this.properties;
|
|
const defaultStroke = strokes[datumIndex];
|
|
const defaultFill = fills[datumIndex];
|
|
const {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
cornerRadius,
|
|
opacity
|
|
} = mergeDefaults(
|
|
this.getHighlightStyle(isHighlight, datumIndex, highlightState, legendItemValues),
|
|
{ fill: defaultFill, stroke: defaultStroke },
|
|
this.properties
|
|
);
|
|
let overrides;
|
|
if (itemStyler) {
|
|
overrides = this.cachedDatumCallback(
|
|
this.getDatumId(datumIndex) + (isHighlight ? "-highlight" : "-hide"),
|
|
() => {
|
|
const params = this.makeItemStylerParams(datum, datumIndex, isHighlight, {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
cornerRadius
|
|
});
|
|
return this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`],
|
|
this.callWithContext(itemStyler, params),
|
|
{ proxyPaths: { fill: ["fills", `${datumIndex}`], stroke: ["strokes", `${datumIndex}`] } }
|
|
);
|
|
}
|
|
);
|
|
}
|
|
return {
|
|
fill: overrides?.fill ?? fill,
|
|
fillOpacity: overrides?.fillOpacity ?? fillOpacity,
|
|
stroke: overrides?.stroke ?? stroke3,
|
|
strokeWidth: overrides?.strokeWidth ?? strokeWidth,
|
|
strokeOpacity: overrides?.strokeOpacity ?? strokeOpacity,
|
|
lineDash: overrides?.lineDash ?? lineDash,
|
|
lineDashOffset: overrides?.lineDashOffset ?? lineDashOffset,
|
|
cornerRadius: overrides?.cornerRadius ?? cornerRadius,
|
|
opacity
|
|
};
|
|
}
|
|
makeItemStylerParams(datum, datumIndex, isHighlight, style2) {
|
|
const { angleKey, radiusKey, calloutLabelKey, sectorLabelKey, legendItemKey } = this.properties;
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
datum,
|
|
angleKey,
|
|
radiusKey,
|
|
calloutLabelKey,
|
|
sectorLabelKey,
|
|
legendItemKey,
|
|
...style2,
|
|
fill,
|
|
highlightState: this.getHighlightStateString(
|
|
this.ctx.highlightManager?.getActiveHighlight(),
|
|
isHighlight,
|
|
datumIndex
|
|
),
|
|
seriesId: this.id
|
|
};
|
|
}
|
|
getCalloutLineStyle(nodeDatum, highlighted) {
|
|
const { properties } = this;
|
|
let itemStylerResult = {};
|
|
if (properties.calloutLine.itemStyler) {
|
|
const highlightState = this.getHighlightStateString(
|
|
this.ctx.highlightManager?.getActiveHighlight(),
|
|
highlighted,
|
|
nodeDatum.datumIndex
|
|
);
|
|
const params = {
|
|
angleKey: properties.angleKey,
|
|
angleName: properties.angleName ?? properties.angleKey,
|
|
calloutLabelKey: properties.calloutLabelKey,
|
|
calloutLabelName: properties.calloutLabelName ?? properties.calloutLabelKey,
|
|
datum: nodeDatum.datum,
|
|
highlightState,
|
|
legendItemKey: properties.legendItemKey,
|
|
radiusKey: properties.radiusKey,
|
|
radiusName: properties.radiusName ?? properties.radiusKey,
|
|
sectorLabelKey: properties.sectorLabelKey,
|
|
sectorLabelName: properties.sectorLabelName ?? properties.sectorLabelKey,
|
|
seriesId: this.id
|
|
};
|
|
itemStylerResult = this.cachedCallWithContext(properties.calloutLine.itemStyler, params) ?? {};
|
|
}
|
|
return {
|
|
length: itemStylerResult.length ?? properties.calloutLine.length,
|
|
strokeWidth: itemStylerResult.strokeWidth ?? properties.calloutLine.strokeWidth,
|
|
color: itemStylerResult.color,
|
|
colors: properties.calloutLine.colors
|
|
};
|
|
}
|
|
getInnerRadius() {
|
|
const { radius } = this;
|
|
const { innerRadiusRatio = 1, innerRadiusOffset = 0 } = this.properties;
|
|
const innerRadius = radius * innerRadiusRatio + innerRadiusOffset;
|
|
if (innerRadius === radius || innerRadius < 0) {
|
|
return 0;
|
|
}
|
|
return innerRadius;
|
|
}
|
|
getOuterRadius() {
|
|
const { outerRadiusRatio, outerRadiusOffset } = this.properties;
|
|
return Math.max(this.radius * outerRadiusRatio + outerRadiusOffset, 0);
|
|
}
|
|
updateRadiusScale(resize) {
|
|
const newRange = [this.getInnerRadius(), this.getOuterRadius()];
|
|
this.radiusScale.range = newRange;
|
|
if (resize) {
|
|
this.previousRadiusScale.range = newRange;
|
|
}
|
|
const setRadii = (d) => ({
|
|
...d,
|
|
innerRadius: Math.max(this.radiusScale.convert(0), 0),
|
|
outerRadius: Math.max(this.radiusScale.convert(d.radius), 0)
|
|
});
|
|
this.nodeData = this.nodeData.map(setRadii);
|
|
this.phantomNodeData = this.phantomNodeData?.map(setRadii);
|
|
}
|
|
getTitleTranslationY() {
|
|
const outerRadius = Math.max(0, this.radiusScale.range[1]);
|
|
if (outerRadius === 0) {
|
|
return Number.NaN;
|
|
}
|
|
const spacing = this.properties.title?.spacing ?? 0;
|
|
const titleOffset = 2 + spacing;
|
|
const dy2 = Math.max(0, -outerRadius);
|
|
return -outerRadius - titleOffset - dy2;
|
|
}
|
|
update({ seriesRect }) {
|
|
const { title } = this.properties;
|
|
const newNodeDataDependencies = {
|
|
seriesRectWidth: seriesRect?.width,
|
|
seriesRectHeight: seriesRect?.height
|
|
};
|
|
const resize = jsonDiff(this.nodeDataDependencies, newNodeDataDependencies) != null;
|
|
if (resize) {
|
|
this._nodeDataDependencies = newNodeDataDependencies;
|
|
}
|
|
this.maybeRefreshNodeData();
|
|
this.updateTitleNodes();
|
|
this.updateRadiusScale(resize);
|
|
this.contentGroup.translationX = this.centerX;
|
|
this.contentGroup.translationY = this.centerY;
|
|
this.highlightGroup.translationX = this.centerX;
|
|
this.highlightGroup.translationY = this.centerY;
|
|
this.backgroundGroup.translationX = this.centerX;
|
|
this.backgroundGroup.translationY = this.centerY;
|
|
if (this.labelGroup) {
|
|
this.labelGroup.translationX = this.centerX;
|
|
this.labelGroup.translationY = this.centerY;
|
|
}
|
|
if (title) {
|
|
const dy2 = this.getTitleTranslationY();
|
|
title.node.y = Number.isFinite(dy2) ? dy2 : 0;
|
|
const titleBox = title.node.getBBox();
|
|
title.node.visible = title.enabled && Number.isFinite(dy2) && !this.bboxIntersectsSurroundingSeries(titleBox);
|
|
}
|
|
for (const circle of [this.zerosumInnerRing, this.zerosumOuterRing]) {
|
|
circle.fillOpacity = 0;
|
|
circle.stroke = this.properties.calloutLabel.color;
|
|
circle.strokeWidth = 1;
|
|
circle.strokeOpacity = 1;
|
|
}
|
|
this.updateNodeMidPoint();
|
|
this.updateSelections();
|
|
this.updateNodes(seriesRect);
|
|
}
|
|
updateTitleNodes() {
|
|
const { oldTitle } = this;
|
|
const { title } = this.properties;
|
|
if (oldTitle !== title) {
|
|
if (oldTitle) {
|
|
oldTitle.node.remove();
|
|
}
|
|
if (title) {
|
|
title.node.textBaseline = "bottom";
|
|
this.labelGroup?.appendChild(title.node);
|
|
}
|
|
this.oldTitle = title;
|
|
}
|
|
}
|
|
updateNodeMidPoint() {
|
|
const setMidPoint = (d) => {
|
|
const radius = d.innerRadius + (d.outerRadius - d.innerRadius) / 2;
|
|
d.midPoint = {
|
|
x: d.midCos * Math.max(0, radius),
|
|
y: d.midSin * Math.max(0, radius)
|
|
};
|
|
};
|
|
for (const datum of this.nodeData) {
|
|
setMidPoint(datum);
|
|
}
|
|
if (this.phantomNodeData) {
|
|
for (const datum of this.phantomNodeData) {
|
|
setMidPoint(datum);
|
|
}
|
|
}
|
|
}
|
|
updateSelections() {
|
|
this.updateGroupSelection();
|
|
this.updateInnerCircleSelection();
|
|
}
|
|
updateGroupSelection() {
|
|
const {
|
|
itemSelection,
|
|
highlightSelection,
|
|
phantomSelection,
|
|
phantomHighlightSelection,
|
|
calloutLabelSelection,
|
|
labelSelection,
|
|
highlightLabelSelection,
|
|
innerLabelsSelection
|
|
} = this;
|
|
const highlightedNodeData = this.nodeData.map((datum) => ({
|
|
...datum,
|
|
// Allow mutable sectorFormat, so formatted sector styles can be updated and varied
|
|
// between normal and highlighted cases.
|
|
sectorFormat: { ...datum.sectorFormat }
|
|
}));
|
|
const phantomHighlightedNodeData = this.phantomNodeData?.map((datum) => ({
|
|
...datum,
|
|
// Allow mutable sectorFormat, so formatted sector styles can be updated and varied
|
|
// between normal and highlighted cases.
|
|
sectorFormat: { ...datum.sectorFormat }
|
|
}));
|
|
const update = (selection, nodeData) => {
|
|
selection.update(nodeData, void 0, (datum) => this.getDatumId(datum.datumIndex));
|
|
if (this.ctx.animationManager.isSkipped()) {
|
|
selection.cleanup();
|
|
}
|
|
};
|
|
update(itemSelection, this.nodeData);
|
|
update(highlightSelection, highlightedNodeData);
|
|
update(phantomSelection, this.phantomNodeData ?? []);
|
|
update(phantomHighlightSelection, phantomHighlightedNodeData ?? []);
|
|
calloutLabelSelection.update(this.calloutNodeData, (group) => {
|
|
const line = new Line();
|
|
line.tag = 0 /* CalloutLine */;
|
|
line.pointerEvents = 1 /* None */;
|
|
group.appendChild(line);
|
|
const text2 = new Text();
|
|
text2.tag = 1 /* CalloutLabel */;
|
|
text2.pointerEvents = 1 /* None */;
|
|
group.appendChild(text2);
|
|
});
|
|
labelSelection.update(this.nodeData);
|
|
highlightLabelSelection.update(highlightedNodeData);
|
|
innerLabelsSelection.update(this.properties.innerLabels, (node) => {
|
|
node.pointerEvents = 1 /* None */;
|
|
});
|
|
}
|
|
updateInnerCircleSelection() {
|
|
const { innerCircle } = this.properties;
|
|
let radius = 0;
|
|
const innerRadius = this.getInnerRadius();
|
|
if (innerRadius > 0) {
|
|
const circleRadius = Math.min(innerRadius, this.getOuterRadius());
|
|
const antiAliasingPadding = 1;
|
|
radius = Math.ceil(circleRadius * 2 + antiAliasingPadding);
|
|
}
|
|
const datums = innerCircle ? [{ radius }] : [];
|
|
this.innerCircleSelection.update(datums);
|
|
}
|
|
updateNodes(seriesRect) {
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
const { visible, dataModel, processedData } = this;
|
|
this.backgroundGroup.visible = visible;
|
|
this.contentGroup.visible = visible;
|
|
if (!dataModel || !processedData)
|
|
return;
|
|
const { legendItemValues } = this.getProcessedDataValues(dataModel, processedData);
|
|
const seriesHighlighted = this.isSeriesHighlighted(highlightedDatum, legendItemValues);
|
|
const drawingMode = this.ctx.chartService.highlight?.drawingMode ?? "overlay";
|
|
this.highlightGroup.visible = visible && seriesHighlighted;
|
|
this.labelGroup.visible = visible;
|
|
this.innerCircleSelection.each((node, { radius }) => {
|
|
node.setProperties({
|
|
fill: this.properties.innerCircle?.fill,
|
|
opacity: this.properties.innerCircle?.fillOpacity,
|
|
size: radius
|
|
});
|
|
});
|
|
const innerRadius = this.radiusScale.range[0];
|
|
const outerRadius = this.radiusScale.range[1];
|
|
const fillBBox = this.getShapeFillBBox();
|
|
const animationDisabled = this.ctx.animationManager.isSkipped();
|
|
const updateSectorFn = (sector, datum, _index, isDatumHighlighted, mode) => {
|
|
const format = this.getItemStyle(datum, isDatumHighlighted, void 0, legendItemValues);
|
|
datum.sectorFormat.fill = format.fill;
|
|
datum.sectorFormat.stroke = format.stroke;
|
|
if (animationDisabled) {
|
|
sector.startAngle = datum.startAngle;
|
|
sector.endAngle = datum.endAngle;
|
|
sector.innerRadius = datum.innerRadius;
|
|
sector.outerRadius = datum.outerRadius;
|
|
}
|
|
if (isDatumHighlighted || animationDisabled) {
|
|
sector.fill = format.fill;
|
|
sector.stroke = format.stroke;
|
|
}
|
|
const fillParams = this.getFillParams(format.fill, innerRadius, outerRadius);
|
|
sector.setStyleProperties(format, fillBBox, fillParams);
|
|
sector.drawingMode = mode;
|
|
sector.cornerRadius = format.cornerRadius;
|
|
sector.fillShadow = this.properties.shadow;
|
|
const inset = Math.max(
|
|
(this.properties.sectorSpacing + (format.stroke == null ? 0 : format.strokeWidth)) / 2,
|
|
0
|
|
);
|
|
sector.inset = inset;
|
|
sector.lineJoin = this.properties.sectorSpacing >= 0 || inset > 0 ? "miter" : "round";
|
|
};
|
|
this.itemSelection.each((node, datum, index) => updateSectorFn(node, datum, index, false, "overlay"));
|
|
this.phantomSelection.each((node, datum, index) => updateSectorFn(node, datum, index, false, "overlay"));
|
|
this.highlightSelection.each((node, datum, index) => {
|
|
updateSectorFn(node, datum, index, true, drawingMode);
|
|
node.visible = datum.datumIndex === highlightedDatum?.datumIndex;
|
|
});
|
|
this.phantomHighlightSelection.each((node, datum, index) => {
|
|
updateSectorFn(node, datum, index, true, drawingMode);
|
|
node.visible = datum.datumIndex === highlightedDatum?.datumIndex;
|
|
});
|
|
this.updateCalloutLineNodes();
|
|
this.updateCalloutLabelNodes(seriesRect);
|
|
this.updateSectorLabelNodes();
|
|
this.updateInnerLabelNodes();
|
|
this.updateZerosumRings();
|
|
this.animationState.transition("update");
|
|
}
|
|
updateCalloutLineNodes() {
|
|
const { strokes } = this.properties;
|
|
const { offset } = this.properties.calloutLabel;
|
|
const highlightedDatum = this.ctx.highlightManager?.getActiveHighlight();
|
|
const seriesHighlighted = this.isSeriesHighlighted(highlightedDatum);
|
|
for (const line of this.calloutLabelSelection.selectByTag(0 /* CalloutLine */)) {
|
|
const datum = line.closestDatum();
|
|
const isDatumHighlighted = seriesHighlighted && this.isItemHighlighted(highlightedDatum, datum.datumIndex) === true;
|
|
const { length: calloutLength, strokeWidth, color: color2, colors } = this.getCalloutLineStyle(datum, false);
|
|
const calloutStrokeWidth = strokeWidth;
|
|
const calloutColors = isStringFillArray(colors) ? colors : strokes;
|
|
const { calloutLabel: label, outerRadius, datumIndex } = datum;
|
|
if (label?.text && !label.hidden && outerRadius !== 0) {
|
|
line.visible = true;
|
|
line.strokeWidth = calloutStrokeWidth;
|
|
line.stroke = color2 ?? calloutColors[datumIndex % calloutColors.length];
|
|
line.strokeOpacity = this.getHighlightStyle(isDatumHighlighted, datum.datumIndex).opacity ?? 1;
|
|
line.fill = void 0;
|
|
const x1 = datum.midCos * outerRadius;
|
|
const y1 = datum.midSin * outerRadius;
|
|
let x2 = datum.midCos * (outerRadius + calloutLength);
|
|
let y2 = datum.midSin * (outerRadius + calloutLength);
|
|
const isMoved = label.collisionTextAlign ?? label.collisionOffsetY !== 0;
|
|
if (isMoved && label.box != null) {
|
|
const box = label.box;
|
|
let cx = x2;
|
|
let cy = y2;
|
|
if (x2 < box.x) {
|
|
cx = box.x;
|
|
} else if (x2 > box.x + box.width) {
|
|
cx = box.x + box.width;
|
|
}
|
|
if (y2 < box.y) {
|
|
cy = box.y;
|
|
} else if (y2 > box.y + box.height) {
|
|
cy = box.y + box.height;
|
|
}
|
|
const dx2 = cx - x2;
|
|
const dy2 = cy - y2;
|
|
const length2 = Math.sqrt(Math.pow(dx2, 2) + Math.pow(dy2, 2));
|
|
const paddedLength = length2 - offset;
|
|
if (paddedLength > 0) {
|
|
x2 = x2 + dx2 * paddedLength / length2;
|
|
y2 = y2 + dy2 * paddedLength / length2;
|
|
}
|
|
}
|
|
line.x1 = x1;
|
|
line.y1 = y1;
|
|
line.x2 = x2;
|
|
line.y2 = y2;
|
|
} else {
|
|
line.visible = false;
|
|
}
|
|
}
|
|
}
|
|
getLabelOverflow(box, seriesRect) {
|
|
const seriesLeft = -this.centerX;
|
|
const seriesRight = seriesLeft + seriesRect.width;
|
|
const seriesTop = -this.centerY;
|
|
const seriesBottom = seriesTop + seriesRect.height;
|
|
const errPx = 1;
|
|
let maxWidth = box.width;
|
|
if (box.x + errPx < seriesLeft) {
|
|
maxWidth = (box.x + box.width - seriesLeft) / box.width;
|
|
} else if (box.x + box.width - errPx > seriesRight) {
|
|
maxWidth = (seriesRight - box.x) / box.width;
|
|
}
|
|
const hasVerticalOverflow = box.y + errPx < seriesTop || box.y + box.height - errPx > seriesBottom;
|
|
const hasSurroundingSeriesOverflow = this.bboxIntersectsSurroundingSeries(box);
|
|
return { maxWidth, hasVerticalOverflow, hasSurroundingSeriesOverflow };
|
|
}
|
|
bboxIntersectsSurroundingSeries(box) {
|
|
const { surroundingRadius } = this;
|
|
if (surroundingRadius == null) {
|
|
return false;
|
|
}
|
|
const corners = [
|
|
{ x: box.x, y: box.y },
|
|
{ x: box.x + box.width, y: box.y },
|
|
{ x: box.x + box.width, y: box.y + box.height },
|
|
{ x: box.x, y: box.y + box.height }
|
|
];
|
|
const sur2 = surroundingRadius ** 2;
|
|
return corners.some((corner) => corner.x ** 2 + corner.y ** 2 > sur2);
|
|
}
|
|
computeCalloutLabelCollisionOffsets() {
|
|
const { radiusScale } = this;
|
|
const { calloutLabel } = this.properties;
|
|
const { offset, minSpacing } = calloutLabel;
|
|
const innerRadius = radiusScale.convert(0);
|
|
const shouldSkip = (datum) => {
|
|
const label = datum.calloutLabel;
|
|
return !label || datum.outerRadius === 0;
|
|
};
|
|
const fullData = this.calloutNodeData;
|
|
const data = fullData.filter((t) => !shouldSkip(t));
|
|
for (const datum of data) {
|
|
const label = datum.calloutLabel;
|
|
if (label == null)
|
|
continue;
|
|
label.hidden = false;
|
|
label.collisionTextAlign = void 0;
|
|
label.collisionOffsetY = 0;
|
|
}
|
|
if (data.length <= 1) {
|
|
return;
|
|
}
|
|
const leftLabels = data.filter((d) => d.midCos < 0).sort((a, b) => a.midSin - b.midSin);
|
|
const rightLabels = data.filter((d) => d.midCos >= 0).sort((a, b) => a.midSin - b.midSin);
|
|
const topLabels = data.filter((d) => d.midSin < 0 && d.calloutLabel?.textAlign === "center").sort((a, b) => a.midCos - b.midCos);
|
|
const bottomLabels = data.filter((d) => d.midSin >= 0 && d.calloutLabel?.textAlign === "center").sort((a, b) => a.midCos - b.midCos);
|
|
const getTextBBox = (datum) => {
|
|
const label = datum.calloutLabel;
|
|
if (label == null)
|
|
return BBox.zero.clone();
|
|
const style2 = this.getLabelStyle(datum, calloutLabel, "calloutLabel");
|
|
const padding2 = expandLabelPadding(style2);
|
|
const calloutLength = this.getCalloutLineStyle(datum, false).length;
|
|
const labelRadius = datum.outerRadius + calloutLength + offset;
|
|
const x = datum.midCos * labelRadius;
|
|
const y = datum.midSin * labelRadius + label.collisionOffsetY;
|
|
const textAlign = label.collisionTextAlign ?? label.textAlign;
|
|
const textBaseline = label.textBaseline;
|
|
return Text.measureBBox(label.text, x, y, {
|
|
font: this.properties.calloutLabel,
|
|
textAlign,
|
|
textBaseline
|
|
}).grow(padding2);
|
|
};
|
|
const avoidNeighbourYCollision = (label, next, direction) => {
|
|
const box = getTextBBox(label).grow(minSpacing / 2);
|
|
const other = getTextBBox(next).grow(minSpacing / 2);
|
|
const collidesOrBehind = box.x < other.x + other.width && box.x + box.width > other.x && (direction === "to-top" ? box.y < other.y + other.height : box.y + box.height > other.y);
|
|
if (collidesOrBehind) {
|
|
next.calloutLabel.collisionOffsetY = direction === "to-top" ? box.y - other.y - other.height : box.y + box.height - other.y;
|
|
}
|
|
};
|
|
const avoidYCollisions = (labels) => {
|
|
const midLabel = labels.slice().sort((a, b) => Math.abs(a.midSin) - Math.abs(b.midSin))[0];
|
|
const midIndex = labels.indexOf(midLabel);
|
|
for (let i = midIndex - 1; i >= 0; i--) {
|
|
const prev = labels[i + 1];
|
|
const next = labels[i];
|
|
avoidNeighbourYCollision(prev, next, "to-top");
|
|
}
|
|
for (let i = midIndex + 1; i < labels.length; i++) {
|
|
const prev = labels[i - 1];
|
|
const next = labels[i];
|
|
avoidNeighbourYCollision(prev, next, "to-bottom");
|
|
}
|
|
};
|
|
const avoidXCollisions = (labels) => {
|
|
const labelsCollideLabelsByY = data.some((datum) => datum.calloutLabel.collisionOffsetY !== 0);
|
|
const boxes = labels.map((label) => getTextBBox(label));
|
|
const paddedBoxes = boxes.map((box) => box.clone().grow(minSpacing / 2));
|
|
let labelsCollideLabelsByX = false;
|
|
for (let i = 0; i < paddedBoxes.length && !labelsCollideLabelsByX; i++) {
|
|
const box = paddedBoxes[i];
|
|
for (let j = i + 1; j < labels.length; j++) {
|
|
const other = paddedBoxes[j];
|
|
if (box.collidesBBox(other)) {
|
|
labelsCollideLabelsByX = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
const sectors = fullData.map((datum) => {
|
|
const { startAngle, endAngle, outerRadius } = datum;
|
|
return { startAngle, endAngle, innerRadius, outerRadius };
|
|
});
|
|
const labelsCollideSectors = boxes.some((box) => sectors.some((sector) => boxCollidesSector(box, sector)));
|
|
if (!labelsCollideLabelsByX && !labelsCollideLabelsByY && !labelsCollideSectors)
|
|
return;
|
|
for (const d of labels) {
|
|
if (d.calloutLabel.textAlign !== "center")
|
|
continue;
|
|
const label = d.calloutLabel;
|
|
if (d.midCos < 0) {
|
|
label.collisionTextAlign = "right";
|
|
} else if (d.midCos > 0) {
|
|
label.collisionTextAlign = "left";
|
|
} else {
|
|
label.collisionTextAlign = "center";
|
|
}
|
|
}
|
|
};
|
|
avoidYCollisions(leftLabels);
|
|
avoidYCollisions(rightLabels);
|
|
avoidXCollisions(topLabels);
|
|
avoidXCollisions(bottomLabels);
|
|
}
|
|
getLabelStyle(datum, label, labelPath, isHighlight = false) {
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
return getLabelStyles(this, datum, this.properties, label, isHighlight, activeHighlight, [
|
|
"series",
|
|
`${this.declarationOrder}`,
|
|
labelPath
|
|
]);
|
|
}
|
|
updateCalloutLabelNodes(seriesRect) {
|
|
const { radiusScale } = this;
|
|
const { calloutLabel } = this.properties;
|
|
const tempTextNode = new Text();
|
|
const highlightedDatum = this.ctx.highlightManager?.getActiveHighlight();
|
|
const seriesHighlighted = this.isSeriesHighlighted(highlightedDatum);
|
|
for (const text2 of this.calloutLabelSelection.selectByTag(1 /* CalloutLabel */)) {
|
|
const datum = text2.closestDatum();
|
|
const label = datum.calloutLabel;
|
|
const radius = radiusScale.convert(datum.radius);
|
|
const outerRadius = Math.max(0, radius);
|
|
if (!label?.text || outerRadius === 0 || label.hidden) {
|
|
text2.visible = false;
|
|
continue;
|
|
}
|
|
const isDatumHighlighted = seriesHighlighted && this.isItemHighlighted(highlightedDatum, datum.datumIndex) === true;
|
|
const style2 = this.getLabelStyle(datum, calloutLabel, "calloutLabel", isDatumHighlighted);
|
|
const calloutLength = this.getCalloutLineStyle(datum, false).length;
|
|
const labelRadius = outerRadius + calloutLength + calloutLabel.offset;
|
|
const x = datum.midCos * labelRadius;
|
|
const y = datum.midSin * labelRadius + label.collisionOffsetY;
|
|
const align2 = {
|
|
textAlign: label.collisionTextAlign ?? label.textAlign,
|
|
textBaseline: label.textBaseline
|
|
};
|
|
tempTextNode.text = label.text;
|
|
tempTextNode.x = x;
|
|
tempTextNode.y = y;
|
|
tempTextNode.setFont(style2);
|
|
tempTextNode.setAlign(align2);
|
|
tempTextNode.setBoxing(style2);
|
|
const box = tempTextNode.getBBox();
|
|
let displayText = label.text;
|
|
let visible = true;
|
|
if (calloutLabel.avoidCollisions) {
|
|
const { maxWidth, hasVerticalOverflow } = this.getLabelOverflow(box, seriesRect);
|
|
if (box.width > maxWidth) {
|
|
const options = {
|
|
font: this.properties.calloutLabel,
|
|
textWrap: "on-space",
|
|
overflow: "hide",
|
|
maxWidth
|
|
};
|
|
displayText = wrapTextOrSegments(label.text, options);
|
|
}
|
|
visible = !hasVerticalOverflow;
|
|
}
|
|
text2.text = displayText;
|
|
text2.x = x;
|
|
text2.y = y;
|
|
text2.setFont(style2);
|
|
text2.setAlign(align2);
|
|
text2.setBoxing(style2);
|
|
text2.fill = style2.color;
|
|
text2.fillOpacity = this.getHighlightStyle(isDatumHighlighted, datum.datumIndex).opacity ?? 1;
|
|
text2.visible = visible;
|
|
}
|
|
}
|
|
computeLabelsBBox(options, seriesRect) {
|
|
const { calloutLabel } = this.properties;
|
|
const { offset, maxCollisionOffset, minSpacing } = calloutLabel;
|
|
if (!calloutLabel.avoidCollisions) {
|
|
return null;
|
|
}
|
|
this.maybeRefreshNodeData();
|
|
this.updateRadiusScale(false);
|
|
this.computeCalloutLabelCollisionOffsets();
|
|
const textBoxes = [];
|
|
const text2 = new Text();
|
|
let titleBox = void 0;
|
|
const { title } = this.properties;
|
|
if (title?.text && title.enabled) {
|
|
const dy2 = this.getTitleTranslationY();
|
|
if (Number.isFinite(dy2)) {
|
|
text2.text = title.text;
|
|
text2.x = 0;
|
|
text2.y = dy2;
|
|
text2.setFont(title);
|
|
text2.setAlign({
|
|
textBaseline: "bottom",
|
|
textAlign: "center"
|
|
});
|
|
titleBox = text2.getBBox();
|
|
textBoxes.push(titleBox);
|
|
}
|
|
}
|
|
for (const datum of this.calloutNodeData) {
|
|
const label = datum.calloutLabel;
|
|
if (!label || datum.outerRadius === 0) {
|
|
continue;
|
|
}
|
|
const style2 = this.getLabelStyle(datum, calloutLabel, "calloutLabel");
|
|
const calloutLength = this.getCalloutLineStyle(datum, false).length;
|
|
const labelRadius = datum.outerRadius + calloutLength + offset;
|
|
const x = datum.midCos * labelRadius;
|
|
const y = datum.midSin * labelRadius + label.collisionOffsetY;
|
|
text2.text = label.text;
|
|
text2.x = x;
|
|
text2.y = y;
|
|
text2.setFont(style2);
|
|
text2.setAlign({
|
|
textAlign: label.collisionTextAlign ?? label.textAlign,
|
|
textBaseline: label.textBaseline
|
|
});
|
|
text2.setBoxing(style2);
|
|
const box = text2.getBBox();
|
|
label.box = box;
|
|
if (Math.abs(label.collisionOffsetY) > maxCollisionOffset) {
|
|
label.hidden = true;
|
|
continue;
|
|
}
|
|
if (titleBox) {
|
|
const seriesTop = -this.centerY;
|
|
const titleCleanArea = new BBox(
|
|
titleBox.x - minSpacing,
|
|
seriesTop,
|
|
titleBox.width + 2 * minSpacing,
|
|
titleBox.y + titleBox.height + minSpacing - seriesTop
|
|
);
|
|
if (box.collidesBBox(titleCleanArea)) {
|
|
label.hidden = true;
|
|
continue;
|
|
}
|
|
}
|
|
if (options.hideWhenNecessary) {
|
|
const { maxWidth, hasVerticalOverflow, hasSurroundingSeriesOverflow } = this.getLabelOverflow(
|
|
box,
|
|
seriesRect
|
|
);
|
|
const isTooShort = box.width > maxWidth;
|
|
if (hasVerticalOverflow || isTooShort || hasSurroundingSeriesOverflow) {
|
|
label.hidden = true;
|
|
continue;
|
|
}
|
|
}
|
|
label.hidden = false;
|
|
textBoxes.push(box);
|
|
}
|
|
if (textBoxes.length === 0) {
|
|
return null;
|
|
}
|
|
return BBox.merge(textBoxes);
|
|
}
|
|
updateSectorLabelNodes() {
|
|
const { properties } = this;
|
|
const { positionOffset, positionRatio } = this.properties.sectorLabel;
|
|
const highlightedDatum = this.ctx.highlightManager?.getActiveHighlight();
|
|
const seriesHighlighted = this.isSeriesHighlighted(highlightedDatum);
|
|
const innerRadius = this.radiusScale.convert(0);
|
|
const shouldPutTextInCenter = innerRadius <= 0 && // is donut?
|
|
this.ctx.legendManager.getData(this.id)?.filter((d) => d.enabled).length === 1;
|
|
const align2 = { textAlign: "center", textBaseline: "middle" };
|
|
const updateSelection = (selection) => selection.each((text2, datum) => {
|
|
const { outerRadius, startAngle, endAngle } = datum;
|
|
const isDatumHighlighted = seriesHighlighted && this.isItemHighlighted(highlightedDatum, datum.datumIndex) === true;
|
|
let isTextVisible = false;
|
|
if (datum.sectorLabel && outerRadius !== 0) {
|
|
const style2 = this.getLabelStyle(datum, properties.sectorLabel, "sectorLabel", isDatumHighlighted);
|
|
const labelRadius = innerRadius * (1 - positionRatio) + outerRadius * positionRatio + positionOffset;
|
|
text2.fill = style2.color;
|
|
text2.fillOpacity = this.getHighlightStyle(isDatumHighlighted, datum.datumIndex).opacity ?? 1;
|
|
text2.text = datum.sectorLabel.text;
|
|
if (shouldPutTextInCenter) {
|
|
text2.x = 0;
|
|
text2.y = 0;
|
|
} else {
|
|
text2.x = datum.midCos * labelRadius;
|
|
text2.y = datum.midSin * labelRadius;
|
|
}
|
|
text2.setFont(style2);
|
|
text2.setAlign(align2);
|
|
text2.setBoxing(style2);
|
|
const bbox = text2.getBBox();
|
|
const corners = [
|
|
[bbox.x, bbox.y],
|
|
[bbox.x + bbox.width, bbox.y],
|
|
[bbox.x + bbox.width, bbox.y + bbox.height],
|
|
[bbox.x, bbox.y + bbox.height]
|
|
];
|
|
const sectorBounds = { startAngle, endAngle, innerRadius, outerRadius };
|
|
if (corners.every(([x, y]) => isPointInSector(x, y, sectorBounds))) {
|
|
isTextVisible = true;
|
|
}
|
|
}
|
|
text2.visible = isTextVisible;
|
|
});
|
|
updateSelection(this.labelSelection);
|
|
updateSelection(this.highlightLabelSelection);
|
|
}
|
|
updateInnerLabelNodes() {
|
|
const textBBoxes = [];
|
|
const margins = [];
|
|
this.innerLabelsSelection.each((text2, datum) => {
|
|
const { fontStyle, fontWeight: fontWeight2, fontSize, fontFamily, color: color2 } = datum;
|
|
text2.fontStyle = fontStyle;
|
|
text2.fontWeight = fontWeight2;
|
|
text2.fontSize = fontSize;
|
|
text2.fontFamily = fontFamily;
|
|
text2.text = datum.text;
|
|
text2.x = 0;
|
|
text2.y = 0;
|
|
text2.fill = color2;
|
|
text2.textAlign = "center";
|
|
textBBoxes.push(text2.getBBox());
|
|
margins.push(datum.spacing);
|
|
});
|
|
const getMarginTop = (index) => index === 0 ? 0 : margins[index];
|
|
const getMarginBottom = (index) => index === margins.length - 1 ? 0 : margins[index];
|
|
const totalWidth = textBBoxes.reduce((max, bbox) => Math.max(max, bbox.width), 0);
|
|
const totalHeight = textBBoxes.reduce(
|
|
(sum, bbox, i) => sum + bbox.height + getMarginTop(i) + getMarginBottom(i),
|
|
0
|
|
);
|
|
const innerRadius = this.getInnerRadius();
|
|
const labelRadius = Math.sqrt(Math.pow(totalWidth / 2, 2) + Math.pow(totalHeight / 2, 2));
|
|
const labelsVisible = labelRadius <= (innerRadius > 0 ? innerRadius : this.getOuterRadius());
|
|
const textBottoms = [];
|
|
for (let i = 0, prev = -totalHeight / 2; i < textBBoxes.length; i++) {
|
|
const bbox = textBBoxes[i];
|
|
const bottom = bbox.height + prev + getMarginTop(i);
|
|
textBottoms.push(bottom);
|
|
prev = bottom + getMarginBottom(i);
|
|
}
|
|
this.innerLabelsSelection.each((text2, _datum, index) => {
|
|
text2.visible = labelsVisible;
|
|
if (Array.isArray(text2.text)) {
|
|
text2.y = textBottoms[index] - textBBoxes[index].height;
|
|
} else {
|
|
text2.y = textBottoms[index];
|
|
}
|
|
});
|
|
}
|
|
updateZerosumRings() {
|
|
this.zerosumOuterRing.size = this.getOuterRadius() * 2;
|
|
this.zerosumInnerRing.size = this.getInnerRadius() * 2;
|
|
}
|
|
pickNodeClosestDatum(point) {
|
|
return pickByMatchingAngle(this, point);
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const {
|
|
id: seriesId,
|
|
dataModel,
|
|
processedData,
|
|
properties,
|
|
ctx: { formatManager }
|
|
} = this;
|
|
const {
|
|
legendItemKey,
|
|
calloutLabelKey,
|
|
calloutLabelName,
|
|
sectorLabelKey,
|
|
sectorLabelName,
|
|
angleKey,
|
|
angleName,
|
|
radiusKey,
|
|
radiusName,
|
|
tooltip
|
|
} = properties;
|
|
const title = this.properties.title.node.getPlainText();
|
|
if (!dataModel || !processedData)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data?.[datumIndex];
|
|
const processedDataValues = this.getProcessedDataValues(dataModel, processedData);
|
|
const { angleRawValues } = processedDataValues;
|
|
const angleRawValue = angleRawValues[datumIndex];
|
|
const labelValues = this.getLabelContent(datumIndex, datum, processedDataValues);
|
|
const label = labelValues.legendItem ?? labelValues.callout ?? labelValues.sector ?? angleName;
|
|
const domain = extractDomain(dataModel.getDomain(this, `angleRaw`, "value", processedData));
|
|
const angleContent = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value: angleRawValue,
|
|
datum,
|
|
seriesId,
|
|
legendItemName: void 0,
|
|
key: angleKey,
|
|
source: "tooltip",
|
|
property: "angle",
|
|
domain,
|
|
boundSeries: this.getFormatterContext("angle"),
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
}) ?? formatValue(angleRawValue, 3);
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
title,
|
|
symbol: this.legendItemSymbol(datumIndex),
|
|
data: [{ label: toPlainText(label), fallbackLabel: angleKey, value: angleContent }]
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: angleName,
|
|
legendItemKey,
|
|
calloutLabelKey,
|
|
calloutLabelName,
|
|
sectorLabelKey,
|
|
sectorLabelName,
|
|
angleKey,
|
|
angleName,
|
|
radiusKey,
|
|
radiusName,
|
|
...this.getItemStyle({ datum, datumIndex }, false)
|
|
}
|
|
);
|
|
}
|
|
legendItemSymbol(datumIndex) {
|
|
const datum = this.processedData?.dataSources.get(this.id)?.data?.[datumIndex];
|
|
const sectorFormat = this.getItemStyle({ datum, datumIndex }, false);
|
|
const { fillOpacity, strokeOpacity, strokeWidth, lineDash, lineDashOffset } = this.properties;
|
|
let { fill } = sectorFormat;
|
|
const { stroke: stroke3 } = sectorFormat;
|
|
if (isGradientFill(fill)) {
|
|
fill = { ...fill, gradient: "linear", rotation: 0, reverse: false };
|
|
}
|
|
return {
|
|
marker: {
|
|
fill,
|
|
stroke: stroke3,
|
|
fillOpacity,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash,
|
|
lineDashOffset
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
const {
|
|
visible,
|
|
processedData,
|
|
dataModel,
|
|
id: seriesId,
|
|
ctx: { legendManager }
|
|
} = this;
|
|
if (!dataModel || !processedData || legendType !== "category") {
|
|
return [];
|
|
}
|
|
const { angleKey, calloutLabelKey, sectorLabelKey, legendItemKey, showInLegend } = this.properties;
|
|
if (!legendItemKey && (!calloutLabelKey || calloutLabelKey === angleKey) && (!sectorLabelKey || sectorLabelKey === angleKey)) {
|
|
return [];
|
|
}
|
|
const processedDataValues = this.getProcessedDataValues(dataModel, processedData);
|
|
const { angleRawValues } = processedDataValues;
|
|
const titleText = this.properties.title?.showInLegend && this.properties.title.text;
|
|
const legendData = [];
|
|
const hideZeros = this.properties.hideZeroValueSectorsInLegend;
|
|
const rawData = processedData.dataSources.get(this.id)?.data;
|
|
const invalidData = processedData.invalidData?.get(this.id);
|
|
for (let datumIndex = 0; datumIndex < processedData.input.count; datumIndex++) {
|
|
const datum = rawData?.[datumIndex];
|
|
const angleRawValue = angleRawValues[datumIndex];
|
|
if (invalidData?.[datumIndex] === true || hideZeros && angleRawValue === 0) {
|
|
continue;
|
|
}
|
|
const labelParts = [];
|
|
if (titleText) {
|
|
labelParts.push(titleText);
|
|
}
|
|
const labels = this.getLabelContent(datumIndex, datum, processedDataValues);
|
|
if (legendItemKey && labels.legendItem !== void 0) {
|
|
labelParts.push(labels.legendItem);
|
|
} else if (calloutLabelKey && calloutLabelKey !== angleKey && labels.callout !== void 0) {
|
|
labelParts.push(labels.callout);
|
|
} else if (sectorLabelKey && sectorLabelKey !== angleKey && labels.sector !== void 0) {
|
|
labelParts.push(labels.sector);
|
|
}
|
|
if (labelParts.length === 0)
|
|
continue;
|
|
legendData.push({
|
|
legendType: "category",
|
|
id: seriesId,
|
|
datum,
|
|
itemId: datumIndex,
|
|
seriesId,
|
|
hideToggleOtherSeries: true,
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId, itemId: datumIndex }),
|
|
label: {
|
|
text: labelParts.map((s) => toPlainText(s)).join(" - ")
|
|
},
|
|
symbol: this.legendItemSymbol(datumIndex),
|
|
legendItemName: legendItemKey == null ? void 0 : datum[legendItemKey],
|
|
hideInLegend: !showInLegend
|
|
});
|
|
}
|
|
return legendData;
|
|
}
|
|
// Used for grid
|
|
setLegendState(enabledItems) {
|
|
const {
|
|
id: seriesId,
|
|
ctx: { legendManager, updateService }
|
|
} = this;
|
|
for (const [itemId, enabled] of enabledItems.entries()) {
|
|
legendManager.toggleItem(enabled, seriesId, itemId);
|
|
}
|
|
legendManager.update();
|
|
updateService.update(7 /* SERIES_UPDATE */);
|
|
}
|
|
animateEmptyUpdateReady(_data) {
|
|
const { animationManager } = this.ctx;
|
|
const fns = preparePieSeriesAnimationFunctions(
|
|
true,
|
|
this.properties.rotation,
|
|
this.radiusScale,
|
|
this.previousRadiusScale
|
|
);
|
|
fromToMotion(
|
|
this.id,
|
|
"nodes",
|
|
animationManager,
|
|
[this.itemSelection, this.highlightSelection, this.phantomSelection, this.phantomHighlightSelection],
|
|
fns.nodes,
|
|
(_, datum) => this.getDatumId(datum.datumIndex)
|
|
);
|
|
fromToMotion(this.id, `innerCircle`, animationManager, [this.innerCircleSelection], fns.innerCircle);
|
|
seriesLabelFadeInAnimation(this, "callout", animationManager, this.calloutLabelSelection);
|
|
seriesLabelFadeInAnimation(this, "sector", animationManager, this.labelSelection, this.highlightLabelSelection);
|
|
seriesLabelFadeInAnimation(this, "inner", animationManager, this.innerLabelsSelection);
|
|
this.previousRadiusScale.range = this.radiusScale.range;
|
|
}
|
|
animateWaitingUpdateReady() {
|
|
const {
|
|
itemSelection,
|
|
highlightSelection,
|
|
phantomSelection,
|
|
phantomHighlightSelection,
|
|
processedData,
|
|
radiusScale,
|
|
previousRadiusScale
|
|
} = this;
|
|
const { animationManager } = this.ctx;
|
|
const dataDiff = processedData?.reduced?.diff?.[this.id];
|
|
this.ctx.animationManager.stopByAnimationGroupId(this.id);
|
|
const supportedDiff = (dataDiff?.moved.size ?? 0) === 0;
|
|
const hasKeys = (processedData?.defs.keys.length ?? 0) > 0;
|
|
const hasUniqueKeys = processedData?.reduced?.animationValidation?.uniqueKeys ?? true;
|
|
if (!supportedDiff || !hasKeys || !hasUniqueKeys) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
}
|
|
const noVisibleData = !this.nodeData.some((n) => n.enabled);
|
|
const fns = preparePieSeriesAnimationFunctions(
|
|
false,
|
|
this.properties.rotation,
|
|
radiusScale,
|
|
previousRadiusScale
|
|
);
|
|
fromToMotion(
|
|
this.id,
|
|
"nodes",
|
|
animationManager,
|
|
[itemSelection, highlightSelection, phantomSelection, phantomHighlightSelection],
|
|
fns.nodes,
|
|
(_, datum) => this.getDatumId(datum.datumIndex),
|
|
dataDiff
|
|
);
|
|
fromToMotion(this.id, `innerCircle`, animationManager, [this.innerCircleSelection], fns.innerCircle);
|
|
seriesLabelFadeInAnimation(this, "callout", this.ctx.animationManager, this.calloutLabelSelection);
|
|
seriesLabelFadeInAnimation(
|
|
this,
|
|
"sector",
|
|
this.ctx.animationManager,
|
|
this.labelSelection,
|
|
this.highlightLabelSelection
|
|
);
|
|
if (this.noVisibleData !== noVisibleData) {
|
|
this.noVisibleData = noVisibleData;
|
|
seriesLabelFadeInAnimation(this, "inner", this.ctx.animationManager, this.innerLabelsSelection);
|
|
}
|
|
this.previousRadiusScale.range = this.radiusScale.range;
|
|
}
|
|
animateClearingUpdateEmpty() {
|
|
const {
|
|
itemSelection,
|
|
highlightSelection,
|
|
phantomSelection,
|
|
phantomHighlightSelection,
|
|
radiusScale,
|
|
previousRadiusScale
|
|
} = this;
|
|
const { animationManager } = this.ctx;
|
|
const fns = preparePieSeriesAnimationFunctions(
|
|
false,
|
|
this.properties.rotation,
|
|
radiusScale,
|
|
previousRadiusScale
|
|
);
|
|
fromToMotion(
|
|
this.id,
|
|
"nodes",
|
|
animationManager,
|
|
[itemSelection, highlightSelection, phantomSelection, phantomHighlightSelection],
|
|
fns.nodes,
|
|
(_, datum) => this.getDatumId(datum.datumIndex)
|
|
);
|
|
fromToMotion(this.id, `innerCircle`, animationManager, [this.innerCircleSelection], fns.innerCircle);
|
|
seriesLabelFadeOutAnimation(this, "callout", this.ctx.animationManager, this.calloutLabelSelection);
|
|
seriesLabelFadeOutAnimation(
|
|
this,
|
|
"sector",
|
|
this.ctx.animationManager,
|
|
this.labelSelection,
|
|
this.highlightLabelSelection
|
|
);
|
|
seriesLabelFadeOutAnimation(this, "inner", this.ctx.animationManager, this.innerLabelsSelection);
|
|
this.previousRadiusScale.range = this.radiusScale.range;
|
|
}
|
|
getDatumId(datumIndex) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData) {
|
|
return `${datumIndex}`;
|
|
}
|
|
const { calloutLabelKey, sectorLabelKey, legendItemKey } = this.properties;
|
|
if (!processedData.reduced?.animationValidation?.uniqueKeys) {
|
|
return `${datumIndex}`;
|
|
}
|
|
if (legendItemKey) {
|
|
const legendItemKeys = dataModel.resolveKeysById(this, "legendItemKey", processedData);
|
|
return createDatumId(legendItemKeys[datumIndex]);
|
|
} else if (calloutLabelKey) {
|
|
const calloutLabelKeys = dataModel.resolveKeysById(this, "calloutLabelKey", processedData);
|
|
return createDatumId(calloutLabelKeys[datumIndex]);
|
|
} else if (sectorLabelKey) {
|
|
const sectorLabelKeys = dataModel.resolveKeysById(this, "sectorLabelKey", processedData);
|
|
return createDatumId(sectorLabelKeys[datumIndex]);
|
|
}
|
|
return `${datumIndex}`;
|
|
}
|
|
hasItemStylers() {
|
|
return !(this.properties.itemStyler == null && this.properties.calloutLabel.itemStyler == null && this.properties.sectorLabel.itemStyler == null && this.properties.innerLabels.every((innerLabel) => innerLabel.itemStyler == null));
|
|
}
|
|
};
|
|
DonutSeries.className = "DonutSeries";
|
|
DonutSeries.type = "donut";
|
|
|
|
// packages/ag-charts-community/src/chart/series/polar/pieTheme.ts
|
|
var pieTheme = {
|
|
series: {
|
|
title: {
|
|
enabled: true,
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $rem: FONT_SIZE_RATIO.LARGE },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
color: { $ref: "subtleTextColor" },
|
|
spacing: 5
|
|
},
|
|
calloutLabel: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" },
|
|
offset: 3,
|
|
minAngle: 1e-3
|
|
},
|
|
sectorLabel: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
color: { $ref: "chartBackgroundColor" },
|
|
positionOffset: 0,
|
|
positionRatio: 0.5
|
|
},
|
|
calloutLine: {
|
|
length: 10,
|
|
strokeWidth: 2,
|
|
colors: {
|
|
$map: [
|
|
{
|
|
$if: [
|
|
{
|
|
$or: [
|
|
{ $isGradient: { $value: "$1" } },
|
|
{ $isPattern: { $value: "$1" } },
|
|
{ $isImage: { $value: "$1" } }
|
|
]
|
|
},
|
|
{ $path: ["../../strokes/$index", { $ref: "foregroundColor" }] },
|
|
{ $value: "$1" }
|
|
]
|
|
},
|
|
{
|
|
$if: [
|
|
{ $eq: [{ $path: "../strokeWidth" }, 0] },
|
|
{ $path: "../fills" },
|
|
{ $path: "../strokes" }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
},
|
|
fills: {
|
|
$applyCycle: [
|
|
{ $cacheMax: { $size: { $path: ["./data", { $path: "/data" }] } } },
|
|
{ $palette: "fills" },
|
|
{
|
|
$applySwitch: [
|
|
{ $path: ["/type", void 0, { $value: "$1" }] },
|
|
{ $value: "$1" },
|
|
["gradient", FILL_GRADIENT_RADIAL_REVERSED_SERIES_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS]
|
|
]
|
|
}
|
|
]
|
|
},
|
|
strokes: {
|
|
$applyCycle: [{ $cacheMax: { $size: { $path: ["./data", { $path: "/data" }] } } }, { $palette: "strokes" }]
|
|
},
|
|
fillOpacity: 1,
|
|
strokeOpacity: 1,
|
|
strokeWidth: { $isUserOption: ["./strokes/0", 2, 0] },
|
|
lineDash: [0],
|
|
lineDashOffset: 0,
|
|
rotation: 0,
|
|
sectorSpacing: 1,
|
|
shadow: {
|
|
enabled: false,
|
|
color: DEFAULT_SHADOW_COLOUR,
|
|
xOffset: 3,
|
|
yOffset: 3,
|
|
blur: 5
|
|
},
|
|
highlight: PART_WHOLE_HIGHLIGHT_STYLE
|
|
},
|
|
legend: { enabled: true }
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/polar/donutTheme.ts
|
|
var donutTheme = {
|
|
...pieTheme,
|
|
series: {
|
|
...pieTheme.series,
|
|
innerRadiusRatio: {
|
|
$if: [{ $eq: [{ $path: ["./innerRadiusOffset", void 0] }, void 0] }, 0.7, void 0]
|
|
},
|
|
innerLabels: {
|
|
$apply: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" },
|
|
spacing: 2
|
|
}
|
|
},
|
|
highlight: PART_WHOLE_HIGHLIGHT_STYLE
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/polar/donutSeriesModule.ts
|
|
var DonutSeriesModule = {
|
|
type: "series",
|
|
name: "donut",
|
|
chartType: "polar",
|
|
version: VERSION,
|
|
dependencies: [PolarChartModule],
|
|
options: donutSeriesOptionsDef,
|
|
themeTemplate: donutTheme,
|
|
create: (ctx) => new DonutSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/series/polar/pieSeries.ts
|
|
var PieSeries = class extends DonutSeries {
|
|
};
|
|
PieSeries.className = "PieSeries";
|
|
PieSeries.type = "pie";
|
|
|
|
// packages/ag-charts-community/src/chart/series/polar/pieSeriesModule.ts
|
|
var PieSeriesModule = {
|
|
type: "series",
|
|
name: "pie",
|
|
chartType: "polar",
|
|
version: VERSION,
|
|
dependencies: [PolarChartModule],
|
|
options: pieSeriesOptionsDef,
|
|
themeTemplate: pieTheme,
|
|
create: (ctx) => new PieSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/locale/locale.ts
|
|
var Locale = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.localeText = void 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
ObserveChanges((target) => {
|
|
target.ctx.localeManager.setLocaleText(target.localeText);
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], Locale.prototype, "localeText", 2);
|
|
__decorateClass([
|
|
ObserveChanges((target) => {
|
|
target.ctx.localeManager.setLocaleTextFormatter(target.getLocaleText);
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], Locale.prototype, "getLocaleText", 2);
|
|
|
|
// packages/ag-charts-community/src/locale/localeModule.ts
|
|
var LocaleModule = {
|
|
type: "plugin",
|
|
name: "locale",
|
|
version: VERSION,
|
|
options: {
|
|
localeText: object,
|
|
getLocaleText: callbackOf(string)
|
|
},
|
|
create: (ctx) => new Locale(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/module/axis-modules/numberAxisModule.ts
|
|
var NumberAxisModule = {
|
|
type: "axis",
|
|
name: "number",
|
|
chartType: "cartesian",
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: numberAxisOptionsDefs,
|
|
themeTemplate: {
|
|
line: { enabled: false }
|
|
},
|
|
create: (ctx) => new NumberAxis(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/module/axis-modules/logAxisModule.ts
|
|
var LogAxisModule = {
|
|
type: "axis",
|
|
name: "log",
|
|
chartType: "cartesian",
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: logAxisOptionsDefs,
|
|
themeTemplate: {
|
|
base: 10,
|
|
line: { enabled: false }
|
|
},
|
|
create: (ctx) => new LogAxis(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/module/axis-modules/timeAxisModule.ts
|
|
var TimeAxisModule = {
|
|
type: "axis",
|
|
name: "time",
|
|
chartType: "cartesian",
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: timeAxisOptionsDefs,
|
|
themeTemplate: {
|
|
gridLine: { enabled: false }
|
|
},
|
|
create: (ctx) => new TimeAxis(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/module/axis-modules/categoryAxisModule.ts
|
|
var CategoryAxisModule = {
|
|
type: "axis",
|
|
name: "category",
|
|
chartType: "cartesian",
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: categoryAxisOptionsDefs,
|
|
themeTemplate: {
|
|
groupPaddingInner: 0.1,
|
|
label: { autoRotate: true, wrapping: "on-space" },
|
|
gridLine: { enabled: false },
|
|
interval: { placement: "between" }
|
|
},
|
|
create: (ctx) => new CategoryAxis(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/module/axis-modules/groupedCategoryAxisModule.ts
|
|
var GroupedCategoryAxisModule = {
|
|
type: "axis",
|
|
name: "grouped-category",
|
|
chartType: "cartesian",
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: groupedCategoryAxisOptionsDefs,
|
|
themeTemplate: {
|
|
tick: { enabled: true, stroke: { $ref: "separationLinesColor" } },
|
|
label: { spacing: 10, rotation: 270, wrapping: "on-space" },
|
|
maxThicknessRatio: 0.5,
|
|
paddingInner: 0.4,
|
|
groupPaddingInner: 0.2
|
|
},
|
|
create: (ctx) => new GroupedCategoryAxis(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/chart/axis/unitTimeAxis.ts
|
|
var UnitTimeAxis = class extends DiscreteTimeAxis {
|
|
constructor(moduleCtx) {
|
|
super(moduleCtx, new UnitTimeScale(), false);
|
|
this.parentLevel = new TimeAxisParentLevel();
|
|
this.min = void 0;
|
|
this.max = void 0;
|
|
this.preferredMin = void 0;
|
|
this.preferredMax = void 0;
|
|
// eslint-disable-next-line sonarjs/use-type-alias
|
|
this.unit = void 0;
|
|
this.defaultUnit = void 0;
|
|
}
|
|
get primaryLabel() {
|
|
return this.parentLevel.enabled ? this.parentLevel.label : void 0;
|
|
}
|
|
get primaryTick() {
|
|
return this.parentLevel.enabled ? this.parentLevel.tick : void 0;
|
|
}
|
|
hasDefinedDomain() {
|
|
const { min, max } = this;
|
|
return min != null && max != null && min < max;
|
|
}
|
|
isCategoryLike() {
|
|
return true;
|
|
}
|
|
processData() {
|
|
super.processData();
|
|
let defaultUnit;
|
|
const { domain } = this.dataDomain;
|
|
if (domain.length === 2 && domain[0].valueOf() === domain[1].valueOf()) {
|
|
defaultUnit = lowestGranularityUnitForValue(domain[0]);
|
|
} else {
|
|
const { boundSeries, direction, min, max } = this;
|
|
defaultUnit = calculateDefaultUnit(boundSeries, direction, min, max);
|
|
}
|
|
if (!objectsEqual(this.defaultUnit, defaultUnit)) {
|
|
this.defaultUnit = defaultUnit;
|
|
}
|
|
}
|
|
updateScale() {
|
|
super.updateScale();
|
|
this.scale.interval = this.unit ?? this.defaultUnit;
|
|
}
|
|
normaliseDataDomain(d) {
|
|
const { extent: extent2, clipped } = normalisedTimeExtentWithMetadata(
|
|
d,
|
|
this.min,
|
|
this.max,
|
|
this.preferredMin,
|
|
this.preferredMax
|
|
);
|
|
return { domain: extent2, clipped };
|
|
}
|
|
tickFormatParams(domain, ticks, _fractionDigits, timeInterval3) {
|
|
timeInterval3 ?? (timeInterval3 = lowestGranularityUnitForTicks(ticks));
|
|
const truncateDate = dateTruncationForDomain(domain);
|
|
const unit = intervalUnit(timeInterval3);
|
|
const step = intervalStep(timeInterval3);
|
|
const epoch = intervalEpoch(timeInterval3);
|
|
return { type: "date", unit, step, epoch, truncateDate };
|
|
}
|
|
datumFormatParams(value, params, _fractionDigits, timeInterval3, style2) {
|
|
const interval = this.unit ?? this.defaultUnit ?? "millisecond";
|
|
timeInterval3 ?? (timeInterval3 = interval);
|
|
const { datum, seriesId, legendItemName, key, source, property, domain, boundSeries } = params;
|
|
const unit = intervalUnit(timeInterval3);
|
|
const step = intervalStep(timeInterval3);
|
|
const epoch = intervalEpoch(timeInterval3);
|
|
return {
|
|
type: "date",
|
|
value: intervalFloor(interval, value),
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key,
|
|
source,
|
|
property,
|
|
domain,
|
|
boundSeries,
|
|
unit,
|
|
step,
|
|
epoch,
|
|
style: style2
|
|
};
|
|
}
|
|
};
|
|
UnitTimeAxis.className = "UnitTimeAxis";
|
|
UnitTimeAxis.type = "unit-time";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], UnitTimeAxis.prototype, "parentLevel", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], UnitTimeAxis.prototype, "min", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], UnitTimeAxis.prototype, "max", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], UnitTimeAxis.prototype, "preferredMin", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], UnitTimeAxis.prototype, "preferredMax", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], UnitTimeAxis.prototype, "unit", 2);
|
|
|
|
// packages/ag-charts-community/src/module/axis-modules/unitTimeAxisModule.ts
|
|
var UnitTimeAxisModule = {
|
|
type: "axis",
|
|
name: "unit-time",
|
|
chartType: "cartesian",
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: unitTimeAxisOptionsDefs,
|
|
themeTemplate: {
|
|
groupPaddingInner: 0.1,
|
|
label: { autoRotate: false },
|
|
gridLine: { enabled: false },
|
|
parentLevel: { enabled: true },
|
|
interval: { placement: "between" }
|
|
},
|
|
create: (ctx) => new UnitTimeAxis(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-community/src/module-bundles/cartesian-axes.ts
|
|
var AllCartesianAxesModule = [
|
|
NumberAxisModule,
|
|
LogAxisModule,
|
|
TimeAxisModule,
|
|
CategoryAxisModule,
|
|
GroupedCategoryAxisModule,
|
|
UnitTimeAxisModule
|
|
];
|
|
|
|
// packages/ag-charts-community/src/module-bundles/cartesian-series.ts
|
|
var AllCartesianSeriesModule = [
|
|
AreaSeriesModule,
|
|
BarSeriesModule,
|
|
BubbleSeriesModule,
|
|
HistogramSeriesModule,
|
|
LineSeriesModule,
|
|
ScatterSeriesModule
|
|
];
|
|
|
|
// packages/ag-charts-community/src/module-bundles/cartesian.ts
|
|
var AllCartesianModule = [
|
|
AllCartesianAxesModule,
|
|
AllCartesianSeriesModule,
|
|
LegendModule,
|
|
LocaleModule
|
|
].flat();
|
|
|
|
// packages/ag-charts-community/src/module-bundles/polar.ts
|
|
var AllPolarModule = [
|
|
PolarChartModule,
|
|
DonutSeriesModule,
|
|
PieSeriesModule,
|
|
LegendModule,
|
|
LocaleModule
|
|
];
|
|
|
|
// packages/ag-charts-community/src/module-bundles/all.ts
|
|
var AllCommunityModule = [
|
|
AllCartesianModule,
|
|
AllPolarModule,
|
|
// Presets
|
|
SparklinePresetModule
|
|
].flat();
|
|
|
|
// packages/ag-charts-community/src/integrated-charts-scene.ts
|
|
var integrated_charts_scene_exports = {};
|
|
__export(integrated_charts_scene_exports, {
|
|
Arc: () => Arc,
|
|
BBox: () => BBox,
|
|
Caption: () => Caption,
|
|
CategoryScale: () => CategoryScale,
|
|
Group: () => Group,
|
|
Line: () => Line,
|
|
LinearScale: () => LinearScale,
|
|
Marker: () => Marker,
|
|
Path: () => Path,
|
|
RadialColumnShape: () => RadialColumnShape,
|
|
Rect: () => Rect,
|
|
Scene: () => Scene,
|
|
Sector: () => Sector,
|
|
Shape: () => Shape,
|
|
TranslatableGroup: () => TranslatableGroup,
|
|
getRadialColumnWidth: () => getRadialColumnWidth,
|
|
toRadians: () => toRadians
|
|
});
|
|
|
|
// packages/ag-charts-community/src/integrated-charts-theme.ts
|
|
var integrated_charts_theme_exports = {};
|
|
__export(integrated_charts_theme_exports, {
|
|
AGGREGATION_INDEX_UNSET: () => AGGREGATION_INDEX_UNSET,
|
|
AGGREGATION_INDEX_X_MAX: () => AGGREGATION_INDEX_X_MAX,
|
|
AGGREGATION_INDEX_X_MIN: () => AGGREGATION_INDEX_X_MIN,
|
|
AGGREGATION_INDEX_Y_MAX: () => AGGREGATION_INDEX_Y_MAX,
|
|
AGGREGATION_INDEX_Y_MIN: () => AGGREGATION_INDEX_Y_MIN,
|
|
AGGREGATION_MAX_POINTS: () => AGGREGATION_MAX_POINTS,
|
|
AGGREGATION_MIN_RANGE: () => AGGREGATION_MIN_RANGE,
|
|
AGGREGATION_SPAN: () => AGGREGATION_SPAN,
|
|
AGGREGATION_THRESHOLD: () => AGGREGATION_THRESHOLD,
|
|
AbstractModuleInstance: () => AbstractModuleInstance,
|
|
ActionOnSet: () => ActionOnSet,
|
|
AdjacencyListGraph: () => AdjacencyListGraph,
|
|
AsyncAwaitQueue: () => AsyncAwaitQueue,
|
|
BASE_FONT_SIZE: () => BASE_FONT_SIZE,
|
|
BREAK_TRANSFORM_CHAIN: () => BREAK_TRANSFORM_CHAIN,
|
|
BaseProperties: () => BaseProperties,
|
|
Border: () => Border,
|
|
CANVAS_HEIGHT: () => CANVAS_HEIGHT,
|
|
CANVAS_TO_BUFFER_DEFAULTS: () => CANVAS_TO_BUFFER_DEFAULTS,
|
|
CANVAS_WIDTH: () => CANVAS_WIDTH,
|
|
CARTESIAN_AXIS_TYPE: () => CARTESIAN_AXIS_TYPE,
|
|
CARTESIAN_POSITION: () => CARTESIAN_POSITION,
|
|
CallbackCache: () => CallbackCache,
|
|
ChangeDetectableProperties: () => ChangeDetectableProperties,
|
|
ChartAxisDirection: () => ChartAxisDirection,
|
|
ChartTheme: () => ChartTheme,
|
|
ChartUpdateType: () => ChartUpdateType,
|
|
CleanupRegistry: () => CleanupRegistry,
|
|
Color: () => Color,
|
|
ConfiguredCanvasMixin: () => ConfiguredCanvasMixin,
|
|
DEFAULT_ANNOTATION_HANDLE_FILL: () => DEFAULT_ANNOTATION_HANDLE_FILL,
|
|
DEFAULT_ANNOTATION_STATISTICS_COLOR: () => DEFAULT_ANNOTATION_STATISTICS_COLOR,
|
|
DEFAULT_ANNOTATION_STATISTICS_DIVIDER_STROKE: () => DEFAULT_ANNOTATION_STATISTICS_DIVIDER_STROKE,
|
|
DEFAULT_ANNOTATION_STATISTICS_DOWN_FILL: () => DEFAULT_ANNOTATION_STATISTICS_DOWN_FILL,
|
|
DEFAULT_ANNOTATION_STATISTICS_DOWN_STROKE: () => DEFAULT_ANNOTATION_STATISTICS_DOWN_STROKE,
|
|
DEFAULT_ANNOTATION_STATISTICS_FILL: () => DEFAULT_ANNOTATION_STATISTICS_FILL,
|
|
DEFAULT_ANNOTATION_STATISTICS_STROKE: () => DEFAULT_ANNOTATION_STATISTICS_STROKE,
|
|
DEFAULT_CAPTION_ALIGNMENT: () => DEFAULT_CAPTION_ALIGNMENT,
|
|
DEFAULT_CAPTION_LAYOUT_STYLE: () => DEFAULT_CAPTION_LAYOUT_STYLE,
|
|
DEFAULT_FIBONACCI_STROKES: () => DEFAULT_FIBONACCI_STROKES,
|
|
DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL: () => DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL,
|
|
DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR: () => DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR,
|
|
DEFAULT_POLAR_SERIES_STROKE: () => DEFAULT_POLAR_SERIES_STROKE,
|
|
DEFAULT_SHADOW_COLOUR: () => DEFAULT_SHADOW_COLOUR,
|
|
DEFAULT_SPARKLINE_CROSSHAIR_STROKE: () => DEFAULT_SPARKLINE_CROSSHAIR_STROKE,
|
|
DEFAULT_TEXTBOX_COLOR: () => DEFAULT_TEXTBOX_COLOR,
|
|
DEFAULT_TEXTBOX_FILL: () => DEFAULT_TEXTBOX_FILL,
|
|
DEFAULT_TEXTBOX_STROKE: () => DEFAULT_TEXTBOX_STROKE,
|
|
DEFAULT_TEXT_ANNOTATION_COLOR: () => DEFAULT_TEXT_ANNOTATION_COLOR,
|
|
DEFAULT_TOOLBAR_POSITION: () => DEFAULT_TOOLBAR_POSITION,
|
|
DIRECTION_SWAP_AXES: () => DIRECTION_SWAP_AXES,
|
|
Debug: () => debugLogger_exports,
|
|
DebugMetrics: () => debugMetrics_exports,
|
|
DeclaredSceneChangeDetection: () => DeclaredSceneChangeDetection,
|
|
DeclaredSceneObjectChangeDetection: () => DeclaredSceneObjectChangeDetection,
|
|
Deprecated: () => Deprecated,
|
|
DeprecatedAndRenamedTo: () => DeprecatedAndRenamedTo,
|
|
EllipsisChar: () => EllipsisChar,
|
|
ErrorType: () => ErrorType,
|
|
EventEmitter: () => EventEmitter,
|
|
FILL_GRADIENT_BLANK_DEFAULTS: () => FILL_GRADIENT_BLANK_DEFAULTS,
|
|
FILL_GRADIENT_CONIC_SERIES_DEFAULTS: () => FILL_GRADIENT_CONIC_SERIES_DEFAULTS,
|
|
FILL_GRADIENT_LINEAR_DEFAULTS: () => FILL_GRADIENT_LINEAR_DEFAULTS,
|
|
FILL_GRADIENT_LINEAR_HIERARCHY_DEFAULTS: () => FILL_GRADIENT_LINEAR_HIERARCHY_DEFAULTS,
|
|
FILL_GRADIENT_LINEAR_KEYED_DEFAULTS: () => FILL_GRADIENT_LINEAR_KEYED_DEFAULTS,
|
|
FILL_GRADIENT_LINEAR_SINGLE_DEFAULTS: () => FILL_GRADIENT_LINEAR_SINGLE_DEFAULTS,
|
|
FILL_GRADIENT_RADIAL_DEFAULTS: () => FILL_GRADIENT_RADIAL_DEFAULTS,
|
|
FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS: () => FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS,
|
|
FILL_GRADIENT_RADIAL_REVERSED_SERIES_DEFAULTS: () => FILL_GRADIENT_RADIAL_REVERSED_SERIES_DEFAULTS,
|
|
FILL_GRADIENT_RADIAL_SERIES_DEFAULTS: () => FILL_GRADIENT_RADIAL_SERIES_DEFAULTS,
|
|
FILL_IMAGE_BLANK_DEFAULTS: () => FILL_IMAGE_BLANK_DEFAULTS,
|
|
FILL_IMAGE_DEFAULTS: () => FILL_IMAGE_DEFAULTS,
|
|
FILL_PATTERN_BLANK_DEFAULTS: () => FILL_PATTERN_BLANK_DEFAULTS,
|
|
FILL_PATTERN_DEFAULTS: () => FILL_PATTERN_DEFAULTS,
|
|
FILL_PATTERN_HIERARCHY_DEFAULTS: () => FILL_PATTERN_HIERARCHY_DEFAULTS,
|
|
FILL_PATTERN_KEYED_DEFAULTS: () => FILL_PATTERN_KEYED_DEFAULTS,
|
|
FILL_PATTERN_SINGLE_DEFAULTS: () => FILL_PATTERN_SINGLE_DEFAULTS,
|
|
FONT_SIZE: () => FONT_SIZE,
|
|
FONT_SIZE_RATIO: () => FONT_SIZE_RATIO,
|
|
IS_DARK_THEME: () => IS_DARK_THEME,
|
|
InterpolationProperties: () => InterpolationProperties,
|
|
LABEL_BOXING_DEFAULTS: () => LABEL_BOXING_DEFAULTS,
|
|
LEGEND_CONTAINER_THEME: () => LEGEND_CONTAINER_THEME,
|
|
LRUCache: () => LRUCache,
|
|
LineSplitter: () => LineSplitter,
|
|
Logger: () => logger_exports,
|
|
MARKER_SERIES_HIGHLIGHT_STYLE: () => MARKER_SERIES_HIGHLIGHT_STYLE,
|
|
MULTI_SERIES_HIGHLIGHT_STYLE: () => MULTI_SERIES_HIGHLIGHT_STYLE,
|
|
MementoCaretaker: () => MementoCaretaker,
|
|
ModuleRegistry: () => moduleRegistry_exports,
|
|
ModuleType: () => ModuleType,
|
|
ObserveChanges: () => ObserveChanges,
|
|
PALETTE_ALT_DOWN_FILL: () => PALETTE_ALT_DOWN_FILL,
|
|
PALETTE_ALT_DOWN_STROKE: () => PALETTE_ALT_DOWN_STROKE,
|
|
PALETTE_ALT_NEUTRAL_FILL: () => PALETTE_ALT_NEUTRAL_FILL,
|
|
PALETTE_ALT_NEUTRAL_STROKE: () => PALETTE_ALT_NEUTRAL_STROKE,
|
|
PALETTE_ALT_UP_FILL: () => PALETTE_ALT_UP_FILL,
|
|
PALETTE_ALT_UP_STROKE: () => PALETTE_ALT_UP_STROKE,
|
|
PALETTE_DOWN_FILL: () => PALETTE_DOWN_FILL,
|
|
PALETTE_DOWN_STROKE: () => PALETTE_DOWN_STROKE,
|
|
PALETTE_NEUTRAL_FILL: () => PALETTE_NEUTRAL_FILL,
|
|
PALETTE_NEUTRAL_STROKE: () => PALETTE_NEUTRAL_STROKE,
|
|
PALETTE_UP_FILL: () => PALETTE_UP_FILL,
|
|
PALETTE_UP_STROKE: () => PALETTE_UP_STROKE,
|
|
PART_WHOLE_HIGHLIGHT_STYLE: () => PART_WHOLE_HIGHLIGHT_STYLE,
|
|
POLAR_AXIS_SHAPE: () => POLAR_AXIS_SHAPE,
|
|
POLAR_AXIS_TYPE: () => POLAR_AXIS_TYPE,
|
|
PREV_NEXT_KEYS: () => PREV_NEXT_KEYS,
|
|
Padding: () => Padding,
|
|
ParallelStateMachine: () => ParallelStateMachine,
|
|
PolarZIndexMap: () => PolarZIndexMap,
|
|
PropertiesArray: () => PropertiesArray,
|
|
Property: () => addFakeTransformToInstanceProperty,
|
|
ProxyOnWrite: () => ProxyOnWrite,
|
|
ProxyProperty: () => ProxyProperty,
|
|
ProxyPropertyOnWrite: () => ProxyPropertyOnWrite,
|
|
SAFE_FILLS_OPERATION: () => SAFE_FILLS_OPERATION,
|
|
SAFE_FILL_OPERATION: () => SAFE_FILL_OPERATION,
|
|
SAFE_RANGE2_OPERATION: () => SAFE_RANGE2_OPERATION,
|
|
SAFE_STROKE_FILL_OPERATION: () => SAFE_STROKE_FILL_OPERATION,
|
|
SEGMENTATION_DEFAULTS: () => SEGMENTATION_DEFAULTS,
|
|
SINGLE_SERIES_HIGHLIGHT_STYLE: () => SINGLE_SERIES_HIGHLIGHT_STYLE,
|
|
SKIP_JS_BUILTINS: () => SKIP_JS_BUILTINS,
|
|
ScaleAlignment: () => ScaleAlignment,
|
|
SceneArrayChangeDetection: () => SceneArrayChangeDetection,
|
|
SceneChangeDetection: () => SceneChangeDetection,
|
|
SceneObjectChangeDetection: () => SceneObjectChangeDetection,
|
|
SceneRefChangeDetection: () => SceneRefChangeDetection,
|
|
SeriesContentZIndexMap: () => SeriesContentZIndexMap,
|
|
SeriesZIndexMap: () => SeriesZIndexMap,
|
|
SimpleCache: () => SimpleCache,
|
|
SpanJoin: () => SpanJoin,
|
|
StateMachine: () => StateMachine,
|
|
StateMachineProperty: () => StateMachineProperty,
|
|
TRIPLE_EQ: () => TRIPLE_EQ,
|
|
TextMeasurer: () => TextMeasurer,
|
|
TickIntervals: () => TickIntervals,
|
|
TrimCharsRegex: () => TrimCharsRegex,
|
|
TrimEdgeGuard: () => TrimEdgeGuard,
|
|
UNIT_MAX: () => UNIT_MAX,
|
|
UNIT_MIN: () => UNIT_MIN,
|
|
UnknownError: () => UnknownError,
|
|
ValidationError: () => ValidationError,
|
|
Vec2: () => vector_exports,
|
|
Vec4: () => vector4_exports,
|
|
Vertex: () => Vertex,
|
|
WeakCache: () => WeakCache,
|
|
ZIndexMap: () => ZIndexMap,
|
|
addEscapeEventListener: () => addEscapeEventListener,
|
|
addFakeTransformToInstanceProperty: () => addFakeTransformToInstanceProperty,
|
|
addMouseCloseListener: () => addMouseCloseListener,
|
|
addObserverToInstanceProperty: () => addObserverToInstanceProperty,
|
|
addOverrideFocusVisibleEventListener: () => addOverrideFocusVisibleEventListener,
|
|
addTouchCloseListener: () => addTouchCloseListener,
|
|
addTransformToInstanceProperty: () => addTransformToInstanceProperty,
|
|
aggregationBucketForDatum: () => aggregationBucketForDatum,
|
|
aggregationDatumMatchesIndex: () => aggregationDatumMatchesIndex,
|
|
aggregationDomain: () => aggregationDomain,
|
|
aggregationIndexForXRatio: () => aggregationIndexForXRatio,
|
|
aggregationRangeFittingPoints: () => aggregationRangeFittingPoints,
|
|
aggregationXRatioForDatumIndex: () => aggregationXRatioForDatumIndex,
|
|
aggregationXRatioForXValue: () => aggregationXRatioForXValue,
|
|
and: () => and,
|
|
angleBetween: () => angleBetween,
|
|
angularPadding: () => angularPadding,
|
|
appendEllipsis: () => appendEllipsis,
|
|
applySkiaPatches: () => applySkiaPatches,
|
|
arcDistanceSquared: () => arcDistanceSquared,
|
|
areScalingEqual: () => areScalingEqual,
|
|
array: () => array,
|
|
arrayLength: () => arrayLength,
|
|
arrayOf: () => arrayOf,
|
|
arrayOfDefs: () => arrayOfDefs,
|
|
arraysEqual: () => arraysEqual,
|
|
assignIfNotStrictlyEqual: () => assignIfNotStrictlyEqual,
|
|
attachDescription: () => attachDescription,
|
|
attachListener: () => attachListener,
|
|
autoSizedLabelOptionsDefs: () => autoSizedLabelOptionsDefs,
|
|
barHighlightOptionsDef: () => barHighlightOptionsDef,
|
|
bezier2DDistance: () => bezier2DDistance,
|
|
bezier2DExtrema: () => bezier2DExtrema,
|
|
boolean: () => boolean,
|
|
borderOptionsDef: () => borderOptionsDef,
|
|
boxCollides: () => boxCollides,
|
|
boxContains: () => boxContains,
|
|
boxEmpty: () => boxEmpty,
|
|
boxesEqual: () => boxesEqual,
|
|
buildDateFormatter: () => buildDateFormatter,
|
|
cachedTextMeasurer: () => cachedTextMeasurer,
|
|
calcLineHeight: () => calcLineHeight,
|
|
calculatePlacement: () => calculatePlacement,
|
|
callWithContext: () => callWithContext,
|
|
callback: () => callback,
|
|
callbackDefs: () => callbackDefs,
|
|
callbackOf: () => callbackOf,
|
|
ceilTo: () => ceilTo,
|
|
checkDatum: () => checkDatum,
|
|
circularSliceArray: () => circularSliceArray,
|
|
clamp: () => clamp,
|
|
clampArray: () => clampArray,
|
|
clipLines: () => clipLines,
|
|
clipSpanX: () => clipSpanX,
|
|
collapseSpanToPoint: () => collapseSpanToPoint,
|
|
collectAggregationLevels: () => collectAggregationLevels,
|
|
color: () => color,
|
|
colorStopsOrderValidator: () => colorStopsOrderValidator,
|
|
colorUnion: () => colorUnion,
|
|
commonChartOptionsDefs: () => commonChartOptionsDefs,
|
|
commonSeriesOptionsDefs: () => commonSeriesOptionsDefs,
|
|
commonSeriesThemeableOptionsDefs: () => commonSeriesThemeableOptionsDefs,
|
|
compactAggregationIndices: () => compactAggregationIndices,
|
|
compareDates: () => compareDates,
|
|
computeExtremesAggregation: () => computeExtremesAggregation,
|
|
computeExtremesAggregationPartial: () => computeExtremesAggregationPartial,
|
|
constant: () => constant,
|
|
contextMenuItemsArray: () => contextMenuItemsArray,
|
|
countFractionDigits: () => countFractionDigits,
|
|
countLines: () => countLines,
|
|
createAggregationIndices: () => createAggregationIndices,
|
|
createButton: () => createButton,
|
|
createCanvasContext: () => createCanvasContext,
|
|
createCheckbox: () => createCheckbox,
|
|
createDeprecationWarning: () => createDeprecationWarning,
|
|
createElement: () => createElement,
|
|
createElementId: () => createElementId,
|
|
createIcon: () => createIcon,
|
|
createId: () => createId,
|
|
createIdsGenerator: () => createIdsGenerator,
|
|
createNumberFormatter: () => createNumberFormatter,
|
|
createSelect: () => createSelect,
|
|
createSvgElement: () => createSvgElement,
|
|
createTextArea: () => createTextArea,
|
|
createTicks: () => createTicks,
|
|
date: () => date,
|
|
dateToNumber: () => dateToNumber,
|
|
dateTruncationForDomain: () => dateTruncationForDomain,
|
|
datesSortOrder: () => datesSortOrder,
|
|
debounce: () => debounce,
|
|
decodeIntervalValue: () => decodeIntervalValue,
|
|
deepClone: () => deepClone,
|
|
deepFreeze: () => deepFreeze,
|
|
defaultEpoch: () => defaultEpoch,
|
|
defined: () => defined,
|
|
definedZoomState: () => definedZoomState,
|
|
diffArrays: () => diffArrays,
|
|
distribute: () => distribute,
|
|
downloadUrl: () => downloadUrl,
|
|
dropFirstWhile: () => dropFirstWhile,
|
|
dropLastWhile: () => dropLastWhile,
|
|
durationDay: () => durationDay,
|
|
durationHour: () => durationHour,
|
|
durationMinute: () => durationMinute,
|
|
durationMonth: () => durationMonth,
|
|
durationSecond: () => durationSecond,
|
|
durationWeek: () => durationWeek,
|
|
durationYear: () => durationYear,
|
|
easeIn: () => easeIn,
|
|
easeInOut: () => easeInOut,
|
|
easeInOutQuad: () => easeInOutQuad,
|
|
easeInQuad: () => easeInQuad,
|
|
easeOut: () => easeOut,
|
|
easeOutQuad: () => easeOutQuad,
|
|
encodedToTimestamp: () => encodedToTimestamp,
|
|
enterpriseRegistry: () => enterpriseRegistry,
|
|
entries: () => entries,
|
|
errorBarOptionsDefs: () => errorBarOptionsDefs,
|
|
errorBarThemeableOptionsDefs: () => errorBarThemeableOptionsDefs,
|
|
estimateTickCount: () => estimateTickCount,
|
|
evaluateBezier: () => evaluateBezier,
|
|
every: () => every,
|
|
expandLegendPosition: () => expandLegendPosition,
|
|
extent: () => extent,
|
|
extractDecoratedProperties: () => extractDecoratedProperties,
|
|
extractDomain: () => extractDomain,
|
|
fillGradientDefaults: () => fillGradientDefaults,
|
|
fillImageDefaults: () => fillImageDefaults,
|
|
fillOptionsDef: () => fillOptionsDef,
|
|
fillPatternDefaults: () => fillPatternDefaults,
|
|
findMaxIndex: () => findMaxIndex,
|
|
findMaxValue: () => findMaxValue,
|
|
findMinIndex: () => findMinIndex,
|
|
findMinMax: () => findMinMax,
|
|
findMinValue: () => findMinValue,
|
|
findRangeExtent: () => findRangeExtent,
|
|
first: () => first,
|
|
flush: () => flush,
|
|
focusCursorAtEnd: () => focusCursorAtEnd,
|
|
fontFamilyFull: () => fontFamilyFull,
|
|
fontOptionsDef: () => fontOptionsDef,
|
|
fontWeight: () => fontWeight,
|
|
formatNumber: () => formatNumber,
|
|
formatObjectValidator: () => formatObjectValidator,
|
|
formatPercent: () => formatPercent,
|
|
formatValue: () => formatValue,
|
|
fromPairs: () => fromPairs,
|
|
generateUUID: () => generateUUID,
|
|
geoJson: () => geoJson,
|
|
getAngleRatioRadians: () => getAngleRatioRadians,
|
|
getAttribute: () => getAttribute,
|
|
getChartTheme: () => getChartTheme,
|
|
getDOMMatrix: () => getDOMMatrix,
|
|
getDocument: () => getDocument,
|
|
getElementBBox: () => getElementBBox,
|
|
getIconClassNames: () => getIconClassNames,
|
|
getImage: () => getImage,
|
|
getLastFocus: () => getLastFocus,
|
|
getMaxInnerRectSize: () => getMaxInnerRectSize,
|
|
getMidpointsForIndices: () => getMidpointsForIndices,
|
|
getMinOuterRectSize: () => getMinOuterRectSize,
|
|
getOffscreenCanvas: () => getOffscreenCanvas,
|
|
getPath: () => getPath,
|
|
getPath2D: () => getPath2D,
|
|
getResizeObserver: () => getResizeObserver,
|
|
getSequentialColors: () => getSequentialColors,
|
|
getTickTimeInterval: () => getTickTimeInterval,
|
|
getWindow: () => getWindow,
|
|
googleFont: () => googleFont,
|
|
gradientColorStops: () => gradientColorStops,
|
|
gradientStrict: () => gradientStrict,
|
|
greaterThan: () => greaterThan,
|
|
groupBy: () => groupBy,
|
|
guardTextEdges: () => guardTextEdges,
|
|
hasNoModifiers: () => hasNoModifiers,
|
|
hasRequiredInPath: () => hasRequiredInPath,
|
|
highlightOptionsDef: () => highlightOptionsDef,
|
|
htmlElement: () => htmlElement,
|
|
inRange: () => inRange,
|
|
initRovingTabIndex: () => initRovingTabIndex,
|
|
insertListItemsSorted: () => insertListItemsSorted,
|
|
instanceOf: () => instanceOf,
|
|
interpolationOptionsDefs: () => interpolationOptionsDefs,
|
|
intervalCeil: () => intervalCeil,
|
|
intervalEpoch: () => intervalEpoch,
|
|
intervalExtent: () => intervalExtent,
|
|
intervalFloor: () => intervalFloor,
|
|
intervalHierarchy: () => intervalHierarchy,
|
|
intervalMilliseconds: () => intervalMilliseconds,
|
|
intervalNext: () => intervalNext,
|
|
intervalPrevious: () => intervalPrevious,
|
|
intervalRange: () => intervalRange,
|
|
intervalRangeCount: () => intervalRangeCount,
|
|
intervalRangeNumeric: () => intervalRangeNumeric,
|
|
intervalRangeStartIndex: () => intervalRangeStartIndex,
|
|
intervalStep: () => intervalStep,
|
|
intervalUnit: () => intervalUnit,
|
|
inverseEaseOut: () => inverseEaseOut,
|
|
isArray: () => isArray,
|
|
isBetweenAngles: () => isBetweenAngles,
|
|
isBoolean: () => isBoolean,
|
|
isButtonClickEvent: () => isButtonClickEvent,
|
|
isColor: () => isColor,
|
|
isContinuous: () => isContinuous,
|
|
isDate: () => isDate,
|
|
isDecoratedObject: () => isDecoratedObject,
|
|
isDefined: () => isDefined,
|
|
isDenseInterval: () => isDenseInterval,
|
|
isDocumentFragment: () => isDocumentFragment,
|
|
isElement: () => isElement,
|
|
isEmptyObject: () => isEmptyObject,
|
|
isEnumKey: () => isEnumKey,
|
|
isEnumValue: () => isEnumValue,
|
|
isFiniteNumber: () => isFiniteNumber,
|
|
isFunction: () => isFunction,
|
|
isGradientFill: () => isGradientFill,
|
|
isGradientFillArray: () => isGradientFillArray,
|
|
isGradientOrPatternFill: () => isGradientOrPatternFill,
|
|
isHTMLElement: () => isHTMLElement,
|
|
isHtmlElement: () => isHtmlElement,
|
|
isImageFill: () => isImageFill,
|
|
isInputPending: () => isInputPending,
|
|
isInteger: () => isInteger,
|
|
isKeyOf: () => isKeyOf,
|
|
isNegative: () => isNegative,
|
|
isNode: () => isNode,
|
|
isNumber: () => isNumber,
|
|
isNumberEqual: () => isNumberEqual,
|
|
isNumberObject: () => isNumberObject,
|
|
isObject: () => isObject,
|
|
isObjectLike: () => isObjectLike,
|
|
isObjectWithProperty: () => isObjectWithProperty,
|
|
isObjectWithStringProperty: () => isObjectWithStringProperty,
|
|
isPatternFill: () => isPatternFill,
|
|
isPlainObject: () => isPlainObject,
|
|
isPointLabelDatum: () => isPointLabelDatum,
|
|
isProperties: () => isProperties,
|
|
isRegExp: () => isRegExp,
|
|
isScaleValid: () => isScaleValid,
|
|
isSegmentTruncated: () => isSegmentTruncated,
|
|
isString: () => isString,
|
|
isStringFillArray: () => isStringFillArray,
|
|
isStringObject: () => isStringObject,
|
|
isSymbol: () => isSymbol,
|
|
isTextTruncated: () => isTextTruncated,
|
|
isTruncated: () => isTruncated,
|
|
isUnitTimeCategoryScaling: () => isUnitTimeCategoryScaling,
|
|
isValidDate: () => isValidDate,
|
|
isValidNumberFormat: () => isValidNumberFormat,
|
|
iterate: () => iterate,
|
|
joinFormatted: () => joinFormatted,
|
|
jsonApply: () => jsonApply,
|
|
jsonDiff: () => jsonDiff,
|
|
jsonPropertyCompare: () => jsonPropertyCompare,
|
|
jsonWalk: () => jsonWalk,
|
|
kebabCase: () => kebabCase,
|
|
labelBoxOptionsDef: () => labelBoxOptionsDef,
|
|
legendPositionValidator: () => legendPositionValidator,
|
|
lessThan: () => lessThan,
|
|
lessThanOrEqual: () => lessThanOrEqual,
|
|
levenshteinDistance: () => levenshteinDistance,
|
|
lineDashOptionsDef: () => lineDashOptionsDef,
|
|
lineDistanceSquared: () => lineDistanceSquared,
|
|
lineHighlightOptionsDef: () => lineHighlightOptionsDef,
|
|
lineSegmentOptions: () => lineSegmentOptions,
|
|
lineSegmentation: () => lineSegmentation,
|
|
linear: () => linear,
|
|
linearGaugeSeriesOptionsDef: () => linearGaugeSeriesOptionsDef,
|
|
linearGaugeSeriesThemeableOptionsDef: () => linearGaugeSeriesThemeableOptionsDef,
|
|
linearGaugeTargetOptionsDef: () => linearGaugeTargetOptionsDef,
|
|
linearPoints: () => linearPoints,
|
|
listDecoratedProperties: () => listDecoratedProperties,
|
|
lowestGranularityForInterval: () => lowestGranularityForInterval,
|
|
lowestGranularityUnitForTicks: () => lowestGranularityUnitForTicks,
|
|
lowestGranularityUnitForValue: () => lowestGranularityUnitForValue,
|
|
makeAccessibleClickListener: () => makeAccessibleClickListener,
|
|
mapValues: () => mapValues,
|
|
markerOptionsDefs: () => markerOptionsDefs,
|
|
markerStyleOptionsDefs: () => markerStyleOptionsDefs,
|
|
measureTextSegments: () => measureTextSegments,
|
|
memo: () => memo,
|
|
merge: () => merge,
|
|
mergeArrayDefaults: () => mergeArrayDefaults,
|
|
mergeDefaults: () => mergeDefaults,
|
|
modulus: () => modulus,
|
|
multiSeriesHighlightOptionsDef: () => multiSeriesHighlightOptionsDef,
|
|
nearestSquared: () => nearestSquared,
|
|
nearestSquaredInContainer: () => nearestSquaredInContainer,
|
|
nextPowerOf2: () => nextPowerOf2,
|
|
niceTicksDomain: () => niceTicksDomain,
|
|
normalisedExtentWithMetadata: () => normalisedExtentWithMetadata,
|
|
normalisedTimeExtentWithMetadata: () => normalisedTimeExtentWithMetadata,
|
|
normalizeAngle180: () => normalizeAngle180,
|
|
normalizeAngle360: () => normalizeAngle360,
|
|
normalizeAngle360FromDegrees: () => normalizeAngle360FromDegrees,
|
|
normalizeAngle360Inclusive: () => normalizeAngle360Inclusive,
|
|
number: () => number,
|
|
numberFormatValidator: () => numberFormatValidator,
|
|
numberMin: () => numberMin,
|
|
numberRange: () => numberRange,
|
|
object: () => object,
|
|
objectsEqual: () => objectsEqual,
|
|
objectsEqualWith: () => objectsEqualWith,
|
|
optionsDefs: () => optionsDefs,
|
|
or: () => or,
|
|
padding: () => padding,
|
|
paddingOptions: () => paddingOptions,
|
|
parseNumberFormat: () => parseNumberFormat,
|
|
partialAssign: () => partialAssign,
|
|
pause: () => pause,
|
|
pick: () => pick,
|
|
placeLabels: () => placeLabels,
|
|
positiveNumber: () => positiveNumber,
|
|
positiveNumberNonZero: () => positiveNumberNonZero,
|
|
previousPowerOf2: () => previousPowerOf2,
|
|
radialGaugeSeriesOptionsDef: () => radialGaugeSeriesOptionsDef,
|
|
radialGaugeSeriesThemeableOptionsDef: () => radialGaugeSeriesThemeableOptionsDef,
|
|
radialGaugeTargetOptionsDef: () => radialGaugeTargetOptionsDef,
|
|
range: () => range,
|
|
rangeValidator: () => rangeValidator,
|
|
ratio: () => ratio,
|
|
readIntegratedWrappedValue: () => readIntegratedWrappedValue,
|
|
record: () => record,
|
|
required: () => required,
|
|
rescaleSpan: () => rescaleSpan,
|
|
rescaleVisibleRange: () => rescaleVisibleRange,
|
|
resetIds: () => resetIds,
|
|
resolveOperation: () => resolveOperation,
|
|
rotatePoint: () => rotatePoint,
|
|
roundTo: () => roundTo,
|
|
safeCall: () => safeCall,
|
|
seriesLabelOptionsDefs: () => seriesLabelOptionsDefs,
|
|
seriesTooltipRangeValidator: () => seriesTooltipRangeValidator,
|
|
setAttribute: () => setAttribute,
|
|
setAttributes: () => setAttributes,
|
|
setDocument: () => setDocument,
|
|
setElementBBox: () => setElementBBox,
|
|
setElementStyle: () => setElementStyle,
|
|
setElementStyles: () => setElementStyles,
|
|
setPath: () => setPath,
|
|
setWindow: () => setWindow,
|
|
shadowOptionsDefs: () => shadowOptionsDefs,
|
|
shallowClone: () => shallowClone,
|
|
shapeHighlightOptionsDef: () => shapeHighlightOptionsDef,
|
|
shapeSegmentOptions: () => shapeSegmentOptions,
|
|
shapeSegmentation: () => shapeSegmentation,
|
|
shapeValidator: () => shapeValidator,
|
|
simpleMemorize: () => simpleMemorize,
|
|
simpleMemorize2: () => simpleMemorize2,
|
|
smoothPoints: () => smoothPoints,
|
|
solveBezier: () => solveBezier,
|
|
sortAndUniqueDates: () => sortAndUniqueDates,
|
|
sortBasedOnArray: () => sortBasedOnArray,
|
|
spanRange: () => spanRange,
|
|
splitBezier2D: () => splitBezier2D,
|
|
stepPoints: () => stepPoints,
|
|
stopPageScrolling: () => stopPageScrolling,
|
|
strictObjectKeys: () => strictObjectKeys,
|
|
strictUnion: () => strictUnion,
|
|
string: () => string,
|
|
stringLength: () => stringLength,
|
|
stringifyValue: () => stringifyValue,
|
|
strokeOptionsDef: () => strokeOptionsDef,
|
|
textOrSegments: () => textOrSegments,
|
|
themeNames: () => themeNames,
|
|
themeOperator: () => themeOperator,
|
|
themeSymbols: () => themeSymbols,
|
|
themes: () => themes,
|
|
throttle: () => throttle,
|
|
tickFormat: () => tickFormat,
|
|
tickStep: () => tickStep,
|
|
toArray: () => toArray,
|
|
toDegrees: () => toDegrees,
|
|
toFontString: () => toFontString,
|
|
toIterable: () => toIterable,
|
|
toPlainText: () => toPlainText,
|
|
toRadians: () => toRadians,
|
|
toTextString: () => toTextString,
|
|
toolbarButtonOptionsDefs: () => toolbarButtonOptionsDefs,
|
|
tooltipOptionsDefs: () => tooltipOptionsDefs,
|
|
tooltipOptionsDefsWithArea: () => tooltipOptionsDefsWithArea,
|
|
transformIntegratedCategoryValue: () => transformIntegratedCategoryValue,
|
|
truncateLine: () => truncateLine,
|
|
typeUnion: () => typeUnion,
|
|
undocumented: () => undocumented,
|
|
unguardTextEdges: () => unguardTextEdges,
|
|
union: () => union,
|
|
unionSymbol: () => unionSymbol,
|
|
unique: () => unique,
|
|
validate: () => validate,
|
|
withTimeout: () => withTimeout,
|
|
without: () => without,
|
|
wrapLines: () => wrapLines,
|
|
wrapText: () => wrapText,
|
|
wrapTextOrSegments: () => wrapTextOrSegments,
|
|
wrapTextSegments: () => wrapTextSegments
|
|
});
|
|
var themeSymbols = {
|
|
DEFAULT_ANNOTATION_HANDLE_FILL,
|
|
DEFAULT_ANNOTATION_STATISTICS_COLOR,
|
|
DEFAULT_ANNOTATION_STATISTICS_DIVIDER_STROKE,
|
|
DEFAULT_ANNOTATION_STATISTICS_DOWN_FILL,
|
|
DEFAULT_ANNOTATION_STATISTICS_DOWN_STROKE,
|
|
DEFAULT_ANNOTATION_STATISTICS_FILL,
|
|
DEFAULT_ANNOTATION_STATISTICS_STROKE,
|
|
DEFAULT_CAPTION_ALIGNMENT,
|
|
DEFAULT_CAPTION_LAYOUT_STYLE,
|
|
DEFAULT_FIBONACCI_STROKES,
|
|
DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL,
|
|
DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR,
|
|
DEFAULT_POLAR_SERIES_STROKE,
|
|
DEFAULT_SHADOW_COLOUR,
|
|
DEFAULT_SPARKLINE_CROSSHAIR_STROKE,
|
|
DEFAULT_TEXTBOX_COLOR,
|
|
DEFAULT_TEXTBOX_FILL,
|
|
DEFAULT_TEXTBOX_STROKE,
|
|
DEFAULT_TEXT_ANNOTATION_COLOR,
|
|
DEFAULT_TOOLBAR_POSITION,
|
|
IS_DARK_THEME,
|
|
PALETTE_ALT_DOWN_FILL,
|
|
PALETTE_ALT_DOWN_STROKE,
|
|
PALETTE_ALT_NEUTRAL_FILL,
|
|
PALETTE_ALT_NEUTRAL_STROKE,
|
|
PALETTE_ALT_UP_FILL,
|
|
PALETTE_ALT_UP_STROKE,
|
|
PALETTE_DOWN_FILL,
|
|
PALETTE_DOWN_STROKE,
|
|
PALETTE_NEUTRAL_FILL,
|
|
PALETTE_NEUTRAL_STROKE,
|
|
PALETTE_UP_FILL,
|
|
PALETTE_UP_STROKE
|
|
};
|
|
var themeNames = Object.keys(themes);
|
|
function resolveOperation(operation) {
|
|
const params = ChartTheme.getDefaultPublicParameters();
|
|
const palette = ChartTheme.getDefaultColors();
|
|
const graph = new OptionsGraph({ line: { operation } }, { series: [{ type: "line" }] }, params, palette);
|
|
const resolved = graph.resolve();
|
|
return resolved.operation;
|
|
}
|
|
|
|
// packages/ag-charts-community/src/integrated-charts-util.ts
|
|
var integrated_charts_util_exports = {};
|
|
__export(integrated_charts_util_exports, {
|
|
Color: () => Color,
|
|
interpolateColor: () => interpolateColor
|
|
});
|
|
|
|
// packages/ag-charts-community/src/module-bundles/integrated.ts
|
|
var AgChartsCommunityModule = {
|
|
VERSION,
|
|
_Scene: integrated_charts_scene_exports,
|
|
_Theme: integrated_charts_theme_exports,
|
|
_Util: integrated_charts_util_exports,
|
|
create: AgCharts.create.bind(AgCharts),
|
|
createSparkline: AgCharts.__createSparkline.bind(AgCharts),
|
|
setup: () => {
|
|
moduleRegistry_exports.setRegistryMode(moduleRegistry_exports.RegistryMode.Integrated);
|
|
moduleRegistry_exports.registerModules(AllCommunityModule);
|
|
},
|
|
isEnterprise: false
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/charts/standaloneChart.ts
|
|
var { Chart: Chart2 } = module_support_exports;
|
|
var StandaloneChart = class extends Chart2 {
|
|
getChartType() {
|
|
return "standalone";
|
|
}
|
|
performLayout(ctx) {
|
|
const { seriesRoot, annotationRoot } = this;
|
|
const seriesRect = ctx.layoutBox.clone().shrink(this.seriesArea.getPadding());
|
|
this.seriesRect = seriesRect;
|
|
this.animationRect = seriesRect;
|
|
for (const group of [seriesRoot, annotationRoot]) {
|
|
group.translationX = Math.floor(seriesRect.x);
|
|
group.translationY = Math.floor(seriesRect.y);
|
|
}
|
|
seriesRoot.visible = this.series[0].visible;
|
|
this.ctx.layoutManager.emitLayoutComplete(ctx, {
|
|
series: { visible: true, rect: seriesRect, paddedRect: ctx.layoutBox }
|
|
});
|
|
}
|
|
getAriaLabel() {
|
|
const seriesType = this.series[0]?.type;
|
|
if (seriesType == null)
|
|
return "";
|
|
const caption = this.getCaptionText();
|
|
switch (seriesType) {
|
|
case "radial-gauge":
|
|
case "linear-gauge": {
|
|
const captions = [];
|
|
if (caption.length !== 0) {
|
|
captions.push(caption);
|
|
}
|
|
for (const series of this.series) {
|
|
captions.push(series.getCaptionText());
|
|
}
|
|
return this.ctx.localeManager.t("ariaAnnounceGaugeChart", { caption: captions.join(". ") });
|
|
}
|
|
case "treemap":
|
|
case "sunburst":
|
|
return this.ctx.localeManager.t("ariaAnnounceHierarchyChart", { caption });
|
|
default: {
|
|
return this.ctx.localeManager.t("ariaAnnounceStandaloneChart", { caption });
|
|
}
|
|
}
|
|
}
|
|
};
|
|
StandaloneChart.className = "StandaloneChart";
|
|
StandaloneChart.type = "standalone";
|
|
|
|
// packages/ag-charts-enterprise/src/charts/standaloneChartModule.ts
|
|
var { standaloneChartOptionsDefs: standaloneChartOptionsDefs2 } = module_support_exports;
|
|
var StandaloneChartModule = {
|
|
type: "chart",
|
|
name: "standalone",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: standaloneChartOptionsDefs2,
|
|
create(options, resources) {
|
|
return new StandaloneChart(options, resources);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/flow-proportion/flowProportionUtil.ts
|
|
function computeNodeGraph(nodes, links, includeCircularReferences) {
|
|
if (!includeCircularReferences) {
|
|
links = removeCircularLinks(links);
|
|
}
|
|
const nodeGraph = /* @__PURE__ */ new Map();
|
|
for (const datum of nodes) {
|
|
const itemId = datum.datumIndex.type === 0 /* Link */ ? `link-${datum.datumIndex.index}` : `node-${datum.datumIndex.index}`;
|
|
nodeGraph.set(datum.id, {
|
|
itemId,
|
|
datum,
|
|
linksBefore: [],
|
|
linksAfter: [],
|
|
maxPathLengthBefore: -1,
|
|
maxPathLengthAfter: -1
|
|
});
|
|
}
|
|
let maxPathLength = 0;
|
|
for (const [id, node] of nodeGraph.entries()) {
|
|
maxPathLength = Math.max(
|
|
maxPathLength,
|
|
computePathLength(nodeGraph, links, node, id, -1, []) + computePathLength(nodeGraph, links, node, id, 1, []) + 1
|
|
);
|
|
}
|
|
return { links, nodeGraph, maxPathLength };
|
|
}
|
|
function findCircularLinks(links, link, into, stack) {
|
|
const stackIndex = stack.indexOf(link);
|
|
if (stackIndex !== -1) {
|
|
for (let i = stackIndex; i < stack.length; i += 1) {
|
|
into.add(stack[i]);
|
|
}
|
|
return;
|
|
}
|
|
stack.push(link);
|
|
const { toNode } = link;
|
|
for (const next of links) {
|
|
if (next.fromNode === toNode) {
|
|
findCircularLinks(links, next, into, stack);
|
|
}
|
|
}
|
|
stack.pop();
|
|
}
|
|
function removeCircularLinks(links) {
|
|
const circularLinks = /* @__PURE__ */ new Set();
|
|
for (const link of links) {
|
|
findCircularLinks(links, link, circularLinks, []);
|
|
}
|
|
if (circularLinks.size !== 0) {
|
|
logger_exports.warnOnce("Some links formed circular references. These will be removed from the output.");
|
|
}
|
|
return circularLinks.size === 0 ? links : links.filter((link) => !circularLinks.has(link));
|
|
}
|
|
function computePathLength(nodeGraph, links, node, id, direction, stack) {
|
|
if (stack.includes(id)) {
|
|
return Infinity;
|
|
}
|
|
let maxPathLength = direction === -1 ? node.maxPathLengthBefore : node.maxPathLengthAfter;
|
|
if (maxPathLength === -1) {
|
|
maxPathLength = 0;
|
|
const connectedLinks = direction === -1 ? node.linksBefore : node.linksAfter;
|
|
for (const link of links) {
|
|
const { fromNode, toNode } = link;
|
|
const linkId = direction === -1 ? toNode.id : fromNode.id;
|
|
const nextNodeId = direction === -1 ? fromNode.id : toNode.id;
|
|
const nextNode = id === linkId ? nodeGraph.get(nextNodeId) : void 0;
|
|
if (nextNode == null)
|
|
continue;
|
|
connectedLinks.push({ node: nextNode, link });
|
|
stack?.push(id);
|
|
maxPathLength = Math.max(
|
|
maxPathLength,
|
|
computePathLength(nodeGraph, links, nextNode, nextNodeId, direction, stack) + 1
|
|
);
|
|
stack?.pop();
|
|
}
|
|
if (direction === -1) {
|
|
node.maxPathLengthBefore = maxPathLength;
|
|
} else {
|
|
node.maxPathLengthAfter = maxPathLength;
|
|
}
|
|
}
|
|
return maxPathLength;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/flow-proportion/flowProportionSeries.ts
|
|
var {
|
|
findNodeDatumInArray: findNodeDatumInArray2,
|
|
keyProperty: keyProperty2,
|
|
valueProperty: valueProperty2,
|
|
DataController: DataController2,
|
|
Group: Group4,
|
|
HighlightState: HighlightState3,
|
|
Selection: Selection2,
|
|
Series: Series3,
|
|
TransformableText: TransformableText2
|
|
} = module_support_exports;
|
|
var FlowProportionSeriesNodeEvent = class extends module_support_exports.SeriesNodeEvent {
|
|
constructor(type, nativeEvent, datum, series) {
|
|
super(type, nativeEvent, datum, series);
|
|
const { datumIndex } = datum;
|
|
const nodeDatum = series.contextNodeData?.nodeData.find(
|
|
(d) => d.datumIndex.type === datumIndex.type && d.datumIndex.index === datumIndex.index
|
|
);
|
|
this.size = nodeDatum?.size;
|
|
this.label = nodeDatum?.type === 1 /* Node */ ? nodeDatum?.label : void 0;
|
|
}
|
|
};
|
|
var FlowProportionSeries = class extends Series3 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.NodeEvent = FlowProportionSeriesNodeEvent;
|
|
this.nodeCount = 0;
|
|
this.linkCount = 0;
|
|
this.linksDataModel = void 0;
|
|
this.linksProcessedData = void 0;
|
|
this.nodesDataModel = void 0;
|
|
this.nodesProcessedData = void 0;
|
|
this.processedNodes = /* @__PURE__ */ new Map();
|
|
this.linkGroup = this.contentGroup.appendChild(new Group4({ name: "linkGroup" }));
|
|
this.nodeGroup = this.contentGroup.appendChild(new Group4({ name: "nodeGroup" }));
|
|
this.focusLinkGroup = this.highlightGroup.appendChild(new Group4({ name: "linkGroup" }));
|
|
this.focusNodeGroup = this.highlightGroup.appendChild(new Group4({ name: "nodeGroup" }));
|
|
this.highlightLinkGroup = this.highlightGroup.appendChild(new Group4({ name: "linkGroup" }));
|
|
this.labelSelection = Selection2.select(
|
|
this.labelGroup,
|
|
TransformableText2
|
|
);
|
|
this.linkSelection = Selection2.select(
|
|
this.linkGroup,
|
|
() => this.linkFactory()
|
|
);
|
|
this.nodeSelection = Selection2.select(
|
|
this.nodeGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.focusLinkSelection = Selection2.select(
|
|
this.focusLinkGroup,
|
|
() => this.linkFactory()
|
|
);
|
|
this.focusNodeSelection = Selection2.select(
|
|
this.focusNodeGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.highlightLinkSelection = Selection2.select(
|
|
this.highlightLinkGroup,
|
|
() => this.linkFactory()
|
|
);
|
|
this.highlightNodeSelection = Selection2.select(
|
|
this.highlightNodeGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
}
|
|
get nodes() {
|
|
return this.properties.nodes;
|
|
}
|
|
async processData(dataController) {
|
|
const { data, nodes } = this;
|
|
if (data == null)
|
|
return;
|
|
const { fromKey, toKey, sizeKey, idKey, labelKey } = this.properties;
|
|
const nodesDataController = new DataController2(
|
|
"standalone",
|
|
dataController.suppressFieldDotNotation,
|
|
this.ctx.eventsHub
|
|
);
|
|
const nodesDataModelPromise = nodes == null ? null : nodesDataController.request(
|
|
this.id,
|
|
module_support_exports.DataSet.wrap(nodes) ?? module_support_exports.DataSet.empty(),
|
|
{
|
|
props: [
|
|
keyProperty2(idKey, void 0, { id: "idValue", includeProperty: false }),
|
|
...labelKey == null ? [] : [valueProperty2(labelKey, void 0, { id: "labelValue", includeProperty: false })]
|
|
],
|
|
groupByKeys: true
|
|
}
|
|
);
|
|
const linksDataModelPromise = dataController.request(this.id, data, {
|
|
props: [
|
|
valueProperty2(fromKey, void 0, { id: "fromValue", includeProperty: false }),
|
|
valueProperty2(toKey, void 0, { id: "toValue", includeProperty: false }),
|
|
...sizeKey == null ? [] : [
|
|
valueProperty2(sizeKey, void 0, {
|
|
id: "sizeValue",
|
|
includeProperty: false,
|
|
missingValue: 0
|
|
})
|
|
]
|
|
],
|
|
groupByKeys: false
|
|
});
|
|
if (nodes != null) {
|
|
nodesDataController.execute();
|
|
}
|
|
const [nodesDataModel, linksDataModel] = await Promise.all([
|
|
nodesDataModelPromise ?? Promise.resolve(null),
|
|
linksDataModelPromise
|
|
]);
|
|
this.nodesDataModel = nodesDataModel?.dataModel;
|
|
this.nodesProcessedData = nodesDataModel?.processedData;
|
|
this.linksDataModel = linksDataModel?.dataModel;
|
|
this.linksProcessedData = linksDataModel?.processedData;
|
|
const processedNodes = /* @__PURE__ */ new Map();
|
|
if (nodesDataModel == null) {
|
|
const fromIdValues = linksDataModel.dataModel.resolveColumnById(
|
|
this,
|
|
"fromValue",
|
|
linksDataModel.processedData
|
|
);
|
|
const toIdValues = linksDataModel.dataModel.resolveColumnById(
|
|
this,
|
|
"toValue",
|
|
linksDataModel.processedData
|
|
);
|
|
const createImplicitNode = (id) => {
|
|
const datumIndex = processedNodes.size;
|
|
const label = id;
|
|
return {
|
|
series: this,
|
|
itemId: `node-${datumIndex}`,
|
|
datum: {},
|
|
// Must be a referential object for tooltips
|
|
datumIndex: { type: 1 /* Node */, index: datumIndex },
|
|
type: 1 /* Node */,
|
|
index: datumIndex,
|
|
linksBefore: [],
|
|
linksAfter: [],
|
|
id,
|
|
size: 0,
|
|
label,
|
|
style: this.getNodeStyle(
|
|
{
|
|
datumIndex: { type: 1 /* Node */, index: datumIndex },
|
|
datum: {},
|
|
size: 0,
|
|
label
|
|
},
|
|
datumIndex,
|
|
false
|
|
)
|
|
};
|
|
};
|
|
const linkData = linksDataModel.processedData.dataSources.get(this.id)?.data;
|
|
if (linkData) {
|
|
for (const [datumIndex] of linkData.entries()) {
|
|
const fromId = fromIdValues[datumIndex];
|
|
const toId = toIdValues[datumIndex];
|
|
if (fromId == null || toId == null)
|
|
continue;
|
|
if (!processedNodes.has(fromId)) {
|
|
processedNodes.set(fromId, createImplicitNode(fromId));
|
|
}
|
|
if (!processedNodes.has(toId)) {
|
|
processedNodes.set(toId, createImplicitNode(toId));
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
const nodeIdValues = nodesDataModel.dataModel.resolveColumnById(
|
|
this,
|
|
"idValue",
|
|
nodesDataModel.processedData
|
|
);
|
|
const labelValues = labelKey == null ? void 0 : nodesDataModel.dataModel.resolveColumnById(
|
|
this,
|
|
"labelValue",
|
|
nodesDataModel.processedData
|
|
);
|
|
const nodeData = nodesDataModel.processedData.dataSources.get(this.id)?.data;
|
|
if (nodeData) {
|
|
for (const [datumIndex, datum] of nodeData.entries()) {
|
|
const id = nodeIdValues[datumIndex];
|
|
const label = labelValues?.[datumIndex];
|
|
const nodeDatumIndex = { type: 1 /* Node */, index: datumIndex };
|
|
processedNodes.set(id, {
|
|
series: this,
|
|
itemId: `node-${datumIndex}`,
|
|
datum,
|
|
datumIndex: nodeDatumIndex,
|
|
type: 1 /* Node */,
|
|
index: datumIndex,
|
|
linksBefore: [],
|
|
linksAfter: [],
|
|
id,
|
|
size: 0,
|
|
label,
|
|
style: this.getNodeStyle(
|
|
{ datumIndex: nodeDatumIndex, datum, size: 0, label },
|
|
datumIndex,
|
|
false
|
|
)
|
|
});
|
|
}
|
|
}
|
|
}
|
|
this.processedNodes = processedNodes;
|
|
}
|
|
findNodeDatum(itemId) {
|
|
return findNodeDatumInArray2(itemId, this.contextNodeData?.nodeData);
|
|
}
|
|
getNodeGraph(createNode, createLink, { includeCircularReferences }) {
|
|
const { linksDataModel, linksProcessedData } = this;
|
|
if (linksDataModel == null || linksProcessedData == null) {
|
|
const { links: links2, nodeGraph: nodeGraph2, maxPathLength: maxPathLength2 } = computeNodeGraph(
|
|
(/* @__PURE__ */ new Map()).values(),
|
|
[],
|
|
includeCircularReferences
|
|
);
|
|
this.nodeCount = 0;
|
|
this.linkCount = 0;
|
|
return { nodeGraph: nodeGraph2, links: links2, maxPathLength: maxPathLength2 };
|
|
}
|
|
const { sizeKey } = this.properties;
|
|
const fromIdValues = linksDataModel.resolveColumnById(this, "fromValue", linksProcessedData);
|
|
const toIdValues = linksDataModel.resolveColumnById(this, "toValue", linksProcessedData);
|
|
const sizeValues = sizeKey == null ? void 0 : linksDataModel.resolveColumnById(this, "sizeValue", linksProcessedData);
|
|
const nodesById = /* @__PURE__ */ new Map();
|
|
for (const datum of this.processedNodes.values()) {
|
|
const node = createNode(datum);
|
|
nodesById.set(datum.id, node);
|
|
}
|
|
const baseLinks = [];
|
|
const linkData = linksProcessedData.dataSources.get(this.id)?.data;
|
|
if (linkData) {
|
|
for (const [datumIndex, datum] of linkData.entries()) {
|
|
const fromId = fromIdValues[datumIndex];
|
|
const toId = toIdValues[datumIndex];
|
|
const size = sizeValues == null ? 1 : sizeValues[datumIndex];
|
|
const fromNode = nodesById.get(fromId);
|
|
const toNode = nodesById.get(toId);
|
|
if (size <= 0 || fromNode == null || toNode == null)
|
|
continue;
|
|
const linkNodeDatumIndex = { type: 0 /* Link */, index: datumIndex };
|
|
const link = createLink({
|
|
series: this,
|
|
itemId: `link-${datumIndex}`,
|
|
datum,
|
|
datumIndex: linkNodeDatumIndex,
|
|
type: 0 /* Link */,
|
|
index: datumIndex,
|
|
fromNode,
|
|
toNode,
|
|
size,
|
|
style: this.getLinkStyle(
|
|
{ datum, datumIndex: linkNodeDatumIndex },
|
|
fromNode.datumIndex,
|
|
false
|
|
)
|
|
});
|
|
baseLinks.push(link);
|
|
}
|
|
}
|
|
const { links, nodeGraph, maxPathLength } = computeNodeGraph(
|
|
nodesById.values(),
|
|
baseLinks,
|
|
includeCircularReferences
|
|
);
|
|
for (const node of nodeGraph.values()) {
|
|
node.datum.linksBefore = node.linksBefore.map((linkedNode) => linkedNode.link);
|
|
node.datum.linksAfter = node.linksAfter.map((linkedNode) => linkedNode.link);
|
|
}
|
|
this.nodeCount = nodeGraph.size;
|
|
this.linkCount = links.length;
|
|
return { nodeGraph, links, maxPathLength };
|
|
}
|
|
updateSelections() {
|
|
if (this.nodeDataRefresh) {
|
|
this.contextNodeData = this.createNodeData();
|
|
this.nodeDataRefresh = false;
|
|
}
|
|
}
|
|
update(opts) {
|
|
const { seriesRect } = opts;
|
|
const newNodeDataDependencies = {
|
|
seriesRectWidth: seriesRect?.width ?? 0,
|
|
seriesRectHeight: seriesRect?.height ?? 0
|
|
};
|
|
if (this._nodeDataDependencies == null || this._nodeDataDependencies.seriesRectWidth !== newNodeDataDependencies.seriesRectWidth || this._nodeDataDependencies.seriesRectHeight !== newNodeDataDependencies.seriesRectHeight) {
|
|
this._nodeDataDependencies = newNodeDataDependencies;
|
|
}
|
|
this.updateSelections();
|
|
const nodeData = this.contextNodeData?.nodeData ?? [];
|
|
const labelData = this.contextNodeData?.labelData ?? [];
|
|
const highlightedDatum = this.getHighlightedDatum();
|
|
this.contentGroup.visible = this.visible;
|
|
const highlightState = highlightedDatum == null ? HighlightState3.None : HighlightState3.OtherItem;
|
|
this.contentGroup.opacity = this.properties.highlight.getStyle(highlightState).opacity ?? 1;
|
|
this.labelSelection = this.updateLabelSelection({ labelData, labelSelection: this.labelSelection });
|
|
this.updateLabelNodes({ labelSelection: this.labelSelection });
|
|
this.linkSelection = this.updateLinkSelection({
|
|
nodeData: nodeData.filter((d) => d.type === 0 /* Link */),
|
|
datumSelection: this.linkSelection
|
|
});
|
|
this.updateLinkNodes({ datumSelection: this.linkSelection, isHighlight: false });
|
|
this.nodeSelection = this.updateNodeSelection({
|
|
nodeData: nodeData.filter((d) => d.type === 1 /* Node */),
|
|
datumSelection: this.nodeSelection
|
|
});
|
|
this.updateNodeNodes({ datumSelection: this.nodeSelection, isHighlight: false });
|
|
let focusLinkSelection;
|
|
let focusNodeSelection;
|
|
let highlightLinkSelection;
|
|
let highlightNodeSelection;
|
|
if (highlightedDatum?.type === 1 /* Node */) {
|
|
focusLinkSelection = nodeData.filter((node) => {
|
|
return node.type === 0 /* Link */ && (node.toNode === highlightedDatum || node.fromNode === highlightedDatum);
|
|
});
|
|
focusNodeSelection = focusLinkSelection.map((link) => {
|
|
return link.fromNode === highlightedDatum ? link.toNode : link.fromNode;
|
|
});
|
|
focusNodeSelection.push(highlightedDatum);
|
|
highlightLinkSelection = [];
|
|
highlightNodeSelection = [highlightedDatum];
|
|
} else if (highlightedDatum?.type === 0 /* Link */) {
|
|
focusLinkSelection = [highlightedDatum];
|
|
focusNodeSelection = [highlightedDatum.fromNode, highlightedDatum.toNode];
|
|
highlightLinkSelection = [highlightedDatum];
|
|
highlightNodeSelection = [];
|
|
} else {
|
|
focusLinkSelection = [];
|
|
focusNodeSelection = [];
|
|
highlightLinkSelection = [];
|
|
highlightNodeSelection = [];
|
|
}
|
|
this.focusLinkSelection = this.updateLinkSelection({
|
|
nodeData: focusLinkSelection,
|
|
datumSelection: this.focusLinkSelection
|
|
});
|
|
this.updateLinkNodes({ datumSelection: this.focusLinkSelection, isHighlight: false });
|
|
this.focusNodeSelection = this.updateNodeSelection({
|
|
nodeData: focusNodeSelection,
|
|
datumSelection: this.focusNodeSelection
|
|
});
|
|
this.updateNodeNodes({ datumSelection: this.focusNodeSelection, isHighlight: false });
|
|
this.highlightLinkSelection = this.updateLinkSelection({
|
|
nodeData: highlightLinkSelection,
|
|
datumSelection: this.highlightLinkSelection
|
|
});
|
|
this.updateLinkNodes({ datumSelection: this.highlightLinkSelection, isHighlight: true });
|
|
this.highlightNodeSelection = this.updateNodeSelection({
|
|
nodeData: highlightNodeSelection,
|
|
datumSelection: this.highlightNodeSelection
|
|
});
|
|
this.updateNodeNodes({ datumSelection: this.highlightNodeSelection, isHighlight: true });
|
|
}
|
|
getHighlightedDatum() {
|
|
let highlightedDatum = this.ctx.highlightManager?.getActiveHighlight();
|
|
if (highlightedDatum?.series === this && highlightedDatum.type == null) {
|
|
const { itemId } = highlightedDatum;
|
|
const nodeData = this.contextNodeData?.nodeData ?? [];
|
|
highlightedDatum = itemId == null ? void 0 : nodeData.find((node) => node.type === 1 /* Node */ && node.id === itemId);
|
|
} else if (highlightedDatum?.series !== this) {
|
|
highlightedDatum = void 0;
|
|
}
|
|
return highlightedDatum;
|
|
}
|
|
isLabelHighlighted(datum, activeHighlight) {
|
|
if (activeHighlight == null)
|
|
return false;
|
|
if (activeHighlight.type === 1 /* Node */) {
|
|
return activeHighlight === datum;
|
|
}
|
|
if (activeHighlight.type === 0 /* Link */) {
|
|
return activeHighlight.fromNode === datum || activeHighlight.toNode === datum;
|
|
}
|
|
return false;
|
|
}
|
|
resetAnimation(_chartAnimationPhase) {
|
|
}
|
|
dataCount() {
|
|
return Number.NaN;
|
|
}
|
|
getSeriesDomain(_direction) {
|
|
return { domain: [] };
|
|
}
|
|
getSeriesRange(_direction, _visibleRange) {
|
|
return [Number.NaN, Number.NaN];
|
|
}
|
|
legendItemSymbol(_type, nodeIndex, format = {}) {
|
|
const { fills, strokes } = this.properties;
|
|
const {
|
|
fill = fills[nodeIndex % fills.length],
|
|
fillOpacity = 1,
|
|
stroke: stroke3 = strokes[nodeIndex % strokes.length],
|
|
strokeWidth = 0,
|
|
strokeOpacity = 1,
|
|
lineDash = [0],
|
|
lineDashOffset = 0
|
|
} = format;
|
|
return {
|
|
marker: {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
if (legendType !== "category")
|
|
return [];
|
|
const { showInLegend } = this.properties;
|
|
return Array.from(
|
|
this.processedNodes.values(),
|
|
({ id, label }, nodeIndex) => ({
|
|
legendType: "category",
|
|
id: this.id,
|
|
itemId: id,
|
|
seriesId: this.id,
|
|
enabled: true,
|
|
label: { text: label ?? id },
|
|
symbol: this.legendItemSymbol(1 /* Node */, nodeIndex),
|
|
hideInLegend: !showInLegend,
|
|
isFixed: true
|
|
})
|
|
);
|
|
}
|
|
pickNodeClosestDatum({ x, y }) {
|
|
let minDistanceSquared = Infinity;
|
|
let minDatum;
|
|
this.linkSelection.each((node, datum) => {
|
|
const distanceSquared2 = node.distanceSquared(x, y);
|
|
if (distanceSquared2 < minDistanceSquared) {
|
|
minDistanceSquared = distanceSquared2;
|
|
minDatum = datum;
|
|
}
|
|
});
|
|
this.nodeSelection.each((node, datum) => {
|
|
const distanceSquared2 = node.distanceSquared(x, y);
|
|
if (distanceSquared2 < minDistanceSquared) {
|
|
minDistanceSquared = distanceSquared2;
|
|
minDatum = datum;
|
|
}
|
|
});
|
|
return minDatum == null ? void 0 : { datum: minDatum, distance: Math.sqrt(minDistanceSquared) };
|
|
}
|
|
getDatumAriaText(datum, description) {
|
|
if (datum.type === 0 /* Link */) {
|
|
return this.ctx.localeManager.t("ariaAnnounceFlowProportionLink", {
|
|
index: datum.index + 1,
|
|
count: this.linkCount,
|
|
from: datum.fromNode.id,
|
|
to: datum.toNode.id,
|
|
size: datum.size,
|
|
sizeName: this.properties.sizeName ?? this.properties.sizeKey
|
|
});
|
|
} else if (datum.type === 1 /* Node */) {
|
|
return this.ctx.localeManager.t("ariaAnnounceFlowProportionNode", {
|
|
index: datum.index + 1,
|
|
count: this.nodeCount,
|
|
description
|
|
});
|
|
}
|
|
}
|
|
pickFocus(opts) {
|
|
const { datumIndexDelta: childDelta, otherIndexDelta: depthDelta } = opts;
|
|
const currentNodeDatum = this.contextNodeData?.nodeData[opts.datumIndex - opts.datumIndexDelta];
|
|
let nextNodeDatum = currentNodeDatum;
|
|
if (depthDelta !== 0 || childDelta === 0)
|
|
return;
|
|
if (currentNodeDatum?.type === 0 /* Link */) {
|
|
const allLinks = Array.from(this.linkSelection, (link) => link.datum);
|
|
const selfIndex = allLinks.indexOf(currentNodeDatum);
|
|
const nextIndex = selfIndex + childDelta;
|
|
if (nextIndex >= 0 && nextIndex < allLinks.length) {
|
|
nextNodeDatum = allLinks[nextIndex];
|
|
} else if (nextIndex > 0) {
|
|
nextNodeDatum = allLinks.at(-1);
|
|
} else {
|
|
const allNodes = Array.from(this.nodeSelection, (node) => node.datum);
|
|
nextNodeDatum = allNodes.at(-1);
|
|
}
|
|
} else if (currentNodeDatum?.type === 1 /* Node */) {
|
|
const allNodes = Array.from(this.nodeSelection, (node) => node.datum);
|
|
const selfIndex = allNodes.indexOf(currentNodeDatum);
|
|
const nextIndex = selfIndex + childDelta;
|
|
if (nextIndex >= 0 && nextIndex < allNodes.length) {
|
|
nextNodeDatum = allNodes[nextIndex];
|
|
} else if (nextIndex < 0) {
|
|
nextNodeDatum = allNodes[0];
|
|
} else {
|
|
const allLinks = Array.from(this.linkSelection, (link) => link.datum);
|
|
nextNodeDatum = allLinks[0];
|
|
}
|
|
}
|
|
if (nextNodeDatum == null)
|
|
return;
|
|
const nodeDatum = nextNodeDatum.type === 1 /* Node */ ? Array.from(this.nodeSelection).find((n) => n.datum === nextNodeDatum) : Array.from(this.linkSelection).find((n) => n.datum === nextNodeDatum);
|
|
if (nodeDatum == null)
|
|
return;
|
|
const bounds = this.computeFocusBounds(nodeDatum.node);
|
|
if (bounds == null)
|
|
return;
|
|
return {
|
|
datum: nodeDatum.datum,
|
|
datumIndex: this.contextNodeData?.nodeData.indexOf(nodeDatum.datum) ?? 0,
|
|
otherIndex: 0,
|
|
bounds,
|
|
clipFocusBox: true
|
|
};
|
|
}
|
|
getCategoryValue(_datumIndex) {
|
|
return;
|
|
}
|
|
datumIndexForCategoryValue(_categoryValue) {
|
|
return;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/chord/chordLink.ts
|
|
var { Path: Path2 } = module_support_exports;
|
|
function bezierControlPoints({
|
|
radius,
|
|
startAngle,
|
|
endAngle,
|
|
tension
|
|
}) {
|
|
const cp0x = radius * Math.cos(startAngle);
|
|
const cp0y = radius * Math.sin(startAngle);
|
|
const cp3x = radius * Math.cos(endAngle);
|
|
const cp3y = radius * Math.sin(endAngle);
|
|
const cp1x = cp0x * tension;
|
|
const cp1y = cp0y * tension;
|
|
const cp2x = cp3x * tension;
|
|
const cp2y = cp3y * tension;
|
|
return {
|
|
x: [cp0x, cp1x, cp2x, cp3x],
|
|
y: [cp0y, cp1y, cp2y, cp3y]
|
|
};
|
|
}
|
|
var ChordLink = class extends Path2 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.centerX = 0;
|
|
this.centerY = 0;
|
|
this.radius = 0;
|
|
this.startAngle1 = 0;
|
|
this.endAngle1 = 0;
|
|
this.startAngle2 = 0;
|
|
this.endAngle2 = 0;
|
|
this.tension = 1;
|
|
}
|
|
tensionedCurveTo(cp0x, cp0y, cp1x, cp1y, cp2x, cp2y, cp3x, cp3y) {
|
|
const { path, tension } = this;
|
|
const scale2 = 1 - tension;
|
|
path.cubicCurveTo(
|
|
(cp1x - cp0x) * scale2 + cp0x,
|
|
(cp1y - cp0y) * scale2 + cp0y,
|
|
(cp2x - cp3x) * scale2 + cp3x,
|
|
(cp2y - cp3y) * scale2 + cp3y,
|
|
cp3x,
|
|
cp3y
|
|
);
|
|
}
|
|
updatePath() {
|
|
const { path, centerX, centerY, radius } = this;
|
|
let { startAngle1, endAngle1, startAngle2, endAngle2 } = this;
|
|
if (startAngle1 > startAngle2) {
|
|
[startAngle1, startAngle2] = [startAngle2, startAngle1];
|
|
[endAngle1, endAngle2] = [endAngle2, endAngle1];
|
|
}
|
|
path.clear();
|
|
const startX = centerX + radius * Math.cos(startAngle1);
|
|
const startY = centerY + radius * Math.sin(startAngle1);
|
|
path.moveTo(startX, startY);
|
|
this.tensionedCurveTo(
|
|
startX,
|
|
startY,
|
|
centerX,
|
|
centerY,
|
|
centerX,
|
|
centerY,
|
|
centerX + radius * Math.cos(endAngle2),
|
|
centerY + radius * Math.sin(endAngle2)
|
|
);
|
|
path.arc(centerX, centerY, radius, endAngle2, startAngle2, true);
|
|
this.tensionedCurveTo(
|
|
centerX + radius * Math.cos(startAngle2),
|
|
centerY + radius * Math.sin(startAngle2),
|
|
centerX,
|
|
centerY,
|
|
centerX,
|
|
centerY,
|
|
centerX + radius * Math.cos(endAngle1),
|
|
centerY + radius * Math.sin(endAngle1)
|
|
);
|
|
path.arc(centerX, centerY, radius, endAngle1, startAngle1, true);
|
|
path.closePath();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], ChordLink.prototype, "centerX", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], ChordLink.prototype, "centerY", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], ChordLink.prototype, "radius", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], ChordLink.prototype, "startAngle1", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], ChordLink.prototype, "endAngle1", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], ChordLink.prototype, "startAngle2", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], ChordLink.prototype, "endAngle2", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], ChordLink.prototype, "tension", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/chord/chordSeriesProperties.ts
|
|
var { FillGradientDefaults: FillGradientDefaults2, FillPatternDefaults: FillPatternDefaults2, FillImageDefaults: FillImageDefaults2, makeSeriesTooltip: makeSeriesTooltip2, SeriesProperties: SeriesProperties2, Label: Label3 } = module_support_exports;
|
|
var ChordSeriesLabelProperties = class extends Label3 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.spacing = 1;
|
|
this.maxWidth = 1;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesLabelProperties.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesLabelProperties.prototype, "maxWidth", 2);
|
|
var ChordSeriesLinkProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = void 0;
|
|
this.fillOpacity = 1;
|
|
this.stroke = void 0;
|
|
this.strokeOpacity = 1;
|
|
this.strokeWidth = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.tension = 0;
|
|
}
|
|
getStyle(fills, strokes, index) {
|
|
const { fillOpacity, strokeWidth, strokeOpacity, lineDash, lineDashOffset, tension } = this;
|
|
const fill = this.fill ?? fills[index % fills.length];
|
|
const stroke3 = this.stroke ?? strokes[index % fills.length];
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
tension
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesLinkProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesLinkProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesLinkProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesLinkProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesLinkProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesLinkProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesLinkProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesLinkProperties.prototype, "tension", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesLinkProperties.prototype, "itemStyler", 2);
|
|
var ChordSeriesNodeProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.spacing = 1;
|
|
this.width = 1;
|
|
this.fill = void 0;
|
|
this.fillOpacity = 1;
|
|
this.stroke = void 0;
|
|
this.strokeOpacity = 1;
|
|
this.strokeWidth = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
}
|
|
getStyle(fills, strokes, index) {
|
|
const { fillOpacity, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = this;
|
|
const fill = this.fill ?? fills[index % fills.length];
|
|
const stroke3 = this.stroke ?? strokes[index % fills.length];
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesNodeProperties.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesNodeProperties.prototype, "width", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesNodeProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesNodeProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesNodeProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesNodeProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesNodeProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesNodeProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesNodeProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesNodeProperties.prototype, "itemStyler", 2);
|
|
var ChordSeriesProperties = class extends SeriesProperties2 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.idKey = "";
|
|
this.idName = void 0;
|
|
this.labelKey = void 0;
|
|
this.labelName = void 0;
|
|
this.sizeKey = void 0;
|
|
this.sizeName = void 0;
|
|
this.nodes = void 0;
|
|
this.fillGradientDefaults = new FillGradientDefaults2();
|
|
this.fillPatternDefaults = new FillPatternDefaults2();
|
|
this.fillImageDefaults = new FillImageDefaults2();
|
|
this.fills = [];
|
|
this.strokes = [];
|
|
this.label = new ChordSeriesLabelProperties();
|
|
this.link = new ChordSeriesLinkProperties();
|
|
this.node = new ChordSeriesNodeProperties();
|
|
this.tooltip = makeSeriesTooltip2();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "fromKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "toKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "idKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "idName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "labelKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "labelName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "sizeKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "sizeName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "nodes", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "fillGradientDefaults", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "fillPatternDefaults", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "fillImageDefaults", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "fills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "strokes", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "link", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "node", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChordSeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/chord/chordSeries.ts
|
|
var { SeriesNodePickMode: SeriesNodePickMode2, createDatumId: createDatumId2, Sector: Sector2, getShapeStyle: getShapeStyle2, getLabelStyles: getLabelStyles2, BBox: BBox2 } = module_support_exports;
|
|
var nodeMidAngle = (node) => node.startAngle + angleBetween(node.startAngle, node.endAngle) / 2;
|
|
var ChordSeries = class extends FlowProportionSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
pickModes: [SeriesNodePickMode2.NEAREST_NODE, SeriesNodePickMode2.EXACT_SHAPE_MATCH]
|
|
});
|
|
this.properties = new ChordSeriesProperties();
|
|
}
|
|
isLabelEnabled() {
|
|
return (this.properties.labelKey != null || this.nodes == null) && this.properties.label.enabled;
|
|
}
|
|
linkFactory() {
|
|
return new ChordLink();
|
|
}
|
|
nodeFactory() {
|
|
return new Sector2();
|
|
}
|
|
createNodeData() {
|
|
const {
|
|
id: seriesId,
|
|
_nodeDataDependencies: { seriesRectWidth, seriesRectHeight } = { seriesRectWidth: 0, seriesRectHeight: 0 },
|
|
properties
|
|
} = this;
|
|
const {
|
|
fromKey,
|
|
toKey,
|
|
sizeKey,
|
|
labelKey,
|
|
label: { spacing: labelSpacing, maxWidth: labelMaxWidth, fontSize },
|
|
node: { width: nodeWidth, spacing: nodeSpacing }
|
|
} = properties;
|
|
const centerX = seriesRectWidth / 2;
|
|
const centerY = seriesRectHeight / 2;
|
|
let labelData = [];
|
|
const { nodeGraph, links } = this.getNodeGraph(
|
|
(node) => ({
|
|
...node,
|
|
centerX,
|
|
centerY,
|
|
innerRadius: Number.NaN,
|
|
outerRadius: Number.NaN,
|
|
startAngle: Number.NaN,
|
|
endAngle: Number.NaN
|
|
}),
|
|
(link) => ({
|
|
...link,
|
|
centerX,
|
|
centerY,
|
|
radius: Number.NaN,
|
|
startAngle1: Number.NaN,
|
|
endAngle1: Number.NaN,
|
|
startAngle2: Number.NaN,
|
|
endAngle2: Number.NaN
|
|
}),
|
|
{ includeCircularReferences: true }
|
|
);
|
|
let totalSize = 0;
|
|
for (const [id, { datum: node, linksBefore, linksAfter }] of nodeGraph.entries()) {
|
|
const size = linksBefore.reduce((acc, { link }) => acc + link.size, 0) + linksAfter.reduce((acc, { link }) => acc + link.size, 0);
|
|
if (size === 0) {
|
|
nodeGraph.delete(id);
|
|
} else {
|
|
const { label } = properties;
|
|
node.size = size;
|
|
totalSize += node.size;
|
|
const labelText = label.enabled ? this.getLabelText(
|
|
node.label,
|
|
node.datum,
|
|
labelKey,
|
|
"label",
|
|
[],
|
|
label,
|
|
{ datum: node.datum, value: node.label, fromKey, toKey, sizeKey, size: node.size }
|
|
) : void 0;
|
|
node.label = toPlainText(labelText);
|
|
}
|
|
}
|
|
let labelInset = 0;
|
|
if (this.isLabelEnabled()) {
|
|
const measurer3 = cachedTextMeasurer(this.properties.label);
|
|
let maxMeasuredLabelWidth = 0;
|
|
for (const { datum: node } of nodeGraph.values()) {
|
|
const { id, label } = node;
|
|
if (label == null)
|
|
continue;
|
|
const text2 = wrapText(label, {
|
|
maxWidth: labelMaxWidth,
|
|
font: this.properties.label,
|
|
textWrap: "never"
|
|
});
|
|
const { width: width2 } = measurer3.measureLines(text2);
|
|
maxMeasuredLabelWidth = Math.max(width2, maxMeasuredLabelWidth);
|
|
labelData.push({
|
|
id,
|
|
text: text2,
|
|
centerX,
|
|
centerY,
|
|
angle: Number.NaN,
|
|
radius: Number.NaN,
|
|
size: node.size,
|
|
datumIndex: node.datumIndex,
|
|
nodeDatum: node
|
|
});
|
|
}
|
|
labelInset = maxMeasuredLabelWidth + labelSpacing;
|
|
}
|
|
const nodeCount = nodeGraph.size;
|
|
let radius = Math.min(seriesRectWidth, seriesRectHeight) / 2 - nodeWidth - labelInset;
|
|
let spacingSweep = nodeSpacing / radius;
|
|
if (labelInset !== 0 && (nodeCount * spacingSweep >= 1.5 * Math.PI || radius <= 0)) {
|
|
labelData = [];
|
|
radius = Math.min(seriesRectWidth, seriesRectHeight) / 2 - nodeWidth;
|
|
spacingSweep = nodeSpacing / radius;
|
|
}
|
|
if (nodeCount * spacingSweep >= 2 * Math.PI || radius <= 0) {
|
|
logger_exports.warnOnce("There was insufficient space to display the Chord Series.");
|
|
return;
|
|
}
|
|
const innerRadius = radius;
|
|
const outerRadius = radius + nodeWidth;
|
|
const sizeScale = Math.max((2 * Math.PI - nodeCount * spacingSweep) / totalSize, 0);
|
|
let nodeAngle = 0;
|
|
for (const { datum: node } of nodeGraph.values()) {
|
|
node.innerRadius = innerRadius;
|
|
node.outerRadius = outerRadius;
|
|
node.startAngle = nodeAngle;
|
|
node.endAngle = nodeAngle + node.size * sizeScale;
|
|
nodeAngle = node.endAngle + spacingSweep;
|
|
const midR = (node.innerRadius + node.outerRadius) / 2;
|
|
const midAngle = nodeMidAngle(node);
|
|
node.midPoint = {
|
|
x: node.centerX + midR * Math.cos(midAngle),
|
|
y: node.centerY + midR * Math.sin(midAngle)
|
|
};
|
|
}
|
|
const nodeData = [];
|
|
for (const { datum: node, linksBefore, linksAfter } of nodeGraph.values()) {
|
|
const midAngle = nodeMidAngle(node);
|
|
const combinedLinks = [
|
|
...linksBefore.map((l) => ({
|
|
link: l.link,
|
|
distance: angleBetween(nodeMidAngle(l.node.datum), midAngle),
|
|
after: false
|
|
})),
|
|
...linksAfter.map((l) => ({
|
|
link: l.link,
|
|
distance: angleBetween(nodeMidAngle(l.node.datum), midAngle),
|
|
after: true
|
|
}))
|
|
];
|
|
let linkAngle = node.startAngle;
|
|
for (const { link, after } of combinedLinks.toSorted((a, b) => a.distance - b.distance)) {
|
|
const linkSweep = link.size * sizeScale;
|
|
if (after) {
|
|
link.startAngle1 = linkAngle;
|
|
link.endAngle1 = linkAngle + linkSweep;
|
|
} else {
|
|
link.startAngle2 = linkAngle;
|
|
link.endAngle2 = linkAngle + linkSweep;
|
|
}
|
|
linkAngle += link.size * sizeScale;
|
|
}
|
|
nodeData.push(node);
|
|
}
|
|
const { tension } = this.properties.link;
|
|
for (const link of links) {
|
|
link.radius = radius;
|
|
const outer = bezierControlPoints({
|
|
radius,
|
|
startAngle: link.startAngle1,
|
|
endAngle: link.endAngle2,
|
|
tension
|
|
});
|
|
const inner = bezierControlPoints({
|
|
radius,
|
|
startAngle: link.startAngle2,
|
|
endAngle: link.endAngle1,
|
|
tension
|
|
});
|
|
const outerX = evaluateBezier(...outer.x, 0.5);
|
|
const outerY = evaluateBezier(...outer.y, 0.5);
|
|
const innerX = evaluateBezier(...inner.x, 0.5);
|
|
const innerY = evaluateBezier(...inner.y, 0.5);
|
|
link.midPoint = {
|
|
x: link.centerX + (outerX + innerX) / 2,
|
|
y: link.centerY + (outerY + innerY) / 2
|
|
};
|
|
nodeData.push(link);
|
|
}
|
|
for (const label of labelData) {
|
|
const node = nodeGraph.get(label.id)?.datum;
|
|
if (node == null)
|
|
continue;
|
|
label.radius = outerRadius + labelSpacing;
|
|
label.angle = normalizeAngle360(node.startAngle + angleBetween(node.startAngle, node.endAngle) / 2);
|
|
label.datumIndex = node.datumIndex;
|
|
label.nodeDatum = node;
|
|
}
|
|
labelData.sort((a, b) => a.angle - b.angle);
|
|
let minAngle = Infinity;
|
|
let maxAngle = -Infinity;
|
|
labelData = labelData.filter((label) => {
|
|
const labelHeight = calcLineHeight(fontSize);
|
|
const da = Math.atan2(labelHeight / 2, label.radius);
|
|
const a0 = label.angle - da;
|
|
const a1 = label.angle + da;
|
|
if (isBetweenAngles(minAngle, a0, a1))
|
|
return false;
|
|
if (isBetweenAngles(maxAngle, a0, a1))
|
|
return false;
|
|
minAngle = Math.min(a0, minAngle);
|
|
maxAngle = Math.max(a1, maxAngle);
|
|
return true;
|
|
});
|
|
return {
|
|
itemId: seriesId,
|
|
nodeData,
|
|
labelData
|
|
};
|
|
}
|
|
updateLabelSelection(opts) {
|
|
const labels = this.isLabelEnabled() ? opts.labelData : [];
|
|
return opts.labelSelection.update(labels);
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const params = {
|
|
toKey: this.properties.toKey,
|
|
fromKey: this.properties.fromKey,
|
|
sizeKey: this.properties.sizeKey,
|
|
size: Number.NaN
|
|
};
|
|
const activeHighlightDatum = this.getHighlightedDatum();
|
|
opts.labelSelection.each((label, labelNodeDatum) => {
|
|
const { size, text: text2, centerX, centerY, radius, angle: angle2, datumIndex, nodeDatum } = labelNodeDatum;
|
|
params.size = size;
|
|
const isHighlight = this.isLabelHighlighted(nodeDatum, activeHighlightDatum);
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex);
|
|
const style2 = getLabelStyles2(
|
|
this,
|
|
void 0,
|
|
params,
|
|
this.properties.label,
|
|
isHighlight,
|
|
activeHighlightDatum
|
|
);
|
|
const { fontStyle, fontWeight: fontWeight2, fontSize, fontFamily, color: fill } = style2;
|
|
label.visible = true;
|
|
label.translationX = centerX + radius * Math.cos(angle2);
|
|
label.translationY = centerY + radius * Math.sin(angle2);
|
|
label.text = text2;
|
|
label.fill = fill;
|
|
label.fontStyle = fontStyle;
|
|
label.fontWeight = fontWeight2;
|
|
label.fontSize = fontSize;
|
|
label.fontFamily = fontFamily;
|
|
label.textBaseline = "middle";
|
|
if (Math.cos(angle2) >= 0) {
|
|
label.textAlign = "left";
|
|
label.rotation = angle2;
|
|
} else {
|
|
label.textAlign = "right";
|
|
label.rotation = angle2 - Math.PI;
|
|
}
|
|
const opacity = highlightStyle.opacity ?? 1;
|
|
label.opacity = opacity;
|
|
label.fillOpacity = opacity;
|
|
label.setBoxing(style2);
|
|
});
|
|
}
|
|
updateNodeSelection(opts) {
|
|
return opts.datumSelection.update(opts.nodeData, void 0, (datum) => createDatumId2(datum.type, datum.id));
|
|
}
|
|
getNodeStyle(nodeDatum, fromNodeDatumIndex, isHighlight) {
|
|
const { properties } = this;
|
|
const { fills, strokes, fillGradientDefaults: fillGradientDefaults3, fillPatternDefaults: fillPatternDefaults3, fillImageDefaults: fillImageDefaults3 } = properties;
|
|
const { itemStyler } = properties.node;
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, nodeDatum.datumIndex);
|
|
const baseStyle = mergeDefaults(highlightStyle, properties.node.getStyle(fills, strokes, fromNodeDatumIndex));
|
|
let style2 = getShapeStyle2(baseStyle, fillGradientDefaults3, fillPatternDefaults3, fillImageDefaults3);
|
|
if (itemStyler != null && nodeDatum.datumIndex != null) {
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId2(nodeDatum.datumIndex.index, "node", isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const params = this.makeItemStylerParams(nodeDatum, isHighlight, style2);
|
|
return this.callWithContext(itemStyler, params);
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = getShapeStyle2(
|
|
mergeDefaults(overrides, style2),
|
|
fillGradientDefaults3,
|
|
fillPatternDefaults3,
|
|
fillImageDefaults3
|
|
);
|
|
}
|
|
}
|
|
style2.opacity = 1;
|
|
return style2;
|
|
}
|
|
makeItemStylerParams({ datum, datumIndex, size = 0, label }, isHighlight, style2) {
|
|
const { id: seriesId } = this;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightState = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
seriesId,
|
|
datum,
|
|
highlightState,
|
|
...style2,
|
|
size,
|
|
label,
|
|
fill
|
|
};
|
|
}
|
|
updateNodeNodes(opts) {
|
|
const { datumSelection, isHighlight } = opts;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
datumSelection.each((sector, datum) => {
|
|
const { datumIndex } = datum;
|
|
const style2 = this.getNodeStyle(datum, datumIndex.index, isHighlight);
|
|
sector.setStyleProperties(style2, fillBBox);
|
|
sector.centerX = datum.centerX;
|
|
sector.centerY = datum.centerY;
|
|
sector.innerRadius = datum.innerRadius;
|
|
sector.outerRadius = datum.outerRadius;
|
|
sector.startAngle = datum.startAngle;
|
|
sector.endAngle = datum.endAngle;
|
|
sector.inset = sector.strokeWidth / 2;
|
|
});
|
|
}
|
|
updateLinkSelection(opts) {
|
|
return opts.datumSelection.update(
|
|
opts.nodeData,
|
|
void 0,
|
|
(datum) => createDatumId2(datum.type, datum.index, datum.fromNode.id, datum.toNode.id)
|
|
);
|
|
}
|
|
getLinkStyle({ datumIndex, datum }, fromNodeDatumIndex, isHighlight) {
|
|
const { id: seriesId, properties } = this;
|
|
const { fills, strokes, fillGradientDefaults: fillGradientDefaults3, fillPatternDefaults: fillPatternDefaults3, fillImageDefaults: fillImageDefaults3 } = properties;
|
|
const { itemStyler } = properties.link;
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex);
|
|
const baseStyle = mergeDefaults(
|
|
highlightStyle,
|
|
properties.link.getStyle(fills, strokes, fromNodeDatumIndex.index)
|
|
);
|
|
let style2 = getShapeStyle2(baseStyle, fillGradientDefaults3, fillPatternDefaults3, fillImageDefaults3);
|
|
if (itemStyler != null && datumIndex != null) {
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId2(datumIndex.index, "link", isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const highlightState = this.getHighlightStateString(
|
|
activeHighlight,
|
|
isHighlight,
|
|
fromNodeDatumIndex
|
|
);
|
|
return this.callWithContext(itemStyler, {
|
|
seriesId,
|
|
datum,
|
|
highlightState,
|
|
...style2
|
|
});
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = getShapeStyle2(
|
|
mergeDefaults(overrides, style2),
|
|
fillGradientDefaults3,
|
|
fillPatternDefaults3,
|
|
fillImageDefaults3
|
|
);
|
|
}
|
|
}
|
|
style2.opacity = 1;
|
|
return style2;
|
|
}
|
|
updateLinkNodes(opts) {
|
|
const { datumSelection, isHighlight } = opts;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
datumSelection.each((link, datum) => {
|
|
const fromNodeDatumIndex = datum.fromNode.datumIndex;
|
|
const style2 = this.getLinkStyle(datum, fromNodeDatumIndex, isHighlight);
|
|
link.centerX = datum.centerX;
|
|
link.centerY = datum.centerY;
|
|
link.radius = datum.radius;
|
|
link.startAngle1 = datum.startAngle1;
|
|
link.endAngle1 = datum.endAngle1;
|
|
link.startAngle2 = datum.startAngle2;
|
|
link.endAngle2 = datum.endAngle2;
|
|
link.tension = style2.tension;
|
|
link.setStyleProperties(style2, fillBBox);
|
|
});
|
|
}
|
|
getShapeFillBBox() {
|
|
const width2 = this._nodeDataDependencies?.seriesRectWidth ?? 0;
|
|
const height2 = this._nodeDataDependencies?.seriesRectHeight ?? 0;
|
|
const size = Math.min(width2, height2);
|
|
const x = (width2 - size) / 2;
|
|
const y = (height2 - size) / 2;
|
|
const bbox = new BBox2(x, y, width2, height2);
|
|
return { series: bbox, axis: bbox };
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const {
|
|
id: seriesId,
|
|
linksProcessedData,
|
|
nodesProcessedData,
|
|
properties,
|
|
ctx: { formatManager }
|
|
} = this;
|
|
const { fromKey, toKey, sizeKey, sizeName, tooltip } = properties;
|
|
const seriesDatum = this.contextNodeData?.nodeData.find(
|
|
(d) => d.datumIndex.type === datumIndex.type && d.datumIndex.index === datumIndex.index
|
|
);
|
|
if (seriesDatum == null)
|
|
return;
|
|
const nodeIndex = seriesDatum.type === 0 /* Link */ ? seriesDatum.fromNode.index : seriesDatum.index;
|
|
const title = seriesDatum.type === 0 /* Link */ ? `${seriesDatum.fromNode.label} - ${seriesDatum.toNode.label}` : seriesDatum.label;
|
|
const datum = datumIndex.type === 0 /* Link */ ? linksProcessedData?.dataSources.get(this.id)?.data[datumIndex.index] : nodesProcessedData?.dataSources.get(this.id)?.data[datumIndex.index];
|
|
const size = seriesDatum.size;
|
|
let format;
|
|
if (seriesDatum.type === 0 /* Link */) {
|
|
const fromNodeDatumIndex = seriesDatum.fromNode.datumIndex;
|
|
format = this.getLinkStyle({ datumIndex, datum }, fromNodeDatumIndex, false);
|
|
} else {
|
|
const label = seriesDatum.label;
|
|
format = this.getNodeStyle({ datumIndex, datum, size, label }, datumIndex.index, false);
|
|
}
|
|
const data = [];
|
|
if (sizeKey != null) {
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value: size,
|
|
datum,
|
|
seriesId,
|
|
legendItemName: void 0,
|
|
key: sizeKey,
|
|
source: "tooltip",
|
|
property: "size",
|
|
domain: [],
|
|
boundSeries: this.getFormatterContext("size"),
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
});
|
|
data.push({ label: sizeName, fallbackLabel: sizeKey, value: content ?? String(size) });
|
|
}
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
title,
|
|
symbol: this.legendItemSymbol(seriesDatum.type, nodeIndex, format),
|
|
data
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title,
|
|
fromKey,
|
|
toKey,
|
|
sizeKey,
|
|
sizeName,
|
|
size,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
computeFocusBounds(node) {
|
|
return node;
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.node.itemStyler != null || this.properties.link.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
ChordSeries.className = "ChordSeries";
|
|
ChordSeries.type = "chord";
|
|
|
|
// packages/ag-charts-enterprise/src/series/chord/chordSeriesOptionsDef.ts
|
|
var { chordSeriesThemeableOptionsDef: chordSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var chordSeriesOptionsDef = {
|
|
...chordSeriesThemeableOptionsDef2,
|
|
...commonSeriesOptionsDefs,
|
|
type: required(constant("chord")),
|
|
fromKey: required(string),
|
|
toKey: required(string),
|
|
sizeKey: string,
|
|
sizeName: string
|
|
};
|
|
chordSeriesOptionsDef.fillGradientDefaults = undocumented(fillGradientDefaults);
|
|
chordSeriesOptionsDef.fillPatternDefaults = undocumented(fillPatternDefaults);
|
|
chordSeriesOptionsDef.fillImageDefaults = undocumented(fillImageDefaults);
|
|
|
|
// packages/ag-charts-enterprise/src/series/chord/chordModule.ts
|
|
var ChordSeriesModule = {
|
|
type: "series",
|
|
name: "chord",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
solo: true,
|
|
version: VERSION,
|
|
dependencies: [StandaloneChartModule],
|
|
options: chordSeriesOptionsDef,
|
|
themeTemplate: {
|
|
series: {
|
|
fills: { $palette: "fills" },
|
|
strokes: { $palette: "strokes" },
|
|
fillGradientDefaults: FILL_GRADIENT_LINEAR_DEFAULTS,
|
|
fillPatternDefaults: FILL_PATTERN_DEFAULTS,
|
|
fillImageDefaults: FILL_IMAGE_DEFAULTS,
|
|
highlight: SINGLE_SERIES_HIGHLIGHT_STYLE,
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" },
|
|
spacing: 5,
|
|
maxWidth: 100
|
|
},
|
|
node: {
|
|
spacing: 8,
|
|
width: 10,
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, 0] }
|
|
},
|
|
link: {
|
|
fillOpacity: 0.5,
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, 0] },
|
|
tension: 0.4
|
|
}
|
|
},
|
|
legend: {
|
|
enabled: false,
|
|
toggleSeries: false
|
|
}
|
|
},
|
|
create: (ctx) => new ChordSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/funnel/funnelConnector.ts
|
|
var { BBox: BBox3, Path: Path3 } = module_support_exports;
|
|
var delta3 = 1e-6;
|
|
function pointsEq2([ax, ay], [bx, by]) {
|
|
return Math.abs(ax - bx) <= delta3 && Math.abs(ay - by) <= delta3;
|
|
}
|
|
var FunnelConnector = class extends Path3 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.x0 = 0;
|
|
this.y0 = 0;
|
|
this.x1 = 0;
|
|
this.y1 = 0;
|
|
this.x2 = 0;
|
|
this.y2 = 0;
|
|
this.x3 = 0;
|
|
this.y3 = 0;
|
|
}
|
|
get midPoint() {
|
|
const { x0, y0, x1, y1, x2, y2, x3, y3 } = this;
|
|
return {
|
|
x: (x0 + x1 + x2 + x3) / 4,
|
|
y: (y0 + y1 + y2 + y3) / 4
|
|
};
|
|
}
|
|
distanceSquared(x, y) {
|
|
if (this.containsPoint(x, y))
|
|
return 0;
|
|
const { x0, y0, x1, y1, x2, y2, x3, y3 } = this;
|
|
return Math.min(
|
|
lineDistanceSquared(x, y, x0, y0, x1, y1, Infinity),
|
|
lineDistanceSquared(x, y, x1, y1, x2, y2, Infinity),
|
|
lineDistanceSquared(x, y, x2, y2, x3, y3, Infinity),
|
|
lineDistanceSquared(x, y, x3, y3, x0, y0, Infinity)
|
|
);
|
|
}
|
|
computeBBox() {
|
|
const { x0, y0, x1, y1, x2, y2, x3, y3 } = this;
|
|
const x = Math.min(x0, x1, x2, x3);
|
|
const width2 = Math.max(x0, x1, x2, x3) - x;
|
|
const y = Math.min(y0, y1, y2, y3);
|
|
const height2 = Math.max(y0, y1, y2, y3) - y;
|
|
return new BBox3(x, y, width2, height2);
|
|
}
|
|
updatePath() {
|
|
const { path, x0, y0, x1, y1, x2, y2, x3, y3 } = this;
|
|
const points = [
|
|
[x0, y0],
|
|
[x1, y1],
|
|
[x2, y2],
|
|
[x3, y3]
|
|
];
|
|
path.clear();
|
|
let start2;
|
|
let current;
|
|
for (const p of points) {
|
|
if (start2 != null && pointsEq2(start2, p) || current != null && pointsEq2(current, p)) {
|
|
continue;
|
|
}
|
|
const [x, y] = p;
|
|
if (start2 == null) {
|
|
path.moveTo(x, y);
|
|
} else {
|
|
path.lineTo(x, y);
|
|
}
|
|
start2 ?? (start2 = p);
|
|
current = p;
|
|
}
|
|
path.closePath();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], FunnelConnector.prototype, "x0", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], FunnelConnector.prototype, "y0", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], FunnelConnector.prototype, "x1", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], FunnelConnector.prototype, "y1", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], FunnelConnector.prototype, "x2", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], FunnelConnector.prototype, "y2", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], FunnelConnector.prototype, "x3", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], FunnelConnector.prototype, "y3", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/pyramid/pyramidProperties.ts
|
|
var { SeriesProperties: SeriesProperties3, makeSeriesTooltip: makeSeriesTooltip3, Label: Label4, DropShadow: DropShadow2 } = module_support_exports;
|
|
var PyramidSeriesLabel = class extends Label4 {
|
|
};
|
|
var PyramidSeriesStageLabel = class extends Label4 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.spacing = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidSeriesStageLabel.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidSeriesStageLabel.prototype, "placement", 2);
|
|
var PyramidProperties = class extends SeriesProperties3 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fills = [];
|
|
this.fillOpacity = 1;
|
|
this.strokes = [];
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.direction = "vertical";
|
|
this.reverse = void 0;
|
|
this.spacing = 0;
|
|
this.aspectRatio = void 0;
|
|
this.shadow = new DropShadow2().set({ enabled: false });
|
|
this.label = new PyramidSeriesLabel();
|
|
this.stageLabel = new PyramidSeriesStageLabel();
|
|
this.tooltip = makeSeriesTooltip3();
|
|
}
|
|
getStyle(index = 0) {
|
|
const { fills, strokes, fillOpacity, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = this;
|
|
return {
|
|
fill: fills[index % fills.length],
|
|
fillOpacity,
|
|
stroke: strokes[index % strokes.length],
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
opacity: 1
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "stageKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "valueKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "fills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "strokes", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "direction", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "reverse", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "aspectRatio", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "shadow", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "stageLabel", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PyramidProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/pyramid/pyramidUtil.ts
|
|
function applyPyramidDatum(connector, { x, y, top, right, bottom, left }) {
|
|
connector.x0 = x - top / 2;
|
|
connector.x1 = x + top / 2;
|
|
connector.x2 = x + bottom / 2;
|
|
connector.x3 = x - bottom / 2;
|
|
connector.y0 = y - left / 2;
|
|
connector.y1 = y - right / 2;
|
|
connector.y2 = y + right / 2;
|
|
connector.y3 = y + left / 2;
|
|
}
|
|
function preparePyramidAnimationFunctions(direction) {
|
|
const fromFn = (_connector, datum) => {
|
|
const { x, y } = datum;
|
|
let { top, right, bottom, left } = datum;
|
|
if (direction === "vertical") {
|
|
top = 0;
|
|
bottom = 0;
|
|
} else {
|
|
left = 0;
|
|
right = 0;
|
|
}
|
|
return { x, y, top, right, bottom, left };
|
|
};
|
|
const toFn = (_connector, datum) => {
|
|
const { x, y, top, right, bottom, left } = datum;
|
|
return { x, y, top, right, bottom, left };
|
|
};
|
|
const applyFn = applyPyramidDatum;
|
|
return { fromFn, toFn, applyFn };
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/pyramid/pyramidSeries.ts
|
|
var {
|
|
valueProperty: valueProperty3,
|
|
SeriesNodePickMode: SeriesNodePickMode3,
|
|
createDatumId: createDatumId3,
|
|
BBox: BBox4,
|
|
Group: Group5,
|
|
Selection: Selection3,
|
|
Text: Text2,
|
|
PointerEvents: PointerEvents2,
|
|
fromToMotion: fromToMotion2,
|
|
seriesLabelFadeInAnimation: seriesLabelFadeInAnimation2,
|
|
getLabelStyles: getLabelStyles3
|
|
} = module_support_exports;
|
|
var PyramidSeries = class extends module_support_exports.DataModelSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
categoryKey: void 0,
|
|
pickModes: [SeriesNodePickMode3.EXACT_SHAPE_MATCH, SeriesNodePickMode3.NEAREST_NODE]
|
|
});
|
|
this.properties = new PyramidProperties();
|
|
this.itemGroup = this.contentGroup.appendChild(new Group5({ name: "itemGroup" }));
|
|
this.itemLabelGroup = this.contentGroup.appendChild(new Group5({ name: "itemLabelGroup" }));
|
|
this.stageLabelGroup = this.contentGroup.appendChild(new Group5({ name: "stageLabelGroup" }));
|
|
this.datumSelection = Selection3.select(
|
|
this.itemGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.labelSelection = Selection3.select(
|
|
this.itemLabelGroup,
|
|
Text2
|
|
);
|
|
this.stageLabelSelection = Selection3.select(this.stageLabelGroup, Text2);
|
|
this.highlightLabelSelection = Selection3.select(this.highlightLabelGroup, Text2);
|
|
this.highlightDatumSelection = Selection3.select(
|
|
this.highlightNodeGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.animationState = new StateMachine(
|
|
"empty",
|
|
{
|
|
empty: {
|
|
update: {
|
|
target: "ready",
|
|
action: () => this.animateEmptyUpdateReady()
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
ready: {
|
|
clear: "empty",
|
|
reset: "empty",
|
|
skip: "ready"
|
|
}
|
|
},
|
|
() => this.checkProcessedDataAnimatable()
|
|
);
|
|
this.itemLabelGroup.pointerEvents = PointerEvents2.None;
|
|
this.stageLabelGroup.pointerEvents = PointerEvents2.None;
|
|
this.cleanup.register(this.ctx.eventsHub.on("legend:item-click", (event) => this.onLegendItemClick(event)));
|
|
}
|
|
nodeFactory() {
|
|
return new FunnelConnector();
|
|
}
|
|
getNodeData() {
|
|
return this.contextNodeData?.nodeData;
|
|
}
|
|
resetAnimation(phase) {
|
|
if (phase === "initial") {
|
|
this.animationState.transition("reset");
|
|
} else if (phase === "ready") {
|
|
this.animationState.transition("skip");
|
|
}
|
|
}
|
|
async processData(dataController) {
|
|
if (this.data == null)
|
|
return;
|
|
const {
|
|
id: seriesId,
|
|
visible,
|
|
ctx: { legendManager }
|
|
} = this;
|
|
const { stageKey, valueKey } = this.properties;
|
|
const xScaleType = "category";
|
|
const yScaleType = "number";
|
|
const validation = (_value, _datum, index) => visible && legendManager.getItemEnabled({ seriesId, itemId: index });
|
|
const visibleProps = this.visible ? {} : { forceValue: 0 };
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
await this.requestDataModel(dataController, this.data, {
|
|
props: [
|
|
valueProperty3(stageKey, xScaleType, { id: "xValue", allowNullKey }),
|
|
valueProperty3(valueKey, yScaleType, { id: `yValue`, ...visibleProps, validation, invalidValue: 0 })
|
|
]
|
|
});
|
|
}
|
|
createNodeData() {
|
|
const {
|
|
id: seriesId,
|
|
dataModel,
|
|
processedData,
|
|
properties,
|
|
visible,
|
|
ctx: { legendManager }
|
|
} = this;
|
|
const {
|
|
stageKey,
|
|
valueKey,
|
|
direction,
|
|
reverse = direction === "horizontal",
|
|
spacing,
|
|
aspectRatio,
|
|
label,
|
|
stageLabel
|
|
} = properties;
|
|
if (dataModel == null || processedData == null)
|
|
return;
|
|
const horizontal = direction === "horizontal";
|
|
const xValues = dataModel.resolveColumnById(this, `xValue`, processedData);
|
|
const yValues = dataModel.resolveColumnById(this, `yValue`, processedData);
|
|
const xDomain = dataModel.getDomain(this, "xValue", "value", processedData).domain;
|
|
const yDomain = dataModel.getDomain(this, "yValue", "value", processedData).domain;
|
|
const textMeasurer = cachedTextMeasurer(stageLabel);
|
|
let textAlign;
|
|
let textBaseline;
|
|
if (horizontal) {
|
|
textAlign = "center";
|
|
textBaseline = stageLabel.placement === "before" ? "bottom" : "top";
|
|
} else {
|
|
textAlign = stageLabel.placement === "after" ? "left" : "right";
|
|
textBaseline = "middle";
|
|
}
|
|
const stageLabelData = stageLabel.enabled ? [] : void 0;
|
|
let maxLabelWidth = 0;
|
|
let maxLabelHeight = 0;
|
|
let yTotal = 0;
|
|
const rawData = processedData.dataSources.get(this.id)?.data ?? [];
|
|
for (const [datumIndex, datum] of rawData.entries()) {
|
|
const xValue = xValues[datumIndex];
|
|
if (xValue === void 0 && !this.properties.allowNullKeys)
|
|
continue;
|
|
const yValue = yValues[datumIndex];
|
|
const enabled = visible && legendManager.getItemEnabled({ seriesId, itemId: datumIndex });
|
|
yTotal += yValue;
|
|
if (stageLabelData == null)
|
|
continue;
|
|
const text2 = this.getLabelText(
|
|
xValue,
|
|
datum,
|
|
stageKey,
|
|
"x",
|
|
xDomain,
|
|
this.properties.stageLabel,
|
|
{ datum, value: yValue, stageKey, valueKey },
|
|
this.properties.allowNullKeys ?? false
|
|
);
|
|
const { width: width2, height: height2 } = isArray(text2) ? measureTextSegments(text2, label) : textMeasurer.measureLines(toTextString(text2));
|
|
maxLabelWidth = Math.max(maxLabelWidth, width2);
|
|
maxLabelHeight = Math.max(maxLabelHeight, height2);
|
|
stageLabelData.push({
|
|
x: Number.NaN,
|
|
y: Number.NaN,
|
|
text: text2,
|
|
textAlign,
|
|
textBaseline,
|
|
visible: enabled
|
|
});
|
|
}
|
|
const seriesRectWidth = this._nodeDataDependencies?.seriesRectWidth ?? 0;
|
|
const seriesRectHeight = this._nodeDataDependencies?.seriesRectHeight ?? 0;
|
|
const totalSpacing = spacing * (processedData.input.count - 1);
|
|
let bounds;
|
|
if (horizontal) {
|
|
const verticalInset = maxLabelHeight + stageLabel.spacing;
|
|
bounds = new BBox4(
|
|
0,
|
|
stageLabel.placement === "before" ? verticalInset : 0,
|
|
seriesRectWidth,
|
|
seriesRectHeight - verticalInset
|
|
);
|
|
} else {
|
|
const horizontalInset = maxLabelWidth + stageLabel.spacing;
|
|
bounds = new BBox4(
|
|
stageLabel.placement === "after" ? 0 : horizontalInset,
|
|
0,
|
|
seriesRectWidth - horizontalInset,
|
|
seriesRectHeight
|
|
);
|
|
}
|
|
if (aspectRatio != null && aspectRatio !== 0) {
|
|
const directionalAspectRatio = direction === "horizontal" ? 1 / aspectRatio : aspectRatio;
|
|
const constrainedWidth = Math.min(bounds.width, bounds.height * directionalAspectRatio);
|
|
const constrainedHeight = constrainedWidth / directionalAspectRatio;
|
|
bounds = new BBox4(
|
|
bounds.x + (bounds.width - constrainedWidth) / 2,
|
|
bounds.y + (bounds.height - constrainedHeight) / 2,
|
|
constrainedWidth,
|
|
constrainedHeight
|
|
);
|
|
}
|
|
let labelX;
|
|
let labelY;
|
|
if (horizontal) {
|
|
labelY = stageLabel.placement === "before" ? bounds.y - stageLabel.spacing : bounds.y + bounds.height + stageLabel.spacing;
|
|
} else {
|
|
labelX = stageLabel.placement === "after" ? bounds.x + bounds.width + stageLabel.spacing : bounds.x - stageLabel.spacing;
|
|
}
|
|
const availableWidth = bounds.width - (horizontal ? totalSpacing : 0);
|
|
const availableHeight = bounds.height - (horizontal ? 0 : totalSpacing);
|
|
if (availableWidth < 0 || availableHeight < 0)
|
|
return;
|
|
const nodeData = [];
|
|
const labelData = [];
|
|
let yStart = 0;
|
|
let stageLabelIndex = 0;
|
|
for (const [datumIndex, datum] of rawData.entries()) {
|
|
const xValue = xValues[datumIndex];
|
|
if (xValue === void 0 && !this.properties.allowNullKeys)
|
|
continue;
|
|
const yValue = yValues[datumIndex];
|
|
const enabled = visible && legendManager.getItemEnabled({ seriesId, itemId: datumIndex });
|
|
const yEnd = yStart + yValue;
|
|
const yMidRatio = (yStart + yEnd) / (2 * yTotal);
|
|
const yRangeRatio = (yEnd - yStart) / yTotal;
|
|
const xOffset = horizontal ? availableWidth * yMidRatio + spacing * datumIndex : availableWidth * 0.5;
|
|
const yOffset = horizontal ? availableHeight * 0.5 : availableHeight * yMidRatio + spacing * datumIndex;
|
|
const x = bounds.x + xOffset;
|
|
const y = bounds.y + yOffset;
|
|
if (stageLabelData != null) {
|
|
const stageLabelDatum = stageLabelData[stageLabelIndex++];
|
|
stageLabelDatum.x = labelX ?? x;
|
|
stageLabelDatum.y = labelY ?? y;
|
|
}
|
|
let top;
|
|
let right;
|
|
let bottom;
|
|
let left;
|
|
if (horizontal) {
|
|
const barWidth = availableWidth * yRangeRatio;
|
|
top = barWidth;
|
|
bottom = barWidth;
|
|
const y0 = (xOffset + barWidth / 2) * (availableHeight / bounds.width);
|
|
const y1 = (xOffset - barWidth / 2) * (availableHeight / bounds.width);
|
|
right = reverse ? bounds.height - y0 : y0;
|
|
left = reverse ? bounds.height - y1 : y1;
|
|
} else {
|
|
const barHeight = availableHeight * yRangeRatio;
|
|
right = barHeight;
|
|
left = barHeight;
|
|
const x0 = (yOffset - barHeight / 2) * (availableWidth / bounds.height);
|
|
const x1 = (yOffset + barHeight / 2) * (availableWidth / bounds.height);
|
|
top = reverse ? bounds.width - x0 : x0;
|
|
bottom = reverse ? bounds.width - x1 : x1;
|
|
}
|
|
const text2 = this.getLabelText(
|
|
yValue,
|
|
datum,
|
|
valueKey,
|
|
"y",
|
|
yDomain,
|
|
label,
|
|
{
|
|
datum,
|
|
value: yValue,
|
|
stageKey,
|
|
valueKey
|
|
}
|
|
);
|
|
const labelDatum = {
|
|
x,
|
|
y,
|
|
text: text2,
|
|
textAlign: "center",
|
|
textBaseline: "middle",
|
|
visible: enabled
|
|
};
|
|
labelData.push(labelDatum);
|
|
nodeData.push({
|
|
series: this,
|
|
datum,
|
|
datumIndex,
|
|
index: datumIndex,
|
|
xValue,
|
|
yValue,
|
|
x,
|
|
y,
|
|
top,
|
|
right,
|
|
bottom,
|
|
left,
|
|
label: labelDatum,
|
|
enabled,
|
|
midPoint: {
|
|
x,
|
|
y
|
|
},
|
|
style: this.getItemStyle({ datumIndex, datum }, false)
|
|
});
|
|
yStart = yEnd;
|
|
}
|
|
return {
|
|
itemId: seriesId,
|
|
nodeData,
|
|
labelData,
|
|
stageLabelData,
|
|
bounds
|
|
};
|
|
}
|
|
updateSelections() {
|
|
if (this.nodeDataRefresh) {
|
|
this.contextNodeData = this.createNodeData();
|
|
this.nodeDataRefresh = false;
|
|
}
|
|
}
|
|
update({ seriesRect }) {
|
|
this.checkResize(seriesRect);
|
|
const {
|
|
datumSelection,
|
|
labelSelection,
|
|
stageLabelSelection,
|
|
highlightDatumSelection,
|
|
highlightLabelSelection
|
|
} = this;
|
|
this.updateSelections();
|
|
this.contentGroup.visible = this.visible;
|
|
this.contentGroup.opacity = this.getOpacity();
|
|
let highlightedDatum = this.ctx.highlightManager?.getActiveHighlight();
|
|
if (highlightedDatum != null && (highlightedDatum.series !== this || highlightedDatum.datum == null)) {
|
|
highlightedDatum = void 0;
|
|
}
|
|
const nodeData = this.contextNodeData?.nodeData ?? [];
|
|
const labelData = this.contextNodeData?.labelData ?? [];
|
|
const stageLabelData = this.contextNodeData?.stageLabelData ?? [];
|
|
this.datumSelection = this.updateDatumSelection({ nodeData, datumSelection });
|
|
this.updateDatumStyles({ datumSelection, isHighlight: false });
|
|
this.updateDatumNodes({ datumSelection, isHighlight: false });
|
|
this.labelSelection = this.updateLabelSelection({ labelData, labelSelection });
|
|
this.updateLabelNodes({ labelSelection, labelProperties: this.properties.label });
|
|
this.stageLabelSelection = this.updateStageLabelSelection({ stageLabelData, stageLabelSelection });
|
|
this.updateLabelNodes({
|
|
labelSelection: stageLabelSelection,
|
|
labelProperties: this.properties.stageLabel,
|
|
checkActiveHighlight: true
|
|
});
|
|
const highlightLabelData = this.getHighlightLabelData(labelData, highlightedDatum) ?? [];
|
|
this.highlightLabelSelection = highlightLabelSelection.update(highlightLabelData);
|
|
this.updateLabelNodes({
|
|
labelSelection: this.highlightLabelSelection,
|
|
labelProperties: this.properties.label,
|
|
isHighlight: true
|
|
});
|
|
this.highlightDatumSelection = this.updateDatumSelection({
|
|
nodeData: highlightedDatum == null ? [] : [highlightedDatum],
|
|
datumSelection: highlightDatumSelection
|
|
});
|
|
this.updateDatumStyles({ datumSelection: highlightDatumSelection, isHighlight: true });
|
|
this.updateDatumNodes({ datumSelection: highlightDatumSelection, isHighlight: true });
|
|
this.animationState.transition("update");
|
|
}
|
|
updateDatumSelection(opts) {
|
|
return opts.datumSelection.update(opts.nodeData);
|
|
}
|
|
getItemStyle({ datumIndex, datum }, isHighlight) {
|
|
const { properties } = this;
|
|
const { itemStyler } = properties;
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex);
|
|
const baseStyle = mergeDefaults(highlightStyle, properties.getStyle(datumIndex));
|
|
let style2 = baseStyle;
|
|
if (itemStyler != null && datumIndex != null) {
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId3(datumIndex, isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const params = this.makeItemStylerParams(datum, datumIndex, isHighlight, style2);
|
|
return this.callWithContext(itemStyler, params);
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(overrides, style2);
|
|
}
|
|
}
|
|
return style2;
|
|
}
|
|
makeItemStylerParams(datum, datumIndex, isHighlight, style2) {
|
|
const { id: seriesId, properties } = this;
|
|
const { stageKey, valueKey } = properties;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightState = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
seriesId,
|
|
datum,
|
|
stageKey,
|
|
valueKey,
|
|
highlightState,
|
|
...style2,
|
|
fill
|
|
};
|
|
}
|
|
updateDatumStyles({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
datumSelection.each((_, nodeDatum) => {
|
|
nodeDatum.style = this.getItemStyle(nodeDatum, isHighlight);
|
|
});
|
|
}
|
|
updateDatumNodes({
|
|
datumSelection
|
|
}) {
|
|
const { properties } = this;
|
|
const { shadow } = properties;
|
|
const bounds = this.contextNodeData?.bounds;
|
|
const fillBBox = bounds ? { series: bounds, axis: bounds } : void 0;
|
|
datumSelection.each((connector, nodeDatum) => {
|
|
connector.setStyleProperties(nodeDatum.style, fillBBox);
|
|
applyPyramidDatum(connector, nodeDatum);
|
|
connector.fillShadow = shadow;
|
|
});
|
|
}
|
|
updateLabelSelection(opts) {
|
|
return opts.labelSelection.update(this.properties.label.enabled ? opts.labelData : []);
|
|
}
|
|
updateStageLabelSelection(opts) {
|
|
return opts.stageLabelSelection.update(opts.stageLabelData);
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const { labelSelection, labelProperties, isHighlight = false, checkActiveHighlight = false } = opts;
|
|
labelSelection.each((label, nodeDatum, datumIndex) => {
|
|
const { visible, x, y, text: text2, textAlign, textBaseline } = nodeDatum;
|
|
const datumIsHighlighted = isHighlight || checkActiveHighlight && activeHighlight?.datumIndex === datumIndex;
|
|
const highlightStyle = this.getHighlightStyle(datumIsHighlighted, datumIndex);
|
|
const style2 = getLabelStyles3(
|
|
this,
|
|
void 0,
|
|
this.properties,
|
|
labelProperties,
|
|
datumIsHighlighted,
|
|
activeHighlight
|
|
);
|
|
const { color: fill, fontSize, fontStyle, fontWeight: fontWeight2, fontFamily } = style2;
|
|
label.visible = visible;
|
|
label.x = x;
|
|
label.y = y;
|
|
label.text = text2;
|
|
label.fill = fill;
|
|
label.opacity = (highlightStyle.opacity ?? 1) * (style2.fillOpacity ?? 1);
|
|
label.fillOpacity = (highlightStyle.opacity ?? 1) * (style2.fillOpacity ?? 1);
|
|
label.fontStyle = fontStyle;
|
|
label.fontWeight = fontWeight2;
|
|
label.fontSize = fontSize;
|
|
label.fontFamily = fontFamily;
|
|
label.textAlign = textAlign;
|
|
label.textBaseline = textBaseline;
|
|
label.setBoxing(style2);
|
|
});
|
|
}
|
|
getHighlightLabelData(_labelData, highlightedItem) {
|
|
if (highlightedItem?.label) {
|
|
return [{ ...highlightedItem.label }];
|
|
}
|
|
return void 0;
|
|
}
|
|
computeFocusBounds(opts) {
|
|
const datum = this.getNodeData()?.[opts.datumIndex];
|
|
if (datum === void 0)
|
|
return;
|
|
for (const node of this.datumSelection) {
|
|
if (node.datum === datum) {
|
|
return node.node;
|
|
}
|
|
}
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, properties } = this;
|
|
const { stageKey, valueKey, tooltip } = properties;
|
|
if (!dataModel || !processedData)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const xValue = dataModel.resolveColumnById(this, "xValue", processedData)[datumIndex];
|
|
const yValue = dataModel.resolveColumnById(this, `yValue`, processedData)[datumIndex];
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
if (xValue === void 0 && !allowNullKeys)
|
|
return;
|
|
const label = this.getLabelText(
|
|
xValue,
|
|
datum,
|
|
stageKey,
|
|
"x",
|
|
dataModel.getDomain(this, "xValue", "value", processedData).domain,
|
|
this.properties.stageLabel,
|
|
{ datum, value: xValue, stageKey, valueKey }
|
|
);
|
|
const format = this.getItemStyle({ datumIndex, datum }, false);
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
symbol: this.legendItemSymbol(datumIndex),
|
|
data: [{ label: toPlainText(label), value: toPlainText(yValue) }]
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: void 0,
|
|
stageKey,
|
|
valueKey,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
getSeriesDomain() {
|
|
return { domain: [Number.NaN, Number.NaN] };
|
|
}
|
|
getSeriesRange() {
|
|
return [Number.NaN, Number.NaN];
|
|
}
|
|
pickNodeClosestDatum({ x, y }) {
|
|
let minDistanceSquared = Infinity;
|
|
let minDatum;
|
|
this.datumSelection.each((node, datum) => {
|
|
const distanceSquared2 = node.distanceSquared(x, y);
|
|
if (distanceSquared2 < minDistanceSquared) {
|
|
minDistanceSquared = distanceSquared2;
|
|
minDatum = datum;
|
|
}
|
|
});
|
|
return minDatum == null ? void 0 : { datum: minDatum, distance: Math.sqrt(minDistanceSquared) };
|
|
}
|
|
legendItemSymbol(datumIndex) {
|
|
const { fills, strokes, strokeWidth, fillOpacity, strokeOpacity, lineDash, lineDashOffset } = this.properties;
|
|
const fill = fills[datumIndex] ?? "black";
|
|
const stroke3 = strokes[datumIndex] ?? "black";
|
|
return {
|
|
marker: {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
const {
|
|
processedData,
|
|
dataModel,
|
|
id: seriesId,
|
|
ctx: { legendManager },
|
|
visible
|
|
} = this;
|
|
if (!dataModel || !processedData || legendType !== "category") {
|
|
return [];
|
|
}
|
|
const { showInLegend } = this.properties;
|
|
const stageValues = dataModel.resolveColumnById(this, `xValue`, processedData);
|
|
return (processedData.dataSources.get(this.id)?.data ?? []).map((datum, datumIndex) => {
|
|
const stageValue = stageValues[datumIndex];
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
if (stageValue == null && !allowNullKeys)
|
|
return;
|
|
return {
|
|
legendType: "category",
|
|
id: seriesId,
|
|
datum,
|
|
itemId: datumIndex,
|
|
seriesId,
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId, itemId: datumIndex }),
|
|
label: { text: String(stageValue) },
|
|
symbol: this.legendItemSymbol(datumIndex),
|
|
hideInLegend: !showInLegend
|
|
};
|
|
}).filter((datum) => datum != null);
|
|
}
|
|
animateReset() {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
this.ctx.animationManager.stopByAnimationGroupId(this.id);
|
|
}
|
|
animateEmptyUpdateReady() {
|
|
const { datumSelection, labelSelection, properties } = this;
|
|
const fns = preparePyramidAnimationFunctions(properties.direction);
|
|
fromToMotion2(this.id, "nodes", this.ctx.animationManager, [datumSelection], fns);
|
|
seriesLabelFadeInAnimation2(this, "labels", this.ctx.animationManager, labelSelection);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
PyramidSeries.className = "PyramidSeries";
|
|
PyramidSeries.type = "pyramid";
|
|
|
|
// packages/ag-charts-enterprise/src/series/pyramid/pyramidSeriesOptionsDef.ts
|
|
var { pyramidSeriesThemeableOptionsDef: pyramidSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var pyramidSeriesOptionsDef = {
|
|
...pyramidSeriesThemeableOptionsDef2,
|
|
...commonSeriesOptionsDefs,
|
|
type: required(constant("pyramid")),
|
|
stageKey: required(string),
|
|
valueKey: required(string)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/pyramid/pyramidThemes.ts
|
|
var PYRAMID_SERIES_THEME = {
|
|
series: {
|
|
direction: "vertical",
|
|
strokeWidth: { $isUserOption: ["./strokes/0", 2, 0] },
|
|
spacing: 2,
|
|
fills: {
|
|
$applyCycle: [
|
|
{ $size: { $path: ["./data", { $path: "/data" }] } },
|
|
{ $palette: "fills" },
|
|
{
|
|
$applySwitch: [
|
|
{ $path: ["/type", void 0, { $value: "$1" }] },
|
|
{ $value: "$1" },
|
|
["gradient", FILL_GRADIENT_LINEAR_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS]
|
|
]
|
|
}
|
|
]
|
|
},
|
|
strokes: {
|
|
$applyCycle: [{ $size: { $path: ["./data", { $path: "/data" }] } }, { $palette: "strokes" }]
|
|
},
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "chartBackgroundColor" }
|
|
},
|
|
stageLabel: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" },
|
|
spacing: 12
|
|
},
|
|
shadow: {
|
|
enabled: false,
|
|
color: DEFAULT_SHADOW_COLOUR,
|
|
xOffset: 3,
|
|
yOffset: 3,
|
|
blur: 5
|
|
},
|
|
highlight: {
|
|
unhighlightedItem: {
|
|
opacity: 0.4
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/pyramid/pyramidModule.ts
|
|
var PyramidSeriesModule = {
|
|
type: "series",
|
|
name: "pyramid",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
solo: true,
|
|
version: VERSION,
|
|
dependencies: [StandaloneChartModule],
|
|
options: pyramidSeriesOptionsDef,
|
|
themeTemplate: PYRAMID_SERIES_THEME,
|
|
create: (ctx) => new PyramidSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/sankey/sankeyLink.ts
|
|
var { BBox: BBox5, Path: Path4 } = module_support_exports;
|
|
var SankeyLink = class extends Path4 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.x1 = 0;
|
|
this.x2 = 0;
|
|
this.y1 = 0;
|
|
this.y2 = 0;
|
|
this.height = 0;
|
|
this.inset = 0;
|
|
this.elbows = [];
|
|
}
|
|
computeBBox() {
|
|
const x = Math.min(this.x1, this.x2);
|
|
const width2 = Math.max(this.x1, this.x2) - x;
|
|
const y = Math.min(this.y1, this.y2);
|
|
const height2 = Math.max(this.y1, this.y2) - y + this.height;
|
|
return new BBox5(x, y, width2, height2);
|
|
}
|
|
updatePath() {
|
|
const { path, inset } = this;
|
|
path.clear();
|
|
const height2 = this.height - 2 * this.inset;
|
|
const offset = height2 / 2;
|
|
let x1 = this.x1 + inset;
|
|
let y1 = this.y1 + inset;
|
|
path.moveTo(x1, y1);
|
|
for (const elbow of this.elbows) {
|
|
this.updatePathSection(x1, y1, elbow.x, elbow.y, height2, -offset);
|
|
x1 = elbow.x;
|
|
y1 = elbow.y;
|
|
}
|
|
const x2 = this.x2 - inset;
|
|
const y2 = this.y2 + inset;
|
|
this.updatePathSection(x1, y1, x2, y2, height2, -offset);
|
|
path.lineTo(x2, y2 + height2);
|
|
x1 = x2;
|
|
y1 = y2;
|
|
for (const elbow of this.elbows.toReversed()) {
|
|
this.updatePathSection(x1, y1, elbow.x, elbow.y, height2, offset);
|
|
x1 = elbow.x;
|
|
y1 = elbow.y;
|
|
}
|
|
this.updatePathSection(x1, y1, this.x1 + inset, this.y1 + inset, height2, offset);
|
|
path.closePath();
|
|
}
|
|
updatePathSection(x1, y1, x2, y2, height2, yOffset) {
|
|
const { path } = this;
|
|
const start2 = vector_exports.from(x1, y1 + yOffset + height2 / 2);
|
|
const end3 = vector_exports.from(x2, y2 + yOffset + height2 / 2);
|
|
if (Math.abs(end3.y - start2.y) < 2) {
|
|
path.lineTo(end3.x, end3.y);
|
|
return;
|
|
}
|
|
let angle2 = vector_exports.angle(vector_exports.sub(end3, start2));
|
|
if (angle2 < 0)
|
|
angle2 = 2 * Math.PI + angle2;
|
|
const right = 0;
|
|
const down = Math.PI / 2;
|
|
const left = Math.PI;
|
|
const up = Math.PI * 1.5;
|
|
const innerArc = getArcValues(start2, end3, 0);
|
|
const outerArc = getArcValues(start2, end3, height2);
|
|
if (innerArc.radius < height2) {
|
|
path.cubicCurveTo((start2.x + end3.x) / 2, start2.y, (start2.x + end3.x) / 2, end3.y, end3.x, end3.y);
|
|
return;
|
|
}
|
|
if (angle2 >= up) {
|
|
path.arc(start2.x, y1 - innerArc.radius, innerArc.radius, down, down + outerArc.angle, true);
|
|
path.arc(end3.x, y2 + outerArc.radius, outerArc.radius, up + outerArc.angle, up);
|
|
path.lineTo(end3.x, end3.y);
|
|
} else if (angle2 > right && angle2 <= down) {
|
|
path.arc(start2.x, y1 + outerArc.radius, outerArc.radius, up, up + outerArc.angle);
|
|
path.arc(end3.x, y2 - innerArc.radius, innerArc.radius, down + innerArc.angle, down, true);
|
|
path.lineTo(end3.x, end3.y);
|
|
} else if (angle2 > down && angle2 <= left) {
|
|
path.arc(start2.x, y1 + outerArc.radius, outerArc.radius - height2, up, up + outerArc.angle, true);
|
|
path.arc(end3.x, y2 - innerArc.radius, innerArc.radius + height2, down + innerArc.angle, down);
|
|
path.lineTo(end3.x, end3.y);
|
|
} else {
|
|
path.arc(start2.x, y1 - innerArc.radius, innerArc.radius + height2, down, down + innerArc.angle);
|
|
path.arc(end3.x, y2 + outerArc.radius, outerArc.radius - height2, up + outerArc.angle, up, true);
|
|
path.lineTo(end3.x, end3.y);
|
|
}
|
|
}
|
|
};
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], SankeyLink.prototype, "x1", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], SankeyLink.prototype, "x2", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], SankeyLink.prototype, "y1", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], SankeyLink.prototype, "y2", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], SankeyLink.prototype, "height", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], SankeyLink.prototype, "inset", 2);
|
|
function getArcValues(start2, end3, minRadius) {
|
|
const lineAngle = vector_exports.angle(vector_exports.sub(end3, start2));
|
|
const chordLength = vector_exports.distance(start2, end3);
|
|
const bisect = vector_exports.add(start2, vector_exports.rotate(vector_exports.from(chordLength / 2, 0), lineAngle));
|
|
const gradient2 = -1 / vector_exports.gradient(start2, end3);
|
|
const intercept2 = vector_exports.intercept(bisect, gradient2);
|
|
const offset = lerpClamp(0.1, 0.5, Math.PI / 2 - Math.abs(vector_exports.gradient(start2, end3)));
|
|
const center2 = vector_exports.intersectAtX(gradient2, intercept2, start2.x);
|
|
const radius = Math.max(minRadius, vector_exports.distance(start2, center2) * offset);
|
|
const angle2 = vector_exports.angle(vector_exports.sub(center2, start2), vector_exports.sub(center2, bisect)) / -(1.1 - offset);
|
|
return { angle: angle2, radius };
|
|
}
|
|
function lerpClamp(a, b, ratio2) {
|
|
return clamp(a, (b - a) * ratio2 + a, b);
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/sankey/sankeySeriesProperties.ts
|
|
var { FillGradientDefaults: FillGradientDefaults3, FillPatternDefaults: FillPatternDefaults3, FillImageDefaults: FillImageDefaults3, makeSeriesTooltip: makeSeriesTooltip4, SeriesProperties: SeriesProperties4, Label: Label5 } = module_support_exports;
|
|
var SankeySeriesLabelProperties = class extends Label5 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.spacing = 1;
|
|
this.placement = void 0;
|
|
this.edgePlacement = void 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesLabelProperties.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesLabelProperties.prototype, "placement", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesLabelProperties.prototype, "edgePlacement", 2);
|
|
var SankeySeriesLinkProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = void 0;
|
|
this.fillOpacity = 1;
|
|
this.stroke = void 0;
|
|
this.strokeOpacity = 1;
|
|
this.strokeWidth = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesLinkProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesLinkProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesLinkProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesLinkProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesLinkProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesLinkProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesLinkProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesLinkProperties.prototype, "itemStyler", 2);
|
|
var SankeySeriesNodeProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.spacing = 1;
|
|
this.minSpacing = 0;
|
|
this.width = 1;
|
|
this.alignment = "justify";
|
|
this.verticalAlignment = "center";
|
|
this.sort = "auto";
|
|
this.fill = void 0;
|
|
this.fillOpacity = 1;
|
|
this.stroke = void 0;
|
|
this.strokeOpacity = 1;
|
|
this.strokeWidth = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "minSpacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "width", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "alignment", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "verticalAlignment", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "sort", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesNodeProperties.prototype, "itemStyler", 2);
|
|
var SankeySeriesProperties = class extends SeriesProperties4 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.nodes = void 0;
|
|
this.idKey = "";
|
|
this.idName = void 0;
|
|
this.labelKey = void 0;
|
|
this.labelName = void 0;
|
|
this.sizeKey = void 0;
|
|
this.sizeName = void 0;
|
|
this.fillGradientDefaults = new FillGradientDefaults3();
|
|
this.fillPatternDefaults = new FillPatternDefaults3();
|
|
this.fillImageDefaults = new FillImageDefaults3();
|
|
this.defaultColorRange = [];
|
|
this.defaultPatternFills = [];
|
|
this.fills = [];
|
|
this.strokes = [];
|
|
this.label = new SankeySeriesLabelProperties();
|
|
this.link = new SankeySeriesLinkProperties();
|
|
this.node = new SankeySeriesNodeProperties();
|
|
this.tooltip = makeSeriesTooltip4();
|
|
}
|
|
getStyle(isLink, fills, strokes, index) {
|
|
const {
|
|
fillOpacity,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
fill = fills[index % fills.length],
|
|
stroke: stroke3 = strokes[index % fills.length]
|
|
} = isLink ? this.link : this.node;
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "nodes", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "fromKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "toKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "idKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "idName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "labelKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "labelName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "sizeKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "sizeName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "fillGradientDefaults", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "fillPatternDefaults", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "fillImageDefaults", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "defaultColorRange", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "defaultPatternFills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "fills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "strokes", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "link", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "node", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SankeySeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/sankey/sankeySeries.ts
|
|
var { Transformable: Transformable2, SeriesNodePickMode: SeriesNodePickMode4, createDatumId: createDatumId4, getShapeStyle: getShapeStyle3, getLabelStyles: getLabelStyles4, Rect: Rect2, BBox: BBox6 } = module_support_exports;
|
|
var SankeySeries = class extends FlowProportionSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
pickModes: [SeriesNodePickMode4.NEAREST_NODE, SeriesNodePickMode4.EXACT_SHAPE_MATCH]
|
|
});
|
|
this.properties = new SankeySeriesProperties();
|
|
}
|
|
isLabelEnabled() {
|
|
return (this.properties.labelKey != null || this.nodes == null) && this.properties.label.enabled;
|
|
}
|
|
linkFactory() {
|
|
return new SankeyLink();
|
|
}
|
|
nodeFactory() {
|
|
return new Rect2();
|
|
}
|
|
createNodeData() {
|
|
const seriesRectWidth = this._nodeDataDependencies?.seriesRectWidth ?? 0;
|
|
const nodeWidth = this.properties.node.width;
|
|
const {
|
|
nodeGraph: baseNodeGraph,
|
|
links,
|
|
maxPathLength
|
|
} = this.getNodeGraph(this.createNode.bind(this, nodeWidth), this.createLink, {
|
|
includeCircularReferences: false
|
|
});
|
|
const nodeGraph = baseNodeGraph;
|
|
if (nodeGraph.size === 0)
|
|
return;
|
|
const columns = this.initialiseColumns(maxPathLength);
|
|
this.assignNodesToColumns(nodeGraph, columns, maxPathLength);
|
|
const measurer3 = cachedTextMeasurer(this.properties.label);
|
|
const { columnLabelInsetBefore, columnLabelInsetAfter } = this.getColumnLabelInsets(
|
|
columns,
|
|
measurer3,
|
|
maxPathLength
|
|
);
|
|
const columnWidth = (seriesRectWidth - nodeWidth - columnLabelInsetBefore - columnLabelInsetAfter) / (maxPathLength - 1);
|
|
this.positionNodesInColumnsX(columns, columnWidth, columnLabelInsetBefore);
|
|
this.createGhostNodesAndColumnDiffs(nodeGraph, columns);
|
|
this.weightNodes(columns);
|
|
const minSize = 1;
|
|
const { sizeScale, nodeSpacing } = this.getScaleAndSpacing(columns, minSize);
|
|
if (sizeScale < 0) {
|
|
logger_exports.warnOnce(
|
|
"There was insufficient space to display the Sankey Series. Reduce [node.spacing], [node.minSpacing], or provide a larger container."
|
|
);
|
|
return;
|
|
}
|
|
this.positionNodesInColumnsY(columns, minSize, sizeScale, nodeSpacing);
|
|
this.sortAndPositionLinks(nodeGraph, sizeScale);
|
|
const nodeData = [];
|
|
const labelData = [];
|
|
this.createNodesNodeData(nodeData, nodeGraph, columns, columnWidth, measurer3, labelData);
|
|
this.createLinksNodeData(nodeData, links, minSize, sizeScale);
|
|
return {
|
|
itemId: this.id,
|
|
nodeData,
|
|
labelData
|
|
};
|
|
}
|
|
createNode(nodeWidth, node) {
|
|
return {
|
|
...node,
|
|
x: Number.NaN,
|
|
y: Number.NaN,
|
|
width: nodeWidth,
|
|
height: Number.NaN
|
|
};
|
|
}
|
|
createLink(link) {
|
|
return {
|
|
...link,
|
|
x1: Number.NaN,
|
|
x2: Number.NaN,
|
|
y1: Number.NaN,
|
|
y2: Number.NaN,
|
|
height: Number.NaN,
|
|
elbows: []
|
|
};
|
|
}
|
|
initialiseColumns(maxPathLength) {
|
|
const columns = [];
|
|
for (let index = 0; index < maxPathLength; index += 1) {
|
|
columns.push({ index, size: 0, nodes: [], x: 0 });
|
|
}
|
|
return columns;
|
|
}
|
|
assignNodesToColumns(nodeGraph, columns, maxPathLength) {
|
|
const { fromKey, toKey, sizeKey, labelKey } = this.properties;
|
|
for (const graphNode of nodeGraph.values()) {
|
|
const { datum: node, linksBefore, linksAfter } = graphNode;
|
|
const size = Math.max(
|
|
linksBefore.reduce((acc, { link }) => acc + link.size, 0),
|
|
linksAfter.reduce((acc, { link }) => acc + link.size, 0)
|
|
);
|
|
if (linksBefore.length === 0 && linksAfter.length === 0 || size === 0) {
|
|
graphNode.columnIndex = -1;
|
|
continue;
|
|
}
|
|
const column = this.getNodeColumn(columns, graphNode, maxPathLength);
|
|
node.size = size;
|
|
const { label } = this.properties;
|
|
const labelText = label.enabled ? this.getLabelText(
|
|
node.label,
|
|
node.datum,
|
|
labelKey,
|
|
"label",
|
|
[],
|
|
this.properties.label,
|
|
{ datum: node.datum, value: node.label, fromKey, toKey, sizeKey, size }
|
|
) : void 0;
|
|
node.label = toPlainText(labelText);
|
|
column.nodes.push(graphNode);
|
|
column.size += size;
|
|
graphNode.columnIndex = column.index;
|
|
}
|
|
}
|
|
getNodeColumn(columns, graphNode, maxPathLength) {
|
|
const {
|
|
node: { alignment }
|
|
} = this.properties;
|
|
const { linksBefore, linksAfter, maxPathLengthBefore, maxPathLengthAfter } = graphNode;
|
|
let column;
|
|
switch (alignment) {
|
|
case "left":
|
|
column = columns[maxPathLengthBefore];
|
|
break;
|
|
case "right":
|
|
column = columns[maxPathLength - 1 - maxPathLengthAfter];
|
|
break;
|
|
case "center": {
|
|
if (linksBefore.length !== 0) {
|
|
column = columns[maxPathLengthBefore];
|
|
} else if (linksAfter.length === 0) {
|
|
column = columns[0];
|
|
} else {
|
|
const columnIndex = linksAfter.reduce((acc, link) => Math.min(acc, link.node.maxPathLengthBefore), maxPathLength) - 1;
|
|
column = columns[columnIndex];
|
|
}
|
|
break;
|
|
}
|
|
case "justify": {
|
|
column = linksAfter.length === 0 ? columns[maxPathLength - 1] : columns[maxPathLengthBefore];
|
|
break;
|
|
}
|
|
}
|
|
return column;
|
|
}
|
|
getColumnLabelInsets(columns, measurer3, maxPathLength) {
|
|
const {
|
|
label: { spacing: labelSpacing, placement: labelPlacement, edgePlacement: edgeLabelPlacement },
|
|
node: { width: nodeWidth }
|
|
} = this.properties;
|
|
const seriesRectWidth = this._nodeDataDependencies?.seriesRectWidth ?? 0;
|
|
let columnLabelInsetBefore = 0;
|
|
let columnLabelInsetAfter = 0;
|
|
if (this.isLabelEnabled() && (edgeLabelPlacement === "outside" || edgeLabelPlacement == null)) {
|
|
const reduceLabelWidthFn = (acc, n) => {
|
|
const node = n;
|
|
if (node.datum.label == null || node.datum.label === "")
|
|
return acc;
|
|
let maxWidth = (seriesRectWidth - nodeWidth) / (maxPathLength - 1) - labelSpacing;
|
|
if (labelPlacement === "center" && edgeLabelPlacement == null)
|
|
maxWidth /= 2;
|
|
const text2 = wrapText(node.datum.label, {
|
|
maxWidth,
|
|
maxHeight: node.datum.height,
|
|
font: this.properties.label,
|
|
textWrap: "never"
|
|
});
|
|
let { width: width2 } = measurer3.measureLines(text2);
|
|
if (labelPlacement === "center" && edgeLabelPlacement == null)
|
|
width2 /= 2;
|
|
return Math.max(acc, width2);
|
|
};
|
|
if (labelPlacement !== "right" || edgeLabelPlacement === "outside") {
|
|
columnLabelInsetBefore = nodeWidth + columns[0].nodes.reduce(reduceLabelWidthFn, 0);
|
|
}
|
|
if (labelPlacement !== "left" || edgeLabelPlacement === "outside") {
|
|
columnLabelInsetAfter = nodeWidth + columns.at(-1).nodes.reduce(reduceLabelWidthFn, 0);
|
|
}
|
|
}
|
|
return { columnLabelInsetBefore, columnLabelInsetAfter };
|
|
}
|
|
positionNodesInColumnsX(columns, columnWidth, columnLabelInsetBefore) {
|
|
for (let index = 0; index < columns.length; index++) {
|
|
const column = columns[index];
|
|
column.x = columnLabelInsetBefore + index * columnWidth;
|
|
for (const graphNode of column.nodes) {
|
|
graphNode.datum.x = column.x;
|
|
}
|
|
}
|
|
}
|
|
createGhostNodesAndColumnDiffs(nodeGraph, columns) {
|
|
for (const graphNode of nodeGraph.values()) {
|
|
graphNode.weight = 0;
|
|
let closestColumnDiff = Infinity;
|
|
for (const link of graphNode.linksAfter) {
|
|
const node = link.node;
|
|
closestColumnDiff = Math.min(closestColumnDiff, node.columnIndex - graphNode.columnIndex);
|
|
}
|
|
if (closestColumnDiff === Infinity) {
|
|
for (const link of graphNode.linksBefore) {
|
|
const node = link.node;
|
|
closestColumnDiff = Math.min(closestColumnDiff, graphNode.columnIndex - node.columnIndex);
|
|
}
|
|
}
|
|
graphNode.closestColumnDiff = closestColumnDiff;
|
|
this.createNodeGhostNodes(graphNode, columns, closestColumnDiff);
|
|
}
|
|
}
|
|
createNodeGhostNodes(graphNode, columns, closestColumnDiff) {
|
|
for (const link of graphNode.linksAfter) {
|
|
const node = link.node;
|
|
if (node.columnIndex <= graphNode.columnIndex)
|
|
continue;
|
|
for (let i = node.columnIndex - 1; i > graphNode.columnIndex; i--) {
|
|
const size = link.link.size;
|
|
const ghostNode = {
|
|
ghost: true,
|
|
datum: { ...graphNode.datum, size, y: 0, height: 0 },
|
|
weight: 0,
|
|
linksBefore: [{ node: { columnIndex: i - 1, datum: { size } } }],
|
|
linksAfter: [{ node: { columnIndex: i + 1, datum: { size } } }],
|
|
link: link.link,
|
|
columnIndex: graphNode.columnIndex,
|
|
size: graphNode.datum.size,
|
|
closestColumnDiff,
|
|
fromNode: { y: node.datum.y },
|
|
toNode: { y: 0 }
|
|
};
|
|
columns[i].size += size;
|
|
columns[i].nodes.push(ghostNode);
|
|
}
|
|
}
|
|
}
|
|
weightNodes(columns) {
|
|
const { properties } = this;
|
|
if (properties.node.sort === "data")
|
|
return;
|
|
if (properties.node.sort !== "auto") {
|
|
for (const column of columns) {
|
|
column.nodes.sort((a, b) => this.sortNodes(a, b));
|
|
}
|
|
return;
|
|
}
|
|
const sortedColumns = columns.toSorted((a, b) => {
|
|
const aMax = a.nodes.reduce((acc, n) => Math.max(acc, n.datum.size), 0);
|
|
const bMax = b.nodes.reduce((acc, n) => Math.max(acc, n.datum.size), 0);
|
|
return bMax - aMax;
|
|
});
|
|
const columnWeights = {};
|
|
for (let i = 0; i < sortedColumns.length; i++) {
|
|
columnWeights[sortedColumns[i].index] = Math.pow(10, sortedColumns.length - i - 1);
|
|
}
|
|
for (const column of columns) {
|
|
for (const node of column.nodes) {
|
|
if ("ghost" in node && node.ghost) {
|
|
node.weight = node.size / column.size * columnWeights[column.index];
|
|
continue;
|
|
}
|
|
node.weight = node.datum.size / column.size * columnWeights[column.index];
|
|
}
|
|
column.nodes.sort((a, b) => a.weight - b.weight);
|
|
}
|
|
for (const column of columns) {
|
|
for (const node of column.nodes) {
|
|
if ("ghost" in node && node.ghost) {
|
|
continue;
|
|
}
|
|
node.weight += node.linksBefore.reduce((acc, before) => {
|
|
if (before.node.columnIndex !== column.index - 1)
|
|
return acc;
|
|
const weight = columns[before.node.columnIndex].nodes.indexOf(before.node) * columnWeights[before.node.columnIndex];
|
|
return Math.max(acc, weight);
|
|
}, 0);
|
|
node.weight += node.linksAfter.reduce((acc, after) => {
|
|
if (after.node.columnIndex !== column.index + 1)
|
|
return acc;
|
|
const weight = columns[after.node.columnIndex].nodes.indexOf(after.node) * columnWeights[after.node.columnIndex];
|
|
return Math.max(acc, weight);
|
|
}, 0);
|
|
}
|
|
column.nodes.sort((a, b) => this.sortNodes(a, b));
|
|
}
|
|
}
|
|
getScaleAndSpacing(columns, minSize) {
|
|
const seriesRectHeight = this._nodeDataDependencies?.seriesRectHeight ?? 0;
|
|
const getSizeScale = (spacing) => {
|
|
return columns.reduce((acc, { size, nodes }) => {
|
|
const spacingAccomodation = seriesRectHeight - nodes.length * minSize;
|
|
const spacingOccupation = (nodes.length - 1) * spacing / spacingAccomodation;
|
|
const columnSizeScale = (1 - spacingOccupation) / size;
|
|
return Math.min(acc, columnSizeScale);
|
|
}, Infinity);
|
|
};
|
|
let nodeSpacing = this.properties.node.spacing;
|
|
let sizeScale = getSizeScale(nodeSpacing);
|
|
while (sizeScale < 0 && nodeSpacing > this.properties.node.minSpacing) {
|
|
nodeSpacing -= 1;
|
|
sizeScale = getSizeScale(nodeSpacing);
|
|
}
|
|
return { nodeSpacing, sizeScale };
|
|
}
|
|
positionNodesInColumnsY(columns, minSize, sizeScale, nodeSpacing) {
|
|
const seriesRectHeight = this._nodeDataDependencies?.seriesRectHeight ?? 0;
|
|
for (const column of columns) {
|
|
let columnNodesHeight = 0;
|
|
for (const node of column.nodes) {
|
|
const height2 = seriesRectHeight * node.datum.size * sizeScale;
|
|
node.datum.height = Math.max(minSize, height2);
|
|
columnNodesHeight += height2;
|
|
}
|
|
const spacingOccupation = nodeSpacing * (column.nodes.length - 1);
|
|
let y = 0;
|
|
if (this.properties.node.verticalAlignment === "bottom") {
|
|
y = seriesRectHeight - columnNodesHeight - spacingOccupation;
|
|
} else if (this.properties.node.verticalAlignment === "center") {
|
|
y = (seriesRectHeight - columnNodesHeight - spacingOccupation) / 2;
|
|
}
|
|
for (const node of column.nodes) {
|
|
node.datum.y = y;
|
|
y += seriesRectHeight * node.datum.size * sizeScale + nodeSpacing;
|
|
if ("ghost" in node && node.ghost) {
|
|
node.link.elbows.push({ x: column.x, y: node.datum.y });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sortAndPositionLinks(nodeGraph, sizeScale) {
|
|
const seriesRectHeight = this._nodeDataDependencies?.seriesRectHeight ?? 0;
|
|
for (const { datum, linksBefore, linksAfter } of nodeGraph.values()) {
|
|
let y2 = datum.y;
|
|
linksBefore.sort(
|
|
(a, b) => this.sortNodes(a.node, b.node)
|
|
);
|
|
for (const { link } of linksBefore) {
|
|
link.y2 = y2;
|
|
y2 += link.size * seriesRectHeight * sizeScale;
|
|
}
|
|
let y1 = datum.y;
|
|
linksAfter.sort(
|
|
(a, b) => this.sortNodes(a.node, b.node, {
|
|
invertColumnSort: true
|
|
})
|
|
);
|
|
for (const { link } of linksAfter) {
|
|
link.y1 = y1;
|
|
y1 += link.size * seriesRectHeight * sizeScale;
|
|
}
|
|
}
|
|
}
|
|
createNodesNodeData(nodeData, nodeGraph, columns, columnWidth, measurer3, labelData) {
|
|
for (const [index, column] of columns.entries()) {
|
|
const leading = index === 0;
|
|
const trailing = index === columns.length - 1;
|
|
let bottom = -Infinity;
|
|
column.nodes.sort((a, b) => a.datum.y - b.datum.y);
|
|
for (const n of column.nodes) {
|
|
if ("ghost" in n && n.ghost)
|
|
continue;
|
|
const { datum: node } = n;
|
|
node.midPoint = {
|
|
x: node.x + node.width / 2,
|
|
y: node.y + node.height / 2
|
|
};
|
|
nodeData.push(node);
|
|
bottom = this.createNodeLabelData(
|
|
nodeGraph,
|
|
columnWidth,
|
|
measurer3,
|
|
labelData,
|
|
node,
|
|
leading,
|
|
trailing,
|
|
bottom
|
|
);
|
|
}
|
|
}
|
|
}
|
|
createNodeLabelData(nodeGraph, columnWidth, measurer3, labelData, node, leading, trailing, bottom) {
|
|
if (node.label == null)
|
|
return bottom;
|
|
const {
|
|
label: { spacing: labelSpacing, edgePlacement: edgeLabelPlacement, fontSize }
|
|
} = this.properties;
|
|
const seriesRectWidth = this._nodeDataDependencies?.seriesRectWidth ?? 0;
|
|
const y = node.y + node.height / 2;
|
|
let text2;
|
|
if (!leading && !trailing) {
|
|
const lineHeight = calcLineHeight(fontSize);
|
|
const y12 = y - lineHeight;
|
|
const y2 = y + lineHeight;
|
|
let maxX = seriesRectWidth;
|
|
for (const { datum } of nodeGraph.values()) {
|
|
const intersectsLabel = datum.x > node.x && Math.max(datum.y, y12) <= Math.min(datum.y + datum.height, y2);
|
|
if (intersectsLabel) {
|
|
maxX = Math.min(maxX, datum.x - labelSpacing);
|
|
}
|
|
}
|
|
const maxWidth = maxX - node.x - 2 * labelSpacing;
|
|
text2 = wrapText(node.label, {
|
|
maxWidth,
|
|
maxHeight: node.height,
|
|
font: this.properties.label,
|
|
textWrap: "never",
|
|
overflow: "hide"
|
|
});
|
|
}
|
|
if (text2 == null || text2 === "") {
|
|
const labelInset = edgeLabelPlacement == null && (leading || trailing) ? labelSpacing : labelSpacing * 2;
|
|
text2 = wrapText(node.label, {
|
|
maxWidth: columnWidth - labelInset,
|
|
maxHeight: node.height,
|
|
font: this.properties.label,
|
|
textWrap: "never"
|
|
});
|
|
}
|
|
if (text2 === "")
|
|
return bottom;
|
|
const { height: height2 } = measurer3.measureLines(text2);
|
|
const y0 = y - height2 / 2;
|
|
const y1 = y + height2 / 2;
|
|
const { x, textAlign } = this.getNodeLabelPlacement(node, leading, trailing);
|
|
if (y0 >= bottom) {
|
|
labelData.push({
|
|
x,
|
|
y,
|
|
textAlign,
|
|
text: text2,
|
|
size: node.size,
|
|
nodeDatum: node,
|
|
datumIndex: node.datumIndex
|
|
});
|
|
bottom = y1;
|
|
}
|
|
return bottom;
|
|
}
|
|
getNodeLabelPlacement(node, leading, trailing) {
|
|
const {
|
|
label: { spacing: labelSpacing, placement: labelPlacement, edgePlacement: edgeLabelPlacement }
|
|
} = this.properties;
|
|
let x = node.x + node.width + labelSpacing;
|
|
let textAlign = "left";
|
|
let placement = labelPlacement;
|
|
if (leading && edgeLabelPlacement == null && labelPlacement == null) {
|
|
placement = "left";
|
|
}
|
|
if (edgeLabelPlacement === "outside") {
|
|
if (leading)
|
|
placement = "left";
|
|
if (trailing)
|
|
placement = "right";
|
|
} else if (edgeLabelPlacement === "inside") {
|
|
if (leading)
|
|
placement = "right";
|
|
if (trailing)
|
|
placement = "left";
|
|
}
|
|
if (placement === "left") {
|
|
x = node.x - labelSpacing;
|
|
textAlign = "right";
|
|
} else if (placement === "center") {
|
|
x = node.x + node.width / 2;
|
|
textAlign = "center";
|
|
}
|
|
return { x, textAlign };
|
|
}
|
|
createLinksNodeData(nodeData, links, minSize, sizeScale) {
|
|
const seriesRectHeight = this._nodeDataDependencies?.seriesRectHeight ?? 0;
|
|
const nodeWidth = this.properties.node.width;
|
|
for (const link of links) {
|
|
const { fromNode, toNode, size } = link;
|
|
link.height = Math.max(minSize, seriesRectHeight * size * sizeScale);
|
|
link.x1 = fromNode.x + nodeWidth;
|
|
link.x2 = toNode.x;
|
|
link.midPoint = {
|
|
x: (link.x1 + link.x2) / 2,
|
|
y: (link.y1 + link.y2) / 2 + link.height / 2
|
|
};
|
|
nodeData.push(link);
|
|
}
|
|
}
|
|
sortNodes(a, b, opts) {
|
|
const { properties } = this;
|
|
if (properties.node.sort === "ascending") {
|
|
return (a.datum.label ?? "").localeCompare(b.datum.label ?? "");
|
|
} else if (properties.node.sort === "descending") {
|
|
return (b.datum.label ?? "").localeCompare(a.datum.label ?? "");
|
|
} else if (properties.node.sort === "data") {
|
|
return 0;
|
|
}
|
|
if (a.columnIndex < b.columnIndex)
|
|
return opts?.invertColumnSort ? 1 : -1;
|
|
if (a.columnIndex > b.columnIndex)
|
|
return opts?.invertColumnSort ? -1 : 1;
|
|
if (a.weight === b.weight) {
|
|
return a.datum.size - b.datum.size;
|
|
}
|
|
if (a.closestColumnDiff < b.closestColumnDiff)
|
|
return 1;
|
|
if (a.closestColumnDiff > b.closestColumnDiff)
|
|
return -1;
|
|
return a.weight - b.weight;
|
|
}
|
|
updateLabelSelection(opts) {
|
|
const labels = this.isLabelEnabled() ? opts.labelData : [];
|
|
return opts.labelSelection.update(labels);
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const activeHighlightDatum = this.getHighlightedDatum();
|
|
opts.labelSelection.each((label, datum) => {
|
|
const { x, y, textAlign, text: text2, datumIndex, nodeDatum } = datum;
|
|
const params = {
|
|
fromKey: this.properties.fromKey,
|
|
size: datum.size,
|
|
sizeKey: this.properties.sizeKey,
|
|
toKey: this.properties.toKey
|
|
};
|
|
const isHighlight = this.isLabelHighlighted(nodeDatum, activeHighlightDatum);
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex);
|
|
const style2 = getLabelStyles4(
|
|
this,
|
|
void 0,
|
|
params,
|
|
this.properties.label,
|
|
isHighlight,
|
|
activeHighlightDatum
|
|
);
|
|
const { color: fill, fontStyle, fontWeight: fontWeight2, fontSize, fontFamily } = style2;
|
|
label.visible = true;
|
|
label.x = x;
|
|
label.y = y;
|
|
label.text = text2;
|
|
label.fill = fill;
|
|
label.fontStyle = fontStyle;
|
|
label.fontWeight = fontWeight2;
|
|
label.fontSize = fontSize;
|
|
label.fontFamily = fontFamily;
|
|
label.textAlign = textAlign;
|
|
label.textBaseline = "middle";
|
|
const opacity = highlightStyle.opacity ?? 1;
|
|
label.opacity = opacity;
|
|
label.fillOpacity = opacity;
|
|
label.setBoxing(style2);
|
|
});
|
|
}
|
|
updateNodeSelection(opts) {
|
|
return opts.datumSelection.update(opts.nodeData, void 0, (datum) => createDatumId4(datum.type, datum.id));
|
|
}
|
|
getNodeStyle(nodeDatum, fromNodeDatumIndex, isHighlight) {
|
|
const { properties } = this;
|
|
const {
|
|
fills,
|
|
strokes,
|
|
defaultColorRange,
|
|
defaultPatternFills,
|
|
fillGradientDefaults: fillGradientDefaults3,
|
|
fillPatternDefaults: fillPatternDefaults3,
|
|
fillImageDefaults: fillImageDefaults3
|
|
} = properties;
|
|
const { itemStyler } = properties.node;
|
|
const defaultColorStops = defaultColorRange[fromNodeDatumIndex % defaultColorRange.length].map((color2) => ({
|
|
color: color2
|
|
}));
|
|
const defaultPatternFill = defaultPatternFills[fromNodeDatumIndex % defaultPatternFills.length];
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, nodeDatum.datumIndex);
|
|
const baseStyle = mergeDefaults(highlightStyle, properties.getStyle(false, fills, strokes, fromNodeDatumIndex));
|
|
const hasNodeFill = properties.node.fill != null;
|
|
let style2 = getShapeStyle3(
|
|
baseStyle,
|
|
hasNodeFill ? fillGradientDefaults3 : { ...fillGradientDefaults3.toJson(), colorStops: defaultColorStops },
|
|
hasNodeFill ? fillPatternDefaults3 : { ...fillPatternDefaults3.toJson(), fill: defaultPatternFill, stroke: defaultPatternFill },
|
|
fillImageDefaults3
|
|
);
|
|
if (itemStyler != null && nodeDatum.datumIndex != null) {
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId4(nodeDatum.datumIndex.index, "node", isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const params = this.makeItemStylerParams(nodeDatum, isHighlight, style2);
|
|
return this.callWithContext(itemStyler, params);
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(
|
|
overrides,
|
|
style2,
|
|
{ ...fillGradientDefaults3.toJson(), colorStops: defaultColorStops },
|
|
{ ...fillPatternDefaults3.toJson(), fill: defaultPatternFill, stroke: defaultPatternFill },
|
|
fillImageDefaults3
|
|
);
|
|
}
|
|
}
|
|
style2.opacity = 1;
|
|
return style2;
|
|
}
|
|
makeItemStylerParams({ datum, datumIndex, size = 0, label }, isHighlight, style2) {
|
|
const { id: seriesId } = this;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightState = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
seriesId,
|
|
datum,
|
|
highlightState,
|
|
...style2,
|
|
size,
|
|
label,
|
|
fill
|
|
};
|
|
}
|
|
updateNodeNodes(opts) {
|
|
const { datumSelection, isHighlight } = opts;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
datumSelection.each((rect2, datum) => {
|
|
const { datumIndex } = datum;
|
|
const style2 = this.getNodeStyle(datum, datumIndex.index, isHighlight);
|
|
rect2.x = datum.x;
|
|
rect2.y = datum.y;
|
|
rect2.width = Math.max(datum.width, 0);
|
|
rect2.height = Math.max(datum.height, 0);
|
|
rect2.setStyleProperties(style2, fillBBox);
|
|
});
|
|
}
|
|
getShapeFillBBox() {
|
|
const width2 = this._nodeDataDependencies?.seriesRectWidth ?? 0;
|
|
const height2 = this._nodeDataDependencies?.seriesRectHeight ?? 0;
|
|
const bbox = new BBox6(0, 0, width2, height2);
|
|
return { series: bbox, axis: bbox };
|
|
}
|
|
updateLinkSelection(opts) {
|
|
return opts.datumSelection.update(
|
|
opts.nodeData,
|
|
void 0,
|
|
(datum) => createDatumId4(datum.type, datum.index, datum.fromNode.id, datum.toNode.id)
|
|
);
|
|
}
|
|
getLinkStyle({ datumIndex, datum }, fromNodeDatumIndex, isHighlight) {
|
|
const { id: seriesId, properties } = this;
|
|
const {
|
|
fills,
|
|
strokes,
|
|
defaultColorRange,
|
|
defaultPatternFills,
|
|
fillGradientDefaults: fillGradientDefaults3,
|
|
fillPatternDefaults: fillPatternDefaults3,
|
|
fillImageDefaults: fillImageDefaults3
|
|
} = properties;
|
|
const { itemStyler } = properties.link;
|
|
const defaultColorStops = defaultColorRange[fromNodeDatumIndex.index % defaultColorRange.length].map(
|
|
(color2) => ({
|
|
color: color2
|
|
})
|
|
);
|
|
const defaultPatternFill = defaultPatternFills[fromNodeDatumIndex.index % defaultPatternFills.length];
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex);
|
|
const baseStyle = mergeDefaults(
|
|
highlightStyle,
|
|
properties.getStyle(true, fills, strokes, fromNodeDatumIndex.index)
|
|
);
|
|
const hasLinkFill = properties.link.fill != null;
|
|
let style2 = getShapeStyle3(
|
|
baseStyle,
|
|
hasLinkFill ? fillGradientDefaults3 : { ...fillGradientDefaults3.toJson(), colorStops: defaultColorStops },
|
|
hasLinkFill ? fillPatternDefaults3 : { ...fillPatternDefaults3.toJson(), fill: defaultPatternFill, stroke: defaultPatternFill },
|
|
fillImageDefaults3
|
|
);
|
|
if (itemStyler != null && datumIndex != null) {
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId4(datumIndex.index, "link", isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightState = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
return this.callWithContext(itemStyler, {
|
|
seriesId,
|
|
datum,
|
|
highlightState,
|
|
...style2
|
|
});
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(
|
|
overrides,
|
|
style2,
|
|
{ ...fillGradientDefaults3.toJson(), colorStops: defaultColorStops },
|
|
{ ...fillPatternDefaults3.toJson(), fill: defaultPatternFill, stroke: defaultPatternFill },
|
|
fillImageDefaults3
|
|
);
|
|
}
|
|
}
|
|
style2.opacity = 1;
|
|
return style2;
|
|
}
|
|
updateLinkNodes(opts) {
|
|
const { datumSelection, isHighlight } = opts;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
datumSelection.each((link, datum) => {
|
|
const fromNodeDatumIndex = datum.fromNode.datumIndex;
|
|
const style2 = this.getLinkStyle(datum, fromNodeDatumIndex, isHighlight);
|
|
link.x1 = datum.x1;
|
|
link.y1 = datum.y1;
|
|
link.x2 = datum.x2;
|
|
link.y2 = datum.y2;
|
|
link.height = datum.height;
|
|
link.elbows = datum.elbows;
|
|
link.setStyleProperties(style2, fillBBox);
|
|
link.inset = link.strokeWidth / 2;
|
|
});
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const {
|
|
id: seriesId,
|
|
linksProcessedData,
|
|
nodesProcessedData,
|
|
properties,
|
|
ctx: { formatManager }
|
|
} = this;
|
|
const { fromKey, toKey, sizeKey, sizeName, tooltip } = properties;
|
|
const seriesDatum = this.contextNodeData?.nodeData.find(
|
|
(d) => d.datumIndex.type === datumIndex.type && d.datumIndex.index === datumIndex.index
|
|
);
|
|
if (seriesDatum == null)
|
|
return;
|
|
const nodeIndex = seriesDatum.type === 0 /* Link */ ? seriesDatum.fromNode.index : seriesDatum.index;
|
|
const title = seriesDatum.type === 0 /* Link */ ? `${seriesDatum.fromNode.label} - ${seriesDatum.toNode.label}` : seriesDatum.label;
|
|
const datum = datumIndex.type === 0 /* Link */ ? linksProcessedData?.dataSources.get(this.id)?.data[datumIndex.index] : nodesProcessedData?.dataSources.get(this.id)?.data[datumIndex.index];
|
|
const size = seriesDatum.size;
|
|
let format;
|
|
if (seriesDatum.type === 0 /* Link */) {
|
|
const fromNodeDatumIndex = seriesDatum.fromNode.datumIndex;
|
|
format = this.getLinkStyle({ datumIndex, datum }, fromNodeDatumIndex, false);
|
|
} else {
|
|
format = this.getNodeStyle({ datumIndex, datum }, datumIndex.index, false);
|
|
}
|
|
const data = [];
|
|
if (sizeKey != null) {
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value: size,
|
|
datum,
|
|
seriesId,
|
|
legendItemName: void 0,
|
|
key: sizeKey,
|
|
source: "tooltip",
|
|
property: "size",
|
|
domain: [],
|
|
boundSeries: this.getFormatterContext("size"),
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
});
|
|
data.push({ label: sizeName, fallbackLabel: sizeKey, value: content ?? String(size) });
|
|
}
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
title,
|
|
symbol: this.legendItemSymbol(seriesDatum.type, nodeIndex, format),
|
|
data
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title,
|
|
fromKey,
|
|
toKey,
|
|
sizeKey,
|
|
sizeName,
|
|
size,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
computeFocusBounds(node) {
|
|
if (node instanceof Rect2) {
|
|
const { x, y, width: width2, height: height2 } = node;
|
|
const bbox = new BBox6(x, y, width2, height2);
|
|
return Transformable2.toCanvas(this.contentGroup, bbox);
|
|
}
|
|
return node;
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.node.itemStyler != null || this.properties.link.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
SankeySeries.className = "SankeySeries";
|
|
SankeySeries.type = "sankey";
|
|
|
|
// packages/ag-charts-enterprise/src/series/sankey/sankeySeriesOptionsDef.ts
|
|
var { sankeySeriesThemeableOptionsDef: sankeySeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var sankeySeriesOptionsDef = {
|
|
...sankeySeriesThemeableOptionsDef2,
|
|
...commonSeriesOptionsDefs,
|
|
type: required(constant("sankey")),
|
|
fromKey: required(string),
|
|
toKey: required(string),
|
|
sizeKey: string,
|
|
sizeName: string
|
|
};
|
|
sankeySeriesOptionsDef.fillGradientDefaults = undocumented(fillGradientDefaults);
|
|
sankeySeriesOptionsDef.fillPatternDefaults = undocumented(fillPatternDefaults);
|
|
sankeySeriesOptionsDef.fillImageDefaults = undocumented(fillImageDefaults);
|
|
sankeySeriesOptionsDef.defaultColorRange = undocumented(arrayOf(arrayOf(color)));
|
|
sankeySeriesOptionsDef.defaultPatternFills = undocumented(arrayOf(color));
|
|
|
|
// packages/ag-charts-enterprise/src/series/sankey/sankeyModule.ts
|
|
var SankeySeriesModule = {
|
|
type: "series",
|
|
name: "sankey",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
solo: true,
|
|
version: VERSION,
|
|
dependencies: [StandaloneChartModule],
|
|
options: sankeySeriesOptionsDef,
|
|
themeTemplate: {
|
|
seriesArea: {
|
|
padding: {
|
|
top: 10,
|
|
bottom: 10
|
|
}
|
|
},
|
|
series: {
|
|
fills: { $palette: "fills" },
|
|
strokes: { $palette: "strokes" },
|
|
fillGradientDefaults: FILL_GRADIENT_LINEAR_DEFAULTS,
|
|
fillPatternDefaults: FILL_PATTERN_DEFAULTS,
|
|
fillImageDefaults: FILL_IMAGE_DEFAULTS,
|
|
defaultColorRange: { $palette: "gradients" },
|
|
defaultPatternFills: SAFE_FILLS_OPERATION,
|
|
highlight: {
|
|
unhighlightedItem: {
|
|
opacity: 0.5
|
|
}
|
|
},
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" },
|
|
spacing: 10
|
|
},
|
|
node: {
|
|
spacing: { $if: [{ $greaterThan: [{ $path: "./minSpacing" }, 20] }, { $path: "./minSpacing" }, 20] },
|
|
minSpacing: 0,
|
|
width: 10,
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, 0] }
|
|
},
|
|
link: {
|
|
fillOpacity: 0.5,
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, 0] }
|
|
}
|
|
},
|
|
legend: {
|
|
enabled: false,
|
|
toggleSeries: false
|
|
}
|
|
},
|
|
create: (ctx) => new SankeySeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/util/labelFormatter.ts
|
|
function generateLabelSecondaryLabelFontSizeCandidates(label, secondaryLabel) {
|
|
const { fontSize: labelFontSize, minimumFontSize: labelMinimumFontSize = labelFontSize } = label;
|
|
const {
|
|
fontSize: secondaryLabelFontSize,
|
|
minimumFontSize: secondaryLabelMinimumFontSize = secondaryLabelFontSize
|
|
} = secondaryLabel;
|
|
const labelTracks = labelFontSize - labelMinimumFontSize;
|
|
const secondaryLabelTracks = secondaryLabelFontSize - secondaryLabelMinimumFontSize;
|
|
let currentLabelFontSize = label.fontSize;
|
|
let currentSecondaryLabelFontSize = secondaryLabel.fontSize;
|
|
const out = [{ labelFontSize, secondaryLabelFontSize }];
|
|
while (currentLabelFontSize > labelMinimumFontSize || currentSecondaryLabelFontSize > secondaryLabelMinimumFontSize) {
|
|
const labelProgress = labelTracks > 0 ? (currentLabelFontSize - labelMinimumFontSize) / labelTracks : -1;
|
|
const secondaryLabelProgress = secondaryLabelTracks > 0 ? (currentSecondaryLabelFontSize - secondaryLabelMinimumFontSize) / secondaryLabelTracks : -1;
|
|
if (labelProgress > secondaryLabelProgress) {
|
|
currentLabelFontSize--;
|
|
} else {
|
|
currentSecondaryLabelFontSize--;
|
|
}
|
|
out.push({
|
|
labelFontSize: currentLabelFontSize,
|
|
secondaryLabelFontSize: currentSecondaryLabelFontSize
|
|
});
|
|
}
|
|
out.reverse();
|
|
return out;
|
|
}
|
|
function formatStackedLabels(labelValue, labelProps, secondaryLabelValue, secondaryLabelProps, { padding: padding2 }, sizeFittingHeight) {
|
|
const { spacing = 0 } = labelProps;
|
|
const widthAdjust = 2 * padding2;
|
|
const heightAdjust = 2 * padding2 + spacing;
|
|
const minimumHeight = (labelProps.minimumFontSize ?? labelProps.fontSize) + (secondaryLabelProps.minimumFontSize ?? secondaryLabelProps.fontSize);
|
|
if (minimumHeight > sizeFittingHeight(minimumHeight + heightAdjust, false).height - heightAdjust)
|
|
return;
|
|
const fontSizeCandidates = generateLabelSecondaryLabelFontSizeCandidates(labelProps, secondaryLabelProps);
|
|
const labelTextSizeProps = {
|
|
fontFamily: labelProps.fontFamily,
|
|
fontStyle: labelProps.fontStyle,
|
|
fontWeight: labelProps.fontWeight
|
|
};
|
|
const secondaryLabelTextSizeProps = {
|
|
fontFamily: secondaryLabelProps.fontFamily,
|
|
fontStyle: secondaryLabelProps.fontStyle,
|
|
fontWeight: secondaryLabelProps.fontWeight
|
|
};
|
|
let label;
|
|
let secondaryLabel;
|
|
return findMaxValue(0, fontSizeCandidates.length - 1, (index) => {
|
|
const { labelFontSize, secondaryLabelFontSize } = fontSizeCandidates[index];
|
|
const allowTruncation = index === 0;
|
|
const labelFont = { ...labelTextSizeProps, fontSize: labelFontSize };
|
|
const secondaryLabelFont = { ...secondaryLabelTextSizeProps, fontSize: secondaryLabelFontSize };
|
|
const labelLineHeight = cachedTextMeasurer(labelFont).lineHeight();
|
|
const secondaryLabelLineHeight = cachedTextMeasurer(secondaryLabelFont).lineHeight();
|
|
const sizeFitting = sizeFittingHeight(
|
|
labelLineHeight + secondaryLabelLineHeight + heightAdjust,
|
|
allowTruncation
|
|
);
|
|
const availableWidth = sizeFitting.width - widthAdjust;
|
|
const availableHeight = sizeFitting.height - heightAdjust;
|
|
if (labelLineHeight + secondaryLabelLineHeight > availableHeight)
|
|
return;
|
|
if (label?.fontSize !== labelFontSize) {
|
|
label = wrapLabel(
|
|
labelProps,
|
|
labelValue,
|
|
availableWidth,
|
|
availableHeight,
|
|
labelFont,
|
|
labelProps.wrapping,
|
|
allowTruncation ? labelProps.overflowStrategy : "hide"
|
|
);
|
|
}
|
|
if (label == null || label.width > availableWidth || label.height > availableHeight)
|
|
return;
|
|
if (secondaryLabel?.fontSize !== secondaryLabelFontSize) {
|
|
secondaryLabel = wrapLabel(
|
|
secondaryLabelProps,
|
|
secondaryLabelValue,
|
|
availableWidth,
|
|
availableHeight,
|
|
secondaryLabelFont,
|
|
secondaryLabelProps.wrapping,
|
|
allowTruncation ? secondaryLabelProps.overflowStrategy : "hide"
|
|
);
|
|
}
|
|
if (secondaryLabel == null)
|
|
return;
|
|
const totalLabelHeight = label.height + secondaryLabel.height;
|
|
if (secondaryLabel.width > availableWidth || totalLabelHeight > availableHeight)
|
|
return;
|
|
return {
|
|
width: Math.max(label.width, secondaryLabel.width),
|
|
height: totalLabelHeight + spacing,
|
|
meta: sizeFitting.meta,
|
|
label,
|
|
secondaryLabel
|
|
};
|
|
});
|
|
}
|
|
function formatSingleLabel(value, props, { padding: padding2 }, sizeFittingHeight) {
|
|
const sizeAdjust = 2 * padding2;
|
|
const minimumFontSize = Math.min(props.minimumFontSize ?? props.fontSize, props.fontSize);
|
|
const textSizeProps = {
|
|
fontFamily: props.fontFamily,
|
|
fontStyle: props.fontStyle,
|
|
fontWeight: props.fontWeight
|
|
};
|
|
return findMaxValue(minimumFontSize, props.fontSize, (fontSize) => {
|
|
const currentFont = { ...textSizeProps, fontSize };
|
|
const measurer3 = cachedTextMeasurer(currentFont);
|
|
const allowTruncation = fontSize === minimumFontSize;
|
|
const lineHeight = props.lineHeight ?? measurer3.lineHeight();
|
|
const sizeFitting = sizeFittingHeight(lineHeight + sizeAdjust, allowTruncation);
|
|
const availableWidth = sizeFitting.width - sizeAdjust;
|
|
const availableHeight = sizeFitting.height - sizeAdjust;
|
|
if (lineHeight > availableHeight || availableWidth < 0)
|
|
return;
|
|
const lines = wrapLines(value, {
|
|
maxWidth: availableWidth,
|
|
maxHeight: availableHeight,
|
|
font: currentFont,
|
|
textWrap: props.wrapping,
|
|
overflow: (allowTruncation ? props.overflowStrategy : null) ?? "hide"
|
|
});
|
|
if (!lines.length)
|
|
return;
|
|
const { width: width2, height: height2 } = measurer3.measureLines(lines);
|
|
const text2 = lines.join("\n");
|
|
return [{ width: width2, height: height2, text: text2, fontSize, lineHeight }, sizeFitting.meta];
|
|
});
|
|
}
|
|
function hasInvalidFontSize(label) {
|
|
return label?.minimumFontSize != null && label?.fontSize != null && label?.minimumFontSize > label?.fontSize;
|
|
}
|
|
function formatLabels(baseLabelValue, labelProps, baseSecondaryLabelValue, secondaryLabelProps, layoutParams, sizeFittingHeight) {
|
|
const labelValue = labelProps.enabled ? baseLabelValue : void 0;
|
|
const secondaryLabelValue = secondaryLabelProps.enabled ? baseSecondaryLabelValue : void 0;
|
|
if (hasInvalidFontSize(labelProps) || hasInvalidFontSize(secondaryLabelProps)) {
|
|
logger_exports.warnOnce(`minimumFontSize should be set to a value less than or equal to the font size`);
|
|
}
|
|
let value;
|
|
if (labelValue != null && secondaryLabelValue != null) {
|
|
value = formatStackedLabels(
|
|
labelValue,
|
|
labelProps,
|
|
secondaryLabelValue,
|
|
secondaryLabelProps,
|
|
layoutParams,
|
|
sizeFittingHeight
|
|
);
|
|
}
|
|
let labelMeta;
|
|
if (value == null && labelValue != null) {
|
|
labelMeta = formatSingleLabel(labelValue, labelProps, layoutParams, sizeFittingHeight);
|
|
}
|
|
if (labelMeta != null) {
|
|
const [label, meta] = labelMeta;
|
|
value = {
|
|
width: label.width,
|
|
height: label.height,
|
|
meta,
|
|
label,
|
|
secondaryLabel: void 0
|
|
};
|
|
}
|
|
let secondaryLabelMeta;
|
|
if (value == null && labelValue == null && secondaryLabelValue != null) {
|
|
secondaryLabelMeta = formatSingleLabel(
|
|
secondaryLabelValue,
|
|
secondaryLabelProps,
|
|
layoutParams,
|
|
sizeFittingHeight
|
|
);
|
|
}
|
|
if (secondaryLabelMeta != null) {
|
|
const [secondaryLabel, meta] = secondaryLabelMeta;
|
|
value = {
|
|
width: secondaryLabel.width,
|
|
height: secondaryLabel.height,
|
|
meta,
|
|
label: void 0,
|
|
secondaryLabel
|
|
};
|
|
}
|
|
return value;
|
|
}
|
|
function wrapLabel(props, text2, maxWidth, maxHeight, font3, textWrap2, overflow) {
|
|
const lines = wrapLines(text2, { maxWidth, maxHeight, font: font3, textWrap: textWrap2, overflow });
|
|
if (!lines.length)
|
|
return;
|
|
const measurer3 = cachedTextMeasurer(font3);
|
|
const lineHeight = props.lineHeight ?? measurer3.lineHeight();
|
|
const { width: width2 } = measurer3.measureLines(lines);
|
|
return {
|
|
width: width2,
|
|
lineHeight,
|
|
text: lines.join("\n"),
|
|
height: lines.length * lineHeight,
|
|
fontSize: font3.fontSize
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/util/autoSizedLabel.ts
|
|
var BaseAutoSizedLabel = class extends module_support_exports.Label {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.wrapping = "on-space";
|
|
this.overflowStrategy = "ellipsis";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BaseAutoSizedLabel.prototype, "wrapping", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BaseAutoSizedLabel.prototype, "overflowStrategy", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BaseAutoSizedLabel.prototype, "lineHeight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BaseAutoSizedLabel.prototype, "minimumFontSize", 2);
|
|
var AutoSizedLabel = class extends BaseAutoSizedLabel {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.spacing = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AutoSizedLabel.prototype, "spacing", 2);
|
|
var AutoSizedSecondaryLabel = class extends BaseAutoSizedLabel {
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/sunburst/sunburstSeriesProperties.ts
|
|
var { HierarchySeriesProperties: HierarchySeriesProperties2, makeSeriesTooltip: makeSeriesTooltip5, HighlightProperties: HighlightProperties2 } = module_support_exports;
|
|
var SunburstSeriesHighlightStyle = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesHighlightStyle.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesHighlightStyle.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesHighlightStyle.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesHighlightStyle.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesHighlightStyle.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesHighlightStyle.prototype, "opacity", 2);
|
|
var SunburstSeriesHighlight = class extends HighlightProperties2 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.highlightedBranch = new SunburstSeriesHighlightStyle();
|
|
this.unhighlightedBranch = new SunburstSeriesHighlightStyle();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesHighlight.prototype, "highlightedBranch", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesHighlight.prototype, "unhighlightedBranch", 2);
|
|
var SunburstSeriesProperties = class extends HierarchySeriesProperties2 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fillOpacity = 1;
|
|
this.strokeWidth = 0;
|
|
this.strokeOpacity = 1;
|
|
this.cornerRadius = 0;
|
|
this.highlight = new SunburstSeriesHighlight();
|
|
this.label = new AutoSizedLabel();
|
|
this.secondaryLabel = new AutoSizedSecondaryLabel();
|
|
this.tooltip = makeSeriesTooltip5();
|
|
}
|
|
getStyle(index) {
|
|
const { fills, strokes, fillOpacity, strokeWidth, strokeOpacity } = this;
|
|
return {
|
|
fill: fills[index % fills.length],
|
|
fillOpacity,
|
|
stroke: strokes[index % strokes.length],
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
opacity: 1
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "sizeName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "labelKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "secondaryLabelKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "sectorSpacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "highlight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "secondaryLabel", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SunburstSeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/sunburst/sunburstSeries.ts
|
|
var {
|
|
fromToMotion: fromToMotion3,
|
|
createDatumId: createDatumId5,
|
|
PointerEvents: PointerEvents3,
|
|
Sector: Sector3,
|
|
Group: Group6,
|
|
ScalableGroup: ScalableGroup2,
|
|
Selection: Selection4,
|
|
TransformableText: TransformableText3,
|
|
BBox: BBox7,
|
|
getLabelStyles: getLabelStyles5,
|
|
toHierarchyHighlightString: toHierarchyHighlightString2
|
|
} = module_support_exports;
|
|
var SunburstNode = class extends module_support_exports.HierarchyNode {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.label = void 0;
|
|
this.secondaryLabel = void 0;
|
|
this.contentHeight = 0;
|
|
this.bbox = void 0;
|
|
// cspell:ignore bbox
|
|
this.startAngle = 0;
|
|
this.endAngle = 0;
|
|
}
|
|
};
|
|
function setAngleData(node, startAngle = 0, angleScale = 2 * Math.PI / node.sumSize) {
|
|
for (const child of node.children) {
|
|
const endAngle = startAngle + child.sumSize * angleScale;
|
|
child.startAngle = startAngle;
|
|
child.endAngle = endAngle;
|
|
setAngleData(child, startAngle, angleScale);
|
|
startAngle = endAngle;
|
|
}
|
|
}
|
|
var SunburstSeries = class extends module_support_exports.HierarchySeries {
|
|
constructor(moduleCtx) {
|
|
super(moduleCtx);
|
|
this.NodeClass = SunburstNode;
|
|
this.properties = new SunburstSeriesProperties();
|
|
this.scalingGroup = this.contentGroup.appendChild(new ScalableGroup2());
|
|
this.sectorGroup = this.scalingGroup.appendChild(new Group6());
|
|
this.highlightSectorGroup = this.scalingGroup.appendChild(new Group6());
|
|
this.sectorLabelGroup = this.scalingGroup.appendChild(new Group6());
|
|
this.datumSelection = Selection4.select(this.sectorGroup, Sector3);
|
|
this.labelSelection = Selection4.select(
|
|
this.sectorLabelGroup,
|
|
Group6
|
|
);
|
|
this.highlightSelection = Selection4.select(
|
|
this.highlightSectorGroup,
|
|
Sector3
|
|
);
|
|
this.sectorLabelGroup.pointerEvents = PointerEvents3.None;
|
|
}
|
|
processData() {
|
|
super.processData();
|
|
setAngleData(this.rootNode);
|
|
}
|
|
updateSelections() {
|
|
const highlightedNode = this.getActiveHighlightNode();
|
|
this.highlightSelection.update(
|
|
highlightedNode == null ? [] : [highlightedNode],
|
|
void 0,
|
|
(node) => this.getDatumId(node)
|
|
);
|
|
if (!this.nodeDataRefresh)
|
|
return;
|
|
this.nodeDataRefresh = false;
|
|
const { chart } = this;
|
|
if (chart == null)
|
|
return;
|
|
const seriesRect = chart.seriesRect;
|
|
if (seriesRect == null)
|
|
return;
|
|
const descendants = Array.from(this.rootNode);
|
|
const updateLabelGroup = (group) => {
|
|
group.append([
|
|
new TransformableText3({ tag: 0 /* Primary */ }),
|
|
new TransformableText3({ tag: 1 /* Secondary */ })
|
|
]);
|
|
};
|
|
this.datumSelection.update(descendants, void 0, (node) => this.getDatumId(node));
|
|
this.labelSelection.update(descendants, updateLabelGroup, (node) => this.getDatumId(node));
|
|
}
|
|
getItemStyle(nodeDatum, isHighlight) {
|
|
const { properties, colorScale } = this;
|
|
const { itemStyler } = properties;
|
|
const rootIndex = nodeDatum.datumIndex?.[0] ?? 0;
|
|
const highlightedNode = this.getActiveHighlightNode();
|
|
const highlightState = this.getHierarchyHighlightState(isHighlight, highlightedNode, nodeDatum);
|
|
const highlightStyles = this.getHierarchyHighlightStyles(highlightState, this.properties.highlight);
|
|
const baseStyle = mergeDefaults(highlightStyles, properties.getStyle(rootIndex));
|
|
if (nodeDatum.colorValue != null && highlightStyles?.fill == null) {
|
|
baseStyle.fill = colorScale.convert(nodeDatum.colorValue);
|
|
}
|
|
let style2 = baseStyle;
|
|
if (itemStyler != null && nodeDatum != null) {
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId5(this.getDatumId(nodeDatum), isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const params = this.makeItemStylerParams(
|
|
nodeDatum,
|
|
style2,
|
|
toHierarchyHighlightString2(highlightState)
|
|
);
|
|
return this.callWithContext(itemStyler, params);
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(overrides, style2);
|
|
}
|
|
}
|
|
return style2;
|
|
}
|
|
makeItemStylerParams(nodeDatum, style2, highlightState) {
|
|
const { id: seriesId } = this;
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
seriesId,
|
|
datum: nodeDatum.datum,
|
|
depth: nodeDatum.depth ?? 0,
|
|
highlightState,
|
|
...style2,
|
|
fill
|
|
};
|
|
}
|
|
updateNodes() {
|
|
const { chart, data, maxDepth } = this;
|
|
if (chart == null || data == null) {
|
|
return;
|
|
}
|
|
const { width: width2, height: height2 } = chart.seriesRect;
|
|
const {
|
|
sectorSpacing = 0,
|
|
padding: padding2 = 0,
|
|
cornerRadius,
|
|
childrenKey,
|
|
colorKey,
|
|
colorName,
|
|
labelKey,
|
|
secondaryLabelKey,
|
|
sizeKey,
|
|
sizeName
|
|
} = this.properties;
|
|
this.contentGroup.translationX = width2 / 2;
|
|
this.contentGroup.translationY = height2 / 2;
|
|
const baseInset = sectorSpacing * 0.5;
|
|
const radius = Math.min(width2, height2) / 2;
|
|
const radiusScale = radius / (maxDepth + 1);
|
|
const angleOffset = -Math.PI / 2;
|
|
const seriesFillBBox = {
|
|
series: new BBox7(-radius, -radius, 2 * radius, 2 * radius),
|
|
axis: new BBox7(-radius, -radius, 2 * radius, 2 * radius)
|
|
};
|
|
this.rootNode?.walk((node) => {
|
|
const { startAngle, endAngle } = node;
|
|
if (node.depth != null) {
|
|
const midAngle = (startAngle + endAngle) / 2 + angleOffset;
|
|
const midRadius = (node.depth + 0.5) * radiusScale;
|
|
node.midPoint.x = Math.cos(midAngle) * midRadius;
|
|
node.midPoint.y = Math.sin(midAngle) * midRadius;
|
|
}
|
|
});
|
|
this.rootNode?.walk((node) => {
|
|
const { datum, depth, startAngle, endAngle, parent, sumSize } = node;
|
|
node.label = void 0;
|
|
node.secondaryLabel = void 0;
|
|
node.contentHeight = 0;
|
|
let labelValue;
|
|
if (datum != null && depth != null && labelKey != null) {
|
|
const value = datum[labelKey];
|
|
labelValue = this.getLabelText(
|
|
value,
|
|
datum,
|
|
labelKey,
|
|
"label",
|
|
[],
|
|
this.properties.label,
|
|
{
|
|
depth,
|
|
datum,
|
|
childrenKey,
|
|
colorKey,
|
|
colorName,
|
|
labelKey,
|
|
secondaryLabelKey,
|
|
sizeKey,
|
|
sizeName,
|
|
value
|
|
}
|
|
);
|
|
}
|
|
if (labelValue === "") {
|
|
labelValue = void 0;
|
|
}
|
|
let secondaryLabelValue;
|
|
if (datum != null && depth != null && secondaryLabelKey != null) {
|
|
const value = datum[secondaryLabelKey];
|
|
secondaryLabelValue = this.getLabelText(
|
|
value,
|
|
datum,
|
|
secondaryLabelKey,
|
|
"secondaryLabel",
|
|
[],
|
|
this.properties.secondaryLabel,
|
|
{
|
|
depth,
|
|
datum,
|
|
childrenKey,
|
|
colorKey,
|
|
colorName,
|
|
labelKey,
|
|
secondaryLabelKey,
|
|
sizeKey,
|
|
sizeName,
|
|
value
|
|
}
|
|
);
|
|
}
|
|
if (secondaryLabelValue === "") {
|
|
secondaryLabelValue = void 0;
|
|
}
|
|
if (depth == null)
|
|
return;
|
|
const innerRadius = depth * radiusScale + baseInset;
|
|
const outerRadius = (depth + 1) * radiusScale - baseInset;
|
|
const innerAngleOffset = innerRadius > baseInset ? baseInset / innerRadius : baseInset;
|
|
const outerAngleOffset = outerRadius > baseInset ? baseInset / outerRadius : baseInset;
|
|
const innerStartAngle = startAngle + innerAngleOffset;
|
|
const innerEndAngle = endAngle + innerAngleOffset;
|
|
const deltaInnerAngle = innerEndAngle - innerStartAngle;
|
|
const outerStartAngle = startAngle + outerAngleOffset;
|
|
const outerEndAngle = endAngle + outerAngleOffset;
|
|
const deltaOuterAngle = outerEndAngle - outerStartAngle;
|
|
const sizeFittingHeight = (labelHeight2) => {
|
|
const isCenterCircle = depth === 0 && parent?.sumSize === sumSize;
|
|
if (isCenterCircle) {
|
|
const labelWidth2 = 2 * Math.sqrt(outerRadius ** 2 - (labelHeight2 * 0.5) ** 2);
|
|
return { width: labelWidth2, height: labelHeight2, meta: 0 /* CenterCircle */ };
|
|
}
|
|
const parallelHeight = labelHeight2;
|
|
const availableWidthUntilItHitsTheOuterRadius = 2 * Math.sqrt(outerRadius ** 2 - (innerRadius + parallelHeight) ** 2);
|
|
const availableWidthUntilItHitsTheStraightEdges = deltaInnerAngle < Math.PI ? 2 * innerRadius * Math.tan(deltaInnerAngle * 0.5) : Infinity;
|
|
const parallelWidth = Math.min(
|
|
availableWidthUntilItHitsTheOuterRadius,
|
|
availableWidthUntilItHitsTheStraightEdges
|
|
);
|
|
const maxPerpendicularAngle = Math.PI / 4;
|
|
let perpendicularHeight;
|
|
let perpendicularWidth;
|
|
if (depth === 0) {
|
|
perpendicularHeight = labelHeight2;
|
|
perpendicularWidth = Math.sqrt(outerRadius ** 2 - (perpendicularHeight / 2) ** 2) - labelHeight2 / (2 * Math.tan(deltaOuterAngle * 0.5));
|
|
} else if (normalizeAngle360(deltaInnerAngle) < maxPerpendicularAngle) {
|
|
perpendicularHeight = 2 * innerRadius * Math.tan(deltaInnerAngle * 0.5);
|
|
perpendicularWidth = Math.sqrt(outerRadius ** 2 - (perpendicularHeight / 2) ** 2) - innerRadius;
|
|
} else {
|
|
perpendicularWidth = 0;
|
|
perpendicularHeight = 0;
|
|
}
|
|
return parallelWidth >= perpendicularWidth ? { width: parallelWidth, height: parallelHeight, meta: 1 /* Parallel */ } : { width: perpendicularWidth, height: perpendicularHeight, meta: 2 /* Perpendicular */ };
|
|
};
|
|
const formatting = formatLabels(
|
|
toPlainText(labelValue),
|
|
this.properties.label,
|
|
toPlainText(secondaryLabelValue),
|
|
this.properties.secondaryLabel,
|
|
{ padding: padding2 },
|
|
sizeFittingHeight
|
|
);
|
|
if (formatting == null)
|
|
return;
|
|
const { width: labelWidth, height: labelHeight, meta: labelPlacement, label, secondaryLabel } = formatting;
|
|
const theta = angleOffset + (startAngle + endAngle) / 2;
|
|
const top = Math.sin(theta) >= 0;
|
|
const right = Math.cos(theta) >= 0;
|
|
const circleQuarter = (top ? 3 /* Top */ : 12 /* Bottom */) & (right ? 6 /* Right */ : 9 /* Left */);
|
|
let labelRadius;
|
|
switch (labelPlacement) {
|
|
case 0 /* CenterCircle */:
|
|
labelRadius = 0;
|
|
break;
|
|
case 1 /* Parallel */: {
|
|
const opticalCentering = 0.58;
|
|
const idealRadius = outerRadius - (radiusScale - labelHeight) * opticalCentering;
|
|
const maximumRadius = Math.sqrt((outerRadius - padding2) ** 2 - (labelWidth / 2) ** 2);
|
|
labelRadius = Math.min(idealRadius, maximumRadius);
|
|
break;
|
|
}
|
|
case 2 /* Perpendicular */:
|
|
if (depth === 0) {
|
|
const minimumRadius = labelHeight / (2 * Math.tan(deltaInnerAngle * 0.5)) + labelWidth * 0.5;
|
|
const maximumRadius = Math.sqrt(outerRadius ** 2 - (labelHeight * 0.5) ** 2) - labelWidth * 0.5;
|
|
labelRadius = (minimumRadius + maximumRadius) * 0.5;
|
|
} else {
|
|
labelRadius = (innerRadius + outerRadius) * 0.5;
|
|
}
|
|
break;
|
|
}
|
|
if (label != null) {
|
|
const {
|
|
fontStyle = "normal",
|
|
fontFamily,
|
|
fontWeight: fontWeight2 = "normal",
|
|
color: color2 = "black"
|
|
} = this.properties.label;
|
|
node.label = {
|
|
...label,
|
|
fontStyle,
|
|
fontFamily,
|
|
fontWeight: fontWeight2,
|
|
color: color2,
|
|
labelPlacement,
|
|
circleQuarter,
|
|
radius: labelRadius,
|
|
theta
|
|
};
|
|
}
|
|
if (secondaryLabel != null) {
|
|
const {
|
|
fontStyle = "normal",
|
|
fontFamily,
|
|
fontWeight: fontWeight2 = "normal",
|
|
color: color2 = "black"
|
|
} = this.properties.secondaryLabel;
|
|
node.secondaryLabel = {
|
|
...secondaryLabel,
|
|
fontStyle,
|
|
fontFamily,
|
|
fontWeight: fontWeight2,
|
|
color: color2,
|
|
labelPlacement,
|
|
circleQuarter,
|
|
radius: labelRadius,
|
|
theta
|
|
};
|
|
}
|
|
node.contentHeight = formatting.height;
|
|
});
|
|
const updateSector = (nodeDatum, sector, highlighted) => {
|
|
const { depth, startAngle, endAngle } = nodeDatum;
|
|
if (depth == null) {
|
|
sector.visible = false;
|
|
return;
|
|
}
|
|
sector.visible = true;
|
|
const style2 = this.getItemStyle(nodeDatum, highlighted);
|
|
const fill = style2.fill;
|
|
const strokeWidth = style2.strokeWidth;
|
|
const fillBBox = isGradientFill(fill) && fill.bounds !== "item" ? seriesFillBBox : void 0;
|
|
sector.setStyleProperties(style2, fillBBox);
|
|
sector.centerX = 0;
|
|
sector.centerY = 0;
|
|
sector.innerRadius = depth * radiusScale;
|
|
sector.outerRadius = (depth + 1) * radiusScale;
|
|
sector.startAngle = startAngle + angleOffset;
|
|
sector.endAngle = endAngle + angleOffset;
|
|
sector.inset = baseInset + strokeWidth * 0.5;
|
|
sector.cornerRadius = cornerRadius;
|
|
};
|
|
this.datumSelection.each((sector, datum) => {
|
|
updateSector(datum, sector, false);
|
|
});
|
|
this.highlightSelection.each((rect2, datum) => {
|
|
updateSector(datum, rect2, true);
|
|
});
|
|
const highlightedNode = this.getActiveHighlightNode();
|
|
const updateText = (node, text2, tag, highlighted) => {
|
|
const { depth, contentHeight } = node;
|
|
const primary = tag === 0 /* Primary */;
|
|
const label = primary ? node.label : node.secondaryLabel;
|
|
if (depth == null || label == null) {
|
|
text2.visible = false;
|
|
return;
|
|
}
|
|
const { labelPlacement, circleQuarter, radius: textRadius, theta } = label;
|
|
const highlightState = this.getHierarchyHighlightState(highlighted, highlightedNode, node);
|
|
const { opacity: highlightOpacity } = this.getHierarchyHighlightStyles(highlightState, this.properties.highlight) ?? {};
|
|
const params = {
|
|
childrenKey: this.properties.childrenKey,
|
|
colorKey: this.properties.colorKey,
|
|
colorName: this.properties.colorName ?? this.properties.colorKey,
|
|
depth: node.depth ?? Number.NaN,
|
|
labelKey: this.properties.labelKey,
|
|
secondaryLabelKey: this.properties.secondaryLabelKey,
|
|
sizeKey: this.properties.sizeKey,
|
|
sizeName: this.properties.sizeName ?? this.properties.sizeKey
|
|
};
|
|
const baseLabelStyle = primary ? this.properties.label : this.properties.secondaryLabel;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const style2 = getLabelStyles5(this, node, params, baseLabelStyle, highlighted, activeHighlight);
|
|
text2.text = label.text;
|
|
text2.fontSize = label.fontSize;
|
|
text2.lineHeight = label.lineHeight;
|
|
text2.fontStyle = label.fontStyle;
|
|
text2.fontFamily = label.fontFamily;
|
|
text2.fontWeight = label.fontWeight;
|
|
text2.fillOpacity = highlightOpacity ?? 1;
|
|
text2.fill = style2.color;
|
|
text2.setBoxing(style2);
|
|
switch (labelPlacement) {
|
|
case 0 /* CenterCircle */:
|
|
text2.textAlign = "center";
|
|
text2.textBaseline = "top";
|
|
text2.translationX = 0;
|
|
text2.translationY = (primary ? 0 : contentHeight - label.height) - contentHeight * 0.5;
|
|
text2.rotation = 0;
|
|
break;
|
|
case 1 /* Parallel */: {
|
|
const topHalf = (circleQuarter & 3 /* Top */) !== 0;
|
|
const translationRadius = primary === !topHalf ? textRadius : textRadius - (contentHeight - label.height);
|
|
text2.textAlign = "center";
|
|
text2.textBaseline = topHalf ? "bottom" : "top";
|
|
text2.translationX = Math.cos(theta) * translationRadius;
|
|
text2.translationY = Math.sin(theta) * translationRadius;
|
|
text2.rotation = topHalf ? theta - Math.PI * 0.5 : theta + Math.PI * 0.5;
|
|
break;
|
|
}
|
|
case 2 /* Perpendicular */: {
|
|
const rightHalf = (circleQuarter & 6 /* Right */) !== 0;
|
|
const translation = primary === !rightHalf ? (contentHeight - label.height) * 0.5 : (label.height - contentHeight) * 0.5;
|
|
text2.textAlign = "center";
|
|
text2.textBaseline = "middle";
|
|
text2.translationX = Math.cos(theta) * textRadius + Math.cos(theta + Math.PI / 2) * translation;
|
|
text2.translationY = Math.sin(theta) * textRadius + Math.sin(theta + Math.PI / 2) * translation;
|
|
text2.rotation = rightHalf ? theta : theta + Math.PI;
|
|
break;
|
|
}
|
|
}
|
|
text2.visible = true;
|
|
};
|
|
const highlightedDatum = this.getActiveHighlightNode();
|
|
for (const text2 of this.labelSelection.selectByClass(TransformableText3)) {
|
|
const datum = text2.closestDatum();
|
|
updateText(datum, text2, text2.tag, datum === highlightedDatum);
|
|
}
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, properties, ctx } = this;
|
|
const { labelKey, secondaryLabelKey, childrenKey, sizeKey, sizeName, colorKey, colorName, tooltip } = properties;
|
|
const { formatManager } = ctx;
|
|
const nodeDatum = datumIndex.reduce((n, i) => n?.children[i], this.rootNode);
|
|
if (nodeDatum == null)
|
|
return;
|
|
const { datum, depth } = nodeDatum;
|
|
if (datum == null || depth == null)
|
|
return;
|
|
const data = [];
|
|
const datumSize = sizeKey == null ? void 0 : datum[sizeKey];
|
|
if (datumSize != null) {
|
|
const sizeDomain = [0, this.rootNode?.sumSize ?? 0];
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value: datumSize,
|
|
datum,
|
|
seriesId,
|
|
legendItemName: void 0,
|
|
key: sizeKey,
|
|
source: "tooltip",
|
|
property: "size",
|
|
boundSeries: this.getFormatterContext("size"),
|
|
domain: sizeDomain,
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
});
|
|
data.push({ label: sizeName, fallbackLabel: sizeKey, value: content ?? formatValue(datumSize) });
|
|
}
|
|
const datumColor = colorKey == null ? void 0 : datum[colorKey];
|
|
if (datumColor != null) {
|
|
const { colorDomain } = this;
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value: datumColor,
|
|
datum,
|
|
seriesId,
|
|
legendItemName: void 0,
|
|
key: colorKey,
|
|
source: "tooltip",
|
|
property: "color",
|
|
boundSeries: this.getFormatterContext("color"),
|
|
domain: colorDomain,
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
});
|
|
data.push({ label: colorName, fallbackLabel: colorKey, value: content ?? formatValue(datumColor) });
|
|
}
|
|
const format = this.getItemStyle(
|
|
{ ...nodeDatum, colorValue: datumColor ?? nodeDatum.colorValue },
|
|
false
|
|
);
|
|
const color2 = format.fill;
|
|
const markerStyle = {
|
|
shape: "square",
|
|
fill: color2,
|
|
fillOpacity: 1,
|
|
stroke: void 0,
|
|
strokeWidth: 0,
|
|
strokeOpacity: 1,
|
|
lineDash: [0],
|
|
lineDashOffset: 0
|
|
};
|
|
if (isGradientFill(markerStyle.fill)) {
|
|
markerStyle.fill = { ...markerStyle.fill, gradient: "linear", rotation: 0, reverse: false };
|
|
}
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
title: labelKey == null ? void 0 : datum[labelKey],
|
|
symbol: {
|
|
marker: markerStyle
|
|
},
|
|
data
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: void 0,
|
|
depth,
|
|
labelKey,
|
|
secondaryLabelKey,
|
|
childrenKey,
|
|
sizeKey,
|
|
sizeName,
|
|
colorKey,
|
|
colorName,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
createNodeData() {
|
|
return void 0;
|
|
}
|
|
pickNodeClosestDatum(point) {
|
|
return this.pickNodeNearestDistantObject(point, this.datumSelection.nodes());
|
|
}
|
|
animateEmptyUpdateReady() {
|
|
fromToMotion3(this.id, "nodes", this.ctx.animationManager, [this.scalingGroup], {
|
|
toFn() {
|
|
return { scalingX: 1, scalingY: 1 };
|
|
},
|
|
fromFn() {
|
|
return { scalingX: 0, scalingY: 0 };
|
|
}
|
|
});
|
|
}
|
|
computeFocusBounds(node) {
|
|
return node;
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
SunburstSeries.className = "SunburstSeries";
|
|
SunburstSeries.type = "sunburst";
|
|
|
|
// packages/ag-charts-enterprise/src/series/sunburst/sunburstSeriesOptionsDef.ts
|
|
var { sunburstSeriesThemeableOptionsDef: sunburstSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var sunburstSeriesOptionsDef = {
|
|
...sunburstSeriesThemeableOptionsDef2,
|
|
...without(commonSeriesOptionsDefs, ["highlightStyle", "highlight", "showInLegend"]),
|
|
type: required(constant("sunburst")),
|
|
labelKey: string,
|
|
secondaryLabelKey: string,
|
|
childrenKey: string,
|
|
sizeKey: string,
|
|
colorKey: string,
|
|
sizeName: string,
|
|
colorName: string
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/sunburst/sunburstModule.ts
|
|
var themeTemplate7 = {
|
|
series: {
|
|
fills: {
|
|
$applyCycle: [
|
|
{ $size: { $path: ["./data", { $path: "/data" }] } },
|
|
{ $palette: "fills" },
|
|
{
|
|
$applySwitch: [
|
|
{ $path: ["/type", void 0, { $value: "$1" }] },
|
|
{ $value: "$1" },
|
|
["gradient", FILL_GRADIENT_RADIAL_REVERSED_SERIES_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS]
|
|
]
|
|
}
|
|
]
|
|
},
|
|
strokes: {
|
|
$applyCycle: [{ $size: { $path: ["./data", { $path: "/data" }] } }, { $palette: "strokes" }]
|
|
},
|
|
colorRange: { $palette: "divergingColors" },
|
|
strokeWidth: { $isUserOption: ["./strokes/0", 2, 0] },
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontSize: { $rem: FONT_SIZE_RATIO.LARGE },
|
|
minimumFontSize: { $rem: 9 / BASE_FONT_SIZE },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "chartBackgroundColor" },
|
|
overflowStrategy: "ellipsis",
|
|
wrapping: "never",
|
|
spacing: 2
|
|
},
|
|
secondaryLabel: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontSize: { $rem: FONT_SIZE_RATIO.SMALLEST },
|
|
minimumFontSize: { $rem: 7 / BASE_FONT_SIZE },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "chartBackgroundColor" },
|
|
overflowStrategy: "ellipsis",
|
|
wrapping: "never"
|
|
},
|
|
sectorSpacing: 2,
|
|
padding: 3,
|
|
highlight: {
|
|
unhighlightedItem: {
|
|
fillOpacity: 0.6,
|
|
strokeOpacity: 0.6
|
|
},
|
|
unhighlightedBranch: {
|
|
fillOpacity: 0.2,
|
|
strokeOpacity: 0.2
|
|
}
|
|
}
|
|
},
|
|
gradientLegend: {
|
|
enabled: { $if: [{ $path: "../series/0/colorKey" }, true, false] }
|
|
}
|
|
};
|
|
var SunburstSeriesModule = {
|
|
type: "series",
|
|
name: "sunburst",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
solo: true,
|
|
version: VERSION,
|
|
dependencies: [StandaloneChartModule],
|
|
options: sunburstSeriesOptionsDef,
|
|
themeTemplate: themeTemplate7,
|
|
create: (ctx) => new SunburstSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/treemap/treemapSeriesProperties.ts
|
|
var { HierarchySeriesProperties: HierarchySeriesProperties3, makeSeriesTooltip: makeSeriesTooltip6, Label: Label6 } = module_support_exports;
|
|
var TreemapGroupLabel = class extends Label6 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.spacing = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapGroupLabel.prototype, "spacing", 2);
|
|
var TreemapSeriesGroupHighlightStyle = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroupHighlightStyle.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroupHighlightStyle.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroupHighlightStyle.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroupHighlightStyle.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroupHighlightStyle.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroupHighlightStyle.prototype, "opacity", 2);
|
|
var TreemapSeriesGroupHighlight = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.highlightedItem = new TreemapSeriesGroupHighlightStyle();
|
|
this.unhighlightedItem = new TreemapSeriesGroupHighlightStyle();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroupHighlight.prototype, "highlightedItem", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroupHighlight.prototype, "unhighlightedItem", 2);
|
|
var TreemapSeriesTileHighlightStyle = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTileHighlightStyle.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTileHighlightStyle.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTileHighlightStyle.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTileHighlightStyle.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTileHighlightStyle.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTileHighlightStyle.prototype, "opacity", 2);
|
|
var TreemapSeriesTileHighlight = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.highlightedBranch = new TreemapSeriesTileHighlightStyle();
|
|
this.highlightedItem = new TreemapSeriesTileHighlightStyle();
|
|
this.unhighlightedItem = new TreemapSeriesTileHighlightStyle();
|
|
this.unhighlightedBranch = new TreemapSeriesTileHighlightStyle();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTileHighlight.prototype, "highlightedBranch", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTileHighlight.prototype, "highlightedItem", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTileHighlight.prototype, "unhighlightedItem", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTileHighlight.prototype, "unhighlightedBranch", 2);
|
|
var TreemapSeriesGroup = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = void 0;
|
|
this.fillOpacity = 1;
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.cornerRadius = 0;
|
|
this.textAlign = "center";
|
|
this.gap = 0;
|
|
this.padding = 0;
|
|
this.interactive = true;
|
|
this.label = new TreemapGroupLabel();
|
|
this.highlight = new TreemapSeriesGroupHighlight();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroup.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroup.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroup.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroup.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroup.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroup.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroup.prototype, "textAlign", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroup.prototype, "gap", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroup.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroup.prototype, "interactive", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroup.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesGroup.prototype, "highlight", 2);
|
|
var TreemapSeriesTile = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = void 0;
|
|
this.fillOpacity = 1;
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.cornerRadius = 0;
|
|
this.textAlign = "center";
|
|
this.verticalAlign = "middle";
|
|
this.gap = 0;
|
|
this.padding = 0;
|
|
this.label = new AutoSizedLabel();
|
|
this.secondaryLabel = new AutoSizedSecondaryLabel();
|
|
this.highlight = new TreemapSeriesTileHighlight();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTile.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTile.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTile.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTile.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTile.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTile.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTile.prototype, "textAlign", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTile.prototype, "verticalAlign", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTile.prototype, "gap", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTile.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTile.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTile.prototype, "secondaryLabel", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesTile.prototype, "highlight", 2);
|
|
var TreemapSeriesProperties = class extends HierarchySeriesProperties3 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.tooltip = makeSeriesTooltip6();
|
|
this.group = new TreemapSeriesGroup();
|
|
this.tile = new TreemapSeriesTile();
|
|
this.undocumentedGroupFills = [];
|
|
this.undocumentedGroupStrokes = [];
|
|
}
|
|
getStyle(isLeaf, fills, strokes, index) {
|
|
const {
|
|
fillOpacity,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
fill = isLeaf ? fills[index % fills.length] : fills[Math.min(index, fills.length)],
|
|
stroke: stroke3 = isLeaf ? strokes[index % fills.length] : strokes[Math.min(index, strokes.length)]
|
|
} = isLeaf ? this.tile : this.group;
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
opacity: 1
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesProperties.prototype, "sizeName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesProperties.prototype, "labelKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesProperties.prototype, "secondaryLabelKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesProperties.prototype, "tooltip", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesProperties.prototype, "group", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesProperties.prototype, "tile", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesProperties.prototype, "undocumentedGroupFills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TreemapSeriesProperties.prototype, "undocumentedGroupStrokes", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/treemap/treemapSeries.ts
|
|
var {
|
|
createDatumId: createDatumId6,
|
|
Rect: Rect3,
|
|
Group: Group7,
|
|
BBox: BBox8,
|
|
Selection: Selection5,
|
|
Text: Text3,
|
|
Transformable: Transformable3,
|
|
getLabelStyles: getLabelStyles6,
|
|
HierarchyHighlightState: HierarchyHighlightState2,
|
|
toHierarchyHighlightString: toHierarchyHighlightString3
|
|
} = module_support_exports;
|
|
var TreemapNode = class extends module_support_exports.HierarchyNode {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.labelValue = void 0;
|
|
this.secondaryLabelValue = void 0;
|
|
this.label = void 0;
|
|
this.secondaryLabel = void 0;
|
|
this.bbox = void 0;
|
|
this.padding = void 0;
|
|
}
|
|
};
|
|
function nodeSize(node) {
|
|
return node.children.length > 0 ? node.sumSize - node.sizeValue : node.sizeValue;
|
|
}
|
|
var textAlignFactors = {
|
|
left: 0,
|
|
center: 0.5,
|
|
right: 1
|
|
};
|
|
var verticalAlignFactors = {
|
|
top: 0,
|
|
middle: 0.5,
|
|
bottom: 1
|
|
};
|
|
var DistantGroup = class extends module_support_exports.Group {
|
|
distanceSquared(x, y) {
|
|
return this.getBBox().distanceSquared(x, y);
|
|
}
|
|
};
|
|
var TreemapSeries = class extends module_support_exports.HierarchySeries {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.NodeClass = TreemapNode;
|
|
this.properties = new TreemapSeriesProperties();
|
|
this.rectGroup = this.contentGroup.appendChild(new Group7());
|
|
this.datumSelection = Selection5.select(this.rectGroup, Rect3);
|
|
this.labelSelection = Selection5.select(this.labelGroup, Group7);
|
|
this.highlightSelection = Selection5.select(
|
|
this.rectGroup,
|
|
Rect3
|
|
);
|
|
}
|
|
groupTitleHeight(node, bbox) {
|
|
const heightRatioThreshold = 3;
|
|
const { label } = this.properties.group;
|
|
const { labelValue } = node;
|
|
const { fontSize } = label;
|
|
if (label.enabled && labelValue != null && fontSize <= bbox.width / heightRatioThreshold && fontSize <= bbox.height / heightRatioThreshold) {
|
|
const { height: fontHeight } = cachedTextMeasurer(label).measureLines(labelValue);
|
|
return Math.max(fontHeight, fontSize);
|
|
}
|
|
}
|
|
getNodePadding(node, bbox) {
|
|
if (node.parent == null) {
|
|
return { top: 0, right: 0, bottom: 0, left: 0 };
|
|
} else if (node.children.length === 0) {
|
|
const { padding: padding3 } = this.properties.tile;
|
|
return { top: padding3, right: padding3, bottom: padding3, left: padding3 };
|
|
}
|
|
const {
|
|
padding: padding2,
|
|
label: { spacing }
|
|
} = this.properties.group;
|
|
const fontHeight = this.groupTitleHeight(node, bbox);
|
|
const titleHeight = fontHeight == null ? 0 : fontHeight + spacing;
|
|
return {
|
|
top: padding2 + titleHeight,
|
|
right: padding2,
|
|
bottom: padding2,
|
|
left: padding2
|
|
};
|
|
}
|
|
sortChildren({ children }) {
|
|
const sortedChildrenIndices = Array.from(children, (_, i) => i).filter((i) => nodeSize(children[i]) > 0).sort((aIndex, bIndex) => nodeSize(children[bIndex]) - nodeSize(children[aIndex]));
|
|
const childAt = (i) => {
|
|
const sortedIndex = sortedChildrenIndices[i];
|
|
return children[sortedIndex];
|
|
};
|
|
return { sortedChildrenIndices, childAt };
|
|
}
|
|
/**
|
|
* Squarified Treemap algorithm
|
|
* https://www.win.tue.nl/~vanwijk/stm.pdf
|
|
*/
|
|
squarify(node, bbox) {
|
|
const { datum, children } = node;
|
|
if (bbox.width <= 0 || bbox.height <= 0) {
|
|
node.bbox = void 0;
|
|
node.padding = void 0;
|
|
node.midPoint.x = Number.NaN;
|
|
node.midPoint.y = Number.NaN;
|
|
return;
|
|
}
|
|
const padding2 = datum == null ? { top: 0, right: 0, bottom: 0, left: 0 } : this.getNodePadding(node, bbox);
|
|
if (node.parent == null) {
|
|
node.bbox = void 0;
|
|
node.padding = void 0;
|
|
node.midPoint.x = Number.NaN;
|
|
node.midPoint.y = Number.NaN;
|
|
} else {
|
|
node.bbox = bbox;
|
|
node.padding = padding2;
|
|
node.midPoint.x = bbox.x + bbox.width / 2;
|
|
node.midPoint.y = bbox.y;
|
|
}
|
|
const { sortedChildrenIndices, childAt } = this.sortChildren(node);
|
|
const allLeafNodes = sortedChildrenIndices.every((sortedIndex) => children[sortedIndex].children.length === 0);
|
|
const targetTileAspectRatio = 1;
|
|
const width2 = bbox.width - padding2.left - padding2.right;
|
|
const height2 = bbox.height - padding2.top - padding2.bottom;
|
|
if (width2 <= 0 || height2 <= 0)
|
|
return;
|
|
const numChildren = sortedChildrenIndices.length;
|
|
let stackSum = 0;
|
|
let startIndex = 0;
|
|
let minRatioDiff = Infinity;
|
|
let partitionSum = sortedChildrenIndices.reduce((sum, sortedIndex) => sum + nodeSize(children[sortedIndex]), 0);
|
|
const innerBox = new BBox8(bbox.x + padding2.left, bbox.y + padding2.top, width2, height2);
|
|
const partition = innerBox.clone();
|
|
let i = 0;
|
|
while (i < numChildren) {
|
|
const value = nodeSize(childAt(i));
|
|
const firstValue = nodeSize(childAt(startIndex));
|
|
const isVertical2 = partition.width < partition.height;
|
|
stackSum += value;
|
|
const partThickness = isVertical2 ? partition.height : partition.width;
|
|
const partLength = isVertical2 ? partition.width : partition.height;
|
|
const firstTileLength = partLength * firstValue / stackSum;
|
|
let stackThickness = partThickness * stackSum / partitionSum;
|
|
const ratio2 = Math.max(firstTileLength, stackThickness) / Math.min(firstTileLength, stackThickness);
|
|
const diff9 = Math.abs(targetTileAspectRatio - ratio2);
|
|
if (diff9 < minRatioDiff) {
|
|
minRatioDiff = diff9;
|
|
i++;
|
|
continue;
|
|
}
|
|
stackSum -= value;
|
|
stackThickness = partThickness * stackSum / partitionSum;
|
|
let start3 = isVertical2 ? partition.x : partition.y;
|
|
for (let j = startIndex; j < i; j++) {
|
|
const child = childAt(j);
|
|
const childSize = nodeSize(child);
|
|
const x = isVertical2 ? start3 : partition.x;
|
|
const y = isVertical2 ? partition.y : start3;
|
|
const length2 = partLength * childSize / stackSum;
|
|
const stackWidth = isVertical2 ? length2 : stackThickness;
|
|
const stackHeight = isVertical2 ? stackThickness : length2;
|
|
const childBbox = new BBox8(x, y, stackWidth, stackHeight);
|
|
this.applyGap(innerBox, childBbox, allLeafNodes);
|
|
this.squarify(child, childBbox);
|
|
partitionSum -= childSize;
|
|
start3 += length2;
|
|
}
|
|
if (isVertical2) {
|
|
partition.y += stackThickness;
|
|
partition.height -= stackThickness;
|
|
} else {
|
|
partition.x += stackThickness;
|
|
partition.width -= stackThickness;
|
|
}
|
|
startIndex = i;
|
|
stackSum = 0;
|
|
minRatioDiff = Infinity;
|
|
}
|
|
const isVertical = partition.width < partition.height;
|
|
let start2 = isVertical ? partition.x : partition.y;
|
|
for (let childIdx = startIndex; childIdx < numChildren; childIdx++) {
|
|
const child = childAt(childIdx);
|
|
const x = isVertical ? start2 : partition.x;
|
|
const y = isVertical ? partition.y : start2;
|
|
const part = nodeSize(child) / partitionSum;
|
|
const childWidth = partition.width * (isVertical ? part : 1);
|
|
const childHeight = partition.height * (isVertical ? 1 : part);
|
|
const childBox = new BBox8(x, y, childWidth, childHeight);
|
|
this.applyGap(innerBox, childBox, allLeafNodes);
|
|
this.squarify(child, childBox);
|
|
start2 += isVertical ? childWidth : childHeight;
|
|
}
|
|
}
|
|
applyGap(innerBox, childBox, allLeafNodes) {
|
|
const gap = allLeafNodes ? this.properties.tile.gap * 0.5 : this.properties.group.gap * 0.5;
|
|
const getBounds = (box) => ({
|
|
left: box.x,
|
|
top: box.y,
|
|
right: box.x + box.width,
|
|
bottom: box.y + box.height
|
|
});
|
|
const innerBounds = getBounds(innerBox);
|
|
const childBounds = getBounds(childBox);
|
|
const sides = ["top", "right", "bottom", "left"];
|
|
for (const side of sides) {
|
|
if (!isNumberEqual(innerBounds[side], childBounds[side])) {
|
|
childBox.shrink(gap, side);
|
|
}
|
|
}
|
|
}
|
|
createNodeData() {
|
|
return void 0;
|
|
}
|
|
getItemStyle(nodeDatum, isLeaf, isHighlight) {
|
|
const { properties, colorScale } = this;
|
|
const { itemStyler } = properties;
|
|
const rootIndex = nodeDatum.datumIndex?.[0] ?? 0;
|
|
const fills = isLeaf ? properties.fills : properties.undocumentedGroupFills;
|
|
const strokes = isLeaf ? properties.strokes : properties.undocumentedGroupStrokes;
|
|
const index = isLeaf ? rootIndex : nodeDatum.depth ?? -1;
|
|
const highlightedNode = this.getActiveHighlightNode();
|
|
const tileHighlightState = this.getHierarchyHighlightState(isHighlight, highlightedNode, nodeDatum);
|
|
const groupHighlightState = this.getGroupHighlightState(isHighlight, highlightedNode, nodeDatum);
|
|
const highlightState = isLeaf ? tileHighlightState : groupHighlightState;
|
|
const highlightStyle = isLeaf ? this.getTileHighlightStyle(tileHighlightState, groupHighlightState, highlightedNode) : this.getGroupHighlightStyle(groupHighlightState);
|
|
const baseStyle = mergeDefaults(highlightStyle, properties.getStyle(isLeaf, fills, strokes, index));
|
|
if (isLeaf && nodeDatum.colorValue != null && highlightStyle?.fill == null) {
|
|
baseStyle.fill = colorScale.convert(nodeDatum.colorValue);
|
|
}
|
|
let style2 = baseStyle;
|
|
if (itemStyler != null && nodeDatum != null) {
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId6(this.getDatumId(nodeDatum), isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const params = this.makeItemStylerParams(
|
|
nodeDatum,
|
|
style2,
|
|
toHierarchyHighlightString3(highlightState)
|
|
);
|
|
return this.callWithContext(itemStyler, params);
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(overrides, style2);
|
|
}
|
|
}
|
|
return style2;
|
|
}
|
|
makeItemStylerParams(nodeDatum, style2, highlightState) {
|
|
const { id: seriesId } = this;
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
seriesId,
|
|
datum: nodeDatum.datum,
|
|
depth: nodeDatum.depth ?? -1,
|
|
highlightState,
|
|
...style2,
|
|
fill
|
|
};
|
|
}
|
|
updateSelections() {
|
|
const highlightedNode = this.getActiveHighlightNode();
|
|
this.highlightSelection.update(
|
|
highlightedNode == null ? [] : [highlightedNode],
|
|
void 0,
|
|
(node) => this.getDatumId(node)
|
|
);
|
|
if (!this.nodeDataRefresh) {
|
|
return;
|
|
}
|
|
this.nodeDataRefresh = false;
|
|
const { seriesRect } = this.chart ?? {};
|
|
if (!seriesRect)
|
|
return;
|
|
const descendants = Array.from(this.rootNode);
|
|
const updateLabelGroup = (group) => {
|
|
group.append([new Text3({ tag: 0 /* Primary */ }), new Text3({ tag: 1 /* Secondary */ })]);
|
|
};
|
|
this.datumSelection.update(descendants, void 0, (node) => this.getDatumId(node));
|
|
this.labelSelection.update(descendants, updateLabelGroup, (node) => this.getDatumId(node));
|
|
}
|
|
getActiveHighlightNode() {
|
|
const highlightedNode = super.getActiveHighlightNode();
|
|
if (highlightedNode?.children.length && !this.properties.group.interactive) {
|
|
return void 0;
|
|
}
|
|
return highlightedNode;
|
|
}
|
|
updateNodes() {
|
|
const { rootNode, data } = this;
|
|
const { childrenKey, colorKey, colorName, labelKey, secondaryLabelKey, sizeKey, sizeName, tile, group } = this.properties;
|
|
const { seriesRect } = this.chart ?? {};
|
|
if (!seriesRect || !data)
|
|
return;
|
|
this.rootNode?.walk((node) => {
|
|
const { datum, depth, children } = node;
|
|
const isLeaf = children.length === 0;
|
|
const labelStyle = isLeaf ? tile.label : group.label;
|
|
let labelValue;
|
|
if (labelStyle.enabled && datum != null && depth != null && labelKey != null) {
|
|
const value = datum[labelKey];
|
|
labelValue = this.getLabelText(
|
|
value,
|
|
datum,
|
|
labelKey,
|
|
"label",
|
|
[],
|
|
labelStyle,
|
|
{
|
|
depth,
|
|
datum,
|
|
childrenKey,
|
|
colorKey,
|
|
colorName,
|
|
labelKey,
|
|
secondaryLabelKey,
|
|
sizeKey,
|
|
sizeName,
|
|
value
|
|
}
|
|
);
|
|
}
|
|
if (labelValue === "") {
|
|
labelValue = void 0;
|
|
}
|
|
let secondaryLabelValue;
|
|
if (tile.secondaryLabel.enabled && isLeaf && datum != null && depth != null && secondaryLabelKey != null) {
|
|
const value = datum[secondaryLabelKey];
|
|
secondaryLabelValue = this.getLabelText(
|
|
value,
|
|
datum,
|
|
secondaryLabelKey,
|
|
"secondaryLabel",
|
|
[],
|
|
tile.secondaryLabel,
|
|
{
|
|
depth,
|
|
datum,
|
|
childrenKey,
|
|
colorKey,
|
|
colorName,
|
|
labelKey,
|
|
secondaryLabelKey,
|
|
sizeKey,
|
|
sizeName,
|
|
value
|
|
}
|
|
);
|
|
}
|
|
if (secondaryLabelValue === "") {
|
|
secondaryLabelValue = void 0;
|
|
}
|
|
node.labelValue = toPlainText(labelValue);
|
|
node.secondaryLabelValue = toPlainText(secondaryLabelValue);
|
|
});
|
|
const { width: width2, height: height2 } = seriesRect;
|
|
this.squarify(rootNode, new BBox8(0, 0, width2, height2));
|
|
this.rootNode?.walk((node) => {
|
|
const { bbox, children, labelValue, secondaryLabelValue } = node;
|
|
node.label = void 0;
|
|
node.secondaryLabel = void 0;
|
|
if (bbox == null)
|
|
return;
|
|
if (children.length === 0) {
|
|
const layout = {
|
|
width: bbox.width,
|
|
height: bbox.height,
|
|
meta: null
|
|
};
|
|
const formatting = formatLabels(
|
|
labelValue,
|
|
this.properties.tile.label,
|
|
secondaryLabelValue,
|
|
this.properties.tile.secondaryLabel,
|
|
{ padding: tile.padding },
|
|
() => layout
|
|
);
|
|
if (formatting == null) {
|
|
return;
|
|
}
|
|
const { height: labelHeight, label, secondaryLabel } = formatting;
|
|
const { textAlign, verticalAlign, padding: padding2 } = tile;
|
|
const textAlignFactor = textAlignFactors[textAlign] ?? 0.5;
|
|
const labelX = bbox.x + padding2 + (bbox.width - 2 * padding2) * textAlignFactor;
|
|
const verticalAlignFactor = verticalAlignFactors[verticalAlign] ?? 0.5;
|
|
const labelYStart = bbox.y + padding2 + labelHeight * 0.5 + (bbox.height - 2 * padding2 - labelHeight) * verticalAlignFactor;
|
|
if (label != null) {
|
|
const {
|
|
fontStyle = "normal",
|
|
fontFamily,
|
|
fontWeight: fontWeight2 = "normal",
|
|
color: color2 = "black"
|
|
} = this.properties.tile.label;
|
|
node.label = {
|
|
text: label.text,
|
|
fontSize: label.fontSize,
|
|
lineHeight: label.lineHeight,
|
|
fontStyle,
|
|
fontFamily,
|
|
fontWeight: fontWeight2,
|
|
color: color2,
|
|
textAlign,
|
|
verticalAlign: "middle",
|
|
x: labelX,
|
|
y: labelYStart - (labelHeight - label.height) * 0.5
|
|
};
|
|
}
|
|
if (secondaryLabel != null) {
|
|
const {
|
|
fontStyle = "normal",
|
|
fontFamily,
|
|
fontWeight: fontWeight2 = "normal",
|
|
color: color2 = "black"
|
|
} = this.properties.tile.secondaryLabel;
|
|
node.secondaryLabel = {
|
|
text: secondaryLabel.text,
|
|
fontSize: secondaryLabel.fontSize,
|
|
lineHeight: secondaryLabel.fontSize,
|
|
fontStyle,
|
|
fontFamily,
|
|
fontWeight: fontWeight2,
|
|
color: color2,
|
|
textAlign,
|
|
verticalAlign: "middle",
|
|
x: labelX,
|
|
y: labelYStart + (labelHeight - secondaryLabel.height) * 0.5
|
|
};
|
|
}
|
|
} else if (labelValue == null) {
|
|
return;
|
|
} else {
|
|
const { padding: padding2, textAlign } = group;
|
|
const groupTitleHeight = this.groupTitleHeight(node, bbox);
|
|
if (groupTitleHeight == null)
|
|
return;
|
|
const innerWidth = bbox.width - 2 * padding2;
|
|
const text2 = wrapText(labelValue, {
|
|
maxWidth: bbox.width - 2 * padding2,
|
|
font: group.label,
|
|
textWrap: "never"
|
|
});
|
|
const textAlignFactor = textAlignFactors[textAlign] ?? 0.5;
|
|
const {
|
|
fontStyle = "normal",
|
|
fontFamily,
|
|
fontWeight: fontWeight2 = "normal",
|
|
color: color2 = "black"
|
|
} = this.properties.group.label;
|
|
node.label = {
|
|
text: text2,
|
|
fontSize: group.label.fontSize,
|
|
lineHeight: calcLineHeight(group.label.fontSize),
|
|
fontStyle,
|
|
fontFamily,
|
|
fontWeight: fontWeight2,
|
|
color: color2,
|
|
textAlign,
|
|
verticalAlign: "middle",
|
|
x: bbox.x + padding2 + innerWidth * textAlignFactor,
|
|
y: bbox.y + padding2 + groupTitleHeight * 0.5
|
|
};
|
|
}
|
|
});
|
|
const fillBBox = {
|
|
series: new BBox8(0, 0, width2, height2),
|
|
axis: new BBox8(0, 0, width2, height2)
|
|
};
|
|
const updateRectFn = (node, rect2, isHighlight) => {
|
|
const { bbox } = node;
|
|
if (bbox == null) {
|
|
rect2.visible = false;
|
|
return;
|
|
}
|
|
const { depth = -1 } = node;
|
|
const isLeaf = node.children.length === 0;
|
|
const style2 = this.getItemStyle(node, isLeaf, isHighlight);
|
|
rect2.crisp = true;
|
|
rect2.setStyleProperties(style2, fillBBox);
|
|
rect2.cornerRadius = isLeaf ? tile.cornerRadius : group.cornerRadius;
|
|
rect2.zIndex = [0, depth, isHighlight ? 1 : 0];
|
|
const onlyLeaves = node.parent?.children.every((n) => n.children.length === 0);
|
|
const parentBbox = node.parent == null ? void 0 : node.parent.bbox;
|
|
const parentPadding = node.parent == null ? void 0 : node.parent.padding;
|
|
if (onlyLeaves === true && parentBbox != null && parentPadding != null) {
|
|
rect2.clipBBox = bbox;
|
|
rect2.x = parentBbox.x + parentPadding.left;
|
|
rect2.y = parentBbox.y + parentPadding.top;
|
|
rect2.width = parentBbox.width - (parentPadding.left + parentPadding.right);
|
|
rect2.height = parentBbox.height - (parentPadding.top + parentPadding.bottom);
|
|
} else {
|
|
rect2.clipBBox = void 0;
|
|
rect2.x = bbox.x;
|
|
rect2.y = bbox.y;
|
|
rect2.width = bbox.width;
|
|
rect2.height = bbox.height;
|
|
}
|
|
rect2.visible = true;
|
|
};
|
|
this.datumSelection.each((rect2, datum) => updateRectFn(datum, rect2, false));
|
|
this.highlightSelection.each((rect2, datum) => {
|
|
updateRectFn(datum, rect2, true);
|
|
});
|
|
const updateLabelFn = (node, text2, tag, highlighted) => {
|
|
const isLeaf = node.children.length === 0;
|
|
const label = tag === 0 /* Primary */ ? node.label : node.secondaryLabel;
|
|
if (label == null) {
|
|
text2.visible = false;
|
|
return;
|
|
}
|
|
let labelProps;
|
|
let labelPath;
|
|
if (tag === 0 /* Primary */) {
|
|
labelProps = isLeaf ? tile.label : group.label;
|
|
labelPath = ["series", `${this.declarationOrder}`, isLeaf ? "tile" : "group", "label"];
|
|
} else {
|
|
labelProps = tile.secondaryLabel;
|
|
labelPath = ["series", `${this.declarationOrder}`, "tile", "secondaryLabel"];
|
|
}
|
|
const { opacity: highlightOpacity } = this.getItemStyle(node, isLeaf, highlighted) ?? {};
|
|
const params = {
|
|
childrenKey: this.properties.childrenKey,
|
|
colorKey: this.properties.colorKey,
|
|
colorName: this.properties.colorName ?? this.properties.colorKey,
|
|
depth: node.depth ?? Number.NaN,
|
|
labelKey: this.properties.labelKey,
|
|
secondaryLabelKey: this.properties.secondaryLabelKey,
|
|
sizeKey: this.properties.sizeKey,
|
|
sizeName: this.properties.sizeName ?? this.properties.sizeKey
|
|
};
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const style2 = getLabelStyles6(this, node, params, labelProps, highlighted, activeHighlight, labelPath);
|
|
text2.text = label.text;
|
|
text2.fontSize = label.fontSize;
|
|
text2.lineHeight = label.lineHeight;
|
|
text2.fontStyle = label.fontStyle;
|
|
text2.fontFamily = label.fontFamily;
|
|
text2.fontWeight = label.fontWeight;
|
|
text2.fillOpacity = highlightOpacity ?? 1;
|
|
text2.fill = style2.color;
|
|
text2.setBoxing(style2);
|
|
text2.textAlign = label.textAlign;
|
|
text2.textBaseline = label.verticalAlign;
|
|
text2.x = label.x;
|
|
text2.y = label.y;
|
|
text2.visible = true;
|
|
text2.zIndex = 1;
|
|
};
|
|
const highlightedDatum = this.getActiveHighlightNode();
|
|
for (const text2 of this.labelSelection.selectByClass(Text3)) {
|
|
const datum = text2.closestDatum();
|
|
updateLabelFn(datum, text2, text2.tag, datum === highlightedDatum);
|
|
}
|
|
}
|
|
getGroupHighlightState(isHighlight, highlightedNode, nodeDatum) {
|
|
const nodeIndex = nodeDatum.datumIndex;
|
|
const highlightedIndex = highlightedNode?.datumIndex;
|
|
const isDescendant = this.isDescendantDatumIndex(nodeIndex, highlightedIndex);
|
|
if (nodeDatum.children?.length === 0) {
|
|
if (nodeIndex == null || highlightedNode == null || highlightedNode.children?.length === 0) {
|
|
return HierarchyHighlightState2.None;
|
|
}
|
|
return isDescendant ? HierarchyHighlightState2.Item : HierarchyHighlightState2.OtherItem;
|
|
}
|
|
if (highlightedNode == null || highlightedNode.children?.length === 0) {
|
|
return HierarchyHighlightState2.None;
|
|
}
|
|
const isSibling = nodeDatum.depth != null && highlightedNode.depth != null && nodeDatum.depth === highlightedNode.depth;
|
|
if (isDescendant && !isSibling) {
|
|
return HierarchyHighlightState2.None;
|
|
}
|
|
return isHighlight ? HierarchyHighlightState2.Item : HierarchyHighlightState2.OtherItem;
|
|
}
|
|
getTileHighlightStyle(tileHighlightState, groupHighlightState, highlightedNode) {
|
|
const isGroupHighlighted = highlightedNode?.children && highlightedNode.children.length > 0;
|
|
if (isGroupHighlighted) {
|
|
const groupStyle = this.getGroupHighlightStyle(groupHighlightState);
|
|
if (groupStyle?.fillOpacity == null && groupStyle?.strokeOpacity == null) {
|
|
return void 0;
|
|
}
|
|
return { fillOpacity: groupStyle.fillOpacity, strokeOpacity: groupStyle.strokeOpacity };
|
|
}
|
|
return this.getHierarchyHighlightStyles(tileHighlightState, this.properties.tile.highlight);
|
|
}
|
|
getGroupHighlightStyle(highlightState) {
|
|
const { highlight: highlight5 } = this.properties.group;
|
|
switch (highlightState) {
|
|
case HierarchyHighlightState2.Item:
|
|
return highlight5.highlightedItem;
|
|
case HierarchyHighlightState2.OtherItem:
|
|
return highlight5.unhighlightedItem;
|
|
default:
|
|
return void 0;
|
|
}
|
|
}
|
|
getHighlightStateString(_datum, isHighlight, datumIndex) {
|
|
if (datumIndex == null) {
|
|
return toHierarchyHighlightString3(HierarchyHighlightState2.None);
|
|
}
|
|
const nodeDatum = datumIndex.reduce((node, idx) => node?.children[idx], this.rootNode);
|
|
const highlightedNode = this.getActiveHighlightNode();
|
|
if (nodeDatum == null) {
|
|
return toHierarchyHighlightString3(HierarchyHighlightState2.None);
|
|
}
|
|
const isLeaf = (nodeDatum.children?.length ?? 0) === 0;
|
|
if (isLeaf) {
|
|
const tileState = this.getHierarchyHighlightState(isHighlight ?? false, highlightedNode, nodeDatum);
|
|
return toHierarchyHighlightString3(tileState);
|
|
}
|
|
const groupState = this.getGroupHighlightState(isHighlight ?? false, highlightedNode, nodeDatum);
|
|
return toHierarchyHighlightString3(groupState);
|
|
}
|
|
isDescendantDatumIndex(nodeIndex, ancestorIndex) {
|
|
if (ancestorIndex == null || ancestorIndex.length === 0) {
|
|
return true;
|
|
}
|
|
if (nodeIndex == null || nodeIndex.length < ancestorIndex.length) {
|
|
return false;
|
|
}
|
|
for (let i = 0; i < ancestorIndex.length; i += 1) {
|
|
if (nodeIndex[i] !== ancestorIndex[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
pickNodesExactShape(point) {
|
|
const nodes = super.pickNodesExactShape(point);
|
|
nodes.sort((a, b) => b.datumIndex.length - a.datumIndex.length);
|
|
return nodes;
|
|
}
|
|
pickNodeClosestDatum(point) {
|
|
const exactMatch = this.pickNodesExactShape(point);
|
|
if (exactMatch.length !== 0) {
|
|
return { datum: exactMatch[0], distance: 0 };
|
|
}
|
|
return this.pickNodeNearestDistantObject(point, this.datumSelection.nodes());
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, properties, ctx } = this;
|
|
const { formatManager } = ctx;
|
|
const { labelKey, secondaryLabelKey, childrenKey, sizeKey, sizeName, colorKey, colorName, tooltip } = properties;
|
|
const nodeDatum = datumIndex.reduce((n, i) => n?.children[i], this.rootNode);
|
|
if (nodeDatum == null)
|
|
return;
|
|
const { datum, depth, children } = nodeDatum;
|
|
if (datum == null || depth == null)
|
|
return;
|
|
const isLeaf = children.length === 0;
|
|
const data = [];
|
|
const datumSize = sizeKey == null ? void 0 : datum[sizeKey];
|
|
if (datumSize != null) {
|
|
const sizeDomain = [0, this.rootNode?.sumSize ?? 0];
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value: datumSize,
|
|
datum,
|
|
seriesId,
|
|
legendItemName: void 0,
|
|
key: sizeKey,
|
|
source: "tooltip",
|
|
property: "size",
|
|
boundSeries: this.getFormatterContext("size"),
|
|
domain: sizeDomain,
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
});
|
|
data.push({ label: sizeName, fallbackLabel: sizeKey, value: content ?? formatValue(datumSize) });
|
|
}
|
|
const datumColor = colorKey == null ? void 0 : datum[colorKey];
|
|
if (datumColor != null) {
|
|
const { colorDomain } = this;
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value: datumColor,
|
|
datum,
|
|
seriesId,
|
|
legendItemName: void 0,
|
|
key: colorKey,
|
|
source: "tooltip",
|
|
property: "color",
|
|
boundSeries: this.getFormatterContext("color"),
|
|
domain: colorDomain,
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
});
|
|
data.push({ label: colorName, fallbackLabel: colorKey, value: content ?? formatValue(datumColor) });
|
|
}
|
|
const format = this.getItemStyle(
|
|
{ ...nodeDatum, colorValue: datumColor ?? nodeDatum.colorValue },
|
|
isLeaf,
|
|
false
|
|
);
|
|
const color2 = format.fill;
|
|
const markerStyle = {
|
|
shape: "square",
|
|
fill: color2,
|
|
fillOpacity: 1,
|
|
stroke: void 0,
|
|
strokeWidth: 0,
|
|
strokeOpacity: 1,
|
|
lineDash: [0],
|
|
lineDashOffset: 0
|
|
};
|
|
if (isGradientFill(markerStyle.fill)) {
|
|
markerStyle.fill = { ...markerStyle.fill, gradient: "linear", rotation: 0, reverse: false };
|
|
}
|
|
const symbol = isLeaf ? { marker: markerStyle } : void 0;
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
title: labelKey == null ? void 0 : datum[labelKey],
|
|
symbol,
|
|
data
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: void 0,
|
|
depth,
|
|
labelKey,
|
|
secondaryLabelKey,
|
|
childrenKey,
|
|
sizeKey,
|
|
sizeName,
|
|
colorKey,
|
|
colorName,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
computeFocusBounds(node) {
|
|
return Transformable3.toCanvas(this.contentGroup, node.getBBox());
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null || this.properties.tile.label.itemStyler != null || this.properties.group.label.itemStyler != null;
|
|
}
|
|
};
|
|
TreemapSeries.className = "TreemapSeries";
|
|
TreemapSeries.type = "treemap";
|
|
|
|
// packages/ag-charts-enterprise/src/series/treemap/treemapSeriesOptionsDef.ts
|
|
var { treemapSeriesThemeableOptionsDef: treemapSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var treemapSeriesOptionsDef = {
|
|
...treemapSeriesThemeableOptionsDef2,
|
|
...without(commonSeriesOptionsDefs, ["highlightStyle", "highlight", "showInLegend"]),
|
|
type: required(constant("treemap")),
|
|
labelKey: string,
|
|
secondaryLabelKey: string,
|
|
childrenKey: string,
|
|
sizeKey: string,
|
|
colorKey: string,
|
|
sizeName: string,
|
|
colorName: string
|
|
};
|
|
treemapSeriesOptionsDef.undocumentedGroupFills = undocumented(arrayOf(color));
|
|
treemapSeriesOptionsDef.undocumentedGroupStrokes = undocumented(arrayOf(color));
|
|
|
|
// packages/ag-charts-enterprise/src/series/treemap/treemapModule.ts
|
|
var TreemapSeriesModule = {
|
|
type: "series",
|
|
name: "treemap",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
solo: true,
|
|
version: VERSION,
|
|
dependencies: [StandaloneChartModule],
|
|
options: treemapSeriesOptionsDef,
|
|
themeTemplate: {
|
|
series: {
|
|
fills: {
|
|
$applyCycle: [
|
|
{ $size: { $path: ["./data", { $path: "/data" }] } },
|
|
{ $palette: "fills" },
|
|
{
|
|
$applySwitch: [
|
|
{ $path: ["/type", void 0, { $value: "$1" }] },
|
|
{ $value: "$1" },
|
|
["gradient", FILL_GRADIENT_LINEAR_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS]
|
|
]
|
|
}
|
|
]
|
|
},
|
|
strokes: {
|
|
$applyCycle: [{ $size: { $path: ["./data", { $path: "/data" }] } }, { $palette: "strokes" }]
|
|
},
|
|
colorRange: { $palette: "divergingColors" },
|
|
undocumentedGroupFills: { $palette: "hierarchyColors" },
|
|
undocumentedGroupStrokes: { $palette: "secondHierarchyColors" },
|
|
group: {
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
color: { $ref: "textColor" },
|
|
fontStyle: void 0,
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
spacing: 4
|
|
},
|
|
fill: void 0,
|
|
// Override default fill
|
|
stroke: void 0,
|
|
// Override default stroke
|
|
strokeWidth: 1,
|
|
padding: 4,
|
|
gap: 2,
|
|
textAlign: "left",
|
|
highlight: {
|
|
unhighlightedItem: {
|
|
opacity: 0.2,
|
|
fillOpacity: 0.2,
|
|
strokeOpacity: 0.2
|
|
}
|
|
}
|
|
},
|
|
tile: {
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
color: { $ref: "chartBackgroundColor" },
|
|
fontStyle: void 0,
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $rem: 1.5 },
|
|
minimumFontSize: { $rem: FONT_SIZE_RATIO.SMALLER },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
wrapping: "on-space",
|
|
overflowStrategy: "ellipsis",
|
|
spacing: 2
|
|
},
|
|
secondaryLabel: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
color: { $ref: "chartBackgroundColor" },
|
|
fontStyle: void 0,
|
|
fontWeight: void 0,
|
|
fontSize: { $ref: "fontSize" },
|
|
minimumFontSize: { $rem: FONT_SIZE_RATIO.SMALLER },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
wrapping: "never",
|
|
overflowStrategy: "ellipsis"
|
|
},
|
|
fill: void 0,
|
|
// Override default fill
|
|
stroke: void 0,
|
|
// Override default stroke
|
|
strokeWidth: { $isUserOption: ["../strokes/0", 2, { $isUserOption: ["./stroke", 2, 0] }] },
|
|
padding: 3,
|
|
gap: 1,
|
|
highlight: {
|
|
unhighlightedItem: {
|
|
fillOpacity: 0.6,
|
|
strokeOpacity: 0.6
|
|
},
|
|
unhighlightedBranch: {
|
|
fillOpacity: 0.2,
|
|
strokeOpacity: 0.2
|
|
}
|
|
}
|
|
}
|
|
},
|
|
gradientLegend: {
|
|
enabled: { $if: [{ $path: "../series/0/colorKey" }, true, false] }
|
|
}
|
|
},
|
|
create: (ctx) => new TreemapSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/animation/animation.ts
|
|
var Animation2 = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.enabled = true;
|
|
ctx.animationManager.skip(false);
|
|
this.cleanup.register(() => ctx.animationManager.skip(true));
|
|
}
|
|
};
|
|
__decorateClass([
|
|
ObserveChanges((target, newValue) => {
|
|
target.ctx.animationManager.skip(!newValue);
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], Animation2.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
ObserveChanges((target, newValue) => {
|
|
if (newValue != null) {
|
|
target.ctx.animationManager.defaultDuration = newValue;
|
|
}
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], Animation2.prototype, "duration", 2);
|
|
__decorateClass([
|
|
ObserveChanges((target, newValue) => {
|
|
target.ctx.animationManager.maxAnimatableItems = newValue ?? Infinity;
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], Animation2.prototype, "maxAnimatableItems", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/animation/animationModule.ts
|
|
var AnimationModule = {
|
|
type: "plugin",
|
|
name: "animation",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: {
|
|
enabled: boolean,
|
|
duration: positiveNumber
|
|
},
|
|
themeTemplate: {
|
|
enabled: true
|
|
},
|
|
create: (ctx) => new Animation2(ctx)
|
|
};
|
|
AnimationModule.options.maxAnimatableItems = undocumented(positiveNumber);
|
|
|
|
// packages/ag-charts-enterprise/src/features/shared-toolbar/sharedToolbar.ts
|
|
var _SharedToolbar = class _SharedToolbar extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.activeSections = /* @__PURE__ */ new Set();
|
|
this.sectionButtons = {
|
|
annotations: [],
|
|
chartToolbar: []
|
|
};
|
|
this.container = this.ctx.domManager.addChild("canvas-overlay", "shared-toolbar");
|
|
this.container.role = "presentation";
|
|
}
|
|
getSharedToolbar(section) {
|
|
if (!this.sharedToolbar) {
|
|
this.createSharedToolbar();
|
|
}
|
|
if (section === "chartToolbar" && this.sharedToolbar) {
|
|
this.sharedToolbar.setAriaLabelId("ariaLabelFinancialCharts");
|
|
}
|
|
return this.toolbarWithSection(section);
|
|
}
|
|
createSharedToolbar() {
|
|
this.sharedToolbar = new module_support_exports.Toolbar(this.ctx, "ariaLabelAnnotationsToolbar", "vertical");
|
|
this.sharedToolbar.addClass("ag-charts-shared-toolbar");
|
|
this.container.append(this.sharedToolbar.getElement());
|
|
this.cleanup.register(() => {
|
|
if (!this.sharedToolbar)
|
|
return;
|
|
this.sharedToolbar.getElement().remove();
|
|
this.sharedToolbar.destroy();
|
|
this.sharedToolbar = void 0;
|
|
});
|
|
}
|
|
toolbarWithSection(section) {
|
|
const sharedToolbar = this.sharedToolbar;
|
|
const withSection = {
|
|
layout: (layoutBox, padding2) => {
|
|
if (this.firstLayoutSection != null && this.firstLayoutSection !== section && this.activeSections.has(this.firstLayoutSection)) {
|
|
return;
|
|
}
|
|
this.firstLayoutSection = section;
|
|
const width2 = sharedToolbar.getBounds().width;
|
|
sharedToolbar.setBounds({
|
|
x: layoutBox.x,
|
|
y: layoutBox.y,
|
|
width: width2
|
|
});
|
|
layoutBox.shrink({ left: width2 + sharedToolbar.horizontalSpacing + (padding2 ?? 0) });
|
|
},
|
|
addToolbarListener: (eventType, handler) => {
|
|
return sharedToolbar.addToolbarListener(eventType, (sharedEvent) => {
|
|
const sectionIndex = this.getSectionIndex(section, sharedEvent.button.index);
|
|
if (sectionIndex < 0)
|
|
return;
|
|
const event = {
|
|
...sharedEvent,
|
|
button: this.sectionButtons[section][sectionIndex]
|
|
};
|
|
handler(event);
|
|
});
|
|
},
|
|
updateButtons: (buttons) => {
|
|
this.sectionButtons[section] = buttons;
|
|
const sharedButtons = _SharedToolbar.SECTION_ORDER.flatMap((order) => this.sectionButtons[order]);
|
|
sharedToolbar.updateButtons(sharedButtons);
|
|
},
|
|
updateButtonByIndex: (index, button) => {
|
|
sharedToolbar.updateButtonByIndex(this.getIndex(section, index), button);
|
|
},
|
|
toggleActiveButtonByIndex: (index) => {
|
|
sharedToolbar.toggleActiveButtonByIndex(this.getIndex(section, index));
|
|
},
|
|
toggleButtonEnabledByIndex: (index, enabled) => {
|
|
sharedToolbar.toggleButtonEnabledByIndex(this.getIndex(section, index), enabled);
|
|
},
|
|
setHidden: (hidden) => {
|
|
if (hidden) {
|
|
this.activeSections.delete(section);
|
|
} else {
|
|
this.activeSections.add(section);
|
|
}
|
|
let sum = 0;
|
|
for (const order of _SharedToolbar.SECTION_ORDER) {
|
|
if (order !== section) {
|
|
sum += this.sectionButtons[order].length;
|
|
continue;
|
|
}
|
|
for (const index of this.sectionButtons[section].keys()) {
|
|
sharedToolbar.setButtonHiddenByIndex(sum + index, hidden);
|
|
}
|
|
}
|
|
},
|
|
destroy: () => {
|
|
withSection.setHidden(true);
|
|
if (this.activeSections.size === 0) {
|
|
this.destroy();
|
|
}
|
|
},
|
|
clearActiveButton: sharedToolbar.clearActiveButton.bind(sharedToolbar),
|
|
addListener: sharedToolbar.addListener.bind(sharedToolbar),
|
|
removeListener: sharedToolbar.removeListener.bind(sharedToolbar)
|
|
};
|
|
withSection.setHidden(false);
|
|
return withSection;
|
|
}
|
|
getIndex(section, index) {
|
|
let sum = 0;
|
|
for (const order of _SharedToolbar.SECTION_ORDER) {
|
|
if (order === section)
|
|
return sum + index;
|
|
sum += this.sectionButtons[order].length;
|
|
}
|
|
return -1;
|
|
}
|
|
getSectionIndex(section, index) {
|
|
let sum = 0;
|
|
for (const order of _SharedToolbar.SECTION_ORDER) {
|
|
if (order === section) {
|
|
if (index >= sum + this.sectionButtons[section].length)
|
|
return -1;
|
|
return index - sum;
|
|
}
|
|
sum += this.sectionButtons[order].length;
|
|
}
|
|
return -1;
|
|
}
|
|
};
|
|
_SharedToolbar.SECTION_ORDER = ["chartToolbar", "annotations"];
|
|
var SharedToolbar = _SharedToolbar;
|
|
|
|
// packages/ag-charts-enterprise/src/features/text-input/textInputTemplate.html
|
|
var textInputTemplate_default = '<div contenteditable="plaintext-only" class="ag-charts-text-input__textarea" tabindex="0"></div>';
|
|
|
|
// packages/ag-charts-enterprise/src/features/text-input/textInput.ts
|
|
var moduleId = "text-input";
|
|
var canvasOverlay2 = "canvas-overlay";
|
|
var TextInput = class {
|
|
constructor(ctx) {
|
|
this.ctx = ctx;
|
|
this.cleanup = new CleanupRegistry();
|
|
this.layout = {
|
|
getTextInputCoords: () => ({ x: 0, y: 0 }),
|
|
getTextPosition: () => "center",
|
|
alignment: "center",
|
|
textAlign: "center"
|
|
};
|
|
this.visible = false;
|
|
this.element = ctx.domManager.addChild(canvasOverlay2, moduleId);
|
|
this.element.classList.add("ag-charts-text-input");
|
|
this.cleanup.register(() => ctx.domManager.removeChild(canvasOverlay2, moduleId));
|
|
}
|
|
setKeyDownHandler(handler) {
|
|
this.cleanup.register(attachListener(this.element, "keydown", handler));
|
|
}
|
|
show(opts) {
|
|
this.element.innerHTML = textInputTemplate_default;
|
|
const textArea = this.element.firstElementChild;
|
|
setAttributes(textArea, {
|
|
role: "textbox",
|
|
// AG-15233
|
|
"data-preventdefault": false
|
|
// AG-13715
|
|
});
|
|
if (!textArea.isContentEditable) {
|
|
textArea.contentEditable = "true";
|
|
}
|
|
textArea.setAttribute(
|
|
"placeholder",
|
|
this.ctx.localeManager.t(opts.placeholderText ?? "inputTextareaPlaceholder")
|
|
);
|
|
if (opts.styles?.placeholderColor) {
|
|
textArea.style.setProperty("--placeholder-text-color", opts.styles?.placeholderColor);
|
|
}
|
|
textArea.innerText = opts.text ?? "";
|
|
textArea.style.color = opts.styles?.color ?? "inherit";
|
|
textArea.style.fontFamily = opts.styles?.fontFamily ?? "inherit";
|
|
textArea.style.fontSize = opts.styles?.fontSize ? `${opts.styles.fontSize}px` : "inherit";
|
|
textArea.style.fontStyle = opts.styles?.fontStyle ?? "inherit";
|
|
textArea.style.fontWeight = typeof opts.styles?.fontWeight === "number" ? `${opts.styles.fontWeight}` : opts.styles?.fontWeight ?? "inherit";
|
|
focusCursorAtEnd(textArea);
|
|
textArea.addEventListener("input", () => {
|
|
this.updatePosition();
|
|
opts.onChange?.(this.getValue(), this.getBBox());
|
|
});
|
|
textArea.addEventListener("click", (event) => {
|
|
event.stopPropagation();
|
|
});
|
|
if (opts.layout) {
|
|
this.layout = opts.layout;
|
|
this.updatePosition();
|
|
}
|
|
opts.onChange?.(this.getValue(), this.getBBox());
|
|
this.visible = true;
|
|
}
|
|
hide() {
|
|
this.element.innerHTML = "";
|
|
this.layout = {
|
|
getTextInputCoords: () => ({ x: 0, y: 0 }),
|
|
getTextPosition: () => "center",
|
|
alignment: "center",
|
|
textAlign: "center"
|
|
};
|
|
this.visible = false;
|
|
}
|
|
isVisible() {
|
|
return this.visible;
|
|
}
|
|
updateColor(color2) {
|
|
if (!this.element.firstElementChild)
|
|
return;
|
|
this.element.firstElementChild.style.color = color2;
|
|
}
|
|
updateFontSize(fontSize) {
|
|
if (!this.element.firstElementChild)
|
|
return;
|
|
this.element.firstElementChild.style.fontSize = `${fontSize}px`;
|
|
this.updatePosition();
|
|
return this.getBBox();
|
|
}
|
|
getValue() {
|
|
if (!this.element.firstElementChild)
|
|
return;
|
|
return this.element.firstElementChild.innerText.trim();
|
|
}
|
|
updatePosition() {
|
|
const { element: element2 } = this;
|
|
const textArea = element2.firstElementChild;
|
|
if (!textArea)
|
|
return;
|
|
const sceneRect = this.ctx.domManager.getBoundingClientRect();
|
|
const { width: width2, getTextInputCoords, getTextPosition, alignment, textAlign } = this.layout;
|
|
element2.style.setProperty("width", width2 ? `${width2}px` : "unset");
|
|
const textRect = textArea.getBoundingClientRect();
|
|
const point = getTextInputCoords(textRect.height);
|
|
let horizontalPosition = point.x;
|
|
if (alignment === "center") {
|
|
horizontalPosition -= (width2 ?? textRect.width) / 2;
|
|
} else if (alignment === "right") {
|
|
horizontalPosition -= width2 ?? textRect.width;
|
|
}
|
|
const position = getTextPosition();
|
|
let verticalPosition = point.y;
|
|
if (position === "center") {
|
|
verticalPosition -= textRect.height / 2;
|
|
} else if (position === "bottom") {
|
|
verticalPosition -= textRect.height;
|
|
}
|
|
element2.style.setProperty("top", `${verticalPosition}px`);
|
|
element2.style.setProperty("left", `${horizontalPosition}px`);
|
|
element2.style.setProperty("max-width", `${sceneRect.width - horizontalPosition}px`);
|
|
element2.style.setProperty("text-align", alignment);
|
|
textArea.style.setProperty("text-align", textAlign);
|
|
}
|
|
getBBox() {
|
|
const { left, top, width: width2, height: height2 } = this.element.getBoundingClientRect();
|
|
return new module_support_exports.BBox(left, top, ceilTo(width2, 2), height2);
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/annotationAxesButtons.ts
|
|
var AxesButtons = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = false;
|
|
this.axes = "y";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxesButtons.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxesButtons.prototype, "axes", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/annotationTypes.ts
|
|
var AnnotationType = /* @__PURE__ */ ((AnnotationType2) => {
|
|
AnnotationType2["Line"] = "line";
|
|
AnnotationType2["HorizontalLine"] = "horizontal-line";
|
|
AnnotationType2["VerticalLine"] = "vertical-line";
|
|
AnnotationType2["DisjointChannel"] = "disjoint-channel";
|
|
AnnotationType2["ParallelChannel"] = "parallel-channel";
|
|
AnnotationType2["FibonacciRetracement"] = "fibonacci-retracement";
|
|
AnnotationType2["FibonacciRetracementTrendBased"] = "fibonacci-retracement-trend-based";
|
|
AnnotationType2["Callout"] = "callout";
|
|
AnnotationType2["Comment"] = "comment";
|
|
AnnotationType2["Note"] = "note";
|
|
AnnotationType2["Text"] = "text";
|
|
AnnotationType2["Arrow"] = "arrow";
|
|
AnnotationType2["ArrowUp"] = "arrow-up";
|
|
AnnotationType2["ArrowDown"] = "arrow-down";
|
|
AnnotationType2["DateRange"] = "date-range";
|
|
AnnotationType2["PriceRange"] = "price-range";
|
|
AnnotationType2["DatePriceRange"] = "date-price-range";
|
|
AnnotationType2["QuickDatePriceRange"] = "quick-date-price-range";
|
|
return AnnotationType2;
|
|
})(AnnotationType || {});
|
|
var ANNOTATION_TYPES = Object.values(AnnotationType);
|
|
function stringToAnnotationType(value) {
|
|
for (const t of ANNOTATION_TYPES) {
|
|
if (t === value)
|
|
return t;
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/annotationProperties.ts
|
|
var PointProperties = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PointProperties.prototype, "x", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PointProperties.prototype, "y", 2);
|
|
var ChannelAnnotationMiddleProperties = class extends Stroke(LineStyle(Visible(BaseProperties))) {
|
|
};
|
|
var AxisLabelProperties = class extends Stroke(LineStyle(Fill(Label7(Font(BaseProperties))))) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.cornerRadius = 2;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabelProperties.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabelProperties.prototype, "cornerRadius", 2);
|
|
var BackgroundProperties = class extends Fill(BaseProperties) {
|
|
};
|
|
var HandleProperties = class extends Stroke(LineStyle(Fill(BaseProperties))) {
|
|
};
|
|
var LineTextProperties = class extends Font(BaseProperties) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.label = "";
|
|
this.position = "top";
|
|
this.alignment = "left";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineTextProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineTextProperties.prototype, "position", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineTextProperties.prototype, "alignment", 2);
|
|
var LabelTextProperties = class extends Font(BaseProperties) {
|
|
};
|
|
var ChannelTextProperties = class extends Font(BaseProperties) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.label = "";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChannelTextProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChannelTextProperties.prototype, "position", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ChannelTextProperties.prototype, "alignment", 2);
|
|
function Annotation(Parent) {
|
|
class AnnotationInternal extends Writeable(Visible(Parent)) {
|
|
constructor() {
|
|
super(...arguments);
|
|
// A uuid is required, over the usual incrementing index, as annotations can be restored from external databases
|
|
this.id = generateUUID();
|
|
}
|
|
}
|
|
return AnnotationInternal;
|
|
}
|
|
function Line2(Parent) {
|
|
class LineInternal extends Parent {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.start = new PointProperties();
|
|
this.end = new PointProperties();
|
|
}
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineInternal.prototype, "start", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineInternal.prototype, "end", 2);
|
|
return LineInternal;
|
|
}
|
|
function Point(Parent) {
|
|
class PointInternal extends Parent {
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PointInternal.prototype, "x", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PointInternal.prototype, "y", 2);
|
|
return PointInternal;
|
|
}
|
|
function Value(Parent) {
|
|
class ValueInternal extends Parent {
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ValueInternal.prototype, "value", 2);
|
|
return ValueInternal;
|
|
}
|
|
function Background2(Parent) {
|
|
class BackgroundInternal extends Parent {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.background = new BackgroundProperties();
|
|
}
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BackgroundInternal.prototype, "background", 2);
|
|
return BackgroundInternal;
|
|
}
|
|
function Handle(Parent) {
|
|
class HandleInternal extends Parent {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.handle = new HandleProperties();
|
|
}
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HandleInternal.prototype, "handle", 2);
|
|
return HandleInternal;
|
|
}
|
|
function AxisLabel2(Parent) {
|
|
class AxisLabelInternal extends Parent {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.axisLabel = new AxisLabelProperties();
|
|
}
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisLabelInternal.prototype, "axisLabel", 2);
|
|
return AxisLabelInternal;
|
|
}
|
|
function Label7(Parent) {
|
|
class LabelInternal extends Parent {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.padding = void 0;
|
|
this.textAlign = "center";
|
|
this.formatter = void 0;
|
|
}
|
|
// TODO: making this generic causes issues with mixins sequence
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelInternal.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelInternal.prototype, "textAlign", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LabelInternal.prototype, "formatter", 2);
|
|
return LabelInternal;
|
|
}
|
|
function Cappable(Parent) {
|
|
class CappableInternal extends Parent {
|
|
}
|
|
return CappableInternal;
|
|
}
|
|
function Extendable(Parent) {
|
|
class ExtendableInternal extends Parent {
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ExtendableInternal.prototype, "extendStart", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ExtendableInternal.prototype, "extendEnd", 2);
|
|
return ExtendableInternal;
|
|
}
|
|
function Writeable(Parent) {
|
|
class WriteableInternal extends Parent {
|
|
isWriteable() {
|
|
return !this.locked && !this.readOnly;
|
|
}
|
|
isHoverable() {
|
|
return !this.readOnly;
|
|
}
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WriteableInternal.prototype, "locked", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WriteableInternal.prototype, "readOnly", 2);
|
|
return WriteableInternal;
|
|
}
|
|
function Localisable(Parent) {
|
|
class LocalisableInternal extends Parent {
|
|
setLocaleManager(localeManager) {
|
|
this.localeManager ?? (this.localeManager = localeManager);
|
|
}
|
|
}
|
|
return LocalisableInternal;
|
|
}
|
|
function Visible(Parent) {
|
|
class VisibleInternal extends Parent {
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], VisibleInternal.prototype, "visible", 2);
|
|
return VisibleInternal;
|
|
}
|
|
function Fill(Parent) {
|
|
class FillInternal extends Parent {
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillInternal.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FillInternal.prototype, "fillOpacity", 2);
|
|
return FillInternal;
|
|
}
|
|
function Stroke(Parent) {
|
|
class StrokeInternal extends Parent {
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StrokeInternal.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StrokeInternal.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StrokeInternal.prototype, "strokeWidth", 2);
|
|
return StrokeInternal;
|
|
}
|
|
function LineStyle(Parent) {
|
|
class LineDashInternal extends Parent {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.lineCap = void 0;
|
|
this.computedLineDash = void 0;
|
|
}
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineDashInternal.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineDashInternal.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineDashInternal.prototype, "lineStyle", 2);
|
|
return LineDashInternal;
|
|
}
|
|
function Font(Parent) {
|
|
class FontInternal extends Parent {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fontSize = 12 /* SMALL */;
|
|
this.fontFamily = "Verdana, sans-serif";
|
|
}
|
|
}
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FontInternal.prototype, "fontStyle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FontInternal.prototype, "fontWeight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FontInternal.prototype, "fontSize", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FontInternal.prototype, "fontFamily", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FontInternal.prototype, "color", 2);
|
|
return FontInternal;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/utils/scale.ts
|
|
function getGrouping(d) {
|
|
if (isNumber(d) || isString(d) || isDate(d)) {
|
|
return { value: d, groupPercentage: 0 };
|
|
}
|
|
return d ?? { value: void 0, groupPercentage: 0 };
|
|
}
|
|
function getGroupingValue(d) {
|
|
return getGrouping(d)?.value;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/utils/values.ts
|
|
function convertLine(datum, context) {
|
|
if (datum.start == null || datum.end == null)
|
|
return;
|
|
const start2 = convertPoint(datum.start, context);
|
|
const end3 = convertPoint(datum.end, context);
|
|
if (start2 == null || end3 == null)
|
|
return;
|
|
return { x1: start2.x, y1: start2.y, x2: end3.x, y2: end3.y };
|
|
}
|
|
function convertPoint(point, context) {
|
|
const x = convert(point.x, context.xAxis);
|
|
const y = convert(point.y, context.yAxis);
|
|
return { x, y };
|
|
}
|
|
function convert(p, context) {
|
|
if (p == null)
|
|
return 0;
|
|
const { value, groupPercentage } = getGrouping(p);
|
|
const { scale: scale2, snapToGroup } = context;
|
|
const width2 = scale2.bandwidth === 0 ? scale2.step ?? 0 : scale2.bandwidth ?? 0;
|
|
const offset = snapToGroup ? width2 / 2 : width2 * groupPercentage;
|
|
return scale2.convert(value) + offset;
|
|
}
|
|
function invertCoords(coords, context) {
|
|
const x = invert(coords.x, context.xAxis);
|
|
const y = invert(coords.y, context.yAxis);
|
|
return { x, y };
|
|
}
|
|
function invert(n, context) {
|
|
const { scale: scale2 } = context;
|
|
if (context.continuous && scale2.step == null) {
|
|
return context.scaleInvert(n);
|
|
}
|
|
const value = context.scaleInvertNearest(n);
|
|
const width2 = scale2.bandwidth === 0 ? scale2.step : scale2.bandwidth ?? 0;
|
|
const bandStart = scale2.convert(value);
|
|
const bandEnd = bandStart + width2;
|
|
const groupPercentage = bandStart === bandEnd ? 0 : (n - bandStart) / (bandEnd - bandStart);
|
|
return { value, groupPercentage };
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/properties/startEndProperties.ts
|
|
var StartEndProperties = class extends Annotation(Line2(Handle(BaseProperties))) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.snapToAngle = 45;
|
|
}
|
|
getDefaultColor(_colorPickerType) {
|
|
return void 0;
|
|
}
|
|
getDefaultOpacity(_colorPickerType) {
|
|
return void 0;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/properties/textualStartEndProperties.ts
|
|
var TextualStartEndProperties = class extends Localisable(Label7(Font(StartEndProperties))) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.text = "";
|
|
this.position = "top";
|
|
this.alignment = "left";
|
|
this.placement = "inside";
|
|
this.placeholderText = "inputTextareaPlaceholder";
|
|
}
|
|
getDefaultColor(_colorPickerType) {
|
|
return this.color;
|
|
}
|
|
getDefaultOpacity(_colorPickerType) {
|
|
return void 0;
|
|
}
|
|
getPlaceholderColor() {
|
|
return void 0;
|
|
}
|
|
getPadding() {
|
|
const { padding: padding2 = 0 } = this;
|
|
return {
|
|
top: padding2,
|
|
right: padding2,
|
|
bottom: padding2,
|
|
left: padding2
|
|
};
|
|
}
|
|
getText() {
|
|
const isPlaceholder = this.text.length == 0;
|
|
let text2 = this.text;
|
|
if (isPlaceholder) {
|
|
text2 = this.placeholderText ?? "";
|
|
if (this.localeManager)
|
|
text2 = this.localeManager.t(text2);
|
|
}
|
|
return {
|
|
text: text2,
|
|
isPlaceholder
|
|
};
|
|
}
|
|
getTextInputCoords(context, _height) {
|
|
return convertPoint(this.end, context);
|
|
}
|
|
getTextPosition() {
|
|
return this.position;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TextualStartEndProperties.prototype, "text", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/callout/calloutProperties.ts
|
|
var DEFAULT_CALLOUT_PADDING = {
|
|
top: 6,
|
|
right: 12,
|
|
bottom: 9,
|
|
left: 12
|
|
};
|
|
var CalloutProperties = class extends Fill(Stroke(TextualStartEndProperties)) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "callout" /* Callout */;
|
|
this.position = "bottom";
|
|
this.alignment = "left";
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "callout" /* Callout */;
|
|
}
|
|
getDefaultColor(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case `fill-color`:
|
|
return this.fill;
|
|
case `line-color`:
|
|
return this.stroke;
|
|
case `text-color`:
|
|
default:
|
|
return this.color;
|
|
}
|
|
}
|
|
getDefaultOpacity(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case `fill-color`:
|
|
return this.fillOpacity;
|
|
case `line-color`:
|
|
return this.strokeOpacity;
|
|
case `text-color`:
|
|
default:
|
|
return void 0;
|
|
}
|
|
}
|
|
getPlaceholderColor() {
|
|
const { r, g, b } = Color.fromString(this.color ?? "#888888");
|
|
return new Color(r, g, b, 0.66).toString();
|
|
}
|
|
getPadding() {
|
|
const { padding: padding2 } = this;
|
|
if (padding2 == null) {
|
|
return { ...DEFAULT_CALLOUT_PADDING };
|
|
}
|
|
return {
|
|
top: padding2,
|
|
right: padding2,
|
|
bottom: padding2,
|
|
left: padding2
|
|
};
|
|
}
|
|
getTextInputCoords(context, height2) {
|
|
const coords = super.getTextInputCoords(context, height2);
|
|
const padding2 = this.getPadding();
|
|
const paddingLeft = padding2.left ?? 0;
|
|
const paddingBottom = padding2.bottom ?? 0;
|
|
return {
|
|
x: coords.x + paddingLeft,
|
|
y: coords.y - paddingBottom
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CalloutProperties.prototype, "type", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/properties/textualPointProperties.ts
|
|
var TextualPointProperties = class extends Annotation(Point(Handle(Label7(Font(BaseProperties))))) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.text = "";
|
|
this.position = "top";
|
|
this.alignment = "left";
|
|
this.placement = "inside";
|
|
this.placeholderText = "inputTextareaPlaceholder";
|
|
}
|
|
getDefaultColor(_colorPickerType) {
|
|
return this.color;
|
|
}
|
|
getDefaultOpacity(_colorPickerType) {
|
|
return void 0;
|
|
}
|
|
getPlaceholderColor() {
|
|
return void 0;
|
|
}
|
|
getPadding() {
|
|
const { padding: padding2 = 0 } = this;
|
|
return {
|
|
top: padding2,
|
|
right: padding2,
|
|
bottom: padding2,
|
|
left: padding2
|
|
};
|
|
}
|
|
getText() {
|
|
const isPlaceholder = this.text.length == 0;
|
|
const text2 = isPlaceholder ? this.placeholderText ?? "" : this.text;
|
|
return {
|
|
text: text2,
|
|
isPlaceholder
|
|
};
|
|
}
|
|
getTextInputCoords(context, _height) {
|
|
return convertPoint(this, context);
|
|
}
|
|
getTextPosition() {
|
|
return this.position;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TextualPointProperties.prototype, "text", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/comment/commentProperties.ts
|
|
var DEFAULT_COMMENT_PADDING = {
|
|
top: 8,
|
|
right: 14,
|
|
bottom: 8,
|
|
left: 14
|
|
};
|
|
var CommentProperties = class extends Fill(Stroke(TextualPointProperties)) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "comment" /* Comment */;
|
|
this.position = "bottom";
|
|
this.alignment = "left";
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "comment" /* Comment */;
|
|
}
|
|
getDefaultColor(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case `fill-color`:
|
|
return this.fill;
|
|
case `line-color`:
|
|
return this.stroke;
|
|
case `text-color`:
|
|
default:
|
|
return this.color;
|
|
}
|
|
}
|
|
getDefaultOpacity(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case `fill-color`:
|
|
return this.fillOpacity;
|
|
case `line-color`:
|
|
return this.strokeOpacity;
|
|
case `text-color`:
|
|
default:
|
|
return void 0;
|
|
}
|
|
}
|
|
getPlaceholderColor() {
|
|
const { r, g, b } = Color.fromString(this.color ?? "#888888");
|
|
return new Color(r, g, b, 0.66).toString();
|
|
}
|
|
getPadding() {
|
|
const { padding: padding2, fontSize } = this;
|
|
if (padding2 == null) {
|
|
return {
|
|
top: Math.max(fontSize * 0.4, DEFAULT_COMMENT_PADDING.top),
|
|
bottom: Math.max(fontSize * 0.4, DEFAULT_COMMENT_PADDING.bottom),
|
|
left: Math.max(fontSize * 0.8, DEFAULT_COMMENT_PADDING.left),
|
|
right: Math.max(fontSize * 0.8, DEFAULT_COMMENT_PADDING.right)
|
|
};
|
|
}
|
|
return {
|
|
top: padding2,
|
|
right: padding2,
|
|
bottom: padding2,
|
|
left: padding2
|
|
};
|
|
}
|
|
getTextInputCoords(context, height2) {
|
|
const coords = super.getTextInputCoords(context, height2);
|
|
const padding2 = this.getPadding();
|
|
return {
|
|
x: coords.x + padding2.left,
|
|
y: coords.y - padding2.bottom
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CommentProperties.prototype, "type", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/utils/line.ts
|
|
function getLineStyle(lineDash, lineStyle) {
|
|
return lineDash ? "dashed" : lineStyle ?? "solid";
|
|
}
|
|
function getComputedLineDash(strokeWidth, styleType) {
|
|
switch (styleType) {
|
|
case "solid":
|
|
return [];
|
|
case "dashed":
|
|
return [strokeWidth * 4, strokeWidth * 2];
|
|
case "dotted":
|
|
return [0, strokeWidth * 2];
|
|
}
|
|
}
|
|
function getLineDash(lineDash, computedLineDash, lineStyle, strokeWidth) {
|
|
const styleType = getLineStyle(lineDash, lineStyle);
|
|
return computedLineDash ?? lineDash ?? getComputedLineDash(strokeWidth ?? 1, styleType);
|
|
}
|
|
function getLineCap(lineCap, lineDash, lineStyle) {
|
|
const styleType = getLineStyle(lineDash, lineStyle);
|
|
return lineCap ?? styleType === "dotted" ? "round" : void 0;
|
|
}
|
|
function boundsIntersections(coords, bounds) {
|
|
const [p1, p2] = vector_exports.from(coords);
|
|
const reflection = bounds.height;
|
|
const gradient2 = vector_exports.gradient(p2, p1, reflection);
|
|
const intercept2 = vector_exports.intercept(p2, gradient2, reflection);
|
|
const fallback = [
|
|
{ x: p1.x, y: reflection ?? 0 },
|
|
{ x: p1.x, y: reflection == null ? bounds.height : reflection - bounds.height }
|
|
];
|
|
if (gradient2 === Infinity) {
|
|
return fallback;
|
|
}
|
|
let points = [
|
|
vector_exports.intersectAtY(gradient2, intercept2, 0, reflection),
|
|
vector_exports.intersectAtY(gradient2, intercept2, bounds.height, reflection),
|
|
vector_exports.intersectAtX(gradient2, intercept2, 0, reflection),
|
|
vector_exports.intersectAtX(gradient2, intercept2, bounds.width, reflection)
|
|
];
|
|
points = points.filter((p) => p.x >= bounds.x && p.x <= bounds.width && p.y >= bounds.y && p.y <= bounds.height).sort((a, b) => {
|
|
if (a.x === b.x)
|
|
return 0;
|
|
return a.x < b.x ? -1 : 1;
|
|
});
|
|
if (points.length !== 2) {
|
|
return fallback;
|
|
}
|
|
return points;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/measurer/measurerProperties.ts
|
|
var MeasurerStatisticsDivider = class extends Stroke(BaseProperties) {
|
|
};
|
|
var MeasurerStatistics = class extends Font(Fill(Stroke(BaseProperties))) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.divider = new MeasurerStatisticsDivider();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MeasurerStatistics.prototype, "divider", 2);
|
|
var MeasurerDirectionProperties = class extends Fill(Stroke(Handle(BaseProperties))) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.statistics = new MeasurerStatistics();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MeasurerDirectionProperties.prototype, "statistics", 2);
|
|
var MeasurerTypeProperties = class extends Localisable(Background2(Stroke(LineStyle(StartEndProperties)))) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.direction = "both";
|
|
this.hasDateRange = false;
|
|
this.hasPriceRange = false;
|
|
this.statistics = new MeasurerStatistics();
|
|
this.getVolume = () => void 0;
|
|
this.text = new LineTextProperties();
|
|
}
|
|
getDefaultColor(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case `fill-color`:
|
|
return this.background.fill;
|
|
case `line-color`:
|
|
return this.stroke;
|
|
case `text-color`:
|
|
return this.text.color;
|
|
}
|
|
}
|
|
getDefaultOpacity(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case `fill-color`:
|
|
return this.background.fillOpacity;
|
|
case `line-color`:
|
|
return this.strokeOpacity;
|
|
}
|
|
}
|
|
getLineDash() {
|
|
return getLineDash(this.lineDash, this.computedLineDash, this.lineStyle, this.strokeWidth);
|
|
}
|
|
getLineCap() {
|
|
return getLineCap(this.lineCap, this.lineDash, this.lineStyle);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MeasurerTypeProperties.prototype, "statistics", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MeasurerTypeProperties.prototype, "text", 2);
|
|
function DateRange(Parent) {
|
|
class DateRangeInternal extends Parent {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.hasDateRange = true;
|
|
}
|
|
}
|
|
return DateRangeInternal;
|
|
}
|
|
function PriceRange(Parent) {
|
|
class PriceRangeInternal extends Parent {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.hasPriceRange = true;
|
|
}
|
|
}
|
|
return PriceRangeInternal;
|
|
}
|
|
var DateRangeProperties = class extends DateRange(MeasurerTypeProperties) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "date-range" /* DateRange */;
|
|
this.direction = "horizontal";
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "date-range" /* DateRange */;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DateRangeProperties.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DateRangeProperties.prototype, "extendAbove", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DateRangeProperties.prototype, "extendBelow", 2);
|
|
var PriceRangeProperties = class extends PriceRange(MeasurerTypeProperties) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "price-range" /* PriceRange */;
|
|
this.direction = "vertical";
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "price-range" /* PriceRange */;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PriceRangeProperties.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PriceRangeProperties.prototype, "extendLeft", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PriceRangeProperties.prototype, "extendRight", 2);
|
|
var DatePriceRangeProperties = class extends DateRange(PriceRange(MeasurerTypeProperties)) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "date-price-range" /* DatePriceRange */;
|
|
this.direction = "both";
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "date-price-range" /* DatePriceRange */;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DatePriceRangeProperties.prototype, "type", 2);
|
|
var QuickDatePriceRangeProperties = class extends DateRange(PriceRange(MeasurerTypeProperties)) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "quick-date-price-range" /* QuickDatePriceRange */;
|
|
this.up = new MeasurerDirectionProperties();
|
|
this.down = new MeasurerDirectionProperties();
|
|
this.direction = "both";
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "quick-date-price-range" /* QuickDatePriceRange */;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], QuickDatePriceRangeProperties.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], QuickDatePriceRangeProperties.prototype, "up", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], QuickDatePriceRangeProperties.prototype, "down", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/text/util.ts
|
|
var { BBox: BBox9 } = module_support_exports;
|
|
var ANNOTATION_TEXT_LINE_HEIGHT = 1.38;
|
|
function maybeWrapText(options, text2, maxWidth) {
|
|
return maxWidth ? wrapText(text2, { maxWidth, font: options, textWrap: "always", avoidOrphans: false }) : text2;
|
|
}
|
|
function measureAnnotationText(options, text2) {
|
|
const { lineMetrics, width: width2 } = cachedTextMeasurer(options).measureLines(text2);
|
|
const height2 = lineMetrics.length * calcLineHeight(options.fontSize, ANNOTATION_TEXT_LINE_HEIGHT);
|
|
return { width: width2, height: height2 };
|
|
}
|
|
function getBBox(options, text2, coords, bbox) {
|
|
let width2 = bbox?.width ?? 0;
|
|
let height2 = bbox?.height ?? 0;
|
|
if (!bbox) {
|
|
const wrappedText = options.width == null ? text2 : maybeWrapText(options, text2, options.width);
|
|
({ width: width2, height: height2 } = measureAnnotationText(options, wrappedText));
|
|
}
|
|
return new BBox9(coords.x, coords.y, width2, height2);
|
|
}
|
|
function updateTextNode(node, text2, isPlaceholder, config, { x, y }, textBaseline) {
|
|
const { visible = true, fontFamily, fontSize = 14, fontStyle, fontWeight: fontWeight2, textAlign } = config;
|
|
const lineHeight = calcLineHeight(fontSize, ANNOTATION_TEXT_LINE_HEIGHT);
|
|
textBaseline ?? (textBaseline = config.position == "center" ? "middle" : config.position);
|
|
const fill = isPlaceholder ? config.getPlaceholderColor() : config.color;
|
|
node.setProperties({
|
|
x,
|
|
y,
|
|
visible,
|
|
text: text2,
|
|
fill,
|
|
fontFamily,
|
|
fontSize,
|
|
fontStyle,
|
|
fontWeight: fontWeight2,
|
|
textAlign,
|
|
lineHeight,
|
|
textBaseline
|
|
});
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/note/noteProperties.ts
|
|
var DEFAULT_NOTE_PADDING = 10;
|
|
var HANDLE_SIZE = 11;
|
|
var ICON_HEIGHT = 20;
|
|
var ICON_WIDTH = 22;
|
|
var ICON_SPACING = 10;
|
|
var LABEL_OFFSET = ICON_HEIGHT + ICON_SPACING;
|
|
var TOOLBAR_OFFSET = 34;
|
|
var NoteBackgroundProperties = class extends Fill(Stroke(BaseProperties)) {
|
|
};
|
|
var NoteProperties = class extends Fill(Stroke(TextualPointProperties)) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "note" /* Note */;
|
|
this.background = new NoteBackgroundProperties();
|
|
this.position = "bottom";
|
|
this.alignment = "center";
|
|
this.width = 200;
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "note" /* Note */;
|
|
}
|
|
getDefaultColor(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case `line-color`:
|
|
return this.fill;
|
|
case `text-color`:
|
|
return this.color;
|
|
}
|
|
}
|
|
getDefaultOpacity(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case `line-color`:
|
|
return this.fillOpacity;
|
|
case `text-color`:
|
|
return void 0;
|
|
}
|
|
}
|
|
getPadding() {
|
|
const padding2 = this.padding ?? DEFAULT_NOTE_PADDING;
|
|
return {
|
|
top: padding2,
|
|
right: padding2,
|
|
bottom: padding2,
|
|
left: padding2
|
|
};
|
|
}
|
|
getTextInputCoords(context, height2) {
|
|
const { width: width2, text: text2 } = this;
|
|
const textInputCoords = super.getTextInputCoords(context, height2);
|
|
const padding2 = this.getPadding().top;
|
|
const bbox = getBBox(this, text2, textInputCoords);
|
|
bbox.x = clamp(width2 / 2, bbox.x, context.seriesRect.width - width2 / 2);
|
|
const topY = bbox.y - LABEL_OFFSET - padding2 * 2;
|
|
const bottomY = bbox.y + HANDLE_SIZE + padding2 * 2;
|
|
const textHeight = Math.max(bbox.height, height2);
|
|
if (topY - textHeight - TOOLBAR_OFFSET < 0) {
|
|
bbox.y = bottomY;
|
|
this.position = "top";
|
|
} else {
|
|
bbox.y = topY + padding2;
|
|
this.position = "bottom";
|
|
}
|
|
return {
|
|
x: bbox.x,
|
|
y: bbox.y
|
|
};
|
|
}
|
|
isHoverable() {
|
|
return true;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], NoteProperties.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], NoteProperties.prototype, "background", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/properties/pointProperties.ts
|
|
var PointProperties2 = class extends Annotation(Point(Handle(BaseProperties))) {
|
|
getDefaultColor(_colorPickerType) {
|
|
return void 0;
|
|
}
|
|
getDefaultOpacity(_colorPickerType) {
|
|
return void 0;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/properties/shapePointProperties.ts
|
|
var ShapePointProperties = class _ShapePointProperties extends Fill(PointProperties2) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.size = 32;
|
|
}
|
|
static is(value) {
|
|
return value instanceof _ShapePointProperties;
|
|
}
|
|
getDefaultColor(colorPickerType) {
|
|
return colorPickerType === `fill-color` ? this.fill : void 0;
|
|
}
|
|
getDefaultOpacity(colorPickerType) {
|
|
return colorPickerType === `fill-color` ? this.fillOpacity : void 0;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/cross-line/crossLineProperties.ts
|
|
var HorizontalLineProperties = class extends Annotation(Value(Handle(AxisLabel2(Stroke(LineStyle(BaseProperties)))))) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.direction = "horizontal";
|
|
this.type = "horizontal-line" /* HorizontalLine */;
|
|
this.text = new LineTextProperties();
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "horizontal-line" /* HorizontalLine */;
|
|
}
|
|
getDefaultColor() {
|
|
return this.stroke;
|
|
}
|
|
getDefaultOpacity() {
|
|
return this.strokeOpacity;
|
|
}
|
|
getLineDash() {
|
|
return getLineDash(this.lineDash, this.computedLineDash, this.lineStyle, this.strokeWidth);
|
|
}
|
|
getLineCap() {
|
|
return getLineCap(this.lineCap, this.lineDash, this.lineStyle);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HorizontalLineProperties.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HorizontalLineProperties.prototype, "text", 2);
|
|
var VerticalLineProperties = class extends Annotation(Value(Handle(AxisLabel2(Stroke(LineStyle(BaseProperties)))))) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.direction = "vertical";
|
|
this.type = "vertical-line" /* VerticalLine */;
|
|
this.text = new LineTextProperties();
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "vertical-line" /* VerticalLine */;
|
|
}
|
|
getDefaultColor() {
|
|
return this.stroke;
|
|
}
|
|
getDefaultOpacity() {
|
|
return this.strokeOpacity;
|
|
}
|
|
getLineDash() {
|
|
return getLineDash(this.lineDash, this.computedLineDash, this.lineStyle, this.strokeWidth);
|
|
}
|
|
getLineCap() {
|
|
return getLineCap(this.lineCap, this.lineDash, this.lineStyle);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], VerticalLineProperties.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], VerticalLineProperties.prototype, "text", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/disjoint-channel/disjointChannelProperties.ts
|
|
var DisjointChannelProperties = class extends Annotation(
|
|
Background2(Line2(Handle(Extendable(Stroke(LineStyle(BaseProperties))))))
|
|
) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "disjoint-channel" /* DisjointChannel */;
|
|
this.text = new ChannelTextProperties();
|
|
this.snapToAngle = 45;
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "disjoint-channel" /* DisjointChannel */;
|
|
}
|
|
get bottom() {
|
|
const bottom = {
|
|
start: { x: this.start.x, y: this.start.y },
|
|
end: { x: this.end.x, y: this.end.y }
|
|
};
|
|
if (typeof bottom.start.y === "number" && typeof bottom.end.y === "number") {
|
|
bottom.start.y -= this.startHeight;
|
|
bottom.end.y -= this.endHeight;
|
|
} else {
|
|
logger_exports.warnOnce(`Annotation [${this.type}] can only be used with a numeric y-axis.`);
|
|
}
|
|
return bottom;
|
|
}
|
|
getDefaultColor(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case `fill-color`:
|
|
return this.background.fill;
|
|
case `line-color`:
|
|
return this.stroke;
|
|
case "text-color":
|
|
return this.text.color;
|
|
}
|
|
}
|
|
getDefaultOpacity(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case `fill-color`:
|
|
return this.background.fillOpacity;
|
|
case `line-color`:
|
|
return this.strokeOpacity;
|
|
}
|
|
}
|
|
getLineDash() {
|
|
return getLineDash(this.lineDash, this.computedLineDash, this.lineStyle, this.strokeWidth);
|
|
}
|
|
getLineCap() {
|
|
return getLineCap(this.lineCap, this.lineDash, this.lineStyle);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DisjointChannelProperties.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DisjointChannelProperties.prototype, "startHeight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DisjointChannelProperties.prototype, "endHeight", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], DisjointChannelProperties.prototype, "text", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/line/lineProperties.ts
|
|
var LineTypeProperties = class extends Localisable(
|
|
Cappable(Extendable(Stroke(LineStyle(StartEndProperties))))
|
|
) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.text = new LineTextProperties();
|
|
}
|
|
getDefaultColor(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case "line-color":
|
|
return this.stroke;
|
|
case "text-color":
|
|
return this.text.color;
|
|
}
|
|
}
|
|
getDefaultOpacity() {
|
|
return this.strokeOpacity;
|
|
}
|
|
getLineDash() {
|
|
return getLineDash(this.lineDash, this.computedLineDash, this.lineStyle, this.strokeWidth);
|
|
}
|
|
getLineCap() {
|
|
return getLineCap(this.lineCap, this.lineDash, this.lineStyle);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineTypeProperties.prototype, "text", 2);
|
|
var ArrowProperties = class extends LineTypeProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "arrow" /* Arrow */;
|
|
this.endCap = "arrow";
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "arrow" /* Arrow */;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ArrowProperties.prototype, "type", 2);
|
|
var LineProperties = class extends LineTypeProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "line" /* Line */;
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "line" /* Line */;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LineProperties.prototype, "type", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/properties/fibonacciProperties.ts
|
|
var FibonacciProperties = class extends LineTypeProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.label = new LabelTextProperties();
|
|
this.reverse = false;
|
|
this.showFill = true;
|
|
this.isMultiColor = true;
|
|
this.strokes = [];
|
|
this.bands = 10;
|
|
}
|
|
getDefaultColor(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case "line-color":
|
|
return this.rangeStroke ?? this.stroke;
|
|
case "text-color":
|
|
return this.text.color;
|
|
}
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FibonacciProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FibonacciProperties.prototype, "reverse", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FibonacciProperties.prototype, "showFill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FibonacciProperties.prototype, "isMultiColor", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FibonacciProperties.prototype, "strokes", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FibonacciProperties.prototype, "rangeStroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FibonacciProperties.prototype, "bands", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/fibonacci-retracement-trend-based/fibonacciRetracementTrendBasedProperties.ts
|
|
var FibonacciRetracementTrendBasedProperties = class extends FibonacciProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "fibonacci-retracement-trend-based" /* FibonacciRetracementTrendBased */;
|
|
this.endRetracement = new PointProperties();
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "fibonacci-retracement-trend-based" /* FibonacciRetracementTrendBased */;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FibonacciRetracementTrendBasedProperties.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FibonacciRetracementTrendBasedProperties.prototype, "endRetracement", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/fibonacci-retracement/fibonacciRetracementProperties.ts
|
|
var FibonacciRetracementProperties = class extends FibonacciProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "fibonacci-retracement" /* FibonacciRetracement */;
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "fibonacci-retracement" /* FibonacciRetracement */;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FibonacciRetracementProperties.prototype, "type", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/parallel-channel/parallelChannelProperties.ts
|
|
var ParallelChannelProperties = class extends Annotation(
|
|
Background2(Line2(Handle(Extendable(Stroke(LineStyle(BaseProperties))))))
|
|
) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "parallel-channel" /* ParallelChannel */;
|
|
this.middle = new ChannelAnnotationMiddleProperties();
|
|
this.text = new ChannelTextProperties();
|
|
this.snapToAngle = 45;
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "parallel-channel" /* ParallelChannel */;
|
|
}
|
|
get bottom() {
|
|
const bottom = {
|
|
start: { x: this.start.x, y: this.start.y },
|
|
end: { x: this.end.x, y: this.end.y }
|
|
};
|
|
if (typeof bottom.start.y === "number" && typeof bottom.end.y === "number") {
|
|
bottom.start.y -= this.height;
|
|
bottom.end.y -= this.height;
|
|
} else {
|
|
logger_exports.warnOnce(`Annotation [${this.type}] can only be used with a numeric y-axis.`);
|
|
}
|
|
return bottom;
|
|
}
|
|
getDefaultColor(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case `fill-color`:
|
|
return this.background.fill;
|
|
case `line-color`:
|
|
return this.stroke;
|
|
case "text-color":
|
|
return this.text.color;
|
|
}
|
|
}
|
|
getDefaultOpacity(colorPickerType) {
|
|
switch (colorPickerType) {
|
|
case `fill-color`:
|
|
return this.background.fillOpacity;
|
|
case `line-color`:
|
|
return this.strokeOpacity;
|
|
}
|
|
}
|
|
getLineDash() {
|
|
return getLineDash(this.lineDash, this.computedLineDash, this.lineStyle, this.strokeWidth);
|
|
}
|
|
getLineCap() {
|
|
return getLineCap(this.lineCap, this.lineDash, this.lineStyle);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ParallelChannelProperties.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ParallelChannelProperties.prototype, "height", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ParallelChannelProperties.prototype, "middle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ParallelChannelProperties.prototype, "text", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/text/textProperties.ts
|
|
var TextProperties = class extends TextualPointProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "text" /* Text */;
|
|
this.position = "bottom";
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "text" /* Text */;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TextProperties.prototype, "type", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/utils/types.ts
|
|
function isEphemeralType(datum) {
|
|
return QuickDatePriceRangeProperties.is(datum);
|
|
}
|
|
function isLineType(datum) {
|
|
return LineProperties.is(datum) || HorizontalLineProperties.is(datum) || VerticalLineProperties.is(datum) || ArrowProperties.is(datum) || isFibonacciType(datum);
|
|
}
|
|
function isChannelType(datum) {
|
|
return DisjointChannelProperties.is(datum) || ParallelChannelProperties.is(datum);
|
|
}
|
|
function isFibonacciType(datum) {
|
|
return FibonacciRetracementProperties.is(datum) || FibonacciRetracementTrendBasedProperties.is(datum);
|
|
}
|
|
function isTextType(datum) {
|
|
return CalloutProperties.is(datum) || CommentProperties.is(datum) || NoteProperties.is(datum) || TextProperties.is(datum);
|
|
}
|
|
function isMeasurerType(datum) {
|
|
return DateRangeProperties.is(datum) || PriceRangeProperties.is(datum) || DatePriceRangeProperties.is(datum) || QuickDatePriceRangeProperties.is(datum);
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/utils/has.ts
|
|
function hasFontSize(datum) {
|
|
return isTextType(datum) && !NoteProperties.is(datum);
|
|
}
|
|
function hasLineStyle(datum) {
|
|
return isLineType(datum) || isChannelType(datum) || isMeasurerType(datum) && !QuickDatePriceRangeProperties.is(datum);
|
|
}
|
|
function hasLineColor(datum) {
|
|
return isLineType(datum) || isChannelType(datum) || isMeasurerType(datum) || CalloutProperties.is(datum) || NoteProperties.is(datum);
|
|
}
|
|
function hasIconColor(datum) {
|
|
return NoteProperties.is(datum);
|
|
}
|
|
function hasFillColor(datum) {
|
|
return isChannelType(datum) || isMeasurerType(datum) || CalloutProperties.is(datum) || CommentProperties.is(datum) || ShapePointProperties.is(datum);
|
|
}
|
|
function hasTextColor(datum) {
|
|
return isTextType(datum) && !NoteProperties.is(datum);
|
|
}
|
|
function hasLineText(datum) {
|
|
return (isLineType(datum) || isChannelType(datum) || isMeasurerType(datum)) && !isEphemeralType(datum) && isObject(datum.text);
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/utils/styles.ts
|
|
function setFontSize(datum, fontSize) {
|
|
if ("fontSize" in datum)
|
|
datum.fontSize = fontSize;
|
|
if (hasLineText(datum))
|
|
datum.text.fontSize = fontSize;
|
|
}
|
|
function setLineStyle(datum, style2) {
|
|
const strokeWidth = style2?.strokeWidth ?? datum.strokeWidth ?? 1;
|
|
const lineType = style2?.type ?? datum.lineStyle;
|
|
const lineStyle = lineType ?? getLineStyle(datum.lineDash, lineType);
|
|
const computedLineDash = getComputedLineDash(strokeWidth, lineStyle);
|
|
datum.strokeWidth = strokeWidth;
|
|
datum.computedLineDash = computedLineDash;
|
|
datum.lineStyle = lineStyle;
|
|
datum.lineCap = lineStyle === "dotted" ? "round" : void 0;
|
|
}
|
|
function setColor(datum, colorPickerType, colorOpacity, color2, opacity, isMultiColor) {
|
|
switch (colorPickerType) {
|
|
case `fill-color`: {
|
|
if ("fill" in datum)
|
|
datum.fill = color2;
|
|
if ("fillOpacity" in datum)
|
|
datum.fillOpacity = opacity;
|
|
if ("background" in datum) {
|
|
datum.background.fill = color2;
|
|
datum.background.fillOpacity = opacity;
|
|
}
|
|
break;
|
|
}
|
|
case `line-color`: {
|
|
if ("axisLabel" in datum) {
|
|
datum.axisLabel.fill = color2;
|
|
datum.axisLabel.fillOpacity = opacity;
|
|
datum.axisLabel.stroke = color2;
|
|
datum.axisLabel.strokeOpacity = opacity;
|
|
}
|
|
if ("fill" in datum && "fillOpacity" in datum && hasIconColor(datum)) {
|
|
datum.fill = color2;
|
|
datum.fillOpacity = opacity;
|
|
} else {
|
|
if ("strokeOpacity" in datum)
|
|
datum.strokeOpacity = opacity;
|
|
if ("isMultiColor" in datum && "rangeStroke" in datum) {
|
|
datum.isMultiColor = isMultiColor;
|
|
datum.rangeStroke = color2;
|
|
} else if ("stroke" in datum) {
|
|
datum.stroke = color2;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case `text-color`: {
|
|
if ("color" in datum)
|
|
datum.color = colorOpacity;
|
|
if (hasLineText(datum))
|
|
datum.text.color = color2;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/annotationDefaults.ts
|
|
var AnnotationDefaults = class {
|
|
constructor() {
|
|
this.mementoOriginatorKey = "annotation-defaults";
|
|
this.colors = new Map(
|
|
Object.values(AnnotationType).map((type) => [
|
|
type,
|
|
/* @__PURE__ */ new Map([
|
|
["line-color", void 0],
|
|
["fill-color", void 0],
|
|
["text-color", void 0]
|
|
])
|
|
])
|
|
);
|
|
this.fontSizes = /* @__PURE__ */ new Map([
|
|
["callout" /* Callout */, void 0],
|
|
["comment" /* Comment */, void 0],
|
|
["text" /* Text */, void 0],
|
|
["arrow" /* Arrow */, void 0],
|
|
["line" /* Line */, void 0],
|
|
["disjoint-channel" /* DisjointChannel */, void 0],
|
|
["parallel-channel" /* ParallelChannel */, void 0],
|
|
["date-range" /* DateRange */, void 0],
|
|
["price-range" /* PriceRange */, void 0],
|
|
["date-price-range" /* DatePriceRange */, void 0]
|
|
]);
|
|
this.lineStyles = /* @__PURE__ */ new Map([
|
|
["line" /* Line */, void 0],
|
|
["horizontal-line" /* HorizontalLine */, void 0],
|
|
["vertical-line" /* VerticalLine */, void 0],
|
|
["disjoint-channel" /* DisjointChannel */, void 0],
|
|
["parallel-channel" /* ParallelChannel */, void 0],
|
|
["arrow" /* Arrow */, void 0],
|
|
["date-range" /* DateRange */, void 0],
|
|
["price-range" /* PriceRange */, void 0],
|
|
["date-price-range" /* DatePriceRange */, void 0]
|
|
]);
|
|
this.lineTextAlignments = /* @__PURE__ */ new Map([
|
|
["line" /* Line */, void 0],
|
|
["horizontal-line" /* HorizontalLine */, void 0],
|
|
["vertical-line" /* VerticalLine */, void 0],
|
|
["disjoint-channel" /* DisjointChannel */, void 0],
|
|
["parallel-channel" /* ParallelChannel */, void 0],
|
|
["arrow" /* Arrow */, void 0],
|
|
["date-range" /* DateRange */, void 0],
|
|
["price-range" /* PriceRange */, void 0],
|
|
["date-price-range" /* DatePriceRange */, void 0]
|
|
]);
|
|
this.lineTextPositions = /* @__PURE__ */ new Map([
|
|
["line" /* Line */, void 0],
|
|
["horizontal-line" /* HorizontalLine */, void 0],
|
|
["vertical-line" /* VerticalLine */, void 0],
|
|
["disjoint-channel" /* DisjointChannel */, void 0],
|
|
["parallel-channel" /* ParallelChannel */, void 0],
|
|
["arrow" /* Arrow */, void 0],
|
|
["date-range" /* DateRange */, void 0],
|
|
["price-range" /* PriceRange */, void 0],
|
|
["date-price-range" /* DatePriceRange */, void 0]
|
|
]);
|
|
this.fibonacciOptions = /* @__PURE__ */ new Map([
|
|
[
|
|
"fibonacci-retracement" /* FibonacciRetracement */,
|
|
{
|
|
bands: void 0,
|
|
reverse: void 0,
|
|
showFill: void 0
|
|
}
|
|
],
|
|
[
|
|
"fibonacci-retracement-trend-based" /* FibonacciRetracementTrendBased */,
|
|
{
|
|
bands: void 0,
|
|
reverse: void 0,
|
|
showFill: void 0
|
|
}
|
|
]
|
|
]);
|
|
}
|
|
createMemento() {
|
|
return {
|
|
colors: deepClone(this.colors),
|
|
fontSizes: deepClone(this.fontSizes),
|
|
lineStyles: deepClone(this.lineStyles),
|
|
lineTextAlignments: deepClone(this.lineTextAlignments),
|
|
lineTextPositions: deepClone(this.lineTextPositions),
|
|
fibonacciOptions: deepClone(this.fibonacciOptions)
|
|
};
|
|
}
|
|
guardMemento(_blob) {
|
|
return true;
|
|
}
|
|
restoreMemento(_version, _mementoVersion, blob) {
|
|
this.colors = deepClone(blob.colors);
|
|
this.fontSizes = deepClone(blob.fontSizes);
|
|
this.lineStyles = deepClone(blob.lineStyles);
|
|
this.lineTextAlignments = deepClone(blob.lineTextAlignments);
|
|
this.lineTextPositions = deepClone(blob.lineTextPositions);
|
|
this.fibonacciOptions = deepClone(blob.fibonacciOptions);
|
|
}
|
|
setDefaultColor(type, colorType, colorOpacity, color2, opacity, isMultiColor) {
|
|
this.colors.get(type)?.set(colorType, [colorOpacity, color2, opacity, isMultiColor]);
|
|
}
|
|
setDefaultFontSize(type, fontSize) {
|
|
this.fontSizes.set(type, fontSize);
|
|
}
|
|
setDefaultLineStyleType(type, lineStyleType) {
|
|
const defaultStyle = this.lineStyles.get(type);
|
|
if (defaultStyle) {
|
|
defaultStyle.type = lineStyleType;
|
|
} else {
|
|
this.lineStyles.set(type, { type: lineStyleType });
|
|
}
|
|
}
|
|
setDefaultLineStyleWidth(type, strokeWidth) {
|
|
const defaultStyle = this.lineStyles.get(type);
|
|
if (defaultStyle) {
|
|
defaultStyle.strokeWidth = strokeWidth;
|
|
} else {
|
|
this.lineStyles.set(type, { strokeWidth });
|
|
}
|
|
}
|
|
setDefaultLineTextAlignment(type, alignment) {
|
|
this.lineTextAlignments.set(type, alignment);
|
|
}
|
|
setDefaultLineTextPosition(type, position) {
|
|
this.lineTextPositions.set(type, position);
|
|
}
|
|
setDefaultFibonacciOptions(type, key, value) {
|
|
if (type != "fibonacci-retracement" /* FibonacciRetracement */ && type != "fibonacci-retracement-trend-based" /* FibonacciRetracementTrendBased */)
|
|
return;
|
|
const options = this.fibonacciOptions.get(type);
|
|
options[key] = value;
|
|
this.fibonacciOptions.set(type, options);
|
|
}
|
|
applyDefaults(datum) {
|
|
for (const [annotationType, colors] of this.colors) {
|
|
if (datum.type !== annotationType)
|
|
continue;
|
|
for (const [colorPickerType, [colorOpacity, color2, opacity, isMultiColor] = []] of colors) {
|
|
if (colorOpacity && color2 && opacity != null && isMultiColor != null) {
|
|
setColor(datum, colorPickerType, colorOpacity, color2, opacity, isMultiColor);
|
|
}
|
|
}
|
|
}
|
|
for (const [annotationType, size] of this.fontSizes) {
|
|
if (datum.type !== annotationType || size == null)
|
|
continue;
|
|
setFontSize(datum, size);
|
|
}
|
|
for (const [annotationType, style2] of this.lineStyles) {
|
|
if (datum.type !== annotationType || style2 == null)
|
|
continue;
|
|
setLineStyle(datum, style2);
|
|
}
|
|
for (const [annotationType, position] of this.lineTextPositions) {
|
|
if (datum.type !== annotationType || position == null)
|
|
continue;
|
|
datum.text.position = position;
|
|
}
|
|
for (const [annotationType, alignment] of this.lineTextAlignments) {
|
|
if (datum.type !== annotationType || alignment == null)
|
|
continue;
|
|
datum.text.alignment = alignment;
|
|
}
|
|
for (const [annotationType, options] of this.fibonacciOptions) {
|
|
if (datum.type !== annotationType || options == null)
|
|
continue;
|
|
for (const option of Object.keys(options)) {
|
|
const value = options[option];
|
|
if (value == null) {
|
|
continue;
|
|
}
|
|
datum.set({ [option]: value });
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/components/color-picker/colorPickerTemplate.html
|
|
var colorPickerTemplate_default = '<div class="ag-charts-color-picker__palette" role="slider" tabindex="0"></div><div class="ag-charts-color-picker__color-row" role="presentation"><button class="ag-charts-color-picker__multi-color-button" tabindex="0" type="button" role="switch"></button> <input class="ag-charts-color-picker__hue-input" tabindex="0" type="range" min="0" max="360" value="0"></div><input class="ag-charts-color-picker__alpha-input" tabindex="0" type="range" min="0" max="1" value="1" step="0.01"> <label class="ag-charts-color-picker__color-field" role="presentation"><span class="ag-charts-color-picker__color-label" aria-hidden="true"></span> <input class="ag-charts-color-picker__color-input" tabindex="0" value="#000"></label>';
|
|
|
|
// packages/ag-charts-enterprise/src/components/color-picker/colorPicker.ts
|
|
var getHsva = (input) => {
|
|
try {
|
|
const color2 = Color.fromString(input);
|
|
const [h, s, v] = color2.toHSB();
|
|
return [h, s, v, color2.a];
|
|
} catch {
|
|
return;
|
|
}
|
|
};
|
|
var ColorPicker = class extends module_support_exports.AnchoredPopover {
|
|
constructor(ctx, options) {
|
|
super(ctx, "color-picker", options);
|
|
this.hasChanged = false;
|
|
this.hideFns.push(() => {
|
|
this.i18nUpdater = void 0;
|
|
if (this.hasChanged)
|
|
this.onChangeHide?.();
|
|
});
|
|
this.cleanup.register(this.ctx.eventsHub.on("locale:change", () => this.i18nUpdater?.()));
|
|
}
|
|
show(options) {
|
|
this.hasChanged = false;
|
|
this.onChangeHide = options.onChangeHide;
|
|
const { element: element2, initialFocus } = this.createColorPicker(options);
|
|
const popover = this.showWithChildren([element2], { initialFocus, ...options });
|
|
popover.classList.add("ag-charts-color-picker");
|
|
popover.setAttribute("role", "dialog");
|
|
}
|
|
createColorPicker(opts) {
|
|
const { localeManager } = this.ctx;
|
|
let isMultiColor = opts.isMultiColor ?? false;
|
|
let [h, s, v, a] = getHsva(opts.color ?? "#f00") ?? [0, 1, 0.5, 1];
|
|
a = opts.opacity ?? a;
|
|
const colorPicker = createElement("div", "ag-charts-color-picker__content");
|
|
colorPicker.innerHTML = colorPickerTemplate_default;
|
|
colorPicker.ariaLabel = this.ctx.localeManager.t("ariaLabelColorPicker");
|
|
const paletteInput = colorPicker.querySelector(".ag-charts-color-picker__palette");
|
|
const hueInput = colorPicker.querySelector(".ag-charts-color-picker__hue-input");
|
|
const multiColorButton = colorPicker.querySelector(
|
|
".ag-charts-color-picker__multi-color-button"
|
|
);
|
|
const alphaInput = colorPicker.querySelector(".ag-charts-color-picker__alpha-input");
|
|
const colorInput = colorPicker.querySelector(".ag-charts-color-picker__color-input");
|
|
const colorInputLabel = colorPicker.querySelector(".ag-charts-color-picker__color-label");
|
|
const updatePaletteInputAriaValue = (first2) => {
|
|
const key = { s: "ariaValueColorPalette", v: "ariaValueColorPaletteFirstV" }[first2];
|
|
paletteInput.ariaValueText = localeManager.t(key, { s, v });
|
|
};
|
|
this.i18nUpdater = () => {
|
|
paletteInput.ariaRoleDescription = localeManager.t("ariaRoleDescription2DSlider");
|
|
paletteInput.ariaLabel = localeManager.t("ariaLabelColorPickerPalette");
|
|
hueInput.ariaLabel = localeManager.t("ariaLabelColorPickerHue");
|
|
multiColorButton.ariaLabel = localeManager.t("ariaLabelColorPickerMultiColor");
|
|
alphaInput.ariaLabel = localeManager.t("ariaLabelColorPickerAlpha");
|
|
colorInput.ariaLabel = localeManager.t("ariaLabelColor");
|
|
updatePaletteInputAriaValue("s");
|
|
};
|
|
this.i18nUpdater();
|
|
multiColorButton.classList.toggle(
|
|
"ag-charts-color-picker__multi-color-button--hidden",
|
|
!opts.hasMultiColorOption
|
|
);
|
|
const update = (trackChange = true) => {
|
|
const color2 = Color.fromHSB(h, s, v, a);
|
|
const colorString = color2.toHexString();
|
|
colorPicker.style.setProperty("--h", `${h}`);
|
|
colorPicker.style.setProperty("--s", `${s}`);
|
|
colorPicker.style.setProperty("--v", `${v}`);
|
|
colorPicker.style.setProperty("--a", `${a}`);
|
|
colorPicker.style.setProperty("--color", colorString.slice(0, 7));
|
|
colorPicker.style.setProperty("--color-a", colorString);
|
|
hueInput.value = `${h}`;
|
|
alphaInput.value = `${a}`;
|
|
alphaInput.classList.toggle("ag-charts-color-picker__alpha-input--opaque", a === 1);
|
|
multiColorButton.classList.toggle("ag-charts-color-picker__multi-color-button--active", isMultiColor);
|
|
colorInputLabel.classList.toggle("ag-charts-color-picker__color-label--multi-color", isMultiColor);
|
|
if (document.activeElement !== colorInput) {
|
|
multiColorButton.ariaChecked = isMultiColor.toString();
|
|
colorInput.value = isMultiColor ? localeManager.t("ariaLabelColorPickerMultiColor") : colorString.toUpperCase();
|
|
}
|
|
if (trackChange || opts.color == null) {
|
|
const plainColor = Color.fromHSB(h, s, v, 1).toHexString();
|
|
opts.onChange?.(colorString, plainColor, a, isMultiColor);
|
|
}
|
|
if (trackChange)
|
|
this.hasChanged = true;
|
|
};
|
|
update(false);
|
|
const preventDefault = (event) => event.preventDefault();
|
|
const stopPropagation = (event) => event.stopPropagation();
|
|
const beginPaletteInteraction = (e) => {
|
|
e.preventDefault();
|
|
const currentTarget = e.currentTarget;
|
|
currentTarget.focus();
|
|
const rect2 = currentTarget.getBoundingClientRect();
|
|
const pointerMove = ({ clientX, clientY }) => {
|
|
isMultiColor = false;
|
|
s = Math.min(Math.max((clientX - rect2.left) / rect2.width, 0), 1);
|
|
v = 1 - Math.min(Math.max((clientY - rect2.top) / rect2.height, 0), 1);
|
|
update();
|
|
updatePaletteInputAriaValue("s");
|
|
};
|
|
pointerMove(e);
|
|
const pointerUp = attachListener(getWindow(), "pointermove", pointerMove);
|
|
getWindow().addEventListener("pointerup", pointerUp, { once: true });
|
|
};
|
|
colorPicker.addEventListener("mousedown", stopPropagation);
|
|
colorPicker.addEventListener("touchstart", stopPropagation);
|
|
colorPicker.addEventListener("touchmove", stopPropagation);
|
|
colorPicker.addEventListener("keydown", (e) => {
|
|
e.stopPropagation();
|
|
switch (e.key) {
|
|
case "Enter":
|
|
case "Escape":
|
|
this.hide();
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
});
|
|
paletteInput.addEventListener("pointerdown", beginPaletteInteraction);
|
|
paletteInput.addEventListener("touchstart", preventDefault, { passive: false });
|
|
paletteInput.addEventListener("touchmove", preventDefault, { passive: false });
|
|
paletteInput.addEventListener("keydown", (e) => {
|
|
if (e.key === "ArrowLeft") {
|
|
s = clamp(0, s - 0.01, 1);
|
|
updatePaletteInputAriaValue("s");
|
|
} else if (e.key === "ArrowRight") {
|
|
s = clamp(0, s + 0.01, 1);
|
|
updatePaletteInputAriaValue("s");
|
|
} else if (e.key === "ArrowUp") {
|
|
v = clamp(0, v + 0.01, 1);
|
|
updatePaletteInputAriaValue("v");
|
|
} else if (e.key === "ArrowDown") {
|
|
v = clamp(0, v - 0.01, 1);
|
|
updatePaletteInputAriaValue("v");
|
|
} else {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
update();
|
|
});
|
|
paletteInput.addEventListener("focus", () => {
|
|
updatePaletteInputAriaValue("s");
|
|
});
|
|
multiColorButton.addEventListener("click", () => {
|
|
isMultiColor = !isMultiColor;
|
|
update();
|
|
});
|
|
hueInput.addEventListener("input", (e) => {
|
|
isMultiColor = false;
|
|
h = e.currentTarget.valueAsNumber ?? 0;
|
|
update();
|
|
});
|
|
alphaInput.addEventListener("input", (e) => {
|
|
isMultiColor = false;
|
|
a = e.currentTarget.valueAsNumber ?? 0;
|
|
update();
|
|
});
|
|
colorInput.addEventListener("input", (e) => {
|
|
isMultiColor = false;
|
|
const hsva = getHsva(e.currentTarget.value);
|
|
if (hsva == null)
|
|
return;
|
|
[h, s, v, a] = hsva;
|
|
update();
|
|
});
|
|
colorInput.addEventListener("blur", () => update());
|
|
colorInput.addEventListener("keydown", (e) => {
|
|
if (e.key === "Enter") {
|
|
e.currentTarget.blur();
|
|
update();
|
|
}
|
|
});
|
|
return { element: colorPicker, initialFocus: paletteInput };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/toolbar/buttonProperties.ts
|
|
var ToolbarButtonProperties = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ToolbarButtonProperties.prototype, "icon", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ToolbarButtonProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ToolbarButtonProperties.prototype, "ariaLabel", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ToolbarButtonProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/annotationsMenuOptions.ts
|
|
function channelMenuItemVisible(scale2) {
|
|
return !(scale2 instanceof module_support_exports.LogScale) && !(scale2 instanceof module_support_exports.BandScale);
|
|
}
|
|
var LINE_ANNOTATION_ITEMS = [
|
|
{
|
|
label: "toolbarAnnotationsTrendLine",
|
|
icon: "trend-line-drawing",
|
|
value: "line" /* Line */
|
|
},
|
|
{
|
|
label: "toolbarAnnotationsHorizontalLine",
|
|
icon: "horizontal-line-drawing",
|
|
value: "horizontal-line" /* HorizontalLine */
|
|
},
|
|
{
|
|
label: "toolbarAnnotationsVerticalLine",
|
|
icon: "vertical-line-drawing",
|
|
value: "vertical-line" /* VerticalLine */
|
|
},
|
|
{
|
|
label: "toolbarAnnotationsParallelChannel",
|
|
icon: "parallel-channel-drawing",
|
|
value: "parallel-channel" /* ParallelChannel */,
|
|
visible: channelMenuItemVisible
|
|
},
|
|
{
|
|
label: "toolbarAnnotationsDisjointChannel",
|
|
icon: "disjoint-channel-drawing",
|
|
value: "disjoint-channel" /* DisjointChannel */,
|
|
visible: channelMenuItemVisible
|
|
}
|
|
];
|
|
var FIBONACCI_ANNOTATION_ITEMS = [
|
|
{
|
|
label: "toolbarAnnotationsFibonacciRetracement",
|
|
icon: "fibonacci-retracement-drawing",
|
|
value: "fibonacci-retracement" /* FibonacciRetracement */
|
|
},
|
|
{
|
|
label: "toolbarAnnotationsFibonacciRetracementTrendBased",
|
|
icon: "fibonacci-retracement-trend-based-drawing",
|
|
value: "fibonacci-retracement-trend-based" /* FibonacciRetracementTrendBased */
|
|
}
|
|
];
|
|
var FIBONACCI_RATIO_ITEMS = [
|
|
{ label: "Fibonacci - Extended", value: 10 },
|
|
{ label: "Fibonacci - 6 Band", value: 6 },
|
|
{ label: "Fibonacci - 4 Band", value: 4 }
|
|
];
|
|
var TEXT_ANNOTATION_ITEMS = [
|
|
{ label: "toolbarAnnotationsText", icon: "text-annotation", value: "text" /* Text */ },
|
|
{ label: "toolbarAnnotationsComment", icon: "comment-annotation", value: "comment" /* Comment */ },
|
|
{ label: "toolbarAnnotationsCallout", icon: "callout-annotation", value: "callout" /* Callout */ },
|
|
{ label: "toolbarAnnotationsNote", icon: "note-annotation", value: "note" /* Note */ }
|
|
];
|
|
var SHAPE_ANNOTATION_ITEMS = [
|
|
{ label: "toolbarAnnotationsArrow", icon: "arrow-drawing", value: "arrow" /* Arrow */ },
|
|
{ label: "toolbarAnnotationsArrowUp", icon: "arrow-up-drawing", value: "arrow-up" /* ArrowUp */ },
|
|
{ label: "toolbarAnnotationsArrowDown", icon: "arrow-down-drawing", value: "arrow-down" /* ArrowDown */ }
|
|
];
|
|
var MEASURER_ANNOTATION_ITEMS = [
|
|
{
|
|
label: "toolbarAnnotationsQuickDatePriceRange",
|
|
icon: "measurer-drawing",
|
|
value: "quick-date-price-range" /* QuickDatePriceRange */
|
|
},
|
|
{ label: "toolbarAnnotationsDateRange", icon: "date-range-drawing", value: "date-range" /* DateRange */ },
|
|
{ label: "toolbarAnnotationsPriceRange", icon: "price-range-drawing", value: "price-range" /* PriceRange */ },
|
|
{
|
|
label: "toolbarAnnotationsDatePriceRange",
|
|
icon: "date-price-range-drawing",
|
|
value: "date-price-range" /* DatePriceRange */
|
|
}
|
|
];
|
|
var LINE_STROKE_WIDTH_ITEMS = [
|
|
{ strokeWidth: 1, label: "1", value: 1 },
|
|
{ strokeWidth: 2, label: "2", value: 2 },
|
|
{ strokeWidth: 3, label: "3", value: 3 },
|
|
{ strokeWidth: 4, label: "4", value: 4 },
|
|
{ strokeWidth: 8, label: "8", value: 8 }
|
|
];
|
|
var LINE_STYLE_TYPE_ITEMS = [
|
|
{ icon: "line-style-solid", altText: "iconAltTextLineStyleSolid", value: "solid" },
|
|
{ icon: "line-style-dashed", altText: "iconAltTextLineStyleDashed", value: "dashed" },
|
|
{ icon: "line-style-dotted", altText: "iconAltTextLineStyleDotted", value: "dotted" }
|
|
];
|
|
var TEXT_SIZE_ITEMS = [
|
|
{ label: "10", value: 10 },
|
|
{ label: "12", value: 12 },
|
|
{ label: "14", value: 14 },
|
|
{ label: "16", value: 16 },
|
|
{ label: "18", value: 18 },
|
|
{ label: "22", value: 22 },
|
|
{ label: "28", value: 28 },
|
|
{ label: "36", value: 36 },
|
|
{ label: "46", value: 46 }
|
|
];
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/annotationOptionsToolbar.ts
|
|
var { FloatingToolbar: FloatingToolbar2, Menu: Menu2, ToolbarButtonWidget: ToolbarButtonWidget2 } = module_support_exports;
|
|
var AnnotationOptionsButtonProperties = class extends ToolbarButtonProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.checkedOverrides = new ToolbarButtonProperties();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AnnotationOptionsButtonProperties.prototype, "value", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AnnotationOptionsButtonProperties.prototype, "checkedOverrides", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AnnotationOptionsButtonProperties.prototype, "color", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AnnotationOptionsButtonProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AnnotationOptionsButtonProperties.prototype, "isMultiColor", 2);
|
|
var AnnotationOptionsButtonWidget = class extends ToolbarButtonWidget2 {
|
|
update(options) {
|
|
super.update(options);
|
|
if (options.value === "line-stroke-width" /* LineStrokeWidth */) {
|
|
this.updateLineStrokeWidth(options);
|
|
}
|
|
if (options.value === "fill-color" /* FillColor */ || options.value === "line-color" /* LineColor */ || options.value === "text-color" /* TextColor */) {
|
|
this.updateFillColor(options);
|
|
}
|
|
}
|
|
updateFillColor(options) {
|
|
const element2 = this.getElement();
|
|
element2.classList.add("ag-charts-annotations__color-picker-button");
|
|
element2.classList.toggle("ag-charts-annotations__color-picker-button--multi-color", options.isMultiColor);
|
|
element2.style.setProperty("--color", options.color ?? null);
|
|
}
|
|
updateLineStrokeWidth(options) {
|
|
const element2 = this.getElement();
|
|
element2.classList.add("ag-charts-annotations__stroke-width-button");
|
|
element2.style.setProperty("--stroke-width", `${options.strokeWidth}px`);
|
|
}
|
|
};
|
|
var FloatingAnnotationOptionsToolbar = class extends FloatingToolbar2 {
|
|
createButtonWidget() {
|
|
return new AnnotationOptionsButtonWidget(this.localeManager);
|
|
}
|
|
};
|
|
var AnnotationOptionsToolbar = class extends BaseProperties {
|
|
constructor(ctx, getActiveDatum) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.getActiveDatum = getActiveDatum;
|
|
this.enabled = true;
|
|
this.buttons = new PropertiesArray(AnnotationOptionsButtonProperties);
|
|
this.cleanup = new CleanupRegistry();
|
|
this.events = new EventEmitter();
|
|
this.visibleButtons = [];
|
|
this.toolbar = new FloatingAnnotationOptionsToolbar(
|
|
this.ctx,
|
|
"ariaLabelAnnotationOptionsToolbar",
|
|
"annotation-options"
|
|
);
|
|
this.colorPicker = new ColorPicker(this.ctx);
|
|
this.textSizeMenu = new Menu2(this.ctx, "text-size");
|
|
this.lineStyleTypeMenu = new Menu2(this.ctx, "annotations-line-style-type");
|
|
this.lineStrokeWidthMenu = new Menu2(this.ctx, "annotations-line-stroke-width");
|
|
this.cleanup.register(
|
|
this.toolbar.addToolbarListener("button-pressed", this.onButtonPress.bind(this)),
|
|
this.toolbar.addToolbarListener("toolbar-moved", this.onToolbarMoved.bind(this)),
|
|
ctx.widgets.seriesWidget.addListener("drag-start", this.onDragStart.bind(this)),
|
|
ctx.widgets.seriesWidget.addListener("drag-end", this.onDragEnd.bind(this)),
|
|
() => {
|
|
this.colorPicker.destroy();
|
|
this.toolbar.destroy();
|
|
}
|
|
);
|
|
}
|
|
onDragStart() {
|
|
this.toolbar.ignorePointerEvents();
|
|
}
|
|
onDragEnd() {
|
|
this.toolbar.capturePointerEvents();
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
}
|
|
show() {
|
|
if (!this.enabled)
|
|
return;
|
|
this.toolbar.show();
|
|
}
|
|
hide() {
|
|
this.toolbar.hide();
|
|
}
|
|
updateButtons(datum) {
|
|
if (!this.enabled)
|
|
return;
|
|
const visible = {
|
|
["line-style-type" /* LineStyleType */]: hasLineStyle(datum),
|
|
["line-stroke-width" /* LineStrokeWidth */]: hasLineStyle(datum),
|
|
["line-color" /* LineColor */]: hasLineColor(datum),
|
|
["text-color" /* TextColor */]: hasTextColor(datum),
|
|
["fill-color" /* FillColor */]: hasFillColor(datum),
|
|
["text-size" /* TextSize */]: hasFontSize(datum),
|
|
["settings" /* Settings */]: hasLineText(datum),
|
|
["lock" /* Lock */]: true,
|
|
["delete" /* Delete */]: true
|
|
};
|
|
this.visibleButtons = this.buttons.filter((button) => visible[button.value]);
|
|
this.toolbar.clearButtons();
|
|
this.toolbar.updateButtons(this.visibleButtons);
|
|
this.refreshButtons(datum);
|
|
}
|
|
setAnchorScene(scene) {
|
|
if (this.toolbar.hasBeenDragged())
|
|
return;
|
|
this.toolbar.setAnchor(scene.getAnchor());
|
|
}
|
|
hideOverlays() {
|
|
this.toolbar.clearActiveButton();
|
|
this.colorPicker.hide({ lastFocus: null });
|
|
this.textSizeMenu.hide();
|
|
this.lineStyleTypeMenu.hide();
|
|
this.lineStrokeWidthMenu.hide();
|
|
this.events.emit("hid-overlays", null);
|
|
}
|
|
clearActiveButton() {
|
|
this.toolbar.clearActiveButton();
|
|
}
|
|
updateColors(datum) {
|
|
this.updateColorPickerColor(
|
|
"line-color" /* LineColor */,
|
|
datum.getDefaultColor("line-color" /* LineColor */),
|
|
datum.getDefaultOpacity("line-color" /* LineColor */),
|
|
"isMultiColor" in datum && datum?.isMultiColor
|
|
);
|
|
this.updateColorPickerColor(
|
|
"fill-color" /* FillColor */,
|
|
datum.getDefaultColor("fill-color" /* FillColor */),
|
|
datum.getDefaultOpacity("fill-color" /* FillColor */),
|
|
"isMultiColor" in datum && datum?.isMultiColor
|
|
);
|
|
this.updateColorPickerColor(
|
|
"text-color" /* TextColor */,
|
|
datum.getDefaultColor("text-color" /* TextColor */),
|
|
datum.getDefaultOpacity("text-color" /* TextColor */),
|
|
"isMultiColor" in datum && datum?.isMultiColor
|
|
);
|
|
}
|
|
updateColorPickerColor(colorPickerType, color2, opacity, isMultiColor) {
|
|
if (color2 != null && opacity != null) {
|
|
const { r, g, b } = Color.fromString(color2);
|
|
color2 = Color.fromArray([r, g, b, opacity]).toHexString();
|
|
}
|
|
this.updateButtonByValue(colorPickerType, { color: color2, isMultiColor });
|
|
}
|
|
updateFontSize(fontSize) {
|
|
this.updateButtonByValue("text-size" /* TextSize */, {
|
|
label: fontSize == null ? void 0 : String(fontSize)
|
|
});
|
|
}
|
|
updateLineStyleType(item) {
|
|
this.updateButtonByValue("line-style-type" /* LineStyleType */, {
|
|
icon: item.icon
|
|
});
|
|
}
|
|
updateStrokeWidth(item) {
|
|
this.updateButtonByValue("line-stroke-width" /* LineStrokeWidth */, {
|
|
label: item.label,
|
|
strokeWidth: item.value
|
|
});
|
|
}
|
|
onButtonPress({
|
|
event,
|
|
button,
|
|
buttonWidget
|
|
}) {
|
|
const datum = this.getActiveDatum();
|
|
if (!datum)
|
|
return;
|
|
this.hideOverlays();
|
|
switch (button.value) {
|
|
case "line-style-type" /* LineStyleType */: {
|
|
const lineStyle = hasLineStyle(datum) ? getLineStyle(datum.lineDash, datum.lineStyle) : void 0;
|
|
this.lineStyleTypeMenu.show(buttonWidget, {
|
|
items: LINE_STYLE_TYPE_ITEMS,
|
|
ariaLabel: this.ctx.localeManager.t("toolbarAnnotationsLineStyle"),
|
|
value: lineStyle,
|
|
onPress: (item) => this.onLineStyleTypeMenuPress(item, datum),
|
|
class: "ag-charts-annotations__line-style-type-menu"
|
|
});
|
|
break;
|
|
}
|
|
case "line-stroke-width" /* LineStrokeWidth */: {
|
|
const strokeWidth = hasLineStyle(datum) ? datum.strokeWidth : void 0;
|
|
this.lineStrokeWidthMenu.show(buttonWidget, {
|
|
items: LINE_STROKE_WIDTH_ITEMS,
|
|
ariaLabel: this.ctx.localeManager.t("toolbarAnnotationsLineStrokeWidth"),
|
|
value: strokeWidth,
|
|
onPress: (item) => this.onLineStrokeWidthMenuPress(item, datum),
|
|
class: "ag-charts-annotations__line-stroke-width-menu"
|
|
});
|
|
break;
|
|
}
|
|
case "line-color" /* LineColor */:
|
|
case "fill-color" /* FillColor */:
|
|
case "text-color" /* TextColor */: {
|
|
this.toolbar.toggleActiveButtonByIndex(button.index);
|
|
this.colorPicker.show({
|
|
color: datum?.getDefaultColor(button.value),
|
|
opacity: datum?.getDefaultOpacity(button.value),
|
|
sourceEvent: event.sourceEvent,
|
|
hasMultiColorOption: "isMultiColor" in datum,
|
|
isMultiColor: "isMultiColor" in datum && datum?.isMultiColor,
|
|
onChange: datum == null ? void 0 : this.onColorPickerChange.bind(this, button.value, datum),
|
|
onChangeHide: ((type) => {
|
|
this.events.emit("saved-color", {
|
|
type: datum.type,
|
|
colorPickerType: button.value,
|
|
color: datum.getDefaultColor(type)
|
|
});
|
|
}).bind(this, button.value)
|
|
});
|
|
break;
|
|
}
|
|
case "text-size" /* TextSize */: {
|
|
const fontSize = isTextType(datum) ? datum.fontSize : void 0;
|
|
this.textSizeMenu.show(buttonWidget, {
|
|
items: TEXT_SIZE_ITEMS,
|
|
ariaLabel: this.ctx.localeManager.t("toolbarAnnotationsTextSize"),
|
|
value: fontSize,
|
|
onPress: (item) => this.onTextSizeMenuPress(item, datum),
|
|
class: "ag-charts-annotations__text-size-menu"
|
|
});
|
|
break;
|
|
}
|
|
case "delete" /* Delete */: {
|
|
this.events.emit("pressed-delete", null);
|
|
break;
|
|
}
|
|
case "lock" /* Lock */: {
|
|
datum.locked = !datum.locked;
|
|
this.refreshButtons(datum);
|
|
this.events.emit("pressed-lock", { locked: datum.locked });
|
|
break;
|
|
}
|
|
case "settings" /* Settings */: {
|
|
this.toolbar.toggleActiveButtonByIndex(button.index);
|
|
this.events.emit("pressed-settings", event);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
onToolbarMoved(event) {
|
|
const { buttonBounds, popoverBounds } = event;
|
|
const colorPickerAnchor = { x: popoverBounds.x, y: popoverBounds.y + popoverBounds.height + 4 };
|
|
const colorPickerFallbackAnchor = { y: popoverBounds.y - 4 };
|
|
this.colorPicker.setAnchor(colorPickerAnchor, colorPickerFallbackAnchor);
|
|
for (const [index, bounds] of buttonBounds.entries()) {
|
|
const button = this.visibleButtons.at(index);
|
|
if (!button)
|
|
continue;
|
|
const anchor = { x: bounds.x, y: bounds.y + bounds.height - 1 };
|
|
const fallbackAnchor = { y: bounds.y };
|
|
switch (button.value) {
|
|
case "line-stroke-width" /* LineStrokeWidth */:
|
|
this.lineStrokeWidthMenu.setAnchor(anchor, fallbackAnchor);
|
|
break;
|
|
case "line-style-type" /* LineStyleType */:
|
|
this.lineStyleTypeMenu.setAnchor(anchor, fallbackAnchor);
|
|
break;
|
|
case "text-size" /* TextSize */:
|
|
this.textSizeMenu.setAnchor(anchor, fallbackAnchor);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
onColorPickerChange(colorPickerType, datum, colorOpacity, color2, opacity, isMultiColor) {
|
|
this.events.emit("updated-color", {
|
|
type: datum.type,
|
|
colorPickerType,
|
|
colorOpacity,
|
|
color: color2,
|
|
opacity,
|
|
isMultiColor
|
|
});
|
|
this.updateColorPickerColor(colorPickerType, colorOpacity, opacity, isMultiColor);
|
|
}
|
|
onTextSizeMenuPress(item, datum) {
|
|
if (!hasFontSize(datum))
|
|
return;
|
|
const fontSize = item.value;
|
|
this.events.emit("updated-font-size", { type: datum.type, fontSize });
|
|
this.textSizeMenu.hide();
|
|
this.updateFontSize(fontSize);
|
|
}
|
|
onLineStyleTypeMenuPress(item, datum) {
|
|
if (!hasLineStyle(datum))
|
|
return;
|
|
const type = item.value;
|
|
this.events.emit("updated-line-style", { type: datum.type, lineStyleType: type });
|
|
this.lineStyleTypeMenu.hide();
|
|
this.updateLineStyleType(item);
|
|
}
|
|
onLineStrokeWidthMenuPress(item, datum) {
|
|
if (!hasLineStyle(datum)) {
|
|
return;
|
|
}
|
|
const strokeWidth = item.value;
|
|
this.events.emit("updated-line-width", { type: datum.type, strokeWidth });
|
|
this.lineStrokeWidthMenu.hide();
|
|
this.updateStrokeWidth(item);
|
|
}
|
|
refreshButtons(datum) {
|
|
const locked = datum.locked ?? false;
|
|
for (const [index, button] of this.visibleButtons.entries()) {
|
|
if (!button)
|
|
continue;
|
|
if (button.value === "lock" /* Lock */) {
|
|
this.toolbar.toggleSwitchCheckedByIndex(index, locked);
|
|
this.updateButtonByIndex(index, locked ? button.checkedOverrides.toJson() : button.toJson());
|
|
} else {
|
|
this.toolbar.toggleButtonEnabledByIndex(index, !locked);
|
|
}
|
|
}
|
|
if (hasFontSize(datum))
|
|
this.updateFontSize(datum.fontSize);
|
|
this.updateColors(datum);
|
|
this.updateLineStyles(datum);
|
|
}
|
|
updateLineStyles(datum) {
|
|
if (!hasLineStyle(datum))
|
|
return;
|
|
const strokeWidth = datum.strokeWidth ?? 1;
|
|
const lineStyleType = getLineStyle(datum.lineDash, datum.lineStyle);
|
|
this.updateStrokeWidth({
|
|
strokeWidth,
|
|
value: strokeWidth,
|
|
label: String(strokeWidth)
|
|
});
|
|
this.updateLineStyleType(
|
|
LINE_STYLE_TYPE_ITEMS.find((item) => item.value === lineStyleType) ?? LINE_STYLE_TYPE_ITEMS[0]
|
|
);
|
|
}
|
|
updateButtonByValue(value, change) {
|
|
const index = this.visibleButtons.findIndex((button) => button.value === value);
|
|
if (index === -1)
|
|
return;
|
|
this.updateButtonByIndex(index, change);
|
|
}
|
|
updateButtonByIndex(index, change) {
|
|
const button = this.visibleButtons.at(index);
|
|
if (!button)
|
|
return;
|
|
this.toolbar.updateButtonByIndex(index, { ...button.toJson(), ...change, value: change.value ?? button.value });
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AnnotationOptionsToolbar.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AnnotationOptionsToolbar.prototype, "buttons", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/states/dragState.ts
|
|
var DragStateMachine = class extends StateMachine {
|
|
constructor(ctx) {
|
|
const actionKeyChange = ({ context }) => {
|
|
this.node?.drag(this.datum, this.offset, context, this.snapping);
|
|
ctx.update();
|
|
};
|
|
super("idle", {
|
|
idle: {
|
|
dragStart: {
|
|
target: "dragging",
|
|
action: ({ offset, context }) => {
|
|
this.hasMoved = false;
|
|
this.dragStart = offset;
|
|
this.offset = offset;
|
|
this.node?.dragStart(this.datum, offset, context);
|
|
}
|
|
}
|
|
},
|
|
dragging: {
|
|
keyDown: actionKeyChange,
|
|
keyUp: actionKeyChange,
|
|
drag: ({ offset, context }) => {
|
|
this.hasMoved = vector_exports.lengthSquared(vector_exports.sub(offset, this.dragStart)) > 0;
|
|
this.offset = offset;
|
|
this.node?.drag(this.datum, offset, context, this.snapping);
|
|
ctx.update();
|
|
},
|
|
dragEnd: {
|
|
target: StateMachine.parent,
|
|
action: () => {
|
|
this.node?.stopDragging();
|
|
if (this.hasMoved)
|
|
ctx.recordAction("Move annotation");
|
|
ctx.update();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
this.debug = debugLogger_exports.create(true, "annotations");
|
|
this.hasMoved = false;
|
|
this.snapping = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], DragStateMachine.prototype, "snapping", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], DragStateMachine.prototype, "datum", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], DragStateMachine.prototype, "node", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/arrow-down/arrowDownProperties.ts
|
|
var ArrowDownProperties = class extends ShapePointProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "arrow-down" /* ArrowDown */;
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "arrow-down" /* ArrowDown */;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ArrowDownProperties.prototype, "type", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/annotationShape.ts
|
|
var AnnotationShape = class extends module_support_exports.Marker {
|
|
// Use exact method for this, rather than the Marker's high performance approximation.
|
|
isPointInPath(x, y) {
|
|
this.updatePathIfDirty();
|
|
return this.path.closedPath && this.path.isPointInPath(x, y);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/handle.ts
|
|
var _Handle = class _Handle extends module_support_exports.Group {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.active = false;
|
|
this.locked = false;
|
|
this.visible = false;
|
|
this.zIndex = 1;
|
|
}
|
|
drag(target) {
|
|
const { handle: handle3, locked } = this;
|
|
if (locked) {
|
|
return { point: { x: handle3.x, y: handle3.y }, offset: { x: 0, y: 0 } };
|
|
}
|
|
return {
|
|
point: target,
|
|
offset: { x: target.x - handle3.x, y: target.y - handle3.y }
|
|
};
|
|
}
|
|
toggleActive(active) {
|
|
this.active = active;
|
|
if (!active) {
|
|
this.handle.strokeWidth = _Handle.INACTIVE_STROKE_WIDTH;
|
|
}
|
|
}
|
|
toggleHovered(hovered) {
|
|
this.glow.visible = !this.locked && hovered;
|
|
this.glow.dirtyPath = true;
|
|
}
|
|
toggleDragging(dragging) {
|
|
if (this.locked)
|
|
return;
|
|
this.handle.visible = !dragging;
|
|
this.glow.visible = this.glow.visible && !dragging;
|
|
this.handle.dirtyPath = true;
|
|
this.glow.dirtyPath = true;
|
|
}
|
|
toggleLocked(locked) {
|
|
this.locked = locked;
|
|
}
|
|
getCursor() {
|
|
return void 0;
|
|
}
|
|
containsPoint(x, y) {
|
|
return this.handle.containsPoint(x, y);
|
|
}
|
|
};
|
|
_Handle.INACTIVE_STROKE_WIDTH = 2;
|
|
var Handle2 = _Handle;
|
|
var _InvariantHandle = class _InvariantHandle extends Handle2 {
|
|
constructor() {
|
|
super();
|
|
this.handle = new AnnotationShape({ shape: "circle" });
|
|
this.glow = new AnnotationShape({ shape: "circle" });
|
|
this.append([this.handle]);
|
|
this.handle.size = _InvariantHandle.HANDLE_SIZE;
|
|
this.handle.strokeWidth = Handle2.INACTIVE_STROKE_WIDTH;
|
|
this.handle.zIndex = 2;
|
|
}
|
|
update(styles) {
|
|
this.handle.setProperties({ ...styles, strokeWidth: Handle2.INACTIVE_STROKE_WIDTH });
|
|
}
|
|
drag(target) {
|
|
return { point: target, offset: { x: 0, y: 0 } };
|
|
}
|
|
};
|
|
_InvariantHandle.HANDLE_SIZE = 7;
|
|
_InvariantHandle.GLOW_SIZE = 9;
|
|
var InvariantHandle = _InvariantHandle;
|
|
var _UnivariantHandle = class _UnivariantHandle extends Handle2 {
|
|
constructor() {
|
|
super();
|
|
this.handle = new module_support_exports.Rect();
|
|
this.glow = new module_support_exports.Rect();
|
|
this.gradient = "horizontal";
|
|
this.append([this.glow, this.handle]);
|
|
this.handle.cornerRadius = _UnivariantHandle.CORNER_RADIUS;
|
|
this.handle.width = _UnivariantHandle.HANDLE_SIZE;
|
|
this.handle.height = _UnivariantHandle.HANDLE_SIZE;
|
|
this.handle.strokeWidth = Handle2.INACTIVE_STROKE_WIDTH;
|
|
this.handle.zIndex = 2;
|
|
this.glow.cornerRadius = _UnivariantHandle.CORNER_RADIUS;
|
|
this.glow.width = _UnivariantHandle.GLOW_SIZE;
|
|
this.glow.height = _UnivariantHandle.GLOW_SIZE;
|
|
this.glow.strokeWidth = 0;
|
|
this.glow.fillOpacity = 0.2;
|
|
this.glow.zIndex = 1;
|
|
this.glow.visible = false;
|
|
}
|
|
toggleLocked(locked) {
|
|
super.toggleLocked(locked);
|
|
if (locked) {
|
|
const offset = (_UnivariantHandle.HANDLE_SIZE - InvariantHandle.HANDLE_SIZE) / 2;
|
|
this.handle.cornerRadius = 1;
|
|
this.handle.fill = this.handle.stroke;
|
|
this.handle.strokeWidth = 0;
|
|
this.handle.x += offset;
|
|
this.handle.y += offset;
|
|
this.handle.width = InvariantHandle.HANDLE_SIZE;
|
|
this.handle.height = InvariantHandle.HANDLE_SIZE;
|
|
this.glow.width = InvariantHandle.GLOW_SIZE;
|
|
this.glow.height = InvariantHandle.GLOW_SIZE;
|
|
} else {
|
|
this.handle.cornerRadius = _UnivariantHandle.CORNER_RADIUS;
|
|
this.handle.width = _UnivariantHandle.HANDLE_SIZE;
|
|
this.handle.height = _UnivariantHandle.HANDLE_SIZE;
|
|
this.glow.width = _UnivariantHandle.GLOW_SIZE;
|
|
this.glow.height = _UnivariantHandle.GLOW_SIZE;
|
|
if (this.cachedStyles) {
|
|
this.handle.setProperties(this.cachedStyles);
|
|
}
|
|
}
|
|
}
|
|
update(styles) {
|
|
this.cachedStyles = { ...styles };
|
|
if (!this.active) {
|
|
delete styles.strokeWidth;
|
|
}
|
|
if (this.locked) {
|
|
delete styles.fill;
|
|
delete styles.strokeWidth;
|
|
const offset = (_UnivariantHandle.HANDLE_SIZE - InvariantHandle.HANDLE_SIZE) / 2;
|
|
styles.x -= offset;
|
|
styles.y -= offset;
|
|
this.cachedStyles.x -= offset;
|
|
this.cachedStyles.y -= offset;
|
|
}
|
|
this.handle.setProperties(styles);
|
|
this.glow.setProperties({
|
|
...styles,
|
|
x: (styles.x ?? this.glow.x) - 2,
|
|
y: (styles.y ?? this.glow.y) - 2,
|
|
strokeWidth: 0,
|
|
fill: styles.stroke
|
|
});
|
|
}
|
|
drag(target) {
|
|
if (this.locked) {
|
|
return { point: target, offset: { x: 0, y: 0 } };
|
|
}
|
|
if (this.gradient === "vertical") {
|
|
return {
|
|
point: { x: target.x, y: this.handle.y },
|
|
offset: { x: target.x - this.handle.x, y: 0 }
|
|
};
|
|
}
|
|
return {
|
|
point: { x: this.handle.x, y: target.y },
|
|
offset: { x: 0, y: target.y - this.handle.y }
|
|
};
|
|
}
|
|
getCursor() {
|
|
if (this.locked)
|
|
return;
|
|
return this.gradient === "vertical" ? "col-resize" : "row-resize";
|
|
}
|
|
};
|
|
_UnivariantHandle.HANDLE_SIZE = 12;
|
|
_UnivariantHandle.GLOW_SIZE = 16;
|
|
_UnivariantHandle.CORNER_RADIUS = 4;
|
|
var UnivariantHandle = _UnivariantHandle;
|
|
var _DivariantHandle = class _DivariantHandle extends Handle2 {
|
|
constructor() {
|
|
super();
|
|
this.handle = new AnnotationShape({ shape: "circle" });
|
|
this.glow = new AnnotationShape({ shape: "circle" });
|
|
this.append([this.glow, this.handle]);
|
|
this.handle.size = _DivariantHandle.HANDLE_SIZE;
|
|
this.handle.strokeWidth = Handle2.INACTIVE_STROKE_WIDTH;
|
|
this.handle.zIndex = 2;
|
|
this.glow.size = _DivariantHandle.GLOW_SIZE;
|
|
this.glow.strokeWidth = 0;
|
|
this.glow.fillOpacity = 0.2;
|
|
this.glow.zIndex = 1;
|
|
this.glow.visible = false;
|
|
}
|
|
toggleLocked(locked) {
|
|
super.toggleLocked(locked);
|
|
if (locked) {
|
|
this.handle.fill = this.handle.stroke;
|
|
this.handle.strokeWidth = 0;
|
|
this.handle.size = InvariantHandle.HANDLE_SIZE;
|
|
this.glow.size = InvariantHandle.GLOW_SIZE;
|
|
} else {
|
|
this.handle.size = _DivariantHandle.HANDLE_SIZE;
|
|
this.glow.size = _DivariantHandle.GLOW_SIZE;
|
|
if (this.cachedStyles) {
|
|
this.handle.setProperties(this.cachedStyles);
|
|
}
|
|
}
|
|
}
|
|
update(styles) {
|
|
this.cachedStyles = { ...styles };
|
|
if (!this.active) {
|
|
delete styles.strokeWidth;
|
|
}
|
|
if (this.locked) {
|
|
delete styles.fill;
|
|
delete styles.strokeWidth;
|
|
}
|
|
this.handle.setProperties(styles);
|
|
this.glow.setProperties({ ...styles, strokeWidth: 0, fill: styles.stroke });
|
|
}
|
|
getCursor() {
|
|
return "pointer";
|
|
}
|
|
};
|
|
_DivariantHandle.HANDLE_SIZE = 11;
|
|
_DivariantHandle.GLOW_SIZE = 17;
|
|
var DivariantHandle = _DivariantHandle;
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/annotationScene.ts
|
|
var AnnotationScene = class extends module_support_exports.Group {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.name = "AnnotationScene";
|
|
this.zIndex = 12 /* CHART_ANNOTATION */;
|
|
}
|
|
static isCheck(value, type) {
|
|
return isObject(value) && Object.hasOwn(value, "type") && value.type === type;
|
|
}
|
|
toggleHovered(hovered, active, readOnly) {
|
|
if (readOnly === true)
|
|
return;
|
|
this.toggleHandles(hovered || active);
|
|
}
|
|
computeBBoxWithoutHandles() {
|
|
return module_support_exports.Transformable.toCanvas(
|
|
this,
|
|
module_support_exports.Group.computeChildrenBBox(this.excludeChildren({ instance: Handle2 }))
|
|
);
|
|
}
|
|
updateNode(constructor, node, isConfigured) {
|
|
if (!isConfigured && node) {
|
|
node.remove();
|
|
return;
|
|
}
|
|
if (isConfigured && node == null) {
|
|
node = new constructor();
|
|
this.appendChild(node);
|
|
}
|
|
return node;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/utils/coords.ts
|
|
var { ContinuousScale: ContinuousScale2 } = module_support_exports;
|
|
function snapPoint(offset, context, snapping = false, origin3, angleStep = 1) {
|
|
if (!snapping)
|
|
return invertCoords(offset, context);
|
|
const center2 = origin3 ? convertPoint(origin3, context) : vector_exports.origin();
|
|
return invertCoords(snapToAngle(offset, center2, angleStep), context);
|
|
}
|
|
function snapToAngle(vector, center2, step) {
|
|
const radial = vector_exports.sub(vector, center2);
|
|
const stepRadians = toRadians(step);
|
|
const theta = Math.round(vector_exports.angle(radial) / stepRadians) * stepRadians;
|
|
return vector_exports.rotate(radial, theta, center2);
|
|
}
|
|
function getDragStartState(points, context) {
|
|
const dragState = {};
|
|
for (const [name, point] of entries(points)) {
|
|
dragState[name] = convertPoint(point, context);
|
|
}
|
|
return dragState;
|
|
}
|
|
function translate(vectors, translation, context, options = {
|
|
overflowContinuous: 0,
|
|
translateVectors: void 0,
|
|
invertYVectors: void 0,
|
|
snap: void 0
|
|
}) {
|
|
const { xAxis, yAxis } = context;
|
|
const vectorNames = Object.keys(vectors);
|
|
const overflowsX = [];
|
|
const overflowsY = [];
|
|
const translateVectors = new Set(options.translateVectors ?? vectorNames);
|
|
const invertYVectors = new Set(options.invertYVectors ?? []);
|
|
const movingVectors = /* @__PURE__ */ new Set([...translateVectors, ...invertYVectors]);
|
|
const invertYTranslation = vector_exports.multiply(translation, vector_exports.from(1, -1));
|
|
for (const name of vectorNames) {
|
|
if (movingVectors.has(name)) {
|
|
vectors[name] = vector_exports.add(vectors[name], invertYVectors.has(name) ? invertYTranslation : translation);
|
|
if (options.snap) {
|
|
vectors[name] = snapToAngle(vectors[name], options.snap.vectors[name], options.snap.angle);
|
|
}
|
|
}
|
|
overflowsX.push(xAxis.getRangeOverflow(vectors[name].x));
|
|
overflowsY.push(yAxis.getRangeOverflow(vectors[name].y));
|
|
}
|
|
const sortNumbersAbs = (a, b) => Math.abs(a) - Math.abs(b);
|
|
const overflowDirection = (scale2, directionTranslation, overflows) => {
|
|
if (options.overflowContinuous === 0 || !ContinuousScale2.is(scale2)) {
|
|
return overflows.toSorted(sortNumbersAbs).at(-1) ?? 0;
|
|
}
|
|
if (vectorNames.length === movingVectors.size) {
|
|
return overflows.toSorted(sortNumbersAbs).at(-options.overflowContinuous - 1) ?? 0;
|
|
}
|
|
if (overflows.filter((value) => value !== 0).length <= options.overflowContinuous) {
|
|
return 0;
|
|
}
|
|
const newTranslatedOverflows = overflows.filter(
|
|
(value, index) => value !== 0 && Math.abs(value) <= Math.abs(directionTranslation) && movingVectors.has(vectorNames[index])
|
|
);
|
|
return newTranslatedOverflows.toSorted(sortNumbersAbs).at(-1) ?? 0;
|
|
};
|
|
const overflow = vector_exports.from(
|
|
overflowDirection(xAxis.scale, translation.x, overflowsX),
|
|
overflowDirection(yAxis.scale, translation.y, overflowsY)
|
|
);
|
|
if (!vector_exports.equal(overflow, vector_exports.origin())) {
|
|
for (const name of vectorNames) {
|
|
if (!movingVectors.has(name))
|
|
continue;
|
|
vectors[name] = vector_exports.round(vector_exports.sub(vectors[name], overflow), 4);
|
|
}
|
|
}
|
|
const result = {};
|
|
for (const name of vectorNames) {
|
|
result[name] = invertCoords(vectors[name], context);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/pointScene.ts
|
|
var PointScene = class extends AnnotationScene {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.handle = new DivariantHandle();
|
|
this.anchor = {
|
|
x: 0,
|
|
y: 0,
|
|
position: "above"
|
|
};
|
|
}
|
|
update(datum, context) {
|
|
const coords = convertPoint(datum, context);
|
|
this.updateHandle(datum, coords);
|
|
this.anchor = this.updateAnchor(datum, coords, context);
|
|
}
|
|
dragStart(datum, target, context) {
|
|
this.dragState = {
|
|
offset: target,
|
|
...getDragStartState({ handle: datum }, context)
|
|
};
|
|
}
|
|
drag(datum, target, context) {
|
|
const { dragState } = this;
|
|
if (!datum.isWriteable() || !dragState)
|
|
return;
|
|
const { point } = translate({ point: dragState.handle }, vector_exports.sub(target, dragState.offset), context);
|
|
datum.x = point.x;
|
|
datum.y = point.y;
|
|
}
|
|
translate(datum, translation, context) {
|
|
if (!datum.isWriteable())
|
|
return;
|
|
const { point } = translate({ point: convertPoint(datum, context) }, translation, context);
|
|
datum.x = point.x;
|
|
datum.y = point.y;
|
|
}
|
|
toggleHandles(show) {
|
|
this.handle.visible = Boolean(show);
|
|
this.handle.toggleHovered(this.activeHandle === "handle");
|
|
}
|
|
toggleActive(active) {
|
|
this.toggleHandles(active);
|
|
this.handle.toggleActive(active);
|
|
}
|
|
stopDragging() {
|
|
this.handle.toggleDragging(false);
|
|
}
|
|
copy(datum, copiedDatum, context) {
|
|
const coords = convertPoint(datum, context);
|
|
const point = invertCoords({ x: coords.x - 30, y: coords.y - 30 }, context);
|
|
copiedDatum.x = point.x;
|
|
copiedDatum.y = point.y;
|
|
return copiedDatum;
|
|
}
|
|
getAnchor() {
|
|
return this.anchor;
|
|
}
|
|
getCursor() {
|
|
return "pointer";
|
|
}
|
|
containsPoint(x, y) {
|
|
const { handle: handle3 } = this;
|
|
this.activeHandle = void 0;
|
|
if (handle3.containsPoint(x, y)) {
|
|
this.activeHandle = "handle";
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
getNodeAtCoords(x, y) {
|
|
if (this.handle.containsPoint(x, y))
|
|
return "handle";
|
|
}
|
|
updateHandle(datum, point, bbox) {
|
|
const { x, y } = this.getHandleCoords(datum, point, bbox);
|
|
const styles = this.getHandleStyles(datum);
|
|
this.handle.update({ ...styles, x, y });
|
|
this.handle.toggleLocked(datum.locked ?? false);
|
|
}
|
|
updateAnchor(datum, point, context) {
|
|
const coords = this.getHandleCoords(datum, point);
|
|
return {
|
|
x: coords.x + context.seriesRect.x,
|
|
y: coords.y + context.seriesRect.y,
|
|
position: this.anchor.position
|
|
};
|
|
}
|
|
getHandleCoords(_datum, point, _bbox) {
|
|
return {
|
|
x: point.x,
|
|
y: point.y
|
|
};
|
|
}
|
|
getHandleStyles(datum) {
|
|
return {
|
|
fill: datum.handle.fill,
|
|
stroke: datum.handle.stroke,
|
|
strokeOpacity: datum.handle.strokeOpacity,
|
|
strokeWidth: datum.handle.strokeWidth
|
|
};
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/shapePointScene.ts
|
|
var ShapePointScene = class extends PointScene {
|
|
constructor() {
|
|
super();
|
|
this.append([this.handle]);
|
|
}
|
|
update(datum, context) {
|
|
super.update(datum, context);
|
|
const coords = convertPoint(datum, context);
|
|
this.updateShape(datum, coords);
|
|
}
|
|
updateShape(datum, point) {
|
|
this.updateShapeStyles(datum);
|
|
this.updateShapePath(datum, point);
|
|
}
|
|
updateShapeStyles(datum) {
|
|
const { shape } = this;
|
|
shape.fill = datum.fill;
|
|
shape.fillOpacity = datum.fillOpacity ?? 1;
|
|
}
|
|
updateShapePath(datum, point) {
|
|
const { shape } = this;
|
|
shape.x = point.x;
|
|
shape.y = point.y;
|
|
shape.size = datum.size;
|
|
}
|
|
containsPoint(x, y) {
|
|
return super.containsPoint(x, y) || this.shape.containsPoint(x, y);
|
|
}
|
|
getNodeAtCoords(x, y) {
|
|
if (this.shape.containsPoint(x, y))
|
|
return "shape";
|
|
return super.getNodeAtCoords(x, y);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/arrow-up/arrowUpScene.ts
|
|
var arrowUpPoints = [
|
|
[0.5, 0],
|
|
[1, 0.5],
|
|
[0.75, 0.5],
|
|
[0.75, 1],
|
|
[0.25, 1],
|
|
[0.25, 0.5],
|
|
[0, 0.5]
|
|
];
|
|
function arrowUp(params) {
|
|
module_support_exports.drawMarkerUnitPolygon(params, arrowUpPoints);
|
|
}
|
|
arrowUp.anchor = { x: 0.5, y: 0 };
|
|
var ArrowUpScene = class extends ShapePointScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "arrow-up" /* ArrowUp */;
|
|
this.shape = new AnnotationShape({ shape: arrowUp });
|
|
this.append([this.shape]);
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "arrow-up" /* ArrowUp */);
|
|
}
|
|
getHandleCoords(datum, point) {
|
|
const halfSize = DivariantHandle.HANDLE_SIZE / 2;
|
|
const handleCoords = super.getHandleCoords(datum, point);
|
|
handleCoords.y -= halfSize;
|
|
return handleCoords;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/arrow-down/arrowDownScene.ts
|
|
var arrowDownPoints = arrowUpPoints.map(([x, y]) => [x, 1 - y]);
|
|
function arrowDown(params) {
|
|
module_support_exports.drawMarkerUnitPolygon(params, arrowDownPoints);
|
|
}
|
|
arrowDown.anchor = { x: 0.5, y: 1 };
|
|
var ArrowDownScene = class extends ShapePointScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "arrow-down" /* ArrowDown */;
|
|
this.shape = new AnnotationShape({ shape: arrowDown });
|
|
this.append([this.shape]);
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "arrow-down" /* ArrowDown */);
|
|
}
|
|
updateAnchor(datum, point, context) {
|
|
const anchor = super.updateAnchor(datum, point, context);
|
|
anchor.y -= datum.size;
|
|
return anchor;
|
|
}
|
|
getHandleCoords(datum, point) {
|
|
const halfSize = DivariantHandle.HANDLE_SIZE / 2;
|
|
const handleCoords = super.getHandleCoords(datum, point);
|
|
handleCoords.y += halfSize;
|
|
return handleCoords;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/states/pointState.ts
|
|
var PointStateMachine = class extends StateMachine {
|
|
constructor(ctx) {
|
|
const actionCreate = ({ point }) => {
|
|
const datum = this.createDatum();
|
|
datum.set({ x: point.x, y: point.y });
|
|
ctx.create(datum);
|
|
};
|
|
const actionFirstRender = () => {
|
|
this.node?.toggleActive(true);
|
|
ctx.showAnnotationOptions();
|
|
ctx.update();
|
|
};
|
|
super("start", {
|
|
start: {
|
|
click: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
drag: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
cancel: StateMachine.parent,
|
|
reset: StateMachine.parent
|
|
},
|
|
"waiting-first-render": {
|
|
render: {
|
|
target: StateMachine.parent,
|
|
action: actionFirstRender
|
|
}
|
|
}
|
|
});
|
|
this.debug = debugLogger_exports.create(true, "annotations");
|
|
}
|
|
};
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], PointStateMachine.prototype, "node", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/arrow-down/arrowDownState.ts
|
|
var ArrowDownStateMachine = class extends PointStateMachine {
|
|
createDatum() {
|
|
return new ArrowDownProperties();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/arrow-down/arrowDownConfig.ts
|
|
var arrowDownConfig = {
|
|
type: "arrow-down" /* ArrowDown */,
|
|
datum: ArrowDownProperties,
|
|
scene: ArrowDownScene,
|
|
isDatum: ArrowDownProperties.is,
|
|
translate: (node, datum, translation, context) => {
|
|
if (ArrowDownProperties.is(datum) && ArrowDownScene.is(node))
|
|
node.translate(datum, translation, context);
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (ArrowDownProperties.is(datum) && ArrowDownProperties.is(copiedDatum) && ArrowDownScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (ArrowDownProperties.is(datum) && ArrowDownScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new ArrowDownStateMachine({
|
|
...ctx,
|
|
create: createDatum("arrow-down" /* ArrowDown */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/arrow-up/arrowUpProperties.ts
|
|
var ArrowUpProperties = class extends ShapePointProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "arrow-up" /* ArrowUp */;
|
|
}
|
|
static is(value) {
|
|
return isObject(value) && value.type === "arrow-up" /* ArrowUp */;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ArrowUpProperties.prototype, "type", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/arrow-up/arrowUpState.ts
|
|
var ArrowUpStateMachine = class extends PointStateMachine {
|
|
createDatum() {
|
|
return new ArrowUpProperties();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/arrow-up/arrowUpConfig.ts
|
|
var arrowUpConfig = {
|
|
type: "arrow-up" /* ArrowUp */,
|
|
datum: ArrowUpProperties,
|
|
scene: ArrowUpScene,
|
|
isDatum: ArrowUpProperties.is,
|
|
translate: (node, datum, translation, context) => {
|
|
if (ArrowUpProperties.is(datum) && ArrowUpScene.is(node))
|
|
node.translate(datum, translation, context);
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (ArrowUpProperties.is(datum) && ArrowUpProperties.is(copiedDatum) && ArrowUpScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (ArrowUpProperties.is(datum) && ArrowUpScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new ArrowUpStateMachine({
|
|
...ctx,
|
|
create: createDatum("arrow-up" /* ArrowUp */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/linearScene.ts
|
|
var LinearScene = class extends AnnotationScene {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.overflowContinuous = 0;
|
|
}
|
|
extendLine({ x1, y1, x2, y2 }, datum, context) {
|
|
const linePoints = { x1, y1, x2, y2 };
|
|
if (!datum.extendStart && !datum.extendEnd) {
|
|
return linePoints;
|
|
}
|
|
const [left, right] = boundsIntersections(linePoints, context.yAxis.bounds);
|
|
const isFlippedX = linePoints.x2 < linePoints.x1;
|
|
const isFlippedY = linePoints.y1 >= linePoints.y2;
|
|
const isVertical = linePoints.x2 === linePoints.x1;
|
|
if (datum.extendEnd) {
|
|
if (isVertical) {
|
|
linePoints.y2 = isFlippedY ? right.y : left.y;
|
|
} else {
|
|
linePoints.x2 = isFlippedX ? left.x : right.x;
|
|
linePoints.y2 = isFlippedX ? left.y : right.y;
|
|
}
|
|
}
|
|
if (datum.extendStart) {
|
|
if (isVertical) {
|
|
linePoints.y1 = isFlippedY ? left.y : right.y;
|
|
} else {
|
|
linePoints.x1 = isFlippedX ? right.x : left.x;
|
|
linePoints.y1 = isFlippedX ? right.y : left.y;
|
|
}
|
|
}
|
|
return linePoints;
|
|
}
|
|
dragStart(datum, target, context) {
|
|
this.dragState = {
|
|
offset: target,
|
|
...getDragStartState({ start: datum.start, end: datum.end }, context)
|
|
};
|
|
}
|
|
drag(datum, target, context, snapping) {
|
|
if (!datum.isWriteable())
|
|
return;
|
|
if (this.activeHandle) {
|
|
this.dragHandle(datum, target, context, snapping);
|
|
} else {
|
|
this.dragAll(datum, target, context);
|
|
}
|
|
}
|
|
dragAll(datum, target, context) {
|
|
const { dragState } = this;
|
|
if (!dragState)
|
|
return;
|
|
this.translatePoints(datum, dragState.start, dragState.end, vector_exports.sub(target, dragState.offset), context);
|
|
}
|
|
translate(datum, translation, context) {
|
|
if (!datum.isWriteable())
|
|
return;
|
|
this.translatePoints(
|
|
datum,
|
|
convertPoint(datum.start, context),
|
|
convertPoint(datum.end, context),
|
|
translation,
|
|
context
|
|
);
|
|
}
|
|
copy(datum, copiedDatum, context) {
|
|
const coords = convertLine(datum, context);
|
|
if (!coords) {
|
|
return;
|
|
}
|
|
const bbox = this.computeBBoxWithoutHandles();
|
|
const translation = { x: -bbox.width / 2, y: -bbox.height / 2 };
|
|
this.translatePoints(copiedDatum, vector4_exports.start(coords), vector4_exports.end(coords), translation, context);
|
|
return copiedDatum;
|
|
}
|
|
translatePoints(datum, start2, end3, translation, context) {
|
|
const vectors = this.getTranslatePointsVectors(start2, end3);
|
|
const points = translate(vectors, translation, context, {
|
|
overflowContinuous: this.overflowContinuous
|
|
});
|
|
datum.start.x = points.start.x;
|
|
datum.end.x = points.end.x;
|
|
datum.start.y = points.start.y;
|
|
datum.end.y = points.end.y;
|
|
}
|
|
getTranslatePointsVectors(start2, end3) {
|
|
return { start: start2, end: end3 };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/startEndScene.ts
|
|
var StartEndScene = class extends LinearScene {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.start = new DivariantHandle();
|
|
this.end = new DivariantHandle();
|
|
this.anchor = {
|
|
x: 0,
|
|
y: 0,
|
|
position: "above"
|
|
};
|
|
}
|
|
update(datum, context) {
|
|
const coords = convertLine(datum, context);
|
|
if (coords == null) {
|
|
return;
|
|
}
|
|
this.updateHandles(datum, coords);
|
|
this.updateAnchor(datum, coords, context);
|
|
}
|
|
toggleHandles(show) {
|
|
if (typeof show === "boolean") {
|
|
this.start.visible = show;
|
|
this.end.visible = show;
|
|
} else {
|
|
for (const [handle3, visible] of entries(show)) {
|
|
this[handle3].visible = visible;
|
|
}
|
|
}
|
|
this.start.toggleHovered(this.activeHandle === "start");
|
|
this.end.toggleHovered(this.activeHandle === "end");
|
|
}
|
|
toggleActive(active) {
|
|
this.toggleHandles(active);
|
|
this.start.toggleActive(active);
|
|
this.end.toggleActive(active);
|
|
}
|
|
dragHandle(datum, target, context, snapping) {
|
|
const { activeHandle, dragState } = this;
|
|
if (!activeHandle || !dragState)
|
|
return;
|
|
this[activeHandle].toggleDragging(true);
|
|
const snapHandle = activeHandle === "start" ? "end" : "start";
|
|
const snap = snapping ? { vectors: { [activeHandle]: convertPoint(datum[snapHandle], context) }, angle: datum.snapToAngle } : void 0;
|
|
const { [activeHandle]: point } = translate(
|
|
{ [activeHandle]: dragState[activeHandle] },
|
|
vector_exports.sub(target, dragState.offset),
|
|
context,
|
|
{ overflowContinuous: 0, snap }
|
|
);
|
|
datum[activeHandle].x = point.x;
|
|
datum[activeHandle].y = point.y;
|
|
}
|
|
stopDragging() {
|
|
this.start.toggleDragging(false);
|
|
this.end.toggleDragging(false);
|
|
}
|
|
getAnchor() {
|
|
return this.anchor;
|
|
}
|
|
getCursor() {
|
|
return "pointer";
|
|
}
|
|
containsPoint(x, y) {
|
|
const { start: start2, end: end3 } = this;
|
|
this.activeHandle = void 0;
|
|
if (start2.containsPoint(x, y)) {
|
|
this.activeHandle = "start";
|
|
return true;
|
|
}
|
|
if (end3.containsPoint(x, y)) {
|
|
this.activeHandle = "end";
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
getNodeAtCoords(x, y) {
|
|
if (this.start.containsPoint(x, y) || this.end.containsPoint(x, y))
|
|
return "handle";
|
|
}
|
|
updateHandles(datum, coords, bbox) {
|
|
this.start.update({
|
|
...this.getHandleStyles(datum, "start"),
|
|
...this.getHandleCoords(datum, coords, "start")
|
|
});
|
|
this.end.update({
|
|
...this.getHandleStyles(datum, "end"),
|
|
...this.getHandleCoords(datum, coords, "end", bbox)
|
|
});
|
|
this.start.toggleLocked(datum.locked ?? false);
|
|
this.end.toggleLocked(datum.locked ?? false);
|
|
}
|
|
updateAnchor(_datum, coords, context, _bbox) {
|
|
this.anchor = {
|
|
x: coords.x1 + context.seriesRect.x,
|
|
y: coords.y1 + context.seriesRect.y,
|
|
position: this.anchor.position
|
|
};
|
|
}
|
|
getHandleCoords(_datum, coords, handle3, _bbox) {
|
|
return handle3 === "start" ? vector4_exports.start(coords) : vector4_exports.end(coords);
|
|
}
|
|
getHandleStyles(datum, _handle) {
|
|
return {
|
|
fill: datum.handle.fill,
|
|
stroke: datum.handle.stroke,
|
|
strokeOpacity: datum.handle.strokeOpacity,
|
|
strokeWidth: datum.handle.strokeWidth
|
|
};
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/textualStartEndScene.ts
|
|
var TextualStartEndScene = class extends StartEndScene {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.label = new module_support_exports.Text({ zIndex: 1 });
|
|
this.anchor = {
|
|
x: 0,
|
|
y: 0,
|
|
position: "above-left"
|
|
};
|
|
}
|
|
setTextInputBBox(bbox) {
|
|
this.textInputBBox = bbox;
|
|
this.markDirty("TextualStartEndScene");
|
|
}
|
|
update(datum, context) {
|
|
const coords = convertLine(datum, context);
|
|
if (coords == null) {
|
|
return;
|
|
}
|
|
const bbox = this.getTextBBox(datum, coords);
|
|
this.updateLabel(datum, bbox, coords);
|
|
this.updateHandles(datum, coords, bbox);
|
|
this.updateShape(datum, bbox, coords);
|
|
this.updateAnchor(datum, coords, context, bbox);
|
|
}
|
|
containsPoint(x, y) {
|
|
return super.containsPoint(x, y) || this.label.containsPoint(x, y);
|
|
}
|
|
getNodeAtCoords(x, y) {
|
|
if (this.label.containsPoint(x, y))
|
|
return "text";
|
|
return super.getNodeAtCoords(x, y);
|
|
}
|
|
getTextBBox(datum, coords) {
|
|
const { text: text2 } = datum.getText();
|
|
return getBBox(datum, text2, vector4_exports.end(coords), this.textInputBBox);
|
|
}
|
|
updateLabel(datum, bbox, coords) {
|
|
const { text: text2, isPlaceholder } = datum.getText();
|
|
updateTextNode(this.label, text2, isPlaceholder, datum, this.getLabelCoords(datum, bbox, coords));
|
|
}
|
|
updateShape(_datum, _textBBox, _coords) {
|
|
}
|
|
getLabelCoords(_datum, _bbox, coords) {
|
|
return vector4_exports.end(coords);
|
|
}
|
|
getHandleStyles(datum, handle3) {
|
|
return {
|
|
...super.getHandleStyles(datum, handle3),
|
|
stroke: datum.handle.stroke ?? datum.color
|
|
};
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/callout/calloutScene.ts
|
|
var { drawCorner: drawCorner2, Path: Path5 } = module_support_exports;
|
|
var CalloutScene = class extends TextualStartEndScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "callout" /* Callout */;
|
|
this.shape = new Path5();
|
|
this.append([this.shape, this.label, this.start, this.end]);
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "callout" /* Callout */);
|
|
}
|
|
drag(datum, target, context, snapping) {
|
|
if (!datum.isWriteable())
|
|
return;
|
|
if (this.activeHandle === "end") {
|
|
this.dragHandle(datum, target, context, snapping);
|
|
} else {
|
|
this.dragAll(datum, target, context);
|
|
}
|
|
}
|
|
getLabelCoords(datum, bbox, coords) {
|
|
const padding2 = datum.getPadding();
|
|
const {
|
|
bodyBounds = {
|
|
x: 0,
|
|
y: 0,
|
|
width: 0,
|
|
height: 0
|
|
}
|
|
} = this.getDimensions(datum, bbox, coords) ?? {};
|
|
return {
|
|
x: bodyBounds.x + padding2.left,
|
|
y: bodyBounds.y - padding2.bottom
|
|
};
|
|
}
|
|
getHandleStyles(datum, handle3) {
|
|
return handle3 === "start" ? {
|
|
fill: datum.handle.fill,
|
|
stroke: datum.handle.stroke ?? datum.stroke,
|
|
strokeOpacity: datum.handle.strokeOpacity,
|
|
strokeWidth: datum.handle.strokeWidth
|
|
} : { fill: void 0, strokeWidth: 0 };
|
|
}
|
|
updateAnchor(datum, coords, context, bbox) {
|
|
const { bodyBounds } = this.getDimensions(datum, bbox, coords) ?? {};
|
|
const bounds = bodyBounds ?? bbox;
|
|
this.anchor = {
|
|
x: bounds.x + context.seriesRect.x,
|
|
y: bounds.y + context.seriesRect.y - bounds.height,
|
|
position: this.anchor.position
|
|
};
|
|
}
|
|
updateShape(datum, textBox, coords) {
|
|
const { shape } = this;
|
|
shape.fill = datum.fill;
|
|
shape.fillOpacity = datum.fillOpacity ?? 1;
|
|
shape.stroke = datum.stroke;
|
|
shape.strokeWidth = datum.strokeWidth ?? 1;
|
|
shape.strokeOpacity = datum.strokeOpacity ?? 1;
|
|
const { tailPoint, bodyBounds } = this.getDimensions(datum, textBox, coords) ?? {};
|
|
if (!tailPoint || !bodyBounds) {
|
|
return;
|
|
}
|
|
this.updatePath(tailPoint, bodyBounds);
|
|
}
|
|
updatePath(tailPoint, bodyBounds) {
|
|
const { x: tailX, y: tailY } = tailPoint;
|
|
const { x, y, width: width2, height: height2 } = bodyBounds;
|
|
const top = y - height2;
|
|
const right = x + width2;
|
|
const placement = this.calculateCalloutPlacement({ x: tailX, y: tailY }, bodyBounds);
|
|
const cornerRadius = 8;
|
|
const pathParams2 = [
|
|
{
|
|
coordinates: {
|
|
x0: x,
|
|
x1: x + cornerRadius,
|
|
y0: top + cornerRadius,
|
|
y1: top,
|
|
cx: placement === `topLeft` ? tailX : x + cornerRadius,
|
|
cy: placement === `topLeft` ? tailY : top + cornerRadius
|
|
},
|
|
type: placement === `topLeft` ? "calloutCorner" : "corner"
|
|
},
|
|
{
|
|
coordinates: {
|
|
x0: x + cornerRadius,
|
|
x1: right - cornerRadius,
|
|
y0: top,
|
|
y1: top,
|
|
cx: tailX,
|
|
cy: tailY
|
|
},
|
|
type: placement === `top` ? "calloutSide" : "side"
|
|
},
|
|
{
|
|
coordinates: {
|
|
x0: right - cornerRadius,
|
|
x1: right,
|
|
y0: top,
|
|
y1: top + cornerRadius,
|
|
cx: placement === `topRight` ? tailX : right - cornerRadius,
|
|
cy: placement === `topRight` ? tailY : top + cornerRadius
|
|
},
|
|
type: placement === `topRight` ? "calloutCorner" : "corner"
|
|
},
|
|
{
|
|
coordinates: {
|
|
x0: right,
|
|
x1: right,
|
|
y0: top + cornerRadius,
|
|
y1: y - cornerRadius,
|
|
cx: tailX,
|
|
cy: tailY
|
|
},
|
|
type: placement === `right` ? "calloutSide" : "side"
|
|
},
|
|
{
|
|
coordinates: {
|
|
x0: right,
|
|
x1: right - cornerRadius,
|
|
y0: y - cornerRadius,
|
|
y1: y,
|
|
cx: placement === `bottomRight` ? tailX : right - cornerRadius,
|
|
cy: placement === `bottomRight` ? tailY : y - cornerRadius
|
|
},
|
|
type: placement === `bottomRight` ? "calloutCorner" : "corner"
|
|
},
|
|
{
|
|
coordinates: {
|
|
x0: right - cornerRadius,
|
|
x1: x + cornerRadius,
|
|
y0: y,
|
|
y1: y,
|
|
cx: tailX,
|
|
cy: tailY
|
|
},
|
|
type: placement === `bottom` ? "calloutSide" : "side"
|
|
},
|
|
{
|
|
coordinates: {
|
|
x0: x + cornerRadius,
|
|
x1: x,
|
|
y0: y,
|
|
y1: y - cornerRadius,
|
|
cx: placement === `bottomLeft` ? tailX : x + cornerRadius,
|
|
cy: placement === `bottomLeft` ? tailY : y - cornerRadius
|
|
},
|
|
type: placement === `bottomLeft` ? "calloutCorner" : "corner"
|
|
},
|
|
{
|
|
coordinates: {
|
|
x0: x,
|
|
x1: x,
|
|
y0: y - cornerRadius,
|
|
y1: top + cornerRadius,
|
|
cx: tailX,
|
|
cy: tailY
|
|
},
|
|
type: placement === `left` ? "calloutSide" : "side"
|
|
}
|
|
];
|
|
const { path } = this.shape;
|
|
path.clear();
|
|
path.moveTo(x, top + cornerRadius);
|
|
for (const { coordinates, type } of pathParams2) {
|
|
this.drawPath(path, coordinates, cornerRadius, type);
|
|
}
|
|
path.closePath();
|
|
}
|
|
drawPath(path, { x0, y0, x1, y1, cx, cy }, cornerRadius, type) {
|
|
const sideTailRadius = 6;
|
|
switch (type) {
|
|
case "calloutCorner": {
|
|
path.lineTo(cx, cy);
|
|
path.lineTo(x1, y1);
|
|
break;
|
|
}
|
|
case "corner": {
|
|
drawCorner2(
|
|
path,
|
|
{
|
|
x0,
|
|
x1,
|
|
y0,
|
|
y1,
|
|
cx,
|
|
cy
|
|
},
|
|
cornerRadius,
|
|
false
|
|
);
|
|
break;
|
|
}
|
|
case "calloutSide": {
|
|
if (x0 === x1) {
|
|
const direction = y0 > y1 ? -1 : 1;
|
|
const midY = Math.min(y0, y1) + Math.abs(y0 - y1) / 2;
|
|
path.lineTo(x0, midY - sideTailRadius * direction);
|
|
path.lineTo(cx, cy);
|
|
path.lineTo(x0, midY + sideTailRadius * direction);
|
|
path.lineTo(x1, y1);
|
|
} else {
|
|
const direction = x0 > x1 ? -1 : 1;
|
|
const midX = Math.min(x0, x1) + Math.abs(x1 - x0) / 2;
|
|
path.lineTo(midX - sideTailRadius * direction, y0);
|
|
path.lineTo(cx, cy);
|
|
path.lineTo(midX + sideTailRadius * direction, y0);
|
|
path.lineTo(x1, y1);
|
|
}
|
|
break;
|
|
}
|
|
case "side":
|
|
default: {
|
|
path.lineTo(x1, y1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
calculateCalloutPlacement(placement, bounds) {
|
|
const right = bounds.x + bounds.width;
|
|
const top = bounds.y - bounds.height;
|
|
let xPlacement;
|
|
let yPlacement;
|
|
if (placement.x > right) {
|
|
xPlacement = "right";
|
|
} else if (placement.x < bounds.x) {
|
|
xPlacement = "left";
|
|
}
|
|
if (placement.y > bounds.y) {
|
|
yPlacement = "bottom";
|
|
} else if (placement.y < top) {
|
|
yPlacement = "top";
|
|
}
|
|
if (xPlacement && yPlacement) {
|
|
return `${yPlacement}${xPlacement[0].toUpperCase()}${xPlacement.substring(1)}`;
|
|
} else {
|
|
return yPlacement ?? xPlacement;
|
|
}
|
|
}
|
|
getDimensions(datum, textBox, coords) {
|
|
const { fontSize } = datum;
|
|
const padding2 = datum.getPadding();
|
|
const horizontalPadding = padding2.left + padding2.right;
|
|
const verticalPadding = padding2.top + padding2.bottom;
|
|
const width2 = textBox.width + horizontalPadding;
|
|
const height2 = Math.max(textBox.height + verticalPadding, fontSize + verticalPadding);
|
|
return {
|
|
tailPoint: {
|
|
x: coords.x1,
|
|
y: coords.y1
|
|
},
|
|
bodyBounds: {
|
|
x: textBox.x,
|
|
y: textBox.y,
|
|
width: width2,
|
|
height: height2
|
|
}
|
|
};
|
|
}
|
|
containsPoint(x, y) {
|
|
const { start: start2, end: end3, shape } = this;
|
|
this.activeHandle = void 0;
|
|
if (start2.containsPoint(x, y)) {
|
|
this.activeHandle = "start";
|
|
return true;
|
|
}
|
|
const bodyContainsPoint = end3.containsPoint(x, y) || shape.containsPoint(x, y);
|
|
if (bodyContainsPoint) {
|
|
this.activeHandle = "end";
|
|
}
|
|
return bodyContainsPoint;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/states/textualStateUtils.ts
|
|
function guardCancelAndExit({ key }) {
|
|
return key === "Escape";
|
|
}
|
|
function guardSaveAndExit({ key, shiftKey }) {
|
|
return !shiftKey && key === "Enter";
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/states/textualStartEndState.ts
|
|
var TextualStartEndStateMachine = class extends StateMachine {
|
|
constructor(ctx) {
|
|
const actionCreate = ({ point }) => {
|
|
const datum = this.createDatum();
|
|
datum.set({ start: point, end: point, visible: true });
|
|
ctx.create(datum);
|
|
};
|
|
const actionFirstRender = () => {
|
|
const { node } = this;
|
|
node?.toggleActive(true);
|
|
node?.toggleHandles({ start: true });
|
|
};
|
|
const onStartEditing = () => {
|
|
ctx.showTextInput();
|
|
if (this.datum)
|
|
this.datum.visible = false;
|
|
};
|
|
const onStopEditing = () => {
|
|
ctx.hideTextInput();
|
|
if (this.datum)
|
|
this.datum.visible = true;
|
|
ctx.deselect();
|
|
};
|
|
const actionUpdateTextInputBBox = (bbox) => {
|
|
this.node?.setTextInputBBox(bbox);
|
|
ctx.update();
|
|
};
|
|
const onEndHover = ({ point }) => {
|
|
const { datum, node } = this;
|
|
datum?.set({ end: point });
|
|
node?.toggleActive(true);
|
|
node?.toggleHandles({ end: false });
|
|
ctx.update();
|
|
};
|
|
const onEndClick = () => {
|
|
ctx.showAnnotationOptions();
|
|
this.node?.toggleHandles({ end: true });
|
|
};
|
|
const actionColor = ({
|
|
colorPickerType,
|
|
colorOpacity,
|
|
color: color2,
|
|
opacity,
|
|
isMultiColor
|
|
}) => {
|
|
const { datum } = this;
|
|
if (!datum)
|
|
return;
|
|
if (colorPickerType === "text-color") {
|
|
ctx.updateTextInputColor(color2);
|
|
}
|
|
setColor(datum, colorPickerType, colorOpacity, color2, opacity, isMultiColor);
|
|
ctx.update();
|
|
};
|
|
const actionFontSize = (fontSize) => {
|
|
const { datum, node } = this;
|
|
if (!datum || !node || !isTextType(datum))
|
|
return;
|
|
datum.fontSize = fontSize;
|
|
ctx.updateTextInputFontSize(fontSize);
|
|
ctx.update();
|
|
};
|
|
const actionCancel = () => {
|
|
ctx.delete();
|
|
};
|
|
const actionSave = ({ textInputValue, bbox }) => {
|
|
const { datum } = this;
|
|
if (bbox != null && textInputValue != null && textInputValue.length > 0) {
|
|
if (!isTextType(datum)) {
|
|
return;
|
|
}
|
|
const wrappedText = maybeWrapText(datum, textInputValue, bbox.width);
|
|
datum?.set({ text: wrappedText });
|
|
ctx.update();
|
|
ctx.recordAction(`Create ${datum?.type} annotation`);
|
|
} else {
|
|
ctx.delete();
|
|
}
|
|
};
|
|
super("start", {
|
|
start: {
|
|
click: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
dragStart: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
cancel: StateMachine.parent,
|
|
reset: StateMachine.parent
|
|
},
|
|
"waiting-first-render": {
|
|
render: {
|
|
target: "end",
|
|
action: actionFirstRender
|
|
}
|
|
},
|
|
end: {
|
|
hover: onEndHover,
|
|
drag: onEndHover,
|
|
click: {
|
|
target: "edit",
|
|
action: onEndClick
|
|
},
|
|
dragEnd: {
|
|
target: "edit",
|
|
action: onEndClick
|
|
},
|
|
reset: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
cancel: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
}
|
|
},
|
|
edit: {
|
|
onEnter: onStartEditing,
|
|
updateTextInputBBox: actionUpdateTextInputBBox,
|
|
color: actionColor,
|
|
fontSize: actionFontSize,
|
|
textInput: [
|
|
{
|
|
guard: guardCancelAndExit,
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
{
|
|
guard: guardSaveAndExit,
|
|
target: StateMachine.parent,
|
|
action: actionSave
|
|
}
|
|
],
|
|
click: {
|
|
target: StateMachine.parent,
|
|
action: actionSave
|
|
},
|
|
dragStart: {
|
|
target: StateMachine.parent,
|
|
action: actionSave
|
|
},
|
|
resize: {
|
|
target: StateMachine.parent,
|
|
action: actionSave
|
|
},
|
|
onExit: onStopEditing,
|
|
cancel: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
}
|
|
}
|
|
});
|
|
this.debug = debugLogger_exports.create(true, "annotations");
|
|
}
|
|
};
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], TextualStartEndStateMachine.prototype, "datum", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], TextualStartEndStateMachine.prototype, "node", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/callout/calloutState.ts
|
|
var CalloutStateMachine = class extends TextualStartEndStateMachine {
|
|
createDatum() {
|
|
return new CalloutProperties();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/callout/calloutConfig.ts
|
|
var calloutConfig = {
|
|
type: "callout" /* Callout */,
|
|
datum: CalloutProperties,
|
|
scene: CalloutScene,
|
|
isDatum: CalloutProperties.is,
|
|
translate: (node, datum, transition, context) => {
|
|
if (CalloutProperties.is(datum) && CalloutScene.is(node))
|
|
return node.translate(datum, transition, context);
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (CalloutProperties.is(datum) && CalloutProperties.is(copiedDatum) && CalloutScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (CalloutProperties.is(datum) && CalloutScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new CalloutStateMachine({
|
|
...ctx,
|
|
create: createDatum("callout" /* Callout */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/textualPointScene.ts
|
|
var TextualPointScene = class extends PointScene {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.label = new module_support_exports.Text({ zIndex: 1 });
|
|
this.anchor = {
|
|
x: 0,
|
|
y: 0,
|
|
position: "above-left"
|
|
};
|
|
}
|
|
setTextInputBBox(bbox) {
|
|
this.textInputBBox = bbox;
|
|
this.markDirty("TextualPointScene");
|
|
}
|
|
update(datum, context) {
|
|
const coords = convertPoint(datum, context);
|
|
const bbox = this.getTextBBox(datum, coords, context);
|
|
this.updateLabel(datum, bbox);
|
|
this.updateHandle(datum, coords, bbox);
|
|
this.updateShape(datum, bbox);
|
|
this.anchor = this.updateAnchor(datum, bbox, context);
|
|
}
|
|
copy(datum, copiedDatum, context) {
|
|
const coords = convertPoint(datum, context);
|
|
const bbox = this.getTextBBox(datum, coords, context);
|
|
const padding2 = datum.getPadding();
|
|
const horizontalPadding = padding2.left + padding2.right;
|
|
const verticalPadding = padding2.top + padding2.bottom;
|
|
const xOffset = (bbox.width + horizontalPadding) / 2;
|
|
const yOffset = bbox.height + verticalPadding;
|
|
const point = invertCoords({ x: coords.x - xOffset, y: coords.y - yOffset }, context);
|
|
copiedDatum.x = point.x;
|
|
copiedDatum.y = point.y;
|
|
return copiedDatum;
|
|
}
|
|
containsPoint(x, y) {
|
|
const { label } = this;
|
|
return super.containsPoint(x, y) || label.visible && label.containsPoint(x, y);
|
|
}
|
|
getNodeAtCoords(x, y) {
|
|
if (this.label.visible && this.label.containsPoint(x, y))
|
|
return "text";
|
|
return super.getNodeAtCoords(x, y);
|
|
}
|
|
getTextBBox(datum, coords, _context) {
|
|
const { text: text2 } = datum.getText();
|
|
return getBBox(datum, text2, { x: coords.x, y: coords.y }, this.textInputBBox);
|
|
}
|
|
updateLabel(datum, bbox) {
|
|
const { text: text2, isPlaceholder } = datum.getText();
|
|
updateTextNode(
|
|
this.label,
|
|
text2,
|
|
isPlaceholder,
|
|
datum,
|
|
this.getLabelCoords(datum, bbox),
|
|
this.getTextBaseline(datum)
|
|
);
|
|
}
|
|
updateShape(_datum, _bbox) {
|
|
}
|
|
updateAnchor(_datum, bbox, context) {
|
|
return {
|
|
x: bbox.x + context.seriesRect.x,
|
|
y: bbox.y + context.seriesRect.y - bbox.height,
|
|
position: this.anchor.position
|
|
};
|
|
}
|
|
getLabelCoords(_datum, bbox) {
|
|
return bbox;
|
|
}
|
|
getTextBaseline(datum) {
|
|
return datum.position == "center" ? "middle" : datum.position;
|
|
}
|
|
getHandleCoords(_datum, _coords, bbox) {
|
|
return bbox;
|
|
}
|
|
getHandleStyles(datum) {
|
|
const styles = super.getHandleStyles(datum);
|
|
styles.stroke = datum.handle.stroke ?? datum.color;
|
|
return styles;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/comment/commentScene.ts
|
|
var { drawCorner: drawCorner3 } = module_support_exports;
|
|
var CommentScene = class extends TextualPointScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "comment" /* Comment */;
|
|
this.shape = new module_support_exports.Path();
|
|
this.append([this.shape, this.label, this.handle]);
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "comment" /* Comment */);
|
|
}
|
|
updateShape(datum, bbox) {
|
|
const { shape } = this;
|
|
shape.fill = datum.fill;
|
|
shape.fillOpacity = datum.fillOpacity ?? 1;
|
|
shape.stroke = datum.stroke ?? "transparent";
|
|
shape.strokeWidth = datum.strokeWidth ?? 1;
|
|
shape.strokeOpacity = datum.strokeOpacity ?? 1;
|
|
this.updatePath(datum, bbox);
|
|
}
|
|
getLabelCoords(datum, point) {
|
|
const padding2 = datum.getPadding();
|
|
return {
|
|
x: point.x + padding2.left,
|
|
y: point.y - padding2.bottom
|
|
};
|
|
}
|
|
getHandleStyles(datum) {
|
|
return {
|
|
fill: datum.handle.fill,
|
|
stroke: datum.handle.stroke ?? datum.stroke ?? datum.fill,
|
|
strokeOpacity: datum.handle.strokeOpacity,
|
|
strokeWidth: datum.handle.strokeWidth
|
|
};
|
|
}
|
|
updateAnchor(datum, bbox, context) {
|
|
const anchor = super.updateAnchor(datum, bbox, context);
|
|
const padding2 = datum.getPadding();
|
|
anchor.y -= padding2.bottom + padding2.top;
|
|
return anchor;
|
|
}
|
|
updatePath(datum, bbox) {
|
|
const padding2 = datum.getPadding();
|
|
const { x, y } = bbox;
|
|
let { width: width2, height: height2 } = bbox;
|
|
const { fontSize } = datum;
|
|
const horizontalPadding = padding2.left + padding2.right;
|
|
const verticalPadding = padding2.top + padding2.bottom;
|
|
width2 = width2 + horizontalPadding;
|
|
height2 = Math.max(height2 + verticalPadding, fontSize + verticalPadding);
|
|
const top = y - height2;
|
|
const right = x + width2;
|
|
const cornerRadius = (calcLineHeight(fontSize, ANNOTATION_TEXT_LINE_HEIGHT) + verticalPadding) / 2;
|
|
const { path } = this.shape;
|
|
path.clear();
|
|
path.moveTo(x, y);
|
|
path.lineTo(x, top + cornerRadius);
|
|
drawCorner3(
|
|
path,
|
|
{
|
|
x0: x,
|
|
x1: x + cornerRadius,
|
|
y0: top + cornerRadius,
|
|
y1: top,
|
|
cx: x + cornerRadius,
|
|
cy: top + cornerRadius
|
|
},
|
|
cornerRadius,
|
|
false
|
|
);
|
|
path.lineTo(right - cornerRadius, top);
|
|
drawCorner3(
|
|
path,
|
|
{
|
|
x0: right - cornerRadius,
|
|
x1: right,
|
|
y0: top,
|
|
y1: top + cornerRadius,
|
|
cx: right - cornerRadius,
|
|
cy: top + cornerRadius
|
|
},
|
|
cornerRadius,
|
|
false
|
|
);
|
|
path.lineTo(right, y - cornerRadius);
|
|
drawCorner3(
|
|
path,
|
|
{
|
|
x0: right,
|
|
x1: right - cornerRadius,
|
|
y0: y - cornerRadius,
|
|
y1: y,
|
|
cx: right - cornerRadius,
|
|
cy: y - cornerRadius
|
|
},
|
|
cornerRadius,
|
|
false
|
|
);
|
|
path.closePath();
|
|
}
|
|
containsPoint(x, y) {
|
|
return super.containsPoint(x, y) || this.shape.containsPoint(x, y);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/states/textualPointState.ts
|
|
var TextualPointStateMachine = class extends StateMachine {
|
|
constructor(ctx) {
|
|
const actionCreate = ({ point }) => {
|
|
const datum = this.createDatum();
|
|
datum.set({ x: point.x, y: point.y });
|
|
ctx.create(datum);
|
|
};
|
|
const actionFirstRender = () => {
|
|
this.node?.toggleActive(true);
|
|
ctx.showAnnotationOptions();
|
|
ctx.update();
|
|
};
|
|
const onStartEditing = () => {
|
|
ctx.showTextInput();
|
|
if (this.datum) {
|
|
this.datum.visible = false;
|
|
}
|
|
};
|
|
const onStopEditing = () => {
|
|
ctx.hideTextInput();
|
|
if (this.datum)
|
|
this.datum.visible = true;
|
|
ctx.deselect();
|
|
};
|
|
const actionUpdateTextInputBBox = (bbox) => {
|
|
this.node?.setTextInputBBox(bbox);
|
|
ctx.update();
|
|
};
|
|
const actionColor = ({
|
|
colorPickerType,
|
|
colorOpacity,
|
|
color: color2,
|
|
opacity,
|
|
isMultiColor
|
|
}) => {
|
|
if (!this.datum)
|
|
return;
|
|
if (colorPickerType === "text-color") {
|
|
ctx.updateTextInputColor(color2);
|
|
}
|
|
setColor(this.datum, colorPickerType, colorOpacity, color2, opacity, isMultiColor);
|
|
ctx.update();
|
|
};
|
|
const actionFontSize = (fontSize) => {
|
|
const { datum, node } = this;
|
|
if (!datum || !node || !isTextType(datum))
|
|
return;
|
|
datum.fontSize = fontSize;
|
|
ctx.updateTextInputFontSize(fontSize);
|
|
ctx.update();
|
|
};
|
|
const actionCancel = () => {
|
|
ctx.delete();
|
|
};
|
|
const actionSave = ({ textInputValue, bbox }) => {
|
|
if (bbox != null && textInputValue != null && textInputValue.length > 0) {
|
|
const { datum } = this;
|
|
if (!isTextType(datum)) {
|
|
return;
|
|
}
|
|
const wrappedText = maybeWrapText(datum, textInputValue, bbox.width);
|
|
datum?.set({ text: wrappedText });
|
|
ctx.update();
|
|
ctx.recordAction(`Create ${datum?.type} annotation`);
|
|
} else {
|
|
ctx.delete();
|
|
}
|
|
};
|
|
super("start", {
|
|
start: {
|
|
click: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
dragStart: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
cancel: StateMachine.parent,
|
|
reset: StateMachine.parent
|
|
},
|
|
"waiting-first-render": {
|
|
render: {
|
|
target: "edit",
|
|
action: actionFirstRender
|
|
}
|
|
},
|
|
edit: {
|
|
onEnter: onStartEditing,
|
|
updateTextInputBBox: actionUpdateTextInputBBox,
|
|
color: actionColor,
|
|
fontSize: actionFontSize,
|
|
textInput: [
|
|
{
|
|
guard: guardCancelAndExit,
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
{
|
|
guard: guardSaveAndExit,
|
|
target: StateMachine.parent,
|
|
action: actionSave
|
|
}
|
|
],
|
|
click: {
|
|
target: StateMachine.parent,
|
|
action: actionSave
|
|
},
|
|
dragStart: {
|
|
target: StateMachine.parent,
|
|
action: actionSave
|
|
},
|
|
resize: {
|
|
target: StateMachine.parent,
|
|
action: actionSave
|
|
},
|
|
onExit: onStopEditing,
|
|
cancel: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
}
|
|
}
|
|
});
|
|
this.debug = debugLogger_exports.create(true, "annotations");
|
|
}
|
|
};
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], TextualPointStateMachine.prototype, "datum", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], TextualPointStateMachine.prototype, "node", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/comment/commentState.ts
|
|
var CommentStateMachine = class extends TextualPointStateMachine {
|
|
createDatum() {
|
|
return new CommentProperties();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/comment/commentConfig.ts
|
|
var commentConfig = {
|
|
type: "comment" /* Comment */,
|
|
datum: CommentProperties,
|
|
scene: CommentScene,
|
|
isDatum: CommentProperties.is,
|
|
translate: (node, datum, translation, context) => {
|
|
if (CommentProperties.is(datum) && CommentScene.is(node))
|
|
node.translate(datum, translation, context);
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (CommentProperties.is(datum) && CommentProperties.is(copiedDatum) && CommentScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (CommentProperties.is(datum) && CommentScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new CommentStateMachine({
|
|
...ctx,
|
|
create: createDatum("comment" /* Comment */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/axisLabelScene.ts
|
|
var { calculateLabelTranslation: calculateLabelTranslation2 } = module_support_exports;
|
|
var AxisLabelScene = class extends module_support_exports.Group {
|
|
constructor() {
|
|
super({ name: "AnnotationAxisLabelGroup" });
|
|
this.label = new module_support_exports.Text({ zIndex: 1 });
|
|
this.rect = new module_support_exports.Rect();
|
|
const { label } = this;
|
|
label.fontSize = 12;
|
|
label.fontFamily = "Verdana, sans-serif";
|
|
label.fill = "black";
|
|
label.textBaseline = "middle";
|
|
label.textAlign = "center";
|
|
this.append([this.rect, this.label]);
|
|
}
|
|
update(opts) {
|
|
this.updateLabel(opts);
|
|
this.updateRect(opts);
|
|
this.updatePosition(opts);
|
|
}
|
|
updateLabel({ value, styles, context }) {
|
|
const { fontWeight: fontWeight2, fontSize, fontStyle, fontFamily, textAlign, color: color2 = "white", formatter: formatter2 } = styles;
|
|
const text2 = formatter2 ? formatter2({ value }) : context.formatScaleValue(value, "annotation-label");
|
|
this.label.setProperties({
|
|
fontWeight: fontWeight2,
|
|
fontSize,
|
|
fontStyle,
|
|
fontFamily,
|
|
textAlign,
|
|
fill: color2,
|
|
text: text2
|
|
});
|
|
}
|
|
updateRect({ styles }) {
|
|
const { rect: rect2 } = this;
|
|
const { cornerRadius, fill, fillOpacity, stroke: stroke3, strokeOpacity } = styles;
|
|
rect2.fill = fill;
|
|
rect2.fillOpacity = fillOpacity ?? 1;
|
|
rect2.stroke = stroke3;
|
|
rect2.strokeOpacity = strokeOpacity ?? 1;
|
|
rect2.cornerRadius = cornerRadius ?? 0;
|
|
}
|
|
updatePosition({ x, y, context, styles: { padding: padding2 } }) {
|
|
const { label, rect: rect2 } = this;
|
|
const labelBBox = label.getBBox()?.clone();
|
|
const horizontalPadding = padding2 ?? 8;
|
|
const verticalPadding = padding2 ?? 4;
|
|
const { xTranslation, yTranslation } = calculateLabelTranslation2({
|
|
yDirection: true,
|
|
padding: context.labelPadding,
|
|
position: context.position ?? "left",
|
|
bbox: labelBBox
|
|
});
|
|
labelBBox.grow(horizontalPadding, "horizontal");
|
|
labelBBox.grow(verticalPadding, "vertical");
|
|
const translationX = x + xTranslation;
|
|
const translationY = y + yTranslation;
|
|
label.x = translationX;
|
|
label.y = translationY;
|
|
rect2.y = translationY - Math.round(labelBBox.height / 2);
|
|
rect2.x = translationX - Math.round(labelBBox.width / 2);
|
|
rect2.height = labelBBox.height;
|
|
rect2.width = labelBBox.width;
|
|
}
|
|
};
|
|
AxisLabelScene.className = "AxisLabel";
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/collidableLineScene.ts
|
|
var CollidableLine = class extends module_support_exports.Line {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.growCollisionBox = 9;
|
|
this.clipMask = /* @__PURE__ */ new Map();
|
|
}
|
|
setProperties(styles) {
|
|
super.setProperties(styles);
|
|
this.updateCollisionBBox();
|
|
return this;
|
|
}
|
|
updateCollisionBBox() {
|
|
const { growCollisionBox, strokeWidth, x1, y1, x2, y2 } = this;
|
|
let height2 = strokeWidth + growCollisionBox;
|
|
if (height2 % 2 === 0)
|
|
height2 += 1;
|
|
const topLeft = vector_exports.from(x1, y1 - Math.floor(height2 / 2));
|
|
const bottomRight = vector_exports.from(x2, y2);
|
|
const width2 = vector_exports.distance(topLeft, bottomRight);
|
|
this.collisionBBox = new module_support_exports.BBox(topLeft.x, topLeft.y, width2, height2);
|
|
}
|
|
isPointInPath(pointX, pointY) {
|
|
const { collisionBBox, x1, y1, x2, y2 } = this;
|
|
if (!collisionBBox)
|
|
return false;
|
|
const v1 = vector_exports.from(x1, y1);
|
|
const v2 = vector_exports.from(x2, y2);
|
|
const point = vector_exports.sub(vector_exports.from(pointX, pointY), v1);
|
|
const end3 = vector_exports.sub(v2, v1);
|
|
const rotated = vector_exports.rotate(point, vector_exports.angle(point, end3), v1);
|
|
return collisionBBox.containsPoint(rotated.x, rotated.y) ?? false;
|
|
}
|
|
render(renderCtx) {
|
|
const { clipMask } = this;
|
|
const { ctx } = renderCtx;
|
|
if (clipMask.size === 0) {
|
|
super.render(renderCtx);
|
|
return;
|
|
}
|
|
ctx.save();
|
|
try {
|
|
for (const mask of this.clipMask.values()) {
|
|
const { x, y, radius } = mask;
|
|
ctx.beginPath();
|
|
ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
ctx.ellipse(x, y, radius, radius, 0, Math.PI * 2, 0, true);
|
|
ctx.clip();
|
|
}
|
|
super.render(renderCtx);
|
|
} finally {
|
|
ctx.restore();
|
|
}
|
|
}
|
|
setClipMask(id, mask) {
|
|
const cm = this.clipMask.get(id);
|
|
if (jsonDiff(cm, mask) != null) {
|
|
this.markDirty("CollidableLine");
|
|
}
|
|
if (mask) {
|
|
this.clipMask.set(id, mask);
|
|
} else {
|
|
this.clipMask.delete(id);
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/collidableTextScene.ts
|
|
var CollidableText = class extends module_support_exports.TransformableText {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.growCollisionBox = {
|
|
top: 4,
|
|
right: 4,
|
|
bottom: 4,
|
|
left: 4
|
|
};
|
|
}
|
|
isPointInPath(pointX, pointY) {
|
|
const localPoint = this.fromParentPoint(pointX, pointY);
|
|
const uBBox = this.computeBBoxWithoutTransforms();
|
|
if (!uBBox)
|
|
return false;
|
|
return uBBox.grow(this.growCollisionBox).containsPoint(localPoint.x, localPoint.y);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/utils/lineWithText.ts
|
|
function updateLineText(id, line, coords, textProperties, textNode, text2, lineWidth) {
|
|
if (!text2 || !textNode || !textProperties) {
|
|
line.setClipMask(id);
|
|
return;
|
|
}
|
|
const { alignment, position } = textProperties;
|
|
const numbers = getNumbers(coords, textProperties.fontSize, lineWidth);
|
|
const { point, textBaseline } = positionAndAlignment(numbers, position, alignment);
|
|
setProperties(textNode, text2, textProperties, point, numbers.angle, textBaseline);
|
|
const { x, y, width: width2, height: height2 } = textNode.getBBox();
|
|
const diameter = vector_exports.length(vector_exports.from(width2, height2));
|
|
const clipMask = {
|
|
x: x + width2 / 2,
|
|
y: y + height2 / 2,
|
|
radius: diameter / 2 + vector_exports.length(numbers.offset)
|
|
};
|
|
if (position === "center") {
|
|
line.setClipMask(id, clipMask);
|
|
} else {
|
|
line.setClipMask(id);
|
|
}
|
|
return { clipMask, numbers };
|
|
}
|
|
function updateChannelText(offsetInsideTextLabel, top, bottom, textProperties, lineWidth, textNode, text2) {
|
|
if (!text2 || !textNode)
|
|
return;
|
|
const { alignment, position } = textProperties;
|
|
const [actualTop, actualBottom] = top.y1 <= bottom.y1 ? [top, bottom] : [bottom, top];
|
|
let relativeLine = actualTop;
|
|
if (position === "bottom") {
|
|
relativeLine = actualBottom;
|
|
} else if (position === "inside") {
|
|
relativeLine = {
|
|
x1: (actualTop.x1 + actualBottom.x1) / 2,
|
|
y1: (actualTop.y1 + actualBottom.y1) / 2,
|
|
x2: (actualTop.x2 + actualBottom.x2) / 2,
|
|
y2: (actualTop.y2 + actualBottom.y2) / 2
|
|
};
|
|
}
|
|
const numbers = getNumbers(relativeLine, textProperties.fontSize, lineWidth);
|
|
const { point, textBaseline } = positionAndAlignment(
|
|
numbers,
|
|
position === "inside" ? "center" : position,
|
|
alignment,
|
|
offsetInsideTextLabel
|
|
);
|
|
setProperties(textNode, text2, textProperties, point, numbers.angle, textBaseline);
|
|
}
|
|
function getNumbers(coords, fontSize, strokeWidth) {
|
|
let [left, right] = vector_exports.from(coords);
|
|
if (left.x > right.x)
|
|
[left, right] = [right, left];
|
|
const normal = vector_exports.normalized(vector_exports.sub(right, left));
|
|
const angle2 = vector_exports.angle(normal);
|
|
const inset = vector_exports.multiply(normal, DivariantHandle.HANDLE_SIZE / 2 + (fontSize ?? 14) / 2);
|
|
const offset = vector_exports.multiply(normal, (strokeWidth ?? 2) / 2 + (fontSize ?? 14) / 3);
|
|
return { left, right, normal, angle: angle2, inset, offset };
|
|
}
|
|
function positionAndAlignment({ left, right, normal, angle: angle2, inset, offset }, position, alignment, offsetInsideTextLabel) {
|
|
let point;
|
|
if (alignment === "right") {
|
|
point = vector_exports.sub(right, inset);
|
|
} else if (alignment === "center") {
|
|
point = vector_exports.add(left, vector_exports.multiply(normal, vector_exports.distance(left, right) / 2));
|
|
} else {
|
|
point = vector_exports.add(left, inset);
|
|
}
|
|
let textBaseline = "bottom";
|
|
if (position === "bottom") {
|
|
point = vector_exports.rotate(offset, angle2 + Math.PI / 2, point);
|
|
textBaseline = "top";
|
|
} else if (position === "center" && !offsetInsideTextLabel) {
|
|
textBaseline = "middle";
|
|
} else {
|
|
point = vector_exports.rotate(offset, angle2 - Math.PI / 2, point);
|
|
}
|
|
return { point, textBaseline };
|
|
}
|
|
function setProperties(scene, text2, textProperties, point, angle2, textBaseline) {
|
|
scene.setProperties({
|
|
text: text2,
|
|
x: point.x,
|
|
y: point.y,
|
|
rotation: angle2,
|
|
rotationCenterX: point.x,
|
|
rotationCenterY: point.y,
|
|
fill: textProperties.color,
|
|
fontFamily: textProperties.fontFamily,
|
|
fontSize: textProperties.fontSize,
|
|
fontStyle: textProperties.fontStyle,
|
|
fontWeight: textProperties.fontWeight,
|
|
textAlign: textProperties.alignment,
|
|
textBaseline
|
|
});
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/cross-line/crossLineScene.ts
|
|
var CrossLineScene = class extends AnnotationScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "cross-line";
|
|
this.line = new CollidableLine();
|
|
this.middle = new UnivariantHandle();
|
|
this.isHorizontal = false;
|
|
this.append([this.line, this.middle]);
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "cross-line");
|
|
}
|
|
update(datum, context) {
|
|
const { seriesRect } = context;
|
|
this.seriesRect = seriesRect;
|
|
this.isHorizontal = HorizontalLineProperties.is(datum);
|
|
const axisContext = this.isHorizontal ? context.yAxis : context.xAxis;
|
|
const coords = this.convertCrossLine(datum, axisContext);
|
|
if (coords == null) {
|
|
this.visible = false;
|
|
return;
|
|
}
|
|
this.visible = datum.visible ?? true;
|
|
if (!this.visible)
|
|
return;
|
|
this.updateLine(datum, coords);
|
|
this.updateHandle(datum, coords);
|
|
this.updateText(datum, coords);
|
|
this.updateAxisLabel(datum, axisContext, coords);
|
|
}
|
|
updateLine(datum, coords) {
|
|
const { line } = this;
|
|
const { lineDashOffset, stroke: stroke3, strokeWidth, strokeOpacity } = datum;
|
|
const { x1, y1, x2, y2 } = coords;
|
|
line.setProperties({
|
|
x1,
|
|
y1,
|
|
x2,
|
|
y2,
|
|
lineCap: datum.getLineCap(),
|
|
lineDash: datum.getLineDash(),
|
|
lineDashOffset,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
fillOpacity: 0
|
|
});
|
|
}
|
|
updateHandle(datum, coords) {
|
|
const { middle } = this;
|
|
const { locked, stroke: stroke3, strokeWidth, strokeOpacity } = datum;
|
|
const handleStyles = {
|
|
fill: datum.handle.fill,
|
|
stroke: datum.handle.stroke ?? stroke3,
|
|
strokeOpacity: datum.handle.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: datum.handle.strokeWidth ?? strokeWidth
|
|
};
|
|
const handlePosition = vector_exports.sub(
|
|
vector4_exports.center(coords),
|
|
vector_exports.from(middle.handle.width / 2, middle.handle.height / 2)
|
|
);
|
|
middle.gradient = this.isHorizontal ? "horizontal" : "vertical";
|
|
middle.update({ ...handleStyles, ...handlePosition });
|
|
middle.toggleLocked(locked ?? false);
|
|
}
|
|
updateText(datum, coords) {
|
|
this.text = this.updateNode(CollidableText, this.text, !!datum.text.label);
|
|
updateLineText(this.line.id, this.line, coords, datum.text, this.text, datum.text.label, datum.strokeWidth);
|
|
}
|
|
createAxisLabel(context) {
|
|
const axisLabel3 = new AxisLabelScene();
|
|
context.attachLabel(axisLabel3);
|
|
return axisLabel3;
|
|
}
|
|
updateAxisLabel(datum, axisContext, coords) {
|
|
this.axisLabel ?? (this.axisLabel = this.createAxisLabel(axisContext));
|
|
const { axisLabel: axisLabel3, seriesRect } = this;
|
|
const { direction, position } = axisContext;
|
|
if (datum.axisLabel.enabled) {
|
|
axisLabel3.visible = this.visible;
|
|
const labelCorner = position === "left" || position === "top" ? vector4_exports.start(coords) : vector4_exports.end(coords);
|
|
const labelPosition = direction === "x" /* X */ ? labelCorner.x : labelCorner.y;
|
|
if (!axisContext.inRange(labelPosition)) {
|
|
axisLabel3.visible = false;
|
|
return;
|
|
}
|
|
const value = getGroupingValue(datum.value);
|
|
axisLabel3.update({
|
|
...vector_exports.add(labelCorner, vector_exports.required(seriesRect)),
|
|
value,
|
|
styles: datum.axisLabel,
|
|
context: axisContext
|
|
});
|
|
} else {
|
|
axisLabel3.visible = false;
|
|
}
|
|
}
|
|
setAxisLabelOpacity(opacity) {
|
|
if (!this.axisLabel)
|
|
return;
|
|
this.axisLabel.opacity = opacity;
|
|
}
|
|
setAxisLabelVisible(visible) {
|
|
if (!this.axisLabel)
|
|
return;
|
|
this.axisLabel.visible = visible;
|
|
}
|
|
toggleHandles(show) {
|
|
this.middle.visible = show;
|
|
this.middle.toggleHovered(this.activeHandle === "middle");
|
|
}
|
|
destroy() {
|
|
super.destroy();
|
|
this.axisLabel?.destroy();
|
|
}
|
|
toggleActive(active) {
|
|
this.toggleHandles(active);
|
|
this.middle.toggleActive(active);
|
|
}
|
|
dragStart(datum, target, context) {
|
|
const middle = HorizontalLineProperties.is(datum) ? { x: target.x, y: convert(datum.value, context.yAxis) } : { x: convert(datum.value, context.xAxis), y: target.y };
|
|
this.dragState = {
|
|
offset: target,
|
|
middle
|
|
};
|
|
}
|
|
drag(datum, target, context) {
|
|
const { activeHandle, dragState } = this;
|
|
if (!datum.isWriteable() || !dragState)
|
|
return;
|
|
if (activeHandle) {
|
|
this[activeHandle].toggleDragging(true);
|
|
}
|
|
this.translatePoint(datum, dragState.middle, vector_exports.sub(target, dragState.offset), context);
|
|
}
|
|
translate(datum, translation, context) {
|
|
if (!datum.isWriteable())
|
|
return;
|
|
const vector = HorizontalLineProperties.is(datum) ? vector_exports.from(0, convert(datum.value, context.yAxis)) : vector_exports.from(convert(datum.value, context.xAxis), 0);
|
|
this.translatePoint(datum, vector, translation, context);
|
|
}
|
|
translatePoint(datum, value, translation, context) {
|
|
const isHorizontal2 = HorizontalLineProperties.is(datum);
|
|
if (isHorizontal2) {
|
|
translation.x = 0;
|
|
} else {
|
|
translation.y = 0;
|
|
}
|
|
const { point } = translate({ point: value }, translation, context);
|
|
datum.value = isHorizontal2 ? point.y : point.x;
|
|
}
|
|
stopDragging() {
|
|
this.middle.toggleDragging(false);
|
|
}
|
|
copy(datum, copiedDatum, context) {
|
|
const isHorizontal2 = HorizontalLineProperties.is(datum);
|
|
const axisContext = this.isHorizontal ? context.yAxis : context.xAxis;
|
|
const coords = this.convertCrossLine(datum, axisContext);
|
|
if (!coords) {
|
|
return;
|
|
}
|
|
const yOffset = isHorizontal2 ? -30 : 0;
|
|
const xOffset = isHorizontal2 ? 0 : -30;
|
|
const point = invertCoords({ x: coords.x1 + xOffset, y: coords.y1 + yOffset }, context);
|
|
copiedDatum.set({ value: isHorizontal2 ? point.y : point.x });
|
|
return copiedDatum;
|
|
}
|
|
getCursor() {
|
|
if (this.activeHandle == null)
|
|
return "pointer";
|
|
return this[this.activeHandle].getCursor();
|
|
}
|
|
containsPoint(x, y) {
|
|
const { middle, line, text: text2 } = this;
|
|
this.activeHandle = void 0;
|
|
if (middle.containsPoint(x, y)) {
|
|
this.activeHandle = "middle";
|
|
return true;
|
|
}
|
|
return line.isPointInPath(x, y) || Boolean(text2?.containsPoint(x, y));
|
|
}
|
|
getNodeAtCoords(x, y) {
|
|
if (this.text?.containsPoint(x, y))
|
|
return "text";
|
|
if (this.line.isPointInPath(x, y))
|
|
return "line";
|
|
if (this.middle.containsPoint(x, y))
|
|
return "handle";
|
|
}
|
|
getAnchor() {
|
|
const bbox = this.computeBBoxWithoutHandles();
|
|
if (this.isHorizontal) {
|
|
return { x: bbox.x + bbox.width / 2, y: bbox.y };
|
|
}
|
|
return { x: bbox.x + bbox.width, y: bbox.y + bbox.height / 2, position: "right" };
|
|
}
|
|
convertCrossLine(datum, context) {
|
|
if (datum.value == null)
|
|
return;
|
|
let x1 = 0;
|
|
let y1 = 0;
|
|
let x2, y2;
|
|
const { bounds } = context;
|
|
const scaledValue = convert(datum.value, context);
|
|
if (HorizontalLineProperties.is(datum)) {
|
|
x2 = bounds.width;
|
|
y1 = scaledValue;
|
|
y2 = scaledValue;
|
|
} else {
|
|
x1 = scaledValue;
|
|
x2 = scaledValue;
|
|
y2 = bounds.height;
|
|
}
|
|
return { x1, y1, x2, y2 };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/cross-line/crossLineState.ts
|
|
var CrossLineStateMachine = class extends StateMachine {
|
|
constructor(direction, ctx) {
|
|
const onClick = ({ point }) => {
|
|
const isHorizontal2 = direction === "horizontal";
|
|
const datum = isHorizontal2 ? new HorizontalLineProperties() : new VerticalLineProperties();
|
|
datum.set({ value: isHorizontal2 ? point.y : point.x });
|
|
ctx.create(datum);
|
|
ctx.recordAction(
|
|
`Create ${isHorizontal2 ? "horizontal-line" /* HorizontalLine */ : "vertical-line" /* VerticalLine */} annotation`
|
|
);
|
|
};
|
|
const actionFirstRender = () => {
|
|
this.node?.toggleActive(true);
|
|
ctx.showAnnotationOptions();
|
|
ctx.update();
|
|
};
|
|
super("start", {
|
|
start: {
|
|
click: {
|
|
target: "waiting-first-render",
|
|
action: onClick
|
|
},
|
|
drag: {
|
|
target: "waiting-first-render",
|
|
action: onClick
|
|
},
|
|
reset: StateMachine.parent,
|
|
cancel: StateMachine.parent
|
|
},
|
|
"waiting-first-render": {
|
|
render: {
|
|
target: StateMachine.parent,
|
|
action: actionFirstRender
|
|
}
|
|
}
|
|
});
|
|
this.debug = debugLogger_exports.create(true, "annotations");
|
|
}
|
|
};
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], CrossLineStateMachine.prototype, "node", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/cross-line/crossLineConfig.ts
|
|
var horizontalLineConfig = {
|
|
type: "horizontal-line" /* HorizontalLine */,
|
|
datum: HorizontalLineProperties,
|
|
scene: CrossLineScene,
|
|
isDatum: HorizontalLineProperties.is,
|
|
translate: (node, datum, translation, context) => {
|
|
if (HorizontalLineProperties.is(datum) && CrossLineScene.is(node))
|
|
node.translate(datum, translation, context);
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (HorizontalLineProperties.is(datum) && HorizontalLineProperties.is(copiedDatum) && CrossLineScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (HorizontalLineProperties.is(datum) && CrossLineScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new CrossLineStateMachine("horizontal", {
|
|
...ctx,
|
|
create: createDatum("horizontal-line" /* HorizontalLine */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
var verticalLineConfig = {
|
|
type: "vertical-line" /* VerticalLine */,
|
|
datum: VerticalLineProperties,
|
|
scene: CrossLineScene,
|
|
isDatum: VerticalLineProperties.is,
|
|
translate: (node, datum, translation, context) => {
|
|
if (VerticalLineProperties.is(datum) && CrossLineScene.is(node))
|
|
node.translate(datum, translation, context);
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (VerticalLineProperties.is(datum) && VerticalLineProperties.is(copiedDatum) && CrossLineScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (VerticalLineProperties.is(datum) && CrossLineScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new CrossLineStateMachine("vertical", {
|
|
...ctx,
|
|
create: createDatum("vertical-line" /* VerticalLine */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/withBackgroundScene.ts
|
|
var WithBackgroundScene = class {
|
|
static updateBackground(datum, top, bottom, context) {
|
|
const { background } = this;
|
|
const { seriesRect } = context;
|
|
background.path.clear(true);
|
|
const bounds = vector4_exports.from(0, 0, seriesRect.width, seriesRect.height);
|
|
const points = this.getBackgroundPoints(datum, top, bottom, bounds);
|
|
for (let i = 0; i < points.length; i++) {
|
|
const point = points[i];
|
|
if (i === 0) {
|
|
background.path.moveTo(point.x, point.y);
|
|
} else {
|
|
background.path.lineTo(point.x, point.y);
|
|
}
|
|
}
|
|
background.path.closePath();
|
|
background.checkPathDirty();
|
|
const backgroundStyles = this.getBackgroundStyles?.(datum) ?? datum.background;
|
|
background.fill = backgroundStyles.fill;
|
|
background.fillOpacity = backgroundStyles.fillOpacity ?? 1;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/channelScene.ts
|
|
var ChannelScene = class extends LinearScene {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.handles = {};
|
|
this.overflowContinuous = 2;
|
|
this.topLine = new CollidableLine();
|
|
this.bottomLine = new CollidableLine();
|
|
this.background = new module_support_exports.Path({ zIndex: -1 });
|
|
this.anchor = { x: 0, y: 0 };
|
|
this.updateBackground = WithBackgroundScene.updateBackground.bind(this);
|
|
}
|
|
update(datum, context) {
|
|
const { locked, visible } = datum;
|
|
const top = convertLine(datum, context);
|
|
const bottom = convertLine(datum.bottom, context);
|
|
if (top == null || bottom == null) {
|
|
this.visible = false;
|
|
return;
|
|
} else {
|
|
this.visible = visible ?? true;
|
|
}
|
|
const topLine = this.extendLine(top, datum, context);
|
|
const bottomLine = this.extendLine(bottom, datum, context);
|
|
this.updateLines(datum, topLine, bottomLine, context, top, bottom);
|
|
this.updateHandles(datum, top, bottom);
|
|
this.updateText(datum, top, bottom);
|
|
this.updateBackground(datum, topLine, bottomLine, context);
|
|
this.updateAnchor(top, bottom);
|
|
for (const handle3 of Object.values(this.handles)) {
|
|
handle3.toggleLocked(locked ?? false);
|
|
}
|
|
}
|
|
toggleHandles(show) {
|
|
const { handles } = this;
|
|
if (typeof show === "boolean") {
|
|
for (const [handle3, node] of Object.entries(handles)) {
|
|
node.visible = show;
|
|
node.toggleHovered(this.activeHandle === handle3);
|
|
}
|
|
return;
|
|
}
|
|
for (const [handle3, visible] of Object.entries(show)) {
|
|
const node = handles[handle3];
|
|
node.visible = visible ?? true;
|
|
node.toggleHovered(this.activeHandle === handle3);
|
|
}
|
|
}
|
|
toggleActive(active) {
|
|
this.toggleHandles(active);
|
|
for (const node of Object.values(this.handles)) {
|
|
node.toggleActive(active);
|
|
}
|
|
}
|
|
stopDragging() {
|
|
const { activeHandle, handles } = this;
|
|
if (activeHandle == null)
|
|
return;
|
|
handles[activeHandle].toggleDragging(false);
|
|
}
|
|
getAnchor() {
|
|
return this.anchor;
|
|
}
|
|
getCursor() {
|
|
if (this.activeHandle == null)
|
|
return "pointer";
|
|
return this.handles[this.activeHandle].getCursor();
|
|
}
|
|
containsPoint(x, y) {
|
|
const { handles, topLine, bottomLine, text: text2 } = this;
|
|
this.activeHandle = void 0;
|
|
for (const [handle3, child] of Object.entries(handles)) {
|
|
if (child.containsPoint(x, y)) {
|
|
this.activeHandle = handle3;
|
|
return true;
|
|
}
|
|
}
|
|
return topLine.containsPoint(x, y) || bottomLine.containsPoint(x, y) || Boolean(text2?.containsPoint(x, y));
|
|
}
|
|
getNodeAtCoords(x, y) {
|
|
if (this.text?.containsPoint(x, y))
|
|
return "text";
|
|
if (this.topLine.containsPoint(x, y) || this.bottomLine.containsPoint(x, y))
|
|
return "line";
|
|
for (const [, child] of Object.entries(this.handles)) {
|
|
if (child.containsPoint(x, y))
|
|
return "handle";
|
|
}
|
|
}
|
|
updateAnchor(top, bottom) {
|
|
const { x, y } = module_support_exports.Transformable.toCanvasPoint(
|
|
this.topLine,
|
|
(top.x1 + top.x2) / 2,
|
|
Math.min(top.y1, top.y2, bottom.y1, bottom.y2)
|
|
);
|
|
this.anchor.x = x;
|
|
this.anchor.y = y;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/disjoint-channel/disjointChannelScene.ts
|
|
var DisjointChannelScene = class extends ChannelScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "disjoint-channel";
|
|
this.handles = {
|
|
topLeft: new DivariantHandle(),
|
|
topRight: new DivariantHandle(),
|
|
bottomLeft: new DivariantHandle(),
|
|
bottomRight: new UnivariantHandle()
|
|
};
|
|
this.append([this.background, this.topLine, this.bottomLine, ...Object.values(this.handles)]);
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "disjoint-channel");
|
|
}
|
|
dragHandle(datum, target, context, snapping) {
|
|
const { activeHandle, handles } = this;
|
|
if (activeHandle == null)
|
|
return;
|
|
const { offset } = handles[activeHandle].drag(target);
|
|
handles[activeHandle].toggleDragging(true);
|
|
if (activeHandle === "bottomRight") {
|
|
offset.x = 0;
|
|
}
|
|
let translateVectors = [];
|
|
let invertYVectors = [];
|
|
let allowSnapping = snapping;
|
|
switch (activeHandle) {
|
|
case "topLeft":
|
|
translateVectors = ["topLeft"];
|
|
invertYVectors = ["bottomLeft"];
|
|
break;
|
|
case "bottomLeft":
|
|
translateVectors = ["bottomLeft"];
|
|
invertYVectors = ["topLeft"];
|
|
break;
|
|
case "topRight":
|
|
translateVectors = ["topRight"];
|
|
invertYVectors = ["bottomRight"];
|
|
break;
|
|
case "bottomRight":
|
|
translateVectors = ["bottomLeft", "bottomRight"];
|
|
allowSnapping = false;
|
|
break;
|
|
}
|
|
const top = convertLine(datum, context);
|
|
const bottom = convertLine(datum.bottom, context);
|
|
if (!top || !bottom)
|
|
return;
|
|
const vectors = {
|
|
topLeft: vector4_exports.start(top),
|
|
topRight: vector4_exports.end(top),
|
|
bottomLeft: vector4_exports.start(bottom),
|
|
bottomRight: vector4_exports.end(bottom)
|
|
};
|
|
const snap = {
|
|
vectors: {
|
|
topLeft: vectors.topRight,
|
|
bottomLeft: vectors.bottomRight,
|
|
topRight: vectors.topLeft,
|
|
bottomRight: vectors.bottomLeft
|
|
},
|
|
angle: datum.snapToAngle
|
|
};
|
|
const points = translate(vectors, offset, context, {
|
|
overflowContinuous: this.overflowContinuous,
|
|
translateVectors,
|
|
invertYVectors,
|
|
snap: allowSnapping ? snap : void 0
|
|
});
|
|
datum.start.x = points.topLeft.x;
|
|
datum.start.y = points.topLeft.y;
|
|
datum.end.x = points.topRight.x;
|
|
datum.end.y = points.topRight.y;
|
|
datum.startHeight = points.topLeft.y - points.bottomLeft.y;
|
|
datum.endHeight = points.topRight.y - points.bottomRight.y;
|
|
}
|
|
getTranslatePointsVectors(start2, end3) {
|
|
const { bottomLeft, bottomRight, topLeft, topRight } = this.handles;
|
|
const startHeight = bottomLeft.getBBox().y - topLeft.getBBox().y;
|
|
const endHeight = bottomRight.getBBox().y - topRight.getBBox().y;
|
|
const bottomStart = vector_exports.add(start2, vector_exports.from(0, startHeight));
|
|
const bottomEnd = vector_exports.add(end3, vector_exports.from(0, endHeight));
|
|
return { start: start2, end: end3, bottomStart, bottomEnd };
|
|
}
|
|
updateLines(datum, top, bottom) {
|
|
const { topLine, bottomLine } = this;
|
|
const { lineDashOffset, stroke: stroke3, strokeOpacity, strokeWidth } = datum;
|
|
const lineStyles = {
|
|
lineCap: datum.getLineCap(),
|
|
lineDash: datum.getLineDash(),
|
|
lineDashOffset,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth
|
|
};
|
|
topLine.setProperties({ ...top, ...lineStyles });
|
|
bottomLine.setProperties({ ...bottom, ...lineStyles });
|
|
}
|
|
updateHandles(datum, top, bottom) {
|
|
const {
|
|
handles: { topLeft, topRight, bottomLeft, bottomRight }
|
|
} = this;
|
|
const handleStyles = {
|
|
fill: datum.handle.fill,
|
|
stroke: datum.handle.stroke ?? datum.stroke,
|
|
strokeOpacity: datum.handle.strokeOpacity ?? datum.strokeOpacity,
|
|
strokeWidth: datum.handle.strokeWidth ?? datum.strokeWidth
|
|
};
|
|
topLeft.update({ ...handleStyles, ...vector4_exports.start(top) });
|
|
topRight.update({ ...handleStyles, ...vector4_exports.end(top) });
|
|
bottomLeft.update({ ...handleStyles, ...vector4_exports.start(bottom) });
|
|
bottomRight.update({
|
|
...handleStyles,
|
|
...vector_exports.sub(vector4_exports.end(bottom), vector_exports.from(bottomRight.handle.width / 2, bottomRight.handle.height / 2))
|
|
});
|
|
}
|
|
updateText(datum, top, bottom) {
|
|
this.text = this.updateNode(CollidableText, this.text, !!datum.text.label);
|
|
updateChannelText(false, top, bottom, datum.text, datum.strokeWidth, this.text, datum.text.label);
|
|
}
|
|
getBackgroundPoints(datum, top, bottom, bounds) {
|
|
const isFlippedX = top.x1 > top.x2;
|
|
const isFlippedY = top.y1 > top.y2;
|
|
const topY = isFlippedY ? bounds.y2 : bounds.y1;
|
|
const bottomY = isFlippedY ? bounds.y1 : bounds.y2;
|
|
const points = vector_exports.from(top);
|
|
if (datum.extendEnd && top.y2 === bottomY) {
|
|
points.push(vector_exports.from(isFlippedX ? bounds.x1 : bounds.x2, isFlippedY ? bounds.y1 : bounds.y2));
|
|
}
|
|
if (datum.extendEnd && bottom.y2 === topY) {
|
|
points.push(vector_exports.from(isFlippedX ? bounds.x1 : bounds.x2, isFlippedY ? bounds.y2 : bounds.y1));
|
|
}
|
|
points.push(...vector_exports.from(bottom).reverse());
|
|
if (datum.extendStart && bottom.y1 === bottomY) {
|
|
points.push(vector_exports.from(isFlippedX ? bounds.x2 : bounds.x1, isFlippedY ? bounds.y1 : bounds.y2));
|
|
}
|
|
if (datum.extendStart && top.y1 === topY) {
|
|
points.push(vector_exports.from(isFlippedX ? bounds.x2 : bounds.x1, isFlippedY ? bounds.y2 : bounds.y1));
|
|
}
|
|
return points;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/disjoint-channel/disjointChannelState.ts
|
|
var DisjointChannelStateMachine = class extends StateMachine {
|
|
constructor(ctx) {
|
|
const actionCreate = ({ point }) => {
|
|
const datum = new DisjointChannelProperties();
|
|
datum.set({ start: point, end: point, startHeight: 0, endHeight: 0 });
|
|
ctx.create(datum);
|
|
};
|
|
const actionFirstRender = () => {
|
|
const { node } = this;
|
|
node?.toggleActive(true);
|
|
node?.toggleHandles({ topLeft: true, topRight: false, bottomLeft: false, bottomRight: false });
|
|
};
|
|
const actionEndUpdate = ({ offset, context }) => {
|
|
const { datum, snapping } = this;
|
|
if (!datum)
|
|
return;
|
|
datum.set({ end: snapPoint(offset, context, snapping, datum.start, datum.snapToAngle) });
|
|
ctx.update();
|
|
};
|
|
const actionEndFinish = () => {
|
|
this.node?.toggleHandles({ topRight: true });
|
|
ctx.update();
|
|
};
|
|
const actionHeightUpdate = ({ point }) => {
|
|
const { datum, node } = this;
|
|
const endY = getGroupingValue(datum?.end.y);
|
|
const startY = getGroupingValue(datum?.start.y);
|
|
const { y: pointY } = point;
|
|
if (datum == null || !isNumber(startY) || !isNumber(endY) || !isNumber(pointY))
|
|
return;
|
|
const endHeight = endY - (pointY ?? 0);
|
|
const startHeight = (startY - endY) * 2 + endHeight;
|
|
const bottomStart = { x: datum?.start.x, y: startY - startHeight };
|
|
const bottomEnd = { x: datum?.end.x, y: point.y };
|
|
node?.toggleHandles({ bottomLeft: true, bottomRight: true });
|
|
if (!ctx.validatePoint(bottomStart, { overflowContinuous: true }) || !ctx.validatePoint(bottomEnd, { overflowContinuous: true })) {
|
|
return;
|
|
}
|
|
datum.set({ startHeight, endHeight });
|
|
ctx.update();
|
|
};
|
|
const actionHeightFinish = ({ point }) => {
|
|
const { datum, node } = this;
|
|
const endY = getGroupingValue(datum?.end.y);
|
|
const startY = getGroupingValue(datum?.start.y);
|
|
const { y: pointY } = point;
|
|
if (datum == null || !isNumber(startY) || !isNumber(endY) || !isNumber(pointY))
|
|
return;
|
|
const endHeight = endY - (pointY ?? 0);
|
|
const startHeight = (startY - endY) * 2 + endHeight;
|
|
const bottomStart = { x: datum.start.x, y: startY - endHeight };
|
|
const bottomEnd = { x: datum.end.x, y: point.y };
|
|
node?.toggleHandles(true);
|
|
if (!ctx.validatePoint(bottomStart, { overflowContinuous: true }) || !ctx.validatePoint(bottomEnd, { overflowContinuous: true })) {
|
|
return;
|
|
}
|
|
datum.set({ startHeight, endHeight });
|
|
ctx.recordAction(`Create ${"disjoint-channel" /* DisjointChannel */} annotation`);
|
|
ctx.showAnnotationOptions();
|
|
ctx.update();
|
|
};
|
|
const actionCancel = () => ctx.delete();
|
|
super("start", {
|
|
start: {
|
|
click: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
drag: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
reset: StateMachine.parent
|
|
},
|
|
"waiting-first-render": {
|
|
render: {
|
|
target: "end",
|
|
action: actionFirstRender
|
|
}
|
|
},
|
|
end: {
|
|
hover: actionEndUpdate,
|
|
drag: actionEndUpdate,
|
|
click: {
|
|
target: "height",
|
|
action: actionEndFinish
|
|
},
|
|
dragEnd: {
|
|
target: "height",
|
|
action: actionEndFinish
|
|
},
|
|
reset: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
cancel: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
}
|
|
},
|
|
height: {
|
|
hover: actionHeightUpdate,
|
|
click: {
|
|
target: StateMachine.parent,
|
|
action: actionHeightFinish
|
|
},
|
|
drag: {
|
|
target: StateMachine.parent,
|
|
action: actionHeightFinish
|
|
},
|
|
reset: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
cancel: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
}
|
|
}
|
|
});
|
|
this.debug = debugLogger_exports.create(true, "annotations");
|
|
this.snapping = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], DisjointChannelStateMachine.prototype, "datum", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], DisjointChannelStateMachine.prototype, "node", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], DisjointChannelStateMachine.prototype, "snapping", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/disjoint-channel/disjointChannelConfig.ts
|
|
var disjointChannelConfig = {
|
|
type: "disjoint-channel" /* DisjointChannel */,
|
|
datum: DisjointChannelProperties,
|
|
scene: DisjointChannelScene,
|
|
isDatum: DisjointChannelProperties.is,
|
|
translate: (node, datum, transition, context) => {
|
|
if (DisjointChannelProperties.is(datum) && DisjointChannelScene.is(node)) {
|
|
node.translate(datum, transition, context);
|
|
}
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (DisjointChannelProperties.is(datum) && DisjointChannelProperties.is(copiedDatum) && DisjointChannelScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (DisjointChannelProperties.is(datum) && DisjointChannelScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new DisjointChannelStateMachine({
|
|
...ctx,
|
|
create: createDatum("disjoint-channel" /* DisjointChannel */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/utils/fibonacci.ts
|
|
var FIBONACCI_RETRACEMENT_RATIOS = [0, 23.6, 38.2, 50, 61.8, 78.6, 100];
|
|
var FIBONACCI_EXTENSION_RATIOS = [161.8, 261.8, 361.8, 423.6];
|
|
var FIBONACCI_RATIOS = [...FIBONACCI_RETRACEMENT_RATIOS, ...FIBONACCI_EXTENSION_RATIOS];
|
|
var FIBONACCI_RATIOS_MAP = {
|
|
10: FIBONACCI_RATIOS,
|
|
6: FIBONACCI_RETRACEMENT_RATIOS,
|
|
4: FIBONACCI_RETRACEMENT_RATIOS.filter((r) => r !== 78.6 && r !== 23.6)
|
|
};
|
|
var FIBONACCI_RANGE_LABEL_PADDING = 10;
|
|
function getFibonacciCoords(coords1, coords2) {
|
|
const { x2, y1, y2 } = coords1;
|
|
const trendLineVerticalDistance = y1 - y2;
|
|
if (coords2 == null) {
|
|
return {
|
|
x1: x2,
|
|
x2,
|
|
y1: y2 - trendLineVerticalDistance,
|
|
y2
|
|
};
|
|
}
|
|
return {
|
|
x1: coords2.x1,
|
|
x2: coords2.x2,
|
|
y1: coords2.y2 - trendLineVerticalDistance,
|
|
y2: coords2.y2
|
|
};
|
|
}
|
|
function createFibonacciRangesData({ x1, y1, x2, y2 }, context, reverse, yZero, bands = 10) {
|
|
const verticalDistance = y1 - y2;
|
|
const direction = reverse ? -1 : 1;
|
|
let startY = yZero;
|
|
const data = [];
|
|
for (const [index, ratio2] of FIBONACCI_RATIOS_MAP[bands].entries()) {
|
|
const endY = yZero + verticalDistance * (ratio2 / 100) * direction;
|
|
const yDatumVal = context.yAxis.scaleInvert(endY);
|
|
data.push({
|
|
id: index,
|
|
x1,
|
|
x2,
|
|
y1: startY,
|
|
y2: endY,
|
|
tag: ratio2 == 100 ? 0 /* OneLine */ : 1 /* HorizontalLine */,
|
|
label: {
|
|
x1: Math.min(x1, x2) - FIBONACCI_RANGE_LABEL_PADDING,
|
|
x2,
|
|
y1: endY,
|
|
y2: endY,
|
|
text: `${(ratio2 / 100).toFixed(3)} (${yDatumVal.toFixed(2)})`
|
|
}
|
|
});
|
|
startY = endY;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/fibonacciScene.ts
|
|
var FibonacciScene = class extends AnnotationScene {
|
|
constructor() {
|
|
super();
|
|
this.trendLine = new CollidableLine();
|
|
this.rangeFillsGroup = new module_support_exports.Group({
|
|
name: `${this.id}-range-fills`
|
|
});
|
|
this.rangeFillsGroupSelection = module_support_exports.Selection.select(this.rangeFillsGroup, module_support_exports.Range);
|
|
this.rangeStrokesGroup = new module_support_exports.Group({
|
|
name: `${this.id}-range-strokes`
|
|
});
|
|
this.rangeStrokesGroupSelection = module_support_exports.Selection.select(this.rangeStrokesGroup, CollidableLine);
|
|
this.labelsGroup = new module_support_exports.Group({
|
|
name: `${this.id}-ranges-labels`
|
|
});
|
|
this.labelsGroupSelection = module_support_exports.Selection.select(this.labelsGroup, CollidableText);
|
|
this.anchor = {
|
|
x: 0,
|
|
y: 0,
|
|
position: "above"
|
|
};
|
|
this.append([this.trendLine, this.rangeFillsGroup, this.rangeStrokesGroup, this.labelsGroup]);
|
|
}
|
|
update(datum, context) {
|
|
let coords = convertLine(datum, context);
|
|
if (coords == null) {
|
|
this.visible = false;
|
|
return;
|
|
}
|
|
coords = vector4_exports.round(coords);
|
|
this.visible = datum.visible ?? true;
|
|
if (!this.visible)
|
|
return;
|
|
this.updateLine(datum, coords, this.trendLine);
|
|
this.updateHandles(datum, coords);
|
|
this.updateAnchor(datum, coords, context);
|
|
const { reverse } = datum;
|
|
const extendedCoords = this.extendLine(coords, datum, context);
|
|
const yZero = reverse ? extendedCoords.y1 : extendedCoords.y2;
|
|
const yOne = reverse ? extendedCoords.y2 : extendedCoords.y1;
|
|
const data = createFibonacciRangesData(extendedCoords, context, datum.reverse, yZero, datum.bands);
|
|
this.updateRanges(datum, data, context);
|
|
const oneLinePoints = { ...extendedCoords, y1: yOne, y2: yOne };
|
|
this.updateText(datum, oneLinePoints);
|
|
}
|
|
extendLine({ x1, y1, x2, y2 }, datum, context) {
|
|
const linePoints = { x1, y1, x2, y2 };
|
|
if (!datum.extendStart && !datum.extendEnd) {
|
|
return linePoints;
|
|
}
|
|
const { x, width: width2 } = context.xAxis.bounds;
|
|
if (datum.extendEnd) {
|
|
linePoints[x1 > x2 ? "x1" : "x2"] = x + width2;
|
|
}
|
|
if (datum.extendStart) {
|
|
linePoints[x1 > x2 ? "x2" : "x1"] = x;
|
|
}
|
|
return linePoints;
|
|
}
|
|
updateLine(datum, coords, line) {
|
|
if (!coords || !line) {
|
|
return;
|
|
}
|
|
const { lineDashOffset, strokeWidth, strokeOpacity, stroke: stroke3 } = datum;
|
|
line.setProperties({
|
|
...coords,
|
|
lineCap: datum.getLineCap(),
|
|
lineDash: [3, 4],
|
|
lineDashOffset,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
fillOpacity: 0,
|
|
stroke: stroke3
|
|
});
|
|
}
|
|
updateRangeStrokes(datum) {
|
|
const { lineDashOffset, strokeWidth, strokeOpacity, strokes, rangeStroke, isMultiColor } = datum;
|
|
this.rangeStrokesGroupSelection.each((line, { x1, x2, y2, tag }, index) => {
|
|
const y = y2;
|
|
const color2 = isMultiColor ? strokes[index % strokes.length] : rangeStroke;
|
|
line.setProperties({
|
|
x1,
|
|
x2,
|
|
y1: y,
|
|
y2: y,
|
|
stroke: color2,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineCap: datum.getLineCap(),
|
|
lineDash: datum.getLineDash(),
|
|
lineDashOffset,
|
|
tag
|
|
});
|
|
});
|
|
}
|
|
updateRanges(datum, data, context) {
|
|
const getDatumId = (d) => d.id;
|
|
this.rangeFillsGroupSelection.update(data, void 0, getDatumId);
|
|
this.rangeStrokesGroupSelection.update(data, void 0, getDatumId);
|
|
this.labelsGroupSelection.update(data, void 0, getDatumId);
|
|
this.updateRangeFills(datum);
|
|
this.updateRangeStrokes(datum);
|
|
this.updateRangeLabels(datum, context);
|
|
}
|
|
updateRangeFills(datum) {
|
|
const {
|
|
lineDashOffset,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
strokes: colors,
|
|
rangeStroke,
|
|
showFill,
|
|
isMultiColor
|
|
} = datum;
|
|
this.rangeFillsGroupSelection.each((range3, { x1, x2, y1, y2 }, index) => {
|
|
const color2 = isMultiColor ? colors[index % colors.length] : rangeStroke;
|
|
if (!showFill) {
|
|
range3.visible = false;
|
|
return;
|
|
}
|
|
range3.setProperties({
|
|
x1,
|
|
x2,
|
|
y1,
|
|
y2,
|
|
startLine: false,
|
|
endLine: false,
|
|
stroke: color2,
|
|
strokeOpacity,
|
|
fill: color2,
|
|
fillOpacity: (strokeOpacity ?? 1) * 0.15,
|
|
strokeWidth,
|
|
lineCap: datum.getLineCap(),
|
|
lineDash: datum.getLineDash(),
|
|
lineDashOffset,
|
|
visible: true
|
|
});
|
|
});
|
|
}
|
|
updateRangeLabels(trendLineProperties, { xAxis }) {
|
|
const { rangeStrokesGroupSelection } = this;
|
|
const {
|
|
strokes: colors,
|
|
strokeWidth,
|
|
rangeStroke,
|
|
isMultiColor,
|
|
label: { fontFamily, fontSize, fontStyle, fontWeight: fontWeight2, color: color2 }
|
|
} = trendLineProperties;
|
|
const labelProperties = {
|
|
fontFamily,
|
|
fontSize,
|
|
fontStyle,
|
|
fontWeight: fontWeight2
|
|
};
|
|
const withinBounds = this.checkWithinBounds(xAxis, labelProperties, this.labelsGroupSelection.at(0));
|
|
this.labelsGroupSelection.each((textNode, datum, index) => {
|
|
const textColor = color2 ?? (isMultiColor ? colors[index % colors.length] : rangeStroke);
|
|
const line = rangeStrokesGroupSelection.at(index);
|
|
if (!line) {
|
|
return;
|
|
}
|
|
const { text: text2, ...coords } = datum.label;
|
|
if (withinBounds) {
|
|
textNode.setProperties({
|
|
...labelProperties,
|
|
text: text2,
|
|
x: coords.x1,
|
|
y: coords.y1,
|
|
textBaseline: "middle",
|
|
textAlign: "end",
|
|
fill: textColor
|
|
});
|
|
updateLineText(textNode.id, line, coords);
|
|
} else {
|
|
const textProperties = {
|
|
...labelProperties,
|
|
label: text2,
|
|
position: "center",
|
|
alignment: "left",
|
|
color: textColor
|
|
};
|
|
updateLineText(textNode.id, line, coords, textProperties, textNode, text2, strokeWidth);
|
|
}
|
|
});
|
|
}
|
|
checkWithinBounds(xAxis, fontOptions, textNode) {
|
|
if (!textNode) {
|
|
return false;
|
|
}
|
|
const { text: text2, ...coords } = textNode.datum.label;
|
|
textNode.setProperties({
|
|
...fontOptions,
|
|
text: text2,
|
|
x: coords.x1,
|
|
y: coords.y1,
|
|
textBaseline: "middle",
|
|
textAlign: "end"
|
|
});
|
|
const { x } = textNode.getBBox();
|
|
return x >= xAxis.bounds.x && x <= xAxis.bounds.x + xAxis.bounds.width;
|
|
}
|
|
updateText(datum, coords) {
|
|
const oneLine = this.rangeStrokesGroupSelection.selectByTag(0 /* OneLine */)[0];
|
|
if (!oneLine) {
|
|
return;
|
|
}
|
|
const { text: textProperties, strokeWidth } = datum;
|
|
this.text = this.updateNode(CollidableText, this.text, !!textProperties.label);
|
|
updateLineText(oneLine.id, oneLine, coords, textProperties, this.text, textProperties.label, strokeWidth);
|
|
}
|
|
updateAnchor(_datum, coords, _context, _bbox) {
|
|
const point = vector4_exports.topCenter(coords);
|
|
vector_exports.apply(this.anchor, module_support_exports.Transformable.toCanvasPoint(this.trendLine, point.x, point.y));
|
|
}
|
|
containsPoint(x, y) {
|
|
const { trendLine, rangeStrokesGroupSelection, text: text2 } = this;
|
|
let isInStrokePath = false;
|
|
rangeStrokesGroupSelection.each((line) => isInStrokePath || (isInStrokePath = line.isPointInPath(x, y)));
|
|
return isInStrokePath || trendLine.isPointInPath(x, y) || Boolean(text2?.containsPoint(x, y));
|
|
}
|
|
getNodeAtCoords(x, y) {
|
|
if (this.text?.containsPoint(x, y))
|
|
return "text";
|
|
if (this.trendLine.isPointInPath(x, y))
|
|
return "line";
|
|
}
|
|
getHandleStyles(datum) {
|
|
return {
|
|
fill: datum.handle.fill,
|
|
stroke: datum.handle.stroke ?? datum.stroke,
|
|
strokeOpacity: datum.handle.strokeOpacity ?? datum.strokeOpacity,
|
|
strokeWidth: datum.handle.strokeWidth ?? datum.strokeWidth
|
|
};
|
|
}
|
|
drag(datum, target, context, snapping) {
|
|
if (!datum.isWriteable())
|
|
return;
|
|
if (this.activeHandle) {
|
|
this.dragHandle(datum, target, context, snapping);
|
|
} else {
|
|
this.dragAll(datum, target, context);
|
|
}
|
|
}
|
|
getAnchor() {
|
|
return this.anchor;
|
|
}
|
|
getCursor() {
|
|
return "pointer";
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/utils/validation.ts
|
|
var { ContinuousScale: ContinuousScale3 } = module_support_exports;
|
|
function validateDatumPoint(context, point, options = { overflowContinuous: false }, warningPrefix) {
|
|
if (point.x == null || point.y == null) {
|
|
if (warningPrefix) {
|
|
logger_exports.warnOnce(`${warningPrefix}requires both an [x] and [y] property, ignoring.`);
|
|
}
|
|
return false;
|
|
}
|
|
const { xAxis, yAxis } = context;
|
|
const continuousX = options.overflowContinuous && ContinuousScale3.is(xAxis.scale);
|
|
const continuousY = options.overflowContinuous && ContinuousScale3.is(yAxis.scale);
|
|
const validX = continuousX || validateDatumPointDirection(point.x, xAxis);
|
|
const validY = continuousY || validateDatumPointDirection(point.y, yAxis);
|
|
if (validX && validY)
|
|
return true;
|
|
if (warningPrefix) {
|
|
let text2 = "x & y domains";
|
|
if (validX)
|
|
text2 = "y domain";
|
|
if (validY)
|
|
text2 = "x domain";
|
|
const xValue = getGroupingValue(point.x);
|
|
const yValue = getGroupingValue(point.y);
|
|
logger_exports.warnOnce(`${warningPrefix}is outside the ${text2}, ignoring. - x: [${xValue}], y: ${yValue}]`);
|
|
}
|
|
return false;
|
|
}
|
|
function validateDatumPointDirection(d, context) {
|
|
const { domain } = context.scale;
|
|
const value = getGroupingValue(d);
|
|
if (domain && value != null && context.continuous) {
|
|
return value >= domain[0] && value <= domain.at(-1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/fibonacci-retracement-trend-based/fibonacciRetracementTrendBasedScene.ts
|
|
var FibonacciRetracementTrendBasedScene = class extends FibonacciScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "fibonacci-retracement-trend-based";
|
|
this.endRetracementLine = new CollidableLine();
|
|
this.start = new DivariantHandle();
|
|
this.end = new DivariantHandle();
|
|
this.endRetracement = new DivariantHandle();
|
|
this.append([this.endRetracementLine, this.start, this.end, this.endRetracement]);
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "fibonacci-retracement-trend-based");
|
|
}
|
|
update(datum, context) {
|
|
let { coords1, coords2 } = this.getCoords(datum, context);
|
|
if (coords1 == null || coords2 == null) {
|
|
this.visible = false;
|
|
return;
|
|
}
|
|
coords1 = vector4_exports.round(coords1);
|
|
coords2 = vector4_exports.round(coords2);
|
|
this.visible = datum.visible ?? true;
|
|
if (!this.visible)
|
|
return;
|
|
if (datum.endRetracement.x == void 0 || datum.endRetracement.y == void 0) {
|
|
coords2 = void 0;
|
|
}
|
|
this.updateLine(datum, coords1, this.trendLine);
|
|
this.updateLine(datum, coords2, this.endRetracementLine);
|
|
this.updateHandles(datum, coords1, coords2);
|
|
this.updateAnchor(datum, coords2 ?? coords1, context);
|
|
const { reverse, bands } = datum;
|
|
const coords = getFibonacciCoords(coords1, coords2);
|
|
const extendedCoords = this.extendLine(coords, datum, context);
|
|
const yZero = extendedCoords.y2;
|
|
const yOne = extendedCoords.y1;
|
|
const data = coords2 ? createFibonacciRangesData(extendedCoords, context, reverse, yZero, bands) : [];
|
|
this.updateRanges(datum, data, context);
|
|
const oneLinePoints = { ...extendedCoords, y1: yOne, y2: yOne };
|
|
this.updateText(datum, oneLinePoints);
|
|
}
|
|
containsPoint(x, y) {
|
|
const { start: start2, end: end3, endRetracement, endRetracementLine } = this;
|
|
this.activeHandle = void 0;
|
|
if (start2.containsPoint(x, y)) {
|
|
this.activeHandle = "start";
|
|
return true;
|
|
}
|
|
if (end3.containsPoint(x, y)) {
|
|
this.activeHandle = "end";
|
|
return true;
|
|
}
|
|
if (endRetracement.containsPoint(x, y)) {
|
|
this.activeHandle = "endRetracement";
|
|
return true;
|
|
}
|
|
return endRetracementLine.isPointInPath(x, y) || super.containsPoint(x, y);
|
|
}
|
|
getNodeAtCoords(x, y) {
|
|
if (this.start.containsPoint(x, y) || this.end.containsPoint(x, y) || this.endRetracement.containsPoint(x, y))
|
|
return "handle";
|
|
if (this.endRetracementLine.isPointInPath(x, y))
|
|
return "line";
|
|
return super.getNodeAtCoords(x, y);
|
|
}
|
|
dragStart(datum, target, context) {
|
|
this.dragState = {
|
|
offset: target,
|
|
...getDragStartState({ start: datum.start, end: datum.end, endRetracement: datum.endRetracement }, context)
|
|
};
|
|
}
|
|
stopDragging() {
|
|
this.start.toggleDragging(false);
|
|
this.end.toggleDragging(false);
|
|
this.endRetracement.toggleDragging(false);
|
|
}
|
|
dragAll(datum, target, context) {
|
|
const { dragState } = this;
|
|
if (!dragState)
|
|
return;
|
|
this.translatePoints({
|
|
datum,
|
|
start: dragState.start,
|
|
end: dragState.end,
|
|
endRetracement: dragState.endRetracement,
|
|
translation: vector_exports.sub(target, dragState.offset),
|
|
context
|
|
});
|
|
}
|
|
dragHandle(datum, target, context, snapping) {
|
|
const { activeHandle, dragState } = this;
|
|
if (!activeHandle || !dragState)
|
|
return;
|
|
this[activeHandle].toggleDragging(true);
|
|
const point = snapping ? this.snapToAngle(datum, target, context) : invertCoords(this[activeHandle].drag(target).point, context);
|
|
if (!point || !validateDatumPoint(context, point))
|
|
return;
|
|
datum[activeHandle].x = point.x;
|
|
datum[activeHandle].y = point.y;
|
|
}
|
|
snapToAngle(datum, coords, context) {
|
|
const { activeHandle } = this;
|
|
const handles = ["start", "end", "endRetracement"];
|
|
if (!activeHandle)
|
|
return;
|
|
const index = (handles.indexOf(activeHandle) + 1) % handles.length;
|
|
const fixedHandle = handles[index];
|
|
this[activeHandle].toggleDragging(true);
|
|
const fixed = convertPoint(datum[fixedHandle], context);
|
|
return invertCoords(snapToAngle(coords, fixed, datum.snapToAngle), context);
|
|
}
|
|
translatePoints({
|
|
datum,
|
|
start: start2,
|
|
end: end3,
|
|
endRetracement,
|
|
translation,
|
|
context
|
|
}) {
|
|
const points = translate({ start: start2, end: end3, endRetracement }, translation, context, { overflowContinuous: 2 });
|
|
datum.start.x = points.start.x;
|
|
datum.end.x = points.end.x;
|
|
datum.endRetracement.x = points.endRetracement.x;
|
|
datum.start.y = points.start.y;
|
|
datum.end.y = points.end.y;
|
|
datum.endRetracement.y = points.endRetracement.y;
|
|
}
|
|
translate(datum, translation, context) {
|
|
this.translatePoints({
|
|
datum,
|
|
start: convertPoint(datum.start, context),
|
|
end: convertPoint(datum.end, context),
|
|
endRetracement: convertPoint(datum.endRetracement, context),
|
|
translation,
|
|
context
|
|
});
|
|
}
|
|
copy(datum, copiedDatum, context) {
|
|
const { coords1, coords2 } = this.getCoords(datum, context);
|
|
if (!coords1 || !coords2) {
|
|
return;
|
|
}
|
|
const bbox = this.computeBBoxWithoutHandles();
|
|
this.translatePoints({
|
|
datum: copiedDatum,
|
|
start: vector4_exports.start(coords1),
|
|
end: vector4_exports.end(coords1),
|
|
endRetracement: vector4_exports.end(coords2),
|
|
translation: { x: -bbox.width / 2, y: -bbox.height / 2 },
|
|
context
|
|
});
|
|
return copiedDatum;
|
|
}
|
|
getCoords(datum, context) {
|
|
return {
|
|
coords1: convertLine(datum, context),
|
|
coords2: convertLine({ start: datum.end, end: datum.endRetracement }, context)
|
|
};
|
|
}
|
|
toggleHandles(show) {
|
|
if (typeof show === "boolean") {
|
|
this.start.visible = show;
|
|
this.end.visible = show;
|
|
this.endRetracement.visible = show;
|
|
} else {
|
|
for (const [handle3, visible] of entries(show)) {
|
|
this[handle3].visible = visible;
|
|
}
|
|
}
|
|
this.start.toggleHovered(this.activeHandle === "start");
|
|
this.end.toggleHovered(this.activeHandle === "end");
|
|
this.endRetracement.toggleHovered(this.activeHandle === "endRetracement");
|
|
}
|
|
toggleActive(active) {
|
|
this.toggleHandles(active);
|
|
this.start.toggleActive(active);
|
|
this.end.toggleActive(active);
|
|
this.endRetracement.toggleActive(active);
|
|
}
|
|
updateHandles(datum, coords1, coords2, bbox) {
|
|
this.start.update({
|
|
...this.getHandleStyles(datum),
|
|
...this.getHandleCoords(datum, coords1, "start")
|
|
});
|
|
this.end.update({
|
|
...this.getHandleStyles(datum),
|
|
...this.getHandleCoords(datum, coords1, "end", bbox)
|
|
});
|
|
if (coords2) {
|
|
this.endRetracement.update({
|
|
...this.getHandleStyles(datum),
|
|
...this.getHandleCoords(datum, coords2, "endRetracement", bbox)
|
|
});
|
|
}
|
|
this.start.toggleLocked(datum.locked ?? false);
|
|
this.end.toggleLocked(datum.locked ?? false);
|
|
this.endRetracement.toggleLocked(datum.locked ?? false);
|
|
}
|
|
getHandleCoords(_datum, coords, handle3, _bbox) {
|
|
return handle3 === "start" ? vector4_exports.start(coords) : vector4_exports.end(coords);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/fibonacci-retracement-trend-based/fibonacciRetracementTrendBasedState.ts
|
|
var FibonacciRetracementTrendBasedStateMachine = class extends StateMachine {
|
|
constructor(ctx) {
|
|
const actionCreate = ({ point }) => {
|
|
const datum = this.createDatum();
|
|
datum.set({ start: point, end: point });
|
|
ctx.create(datum);
|
|
};
|
|
const actionFirstRender = () => {
|
|
const { node } = this;
|
|
node?.toggleActive(true);
|
|
node?.toggleHandles({ start: true, end: false, endRetracement: false });
|
|
};
|
|
const actionEndUpdate = ({ offset, context }) => {
|
|
const { datum, snapping } = this;
|
|
if (!datum)
|
|
return;
|
|
datum.set({ end: snapPoint(offset, context, snapping, datum.start, datum.snapToAngle) });
|
|
ctx.update();
|
|
};
|
|
const actionEndFinish = () => {
|
|
const { datum } = this;
|
|
if (!datum)
|
|
return;
|
|
datum.endRetracement.x = datum.end.x;
|
|
datum.endRetracement.y = datum.end.y;
|
|
this.node?.toggleHandles({ end: true });
|
|
ctx.update();
|
|
};
|
|
const actionEndRetracementUpdate = ({ offset, context }) => {
|
|
const { datum, snapping } = this;
|
|
if (!datum)
|
|
return;
|
|
datum.set({ endRetracement: snapPoint(offset, context, snapping, datum.end, datum.snapToAngle) });
|
|
ctx.update();
|
|
};
|
|
const actionEndRetracementFinish = () => {
|
|
this.node?.toggleHandles({ endRetracement: true });
|
|
ctx.update();
|
|
};
|
|
const actionCancel = () => ctx.delete();
|
|
const onExitEnd = () => {
|
|
ctx.showAnnotationOptions();
|
|
ctx.recordAction(`Create ${this.datum?.type} annotation`);
|
|
};
|
|
super("start", {
|
|
start: {
|
|
click: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
drag: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
reset: StateMachine.parent
|
|
},
|
|
"waiting-first-render": {
|
|
render: {
|
|
target: "end",
|
|
action: actionFirstRender
|
|
}
|
|
},
|
|
end: {
|
|
hover: actionEndUpdate,
|
|
click: {
|
|
target: "endRetracement",
|
|
action: actionEndFinish
|
|
},
|
|
drag: actionEndUpdate,
|
|
dragEnd: {
|
|
target: "endRetracement",
|
|
action: actionEndFinish
|
|
},
|
|
reset: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
cancel: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
onExit: onExitEnd
|
|
},
|
|
endRetracement: {
|
|
hover: actionEndRetracementUpdate,
|
|
click: {
|
|
target: StateMachine.parent,
|
|
action: actionEndRetracementFinish
|
|
},
|
|
drag: {
|
|
target: StateMachine.parent,
|
|
action: actionEndRetracementFinish
|
|
},
|
|
reset: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
cancel: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
}
|
|
}
|
|
});
|
|
this.debug = debugLogger_exports.create(true, "annotations");
|
|
this.snapping = false;
|
|
}
|
|
createDatum() {
|
|
return new FibonacciRetracementTrendBasedProperties();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], FibonacciRetracementTrendBasedStateMachine.prototype, "datum", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], FibonacciRetracementTrendBasedStateMachine.prototype, "node", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], FibonacciRetracementTrendBasedStateMachine.prototype, "snapping", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/fibonacci-retracement-trend-based/fibonacciRetracementTrendBasedConfig.ts
|
|
var fibonacciRetracementTrendBasedConfig = {
|
|
type: "fibonacci-retracement-trend-based" /* FibonacciRetracementTrendBased */,
|
|
datum: FibonacciRetracementTrendBasedProperties,
|
|
scene: FibonacciRetracementTrendBasedScene,
|
|
isDatum: FibonacciRetracementTrendBasedProperties.is,
|
|
translate: (node, datum, transition, context) => {
|
|
if (FibonacciRetracementTrendBasedProperties.is(datum) && FibonacciRetracementTrendBasedScene.is(node))
|
|
node.translate(datum, transition, context);
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (FibonacciRetracementTrendBasedProperties.is(datum) && FibonacciRetracementTrendBasedProperties.is(copiedDatum) && FibonacciRetracementTrendBasedScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (FibonacciRetracementTrendBasedProperties.is(datum) && FibonacciRetracementTrendBasedScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new FibonacciRetracementTrendBasedStateMachine({
|
|
...ctx,
|
|
create: createDatum("fibonacci-retracement-trend-based" /* FibonacciRetracementTrendBased */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/fibonacci-retracement/fibonacciRetracementScene.ts
|
|
var FibonacciRetracementScene = class extends FibonacciScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "fibonacci-retracement";
|
|
this.start = new DivariantHandle();
|
|
this.end = new DivariantHandle();
|
|
this.append([this.start, this.end]);
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "fibonacci-retracement");
|
|
}
|
|
containsPoint(x, y) {
|
|
const { start: start2, end: end3 } = this;
|
|
this.activeHandle = void 0;
|
|
if (start2.containsPoint(x, y)) {
|
|
this.activeHandle = "start";
|
|
return true;
|
|
}
|
|
if (end3.containsPoint(x, y)) {
|
|
this.activeHandle = "end";
|
|
return true;
|
|
}
|
|
return super.containsPoint(x, y);
|
|
}
|
|
getNodeAtCoords(x, y) {
|
|
if (this.start.containsPoint(x, y) || this.end.containsPoint(x, y))
|
|
return "handle";
|
|
return super.getNodeAtCoords(x, y);
|
|
}
|
|
dragStart(datum, target, context) {
|
|
this.dragState = {
|
|
offset: target,
|
|
...getDragStartState({ start: datum.start, end: datum.end }, context)
|
|
};
|
|
}
|
|
stopDragging() {
|
|
this.start.toggleDragging(false);
|
|
this.end.toggleDragging(false);
|
|
}
|
|
dragAll(datum, target, context) {
|
|
const { dragState } = this;
|
|
if (!dragState)
|
|
return;
|
|
this.translatePoints({
|
|
datum,
|
|
start: dragState.start,
|
|
end: dragState.end,
|
|
translation: vector_exports.sub(target, dragState.offset),
|
|
context
|
|
});
|
|
}
|
|
dragHandle(datum, target, context, snapping) {
|
|
const { activeHandle, dragState } = this;
|
|
if (!activeHandle || !dragState)
|
|
return;
|
|
this[activeHandle].toggleDragging(true);
|
|
const point = snapping ? this.snapToAngle(datum, target, context) : invertCoords(this[activeHandle].drag(target).point, context);
|
|
if (!point || !validateDatumPoint(context, point))
|
|
return;
|
|
datum[activeHandle].x = point.x;
|
|
datum[activeHandle].y = point.y;
|
|
}
|
|
snapToAngle(datum, coords, context) {
|
|
const { activeHandle } = this;
|
|
const handles = ["start", "end"];
|
|
const fixedHandle = handles.find((handle3) => handle3 !== activeHandle);
|
|
if (!activeHandle || !fixedHandle)
|
|
return;
|
|
this[activeHandle].toggleDragging(true);
|
|
const fixed = convertPoint(datum[fixedHandle], context);
|
|
return invertCoords(snapToAngle(coords, fixed, datum.snapToAngle), context);
|
|
}
|
|
translatePoints({
|
|
datum,
|
|
start: start2,
|
|
end: end3,
|
|
translation,
|
|
context
|
|
}) {
|
|
const points = translate({ start: start2, end: end3 }, translation, context, { overflowContinuous: 1 });
|
|
datum.start.x = points.start.x;
|
|
datum.end.x = points.end.x;
|
|
datum.start.y = points.start.y;
|
|
datum.end.y = points.end.y;
|
|
}
|
|
translate(datum, translation, context) {
|
|
this.translatePoints({
|
|
datum,
|
|
start: convertPoint(datum.start, context),
|
|
end: convertPoint(datum.end, context),
|
|
translation,
|
|
context
|
|
});
|
|
}
|
|
copy(datum, copiedDatum, context) {
|
|
const coords = convertLine(datum, context);
|
|
if (!coords) {
|
|
return;
|
|
}
|
|
const bbox = this.computeBBoxWithoutHandles();
|
|
this.translatePoints({
|
|
datum: copiedDatum,
|
|
start: { x: coords.x1, y: coords.y1 },
|
|
end: { x: coords.x2, y: coords.y2 },
|
|
translation: { x: -bbox.width / 2, y: -bbox.height / 2 },
|
|
context
|
|
});
|
|
return copiedDatum;
|
|
}
|
|
toggleHandles(show) {
|
|
if (typeof show === "boolean") {
|
|
this.start.visible = show;
|
|
this.end.visible = show;
|
|
} else {
|
|
for (const [handle3, visible] of entries(show)) {
|
|
this[handle3].visible = visible;
|
|
}
|
|
}
|
|
this.start.toggleHovered(this.activeHandle === "start");
|
|
this.end.toggleHovered(this.activeHandle === "end");
|
|
}
|
|
toggleActive(active) {
|
|
this.toggleHandles(active);
|
|
this.start.toggleActive(active);
|
|
this.end.toggleActive(active);
|
|
}
|
|
updateHandles(datum, coords, _coords2, bbox) {
|
|
this.start.update({
|
|
...this.getHandleStyles(datum),
|
|
...this.getHandleCoords(datum, coords, "start")
|
|
});
|
|
this.end.update({
|
|
...this.getHandleStyles(datum),
|
|
...this.getHandleCoords(datum, coords, "end", bbox)
|
|
});
|
|
this.start.toggleLocked(datum.locked ?? false);
|
|
this.end.toggleLocked(datum.locked ?? false);
|
|
}
|
|
getHandleCoords(_datum, coords, handle3, _bbox) {
|
|
return handle3 === "start" ? vector4_exports.start(coords) : vector4_exports.end(coords);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/line/lineState.ts
|
|
var LineTypeStateMachine = class extends StateMachine {
|
|
constructor(ctx) {
|
|
const actionCreate = ({ point }) => {
|
|
const datum = this.createDatum();
|
|
datum.set({ start: point, end: point });
|
|
ctx.create(datum);
|
|
};
|
|
const actionFirstRender = () => {
|
|
const { node } = this;
|
|
node?.toggleActive(true);
|
|
node?.toggleHandles({ start: true, end: false });
|
|
};
|
|
const actionEndUpdate = ({ offset, context }) => {
|
|
const { datum, snapping } = this;
|
|
if (!datum)
|
|
return;
|
|
datum.set({ end: snapPoint(offset, context, snapping, datum.start, datum.snapToAngle) });
|
|
ctx.update();
|
|
};
|
|
const actionEndFinish = () => {
|
|
this.node?.toggleHandles({ end: true });
|
|
ctx.update();
|
|
};
|
|
const actionCancel = () => ctx.delete();
|
|
const onExitEnd = () => {
|
|
ctx.showAnnotationOptions();
|
|
ctx.recordAction(`Create ${this.datum?.type} annotation`);
|
|
};
|
|
super("start", {
|
|
start: {
|
|
click: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
drag: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
reset: StateMachine.parent
|
|
},
|
|
"waiting-first-render": {
|
|
render: {
|
|
target: "end",
|
|
action: actionFirstRender
|
|
}
|
|
},
|
|
end: {
|
|
hover: actionEndUpdate,
|
|
click: {
|
|
target: StateMachine.parent,
|
|
action: actionEndFinish
|
|
},
|
|
drag: actionEndUpdate,
|
|
dragEnd: {
|
|
target: StateMachine.parent,
|
|
action: actionEndFinish
|
|
},
|
|
reset: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
cancel: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
onExit: onExitEnd
|
|
}
|
|
});
|
|
this.debug = debugLogger_exports.create(true, "annotations");
|
|
this.snapping = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], LineTypeStateMachine.prototype, "datum", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], LineTypeStateMachine.prototype, "node", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], LineTypeStateMachine.prototype, "snapping", 2);
|
|
var ArrowStateMachine = class extends LineTypeStateMachine {
|
|
createDatum() {
|
|
return new ArrowProperties();
|
|
}
|
|
};
|
|
var LineStateMachine = class extends LineTypeStateMachine {
|
|
createDatum() {
|
|
return new LineProperties();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/fibonacci-retracement/fibonacciRetracementState.ts
|
|
var FibonacciRetracementStateMachine = class extends LineTypeStateMachine {
|
|
createDatum() {
|
|
return new FibonacciRetracementProperties();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/fibonacci-retracement/fibonacciRetracementConfig.ts
|
|
var fibonacciRetracementConfig = {
|
|
type: "fibonacci-retracement" /* FibonacciRetracement */,
|
|
datum: FibonacciRetracementProperties,
|
|
scene: FibonacciRetracementScene,
|
|
isDatum: FibonacciRetracementProperties.is,
|
|
translate: (node, datum, transition, context) => {
|
|
if (FibonacciRetracementProperties.is(datum) && FibonacciRetracementScene.is(node))
|
|
node.translate(datum, transition, context);
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (FibonacciRetracementProperties.is(datum) && FibonacciRetracementProperties.is(copiedDatum) && FibonacciRetracementScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (FibonacciRetracementProperties.is(datum) && FibonacciRetracementScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new FibonacciRetracementStateMachine({
|
|
...ctx,
|
|
create: createDatum("fibonacci-retracement" /* FibonacciRetracement */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/scenes/capScene.ts
|
|
var CapScene = class extends module_support_exports.Group {
|
|
};
|
|
var ArrowCapScene = class extends CapScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "arrow";
|
|
this.path = new module_support_exports.Path();
|
|
this.armLength = 6;
|
|
this.append([this.path]);
|
|
}
|
|
update(options) {
|
|
const { path } = this;
|
|
const { x, y, angle: angle2, ...rest } = options;
|
|
const origin3 = vector_exports.from(x, y);
|
|
const offsetAngle = 3 * Math.PI / 4;
|
|
const armLength = this.armLength + (options.strokeWidth ?? 0) * 2;
|
|
const leftEnd = vector_exports.rotate(vector_exports.from(0, armLength), angle2 + offsetAngle, origin3);
|
|
const rightEnd = vector_exports.rotate(vector_exports.from(armLength, 0), angle2 - offsetAngle, origin3);
|
|
path.setProperties(rest);
|
|
path.fillOpacity = 0;
|
|
path.path.clear();
|
|
path.path.moveTo(leftEnd.x, leftEnd.y);
|
|
path.path.lineTo(origin3.x, origin3.y);
|
|
path.path.lineTo(rightEnd.x, rightEnd.y);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/line/lineScene.ts
|
|
var { Transformable: Transformable4 } = module_support_exports;
|
|
var LineScene = class extends StartEndScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "line";
|
|
this.line = new CollidableLine();
|
|
this.append([this.line, this.start, this.end]);
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "line");
|
|
}
|
|
update(datum, context) {
|
|
let coords = convertLine(datum, context);
|
|
if (coords == null) {
|
|
this.visible = false;
|
|
return;
|
|
}
|
|
coords = vector4_exports.round(coords);
|
|
this.visible = datum.visible ?? true;
|
|
if (!this.visible)
|
|
return;
|
|
this.updateLine(datum, coords, context);
|
|
this.updateHandles(datum, coords);
|
|
this.updateText(datum, coords);
|
|
this.updateCaps(datum, coords);
|
|
this.updateAnchor(datum, coords, context);
|
|
}
|
|
updateLine(datum, coords, context) {
|
|
const { line } = this;
|
|
const { lineDashOffset, stroke: stroke3, strokeWidth, strokeOpacity } = datum;
|
|
const linePoints = this.extendLine(coords, datum, context);
|
|
line.setProperties({
|
|
...linePoints,
|
|
lineCap: datum.getLineCap(),
|
|
lineDash: datum.getLineDash(),
|
|
lineDashOffset,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
fillOpacity: 0
|
|
});
|
|
}
|
|
updateText(datum, coords) {
|
|
this.text = this.updateNode(CollidableText, this.text, !!datum.text.label);
|
|
updateLineText(this.line.id, this.line, coords, datum.text, this.text, datum.text.label, datum.strokeWidth);
|
|
}
|
|
updateCaps(datum, coords) {
|
|
if (!datum.startCap && this.startCap) {
|
|
this.startCap.remove();
|
|
this.startCap = void 0;
|
|
}
|
|
if (!datum.endCap && this.endCap) {
|
|
this.endCap.remove();
|
|
this.endCap = void 0;
|
|
}
|
|
if (!datum.startCap && !datum.endCap)
|
|
return;
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity } = datum;
|
|
const [start2, end3] = vector_exports.from(coords);
|
|
const angle2 = vector_exports.angle(vector_exports.sub(end3, start2));
|
|
if (datum.startCap) {
|
|
if (this.startCap && this.startCap.type !== datum.startCap) {
|
|
this.startCap.remove();
|
|
this.startCap = void 0;
|
|
}
|
|
if (this.startCap == null) {
|
|
this.startCap = new ArrowCapScene();
|
|
this.append([this.startCap]);
|
|
}
|
|
this.startCap.update({
|
|
x: start2.x,
|
|
y: start2.y,
|
|
angle: angle2 - Math.PI,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity
|
|
});
|
|
}
|
|
if (datum.endCap) {
|
|
if (this.endCap && this.endCap.type !== datum.endCap) {
|
|
this.endCap.remove();
|
|
this.endCap = void 0;
|
|
}
|
|
if (this.endCap == null) {
|
|
this.endCap = new ArrowCapScene();
|
|
this.append([this.endCap]);
|
|
}
|
|
this.endCap.update({
|
|
x: end3.x,
|
|
y: end3.y,
|
|
angle: angle2,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity
|
|
});
|
|
}
|
|
}
|
|
updateAnchor(_datum, coords, _context, _bbox) {
|
|
const point = vector4_exports.topCenter(coords);
|
|
vector_exports.apply(this.anchor, Transformable4.toCanvasPoint(this.line, point.x, point.y));
|
|
}
|
|
containsPoint(x, y) {
|
|
const { line, text: text2 } = this;
|
|
return super.containsPoint(x, y) || line.isPointInPath(x, y) || Boolean(text2?.containsPoint(x, y));
|
|
}
|
|
getNodeAtCoords(x, y) {
|
|
if (this.text?.containsPoint(x, y))
|
|
return "text";
|
|
if (this.line.isPointInPath(x, y))
|
|
return "line";
|
|
return super.getNodeAtCoords(x, y);
|
|
}
|
|
getHandleCoords(_datum, coords, handle3, _bbox) {
|
|
const { startCap, endCap } = this;
|
|
let [startPoint, endPoint] = vector_exports.from(coords);
|
|
const angle2 = vector_exports.angle(vector_exports.sub(endPoint, startPoint));
|
|
if (startCap) {
|
|
startPoint = vector_exports.rotate(vector_exports.from(0, -DivariantHandle.HANDLE_SIZE / 2), angle2, startPoint);
|
|
}
|
|
if (endCap) {
|
|
endPoint = vector_exports.rotate(vector_exports.from(0, DivariantHandle.HANDLE_SIZE / 2), angle2, endPoint);
|
|
}
|
|
return handle3 === "start" ? startPoint : endPoint;
|
|
}
|
|
getHandleStyles(datum) {
|
|
return {
|
|
fill: datum.handle.fill,
|
|
stroke: datum.handle.stroke ?? datum.stroke,
|
|
strokeOpacity: datum.handle.strokeOpacity ?? datum.strokeOpacity,
|
|
strokeWidth: datum.handle.strokeWidth ?? datum.strokeWidth
|
|
};
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/line/lineConfig.ts
|
|
var lineConfig = {
|
|
type: "line" /* Line */,
|
|
datum: LineProperties,
|
|
scene: LineScene,
|
|
isDatum: LineProperties.is,
|
|
translate: (node, datum, transition, context) => {
|
|
if (LineProperties.is(datum) && LineScene.is(node))
|
|
node.translate(datum, transition, context);
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (LineProperties.is(datum) && LineProperties.is(copiedDatum) && LineScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (LineProperties.is(datum) && LineScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new LineStateMachine({
|
|
...ctx,
|
|
create: createDatum("line" /* Line */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
var arrowConfig = {
|
|
type: "arrow" /* Arrow */,
|
|
datum: ArrowProperties,
|
|
scene: LineScene,
|
|
isDatum: ArrowProperties.is,
|
|
translate: (node, datum, transition, context) => {
|
|
if (ArrowProperties.is(datum) && LineScene.is(node))
|
|
node.translate(datum, transition, context);
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (ArrowProperties.is(datum) && ArrowProperties.is(copiedDatum) && LineScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (ArrowProperties.is(datum) && LineScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new ArrowStateMachine({
|
|
...ctx,
|
|
create: createDatum("arrow" /* Arrow */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/utils/layout.ts
|
|
function layoutScenesRow(scenes, startX = 0, gap = 0) {
|
|
let x = startX;
|
|
for (const scene of scenes) {
|
|
if (Array.isArray(scene)) {
|
|
for (const scene_ of scene) {
|
|
layoutSetX(scene_, x);
|
|
}
|
|
x += module_support_exports.Group.computeChildrenBBox(scene).width + gap;
|
|
} else {
|
|
layoutSetX(scene, x);
|
|
x += scene.getBBox().width + gap;
|
|
}
|
|
}
|
|
}
|
|
function layoutScenesColumn(scenes, startY = 0, gap = 0) {
|
|
let y = startY;
|
|
for (const scene of scenes) {
|
|
if (Array.isArray(scene)) {
|
|
for (const scene_ of scene) {
|
|
layoutSetY(scene_, y);
|
|
}
|
|
y += module_support_exports.Group.computeChildrenBBox(scene).height + gap;
|
|
} else {
|
|
layoutSetY(scene, y);
|
|
y += scene.getBBox().height + gap;
|
|
}
|
|
}
|
|
}
|
|
function layoutSetX(scene, x) {
|
|
if ("x1" in scene) {
|
|
scene.x2 = x + (scene.x2 - scene.x1);
|
|
scene.x1 = x;
|
|
} else {
|
|
scene.x = x;
|
|
}
|
|
}
|
|
function layoutSetY(scene, y) {
|
|
if ("y1" in scene) {
|
|
scene.y2 = y + (scene.y2 - scene.y1);
|
|
scene.y1 = y;
|
|
} else {
|
|
scene.y = y;
|
|
}
|
|
}
|
|
function layoutAddX(scene, x) {
|
|
if ("x1" in scene) {
|
|
scene.x1 += x;
|
|
scene.x2 += x;
|
|
} else {
|
|
scene.x += x;
|
|
}
|
|
}
|
|
function layoutAddY(scene, y) {
|
|
if ("y1" in scene) {
|
|
scene.y1 += y;
|
|
scene.y2 += y;
|
|
} else {
|
|
scene.y += y;
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/measurer/measurerStatisticsScene.ts
|
|
var MeasurerStatisticsScene = class extends module_support_exports.Group {
|
|
constructor() {
|
|
super();
|
|
this.name = "MeasurerStatisticsScene";
|
|
this.background = new module_support_exports.Rect();
|
|
this.dateRangeBarsText = new module_support_exports.Text();
|
|
this.dateRangeDivider = new module_support_exports.Line();
|
|
this.dateRangeValueText = new module_support_exports.Text();
|
|
this.priceRangeValueText = new module_support_exports.Text();
|
|
this.priceRangeDivider = new module_support_exports.Line();
|
|
this.priceRangePercentageText = new module_support_exports.Text();
|
|
this.volumeText = new module_support_exports.Text();
|
|
this.volumeFormatter = new Intl.NumberFormat("en-US", {
|
|
notation: "compact",
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
});
|
|
this.append([
|
|
this.background,
|
|
this.dateRangeBarsText,
|
|
this.dateRangeDivider,
|
|
this.dateRangeValueText,
|
|
this.priceRangeValueText,
|
|
this.priceRangeDivider,
|
|
this.priceRangePercentageText,
|
|
this.volumeText
|
|
]);
|
|
}
|
|
update(datum, stats, anchor, coords, context, verticalDirection, localeManager) {
|
|
this.verticalDirection = verticalDirection;
|
|
const scenes = this.updateStatistics(datum, stats, anchor, localeManager);
|
|
const bbox = module_support_exports.Group.computeChildrenBBox(scenes.flat());
|
|
const padding2 = 10;
|
|
bbox.grow(padding2);
|
|
this.updateBackground(datum, bbox, padding2);
|
|
this.reposition(scenes, padding2, context);
|
|
this.checkVisibility(datum, context, coords);
|
|
}
|
|
checkVisibility(datum, context, coords) {
|
|
const bounds = vector4_exports.from(context.seriesRect);
|
|
this.visible = vector4_exports.collides(coords, bounds) && (datum.visible ?? true);
|
|
}
|
|
updateStatistics(datum, stats, anchor, localeManager) {
|
|
const {
|
|
dateRangeBarsText,
|
|
dateRangeDivider,
|
|
dateRangeValueText,
|
|
priceRangeValueText,
|
|
priceRangeDivider,
|
|
priceRangePercentageText,
|
|
volumeText
|
|
} = this;
|
|
const horizontalGap = 8;
|
|
const verticalGap = 6;
|
|
const dividerLineHeight = datum.statistics.fontSize + 3;
|
|
const dividerLineOffset = -2;
|
|
const textStyles = this.getTextStyles(datum);
|
|
const dividerLineStyles = {
|
|
...this.getDividerStyles(datum),
|
|
x1: 0,
|
|
y1: 0,
|
|
x2: 0,
|
|
y2: dividerLineHeight
|
|
};
|
|
const dateScenes = [dateRangeBarsText, dateRangeDivider, dateRangeValueText];
|
|
const priceScenes = [priceRangeValueText, priceRangeDivider, priceRangePercentageText];
|
|
const scenes = [];
|
|
if (stats.priceRange) {
|
|
priceRangeValueText.setProperties({
|
|
...textStyles,
|
|
text: this.formatPriceRangeValue(stats.priceRange.value, localeManager)
|
|
});
|
|
priceRangeDivider.setProperties(dividerLineStyles);
|
|
priceRangePercentageText.setProperties({
|
|
...textStyles,
|
|
text: this.formatPriceRangePercentage(stats.priceRange.percentage, localeManager)
|
|
});
|
|
layoutScenesRow(priceScenes, anchor.x, horizontalGap);
|
|
scenes.push(priceScenes);
|
|
}
|
|
if (stats.dateRange) {
|
|
dateRangeBarsText.setProperties({
|
|
...textStyles,
|
|
text: this.formatDateRangeBars(stats.dateRange.bars, localeManager)
|
|
});
|
|
dateRangeDivider.setProperties(dividerLineStyles);
|
|
dateRangeValueText.setProperties({
|
|
...textStyles,
|
|
text: this.formatDateRangeValue(stats.dateRange.value)
|
|
});
|
|
layoutScenesRow(dateScenes, anchor.x, horizontalGap);
|
|
scenes.push(dateScenes);
|
|
}
|
|
if (stats.volume == null) {
|
|
volumeText.visible = false;
|
|
} else {
|
|
volumeText.setProperties({
|
|
...textStyles,
|
|
x: anchor.x,
|
|
text: this.formatVolume(stats.volume, localeManager),
|
|
visible: true
|
|
});
|
|
scenes.push(volumeText);
|
|
}
|
|
layoutScenesColumn(scenes, anchor.y, verticalGap);
|
|
priceRangeDivider.y1 += dividerLineOffset;
|
|
priceRangeDivider.y2 += dividerLineOffset;
|
|
dateRangeDivider.y1 += dividerLineOffset;
|
|
dateRangeDivider.y2 += dividerLineOffset;
|
|
return scenes;
|
|
}
|
|
updateBackground(datum, bbox, padding2) {
|
|
const styles = this.getBackgroundStyles(datum);
|
|
this.background.setProperties({
|
|
...styles,
|
|
...bbox,
|
|
x: bbox.x - bbox.width / 2 + padding2,
|
|
y: bbox.y
|
|
});
|
|
}
|
|
reposition(scenes, padding2, context) {
|
|
const { width: width2, height: height2 } = context.seriesRect;
|
|
const background = vector4_exports.from(this.background.getBBox());
|
|
let offsetX = 0;
|
|
if (background.x1 < 0)
|
|
offsetX = -background.x1;
|
|
if (background.x2 > width2)
|
|
offsetX = width2 - background.x2;
|
|
const offsetY = Math.min(padding2, height2 - background.y2);
|
|
for (const scene of scenes) {
|
|
if (Array.isArray(scene)) {
|
|
const rowWidth = module_support_exports.Group.computeChildrenBBox(scene).width;
|
|
for (const scene_ of scene) {
|
|
layoutAddX(scene_, offsetX - rowWidth / 2);
|
|
layoutAddY(scene_, offsetY);
|
|
}
|
|
} else {
|
|
layoutAddX(scene, offsetX - scene.getBBox().width / 2);
|
|
layoutAddY(scene, offsetY);
|
|
}
|
|
}
|
|
this.background.x += offsetX;
|
|
this.background.y += offsetY;
|
|
}
|
|
getTextStyles(datum) {
|
|
return {
|
|
fill: datum.statistics.color,
|
|
fontFamily: datum.statistics.fontFamily,
|
|
fontSize: datum.statistics.fontSize,
|
|
fontStyle: datum.statistics.fontStyle,
|
|
fontWeight: datum.statistics.fontWeight,
|
|
textBaseline: "top"
|
|
};
|
|
}
|
|
getDividerStyles(datum) {
|
|
return {
|
|
stroke: datum.statistics.divider.stroke,
|
|
strokeOpacity: datum.statistics.divider.strokeOpacity,
|
|
strokeWidth: datum.statistics.divider.strokeWidth
|
|
};
|
|
}
|
|
getBackgroundStyles(datum) {
|
|
return {
|
|
fill: datum.statistics.fill,
|
|
stroke: datum.statistics.stroke,
|
|
strokeOpacity: datum.statistics.strokeOpacity,
|
|
strokeWidth: datum.statistics.strokeWidth,
|
|
cornerRadius: 4
|
|
};
|
|
}
|
|
formatDateRangeBars(bars, localeManager) {
|
|
return localeManager?.t("measurerDateRangeBars", { value: bars }) ?? `${bars}`;
|
|
}
|
|
formatDateRangeValue(time3) {
|
|
const range3 = [];
|
|
const sign = time3 >= 0 ? "" : "-";
|
|
time3 = Math.abs(time3);
|
|
const MINUTE = 1e3 * 60;
|
|
const HOUR = MINUTE * 60;
|
|
const DAY2 = HOUR * 24;
|
|
const minutes = Math.floor(time3 / MINUTE);
|
|
const hours = Math.floor(time3 / HOUR);
|
|
const days = Math.floor(time3 / DAY2);
|
|
const remainderHours = hours % (DAY2 / HOUR);
|
|
const remainderMinutes = minutes % (HOUR / MINUTE);
|
|
if (days >= 1)
|
|
range3.push(`${days}d`);
|
|
if (hours >= 1 && (time3 < DAY2 || remainderHours !== 0))
|
|
range3.push(`${remainderHours}h`);
|
|
if (time3 < HOUR || remainderMinutes !== 0)
|
|
range3.push(`${remainderMinutes}m`);
|
|
range3[0] = `${sign}${range3[0]}`;
|
|
return range3.join(" ");
|
|
}
|
|
formatPriceRangeValue(value, localeManager) {
|
|
return localeManager?.t("measurerPriceRangeValue", { value: Number(value.toFixed(2)) }) ?? `${value}`;
|
|
}
|
|
formatPriceRangePercentage(percentage, localeManager) {
|
|
return localeManager?.t("measurerPriceRangePercent", { value: percentage }) ?? `${percentage}`;
|
|
}
|
|
formatVolume(volume, localeManager) {
|
|
const volumeString = Number.isNaN(volume) ? "" : this.volumeFormatter.format(volume);
|
|
return localeManager?.t("measurerVolume", { value: volumeString }) ?? volumeString;
|
|
}
|
|
};
|
|
var QuickMeasurerStatisticsScene = class extends MeasurerStatisticsScene {
|
|
getDirectionStyles(datum) {
|
|
return this.verticalDirection === "down" ? datum.down.statistics : datum.up.statistics;
|
|
}
|
|
getTextStyles(datum) {
|
|
const styles = this.getDirectionStyles(datum);
|
|
return {
|
|
...super.getTextStyles(datum),
|
|
fill: styles.color,
|
|
fontFamily: styles.fontFamily,
|
|
fontSize: styles.fontSize,
|
|
fontStyle: styles.fontStyle,
|
|
fontWeight: styles.fontWeight
|
|
};
|
|
}
|
|
getDividerStyles(datum) {
|
|
const styles = this.getDirectionStyles(datum);
|
|
return {
|
|
stroke: styles.divider.stroke,
|
|
strokeOpacity: styles.divider.strokeOpacity,
|
|
strokeWidth: styles.divider.strokeWidth
|
|
};
|
|
}
|
|
getBackgroundStyles(datum) {
|
|
const styles = this.getDirectionStyles(datum);
|
|
return {
|
|
...super.getBackgroundStyles(datum),
|
|
fill: styles.fill,
|
|
stroke: styles.stroke,
|
|
strokeOpacity: styles.strokeOpacity,
|
|
strokeWidth: styles.strokeWidth
|
|
};
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/measurer/measurerScene.ts
|
|
var MeasurerScene = class extends StartEndScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "measurer";
|
|
this.horizontalLine = new CollidableLine();
|
|
this.verticalLine = new CollidableLine();
|
|
// These four bounding lines are named after the way they are drawn, e.g. the horizontalStartLine is a horizontal
|
|
// line that is only shown when the measurer has the 'vertical' direction.
|
|
this.horizontalStartLine = new CollidableLine();
|
|
this.horizontalEndLine = new CollidableLine();
|
|
this.verticalStartLine = new CollidableLine();
|
|
this.verticalEndLine = new CollidableLine();
|
|
this.horizontalEndCap = new ArrowCapScene();
|
|
this.verticalEndCap = new ArrowCapScene();
|
|
this.background = new module_support_exports.Path({ zIndex: -1 });
|
|
this.updateBackground = WithBackgroundScene.updateBackground.bind(this);
|
|
this.statistics = this.createStatisticsScene();
|
|
this.statistics.zIndex = 1;
|
|
this.append([
|
|
this.background,
|
|
this.verticalStartLine,
|
|
this.verticalEndLine,
|
|
this.horizontalStartLine,
|
|
this.horizontalEndLine,
|
|
this.horizontalLine,
|
|
this.verticalLine,
|
|
this.horizontalEndCap,
|
|
this.verticalEndCap,
|
|
this.start,
|
|
this.end,
|
|
this.statistics
|
|
]);
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "measurer");
|
|
}
|
|
createStatisticsScene() {
|
|
return new MeasurerStatisticsScene();
|
|
}
|
|
update(datum, context) {
|
|
const coords = convertLine(datum, context);
|
|
if (coords == null) {
|
|
this.visible = false;
|
|
return;
|
|
}
|
|
this.visible = datum.visible ?? true;
|
|
if (!this.visible)
|
|
return;
|
|
const extended = this.extendPerpendicular(coords, datum, context);
|
|
const verticalStart = { ...extended, y2: extended.y1 };
|
|
const verticalEnd = { ...extended, y1: extended.y2 };
|
|
this.verticalDirection = coords.y1 < coords.y2 ? "down" : "up";
|
|
this.updateVisibilities(datum);
|
|
this.updateLines(datum, coords);
|
|
this.updateHandles(datum, coords);
|
|
this.updateText(datum, coords);
|
|
this.updateCaps(datum, coords);
|
|
this.updateBoundingLines(datum, extended);
|
|
this.updateBackground(datum, verticalStart, verticalEnd, context);
|
|
this.updateStatistics(datum, coords, context);
|
|
this.updateAnchor(datum, coords, context);
|
|
}
|
|
extendPerpendicular(coords, datum, context) {
|
|
const extended = {
|
|
x1: Math.min(coords.x1, coords.x2),
|
|
x2: Math.max(coords.x1, coords.x2),
|
|
y1: Math.min(coords.y1, coords.y2),
|
|
y2: Math.max(coords.y1, coords.y2)
|
|
};
|
|
const [start2, end3] = vector_exports.from(context.yAxis.bounds);
|
|
if (DateRangeProperties.is(datum)) {
|
|
if (datum.extendAbove)
|
|
extended.y1 = start2.y;
|
|
if (datum.extendBelow)
|
|
extended.y2 = end3.y;
|
|
} else if (PriceRangeProperties.is(datum)) {
|
|
if (datum.extendLeft)
|
|
extended.x1 = start2.x;
|
|
if (datum.extendRight)
|
|
extended.x2 = end3.x;
|
|
}
|
|
return extended;
|
|
}
|
|
updateVisibilities(datum) {
|
|
const {
|
|
horizontalStartLine,
|
|
horizontalEndLine,
|
|
horizontalEndCap,
|
|
verticalStartLine,
|
|
verticalEndLine,
|
|
verticalEndCap
|
|
} = this;
|
|
const { direction } = datum;
|
|
verticalStartLine.visible = direction !== "vertical";
|
|
verticalEndLine.visible = direction !== "vertical";
|
|
horizontalEndCap.visible = direction !== "vertical";
|
|
horizontalStartLine.visible = direction !== "horizontal";
|
|
horizontalEndLine.visible = direction !== "horizontal";
|
|
verticalEndCap.visible = direction !== "horizontal";
|
|
}
|
|
updateLines(datum, coords) {
|
|
const { horizontalLine, verticalLine } = this;
|
|
const { direction } = datum;
|
|
const { x1, y1, x2, y2 } = coords;
|
|
const center2 = vector_exports.round(vector4_exports.center(coords), 0);
|
|
const lineStyles = this.getLineStyles(datum);
|
|
if (direction !== "vertical") {
|
|
horizontalLine.setProperties({
|
|
...lineStyles,
|
|
x1,
|
|
x2,
|
|
y1: center2.y,
|
|
y2: center2.y
|
|
});
|
|
}
|
|
if (direction !== "horizontal") {
|
|
verticalLine.setProperties({
|
|
...lineStyles,
|
|
x1: center2.x,
|
|
x2: center2.x,
|
|
y1,
|
|
y2
|
|
});
|
|
}
|
|
}
|
|
updateText(datum, coords) {
|
|
const { direction } = datum;
|
|
const center2 = vector_exports.round(vector4_exports.center(coords), 0);
|
|
let line;
|
|
const textCoords = { ...coords };
|
|
if (direction === "vertical") {
|
|
line = this.verticalLine;
|
|
textCoords.x1 = center2.x;
|
|
textCoords.x2 = center2.x;
|
|
} else {
|
|
line = this.horizontalLine;
|
|
textCoords.y1 = center2.y;
|
|
textCoords.y2 = center2.y;
|
|
}
|
|
this.text = this.updateNode(CollidableText, this.text, !!datum.text.label);
|
|
const { id } = line;
|
|
const clip = updateLineText(id, line, textCoords, datum.text, this.text, datum.text.label, datum.strokeWidth);
|
|
let verticalClipMask;
|
|
if (direction === "both" && clip && this.text) {
|
|
const textBBox = vector4_exports.from(this.text.getBBox());
|
|
const { offset } = clip.numbers;
|
|
const crossesVerticalLine = textBBox.x1 <= center2.x + offset.x && textBBox.x2 >= center2.x - offset.x;
|
|
if (crossesVerticalLine) {
|
|
verticalClipMask = {
|
|
x: center2.x,
|
|
y: clip.clipMask.y,
|
|
radius: this.text.getBBox().height / 2 + vector_exports.length(offset)
|
|
};
|
|
}
|
|
}
|
|
this.verticalLine.setClipMask(id, verticalClipMask);
|
|
}
|
|
updateCaps(datum, coords) {
|
|
const { horizontalEndCap, verticalEndCap } = this;
|
|
const { direction } = datum;
|
|
const { x1, y1, x2, y2 } = coords;
|
|
const center2 = vector_exports.round(vector4_exports.center(coords), 0);
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity } = this.getLineStyles(datum);
|
|
const capStyles = { stroke: stroke3, strokeWidth, strokeOpacity };
|
|
if (direction !== "vertical") {
|
|
const angle2 = x1 <= x2 ? 0 : Math.PI;
|
|
let x = x2;
|
|
if (direction === "horizontal") {
|
|
x += x1 <= x2 ? -2 : 2;
|
|
}
|
|
horizontalEndCap.update({ ...capStyles, x, y: center2.y, angle: angle2 });
|
|
}
|
|
if (direction !== "horizontal") {
|
|
const angle2 = y1 <= y2 ? Math.PI / 2 : Math.PI / -2;
|
|
let y = y2;
|
|
if (direction === "vertical") {
|
|
y += y1 <= y2 ? -2 : 2;
|
|
}
|
|
verticalEndCap.update({ ...capStyles, x: center2.x, y, angle: angle2 });
|
|
}
|
|
}
|
|
updateBoundingLines(datum, extendedCoords) {
|
|
const { verticalStartLine, verticalEndLine, horizontalStartLine, horizontalEndLine } = this;
|
|
const { direction } = datum;
|
|
const { x1, y1, x2, y2 } = extendedCoords;
|
|
const lineStyles = this.getLineStyles(datum);
|
|
if (direction === "horizontal") {
|
|
verticalStartLine.setProperties({ ...lineStyles, x1, y1, x2: x1, y2 });
|
|
verticalEndLine.setProperties({ ...lineStyles, x1: x2, y1, x2, y2 });
|
|
}
|
|
if (direction === "vertical") {
|
|
horizontalStartLine.setProperties({ ...lineStyles, x1, y1, x2, y2: y1 });
|
|
horizontalEndLine.setProperties({ ...lineStyles, x1, y1: y2, x2, y2 });
|
|
}
|
|
}
|
|
updateStatistics(datum, coords, context) {
|
|
const point = vector_exports.add(vector4_exports.bottomCenter(coords), vector_exports.from(0, 10));
|
|
const statistics = { volume: this.getVolume(datum) };
|
|
if (datum.hasPriceRange) {
|
|
statistics.priceRange = {
|
|
percentage: this.getPriceRangePercentage(datum),
|
|
value: this.getPriceRangeValue(datum)
|
|
};
|
|
}
|
|
if (datum.hasDateRange) {
|
|
statistics.dateRange = {
|
|
bars: this.getDateRangeBars(coords, context),
|
|
value: this.getDateRangeValue(datum)
|
|
};
|
|
}
|
|
this.statistics.update(datum, statistics, point, coords, context, this.verticalDirection, datum.localeManager);
|
|
}
|
|
updateAnchor(_datum, coords, _context, _bbox) {
|
|
const point = vector4_exports.topCenter(coords);
|
|
vector_exports.apply(this.anchor, module_support_exports.Transformable.toCanvasPoint(this.horizontalLine, point.x, point.y));
|
|
}
|
|
getBackgroundPoints(_datum, verticalStart, verticalEnd, _bounds) {
|
|
const [startStart, startEnd] = vector_exports.from(verticalStart);
|
|
const [endStart, endEnd] = vector_exports.from(verticalEnd);
|
|
return [startStart, startEnd, endEnd, endStart];
|
|
}
|
|
getLineStyles(datum) {
|
|
const { lineDashOffset, stroke: stroke3, strokeWidth, strokeOpacity } = datum;
|
|
return {
|
|
lineCap: datum.getLineCap(),
|
|
lineDash: datum.getLineDash(),
|
|
lineDashOffset,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
fillOpacity: 0
|
|
};
|
|
}
|
|
getBackgroundStyles(datum) {
|
|
const { background } = datum;
|
|
return {
|
|
fill: background.fill,
|
|
fillOpacity: background.fillOpacity
|
|
};
|
|
}
|
|
getHandleStyles(datum) {
|
|
return {
|
|
fill: datum.handle.fill,
|
|
stroke: datum.handle.stroke ?? datum.stroke,
|
|
strokeOpacity: datum.handle.strokeOpacity ?? datum.strokeOpacity,
|
|
strokeWidth: datum.handle.strokeWidth ?? datum.strokeWidth
|
|
};
|
|
}
|
|
containsPoint(x, y) {
|
|
const {
|
|
horizontalLine,
|
|
text: text2,
|
|
verticalLine,
|
|
horizontalStartLine,
|
|
horizontalEndLine,
|
|
verticalStartLine,
|
|
verticalEndLine
|
|
} = this;
|
|
return super.containsPoint(x, y) || horizontalLine.isPointInPath(x, y) || verticalLine.isPointInPath(x, y) || horizontalStartLine.visible && horizontalStartLine.isPointInPath(x, y) || horizontalEndLine.visible && horizontalEndLine.isPointInPath(x, y) || verticalStartLine.visible && verticalStartLine.isPointInPath(x, y) || verticalEndLine.visible && verticalEndLine.isPointInPath(x, y) || Boolean(text2?.containsPoint(x, y));
|
|
}
|
|
getNodeAtCoords(x, y) {
|
|
if (this.text?.containsPoint(x, y))
|
|
return "text";
|
|
if (this.start.containsPoint(x, y) || this.end.containsPoint(x, y))
|
|
return "handle";
|
|
return "line";
|
|
}
|
|
getDateRangeBars(coords, context) {
|
|
const { step } = context.xAxis.scale;
|
|
const sign = coords.x1 <= coords.x2 ? 1 : -1;
|
|
return step ? Math.round(vector4_exports.width(coords) / step) * sign : 0;
|
|
}
|
|
getDateRangeValue(datum) {
|
|
const start2 = getGroupingValue(datum.start.x);
|
|
const end3 = getGroupingValue(datum.end.x);
|
|
if (!isDate(start2) || !isDate(end3)) {
|
|
throw new Error("Can not create a date range measurement of non-date x-axis.");
|
|
}
|
|
return end3.getTime() - start2.getTime();
|
|
}
|
|
getPriceRangePercentage(datum) {
|
|
if (datum.start.y == null || datum.end.y == null) {
|
|
throw new Error("Can not create a price range measurement of a non-numeric y-axis");
|
|
}
|
|
const endY = getGroupingValue(datum.end.y);
|
|
const startY = getGroupingValue(datum.start.y);
|
|
if (!isNumber(endY) || !isNumber(startY)) {
|
|
throw new Error("Can not create a price range measurement of a non-numeric y-axis");
|
|
}
|
|
return (endY - startY) / startY;
|
|
}
|
|
getPriceRangeValue(datum) {
|
|
if (datum.start.y == null || datum.end.y == null) {
|
|
throw new Error("Can not create a price range measurement of a non-numeric y-axis");
|
|
}
|
|
const endY = getGroupingValue(datum.end.y);
|
|
const startY = getGroupingValue(datum.start.y);
|
|
if (!isNumber(endY) || !isNumber(startY)) {
|
|
throw new Error("Can not create a price range measurement of a non-numeric y-axis");
|
|
}
|
|
return endY - startY;
|
|
}
|
|
getVolume(datum) {
|
|
return datum.getVolume(datum.start.x, datum.end.x);
|
|
}
|
|
};
|
|
var QuickMeasurerScene = class extends MeasurerScene {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.type = "quick-measurer";
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "quick-measurer");
|
|
}
|
|
createStatisticsScene() {
|
|
return new QuickMeasurerStatisticsScene();
|
|
}
|
|
getDirectionStyles(datum) {
|
|
return this.verticalDirection === "down" ? datum.down : datum.up;
|
|
}
|
|
getLineStyles(datum) {
|
|
const styles = this.getDirectionStyles(datum);
|
|
return {
|
|
...super.getLineStyles(datum),
|
|
stroke: styles.stroke,
|
|
strokeWidth: styles.strokeWidth,
|
|
strokeOpacity: styles.strokeOpacity
|
|
};
|
|
}
|
|
getBackgroundStyles(datum) {
|
|
const styles = this.getDirectionStyles(datum);
|
|
return {
|
|
fill: styles.fill,
|
|
fillOpacity: styles.fillOpacity
|
|
};
|
|
}
|
|
getHandleStyles(datum) {
|
|
const styles = this.getDirectionStyles(datum);
|
|
return {
|
|
fill: styles.handle.fill,
|
|
stroke: styles.handle.stroke ?? styles.stroke,
|
|
strokeOpacity: styles.handle.strokeOpacity ?? styles.strokeOpacity,
|
|
strokeWidth: styles.handle.strokeWidth ?? styles.strokeWidth
|
|
};
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/measurer/measurerState.ts
|
|
var MeasurerTypeStateMachine = class extends StateMachine {
|
|
constructor(ctx) {
|
|
const actionCreate = ({ point }) => {
|
|
const datum = this.createDatum();
|
|
datum.set({ start: point, end: point });
|
|
ctx.create(datum);
|
|
};
|
|
const actionEndUpdate = ({ point }) => {
|
|
const { datum, node } = this;
|
|
datum?.set({ end: point });
|
|
node?.toggleActive(true);
|
|
node?.toggleHandles({ end: false });
|
|
ctx.update();
|
|
};
|
|
const actionEndFinish = () => {
|
|
this.node?.toggleHandles({ end: true });
|
|
};
|
|
const actionCancel = () => ctx.delete();
|
|
const onExitEnd = () => {
|
|
ctx.showAnnotationOptions();
|
|
ctx.recordAction(`Create ${this.node?.type} annotation`);
|
|
};
|
|
super("start", {
|
|
start: {
|
|
reset: StateMachine.parent,
|
|
click: {
|
|
target: "end",
|
|
action: actionCreate
|
|
},
|
|
drag: {
|
|
target: "end",
|
|
action: actionCreate
|
|
}
|
|
},
|
|
end: {
|
|
hover: actionEndUpdate,
|
|
drag: actionEndUpdate,
|
|
click: {
|
|
target: StateMachine.parent,
|
|
action: actionEndFinish
|
|
},
|
|
dragEnd: {
|
|
target: StateMachine.parent,
|
|
action: actionEndFinish
|
|
},
|
|
reset: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
cancel: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
onExit: onExitEnd
|
|
}
|
|
});
|
|
this.debug = debugLogger_exports.create(true, "annotations");
|
|
}
|
|
};
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], MeasurerTypeStateMachine.prototype, "datum", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], MeasurerTypeStateMachine.prototype, "node", 2);
|
|
var DateRangeStateMachine = class extends MeasurerTypeStateMachine {
|
|
createDatum() {
|
|
return new DateRangeProperties();
|
|
}
|
|
};
|
|
var PriceRangeStateMachine = class extends MeasurerTypeStateMachine {
|
|
createDatum() {
|
|
return new PriceRangeProperties();
|
|
}
|
|
};
|
|
var DatePriceRangeStateMachine = class extends MeasurerTypeStateMachine {
|
|
createDatum() {
|
|
return new DatePriceRangeProperties();
|
|
}
|
|
};
|
|
var QuickDatePriceRangeStateMachine = class extends MeasurerTypeStateMachine {
|
|
createDatum() {
|
|
return new QuickDatePriceRangeProperties();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/measurer/measurerConfig.ts
|
|
var dateRangeConfig = {
|
|
type: "date-range" /* DateRange */,
|
|
datum: DateRangeProperties,
|
|
scene: MeasurerScene,
|
|
isDatum: DateRangeProperties.is,
|
|
translate: (node, datum, translation, context) => {
|
|
if (DateRangeProperties.is(datum) && MeasurerScene.is(node)) {
|
|
node.translate(datum, translation, context);
|
|
}
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (DateRangeProperties.is(datum) && DateRangeProperties.is(copiedDatum) && MeasurerScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (DateRangeProperties.is(datum) && MeasurerScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new DateRangeStateMachine({
|
|
...ctx,
|
|
create: createDatum("date-range" /* DateRange */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
var priceRangeConfig = {
|
|
type: "price-range" /* PriceRange */,
|
|
datum: PriceRangeProperties,
|
|
scene: MeasurerScene,
|
|
isDatum: PriceRangeProperties.is,
|
|
translate: (node, datum, translation, context) => {
|
|
if (PriceRangeProperties.is(datum) && MeasurerScene.is(node)) {
|
|
node.translate(datum, translation, context);
|
|
}
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (PriceRangeProperties.is(datum) && PriceRangeProperties.is(copiedDatum) && MeasurerScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (PriceRangeProperties.is(datum) && MeasurerScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new PriceRangeStateMachine({
|
|
...ctx,
|
|
create: createDatum("date-range" /* DateRange */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
var datePriceRangeConfig = {
|
|
type: "date-price-range" /* DatePriceRange */,
|
|
datum: DatePriceRangeProperties,
|
|
scene: MeasurerScene,
|
|
isDatum: DatePriceRangeProperties.is,
|
|
translate: (node, datum, translation, context) => {
|
|
if (DatePriceRangeProperties.is(datum) && MeasurerScene.is(node)) {
|
|
node.translate(datum, translation, context);
|
|
}
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (DatePriceRangeProperties.is(datum) && DatePriceRangeProperties.is(copiedDatum) && MeasurerScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (DatePriceRangeProperties.is(datum) && MeasurerScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new DatePriceRangeStateMachine({
|
|
...ctx,
|
|
create: createDatum("date-range" /* DateRange */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
var quickDatePriceRangeConfig = {
|
|
type: "quick-date-price-range" /* QuickDatePriceRange */,
|
|
datum: QuickDatePriceRangeProperties,
|
|
scene: QuickMeasurerScene,
|
|
isDatum: QuickDatePriceRangeProperties.is,
|
|
translate: (node, datum, translation, context) => {
|
|
if (QuickDatePriceRangeProperties.is(datum) && QuickMeasurerScene.is(node)) {
|
|
node.translate(datum, translation, context);
|
|
}
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (QuickDatePriceRangeProperties.is(datum) && QuickDatePriceRangeProperties.is(copiedDatum) && QuickMeasurerScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (QuickDatePriceRangeProperties.is(datum) && QuickMeasurerScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new QuickDatePriceRangeStateMachine({
|
|
...ctx,
|
|
create: createDatum("quick-date-price-range" /* QuickDatePriceRange */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/note/noteScene.ts
|
|
var NoteScene = class extends TextualPointScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "note" /* Note */;
|
|
this.shape = new module_support_exports.Rect();
|
|
this.iconBackground = new module_support_exports.TranslatableSvgPath(
|
|
"M22 1.83333C22 0.820811 21.1792 0 20.1667 0H1.83333C0.820811 0 0 0.82081 0 1.83333V13.9868C0 14.9994 0.820811 15.8202 1.83333 15.8202L5.88971 15.8202C6.44575 15.8202 6.97175 16.0725 7.31971 16.5062L9.57006 19.3112C10.304 20.2259 11.6962 20.2259 12.4301 19.3112L14.6804 16.5062C15.0284 16.0725 15.5544 15.8202 16.1104 15.8202L20.1667 15.8202C21.1792 15.8202 22 14.9994 22 13.9868V1.83333Z"
|
|
);
|
|
this.iconLines = new module_support_exports.TranslatableSvgPath(
|
|
"M17.1114 5.75C17.1114 6.16421 16.7756 6.5 16.3614 6.5H5.63916C5.22495 6.5 4.88916 6.16421 4.88916 5.75V5.75C4.88916 5.33579 5.22495 5 5.63916 5H16.3614C16.7756 5 17.1114 5.33579 17.1114 5.75V5.75ZM17.1114 9.25C17.1114 9.66421 16.7756 10 16.3614 10H5.63916C5.22495 10 4.88916 9.66421 4.88916 9.25V9.25C4.88916 8.83579 5.22495 8.5 5.63916 8.5H16.3614C16.7756 8.5 17.1114 8.83579 17.1114 9.25V9.25Z"
|
|
);
|
|
this.active = false;
|
|
this.shape.visible = false;
|
|
this.label.visible = false;
|
|
this.iconBackground.fillShadow = new module_support_exports.DropShadow();
|
|
this.append([this.shape, this.label, this.iconBackground, this.iconLines, this.handle]);
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "note" /* Note */);
|
|
}
|
|
update(datum, context) {
|
|
this.updateIcon(datum, context);
|
|
super.update(datum, context);
|
|
}
|
|
getTextBBox(datum, coords, context) {
|
|
const bbox = super.getTextBBox(datum, coords, context);
|
|
bbox.x -= datum.width / 2;
|
|
bbox.x = clamp(0, bbox.x, context.seriesRect.width - datum.width);
|
|
const padding2 = datum.getPadding().top;
|
|
const topY = bbox.y - LABEL_OFFSET - padding2 * 2;
|
|
const bottomY = bbox.y + DivariantHandle.HANDLE_SIZE + padding2 * 2;
|
|
if (topY - bbox.height - TOOLBAR_OFFSET < 0) {
|
|
bbox.y = bottomY;
|
|
datum.position = "top";
|
|
} else {
|
|
bbox.y = topY + padding2;
|
|
datum.position = "bottom";
|
|
}
|
|
return bbox;
|
|
}
|
|
updateLabel(datum, bbox) {
|
|
const labelVisibility = datum.visible === false ? false : this.label.visible;
|
|
super.updateLabel(datum, bbox);
|
|
this.label.visible = labelVisibility;
|
|
this.label.text = wrapText(datum.text, {
|
|
maxWidth: 200,
|
|
font: datum,
|
|
textWrap: "always",
|
|
avoidOrphans: false
|
|
});
|
|
}
|
|
updateShape(datum, bbox) {
|
|
const { shape } = this;
|
|
shape.fill = datum.background.fill;
|
|
shape.fillOpacity = datum.background.fillOpacity ?? 1;
|
|
shape.stroke = datum.background.stroke;
|
|
shape.strokeOpacity = datum.background.strokeOpacity ?? 1;
|
|
shape.strokeWidth = datum.background.strokeWidth ?? 1;
|
|
shape.cornerRadius = 4;
|
|
const padding2 = datum.getPadding().top;
|
|
const isPositionTop = datum.position === "top";
|
|
shape.x = bbox.x - padding2;
|
|
shape.width = datum.width + padding2 * 2;
|
|
shape.height = bbox.height + padding2 * 2;
|
|
shape.y = bbox.y + (isPositionTop ? 0 : -bbox.height) - padding2;
|
|
}
|
|
updateIcon(datum, context) {
|
|
const { active, iconBackground, iconLines } = this;
|
|
const { x, y } = convertPoint(datum, context);
|
|
iconBackground.translationX = x - ICON_WIDTH / 2;
|
|
iconBackground.translationY = y - ICON_HEIGHT;
|
|
iconLines.translationX = iconBackground.translationX;
|
|
iconLines.translationY = iconBackground.translationY;
|
|
iconBackground.fill = datum.fill;
|
|
iconBackground.fillOpacity = datum.fillOpacity ?? 1;
|
|
iconBackground.stroke = datum.stroke;
|
|
iconBackground.strokeOpacity = datum.strokeOpacity ?? 1;
|
|
iconBackground.strokeWidth = datum.strokeWidth ?? 1;
|
|
iconLines.fill = datum.stroke;
|
|
if (active) {
|
|
iconBackground.fillShadow.color = datum.fill ?? "rgba(0, 0, 0, 0.22)";
|
|
} else {
|
|
iconBackground.fillShadow.color = "rgba(0, 0, 0, 0.22)";
|
|
}
|
|
}
|
|
updateAnchor(datum, bbox, context) {
|
|
const padding2 = datum.getPadding().top;
|
|
const isPositionTop = datum.position === "top";
|
|
const direction = isPositionTop ? 1 : -1;
|
|
return {
|
|
x: bbox.x + context.seriesRect.x + datum.width / 2,
|
|
y: bbox.y + context.seriesRect.y + direction * (bbox.height + padding2),
|
|
position: isPositionTop ? "below" : "above"
|
|
};
|
|
}
|
|
getLabelCoords(datum, bbox) {
|
|
const isPositionTop = datum.position === "top";
|
|
const padding2 = datum.getPadding().top + calcLineHeight(datum.fontSize, ANNOTATION_TEXT_LINE_HEIGHT) / 2;
|
|
return { x: bbox.x, y: bbox.y + (isPositionTop ? padding2 / 2 : 0) };
|
|
}
|
|
getTextBaseline(datum) {
|
|
return datum.position === "top" ? "middle" : datum.position;
|
|
}
|
|
getHandleCoords(_datum, coords, _bbox) {
|
|
return {
|
|
x: coords.x,
|
|
y: coords.y + DivariantHandle.HANDLE_SIZE / 2 + 4
|
|
};
|
|
}
|
|
getHandleStyles(datum) {
|
|
return {
|
|
fill: datum.handle.fill,
|
|
stroke: datum.handle.stroke ?? datum.fill,
|
|
strokeOpacity: datum.handle.strokeOpacity,
|
|
strokeWidth: datum.handle.strokeWidth
|
|
};
|
|
}
|
|
toggleHovered(hovered, active, readOnly) {
|
|
super.toggleHovered(hovered, active, readOnly);
|
|
const visible = hovered || active && !readOnly;
|
|
this.label.visible = visible;
|
|
this.shape.visible = visible;
|
|
this.zIndex = visible ? 13 /* CHART_ANNOTATION_FOCUSED */ : 12 /* CHART_ANNOTATION */;
|
|
}
|
|
toggleActive(active) {
|
|
super.toggleActive(active);
|
|
this.label.visible = active;
|
|
this.shape.visible = active;
|
|
this.active = active;
|
|
}
|
|
containsPoint(x, y) {
|
|
if (this.shape.visible && this.shape.containsPoint(x, y))
|
|
return true;
|
|
if (this.iconBackground.containsPoint(x, y))
|
|
return true;
|
|
return super.containsPoint(x, y);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/note/noteState.ts
|
|
var NoteStateMachine = class extends TextualPointStateMachine {
|
|
createDatum() {
|
|
return new NoteProperties();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/note/noteConfig.ts
|
|
var noteConfig = {
|
|
type: "note" /* Note */,
|
|
datum: NoteProperties,
|
|
scene: NoteScene,
|
|
isDatum: NoteProperties.is,
|
|
translate: (node, datum, transition, context) => {
|
|
if (NoteProperties.is(datum) && NoteScene.is(node))
|
|
node.translate(datum, transition, context);
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (NoteProperties.is(datum) && NoteProperties.is(copiedDatum) && NoteScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (NoteProperties.is(datum) && NoteScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new NoteStateMachine({
|
|
...ctx,
|
|
create: createDatum("note" /* Note */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/parallel-channel/parallelChannelScene.ts
|
|
var ParallelChannelScene = class extends ChannelScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "parallel-channel";
|
|
this.handles = {
|
|
topLeft: new DivariantHandle(),
|
|
topMiddle: new UnivariantHandle(),
|
|
topRight: new DivariantHandle(),
|
|
bottomLeft: new DivariantHandle(),
|
|
bottomMiddle: new UnivariantHandle(),
|
|
bottomRight: new DivariantHandle()
|
|
};
|
|
this.middleLine = new CollidableLine();
|
|
this.append([this.background, this.topLine, this.middleLine, this.bottomLine, ...Object.values(this.handles)]);
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "parallel-channel");
|
|
}
|
|
dragHandle(datum, target, context, snapping) {
|
|
const { activeHandle, handles } = this;
|
|
if (activeHandle == null)
|
|
return;
|
|
const { offset } = handles[activeHandle].drag(target);
|
|
handles[activeHandle].toggleDragging(true);
|
|
if (activeHandle === "topMiddle" || activeHandle === "bottomMiddle") {
|
|
offset.x = 0;
|
|
}
|
|
let translateVectors = [];
|
|
let allowSnapping = snapping;
|
|
switch (activeHandle) {
|
|
case "topLeft":
|
|
case "bottomLeft":
|
|
translateVectors = ["topLeft", "bottomLeft"];
|
|
break;
|
|
case "topMiddle":
|
|
translateVectors = ["topLeft", "topRight"];
|
|
offset.y -= UnivariantHandle.HANDLE_SIZE / 2;
|
|
allowSnapping = false;
|
|
break;
|
|
case "topRight":
|
|
case "bottomRight":
|
|
translateVectors = ["topRight", "bottomRight"];
|
|
break;
|
|
case "bottomMiddle":
|
|
translateVectors = ["bottomLeft", "bottomRight"];
|
|
offset.y -= UnivariantHandle.HANDLE_SIZE / 2;
|
|
allowSnapping = false;
|
|
break;
|
|
}
|
|
const top = convertLine(datum, context);
|
|
const bottom = convertLine(datum.bottom, context);
|
|
if (!top || !bottom)
|
|
return;
|
|
const vectors = {
|
|
topLeft: vector4_exports.start(top),
|
|
topRight: vector4_exports.end(top),
|
|
bottomLeft: vector4_exports.start(bottom),
|
|
bottomRight: vector4_exports.end(bottom)
|
|
};
|
|
const snap = {
|
|
vectors: {
|
|
topLeft: vectors.topRight,
|
|
bottomLeft: vectors.bottomRight,
|
|
topRight: vectors.topLeft,
|
|
bottomRight: vectors.bottomLeft
|
|
},
|
|
angle: datum.snapToAngle
|
|
};
|
|
const points = translate(vectors, offset, context, {
|
|
overflowContinuous: this.overflowContinuous,
|
|
translateVectors,
|
|
snap: allowSnapping ? snap : void 0
|
|
});
|
|
datum.start.x = points.topLeft.x;
|
|
datum.start.y = points.topLeft.y;
|
|
datum.end.x = points.topRight.x;
|
|
datum.end.y = points.topRight.y;
|
|
datum.height = points.topLeft.y - points.bottomLeft.y;
|
|
}
|
|
getTranslatePointsVectors(start2, end3) {
|
|
const { bottomLeft, topLeft } = this.handles;
|
|
const height2 = bottomLeft.getBBox().y - topLeft.getBBox().y;
|
|
const bottomStart = vector_exports.add(start2, vector_exports.from(0, height2));
|
|
const bottomEnd = vector_exports.add(end3, vector_exports.from(0, height2));
|
|
return { start: start2, end: end3, bottomStart, bottomEnd };
|
|
}
|
|
containsPoint(x, y) {
|
|
return super.containsPoint(x, y) || this.middleLine.visible && this.middleLine.strokeWidth > 0 && this.middleLine.containsPoint(x, y);
|
|
}
|
|
getNodeAtCoords(x, y) {
|
|
if (this.middleLine.visible && this.middleLine.strokeWidth > 0 && this.middleLine.containsPoint(x, y))
|
|
return "line";
|
|
return super.getNodeAtCoords(x, y);
|
|
}
|
|
updateLines(datum, top, bottom, context, naturalTop, naturalBottom) {
|
|
const { topLine, middleLine, bottomLine } = this;
|
|
const { lineDashOffset, stroke: stroke3, strokeOpacity, strokeWidth } = datum;
|
|
const lineDash = datum.getLineDash();
|
|
const lineStyles = {
|
|
lineCap: datum.getLineCap(),
|
|
lineDash,
|
|
lineDashOffset,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth
|
|
};
|
|
topLine.setProperties({ ...top, ...lineStyles });
|
|
bottomLine.setProperties({ ...bottom, ...lineStyles });
|
|
const middlePoints = this.extendLine(
|
|
{
|
|
x1: naturalTop.x1,
|
|
y1: naturalBottom.y1 + (naturalTop.y1 - naturalBottom.y1) / 2,
|
|
x2: naturalTop.x2,
|
|
y2: naturalBottom.y2 + (naturalTop.y2 - naturalBottom.y2) / 2
|
|
},
|
|
datum,
|
|
context
|
|
);
|
|
middleLine.setProperties({
|
|
...middlePoints,
|
|
lineDash: datum.middle.lineDash ?? lineDash,
|
|
lineDashOffset: datum.middle.lineDashOffset ?? lineDashOffset,
|
|
stroke: datum.middle.stroke ?? stroke3,
|
|
strokeOpacity: datum.middle.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: datum.middle.strokeWidth ?? strokeWidth,
|
|
visible: datum.middle.visible ?? true
|
|
});
|
|
}
|
|
updateHandles(datum, top, bottom) {
|
|
const {
|
|
handles: { topLeft, topMiddle, topRight, bottomLeft, bottomMiddle, bottomRight }
|
|
} = this;
|
|
const handleStyles = {
|
|
fill: datum.handle.fill,
|
|
stroke: datum.handle.stroke ?? datum.stroke,
|
|
strokeOpacity: datum.handle.strokeOpacity ?? datum.strokeOpacity,
|
|
strokeWidth: datum.handle.strokeWidth ?? datum.strokeWidth
|
|
};
|
|
topLeft.update({ ...handleStyles, ...vector4_exports.start(top) });
|
|
topRight.update({ ...handleStyles, ...vector4_exports.end(top) });
|
|
bottomLeft.update({ ...handleStyles, ...vector4_exports.start(bottom) });
|
|
bottomRight.update({ ...handleStyles, ...vector4_exports.end(bottom) });
|
|
topMiddle.update({
|
|
...handleStyles,
|
|
...vector_exports.sub(vector4_exports.center(top), vector_exports.from(topMiddle.handle.width / 2, topMiddle.handle.height / 2))
|
|
});
|
|
bottomMiddle.update({
|
|
...handleStyles,
|
|
...vector_exports.sub(vector4_exports.center(bottom), vector_exports.from(bottomMiddle.handle.width / 2, bottomMiddle.handle.height / 2))
|
|
});
|
|
}
|
|
updateText(datum, top, bottom) {
|
|
this.text = this.updateNode(CollidableText, this.text, !!datum.text.label);
|
|
updateChannelText(true, top, bottom, datum.text, datum.strokeWidth, this.text, datum.text.label);
|
|
}
|
|
getBackgroundPoints(datum, top, bottom, bounds) {
|
|
const isFlippedX = top.x1 > top.x2;
|
|
const isFlippedY = top.y1 > top.y2;
|
|
const outOfBoundsStart = top.x1 !== bottom.x1 && top.y1 !== bottom.y1;
|
|
const outOfBoundsEnd = top.x2 !== bottom.x2 && top.y2 !== bottom.y2;
|
|
const points = vector_exports.from(top);
|
|
if (datum.extendEnd && outOfBoundsEnd) {
|
|
points.push(vector_exports.from(isFlippedX ? bounds.x1 : bounds.x2, isFlippedY ? bounds.y1 : bounds.y2));
|
|
}
|
|
points.push(...vector_exports.from(bottom).reverse());
|
|
if (datum.extendStart && outOfBoundsStart) {
|
|
points.push(vector_exports.from(isFlippedX ? bounds.x2 : bounds.x1, isFlippedY ? bounds.y2 : bounds.y1));
|
|
}
|
|
return points;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/parallel-channel/parallelChannelState.ts
|
|
var ParallelChannelStateMachine = class extends StateMachine {
|
|
constructor(ctx) {
|
|
const actionCreate = ({ point }) => {
|
|
const datum = new ParallelChannelProperties();
|
|
datum.set({ start: point, end: point, height: 0 });
|
|
ctx.create(datum);
|
|
};
|
|
const actionFirstRender = () => {
|
|
const { node } = this;
|
|
node?.toggleActive(true);
|
|
node?.toggleHandles({
|
|
topLeft: true,
|
|
topMiddle: false,
|
|
topRight: false,
|
|
bottomLeft: false,
|
|
bottomMiddle: false,
|
|
bottomRight: false
|
|
});
|
|
};
|
|
const actionEndUpdate = ({ offset, context }) => {
|
|
const { datum, snapping } = this;
|
|
if (!datum)
|
|
return;
|
|
datum.set({ end: snapPoint(offset, context, snapping, datum.start, datum.snapToAngle) });
|
|
ctx.update();
|
|
};
|
|
const actionEndFinish = () => {
|
|
this.node?.toggleHandles({
|
|
topRight: true
|
|
});
|
|
ctx.update();
|
|
};
|
|
const actionHeightUpdate = ({ point }) => {
|
|
const { datum, node } = this;
|
|
const endY = getGroupingValue(datum?.end.y);
|
|
const startY = getGroupingValue(datum?.start.y);
|
|
const { y: pointY } = point;
|
|
if (datum == null || !isNumber(startY) || !isNumber(endY) || !isNumber(pointY))
|
|
return;
|
|
const height2 = endY - (pointY ?? 0);
|
|
const bottomStartY = startY - height2;
|
|
node?.toggleHandles({ bottomLeft: true, bottomRight: true });
|
|
if (!ctx.validatePoint({ x: datum.start.x, y: bottomStartY }, { overflowContinuous: true }) || !ctx.validatePoint({ x: datum.end.x, y: point.y }, { overflowContinuous: true })) {
|
|
return;
|
|
}
|
|
datum.set({ height: height2 });
|
|
ctx.update();
|
|
};
|
|
const actionHeightFinish = ({ point }) => {
|
|
const { datum, node } = this;
|
|
const endY = getGroupingValue(datum?.end.y);
|
|
const startY = getGroupingValue(datum?.start.y);
|
|
const { y: pointY } = point;
|
|
if (datum == null || !isNumber(startY) || !isNumber(endY) || !isNumber(pointY))
|
|
return;
|
|
const height2 = endY - (pointY ?? 0);
|
|
const bottomStartY = startY - height2;
|
|
node?.toggleHandles(true);
|
|
if (!ctx.validatePoint({ x: datum.start.x, y: bottomStartY }, { overflowContinuous: true }) || !ctx.validatePoint({ x: datum.end.x, y: point.y }, { overflowContinuous: true })) {
|
|
return;
|
|
}
|
|
datum.set({ height: height2 });
|
|
ctx.recordAction(`Create ${"parallel-channel" /* ParallelChannel */} annotation`);
|
|
ctx.showAnnotationOptions();
|
|
ctx.update();
|
|
};
|
|
const actionCancel = () => ctx.delete();
|
|
super("start", {
|
|
start: {
|
|
click: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
drag: {
|
|
target: "waiting-first-render",
|
|
action: actionCreate
|
|
},
|
|
reset: StateMachine.parent
|
|
},
|
|
"waiting-first-render": {
|
|
render: {
|
|
target: "end",
|
|
action: actionFirstRender
|
|
}
|
|
},
|
|
end: {
|
|
hover: actionEndUpdate,
|
|
drag: actionEndUpdate,
|
|
click: {
|
|
target: "height",
|
|
action: actionEndFinish
|
|
},
|
|
dragEnd: {
|
|
target: "height",
|
|
action: actionEndFinish
|
|
},
|
|
reset: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
cancel: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
}
|
|
},
|
|
height: {
|
|
hover: actionHeightUpdate,
|
|
click: {
|
|
target: StateMachine.parent,
|
|
action: actionHeightFinish
|
|
},
|
|
drag: {
|
|
target: StateMachine.parent,
|
|
action: actionHeightFinish
|
|
},
|
|
reset: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
},
|
|
cancel: {
|
|
target: StateMachine.parent,
|
|
action: actionCancel
|
|
}
|
|
}
|
|
});
|
|
this.debug = debugLogger_exports.create(true, "annotations");
|
|
this.snapping = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], ParallelChannelStateMachine.prototype, "datum", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], ParallelChannelStateMachine.prototype, "node", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], ParallelChannelStateMachine.prototype, "snapping", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/parallel-channel/parallelChannelConfig.ts
|
|
var parallelChannelConfig = {
|
|
type: "parallel-channel" /* ParallelChannel */,
|
|
datum: ParallelChannelProperties,
|
|
scene: ParallelChannelScene,
|
|
isDatum: ParallelChannelProperties.is,
|
|
translate: (node, datum, transition, context) => {
|
|
if (ParallelChannelProperties.is(datum) && ParallelChannelScene.is(node)) {
|
|
node.translate(datum, transition, context);
|
|
}
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (ParallelChannelProperties.is(datum) && ParallelChannelProperties.is(copiedDatum) && ParallelChannelScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (ParallelChannelProperties.is(datum) && ParallelChannelScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new ParallelChannelStateMachine({
|
|
...ctx,
|
|
create: createDatum("parallel-channel" /* ParallelChannel */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/text/textScene.ts
|
|
var TextScene = class extends TextualPointScene {
|
|
constructor() {
|
|
super();
|
|
this.type = "text" /* Text */;
|
|
this.append([this.label, this.handle]);
|
|
}
|
|
static is(value) {
|
|
return AnnotationScene.isCheck(value, "text" /* Text */);
|
|
}
|
|
getHandleCoords(_datum, point) {
|
|
const halfSize = DivariantHandle.HANDLE_SIZE / 2;
|
|
return {
|
|
x: point.x + halfSize,
|
|
y: point.y + 2 + halfSize
|
|
};
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/text/textState.ts
|
|
var TextStateMachine = class extends TextualPointStateMachine {
|
|
createDatum() {
|
|
return new TextProperties();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/text/textConfig.ts
|
|
var textConfig = {
|
|
type: "text" /* Text */,
|
|
datum: TextProperties,
|
|
scene: TextScene,
|
|
isDatum: TextProperties.is,
|
|
translate: (node, datum, transition, context) => {
|
|
if (TextProperties.is(datum) && TextScene.is(node))
|
|
node.translate(datum, transition, context);
|
|
},
|
|
copy: (node, datum, copiedDatum, context) => {
|
|
if (TextProperties.is(datum) && TextProperties.is(copiedDatum) && TextScene.is(node)) {
|
|
return node.copy(datum, copiedDatum, context);
|
|
}
|
|
},
|
|
update: (node, datum, context) => {
|
|
if (TextProperties.is(datum) && TextScene.is(node)) {
|
|
node.update(datum, context);
|
|
}
|
|
},
|
|
createState: (ctx, { createDatum }) => new TextStateMachine({
|
|
...ctx,
|
|
create: createDatum("text" /* Text */)
|
|
}),
|
|
dragState: (ctx) => new DragStateMachine(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/annotationsConfig.ts
|
|
var annotationConfigs = {
|
|
// Lines
|
|
[lineConfig.type]: lineConfig,
|
|
[horizontalLineConfig.type]: horizontalLineConfig,
|
|
[verticalLineConfig.type]: verticalLineConfig,
|
|
// Channels
|
|
[parallelChannelConfig.type]: parallelChannelConfig,
|
|
[disjointChannelConfig.type]: disjointChannelConfig,
|
|
// Fibonaccis
|
|
[fibonacciRetracementConfig.type]: fibonacciRetracementConfig,
|
|
[fibonacciRetracementTrendBasedConfig.type]: fibonacciRetracementTrendBasedConfig,
|
|
// Texts
|
|
[calloutConfig.type]: calloutConfig,
|
|
[commentConfig.type]: commentConfig,
|
|
[noteConfig.type]: noteConfig,
|
|
[textConfig.type]: textConfig,
|
|
// Shapes
|
|
[arrowConfig.type]: arrowConfig,
|
|
[arrowUpConfig.type]: arrowUpConfig,
|
|
[arrowDownConfig.type]: arrowDownConfig,
|
|
// Measurers
|
|
[dateRangeConfig.type]: dateRangeConfig,
|
|
[priceRangeConfig.type]: priceRangeConfig,
|
|
[datePriceRangeConfig.type]: datePriceRangeConfig,
|
|
[quickDatePriceRangeConfig.type]: quickDatePriceRangeConfig
|
|
};
|
|
function getTypedDatum(datum) {
|
|
for (const { isDatum } of Object.values(annotationConfigs)) {
|
|
if (isDatum(datum)) {
|
|
return datum;
|
|
}
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/annotationsStateMachine.ts
|
|
var AnnotationsStateMachine = class extends ParallelStateMachine {
|
|
constructor(ctx) {
|
|
super(
|
|
new SnappingStateMachine((snapping) => {
|
|
this.snapping = snapping;
|
|
}),
|
|
new UpdateMachine(() => {
|
|
this.node = this.active == null ? void 0 : ctx.node(this.active);
|
|
}),
|
|
new AnnotationsMainStateMachine(ctx, (index) => {
|
|
this.active = index;
|
|
this.datum = this.active == null ? void 0 : ctx.datum(this.active);
|
|
this.node = this.active == null ? void 0 : ctx.node(this.active);
|
|
})
|
|
);
|
|
this.snapping = false;
|
|
}
|
|
// TODO: remove this leak
|
|
getActive() {
|
|
return this.active;
|
|
}
|
|
// TODO: remove this leak
|
|
isActive(index) {
|
|
return index === this.active;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], AnnotationsStateMachine.prototype, "snapping", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], AnnotationsStateMachine.prototype, "datum", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], AnnotationsStateMachine.prototype, "node", 2);
|
|
var SnappingStateMachine = class extends StateMachine {
|
|
constructor(setSnapping) {
|
|
super("idle" /* Idle */, {
|
|
["idle" /* Idle */]: {
|
|
hover: ({ shiftKey }) => setSnapping(shiftKey),
|
|
keyDown: ({ shiftKey }) => setSnapping(shiftKey),
|
|
keyUp: ({ shiftKey }) => setSnapping(shiftKey),
|
|
click: ({ shiftKey }) => setSnapping(shiftKey),
|
|
drag: ({ shiftKey }) => setSnapping(shiftKey)
|
|
},
|
|
["dragging" /* Dragging */]: {},
|
|
["translating" /* Translating */]: {},
|
|
["text-input" /* TextInput */]: {}
|
|
});
|
|
}
|
|
};
|
|
var UpdateMachine = class extends StateMachine {
|
|
constructor(update) {
|
|
super("idle" /* Idle */, {
|
|
["idle" /* Idle */]: {
|
|
onEnter: update,
|
|
render: update
|
|
},
|
|
["dragging" /* Dragging */]: {
|
|
onEnter: update,
|
|
render: update
|
|
},
|
|
["translating" /* Translating */]: {
|
|
onEnter: update,
|
|
render: update
|
|
},
|
|
["text-input" /* TextInput */]: {
|
|
render: update
|
|
}
|
|
});
|
|
}
|
|
};
|
|
var AnnotationsMainStateMachine = class extends StateMachine {
|
|
constructor(ctx, setActive) {
|
|
const createDatum = (type) => (datum) => {
|
|
ctx.create(type, datum);
|
|
this.active = ctx.selectLast();
|
|
};
|
|
const deleteDatum = () => {
|
|
if (this.active != null)
|
|
ctx.delete(this.active);
|
|
this.active = void 0;
|
|
ctx.select();
|
|
};
|
|
const stateMachineHelpers = {
|
|
createDatum
|
|
};
|
|
const createStateMachineContext = {
|
|
...ctx,
|
|
delete: deleteDatum,
|
|
showTextInput: () => {
|
|
if (this.active != null)
|
|
ctx.showTextInput(this.active);
|
|
},
|
|
deselect: () => {
|
|
const prevActive = this.active;
|
|
this.active = void 0;
|
|
this.hovered = void 0;
|
|
ctx.select(this.active, prevActive);
|
|
},
|
|
showAnnotationOptions: () => {
|
|
if (this.active != null)
|
|
ctx.showAnnotationOptions(this.active);
|
|
}
|
|
};
|
|
const createStateMachines = Object.fromEntries(
|
|
Object.entries(annotationConfigs).map(([type, config]) => [
|
|
type,
|
|
config.createState(createStateMachineContext, stateMachineHelpers)
|
|
])
|
|
);
|
|
const dragStateMachines = Object.fromEntries(
|
|
Object.entries(annotationConfigs).map(([type, config]) => [
|
|
type,
|
|
config.dragState(ctx, stateMachineHelpers)
|
|
])
|
|
);
|
|
const actionColor = ({
|
|
colorPickerType,
|
|
colorOpacity,
|
|
color: color2,
|
|
opacity,
|
|
isMultiColor
|
|
}) => {
|
|
if (!this.datum)
|
|
return;
|
|
if (colorPickerType === "text-color") {
|
|
ctx.updateTextInputColor(color2);
|
|
}
|
|
setColor(this.datum, colorPickerType, colorOpacity, color2, opacity, isMultiColor);
|
|
ctx.update();
|
|
};
|
|
const actionFontSize = (fontSize) => {
|
|
const { datum, node } = this;
|
|
if (!datum || !node)
|
|
return;
|
|
if (isTextType(datum)) {
|
|
datum.fontSize = fontSize;
|
|
ctx.updateTextInputFontSize(fontSize);
|
|
} else if (hasLineText(datum)) {
|
|
datum.text.fontSize = fontSize;
|
|
}
|
|
ctx.update();
|
|
};
|
|
const actionLineStyle = (lineStyle) => {
|
|
const { datum, node } = this;
|
|
if (!datum || !node || !hasLineStyle(datum))
|
|
return;
|
|
setLineStyle(datum, lineStyle);
|
|
ctx.update();
|
|
};
|
|
const actionUpdateTextInputBBox = (bbox) => {
|
|
const { node } = this;
|
|
if (!node || !("setTextInputBBox" in node))
|
|
return;
|
|
node.setTextInputBBox(bbox);
|
|
ctx.update();
|
|
};
|
|
const actionSaveText = ({ textInputValue, bbox }) => {
|
|
const { datum } = this;
|
|
if (bbox != null && textInputValue != null && textInputValue.length > 0) {
|
|
if (!isTextType(datum)) {
|
|
return;
|
|
}
|
|
const wrappedText = maybeWrapText(datum, textInputValue, bbox.width);
|
|
datum.set({ text: wrappedText });
|
|
ctx.update();
|
|
ctx.recordAction(`Change ${datum.type} annotation text`);
|
|
} else {
|
|
ctx.delete(this.active);
|
|
ctx.recordAction(`Delete ${datum?.type} annotation`);
|
|
}
|
|
};
|
|
const actionCancel = () => {
|
|
ctx.updateTextInputBBox(void 0);
|
|
};
|
|
const guardActive = () => this.active != null;
|
|
const guardCopied = () => this.copied != null;
|
|
const guardActiveHasLineText = () => {
|
|
const { active, datum } = this;
|
|
if (active == null)
|
|
return false;
|
|
if (!datum)
|
|
return false;
|
|
return hasLineText(datum) && datum.isWriteable();
|
|
};
|
|
const guardActiveNotEphemeral = () => this.active != null && !isEphemeralType(this.datum);
|
|
const guardHovered = () => this.hovered != null;
|
|
super("idle" /* Idle */, {
|
|
["idle" /* Idle */]: {
|
|
onEnter: () => {
|
|
ctx.select(this.active, this.active);
|
|
if (this.hoverCoords) {
|
|
this.hovered = ctx.hoverAtCoords(this.hoverCoords, this.active, this.hovered);
|
|
}
|
|
},
|
|
hover: ({ offset }) => {
|
|
this.hovered = ctx.hoverAtCoords(offset, this.active, this.hovered);
|
|
this.hoverCoords = offset;
|
|
},
|
|
translate: {
|
|
guard: guardActive,
|
|
target: "translating" /* Translating */,
|
|
action: ({ translation }) => {
|
|
ctx.startInteracting();
|
|
ctx.translate(this.active, translation);
|
|
ctx.update();
|
|
}
|
|
},
|
|
copy: {
|
|
guard: guardActiveNotEphemeral,
|
|
action: () => {
|
|
this.copied = ctx.copy(this.active);
|
|
}
|
|
},
|
|
cut: {
|
|
guard: guardActiveNotEphemeral,
|
|
action: () => {
|
|
this.copied = ctx.copy(this.active);
|
|
deleteDatum();
|
|
}
|
|
},
|
|
paste: {
|
|
guard: guardCopied,
|
|
action: () => {
|
|
ctx.paste(this.copied);
|
|
}
|
|
},
|
|
selectLast: () => {
|
|
const previousActive = this.active;
|
|
this.active = ctx.selectLast();
|
|
ctx.select(this.active, previousActive);
|
|
},
|
|
click: [
|
|
{
|
|
guard: () => {
|
|
const { active, hovered, datum } = this;
|
|
if (active == null || hovered !== active)
|
|
return false;
|
|
if (!datum)
|
|
return false;
|
|
return isTextType(datum) && datum.isWriteable();
|
|
},
|
|
target: "text-input" /* TextInput */
|
|
},
|
|
{
|
|
action: () => {
|
|
const prevActive = this.active;
|
|
this.active = this.hovered;
|
|
ctx.select(this.active, prevActive);
|
|
}
|
|
}
|
|
],
|
|
dblclick: {
|
|
guard: guardActiveHasLineText,
|
|
action: ({ offset }) => {
|
|
const nodeAtCoords = ctx.getNodeAtCoords(offset, this.active) === "text" ? "text" : "line";
|
|
ctx.showAnnotationSettings(this.active, void 0, nodeAtCoords);
|
|
}
|
|
},
|
|
dragStart: [
|
|
{
|
|
guard: guardHovered,
|
|
target: "dragging" /* Dragging */,
|
|
action: () => {
|
|
const prevActive = this.active;
|
|
this.active = this.hovered;
|
|
ctx.select(this.active, prevActive);
|
|
ctx.startInteracting();
|
|
}
|
|
},
|
|
{
|
|
action: () => {
|
|
const prevActive = this.active;
|
|
this.active = this.hovered;
|
|
ctx.select(this.active, prevActive);
|
|
}
|
|
}
|
|
],
|
|
color: {
|
|
guard: guardActive,
|
|
action: actionColor
|
|
},
|
|
fontSize: {
|
|
guard: guardActive,
|
|
action: actionFontSize
|
|
},
|
|
lineProps: {
|
|
guard: guardActive,
|
|
action: (props) => {
|
|
const datum = getTypedDatum(this.datum);
|
|
datum?.set(props);
|
|
ctx.update();
|
|
ctx.recordAction(
|
|
`Change ${datum?.type} ${Object.entries(props).map(([key, value]) => `${key} to ${value}`).join(", ")}`
|
|
);
|
|
}
|
|
},
|
|
lineStyle: {
|
|
guard: guardActive,
|
|
action: actionLineStyle
|
|
},
|
|
lineText: {
|
|
guard: guardActive,
|
|
action: (props) => {
|
|
const datum = getTypedDatum(this.datum);
|
|
if (!hasLineText(datum))
|
|
return;
|
|
if (isChannelType(datum) && props.position === "center") {
|
|
props.position = "inside";
|
|
}
|
|
datum.text.set(props);
|
|
ctx.update();
|
|
}
|
|
},
|
|
updateTextInputBBox: {
|
|
guard: guardActive,
|
|
action: actionUpdateTextInputBBox
|
|
},
|
|
toolbarPressSettings: {
|
|
guard: guardActiveHasLineText,
|
|
action: (sourceEvent) => {
|
|
ctx.showAnnotationSettings(this.active, sourceEvent);
|
|
}
|
|
},
|
|
reset: () => {
|
|
if (this.active != null) {
|
|
this.node?.toggleActive(false);
|
|
}
|
|
this.hovered = void 0;
|
|
this.active = void 0;
|
|
ctx.select(this.active, this.active);
|
|
ctx.resetToIdle();
|
|
},
|
|
delete: () => {
|
|
if (this.active == null)
|
|
return;
|
|
ctx.delete(this.active);
|
|
if (isEphemeralType(this.datum))
|
|
return;
|
|
ctx.recordAction(`Delete ${this.datum?.type} annotation`);
|
|
},
|
|
deleteAll: () => {
|
|
ctx.deleteAll();
|
|
},
|
|
...createStateMachines
|
|
},
|
|
["dragging" /* Dragging */]: {
|
|
onEnter: (_, data) => {
|
|
if (this.active == null)
|
|
return;
|
|
const type = ctx.getAnnotationType(this.active);
|
|
if (!type)
|
|
return;
|
|
this.transitionRoot(type);
|
|
this.transitionRoot("dragStart", data);
|
|
},
|
|
...dragStateMachines
|
|
},
|
|
["translating" /* Translating */]: {
|
|
onEnter: () => {
|
|
},
|
|
translate: {
|
|
guard: guardActive,
|
|
target: "translating" /* Translating */,
|
|
action: ({ translation }) => {
|
|
ctx.startInteracting();
|
|
ctx.translate(this.active, translation);
|
|
ctx.update();
|
|
}
|
|
},
|
|
translateEnd: {
|
|
guard: guardActive,
|
|
target: "idle" /* Idle */
|
|
},
|
|
onExit: () => {
|
|
ctx.stopInteracting();
|
|
ctx.update();
|
|
ctx.recordAction("Translate annotation");
|
|
}
|
|
},
|
|
["text-input" /* TextInput */]: {
|
|
onEnter: () => {
|
|
if (this.active == null)
|
|
return;
|
|
const datum = getTypedDatum(this.datum);
|
|
if (!datum || !("getTextInputCoords" in datum))
|
|
return;
|
|
ctx.startInteracting();
|
|
ctx.showTextInput(this.active);
|
|
datum.visible = false;
|
|
ctx.update();
|
|
},
|
|
updateTextInputBBox: {
|
|
guard: guardActive,
|
|
action: actionUpdateTextInputBBox
|
|
},
|
|
resize: {
|
|
target: "idle" /* Idle */,
|
|
action: actionSaveText
|
|
},
|
|
click: {
|
|
target: "idle" /* Idle */,
|
|
action: actionSaveText
|
|
},
|
|
drag: {
|
|
target: "idle" /* Idle */,
|
|
action: actionSaveText
|
|
},
|
|
textInput: [
|
|
{
|
|
guard: guardCancelAndExit,
|
|
target: "idle" /* Idle */,
|
|
action: actionCancel
|
|
},
|
|
{
|
|
guard: guardSaveAndExit,
|
|
target: "idle" /* Idle */,
|
|
action: actionSaveText
|
|
}
|
|
],
|
|
color: {
|
|
guard: guardActive,
|
|
action: actionColor
|
|
},
|
|
fontSize: {
|
|
guard: guardActive,
|
|
action: actionFontSize
|
|
},
|
|
cancel: {
|
|
target: "idle" /* Idle */,
|
|
action: actionCancel
|
|
},
|
|
onExit: () => {
|
|
ctx.stopInteracting();
|
|
ctx.hideTextInput();
|
|
const wasActive = this.active;
|
|
this.active = this.hovered = void 0;
|
|
ctx.select(this.active, wasActive);
|
|
if (wasActive == null)
|
|
return;
|
|
const datum = ctx.datum(wasActive);
|
|
const node = ctx.node(wasActive);
|
|
if (!datum || !node)
|
|
return;
|
|
datum.visible = true;
|
|
}
|
|
}
|
|
});
|
|
this.setActive = setActive;
|
|
this.debug = debugLogger_exports.create(true, "annotations");
|
|
this.snapping = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue(newValue) {
|
|
this.setActive(newValue);
|
|
}
|
|
}),
|
|
StateMachineProperty()
|
|
], AnnotationsMainStateMachine.prototype, "active", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], AnnotationsMainStateMachine.prototype, "hovered", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], AnnotationsMainStateMachine.prototype, "hoverCoords", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], AnnotationsMainStateMachine.prototype, "copied", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], AnnotationsMainStateMachine.prototype, "snapping", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], AnnotationsMainStateMachine.prototype, "datum", 2);
|
|
__decorateClass([
|
|
StateMachineProperty()
|
|
], AnnotationsMainStateMachine.prototype, "node", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/annotationsToolbar.ts
|
|
var { LayoutElement: LayoutElement2, Menu: Menu3 } = module_support_exports;
|
|
var AnnotationsToolbarButtonProperties = class extends ToolbarButtonProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AnnotationsToolbarButtonProperties.prototype, "value", 2);
|
|
var AnnotationsToolbar = class extends BaseProperties {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.enabled = true;
|
|
this.padding = 20;
|
|
this.buttons = new PropertiesArray(AnnotationsToolbarButtonProperties);
|
|
this.events = new EventEmitter();
|
|
this.annotationMenu = new Menu3(this.ctx, "annotations");
|
|
this.cleanup = new CleanupRegistry();
|
|
this.toolbar = ctx.sharedToolbar.getSharedToolbar("annotations");
|
|
const onKeyDown = this.onKeyDown.bind(this);
|
|
this.toolbar.addListener("keydown", onKeyDown);
|
|
this.cleanup.register(
|
|
this.toolbar.addToolbarListener("button-pressed", this.onToolbarButtonPress.bind(this)),
|
|
ctx.layoutManager.registerElement(LayoutElement2.ToolbarLeft, this.onLayoutStart.bind(this)),
|
|
() => {
|
|
this.toolbar.removeListener("keydown", onKeyDown);
|
|
this.toolbar.destroy();
|
|
}
|
|
);
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
}
|
|
toggleClearButtonEnabled(enabled) {
|
|
const index = this.buttons.findIndex((button) => button.value === "clear");
|
|
this.toolbar.toggleButtonEnabledByIndex(index, enabled);
|
|
}
|
|
resetButtonIcons() {
|
|
for (const [index, button] of this.buttons.entries()) {
|
|
switch (button.value) {
|
|
case "line-menu":
|
|
this.updateButtonByIndex(index, { icon: "trend-line-drawing", value: "line-menu" });
|
|
break;
|
|
case "fibonacci-menu":
|
|
this.updateButtonByIndex(index, { icon: "fibonacci-retracement-drawing", value: "fibonacci-menu" });
|
|
break;
|
|
case "text-menu":
|
|
this.updateButtonByIndex(index, { icon: "text-annotation", value: "text-menu" });
|
|
break;
|
|
case "shape-menu":
|
|
this.updateButtonByIndex(index, { icon: "arrow-drawing", value: "shape-menu" });
|
|
break;
|
|
case "measurer-menu":
|
|
this.updateButtonByIndex(index, { icon: "measurer-drawing", value: "measurer-menu" });
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
hideOverlays() {
|
|
this.annotationMenu.hide();
|
|
}
|
|
clearActiveButton() {
|
|
this.toolbar.clearActiveButton();
|
|
}
|
|
onLayoutStart(ctx) {
|
|
if (!this.enabled)
|
|
return;
|
|
this.toolbar.updateButtons(this.buttons);
|
|
this.toolbar.layout(ctx.layoutBox, this.padding);
|
|
}
|
|
refreshButtonsEnabled(enabled) {
|
|
for (const [index, button] of this.buttons.entries()) {
|
|
if (!button)
|
|
continue;
|
|
this.toolbar.toggleButtonEnabledByIndex(index, enabled);
|
|
}
|
|
}
|
|
onToolbarButtonPress({
|
|
event,
|
|
button,
|
|
buttonBounds,
|
|
buttonWidget
|
|
}) {
|
|
const axisScale = this.ctx.axisManager.getAxisContext("y" /* Y */)[0].scale;
|
|
switch (button.value) {
|
|
case "clear":
|
|
this.events.emit("pressed-clear", null);
|
|
break;
|
|
case "line-menu":
|
|
this.onToolbarButtonPressShowMenu(
|
|
event,
|
|
buttonBounds,
|
|
buttonWidget,
|
|
button.value,
|
|
"toolbarAnnotationsLineAnnotations",
|
|
LINE_ANNOTATION_ITEMS.filter((item) => item.visible ? item.visible(axisScale) : true)
|
|
);
|
|
break;
|
|
case "fibonacci-menu":
|
|
this.onToolbarButtonPressShowMenu(
|
|
event,
|
|
buttonBounds,
|
|
buttonWidget,
|
|
button.value,
|
|
"toolbarAnnotationsFibonacciAnnotations",
|
|
FIBONACCI_ANNOTATION_ITEMS
|
|
);
|
|
break;
|
|
case "text-menu":
|
|
this.onToolbarButtonPressShowMenu(
|
|
event,
|
|
buttonBounds,
|
|
buttonWidget,
|
|
button.value,
|
|
"toolbarAnnotationsTextAnnotations",
|
|
TEXT_ANNOTATION_ITEMS
|
|
);
|
|
break;
|
|
case "shape-menu":
|
|
this.onToolbarButtonPressShowMenu(
|
|
event,
|
|
buttonBounds,
|
|
buttonWidget,
|
|
button.value,
|
|
"toolbarAnnotationsShapeAnnotations",
|
|
SHAPE_ANNOTATION_ITEMS
|
|
);
|
|
break;
|
|
case "measurer-menu":
|
|
this.onToolbarButtonPressShowMenu(
|
|
event,
|
|
buttonBounds,
|
|
buttonWidget,
|
|
button.value,
|
|
"toolbarAnnotationsMeasurerAnnotations",
|
|
MEASURER_ANNOTATION_ITEMS
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
onToolbarButtonPressShowMenu(event, buttonBounds, controller, menu, ariaLabel, items) {
|
|
this.events.emit("pressed-show-menu", null);
|
|
const index = this.buttons.findIndex((button) => button.value === menu);
|
|
this.toolbar.toggleActiveButtonByIndex(index);
|
|
this.annotationMenu.setAnchor({ x: buttonBounds.x + buttonBounds.width + 6, y: buttonBounds.y });
|
|
this.annotationMenu.show(controller, {
|
|
items,
|
|
ariaLabel: this.ctx.localeManager.t(ariaLabel),
|
|
class: "ag-charts-annotations__toolbar-menu",
|
|
sourceEvent: event.sourceEvent,
|
|
onPress: this.onButtonPressMenuCreateAnnotation.bind(this, menu)
|
|
});
|
|
}
|
|
onButtonPressMenuCreateAnnotation(menu, item) {
|
|
const index = this.buttons.findIndex((button) => button.value === menu);
|
|
this.updateButtonByIndex(index, { icon: item.icon });
|
|
this.events.emit("pressed-create-annotation", { annotation: item.value });
|
|
this.annotationMenu.hide();
|
|
}
|
|
onKeyDown({ sourceEvent }) {
|
|
if (sourceEvent.key === "Escape") {
|
|
this.events.emit("cancel-create-annotation", null);
|
|
}
|
|
}
|
|
updateButtonByIndex(index, change) {
|
|
const button = this.buttons.at(index);
|
|
if (!button)
|
|
return;
|
|
button.set({ ...button.toJson(), ...change, value: change.value ?? button.value });
|
|
this.toolbar.updateButtonByIndex(index, { ...button.toJson() });
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ActionOnSet({
|
|
changeValue(enabled) {
|
|
this.toolbar?.setHidden(!enabled);
|
|
}
|
|
})
|
|
], AnnotationsToolbar.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AnnotationsToolbar.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AnnotationsToolbar.prototype, "buttons", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/axisButton.ts
|
|
var { InteractionState: InteractionState2 } = module_support_exports;
|
|
var DEFAULT_ANNOTATION_AXIS_BUTTON_CLASS = `ag-charts-annotations__axis-button`;
|
|
var AxisButton = class extends AbstractModuleInstance {
|
|
constructor(ctx, axisCtx, onButtonClick, seriesRect) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.axisCtx = axisCtx;
|
|
this.onButtonClick = onButtonClick;
|
|
this.seriesRect = seriesRect;
|
|
this.enabled = true;
|
|
this.snap = false;
|
|
this.padding = 0;
|
|
this.button = this.setup();
|
|
this.toggleVisibility(false);
|
|
this.updateButtonElement();
|
|
this.snap = Boolean(axisCtx.scale.bandwidth);
|
|
ctx.domManager.addEventListener("focusin", ({ target }) => {
|
|
const htmlTarget = target instanceof HTMLElement ? target : void 0;
|
|
const isSeriesAreaChild = htmlTarget && ctx.domManager.contains(htmlTarget, "series-area");
|
|
if (!isSeriesAreaChild && htmlTarget !== this.button.getElement())
|
|
this.hide();
|
|
});
|
|
this.cleanup.register(
|
|
ctx.widgets.seriesWidget.addListener("drag-move", (e) => this.onMouseDrag(e)),
|
|
ctx.widgets.seriesWidget.addListener("mousemove", (e) => this.onMouseMove(e)),
|
|
ctx.widgets.seriesWidget.addListener("mouseleave", () => this.onMouseLeave()),
|
|
ctx.widgets.seriesDragInterpreter?.events.on("click", (e) => this.onClick(e)),
|
|
ctx.eventsHub.on("series:focus-change", () => this.onKeyPress()),
|
|
ctx.eventsHub.on("zoom:pan-start", () => this.hide()),
|
|
ctx.eventsHub.on("zoom:change-complete", () => this.hide()),
|
|
() => this.destroyElements(),
|
|
() => this.button.destroy()
|
|
);
|
|
}
|
|
update(seriesRect, padding2) {
|
|
this.seriesRect = seriesRect;
|
|
this.padding = padding2;
|
|
}
|
|
setup() {
|
|
const button = new exports_exports.ButtonWidget();
|
|
button.addClass(DEFAULT_ANNOTATION_AXIS_BUTTON_CLASS);
|
|
button.setTabIndex(-1);
|
|
button.setAriaLabel(this.ctx.localeManager.t("ariaLabelAddHorizontalLine"));
|
|
this.ctx.widgets.seriesWidget.getElement().appendChild(button.getElement());
|
|
return button;
|
|
}
|
|
destroyElements() {
|
|
this.ctx.domManager.removeChild("canvas-overlay", DEFAULT_ANNOTATION_AXIS_BUTTON_CLASS);
|
|
}
|
|
onMouseMove(e) {
|
|
if (this.ctx.interactionManager.isState(InteractionState2.Clickable))
|
|
this.show(e);
|
|
}
|
|
onMouseDrag(e) {
|
|
if (this.ctx.interactionManager.isState(InteractionState2.AnnotationsMoveable))
|
|
this.show(e);
|
|
}
|
|
onMouseLeave() {
|
|
if (this.ctx.interactionManager.isState(InteractionState2.Clickable))
|
|
this.hide();
|
|
}
|
|
onClick(e) {
|
|
if (this.ctx.interactionManager.isState(InteractionState2.Clickable) && e.device === "touch")
|
|
this.show(e);
|
|
}
|
|
show(event) {
|
|
const { sourceEvent, currentX: x, currentY: y } = event;
|
|
if (!(this.enabled && this.ctx.widgets.seriesWidget.getElement().contains(sourceEvent.target))) {
|
|
this.hide();
|
|
return;
|
|
}
|
|
this.toggleVisibility(true);
|
|
const buttonCoords = this.getButtonCoordinates({ x, y });
|
|
this.coords = {
|
|
x: buttonCoords.x + this.button.clientWidth / 2,
|
|
y: buttonCoords.y + this.button.clientHeight / 2
|
|
};
|
|
this.updatePosition(buttonCoords);
|
|
}
|
|
hide() {
|
|
this.toggleVisibility(false);
|
|
}
|
|
onKeyPress() {
|
|
if (this.snap && this.ctx.interactionManager.isState(InteractionState2.Default))
|
|
return;
|
|
this.hide();
|
|
}
|
|
getButtonCoordinates({ x, y }) {
|
|
const {
|
|
axisCtx: { direction, position },
|
|
seriesRect,
|
|
snap,
|
|
axisCtx,
|
|
padding: padding2
|
|
} = this;
|
|
const { clientWidth: buttonWidth, clientHeight: buttonHeight } = this.button;
|
|
const [minY, maxY] = [0, seriesRect.height];
|
|
const [minX, maxX] = [0, seriesRect.width];
|
|
if (snap) {
|
|
x = convert(invert(x - seriesRect.x, axisCtx), axisCtx) + seriesRect.x;
|
|
y = convert(invert(y - seriesRect.y, axisCtx), axisCtx) + seriesRect.y;
|
|
}
|
|
if (direction === "x" /* X */) {
|
|
const crosshairLabelPadding = 5;
|
|
const offset = buttonHeight - Math.max(0, padding2 - crosshairLabelPadding);
|
|
x = x - buttonWidth / 2;
|
|
y = position === "top" ? minY - buttonHeight + offset : maxY - offset;
|
|
} else {
|
|
const crosshairLabelPadding = 9;
|
|
const offset = buttonWidth - Math.max(0, padding2 - crosshairLabelPadding);
|
|
x = position === "left" ? minX - buttonWidth + offset : maxX - offset;
|
|
y = y - buttonHeight / 2;
|
|
}
|
|
return { x, y };
|
|
}
|
|
toggleVisibility(visible) {
|
|
const { button } = this;
|
|
if (button == null)
|
|
return;
|
|
const isVisible = this.enabled && visible;
|
|
this.toggleClass("-hidden", !isVisible);
|
|
}
|
|
toggleClass(name, include) {
|
|
this.button.toggleClass(`${DEFAULT_ANNOTATION_AXIS_BUTTON_CLASS}-${name}`, include);
|
|
}
|
|
updatePosition({ x, y }) {
|
|
this.button.getElement().style.transform = `translate(${Math.round(x)}px, ${Math.round(y)}px)`;
|
|
}
|
|
updateButtonElement() {
|
|
const { button } = this;
|
|
button.addListener("click", () => this.onButtonClick(this.coords));
|
|
button.addListener("touchend", () => this.onButtonClick(this.coords));
|
|
button.addListener("drag-start", () => {
|
|
});
|
|
button.setInnerHTML(
|
|
`<span class="${getIconClassNames("zoom-in")} ${DEFAULT_ANNOTATION_AXIS_BUTTON_CLASS}-icon"></span>`
|
|
);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AxisButton.prototype, "enabled", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/components/dialog/dialog.ts
|
|
var { DraggablePopover: DraggablePopover2, NativeWidget: NativeWidget2 } = module_support_exports;
|
|
var _Dialog = class _Dialog extends DraggablePopover2 {
|
|
constructor(ctx, id) {
|
|
super(ctx, id);
|
|
this.dragHandleDraggingClass = "ag-charts-dialog__drag-handle--dragging";
|
|
this.colorPicker = new ColorPicker(this.ctx, { detached: true });
|
|
this.cleanup.register(ctx.eventsHub.on("layout:complete", this.onLayoutComplete.bind(this)));
|
|
}
|
|
showWithChildren(children, options) {
|
|
const popover = super.showWithChildren(children, options);
|
|
popover.classList.add("ag-charts-dialog");
|
|
popover.setAttribute("role", "dialog");
|
|
popover.addEventListener("mousedown", (event) => {
|
|
if (event.target.classList?.contains("ag-charts-dialog__color-picker-button"))
|
|
return;
|
|
this.colorPicker.hide();
|
|
});
|
|
popover.addEventListener("keydown", this.onKeyDown.bind(this));
|
|
getWindow().requestAnimationFrame(() => this.reposition());
|
|
this.colorPicker.attachTo(this);
|
|
return popover;
|
|
}
|
|
updatePosition(position) {
|
|
super.updatePosition(position);
|
|
const { anchor, fallbackAnchor } = this.getColorPickerAnchors() ?? {};
|
|
if (!anchor)
|
|
return;
|
|
this.colorPicker.setAnchor(anchor, fallbackAnchor);
|
|
}
|
|
/**************
|
|
* Containers *
|
|
**************/
|
|
createTabs(tablistLabel, initial, tabs) {
|
|
const element2 = createElement("div", "ag-charts-dialog__tabs");
|
|
const tabButtonIds = mapValues(tabs, () => createElementId());
|
|
const tabPanelIds = mapValues(tabs, () => createElementId());
|
|
for (const [key, tab] of entries(tabs)) {
|
|
setAttributes(tab.panel, {
|
|
id: tabPanelIds[key],
|
|
role: "tabpanel",
|
|
"aria-labelledby": tabButtonIds[key]
|
|
});
|
|
}
|
|
const onPressTab = (active) => {
|
|
for (const [key, tab] of entries(tabs)) {
|
|
tab.panel.classList.toggle("ag-charts-dialog__tab-panel--active", key === active);
|
|
tabButtons[key].classList.toggle("ag-charts-dialog__tab-button--active", key === active);
|
|
setAttribute(tabButtons[key], "aria-selected", key === active);
|
|
if (key === active)
|
|
tab.onShow?.();
|
|
}
|
|
};
|
|
const header = new NativeWidget2(createElement("div", "ag-charts-dialog__header"));
|
|
header.addListener("drag-start", (event) => {
|
|
const { sourceEvent } = event;
|
|
if (sourceEvent.target instanceof Element && sourceEvent.target.classList.contains("ag-charts-dialog__header")) {
|
|
this.onDragStart(event);
|
|
}
|
|
});
|
|
header.addListener("drag-move", (event) => this.onDragMove(event));
|
|
header.addListener("drag-end", () => this.onDragEnd());
|
|
const dragHandle = new DragHandleWidget2();
|
|
this.setDragHandle(dragHandle);
|
|
const tabButtons = mapValues(
|
|
tabs,
|
|
(tab, key) => createButton(
|
|
{
|
|
label: this.ctx.localeManager.t(tab.label),
|
|
onPress: () => onPressTab(key)
|
|
},
|
|
{
|
|
id: tabButtonIds[key],
|
|
class: "ag-charts-dialog__tab-button",
|
|
role: "tab",
|
|
"aria-controls": tabPanelIds[key]
|
|
}
|
|
)
|
|
);
|
|
const tabList = createElement("div", "ag-charts-dialog__tab-list");
|
|
setAttributes(tabList, { role: "tablist", "aria-label": this.ctx.localeManager.t(tablistLabel) });
|
|
tabList.append(...Object.values(tabButtons));
|
|
const closeButton = this.createHeaderCloseButton();
|
|
header.getElement().append(dragHandle.getElement(), tabList, closeButton);
|
|
element2.append(header.getElement(), ...Object.values(tabs).map((t) => t.panel));
|
|
onPressTab(initial);
|
|
initRovingTabIndex({ orientation: "horizontal", buttons: Object.values(tabButtons) });
|
|
return { tabs: element2, initialFocus: tabButtons[initial] };
|
|
}
|
|
createTabPanel() {
|
|
return createElement("div", "ag-charts-dialog__tab-panel");
|
|
}
|
|
/**********
|
|
* Inputs *
|
|
**********/
|
|
createInputGroupLine() {
|
|
return createElement("div", "ag-charts-dialog__input-group-line");
|
|
}
|
|
createRadioGroup({ label, options, value, onChange }) {
|
|
const group = this.createInputGroup(label);
|
|
setAttributes(group, {
|
|
role: "radiogroup",
|
|
tabindex: -1,
|
|
"aria-label": this.ctx.localeManager.t(label)
|
|
});
|
|
const activeClass = "ag-charts-dialog__button--active";
|
|
const buttons = [];
|
|
for (const button of options) {
|
|
const { icon, altText: altTextKey } = button;
|
|
const altText = this.ctx.localeManager.t(altTextKey);
|
|
const buttonEl = createButton(
|
|
{
|
|
icon,
|
|
altText,
|
|
onPress: () => {
|
|
for (const b of Array.from(group.children)) {
|
|
b.classList.remove(activeClass);
|
|
b.ariaChecked = "false";
|
|
}
|
|
buttonEl.classList.add(activeClass);
|
|
buttonEl.ariaChecked = "true";
|
|
onChange(button.value);
|
|
}
|
|
},
|
|
{
|
|
"aria-checked": button.value === value,
|
|
class: "ag-charts-dialog__button",
|
|
role: "radio",
|
|
title: altText
|
|
}
|
|
);
|
|
if (button.value === value) {
|
|
buttonEl.classList.add(activeClass);
|
|
}
|
|
group.appendChild(buttonEl);
|
|
buttons.push(buttonEl);
|
|
}
|
|
initRovingTabIndex({ orientation: "horizontal", buttons });
|
|
return group;
|
|
}
|
|
createSelect({ altText, label, options, value, onChange }) {
|
|
const group = this.createInputGroup(label);
|
|
const altTextT = this.ctx.localeManager.t(altText);
|
|
const select = createSelect(
|
|
{ value, options, onChange },
|
|
{ class: "ag-charts-dialog__select", "aria-label": altTextT, title: altTextT }
|
|
);
|
|
group.append(select);
|
|
return group;
|
|
}
|
|
createTextArea({ placeholder, value, onChange }) {
|
|
const placeholderT = placeholder ? this.ctx.localeManager.t(placeholder) : void 0;
|
|
return createTextArea({ value, onChange }, { placeholder: placeholderT });
|
|
}
|
|
createCheckbox({ label, checked, onChange }) {
|
|
const id = createElementId();
|
|
const group = this.createInputGroup(label, { for: id });
|
|
const checkbox = createCheckbox(
|
|
{ checked, onChange },
|
|
{ class: "ag-charts-dialog__checkbox", role: "switch", id }
|
|
);
|
|
group.append(checkbox);
|
|
return group;
|
|
}
|
|
createColorPicker({
|
|
color: color2,
|
|
opacity,
|
|
label,
|
|
altText,
|
|
onChange,
|
|
onChangeHide,
|
|
isMultiColor,
|
|
hasMultiColorOption
|
|
}) {
|
|
const group = this.createInputGroup(label);
|
|
const altTextT = this.ctx.localeManager.t(altText);
|
|
const colorEl = createButton(
|
|
{
|
|
label: altTextT,
|
|
onPress: (event) => {
|
|
const { anchor, fallbackAnchor } = this.getColorPickerAnchors(colorEl) ?? {};
|
|
this.colorPicker.show({
|
|
anchor,
|
|
fallbackAnchor,
|
|
color: color2,
|
|
opacity,
|
|
isMultiColor,
|
|
hasMultiColorOption,
|
|
sourceEvent: event,
|
|
onChange: (newColorOpacity, newColor, newOpacity, newIsMultiColor) => {
|
|
colorEl.style.setProperty("--color", newColorOpacity);
|
|
colorEl.classList.toggle(
|
|
"ag-charts-dialog__color-picker-button--multi-color",
|
|
newIsMultiColor
|
|
);
|
|
onChange(newColorOpacity, newColor, newOpacity, newIsMultiColor);
|
|
},
|
|
onChangeHide
|
|
});
|
|
}
|
|
},
|
|
{
|
|
"aria-label": altTextT,
|
|
tabindex: 0,
|
|
class: "ag-charts-dialog__color-picker-button",
|
|
title: altTextT
|
|
}
|
|
);
|
|
if (isMultiColor) {
|
|
colorEl.classList.toggle("ag-charts-dialog__color-picker-button--multi-color");
|
|
} else if (color2) {
|
|
const hex = Color.fromString(color2);
|
|
const hexWithOpacity = new Color(hex.r, hex.g, hex.b, opacity);
|
|
colorEl.style.setProperty("--color", hexWithOpacity.toHexString());
|
|
}
|
|
group.append(colorEl);
|
|
this.hideFns.push(() => {
|
|
this.colorPicker.hide();
|
|
});
|
|
return group;
|
|
}
|
|
/***********
|
|
* Private *
|
|
***********/
|
|
createHeaderCloseButton() {
|
|
return createButton(
|
|
{ icon: "close", altText: this.ctx.localeManager.t("iconAltTextClose"), onPress: () => this.hide() },
|
|
{ class: "ag-charts-dialog__close-button" }
|
|
);
|
|
}
|
|
createInputGroup(label, options) {
|
|
const group = createElement("div", "ag-charts-dialog__input-group");
|
|
const labelEl = createElement("label", "ag-charts-dialog__input-group-label");
|
|
labelEl.innerText = this.ctx.localeManager.t(label);
|
|
setAttribute(labelEl, "for", options?.for);
|
|
group.appendChild(labelEl);
|
|
return group;
|
|
}
|
|
onLayoutComplete(event) {
|
|
this.seriesRect = event.series.paddedRect;
|
|
this.reposition();
|
|
}
|
|
onKeyDown(event) {
|
|
if (event.altKey || event.ctrlKey || event.metaKey || event.isComposing || event.key !== "Escape")
|
|
return;
|
|
this.hide();
|
|
}
|
|
reposition() {
|
|
const { seriesRect, ctx } = this;
|
|
const popover = this.getPopoverElement();
|
|
if (!seriesRect || !popover)
|
|
return;
|
|
const clientRect = ctx.domManager.getBoundingClientRect();
|
|
const outerOffset = vector_exports.from(0, seriesRect.y);
|
|
const outerSize = vector_exports.from(clientRect.width, seriesRect.height);
|
|
const popoverSize = vector_exports.from(popover);
|
|
const halfWidth = vector_exports.from(0.5, 1);
|
|
let position;
|
|
if (seriesRect.width > 1e3) {
|
|
const bottomCenter2 = vector_exports.sub(
|
|
vector_exports.add(outerOffset, vector_exports.multiply(outerSize, halfWidth)),
|
|
vector_exports.multiply(popoverSize, halfWidth)
|
|
);
|
|
position = vector_exports.sub(bottomCenter2, vector_exports.from(0, _Dialog.offset));
|
|
} else {
|
|
const bottomRight = vector_exports.sub(vector_exports.add(outerOffset, outerSize), popoverSize);
|
|
position = vector_exports.sub(bottomRight, _Dialog.offset);
|
|
}
|
|
this.updatePosition(position);
|
|
}
|
|
getColorPickerAnchors(element2) {
|
|
if (element2)
|
|
this.colorPickerAnchorElement = element2;
|
|
if (!this.colorPickerAnchorElement)
|
|
return;
|
|
const rect2 = this.colorPickerAnchorElement.getBoundingClientRect();
|
|
const canvasRect = this.ctx.domManager.getBoundingClientRect();
|
|
const topLeft = vector_exports.sub(vector_exports.from(rect2.x, rect2.y), vector_exports.from(canvasRect.left, canvasRect.top));
|
|
const anchor = vector_exports.add(topLeft, vector_exports.from(0, rect2.height + 5));
|
|
const fallbackAnchor = vector_exports.sub(topLeft, vector_exports.from(0, 5));
|
|
return { anchor, fallbackAnchor };
|
|
}
|
|
};
|
|
_Dialog.offset = 60;
|
|
var Dialog = _Dialog;
|
|
var DragHandleWidget2 = class extends NativeWidget2 {
|
|
constructor() {
|
|
super(createElement("div", "ag-charts-dialog__drag-handle"));
|
|
const icon = new NativeWidget2(createElement("span", getIconClassNames("drag-handle")));
|
|
icon.setAriaHidden(true);
|
|
this.addChild(icon);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/settings-dialog/settingsDialog.ts
|
|
var AnnotationSettingsDialog = class extends Dialog {
|
|
constructor(ctx) {
|
|
super(ctx, "settings");
|
|
this.events = new EventEmitter();
|
|
this.hideFns.push(() => this.events.emit("hidden", null));
|
|
}
|
|
show(datum, options) {
|
|
const lineTab = this.createLinearLineTab(datum, options);
|
|
const textTab = this.createLinearTextTab(datum, options);
|
|
let lineLabel = "dialogHeaderLine";
|
|
if (isChannelType(datum)) {
|
|
lineLabel = "dialogHeaderChannel";
|
|
} else if (isFibonacciType(datum)) {
|
|
lineLabel = "dialogHeaderFibonacciRange";
|
|
} else if (datum.type === "date-range" /* DateRange */) {
|
|
lineLabel = "dialogHeaderDateRange";
|
|
} else if (datum.type === "price-range" /* PriceRange */) {
|
|
lineLabel = "dialogHeaderPriceRange";
|
|
} else if (datum.type === "date-price-range" /* DatePriceRange */) {
|
|
lineLabel = "dialogHeaderDatePriceRange";
|
|
}
|
|
const { tabs, initialFocus } = this.createTabs("ariaLabelSettingsTabBar", options.initialSelectedTab, {
|
|
line: {
|
|
label: lineLabel,
|
|
panel: lineTab
|
|
},
|
|
text: {
|
|
label: "dialogHeaderText",
|
|
panel: textTab.panel,
|
|
onShow: textTab.onShow
|
|
}
|
|
});
|
|
options.initialFocus = initialFocus;
|
|
const popover = this.showWithChildren([tabs], options);
|
|
popover.classList.add("ag-charts-dialog--annotation-settings");
|
|
}
|
|
createLinearLineTab(datum, options) {
|
|
const panel = this.createTabPanel();
|
|
const groupOne = this.createInputGroupLine();
|
|
const groupTwo = this.createInputGroupLine();
|
|
const hasMultiColorOption = "isMultiColor" in datum;
|
|
const lineColorPicker = this.createColorPickerInput(
|
|
"line-color",
|
|
datum.getDefaultColor("line-color"),
|
|
datum.getDefaultOpacity("line-color"),
|
|
hasMultiColorOption ? datum.isMultiColor : false,
|
|
hasMultiColorOption,
|
|
options.onChangeLineColor,
|
|
options.onChangeHideLineColor
|
|
);
|
|
const strokeWidth = this.createStrokeWidthSelect(datum.strokeWidth ?? 2, options.onChangeLineStyleWidth);
|
|
const lineStyle = this.createLineStyleRadioGroup(datum.lineStyle ?? "solid", options.onChangeLineStyleType);
|
|
groupOne.append(lineColorPicker);
|
|
if ("background" in datum) {
|
|
const fillColorPicker = this.createColorPickerInput(
|
|
"fill-color",
|
|
datum.getDefaultColor("fill-color"),
|
|
datum.getDefaultOpacity("fill-color"),
|
|
false,
|
|
false,
|
|
options.onChangeFillColor,
|
|
options.onChangeHideFillColor
|
|
);
|
|
groupOne.append(fillColorPicker);
|
|
groupTwo.append(strokeWidth);
|
|
} else if ("showFill" in datum) {
|
|
groupOne.append(
|
|
this.createCheckbox({
|
|
label: "dialogInputShowFill",
|
|
checked: datum.showFill ?? true,
|
|
onChange: (showFill) => options.onChangeLine({ showFill })
|
|
})
|
|
);
|
|
groupTwo.append(strokeWidth);
|
|
} else {
|
|
groupOne.append(strokeWidth);
|
|
}
|
|
groupTwo.append(lineStyle);
|
|
panel.append(groupOne, groupTwo);
|
|
if ("bands" in datum) {
|
|
panel.append(
|
|
this.createFibonacciRatioSelect(datum.bands ?? 10, (bands) => options.onChangeLine({ bands }))
|
|
);
|
|
}
|
|
if ("extendStart" in datum && "extendEnd" in datum) {
|
|
panel.append(
|
|
this.createCheckbox({
|
|
label: isChannelType(datum) ? "dialogInputExtendChannelStart" : "dialogInputExtendLineStart",
|
|
checked: datum.extendStart ?? false,
|
|
onChange: (extendStart) => options.onChangeLine({ extendStart })
|
|
}),
|
|
this.createCheckbox({
|
|
label: isChannelType(datum) ? "dialogInputExtendChannelEnd" : "dialogInputExtendLineEnd",
|
|
checked: datum.extendEnd ?? false,
|
|
onChange: (extendEnd) => options.onChangeLine({ extendEnd })
|
|
})
|
|
);
|
|
}
|
|
if ("extendAbove" in datum && "extendBelow" in datum) {
|
|
panel.append(
|
|
this.createCheckbox({
|
|
label: "dialogInputExtendAbove",
|
|
checked: datum.extendAbove ?? false,
|
|
onChange: (extendAbove) => options.onChangeLine({ extendAbove })
|
|
}),
|
|
this.createCheckbox({
|
|
label: "dialogInputExtendBelow",
|
|
checked: datum.extendBelow ?? false,
|
|
onChange: (extendBelow) => options.onChangeLine({ extendBelow })
|
|
})
|
|
);
|
|
}
|
|
if ("extendLeft" in datum && "extendRight" in datum) {
|
|
panel.append(
|
|
this.createCheckbox({
|
|
label: "dialogInputExtendLeft",
|
|
checked: datum.extendLeft ?? false,
|
|
onChange: (extendLeft) => options.onChangeLine({ extendLeft })
|
|
}),
|
|
this.createCheckbox({
|
|
label: "dialogInputExtendRight",
|
|
checked: datum.extendRight ?? false,
|
|
onChange: (extendRight) => options.onChangeLine({ extendRight })
|
|
})
|
|
);
|
|
}
|
|
if ("reverse" in datum && "showFill" in datum) {
|
|
panel.append(
|
|
this.createCheckbox({
|
|
label: "dialogInputReverse",
|
|
checked: datum.reverse ?? false,
|
|
onChange: (reverse) => options.onChangeLine({ reverse })
|
|
})
|
|
);
|
|
}
|
|
return panel;
|
|
}
|
|
createLinearTextTab(datum, options) {
|
|
const panel = this.createTabPanel();
|
|
const textArea = this.createTextArea({
|
|
placeholder: "inputTextareaPlaceholder",
|
|
value: datum.text.label,
|
|
onChange: (value) => options.onChangeText({ label: value })
|
|
});
|
|
const fontSize = this.createFontSizeSelect(datum.text.fontSize, options.onChangeTextFontSize);
|
|
const colorPicker = this.createColorPickerInput(
|
|
"text-color",
|
|
datum.text.color,
|
|
1,
|
|
false,
|
|
false,
|
|
options.onChangeTextColor,
|
|
options.onChangeHideTextColor
|
|
);
|
|
const textPosition = datum.text.position === "inside" ? "center" : datum.text.position;
|
|
const position = this.createPositionRadioGroup(
|
|
textPosition ?? "top",
|
|
(value) => options.onChangeText({ position: value })
|
|
);
|
|
const alignment = this.createAlignmentRadioGroup(
|
|
datum.text.alignment ?? "center",
|
|
(value) => options.onChangeText({ alignment: value })
|
|
);
|
|
const inputGroupLine = this.createInputGroupLine();
|
|
inputGroupLine.append(fontSize, colorPicker, position, alignment);
|
|
panel.append(textArea, inputGroupLine);
|
|
return { panel, onShow: () => focusCursorAtEnd(textArea) };
|
|
}
|
|
createColorPickerInput(colorType, color2, opacity, isMultiColor, hasMultiColorOption, onChange, onChangeHide) {
|
|
const label = colorType === "fill-color" ? "dialogInputFillColorPicker" : "dialogInputColorPicker";
|
|
const altText = colorType === "fill-color" ? "dialogInputFillColorPickerAltText" : "dialogInputColorPickerAltText";
|
|
return this.createColorPicker({
|
|
label,
|
|
altText,
|
|
color: color2,
|
|
opacity,
|
|
isMultiColor,
|
|
hasMultiColorOption,
|
|
onChange,
|
|
onChangeHide
|
|
});
|
|
}
|
|
createStrokeWidthSelect(strokeWidth, onChange) {
|
|
return this.createSelect({
|
|
label: "dialogInputStrokeWidth",
|
|
altText: "dialogInputStrokeWidthAltText",
|
|
options: LINE_STROKE_WIDTH_ITEMS.map(({ label, value }) => ({ label, value: `${value}` })),
|
|
value: String(strokeWidth),
|
|
onChange: (value) => onChange(Number(value))
|
|
});
|
|
}
|
|
createFibonacciRatioSelect(bands, onChange) {
|
|
return this.createSelect({
|
|
label: "dialogInputFibonacciBands",
|
|
altText: "dialogInputFibonacciBandsAltText",
|
|
options: FIBONACCI_RATIO_ITEMS.map(({ label, value }) => ({ label, value: `${value}` })),
|
|
value: String(bands),
|
|
onChange: (value) => onChange(Number(value))
|
|
});
|
|
}
|
|
createLineStyleRadioGroup(lineStyle, onChange) {
|
|
return this.createRadioGroup({
|
|
label: "dialogInputLineStyle",
|
|
options: [
|
|
{ icon: "line-style-solid", altText: "iconAltTextLineStyleSolid", value: "solid" },
|
|
{ icon: "line-style-dashed", altText: "iconAltTextLineStyleDashed", value: "dashed" },
|
|
{ icon: "line-style-dotted", altText: "iconAltTextLineStyleDotted", value: "dotted" }
|
|
],
|
|
value: lineStyle,
|
|
onChange
|
|
});
|
|
}
|
|
createFontSizeSelect(fontSize, onChange) {
|
|
return this.createSelect({
|
|
label: "dialogInputFontSize",
|
|
altText: "dialogInputFontSizeAltText",
|
|
options: TEXT_SIZE_ITEMS.map(({ label, value }) => ({ label, value: String(value) })),
|
|
value: String(fontSize),
|
|
onChange: (value) => onChange(Number(value))
|
|
});
|
|
}
|
|
createPositionRadioGroup(position, onChange) {
|
|
return this.createRadioGroup({
|
|
label: "dialogInputPosition",
|
|
options: [
|
|
{ icon: "position-top", altText: "iconAltTextPositionTop", value: "top" },
|
|
{ icon: "position-center", altText: "iconAltTextPositionCenter", value: "center" },
|
|
{ icon: "position-bottom", altText: "iconAltTextPositionBottom", value: "bottom" }
|
|
],
|
|
value: position,
|
|
onChange
|
|
});
|
|
}
|
|
createAlignmentRadioGroup(alignment, onChange) {
|
|
return this.createRadioGroup({
|
|
label: "dialogInputAlign",
|
|
options: [
|
|
{ icon: "align-left", altText: "iconAltTextAlignLeft", value: "left" },
|
|
{ icon: "align-center", altText: "iconAltTextAlignCenter", value: "center" },
|
|
{ icon: "align-right", altText: "iconAltTextAlignRight", value: "right" }
|
|
],
|
|
value: alignment,
|
|
onChange
|
|
});
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/utils/axis.ts
|
|
function calculateAxisLabelPadding(axisLayout) {
|
|
return axisLayout.gridPadding + axisLayout.seriesAreaPadding + axisLayout.tickSize + axisLayout.label.spacing;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/utils/update.ts
|
|
function updateAnnotation(node, datum, context) {
|
|
for (const { update } of Object.values(annotationConfigs)) {
|
|
update(node, datum, context);
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/annotations.ts
|
|
var { InteractionState: InteractionState3, keyProperty: keyProperty3, valueProperty: valueProperty4, Selection: Selection6, BBox: BBox10 } = module_support_exports;
|
|
var _Annotations = class _Annotations extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.toolbar = new AnnotationsToolbar(this.ctx);
|
|
this.optionsToolbar = new AnnotationOptionsToolbar(this.ctx, () => {
|
|
const active = this.state.getActive();
|
|
if (active == null)
|
|
return;
|
|
return getTypedDatum(this.annotationData.at(active));
|
|
});
|
|
this.axesButtons = new AxesButtons();
|
|
this.enabled = true;
|
|
this.snap = false;
|
|
// Hidden options for use with measurer statistics
|
|
this.data = void 0;
|
|
this.xKey = void 0;
|
|
this.volumeKey = void 0;
|
|
this.annotationData = new PropertiesArray(
|
|
_Annotations.createAnnotationDatum
|
|
);
|
|
this.defaults = new AnnotationDefaults();
|
|
this.container = new module_support_exports.Group({ name: "static-annotations" });
|
|
this.annotations = new Selection6(
|
|
this.container,
|
|
this.createAnnotationScene.bind(this)
|
|
);
|
|
this.settingsDialog = new AnnotationSettingsDialog(this.ctx);
|
|
this.textInput = new TextInput(this.ctx);
|
|
this.postUpdateFns = [];
|
|
this.state = this.setupStateMachine();
|
|
this.setupListeners();
|
|
this.setupDOM();
|
|
this.ctx.historyManager.addMementoOriginator(ctx.annotationManager);
|
|
this.ctx.historyManager.addMementoOriginator(this.defaults);
|
|
this.textInput.setKeyDownHandler(this.onTextInput.bind(this));
|
|
this.cleanup.register(() => {
|
|
this.clear();
|
|
this.xAxis?.button?.destroy();
|
|
this.yAxis?.button?.destroy();
|
|
this.textInput.destroy();
|
|
});
|
|
}
|
|
setupStateMachine() {
|
|
const { ctx } = this;
|
|
return new AnnotationsStateMachine({
|
|
resetToIdle: () => {
|
|
ctx.domManager.updateCursor("annotations");
|
|
this.popAnnotationState(InteractionState3.Annotations);
|
|
this.hideOverlays();
|
|
this.optionsToolbar.hide();
|
|
this.deleteEphemeralAnnotations();
|
|
this.update();
|
|
},
|
|
hoverAtCoords: (coords, active, previousHovered) => {
|
|
let hovered;
|
|
this.annotations.each((annotation, datum, index) => {
|
|
if (!datum.isHoverable())
|
|
return;
|
|
const contains = annotation.containsPoint(coords.x, coords.y);
|
|
if (contains)
|
|
hovered ?? (hovered = index);
|
|
annotation.toggleHovered(contains, active === index, datum.readOnly);
|
|
});
|
|
if (hovered != null) {
|
|
ctx.tooltipManager.suppressTooltip("annotations");
|
|
} else if (!this.isAnnotationState()) {
|
|
ctx.tooltipManager.unsuppressTooltip("annotations");
|
|
}
|
|
if (hovered == null || !this.annotationData.at(hovered)?.readOnly) {
|
|
this.ctx.domManager.updateCursor(
|
|
"annotations",
|
|
hovered == null ? void 0 : this.annotations.at(hovered)?.getCursor()
|
|
);
|
|
}
|
|
if (hovered !== previousHovered) {
|
|
this.update();
|
|
}
|
|
return hovered;
|
|
},
|
|
getNodeAtCoords: (coords, active) => {
|
|
const node = this.annotations.at(active);
|
|
if (!node) {
|
|
return;
|
|
}
|
|
return node.getNodeAtCoords(coords.x, coords.y);
|
|
},
|
|
translate: (index, translation) => {
|
|
const node = this.annotations.at(index);
|
|
const datum = getTypedDatum(this.annotationData.at(index));
|
|
if (!node || !datum) {
|
|
return;
|
|
}
|
|
return this.translateNode(node, datum, translation);
|
|
},
|
|
copy: (index) => {
|
|
const node = this.annotations.at(index);
|
|
const datum = getTypedDatum(this.annotationData.at(index));
|
|
if (!node || !datum) {
|
|
return;
|
|
}
|
|
return this.createAnnotationDatumCopy(node, datum);
|
|
},
|
|
paste: (datum) => {
|
|
this.createAnnotation(datum.type, datum, false);
|
|
this.postUpdateFns.push(() => {
|
|
this.state.transitionAsync("selectLast");
|
|
this.state.transitionAsync("copy");
|
|
});
|
|
},
|
|
select: (index, previous) => {
|
|
const { annotations, optionsToolbar: optionsToolbar2, toolbar: toolbar2 } = this;
|
|
this.hideOverlays();
|
|
toolbar2.clearActiveButton();
|
|
toolbar2.resetButtonIcons();
|
|
const selectedNode = index == null ? null : annotations.at(index);
|
|
const previousNode = previous == null ? null : annotations.at(previous);
|
|
const selectedDatum = index == null ? null : this.annotationData.at(index);
|
|
if (previousNode === selectedNode && selectedNode != null) {
|
|
return;
|
|
}
|
|
previousNode?.toggleActive(false);
|
|
optionsToolbar2.hide();
|
|
if (selectedNode && !selectedDatum?.readOnly) {
|
|
this.pushAnnotationState(InteractionState3.AnnotationsSelected);
|
|
selectedNode.toggleActive(true);
|
|
if (!isEphemeralType(selectedDatum)) {
|
|
optionsToolbar2.updateButtons(this.annotationData.at(index));
|
|
this.postUpdateFns.push(() => {
|
|
optionsToolbar2.show();
|
|
optionsToolbar2.setAnchorScene(selectedNode);
|
|
});
|
|
}
|
|
} else {
|
|
this.popAnnotationState(InteractionState3.AnnotationsSelected);
|
|
this.popAnnotationState(InteractionState3.Annotations);
|
|
}
|
|
if (!isEphemeralType(selectedDatum)) {
|
|
this.deleteEphemeralAnnotations();
|
|
}
|
|
this.update();
|
|
},
|
|
selectLast: () => {
|
|
this.pushAnnotationState(InteractionState3.AnnotationsSelected);
|
|
return this.annotationData.length - 1;
|
|
},
|
|
startInteracting: () => {
|
|
this.pushAnnotationState(InteractionState3.Annotations);
|
|
},
|
|
stopInteracting: () => {
|
|
this.popAnnotationState(InteractionState3.Annotations);
|
|
},
|
|
create: (type, datum) => {
|
|
this.createAnnotation(type, datum);
|
|
},
|
|
delete: (index) => {
|
|
this.annotationData.splice(index, 1);
|
|
},
|
|
deleteAll: () => {
|
|
const readOnly = this.annotationData.filter((datum) => {
|
|
if (datum.readOnly === true)
|
|
return datum;
|
|
});
|
|
this.annotationData.splice(0, this.annotationData.length);
|
|
for (const datum of readOnly) {
|
|
this.annotationData.push(datum);
|
|
}
|
|
},
|
|
validatePoint: (point, options) => {
|
|
const context = this.getAnnotationContext();
|
|
return context ? validateDatumPoint(context, point, options) : true;
|
|
},
|
|
getAnnotationType: (index) => {
|
|
return stringToAnnotationType(this.annotationData[index].type);
|
|
},
|
|
datum: (index) => {
|
|
return this.annotationData.at(index);
|
|
},
|
|
node: (index) => {
|
|
return this.annotations.at(index);
|
|
},
|
|
recordAction: (label) => {
|
|
this.recordActionAfterNextUpdate(label);
|
|
},
|
|
update: () => {
|
|
this.postUpdateFns.push(() => {
|
|
const active = this.state.getActive();
|
|
const node = active == null ? null : this.annotations.at(active);
|
|
if (node == null)
|
|
return;
|
|
this.optionsToolbar.setAnchorScene(node);
|
|
});
|
|
this.update();
|
|
},
|
|
showTextInput: (active) => {
|
|
const datum = getTypedDatum(this.annotationData.at(active));
|
|
const node = this.annotations.at(active);
|
|
if (!node || !datum || !("getTextInputCoords" in datum) || !("getTextPosition" in datum))
|
|
return;
|
|
const styles = {
|
|
color: datum.color,
|
|
fontFamily: datum.fontFamily,
|
|
fontSize: datum.fontSize,
|
|
fontStyle: datum.fontStyle,
|
|
fontWeight: datum.fontWeight,
|
|
placeholderColor: datum.getPlaceholderColor()
|
|
};
|
|
const context = this.getAnnotationContext();
|
|
const getTextInputCoords = (height2) => vector_exports.add(datum.getTextInputCoords(context, height2), vector_exports.required(this.seriesRect));
|
|
const getTextPosition = () => datum.getTextPosition();
|
|
this.textInput.show({
|
|
styles,
|
|
layout: {
|
|
getTextInputCoords,
|
|
getTextPosition,
|
|
alignment: datum.alignment,
|
|
textAlign: datum.textAlign,
|
|
width: datum.width
|
|
},
|
|
text: datum.text,
|
|
placeholderText: datum.placeholderText,
|
|
onChange: (_text, bbox) => {
|
|
this.state.transition("updateTextInputBBox", bbox);
|
|
}
|
|
});
|
|
this.ctx.domManager.updateCursor("annotations");
|
|
},
|
|
hideTextInput: () => {
|
|
this.textInput.hide();
|
|
},
|
|
updateTextInputColor: (color2) => {
|
|
this.textInput.updateColor(color2);
|
|
},
|
|
updateTextInputFontSize: (fontSize) => {
|
|
const bbox = this.textInput.updateFontSize(fontSize);
|
|
this.state.transition("updateTextInputBBox", bbox);
|
|
},
|
|
updateTextInputBBox: (bbox) => {
|
|
this.state.transition("updateTextInputBBox", bbox);
|
|
},
|
|
showAnnotationOptions: (active) => {
|
|
const node = this.annotations.at(active);
|
|
if (!node || isEphemeralType(this.annotationData.at(active)))
|
|
return;
|
|
this.optionsToolbar.updateButtons(this.annotationData.at(active));
|
|
this.optionsToolbar.show();
|
|
this.optionsToolbar.setAnchorScene(node);
|
|
},
|
|
showAnnotationSettings: (active, sourceEvent, initialTab = "line") => {
|
|
const datum = this.annotationData.at(active);
|
|
if (!isLineType(datum) && !isChannelType(datum) && !isMeasurerType(datum))
|
|
return;
|
|
if (isEphemeralType(datum))
|
|
return;
|
|
const onChangeColor = (colorType) => (colorOpacity, color2, opacity, isMultiColor) => {
|
|
this.setColorAndDefault(datum.type, colorType, colorOpacity, color2, opacity, isMultiColor);
|
|
this.optionsToolbar.updateColorPickerColor(colorType, color2, opacity, isMultiColor);
|
|
};
|
|
const onChangeHideColor = (colorType) => () => {
|
|
this.recordActionAfterNextUpdate(
|
|
`Change ${datum.type} ${colorType} to ${datum.getDefaultColor(colorType)}`,
|
|
["annotations", "defaults"]
|
|
);
|
|
this.update();
|
|
};
|
|
const options = {
|
|
initialSelectedTab: initialTab,
|
|
ariaLabel: this.ctx.localeManager.t("ariaLabelAnnotationSettingsDialog"),
|
|
sourceEvent,
|
|
onChangeLine: (props) => {
|
|
this.state.transition("lineProps", props);
|
|
if (props.bands != null)
|
|
this.defaults.setDefaultFibonacciOptions(datum.type, "bands", props.bands);
|
|
if (props.reverse != null)
|
|
this.defaults.setDefaultFibonacciOptions(datum.type, "reverse", props.reverse);
|
|
if (props.showFill != null)
|
|
this.defaults.setDefaultFibonacciOptions(datum.type, "showFill", props.showFill);
|
|
},
|
|
onChangeText: (props) => {
|
|
this.state.transition("lineText", props);
|
|
if (props.alignment)
|
|
this.defaults.setDefaultLineTextAlignment(datum.type, props.alignment);
|
|
if (props.position)
|
|
this.defaults.setDefaultLineTextPosition(datum.type, props.position);
|
|
this.recordActionAfterNextUpdate(
|
|
`Change ${datum.type} text ${Object.keys(props).map((key) => `${key} to ${props[key]}`).join(", ")}`
|
|
);
|
|
},
|
|
onChangeFillColor: onChangeColor("fill-color"),
|
|
onChangeHideFillColor: onChangeHideColor("fill-color"),
|
|
onChangeLineColor: onChangeColor("line-color"),
|
|
onChangeHideLineColor: onChangeHideColor("line-color"),
|
|
onChangeLineStyleType: (lineStyleType) => {
|
|
this.setLineStyleTypeAndDefault(datum.type, lineStyleType);
|
|
this.optionsToolbar.updateLineStyleType(
|
|
LINE_STYLE_TYPE_ITEMS.find((item) => item.value === lineStyleType) ?? LINE_STYLE_TYPE_ITEMS[0]
|
|
);
|
|
},
|
|
onChangeLineStyleWidth: (strokeWidth) => {
|
|
this.setLineStyleWidthAndDefault(datum.type, strokeWidth);
|
|
this.optionsToolbar.updateStrokeWidth({
|
|
strokeWidth,
|
|
value: strokeWidth,
|
|
label: String(strokeWidth)
|
|
});
|
|
},
|
|
onChangeTextColor: onChangeColor("text-color"),
|
|
onChangeHideTextColor: onChangeHideColor("text-color"),
|
|
onChangeTextFontSize: (fontSize) => {
|
|
this.setFontSizeAndDefault(datum.type, fontSize);
|
|
}
|
|
};
|
|
this.settingsDialog.show(datum, options);
|
|
}
|
|
});
|
|
}
|
|
setupListeners() {
|
|
const { ctx, optionsToolbar: optionsToolbar2, settingsDialog, toolbar: toolbar2 } = this;
|
|
const { seriesWidget, seriesDragInterpreter, chartWidget } = ctx.widgets;
|
|
if (seriesDragInterpreter) {
|
|
this.cleanup.register(
|
|
// Interactions
|
|
seriesDragInterpreter.events.on("click", this.hoverTouchPreHandler.bind(this)),
|
|
seriesDragInterpreter.events.on("drag-start", this.hoverTouchPreHandler.bind(this)),
|
|
seriesDragInterpreter.events.on("drag-move", this.dragMoveTouchPreHandler.bind(this)),
|
|
seriesDragInterpreter.events.on("mousemove", this.onHover.bind(this)),
|
|
seriesDragInterpreter.events.on("click", this.onClick.bind(this)),
|
|
seriesDragInterpreter.events.on("dblclick", this.onDoubleClick.bind(this)),
|
|
seriesDragInterpreter.events.on("drag-start", this.onDragStart.bind(this)),
|
|
seriesDragInterpreter.events.on("drag-move", this.onDrag.bind(this)),
|
|
seriesDragInterpreter.events.on("drag-end", this.onDragEnd.bind(this))
|
|
);
|
|
}
|
|
this.cleanup.register(
|
|
// Interactions
|
|
seriesWidget.addListener("keydown", this.onKeyDown.bind(this)),
|
|
seriesWidget.addListener("keyup", this.onKeyUp.bind(this)),
|
|
chartWidget.addListener("click", this.onCancel.bind(this)),
|
|
// Services
|
|
ctx.eventsHub.on("annotations:restore", this.onRestoreAnnotations.bind(this)),
|
|
ctx.eventsHub.on("layout:complete", this.onLayoutComplete.bind(this)),
|
|
ctx.updateService.addListener("pre-scene-render", this.onPreRender.bind(this)),
|
|
ctx.eventsHub.on("zoom:change-complete", () => this.onResize()),
|
|
ctx.eventsHub.on("dom:resize", () => this.onResize()),
|
|
// Toolbar
|
|
toolbar2.events.on("cancel-create-annotation", () => {
|
|
this.cancel();
|
|
this.reset();
|
|
this.update();
|
|
}),
|
|
toolbar2.events.on("pressed-create-annotation", ({ annotation }) => {
|
|
this.cancel();
|
|
this.pushAnnotationState(InteractionState3.Annotations);
|
|
this.state.transition(annotation);
|
|
this.update();
|
|
}),
|
|
toolbar2.events.on("pressed-clear", () => {
|
|
this.clear();
|
|
this.recordActionAfterNextUpdate("Clear all");
|
|
}),
|
|
toolbar2.events.on("pressed-show-menu", () => {
|
|
this.cancel();
|
|
this.reset();
|
|
}),
|
|
toolbar2.events.on("pressed-unrelated", () => {
|
|
this.reset();
|
|
}),
|
|
// Annotation Options Toolbar
|
|
optionsToolbar2.events.on("pressed-delete", () => {
|
|
this.cancel();
|
|
this.delete();
|
|
this.reset();
|
|
}),
|
|
optionsToolbar2.events.on("pressed-settings", ({ sourceEvent }) => {
|
|
this.state.transition("toolbarPressSettings", sourceEvent);
|
|
}),
|
|
optionsToolbar2.events.on("pressed-lock", ({ locked }) => {
|
|
this.recordActionAfterNextUpdate(locked ? "Locked" : "Unlocked");
|
|
this.update();
|
|
}),
|
|
optionsToolbar2.events.on("hid-overlays", () => {
|
|
this.settingsDialog.hide();
|
|
}),
|
|
optionsToolbar2.events.on("saved-color", ({ type, colorPickerType, color: color2 }) => {
|
|
this.recordActionAfterNextUpdate(`Change ${type} ${colorPickerType} to ${color2}`, [
|
|
"annotations",
|
|
"defaults"
|
|
]);
|
|
}),
|
|
optionsToolbar2.events.on(
|
|
"updated-color",
|
|
({ type, colorPickerType, colorOpacity, color: color2, opacity, isMultiColor }) => {
|
|
this.setColorAndDefault(type, colorPickerType, colorOpacity, color2, opacity, isMultiColor);
|
|
}
|
|
),
|
|
optionsToolbar2.events.on("updated-font-size", ({ type, fontSize }) => {
|
|
this.setFontSizeAndDefault(type, fontSize);
|
|
}),
|
|
optionsToolbar2.events.on("updated-line-style", ({ type, lineStyleType }) => {
|
|
this.setLineStyleTypeAndDefault(type, lineStyleType);
|
|
}),
|
|
optionsToolbar2.events.on("updated-line-width", ({ type, strokeWidth }) => {
|
|
this.setLineStyleWidthAndDefault(type, strokeWidth);
|
|
}),
|
|
// Settings Dialog
|
|
settingsDialog.events.on("hidden", () => {
|
|
this.optionsToolbar.clearActiveButton();
|
|
})
|
|
);
|
|
}
|
|
setupDOM() {
|
|
const { ctx, toolbar: toolbar2, optionsToolbar: optionsToolbar2 } = this;
|
|
this.cleanup.register(ctx.annotationManager.attachNode(this.container), () => {
|
|
ctx.domManager.removeStyles(DEFAULT_ANNOTATION_AXIS_BUTTON_CLASS);
|
|
toolbar2.destroy();
|
|
optionsToolbar2.destroy();
|
|
});
|
|
}
|
|
async processData(dataController) {
|
|
if (!this.enabled || this.data == null || this.xKey == null || this.volumeKey == null)
|
|
return;
|
|
const props = [
|
|
keyProperty3(this.xKey, void 0, { id: "date" }),
|
|
valueProperty4(this.volumeKey, "number", { id: "volume" })
|
|
];
|
|
const dataSet = module_support_exports.DataSet.wrap(this.data) ?? module_support_exports.DataSet.empty();
|
|
const { dataModel, processedData } = await dataController.request("annotations", dataSet, {
|
|
props
|
|
});
|
|
this.dataModel = dataModel;
|
|
this.processedData = processedData;
|
|
}
|
|
/**
|
|
* Create an annotation scene within the `this.annotations` scene selection. This method is automatically called by
|
|
* the selection when a new scene is required.
|
|
*/
|
|
createAnnotationScene(datum) {
|
|
if (datum.type in annotationConfigs) {
|
|
return new annotationConfigs[datum.type].scene();
|
|
}
|
|
throw new Error(
|
|
`AG Charts - Cannot create annotation scene of type [${datum.type}], expected one of [${Object.keys(annotationConfigs)}], ignoring.`
|
|
);
|
|
}
|
|
/**
|
|
* Create an annotation datum within the `this.annotationData` properties array. It is created as an instance
|
|
* of `AnnotationProperties` from the given config for its type. This method is only called when annotations
|
|
* are added from the initial state.
|
|
*/
|
|
static createAnnotationDatum(params) {
|
|
if (params.type in annotationConfigs) {
|
|
return new annotationConfigs[params.type].datum().set(params);
|
|
}
|
|
throw new Error(
|
|
`AG Charts - Cannot create annotation datum of unknown type [${params.type}], expected one of [${Object.keys(annotationConfigs)}], ignoring.`
|
|
);
|
|
}
|
|
/**
|
|
* Append an annotation datum to `this.annotationData`, applying default styles. This method is called when a user
|
|
* interacts with the chart to draw their own annotations.
|
|
*/
|
|
createAnnotation(type, datum, applyDefaults = true) {
|
|
this.annotationData.push(datum);
|
|
if (applyDefaults) {
|
|
const styles = this.ctx.annotationManager.getAnnotationTypeStyles(type);
|
|
if (styles)
|
|
datum.set(styles);
|
|
this.defaults.applyDefaults(datum);
|
|
}
|
|
this.injectDatumDependencies(datum);
|
|
this.update();
|
|
}
|
|
injectDatumDependencies(datum) {
|
|
if ("setLocaleManager" in datum) {
|
|
datum.setLocaleManager(this.ctx.localeManager);
|
|
}
|
|
if ("getVolume" in datum) {
|
|
datum.getVolume = this.getDatumRangeVolume.bind(this);
|
|
}
|
|
}
|
|
getDatumRangeVolume(fromPoint, toPoint) {
|
|
const { dataModel, processedData } = this;
|
|
let from3 = getGroupingValue(fromPoint);
|
|
let to = getGroupingValue(toPoint);
|
|
if (!isValidDate(from3) || !isValidDate(to) || !dataModel || !processedData || this.volumeKey == null)
|
|
return;
|
|
if (from3 > to) {
|
|
[from3, to] = [to, from3];
|
|
}
|
|
const dateValues = dataModel.resolveKeysById({ id: "annotations" }, "date", processedData);
|
|
const volumeValues = dataModel.resolveColumnById({ id: "annotations" }, "volume", processedData);
|
|
let sum = 0;
|
|
for (let datumIndex = 0; datumIndex < processedData.input.count; datumIndex++) {
|
|
const key = dateValues[datumIndex];
|
|
if (isValidDate(key) && key >= from3 && key <= to) {
|
|
sum += volumeValues[datumIndex];
|
|
}
|
|
}
|
|
return sum;
|
|
}
|
|
translateNode(node, datum, translation) {
|
|
const config = this.getAnnotationConfig(datum);
|
|
const context = this.getAnnotationContext();
|
|
if (!context) {
|
|
return;
|
|
}
|
|
config.translate(node, datum, translation, context);
|
|
}
|
|
createAnnotationDatumCopy(node, datum) {
|
|
const config = this.getAnnotationConfig(datum);
|
|
const newDatum = new config.datum();
|
|
newDatum.set(datum.toJson());
|
|
const context = this.getAnnotationContext();
|
|
if (!context) {
|
|
return;
|
|
}
|
|
return config.copy(node, datum, newDatum, context);
|
|
}
|
|
getAnnotationConfig(datum) {
|
|
if (datum.type in annotationConfigs) {
|
|
return annotationConfigs[datum.type];
|
|
}
|
|
throw new Error(
|
|
`AG Charts - Cannot get annotation config of unknown type [${datum.type}], expected one of [${Object.keys(annotationConfigs)}], ignoring.`
|
|
);
|
|
}
|
|
onRestoreAnnotations(event) {
|
|
if (!this.enabled)
|
|
return;
|
|
this.clear();
|
|
this.annotationData.set(event.annotations);
|
|
this.postUpdateFns.push(() => {
|
|
this.ctx.annotationManager.fireChangedEvent();
|
|
});
|
|
this.update();
|
|
}
|
|
onLayoutComplete(event) {
|
|
if (!this.enabled)
|
|
return;
|
|
const seriesRect = event.series.paddedRect;
|
|
this.seriesRect = seriesRect;
|
|
this.container.setClipRect(seriesRect);
|
|
this.xAxis = this.getAxis(event.axes["x" /* X */], seriesRect, this.xAxis?.button);
|
|
this.yAxis = this.getAxis(event.axes["y" /* Y */], seriesRect, this.yAxis?.button);
|
|
if (this.showAnnotations()) {
|
|
this.animateAnnotations({ from: 0, to: 1, phase: "trailing" });
|
|
} else {
|
|
this.animateAnnotations({ from: 1, to: 0, phase: "remove" });
|
|
}
|
|
}
|
|
showAnnotations() {
|
|
if (!this.yAxis || !this.xAxis) {
|
|
return false;
|
|
}
|
|
const hasData = this.ctx.chartService.series.some((s) => s.hasData);
|
|
const seriesIds = this.yAxis.context.seriesIds();
|
|
const anyBoundSeriesVisible = seriesIds.some((id) => {
|
|
const series = this.ctx.chartService.series.find((s) => s.id === id);
|
|
return series?.visible;
|
|
});
|
|
return hasData && anyBoundSeriesVisible;
|
|
}
|
|
animateAnnotations({ from: from3, to, phase }) {
|
|
const { annotations } = this;
|
|
this.ctx.animationManager?.animate({
|
|
from: from3,
|
|
to,
|
|
id: "chart-annotations",
|
|
phase,
|
|
groupId: "opacity",
|
|
onUpdate(value) {
|
|
annotations.each((node) => {
|
|
node.opacity = value;
|
|
if ("setAxisLabelOpacity" in node) {
|
|
node.setAxisLabelOpacity(value);
|
|
}
|
|
});
|
|
},
|
|
onStop() {
|
|
annotations.each((node) => {
|
|
node.opacity = to;
|
|
if ("setAxisLabelOpacity" in node) {
|
|
node.setAxisLabelOpacity(to);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
onPreRender() {
|
|
if (!this.enabled)
|
|
return;
|
|
this.updateAnnotations();
|
|
this.state.transition("render");
|
|
}
|
|
getAxis(axisLayout, seriesRect, button) {
|
|
const axisCtx = this.ctx.axisManager.getAxisContext(axisLayout.direction)[0];
|
|
const { position: axisPosition = "bottom", direction } = axisCtx;
|
|
const padding2 = axisLayout.gridPadding + axisLayout.seriesAreaPadding;
|
|
const bounds = new BBox10(0, 0, seriesRect.width, seriesRect.height).grow(padding2, axisPosition);
|
|
const lineDirection = direction === "x" /* X */ ? "vertical" : "horizontal";
|
|
const { axesButtons, snap } = this;
|
|
const buttonEnabled = this.enabled && axesButtons.enabled && (axesButtons.axes === "xy" || axesButtons.axes === direction);
|
|
if (buttonEnabled) {
|
|
button ?? (button = new AxisButton(
|
|
this.ctx,
|
|
{ ...axisCtx, snapToGroup: snap },
|
|
(coords) => this.onAxisButtonClick(coords, lineDirection),
|
|
seriesRect
|
|
));
|
|
const axisLabelPadding = calculateAxisLabelPadding(axisLayout);
|
|
button.update(seriesRect, axisLabelPadding);
|
|
} else {
|
|
button?.destroy();
|
|
button = void 0;
|
|
}
|
|
return { layout: axisLayout, context: axisCtx, bounds, button };
|
|
}
|
|
recordActionAfterNextUpdate(label, types = ["annotations"]) {
|
|
const {
|
|
defaults,
|
|
ctx: { annotationManager, historyManager }
|
|
} = this;
|
|
const originators = types.map((type) => type === "defaults" ? defaults : annotationManager);
|
|
this.postUpdateFns.push(() => {
|
|
historyManager.record(label, ...originators);
|
|
annotationManager.fireChangedEvent();
|
|
});
|
|
}
|
|
setColorAndDefault(datumType, colorPickerType, colorOpacity, color2, opacity, isMultiColor) {
|
|
this.state.transition("color", { colorPickerType, colorOpacity, color: color2, opacity, isMultiColor });
|
|
this.defaults.setDefaultColor(datumType, colorPickerType, colorOpacity, color2, opacity, isMultiColor);
|
|
}
|
|
setFontSizeAndDefault(datumType, fontSize) {
|
|
this.state.transition("fontSize", fontSize);
|
|
this.defaults.setDefaultFontSize(datumType, fontSize);
|
|
this.recordActionAfterNextUpdate(`Change ${datumType} font size to ${fontSize}`, ["annotations", "defaults"]);
|
|
}
|
|
setLineStyleTypeAndDefault(datumType, styleType) {
|
|
this.state.transition("lineStyle", { type: styleType });
|
|
this.defaults.setDefaultLineStyleType(datumType, styleType);
|
|
this.recordActionAfterNextUpdate(`Change ${datumType} line style to ${styleType}`, ["annotations", "defaults"]);
|
|
}
|
|
setLineStyleWidthAndDefault(datumType, strokeWidth) {
|
|
this.state.transition("lineStyle", { strokeWidth });
|
|
this.defaults.setDefaultLineStyleWidth(datumType, strokeWidth);
|
|
this.recordActionAfterNextUpdate(`Change ${datumType} stroke width to ${strokeWidth}`, [
|
|
"annotations",
|
|
"defaults"
|
|
]);
|
|
}
|
|
updateAnnotations() {
|
|
const {
|
|
annotationData,
|
|
annotations,
|
|
seriesRect,
|
|
ctx: { annotationManager }
|
|
} = this;
|
|
const context = this.getAnnotationContext();
|
|
if (!seriesRect || !context)
|
|
return;
|
|
annotationManager.updateData(annotationData.toJson());
|
|
const showAnnotations = this.showAnnotations();
|
|
this.toolbar.refreshButtonsEnabled(showAnnotations);
|
|
this.toolbar.toggleClearButtonEnabled(annotationData.length > 0 && showAnnotations);
|
|
annotations.update(annotationData ?? [], void 0, (datum) => datum.id).each((node, datum) => {
|
|
if (!showAnnotations) {
|
|
node.visible = false;
|
|
if ("setAxisLabelVisible" in node) {
|
|
node.setAxisLabelVisible(false);
|
|
}
|
|
return;
|
|
}
|
|
if ("setAxisLabelVisible" in node) {
|
|
node.setAxisLabelVisible(true);
|
|
}
|
|
this.injectDatumDependencies(datum);
|
|
updateAnnotation(node, datum, context);
|
|
});
|
|
for (const fn of this.postUpdateFns) {
|
|
fn();
|
|
}
|
|
this.postUpdateFns = [];
|
|
}
|
|
getAnnotationContext() {
|
|
const { seriesRect, xAxis, yAxis, snap } = this;
|
|
if (!(seriesRect && xAxis && yAxis)) {
|
|
return;
|
|
}
|
|
return {
|
|
seriesRect,
|
|
xAxis: {
|
|
...xAxis.context,
|
|
bounds: xAxis.bounds,
|
|
labelPadding: calculateAxisLabelPadding(xAxis.layout),
|
|
snapToGroup: snap
|
|
},
|
|
yAxis: {
|
|
...yAxis.context,
|
|
bounds: yAxis.bounds,
|
|
labelPadding: calculateAxisLabelPadding(xAxis.layout),
|
|
snapToGroup: snap
|
|
}
|
|
};
|
|
}
|
|
onHover(event) {
|
|
const { state } = this;
|
|
const context = this.getAnnotationContext();
|
|
if (!context)
|
|
return;
|
|
const shiftKey = event.sourceEvent.shiftKey;
|
|
const offset = vector_exports.from(event);
|
|
const point = invertCoords(offset, context);
|
|
state.transition("hover", { offset, point, shiftKey, context });
|
|
}
|
|
onClick(event) {
|
|
const { state } = this;
|
|
const context = this.getAnnotationContext();
|
|
if (!context)
|
|
return;
|
|
const shiftKey = event.sourceEvent.shiftKey;
|
|
const point = invertCoords(vector_exports.from(event), context);
|
|
const textInputValue = this.textInput.getValue();
|
|
const bbox = this.textInput.getBBox();
|
|
state.transition("click", { point, shiftKey, textInputValue, bbox });
|
|
}
|
|
onDoubleClick(event) {
|
|
const { state } = this;
|
|
const context = this.getAnnotationContext();
|
|
if (!context)
|
|
return;
|
|
const offset = vector_exports.from(event);
|
|
state.transition("dblclick", { offset });
|
|
}
|
|
onAxisButtonClick(coords, direction) {
|
|
this.cancel();
|
|
this.reset();
|
|
const context = this.getAnnotationContext();
|
|
if (!this.annotationData || !context)
|
|
return;
|
|
const { state } = this;
|
|
this.pushAnnotationState(InteractionState3.Annotations);
|
|
const isHorizontal2 = direction === "horizontal";
|
|
state.transition(isHorizontal2 ? "horizontal-line" /* HorizontalLine */ : "vertical-line" /* VerticalLine */);
|
|
this.optionsToolbar.hide();
|
|
if (!coords) {
|
|
return;
|
|
}
|
|
const point = invertCoords(coords, context);
|
|
if (!validateDatumPoint(context, point)) {
|
|
return;
|
|
}
|
|
state.transition("click", { point, shiftKey: false });
|
|
this.update();
|
|
}
|
|
onResize() {
|
|
const textInputValue = this.textInput.getValue();
|
|
const bbox = this.textInput.getBBox();
|
|
this.state.transition("resize", { textInputValue, bbox });
|
|
}
|
|
hoverTouchPreHandler(event) {
|
|
if (event.device === "touch") {
|
|
this.onHover(event);
|
|
}
|
|
}
|
|
dragMoveTouchPreHandler(event) {
|
|
if (event.device === "touch" && this.ctx.interactionManager.isState(InteractionState3.AnnotationsSelected)) {
|
|
event.sourceEvent.preventDefault();
|
|
}
|
|
}
|
|
onDragStart(event) {
|
|
if (!this.ctx.interactionManager.isState(InteractionState3.AnnotationsDraggable))
|
|
return;
|
|
const context = this.getAnnotationContext();
|
|
if (!context)
|
|
return;
|
|
const offset = vector_exports.from(event);
|
|
const point = invertCoords(offset, context);
|
|
const textInputValue = this.textInput.getValue();
|
|
const bbox = this.textInput.getBBox();
|
|
this.state.transition("dragStart", { context, offset, point, textInputValue, bbox });
|
|
}
|
|
onDrag(event) {
|
|
if (!this.ctx.interactionManager.isState(InteractionState3.AnnotationsDraggable))
|
|
return;
|
|
const context = this.getAnnotationContext();
|
|
if (!context)
|
|
return;
|
|
const offset = vector_exports.from(event);
|
|
const point = invertCoords(offset, context);
|
|
const shiftKey = event.sourceEvent.shiftKey;
|
|
const textInputValue = this.textInput.getValue();
|
|
const bbox = this.textInput.getBBox();
|
|
this.state.transition("drag", { context, offset, point, shiftKey, textInputValue, bbox });
|
|
}
|
|
onDragEnd() {
|
|
this.state.transition("dragEnd");
|
|
}
|
|
onCancel(widgetEvent) {
|
|
const { sourceEvent } = widgetEvent ?? {};
|
|
if (sourceEvent?.currentTarget !== sourceEvent?.target)
|
|
return;
|
|
this.cancel();
|
|
this.reset();
|
|
}
|
|
onDelete() {
|
|
if (this.textInput.isVisible())
|
|
return;
|
|
this.cancel();
|
|
this.delete();
|
|
this.reset();
|
|
this.update();
|
|
}
|
|
onTextInput(event) {
|
|
const { state } = this;
|
|
const context = this.getAnnotationContext();
|
|
if (!context)
|
|
return;
|
|
const { key, shiftKey } = event;
|
|
const textInputValue = this.textInput.getValue();
|
|
const bbox = this.textInput.getBBox();
|
|
state.transition("textInput", { key, shiftKey, textInputValue, bbox, context });
|
|
}
|
|
onKeyDown(event) {
|
|
const { state } = this;
|
|
const context = this.getAnnotationContext();
|
|
if (!context) {
|
|
return;
|
|
}
|
|
const { sourceEvent } = event;
|
|
const { shiftKey, ctrlKey, metaKey } = sourceEvent;
|
|
const ctrlMeta = ctrlKey || metaKey;
|
|
const ctrlShift = ctrlKey || shiftKey;
|
|
state.transition("keyDown", { shiftKey, context });
|
|
const translation = { x: 0, y: 0 };
|
|
const xStep = Math.max(context?.xAxis.scale.bandwidth ?? 0, ctrlShift ? 10 : 1);
|
|
const yStep = Math.max(context?.yAxis.scale.bandwidth ?? 0, ctrlShift ? 10 : 1);
|
|
switch (sourceEvent.key) {
|
|
case "ArrowDown":
|
|
translation.y = yStep;
|
|
break;
|
|
case "ArrowUp":
|
|
translation.y = -yStep;
|
|
break;
|
|
case "ArrowLeft":
|
|
translation.x = -xStep;
|
|
break;
|
|
case "ArrowRight":
|
|
translation.x = xStep;
|
|
break;
|
|
case "Escape":
|
|
this.onCancel();
|
|
return;
|
|
case "Backspace":
|
|
case "Delete":
|
|
this.onDelete();
|
|
return;
|
|
}
|
|
if (translation.x || translation.y) {
|
|
state.transition("translate", { translation });
|
|
sourceEvent.stopPropagation();
|
|
sourceEvent.preventDefault();
|
|
}
|
|
if (!ctrlMeta) {
|
|
return;
|
|
}
|
|
switch (sourceEvent.key) {
|
|
case "c":
|
|
state.transition("copy");
|
|
return;
|
|
case "x":
|
|
state.transition("cut");
|
|
this.recordActionAfterNextUpdate("Cut annotation");
|
|
return;
|
|
case "v":
|
|
state.transition("paste");
|
|
this.recordActionAfterNextUpdate("Paste annotation");
|
|
return;
|
|
}
|
|
}
|
|
onKeyUp(event) {
|
|
const { shiftKey } = event.sourceEvent;
|
|
const context = this.getAnnotationContext();
|
|
if (!context) {
|
|
return;
|
|
}
|
|
this.state.transition("keyUp", { shiftKey, context });
|
|
this.state.transition("translateEnd");
|
|
}
|
|
clear() {
|
|
this.cancel();
|
|
this.deleteAll();
|
|
this.reset();
|
|
}
|
|
reset() {
|
|
this.state.transition("reset");
|
|
}
|
|
cancel() {
|
|
this.state.transition("cancel");
|
|
}
|
|
delete() {
|
|
this.state.transition("delete");
|
|
}
|
|
deleteAll() {
|
|
this.state.transition("deleteAll");
|
|
}
|
|
deleteEphemeralAnnotations() {
|
|
let deletedEphemeral = false;
|
|
for (const [index, datum] of this.annotationData.entries()) {
|
|
if (isEphemeralType(datum)) {
|
|
this.annotationData.splice(index, 1);
|
|
deletedEphemeral = true;
|
|
}
|
|
}
|
|
if (deletedEphemeral) {
|
|
this.recordActionAfterNextUpdate("Delete ephemeral annotations");
|
|
}
|
|
}
|
|
hideOverlays() {
|
|
this.settingsDialog.hide();
|
|
this.toolbar.hideOverlays();
|
|
this.optionsToolbar.hideOverlays();
|
|
}
|
|
pushAnnotationState(state) {
|
|
this.ctx.interactionManager.pushState(state);
|
|
this.ctx.tooltipManager.suppressTooltip("annotations");
|
|
}
|
|
popAnnotationState(state) {
|
|
this.ctx.interactionManager.popState(state);
|
|
this.ctx.tooltipManager.unsuppressTooltip("annotations");
|
|
}
|
|
isAnnotationState() {
|
|
return this.ctx.interactionManager.isState(InteractionState3.Annotations) || this.ctx.interactionManager.isState(InteractionState3.AnnotationsSelected);
|
|
}
|
|
update(status = 8 /* PRE_SCENE_RENDER */) {
|
|
this.ctx.updateService.update(status);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Annotations.prototype, "toolbar", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Annotations.prototype, "optionsToolbar", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Annotations.prototype, "axesButtons", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ObserveChanges((target, value) => {
|
|
const enabled = value ?? true;
|
|
target.toolbar.enabled = enabled;
|
|
target.optionsToolbar.enabled = enabled;
|
|
target.axesButtons.enabled = enabled;
|
|
})
|
|
], _Annotations.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], _Annotations.prototype, "snap", 2);
|
|
var Annotations = _Annotations;
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/annotationsTheme.ts
|
|
var stroke = {
|
|
stroke: { $ref: "foregroundColor" },
|
|
strokeOpacity: 1,
|
|
strokeWidth: 2
|
|
};
|
|
var handle = {
|
|
fill: DEFAULT_ANNOTATION_HANDLE_FILL,
|
|
strokeOpacity: 1,
|
|
strokeWidth: 2
|
|
};
|
|
var font = {
|
|
color: { $ref: "chartBackgroundColor" },
|
|
fontSize: { $rem: FONT_SIZE_RATIO.LARGE },
|
|
fontFamily: { $ref: "fontFamily" }
|
|
};
|
|
var axisLabel = {
|
|
...font,
|
|
enabled: true,
|
|
fill: { $ref: "foregroundColor" },
|
|
fontSize: { $ref: "fontSize" }
|
|
};
|
|
var text = {
|
|
...font,
|
|
textAlign: "left"
|
|
};
|
|
var lineText = {
|
|
...font,
|
|
position: "top",
|
|
alignment: "center",
|
|
color: { $ref: "textColor" }
|
|
};
|
|
var channelText = {
|
|
...font,
|
|
position: "top",
|
|
alignment: "center",
|
|
color: { $ref: "textColor" }
|
|
};
|
|
var measurerStatistics = {
|
|
...font,
|
|
fontSize: { $ref: "fontSize" },
|
|
color: DEFAULT_ANNOTATION_STATISTICS_COLOR,
|
|
fill: DEFAULT_ANNOTATION_STATISTICS_FILL,
|
|
stroke: DEFAULT_ANNOTATION_STATISTICS_STROKE,
|
|
strokeWidth: 1,
|
|
divider: {
|
|
stroke: DEFAULT_ANNOTATION_STATISTICS_DIVIDER_STROKE,
|
|
strokeWidth: 1,
|
|
strokeOpacity: 0.5
|
|
}
|
|
};
|
|
var measurer = {
|
|
...stroke,
|
|
background: {
|
|
fill: { $ref: "foregroundColor" },
|
|
fillOpacity: 0.075
|
|
},
|
|
handle: { ...handle },
|
|
text: { ...lineText },
|
|
statistics: { ...measurerStatistics }
|
|
};
|
|
var toolbar = {
|
|
buttons: {
|
|
$shallowSimple: [
|
|
{
|
|
icon: "text-annotation",
|
|
tooltip: "toolbarAnnotationsTextAnnotations",
|
|
value: "text-menu"
|
|
},
|
|
{
|
|
icon: "trend-line-drawing",
|
|
tooltip: "toolbarAnnotationsLineAnnotations",
|
|
value: "line-menu"
|
|
},
|
|
{
|
|
icon: "arrow-drawing",
|
|
tooltip: "toolbarAnnotationsShapeAnnotations",
|
|
value: "shape-menu"
|
|
},
|
|
{
|
|
icon: "delete",
|
|
tooltip: "toolbarAnnotationsClearAll",
|
|
value: "clear"
|
|
}
|
|
]
|
|
},
|
|
padding: { $ref: "chartPadding" }
|
|
};
|
|
var optionsToolbar = {
|
|
buttons: {
|
|
$shallowSimple: [
|
|
{
|
|
icon: "text-annotation",
|
|
tooltip: "toolbarAnnotationsTextColor",
|
|
value: "text-color"
|
|
},
|
|
{
|
|
icon: "line-color",
|
|
tooltip: "toolbarAnnotationsLineColor",
|
|
value: "line-color"
|
|
},
|
|
{
|
|
icon: "fill-color",
|
|
tooltip: "toolbarAnnotationsFillColor",
|
|
value: "fill-color"
|
|
},
|
|
{
|
|
tooltip: "toolbarAnnotationsTextSize",
|
|
value: "text-size"
|
|
},
|
|
{
|
|
tooltip: "toolbarAnnotationsLineStrokeWidth",
|
|
value: "line-stroke-width"
|
|
},
|
|
{
|
|
icon: "line-style-solid",
|
|
tooltip: "toolbarAnnotationsLineStyle",
|
|
value: "line-style-type"
|
|
},
|
|
{
|
|
icon: "settings",
|
|
tooltip: "toolbarAnnotationsSettings",
|
|
value: "settings"
|
|
},
|
|
{
|
|
icon: "unlocked",
|
|
tooltip: "toolbarAnnotationsLock",
|
|
ariaLabel: "toolbarAnnotationsLock",
|
|
checkedOverrides: {
|
|
icon: "locked",
|
|
tooltip: "toolbarAnnotationsUnlock"
|
|
},
|
|
value: "lock"
|
|
},
|
|
{
|
|
icon: "delete",
|
|
tooltip: "toolbarAnnotationsDelete",
|
|
value: "delete"
|
|
}
|
|
]
|
|
}
|
|
};
|
|
var annotationsTheme = {
|
|
enabled: false,
|
|
// Lines
|
|
line: {
|
|
...stroke,
|
|
handle: { ...handle },
|
|
text: { ...lineText }
|
|
},
|
|
"horizontal-line": {
|
|
...stroke,
|
|
handle: { ...handle },
|
|
axisLabel: { ...axisLabel },
|
|
text: { ...lineText }
|
|
},
|
|
"vertical-line": {
|
|
...stroke,
|
|
handle: { ...handle },
|
|
axisLabel: { ...axisLabel },
|
|
text: { ...lineText }
|
|
},
|
|
// Channels
|
|
"disjoint-channel": {
|
|
...stroke,
|
|
background: {
|
|
fill: { $ref: "foregroundColor" },
|
|
fillOpacity: 0.075
|
|
},
|
|
handle: { ...handle },
|
|
text: { ...channelText }
|
|
},
|
|
"parallel-channel": {
|
|
...stroke,
|
|
middle: {
|
|
lineDash: [6, 5],
|
|
strokeWidth: 1
|
|
},
|
|
background: {
|
|
fill: { $ref: "foregroundColor" },
|
|
fillOpacity: 0.075
|
|
},
|
|
handle: { ...handle },
|
|
text: { ...channelText }
|
|
},
|
|
// Fibonnaccis
|
|
"fibonacci-retracement": {
|
|
...stroke,
|
|
strokes: DEFAULT_FIBONACCI_STROKES,
|
|
rangeStroke: { $ref: "foregroundColor" },
|
|
handle: { ...handle },
|
|
text: { ...lineText, position: "center" },
|
|
label: {
|
|
...font,
|
|
color: void 0,
|
|
fontSize: { $rem: FONT_SIZE_RATIO.SMALLER }
|
|
}
|
|
},
|
|
"fibonacci-retracement-trend-based": {
|
|
...stroke,
|
|
strokes: DEFAULT_FIBONACCI_STROKES,
|
|
rangeStroke: { $ref: "foregroundColor" },
|
|
handle: { ...handle },
|
|
text: { ...lineText, position: "center" },
|
|
label: {
|
|
...font,
|
|
color: void 0,
|
|
fontSize: { $rem: FONT_SIZE_RATIO.SMALLER }
|
|
}
|
|
},
|
|
// Texts
|
|
callout: {
|
|
...stroke,
|
|
...text,
|
|
color: { $ref: "textColor" },
|
|
handle: { ...handle },
|
|
fill: { $ref: "foregroundColor" },
|
|
fillOpacity: 0.075
|
|
},
|
|
comment: {
|
|
...text,
|
|
fontWeight: 700,
|
|
handle: { ...handle },
|
|
fill: { $ref: "foregroundColor" }
|
|
},
|
|
note: {
|
|
...text,
|
|
color: DEFAULT_TEXTBOX_COLOR,
|
|
fill: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR,
|
|
stroke: { $ref: "chartBackgroundColor" },
|
|
strokeWidth: 1,
|
|
strokeOpacity: 1,
|
|
handle: { ...handle },
|
|
background: {
|
|
fill: DEFAULT_TEXTBOX_FILL,
|
|
stroke: DEFAULT_TEXTBOX_STROKE,
|
|
strokeWidth: 1
|
|
}
|
|
},
|
|
text: {
|
|
...text,
|
|
color: { $ref: "textColor" },
|
|
handle: { ...handle }
|
|
},
|
|
// Shapes
|
|
arrow: {
|
|
...stroke,
|
|
handle: { ...handle },
|
|
text: { ...lineText }
|
|
},
|
|
"arrow-up": {
|
|
fill: { $palette: "up.fill" },
|
|
handle: { ...handle, stroke: { $ref: "foregroundColor" } }
|
|
},
|
|
"arrow-down": {
|
|
fill: { $palette: "down.fill" },
|
|
handle: { ...handle, stroke: { $ref: "foregroundColor" } }
|
|
},
|
|
// Measurers
|
|
"date-range": {
|
|
...measurer
|
|
},
|
|
"price-range": {
|
|
...measurer
|
|
},
|
|
"date-price-range": {
|
|
...measurer
|
|
},
|
|
"quick-date-price-range": {
|
|
up: {
|
|
...stroke,
|
|
fill: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL,
|
|
fillOpacity: 0.2,
|
|
handle: { ...handle },
|
|
statistics: {
|
|
...measurerStatistics,
|
|
color: "#fff",
|
|
fill: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL,
|
|
strokeWidth: 0,
|
|
divider: {
|
|
stroke: "#fff",
|
|
strokeWidth: 1,
|
|
strokeOpacity: 0.5
|
|
}
|
|
}
|
|
},
|
|
down: {
|
|
...stroke,
|
|
stroke: DEFAULT_ANNOTATION_STATISTICS_DOWN_STROKE,
|
|
fill: DEFAULT_ANNOTATION_STATISTICS_DOWN_FILL,
|
|
fillOpacity: 0.2,
|
|
handle: {
|
|
...handle,
|
|
stroke: DEFAULT_ANNOTATION_STATISTICS_DOWN_STROKE
|
|
},
|
|
statistics: {
|
|
...measurerStatistics,
|
|
color: "#fff",
|
|
fill: DEFAULT_ANNOTATION_STATISTICS_DOWN_FILL,
|
|
strokeWidth: 0,
|
|
divider: {
|
|
stroke: "#fff",
|
|
strokeWidth: 1,
|
|
strokeOpacity: 0.5
|
|
}
|
|
}
|
|
}
|
|
},
|
|
axesButtons: {},
|
|
// Toolbars
|
|
toolbar,
|
|
optionsToolbar
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/annotations/annotationsModule.ts
|
|
var AnnotationsModule = {
|
|
type: "plugin",
|
|
name: "annotations",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: module_support_exports.annotationOptionsDef,
|
|
themeTemplate: annotationsTheme,
|
|
create: (ctx) => new Annotations(ctx),
|
|
patchContext: (ctx) => {
|
|
if (ctx.sharedToolbar)
|
|
return;
|
|
ctx.sharedToolbar = new SharedToolbar(ctx);
|
|
ctx.cleanup.register(() => ctx.sharedToolbar.destroy());
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/band-highlight/bandHighlight.ts
|
|
var {
|
|
Range: Range2,
|
|
TranslatableGroup: TranslatableGroup2,
|
|
BBox: BBox11,
|
|
FillGradientDefaults: FillGradientDefaults4,
|
|
FillImageDefaults: FillImageDefaults4,
|
|
FillPatternDefaults: FillPatternDefaults4,
|
|
getShapeFill: getShapeFill2,
|
|
InteractionState: InteractionState4
|
|
} = module_support_exports;
|
|
var BandHighlight = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.id = createId(this);
|
|
this.enabled = false;
|
|
this.stroke = "rgb(195, 195, 195)";
|
|
this.lineDash = [6, 3];
|
|
this.lineDashOffset = 0;
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.fill = "#c16068";
|
|
this.fillOpacity = 1;
|
|
this.fillGradientDefaults = new FillGradientDefaults4();
|
|
this.fillPatternDefaults = new FillPatternDefaults4();
|
|
this.fillImageDefaults = new FillImageDefaults4();
|
|
this.bounds = new BBox11(0, 0, 0, 0);
|
|
this.bandHighlightGroup = new TranslatableGroup2({
|
|
name: "bandHighlight",
|
|
zIndex: 1 /* AXIS_BAND_HIGHLIGHT */
|
|
});
|
|
this.rangeNode = this.bandHighlightGroup.appendChild(new Range2());
|
|
this.activeAxisHighlight = void 0;
|
|
this.axisCtx = ctx.parent;
|
|
this.hideBand();
|
|
ctx.domManager.addEventListener("focusin", ({ target }) => {
|
|
const isSeriesAreaChild = target instanceof HTMLElement && ctx.domManager.contains(target, "series-area");
|
|
if (this.bandHighlightGroup.visible && !isSeriesAreaChild) {
|
|
this.hideBand();
|
|
this.ctx.updateService.update(9 /* SCENE_RENDER */);
|
|
}
|
|
});
|
|
const {
|
|
widgets: { seriesWidget, seriesDragInterpreter },
|
|
animationManager,
|
|
eventsHub
|
|
} = ctx;
|
|
this.cleanup.register(
|
|
ctx.scene.attachNode(this.bandHighlightGroup),
|
|
seriesWidget.addListener("mousemove", (event) => this.onHoverLikeEvent(event)),
|
|
seriesWidget.addListener("mouseleave", () => this.clearAllHighlight()),
|
|
animationManager.addListener("animation-start", () => this.clearAllHighlight()),
|
|
eventsHub.on("layout:complete", (event) => this.layout(event)),
|
|
eventsHub.on("series:focus-change", () => this.onKeyPress()),
|
|
eventsHub.on("zoom:pan-start", () => this.clearAllHighlight()),
|
|
eventsHub.on("zoom:change-complete", () => this.clearAllHighlight()),
|
|
eventsHub.on("dom:resize", () => this.clearAllHighlight()),
|
|
eventsHub.on("axis:change", () => this.axisChange())
|
|
);
|
|
if (seriesDragInterpreter) {
|
|
this.cleanup.register(
|
|
seriesDragInterpreter.events.on("drag-move", (event) => this.onHoverLikeEvent(event)),
|
|
seriesDragInterpreter.events.on("click", (event) => this.onClick(event))
|
|
);
|
|
}
|
|
}
|
|
axisChange() {
|
|
this.onHighlightChange();
|
|
}
|
|
isHover(event) {
|
|
return event.type === "mousemove" || event.type === "click" || event.device === "touch" && this.ctx.chartService.touch.dragAction === "hover";
|
|
}
|
|
onClick(event) {
|
|
if (event.device === "touch") {
|
|
this.onHoverLikeEvent(event);
|
|
}
|
|
}
|
|
clearAllHighlight() {
|
|
if (!this.ctx.interactionManager.isState(InteractionState4.Clickable))
|
|
return;
|
|
this.onHighlightChange();
|
|
}
|
|
onKeyPress() {
|
|
if (this.ctx.interactionManager.isState(InteractionState4.Default)) {
|
|
this.onHighlightChange();
|
|
}
|
|
}
|
|
onHoverLikeEvent(event) {
|
|
const requiredState = this.isHover(event) ? InteractionState4.Clickable : InteractionState4.AnnotationsMoveable;
|
|
if (!this.ctx.interactionManager.isState(requiredState))
|
|
return;
|
|
this.handleHoverHighlight(event);
|
|
}
|
|
handleHoverHighlight(event) {
|
|
if (!event)
|
|
return;
|
|
const { currentX: x, currentY: y } = event;
|
|
this.onHighlightChange(this.axisCtx.pickBand({ x, y }));
|
|
}
|
|
layout({ series: { rect: rect2, visible }, axes }) {
|
|
if (!visible || !axes || !this.enabled)
|
|
return;
|
|
const { position: axisPosition = "left", axisId } = this.axisCtx;
|
|
const axisLayout = axes[axisId];
|
|
if (!axisLayout)
|
|
return;
|
|
this.axisLayout = axisLayout;
|
|
this.bounds = rect2.clone().grow(axisLayout.gridPadding, axisPosition);
|
|
const { bandHighlightGroup, bounds } = this;
|
|
bandHighlightGroup.translationX = Math.round(bounds.x);
|
|
bandHighlightGroup.translationY = Math.round(bounds.y);
|
|
this.updateBand();
|
|
}
|
|
updateBand() {
|
|
const {
|
|
rangeNode: node,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
fill,
|
|
fillOpacity,
|
|
fillGradientDefaults: fillGradientDefaults3,
|
|
fillPatternDefaults: fillPatternDefaults3,
|
|
fillImageDefaults: fillImageDefaults3,
|
|
lineDashOffset,
|
|
axisLayout
|
|
} = this;
|
|
if (!axisLayout)
|
|
return;
|
|
node.stroke = stroke3;
|
|
node.strokeWidth = strokeWidth;
|
|
node.strokeOpacity = strokeOpacity;
|
|
node.lineDash = lineDash;
|
|
node.lineDashOffset = lineDashOffset;
|
|
node.fill = getShapeFill2(fill, fillGradientDefaults3, fillPatternDefaults3, fillImageDefaults3);
|
|
node.fillOpacity = fillOpacity;
|
|
node.startLine = true;
|
|
node.endLine = true;
|
|
}
|
|
isVertical() {
|
|
return this.axisCtx.direction === "x" /* X */;
|
|
}
|
|
onHighlightChange(axisBandDatum) {
|
|
if (!this.enabled)
|
|
return;
|
|
this.activeAxisHighlight = axisBandDatum;
|
|
if (this.activeAxisHighlight) {
|
|
this.showBand();
|
|
} else {
|
|
this.hideBand();
|
|
}
|
|
this.ctx.updateService.update(9 /* SCENE_RENDER */);
|
|
}
|
|
updateBandPosition() {
|
|
const { rangeNode, bounds } = this;
|
|
const { band } = this.activeAxisHighlight ?? {};
|
|
if (band == void 0) {
|
|
this.hideBand();
|
|
return;
|
|
}
|
|
let r0 = Math.min(...band);
|
|
let r1 = Math.max(...band);
|
|
if (r1 - r0 < 1) {
|
|
const mid = (r0 + r1) / 2;
|
|
r0 = mid - 0.5;
|
|
r1 = mid + 0.5;
|
|
}
|
|
if (this.isVertical()) {
|
|
rangeNode.y1 = 0;
|
|
rangeNode.y2 = bounds.height;
|
|
rangeNode.x1 = r0;
|
|
rangeNode.x2 = r1;
|
|
rangeNode.horizontal = true;
|
|
} else {
|
|
rangeNode.y1 = r0;
|
|
rangeNode.y2 = r1;
|
|
rangeNode.x1 = 0;
|
|
rangeNode.x2 = bounds.width;
|
|
rangeNode.horizontal = false;
|
|
}
|
|
}
|
|
showBand() {
|
|
this.updateBandPosition();
|
|
this.bandHighlightGroup.visible = true;
|
|
}
|
|
hideBand() {
|
|
this.bandHighlightGroup.visible = false;
|
|
}
|
|
};
|
|
BandHighlight.className = "BandHighlight";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BandHighlight.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BandHighlight.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BandHighlight.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BandHighlight.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BandHighlight.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BandHighlight.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BandHighlight.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BandHighlight.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BandHighlight.prototype, "fillGradientDefaults", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BandHighlight.prototype, "fillPatternDefaults", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BandHighlight.prototype, "fillImageDefaults", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/band-highlight/bandHighlightModule.ts
|
|
var BandHighlightModule = {
|
|
type: "axis:plugin",
|
|
name: "bandHighlight",
|
|
chartType: "cartesian",
|
|
axisTypes: ["category", "ordinal-time", "unit-time", "grouped-category"],
|
|
enterprise: true,
|
|
version: VERSION,
|
|
themeTemplate: {
|
|
enabled: false,
|
|
strokeWidth: 0,
|
|
lineDash: [],
|
|
fill: { $foregroundBackgroundMix: 0.05 }
|
|
},
|
|
create: (ctx) => new BandHighlight(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/chart-toolbar/chartToolbar.ts
|
|
var { LayoutElement: LayoutElement3, Menu: Menu4 } = module_support_exports;
|
|
var menuItems = [
|
|
{ label: "toolbarSeriesTypeOHLC", icon: "ohlc-series", value: "ohlc" },
|
|
{ label: "toolbarSeriesTypeCandles", icon: "candlestick-series", value: "candlestick" },
|
|
{ label: "toolbarSeriesTypeHollowCandles", icon: "hollow-candlestick-series", value: "hollow-candlestick" },
|
|
{ label: "toolbarSeriesTypeLine", icon: "line-series", value: "line" },
|
|
{ label: "toolbarSeriesTypeStepLine", icon: "step-line-series", value: "step-line" },
|
|
{ label: "toolbarSeriesTypeHLC", icon: "hlc-series", value: "hlc" },
|
|
{ label: "toolbarSeriesTypeHighLow", icon: "high-low-series", value: "high-low" }
|
|
];
|
|
var ChartToolbar = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.enabled = false;
|
|
this.menu = new Menu4(this.ctx, "chart-toolbar");
|
|
this.toolbar = ctx.sharedToolbar.getSharedToolbar("chartToolbar");
|
|
this.cleanup.register(
|
|
this.toolbar.addToolbarListener("button-pressed", this.onButtonPressed.bind(this)),
|
|
ctx.layoutManager.registerElement(LayoutElement3.ToolbarLeft, this.onLayoutStart.bind(this)),
|
|
() => this.toolbar.destroy()
|
|
);
|
|
}
|
|
onLayoutStart(ctx) {
|
|
if (!this.enabled)
|
|
return;
|
|
this.updateButton();
|
|
this.toolbar.layout(ctx.layoutBox);
|
|
}
|
|
onButtonPressed({ event, buttonBounds, buttonWidget }) {
|
|
this.menu.setAnchor({ x: buttonBounds.x + buttonBounds.width + 6, y: buttonBounds.y });
|
|
this.menu.show(buttonWidget, {
|
|
items: menuItems,
|
|
menuItemRole: "menuitemradio",
|
|
ariaLabel: this.ctx.localeManager.t("toolbarSeriesTypeDropdown"),
|
|
class: "ag-charts-chart-toolbar__menu",
|
|
value: this.getChartType(),
|
|
sourceEvent: event.sourceEvent,
|
|
onPress: (item) => {
|
|
this.setChartType(item.value);
|
|
this.hidePopover();
|
|
},
|
|
onHide: () => {
|
|
this.toolbar.clearActiveButton();
|
|
}
|
|
});
|
|
this.toolbar.toggleActiveButtonByIndex(0);
|
|
}
|
|
updateButton() {
|
|
const chartType = this.getChartType();
|
|
const icon = menuItems.find((item) => item.value === chartType)?.icon;
|
|
if (icon != null) {
|
|
this.toolbar.updateButtons([{ icon, tooltip: "toolbarSeriesTypeDropdown", value: "menu" }]);
|
|
}
|
|
}
|
|
hidePopover() {
|
|
this.toolbar.clearActiveButton();
|
|
this.menu.hide();
|
|
}
|
|
setChartType(chartType) {
|
|
const options = { chartType };
|
|
this.ctx.chartService.publicApi?.updateDelta(options).catch((e) => logger_exports.error(e));
|
|
}
|
|
getChartType() {
|
|
const chartType = this.ctx.chartService.publicApi?.getOptions()?.chartType;
|
|
if (chartType == null || !menuItems.some((item) => item.value === chartType)) {
|
|
return "candlestick";
|
|
}
|
|
return chartType;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ActionOnSet({
|
|
changeValue(enabled) {
|
|
this.toolbar?.setHidden(!enabled);
|
|
}
|
|
})
|
|
], ChartToolbar.prototype, "enabled", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/chart-toolbar/chartToolbarModule.ts
|
|
var ChartToolbarModule = {
|
|
type: "plugin",
|
|
name: "chartToolbar",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: {
|
|
enabled: boolean
|
|
},
|
|
create: (ctx) => new ChartToolbar(ctx),
|
|
patchContext: (ctx) => {
|
|
if (ctx.sharedToolbar)
|
|
return;
|
|
ctx.sharedToolbar = new SharedToolbar(ctx);
|
|
ctx.cleanup.register(() => ctx.sharedToolbar.destroy());
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/context-menu/contextMenuItem.ts
|
|
function showsFor(showOn, showing) {
|
|
if (showOn === "always")
|
|
return true;
|
|
if (showOn === "series-area")
|
|
return showing === "series-area" || showing === "series-node";
|
|
return showOn === showing;
|
|
}
|
|
function appendItem(showing, item, result) {
|
|
let mustShow = true;
|
|
if (item.type === "separator") {
|
|
const last = result.at(-1);
|
|
mustShow = last !== void 0 && last.type !== "separator";
|
|
}
|
|
mustShow && (mustShow = showsFor(item.showOn ?? "always", showing));
|
|
if (mustShow) {
|
|
const menuItem = new ContextMenuItem(item);
|
|
result.push(menuItem);
|
|
return menuItem;
|
|
}
|
|
}
|
|
function appendBuiltinItem(showing, registry, keyword, result) {
|
|
if (registry.isVisible(keyword)) {
|
|
appendItem(showing, registry.builtins.items[keyword], result);
|
|
}
|
|
}
|
|
function expandBuiltin(showing, registry, keyword, result) {
|
|
const { builtins } = registry;
|
|
if (isKeyOf(keyword, builtins.lists)) {
|
|
for (const childKeyword of builtins.lists[keyword]) {
|
|
appendBuiltinItem(showing, registry, childKeyword, result);
|
|
}
|
|
} else {
|
|
appendBuiltinItem(showing, registry, keyword, result);
|
|
}
|
|
}
|
|
function expandBuiltinLists(showing, items, registry) {
|
|
const unfiltered = [];
|
|
const { builtins } = registry;
|
|
for (const it of items) {
|
|
if (typeof it === "string" && isKeyOf(it, builtins.lists)) {
|
|
for (const listItem of builtins.lists[it]) {
|
|
unfiltered.push(listItem);
|
|
}
|
|
} else {
|
|
unfiltered.push(it);
|
|
}
|
|
}
|
|
return unfiltered.filter((it) => {
|
|
if (typeof it === "string") {
|
|
const showOn = registry.builtins.items[it].showOn ?? "always";
|
|
return registry.isVisible(it) && showsFor(showOn, showing);
|
|
} else {
|
|
return showsFor(it.showOn ?? "always", showing);
|
|
}
|
|
});
|
|
}
|
|
function expandItems(showing, registry, items, result) {
|
|
for (const item of items) {
|
|
if (typeof item === "string") {
|
|
expandBuiltin(showing, registry, item, result);
|
|
} else {
|
|
const menuItem = appendItem(showing, item, result);
|
|
if (item.items && menuItem && item.items.length > 0) {
|
|
expandItems(showing, registry, item.items, menuItem.items);
|
|
}
|
|
}
|
|
}
|
|
if (result.at(-1)?.type === "separator") {
|
|
result.pop();
|
|
}
|
|
}
|
|
var ContextMenuItem = class {
|
|
constructor(options) {
|
|
this.type = "action";
|
|
this.showOn = "always";
|
|
this.label = "";
|
|
this.iconUrl = void 0;
|
|
this.enabled = true;
|
|
this.items = [];
|
|
this.action = void 0;
|
|
if (options)
|
|
this.setOptions(options);
|
|
this.items = [];
|
|
}
|
|
setField(key, that, value) {
|
|
that[key] = value;
|
|
}
|
|
setOptions(options) {
|
|
let key;
|
|
for (key in options) {
|
|
if (options[key] !== void 0) {
|
|
this.setField(key, this, options[key]);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/context-menu/contextMenuStyles.ts
|
|
var DEFAULT_CONTEXT_MENU_CLASS = "ag-charts-context-menu";
|
|
|
|
// packages/ag-charts-enterprise/src/features/context-menu/contextMenu.ts
|
|
var { ContextMenuRegistry: ContextMenuRegistry2 } = module_support_exports;
|
|
var moduleId2 = "context-menu";
|
|
var DATUM_KEYS = [
|
|
"angleKey",
|
|
"calloutLabelKey",
|
|
"colorKey",
|
|
"labelKey",
|
|
"radiusKey",
|
|
"sectorLabelKey",
|
|
"sizeKey",
|
|
"xKey",
|
|
"yKey"
|
|
];
|
|
var ContextMenu = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.enabled = true;
|
|
this.darkTheme = false;
|
|
this.items = ["defaults"];
|
|
// State
|
|
this.pickedNode = void 0;
|
|
this.showEvent = void 0;
|
|
this.x = 0;
|
|
this.y = 0;
|
|
this.collapsingSubMenus = 0;
|
|
this.menuWidget = new exports_exports.MenuWidget();
|
|
this.interactionManager = ctx.interactionManager;
|
|
this.element = ctx.domManager.addChild("canvas-overlay", moduleId2);
|
|
this.element.classList.add(DEFAULT_CONTEXT_MENU_CLASS);
|
|
this.element.style.display = "none";
|
|
this.element.addEventListener("contextmenu", (event) => event.preventDefault());
|
|
this.element.addEventListener("focusout", ({ relatedTarget }) => {
|
|
if (this.collapsingSubMenus > 0)
|
|
return;
|
|
if (relatedTarget == null || relatedTarget instanceof Node && !this.element.contains(relatedTarget)) {
|
|
this.hide();
|
|
}
|
|
});
|
|
this.cleanup.register(
|
|
() => this.element.remove(),
|
|
() => this.menuWidget.destroy(),
|
|
ctx.eventsHub.on("dom:hidden", () => this.hide()),
|
|
this.menuWidget.addListener("collapse-widget", () => this.onCollapse())
|
|
);
|
|
this.menuWidget.addClass(`${DEFAULT_CONTEXT_MENU_CLASS}__menu`);
|
|
if (typeof MutationObserver !== "undefined") {
|
|
const observer = new MutationObserver(() => {
|
|
if (this.element.contains(this.menuWidget.getElement())) {
|
|
this.reposition();
|
|
}
|
|
});
|
|
observer.observe(this.element, { childList: true });
|
|
this.mutationObserver = observer;
|
|
this.cleanup.register(() => observer.disconnect());
|
|
}
|
|
this.ctx.contextMenuRegistry.builtins.items["download"].action = () => {
|
|
const title = ctx.chartService.title;
|
|
let fileName = "image";
|
|
if (title?.enabled) {
|
|
fileName = title.node.getPlainText().replace(/\.+/, "");
|
|
}
|
|
this.ctx.chartService.publicApi?.download({ fileName }).catch((e) => {
|
|
logger_exports.error("Unable to download chart", e);
|
|
});
|
|
};
|
|
this.cleanup.register(this.ctx.eventsHub.on("context-menu:complete", (e) => this.onContext(e)));
|
|
}
|
|
makeGetItemsParams(event) {
|
|
const { showOn } = event;
|
|
const { context } = this.ctx.chartService;
|
|
const defaultItems = expandBuiltinLists(showOn, this.items, this.ctx.contextMenuRegistry);
|
|
switch (showOn) {
|
|
case "always":
|
|
case "series-area":
|
|
return { showOn, context, defaultItems };
|
|
case "series-node": {
|
|
if (this.pickedNode == null)
|
|
throw new Error(`this.pickedNode is null`);
|
|
const params = {
|
|
showOn,
|
|
context,
|
|
seriesId: this.pickedNode.series.id,
|
|
datum: this.pickedNode.datum,
|
|
defaultItems
|
|
};
|
|
for (const k of DATUM_KEYS) {
|
|
if (this.pickedNode[k] !== void 0) {
|
|
params[k] = this.pickedNode[k];
|
|
}
|
|
}
|
|
return params;
|
|
}
|
|
case "legend-item":
|
|
if (this.pickedLegendItem == null)
|
|
throw new Error(`this.pickedLegendItem is null`);
|
|
const { itemId, seriesId, label, enabled } = this.pickedLegendItem;
|
|
const text2 = toPlainText(label.text);
|
|
if (typeof itemId !== "string") {
|
|
throw new Error(`unexpected itemId type: [${typeof itemId}] (expected [string])`);
|
|
}
|
|
return { showOn, context, itemId, seriesId, text: text2, visible: enabled, defaultItems };
|
|
default:
|
|
return showOn;
|
|
}
|
|
}
|
|
expandItemsOptions(event) {
|
|
const result = [];
|
|
let items;
|
|
if (this.getItems) {
|
|
const cbParams = this.makeGetItemsParams(event);
|
|
items = this.getItems(cbParams);
|
|
}
|
|
items ?? (items = this.items);
|
|
expandItems(event.showOn, this.ctx.contextMenuRegistry, items, result);
|
|
return result;
|
|
}
|
|
onContext(event) {
|
|
if (!this.enabled)
|
|
return;
|
|
event.widgetEvent.sourceEvent.preventDefault();
|
|
this.showEvent = event.widgetEvent.sourceEvent;
|
|
this.x = event.x;
|
|
this.y = event.y;
|
|
this.pickedNode = void 0;
|
|
this.pickedLegendItem = void 0;
|
|
if (ContextMenuRegistry2.check("series-node", event)) {
|
|
this.pickedNode = event.context.pickedNode;
|
|
} else if (ContextMenuRegistry2.check("legend-item", event)) {
|
|
this.pickedLegendItem = event.context.legendItem;
|
|
}
|
|
const expandedItems = this.expandItemsOptions(event);
|
|
if (expandedItems.length === 0)
|
|
return;
|
|
this.show(event.widgetEvent, expandedItems);
|
|
}
|
|
show(widgetEvent, expandedItems) {
|
|
const { sourceEvent } = widgetEvent;
|
|
this.interactionManager.pushState(module_support_exports.InteractionState.ContextMenu);
|
|
this.element.style.display = "block";
|
|
const overrideFocusVisible = sourceEvent.pointerType === "touch" ? false : void 0;
|
|
if (overrideFocusVisible !== void 0) {
|
|
this.ctx.chartService.overrideFocusVisible(overrideFocusVisible);
|
|
}
|
|
this.createMenu(expandedItems);
|
|
this.element.appendChild(this.menuWidget.getElement());
|
|
this.menuWidget.expand({ sourceEvent, overrideFocusVisible });
|
|
}
|
|
hide() {
|
|
this.menuWidget.collapse();
|
|
}
|
|
onCollapse() {
|
|
this.interactionManager.popState(module_support_exports.InteractionState.ContextMenu);
|
|
this.menuWidget.getElement().remove();
|
|
this.element.style.display = "none";
|
|
}
|
|
onSubMenuExpand(button, menu) {
|
|
const bounds = button.getBounds();
|
|
button.setFocusOverride(true);
|
|
button.getElement().insertAdjacentElement("afterend", menu.getElement());
|
|
menu.getElement().style.position = "absolute";
|
|
const canvasRect = this.ctx.domManager.getBoundingClientRect();
|
|
const buttonClientRect = button.getBoundingClientRect();
|
|
const remainingSpaceOnRight = canvasRect.right - buttonClientRect.right;
|
|
const remainingSpaceOnLeft = buttonClientRect.left - canvasRect.left;
|
|
const { offsetWidth: menuOffsetWidth, offsetHeight: menuOffsetHeight } = menu.getElement();
|
|
let y = bounds.y;
|
|
if (canvasRect.height > menuOffsetHeight) {
|
|
const remainingSpaceOnBottom = canvasRect.bottom - buttonClientRect.top;
|
|
if (remainingSpaceOnBottom < menuOffsetHeight) {
|
|
y -= menuOffsetHeight - remainingSpaceOnBottom;
|
|
}
|
|
}
|
|
if (remainingSpaceOnRight >= menuOffsetWidth) {
|
|
menu.setBounds({ x: bounds.x + bounds.width, y });
|
|
} else {
|
|
const x = bounds.x - menuOffsetWidth;
|
|
const leftDelta = remainingSpaceOnLeft + x;
|
|
if (leftDelta >= 0) {
|
|
menu.setBounds({ x, y });
|
|
} else {
|
|
menu.setBounds({ x: x - leftDelta, y });
|
|
}
|
|
}
|
|
}
|
|
onSubMenuCollapse(button, menu) {
|
|
button.setFocusOverride(void 0);
|
|
this.collapsingSubMenus++;
|
|
menu.remove();
|
|
this.collapsingSubMenus--;
|
|
}
|
|
createMenu(expandedItems) {
|
|
const { menuWidget } = this;
|
|
menuWidget.clear();
|
|
menuWidget.setTabIndex(-1);
|
|
this.createMenuItems(menuWidget, expandedItems);
|
|
}
|
|
createMenuItems(menuWidget, expandedItems) {
|
|
for (const item of expandedItems) {
|
|
switch (item.type) {
|
|
case "separator": {
|
|
const sep = menuWidget.addSeparator();
|
|
sep.classList.add(`${DEFAULT_CONTEXT_MENU_CLASS}__divider`);
|
|
break;
|
|
}
|
|
case "action": {
|
|
if (item.items.length === 0) {
|
|
const btn = new exports_exports.MenuItemWidget();
|
|
this.initButtonElement(btn, item);
|
|
menuWidget.addChild(btn);
|
|
} else {
|
|
const { subMenuButton, subMenu } = menuWidget.addSubMenu();
|
|
subMenu.addClass(`${DEFAULT_CONTEXT_MENU_CLASS}__menu`);
|
|
subMenu.addListener("expand-widget", () => this.onSubMenuExpand(subMenuButton, subMenu));
|
|
subMenu.addListener("collapse-widget", () => this.onSubMenuCollapse(subMenuButton, subMenu));
|
|
this.initButtonElement(subMenuButton, item);
|
|
this.createMenuItems(subMenu, item.items);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
throw new Error("unhandled case");
|
|
}
|
|
}
|
|
}
|
|
createButtonOnClick(showOn, callback2) {
|
|
if (ContextMenuRegistry2.checkCallback("legend-item", showOn, callback2)) {
|
|
return (widgetEvent) => {
|
|
const event = widgetEvent.sourceEvent;
|
|
if (this.pickedLegendItem) {
|
|
const { seriesId, itemId, label } = this.pickedLegendItem;
|
|
const { chartService: chart } = this.ctx;
|
|
if (typeof itemId !== "string") {
|
|
logger_exports.error(`unexpected itemId type: [${typeof itemId}] (expected [string])`);
|
|
return;
|
|
}
|
|
const series = chart.series.find((s) => s.id === seriesId);
|
|
const callers = [series?.properties, chart];
|
|
const apiEvent = {
|
|
type: "contextmenu",
|
|
seriesId,
|
|
itemId,
|
|
text: toPlainText(label.text),
|
|
event
|
|
};
|
|
callWithContext(callers, callback2, apiEvent);
|
|
this.hide();
|
|
} else {
|
|
logger_exports.error("legend item not found");
|
|
}
|
|
};
|
|
} else if (ContextMenuRegistry2.checkCallback("series-area", showOn, callback2)) {
|
|
return () => {
|
|
const caller = this.ctx.chartService;
|
|
const apiEvent = { type: "seriesContextMenuAction", event: this.showEvent };
|
|
callWithContext(caller, callback2, apiEvent);
|
|
this.hide();
|
|
};
|
|
} else if (ContextMenuRegistry2.checkCallback("series-node", showOn, callback2)) {
|
|
return () => {
|
|
const { showEvent } = this;
|
|
const { chartService: chart } = this.ctx;
|
|
const pickedNode = this.pickedNode;
|
|
const callers = [pickedNode?.series.properties, chart];
|
|
const apiEvent = pickedNode?.series.createNodeContextMenuActionEvent(showEvent, pickedNode);
|
|
if (apiEvent) {
|
|
callWithContext(callers, callback2, apiEvent);
|
|
} else {
|
|
logger_exports.error("series node not found");
|
|
}
|
|
this.hide();
|
|
};
|
|
}
|
|
return () => {
|
|
const caller = this.ctx.chartService;
|
|
const apiEvent = { type: "contextMenuEvent", event: this.showEvent };
|
|
callWithContext(caller, callback2, apiEvent);
|
|
this.hide();
|
|
};
|
|
}
|
|
initTableCells(elem) {
|
|
const cellIcon = createElement("div");
|
|
const cellLabel = createElement("div");
|
|
const cellArrow = createElement("div");
|
|
cellIcon.classList.toggle(`${DEFAULT_CONTEXT_MENU_CLASS}__icon`, true);
|
|
cellLabel.classList.toggle(`${DEFAULT_CONTEXT_MENU_CLASS}__cell`, true);
|
|
cellArrow.classList.toggle(`${DEFAULT_CONTEXT_MENU_CLASS}__cell`, true);
|
|
cellIcon.ariaHidden = "true";
|
|
cellLabel.role = "presentation";
|
|
cellArrow.ariaHidden = "true";
|
|
elem.append(cellIcon, cellLabel, cellArrow);
|
|
return { cellIcon, cellLabel, cellArrow };
|
|
}
|
|
initButtonElement(button, item) {
|
|
button.addClass(`${DEFAULT_CONTEXT_MENU_CLASS}__item`);
|
|
button.setEnabled(item.enabled);
|
|
const label = this.ctx.localeManager.t(item.label);
|
|
const cellPaddingClass = `${DEFAULT_CONTEXT_MENU_CLASS}__cellpadding`;
|
|
const { cellIcon, cellLabel, cellArrow } = this.initTableCells(button.getElement());
|
|
cellLabel.textContent = label;
|
|
cellLabel.classList.add(cellPaddingClass);
|
|
if (item.iconUrl != null) {
|
|
const img = createElement("img");
|
|
img.src = item.iconUrl;
|
|
cellIcon.append(img);
|
|
cellIcon.classList.add(cellPaddingClass);
|
|
}
|
|
if (item.items.length > 0) {
|
|
const span = createElement("span", getIconClassNames("chevron-right"));
|
|
cellArrow.append(span);
|
|
cellArrow.classList.add(cellPaddingClass);
|
|
}
|
|
const { showOn, action } = item;
|
|
if (action != null) {
|
|
button.addListener("click", this.createButtonOnClick(showOn, action));
|
|
}
|
|
if (item.items.length === 0) {
|
|
button.addListener("mouseleave", () => button.setFocusOverride(false));
|
|
button.addListener("mouseenter", () => button.setFocusOverride(void 0));
|
|
}
|
|
}
|
|
reposition() {
|
|
let { x, y } = this;
|
|
this.element.style.top = "unset";
|
|
this.element.style.bottom = "unset";
|
|
const canvasRect = this.ctx.domManager.getBoundingClientRect();
|
|
const { offsetWidth: width2, offsetHeight: height2 } = this.element;
|
|
x = clamp(0, x, canvasRect.width - width2);
|
|
y = clamp(0, y, canvasRect.height - height2);
|
|
this.element.style.left = `${x}px`;
|
|
this.element.style.top = `calc(${y}px - 0.5em)`;
|
|
}
|
|
destroy() {
|
|
super.destroy();
|
|
this.mutationObserver?.disconnect();
|
|
this.ctx.domManager.removeStyles(moduleId2);
|
|
this.ctx.domManager.removeChild("canvas-overlay", moduleId2);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ContextMenu.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ContextMenu.prototype, "darkTheme", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ContextMenu.prototype, "items", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ContextMenu.prototype, "getItems", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/context-menu/contextMenuModule.ts
|
|
var ContextMenuModule = {
|
|
type: "plugin",
|
|
name: "contextMenu",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: {
|
|
enabled: boolean,
|
|
items: contextMenuItemsArray,
|
|
getItems: callbackOf(contextMenuItemsArray, "a menu items array")
|
|
},
|
|
themeTemplate: {
|
|
enabled: true,
|
|
darkTheme: IS_DARK_THEME
|
|
},
|
|
create: (ctx) => new ContextMenu(ctx)
|
|
};
|
|
ContextMenuModule.options.darkTheme = undocumented(boolean);
|
|
|
|
// packages/ag-charts-enterprise/src/utils/datum.ts
|
|
function readDatum(nodeDatum) {
|
|
if (typeof nodeDatum?.datum === "object") {
|
|
return nodeDatum.datum;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/crosshair/crosshairLabel.ts
|
|
var { FormatManager: FormatManager2 } = module_support_exports;
|
|
var DEFAULT_LABEL_CLASS = "ag-charts-crosshair-label";
|
|
var CrosshairLabelProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = true;
|
|
this.xOffset = 0;
|
|
this.yOffset = 0;
|
|
this.format = void 0;
|
|
this.renderer = void 0;
|
|
this._cachedFormatter = void 0;
|
|
}
|
|
formatValue(callWithContext2, type, value, params) {
|
|
const { formatter: formatter2, format } = this;
|
|
const { domain, boundSeries } = params;
|
|
let result;
|
|
if (formatter2 != null) {
|
|
const fractionDigits = params.type === "number" ? params.fractionDigits : void 0;
|
|
const unit = params.type === "date" ? params.unit : void 0;
|
|
const step = params.type === "date" ? params.step : void 0;
|
|
result = callWithContext2(formatter2, { value, domain, fractionDigits, unit, step, boundSeries });
|
|
}
|
|
if (format != null) {
|
|
let cachedFormatter = this._cachedFormatter;
|
|
if (cachedFormatter?.type !== type || cachedFormatter?.format !== format) {
|
|
cachedFormatter = {
|
|
type,
|
|
format,
|
|
formatter: FormatManager2.getFormatter(type, format)
|
|
};
|
|
this._cachedFormatter = cachedFormatter;
|
|
}
|
|
result ?? (result = cachedFormatter.formatter?.(value));
|
|
}
|
|
return result == null ? void 0 : String(result);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CrosshairLabelProperties.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CrosshairLabelProperties.prototype, "xOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CrosshairLabelProperties.prototype, "yOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CrosshairLabelProperties.prototype, "formatter", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CrosshairLabelProperties.prototype, "format", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CrosshairLabelProperties.prototype, "renderer", 2);
|
|
var CrosshairLabel = class extends CrosshairLabelProperties {
|
|
constructor(domManager, key, axisId) {
|
|
super();
|
|
this.domManager = domManager;
|
|
this.id = createId(this);
|
|
this.element = domManager.addChild("canvas-overlay", `crosshair-label-${this.id}`);
|
|
this.element.classList.add(DEFAULT_LABEL_CLASS);
|
|
setAttribute(this.element, "aria-hidden", true);
|
|
this.element.dataset.key = key;
|
|
this.element.dataset.axisId = axisId;
|
|
}
|
|
show(meta) {
|
|
const { element: element2 } = this;
|
|
const left = meta.x + this.xOffset;
|
|
const top = meta.y + this.yOffset;
|
|
element2.style.top = `${Math.round(top)}px`;
|
|
element2.style.left = `${Math.round(left)}px`;
|
|
this.toggle(true);
|
|
}
|
|
setLabelHtml({ html, styles }) {
|
|
if (html !== void 0) {
|
|
this.element.innerHTML = html;
|
|
}
|
|
if (styles !== void 0) {
|
|
const styleElement = this.element.children[0] ?? this.element;
|
|
Object.assign(styleElement.style, styles);
|
|
}
|
|
}
|
|
getBBox() {
|
|
const { element: element2 } = this;
|
|
return new module_support_exports.BBox(
|
|
element2.clientLeft,
|
|
element2.clientTop,
|
|
element2.clientWidth,
|
|
element2.clientHeight
|
|
);
|
|
}
|
|
toggle(visible) {
|
|
this.element.classList.toggle(`ag-charts-crosshair-label--hidden`, !visible);
|
|
}
|
|
destroy() {
|
|
this.domManager.removeChild("canvas-overlay", `crosshair-label-${this.id}`);
|
|
}
|
|
toLabelHtml(input, defaults) {
|
|
if (typeof input === "string") {
|
|
return { html: input, styles: {} };
|
|
}
|
|
defaults = defaults ?? {};
|
|
const {
|
|
text: text2 = defaults.text ?? "",
|
|
color: color2 = defaults.color,
|
|
backgroundColor = defaults.backgroundColor,
|
|
opacity = defaults.opacity ?? 1
|
|
} = input;
|
|
const styles = {
|
|
opacity,
|
|
"background-color": backgroundColor?.toLowerCase(),
|
|
color: color2
|
|
};
|
|
return {
|
|
html: `<div class="ag-charts-crosshair-label-content">
|
|
<span>${text2}</span>
|
|
</div>`,
|
|
styles
|
|
};
|
|
}
|
|
};
|
|
CrosshairLabel.className = "CrosshairLabel";
|
|
|
|
// packages/ag-charts-enterprise/src/features/crosshair/crosshair.ts
|
|
var { Group: Group8, TranslatableGroup: TranslatableGroup3, Line: Line3, BBox: BBox12, InteractionState: InteractionState5 } = module_support_exports;
|
|
var Crosshair = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.id = createId(this);
|
|
this.enabled = false;
|
|
this.stroke = "rgb(195, 195, 195)";
|
|
this.lineDash = [6, 3];
|
|
this.lineDashOffset = 0;
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.snap = true;
|
|
this.label = new CrosshairLabelProperties();
|
|
this.seriesRect = new BBox12(0, 0, 0, 0);
|
|
this.bounds = new BBox12(0, 0, 0, 0);
|
|
this.crosshairGroup = new TranslatableGroup3({
|
|
name: "crosshairs",
|
|
zIndex: 9 /* SERIES_CROSSHAIR */
|
|
});
|
|
this.lineGroup = this.crosshairGroup.appendChild(
|
|
new Group8({
|
|
name: `${this.id}-crosshair-lines`,
|
|
zIndex: 9 /* SERIES_CROSSHAIR */
|
|
})
|
|
);
|
|
this.lineGroupSelection = module_support_exports.Selection.select(this.lineGroup, Line3, false);
|
|
this.activeHighlight = void 0;
|
|
this.axisCtx = ctx.parent;
|
|
this.labels = {};
|
|
this.hideCrosshairs();
|
|
ctx.domManager.addEventListener("focusin", ({ target }) => {
|
|
if (this.checkInteractionState())
|
|
return;
|
|
const isSeriesAreaChild = target instanceof HTMLElement && ctx.domManager.contains(target, "series-area");
|
|
if (this.crosshairGroup.visible && !isSeriesAreaChild) {
|
|
this.hideCrosshairs();
|
|
this.ctx.updateService.update(9 /* SCENE_RENDER */);
|
|
}
|
|
});
|
|
const { seriesDragInterpreter } = ctx.widgets;
|
|
this.cleanup.register(
|
|
ctx.scene.attachNode(this.crosshairGroup),
|
|
ctx.widgets.seriesWidget.addListener("mousemove", (event) => this.onMouseHoverLike(event)),
|
|
ctx.widgets.seriesWidget.addListener("mouseleave", () => this.onMouseOut()),
|
|
ctx.eventsHub.on("series:focus-change", () => this.onKeyPress()),
|
|
ctx.eventsHub.on("zoom:pan-start", () => this.onMouseOut()),
|
|
ctx.eventsHub.on("zoom:change-complete", () => this.onMouseOut()),
|
|
ctx.eventsHub.on("highlight:change", (event) => this.onHighlightChange(event)),
|
|
ctx.eventsHub.on("layout:complete", (event) => this.layout(event)),
|
|
() => {
|
|
for (const label of Object.values(this.labels)) {
|
|
label.destroy();
|
|
}
|
|
}
|
|
);
|
|
if (seriesDragInterpreter) {
|
|
this.cleanup.register(
|
|
seriesDragInterpreter.events.on("drag-move", (event) => this.onMouseHoverLike(event)),
|
|
seriesDragInterpreter.events.on("click", (event) => this.onClick(event))
|
|
);
|
|
}
|
|
}
|
|
checkInteractionState() {
|
|
return this.ctx.interactionManager.isState(InteractionState5.Frozen);
|
|
}
|
|
layout({ series: { rect: rect2, visible }, axes }) {
|
|
if (!visible || !axes || !this.enabled)
|
|
return;
|
|
this.seriesRect = rect2;
|
|
const { position: axisPosition = "left", axisId } = this.axisCtx;
|
|
const axisLayout = axes[axisId];
|
|
if (!axisLayout)
|
|
return;
|
|
this.axisLayout = axisLayout;
|
|
this.bounds = rect2.clone().grow(axisLayout.gridPadding + axisLayout.seriesAreaPadding, axisPosition);
|
|
const { crosshairGroup, bounds } = this;
|
|
crosshairGroup.translationX = Math.round(bounds.x);
|
|
crosshairGroup.translationY = Math.round(bounds.y);
|
|
const crosshairKeys = ["pointer", ...this.axisCtx.seriesKeyProperties()];
|
|
this.updateSelections(crosshairKeys);
|
|
this.updateLines();
|
|
this.updateLabels(crosshairKeys);
|
|
this.refreshPositions();
|
|
}
|
|
updateSelections(data) {
|
|
this.lineGroupSelection.update(data, void 0, (key) => key);
|
|
}
|
|
updateLabels(keys) {
|
|
const { labels, ctx } = this;
|
|
for (const key of keys) {
|
|
if (this.label.enabled) {
|
|
labels[key] ?? (labels[key] = new CrosshairLabel(ctx.domManager, key, this.axisCtx.axisId));
|
|
}
|
|
if (labels[key]) {
|
|
this.updateLabel(labels[key]);
|
|
}
|
|
}
|
|
}
|
|
updateLabel(label) {
|
|
const { enabled, xOffset, yOffset, format, renderer } = this.label;
|
|
label.enabled = enabled;
|
|
label.xOffset = xOffset;
|
|
label.yOffset = yOffset;
|
|
label.format = format;
|
|
label.renderer = renderer;
|
|
}
|
|
updateLines() {
|
|
const { lineGroupSelection, bounds, stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset, axisLayout } = this;
|
|
if (!axisLayout)
|
|
return;
|
|
const isVertical = this.isVertical();
|
|
lineGroupSelection.each((line) => {
|
|
line.stroke = stroke3;
|
|
line.strokeWidth = strokeWidth;
|
|
line.strokeOpacity = strokeOpacity;
|
|
line.lineDash = lineDash;
|
|
line.lineDashOffset = lineDashOffset;
|
|
line.y1 = 0;
|
|
line.y2 = isVertical ? bounds.height : 0;
|
|
line.x1 = 0;
|
|
line.x2 = isVertical ? 0 : bounds.width;
|
|
});
|
|
}
|
|
isVertical() {
|
|
return this.axisCtx.direction === "x" /* X */;
|
|
}
|
|
isHover(event) {
|
|
return event.type === "mousemove" || event.type === "click" || event.device === "touch" && this.ctx.chartService.touch.dragAction === "hover";
|
|
}
|
|
formatValue(value) {
|
|
return toPlainText(this.axisCtx.formatScaleValue(value, "crosshair", this.label));
|
|
}
|
|
onClick(event) {
|
|
if (event.device === "touch") {
|
|
this.onMouseHoverLike(event);
|
|
}
|
|
}
|
|
onMouseHoverLike(event) {
|
|
if (!this.enabled || this.snap)
|
|
return;
|
|
const requiredState = this.isHover(event) ? InteractionState5.Clickable : InteractionState5.AnnotationsMoveable;
|
|
if (!this.ctx.interactionManager.isState(requiredState))
|
|
return;
|
|
this.updatePositions(this.getData(event));
|
|
this.crosshairGroup.visible = true;
|
|
this.ctx.updateService.update(9 /* SCENE_RENDER */);
|
|
}
|
|
onMouseOut() {
|
|
if (!this.ctx.interactionManager.isState(InteractionState5.Clickable))
|
|
return;
|
|
this.hideCrosshairs();
|
|
this.ctx.updateService.update(9 /* SCENE_RENDER */);
|
|
}
|
|
onKeyPress() {
|
|
if (this.enabled && !this.snap && this.ctx.interactionManager.isState(InteractionState5.Default)) {
|
|
this.hideCrosshairs();
|
|
}
|
|
}
|
|
onHighlightChange(event) {
|
|
if (!this.enabled)
|
|
return;
|
|
const { crosshairGroup, axisCtx } = this;
|
|
const { datum, series } = event.currentHighlight ?? {};
|
|
const hasCrosshair = datum && (series?.axes.x?.id === axisCtx.axisId || series?.axes.y?.id === axisCtx.axisId);
|
|
this.activeHighlight = hasCrosshair ? event.currentHighlight : void 0;
|
|
if (!this.activeHighlight) {
|
|
this.hideCrosshairs();
|
|
} else if (this.snap) {
|
|
const activeHighlightData = this.getActiveHighlightData(this.activeHighlight);
|
|
this.updatePositions(activeHighlightData);
|
|
crosshairGroup.visible = true;
|
|
}
|
|
}
|
|
isInRange(value) {
|
|
return this.axisCtx.inRange(value);
|
|
}
|
|
refreshPositions() {
|
|
if (this.activeHighlight) {
|
|
this.updatePositions(this.getActiveHighlightData(this.activeHighlight));
|
|
}
|
|
}
|
|
updatePositions(data) {
|
|
const { seriesRect, lineGroupSelection } = this;
|
|
lineGroupSelection.each((line, key) => {
|
|
const lineData = data[key];
|
|
if (!lineData) {
|
|
line.visible = false;
|
|
this.hideLabel(key);
|
|
return;
|
|
}
|
|
line.visible = true;
|
|
const { value, position } = lineData;
|
|
let x = 0;
|
|
let y = 0;
|
|
if (this.isVertical()) {
|
|
x = position;
|
|
line.x = Math.round(x);
|
|
} else {
|
|
y = position;
|
|
line.y = Math.round(y);
|
|
}
|
|
if (this.label.enabled) {
|
|
this.showLabel(x + seriesRect.x, y + seriesRect.y, value, key);
|
|
} else {
|
|
this.hideLabel(key);
|
|
}
|
|
});
|
|
}
|
|
getData(event) {
|
|
const { axisCtx } = this;
|
|
const key = "pointer";
|
|
const { xKey = "", yKey = "" } = this.activeHighlight ?? {};
|
|
const { currentX, currentY } = event;
|
|
const datum = readDatum(this.activeHighlight);
|
|
const isVertical = this.isVertical();
|
|
const position = isVertical ? currentX : currentY;
|
|
let value = datum?.[isVertical ? xKey : yKey] ?? "";
|
|
if (axisCtx.continuous) {
|
|
value = axisCtx.scaleInvert(position);
|
|
}
|
|
return { [key]: { position, value } };
|
|
}
|
|
getActiveHighlightData(activeHighlight) {
|
|
const { axisCtx } = this;
|
|
const { series, xKey = "", aggregatedValue, cumulativeValue, midPoint } = activeHighlight;
|
|
const datum = readDatum(activeHighlight);
|
|
const seriesKeyProperties = series.getKeyProperties(axisCtx.direction);
|
|
const halfBandwidth = (axisCtx.scale.bandwidth ?? 0) / 2;
|
|
const matchingAxisId = series.axes[axisCtx.direction]?.id === axisCtx.axisId;
|
|
const isYKey = seriesKeyProperties.includes("yKey") && matchingAxisId;
|
|
const isXKey = seriesKeyProperties.includes("xKey") && matchingAxisId;
|
|
const datumValue = aggregatedValue ?? cumulativeValue;
|
|
if (isYKey && datumValue !== void 0) {
|
|
const position = axisCtx.scale.convert(datumValue) + halfBandwidth;
|
|
const isInRange = this.isInRange(position);
|
|
return isInRange ? {
|
|
yKey: { value: datumValue, position }
|
|
} : {};
|
|
}
|
|
if (isXKey) {
|
|
const position = (this.isVertical() ? midPoint?.x : midPoint?.y) ?? 0;
|
|
const value = axisCtx.continuous ? axisCtx.scaleInvert(position) : datum?.[xKey];
|
|
return this.isInRange(position) ? { xKey: { value, position } } : {};
|
|
}
|
|
const activeHighlightData = {};
|
|
for (const key of seriesKeyProperties) {
|
|
const keyValue = series.properties[key];
|
|
const value = datum?.[keyValue];
|
|
const position = axisCtx.scale.convert(value) + halfBandwidth;
|
|
const isInRange = this.isInRange(position);
|
|
if (isInRange) {
|
|
activeHighlightData[key] = { value, position };
|
|
}
|
|
}
|
|
return activeHighlightData;
|
|
}
|
|
getLabelHtml(value, label) {
|
|
const fractionDigits = this.axisLayout?.label?.fractionDigits ?? 0;
|
|
const defaults = { text: this.formatValue(value) };
|
|
if (this.label.renderer) {
|
|
return label.toLabelHtml(this.label.renderer({ value, fractionDigits }), defaults);
|
|
}
|
|
return label.toLabelHtml(defaults);
|
|
}
|
|
showLabel(x, y, value, key) {
|
|
if (!this.axisLayout)
|
|
return;
|
|
const { bounds } = this;
|
|
const label = this.labels[key];
|
|
const html = this.getLabelHtml(value, label);
|
|
label.setLabelHtml(html);
|
|
const { width: width2, height: height2 } = label.getBBox();
|
|
const axisPosition = this.axisCtx.position;
|
|
let padding2 = this.axisLayout.label.spacing + this.axisLayout.tickSize;
|
|
if (this.axisCtx.direction === "x" /* X */) {
|
|
padding2 -= 4;
|
|
label.show({
|
|
x: x - width2 / 2,
|
|
y: axisPosition === "bottom" ? bounds.y + bounds.height + padding2 : bounds.y - height2 - padding2
|
|
});
|
|
} else {
|
|
padding2 -= 8;
|
|
label.show({
|
|
x: axisPosition === "right" ? bounds.x + bounds.width + padding2 : bounds.x - width2 - padding2,
|
|
y: y - height2 / 2
|
|
});
|
|
}
|
|
}
|
|
hideCrosshairs() {
|
|
this.crosshairGroup.visible = false;
|
|
for (const key of Object.keys(this.labels)) {
|
|
this.hideLabel(key);
|
|
}
|
|
}
|
|
hideLabel(key) {
|
|
this.labels[key]?.toggle(false);
|
|
}
|
|
};
|
|
Crosshair.className = "Crosshair";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Crosshair.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Crosshair.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Crosshair.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Crosshair.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Crosshair.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Crosshair.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Crosshair.prototype, "snap", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Crosshair.prototype, "label", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/crosshair/crosshairModule.ts
|
|
var CrosshairModule = {
|
|
type: "axis:plugin",
|
|
name: "crosshair",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
themeTemplate: {
|
|
enabled: {
|
|
$if: [
|
|
{
|
|
$or: [
|
|
{ $eq: [{ $path: "../type" }, "number"] },
|
|
{ $eq: [{ $path: "../type" }, "log"] },
|
|
{ $eq: [{ $path: "../type" }, "time"] },
|
|
{ $eq: [{ $path: "../type" }, "unit-time"] },
|
|
{ $eq: [{ $path: "../type" }, "ordinal-time"] }
|
|
]
|
|
},
|
|
true,
|
|
false
|
|
]
|
|
},
|
|
snap: true,
|
|
stroke: { $ref: "subtleTextColor" },
|
|
strokeWidth: 1,
|
|
strokeOpacity: 1,
|
|
lineDash: [5, 6],
|
|
lineDashOffset: 0,
|
|
label: {
|
|
enabled: true
|
|
}
|
|
},
|
|
create: (ctx) => new Crosshair(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/data-source/dataSource.ts
|
|
var DataSource = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.enabled = true;
|
|
this.getData = () => Promise.resolve();
|
|
this.dataService = ctx.dataService;
|
|
let dirty = false;
|
|
this.cleanup.register(
|
|
ctx.eventsHub.on("data:load", () => {
|
|
dirty = true;
|
|
}),
|
|
ctx.eventsHub.on("layout:complete", () => {
|
|
if (dirty) {
|
|
ctx.zoomManager.updateZoom({ source: "data-update", sourceDetail: "dataSource" });
|
|
}
|
|
})
|
|
);
|
|
}
|
|
updateCallback(enabled, getData) {
|
|
if (!this.dataService)
|
|
return;
|
|
if (enabled && getData != null) {
|
|
this.dataService.updateCallback(getData);
|
|
} else {
|
|
this.dataService.clearCallback();
|
|
}
|
|
}
|
|
};
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(enabled) {
|
|
this.updateCallback(enabled, this.getData);
|
|
}
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], DataSource.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(getData) {
|
|
this.updateCallback(this.enabled, getData);
|
|
}
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], DataSource.prototype, "getData", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(requestThrottle) {
|
|
this.dataService.requestThrottle = requestThrottle;
|
|
}
|
|
})
|
|
], DataSource.prototype, "requestThrottle", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(updateThrottle) {
|
|
this.dataService.dispatchThrottle = updateThrottle;
|
|
}
|
|
})
|
|
], DataSource.prototype, "updateThrottle", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(updateDuringInteraction) {
|
|
this.dataService.dispatchOnlyLatest = !updateDuringInteraction;
|
|
}
|
|
})
|
|
], DataSource.prototype, "updateDuringInteraction", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/data-source/dataSourceModule.ts
|
|
var DataSourceModule = {
|
|
type: "plugin",
|
|
name: "dataSource",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: {
|
|
getData: callback,
|
|
requestThrottle: undocumented(positiveNumber),
|
|
updateThrottle: undocumented(positiveNumber),
|
|
updateDuringInteraction: undocumented(boolean)
|
|
},
|
|
create: (ctx) => new DataSource(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/error-bar/errorBarNode.ts
|
|
var { BBox: BBox13 } = module_support_exports;
|
|
var HierarchicalBBox = class {
|
|
constructor(components) {
|
|
this.components = components;
|
|
this.union = BBox13.merge(components);
|
|
}
|
|
containsPoint(x, y) {
|
|
if (!this.union.containsPoint(x, y)) {
|
|
return false;
|
|
}
|
|
for (const bbox of this.components) {
|
|
if (bbox.containsPoint(x, y)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
var ErrorBarNode = class extends module_support_exports.Group {
|
|
constructor() {
|
|
super();
|
|
this.capLength = Number.NaN;
|
|
this._datum = void 0;
|
|
this.whiskerPath = new module_support_exports.Path();
|
|
this.capsPath = new module_support_exports.Path();
|
|
this.bboxes = new HierarchicalBBox([]);
|
|
this.append([this.whiskerPath, this.capsPath]);
|
|
}
|
|
get datum() {
|
|
return this._datum;
|
|
}
|
|
set datum(datum) {
|
|
this._datum = datum;
|
|
}
|
|
calculateCapLength(capsTheme, capDefaults) {
|
|
const { lengthRatio = 1, length: length2 } = capsTheme;
|
|
const { lengthRatioMultiplier, lengthMax } = capDefaults;
|
|
const desiredLength = length2 ?? lengthRatio * lengthRatioMultiplier;
|
|
return Math.min(desiredLength, lengthMax);
|
|
}
|
|
getItemStylerParams(options, style2, highlighted, highlightState) {
|
|
const { datum } = this;
|
|
if (datum == null || options.itemStyler == null)
|
|
return;
|
|
const { xLowerKey, xUpperKey, yLowerKey, yUpperKey } = options;
|
|
return {
|
|
...style2,
|
|
datum: datum.datum,
|
|
seriesId: datum.series.id,
|
|
xKey: datum.xKey,
|
|
yKey: datum.yKey,
|
|
xLowerKey,
|
|
xUpperKey,
|
|
yLowerKey,
|
|
yUpperKey,
|
|
highlighted,
|
|
highlightState
|
|
};
|
|
}
|
|
formatStyles(style2, options, caller, highlighted, highlightState) {
|
|
let { cap: capsStyle, ...whiskerStyle } = style2;
|
|
const params = this.getItemStylerParams(options, style2, highlighted, highlightState);
|
|
if (params != null && options.itemStyler != null) {
|
|
const result = caller.callWithContext(options.itemStyler, params);
|
|
whiskerStyle = mergeDefaults(result, whiskerStyle);
|
|
capsStyle = mergeDefaults(result?.cap, result, capsStyle);
|
|
}
|
|
return { whiskerStyle, capsStyle };
|
|
}
|
|
applyStyling(target, source) {
|
|
partialAssign(
|
|
["visible", "stroke", "strokeWidth", "strokeOpacity", "lineDash", "lineDashOffset"],
|
|
target,
|
|
source
|
|
);
|
|
}
|
|
update(style2, formatters2, caller, highlighted, highlightState) {
|
|
if (this.datum === void 0) {
|
|
return;
|
|
}
|
|
const { whiskerStyle, capsStyle } = this.formatStyles(style2, formatters2, caller, highlighted, highlightState);
|
|
const { xBar, yBar, capDefaults } = this.datum;
|
|
const whisker = this.whiskerPath;
|
|
this.applyStyling(whisker, whiskerStyle);
|
|
whisker.path.clear(true);
|
|
if (yBar !== void 0) {
|
|
whisker.path.moveTo(yBar.lowerPoint.x, yBar.lowerPoint.y);
|
|
whisker.path.lineTo(yBar.upperPoint.x, yBar.upperPoint.y);
|
|
}
|
|
if (xBar !== void 0) {
|
|
whisker.path.moveTo(xBar.lowerPoint.x, xBar.lowerPoint.y);
|
|
whisker.path.lineTo(xBar.upperPoint.x, xBar.upperPoint.y);
|
|
}
|
|
whisker.path.closePath();
|
|
this.capLength = this.calculateCapLength(capsStyle ?? {}, capDefaults);
|
|
const capOffset = this.capLength / 2;
|
|
const caps = this.capsPath;
|
|
this.applyStyling(caps, capsStyle);
|
|
caps.path.clear(true);
|
|
if (yBar !== void 0) {
|
|
caps.path.moveTo(yBar.lowerPoint.x - capOffset, yBar.lowerPoint.y);
|
|
caps.path.lineTo(yBar.lowerPoint.x + capOffset, yBar.lowerPoint.y);
|
|
caps.path.moveTo(yBar.upperPoint.x - capOffset, yBar.upperPoint.y);
|
|
caps.path.lineTo(yBar.upperPoint.x + capOffset, yBar.upperPoint.y);
|
|
}
|
|
if (xBar !== void 0) {
|
|
caps.path.moveTo(xBar.lowerPoint.x, xBar.lowerPoint.y - capOffset);
|
|
caps.path.lineTo(xBar.lowerPoint.x, xBar.lowerPoint.y + capOffset);
|
|
caps.path.moveTo(xBar.upperPoint.x, xBar.upperPoint.y - capOffset);
|
|
caps.path.lineTo(xBar.upperPoint.x, xBar.upperPoint.y + capOffset);
|
|
}
|
|
caps.path.closePath();
|
|
}
|
|
updateBBoxes() {
|
|
const { capLength, whiskerPath: whisker, capsPath: caps } = this;
|
|
const { yBar, xBar } = this.datum ?? {};
|
|
const capOffset = capLength / 2;
|
|
const components = [];
|
|
if (yBar !== void 0) {
|
|
const whiskerHeight = yBar.lowerPoint.y - yBar.upperPoint.y;
|
|
components.push(
|
|
new BBox13(yBar.lowerPoint.x, yBar.upperPoint.y, whisker.strokeWidth, whiskerHeight),
|
|
new BBox13(yBar.lowerPoint.x - capOffset, yBar.lowerPoint.y, capLength, caps.strokeWidth),
|
|
new BBox13(yBar.upperPoint.x - capOffset, yBar.upperPoint.y, capLength, caps.strokeWidth)
|
|
);
|
|
}
|
|
if (xBar !== void 0) {
|
|
const whiskerWidth = xBar.upperPoint.x - xBar.lowerPoint.x;
|
|
components.push(
|
|
new BBox13(xBar.lowerPoint.x, xBar.upperPoint.y, whiskerWidth, whisker.strokeWidth),
|
|
new BBox13(xBar.lowerPoint.x, xBar.lowerPoint.y - capOffset, caps.strokeWidth, capLength),
|
|
new BBox13(xBar.upperPoint.x, xBar.upperPoint.y - capOffset, caps.strokeWidth, capLength)
|
|
);
|
|
}
|
|
this.bboxes.components = components;
|
|
this.bboxes.union = BBox13.merge(components);
|
|
}
|
|
containsPoint(x, y) {
|
|
return this.bboxes.containsPoint(x, y);
|
|
}
|
|
pickNode(x, y) {
|
|
return this.containsPoint(x, y) ? this : void 0;
|
|
}
|
|
nearestSquared(x, y, maxDistance) {
|
|
const { bboxes } = this;
|
|
if (bboxes.union.distanceSquared(x, y) > maxDistance) {
|
|
return { nearest: void 0, distanceSquared: Infinity };
|
|
}
|
|
const { distanceSquared: distanceSquared2 } = nearestSquared(x, y, bboxes.components);
|
|
return { nearest: this, distanceSquared: distanceSquared2 };
|
|
}
|
|
};
|
|
var ErrorBarGroup = class extends module_support_exports.Group {
|
|
nearestSquared(x, y) {
|
|
const { nearest, distanceSquared: distanceSquared2 } = nearestSquaredInContainer(x, y, {
|
|
children: this.children()
|
|
});
|
|
if (nearest !== void 0 && !Number.isNaN(distanceSquared2)) {
|
|
return { datum: nearest.datum, distanceSquared: distanceSquared2 };
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/error-bar/errorBarProperties.ts
|
|
var ErrorBarCap = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarCap.prototype, "visible", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarCap.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarCap.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarCap.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarCap.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarCap.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarCap.prototype, "length", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarCap.prototype, "lengthRatio", 2);
|
|
var ErrorBarProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.visible = true;
|
|
this.stroke = "black";
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.cap = new ErrorBarCap();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "yLowerKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "yLowerName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "yUpperKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "yUpperName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "xLowerKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "xLowerName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "xUpperKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "xUpperName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "visible", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ErrorBarProperties.prototype, "cap", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/error-bar/errorBar.ts
|
|
var { fixNumericExtent: fixNumericExtent2, groupAccumulativeValueProperty: groupAccumulativeValueProperty2, valueProperty: valueProperty5 } = module_support_exports;
|
|
var ErrorBars = class _ErrorBars extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.properties = new ErrorBarProperties();
|
|
const series = ctx.series;
|
|
const { annotationGroup, annotationSelections } = series;
|
|
this.cartesianSeries = series;
|
|
this.groupNode = new ErrorBarGroup({
|
|
name: `${annotationGroup.id}-errorBars`
|
|
});
|
|
annotationGroup.appendChild(this.groupNode);
|
|
this.selection = module_support_exports.Selection.select(this.groupNode, () => this.errorBarFactory());
|
|
annotationSelections.add(this.selection);
|
|
series.addEventListener("seriesVisibilityChange", (e) => this.onToggleSeriesItem(e));
|
|
this.cleanup.register(
|
|
series.events.on("data-processed", (e) => this.onDataProcessed(e)),
|
|
series.events.on("data-update", (e) => this.onDataUpdate(e)),
|
|
ctx.eventsHub.on("highlight:change", (event) => this.onHighlightChange(event)),
|
|
() => this.groupNode.remove(),
|
|
() => annotationSelections.delete(this.selection)
|
|
);
|
|
}
|
|
hasErrorBars() {
|
|
const { xLowerKey, xUpperKey, yLowerKey, yUpperKey } = this.properties;
|
|
return isDefined(xLowerKey) && isDefined(xUpperKey) || isDefined(yLowerKey) && isDefined(yUpperKey);
|
|
}
|
|
isStacked() {
|
|
const stackCount = this.cartesianSeries.seriesGrouping?.stackCount;
|
|
return stackCount == null ? false : stackCount > 0;
|
|
}
|
|
getUnstackPropertyDefinition(opts) {
|
|
const props = [];
|
|
const { xLowerKey, xUpperKey, yLowerKey, yUpperKey, xErrorsID, yErrorsID } = this.getMaybeFlippedKeys();
|
|
const { xScaleType, yScaleType } = opts;
|
|
if (yLowerKey != null && yUpperKey != null) {
|
|
props.push(
|
|
valueProperty5(yLowerKey, yScaleType, { id: `${yErrorsID}-lower` }),
|
|
valueProperty5(yUpperKey, yScaleType, { id: `${yErrorsID}-upper` })
|
|
);
|
|
}
|
|
if (xLowerKey != null && xUpperKey != null) {
|
|
props.push(
|
|
valueProperty5(xLowerKey, xScaleType, { id: `${xErrorsID}-lower` }),
|
|
valueProperty5(xUpperKey, xScaleType, { id: `${xErrorsID}-upper` })
|
|
);
|
|
}
|
|
return props;
|
|
}
|
|
getStackPropertyDefinition(opts) {
|
|
const props = [];
|
|
const { cartesianSeries } = this;
|
|
const { xLowerKey, xUpperKey, yLowerKey, yUpperKey, xErrorsID, yErrorsID } = this.getMaybeFlippedKeys();
|
|
const { xScaleType, yScaleType } = opts;
|
|
const groupIndex = cartesianSeries.seriesGrouping?.groupIndex ?? cartesianSeries.id;
|
|
const groupOpts = {
|
|
invalidValue: null,
|
|
missingValue: 0,
|
|
separateNegative: true,
|
|
...cartesianSeries.visible ? {} : { forceValue: 0 }
|
|
};
|
|
const makeErrorProperty = (key, id, type, scaleType) => {
|
|
return groupAccumulativeValueProperty2(
|
|
key,
|
|
"normal",
|
|
{
|
|
id: `${id}-${type}`,
|
|
groupId: `errorGroup-${groupIndex}-${type}`,
|
|
...groupOpts
|
|
},
|
|
scaleType
|
|
);
|
|
};
|
|
const pushErrorProperties = (lowerKey, upperKey, id, scaleType) => {
|
|
props.push(
|
|
...makeErrorProperty(lowerKey, id, "lower", scaleType),
|
|
...makeErrorProperty(upperKey, id, "upper", scaleType)
|
|
);
|
|
};
|
|
if (yLowerKey != null && yUpperKey != null) {
|
|
pushErrorProperties(yLowerKey, yUpperKey, yErrorsID, yScaleType);
|
|
}
|
|
if (xLowerKey != null && xUpperKey != null) {
|
|
pushErrorProperties(xLowerKey, xUpperKey, xErrorsID, xScaleType);
|
|
}
|
|
return props;
|
|
}
|
|
getPropertyDefinitions(opts) {
|
|
if (this.isStacked()) {
|
|
return this.getStackPropertyDefinition(opts);
|
|
} else {
|
|
return this.getUnstackPropertyDefinition(opts);
|
|
}
|
|
}
|
|
onDataProcessed(event) {
|
|
this.dataModel = event.dataModel;
|
|
this.processedData = event.processedData;
|
|
}
|
|
getDomain(direction) {
|
|
const { xLowerKey, xUpperKey, xErrorsID, yLowerKey, yUpperKey, yErrorsID } = this.getMaybeFlippedKeys();
|
|
const hasAxisErrors = direction === "x" /* X */ ? isDefined(xLowerKey) && isDefined(xUpperKey) : isDefined(yLowerKey) && isDefined(yUpperKey);
|
|
if (hasAxisErrors) {
|
|
const { dataModel, processedData, cartesianSeries: series } = this;
|
|
if (dataModel != null && processedData != null) {
|
|
const id = { x: xErrorsID, y: yErrorsID }[direction];
|
|
const lowerDomain = dataModel.getDomain(series, `${id}-lower`, "value", processedData).domain;
|
|
const upperDomain = dataModel.getDomain(series, `${id}-upper`, "value", processedData).domain;
|
|
const domain = [Math.min(...lowerDomain, ...upperDomain), Math.max(...lowerDomain, ...upperDomain)];
|
|
return fixNumericExtent2(domain);
|
|
}
|
|
}
|
|
return [];
|
|
}
|
|
onDataUpdate(event) {
|
|
this.dataModel = event.dataModel;
|
|
this.processedData = event.processedData;
|
|
if (isDefined(event.dataModel) && isDefined(event.processedData)) {
|
|
this.createNodeData();
|
|
this.update();
|
|
}
|
|
}
|
|
getNodeData() {
|
|
return this.hasErrorBars() ? this.cartesianSeries.contextNodeData?.nodeData : void 0;
|
|
}
|
|
createNodeData() {
|
|
const nodeData = this.getNodeData();
|
|
const xScale = this.cartesianSeries.axes["x" /* X */]?.scale;
|
|
const yScale = this.cartesianSeries.axes["y" /* Y */]?.scale;
|
|
if (!xScale || !yScale || !nodeData) {
|
|
return;
|
|
}
|
|
for (let i = 0; i < nodeData.length; i++) {
|
|
const { midPoint, xLower, xUpper, yLower, yUpper } = this.getDatum(nodeData, i);
|
|
if (midPoint != null) {
|
|
let xBar, yBar;
|
|
if (isDefined(xLower) && isDefined(xUpper)) {
|
|
xBar = {
|
|
lowerPoint: { x: this.convert(xScale, xLower), y: midPoint.y },
|
|
upperPoint: { x: this.convert(xScale, xUpper), y: midPoint.y }
|
|
};
|
|
}
|
|
if (isDefined(yLower) && isDefined(yUpper)) {
|
|
yBar = {
|
|
lowerPoint: { x: midPoint.x, y: this.convert(yScale, yLower) },
|
|
upperPoint: { x: midPoint.x, y: this.convert(yScale, yUpper) }
|
|
};
|
|
}
|
|
nodeData[i].xBar = xBar;
|
|
nodeData[i].yBar = yBar;
|
|
}
|
|
}
|
|
}
|
|
getMaybeFlippedKeys() {
|
|
let { xLowerKey, xUpperKey, yLowerKey, yUpperKey } = this.properties;
|
|
let [xErrorsID, yErrorsID] = ["xValue-errors", "yValue-errors"];
|
|
if (this.cartesianSeries.shouldFlipXY()) {
|
|
[xLowerKey, yLowerKey] = [yLowerKey, xLowerKey];
|
|
[xUpperKey, yUpperKey] = [yUpperKey, xUpperKey];
|
|
[xErrorsID, yErrorsID] = [yErrorsID, xErrorsID];
|
|
}
|
|
return { xLowerKey, xUpperKey, xErrorsID, yLowerKey, yUpperKey, yErrorsID };
|
|
}
|
|
static getDatumKey(nodeDatum, key, offset) {
|
|
if (key == null) {
|
|
return;
|
|
}
|
|
const datum = readDatum(nodeDatum);
|
|
const value = datum?.[key];
|
|
if (value == null) {
|
|
return;
|
|
}
|
|
if (typeof value !== "number") {
|
|
logger_exports.warnOnce(`Found [${key}] error value of type ${typeof value}. Expected number type`);
|
|
return;
|
|
}
|
|
return value + offset;
|
|
}
|
|
getDatum(nodeData, datumIndex) {
|
|
const { xLowerKey, xUpperKey, yLowerKey, yUpperKey } = this.getMaybeFlippedKeys();
|
|
const datum = nodeData[datumIndex];
|
|
const d = datum.cumulativeValue == null || !this.isStacked() ? 0 : datum.cumulativeValue - datum.yValue;
|
|
const [xOffset, yOffset] = this.cartesianSeries.shouldFlipXY() ? [d, 0] : [0, d];
|
|
return {
|
|
midPoint: datum.midPoint,
|
|
xLower: _ErrorBars.getDatumKey(datum, xLowerKey, xOffset),
|
|
xUpper: _ErrorBars.getDatumKey(datum, xUpperKey, xOffset),
|
|
yLower: _ErrorBars.getDatumKey(datum, yLowerKey, yOffset),
|
|
yUpper: _ErrorBars.getDatumKey(datum, yUpperKey, yOffset)
|
|
};
|
|
}
|
|
convert(scale2, value) {
|
|
const offset = (scale2.bandwidth ?? 0) / 2;
|
|
return scale2.convert(value) + offset;
|
|
}
|
|
update() {
|
|
const nodeData = this.getNodeData();
|
|
if (nodeData != null) {
|
|
this.selection.update(nodeData);
|
|
this.selection.each((node, datum, i) => this.updateNode(node, datum, i));
|
|
}
|
|
}
|
|
updateNode(node, datum, _index) {
|
|
node.datum = datum;
|
|
node.update(this.getDefaultStyle(), this.properties, this.cartesianSeries, false, "none");
|
|
node.updateBBoxes();
|
|
}
|
|
pickNodeExact(point) {
|
|
const { x, y } = point;
|
|
const node = this.groupNode.pickNode(x, y);
|
|
if (node != null) {
|
|
return { datum: node.datum, distanceSquared: 0 };
|
|
}
|
|
}
|
|
pickNodeNearest(point) {
|
|
return this.groupNode.nearestSquared(point.x, point.y);
|
|
}
|
|
pickNodeMainAxisFirst(point, majorDirection) {
|
|
let closestDatum;
|
|
let closestDistance = [Infinity, Infinity];
|
|
const referencePoints = [point.x, point.y];
|
|
if (majorDirection === "y" /* Y */) {
|
|
referencePoints.reverse();
|
|
}
|
|
for (const child of this.groupNode.children()) {
|
|
const childBBox = child.getBBox();
|
|
const childReferencePoints = [childBBox.x + childBBox.width / 2, childBBox.y + childBBox.height / 2];
|
|
if (majorDirection === "y" /* Y */) {
|
|
childReferencePoints.reverse();
|
|
}
|
|
const childDistances = [];
|
|
for (let i = 0; i < referencePoints.length; i++) {
|
|
childDistances.push(Math.abs(referencePoints[i] - childReferencePoints[i]));
|
|
}
|
|
if (childDistances[0] < closestDistance[0] || childDistances[0] == closestDistance[0] && childDistances[1] < closestDistance[1]) {
|
|
closestDatum = child.datum;
|
|
closestDistance = childDistances;
|
|
}
|
|
}
|
|
if (closestDatum) {
|
|
return {
|
|
datum: closestDatum,
|
|
distanceSquared: Math.pow(closestDistance[0], 2) + Math.pow(closestDistance[1], 2)
|
|
};
|
|
}
|
|
}
|
|
getTooltipParams() {
|
|
const {
|
|
xLowerKey,
|
|
xUpperKey,
|
|
yLowerKey,
|
|
yUpperKey,
|
|
xLowerName = xLowerKey,
|
|
xUpperName = xUpperKey,
|
|
yLowerName = yLowerKey,
|
|
yUpperName = yUpperKey
|
|
} = this.properties;
|
|
return { xLowerKey, xLowerName, xUpperKey, xUpperName, yLowerKey, yLowerName, yUpperKey, yUpperName };
|
|
}
|
|
onToggleSeriesItem(event) {
|
|
this.groupNode.visible = event.visible;
|
|
}
|
|
makeStyle(baseStyle) {
|
|
return {
|
|
visible: baseStyle.visible,
|
|
lineDash: baseStyle.lineDash,
|
|
lineDashOffset: baseStyle.lineDashOffset,
|
|
stroke: baseStyle.stroke,
|
|
strokeWidth: baseStyle.strokeWidth,
|
|
strokeOpacity: baseStyle.strokeOpacity,
|
|
cap: mergeDefaults(this.properties.cap, baseStyle)
|
|
};
|
|
}
|
|
getDefaultStyle() {
|
|
return this.makeStyle(this.getWhiskerProperties());
|
|
}
|
|
getHighlightStyle() {
|
|
return this.makeStyle(this.getWhiskerProperties());
|
|
}
|
|
restyleHighlightChange(highlightChange, style2, highlighted) {
|
|
const nodeData = this.getNodeData();
|
|
if (nodeData == null)
|
|
return;
|
|
for (let i = 0; i < nodeData.length; i++) {
|
|
if (highlightChange === nodeData[i]) {
|
|
this.selection.at(i)?.update(
|
|
style2,
|
|
this.properties,
|
|
this.cartesianSeries,
|
|
highlighted,
|
|
highlighted ? "highlighted-item" : "unhighlighted-item"
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
onHighlightChange(event) {
|
|
const { previousHighlight, currentHighlight } = event;
|
|
if (currentHighlight?.series === this.cartesianSeries) {
|
|
this.restyleHighlightChange(currentHighlight, this.getHighlightStyle(), true);
|
|
}
|
|
if (previousHighlight?.series === this.cartesianSeries) {
|
|
this.restyleHighlightChange(previousHighlight, this.getDefaultStyle(), false);
|
|
}
|
|
this.groupNode.opacity = this.cartesianSeries.getOpacity();
|
|
}
|
|
errorBarFactory() {
|
|
return new ErrorBarNode();
|
|
}
|
|
getWhiskerProperties() {
|
|
const { stroke: stroke3, strokeWidth, visible, strokeOpacity, lineDash, lineDashOffset } = this.properties;
|
|
return { stroke: stroke3, strokeWidth, visible, strokeOpacity, lineDash, lineDashOffset };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/error-bar/errorBarModule.ts
|
|
var ErrorBarsModule = {
|
|
type: "series:plugin",
|
|
name: "errorBar",
|
|
chartType: "cartesian",
|
|
seriesTypes: ["bar", "line", "scatter"],
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: errorBarOptionsDefs,
|
|
themeTemplate: {
|
|
visible: true,
|
|
stroke: { $ref: "foregroundColor" },
|
|
strokeWidth: 1,
|
|
strokeOpacity: 1,
|
|
cap: {
|
|
lengthRatio: {
|
|
$if: [{ $eq: [{ $path: "../../type" }, "bar"] }, 0.3, 1]
|
|
}
|
|
}
|
|
},
|
|
create: (ctx) => new ErrorBars(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/flash-on-update/flashOnUpdate.ts
|
|
function findPrimaryCategoryAxisContext(ctx) {
|
|
for (const dir of ["x" /* X */, "y" /* Y */]) {
|
|
for (const axisCtx of ctx.axisManager.getAxisContext(dir)) {
|
|
if (module_support_exports.BandScale.is(axisCtx.scale)) {
|
|
return axisCtx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var FlashOnUpdate = class extends BaseProperties {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.enabled = false;
|
|
this.item = "chart";
|
|
this.color = "#cfeeff";
|
|
this.opacity = 1;
|
|
this.flashDuration = 100;
|
|
this.fadeDuration = 900;
|
|
this.cleanup = new CleanupRegistry();
|
|
this.element = this.ctx.domManager.addChild("canvas-background", "flashOnUpdate");
|
|
this.element.role = "presentation";
|
|
let firstUpdate = true;
|
|
const onDataUpdate = (ev) => {
|
|
if (firstUpdate) {
|
|
firstUpdate = false;
|
|
} else {
|
|
this.onDataUpdate(ev);
|
|
}
|
|
};
|
|
this.cleanup.register(
|
|
this.ctx.eventsHub.on("data:update", onDataUpdate),
|
|
this.ctx.eventsHub.on("datamodel:diff", (e) => this.onDataModelDiff(e))
|
|
);
|
|
}
|
|
destroy() {
|
|
this.ctx.domManager.removeChild("canvas-background", "flashOnUpdate");
|
|
this.cleanup.flush();
|
|
}
|
|
clearFlash() {
|
|
this.element.innerHTML = "";
|
|
clearTimeout(this.animationTimeout);
|
|
this.animationTimeout = void 0;
|
|
}
|
|
flashElem(el) {
|
|
const { flashDuration, fadeDuration } = this;
|
|
const duration = flashDuration + fadeDuration;
|
|
el.animate(
|
|
[
|
|
{ background: this.color, offset: 0 },
|
|
{ background: this.color, offset: flashDuration / duration },
|
|
{ background: "transparent", offset: 1 }
|
|
],
|
|
{ duration, easing: "ease-out" }
|
|
);
|
|
}
|
|
flashCategoryBands(diff9) {
|
|
const axisCtx = findPrimaryCategoryAxisContext(this.ctx);
|
|
if (!axisCtx)
|
|
return;
|
|
this.clearFlash();
|
|
const flashBounds = this.computeCategoryFlashBounds(axisCtx, diff9);
|
|
for (const bounds of flashBounds) {
|
|
const e = createElement("div");
|
|
setAttribute(e, "role", "presentation");
|
|
setElementStyle(e, "position", "absolute");
|
|
setElementBBox(e, bounds);
|
|
this.element.appendChild(e);
|
|
this.flashElem(e);
|
|
}
|
|
const duration = this.flashDuration + this.fadeDuration;
|
|
this.animationTimeout = setTimeout(() => this.clearFlash(), duration);
|
|
}
|
|
computeCategories(diff9) {
|
|
const result = /* @__PURE__ */ new Set();
|
|
for (const seriesId of Object.keys(diff9)) {
|
|
for (const key of ["updated", "added", "moved"]) {
|
|
for (const value of diff9[seriesId][key]) {
|
|
result.add(value);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
computeCategoryFlashBounds(axisCtx, diff9) {
|
|
const seriesBounds = this.ctx.widgets.seriesWidget.getBounds();
|
|
const makeBox = axisCtx.direction === "x" /* X */ ? ([start2, end3]) => {
|
|
return {
|
|
x: seriesBounds.x + start2,
|
|
y: seriesBounds.y,
|
|
width: end3 - start2,
|
|
height: seriesBounds.height
|
|
};
|
|
} : ([start2, end3]) => {
|
|
return {
|
|
x: seriesBounds.x,
|
|
y: seriesBounds.y + start2,
|
|
width: seriesBounds.width,
|
|
height: end3 - start2
|
|
};
|
|
};
|
|
const result = [];
|
|
const categories = this.computeCategories(diff9);
|
|
for (const c of categories) {
|
|
const measurements = axisCtx.measureBand(c);
|
|
if (measurements?.band) {
|
|
result.push(makeBox(measurements.band));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
onDataUpdate(ev) {
|
|
if (!this.enabled || this.item !== "chart" || !ev)
|
|
return;
|
|
this.flashElem(this.ctx.widgets.containerWidget.getElement());
|
|
}
|
|
onDataModelDiff(ev) {
|
|
if (!this.enabled || this.item !== "category")
|
|
return;
|
|
this.flashCategoryBands(ev.diff);
|
|
}
|
|
};
|
|
FlashOnUpdate.className = "FlashOnUpdate";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FlashOnUpdate.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FlashOnUpdate.prototype, "item", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FlashOnUpdate.prototype, "color", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FlashOnUpdate.prototype, "opacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FlashOnUpdate.prototype, "flashDuration", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FlashOnUpdate.prototype, "fadeDuration", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/flash-on-update/flashOnUpdateModule.ts
|
|
var FlashOnUpdateModule = {
|
|
type: "plugin",
|
|
name: "flashOnUpdate",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: {
|
|
enabled: boolean,
|
|
item: strictUnion()("chart", "category"),
|
|
color,
|
|
opacity: ratio,
|
|
flashDuration: positiveNumber,
|
|
fadeDuration: positiveNumber
|
|
},
|
|
themeTemplate: {
|
|
enabled: false,
|
|
item: "chart",
|
|
color: "#cfeeff",
|
|
opacity: 1,
|
|
flashDuration: 100,
|
|
fadeDuration: 900
|
|
},
|
|
create: (ctx) => new FlashOnUpdate(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/navigator/shapes/miniChartGroup.ts
|
|
var { TranslatableGroup: TranslatableGroup4 } = module_support_exports;
|
|
var MiniChartGroup = class extends TranslatableGroup4 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.inset = 0;
|
|
this.cornerRadius = 0;
|
|
}
|
|
applyClip(ctx, clipRect) {
|
|
const { cornerRadius, inset } = this;
|
|
const { x, y, width: width2, height: height2 } = clipRect;
|
|
const Path2DCtor = getPath2D();
|
|
const path = new Path2DCtor();
|
|
path.roundRect(x + inset, y + inset, width2 - 2 * inset, height2 - 2 * inset, cornerRadius);
|
|
ctx.clip(path);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], MiniChartGroup.prototype, "inset", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], MiniChartGroup.prototype, "cornerRadius", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/navigator/miniChart.ts
|
|
var { CategoryAxis: CategoryAxis2, Group: Group9, BBox: BBox14, stackCartesianSeries: stackCartesianSeries2 } = module_support_exports;
|
|
var MiniChartPadding = class {
|
|
constructor() {
|
|
this.top = 0;
|
|
this.bottom = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MiniChartPadding.prototype, "top", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MiniChartPadding.prototype, "bottom", 2);
|
|
var MiniChart = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.enabled = false;
|
|
this.padding = new MiniChartPadding();
|
|
this.root = new Group9({ name: "root" });
|
|
this.seriesRoot = this.root.appendChild(
|
|
new MiniChartGroup({ name: "Series-root", zIndex: 7 /* SERIES_LAYER */, renderToOffscreenCanvas: true })
|
|
);
|
|
this.axisGridGroup = this.root.appendChild(new Group9({ name: "Axes-Grids", zIndex: 2 /* AXIS_GRID */ }));
|
|
this.axisGroup = this.root.appendChild(new Group9({ name: "Axes-Grids", zIndex: 2 /* AXIS_GRID */ }));
|
|
this.axisLabelGroup = this.root.appendChild(new Group9({ name: "Axes-Labels", zIndex: 15 /* SERIES_LABEL */ }));
|
|
this.axisCrosslineRangeGroup = this.root.appendChild(
|
|
new Group9({ name: "Axes-Crosslines-Range", zIndex: 6 /* SERIES_CROSSLINE_RANGE */ })
|
|
);
|
|
this.axisCrosslineLineGroup = this.root.appendChild(
|
|
new Group9({ name: "Axes-Crosslines-Line", zIndex: 10 /* SERIES_CROSSLINE_LINE */ })
|
|
);
|
|
this.axisCrosslineLabelGroup = this.root.appendChild(
|
|
new Group9({ name: "Axes-Crosslines-Label", zIndex: 15 /* SERIES_LABEL */ })
|
|
);
|
|
this.data = [];
|
|
this._destroyed = false;
|
|
this.miniChartAnimationPhase = "initial";
|
|
// Should be available after the first layout.
|
|
this.seriesRect = void 0;
|
|
this.axes = new module_support_exports.ChartAxes();
|
|
this.series = [];
|
|
this.cleanup.register(this.ctx.eventsHub.on("data:update", (data) => this.updateData(data)));
|
|
}
|
|
destroy() {
|
|
if (this._destroyed) {
|
|
return;
|
|
}
|
|
super.destroy();
|
|
this.destroySeries(this.series);
|
|
this.axes.destroy();
|
|
this._destroyed = true;
|
|
}
|
|
onSeriesChange(newValue, oldValue) {
|
|
const seriesToDestroy = oldValue?.filter((series) => !newValue.includes(series)) ?? [];
|
|
this.destroySeries(seriesToDestroy);
|
|
for (const series of newValue) {
|
|
if (oldValue?.includes(series))
|
|
continue;
|
|
series.attachSeries(this.seriesRoot, this.seriesRoot, void 0);
|
|
series.chart = {};
|
|
Object.defineProperty(series.chart, "mode", {
|
|
get: () => "standalone"
|
|
});
|
|
Object.defineProperty(series.chart, "isMiniChart", {
|
|
get: () => true
|
|
});
|
|
Object.defineProperty(series.chart, "flashOnUpdateEnabled", {
|
|
get: () => false
|
|
});
|
|
Object.defineProperty(series.chart, "seriesRect", {
|
|
get: () => this.seriesRect
|
|
});
|
|
series.resetAnimation(this.miniChartAnimationPhase === "initial" ? "initial" : "disabled");
|
|
}
|
|
this.seriesRect = void 0;
|
|
}
|
|
destroySeries(allSeries) {
|
|
if (allSeries) {
|
|
for (const series of allSeries) {
|
|
series.destroy();
|
|
series.detachSeries(this.seriesRoot, this.seriesRoot, void 0);
|
|
series.chart = void 0;
|
|
}
|
|
}
|
|
}
|
|
assignSeriesToAxes() {
|
|
for (const axis of this.axes) {
|
|
axis.boundSeries = this.series.filter((s) => {
|
|
const seriesAxis = s.axes[axis.direction];
|
|
return seriesAxis === axis;
|
|
});
|
|
}
|
|
}
|
|
assignAxesToSeries() {
|
|
const directionToAxesMap = {};
|
|
for (const axis of this.axes) {
|
|
const direction = axis.direction;
|
|
const directionAxes = directionToAxesMap[direction] ?? (directionToAxesMap[direction] = []);
|
|
directionAxes.push(axis);
|
|
}
|
|
for (const series of this.series) {
|
|
for (const direction of series.directions) {
|
|
const seriesAxisId = series.getKeyAxis(direction) ?? direction;
|
|
const newAxis = this.axes.findById(seriesAxisId);
|
|
if (!newAxis) {
|
|
logger_exports.warnOnce(
|
|
`no matching axis for direction [${direction}] and id [${seriesAxisId}]; check series and axes configuration.`
|
|
);
|
|
return;
|
|
}
|
|
series.axes[direction] = newAxis;
|
|
}
|
|
}
|
|
}
|
|
updateData(data) {
|
|
for (const s of this.series) {
|
|
s.setChartData(data);
|
|
}
|
|
if (this.miniChartAnimationPhase === "initial") {
|
|
this.ctx.animationManager.onBatchStop(() => {
|
|
this.miniChartAnimationPhase = "ready";
|
|
for (const s of this.series) {
|
|
s.resetAnimation("disabled");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
async processData(dataController) {
|
|
if (this.series.some((s) => s.canHaveAxes)) {
|
|
this.assignAxesToSeries();
|
|
this.assignSeriesToAxes();
|
|
}
|
|
await Promise.all(
|
|
this.series.map(async (s) => {
|
|
s.resetDatumCallbackCache();
|
|
return s.processData(dataController);
|
|
})
|
|
);
|
|
for (const axis of this.axes) {
|
|
axis.processData();
|
|
}
|
|
}
|
|
computeAxisPadding() {
|
|
const padding2 = new Padding();
|
|
if (!this.enabled) {
|
|
return padding2;
|
|
}
|
|
for (const { position, thickness, line, label } of this.axes) {
|
|
if (position == null)
|
|
continue;
|
|
let size;
|
|
if (thickness) {
|
|
size = thickness;
|
|
} else {
|
|
size = (line.enabled ? line.width : 0) + (label.enabled ? calcLineHeight(label.fontSize ?? 0) + label.spacing : 0);
|
|
}
|
|
padding2[position] = Math.ceil(size);
|
|
}
|
|
return padding2;
|
|
}
|
|
async layout(width2, height2) {
|
|
var _a;
|
|
const { padding: padding2 } = this;
|
|
const animated = this.seriesRect != null;
|
|
const seriesRect = new BBox14(0, 0, width2, height2 - (padding2.top + padding2.bottom));
|
|
const resized = this.seriesRect?.width !== width2 || this.seriesRect?.height !== height2;
|
|
this.seriesRect = seriesRect;
|
|
this.seriesRoot.translationY = padding2.top;
|
|
this.seriesRoot.setClipRectCanvasSpace(new BBox14(0, -padding2.top, width2, height2));
|
|
for (const axis of this.axes) {
|
|
const { position = "left" } = axis;
|
|
switch (position) {
|
|
case "top":
|
|
case "bottom":
|
|
axis.range = [0, seriesRect.width];
|
|
axis.gridLength = seriesRect.height;
|
|
break;
|
|
case "right":
|
|
case "left": {
|
|
const isCategoryAxis = axis instanceof CategoryAxis2;
|
|
axis.range = isCategoryAxis ? [0, seriesRect.height] : [seriesRect.height, 0];
|
|
axis.gridLength = seriesRect.width;
|
|
break;
|
|
}
|
|
}
|
|
axis.gridPadding = 0;
|
|
axis.translation.x = 0;
|
|
axis.translation.y = 0;
|
|
if (position === "right") {
|
|
axis.translation.x = width2;
|
|
} else if (position === "bottom") {
|
|
axis.translation.y = height2;
|
|
}
|
|
if (!animated) {
|
|
axis.resetAnimation("initial");
|
|
}
|
|
if (axis.crossLines) {
|
|
for (const crossLine of axis.crossLines) {
|
|
if (crossLine instanceof module_support_exports.CartesianCrossLine) {
|
|
crossLine.position = axis.position ?? "top";
|
|
(_a = crossLine.label).parallel ?? (_a.parallel = axis.label?.parallel);
|
|
}
|
|
}
|
|
}
|
|
axis.calculateLayout();
|
|
axis.update();
|
|
}
|
|
if (resized) {
|
|
stackCartesianSeries2(this.series);
|
|
}
|
|
await Promise.all(this.series.map(async (series) => series.update({ seriesRect })));
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MiniChart.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
ProxyProperty(["seriesRoot", "inset"])
|
|
], MiniChart.prototype, "inset", 2);
|
|
__decorateClass([
|
|
ProxyProperty(["seriesRoot", "cornerRadius"])
|
|
], MiniChart.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue(newValue, oldValue = new module_support_exports.ChartAxes()) {
|
|
const axisNodes = {
|
|
axisNode: this.axisGroup,
|
|
gridNode: this.axisGridGroup,
|
|
labelNode: this.axisLabelGroup,
|
|
crossLineLineNode: this.axisCrosslineLineGroup,
|
|
crossLineRangeNode: this.axisCrosslineRangeGroup,
|
|
crossLineLabelNode: this.axisCrosslineLabelGroup
|
|
};
|
|
for (const axis of oldValue) {
|
|
if (newValue.includes(axis))
|
|
continue;
|
|
axis.detachAxis();
|
|
axis.destroy();
|
|
}
|
|
for (const axis of newValue) {
|
|
if (oldValue?.includes(axis))
|
|
continue;
|
|
axis.attachAxis(axisNodes);
|
|
}
|
|
}
|
|
})
|
|
], MiniChart.prototype, "axes", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue(newValue, oldValue) {
|
|
this.onSeriesChange(newValue, oldValue);
|
|
}
|
|
})
|
|
], MiniChart.prototype, "series", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/navigator/navigatorDOMProxy.ts
|
|
var { SliderWidget: SliderWidget2 } = module_support_exports;
|
|
var NavigatorDOMProxy = class {
|
|
constructor(ctx, sliderHandlers) {
|
|
this.ctx = ctx;
|
|
this.sliderHandlers = sliderHandlers;
|
|
this._min = 0;
|
|
this._max = 1;
|
|
this.minRange = 1e-3;
|
|
this.dragStartX = 0;
|
|
this.ctx = ctx;
|
|
this.toolbar = ctx.proxyInteractionService.createProxyContainer({
|
|
type: "toolbar",
|
|
domManagerId: `navigator-toolbar`,
|
|
classList: ["ag-charts-proxy-navigator-toolbar"],
|
|
orientation: "vertical",
|
|
ariaLabel: { id: "ariaLabelNavigator" }
|
|
});
|
|
this.sliders = [
|
|
ctx.proxyInteractionService.createProxyElement({
|
|
type: "slider",
|
|
domIndex: 1,
|
|
ariaLabel: { id: "ariaLabelNavigatorMinimum" },
|
|
parent: this.toolbar,
|
|
cursor: "ew-resize"
|
|
}),
|
|
ctx.proxyInteractionService.createProxyElement({
|
|
type: "slider",
|
|
domIndex: -Infinity,
|
|
ariaLabel: { id: "ariaLabelNavigatorRange" },
|
|
parent: this.toolbar,
|
|
cursor: "grab"
|
|
}),
|
|
ctx.proxyInteractionService.createProxyElement({
|
|
type: "slider",
|
|
domIndex: 2,
|
|
ariaLabel: { id: "ariaLabelNavigatorMaximum" },
|
|
parent: this.toolbar,
|
|
cursor: "ew-resize"
|
|
})
|
|
];
|
|
for (const [index, key] of ["min", "pan", "max"].entries()) {
|
|
const slider = this.sliders[index];
|
|
slider.step = SliderWidget2.STEP_HUNDRETH;
|
|
slider.keyboardStep = SliderWidget2.STEP_ONE;
|
|
slider.orientation = "horizontal";
|
|
slider.setPreventsDefault(false);
|
|
slider.addListener("drag-start", (ev) => this.onDragStart(index, ev, key));
|
|
slider.addListener("drag-move", (ev) => this.onDrag(slider, ev, key));
|
|
slider.addListener("drag-end", () => this.updateSliderRatios());
|
|
slider.addListener("contextmenu", (ev) => this.onContextMenu(slider, ev));
|
|
}
|
|
this.sliders[0].addListener("change", () => this.onMinSliderChange());
|
|
this.sliders[1].addListener("change", () => this.onPanSliderChange());
|
|
this.sliders[2].addListener("change", () => this.onMaxSliderChange());
|
|
this.updateSliderRatios();
|
|
this.updateVisibility(false);
|
|
}
|
|
destroy() {
|
|
this.toolbar.destroy();
|
|
}
|
|
updateVisibility(visible) {
|
|
this.toolbar.setHidden(!visible);
|
|
}
|
|
updateZoom() {
|
|
const { _min: min, _max: max } = this;
|
|
if (min == null || max == null)
|
|
return;
|
|
this.ctx.zoomManager.updateZoom(
|
|
{ source: "user-interaction", sourceDetail: "navigatorDOM" },
|
|
{ x: { min, max } }
|
|
);
|
|
}
|
|
updateBounds(bounds) {
|
|
this.toolbar.setBounds(bounds);
|
|
}
|
|
updateSliderBounds(sliderIndex, bounds) {
|
|
this.sliders[sliderIndex].setBounds(bounds);
|
|
}
|
|
updateMinMax(min, max) {
|
|
this._min = min;
|
|
this._max = max;
|
|
this.updateSliderRatios();
|
|
}
|
|
updateSliderRatios() {
|
|
let { _min: min, _max: max } = this;
|
|
min = Math.round(min * 100) / 100;
|
|
max = Math.round(max * 100) / 100;
|
|
const panAria = this.ctx.localeManager.t("ariaValuePanRange", { min, max });
|
|
this.sliders[0].setValueRatio(min);
|
|
this.sliders[1].setValueRatio(min, { ariaValueText: panAria });
|
|
this.sliders[2].setValueRatio(max);
|
|
}
|
|
toCanvasOffsets(event) {
|
|
return { offsetX: this.dragStartX + event.originDeltaX };
|
|
}
|
|
moveToFront(index) {
|
|
if (index === 1)
|
|
return;
|
|
const frontSlider = this.sliders[index];
|
|
const otherSlider = this.sliders[2 - index];
|
|
this.toolbar.moveChild(otherSlider, frontSlider.domIndex - 1);
|
|
}
|
|
onDragStart(index, event, key) {
|
|
const slider = this.sliders[index];
|
|
const toolbarLeft = this.toolbar.cssLeft();
|
|
const sliderLeft = slider.cssLeft();
|
|
this.dragStartX = toolbarLeft + sliderLeft + event.offsetX;
|
|
this.moveToFront(index);
|
|
event.sourceEvent.preventDefault();
|
|
this.sliderHandlers.onDragStart(key, this.toCanvasOffsets(event));
|
|
}
|
|
onDrag(_slider, event, key) {
|
|
event.sourceEvent.preventDefault();
|
|
this.sliderHandlers.onDrag(key, this.toCanvasOffsets(event));
|
|
}
|
|
onContextMenu(slider, widgetEvent) {
|
|
const { offsetX, offsetY } = widgetEvent;
|
|
const { x: toolbarX, y: toolbarY } = this.toolbar.getBounds();
|
|
const { x: sliderX, y: sliderY } = slider.getBounds();
|
|
const canvasX = offsetX + toolbarX + sliderX;
|
|
const canvasY = offsetY + toolbarY + sliderY;
|
|
this.ctx.contextMenuRegistry.dispatchContext("always", { widgetEvent, canvasX, canvasY }, void 0);
|
|
}
|
|
onPanSliderChange() {
|
|
const ratio2 = this.sliders[1].getValueRatio();
|
|
const span = this._max - this._min;
|
|
this._min = clamp(0, ratio2, 1 - span);
|
|
this._max = this._min + span;
|
|
this.updateZoom();
|
|
}
|
|
onMinSliderChange() {
|
|
this._min = this.sliders[0].clampValueRatio(0, this._max - this.minRange);
|
|
this.updateZoom();
|
|
}
|
|
onMaxSliderChange() {
|
|
this._max = this.sliders[2].clampValueRatio(this._min + this.minRange, 1);
|
|
this.updateZoom();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/navigator/shapes/rangeHandle.ts
|
|
var { BBox: BBox15, ExtendedPath2D: ExtendedPath2D2 } = module_support_exports;
|
|
var RangeHandle = class extends module_support_exports.Path {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.zIndex = 3;
|
|
this.centerX = 0;
|
|
this.centerY = 0;
|
|
this.width = 8;
|
|
this.height = 16;
|
|
this.cornerRadius = 4;
|
|
this.grip = true;
|
|
this.gripPath = new ExtendedPath2D2();
|
|
}
|
|
setCenter(x, y) {
|
|
this.dirtyPath = true;
|
|
if (this.centerX !== x || this.centerY !== y) {
|
|
this.centerX = x;
|
|
this.centerY = y;
|
|
this.markDirty("center");
|
|
}
|
|
}
|
|
static align(minHandle, maxHandle, x, y, width2, height2, min, max, pixelAlign) {
|
|
const minHandleX = minHandle.align(x + width2 * min) + pixelAlign;
|
|
const maxHandleX = minHandleX + minHandle.align(x + width2 * min, width2 * (max - min)) - 2 * pixelAlign;
|
|
const handleY = minHandle.align(y + height2 / 2);
|
|
minHandle.setCenter(minHandleX, handleY);
|
|
maxHandle.setCenter(maxHandleX, handleY);
|
|
}
|
|
computeBBox() {
|
|
const { centerX, centerY, width: width2, height: height2 } = this;
|
|
const x = centerX - width2 / 2;
|
|
const y = centerY - height2 / 2;
|
|
return new BBox15(x, y, width2, height2);
|
|
}
|
|
isPointInPath(x, y) {
|
|
const bbox = this.getBBox();
|
|
return bbox.containsPoint(x, y);
|
|
}
|
|
updatePath() {
|
|
const { centerX, centerY, path, gripPath, strokeWidth, cornerRadius, grip } = this;
|
|
const pixelAlign = strokeWidth / 2;
|
|
const pixelRatio = this.layerManager?.canvas?.pixelRatio ?? 1;
|
|
path.clear();
|
|
gripPath.clear();
|
|
const halfWidth = Math.floor(this.width / 2 * pixelRatio) / pixelRatio;
|
|
const halfHeight = Math.floor(this.height / 2 * pixelRatio) / pixelRatio;
|
|
path.roundRect(
|
|
centerX - halfWidth + pixelAlign,
|
|
centerY - halfHeight + pixelAlign,
|
|
2 * (halfWidth - pixelAlign),
|
|
2 * (halfHeight - pixelAlign),
|
|
cornerRadius
|
|
);
|
|
const gripSpacing = 3;
|
|
if (grip) {
|
|
for (let x = -0.5; x <= 0.5; x += 1) {
|
|
for (let y = -1; y <= 1; y += 1) {
|
|
gripPath.arc(centerX + x * gripSpacing, centerY + y * gripSpacing, 1, 0, 2 * Math.PI);
|
|
gripPath.closePath();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
renderFill(ctx, path) {
|
|
const { stroke: stroke3 } = this;
|
|
super.renderFill(ctx, path);
|
|
ctx.fillStyle = typeof stroke3 === "string" ? stroke3 : "black";
|
|
ctx.fill(this.gripPath.getPath2D());
|
|
}
|
|
};
|
|
RangeHandle.className = "RangeHandle";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], RangeHandle.prototype, "width", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], RangeHandle.prototype, "height", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], RangeHandle.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], RangeHandle.prototype, "grip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/navigator/shapes/rangeMask.ts
|
|
var { Path: Path6, BBox: BBox16, ExtendedPath2D: ExtendedPath2D3, clippedRoundRect: clippedRoundRect2 } = module_support_exports;
|
|
var RangeMask = class extends Path6 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.cornerRadius = 4;
|
|
this.zIndex = 2;
|
|
this.x = 0;
|
|
this.y = 0;
|
|
this.width = 200;
|
|
this.height = 30;
|
|
this.min = 0;
|
|
this.max = 1;
|
|
this.visiblePath = new ExtendedPath2D3();
|
|
}
|
|
layout(x, y, width2, height2, min, max) {
|
|
min = Number.isNaN(min) ? this.min : min;
|
|
max = Number.isNaN(max) ? this.max : max;
|
|
if (x !== this.x || y !== this.y || width2 !== this.width || this.height !== height2 || min !== this.min || max !== this.max) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width2;
|
|
this.height = height2;
|
|
this.min = min;
|
|
this.max = max;
|
|
this.dirtyPath = true;
|
|
this.markDirty("RangeMask.layout");
|
|
}
|
|
}
|
|
computeBBox() {
|
|
const { x, y, width: width2, height: height2 } = this;
|
|
return new BBox16(x, y, width2, height2);
|
|
}
|
|
computeVisibleRangeBBox() {
|
|
const { x, y, width: width2, height: height2, min, max } = this;
|
|
const minX = x + width2 * min;
|
|
const maxX = x + width2 * max;
|
|
return new BBox16(minX, y, maxX - minX, height2);
|
|
}
|
|
updatePath() {
|
|
const { path, visiblePath, x, y, width: width2, height: height2, min, max, strokeWidth, cornerRadius } = this;
|
|
const pixelAlign = strokeWidth / 2;
|
|
path.clear();
|
|
visiblePath.clear();
|
|
const ax = this.align(x) + pixelAlign;
|
|
const ay = this.align(y) + pixelAlign;
|
|
const aw = this.align(x, width2) - 2 * pixelAlign;
|
|
const ah = this.align(y, height2) - 2 * pixelAlign;
|
|
const minX = this.align(x + width2 * min) + pixelAlign;
|
|
const maxX = minX + this.align(x + width2 * min, width2 * (max - min)) - 2 * pixelAlign;
|
|
const cornerRadiusParams = {
|
|
topLeft: cornerRadius,
|
|
topRight: cornerRadius,
|
|
bottomRight: cornerRadius,
|
|
bottomLeft: cornerRadius
|
|
};
|
|
const drawRect = (p, x0, x1) => {
|
|
if (x1 - x0 < 1)
|
|
return;
|
|
const bbox = new BBox16(x0, ay, x1 - x0, ah);
|
|
clippedRoundRect2(p, ax, ay, aw, ah, cornerRadiusParams, bbox);
|
|
};
|
|
drawRect(path, ax, minX);
|
|
drawRect(path, maxX, aw + ax);
|
|
drawRect(visiblePath, minX, maxX);
|
|
}
|
|
renderStroke(ctx, path) {
|
|
super.renderStroke(ctx, path);
|
|
super.renderStroke(ctx, this.visiblePath.getPath2D());
|
|
}
|
|
};
|
|
RangeMask.className = "RangeMask";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], RangeMask.prototype, "cornerRadius", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/navigator/shapes/rangeSelector.ts
|
|
var RangeSelector = class extends module_support_exports.Group {
|
|
constructor(children) {
|
|
super({ name: "rangeSelectorGroup", zIndex: 17 /* NAVIGATOR */ });
|
|
this.x = 0;
|
|
this.y = 0;
|
|
this.width = 200;
|
|
this.height = 30;
|
|
this.lOffset = 0;
|
|
this.rOffset = 0;
|
|
this.background = this.appendChild(
|
|
new module_support_exports.TranslatableGroup({ name: "navigator-background", zIndex: 1 })
|
|
);
|
|
this.append(children);
|
|
}
|
|
layout(x, y, width2, height2, lOffset, rOffset) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.width = width2;
|
|
this.height = height2;
|
|
this.lOffset = lOffset;
|
|
this.rOffset = rOffset;
|
|
this.background.translationX = x;
|
|
this.background.translationY = y;
|
|
this.markDirty("RangeSelector");
|
|
}
|
|
updateBackground(oldGroup, newGroup) {
|
|
if (oldGroup != null) {
|
|
oldGroup.remove();
|
|
}
|
|
if (newGroup != null) {
|
|
this.background.appendChild(newGroup);
|
|
}
|
|
this.markDirty("RangeSelector");
|
|
}
|
|
computeBBox() {
|
|
const { x, y, width: width2, height: height2, lOffset, rOffset } = this;
|
|
return new module_support_exports.BBox(x - lOffset, y, width2 + (lOffset + rOffset), height2);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/navigator/navigator.ts
|
|
var Navigator = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.enabled = false;
|
|
this.mask = new RangeMask();
|
|
this.minHandle = new RangeHandle();
|
|
this.maxHandle = new RangeHandle();
|
|
this.maskVisibleRange = {
|
|
id: "navigator-mask-visible-range",
|
|
getBBox: () => this.mask.computeVisibleRangeBBox(),
|
|
toCanvasBBox: () => this.mask.computeVisibleRangeBBox(),
|
|
fromCanvasPoint: (x, y) => ({ x, y })
|
|
};
|
|
this.height = 30;
|
|
this.cornerRadius = 0;
|
|
this.spacing = 10;
|
|
this.x = 0;
|
|
this.y = 0;
|
|
this.width = 0;
|
|
this.rangeSelector = new RangeSelector([this.mask, this.minHandle, this.maxHandle]);
|
|
this.cleanup.register(
|
|
ctx.scene.attachNode(this.rangeSelector),
|
|
ctx.eventsHub.on("locale:change", () => this.updateZoom()),
|
|
ctx.layoutManager.registerElement(module_support_exports.LayoutElement.Navigator, (e) => this.onLayoutStart(e)),
|
|
ctx.eventsHub.on("layout:complete", (e) => this.onLayoutComplete(e)),
|
|
ctx.eventsHub.on("zoom:change-complete", (event) => this.onZoomChange(event))
|
|
);
|
|
this.domProxy = new NavigatorDOMProxy(ctx, this);
|
|
this.updateGroupVisibility();
|
|
this.miniChart = new MiniChart(ctx);
|
|
}
|
|
updateBackground(oldGroup, newGroup) {
|
|
this.rangeSelector?.updateBackground(oldGroup, newGroup);
|
|
}
|
|
updateGroupVisibility() {
|
|
const { enabled } = this;
|
|
if (this.rangeSelector == null || enabled === this.rangeSelector.visible)
|
|
return;
|
|
this.rangeSelector.visible = enabled;
|
|
this.domProxy.updateVisibility(enabled);
|
|
if (enabled) {
|
|
this.updateZoom();
|
|
} else {
|
|
this.ctx.zoomManager.updateZoom(
|
|
{ source: "chart-update", sourceDetail: "navigator" },
|
|
{ x: { min: 0, max: 1 } }
|
|
);
|
|
}
|
|
}
|
|
onLayoutStart({ layoutBox }) {
|
|
if (this.enabled) {
|
|
const navigatorTotalHeight = this.height + this.spacing;
|
|
layoutBox.shrink(navigatorTotalHeight, "bottom");
|
|
this.y = layoutBox.y + layoutBox.height + this.spacing;
|
|
} else {
|
|
this.y = 0;
|
|
}
|
|
if (this.enabled && this.miniChart) {
|
|
const { top, bottom } = this.miniChart.computeAxisPadding();
|
|
layoutBox.shrink(top + bottom, "bottom");
|
|
this.y -= bottom;
|
|
this.miniChart.inset = this.mask.strokeWidth / 2;
|
|
this.miniChart.cornerRadius = this.mask.cornerRadius;
|
|
}
|
|
}
|
|
onLayoutComplete(opts) {
|
|
const { x, width: width2 } = opts.series.rect;
|
|
const { y, height: height2 } = this;
|
|
this.domProxy.updateVisibility(this.enabled);
|
|
if (this.enabled) {
|
|
const { _min: min, _max: max } = this.domProxy;
|
|
this.layoutNodes(x, y, width2, height2, min, max);
|
|
this.domProxy.updateBounds({ x, y, width: width2, height: height2 });
|
|
}
|
|
this.x = x;
|
|
this.width = width2;
|
|
this.miniChart?.layout(width2, height2).catch((e) => logger_exports.error(e));
|
|
}
|
|
canDrag() {
|
|
return this.enabled && this.ctx.interactionManager.isState(module_support_exports.InteractionState.ZoomDraggable);
|
|
}
|
|
onDragStart(dragging, { offsetX }) {
|
|
if (!this.canDrag())
|
|
return;
|
|
if (dragging === "pan") {
|
|
this.panStart = (offsetX - this.x) / this.width - this.domProxy._min;
|
|
}
|
|
this.ctx.zoomManager.fireZoomPanStartEvent("navigator");
|
|
}
|
|
onDrag(dragging, { offsetX }) {
|
|
if (!this.canDrag())
|
|
return;
|
|
const { panStart, x, width: width2 } = this;
|
|
const { minRange } = this.domProxy;
|
|
let { _min: min, _max: max } = this.domProxy;
|
|
const ratio2 = (offsetX - x) / width2;
|
|
if (dragging === "min") {
|
|
min = clamp(0, ratio2, max - minRange);
|
|
} else if (dragging === "max") {
|
|
max = clamp(min + minRange, ratio2, 1);
|
|
} else if (dragging === "pan" && panStart != null) {
|
|
const span = max - min;
|
|
min = clamp(0, ratio2 - panStart, 1 - span);
|
|
max = min + span;
|
|
}
|
|
this.domProxy._min = min;
|
|
this.domProxy._max = max;
|
|
this.updateZoom();
|
|
}
|
|
onZoomChange(event) {
|
|
const { x: xZoom } = event;
|
|
if (!xZoom)
|
|
return;
|
|
const { x, y, width: width2, height: height2 } = this;
|
|
const { min, max } = xZoom;
|
|
this.domProxy.updateMinMax(min, max);
|
|
this.layoutNodes(x, y, width2, height2, min, max);
|
|
}
|
|
layoutNodes(x, y, width2, height2, min, max) {
|
|
const { rangeSelector, mask, minHandle, maxHandle } = this;
|
|
mask.layout(x, y, width2, height2, min, max);
|
|
rangeSelector.layout(x, y, width2, height2, minHandle.width / 2, maxHandle.width / 2);
|
|
RangeHandle.align(minHandle, maxHandle, x, y, width2, height2, min, max, mask.strokeWidth / 2);
|
|
if (min + (max - min) / 2 < 0.5) {
|
|
minHandle.zIndex = 3;
|
|
maxHandle.zIndex = 4;
|
|
} else {
|
|
minHandle.zIndex = 4;
|
|
maxHandle.zIndex = 3;
|
|
}
|
|
for (const [index, node] of [minHandle, this.maskVisibleRange, maxHandle].entries()) {
|
|
const bbox = node.getBBox();
|
|
const tbox = { x: bbox.x - x, y: bbox.y - y, height: bbox.height, width: bbox.width };
|
|
this.domProxy.updateSliderBounds(index, tbox);
|
|
}
|
|
}
|
|
updateZoom() {
|
|
if (!this.enabled)
|
|
return;
|
|
this.domProxy.updateZoom();
|
|
}
|
|
async processData(dataController) {
|
|
return this.miniChart?.processData(dataController);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
ObserveChanges((target, value, oldValue) => {
|
|
target.updateBackground(oldValue?.root, value?.root);
|
|
})
|
|
], Navigator.prototype, "miniChart", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ObserveChanges((target, value) => {
|
|
target.ctx.zoomManager.setNavigatorEnabled(Boolean(value));
|
|
target.updateGroupVisibility();
|
|
})
|
|
], Navigator.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Navigator.prototype, "height", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ObserveChanges((target, value) => {
|
|
target.mask.cornerRadius = value;
|
|
})
|
|
], Navigator.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Navigator.prototype, "spacing", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/box-plot/blotPlotUtil.ts
|
|
function prepareBoxPlotFromTo(isVertical) {
|
|
const from3 = isVertical ? { scalingX: 1, scalingY: 0 } : { scalingX: 0, scalingY: 1 };
|
|
const to = { scalingX: 1, scalingY: 1 };
|
|
return { from: from3, to };
|
|
}
|
|
function resetBoxPlotSelectionsScalingCenterFn(isVertical) {
|
|
return (_node, datum) => {
|
|
if (isVertical) {
|
|
return { scalingCenterY: datum.scaledValues.medianValue };
|
|
}
|
|
return { scalingCenterX: datum.scaledValues.medianValue };
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/box-plot/boxPlotNode.ts
|
|
var { Path: Path7, Scalable: Scalable2, ExtendedPath2D: ExtendedPath2D4, BBox: BBox17, clippedRoundRect: baseClippedRoundRect } = module_support_exports;
|
|
var BoxPlotNode = class extends Scalable2(Path7) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.wickPath = new ExtendedPath2D4();
|
|
this.horizontal = false;
|
|
this.center = 0;
|
|
this.thickness = 0;
|
|
this.min = 0;
|
|
this.q1 = 0;
|
|
this.median = 0;
|
|
this.q3 = 0;
|
|
this.max = 0;
|
|
this.cornerRadius = 0;
|
|
this.crisp = false;
|
|
this.strokeAlignment = 0;
|
|
this.wickStroke = void 0;
|
|
this.wickStrokeWidth = void 0;
|
|
this.wickStrokeOpacity = void 0;
|
|
this.capLengthRatio = 1;
|
|
this.wickStrokeAlignment = 0;
|
|
}
|
|
computeBBox() {
|
|
const { horizontal, center: center2, thickness, min, max } = this;
|
|
return horizontal ? new BBox17(Math.min(min, max), center2 - thickness / 2, Math.abs(max - min), thickness) : new BBox17(center2 - thickness / 2, Math.min(min, max), thickness, Math.abs(max - min));
|
|
}
|
|
computeDefaultGradientFillBBox() {
|
|
const { horizontal, center: center2, thickness, q1, q3 } = this;
|
|
return horizontal ? new BBox17(Math.min(q1, q3), center2 - thickness / 2, Math.abs(q3 - q1), thickness) : new BBox17(center2 - thickness / 2, Math.min(q1, q3), thickness, Math.abs(q3 - q1));
|
|
}
|
|
isPointInPath(x, y) {
|
|
return this.getBBox().containsPoint(x, y);
|
|
}
|
|
distanceSquared(x, y) {
|
|
return this.getBBox().distanceSquared(x, y);
|
|
}
|
|
get midPoint() {
|
|
return this.horizontal ? { x: (this.min + this.max) / 2, y: this.center } : { x: this.center, y: (this.min + this.max) / 2 };
|
|
}
|
|
alignedCoordinates() {
|
|
const { thickness, crisp } = this;
|
|
let { center: center2, min, q1, median, q3, max } = this;
|
|
let x0 = center2 - thickness / 2;
|
|
let x1 = center2 + thickness / 2;
|
|
if (crisp && thickness > 1) {
|
|
min = this.align(min);
|
|
q1 = this.align(q1);
|
|
median = this.align(median);
|
|
q3 = this.align(q3);
|
|
max = min + this.align(min, max - min);
|
|
const halfWidth = this.align(thickness / 2);
|
|
center2 = this.align(center2);
|
|
x0 = center2 - halfWidth;
|
|
x1 = center2 + halfWidth;
|
|
}
|
|
return { center: center2, x0, x1, min, max, q1, median, q3 };
|
|
}
|
|
updatePath() {
|
|
const {
|
|
path,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
wickStroke,
|
|
wickStrokeWidth,
|
|
wickStrokeOpacity,
|
|
wickLineDash,
|
|
wickLineDashOffset,
|
|
strokeAlignment,
|
|
cornerRadius,
|
|
capLengthRatio,
|
|
horizontal
|
|
} = this;
|
|
const { center: center2, x0, x1, min, max, q1, median, q3 } = this.alignedCoordinates();
|
|
const pixelRatio = this.layerManager?.canvas.pixelRatio ?? 1;
|
|
const wickStrokeAlignment = this.wickStrokeAlignment > 0 ? pixelRatio / this.wickStrokeAlignment / 2 % 1 : 0;
|
|
this.path.clear();
|
|
this.wickPath.clear();
|
|
const needsWickPath = wickStroke != null && wickStroke !== stroke3 || wickStrokeWidth != null && wickStrokeWidth !== strokeWidth || wickStrokeOpacity != null && wickStrokeOpacity !== strokeOpacity || wickLineDash != null && wickLineDash !== lineDash || wickLineDashOffset != null && wickLineDashOffset !== lineDashOffset;
|
|
const wickPath = needsWickPath ? this.wickPath : path;
|
|
if (Math.abs(x1 - x0) <= 3) {
|
|
moveTo(wickPath, horizontal, center2, min);
|
|
lineTo(wickPath, horizontal, center2, max);
|
|
return;
|
|
}
|
|
const wickTop = Math.min(min, max);
|
|
const wickBottom = Math.max(min, max);
|
|
const boxTop = Math.min(q1, q3);
|
|
const boxBottom = Math.max(q1, q3);
|
|
const capX0 = center2 - Math.abs((x1 - x0) * capLengthRatio) / 2;
|
|
const capX1 = center2 + Math.abs((x1 - x0) * capLengthRatio) / 2;
|
|
moveTo(wickPath, horizontal, capX0, wickTop - wickStrokeAlignment);
|
|
lineTo(wickPath, horizontal, capX1, wickTop - wickStrokeAlignment);
|
|
moveTo(wickPath, horizontal, center2 - wickStrokeAlignment, wickTop - wickStrokeAlignment);
|
|
lineTo(wickPath, horizontal, center2 - wickStrokeAlignment, boxTop + strokeWidth / 2);
|
|
moveTo(wickPath, horizontal, center2 - wickStrokeAlignment, wickBottom + wickStrokeAlignment);
|
|
lineTo(wickPath, horizontal, center2 - wickStrokeAlignment, boxBottom - strokeWidth / 2);
|
|
moveTo(wickPath, horizontal, capX0, wickBottom + wickStrokeAlignment);
|
|
lineTo(wickPath, horizontal, capX1, wickBottom + wickStrokeAlignment);
|
|
const horizontalBoxStrokeAdjustment = strokeWidth / 2 + strokeAlignment;
|
|
const verticalBoxStrokeAdjustment = strokeWidth / 2 - strokeAlignment;
|
|
const rectHeight = boxBottom - boxTop - 2 * verticalBoxStrokeAdjustment;
|
|
if (rectHeight > 0) {
|
|
const rectX = x0 + horizontalBoxStrokeAdjustment;
|
|
const rectY = boxTop + verticalBoxStrokeAdjustment;
|
|
const rectWidth = x1 - x0 - 2 * horizontalBoxStrokeAdjustment;
|
|
const cornerRadii = {
|
|
topLeft: cornerRadius,
|
|
topRight: cornerRadius,
|
|
bottomRight: cornerRadius,
|
|
bottomLeft: cornerRadius
|
|
};
|
|
clippedRoundRect3(
|
|
path,
|
|
horizontal,
|
|
rectX,
|
|
rectY,
|
|
rectWidth,
|
|
rectHeight,
|
|
cornerRadii,
|
|
new BBox17(rectX, rectY, rectWidth, median - rectY)
|
|
);
|
|
clippedRoundRect3(
|
|
path,
|
|
horizontal,
|
|
rectX,
|
|
rectY,
|
|
rectWidth,
|
|
rectHeight,
|
|
cornerRadii,
|
|
new BBox17(rectX, median, rectWidth, rectY + rectHeight - median)
|
|
);
|
|
} else {
|
|
const boxMid = (boxTop + boxBottom) / 2;
|
|
moveTo(path, horizontal, x0, boxMid);
|
|
lineTo(path, horizontal, x1, boxMid);
|
|
}
|
|
}
|
|
drawPath(ctx) {
|
|
super.drawPath(ctx);
|
|
const { wickPath } = this;
|
|
if (wickPath.isEmpty())
|
|
return;
|
|
const {
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
wickStroke = stroke3,
|
|
wickStrokeWidth = strokeWidth,
|
|
wickStrokeOpacity = strokeOpacity,
|
|
wickLineDash = lineDash,
|
|
wickLineDashOffset = lineDashOffset
|
|
} = this;
|
|
if (wickStrokeWidth === 0)
|
|
return;
|
|
ctx.globalAlpha *= wickStrokeOpacity;
|
|
if (typeof wickStroke === "string") {
|
|
ctx.strokeStyle = wickStroke;
|
|
}
|
|
ctx.lineWidth = wickStrokeWidth;
|
|
if (wickLineDash != null) {
|
|
ctx.setLineDash([...wickLineDash]);
|
|
}
|
|
ctx.lineDashOffset = wickLineDashOffset;
|
|
ctx.stroke(wickPath.getPath2D());
|
|
}
|
|
};
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "horizontal", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "center", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "thickness", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "min", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "q1", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "median", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "q3", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "max", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "crisp", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "strokeAlignment", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "wickStroke", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "wickStrokeWidth", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "wickStrokeOpacity", 2);
|
|
__decorateClass([
|
|
SceneArrayChangeDetection()
|
|
], BoxPlotNode.prototype, "wickLineDash", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "wickLineDashOffset", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "capLengthRatio", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], BoxPlotNode.prototype, "wickStrokeAlignment", 2);
|
|
function moveTo(path, horizontal, x, y) {
|
|
if (horizontal) {
|
|
path.moveTo(y, x);
|
|
} else {
|
|
path.moveTo(x, y);
|
|
}
|
|
}
|
|
function lineTo(path, horizontal, x, y) {
|
|
if (horizontal) {
|
|
path.lineTo(y, x);
|
|
} else {
|
|
path.lineTo(x, y);
|
|
}
|
|
}
|
|
function clippedRoundRect3(path, horizontal, x, y, width2, height2, cornerRadii, clipBBox) {
|
|
if (horizontal) {
|
|
baseClippedRoundRect(
|
|
// eslint-disable-next-line sonarjs/arguments-order
|
|
path,
|
|
y,
|
|
x,
|
|
height2,
|
|
width2,
|
|
cornerRadii,
|
|
clipBBox == null ? void 0 : new BBox17(clipBBox.y, clipBBox.x, clipBBox.height, clipBBox.width)
|
|
);
|
|
} else {
|
|
baseClippedRoundRect(path, x, y, width2, height2, cornerRadii, clipBBox);
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/box-plot/boxPlotSeriesProperties.ts
|
|
var { AbstractBarSeriesProperties: AbstractBarSeriesProperties2, makeSeriesTooltip: makeSeriesTooltip7 } = module_support_exports;
|
|
var BoxPlotSeriesCap = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.lengthRatio = 0.5;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesCap.prototype, "lengthRatio", 2);
|
|
var BoxPlotSeriesWhisker = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesWhisker.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesWhisker.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesWhisker.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesWhisker.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesWhisker.prototype, "lineDashOffset", 2);
|
|
var BoxPlotSeriesProperties = class extends AbstractBarSeriesProperties2 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = "#c16068";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "#333";
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.cornerRadius = 0;
|
|
this.cap = new BoxPlotSeriesCap();
|
|
this.whisker = new BoxPlotSeriesWhisker();
|
|
this.tooltip = makeSeriesTooltip7();
|
|
}
|
|
toJson() {
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = this;
|
|
const properties = super.toJson();
|
|
properties.whisker = mergeDefaults(properties.whisker, {
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
});
|
|
return properties;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "xKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "minKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "q1Key", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "medianKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "q3Key", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "maxKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "xName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "yName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "minName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "q1Name", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "medianName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "q3Name", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "maxName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "styler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "cap", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "whisker", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], BoxPlotSeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/box-plot/boxPlotSeries.ts
|
|
var {
|
|
fixNumericExtent: fixNumericExtent3,
|
|
keyProperty: keyProperty4,
|
|
SeriesNodePickMode: SeriesNodePickMode5,
|
|
SMALLEST_KEY_INTERVAL: SMALLEST_KEY_INTERVAL2,
|
|
valueProperty: valueProperty6,
|
|
diff: diff2,
|
|
animationValidation: animationValidation2,
|
|
computeBarFocusBounds: computeBarFocusBounds2,
|
|
createDatumId: createDatumId7,
|
|
HighlightState: HighlightState4,
|
|
motion: motion2,
|
|
getItemStyles: getItemStyles2,
|
|
calculateSegments: calculateSegments2,
|
|
toHighlightString: toHighlightString2,
|
|
processedDataIsAnimatable: processedDataIsAnimatable2,
|
|
upsertNodeDatum: upsertNodeDatum2
|
|
} = module_support_exports;
|
|
var BoxPlotSeriesNodeEvent = class extends module_support_exports.SeriesNodeEvent {
|
|
constructor(type, nativeEvent, datum, series) {
|
|
super(type, nativeEvent, datum, series);
|
|
this.xKey = series.properties.xKey;
|
|
this.minKey = series.properties.minKey;
|
|
this.q1Key = series.properties.q1Key;
|
|
this.medianKey = series.properties.medianKey;
|
|
this.q3Key = series.properties.q3Key;
|
|
this.maxKey = series.properties.maxKey;
|
|
}
|
|
};
|
|
var BoxPlotSeries = class extends module_support_exports.AbstractBarSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
pickModes: [SeriesNodePickMode5.NEAREST_NODE, SeriesNodePickMode5.EXACT_SHAPE_MATCH],
|
|
propertyKeys: {
|
|
x: ["xKey"],
|
|
y: ["medianKey", "q1Key", "q3Key", "minKey", "maxKey"]
|
|
},
|
|
propertyNames: {
|
|
x: ["xName"],
|
|
y: ["medianName", "q1Name", "q3Name", "minName", "maxName"]
|
|
},
|
|
categoryKey: "xValue",
|
|
pathsPerSeries: []
|
|
});
|
|
this.properties = new BoxPlotSeriesProperties();
|
|
this.NodeEvent = BoxPlotSeriesNodeEvent;
|
|
}
|
|
async processData(dataController) {
|
|
if (!this.visible)
|
|
return;
|
|
const { xKey, minKey, q1Key, medianKey, q3Key, maxKey } = this.properties;
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
const xScale = this.getCategoryAxis()?.scale;
|
|
const yScale = this.getValueAxis()?.scale;
|
|
const { isContinuousX, xScaleType, yScaleType } = this.getScaleInformation({ xScale, yScale });
|
|
const extraProps = [];
|
|
if (this.needsDataModelDiff() && this.processedData) {
|
|
extraProps.push(diff2(this.id, this.processedData));
|
|
}
|
|
if (animationEnabled) {
|
|
extraProps.push(animationValidation2());
|
|
}
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
const { processedData } = await this.requestDataModel(dataController, this.data, {
|
|
props: [
|
|
keyProperty4(xKey, xScaleType, { id: `xValue`, allowNullKey }),
|
|
valueProperty6(minKey, yScaleType, { id: `minValue` }),
|
|
valueProperty6(q1Key, yScaleType, { id: `q1Value` }),
|
|
valueProperty6(medianKey, yScaleType, { id: `medianValue` }),
|
|
valueProperty6(q3Key, yScaleType, { id: `q3Value` }),
|
|
valueProperty6(maxKey, yScaleType, { id: `maxValue` }),
|
|
...isContinuousX ? [SMALLEST_KEY_INTERVAL2] : [],
|
|
...extraProps
|
|
]
|
|
});
|
|
this.smallestDataInterval = processedData.reduced?.smallestKeyInterval;
|
|
this.animationState.transition("updateData");
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { processedData, dataModel } = this;
|
|
if (!(processedData && dataModel))
|
|
return { domain: [] };
|
|
if (direction !== this.getBarDirection()) {
|
|
const { index, def } = dataModel.resolveProcessedDataDefById(this, `xValue`);
|
|
const keys = processedData.domain.keys[index];
|
|
if (def.type === "key" && def.valueType === "category") {
|
|
const sortMetadata = dataModel.getKeySortMetadata(this, "xValue", processedData);
|
|
return { domain: keys, sortMetadata };
|
|
}
|
|
return { domain: this.padBandExtent(keys) };
|
|
}
|
|
const yExtent = this.domainForClippedRange(direction, ["minValue", "maxValue"], "xValue");
|
|
return { domain: fixNumericExtent3(yExtent) };
|
|
}
|
|
getSeriesRange(_direction, visibleRange) {
|
|
return this.domainForVisibleRange("y" /* Y */, ["maxValue", "minValue"], "xValue", visibleRange);
|
|
}
|
|
/**
|
|
* Creates the shared context for datum creation.
|
|
* Caches expensive lookups and computations that are constant across all datums.
|
|
*/
|
|
createNodeDatumContext(xAxis, yAxis) {
|
|
const { dataModel, processedData, contextNodeData } = this;
|
|
if (!dataModel || !processedData)
|
|
return void 0;
|
|
const canIncrementallyUpdate = contextNodeData?.nodeData != null && processedData.changeDescription != null;
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
const { groupOffset, barOffset, barWidth } = this.getBarDimensions();
|
|
return {
|
|
xAxis,
|
|
yAxis,
|
|
rawData: processedData.dataSources.get(this.id)?.data ?? [],
|
|
xValues: dataModel.resolveKeysById(this, "xValue", processedData),
|
|
minValues: dataModel.resolveColumnById(this, "minValue", processedData),
|
|
q1Values: dataModel.resolveColumnById(this, "q1Value", processedData),
|
|
medianValues: dataModel.resolveColumnById(this, "medianValue", processedData),
|
|
q3Values: dataModel.resolveColumnById(this, "q3Value", processedData),
|
|
maxValues: dataModel.resolveColumnById(this, "maxValue", processedData),
|
|
xScale: xAxis.scale,
|
|
yScale: yAxis.scale,
|
|
groupOffset,
|
|
barOffset,
|
|
barWidth,
|
|
isVertical: this.isVertical(),
|
|
xKey: this.properties.xKey,
|
|
animationEnabled,
|
|
canIncrementallyUpdate,
|
|
nodes: canIncrementallyUpdate ? contextNodeData.nodeData : [],
|
|
nodeIndex: 0
|
|
};
|
|
}
|
|
/**
|
|
* Validates box plot values and checks ordering constraints.
|
|
* Returns true if values are valid (all numbers, min <= q1 <= median <= q3 <= max).
|
|
*/
|
|
validateBoxPlotValues(minValue, q1Value, medianValue, q3Value, maxValue) {
|
|
return [minValue, q1Value, medianValue, q3Value, maxValue].every((value) => typeof value === "number") && minValue <= q1Value && q1Value <= medianValue && medianValue <= q3Value && q3Value <= maxValue;
|
|
}
|
|
/**
|
|
* Computes scaled values for a single datum.
|
|
* Populates the scratch object to avoid allocations.
|
|
*/
|
|
computeScaledValues(ctx, scratch, datumIndex) {
|
|
const x = ctx.xScale.convert(ctx.xValues[datumIndex]);
|
|
if (!Number.isFinite(x))
|
|
return false;
|
|
scratch.xValue = x + ctx.groupOffset + ctx.barOffset + ctx.barWidth / 2;
|
|
scratch.minValue = ctx.yScale.convert(ctx.minValues[datumIndex]);
|
|
scratch.q1Value = ctx.yScale.convert(ctx.q1Values[datumIndex]);
|
|
scratch.medianValue = ctx.yScale.convert(ctx.medianValues[datumIndex]);
|
|
scratch.q3Value = ctx.yScale.convert(ctx.q3Values[datumIndex]);
|
|
scratch.maxValue = ctx.yScale.convert(ctx.maxValues[datumIndex]);
|
|
return true;
|
|
}
|
|
/**
|
|
* Creates a skeleton BoxPlotNodeDatum with minimal required fields.
|
|
* The node will be populated by updateNodeDatum.
|
|
*/
|
|
createSkeletonNodeDatum(ctx, params) {
|
|
return {
|
|
series: this,
|
|
datum: params.datum,
|
|
datumIndex: params.datumIndex,
|
|
xKey: ctx.xKey,
|
|
bandwidth: ctx.barWidth,
|
|
scaledValues: {
|
|
xValue: 0,
|
|
minValue: 0,
|
|
q1Value: 0,
|
|
medianValue: 0,
|
|
q3Value: 0,
|
|
maxValue: 0
|
|
},
|
|
midPoint: { x: 0, y: 0 },
|
|
focusRect: { x: 0, y: 0, width: 0, height: 0 }
|
|
};
|
|
}
|
|
/**
|
|
* Updates an existing BoxPlotNodeDatum in-place.
|
|
* This is more efficient than recreating the entire node when only data values change.
|
|
*/
|
|
updateNodeDatum(ctx, node, params) {
|
|
const { isVertical, barWidth } = ctx;
|
|
const scaledValues = params.scaledValues;
|
|
const mutableNode = node;
|
|
mutableNode.datum = params.datum;
|
|
mutableNode.datumIndex = params.datumIndex;
|
|
mutableNode.bandwidth = barWidth;
|
|
const mutableScaledValues = mutableNode.scaledValues;
|
|
mutableScaledValues.xValue = scaledValues.xValue;
|
|
mutableScaledValues.minValue = scaledValues.minValue;
|
|
mutableScaledValues.q1Value = scaledValues.q1Value;
|
|
mutableScaledValues.medianValue = scaledValues.medianValue;
|
|
mutableScaledValues.q3Value = scaledValues.q3Value;
|
|
mutableScaledValues.maxValue = scaledValues.maxValue;
|
|
const height2 = Math.abs(scaledValues.q3Value - scaledValues.q1Value);
|
|
const midX = scaledValues.xValue;
|
|
const midY = Math.min(scaledValues.q3Value, scaledValues.q1Value) + height2 / 2;
|
|
const midPointX = isVertical ? midX : midY;
|
|
const midPointY = isVertical ? midY : midX;
|
|
if (mutableNode.midPoint) {
|
|
mutableNode.midPoint.x = midPointX;
|
|
mutableNode.midPoint.y = midPointY;
|
|
} else {
|
|
mutableNode.midPoint = { x: midPointX, y: midPointY };
|
|
}
|
|
const focusRect = mutableNode.focusRect;
|
|
if (isVertical) {
|
|
focusRect.x = midPointX - barWidth / 2;
|
|
focusRect.y = scaledValues.minValue;
|
|
focusRect.width = barWidth;
|
|
focusRect.height = scaledValues.maxValue - scaledValues.minValue;
|
|
} else {
|
|
focusRect.x = scaledValues.minValue;
|
|
focusRect.y = midPointY - barWidth / 2;
|
|
focusRect.width = scaledValues.maxValue - scaledValues.minValue;
|
|
focusRect.height = barWidth;
|
|
}
|
|
}
|
|
/**
|
|
* Creates a BoxPlotNodeDatum for a single data point.
|
|
* Creates a skeleton node and uses updateNodeDatum to populate it.
|
|
*/
|
|
createNodeDatum(ctx, params) {
|
|
const node = this.createSkeletonNodeDatum(ctx, params);
|
|
this.updateNodeDatum(ctx, node, params);
|
|
return node;
|
|
}
|
|
/**
|
|
* Initialize the result object shell before populating node data.
|
|
*/
|
|
initializeResult(ctx) {
|
|
return {
|
|
itemId: this.properties.xKey,
|
|
nodeData: ctx.nodes,
|
|
labelData: [],
|
|
scales: this.calculateScaling(),
|
|
visible: this.visible,
|
|
// Set by assembleResult()
|
|
groupScale: void 0,
|
|
styles: void 0,
|
|
segments: void 0
|
|
};
|
|
}
|
|
/**
|
|
* Populate node data by iterating over raw data.
|
|
*/
|
|
populateNodeData(ctx) {
|
|
const scaledValuesScratch = {
|
|
xValue: 0,
|
|
minValue: 0,
|
|
q1Value: 0,
|
|
medianValue: 0,
|
|
q3Value: 0,
|
|
maxValue: 0
|
|
};
|
|
const paramsScratch = {
|
|
datumIndex: 0,
|
|
datum: void 0,
|
|
scaledValues: scaledValuesScratch
|
|
};
|
|
for (let datumIndex = 0; datumIndex < ctx.rawData.length; datumIndex++) {
|
|
const datum = ctx.rawData[datumIndex];
|
|
const xValue = ctx.xValues[datumIndex];
|
|
if (xValue === void 0 && !this.properties.allowNullKeys)
|
|
continue;
|
|
const minValue = ctx.minValues[datumIndex];
|
|
const q1Value = ctx.q1Values[datumIndex];
|
|
const medianValue = ctx.medianValues[datumIndex];
|
|
const q3Value = ctx.q3Values[datumIndex];
|
|
const maxValue = ctx.maxValues[datumIndex];
|
|
if (!this.validateBoxPlotValues(minValue, q1Value, medianValue, q3Value, maxValue)) {
|
|
continue;
|
|
}
|
|
if (!this.computeScaledValues(ctx, scaledValuesScratch, datumIndex)) {
|
|
continue;
|
|
}
|
|
paramsScratch.datumIndex = datumIndex;
|
|
paramsScratch.datum = datum;
|
|
upsertNodeDatum2(
|
|
ctx,
|
|
paramsScratch,
|
|
(c, p) => this.createNodeDatum(c, p),
|
|
(c, n, p) => this.updateNodeDatum(c, n, p)
|
|
);
|
|
}
|
|
}
|
|
/**
|
|
* Finalize node data by trimming excess nodes.
|
|
*/
|
|
finalizeNodeData(ctx) {
|
|
if (ctx.canIncrementallyUpdate && ctx.nodeIndex < ctx.nodes.length) {
|
|
ctx.nodes.length = ctx.nodeIndex;
|
|
}
|
|
}
|
|
/**
|
|
* Assemble the final result with computed fields.
|
|
*/
|
|
assembleResult(ctx, result) {
|
|
const segments = calculateSegments2(
|
|
this.properties.segmentation,
|
|
ctx.xAxis,
|
|
ctx.yAxis,
|
|
this.chart.seriesRect,
|
|
this.ctx.scene
|
|
);
|
|
result.groupScale = this.getScaling(this.ctx.seriesStateManager.getGroupScale(this));
|
|
result.styles = getItemStyles2(this.getItemStyle.bind(this));
|
|
result.segments = segments;
|
|
return result;
|
|
}
|
|
legendItemSymbol() {
|
|
const { fill, stroke: stroke3, strokeWidth, fillOpacity, strokeOpacity, lineDash, lineDashOffset } = this.getStyle(
|
|
false,
|
|
HighlightState4.None
|
|
);
|
|
return {
|
|
marker: {
|
|
fill: deepClone(fill),
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash,
|
|
lineDashOffset
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
const {
|
|
id: seriesId,
|
|
ctx: { legendManager },
|
|
visible
|
|
} = this;
|
|
const { xKey, yName, showInLegend, legendItemName } = this.properties;
|
|
if (!xKey || legendType !== "category") {
|
|
return [];
|
|
}
|
|
return [
|
|
{
|
|
legendType: "category",
|
|
id: seriesId,
|
|
itemId: seriesId,
|
|
seriesId,
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId, itemId: seriesId }),
|
|
label: {
|
|
text: legendItemName ?? yName ?? seriesId
|
|
},
|
|
symbol: this.legendItemSymbol(),
|
|
legendItemName,
|
|
hideInLegend: !showInLegend
|
|
}
|
|
];
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, properties } = this;
|
|
const {
|
|
xKey,
|
|
xName,
|
|
yName,
|
|
medianKey,
|
|
medianName,
|
|
q1Key,
|
|
q1Name,
|
|
q3Key,
|
|
q3Name,
|
|
minKey,
|
|
minName,
|
|
maxKey,
|
|
maxName,
|
|
legendItemName,
|
|
tooltip
|
|
} = properties;
|
|
const xAxis = this.getCategoryAxis();
|
|
const yAxis = this.getValueAxis();
|
|
if (!dataModel || !processedData || !xAxis || !yAxis)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const xValue = dataModel.resolveKeysById(this, `xValue`, processedData)[datumIndex];
|
|
const minValue = dataModel.resolveColumnById(this, `minValue`, processedData)[datumIndex];
|
|
const q1Value = dataModel.resolveColumnById(this, `q1Value`, processedData)[datumIndex];
|
|
const medianValue = dataModel.resolveColumnById(this, `medianValue`, processedData)[datumIndex];
|
|
const q3Value = dataModel.resolveColumnById(this, `q3Value`, processedData)[datumIndex];
|
|
const maxValue = dataModel.resolveColumnById(this, `maxValue`, processedData)[datumIndex];
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
if (xValue === void 0 && !allowNullKeys)
|
|
return;
|
|
const format = this.getItemStyle(datumIndex, false);
|
|
const data = [
|
|
{
|
|
label: minName,
|
|
fallbackLabel: minKey,
|
|
value: this.getAxisValueText(yAxis, "tooltip", minValue, datum, minKey, legendItemName),
|
|
missing: module_support_exports.isTooltipValueMissing(minValue)
|
|
},
|
|
{
|
|
label: q1Name,
|
|
fallbackLabel: q1Key,
|
|
value: this.getAxisValueText(yAxis, "tooltip", q1Value, datum, q1Key, legendItemName),
|
|
missing: module_support_exports.isTooltipValueMissing(q1Value)
|
|
},
|
|
{
|
|
label: medianName,
|
|
fallbackLabel: medianKey,
|
|
value: this.getAxisValueText(yAxis, "tooltip", medianValue, datum, medianKey, legendItemName),
|
|
missing: module_support_exports.isTooltipValueMissing(medianValue)
|
|
},
|
|
{
|
|
label: q3Name,
|
|
fallbackLabel: q3Key,
|
|
value: this.getAxisValueText(yAxis, "tooltip", q3Value, datum, q3Key, legendItemName),
|
|
missing: module_support_exports.isTooltipValueMissing(q3Value)
|
|
},
|
|
{
|
|
label: maxName,
|
|
fallbackLabel: maxKey,
|
|
value: this.getAxisValueText(yAxis, "tooltip", maxValue, datum, maxKey, legendItemName),
|
|
missing: module_support_exports.isTooltipValueMissing(maxValue)
|
|
}
|
|
];
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading: this.getAxisValueText(xAxis, "tooltip", xValue, datum, xKey, legendItemName),
|
|
title: legendItemName ?? yName,
|
|
symbol: this.legendItemSymbol(),
|
|
data
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: yName,
|
|
xKey,
|
|
xName,
|
|
yName,
|
|
medianKey,
|
|
medianName,
|
|
q1Key,
|
|
q1Name,
|
|
q3Key,
|
|
q3Name,
|
|
minKey,
|
|
minName,
|
|
maxKey,
|
|
maxName,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
animateEmptyUpdateReady({
|
|
datumSelection
|
|
}) {
|
|
const isVertical = this.isVertical();
|
|
const { from: from3, to } = prepareBoxPlotFromTo(isVertical);
|
|
motion2.resetMotion([datumSelection], resetBoxPlotSelectionsScalingCenterFn(isVertical));
|
|
motion2.staticFromToMotion(this.id, "datums", this.ctx.animationManager, [datumSelection], from3, to, {
|
|
phase: "initial"
|
|
});
|
|
}
|
|
isLabelEnabled() {
|
|
return false;
|
|
}
|
|
updateDatumSelection(opts) {
|
|
const data = opts.nodeData ?? [];
|
|
if (!processedDataIsAnimatable2(this.processedData)) {
|
|
return opts.datumSelection.update(data);
|
|
}
|
|
return opts.datumSelection.update(data, void 0, (datum) => createDatumId7(datum.datumIndex));
|
|
}
|
|
makeStylerParams(highlightStateEnum) {
|
|
const { id: seriesId } = this;
|
|
const {
|
|
cornerRadius,
|
|
cap: { lengthRatio },
|
|
fill,
|
|
fillOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
maxKey,
|
|
maxName,
|
|
medianKey,
|
|
medianName,
|
|
minKey,
|
|
minName,
|
|
q1Key,
|
|
q1Name,
|
|
q3Key,
|
|
q3Name,
|
|
whisker: {
|
|
lineDash: whiskerLineDash,
|
|
lineDashOffset: whiskerLineDashOffset,
|
|
stroke: whiskerStroke,
|
|
strokeOpacity: whiskerStrokeOpacity,
|
|
strokeWidth: whiskerStrokeWidth
|
|
},
|
|
xKey,
|
|
xName,
|
|
yName
|
|
} = this.properties;
|
|
const highlightState = toHighlightString2(highlightStateEnum ?? HighlightState4.None);
|
|
return {
|
|
cap: { lengthRatio },
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
highlightState,
|
|
lineDash,
|
|
lineDashOffset,
|
|
maxKey,
|
|
maxName: maxName ?? maxKey,
|
|
medianKey,
|
|
medianName: medianName ?? medianKey,
|
|
minKey,
|
|
minName: minName ?? minKey,
|
|
q1Key,
|
|
q1Name: q1Name ?? q1Key,
|
|
q3Key,
|
|
q3Name: q3Name ?? q3Key,
|
|
seriesId,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
whisker: {
|
|
lineDash: whiskerLineDash ?? lineDash,
|
|
lineDashOffset: whiskerLineDashOffset ?? lineDashOffset,
|
|
stroke: whiskerStroke ?? stroke3,
|
|
strokeOpacity: whiskerStrokeOpacity ?? strokeOpacity,
|
|
strokeWidth: whiskerStrokeWidth ?? strokeWidth
|
|
},
|
|
xKey,
|
|
xName: xName ?? xKey,
|
|
yName
|
|
};
|
|
}
|
|
getStyle(ignoreStylerCallback, highlightState) {
|
|
const {
|
|
cap,
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
styler,
|
|
whisker
|
|
} = this.properties;
|
|
let stylerResult = {};
|
|
if (!ignoreStylerCallback && styler) {
|
|
const stylerParams = this.makeStylerParams(highlightState);
|
|
stylerResult = this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`],
|
|
this.cachedCallWithContext(styler, stylerParams) ?? {},
|
|
{ pick: false }
|
|
) ?? {};
|
|
}
|
|
return {
|
|
cornerRadius: stylerResult.cornerRadius ?? cornerRadius,
|
|
fill: stylerResult.fill ?? fill,
|
|
fillOpacity: stylerResult.fillOpacity ?? fillOpacity,
|
|
lineDash: stylerResult.lineDash ?? lineDash,
|
|
lineDashOffset: stylerResult.lineDashOffset ?? lineDashOffset,
|
|
opacity: 1,
|
|
stroke: stylerResult.stroke ?? stroke3,
|
|
strokeOpacity: stylerResult.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: stylerResult.strokeWidth ?? strokeWidth,
|
|
cap: { lengthRatio: stylerResult.cap?.lengthRatio ?? cap.lengthRatio },
|
|
whisker: {
|
|
lineDash: stylerResult.whisker?.lineDash ?? whisker.lineDash,
|
|
lineDashOffset: stylerResult.whisker?.lineDashOffset ?? whisker.lineDashOffset,
|
|
stroke: stylerResult.whisker?.stroke ?? whisker.stroke,
|
|
strokeOpacity: stylerResult.whisker?.strokeOpacity ?? whisker.strokeOpacity,
|
|
strokeWidth: stylerResult.whisker?.strokeWidth ?? whisker.strokeWidth
|
|
}
|
|
};
|
|
}
|
|
getItemStyle(datumIndex, isHighlight, highlightState) {
|
|
const { properties } = this;
|
|
const { itemStyler } = properties;
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex, highlightState);
|
|
let style2 = mergeDefaults(highlightStyle, this.getStyle(datumIndex === void 0, highlightState));
|
|
if (itemStyler != null && datumIndex != null) {
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId7(datumIndex, isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const params = this.makeItemStylerParams(datumIndex, isHighlight, style2);
|
|
return this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`],
|
|
this.callWithContext(itemStyler, params)
|
|
);
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(overrides, style2);
|
|
}
|
|
}
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = style2;
|
|
style2.whisker = mergeDefaults(style2.whisker, {
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
});
|
|
return style2;
|
|
}
|
|
makeItemStylerParams(datumIndex, isHighlight, style2) {
|
|
const { id: seriesId } = this;
|
|
const { xKey, minKey, q1Key, medianKey, q3Key, maxKey } = this.properties;
|
|
const datum = this.processedData?.dataSources.get(seriesId)?.data[datumIndex];
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightStateString = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
seriesId,
|
|
datum,
|
|
xKey,
|
|
minKey,
|
|
q1Key,
|
|
medianKey,
|
|
q3Key,
|
|
maxKey,
|
|
highlightState: highlightStateString,
|
|
...style2,
|
|
fill
|
|
};
|
|
}
|
|
updateDatumStyles({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
datumSelection.each((_, nodeDatum) => {
|
|
const highlightState = this.getHighlightState(highlightedDatum, isHighlight, nodeDatum.datumIndex);
|
|
nodeDatum.style = this.getItemStyle(nodeDatum.datumIndex, isHighlight, highlightState);
|
|
});
|
|
}
|
|
updateDatumNodes({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
const { contextNodeData, properties } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const isVertical = this.isVertical();
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
const fillBBox = this.getShapeFillBBox();
|
|
const strokeAlignment = this.getStyle(false, HighlightState4.None).strokeWidth / 2;
|
|
const wickStrokeAlignment = properties.whisker.strokeWidth ?? properties.strokeWidth;
|
|
datumSelection.each((boxPlotNode, nodeDatum) => {
|
|
const style2 = nodeDatum.style ?? contextNodeData.styles[this.getHighlightState(highlightedDatum, isHighlight, nodeDatum.datumIndex)];
|
|
boxPlotNode.setFillProperties(style2.fill, fillBBox);
|
|
const nodeOpacity = style2.opacity ?? 1;
|
|
const whiskerOpacity = style2.whisker?.strokeOpacity ?? style2.strokeOpacity;
|
|
boxPlotNode.fill = style2.fill;
|
|
boxPlotNode.fillOpacity = style2.fillOpacity * nodeOpacity;
|
|
boxPlotNode.stroke = style2.stroke;
|
|
boxPlotNode.strokeWidth = style2.strokeWidth;
|
|
boxPlotNode.strokeOpacity = style2.strokeOpacity * nodeOpacity;
|
|
boxPlotNode.lineDash = style2.lineDash;
|
|
boxPlotNode.lineDashOffset = style2.lineDashOffset;
|
|
boxPlotNode.wickStroke = style2.whisker.stroke;
|
|
boxPlotNode.wickStrokeWidth = style2.whisker.strokeWidth;
|
|
boxPlotNode.wickStrokeOpacity = whiskerOpacity * nodeOpacity;
|
|
boxPlotNode.wickLineDash = style2.whisker.lineDash;
|
|
boxPlotNode.wickLineDashOffset = style2.whisker.lineDashOffset;
|
|
boxPlotNode.cornerRadius = style2.cornerRadius;
|
|
boxPlotNode.crisp = true;
|
|
boxPlotNode.horizontal = !isVertical;
|
|
boxPlotNode.center = nodeDatum.scaledValues.xValue;
|
|
boxPlotNode.thickness = nodeDatum.bandwidth;
|
|
boxPlotNode.min = nodeDatum.scaledValues.minValue;
|
|
boxPlotNode.q1 = nodeDatum.scaledValues.q1Value;
|
|
boxPlotNode.median = nodeDatum.scaledValues.medianValue;
|
|
boxPlotNode.q3 = nodeDatum.scaledValues.q3Value;
|
|
boxPlotNode.max = nodeDatum.scaledValues.maxValue;
|
|
boxPlotNode.capLengthRatio = style2.cap.lengthRatio;
|
|
boxPlotNode.strokeAlignment = strokeAlignment;
|
|
boxPlotNode.wickStrokeAlignment = wickStrokeAlignment;
|
|
});
|
|
}
|
|
updateLabelNodes() {
|
|
}
|
|
updateLabelSelection(opts) {
|
|
const { labelData, labelSelection } = opts;
|
|
return labelSelection.update(labelData);
|
|
}
|
|
nodeFactory() {
|
|
return new BoxPlotNode();
|
|
}
|
|
computeFocusBounds({ datumIndex }) {
|
|
return computeBarFocusBounds2(this, this.contextNodeData?.nodeData[datumIndex].focusRect);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null || this.properties.styler != null;
|
|
}
|
|
};
|
|
BoxPlotSeries.className = "BoxPlotSeries";
|
|
BoxPlotSeries.type = "box-plot";
|
|
|
|
// packages/ag-charts-enterprise/src/series/box-plot/boxPlotSeriesOptionsDef.ts
|
|
var { boxPlotSeriesThemeableOptionsDef: boxPlotSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var boxPlotSeriesOptionsDef = {
|
|
...commonSeriesOptionsDefs,
|
|
...boxPlotSeriesThemeableOptionsDef2,
|
|
type: required(constant("box-plot")),
|
|
xKey: required(string),
|
|
minKey: required(string),
|
|
q1Key: required(string),
|
|
medianKey: required(string),
|
|
q3Key: required(string),
|
|
maxKey: required(string),
|
|
xKeyAxis: string,
|
|
yKeyAxis: string,
|
|
xName: string,
|
|
yName: string,
|
|
minName: string,
|
|
q1Name: string,
|
|
medianName: string,
|
|
q3Name: string,
|
|
maxName: string,
|
|
grouped: boolean,
|
|
legendItemName: string,
|
|
segmentation: shapeSegmentation,
|
|
width: positiveNumberNonZero,
|
|
widthRatio: ratio
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/box-plot/boxPlotThemes.ts
|
|
var BOX_PLOT_SERIES_THEME = {
|
|
series: {
|
|
direction: "vertical",
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{
|
|
$if: [
|
|
{
|
|
$or: [
|
|
{ $isGradient: { $palette: "fill" } },
|
|
{ $isPattern: { $palette: "fill" } },
|
|
{ $isImage: { $palette: "fill" } }
|
|
]
|
|
},
|
|
{ $palette: "fill" },
|
|
{ $mix: [SAFE_FILL_OPERATION, { $ref: "chartBackgroundColor" }, 0.7] }
|
|
]
|
|
},
|
|
["gradient", FILL_GRADIENT_LINEAR_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: { $palette: "stroke" },
|
|
strokeWidth: 2,
|
|
fillOpacity: 1,
|
|
strokeOpacity: 1,
|
|
lineDash: void 0,
|
|
lineDashOffset: 0,
|
|
highlight: {
|
|
unhighlightedItem: {
|
|
opacity: 0.5
|
|
},
|
|
unhighlightedSeries: {
|
|
opacity: 0.1
|
|
}
|
|
},
|
|
segmentation: SEGMENTATION_DEFAULTS
|
|
},
|
|
axes: {
|
|
["number" /* NUMBER */]: {
|
|
crosshair: {
|
|
snap: false
|
|
}
|
|
},
|
|
["category" /* CATEGORY */]: {
|
|
groupPaddingInner: 0.2,
|
|
crosshair: {
|
|
enabled: false,
|
|
snap: false
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/box-plot/boxPlotModule.ts
|
|
var { predictCartesianNonPrimitiveAxis: predictCartesianNonPrimitiveAxis2 } = module_support_exports;
|
|
var BoxPlotSeriesModule = {
|
|
type: "series",
|
|
name: "box-plot",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
groupable: true,
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: boxPlotSeriesOptionsDef,
|
|
matchingKeys: ["xKey", "lowKey", "q1Key", "medianKey", "q3Key", "highKey", "outlierKey", "normalizedTo"],
|
|
predictAxis: predictCartesianNonPrimitiveAxis2,
|
|
defaultAxes: DIRECTION_SWAP_AXES,
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
axisKeysFlipped: { ["x" /* X */]: "yKeyAxis", ["y" /* Y */]: "xKeyAxis" },
|
|
themeTemplate: BOX_PLOT_SERIES_THEME,
|
|
create: (ctx) => new BoxPlotSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/ohlc/ohlcAggregation.ts
|
|
function aggregateOhlcData(scale2, xValues, highValues, lowValues, domainInput, smallestKeyInterval, xNeedsValueOf, yNeedsValueOf) {
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeExtremesAggregation([d0, d1], xValues, highValues, lowValues, {
|
|
smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf
|
|
});
|
|
}
|
|
var memoizedAggregateOhlcData = simpleMemorize2(aggregateOhlcData);
|
|
function aggregateOhlcDataFromDataModel(scale2, dataModel, processedData, series, existingFilters) {
|
|
const xValues = dataModel.resolveKeysById(series, "xValue", processedData);
|
|
const highValues = dataModel.resolveColumnById(series, "highValue", processedData);
|
|
const lowValues = dataModel.resolveColumnById(series, "lowValue", processedData);
|
|
const domainInput = dataModel.getDomain(series, "xValue", "key", processedData);
|
|
const xNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "xValue", processedData);
|
|
const yNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "highValue", processedData) ?? dataModel.resolveColumnNeedsValueOf(series, "lowValue", processedData);
|
|
if (existingFilters) {
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeExtremesAggregation([d0, d1], xValues, highValues, lowValues, {
|
|
smallestKeyInterval: processedData.reduced?.smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
existingFilters
|
|
});
|
|
}
|
|
return memoizedAggregateOhlcData(
|
|
scale2,
|
|
xValues,
|
|
highValues,
|
|
lowValues,
|
|
domainInput,
|
|
processedData.reduced?.smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf
|
|
);
|
|
}
|
|
function aggregateOhlcDataFromDataModelPartial(scale2, dataModel, processedData, series, targetRange, existingFilters) {
|
|
const xValues = dataModel.resolveKeysById(series, "xValue", processedData);
|
|
const highValues = dataModel.resolveColumnById(series, "highValue", processedData);
|
|
const lowValues = dataModel.resolveColumnById(series, "lowValue", processedData);
|
|
const domainInput = dataModel.getDomain(series, "xValue", "key", processedData);
|
|
const xNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "xValue", processedData);
|
|
const yNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "highValue", processedData) ?? dataModel.resolveColumnNeedsValueOf(series, "lowValue", processedData);
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeExtremesAggregationPartial([d0, d1], xValues, highValues, lowValues, {
|
|
smallestKeyInterval: processedData.reduced?.smallestKeyInterval,
|
|
targetRange,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
existingFilters
|
|
});
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/ohlc/ohlcNode.ts
|
|
var { Path: Path8, BBox: BBox18 } = module_support_exports;
|
|
var OhlcBaseNode = class extends Path8 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.centerX = 0;
|
|
this.y = 0;
|
|
this.width = 0;
|
|
this.height = 0;
|
|
this.yOpen = 0;
|
|
this.yClose = 0;
|
|
this.crisp = false;
|
|
}
|
|
/**
|
|
* 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(centerX, width2, y, height2, yOpen, yClose, crisp) {
|
|
this.__centerX = centerX;
|
|
this.__width = width2;
|
|
this.__y = y;
|
|
this.__height = height2;
|
|
this.__yOpen = yOpen;
|
|
this.__yClose = yClose;
|
|
this.__crisp = crisp;
|
|
this.dirtyPath = true;
|
|
this.markDirty();
|
|
}
|
|
computeBBox() {
|
|
const { __centerX: centerX, __y: y, __width: width2, __height: height2 } = this;
|
|
return new BBox18(centerX - width2 / 2, y, width2, height2);
|
|
}
|
|
isPointInPath(x, y) {
|
|
return this.getBBox().containsPoint(x, y);
|
|
}
|
|
distanceSquared(x, y) {
|
|
return this.getBBox().distanceSquared(x, y);
|
|
}
|
|
get midPoint() {
|
|
return { x: this.__centerX, y: this.__y + this.__height / 2 };
|
|
}
|
|
alignedCoordinates() {
|
|
const { __y: y, __width: width2, __height: height2, __crisp: crisp } = this;
|
|
let { __centerX: centerX, __yOpen: yOpen, __yClose: yClose } = this;
|
|
let x0 = centerX - width2 / 2;
|
|
let x1 = centerX + width2 / 2;
|
|
let y0 = y;
|
|
let y1 = y + height2;
|
|
if (crisp && width2 > 1) {
|
|
centerX = this.align(centerX);
|
|
if (yOpen <= yClose) {
|
|
const h = this.align(yOpen, yClose - yOpen);
|
|
yOpen = this.align(yOpen);
|
|
yClose = yOpen + h;
|
|
} else {
|
|
const h = this.align(yClose, yOpen - yClose);
|
|
yClose = this.align(yClose);
|
|
yOpen = yClose + h;
|
|
}
|
|
const halfWidth = this.align(width2 / 2);
|
|
x0 = centerX - halfWidth;
|
|
x1 = centerX + halfWidth;
|
|
y0 = this.align(y);
|
|
y1 = y0 + this.align(y0, height2);
|
|
}
|
|
return { centerX, x0, x1, y0, y1, yOpen, yClose };
|
|
}
|
|
executeStroke(ctx, path) {
|
|
const { __width: width2, strokeWidth } = this;
|
|
if (width2 < strokeWidth) {
|
|
ctx.lineWidth = width2;
|
|
}
|
|
super.executeStroke(ctx, path);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], OhlcBaseNode.prototype, "centerX", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], OhlcBaseNode.prototype, "y", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], OhlcBaseNode.prototype, "width", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], OhlcBaseNode.prototype, "height", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], OhlcBaseNode.prototype, "yOpen", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], OhlcBaseNode.prototype, "yClose", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], OhlcBaseNode.prototype, "crisp", 2);
|
|
var OhlcNode = class extends OhlcBaseNode {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.strokeAlignment = 0;
|
|
}
|
|
updatePath() {
|
|
const { path } = this;
|
|
const { centerX, x0, x1, y0, y1, yOpen, yClose } = this.alignedCoordinates();
|
|
const pixelRatio = this.layerManager?.canvas.pixelRatio ?? 1;
|
|
const strokeAlignment = this.__strokeAlignment > 0 ? pixelRatio / this.__strokeAlignment / 2 % 1 : 0;
|
|
path.clear();
|
|
path.moveTo(centerX - strokeAlignment, y0);
|
|
path.lineTo(centerX - strokeAlignment, y1);
|
|
if (Math.abs(x1 - x0) > 1) {
|
|
path.moveTo(x0, yOpen - strokeAlignment);
|
|
path.lineTo(centerX - strokeAlignment, yOpen - strokeAlignment);
|
|
path.moveTo(centerX - strokeAlignment, yClose - strokeAlignment);
|
|
path.lineTo(x1, yClose - strokeAlignment);
|
|
}
|
|
}
|
|
};
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], OhlcNode.prototype, "strokeAlignment", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/ohlc/ohlcSeriesBase.ts
|
|
var OPEN = AGGREGATION_INDEX_X_MIN;
|
|
var HIGH = AGGREGATION_INDEX_Y_MAX;
|
|
var LOW = AGGREGATION_INDEX_Y_MIN;
|
|
var CLOSE = AGGREGATION_INDEX_X_MAX;
|
|
var SPAN2 = AGGREGATION_SPAN;
|
|
var {
|
|
AggregationManager: AggregationManager2,
|
|
fixNumericExtent: fixNumericExtent4,
|
|
keyProperty: keyProperty5,
|
|
createDatumId: createDatumId8,
|
|
SeriesNodePickMode: SeriesNodePickMode6,
|
|
SMALLEST_KEY_INTERVAL: SMALLEST_KEY_INTERVAL3,
|
|
valueProperty: valueProperty7,
|
|
diff: diff3,
|
|
animationValidation: animationValidation3,
|
|
computeBarFocusBounds: computeBarFocusBounds3,
|
|
visibleRangeIndices: visibleRangeIndices2,
|
|
BandScale: BandScale2,
|
|
processedDataIsAnimatable: processedDataIsAnimatable3,
|
|
getItemStylesPerItemId: getItemStylesPerItemId2
|
|
} = module_support_exports;
|
|
var OhlcSeriesNodeEvent = class extends module_support_exports.SeriesNodeEvent {
|
|
constructor(type, nativeEvent, datum, series) {
|
|
super(type, nativeEvent, datum, series);
|
|
this.xKey = series.properties.xKey;
|
|
this.openKey = series.properties.openKey;
|
|
this.closeKey = series.properties.closeKey;
|
|
this.highKey = series.properties.highKey;
|
|
this.lowKey = series.properties.lowKey;
|
|
}
|
|
};
|
|
function resetOhlcSelectionsDirect(selections) {
|
|
for (const selection of selections) {
|
|
const nodes = selection.nodes();
|
|
selection.batchedUpdate(function resetOhlcNodes() {
|
|
for (const node of nodes) {
|
|
const datum = node.datum;
|
|
if (datum == null)
|
|
continue;
|
|
node.setStaticProperties(
|
|
datum.centerX,
|
|
datum.width,
|
|
datum.y,
|
|
datum.height,
|
|
datum.yOpen,
|
|
datum.yClose,
|
|
datum.crisp
|
|
);
|
|
}
|
|
selection.cleanup();
|
|
});
|
|
}
|
|
}
|
|
var OhlcSeriesBase = class extends module_support_exports.AbstractBarSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
pickModes: [SeriesNodePickMode6.AXIS_ALIGNED, SeriesNodePickMode6.EXACT_SHAPE_MATCH],
|
|
propertyKeys: {
|
|
x: ["xKey"],
|
|
y: ["lowKey", "highKey", "openKey", "closeKey"]
|
|
},
|
|
propertyNames: {
|
|
x: ["xName"],
|
|
y: ["lowName", "highName", "openName", "closeName"]
|
|
},
|
|
categoryKey: "xValue",
|
|
pathsPerSeries: []
|
|
});
|
|
this.NodeEvent = OhlcSeriesNodeEvent;
|
|
this.aggregationManager = new AggregationManager2();
|
|
}
|
|
async processData(dataController) {
|
|
if (!this.visible)
|
|
return;
|
|
const { xKey, openKey, closeKey, highKey, lowKey } = this.properties;
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
const xScale = this.getCategoryAxis()?.scale;
|
|
const yScale = this.getValueAxis()?.scale;
|
|
const { isContinuousX, xScaleType, yScaleType } = this.getScaleInformation({ xScale, yScale });
|
|
const extraProps = [];
|
|
if (this.needsDataModelDiff() && this.processedData) {
|
|
extraProps.push(diff3(this.id, this.processedData));
|
|
}
|
|
if (animationEnabled) {
|
|
extraProps.push(animationValidation3());
|
|
}
|
|
if (openKey) {
|
|
extraProps.push(
|
|
valueProperty7(openKey, yScaleType, {
|
|
id: `openValue`,
|
|
invalidValue: void 0,
|
|
missingValue: void 0
|
|
})
|
|
);
|
|
}
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
const { dataModel, processedData } = await this.requestDataModel(dataController, this.data, {
|
|
props: [
|
|
keyProperty5(xKey, xScaleType, { id: `xValue`, allowNullKey }),
|
|
valueProperty7(closeKey, yScaleType, { id: `closeValue` }),
|
|
valueProperty7(highKey, yScaleType, { id: `highValue` }),
|
|
valueProperty7(lowKey, yScaleType, { id: `lowValue` }),
|
|
...isContinuousX ? [SMALLEST_KEY_INTERVAL3] : [],
|
|
...extraProps
|
|
]
|
|
});
|
|
this.smallestDataInterval = processedData.reduced?.smallestKeyInterval;
|
|
this.aggregateData(dataModel, processedData);
|
|
this.animationState.transition("updateData");
|
|
}
|
|
aggregateData(dataModel, processedData) {
|
|
this.aggregationManager.markStale(processedData.input.count);
|
|
if (processedData.type !== "ungrouped")
|
|
return;
|
|
if (processedDataIsAnimatable3(processedData))
|
|
return;
|
|
const xAxis = this.axes["x" /* X */];
|
|
if (xAxis == null)
|
|
return;
|
|
const targetRange = this.estimateTargetRange();
|
|
this.aggregationManager.aggregate({
|
|
computePartial: (existingFilters) => aggregateOhlcDataFromDataModelPartial(
|
|
xAxis.scale.type,
|
|
dataModel,
|
|
processedData,
|
|
this,
|
|
targetRange,
|
|
existingFilters
|
|
),
|
|
computeFull: (existingFilters) => aggregateOhlcDataFromDataModel(xAxis.scale.type, dataModel, processedData, this, existingFilters),
|
|
targetRange
|
|
});
|
|
const filters = this.aggregationManager.filters;
|
|
if (filters && filters.length > 0) {
|
|
debugMetrics_exports.record(
|
|
`${this.type}:aggregation`,
|
|
filters.map((f) => f.maxRange)
|
|
);
|
|
}
|
|
}
|
|
estimateTargetRange() {
|
|
const xAxis = this.axes["x" /* X */];
|
|
if (!xAxis)
|
|
return -1;
|
|
const [r0, r1] = xAxis.scale.range;
|
|
return Math.abs(r1 - r0);
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { processedData, dataModel } = this;
|
|
if (!(processedData && dataModel))
|
|
return { domain: [] };
|
|
if (direction !== this.getBarDirection()) {
|
|
const { def } = dataModel.resolveProcessedDataDefById(this, `xValue`);
|
|
const keys = dataModel.getDomain(this, `xValue`, "key", processedData);
|
|
if (def.type === "key" && def.valueType === "category") {
|
|
return keys;
|
|
}
|
|
return { domain: this.padBandExtent(keys.domain) };
|
|
}
|
|
const yExtent = this.domainForClippedRange(direction, ["highValue", "lowValue"], "xValue");
|
|
return { domain: fixNumericExtent4(yExtent) };
|
|
}
|
|
getSeriesRange(_direction, visibleRange) {
|
|
return this.domainForVisibleRange("y" /* Y */, ["highValue", "lowValue"], "xValue", visibleRange);
|
|
}
|
|
getZoomRangeFittingItems(xVisibleRange, yVisibleRange, minVisibleItems) {
|
|
return this.zoomFittingVisibleItems(
|
|
"xValue",
|
|
["highValue", "lowValue"],
|
|
xVisibleRange,
|
|
yVisibleRange,
|
|
minVisibleItems
|
|
);
|
|
}
|
|
getVisibleItems(xVisibleRange, yVisibleRange, minVisibleItems) {
|
|
return this.countVisibleItems(
|
|
"xValue",
|
|
["highValue", "lowValue"],
|
|
xVisibleRange,
|
|
yVisibleRange,
|
|
minVisibleItems
|
|
);
|
|
}
|
|
/**
|
|
* Creates shared context for node datum creation/update operations.
|
|
* This context is instantiated once and reused across all datum operations
|
|
* to minimize memory allocations. Only caches values that are expensive to
|
|
* compute - cheap property lookups use `this` directly.
|
|
*/
|
|
buildDatumContext(xAxis, yAxis) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData)
|
|
return void 0;
|
|
const rawData = processedData.dataSources.get(this.id)?.data ?? [];
|
|
if (rawData.length === 0)
|
|
return void 0;
|
|
const xScale = xAxis.scale;
|
|
const yScale = yAxis.scale;
|
|
const applyWidthOffset = BandScale2.is(xScale);
|
|
const [r0, r1] = xScale.range;
|
|
const range3 = Math.abs(r1 - r0);
|
|
this.aggregationManager.ensureLevelForRange(range3);
|
|
const dataAggregationFilter = this.aggregationManager.getFilterForRange(range3);
|
|
const crisp = dataAggregationFilter == null;
|
|
const canIncrementallyUpdate = this.contextNodeData?.nodeData != null && (processedData.changeDescription != null || !processedDataIsAnimatable3(processedData) || dataAggregationFilter != null);
|
|
const { groupOffset, barWidth } = this.getBarDimensions();
|
|
return {
|
|
rawData,
|
|
xValues: dataModel.resolveKeysById(this, "xValue", processedData),
|
|
openValues: dataModel.resolveColumnById(this, "openValue", processedData),
|
|
closeValues: dataModel.resolveColumnById(this, "closeValue", processedData),
|
|
highValues: dataModel.resolveColumnById(this, "highValue", processedData),
|
|
lowValues: dataModel.resolveColumnById(this, "lowValue", processedData),
|
|
xScale,
|
|
yScale,
|
|
xAxis,
|
|
yAxis,
|
|
groupOffset,
|
|
barWidth,
|
|
applyWidthOffset,
|
|
// TODO: replace with barOffset?
|
|
crisp,
|
|
xKey: this.properties.xKey,
|
|
openKey: this.properties.openKey,
|
|
closeKey: this.properties.closeKey,
|
|
highKey: this.properties.highKey,
|
|
lowKey: this.properties.lowKey,
|
|
dataAggregationFilter,
|
|
range: range3,
|
|
nodeDatumStateScratch: {
|
|
datum: void 0,
|
|
xValue: void 0,
|
|
openValue: 0,
|
|
closeValue: 0,
|
|
highValue: 0,
|
|
lowValue: 0,
|
|
isRising: true,
|
|
itemType: "up"
|
|
},
|
|
canIncrementallyUpdate,
|
|
nodeIndex: 0,
|
|
nodeData: canIncrementallyUpdate ? this.contextNodeData.nodeData : []
|
|
};
|
|
}
|
|
/**
|
|
* Validates and prepares state for a single OHLC datum.
|
|
* Mutates ctx.nodeDatumStateScratch with computed values.
|
|
* Returns the scratch object if valid, undefined if invalid.
|
|
*/
|
|
prepareOhlcNodeDatumState(ctx, datumIndex) {
|
|
const xValue = ctx.xValues[datumIndex];
|
|
if (xValue === void 0 && !this.properties.allowNullKeys) {
|
|
return void 0;
|
|
}
|
|
const openValue = ctx.openValues[datumIndex];
|
|
const closeValue = ctx.closeValues[datumIndex];
|
|
const highValue = ctx.highValues[datumIndex];
|
|
const lowValue = ctx.lowValues[datumIndex];
|
|
const validLowValue = lowValue != null && lowValue <= openValue && lowValue <= closeValue;
|
|
const validHighValue = highValue != null && highValue >= openValue && highValue >= closeValue;
|
|
if (!validLowValue) {
|
|
logger_exports.warnOnce(
|
|
`invalid low value for key [${ctx.lowKey}] in data element, low value cannot be higher than datum open or close values`
|
|
);
|
|
return void 0;
|
|
}
|
|
if (!validHighValue) {
|
|
logger_exports.warnOnce(
|
|
`invalid high value for key [${ctx.highKey}] in data element, high value cannot be lower than datum open or close values.`
|
|
);
|
|
return void 0;
|
|
}
|
|
const datum = ctx.rawData[datumIndex];
|
|
const isRising = closeValue > openValue;
|
|
const itemType = isRising ? "up" : "down";
|
|
const scratch = ctx.nodeDatumStateScratch;
|
|
scratch.datum = datum;
|
|
scratch.xValue = xValue;
|
|
scratch.openValue = openValue;
|
|
scratch.closeValue = closeValue;
|
|
scratch.highValue = highValue;
|
|
scratch.lowValue = lowValue;
|
|
scratch.isRising = isRising;
|
|
scratch.itemType = itemType;
|
|
return scratch;
|
|
}
|
|
/**
|
|
* Creates a skeleton OhlcNodeDatum from prepared state.
|
|
* Takes pre-computed positioning and state from scratch object.
|
|
*/
|
|
createSkeletonNodeDatum(ctx, scratch, datumIndex, centerX, width2, crisp) {
|
|
const xOffset = ctx.applyWidthOffset ? width2 / 2 : 0;
|
|
const adjustedCenterX = centerX + xOffset;
|
|
const yOpen = ctx.yScale.convert(scratch.openValue);
|
|
const yClose = ctx.yScale.convert(scratch.closeValue);
|
|
const yHigh = ctx.yScale.convert(scratch.highValue);
|
|
const yLow = ctx.yScale.convert(scratch.lowValue);
|
|
const y = Math.min(yHigh, yLow);
|
|
const height2 = Math.max(yHigh, yLow) - y;
|
|
return {
|
|
series: this,
|
|
itemType: scratch.itemType,
|
|
datum: scratch.datum,
|
|
datumIndex,
|
|
xKey: ctx.xKey,
|
|
xValue: scratch.xValue,
|
|
openValue: scratch.openValue,
|
|
closeValue: scratch.closeValue,
|
|
highValue: scratch.highValue,
|
|
lowValue: scratch.lowValue,
|
|
midPoint: {
|
|
x: adjustedCenterX,
|
|
y: y + height2 / 2
|
|
},
|
|
aggregatedValue: scratch.closeValue,
|
|
isRising: scratch.isRising,
|
|
centerX: adjustedCenterX,
|
|
width: width2,
|
|
y,
|
|
height: height2,
|
|
yOpen,
|
|
yClose,
|
|
crisp
|
|
};
|
|
}
|
|
/**
|
|
* Updates an existing OhlcNodeDatum in-place for value-only changes.
|
|
* This is more efficient than recreating the entire node when only data values change
|
|
* but the structure (insertions/removals) remains the same.
|
|
*/
|
|
updateNodeDatum(ctx, node, prepared, datumIndex, centerX, width2, crisp) {
|
|
const mutableNode = node;
|
|
const xOffset = ctx.applyWidthOffset ? width2 / 2 : 0;
|
|
const adjustedCenterX = centerX + xOffset;
|
|
const yOpen = ctx.yScale.convert(prepared.openValue);
|
|
const yClose = ctx.yScale.convert(prepared.closeValue);
|
|
const yHigh = ctx.yScale.convert(prepared.highValue);
|
|
const yLow = ctx.yScale.convert(prepared.lowValue);
|
|
const y = Math.min(yHigh, yLow);
|
|
const height2 = Math.max(yHigh, yLow) - y;
|
|
mutableNode.datum = prepared.datum;
|
|
mutableNode.datumIndex = datumIndex;
|
|
mutableNode.itemType = prepared.itemType;
|
|
mutableNode.xValue = prepared.xValue;
|
|
mutableNode.openValue = prepared.openValue;
|
|
mutableNode.closeValue = prepared.closeValue;
|
|
mutableNode.highValue = prepared.highValue;
|
|
mutableNode.lowValue = prepared.lowValue;
|
|
mutableNode.aggregatedValue = prepared.closeValue;
|
|
mutableNode.isRising = prepared.isRising;
|
|
mutableNode.centerX = adjustedCenterX;
|
|
mutableNode.width = width2;
|
|
mutableNode.y = y;
|
|
mutableNode.height = height2;
|
|
mutableNode.yOpen = yOpen;
|
|
mutableNode.yClose = yClose;
|
|
mutableNode.crisp = crisp;
|
|
const mutableMidPoint = mutableNode.midPoint;
|
|
mutableMidPoint.x = adjustedCenterX;
|
|
mutableMidPoint.y = y + height2 / 2;
|
|
}
|
|
/**
|
|
* Handles node creation/update - reuses existing nodes when possible for incremental updates.
|
|
* This method decides whether to update existing nodes in-place or create new ones.
|
|
*/
|
|
upsertNodeDatum(ctx, datumIndex, centerX, width2, crisp) {
|
|
const prepared = this.prepareOhlcNodeDatumState(ctx, datumIndex);
|
|
if (!prepared)
|
|
return;
|
|
const canReuse = ctx.canIncrementallyUpdate && ctx.nodeIndex < ctx.nodeData.length;
|
|
if (canReuse) {
|
|
this.updateNodeDatum(ctx, ctx.nodeData[ctx.nodeIndex], prepared, datumIndex, centerX, width2, crisp);
|
|
} else {
|
|
const newNode = this.createSkeletonNodeDatum(ctx, prepared, datumIndex, centerX, width2, crisp);
|
|
ctx.nodeData.push(newNode);
|
|
}
|
|
ctx.nodeIndex++;
|
|
}
|
|
createNodeData() {
|
|
const { visible } = this;
|
|
const xAxis = this.getCategoryAxis();
|
|
const yAxis = this.getValueAxis();
|
|
if (!xAxis || !yAxis)
|
|
return;
|
|
const ctx = this.buildDatumContext(xAxis, yAxis);
|
|
const resultContext = {
|
|
itemId: this.properties.xKey,
|
|
nodeData: ctx?.nodeData ?? [],
|
|
labelData: [],
|
|
scales: this.calculateScaling(),
|
|
groupScale: this.getScaling(this.ctx.seriesStateManager.getGroupScale(this)),
|
|
visible: this.visible,
|
|
styles: getItemStylesPerItemId2(this.getItemStyle.bind(this), "up", "down")
|
|
};
|
|
if (!visible || !ctx)
|
|
return resultContext;
|
|
const xPosition = (index) => {
|
|
const x = ctx.xScale.convert(ctx.xValues[index]);
|
|
if (!Number.isFinite(x))
|
|
return Number.NaN;
|
|
return x + ctx.groupOffset;
|
|
};
|
|
if (ctx.dataAggregationFilter == null) {
|
|
const invalidData = this.processedData.invalidData?.get(this.id);
|
|
let [start2, end3] = visibleRangeIndices2(1, ctx.rawData.length, ctx.xAxis.range, (index) => {
|
|
const xOffset = ctx.applyWidthOffset ? 0 : -ctx.barWidth / 2;
|
|
const x = xPosition(index) + xOffset;
|
|
return [x, x + ctx.barWidth];
|
|
});
|
|
if (this.processedData.input.count < 1e3) {
|
|
start2 = 0;
|
|
end3 = this.processedData.input.count;
|
|
}
|
|
for (let datumIndex = start2; datumIndex < end3; datumIndex += 1) {
|
|
if (invalidData?.[datumIndex] === true)
|
|
continue;
|
|
const centerX = xPosition(datumIndex);
|
|
this.upsertNodeDatum(ctx, datumIndex, centerX, ctx.barWidth, ctx.crisp);
|
|
}
|
|
if (ctx.canIncrementallyUpdate && ctx.nodeIndex < ctx.nodeData.length) {
|
|
ctx.nodeData.length = ctx.nodeIndex;
|
|
}
|
|
} else {
|
|
const { maxRange, indexData, midpointIndices } = ctx.dataAggregationFilter;
|
|
const [start2, end3] = visibleRangeIndices2(1, maxRange, ctx.xAxis.range, (index) => {
|
|
const aggIndex = index * SPAN2;
|
|
const closeIndex = indexData[aggIndex + CLOSE];
|
|
const midDatumIndex = midpointIndices[index];
|
|
if (midDatumIndex === -1)
|
|
return;
|
|
const xOffset = ctx.applyWidthOffset ? 0 : -ctx.barWidth / 2;
|
|
return [xPosition(midDatumIndex) + xOffset, xPosition(closeIndex) + xOffset + ctx.barWidth];
|
|
});
|
|
for (let i = start2; i < end3; i += 1) {
|
|
const aggIndex = i * SPAN2;
|
|
const openIndex = indexData[aggIndex + OPEN];
|
|
const closeIndex = indexData[aggIndex + CLOSE];
|
|
const highIndex = indexData[aggIndex + HIGH];
|
|
const lowIndex = indexData[aggIndex + LOW];
|
|
const midDatumIndex = midpointIndices[i];
|
|
if (midDatumIndex === -1)
|
|
continue;
|
|
const prepared = this.prepareOhlcNodeDatumState(ctx, midDatumIndex);
|
|
if (!prepared)
|
|
continue;
|
|
prepared.openValue = ctx.openValues[openIndex];
|
|
prepared.closeValue = ctx.closeValues[closeIndex];
|
|
prepared.highValue = ctx.highValues[highIndex];
|
|
prepared.lowValue = ctx.lowValues[lowIndex];
|
|
prepared.isRising = prepared.closeValue > prepared.openValue;
|
|
prepared.itemType = prepared.isRising ? "up" : "down";
|
|
const centerX = xPosition(midDatumIndex);
|
|
const width2 = Math.abs(xPosition(closeIndex) - xPosition(openIndex)) + ctx.barWidth;
|
|
const canReuse = ctx.canIncrementallyUpdate && ctx.nodeIndex < ctx.nodeData.length;
|
|
if (canReuse) {
|
|
this.updateNodeDatum(
|
|
ctx,
|
|
ctx.nodeData[ctx.nodeIndex],
|
|
prepared,
|
|
midDatumIndex,
|
|
centerX,
|
|
width2,
|
|
false
|
|
);
|
|
} else {
|
|
const nodeDatum = this.createSkeletonNodeDatum(ctx, prepared, midDatumIndex, centerX, width2, false);
|
|
ctx.nodeData.push(nodeDatum);
|
|
}
|
|
ctx.nodeIndex++;
|
|
}
|
|
if (ctx.canIncrementallyUpdate && ctx.nodeIndex < ctx.nodeData.length) {
|
|
ctx.nodeData.length = ctx.nodeIndex;
|
|
}
|
|
}
|
|
return resultContext;
|
|
}
|
|
isVertical() {
|
|
return true;
|
|
}
|
|
isLabelEnabled() {
|
|
return false;
|
|
}
|
|
resetDatumAnimation(data) {
|
|
resetOhlcSelectionsDirect([data.datumSelection]);
|
|
}
|
|
updateDatumSelection(opts) {
|
|
const data = opts.nodeData ?? [];
|
|
if (!processedDataIsAnimatable3(this.processedData)) {
|
|
return opts.datumSelection.update(data);
|
|
}
|
|
return opts.datumSelection.update(data, void 0, (datum) => createDatumId8(datum.xValue));
|
|
}
|
|
updateLabelNodes(_opts) {
|
|
}
|
|
updateLabelSelection(opts) {
|
|
const { labelData, labelSelection } = opts;
|
|
return labelSelection.update(labelData);
|
|
}
|
|
getItemStyle(datumIndex, isHighlight, highlightState, itemType = "up") {
|
|
const { properties, dataModel, processedData } = this;
|
|
const { itemStyler } = properties;
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex, highlightState);
|
|
const baseStyle = mergeDefaults(highlightStyle, properties.getStyle(itemType));
|
|
let style2 = baseStyle;
|
|
if (itemStyler && dataModel != null && processedData != null && datumIndex != null) {
|
|
const xValue = dataModel.resolveKeysById(this, `xValue`, processedData)[datumIndex];
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId8(createDatumId8(xValue), isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const params = this.makeItemStylerParams(itemType, datumIndex, isHighlight, style2);
|
|
return this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`, "item", itemType],
|
|
this.callWithContext(itemStyler, params)
|
|
);
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(overrides, style2);
|
|
}
|
|
}
|
|
return style2;
|
|
}
|
|
makeItemStylerParams(itemType, datumIndex, isHighlight, style2) {
|
|
const { id: seriesId, properties, processedData } = this;
|
|
const { xKey, openKey, closeKey, highKey, lowKey } = properties;
|
|
const datum = processedData.dataSources.get(seriesId)?.data[datumIndex];
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightStateString = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
const params = {
|
|
seriesId,
|
|
datum,
|
|
itemType,
|
|
xKey,
|
|
openKey,
|
|
closeKey,
|
|
highKey,
|
|
lowKey,
|
|
highlightState: highlightStateString,
|
|
...style2
|
|
};
|
|
if ("fill" in params && "fill" in style2) {
|
|
params.fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
}
|
|
return params;
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, properties } = this;
|
|
const {
|
|
xKey,
|
|
xName,
|
|
yName,
|
|
openKey,
|
|
openName,
|
|
highKey,
|
|
highName,
|
|
lowKey,
|
|
lowName,
|
|
closeKey,
|
|
closeName,
|
|
legendItemName,
|
|
tooltip
|
|
} = properties;
|
|
const xAxis = this.getCategoryAxis();
|
|
const yAxis = this.getValueAxis();
|
|
if (!dataModel || !processedData || !xAxis || !yAxis)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const xValue = dataModel.resolveKeysById(this, `xValue`, processedData)[datumIndex];
|
|
const openValue = dataModel.resolveColumnById(this, `openValue`, processedData)[datumIndex];
|
|
const highValue = dataModel.resolveColumnById(this, `highValue`, processedData)[datumIndex];
|
|
const lowValue = dataModel.resolveColumnById(this, `lowValue`, processedData)[datumIndex];
|
|
const closeValue = dataModel.resolveColumnById(this, `closeValue`, processedData)[datumIndex];
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
if (xValue === void 0 && !allowNullKeys)
|
|
return;
|
|
const itemType = closeValue >= openValue ? "up" : "down";
|
|
const item = this.properties.item[itemType];
|
|
const format = this.getItemStyle(datumIndex, false);
|
|
const marker = {
|
|
fill: item.fill ?? item.stroke,
|
|
fillOpacity: item.fillOpacity ?? item.strokeOpacity ?? 1,
|
|
stroke: item.stroke,
|
|
strokeWidth: item.strokeWidth ?? 1,
|
|
strokeOpacity: item.strokeOpacity ?? 1,
|
|
lineDash: item.lineDash ?? [0],
|
|
lineDashOffset: item.lineDashOffset ?? 0
|
|
};
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading: this.getAxisValueText(xAxis, "tooltip", xValue, datum, xKey, legendItemName),
|
|
title: legendItemName,
|
|
symbol: {
|
|
marker
|
|
},
|
|
data: [
|
|
{
|
|
label: openName,
|
|
fallbackLabel: openKey,
|
|
value: this.getAxisValueText(yAxis, "tooltip", openValue, datum, openKey, legendItemName),
|
|
missing: module_support_exports.isTooltipValueMissing(openValue)
|
|
},
|
|
{
|
|
label: highName,
|
|
fallbackLabel: highKey,
|
|
value: this.getAxisValueText(yAxis, "tooltip", highValue, datum, highKey, legendItemName),
|
|
missing: module_support_exports.isTooltipValueMissing(highValue)
|
|
},
|
|
{
|
|
label: lowName,
|
|
fallbackLabel: lowKey,
|
|
value: this.getAxisValueText(yAxis, "tooltip", lowValue, datum, lowKey, legendItemName),
|
|
missing: module_support_exports.isTooltipValueMissing(lowValue)
|
|
},
|
|
{
|
|
label: closeName,
|
|
fallbackLabel: closeKey,
|
|
value: this.getAxisValueText(yAxis, "tooltip", closeValue, datum, closeKey, legendItemName),
|
|
missing: module_support_exports.isTooltipValueMissing(closeValue)
|
|
}
|
|
]
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: yName,
|
|
itemType,
|
|
xKey,
|
|
xName,
|
|
yName,
|
|
openKey,
|
|
openName,
|
|
highKey,
|
|
highName,
|
|
lowKey,
|
|
lowName,
|
|
closeKey,
|
|
closeName,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
computeFocusBounds(opts) {
|
|
const nodeDatum = this.getNodeData()?.at(opts.datumIndex);
|
|
if (nodeDatum == null)
|
|
return;
|
|
const { centerX, y, width: width2, height: height2 } = nodeDatum;
|
|
const datum = {
|
|
x: centerX - width2 / 2,
|
|
y,
|
|
width: width2,
|
|
height: height2
|
|
};
|
|
return computeBarFocusBounds3(this, datum);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/candlestick/candlestickNode.ts
|
|
var { ExtendedPath2D: ExtendedPath2D5, BBox: BBox19 } = module_support_exports;
|
|
var CandlestickNode = class extends OhlcBaseNode {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.wickPath = new ExtendedPath2D5();
|
|
this.wickStroke = void 0;
|
|
this.wickStrokeWidth = void 0;
|
|
this.wickStrokeOpacity = void 0;
|
|
this.wickStrokeAlignment = 0;
|
|
}
|
|
/**
|
|
* High-performance wick 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).
|
|
*/
|
|
setWickProperties(wickStroke, wickStrokeWidth, wickStrokeOpacity, wickLineDash, wickLineDashOffset) {
|
|
this.__wickStroke = wickStroke;
|
|
this.__wickStrokeWidth = wickStrokeWidth;
|
|
this.__wickStrokeOpacity = wickStrokeOpacity;
|
|
this.wickLineDash = wickLineDash;
|
|
this.__wickLineDashOffset = wickLineDashOffset;
|
|
this.dirtyPath = true;
|
|
this.markDirty();
|
|
}
|
|
computeDefaultGradientFillBBox() {
|
|
const { __width: width2, __centerX: centerX, __yOpen: yOpen, __yClose: yClose } = this;
|
|
const boxTop = Math.min(yOpen, yClose);
|
|
const boxBottom = Math.max(yOpen, yClose);
|
|
const rectHeight = boxBottom - boxTop;
|
|
const x0 = centerX - width2 / 2;
|
|
const x1 = centerX + width2 / 2;
|
|
return new BBox19(x0, boxTop, x1 - x0, rectHeight);
|
|
}
|
|
updatePath() {
|
|
const {
|
|
path,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
__wickStroke: wickStroke,
|
|
__wickStrokeWidth: wickStrokeWidth,
|
|
__wickStrokeOpacity: wickStrokeOpacity,
|
|
wickLineDash,
|
|
__wickLineDashOffset: wickLineDashOffset
|
|
} = this;
|
|
const { centerX, x0, x1, y0, y1, yOpen, yClose } = this.alignedCoordinates();
|
|
const pixelRatio = this.layerManager?.canvas.pixelRatio ?? 1;
|
|
const wickStrokeAlignment = this.__wickStrokeAlignment > 0 ? pixelRatio / this.__wickStrokeAlignment / 2 % 1 : 0;
|
|
this.path.clear();
|
|
this.wickPath.clear();
|
|
const needsWickPath = wickStroke != null && wickStroke !== stroke3 || wickStrokeWidth != null && wickStrokeWidth !== strokeWidth || wickStrokeOpacity != null && wickStrokeOpacity !== strokeOpacity || wickLineDash != null && wickLineDash !== lineDash || wickLineDashOffset != null && wickLineDashOffset !== lineDashOffset;
|
|
const wickPath = needsWickPath ? this.wickPath : path;
|
|
if (Math.abs(x1 - x0) <= 3) {
|
|
wickPath.moveTo(centerX - wickStrokeAlignment, y0);
|
|
wickPath.lineTo(centerX - wickStrokeAlignment, y1);
|
|
return;
|
|
}
|
|
const boxTop = Math.min(yOpen, yClose);
|
|
const boxBottom = Math.max(yOpen, yClose);
|
|
const boxStrokeAdjustment = strokeWidth / 2;
|
|
wickPath.moveTo(centerX - wickStrokeAlignment, y0);
|
|
wickPath.lineTo(centerX - wickStrokeAlignment, boxTop + boxStrokeAdjustment);
|
|
wickPath.moveTo(centerX - wickStrokeAlignment, y1);
|
|
wickPath.lineTo(centerX - wickStrokeAlignment, boxBottom - boxStrokeAdjustment);
|
|
const rectHeight = boxBottom - boxTop - 2 * boxStrokeAdjustment;
|
|
if (rectHeight > 0) {
|
|
path.rect(
|
|
x0 + boxStrokeAdjustment,
|
|
boxTop + boxStrokeAdjustment,
|
|
x1 - x0 - 2 * boxStrokeAdjustment,
|
|
rectHeight
|
|
);
|
|
} else {
|
|
const boxMid = (boxTop + boxBottom) / 2;
|
|
path.moveTo(x0, boxMid);
|
|
path.lineTo(x1, boxMid);
|
|
}
|
|
}
|
|
drawPath(ctx) {
|
|
super.drawPath(ctx);
|
|
const { wickPath } = this;
|
|
if (wickPath.isEmpty())
|
|
return;
|
|
const {
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
__wickStroke: wickStroke = stroke3,
|
|
__wickStrokeWidth: wickStrokeWidth = strokeWidth,
|
|
__wickStrokeOpacity: wickStrokeOpacity = strokeOpacity,
|
|
wickLineDash = lineDash,
|
|
__wickLineDashOffset: wickLineDashOffset = lineDashOffset
|
|
} = this;
|
|
if (wickStrokeWidth === 0)
|
|
return;
|
|
ctx.globalAlpha *= wickStrokeOpacity;
|
|
if (typeof wickStroke === "string") {
|
|
ctx.strokeStyle = wickStroke;
|
|
}
|
|
ctx.lineWidth = wickStrokeWidth;
|
|
if (wickLineDash != null) {
|
|
ctx.setLineDash([...wickLineDash]);
|
|
}
|
|
ctx.lineDashOffset = wickLineDashOffset;
|
|
ctx.stroke(wickPath.getPath2D());
|
|
}
|
|
};
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], CandlestickNode.prototype, "wickStroke", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], CandlestickNode.prototype, "wickStrokeWidth", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], CandlestickNode.prototype, "wickStrokeOpacity", 2);
|
|
__decorateClass([
|
|
SceneArrayChangeDetection()
|
|
], CandlestickNode.prototype, "wickLineDash", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], CandlestickNode.prototype, "wickLineDashOffset", 2);
|
|
__decorateClass([
|
|
DeclaredSceneChangeDetection()
|
|
], CandlestickNode.prototype, "wickStrokeAlignment", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/ohlc/ohlcSeriesProperties.ts
|
|
var { AbstractBarSeriesProperties: AbstractBarSeriesProperties3, makeSeriesTooltip: makeSeriesTooltip8 } = module_support_exports;
|
|
var OhlcSeriesItem = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.stroke = "#333";
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesItem.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesItem.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesItem.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesItem.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesItem.prototype, "lineDashOffset", 2);
|
|
var OhlcSeriesItems = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.up = new OhlcSeriesItem();
|
|
this.down = new OhlcSeriesItem();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesItems.prototype, "up", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesItems.prototype, "down", 2);
|
|
var OhlcSeriesBaseProperties = class extends AbstractBarSeriesProperties3 {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesBaseProperties.prototype, "xKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesBaseProperties.prototype, "openKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesBaseProperties.prototype, "closeKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesBaseProperties.prototype, "highKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesBaseProperties.prototype, "lowKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesBaseProperties.prototype, "xName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesBaseProperties.prototype, "yName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesBaseProperties.prototype, "openName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesBaseProperties.prototype, "closeName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesBaseProperties.prototype, "highName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesBaseProperties.prototype, "lowName", 2);
|
|
var OhlcSeriesProperties = class extends OhlcSeriesBaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.tooltip = makeSeriesTooltip8();
|
|
this.item = new OhlcSeriesItems();
|
|
}
|
|
getStyle(itemType) {
|
|
const { strokeWidth, strokeOpacity, stroke: stroke3, lineDash, lineDashOffset } = this.item[itemType];
|
|
return {
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
opacity: 1
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesProperties.prototype, "tooltip", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesProperties.prototype, "item", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OhlcSeriesProperties.prototype, "itemStyler", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/candlestick/candlestickSeriesProperties.ts
|
|
var { makeSeriesTooltip: makeSeriesTooltip9 } = module_support_exports;
|
|
var CandlestickSeriesWick = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesWick.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesWick.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesWick.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesWick.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesWick.prototype, "lineDashOffset", 2);
|
|
var CandlestickSeriesItem = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = "#c16068";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "#333";
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.cornerRadius = 0;
|
|
this.wick = new CandlestickSeriesWick();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesItem.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesItem.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesItem.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesItem.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesItem.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesItem.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesItem.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesItem.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesItem.prototype, "wick", 2);
|
|
var CandlestickSeriesItems = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.up = new CandlestickSeriesItem();
|
|
this.down = new CandlestickSeriesItem();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesItems.prototype, "up", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesItems.prototype, "down", 2);
|
|
var CandlestickSeriesProperties = class extends OhlcSeriesBaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.item = new CandlestickSeriesItems();
|
|
this.tooltip = makeSeriesTooltip9();
|
|
}
|
|
getStyle(itemType) {
|
|
const { fill, fillOpacity, strokeWidth, strokeOpacity, stroke: stroke3, lineDash, lineDashOffset, cornerRadius, wick } = this.item[itemType];
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
cornerRadius,
|
|
opacity: 1,
|
|
wick
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesProperties.prototype, "item", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesProperties.prototype, "tooltip", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], CandlestickSeriesProperties.prototype, "itemStyler", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/candlestick/candlestickSeries.ts
|
|
var CandlestickSeries = class extends OhlcSeriesBase {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.properties = new CandlestickSeriesProperties();
|
|
}
|
|
nodeFactory() {
|
|
const node = new CandlestickNode();
|
|
node.lineCap = "butt";
|
|
return node;
|
|
}
|
|
updateDatumStyles({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
datumSelection.each((_, datum) => {
|
|
datum.style = this.getItemStyle(datum.datumIndex, isHighlight, void 0, datum.itemType);
|
|
});
|
|
}
|
|
updateDatumNodes({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
const { contextNodeData, properties } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
const { up, down } = properties.item;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
const series = this;
|
|
datumSelection.each(function updateCandlestickNode(node, datum) {
|
|
const { centerX, width: width2, y, height: height2, yOpen, yClose, crisp } = datum;
|
|
const baseStyle = datum.isRising ? up : down;
|
|
const highlightState = series.getHighlightState(highlightedDatum, isHighlight, datum.datumIndex);
|
|
const style2 = datum.style ?? contextNodeData.styles[datum.itemType][highlightState];
|
|
node.setStaticProperties(centerX, width2, y, height2, yOpen, yClose, crisp);
|
|
node.setStyleProperties(style2, fillBBox);
|
|
const styleWick = style2?.wick;
|
|
node.setWickProperties(
|
|
styleWick?.stroke,
|
|
styleWick?.strokeWidth,
|
|
styleWick?.strokeOpacity,
|
|
styleWick?.lineDash,
|
|
styleWick?.lineDashOffset
|
|
);
|
|
node.wickStrokeAlignment = baseStyle.wick.strokeWidth ?? baseStyle.strokeWidth;
|
|
});
|
|
}
|
|
legendItemSymbol() {
|
|
const { up, down } = this.properties.item;
|
|
const upColorStops = isGradientFill(up.fill) ? up.fill.colorStops.map(
|
|
(c) => typeof c === "string" ? c : { color: c.color, stop: c.stop == null ? void 0 : c.stop * 0.5 }
|
|
) : [
|
|
{ color: isPatternFill(up.fill) || isImageFill(up.fill) ? up.stroke : up.fill, stop: 0 },
|
|
{ color: isPatternFill(up.fill) || isImageFill(up.fill) ? up.stroke : up.fill, stop: 0.5 }
|
|
];
|
|
const downColorStops = isGradientFill(down.fill) ? down.fill.colorStops.map(
|
|
(c) => typeof c === "string" ? c : { color: c.color, stop: c.stop == null ? void 0 : c.stop * 0.5 }
|
|
) : [{ color: isPatternFill(down.fill) || isImageFill(down.fill) ? down.stroke : down.fill, stop: 0.5 }];
|
|
const fill = {
|
|
type: "gradient",
|
|
gradient: "linear",
|
|
rotation: 90,
|
|
colorStops: [...upColorStops, ...downColorStops],
|
|
reverse: false
|
|
};
|
|
const stroke3 = {
|
|
type: "gradient",
|
|
gradient: "linear",
|
|
rotation: 90,
|
|
colorStops: [
|
|
{ color: up.stroke, stop: 0 },
|
|
{ color: up.stroke, stop: 0.5 },
|
|
{ color: down.stroke, stop: 0.5 }
|
|
],
|
|
reverse: false
|
|
};
|
|
return {
|
|
marker: {
|
|
fill,
|
|
fillOpacity: up.fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth: up.strokeWidth ?? 1,
|
|
strokeOpacity: up.strokeOpacity ?? 1,
|
|
lineDash: up.lineDash,
|
|
lineDashOffset: up.lineDashOffset
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
const {
|
|
id,
|
|
data,
|
|
visible,
|
|
ctx: { legendManager }
|
|
} = this;
|
|
const { xKey, yName, showInLegend, legendItemName } = this.properties;
|
|
if (!data?.data.length || !xKey || legendType !== "category") {
|
|
return [];
|
|
}
|
|
return [
|
|
{
|
|
legendType: "category",
|
|
id,
|
|
itemId: id,
|
|
seriesId: id,
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId: id, itemId: id }),
|
|
label: {
|
|
text: legendItemName ?? yName ?? id
|
|
},
|
|
symbol: this.legendItemSymbol(),
|
|
legendItemName,
|
|
hideInLegend: !showInLegend
|
|
}
|
|
];
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null;
|
|
}
|
|
};
|
|
CandlestickSeries.className = "CandleStickSeries";
|
|
CandlestickSeries.type = "candlestick";
|
|
|
|
// packages/ag-charts-enterprise/src/series/candlestick/candlestickSeriesOptionsDef.ts
|
|
var { candlestickSeriesThemeableOptionsDef: candlestickSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var candlestickSeriesOptionsDef = {
|
|
...commonSeriesOptionsDefs,
|
|
...candlestickSeriesThemeableOptionsDef2,
|
|
type: required(constant("candlestick")),
|
|
xKey: required(string),
|
|
openKey: required(string),
|
|
highKey: required(string),
|
|
lowKey: required(string),
|
|
closeKey: required(string),
|
|
xName: string,
|
|
yName: string,
|
|
openName: string,
|
|
highName: string,
|
|
lowName: string,
|
|
closeName: string,
|
|
xKeyAxis: string,
|
|
yKeyAxis: string
|
|
};
|
|
candlestickSeriesOptionsDef.pickOutsideVisibleMinorAxis = undocumented(boolean);
|
|
candlestickSeriesOptionsDef.focusPriority = undocumented(number);
|
|
|
|
// packages/ag-charts-enterprise/src/series/candlestick/candlestickThemes.ts
|
|
function itemTheme(key) {
|
|
return {
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{
|
|
$if: [
|
|
{ $eq: [{ $palette: "type" }, "user-indexed"] },
|
|
key === "up" ? "transparent" : { $palette: "fill" },
|
|
{ $palette: `${key}.fill` }
|
|
]
|
|
},
|
|
["gradient", FILL_GRADIENT_LINEAR_KEYED_DEFAULTS(key)],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_KEYED_DEFAULTS(key)]
|
|
]
|
|
},
|
|
stroke: {
|
|
$if: [
|
|
{ $eq: [{ $palette: "type" }, "user-indexed"] },
|
|
{ $palette: "stroke" },
|
|
{ $palette: `${key}.stroke` }
|
|
]
|
|
}
|
|
};
|
|
}
|
|
var CANDLESTICK_SERIES_THEME = {
|
|
series: {
|
|
item: {
|
|
up: itemTheme("up"),
|
|
down: itemTheme("down")
|
|
},
|
|
tooltip: {
|
|
range: { $path: ["/tooltip/range", "nearest"] }
|
|
},
|
|
highlight: MULTI_SERIES_HIGHLIGHT_STYLE
|
|
},
|
|
animation: { enabled: false },
|
|
axes: {
|
|
["number" /* NUMBER */]: {
|
|
crosshair: {
|
|
snap: false
|
|
}
|
|
},
|
|
["ordinal-time" /* ORDINAL_TIME */]: {
|
|
groupPaddingInner: 0,
|
|
crosshair: {
|
|
enabled: true
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/candlestick/candlestickModule.ts
|
|
var { predictCartesianFinancialAxis: predictCartesianFinancialAxis2 } = module_support_exports;
|
|
var CandlestickSeriesModule = {
|
|
type: "series",
|
|
name: "candlestick",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
groupable: false,
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: candlestickSeriesOptionsDef,
|
|
matchingKeys: ["xKey", "lowKey", "highKey", "openKey", "closeKey", "normalizedTo"],
|
|
predictAxis: predictCartesianFinancialAxis2,
|
|
defaultAxes: {
|
|
y: {
|
|
type: "number" /* NUMBER */,
|
|
position: "left" /* LEFT */
|
|
},
|
|
x: {
|
|
type: "ordinal-time" /* ORDINAL_TIME */,
|
|
position: "bottom" /* BOTTOM */
|
|
}
|
|
},
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
themeTemplate: CANDLESTICK_SERIES_THEME,
|
|
create: (ctx) => new CandlestickSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/heatmap/heatmapSeriesProperties.ts
|
|
var { CartesianSeriesProperties: CartesianSeriesProperties2, makeSeriesTooltip: makeSeriesTooltip10 } = module_support_exports;
|
|
var HeatmapSeriesProperties = class extends CartesianSeriesProperties2 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.colorRange = ["black", "black"];
|
|
this.stroke = "black";
|
|
this.strokeOpacity = 1;
|
|
this.strokeWidth = 0;
|
|
this.textAlign = "center";
|
|
this.verticalAlign = "middle";
|
|
this.itemPadding = 0;
|
|
this.label = new AutoSizedLabel();
|
|
this.tooltip = makeSeriesTooltip10();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "title", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "xKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "yKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "colorKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "xName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "yName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "colorName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "colorRange", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "textAlign", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "verticalAlign", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "itemPadding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], HeatmapSeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/heatmap/heatmapSeries.ts
|
|
var {
|
|
SeriesNodePickMode: SeriesNodePickMode7,
|
|
computeBarFocusBounds: computeBarFocusBounds4,
|
|
getMissCount: getMissCount2,
|
|
valueProperty: valueProperty8,
|
|
DEFAULT_CARTESIAN_DIRECTION_KEYS: DEFAULT_CARTESIAN_DIRECTION_KEYS2,
|
|
DEFAULT_CARTESIAN_DIRECTION_NAMES: DEFAULT_CARTESIAN_DIRECTION_NAMES2,
|
|
createDatumId: createDatumId9,
|
|
ColorScale: ColorScale2,
|
|
Rect: Rect4,
|
|
PointerEvents: PointerEvents4,
|
|
addHitTestersToQuadtree: addHitTestersToQuadtree2,
|
|
findQuadtreeMatch: findQuadtreeMatch2,
|
|
updateLabelNode: updateLabelNode2,
|
|
upsertNodeDatum: upsertNodeDatum3
|
|
} = module_support_exports;
|
|
var HeatmapSeriesNodeEvent = class extends module_support_exports.CartesianSeriesNodeEvent {
|
|
constructor(type, nativeEvent, datum, series) {
|
|
super(type, nativeEvent, datum, series);
|
|
this.colorKey = series.properties.colorKey;
|
|
}
|
|
};
|
|
var textAlignFactors2 = {
|
|
left: -0.5,
|
|
center: 0,
|
|
right: -0.5
|
|
};
|
|
var verticalAlignFactors2 = {
|
|
top: -0.5,
|
|
middle: 0,
|
|
bottom: -0.5
|
|
};
|
|
var HeatmapSeries = class extends module_support_exports.CartesianSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
propertyKeys: {
|
|
...DEFAULT_CARTESIAN_DIRECTION_KEYS2,
|
|
color: ["colorKey"]
|
|
},
|
|
propertyNames: {
|
|
...DEFAULT_CARTESIAN_DIRECTION_NAMES2,
|
|
color: ["colorName"]
|
|
},
|
|
categoryKey: void 0,
|
|
pickModes: [SeriesNodePickMode7.NEAREST_NODE, SeriesNodePickMode7.EXACT_SHAPE_MATCH],
|
|
pathsPerSeries: []
|
|
});
|
|
this.properties = new HeatmapSeriesProperties();
|
|
this.NodeEvent = HeatmapSeriesNodeEvent;
|
|
this.colorScale = new ColorScale2();
|
|
}
|
|
async processData(dataController) {
|
|
const xAxis = this.axes["x" /* X */];
|
|
const yAxis = this.axes["y" /* Y */];
|
|
if (!xAxis || !yAxis) {
|
|
return;
|
|
}
|
|
const { xKey, yKey, colorRange, colorKey } = this.properties;
|
|
const xScale = this.axes["x" /* X */]?.scale;
|
|
const yScale = this.axes["y" /* Y */]?.scale;
|
|
const { xScaleType, yScaleType } = this.getScaleInformation({ xScale, yScale });
|
|
const colorScaleType = this.colorScale.type;
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
const { dataModel, processedData } = await this.requestDataModel(dataController, this.data, {
|
|
props: [
|
|
valueProperty8(xKey, xScaleType, { id: "xValue", allowNullKey }),
|
|
valueProperty8(yKey, yScaleType, { id: "yValue", allowNullKey }),
|
|
...colorKey ? [valueProperty8(colorKey, colorScaleType, { id: "colorValue", invalidValue: void 0 })] : []
|
|
]
|
|
});
|
|
if (this.isColorScaleValid()) {
|
|
const colorKeyIdx = dataModel.resolveProcessedDataIndexById(this, "colorValue");
|
|
const rawDomain = processedData.domain.values[colorKeyIdx].filter((v) => v != null);
|
|
const domain = extent(rawDomain);
|
|
this.colorScale.domain = domain ?? [];
|
|
if (domain?.length && domain[0] === domain[1]) {
|
|
const midIndex = Math.floor(colorRange.length / 2);
|
|
this.colorScale.range = [colorRange[midIndex], colorRange[midIndex]];
|
|
} else {
|
|
this.colorScale.range = colorRange;
|
|
}
|
|
this.colorScale.update();
|
|
}
|
|
}
|
|
isColorScaleValid() {
|
|
const { colorKey } = this.properties;
|
|
if (!colorKey) {
|
|
return false;
|
|
}
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData) {
|
|
return false;
|
|
}
|
|
const colorDataIdx = dataModel.resolveProcessedDataIndexById(this, "colorValue");
|
|
const dataCount = processedData.input.count;
|
|
const missCount = getMissCount2(this, processedData.defs.values[colorDataIdx].missing);
|
|
const colorKeyIdx = dataModel.resolveProcessedDataIndexById(this, "colorValue");
|
|
const actualCount = processedData.domain.values[colorKeyIdx].filter((v) => v != null).length;
|
|
const colorDataMissing = dataCount === 0 || dataCount === missCount || actualCount === 0;
|
|
return !colorDataMissing;
|
|
}
|
|
xCoordinateRange(xValue, pixelSize) {
|
|
const xScale = this.axes["x" /* X */].scale;
|
|
const xOffset = pixelSize * (xScale.bandwidth ?? 0) / 2;
|
|
const x = xScale.convert(xValue) + xOffset;
|
|
const width2 = pixelSize * (xScale.bandwidth ?? 10);
|
|
return [x, x + width2];
|
|
}
|
|
yCoordinateRange(yValues, pixelSize) {
|
|
const yScale = this.axes["y" /* Y */].scale;
|
|
const yOffset = pixelSize * (yScale.bandwidth ?? 0) / 2;
|
|
const y = yScale.convert(yValues[0]) + yOffset;
|
|
const height2 = pixelSize * (yScale.bandwidth ?? 10);
|
|
return [y, y + height2];
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData)
|
|
return { domain: [] };
|
|
if (direction === "x" /* X */) {
|
|
const domain = dataModel.getDomain(this, `xValue`, "value", processedData).domain;
|
|
return { domain };
|
|
} else {
|
|
const domain = dataModel.getDomain(this, `yValue`, "value", processedData).domain;
|
|
return { domain };
|
|
}
|
|
}
|
|
getSeriesRange() {
|
|
return [Number.NaN, Number.NaN];
|
|
}
|
|
/**
|
|
* Template method hook: Validates preconditions for createNodeData.
|
|
* Overrides base to add heatmap-specific category axis validation.
|
|
*/
|
|
validateCreateNodeDataPreconditions() {
|
|
const result = super.validateCreateNodeDataPreconditions();
|
|
if (!result)
|
|
return void 0;
|
|
const { xAxis, yAxis } = result;
|
|
if (xAxis.type !== "category" || yAxis.type !== "category") {
|
|
logger_exports.warnOnce(
|
|
`Heatmap series expected axes to have "category" type, but received "${xAxis.type}" and "${yAxis.type}" instead.`
|
|
);
|
|
return void 0;
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Template method hook: Iterates over data and creates/updates node datums.
|
|
*/
|
|
populateNodeData(ctx) {
|
|
for (const [datumIndex, datum] of ctx.rawData.entries()) {
|
|
const nodeDatum = upsertNodeDatum3(
|
|
ctx,
|
|
{ datumIndex, datum },
|
|
(c, p) => this.createNodeDatum(c, p.datumIndex, p.datum),
|
|
(c, n, p) => this.updateNodeDatum(c, n, p.datumIndex, p.datum)
|
|
);
|
|
if (nodeDatum) {
|
|
const labelDatum = this.createLabelDatum(ctx, datumIndex, datum, nodeDatum);
|
|
if (labelDatum) {
|
|
ctx.labels.push(labelDatum);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Template method hook: Creates the result object shell.
|
|
*/
|
|
initializeResult(ctx) {
|
|
return {
|
|
itemId: this.properties.yKey ?? this.id,
|
|
nodeData: ctx.nodes,
|
|
labelData: ctx.labels,
|
|
scales: this.calculateScaling(),
|
|
visible: this.visible
|
|
};
|
|
}
|
|
/**
|
|
* Template method hook: Creates the shared context for datum creation.
|
|
* Caches expensive lookups and computations that are constant across all datums.
|
|
*/
|
|
createNodeDatumContext(xAxis, yAxis) {
|
|
const { dataModel, processedData, contextNodeData } = this;
|
|
if (!dataModel || !processedData)
|
|
return void 0;
|
|
const { xKey, xName, yKey, yName, colorKey, colorName, textAlign, verticalAlign, itemPadding } = this.properties;
|
|
const xScale = xAxis.scale;
|
|
const yScale = yAxis.scale;
|
|
const xValues = dataModel.resolveColumnById(this, `xValue`, processedData);
|
|
const yValues = dataModel.resolveColumnById(this, `yValue`, processedData);
|
|
const colorValues = colorKey ? dataModel.resolveColumnById(this, `colorValue`, processedData) : void 0;
|
|
const colorDomain = colorKey ? dataModel.getDomain(this, "colorValue", "value", processedData).domain : [];
|
|
const width2 = xScale.bandwidth ?? 10;
|
|
const height2 = yScale.bandwidth ?? 10;
|
|
const rawData = processedData.dataSources.get(this.id)?.data ?? [];
|
|
const canIncrementallyUpdate = contextNodeData?.nodeData != null && processedData.changeDescription != null;
|
|
return {
|
|
// Base context fields
|
|
xAxis,
|
|
yAxis,
|
|
xScale,
|
|
yScale,
|
|
rawData,
|
|
xValues,
|
|
xKey,
|
|
yKey,
|
|
xName,
|
|
yName,
|
|
animationEnabled: !this.ctx.animationManager.isSkipped(),
|
|
canIncrementallyUpdate,
|
|
nodes: canIncrementallyUpdate ? contextNodeData.nodeData : [],
|
|
nodeIndex: 0,
|
|
// Heatmap-specific positioning
|
|
xOffset: (xScale.bandwidth ?? 0) / 2,
|
|
yOffset: (yScale.bandwidth ?? 0) / 2,
|
|
width: width2,
|
|
height: height2,
|
|
textAlignFactor: (width2 - 2 * itemPadding) * textAlignFactors2[textAlign],
|
|
verticalAlignFactor: (height2 - 2 * itemPadding) * verticalAlignFactors2[verticalAlign],
|
|
// Heatmap-specific data
|
|
yValues,
|
|
colorKey,
|
|
colorName,
|
|
colorValues,
|
|
colorDomain,
|
|
itemPadding,
|
|
// Label support - labels are always rebuilt from scratch (not incrementally updated)
|
|
labels: [],
|
|
labelIndex: 0
|
|
};
|
|
}
|
|
/**
|
|
* Creates a skeleton HeatmapNodeDatum with minimal required fields.
|
|
* The node will be populated by updateNodeDatum.
|
|
*/
|
|
createSkeletonNodeDatum(ctx, datumIndex, datum) {
|
|
const { xKey, yKey, width: width2, height: height2, colorValues } = ctx;
|
|
const xDatum = ctx.xValues[datumIndex];
|
|
const yDatum = ctx.yValues[datumIndex];
|
|
const colorValue = colorValues?.[datumIndex];
|
|
return {
|
|
series: this,
|
|
datumIndex,
|
|
yKey,
|
|
xKey,
|
|
xValue: xDatum,
|
|
yValue: yDatum,
|
|
colorValue,
|
|
datum,
|
|
point: { x: 0, y: 0, size: 0 },
|
|
width: width2,
|
|
height: height2,
|
|
midPoint: { x: 0, y: 0 },
|
|
missing: colorValues != null && colorValue == null,
|
|
style: {}
|
|
};
|
|
}
|
|
/**
|
|
* Updates an existing HeatmapNodeDatum in-place.
|
|
*/
|
|
updateNodeDatum(ctx, node, datumIndex, datum) {
|
|
const { xScale, yScale, xOffset, yOffset, width: width2, height: height2, xKey, yKey, colorValues } = ctx;
|
|
const mutableNode = node;
|
|
const xDatum = ctx.xValues[datumIndex];
|
|
const yDatum = ctx.yValues[datumIndex];
|
|
const x = xScale.convert(xDatum) + xOffset;
|
|
const y = yScale.convert(yDatum) + yOffset;
|
|
if (!Number.isFinite(x) || !Number.isFinite(y))
|
|
return;
|
|
const colorValue = colorValues?.[datumIndex];
|
|
mutableNode.datumIndex = datumIndex;
|
|
mutableNode.datum = datum;
|
|
mutableNode.yKey = yKey;
|
|
mutableNode.xKey = xKey;
|
|
mutableNode.xValue = xDatum;
|
|
mutableNode.yValue = yDatum;
|
|
mutableNode.colorValue = colorValue;
|
|
mutableNode.width = width2;
|
|
mutableNode.height = height2;
|
|
mutableNode.missing = colorValues != null && colorValue == null;
|
|
const mutablePoint = mutableNode.point;
|
|
mutablePoint.x = x;
|
|
mutablePoint.y = y;
|
|
mutablePoint.size = 0;
|
|
mutableNode.midPoint.x = x;
|
|
mutableNode.midPoint.y = y;
|
|
mutableNode.style = this.getItemStyle({ datumIndex, datum, colorValue }, false);
|
|
}
|
|
/**
|
|
* Creates a HeatmapNodeDatum for a single data point.
|
|
* Returns undefined for invalid data points (e.g., null/undefined keys when not allowed).
|
|
*/
|
|
createNodeDatum(ctx, datumIndex, datum) {
|
|
const { xScale, yScale, xOffset, yOffset } = ctx;
|
|
const xDatum = ctx.xValues[datumIndex];
|
|
const yDatum = ctx.yValues[datumIndex];
|
|
const x = xScale.convert(xDatum) + xOffset;
|
|
const y = yScale.convert(yDatum) + yOffset;
|
|
if (!Number.isFinite(x) || !Number.isFinite(y)) {
|
|
return void 0;
|
|
}
|
|
const node = this.createSkeletonNodeDatum(ctx, datumIndex, datum);
|
|
this.updateNodeDatum(ctx, node, datumIndex, datum);
|
|
return node;
|
|
}
|
|
createLabelDatum(ctx, datumIndex, datum, nodeDatum) {
|
|
const { label } = this.properties;
|
|
const {
|
|
width: width2,
|
|
height: height2,
|
|
textAlignFactor,
|
|
verticalAlignFactor,
|
|
itemPadding,
|
|
colorKey,
|
|
colorName,
|
|
colorDomain,
|
|
xKey,
|
|
yKey,
|
|
xName,
|
|
yName
|
|
} = ctx;
|
|
const colorValue = ctx.colorValues?.[datumIndex];
|
|
const labelText = label.enabled && colorValue != null ? this.getLabelText(
|
|
colorValue,
|
|
datum,
|
|
colorKey,
|
|
"color",
|
|
colorDomain,
|
|
label,
|
|
{ value: colorValue, datum, colorKey, colorName, xKey, yKey, xName, yName }
|
|
) : void 0;
|
|
const sizeFittingHeight = () => ({ width: width2, height: height2, meta: null });
|
|
const labels = formatLabels(
|
|
toPlainText(labelText),
|
|
this.properties.label,
|
|
void 0,
|
|
this.properties.label,
|
|
{ padding: itemPadding },
|
|
sizeFittingHeight
|
|
);
|
|
if (labels?.label == null) {
|
|
return void 0;
|
|
}
|
|
const { text: text2, fontSize, lineHeight, height: labelHeight } = labels.label;
|
|
const { fontStyle, fontFamily, fontWeight: fontWeight2, color: color2 } = this.properties.label;
|
|
const { textAlign, verticalAlign } = this.properties;
|
|
const lx = nodeDatum.point.x + textAlignFactor * (width2 - 2 * itemPadding);
|
|
const ly = nodeDatum.point.y + verticalAlignFactor * (height2 - 2 * itemPadding) - (labels.height - labelHeight) * 0.5;
|
|
return {
|
|
series: this,
|
|
datum,
|
|
datumIndex,
|
|
text: text2,
|
|
fontSize,
|
|
lineHeight,
|
|
fontStyle,
|
|
fontFamily,
|
|
fontWeight: fontWeight2,
|
|
color: color2,
|
|
textAlign,
|
|
textBaseline: verticalAlign,
|
|
x: lx,
|
|
y: ly,
|
|
style: nodeDatum.style
|
|
};
|
|
}
|
|
nodeFactory() {
|
|
return new Rect4();
|
|
}
|
|
update(params) {
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
return super.update(params);
|
|
}
|
|
updateDatumSelection(opts) {
|
|
const { nodeData, datumSelection } = opts;
|
|
const data = nodeData ?? [];
|
|
return datumSelection.update(data);
|
|
}
|
|
getItemStyle({ datumIndex, datum, colorValue }, isHighlight, highlightState) {
|
|
const { properties } = this;
|
|
const { itemStyler, stroke: stroke3, strokeWidth, strokeOpacity } = properties;
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex, highlightState);
|
|
const style2 = mergeDefaults(highlightStyle, {
|
|
fill: this.isColorScaleValid() && colorValue != null ? this.colorScale.convert(colorValue) : "transparent",
|
|
fillOpacity: 1,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
opacity: 1
|
|
});
|
|
let overrides;
|
|
if (itemStyler != null && datumIndex != null) {
|
|
overrides = this.cachedDatumCallback(createDatumId9(datumIndex, isHighlight ? "highlight" : "node"), () => {
|
|
const params = this.makeItemStylerParams(datum, datumIndex, isHighlight, style2);
|
|
return this.callWithContext(itemStyler, params);
|
|
});
|
|
}
|
|
return overrides ? mergeDefaults(overrides, style2) : style2;
|
|
}
|
|
makeItemStylerParams(datum, datumIndex, isHighlight, style2) {
|
|
const { id: seriesId, properties } = this;
|
|
const { xKey, yKey } = properties;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightState = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
seriesId,
|
|
datum,
|
|
xKey,
|
|
yKey,
|
|
highlightState,
|
|
...style2,
|
|
fill
|
|
};
|
|
}
|
|
updateDatumStyles({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
datumSelection.each((_, nodeDatum) => {
|
|
const highlightState = this.getHighlightState(activeHighlight, isHighlight, nodeDatum.datumIndex);
|
|
nodeDatum.style = this.getItemStyle(nodeDatum, isHighlight, highlightState);
|
|
});
|
|
}
|
|
updateDatumNodes({
|
|
datumSelection
|
|
}) {
|
|
const xAxis = this.axes["x" /* X */];
|
|
const [visibleMin, visibleMax] = xAxis?.visibleRange ?? [];
|
|
const isZoomed = visibleMin !== 0 || visibleMax !== 1;
|
|
const crisp = !isZoomed;
|
|
datumSelection.each((rect2, nodeDatum) => {
|
|
const { point, width: width2, height: height2, style: style2 } = nodeDatum;
|
|
rect2.setStyleProperties(style2);
|
|
rect2.crisp = crisp;
|
|
rect2.x = Math.floor(point.x - width2 / 2);
|
|
rect2.y = Math.floor(point.y - height2 / 2);
|
|
rect2.width = Math.ceil(width2);
|
|
rect2.height = Math.ceil(height2);
|
|
});
|
|
}
|
|
updateLabelSelection(opts) {
|
|
const { labelData, labelSelection } = opts;
|
|
const { enabled } = this.properties.label;
|
|
const data = enabled ? labelData : [];
|
|
return labelSelection.update(data);
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const { isHighlight = false } = opts;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
opts.labelSelection.each((text2, datum) => {
|
|
text2.pointerEvents = PointerEvents4.None;
|
|
text2.text = datum.text;
|
|
text2.fillOpacity = this.getHighlightStyle(isHighlight, datum.datumIndex)?.opacity ?? 1;
|
|
updateLabelNode2(
|
|
this,
|
|
text2,
|
|
this.properties,
|
|
this.properties.label,
|
|
datum,
|
|
isHighlight,
|
|
activeHighlight
|
|
);
|
|
});
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, axes, properties, colorScale, ctx } = this;
|
|
const { formatManager } = ctx;
|
|
const { xKey, xName, yKey, yName, colorKey, colorName, colorRange, title, legendItemName, tooltip } = properties;
|
|
const xAxis = axes["x" /* X */];
|
|
const yAxis = axes["y" /* Y */];
|
|
if (!dataModel || !processedData || !xAxis || !yAxis)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const xValue = dataModel.resolveColumnById(this, `xValue`, processedData)[datumIndex];
|
|
const yValue = dataModel.resolveColumnById(this, `yValue`, processedData)[datumIndex];
|
|
const colorValue = colorKey != null && this.isColorScaleValid() ? dataModel.resolveColumnById(this, `colorValue`, processedData)[datumIndex] : void 0;
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
if (xValue === void 0 && !allowNullKeys)
|
|
return;
|
|
const data = [];
|
|
let fill;
|
|
if (colorValue == null) {
|
|
fill = colorRange[0];
|
|
} else {
|
|
fill = colorScale.convert(colorValue);
|
|
const domain = dataModel.getDomain(this, `colorValue`, "value", processedData).domain;
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value: colorValue,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key: colorKey,
|
|
source: "tooltip",
|
|
property: "color",
|
|
domain,
|
|
boundSeries: this.getFormatterContext("color"),
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
});
|
|
data.push({ label: colorName, fallbackLabel: colorKey, value: content ?? formatValue(colorValue) });
|
|
}
|
|
data.push(
|
|
{
|
|
label: xName,
|
|
fallbackLabel: xKey,
|
|
value: this.getAxisValueText(xAxis, "tooltip", xValue, datum, xKey, legendItemName)
|
|
},
|
|
{
|
|
label: yName,
|
|
fallbackLabel: yKey,
|
|
value: this.getAxisValueText(yAxis, "tooltip", yValue, datum, yKey, legendItemName)
|
|
}
|
|
);
|
|
const format = this.getItemStyle({ datumIndex, datum, colorValue }, false);
|
|
if (format.fill != null) {
|
|
fill = format.fill;
|
|
}
|
|
const symbol = fill == null ? void 0 : {
|
|
marker: {
|
|
shape: "square",
|
|
fill,
|
|
fillOpacity: 1,
|
|
stroke: void 0,
|
|
strokeWidth: 0,
|
|
strokeOpacity: 1,
|
|
lineDash: [0],
|
|
lineDashOffset: 0
|
|
}
|
|
};
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{ title: title ?? legendItemName, symbol, data },
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title,
|
|
xKey,
|
|
xName,
|
|
yKey,
|
|
yName,
|
|
colorKey,
|
|
colorName,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
getLegendData(legendType) {
|
|
if (legendType !== "gradient" || !this.isColorScaleValid() || !this.dataModel) {
|
|
return [];
|
|
}
|
|
return [
|
|
{
|
|
legendType: "gradient",
|
|
enabled: this.visible,
|
|
seriesId: this.id,
|
|
series: this.getFormatterContext("color"),
|
|
colorDomain: this.colorScale.domain,
|
|
colorRange: this.colorScale.range
|
|
}
|
|
];
|
|
}
|
|
isLabelEnabled() {
|
|
return this.properties.label.enabled && Boolean(this.properties.colorKey);
|
|
}
|
|
getBandScalePadding() {
|
|
return { inner: 0, outer: 0 };
|
|
}
|
|
computeFocusBounds({ datumIndex }) {
|
|
const datum = this.contextNodeData?.nodeData[datumIndex];
|
|
if (datum === void 0)
|
|
return void 0;
|
|
const { width: width2, height: height2, midPoint } = datum;
|
|
const focusRect = { x: midPoint.x - width2 / 2, y: midPoint.y - height2 / 2, width: width2, height: height2 };
|
|
return computeBarFocusBounds4(this, focusRect);
|
|
}
|
|
initQuadTree(quadtree) {
|
|
addHitTestersToQuadtree2(quadtree, this.datumNodesIter());
|
|
}
|
|
pickNodesExactShape(point) {
|
|
const item = findQuadtreeMatch2(this, point);
|
|
return item != null && item.distance <= 0 ? [item.datum] : [];
|
|
}
|
|
pickNodeClosestDatum(point) {
|
|
return findQuadtreeMatch2(this, point);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null || this.properties.label.itemStyler != null || this.isColorScaleValid();
|
|
}
|
|
};
|
|
HeatmapSeries.className = "HeatmapSeries";
|
|
HeatmapSeries.type = "heatmap";
|
|
|
|
// packages/ag-charts-enterprise/src/series/heatmap/heatmapSeriesOptionsDef.ts
|
|
var { heatmapSeriesThemeableOptionsDef: heatmapSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var heatmapSeriesOptionsDef = {
|
|
...without(heatmapSeriesThemeableOptionsDef2, ["showInLegend"]),
|
|
...without(commonSeriesOptionsDefs, ["showInLegend"]),
|
|
type: required(constant("heatmap")),
|
|
xKey: required(string),
|
|
yKey: required(string),
|
|
xKeyAxis: string,
|
|
yKeyAxis: string,
|
|
colorKey: string,
|
|
xName: string,
|
|
yName: string,
|
|
colorName: string,
|
|
colorRange: arrayOf(color)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/heatmap/heatmapThemes.ts
|
|
var HEATMAP_SERIES_THEME = {
|
|
series: {
|
|
stroke: {
|
|
$if: [
|
|
{ $eq: [{ $palette: "type" }, "inbuilt"] },
|
|
{ $ref: "chartBackgroundColor" },
|
|
{ $path: ["/0", { $palette: "stroke" }, { $palette: "strokes" }] }
|
|
]
|
|
},
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, void 0] },
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
color: { $ref: "textColor" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
wrapping: "on-space",
|
|
overflowStrategy: "ellipsis"
|
|
},
|
|
itemPadding: 3,
|
|
highlight: {
|
|
unhighlightedItem: {
|
|
opacity: 0.6
|
|
}
|
|
}
|
|
},
|
|
gradientLegend: {
|
|
enabled: true
|
|
}
|
|
};
|
|
HEATMAP_SERIES_THEME.series.colorRange = {
|
|
$if: [{ $eq: [{ $palette: "type" }, "inbuilt"] }, { $palette: "divergingColors" }, SAFE_RANGE2_OPERATION]
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/heatmap/heatmapModule.ts
|
|
var HeatmapSeriesModule = {
|
|
type: "series",
|
|
name: "heatmap",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: heatmapSeriesOptionsDef,
|
|
defaultAxes: {
|
|
y: { type: "category" /* CATEGORY */, position: "left" /* LEFT */ },
|
|
x: { type: "category" /* CATEGORY */, position: "bottom" /* BOTTOM */ }
|
|
},
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
themeTemplate: HEATMAP_SERIES_THEME,
|
|
create: (ctx) => new HeatmapSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/ohlc/ohlcSeries.ts
|
|
var OhlcSeries = class extends OhlcSeriesBase {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.properties = new OhlcSeriesProperties();
|
|
}
|
|
nodeFactory() {
|
|
const node = new OhlcNode();
|
|
node.lineCap = "square";
|
|
return node;
|
|
}
|
|
updateDatumStyles({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
datumSelection.each((_, datum) => {
|
|
datum.style = this.getItemStyle(datum.datumIndex, isHighlight, void 0, datum.itemType);
|
|
});
|
|
}
|
|
updateDatumNodes({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
const { contextNodeData, properties } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
const { up, down } = properties.item;
|
|
const series = this;
|
|
datumSelection.each(function updateOhlcNode(node, datum) {
|
|
const { centerX, width: width2, y, height: height2, yOpen, yClose, crisp } = datum;
|
|
const baseStyle = datum.isRising ? up : down;
|
|
node.setStaticProperties(centerX, width2, y, height2, yOpen, yClose, crisp);
|
|
const style2 = datum.style ?? contextNodeData.styles[datum.itemType][series.getHighlightState(highlightedDatum, isHighlight, datum.datumIndex)];
|
|
node.setStyleProperties(style2);
|
|
node.strokeAlignment = baseStyle.strokeWidth;
|
|
});
|
|
}
|
|
getLegendData(legendType) {
|
|
const {
|
|
id,
|
|
data,
|
|
ctx: { legendManager },
|
|
visible
|
|
} = this;
|
|
const {
|
|
xKey,
|
|
yName,
|
|
item: { up, down },
|
|
showInLegend,
|
|
legendItemName
|
|
} = this.properties;
|
|
if (!data?.data.length || !xKey || legendType !== "category") {
|
|
return [];
|
|
}
|
|
const fill = {
|
|
type: "gradient",
|
|
gradient: "linear",
|
|
colorSpace: "rgb",
|
|
colorStops: [
|
|
{ color: up.stroke, stop: 0 },
|
|
{ color: up.stroke, stop: 0.5 },
|
|
{ color: down.stroke, stop: 0.5 }
|
|
],
|
|
rotation: 90
|
|
};
|
|
return [
|
|
{
|
|
legendType: "category",
|
|
id,
|
|
itemId: id,
|
|
seriesId: id,
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId: id, itemId: id }),
|
|
label: {
|
|
text: legendItemName ?? yName ?? id
|
|
},
|
|
symbol: {
|
|
marker: {
|
|
fill,
|
|
fillOpacity: up.strokeOpacity,
|
|
stroke: void 0,
|
|
strokeWidth: 0,
|
|
strokeOpacity: 1,
|
|
lineDash: [0],
|
|
lineDashOffset: 0
|
|
}
|
|
},
|
|
legendItemName,
|
|
hideInLegend: !showInLegend
|
|
}
|
|
];
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null;
|
|
}
|
|
};
|
|
OhlcSeries.className = "ohlc";
|
|
OhlcSeries.type = "ohlc";
|
|
|
|
// packages/ag-charts-enterprise/src/series/ohlc/ohlcSeriesOptionsDef.ts
|
|
var { ohlcSeriesThemeableOptionsDef: ohlcSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var ohlcSeriesOptionsDef = {
|
|
...commonSeriesOptionsDefs,
|
|
...ohlcSeriesThemeableOptionsDef2,
|
|
type: required(constant("ohlc")),
|
|
xKey: required(string),
|
|
openKey: required(string),
|
|
highKey: required(string),
|
|
lowKey: required(string),
|
|
closeKey: required(string),
|
|
xKeyAxis: string,
|
|
yKeyAxis: string,
|
|
xName: string,
|
|
yName: string,
|
|
openName: string,
|
|
highName: string,
|
|
lowName: string,
|
|
closeName: string
|
|
};
|
|
ohlcSeriesOptionsDef.pickOutsideVisibleMinorAxis = undocumented(boolean);
|
|
ohlcSeriesOptionsDef.focusPriority = undocumented(number);
|
|
|
|
// packages/ag-charts-enterprise/src/series/ohlc/ohlcModule.ts
|
|
var { predictCartesianFinancialAxis: predictCartesianFinancialAxis3 } = module_support_exports;
|
|
var themeTemplate8 = {
|
|
animation: { enabled: false },
|
|
series: {
|
|
item: {
|
|
up: {
|
|
stroke: {
|
|
$if: [
|
|
{ $eq: [{ $palette: "type" }, "user-indexed"] },
|
|
{ $palette: "stroke" },
|
|
{ $palette: "up.stroke" }
|
|
]
|
|
}
|
|
},
|
|
down: {
|
|
stroke: {
|
|
$if: [
|
|
{ $eq: [{ $palette: "type" }, "user-indexed"] },
|
|
{ $palette: "stroke" },
|
|
{ $palette: "down.stroke" }
|
|
]
|
|
}
|
|
}
|
|
},
|
|
tooltip: {
|
|
range: { $path: ["/tooltip/range", "nearest"] }
|
|
},
|
|
highlight: MULTI_SERIES_HIGHLIGHT_STYLE
|
|
},
|
|
axes: {
|
|
["number" /* NUMBER */]: {
|
|
crosshair: {
|
|
snap: false
|
|
}
|
|
},
|
|
["ordinal-time" /* ORDINAL_TIME */]: {
|
|
groupPaddingInner: 0,
|
|
crosshair: {
|
|
enabled: true
|
|
}
|
|
}
|
|
}
|
|
};
|
|
var OhlcSeriesModule = {
|
|
type: "series",
|
|
name: "ohlc",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: ohlcSeriesOptionsDef,
|
|
matchingKeys: ["xKey", "lowKey", "highKey", "openKey", "closeKey", "normalizedTo"],
|
|
predictAxis: predictCartesianFinancialAxis3,
|
|
defaultAxes: {
|
|
y: { type: "number" /* NUMBER */, position: "left" /* LEFT */ },
|
|
x: { type: "ordinal-time" /* ORDINAL_TIME */, position: "bottom" /* BOTTOM */ }
|
|
},
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
themeTemplate: themeTemplate8,
|
|
create: (ctx) => new OhlcSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-area/rangeAreaAggregation.ts
|
|
function aggregateRangeAreaData(scale2, xValues, highValues, lowValues, domainInput, smallestKeyInterval, xNeedsValueOf, yNeedsValueOf) {
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeExtremesAggregation([d0, d1], xValues, highValues, lowValues, {
|
|
smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf
|
|
});
|
|
}
|
|
var memoizedAggregateRangeAreaData = simpleMemorize2(aggregateRangeAreaData);
|
|
function aggregateRangeAreaDataFromDataModel(scale2, dataModel, processedData, series, existingFilters) {
|
|
const xValues = dataModel.resolveKeysById(series, "xValue", processedData);
|
|
const highValues = dataModel.resolveColumnById(series, "yHighValue", processedData);
|
|
const lowValues = dataModel.resolveColumnById(series, "yLowValue", processedData);
|
|
const domainInput = dataModel.getDomain(series, "xValue", "key", processedData);
|
|
const xNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "xValue", processedData);
|
|
const yNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "yHighValue", processedData) ?? dataModel.resolveColumnNeedsValueOf(series, "yLowValue", processedData);
|
|
if (existingFilters) {
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeExtremesAggregation([d0, d1], xValues, highValues, lowValues, {
|
|
smallestKeyInterval: processedData.reduced?.smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
existingFilters
|
|
});
|
|
}
|
|
return memoizedAggregateRangeAreaData(
|
|
scale2,
|
|
xValues,
|
|
highValues,
|
|
lowValues,
|
|
domainInput,
|
|
processedData.reduced?.smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf
|
|
);
|
|
}
|
|
function aggregateRangeAreaDataFromDataModelPartial(scale2, dataModel, processedData, series, targetRange, existingFilters) {
|
|
const xValues = dataModel.resolveKeysById(series, "xValue", processedData);
|
|
const highValues = dataModel.resolveColumnById(series, "yHighValue", processedData);
|
|
const lowValues = dataModel.resolveColumnById(series, "yLowValue", processedData);
|
|
const domainInput = dataModel.getDomain(series, "xValue", "key", processedData);
|
|
const xNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "xValue", processedData);
|
|
const yNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "yHighValue", processedData) ?? dataModel.resolveColumnNeedsValueOf(series, "yLowValue", processedData);
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeExtremesAggregationPartial([d0, d1], xValues, highValues, lowValues, {
|
|
smallestKeyInterval: processedData.reduced?.smallestKeyInterval,
|
|
targetRange,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
existingFilters
|
|
});
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-area/rangeAreaIntersection.ts
|
|
function getYValueAtX({ span }, x) {
|
|
switch (span.type) {
|
|
case "linear":
|
|
case "step":
|
|
case "multi-line": {
|
|
const t = (x - span.x0) / (span.x1 - span.x0);
|
|
return span.y0 + t * (span.y1 - span.y0);
|
|
}
|
|
case "cubic": {
|
|
const { cp0x, cp0y, cp1x, cp1y, cp2x, cp2y, cp3x, cp3y } = span;
|
|
let t = 0.5;
|
|
const tolerance = 1e-6;
|
|
for (let i = 0; i < 10; i++) {
|
|
const mt2 = 1 - t;
|
|
const xt = mt2 * mt2 * mt2 * cp0x + 3 * mt2 * mt2 * t * cp1x + 3 * mt2 * t * t * cp2x + t * t * t * cp3x;
|
|
const fx = xt - x;
|
|
if (Math.abs(fx) < tolerance)
|
|
break;
|
|
const dxdt = 3 * mt2 * mt2 * (cp1x - cp0x) + 6 * mt2 * t * (cp2x - cp1x) + 3 * t * t * (cp3x - cp2x);
|
|
if (Math.abs(dxdt) < 1e-12)
|
|
break;
|
|
t = t - fx / dxdt;
|
|
t = Math.max(0, Math.min(1, t));
|
|
}
|
|
const mt = 1 - t;
|
|
return mt * mt * mt * cp0y + 3 * mt * mt * t * cp1y + 3 * mt * t * t * cp2y + t * t * t * cp3y;
|
|
}
|
|
}
|
|
}
|
|
function findSpanForX(spans, x, startIndex = 0) {
|
|
for (let i = startIndex; i < spans.length; i++) {
|
|
const span = spans[i];
|
|
const [start2, end3] = spanRange(span.span);
|
|
if (x >= start2.x && x <= end3.x) {
|
|
return { span, index: i };
|
|
}
|
|
if (x < start2.x) {
|
|
break;
|
|
}
|
|
}
|
|
return { span: null, index: startIndex };
|
|
}
|
|
function checkForIntersection(highSpans, lowSpans, x, wasInverted, spanIndex) {
|
|
const high = findSpanForX(highSpans, x, spanIndex);
|
|
const low = findSpanForX(lowSpans, x, spanIndex);
|
|
if (!high.span || !low.span) {
|
|
return { intersection: null, spanIndex: high.index, isInverted: wasInverted };
|
|
}
|
|
const highY = getYValueAtX(high.span, x);
|
|
const lowY = getYValueAtX(low.span, x);
|
|
const isInverted = highY > lowY;
|
|
const intersection = wasInverted === isInverted ? null : { x, y: highY };
|
|
return { intersection, spanIndex: high.index, isInverted };
|
|
}
|
|
function findRangeAreaIntersections(highSpans, lowSpans, minX, maxX, initiallyInverted = false) {
|
|
if (highSpans.length === 0 || lowSpans.length === 0)
|
|
return [];
|
|
const intersections = [];
|
|
let wasInverted = initiallyInverted;
|
|
let spanIndex = 0;
|
|
for (let x = minX; x <= maxX; x += 0.5) {
|
|
const result = checkForIntersection(highSpans, lowSpans, x, wasInverted, spanIndex);
|
|
if (result.intersection) {
|
|
intersections.push(result.intersection.x);
|
|
}
|
|
spanIndex = result.spanIndex;
|
|
wasInverted = result.isInverted;
|
|
}
|
|
return intersections;
|
|
}
|
|
function calculateIntersectionSegments(intersections, seriesRect, chartSize, startsInverted, style2 = {}) {
|
|
const horizontalMargin = Math.max(seriesRect.x, chartSize.width - (seriesRect.x + seriesRect.width));
|
|
const verticalMargin = Math.max(seriesRect.y, chartSize.height - (seriesRect.y + seriesRect.height));
|
|
const result = [];
|
|
const createClipRect = (x0, x1) => ({
|
|
x0,
|
|
y0: -verticalMargin,
|
|
x1,
|
|
y1: seriesRect.height + verticalMargin
|
|
});
|
|
if (startsInverted) {
|
|
result.push({
|
|
clipRect: createClipRect(-horizontalMargin, intersections[0] ?? seriesRect.width + horizontalMargin),
|
|
...style2
|
|
});
|
|
}
|
|
const startIndex = startsInverted ? 1 : 0;
|
|
for (let i = startIndex; i < intersections.length; i += 2) {
|
|
result.push({
|
|
clipRect: createClipRect(intersections[i], intersections[i + 1] ?? seriesRect.width + horizontalMargin),
|
|
...style2
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-area/rangeAreaProperties.ts
|
|
var { CartesianSeriesProperties: CartesianSeriesProperties3, SeriesMarker: SeriesMarker2, makeSeriesTooltip: makeSeriesTooltip11, DropShadow: DropShadow3, Label: Label8 } = module_support_exports;
|
|
var RangeAreaSeriesLabel = class extends Label8 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.placement = "outside";
|
|
this.spacing = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaSeriesLabel.prototype, "placement", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaSeriesLabel.prototype, "spacing", 2);
|
|
var RangeAreaInvertedStyle = class {
|
|
constructor() {
|
|
this.enabled = false;
|
|
this.fillOpacity = 1;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaInvertedStyle.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaInvertedStyle.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaInvertedStyle.prototype, "fillOpacity", 2);
|
|
var RangeAreaLineStyle = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.stroke = "#99CCFF";
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.marker = new SeriesMarker2();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaLineStyle.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaLineStyle.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaLineStyle.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaLineStyle.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaLineStyle.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaLineStyle.prototype, "marker", 2);
|
|
var RangeAreaItemProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.low = new RangeAreaLineStyle();
|
|
this.high = new RangeAreaLineStyle();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaItemProperties.prototype, "low", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaItemProperties.prototype, "high", 2);
|
|
var SharedRangeAreaMarker = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SharedRangeAreaMarker.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SharedRangeAreaMarker.prototype, "shape", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SharedRangeAreaMarker.prototype, "size", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SharedRangeAreaMarker.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SharedRangeAreaMarker.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SharedRangeAreaMarker.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SharedRangeAreaMarker.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SharedRangeAreaMarker.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SharedRangeAreaMarker.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SharedRangeAreaMarker.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], SharedRangeAreaMarker.prototype, "itemStyler", 2);
|
|
var RangeAreaProperties = class extends CartesianSeriesProperties3 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = "#99CCFF";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "#99CCFF";
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.interpolation = new InterpolationProperties();
|
|
this.item = new RangeAreaItemProperties();
|
|
this.invertedStyle = new RangeAreaInvertedStyle();
|
|
this.shadow = new DropShadow3().set({ enabled: false });
|
|
this.marker = new SharedRangeAreaMarker();
|
|
this.label = new RangeAreaSeriesLabel();
|
|
this.tooltip = makeSeriesTooltip11();
|
|
this.connectMissingData = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "xKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "yLowKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "yHighKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "xName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "yName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "yLowName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "yHighName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "interpolation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "styler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "item", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "invertedStyle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "shadow", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "marker", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "tooltip", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeAreaProperties.prototype, "connectMissingData", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-area/rangeAreaUtil.ts
|
|
var {
|
|
CollapseMode: CollapseMode2,
|
|
pairUpSpans: pairUpSpans2,
|
|
prepareAreaFillAnimationFns: prepareAreaFillAnimationFns2,
|
|
plotInterpolatedLinePathStroke: plotInterpolatedLinePathStroke2,
|
|
prepareLinePathPropertyAnimation: prepareLinePathPropertyAnimation2
|
|
} = module_support_exports;
|
|
function prepareRangeAreaPathStrokeAnimationFns(status, highSpans, lowSpans, visibleToggleMode) {
|
|
const removePhaseFn = (ratio2, path) => {
|
|
plotInterpolatedLinePathStroke2(ratio2, path, highSpans.removed);
|
|
plotInterpolatedLinePathStroke2(ratio2, path, lowSpans.removed);
|
|
};
|
|
const updatePhaseFn = (ratio2, path) => {
|
|
plotInterpolatedLinePathStroke2(ratio2, path, highSpans.moved);
|
|
plotInterpolatedLinePathStroke2(ratio2, path, lowSpans.moved);
|
|
};
|
|
const addPhaseFn = (ratio2, path) => {
|
|
plotInterpolatedLinePathStroke2(ratio2, path, highSpans.added);
|
|
plotInterpolatedLinePathStroke2(ratio2, path, lowSpans.added);
|
|
};
|
|
const pathProperties = prepareLinePathPropertyAnimation2(status, visibleToggleMode);
|
|
return { status, path: { addPhaseFn, updatePhaseFn, removePhaseFn }, pathProperties };
|
|
}
|
|
function prepareRangeAreaPathAnimation(newData, oldData, diff9) {
|
|
const isCategoryBased = newData.scales.x?.type === "category";
|
|
const wasCategoryBased = oldData.scales.x?.type === "category";
|
|
if (isCategoryBased !== wasCategoryBased || !isScaleValid(newData.scales.x) || !isScaleValid(oldData.scales.x)) {
|
|
return;
|
|
}
|
|
let status = "updated";
|
|
if (oldData.visible && !newData.visible) {
|
|
status = "removed";
|
|
} else if (!oldData.visible && newData.visible) {
|
|
status = "added";
|
|
}
|
|
const fillSpans = pairUpSpans2(
|
|
{ scales: newData.scales, data: newData.fillData.spans },
|
|
{ scales: oldData.scales, data: oldData.fillData.spans },
|
|
CollapseMode2.Split
|
|
);
|
|
if (fillSpans == null)
|
|
return;
|
|
const fillPhantomSpans = pairUpSpans2(
|
|
{ scales: newData.scales, data: newData.fillData.phantomSpans },
|
|
{ scales: oldData.scales, data: oldData.fillData.phantomSpans },
|
|
CollapseMode2.Split
|
|
);
|
|
if (fillPhantomSpans == null)
|
|
return;
|
|
const highStrokeSpans = pairUpSpans2(
|
|
{ scales: newData.scales, data: newData.highStrokeData.spans },
|
|
{ scales: oldData.scales, data: oldData.highStrokeData.spans },
|
|
CollapseMode2.Split
|
|
);
|
|
if (highStrokeSpans == null)
|
|
return;
|
|
const lowStrokeSpans = pairUpSpans2(
|
|
{ scales: newData.scales, data: newData.lowStrokeData.spans },
|
|
{ scales: oldData.scales, data: oldData.lowStrokeData.spans },
|
|
CollapseMode2.Split
|
|
);
|
|
if (lowStrokeSpans == null)
|
|
return;
|
|
const fadeMode = "fade";
|
|
const fill = prepareAreaFillAnimationFns2(status, fillSpans, fillPhantomSpans, fadeMode);
|
|
const stroke3 = prepareRangeAreaPathStrokeAnimationFns(status, highStrokeSpans, lowStrokeSpans, fadeMode);
|
|
const hasMotion = (diff9?.changed ?? true) || !areScalingEqual(newData.scales.x, oldData.scales.x) || !areScalingEqual(newData.scales.y, oldData.scales.y) || status !== "updated";
|
|
return { status, fill, stroke: stroke3, hasMotion };
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-area/rangeArea.ts
|
|
var HIGH2 = AGGREGATION_INDEX_Y_MAX;
|
|
var LOW2 = AGGREGATION_INDEX_Y_MIN;
|
|
var SPAN3 = AGGREGATION_SPAN;
|
|
var {
|
|
valueProperty: valueProperty9,
|
|
keyProperty: keyProperty6,
|
|
updateLabelNode: updateLabelNode3,
|
|
fixNumericExtent: fixNumericExtent5,
|
|
buildResetPathFn: buildResetPathFn2,
|
|
resetLabelFn: resetLabelFn2,
|
|
resetMarkerFn: resetMarkerFn2,
|
|
resetMarkerPositionFn: resetMarkerPositionFn2,
|
|
pathSwipeInAnimation: pathSwipeInAnimation2,
|
|
resetMotion: resetMotion2,
|
|
markerSwipeScaleInAnimation: markerSwipeScaleInAnimation2,
|
|
seriesLabelFadeInAnimation: seriesLabelFadeInAnimation3,
|
|
animationValidation: animationValidation4,
|
|
diff: diff4,
|
|
updateClipPath: updateClipPath2,
|
|
computeMarkerFocusBounds: computeMarkerFocusBounds2,
|
|
plotAreaPathFill: plotAreaPathFill2,
|
|
plotLinePathStroke: plotLinePathStroke2,
|
|
interpolatePoints: interpolatePoints2,
|
|
pathFadeInAnimation: pathFadeInAnimation2,
|
|
markerFadeInAnimation: markerFadeInAnimation2,
|
|
fromToMotion: fromToMotion4,
|
|
pathMotion: pathMotion2,
|
|
PointerEvents: PointerEvents5,
|
|
Marker: Marker2,
|
|
BBox: BBox20,
|
|
processedDataIsAnimatable: processedDataIsAnimatable4,
|
|
markerEnabled: markerEnabled2,
|
|
getMarkerStyles: getMarkerStyles2,
|
|
calculateSegments: calculateSegments3,
|
|
toHighlightString: toHighlightString3,
|
|
HighlightState: HighlightState5,
|
|
AggregationManager: AggregationManager3,
|
|
resetMarkerSelectionsDirect: resetMarkerSelectionsDirect2,
|
|
createDatumId: createDatumId10,
|
|
visibleRangeIndices: visibleRangeIndices3
|
|
} = module_support_exports;
|
|
var RangeAreaSeriesNodeEvent = class extends module_support_exports.SeriesNodeEvent {
|
|
constructor(type, nativeEvent, datum, series) {
|
|
super(type, nativeEvent, datum, series);
|
|
this.xKey = series.properties.xKey;
|
|
this.yLowKey = series.properties.yLowKey;
|
|
this.yHighKey = series.properties.yHighKey;
|
|
}
|
|
};
|
|
var RangeAreaSeries = class extends module_support_exports.CartesianSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
pathsPerSeries: ["fill", "lowStroke", "highStroke"],
|
|
pickModes: [module_support_exports.SeriesNodePickMode.AXIS_ALIGNED],
|
|
propertyKeys: {
|
|
["x" /* X */]: ["xKey"],
|
|
["y" /* Y */]: ["yLowKey", "yHighKey"]
|
|
},
|
|
propertyNames: {
|
|
["x" /* X */]: ["xName"],
|
|
["y" /* Y */]: ["yLowName", "yHighName", "yName"]
|
|
},
|
|
categoryKey: "xValue",
|
|
animationResetFns: {
|
|
path: buildResetPathFn2({ getVisible: () => this.visible, getOpacity: () => this.getOpacity() }),
|
|
label: resetLabelFn2,
|
|
datum: (node, datum) => ({ ...resetMarkerFn2(node), ...resetMarkerPositionFn2(node, datum) })
|
|
},
|
|
clipFocusBox: false
|
|
});
|
|
this.properties = new RangeAreaProperties();
|
|
this.NodeEvent = RangeAreaSeriesNodeEvent;
|
|
this.aggregationManager = new AggregationManager3();
|
|
}
|
|
renderToOffscreenCanvas() {
|
|
const hasMarkers = (this.contextNodeData?.nodeData?.length ?? 0) > 0;
|
|
return hasMarkers && this.getDrawingMode(false) === "cutout" || super.renderToOffscreenCanvas();
|
|
}
|
|
async processData(dataController) {
|
|
const { xKey, yLowKey, yHighKey } = this.properties;
|
|
const xScale = this.axes["x" /* X */]?.scale;
|
|
const yScale = this.axes["y" /* Y */]?.scale;
|
|
const { xScaleType, yScaleType } = this.getScaleInformation({ xScale, yScale });
|
|
const extraProps = [];
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
if (this.needsDataModelDiff() && this.processedData) {
|
|
extraProps.push(diff4(this.id, this.processedData));
|
|
}
|
|
if (animationEnabled) {
|
|
extraProps.push(animationValidation4());
|
|
}
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
const { dataModel, processedData } = await this.requestDataModel(dataController, this.data, {
|
|
props: [
|
|
keyProperty6(xKey, xScaleType, { id: `xValue`, allowNullKey }),
|
|
valueProperty9(yLowKey, yScaleType, { id: `yLowValue` }),
|
|
valueProperty9(yHighKey, yScaleType, { id: `yHighValue` }),
|
|
...extraProps
|
|
]
|
|
});
|
|
this.aggregateData(dataModel, processedData);
|
|
this.animationState.transition("updateData");
|
|
}
|
|
aggregateData(dataModel, processedData) {
|
|
this.aggregationManager.markStale(processedData.input.count);
|
|
if (processedData.type !== "ungrouped")
|
|
return;
|
|
if (processedDataIsAnimatable4(processedData))
|
|
return;
|
|
const xAxis = this.axes["x" /* X */];
|
|
if (xAxis == null)
|
|
return;
|
|
const targetRange = this.estimateTargetRange();
|
|
this.aggregationManager.aggregate({
|
|
computePartial: (existingFilters) => aggregateRangeAreaDataFromDataModelPartial(
|
|
xAxis.scale.type,
|
|
dataModel,
|
|
processedData,
|
|
this,
|
|
targetRange,
|
|
existingFilters
|
|
),
|
|
computeFull: (existingFilters) => aggregateRangeAreaDataFromDataModel(xAxis.scale.type, dataModel, processedData, this, existingFilters),
|
|
targetRange
|
|
});
|
|
const filters = this.aggregationManager.filters;
|
|
if (filters && filters.length > 0) {
|
|
debugMetrics_exports.record(
|
|
`${this.type}:aggregation`,
|
|
filters.map((f) => f.maxRange)
|
|
);
|
|
}
|
|
}
|
|
estimateTargetRange() {
|
|
const xAxis = this.axes["x" /* X */];
|
|
if (xAxis?.scale?.range) {
|
|
const [r0, r1] = xAxis.scale.range;
|
|
return Math.abs(r1 - r0);
|
|
}
|
|
return this.ctx.scene?.canvas?.width ?? 800;
|
|
}
|
|
/**
|
|
* Creates the context object for efficient node datum creation.
|
|
* Caches expensive-to-compute values that are reused across all datum iterations.
|
|
*/
|
|
createNodeDatumContext(xAxis, yAxis) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData)
|
|
return void 0;
|
|
const rawData = processedData.dataSources.get(this.id)?.data ?? [];
|
|
const xScale = xAxis.scale;
|
|
const yScale = yAxis.scale;
|
|
const xAxisRange = xAxis.range;
|
|
const [r0, r1] = xScale.range;
|
|
const range3 = Math.abs(r1 - r0);
|
|
this.aggregationManager.ensureLevelForRange(range3);
|
|
const dataAggregationFilter = this.aggregationManager.getFilterForRange(range3);
|
|
const existingNodes = this.contextNodeData?.nodeData;
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
const canIncrementallyUpdate = existingNodes != null && (processedData.changeDescription != null || !processedDataIsAnimatable4(processedData) || dataAggregationFilter != null);
|
|
return {
|
|
xAxis,
|
|
yAxis,
|
|
rawData,
|
|
xValues: dataModel.resolveKeysById(this, "xValue", processedData),
|
|
yHighValues: dataModel.resolveColumnById(this, "yHighValue", processedData),
|
|
yLowValues: dataModel.resolveColumnById(this, "yLowValue", processedData),
|
|
xScale,
|
|
yScale,
|
|
xAxisRange,
|
|
xOffset: (xScale.bandwidth ?? 0) / 2,
|
|
dataAggregationFilter,
|
|
range: range3,
|
|
labelsEnabled: this.properties.label.enabled,
|
|
animationEnabled,
|
|
canIncrementallyUpdate,
|
|
xKey: this.properties.xKey,
|
|
yLowKey: this.properties.yLowKey,
|
|
yHighKey: this.properties.yHighKey,
|
|
item: this.properties.item,
|
|
yDomain: this.getSeriesDomain("y" /* Y */).domain,
|
|
connectMissingData: this.properties.connectMissingData,
|
|
interpolation: this.properties.interpolation,
|
|
nodes: canIncrementallyUpdate ? existingNodes : [],
|
|
labelData: [],
|
|
spanPoints: [],
|
|
nodeIndex: 0
|
|
};
|
|
}
|
|
xCoordinateRange(xValue) {
|
|
const x = this.axes["x" /* X */].scale.convert(xValue);
|
|
return [x, x];
|
|
}
|
|
yCoordinateRange(yValues) {
|
|
const y = this.axes["y" /* Y */].scale.convert(yValues[0]);
|
|
return [y, y];
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { processedData, dataModel } = this;
|
|
if (!(processedData && dataModel))
|
|
return { domain: [] };
|
|
const {
|
|
domain: {
|
|
keys: [keys]
|
|
}
|
|
} = processedData;
|
|
if (direction === "x" /* X */) {
|
|
const keyDef = dataModel.resolveProcessedDataDefById(this, `xValue`);
|
|
if (keyDef?.def.type === "key" && keyDef.def.valueType === "category") {
|
|
const sortMetadata = dataModel.getKeySortMetadata(this, "xValue", processedData);
|
|
return { domain: keys, sortMetadata };
|
|
}
|
|
return { domain: fixNumericExtent5(extent(keys)) };
|
|
} else {
|
|
const yExtent = this.domainForClippedRange("y" /* Y */, ["yHighValue", "yLowValue"], "xValue");
|
|
const fixedYExtent = findMinMax(yExtent);
|
|
return { domain: fixNumericExtent5(fixedYExtent) };
|
|
}
|
|
}
|
|
getSeriesRange(_direction, visibleRange) {
|
|
return this.domainForVisibleRange("y" /* Y */, ["yHighValue", "yLowValue"], "xValue", visibleRange);
|
|
}
|
|
/**
|
|
* Processes a single datum and updates the context's marker, label, and span arrays.
|
|
* Uses the scratch object to avoid per-iteration allocations.
|
|
*
|
|
* @param yHighValueOverride - Optional override for yHighValue, used in aggregation mode
|
|
* when the extreme values come from different data points
|
|
* @param yLowValueOverride - Optional override for yLowValue, used in aggregation mode
|
|
*/
|
|
handleDatumPoint(ctx, scratch, datumIndex, yHighValueOverride, yLowValueOverride) {
|
|
scratch.xValue = ctx.xValues[datumIndex];
|
|
if (scratch.xValue === void 0 && !this.properties.allowNullKeys)
|
|
return;
|
|
scratch.datum = ctx.rawData[datumIndex];
|
|
scratch.yHighValue = yHighValueOverride ?? ctx.yHighValues[datumIndex];
|
|
scratch.yLowValue = yLowValueOverride ?? ctx.yLowValues[datumIndex];
|
|
const currentSpanPoints = ctx.spanPoints.at(-1);
|
|
if (Number.isFinite(scratch.yHighValue) && Number.isFinite(scratch.yLowValue)) {
|
|
scratch.inverted = scratch.yLowValue > scratch.yHighValue;
|
|
scratch.x = ctx.xScale.convert(scratch.xValue) + ctx.xOffset;
|
|
if (!Number.isFinite(scratch.x))
|
|
return;
|
|
scratch.yHighCoordinate = ctx.yScale.convert(scratch.yHighValue);
|
|
scratch.yLowCoordinate = ctx.yScale.convert(scratch.yLowValue);
|
|
this.upsertMarkerDatum(ctx, scratch, datumIndex, "high", scratch.yHighValue, scratch.yHighCoordinate);
|
|
this.upsertMarkerDatum(ctx, scratch, datumIndex, "low", scratch.yLowValue, scratch.yLowCoordinate);
|
|
const spanPoint = {
|
|
high: {
|
|
point: { x: scratch.x, y: scratch.yHighCoordinate },
|
|
xDatum: scratch.xValue,
|
|
yDatum: scratch.yHighValue
|
|
},
|
|
low: {
|
|
point: { x: scratch.x, y: scratch.yLowCoordinate },
|
|
xDatum: scratch.xValue,
|
|
yDatum: scratch.yLowValue
|
|
}
|
|
};
|
|
if (Array.isArray(currentSpanPoints)) {
|
|
currentSpanPoints.push(spanPoint);
|
|
} else if (currentSpanPoints == null) {
|
|
ctx.spanPoints.push([spanPoint]);
|
|
} else {
|
|
currentSpanPoints.skip += 1;
|
|
ctx.spanPoints.push([spanPoint]);
|
|
}
|
|
} else if (!ctx.connectMissingData) {
|
|
if (Array.isArray(currentSpanPoints) || currentSpanPoints == null) {
|
|
ctx.spanPoints.push({ skip: 0 });
|
|
} else {
|
|
currentSpanPoints.skip += 1;
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Creates or updates marker datum for a single boundary (high or low).
|
|
* Supports incremental updates by reusing existing marker data objects when possible.
|
|
*/
|
|
upsertMarkerDatum(ctx, scratch, datumIndex, itemType, yValue, y) {
|
|
const { size } = ctx.item[itemType].marker;
|
|
const canReuseNode = ctx.canIncrementallyUpdate && ctx.nodeIndex < ctx.nodes.length;
|
|
if (canReuseNode) {
|
|
const existingNode = ctx.nodes[ctx.nodeIndex];
|
|
existingNode.index = datumIndex;
|
|
existingNode.itemType = itemType;
|
|
existingNode.datum = scratch.datum;
|
|
existingNode.datumIndex = datumIndex;
|
|
existingNode.midPoint = { x: scratch.x, y };
|
|
existingNode.yHighValue = scratch.yHighValue;
|
|
existingNode.yLowValue = scratch.yLowValue;
|
|
existingNode.xValue = scratch.xValue;
|
|
existingNode.point = { x: scratch.x, y, size };
|
|
} else {
|
|
ctx.nodes.push({
|
|
index: datumIndex,
|
|
series: this,
|
|
itemType,
|
|
datum: scratch.datum,
|
|
datumIndex,
|
|
midPoint: { x: scratch.x, y },
|
|
yHighValue: scratch.yHighValue,
|
|
yLowValue: scratch.yLowValue,
|
|
xValue: scratch.xValue,
|
|
xKey: ctx.xKey,
|
|
yLowKey: ctx.yLowKey,
|
|
yHighKey: ctx.yHighKey,
|
|
point: { x: scratch.x, y, size },
|
|
enabled: true
|
|
});
|
|
}
|
|
ctx.nodeIndex++;
|
|
if (ctx.labelsEnabled) {
|
|
const labelDatum = this.createLabelData({
|
|
datumIndex,
|
|
point: { x: scratch.x, y },
|
|
value: yValue,
|
|
yLowValue: scratch.yLowValue,
|
|
yHighValue: scratch.yHighValue,
|
|
itemType,
|
|
inverted: scratch.inverted,
|
|
datum: scratch.datum,
|
|
series: this
|
|
});
|
|
ctx.labelData.push(labelDatum);
|
|
}
|
|
}
|
|
populateNodeData(ctx) {
|
|
const { processedData } = this;
|
|
if (!processedData)
|
|
return;
|
|
const scratch = {
|
|
datum: void 0,
|
|
xValue: void 0,
|
|
yHighValue: 0,
|
|
yLowValue: 0,
|
|
x: 0,
|
|
yHighCoordinate: 0,
|
|
yLowCoordinate: 0,
|
|
inverted: false
|
|
};
|
|
const xPosition = (index) => ctx.xScale.convert(ctx.xValues[index]) + ctx.xOffset;
|
|
if (processedData.input.count < 1e3 || ctx.dataAggregationFilter == null) {
|
|
let [start2, end3] = visibleRangeIndices3(1, ctx.xValues.length, ctx.xAxisRange, (index) => {
|
|
const x = xPosition(index);
|
|
return [x, x];
|
|
});
|
|
if (processedData.input.count < 1e3) {
|
|
start2 = 0;
|
|
end3 = processedData.input.count;
|
|
}
|
|
start2 = Math.max(start2 - 1, 0);
|
|
end3 = Math.min(end3 + 1, ctx.xValues.length);
|
|
for (let datumIndex = start2; datumIndex < end3; datumIndex += 1) {
|
|
this.handleDatumPoint(ctx, scratch, datumIndex);
|
|
}
|
|
} else {
|
|
const { maxRange, indexData, midpointIndices } = ctx.dataAggregationFilter;
|
|
const [start2, end3] = visibleRangeIndices3(1, maxRange, ctx.xAxisRange, (index) => {
|
|
const midDatumIndex = midpointIndices[index];
|
|
if (midDatumIndex === AGGREGATION_INDEX_UNSET)
|
|
return;
|
|
return [xPosition(midDatumIndex), xPosition(midDatumIndex)];
|
|
});
|
|
for (let bucketIndex = start2; bucketIndex < end3; bucketIndex += 1) {
|
|
const midIndex = midpointIndices[bucketIndex];
|
|
if (midIndex === AGGREGATION_INDEX_UNSET)
|
|
continue;
|
|
const aggIndex = bucketIndex * SPAN3;
|
|
const yHighDatumIndex = indexData[aggIndex + HIGH2];
|
|
const yLowDatumIndex = indexData[aggIndex + LOW2];
|
|
this.handleDatumPoint(
|
|
ctx,
|
|
scratch,
|
|
yHighDatumIndex,
|
|
ctx.yHighValues[yHighDatumIndex],
|
|
ctx.yLowValues[yLowDatumIndex]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
finalizeNodeData(ctx) {
|
|
if (ctx.canIncrementallyUpdate && ctx.nodeIndex < ctx.nodes.length) {
|
|
ctx.nodes.length = ctx.nodeIndex;
|
|
}
|
|
}
|
|
initializeResult(ctx) {
|
|
return {
|
|
itemId: `${ctx.yLowKey}-${ctx.yHighKey}`,
|
|
labelData: ctx.labelData,
|
|
nodeData: ctx.nodes,
|
|
fillData: { itemType: "high", spans: [], phantomSpans: [] },
|
|
highStrokeData: { itemType: "high", spans: [] },
|
|
lowStrokeData: { itemType: "low", spans: [] },
|
|
scales: this.calculateScaling(),
|
|
visible: this.visible,
|
|
styles: {
|
|
low: this.getLowOrHighMarkerStyles("low"),
|
|
high: this.getLowOrHighMarkerStyles("high")
|
|
},
|
|
segments: void 0,
|
|
intersectionSegments: void 0
|
|
};
|
|
}
|
|
assembleResult(ctx, result) {
|
|
const xAxis = this.axes["x" /* X */];
|
|
const yAxis = this.axes["y" /* Y */];
|
|
if (!xAxis || !yAxis || !this.chart?.seriesRect)
|
|
return result;
|
|
const highSpans = ctx.spanPoints.flatMap((p) => {
|
|
if (!Array.isArray(p))
|
|
return [];
|
|
const highPoints = p.map((d) => d.high);
|
|
return interpolatePoints2(highPoints, ctx.interpolation);
|
|
});
|
|
const lowSpans = ctx.spanPoints.flatMap((p) => {
|
|
if (!Array.isArray(p))
|
|
return [];
|
|
const lowPoints = p.map((d) => d.low);
|
|
return interpolatePoints2(lowPoints, ctx.interpolation);
|
|
});
|
|
const segments = calculateSegments3(
|
|
this.properties.segmentation,
|
|
xAxis,
|
|
yAxis,
|
|
this.chart.seriesRect,
|
|
this.ctx.scene,
|
|
false
|
|
);
|
|
let intersectionSegments = void 0;
|
|
if (this.properties.invertedStyle.enabled) {
|
|
const startsInverted = ctx.yHighValues[0] < ctx.yLowValues[0];
|
|
const intersectionXValues = findRangeAreaIntersections(
|
|
highSpans,
|
|
lowSpans,
|
|
ctx.xScale.range[0],
|
|
ctx.xScale.range[1],
|
|
startsInverted
|
|
);
|
|
intersectionSegments = calculateIntersectionSegments(
|
|
intersectionXValues,
|
|
this.chart.seriesRect,
|
|
this.ctx.scene,
|
|
startsInverted,
|
|
this.properties.invertedStyle
|
|
);
|
|
}
|
|
result.fillData = { itemType: "high", spans: highSpans, phantomSpans: lowSpans };
|
|
result.highStrokeData = { itemType: "high", spans: highSpans };
|
|
result.lowStrokeData = { itemType: "low", spans: lowSpans };
|
|
result.segments = segments;
|
|
result.intersectionSegments = intersectionSegments;
|
|
return result;
|
|
}
|
|
getLowOrHighMarkerStyles(lowOrHigh) {
|
|
const { fill, fillOpacity, item } = this.properties;
|
|
const line = item[lowOrHigh];
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity } = line;
|
|
const inheritedStyles = { fill, fillOpacity, stroke: stroke3, strokeWidth, strokeOpacity };
|
|
return getMarkerStyles2(this, line, line.marker, inheritedStyles);
|
|
}
|
|
createLabelData({
|
|
datumIndex,
|
|
point,
|
|
value,
|
|
itemType,
|
|
inverted,
|
|
datum,
|
|
series
|
|
}) {
|
|
const { xKey, yLowKey, yHighKey, xName, yName, yLowName, yHighName, legendItemName, label } = this.properties;
|
|
const { placement } = label;
|
|
const spacing = label.spacing + (typeof label.padding === "number" ? label.padding : 0);
|
|
let actualItemId = itemType;
|
|
if (inverted) {
|
|
actualItemId = itemType === "low" ? "high" : "low";
|
|
}
|
|
const direction = placement === "outside" && actualItemId === "high" || placement === "inside" && actualItemId === "low" ? -1 : 1;
|
|
const yDomain = this.getSeriesDomain("y" /* Y */).domain;
|
|
return {
|
|
x: point.x,
|
|
y: point.y + spacing * direction,
|
|
series,
|
|
itemType,
|
|
datum,
|
|
datumIndex,
|
|
text: this.getLabelText(
|
|
value,
|
|
datum,
|
|
itemType === "high" ? yHighKey : yLowKey,
|
|
"y",
|
|
yDomain,
|
|
label,
|
|
{ value, datum, itemType, xKey, yLowKey, yHighKey, xName, yLowName, yHighName, yName, legendItemName }
|
|
),
|
|
textAlign: "center",
|
|
textBaseline: direction === -1 ? "bottom" : "top"
|
|
};
|
|
}
|
|
isPathOrSelectionDirty() {
|
|
const { low, high } = this.properties.item;
|
|
return low.marker.isDirty() || high.marker.isDirty();
|
|
}
|
|
strokewidthChange() {
|
|
const itemStrokeWidthChange = (lowOrHigh) => {
|
|
const unhighlightedStrokeWidth = this.properties.item[lowOrHigh].strokeWidth ?? 0;
|
|
const highlightedSeriesStrokeWidth = this.properties.highlight.highlightedSeries.item?.[lowOrHigh]?.strokeWidth ?? unhighlightedStrokeWidth;
|
|
const highlightedItemStrokeWidth = this.properties.highlight.highlightedItem.item?.[lowOrHigh]?.strokeWidth ?? unhighlightedStrokeWidth;
|
|
return unhighlightedStrokeWidth > highlightedItemStrokeWidth || highlightedSeriesStrokeWidth > highlightedItemStrokeWidth;
|
|
};
|
|
return itemStrokeWidthChange("low") || itemStrokeWidthChange("high");
|
|
}
|
|
updatePathNodes(opts) {
|
|
const { visible } = opts;
|
|
const [fillPath, lowStrokePath, highStrokePath] = opts.paths;
|
|
const segments = this.contextNodeData?.segments;
|
|
const highlightDatum = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightState = this.getHighlightState(highlightDatum, false);
|
|
const highlightStyle = this.getHighlightStyle();
|
|
const { item, fill, fillOpacity, opacity } = mergeDefaults(highlightStyle, this.getStyle(highlightState));
|
|
lowStrokePath.setProperties({
|
|
datum: segments,
|
|
segments,
|
|
fill: void 0,
|
|
lineCap: "round",
|
|
lineJoin: "round",
|
|
pointerEvents: PointerEvents5.None,
|
|
stroke: item.low.stroke,
|
|
strokeWidth: item.low.strokeWidth,
|
|
strokeOpacity: item.low.strokeOpacity,
|
|
lineDash: item.low.lineDash,
|
|
lineDashOffset: item.low.lineDashOffset,
|
|
opacity,
|
|
visible
|
|
});
|
|
highStrokePath.setProperties({
|
|
segments,
|
|
fill: void 0,
|
|
lineCap: "round",
|
|
lineJoin: "round",
|
|
pointerEvents: PointerEvents5.None,
|
|
stroke: item.high.stroke,
|
|
strokeWidth: item.high.strokeWidth,
|
|
strokeOpacity: item.high.strokeOpacity,
|
|
lineDash: item.high.lineDash,
|
|
lineDashOffset: item.high.lineDashOffset,
|
|
opacity,
|
|
visible
|
|
});
|
|
const fillBBox = this.getShapeFillBBox();
|
|
fillPath.setFillProperties(fill, fillBBox);
|
|
fillPath.setStyleProperties({ stroke: void 0, fill, fillOpacity, opacity }, fillBBox);
|
|
const fillSegments = this.contextNodeData?.intersectionSegments ?? segments;
|
|
fillPath.setProperties({
|
|
segments: fillSegments,
|
|
pointerEvents: PointerEvents5.None,
|
|
lineJoin: "round",
|
|
fillShadow: this.properties.shadow,
|
|
opacity,
|
|
visible
|
|
});
|
|
fillPath.datum = fillSegments;
|
|
updateClipPath2(this, fillPath);
|
|
updateClipPath2(this, lowStrokePath);
|
|
updateClipPath2(this, highStrokePath);
|
|
}
|
|
updatePaths(opts) {
|
|
this.updateAreaPaths(opts.paths, opts.contextData);
|
|
}
|
|
updateAreaPaths(paths, contextData) {
|
|
for (const path of paths) {
|
|
path.visible = contextData.visible;
|
|
}
|
|
if (contextData.visible) {
|
|
this.updateFillPath(paths, contextData);
|
|
this.updateStrokePath(paths, contextData);
|
|
} else {
|
|
for (const path of paths) {
|
|
path.path.clear();
|
|
path.markDirty("RangeArea");
|
|
}
|
|
}
|
|
}
|
|
updateFillPath(paths, contextData) {
|
|
const [fill] = paths;
|
|
fill.path.clear();
|
|
plotAreaPathFill2(fill, contextData.fillData);
|
|
fill.markDirty("RangeArea");
|
|
}
|
|
updateStrokePath(paths, contextData) {
|
|
const [, lowStroke, highStroke] = paths;
|
|
lowStroke.path.clear();
|
|
highStroke.path.clear();
|
|
plotLinePathStroke2(lowStroke, contextData.lowStrokeData.spans);
|
|
plotLinePathStroke2(highStroke, contextData.highStrokeData.spans);
|
|
lowStroke.markDirty("RangeArea");
|
|
highStroke.markDirty("RangeArea");
|
|
}
|
|
resetDatumAnimation(data) {
|
|
resetMarkerSelectionsDirect2([data.datumSelection]);
|
|
}
|
|
updateDatumSelection(opts) {
|
|
const { nodeData, datumSelection } = opts;
|
|
const { processedData, axes, properties } = this;
|
|
const rules = properties.styler ? this.getStylerMarkerOptions().item : properties.item;
|
|
const { low, high } = rules;
|
|
const markersEnabled = markerEnabled2(processedData.input.count, axes["x" /* X */].scale, {
|
|
enabled: low.marker.enabled || high.marker.enabled
|
|
});
|
|
if (properties.item.low.marker.isDirty() || properties.item.high.marker.isDirty()) {
|
|
datumSelection.clear();
|
|
datumSelection.cleanup();
|
|
}
|
|
let resolvedNodeData;
|
|
if (markersEnabled) {
|
|
if (low.marker.enabled && high.marker.enabled) {
|
|
resolvedNodeData = nodeData;
|
|
} else {
|
|
resolvedNodeData = [];
|
|
for (const datum of nodeData) {
|
|
if (rules[datum.itemType].marker.enabled) {
|
|
resolvedNodeData.push(datum);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
resolvedNodeData = [];
|
|
}
|
|
if (!processedDataIsAnimatable4(this.processedData)) {
|
|
return datumSelection.update(resolvedNodeData);
|
|
}
|
|
return datumSelection.update(
|
|
resolvedNodeData,
|
|
void 0,
|
|
(datum) => createDatumId10(datum.xValue, datum.itemType)
|
|
);
|
|
}
|
|
updateDatumStyles({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
datumSelection.each((_, datum) => {
|
|
const highlightState = this.getHighlightState(highlightedDatum, isHighlight, datum.datumIndex);
|
|
const stylerStyle = this.getStyle(highlightState);
|
|
const { fill, fillOpacity, item } = stylerStyle;
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity } = item[datum.itemType];
|
|
const { marker } = this.properties.item[datum.itemType];
|
|
const params = this.makeItemStylerParams(datum.itemType);
|
|
datum.style = this.getMarkerStyle(
|
|
marker,
|
|
datum,
|
|
params,
|
|
{ isHighlight, highlightState, resolveMarkerSubPath: ["item", datum.itemType, "marker"] },
|
|
stylerStyle.item[datum.itemType].marker,
|
|
{
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity
|
|
}
|
|
);
|
|
});
|
|
}
|
|
updateDatumNodes(opts) {
|
|
const { contextNodeData } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const { datumSelection, isHighlight } = opts;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
const drawingMode = this.getDrawingMode(isHighlight, opts.drawingMode);
|
|
datumSelection.each((node, datum) => {
|
|
const { itemType } = datum;
|
|
const style2 = datum.style ?? contextNodeData.styles[itemType][this.getHighlightState(highlightedDatum, isHighlight, datum.datumIndex)];
|
|
this.applyMarkerStyle(style2, node, datum.point, fillBBox);
|
|
node.drawingMode = drawingMode;
|
|
});
|
|
if (!isHighlight) {
|
|
this.properties.item.low.marker.markClean();
|
|
this.properties.item.high.marker.markClean();
|
|
}
|
|
}
|
|
updateLabelSelection(opts) {
|
|
const { labelData, labelSelection } = opts;
|
|
return labelSelection.update(labelData, (text2) => {
|
|
text2.pointerEvents = PointerEvents5.None;
|
|
});
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const params = {
|
|
xKey: this.properties.xKey,
|
|
xName: this.properties.xName ?? this.properties.xKey,
|
|
yName: this.properties.yName,
|
|
yLowKey: this.properties.yLowKey,
|
|
yLowName: this.properties.yLowName ?? this.properties.yLowKey,
|
|
yHighKey: this.properties.yHighKey,
|
|
yHighName: this.properties.yHighName ?? this.properties.yHighKey,
|
|
legendItemName: this.properties.legendItemName
|
|
};
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const { isHighlight = false, labelSelection } = opts;
|
|
labelSelection.each((textNode, datum) => {
|
|
textNode.fillOpacity = this.getHighlightStyle(isHighlight, datum.datumIndex).opacity ?? 1;
|
|
updateLabelNode3(this, textNode, params, this.properties.label, datum, isHighlight, activeHighlight);
|
|
});
|
|
}
|
|
getHighlightLabelData(labelData, highlightedItem) {
|
|
if (!labelData?.length)
|
|
return [];
|
|
return labelData.filter((labelDatum) => labelDatum.datum === highlightedItem.datum);
|
|
}
|
|
getHighlightData(nodeData, highlightedItem) {
|
|
const highlightItems = nodeData.filter((nodeDatum) => nodeDatum.datum === highlightedItem.datum).map((nodeDatum) => ({ ...nodeDatum }));
|
|
return highlightItems.length > 0 ? highlightItems : void 0;
|
|
}
|
|
getStyle(highlightState) {
|
|
return this.getStylerCouple(highlightState)[0];
|
|
}
|
|
getStylerMarkerOptions() {
|
|
return this.getStylerCouple()[1];
|
|
}
|
|
getStylerCouple(highlightState) {
|
|
const { fill, fillOpacity, item, styler } = this.properties;
|
|
let stylerResult = {};
|
|
if (styler) {
|
|
const stylerParams = this.makeStylerParams(highlightState);
|
|
stylerResult = this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`],
|
|
this.cachedCallWithContext(styler, stylerParams) ?? {},
|
|
{ pick: false }
|
|
) ?? {};
|
|
}
|
|
const markerOpts = {
|
|
item: { low: { marker: { enabled: false } }, high: { marker: { enabled: false } } }
|
|
};
|
|
const makeItemResult = (lowOrHigh) => {
|
|
const stylerItem = stylerResult.item?.[lowOrHigh];
|
|
const { lineDash, lineDashOffset, marker, stroke: stroke3, strokeOpacity, strokeWidth } = item[lowOrHigh];
|
|
markerOpts.item[lowOrHigh].marker.enabled = stylerItem?.marker?.enabled ?? marker.enabled;
|
|
return {
|
|
marker: {
|
|
fill: stylerItem?.marker?.fill ?? marker.fill ?? fill,
|
|
fillOpacity: stylerItem?.marker?.fillOpacity ?? marker.fillOpacity,
|
|
shape: stylerItem?.marker?.shape ?? marker.shape,
|
|
size: stylerItem?.marker?.size ?? marker.size,
|
|
lineDash: stylerItem?.marker?.lineDash ?? marker.lineDash,
|
|
lineDashOffset: stylerItem?.marker?.lineDashOffset ?? marker.lineDashOffset,
|
|
stroke: stylerItem?.marker?.stroke ?? marker.stroke ?? stroke3,
|
|
strokeOpacity: stylerItem?.marker?.strokeOpacity ?? marker.strokeOpacity,
|
|
strokeWidth: stylerItem?.marker?.strokeWidth ?? marker.strokeWidth
|
|
},
|
|
lineDash: stylerItem?.lineDash ?? lineDash,
|
|
lineDashOffset: stylerItem?.lineDashOffset ?? lineDashOffset,
|
|
stroke: stylerItem?.stroke ?? stroke3,
|
|
strokeOpacity: stylerItem?.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: stylerItem?.strokeWidth ?? strokeWidth
|
|
};
|
|
};
|
|
const style2 = {
|
|
fill: stylerResult.fill ?? fill,
|
|
fillOpacity: stylerResult.fillOpacity ?? fillOpacity,
|
|
opacity: 1,
|
|
topLevel: {
|
|
lineDash: this.properties.lineDash,
|
|
lineDashOffset: this.properties.lineDashOffset,
|
|
marker: this.properties.marker,
|
|
stroke: this.properties.stroke,
|
|
strokeOpacity: this.properties.strokeOpacity,
|
|
strokeWidth: this.properties.strokeWidth
|
|
},
|
|
item: {
|
|
low: makeItemResult("low"),
|
|
high: makeItemResult("high")
|
|
}
|
|
};
|
|
return [style2, markerOpts];
|
|
}
|
|
makeStylerParams(highlightStateEnum) {
|
|
const { id: seriesId } = this;
|
|
const { fill, fillOpacity, item, xKey, yHighKey, yLowKey } = this.properties;
|
|
const highlightState = toHighlightString3(highlightStateEnum ?? HighlightState5.None);
|
|
const makeItemParam = (lowOrHigh) => {
|
|
const { lineDash, lineDashOffset, marker, stroke: stroke3, strokeOpacity, strokeWidth } = item[lowOrHigh];
|
|
return {
|
|
marker: {
|
|
fill: marker.fill ?? fill,
|
|
fillOpacity: marker.fillOpacity,
|
|
size: marker.size,
|
|
shape: marker.shape,
|
|
stroke: marker.stroke ?? stroke3,
|
|
strokeOpacity: marker.strokeOpacity,
|
|
strokeWidth: marker.strokeWidth,
|
|
lineDash: marker.lineDash,
|
|
lineDashOffset: marker.lineDashOffset
|
|
},
|
|
lineDash,
|
|
lineDashOffset,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth
|
|
};
|
|
};
|
|
return {
|
|
item: {
|
|
low: makeItemParam("low"),
|
|
high: makeItemParam("high")
|
|
},
|
|
fill,
|
|
fillOpacity,
|
|
highlightState,
|
|
seriesId,
|
|
xKey,
|
|
yLowKey,
|
|
yHighKey
|
|
};
|
|
}
|
|
makeItemStylerParams(itemType) {
|
|
const { xKey, yLowKey, yHighKey } = this.properties;
|
|
return { xKey, yLowKey, yHighKey, itemType };
|
|
}
|
|
getTooltipContent(datumIndex, removeThisDatum) {
|
|
const itemType = removeThisDatum?.itemType ?? "high";
|
|
const { id: seriesId, dataModel, processedData, axes, properties } = this;
|
|
const { xName, yName, yLowKey, yLowName, xKey, yHighKey, yHighName, tooltip, legendItemName } = properties;
|
|
const xAxis = axes["x" /* X */];
|
|
const yAxis = axes["y" /* Y */];
|
|
if (!dataModel || !processedData || !xAxis || !yAxis)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const xValue = dataModel.resolveKeysById(this, `xValue`, processedData)[datumIndex];
|
|
const yHighValue = dataModel.resolveColumnById(this, `yHighValue`, processedData)[datumIndex];
|
|
const yLowValue = dataModel.resolveColumnById(this, `yLowValue`, processedData)[datumIndex];
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
if (xValue === void 0 && !allowNullKeys)
|
|
return;
|
|
const stylerStyle = this.getStyle();
|
|
const params = this.makeItemStylerParams(itemType);
|
|
const format = this.getMarkerStyle(
|
|
this.properties.item[itemType].marker,
|
|
{ datumIndex, datum },
|
|
params,
|
|
{ isHighlight: false, resolveMarkerSubPath: ["item", itemType, "marker"] },
|
|
stylerStyle.item[itemType].marker
|
|
);
|
|
const value = `${this.getAxisValueText(yAxis, "tooltip", yLowValue, datum, yLowKey, legendItemName)} - ${this.getAxisValueText(yAxis, "tooltip", yHighValue, datum, yHighKey, legendItemName)}`;
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading: this.getAxisValueText(xAxis, "tooltip", xValue, datum, xKey, legendItemName),
|
|
symbol: this.legendItemSymbol(),
|
|
data: [
|
|
{
|
|
label: yName,
|
|
fallbackLabel: `${yLowName ?? yLowKey} - ${yHighName ?? yHighKey}`,
|
|
value,
|
|
missing: module_support_exports.isTooltipValueMissing(yHighValue) && module_support_exports.isTooltipValueMissing(yLowValue)
|
|
}
|
|
]
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: yName,
|
|
itemType,
|
|
xName,
|
|
yName,
|
|
yLowKey,
|
|
yLowName,
|
|
xKey,
|
|
yHighKey,
|
|
yHighName,
|
|
legendItemName,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
legendItemSymbol() {
|
|
const { fill, topLevel } = this.getStyle();
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity, lineDash, marker } = topLevel;
|
|
const markerStyle = {
|
|
shape: marker.shape,
|
|
fill: marker.fill ?? fill,
|
|
stroke: marker.stroke ?? stroke3,
|
|
fillOpacity: marker.fillOpacity,
|
|
strokeOpacity: marker.strokeOpacity,
|
|
strokeWidth: marker.strokeWidth,
|
|
lineDash: marker.lineDash,
|
|
lineDashOffset: marker.lineDashOffset
|
|
};
|
|
return {
|
|
marker: markerStyle,
|
|
line: {
|
|
enabled: true,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
if (legendType !== "category") {
|
|
return [];
|
|
}
|
|
const { id: seriesId, visible } = this;
|
|
const { yLowKey, yHighKey, yName, yLowName, yHighName, legendItemName, showInLegend } = this.properties;
|
|
const legendItemText = legendItemName ?? yName ?? `${yLowName ?? yLowKey} - ${yHighName ?? yHighKey}`;
|
|
const itemId = `${yLowKey}-${yHighKey}`;
|
|
return [
|
|
{
|
|
legendType: "category",
|
|
id: seriesId,
|
|
itemId,
|
|
seriesId,
|
|
enabled: visible,
|
|
label: { text: `${legendItemText}` },
|
|
symbol: this.legendItemSymbol(),
|
|
legendItemName,
|
|
hideInLegend: !showInLegend
|
|
}
|
|
];
|
|
}
|
|
isLabelEnabled() {
|
|
return this.properties.label.enabled;
|
|
}
|
|
nodeFactory() {
|
|
return new Marker2();
|
|
}
|
|
animateEmptyUpdateReady(animationData) {
|
|
const { datumSelection, labelSelection, contextData, paths } = animationData;
|
|
const { animationManager } = this.ctx;
|
|
this.updateAreaPaths(paths, contextData);
|
|
pathSwipeInAnimation2(this, animationManager, ...paths);
|
|
resetMotion2([datumSelection], resetMarkerPositionFn2);
|
|
markerSwipeScaleInAnimation2(
|
|
this,
|
|
animationManager,
|
|
{ ...this.getAnimationDrawingModes(), phase: "initial" },
|
|
datumSelection
|
|
);
|
|
seriesLabelFadeInAnimation3(this, "labels", animationManager, labelSelection, this.highlightLabelSelection);
|
|
}
|
|
animateReadyResize(animationData) {
|
|
const { contextData, paths } = animationData;
|
|
this.updateAreaPaths(paths, contextData);
|
|
super.animateReadyResize(animationData);
|
|
}
|
|
animateWaitingUpdateReady(animationData) {
|
|
const { animationManager } = this.ctx;
|
|
const { datumSelection, labelSelection, contextData, paths, previousContextData } = animationData;
|
|
const [fill, lowStroke, highStroke] = paths;
|
|
if (fill == null && lowStroke == null && highStroke == null)
|
|
return;
|
|
this.resetDatumAnimation(animationData);
|
|
this.resetLabelAnimation(animationData);
|
|
const update = () => {
|
|
this.resetPathAnimation(animationData);
|
|
this.updateAreaPaths(paths, contextData);
|
|
};
|
|
const skip = () => {
|
|
animationManager.skipCurrentBatch();
|
|
update();
|
|
};
|
|
if (contextData == null || previousContextData == null) {
|
|
update();
|
|
markerFadeInAnimation2(this, animationManager, "added", this.getAnimationDrawingModes(), datumSelection);
|
|
pathFadeInAnimation2(this, "fill_path_properties", animationManager, "add", fill);
|
|
pathFadeInAnimation2(this, "low_stroke_path_properties", animationManager, "add", lowStroke);
|
|
pathFadeInAnimation2(this, "high_stroke_path_properties", animationManager, "add", highStroke);
|
|
seriesLabelFadeInAnimation3(this, "labels", animationManager, labelSelection, this.highlightLabelSelection);
|
|
return;
|
|
}
|
|
const fns = prepareRangeAreaPathAnimation(
|
|
contextData,
|
|
previousContextData,
|
|
this.processedData?.reduced?.diff?.[this.id]
|
|
);
|
|
if (fns === void 0) {
|
|
skip();
|
|
return;
|
|
} else if (fns.status === "no-op") {
|
|
return;
|
|
}
|
|
fromToMotion4(this.id, "fill_path_properties", animationManager, [fill], fns.fill.pathProperties);
|
|
fromToMotion4(this.id, "low_stroke_path_properties", animationManager, [lowStroke], fns.stroke.pathProperties);
|
|
fromToMotion4(this.id, "high_stroke_path_properties", animationManager, [highStroke], fns.stroke.pathProperties);
|
|
if (fns.status === "added") {
|
|
this.updateAreaPaths(paths, contextData);
|
|
} else if (fns.status === "removed") {
|
|
this.updateAreaPaths(paths, previousContextData);
|
|
} else {
|
|
pathMotion2(this.id, "fill_path_update", animationManager, [fill], fns.fill.path);
|
|
pathMotion2(this.id, "low_stroke_path_update", animationManager, [lowStroke], fns.stroke.path);
|
|
pathMotion2(this.id, "high_stroke_path_update", animationManager, [highStroke], fns.stroke.path);
|
|
}
|
|
if (fns.hasMotion) {
|
|
markerFadeInAnimation2(this, animationManager, void 0, this.getAnimationDrawingModes(), datumSelection);
|
|
seriesLabelFadeInAnimation3(this, "labels", animationManager, labelSelection, this.highlightLabelSelection);
|
|
}
|
|
this.ctx.animationManager.animate({
|
|
id: this.id,
|
|
groupId: "reset_after_animation",
|
|
phase: "trailing",
|
|
from: {},
|
|
to: {},
|
|
onComplete: () => this.updateAreaPaths(paths, contextData)
|
|
});
|
|
}
|
|
getFormattedMarkerStyle(datum) {
|
|
const stylerStyle = this.getStyle();
|
|
const params = this.makeItemStylerParams(datum.itemType);
|
|
return this.getMarkerStyle(
|
|
this.properties.item[datum.itemType].marker,
|
|
datum,
|
|
params,
|
|
{ isHighlight: true, resolveMarkerSubPath: ["item", datum.itemType, "marker"] },
|
|
void 0,
|
|
stylerStyle
|
|
);
|
|
}
|
|
getMarkerStyle(marker, datum, params, opts, defaultOverrideStyle, inheritedStyle) {
|
|
true;
|
|
marker.itemStyler = this.properties.marker.itemStyler;
|
|
return super.getMarkerStyle(marker, datum, params, opts, defaultOverrideStyle, inheritedStyle);
|
|
}
|
|
computeFocusBounds(opts) {
|
|
const hiBox = computeMarkerFocusBounds2(this, opts);
|
|
const loBox = computeMarkerFocusBounds2(this, { ...opts, datumIndex: opts.datumIndex + 1 });
|
|
if (hiBox && loBox) {
|
|
return BBox20.merge([hiBox, loBox]);
|
|
}
|
|
return void 0;
|
|
}
|
|
isDatumEnabled(nodeData, datumIndex) {
|
|
return datumIndex % 2 === 0 && super.isDatumEnabled(nodeData, datumIndex);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.styler != null || this.properties.marker.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
RangeAreaSeries.className = "RangeAreaSeries";
|
|
RangeAreaSeries.type = "range-area";
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-area/rangeAreaSeriesOptionsDef.ts
|
|
var { rangeAreaSeriesThemeableOptionsDef: rangeAreaSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var rangeAreaSeriesOptionsDef = {
|
|
...commonSeriesOptionsDefs,
|
|
...rangeAreaSeriesThemeableOptionsDef2,
|
|
type: required(constant("range-area")),
|
|
xKey: required(string),
|
|
yLowKey: required(string),
|
|
yHighKey: required(string),
|
|
xKeyAxis: string,
|
|
yKeyAxis: string,
|
|
xName: string,
|
|
yName: string,
|
|
yLowName: string,
|
|
yHighName: string,
|
|
legendItemName: string,
|
|
segmentation: shapeSegmentation,
|
|
invertedStyle: {
|
|
enabled: boolean,
|
|
...fillOptionsDef
|
|
}
|
|
};
|
|
rangeAreaSeriesOptionsDef.pickOutsideVisibleMinorAxis = undocumented(boolean);
|
|
rangeAreaSeriesOptionsDef.focusPriority = undocumented(number);
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-area/rangeAreaThemes.ts
|
|
var RANGE_AREA_ITEM = {
|
|
lineDash: {
|
|
$path: "/series/$index/lineDash"
|
|
},
|
|
lineDashOffset: {
|
|
$path: "/series/$index/lineDashOffset"
|
|
},
|
|
stroke: {
|
|
$path: ["/series/$index/stroke", { $palette: "stroke" }]
|
|
},
|
|
strokeOpacity: {
|
|
$path: "/series/$index/strokeOpacity"
|
|
},
|
|
strokeWidth: {
|
|
$path: ["/series/$index/strokeWidth", 1]
|
|
},
|
|
marker: {
|
|
enabled: {
|
|
$path: "/series/$index/marker/enabled"
|
|
},
|
|
fill: {
|
|
$isUserOption: [
|
|
"/series/$index/marker/fill",
|
|
{
|
|
$if: [
|
|
{
|
|
$or: [
|
|
{ $isGradient: { $path: "/series/$index/marker/fill" } },
|
|
{ $isImage: { $path: "/series/$index/marker/fill" } },
|
|
{ $isPattern: { $path: "/series/$index/marker/fill" } }
|
|
]
|
|
},
|
|
{
|
|
$merge: [
|
|
{ $path: "/series/$index/marker/fill" },
|
|
{
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
void 0,
|
|
// default case shouldn't be hit because of $if
|
|
["gradient", FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
$isUserOption: [
|
|
"/series/$index/marker/fill",
|
|
{ $path: "/series/$index/marker/fill" },
|
|
{ $palette: "fill" }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
}
|
|
]
|
|
},
|
|
fillOpacity: {
|
|
$path: "/series/$index/marker/fillOpacity"
|
|
},
|
|
lineDash: {
|
|
$path: "/series/$index/marker/lineDash"
|
|
},
|
|
lineDashOffset: {
|
|
$path: "/series/$index/marker/lineDashOffset"
|
|
},
|
|
shape: {
|
|
$path: "/series/$index/marker/shape"
|
|
},
|
|
size: {
|
|
$path: ["/series/$index/marker/size", 6]
|
|
},
|
|
stroke: {
|
|
$path: ["/series/$index/marker/stroke", { $palette: "stroke" }]
|
|
},
|
|
strokeOpacity: {
|
|
$path: "/series/$index/marker/strokeOpacity"
|
|
},
|
|
strokeWidth: {
|
|
$path: ["/series/$index/marker/strokeWidth", 2]
|
|
}
|
|
}
|
|
};
|
|
var RANGE_AREA_SERIES_THEME = {
|
|
series: {
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_LINEAR_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
fillOpacity: 0.7,
|
|
stroke: { $palette: "stroke" },
|
|
strokeWidth: 1,
|
|
marker: {
|
|
enabled: false,
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
shape: "circle",
|
|
stroke: { $palette: "stroke" },
|
|
size: 6,
|
|
strokeWidth: 2
|
|
},
|
|
nodeClickRange: "nearest",
|
|
item: {
|
|
low: RANGE_AREA_ITEM,
|
|
high: RANGE_AREA_ITEM
|
|
},
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
placement: "outside",
|
|
padding: { $isUserOption: ["./spacing", 0, 10] },
|
|
// compatibility with old `padding` property (now named `spacing`).
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" }
|
|
},
|
|
interpolation: {
|
|
type: "linear"
|
|
},
|
|
tooltip: {
|
|
range: { $path: ["/tooltip/range", "nearest"] }
|
|
},
|
|
highlight: MARKER_SERIES_HIGHLIGHT_STYLE,
|
|
segmentation: SEGMENTATION_DEFAULTS,
|
|
invertedStyle: {
|
|
enabled: false,
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
// @todo(AG-14792) should be { $path: '../fill' } to inherit from series.fill
|
|
["gradient", FILL_GRADIENT_LINEAR_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
fillOpacity: { $path: "../fillOpacity" }
|
|
}
|
|
},
|
|
axes: {
|
|
["number" /* NUMBER */]: {
|
|
crosshair: { enabled: true }
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-area/rangeAreaModule.ts
|
|
var { predictCartesianNonPrimitiveAxis: predictCartesianNonPrimitiveAxis3 } = module_support_exports;
|
|
var RangeAreaSeriesModule = {
|
|
type: "series",
|
|
name: "range-area",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: rangeAreaSeriesOptionsDef,
|
|
matchingKeys: ["xKey", "yLowKey", "yHighKey", "normalizedTo"],
|
|
predictAxis: predictCartesianNonPrimitiveAxis3,
|
|
defaultAxes: {
|
|
y: { type: "number" /* NUMBER */, position: "left" /* LEFT */ },
|
|
x: { type: "category" /* CATEGORY */, position: "bottom" /* BOTTOM */ }
|
|
},
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
themeTemplate: RANGE_AREA_SERIES_THEME,
|
|
create: (ctx) => new RangeAreaSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-bar/rangeBarAggregation.ts
|
|
function aggregateRangeBarData(scale2, xValues, highValues, lowValues, domainInput, smallestKeyInterval, xNeedsValueOf, yNeedsValueOf) {
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeExtremesAggregation([d0, d1], xValues, highValues, lowValues, {
|
|
smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf
|
|
});
|
|
}
|
|
var memoizedAggregateRangeBarData = simpleMemorize2(aggregateRangeBarData);
|
|
function aggregateRangeBarDataFromDataModel(scale2, dataModel, processedData, series, existingFilters) {
|
|
const xValues = dataModel.resolveKeysById(series, "xValue", processedData);
|
|
const highValues = dataModel.resolveColumnById(series, "yHighValue", processedData);
|
|
const lowValues = dataModel.resolveColumnById(series, "yLowValue", processedData);
|
|
const domainInput = dataModel.getDomain(series, "xValue", "key", processedData);
|
|
const xNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "xValue", processedData);
|
|
const yNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "yHighValue", processedData) ?? dataModel.resolveColumnNeedsValueOf(series, "yLowValue", processedData);
|
|
if (existingFilters) {
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeExtremesAggregation([d0, d1], xValues, highValues, lowValues, {
|
|
smallestKeyInterval: processedData.reduced?.smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
existingFilters
|
|
});
|
|
}
|
|
return memoizedAggregateRangeBarData(
|
|
scale2,
|
|
xValues,
|
|
highValues,
|
|
lowValues,
|
|
domainInput,
|
|
processedData.reduced?.smallestKeyInterval,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf
|
|
);
|
|
}
|
|
function aggregateRangeBarDataFromDataModelPartial(scale2, dataModel, processedData, series, targetRange, existingFilters) {
|
|
const xValues = dataModel.resolveKeysById(series, "xValue", processedData);
|
|
const highValues = dataModel.resolveColumnById(series, "yHighValue", processedData);
|
|
const lowValues = dataModel.resolveColumnById(series, "yLowValue", processedData);
|
|
const domainInput = dataModel.getDomain(series, "xValue", "key", processedData);
|
|
const xNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "xValue", processedData);
|
|
const yNeedsValueOf = dataModel.resolveColumnNeedsValueOf(series, "yHighValue", processedData) ?? dataModel.resolveColumnNeedsValueOf(series, "yLowValue", processedData);
|
|
const [d0, d1] = aggregationDomain(scale2, domainInput);
|
|
return computeExtremesAggregationPartial([d0, d1], xValues, highValues, lowValues, {
|
|
smallestKeyInterval: processedData.reduced?.smallestKeyInterval,
|
|
targetRange,
|
|
xNeedsValueOf,
|
|
yNeedsValueOf,
|
|
existingFilters
|
|
});
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-bar/rangeBarProperties.ts
|
|
var { AbstractBarSeriesProperties: AbstractBarSeriesProperties4, makeSeriesTooltip: makeSeriesTooltip12, DropShadow: DropShadow4, Label: Label9 } = module_support_exports;
|
|
var RangeBarSeriesLabel = class extends Label9 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.placement = "inside";
|
|
this.spacing = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarSeriesLabel.prototype, "placement", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarSeriesLabel.prototype, "spacing", 2);
|
|
var RangeBarProperties = class extends AbstractBarSeriesProperties4 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = "#99CCFF";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "#99CCFF";
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.cornerRadius = 0;
|
|
this.shadow = new DropShadow4().set({ enabled: false });
|
|
this.label = new RangeBarSeriesLabel();
|
|
this.tooltip = makeSeriesTooltip12();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "xKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "yLowKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "yHighKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "xName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "yName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "yLowName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "yHighName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "styler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "shadow", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangeBarProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-bar/rangeBarSeries.ts
|
|
var {
|
|
SeriesNodePickMode: SeriesNodePickMode8,
|
|
valueProperty: valueProperty10,
|
|
keyProperty: keyProperty7,
|
|
checkCrisp: checkCrisp2,
|
|
updateLabelNode: updateLabelNode4,
|
|
SMALLEST_KEY_INTERVAL: SMALLEST_KEY_INTERVAL4,
|
|
LARGEST_KEY_INTERVAL: LARGEST_KEY_INTERVAL2,
|
|
diff: diff5,
|
|
prepareBarAnimationFunctions: prepareBarAnimationFunctions2,
|
|
midpointStartingBarPosition: midpointStartingBarPosition2,
|
|
resetBarSelectionsFn: resetBarSelectionsFn2,
|
|
resetBarSelectionsDirect: resetBarSelectionsDirect2,
|
|
fixNumericExtent: fixNumericExtent6,
|
|
seriesLabelFadeInAnimation: seriesLabelFadeInAnimation4,
|
|
resetLabelFn: resetLabelFn3,
|
|
animationValidation: animationValidation5,
|
|
computeBarFocusBounds: computeBarFocusBounds5,
|
|
visibleRangeIndices: visibleRangeIndices4,
|
|
createDatumId: createDatumId11,
|
|
Rect: Rect5,
|
|
PointerEvents: PointerEvents6,
|
|
motion: motion3,
|
|
processedDataIsAnimatable: processedDataIsAnimatable5,
|
|
getItemStyles: getItemStyles3,
|
|
calculateSegments: calculateSegments4,
|
|
toHighlightString: toHighlightString4,
|
|
HighlightState: HighlightState6,
|
|
AggregationManager: AggregationManager4,
|
|
upsertNodeDatum: upsertNodeDatum4
|
|
} = module_support_exports;
|
|
var RangeBarSeriesNodeEvent = class extends module_support_exports.SeriesNodeEvent {
|
|
constructor(type, nativeEvent, datum, series) {
|
|
super(type, nativeEvent, datum, series);
|
|
this.xKey = series.properties.xKey;
|
|
this.yLowKey = series.properties.yLowKey;
|
|
this.yHighKey = series.properties.yHighKey;
|
|
}
|
|
};
|
|
var RangeBarSeries = class extends module_support_exports.AbstractBarSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
pickModes: [SeriesNodePickMode8.AXIS_ALIGNED, SeriesNodePickMode8.EXACT_SHAPE_MATCH],
|
|
propertyKeys: {
|
|
x: ["xKey"],
|
|
y: ["yLowKey", "yHighKey"]
|
|
},
|
|
propertyNames: {
|
|
x: ["xName"],
|
|
y: ["yLowName", "yHighName", "yName"]
|
|
},
|
|
categoryKey: "xValue",
|
|
datumSelectionGarbageCollection: false,
|
|
animationResetFns: {
|
|
datum: resetBarSelectionsFn2,
|
|
label: resetLabelFn3
|
|
}
|
|
});
|
|
this.properties = new RangeBarProperties();
|
|
this.aggregationManager = new AggregationManager4();
|
|
this.NodeEvent = RangeBarSeriesNodeEvent;
|
|
}
|
|
async processData(dataController) {
|
|
const { xKey, yLowKey, yHighKey } = this.properties;
|
|
const xScale = this.getCategoryAxis()?.scale;
|
|
const yScale = this.getValueAxis()?.scale;
|
|
const { isContinuousX, xScaleType, yScaleType } = this.getScaleInformation({ xScale, yScale });
|
|
const extraProps = [];
|
|
if (this.needsDataModelDiff() && this.processedData) {
|
|
extraProps.push(diff5(this.id, this.processedData));
|
|
}
|
|
if (!this.ctx.animationManager.isSkipped()) {
|
|
extraProps.push(animationValidation5());
|
|
}
|
|
const visibleProps = this.visible ? {} : { forceValue: 0 };
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
const { dataModel, processedData } = await this.requestDataModel(dataController, this.data, {
|
|
props: [
|
|
keyProperty7(xKey, xScaleType, { id: "xValue", allowNullKey }),
|
|
valueProperty10(yLowKey, yScaleType, { id: `yLowValue`, invalidValue: null, ...visibleProps }),
|
|
valueProperty10(yHighKey, yScaleType, { id: `yHighValue`, invalidValue: null, ...visibleProps }),
|
|
...isContinuousX ? [SMALLEST_KEY_INTERVAL4, LARGEST_KEY_INTERVAL2] : [],
|
|
...extraProps
|
|
],
|
|
groupByKeys: false
|
|
});
|
|
this.smallestDataInterval = processedData.reduced?.smallestKeyInterval;
|
|
this.largestDataInterval = processedData.reduced?.largestKeyInterval;
|
|
this.aggregateData(dataModel, processedData);
|
|
this.animationState.transition("updateData");
|
|
}
|
|
aggregateData(dataModel, processedData) {
|
|
this.aggregationManager.markStale(processedData.input.count);
|
|
if (processedData.type !== "ungrouped")
|
|
return;
|
|
if (processedDataIsAnimatable5(processedData))
|
|
return;
|
|
const xAxis = this.axes["x" /* X */];
|
|
if (xAxis == null)
|
|
return;
|
|
const targetRange = this.estimateTargetRange();
|
|
this.aggregationManager.aggregate({
|
|
computePartial: (existingFilters) => aggregateRangeBarDataFromDataModelPartial(
|
|
xAxis.scale.type,
|
|
dataModel,
|
|
processedData,
|
|
this,
|
|
targetRange,
|
|
existingFilters
|
|
),
|
|
computeFull: (existingFilters) => aggregateRangeBarDataFromDataModel(xAxis.scale.type, dataModel, processedData, this, existingFilters),
|
|
targetRange
|
|
});
|
|
const filters = this.aggregationManager.filters;
|
|
if (filters && filters.length > 0) {
|
|
debugMetrics_exports.record(
|
|
`${this.type}:aggregation`,
|
|
filters.map((f) => f.maxRange)
|
|
);
|
|
}
|
|
}
|
|
estimateTargetRange() {
|
|
const xAxis = this.axes["x" /* X */];
|
|
if (xAxis?.scale == null)
|
|
return 0;
|
|
const [r0, r1] = xAxis.scale.range;
|
|
return Math.abs(r1 - r0);
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { processedData, dataModel } = this;
|
|
if (!processedData || !dataModel)
|
|
return { domain: [] };
|
|
const {
|
|
keys: [keys]
|
|
} = processedData.domain;
|
|
if (direction === this.getCategoryDirection()) {
|
|
const keyDef = dataModel.resolveProcessedDataDefById(this, `xValue`);
|
|
if (keyDef?.def.type === "key" && keyDef?.def.valueType === "category") {
|
|
const sortMetadata = dataModel.getKeySortMetadata(this, "xValue", processedData);
|
|
return { domain: keys, sortMetadata };
|
|
}
|
|
return { domain: this.padBandExtent(keys) };
|
|
} else {
|
|
const yExtent = this.domainForClippedRange(direction, ["yHighValue", "yLowValue"], "xValue");
|
|
const fixedYExtent = findMinMax(yExtent);
|
|
return { domain: fixNumericExtent6(fixedYExtent) };
|
|
}
|
|
}
|
|
getSeriesRange(_direction, visibleRange) {
|
|
return this.domainForVisibleRange("y" /* Y */, ["yHighValue", "yLowValue"], "xValue", visibleRange);
|
|
}
|
|
/**
|
|
* Creates shared context for node datum creation/update operations.
|
|
* This context is instantiated once and reused across all datum operations
|
|
* to minimize memory allocations. Only caches values that are expensive to
|
|
* compute - cheap property lookups use `this` directly.
|
|
*/
|
|
createNodeDatumContext(xAxis, yAxis) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData)
|
|
return void 0;
|
|
const rawData = processedData.dataSources?.get(this.id)?.data;
|
|
if (rawData == null)
|
|
return void 0;
|
|
const xScale = xAxis.scale;
|
|
const yScale = yAxis.scale;
|
|
const barAlongX = this.getBarDirection() === "x" /* X */;
|
|
const crisp = checkCrisp2(
|
|
xAxis?.scale,
|
|
xAxis?.visibleRange,
|
|
this.smallestDataInterval,
|
|
this.largestDataInterval
|
|
);
|
|
const [r0, r1] = xScale.range;
|
|
const range3 = Math.abs(r1 - r0);
|
|
this.aggregationManager.ensureLevelForRange(range3);
|
|
const dataAggregationFilter = this.aggregationManager.getFilterForRange(range3);
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
const canIncrementallyUpdate = this.contextNodeData?.nodeData != null && (processedData.changeDescription != null || !processedDataIsAnimatable5(processedData) || dataAggregationFilter != null);
|
|
const { groupOffset, barOffset, barWidth } = this.getBarDimensions();
|
|
return {
|
|
xAxis,
|
|
yAxis,
|
|
rawData,
|
|
xValues: dataModel.resolveKeysById(this, `xValue`, processedData),
|
|
yLowValues: dataModel.resolveColumnById(this, `yLowValue`, processedData),
|
|
yHighValues: dataModel.resolveColumnById(this, `yHighValue`, processedData),
|
|
xScale,
|
|
yScale,
|
|
groupOffset,
|
|
barOffset,
|
|
barWidth,
|
|
barAlongX,
|
|
crisp,
|
|
dataAggregationFilter,
|
|
animationEnabled,
|
|
xKey: this.properties.xKey,
|
|
yLowKey: this.properties.yLowKey,
|
|
yHighKey: this.properties.yHighKey,
|
|
labelEnabled: this.properties.label.enabled,
|
|
labelPlacement: this.properties.label.placement,
|
|
labelPadding: (this.properties.label.spacing + (typeof this.properties.label.padding === "number" ? this.properties.label.padding : 0)) * (this.properties.label.placement === "outside" ? 1 : -1),
|
|
canIncrementallyUpdate,
|
|
nodes: canIncrementallyUpdate ? this.contextNodeData.nodeData : [],
|
|
nodeIndex: 0
|
|
};
|
|
}
|
|
/**
|
|
* Validates and prepares state needed for node creation/update.
|
|
* Returns undefined if datum should be skipped.
|
|
*/
|
|
prepareNodeDatumState(ctx, scratch, datumIndex) {
|
|
const datum = ctx.rawData[datumIndex];
|
|
const xValue = ctx.xValues[datumIndex];
|
|
if (xValue === void 0 && !this.properties.allowNullKeys)
|
|
return void 0;
|
|
const rawLowValue = ctx.yLowValues[datumIndex];
|
|
const rawHighValue = ctx.yHighValues[datumIndex];
|
|
if (!Number.isFinite(rawLowValue?.valueOf()) || !Number.isFinite(rawHighValue?.valueOf()))
|
|
return void 0;
|
|
const [yLowValue, yHighValue] = rawLowValue < rawHighValue ? [rawLowValue, rawHighValue] : [rawHighValue, rawLowValue];
|
|
scratch.datum = datum;
|
|
scratch.xValue = xValue;
|
|
scratch.yLowValue = yLowValue;
|
|
scratch.yHighValue = yHighValue;
|
|
scratch.rawLowValue = rawLowValue;
|
|
scratch.rawHighValue = rawHighValue;
|
|
return scratch;
|
|
}
|
|
/**
|
|
* Creates a minimal skeleton node - actual values set by updateNodeDatum.
|
|
*/
|
|
createSkeletonNodeDatum(ctx, params) {
|
|
const scratch = params.nodeDatumScratch;
|
|
return {
|
|
index: params.groupedDataIndex,
|
|
series: this,
|
|
datum: scratch.datum,
|
|
datumIndex: params.datumIndex,
|
|
xValue: scratch.xValue,
|
|
yLowValue: 0,
|
|
// Will be updated by updateNodeDatum
|
|
yHighValue: 0,
|
|
// Will be updated by updateNodeDatum
|
|
yLowKey: ctx.yLowKey,
|
|
yHighKey: ctx.yHighKey,
|
|
xKey: ctx.xKey,
|
|
x: 0,
|
|
// Will be updated by updateNodeDatum
|
|
y: 0,
|
|
// Will be updated by updateNodeDatum
|
|
width: 0,
|
|
// Will be updated by updateNodeDatum
|
|
height: 0,
|
|
// Will be updated by updateNodeDatum
|
|
midPoint: { x: 0, y: 0 },
|
|
// Will be updated by updateNodeDatum
|
|
crisp: params.crisp,
|
|
labels: []
|
|
// Will be updated by updateNodeDatum
|
|
};
|
|
}
|
|
/**
|
|
* Creates a new node: skeleton + update.
|
|
*/
|
|
createNodeDatum(ctx, params, _itemId, strokeWidth) {
|
|
const prepared = this.prepareNodeDatumState(ctx, params.nodeDatumScratch, params.datumIndex);
|
|
if (!prepared)
|
|
return void 0;
|
|
const nodeData = this.createSkeletonNodeDatum(ctx, params);
|
|
this.updateNodeDatum(ctx, nodeData, params, strokeWidth, prepared);
|
|
return nodeData;
|
|
}
|
|
/**
|
|
* Updates node properties in-place.
|
|
* Shared by both create (skeleton + update) and incremental update paths.
|
|
*/
|
|
updateNodeDatum(ctx, node, params, strokeWidth, prepared) {
|
|
prepared ?? (prepared = this.prepareNodeDatumState(ctx, params.nodeDatumScratch, params.datumIndex));
|
|
if (!prepared)
|
|
return;
|
|
const mutableNode = node;
|
|
mutableNode.index = params.groupedDataIndex;
|
|
mutableNode.datum = prepared.datum;
|
|
mutableNode.datumIndex = params.datumIndex;
|
|
mutableNode.xValue = prepared.xValue;
|
|
mutableNode.yLowValue = prepared.rawLowValue;
|
|
mutableNode.yHighValue = prepared.rawHighValue;
|
|
mutableNode.crisp = params.crisp;
|
|
const y = Math.round(ctx.yScale.convert(params.yHigh));
|
|
const bottomY = Math.round(ctx.yScale.convert(params.yLow));
|
|
const height2 = Math.max(strokeWidth, Math.abs(bottomY - y));
|
|
const rect2 = {
|
|
x: ctx.barAlongX ? Math.min(y, bottomY) : params.x,
|
|
y: ctx.barAlongX ? params.x : Math.min(y, bottomY),
|
|
width: ctx.barAlongX ? height2 : params.width,
|
|
height: ctx.barAlongX ? params.width : height2
|
|
};
|
|
mutableNode.x = rect2.x;
|
|
mutableNode.y = rect2.y;
|
|
mutableNode.width = rect2.width;
|
|
mutableNode.height = rect2.height;
|
|
const mutableMidPoint = mutableNode.midPoint;
|
|
mutableMidPoint.x = rect2.x + rect2.width / 2;
|
|
mutableMidPoint.y = rect2.y + rect2.height / 2;
|
|
const existingClipBBox = mutableNode.clipBBox;
|
|
if (existingClipBBox) {
|
|
existingClipBBox.x = rect2.x;
|
|
existingClipBBox.y = rect2.y;
|
|
existingClipBBox.width = rect2.width;
|
|
existingClipBBox.height = rect2.height;
|
|
}
|
|
const labelParams = params.labelParamsScratch;
|
|
labelParams.labels = mutableNode.labels;
|
|
labelParams.datumIndex = params.datumIndex;
|
|
labelParams.rectX = rect2.x;
|
|
labelParams.rectY = rect2.y;
|
|
labelParams.rectWidth = rect2.width;
|
|
labelParams.rectHeight = rect2.height;
|
|
labelParams.yLowValue = prepared.yLowValue;
|
|
labelParams.yHighValue = prepared.yHighValue;
|
|
labelParams.datum = prepared.datum;
|
|
this.updateLabelData(ctx, labelParams);
|
|
}
|
|
/**
|
|
* Creates node data using aggregation filters for large datasets.
|
|
*/
|
|
createNodeDataWithAggregation(ctx, xPosition, nodeDatumParamsScratch, itemId, strokeWidth, dataAggregationFilter) {
|
|
const { maxRange, indexData, midpointIndices } = dataAggregationFilter;
|
|
const [start2, end3] = visibleRangeIndices4(1, maxRange, ctx.xAxis.range, (index) => {
|
|
const aggIndex = index * AGGREGATION_SPAN;
|
|
const xMaxIndex = indexData[aggIndex + AGGREGATION_INDEX_X_MAX];
|
|
const midDatumIndex = midpointIndices[index];
|
|
if (midDatumIndex === -1)
|
|
return;
|
|
return [xPosition(midDatumIndex), xPosition(xMaxIndex) + ctx.barWidth];
|
|
});
|
|
for (let i = start2; i < end3; i += 1) {
|
|
const aggIndex = i * AGGREGATION_SPAN;
|
|
const xMinIndex = indexData[aggIndex + AGGREGATION_INDEX_X_MIN];
|
|
const xMaxIndex = indexData[aggIndex + AGGREGATION_INDEX_X_MAX];
|
|
const yMinIndex = indexData[aggIndex + AGGREGATION_INDEX_Y_MIN];
|
|
const yMaxIndex = indexData[aggIndex + AGGREGATION_INDEX_Y_MAX];
|
|
const midDatumIndex = midpointIndices[i];
|
|
if (midDatumIndex === -1)
|
|
continue;
|
|
const xValue = ctx.xValues[midDatumIndex];
|
|
if (xValue === void 0 && !this.properties.allowNullKeys)
|
|
continue;
|
|
nodeDatumParamsScratch.datumIndex = midDatumIndex;
|
|
nodeDatumParamsScratch.groupedDataIndex = 0;
|
|
nodeDatumParamsScratch.x = xPosition(midDatumIndex);
|
|
nodeDatumParamsScratch.width = Math.abs(xPosition(xMinIndex) - xPosition(xMaxIndex)) + ctx.barWidth;
|
|
nodeDatumParamsScratch.yLow = ctx.yLowValues[yMinIndex];
|
|
nodeDatumParamsScratch.yHigh = ctx.yHighValues[yMaxIndex];
|
|
nodeDatumParamsScratch.crisp = false;
|
|
upsertNodeDatum4(
|
|
ctx,
|
|
nodeDatumParamsScratch,
|
|
(c, p) => this.createNodeDatum(c, p, itemId, strokeWidth),
|
|
(c, n, p) => this.updateNodeDatum(c, n, p, strokeWidth)
|
|
);
|
|
}
|
|
}
|
|
/**
|
|
* Creates node data for simple (ungrouped) data processing.
|
|
*/
|
|
createNodeDataSimple(ctx, xPosition, nodeDatumParamsScratch, itemId, strokeWidth, processedData) {
|
|
const invalidData = processedData.invalidData?.get(this.id);
|
|
let [start2, end3] = this.visibleRangeIndices("xValue", ctx.xAxis.range);
|
|
if (processedData.input.count < 1e3) {
|
|
start2 = 0;
|
|
end3 = processedData.input.count;
|
|
}
|
|
for (let datumIndex = start2; datumIndex < end3; datumIndex += 1) {
|
|
if (invalidData?.[datumIndex] === true)
|
|
continue;
|
|
nodeDatumParamsScratch.datumIndex = datumIndex;
|
|
nodeDatumParamsScratch.groupedDataIndex = 0;
|
|
nodeDatumParamsScratch.x = xPosition(datumIndex);
|
|
nodeDatumParamsScratch.width = ctx.barWidth;
|
|
nodeDatumParamsScratch.yLow = ctx.yLowValues[datumIndex];
|
|
nodeDatumParamsScratch.yHigh = ctx.yHighValues[datumIndex];
|
|
nodeDatumParamsScratch.crisp = ctx.crisp;
|
|
upsertNodeDatum4(
|
|
ctx,
|
|
nodeDatumParamsScratch,
|
|
(c, p) => this.createNodeDatum(c, p, itemId, strokeWidth),
|
|
(c, n, p) => this.updateNodeDatum(c, n, p, strokeWidth)
|
|
);
|
|
}
|
|
}
|
|
/**
|
|
* Creates node data for grouped data processing.
|
|
*/
|
|
createNodeDataGrouped(ctx, xPosition, nodeDatumParamsScratch, itemId, strokeWidth) {
|
|
const processedData = this.processedData;
|
|
for (const { datumIndex, groupIndex: groupDataIndex } of this.dataModel.forEachGroupDatum(
|
|
this,
|
|
processedData
|
|
)) {
|
|
nodeDatumParamsScratch.datumIndex = datumIndex;
|
|
nodeDatumParamsScratch.groupedDataIndex = groupDataIndex;
|
|
nodeDatumParamsScratch.x = xPosition(datumIndex);
|
|
nodeDatumParamsScratch.width = ctx.barWidth;
|
|
nodeDatumParamsScratch.yLow = ctx.yLowValues[datumIndex];
|
|
nodeDatumParamsScratch.yHigh = ctx.yHighValues[datumIndex];
|
|
nodeDatumParamsScratch.crisp = ctx.crisp;
|
|
upsertNodeDatum4(
|
|
ctx,
|
|
nodeDatumParamsScratch,
|
|
(c, p) => this.createNodeDatum(c, p, itemId, strokeWidth),
|
|
(c, n, p) => this.updateNodeDatum(c, n, p, strokeWidth)
|
|
);
|
|
}
|
|
}
|
|
populateNodeData(ctx) {
|
|
const { processedData } = this;
|
|
if (!processedData)
|
|
return;
|
|
const { yLowKey, yHighKey, strokeWidth } = this.properties;
|
|
const itemId = `${yLowKey}-${yHighKey}`;
|
|
const xPosition = (datumIndex) => {
|
|
const x = ctx.xScale.convert(ctx.xValues[datumIndex]);
|
|
if (!Number.isFinite(x))
|
|
return Number.NaN;
|
|
return Math.round(x) + ctx.groupOffset + ctx.barOffset;
|
|
};
|
|
const nodeDatumParamsScratch = {
|
|
nodeDatumScratch: {
|
|
datum: void 0,
|
|
xValue: void 0,
|
|
yLowValue: 0,
|
|
yHighValue: 0,
|
|
rawLowValue: void 0,
|
|
rawHighValue: void 0
|
|
},
|
|
labelParamsScratch: {
|
|
labels: [],
|
|
datumIndex: 0,
|
|
rectX: 0,
|
|
rectY: 0,
|
|
rectWidth: 0,
|
|
rectHeight: 0,
|
|
yLowValue: 0,
|
|
yHighValue: 0,
|
|
datum: void 0
|
|
},
|
|
datumIndex: 0,
|
|
groupedDataIndex: 0,
|
|
x: 0,
|
|
width: 0,
|
|
yLow: 0,
|
|
yHigh: 0,
|
|
crisp: false
|
|
};
|
|
if (ctx.dataAggregationFilter != null) {
|
|
this.createNodeDataWithAggregation(
|
|
ctx,
|
|
xPosition,
|
|
nodeDatumParamsScratch,
|
|
itemId,
|
|
strokeWidth,
|
|
ctx.dataAggregationFilter
|
|
);
|
|
} else if (processedData.type === "ungrouped") {
|
|
this.createNodeDataSimple(ctx, xPosition, nodeDatumParamsScratch, itemId, strokeWidth, processedData);
|
|
} else {
|
|
this.createNodeDataGrouped(ctx, xPosition, nodeDatumParamsScratch, itemId, strokeWidth);
|
|
}
|
|
}
|
|
finalizeNodeData(ctx) {
|
|
if (ctx.canIncrementallyUpdate && ctx.nodeIndex < ctx.nodes.length) {
|
|
ctx.nodes.length = ctx.nodeIndex;
|
|
}
|
|
}
|
|
initializeResult(ctx) {
|
|
const { yLowKey, yHighKey } = this.properties;
|
|
const itemId = `${yLowKey}-${yHighKey}`;
|
|
const xAxis = this.getCategoryAxis();
|
|
const yAxis = this.getValueAxis();
|
|
const segments = xAxis && yAxis && this.chart?.seriesRect ? calculateSegments4(this.properties.segmentation, xAxis, yAxis, this.chart.seriesRect, this.ctx.scene) : void 0;
|
|
return {
|
|
itemId,
|
|
nodeData: ctx.nodes,
|
|
labelData: [],
|
|
scales: this.calculateScaling(),
|
|
groupScale: this.getScaling(this.ctx.seriesStateManager.getGroupScale(this)),
|
|
visible: this.visible,
|
|
styles: getItemStyles3(this.getItemStyle.bind(this)),
|
|
segments
|
|
};
|
|
}
|
|
assembleResult(ctx, result) {
|
|
for (const node of ctx.nodes) {
|
|
result.labelData.push(...node.labels);
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Updates existing label data in place or creates new labels if needed.
|
|
* This avoids array allocations during incremental updates.
|
|
* Uses positional params (no destructuring) for performance in hot path.
|
|
*/
|
|
updateLabelData(ctx, params) {
|
|
const labels = params.labels;
|
|
if (!ctx.labelEnabled) {
|
|
if (labels.length > 0) {
|
|
labels.length = 0;
|
|
}
|
|
return;
|
|
}
|
|
const { xKey, yLowKey, yHighKey, xName, yLowName, yHighName, yName, legendItemName, label } = this.properties;
|
|
const barAlongX = ctx.barAlongX;
|
|
const placement = ctx.labelPlacement;
|
|
const labelPadding = ctx.labelPadding;
|
|
const rectX = params.rectX;
|
|
const rectY = params.rectY;
|
|
const rectWidth = params.rectWidth;
|
|
const rectHeight = params.rectHeight;
|
|
const yLowX = rectX + (barAlongX ? -labelPadding : rectWidth / 2);
|
|
const yLowY = rectY + (barAlongX ? rectHeight / 2 : rectHeight + labelPadding);
|
|
let yLowTextAlign;
|
|
if (placement === "outside") {
|
|
yLowTextAlign = barAlongX ? "right" : "center";
|
|
} else {
|
|
yLowTextAlign = barAlongX ? "left" : "center";
|
|
}
|
|
let yLowTextBaseline;
|
|
if (placement === "outside") {
|
|
yLowTextBaseline = barAlongX ? "middle" : "top";
|
|
} else {
|
|
yLowTextBaseline = barAlongX ? "middle" : "bottom";
|
|
}
|
|
const yHighX = rectX + (barAlongX ? rectWidth + labelPadding : rectWidth / 2);
|
|
const yHighY = rectY + (barAlongX ? rectHeight / 2 : -labelPadding);
|
|
let yHighTextAlign;
|
|
if (placement === "outside") {
|
|
yHighTextAlign = barAlongX ? "left" : "center";
|
|
} else {
|
|
yHighTextAlign = barAlongX ? "right" : "center";
|
|
}
|
|
let yHighTextBaseline;
|
|
if (placement === "outside") {
|
|
yHighTextBaseline = barAlongX ? "middle" : "bottom";
|
|
} else {
|
|
yHighTextBaseline = barAlongX ? "middle" : "top";
|
|
}
|
|
const datum = params.datum;
|
|
const yLowValue = params.yLowValue;
|
|
const yHighValue = params.yHighValue;
|
|
const datumIndex = params.datumIndex;
|
|
const labelTextParams = { datum, xKey, yLowKey, yHighKey, xName, yLowName, yHighName, yName, legendItemName };
|
|
const yDomain = this.getSeriesDomain("y" /* Y */).domain;
|
|
const yLowText = this.getLabelText(
|
|
yLowValue,
|
|
datum,
|
|
yLowKey,
|
|
"y",
|
|
yDomain,
|
|
label,
|
|
{ itemType: "low", value: yLowValue, ...labelTextParams }
|
|
);
|
|
const yHighText = this.getLabelText(
|
|
yHighValue,
|
|
datum,
|
|
yHighKey,
|
|
"y",
|
|
yDomain,
|
|
label,
|
|
{ itemType: "high", value: yHighValue, ...labelTextParams }
|
|
);
|
|
if (labels.length > 0 && labels[0].itemType === "low") {
|
|
const yLowLabel = labels[0];
|
|
yLowLabel.datumIndex = datumIndex;
|
|
yLowLabel.x = yLowX;
|
|
yLowLabel.y = yLowY;
|
|
yLowLabel.textAlign = yLowTextAlign;
|
|
yLowLabel.textBaseline = yLowTextBaseline;
|
|
yLowLabel.text = yLowText;
|
|
yLowLabel.datum = datum;
|
|
} else {
|
|
labels[0] = {
|
|
datumIndex,
|
|
x: yLowX,
|
|
y: yLowY,
|
|
textAlign: yLowTextAlign,
|
|
textBaseline: yLowTextBaseline,
|
|
text: yLowText,
|
|
itemType: "low",
|
|
datum,
|
|
series: this
|
|
};
|
|
}
|
|
if (labels.length > 1 && labels[1].itemType === "high") {
|
|
const yHighLabel = labels[1];
|
|
yHighLabel.datumIndex = datumIndex;
|
|
yHighLabel.x = yHighX;
|
|
yHighLabel.y = yHighY;
|
|
yHighLabel.textAlign = yHighTextAlign;
|
|
yHighLabel.textBaseline = yHighTextBaseline;
|
|
yHighLabel.text = yHighText;
|
|
yHighLabel.datum = datum;
|
|
} else {
|
|
labels[1] = {
|
|
datumIndex,
|
|
x: yHighX,
|
|
y: yHighY,
|
|
textAlign: yHighTextAlign,
|
|
textBaseline: yHighTextBaseline,
|
|
text: yHighText,
|
|
itemType: "high",
|
|
datum,
|
|
series: this
|
|
};
|
|
}
|
|
labels.length = 2;
|
|
}
|
|
nodeFactory() {
|
|
return new Rect5();
|
|
}
|
|
getStyle(ignoreStylerCallback, highlightState) {
|
|
const {
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
styler
|
|
} = this.properties;
|
|
let stylerResult = {};
|
|
if (!ignoreStylerCallback && styler) {
|
|
const stylerParams = this.makeStylerParams(highlightState);
|
|
stylerResult = this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`],
|
|
this.cachedCallWithContext(styler, stylerParams) ?? {},
|
|
{ pick: false }
|
|
) ?? {};
|
|
}
|
|
return {
|
|
cornerRadius: stylerResult.cornerRadius ?? cornerRadius,
|
|
fill: stylerResult.fill ?? fill,
|
|
fillOpacity: stylerResult.fillOpacity ?? fillOpacity,
|
|
lineDash: stylerResult.lineDash ?? lineDash,
|
|
lineDashOffset: stylerResult.lineDashOffset ?? lineDashOffset,
|
|
opacity: 1,
|
|
stroke: stylerResult.stroke ?? stroke3,
|
|
strokeOpacity: stylerResult.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: stylerResult.strokeWidth ?? strokeWidth
|
|
};
|
|
}
|
|
makeStylerParams(highlightStateEnum) {
|
|
const { id: seriesId } = this;
|
|
const {
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
xKey,
|
|
yLowKey,
|
|
yHighKey
|
|
} = this.properties;
|
|
const highlightState = toHighlightString4(highlightStateEnum ?? HighlightState6.None);
|
|
return {
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
highlightState,
|
|
lineDash,
|
|
lineDashOffset,
|
|
seriesId,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
xKey,
|
|
yLowKey,
|
|
yHighKey
|
|
};
|
|
}
|
|
updateDatumSelection(opts) {
|
|
const { nodeData, datumSelection } = opts;
|
|
const data = nodeData ?? [];
|
|
if (!processedDataIsAnimatable5(this.processedData)) {
|
|
return datumSelection.update(data);
|
|
}
|
|
return datumSelection.update(data, void 0, (datum) => this.getDatumId(datum));
|
|
}
|
|
getItemStyle(datumIndex, isHighlight, highlightState) {
|
|
const { properties, dataModel, processedData } = this;
|
|
const { itemStyler } = properties;
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex, highlightState);
|
|
let style2 = mergeDefaults(highlightStyle, this.getStyle(datumIndex === void 0, highlightState));
|
|
if (itemStyler && dataModel != null && processedData != null && datumIndex != null) {
|
|
const xValue = dataModel.resolveKeysById(this, `xValue`, processedData)[datumIndex];
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId11(this.getDatumId({ xValue }), isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const params = this.makeItemStylerParams(datumIndex, isHighlight, style2);
|
|
return this.callWithContext(itemStyler, params);
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(overrides, style2);
|
|
}
|
|
}
|
|
return style2;
|
|
}
|
|
makeItemStylerParams(datumIndex, isHighlight, style2) {
|
|
const { id: seriesId, properties, processedData } = this;
|
|
const { xKey, yHighKey, yLowKey } = properties;
|
|
const datum = processedData.dataSources.get(seriesId)?.data[datumIndex];
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightStateString = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
seriesId,
|
|
datum,
|
|
xKey,
|
|
yHighKey,
|
|
yLowKey,
|
|
highlightState: highlightStateString,
|
|
...style2,
|
|
fill
|
|
};
|
|
}
|
|
updateDatumStyles(opts) {
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
opts.datumSelection.each((node, datum) => {
|
|
if (!opts.datumSelection.isGarbage(node)) {
|
|
const highlightState = this.getHighlightState(highlightedDatum, opts.isHighlight, datum.datumIndex);
|
|
datum.style = this.getItemStyle(datum.datumIndex, opts.isHighlight, highlightState);
|
|
}
|
|
});
|
|
}
|
|
updateDatumNodes({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
const { contextNodeData } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
const categoryAlongX = this.getCategoryDirection() === "x" /* X */;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
const series = this;
|
|
datumSelection.each(function updateRangeBarNode(rect2, datum) {
|
|
const style2 = datum.style ?? contextNodeData.styles[series.getHighlightState(highlightedDatum, isHighlight, datum.datumIndex)];
|
|
rect2.setStyleProperties(style2, fillBBox);
|
|
rect2.setStaticProperties(
|
|
"overlay",
|
|
style2.cornerRadius ?? 0,
|
|
style2.cornerRadius ?? 0,
|
|
style2.cornerRadius ?? 0,
|
|
style2.cornerRadius ?? 0,
|
|
categoryAlongX ? datum.width > 0 : datum.height > 0,
|
|
datum.crisp,
|
|
void 0
|
|
);
|
|
});
|
|
}
|
|
updateLabelSelection(opts) {
|
|
const labelData = this.properties.label.enabled ? opts.labelData : [];
|
|
return opts.labelSelection.update(labelData, (text2) => {
|
|
text2.pointerEvents = PointerEvents6.None;
|
|
});
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const { isHighlight = false } = opts;
|
|
const params = {
|
|
xKey: this.properties.xKey,
|
|
xName: this.properties.xName ?? this.properties.xKey,
|
|
yName: this.properties.yName,
|
|
yLowKey: this.properties.yLowKey,
|
|
yLowName: this.properties.yLowName ?? this.properties.yLowKey,
|
|
yHighKey: this.properties.yHighKey,
|
|
yHighName: this.properties.yHighName ?? this.properties.yHighKey,
|
|
legendItemName: this.properties.legendItemName
|
|
};
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
opts.labelSelection.each((textNode, datum) => {
|
|
textNode.fillOpacity = this.getHighlightStyle(isHighlight, datum?.datumIndex).opacity ?? 1;
|
|
updateLabelNode4(this, textNode, params, this.properties.label, datum, isHighlight, activeHighlight);
|
|
});
|
|
}
|
|
getHighlightLabelData(labelData, highlightedItem) {
|
|
if (highlightedItem.labels?.length) {
|
|
return highlightedItem.labels;
|
|
}
|
|
return super.getHighlightLabelData(labelData, highlightedItem);
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, properties } = this;
|
|
const { xKey, xName, yName, yLowKey, yHighKey, yLowName, yHighName, tooltip, legendItemName } = properties;
|
|
const xAxis = this.getCategoryAxis();
|
|
const yAxis = this.getValueAxis();
|
|
if (!dataModel || !processedData || !xAxis || !yAxis)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const xValue = dataModel.resolveKeysById(this, `xValue`, processedData)[datumIndex];
|
|
const yHighValue = dataModel.resolveColumnById(this, `yHighValue`, processedData)[datumIndex];
|
|
const yLowValue = dataModel.resolveColumnById(this, `yLowValue`, processedData)[datumIndex];
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
if (xValue === void 0 && !allowNullKeys)
|
|
return;
|
|
const format = this.getItemStyle(datumIndex, false);
|
|
const value = `${this.getAxisValueText(yAxis, "tooltip", yLowValue, datum, yLowKey, legendItemName)} - ${this.getAxisValueText(yAxis, "tooltip", yHighValue, datum, yHighKey, legendItemName)}`;
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading: this.getAxisValueText(xAxis, "tooltip", xValue, datum, xKey, legendItemName),
|
|
symbol: this.legendItemSymbol(),
|
|
data: [
|
|
{
|
|
label: yName,
|
|
fallbackLabel: `${yLowName ?? yLowKey} - ${yHighName ?? yHighKey}`,
|
|
value,
|
|
missing: module_support_exports.isTooltipValueMissing(yHighValue) && module_support_exports.isTooltipValueMissing(yLowValue)
|
|
}
|
|
]
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: yName,
|
|
xKey,
|
|
xName,
|
|
yName,
|
|
yLowKey,
|
|
yHighKey,
|
|
yLowName,
|
|
yHighName,
|
|
legendItemName,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
legendItemSymbol() {
|
|
const { fill, stroke: stroke3, strokeWidth, fillOpacity, strokeOpacity, lineDash, lineDashOffset } = this.getStyle(
|
|
false,
|
|
HighlightState6.None
|
|
);
|
|
return {
|
|
marker: {
|
|
fill,
|
|
stroke: stroke3,
|
|
fillOpacity,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash,
|
|
lineDashOffset
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
if (legendType !== "category") {
|
|
return [];
|
|
}
|
|
const { id: seriesId, visible } = this;
|
|
const { yName, yLowName, yHighName, yLowKey, yHighKey, legendItemName, showInLegend } = this.properties;
|
|
const legendItemText = legendItemName ?? yName ?? `${yLowName ?? yLowKey} - ${yHighName ?? yHighKey}`;
|
|
const itemId = `${yLowKey}-${yHighKey}`;
|
|
return [
|
|
{
|
|
legendType: "category",
|
|
id: seriesId,
|
|
itemId,
|
|
seriesId,
|
|
enabled: visible,
|
|
label: { text: `${legendItemText}` },
|
|
symbol: this.legendItemSymbol(),
|
|
legendItemName,
|
|
hideInLegend: !showInLegend
|
|
}
|
|
];
|
|
}
|
|
resetDatumAnimation(data) {
|
|
resetBarSelectionsDirect2([data.datumSelection]);
|
|
}
|
|
animateEmptyUpdateReady({ datumSelection, labelSelection }) {
|
|
const fns = prepareBarAnimationFunctions2(midpointStartingBarPosition2(this.isVertical(), "normal"), "unknown");
|
|
motion3.fromToMotion(this.id, "datums", this.ctx.animationManager, [datumSelection], fns);
|
|
seriesLabelFadeInAnimation4(
|
|
this,
|
|
"labels",
|
|
this.ctx.animationManager,
|
|
labelSelection,
|
|
this.highlightLabelSelection
|
|
);
|
|
}
|
|
animateWaitingUpdateReady(data) {
|
|
const { datumSelection: datumSelections, labelSelection, contextData, previousContextData } = data;
|
|
const dataDiff = module_support_exports.calculateDataDiff(
|
|
this.id,
|
|
datumSelections,
|
|
this.getDatumId.bind(this),
|
|
contextData,
|
|
previousContextData,
|
|
this.processedData,
|
|
this.processedDataUpdated
|
|
);
|
|
this.ctx.animationManager.stopByAnimationGroupId(this.id);
|
|
const mode = previousContextData == null ? "fade" : "normal";
|
|
const fns = prepareBarAnimationFunctions2(midpointStartingBarPosition2(this.isVertical(), mode), "added");
|
|
motion3.fromToMotion(
|
|
this.id,
|
|
"datums",
|
|
this.ctx.animationManager,
|
|
[datumSelections],
|
|
fns,
|
|
(_, datum) => this.getDatumId(datum),
|
|
dataDiff
|
|
);
|
|
if (dataDiff?.changed || !areScalingEqual(contextData.groupScale, previousContextData?.groupScale)) {
|
|
seriesLabelFadeInAnimation4(
|
|
this,
|
|
"labels",
|
|
this.ctx.animationManager,
|
|
labelSelection,
|
|
this.highlightLabelSelection
|
|
);
|
|
}
|
|
}
|
|
getDatumId(datum) {
|
|
return `${datum.xValue}`;
|
|
}
|
|
isLabelEnabled() {
|
|
return this.properties.label.enabled;
|
|
}
|
|
computeFocusBounds({ datumIndex }) {
|
|
return computeBarFocusBounds5(this, this.contextNodeData?.nodeData[datumIndex]);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.styler != null || this.properties.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
RangeBarSeries.className = "RangeBarSeries";
|
|
RangeBarSeries.type = "range-bar";
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-bar/rangeBarSeriesOptionsDef.ts
|
|
var { rangeBarSeriesThemeableOptionsDef: rangeBarSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var rangeBarSeriesOptionsDef = {
|
|
...commonSeriesOptionsDefs,
|
|
...rangeBarSeriesThemeableOptionsDef2,
|
|
type: required(constant("range-bar")),
|
|
xKey: required(string),
|
|
yLowKey: required(string),
|
|
yHighKey: required(string),
|
|
xKeyAxis: string,
|
|
yKeyAxis: string,
|
|
xName: string,
|
|
yName: string,
|
|
yLowName: string,
|
|
yHighName: string,
|
|
legendItemName: string,
|
|
segmentation: shapeSegmentation,
|
|
width: positiveNumberNonZero,
|
|
widthRatio: ratio
|
|
};
|
|
rangeBarSeriesOptionsDef.pickOutsideVisibleMinorAxis = undocumented(boolean);
|
|
rangeBarSeriesOptionsDef.focusPriority = undocumented(number);
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-bar/rangeBarThemes.ts
|
|
var RANGE_BAR_SERIES_THEME = {
|
|
series: {
|
|
direction: "vertical",
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_LINEAR_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: { $palette: "stroke" },
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, 0] },
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "chartBackgroundColor" },
|
|
placement: "inside",
|
|
padding: { $isUserOption: ["./spacing", 0, 6] }
|
|
// compatibility with old `padding` property (now named `spacing`).
|
|
},
|
|
highlight: MULTI_SERIES_HIGHLIGHT_STYLE,
|
|
segmentation: SEGMENTATION_DEFAULTS
|
|
},
|
|
axes: {
|
|
["number" /* NUMBER */]: {
|
|
crosshair: { enabled: true }
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/range-bar/rangeBarModule.ts
|
|
var { predictCartesianNonPrimitiveAxis: predictCartesianNonPrimitiveAxis4 } = module_support_exports;
|
|
var RangeBarSeriesModule = {
|
|
type: "series",
|
|
name: "range-bar",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
groupable: true,
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: rangeBarSeriesOptionsDef,
|
|
matchingKeys: ["xKey", "yLowKey", "yHighKey", "normalizedTo"],
|
|
predictAxis: predictCartesianNonPrimitiveAxis4,
|
|
defaultAxes: DIRECTION_SWAP_AXES,
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
axisKeysFlipped: { ["x" /* X */]: "yKeyAxis", ["y" /* Y */]: "xKeyAxis" },
|
|
themeTemplate: RANGE_BAR_SERIES_THEME,
|
|
create: (ctx) => new RangeBarSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/waterfall/waterfallSeriesProperties.ts
|
|
var { AbstractBarSeriesProperties: AbstractBarSeriesProperties5, makeSeriesTooltip: makeSeriesTooltip13, DropShadow: DropShadow5, Label: Label10 } = module_support_exports;
|
|
var WaterfallSeriesTotal = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesTotal.prototype, "totalType", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesTotal.prototype, "index", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesTotal.prototype, "axisLabel", 2);
|
|
var WaterfallSeriesItemTooltip = class extends BaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItemTooltip.prototype, "renderer", 2);
|
|
var WaterfallSeriesLabel = class extends Label10 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.placement = "outside-end";
|
|
this.spacing = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesLabel.prototype, "placement", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesLabel.prototype, "spacing", 2);
|
|
var WaterfallSeriesItem = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = "#c16068";
|
|
this.stroke = "#c16068";
|
|
this.fillOpacity = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.strokeWidth = 1;
|
|
this.cornerRadius = 0;
|
|
this.shadow = new DropShadow5().set({ enabled: false });
|
|
this.label = new WaterfallSeriesLabel();
|
|
this.tooltip = new WaterfallSeriesItemTooltip();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItem.prototype, "name", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItem.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItem.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItem.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItem.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItem.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItem.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItem.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItem.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItem.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItem.prototype, "shadow", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItem.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItem.prototype, "tooltip", 2);
|
|
var WaterfallSeriesConnectorLine = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = true;
|
|
this.stroke = "black";
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.strokeWidth = 2;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesConnectorLine.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesConnectorLine.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesConnectorLine.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesConnectorLine.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesConnectorLine.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesConnectorLine.prototype, "strokeWidth", 2);
|
|
var WaterfallSeriesItems = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.positive = new WaterfallSeriesItem();
|
|
this.negative = new WaterfallSeriesItem();
|
|
this.total = new WaterfallSeriesItem();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItems.prototype, "positive", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItems.prototype, "negative", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesItems.prototype, "total", 2);
|
|
var WaterfallSeriesProperties = class extends AbstractBarSeriesProperties5 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.item = new WaterfallSeriesItems();
|
|
this.totals = new PropertiesArray(WaterfallSeriesTotal);
|
|
this.line = new WaterfallSeriesConnectorLine();
|
|
this.tooltip = makeSeriesTooltip13();
|
|
}
|
|
getStyle(itemType) {
|
|
const { fillOpacity, strokeWidth, strokeOpacity, fill, stroke: stroke3, lineDash, lineDashOffset, cornerRadius } = this.item[itemType === "subtotal" ? "total" : itemType];
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
cornerRadius,
|
|
opacity: 1
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesProperties.prototype, "xKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesProperties.prototype, "yKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesProperties.prototype, "xName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesProperties.prototype, "yName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesProperties.prototype, "item", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesProperties.prototype, "totals", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesProperties.prototype, "line", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], WaterfallSeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/waterfall/waterfallSeries.ts
|
|
var {
|
|
adjustLabelPlacement: adjustLabelPlacement2,
|
|
SeriesNodePickMode: SeriesNodePickMode9,
|
|
fixNumericExtent: fixNumericExtent7,
|
|
valueProperty: valueProperty11,
|
|
keyProperty: keyProperty8,
|
|
accumulativeValueProperty: accumulativeValueProperty2,
|
|
trailingAccumulatedValueProperty: trailingAccumulatedValueProperty2,
|
|
createDatumId: createDatumId12,
|
|
checkCrisp: checkCrisp3,
|
|
updateLabelNode: updateLabelNode5,
|
|
prepareBarAnimationFunctions: prepareBarAnimationFunctions3,
|
|
collapsedStartingBarPosition: collapsedStartingBarPosition2,
|
|
resetBarSelectionsDirect: resetBarSelectionsDirect3,
|
|
resetBarSelectionsFn: resetBarSelectionsFn3,
|
|
seriesLabelFadeInAnimation: seriesLabelFadeInAnimation5,
|
|
resetLabelFn: resetLabelFn4,
|
|
animationValidation: animationValidation6,
|
|
DEFAULT_CARTESIAN_DIRECTION_KEYS: DEFAULT_CARTESIAN_DIRECTION_KEYS3,
|
|
DEFAULT_CARTESIAN_DIRECTION_NAMES: DEFAULT_CARTESIAN_DIRECTION_NAMES3,
|
|
computeBarFocusBounds: computeBarFocusBounds6,
|
|
Rect: Rect6,
|
|
motion: motion4,
|
|
getItemStylesPerItemId: getItemStylesPerItemId3,
|
|
DataSet: DataSet2,
|
|
processedDataIsAnimatable: processedDataIsAnimatable6,
|
|
upsertNodeDatum: upsertNodeDatum5
|
|
} = module_support_exports;
|
|
var WaterfallSeries = class extends module_support_exports.AbstractBarSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
propertyKeys: DEFAULT_CARTESIAN_DIRECTION_KEYS3,
|
|
propertyNames: DEFAULT_CARTESIAN_DIRECTION_NAMES3,
|
|
categoryKey: void 0,
|
|
pickModes: [SeriesNodePickMode9.NEAREST_NODE, SeriesNodePickMode9.EXACT_SHAPE_MATCH],
|
|
pathsPerSeries: ["connector"],
|
|
pathsZIndexSubOrderOffset: [-1, -1],
|
|
animationResetFns: {
|
|
datum: resetBarSelectionsFn3,
|
|
label: resetLabelFn4
|
|
}
|
|
});
|
|
this.properties = new WaterfallSeriesProperties();
|
|
this.seriesItemTypes = /* @__PURE__ */ new Set(["positive", "negative", "total"]);
|
|
}
|
|
async processData(dataController) {
|
|
const { xKey, yKey, totals } = this.properties;
|
|
const { data } = this;
|
|
if (!this.visible)
|
|
return;
|
|
const positiveNumber2 = (v) => isContinuous(v) && Number(v) >= 0;
|
|
const negativeNumber = (v) => isContinuous(v) && Number(v) >= 0;
|
|
const totalTypeValue = (v) => v === "total" || v === "subtotal";
|
|
const propertyDefinition = { missingValue: void 0, invalidValue: void 0 };
|
|
const dataWithTotals = [];
|
|
const totalsMap = totals.reduce((result, total) => {
|
|
const totalsAtIndex = result.get(total.index);
|
|
if (totalsAtIndex) {
|
|
totalsAtIndex.push(total);
|
|
} else {
|
|
result.set(total.index, [total]);
|
|
}
|
|
return result;
|
|
}, /* @__PURE__ */ new Map());
|
|
for (const [i, datum] of data?.data.entries() ?? []) {
|
|
dataWithTotals.push(datum);
|
|
const totalsAtIndex = totalsMap.get(i);
|
|
if (totalsAtIndex) {
|
|
for (const total of totalsAtIndex) {
|
|
dataWithTotals.push({ ...total.toJson(), [xKey]: total.axisLabel });
|
|
}
|
|
}
|
|
}
|
|
const extraProps = [];
|
|
if (!this.ctx.animationManager.isSkipped()) {
|
|
extraProps.push(animationValidation6());
|
|
}
|
|
const xScale = this.getCategoryAxis()?.scale;
|
|
const yScale = this.getValueAxis()?.scale;
|
|
const { isContinuousX, xScaleType, yScaleType } = this.getScaleInformation({ xScale, yScale });
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
const { processedData } = await this.requestDataModel(
|
|
dataController,
|
|
DataSet2.wrap(dataWithTotals),
|
|
{
|
|
props: [
|
|
keyProperty8(xKey, xScaleType, { id: `xValue`, allowNullKey }),
|
|
accumulativeValueProperty2(yKey, yScaleType, {
|
|
...propertyDefinition,
|
|
id: `yCurrent`
|
|
}),
|
|
accumulativeValueProperty2(yKey, yScaleType, {
|
|
...propertyDefinition,
|
|
missingValue: 0,
|
|
id: `yCurrentTotal`
|
|
}),
|
|
accumulativeValueProperty2(yKey, yScaleType, {
|
|
...propertyDefinition,
|
|
id: `yCurrentPositive`,
|
|
validation: positiveNumber2
|
|
}),
|
|
accumulativeValueProperty2(yKey, yScaleType, {
|
|
...propertyDefinition,
|
|
id: `yCurrentNegative`,
|
|
validation: negativeNumber
|
|
}),
|
|
trailingAccumulatedValueProperty2(yKey, yScaleType, {
|
|
...propertyDefinition,
|
|
id: `yPrevious`
|
|
}),
|
|
valueProperty11(yKey, yScaleType, { id: `yRaw` }),
|
|
// Raw value pass-through.
|
|
valueProperty11("totalType", "category", {
|
|
id: `totalTypeValue`,
|
|
missingValue: void 0,
|
|
validation: totalTypeValue
|
|
}),
|
|
...isContinuousX ? [module_support_exports.SMALLEST_KEY_INTERVAL, module_support_exports.LARGEST_KEY_INTERVAL] : [],
|
|
...extraProps
|
|
]
|
|
}
|
|
);
|
|
this.smallestDataInterval = processedData.reduced?.smallestKeyInterval;
|
|
this.largestDataInterval = processedData.reduced?.largestKeyInterval;
|
|
this.updateSeriesItemTypes();
|
|
this.animationState.transition("updateData");
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { processedData, dataModel } = this;
|
|
if (!processedData || !dataModel)
|
|
return { domain: [] };
|
|
const {
|
|
keys: [keys],
|
|
values
|
|
} = processedData.domain;
|
|
if (direction === this.getCategoryDirection()) {
|
|
const keyDef = dataModel.resolveProcessedDataDefById(this, `xValue`);
|
|
if (keyDef?.def.type === "key" && keyDef?.def.valueType === "category") {
|
|
const sortMetadata = dataModel.getKeySortMetadata(this, "xValue", processedData);
|
|
return { domain: keys, sortMetadata };
|
|
}
|
|
const isDirectionY = direction === "y" /* Y */;
|
|
const isReversed = this.getCategoryAxis().isReversed();
|
|
return { domain: this.padBandExtent(keys, isReversed !== isDirectionY) };
|
|
} else {
|
|
const yCurrIndex = dataModel.resolveProcessedDataIndexById(this, "yCurrent");
|
|
const yExtent = values[yCurrIndex];
|
|
const fixedYExtent = [Math.min(0, yExtent[0]), Math.max(0, yExtent[1])];
|
|
return { domain: fixNumericExtent7(fixedYExtent) };
|
|
}
|
|
}
|
|
getSeriesRange() {
|
|
return [Number.NaN, Number.NaN];
|
|
}
|
|
populateNodeData(ctx) {
|
|
let trailingSubtotal = 0;
|
|
const paramsScratch = {
|
|
datumIndex: 0,
|
|
datum: void 0,
|
|
xDatum: void 0,
|
|
value: void 0,
|
|
cumulativeValue: void 0,
|
|
trailingValue: void 0,
|
|
datumType: void 0
|
|
};
|
|
for (const [datumIndex, datum] of ctx.rawData.entries()) {
|
|
const datumType = ctx.totalTypeValues[datumIndex];
|
|
const isSubtotal = this.isSubtotal(datumType);
|
|
const isTotal = this.isTotal(datumType);
|
|
const isTotalOrSubtotal = isTotal || isSubtotal;
|
|
const xDatum = ctx.xValues[datumIndex];
|
|
if (xDatum === void 0 && !this.properties.allowNullKeys)
|
|
continue;
|
|
const rawValue = ctx.yRawValues[datumIndex];
|
|
const { cumulativeValue, trailingValue } = this.computeWaterfallValues(
|
|
ctx,
|
|
datumIndex,
|
|
isTotal,
|
|
isSubtotal,
|
|
trailingSubtotal
|
|
);
|
|
if (isTotalOrSubtotal) {
|
|
trailingSubtotal = cumulativeValue ?? 0;
|
|
}
|
|
const value = this.computeDisplayValue(isTotal, isSubtotal, rawValue, cumulativeValue, trailingValue);
|
|
paramsScratch.datumIndex = datumIndex;
|
|
paramsScratch.datum = datum;
|
|
paramsScratch.xDatum = xDatum;
|
|
paramsScratch.value = value;
|
|
paramsScratch.cumulativeValue = cumulativeValue;
|
|
paramsScratch.trailingValue = trailingValue;
|
|
paramsScratch.datumType = datumType;
|
|
const nodeDatum = upsertNodeDatum5(
|
|
ctx,
|
|
paramsScratch,
|
|
(c, p) => this.createNodeDatum(c, p),
|
|
(c, n, p) => this.updateNodeDatum(c, n, p)
|
|
);
|
|
if (nodeDatum) {
|
|
const pathPoint = this.createPointDatum(
|
|
ctx,
|
|
nodeDatum,
|
|
cumulativeValue,
|
|
trailingValue,
|
|
isTotalOrSubtotal
|
|
);
|
|
ctx.pointData.push(pathPoint);
|
|
}
|
|
}
|
|
}
|
|
finalizeNodeData(ctx) {
|
|
if (ctx.nodeIndex < ctx.nodes.length) {
|
|
ctx.nodes.length = ctx.nodeIndex;
|
|
}
|
|
}
|
|
initializeResult(ctx) {
|
|
return {
|
|
itemId: this.properties.yKey,
|
|
nodeData: ctx.nodes,
|
|
labelData: ctx.nodes,
|
|
pointData: [],
|
|
scales: this.calculateScaling(),
|
|
groupScale: this.getScaling(this.ctx.seriesStateManager.getGroupScale(this)),
|
|
visible: this.visible,
|
|
styles: getItemStylesPerItemId3(this.getItemStyle.bind(this), "total", "subtotal", "positive", "negative")
|
|
};
|
|
}
|
|
assembleResult(ctx, result) {
|
|
const connectorLinesEnabled = this.properties.line.enabled;
|
|
if (ctx.yCurrValues != null && connectorLinesEnabled) {
|
|
result.pointData = ctx.pointData;
|
|
}
|
|
return result;
|
|
}
|
|
createNodeDatumContext(xAxis, yAxis) {
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData || processedData.type !== "ungrouped")
|
|
return void 0;
|
|
const categoryAxis = this.getCategoryAxis();
|
|
const valueAxis = this.getValueAxis();
|
|
if (!categoryAxis || !valueAxis)
|
|
return void 0;
|
|
const xScale = xAxis.scale;
|
|
const yScale = yAxis.scale;
|
|
const xValues = dataModel.resolveKeysById(this, `xValue`, processedData);
|
|
const yRawValues = dataModel.resolveColumnById(this, `yRaw`, processedData);
|
|
const totalTypeValues = dataModel.resolveColumnById(
|
|
this,
|
|
`totalTypeValue`,
|
|
processedData
|
|
);
|
|
const yCurrValues = dataModel.resolveColumnById(this, "yCurrent", processedData);
|
|
const yPrevValues = dataModel.resolveColumnById(this, "yPrevious", processedData);
|
|
const yCurrTotalValues = dataModel.resolveColumnById(this, "yCurrentTotal", processedData);
|
|
const rawData = processedData.dataSources.get(this.id)?.data ?? [];
|
|
const { xKey, yKey, xName, yName, line } = this.properties;
|
|
const { contextNodeData } = this;
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
const canIncrementallyUpdate = contextNodeData?.nodeData != null && processedData.changeDescription != null;
|
|
const { barWidth } = this.getBarDimensions();
|
|
return {
|
|
xAxis,
|
|
yAxis,
|
|
xScale,
|
|
yScale,
|
|
categoryAxis,
|
|
valueAxis,
|
|
barAlongX: this.getBarDirection() === "x" /* X */,
|
|
barWidth,
|
|
categoryAxisReversed: categoryAxis.isReversed(),
|
|
valueAxisReversed: valueAxis.isReversed(),
|
|
crisp: checkCrisp3(
|
|
categoryAxis.scale,
|
|
categoryAxis.visibleRange,
|
|
this.smallestDataInterval,
|
|
this.largestDataInterval
|
|
),
|
|
animationEnabled,
|
|
xKey,
|
|
yKey,
|
|
xName,
|
|
yName,
|
|
lineStrokeWidth: line.strokeWidth,
|
|
yDomain: this.getSeriesDomain("y" /* Y */).domain,
|
|
xValues,
|
|
rawData,
|
|
yRawValues,
|
|
totalTypeValues,
|
|
yCurrValues,
|
|
yPrevValues,
|
|
yCurrTotalValues,
|
|
canIncrementallyUpdate,
|
|
nodes: canIncrementallyUpdate ? contextNodeData.nodeData : [],
|
|
nodeIndex: 0,
|
|
pointData: []
|
|
};
|
|
}
|
|
computeWaterfallValues(ctx, datumIndex, isTotal, isSubtotal, trailingSubtotal) {
|
|
if (isTotal || isSubtotal) {
|
|
return {
|
|
cumulativeValue: ctx.yCurrTotalValues[datumIndex],
|
|
trailingValue: isSubtotal ? trailingSubtotal : 0
|
|
};
|
|
}
|
|
return {
|
|
cumulativeValue: ctx.yCurrValues[datumIndex],
|
|
trailingValue: ctx.yPrevValues[datumIndex]
|
|
};
|
|
}
|
|
computeDisplayValue(isTotal, isSubtotal, rawValue, cumulativeValue, trailingValue) {
|
|
if (isTotal) {
|
|
return cumulativeValue;
|
|
}
|
|
if (isSubtotal) {
|
|
return (cumulativeValue ?? 0) - (trailingValue ?? 0);
|
|
}
|
|
return rawValue;
|
|
}
|
|
/**
|
|
* Creates a skeleton WaterfallNodeDatum with minimal required fields.
|
|
* The node will be populated by updateNodeDatum.
|
|
*/
|
|
createSkeletonNodeDatum(ctx, params) {
|
|
const { xKey, yKey, crisp } = ctx;
|
|
const { datumIndex, datum, xDatum, value, cumulativeValue, datumType } = params;
|
|
const isPositive = (value ?? 0) >= 0;
|
|
const seriesItemType = this.getSeriesItemType(isPositive, datumType);
|
|
return {
|
|
index: datumIndex,
|
|
series: this,
|
|
itemType: seriesItemType,
|
|
datum,
|
|
datumIndex,
|
|
cumulativeValue: cumulativeValue ?? 0,
|
|
xValue: xDatum,
|
|
yValue: value,
|
|
yKey,
|
|
xKey,
|
|
x: 0,
|
|
y: 0,
|
|
width: 0,
|
|
height: 0,
|
|
midPoint: { x: 0, y: 0 },
|
|
crisp,
|
|
label: { text: "", x: 0, y: 0, textAlign: "center", textBaseline: "middle" }
|
|
};
|
|
}
|
|
/**
|
|
* Updates an existing WaterfallNodeDatum in-place.
|
|
* This is more efficient than recreating the entire node when only data values change.
|
|
*/
|
|
updateNodeDatum(ctx, node, params) {
|
|
const { xScale, yScale, barAlongX, barWidth, valueAxisReversed, xKey, yKey, xName, yName, yDomain, crisp } = ctx;
|
|
const { datumIndex, datum, xDatum, value, cumulativeValue, trailingValue, datumType } = params;
|
|
const mutableNode = node;
|
|
const x = Math.round(xScale.convert(xDatum));
|
|
if (!Number.isFinite(x))
|
|
return;
|
|
const isPositive = (value ?? 0) >= 0;
|
|
const seriesItemType = this.getSeriesItemType(isPositive, datumType);
|
|
const { strokeWidth, label } = this.getItemConfig(seriesItemType);
|
|
const currY = Math.round(yScale.convert(cumulativeValue));
|
|
const trailY = Math.round(yScale.convert(trailingValue));
|
|
const y = isPositive ? currY : trailY;
|
|
const bottomY = isPositive ? trailY : currY;
|
|
const barHeight = Math.max(strokeWidth, Math.abs(bottomY - y));
|
|
const rectX = barAlongX ? Math.min(y, bottomY) : x;
|
|
const rectY = barAlongX ? x : Math.min(y, bottomY);
|
|
const rectWidth = barAlongX ? barHeight : barWidth;
|
|
const rectHeight = barAlongX ? barWidth : barHeight;
|
|
mutableNode.index = datumIndex;
|
|
mutableNode.itemType = seriesItemType;
|
|
mutableNode.datum = datum;
|
|
mutableNode.datumIndex = datumIndex;
|
|
mutableNode.cumulativeValue = cumulativeValue ?? 0;
|
|
mutableNode.xValue = xDatum;
|
|
mutableNode.yValue = value;
|
|
mutableNode.x = rectX;
|
|
mutableNode.y = rectY;
|
|
mutableNode.width = rectWidth;
|
|
mutableNode.height = rectHeight;
|
|
mutableNode.crisp = crisp;
|
|
if (mutableNode.midPoint) {
|
|
mutableNode.midPoint.x = rectX + rectWidth / 2;
|
|
mutableNode.midPoint.y = rectY + rectHeight / 2;
|
|
} else {
|
|
mutableNode.midPoint = { x: rectX + rectWidth / 2, y: rectY + rectHeight / 2 };
|
|
}
|
|
if (label.enabled) {
|
|
const itemType = seriesItemType === "subtotal" ? "total" : seriesItemType;
|
|
const labelText = this.getLabelText(
|
|
value,
|
|
datum,
|
|
yKey,
|
|
"y",
|
|
yDomain,
|
|
label,
|
|
{
|
|
itemType,
|
|
value,
|
|
datum,
|
|
xKey,
|
|
yKey,
|
|
xName,
|
|
yName
|
|
}
|
|
);
|
|
const spacing = label.spacing + (typeof label.padding === "number" ? label.padding : 0);
|
|
const labelPlacement = adjustLabelPlacement2({
|
|
isUpward: (value ?? -1) >= 0 !== valueAxisReversed,
|
|
isVertical: !barAlongX,
|
|
placement: label.placement,
|
|
spacing,
|
|
rect: { x: rectX, y: rectY, width: rectWidth, height: rectHeight }
|
|
});
|
|
mutableNode.label.text = labelText;
|
|
mutableNode.label.x = labelPlacement.x;
|
|
mutableNode.label.y = labelPlacement.y;
|
|
mutableNode.label.textAlign = labelPlacement.textAlign;
|
|
mutableNode.label.textBaseline = labelPlacement.textBaseline;
|
|
} else {
|
|
mutableNode.label.text = "";
|
|
}
|
|
}
|
|
/**
|
|
* Creates a WaterfallNodeDatum for a single data point.
|
|
* Creates a skeleton node and uses updateNodeDatum to populate it.
|
|
*/
|
|
createNodeDatum(ctx, params) {
|
|
const node = this.createSkeletonNodeDatum(ctx, params);
|
|
this.updateNodeDatum(ctx, node, params);
|
|
return node;
|
|
}
|
|
createPointDatum(ctx, nodeDatum, cumulativeValue, trailingValue, isTotalOrSubtotal) {
|
|
const { yScale, barAlongX, categoryAxisReversed, lineStrokeWidth } = ctx;
|
|
const currY = Math.round(yScale.convert(cumulativeValue));
|
|
const trailY = Math.round(yScale.convert(trailingValue));
|
|
const pointY = isTotalOrSubtotal ? currY : trailY;
|
|
const pixelAlignmentOffset = Math.floor(lineStrokeWidth) % 2 / 2;
|
|
const startY = categoryAxisReversed ? currY : pointY;
|
|
const stopY = categoryAxisReversed ? pointY : currY;
|
|
const rect2 = { x: nodeDatum.x, y: nodeDatum.y, width: nodeDatum.width, height: nodeDatum.height };
|
|
let startCoordinates;
|
|
let stopCoordinates;
|
|
if (barAlongX) {
|
|
startCoordinates = {
|
|
x: startY + pixelAlignmentOffset,
|
|
y: rect2.y
|
|
};
|
|
stopCoordinates = {
|
|
x: stopY + pixelAlignmentOffset,
|
|
y: rect2.y + rect2.height
|
|
};
|
|
} else {
|
|
startCoordinates = {
|
|
x: rect2.x,
|
|
y: startY + pixelAlignmentOffset
|
|
};
|
|
stopCoordinates = {
|
|
x: rect2.x + rect2.width,
|
|
y: stopY + pixelAlignmentOffset
|
|
};
|
|
}
|
|
return {
|
|
// lineTo
|
|
x: categoryAxisReversed ? stopCoordinates.x : startCoordinates.x,
|
|
y: categoryAxisReversed ? stopCoordinates.y : startCoordinates.y,
|
|
// moveTo
|
|
x2: categoryAxisReversed ? startCoordinates.x : stopCoordinates.x,
|
|
y2: categoryAxisReversed ? startCoordinates.y : stopCoordinates.y,
|
|
size: 0
|
|
};
|
|
}
|
|
updateSeriesItemTypes() {
|
|
const { dataModel, seriesItemTypes, processedData } = this;
|
|
if (!dataModel || !processedData) {
|
|
return;
|
|
}
|
|
seriesItemTypes.clear();
|
|
const yPositiveIndex = dataModel.resolveProcessedDataIndexById(this, "yCurrentPositive");
|
|
const yNegativeIndex = dataModel.resolveProcessedDataIndexById(this, "yCurrentNegative");
|
|
const totalTypeIndex = dataModel.resolveProcessedDataIndexById(this, `totalTypeValue`);
|
|
const positiveDomain = processedData.domain.values[yPositiveIndex] ?? [];
|
|
const negativeDomain = processedData.domain.values[yNegativeIndex] ?? [];
|
|
if (positiveDomain.length > 0) {
|
|
seriesItemTypes.add("positive");
|
|
}
|
|
if (negativeDomain.length > 0) {
|
|
seriesItemTypes.add("negative");
|
|
}
|
|
const itemTypes = processedData?.domain.values[totalTypeIndex];
|
|
if (!itemTypes) {
|
|
return;
|
|
}
|
|
for (const type of itemTypes) {
|
|
if (type === "total" || type === "subtotal") {
|
|
seriesItemTypes.add("total");
|
|
}
|
|
}
|
|
}
|
|
isSubtotal(datumType) {
|
|
return datumType === "subtotal";
|
|
}
|
|
isTotal(datumType) {
|
|
return datumType === "total";
|
|
}
|
|
nodeFactory() {
|
|
return new Rect6();
|
|
}
|
|
getSeriesItemType(isPositive, datumType) {
|
|
return datumType ?? (isPositive ? "positive" : "negative");
|
|
}
|
|
getItemConfig(seriesItemType) {
|
|
switch (seriesItemType) {
|
|
case "positive": {
|
|
return this.properties.item.positive;
|
|
}
|
|
case "negative": {
|
|
return this.properties.item.negative;
|
|
}
|
|
case "subtotal":
|
|
case "total": {
|
|
return this.properties.item.total;
|
|
}
|
|
}
|
|
}
|
|
updateDatumSelection(opts) {
|
|
const { nodeData, datumSelection } = opts;
|
|
const data = nodeData ?? [];
|
|
if (!processedDataIsAnimatable6(this.processedData)) {
|
|
return datumSelection.update(data);
|
|
}
|
|
return datumSelection.update(data, void 0, (datum) => createDatumId12(datum.datumIndex));
|
|
}
|
|
getItemStyle(nodeDatum, isHighlight, highlightState, itemType = "total") {
|
|
const { properties } = this;
|
|
const { datumIndex = 0, datum } = nodeDatum ?? {};
|
|
const propertyItemId = itemType === "subtotal" ? "total" : itemType;
|
|
const item = properties.item[propertyItemId];
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex, highlightState);
|
|
const baseStyle = mergeDefaults(highlightStyle, properties.getStyle(itemType));
|
|
const { itemStyler } = item;
|
|
let style2 = baseStyle;
|
|
if (itemStyler != null && nodeDatum != null) {
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId12(datumIndex, isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const params = this.makeItemStylerParams(itemType, datumIndex, datum, isHighlight, style2);
|
|
return this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`, "item", propertyItemId],
|
|
this.callWithContext(itemStyler, params)
|
|
);
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(overrides, style2);
|
|
}
|
|
}
|
|
return style2;
|
|
}
|
|
makeItemStylerParams(itemType, datumIndex, datum, isHighlight, style2) {
|
|
const { id: seriesId, properties } = this;
|
|
const { xKey, yKey } = properties;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightStateString = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
seriesId,
|
|
itemType,
|
|
datum,
|
|
xKey,
|
|
yKey,
|
|
highlightState: highlightStateString,
|
|
...style2,
|
|
fill
|
|
};
|
|
}
|
|
updateDatumStyles({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
datumSelection.each((_, datum) => {
|
|
datum.style = this.getItemStyle(datum, isHighlight, void 0, datum.itemType);
|
|
});
|
|
}
|
|
updateDatumNodes({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
const { contextNodeData } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
const categoryAlongX = this.getCategoryDirection() === "x" /* X */;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
datumSelection.each((rect2, datum) => {
|
|
const style2 = datum.style ?? contextNodeData.styles[datum.itemType][this.getHighlightState(highlightedDatum, isHighlight, datum.datumIndex)];
|
|
rect2.setStyleProperties(style2, fillBBox);
|
|
rect2.cornerRadius = style2.cornerRadius ?? 0;
|
|
rect2.visible = categoryAlongX ? datum.width > 0 : datum.height > 0;
|
|
rect2.crisp = datum.crisp;
|
|
});
|
|
}
|
|
updateLabelSelection(opts) {
|
|
const { labelData, labelSelection } = opts;
|
|
if (labelData.length === 0) {
|
|
return labelSelection.update([]);
|
|
}
|
|
const data = labelData.filter((labelDatum) => {
|
|
const { label } = this.getItemConfig(labelDatum.itemType);
|
|
return label.enabled;
|
|
});
|
|
return labelSelection.update(data);
|
|
}
|
|
updateLabelNodes({
|
|
labelSelection,
|
|
isHighlight
|
|
}) {
|
|
const params = {
|
|
itemType: "positive",
|
|
xKey: this.properties.xKey,
|
|
xName: this.properties.xName ?? this.properties.xName,
|
|
yKey: this.properties.yKey,
|
|
yName: this.properties.yName ?? this.properties.yName
|
|
};
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
labelSelection.each((textNode, datum) => {
|
|
params.itemType = datum.itemType;
|
|
const styleOpacity = this.getHighlightStyle(isHighlight, datum.datumIndex)?.opacity ?? 1;
|
|
textNode.visible = true;
|
|
textNode.fillOpacity = styleOpacity;
|
|
const label = this.getItemConfig(datum.itemType).label;
|
|
updateLabelNode5(this, textNode, params, label, datum.label, isHighlight, activeHighlight);
|
|
});
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, properties } = this;
|
|
const { xKey, xName, yKey, yName, tooltip, legendItemName } = properties;
|
|
const xAxis = this.getCategoryAxis();
|
|
const yAxis = this.getValueAxis();
|
|
if (!dataModel || !processedData || !xAxis || !yAxis)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const xValue = dataModel.resolveKeysById(this, `xValue`, processedData)[datumIndex];
|
|
const yValue = dataModel.resolveColumnById(this, `yRaw`, processedData)[datumIndex];
|
|
const yCurrTotalValues = dataModel.resolveColumnById(this, "yCurrentTotal", processedData);
|
|
const totalTypeValues = dataModel.resolveColumnById(
|
|
this,
|
|
`totalTypeValue`,
|
|
processedData
|
|
);
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
if (xValue === void 0 && !allowNullKeys)
|
|
return;
|
|
const datumType = totalTypeValues[datumIndex];
|
|
const isPositive = (yValue ?? 0) >= 0;
|
|
const seriesItemType = this.getSeriesItemType(isPositive, datumType);
|
|
let total;
|
|
if (this.isTotal(datumType)) {
|
|
total = yCurrTotalValues[datumIndex];
|
|
} else if (this.isSubtotal(datumType)) {
|
|
total = yCurrTotalValues[datumIndex];
|
|
for (let previousIndex = datumIndex - 1; previousIndex >= 0; previousIndex -= 1) {
|
|
if (this.isSubtotal(totalTypeValues[previousIndex])) {
|
|
total = total - yCurrTotalValues[previousIndex];
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
total = yValue;
|
|
}
|
|
const nodeDatum = this.contextNodeData?.nodeData?.[datumIndex];
|
|
const format = this.getItemStyle(nodeDatum, false, void 0, nodeDatum?.itemType);
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading: this.getAxisValueText(xAxis, "tooltip", xValue, datum, xKey, legendItemName),
|
|
symbol: this.legendItemSymbol(seriesItemType),
|
|
data: [
|
|
{
|
|
label: yName,
|
|
fallbackLabel: yKey,
|
|
value: this.getAxisValueText(yAxis, "tooltip", total, datum, yKey, legendItemName),
|
|
missing: module_support_exports.isTooltipValueMissing(total)
|
|
}
|
|
]
|
|
},
|
|
{ seriesId, datum, title: yName, itemType: seriesItemType, xKey, xName, yKey, yName, ...format }
|
|
);
|
|
}
|
|
legendItemSymbol(item) {
|
|
const { fill, stroke: stroke3, fillOpacity, strokeOpacity, strokeWidth, lineDash, lineDashOffset } = this.getItemConfig(item);
|
|
return {
|
|
marker: {
|
|
fill,
|
|
stroke: stroke3,
|
|
fillOpacity,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash,
|
|
lineDashOffset
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
if (legendType !== "category") {
|
|
return [];
|
|
}
|
|
const { id, seriesItemTypes } = this;
|
|
const legendData = [];
|
|
const capitalise = (text2) => text2.charAt(0).toUpperCase() + text2.substring(1);
|
|
const { showInLegend } = this.properties;
|
|
for (const item of seriesItemTypes) {
|
|
const { name } = this.getItemConfig(item);
|
|
legendData.push({
|
|
legendType: "category",
|
|
id,
|
|
itemId: createDatumId12(item),
|
|
seriesId: id,
|
|
enabled: true,
|
|
label: { text: name ?? capitalise(item) },
|
|
symbol: this.legendItemSymbol(item),
|
|
hideInLegend: !showInLegend,
|
|
isFixed: true
|
|
});
|
|
}
|
|
return legendData;
|
|
}
|
|
toggleSeriesItem() {
|
|
}
|
|
resetDatumAnimation(data) {
|
|
resetBarSelectionsDirect3([data.datumSelection]);
|
|
}
|
|
animateEmptyUpdateReady(opts) {
|
|
const { datumSelection, labelSelection, contextData } = opts;
|
|
const fns = prepareBarAnimationFunctions3(
|
|
collapsedStartingBarPosition2(this.isVertical(), this.axes, "normal"),
|
|
"unknown"
|
|
);
|
|
motion4.fromToMotion(this.id, "datums", this.ctx.animationManager, [datumSelection], fns);
|
|
seriesLabelFadeInAnimation5(this, "labels", this.ctx.animationManager, labelSelection);
|
|
const { pointData } = contextData;
|
|
if (!pointData)
|
|
return;
|
|
if (this.isVertical()) {
|
|
this.animateConnectorLinesVertical(opts);
|
|
} else {
|
|
this.animateConnectorLinesHorizontal(opts);
|
|
}
|
|
}
|
|
animateConnectorLinesHorizontal(opts) {
|
|
const { pointData = [] } = opts.contextData;
|
|
const [lineNode] = opts.paths;
|
|
const { path: linePath } = lineNode;
|
|
this.updateLineNode(lineNode);
|
|
const valueAxis = this.getValueAxis();
|
|
const valueAxisReversed = valueAxis?.isReversed();
|
|
const compare = valueAxisReversed ? (v, v2) => v < v2 : (v, v2) => v > v2;
|
|
const startX = valueAxis?.scale.convert(0);
|
|
const endX = pointData.reduce(
|
|
(end3, point) => {
|
|
if (compare(point.x, end3)) {
|
|
end3 = point.x;
|
|
}
|
|
return end3;
|
|
},
|
|
valueAxisReversed ? Infinity : 0
|
|
);
|
|
const scale2 = (value, start1, end1, start2, end22) => {
|
|
return (value - start1) / (end1 - start1) * (end22 - start2) + start2;
|
|
};
|
|
this.ctx.animationManager.animate({
|
|
id: `${this.id}_connectors`,
|
|
groupId: this.id,
|
|
phase: "initial",
|
|
from: startX,
|
|
to: endX,
|
|
ease: easeOut,
|
|
collapsable: false,
|
|
onUpdate(pointX) {
|
|
linePath.clear(true);
|
|
for (const [index, point] of pointData.entries()) {
|
|
const x = scale2(pointX, startX, endX, startX, point.x);
|
|
const x2 = scale2(pointX, startX, endX, startX, point.x2);
|
|
if (index !== 0) {
|
|
linePath.lineTo(x, point.y);
|
|
}
|
|
linePath.moveTo(x2, point.y2);
|
|
}
|
|
lineNode.checkPathDirty();
|
|
},
|
|
onStop: () => this.resetConnectorLinesPath(opts)
|
|
});
|
|
}
|
|
animateConnectorLinesVertical(opts) {
|
|
const { pointData = [] } = opts.contextData;
|
|
const [lineNode] = opts.paths;
|
|
const { path: linePath } = lineNode;
|
|
this.updateLineNode(lineNode);
|
|
const valueAxis = this.getValueAxis();
|
|
const valueAxisReversed = valueAxis?.isReversed();
|
|
const compare = valueAxisReversed ? (v, v2) => v > v2 : (v, v2) => v < v2;
|
|
const startY = valueAxis?.scale.convert(0);
|
|
const endY = pointData.reduce(
|
|
(end3, point) => {
|
|
if (compare(point.y, end3)) {
|
|
end3 = point.y;
|
|
}
|
|
return end3;
|
|
},
|
|
valueAxisReversed ? 0 : Infinity
|
|
);
|
|
const scale2 = (value, start1, end1, start2, end22) => {
|
|
return (value - start1) / (end1 - start1) * (end22 - start2) + start2;
|
|
};
|
|
this.ctx.animationManager.animate({
|
|
id: `${this.id}_connectors`,
|
|
groupId: this.id,
|
|
phase: "initial",
|
|
from: startY,
|
|
to: endY,
|
|
ease: easeOut,
|
|
collapsable: false,
|
|
onUpdate(pointY) {
|
|
linePath.clear(true);
|
|
for (const [index, point] of pointData.entries()) {
|
|
const y = scale2(pointY, startY, endY, startY, point.y);
|
|
const y2 = scale2(pointY, startY, endY, startY, point.y2);
|
|
if (index !== 0) {
|
|
linePath.lineTo(point.x, y);
|
|
}
|
|
linePath.moveTo(point.x2, y2);
|
|
}
|
|
lineNode.checkPathDirty();
|
|
},
|
|
onStop: () => this.resetConnectorLinesPath(opts)
|
|
});
|
|
}
|
|
animateReadyResize(data) {
|
|
super.animateReadyResize(data);
|
|
this.resetConnectorLinesPath(data);
|
|
}
|
|
updatePaths(opts) {
|
|
this.resetConnectorLinesPath({ contextData: opts.contextData, paths: opts.paths });
|
|
}
|
|
resetConnectorLinesPath({
|
|
contextData,
|
|
paths
|
|
}) {
|
|
if (paths.length === 0) {
|
|
return;
|
|
}
|
|
const [lineNode] = paths;
|
|
this.updateLineNode(lineNode);
|
|
const { path: linePath } = lineNode;
|
|
linePath.clear(true);
|
|
const { pointData } = contextData;
|
|
if (!pointData) {
|
|
return;
|
|
}
|
|
for (const [index, point] of pointData.entries()) {
|
|
if (index !== 0) {
|
|
linePath.lineTo(point.x, point.y);
|
|
}
|
|
linePath.moveTo(point.x2, point.y2);
|
|
}
|
|
lineNode.checkPathDirty();
|
|
}
|
|
updateLineNode(lineNode) {
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = this.properties.line;
|
|
lineNode.setProperties({
|
|
fill: void 0,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
lineJoin: "round",
|
|
pointerEvents: module_support_exports.PointerEvents.None
|
|
});
|
|
}
|
|
isLabelEnabled() {
|
|
const { positive, negative, total } = this.properties.item;
|
|
return positive.label.enabled || negative.label.enabled || total.label.enabled;
|
|
}
|
|
computeFocusBounds({ datumIndex }) {
|
|
return computeBarFocusBounds6(this, this.contextNodeData?.nodeData[datumIndex]);
|
|
}
|
|
hasItemStylers() {
|
|
const { positive, negative, total } = this.properties.item;
|
|
return positive.itemStyler != null || positive.label.itemStyler != null || negative.itemStyler != null || negative.label.itemStyler != null || total.itemStyler != null || total.label.itemStyler != null;
|
|
}
|
|
};
|
|
WaterfallSeries.className = "WaterfallSeries";
|
|
WaterfallSeries.type = "waterfall";
|
|
|
|
// packages/ag-charts-enterprise/src/series/waterfall/waterfallSeriesOptionsDef.ts
|
|
var { waterfallSeriesThemeableOptionsDef: waterfallSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var waterfallSeriesOptionsDef = {
|
|
...waterfallSeriesThemeableOptionsDef2,
|
|
...commonSeriesOptionsDefs,
|
|
type: required(constant("waterfall")),
|
|
xKey: required(string),
|
|
yKey: required(string),
|
|
xKeyAxis: string,
|
|
yKeyAxis: string,
|
|
xName: string,
|
|
yName: string,
|
|
totals: arrayOfDefs(
|
|
{
|
|
totalType: required(union("total", "subtotal")),
|
|
index: required(positiveNumber),
|
|
axisLabel: required(string)
|
|
},
|
|
"a total definition options array"
|
|
),
|
|
width: positiveNumberNonZero
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/waterfall/waterfallThemes.ts
|
|
function itemTheme2(key, index) {
|
|
return {
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{
|
|
$if: [
|
|
{ $eq: [{ $palette: "type" }, "user-indexed"] },
|
|
{ $path: [`/${index}`, { $palette: "fill" }, { $palette: "fills" }] },
|
|
{ $palette: `${key}.fill` }
|
|
]
|
|
},
|
|
["gradient", FILL_GRADIENT_LINEAR_KEYED_DEFAULTS(key)],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_KEYED_DEFAULTS(key)]
|
|
]
|
|
},
|
|
stroke: { $palette: `${key}.stroke` },
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, 0] },
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
fontStyle: void 0,
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
color: { $ref: "textColor" },
|
|
formatter: void 0,
|
|
placement: "outside-end",
|
|
padding: { $isUserOption: ["./spacing", 0, 6] }
|
|
// compatibility with old `padding` property (now named `spacing`).
|
|
}
|
|
};
|
|
}
|
|
var WATERFALL_SERIES_THEME = {
|
|
series: {
|
|
item: {
|
|
positive: itemTheme2("altUp", 0),
|
|
negative: itemTheme2("altDown", 1),
|
|
total: itemTheme2("neutral", 2)
|
|
},
|
|
line: {
|
|
stroke: { $palette: "neutral.stroke" },
|
|
strokeOpacity: 1,
|
|
lineDash: [0],
|
|
lineDashOffset: 0,
|
|
strokeWidth: 2
|
|
},
|
|
highlight: SINGLE_SERIES_HIGHLIGHT_STYLE
|
|
},
|
|
legend: {
|
|
enabled: true,
|
|
toggleSeries: false
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/waterfall/waterfallModule.ts
|
|
var WaterfallSeriesModule = {
|
|
type: "series",
|
|
name: "waterfall",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
solo: true,
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: waterfallSeriesOptionsDef,
|
|
defaultAxes: DIRECTION_SWAP_AXES,
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
axisKeysFlipped: { ["x" /* X */]: "yKeyAxis", ["y" /* Y */]: "xKeyAxis" },
|
|
themeTemplate: WATERFALL_SERIES_THEME,
|
|
create: (ctx) => new WaterfallSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/navigator/navigatorOptionsDefs.ts
|
|
var navigatorHandleOptionsDef2 = {
|
|
width: positiveNumber,
|
|
height: positiveNumber,
|
|
grip: boolean,
|
|
fill: color,
|
|
stroke: color,
|
|
strokeWidth: positiveNumber,
|
|
cornerRadius: positiveNumber
|
|
};
|
|
var commonIgnoredMiniChartProperties = [
|
|
"cursor",
|
|
"highlightStyle",
|
|
"listeners",
|
|
"nodeClickRange",
|
|
"showInLegend",
|
|
"showInMiniChart",
|
|
"tooltip",
|
|
"visible",
|
|
"xName",
|
|
"yName"
|
|
];
|
|
var barIgnoredMiniChartProperties = [
|
|
...commonIgnoredMiniChartProperties,
|
|
"errorBar",
|
|
"label",
|
|
"legendItemName"
|
|
];
|
|
var boxPlotIngnoredMiniChartProperties = [
|
|
...commonIgnoredMiniChartProperties,
|
|
"direction",
|
|
"legendItemName",
|
|
"minName",
|
|
"q1Name",
|
|
"medianName",
|
|
"q3Name",
|
|
"maxName"
|
|
];
|
|
var bubbleIgnoredMiniChartProperties = [
|
|
...commonIgnoredMiniChartProperties,
|
|
"title",
|
|
"label",
|
|
"labelKey",
|
|
"labelName",
|
|
"sizeName"
|
|
];
|
|
var heatmapIgnoredMiniChartProperties = [
|
|
...commonIgnoredMiniChartProperties,
|
|
"title",
|
|
"label",
|
|
"colorName",
|
|
"textAlign",
|
|
"verticalAlign",
|
|
"itemPadding",
|
|
"colorRange"
|
|
];
|
|
var histogramIgnoredMiniChartProperties = [
|
|
...commonIgnoredMiniChartProperties,
|
|
"label"
|
|
];
|
|
var lineIgnoredMiniChartProperties = [
|
|
...commonIgnoredMiniChartProperties,
|
|
"errorBar",
|
|
"title",
|
|
"label"
|
|
];
|
|
var rangeAreaIgnoredMiniChartProperties = [
|
|
...commonIgnoredMiniChartProperties,
|
|
"label",
|
|
"yLowName",
|
|
"yHighName"
|
|
];
|
|
var rangeBarIgnoredMiniChartProperties = [
|
|
...commonIgnoredMiniChartProperties,
|
|
"direction",
|
|
"label",
|
|
"yLowName",
|
|
"yHighName"
|
|
];
|
|
var scatterIgnoredMiniChartProperties = [
|
|
...commonIgnoredMiniChartProperties,
|
|
"errorBar",
|
|
"title",
|
|
"label",
|
|
"labelKey",
|
|
"labelName"
|
|
];
|
|
var waterfallIgnoredMiniChartProperties = [
|
|
...commonIgnoredMiniChartProperties,
|
|
"direction"
|
|
];
|
|
var navigatorOptionsDef2 = {
|
|
enabled: boolean,
|
|
height: positiveNumber,
|
|
spacing: positiveNumber,
|
|
cornerRadius: number,
|
|
mask: {
|
|
fill: color,
|
|
fillOpacity: ratio,
|
|
stroke: color,
|
|
strokeWidth: positiveNumber
|
|
},
|
|
minHandle: navigatorHandleOptionsDef2,
|
|
maxHandle: navigatorHandleOptionsDef2,
|
|
miniChart: {
|
|
enabled: boolean,
|
|
padding: {
|
|
top: positiveNumber,
|
|
bottom: positiveNumber
|
|
},
|
|
label: {
|
|
enabled: boolean,
|
|
avoidCollisions: boolean,
|
|
spacing: positiveNumber,
|
|
format: numberFormatValidator,
|
|
formatter: callbackOf(textOrSegments),
|
|
interval: {
|
|
minSpacing: positiveNumber,
|
|
maxSpacing: positiveNumber,
|
|
values: array,
|
|
step: number
|
|
},
|
|
...fontOptionsDef
|
|
},
|
|
series: arrayOfDefs(
|
|
typeUnion(
|
|
{
|
|
area: without(AreaSeriesModule.options, [...commonIgnoredMiniChartProperties, "type"]),
|
|
bar: without(BarSeriesModule.options, [...barIgnoredMiniChartProperties, "type"]),
|
|
"box-plot": without(BoxPlotSeriesModule.options, [...boxPlotIngnoredMiniChartProperties, "type"]),
|
|
bubble: without(BubbleSeriesModule.options, [...bubbleIgnoredMiniChartProperties, "type"]),
|
|
candlestick: without(CandlestickSeriesModule.options, [
|
|
...commonIgnoredMiniChartProperties,
|
|
"type"
|
|
]),
|
|
heatmap: without(HeatmapSeriesModule.options, [...heatmapIgnoredMiniChartProperties, "type"]),
|
|
histogram: without(HistogramSeriesModule.options, [...histogramIgnoredMiniChartProperties, "type"]),
|
|
line: without(LineSeriesModule.options, [...lineIgnoredMiniChartProperties, "type"]),
|
|
ohlc: without(OhlcSeriesModule.options, [...commonIgnoredMiniChartProperties, "type"]),
|
|
"range-area": without(RangeAreaSeriesModule.options, [
|
|
...rangeAreaIgnoredMiniChartProperties,
|
|
"type"
|
|
]),
|
|
"range-bar": without(RangeBarSeriesModule.options, [...rangeBarIgnoredMiniChartProperties, "type"]),
|
|
scatter: without(ScatterSeriesModule.options, [...scatterIgnoredMiniChartProperties, "type"]),
|
|
waterfall: without(WaterfallSeriesModule.options, [...waterfallIgnoredMiniChartProperties, "type"])
|
|
},
|
|
"miniChart series options"
|
|
)
|
|
)
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/navigator/navigatorTheme.ts
|
|
var validMiniChartSeriesTypes = [
|
|
"area",
|
|
"bar",
|
|
"bubble",
|
|
"candlestick",
|
|
"heatmap",
|
|
"histogram",
|
|
"line",
|
|
"ohlc",
|
|
"range-area",
|
|
"range-bar",
|
|
"scatter",
|
|
"waterfall"
|
|
];
|
|
var priceVolumePresetIgnoredMiniChartProperties = [
|
|
"itemStyler",
|
|
"simpleItemStyler",
|
|
"direction",
|
|
"fill",
|
|
"fillGradientDefaults",
|
|
"fillPatternDefaults",
|
|
"fillImageDefaults",
|
|
"fillOpacity",
|
|
"shadow",
|
|
"focusPriority",
|
|
"highlight",
|
|
"lineDash",
|
|
"lineDashOffset",
|
|
"strokeWidth"
|
|
];
|
|
function miniChartSeriesTheme(seriesPath, typePath) {
|
|
return {
|
|
$merge: [
|
|
{
|
|
$switch: [
|
|
typePath,
|
|
{},
|
|
[
|
|
["area", "line", "range-area"],
|
|
{
|
|
marker: {
|
|
enabled: {
|
|
$isUserOption: [
|
|
"/series/$index/marker/enabled",
|
|
{ $path: ["/series/$index/marker/enabled", false] },
|
|
false
|
|
]
|
|
}
|
|
}
|
|
}
|
|
]
|
|
]
|
|
},
|
|
{
|
|
$omit: [
|
|
{
|
|
$switch: [
|
|
typePath,
|
|
commonIgnoredMiniChartProperties,
|
|
["bar", barIgnoredMiniChartProperties],
|
|
["box-plot", boxPlotIngnoredMiniChartProperties],
|
|
["bubble", bubbleIgnoredMiniChartProperties],
|
|
["heatmap", heatmapIgnoredMiniChartProperties],
|
|
["histogram", histogramIgnoredMiniChartProperties],
|
|
[
|
|
"line",
|
|
[...lineIgnoredMiniChartProperties, ...priceVolumePresetIgnoredMiniChartProperties]
|
|
],
|
|
["range-area", rangeAreaIgnoredMiniChartProperties],
|
|
["range-bar", rangeBarIgnoredMiniChartProperties],
|
|
["scatter", scatterIgnoredMiniChartProperties],
|
|
["waterfall", waterfallIgnoredMiniChartProperties]
|
|
]
|
|
},
|
|
seriesPath
|
|
]
|
|
}
|
|
]
|
|
};
|
|
}
|
|
var NAVIGATOR_THEME = {
|
|
enabled: false,
|
|
height: { $if: [{ $path: "./miniChart/enabled" }, 40, 18] },
|
|
cornerRadius: 4,
|
|
mask: {
|
|
fill: { $ref: "foregroundColor" },
|
|
fillOpacity: 0.1,
|
|
stroke: { $ref: "borderColor" },
|
|
strokeWidth: 1
|
|
},
|
|
minHandle: {
|
|
fill: { $ref: "chartBackgroundColor" },
|
|
stroke: { $ref: "borderColor" },
|
|
strokeWidth: 1,
|
|
width: 12,
|
|
height: 24,
|
|
cornerRadius: 4
|
|
},
|
|
maxHandle: {
|
|
fill: { $ref: "chartBackgroundColor" },
|
|
stroke: { $ref: "borderColor" },
|
|
strokeWidth: 1,
|
|
width: 12,
|
|
height: 24,
|
|
cornerRadius: 4
|
|
},
|
|
miniChart: {
|
|
enabled: false,
|
|
label: {
|
|
color: { $ref: "textColor" },
|
|
fontSize: { $rem: FONT_SIZE_RATIO.SMALLER },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
spacing: 5
|
|
},
|
|
padding: {
|
|
top: 0,
|
|
bottom: 0
|
|
},
|
|
series: {
|
|
$apply: [
|
|
miniChartSeriesTheme(
|
|
{ $path: "/series/$index" },
|
|
{
|
|
$path: [
|
|
"/navigator/miniChart/series/$index/type",
|
|
{ $path: ["type", { $path: "/series/$index/type" }] }
|
|
]
|
|
}
|
|
),
|
|
{
|
|
// TODO: this should be a $switch but switches can not resolve the case value yet
|
|
$if: [
|
|
{
|
|
$or: validMiniChartSeriesTypes.map((type) => ({
|
|
$eq: [{ $path: "/series/0/type" }, type]
|
|
}))
|
|
},
|
|
{
|
|
$map: [
|
|
miniChartSeriesTheme({ $value: "$1" }, { $path: "/series/$index/type" }),
|
|
{ $path: "/series" }
|
|
]
|
|
},
|
|
void 0
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/navigator/navigatorModule.ts
|
|
var NavigatorModule = {
|
|
type: "plugin",
|
|
name: "navigator",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
// removable: false, // Toggling this module causes zoom state flakiness.
|
|
options: navigatorOptionsDef2,
|
|
themeTemplate: NAVIGATOR_THEME,
|
|
create: (ctx) => new Navigator(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/ranges/rangesButtonProperties.ts
|
|
var RangesButtonProperties = class extends ToolbarButtonProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RangesButtonProperties.prototype, "value", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/ranges/ranges.ts
|
|
var { userInteraction: userInteraction2, LayoutElement: LayoutElement4, Toolbar: Toolbar2 } = module_support_exports;
|
|
var Ranges = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.enabled = false;
|
|
this.buttons = new PropertiesArray(RangesButtonProperties);
|
|
this.verticalSpacing = 10;
|
|
this.container = ctx.domManager.addChild("canvas-overlay", "range-buttons");
|
|
this.container.role = "presentation";
|
|
this.toolbar = new Toolbar2(this.ctx, "ariaLabelRangesToolbar", "horizontal");
|
|
this.toolbar.addClass("ag-charts-range-buttons");
|
|
this.container.append(this.toolbar.getElement());
|
|
this.cleanup.register(
|
|
this.toolbar.addToolbarListener("button-pressed", this.onButtonPress.bind(this)),
|
|
ctx.layoutManager.registerElement(LayoutElement4.ToolbarBottom, this.onLayoutStart.bind(this)),
|
|
ctx.eventsHub.on("zoom:change-complete", this.onZoomChanged.bind(this)),
|
|
this.teardown.bind(this)
|
|
);
|
|
}
|
|
teardown() {
|
|
this.toolbar.getElement().remove();
|
|
this.toolbar.destroy();
|
|
}
|
|
onLayoutStart({ layoutBox }) {
|
|
const { buttons, ctx, enabled, toolbar: toolbar2, verticalSpacing } = this;
|
|
if (!enabled || !ctx.zoomManager.isZoomEnabled()) {
|
|
toolbar2.setHidden(true);
|
|
return;
|
|
}
|
|
toolbar2.setHidden(false);
|
|
toolbar2.updateButtons(buttons);
|
|
const height2 = toolbar2.getBounds().height;
|
|
toolbar2.setBounds({
|
|
x: layoutBox.x,
|
|
y: layoutBox.y + layoutBox.height - height2,
|
|
width: layoutBox.width,
|
|
height: height2
|
|
});
|
|
layoutBox.shrink({ bottom: height2 + verticalSpacing });
|
|
}
|
|
onZoomChanged() {
|
|
this.toolbar.clearActiveButton();
|
|
}
|
|
onButtonPress({ button: { index } }) {
|
|
const { zoomManager } = this.ctx;
|
|
const button = this.buttons.at(index);
|
|
if (!button)
|
|
return;
|
|
const { value } = button;
|
|
const sourcing = userInteraction2(`zoom-range-button-${index}`);
|
|
if (value == null) {
|
|
zoomManager.resetZoom(sourcing);
|
|
} else if (typeof value === "number") {
|
|
zoomManager.extendToEnd(sourcing, "x" /* X */, value);
|
|
} else if (Array.isArray(value)) {
|
|
zoomManager.updateWith(sourcing, "x" /* X */, () => value);
|
|
} else if (typeof value === "function") {
|
|
zoomManager.updateWith(sourcing, "x" /* X */, value);
|
|
}
|
|
this.toolbar.toggleActiveButtonByIndex(index);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Ranges.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Ranges.prototype, "buttons", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/ranges/rangesModule.ts
|
|
var DAY = 1e3 * 60 * 60 * 24;
|
|
var MONTH = DAY * 30;
|
|
var YEAR = DAY * 365;
|
|
var RangesModule = {
|
|
type: "plugin",
|
|
name: "ranges",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: {
|
|
enabled: boolean,
|
|
buttons: arrayOfDefs(
|
|
{
|
|
...toolbarButtonOptionsDefs,
|
|
value: or(number, and(arrayOf(or(number, date)), arrayLength(2, 2)), callback)
|
|
},
|
|
"range button options array"
|
|
)
|
|
},
|
|
themeTemplate: {
|
|
enabled: false,
|
|
buttons: {
|
|
$shallowSimple: [
|
|
{
|
|
label: "toolbarRange1Month",
|
|
ariaLabel: "toolbarRange1MonthAria",
|
|
value: MONTH
|
|
},
|
|
{
|
|
label: "toolbarRange3Months",
|
|
ariaLabel: "toolbarRange3MonthsAria",
|
|
value: 3 * MONTH
|
|
},
|
|
{
|
|
label: "toolbarRange6Months",
|
|
ariaLabel: "toolbarRange6MonthsAria",
|
|
value: 6 * MONTH
|
|
},
|
|
{
|
|
label: "toolbarRangeYearToDate",
|
|
ariaLabel: "toolbarRangeYearToDateAria",
|
|
value: (_start, end3) => [
|
|
(/* @__PURE__ */ new Date(`${new Date(end3).getFullYear()}-01-01`)).getTime(),
|
|
void 0
|
|
]
|
|
},
|
|
{
|
|
label: "toolbarRange1Year",
|
|
ariaLabel: "toolbarRange1YearAria",
|
|
value: YEAR
|
|
},
|
|
{
|
|
label: "toolbarRangeAll",
|
|
ariaLabel: "toolbarRangeAllAria",
|
|
value: void 0
|
|
// Reset zoom
|
|
}
|
|
]
|
|
}
|
|
},
|
|
create: (ctx) => new Ranges(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/scrollbar/scrollbarDOMProxy.ts
|
|
var { SliderWidget: SliderWidget3 } = module_support_exports;
|
|
var STEP_REPEAT_DELAY_MS = 400;
|
|
var STEP_REPEAT_INTERVAL_MS = 50;
|
|
var StepRepeater = class {
|
|
constructor(applyStep) {
|
|
this.applyStep = applyStep;
|
|
}
|
|
start(target) {
|
|
this.setTarget(target);
|
|
this.run(STEP_REPEAT_DELAY_MS);
|
|
}
|
|
updateTarget(target) {
|
|
this.setTarget(target);
|
|
if (!this.isActive()) {
|
|
this.run(STEP_REPEAT_DELAY_MS);
|
|
}
|
|
}
|
|
stop(resetTarget = true) {
|
|
this.clearTimer();
|
|
if (resetTarget) {
|
|
this.target = void 0;
|
|
}
|
|
}
|
|
setTarget(target) {
|
|
this.target = clamp(0, target, 1);
|
|
}
|
|
run(delay) {
|
|
if (this.target == null) {
|
|
this.stop();
|
|
return;
|
|
}
|
|
const finished = this.applyStep(this.target);
|
|
if (finished) {
|
|
this.stop();
|
|
return;
|
|
}
|
|
this.schedule(delay);
|
|
}
|
|
schedule(delay) {
|
|
if (this.isActive())
|
|
return;
|
|
this.timer = setTimeout(() => {
|
|
this.timer = void 0;
|
|
this.run(STEP_REPEAT_INTERVAL_MS);
|
|
}, delay);
|
|
}
|
|
clearTimer() {
|
|
if (this.timer != null) {
|
|
clearTimeout(this.timer);
|
|
this.timer = void 0;
|
|
}
|
|
}
|
|
isActive() {
|
|
return this.timer != null;
|
|
}
|
|
};
|
|
var ScrollbarState = class {
|
|
constructor() {
|
|
this.min = 0;
|
|
this.span = 1;
|
|
this.thumbSpan = 1;
|
|
}
|
|
update(min, max, thumbSpan = this.thumbSpan) {
|
|
const span = clamp(0, max - min, 1);
|
|
this.span = span;
|
|
this.thumbSpan = clamp(0, thumbSpan, 1);
|
|
this.min = this.clampMin(min, span);
|
|
}
|
|
clampMin(min, span = this.span) {
|
|
return clamp(0, min, 1 - span);
|
|
}
|
|
getThumbBounds(min = this.min, span = this.thumbSpan) {
|
|
const start2 = this.clampMin(min, span);
|
|
return { start: start2, end: start2 + span };
|
|
}
|
|
isWithinThumb(ratio2) {
|
|
const { start: start2, end: end3 } = this.getThumbBounds();
|
|
return ratio2 >= start2 && ratio2 <= end3;
|
|
}
|
|
getJumpRange(ratio2) {
|
|
if (!this.canScroll())
|
|
return;
|
|
let min = this.clampMin(ratio2 - this.thumbSpan / 2, this.thumbSpan);
|
|
min = this.clampMin(min);
|
|
return { min, max: min + this.span };
|
|
}
|
|
getStepRange(ratio2) {
|
|
if (!this.canScroll())
|
|
return;
|
|
const cursor = clamp(0, ratio2, 1);
|
|
const { start: start2, end: end3 } = this.getThumbBounds();
|
|
if (cursor >= start2 && cursor <= end3)
|
|
return;
|
|
const movingLeft = cursor < start2;
|
|
const distance2 = movingLeft ? start2 - cursor : cursor - end3;
|
|
const step = Math.min(this.span, distance2);
|
|
const nextMin = this.clampMin(this.min + (movingLeft ? -step : step));
|
|
return { min: nextMin, max: nextMin + this.span };
|
|
}
|
|
canScroll() {
|
|
return this.span > 0 && this.span < 1;
|
|
}
|
|
};
|
|
var ScrollbarDOMProxy = class {
|
|
constructor(ctx, orientation, onChange, onHoverChange) {
|
|
this.ctx = ctx;
|
|
this.orientation = orientation;
|
|
this.onChange = onChange;
|
|
this.onHoverChange = onHoverChange;
|
|
this.dragStartRatio = 0;
|
|
this.interactionMode = "none";
|
|
this.state = new ScrollbarState();
|
|
this.repeater = new StepRepeater((target) => this.applyStepToward(target));
|
|
this.container = ctx.proxyInteractionService.createProxyContainer({
|
|
type: "group",
|
|
domManagerId: `scrollbar-${orientation}`,
|
|
classList: ["ag-charts-proxy-scrollbar", `ag-charts-proxy-scrollbar-${orientation}`],
|
|
ariaLabel: void 0,
|
|
role: "presentation"
|
|
});
|
|
const ariaLabelId = orientation === "horizontal" ? "ariaLabelScrollbarHorizontal" : "ariaLabelScrollbarVertical";
|
|
this.slider = ctx.proxyInteractionService.createProxyElement({
|
|
type: "slider",
|
|
domIndex: 0,
|
|
tabIndex: 0,
|
|
ariaLabel: { id: ariaLabelId },
|
|
role: "slider",
|
|
parent: this.container,
|
|
classList: ["ag-charts-proxy-scrollbar-slider"]
|
|
});
|
|
const element2 = this.slider.getElement();
|
|
element2.ariaValueMin = "0";
|
|
element2.ariaValueMax = "100";
|
|
this.slider.step = SliderWidget3.STEP_HUNDRETH;
|
|
this.slider.keyboardStep = SliderWidget3.STEP_ONE;
|
|
this.slider.orientation = orientation;
|
|
this.slider.setPreventsDefault(false);
|
|
this.slider.addListener("change", () => this.onSliderChange());
|
|
this.slider.addListener("keydown", (ev) => this.onSliderKeyDown(ev));
|
|
this.slider.addListener("drag-start", (ev) => this.onDragStart(ev));
|
|
this.slider.addListener("drag-move", (ev) => this.onDragMove(ev));
|
|
this.slider.addListener("drag-end", (ev) => this.onDragEnd(ev));
|
|
this.slider.addListener("mouseenter", (event) => this.handleHoverEvent(event));
|
|
this.slider.addListener("mousemove", (event) => this.handleHoverEvent(event));
|
|
this.slider.addListener("mouseleave", () => this.onMouseLeave());
|
|
this.thumbFocus = ctx.proxyInteractionService.createProxyElement({
|
|
type: "region",
|
|
parent: this.container,
|
|
classList: ["ag-charts-proxy-scrollbar-thumb-focus"],
|
|
role: "presentation"
|
|
});
|
|
this.thumbFocus.setAriaHidden(true);
|
|
this.thumbFocus.setPointerEvents("none");
|
|
}
|
|
destroy() {
|
|
this.interactionBounds = void 0;
|
|
this.repeater.stop();
|
|
this.container.destroy();
|
|
}
|
|
updateBounds(bounds) {
|
|
this.interactionBounds = void 0;
|
|
this.container.setBounds(bounds);
|
|
this.slider.setBounds({ x: 0, y: 0, width: bounds.width, height: bounds.height });
|
|
}
|
|
updateVisibility(visible) {
|
|
this.container.setHidden(!visible);
|
|
}
|
|
updateMinMax(min, max, thumbSpan = this.state.thumbSpan, options) {
|
|
this.state.update(min, max, thumbSpan);
|
|
const aria = this.ctx.localeManager.t("ariaValuePanRange", {
|
|
min: Math.round(min * 100) / 100,
|
|
max: Math.round(max * 100) / 100
|
|
});
|
|
const element2 = this.slider.getElement();
|
|
element2.ariaValueText = aria;
|
|
const shouldUpdateSlider = !options?.skipSliderUpdate || Math.abs(this.slider.getValueRatio() - min) > 1e-9;
|
|
if (shouldUpdateSlider) {
|
|
this.slider.setValueRatio(min, { ariaValueText: aria });
|
|
}
|
|
}
|
|
updateThumbBounds(thumb, track, cornerRadius) {
|
|
const radius = Math.max(0, cornerRadius ?? 0);
|
|
this.thumbFocus.getElement().style.borderRadius = `${radius}px`;
|
|
this.thumbFocus.setBounds({
|
|
x: thumb.x - track.x,
|
|
y: thumb.y - track.y,
|
|
width: thumb.width,
|
|
height: thumb.height
|
|
});
|
|
}
|
|
update(min, max, options) {
|
|
this.onChange(min, max);
|
|
this.updateMinMax(min, max, void 0, options);
|
|
}
|
|
onSliderChange() {
|
|
const min = this.state.clampMin(this.slider.getValueRatio());
|
|
const max = min + this.state.span;
|
|
this.update(min, max, { skipSliderUpdate: true });
|
|
}
|
|
onSliderKeyDown(event) {
|
|
const { code } = event.sourceEvent;
|
|
const isVertical = this.orientation === "vertical";
|
|
const decrement = isVertical && code === "ArrowUp" || !isVertical && code === "ArrowLeft";
|
|
const increment = isVertical && code === "ArrowDown" || !isVertical && code === "ArrowRight";
|
|
if (!decrement && !increment)
|
|
return;
|
|
event.sourceEvent.preventDefault();
|
|
const element2 = this.slider.getElement();
|
|
element2.step = this.slider.keyboardStep?.attributeValue ?? "1";
|
|
if (decrement) {
|
|
element2.stepDown();
|
|
} else if (increment) {
|
|
element2.stepUp();
|
|
}
|
|
this.onSliderChange();
|
|
}
|
|
onDragMove(event) {
|
|
event.sourceEvent.preventDefault();
|
|
if (this.interactionMode === "drag") {
|
|
const { isHorizontal: isHorizontal2, size, start: start2 } = this.getInteractionBounds() ?? {};
|
|
if (start2 == null || size == null)
|
|
return;
|
|
const delta5 = (isHorizontal2 ? event.originDeltaX : event.originDeltaY) / size;
|
|
const min = this.state.clampMin(this.dragStartRatio + delta5);
|
|
const max = min + this.state.span;
|
|
this.update(min, max);
|
|
return;
|
|
}
|
|
if (this.interactionMode !== "step")
|
|
return;
|
|
const pointer = this.getPointerInfo(event);
|
|
if (pointer == null || !Number.isFinite(pointer.ratio))
|
|
return;
|
|
const { ratio: ratio2, inCrossBounds } = pointer;
|
|
if (!inCrossBounds) {
|
|
this.repeater.stop();
|
|
return;
|
|
}
|
|
this.repeater.updateTarget(ratio2);
|
|
}
|
|
onDragEnd(event) {
|
|
event.sourceEvent.preventDefault();
|
|
this.interactionBounds = void 0;
|
|
this.setInteraction("none");
|
|
this.onHoverChange(false);
|
|
}
|
|
onDragStart(event) {
|
|
event.sourceEvent.preventDefault();
|
|
this.interactionBounds = void 0;
|
|
const click = this.getClickInfo(event);
|
|
if (!click?.inBounds)
|
|
return;
|
|
if (click.inThumb) {
|
|
this.dragStartRatio = this.slider.getValueRatio();
|
|
this.setInteraction("drag");
|
|
return;
|
|
}
|
|
if (event.sourceEvent.shiftKey) {
|
|
this.jumpTo(click.ratio);
|
|
this.setInteraction("none");
|
|
return;
|
|
}
|
|
this.beginStepRepeat(click.ratio);
|
|
}
|
|
onMouseLeave() {
|
|
this.onHoverChange(false);
|
|
}
|
|
getClickInfo(event) {
|
|
const ratio2 = this.getPointerRatio(event);
|
|
if (ratio2 == null)
|
|
return;
|
|
const inBounds = ratio2 >= 0 && ratio2 <= 1;
|
|
if (!inBounds)
|
|
return { ratio: 0, inBounds: false, inThumb: false };
|
|
return { ratio: ratio2, inBounds: true, inThumb: this.isWithinThumb(ratio2) };
|
|
}
|
|
getPointerRatio(event) {
|
|
return this.getPointerInfo(event)?.ratio;
|
|
}
|
|
getPointerInfo(event) {
|
|
if (event.device === "keyboard")
|
|
return;
|
|
const { isHorizontal: isHorizontal2, size, start: start2, crossStart, crossSize } = this.getInteractionBounds();
|
|
const pos = isHorizontal2 ? event.clientX : event.clientY;
|
|
const crossPos = isHorizontal2 ? event.clientY : event.clientX;
|
|
const ratio2 = (pos - start2) / size;
|
|
const inCrossBounds = crossPos >= crossStart && crossPos <= crossStart + crossSize;
|
|
return { ratio: ratio2, inCrossBounds };
|
|
}
|
|
jumpTo(ratio2) {
|
|
const next = this.state.getJumpRange(ratio2);
|
|
if (!next)
|
|
return;
|
|
this.update(next.min, next.max);
|
|
}
|
|
applyStepToward(target) {
|
|
const next = this.state.getStepRange(target);
|
|
if (!next)
|
|
return true;
|
|
this.update(next.min, next.max);
|
|
return false;
|
|
}
|
|
beginStepRepeat(ratio2) {
|
|
this.setInteraction("step");
|
|
this.repeater.start(ratio2);
|
|
}
|
|
setInteraction(mode) {
|
|
this.interactionMode = mode;
|
|
if (mode !== "step") {
|
|
this.repeater.stop();
|
|
}
|
|
}
|
|
getInteractionBounds() {
|
|
if (this.interactionBounds) {
|
|
return this.interactionBounds;
|
|
}
|
|
const { width: width2, height: height2, left, top } = this.container.getBoundingClientRect();
|
|
const isHorizontal2 = this.orientation === "horizontal";
|
|
const size = isHorizontal2 ? width2 : height2;
|
|
const start2 = isHorizontal2 ? left : top;
|
|
const crossStart = isHorizontal2 ? top : left;
|
|
const crossSize = isHorizontal2 ? height2 : width2;
|
|
this.interactionBounds = { isHorizontal: isHorizontal2, size, start: start2, crossStart, crossSize };
|
|
return this.interactionBounds;
|
|
}
|
|
isWithinThumb(ratio2) {
|
|
return this.state.isWithinThumb(ratio2);
|
|
}
|
|
handleHoverEvent(event) {
|
|
if (this.interactionMode === "drag")
|
|
return;
|
|
const pointer = this.getPointerInfo(event);
|
|
if (!pointer) {
|
|
this.onHoverChange(false);
|
|
return;
|
|
}
|
|
const hovered = this.isWithinThumb(pointer.ratio);
|
|
this.onHoverChange(hovered);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/scrollbar/scrollbarProperties.ts
|
|
var ScrollbarTrack = class extends ChangeDetectableProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = false;
|
|
this.fillOpacity = 1;
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.cornerRadius = 0;
|
|
this.opacity = 1;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarTrack.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarTrack.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarTrack.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarTrack.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarTrack.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarTrack.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ScrollbarTrack.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ScrollbarTrack.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ScrollbarTrack.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ScrollbarTrack.prototype, "opacity", 2);
|
|
var ScrollbarThumbHoverStyle = class extends ChangeDetectableProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarThumbHoverStyle.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarThumbHoverStyle.prototype, "stroke", 2);
|
|
var ScrollbarThumb = class extends ScrollbarTrack {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.minSize = 20;
|
|
this.hoverStyle = new ScrollbarThumbHoverStyle();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarThumb.prototype, "minSize", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ScrollbarThumb.prototype, "hoverStyle", 2);
|
|
var ScrollbarProperties = class extends ChangeDetectableProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = false;
|
|
this.thickness = 12;
|
|
this.spacing = 4;
|
|
this.tickSpacing = 0;
|
|
this.placement = "outer";
|
|
this.visible = "auto";
|
|
this.track = new ScrollbarTrack();
|
|
this.thumb = new ScrollbarThumb();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarProperties.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarProperties.prototype, "thickness", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarProperties.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarProperties.prototype, "tickSpacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarProperties.prototype, "placement", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], ScrollbarProperties.prototype, "visible", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ScrollbarProperties.prototype, "track", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ScrollbarProperties.prototype, "thumb", 2);
|
|
var HorizontalScrollbarProperties = class extends ScrollbarProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], HorizontalScrollbarProperties.prototype, "position", 2);
|
|
var VerticalScrollbarProperties = class extends ScrollbarProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
SceneChangeDetection()
|
|
], VerticalScrollbarProperties.prototype, "position", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/scrollbar/scrollbar.ts
|
|
var { BBox: BBox21, Group: Group10, Rect: Rect7, LayoutElement: LayoutElement5, InteractionState: InteractionState6 } = module_support_exports;
|
|
var Scrollbar = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.track = new ScrollbarTrack();
|
|
this.thumb = new ScrollbarThumb();
|
|
this.horizontal = new HorizontalScrollbarProperties();
|
|
this.vertical = new VerticalScrollbarProperties();
|
|
this.state = {
|
|
horizontal: this.createOrientationState("horizontal"),
|
|
vertical: this.createOrientationState("vertical")
|
|
};
|
|
this.cleanup.register(
|
|
ctx.scene.attachNode(this.state.horizontal.group),
|
|
ctx.scene.attachNode(this.state.vertical.group),
|
|
ctx.layoutManager.registerElement(LayoutElement5.Scrollbar, (e) => this.onLayoutStart(e)),
|
|
ctx.eventsHub.on("layout:complete", (e) => this.onLayoutComplete(e)),
|
|
ctx.eventsHub.on("zoom:change-complete", () => this.updateThumbs())
|
|
);
|
|
}
|
|
createOrientationState(orientation) {
|
|
const group = new Group10({ name: `scrollbar-${orientation}`, zIndex: 17 /* NAVIGATOR */ });
|
|
const track = new Rect7();
|
|
const thumb = new Rect7();
|
|
group.append(track);
|
|
group.append(thumb);
|
|
const dom = new ScrollbarDOMProxy(
|
|
this.ctx,
|
|
orientation,
|
|
(min, max) => this.handleUserChange(orientation, min, max),
|
|
(hovered) => this.handleHoverChange(orientation, hovered)
|
|
);
|
|
const properties = this.resolveProperties(orientation);
|
|
return {
|
|
orientation,
|
|
group,
|
|
track,
|
|
thumb,
|
|
dom,
|
|
properties,
|
|
position: this.getDefaultPosition(orientation),
|
|
positionHasAxis: false,
|
|
hovered: false
|
|
};
|
|
}
|
|
resolveProperties(orientation) {
|
|
return orientation === "horizontal" ? this.horizontal : this.vertical;
|
|
}
|
|
getDefaultPosition(orientation) {
|
|
return orientation === "horizontal" ? "bottom" : "left";
|
|
}
|
|
resolveAxis(orientation, configuredPosition) {
|
|
const direction = orientation === "horizontal" ? "x" /* X */ : "y" /* Y */;
|
|
const contexts = this.ctx.axisManager.getAxisContext(direction);
|
|
if (contexts.length === 0) {
|
|
return { position: this.getDefaultPosition(orientation), positionHasAxis: false };
|
|
}
|
|
if (configuredPosition == null) {
|
|
const axis2 = contexts[0];
|
|
return { axis: axis2, position: axis2.position ?? this.getDefaultPosition(orientation), positionHasAxis: true };
|
|
}
|
|
const axis = contexts.find((ctx) => ctx.position === configuredPosition);
|
|
if (axis) {
|
|
return { axis, position: configuredPosition, positionHasAxis: true };
|
|
}
|
|
return { axis: contexts[0], position: configuredPosition, positionHasAxis: false };
|
|
}
|
|
onLayoutStart({ scrollbars, layoutBox }) {
|
|
for (const orientation of ["horizontal", "vertical"]) {
|
|
const state = this.state[orientation];
|
|
const properties = this.resolveProperties(orientation);
|
|
const { min, max } = this.getZoomRange(state.orientation);
|
|
const span = clamp(0, max - min, 1);
|
|
const {
|
|
axis: { axisId } = {},
|
|
position,
|
|
positionHasAxis
|
|
} = this.resolveAxis(orientation, properties.position);
|
|
state.properties = properties;
|
|
state.axisId = axisId;
|
|
state.position = position;
|
|
state.positionHasAxis = positionHasAxis;
|
|
const show = this.updateVisibility(state, span);
|
|
if (!show || axisId == null)
|
|
continue;
|
|
const { thickness, spacing, placement, tickSpacing } = properties;
|
|
if (positionHasAxis) {
|
|
scrollbars[axisId] = {
|
|
enabled: show,
|
|
thickness,
|
|
spacing,
|
|
tickSpacing,
|
|
placement
|
|
};
|
|
} else {
|
|
layoutBox.shrink(spacing + thickness, position);
|
|
}
|
|
this.updateStyles(state);
|
|
}
|
|
}
|
|
onLayoutComplete(event) {
|
|
this.seriesRect = event.series.rect;
|
|
for (const orientation of ["horizontal", "vertical"]) {
|
|
const state = this.state[orientation];
|
|
const {
|
|
properties: { enabled, visible }
|
|
} = state;
|
|
if (!enabled || visible === "never") {
|
|
continue;
|
|
}
|
|
const layoutRect = this.getLayoutRect(state, orientation, event);
|
|
state.layoutRect = layoutRect;
|
|
if (!layoutRect)
|
|
continue;
|
|
this.updateTrack(state, layoutRect);
|
|
this.updateThumb(state, layoutRect);
|
|
}
|
|
}
|
|
getLayoutRect(state, orientation, event) {
|
|
const {
|
|
properties: { thickness, spacing },
|
|
position,
|
|
positionHasAxis
|
|
} = state;
|
|
const axisLayout = state.axisId ? event.axes[state.axisId] : void 0;
|
|
if (!axisLayout)
|
|
return;
|
|
const { x, y, width: width2, height: height2 } = event.series.rect;
|
|
const isHorizontal2 = orientation === "horizontal";
|
|
if (!positionHasAxis) {
|
|
if (isHorizontal2) {
|
|
const coord2 = position === "bottom" ? y + height2 + spacing : y - spacing - thickness;
|
|
return new BBox21(x, coord2, width2, thickness);
|
|
} else {
|
|
const coord2 = position === "right" ? x + width2 + spacing : x - spacing - thickness;
|
|
return new BBox21(coord2, y, thickness, height2);
|
|
}
|
|
}
|
|
const { scrollbar: scrollbarLayout, translation } = axisLayout;
|
|
if (!scrollbarLayout?.enabled)
|
|
return;
|
|
const coord = isHorizontal2 ? translation.y + scrollbarLayout.offset : translation.x + scrollbarLayout.offset;
|
|
return isHorizontal2 ? new BBox21(x, coord, width2, thickness) : new BBox21(coord, y, thickness, height2);
|
|
}
|
|
updateStyles({ track, thumb, properties, hovered }) {
|
|
track.setStyleProperties(properties.track);
|
|
track.cornerRadius = properties.track.cornerRadius ?? 0;
|
|
track.opacity = properties.track.opacity ?? 1;
|
|
thumb.setStyleProperties(properties.thumb);
|
|
thumb.cornerRadius = properties.thumb.cornerRadius ?? 0;
|
|
thumb.opacity = properties.thumb.opacity ?? 1;
|
|
const hoverStyle = properties.thumb.hoverStyle;
|
|
thumb.fill = hovered ? hoverStyle?.fill ?? properties.thumb.fill : properties.thumb.fill;
|
|
thumb.stroke = hovered ? hoverStyle?.stroke ?? properties.thumb.stroke : properties.thumb.stroke;
|
|
}
|
|
updateTrack(state, bounds) {
|
|
state.track.x = bounds.x;
|
|
state.track.y = bounds.y;
|
|
state.track.width = bounds.width;
|
|
state.track.height = bounds.height;
|
|
state.dom.updateBounds(bounds);
|
|
}
|
|
updateThumb(state, track) {
|
|
const { min, max } = this.getZoomRange(state.orientation);
|
|
const span = clamp(0, max - min, 1);
|
|
const show = this.updateVisibility(state, span);
|
|
if (!show || track.width <= 0 || track.height <= 0) {
|
|
return;
|
|
}
|
|
const minSize = state.properties.thumb.minSize ?? 0;
|
|
let thumbSpan;
|
|
if (state.orientation === "horizontal") {
|
|
const thumbWidth = Math.min(Math.max(minSize, track.width * span), track.width);
|
|
const start2 = clamp(track.x, track.x + track.width * min, track.x + track.width - thumbWidth);
|
|
state.thumb.x = start2;
|
|
state.thumb.y = track.y;
|
|
state.thumb.width = thumbWidth;
|
|
state.thumb.height = track.height;
|
|
thumbSpan = clamp(0, thumbWidth / track.width, 1);
|
|
} else {
|
|
const thumbHeight = Math.min(Math.max(minSize, track.height * span), track.height);
|
|
const start2 = clamp(track.y, track.y + track.height * min, track.y + track.height - thumbHeight);
|
|
state.thumb.x = track.x;
|
|
state.thumb.y = start2;
|
|
state.thumb.width = track.width;
|
|
state.thumb.height = thumbHeight;
|
|
thumbSpan = clamp(0, thumbHeight / track.height, 1);
|
|
}
|
|
state.dom.updateThumbBounds(state.thumb, track, state.properties.thumb.cornerRadius);
|
|
state.dom.updateMinMax(min, max, thumbSpan);
|
|
}
|
|
updateThumbs() {
|
|
if (!this.seriesRect)
|
|
return;
|
|
for (const orientation of ["horizontal", "vertical"]) {
|
|
const state = this.state[orientation];
|
|
const layoutRect = state.layoutRect;
|
|
if (!layoutRect || !state.properties.enabled || state.properties.visible === "never")
|
|
continue;
|
|
const trackBounds = orientation === "horizontal" ? new BBox21(this.seriesRect.x, layoutRect.y, this.seriesRect.width, state.properties.thickness) : new BBox21(layoutRect.x, this.seriesRect.y, state.properties.thickness, this.seriesRect.height);
|
|
this.updateThumb(state, trackBounds);
|
|
}
|
|
}
|
|
updateVisibility(state, span) {
|
|
const show = state.properties.enabled && state.axisId != null && state.properties.visible !== "never" && (state.properties.visible === "always" || span < 1);
|
|
state.group.visible = show;
|
|
state.track.visible = show;
|
|
state.thumb.visible = show;
|
|
state.dom.updateVisibility(show);
|
|
return show;
|
|
}
|
|
getZoomRange(orientation) {
|
|
const zoom = this.ctx.zoomManager.getZoom();
|
|
const isHorizontal2 = orientation === "horizontal";
|
|
const range3 = isHorizontal2 ? zoom?.x : zoom?.y;
|
|
if (!isHorizontal2 && range3 != null) {
|
|
return {
|
|
min: 1 - (range3.max ?? 1),
|
|
max: 1 - (range3.min ?? 0)
|
|
};
|
|
}
|
|
return {
|
|
min: range3?.min ?? 0,
|
|
max: range3?.max ?? 1
|
|
};
|
|
}
|
|
handleUserChange(orientation, min, max) {
|
|
if (!this.ctx.interactionManager.isState(InteractionState6.ZoomDraggable))
|
|
return;
|
|
const isHorizontal2 = orientation === "horizontal";
|
|
if (!isHorizontal2) {
|
|
[min, max] = [1 - max, 1 - min];
|
|
}
|
|
const zoom = isHorizontal2 ? { x: { min, max } } : { y: { min, max } };
|
|
this.ctx.zoomManager.updateZoom(
|
|
{
|
|
source: "user-interaction",
|
|
sourceDetail: "scrollbar"
|
|
},
|
|
zoom
|
|
);
|
|
}
|
|
handleHoverChange(orientation, hovered) {
|
|
const state = this.state[orientation];
|
|
const nextHovered = hovered && state.group.visible;
|
|
if (state.hovered === nextHovered)
|
|
return;
|
|
state.hovered = nextHovered;
|
|
this.updateStyles(state);
|
|
this.ctx.updateService.update(9 /* SCENE_RENDER */);
|
|
}
|
|
destroy() {
|
|
super.destroy();
|
|
this.state.horizontal.dom.destroy();
|
|
this.state.vertical.dom.destroy();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Scrollbar.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Scrollbar.prototype, "thickness", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Scrollbar.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Scrollbar.prototype, "tickSpacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Scrollbar.prototype, "placement", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Scrollbar.prototype, "visible", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Scrollbar.prototype, "track", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Scrollbar.prototype, "thumb", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Scrollbar.prototype, "horizontal", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Scrollbar.prototype, "vertical", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/scrollbar/scrollbarOptionsDefs.ts
|
|
var scrollbarTrackOptionsDef2 = {
|
|
...fillOptionsDef,
|
|
...strokeOptionsDef,
|
|
...lineDashOptionsDef,
|
|
cornerRadius: positiveNumber,
|
|
opacity: ratio
|
|
};
|
|
var scrollbarThumbOptionsDef2 = {
|
|
...scrollbarTrackOptionsDef2,
|
|
minSize: positiveNumber,
|
|
hoverStyle: {
|
|
fill: fillOptionsDef.fill,
|
|
stroke: strokeOptionsDef.stroke
|
|
}
|
|
};
|
|
var scrollbarBaseOptionsDef2 = {
|
|
enabled: boolean,
|
|
thickness: positiveNumber,
|
|
spacing: positiveNumber,
|
|
tickSpacing: positiveNumber,
|
|
visible: union("auto", "always", "never"),
|
|
placement: union("outer", "inner"),
|
|
track: scrollbarTrackOptionsDef2,
|
|
thumb: scrollbarThumbOptionsDef2
|
|
};
|
|
var scrollbarHorizontalOrientationOptionsDef2 = {
|
|
...scrollbarBaseOptionsDef2,
|
|
position: union("top", "bottom")
|
|
};
|
|
var scrollbarVerticalOrientationOptionsDef2 = {
|
|
...scrollbarBaseOptionsDef2,
|
|
position: union("left", "right")
|
|
};
|
|
var scrollbarOptionsDef2 = {
|
|
enabled: boolean,
|
|
thickness: positiveNumber,
|
|
spacing: positiveNumber,
|
|
tickSpacing: positiveNumber,
|
|
visible: union("auto", "always", "never"),
|
|
placement: union("outer", "inner"),
|
|
track: scrollbarTrackOptionsDef2,
|
|
thumb: scrollbarThumbOptionsDef2,
|
|
horizontal: scrollbarHorizontalOrientationOptionsDef2,
|
|
vertical: scrollbarVerticalOrientationOptionsDef2
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/scrollbar/scrollbarTheme.ts
|
|
var SCROLLBAR_ORIENTATION_THEME = {
|
|
enabled: { $path: "../enabled" },
|
|
thickness: { $path: "../thickness" },
|
|
spacing: { $path: "../spacing" },
|
|
tickSpacing: { $path: "../tickSpacing" },
|
|
placement: { $path: "../placement" },
|
|
visible: { $path: "../visible" },
|
|
track: {
|
|
fill: { $path: "../../track/fill" },
|
|
stroke: { $path: "../../track/stroke" },
|
|
fillOpacity: { $path: "../../track/fillOpacity" },
|
|
strokeWidth: { $path: "../../track/strokeWidth" },
|
|
lineDash: { $path: "../../track/lineDash" },
|
|
lineDashOffset: { $path: "../../track/lineDashOffset" },
|
|
opacity: { $path: "../../track/opacity" },
|
|
cornerRadius: { $path: "../../track/cornerRadius" }
|
|
},
|
|
thumb: {
|
|
fill: { $path: "../../thumb/fill" },
|
|
stroke: { $path: "../../thumb/stroke" },
|
|
fillOpacity: { $path: "../../thumb/fillOpacity" },
|
|
strokeWidth: { $path: "../../thumb/strokeWidth" },
|
|
lineDash: { $path: "../../thumb/lineDash" },
|
|
lineDashOffset: { $path: "../../thumb/lineDashOffset" },
|
|
opacity: { $path: "../../thumb/opacity" },
|
|
cornerRadius: { $path: "../../thumb/cornerRadius" },
|
|
minSize: { $path: "../../thumb/minSize" },
|
|
hoverStyle: {
|
|
fill: { $path: "../../../thumb/hoverStyle/fill" },
|
|
stroke: { $path: "../../../thumb/hoverStyle/stroke" }
|
|
}
|
|
}
|
|
};
|
|
var SCROLLBAR_THEME = {
|
|
enabled: false,
|
|
thickness: 12,
|
|
spacing: 16,
|
|
tickSpacing: 0,
|
|
placement: "outer",
|
|
visible: "auto",
|
|
track: {
|
|
fill: { $foregroundBackgroundMix: 0.03 },
|
|
stroke: { $foregroundBackgroundMix: 0.177 },
|
|
strokeWidth: 1,
|
|
lineDash: [0],
|
|
lineDashOffset: 0,
|
|
opacity: 1,
|
|
cornerRadius: 6
|
|
},
|
|
thumb: {
|
|
fill: { $foregroundBackgroundMix: 0.125 },
|
|
stroke: { $foregroundBackgroundMix: 0.364 },
|
|
strokeWidth: 1,
|
|
lineDash: [0],
|
|
lineDashOffset: 0,
|
|
opacity: 1,
|
|
cornerRadius: 6,
|
|
minSize: 20,
|
|
hoverStyle: {
|
|
fill: { $mix: [{ $path: "../fill" }, { $ref: "foregroundColor" }, 0.075] },
|
|
stroke: { $mix: [{ $path: "../stroke" }, { $ref: "foregroundColor" }, 0.075] }
|
|
}
|
|
},
|
|
vertical: SCROLLBAR_ORIENTATION_THEME,
|
|
horizontal: SCROLLBAR_ORIENTATION_THEME
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/scrollbar/scrollbarModule.ts
|
|
var ScrollbarModule = {
|
|
type: "plugin",
|
|
name: "scrollbar",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: scrollbarOptionsDef2,
|
|
themeTemplate: SCROLLBAR_THEME,
|
|
create: (ctx) => new Scrollbar(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/status-bar/statusBar.ts
|
|
var { LayoutElement: LayoutElement6, Group: Group11, Label: Label11, Rect: Rect8, Text: Text4 } = module_support_exports;
|
|
var chartConfigurations = {
|
|
ohlc: 2 /* Open */ | 4 /* Close */ | 8 /* Low */ | 16 /* High */ | 32 /* Volume */,
|
|
candlestick: 2 /* Open */ | 4 /* Close */ | 8 /* Low */ | 16 /* High */ | 32 /* Volume */,
|
|
"hollow-candlestick": 2 /* Open */ | 4 /* Close */ | 8 /* Low */ | 16 /* High */ | 32 /* Volume */,
|
|
line: 64 /* UnlabelledClose */ | 32 /* Volume */,
|
|
"step-line": 64 /* UnlabelledClose */ | 32 /* Volume */,
|
|
hlc: 128 /* NeutralClose */ | 8 /* Low */ | 16 /* High */ | 32 /* Volume */,
|
|
"high-low": 512 /* NeutralLow */ | 256 /* NeutralHigh */ | 32 /* Volume */
|
|
};
|
|
var itemIdMap = {
|
|
up: "positive",
|
|
down: "negative"
|
|
};
|
|
var neutralColorMap = {
|
|
hlc: "altNeutral"
|
|
};
|
|
var StatusBarBackground = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = "black";
|
|
this.fillOpacity = 1;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBarBackground.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBarBackground.prototype, "fillOpacity", 2);
|
|
var StatusBar = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.enabled = false;
|
|
this.openKey = void 0;
|
|
this.highKey = void 0;
|
|
this.lowKey = void 0;
|
|
this.closeKey = void 0;
|
|
this.volumeKey = void 0;
|
|
this.title = new Label11();
|
|
this.positive = new Label11();
|
|
this.negative = new Label11();
|
|
this.neutral = new Label11();
|
|
this.altNeutral = new Label11();
|
|
this.background = new StatusBarBackground();
|
|
this.layoutStyle = "block";
|
|
this.id = "status-bar";
|
|
this.layer = new Group11({
|
|
name: "StatusBar",
|
|
zIndex: 14 /* STATUS_BAR */
|
|
});
|
|
this.labelGroup = this.layer.appendChild(new module_support_exports.TranslatableGroup());
|
|
this.backgroundNode = this.labelGroup.appendChild(new Rect8());
|
|
this.labels = [
|
|
{
|
|
label: "O",
|
|
configuration: 2 /* Open */,
|
|
title: this.labelGroup.appendChild(new Text4()),
|
|
value: this.labelGroup.appendChild(new Text4()),
|
|
id: "openValue",
|
|
key: "openKey",
|
|
domain: void 0,
|
|
formatter: new Intl.NumberFormat("en-US", {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
})
|
|
},
|
|
{
|
|
label: "H",
|
|
configuration: 16 /* High */,
|
|
title: this.labelGroup.appendChild(new Text4()),
|
|
value: this.labelGroup.appendChild(new Text4()),
|
|
id: "highValue",
|
|
key: "highKey",
|
|
domain: void 0,
|
|
formatter: new Intl.NumberFormat("en-US", {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
})
|
|
},
|
|
{
|
|
label: "H",
|
|
configuration: 256 /* NeutralHigh */,
|
|
title: this.labelGroup.appendChild(new Text4()),
|
|
value: this.labelGroup.appendChild(new Text4()),
|
|
style: "neutral",
|
|
id: "highValue",
|
|
key: "highKey",
|
|
domain: void 0,
|
|
formatter: new Intl.NumberFormat("en-US", {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
})
|
|
},
|
|
{
|
|
label: "L",
|
|
configuration: 8 /* Low */,
|
|
title: this.labelGroup.appendChild(new Text4()),
|
|
value: this.labelGroup.appendChild(new Text4()),
|
|
id: "lowValue",
|
|
key: "lowKey",
|
|
domain: void 0,
|
|
formatter: new Intl.NumberFormat("en-US", {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
})
|
|
},
|
|
{
|
|
label: "L",
|
|
configuration: 512 /* NeutralLow */,
|
|
title: this.labelGroup.appendChild(new Text4()),
|
|
value: this.labelGroup.appendChild(new Text4()),
|
|
style: "neutral",
|
|
id: "lowValue",
|
|
key: "lowKey",
|
|
domain: void 0,
|
|
formatter: new Intl.NumberFormat("en-US", {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
})
|
|
},
|
|
{
|
|
label: "C",
|
|
configuration: 4 /* Close */,
|
|
title: this.labelGroup.appendChild(new Text4()),
|
|
value: this.labelGroup.appendChild(new Text4()),
|
|
id: "closeValue",
|
|
key: "closeKey",
|
|
domain: void 0,
|
|
formatter: new Intl.NumberFormat("en-US", {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
})
|
|
},
|
|
{
|
|
label: "C",
|
|
configuration: 128 /* NeutralClose */,
|
|
title: this.labelGroup.appendChild(new Text4()),
|
|
value: this.labelGroup.appendChild(new Text4()),
|
|
id: "closeValue",
|
|
key: "closeKey",
|
|
style: "neutral",
|
|
domain: void 0,
|
|
formatter: new Intl.NumberFormat("en-US", {
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
})
|
|
},
|
|
{
|
|
label: "",
|
|
configuration: 64 /* UnlabelledClose */,
|
|
title: this.labelGroup.appendChild(new Text4()),
|
|
value: this.labelGroup.appendChild(new Text4()),
|
|
style: "neutral",
|
|
id: "closeValue",
|
|
key: "closeKey",
|
|
domain: void 0,
|
|
formatter: new Intl.NumberFormat("en-US", {
|
|
notation: "compact",
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
})
|
|
},
|
|
{
|
|
label: "Vol",
|
|
configuration: 32 /* Volume */,
|
|
title: this.labelGroup.appendChild(new Text4()),
|
|
value: this.labelGroup.appendChild(new Text4()),
|
|
id: "volumeValue",
|
|
key: "volumeKey",
|
|
domain: void 0,
|
|
formatter: new Intl.NumberFormat("en-US", {
|
|
notation: "compact",
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2
|
|
})
|
|
}
|
|
];
|
|
this.highlightManager = ctx.highlightManager;
|
|
this.labelGroup.visible = false;
|
|
this.cleanup.register(
|
|
ctx.scene.attachNode(this.layer),
|
|
ctx.layoutManager.registerElement(LayoutElement6.Overlay, (e) => this.startPerformLayout(e)),
|
|
ctx.eventsHub.on("layout:complete", (e) => this.onLayoutComplete(e)),
|
|
ctx.eventsHub.on("highlight:change", () => this.updateHighlight()),
|
|
ctx.eventsHub.on("data:update", (data) => {
|
|
this.chartData = data;
|
|
})
|
|
);
|
|
}
|
|
updateDomainsFromSeries() {
|
|
if (!this.enabled)
|
|
return;
|
|
const series = this.ctx.chartService.series;
|
|
if (series.length === 0)
|
|
return;
|
|
let priceDomain;
|
|
let volumeDomain;
|
|
for (const s of series) {
|
|
const domainResult = s.getDomain("y" /* Y */);
|
|
const yDomain = domainResult?.domain;
|
|
if (!Array.isArray(yDomain) || yDomain.length < 2)
|
|
continue;
|
|
if (s.type === "bar") {
|
|
volumeDomain = [yDomain[0], yDomain.at(-1)];
|
|
} else {
|
|
priceDomain = [yDomain[0], yDomain.at(-1)];
|
|
}
|
|
}
|
|
for (const label of this.labels) {
|
|
const key = this[label.key];
|
|
if (key == null) {
|
|
label.domain = void 0;
|
|
continue;
|
|
}
|
|
label.domain = label.key === "volumeKey" ? volumeDomain : priceDomain;
|
|
}
|
|
}
|
|
startPerformLayout({ layoutBox }) {
|
|
this.labelGroup.translationX = 0;
|
|
this.labelGroup.translationY = 0;
|
|
if (!this.enabled) {
|
|
this.labelGroup.visible = false;
|
|
return;
|
|
}
|
|
this.updateDomainsFromSeries();
|
|
const innerSpacing = 4;
|
|
const outerSpacing = 12;
|
|
const spacingAbove = 0;
|
|
const spacingBelow = 8;
|
|
this.labelGroup.translationY = layoutBox.y + spacingAbove;
|
|
const maxFontSize = Math.max(this.title.fontSize, this.positive.fontSize, this.negative.fontSize);
|
|
const lineHeight = calcLineHeight(maxFontSize);
|
|
const labelConfigurations = chartConfigurations[this.getChartType()] ?? 0;
|
|
let left = 0;
|
|
let offsetTop;
|
|
let textVAlign = "alphabetic";
|
|
if (this.layoutStyle === "block") {
|
|
layoutBox.shrink(spacingAbove + lineHeight + spacingBelow, "top");
|
|
offsetTop = maxFontSize + (lineHeight - maxFontSize) / 2;
|
|
} else {
|
|
const { title } = this.ctx.chartService;
|
|
textVAlign = "top";
|
|
offsetTop = spacingAbove + title.padding;
|
|
if (title.enabled) {
|
|
const titleBox = title.node.getBBox();
|
|
left = titleBox.x + titleBox.width + outerSpacing;
|
|
} else {
|
|
left = title.padding;
|
|
}
|
|
}
|
|
for (const { label, configuration, title, value, domain, formatter: formatter2 } of this.labels) {
|
|
if (domain == null || (labelConfigurations & configuration) === 0) {
|
|
title.visible = false;
|
|
value.visible = false;
|
|
continue;
|
|
}
|
|
const positiveTextMeasurer = cachedTextMeasurer(this.positive);
|
|
const negativeTextMeasurer = cachedTextMeasurer(this.negative);
|
|
const maxValueWidth = Math.max(
|
|
positiveTextMeasurer.textWidth(formatter2.format(domain[0])),
|
|
positiveTextMeasurer.textWidth(formatter2.format(domain[1])),
|
|
negativeTextMeasurer.textWidth(formatter2.format(domain[0])),
|
|
negativeTextMeasurer.textWidth(formatter2.format(domain[1]))
|
|
);
|
|
title.visible = true;
|
|
value.visible = true;
|
|
const titleMetrics = cachedTextMeasurer(this.title).measureLines(label);
|
|
title.setFont(this.title);
|
|
title.fill = this.title.color;
|
|
title.text = label;
|
|
title.textBaseline = textVAlign;
|
|
title.y = offsetTop;
|
|
title.x = left;
|
|
left += titleMetrics.width + innerSpacing;
|
|
value.textBaseline = textVAlign;
|
|
value.y = offsetTop;
|
|
value.x = left;
|
|
left += maxValueWidth + outerSpacing;
|
|
}
|
|
this.backgroundNode.x = 0;
|
|
this.backgroundNode.y = 0;
|
|
this.backgroundNode.width = left - outerSpacing;
|
|
this.backgroundNode.height = lineHeight + spacingAbove + spacingBelow;
|
|
this.backgroundNode.fill = this.background.fill;
|
|
this.backgroundNode.fillOpacity = this.background.fillOpacity;
|
|
}
|
|
onLayoutComplete(opts) {
|
|
this.labelGroup.translationX = opts.series.rect.x;
|
|
this.updateHighlight();
|
|
}
|
|
updateHighlight() {
|
|
if (!this.enabled)
|
|
return;
|
|
const activeHighlight = this.highlightManager.getActiveHighlight();
|
|
const datum = activeHighlight?.datum ?? this.chartData?.data?.at(-1);
|
|
if (datum == null) {
|
|
this.labelGroup.visible = false;
|
|
return;
|
|
}
|
|
this.labelGroup.visible = true;
|
|
const itemId = activeHighlight?.itemId;
|
|
let baseStyle = itemId == null ? void 0 : itemIdMap[itemId];
|
|
if (baseStyle == null && this.openKey != null && this.closeKey != null) {
|
|
if (datum[this.openKey] < datum[this.closeKey]) {
|
|
baseStyle = "positive";
|
|
} else {
|
|
baseStyle = "negative";
|
|
}
|
|
}
|
|
for (const { domain, value, key, formatter: formatter2, style: style2 } of this.labels) {
|
|
if (domain == null)
|
|
continue;
|
|
let labelStyle = style2 ?? baseStyle ?? "neutral";
|
|
if (labelStyle === "neutral") {
|
|
labelStyle = neutralColorMap[this.getChartType()] ?? labelStyle;
|
|
}
|
|
const datumKey = this[key];
|
|
const datumValue = datumKey == null ? void 0 : datum?.[datumKey];
|
|
value.setFont(this[labelStyle]);
|
|
value.fill = this[labelStyle].color;
|
|
value.text = typeof datumValue === "number" ? formatter2.format(datumValue) : "";
|
|
}
|
|
}
|
|
getChartType() {
|
|
let chartType = this.ctx.chartService.publicApi?.getOptions()?.chartType;
|
|
if (chartType == null || chartConfigurations[chartType] == null) {
|
|
chartType = "candlestick";
|
|
}
|
|
return chartType;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBar.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBar.prototype, "openKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBar.prototype, "highKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBar.prototype, "lowKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBar.prototype, "closeKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBar.prototype, "volumeKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBar.prototype, "title", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBar.prototype, "positive", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBar.prototype, "negative", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBar.prototype, "neutral", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBar.prototype, "altNeutral", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBar.prototype, "background", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], StatusBar.prototype, "layoutStyle", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/status-bar/statusBarModule.ts
|
|
var StatusBarModule = {
|
|
type: "plugin",
|
|
name: "statusBar",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
themeTemplate: {
|
|
enabled: false,
|
|
layoutStyle: DEFAULT_CAPTION_LAYOUT_STYLE,
|
|
title: {
|
|
color: { $ref: "textColor" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontWeight: { $ref: "fontWeight" }
|
|
},
|
|
positive: {
|
|
color: { $palette: "up.stroke" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontWeight: { $ref: "fontWeight" }
|
|
},
|
|
negative: {
|
|
color: { $palette: "down.stroke" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontWeight: { $ref: "fontWeight" }
|
|
},
|
|
neutral: {
|
|
color: { $palette: "neutral.stroke" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontWeight: { $ref: "fontWeight" }
|
|
},
|
|
background: {
|
|
fill: { $ref: "chartBackgroundColor" },
|
|
fillOpacity: 0.5
|
|
},
|
|
altNeutral: {
|
|
color: "gray",
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontWeight: { $ref: "fontWeight" }
|
|
}
|
|
},
|
|
create: (ctx) => new StatusBar(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/sync/chartSync.ts
|
|
var { CartesianAxis: CartesianAxis2, ContinuousScale: ContinuousScale4, TimeScale: TimeScale2, UnitTimeScale: UnitTimeScale2, TooltipManager: TooltipManager2 } = module_support_exports;
|
|
var debug5 = debugLogger_exports.create("sync");
|
|
function getDirectionKeys(series, primary, secondary) {
|
|
const primaryKeys = series.getKeys(primary);
|
|
const secondaryKeys = series.getKeys(secondary);
|
|
if (series.shouldFlipXY?.()) {
|
|
return [secondaryKeys, primaryKeys];
|
|
}
|
|
return [primaryKeys, secondaryKeys];
|
|
}
|
|
function syncedDirections(axes = "x") {
|
|
switch (axes) {
|
|
case "x":
|
|
return ["x" /* X */];
|
|
case "y":
|
|
return ["y" /* Y */];
|
|
case "xy":
|
|
return ["x" /* X */, "y" /* Y */];
|
|
}
|
|
}
|
|
function domainChanged(scale2, a, b) {
|
|
if (TimeScale2.is(scale2) || UnitTimeScale2.is(scale2)) {
|
|
return !arraysEqual(
|
|
a.map((x) => x?.valueOf()),
|
|
b.map((x) => x?.valueOf())
|
|
);
|
|
} else {
|
|
return !arraysEqual(a, b);
|
|
}
|
|
}
|
|
var ChartSync = class extends BaseProperties {
|
|
constructor(moduleContext) {
|
|
super();
|
|
this.moduleContext = moduleContext;
|
|
this.enabled = false;
|
|
this.axes = "x";
|
|
this.nodeInteraction = true;
|
|
this.zoom = true;
|
|
this.domainMode = "id";
|
|
this.domainSync = new AsyncAwaitQueue();
|
|
}
|
|
updateSiblings(groupId) {
|
|
const { syncManager } = this.moduleContext;
|
|
for (const chart of syncManager.getGroupSiblings(groupId ?? this.groupId)) {
|
|
debug5("ChartSync.updateSiblings()", chart.id, chart);
|
|
this.updateChart(chart);
|
|
}
|
|
}
|
|
updateChart(chart, updateType = 3 /* PROCESS_DOMAIN */) {
|
|
debug5("ChartSync.updateChart()", chart.id, ChartUpdateType[updateType], chart);
|
|
if (updateType === 3 /* PROCESS_DOMAIN */) {
|
|
chart.ctx.updateService.update(updateType, { forceNodeDataRefresh: true });
|
|
} else {
|
|
chart.ctx.updateService.update(updateType);
|
|
}
|
|
}
|
|
enabledZoomSync() {
|
|
const { eventsHub } = this.moduleContext;
|
|
this.disableZoomSync?.();
|
|
this.disableZoomSync = eventsHub.on("zoom:change-complete", (e) => this.onZoom(e));
|
|
}
|
|
onZoom(e) {
|
|
const { syncManager } = this.moduleContext;
|
|
for (const chart of syncManager.getGroupSiblings(this.groupId)) {
|
|
const syncModule = chart.modulesManager.getModule("sync");
|
|
if (!syncModule?.zoom)
|
|
continue;
|
|
const zoomModule = chart.modulesManager.getModule("zoom");
|
|
if (!zoomModule)
|
|
continue;
|
|
const zoom = this.prepareZoomUpdate();
|
|
if (e.source !== "sync") {
|
|
debug5("ChartsSyncManager.enabledZoomSync()", chart.id, zoom);
|
|
zoomModule.updateSyncZoom(zoom);
|
|
}
|
|
}
|
|
}
|
|
enabledNodeInteractionSync() {
|
|
this.disableNodeInteractionSync?.();
|
|
const offHighlightChange = this.moduleContext.eventsHub.on(
|
|
"highlight:change",
|
|
this.onHighlightChange.bind(this)
|
|
);
|
|
const offActiveChangeListener = this.moduleContext.eventsHub.on(
|
|
"active:load-memento",
|
|
this.onActiveLoadMemento.bind(this)
|
|
);
|
|
this.disableNodeInteractionSync = () => {
|
|
offHighlightChange();
|
|
offActiveChangeListener();
|
|
};
|
|
}
|
|
onHighlightChange(event) {
|
|
const { syncManager } = this.moduleContext;
|
|
if (event.callerId.endsWith("-sync"))
|
|
return;
|
|
debug5("ChartSync.onHighlightChange()", event);
|
|
const series = event.currentHighlight?.series;
|
|
const [mainDirection] = syncedDirections(this.axes);
|
|
const secondaryDirection = mainDirection === "x" /* X */ ? "y" /* Y */ : "x" /* X */;
|
|
const [primaryKeys, secondaryKeys] = series ? getDirectionKeys(series, mainDirection, secondaryDirection) : [];
|
|
const datum = readDatum(event.currentHighlight);
|
|
let eventValue = primaryKeys?.[0] ? datum?.[primaryKeys[0]] : void 0;
|
|
let valueIsDate = false;
|
|
if (isDate(eventValue)) {
|
|
valueIsDate = true;
|
|
eventValue = eventValue.getTime();
|
|
}
|
|
if (!event.currentHighlight?.datum) {
|
|
for (const chart of syncManager.getGroupSiblings(this.groupId)) {
|
|
const syncModule = chart.modulesManager.getModule("sync");
|
|
if (!syncModule?.nodeInteraction)
|
|
continue;
|
|
chart.ctx.highlightManager.updateHighlight(`${chart.id}-sync`, void 0, true);
|
|
chart.ctx.tooltipManager.removeTooltip(`${chart.id}-sync`, void 0, true);
|
|
}
|
|
return;
|
|
}
|
|
const useSecondaryDirectionKey = syncManager.getGroupSyncMode(this.groupId) === "multi-series";
|
|
this.findMatchingHighlightNodes(
|
|
mainDirection,
|
|
secondaryDirection,
|
|
useSecondaryDirectionKey ? secondaryKeys : [],
|
|
valueIsDate,
|
|
eventValue,
|
|
event
|
|
);
|
|
}
|
|
onActiveLoadMemento(event) {
|
|
const { activeItem, chartId } = event;
|
|
if (activeItem === void 0) {
|
|
this.moduleContext.highlightManager.updateHighlight(`${chartId}-sync`, void 0, false);
|
|
this.moduleContext.tooltipManager.removeTooltip(`${chartId}-sync`, void 0, false);
|
|
for (const chart of this.moduleContext.syncManager.getGroupSiblings(this.groupId)) {
|
|
chart.onSyncActiveClear();
|
|
}
|
|
}
|
|
}
|
|
findMatchingHighlightNodes(primaryDirection, secondaryDirection, secondaryKeys, valueIsDate, eventValue, event) {
|
|
const { syncManager } = this.moduleContext;
|
|
debug5("ChartSync.findMatchingHighlightNodes()", {
|
|
mainDirection: primaryDirection,
|
|
secondaryKeys
|
|
});
|
|
for (const chart of syncManager.getGroupSiblings(this.groupId)) {
|
|
const syncModule = chart.modulesManager.getModule("sync");
|
|
if (!syncModule?.nodeInteraction)
|
|
continue;
|
|
let dispatched = false;
|
|
for (const axis of chart.axes) {
|
|
if (!CartesianAxis2.is(axis) || axis.direction !== primaryDirection)
|
|
continue;
|
|
const matchingNodes = chart.series.filter((s) => {
|
|
if (!s.visible)
|
|
return false;
|
|
if (secondaryKeys.length > 0) {
|
|
const [, seriesKeys] = getDirectionKeys(s, primaryDirection, secondaryDirection);
|
|
return secondaryKeys.every((key) => seriesKeys.includes(key));
|
|
}
|
|
return true;
|
|
}).map(this.findMatchingNodes(axis, primaryDirection, valueIsDate, eventValue)).filter(isDefined);
|
|
if (matchingNodes.length === 1 && matchingNodes[0]?.nodeDatum !== chart.ctx.highlightManager.getActiveHighlight()) {
|
|
this.dispatchHighlightUpdate(chart, matchingNodes[0].nodeDatum);
|
|
dispatched = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!dispatched) {
|
|
debug5("ChartSync.findMatchingHighlightNodes() - no matching nodes", chart.id, event);
|
|
this.dispatchHighlightUpdate(chart);
|
|
}
|
|
}
|
|
}
|
|
findMatchingNodes(axis, mainDirection, valueIsDate, eventValue) {
|
|
return (series) => {
|
|
const seriesKeyAxis = series.getKeyAxis(axis.direction);
|
|
if (seriesKeyAxis !== axis.id)
|
|
return;
|
|
const nodeData = series.contextNodeData?.nodeData ?? [];
|
|
if (!nodeData?.length)
|
|
return;
|
|
const firstNode = nodeData[0];
|
|
const mainDirectionKey = `${mainDirection}Key`;
|
|
if (!isObjectWithStringProperty(firstNode, mainDirectionKey))
|
|
return;
|
|
const valueKey = firstNode[mainDirectionKey];
|
|
const nodeDatum = nodeData.find((datum) => {
|
|
const nodeValue = datum.datum[valueKey];
|
|
return valueIsDate ? nodeValue.getTime() === eventValue : nodeValue === eventValue;
|
|
});
|
|
return nodeDatum ? { series, nodeDatum } : null;
|
|
};
|
|
}
|
|
dispatchHighlightUpdate(chart, nodeDatum) {
|
|
debug5("ChartSync.dispatchHighlightUpdate()", chart.id, nodeDatum);
|
|
const delayed = nodeDatum == null;
|
|
chart.ctx.highlightManager.updateHighlight(`${chart.id}-sync`, nodeDatum, delayed);
|
|
const tooltipEnabled = nodeDatum?.series.tooltipEnabled ?? chart.tooltip.enabled;
|
|
if (nodeDatum && tooltipEnabled) {
|
|
const bbox = chart.seriesAreaBoundingBox;
|
|
const canvasX = bbox.x + (nodeDatum.midPoint?.x ?? nodeDatum.point?.x ?? 0);
|
|
const canvasY = bbox.y + (nodeDatum.midPoint?.y ?? nodeDatum.point?.y ?? 0);
|
|
const tooltipMeta = TooltipManager2.makeTooltipMeta(
|
|
{ type: "pointermove", canvasX, canvasY },
|
|
nodeDatum.series,
|
|
nodeDatum,
|
|
void 0
|
|
);
|
|
chart.ctx.tooltipManager.updateTooltip(
|
|
`${chart.id}-sync`,
|
|
tooltipMeta,
|
|
chart.getTooltipContent(nodeDatum.series, nodeDatum.datumIndex, nodeDatum, "tooltip")
|
|
);
|
|
} else {
|
|
chart.ctx.tooltipManager.removeTooltip(`${chart.id}-sync`, void 0, true);
|
|
}
|
|
this.updateChart(chart, 7 /* SERIES_UPDATE */);
|
|
}
|
|
async getSyncedDomain(axis) {
|
|
if (!CartesianAxis2.is(axis) || this.axes !== "xy" && this.axes !== axis.direction) {
|
|
return;
|
|
}
|
|
const { groupState, directionDomains, idDomains, positionDomains } = this.updateDomainState(axis);
|
|
this.validateAxis(axis, groupState);
|
|
await this.waitForDomainsToBeReady();
|
|
if (this.domainMode === "position") {
|
|
return this.calculateDerivedDomain(axis, positionDomains);
|
|
}
|
|
if (this.domainMode === "direction") {
|
|
return this.calculateDerivedDomain(axis, directionDomains);
|
|
}
|
|
return this.calculateDerivedDomain(axis, idDomains);
|
|
}
|
|
updateDomainState(axis) {
|
|
var _a, _b, _c, _d, _e;
|
|
const { syncManager } = this.moduleContext;
|
|
const chartId = syncManager.getChart().id;
|
|
const axisId = axis.id;
|
|
const groupState = syncManager.getGroupState(this.groupId);
|
|
if (!groupState)
|
|
throw new Error("AG Charts - no GroupState for groupId: " + this.groupId);
|
|
const domainsByDirection = groupState.domains ?? (groupState.domains = {});
|
|
const directionDomains = domainsByDirection[_a = axis.direction] ?? (domainsByDirection[_a] = { derived: [], sources: {}, dirty: true });
|
|
const chartDirectionDomains = (_b = directionDomains.sources)[chartId] ?? (_b[chartId] = {});
|
|
chartDirectionDomains[axisId] = axis.dataDomain.domain;
|
|
directionDomains.dirty = true;
|
|
const domainsById = groupState.domainsById ?? (groupState.domainsById = {});
|
|
const idDomains = domainsById[axisId] ?? (domainsById[axisId] = { derived: [], sources: {}, dirty: true });
|
|
const chartIdDomains = (_c = idDomains.sources)[chartId] ?? (_c[chartId] = {});
|
|
chartIdDomains[axisId] = axis.dataDomain.domain;
|
|
idDomains.dirty = true;
|
|
const domainsByPosition = groupState.domainsByPosition ?? (groupState.domainsByPosition = {});
|
|
const positionDomains = domainsByPosition[_d = axis.position] ?? (domainsByPosition[_d] = { derived: [], sources: {}, dirty: true });
|
|
const chartPositionDomains = (_e = positionDomains.sources)[chartId] ?? (_e[chartId] = {});
|
|
chartPositionDomains[axisId] = axis.dataDomain.domain;
|
|
positionDomains.dirty = true;
|
|
return { groupState, directionDomains, idDomains, positionDomains };
|
|
}
|
|
validateAxis(axis, groupState) {
|
|
const multiSeries = this.moduleContext.syncManager.getGroupSyncMode(this.groupId) === "multi-series";
|
|
if (!syncedDirections(this.axes).includes(axis.direction))
|
|
return;
|
|
if (multiSeries) {
|
|
this.validateMultiSeries(axis, groupState);
|
|
} else {
|
|
this.validateSingleSeries(axis, groupState);
|
|
}
|
|
}
|
|
validateMultiSeries(axis, groupState) {
|
|
const { min, max, nice, reverse } = axis;
|
|
const matchingKeys = new Set(axis.boundSeries.flatMap((s) => s.getKeys(axis.direction)));
|
|
for (const member of groupState.members) {
|
|
const { axes, modulesManager } = member;
|
|
const syncModule = modulesManager.getModule("sync");
|
|
const memberSyncDirections = syncedDirections(syncModule?.axes);
|
|
const keyMatchedAxes = axes.filter((a) => memberSyncDirections.includes(a.direction)).filter((a) => a.boundSeries.some((s) => s.getKeys(a.direction).some((k) => matchingKeys.has(k))));
|
|
if (keyMatchedAxes.length === 0)
|
|
continue;
|
|
const [firstAxis] = keyMatchedAxes;
|
|
if (firstAxis.min !== min || firstAxis.max !== max || firstAxis.nice !== nice || firstAxis.reverse !== reverse) {
|
|
logger_exports.warnOnce(
|
|
"To allow synchronization, ensure that all synchronized axes with matching keys have matching min, max, nice, and reverse properties."
|
|
);
|
|
this.enabled = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
validateSingleSeries(axis, groupState) {
|
|
const members = groupState.members;
|
|
const [{ axes: syncAxes }] = members;
|
|
const { direction, min, max, nice, reverse } = axis;
|
|
for (const nextAxis of syncAxes) {
|
|
if (direction !== nextAxis.direction)
|
|
continue;
|
|
if (nice !== nextAxis.nice || reverse !== nextAxis.reverse || min !== nextAxis.min && (isFiniteNumber(min) || isFiniteNumber(nextAxis.min)) || max !== nextAxis.max && (isFiniteNumber(max) || isFiniteNumber(nextAxis.max))) {
|
|
logger_exports.warnOnce(
|
|
"To allow synchronization, ensure that all charts have matching min, max, nice, and reverse properties on the synchronized axes."
|
|
);
|
|
this.enabled = false;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
calculateDerivedDomain(axis, domains) {
|
|
if (!domains.dirty)
|
|
return domains.derived;
|
|
let previousDerived = domains.derived;
|
|
const newDerivedBySource = Object.values(domains.sources).map((d) => Object.values(d));
|
|
let newDerived;
|
|
if (ContinuousScale4.is(axis.scale)) {
|
|
newDerived = newDerivedBySource.flat(2);
|
|
} else {
|
|
newDerived = newDerivedBySource.flat().toSorted((a, b) => a.length > b.length ? -1 : 1).flat();
|
|
}
|
|
domains.derived = unique(newDerived);
|
|
if (ContinuousScale4.is(axis.scale)) {
|
|
previousDerived = findMinMax(previousDerived);
|
|
domains.derived = findMinMax(domains.derived);
|
|
}
|
|
domains.dirty = false;
|
|
if (domainChanged(axis.scale, previousDerived, domains.derived)) {
|
|
debug5(axis.id, "updated", { before: previousDerived, after: domains.derived });
|
|
this.updateSiblings();
|
|
}
|
|
return domains.derived;
|
|
}
|
|
removeAxis(axis) {
|
|
if (!CartesianAxis2.is(axis) || this.axes !== "xy" && this.axes !== axis.direction) {
|
|
return;
|
|
}
|
|
const { syncManager } = this.moduleContext;
|
|
const syncGroup = syncManager.getGroupState(this.groupId);
|
|
const chartId = syncManager.getChart().id;
|
|
const axisId = axis.id;
|
|
delete syncGroup?.domains?.[axis.direction]?.sources?.[chartId]?.[axisId];
|
|
delete syncGroup?.domainsByPosition?.[axis.position]?.sources?.[chartId]?.[axisId];
|
|
delete syncGroup?.domainsById?.[axisId]?.sources?.[chartId]?.[axisId];
|
|
}
|
|
async waitForDomainsToBeReady() {
|
|
const { syncManager } = this.moduleContext;
|
|
let count = 0;
|
|
while (syncManager.getGroupMembers(this.groupId).some((c) => c.syncStatus === "init")) {
|
|
debug5("ChartSync.waitForDomainsToBeReady() - waiting for all domains to be calculated", this.groupId);
|
|
await this.domainSync.waitForCompletion();
|
|
count++;
|
|
}
|
|
if (count > 0) {
|
|
debug5("ChartSync.waitForDomainsToBeReady() - waited for", count, "iterations");
|
|
}
|
|
this.domainSync.notify();
|
|
}
|
|
prepareZoomUpdate() {
|
|
const { zoomManager } = this.moduleContext;
|
|
const zoom = zoomManager.getZoom();
|
|
if (this.axes === "x") {
|
|
delete zoom?.y;
|
|
} else if (this.axes === "y") {
|
|
delete zoom?.x;
|
|
}
|
|
return definedZoomState(zoom);
|
|
}
|
|
onEnabledChange() {
|
|
const { syncManager, highlightManager } = this.moduleContext;
|
|
if (this.enabled) {
|
|
syncManager.subscribe(this.groupId);
|
|
highlightManager.unhighlightDelay = 0;
|
|
} else {
|
|
syncManager.unsubscribe(this.groupId);
|
|
highlightManager.unhighlightDelay = 100;
|
|
}
|
|
this.updateSiblings();
|
|
this.onNodeInteractionChange();
|
|
this.onZoomChange();
|
|
}
|
|
onGroupIdChange(newValue, oldValue) {
|
|
if (!this.enabled || newValue === oldValue)
|
|
return;
|
|
const { syncManager } = this.moduleContext;
|
|
syncManager.unsubscribe(oldValue);
|
|
syncManager.subscribe(newValue);
|
|
this.updateSiblings(oldValue);
|
|
this.updateSiblings(newValue);
|
|
}
|
|
onAxesChange() {
|
|
if (!this.enabled)
|
|
return;
|
|
const { syncManager } = this.moduleContext;
|
|
this.updateChart(syncManager.getChart());
|
|
}
|
|
onNodeInteractionChange() {
|
|
if (this.enabled && this.nodeInteraction) {
|
|
this.enabledNodeInteractionSync();
|
|
} else {
|
|
this.disableNodeInteractionSync?.();
|
|
}
|
|
}
|
|
onZoomChange() {
|
|
if (this.enabled && this.zoom) {
|
|
this.enabledZoomSync();
|
|
} else {
|
|
this.disableZoomSync?.();
|
|
}
|
|
}
|
|
destroy() {
|
|
const { syncManager } = this.moduleContext;
|
|
syncManager.unsubscribe(this.groupId);
|
|
this.updateSiblings();
|
|
this.disableZoomSync?.();
|
|
}
|
|
};
|
|
ChartSync.className = "Sync";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ObserveChanges((target) => target.onEnabledChange())
|
|
], ChartSync.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ObserveChanges((target, newValue, oldValue) => target.onGroupIdChange(newValue, oldValue))
|
|
], ChartSync.prototype, "groupId", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ObserveChanges((target) => target.onAxesChange())
|
|
], ChartSync.prototype, "axes", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ObserveChanges((target) => target.onNodeInteractionChange())
|
|
], ChartSync.prototype, "nodeInteraction", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ObserveChanges((target) => target.onZoomChange())
|
|
], ChartSync.prototype, "zoom", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ObserveChanges((target) => target.onAxesChange())
|
|
], ChartSync.prototype, "domainMode", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/sync/syncModule.ts
|
|
var SyncModule = {
|
|
type: "plugin",
|
|
name: "sync",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: {
|
|
enabled: boolean,
|
|
groupId: string,
|
|
axes: union("x", "y", "xy"),
|
|
nodeInteraction: boolean,
|
|
zoom: boolean
|
|
},
|
|
themeTemplate: { enabled: false },
|
|
create: (ctx) => new ChartSync(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/scenes/zoomRect.ts
|
|
var VALID_COLOR = "#2196f3";
|
|
var INVALID_COLOR = "#8a8a8a";
|
|
var ZoomRect = class extends module_support_exports.Rect {
|
|
constructor() {
|
|
super();
|
|
this.fill = VALID_COLOR;
|
|
this.fillOpacity = 0.2;
|
|
this.zIndex = 5 /* ZOOM_SELECTION */;
|
|
}
|
|
updateValid() {
|
|
this.fill = VALID_COLOR;
|
|
}
|
|
updateInvalid() {
|
|
this.fill = INVALID_COLOR;
|
|
}
|
|
};
|
|
ZoomRect.className = "ZoomRect";
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomAutoScale.ts
|
|
var ZoomAutoScalingProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super();
|
|
this.enabled = false;
|
|
this.padding = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ZoomAutoScalingProperties.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ZoomAutoScalingProperties.prototype, "padding", 2);
|
|
var ZoomAutoScaler = class {
|
|
constructor(properties, zoomManager, deps, eventsHub, eventsCleanup) {
|
|
this.properties = properties;
|
|
this.zoomManager = zoomManager;
|
|
this.deps = deps;
|
|
this.manuallyAdjusted = false;
|
|
eventsCleanup.register(
|
|
eventsHub.on("zoom:save-memento", (e) => this.onSaveMemento(e)),
|
|
eventsHub.on("zoom:load-memento", (e) => this.onLoadMemento(e)),
|
|
eventsHub.on("zoom:change-request", (e) => this.onChangeRequest(e))
|
|
);
|
|
}
|
|
get enabled() {
|
|
return this.deps.enabled && this.properties.enabled && !this.manuallyAdjusted;
|
|
}
|
|
onManualAdjustment(direction) {
|
|
if (direction === "y" /* Y */) {
|
|
this.manuallyAdjusted = true;
|
|
}
|
|
}
|
|
onChangeRequest(event) {
|
|
const hasYAxisChange = this.hasYAxisChange(event);
|
|
if (event.sourceDetail === "scrollbar" && hasYAxisChange) {
|
|
this.manuallyAdjusted = true;
|
|
}
|
|
if (event.isReset && hasYAxisChange) {
|
|
this.manuallyAdjusted = false;
|
|
}
|
|
if (this.enabled) {
|
|
const constrainedZoom = this.autoScaleYZoom(event.state);
|
|
if (constrainedZoom) {
|
|
event.constrainChanges(constrainedZoom);
|
|
}
|
|
}
|
|
}
|
|
hasYAxisChange(event) {
|
|
for (const id of event.changedAxes) {
|
|
if (event.state[id]?.direction === "y" /* Y */) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
onSaveMemento(event) {
|
|
event.memento.autoScaledAxes = this.enabled ? ["y"] : void 0;
|
|
}
|
|
onLoadMemento(event) {
|
|
const { zoom, memento, navigatorModule, zoomModule } = event;
|
|
if (!navigatorModule || zoomModule) {
|
|
let yAutoScale = memento?.autoScaledAxes?.includes("y");
|
|
if (memento?.rangeY) {
|
|
yAutoScale ?? (yAutoScale = false);
|
|
zoom.y = this.zoomManager.rangeToRatioDirection("y" /* Y */, memento.rangeY) ?? {
|
|
min: 0,
|
|
max: 1
|
|
};
|
|
} else if (memento?.ratioY) {
|
|
yAutoScale ?? (yAutoScale = false);
|
|
zoom.y = {
|
|
min: memento.ratioY.start ?? 0,
|
|
max: memento.ratioY.end ?? 1
|
|
};
|
|
} else {
|
|
yAutoScale ?? (yAutoScale = true);
|
|
const autoZoomY = yAutoScale ? this.getAutoScaleYZoom(zoom.x) : void 0;
|
|
zoom.y = autoZoomY ?? { min: 0, max: 1 };
|
|
}
|
|
if (yAutoScale != void 0) {
|
|
this.manuallyAdjusted = !yAutoScale;
|
|
}
|
|
}
|
|
}
|
|
getAutoScaleYZoom(zoomX) {
|
|
if (!this.enabled)
|
|
return;
|
|
const { padding: padding2 } = this.properties;
|
|
let yZoom;
|
|
if (this.deps.enableIndependentAxes) {
|
|
yZoom = this.primaryAxisZoom("y" /* Y */, zoomX, { padding: padding2 });
|
|
} else {
|
|
yZoom = this.combinedAxisZoom("y" /* Y */, zoomX, { padding: padding2 });
|
|
}
|
|
if (zoomX.min === 0 && zoomX.max === 1) {
|
|
return yZoom == null ? void 0 : { min: 0, max: 1 };
|
|
} else {
|
|
return yZoom;
|
|
}
|
|
}
|
|
autoScaleYZoom(changes) {
|
|
const zoom = this.zoomManager.getZoom();
|
|
if (zoom && changes) {
|
|
const state = this.zoomManager.getAxisZooms();
|
|
for (const dir of ["x" /* X */, "y" /* Y */]) {
|
|
for (const id of strictObjectKeys(changes)) {
|
|
if (state[id]?.direction === dir) {
|
|
zoom[dir] = changes[id];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (zoom?.x == null)
|
|
return;
|
|
const zoomY = this.getAutoScaleYZoom(zoom.x);
|
|
if (zoomY == null || objectsEqual(zoom.y, zoomY))
|
|
return;
|
|
return this.zoomManager.toCoreZoomState({ x: zoom.x, y: zoomY });
|
|
}
|
|
zoomBounds(xAxis, yAxis, zoom, padding2) {
|
|
const xScale = xAxis.scale;
|
|
const xScaleRange = xScale.range;
|
|
xScale.range = [0, 1];
|
|
const yScale = yAxis.scale;
|
|
const yScaleRange = yScale.range;
|
|
yScale.range = [0, 1];
|
|
let min = 1;
|
|
let minPadding = false;
|
|
let max = 0;
|
|
let maxPadding = false;
|
|
for (const series of yAxis.boundSeries) {
|
|
if (!series.visible)
|
|
continue;
|
|
const { connectsToYAxis } = series;
|
|
const yValues = series.getRange("y" /* Y */, [zoom.min, zoom.max]);
|
|
for (const yValue of yValues) {
|
|
const y = yScale.convert(yValue);
|
|
if (!Number.isFinite(y))
|
|
continue;
|
|
if (y < min) {
|
|
min = y;
|
|
minPadding = !connectsToYAxis || yValue < 0;
|
|
}
|
|
if (y > max) {
|
|
max = y;
|
|
maxPadding = !connectsToYAxis || yValue > 0;
|
|
}
|
|
}
|
|
}
|
|
if (isFiniteNumber(yAxis.min)) {
|
|
min = 0;
|
|
}
|
|
if (isFiniteNumber(yAxis.max)) {
|
|
max = 1;
|
|
}
|
|
xScale.range = xScaleRange;
|
|
yScale.range = yScaleRange;
|
|
if (min >= max)
|
|
return;
|
|
const totalPadding = (minPadding ? padding2 : 0) + (maxPadding ? padding2 : 0);
|
|
const paddedDelta = Math.min((max - min) * (1 + totalPadding), 1);
|
|
if (paddedDelta <= 0)
|
|
return;
|
|
if (minPadding && maxPadding) {
|
|
const mid = (max + min) / 2;
|
|
min = mid - paddedDelta / 2;
|
|
max = mid + paddedDelta / 2;
|
|
} else if (!minPadding && maxPadding) {
|
|
max = min + paddedDelta;
|
|
} else if (minPadding && !maxPadding) {
|
|
min = max - paddedDelta;
|
|
}
|
|
if (min < 0) {
|
|
max += -min;
|
|
min = 0;
|
|
} else if (max > 1) {
|
|
min -= max - 1;
|
|
max = 1;
|
|
}
|
|
return { min, max };
|
|
}
|
|
primaryAxisZoom(direction, zoom, { padding: padding2 = 0 } = {}) {
|
|
const crossDirection = direction === "x" /* X */ ? "y" /* Y */ : "x" /* X */;
|
|
const xAxis = this.zoomManager.getPrimaryAxis(crossDirection);
|
|
const yAxis = this.zoomManager.getPrimaryAxis(direction);
|
|
if (xAxis == null || yAxis == null)
|
|
return;
|
|
return this.zoomBounds(xAxis, yAxis, zoom, padding2);
|
|
}
|
|
combinedAxisZoom(direction, zoom, { padding: padding2 = 0 } = {}) {
|
|
const axes = this.zoomManager.getAxes();
|
|
const crossDirection = direction === "x" /* X */ ? "y" /* Y */ : "x" /* X */;
|
|
const seriesXAxes = /* @__PURE__ */ new Map();
|
|
for (const xAxis of axes) {
|
|
if (xAxis.direction !== crossDirection)
|
|
continue;
|
|
for (const series of xAxis.boundSeries) {
|
|
seriesXAxes.set(series, xAxis);
|
|
}
|
|
}
|
|
let min = 1;
|
|
let max = 0;
|
|
for (const yAxis of axes) {
|
|
if (yAxis.direction !== direction)
|
|
continue;
|
|
for (const series of yAxis.boundSeries) {
|
|
const xAxis = seriesXAxes.get(series);
|
|
if (xAxis == null)
|
|
continue;
|
|
const bounds = this.zoomBounds(xAxis, yAxis, zoom, padding2);
|
|
if (bounds == null)
|
|
return;
|
|
min = Math.min(min, bounds.min);
|
|
max = Math.max(max, bounds.max);
|
|
}
|
|
}
|
|
const delta5 = 1e-6;
|
|
if (min < delta5)
|
|
min = 0;
|
|
if (max > 1 - delta5)
|
|
max = 1;
|
|
if (min > max)
|
|
return;
|
|
return { min, max };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomUtils.ts
|
|
var UNIT_SIZE = UNIT_MAX - UNIT_MIN;
|
|
var DEFAULT_ANCHOR_POINT_X = "end";
|
|
var DEFAULT_ANCHOR_POINT_Y = "middle";
|
|
var ZOOM_VALID_CHECK_DEBOUNCE = 300;
|
|
var constrain = (value, min = UNIT_MIN, max = UNIT_MAX) => clamp(min, value, max);
|
|
function dx(zoom) {
|
|
return zoom.x.max - zoom.x.min;
|
|
}
|
|
function dy(zoom) {
|
|
return zoom.y.max - zoom.y.min;
|
|
}
|
|
function isZoomRangeEqual(left, right) {
|
|
return isNumberEqual(left.min, right.min) && isNumberEqual(left.max, right.max);
|
|
}
|
|
function isZoomEqual(left, right) {
|
|
return isZoomRangeEqual(left.x, right.x) && isZoomRangeEqual(left.y, right.y);
|
|
}
|
|
function isMaxZoom(zoom) {
|
|
return isZoomEqual(zoom, definedZoomState());
|
|
}
|
|
function pointToRatio(bbox, x, y) {
|
|
if (!bbox)
|
|
return { x: 0, y: 0 };
|
|
const constrainedX = constrain(x - bbox.x, 0, bbox.x + bbox.width);
|
|
const constrainedY = constrain(y - bbox.y, 0, bbox.y + bbox.height);
|
|
const rx = 1 / bbox.width * constrainedX;
|
|
const ry = 1 - 1 / bbox.height * constrainedY;
|
|
return { x: constrain(rx), y: constrain(ry) };
|
|
}
|
|
function translateZoom(zoom, x, y) {
|
|
return {
|
|
x: { min: zoom.x.min + x, max: zoom.x.max + x },
|
|
y: { min: zoom.y.min + y, max: zoom.y.max + y }
|
|
};
|
|
}
|
|
function scaleZoom(zoom, sx, sy) {
|
|
return {
|
|
x: { min: zoom.x.min, max: zoom.x.min + dx(zoom) * sx },
|
|
y: { min: zoom.y.min, max: zoom.y.min + dy(zoom) * sy }
|
|
};
|
|
}
|
|
function scaleZoomCenter(zoom, sx, sy) {
|
|
const dx_ = dx(zoom);
|
|
const dy_ = dy(zoom);
|
|
const cx = zoom.x.min + dx_ / 2;
|
|
const cy = zoom.y.min + dy_ / 2;
|
|
return {
|
|
x: { min: cx - dx_ * sx / 2, max: cx + dx_ * sx / 2 },
|
|
y: { min: cy - dy_ * sy / 2, max: cy + dy_ * sy / 2 }
|
|
};
|
|
}
|
|
function scaleZoomAxisWithAnchor(newState, oldState, anchor, origin3) {
|
|
const { min, max } = oldState;
|
|
const center2 = min + (max - min) / 2;
|
|
const diff9 = newState.max - newState.min;
|
|
switch (anchor) {
|
|
case "start":
|
|
return { min, max: oldState.min + diff9 };
|
|
case "end":
|
|
return { min: oldState.max - diff9, max };
|
|
case "middle":
|
|
return { min: center2 - diff9 / 2, max: center2 + diff9 / 2 };
|
|
case "pointer":
|
|
return scaleZoomAxisWithPoint(newState, oldState, origin3 ?? center2);
|
|
default:
|
|
return { min, max };
|
|
}
|
|
}
|
|
function scaleZoomAxisWithPoint(newState, oldState, origin3) {
|
|
const newDelta = newState.max - newState.min;
|
|
const oldDelta = oldState.max - oldState.min;
|
|
const scaledOrigin = origin3 * (1 - (oldDelta - newDelta));
|
|
const translation = origin3 - scaledOrigin;
|
|
const min = newState.min + translation;
|
|
const max = newState.max + translation;
|
|
return { min, max };
|
|
}
|
|
function multiplyZoom(zoom, nx, ny) {
|
|
return {
|
|
x: { min: zoom.x.min * nx, max: zoom.x.max * nx },
|
|
y: { min: zoom.y.min * ny, max: zoom.y.max * ny }
|
|
};
|
|
}
|
|
function constrainZoom(zoom) {
|
|
return {
|
|
x: constrainAxis(zoom.x),
|
|
y: constrainAxis(zoom.y)
|
|
};
|
|
}
|
|
function constrainAxis(axis) {
|
|
const size = axis.max - axis.min;
|
|
let min = axis.max > UNIT_MAX ? UNIT_MAX - size : axis.min;
|
|
let max = axis.min < UNIT_MIN ? size : axis.max;
|
|
min = Math.max(UNIT_MIN, min);
|
|
max = Math.min(UNIT_MAX, max);
|
|
return { min, max };
|
|
}
|
|
function canResetZoom(zoomManager) {
|
|
const current = zoomManager.getCoreZoom();
|
|
const restore = zoomManager.getRestoredZoom();
|
|
return jsonDiff(current, restore) != null;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomAxisDragger.ts
|
|
var ZoomAxisDragger = class {
|
|
update(event, direction, anchor, bbox, zoom, axisZoom) {
|
|
this.oldZoom ?? (this.oldZoom = definedZoomState(
|
|
direction === "x" /* X */ ? { ...zoom, x: axisZoom } : { ...zoom, y: axisZoom }
|
|
));
|
|
this.updateCoords(event.offsetX, event.offsetY);
|
|
return this.updateZoom(direction, anchor, bbox);
|
|
}
|
|
stop() {
|
|
this.coords = void 0;
|
|
this.oldZoom = void 0;
|
|
}
|
|
updateCoords(x, y) {
|
|
if (this.coords) {
|
|
this.coords.x2 = x;
|
|
this.coords.y2 = y;
|
|
} else {
|
|
this.coords = { x1: x, y1: y, x2: x, y2: y };
|
|
}
|
|
}
|
|
updateZoom(direction, anchor, bbox) {
|
|
const { coords, oldZoom } = this;
|
|
let newZoom = definedZoomState(oldZoom);
|
|
if (!coords || !oldZoom) {
|
|
if (direction === "x" /* X */)
|
|
return newZoom.x;
|
|
return newZoom.y;
|
|
}
|
|
const origin3 = pointToRatio(bbox, coords.x1, coords.y1);
|
|
const target = pointToRatio(bbox, coords.x2, coords.y2);
|
|
if (direction === "x" /* X */) {
|
|
const scaleX = (target.x - origin3.x) * dx(oldZoom);
|
|
newZoom.x.max += scaleX;
|
|
newZoom.x = scaleZoomAxisWithAnchor(newZoom.x, oldZoom.x, anchor, origin3.x);
|
|
newZoom = constrainZoom(newZoom);
|
|
return newZoom.x;
|
|
}
|
|
const scaleY = (target.y - origin3.y) * dy(oldZoom);
|
|
newZoom.y.max -= scaleY;
|
|
newZoom.y = scaleZoomAxisWithAnchor(newZoom.y, oldZoom.y, anchor, origin3.y);
|
|
newZoom = constrainZoom(newZoom);
|
|
return newZoom.y;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomContextMenu.ts
|
|
var { userInteraction: userInteraction3 } = module_support_exports;
|
|
var ZoomContextMenu = class {
|
|
constructor(eventsHub, contextMenuRegistry, zoomManager, getModuleProperties, getRect, updateZoom, isZoomValid) {
|
|
this.eventsHub = eventsHub;
|
|
this.contextMenuRegistry = contextMenuRegistry;
|
|
this.zoomManager = zoomManager;
|
|
this.getModuleProperties = getModuleProperties;
|
|
this.getRect = getRect;
|
|
this.updateZoom = updateZoom;
|
|
this.isZoomValid = isZoomValid;
|
|
}
|
|
registerActions(enabled) {
|
|
const { contextMenuRegistry } = this;
|
|
const action = enabled ? "show" : "hide";
|
|
contextMenuRegistry.toggle("zoom-to-cursor", action);
|
|
contextMenuRegistry.toggle("pan-to-cursor", action);
|
|
contextMenuRegistry.toggle("reset-zoom", action);
|
|
if (!enabled) {
|
|
return;
|
|
}
|
|
contextMenuRegistry.builtins.items["zoom-to-cursor"].action = this.onZoomToHere.bind(this);
|
|
contextMenuRegistry.builtins.items["pan-to-cursor"].action = this.onPanToHere.bind(this);
|
|
contextMenuRegistry.builtins.items["reset-zoom"].action = this.onResetZoom.bind(this);
|
|
const shouldEnableZoomToHere = (event) => {
|
|
const rect2 = this.getRect();
|
|
if (!rect2)
|
|
return true;
|
|
const origin3 = pointToRatio(rect2, event.x, event.y);
|
|
return this.iterateFindNextZoomAtPoint(origin3) != null;
|
|
};
|
|
const shouldEnablePanToHere = () => {
|
|
return !isMaxZoom(definedZoomState(this.zoomManager.getZoom()));
|
|
};
|
|
const removeListener = this.eventsHub.on("context-menu:setup", (event) => {
|
|
contextMenuRegistry.builtins.items["zoom-to-cursor"].enabled = shouldEnableZoomToHere(event);
|
|
contextMenuRegistry.builtins.items["pan-to-cursor"].enabled = shouldEnablePanToHere();
|
|
contextMenuRegistry.builtins.items["reset-zoom"].enabled = canResetZoom(this.zoomManager);
|
|
});
|
|
return () => {
|
|
removeListener();
|
|
contextMenuRegistry.toggle("zoom-to-cursor", "hide");
|
|
contextMenuRegistry.toggle("pan-to-cursor", "hide");
|
|
contextMenuRegistry.toggle("reset-zoom", "hide");
|
|
};
|
|
}
|
|
computeOrigin(event) {
|
|
const rect2 = this.getRect();
|
|
const { enabled } = this.getModuleProperties();
|
|
if (!enabled || !rect2 || !event?.target || !(event instanceof MouseEvent))
|
|
return;
|
|
const relativeRect = { x: 0, y: 0, width: rect2.width, height: rect2.height };
|
|
return pointToRatio(relativeRect, event.offsetX, event.offsetY);
|
|
}
|
|
onZoomToHere({ event }) {
|
|
const origin3 = this.computeOrigin(event);
|
|
if (!origin3)
|
|
return;
|
|
const zoom = this.iterateFindNextZoomAtPoint(origin3);
|
|
if (zoom == null)
|
|
return;
|
|
this.updateZoom(userInteraction3("contextmenu-zoom-to-cursor"), zoom);
|
|
}
|
|
onPanToHere({ event }) {
|
|
const origin3 = this.computeOrigin(event);
|
|
if (!origin3)
|
|
return;
|
|
const zoom = definedZoomState(this.zoomManager.getZoom());
|
|
const scaleX = dx(zoom);
|
|
const scaleY = dy(zoom);
|
|
const scaledOriginX = origin3.x * scaleX;
|
|
const scaledOriginY = origin3.y * scaleY;
|
|
const halfSize = UNIT_SIZE / 2;
|
|
let newZoom = {
|
|
x: { min: origin3.x - halfSize, max: origin3.x + halfSize },
|
|
y: { min: origin3.y - halfSize, max: origin3.y + halfSize }
|
|
};
|
|
newZoom = scaleZoomCenter(newZoom, scaleX, scaleY);
|
|
newZoom = translateZoom(newZoom, zoom.x.min - origin3.x + scaledOriginX, zoom.y.min - origin3.y + scaledOriginY);
|
|
this.updateZoom(userInteraction3("contextmenu-pan-to-cursor"), constrainZoom(newZoom));
|
|
}
|
|
onResetZoom(_actionEvent) {
|
|
this.zoomManager.resetZoom(userInteraction3("contextmenu-reset"));
|
|
}
|
|
iterateFindNextZoomAtPoint(origin3) {
|
|
const { scrollingStep } = this.getModuleProperties();
|
|
for (let i = scrollingStep; i <= 1 - scrollingStep; i += scrollingStep) {
|
|
const zoom = this.getNextZoomAtPoint(origin3, i);
|
|
if (this.isZoomValid(zoom)) {
|
|
return zoom;
|
|
}
|
|
}
|
|
}
|
|
getNextZoomAtPoint(origin3, step) {
|
|
const { isScalingX, isScalingY } = this.getModuleProperties();
|
|
const zoom = definedZoomState(this.zoomManager.getZoom());
|
|
const scaledOriginX = origin3.x * dx(zoom);
|
|
const scaledOriginY = origin3.y * dy(zoom);
|
|
const halfSize = UNIT_SIZE / 2;
|
|
let newZoom = {
|
|
x: { min: origin3.x - halfSize, max: origin3.x + halfSize },
|
|
y: { min: origin3.y - halfSize, max: origin3.y + halfSize }
|
|
};
|
|
newZoom = scaleZoomCenter(
|
|
newZoom,
|
|
isScalingX ? dx(zoom) * step : UNIT_SIZE,
|
|
isScalingY ? dy(zoom) * step : UNIT_SIZE
|
|
);
|
|
newZoom = translateZoom(newZoom, zoom.x.min - origin3.x + scaledOriginX, zoom.y.min - origin3.y + scaledOriginY);
|
|
return constrainZoom(newZoom);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomDOMProxy.ts
|
|
var ZoomDOMProxy = class {
|
|
constructor(axesHandlers) {
|
|
this.axesHandlers = axesHandlers;
|
|
this.axes = [];
|
|
this.overlappingAxisIds = /* @__PURE__ */ new Set();
|
|
}
|
|
destroy() {
|
|
for (const a of this.axes) {
|
|
a.div.destroy();
|
|
}
|
|
}
|
|
update(enabled, enableAxisDragging, enableAxisScrolling, ctx, seriesRect) {
|
|
this.seriesRect = seriesRect;
|
|
const disabled = !enabled || !enableAxisDragging && !enableAxisScrolling;
|
|
for (const ax of this.axes) {
|
|
ax.div.setHidden(disabled);
|
|
}
|
|
if (disabled)
|
|
return;
|
|
const { X, Y } = ChartAxisDirection;
|
|
const axesCtx = [...ctx.axisManager.getAxisContext(X), ...ctx.axisManager.getAxisContext(Y)];
|
|
const { removed, added } = this.diffAxisIds(axesCtx);
|
|
if (removed.length > 0) {
|
|
this.axes = this.axes.filter((entry) => {
|
|
if (removed.includes(entry.axisId)) {
|
|
entry.div.destroy();
|
|
this.overlappingAxisIds.delete(entry.axisId);
|
|
if (this.hoveredAxisId === entry.axisId)
|
|
this.hoveredAxisId = void 0;
|
|
if (this.activeAxisId === entry.axisId)
|
|
this.activeAxisId = void 0;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
for (const newAxisCtx of added) {
|
|
const { axisId, direction } = newAxisCtx;
|
|
this.axes.push(this.initAxis(ctx, axisId, this.axesHandlers, direction));
|
|
}
|
|
for (const axis of this.axes) {
|
|
const axisCtx = axesCtx.find((ac) => ac.axisId === axis.axisId);
|
|
const bbox = axisCtx.getCanvasBounds();
|
|
axis.div.setHidden(boxEmpty(bbox));
|
|
if (bbox == void 0) {
|
|
axis.bounds = void 0;
|
|
} else {
|
|
axis.div.setBounds(bbox);
|
|
axis.bounds = new module_support_exports.BBox(bbox.x, bbox.y, bbox.width, bbox.height);
|
|
}
|
|
}
|
|
this.updateOverlappingAxisPointerEvents(enableAxisDragging, enableAxisScrolling);
|
|
}
|
|
setAxisCursor(cursor) {
|
|
this.cursor = cursor;
|
|
for (const axis of this.axes) {
|
|
axis.div.setCursor(this.getCursor(axis.direction));
|
|
}
|
|
}
|
|
toggleAxisDraggingCursor(direction, enabled) {
|
|
for (const axis of this.axes) {
|
|
if (axis.direction !== direction)
|
|
continue;
|
|
axis.div.setCursor(enabled ? this.getCursor(direction) : void 0);
|
|
}
|
|
}
|
|
updateOverlappingAxisPointerEvents(enableAxisDragging, enableAxisScrolling) {
|
|
this.overlappingAxisIds.clear();
|
|
const shouldEnableInteraction = (enableAxisDragging || enableAxisScrolling) && this.seriesRect;
|
|
for (const axis of this.axes) {
|
|
if (!shouldEnableInteraction) {
|
|
axis.div.setPointerEvents(void 0);
|
|
continue;
|
|
}
|
|
const isOverlapping = Boolean(axis.bounds?.collidesBBox(this.seriesRect));
|
|
if (isOverlapping) {
|
|
this.overlappingAxisIds.add(axis.axisId);
|
|
axis.div.setPointerEvents("none");
|
|
} else {
|
|
axis.div.setPointerEvents(void 0);
|
|
}
|
|
}
|
|
this.cleanupAxisState();
|
|
}
|
|
cleanupAxisState() {
|
|
if (this.hoveredAxisId && !this.overlappingAxisIds.has(this.hoveredAxisId)) {
|
|
this.hoveredAxisId = void 0;
|
|
}
|
|
if (this.activeAxisId && !this.overlappingAxisIds.has(this.activeAxisId)) {
|
|
this.activeAxisId = void 0;
|
|
}
|
|
}
|
|
pickAxisAtPoint(point) {
|
|
for (const axis of this.axes) {
|
|
if (!this.overlappingAxisIds.has(axis.axisId))
|
|
continue;
|
|
if (axis.bounds?.containsPoint(point.canvasX, point.canvasY)) {
|
|
return { axisId: axis.axisId, direction: axis.direction };
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
setHoveredAxis(axisId) {
|
|
if (this.overlappingAxisIds.has(axisId)) {
|
|
this.hoveredAxisId = axisId;
|
|
}
|
|
}
|
|
clearHoveredAxis() {
|
|
if (!this.activeAxisId) {
|
|
this.hoveredAxisId = void 0;
|
|
}
|
|
}
|
|
beginDelegatedAxisDrag(axisId) {
|
|
if (!this.overlappingAxisIds.has(axisId))
|
|
return false;
|
|
this.activeAxisId = axisId;
|
|
this.hoveredAxisId = void 0;
|
|
return true;
|
|
}
|
|
endDelegatedAxisDrag(axisId) {
|
|
if (this.activeAxisId === axisId) {
|
|
this.activeAxisId = void 0;
|
|
}
|
|
this.hoveredAxisId = void 0;
|
|
}
|
|
hasOverlappingAxes() {
|
|
return this.overlappingAxisIds.size > 0;
|
|
}
|
|
getHoveredAxis() {
|
|
if (!this.hoveredAxisId)
|
|
return void 0;
|
|
const axis = this.axes.find((a) => a.axisId === this.hoveredAxisId);
|
|
return axis ? { axisId: axis.axisId, direction: axis.direction } : void 0;
|
|
}
|
|
getCursor(direction) {
|
|
if (this.cursor)
|
|
return this.cursor;
|
|
return direction === "x" /* X */ ? "ew-resize" : "ns-resize";
|
|
}
|
|
initAxis(ctx, axisId, handlers, direction) {
|
|
const where = "afterend";
|
|
const div = ctx.proxyInteractionService.createProxyElement({ type: "region", domManagerId: axisId, where });
|
|
div.setCursor(this.getCursor(direction));
|
|
div.addListener("drag-start", (e) => {
|
|
if (e.device === "touch") {
|
|
e.sourceEvent.preventDefault();
|
|
}
|
|
this.activeAxisId = axisId;
|
|
handlers.onAxisDragStart(direction);
|
|
});
|
|
div.addListener("drag-move", (event) => handlers.onAxisDragMove(axisId, direction, event));
|
|
div.addListener("drag-end", () => {
|
|
this.activeAxisId = void 0;
|
|
this.hoveredAxisId = void 0;
|
|
handlers.onAxisDragEnd();
|
|
});
|
|
div.addListener("dblclick", () => handlers.onAxisDoubleClick(axisId, direction));
|
|
div.addListener("wheel", (event) => handlers.onAxisWheel(direction, event));
|
|
return { axisId, div, direction };
|
|
}
|
|
diffAxisIds(axesCtx) {
|
|
const myIds = this.axes.map((entry) => entry.axisId);
|
|
const ctxIds = axesCtx.map((ctx) => ctx.axisId);
|
|
const removed = myIds.filter((id) => !ctxIds.includes(id));
|
|
const added = axesCtx.filter((ac) => !myIds.includes(ac.axisId));
|
|
return { removed, added };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomOnDataChange.ts
|
|
var { userInteraction: userInteraction4 } = module_support_exports;
|
|
function shouldIgnoreDataUpdate(zoom) {
|
|
return zoom.x.min === 0 && zoom.x.max === 1 && zoom.y.min === 0 && zoom.y.max === 1;
|
|
}
|
|
function shouldStickToEnd(properties, zoom) {
|
|
return properties.stickToEnd && zoom.x.max === 1;
|
|
}
|
|
function toVisibleMinMax(axisId, domainMinMax, ratios) {
|
|
const { domainMin, domainMax } = domainMinMax;
|
|
const span = domainMax - domainMin;
|
|
return {
|
|
axisId,
|
|
visibleMin: domainMin + span * ratios.min,
|
|
visibleMax: domainMin + span * ratios.max
|
|
};
|
|
}
|
|
function fromVisibleMinMax(domainMinMax, visibleMinMax) {
|
|
const { domainMin, domainMax } = domainMinMax;
|
|
const { visibleMin, visibleMax } = visibleMinMax;
|
|
const span = domainMax - domainMin;
|
|
return {
|
|
direction: "x",
|
|
min: clamp(0, (visibleMin - domainMin) / span, 1),
|
|
max: clamp(0, (visibleMax - domainMin) / span, 1)
|
|
};
|
|
}
|
|
var ZoomOnDataChangeProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.strategy = "preserveDomain";
|
|
// TODO(olegat): change default to 'true'
|
|
this.stickToEnd = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ZoomOnDataChangeProperties.prototype, "strategy", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ZoomOnDataChangeProperties.prototype, "stickToEnd", 2);
|
|
var ZoomOnDataChange = class {
|
|
constructor(onConstrainChangesCallback, properties, ctx, eventsCleanup) {
|
|
this.onConstrainChangesCallback = onConstrainChangesCallback;
|
|
this.properties = properties;
|
|
this.ctx = ctx;
|
|
const onFirstDraw = () => {
|
|
ctx.eventsHub.off("layout:complete", onFirstDraw);
|
|
eventsCleanup.register(
|
|
ctx.eventsHub.on("data:load", (e) => this.onDataLoad(e)),
|
|
ctx.eventsHub.on("data:update", (e) => this.onDataUpdate(e))
|
|
);
|
|
};
|
|
eventsCleanup.register(
|
|
ctx.eventsHub.on("layout:complete", onFirstDraw),
|
|
ctx.eventsHub.on("zoom:change-request", (e) => this.onZoomChangeRequest(e))
|
|
);
|
|
}
|
|
destroy() {
|
|
}
|
|
onDataLoad(_e) {
|
|
this.performUpdateStrategy();
|
|
}
|
|
onDataUpdate(_e) {
|
|
this.performUpdateStrategy();
|
|
}
|
|
onZoomChangeRequest(e) {
|
|
if (e.sourceDetail === "internal-requiredWidth") {
|
|
this.desiredChanges = void 0;
|
|
}
|
|
const changes = this.popDesiredChanges();
|
|
if (changes) {
|
|
e.constrainChanges(changes);
|
|
this.onConstrainChangesCallback(e);
|
|
}
|
|
}
|
|
/**
|
|
* Convert ambiguous axes-scale (number | Date | string) into a strictly numerical scale, so that we can use
|
|
* interpolation to implement preserveDomain in an axes-scale agnostic way.
|
|
*/
|
|
computeDomainMinMax(axisId) {
|
|
const ctx = this.ctx.axisManager.getAxisIdContext(axisId);
|
|
if (!ctx?.continuous || ctx.scale.domain.length === 0)
|
|
return;
|
|
const [min, max] = ctx.scale.getDomainMinMax();
|
|
if (typeof min === "number" && typeof max === "number") {
|
|
return { domainMin: min, domainMax: max };
|
|
} else if (min instanceof Date && max instanceof Date) {
|
|
return { domainMin: min.getTime(), domainMax: max.getTime() };
|
|
} else {
|
|
logger_exports.error(`Unexpected range types: start (${typeof min}), end (${typeof max})`);
|
|
}
|
|
}
|
|
popDesiredChanges() {
|
|
const { desiredChanges } = this;
|
|
if (!desiredChanges)
|
|
return;
|
|
this.desiredChanges = void 0;
|
|
switch (desiredChanges.type) {
|
|
case "domain": {
|
|
const changes = {};
|
|
for (const entry of desiredChanges.domains) {
|
|
const domainMinMax = this.computeDomainMinMax(entry.axisId);
|
|
if (domainMinMax) {
|
|
changes[entry.axisId] = fromVisibleMinMax(domainMinMax, entry);
|
|
}
|
|
}
|
|
return changes;
|
|
}
|
|
case "stickToEnd": {
|
|
const { axisId, difference } = desiredChanges;
|
|
const domainMinMax = this.computeDomainMinMax(axisId);
|
|
if (domainMinMax) {
|
|
const visibleMinMax = {
|
|
axisId,
|
|
visibleMin: domainMinMax.domainMax - difference,
|
|
visibleMax: domainMinMax.domainMax
|
|
};
|
|
return { [axisId]: fromVisibleMinMax(domainMinMax, visibleMinMax) };
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
const unreachable = (a) => a;
|
|
return unreachable(desiredChanges);
|
|
}
|
|
}
|
|
performUpdateStrategy() {
|
|
const zoom = definedZoomState(this.ctx.zoomManager.getZoom());
|
|
if (shouldIgnoreDataUpdate(zoom)) {
|
|
return;
|
|
} else if (shouldStickToEnd(this.properties, zoom)) {
|
|
return this.performStickToEnd();
|
|
}
|
|
switch (this.properties.strategy) {
|
|
case "reset":
|
|
return this.ctx.zoomManager.resetZoom(userInteraction4("onDataChange-reset"));
|
|
case "preserveRatios":
|
|
return;
|
|
case "preserveDomain":
|
|
return this.performPreserveDomain();
|
|
default:
|
|
const unreachable = (a) => a;
|
|
return unreachable(this.properties.strategy);
|
|
}
|
|
}
|
|
performPreserveDomain() {
|
|
this.desiredChanges = { type: "domain", domains: [] };
|
|
const xaxes = this.ctx.zoomManager.getAxes().filter((a) => a.direction === "x" /* X */);
|
|
for (const { id: axisId } of xaxes) {
|
|
const domainMinMax = this.computeDomainMinMax(axisId);
|
|
if (domainMinMax) {
|
|
const ratios = this.ctx.zoomManager.getAxisZoom(axisId);
|
|
const entry = toVisibleMinMax(axisId, domainMinMax, ratios);
|
|
this.desiredChanges.domains.push(entry);
|
|
}
|
|
}
|
|
}
|
|
performStickToEnd() {
|
|
const axisId = this.ctx.zoomManager.getPrimaryAxisId("x" /* X */);
|
|
if (!axisId)
|
|
return;
|
|
const domainMinMax = this.computeDomainMinMax(axisId);
|
|
if (!domainMinMax)
|
|
return;
|
|
const ratios = this.ctx.zoomManager.getAxisZoom(axisId);
|
|
if (!ratios)
|
|
return;
|
|
const { visibleMin, visibleMax } = toVisibleMinMax(axisId, domainMinMax, ratios);
|
|
const difference = visibleMax - visibleMin;
|
|
this.desiredChanges = { type: "stickToEnd", axisId, difference };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomPanner.ts
|
|
var maxZoomCoords = 16;
|
|
var decelerationValues = {
|
|
off: 1,
|
|
short: 0.01,
|
|
long: 2e-3
|
|
};
|
|
var ZoomPanner = class {
|
|
constructor() {
|
|
this.deceleration = 1;
|
|
this.zoomCoordsHistoryIndex = 0;
|
|
this.coordsHistory = [];
|
|
}
|
|
get decelerationValue() {
|
|
const { deceleration } = this;
|
|
return Math.max(
|
|
typeof deceleration === "number" ? deceleration : decelerationValues[deceleration] ?? 1,
|
|
1e-4
|
|
);
|
|
}
|
|
addListener(_type, fn) {
|
|
this.onUpdate = fn;
|
|
return () => {
|
|
this.onUpdate = void 0;
|
|
};
|
|
}
|
|
stopInteractions() {
|
|
if (this.inertiaHandle != null) {
|
|
cancelAnimationFrame(this.inertiaHandle);
|
|
this.inertiaHandle = void 0;
|
|
}
|
|
}
|
|
update(event) {
|
|
this.updateCoords(event.currentX, event.currentY);
|
|
const { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } = this.coords ?? {};
|
|
this.onUpdate?.({
|
|
type: "update",
|
|
deltaX: this.isPanningX() ? x1 - x2 : 0,
|
|
deltaY: this.isPanningY() ? y1 - y2 : 0
|
|
});
|
|
}
|
|
start(direction) {
|
|
this.direction = direction;
|
|
this.coordsMonitorTimeout = setInterval(this.recordCurrentZoomCoords.bind(this), 16);
|
|
}
|
|
stop() {
|
|
const { coordsHistory } = this;
|
|
let deltaX = 0;
|
|
let deltaY = 0;
|
|
let deltaT = 0;
|
|
if (coordsHistory.length > 0) {
|
|
const arrayIndex = this.zoomCoordsHistoryIndex % maxZoomCoords;
|
|
let index1 = arrayIndex - 1;
|
|
if (index1 < 0)
|
|
index1 = coordsHistory.length - 1;
|
|
let index0 = arrayIndex;
|
|
if (index0 >= coordsHistory.length)
|
|
index0 = 0;
|
|
const coords1 = coordsHistory[index1];
|
|
const coords0 = coordsHistory[index0];
|
|
deltaX = this.isPanningX() ? coords1.x - coords0.x : 0;
|
|
deltaY = this.isPanningY() ? coords1.y - coords0.y : 0;
|
|
deltaT = coords1.t - coords0.t;
|
|
}
|
|
this.coords = void 0;
|
|
this.direction = void 0;
|
|
clearInterval(this.coordsMonitorTimeout);
|
|
this.coordsMonitorTimeout = void 0;
|
|
this.zoomCoordsHistoryIndex = 0;
|
|
this.coordsHistory.length = 0;
|
|
if (deltaT > 0 && this.decelerationValue < 1) {
|
|
const xVelocity = deltaX / deltaT;
|
|
const yVelocity = deltaY / deltaT;
|
|
const velocity = Math.hypot(xVelocity, yVelocity);
|
|
const angle2 = Math.atan2(yVelocity, xVelocity);
|
|
const t0 = performance.now();
|
|
this.inertiaHandle = getWindow().requestAnimationFrame((t) => {
|
|
this.animateInertia(t, t, t0, velocity, angle2);
|
|
});
|
|
}
|
|
}
|
|
recordCurrentZoomCoords() {
|
|
const { coords, coordsHistory, zoomCoordsHistoryIndex } = this;
|
|
if (!coords)
|
|
return;
|
|
const { x2: x, y2: y } = coords;
|
|
const t = Date.now();
|
|
coordsHistory[zoomCoordsHistoryIndex % maxZoomCoords] = { x, y, t };
|
|
this.zoomCoordsHistoryIndex += 1;
|
|
}
|
|
animateInertia(t, prevT, t0, velocity, angle2) {
|
|
const friction = 1 - this.decelerationValue;
|
|
const maxS = -velocity / Math.log(friction);
|
|
const s0 = velocity * (friction ** (prevT - t0) - 1) / Math.log(friction);
|
|
const s1 = velocity * (friction ** (t - t0) - 1) / Math.log(friction);
|
|
this.onUpdate?.({
|
|
type: "update",
|
|
deltaX: this.isPanningX() ? -Math.cos(angle2) * (s1 - s0) : 0,
|
|
deltaY: this.isPanningY() ? -Math.sin(angle2) * (s1 - s0) : 0
|
|
});
|
|
if (s1 >= maxS - 1)
|
|
return;
|
|
this.inertiaHandle = requestAnimationFrame((nextT) => {
|
|
this.animateInertia(nextT, t, t0, velocity, angle2);
|
|
});
|
|
}
|
|
updateCoords(x, y) {
|
|
if (this.coords) {
|
|
this.coords = { x1: this.coords.x2, y1: this.coords.y2, x2: x, y2: y };
|
|
} else {
|
|
this.coords = { x1: x, y1: y, x2: x, y2: y };
|
|
}
|
|
}
|
|
isPanningX() {
|
|
return this.direction == null || this.direction === "x" /* X */;
|
|
}
|
|
isPanningY() {
|
|
return this.direction == null || this.direction === "y" /* Y */;
|
|
}
|
|
translateZooms(bbox, currentZooms, deltaX, deltaY) {
|
|
const offset = pointToRatio(bbox, bbox.x + Math.abs(deltaX), bbox.y + bbox.height - Math.abs(deltaY));
|
|
const offsetX = Math.sign(deltaX) * offset.x;
|
|
const offsetY = -Math.sign(deltaY) * offset.y;
|
|
const newZooms = {};
|
|
for (const [axisId, currentZoom] of entries(currentZooms)) {
|
|
if (currentZoom == null)
|
|
continue;
|
|
if (currentZoom.min === UNIT_MIN && currentZoom.max === UNIT_MAX) {
|
|
continue;
|
|
}
|
|
const { direction } = currentZoom;
|
|
let zoom = definedZoomState({ [direction]: currentZoom });
|
|
zoom = constrainZoom(translateZoom(zoom, offsetX * dx(zoom), offsetY * dy(zoom)));
|
|
const { min, max } = zoom[direction];
|
|
newZooms[axisId] = { direction, min, max };
|
|
}
|
|
return newZooms;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomScrollPanner.ts
|
|
var DELTA_SCALE = 200;
|
|
var ZoomScrollPanner = class {
|
|
update(event, step, bbox, zooms) {
|
|
const deltaX = event.deltaX * step * DELTA_SCALE;
|
|
return this.translateZooms(bbox, zooms, deltaX);
|
|
}
|
|
translateZooms(bbox, currentZooms, deltaX) {
|
|
const newZooms = {};
|
|
const offset = pointToRatio(bbox, bbox.x + Math.abs(deltaX), 0);
|
|
const offsetX = deltaX < 0 ? -offset.x : offset.x;
|
|
for (const [axisId, value] of entries(currentZooms)) {
|
|
if (value?.direction !== "x" /* X */)
|
|
continue;
|
|
const { direction, min, max } = value;
|
|
let zoom = definedZoomState({ x: { min, max } });
|
|
zoom = constrainZoom(translateZoom(zoom, offsetX * dx(zoom), 0));
|
|
newZooms[axisId] = { direction, min: zoom.x.min, max: zoom.x.max };
|
|
}
|
|
return newZooms;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomScroller.ts
|
|
var ZoomScroller = class {
|
|
updateAxes(event, props, bbox, zooms) {
|
|
const sourceEvent = event.sourceEvent;
|
|
const newZooms = {};
|
|
const { anchorPointX, anchorPointY, isScalingX, isScalingY, scrollingStep } = props;
|
|
const origin3 = pointToRatio(
|
|
bbox,
|
|
sourceEvent.offsetX ?? sourceEvent.clientX,
|
|
sourceEvent.offsetY ?? sourceEvent.clientY
|
|
);
|
|
for (const [axisId, value] of entries(zooms)) {
|
|
if (value == null)
|
|
continue;
|
|
const { direction, min, max } = value;
|
|
let newZoom = { min, max };
|
|
const delta5 = scrollingStep * event.deltaY * (max - min);
|
|
if (direction === "x" /* X */ && isScalingX) {
|
|
newZoom.max += delta5;
|
|
newZoom = scaleZoomAxisWithAnchor(newZoom, value, anchorPointX, origin3.x);
|
|
} else if (direction === "y" /* Y */ && isScalingY) {
|
|
newZoom.max += delta5;
|
|
newZoom = scaleZoomAxisWithAnchor(newZoom, value, anchorPointY, origin3.y);
|
|
} else {
|
|
continue;
|
|
}
|
|
if (newZoom.max < newZoom.min)
|
|
continue;
|
|
const constrained = constrainAxis(newZoom);
|
|
newZooms[axisId] = { direction, min: constrained.min, max: constrained.max };
|
|
}
|
|
return newZooms;
|
|
}
|
|
update(event, props, bbox, oldZoom) {
|
|
const { anchorPointX, anchorPointY, isScalingX, isScalingY, scrollingStep } = props;
|
|
const canvasX = event.offsetX + bbox.x;
|
|
const canvasY = event.offsetY + bbox.y;
|
|
const origin3 = pointToRatio(bbox, canvasX, canvasY);
|
|
const dir = event.deltaY;
|
|
let newZoom = definedZoomState(oldZoom);
|
|
newZoom.x.max += isScalingX ? scrollingStep * dir * dx(oldZoom) : 0;
|
|
newZoom.y.max += isScalingY ? scrollingStep * dir * dy(oldZoom) : 0;
|
|
if (newZoom.x.max < newZoom.x.min || newZoom.y.max < newZoom.y.min)
|
|
return;
|
|
if (isScalingX) {
|
|
newZoom.x = scaleZoomAxisWithAnchor(newZoom.x, oldZoom.x, anchorPointX, origin3.x);
|
|
}
|
|
if (isScalingY) {
|
|
newZoom.y = scaleZoomAxisWithAnchor(newZoom.y, oldZoom.y, anchorPointY, origin3.y);
|
|
}
|
|
newZoom = constrainZoom(newZoom);
|
|
return newZoom;
|
|
}
|
|
updateDelta(delta5, props, oldZoom) {
|
|
const { anchorPointX, anchorPointY, isScalingX, isScalingY, scrollingStep } = props;
|
|
let newZoom = definedZoomState(oldZoom);
|
|
newZoom.x.max += isScalingX ? scrollingStep * -delta5 * dx(oldZoom) : 0;
|
|
newZoom.y.max += isScalingY ? scrollingStep * -delta5 * dy(oldZoom) : 0;
|
|
if (isScalingX) {
|
|
newZoom.x = scaleZoomAxisWithAnchor(newZoom.x, oldZoom.x, anchorPointX);
|
|
}
|
|
if (isScalingY) {
|
|
newZoom.y = scaleZoomAxisWithAnchor(newZoom.y, oldZoom.y, anchorPointY);
|
|
}
|
|
newZoom = constrainZoom(newZoom);
|
|
return newZoom;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomSelector.ts
|
|
var ZoomSelector = class {
|
|
constructor(rect2, getZoom, isZoomValid) {
|
|
this.rect = rect2;
|
|
this.getZoom = getZoom;
|
|
this.isZoomValid = isZoomValid;
|
|
this.rect.visible = false;
|
|
}
|
|
update(event, props, bbox) {
|
|
const canvasX = event.currentX + (bbox?.x ?? 0);
|
|
const canvasY = event.currentY + (bbox?.y ?? 0);
|
|
this.rect.visible = true;
|
|
this.updateCoords(canvasX, canvasY, props, bbox);
|
|
this.updateRect(bbox);
|
|
}
|
|
stop(innerBBox, bbox, currentZoom) {
|
|
let zoom = definedZoomState();
|
|
if (!innerBBox || !bbox)
|
|
return zoom;
|
|
if (this.coords) {
|
|
zoom = this.createZoomFromCoords(bbox, currentZoom);
|
|
}
|
|
const multiplyX = bbox.width / innerBBox.width;
|
|
const multiplyY = bbox.height / innerBBox.height;
|
|
zoom = constrainZoom(multiplyZoom(zoom, multiplyX, multiplyY));
|
|
this.reset();
|
|
if (this.isZoomValid(zoom)) {
|
|
return zoom;
|
|
}
|
|
}
|
|
reset() {
|
|
this.coords = void 0;
|
|
this.rect.visible = false;
|
|
}
|
|
didUpdate() {
|
|
return this.rect.visible && this.rect.width > 0 && this.rect.height > 0;
|
|
}
|
|
updateCoords(x, y, props, bbox) {
|
|
if (!this.coords) {
|
|
this.coords = { x1: x, y1: y, x2: x, y2: y };
|
|
return;
|
|
}
|
|
const { coords } = this;
|
|
coords.x2 = x;
|
|
coords.y2 = y;
|
|
if (!bbox)
|
|
return;
|
|
const { isScalingX, isScalingY, keepAspectRatio } = props;
|
|
const normal = this.getNormalisedDimensions();
|
|
if (keepAspectRatio && isScalingX && isScalingY) {
|
|
const aspectRatio = bbox.width / bbox.height;
|
|
if (coords.y2 < coords.y1) {
|
|
coords.y2 = Math.min(coords.y1 - normal.width / aspectRatio, coords.y1);
|
|
} else {
|
|
coords.y2 = Math.max(coords.y1 + normal.width / aspectRatio, coords.y1);
|
|
}
|
|
}
|
|
if (!isScalingX) {
|
|
coords.x1 = bbox.x;
|
|
coords.x2 = bbox.x + bbox.width;
|
|
}
|
|
if (!isScalingY) {
|
|
coords.y1 = bbox.y;
|
|
coords.y2 = bbox.y + bbox.height;
|
|
}
|
|
}
|
|
updateRect(bbox) {
|
|
if (!bbox)
|
|
return;
|
|
const { rect: rect2 } = this;
|
|
const normal = this.getNormalisedDimensions();
|
|
const { width: width2, height: height2 } = normal;
|
|
let { x, y } = normal;
|
|
x = Math.max(x, bbox.x);
|
|
x -= Math.max(0, x + width2 - (bbox.x + bbox.width));
|
|
y = Math.max(y, bbox.y);
|
|
y -= Math.max(0, y + height2 - (bbox.y + bbox.height));
|
|
rect2.x = x;
|
|
rect2.y = y;
|
|
rect2.width = width2;
|
|
rect2.height = height2;
|
|
const zoom = this.createZoomFromCoords(bbox, this.getZoom());
|
|
if (this.isZoomValid(zoom)) {
|
|
rect2.updateValid();
|
|
} else {
|
|
rect2.updateInvalid();
|
|
}
|
|
}
|
|
createZoomFromCoords(bbox, currentZoom) {
|
|
const oldZoom = definedZoomState(currentZoom);
|
|
const normal = this.getNormalisedDimensions();
|
|
const origin3 = pointToRatio(bbox, normal.x, normal.y + normal.height);
|
|
const xFactor = normal.width / bbox.width;
|
|
const yFactor = normal.height / bbox.height;
|
|
let newZoom = scaleZoom(oldZoom, xFactor, yFactor);
|
|
const translateX = origin3.x * dx(oldZoom);
|
|
const translateY = origin3.y * dy(oldZoom);
|
|
newZoom = translateZoom(newZoom, translateX, translateY);
|
|
newZoom = constrainZoom(newZoom);
|
|
return newZoom;
|
|
}
|
|
getNormalisedDimensions() {
|
|
const { x1 = 0, y1 = 0, x2 = 0, y2 = 0 } = this.coords ?? {};
|
|
const x = Math.min(x1, x2);
|
|
const y = Math.min(y1, y2);
|
|
const width2 = x1 <= x2 ? x2 - x1 : x1 - x2;
|
|
const height2 = y1 <= y2 ? y2 - y1 : y1 - y2;
|
|
return { x, y, width: width2, height: height2 };
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomToolbar.ts
|
|
var { userInteraction: userInteraction5, NativeWidget: NativeWidget3, Toolbar: Toolbar3 } = module_support_exports;
|
|
var ZoomButtonProperties = class extends ToolbarButtonProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ZoomButtonProperties.prototype, "value", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ZoomButtonProperties.prototype, "section", 2);
|
|
var ZoomToolbar = class extends BaseProperties {
|
|
constructor(ctx, getModuleProperties, updateZoom, updateAxisZoom, resetZoom, isZoomValid) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.getModuleProperties = getModuleProperties;
|
|
this.updateZoom = updateZoom;
|
|
this.updateAxisZoom = updateAxisZoom;
|
|
this.resetZoom = resetZoom;
|
|
this.isZoomValid = isZoomValid;
|
|
this.enabled = false;
|
|
this.buttons = new PropertiesArray(ZoomButtonProperties);
|
|
this.visible = "hover";
|
|
this.verticalSpacing = 10;
|
|
this.detectionRange = 38;
|
|
this.cleanup = new CleanupRegistry();
|
|
this.toggleButtonsDebounced = debounce(this.toggleButtons.bind(this), ZOOM_VALID_CHECK_DEBOUNCE, {
|
|
leading: true,
|
|
trailing: true
|
|
});
|
|
this.container = new NativeWidget3(createElement("div"));
|
|
this.container.addClass("ag-charts-zoom-buttons");
|
|
ctx.domManager.addChild("canvas-overlay", "zoom-buttons", this.container.getElement());
|
|
this.toolbar = new Toolbar3(ctx, "ariaLabelZoomToolbar", "horizontal");
|
|
this.container.addChild(this.toolbar);
|
|
this.toolbar.getElement().style.transform = `translateY(54px)`;
|
|
this.toolbar.setHidden(!this.enabled);
|
|
this.toggleVisibility(this.visible === "always");
|
|
this.cleanup.register(
|
|
this.toolbar.addToolbarListener("button-pressed", this.onButtonPress.bind(this)),
|
|
this.toolbar.addToolbarListener("button-focused", this.onButtonFocus.bind(this)),
|
|
ctx.widgets.containerWidget.addListener("mousemove", this.onHover.bind(this)),
|
|
ctx.widgets.containerWidget.addListener("mouseleave", this.onLeave.bind(this)),
|
|
ctx.eventsHub.on("layout:complete", this.onLayoutComplete.bind(this)),
|
|
this.teardown.bind(this)
|
|
);
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
}
|
|
toggleVisibleZoomed(maxZoom) {
|
|
if (this.visible !== "zoomed")
|
|
return;
|
|
this.toggleVisibility(!maxZoom);
|
|
}
|
|
teardown() {
|
|
this.ctx.domManager.removeChild("canvas-overlay", "zoom-buttons");
|
|
this.container.destroy();
|
|
}
|
|
onLayoutComplete(event) {
|
|
if (!this.enabled)
|
|
return;
|
|
const { buttons, container } = this;
|
|
const { rect: rect2 } = event.series;
|
|
for (const b of buttons) {
|
|
if (b.tooltip == null && b.label == null) {
|
|
const map = {
|
|
"pan-end": "toolbarZoomPanEnd",
|
|
"pan-left": "toolbarZoomPanLeft",
|
|
"pan-right": "toolbarZoomPanRight",
|
|
"pan-start": "toolbarZoomPanStart",
|
|
"zoom-in": "toolbarZoomZoomIn",
|
|
"zoom-out": "toolbarZoomZoomOut",
|
|
reset: "toolbarZoomReset"
|
|
};
|
|
b.tooltip = map[b.value];
|
|
}
|
|
}
|
|
this.toolbar.updateButtons(buttons);
|
|
this.toggleButtonsDebounced();
|
|
const height2 = container.getBounds().height;
|
|
container.setBounds({ y: rect2.y + rect2.height - height2 });
|
|
}
|
|
onHover(event) {
|
|
if (!this.enabled || this.visible !== "hover" || this.toolbar.isHidden())
|
|
return;
|
|
const {
|
|
container,
|
|
detectionRange,
|
|
ctx: { scene }
|
|
} = this;
|
|
const {
|
|
currentY,
|
|
sourceEvent: { target }
|
|
} = event;
|
|
const element2 = container.getElement();
|
|
const detectionY = element2.offsetTop - detectionRange;
|
|
const visible = currentY > detectionY && currentY < scene.canvas.element.offsetHeight || target === element2;
|
|
this.toggleVisibility(visible);
|
|
}
|
|
onLeave() {
|
|
if (this.visible !== "hover")
|
|
return;
|
|
this.toggleVisibility(false);
|
|
}
|
|
toggleVisibility(visible, immediate = false) {
|
|
const { container, toolbar: toolbar2, verticalSpacing } = this;
|
|
toolbar2.toggleClass("ag-charts-zoom-buttons__toolbar--hidden", !visible);
|
|
const element2 = toolbar2.getElement();
|
|
element2.style.transitionDuration = immediate ? "0s" : "";
|
|
element2.style.transform = visible ? "translateY(0)" : `translateY(${container.getBounds().height + verticalSpacing}px)`;
|
|
}
|
|
toggleButtons() {
|
|
const zoom = definedZoomState(this.ctx.zoomManager.getZoom());
|
|
if (this.previousZoom && isZoomEqual(this.previousZoom, zoom))
|
|
return;
|
|
this.previousZoom = zoom;
|
|
for (const [index, button] of this.buttons.entries()) {
|
|
let enabled = true;
|
|
switch (button?.value) {
|
|
case "pan-start":
|
|
enabled = zoom.x.min > UNIT_MIN;
|
|
break;
|
|
case "pan-end":
|
|
enabled = zoom.x.max < UNIT_MAX;
|
|
break;
|
|
case "pan-left":
|
|
enabled = zoom.x.min > UNIT_MIN;
|
|
break;
|
|
case "pan-right":
|
|
enabled = zoom.x.max < UNIT_MAX;
|
|
break;
|
|
case "zoom-out":
|
|
enabled = !isMaxZoom(zoom);
|
|
break;
|
|
case "zoom-in":
|
|
enabled = this.isZoomValid(
|
|
this.getNextZoomStateUnified("zoom-in", zoom, this.getModuleProperties())
|
|
);
|
|
break;
|
|
case "reset":
|
|
enabled = canResetZoom(this.ctx.zoomManager);
|
|
break;
|
|
}
|
|
this.toolbar.toggleButtonEnabledByIndex(index, enabled);
|
|
}
|
|
}
|
|
onButtonPress({ button }) {
|
|
if (!this.enabled || this.toolbar.isHidden())
|
|
return;
|
|
const props = this.getModuleProperties();
|
|
if (props.independentAxes && button.value !== "reset") {
|
|
const axisZooms = this.ctx.zoomManager.getAxisZooms();
|
|
for (const [axisId, value] of entries(axisZooms)) {
|
|
if (value == null)
|
|
continue;
|
|
const { direction, min, max } = value;
|
|
this.onButtonPressAxis(button, props, axisId, direction, { min, max });
|
|
}
|
|
} else {
|
|
this.onButtonPressUnified(button, props);
|
|
}
|
|
}
|
|
onButtonFocus(_event) {
|
|
this.toggleVisibility(true, true);
|
|
}
|
|
onButtonPressAxis(event, props, axisId, direction, zoom) {
|
|
const { isScalingX, isScalingY, scrollingStep } = props;
|
|
let newZoom = { ...zoom };
|
|
const delta5 = zoom.max - zoom.min;
|
|
switch (event.value) {
|
|
case "pan-start":
|
|
newZoom.max = delta5;
|
|
newZoom.min = 0;
|
|
break;
|
|
case "pan-end":
|
|
newZoom.min = newZoom.max - delta5;
|
|
newZoom.max = UNIT_MAX;
|
|
break;
|
|
case "pan-left":
|
|
newZoom.min -= delta5 * scrollingStep;
|
|
newZoom.max -= delta5 * scrollingStep;
|
|
break;
|
|
case "pan-right":
|
|
newZoom.min += delta5 * scrollingStep;
|
|
newZoom.max += delta5 * scrollingStep;
|
|
break;
|
|
case "zoom-in":
|
|
case "zoom-out": {
|
|
const isDirectionX = direction === "x" /* X */;
|
|
const isScalingDirection = isDirectionX && isScalingX || !isDirectionX && isScalingY;
|
|
let scale2 = event.value === "zoom-in" ? 1 - scrollingStep : 1 + scrollingStep;
|
|
if (!isScalingDirection)
|
|
scale2 = 1;
|
|
const placement = isDirectionX ? this.getAnchorPointX(props) : this.getAnchorPointY(props);
|
|
newZoom.max = newZoom.min + (newZoom.max - newZoom.min) * scale2;
|
|
newZoom = scaleZoomAxisWithAnchor(newZoom, zoom, placement);
|
|
break;
|
|
}
|
|
}
|
|
this.updateAxisZoom(userInteraction5(`zoom-button-${event.value}`), axisId, direction, constrainAxis(newZoom));
|
|
}
|
|
onButtonPressUnified(event, props) {
|
|
const { scrollingStep } = props;
|
|
const oldZoom = definedZoomState(this.ctx.zoomManager.getZoom());
|
|
let zoom = definedZoomState(oldZoom);
|
|
switch (event.value) {
|
|
case "reset":
|
|
this.resetZoom("zoom-button-reset");
|
|
return;
|
|
case "pan-start":
|
|
zoom.x.max = dx(zoom);
|
|
zoom.x.min = 0;
|
|
break;
|
|
case "pan-end":
|
|
zoom.x.min = UNIT_MAX - dx(zoom);
|
|
zoom.x.max = UNIT_MAX;
|
|
break;
|
|
case "pan-left":
|
|
zoom = translateZoom(zoom, -dx(zoom) * scrollingStep, 0);
|
|
break;
|
|
case "pan-right":
|
|
zoom = translateZoom(zoom, dx(zoom) * scrollingStep, 0);
|
|
break;
|
|
case "zoom-in":
|
|
case "zoom-out": {
|
|
zoom = this.getNextZoomStateUnified(event.value, oldZoom, props);
|
|
break;
|
|
}
|
|
}
|
|
this.updateZoom(userInteraction5(`zoom-button-${event.value}`), constrainZoom(zoom));
|
|
}
|
|
getNextZoomStateUnified(button, oldZoom, props) {
|
|
const { isScalingX, isScalingY, scrollingStep } = props;
|
|
const scale2 = button === "zoom-in" ? 1 - scrollingStep : 1 + scrollingStep;
|
|
const zoom = scaleZoom(oldZoom, isScalingX ? scale2 : 1, isScalingY ? scale2 : 1);
|
|
zoom.x = scaleZoomAxisWithAnchor(zoom.x, oldZoom.x, this.getAnchorPointX(props));
|
|
zoom.y = scaleZoomAxisWithAnchor(zoom.y, oldZoom.y, this.getAnchorPointY(props));
|
|
return zoom;
|
|
}
|
|
getAnchorPointX(props) {
|
|
const anchorPointX = this.anchorPointX ?? props.anchorPointX;
|
|
return anchorPointX === "pointer" ? DEFAULT_ANCHOR_POINT_X : anchorPointX;
|
|
}
|
|
getAnchorPointY(props) {
|
|
const anchorPointY = this.anchorPointY ?? props.anchorPointY;
|
|
return anchorPointY === "pointer" ? DEFAULT_ANCHOR_POINT_Y : anchorPointY;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ActionOnSet({
|
|
changeValue(enabled) {
|
|
this.toolbar?.setHidden(!enabled);
|
|
}
|
|
})
|
|
], ZoomToolbar.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ZoomToolbar.prototype, "buttons", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ActionOnSet({
|
|
changeValue(visible, oldValue) {
|
|
if (oldValue == null)
|
|
return;
|
|
const always = visible === "always";
|
|
const zoomed = visible === "zoomed" && this.previousZoom != null && !isMaxZoom(this.previousZoom);
|
|
this.toggleVisibility(always || zoomed);
|
|
}
|
|
})
|
|
], ZoomToolbar.prototype, "visible", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ZoomToolbar.prototype, "anchorPointX", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ZoomToolbar.prototype, "anchorPointY", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomTwoFingers.ts
|
|
var N = 1e6;
|
|
function clientToNormal({ min, max }, a, Rx, Rw) {
|
|
if (Rw === 0)
|
|
return 0;
|
|
return N * ((a - Rx) / Rw * (max - min) + min);
|
|
}
|
|
function solveTwoUnknowns(x1, x2, a1, a2, Rx, Rw) {
|
|
[x1, x2] = [Math.min(x1, x2), Math.max(x1, x2)];
|
|
[a1, a2] = [Math.min(a1, a2), Math.max(a1, a2)];
|
|
const t1 = N * (a1 - Rx) / Rw;
|
|
const t2 = N * (a2 - Rx) / Rw;
|
|
const c = (a1 - Rx) / (a2 - Rx);
|
|
const min = (x1 - c * x2) / (N - t1 + c * (t2 - N));
|
|
const max = (x2 + (t2 - N) * min) / t2;
|
|
return { min, max };
|
|
}
|
|
function isRangeOverlapping(centerA, radiusA, centerB, radiusB) {
|
|
if (radiusA === 0)
|
|
radiusA = 30;
|
|
if (radiusB === 0)
|
|
radiusB = 30;
|
|
const minA = centerA - radiusA;
|
|
const maxA = centerA + radiusA;
|
|
const minB = centerB - radiusB;
|
|
const maxB = centerB + radiusB;
|
|
return !(maxA < minB || maxB < minA);
|
|
}
|
|
var ZoomTwoFingers = class {
|
|
constructor() {
|
|
this.touchStart = {
|
|
origins: [
|
|
{ identifier: 0, normalX: Number.NaN, normalY: Number.NaN },
|
|
{ identifier: 0, normalX: Number.NaN, normalY: Number.NaN }
|
|
]
|
|
};
|
|
this.initialZoom = { x: { min: 0, max: 1 }, y: { min: 0, max: 1 } };
|
|
this.previous = { a1: Number.NaN, a2: Number.NaN, b1: Number.NaN, b2: Number.NaN };
|
|
}
|
|
start(event, target, zoom) {
|
|
if (event.sourceEvent.targetTouches.length !== 2)
|
|
return false;
|
|
event.sourceEvent.preventDefault();
|
|
const targetTouches = Array.from(event.sourceEvent.targetTouches);
|
|
const { x: Rx, y: Ry, width: Rw, height: Rh } = target.getBoundingClientRect();
|
|
this.initialZoom.x.min = zoom.x?.min ?? 0;
|
|
this.initialZoom.x.max = zoom.x?.max ?? 1;
|
|
this.initialZoom.y.min = zoom.y?.min ?? 0;
|
|
this.initialZoom.y.max = zoom.y?.max ?? 1;
|
|
for (const t of this.touchStart.origins) {
|
|
t.identifier = 0;
|
|
}
|
|
this.previous.a1 = Number.NaN;
|
|
this.previous.a2 = Number.NaN;
|
|
this.previous.b1 = Number.NaN;
|
|
this.previous.b2 = Number.NaN;
|
|
for (const i of [0, 1]) {
|
|
const a = targetTouches[i].clientX;
|
|
const b = Ry + Rh - targetTouches[i].clientY;
|
|
this.touchStart.origins[i].identifier = targetTouches[i].identifier;
|
|
this.touchStart.origins[i].normalX = clientToNormal(this.initialZoom.x, a, Rx, Rw);
|
|
this.touchStart.origins[i].normalY = clientToNormal(this.initialZoom.y, b, Ry, Rh);
|
|
}
|
|
const [tA, tB] = targetTouches;
|
|
const [oA, oB] = this.touchStart.origins;
|
|
const xOverlap = isRangeOverlapping(tA.clientX, tA.radiusX, tB.clientX, tB.radiusX);
|
|
const yOverlap = isRangeOverlapping(tA.clientY, tA.radiusY, tB.clientY, tB.radiusY);
|
|
if (yOverlap)
|
|
oA.normalY = oB.normalY = (oA.normalY + oB.normalY) / 2;
|
|
if (xOverlap)
|
|
oA.normalX = oB.normalX = (oA.normalX + oB.normalX) / 2;
|
|
return true;
|
|
}
|
|
update(event, target) {
|
|
event.sourceEvent.preventDefault();
|
|
const targetTouches = Array.from(event.sourceEvent.targetTouches);
|
|
const { x: Rx, y: Ry, width: Rw, height: Rh } = target.getBoundingClientRect();
|
|
const { origins } = this.touchStart;
|
|
const touches = [0, 1].map((i) => targetTouches.find((t) => t.identifier === origins[i].identifier));
|
|
const x1 = origins[0].normalX;
|
|
const x2 = origins[1].normalX;
|
|
const a1 = touches[0].clientX;
|
|
const a2 = touches[1].clientX;
|
|
const y1 = origins[0].normalY;
|
|
const y2 = origins[1].normalY;
|
|
const b1 = Ry + Rh - touches[0].clientY;
|
|
const b2 = Ry + Rh - touches[1].clientY;
|
|
return this.twitchTolerantZoomPan4(x1, x2, a1, a2, y1, y2, b1, b2, Rx, Ry, Rw, Rh);
|
|
}
|
|
end(event) {
|
|
const identifiers = Array.from(event.sourceEvent.targetTouches).map((t) => t.identifier);
|
|
return !identifiers.includes(this.touchStart.origins[0].identifier) || !identifiers.includes(this.touchStart.origins[1].identifier);
|
|
}
|
|
// Small touch deltas on an axis, which can defined as one fingers moving ±1 pixel and the other not moving, can
|
|
// cause the canvas to flicker between two zoompan views.
|
|
//
|
|
// For example, consider two fingers moving upwards slowly on the Y-axis with the following events (Y=0 is the top
|
|
// of the screen):
|
|
//
|
|
// [0]: { finger1: { clientY: 101 }, finger2: { clientY: 201 } }
|
|
// [1]: { finger1: { clientY: 101 }, finger2: { clientY: 200 } }
|
|
// [2]: { finger1: { clientY: 100 }, finger2: { clientY: 200 } }
|
|
//
|
|
// The following transitions cause these changes to the zoompan respectively.
|
|
//
|
|
// [0] => [1] : yMin decreases, yMax increases
|
|
// [1] => [2] : yMin increases, yMax decreases
|
|
//
|
|
// At highly-zoomed views, this sudden shift in yMin/yMax in the [1] => [2] transition is very noticeable. When many
|
|
// of these kind of a transitions occur, the chart flickers between pan states instead of smoothly panning. Note
|
|
// however that, if we didn't receive event [1], our transition would like this:
|
|
//
|
|
// [0] => [2] : yMin increases, yMax increases
|
|
//
|
|
// ... which is a smooth panning transition. Therefore to prevent flickering, we skip event [1].
|
|
twitchTolerantZoomPan4(x1, x2, a1, a2, y1, y2, b1, b2, Rx, Ry, Rw, Rh) {
|
|
const { initialZoom, previous } = this;
|
|
const x = twitchTolerantZoomPan2(x1, x2, a1, a2, previous, "a1", "a2", Rx, Rw, initialZoom.x);
|
|
const y = twitchTolerantZoomPan2(y1, y2, b1, b2, previous, "b1", "b2", Ry, Rh, initialZoom.y);
|
|
return { x, y };
|
|
}
|
|
};
|
|
function twitchTolerantZoomPan2(x1, x2, a1, a2, previous, previousKey1, previousKey2, Rx, Rw, initialZoom) {
|
|
if (x1 == x2) {
|
|
const xn1 = clientToNormal(initialZoom, a1, Rx, Rw);
|
|
const xn2 = clientToNormal(initialZoom, a2, Rx, Rw);
|
|
const xavg = (xn1 + xn2) / 2;
|
|
const dzoom = (x1 - xavg) / N;
|
|
return { min: initialZoom.min + dzoom, max: initialZoom.max + dzoom };
|
|
} else {
|
|
const a1prev = previous[previousKey1];
|
|
const a2prev = previous[previousKey2];
|
|
const dx2 = Math.abs(a1 - a1prev) + Math.abs(a2 - a2prev);
|
|
if (dx2 <= 1) {
|
|
a1 = a1prev;
|
|
a2 = a2prev;
|
|
} else {
|
|
previous[previousKey1] = a1;
|
|
previous[previousKey2] = a2;
|
|
}
|
|
return solveTwoUnknowns(x1, x2, a1, a2, Rx, Rw);
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomWheelSequencer.ts
|
|
var ZoomWheelSequencer = class {
|
|
constructor() {
|
|
this.isFirstWheelEvent = true;
|
|
this.debouncedWheelReset = debounce(() => {
|
|
this.isFirstWheelEvent = true;
|
|
this.wasFirstWheelEventZoomCapped = void 0;
|
|
}, 100);
|
|
}
|
|
onWheel(event, callback2) {
|
|
if (event.sourceEvent.cancelable === false) {
|
|
return;
|
|
}
|
|
const result = callback2();
|
|
if (result === "abort")
|
|
return;
|
|
const isZoomCapped = result === "capped";
|
|
if (this.firstWheelEventDirection != null && this.firstWheelEventDirection !== event.deltaY < 0) {
|
|
this.isFirstWheelEvent = true;
|
|
}
|
|
if (this.isFirstWheelEvent) {
|
|
this.wasFirstWheelEventZoomCapped = isZoomCapped;
|
|
this.firstWheelEventDirection = event.deltaY < 0;
|
|
if (!isZoomCapped) {
|
|
event.sourceEvent.preventDefault();
|
|
}
|
|
} else if (this.wasFirstWheelEventZoomCapped === false) {
|
|
event.sourceEvent.preventDefault();
|
|
}
|
|
this.isFirstWheelEvent = false;
|
|
this.debouncedWheelReset();
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoom.ts
|
|
var { userInteraction: userInteraction6, InteractionState: InteractionState7 } = module_support_exports;
|
|
var round3 = (value) => roundTo(value, 10);
|
|
var CURSOR_ID = "zoom-cursor";
|
|
var TOOLTIP_ID = "zoom-tooltip";
|
|
var Zoom = class extends AbstractModuleInstance {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.enabled = false;
|
|
this.enableAxisDragging = true;
|
|
this.enableAxisScrolling = true;
|
|
this.enableDoubleClickToReset = true;
|
|
this.enablePanning = true;
|
|
this.enableScrolling = true;
|
|
this.enableSelecting = false;
|
|
this.enableTwoFingerZoom = true;
|
|
this.panKey = "alt";
|
|
this.axes = "x";
|
|
this.scrollingStep = UNIT_SIZE / 10;
|
|
this.keepAspectRatio = false;
|
|
this.minVisibleItems = 2;
|
|
this.anchorPointX = DEFAULT_ANCHOR_POINT_X;
|
|
this.anchorPointY = DEFAULT_ANCHOR_POINT_Y;
|
|
this.autoScaling = new ZoomAutoScalingProperties();
|
|
this.axisDraggingMode = "zoom";
|
|
this.buttons = new ZoomToolbar(
|
|
this.ctx,
|
|
this.getModuleProperties.bind(this),
|
|
this.updateZoom.bind(this),
|
|
this.updateAxisZoom.bind(this),
|
|
this.resetZoom.bind(this),
|
|
this.isZoomValid.bind(this)
|
|
);
|
|
this.onDataChange = new ZoomOnDataChangeProperties();
|
|
// Zoom methods
|
|
this.axisDragger = new ZoomAxisDragger();
|
|
this.panner = new ZoomPanner();
|
|
this.scroller = new ZoomScroller();
|
|
this.scrollPanner = new ZoomScrollPanner();
|
|
this.twoFingers = new ZoomTwoFingers();
|
|
this.deceleration = "short";
|
|
// State
|
|
this.dragState = 0 /* None */;
|
|
this.isState = (state) => this.ctx.interactionManager.isState(state);
|
|
this.destroyContextMenuActions = void 0;
|
|
this.wheelSequencer = new ZoomWheelSequencer();
|
|
this.previousZoomValid = true;
|
|
this.previousAxisZoomValid = {
|
|
["x" /* X */]: true,
|
|
["y" /* Y */]: true
|
|
};
|
|
this.toggleAxisDraggingCursorsDebounced = debounce(
|
|
this.toggleAxisDraggingCursors.bind(this),
|
|
ZOOM_VALID_CHECK_DEBOUNCE,
|
|
{
|
|
leading: true,
|
|
trailing: true
|
|
}
|
|
);
|
|
const selectionRect = new ZoomRect();
|
|
this.selector = new ZoomSelector(selectionRect, this.getZoom.bind(this), this.isZoomValid.bind(this));
|
|
this.contextMenu = new ZoomContextMenu(
|
|
ctx.eventsHub,
|
|
ctx.contextMenuRegistry,
|
|
ctx.zoomManager,
|
|
this.getModuleProperties.bind(this),
|
|
() => this.paddedRect,
|
|
this.updateZoom.bind(this),
|
|
this.isZoomValid.bind(this)
|
|
);
|
|
const minVisibleItemsCallback = (event) => {
|
|
if (this.minVisibleItems > 0) {
|
|
const restrictions = event.stateAsDefinedZoom();
|
|
event.constrainZoom(this.constrainZoom(restrictions));
|
|
}
|
|
};
|
|
this.dataChangeHandler = new ZoomOnDataChange(
|
|
minVisibleItemsCallback,
|
|
this.onDataChange,
|
|
this.ctx,
|
|
this.cleanup
|
|
);
|
|
this.domProxy = new ZoomDOMProxy({
|
|
onAxisDragStart: (direction) => this.onAxisDragStart(direction),
|
|
onAxisDragMove: (id, direction, event) => this.onAxisDragMove(id, direction, event),
|
|
onAxisDragEnd: () => this.onAxisDragEnd(),
|
|
onAxisDoubleClick: (id) => this.onAxisDoubleClick(id),
|
|
onAxisWheel: (direction, event) => this.onAxisWheel(direction, event)
|
|
});
|
|
if (ctx.widgets.seriesDragInterpreter) {
|
|
this.cleanup.register(
|
|
ctx.widgets.seriesDragInterpreter.events.on("dblclick", (event) => this.onSeriesAreaDoubleClick(event)),
|
|
ctx.widgets.seriesDragInterpreter.events.on("drag-move", (event) => this.onSeriesAreaDragMove(event)),
|
|
ctx.widgets.seriesDragInterpreter.events.on("drag-start", (event) => this.onSeriesAreaDragStart(event)),
|
|
ctx.widgets.seriesDragInterpreter.events.on("drag-end", () => this.onSeriesAreaDragEnd())
|
|
);
|
|
}
|
|
this.cleanup.register(
|
|
ctx.scene.attachNode(selectionRect),
|
|
ctx.eventsHub.on("series-area:hover", (event) => this.onSeriesAreaHoverEvent(event)),
|
|
ctx.eventsHub.on("series-area:click", (event) => this.onSeriesAreaClickEvent(event)),
|
|
ctx.eventsHub.on("series:keynav-zoom", (event) => this.onNavZoom(event)),
|
|
ctx.widgets.seriesWidget.addListener("wheel", (event) => this.onWheel(event)),
|
|
ctx.widgets.seriesWidget.addListener("touchstart", (event, current) => this.onTouchStart(event, current)),
|
|
ctx.widgets.seriesWidget.addListener("touchmove", (event, current) => this.onTouchMove(event, current)),
|
|
ctx.widgets.seriesWidget.addListener("touchend", (event) => this.onTouchEnd(event)),
|
|
ctx.widgets.seriesWidget.addListener("touchcancel", (event) => this.onTouchEnd(event)),
|
|
ctx.updateService.addListener("process-data", (event) => this.onProcessData(event)),
|
|
ctx.eventsHub.on("layout:complete", (event) => this.onLayoutComplete(event)),
|
|
ctx.eventsHub.on("zoom:change-request", (event) => this.onZoomChangeRequested(event)),
|
|
ctx.eventsHub.on("zoom:pan-start", (event) => this.onZoomPanStart(event)),
|
|
this.panner.addListener("update", (event) => this.onPanUpdate(event)),
|
|
() => this.teardown()
|
|
);
|
|
this.autoScaler = new ZoomAutoScaler(this.autoScaling, ctx.zoomManager, this, ctx.eventsHub, this.cleanup);
|
|
}
|
|
teardown() {
|
|
this.ctx.zoomManager.setZoomModuleEnabled(false);
|
|
this.buttons.destroy();
|
|
this.destroyContextMenuActions?.();
|
|
this.domProxy.destroy();
|
|
this.dataChangeHandler.destroy();
|
|
}
|
|
onEnabledChange(enabled) {
|
|
this.ctx.zoomManager.setZoomModuleEnabled(enabled);
|
|
if (this.contextMenu) {
|
|
this.destroyContextMenuActions?.();
|
|
this.destroyContextMenuActions = this.contextMenu.registerActions(enabled);
|
|
}
|
|
}
|
|
isIgnoredTouch(event) {
|
|
if (event?.device !== "touch") {
|
|
return false;
|
|
}
|
|
if (this.ctx.chartService.touch.dragAction !== "drag") {
|
|
return true;
|
|
}
|
|
if (this.enableSelecting) {
|
|
return false;
|
|
}
|
|
if (!this.enablePanning) {
|
|
return true;
|
|
}
|
|
return isMaxZoom(this.getZoom());
|
|
}
|
|
onSeriesAreaDoubleClick(event) {
|
|
const { enabled, enableDoubleClickToReset } = this;
|
|
if (!enabled || !enableDoubleClickToReset)
|
|
return;
|
|
if (event?.preventZoomDblClick || !this.isState(InteractionState7.ZoomClickable))
|
|
return;
|
|
this.resetZoom("zoom-seriesarea-dblclick");
|
|
}
|
|
onSeriesAreaHoverEvent(event) {
|
|
if (!this.shouldHandleAxisHover(event)) {
|
|
this.clearHoveredAxis();
|
|
return;
|
|
}
|
|
const axis = this.domProxy.pickAxisAtPoint(event);
|
|
if (axis) {
|
|
this.domProxy.setHoveredAxis(axis.axisId);
|
|
this.ctx.domManager.updateCursor(CURSOR_ID, this.domProxy.getCursor(axis.direction));
|
|
} else {
|
|
this.clearHoveredAxis();
|
|
}
|
|
}
|
|
onSeriesAreaClickEvent(event) {
|
|
if (!this.shouldHandleAxisClick(event))
|
|
return;
|
|
const axis = this.domProxy.pickAxisAtPoint(event);
|
|
if (axis && event.sourceEvent?.type === "dblclick") {
|
|
this.onAxisDoubleClick(axis.axisId);
|
|
}
|
|
}
|
|
shouldHandleAxisHover(event) {
|
|
const { enabled, enableAxisDragging, enableAxisScrolling, dragState } = this;
|
|
return enabled && (enableAxisDragging || enableAxisScrolling) && !event.consumed && !this.activeAxis && dragState === 0 /* None */ && this.domProxy.hasOverlappingAxes();
|
|
}
|
|
shouldHandleAxisClick(event) {
|
|
const { enabled, enableAxisDragging, enableAxisScrolling, enableDoubleClickToReset } = this;
|
|
return enabled && !this.activeAxis && !event.consumed && (enableAxisDragging || enableAxisScrolling || enableDoubleClickToReset);
|
|
}
|
|
clearHoveredAxis() {
|
|
if (this.activeAxis)
|
|
return;
|
|
this.domProxy.clearHoveredAxis();
|
|
if (this.dragState === 0 /* None */) {
|
|
this.ctx.domManager.updateCursor(CURSOR_ID);
|
|
}
|
|
}
|
|
tryBeginAxisDelegation(event) {
|
|
const hoveredAxis = this.domProxy.getHoveredAxis();
|
|
if (!this.enabled || !this.enableAxisDragging || !hoveredAxis) {
|
|
return false;
|
|
}
|
|
if (!this.domProxy.beginDelegatedAxisDrag(hoveredAxis.axisId)) {
|
|
return false;
|
|
}
|
|
this.activeAxis = hoveredAxis;
|
|
this.onAxisDragStart(hoveredAxis.direction);
|
|
const cursor = this.dragState === 2 /* Pan */ ? "grabbing" : this.domProxy.getCursor(hoveredAxis.direction);
|
|
this.ctx.domManager.updateCursor(CURSOR_ID, cursor);
|
|
if (event.device === "touch") {
|
|
event.sourceEvent.preventDefault();
|
|
}
|
|
return true;
|
|
}
|
|
onSeriesAreaDragStart(event) {
|
|
const {
|
|
enabled,
|
|
enablePanning,
|
|
enableSelecting,
|
|
ctx: { domManager, zoomManager }
|
|
} = this;
|
|
if (!enabled || !this.isState(InteractionState7.ZoomDraggable) || this.dragState !== 0 /* None */ || this.isIgnoredTouch(event)) {
|
|
return;
|
|
}
|
|
this.panner.stopInteractions();
|
|
if (this.tryBeginAxisDelegation(event)) {
|
|
return;
|
|
}
|
|
let newDragState = 0 /* None */;
|
|
const panKeyPressed = this.isPanningKeyPressed(event.sourceEvent);
|
|
if (enablePanning && (!enableSelecting || panKeyPressed)) {
|
|
domManager.updateCursor(CURSOR_ID, "grabbing");
|
|
newDragState = 2 /* Pan */;
|
|
this.panner.start();
|
|
} else if (enableSelecting && !panKeyPressed) {
|
|
newDragState = 3 /* Select */;
|
|
}
|
|
if ((this.dragState = newDragState) !== 0 /* None */) {
|
|
zoomManager.fireZoomPanStartEvent("zoom");
|
|
}
|
|
}
|
|
onSeriesAreaDragMove(event) {
|
|
const {
|
|
dragState,
|
|
enabled,
|
|
paddedRect,
|
|
panner,
|
|
selector,
|
|
ctx: { interactionManager, tooltipManager, updateService }
|
|
} = this;
|
|
if (this.activeAxis) {
|
|
this.onAxisDragMove(this.activeAxis.axisId, this.activeAxis.direction, event);
|
|
return;
|
|
}
|
|
if (!enabled || !paddedRect || !this.isState(InteractionState7.ZoomDraggable) || this.isIgnoredTouch(event)) {
|
|
return;
|
|
}
|
|
interactionManager.pushState(module_support_exports.InteractionState.ZoomDrag);
|
|
if (event.device === "touch") {
|
|
event.sourceEvent.preventDefault();
|
|
}
|
|
switch (dragState) {
|
|
case 2 /* Pan */:
|
|
panner.update(event);
|
|
break;
|
|
case 3 /* Select */:
|
|
selector.update(event, this.getModuleProperties(), paddedRect);
|
|
break;
|
|
case 0 /* None */:
|
|
return;
|
|
}
|
|
tooltipManager.updateTooltip(TOOLTIP_ID);
|
|
updateService.update(5 /* PERFORM_LAYOUT */, { skipAnimations: true });
|
|
}
|
|
onSeriesAreaDragEnd() {
|
|
const {
|
|
ctx: { interactionManager }
|
|
} = this;
|
|
if (this.activeAxis) {
|
|
this.handleAxisDragEnd();
|
|
return;
|
|
}
|
|
interactionManager.popState(module_support_exports.InteractionState.ZoomDrag);
|
|
if (!this.enabled || this.dragState === 0 /* None */)
|
|
return;
|
|
this.handleRegularDragEnd();
|
|
this.resetDragState();
|
|
}
|
|
handleAxisDragEnd() {
|
|
this.onAxisDragEnd();
|
|
this.domProxy.endDelegatedAxisDrag(this.activeAxis.axisId);
|
|
this.activeAxis = void 0;
|
|
this.clearHoveredAxis();
|
|
}
|
|
handleRegularDragEnd() {
|
|
const { panner, selector } = this;
|
|
switch (this.dragState) {
|
|
case 2 /* Pan */:
|
|
panner.stop();
|
|
break;
|
|
case 3 /* Select */:
|
|
if (selector.didUpdate()) {
|
|
const newZoom = selector.stop(this.seriesRect, this.paddedRect, this.getZoom());
|
|
if (newZoom) {
|
|
this.updateZoom(userInteraction6("zoom-seriesarea-selector"), newZoom);
|
|
} else {
|
|
this.ctx.updateService.update();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
resetDragState() {
|
|
this.dragState = 0 /* None */;
|
|
this.ctx.domManager.updateCursor(CURSOR_ID);
|
|
this.ctx.tooltipManager.removeTooltip(TOOLTIP_ID);
|
|
}
|
|
onAxisDoubleClick(id) {
|
|
const {
|
|
enabled,
|
|
enableDoubleClickToReset,
|
|
ctx: { zoomManager }
|
|
} = this;
|
|
if (!enabled || !enableDoubleClickToReset || !this.isState(InteractionState7.ZoomClickable))
|
|
return;
|
|
this.previousAxisZoomValid = { ["x" /* X */]: true, ["y" /* Y */]: true };
|
|
zoomManager.resetAxisZoom({ source: "user-interaction", sourceDetail: "zoom-axis-dblclick" }, id);
|
|
}
|
|
onAxisDragStart(direction) {
|
|
const {
|
|
axisDraggingMode,
|
|
domProxy,
|
|
enabled,
|
|
enableAxisDragging,
|
|
panner,
|
|
ctx: { zoomManager }
|
|
} = this;
|
|
if (!enabled || !enableAxisDragging)
|
|
return;
|
|
panner.stopInteractions();
|
|
if (axisDraggingMode === "pan") {
|
|
domProxy.setAxisCursor("grabbing");
|
|
this.dragState = 2 /* Pan */;
|
|
this.panner.start(direction);
|
|
zoomManager.fireZoomPanStartEvent("zoom");
|
|
} else {
|
|
this.dragState = 1 /* Axis */;
|
|
}
|
|
}
|
|
onAxisDragMove(axisId, direction, event) {
|
|
const {
|
|
anchorPointX,
|
|
anchorPointY,
|
|
axisDragger,
|
|
dragState,
|
|
enabled,
|
|
enableAxisDragging,
|
|
seriesRect,
|
|
shouldFlipXY,
|
|
ctx: { interactionManager, tooltipManager, updateService, zoomManager }
|
|
} = this;
|
|
if (!enabled || !enableAxisDragging || !seriesRect)
|
|
return;
|
|
interactionManager.pushState(module_support_exports.InteractionState.ZoomDrag);
|
|
if (event.device === "touch") {
|
|
event.sourceEvent.preventDefault();
|
|
}
|
|
const zoom = this.getZoom();
|
|
if (dragState === 2 /* Pan */) {
|
|
this.panner.update({ currentX: event.offsetX, currentY: event.offsetY });
|
|
} else {
|
|
let anchor = direction === "x" /* X */ ? anchorPointX : anchorPointY;
|
|
if (shouldFlipXY)
|
|
anchor = direction === "x" /* X */ ? anchorPointY : anchorPointX;
|
|
const axisZoom = zoomManager.getAxisZoom(axisId);
|
|
const newZoom = axisDragger.update(event, direction, anchor, seriesRect, zoom, axisZoom);
|
|
this.autoScaler.onManualAdjustment(direction);
|
|
this.updateAxisZoom(
|
|
userInteraction6("zoom-axis-drag"),
|
|
axisId,
|
|
direction,
|
|
newZoom,
|
|
{ directional: true }
|
|
);
|
|
}
|
|
tooltipManager.updateTooltip(TOOLTIP_ID);
|
|
updateService.update(5 /* PERFORM_LAYOUT */, { skipAnimations: true });
|
|
}
|
|
onAxisDragEnd() {
|
|
const {
|
|
axisDraggingMode,
|
|
axisDragger,
|
|
dragState,
|
|
domProxy,
|
|
enabled,
|
|
enableAxisDragging,
|
|
ctx: { domManager, interactionManager, tooltipManager }
|
|
} = this;
|
|
interactionManager.popState(module_support_exports.InteractionState.ZoomDrag);
|
|
if (!enabled || !enableAxisDragging || dragState === 0 /* None */)
|
|
return;
|
|
this.dragState = 0 /* None */;
|
|
if (axisDraggingMode === "pan") {
|
|
domProxy.setAxisCursor("grab");
|
|
this.panner.stop();
|
|
}
|
|
axisDragger.stop();
|
|
domManager.updateCursor(CURSOR_ID);
|
|
tooltipManager.removeTooltip(TOOLTIP_ID);
|
|
}
|
|
onNavZoom(event) {
|
|
const { enabled, enableScrolling, scroller } = this;
|
|
const isDefaultState = this.ctx.interactionManager.isState(module_support_exports.InteractionState.Default);
|
|
if (!isDefaultState || !enabled || !enableScrolling)
|
|
return;
|
|
event.widgetEvent.sourceEvent.preventDefault();
|
|
this.updateZoom(
|
|
userInteraction6(`keyboard(${event.delta})`),
|
|
scroller.updateDelta(event.delta, this.getModuleProperties(), this.getZoom())
|
|
);
|
|
}
|
|
onWheel(event) {
|
|
const { enabled, enablePanning, enableScrolling, paddedRect } = this;
|
|
if (!enabled || !enableScrolling || !paddedRect || !this.isState(InteractionState7.ZoomWheelable))
|
|
return;
|
|
const { deltaX, deltaY } = event.sourceEvent;
|
|
const isHorizontalScrolling = deltaX != null && deltaY != null && Math.abs(deltaX) > Math.abs(deltaY);
|
|
if (enablePanning && isHorizontalScrolling) {
|
|
this.onWheelPanning(event);
|
|
} else {
|
|
this.onWheelScrolling(event);
|
|
}
|
|
}
|
|
onWheelPanning(event) {
|
|
const {
|
|
scrollingStep,
|
|
scrollPanner,
|
|
seriesRect,
|
|
ctx: { zoomManager }
|
|
} = this;
|
|
if (!seriesRect)
|
|
return;
|
|
event.sourceEvent.preventDefault();
|
|
const newZooms = scrollPanner.update(event, scrollingStep, seriesRect, zoomManager.getAxisZooms());
|
|
this.updateChanges(userInteraction6("zoom-seriesarea-wheel"), newZooms);
|
|
}
|
|
onWheelScrolling(event) {
|
|
const zoom = this.getZoom();
|
|
const isZoomCapped = event.deltaY > 0 && isMaxZoom(zoom);
|
|
this.wheelSequencer.onWheel(event, () => this.handleWheelScrolling(event, isZoomCapped));
|
|
}
|
|
onAxisWheel(axisDirection, event) {
|
|
if (!this.enableAxisScrolling)
|
|
return;
|
|
if (axisDirection !== "x" /* X */ && axisDirection !== "y" /* Y */) {
|
|
return;
|
|
}
|
|
const isScalingX = axisDirection === "x" /* X */;
|
|
const isScalingY = !isScalingX;
|
|
const props = this.getModuleProperties({ isScalingX, isScalingY });
|
|
const zoom = this.getZoom();
|
|
const isZoomCapped = event.deltaY > 0 && zoom[axisDirection].min === UNIT_MIN && zoom[axisDirection].max === UNIT_MAX;
|
|
this.autoScaler.onManualAdjustment(axisDirection);
|
|
this.wheelSequencer.onWheel(event, () => this.handleWheelScrolling(event, isZoomCapped, props));
|
|
}
|
|
handleWheelScrolling(event, isZoomCapped, props = this.getModuleProperties()) {
|
|
const {
|
|
enableIndependentAxes,
|
|
scroller,
|
|
seriesRect,
|
|
ctx: { zoomManager }
|
|
} = this;
|
|
if (!seriesRect)
|
|
return "abort";
|
|
let updated = true;
|
|
const sourcing = userInteraction6("zoom-axis-wheel");
|
|
if (enableIndependentAxes === true) {
|
|
const newZooms = scroller.updateAxes(event, props, seriesRect, zoomManager.getAxisZooms());
|
|
for (const [axisId, { direction, min, max }] of entries(newZooms)) {
|
|
const constrainedZoom = direction === "x" /* X */ ? this.constrainZoom({ x: { min, max }, y: { min: UNIT_MAX, max: UNIT_MAX } }).x : { min, max };
|
|
updated && (updated = this.updateAxisZoom(sourcing, axisId, direction, constrainedZoom));
|
|
}
|
|
} else {
|
|
const newZoom = scroller.update(event, props, seriesRect, this.getZoom());
|
|
if (newZoom == null)
|
|
return "abort";
|
|
updated = this.updateUnifiedZoom(sourcing, newZoom, { directional: true });
|
|
}
|
|
return isZoomCapped || event.deltaY < 0 && !updated ? "capped" : "uncapped";
|
|
}
|
|
onTouchStart(event, current) {
|
|
if (!this.enableTwoFingerZoom || this.dragState !== 0 /* None */)
|
|
return;
|
|
if (this.twoFingers.start(event, current, this.getZoom())) {
|
|
this.dragState = 4 /* TwoFingers */;
|
|
}
|
|
}
|
|
onTouchMove(event, current) {
|
|
if (!this.enableTwoFingerZoom || this.dragState !== 4 /* TwoFingers */)
|
|
return;
|
|
const newZoom = this.twoFingers.update(event, current);
|
|
this.updateZoom(userInteraction6("zoom-seriesarea-twofingers"), constrainZoom(newZoom));
|
|
}
|
|
onTouchEnd(event) {
|
|
if (!this.enableTwoFingerZoom || this.dragState !== 4 /* TwoFingers */)
|
|
return;
|
|
event.sourceEvent.preventDefault();
|
|
if (this.twoFingers.end(event)) {
|
|
this.dragState = 0 /* None */;
|
|
}
|
|
}
|
|
onProcessData(event) {
|
|
this.shouldFlipXY = event.series.shouldFlipXY;
|
|
}
|
|
onLayoutComplete(event) {
|
|
this.domProxy.update(
|
|
this.enabled,
|
|
this.enableAxisDragging,
|
|
this.enableAxisScrolling,
|
|
this.ctx,
|
|
event.series.rect
|
|
);
|
|
if (!this.enabled)
|
|
return;
|
|
this.seriesRect = event.series.rect;
|
|
this.paddedRect = event.series.paddedRect;
|
|
if (this.enableAxisDragging) {
|
|
this.toggleAxisDraggingCursorsDebounced();
|
|
}
|
|
}
|
|
onZoomChangeRequested(event) {
|
|
if (event.sourceDetail !== "zoom-seriesarea-panner") {
|
|
this.panner.stopInteractions();
|
|
}
|
|
const zoom = this.getZoom();
|
|
this.buttons.toggleVisibleZoomed(isMaxZoom(zoom));
|
|
}
|
|
onZoomPanStart(event) {
|
|
if (event.callerId === "zoom") {
|
|
this.panner.stopInteractions();
|
|
}
|
|
}
|
|
onPanUpdate(event) {
|
|
const {
|
|
panner,
|
|
seriesRect,
|
|
ctx: { tooltipManager, zoomManager }
|
|
} = this;
|
|
if (!seriesRect)
|
|
return;
|
|
const newZooms = panner.translateZooms(seriesRect, zoomManager.getAxisZooms(), event.deltaX, event.deltaY);
|
|
this.updateChanges(userInteraction6("zoom-seriesarea-panner"), newZooms);
|
|
tooltipManager.updateTooltip(TOOLTIP_ID);
|
|
}
|
|
isPanningKeyPressed(event) {
|
|
switch (this.panKey) {
|
|
case "alt":
|
|
return event.altKey;
|
|
case "ctrl":
|
|
return event.ctrlKey;
|
|
case "shift":
|
|
return event.shiftKey;
|
|
case "meta":
|
|
return event.metaKey;
|
|
}
|
|
}
|
|
isScalingX() {
|
|
if (this.axes === "xy")
|
|
return true;
|
|
return this.shouldFlipXY ? this.axes === "y" : this.axes === "x";
|
|
}
|
|
isScalingY() {
|
|
if (this.axes === "xy")
|
|
return true;
|
|
return this.shouldFlipXY ? this.axes === "x" : this.axes === "y";
|
|
}
|
|
getAnchorPointX() {
|
|
return this.shouldFlipXY ? this.anchorPointY : this.anchorPointX;
|
|
}
|
|
getAnchorPointY() {
|
|
return this.shouldFlipXY ? this.anchorPointX : this.anchorPointY;
|
|
}
|
|
constrainZoom(newZoom) {
|
|
return this.ctx.zoomManager.constrainZoomToItemCount(newZoom, this.minVisibleItems, this.autoScaler.enabled);
|
|
}
|
|
isZoomValid(newZoom, options) {
|
|
const {
|
|
minVisibleItems,
|
|
ctx: { zoomManager }
|
|
} = this;
|
|
if (minVisibleItems === 0) {
|
|
this.previousZoomValid = true;
|
|
return true;
|
|
}
|
|
const zoom = this.getZoom();
|
|
const zoomedInX = round3(dx(newZoom)) < round3(dx(zoom));
|
|
const zoomedInY = round3(dy(newZoom)) < round3(dy(zoom));
|
|
if (!zoomedInX && !zoomedInY) {
|
|
this.previousZoomValid = true;
|
|
return true;
|
|
}
|
|
if (!this.previousZoomValid && options?.directional) {
|
|
return false;
|
|
}
|
|
const includeYVisibleRange = options?.includeYVisibleRange ?? false;
|
|
const autoScaleYAxis = this.autoScaler.enabled;
|
|
const valid = zoomManager.isVisibleItemsCountAtLeast(newZoom, minVisibleItems, {
|
|
includeYVisibleRange,
|
|
autoScaleYAxis
|
|
});
|
|
this.previousZoomValid = options?.directional ? valid : true;
|
|
return valid;
|
|
}
|
|
isAxisZoomValid(direction, axisZoom, options) {
|
|
const {
|
|
minVisibleItems,
|
|
ctx: { zoomManager }
|
|
} = this;
|
|
const zoom = this.getZoom();
|
|
const deltaAxis = axisZoom.max - axisZoom.min;
|
|
const deltaOld = zoom[direction].max - zoom[direction].min;
|
|
const newZoom = { ...zoom, [direction]: axisZoom };
|
|
if (deltaAxis >= deltaOld) {
|
|
this.previousAxisZoomValid[direction] = true;
|
|
return true;
|
|
}
|
|
if (!this.previousAxisZoomValid[direction] && options?.directional) {
|
|
return false;
|
|
}
|
|
const opts = { includeYVisibleRange: false, autoScaleYAxis: this.autoScaler.enabled };
|
|
const valid = zoomManager.isVisibleItemsCountAtLeast(newZoom, minVisibleItems, opts);
|
|
this.previousAxisZoomValid[direction] = options?.directional ? valid : true;
|
|
return valid;
|
|
}
|
|
resetZoom(sourceDetail) {
|
|
this.previousZoomValid = true;
|
|
this.previousAxisZoomValid = { ["x" /* X */]: true, ["y" /* Y */]: true };
|
|
this.ctx.zoomManager.resetZoom({ source: "user-interaction", sourceDetail });
|
|
}
|
|
updateSyncZoom(zoom) {
|
|
this.updateZoom({ source: "sync", sourceDetail: "internal-updateSyncZoom" }, zoom);
|
|
}
|
|
updateChanges(sourcing, changes) {
|
|
const partialZoom = this.ctx.zoomManager.toZoomState(changes) ?? {};
|
|
const currentZoom = definedZoomState(this.ctx.zoomManager.getZoom());
|
|
this.updateZoom(sourcing, {
|
|
x: partialZoom.x ?? currentZoom.x,
|
|
y: partialZoom.y ?? currentZoom.y
|
|
});
|
|
}
|
|
updateZoom(sourcing, zoom) {
|
|
if (this.enableIndependentAxes) {
|
|
this.updatePrimaryAxisZooms(sourcing, zoom);
|
|
} else {
|
|
this.updateUnifiedZoom(sourcing, zoom);
|
|
}
|
|
}
|
|
updateUnifiedZoom(sourcing, zoom, validOptions) {
|
|
zoom = this.constrainZoom(zoom);
|
|
if (!this.isZoomValid(zoom, validOptions)) {
|
|
this.ctx.updateService.update(9 /* SCENE_RENDER */, { skipAnimations: true });
|
|
return false;
|
|
}
|
|
this.ctx.zoomManager.updateZoom(sourcing, zoom);
|
|
return true;
|
|
}
|
|
updatePrimaryAxisZooms(sourcing, zoom) {
|
|
this.updatePrimaryAxisZoom(sourcing, zoom, "x" /* X */);
|
|
this.updatePrimaryAxisZoom(sourcing, zoom, "y" /* Y */);
|
|
}
|
|
updatePrimaryAxisZoom(sourcing, zoom, direction) {
|
|
const axisId = this.ctx.zoomManager.getPrimaryAxisId(direction);
|
|
if (axisId == null)
|
|
return;
|
|
this.updateAxisZoom(sourcing, axisId, direction, zoom[direction]);
|
|
}
|
|
updateAxisZoom(sourcing, axisId, direction, axisZoom, validOptions) {
|
|
const {
|
|
enableIndependentAxes,
|
|
ctx: { zoomManager }
|
|
} = this;
|
|
if (!axisZoom)
|
|
return false;
|
|
const zoom = this.getZoom();
|
|
if (enableIndependentAxes !== true) {
|
|
zoom[direction] = axisZoom;
|
|
return this.updateUnifiedZoom(sourcing, zoom, validOptions);
|
|
}
|
|
if (!this.isAxisZoomValid(direction, axisZoom, validOptions))
|
|
return false;
|
|
const { source, sourceDetail } = sourcing;
|
|
zoomManager.updateChanges({ source, sourceDetail, changes: { [axisId]: axisZoom }, isReset: false });
|
|
return true;
|
|
}
|
|
updateAxisCursor(opts) {
|
|
if (!this.domProxy)
|
|
return;
|
|
const {
|
|
enableAxisDragging = this.enableAxisDragging,
|
|
enableAxisScrolling = this.enableAxisScrolling,
|
|
axisDraggingMode = this.axisDraggingMode
|
|
} = opts;
|
|
if (enableAxisDragging) {
|
|
this.domProxy.setAxisCursor(axisDraggingMode === "pan" ? "grab" : void 0);
|
|
} else if (enableAxisScrolling) {
|
|
this.domProxy.setAxisCursor("default");
|
|
} else {
|
|
this.domProxy.setAxisCursor(void 0);
|
|
}
|
|
}
|
|
toggleAxisDraggingCursors() {
|
|
const { anchorPointX, anchorPointY, domProxy } = this;
|
|
const zoom = this.getZoom();
|
|
let showCursorX = dx(zoom) !== UNIT_SIZE;
|
|
let showCursorY = dy(zoom) !== UNIT_SIZE;
|
|
if (!showCursorX) {
|
|
const checkZoomX = scaleZoom(zoom, 0.999, 1);
|
|
checkZoomX.x = scaleZoomAxisWithAnchor(checkZoomX.x, zoom.x, anchorPointX);
|
|
showCursorX = this.isZoomValid(checkZoomX, { includeYVisibleRange: true });
|
|
}
|
|
if (!showCursorY) {
|
|
const checkZoomY = scaleZoom(zoom, 1, 0.999);
|
|
checkZoomY.y = scaleZoomAxisWithAnchor(checkZoomY.y, zoom.y, anchorPointY);
|
|
showCursorY = this.isZoomValid(checkZoomY, { includeYVisibleRange: true });
|
|
}
|
|
domProxy.toggleAxisDraggingCursor("x" /* X */, showCursorX);
|
|
domProxy.toggleAxisDraggingCursor("y" /* Y */, showCursorY);
|
|
}
|
|
getZoom() {
|
|
return definedZoomState(this.ctx.zoomManager.getZoom());
|
|
}
|
|
getModuleProperties(overrides) {
|
|
return {
|
|
anchorPointX: overrides?.anchorPointX ?? this.getAnchorPointX(),
|
|
anchorPointY: overrides?.anchorPointY ?? this.getAnchorPointY(),
|
|
enabled: overrides?.enabled ?? this.enabled,
|
|
independentAxes: overrides?.independentAxes ?? this.enableIndependentAxes === true,
|
|
isScalingX: overrides?.isScalingX ?? this.isScalingX(),
|
|
isScalingY: overrides?.isScalingY ?? this.isScalingY(),
|
|
keepAspectRatio: overrides?.keepAspectRatio ?? this.keepAspectRatio,
|
|
scrollingStep: overrides?.scrollingStep ?? this.scrollingStep
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
newValue(enabled) {
|
|
this.onEnabledChange(enabled);
|
|
}
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue(newValue) {
|
|
this.updateAxisCursor({ enableAxisDragging: newValue });
|
|
}
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "enableAxisDragging", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue(newValue) {
|
|
this.updateAxisCursor({ enableAxisScrolling: newValue });
|
|
}
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "enableAxisScrolling", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "enableDoubleClickToReset", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue(newValue) {
|
|
this.ctx.zoomManager.setIndependentAxes(Boolean(newValue));
|
|
}
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "enableIndependentAxes", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "enablePanning", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "enableScrolling", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "enableSelecting", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "enableTwoFingerZoom", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "panKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "axes", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "scrollingStep", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "keepAspectRatio", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "minVisibleItems", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "anchorPointX", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "anchorPointY", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "autoScaling", 2);
|
|
__decorateClass([
|
|
ActionOnSet({
|
|
changeValue(newValue) {
|
|
this.updateAxisCursor({ axisDraggingMode: newValue });
|
|
}
|
|
}),
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "axisDraggingMode", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "buttons", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "onDataChange", 2);
|
|
__decorateClass([
|
|
ProxyProperty("panner.deceleration"),
|
|
addFakeTransformToInstanceProperty
|
|
], Zoom.prototype, "deceleration", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/zoom/zoomModule.ts
|
|
var zoomAnchorPoint = union("pointer", "start", "middle", "end");
|
|
var ZoomModule = {
|
|
type: "plugin",
|
|
name: "zoom",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: {
|
|
enabled: boolean,
|
|
enableAxisDragging: boolean,
|
|
enableAxisScrolling: boolean,
|
|
enableDoubleClickToReset: boolean,
|
|
enablePanning: boolean,
|
|
enableScrolling: boolean,
|
|
enableSelecting: boolean,
|
|
enableTwoFingerZoom: boolean,
|
|
keepAspectRatio: boolean,
|
|
anchorPointX: zoomAnchorPoint,
|
|
anchorPointY: zoomAnchorPoint,
|
|
axisDraggingMode: union("pan", "zoom"),
|
|
axes: union("x", "y", "xy"),
|
|
deceleration: or(union("off", "short", "long"), ratio),
|
|
minVisibleItems: positiveNumber,
|
|
panKey: union("alt", "ctrl", "meta", "shift"),
|
|
scrollingStep: ratio,
|
|
autoScaling: {
|
|
enabled: boolean,
|
|
padding: ratio
|
|
},
|
|
onDataChange: {
|
|
strategy: strictUnion()("reset", "preserveDomain", "preserveRatios"),
|
|
stickToEnd: boolean
|
|
},
|
|
buttons: {
|
|
enabled: boolean,
|
|
buttons: arrayOfDefs(
|
|
{
|
|
...toolbarButtonOptionsDefs,
|
|
value: union("reset", "zoom-in", "zoom-out", "pan-left", "pan-right", "pan-start", "pan-end"),
|
|
section: string
|
|
},
|
|
"zoom button options array"
|
|
),
|
|
visible: union("always", "zoomed", "hover")
|
|
}
|
|
},
|
|
themeTemplate: {
|
|
enabled: false,
|
|
enableAxisDragging: true,
|
|
enableAxisScrolling: true,
|
|
enableDoubleClickToReset: true,
|
|
enablePanning: true,
|
|
enableScrolling: true,
|
|
enableSelecting: false,
|
|
enableTwoFingerZoom: true,
|
|
deceleration: "short",
|
|
minVisibleItems: 2,
|
|
panKey: "alt",
|
|
scrollingStep: 0.1,
|
|
autoScaling: {
|
|
enabled: {
|
|
$and: [
|
|
{ $eq: [{ $path: "../axes" }, "x"] },
|
|
{ $not: { $eq: [{ $path: "/series/0/direction" }, "horizontal"] } }
|
|
]
|
|
},
|
|
padding: 0.05
|
|
},
|
|
onDataChange: {
|
|
strategy: "preserveDomain",
|
|
stickToEnd: false
|
|
// TODO(olegat): change default to 'true'
|
|
},
|
|
anchorPointX: "end",
|
|
anchorPointY: "middle",
|
|
axes: "x",
|
|
buttons: {
|
|
enabled: { $path: "../enabled" },
|
|
visible: "hover",
|
|
buttons: {
|
|
$shallowSimple: [
|
|
{ icon: "zoom-out", value: "zoom-out", section: "scale" },
|
|
{ icon: "zoom-in", value: "zoom-in", section: "scale" },
|
|
{ icon: "pan-left", value: "pan-left", section: "pan" },
|
|
{ icon: "pan-right", value: "pan-right", section: "pan" },
|
|
{ icon: "reset", value: "reset", section: "reset" }
|
|
]
|
|
}
|
|
}
|
|
},
|
|
create: (ctx) => new Zoom(ctx)
|
|
};
|
|
ZoomModule.options.enableIndependentAxes = undocumented(boolean);
|
|
ZoomModule.options.buttons.anchorPointX = undocumented(zoomAnchorPoint);
|
|
ZoomModule.options.buttons.anchorPointY = undocumented(zoomAnchorPoint);
|
|
|
|
// packages/ag-charts-enterprise/src/utils/formatter.ts
|
|
function formatWithContext(ctx, formatter2, params) {
|
|
return callWithContext(ctx.chartService, formatter2, params);
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/gradient-legend/axisTicks.ts
|
|
var { AxisInterval: AxisInterval2, AxisLabel: AxisLabel3, LinearScale: LinearScale2, BBox: BBox22, TranslatableGroup: TranslatableGroup5, Selection: Selection7, Text: Text5 } = module_support_exports;
|
|
var _AxisTicks = class _AxisTicks {
|
|
constructor(ctx, dataProvider) {
|
|
this.ctx = ctx;
|
|
this.dataProvider = dataProvider;
|
|
this.id = createId(this);
|
|
this.axisGroup = new TranslatableGroup5({ name: `${this.id}-AxisTicks`, zIndex: 3 /* AXIS */ });
|
|
this.labelSelection = Selection7.select(this.axisGroup, Text5);
|
|
this.interval = new AxisInterval2();
|
|
this.label = new AxisLabel3();
|
|
this.scale = new LinearScale2();
|
|
this.placement = "bottom";
|
|
this.translationX = 0;
|
|
this.translationY = 0;
|
|
this.padding = 0;
|
|
}
|
|
get horizontal() {
|
|
return this.placement.startsWith("top") || this.placement.startsWith("bottom");
|
|
}
|
|
attachAxis(axisNode) {
|
|
axisNode.appendChild(this.axisGroup);
|
|
}
|
|
calculateLayout() {
|
|
const { placement, translationX, translationY, horizontal, label } = this;
|
|
function unreachable(_a) {
|
|
return void 0;
|
|
}
|
|
let textBaseline;
|
|
let textAlign;
|
|
switch (placement) {
|
|
case "top":
|
|
case "top-right":
|
|
case "top-left":
|
|
textBaseline = "bottom";
|
|
textAlign = "center";
|
|
label.mirrored = false;
|
|
label.parallel = true;
|
|
break;
|
|
case "bottom":
|
|
case "bottom-right":
|
|
case "bottom-left":
|
|
textBaseline = "top";
|
|
textAlign = "center";
|
|
label.mirrored = false;
|
|
label.parallel = true;
|
|
break;
|
|
case "right":
|
|
case "right-top":
|
|
case "right-bottom":
|
|
case "left":
|
|
case "left-top":
|
|
case "left-bottom":
|
|
textBaseline = "middle";
|
|
textAlign = "left";
|
|
label.mirrored = true;
|
|
label.parallel = false;
|
|
break;
|
|
default:
|
|
unreachable(placement);
|
|
}
|
|
const boxes = [];
|
|
const tickGenerationResult = this.generateTicks();
|
|
const { ticks } = tickGenerationResult;
|
|
this.labelSelection.update(ticks, void 0, (datum) => datum.tickId);
|
|
this.axisGroup.setProperties({ translationX, translationY });
|
|
this.labelSelection.each((node, datum) => {
|
|
node.fontFamily = label.fontFamily;
|
|
node.fontSize = label.fontSize;
|
|
node.fontStyle = label.fontStyle;
|
|
node.fontWeight = label.fontWeight;
|
|
node.fill = label.color;
|
|
node.textBaseline = textBaseline;
|
|
node.textAlign = textAlign;
|
|
node.text = datum.tickLabel;
|
|
node.x = horizontal ? datum.translation : 0;
|
|
node.y = horizontal ? 0 : datum.translation;
|
|
boxes.push(node.getBBox());
|
|
});
|
|
return boxes.length > 0 ? BBox22.merge(boxes).translate(translationX, translationY) : void 0;
|
|
}
|
|
tickFormatter(domain, _ticks, _primary, fractionDigits) {
|
|
const { ctx } = this;
|
|
const { formatManager } = ctx;
|
|
const boundSeries = this.dataProvider.data.flatMap((d) => d.series);
|
|
return (value, index) => {
|
|
const formatParams = {
|
|
type: "number",
|
|
value,
|
|
datum: void 0,
|
|
seriesId: void 0,
|
|
legendItemName: void 0,
|
|
key: void 0,
|
|
source: "gradient-legend",
|
|
property: "color",
|
|
domain,
|
|
boundSeries,
|
|
fractionDigits,
|
|
visibleDomain: void 0
|
|
};
|
|
return this.label.formatValue((fn, params) => formatWithContext(ctx, fn, params), formatParams, index) ?? formatManager.format((fn, params) => formatWithContext(ctx, fn, params), formatParams) ?? formatManager.defaultFormat(formatParams);
|
|
};
|
|
}
|
|
inRange(x, tolerance = 1e-3) {
|
|
const [min, max] = findMinMax(this.scale.range);
|
|
return x >= min - tolerance && x <= max + tolerance;
|
|
}
|
|
generateTicks() {
|
|
const { minSpacing, maxSpacing } = this.interval;
|
|
const { maxTickCount, minTickCount, tickCount } = estimateTickCount(
|
|
findRangeExtent(this.scale.range),
|
|
1,
|
|
minSpacing,
|
|
maxSpacing,
|
|
_AxisTicks.DefaultTickCount,
|
|
_AxisTicks.DefaultMinSpacing
|
|
);
|
|
const tickData = this.getTicksData({
|
|
nice: [true, true],
|
|
interval: this.interval.step,
|
|
tickCount,
|
|
minTickCount,
|
|
maxTickCount
|
|
});
|
|
if (this.placement === "bottom" || this.placement === "top") {
|
|
const measurer3 = cachedTextMeasurer(this.label);
|
|
const { domain } = this.scale;
|
|
const reversed = domain[0] > domain[1];
|
|
const direction = reversed ? -1 : 1;
|
|
let lastTickPosition = -Infinity * direction;
|
|
tickData.ticks = tickData.ticks.filter((data) => {
|
|
if (Math.sign(data.translation - lastTickPosition) !== direction)
|
|
return false;
|
|
const { width: labelWidth } = isArray(data.tickLabel) ? measureTextSegments(data.tickLabel, this.label) : measurer3.measureLines(toTextString(data.tickLabel));
|
|
lastTickPosition = data.translation + labelWidth * direction;
|
|
return true;
|
|
});
|
|
}
|
|
return tickData;
|
|
}
|
|
getTicksData(tickParams) {
|
|
const ticks = [];
|
|
const domain = tickParams.nice ? this.scale.niceDomain(tickParams) : this.scale.domain;
|
|
const rawTicks = this.scale.ticks(tickParams, domain)?.ticks ?? [];
|
|
const fractionDigits = rawTicks.reduce((max, tick) => Math.max(max, countFractionDigits(tick)), 0);
|
|
const idGenerator = createIdsGenerator();
|
|
const tickFormatter = this.tickFormatter(domain, rawTicks, false, fractionDigits);
|
|
for (let index = 0; index < rawTicks.length; index++) {
|
|
const tick = rawTicks[index];
|
|
const translation = this.scale.convert(tick);
|
|
if (!this.inRange(translation))
|
|
continue;
|
|
const tickLabel = tickFormatter(tick, index);
|
|
if (tickLabel == null || tickLabel === "")
|
|
continue;
|
|
const tickId = idGenerator(toPlainText(tickLabel));
|
|
ticks.push({ tick, tickId, tickLabel, translation });
|
|
}
|
|
return { rawTicks, fractionDigits, ticks };
|
|
}
|
|
};
|
|
_AxisTicks.className = "AxisTicks";
|
|
_AxisTicks.DefaultTickCount = 5;
|
|
_AxisTicks.DefaultMinSpacing = 10;
|
|
var AxisTicks = _AxisTicks;
|
|
|
|
// packages/ag-charts-enterprise/src/gradient-legend/gradientLegend.ts
|
|
var { LayoutElement: LayoutElement7, Group: Group12, Rect: Rect9, Marker: Marker3, TranslatableGroup: TranslatableGroup6, BBox: BBox23 } = module_support_exports;
|
|
var GradientBar = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.thickness = 16;
|
|
this.preferredLength = 100;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GradientBar.prototype, "thickness", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GradientBar.prototype, "preferredLength", 2);
|
|
var GradientLegendScale = class extends BaseProperties {
|
|
constructor(axisTicks) {
|
|
super();
|
|
this.axisTicks = axisTicks;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
ProxyProperty("axisTicks.label")
|
|
], GradientLegendScale.prototype, "label", 2);
|
|
__decorateClass([
|
|
ProxyProperty("axisTicks.interval")
|
|
], GradientLegendScale.prototype, "interval", 2);
|
|
__decorateClass([
|
|
ProxyProperty("axisTicks.padding")
|
|
], GradientLegendScale.prototype, "padding", 2);
|
|
var GradientLegend = class extends BaseProperties {
|
|
constructor(ctx) {
|
|
super();
|
|
this.ctx = ctx;
|
|
this.id = createId(this);
|
|
this.legendGroup = new TranslatableGroup6({ name: "legend", zIndex: 16 /* LEGEND */ });
|
|
this.containerNode = this.legendGroup.appendChild(new Rect9({ name: "legend-container" }));
|
|
this.gradientRect = new Rect9();
|
|
this.arrow = new Marker3({ shape: "triangle" });
|
|
this.ticksGroup = new Group12({ name: "legend-axis-group" });
|
|
this.cleanup = new CleanupRegistry();
|
|
this.enabled = false;
|
|
this.position = "bottom";
|
|
this.reverseOrder = false;
|
|
this.gradient = new GradientBar();
|
|
this.spacing = 20;
|
|
this.border = new Border(this.containerNode);
|
|
this.cornerRadius = 0;
|
|
this.fillOpacity = 1;
|
|
this.padding = 4;
|
|
this.data = [];
|
|
this.highlightManager = ctx.highlightManager;
|
|
this.axisTicks = new AxisTicks(ctx, this);
|
|
this.axisTicks.attachAxis(this.ticksGroup);
|
|
this.scale = new GradientLegendScale(this.axisTicks);
|
|
this.legendGroup.append([this.gradientRect, this.arrow, this.ticksGroup]);
|
|
this.cleanup.register(
|
|
ctx.eventsHub.on("highlight:change", () => this.onChartHoverChange()),
|
|
ctx.layoutManager.registerElement(LayoutElement7.Legend, (e) => this.onStartLayout(e)),
|
|
() => this.legendGroup.remove()
|
|
);
|
|
}
|
|
isVertical() {
|
|
const { placement } = expandLegendPosition(this.position);
|
|
return placement.startsWith("right") || placement.startsWith("left");
|
|
}
|
|
destroy() {
|
|
this.cleanup.flush();
|
|
}
|
|
attachLegend(scene) {
|
|
scene.appendChild(this.legendGroup);
|
|
}
|
|
onStartLayout({ layoutBox }) {
|
|
const [data] = this.data;
|
|
if (!this.enabled || !data?.enabled || data.legendType !== "gradient") {
|
|
this.legendGroup.visible = false;
|
|
return;
|
|
}
|
|
const { colorRange } = this.normalizeColorArrays(data);
|
|
const { strokeWidth, padding: padding2 } = this.getContainerStyles();
|
|
const gradientRectBBox = this.updateGradientRect(layoutBox, colorRange);
|
|
const axisBBox = this.updateAxis(data, gradientRectBBox) ?? new BBox23(0, 0, 0, 0);
|
|
const legendBBox = BBox23.merge([gradientRectBBox, axisBBox]);
|
|
legendBBox.grow(padding2).grow(strokeWidth);
|
|
const { left, top } = this.getMeasurements(layoutBox, legendBBox);
|
|
this.updateContainer(legendBBox);
|
|
this.updateArrow();
|
|
this.legendGroup.visible = true;
|
|
this.legendGroup.translationX = left;
|
|
this.legendGroup.translationY = top;
|
|
}
|
|
normalizeColorArrays(data) {
|
|
let colorDomain = data.colorDomain.slice();
|
|
const colorRange = data.colorRange.slice();
|
|
if (colorDomain.length === colorRange.length) {
|
|
return { colorDomain, colorRange };
|
|
}
|
|
if (colorDomain.length > colorRange.length) {
|
|
colorRange.splice(colorDomain.length);
|
|
}
|
|
const [d0, d1] = colorDomain;
|
|
const count = colorRange.length;
|
|
colorDomain = colorRange.map((_, i) => {
|
|
if (i === 0) {
|
|
return d0;
|
|
} else if (i === count - 1) {
|
|
return d1;
|
|
}
|
|
return d0 + (d1 - d0) * i / (count - 1);
|
|
});
|
|
return { colorDomain, colorRange };
|
|
}
|
|
updateGradientRect(shrinkRect, colorRange) {
|
|
const { gradientRect, gradient: gradient2 } = this;
|
|
const { preferredLength, thickness } = gradient2;
|
|
const gradientRectBBox = new BBox23(0, 0, 0, 0);
|
|
const colorCount = Math.max(colorRange.length - 1, 1);
|
|
let angle2;
|
|
if (this.isVertical()) {
|
|
angle2 = 0;
|
|
gradientRectBBox.width = thickness;
|
|
gradientRectBBox.height = Math.min(shrinkRect.height, preferredLength);
|
|
} else {
|
|
angle2 = 90;
|
|
gradientRectBBox.width = Math.min(shrinkRect.width, preferredLength);
|
|
gradientRectBBox.height = thickness;
|
|
}
|
|
gradientRect.x = gradientRectBBox.x;
|
|
gradientRect.y = gradientRectBBox.y;
|
|
gradientRect.width = gradientRectBBox.width;
|
|
gradientRect.height = gradientRectBBox.height;
|
|
gradientRect.fill = {
|
|
type: "gradient",
|
|
gradient: "linear",
|
|
colorSpace: "oklch",
|
|
colorStops: colorRange.map((color2, i) => ({
|
|
stop: i / colorCount,
|
|
color: color2
|
|
})),
|
|
rotation: angle2
|
|
};
|
|
return gradientRectBBox;
|
|
}
|
|
updateAxis(data, gradientRectBBox) {
|
|
const { axisTicks, gradient: gradient2, scale: scale2 } = this;
|
|
const { placement } = expandLegendPosition(this.position);
|
|
const vertical = this.isVertical();
|
|
const positiveAxis = this.reverseOrder !== vertical;
|
|
axisTicks.placement = placement;
|
|
const offset = gradient2.thickness + (scale2.padding ?? 0);
|
|
axisTicks.translationX = vertical ? offset : gradientRectBBox.x;
|
|
axisTicks.translationY = vertical ? gradientRectBBox.y : offset;
|
|
axisTicks.scale.domain = positiveAxis ? data.colorDomain.slice().reverse() : data.colorDomain;
|
|
axisTicks.scale.range = vertical ? [gradientRectBBox.x, gradientRectBBox.height] : [gradientRectBBox.y, gradientRectBBox.width];
|
|
return axisTicks.calculateLayout();
|
|
}
|
|
updateContainer(bbox) {
|
|
const containerStyles = this.getContainerStyles();
|
|
this.containerNode.setStyleProperties(containerStyles);
|
|
this.containerNode.cornerRadius = containerStyles.cornerRadius;
|
|
this.containerNode.x = bbox.x;
|
|
this.containerNode.y = bbox.y;
|
|
this.containerNode.width = bbox.width;
|
|
this.containerNode.height = bbox.height;
|
|
}
|
|
updateArrow() {
|
|
const highlighted = this.highlightManager.getActiveHighlight();
|
|
const { arrow } = this;
|
|
if (highlighted?.colorValue == null) {
|
|
arrow.visible = false;
|
|
return;
|
|
}
|
|
const { scale: scale2, label } = this.axisTicks;
|
|
const size = label.fontSize ?? 0;
|
|
const t = scale2.convert(highlighted.colorValue);
|
|
let { x, y } = this.gradientRect;
|
|
let rotation = Math.PI;
|
|
if (this.isVertical()) {
|
|
x -= size / 2;
|
|
y += t;
|
|
rotation /= 2;
|
|
} else {
|
|
x += t;
|
|
y -= size / 2;
|
|
}
|
|
arrow.visible = true;
|
|
arrow.fill = label.color;
|
|
arrow.rotation = rotation;
|
|
arrow.size = size;
|
|
arrow.translationX = x;
|
|
arrow.translationY = y;
|
|
}
|
|
getMeasurements(shrinkRect, legendBBox) {
|
|
const unreachable = (_a) => {
|
|
return void 0;
|
|
};
|
|
let { x: left, y: top } = shrinkRect;
|
|
const { width: width2, height: height2 } = legendBBox;
|
|
const { placement, floating, xOffset, yOffset } = expandLegendPosition(this.position);
|
|
const containerStyles = this.getContainerStyles();
|
|
left += containerStyles.strokeWidth + containerStyles.padding.left;
|
|
top += containerStyles.strokeWidth + containerStyles.padding.top;
|
|
switch (placement) {
|
|
case "left":
|
|
top += shrinkRect.height / 2 - height2 / 2;
|
|
break;
|
|
case "right":
|
|
left += shrinkRect.width - width2;
|
|
top += shrinkRect.height / 2 - height2 / 2;
|
|
break;
|
|
case "top":
|
|
left += shrinkRect.width / 2 - width2 / 2;
|
|
break;
|
|
case "bottom":
|
|
left += shrinkRect.width / 2 - width2 / 2;
|
|
top += shrinkRect.height - height2;
|
|
break;
|
|
case "right-top":
|
|
case "top-right":
|
|
left += shrinkRect.width - width2;
|
|
break;
|
|
case "right-bottom":
|
|
case "bottom-right":
|
|
left += shrinkRect.width - width2;
|
|
top += shrinkRect.height - height2;
|
|
break;
|
|
case "left-bottom":
|
|
case "bottom-left":
|
|
top += shrinkRect.height - height2;
|
|
break;
|
|
case "left-top":
|
|
case "top-left":
|
|
break;
|
|
default:
|
|
unreachable(placement);
|
|
}
|
|
if (!floating) {
|
|
switch (placement) {
|
|
case "left":
|
|
case "left-top":
|
|
case "left-bottom":
|
|
shrinkRect.shrink(width2 + this.spacing, "left");
|
|
break;
|
|
case "right":
|
|
case "right-top":
|
|
case "right-bottom":
|
|
shrinkRect.shrink(width2 + this.spacing, "right");
|
|
break;
|
|
case "top":
|
|
case "top-left":
|
|
case "top-right":
|
|
shrinkRect.shrink(height2 + this.spacing, "top");
|
|
break;
|
|
case "bottom":
|
|
case "bottom-left":
|
|
case "bottom-right":
|
|
shrinkRect.shrink(height2 + this.spacing, "bottom");
|
|
break;
|
|
default:
|
|
unreachable(placement);
|
|
}
|
|
}
|
|
left += xOffset;
|
|
top += yOffset;
|
|
return { top, left };
|
|
}
|
|
getContainerStyles() {
|
|
const { stroke: stroke3, strokeOpacity, strokeWidth } = this.border;
|
|
const { cornerRadius, fill, fillOpacity, padding: padding2 } = this;
|
|
const isPaddingNumber = typeof padding2 === "number";
|
|
return {
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
padding: {
|
|
top: isPaddingNumber ? padding2 : padding2.top ?? 0,
|
|
right: isPaddingNumber ? padding2 : padding2.right ?? 0,
|
|
bottom: isPaddingNumber ? padding2 : padding2.bottom ?? 0,
|
|
left: isPaddingNumber ? padding2 : padding2.left ?? 0
|
|
},
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth: this.border.enabled ? strokeWidth : 0
|
|
};
|
|
}
|
|
onChartHoverChange() {
|
|
if (!this.enabled)
|
|
return;
|
|
this.updateArrow();
|
|
}
|
|
};
|
|
GradientLegend.className = "GradientLegend";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GradientLegend.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GradientLegend.prototype, "position", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GradientLegend.prototype, "reverseOrder", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GradientLegend.prototype, "gradient", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GradientLegend.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GradientLegend.prototype, "border", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GradientLegend.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GradientLegend.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GradientLegend.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GradientLegend.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GradientLegend.prototype, "scale", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/gradient-legend/gradientLegendModule.ts
|
|
var GradientLegendModule = {
|
|
type: "plugin",
|
|
name: "gradientLegend",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
// removable: 'standalone-only',
|
|
options: {
|
|
enabled: boolean,
|
|
position: legendPositionValidator,
|
|
spacing: positiveNumber,
|
|
reverseOrder: boolean,
|
|
border: borderOptionsDef,
|
|
cornerRadius: number,
|
|
padding,
|
|
fill: colorUnion,
|
|
fillOpacity: ratio,
|
|
gradient: {
|
|
preferredLength: positiveNumber,
|
|
thickness: positiveNumber
|
|
},
|
|
scale: {
|
|
label: {
|
|
...fontOptionsDef,
|
|
minSpacing: positiveNumber,
|
|
format: numberFormatValidator,
|
|
formatter: callback
|
|
},
|
|
padding: positiveNumber,
|
|
interval: {
|
|
step: number,
|
|
values: array,
|
|
minSpacing: and(positiveNumber, lessThan("maxSpacing")),
|
|
maxSpacing: and(positiveNumber, greaterThan("minSpacing"))
|
|
}
|
|
}
|
|
},
|
|
themeTemplate: {
|
|
...LEGEND_CONTAINER_THEME,
|
|
enabled: false,
|
|
position: "bottom",
|
|
spacing: 20,
|
|
scale: {
|
|
padding: 13,
|
|
label: {
|
|
color: { $ref: "textColor" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
minSpacing: 5
|
|
},
|
|
interval: {
|
|
minSpacing: 15
|
|
}
|
|
},
|
|
gradient: {
|
|
preferredLength: 100,
|
|
thickness: 16
|
|
},
|
|
reverseOrder: false,
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $ref: "chartBackgroundColour" },
|
|
["gradient", FILL_GRADIENT_BLANK_DEFAULTS],
|
|
["pattern", FILL_PATTERN_BLANK_DEFAULTS],
|
|
["image", FILL_IMAGE_BLANK_DEFAULTS]
|
|
]
|
|
}
|
|
},
|
|
create: (ctx) => {
|
|
const moduleInstance = new GradientLegend(ctx);
|
|
moduleInstance.attachLegend(ctx.scene);
|
|
return moduleInstance;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/axes/ordinal/ordinalTimeAxis.ts
|
|
var {
|
|
OrdinalTimeScale: OrdinalTimeScale2,
|
|
ApproximateOrdinalTimeScale: ApproximateOrdinalTimeScale2,
|
|
APPROXIMATE_THRESHOLD: APPROXIMATE_THRESHOLD4,
|
|
TimeAxisParentLevel: TimeAxisParentLevel2,
|
|
minimumTimeAxisDatumGranularity: minimumTimeAxisDatumGranularity2
|
|
} = module_support_exports;
|
|
var OrdinalTimeAxis = class extends module_support_exports.DiscreteTimeAxis {
|
|
constructor(moduleCtx) {
|
|
const accurateScale = new OrdinalTimeScale2();
|
|
super(moduleCtx, accurateScale);
|
|
this.parentLevel = new TimeAxisParentLevel2();
|
|
this.accurateScale = accurateScale;
|
|
this.approximateScale = new ApproximateOrdinalTimeScale2();
|
|
this.approximateScale.setSourceScale(accurateScale);
|
|
Object.defineProperty(this, "scale", {
|
|
get: () => this.getActiveScale(),
|
|
configurable: true
|
|
});
|
|
}
|
|
get primaryLabel() {
|
|
return this.parentLevel.enabled ? this.parentLevel.label : void 0;
|
|
}
|
|
get primaryTick() {
|
|
return this.parentLevel.enabled ? this.parentLevel.tick : void 0;
|
|
}
|
|
/**
|
|
* Returns the active scale based on visible range and data uniformity.
|
|
* Use approximate scale when data is uniform and visible datum count is large.
|
|
*/
|
|
getActiveScale() {
|
|
const visibleBandCount = this.accurateScale.bandCount(this.visibleRange);
|
|
const isUniform = this.accurateScale.getUniformityCache(this.visibleRange)?.isUniform ?? false;
|
|
if (isUniform && visibleBandCount >= APPROXIMATE_THRESHOLD4) {
|
|
return this.approximateScale;
|
|
}
|
|
return this.accurateScale;
|
|
}
|
|
processData() {
|
|
super.processData();
|
|
const { boundSeries, direction } = this;
|
|
this.minimumTimeGranularity = minimumTimeAxisDatumGranularity2(boundSeries, direction, void 0, void 0);
|
|
}
|
|
tickFormatParams(domain, ticks, _fractionDigits, timeInterval3) {
|
|
timeInterval3 ?? (timeInterval3 = lowestGranularityUnitForTicks(ticks));
|
|
const truncateDate = dateTruncationForDomain(domain);
|
|
const unit = intervalUnit(timeInterval3);
|
|
const step = intervalStep(timeInterval3);
|
|
const epoch = intervalEpoch(timeInterval3);
|
|
return { type: "date", unit, step, epoch, truncateDate };
|
|
}
|
|
datumFormatParams(value, params, _fractionDigits, timeInterval3, style2) {
|
|
if (typeof value === "number")
|
|
value = new Date(value);
|
|
if (timeInterval3 == null) {
|
|
const { minimumTimeGranularity } = this;
|
|
const datumGranularity = lowestGranularityUnitForValue(value);
|
|
if (minimumTimeGranularity != null && intervalMilliseconds(minimumTimeGranularity) < intervalMilliseconds(datumGranularity)) {
|
|
timeInterval3 = minimumTimeGranularity;
|
|
} else {
|
|
timeInterval3 = datumGranularity;
|
|
}
|
|
}
|
|
const { datum, seriesId, legendItemName, key, source, property, domain, boundSeries } = params;
|
|
const unit = intervalUnit(timeInterval3);
|
|
const step = intervalStep(timeInterval3);
|
|
const epoch = intervalEpoch(timeInterval3);
|
|
return {
|
|
type: "date",
|
|
value,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key,
|
|
source,
|
|
property,
|
|
domain,
|
|
boundSeries,
|
|
unit,
|
|
step,
|
|
epoch,
|
|
style: style2
|
|
};
|
|
}
|
|
};
|
|
OrdinalTimeAxis.className = "OrdinalTimeAxis";
|
|
OrdinalTimeAxis.type = "ordinal-time";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], OrdinalTimeAxis.prototype, "parentLevel", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/axes/ordinal/ordinalTimeAxisModule.ts
|
|
var OrdinalTimeAxisModule = {
|
|
type: "axis",
|
|
name: "ordinal-time",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: module_support_exports.ordinalTimeAxisOptionsDefs,
|
|
themeTemplate: {
|
|
groupPaddingInner: 0,
|
|
label: { autoRotate: false, minSpacing: 40 },
|
|
gridLine: { enabled: false },
|
|
interval: { placement: "between" }
|
|
},
|
|
create: (ctx) => new OrdinalTimeAxis(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/module-bundles/cartesian-axes.ts
|
|
var AllCartesianAxesModule2 = [
|
|
AllCartesianAxesModule,
|
|
OrdinalTimeAxisModule
|
|
].flat();
|
|
|
|
// packages/ag-charts-enterprise/src/series/funnel/funnelThemes.ts
|
|
var isHorizontal = { $eq: [{ $path: ["/series/0/direction", void 0] }, "horizontal"] };
|
|
var labelOptions = { $clone: { $omit: [["placement", "spacing"], { $path: "/series/0/stageLabel" }] } };
|
|
var FUNNEL_SERIES_AXES = {
|
|
y: {
|
|
type: {
|
|
$if: [isHorizontal, "number" /* NUMBER */, "category" /* CATEGORY */]
|
|
},
|
|
position: {
|
|
$if: [
|
|
isHorizontal,
|
|
"left" /* LEFT */,
|
|
{
|
|
$if: [
|
|
{ $eq: [{ $path: ["/series/0/stageLabel/placement", void 0] }, "after"] },
|
|
"right" /* RIGHT */,
|
|
"left" /* LEFT */
|
|
]
|
|
}
|
|
]
|
|
},
|
|
label: {
|
|
$if: [isHorizontal, void 0, labelOptions]
|
|
}
|
|
},
|
|
x: {
|
|
type: {
|
|
$if: [isHorizontal, "category" /* CATEGORY */, "number" /* NUMBER */]
|
|
},
|
|
position: {
|
|
$if: [
|
|
isHorizontal,
|
|
{
|
|
$if: [
|
|
{ $eq: [{ $path: ["/series/0/stageLabel/placement", void 0] }, "before"] },
|
|
"top" /* TOP */,
|
|
"bottom" /* BOTTOM */
|
|
]
|
|
},
|
|
"bottom" /* BOTTOM */
|
|
]
|
|
},
|
|
label: {
|
|
$if: [isHorizontal, labelOptions, void 0]
|
|
}
|
|
}
|
|
};
|
|
var FUNNEL_SERIES_THEME = {
|
|
series: {
|
|
direction: "vertical",
|
|
strokeWidth: { $isUserOption: ["./strokes/0", 2, 0] },
|
|
spacingRatio: 0.25,
|
|
fills: {
|
|
$applyCycle: [
|
|
{ $size: { $path: ["./data", { $path: "/data" }] } },
|
|
[{ $path: ["/0", void 0, { $palette: "fills" }] }],
|
|
{
|
|
$applySwitch: [
|
|
{ $path: ["/type", void 0, { $value: "$1" }] },
|
|
{ $value: "$1" },
|
|
["gradient", FILL_GRADIENT_LINEAR_SINGLE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_SINGLE_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS]
|
|
]
|
|
}
|
|
]
|
|
},
|
|
strokes: {
|
|
$applyCycle: [
|
|
{ $size: { $path: ["./data", { $path: "/data" }] } },
|
|
[{ $path: ["/0", void 0, { $palette: "strokes" }] }]
|
|
]
|
|
},
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "chartBackgroundColor" }
|
|
},
|
|
dropOff: {
|
|
enabled: true,
|
|
fillOpacity: 0.2,
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, 0] }
|
|
},
|
|
shadow: {
|
|
enabled: false,
|
|
color: DEFAULT_SHADOW_COLOUR,
|
|
xOffset: 3,
|
|
yOffset: 3,
|
|
blur: 5
|
|
},
|
|
highlight: {
|
|
unhighlightedItem: {
|
|
opacity: 0.6
|
|
}
|
|
}
|
|
},
|
|
axes: {
|
|
["number" /* NUMBER */]: {
|
|
nice: false,
|
|
gridLine: {
|
|
enabled: false
|
|
},
|
|
crosshair: {
|
|
enabled: false
|
|
},
|
|
label: {
|
|
enabled: false
|
|
}
|
|
},
|
|
["category" /* CATEGORY */]: {
|
|
line: {
|
|
enabled: false
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/funnel/funnelUtil.ts
|
|
var { NODE_UPDATE_STATE_TO_PHASE_MAPPING: NODE_UPDATE_STATE_TO_PHASE_MAPPING2 } = module_support_exports;
|
|
function connectorStartingPosition(datum, _prevDatum, isVertical, _mode) {
|
|
const { x0, y0, x1, y1, x2, y2, x3, y3, opacity } = datum;
|
|
if (isVertical) {
|
|
return {
|
|
x0: (x0 + x3) / 2,
|
|
y0: (y0 + y3) / 2,
|
|
x1: (x1 + x2) / 2,
|
|
y1: (y1 + y2) / 2,
|
|
x2: (x1 + x2) / 2,
|
|
y2: (y1 + y2) / 2,
|
|
x3: (x0 + x3) / 2,
|
|
y3: (y0 + y3) / 2,
|
|
opacity
|
|
};
|
|
} else {
|
|
return {
|
|
x0: (x0 + x1) / 2,
|
|
y0: (y0 + y1) / 2,
|
|
x1: (x0 + x1) / 2,
|
|
y1: (y0 + y1) / 2,
|
|
x2: (x2 + x3) / 2,
|
|
y2: (y2 + y3) / 2,
|
|
x3: (x2 + x3) / 2,
|
|
y3: (y2 + y3) / 2,
|
|
opacity
|
|
};
|
|
}
|
|
}
|
|
function prepareConnectorAnimationFunctions(isVertical, mode) {
|
|
const isRemoved = (datum) => datum == null;
|
|
const fromFn = (connector, datum, status) => {
|
|
if (status === "updated" && isRemoved(datum)) {
|
|
status = "removed";
|
|
} else if (status === "updated" && isRemoved(connector.previousDatum)) {
|
|
status = "added";
|
|
}
|
|
let source;
|
|
if (status === "added" && connector.previousDatum == null && mode === "fade") {
|
|
source = { ...resetConnectorSelectionsFn(connector, datum), opacity: 0 };
|
|
} else if (status === "unknown" || status === "added") {
|
|
source = connectorStartingPosition(datum, connector.previousDatum, isVertical, mode);
|
|
} else {
|
|
source = {
|
|
x0: connector.x0,
|
|
y0: connector.y0,
|
|
x1: connector.x1,
|
|
y1: connector.y1,
|
|
x2: connector.x2,
|
|
y2: connector.y2,
|
|
x3: connector.x3,
|
|
y3: connector.y3,
|
|
opacity: connector.opacity
|
|
};
|
|
}
|
|
const phase = NODE_UPDATE_STATE_TO_PHASE_MAPPING2[status];
|
|
return { ...source, phase };
|
|
};
|
|
const toFn = (connector, datum, status) => {
|
|
let source;
|
|
if (status === "removed" && connector.datum == null && mode === "fade") {
|
|
source = { ...resetConnectorSelectionsFn(connector, datum), opacity: 0 };
|
|
} else if (status === "removed" || isRemoved(datum)) {
|
|
source = connectorStartingPosition(datum, connector.previousDatum, isVertical, mode);
|
|
} else {
|
|
source = resetConnectorSelectionsFn(connector, datum);
|
|
}
|
|
return source;
|
|
};
|
|
return { fromFn, toFn };
|
|
}
|
|
function resetConnectorSelectionsFn(_node, datum) {
|
|
const { x0, y0, x1, y1, x2, y2, x3, y3, opacity } = datum;
|
|
return { x0, y0, x1, y1, x2, y2, x3, y3, opacity };
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/funnel/baseFunnelSeries.ts
|
|
var {
|
|
SeriesNodePickMode: SeriesNodePickMode10,
|
|
valueProperty: valueProperty12,
|
|
keyProperty: keyProperty9,
|
|
updateLabelNode: updateLabelNode6,
|
|
SMALLEST_KEY_INTERVAL: SMALLEST_KEY_INTERVAL5,
|
|
LARGEST_KEY_INTERVAL: LARGEST_KEY_INTERVAL3,
|
|
diff: diff6,
|
|
fixNumericExtent: fixNumericExtent8,
|
|
seriesLabelFadeInAnimation: seriesLabelFadeInAnimation6,
|
|
resetMotion: resetMotion3,
|
|
resetLabelFn: resetLabelFn5,
|
|
animationValidation: animationValidation7,
|
|
computeBarFocusBounds: computeBarFocusBounds7,
|
|
Group: Group13,
|
|
Selection: Selection8,
|
|
PointerEvents: PointerEvents7,
|
|
motion: motion5,
|
|
checkCrisp: checkCrisp4,
|
|
createDatumId: createDatumId13
|
|
} = module_support_exports;
|
|
var FunnelSeriesNodeEvent = class extends module_support_exports.SeriesNodeEvent {
|
|
constructor(type, nativeEvent, datum, series) {
|
|
super(type, nativeEvent, datum, series);
|
|
this.xKey = series.properties.stageKey;
|
|
this.yKey = series.properties.valueKey;
|
|
}
|
|
};
|
|
var BaseFunnelSeries = class extends module_support_exports.AbstractBarSeries {
|
|
constructor({
|
|
moduleCtx,
|
|
animationResetFns
|
|
}) {
|
|
super({
|
|
moduleCtx,
|
|
pickModes: [SeriesNodePickMode10.AXIS_ALIGNED, SeriesNodePickMode10.EXACT_SHAPE_MATCH],
|
|
propertyKeys: {
|
|
x: ["stageKey"],
|
|
y: ["valueKey"]
|
|
},
|
|
propertyNames: {
|
|
x: [],
|
|
y: []
|
|
},
|
|
categoryKey: "xValue",
|
|
datumSelectionGarbageCollection: false,
|
|
animationResetFns: {
|
|
datum: animationResetFns.datum,
|
|
label: resetLabelFn5
|
|
}
|
|
});
|
|
// @ts-expect-error xKey/yKey renamed
|
|
this.NodeEvent = FunnelSeriesNodeEvent;
|
|
this.connectorNodeGroup = this.contentGroup.appendChild(
|
|
new Group13({
|
|
name: `${this.id}-series-connectorNodes`,
|
|
zIndex: 0 /* BACKGROUND */
|
|
})
|
|
);
|
|
this.connectorSelection = Selection8.select(
|
|
this.connectorNodeGroup,
|
|
() => this.connectionFactory()
|
|
);
|
|
this.connectorNodeGroup.pointerEvents = PointerEvents7.None;
|
|
}
|
|
get pickModeAxis() {
|
|
return "main-category";
|
|
}
|
|
setZIndex(zIndex) {
|
|
super.setZIndex(zIndex);
|
|
this.connectorNodeGroup.zIndex = [0 /* BACKGROUND */, zIndex];
|
|
return true;
|
|
}
|
|
isVertical() {
|
|
return !super.isVertical();
|
|
}
|
|
connectionFactory() {
|
|
return new FunnelConnector();
|
|
}
|
|
getKeyAxis(direction) {
|
|
if (direction === "x" /* X */)
|
|
return this.properties.xKeyAxis;
|
|
if (direction === "y" /* Y */)
|
|
return this.properties.yKeyAxis;
|
|
}
|
|
async processData(dataController) {
|
|
const { stageKey, valueKey } = this.properties;
|
|
const { visible, id: seriesId } = this;
|
|
const validation = (_value, _datum, index) => visible && this.ctx.legendManager.getItemEnabled({ seriesId, itemId: index });
|
|
const xScale = this.getCategoryAxis()?.scale;
|
|
const yScale = this.getValueAxis()?.scale;
|
|
const { isContinuousX, xScaleType, yScaleType } = this.getScaleInformation({ xScale, yScale });
|
|
const extraProps = [];
|
|
if (this.needsDataModelDiff() && this.processedData) {
|
|
extraProps.push(diff6(this.id, this.processedData));
|
|
}
|
|
if (!this.ctx.animationManager.isSkipped()) {
|
|
extraProps.push(animationValidation7());
|
|
}
|
|
const visibleProps = this.visible ? {} : { forceValue: 0 };
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
const { processedData } = await this.requestDataModel(dataController, this.data, {
|
|
props: [
|
|
keyProperty9(stageKey, xScaleType, { id: "xValue", allowNullKey }),
|
|
valueProperty12(valueKey, yScaleType, { id: `yValue`, ...visibleProps, validation, invalidValue: 0 }),
|
|
...isContinuousX ? [SMALLEST_KEY_INTERVAL5, LARGEST_KEY_INTERVAL3] : [],
|
|
...extraProps
|
|
],
|
|
groupByKeys: false
|
|
});
|
|
this.smallestDataInterval = processedData.reduced?.smallestKeyInterval;
|
|
this.largestDataInterval = processedData.reduced?.largestKeyInterval;
|
|
this.animationState.transition("updateData");
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const {
|
|
processedData,
|
|
dataModel,
|
|
id: seriesId,
|
|
ctx: { legendManager }
|
|
} = this;
|
|
if (!processedData || !dataModel)
|
|
return { domain: [] };
|
|
const {
|
|
keys: [keys]
|
|
} = processedData.domain;
|
|
if (direction === this.getCategoryDirection()) {
|
|
const keyDef = dataModel.resolveProcessedDataDefById(this, `xValue`);
|
|
if (keyDef?.def.type === "key" && keyDef?.def.valueType === "category") {
|
|
if (!this.hasData)
|
|
return { domain: [] };
|
|
const domain = keys.filter((_key, index) => legendManager.getItemEnabled({ seriesId, itemId: index }));
|
|
const sortMetadata = dataModel.getKeySortMetadata(this, "xValue", processedData);
|
|
return { domain, sortMetadata };
|
|
}
|
|
return { domain: this.padBandExtent(keys) };
|
|
} else {
|
|
const yExtent = this.domainForClippedRange(direction, ["yValue"], "xValue");
|
|
const maxExtent = Math.max(...yExtent);
|
|
const fixedYExtent = [-maxExtent, maxExtent];
|
|
return { domain: fixNumericExtent8(fixedYExtent) };
|
|
}
|
|
}
|
|
getSeriesRange(_direction, _visibleRange) {
|
|
return [Number.NaN, Number.NaN];
|
|
}
|
|
createNodeData() {
|
|
const {
|
|
hasData,
|
|
data,
|
|
dataModel,
|
|
processedData,
|
|
id: seriesId,
|
|
ctx: { legendManager }
|
|
} = this;
|
|
const xAxis = this.getCategoryAxis();
|
|
const yAxis = this.getValueAxis();
|
|
if (!(hasData && data && xAxis && yAxis && dataModel && processedData?.type === "ungrouped")) {
|
|
return;
|
|
}
|
|
const xScale = xAxis.scale;
|
|
const yScale = yAxis.scale;
|
|
const barAlongX = this.getBarDirection() === "x" /* X */;
|
|
const { stageKey, valueKey } = this.properties;
|
|
const itemId = `${valueKey}`;
|
|
const context = {
|
|
itemId,
|
|
nodeData: [],
|
|
labelData: [],
|
|
connectorData: [],
|
|
scales: this.calculateScaling(),
|
|
groupScale: this.getScaling(this.ctx.seriesStateManager.getGroupScale(this)),
|
|
visible: this.visible
|
|
};
|
|
const isVisible = this.visible;
|
|
if (!isVisible)
|
|
return context;
|
|
const xValues = dataModel.resolveKeysById(this, "xValue", processedData);
|
|
const yValues = dataModel.resolveColumnById(this, `yValue`, processedData);
|
|
const { groupOffset, barOffset, barWidth } = this.getBarDimensions();
|
|
const crisp = checkCrisp4(
|
|
xAxis?.scale,
|
|
xAxis?.visibleRange,
|
|
this.smallestDataInterval,
|
|
this.largestDataInterval
|
|
);
|
|
let previousConnection;
|
|
const rawData = processedData.dataSources.get(this.id)?.data ?? [];
|
|
for (const [datumIndex, datum] of rawData.entries()) {
|
|
const visible = isVisible && legendManager.getItemEnabled({ seriesId, itemId: datumIndex });
|
|
const xDatum = xValues[datumIndex];
|
|
if (xDatum === void 0 && !this.properties.allowNullKeys)
|
|
continue;
|
|
const xConverted = xScale.convert(xDatum);
|
|
if (!Number.isFinite(xConverted))
|
|
continue;
|
|
const x = Math.round(xConverted) + groupOffset + barOffset;
|
|
const yDatum = yValues[datumIndex];
|
|
const yNegative = Math.round(yScale.convert(-yDatum));
|
|
const yPositive = Math.round(yScale.convert(yDatum));
|
|
const style2 = this.getItemStyle({ datum, datumIndex }, false);
|
|
const barHeight = Math.max(style2.strokeWidth ?? 0, Math.abs(yPositive - yNegative));
|
|
const rect2 = {
|
|
x: barAlongX ? Math.min(yPositive, yNegative) : x,
|
|
y: barAlongX ? x : Math.min(yPositive, yNegative),
|
|
width: barAlongX ? barHeight : barWidth,
|
|
height: barAlongX ? barWidth : barHeight
|
|
};
|
|
const nodeMidPoint = {
|
|
x: rect2.x + rect2.width / 2,
|
|
y: rect2.y + rect2.height / 2
|
|
};
|
|
const labelData = this.createLabelData({
|
|
datumIndex,
|
|
rect: rect2,
|
|
barAlongX,
|
|
yDatum,
|
|
datum,
|
|
visible
|
|
});
|
|
const nodeDatum = {
|
|
index: datumIndex,
|
|
series: this,
|
|
datum,
|
|
datumIndex,
|
|
xValue: xDatum,
|
|
yValue: yDatum,
|
|
xKey: stageKey,
|
|
yKey: valueKey,
|
|
x: rect2.x,
|
|
y: rect2.y,
|
|
width: rect2.width,
|
|
height: rect2.height,
|
|
midPoint: nodeMidPoint,
|
|
crisp,
|
|
label: labelData,
|
|
visible
|
|
};
|
|
context.nodeData.push(nodeDatum);
|
|
if (labelData != null) {
|
|
context.labelData.push(labelData);
|
|
}
|
|
if (previousConnection != null) {
|
|
const prevRect = previousConnection.rect;
|
|
const startNodeDatum = previousConnection.nodeDatum;
|
|
const startDatumIndex = previousConnection.datumIndex;
|
|
if (barAlongX) {
|
|
context.connectorData.push({
|
|
datum: startNodeDatum,
|
|
datumIndex: startDatumIndex,
|
|
x0: prevRect.x,
|
|
y0: prevRect.y + prevRect.height,
|
|
x1: prevRect.x + prevRect.width,
|
|
y1: prevRect.y + prevRect.height,
|
|
x2: rect2.x + rect2.width,
|
|
y2: rect2.y,
|
|
x3: rect2.x,
|
|
y3: rect2.y,
|
|
opacity: 1
|
|
});
|
|
} else {
|
|
context.connectorData.push({
|
|
datum: startNodeDatum,
|
|
datumIndex: startDatumIndex,
|
|
x0: prevRect.x + prevRect.width,
|
|
y0: prevRect.y,
|
|
x1: rect2.x,
|
|
y1: rect2.y,
|
|
x2: rect2.x,
|
|
y2: rect2.y + rect2.height,
|
|
x3: prevRect.x + prevRect.width,
|
|
y3: prevRect.y + prevRect.height,
|
|
opacity: 1
|
|
});
|
|
}
|
|
}
|
|
if (visible) {
|
|
previousConnection = {
|
|
itemId,
|
|
rect: rect2,
|
|
nodeDatum,
|
|
datumIndex
|
|
};
|
|
}
|
|
}
|
|
return context;
|
|
}
|
|
updateNodes(seriesHighlighted, nodeRefresh) {
|
|
super.updateNodes(seriesHighlighted, nodeRefresh);
|
|
const { connectorSelection } = this;
|
|
const connectorData = this.contextNodeData?.connectorData ?? [];
|
|
this.connectorSelection = this.updateConnectorSelection({ connectorSelection, connectorData });
|
|
this.updateConnectorNodes({ connectorSelection });
|
|
}
|
|
updateDatumSelection(opts) {
|
|
const { nodeData, datumSelection } = opts;
|
|
const data = nodeData ?? [];
|
|
return datumSelection.update(data, void 0, (datum) => this.getDatumId(datum));
|
|
}
|
|
updateConnectorSelection(opts) {
|
|
const { connectorData, connectorSelection } = opts;
|
|
return connectorSelection.update(
|
|
this.connectorEnabled() ? connectorData : [],
|
|
void 0,
|
|
(connector) => this.getDatumId(connector.datum)
|
|
);
|
|
}
|
|
updateConnectorNodes(opts) {
|
|
const fillBBox = this.getShapeFillBBox();
|
|
opts.connectorSelection.each((connector, datum) => {
|
|
const { fill, fillOpacity, stroke: stroke3, strokeOpacity, strokeWidth, lineDash, lineDashOffset } = this.connectorStyle(datum.datumIndex);
|
|
connector.setProperties(resetConnectorSelectionsFn(connector, datum));
|
|
connector.setStyleProperties(
|
|
{
|
|
fill,
|
|
stroke: stroke3,
|
|
fillOpacity,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash,
|
|
lineDashOffset
|
|
},
|
|
fillBBox
|
|
);
|
|
});
|
|
}
|
|
updateLabelSelection(opts) {
|
|
const labelData = this.properties.label.enabled ? opts.labelData : [];
|
|
return opts.labelSelection.update(labelData, (text2) => {
|
|
text2.pointerEvents = PointerEvents7.None;
|
|
});
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const params = {
|
|
stageKey: this.properties.stageKey,
|
|
valueKey: this.properties.valueKey
|
|
};
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const { isHighlight = false, labelSelection } = opts;
|
|
labelSelection.each((textNode, datum) => {
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datum.datumIndex);
|
|
textNode.visible = datum.visible || isHighlight;
|
|
textNode.fillOpacity = highlightStyle.opacity ?? 1;
|
|
textNode.opacity = highlightStyle.opacity ?? 1;
|
|
updateLabelNode6(this, textNode, params, this.properties.label, datum, isHighlight, activeHighlight);
|
|
});
|
|
}
|
|
getHighlightLabelData(_labelData, highlightedItem) {
|
|
if (highlightedItem.label) {
|
|
return [{ ...highlightedItem.label }];
|
|
}
|
|
return void 0;
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, properties } = this;
|
|
const { stageKey, valueKey, tooltip, legendItemName } = properties;
|
|
const xAxis = this.getCategoryAxis();
|
|
const yAxis = this.getValueAxis();
|
|
if (!dataModel || !processedData || !xAxis || !yAxis)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const xValue = dataModel.resolveKeysById(this, "xValue", processedData)[datumIndex];
|
|
const yValue = dataModel.resolveColumnById(this, `yValue`, processedData)[datumIndex];
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
if (xValue === void 0 && !allowNullKeys)
|
|
return;
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
symbol: this.legendItemSymbol(datumIndex),
|
|
data: [
|
|
{
|
|
label: this.getAxisValueText(xAxis, "tooltip", xValue, datum, stageKey, legendItemName),
|
|
value: this.getAxisValueText(yAxis, "tooltip", yValue, datum, valueKey, legendItemName)
|
|
}
|
|
]
|
|
},
|
|
{ seriesId, datum, title: stageKey, stageKey, valueKey, ...this.tooltipStyle(datum, datumIndex) }
|
|
);
|
|
}
|
|
resetAllAnimation(data) {
|
|
super.resetAllAnimation(data);
|
|
resetMotion3([this.connectorSelection], resetConnectorSelectionsFn);
|
|
}
|
|
animateEmptyUpdateReady({ labelSelection }) {
|
|
const { connectorSelection } = this;
|
|
const isVertical = this.isVertical();
|
|
const mode = "normal";
|
|
const connectorFns = prepareConnectorAnimationFunctions(isVertical, mode);
|
|
motion5.fromToMotion(this.id, "connectors", this.ctx.animationManager, [connectorSelection], connectorFns);
|
|
seriesLabelFadeInAnimation6(this, "labels", this.ctx.animationManager, labelSelection);
|
|
}
|
|
animateWaitingUpdateReady(data) {
|
|
const { labelSelection: labelSelections } = data;
|
|
this.ctx.animationManager.stopByAnimationGroupId(this.id);
|
|
seriesLabelFadeInAnimation6(this, "labels", this.ctx.animationManager, labelSelections);
|
|
}
|
|
getDatumId(datum) {
|
|
return createDatumId13(datum.xValue);
|
|
}
|
|
isLabelEnabled() {
|
|
return this.properties.label.enabled;
|
|
}
|
|
computeFocusBounds({ datumIndex }) {
|
|
return computeBarFocusBounds7(this, this.contextNodeData?.nodeData[datumIndex]);
|
|
}
|
|
legendItemSymbol(datumIndex) {
|
|
const { strokeWidth, fillOpacity, strokeOpacity, lineDash, lineDashOffset, fill, stroke: stroke3 } = this.properties.getStyle(datumIndex);
|
|
return {
|
|
marker: {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
const {
|
|
id: seriesId,
|
|
processedData,
|
|
dataModel,
|
|
ctx: { legendManager },
|
|
visible
|
|
} = this;
|
|
if (!dataModel || !processedData || legendType !== "category") {
|
|
return [];
|
|
}
|
|
const { showInLegend } = this.properties;
|
|
const xValues = dataModel.resolveKeysById(this, "xValue", processedData);
|
|
return (processedData.dataSources.get(this.id)?.data ?? []).map((datum, datumIndex) => {
|
|
const stageValue = xValues[datumIndex];
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
if (stageValue == null && !allowNullKeys)
|
|
return;
|
|
return {
|
|
legendType: "category",
|
|
id: seriesId,
|
|
datum,
|
|
itemId: datumIndex,
|
|
seriesId,
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId, itemId: datumIndex }),
|
|
label: { text: String(stageValue) },
|
|
symbol: this.legendItemSymbol(datumIndex),
|
|
skipAnimations: true,
|
|
hideInLegend: !showInLegend
|
|
};
|
|
}).filter((datum) => datum != null);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/cone-funnel/coneFunnelProperties.ts
|
|
var { Label: Label12, AbstractBarSeriesProperties: AbstractBarSeriesProperties6, makeSeriesTooltip: makeSeriesTooltip14, AxisLabel: AxisLabel4 } = module_support_exports;
|
|
var ConeFunnelSeriesLabel = class extends Label12 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.spacing = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelSeriesLabel.prototype, "placement", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelSeriesLabel.prototype, "spacing", 2);
|
|
var ConeFunnelSeriesStageLabel = class extends AxisLabel4 {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelSeriesStageLabel.prototype, "placement", 2);
|
|
var ConeFunnelProperties = class extends AbstractBarSeriesProperties6 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fills = [];
|
|
this.fillOpacity = 1;
|
|
this.strokes = [];
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.label = new ConeFunnelSeriesLabel();
|
|
this.stageLabel = new ConeFunnelSeriesStageLabel();
|
|
this.tooltip = makeSeriesTooltip14();
|
|
}
|
|
getStyle(index) {
|
|
const { fills, strokes, fillOpacity, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = this;
|
|
return {
|
|
fill: fills[index],
|
|
fillOpacity,
|
|
stroke: strokes[index],
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
opacity: 1
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelProperties.prototype, "stageKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelProperties.prototype, "valueKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelProperties.prototype, "fills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelProperties.prototype, "strokes", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelProperties.prototype, "stageLabel", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], ConeFunnelProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/cone-funnel/coneFunnelUtil.ts
|
|
function resetLineSelectionsFn(_node, { x, y, width: width2, height: height2, opacity }) {
|
|
return { x1: x, y1: y, x2: x + width2, y2: y + height2, opacity: opacity ?? 1 };
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/cone-funnel/coneFunnelSeries.ts
|
|
var { Line: Line4 } = module_support_exports;
|
|
var ConeFunnelSeries = class extends BaseFunnelSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
animationResetFns: {
|
|
datum: resetLineSelectionsFn
|
|
}
|
|
});
|
|
this.properties = new ConeFunnelProperties();
|
|
}
|
|
get hasData() {
|
|
const {
|
|
id: seriesId,
|
|
ctx: { legendManager }
|
|
} = this;
|
|
const visibleItems = this.data?.data.reduce(
|
|
(accum, _, datumIndex) => accum + (legendManager.getItemEnabled({ seriesId, itemId: datumIndex }) ? 1 : 0),
|
|
0
|
|
);
|
|
return visibleItems != null && visibleItems > 1;
|
|
}
|
|
getBandScalePadding() {
|
|
return { inner: 1, outer: 0 };
|
|
}
|
|
connectorEnabled() {
|
|
return true;
|
|
}
|
|
getItemStyle({ datumIndex }, _isHighlight) {
|
|
return this.properties.getStyle(datumIndex);
|
|
}
|
|
connectorStyle(index) {
|
|
return this.properties.getStyle(index);
|
|
}
|
|
nodeFactory() {
|
|
return new Line4();
|
|
}
|
|
createLabelData({
|
|
datumIndex,
|
|
rect: rect2,
|
|
barAlongX,
|
|
yDatum,
|
|
datum,
|
|
visible
|
|
}) {
|
|
const { stageKey, valueKey, label } = this.properties;
|
|
const { spacing, placement } = label;
|
|
if (!label.enabled)
|
|
return;
|
|
let x;
|
|
let y;
|
|
let textAlign;
|
|
let textBaseline;
|
|
if (barAlongX) {
|
|
x = rect2.x + rect2.width / 2;
|
|
textAlign = "center";
|
|
switch (placement) {
|
|
case "before":
|
|
y = rect2.y - spacing;
|
|
textBaseline = "bottom";
|
|
break;
|
|
case "after":
|
|
y = rect2.y + rect2.height + spacing;
|
|
textBaseline = "top";
|
|
break;
|
|
default:
|
|
y = rect2.y + rect2.height / 2;
|
|
textBaseline = "middle";
|
|
}
|
|
} else {
|
|
y = rect2.y + rect2.height / 2;
|
|
textBaseline = "middle";
|
|
switch (placement) {
|
|
case "before":
|
|
x = rect2.x - spacing;
|
|
textAlign = "right";
|
|
break;
|
|
case "after":
|
|
x = rect2.x + rect2.width + spacing;
|
|
textAlign = "left";
|
|
break;
|
|
default:
|
|
x = rect2.x + rect2.width / 2;
|
|
textAlign = "center";
|
|
}
|
|
}
|
|
const yDomain = this.getSeriesDomain("y" /* Y */).domain;
|
|
const text2 = this.getLabelText(
|
|
yDatum,
|
|
datum,
|
|
valueKey,
|
|
"y",
|
|
yDomain,
|
|
label,
|
|
{ itemId: valueKey, value: yDatum, datum, stageKey, valueKey }
|
|
);
|
|
return {
|
|
x,
|
|
y,
|
|
textAlign,
|
|
textBaseline,
|
|
text: text2,
|
|
itemId: valueKey,
|
|
datum,
|
|
datumIndex,
|
|
series: this,
|
|
visible
|
|
};
|
|
}
|
|
updateDatumNodes(opts) {
|
|
const highlightStyle = this.getHighlightStyle(opts.isHighlight);
|
|
opts.datumSelection.each((line, datum) => {
|
|
line.setProperties(resetLineSelectionsFn(line, datum));
|
|
line.stroke = highlightStyle?.stroke;
|
|
line.strokeWidth = highlightStyle?.strokeWidth ?? 0;
|
|
line.strokeOpacity = highlightStyle?.strokeOpacity ?? 1;
|
|
line.lineDash = highlightStyle?.lineDash;
|
|
line.lineDashOffset = highlightStyle?.lineDashOffset ?? 0;
|
|
line.opacity = highlightStyle?.opacity ?? 1;
|
|
});
|
|
}
|
|
tooltipStyle(_datum, datumIndex) {
|
|
const { fill, stroke: stroke3, fillOpacity, strokeOpacity, strokeWidth, lineDash, lineDashOffset } = this.properties.getStyle(datumIndex);
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash,
|
|
lineDashOffset
|
|
};
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
ConeFunnelSeries.className = "ConeFunnelSeries";
|
|
ConeFunnelSeries.type = "cone-funnel";
|
|
|
|
// packages/ag-charts-enterprise/src/series/cone-funnel/coneFunnelSeriesOptionsDef.ts
|
|
var { coneFunnelSeriesThemeableOptionsDef: coneFunnelSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var coneFunnelSeriesOptionsDef = {
|
|
...without(commonSeriesOptionsDefs, ["showInLegend"]),
|
|
...coneFunnelSeriesThemeableOptionsDef2,
|
|
type: required(constant("cone-funnel")),
|
|
stageKey: required(string),
|
|
valueKey: required(string)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/cone-funnel/coneFunnelThemes.ts
|
|
var CONE_FUNNEL_SERIES_THEME = {
|
|
series: {
|
|
direction: "vertical",
|
|
fills: {
|
|
$applyCycle: [
|
|
{ $size: { $path: ["./data", { $path: "/data" }] } },
|
|
{
|
|
$if: [
|
|
{ $eq: [{ $palette: "type" }, "inbuilt"] },
|
|
{ $palette: "secondSequentialColors" },
|
|
SAFE_RANGE2_OPERATION
|
|
]
|
|
},
|
|
{
|
|
$applySwitch: [
|
|
{ $path: ["/type", void 0, { $value: "$1" }] },
|
|
{ $value: "$1" },
|
|
["gradient", FILL_GRADIENT_LINEAR_SINGLE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_SINGLE_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS]
|
|
]
|
|
}
|
|
]
|
|
},
|
|
strokes: {
|
|
$applyCycle: [
|
|
{ $size: { $path: ["./data", { $path: "/data" }] } },
|
|
{
|
|
$if: [
|
|
{ $eq: [{ $palette: "type" }, "inbuilt"] },
|
|
{ $palette: "secondSequentialColors" },
|
|
SAFE_RANGE2_OPERATION
|
|
]
|
|
}
|
|
]
|
|
},
|
|
strokeWidth: { $isUserOption: ["./strokes/0", 2, 0] },
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" },
|
|
placement: "before",
|
|
spacing: 4
|
|
},
|
|
tooltip: {
|
|
range: { $path: ["/tooltip/range", "nearest"] }
|
|
},
|
|
highlight: {
|
|
highlightedItem: {
|
|
stroke: `rgba(0, 0, 0, 0.4)`,
|
|
strokeWidth: 2
|
|
}
|
|
}
|
|
},
|
|
seriesArea: {
|
|
padding: {
|
|
top: 20,
|
|
bottom: 20
|
|
}
|
|
},
|
|
axes: {
|
|
["number" /* NUMBER */]: {
|
|
nice: false,
|
|
gridLine: {
|
|
enabled: false
|
|
},
|
|
crosshair: {
|
|
enabled: false
|
|
},
|
|
label: {
|
|
enabled: false
|
|
}
|
|
},
|
|
["category" /* CATEGORY */]: {
|
|
line: {
|
|
enabled: false
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/cone-funnel/coneFunnelModule.ts
|
|
var ConeFunnelSeriesModule = {
|
|
type: "series",
|
|
name: "cone-funnel",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
solo: true,
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: coneFunnelSeriesOptionsDef,
|
|
defaultAxes: FUNNEL_SERIES_AXES,
|
|
themeTemplate: CONE_FUNNEL_SERIES_THEME,
|
|
create: (ctx) => new ConeFunnelSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/funnel/funnelProperties.ts
|
|
var { Label: Label13, DropShadow: DropShadow6, AbstractBarSeriesProperties: AbstractBarSeriesProperties7, makeSeriesTooltip: makeSeriesTooltip15, AxisLabel: AxisLabel5 } = module_support_exports;
|
|
var FunnelSeriesLabel = class extends Label13 {
|
|
};
|
|
var FunnelSeriesStageLabel = class extends AxisLabel5 {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelSeriesStageLabel.prototype, "placement", 2);
|
|
var FunnelDropOff = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = true;
|
|
this.fill = void 0;
|
|
this.fillOpacity = 1;
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
}
|
|
getStyle() {
|
|
const { fill, stroke: stroke3, fillOpacity, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = this;
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
opacity: 1
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelDropOff.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelDropOff.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelDropOff.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelDropOff.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelDropOff.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelDropOff.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelDropOff.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelDropOff.prototype, "lineDashOffset", 2);
|
|
var FunnelProperties = class extends AbstractBarSeriesProperties7 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fills = [];
|
|
this.fillOpacity = 1;
|
|
this.strokes = [];
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.spacingRatio = 0;
|
|
this.dropOff = new FunnelDropOff();
|
|
this.shadow = new DropShadow6().set({ enabled: false });
|
|
this.label = new FunnelSeriesLabel();
|
|
this.stageLabel = new FunnelSeriesStageLabel();
|
|
this.tooltip = makeSeriesTooltip15();
|
|
}
|
|
getStyle(index) {
|
|
const { fills, strokes, fillOpacity, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = this;
|
|
return {
|
|
fill: fills[index],
|
|
fillOpacity,
|
|
stroke: strokes[index],
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
opacity: 1
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "stageKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "valueKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "fills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "strokes", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "spacingRatio", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "dropOff", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "shadow", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "stageLabel", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], FunnelProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/funnel/funnelSeries.ts
|
|
var { resetBarSelectionsFn: resetBarSelectionsFn4, prepareBarAnimationFunctions: prepareBarAnimationFunctions4, midpointStartingBarPosition: midpointStartingBarPosition3, createDatumId: createDatumId14, Rect: Rect10, motion: motion6 } = module_support_exports;
|
|
var FunnelSeries = class extends BaseFunnelSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
animationResetFns: {
|
|
datum: resetBarSelectionsFn4
|
|
}
|
|
});
|
|
this.properties = new FunnelProperties();
|
|
}
|
|
getBandScalePadding() {
|
|
return { inner: this.properties.spacingRatio, outer: 0 };
|
|
}
|
|
connectorEnabled() {
|
|
return this.properties.dropOff.enabled;
|
|
}
|
|
connectorStyle(index) {
|
|
return mergeDefaults(this.properties.dropOff.getStyle(), this.properties.getStyle(index));
|
|
}
|
|
nodeFactory() {
|
|
return new Rect10();
|
|
}
|
|
createLabelData({
|
|
datumIndex,
|
|
rect: rect2,
|
|
yDatum,
|
|
datum,
|
|
visible
|
|
}) {
|
|
const { valueKey, stageKey, label } = this.properties;
|
|
if (!label.enabled)
|
|
return;
|
|
const yDomain = this.getSeriesDomain("y" /* Y */).domain;
|
|
const text2 = this.getLabelText(
|
|
yDatum,
|
|
datum,
|
|
valueKey,
|
|
"y",
|
|
yDomain,
|
|
label,
|
|
{ itemId: valueKey, value: yDatum, datum, stageKey, valueKey }
|
|
);
|
|
return {
|
|
x: rect2.x + rect2.width / 2,
|
|
y: rect2.y + rect2.height / 2,
|
|
textAlign: "center",
|
|
textBaseline: "middle",
|
|
text: text2,
|
|
itemId: stageKey,
|
|
datum,
|
|
datumIndex,
|
|
series: this,
|
|
visible
|
|
};
|
|
}
|
|
getItemStyle({ datum, datumIndex }, isHighlight) {
|
|
const { id: seriesId, properties } = this;
|
|
const { stageKey, valueKey, itemStyler } = properties;
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex);
|
|
const baseStyle = mergeDefaults(highlightStyle, properties.getStyle(datumIndex));
|
|
let style2 = baseStyle;
|
|
if (itemStyler != null) {
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId14(datumIndex, isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const highlightStateString = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
return this.callWithContext(itemStyler, {
|
|
seriesId,
|
|
datum,
|
|
highlightState: highlightStateString,
|
|
stageKey,
|
|
valueKey,
|
|
...style2
|
|
});
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(overrides, style2);
|
|
}
|
|
}
|
|
return style2;
|
|
}
|
|
updateDatumNodes({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
const { contextNodeData } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const { shadow } = this.properties;
|
|
const categoryAlongX = this.getCategoryDirection() === "x" /* X */;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
datumSelection.each((rect2, datum) => {
|
|
const style2 = this.getItemStyle(datum, isHighlight);
|
|
rect2.setStyleProperties(style2, fillBBox);
|
|
rect2.visible = categoryAlongX ? datum.width > 0 : datum.height > 0;
|
|
rect2.crisp = datum.crisp;
|
|
rect2.fillShadow = shadow;
|
|
});
|
|
}
|
|
tooltipStyle(datum, datumIndex) {
|
|
return this.getItemStyle({ datum, datumIndex }, false);
|
|
}
|
|
animateEmptyUpdateReady(params) {
|
|
super.animateEmptyUpdateReady(params);
|
|
const { datumSelection } = params;
|
|
const isVertical = this.isVertical();
|
|
const mode = "normal";
|
|
const barFns = prepareBarAnimationFunctions4(midpointStartingBarPosition3(isVertical, mode), "unknown");
|
|
motion6.fromToMotion(this.id, "datums", this.ctx.animationManager, [datumSelection], barFns);
|
|
}
|
|
animateWaitingUpdateReady(data) {
|
|
super.animateWaitingUpdateReady(data);
|
|
const { datumSelection: datumSelections } = data;
|
|
const { processedData } = this;
|
|
const dataDiff = processedData?.reduced?.diff?.[this.id];
|
|
const fns = prepareBarAnimationFunctions4(midpointStartingBarPosition3(this.isVertical(), "fade"), "added");
|
|
motion6.fromToMotion(
|
|
this.id,
|
|
"datums",
|
|
this.ctx.animationManager,
|
|
[datumSelections],
|
|
fns,
|
|
(_, datum) => datum.xValue,
|
|
dataDiff
|
|
);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
FunnelSeries.className = "FunnelSeries";
|
|
FunnelSeries.type = "funnel";
|
|
|
|
// packages/ag-charts-enterprise/src/series/funnel/funnelSeriesOptionsDef.ts
|
|
var { funnelSeriesThemeableOptionsDef: funnelSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var funnelSeriesOptionsDef = {
|
|
...funnelSeriesThemeableOptionsDef2,
|
|
...without(commonSeriesOptionsDefs, ["showInLegend"]),
|
|
type: required(constant("funnel")),
|
|
stageKey: required(string),
|
|
valueKey: required(string)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/funnel/funnelModule.ts
|
|
var FunnelSeriesModule = {
|
|
type: "series",
|
|
name: "funnel",
|
|
chartType: "cartesian",
|
|
enterprise: true,
|
|
solo: true,
|
|
version: VERSION,
|
|
dependencies: [CartesianChartModule],
|
|
options: funnelSeriesOptionsDef,
|
|
defaultAxes: FUNNEL_SERIES_AXES,
|
|
themeTemplate: FUNNEL_SERIES_THEME,
|
|
create: (ctx) => new FunnelSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/module-bundles/cartesian-series.ts
|
|
var AllCartesianSeriesModule2 = [
|
|
AllCartesianSeriesModule,
|
|
BoxPlotSeriesModule,
|
|
CandlestickSeriesModule,
|
|
ConeFunnelSeriesModule,
|
|
FunnelSeriesModule,
|
|
HeatmapSeriesModule,
|
|
OhlcSeriesModule,
|
|
RangeAreaSeriesModule,
|
|
RangeBarSeriesModule,
|
|
WaterfallSeriesModule
|
|
].flat();
|
|
|
|
// packages/ag-charts-enterprise/src/module-bundles/cartesian.ts
|
|
var AllCartesianModule2 = [
|
|
AllCartesianAxesModule2,
|
|
AllCartesianSeriesModule2,
|
|
AnimationModule,
|
|
AnnotationsModule,
|
|
BandHighlightModule,
|
|
ChartToolbarModule,
|
|
ContextMenuModule,
|
|
CrosshairModule,
|
|
DataSourceModule,
|
|
ErrorBarsModule,
|
|
FlashOnUpdateModule,
|
|
GradientLegendModule,
|
|
NavigatorModule,
|
|
RangesModule,
|
|
ScrollbarModule,
|
|
StatusBarModule,
|
|
SyncModule,
|
|
ZoomModule
|
|
].flat();
|
|
|
|
// packages/ag-charts-enterprise/src/preset/priceVolumePresetTheme.ts
|
|
var {
|
|
DEFAULT_ANNOTATION_HANDLE_FILL: DEFAULT_ANNOTATION_HANDLE_FILL2,
|
|
DEFAULT_ANNOTATION_STATISTICS_COLOR: DEFAULT_ANNOTATION_STATISTICS_COLOR2,
|
|
DEFAULT_ANNOTATION_STATISTICS_DIVIDER_STROKE: DEFAULT_ANNOTATION_STATISTICS_DIVIDER_STROKE2,
|
|
DEFAULT_ANNOTATION_STATISTICS_DOWN_FILL: DEFAULT_ANNOTATION_STATISTICS_DOWN_FILL2,
|
|
DEFAULT_ANNOTATION_STATISTICS_DOWN_STROKE: DEFAULT_ANNOTATION_STATISTICS_DOWN_STROKE2,
|
|
DEFAULT_ANNOTATION_STATISTICS_FILL: DEFAULT_ANNOTATION_STATISTICS_FILL2,
|
|
DEFAULT_ANNOTATION_STATISTICS_STROKE: DEFAULT_ANNOTATION_STATISTICS_STROKE2,
|
|
DEFAULT_FIBONACCI_STROKES: DEFAULT_FIBONACCI_STROKES2,
|
|
DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL2,
|
|
DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR2,
|
|
DEFAULT_TEXTBOX_COLOR: DEFAULT_TEXTBOX_COLOR2,
|
|
DEFAULT_TEXTBOX_FILL: DEFAULT_TEXTBOX_FILL2,
|
|
DEFAULT_TEXTBOX_STROKE: DEFAULT_TEXTBOX_STROKE2,
|
|
DEFAULT_TEXT_ANNOTATION_COLOR: DEFAULT_TEXT_ANNOTATION_COLOR2
|
|
} = main_exports;
|
|
var stroke2 = {
|
|
stroke: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR2
|
|
};
|
|
var handle2 = {
|
|
fill: DEFAULT_ANNOTATION_HANDLE_FILL2
|
|
};
|
|
var axisLabel2 = {
|
|
color: "white",
|
|
fill: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR2
|
|
};
|
|
var lineText2 = {
|
|
color: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR2
|
|
};
|
|
var font2 = {
|
|
color: DEFAULT_TEXT_ANNOTATION_COLOR2,
|
|
fontSize: { $rem: FONT_SIZE_RATIO.LARGE },
|
|
fontFamily: { $ref: "fontFamily" }
|
|
};
|
|
var measurerStatistics2 = {
|
|
...font2,
|
|
fontSize: { $ref: "fontSize" },
|
|
color: DEFAULT_ANNOTATION_STATISTICS_COLOR2,
|
|
fill: DEFAULT_ANNOTATION_STATISTICS_FILL2,
|
|
stroke: DEFAULT_ANNOTATION_STATISTICS_STROKE2,
|
|
strokeWidth: 1,
|
|
divider: {
|
|
stroke: DEFAULT_ANNOTATION_STATISTICS_DIVIDER_STROKE2,
|
|
strokeWidth: 1,
|
|
strokeOpacity: 0.5
|
|
}
|
|
};
|
|
var measurer2 = {
|
|
...stroke2,
|
|
background: {
|
|
fill: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL2,
|
|
fillOpacity: 0.2
|
|
},
|
|
handle: { ...handle2 },
|
|
text: { ...lineText2 },
|
|
statistics: { ...measurerStatistics2 }
|
|
};
|
|
var annotationsTheme2 = {
|
|
// Lines
|
|
line: {
|
|
...stroke2,
|
|
handle: { ...handle2 },
|
|
text: { ...lineText2 }
|
|
},
|
|
"horizontal-line": {
|
|
...stroke2,
|
|
handle: { ...handle2 },
|
|
axisLabel: { ...axisLabel2 },
|
|
text: { ...lineText2 }
|
|
},
|
|
"vertical-line": {
|
|
...stroke2,
|
|
handle: { ...handle2 },
|
|
axisLabel: { ...axisLabel2 },
|
|
text: { ...lineText2 }
|
|
},
|
|
// Channels
|
|
"disjoint-channel": {
|
|
...stroke2,
|
|
background: {
|
|
fill: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL2,
|
|
fillOpacity: 0.2
|
|
},
|
|
handle: { ...handle2 },
|
|
text: { ...lineText2 }
|
|
},
|
|
"parallel-channel": {
|
|
...stroke2,
|
|
background: {
|
|
fill: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL2,
|
|
fillOpacity: 0.2
|
|
},
|
|
handle: { ...handle2 },
|
|
text: { ...lineText2 }
|
|
},
|
|
// Fibonnaccis
|
|
"fibonacci-retracement": {
|
|
...stroke2,
|
|
strokes: DEFAULT_FIBONACCI_STROKES2,
|
|
rangeStroke: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR2,
|
|
handle: { ...handle2 },
|
|
text: { ...lineText2, position: "center" },
|
|
label: {
|
|
...font2,
|
|
color: void 0,
|
|
fontSize: { $rem: FONT_SIZE_RATIO.SMALLER }
|
|
}
|
|
},
|
|
"fibonacci-retracement-trend-based": {
|
|
...stroke2,
|
|
strokes: DEFAULT_FIBONACCI_STROKES2,
|
|
rangeStroke: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR2,
|
|
handle: { ...handle2 },
|
|
text: { ...lineText2, position: "center" },
|
|
label: {
|
|
...font2,
|
|
color: void 0,
|
|
fontSize: { $rem: FONT_SIZE_RATIO.SMALLER }
|
|
}
|
|
},
|
|
// Texts
|
|
callout: {
|
|
...stroke2,
|
|
...font2,
|
|
color: { $ref: "textColor" },
|
|
handle: { ...handle2 },
|
|
fill: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL2,
|
|
fillOpacity: 0.2
|
|
},
|
|
comment: {
|
|
...font2,
|
|
color: "white",
|
|
fontWeight: 700,
|
|
handle: { ...handle2 },
|
|
fill: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR2
|
|
},
|
|
note: {
|
|
...font2,
|
|
color: DEFAULT_TEXTBOX_COLOR2,
|
|
fill: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR2,
|
|
stroke: { $ref: "chartBackgroundColor" },
|
|
strokeWidth: 1,
|
|
strokeOpacity: 1,
|
|
handle: { ...handle2 },
|
|
background: {
|
|
fill: DEFAULT_TEXTBOX_FILL2,
|
|
stroke: DEFAULT_TEXTBOX_STROKE2,
|
|
strokeWidth: 1
|
|
}
|
|
},
|
|
text: {
|
|
...font2,
|
|
handle: { ...handle2 }
|
|
},
|
|
// Shapes
|
|
arrow: {
|
|
...stroke2,
|
|
handle: { ...handle2 },
|
|
text: { ...lineText2 }
|
|
},
|
|
"arrow-up": {
|
|
fill: { $palette: "up.fill" },
|
|
handle: { ...handle2, stroke: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR2 }
|
|
},
|
|
"arrow-down": {
|
|
fill: { $palette: "down.fill" },
|
|
handle: { ...handle2, stroke: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_COLOR2 }
|
|
},
|
|
// Measurers
|
|
"date-range": {
|
|
...measurer2
|
|
},
|
|
"price-range": {
|
|
...measurer2
|
|
},
|
|
"date-price-range": {
|
|
...measurer2
|
|
},
|
|
"quick-date-price-range": {
|
|
up: {
|
|
...stroke2,
|
|
fill: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL2,
|
|
fillOpacity: 0.2,
|
|
handle: { ...handle2 },
|
|
statistics: {
|
|
...measurerStatistics2,
|
|
color: "#fff",
|
|
fill: DEFAULT_FINANCIAL_CHARTS_ANNOTATION_BACKGROUND_FILL2,
|
|
strokeWidth: 0,
|
|
divider: {
|
|
stroke: "#fff",
|
|
strokeWidth: 1,
|
|
strokeOpacity: 0.5
|
|
}
|
|
}
|
|
},
|
|
down: {
|
|
...stroke2,
|
|
stroke: DEFAULT_ANNOTATION_STATISTICS_DOWN_STROKE2,
|
|
fill: DEFAULT_ANNOTATION_STATISTICS_DOWN_FILL2,
|
|
fillOpacity: 0.2,
|
|
handle: {
|
|
...handle2,
|
|
stroke: DEFAULT_ANNOTATION_STATISTICS_DOWN_STROKE2
|
|
},
|
|
statistics: {
|
|
...measurerStatistics2,
|
|
color: "#fff",
|
|
fill: DEFAULT_ANNOTATION_STATISTICS_DOWN_FILL2,
|
|
strokeWidth: 0,
|
|
divider: {
|
|
stroke: "#fff",
|
|
strokeWidth: 1,
|
|
strokeOpacity: 0.5
|
|
}
|
|
}
|
|
}
|
|
},
|
|
axesButtons: {
|
|
enabled: true
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/preset/priceVolumePreset.ts
|
|
var chartTypes2 = ["ohlc", "line", "step-line", "hlc", "high-low", "candlestick", "hollow-candlestick"];
|
|
var toolbarButtons = [
|
|
{
|
|
icon: "trend-line-drawing",
|
|
tooltip: "toolbarAnnotationsLineAnnotations",
|
|
value: "line-menu"
|
|
},
|
|
{
|
|
icon: "fibonacci-retracement-drawing",
|
|
tooltip: "toolbarAnnotationsFibonacciAnnotations",
|
|
value: "fibonacci-menu"
|
|
},
|
|
{
|
|
icon: "text-annotation",
|
|
tooltip: "toolbarAnnotationsTextAnnotations",
|
|
value: "text-menu"
|
|
},
|
|
{
|
|
icon: "arrow-drawing",
|
|
tooltip: "toolbarAnnotationsShapeAnnotations",
|
|
value: "shape-menu"
|
|
},
|
|
{
|
|
icon: "measurer-drawing",
|
|
tooltip: "toolbarAnnotationsMeasurerAnnotations",
|
|
value: "measurer-menu"
|
|
},
|
|
{
|
|
icon: "delete",
|
|
tooltip: "toolbarAnnotationsClearAll",
|
|
value: "clear"
|
|
}
|
|
];
|
|
function priceVolume(opts, _presetTheme, getTheme) {
|
|
const {
|
|
dateKey = "date",
|
|
highKey = "high",
|
|
openKey = "open",
|
|
lowKey = "low",
|
|
closeKey = "close",
|
|
volumeKey = "volume",
|
|
chartType = "candlestick",
|
|
navigator = false,
|
|
volume = true,
|
|
rangeButtons = true,
|
|
statusBar = true,
|
|
toolbar: toolbar2 = true,
|
|
zoom = true,
|
|
sync = false,
|
|
theme,
|
|
data,
|
|
formatter: formatter2,
|
|
...unusedOpts
|
|
} = opts;
|
|
const priceSeries = createPriceSeries(chartType, dateKey, highKey, lowKey, openKey, closeKey);
|
|
const volumeSeries = createVolumeSeries(getTheme, dateKey, openKey, closeKey, volume, volumeKey);
|
|
const miniChart = volume ? {
|
|
miniChart: {
|
|
enabled: navigator,
|
|
series: [
|
|
{
|
|
type: "line",
|
|
xKey: dateKey,
|
|
yKey: volumeKey,
|
|
stroke: SAFE_STROKE_FILL_OPERATION,
|
|
marker: { enabled: false }
|
|
}
|
|
]
|
|
},
|
|
height: 40,
|
|
minHandle: {
|
|
height: 46
|
|
},
|
|
maxHandle: {
|
|
height: 46
|
|
}
|
|
} : null;
|
|
const navigatorOpts = {
|
|
navigator: {
|
|
enabled: navigator,
|
|
...miniChart
|
|
}
|
|
};
|
|
const annotationOpts = {
|
|
annotations: {
|
|
enabled: toolbar2,
|
|
optionsToolbar: {
|
|
enabled: toolbar2
|
|
},
|
|
// @ts-expect-error undocumented option
|
|
snap: true,
|
|
toolbar: {
|
|
enabled: toolbar2,
|
|
buttons: toolbarButtons,
|
|
padding: 0
|
|
},
|
|
data,
|
|
xKey: dateKey,
|
|
volumeKey: volume ? volumeKey : void 0
|
|
}
|
|
};
|
|
const statusBarOpts = statusBar ? {
|
|
statusBar: {
|
|
enabled: true,
|
|
highKey,
|
|
openKey,
|
|
lowKey,
|
|
closeKey,
|
|
volumeKey: volume ? volumeKey : void 0
|
|
}
|
|
} : null;
|
|
const zoomOpts = {
|
|
zoom: {
|
|
enabled: zoom,
|
|
autoScaling: {
|
|
enabled: true
|
|
},
|
|
onDataChange: {
|
|
stickToEnd: true
|
|
},
|
|
// @ts-expect-error undocumented option
|
|
enableIndependentAxes: true
|
|
}
|
|
};
|
|
const toolbarOpts = {
|
|
ranges: {
|
|
enabled: rangeButtons
|
|
}
|
|
};
|
|
const syncGroup = sync ? {
|
|
sync: {
|
|
enabled: sync,
|
|
nodeInteraction: true,
|
|
zoom: true
|
|
}
|
|
} : null;
|
|
const volumeAxis = volume ? {
|
|
yVolume: {
|
|
type: "number",
|
|
position: "left",
|
|
label: { enabled: false },
|
|
crosshair: { enabled: false },
|
|
gridLine: { enabled: false },
|
|
nice: false,
|
|
// @ts-expect-error undocumented option
|
|
layoutConstraints: {
|
|
stacked: false,
|
|
width: 20,
|
|
unit: "percent",
|
|
align: "end"
|
|
}
|
|
}
|
|
} : {};
|
|
return {
|
|
theme: {
|
|
baseTheme: typeof theme === "string" ? theme : "ag-financial",
|
|
...mergeDefaults(typeof theme === "object" ? theme : null, {
|
|
overrides: {
|
|
common: {
|
|
title: { padding: 4 },
|
|
padding: {
|
|
top: 6,
|
|
right: 8,
|
|
bottom: 6
|
|
},
|
|
chartToolbar: {
|
|
enabled: toolbar2
|
|
},
|
|
annotations: { ...annotationsTheme2 },
|
|
axes: {
|
|
number: {
|
|
interval: { maxSpacing: 45 },
|
|
label: { format: ".2f" }
|
|
},
|
|
category: {
|
|
gridLine: { enabled: true }
|
|
},
|
|
time: {
|
|
gridLine: { enabled: true }
|
|
},
|
|
"unit-time": {
|
|
gridLine: { enabled: true }
|
|
},
|
|
"ordinal-time": {
|
|
gridLine: { enabled: true }
|
|
}
|
|
}
|
|
},
|
|
bar: {
|
|
series: {
|
|
fillOpacity: 0.5,
|
|
highlight: { unhighlightedItem: { opacity: 1 }, unhighlightedSeries: { opacity: 1 } }
|
|
}
|
|
},
|
|
line: {
|
|
series: {
|
|
marker: { enabled: false },
|
|
highlight: { unhighlightedSeries: { opacity: 1 } },
|
|
...inlineSwitch(chartType, {
|
|
hlc: {
|
|
stroke: { $palette: "altNeutral.stroke" },
|
|
strokeWidth: 2
|
|
},
|
|
line: {
|
|
stroke: { $palette: "neutral.stroke" }
|
|
},
|
|
"step-line": {
|
|
stroke: { $palette: "neutral.stroke" },
|
|
interpolation: { type: "step" }
|
|
}
|
|
})
|
|
}
|
|
},
|
|
candlestick: {
|
|
series: {
|
|
highlight: { unhighlightedItem: { opacity: 1 }, unhighlightedSeries: { opacity: 1 } },
|
|
...inlineSwitch(chartType, {
|
|
"hollow-candlestick": {
|
|
item: {
|
|
up: { fill: "transparent" }
|
|
}
|
|
}
|
|
})
|
|
}
|
|
},
|
|
ohlc: {
|
|
series: {
|
|
highlight: { unhighlightedItem: { opacity: 1 }, unhighlightedSeries: { opacity: 1 } }
|
|
}
|
|
},
|
|
"range-area": {
|
|
series: {
|
|
fillOpacity: 0.3,
|
|
strokeWidth: 2,
|
|
highlight: {
|
|
bringToFront: false,
|
|
unhighlightedItem: { opacity: 1 },
|
|
unhighlightedSeries: { opacity: 1 }
|
|
},
|
|
...inlineSwitch(chartType, {
|
|
hlc: {
|
|
fill: {
|
|
$if: [
|
|
{ $eq: [{ $value: "$index" }, 1] },
|
|
{ $palette: "up.fill" },
|
|
{ $palette: "down.fill" }
|
|
]
|
|
},
|
|
stroke: {
|
|
$if: [
|
|
{ $eq: [{ $value: "$index" }, 1] },
|
|
{ $palette: "up.stroke" },
|
|
{ $palette: "down.stroke" }
|
|
]
|
|
}
|
|
}
|
|
})
|
|
}
|
|
},
|
|
"range-bar": {
|
|
series: {
|
|
highlight: {
|
|
unhighlightedItem: { opacity: 1 },
|
|
unhighlightedSeries: { opacity: 1 }
|
|
},
|
|
...inlineSwitch(chartType, {
|
|
"high-low": {
|
|
fill: { $palette: "neutral.fill" },
|
|
stroke: { $palette: "neutral.stroke" }
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
})
|
|
},
|
|
animation: { enabled: false },
|
|
legend: { enabled: false },
|
|
series: [...volumeSeries, ...priceSeries],
|
|
axes: {
|
|
y: {
|
|
type: "number",
|
|
position: "right",
|
|
crosshair: {
|
|
enabled: true,
|
|
snap: false
|
|
},
|
|
// @ts-expect-error undocumented option
|
|
layoutConstraints: {
|
|
stacked: false,
|
|
width: 100,
|
|
unit: "percent",
|
|
align: "start"
|
|
}
|
|
},
|
|
...volumeAxis,
|
|
x: {
|
|
type: "ordinal-time",
|
|
position: "bottom",
|
|
line: {
|
|
enabled: false
|
|
},
|
|
label: {
|
|
enabled: true
|
|
},
|
|
crosshair: {
|
|
enabled: true
|
|
}
|
|
}
|
|
},
|
|
tooltip: { enabled: false },
|
|
data,
|
|
formatter: formatter2,
|
|
...annotationOpts,
|
|
...navigatorOpts,
|
|
...statusBarOpts,
|
|
...zoomOpts,
|
|
...toolbarOpts,
|
|
...syncGroup,
|
|
...unusedOpts
|
|
};
|
|
}
|
|
function createVolumeSeries(getTheme, xKey, openKey, closeKey, volume, volumeKey) {
|
|
if (!volume)
|
|
return [];
|
|
return [
|
|
{
|
|
type: "bar",
|
|
xKey,
|
|
yKey: volumeKey,
|
|
yKeyAxis: "yVolume",
|
|
tooltip: { enabled: false },
|
|
// @ts-expect-error undocumented options: simpleItemStyler, focusPriority
|
|
simpleItemStyler(datum) {
|
|
const { up, down } = getTheme().palette;
|
|
return { fill: datum[openKey] < datum[closeKey] ? up?.fill : down?.fill };
|
|
},
|
|
focusPriority: 1,
|
|
highlight: { unhighlightedSeries: { opacity: 1 } }
|
|
}
|
|
];
|
|
}
|
|
var RANGE_AREA_TYPE = "range-area";
|
|
function createPriceSeries(chartType, xKey, highKey, lowKey, openKey, closeKey) {
|
|
const keys = {
|
|
xKey,
|
|
openKey,
|
|
closeKey,
|
|
highKey,
|
|
lowKey
|
|
};
|
|
const singleKeys = {
|
|
xKey,
|
|
yKey: closeKey
|
|
};
|
|
const common = {
|
|
pickOutsideVisibleMinorAxis: true
|
|
};
|
|
switch (chartType ?? "candlestick") {
|
|
case "ohlc":
|
|
return createPriceSeriesOHLC(common, keys);
|
|
case "line":
|
|
case "step-line":
|
|
return createPriceSeriesLine(common, singleKeys);
|
|
case "hlc":
|
|
return createPriceSeriesHLC(common, singleKeys, keys);
|
|
case "high-low":
|
|
return createPriceSeriesHighLow(common, keys);
|
|
case "candlestick":
|
|
case "hollow-candlestick":
|
|
return createPriceSeriesCandlestick(common, keys);
|
|
default:
|
|
logger_exports.warnOnce(`unknown chart type: ${chartType}; expected one of: ${chartTypes2.join(", ")}`);
|
|
return createPriceSeriesCandlestick(common, keys);
|
|
}
|
|
}
|
|
function createPriceSeriesOHLC(common, keys) {
|
|
return [
|
|
{
|
|
type: "ohlc",
|
|
// @ts-expect-error undocumented option
|
|
focusPriority: 0,
|
|
...common,
|
|
...keys
|
|
}
|
|
];
|
|
}
|
|
function createPriceSeriesLine(common, singleKeys) {
|
|
return [
|
|
{
|
|
type: "line",
|
|
// @ts-expect-error undocumented option
|
|
focusPriority: 0,
|
|
...common,
|
|
...singleKeys
|
|
}
|
|
];
|
|
}
|
|
function createPriceSeriesHLC(common, singleKeys, { xKey, highKey, closeKey, lowKey }) {
|
|
return [
|
|
{
|
|
type: RANGE_AREA_TYPE,
|
|
// @ts-expect-error undocumented option
|
|
focusPriority: 0,
|
|
...common,
|
|
xKey,
|
|
yHighKey: highKey,
|
|
yLowKey: closeKey
|
|
},
|
|
{
|
|
type: RANGE_AREA_TYPE,
|
|
// @ts-expect-error undocumented option
|
|
focusPriority: 0,
|
|
...common,
|
|
xKey,
|
|
yHighKey: closeKey,
|
|
yLowKey: lowKey
|
|
},
|
|
{
|
|
type: "line",
|
|
...common,
|
|
...singleKeys
|
|
}
|
|
];
|
|
}
|
|
function createPriceSeriesHighLow(common, { xKey, highKey, lowKey }) {
|
|
return [
|
|
{
|
|
type: "range-bar",
|
|
...common,
|
|
xKey,
|
|
yHighKey: highKey,
|
|
yLowKey: lowKey,
|
|
tooltip: { range: "nearest" },
|
|
// @ts-expect-error undocumented option
|
|
focusPriority: 0
|
|
}
|
|
];
|
|
}
|
|
function createPriceSeriesCandlestick(common, keys) {
|
|
return [
|
|
{
|
|
type: "candlestick",
|
|
// @ts-expect-error undocumented option
|
|
focusPriority: 0,
|
|
...common,
|
|
...keys
|
|
}
|
|
];
|
|
}
|
|
function inlineSwitch(caseName, switchCases) {
|
|
return switchCases[caseName] ?? switchCases.default;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/preset/priceVolumePresetModules.ts
|
|
var priceVolumeOptionsDef = {
|
|
chartType: union("candlestick", "hollow-candlestick", "ohlc", "line", "step-line", "hlc", "high-low"),
|
|
dateKey: string,
|
|
openKey: string,
|
|
highKey: string,
|
|
lowKey: string,
|
|
closeKey: string,
|
|
volumeKey: string,
|
|
navigator: boolean,
|
|
volume: boolean,
|
|
rangeButtons: boolean,
|
|
statusBar: boolean,
|
|
toolbar: boolean,
|
|
zoom: boolean,
|
|
sync: boolean,
|
|
// Valid pass-through options
|
|
theme: defined,
|
|
container: defined,
|
|
width: defined,
|
|
height: defined,
|
|
minWidth: defined,
|
|
minHeight: defined,
|
|
listeners: defined,
|
|
initialState: defined,
|
|
title: defined,
|
|
data: array,
|
|
formatter: defined
|
|
};
|
|
var commonGaugeOptions = {
|
|
// Valid pass-through options
|
|
theme: defined,
|
|
container: defined,
|
|
animation: defined,
|
|
background: defined,
|
|
contextMenu: defined,
|
|
context: () => true,
|
|
listeners: defined,
|
|
locale: defined,
|
|
width: defined,
|
|
height: defined,
|
|
minWidth: defined,
|
|
minHeight: defined,
|
|
title: defined,
|
|
subtitle: defined,
|
|
footnote: defined,
|
|
padding: defined,
|
|
tooltip: {
|
|
...tooltipOptionsDefs,
|
|
...commonChartOptionsDefs.tooltip
|
|
}
|
|
};
|
|
commonGaugeOptions.overrideDevicePixelRatio = undocumented(positiveNumber);
|
|
var PriceVolumePresetModule = {
|
|
type: "preset",
|
|
name: "price-volume",
|
|
enterprise: true,
|
|
dependencies: [ChartToolbarModule, StatusBarModule],
|
|
version: VERSION,
|
|
options: priceVolumeOptionsDef,
|
|
create: priceVolume
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/module-bundles/financial.ts
|
|
var FinancialChartModule = [
|
|
PriceVolumePresetModule,
|
|
BarSeriesModule,
|
|
LineSeriesModule,
|
|
CandlestickSeriesModule,
|
|
OhlcSeriesModule,
|
|
RangeBarSeriesModule,
|
|
RangeAreaSeriesModule,
|
|
AnimationModule,
|
|
AnnotationsModule,
|
|
BandHighlightModule,
|
|
ChartToolbarModule,
|
|
ContextMenuModule,
|
|
CrosshairModule,
|
|
DataSourceModule,
|
|
ErrorBarsModule,
|
|
GradientLegendModule,
|
|
NavigatorModule,
|
|
RangesModule,
|
|
SyncModule,
|
|
ZoomModule,
|
|
OrdinalTimeAxisModule,
|
|
TimeAxisModule,
|
|
NumberAxisModule
|
|
];
|
|
|
|
// packages/ag-charts-enterprise/src/preset/gaugePreset.ts
|
|
function tooltipOptions(opts) {
|
|
const { enabled, mode, showArrow, range: range3, position, pagination, delay, wrapping, interaction, renderer, ...rest } = opts;
|
|
const seriesTooltipOptions = {
|
|
enabled,
|
|
showArrow,
|
|
range: range3,
|
|
position,
|
|
interaction,
|
|
renderer,
|
|
...rest
|
|
};
|
|
const chartTooltipOptions = {
|
|
mode,
|
|
pagination,
|
|
delay,
|
|
wrapping,
|
|
...rest
|
|
};
|
|
return { chartTooltipOptions, seriesTooltipOptions };
|
|
}
|
|
function radialGaugeOptions(opts) {
|
|
const {
|
|
animation,
|
|
background,
|
|
container,
|
|
contextMenu,
|
|
context,
|
|
footnote,
|
|
foreground,
|
|
height: height2,
|
|
listeners,
|
|
locale,
|
|
minHeight,
|
|
minWidth,
|
|
overrideDevicePixelRatio,
|
|
padding: padding2,
|
|
subtitle,
|
|
theme,
|
|
title,
|
|
width: width2,
|
|
type,
|
|
cursor,
|
|
nodeClickRange,
|
|
tooltip: tooltipInput,
|
|
value,
|
|
scale: scale2 = {},
|
|
startAngle,
|
|
endAngle,
|
|
highlight: highlight5,
|
|
segmentation,
|
|
bar,
|
|
needle,
|
|
targets,
|
|
outerRadius,
|
|
innerRadius,
|
|
outerRadiusRatio,
|
|
innerRadiusRatio,
|
|
cornerRadius,
|
|
cornerMode,
|
|
label,
|
|
secondaryLabel,
|
|
spacing,
|
|
...seriesRest
|
|
} = opts;
|
|
const hasTooltip = tooltipInput != null;
|
|
const tooltip = tooltipInput ?? {};
|
|
const { chartTooltipOptions, seriesTooltipOptions } = tooltipOptions(tooltip);
|
|
const seriesOpts = {
|
|
...seriesRest,
|
|
type,
|
|
cursor,
|
|
context,
|
|
nodeClickRange,
|
|
value,
|
|
scale: scale2,
|
|
startAngle,
|
|
endAngle,
|
|
highlight: highlight5,
|
|
segmentation,
|
|
bar,
|
|
targets,
|
|
outerRadius,
|
|
innerRadius,
|
|
outerRadiusRatio,
|
|
innerRadiusRatio,
|
|
cornerRadius,
|
|
cornerMode,
|
|
label,
|
|
secondaryLabel,
|
|
spacing
|
|
};
|
|
if (hasTooltip) {
|
|
seriesOpts.tooltip = seriesTooltipOptions;
|
|
}
|
|
if (needle != null) {
|
|
seriesOpts.needle = { enabled: true, ...needle };
|
|
}
|
|
return {
|
|
animation,
|
|
background,
|
|
container,
|
|
contextMenu,
|
|
context,
|
|
footnote,
|
|
foreground,
|
|
height: height2,
|
|
listeners,
|
|
locale,
|
|
minHeight,
|
|
minWidth,
|
|
overrideDevicePixelRatio,
|
|
padding: padding2,
|
|
subtitle,
|
|
theme,
|
|
title,
|
|
width: width2,
|
|
...hasTooltip ? { tooltip: chartTooltipOptions } : {},
|
|
series: [seriesOpts]
|
|
};
|
|
}
|
|
function linearGaugeOptions(opts) {
|
|
const {
|
|
animation,
|
|
background,
|
|
container,
|
|
contextMenu,
|
|
context,
|
|
footnote,
|
|
foreground,
|
|
height: height2,
|
|
listeners,
|
|
locale,
|
|
minHeight,
|
|
minWidth,
|
|
overrideDevicePixelRatio,
|
|
padding: padding2,
|
|
subtitle,
|
|
theme,
|
|
title,
|
|
width: width2,
|
|
type,
|
|
cursor,
|
|
nodeClickRange,
|
|
tooltip: tooltipInput,
|
|
value,
|
|
scale: scale2 = {},
|
|
direction = "vertical",
|
|
thickness,
|
|
highlight: highlight5,
|
|
segmentation,
|
|
bar,
|
|
targets,
|
|
cornerRadius,
|
|
cornerMode,
|
|
label,
|
|
...seriesRest
|
|
} = opts;
|
|
const hasTooltip = tooltipInput != null;
|
|
const tooltip = tooltipInput ?? {};
|
|
const { chartTooltipOptions, seriesTooltipOptions } = tooltipOptions(tooltip);
|
|
const seriesOpts = {
|
|
...seriesRest,
|
|
type,
|
|
cursor,
|
|
nodeClickRange,
|
|
value,
|
|
scale: scale2,
|
|
direction,
|
|
thickness,
|
|
highlight: highlight5,
|
|
segmentation,
|
|
bar,
|
|
targets,
|
|
cornerRadius,
|
|
cornerMode,
|
|
label
|
|
};
|
|
if (hasTooltip) {
|
|
seriesOpts.tooltip = seriesTooltipOptions;
|
|
}
|
|
return {
|
|
animation,
|
|
background,
|
|
container,
|
|
contextMenu,
|
|
context,
|
|
footnote,
|
|
foreground,
|
|
height: height2,
|
|
listeners,
|
|
locale,
|
|
minHeight,
|
|
minWidth,
|
|
overrideDevicePixelRatio,
|
|
padding: padding2,
|
|
subtitle,
|
|
theme,
|
|
title,
|
|
width: width2,
|
|
...hasTooltip ? { tooltip: chartTooltipOptions } : {},
|
|
series: [seriesOpts]
|
|
};
|
|
}
|
|
function applyThemeDefaults(opts, presetTheme) {
|
|
if (presetTheme == null)
|
|
return opts;
|
|
const { targets: targetsTheme, ...gaugeTheme } = presetTheme;
|
|
opts = mergeDefaults(opts, gaugeTheme);
|
|
if (opts.targets != null && targetsTheme != null) {
|
|
if (opts.type === "radial-gauge") {
|
|
opts.targets = mergeArrayDefaults(opts.targets, targetsTheme);
|
|
} else {
|
|
opts.targets = mergeArrayDefaults(opts.targets, targetsTheme);
|
|
}
|
|
}
|
|
return opts;
|
|
}
|
|
function createGauge(opts, presetTheme) {
|
|
switch (opts.type) {
|
|
case "radial-gauge":
|
|
return radialGaugeOptions(applyThemeDefaults(opts, presetTheme));
|
|
case "linear-gauge":
|
|
return linearGaugeOptions(applyThemeDefaults(opts, presetTheme));
|
|
default:
|
|
return { series: [] };
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/preset/gaugePresetModule.ts
|
|
var commonGaugeOptions2 = {
|
|
// Valid pass-through options
|
|
theme: defined,
|
|
container: defined,
|
|
animation: defined,
|
|
background: defined,
|
|
contextMenu: defined,
|
|
context: () => true,
|
|
listeners: defined,
|
|
locale: defined,
|
|
width: defined,
|
|
height: defined,
|
|
minWidth: defined,
|
|
minHeight: defined,
|
|
title: defined,
|
|
subtitle: defined,
|
|
footnote: defined,
|
|
padding: defined,
|
|
tooltip: {
|
|
...tooltipOptionsDefs,
|
|
...commonChartOptionsDefs.tooltip
|
|
}
|
|
};
|
|
commonGaugeOptions2.overrideDevicePixelRatio = undocumented(positiveNumber);
|
|
commonGaugeOptions2.foreground = undocumented(defined);
|
|
var GaugePresetModule = {
|
|
type: "preset",
|
|
name: "gauge-preset",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
dependencies: [StandaloneChartModule],
|
|
options: typeUnion(
|
|
{
|
|
"linear-gauge": {
|
|
...without(linearGaugeSeriesOptionsDef, ["type"]),
|
|
...commonGaugeOptions2
|
|
},
|
|
"radial-gauge": {
|
|
...without(radialGaugeSeriesOptionsDef, ["type"]),
|
|
...commonGaugeOptions2
|
|
}
|
|
},
|
|
"gauge options"
|
|
),
|
|
create: createGauge
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/gauge-util/datumUnion.ts
|
|
var DatumUnion = class {
|
|
*[Symbol.iterator]() {
|
|
const { node, datum } = this;
|
|
if (node && datum)
|
|
yield { node, datum };
|
|
}
|
|
nodes() {
|
|
return this.node ? [this.node] : [];
|
|
}
|
|
update(datumSelection, group, ctor, nodeUpdater) {
|
|
const nodes = datumSelection.nodes();
|
|
if (nodes.length === 0) {
|
|
this.node?.remove();
|
|
this.node = void 0;
|
|
} else {
|
|
if (this.node === void 0) {
|
|
this.node = new ctor();
|
|
this.node.fillOpacity = 0;
|
|
this.node.strokeOpacity = 0;
|
|
group.appendChild(this.node);
|
|
}
|
|
const first2 = nodes[0];
|
|
const last = nodes.toReversed().find((n) => n.datum.datum.value > n.datum.datum.segmentStart) ?? nodes.at(-1);
|
|
this.node.datum = this.datum = first2.datum;
|
|
nodeUpdater(this.node, first2, last);
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/gauge-util/label.ts
|
|
var fadeInFns = {
|
|
fromFn: () => ({ opacity: 0, phase: "initial" }),
|
|
toFn: () => ({ opacity: 1 })
|
|
};
|
|
function formatLabel(value, scale2) {
|
|
if (value == null)
|
|
return "";
|
|
const { min, max } = scale2;
|
|
const minLog10 = min === 0 ? 0 : Math.ceil(Math.log10(Math.abs(min)));
|
|
const maxLog10 = max === 0 ? 0 : Math.ceil(Math.log10(Math.abs(max)));
|
|
const dp = Math.max(2 - Math.max(minLog10, maxLog10), 0);
|
|
return value.toFixed(dp);
|
|
}
|
|
function getLabelText(seriesId, ctx, datum, valueOverride) {
|
|
if (datum.text != null)
|
|
return datum.text;
|
|
const value = valueOverride ?? datum.value;
|
|
let labelFormat;
|
|
if (datum?.formatter != null) {
|
|
labelFormat = formatWithContext(ctx, datum.formatter, { seriesId, datum: void 0, value });
|
|
}
|
|
return labelFormat == null || isArray(labelFormat) ? labelFormat : String(labelFormat);
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/gauge-util/lineMarker.ts
|
|
function lineMarker({ path, x, y, size }) {
|
|
path.moveTo(x, y - size / 2);
|
|
path.lineTo(x, y + size / 2);
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/gauge-util/pick.ts
|
|
function pickGaugeNearestDatum(self, point) {
|
|
const it = iterate(self.datumUnion.nodes(), self.targetSelection.nodes());
|
|
return self.pickNodeNearestDistantObject(point, it);
|
|
}
|
|
function pickGaugeFocus(self, opts) {
|
|
const others = [
|
|
{ data: self.contextNodeData?.nodeData, selection: self.datumUnion },
|
|
{ data: self.contextNodeData?.targetData, selection: self.targetSelection }
|
|
].filter((v) => v.data && v.data.length > 0);
|
|
const otherIndex = clamp(0, opts.otherIndex + opts.otherIndexDelta, others.length - 1);
|
|
if (others.length === 0)
|
|
return;
|
|
const { data, selection } = others[otherIndex];
|
|
if (data == null || data.length === 0)
|
|
return;
|
|
const datumIndex = clamp(0, opts.datumIndex, data.length - 1);
|
|
const datum = data[datumIndex];
|
|
for (const node of selection) {
|
|
if (node.datum === datum) {
|
|
const bounds = node.node;
|
|
return { bounds, clipFocusBox: true, datum, datumIndex, otherIndex };
|
|
}
|
|
}
|
|
}
|
|
function findGaugeNodeDatum(self, itemId) {
|
|
return module_support_exports.findNodeDatumInArray(itemId, self.contextNodeData?.nodeData) ?? module_support_exports.findNodeDatumInArray(itemId, self.contextNodeData?.targetData);
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/gauge-util/segmentation.ts
|
|
var GaugeSegmentationIntervalProperties = class extends BaseProperties {
|
|
getSegments(scale2, maxTicks) {
|
|
const { values, step, count } = this;
|
|
const d0 = Math.min(...scale2.domain);
|
|
const d1 = Math.max(...scale2.domain);
|
|
let ticks;
|
|
if (values != null) {
|
|
const segments = values.filter((v) => v > d0 && v < d1).sort((a, b) => a - b);
|
|
ticks = [d0, ...segments, d1];
|
|
} else if (step != null) {
|
|
const segments = [];
|
|
for (let i = d0; i < d1; i += step) {
|
|
segments.push(i);
|
|
}
|
|
segments.push(d1);
|
|
ticks = segments;
|
|
} else if (count == null) {
|
|
const segments = scale2.ticks({
|
|
nice: [true, true],
|
|
interval: void 0,
|
|
tickCount: void 0,
|
|
minTickCount: 0,
|
|
maxTickCount: Infinity
|
|
})?.ticks?.filter((v) => v > d0 && v < d1);
|
|
ticks = segments == null ? void 0 : [d0, ...segments, d1];
|
|
} else {
|
|
const segments = count + 1;
|
|
ticks = Array.from({ length: segments + 1 }, (_, i) => i / segments * (d1 - d0) + d0);
|
|
}
|
|
if (ticks != null && ticks.length > maxTicks) {
|
|
logger_exports.warnOnce(
|
|
`the configured segmentation results in more than 1 item per pixel, ignoring. Supply a segmentation configuration that results in larger segments or omit this configuration`
|
|
);
|
|
ticks = void 0;
|
|
}
|
|
ticks ?? (ticks = [d0, d1]);
|
|
return ticks;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GaugeSegmentationIntervalProperties.prototype, "values", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GaugeSegmentationIntervalProperties.prototype, "step", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GaugeSegmentationIntervalProperties.prototype, "count", 2);
|
|
var GaugeSegmentationProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = false;
|
|
this.interval = new GaugeSegmentationIntervalProperties();
|
|
this.spacing = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GaugeSegmentationProperties.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GaugeSegmentationProperties.prototype, "interval", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], GaugeSegmentationProperties.prototype, "spacing", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/linear-gauge/linearGaugeSeriesProperties.ts
|
|
var { makeSeriesTooltip: makeSeriesTooltip16, SeriesProperties: SeriesProperties5, Label: Label14, AxisLabel: AxisLabel6, getColorStops: getColorStops2 } = module_support_exports;
|
|
var LinearGaugeDefaultTargetLabelProperties = class extends Label14 {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeDefaultTargetLabelProperties.prototype, "spacing", 2);
|
|
var LinearGaugeTargetProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.value = 0;
|
|
this.label = new LinearGaugeDefaultTargetLabelProperties();
|
|
}
|
|
getStyle(defaultTarget) {
|
|
const {
|
|
fill = defaultTarget.fill ?? "black",
|
|
fillOpacity = defaultTarget.fillOpacity ?? 1,
|
|
stroke: stroke3 = defaultTarget.stroke ?? "black",
|
|
strokeWidth = defaultTarget.strokeWidth ?? 0,
|
|
strokeOpacity = defaultTarget.strokeOpacity ?? 1,
|
|
lineDash = defaultTarget.lineDash ?? [0],
|
|
lineDashOffset = defaultTarget.lineDashOffset ?? 0
|
|
} = this;
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "text", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "value", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "shape", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "placement", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "size", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "rotation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeTargetProperties.prototype, "label", 2);
|
|
var LinearGaugeBarProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = true;
|
|
this.thicknessRatio = 1;
|
|
this.fills = new PropertiesArray(module_support_exports.StopProperties);
|
|
this.fillMode = "continuous";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "black";
|
|
this.strokeWidth = 0;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
}
|
|
getStyle(defaultColorRange, horizontal, scale2) {
|
|
const { fill, fills, fillMode, fillOpacity, stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = this;
|
|
const barFill = fill ?? createLinearGradient(fills, fillMode, defaultColorRange, scale2, horizontal);
|
|
return {
|
|
fill: barFill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeBarProperties.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeBarProperties.prototype, "thickness", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeBarProperties.prototype, "thicknessRatio", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeBarProperties.prototype, "fills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeBarProperties.prototype, "fillMode", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeBarProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeBarProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeBarProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeBarProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeBarProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeBarProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeBarProperties.prototype, "lineDashOffset", 2);
|
|
var LinearGaugeScaleIntervalProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.values = void 0;
|
|
this.step = void 0;
|
|
this.minSpacing = 0;
|
|
this.maxSpacing = 1e3;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleIntervalProperties.prototype, "values", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleIntervalProperties.prototype, "step", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleIntervalProperties.prototype, "minSpacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleIntervalProperties.prototype, "maxSpacing", 2);
|
|
var LinearGaugeScaleLabelProperties = class extends AxisLabel6 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.placement = void 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleLabelProperties.prototype, "placement", 2);
|
|
var LinearGaugeScaleProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.min = 0;
|
|
this.max = 1;
|
|
this.fills = new PropertiesArray(module_support_exports.StopProperties);
|
|
this.fillMode = "continuous";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "black";
|
|
this.strokeWidth = 0;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.defaultFill = "black";
|
|
this.interval = new LinearGaugeScaleIntervalProperties();
|
|
this.label = new LinearGaugeScaleLabelProperties();
|
|
}
|
|
getStyle(barEnabled, defaultColorRange, horizontal, scale2) {
|
|
const {
|
|
fill,
|
|
fills,
|
|
defaultFill,
|
|
fillMode,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
} = this;
|
|
const scaleFill = fill ?? (barEnabled && fills.length === 0 ? defaultFill : void 0) ?? createLinearGradient(fills, fillMode, defaultColorRange, scale2, horizontal);
|
|
return {
|
|
fill: scaleFill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "min", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "max", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "fills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "fillMode", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "defaultFill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "interval", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeScaleProperties.prototype, "label", 2);
|
|
var LinearGaugeLabelProperties = class extends AutoSizedLabel {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.placement = "inside-center";
|
|
this.avoidCollisions = true;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeLabelProperties.prototype, "text", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeLabelProperties.prototype, "placement", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeLabelProperties.prototype, "avoidCollisions", 2);
|
|
var LinearGaugeSeriesProperties = class extends SeriesProperties5 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.value = 0;
|
|
this.segmentation = new GaugeSegmentationProperties();
|
|
this.defaultColorRange = [];
|
|
this.targets = new PropertiesArray(LinearGaugeTargetProperties);
|
|
this.defaultTarget = new LinearGaugeTargetProperties();
|
|
this.defaultScale = new LinearGaugeScaleProperties();
|
|
this.direction = "vertical";
|
|
this.thickness = 1;
|
|
this.cornerRadius = 0;
|
|
this.cornerMode = "container";
|
|
this.margin = 0;
|
|
this.scale = new LinearGaugeScaleProperties();
|
|
this.bar = new LinearGaugeBarProperties();
|
|
this.label = new LinearGaugeLabelProperties();
|
|
this.tooltip = makeSeriesTooltip16();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "value", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "segmentation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "defaultColorRange", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "targets", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "defaultTarget", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "defaultScale", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "direction", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "thickness", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "cornerMode", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "margin", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "scale", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "bar", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], LinearGaugeSeriesProperties.prototype, "tooltip", 2);
|
|
function createLinearGradient(fills, fillMode, defaultColorRange, scale2, horizontal) {
|
|
const colorStops = getColorStops2(fills, defaultColorRange, scale2.domain, fillMode);
|
|
return {
|
|
type: "gradient",
|
|
gradient: "linear",
|
|
colorSpace: "oklch",
|
|
colorStops,
|
|
rotation: horizontal ? 90 : 0,
|
|
bounds: "series"
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/linear-gauge/linearGaugeUtil.ts
|
|
var { BBox: BBox24 } = module_support_exports;
|
|
function datumRect(datum) {
|
|
const { x0, y0, x1, y1, horizontalInset, verticalInset } = datum;
|
|
const x = Math.min(x0, x1) + horizontalInset;
|
|
const y = Math.min(y0, y1) + verticalInset;
|
|
const width2 = Math.max(Math.abs(x1 - x0) - 2 * horizontalInset, 0);
|
|
const height2 = Math.max(Math.abs(y1 - y0) - 2 * verticalInset, 0);
|
|
return { x, y, width: width2, height: height2 };
|
|
}
|
|
function clipBBoxVisibility(datum, clipBBox) {
|
|
if (clipBBox == null)
|
|
return true;
|
|
const rect2 = datumRect(datum);
|
|
const delta5 = 1e-6;
|
|
const x0 = rect2.x + delta5;
|
|
const y0 = rect2.y + delta5;
|
|
const x1 = rect2.x + rect2.width - delta5;
|
|
const y1 = rect2.y + rect2.height - delta5;
|
|
const clipX0 = clipBBox.x;
|
|
const clipX1 = clipBBox.x + clipBBox.width;
|
|
const clipY0 = clipBBox.y;
|
|
const clipY1 = clipBBox.y + clipBBox.height;
|
|
return Math.max(x0, clipX0) <= Math.min(x1, clipX1) && Math.max(y0, clipY0) <= Math.min(y1, clipY1);
|
|
}
|
|
function hasClipBBox(datum) {
|
|
const { clipX0, clipX1, clipY0, clipY1 } = datum;
|
|
return clipX0 != null && clipX1 != null || clipY0 != null && clipY1 != null;
|
|
}
|
|
function computeClipBBox(datum) {
|
|
if (!hasClipBBox(datum))
|
|
return;
|
|
const { x0, y0, x1, y1 } = datum;
|
|
const { x, y, width: width2, height: height2 } = datumRect(datum);
|
|
let { clipX0, clipX1, clipY0, clipY1 } = datum;
|
|
if (clipX0 == null || clipX1 == null) {
|
|
clipX0 = x0;
|
|
clipX1 = x1;
|
|
}
|
|
if (clipY0 == null || clipY1 == null) {
|
|
clipY0 = y0;
|
|
clipY1 = y1;
|
|
}
|
|
const clipX = Math.min(clipX0, clipX1);
|
|
const clipY = Math.min(clipY0, clipY1);
|
|
const clipWidth = Math.abs(clipX1 - clipX0);
|
|
const clipHeight = Math.abs(clipY1 - clipY0);
|
|
clipX0 = Math.max(x, clipX);
|
|
clipY0 = Math.max(y, clipY);
|
|
clipX1 = Math.min(x + width2, clipX + clipWidth);
|
|
clipY1 = Math.min(y + height2, clipY + clipHeight);
|
|
return new BBox24(
|
|
Math.min(clipX0, clipX1),
|
|
Math.min(clipY0, clipY1),
|
|
Math.abs(clipX1 - clipX0),
|
|
Math.abs(clipY1 - clipY0)
|
|
);
|
|
}
|
|
function prepareLinearGaugeSeriesAnimationFunctions(initialLoad, horizontal) {
|
|
const phase = initialLoad ? "initial" : "update";
|
|
const node = {
|
|
fromFn(sect, datum) {
|
|
const previousDatum = sect.previousDatum;
|
|
let { x0, y0, x1, y1, clipX0, clipY0, clipX1, clipY1 } = previousDatum ?? datum;
|
|
const { horizontalInset, verticalInset } = datum;
|
|
const previousHadClipBBox = previousDatum != null && hasClipBBox(previousDatum);
|
|
const nextHasClipBBox = hasClipBBox(datum);
|
|
if (previousHadClipBBox && nextHasClipBBox) {
|
|
} else if (!previousHadClipBBox && nextHasClipBBox) {
|
|
({ x0, y0, x1, y1, clipX0, clipY0, clipX1, clipY1 } = datum);
|
|
if (initialLoad) {
|
|
if (horizontal) {
|
|
clipX1 = datum.clipX0;
|
|
} else {
|
|
clipY1 = datum.clipY0;
|
|
}
|
|
}
|
|
} else if (previousHadClipBBox && !nextHasClipBBox) {
|
|
({ x0, y0, x1, y1 } = datum);
|
|
clipX0 = void 0;
|
|
clipY0 = void 0;
|
|
clipX1 = void 0;
|
|
clipY1 = void 0;
|
|
} else if (initialLoad) {
|
|
if (horizontal) {
|
|
x1 = x0;
|
|
} else {
|
|
y1 = y0;
|
|
}
|
|
}
|
|
return { x0, y0, x1, y1, clipX0, clipY0, clipX1, clipY1, horizontalInset, verticalInset, phase };
|
|
},
|
|
toFn(_sect, datum) {
|
|
const { x0, y0, x1, y1, clipX0, clipY0, clipX1, clipY1, horizontalInset, verticalInset } = datum;
|
|
return { x0, y0, x1, y1, clipX0, clipY0, clipX1, clipY1, horizontalInset, verticalInset };
|
|
},
|
|
applyFn(rect2, params) {
|
|
rect2.setProperties(resetLinearGaugeSeriesResetRectFunction(rect2, params));
|
|
}
|
|
};
|
|
return { node };
|
|
}
|
|
function resetLinearGaugeSeriesResetRectFunction(_node, datum) {
|
|
const { x, y, width: width2, height: height2 } = datumRect(datum);
|
|
const clipBBox = computeClipBBox(datum);
|
|
const visible = clipBBoxVisibility(datum, clipBBox);
|
|
return { x, y, width: width2, height: height2, clipBBox, visible };
|
|
}
|
|
var horizontalTextAligns = {
|
|
["Before" /* Before */]: "right",
|
|
["Center" /* Center */]: "center",
|
|
["After" /* After */]: "left"
|
|
};
|
|
var verticalTextBaselines = {
|
|
["Before" /* Before */]: "top",
|
|
["Center" /* Center */]: "middle",
|
|
["After" /* After */]: "bottom"
|
|
};
|
|
var horizontalAlignFactors = {
|
|
["Before" /* Before */]: -1,
|
|
["Center" /* Center */]: -0.5,
|
|
["After" /* After */]: 0
|
|
};
|
|
var verticalAlignFactors3 = {
|
|
["Before" /* Before */]: 0,
|
|
["Center" /* Center */]: -0.5,
|
|
["After" /* After */]: -1
|
|
};
|
|
function formatLinearGaugeLabels(series, ctx, selection, opts, bboxes, datumOverrides) {
|
|
const { seriesRect, gaugeRect, barRect } = bboxes;
|
|
const { padding: padding2, horizontal } = opts;
|
|
selection.each((label, labelDatum) => {
|
|
const labelText = getLabelText(series.id, ctx, labelDatum, datumOverrides?.label);
|
|
let boundingWidth;
|
|
let boundingHeight;
|
|
if (labelDatum.placement === "outside-start") {
|
|
if (horizontal) {
|
|
boundingWidth = gaugeRect.x;
|
|
boundingHeight = seriesRect.height;
|
|
} else {
|
|
boundingWidth = seriesRect.width;
|
|
boundingHeight = seriesRect.height - (gaugeRect.y + gaugeRect.height);
|
|
}
|
|
} else if (labelDatum.placement === "outside-end") {
|
|
if (horizontal) {
|
|
boundingWidth = seriesRect.width - (gaugeRect.x + gaugeRect.width);
|
|
boundingHeight = seriesRect.height;
|
|
} else {
|
|
boundingWidth = seriesRect.width;
|
|
boundingHeight = gaugeRect.y;
|
|
}
|
|
} else if (labelDatum.avoidCollisions) {
|
|
boundingWidth = gaugeRect.width;
|
|
boundingHeight = gaugeRect.height;
|
|
}
|
|
let layout;
|
|
if (labelText == null) {
|
|
return;
|
|
} else if (boundingWidth != null && boundingHeight != null) {
|
|
const sizeFittingHeight = () => ({
|
|
width: boundingWidth,
|
|
height: boundingHeight,
|
|
meta: null
|
|
});
|
|
const labelMeta = formatSingleLabel(toPlainText(labelText), labelDatum, { padding: padding2 }, sizeFittingHeight);
|
|
layout = labelMeta?.[0];
|
|
} else {
|
|
const measurer3 = cachedTextMeasurer(labelDatum);
|
|
const { width: width2, height: height2 } = isArray(labelText) ? measureTextSegments(labelText, labelDatum) : measurer3.measureLines(toTextString(labelText));
|
|
layout = {
|
|
text: labelText,
|
|
fontSize: labelDatum.fontSize,
|
|
lineHeight: labelDatum.lineHeight ?? measurer3.lineHeight(),
|
|
width: width2,
|
|
height: height2
|
|
};
|
|
}
|
|
if (layout == null) {
|
|
label.visible = false;
|
|
return;
|
|
}
|
|
const scale0 = horizontal ? gaugeRect.x : gaugeRect.y + gaugeRect.height;
|
|
const scale1 = horizontal ? gaugeRect.x + gaugeRect.width : gaugeRect.y;
|
|
const bar0 = horizontal ? barRect.x : barRect.y + barRect.height;
|
|
const bar1 = horizontal ? barRect.x + barRect.width : barRect.y;
|
|
const offset = labelDatum.spacing * (horizontal ? 1 : -1);
|
|
let bounds0;
|
|
let bounds1;
|
|
let s;
|
|
let align2;
|
|
switch (labelDatum.placement) {
|
|
case "outside-start":
|
|
bounds0 = -Infinity;
|
|
bounds1 = Infinity;
|
|
s = scale0 - offset;
|
|
align2 = "Before" /* Before */;
|
|
break;
|
|
case "outside-end":
|
|
bounds0 = -Infinity;
|
|
bounds1 = Infinity;
|
|
s = scale1 + offset;
|
|
align2 = "After" /* After */;
|
|
break;
|
|
case "inside-start":
|
|
bounds0 = scale0;
|
|
bounds1 = bar1;
|
|
s = scale0 + offset;
|
|
align2 = "After" /* After */;
|
|
break;
|
|
case "inside-end":
|
|
bounds0 = bar1;
|
|
bounds1 = scale1;
|
|
s = scale1 - offset;
|
|
align2 = "Before" /* Before */;
|
|
break;
|
|
case "inside-center":
|
|
bounds0 = scale0;
|
|
bounds1 = scale1;
|
|
s = (scale0 + scale1) / 2;
|
|
align2 = "Center" /* Center */;
|
|
break;
|
|
case "bar-inside":
|
|
bounds0 = bar0;
|
|
bounds1 = bar1;
|
|
s = (bar0 + bar1) / 2;
|
|
align2 = "Center" /* Center */;
|
|
break;
|
|
case "bar-inside-end":
|
|
bounds0 = bar0;
|
|
bounds1 = bar1;
|
|
s = bar1 - offset;
|
|
align2 = "Before" /* Before */;
|
|
break;
|
|
case "bar-outside-end":
|
|
bounds0 = bar1;
|
|
bounds1 = scale1;
|
|
s = bar1 + offset;
|
|
align2 = "After" /* After */;
|
|
break;
|
|
case "bar-end":
|
|
bounds0 = -Infinity;
|
|
bounds1 = Infinity;
|
|
s = bar1;
|
|
align2 = "Center" /* Center */;
|
|
break;
|
|
}
|
|
const x = horizontal ? s : gaugeRect.x + gaugeRect.width / 2;
|
|
const y = horizontal ? gaugeRect.y + gaugeRect.height / 2 : s;
|
|
let s0;
|
|
let s1;
|
|
if (horizontal) {
|
|
s0 = x + horizontalAlignFactors[align2] * layout.width;
|
|
s1 = s0 + layout.width;
|
|
} else {
|
|
s0 = y + verticalAlignFactors3[align2] * layout.height;
|
|
s1 = s0 + layout.height;
|
|
}
|
|
const inside = Math.min(s0, s1) >= Math.min(bounds0, bounds1) && Math.max(s0, s1) <= Math.max(bounds0, bounds1);
|
|
if (labelDatum.avoidCollisions && !inside) {
|
|
label.visible = false;
|
|
return;
|
|
}
|
|
label.visible = true;
|
|
label.text = layout.text;
|
|
label.fontSize = layout.fontSize;
|
|
label.lineHeight = layout.lineHeight;
|
|
label.textAlign = horizontal ? horizontalTextAligns[align2] : "center";
|
|
label.textBaseline = horizontal ? "middle" : verticalTextBaselines[align2];
|
|
label.x = x;
|
|
label.y = y;
|
|
});
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/linear-gauge/linearGaugeSeries.ts
|
|
var {
|
|
fromToMotion: fromToMotion5,
|
|
resetMotion: resetMotion4,
|
|
SeriesNodePickMode: SeriesNodePickMode11,
|
|
createDatumId: createDatumId15,
|
|
BBox: BBox25,
|
|
Group: Group14,
|
|
PointerEvents: PointerEvents8,
|
|
Selection: Selection9,
|
|
Rect: Rect11,
|
|
Text: Text6,
|
|
TransformableText: TransformableText4,
|
|
Marker: Marker4,
|
|
LinearScale: LinearScale3,
|
|
generateTicks: generateTicks2,
|
|
NiceMode: NiceMode2
|
|
} = module_support_exports;
|
|
var horizontalTargetPlacementRotation = {
|
|
before: 180,
|
|
middle: 0,
|
|
after: 0
|
|
};
|
|
var verticalTargetPlacementRotation = {
|
|
before: 90,
|
|
middle: 0,
|
|
after: -90
|
|
};
|
|
var LinearGaugeSeries = class extends module_support_exports.Series {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
pickModes: [SeriesNodePickMode11.EXACT_SHAPE_MATCH, SeriesNodePickMode11.NEAREST_NODE]
|
|
});
|
|
this.properties = new LinearGaugeSeriesProperties();
|
|
this.seriesRect = BBox25.NaN;
|
|
this.gaugeRect = BBox25.NaN;
|
|
this.scale = new LinearScale3();
|
|
this.originX = 0;
|
|
this.originY = 0;
|
|
this.scaleGroup = this.contentGroup.appendChild(new Group14({ name: "scaleGroup" }));
|
|
this.itemGroup = this.contentGroup.appendChild(new Group14({ name: "itemGroup" }));
|
|
this.itemTargetGroup = this.contentGroup.appendChild(new Group14({ name: "itemTargetGroup" }));
|
|
this.itemTargetLabelGroup = this.contentGroup.appendChild(new Group14({ name: "itemTargetLabelGroup" }));
|
|
this.itemLabelGroup = this.contentGroup.appendChild(new Group14({ name: "itemLabelGroup" }));
|
|
this.highlightTargetGroup = this.highlightGroup.appendChild(
|
|
new Group14({ name: "itemTargetLabelGroup" })
|
|
);
|
|
this.tickGroup = this.contentGroup.appendChild(new Group14({ name: "tickGroup" }));
|
|
this.scaleSelection = Selection9.select(
|
|
this.scaleGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.datumSelection = Selection9.select(
|
|
this.itemGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.targetSelection = Selection9.select(
|
|
this.itemTargetGroup,
|
|
() => this.markerFactory()
|
|
);
|
|
this.targetLabelSelection = Selection9.select(this.itemTargetLabelGroup, Text6);
|
|
this.labelSelection = Selection9.select(
|
|
this.itemLabelGroup,
|
|
Text6
|
|
);
|
|
this.highlightTargetSelection = Selection9.select(this.highlightTargetGroup, () => this.markerFactory());
|
|
this.tickSelection = Selection9.select(this.tickGroup, TransformableText4);
|
|
this.datumUnion = new DatumUnion();
|
|
this.animationState = new StateMachine("empty", {
|
|
empty: {
|
|
update: {
|
|
target: "ready",
|
|
action: () => this.animateEmptyUpdateReady()
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
ready: {
|
|
updateData: "waiting",
|
|
clear: "clearing",
|
|
resize: () => this.animateReadyResize(),
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
waiting: {
|
|
update: {
|
|
target: "ready",
|
|
action: () => this.animateWaitingUpdateReady()
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
clearing: {
|
|
update: {
|
|
target: "empty"
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
}
|
|
});
|
|
this.scaleGroup.pointerEvents = PointerEvents8.None;
|
|
this.tickGroup.pointerEvents = PointerEvents8.None;
|
|
}
|
|
get range() {
|
|
return this.horizontal ? [0, this.gaugeRect.width] : [0, this.gaugeRect.height];
|
|
}
|
|
get horizontal() {
|
|
return this.properties.direction === "horizontal";
|
|
}
|
|
get hasData() {
|
|
return true;
|
|
}
|
|
nodeFactory() {
|
|
const rect2 = new Rect11();
|
|
rect2.crisp = true;
|
|
return rect2;
|
|
}
|
|
markerFactory() {
|
|
return new Marker4();
|
|
}
|
|
processData() {
|
|
this.nodeDataRefresh = true;
|
|
this.animationState.transition("updateData");
|
|
}
|
|
formatLabel(value) {
|
|
return formatLabel(value, this.properties.scale);
|
|
}
|
|
getShapeFillBBox() {
|
|
const { properties, originX, originY, horizontal, scale: scale2 } = this;
|
|
const { thickness } = properties;
|
|
const length2 = findRangeExtent(scale2.range);
|
|
const bbox = new BBox25(originX, originY, horizontal ? length2 : thickness, horizontal ? thickness : length2);
|
|
return {
|
|
axis: bbox,
|
|
series: bbox
|
|
};
|
|
}
|
|
getTargets() {
|
|
const { properties } = this;
|
|
const defaultTarget = properties.defaultTarget;
|
|
return Array.from(properties.targets).map((target) => {
|
|
const {
|
|
text: text2 = defaultTarget.text,
|
|
value = defaultTarget.value ?? 0,
|
|
shape = defaultTarget.shape ?? "triangle",
|
|
rotation = defaultTarget.rotation ?? 0,
|
|
placement = defaultTarget.placement ?? "middle",
|
|
spacing = defaultTarget.spacing ?? 0,
|
|
size = defaultTarget.size ?? 0
|
|
} = target;
|
|
const {
|
|
enabled: labelEnabled = defaultTarget.label.enabled,
|
|
color: labelColor = defaultTarget.label.color ?? "black",
|
|
fontStyle: labelFontStyle = defaultTarget.label.fontStyle ?? "normal",
|
|
fontWeight: labelFontWeight = defaultTarget.label.fontWeight ?? "normal",
|
|
fontSize: labelFontSize = defaultTarget.label.fontSize,
|
|
fontFamily: labelFontFamily = defaultTarget.label.fontFamily,
|
|
spacing: labelSpacing = defaultTarget.label.spacing ?? 0
|
|
} = target.label;
|
|
return {
|
|
text: text2,
|
|
value,
|
|
shape,
|
|
placement,
|
|
spacing,
|
|
size,
|
|
rotation,
|
|
label: {
|
|
enabled: labelEnabled,
|
|
color: labelColor,
|
|
fontStyle: labelFontStyle,
|
|
fontWeight: labelFontWeight,
|
|
fontSize: labelFontSize,
|
|
fontFamily: labelFontFamily,
|
|
spacing: labelSpacing
|
|
},
|
|
style: target.getStyle(defaultTarget)
|
|
};
|
|
});
|
|
}
|
|
getTargetPoint(target) {
|
|
const { properties, originX, originY, horizontal, scale: scale2, gaugeRect } = this;
|
|
const { thickness } = properties;
|
|
const { value, placement, spacing, size } = target;
|
|
const mainOffset = scale2.convert(value);
|
|
let crossOffset;
|
|
switch (placement) {
|
|
case "before":
|
|
crossOffset = -(spacing + size / 2);
|
|
break;
|
|
case "after":
|
|
crossOffset = thickness + spacing + size / 2;
|
|
break;
|
|
default:
|
|
crossOffset = thickness / 2;
|
|
break;
|
|
}
|
|
return {
|
|
x: originX + gaugeRect.x + (horizontal ? mainOffset : crossOffset),
|
|
y: originY + gaugeRect.y + (horizontal ? crossOffset : mainOffset)
|
|
};
|
|
}
|
|
getTargetLabel(target) {
|
|
const { size, placement, label } = target;
|
|
const { spacing, color: fill, fontStyle, fontWeight: fontWeight2, fontSize, fontFamily } = label;
|
|
const lineHeight = void 0;
|
|
const offset = size / 2 + spacing;
|
|
let textAlign;
|
|
let textBaseline;
|
|
let offsetX = 0;
|
|
let offsetY = 0;
|
|
if (this.horizontal) {
|
|
textAlign = "center";
|
|
if (placement === "after") {
|
|
textBaseline = "top";
|
|
offsetY = offset;
|
|
} else {
|
|
textBaseline = "bottom";
|
|
offsetY = -offset;
|
|
}
|
|
} else {
|
|
textBaseline = "middle";
|
|
if (placement === "before") {
|
|
textAlign = "right";
|
|
offsetX = -offset;
|
|
} else {
|
|
textAlign = "left";
|
|
offsetX = offset;
|
|
}
|
|
}
|
|
return {
|
|
offsetX,
|
|
offsetY,
|
|
fill,
|
|
textAlign,
|
|
textBaseline,
|
|
fontStyle,
|
|
fontWeight: fontWeight2,
|
|
fontSize,
|
|
fontFamily,
|
|
lineHeight
|
|
};
|
|
}
|
|
labelDatum(label, value) {
|
|
const {
|
|
placement,
|
|
avoidCollisions,
|
|
spacing,
|
|
text: text2,
|
|
color: fill,
|
|
fontSize,
|
|
minimumFontSize,
|
|
fontStyle,
|
|
fontWeight: fontWeight2,
|
|
fontFamily,
|
|
lineHeight,
|
|
wrapping,
|
|
overflowStrategy,
|
|
formatter: formatter2 = (params) => this.formatLabel(params.value)
|
|
} = label;
|
|
return {
|
|
placement,
|
|
avoidCollisions,
|
|
spacing,
|
|
text: text2,
|
|
value,
|
|
fill,
|
|
fontSize,
|
|
minimumFontSize,
|
|
fontStyle,
|
|
fontWeight: fontWeight2,
|
|
fontFamily,
|
|
lineHeight,
|
|
wrapping,
|
|
overflowStrategy,
|
|
formatter: formatter2
|
|
};
|
|
}
|
|
verticalLabelInset() {
|
|
const { label } = this.properties;
|
|
const measurer3 = cachedTextMeasurer(label);
|
|
const lines = label.text?.split("\n");
|
|
const labelSize = (label.lineHeight ?? measurer3.lineHeight()) * (lines?.length ?? 1);
|
|
return label.spacing + labelSize;
|
|
}
|
|
horizontalLabelInset() {
|
|
const { scale: scale2, properties } = this;
|
|
const { scale: scaleProps, label } = properties;
|
|
const lines = label.text?.split("\n");
|
|
const measurer3 = cachedTextMeasurer(label);
|
|
const ticks = scaleProps.interval.values ?? scale2.ticks({
|
|
nice: [false, false],
|
|
interval: scaleProps.interval.step,
|
|
minTickCount: 0,
|
|
maxTickCount: 6,
|
|
tickCount: 5
|
|
})?.ticks ?? [];
|
|
const linesOrTicks = lines ?? ticks?.map((tick) => getLabelText(this.id, this.ctx, this.labelDatum(label, tick)) ?? "");
|
|
const labelSize = linesOrTicks.reduce((accum, text2) => {
|
|
const { width: width2 } = isArray(text2) ? measureTextSegments(text2, label) : measurer3.measureLines(toTextString(text2));
|
|
return Math.max(accum, width2);
|
|
}, 0);
|
|
return label.spacing + labelSize;
|
|
}
|
|
tickFormatter(domain, ticks) {
|
|
const { format, formatter: formatter2 } = this.properties.scale.label;
|
|
let tickFormatter;
|
|
if (format != null) {
|
|
tickFormatter = tickFormat(ticks, typeof format === "string" ? format : void 0);
|
|
}
|
|
return (value, index) => {
|
|
let r = void 0;
|
|
if (formatter2) {
|
|
r ?? (r = formatWithContext(this.ctx, formatter2, { value, index, domain, boundSeries: void 0 }));
|
|
}
|
|
r ?? (r = tickFormatter?.(value));
|
|
return r ?? this.formatLabel(value);
|
|
};
|
|
}
|
|
createNodeData() {
|
|
const { id: seriesId, properties, horizontal, scale: scale2, seriesRect } = this;
|
|
const {
|
|
value,
|
|
segmentation,
|
|
thickness,
|
|
cornerRadius,
|
|
cornerMode,
|
|
bar,
|
|
scale: scaleProps,
|
|
label,
|
|
defaultColorRange,
|
|
defaultScale
|
|
} = properties;
|
|
scale2.domain = [scaleProps.min, scaleProps.max];
|
|
scale2.range = horizontal ? [0, seriesRect.width] : [seriesRect.height, 0];
|
|
let axisRotation;
|
|
let sideFlag;
|
|
if (horizontal) {
|
|
sideFlag = 1;
|
|
axisRotation = Math.PI / -2;
|
|
} else if (scaleProps.label.placement === "before") {
|
|
sideFlag = 1;
|
|
axisRotation = 0;
|
|
} else {
|
|
sideFlag = -1;
|
|
axisRotation = 0;
|
|
}
|
|
let x0;
|
|
let x1;
|
|
let y0;
|
|
let y1;
|
|
if (horizontal) {
|
|
x0 = 0;
|
|
x1 = seriesRect.width;
|
|
y0 = (seriesRect.height - thickness) / 2;
|
|
y1 = y0 + thickness;
|
|
if (label.placement === "outside-start") {
|
|
x0 += this.horizontalLabelInset();
|
|
} else if (label.placement === "outside-end") {
|
|
x1 -= this.horizontalLabelInset();
|
|
}
|
|
} else {
|
|
x0 = (seriesRect.width - thickness) / 2;
|
|
x1 = x0 + thickness;
|
|
y1 = 0;
|
|
y0 = seriesRect.height;
|
|
if (label.placement === "outside-start") {
|
|
y0 -= this.verticalLabelInset();
|
|
} else if (label.placement === "outside-end") {
|
|
y1 += this.verticalLabelInset();
|
|
}
|
|
}
|
|
this.gaugeRect = new BBox25(Math.min(x0, x1), Math.min(y0, y1), Math.abs(x1 - x0), Math.abs(y1 - y0));
|
|
const originX = 0;
|
|
const originY = 0;
|
|
scale2.domain = [scaleProps.min, scaleProps.max];
|
|
scale2.range = horizontal ? [x0, x1] : [y0, y1];
|
|
const scaleLabel = mergeDefaults({ parallel: horizontal }, scaleProps.label, defaultScale.label);
|
|
const {
|
|
tickData: { ticks: tickData }
|
|
} = generateTicks2({
|
|
scale: scale2,
|
|
label: scaleLabel,
|
|
interval: scaleProps.interval,
|
|
tickFormatter: (domain, ticks) => this.tickFormatter(domain, ticks),
|
|
domain: scale2.domain,
|
|
range: this.range,
|
|
reverse: false,
|
|
primaryTickCount: void 0,
|
|
defaultTickMinSpacing: 0,
|
|
visibleRange: [0, 1],
|
|
niceMode: [NiceMode2.Off, NiceMode2.Off],
|
|
labelOffset: 0,
|
|
axisRotation,
|
|
sideFlag
|
|
});
|
|
const isReversed = false;
|
|
const targets = this.getTargets();
|
|
const nodeData = [];
|
|
const targetData = [];
|
|
const labelData = [];
|
|
const scaleData = [];
|
|
const [m0, m1] = scale2.range;
|
|
const mainAxisSize = Math.abs(m1 - m0);
|
|
const containerX = horizontal ? scale2.convert(value) : x1;
|
|
const containerY = horizontal ? y1 : scale2.convert(value);
|
|
const inset = segmentation.enabled ? segmentation.spacing / 2 : 0;
|
|
const horizontalInset = horizontal ? inset : 0;
|
|
const verticalInset = horizontal ? 0 : inset;
|
|
const barThickness = Math.min(bar.thickness ?? Math.round(bar.thicknessRatio * thickness), thickness);
|
|
const barInset = -(thickness - barThickness) / 2;
|
|
const barXInset = horizontal ? 0 : barInset;
|
|
const barYInset = horizontal ? barInset : 0;
|
|
const cornersOnAllItems = cornerMode === "item";
|
|
const maxTicks = Math.ceil(mainAxisSize);
|
|
let segments = segmentation.enabled ? segmentation.interval.getSegments(scale2, maxTicks) : void 0;
|
|
const barStyle = bar.getStyle(defaultColorRange, horizontal, scale2);
|
|
const scaleStyle = scaleProps.getStyle(bar.enabled, defaultColorRange, horizontal, scale2);
|
|
if (segments == null && cornersOnAllItems) {
|
|
const segmentStart = Math.min(...scale2.domain);
|
|
const segmentEnd = Math.max(...scale2.domain);
|
|
const datum = { value, segmentStart, segmentEnd };
|
|
if (bar.enabled) {
|
|
const barAppliedCornerRadius = Math.min(cornerRadius, barThickness / 2, mainAxisSize / 2);
|
|
const barCornerInset = barAppliedCornerRadius * (isReversed ? -1 : 1);
|
|
const barCornerXInset = horizontal ? barCornerInset : 0;
|
|
const barCornerYInset = horizontal ? 0 : barCornerInset;
|
|
nodeData.push({
|
|
series: this,
|
|
itemId: `value`,
|
|
datum,
|
|
datumIndex: { type: 0 /* Node */ },
|
|
type: 0 /* Node */,
|
|
x0: originX + x0 - barCornerXInset - barXInset,
|
|
y0: originY + y0 - barCornerYInset - barYInset,
|
|
x1: originX + containerX + barCornerXInset + barXInset,
|
|
y1: originY + containerY + barCornerYInset + barYInset,
|
|
clipX0: void 0,
|
|
clipY0: void 0,
|
|
clipX1: void 0,
|
|
clipY1: void 0,
|
|
topLeftCornerRadius: cornerRadius,
|
|
topRightCornerRadius: cornerRadius,
|
|
bottomRightCornerRadius: cornerRadius,
|
|
bottomLeftCornerRadius: cornerRadius,
|
|
horizontalInset,
|
|
verticalInset,
|
|
style: barStyle
|
|
});
|
|
}
|
|
const scaleAppliedCornerRadius = Math.min(cornerRadius, thickness / 2, mainAxisSize / 2);
|
|
const scaleCornerInset = scaleAppliedCornerRadius * (isReversed ? -1 : 1);
|
|
const scaleCornerXInset = horizontal ? scaleCornerInset : 0;
|
|
const scaleCornerYInset = horizontal ? 0 : scaleCornerInset;
|
|
scaleData.push({
|
|
series: this,
|
|
itemId: `scale`,
|
|
datum,
|
|
datumIndex: { type: 0 /* Node */ },
|
|
type: 0 /* Node */,
|
|
x0: originX + x0 - scaleCornerXInset,
|
|
y0: originY + y0 - scaleCornerYInset,
|
|
x1: originX + x1 + scaleCornerXInset,
|
|
y1: originY + y1 + scaleCornerYInset,
|
|
clipX0: void 0,
|
|
clipY0: void 0,
|
|
clipX1: void 0,
|
|
clipY1: void 0,
|
|
topLeftCornerRadius: cornerRadius,
|
|
topRightCornerRadius: cornerRadius,
|
|
bottomRightCornerRadius: cornerRadius,
|
|
bottomLeftCornerRadius: cornerRadius,
|
|
horizontalInset,
|
|
verticalInset,
|
|
style: scaleStyle
|
|
});
|
|
} else {
|
|
segments ?? (segments = scale2.domain);
|
|
const clipX0 = originX + x0 - barXInset;
|
|
const clipY0 = originY + y0 - barYInset;
|
|
const clipX1 = originX + containerX + barXInset;
|
|
const clipY1 = originY + containerY + barYInset;
|
|
for (let i = 0; i < segments.length - 1; i += 1) {
|
|
const segmentStart = segments[i + 0];
|
|
const segmentEnd = segments[i + 1];
|
|
const datum = { value, segmentStart, segmentEnd };
|
|
const isStart = i === 0;
|
|
const isEnd = i === segments.length - 2;
|
|
const itemStart = scale2.convert(segmentStart);
|
|
const itemEnd = scale2.convert(segmentEnd);
|
|
const startCornerRadius = cornersOnAllItems || isStart ? cornerRadius : 0;
|
|
const endCornerRadius = cornersOnAllItems || isEnd ? cornerRadius : 0;
|
|
const topLeftCornerRadius = horizontal ? startCornerRadius : endCornerRadius;
|
|
const topRightCornerRadius = endCornerRadius;
|
|
const bottomRightCornerRadius = horizontal ? endCornerRadius : startCornerRadius;
|
|
const bottomLeftCornerRadius = startCornerRadius;
|
|
if (bar.enabled) {
|
|
nodeData.push({
|
|
series: this,
|
|
itemId: `value-${i}`,
|
|
datum,
|
|
datumIndex: { type: 0 /* Node */ },
|
|
type: 0 /* Node */,
|
|
x0: originX + (horizontal ? itemStart : x0),
|
|
y0: originY + (horizontal ? y0 : itemStart),
|
|
x1: originX + (horizontal ? itemEnd : x1),
|
|
y1: originY + (horizontal ? y1 : itemEnd),
|
|
clipX0,
|
|
clipY0,
|
|
clipX1,
|
|
clipY1,
|
|
topLeftCornerRadius,
|
|
topRightCornerRadius,
|
|
bottomRightCornerRadius,
|
|
bottomLeftCornerRadius,
|
|
horizontalInset,
|
|
verticalInset,
|
|
style: barStyle
|
|
});
|
|
}
|
|
scaleData.push({
|
|
series: this,
|
|
itemId: `scale-${i}`,
|
|
datum,
|
|
datumIndex: { type: 0 /* Node */ },
|
|
type: 0 /* Node */,
|
|
x0: originX + (horizontal ? itemStart : x0),
|
|
y0: originY + (horizontal ? y0 : itemStart),
|
|
x1: originX + (horizontal ? itemEnd : x1),
|
|
y1: originY + (horizontal ? y1 : itemEnd),
|
|
clipX0: void 0,
|
|
clipY0: void 0,
|
|
clipX1: void 0,
|
|
clipY1: void 0,
|
|
topLeftCornerRadius,
|
|
topRightCornerRadius,
|
|
bottomRightCornerRadius,
|
|
bottomLeftCornerRadius,
|
|
horizontalInset,
|
|
verticalInset,
|
|
style: scaleStyle
|
|
});
|
|
}
|
|
}
|
|
for (const dataArray of [scaleData, nodeData]) {
|
|
for (const datum of dataArray) {
|
|
const dx0 = datum.clipX0 ?? datum.x0;
|
|
const dx1 = datum.clipX1 ?? datum.x1;
|
|
const dy0 = datum.clipY0 ?? datum.y0;
|
|
const dy1 = datum.clipY1 ?? datum.y1;
|
|
datum.midPoint = { x: (dx0 + dx1) / 2, y: (dy0 + dy1) / 2 };
|
|
}
|
|
}
|
|
if (label.enabled) {
|
|
labelData.push(this.labelDatum(label, value));
|
|
}
|
|
const targetPlacementRotation2 = horizontal ? horizontalTargetPlacementRotation : verticalTargetPlacementRotation;
|
|
for (let i = 0; i < targets.length; i += 1) {
|
|
const target = targets[i];
|
|
const { value: targetValue, text: text2, shape, size, style: style2 } = target;
|
|
const targetPoint = this.getTargetPoint(target);
|
|
const targetRotation = toRadians(target.rotation + targetPlacementRotation2[target.placement]);
|
|
targetData.push({
|
|
series: this,
|
|
itemId: `target-${i}`,
|
|
midPoint: targetPoint,
|
|
datum: { value: targetValue },
|
|
datumIndex: { type: 1 /* Target */, index: i },
|
|
type: 1 /* Target */,
|
|
value: targetValue,
|
|
text: text2,
|
|
x: targetPoint.x,
|
|
y: targetPoint.y,
|
|
shape,
|
|
size,
|
|
rotation: targetRotation,
|
|
label: this.getTargetLabel(target),
|
|
style: style2
|
|
});
|
|
}
|
|
return {
|
|
itemId: seriesId,
|
|
nodeData,
|
|
tickData,
|
|
targetData,
|
|
labelData,
|
|
scaleData
|
|
};
|
|
}
|
|
findNodeDatum(itemId) {
|
|
return findGaugeNodeDatum(this, itemId);
|
|
}
|
|
updateSelections(resize) {
|
|
if (this.nodeDataRefresh || resize) {
|
|
this.contextNodeData = this.createNodeData();
|
|
this.nodeDataRefresh = false;
|
|
}
|
|
}
|
|
highlightDatum(node) {
|
|
if (node?.series === this && node.type === 1 /* Target */) {
|
|
return node;
|
|
}
|
|
}
|
|
update({ seriesRect }) {
|
|
const {
|
|
datumSelection,
|
|
labelSelection,
|
|
targetSelection,
|
|
targetLabelSelection,
|
|
scaleSelection,
|
|
highlightTargetSelection,
|
|
tickSelection
|
|
} = this;
|
|
this.seriesRect = seriesRect ?? BBox25.NaN;
|
|
const resize = this.checkResize(seriesRect);
|
|
this.updateSelections(resize);
|
|
this.contentGroup.visible = this.visible;
|
|
this.contentGroup.opacity = this.getOpacity();
|
|
const nodeData = this.contextNodeData?.nodeData ?? [];
|
|
const labelData = this.contextNodeData?.labelData ?? [];
|
|
const targetData = this.contextNodeData?.targetData ?? [];
|
|
const scaleData = this.contextNodeData?.scaleData ?? [];
|
|
const tickData = this.contextNodeData?.tickData ?? [];
|
|
const highlightTargetDatum = this.highlightDatum(this.ctx.highlightManager.getActiveHighlight());
|
|
this.scaleSelection = this.updateScaleSelection({ scaleData, scaleSelection });
|
|
this.updateScaleNodes({ scaleSelection });
|
|
this.targetSelection = this.updateTargetSelection({ targetData, targetSelection });
|
|
this.updateTargetNodes({ targetSelection, isHighlight: false });
|
|
this.targetLabelSelection = this.updateTargetLabelSelection({ targetData, targetLabelSelection });
|
|
this.updateTargetLabelNodes({ targetLabelSelection });
|
|
this.datumSelection = this.updateDatumSelection({ nodeData, datumSelection });
|
|
this.updateDatumNodes({ datumSelection });
|
|
this.labelSelection = this.updateLabelSelection({ labelData, labelSelection });
|
|
this.updateLabelNodes({ labelSelection });
|
|
this.highlightTargetSelection = this.updateTargetSelection({
|
|
targetData: highlightTargetDatum == null ? [] : [highlightTargetDatum],
|
|
targetSelection: highlightTargetSelection
|
|
});
|
|
this.updateTargetNodes({ targetSelection: highlightTargetSelection, isHighlight: true });
|
|
this.tickSelection = this.updateTickSelection({ tickData, tickSelection });
|
|
this.updateTickNodes({ tickSelection });
|
|
if (resize) {
|
|
this.animationState.transition("resize");
|
|
}
|
|
this.animationState.transition("update");
|
|
}
|
|
updateDatumSelection(opts) {
|
|
return opts.datumSelection.update(opts.nodeData, void 0, (datum) => {
|
|
return createDatumId15(opts.nodeData.length, datum.itemId);
|
|
});
|
|
}
|
|
updateDatumNodes(opts) {
|
|
const { datumSelection } = opts;
|
|
const { ctx } = this;
|
|
const animationDisabled = ctx.animationManager.isSkipped();
|
|
const fillBBox = this.getShapeFillBBox();
|
|
datumSelection.each((rect2, datum) => {
|
|
const { topLeftCornerRadius, topRightCornerRadius, bottomRightCornerRadius, bottomLeftCornerRadius } = datum;
|
|
rect2.setStyleProperties(datum.style, fillBBox);
|
|
rect2.topLeftCornerRadius = topLeftCornerRadius;
|
|
rect2.topRightCornerRadius = topRightCornerRadius;
|
|
rect2.bottomRightCornerRadius = bottomRightCornerRadius;
|
|
rect2.bottomLeftCornerRadius = bottomLeftCornerRadius;
|
|
rect2.pointerEvents = this.properties.bar.enabled ? module_support_exports.PointerEvents.All : module_support_exports.PointerEvents.None;
|
|
if (animationDisabled || rect2.previousDatum == null) {
|
|
rect2.setProperties(resetLinearGaugeSeriesResetRectFunction(rect2, datum));
|
|
}
|
|
});
|
|
const { horizontal } = this;
|
|
this.datumUnion.update(datumSelection, this.itemGroup, module_support_exports.Rect, (node, first2, last) => {
|
|
const left = Math.min(first2.x, last.x);
|
|
const right = Math.max(first2.x + first2.width, last.x + last.width);
|
|
const top = Math.min(first2.y, last.y);
|
|
const bottom = Math.max(first2.y + first2.height, last.y + last.height);
|
|
const width2 = right - left;
|
|
const height2 = bottom - top;
|
|
node.pointerEvents = module_support_exports.PointerEvents.None;
|
|
node.x = left;
|
|
node.y = top;
|
|
node.width = width2;
|
|
node.height = height2;
|
|
node.topLeftCornerRadius = horizontal ? first2.topLeftCornerRadius : last.topLeftCornerRadius;
|
|
node.topRightCornerRadius = last.topRightCornerRadius;
|
|
node.bottomRightCornerRadius = horizontal ? last.bottomRightCornerRadius : first2.bottomRightCornerRadius;
|
|
node.bottomLeftCornerRadius = first2.bottomLeftCornerRadius;
|
|
const firstClipBBox = first2.clipBBox;
|
|
const lastClipBBox = last.clipBBox ?? firstClipBBox;
|
|
if (firstClipBBox && lastClipBBox) {
|
|
node.clipBBox = BBox25.merge([firstClipBBox, lastClipBBox]).intersection(
|
|
horizontal ? new BBox25(left, -Infinity, width2, Infinity) : new BBox25(-Infinity, top, Infinity, height2)
|
|
);
|
|
} else {
|
|
node.clipBBox = void 0;
|
|
}
|
|
});
|
|
}
|
|
updateScaleSelection(opts) {
|
|
return opts.scaleSelection.update(opts.scaleData, void 0, (datum) => {
|
|
return createDatumId15(opts.scaleData.length, datum.itemId);
|
|
});
|
|
}
|
|
updateScaleNodes(opts) {
|
|
const { scaleSelection } = opts;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
scaleSelection.each((rect2, datum) => {
|
|
const { topLeftCornerRadius, topRightCornerRadius, bottomRightCornerRadius, bottomLeftCornerRadius } = datum;
|
|
rect2.setStyleProperties(datum.style, fillBBox);
|
|
rect2.setProperties(resetLinearGaugeSeriesResetRectFunction(rect2, datum));
|
|
rect2.topLeftCornerRadius = topLeftCornerRadius;
|
|
rect2.topRightCornerRadius = topRightCornerRadius;
|
|
rect2.bottomRightCornerRadius = bottomRightCornerRadius;
|
|
rect2.bottomLeftCornerRadius = bottomLeftCornerRadius;
|
|
rect2.setProperties(resetLinearGaugeSeriesResetRectFunction(rect2, datum));
|
|
});
|
|
}
|
|
updateTargetSelection(opts) {
|
|
return opts.targetSelection.update(opts.targetData, void 0, (target) => target.itemId);
|
|
}
|
|
updateTargetNodes(opts) {
|
|
const { targetSelection, isHighlight } = opts;
|
|
targetSelection.each((target, datum) => {
|
|
const { x, y, shape, size, rotation } = datum;
|
|
const style2 = this.getTargetStyle(isHighlight, datum);
|
|
target.setStyleProperties(style2);
|
|
target.size = size;
|
|
target.shape = shape === "line" ? lineMarker : shape;
|
|
target.translationX = x;
|
|
target.translationY = y;
|
|
target.rotation = rotation;
|
|
});
|
|
}
|
|
getTargetStyle(isHighlight, { datumIndex, style: style2 }) {
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex);
|
|
return mergeDefaults(highlightStyle, {
|
|
...style2,
|
|
opacity: 1
|
|
});
|
|
}
|
|
updateTargetLabelSelection(opts) {
|
|
return opts.targetLabelSelection.update(opts.targetData);
|
|
}
|
|
updateTargetLabelNodes(opts) {
|
|
const { targetLabelSelection } = opts;
|
|
targetLabelSelection.each((label, target) => {
|
|
const { x, y, text: text2 } = target;
|
|
const { offsetX, offsetY, fill, fontStyle, fontWeight: fontWeight2, fontSize, fontFamily, textAlign, textBaseline } = target.label;
|
|
label.visible = true;
|
|
label.x = x + offsetX;
|
|
label.y = y + offsetY;
|
|
label.text = text2;
|
|
label.fill = fill;
|
|
label.fontStyle = fontStyle;
|
|
label.fontWeight = fontWeight2;
|
|
label.fontSize = fontSize;
|
|
label.fontFamily = fontFamily;
|
|
label.textAlign = textAlign;
|
|
label.textBaseline = textBaseline;
|
|
});
|
|
}
|
|
updateTickSelection(opts) {
|
|
return opts.tickSelection.update(opts.tickData, void 0, (datum) => datum.tickId);
|
|
}
|
|
updateTickNodes(opts) {
|
|
const { gaugeRect, properties } = this;
|
|
const defaultScale = properties.defaultScale;
|
|
const {
|
|
enabled,
|
|
color: color2,
|
|
fontFamily = defaultScale.label.fontFamily,
|
|
fontSize = defaultScale.label.fontSize,
|
|
fontStyle,
|
|
fontWeight: fontWeight2 = defaultScale.label.fontWeight,
|
|
spacing
|
|
} = properties.scale.label;
|
|
let { placement } = properties.scale.label;
|
|
const rotation = toRadians(properties.scale.label.rotation ?? 0);
|
|
let textAlign;
|
|
let textBaseline;
|
|
let textX;
|
|
let textY;
|
|
if (this.horizontal) {
|
|
placement ?? (placement = "after");
|
|
textAlign = "center";
|
|
textBaseline = placement === "before" ? "bottom" : "top";
|
|
textY = this.originY + gaugeRect.y + (placement === "before" ? -spacing : gaugeRect.height + spacing);
|
|
} else {
|
|
placement ?? (placement = "before");
|
|
textAlign = placement === "before" ? "end" : "start";
|
|
textBaseline = "middle";
|
|
textX = this.originX + gaugeRect.x + (placement === "before" ? -spacing : gaugeRect.width + spacing);
|
|
}
|
|
opts.tickSelection.each((label, datum) => {
|
|
if (!enabled) {
|
|
label.visible = false;
|
|
return;
|
|
}
|
|
const x = textX ?? datum.translation;
|
|
const y = textY ?? datum.translation;
|
|
label.visible = true;
|
|
label.text = datum.tickLabel;
|
|
label.fill = color2;
|
|
label.fontFamily = fontFamily;
|
|
label.fontSize = fontSize;
|
|
label.fontStyle = fontStyle;
|
|
label.fontWeight = fontWeight2;
|
|
label.textBaseline = textBaseline;
|
|
label.textAlign = textAlign;
|
|
label.x = x;
|
|
label.y = y;
|
|
label.rotationCenterX = x;
|
|
label.rotationCenterY = y;
|
|
label.rotation = rotation;
|
|
});
|
|
}
|
|
updateLabelSelection(opts) {
|
|
return opts.labelSelection.update(opts.labelData, void 0, (_datum) => "primary");
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const { labelSelection } = opts;
|
|
const animationDisabled = this.ctx.animationManager.isSkipped();
|
|
labelSelection.each((label, datum) => {
|
|
label.fill = datum.fill;
|
|
label.fontStyle = datum.fontStyle;
|
|
label.fontWeight = datum.fontWeight;
|
|
label.fontFamily = datum.fontFamily;
|
|
});
|
|
if (animationDisabled || this.labelsHaveExplicitText()) {
|
|
this.formatLabelText();
|
|
}
|
|
}
|
|
labelsHaveExplicitText() {
|
|
for (const { datum } of this.labelSelection) {
|
|
if (datum.text == null) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
formatLabelText(datum) {
|
|
const { labelSelection, horizontal, scale: scale2, seriesRect, gaugeRect } = this;
|
|
const { x, y, width: width2, height: height2 } = gaugeRect;
|
|
const value = datum?.label ?? this.properties.value;
|
|
let barRect;
|
|
if (horizontal) {
|
|
const xValue = scale2.convert(value);
|
|
barRect = new BBox25(x, y, xValue - x, height2);
|
|
} else {
|
|
const yValue = scale2.convert(value);
|
|
barRect = new BBox25(x, yValue, width2, height2 - yValue);
|
|
}
|
|
const bboxes = { seriesRect, gaugeRect, barRect };
|
|
const { margin: padding2 } = this.properties;
|
|
formatLinearGaugeLabels(this, this.ctx, labelSelection, { padding: padding2, horizontal }, bboxes, datum);
|
|
}
|
|
resetAllAnimation() {
|
|
this.ctx.animationManager.stopByAnimationGroupId(this.id);
|
|
resetMotion4([this.datumSelection], resetLinearGaugeSeriesResetRectFunction);
|
|
this.formatLabelText();
|
|
}
|
|
resetAnimation(phase) {
|
|
if (phase === "initial") {
|
|
this.animationState.transition("reset");
|
|
} else if (phase === "ready") {
|
|
this.animationState.transition("skip");
|
|
}
|
|
}
|
|
animateLabelText(params = {}) {
|
|
const { animationManager } = this.ctx;
|
|
let labelFrom = 0;
|
|
let labelTo = 0;
|
|
this.labelSelection.each((label, datum) => {
|
|
label.opacity = 1;
|
|
labelFrom = label.previousDatum?.value ?? params.from ?? datum.value;
|
|
labelTo = datum.value;
|
|
});
|
|
if (this.labelsHaveExplicitText()) {
|
|
} else if (labelFrom === labelTo) {
|
|
this.formatLabelText({ label: labelTo });
|
|
} else {
|
|
const animationId = `${this.id}_labels`;
|
|
animationManager.animate({
|
|
id: animationId,
|
|
groupId: "label",
|
|
from: { label: labelFrom },
|
|
to: { label: labelTo },
|
|
phase: params.phase ?? "update",
|
|
ease: easeOut,
|
|
onUpdate: (datum) => this.formatLabelText(datum),
|
|
onStop: () => this.formatLabelText({ label: labelTo })
|
|
});
|
|
}
|
|
}
|
|
animateEmptyUpdateReady() {
|
|
const { animationManager } = this.ctx;
|
|
const { node } = prepareLinearGaugeSeriesAnimationFunctions(true, this.horizontal);
|
|
fromToMotion5(this.id, "node", animationManager, [this.datumSelection], node, (_sector, datum) => datum.itemId);
|
|
fromToMotion5(this.id, "label", animationManager, [this.labelSelection], fadeInFns, () => "primary");
|
|
this.animateLabelText({ from: 0, phase: "initial" });
|
|
}
|
|
animateWaitingUpdateReady() {
|
|
const { animationManager } = this.ctx;
|
|
const { node } = prepareLinearGaugeSeriesAnimationFunctions(false, this.horizontal);
|
|
fromToMotion5(this.id, "node", animationManager, [this.datumSelection], node, (_sector, datum) => datum.itemId);
|
|
this.animateLabelText();
|
|
}
|
|
animateReadyResize() {
|
|
this.resetAllAnimation();
|
|
}
|
|
getSeriesDomain() {
|
|
return { domain: [0, 1] };
|
|
}
|
|
dataCount() {
|
|
return Number.NaN;
|
|
}
|
|
getSeriesRange() {
|
|
return [Number.NaN, Number.NaN];
|
|
}
|
|
getLegendData() {
|
|
return [];
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, properties } = this;
|
|
const { tooltip } = properties;
|
|
if (datumIndex == null)
|
|
return;
|
|
let value;
|
|
let text2;
|
|
let fallbackLabel;
|
|
if (datumIndex.type === 0 /* Node */) {
|
|
value = properties.value;
|
|
text2 = properties.label.text;
|
|
fallbackLabel = this.ctx.localeManager.t("ariaLabelGaugeValue");
|
|
} else {
|
|
({ value, text: text2 } = properties.targets[datumIndex.index]);
|
|
fallbackLabel = this.ctx.localeManager.t("ariaLabelGaugeTarget");
|
|
}
|
|
if (value == null)
|
|
return;
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
data: [{ label: text2, fallbackLabel, value: this.formatLabel(value) }]
|
|
},
|
|
{ seriesId, title: void 0, datum: void 0, value }
|
|
);
|
|
}
|
|
pickNodeClosestDatum(point) {
|
|
return pickGaugeNearestDatum(this, point);
|
|
}
|
|
pickFocus(opts) {
|
|
return pickGaugeFocus(this, opts);
|
|
}
|
|
getCaptionText() {
|
|
return this.formatLabel(this.properties.value);
|
|
}
|
|
getCategoryValue(_datumIndex) {
|
|
return;
|
|
}
|
|
datumIndexForCategoryValue(_categoryValue) {
|
|
return;
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
LinearGaugeSeries.className = "LinearGaugeSeries";
|
|
LinearGaugeSeries.type = "linear-gauge";
|
|
|
|
// packages/ag-charts-enterprise/src/series/linear-gauge/linearGaugeModule.ts
|
|
var themeTemplate9 = {
|
|
minWidth: 200,
|
|
minHeight: 200,
|
|
tooltip: {
|
|
enabled: false
|
|
},
|
|
series: {
|
|
thickness: 50,
|
|
defaultColorRange: {
|
|
$if: [
|
|
{ $eq: [{ $palette: "type" }, "inbuilt"] },
|
|
{ $interpolate: [{ $palette: "secondDivergingColors" }, 5] },
|
|
SAFE_RANGE2_OPERATION
|
|
]
|
|
},
|
|
scale: {
|
|
// @ts-expect-error undocumented option
|
|
defaultFill: { $path: ["/1", { $palette: "fill" }, { $palette: "hierarchyColors" }] },
|
|
// TODO: mix backgroundColor and foregroundColor?
|
|
stroke: { $path: ["/2", SAFE_STROKE_FILL_OPERATION, { $palette: "hierarchyColors" }] },
|
|
// TODO: mix backgroundColor and foregroundColor?
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, 0] },
|
|
label: {
|
|
spacing: 11
|
|
}
|
|
},
|
|
bar: {
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, 0] }
|
|
},
|
|
segmentation: {
|
|
enabled: false,
|
|
interval: {},
|
|
spacing: 1
|
|
},
|
|
defaultTarget: {
|
|
fill: { $ref: "foregroundColor" },
|
|
stroke: { $ref: "foregroundColor" },
|
|
size: 10,
|
|
shape: "triangle",
|
|
placement: "after",
|
|
spacing: 5,
|
|
label: {
|
|
enabled: true,
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
color: { $ref: "textColor" },
|
|
spacing: 5
|
|
}
|
|
},
|
|
defaultScale: {
|
|
label: {
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
color: { $ref: "textColor" }
|
|
}
|
|
},
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
placement: "inside-start",
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $rem: 2 },
|
|
minimumFontSize: 12 /* SMALL */,
|
|
spacing: 18,
|
|
color: { $ref: "chartBackgroundColor" }
|
|
},
|
|
margin: 4,
|
|
tooltip: {
|
|
range: { $path: ["/tooltip/range", 10] }
|
|
}
|
|
}
|
|
};
|
|
var LinearGaugeModule = {
|
|
type: "series",
|
|
name: "linear-gauge",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
dependencies: [GaugePresetModule],
|
|
version: VERSION,
|
|
options: linearGaugeSeriesOptionsDef,
|
|
themeTemplate: themeTemplate9,
|
|
create: (ctx) => new LinearGaugeSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/axes/angle-number/linearAngleScale.ts
|
|
var { LinearScale: LinearScale4 } = module_support_exports;
|
|
var LinearAngleScale = class _LinearAngleScale extends LinearScale4 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.arcLength = 0;
|
|
}
|
|
static getNiceStepAndTickCount(ticks, domain) {
|
|
const [start2, stop] = domain;
|
|
let step = LinearScale4.getTickStep(start2, stop, ticks);
|
|
const maxTickCount = Number.isNaN(ticks.maxTickCount) ? Infinity : ticks.maxTickCount;
|
|
const expectedTickCount = Math.abs(stop - start2) / step;
|
|
let niceTickCount = Math.pow(2, Math.ceil(Math.log(expectedTickCount) / Math.log(2)));
|
|
if (niceTickCount > maxTickCount) {
|
|
niceTickCount /= 2;
|
|
step *= 2;
|
|
}
|
|
return { count: niceTickCount, step };
|
|
}
|
|
ticks(ticks, domain = this.domain) {
|
|
const { arcLength } = this;
|
|
if (!domain || domain.length < 2 || domain.some((d) => !Number.isFinite(d)) || arcLength <= 0) {
|
|
return { ticks: [], count: 0 };
|
|
}
|
|
const { nice, interval } = ticks;
|
|
const [d0, d1] = domain;
|
|
if (interval) {
|
|
const step2 = Math.abs(interval);
|
|
const availableRange = this.getPixelRange();
|
|
if (!isDenseInterval((d1 - d0) / step2, availableRange)) {
|
|
const result2 = range(d0, d1, step2);
|
|
return { ticks: result2.ticks, count: result2.count };
|
|
}
|
|
}
|
|
let step;
|
|
if (nice && this.hasNiceRange()) {
|
|
const linearNiceDomain = super.niceDomain(ticks, domain);
|
|
step = _LinearAngleScale.getNiceStepAndTickCount(ticks, linearNiceDomain).step;
|
|
} else {
|
|
step = LinearScale4.getTickStep(d0, d1, ticks);
|
|
}
|
|
const result = range(d0, d1, step);
|
|
return { ticks: result.ticks, count: result.count };
|
|
}
|
|
hasNiceRange() {
|
|
const sortedRange = this.range.slice().sort((a, b) => a - b);
|
|
const niceRanges = [Math.PI, 2 * Math.PI];
|
|
return niceRanges.some((r) => isNumberEqual(r, sortedRange[1] - sortedRange[0]));
|
|
}
|
|
niceDomain(ticks, domain = this.domain) {
|
|
const linearNiceDomain = super.niceDomain(ticks, domain);
|
|
if (!this.hasNiceRange())
|
|
return linearNiceDomain;
|
|
const reversed = linearNiceDomain[0] > linearNiceDomain[1];
|
|
const start2 = reversed ? linearNiceDomain[1] : linearNiceDomain[0];
|
|
const { step, count } = _LinearAngleScale.getNiceStepAndTickCount(ticks, linearNiceDomain);
|
|
const s = 1 / step;
|
|
const stop = step >= 1 ? Math.ceil(start2 / step + count) * step : Math.ceil((start2 + count * step) * s) / s;
|
|
return reversed ? [stop, start2] : [start2, stop];
|
|
}
|
|
getPixelRange() {
|
|
return this.arcLength;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-gauge/radialGaugeNeedle.ts
|
|
var { SvgPath: SvgPath2, Rotatable: Rotatable2, Translatable: Translatable2, Scalable: Scalable3 } = module_support_exports;
|
|
var RadialGaugeNeedle = class extends Rotatable2(Scalable3(Translatable2(SvgPath2))) {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.scalingCenterX = 0.5;
|
|
this.scalingCenterY = 0.5;
|
|
this.rotationCenterX = 0.5;
|
|
this.rotationCenterY = 0.5;
|
|
}
|
|
};
|
|
RadialGaugeNeedle.defaultPathData = "M0.50245 0.53745C0.481767 0.53745 0.465 0.520683 0.465 0.5C0.465 0.479317 0.481767 0.46255 0.50245 0.46255L1 0.500012L0.50245 0.53745Z";
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-gauge/radialGaugeSeriesProperties.ts
|
|
var { getColorStops: getColorStops3 } = module_support_exports;
|
|
var { makeSeriesTooltip: makeSeriesTooltip17, SeriesProperties: SeriesProperties6, AxisLabel: AxisLabel7, Label: Label15 } = module_support_exports;
|
|
var RadialGaugeDefaultTargetLabelProperties = class extends Label15 {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeDefaultTargetLabelProperties.prototype, "spacing", 2);
|
|
var RadialGaugeTargetProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.label = new RadialGaugeDefaultTargetLabelProperties();
|
|
}
|
|
getStyle() {
|
|
const {
|
|
fill = "black",
|
|
fillOpacity = 1,
|
|
stroke: stroke3 = "black",
|
|
strokeWidth = 0,
|
|
strokeOpacity = 1,
|
|
lineDash = [0],
|
|
lineDashOffset = 0
|
|
} = this;
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "text", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "value", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "shape", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "placement", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "size", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "rotation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeTargetProperties.prototype, "label", 2);
|
|
var RadialGaugeBarProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = true;
|
|
this.fills = new PropertiesArray(module_support_exports.StopProperties);
|
|
this.fillMode = "continuous";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "black";
|
|
this.strokeWidth = 0;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
}
|
|
getStyle(defaultColorRange, scale2) {
|
|
const {
|
|
enabled,
|
|
fill,
|
|
fills,
|
|
fillMode,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
} = this;
|
|
const barFill = enabled ? fill ?? createConicGradient(fills, fillMode, defaultColorRange, scale2) : "none";
|
|
return {
|
|
fill: barFill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeBarProperties.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeBarProperties.prototype, "fills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeBarProperties.prototype, "fillMode", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeBarProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeBarProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeBarProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeBarProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeBarProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeBarProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeBarProperties.prototype, "lineDashOffset", 2);
|
|
var RadialGaugeScaleIntervalProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.values = void 0;
|
|
this.step = void 0;
|
|
this.minSpacing = 0;
|
|
this.maxSpacing = 1e3;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleIntervalProperties.prototype, "values", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleIntervalProperties.prototype, "step", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleIntervalProperties.prototype, "minSpacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleIntervalProperties.prototype, "maxSpacing", 2);
|
|
var RadialGaugeScaleLabelProperties = class extends AxisLabel7 {
|
|
};
|
|
var RadialGaugeScaleProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.min = 0;
|
|
this.max = 1;
|
|
this.fills = new PropertiesArray(module_support_exports.StopProperties);
|
|
this.fillMode = "continuous";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "black";
|
|
this.strokeWidth = 0;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.defaultFill = "black";
|
|
this.interval = new RadialGaugeScaleIntervalProperties();
|
|
this.label = new RadialGaugeScaleLabelProperties();
|
|
}
|
|
getStyle(barEnabled, defaultColorRange, scale2) {
|
|
const {
|
|
fill,
|
|
fills,
|
|
defaultFill,
|
|
fillMode,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
} = this;
|
|
const scaleFill = fill ?? (barEnabled && fills.length === 0 ? defaultFill : void 0) ?? createConicGradient(fills, fillMode, defaultColorRange, scale2);
|
|
return {
|
|
fill: scaleFill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "min", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "max", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "fills", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "fillMode", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "defaultFill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "interval", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeScaleProperties.prototype, "label", 2);
|
|
var RadialGaugeNeedleProperties = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.enabled = true;
|
|
this.spacing = 0;
|
|
this.fill = "black";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "black";
|
|
this.strokeWidth = 0;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeNeedleProperties.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeNeedleProperties.prototype, "radiusRatio", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeNeedleProperties.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeNeedleProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeNeedleProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeNeedleProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeNeedleProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeNeedleProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeNeedleProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeNeedleProperties.prototype, "lineDashOffset", 2);
|
|
var RadialGaugeLabelProperties = class extends AutoSizedLabel {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeLabelProperties.prototype, "text", 2);
|
|
var RadialGaugeSecondaryLabelProperties = class extends AutoSizedSecondaryLabel {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSecondaryLabelProperties.prototype, "text", 2);
|
|
var RadialGaugeSeriesProperties = class extends SeriesProperties6 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.startAngle = 0;
|
|
this.endAngle = 0;
|
|
this.segmentation = new GaugeSegmentationProperties();
|
|
this.defaultColorRange = [];
|
|
this.targets = new PropertiesArray(RadialGaugeTargetProperties);
|
|
this.defaultTarget = new RadialGaugeTargetProperties();
|
|
this.outerRadiusRatio = 1;
|
|
this.innerRadiusRatio = 1;
|
|
this.cornerRadius = 0;
|
|
this.cornerMode = "container";
|
|
this.spacing = 0;
|
|
this.scale = new RadialGaugeScaleProperties();
|
|
this.bar = new RadialGaugeBarProperties();
|
|
this.needle = new RadialGaugeNeedleProperties();
|
|
this.label = new RadialGaugeLabelProperties();
|
|
this.secondaryLabel = new RadialGaugeSecondaryLabelProperties();
|
|
this.tooltip = makeSeriesTooltip17();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "value", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "startAngle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "endAngle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "segmentation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "defaultColorRange", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "targets", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "defaultTarget", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "outerRadiusRatio", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "innerRadiusRatio", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "outerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "innerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "cornerMode", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "spacing", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "scale", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "bar", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "needle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "secondaryLabel", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialGaugeSeriesProperties.prototype, "tooltip", 2);
|
|
function createConicGradient(fills, fillMode, defaultColorRange, scale2) {
|
|
const { domain, range: range3 } = scale2;
|
|
const [startAngle, endAngle] = range3;
|
|
const conicAngle = normalizeAngle360((startAngle + endAngle) / 2 + Math.PI);
|
|
const sweepAngle = normalizeAngle360Inclusive(endAngle - startAngle);
|
|
const colorStops = getColorStops3(fills, defaultColorRange, domain, fillMode).map(
|
|
({ color: color2, stop }) => {
|
|
stop = Math.min(Math.max(stop, 0), 1);
|
|
const angle2 = startAngle + sweepAngle * stop;
|
|
stop = (angle2 - conicAngle) / (2 * Math.PI);
|
|
stop = (stop % 1 + 1) % 1;
|
|
return { stop, color: color2 };
|
|
}
|
|
);
|
|
return {
|
|
type: "gradient",
|
|
gradient: "conic",
|
|
colorSpace: "oklch",
|
|
colorStops,
|
|
bounds: "series",
|
|
rotation: toDegrees(conicAngle) + 90
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-gauge/radialGaugeUtil.ts
|
|
var { SectorBox: SectorBox2 } = module_support_exports;
|
|
function computeClipSector(datum) {
|
|
const { startAngle, endAngle, clipStartAngle, clipEndAngle, innerRadius, outerRadius } = datum;
|
|
if (clipStartAngle == null || clipEndAngle == null)
|
|
return;
|
|
return new SectorBox2(
|
|
Math.max(clipStartAngle, startAngle),
|
|
Math.min(clipEndAngle, endAngle),
|
|
innerRadius,
|
|
outerRadius
|
|
);
|
|
}
|
|
function clipSectorVisibility(startAngle, endAngle, clipSector) {
|
|
return Math.max(startAngle, clipSector.startAngle) <= Math.min(endAngle, clipSector.endAngle);
|
|
}
|
|
function hasClipSector(datum) {
|
|
return datum.clipStartAngle != null && datum.clipEndAngle != null;
|
|
}
|
|
function datumClipSector(datum, zero) {
|
|
const { clipStartAngle, clipEndAngle, innerRadius, outerRadius } = datum;
|
|
return new SectorBox2(clipStartAngle, zero ? clipStartAngle : clipEndAngle, innerRadius, outerRadius);
|
|
}
|
|
function prepareRadialGaugeSeriesAnimationFunctions(initialLoad, initialStartAngle) {
|
|
const phase = initialLoad ? "initial" : "update";
|
|
const node = {
|
|
fromFn(sect, datum) {
|
|
const previousDatum = sect.previousDatum;
|
|
let { startAngle, endAngle } = previousDatum ?? datum;
|
|
const previousClipSector = previousDatum != null && hasClipSector(previousDatum) ? datumClipSector(previousDatum, initialLoad) : void 0;
|
|
const nextClipSector = hasClipSector(datum) ? datumClipSector(datum, initialLoad) : void 0;
|
|
let clipSector;
|
|
if (previousClipSector != null && nextClipSector != null) {
|
|
clipSector = previousClipSector;
|
|
} else if (previousClipSector == null && nextClipSector != null) {
|
|
clipSector = nextClipSector;
|
|
startAngle = datum.startAngle;
|
|
endAngle = datum.endAngle;
|
|
} else if (previousClipSector != null && nextClipSector == null) {
|
|
clipSector = void 0;
|
|
startAngle = datum.startAngle;
|
|
endAngle = datum.endAngle;
|
|
} else if (initialLoad) {
|
|
endAngle = startAngle;
|
|
}
|
|
return { startAngle, endAngle, clipSector, phase };
|
|
},
|
|
toFn(_sect, datum) {
|
|
const { startAngle, endAngle } = datum;
|
|
let clipSector;
|
|
if (hasClipSector(datum)) {
|
|
clipSector = datumClipSector(datum, false);
|
|
}
|
|
return { startAngle, endAngle, clipSector };
|
|
},
|
|
applyFn(sect, params) {
|
|
const { startAngle, endAngle } = params;
|
|
let { clipSector } = params;
|
|
if (clipSector != null) {
|
|
clipSector = new SectorBox2(
|
|
Math.max(startAngle, clipSector.startAngle),
|
|
Math.min(endAngle, clipSector.endAngle),
|
|
clipSector.innerRadius,
|
|
clipSector.outerRadius
|
|
);
|
|
}
|
|
const visible = clipSector == null || clipSectorVisibility(startAngle, endAngle, clipSector);
|
|
sect.startAngle = startAngle;
|
|
sect.endAngle = endAngle;
|
|
sect.clipSector = clipSector;
|
|
sect.visible = visible;
|
|
}
|
|
};
|
|
const needle = {
|
|
fromFn(needleNode) {
|
|
let { angle: rotation } = needleNode.previousDatum ?? needleNode.datum;
|
|
if (initialLoad) {
|
|
rotation = initialStartAngle;
|
|
}
|
|
return { rotation, phase };
|
|
},
|
|
toFn(_needleNode, datum) {
|
|
const { angle: rotation } = datum;
|
|
return { rotation };
|
|
}
|
|
};
|
|
return { node, needle };
|
|
}
|
|
function resetRadialGaugeSeriesResetSectorFunction(_node, datum) {
|
|
const { startAngle, endAngle } = datum;
|
|
const clipSector = computeClipSector(datum);
|
|
const visible = clipSector == null || clipSectorVisibility(startAngle, endAngle, clipSector);
|
|
return { startAngle, endAngle, clipSector, visible };
|
|
}
|
|
function resetRadialGaugeSeriesResetNeedleFunction(_node, datum) {
|
|
const { angle: angle2 } = datum;
|
|
return { rotation: angle2 };
|
|
}
|
|
var verticalAlignFactors4 = {
|
|
top: 0,
|
|
middle: 0.5,
|
|
bottom: 1
|
|
};
|
|
function formatRadialGaugeLabels(series, ctx, selection, opts, innerRadius, datumOverrides) {
|
|
const { padding: padding2, textAlign, verticalAlign } = opts;
|
|
let labelDatum;
|
|
let secondaryLabelDatum;
|
|
selection.each((_node, datum) => {
|
|
if (datum.label === "primary" /* Primary */) {
|
|
labelDatum = datum;
|
|
} else if (datum.label === "secondary" /* Secondary */) {
|
|
secondaryLabelDatum = datum;
|
|
}
|
|
});
|
|
if (labelDatum == null)
|
|
return;
|
|
const labelText = getLabelText(series.id, ctx, labelDatum, datumOverrides?.label);
|
|
if (labelText == null)
|
|
return;
|
|
const secondaryLabelText = secondaryLabelDatum == null ? void 0 : getLabelText(series.id, ctx, secondaryLabelDatum, datumOverrides?.secondaryLabel);
|
|
const params = { padding: padding2 };
|
|
const horizontalFactor = textAlign === "center" ? 2 : 1;
|
|
const verticalFactor = verticalAlign === "middle" ? 2 : 1;
|
|
const sizeFittingHeight = (height3) => ({
|
|
width: Math.sqrt(Math.max(innerRadius ** 2 - (height3 / verticalFactor) ** 2, 0)) * horizontalFactor,
|
|
height: Math.min(height3, verticalFactor * innerRadius),
|
|
meta: null
|
|
});
|
|
let labelLayout;
|
|
let secondaryLabelLayout;
|
|
let height2;
|
|
if (secondaryLabelDatum != null && secondaryLabelText != null) {
|
|
const layout = formatStackedLabels(
|
|
toPlainText(labelText),
|
|
labelDatum,
|
|
toPlainText(secondaryLabelText),
|
|
secondaryLabelDatum,
|
|
params,
|
|
sizeFittingHeight
|
|
);
|
|
labelLayout = layout?.label;
|
|
secondaryLabelLayout = layout?.secondaryLabel;
|
|
height2 = layout?.height ?? 0;
|
|
} else {
|
|
const layout = formatSingleLabel(toPlainText(labelText), labelDatum, params, sizeFittingHeight);
|
|
labelLayout = layout?.[0];
|
|
secondaryLabelLayout = void 0;
|
|
height2 = layout?.[0].height ?? 0;
|
|
}
|
|
const rectYOffset = height2 * verticalAlignFactors4[verticalAlign];
|
|
selection.each((label, datum) => {
|
|
let layout;
|
|
if (datum.label === "primary" /* Primary */) {
|
|
layout = labelLayout;
|
|
} else if (datum.label === "secondary" /* Secondary */) {
|
|
layout = secondaryLabelLayout;
|
|
}
|
|
if (layout == null) {
|
|
label.visible = false;
|
|
return;
|
|
}
|
|
label.visible = true;
|
|
label.text = layout.text;
|
|
label.fontSize = layout.fontSize;
|
|
label.lineHeight = layout.lineHeight;
|
|
label.textAlign = textAlign;
|
|
label.textBaseline = "middle";
|
|
const rectOriginInLabelRect = datum.label === "primary" /* Primary */ ? layout.height / 2 : height2 - layout.height / 2;
|
|
label.y = datum.centerY + rectOriginInLabelRect - rectYOffset;
|
|
label.x = datum.centerX;
|
|
});
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-gauge/radialGaugeSeries.ts
|
|
var {
|
|
fromToMotion: fromToMotion6,
|
|
resetMotion: resetMotion5,
|
|
SeriesNodePickMode: SeriesNodePickMode12,
|
|
createDatumId: createDatumId16,
|
|
sectorBox: sectorBox2,
|
|
BBox: BBox26,
|
|
Group: Group15,
|
|
PointerEvents: PointerEvents9,
|
|
Selection: Selection10,
|
|
Sector: Sector4,
|
|
SectorBox: SectorBox3,
|
|
Transformable: Transformable5,
|
|
TransformableText: TransformableText5,
|
|
Text: Text7,
|
|
Marker: Marker5
|
|
} = module_support_exports;
|
|
var targetPlacementRotation = {
|
|
inside: 90,
|
|
middle: 0,
|
|
outside: -90
|
|
};
|
|
var outsideLabelPlacements = [
|
|
{ textAlign: "left", textBaseline: "top" },
|
|
{ textAlign: "right", textBaseline: "top" },
|
|
{ textAlign: "right", textBaseline: "bottom" },
|
|
{ textAlign: "left", textBaseline: "bottom" }
|
|
];
|
|
var insideLabelPlacements = [
|
|
{ textAlign: "right", textBaseline: "bottom" },
|
|
{ textAlign: "left", textBaseline: "bottom" },
|
|
{ textAlign: "left", textBaseline: "top" },
|
|
{ textAlign: "right", textBaseline: "top" }
|
|
];
|
|
var RadialGaugeSeries = class extends module_support_exports.Series {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
pickModes: [SeriesNodePickMode12.EXACT_SHAPE_MATCH, SeriesNodePickMode12.NEAREST_NODE]
|
|
});
|
|
this.centerX = 0;
|
|
this.centerY = 0;
|
|
this.radius = 0;
|
|
this.textAlign = "center";
|
|
this.verticalAlign = "middle";
|
|
this.properties = new RadialGaugeSeriesProperties();
|
|
this.scale = new LinearAngleScale();
|
|
this.scaleGroup = this.contentGroup.appendChild(new Group15({ name: "scaleGroup" }));
|
|
this.itemGroup = this.contentGroup.appendChild(new Group15({ name: "itemGroup" }));
|
|
this.itemNeedleGroup = this.contentGroup.appendChild(new Group15({ name: "itemNeedleGroup" }));
|
|
this.itemTargetGroup = this.contentGroup.appendChild(new Group15({ name: "itemTargetGroup" }));
|
|
this.itemTargetLabelGroup = this.contentGroup.appendChild(new Group15({ name: "itemTargetLabelGroup" }));
|
|
this.itemLabelGroup = this.contentGroup.appendChild(new Group15({ name: "itemLabelGroup" }));
|
|
this.highlightTargetGroup = this.highlightGroup.appendChild(
|
|
new Group15({ name: "itemTargetLabelGroup" })
|
|
);
|
|
this.tickGroup = this.contentGroup.appendChild(new Group15({ name: "tickGroup" }));
|
|
this.scaleSelection = Selection10.select(
|
|
this.scaleGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.datumSelection = Selection10.select(
|
|
this.itemGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.needleSelection = Selection10.select(
|
|
this.itemNeedleGroup,
|
|
RadialGaugeNeedle
|
|
);
|
|
this.targetSelection = Selection10.select(
|
|
this.itemTargetGroup,
|
|
() => this.markerFactory()
|
|
);
|
|
this.targetLabelSelection = Selection10.select(this.itemTargetLabelGroup, Text7);
|
|
this.labelSelection = Selection10.select(
|
|
this.itemLabelGroup,
|
|
Text7
|
|
);
|
|
this.highlightTargetSelection = Selection10.select(this.highlightTargetGroup, () => this.markerFactory());
|
|
this.tickSelection = Selection10.select(this.tickGroup, module_support_exports.TransformableText);
|
|
this.datumUnion = new DatumUnion();
|
|
this.animationState = new StateMachine("empty", {
|
|
empty: {
|
|
update: {
|
|
target: "ready",
|
|
action: () => this.animateEmptyUpdateReady()
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
ready: {
|
|
updateData: "waiting",
|
|
clear: "clearing",
|
|
resize: () => this.animateReadyResize(),
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
waiting: {
|
|
update: {
|
|
target: "ready",
|
|
action: () => this.animateWaitingUpdateReady()
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
clearing: {
|
|
update: {
|
|
target: "empty"
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
}
|
|
});
|
|
this.scaleGroup.pointerEvents = PointerEvents9.None;
|
|
this.tickGroup.pointerEvents = PointerEvents9.None;
|
|
this.itemNeedleGroup.pointerEvents = PointerEvents9.None;
|
|
this.itemLabelGroup.pointerEvents = PointerEvents9.None;
|
|
}
|
|
get hasData() {
|
|
return this.properties.value != null;
|
|
}
|
|
nodeFactory() {
|
|
return new Sector4();
|
|
}
|
|
markerFactory() {
|
|
const marker = new Marker5();
|
|
marker.size = 1;
|
|
return marker;
|
|
}
|
|
processData() {
|
|
this.nodeDataRefresh = true;
|
|
this.animationState.transition("updateData");
|
|
}
|
|
formatLabel(value) {
|
|
const { min, max } = this.properties.scale;
|
|
return formatLabel(value, { min, max });
|
|
}
|
|
layoutScale() {
|
|
const { scale: scale2, properties } = this;
|
|
const { seriesRectWidth, seriesRectHeight } = this.nodeDataDependencies;
|
|
const { scale: scaleProps, outerRadius } = this.properties;
|
|
const { min, max, label, interval } = scaleProps;
|
|
const startAngle = toRadians(properties.startAngle - 90);
|
|
const endAngle = toRadians(properties.endAngle - 90);
|
|
const sweepAngle = normalizeAngle360Inclusive(endAngle - startAngle);
|
|
const largerThanHalf = sweepAngle > Math.PI;
|
|
const containsTop = largerThanHalf || isBetweenAngles(1.5 * Math.PI, startAngle, endAngle);
|
|
const containsRight = largerThanHalf || isBetweenAngles(0 * Math.PI, startAngle, endAngle);
|
|
const containsBottom = largerThanHalf || isBetweenAngles(0.5 * Math.PI, startAngle, endAngle);
|
|
const containsLeft = largerThanHalf || isBetweenAngles(1 * Math.PI, startAngle, endAngle);
|
|
let textAlign;
|
|
if (containsLeft && !containsRight) {
|
|
textAlign = "right";
|
|
} else if (!containsLeft && containsRight) {
|
|
textAlign = "left";
|
|
} else {
|
|
textAlign = "center";
|
|
}
|
|
let verticalAlign;
|
|
if (containsTop && !containsBottom) {
|
|
verticalAlign = "bottom";
|
|
} else if (!containsTop && containsBottom) {
|
|
verticalAlign = "top";
|
|
} else {
|
|
verticalAlign = "middle";
|
|
}
|
|
const unitBox = sectorBox2({
|
|
startAngle,
|
|
endAngle,
|
|
innerRadius: 0,
|
|
outerRadius: 0.5
|
|
});
|
|
const centerXOffset = -(unitBox.x + unitBox.width / 2) * 2;
|
|
const centerYOffset = -(unitBox.y + unitBox.height / 2) * 2;
|
|
const unitBoxSize = Math.min(seriesRectWidth / unitBox.width, seriesRectHeight / unitBox.height);
|
|
scale2.domain = [min, max];
|
|
scale2.range = [startAngle, endAngle];
|
|
scale2.arcLength = unitBoxSize / 2;
|
|
const { maxSpacing, minSpacing } = interval;
|
|
const { arcLength } = scale2;
|
|
const minTickCount = maxSpacing ? Math.floor(arcLength / maxSpacing) : 1;
|
|
const maxTickCount = minSpacing ? Math.floor(arcLength / minSpacing) : Infinity;
|
|
const preferredTickCount = Math.floor(4 / Math.PI * Math.abs(scale2.range[0] - scale2.range[1]));
|
|
const tickCount = Math.max(minTickCount, Math.min(maxTickCount, preferredTickCount));
|
|
const ticks = interval.values ?? scale2.ticks({
|
|
nice: [false, false],
|
|
interval: interval.step,
|
|
minTickCount,
|
|
maxTickCount,
|
|
tickCount
|
|
})?.ticks ?? [];
|
|
const tickFormatter = tickFormat(ticks, typeof label.format === "string" ? label.format : void 0);
|
|
const tickData = [];
|
|
for (const [index, value] of ticks.entries()) {
|
|
let text2;
|
|
if (label.formatter) {
|
|
text2 = formatWithContext(this.ctx, label.formatter, {
|
|
value,
|
|
index,
|
|
domain: scale2.domain,
|
|
boundSeries: void 0
|
|
});
|
|
}
|
|
text2 ?? (text2 = tickFormatter?.(value));
|
|
if (text2 == null)
|
|
continue;
|
|
tickData.push({ index, value, text: text2 });
|
|
}
|
|
const baseRadius = 0.5 * unitBoxSize;
|
|
const labelInset = label.enabled && outerRadius == null && tickData.length > 0 ? this.getTickLabelInset({
|
|
tickData,
|
|
radius: baseRadius,
|
|
centerXOffset,
|
|
centerYOffset,
|
|
seriesRectWidth,
|
|
seriesRectHeight,
|
|
spacing: label.spacing,
|
|
rotation: toRadians(label.rotation ?? 0)
|
|
}) : 0;
|
|
const radiusBounds = Math.max(
|
|
baseRadius - labelInset,
|
|
// seriesRect may have negative size
|
|
0
|
|
);
|
|
const radius = outerRadius ?? radiusBounds;
|
|
this.centerX = seriesRectWidth / 2 + centerXOffset * radius;
|
|
this.centerY = seriesRectHeight / 2 + centerYOffset * radius;
|
|
this.radius = radius;
|
|
this.textAlign = textAlign;
|
|
this.verticalAlign = verticalAlign;
|
|
return tickData;
|
|
}
|
|
getShapeFillBBox() {
|
|
const { centerX, centerY, radius } = this;
|
|
const bbox = new BBox26(centerX - radius, centerY - radius, 2 * radius, 2 * radius);
|
|
return {
|
|
series: bbox,
|
|
axis: bbox
|
|
};
|
|
}
|
|
getTargets() {
|
|
const { properties } = this;
|
|
const defaultTarget = properties.defaultTarget;
|
|
return properties.targets.map((target) => {
|
|
const {
|
|
text: text2 = defaultTarget.text,
|
|
value = defaultTarget.value ?? 0,
|
|
shape = defaultTarget.shape ?? "triangle",
|
|
rotation = defaultTarget.rotation ?? 0,
|
|
placement = defaultTarget.placement ?? "middle",
|
|
spacing = defaultTarget.spacing ?? 0,
|
|
size = defaultTarget.size ?? 0
|
|
} = target;
|
|
const {
|
|
enabled: labelEnabled = defaultTarget.label.enabled,
|
|
color: labelColor = defaultTarget.label.color ?? "black",
|
|
fontStyle: labelFontStyle = defaultTarget.label.fontStyle ?? "normal",
|
|
fontWeight: labelFontWeight = defaultTarget.label.fontWeight ?? "normal",
|
|
fontSize: labelFontSize = defaultTarget.label.fontSize,
|
|
fontFamily: labelFontFamily = defaultTarget.label.fontFamily,
|
|
spacing: labelSpacing = defaultTarget.label.spacing ?? 0
|
|
} = target.label;
|
|
return {
|
|
text: text2,
|
|
value,
|
|
shape,
|
|
placement,
|
|
spacing,
|
|
size,
|
|
rotation,
|
|
label: {
|
|
enabled: labelEnabled,
|
|
color: labelColor,
|
|
fontStyle: labelFontStyle,
|
|
fontWeight: labelFontWeight,
|
|
fontSize: labelFontSize,
|
|
fontFamily: labelFontFamily,
|
|
spacing: labelSpacing
|
|
},
|
|
style: target.getStyle()
|
|
};
|
|
});
|
|
}
|
|
getTargetRadius(target) {
|
|
const { radius, properties } = this;
|
|
const { innerRadiusRatio, outerRadiusRatio } = properties;
|
|
const { placement, spacing, size } = target;
|
|
const outerRadius = radius * outerRadiusRatio;
|
|
const innerRadius = radius * innerRadiusRatio;
|
|
switch (placement) {
|
|
case "inside":
|
|
return Math.max(innerRadius - spacing - size / 2, 0);
|
|
case "outside":
|
|
return outerRadius + spacing + size / 2;
|
|
default:
|
|
return (innerRadius + outerRadius) / 2;
|
|
}
|
|
}
|
|
getTargetLabel(target) {
|
|
const { scale: scale2 } = this;
|
|
const { value, size, placement, label } = target;
|
|
const { spacing, color: fill, fontStyle, fontWeight: fontWeight2, fontSize, fontFamily } = label;
|
|
const angle2 = scale2.convert(value);
|
|
const quadrant = Math.trunc(normalizeAngle360(angle2) / (Math.PI / 2));
|
|
const offset = size / 2 + spacing;
|
|
let textAlign;
|
|
let textBaseline;
|
|
let offsetX;
|
|
let offsetY;
|
|
switch (placement) {
|
|
case "outside":
|
|
({ textAlign, textBaseline } = outsideLabelPlacements[quadrant]);
|
|
offsetX = offset * Math.cos(angle2);
|
|
offsetY = offset * Math.sin(angle2);
|
|
break;
|
|
case "inside":
|
|
({ textAlign, textBaseline } = insideLabelPlacements[quadrant]);
|
|
offsetX = -offset * Math.cos(angle2);
|
|
offsetY = -offset * Math.sin(angle2);
|
|
break;
|
|
default:
|
|
textAlign = "center";
|
|
textBaseline = "bottom";
|
|
offsetX = 0;
|
|
offsetY = -offset;
|
|
break;
|
|
}
|
|
return {
|
|
offsetX,
|
|
offsetY,
|
|
fill,
|
|
textAlign,
|
|
textBaseline,
|
|
fontStyle,
|
|
fontWeight: fontWeight2,
|
|
fontSize,
|
|
fontFamily,
|
|
lineHeight: void 0
|
|
};
|
|
}
|
|
createNodeData() {
|
|
const tickData = this.layoutScale();
|
|
const { id: seriesId, scale: scale2, properties, radius, centerX, centerY } = this;
|
|
const {
|
|
value,
|
|
innerRadiusRatio,
|
|
outerRadiusRatio,
|
|
segmentation,
|
|
cornerRadius,
|
|
cornerMode,
|
|
needle,
|
|
bar,
|
|
scale: scaleProps,
|
|
label,
|
|
secondaryLabel
|
|
} = properties;
|
|
const {
|
|
outerRadius = radius * outerRadiusRatio,
|
|
innerRadius = radius * innerRadiusRatio,
|
|
defaultColorRange
|
|
} = properties;
|
|
const targets = this.getTargets();
|
|
const nodeData = [];
|
|
const targetData = [];
|
|
const needleData = [];
|
|
const labelData = [];
|
|
const scaleData = [];
|
|
const cornersOnAllItems = cornerMode === "item";
|
|
const containerStartAngle = scale2.convert(scale2.domain[0]);
|
|
const containerEndAngle = scale2.convert(value);
|
|
const maxTicks = Math.ceil(normalizeAngle360Inclusive(containerEndAngle - containerStartAngle) * radius);
|
|
let segments = segmentation.enabled ? segmentation.interval.getSegments(scale2, maxTicks) : void 0;
|
|
const barStyle = bar.getStyle(defaultColorRange, scale2);
|
|
const scaleStyle = scaleProps.getStyle(bar.enabled, defaultColorRange, scale2);
|
|
if (segments == null && cornersOnAllItems) {
|
|
const segmentStart = Math.min(...scale2.domain);
|
|
const segmentEnd = Math.max(...scale2.domain);
|
|
const datum = { value, segmentStart, segmentEnd };
|
|
const appliedCornerRadius = Math.min(cornerRadius, (outerRadius - innerRadius) / 2);
|
|
const angleInset = appliedCornerRadius / ((innerRadius + outerRadius) / 2);
|
|
nodeData.push({
|
|
series: this,
|
|
itemId: `value`,
|
|
datum,
|
|
datumIndex: { type: 0 /* Node */ },
|
|
type: 0 /* Node */,
|
|
centerX,
|
|
centerY,
|
|
outerRadius,
|
|
innerRadius,
|
|
startAngle: containerStartAngle - angleInset,
|
|
endAngle: containerEndAngle + angleInset,
|
|
clipStartAngle: void 0,
|
|
clipEndAngle: void 0,
|
|
startCornerRadius: cornerRadius,
|
|
endCornerRadius: cornerRadius,
|
|
style: barStyle
|
|
});
|
|
scaleData.push({
|
|
series: this,
|
|
itemId: `scale`,
|
|
datum,
|
|
datumIndex: { type: 0 /* Node */ },
|
|
type: 0 /* Node */,
|
|
centerX,
|
|
centerY,
|
|
outerRadius,
|
|
innerRadius,
|
|
startAngle: scale2.range[0] - angleInset,
|
|
endAngle: scale2.range[1] + angleInset,
|
|
clipStartAngle: void 0,
|
|
clipEndAngle: void 0,
|
|
startCornerRadius: cornerRadius,
|
|
endCornerRadius: cornerRadius,
|
|
style: scaleStyle
|
|
});
|
|
} else {
|
|
segments ?? (segments = scale2.domain);
|
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
const segmentStart = segments[i];
|
|
const segmentEnd = segments[i + 1];
|
|
const datum = { value, segmentStart, segmentEnd };
|
|
const isStart = i === 0;
|
|
const isEnd = i === segments.length - 2;
|
|
const itemStartAngle = scale2.convert(segmentStart);
|
|
const itemEndAngle = scale2.convert(segmentEnd);
|
|
nodeData.push({
|
|
series: this,
|
|
itemId: `value-${i}`,
|
|
datum,
|
|
datumIndex: { type: 0 /* Node */ },
|
|
type: 0 /* Node */,
|
|
centerX,
|
|
centerY,
|
|
outerRadius,
|
|
innerRadius,
|
|
startAngle: itemStartAngle,
|
|
endAngle: itemEndAngle,
|
|
clipStartAngle: containerStartAngle,
|
|
clipEndAngle: containerEndAngle,
|
|
startCornerRadius: cornersOnAllItems || isStart ? cornerRadius : 0,
|
|
endCornerRadius: cornersOnAllItems || isEnd ? cornerRadius : 0,
|
|
style: barStyle
|
|
});
|
|
scaleData.push({
|
|
series: this,
|
|
itemId: `scale-${i}`,
|
|
datum,
|
|
datumIndex: { type: 0 /* Node */ },
|
|
type: 0 /* Node */,
|
|
centerX,
|
|
centerY,
|
|
outerRadius,
|
|
innerRadius,
|
|
startAngle: itemStartAngle,
|
|
endAngle: itemEndAngle,
|
|
clipStartAngle: void 0,
|
|
clipEndAngle: void 0,
|
|
startCornerRadius: cornersOnAllItems || isStart ? cornerRadius : 0,
|
|
endCornerRadius: cornersOnAllItems || isEnd ? cornerRadius : 0,
|
|
style: scaleStyle
|
|
});
|
|
}
|
|
}
|
|
if (!needle.enabled && label.enabled) {
|
|
const {
|
|
text: text2,
|
|
color: fill,
|
|
fontSize,
|
|
minimumFontSize,
|
|
fontStyle,
|
|
fontWeight: fontWeight2,
|
|
fontFamily,
|
|
lineHeight,
|
|
formatter: formatter2 = (params) => this.formatLabel(params.value)
|
|
} = label;
|
|
labelData.push({
|
|
label: "primary" /* Primary */,
|
|
centerX,
|
|
centerY,
|
|
text: text2,
|
|
value,
|
|
fill,
|
|
fontSize,
|
|
minimumFontSize,
|
|
fontStyle,
|
|
fontWeight: fontWeight2,
|
|
fontFamily,
|
|
lineHeight,
|
|
formatter: formatter2
|
|
});
|
|
}
|
|
if (!needle.enabled && secondaryLabel.enabled) {
|
|
const {
|
|
text: text2,
|
|
color: fill,
|
|
fontSize,
|
|
minimumFontSize,
|
|
fontStyle,
|
|
fontWeight: fontWeight2,
|
|
fontFamily,
|
|
lineHeight,
|
|
formatter: formatter2
|
|
} = secondaryLabel;
|
|
labelData.push({
|
|
label: "secondary" /* Secondary */,
|
|
centerX,
|
|
centerY,
|
|
text: text2,
|
|
value,
|
|
fill,
|
|
fontSize,
|
|
minimumFontSize,
|
|
fontStyle,
|
|
fontWeight: fontWeight2,
|
|
fontFamily,
|
|
lineHeight,
|
|
formatter: formatter2
|
|
});
|
|
}
|
|
if (needle.enabled) {
|
|
let needleRadius = needle.radiusRatio == null ? innerRadius : radius * needle.radiusRatio;
|
|
needleRadius = Math.max(needleRadius - needle.spacing, 0);
|
|
const needleAngle = scale2.convert(value);
|
|
needleData.push({
|
|
centerX,
|
|
centerY,
|
|
radius: needleRadius,
|
|
angle: needleAngle,
|
|
series: this
|
|
});
|
|
}
|
|
for (let i = 0; i < targets.length; i += 1) {
|
|
const target = targets[i];
|
|
const { value: targetValue, text: text2, size, shape, style: style2 } = target;
|
|
if (targetValue < Math.min(...scale2.domain) || targetValue > Math.max(...scale2.domain)) {
|
|
continue;
|
|
}
|
|
const targetRadius = this.getTargetRadius(target);
|
|
const targetAngle = scale2.convert(targetValue);
|
|
const targetRotation = toRadians(target.rotation + targetPlacementRotation[target.placement]);
|
|
targetData.push({
|
|
series: this,
|
|
itemId: `target-${i}`,
|
|
midPoint: {
|
|
x: targetRadius * Math.cos(targetAngle) + centerX,
|
|
y: targetRadius * Math.sin(targetAngle) + centerY
|
|
},
|
|
datum: { value: targetValue },
|
|
datumIndex: { type: 1 /* Target */, index: i },
|
|
type: 1 /* Target */,
|
|
value: targetValue,
|
|
text: text2,
|
|
centerX,
|
|
centerY,
|
|
shape,
|
|
radius: targetRadius,
|
|
angle: targetAngle,
|
|
rotation: targetRotation,
|
|
size,
|
|
label: this.getTargetLabel(target),
|
|
style: style2
|
|
});
|
|
}
|
|
return {
|
|
itemId: seriesId,
|
|
nodeData,
|
|
needleData,
|
|
targetData,
|
|
labelData,
|
|
scaleData,
|
|
tickData
|
|
};
|
|
}
|
|
findNodeDatum(itemId) {
|
|
return findGaugeNodeDatum(this, itemId);
|
|
}
|
|
updateSelections(resize) {
|
|
if (this.nodeDataRefresh || resize) {
|
|
this.contextNodeData = this.createNodeData();
|
|
this.nodeDataRefresh = false;
|
|
}
|
|
}
|
|
highlightDatum(node) {
|
|
if (node?.series === this && node.type === 1 /* Target */) {
|
|
return node;
|
|
}
|
|
}
|
|
update({ seriesRect }) {
|
|
const {
|
|
datumSelection,
|
|
labelSelection,
|
|
needleSelection,
|
|
targetSelection,
|
|
targetLabelSelection,
|
|
scaleSelection,
|
|
highlightTargetSelection,
|
|
tickSelection
|
|
} = this;
|
|
const resize = this.checkResize(seriesRect);
|
|
this.updateSelections(resize);
|
|
this.contentGroup.visible = this.visible;
|
|
this.contentGroup.opacity = this.getOpacity();
|
|
const nodeData = this.contextNodeData?.nodeData ?? [];
|
|
const labelData = this.contextNodeData?.labelData ?? [];
|
|
const needleData = this.contextNodeData?.needleData ?? [];
|
|
const targetData = this.contextNodeData?.targetData ?? [];
|
|
const scaleData = this.contextNodeData?.scaleData ?? [];
|
|
const tickData = this.contextNodeData?.tickData ?? [];
|
|
const highlightTargetDatum = this.highlightDatum(this.ctx.highlightManager.getActiveHighlight());
|
|
this.scaleSelection = this.updateScaleSelection({ scaleData, scaleSelection });
|
|
this.updateScaleNodes({ scaleSelection });
|
|
this.needleSelection = this.updateNeedleSelection({ needleData, needleSelection });
|
|
this.updateNeedleNodes({ needleSelection });
|
|
this.targetSelection = this.updateTargetSelection({ targetData, targetSelection });
|
|
this.updateTargetStyles({ targetSelection, isHighlight: false });
|
|
this.updateTargetNodes({ targetSelection });
|
|
this.targetLabelSelection = this.updateTargetLabelSelection({ targetData, targetLabelSelection });
|
|
this.updateTargetLabelNodes({ targetLabelSelection });
|
|
this.datumSelection = this.updateDatumSelection({ nodeData, datumSelection });
|
|
this.updateDatumNodes({ datumSelection });
|
|
this.labelSelection = this.updateLabelSelection({ labelData, labelSelection });
|
|
this.updateLabelNodes({ labelSelection });
|
|
this.highlightTargetSelection = this.updateTargetSelection({
|
|
targetData: highlightTargetDatum == null ? [] : [highlightTargetDatum],
|
|
targetSelection: highlightTargetSelection
|
|
});
|
|
this.updateTargetStyles({ targetSelection: highlightTargetSelection, isHighlight: true });
|
|
this.updateTargetNodes({ targetSelection: highlightTargetSelection });
|
|
this.tickSelection = this.updateTickSelection({ tickData, tickSelection });
|
|
this.updateTickNodes({ tickSelection });
|
|
if (resize) {
|
|
this.animationState.transition("resize");
|
|
}
|
|
this.animationState.transition("update");
|
|
}
|
|
updateDatumSelection(opts) {
|
|
return opts.datumSelection.update(opts.nodeData, void 0, (datum) => {
|
|
return createDatumId16(opts.nodeData.length, datum.itemId);
|
|
});
|
|
}
|
|
updateDatumNodes(opts) {
|
|
const { datumSelection } = opts;
|
|
const { ctx, properties } = this;
|
|
const { segmentation } = properties;
|
|
const sectorSpacing = segmentation.spacing ?? 0;
|
|
const animationDisabled = ctx.animationManager.isSkipped();
|
|
const fillBBox = this.getShapeFillBBox();
|
|
datumSelection.each((sector, datum) => {
|
|
const { centerX, centerY, innerRadius, outerRadius, startCornerRadius, endCornerRadius } = datum;
|
|
sector.centerX = centerX;
|
|
sector.centerY = centerY;
|
|
sector.innerRadius = innerRadius;
|
|
sector.outerRadius = outerRadius;
|
|
sector.pointerEvents = this.properties.bar.enabled ? module_support_exports.PointerEvents.All : module_support_exports.PointerEvents.None;
|
|
sector.setStyleProperties(datum.style, fillBBox);
|
|
sector.startOuterCornerRadius = startCornerRadius;
|
|
sector.startInnerCornerRadius = startCornerRadius;
|
|
sector.endOuterCornerRadius = endCornerRadius;
|
|
sector.endInnerCornerRadius = endCornerRadius;
|
|
sector.radialEdgeInset = (sectorSpacing + sector.strokeWidth) / 2;
|
|
sector.concentricEdgeInset = sector.strokeWidth / 2;
|
|
datum.midPoint = sector.getBBox().computeCenter();
|
|
if (animationDisabled || sector.previousDatum == null) {
|
|
sector.setProperties(resetRadialGaugeSeriesResetSectorFunction(sector, datum));
|
|
}
|
|
});
|
|
this.datumUnion.update(datumSelection, this.itemGroup, module_support_exports.Sector, (node, first2, last) => {
|
|
node.clipSector ?? (node.clipSector = new SectorBox3(Number.NaN, Number.NaN, Number.NaN, Number.NaN));
|
|
node.centerX = first2.centerX;
|
|
node.centerY = first2.centerY;
|
|
node.outerRadius = node.clipSector.outerRadius = first2.outerRadius;
|
|
node.innerRadius = node.clipSector.innerRadius = first2.innerRadius;
|
|
node.startAngle = node.clipSector.startAngle = first2.startAngle;
|
|
node.startInnerCornerRadius = first2.startInnerCornerRadius;
|
|
node.startOuterCornerRadius = first2.startOuterCornerRadius;
|
|
node.endAngle = last.endAngle;
|
|
node.clipSector.endAngle = last.clipSector?.endAngle ?? last.endAngle;
|
|
node.endInnerCornerRadius = last.endInnerCornerRadius;
|
|
node.endOuterCornerRadius = last.endOuterCornerRadius;
|
|
node.pointerEvents = module_support_exports.PointerEvents.None;
|
|
});
|
|
}
|
|
updateScaleSelection(opts) {
|
|
return opts.scaleSelection.update(opts.scaleData, void 0, (datum) => {
|
|
return createDatumId16(opts.scaleData.length, datum.itemId);
|
|
});
|
|
}
|
|
updateScaleNodes(opts) {
|
|
const { scaleSelection } = opts;
|
|
const { segmentation } = this.properties;
|
|
const sectorSpacing = segmentation.spacing ?? 0;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
scaleSelection.each((sector, datum) => {
|
|
const { centerX, centerY, innerRadius, outerRadius, startCornerRadius, endCornerRadius } = datum;
|
|
sector.centerX = centerX;
|
|
sector.centerY = centerY;
|
|
sector.innerRadius = innerRadius;
|
|
sector.outerRadius = outerRadius;
|
|
sector.setStyleProperties(datum.style, fillBBox);
|
|
sector.startOuterCornerRadius = startCornerRadius;
|
|
sector.startInnerCornerRadius = startCornerRadius;
|
|
sector.endOuterCornerRadius = endCornerRadius;
|
|
sector.endInnerCornerRadius = endCornerRadius;
|
|
sector.radialEdgeInset = (sectorSpacing + sector.strokeWidth) / 2;
|
|
sector.concentricEdgeInset = sector.strokeWidth / 2;
|
|
sector.setProperties(resetRadialGaugeSeriesResetSectorFunction(sector, datum));
|
|
});
|
|
}
|
|
updateNeedleSelection(opts) {
|
|
return opts.needleSelection.update(opts.needleData, void 0, () => createDatumId16(0));
|
|
}
|
|
updateNeedleNodes(opts) {
|
|
const { needleSelection } = opts;
|
|
const { fill, fillOpacity, stroke: stroke3, strokeOpacity, strokeWidth, lineDash, lineDashOffset } = this.properties.needle;
|
|
const animationDisabled = this.ctx.animationManager.isSkipped();
|
|
needleSelection.each((needle, datum) => {
|
|
const { centerX, centerY, radius } = datum;
|
|
const scale2 = radius * 2;
|
|
needle.d = RadialGaugeNeedle.defaultPathData;
|
|
needle.setStyleProperties({
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth: strokeWidth / scale2,
|
|
lineDash: lineDash.map((d) => d / scale2),
|
|
lineDashOffset: lineDashOffset / scale2
|
|
});
|
|
needle.translationX = centerX;
|
|
needle.translationY = centerY;
|
|
needle.scalingX = scale2;
|
|
needle.scalingY = scale2;
|
|
if (animationDisabled) {
|
|
needle.setProperties(resetRadialGaugeSeriesResetNeedleFunction(needle, datum));
|
|
}
|
|
});
|
|
}
|
|
updateTargetSelection(opts) {
|
|
return opts.targetSelection.update(opts.targetData, void 0, (target) => target.itemId);
|
|
}
|
|
updateTargetStyles({
|
|
targetSelection,
|
|
isHighlight
|
|
}) {
|
|
targetSelection.each((_, datum) => {
|
|
datum.style = this.getTargetStyle(isHighlight, datum);
|
|
});
|
|
}
|
|
updateTargetNodes({
|
|
targetSelection
|
|
}) {
|
|
targetSelection.each((target, datum) => {
|
|
const { centerX, centerY, angle: angle2, radius, shape, size, rotation } = datum;
|
|
target.setStyleProperties(datum.style);
|
|
target.size = size;
|
|
target.shape = shape === "line" ? lineMarker : shape;
|
|
target.translationX = centerX + radius * Math.cos(angle2);
|
|
target.translationY = centerY + radius * Math.sin(angle2);
|
|
target.rotation = angle2 + rotation;
|
|
});
|
|
}
|
|
getTargetStyle(isHighlight, { datumIndex, style: style2 }) {
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex);
|
|
return mergeDefaults(highlightStyle, {
|
|
...style2,
|
|
opacity: 1
|
|
});
|
|
}
|
|
updateTargetLabelSelection(opts) {
|
|
return opts.targetLabelSelection.update(opts.targetData, void 0, (target) => target.itemId);
|
|
}
|
|
updateTargetLabelNodes(opts) {
|
|
const { targetLabelSelection } = opts;
|
|
targetLabelSelection.each((label, target) => {
|
|
const { centerX, centerY, radius, angle: angle2, text: text2 } = target;
|
|
const { offsetX, offsetY, fill, fontStyle, fontWeight: fontWeight2, fontSize, fontFamily, textAlign, textBaseline } = target.label;
|
|
if (text2 == null) {
|
|
label.visible = false;
|
|
return;
|
|
}
|
|
label.visible = true;
|
|
label.x = centerX + radius * Math.cos(angle2) + offsetX;
|
|
label.y = centerY + radius * Math.sin(angle2) + offsetY;
|
|
label.text = text2;
|
|
label.fill = fill;
|
|
label.fontStyle = fontStyle;
|
|
label.fontWeight = fontWeight2;
|
|
label.fontSize = fontSize;
|
|
label.fontFamily = fontFamily;
|
|
label.textAlign = textAlign;
|
|
label.textBaseline = textBaseline;
|
|
});
|
|
}
|
|
updateLabelSelection(opts) {
|
|
return opts.labelSelection.update(opts.labelData, void 0, (datum) => datum.label);
|
|
}
|
|
updateLabelNodes(opts) {
|
|
const { labelSelection } = opts;
|
|
const animationDisabled = this.ctx.animationManager.isSkipped();
|
|
labelSelection.each((label, datum) => {
|
|
label.fill = datum.fill;
|
|
label.fontStyle = datum.fontStyle;
|
|
label.fontWeight = datum.fontWeight;
|
|
label.fontFamily = datum.fontFamily;
|
|
});
|
|
if (animationDisabled || this.labelsHaveExplicitText()) {
|
|
this.formatLabelText();
|
|
}
|
|
}
|
|
updateTickSelection(opts) {
|
|
return opts.tickSelection.update(opts.tickData, void 0, (datum) => datum.index);
|
|
}
|
|
updateTickNodes(opts) {
|
|
const { scale: scale2, radius, centerX, centerY, properties } = this;
|
|
const { enabled, color: color2, fontFamily, fontSize, fontStyle, fontWeight: fontWeight2, spacing } = properties.scale.label;
|
|
const rotation = toRadians(properties.scale.label.rotation ?? 0);
|
|
opts.tickSelection.each((label, datum) => {
|
|
if (!enabled) {
|
|
label.visible = false;
|
|
return;
|
|
}
|
|
label.visible = true;
|
|
label.text = datum.text;
|
|
label.fill = color2;
|
|
label.fontFamily = fontFamily;
|
|
label.fontSize = fontSize;
|
|
label.fontStyle = fontStyle;
|
|
label.fontWeight = fontWeight2;
|
|
const angle2 = scale2.convert(datum.value);
|
|
const { textAlign, textBaseline } = this.getTickLabelAlign(angle2);
|
|
const x0 = centerX + (radius + spacing) * Math.cos(angle2);
|
|
const y0 = centerY + (radius + spacing) * Math.sin(angle2);
|
|
label.textAlign = textAlign;
|
|
label.textBaseline = textBaseline;
|
|
label.x = x0;
|
|
label.y = y0;
|
|
label.rotationCenterX = x0;
|
|
label.rotationCenterY = y0;
|
|
label.rotation = rotation;
|
|
});
|
|
}
|
|
getTickLabelAlign(tickAngle) {
|
|
const cos = Math.cos(tickAngle);
|
|
const sin = Math.sin(tickAngle);
|
|
let textAlign;
|
|
let textBaseline;
|
|
const isCos0 = isNumberEqual(cos, 0);
|
|
const isSin0 = isNumberEqual(sin, 0);
|
|
const isCosPositive = cos > 0 && !isCos0;
|
|
const isSinPositive = sin > 0 && !isSin0;
|
|
textAlign = "right";
|
|
if (isCos0) {
|
|
textAlign = "center";
|
|
} else if (isCosPositive) {
|
|
textAlign = "left";
|
|
}
|
|
textBaseline = "bottom";
|
|
if (isSin0) {
|
|
textBaseline = "middle";
|
|
} else if (isSinPositive) {
|
|
textBaseline = "top";
|
|
}
|
|
return { textAlign, textBaseline };
|
|
}
|
|
getTickLabelInset(params) {
|
|
const { tickData, radius, centerXOffset, centerYOffset, seriesRectWidth, seriesRectHeight, spacing, rotation } = params;
|
|
const { label } = this.properties.scale;
|
|
const centerX = seriesRectWidth / 2 + centerXOffset * radius;
|
|
const centerY = seriesRectHeight / 2 + centerYOffset * radius;
|
|
const tempText = new TransformableText5();
|
|
tempText.fontFamily = label.fontFamily;
|
|
tempText.fontSize = label.fontSize;
|
|
tempText.fontStyle = label.fontStyle;
|
|
tempText.fontWeight = label.fontWeight;
|
|
tempText.rotation = rotation;
|
|
const minComponent = 1e-6;
|
|
let inset = 0;
|
|
for (const datum of tickData) {
|
|
const angle2 = this.scale.convert(datum.value);
|
|
const cos = Math.cos(angle2);
|
|
const sin = Math.sin(angle2);
|
|
const x = centerX + (radius + spacing) * cos;
|
|
const y = centerY + (radius + spacing) * sin;
|
|
const { textAlign, textBaseline } = this.getTickLabelAlign(angle2);
|
|
tempText.text = datum.text;
|
|
tempText.x = x;
|
|
tempText.y = y;
|
|
tempText.textAlign = textAlign;
|
|
tempText.textBaseline = textBaseline;
|
|
tempText.rotationCenterX = x;
|
|
tempText.rotationCenterY = y;
|
|
const box = rotation ? Transformable5.toCanvas(tempText) : tempText.getBBox();
|
|
if (box == null)
|
|
continue;
|
|
const minX = box.x;
|
|
const maxX = box.x + box.width;
|
|
const minY = box.y;
|
|
const maxY = box.y + box.height;
|
|
const overflowLeft = Math.max(0, -minX);
|
|
const overflowRight = Math.max(0, maxX - seriesRectWidth);
|
|
const overflowTop = Math.max(0, -minY);
|
|
const overflowBottom = Math.max(0, maxY - seriesRectHeight);
|
|
const dxPerRadius = centerXOffset + cos;
|
|
if (Math.abs(dxPerRadius) > minComponent) {
|
|
if (overflowRight > 0 && dxPerRadius > 0) {
|
|
inset = Math.max(inset, overflowRight / dxPerRadius);
|
|
} else if (overflowLeft > 0 && dxPerRadius < 0) {
|
|
inset = Math.max(inset, overflowLeft / -dxPerRadius);
|
|
}
|
|
}
|
|
const dyPerRadius = centerYOffset + sin;
|
|
if (Math.abs(dyPerRadius) > minComponent) {
|
|
if (overflowBottom > 0 && dyPerRadius > 0) {
|
|
inset = Math.max(inset, overflowBottom / dyPerRadius);
|
|
} else if (overflowTop > 0 && dyPerRadius < 0) {
|
|
inset = Math.max(inset, overflowTop / -dyPerRadius);
|
|
}
|
|
}
|
|
}
|
|
return inset;
|
|
}
|
|
labelsHaveExplicitText() {
|
|
for (const { datum } of this.labelSelection) {
|
|
if (datum.text == null) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
formatLabelText(datum) {
|
|
const { labelSelection, radius, textAlign, verticalAlign } = this;
|
|
const { spacing: padding2, innerRadiusRatio } = this.properties;
|
|
formatRadialGaugeLabels(
|
|
this,
|
|
this.ctx,
|
|
labelSelection,
|
|
{ padding: padding2, textAlign, verticalAlign },
|
|
radius * innerRadiusRatio,
|
|
datum
|
|
);
|
|
}
|
|
resetAllAnimation() {
|
|
this.ctx.animationManager.stopByAnimationGroupId(this.id);
|
|
resetMotion5([this.datumSelection], resetRadialGaugeSeriesResetSectorFunction);
|
|
resetMotion5([this.needleSelection], resetRadialGaugeSeriesResetNeedleFunction);
|
|
this.formatLabelText();
|
|
}
|
|
resetAnimation(phase) {
|
|
if (phase === "initial") {
|
|
this.animationState.transition("reset");
|
|
} else if (phase === "ready") {
|
|
this.animationState.transition("skip");
|
|
}
|
|
}
|
|
animateLabelText(params = {}) {
|
|
const { animationManager } = this.ctx;
|
|
let labelFrom;
|
|
let labelTo;
|
|
let secondaryLabelFrom;
|
|
let secondaryLabelTo;
|
|
this.labelSelection.each((label, datum) => {
|
|
label.opacity = 1;
|
|
if (datum.label === "primary" /* Primary */) {
|
|
labelFrom = label.previousDatum?.value ?? params.from ?? datum.value;
|
|
labelTo = datum.value;
|
|
} else if (datum.label === "secondary" /* Secondary */) {
|
|
secondaryLabelFrom = label.previousDatum?.value ?? params.from ?? datum.value;
|
|
secondaryLabelTo = datum.value;
|
|
}
|
|
});
|
|
if (this.labelsHaveExplicitText()) {
|
|
} else if (labelTo == null || secondaryLabelTo == null) {
|
|
this.formatLabelText();
|
|
} else if (labelFrom === labelTo && secondaryLabelFrom === secondaryLabelTo) {
|
|
this.formatLabelText({ label: labelTo, secondaryLabel: secondaryLabelTo });
|
|
} else {
|
|
const animationId = `${this.id}_labels`;
|
|
animationManager.animate({
|
|
id: animationId,
|
|
groupId: "label",
|
|
from: { label: labelFrom, secondaryLabel: secondaryLabelFrom },
|
|
to: { label: labelTo, secondaryLabel: secondaryLabelTo },
|
|
phase: params.phase ?? "update",
|
|
onUpdate: (datum) => this.formatLabelText(datum),
|
|
onStop: () => this.formatLabelText({ label: labelTo, secondaryLabel: secondaryLabelTo })
|
|
});
|
|
}
|
|
}
|
|
animateEmptyUpdateReady() {
|
|
const { animationManager } = this.ctx;
|
|
const { node, needle } = prepareRadialGaugeSeriesAnimationFunctions(true, this.scale.range[0]);
|
|
fromToMotion6(this.id, "node", animationManager, [this.datumSelection], node, (_sector, datum) => datum.itemId);
|
|
fromToMotion6(this.id, "needle", animationManager, [this.needleSelection], needle, () => "needle");
|
|
fromToMotion6(
|
|
this.id,
|
|
"label",
|
|
animationManager,
|
|
[this.labelSelection],
|
|
fadeInFns,
|
|
(_label, datum) => datum.label
|
|
);
|
|
this.animateLabelText({
|
|
from: this.properties.scale.min,
|
|
phase: "initial"
|
|
});
|
|
}
|
|
animateWaitingUpdateReady() {
|
|
const { animationManager } = this.ctx;
|
|
const { node, needle } = prepareRadialGaugeSeriesAnimationFunctions(false, this.scale.range[0]);
|
|
fromToMotion6(this.id, "node", animationManager, [this.datumSelection], node, (_sector, datum) => datum.itemId);
|
|
fromToMotion6(this.id, "needle", animationManager, [this.needleSelection], needle, () => "needle");
|
|
this.animateLabelText();
|
|
}
|
|
animateReadyResize() {
|
|
this.resetAllAnimation();
|
|
}
|
|
dataCount() {
|
|
return Number.NaN;
|
|
}
|
|
getSeriesDomain() {
|
|
return { domain: [Number.NaN, Number.NaN] };
|
|
}
|
|
getSeriesRange() {
|
|
return [Number.NaN, Number.NaN];
|
|
}
|
|
getLegendData() {
|
|
return [];
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, properties } = this;
|
|
const { tooltip } = properties;
|
|
let value;
|
|
let text2;
|
|
let fallbackLabel;
|
|
if (datumIndex.type === 0 /* Node */) {
|
|
value = properties.value;
|
|
text2 = properties.label.text;
|
|
fallbackLabel = this.ctx.localeManager.t("ariaLabelGaugeValue");
|
|
} else {
|
|
({ value, text: text2 } = properties.targets[datumIndex.index]);
|
|
fallbackLabel = this.ctx.localeManager.t("ariaLabelGaugeTarget");
|
|
}
|
|
if (value == null)
|
|
return;
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
data: [{ label: text2, fallbackLabel, value: this.formatLabel(value) }]
|
|
},
|
|
{ seriesId, title: void 0, datum: void 0, value }
|
|
);
|
|
}
|
|
pickNodeClosestDatum(point) {
|
|
return pickGaugeNearestDatum(this, point);
|
|
}
|
|
pickFocus(opts) {
|
|
return pickGaugeFocus(this, opts);
|
|
}
|
|
getCaptionText() {
|
|
const { value } = this.properties;
|
|
const description = [];
|
|
description.push(this.formatLabel(value));
|
|
this.labelSelection.each((_label, datum) => {
|
|
const text2 = getLabelText(this.id, this.ctx, datum);
|
|
if (text2 != null) {
|
|
description.push(toPlainText(text2));
|
|
}
|
|
});
|
|
return description.join(". ");
|
|
}
|
|
getCategoryValue(_datumIndex) {
|
|
return;
|
|
}
|
|
datumIndexForCategoryValue(_categoryValue) {
|
|
return;
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
RadialGaugeSeries.className = "RadialGaugeSeries";
|
|
RadialGaugeSeries.type = "radial-gauge";
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-gauge/radialGaugeModule.ts
|
|
var RadialGaugeModule = {
|
|
type: "series",
|
|
name: "radial-gauge",
|
|
chartType: "standalone",
|
|
enterprise: true,
|
|
dependencies: [GaugePresetModule],
|
|
version: VERSION,
|
|
options: radialGaugeSeriesOptionsDef,
|
|
themeTemplate: {
|
|
minWidth: 200,
|
|
minHeight: 200,
|
|
tooltip: {
|
|
enabled: false
|
|
},
|
|
series: {
|
|
outerRadiusRatio: 1,
|
|
innerRadiusRatio: 0.8,
|
|
startAngle: 270,
|
|
endAngle: 270 + 180,
|
|
defaultColorRange: {
|
|
$if: [
|
|
{ $eq: [{ $palette: "type" }, "inbuilt"] },
|
|
{ $interpolate: [{ $palette: "secondDivergingColors" }, 5] },
|
|
SAFE_RANGE2_OPERATION
|
|
]
|
|
},
|
|
scale: {
|
|
defaultFill: { $path: ["/1", { $palette: "fill" }, { $palette: "hierarchyColors" }] },
|
|
// TODO: mix backgroundColor and foregroundColor?
|
|
stroke: { $path: ["/2", SAFE_STROKE_FILL_OPERATION, { $palette: "hierarchyColors" }] },
|
|
// TODO: mix backgroundColor and foregroundColor?
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, 0] },
|
|
label: {
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
color: { $ref: "textColor" },
|
|
spacing: 12
|
|
}
|
|
},
|
|
bar: {
|
|
strokeWidth: { $isUserOption: ["./stroke", 2, 0] }
|
|
},
|
|
segmentation: {
|
|
enabled: false,
|
|
interval: {},
|
|
spacing: 2
|
|
},
|
|
defaultTarget: {
|
|
fill: { $ref: "foregroundColor" },
|
|
stroke: { $ref: "foregroundColor" },
|
|
size: 10,
|
|
shape: "triangle",
|
|
placement: "outside",
|
|
spacing: 5,
|
|
label: {
|
|
enabled: true,
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
color: { $ref: "textColor" },
|
|
spacing: 5
|
|
}
|
|
},
|
|
needle: {
|
|
enabled: false,
|
|
fill: { $ref: "foregroundColor" },
|
|
spacing: 10
|
|
},
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: 56,
|
|
minimumFontSize: 18 / 56,
|
|
fontFamily: { $ref: "fontFamily" },
|
|
color: { $ref: "textColor" }
|
|
},
|
|
secondaryLabel: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
fontWeight: { $ref: "fontWeight" },
|
|
fontSize: { $rem: FONT_SIZE_RATIO.LARGE },
|
|
minimumFontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
color: { $ref: "subtleTextColor" }
|
|
},
|
|
tooltip: {
|
|
range: { $path: ["/tooltip/range", 10] }
|
|
}
|
|
}
|
|
},
|
|
create: (ctx) => new RadialGaugeSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/module-bundles/gauge.ts
|
|
var AllGaugeModule = [LinearGaugeModule, RadialGaugeModule];
|
|
|
|
// packages/ag-charts-enterprise/src/utils/polar.ts
|
|
function walkPairsOutward(items, step, visitPair) {
|
|
const middleIndex = Math.floor(items.length / 2);
|
|
return walkPairsByStep(items, step, middleIndex, step, visitPair) || walkPairsByStep(items, items.length - step, middleIndex, -step, visitPair);
|
|
}
|
|
function walkPairsByStep(items, startIndex, endIndex, step, visitPair) {
|
|
let previous = items[0];
|
|
for (let i = startIndex; step > 0 ? i <= endIndex : i > endIndex; i += step) {
|
|
const current = items[i];
|
|
if (visitPair(previous, current)) {
|
|
return true;
|
|
}
|
|
previous = current;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/axes/angle-number/angleAxisInterval.ts
|
|
var { AxisInterval: AxisInterval3 } = module_support_exports;
|
|
var AngleAxisInterval = class extends AxisInterval3 {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AngleAxisInterval.prototype, "minSpacing", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/axes/polar-crosslines/polarCrossLine.ts
|
|
var { Group: Group16, LabelStyle: LabelStyle2 } = module_support_exports;
|
|
var PolarCrossLineLabel = class extends LabelStyle2 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.padding = 5;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLineLabel.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLineLabel.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLineLabel.prototype, "text", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLineLabel.prototype, "parallel", 2);
|
|
var PolarCrossLine = class extends BaseProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.id = createId(this);
|
|
this.defaultColorRange = [];
|
|
this.shape = "polygon";
|
|
this.label = new PolarCrossLineLabel();
|
|
this.scale = void 0;
|
|
this.clippedRange = [-Infinity, Infinity];
|
|
this.gridLength = 0;
|
|
this.sideFlag = -1;
|
|
this.parallelFlipRotation = 0;
|
|
this.regularFlipRotation = 0;
|
|
this.direction = "angle" /* Angle */;
|
|
this.axisInnerRadius = 0;
|
|
this.axisOuterRadius = 0;
|
|
this.lineGroup = new Group16({ name: this.id });
|
|
this.rangeGroup = new Group16({ name: this.id });
|
|
this.labelGroup = new Group16({ name: this.id });
|
|
this._isRange = void 0;
|
|
}
|
|
assignCrossLineGroup(isRange, crossLineRange) {
|
|
if (isRange !== this._isRange) {
|
|
if (isRange) {
|
|
this.rangeGroup.appendChild(crossLineRange);
|
|
} else {
|
|
this.lineGroup.appendChild(crossLineRange);
|
|
}
|
|
}
|
|
this._isRange = isRange;
|
|
}
|
|
setSectorNodeProps(node) {
|
|
node.fill = this.fill;
|
|
node.fillOpacity = this.fillOpacity ?? 1;
|
|
node.stroke = this.stroke;
|
|
node.strokeOpacity = this.strokeOpacity ?? 1;
|
|
node.strokeWidth = this.strokeWidth ?? 1;
|
|
node.lineDash = this.lineDash;
|
|
}
|
|
setLabelNodeProps(node, x, y, baseline, rotation) {
|
|
const { label } = this;
|
|
node.x = x;
|
|
node.y = y;
|
|
node.text = label.text;
|
|
node.textAlign = "center";
|
|
node.textBaseline = baseline;
|
|
node.rotation = rotation;
|
|
node.rotationCenterX = x;
|
|
node.rotationCenterY = y;
|
|
node.fill = label.color;
|
|
node.setFont(label);
|
|
node.setBoxing(label);
|
|
node.visible = true;
|
|
}
|
|
};
|
|
PolarCrossLine.className = "PolarCrossLine";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLine.prototype, "enabled", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLine.prototype, "type", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLine.prototype, "range", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLine.prototype, "value", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLine.prototype, "defaultColorRange", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLine.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLine.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLine.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLine.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLine.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLine.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLine.prototype, "shape", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], PolarCrossLine.prototype, "label", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/axes/polar-crosslines/angleCrossLine.ts
|
|
var { getCrossLineValue: getCrossLineValue2, validateCrossLineValue: validateCrossLineValue2, Group: Group17, Path: Path9, Sector: Sector5, RotatableText: RotatableText2, ContinuousScale: ContinuousScale5 } = module_support_exports;
|
|
var AngleCrossLine = class extends PolarCrossLine {
|
|
constructor() {
|
|
super();
|
|
this.direction = "angle" /* Angle */;
|
|
this.polygonNode = new Path9();
|
|
this.sectorNode = new Sector5();
|
|
this.lineNode = new Path9();
|
|
this.crossLineRange = new Group17();
|
|
this.labelNode = new RotatableText2();
|
|
this.ticks = [];
|
|
this.crossLineRange.append(this.polygonNode);
|
|
this.crossLineRange.append(this.sectorNode);
|
|
this.crossLineRange.append(this.lineNode);
|
|
this.labelGroup.append(this.labelNode);
|
|
}
|
|
visibilityCheck() {
|
|
if (!ContinuousScale5.is(this.scale)) {
|
|
return true;
|
|
}
|
|
const [d0, d1] = this.scale.domain;
|
|
const value = getCrossLineValue2(this);
|
|
if (this.type === "range") {
|
|
const [start2, end3] = value;
|
|
return start2 >= d0 && start2 <= d1 && end3 >= start2 && end3 <= d1;
|
|
} else {
|
|
return value >= d0 && value <= d1;
|
|
}
|
|
}
|
|
update(visible) {
|
|
const { scale: scale2 } = this;
|
|
if (!scale2 || !validateCrossLineValue2(this, scale2) || !this.visibilityCheck()) {
|
|
this.rangeGroup.visible = false;
|
|
this.lineGroup.visible = false;
|
|
this.labelGroup.visible = false;
|
|
return;
|
|
}
|
|
this.rangeGroup.visible = visible;
|
|
this.lineGroup.visible = visible;
|
|
this.labelGroup.visible = visible;
|
|
this.updateLineNode(visible);
|
|
this.updatePolygonNode(visible);
|
|
this.updateSectorNode(visible);
|
|
this.updateLabelNode(visible);
|
|
}
|
|
updateLineNode(visible) {
|
|
const { scale: scale2, type, value, lineNode: line } = this;
|
|
if (!visible || type !== "line" || !scale2) {
|
|
line.visible = false;
|
|
return;
|
|
}
|
|
const angle2 = scale2.convert(value);
|
|
if (Number.isNaN(angle2)) {
|
|
line.visible = false;
|
|
return;
|
|
}
|
|
const { axisInnerRadius, axisOuterRadius } = this;
|
|
line.visible = true;
|
|
line.stroke = this.stroke;
|
|
line.strokeOpacity = this.strokeOpacity ?? 1;
|
|
line.strokeWidth = this.strokeWidth ?? 1;
|
|
line.fill = void 0;
|
|
line.lineDash = this.lineDash;
|
|
const x = axisOuterRadius * Math.cos(angle2);
|
|
const y = axisOuterRadius * Math.sin(angle2);
|
|
const x0 = axisInnerRadius * Math.cos(angle2);
|
|
const y0 = axisInnerRadius * Math.sin(angle2);
|
|
line.path.clear(true);
|
|
line.path.moveTo(x0, y0);
|
|
line.path.lineTo(x, y);
|
|
this.assignCrossLineGroup(false, this.crossLineRange);
|
|
}
|
|
updatePolygonNode(visible) {
|
|
const { polygonNode: polygon, range: range3, scale: scale2, shape, type, ticks } = this;
|
|
if (!visible || type !== "range" || shape !== "polygon" || !scale2 || !range3) {
|
|
polygon.visible = false;
|
|
return;
|
|
}
|
|
const { axisInnerRadius, axisOuterRadius } = this;
|
|
const startIndex = ticks.indexOf(range3[0]);
|
|
const endIndex = ticks.indexOf(range3[1]);
|
|
const stops = startIndex <= endIndex ? ticks.slice(startIndex, endIndex + 1) : ticks.slice(startIndex).concat(ticks.slice(0, endIndex + 1));
|
|
const angles = stops.map((value) => scale2.convert(value));
|
|
polygon.visible = true;
|
|
this.setSectorNodeProps(polygon);
|
|
const { path } = polygon;
|
|
path.clear(true);
|
|
for (const [index, angle2] of angles.entries()) {
|
|
const x = axisOuterRadius * Math.cos(angle2);
|
|
const y = axisOuterRadius * Math.sin(angle2);
|
|
if (index === 0) {
|
|
path.moveTo(x, y);
|
|
} else {
|
|
path.lineTo(x, y);
|
|
}
|
|
}
|
|
if (axisInnerRadius === 0) {
|
|
path.lineTo(0, 0);
|
|
} else {
|
|
const reversedAngles = angles.slice().reverse();
|
|
for (const angle2 of reversedAngles) {
|
|
const x = axisInnerRadius * Math.cos(angle2);
|
|
const y = axisInnerRadius * Math.sin(angle2);
|
|
path.lineTo(x, y);
|
|
}
|
|
}
|
|
polygon.path.closePath();
|
|
this.assignCrossLineGroup(true, this.crossLineRange);
|
|
}
|
|
updateSectorNode(visible) {
|
|
const { sectorNode: sector, range: range3, scale: scale2, shape, type } = this;
|
|
if (!visible || type !== "range" || shape !== "circle" || !scale2 || !range3) {
|
|
sector.visible = false;
|
|
return;
|
|
}
|
|
const { axisInnerRadius, axisOuterRadius } = this;
|
|
const angles = range3.map((value) => scale2.convert(value));
|
|
const step = scale2.step ?? 0;
|
|
const padding2 = scale2 instanceof module_support_exports.BandScale ? step / 2 : 0;
|
|
sector.visible = true;
|
|
this.setSectorNodeProps(sector);
|
|
sector.centerX = 0;
|
|
sector.centerY = 0;
|
|
sector.innerRadius = axisInnerRadius;
|
|
sector.outerRadius = axisOuterRadius;
|
|
sector.startAngle = angles[0] - padding2;
|
|
sector.endAngle = angles[1] + padding2;
|
|
this.assignCrossLineGroup(true, this.crossLineRange);
|
|
}
|
|
updateLabelNode(visible) {
|
|
const { label, labelNode: node, range: range3, scale: scale2, type, ticks } = this;
|
|
if (!visible || label.enabled === false || !label.text || !scale2 || type === "range" && !range3) {
|
|
node.visible = false;
|
|
return;
|
|
}
|
|
node.visible = true;
|
|
const { axisInnerRadius, axisOuterRadius } = this;
|
|
let labelX;
|
|
let labelY;
|
|
let rotation;
|
|
let textBaseline;
|
|
if (type === "line") {
|
|
const angle2 = normalizeAngle360(scale2.convert(this.value));
|
|
const angle270 = 1.5 * Math.PI;
|
|
const isRightSide = isNumberEqual(angle2, angle270) || angle2 > angle270 || angle2 < Math.PI / 2;
|
|
const midX = (axisInnerRadius + axisOuterRadius) / 2 * Math.cos(angle2);
|
|
const midY = (axisInnerRadius + axisOuterRadius) / 2 * Math.sin(angle2);
|
|
labelX = midX + label.padding * Math.cos(angle2 + Math.PI / 2);
|
|
labelY = midY + label.padding * Math.sin(angle2 + Math.PI / 2);
|
|
textBaseline = isRightSide ? "top" : "bottom";
|
|
rotation = isRightSide ? angle2 : angle2 - Math.PI;
|
|
} else {
|
|
const [startAngle, endAngle] = range3.map((value) => normalizeAngle360(scale2.convert(value)));
|
|
let angle2 = (startAngle + endAngle) / 2;
|
|
if (startAngle > endAngle) {
|
|
angle2 -= Math.PI;
|
|
}
|
|
angle2 = normalizeAngle360(angle2);
|
|
const isBottomSide = (isNumberEqual(angle2, 0) || angle2 > 0) && angle2 < Math.PI;
|
|
let distance2;
|
|
if (this.shape === "circle" || ticks.length < 3) {
|
|
distance2 = axisOuterRadius - label.padding;
|
|
} else {
|
|
distance2 = axisOuterRadius * Math.cos(Math.PI / ticks.length) - label.padding;
|
|
}
|
|
labelX = distance2 * Math.cos(angle2);
|
|
labelY = distance2 * Math.sin(angle2);
|
|
textBaseline = isBottomSide ? "bottom" : "top";
|
|
rotation = isBottomSide ? angle2 - Math.PI / 2 : angle2 + Math.PI / 2;
|
|
}
|
|
this.setLabelNodeProps(node, labelX, labelY, textBaseline, rotation);
|
|
}
|
|
};
|
|
AngleCrossLine.className = "AngleCrossLine";
|
|
|
|
// packages/ag-charts-enterprise/src/axes/angle/angleAxis.ts
|
|
var { Path: Path10, RotatableText: RotatableText3, Transformable: Transformable6, BBox: BBox27, Selection: Selection11, Line: Line5 } = module_support_exports;
|
|
var AngleAxisLabel = class extends module_support_exports.AxisLabel {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.orientation = "fixed";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AngleAxisLabel.prototype, "orientation", 2);
|
|
var AngleAxis = class extends module_support_exports.PolarAxis {
|
|
constructor(moduleCtx, scale2) {
|
|
super(moduleCtx, scale2);
|
|
this.startAngle = 0;
|
|
this.endAngle = void 0;
|
|
this.tickLineGroupSelection = Selection11.select(
|
|
this.tickLineGroup,
|
|
Line5,
|
|
false
|
|
);
|
|
this.gridLineGroupSelection = Selection11.select(
|
|
this.gridLineGroup,
|
|
Line5,
|
|
false
|
|
);
|
|
this.labelData = [];
|
|
this.tickData = [];
|
|
this.radiusLineGroup = this.axisGroup.appendChild(new module_support_exports.TransformableGroup());
|
|
this.radiusLine = this.radiusLineGroup.appendChild(new Path10());
|
|
this.includeInvisibleDomains = true;
|
|
}
|
|
get direction() {
|
|
return "angle" /* Angle */;
|
|
}
|
|
createLabel() {
|
|
return new AngleAxisLabel();
|
|
}
|
|
calculateRotations() {
|
|
const rotation = toRadians(this.startAngle);
|
|
const parallelFlipRotation = normalizeAngle360(rotation);
|
|
const regularFlipRotation = normalizeAngle360(rotation - Math.PI / 2);
|
|
return { rotation, parallelFlipRotation, regularFlipRotation };
|
|
}
|
|
calculateTickLayout(domain) {
|
|
const { nice, scale: scale2 } = this;
|
|
const ticksParams = {
|
|
nice: [nice, nice],
|
|
interval: void 0,
|
|
tickCount: void 0,
|
|
minTickCount: 0,
|
|
maxTickCount: Infinity
|
|
};
|
|
const niceDomain = nice ? scale2.niceDomain(ticksParams, domain) : domain;
|
|
const tickData = this.generateAngleTicks(niceDomain);
|
|
this.tickData = tickData;
|
|
const ticks = tickData.map((t) => t.value);
|
|
const fractionDigits = ticks.reduce(
|
|
(f, t) => Math.max(typeof t === "number" ? countFractionDigits(t) : 0, f),
|
|
0
|
|
);
|
|
return {
|
|
niceDomain,
|
|
tickDomain: niceDomain,
|
|
ticks,
|
|
rawTickCount: void 0,
|
|
fractionDigits,
|
|
timeInterval: void 0,
|
|
bbox: this.getBBox()
|
|
};
|
|
}
|
|
update() {
|
|
super.update();
|
|
this.updateRadiusLine();
|
|
this.updateGridLines();
|
|
this.updateTickLines();
|
|
}
|
|
normalizedAngles() {
|
|
const startAngle = normalizeAngle360(-Math.PI / 2 + toRadians(this.startAngle));
|
|
const sweep = this.endAngle == null ? 2 * Math.PI : normalizeAngle360Inclusive(toRadians(this.endAngle) - toRadians(this.startAngle));
|
|
const endAngle = startAngle + sweep;
|
|
return [startAngle, endAngle];
|
|
}
|
|
computeRange() {
|
|
this.range = this.normalizedAngles();
|
|
}
|
|
updateSelections() {
|
|
const data = this.tickData;
|
|
this.gridLineGroupSelection.update(this.gridLength && this.gridLine.enabled ? data : []);
|
|
this.tickLineGroupSelection.update(this.tick.enabled ? data : []);
|
|
this.tickLabelGroupSelection.update(this.label.enabled ? data : []);
|
|
this.gridLineGroupSelection.cleanup();
|
|
this.tickLineGroupSelection.cleanup();
|
|
this.tickLabelGroupSelection.cleanup();
|
|
}
|
|
updatePosition() {
|
|
super.updatePosition();
|
|
const { translation, radiusLineGroup } = this;
|
|
const translationX = Math.floor(translation.x);
|
|
const translationY = Math.floor(translation.y);
|
|
radiusLineGroup.translationX = translationX;
|
|
radiusLineGroup.translationY = translationY;
|
|
}
|
|
updateRadiusLine() {
|
|
const node = this.radiusLine;
|
|
const { path } = node;
|
|
path.clear(true);
|
|
const { points, closePath } = this.getAxisLinePoints();
|
|
for (const { x, y, moveTo: moveTo2, arc, radius = 0, startAngle = 0, endAngle = 0 } of points) {
|
|
if (arc) {
|
|
path.arc(x, y, radius, startAngle, endAngle);
|
|
} else if (moveTo2) {
|
|
path.moveTo(x, y);
|
|
} else {
|
|
path.lineTo(x, y);
|
|
}
|
|
}
|
|
if (closePath) {
|
|
path.closePath();
|
|
}
|
|
node.visible = this.line.enabled;
|
|
node.stroke = this.line.stroke;
|
|
node.strokeWidth = this.line.width;
|
|
node.fill = void 0;
|
|
}
|
|
getAxisLinePoints() {
|
|
const { scale: scale2, shape, gridLength: radius } = this;
|
|
const [startAngle, endAngle] = this.range;
|
|
const isFullCircle = isNumberEqual(endAngle - startAngle, 2 * Math.PI);
|
|
const points = [];
|
|
if (shape === "circle") {
|
|
if (isFullCircle) {
|
|
points.push(
|
|
{ x: radius, y: 0, moveTo: true },
|
|
{
|
|
x: 0,
|
|
y: 0,
|
|
radius,
|
|
startAngle: 0,
|
|
endAngle: 2 * Math.PI,
|
|
arc: true,
|
|
moveTo: false
|
|
}
|
|
);
|
|
} else {
|
|
points.push(
|
|
{
|
|
x: radius * Math.cos(startAngle),
|
|
y: radius * Math.sin(startAngle),
|
|
moveTo: true
|
|
},
|
|
{
|
|
x: 0,
|
|
y: 0,
|
|
radius,
|
|
startAngle: normalizeAngle360(startAngle),
|
|
endAngle: normalizeAngle360(endAngle),
|
|
arc: true,
|
|
moveTo: false
|
|
}
|
|
);
|
|
}
|
|
} else if (shape === "polygon") {
|
|
const angles = scale2.ticks({
|
|
nice: [this.nice, this.nice],
|
|
interval: void 0,
|
|
tickCount: void 0,
|
|
minTickCount: 0,
|
|
maxTickCount: Infinity
|
|
})?.ticks?.map((value) => scale2.convert(value));
|
|
if (angles && angles.length > 2) {
|
|
for (const [i, angle2] of angles.entries()) {
|
|
const x = radius * Math.cos(angle2);
|
|
const y = radius * Math.sin(angle2);
|
|
const moveTo2 = i === 0;
|
|
points.push({ x, y, moveTo: moveTo2 });
|
|
}
|
|
}
|
|
}
|
|
return { points, closePath: isFullCircle };
|
|
}
|
|
updateGridLines() {
|
|
const {
|
|
scale: scale2,
|
|
gridLength: radius,
|
|
gridLine: { style: style2, width: width2 },
|
|
innerRadiusRatio
|
|
} = this;
|
|
if (!(style2 && radius > 0)) {
|
|
return;
|
|
}
|
|
const innerRadius = radius * innerRadiusRatio;
|
|
const styleCount = style2.length;
|
|
this.gridLineGroupSelection.each((line, datum, index) => {
|
|
const { value } = datum;
|
|
const { stroke: stroke3, lineDash } = style2[index % styleCount];
|
|
const angle2 = scale2.convert(value);
|
|
line.x1 = innerRadius * Math.cos(angle2);
|
|
line.y1 = innerRadius * Math.sin(angle2);
|
|
line.x2 = radius * Math.cos(angle2);
|
|
line.y2 = radius * Math.sin(angle2);
|
|
line.stroke = stroke3;
|
|
line.strokeWidth = width2;
|
|
line.lineDash = lineDash;
|
|
line.fill = void 0;
|
|
});
|
|
this.gridLineGroupSelection.cleanup();
|
|
}
|
|
updateLabels() {
|
|
const { label, tickLabelGroupSelection } = this;
|
|
tickLabelGroupSelection.each((node, _, index) => {
|
|
const labelDatum = this.labelData[index];
|
|
if (!labelDatum || labelDatum.hidden) {
|
|
node.visible = false;
|
|
return;
|
|
}
|
|
node.text = labelDatum.text;
|
|
node.setFont(label);
|
|
node.fill = label.color;
|
|
node.x = labelDatum.x;
|
|
node.y = labelDatum.y;
|
|
node.setAlign(labelDatum);
|
|
node.setBoxing(label);
|
|
node.visible = true;
|
|
if (labelDatum.rotation) {
|
|
node.rotation = labelDatum.rotation;
|
|
node.rotationCenterX = labelDatum.x;
|
|
node.rotationCenterY = labelDatum.y;
|
|
} else {
|
|
node.rotation = 0;
|
|
}
|
|
});
|
|
}
|
|
updateTickLines() {
|
|
const { scale: scale2, gridLength: radius, tick, tickLineGroupSelection } = this;
|
|
tickLineGroupSelection.each((line, datum) => {
|
|
const { value } = datum;
|
|
const angle2 = scale2.convert(value);
|
|
const cos = Math.cos(angle2);
|
|
const sin = Math.sin(angle2);
|
|
line.x1 = radius * cos;
|
|
line.y1 = radius * sin;
|
|
line.x2 = (radius + tick.size) * cos;
|
|
line.y2 = (radius + tick.size) * sin;
|
|
line.stroke = tick.stroke;
|
|
line.strokeWidth = tick.width;
|
|
});
|
|
}
|
|
createLabelNodeData(ticks, options, seriesRect) {
|
|
const { label, gridLength: radius, scale: scale2, tick } = this;
|
|
if (!label.enabled) {
|
|
return [];
|
|
}
|
|
const tempText = new RotatableText3();
|
|
const seriesLeft = seriesRect.x - this.translation.x;
|
|
const seriesRight = seriesRect.x + seriesRect.width - this.translation.x;
|
|
const { fractionDigits } = this.layout.label;
|
|
const axisTickFormatter2 = this.tickFormatter(this.scale.domain, this.tickData, false, fractionDigits);
|
|
const labelData = ticks.map((datum, index) => {
|
|
const { value } = datum;
|
|
const distance2 = radius + label.spacing + tick.size;
|
|
const angle2 = scale2.convert(value);
|
|
const cos = Math.cos(angle2);
|
|
const sin = Math.sin(angle2);
|
|
const x = distance2 * cos;
|
|
const y = distance2 * sin;
|
|
const { textAlign, textBaseline } = this.getLabelAlign(angle2);
|
|
const isLastTickOverFirst = index === ticks.length - 1 && value !== ticks[0] && isNumberEqual(normalizeAngle360(angle2), normalizeAngle360(scale2.convert(ticks[0])));
|
|
const rotation = this.getLabelRotation(angle2);
|
|
let text2 = axisTickFormatter2(value, index);
|
|
tempText.text = text2;
|
|
tempText.x = x;
|
|
tempText.y = y;
|
|
tempText.setFont(label);
|
|
tempText.textAlign = textAlign;
|
|
tempText.textBaseline = textBaseline;
|
|
tempText.rotation = rotation;
|
|
if (rotation) {
|
|
tempText.rotationCenterX = x;
|
|
tempText.rotationCenterY = y;
|
|
}
|
|
let box = rotation ? Transformable6.toCanvas(tempText) : tempText.getBBox();
|
|
if (box && options.hideWhenNecessary && !rotation) {
|
|
const overflowLeft = seriesLeft - box.x;
|
|
const overflowRight = box.x + box.width - seriesRight;
|
|
const pixelError = 1;
|
|
if (overflowLeft > pixelError || overflowRight > pixelError) {
|
|
const availWidth = box.width - Math.max(overflowLeft, overflowRight);
|
|
const wrapOptions = { maxWidth: availWidth, font: label, textWrap: "never" };
|
|
text2 = wrapTextOrSegments(text2, wrapOptions);
|
|
tempText.text = text2;
|
|
box = tempText.getBBox();
|
|
}
|
|
}
|
|
return {
|
|
text: text2,
|
|
x,
|
|
y,
|
|
textAlign,
|
|
textBaseline,
|
|
hidden: text2 === "" || (datum.hidden ?? isLastTickOverFirst),
|
|
rotation,
|
|
box
|
|
};
|
|
});
|
|
if (label.avoidCollisions) {
|
|
this.avoidLabelCollisions(labelData);
|
|
}
|
|
return labelData;
|
|
}
|
|
computeLabelsBBox(options, seriesRect) {
|
|
this.labelData = this.createLabelNodeData(this.tickData, options, seriesRect);
|
|
const textBoxes = this.labelData.map(({ box }) => box).filter((box) => box != null);
|
|
if (!this.label.enabled || textBoxes.length === 0) {
|
|
return null;
|
|
}
|
|
return BBox27.merge(textBoxes);
|
|
}
|
|
getLabelOrientation() {
|
|
const { label } = this;
|
|
return label instanceof AngleAxisLabel ? label.orientation : "fixed";
|
|
}
|
|
getLabelRotation(tickAngle) {
|
|
let rotation = toRadians(this.label.rotation ?? 0);
|
|
tickAngle = normalizeAngle360(tickAngle);
|
|
const orientation = this.getLabelOrientation();
|
|
if (orientation === "parallel") {
|
|
rotation += tickAngle;
|
|
if (tickAngle >= 0 && tickAngle < Math.PI) {
|
|
rotation -= Math.PI / 2;
|
|
} else {
|
|
rotation += Math.PI / 2;
|
|
}
|
|
} else if (orientation === "perpendicular") {
|
|
rotation += tickAngle;
|
|
if (tickAngle >= Math.PI / 2 && tickAngle < 1.5 * Math.PI) {
|
|
rotation += Math.PI;
|
|
}
|
|
}
|
|
return rotation;
|
|
}
|
|
getLabelAlign(tickAngle) {
|
|
const cos = Math.cos(tickAngle);
|
|
const sin = Math.sin(tickAngle);
|
|
let textAlign;
|
|
let textBaseline;
|
|
const orientation = this.getLabelOrientation();
|
|
const isCos0 = isNumberEqual(cos, 0);
|
|
const isSin0 = isNumberEqual(sin, 0);
|
|
const isCos1 = isNumberEqual(cos, 1);
|
|
const isSinMinus1 = isNumberEqual(sin, -1);
|
|
const isCosPositive = cos > 0 && !isCos0;
|
|
const isSinPositive = sin > 0 && !isSin0;
|
|
if (orientation === "parallel") {
|
|
textAlign = "center";
|
|
textBaseline = isCos1 && isSin0 || isSinPositive ? "top" : "bottom";
|
|
} else if (orientation === "perpendicular") {
|
|
textAlign = isSinMinus1 || isCosPositive ? "left" : "right";
|
|
textBaseline = "middle";
|
|
} else {
|
|
textAlign = "right";
|
|
if (isCos0) {
|
|
textAlign = "center";
|
|
} else if (isCosPositive) {
|
|
textAlign = "left";
|
|
}
|
|
textBaseline = "bottom";
|
|
if (isSin0) {
|
|
textBaseline = "middle";
|
|
} else if (isSinPositive) {
|
|
textBaseline = "top";
|
|
}
|
|
}
|
|
return { textAlign, textBaseline };
|
|
}
|
|
updateCrossLines() {
|
|
const { shape, gridLength: radius, innerRadiusRatio } = this;
|
|
for (const crossLine of this.crossLines) {
|
|
if (crossLine instanceof AngleCrossLine) {
|
|
crossLine.ticks = this.tickData.map((t) => t.value);
|
|
crossLine.shape = shape;
|
|
crossLine.axisOuterRadius = radius;
|
|
crossLine.axisInnerRadius = radius * innerRadiusRatio;
|
|
}
|
|
}
|
|
super.updateCrossLines();
|
|
}
|
|
};
|
|
AngleAxis.CrossLineConstructor = AngleCrossLine;
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AngleAxis.prototype, "startAngle", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AngleAxis.prototype, "endAngle", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/axes/angle-category/angleCategoryAxis.ts
|
|
var { CategoryScale: CategoryScale2 } = module_support_exports;
|
|
var AngleCategoryAxis = class extends AngleAxis {
|
|
constructor(moduleCtx) {
|
|
super(moduleCtx, new CategoryScale2());
|
|
this.groupPaddingInner = 0;
|
|
this.paddingInner = 0;
|
|
this.interval = new AngleAxisInterval();
|
|
}
|
|
hasDefinedDomain() {
|
|
return false;
|
|
}
|
|
generateAngleTicks(domain) {
|
|
const { scale: scale2, gridLength: radius } = this;
|
|
const { values, minSpacing } = this.interval;
|
|
const tickParams = {
|
|
nice: [this.nice, this.nice],
|
|
interval: void 0,
|
|
tickCount: void 0,
|
|
minTickCount: 0,
|
|
maxTickCount: Infinity
|
|
};
|
|
const ticks = values ?? scale2.ticks(tickParams, domain)?.ticks ?? [];
|
|
if (ticks.length < 2 || minSpacing == null) {
|
|
return ticks.map((value) => {
|
|
return { value, visible: true };
|
|
});
|
|
}
|
|
const startTick = ticks[0];
|
|
const startAngle = scale2.convert(startTick);
|
|
const startX = radius * Math.cos(startAngle);
|
|
const startY = radius * Math.sin(startAngle);
|
|
for (let step = 1; step < ticks.length - 1; step++) {
|
|
const nextTick = ticks[step];
|
|
const nextAngle = scale2.convert(nextTick);
|
|
if (nextAngle - startAngle > Math.PI) {
|
|
break;
|
|
}
|
|
const nextX = radius * Math.cos(nextAngle);
|
|
const nextY = radius * Math.sin(nextAngle);
|
|
const spacing = Math.hypot(nextX - startX, nextY - startY);
|
|
if (spacing > minSpacing) {
|
|
const visibleTicks = /* @__PURE__ */ new Set([startTick]);
|
|
walkPairsOutward(ticks, step, (_, next) => {
|
|
visibleTicks.add(next);
|
|
});
|
|
return ticks.map((value) => {
|
|
const visible = visibleTicks.has(value);
|
|
return { value, visible };
|
|
});
|
|
}
|
|
}
|
|
return [{ value: startTick, visible: true }];
|
|
}
|
|
avoidLabelCollisions(labelData) {
|
|
const { minSpacing } = this.label;
|
|
if (labelData.length < 3)
|
|
return;
|
|
const labelsCollide = (prev, next) => {
|
|
if (prev.hidden || next.hidden) {
|
|
return false;
|
|
} else if (minSpacing == null) {
|
|
return prev.box.collidesBBox(next.box);
|
|
}
|
|
const prevBox = prev.box.clone().grow(minSpacing / 2);
|
|
const nextBox = next.box.clone().grow(minSpacing / 2);
|
|
return prevBox.collidesBBox(nextBox);
|
|
};
|
|
const firstLabel = labelData[0];
|
|
const lastLabel = labelData.at(-1);
|
|
const visibleLabels = /* @__PURE__ */ new Set([firstLabel]);
|
|
const lastLabelIsOverFirst = isNumberEqual(firstLabel.x, lastLabel.x) && isNumberEqual(firstLabel.y, lastLabel.y);
|
|
const maxStep = Math.floor(labelData.length / 2);
|
|
for (let step = 1; step <= maxStep; step++) {
|
|
const labels = lastLabelIsOverFirst ? labelData.slice(0, -1) : labelData;
|
|
const collisionDetected = walkPairsOutward(labels, step, labelsCollide);
|
|
if (!collisionDetected) {
|
|
walkPairsOutward(labels, step, (_, next) => {
|
|
visibleLabels.add(next);
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
for (const datum of labelData) {
|
|
if (!visibleLabels.has(datum)) {
|
|
datum.hidden = true;
|
|
datum.box = void 0;
|
|
}
|
|
}
|
|
}
|
|
tickFormatParams() {
|
|
return { type: "category" };
|
|
}
|
|
datumFormatParams(value, params) {
|
|
const { datum, seriesId, legendItemName, key, source, property, domain, boundSeries } = params;
|
|
return { type: "category", value, datum, seriesId, legendItemName, key, source, property, domain, boundSeries };
|
|
}
|
|
};
|
|
AngleCategoryAxis.className = "AngleCategoryAxis";
|
|
AngleCategoryAxis.type = "angle-category";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AngleCategoryAxis.prototype, "groupPaddingInner", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AngleCategoryAxis.prototype, "paddingInner", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AngleCategoryAxis.prototype, "interval", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/axes/angle-category/angleCategoryAxisModule.ts
|
|
var AngleCategoryAxisModule = {
|
|
type: "axis",
|
|
name: "angle-category",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: module_support_exports.angleCategoryAxisOptionsDefs,
|
|
themeTemplate: {
|
|
label: { spacing: 5 },
|
|
gridLine: { enabled: false },
|
|
shape: { $findFirstSiblingNotOperation: void 0 }
|
|
},
|
|
create: (ctx) => new AngleCategoryAxis(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/axes/angle-number/angleNumberAxis.ts
|
|
var AngleNumberAxis = class extends AngleAxis {
|
|
constructor(moduleCtx) {
|
|
super(moduleCtx, new LinearAngleScale());
|
|
this.shape = "circle";
|
|
this.interval = new AngleAxisInterval();
|
|
}
|
|
hasDefinedDomain() {
|
|
const { min, max } = this;
|
|
return min != null && max != null && min < max;
|
|
}
|
|
normaliseDataDomain(d) {
|
|
const { min, max, preferredMin, preferredMax } = this;
|
|
const { extent: extent2, clipped } = normalisedExtentWithMetadata(
|
|
d.domain,
|
|
min,
|
|
max,
|
|
preferredMin,
|
|
preferredMax,
|
|
void 0,
|
|
d.sortMetadata?.sortOrder
|
|
);
|
|
return { domain: extent2, clipped };
|
|
}
|
|
getDomainExtentsNice() {
|
|
return [this.min == null && this.nice, this.max == null && this.nice];
|
|
}
|
|
updateScale() {
|
|
super.updateScale();
|
|
this.scale.arcLength = this.getRangeArcLength();
|
|
}
|
|
getRangeArcLength() {
|
|
const { range: requestedRange } = this;
|
|
const min = Math.min(...requestedRange);
|
|
const max = Math.max(...requestedRange);
|
|
const rotation = angleBetween(min, max) || 2 * Math.PI;
|
|
const radius = this.gridLength;
|
|
return rotation * radius;
|
|
}
|
|
generateAngleTicks(domain) {
|
|
const { scale: scale2, range: requestedRange, nice } = this;
|
|
const { values, step, minSpacing, maxSpacing } = this.interval;
|
|
let rawTicks;
|
|
if (values == null) {
|
|
const { arcLength } = scale2;
|
|
const minTickCount = maxSpacing ? Math.floor(arcLength / maxSpacing) : 1;
|
|
const maxTickCount = minSpacing ? Math.floor(arcLength / minSpacing) : Infinity;
|
|
const preferredTickCount = Math.floor(4 / Math.PI * Math.abs(requestedRange[0] - requestedRange[1]));
|
|
const tickCount = Math.max(minTickCount, Math.min(maxTickCount, preferredTickCount));
|
|
const tickParams = {
|
|
nice: [nice, nice],
|
|
interval: step,
|
|
tickCount,
|
|
minTickCount,
|
|
maxTickCount
|
|
};
|
|
rawTicks = scale2.ticks(tickParams, domain)?.ticks ?? [];
|
|
} else {
|
|
const [d0, d1] = findMinMax(domain.map(Number));
|
|
rawTicks = values.filter((value) => value >= d0 && value <= d1).sort((a, b) => a - b);
|
|
}
|
|
return rawTicks.map((value) => ({ value, visible: true }));
|
|
}
|
|
avoidLabelCollisions(labelData) {
|
|
const { minSpacing } = this.label;
|
|
const labelsCollide = (prev, next) => {
|
|
if (prev.hidden || next.hidden) {
|
|
return false;
|
|
} else if (minSpacing == null) {
|
|
return prev.box.collidesBBox(next.box);
|
|
}
|
|
const prevBox = prev.box.clone().grow(minSpacing / 2);
|
|
const nextBox = next.box.clone().grow(minSpacing / 2);
|
|
return prevBox.collidesBBox(nextBox);
|
|
};
|
|
const firstLabel = labelData[0];
|
|
const lastLabel = labelData.at(-1);
|
|
if (firstLabel !== lastLabel && isNumberEqual(firstLabel.x, lastLabel.x) && isNumberEqual(firstLabel.y, lastLabel.y)) {
|
|
lastLabel.hidden = true;
|
|
}
|
|
for (let step = 1; step < labelData.length; step *= 2) {
|
|
let collisionDetected = false;
|
|
for (let i = step; i < labelData.length; i += step) {
|
|
const next = labelData[i];
|
|
const prev = labelData[i - step];
|
|
if (labelsCollide(prev, next)) {
|
|
collisionDetected = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!collisionDetected) {
|
|
for (const [i, datum] of labelData.entries()) {
|
|
if (i % step > 0) {
|
|
datum.hidden = true;
|
|
datum.box = void 0;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
for (const [i, datum] of labelData.entries()) {
|
|
if (i > 0) {
|
|
datum.hidden = true;
|
|
datum.box = void 0;
|
|
}
|
|
}
|
|
}
|
|
tickFormatParams(_domain, _ticks, fractionDigits) {
|
|
return { type: "number", visibleDomain: void 0, fractionDigits };
|
|
}
|
|
datumFormatParams(value, params, fractionDigits) {
|
|
const { datum, seriesId, legendItemName, key, source, property, domain, boundSeries } = params;
|
|
return {
|
|
type: "number",
|
|
value,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key,
|
|
source,
|
|
property,
|
|
domain,
|
|
boundSeries,
|
|
fractionDigits,
|
|
visibleDomain: void 0
|
|
};
|
|
}
|
|
};
|
|
AngleNumberAxis.className = "AngleNumberAxis";
|
|
AngleNumberAxis.type = "angle-number";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AngleNumberAxis.prototype, "min", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AngleNumberAxis.prototype, "max", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AngleNumberAxis.prototype, "preferredMin", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AngleNumberAxis.prototype, "preferredMax", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], AngleNumberAxis.prototype, "interval", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/axes/angle-number/angleNumberAxisModule.ts
|
|
var AngleNumberAxisModule = {
|
|
type: "axis",
|
|
name: "angle-number",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: module_support_exports.angleNumberAxisOptionsDefs,
|
|
themeTemplate: {
|
|
label: { spacing: 5 },
|
|
gridLine: { enabled: false }
|
|
},
|
|
create: (ctx) => new AngleNumberAxis(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/axes/polar-crosslines/radiusCrossLine.ts
|
|
var { validateCrossLineValue: validateCrossLineValue3, Group: Group18, Path: Path11, Sector: Sector6, RotatableText: RotatableText4 } = module_support_exports;
|
|
var RadiusCrossLineLabel = class extends PolarCrossLineLabel {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.positionAngle = void 0;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadiusCrossLineLabel.prototype, "positionAngle", 2);
|
|
var RadiusCrossLine = class extends PolarCrossLine {
|
|
constructor() {
|
|
super();
|
|
this.direction = "radius" /* Radius */;
|
|
this.label = new RadiusCrossLineLabel();
|
|
this.polygonNode = new Path11();
|
|
this.sectorNode = new Sector6();
|
|
this.crossLineRange = new Group18();
|
|
this.labelNode = new RotatableText4();
|
|
this.outerRadius = 0;
|
|
this.innerRadius = 0;
|
|
this.crossLineRange.append(this.polygonNode);
|
|
this.crossLineRange.append(this.sectorNode);
|
|
this.labelGroup.append(this.labelNode);
|
|
}
|
|
update(visible) {
|
|
const { scale: scale2 } = this;
|
|
if (!scale2 || !validateCrossLineValue3(this, scale2)) {
|
|
this.rangeGroup.visible = false;
|
|
this.lineGroup.visible = false;
|
|
this.labelGroup.visible = false;
|
|
return;
|
|
}
|
|
this.updateRadii();
|
|
const { innerRadius, outerRadius } = this;
|
|
visible && (visible = innerRadius >= this.axisInnerRadius && outerRadius <= this.axisOuterRadius);
|
|
this.rangeGroup.visible = visible;
|
|
this.lineGroup.visible = visible;
|
|
this.labelGroup.visible = visible;
|
|
this.updatePolygonNode(visible);
|
|
this.updateSectorNode(visible);
|
|
this.updateLabelNode(visible);
|
|
this.assignCrossLineGroup(this.type === "range", this.crossLineRange);
|
|
}
|
|
updateRadii() {
|
|
const { range: range3, scale: scale2, type, axisInnerRadius, axisOuterRadius } = this;
|
|
if (!scale2)
|
|
return { innerRadius: 0, outerRadius: 0 };
|
|
const getRadius = (value) => axisOuterRadius + axisInnerRadius - value;
|
|
let outerRadius, innerRadius;
|
|
if (type === "line") {
|
|
outerRadius = getRadius(scale2.convert(this.value));
|
|
innerRadius = outerRadius;
|
|
} else {
|
|
const bandwidth = Math.abs(scale2?.bandwidth ?? 0);
|
|
const convertedRange = range3.map((r) => scale2.convert(r));
|
|
outerRadius = getRadius(Math.max(...convertedRange));
|
|
innerRadius = getRadius(Math.min(...convertedRange)) + bandwidth;
|
|
}
|
|
this.outerRadius = outerRadius;
|
|
this.innerRadius = innerRadius;
|
|
}
|
|
drawPolygon(radius, angles, polygon) {
|
|
for (const [index, angle2] of angles.entries()) {
|
|
const x = radius * Math.cos(angle2);
|
|
const y = radius * Math.sin(angle2);
|
|
if (index === 0) {
|
|
polygon.path.moveTo(x, y);
|
|
} else {
|
|
polygon.path.lineTo(x, y);
|
|
}
|
|
}
|
|
polygon.path.closePath();
|
|
}
|
|
updatePolygonNode(visible) {
|
|
const { gridAngles, polygonNode: polygon, scale: scale2, shape, type, innerRadius, outerRadius } = this;
|
|
if (!visible || shape !== "polygon" || !scale2 || !gridAngles) {
|
|
polygon.visible = false;
|
|
return;
|
|
}
|
|
polygon.visible = true;
|
|
const padding2 = this.getPadding();
|
|
polygon.path.clear(true);
|
|
this.drawPolygon(outerRadius - padding2, gridAngles, polygon);
|
|
const reversedAngles = gridAngles.slice().reverse();
|
|
const innerPolygonRadius = type === "line" ? outerRadius - padding2 : innerRadius + padding2;
|
|
this.drawPolygon(innerPolygonRadius, reversedAngles, polygon);
|
|
this.setSectorNodeProps(polygon);
|
|
}
|
|
updateSectorNode(visible) {
|
|
const { axisInnerRadius, axisOuterRadius, scale: scale2, sectorNode: sector, shape, innerRadius, outerRadius } = this;
|
|
if (!visible || shape !== "circle" || !scale2) {
|
|
sector.visible = false;
|
|
return;
|
|
}
|
|
sector.visible = true;
|
|
sector.startAngle = 0;
|
|
sector.endAngle = 2 * Math.PI;
|
|
const padding2 = this.getPadding();
|
|
const r0 = clamp(axisInnerRadius, innerRadius + padding2, axisOuterRadius);
|
|
const r1 = clamp(axisInnerRadius, outerRadius - padding2, axisOuterRadius);
|
|
sector.innerRadius = Math.min(r0, r1);
|
|
sector.outerRadius = Math.max(r0, r1);
|
|
this.setSectorNodeProps(sector);
|
|
}
|
|
updateLabelNode(visible) {
|
|
const { innerRadius, label, labelNode: node, scale: scale2, shape, type } = this;
|
|
if (!visible || label.enabled === false || !label.text || !scale2) {
|
|
node.visible = false;
|
|
return;
|
|
}
|
|
const angle2 = normalizeAngle360FromDegrees((label.positionAngle ?? 0) - 90);
|
|
const isBottomSide = (isNumberEqual(angle2, 0) || angle2 > 0) && angle2 < Math.PI;
|
|
const rotation = isBottomSide ? angle2 - Math.PI / 2 : angle2 + Math.PI / 2;
|
|
let distance2;
|
|
const angles = this.gridAngles ?? [];
|
|
if (type === "line") {
|
|
distance2 = innerRadius + label.padding;
|
|
} else if (shape === "circle" || angles.length < 3) {
|
|
distance2 = innerRadius - label.padding;
|
|
} else {
|
|
distance2 = innerRadius * Math.cos(Math.PI / angles.length) - label.padding;
|
|
}
|
|
const labelX = distance2 * Math.cos(angle2);
|
|
const labelY = distance2 * Math.sin(angle2);
|
|
let textBaseline;
|
|
if (type === "line") {
|
|
textBaseline = isBottomSide ? "top" : "bottom";
|
|
} else {
|
|
textBaseline = isBottomSide ? "bottom" : "top";
|
|
}
|
|
this.setLabelNodeProps(node, labelX, labelY, textBaseline, rotation);
|
|
}
|
|
getPadding() {
|
|
const { scale: scale2 } = this;
|
|
if (!scale2)
|
|
return 0;
|
|
const bandwidth = Math.abs(scale2.bandwidth ?? 0);
|
|
const step = Math.abs(scale2.step ?? 0);
|
|
return scale2 instanceof module_support_exports.BandScale ? (step - bandwidth) / 2 : 0;
|
|
}
|
|
};
|
|
RadiusCrossLine.className = "RadiusCrossLine";
|
|
|
|
// packages/ag-charts-enterprise/src/axes/radius/radiusAxis.ts
|
|
var { Caption: Caption2, Group: Group19, TransformableGroup: TransformableGroup2, Path: Path12, Line: Line6, Selection: Selection12, generateTicks: generateTicks3, AxisGroupZIndexMap: AxisGroupZIndexMap2 } = module_support_exports;
|
|
var RadiusAxisLabel = class extends module_support_exports.AxisLabel {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.autoRotateAngle = 335;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadiusAxisLabel.prototype, "autoRotate", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadiusAxisLabel.prototype, "autoRotateAngle", 2);
|
|
var RadiusAxis = class extends module_support_exports.PolarAxis {
|
|
constructor(moduleCtx, scale2) {
|
|
super(moduleCtx, scale2);
|
|
this.positionAngle = 0;
|
|
this.gridLineGroupSelection = Selection12.select(
|
|
this.gridLineGroup,
|
|
Line6,
|
|
false
|
|
);
|
|
this.generatedTicks = void 0;
|
|
this.headingLabelGroup = this.axisGroup.appendChild(
|
|
new TransformableGroup2({ name: `${this.id}-Axis-heading` })
|
|
);
|
|
this.lineNodeGroup = this.axisGroup.appendChild(
|
|
new TransformableGroup2({ name: `${this.id}-Axis-line` })
|
|
);
|
|
this.lineNode = this.lineNodeGroup.appendChild(
|
|
new Line6({
|
|
name: `${this.id}-Axis-line`,
|
|
zIndex: AxisGroupZIndexMap2.AxisLine
|
|
})
|
|
);
|
|
this.gridPathGroup = this.gridGroup.appendChild(
|
|
new Group19({
|
|
name: `${this.id}-gridPaths`,
|
|
zIndex: 2 /* AXIS_GRID */
|
|
})
|
|
);
|
|
this.gridPathSelection = Selection12.select(this.gridPathGroup, Path12);
|
|
this.headingLabelGroup.appendChild(this.title.caption.node);
|
|
this.cleanup.register(this.title.caption.registerInteraction(this.moduleCtx, "afterend"));
|
|
}
|
|
get direction() {
|
|
return "radius" /* Radius */;
|
|
}
|
|
getAxisTransform() {
|
|
const maxRadius = this.scale.range[0];
|
|
const { translation, positionAngle, innerRadiusRatio } = this;
|
|
const innerRadius = maxRadius * innerRadiusRatio;
|
|
const rotation = toRadians(positionAngle);
|
|
return {
|
|
translationX: translation.x,
|
|
translationY: translation.y - maxRadius - innerRadius,
|
|
rotation,
|
|
rotationCenterX: 0,
|
|
rotationCenterY: maxRadius + innerRadius
|
|
};
|
|
}
|
|
update() {
|
|
super.update();
|
|
this.updateTitle();
|
|
this.updateGridLines();
|
|
const { enabled, stroke: stroke3, width: width2 } = this.line;
|
|
this.lineNode.setProperties({
|
|
stroke: stroke3,
|
|
strokeWidth: enabled ? width2 : 0,
|
|
x1: 0,
|
|
y1: this.range[0],
|
|
x2: 0,
|
|
y2: this.range[1]
|
|
});
|
|
}
|
|
updatePosition() {
|
|
super.updatePosition();
|
|
const axisTransform = this.getAxisTransform();
|
|
this.tickLineGroup.setProperties(axisTransform);
|
|
this.tickLabelGroup.setProperties(axisTransform);
|
|
this.lineNodeGroup.setProperties(axisTransform);
|
|
this.headingLabelGroup.setProperties(axisTransform);
|
|
}
|
|
calculateRotations() {
|
|
const rotation = 0;
|
|
const parallelFlipRotation = 0;
|
|
const regularFlipRotation = -Math.PI / 2;
|
|
return { rotation, parallelFlipRotation, regularFlipRotation };
|
|
}
|
|
calculateTickLayout(domain, niceMode, _visibleRange) {
|
|
const visibleRange = [0, 1];
|
|
const sideFlag = this.label.getSideFlag();
|
|
const labelX = sideFlag * (this.getTickSize() + this.label.spacing + this.seriesAreaPadding);
|
|
const { range: range3, reverse, defaultTickMinSpacing } = this;
|
|
const tickGenerationResult = generateTicks3({
|
|
scale: this.scale,
|
|
label: this.label,
|
|
interval: this.interval,
|
|
tickFormatter: (...args) => this.tickFormatter(...args),
|
|
domain,
|
|
range: range3,
|
|
reverse,
|
|
niceMode,
|
|
visibleRange,
|
|
defaultTickMinSpacing,
|
|
labelOffset: labelX,
|
|
sideFlag,
|
|
axisRotation: 0,
|
|
sizeLimit: void 0,
|
|
primaryTickCount: void 0
|
|
});
|
|
const { tickData } = tickGenerationResult;
|
|
const { ticks, rawTicks, rawTickCount, tickDomain, fractionDigits, niceDomain = domain } = tickData;
|
|
const labels = ticks.map((d) => this.getTickLabelProps(d, tickGenerationResult));
|
|
this.generatedTicks = { ticks, labels };
|
|
return { ticks: rawTicks, tickDomain, niceDomain, rawTickCount, fractionDigits, timeInterval: void 0 };
|
|
}
|
|
updateSelections() {
|
|
const { generatedTicks } = this;
|
|
if (!generatedTicks)
|
|
return;
|
|
const { ticks, labels } = generatedTicks;
|
|
this.gridLineGroupSelection.update(this.gridLength ? ticks : []);
|
|
this.tickLabelGroupSelection.update(labels);
|
|
this.gridPathSelection.update(this.gridLine.enabled ? this.prepareGridPathTickData(ticks) : []);
|
|
this.gridLineGroupSelection.cleanup();
|
|
this.tickLabelGroupSelection.cleanup();
|
|
this.gridPathSelection.cleanup();
|
|
}
|
|
// TODO - abstract out
|
|
updateLabels() {
|
|
if (!this.label.enabled)
|
|
return;
|
|
const axisLabelPositionFn = module_support_exports.resetAxisLabelSelectionFn();
|
|
this.tickLabelGroupSelection.each((node, datum) => {
|
|
node.fill = datum.color;
|
|
node.text = datum.text;
|
|
node.textBaseline = datum.textBaseline;
|
|
node.textAlign = datum.textAlign ?? "center";
|
|
node.setFont(datum);
|
|
node.setBoxing(datum);
|
|
node.setProperties(axisLabelPositionFn(node, datum));
|
|
});
|
|
}
|
|
updateGridLines() {
|
|
const {
|
|
gridLine: { style: style2, width: width2 },
|
|
shape,
|
|
generatedTicks
|
|
} = this;
|
|
if (!style2 || !generatedTicks) {
|
|
return;
|
|
}
|
|
const styleCount = style2.length;
|
|
const setStyle = (node, index) => {
|
|
const { stroke: stroke3, lineDash } = style2[index % styleCount];
|
|
node.stroke = stroke3;
|
|
node.strokeWidth = width2;
|
|
node.lineDash = lineDash;
|
|
node.fill = void 0;
|
|
};
|
|
const [startAngle, endAngle] = this.gridRange ?? [0, 2 * Math.PI];
|
|
const isFullCircle = isNumberEqual(endAngle - startAngle, 2 * Math.PI);
|
|
const drawCircleShape = (node, value) => {
|
|
const { path } = node;
|
|
path.clear(true);
|
|
const radius = this.getTickRadius(value);
|
|
if (isFullCircle) {
|
|
path.moveTo(radius, 0);
|
|
path.arc(0, 0, radius, 0, 2 * Math.PI);
|
|
} else {
|
|
path.moveTo(radius * Math.cos(startAngle), radius * Math.sin(startAngle));
|
|
path.arc(0, 0, radius, normalizeAngle360(startAngle), normalizeAngle360(endAngle));
|
|
}
|
|
if (isFullCircle) {
|
|
path.closePath();
|
|
}
|
|
};
|
|
const drawPolygonShape = (node, value) => {
|
|
const { path } = node;
|
|
const angles = this.gridAngles;
|
|
path.clear(true);
|
|
if (!angles || angles.length < 3) {
|
|
return;
|
|
}
|
|
const radius = this.getTickRadius(value);
|
|
for (const [idx, angle2] of angles.entries()) {
|
|
const x = radius * Math.cos(angle2);
|
|
const y = radius * Math.sin(angle2);
|
|
if (idx === 0) {
|
|
path.moveTo(x, y);
|
|
} else {
|
|
path.lineTo(x, y);
|
|
}
|
|
for (const [innerIdx, innerAngle] of angles.entries()) {
|
|
const x2 = radius * Math.cos(innerAngle);
|
|
const y2 = radius * Math.sin(innerAngle);
|
|
if (innerIdx === 0) {
|
|
path.moveTo(x2, y2);
|
|
} else {
|
|
path.lineTo(x2, y2);
|
|
}
|
|
}
|
|
path.closePath();
|
|
}
|
|
path.closePath();
|
|
};
|
|
const drawFn = shape === "circle" ? drawCircleShape : drawPolygonShape;
|
|
this.gridPathSelection.each((node, value, index) => {
|
|
setStyle(node, index);
|
|
drawFn(node, value);
|
|
});
|
|
}
|
|
updateTitle() {
|
|
const identityFormatter = (params) => params.defaultValue;
|
|
const { title, range: requestedRange } = this;
|
|
const { formatter: formatter2 = identityFormatter } = this.title;
|
|
title.caption.enabled = title.enabled;
|
|
title.caption.fontFamily = title.fontFamily;
|
|
title.caption.fontSize = title.fontSize;
|
|
title.caption.fontStyle = title.fontStyle;
|
|
title.caption.fontWeight = title.fontWeight;
|
|
title.caption.color = title.color;
|
|
title.caption.wrapping = title.wrapping;
|
|
let titleVisible = false;
|
|
const titleNode = title.caption.node;
|
|
if (title.enabled) {
|
|
titleVisible = true;
|
|
titleNode.rotation = Math.PI / 2;
|
|
titleNode.x = Math.floor((requestedRange[0] + requestedRange[1]) / 2);
|
|
titleNode.y = -Caption2.SMALL_PADDING;
|
|
titleNode.textAlign = "center";
|
|
titleNode.textBaseline = "bottom";
|
|
titleNode.text = this.cachedCallWithContext(formatter2, this.getTitleFormatterParams(this.scale.domain));
|
|
}
|
|
titleNode.visible = titleVisible;
|
|
}
|
|
updateCrossLines() {
|
|
for (const crossLine of this.crossLines) {
|
|
if (crossLine instanceof RadiusCrossLine) {
|
|
const { shape, gridAngles, range: range3, innerRadiusRatio } = this;
|
|
const radius = range3[0];
|
|
crossLine.shape = shape;
|
|
crossLine.gridAngles = gridAngles;
|
|
crossLine.axisOuterRadius = radius;
|
|
crossLine.axisInnerRadius = radius * innerRadiusRatio;
|
|
}
|
|
}
|
|
super.updateCrossLines();
|
|
}
|
|
createLabel() {
|
|
return new RadiusAxisLabel();
|
|
}
|
|
// TODO - abstract out (shared with cartesian axis)
|
|
getTickLabelProps(datum, tickGenerationResult) {
|
|
const { label } = this;
|
|
const { rotation, textBaseline, textAlign } = tickGenerationResult;
|
|
const range3 = this.scale.range;
|
|
const text2 = datum.tickLabel ?? "";
|
|
const sideFlag = label.getSideFlag();
|
|
const labelX = sideFlag * (this.getTickSize() + label.spacing + this.seriesAreaPadding);
|
|
const visible = text2 !== "" && text2 != null;
|
|
const combinedRotation = rotation;
|
|
return {
|
|
...this.getLabelStyles({ value: datum.tick, formattedValue: datum.tickLabel }),
|
|
tickId: datum.tickId,
|
|
rotation: combinedRotation,
|
|
text: text2,
|
|
textAlign,
|
|
textBaseline,
|
|
visible,
|
|
x: labelX,
|
|
y: datum.translation,
|
|
rotationCenterX: labelX,
|
|
rotationCenterY: datum.translation,
|
|
range: range3
|
|
};
|
|
}
|
|
};
|
|
RadiusAxis.CrossLineConstructor = RadiusCrossLine;
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadiusAxis.prototype, "positionAngle", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/axes/radius-category/radiusCategoryAxis.ts
|
|
var { CategoryScale: CategoryScale3 } = module_support_exports;
|
|
var RadiusCategoryAxis = class extends RadiusAxis {
|
|
constructor(moduleCtx) {
|
|
super(moduleCtx, new CategoryScale3());
|
|
this.shape = "circle";
|
|
this.groupPaddingInner = 0;
|
|
this.paddingInner = 0;
|
|
this.paddingOuter = 0;
|
|
}
|
|
hasDefinedDomain() {
|
|
return false;
|
|
}
|
|
normaliseDataDomain(d) {
|
|
return { domain: d.domain, clipped: false };
|
|
}
|
|
prepareGridPathTickData(data) {
|
|
return data.slice().reverse();
|
|
}
|
|
getTickRadius(tickDatum) {
|
|
const { scale: scale2, innerRadiusRatio } = this;
|
|
const maxRadius = scale2.range[0];
|
|
const minRadius = maxRadius * innerRadiusRatio;
|
|
if (CategoryScale3.is(scale2)) {
|
|
const ticks = scale2.domain;
|
|
const index = ticks.length - 1 - ticks.indexOf(tickDatum.tick);
|
|
return index === 0 ? minRadius : scale2.inset + scale2.step * (index - 0.5) + scale2.bandwidth / 2;
|
|
} else {
|
|
const tickRange = (maxRadius - minRadius) / scale2.domain.length;
|
|
return maxRadius - tickDatum.translation + minRadius - tickRange / 2;
|
|
}
|
|
}
|
|
tickFormatParams() {
|
|
return { type: "category" };
|
|
}
|
|
datumFormatParams(value, params) {
|
|
const { datum, seriesId, legendItemName, key, source, property, domain, boundSeries } = params;
|
|
return { type: "category", value, datum, seriesId, legendItemName, key, source, property, domain, boundSeries };
|
|
}
|
|
};
|
|
RadiusCategoryAxis.className = "RadiusCategoryAxis";
|
|
RadiusCategoryAxis.type = "radius-category";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadiusCategoryAxis.prototype, "groupPaddingInner", 2);
|
|
__decorateClass([
|
|
ProxyPropertyOnWrite("scale", "paddingInner"),
|
|
addFakeTransformToInstanceProperty
|
|
], RadiusCategoryAxis.prototype, "paddingInner", 2);
|
|
__decorateClass([
|
|
ProxyPropertyOnWrite("scale", "paddingOuter"),
|
|
addFakeTransformToInstanceProperty
|
|
], RadiusCategoryAxis.prototype, "paddingOuter", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/axes/radius-category/radiusCategoryAxisModule.ts
|
|
var RadiusCategoryAxisModule = {
|
|
type: "axis",
|
|
name: "radius-category",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: module_support_exports.radiusCategoryAxisOptionsDefs,
|
|
themeTemplate: {
|
|
positionAngle: 0,
|
|
line: { enabled: false },
|
|
label: { minSpacing: 5 }
|
|
},
|
|
create: (ctx) => new RadiusCategoryAxis(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/axes/radius-number/radiusNumberAxis.ts
|
|
var { LinearScale: LinearScale5 } = module_support_exports;
|
|
var RadiusNumberAxis = class extends RadiusAxis {
|
|
constructor(moduleCtx) {
|
|
super(moduleCtx, new LinearScale5());
|
|
this.shape = "polygon";
|
|
}
|
|
hasDefinedDomain() {
|
|
const { min, max } = this;
|
|
return min != null && max != null && min < max;
|
|
}
|
|
prepareGridPathTickData(data) {
|
|
const { scale: scale2 } = this;
|
|
const domainTop = scale2.domain[1];
|
|
return data.filter(({ tick }) => tick !== domainTop).sort((a, b) => b.tick - a.tick);
|
|
}
|
|
getTickRadius(tickDatum) {
|
|
const { scale: scale2 } = this;
|
|
const maxRadius = scale2.range[0];
|
|
const minRadius = maxRadius * this.innerRadiusRatio;
|
|
return maxRadius - tickDatum.translation + minRadius;
|
|
}
|
|
normaliseDataDomain(d) {
|
|
const { min, max, preferredMin, preferredMax } = this;
|
|
const { extent: extent2, clipped } = normalisedExtentWithMetadata(
|
|
d.domain,
|
|
min,
|
|
max,
|
|
preferredMin,
|
|
preferredMax,
|
|
void 0,
|
|
d.sortMetadata?.sortOrder
|
|
);
|
|
return { domain: extent2, clipped };
|
|
}
|
|
getDomainExtentsNice() {
|
|
return [this.min == null && this.nice, this.max == null && this.nice];
|
|
}
|
|
tickFormatParams(_domain, _ticks, fractionDigits) {
|
|
return { type: "number", visibleDomain: void 0, fractionDigits };
|
|
}
|
|
datumFormatParams(value, params, fractionDigits) {
|
|
const { datum, seriesId, legendItemName, key, source, property, domain, boundSeries } = params;
|
|
return {
|
|
type: "number",
|
|
value,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key,
|
|
source,
|
|
property,
|
|
domain,
|
|
boundSeries,
|
|
fractionDigits,
|
|
visibleDomain: void 0
|
|
};
|
|
}
|
|
};
|
|
RadiusNumberAxis.className = "RadiusNumberAxis";
|
|
RadiusNumberAxis.type = "radius-number";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadiusNumberAxis.prototype, "min", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadiusNumberAxis.prototype, "max", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadiusNumberAxis.prototype, "preferredMin", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadiusNumberAxis.prototype, "preferredMax", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/axes/radius-number/radiusNumberAxisModule.ts
|
|
var RadiusNumberAxisModule = {
|
|
type: "axis",
|
|
name: "radius-number",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: module_support_exports.radiusNumberAxisOptionsDefs,
|
|
themeTemplate: {
|
|
positionAngle: 0,
|
|
line: { enabled: false },
|
|
shape: { $findFirstSiblingNotOperation: void 0 },
|
|
label: { minSpacing: 5 }
|
|
},
|
|
create: (ctx) => new RadiusNumberAxis(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/util/radialUtil.ts
|
|
var { createDatumId: createDatumId17, toHighlightString: toHighlightString5 } = module_support_exports;
|
|
function makeStylerParams(series, highlightStateEnum) {
|
|
const { id: seriesId } = series;
|
|
const {
|
|
angleKey,
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
radiusKey,
|
|
stackGroup,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth
|
|
} = series.properties;
|
|
const highlightState = toHighlightString5(highlightStateEnum ?? module_support_exports.HighlightState.None);
|
|
return {
|
|
angleKey,
|
|
cornerRadius,
|
|
fill,
|
|
fillOpacity,
|
|
highlightState,
|
|
lineDash,
|
|
lineDashOffset,
|
|
radiusKey,
|
|
seriesId,
|
|
stackGroup,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth
|
|
};
|
|
}
|
|
function getStyle(series, ignoreStylerCallback, highlightState) {
|
|
const { styler } = series.properties;
|
|
let stylerResult = {};
|
|
if (!ignoreStylerCallback && styler) {
|
|
const stylerParams = makeStylerParams(series, highlightState);
|
|
stylerResult = series.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${series.declarationOrder}`],
|
|
series.cachedCallWithContext(styler, stylerParams) ?? {},
|
|
{ pick: false }
|
|
) ?? {};
|
|
}
|
|
return {
|
|
cornerRadius: stylerResult.cornerRadius ?? series.properties.cornerRadius,
|
|
fill: stylerResult.fill ?? series.properties.fill,
|
|
fillOpacity: stylerResult.fillOpacity ?? series.properties.fillOpacity,
|
|
lineDash: stylerResult.lineDash ?? series.properties.lineDash,
|
|
lineDashOffset: stylerResult.lineDashOffset ?? series.properties.lineDashOffset,
|
|
stroke: stylerResult.stroke ?? series.properties.stroke,
|
|
strokeOpacity: stylerResult.strokeOpacity ?? series.properties.strokeOpacity,
|
|
strokeWidth: stylerResult.strokeWidth ?? series.properties.strokeWidth,
|
|
opacity: 1
|
|
};
|
|
}
|
|
function makeItemStylerParams(series, nodeDatum, isHighlight, style2) {
|
|
const { id: seriesId, properties } = series;
|
|
const { angleKey, radiusKey } = properties;
|
|
const activeHighlight = series.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightStateString = series.getHighlightStateString(activeHighlight, isHighlight, nodeDatum.datumIndex);
|
|
const fill = series.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
seriesId,
|
|
datum: nodeDatum.datum,
|
|
highlightState: highlightStateString,
|
|
angleKey,
|
|
radiusKey,
|
|
...style2,
|
|
fill
|
|
};
|
|
}
|
|
function getItemStyle(series, nodeDatum, isHighlight, highlightState) {
|
|
const { properties } = series;
|
|
const { itemStyler } = properties;
|
|
const highlightStyle = series.getHighlightStyle(isHighlight, nodeDatum?.datumIndex, highlightState);
|
|
const baseStyle = mergeDefaults(highlightStyle, getStyle(series, nodeDatum === void 0, highlightState));
|
|
let style2 = baseStyle;
|
|
if (itemStyler != null && nodeDatum != null) {
|
|
const overrides = series.cachedDatumCallback(
|
|
createDatumId17(series.getDatumId(nodeDatum), isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const params = makeItemStylerParams(series, nodeDatum, isHighlight, style2);
|
|
return series.callWithContext(itemStyler, params);
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(overrides, style2);
|
|
}
|
|
}
|
|
return style2;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-column/radialColumnSeriesBase.ts
|
|
var {
|
|
DEFAULT_POLAR_DIRECTION_KEYS: DEFAULT_POLAR_DIRECTION_KEYS2,
|
|
DEFAULT_POLAR_DIRECTION_NAMES: DEFAULT_POLAR_DIRECTION_NAMES2,
|
|
PolarAxis: PolarAxis2,
|
|
diff: diff7,
|
|
fixNumericExtent: fixNumericExtent9,
|
|
groupAccumulativeValueProperty: groupAccumulativeValueProperty3,
|
|
keyProperty: keyProperty10,
|
|
normaliseGroupTo: normaliseGroupTo2,
|
|
resetLabelFn: resetLabelFn6,
|
|
seriesLabelFadeInAnimation: seriesLabelFadeInAnimation7,
|
|
seriesLabelFadeOutAnimation: seriesLabelFadeOutAnimation2,
|
|
valueProperty: valueProperty13,
|
|
animationValidation: animationValidation8,
|
|
createDatumId: createDatumId18,
|
|
SeriesNodePickMode: SeriesNodePickMode13,
|
|
CategoryScale: CategoryScale4,
|
|
motion: motion7,
|
|
updateLabelNode: updateLabelNode7,
|
|
getItemStyles: getItemStyles4
|
|
} = module_support_exports;
|
|
var RadialColumnSeriesNodeEvent = class extends module_support_exports.SeriesNodeEvent {
|
|
constructor(type, nativeEvent, datum, series) {
|
|
super(type, nativeEvent, datum, series);
|
|
this.angleKey = series.properties.angleKey;
|
|
this.radiusKey = series.properties.radiusKey;
|
|
}
|
|
};
|
|
var RadialColumnSeriesBase = class extends module_support_exports.PolarSeries {
|
|
constructor(moduleCtx, {
|
|
animationResetFns
|
|
}) {
|
|
super({
|
|
moduleCtx,
|
|
categoryKey: "angleValue",
|
|
propertyKeys: DEFAULT_POLAR_DIRECTION_KEYS2,
|
|
propertyNames: DEFAULT_POLAR_DIRECTION_NAMES2,
|
|
canHaveAxes: true,
|
|
pickModes: [SeriesNodePickMode13.NEAREST_NODE, SeriesNodePickMode13.EXACT_SHAPE_MATCH],
|
|
animationResetFns: {
|
|
...animationResetFns,
|
|
label: resetLabelFn6
|
|
}
|
|
});
|
|
this.NodeEvent = RadialColumnSeriesNodeEvent;
|
|
this.groupScale = new CategoryScale4();
|
|
this.circleCache = { r: 0, cx: 0, cy: 0 };
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { dataModel, processedData } = this;
|
|
if (!processedData || !dataModel)
|
|
return { domain: [] };
|
|
if (direction === "angle" /* Angle */) {
|
|
return dataModel.getDomain(this, "angleValue", "key", processedData);
|
|
} else {
|
|
const yExtent = dataModel.getDomain(this, "radiusValue-end", "value", processedData).domain;
|
|
const fixedYExtent = Number.isFinite(yExtent[1] - yExtent[0]) ? [Math.min(yExtent[0], 0), Math.max(yExtent[1], 0)] : [];
|
|
return { domain: fixNumericExtent9(fixedYExtent) };
|
|
}
|
|
}
|
|
async processData(dataController) {
|
|
const { angleKey, radiusKey, normalizedTo } = this.properties;
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
const stackGroupId = this.getStackId();
|
|
const stackGroupTrailingId = `${stackGroupId}-trailing`;
|
|
const extraProps = [];
|
|
if (isDefined(normalizedTo)) {
|
|
extraProps.push(normaliseGroupTo2([stackGroupId, stackGroupTrailingId], Math.abs(normalizedTo)));
|
|
}
|
|
if (this.needsDataModelDiff() && this.processedData) {
|
|
extraProps.push(diff7(this.id, this.processedData));
|
|
}
|
|
if (animationEnabled) {
|
|
extraProps.push(animationValidation8());
|
|
}
|
|
const visibleProps = this.visible ? {} : { forceValue: 0 };
|
|
const radiusScaleType = this.axes["radius" /* Radius */]?.scale.type;
|
|
const angleScaleType = this.axes["angle" /* Angle */]?.scale.type;
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
await this.requestDataModel(dataController, this.data, {
|
|
props: [
|
|
keyProperty10(angleKey, angleScaleType, { id: "angleValue", allowNullKey }),
|
|
valueProperty13(radiusKey, radiusScaleType, {
|
|
id: "radiusValue-raw",
|
|
invalidValue: null,
|
|
...visibleProps
|
|
}),
|
|
...groupAccumulativeValueProperty3(
|
|
radiusKey,
|
|
"normal",
|
|
{
|
|
id: `radiusValue-end`,
|
|
rangeId: `radiusValue-range`,
|
|
invalidValue: null,
|
|
groupId: stackGroupId,
|
|
separateNegative: true,
|
|
...visibleProps
|
|
},
|
|
radiusScaleType
|
|
),
|
|
...groupAccumulativeValueProperty3(
|
|
radiusKey,
|
|
"trailing",
|
|
{
|
|
id: `radiusValue-start`,
|
|
invalidValue: null,
|
|
groupId: stackGroupTrailingId,
|
|
separateNegative: true,
|
|
...visibleProps
|
|
},
|
|
radiusScaleType
|
|
),
|
|
...extraProps
|
|
],
|
|
groupByKeys: true,
|
|
groupByData: false
|
|
});
|
|
this.animationState.transition("updateData");
|
|
}
|
|
didCircleChange() {
|
|
const r = this.radius;
|
|
const cx = this.centerX;
|
|
const cy = this.centerY;
|
|
const cache = this.circleCache;
|
|
if (r !== cache.r || cx !== cache.cx || cy !== cache.cy) {
|
|
this.circleCache = { r, cx, cy };
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
isRadiusAxisReversed() {
|
|
return this.axes["radius" /* Radius */]?.isReversed();
|
|
}
|
|
maybeRefreshNodeData() {
|
|
const circleChanged = this.didCircleChange();
|
|
if (!circleChanged && !this.nodeDataRefresh)
|
|
return;
|
|
this.contextNodeData = this.createNodeData();
|
|
this.nodeData = this.contextNodeData?.nodeData ?? [];
|
|
this.nodeDataRefresh = false;
|
|
}
|
|
getAxisInnerRadius() {
|
|
const radiusAxis = this.axes["radius" /* Radius */];
|
|
return radiusAxis instanceof PolarAxis2 ? this.radius * radiusAxis.innerRadiusRatio : 0;
|
|
}
|
|
createNodeData() {
|
|
const { processedData, dataModel, groupScale } = this;
|
|
if (!dataModel || processedData?.type !== "grouped")
|
|
return;
|
|
const angleAxis = this.axes["angle" /* Angle */];
|
|
const radiusAxis = this.axes["radius" /* Radius */];
|
|
const angleScale = angleAxis?.scale;
|
|
const radiusScale = radiusAxis?.scale;
|
|
if (!angleScale || !radiusScale) {
|
|
return;
|
|
}
|
|
const angleValues = dataModel.resolveKeysById(this, `angleValue`, processedData);
|
|
const radiusStartValues = dataModel.resolveColumnById(this, `radiusValue-start`, processedData);
|
|
const radiusEndValues = dataModel.resolveColumnById(this, `radiusValue-end`, processedData);
|
|
const radiusRawValues = dataModel.resolveColumnById(this, `radiusValue-raw`, processedData);
|
|
let groupPaddingInner = 0;
|
|
let groupPaddingOuter = 0;
|
|
if (angleAxis instanceof AngleCategoryAxis) {
|
|
groupPaddingInner = angleAxis.groupPaddingInner;
|
|
groupPaddingOuter = angleAxis.paddingInner;
|
|
}
|
|
const groupAngleStep = angleScale.bandwidth ?? 0;
|
|
const paddedGroupAngleStep = groupAngleStep * (1 - groupPaddingOuter);
|
|
const { index: groupIndex, visibleGroupCount } = this.ctx.seriesStateManager.getVisiblePeerGroupIndex(this);
|
|
groupScale.domain = Array.from({ length: visibleGroupCount }).map((_, i) => String(i));
|
|
groupScale.range = [-paddedGroupAngleStep / 2, paddedGroupAngleStep / 2];
|
|
groupScale.paddingInner = visibleGroupCount > 1 ? groupPaddingInner : 0;
|
|
const radiusAxisReversed = this.isRadiusAxisReversed();
|
|
const axisInnerRadius = this.getAxisInnerRadius();
|
|
const axisOuterRadius = this.radius;
|
|
const axisTotalRadius = axisOuterRadius + axisInnerRadius;
|
|
const { angleKey, radiusKey, angleName, radiusName, legendItemName, label } = this.properties;
|
|
const radiusDomain = this.getSeriesDomain("radius" /* Radius */).domain;
|
|
const getLabelNodeDatum = (datum, radiusDatum, x, y) => {
|
|
const labelText = this.getLabelText(
|
|
radiusDatum,
|
|
datum,
|
|
radiusKey,
|
|
"radius",
|
|
radiusDomain,
|
|
label,
|
|
{ value: radiusDatum, datum, angleKey, radiusKey, angleName, radiusName, legendItemName }
|
|
);
|
|
if (labelText) {
|
|
return { x, y, text: labelText, textAlign: "center", textBaseline: "middle" };
|
|
}
|
|
};
|
|
const nodeData = [];
|
|
const styles = getItemStyles4(
|
|
(nodeDatum, isHighlight, highlightState) => getItemStyle(this, nodeDatum, isHighlight, highlightState)
|
|
);
|
|
const context = {
|
|
itemId: radiusKey,
|
|
nodeData,
|
|
labelData: nodeData,
|
|
styles
|
|
};
|
|
if (!this.visible)
|
|
return context;
|
|
const { dataSources } = processedData;
|
|
const rawData = dataSources.get(this.id)?.data ?? [];
|
|
for (const { datumIndex } of dataModel.forEachGroupDatum(this, processedData)) {
|
|
const datum = rawData[datumIndex];
|
|
const angleDatum = angleValues[datumIndex];
|
|
if (angleDatum === void 0 && !this.properties.allowNullKeys)
|
|
return;
|
|
const radiusDatum = radiusRawValues[datumIndex];
|
|
const isPositive = radiusDatum >= 0 && !Object.is(radiusDatum, -0);
|
|
const innerRadiusDatum = radiusStartValues[datumIndex];
|
|
const outerRadiusDatum = radiusEndValues[datumIndex];
|
|
const negative = isPositive === radiusAxisReversed;
|
|
if (innerRadiusDatum === void 0 || outerRadiusDatum === void 0)
|
|
return;
|
|
let startAngle;
|
|
let endAngle;
|
|
let angle2;
|
|
if (rawData.length === 1) {
|
|
startAngle = -0.5 * Math.PI;
|
|
endAngle = 1.5 * Math.PI;
|
|
angle2 = startAngle;
|
|
} else {
|
|
const groupAngle = angleScale.convert(angleDatum);
|
|
startAngle = normalizeAngle360(groupAngle + groupScale.convert(String(groupIndex)));
|
|
endAngle = normalizeAngle360(startAngle + groupScale.bandwidth);
|
|
angle2 = startAngle + groupScale.bandwidth / 2;
|
|
}
|
|
const innerRadius = axisTotalRadius - radiusScale.convert(innerRadiusDatum);
|
|
const outerRadius = axisTotalRadius - radiusScale.convert(outerRadiusDatum);
|
|
const midRadius = (innerRadius + outerRadius) / 2;
|
|
const x = Math.cos(angle2) * midRadius;
|
|
const y = Math.sin(angle2) * midRadius;
|
|
const labelNodeDatum = this.properties.label.enabled ? getLabelNodeDatum(datum, radiusDatum, x, y) : void 0;
|
|
const columnWidth = this.getColumnWidth(startAngle, endAngle);
|
|
nodeData.push({
|
|
series: this,
|
|
datum,
|
|
datumIndex,
|
|
point: { x, y, size: 0 },
|
|
midPoint: { x, y },
|
|
label: labelNodeDatum,
|
|
angleValue: angleDatum,
|
|
radiusValue: radiusDatum,
|
|
negative,
|
|
innerRadius,
|
|
outerRadius,
|
|
stackInnerRadius: innerRadius,
|
|
stackOuterRadius: outerRadius,
|
|
startAngle,
|
|
endAngle,
|
|
midAngle: angle2,
|
|
axisInnerRadius,
|
|
axisOuterRadius,
|
|
columnWidth,
|
|
index: datumIndex
|
|
});
|
|
}
|
|
return {
|
|
itemId: radiusKey,
|
|
nodeData,
|
|
labelData: nodeData,
|
|
styles
|
|
};
|
|
}
|
|
getColumnWidth(_startAngle, _endAngle) {
|
|
return Number.NaN;
|
|
}
|
|
update({ seriesRect }) {
|
|
const resize = this.checkResize(seriesRect);
|
|
this.maybeRefreshNodeData();
|
|
this.contentGroup.translationX = this.centerX;
|
|
this.contentGroup.translationY = this.centerY;
|
|
this.highlightGroup.translationX = this.centerX;
|
|
this.highlightGroup.translationY = this.centerY;
|
|
if (this.labelGroup) {
|
|
this.labelGroup.translationX = this.centerX;
|
|
this.labelGroup.translationY = this.centerY;
|
|
}
|
|
this.updateSectorSelection(this.itemSelection, false);
|
|
this.updateSectorSelection(this.highlightSelection, true);
|
|
this.updateLabels();
|
|
if (resize) {
|
|
this.animationState.transition("resize");
|
|
}
|
|
this.animationState.transition("update");
|
|
}
|
|
updateSectorSelection(selection, isHighlight) {
|
|
const { contextNodeData } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
let selectionData = [];
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
if (isHighlight) {
|
|
if (activeHighlight?.datum && activeHighlight.series === this) {
|
|
selectionData.push(activeHighlight);
|
|
}
|
|
} else {
|
|
selectionData = this.nodeData;
|
|
}
|
|
const radiusAxisReversed = this.isRadiusAxisReversed();
|
|
const axisInnerRadius = radiusAxisReversed ? this.radius : this.getAxisInnerRadius();
|
|
const axisOuterRadius = radiusAxisReversed ? this.getAxisInnerRadius() : this.radius;
|
|
const fillBBox = this.getShapeFillBBox();
|
|
const hasItemStylers = this.hasItemStylers();
|
|
selection.update(selectionData, void 0, (datum) => this.getDatumId(datum)).each((node, nodeDatum) => {
|
|
const { midPoint } = nodeDatum;
|
|
if (hasItemStylers) {
|
|
const highlightState = this.getHighlightState(activeHighlight, isHighlight, nodeDatum.datumIndex);
|
|
nodeDatum.style = getItemStyle(this, nodeDatum, isHighlight, highlightState);
|
|
}
|
|
const style2 = nodeDatum.style ?? contextNodeData.styles[this.getHighlightState(highlightedDatum, isHighlight, nodeDatum.datumIndex)];
|
|
const fill = style2.fill;
|
|
const itemBounds = isGradientFill(fill) && fill.bounds === "item";
|
|
const fillParams = itemBounds ? { centerX: midPoint?.x ?? 0, centerY: midPoint?.y ?? 0 } : { centerX: 0, centerY: 0, innerRadius: axisInnerRadius, outerRadius: axisOuterRadius };
|
|
this.updateItemPath(node, nodeDatum, isHighlight);
|
|
node.setStyleProperties(style2, fillBBox, fillParams);
|
|
node.cornerRadius = style2.cornerRadius ?? 0;
|
|
node.lineJoin = "round";
|
|
});
|
|
}
|
|
updateLabels() {
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightDatum = activeHighlight?.series === this && activeHighlight?.datum ? activeHighlight : void 0;
|
|
const highlightData = highlightDatum ? [highlightDatum] : [];
|
|
this.labelSelection.update(this.nodeData).each((node, datum) => {
|
|
updateLabelNode7(this, node, this.properties, this.properties.label, datum.label, false, activeHighlight);
|
|
node.fillOpacity = this.getHighlightStyle(false, datum.datumIndex).opacity ?? 1;
|
|
});
|
|
this.highlightLabelSelection.update(highlightData, void 0, (datum) => this.getDatumId(datum)).each((node, datum) => {
|
|
updateLabelNode7(this, node, this.properties, this.properties.label, datum.label, true, activeHighlight);
|
|
node.fillOpacity = this.getHighlightStyle(true, datum.datumIndex).opacity ?? 1;
|
|
});
|
|
}
|
|
animateEmptyUpdateReady() {
|
|
const { labelSelection } = this;
|
|
const fns = this.getColumnTransitionFunctions();
|
|
motion7.fromToMotion(this.id, "datums", this.ctx.animationManager, [this.itemSelection], fns);
|
|
seriesLabelFadeInAnimation7(
|
|
this,
|
|
"labels",
|
|
this.ctx.animationManager,
|
|
labelSelection,
|
|
this.highlightLabelSelection
|
|
);
|
|
}
|
|
animateClearingUpdateEmpty() {
|
|
const { itemSelection } = this;
|
|
const { animationManager } = this.ctx;
|
|
const fns = this.getColumnTransitionFunctions();
|
|
motion7.fromToMotion(this.id, "datums", animationManager, [itemSelection], fns);
|
|
seriesLabelFadeOutAnimation2(
|
|
this,
|
|
"labels",
|
|
animationManager,
|
|
this.labelSelection,
|
|
this.highlightLabelSelection
|
|
);
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, axes, properties } = this;
|
|
const { angleKey, angleName, radiusKey, radiusName, legendItemName, tooltip } = properties;
|
|
const angleAxis = axes["angle" /* Angle */];
|
|
const radiusAxis = axes["radius" /* Radius */];
|
|
const nodeDatum = this.nodeData?.[datumIndex];
|
|
if (!dataModel || !processedData || !angleAxis || !radiusAxis || !nodeDatum)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const angleValue = dataModel.resolveKeysById(this, `angleValue`, processedData)[datumIndex];
|
|
const radiusValue = dataModel.resolveColumnById(this, `radiusValue-raw`, processedData)[datumIndex];
|
|
if (angleValue === void 0 && !this.properties.allowNullKeys)
|
|
return;
|
|
const format = getItemStyle(this, nodeDatum, false);
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading: this.getAxisValueText(angleAxis, "tooltip", angleValue, datum, angleKey, void 0),
|
|
symbol: this.legendItemSymbol(),
|
|
data: [
|
|
{
|
|
label: radiusName,
|
|
fallbackLabel: radiusKey,
|
|
value: this.getAxisValueText(radiusAxis, "tooltip", radiusValue, datum, radiusKey, void 0),
|
|
missing: module_support_exports.isTooltipValueMissing(radiusValue)
|
|
}
|
|
]
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: angleName,
|
|
angleKey,
|
|
angleName,
|
|
radiusKey,
|
|
radiusName,
|
|
legendItemName,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
pickNodeClosestDatum(point) {
|
|
return this.pickNodeNearestDistantObject(point, this.itemSelection.nodes());
|
|
}
|
|
legendItemSymbol() {
|
|
const { fill, stroke: stroke3, fillOpacity, strokeOpacity, strokeWidth, lineDash, lineDashOffset } = getStyle(
|
|
this,
|
|
false,
|
|
module_support_exports.HighlightState.None
|
|
);
|
|
const markerStyle = {
|
|
fill: fill ?? "rgba(0, 0, 0, 0)",
|
|
stroke: stroke3 ?? "rgba(0, 0, 0, 0)",
|
|
fillOpacity,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash,
|
|
lineDashOffset
|
|
};
|
|
if (isGradientFill(markerStyle.fill)) {
|
|
markerStyle.fill = { ...markerStyle.fill, gradient: "linear", rotation: 0, reverse: false };
|
|
}
|
|
return {
|
|
marker: markerStyle
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
if (legendType !== "category") {
|
|
return [];
|
|
}
|
|
const { id: seriesId, visible } = this;
|
|
const { radiusKey, radiusName, legendItemName, showInLegend } = this.properties;
|
|
return [
|
|
{
|
|
legendType: "category",
|
|
id: seriesId,
|
|
itemId: radiusKey,
|
|
seriesId,
|
|
enabled: visible,
|
|
label: {
|
|
text: legendItemName ?? radiusName ?? radiusKey
|
|
},
|
|
symbol: this.legendItemSymbol(),
|
|
legendItemName,
|
|
hideInLegend: !showInLegend
|
|
}
|
|
];
|
|
}
|
|
getDatumId(datum) {
|
|
return createDatumId18(datum.angleValue);
|
|
}
|
|
computeLabelsBBox() {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-column/radialColumnSeriesBaseProperties.ts
|
|
var { SeriesProperties: SeriesProperties7, makeSeriesTooltip: makeSeriesTooltip18, Label: Label16 } = module_support_exports;
|
|
var RadialColumnSeriesBaseProperties = class extends SeriesProperties7 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.angleKeyAxis = "angle";
|
|
this.radiusKeyAxis = "radius";
|
|
this.fill = "black";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "black";
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.cornerRadius = 0;
|
|
this.rotation = 0;
|
|
this.label = new Label16();
|
|
this.tooltip = makeSeriesTooltip18();
|
|
}
|
|
getStyle() {
|
|
const { fill, fillOpacity, stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset, cornerRadius } = this;
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
cornerRadius,
|
|
opacity: 1
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "angleKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "angleName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "radiusKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "radiusName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "angleKeyAxis", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "radiusKeyAxis", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "legendItemName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "styler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "rotation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "stackGroup", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "normalizedTo", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesBaseProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-column/radialColumnUtil.ts
|
|
var { motion: motion8 } = module_support_exports;
|
|
function createAngleMotionCalculator() {
|
|
const angles = {
|
|
startAngle: /* @__PURE__ */ new Map(),
|
|
endAngle: /* @__PURE__ */ new Map()
|
|
};
|
|
const angleKeys = ["startAngle", "endAngle"];
|
|
const calculate = (node, datum, status) => {
|
|
for (const key of angleKeys) {
|
|
const map = angles[key];
|
|
let from4 = (status === "removed" || status === "updated" ? node : datum)[key];
|
|
let to2 = (status === "removed" ? node : datum)[key];
|
|
if (Number.isNaN(to2)) {
|
|
to2 = node.previousDatum?.[key] ?? Number.NaN;
|
|
}
|
|
const diff9 = from4 - to2;
|
|
if (Math.abs(diff9) > Math.PI) {
|
|
from4 -= Math.sign(diff9) * 2 * Math.PI;
|
|
}
|
|
map.set(datum, { from: from4, to: to2 });
|
|
}
|
|
};
|
|
const getAngles = (datum, fromToKey) => {
|
|
return {
|
|
startAngle: angles.startAngle.get(datum)[fromToKey],
|
|
endAngle: angles.endAngle.get(datum)[fromToKey]
|
|
};
|
|
};
|
|
const from3 = (datum) => getAngles(datum, "from");
|
|
const to = (datum) => getAngles(datum, "to");
|
|
return { calculate, from: from3, to };
|
|
}
|
|
function fixRadialColumnAnimationStatus(node, datum, status) {
|
|
if (status === "updated") {
|
|
if (node.previousDatum == null || Number.isNaN(node.previousDatum.startAngle) || Number.isNaN(node.previousDatum.endAngle)) {
|
|
return "added";
|
|
}
|
|
if (Number.isNaN(datum.startAngle) || Number.isNaN(datum.endAngle)) {
|
|
return "removed";
|
|
}
|
|
}
|
|
if (status === "added" && node.previousDatum != null) {
|
|
return "updated";
|
|
}
|
|
return status;
|
|
}
|
|
function prepareRadialColumnAnimationFunctions(axisZeroRadius) {
|
|
const angles = createAngleMotionCalculator();
|
|
const fromFn = (node, datum, status) => {
|
|
status = fixRadialColumnAnimationStatus(node, datum, status);
|
|
angles.calculate(node, datum, status);
|
|
const { startAngle, endAngle } = angles.from(datum);
|
|
let innerRadius;
|
|
let outerRadius;
|
|
let columnWidth;
|
|
let axisInnerRadius;
|
|
let axisOuterRadius;
|
|
if (status === "removed" || status === "updated") {
|
|
innerRadius = node.innerRadius;
|
|
outerRadius = node.outerRadius;
|
|
columnWidth = node.columnWidth;
|
|
axisInnerRadius = node.axisInnerRadius;
|
|
axisOuterRadius = node.axisOuterRadius;
|
|
} else {
|
|
innerRadius = axisZeroRadius;
|
|
outerRadius = axisZeroRadius;
|
|
columnWidth = datum.columnWidth;
|
|
axisInnerRadius = datum.axisInnerRadius;
|
|
axisOuterRadius = datum.axisOuterRadius;
|
|
}
|
|
const phase = motion8.NODE_UPDATE_STATE_TO_PHASE_MAPPING[status];
|
|
return {
|
|
innerRadius,
|
|
outerRadius,
|
|
columnWidth,
|
|
axisInnerRadius,
|
|
axisOuterRadius,
|
|
startAngle,
|
|
endAngle,
|
|
phase
|
|
};
|
|
};
|
|
const toFn = (node, datum, status) => {
|
|
const { startAngle, endAngle } = angles.to(datum);
|
|
let innerRadius;
|
|
let outerRadius;
|
|
let columnWidth;
|
|
let axisInnerRadius;
|
|
let axisOuterRadius;
|
|
if (status === "removed") {
|
|
innerRadius = node.innerRadius;
|
|
outerRadius = node.innerRadius;
|
|
columnWidth = node.columnWidth;
|
|
axisInnerRadius = node.axisInnerRadius;
|
|
axisOuterRadius = node.axisOuterRadius;
|
|
} else {
|
|
innerRadius = Number.isNaN(datum.innerRadius) ? axisZeroRadius : datum.innerRadius;
|
|
outerRadius = Number.isNaN(datum.outerRadius) ? axisZeroRadius : datum.outerRadius;
|
|
columnWidth = Number.isNaN(datum.columnWidth) ? node.columnWidth : datum.columnWidth;
|
|
axisInnerRadius = datum.axisInnerRadius;
|
|
axisOuterRadius = datum.axisOuterRadius;
|
|
}
|
|
return { innerRadius, outerRadius, columnWidth, axisInnerRadius, axisOuterRadius, startAngle, endAngle };
|
|
};
|
|
return { toFn, fromFn };
|
|
}
|
|
function resetRadialColumnSelectionFn(_node, {
|
|
innerRadius,
|
|
outerRadius,
|
|
columnWidth,
|
|
axisInnerRadius,
|
|
axisOuterRadius,
|
|
startAngle,
|
|
endAngle
|
|
}) {
|
|
return { innerRadius, outerRadius, columnWidth, axisInnerRadius, axisOuterRadius, startAngle, endAngle };
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/nightingale/nightingaleUtil.ts
|
|
var { SectorBox: SectorBox4, motion: motion9 } = module_support_exports;
|
|
function getRadii(datum) {
|
|
const { negative, innerRadius, outerRadius, stackInnerRadius, stackOuterRadius } = datum;
|
|
return {
|
|
innerRadius: negative ? stackOuterRadius : stackInnerRadius,
|
|
outerRadius: negative ? stackInnerRadius : stackOuterRadius,
|
|
clipInnerRadius: negative ? outerRadius : innerRadius,
|
|
clipOuterRadius: negative ? innerRadius : outerRadius
|
|
};
|
|
}
|
|
function prepareNightingaleAnimationFunctions(axisZeroRadius) {
|
|
const angles = createAngleMotionCalculator();
|
|
const fromFn = (sect, datum, status) => {
|
|
status = fixRadialColumnAnimationStatus(sect, datum, status);
|
|
angles.calculate(sect, datum, status);
|
|
const { startAngle, endAngle } = angles.from(datum);
|
|
let innerRadius;
|
|
let outerRadius;
|
|
let clipSector;
|
|
if (status === "removed" || status === "updated") {
|
|
innerRadius = sect.innerRadius;
|
|
outerRadius = sect.outerRadius;
|
|
clipSector = sect.clipSector;
|
|
} else {
|
|
innerRadius = axisZeroRadius;
|
|
outerRadius = axisZeroRadius;
|
|
}
|
|
clipSector ?? (clipSector = new SectorBox4(startAngle, endAngle, innerRadius, outerRadius));
|
|
const phase = motion9.NODE_UPDATE_STATE_TO_PHASE_MAPPING[status];
|
|
return { innerRadius, outerRadius, startAngle, endAngle, clipSector, phase };
|
|
};
|
|
const toFn = (_sect, datum, status) => {
|
|
const { startAngle, endAngle } = angles.to(datum);
|
|
let innerRadius;
|
|
let outerRadius;
|
|
let clipSector;
|
|
if (status === "removed") {
|
|
innerRadius = axisZeroRadius;
|
|
outerRadius = axisZeroRadius;
|
|
clipSector = new SectorBox4(startAngle, endAngle, innerRadius, outerRadius);
|
|
} else {
|
|
let clipInnerRadius, clipOuterRadius;
|
|
({ innerRadius, outerRadius, clipInnerRadius, clipOuterRadius } = getRadii(datum));
|
|
if (Number.isNaN(innerRadius))
|
|
innerRadius = axisZeroRadius;
|
|
if (Number.isNaN(outerRadius))
|
|
outerRadius = axisZeroRadius;
|
|
if (Number.isNaN(clipInnerRadius))
|
|
clipInnerRadius = axisZeroRadius;
|
|
if (Number.isNaN(clipOuterRadius))
|
|
clipOuterRadius = axisZeroRadius;
|
|
clipSector = new SectorBox4(startAngle, endAngle, clipInnerRadius, clipOuterRadius);
|
|
}
|
|
return { innerRadius, outerRadius, startAngle, endAngle, clipSector };
|
|
};
|
|
return { toFn, fromFn };
|
|
}
|
|
function resetNightingaleSelectionFn(_sect, datum) {
|
|
const { startAngle, endAngle } = datum;
|
|
const { innerRadius, outerRadius, clipInnerRadius, clipOuterRadius } = getRadii(datum);
|
|
const clipSector = new SectorBox4(startAngle, endAngle, clipInnerRadius, clipOuterRadius);
|
|
return { innerRadius, outerRadius, startAngle, endAngle, clipSector };
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/nightingale/nightingaleSeries.ts
|
|
var { Sector: Sector7, SectorBox: SectorBox5 } = module_support_exports;
|
|
var NightingaleSeries = class extends RadialColumnSeriesBase {
|
|
// TODO: Enable once the options contract has been revisited
|
|
// @TempValidate
|
|
// sectorSpacing = 1;
|
|
constructor(moduleCtx) {
|
|
super(moduleCtx, { animationResetFns: { item: resetNightingaleSelectionFn } });
|
|
this.properties = new RadialColumnSeriesBaseProperties();
|
|
}
|
|
setZIndex(zIndex) {
|
|
super.setZIndex(zIndex);
|
|
this.contentGroup.zIndex = [0, 1 /* FOREGROUND */, zIndex];
|
|
this.highlightGroup.zIndex = [0, 2 /* HIGHLIGHT */, zIndex];
|
|
this.labelGroup.zIndex = [0, 3 /* LABEL */, zIndex];
|
|
return true;
|
|
}
|
|
getStackId() {
|
|
const groupIndex = this.seriesGrouping?.groupIndex ?? this.id;
|
|
return `nightingale-stack-${groupIndex}-yValues`;
|
|
}
|
|
nodeFactory() {
|
|
return new Sector7();
|
|
}
|
|
updateItemPath(node, datum, highlight5) {
|
|
const { negative } = datum;
|
|
node.centerX = 0;
|
|
node.centerY = 0;
|
|
node.startOuterCornerRadius = negative ? 0 : this.properties.cornerRadius;
|
|
node.endOuterCornerRadius = negative ? 0 : this.properties.cornerRadius;
|
|
node.startInnerCornerRadius = negative ? this.properties.cornerRadius : 0;
|
|
node.endInnerCornerRadius = negative ? this.properties.cornerRadius : 0;
|
|
if (highlight5) {
|
|
const { startAngle, endAngle } = datum;
|
|
const { innerRadius, outerRadius, clipInnerRadius, clipOuterRadius } = getRadii(datum);
|
|
node.innerRadius = innerRadius;
|
|
node.outerRadius = outerRadius;
|
|
node.startAngle = startAngle;
|
|
node.endAngle = endAngle;
|
|
node.clipSector = new SectorBox5(startAngle, endAngle, clipInnerRadius, clipOuterRadius);
|
|
}
|
|
}
|
|
getColumnTransitionFunctions() {
|
|
const axisZeroRadius = this.isRadiusAxisReversed() ? this.radius : this.getAxisInnerRadius();
|
|
return prepareNightingaleAnimationFunctions(axisZeroRadius);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null || this.properties.styler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
NightingaleSeries.className = "NightingaleSeries";
|
|
NightingaleSeries.type = "nightingale";
|
|
|
|
// packages/ag-charts-enterprise/src/series/nightingale/nightingaleSeriesOptionsDef.ts
|
|
var { nightingaleSeriesThemeableOptionsDef: nightingaleSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var nightingaleSeriesOptionsDef = {
|
|
...commonSeriesOptionsDefs,
|
|
...nightingaleSeriesThemeableOptionsDef2,
|
|
type: required(constant("nightingale")),
|
|
angleKey: required(string),
|
|
radiusKey: required(string),
|
|
angleName: string,
|
|
radiusName: string,
|
|
legendItemName: string,
|
|
grouped: boolean,
|
|
stacked: boolean,
|
|
stackGroup: string,
|
|
normalizedTo: number
|
|
};
|
|
nightingaleSeriesOptionsDef.angleKeyAxis = undocumented(string);
|
|
nightingaleSeriesOptionsDef.radiusKeyAxis = undocumented(string);
|
|
|
|
// packages/ag-charts-enterprise/src/series/nightingale/nightingaleThemes.ts
|
|
var NIGHTINGALE_SERIES_THEME = {
|
|
series: {
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_RADIAL_SERIES_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: {
|
|
$if: [{ $eq: [{ $palette: "type" }, "inbuilt"] }, { $ref: "chartBackgroundColor" }, { $palette: "stroke" }]
|
|
},
|
|
strokeWidth: 1,
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" }
|
|
},
|
|
highlight: { ...MULTI_SERIES_HIGHLIGHT_STYLE, bringToFront: false }
|
|
},
|
|
axes: {
|
|
["angle-category" /* ANGLE_CATEGORY */]: {
|
|
shape: { $findFirstSiblingNotOperation: "circle" /* CIRCLE */ },
|
|
groupPaddingInner: 0,
|
|
paddingInner: 0,
|
|
label: {
|
|
spacing: 10
|
|
}
|
|
},
|
|
["radius-number" /* RADIUS_NUMBER */]: {
|
|
shape: { $findFirstSiblingNotOperation: "circle" /* CIRCLE */ }
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/nightingale/nightingaleModule.ts
|
|
var NightingaleSeriesModule = {
|
|
type: "series",
|
|
name: "nightingale",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
stackable: true,
|
|
groupable: true,
|
|
stackedByDefault: true,
|
|
version: VERSION,
|
|
dependencies: [PolarChartModule],
|
|
options: nightingaleSeriesOptionsDef,
|
|
defaultAxes: { angle: { type: "angle-category" /* ANGLE_CATEGORY */ }, radius: { type: "radius-number" /* RADIUS_NUMBER */ } },
|
|
axisKeys: { ["x" /* X */]: "xKeyAxis", ["y" /* Y */]: "yKeyAxis" },
|
|
themeTemplate: NIGHTINGALE_SERIES_THEME,
|
|
create: (ctx) => new NightingaleSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/radar/radarThemes.ts
|
|
var BASE_RADAR_SERIES_THEME = {
|
|
series: {
|
|
stroke: { $palette: "stroke" },
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" }
|
|
},
|
|
marker: {
|
|
enabled: true,
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: { $palette: "stroke" },
|
|
fillOpacity: 1,
|
|
shape: "circle",
|
|
size: 6,
|
|
strokeOpacity: 1,
|
|
strokeWidth: { $isUserOption: ["./stroke", 1, 0] }
|
|
},
|
|
highlight: MARKER_SERIES_HIGHLIGHT_STYLE,
|
|
tooltip: {
|
|
range: { $path: ["/tooltip/range", "nearest"] }
|
|
}
|
|
},
|
|
axes: {
|
|
["angle-category" /* ANGLE_CATEGORY */]: {
|
|
label: {
|
|
spacing: 10
|
|
}
|
|
}
|
|
}
|
|
};
|
|
var RADAR_LINE_SERIES_THEME = mergeDefaults(
|
|
{
|
|
series: {
|
|
stroke: SAFE_STROKE_FILL_OPERATION,
|
|
strokeWidth: 2
|
|
}
|
|
},
|
|
BASE_RADAR_SERIES_THEME
|
|
);
|
|
var RADAR_AREA_SERIES_THEME = mergeDefaults(
|
|
{
|
|
series: {
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_LINEAR_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
fillOpacity: 0.8,
|
|
strokeWidth: 2,
|
|
marker: {
|
|
enabled: false
|
|
}
|
|
}
|
|
},
|
|
BASE_RADAR_SERIES_THEME
|
|
);
|
|
|
|
// packages/ag-charts-enterprise/src/series/radar/radarSeriesProperties.ts
|
|
var { Label: Label17, SeriesMarker: SeriesMarker3, SeriesProperties: SeriesProperties8, makeSeriesTooltip: makeSeriesTooltip19 } = module_support_exports;
|
|
var RadarSeriesProperties = class extends SeriesProperties8 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.angleKeyAxis = "angle";
|
|
this.radiusKeyAxis = "radius";
|
|
this.stroke = "black";
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.rotation = 0;
|
|
this.marker = new SeriesMarker3();
|
|
this.label = new Label17();
|
|
this.tooltip = makeSeriesTooltip19();
|
|
this.connectMissingData = false;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "angleKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "radiusKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "angleName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "radiusName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "angleKeyAxis", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "radiusKeyAxis", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "legendItemName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "rotation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "styler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "marker", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "tooltip", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarSeriesProperties.prototype, "connectMissingData", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/radar/radarSeries.ts
|
|
var {
|
|
DEFAULT_POLAR_DIRECTION_KEYS: DEFAULT_POLAR_DIRECTION_KEYS3,
|
|
DEFAULT_POLAR_DIRECTION_NAMES: DEFAULT_POLAR_DIRECTION_NAMES3,
|
|
PolarAxis: PolarAxis3,
|
|
SeriesNodePickMode: SeriesNodePickMode14,
|
|
keyProperty: keyProperty11,
|
|
valueProperty: valueProperty14,
|
|
fixNumericExtent: fixNumericExtent10,
|
|
seriesLabelFadeInAnimation: seriesLabelFadeInAnimation8,
|
|
markerFadeInAnimation: markerFadeInAnimation3,
|
|
resetMarkerFn: resetMarkerFn3,
|
|
resetLabelFn: resetLabelFn7,
|
|
animationValidation: animationValidation9,
|
|
computeMarkerFocusBounds: computeMarkerFocusBounds3,
|
|
BBox: BBox28,
|
|
Group: Group20,
|
|
Path: Path13,
|
|
Selection: Selection13,
|
|
Text: Text8,
|
|
Marker: Marker6,
|
|
updateLabelNode: updateLabelNode8,
|
|
getMarkerStyles: getMarkerStyles3
|
|
} = module_support_exports;
|
|
var RadarSeriesNodeEvent = class extends module_support_exports.SeriesNodeEvent {
|
|
constructor(type, nativeEvent, datum, series) {
|
|
super(type, nativeEvent, datum, series);
|
|
this.angleKey = series.properties.angleKey;
|
|
this.radiusKey = series.properties.radiusKey;
|
|
}
|
|
};
|
|
var RadarSeries = class extends module_support_exports.PolarSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
categoryKey: "angleValue",
|
|
propertyKeys: DEFAULT_POLAR_DIRECTION_KEYS3,
|
|
propertyNames: DEFAULT_POLAR_DIRECTION_NAMES3,
|
|
pickModes: [SeriesNodePickMode14.NEAREST_NODE, SeriesNodePickMode14.EXACT_SHAPE_MATCH],
|
|
canHaveAxes: true,
|
|
animationResetFns: {
|
|
item: resetMarkerFn3,
|
|
label: resetLabelFn7
|
|
},
|
|
clipFocusBox: false
|
|
});
|
|
this.NodeEvent = RadarSeriesNodeEvent;
|
|
this.lineGroup = this.contentGroup.appendChild(new Group20({ name: "radar-line" }));
|
|
this.lineSelection = Selection13.select(
|
|
this.lineGroup,
|
|
Path13
|
|
);
|
|
this.resetInvalidToZero = false;
|
|
this.circleCache = { r: 0, cx: 0, cy: 0 };
|
|
this.lineGroup.zIndex = 0;
|
|
this.itemGroup.zIndex = 1;
|
|
}
|
|
renderToOffscreenCanvas() {
|
|
const hasMarkers = (this.nodeData?.length ?? 0) > 0;
|
|
return hasMarkers && this.getDrawingMode(false) === "cutout" || super.renderToOffscreenCanvas();
|
|
}
|
|
nodeFactory() {
|
|
return new Marker6();
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { dataModel, processedData } = this;
|
|
if (!processedData || !dataModel)
|
|
return { domain: [] };
|
|
if (direction === "angle" /* Angle */) {
|
|
const domain = dataModel.getDomain(this, `angleValue`, "key", processedData).domain;
|
|
const sortMetadata = dataModel.getKeySortMetadata(this, "angleValue", processedData);
|
|
return { domain, sortMetadata };
|
|
} else {
|
|
const domain = dataModel.getDomain(this, `radiusValue`, "value", processedData).domain;
|
|
const ext = extent(domain.length === 0 ? domain : [0].concat(domain));
|
|
return { domain: fixNumericExtent10(ext) };
|
|
}
|
|
}
|
|
async processData(dataController) {
|
|
const { angleKey, radiusKey } = this.properties;
|
|
const extraProps = [];
|
|
if (!this.ctx.animationManager.isSkipped()) {
|
|
extraProps.push(animationValidation9());
|
|
}
|
|
const radiusScaleType = this.axes["radius" /* Radius */]?.scale.type;
|
|
const angleScaleType = this.axes["angle" /* Angle */]?.scale.type;
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
await this.requestDataModel(dataController, this.data, {
|
|
props: [
|
|
keyProperty11(angleKey, angleScaleType, { id: "angleValue", allowNullKey }),
|
|
valueProperty14(radiusKey, radiusScaleType, { id: "radiusValue", invalidValue: void 0 }),
|
|
...extraProps
|
|
]
|
|
});
|
|
this.animationState.transition("updateData");
|
|
}
|
|
didCircleChange() {
|
|
const r = this.radius;
|
|
const cx = this.centerX;
|
|
const cy = this.centerY;
|
|
const cache = this.circleCache;
|
|
if (!(r === cache.r && cx === cache.cx && cy === cache.cy)) {
|
|
this.circleCache = { r, cx, cy };
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
getAxisInnerRadius() {
|
|
const radiusAxis = this.axes["radius" /* Radius */];
|
|
return radiusAxis instanceof PolarAxis3 ? this.radius * radiusAxis.innerRadiusRatio : 0;
|
|
}
|
|
maybeRefreshNodeData() {
|
|
const didCircleChange = this.didCircleChange();
|
|
if (!didCircleChange && !this.nodeDataRefresh)
|
|
return;
|
|
this.contextNodeData = this.createNodeData();
|
|
this.nodeData = this.contextNodeData?.nodeData ?? [];
|
|
this.nodeDataRefresh = false;
|
|
}
|
|
createNodeData() {
|
|
const { processedData, dataModel } = this;
|
|
if (!processedData || !dataModel)
|
|
return;
|
|
const { angleKey, radiusKey, angleName, radiusName, legendItemName, marker, label } = this.properties;
|
|
const angleScale = this.axes["angle" /* Angle */]?.scale;
|
|
const radiusScale = this.axes["radius" /* Radius */]?.scale;
|
|
if (!angleScale || !radiusScale) {
|
|
return;
|
|
}
|
|
const angleValues = dataModel.resolveKeysById(this, `angleValue`, processedData);
|
|
const radiusValues = dataModel.resolveColumnById(this, `radiusValue`, processedData);
|
|
const axisInnerRadius = this.getAxisInnerRadius();
|
|
const radiusDomain = this.getSeriesDomain("radius" /* Radius */).domain;
|
|
const rawData = processedData.dataSources.get(this.id)?.data ?? [];
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
const nodeData = [];
|
|
for (let datumIndex = 0; datumIndex < rawData.length; datumIndex++) {
|
|
const datum = rawData[datumIndex];
|
|
const angleDatum = angleValues[datumIndex];
|
|
if (angleDatum === void 0 && !allowNullKeys) {
|
|
continue;
|
|
}
|
|
const radiusDatum = radiusValues[datumIndex];
|
|
const angle2 = angleScale.convert(angleDatum);
|
|
const radius = this.radius + axisInnerRadius - radiusScale.convert(radiusDatum);
|
|
const cos = Math.cos(angle2);
|
|
const sin = Math.sin(angle2);
|
|
const x = cos * radius;
|
|
const y = sin * radius;
|
|
let labelNodeDatum;
|
|
if (label.enabled) {
|
|
const labelText = this.getLabelText(
|
|
radiusDatum,
|
|
datum,
|
|
radiusKey,
|
|
"radius",
|
|
radiusDomain,
|
|
label,
|
|
{ value: radiusDatum, datum, angleKey, radiusKey, angleName, radiusName, legendItemName }
|
|
);
|
|
if (labelText) {
|
|
let textAlign = "right";
|
|
if (isNumberEqual(cos, 0)) {
|
|
textAlign = "center";
|
|
} else if (cos > 0) {
|
|
textAlign = "left";
|
|
}
|
|
let textBaseline = "bottom";
|
|
if (isNumberEqual(sin, 0)) {
|
|
textBaseline = "middle";
|
|
} else if (sin > 0) {
|
|
textBaseline = "top";
|
|
}
|
|
labelNodeDatum = {
|
|
x: x + cos * marker.size,
|
|
y: y + sin * marker.size,
|
|
text: labelText,
|
|
textAlign,
|
|
textBaseline
|
|
};
|
|
}
|
|
}
|
|
nodeData.push({
|
|
series: this,
|
|
datum,
|
|
datumIndex,
|
|
index: datumIndex,
|
|
point: { x, y, size: marker.size },
|
|
midPoint: { x, y },
|
|
label: labelNodeDatum,
|
|
angleValue: angleDatum,
|
|
radiusValue: radiusDatum,
|
|
missing: !isFiniteNumber(angle2) || !isFiniteNumber(radius)
|
|
});
|
|
}
|
|
return {
|
|
itemId: radiusKey,
|
|
nodeData,
|
|
labelData: nodeData,
|
|
styles: getMarkerStyles3(this, this.properties, marker)
|
|
};
|
|
}
|
|
update({ seriesRect }) {
|
|
const resize = this.checkResize(seriesRect);
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
const { series } = this.ctx.highlightManager?.getActiveHighlight() ?? {};
|
|
this.highlightGroup.visible = (animationEnabled || this.visible) && series === this;
|
|
this.maybeRefreshNodeData();
|
|
this.contentGroup.translationX = this.centerX;
|
|
this.contentGroup.translationY = this.centerY;
|
|
this.highlightGroup.translationX = this.centerX;
|
|
this.highlightGroup.translationY = this.centerY;
|
|
if (this.labelGroup) {
|
|
this.labelGroup.translationX = this.centerX;
|
|
this.labelGroup.translationY = this.centerY;
|
|
}
|
|
this.updatePathSelections();
|
|
this.updateMarkerSelection();
|
|
this.updateHighlightSelection();
|
|
this.updatePathNodes();
|
|
if (this.hasItemStylers()) {
|
|
this.updateDatumStyles(this.itemSelection, false);
|
|
this.updateDatumStyles(this.highlightSelection, true);
|
|
}
|
|
const drawingMode = this.ctx.chartService.highlight?.drawingMode ?? "overlay";
|
|
this.updateMarkers(this.itemSelection, false, "overlay");
|
|
this.updateMarkers(this.highlightSelection, true, drawingMode);
|
|
this.updateLabels();
|
|
if (resize) {
|
|
this.animationState.transition("resize");
|
|
}
|
|
this.animationState.transition("update");
|
|
}
|
|
updatePathSelections() {
|
|
const pathData = this.visible ? [true] : [];
|
|
this.lineSelection.update(pathData);
|
|
}
|
|
updateMarkerSelection() {
|
|
const { marker, styler } = this.properties;
|
|
if (marker.isDirty()) {
|
|
this.itemSelection.clear();
|
|
this.itemSelection.cleanup();
|
|
this.itemSelection = Selection13.select(this.itemGroup, () => this.nodeFactory(), false);
|
|
}
|
|
const markersEnabled = styler == null ? marker.enabled : this.getStyle().marker.enabled;
|
|
const data = this.visible && marker.shape && markersEnabled ? this.nodeData : [];
|
|
this.itemSelection.update(data);
|
|
}
|
|
updateHighlightSelection() {
|
|
const { marker, styler } = this.properties;
|
|
if (marker.isDirty()) {
|
|
this.highlightSelection.clear();
|
|
this.highlightSelection.cleanup();
|
|
this.highlightSelection = Selection13.select(this.highlightGroup, () => this.nodeFactory(), false);
|
|
}
|
|
const markersEnabled = styler == null ? marker.enabled : this.getStyle().marker.enabled;
|
|
const highlighted = this.ctx.highlightManager?.getActiveHighlight();
|
|
const data = this.visible && marker.shape && markersEnabled && highlighted?.datum ? [{ ...highlighted }] : [];
|
|
this.highlightSelection.update(data);
|
|
}
|
|
getMarkerFill(highlightedStyle) {
|
|
return highlightedStyle?.fill ?? this.getStyle().marker.fill;
|
|
}
|
|
getDatumStylerProperties(datum) {
|
|
const { id: seriesId, properties } = this;
|
|
const { angleKey, radiusKey } = properties;
|
|
return {
|
|
seriesId,
|
|
datum,
|
|
angleKey,
|
|
radiusKey
|
|
};
|
|
}
|
|
updateDatumStyles(selection, isHighlight) {
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
selection.each((_, datum) => {
|
|
const highlightState = this.getHighlightState(highlightedDatum, isHighlight, datum.datumIndex);
|
|
const stylerStyle = this.getStyle(highlightState);
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity } = stylerStyle;
|
|
datum.style = this.getMarkerStyle(
|
|
this.properties.marker,
|
|
datum,
|
|
this.getDatumStylerProperties(datum.datum),
|
|
{ isHighlight, highlightState },
|
|
stylerStyle.marker,
|
|
{
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity
|
|
}
|
|
);
|
|
});
|
|
}
|
|
updateMarkers(selection, isHighlight, drawingMode) {
|
|
const fillBBox = this.getShapeFillBBox();
|
|
const { contextNodeData } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
drawingMode = this.getDrawingMode(isHighlight, drawingMode);
|
|
selection.each((node, datum) => {
|
|
const style2 = datum.style ?? contextNodeData.styles[this.getHighlightState(highlightedDatum, isHighlight, datum.datumIndex)];
|
|
this.applyMarkerStyle(style2, node, datum.point, fillBBox);
|
|
node.drawingMode = drawingMode;
|
|
});
|
|
}
|
|
updateLabels() {
|
|
const { properties } = this;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightData = activeHighlight?.series === this && activeHighlight?.datum ? [{ ...activeHighlight }] : [];
|
|
this.labelSelection.update(this.nodeData).each((node, datum) => {
|
|
if (datum.label) {
|
|
const isHighlight = false;
|
|
node.fillOpacity = this.getHighlightStyle(isHighlight, datum.datumIndex).opacity ?? 1;
|
|
updateLabelNode8(this, node, properties, properties.label, datum.label, isHighlight, activeHighlight);
|
|
}
|
|
});
|
|
this.highlightLabelSelection.update(highlightData).each((node, datum) => {
|
|
if (datum.label) {
|
|
const isHighlight = true;
|
|
node.fillOpacity = this.getHighlightStyle(isHighlight, datum.datumIndex).opacity ?? 1;
|
|
updateLabelNode8(this, node, properties, properties.label, datum.label, isHighlight, activeHighlight);
|
|
}
|
|
});
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, axes, properties } = this;
|
|
const { angleKey, angleName, radiusKey, radiusName, legendItemName, tooltip, marker } = properties;
|
|
const angleAxis = axes["angle" /* Angle */];
|
|
const radiusAxis = axes["radius" /* Radius */];
|
|
if (!dataModel || !processedData || !angleAxis || !radiusAxis)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const angleValue = dataModel.resolveKeysById(this, `angleValue`, processedData)[datumIndex];
|
|
const radiusValue = dataModel.resolveColumnById(this, `radiusValue`, processedData)[datumIndex];
|
|
const allowNullKeys = this.properties.allowNullKeys ?? false;
|
|
if (angleValue === void 0 && !allowNullKeys)
|
|
return;
|
|
const activeStyle = this.getMarkerStyle(marker, { datum, datumIndex }, this.getDatumStylerProperties(datum), {
|
|
isHighlight: false
|
|
});
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading: this.getAxisValueText(
|
|
angleAxis,
|
|
"tooltip",
|
|
angleValue,
|
|
datum,
|
|
angleKey,
|
|
void 0,
|
|
allowNullKeys
|
|
),
|
|
symbol: this.legendItemSymbol(),
|
|
data: [
|
|
{
|
|
label: radiusName,
|
|
fallbackLabel: radiusKey,
|
|
value: this.getAxisValueText(radiusAxis, "tooltip", radiusValue, datum, radiusKey, void 0),
|
|
missing: module_support_exports.isTooltipValueMissing(radiusValue)
|
|
}
|
|
]
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: angleName,
|
|
angleKey,
|
|
radiusKey,
|
|
angleName,
|
|
radiusName,
|
|
legendItemName,
|
|
...activeStyle
|
|
}
|
|
);
|
|
}
|
|
legendItemSymbol() {
|
|
const { stroke: stroke3, strokeWidth, strokeOpacity, lineDash, marker } = this.getStyle();
|
|
const markerStyle = {
|
|
shape: marker.shape,
|
|
enabled: marker.enabled || strokeWidth <= 0,
|
|
fill: this.getMarkerFill() ?? marker.stroke ?? stroke3 ?? "rgba(0, 0, 0, 0)",
|
|
stroke: marker.stroke ?? stroke3 ?? "rgba(0, 0, 0, 0)",
|
|
fillOpacity: marker.fillOpacity,
|
|
strokeOpacity: marker.strokeOpacity,
|
|
strokeWidth: marker.strokeWidth,
|
|
lineDash: marker.lineDash,
|
|
lineDashOffset: marker.lineDashOffset
|
|
};
|
|
return {
|
|
marker: markerStyle,
|
|
line: {
|
|
enabled: true,
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
if (legendType !== "category") {
|
|
return [];
|
|
}
|
|
const {
|
|
id: seriesId,
|
|
ctx: { legendManager },
|
|
visible
|
|
} = this;
|
|
const { radiusKey, radiusName, legendItemName, showInLegend } = this.properties;
|
|
return [
|
|
{
|
|
legendType: "category",
|
|
id: seriesId,
|
|
itemId: radiusKey,
|
|
seriesId,
|
|
enabled: visible && legendManager.getItemEnabled({ seriesId, itemId: radiusKey }),
|
|
label: {
|
|
text: legendItemName ?? radiusName ?? radiusKey
|
|
},
|
|
symbol: this.legendItemSymbol(),
|
|
legendItemName,
|
|
hideInLegend: !showInLegend
|
|
}
|
|
];
|
|
}
|
|
pickNodeClosestDatum(hitPoint) {
|
|
const { nodeData, centerX: cx, centerY: cy } = this;
|
|
const { x, y } = hitPoint;
|
|
const radius = this.radius;
|
|
const distanceFromCenter = Math.hypot(x - cx, y - cy);
|
|
if (distanceFromCenter > radius + this.maxChartMarkerSize) {
|
|
return;
|
|
}
|
|
let minDistance = Infinity;
|
|
let closestDatum;
|
|
for (const datum of nodeData) {
|
|
const { point: { x: datumX = Number.NaN, y: datumY = Number.NaN } = {} } = datum;
|
|
if (Number.isNaN(datumX) || Number.isNaN(datumY)) {
|
|
continue;
|
|
}
|
|
const distance2 = Math.hypot(hitPoint.x - datumX - cx, hitPoint.y - datumY - cy);
|
|
if (distance2 < minDistance) {
|
|
minDistance = distance2;
|
|
closestDatum = datum;
|
|
}
|
|
}
|
|
if (closestDatum) {
|
|
const distance2 = Math.max(minDistance - (closestDatum.point?.size ?? 0) / 2, 0);
|
|
return { datum: closestDatum, distance: distance2 };
|
|
}
|
|
}
|
|
computeLabelsBBox() {
|
|
const { label } = this.properties;
|
|
this.maybeRefreshNodeData();
|
|
const textBoxes = [];
|
|
const tempText = new Text8();
|
|
for (const nodeDatum of this.nodeData) {
|
|
if (!label.enabled || !nodeDatum.label) {
|
|
continue;
|
|
}
|
|
tempText.text = nodeDatum.label.text;
|
|
tempText.x = nodeDatum.label.x;
|
|
tempText.y = nodeDatum.label.y;
|
|
tempText.setFont(label);
|
|
tempText.setAlign(nodeDatum.label);
|
|
const box = tempText.getBBox();
|
|
textBoxes.push(box);
|
|
}
|
|
if (textBoxes.length === 0) {
|
|
return null;
|
|
}
|
|
return BBox28.merge(textBoxes);
|
|
}
|
|
getLineNode() {
|
|
return this.lineSelection?.at(0);
|
|
}
|
|
beforePathAnimation() {
|
|
this.updatePathNodes();
|
|
}
|
|
getPathNodesStyle() {
|
|
const highlightDatum = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightState = this.getHighlightState(highlightDatum);
|
|
const highlightStyle = this.getHighlightStyle(void 0, void 0, highlightState);
|
|
const stylerStyle = this.getStyle(highlightState);
|
|
return mergeDefaults(highlightStyle, stylerStyle);
|
|
}
|
|
getLinePoints() {
|
|
const { nodeData, resetInvalidToZero } = this;
|
|
const { connectMissingData } = this.properties;
|
|
if (nodeData.length === 0) {
|
|
return [];
|
|
}
|
|
const radiusAxis = this.axes["radius" /* Radius */];
|
|
const angleAxis = this.axes["angle" /* Angle */];
|
|
const reversedAngleAxis = angleAxis?.isReversed();
|
|
const reversedRadiusAxis = radiusAxis?.isReversed();
|
|
const data = reversedRadiusAxis && !reversedAngleAxis ? [...nodeData].reverse() : nodeData;
|
|
const points = [];
|
|
let prevPointInvalid = false;
|
|
let firstValid;
|
|
for (const [index, datum] of data.entries()) {
|
|
let { x, y } = datum.point;
|
|
const isPointInvalid = Number.isNaN(x) || Number.isNaN(y);
|
|
if (!isPointInvalid) {
|
|
firstValid ?? (firstValid = datum);
|
|
}
|
|
if (isPointInvalid && !connectMissingData) {
|
|
x = 0;
|
|
y = 0;
|
|
}
|
|
const moveTo2 = index === 0 || !resetInvalidToZero && !connectMissingData && (isPointInvalid || prevPointInvalid);
|
|
points.push({ x, y, moveTo: moveTo2 });
|
|
prevPointInvalid = isPointInvalid;
|
|
}
|
|
if (firstValid !== void 0) {
|
|
points.push({ x: firstValid.point.x, y: firstValid.point.y, moveTo: false });
|
|
}
|
|
return points;
|
|
}
|
|
animateSinglePath(pathNode, points, ratio2) {
|
|
const { path } = pathNode;
|
|
path.clear(true);
|
|
const axisInnerRadius = this.getAxisInnerRadius();
|
|
const radiusAxis = this.axes["radius" /* Radius */];
|
|
const reversedRadiusAxis = radiusAxis?.isReversed();
|
|
const radiusZero = reversedRadiusAxis ? this.radius + axisInnerRadius - radiusAxis?.scale.convert(0) : axisInnerRadius;
|
|
for (const point of points) {
|
|
const { x: x1, y: y1, arc, radius = 0, startAngle = 0, endAngle = 0, moveTo: moveTo2 } = point;
|
|
const angle2 = Math.atan2(y1, x1);
|
|
const x0 = radiusZero * Math.cos(angle2);
|
|
const y0 = radiusZero * Math.sin(angle2);
|
|
const t = ratio2;
|
|
const x = x0 * (1 - t) + x1 * t;
|
|
const y = y0 * (1 - t) + y1 * t;
|
|
if (arc) {
|
|
path.arc(x1, y1, radius, startAngle, endAngle);
|
|
} else if (moveTo2) {
|
|
path.moveTo(x, y);
|
|
} else {
|
|
path.lineTo(x, y);
|
|
}
|
|
}
|
|
pathNode.checkPathDirty();
|
|
}
|
|
animatePaths(ratio2) {
|
|
const linePoints = this.getLinePoints();
|
|
const lineNode = this.getLineNode();
|
|
if (!lineNode)
|
|
return;
|
|
this.animateSinglePath(lineNode, linePoints, ratio2);
|
|
}
|
|
animateEmptyUpdateReady() {
|
|
const { itemSelection, labelSelection } = this;
|
|
const { animationManager } = this.ctx;
|
|
this.beforePathAnimation();
|
|
animationManager.animate({
|
|
id: `${this.id}_'path`,
|
|
groupId: this.id,
|
|
from: 0,
|
|
to: 1,
|
|
phase: "initial",
|
|
collapsable: false,
|
|
onUpdate: (ratio2) => this.animatePaths(ratio2),
|
|
onStop: () => this.animatePaths(1)
|
|
});
|
|
markerFadeInAnimation3(this, animationManager, "added", this.getAnimationDrawingModes(), itemSelection);
|
|
seriesLabelFadeInAnimation8(this, "labels", animationManager, labelSelection, this.highlightLabelSelection);
|
|
}
|
|
animateWaitingUpdateReady(data) {
|
|
super.animateWaitingUpdateReady(data);
|
|
this.resetPaths();
|
|
}
|
|
animateReadyResize(data) {
|
|
super.animateReadyResize(data);
|
|
this.resetPaths();
|
|
}
|
|
resetPaths() {
|
|
const lineNode = this.getLineNode();
|
|
if (lineNode) {
|
|
const { path: linePath } = lineNode;
|
|
const linePoints = this.getLinePoints();
|
|
const stylerStyle = this.getStyle();
|
|
lineNode.fill = void 0;
|
|
lineNode.stroke = stylerStyle.stroke;
|
|
lineNode.strokeWidth = stylerStyle.strokeWidth;
|
|
lineNode.strokeOpacity = stylerStyle.strokeOpacity;
|
|
lineNode.lineDash = stylerStyle.lineDash;
|
|
lineNode.lineDashOffset = stylerStyle.lineDashOffset;
|
|
linePath.clear(true);
|
|
for (const { x, y, moveTo: moveTo2 } of linePoints) {
|
|
if (moveTo2) {
|
|
linePath.moveTo(x, y);
|
|
} else {
|
|
linePath.lineTo(x, y);
|
|
}
|
|
}
|
|
lineNode.checkPathDirty();
|
|
return stylerStyle;
|
|
}
|
|
}
|
|
getStylerResult(stylerResult, highlightState) {
|
|
const { styler } = this.properties;
|
|
if (styler) {
|
|
const stylerParams = this.makeStylerParams(highlightState);
|
|
const cbResult = this.cachedCallWithContext(styler, stylerParams) ?? {};
|
|
const resolved = this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`],
|
|
cbResult,
|
|
{ pick: false }
|
|
);
|
|
if (resolved) {
|
|
stylerResult = resolved;
|
|
}
|
|
}
|
|
return stylerResult;
|
|
}
|
|
getFormattedMarkerStyle(datum) {
|
|
const { angleKey, radiusKey } = this.properties;
|
|
return this.getMarkerStyle(this.properties.marker, datum, { angleKey, radiusKey }, { isHighlight: true });
|
|
}
|
|
computeFocusBounds(opts) {
|
|
return computeMarkerFocusBounds3(this, opts);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.styler != null || this.properties.marker.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
RadarSeries.className = "RadarSeries";
|
|
|
|
// packages/ag-charts-enterprise/src/series/radar-area/radarAreaSeriesProperties.ts
|
|
var RadarAreaSeriesProperties = class extends RadarSeriesProperties {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.fill = "black";
|
|
this.fillOpacity = 1;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarAreaSeriesProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadarAreaSeriesProperties.prototype, "fillOpacity", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/radar-area/radarAreaSeries.ts
|
|
var { Group: Group21, HighlightState: HighlightState7, Path: Path14, PointerEvents: PointerEvents10, Selection: Selection14, toHighlightString: toHighlightString6 } = module_support_exports;
|
|
var RadarAreaSeries = class extends RadarSeries {
|
|
constructor(moduleCtx) {
|
|
super(moduleCtx);
|
|
this.properties = new RadarAreaSeriesProperties();
|
|
this.areaGroup = this.contentGroup.appendChild(new Group21({ name: "radar-area" }));
|
|
this.areaSelection = Selection14.select(
|
|
this.areaGroup,
|
|
Path14
|
|
);
|
|
this.resetInvalidToZero = true;
|
|
this.areaGroup.zIndex = -1;
|
|
}
|
|
updatePathSelections() {
|
|
const pathData = this.visible ? [true] : [];
|
|
this.areaSelection.update(pathData);
|
|
super.updatePathSelections();
|
|
}
|
|
getAreaNode() {
|
|
return this.areaSelection.at(0);
|
|
}
|
|
getMarkerFill(highlightedStyle) {
|
|
if (highlightedStyle?.fill != null)
|
|
return highlightedStyle.fill;
|
|
const stylerStyle = this.getStyle();
|
|
return stylerStyle.marker.fill ?? stylerStyle.fill;
|
|
}
|
|
updatePathNodes() {
|
|
const styles = this.getPathNodesStyle();
|
|
const { fill, fillOpacity, strokeWidth, stroke: stroke3, strokeOpacity, lineDash, lineDashOffset, opacity } = styles;
|
|
const lineNode = this.getLineNode();
|
|
if (lineNode) {
|
|
lineNode.setProperties({
|
|
fill: void 0,
|
|
lineJoin: "round",
|
|
lineCap: "round",
|
|
pointerEvents: PointerEvents10.None,
|
|
opacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
});
|
|
}
|
|
const areaNode = this.getAreaNode();
|
|
if (areaNode) {
|
|
areaNode.setStyleProperties({ fill, fillOpacity, stroke: void 0 }, this.getShapeFillBBox());
|
|
areaNode.setProperties({
|
|
lineJoin: "round",
|
|
pointerEvents: PointerEvents10.None,
|
|
opacity
|
|
});
|
|
}
|
|
}
|
|
animatePaths(ratio2) {
|
|
super.animatePaths(ratio2);
|
|
const areaNode = this.getAreaNode();
|
|
if (areaNode) {
|
|
this.animateSinglePath(areaNode, this.getAreaPoints(), ratio2);
|
|
}
|
|
}
|
|
getAreaPoints() {
|
|
const points = this.getLinePoints();
|
|
const getPolarAxis = (direction) => {
|
|
const axis = this.axes[direction];
|
|
return axis instanceof module_support_exports.PolarAxis ? axis : void 0;
|
|
};
|
|
const radiusAxis = getPolarAxis("radius" /* Radius */);
|
|
const angleAxis = getPolarAxis("angle" /* Angle */);
|
|
const reversedRadiusAxis = radiusAxis?.isReversed();
|
|
if (!reversedRadiusAxis) {
|
|
return points;
|
|
}
|
|
const zeroLinePoints = angleAxis?.getAxisLinePoints()?.points ?? [];
|
|
return points.concat(...zeroLinePoints);
|
|
}
|
|
resetPaths() {
|
|
const superStyle = super.resetPaths();
|
|
const areaNode = this.getAreaNode();
|
|
if (areaNode) {
|
|
const { path: areaPath } = areaNode;
|
|
const areaPoints = this.getAreaPoints();
|
|
const stylerStyle = superStyle ?? this.getStyle();
|
|
const fillBBox = this.getShapeFillBBox();
|
|
areaNode.setStyleProperties(
|
|
{
|
|
fill: stylerStyle.fill,
|
|
stroke: void 0,
|
|
fillOpacity: stylerStyle.fillOpacity,
|
|
lineDash: stylerStyle.lineDash,
|
|
lineDashOffset: stylerStyle.lineDashOffset
|
|
},
|
|
fillBBox
|
|
);
|
|
areaNode.lineJoin = areaNode.lineCap = "round";
|
|
areaPath.clear(true);
|
|
for (const { x, y, moveTo: moveTo2, arc, radius = 0, startAngle = 0, endAngle = 0 } of areaPoints) {
|
|
if (arc) {
|
|
areaPath.arc(x, y, radius, startAngle, endAngle);
|
|
} else if (moveTo2) {
|
|
areaPath.moveTo(x, y);
|
|
} else {
|
|
areaPath.lineTo(x, y);
|
|
}
|
|
}
|
|
areaPath.closePath();
|
|
areaNode.checkPathDirty();
|
|
return stylerStyle;
|
|
}
|
|
}
|
|
makeStylerParams(highlightStateEnum) {
|
|
const { properties } = this;
|
|
const highlightState = toHighlightString6(highlightStateEnum ?? HighlightState7.None);
|
|
return {
|
|
marker: {
|
|
fill: properties.marker.fill,
|
|
fillOpacity: properties.marker.fillOpacity,
|
|
size: properties.marker.size,
|
|
shape: properties.marker.shape,
|
|
stroke: properties.marker.stroke,
|
|
strokeOpacity: properties.marker.strokeOpacity,
|
|
strokeWidth: properties.marker.strokeWidth,
|
|
lineDash: properties.marker.lineDash,
|
|
lineDashOffset: properties.marker.lineDashOffset
|
|
},
|
|
highlightState,
|
|
fill: properties.fill,
|
|
fillOpacity: properties.fillOpacity,
|
|
lineDash: properties.lineDash,
|
|
lineDashOffset: properties.lineDashOffset,
|
|
seriesId: this.id,
|
|
stroke: properties.stroke,
|
|
strokeOpacity: properties.strokeOpacity,
|
|
strokeWidth: properties.strokeWidth,
|
|
angleKey: properties.angleKey,
|
|
radiusKey: properties.radiusKey
|
|
};
|
|
}
|
|
getStyle(highlightState) {
|
|
const { marker, fill, fillOpacity, lineDash, lineDashOffset, stroke: stroke3, strokeOpacity, strokeWidth } = this.properties;
|
|
const { size, shape, fill: markerFill = "transparent", fillOpacity: markerFillOpacity } = marker;
|
|
const stylerResult = this.getStylerResult({}, highlightState);
|
|
stylerResult.marker ?? (stylerResult.marker = {});
|
|
return {
|
|
fill: stylerResult.fill ?? fill,
|
|
fillOpacity: stylerResult.fillOpacity ?? fillOpacity,
|
|
lineDash: stylerResult.lineDash ?? lineDash,
|
|
lineDashOffset: stylerResult.lineDashOffset ?? lineDashOffset,
|
|
stroke: stylerResult.stroke ?? stroke3,
|
|
strokeOpacity: stylerResult.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: stylerResult.strokeWidth ?? strokeWidth,
|
|
marker: {
|
|
enabled: stylerResult.marker.enabled ?? marker.enabled,
|
|
fill: stylerResult.marker.fill ?? markerFill,
|
|
fillOpacity: stylerResult.marker.fillOpacity ?? markerFillOpacity,
|
|
shape: stylerResult.marker.shape ?? shape,
|
|
size: stylerResult.marker.size ?? size,
|
|
lineDash: stylerResult.marker.lineDash ?? marker.lineDash ?? lineDash,
|
|
lineDashOffset: stylerResult.marker.lineDashOffset ?? marker.lineDashOffset ?? lineDashOffset,
|
|
stroke: stylerResult.marker.stroke ?? marker.stroke ?? stroke3,
|
|
strokeOpacity: stylerResult.marker.strokeOpacity ?? marker.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: stylerResult.marker.strokeWidth ?? marker.strokeWidth ?? strokeWidth
|
|
}
|
|
};
|
|
}
|
|
};
|
|
RadarAreaSeries.className = "RadarAreaSeries";
|
|
RadarAreaSeries.type = "radar-area";
|
|
|
|
// packages/ag-charts-enterprise/src/series/radar-area/radarAreaSeriesOptionsDef.ts
|
|
var { radarAreaSeriesThemeableOptionsDef: radarAreaSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var radarAreaSeriesOptionsDef = {
|
|
...commonSeriesOptionsDefs,
|
|
...radarAreaSeriesThemeableOptionsDef2,
|
|
type: required(constant("radar-area")),
|
|
angleKey: required(string),
|
|
radiusKey: required(string),
|
|
angleName: string,
|
|
radiusName: string,
|
|
legendItemName: string
|
|
};
|
|
radarAreaSeriesOptionsDef.angleKeyAxis = undocumented(string);
|
|
radarAreaSeriesOptionsDef.radiusKeyAxis = undocumented(string);
|
|
|
|
// packages/ag-charts-enterprise/src/series/radar-area/radarAreaModule.ts
|
|
var RadarAreaSeriesModule = {
|
|
type: "series",
|
|
name: "radar-area",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
dependencies: [PolarChartModule],
|
|
options: radarAreaSeriesOptionsDef,
|
|
defaultAxes: { angle: { type: "angle-category" /* ANGLE_CATEGORY */ }, radius: { type: "radius-number" /* RADIUS_NUMBER */ } },
|
|
axisKeys: { ["angle" /* Angle */]: "angleKeyAxis", ["radius" /* Radius */]: "radiusKeyAxis" },
|
|
themeTemplate: RADAR_AREA_SERIES_THEME,
|
|
create: (ctx) => new RadarAreaSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/radar-line/radarLineSeries.ts
|
|
var { HighlightState: HighlightState8, PointerEvents: PointerEvents11, toHighlightString: toHighlightString7 } = module_support_exports;
|
|
var RadarLineSeries = class extends RadarSeries {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.properties = new RadarSeriesProperties();
|
|
}
|
|
updatePathSelections() {
|
|
this.lineSelection.update(this.visible ? [true] : []);
|
|
}
|
|
updatePathNodes() {
|
|
const lineNode = this.getLineNode();
|
|
if (!lineNode)
|
|
return;
|
|
const style2 = this.getPathNodesStyle();
|
|
const { strokeWidth, stroke: stroke3, strokeOpacity, lineDash, lineDashOffset, opacity } = style2;
|
|
lineNode.setProperties({
|
|
fill: void 0,
|
|
lineJoin: "round",
|
|
lineCap: "round",
|
|
pointerEvents: PointerEvents11.None,
|
|
opacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
});
|
|
}
|
|
makeStylerParams(highlightStateEnum) {
|
|
const { properties } = this;
|
|
const highlightState = toHighlightString7(highlightStateEnum ?? HighlightState8.None);
|
|
return {
|
|
marker: {
|
|
fill: properties.marker.fill,
|
|
fillOpacity: properties.marker.fillOpacity,
|
|
size: properties.marker.size,
|
|
shape: properties.marker.shape,
|
|
stroke: properties.marker.stroke,
|
|
strokeOpacity: properties.marker.strokeOpacity,
|
|
strokeWidth: properties.marker.strokeWidth,
|
|
lineDash: properties.marker.lineDash,
|
|
lineDashOffset: properties.marker.lineDashOffset
|
|
},
|
|
highlightState,
|
|
lineDash: properties.lineDash,
|
|
lineDashOffset: properties.lineDashOffset,
|
|
seriesId: this.id,
|
|
stroke: properties.stroke,
|
|
strokeOpacity: properties.strokeOpacity,
|
|
strokeWidth: properties.strokeWidth,
|
|
angleKey: properties.angleKey,
|
|
radiusKey: properties.radiusKey
|
|
};
|
|
}
|
|
getStyle(highlightState) {
|
|
const { marker, lineDash, lineDashOffset, stroke: stroke3, strokeOpacity, strokeWidth } = this.properties;
|
|
const { size, shape, fill = "transparent", fillOpacity } = marker;
|
|
const stylerResult = this.getStylerResult({}, highlightState);
|
|
stylerResult.marker ?? (stylerResult.marker = {});
|
|
return {
|
|
lineDash: stylerResult.lineDash ?? lineDash,
|
|
lineDashOffset: stylerResult.lineDashOffset ?? lineDashOffset,
|
|
stroke: stylerResult.stroke ?? stroke3,
|
|
strokeOpacity: stylerResult.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: stylerResult.strokeWidth ?? strokeWidth,
|
|
marker: {
|
|
enabled: stylerResult.marker.enabled ?? marker.enabled,
|
|
fill: stylerResult.marker.fill ?? fill,
|
|
fillOpacity: stylerResult.marker.fillOpacity ?? fillOpacity,
|
|
shape: stylerResult.marker.shape ?? shape,
|
|
size: stylerResult.marker.size ?? size,
|
|
lineDash: stylerResult.marker.lineDash ?? marker.lineDash ?? lineDash,
|
|
lineDashOffset: stylerResult.marker.lineDashOffset ?? marker.lineDashOffset ?? lineDashOffset,
|
|
stroke: stylerResult.marker.stroke ?? marker.stroke ?? stroke3,
|
|
strokeOpacity: stylerResult.marker.strokeOpacity ?? marker.strokeOpacity ?? strokeOpacity,
|
|
strokeWidth: stylerResult.marker.strokeWidth ?? marker.strokeWidth ?? strokeWidth
|
|
}
|
|
};
|
|
}
|
|
};
|
|
RadarLineSeries.className = "RadarLineSeries";
|
|
RadarLineSeries.type = "radar-line";
|
|
|
|
// packages/ag-charts-enterprise/src/series/radar-line/radarLineSeriesOptionsDef.ts
|
|
var { radarLineSeriesThemeableOptionsDef: radarLineSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var radarLineSeriesOptionsDef = {
|
|
...commonSeriesOptionsDefs,
|
|
...radarLineSeriesThemeableOptionsDef2,
|
|
type: required(constant("radar-line")),
|
|
angleKey: required(string),
|
|
radiusKey: required(string),
|
|
angleName: string,
|
|
radiusName: string,
|
|
legendItemName: string
|
|
};
|
|
radarLineSeriesOptionsDef.angleKeyAxis = undocumented(string);
|
|
radarLineSeriesOptionsDef.radiusKeyAxis = undocumented(string);
|
|
|
|
// packages/ag-charts-enterprise/src/series/radar-line/radarLineModule.ts
|
|
var RadarLineSeriesModule = {
|
|
type: "series",
|
|
name: "radar-line",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
dependencies: [PolarChartModule],
|
|
options: radarLineSeriesOptionsDef,
|
|
defaultAxes: { angle: { type: "angle-category" /* ANGLE_CATEGORY */ }, radius: { type: "radius-number" /* RADIUS_NUMBER */ } },
|
|
axisKeys: { ["angle" /* Angle */]: "angleKeyAxis", ["radius" /* Radius */]: "radiusKeyAxis" },
|
|
themeTemplate: RADAR_LINE_SERIES_THEME,
|
|
create: (ctx) => new RadarLineSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-bar/radialBarSeriesProperties.ts
|
|
var { SeriesProperties: SeriesProperties9, makeSeriesTooltip: makeSeriesTooltip20, Label: Label18 } = module_support_exports;
|
|
var RadialBarSeriesProperties = class extends SeriesProperties9 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.angleKeyAxis = "angle";
|
|
this.radiusKeyAxis = "radius";
|
|
this.fill = "black";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "black";
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.cornerRadius = 0;
|
|
this.rotation = 0;
|
|
this.label = new Label18();
|
|
this.tooltip = makeSeriesTooltip20();
|
|
}
|
|
getStyle() {
|
|
const { fill, fillOpacity, stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset, cornerRadius } = this;
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
cornerRadius,
|
|
opacity: 1
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "angleKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "radiusKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "angleName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "radiusName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "angleKeyAxis", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "radiusKeyAxis", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "legendItemName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "cornerRadius", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "styler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "rotation", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "stackGroup", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "normalizedTo", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialBarSeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-bar/radialBarUtil.ts
|
|
var { SectorBox: SectorBox6, motion: motion10 } = module_support_exports;
|
|
function fixRadialBarAnimationStatus(node, datum, status) {
|
|
if (status === "updated") {
|
|
if (node.previousDatum == null || Number.isNaN(node.previousDatum.innerRadius) || Number.isNaN(node.previousDatum.outerRadius)) {
|
|
return "added";
|
|
}
|
|
if (Number.isNaN(datum.innerRadius) || Number.isNaN(datum.outerRadius)) {
|
|
return "removed";
|
|
}
|
|
}
|
|
if (status === "added" && node.previousDatum != null) {
|
|
return "updated";
|
|
}
|
|
return status;
|
|
}
|
|
function prepareRadialBarSeriesAnimationFunctions(axisZeroAngle) {
|
|
const fromFn = (sect, datum, status) => {
|
|
status = fixRadialBarAnimationStatus(sect, datum, status);
|
|
let startAngle;
|
|
let endAngle;
|
|
let innerRadius;
|
|
let outerRadius;
|
|
let clipSector;
|
|
if (status === "removed" || status === "updated") {
|
|
startAngle = sect.startAngle;
|
|
endAngle = sect.endAngle;
|
|
innerRadius = sect.innerRadius;
|
|
outerRadius = sect.outerRadius;
|
|
clipSector = sect.clipSector;
|
|
} else {
|
|
startAngle = axisZeroAngle;
|
|
endAngle = axisZeroAngle;
|
|
innerRadius = datum.innerRadius;
|
|
outerRadius = datum.outerRadius;
|
|
}
|
|
clipSector ?? (clipSector = new SectorBox6(startAngle, endAngle, innerRadius, outerRadius));
|
|
const phase = motion10.NODE_UPDATE_STATE_TO_PHASE_MAPPING[status];
|
|
return { startAngle, endAngle, innerRadius, outerRadius, clipSector, phase };
|
|
};
|
|
const toFn = (sect, datum, status) => {
|
|
let startAngle;
|
|
let endAngle;
|
|
let innerRadius;
|
|
let outerRadius;
|
|
let clipSector;
|
|
if (status === "removed") {
|
|
startAngle = axisZeroAngle;
|
|
endAngle = axisZeroAngle;
|
|
innerRadius = datum.innerRadius;
|
|
outerRadius = datum.outerRadius;
|
|
clipSector = new SectorBox6(startAngle, endAngle, innerRadius, outerRadius);
|
|
} else {
|
|
startAngle = datum.startAngle;
|
|
endAngle = datum.endAngle;
|
|
innerRadius = Number.isNaN(datum.innerRadius) ? sect.innerRadius : datum.innerRadius;
|
|
outerRadius = Number.isNaN(datum.outerRadius) ? sect.outerRadius : datum.outerRadius;
|
|
clipSector = datum.clipSector;
|
|
}
|
|
return { startAngle, endAngle, innerRadius, outerRadius, clipSector };
|
|
};
|
|
return { toFn, fromFn };
|
|
}
|
|
function resetRadialBarSelectionsFn(_node, datum) {
|
|
return {
|
|
centerX: 0,
|
|
centerY: 0,
|
|
innerRadius: datum.innerRadius,
|
|
outerRadius: datum.outerRadius,
|
|
startAngle: datum.startAngle,
|
|
endAngle: datum.endAngle,
|
|
clipSector: datum.clipSector
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-bar/radialBarSeries.ts
|
|
var {
|
|
DEFAULT_POLAR_DIRECTION_KEYS: DEFAULT_POLAR_DIRECTION_KEYS4,
|
|
DEFAULT_POLAR_DIRECTION_NAMES: DEFAULT_POLAR_DIRECTION_NAMES4,
|
|
PolarAxis: PolarAxis4,
|
|
diff: diff8,
|
|
groupAccumulativeValueProperty: groupAccumulativeValueProperty4,
|
|
keyProperty: keyProperty12,
|
|
normaliseGroupTo: normaliseGroupTo3,
|
|
valueProperty: valueProperty15,
|
|
fixNumericExtent: fixNumericExtent11,
|
|
resetLabelFn: resetLabelFn8,
|
|
seriesLabelFadeInAnimation: seriesLabelFadeInAnimation9,
|
|
seriesLabelFadeOutAnimation: seriesLabelFadeOutAnimation3,
|
|
animationValidation: animationValidation10,
|
|
createDatumId: createDatumId19,
|
|
CategoryScale: CategoryScale5,
|
|
Sector: Sector8,
|
|
SectorBox: SectorBox7,
|
|
motion: motion11,
|
|
updateLabelNode: updateLabelNode9,
|
|
getItemStyles: getItemStyles5
|
|
} = module_support_exports;
|
|
var RadialBarSeriesNodeEvent = class extends module_support_exports.SeriesNodeEvent {
|
|
constructor(type, nativeEvent, datum, series) {
|
|
super(type, nativeEvent, datum, series);
|
|
this.angleKey = series.properties.angleKey;
|
|
this.radiusKey = series.properties.radiusKey;
|
|
}
|
|
};
|
|
var RadialBarSeries = class extends module_support_exports.PolarSeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
categoryKey: "radiusValue",
|
|
propertyKeys: DEFAULT_POLAR_DIRECTION_KEYS4,
|
|
propertyNames: DEFAULT_POLAR_DIRECTION_NAMES4,
|
|
canHaveAxes: true,
|
|
animationResetFns: {
|
|
item: resetRadialBarSelectionsFn,
|
|
label: resetLabelFn8
|
|
}
|
|
});
|
|
this.properties = new RadialBarSeriesProperties();
|
|
this.NodeEvent = RadialBarSeriesNodeEvent;
|
|
this.groupScale = new CategoryScale5();
|
|
this.circleCache = { r: 0, cx: 0, cy: 0 };
|
|
}
|
|
nodeFactory() {
|
|
return new Sector8();
|
|
}
|
|
getSeriesDomain(direction) {
|
|
const { dataModel, processedData } = this;
|
|
if (!processedData || !dataModel)
|
|
return { domain: [] };
|
|
if (direction === "angle" /* Angle */) {
|
|
const xExtent = dataModel.getDomain(this, "angleValue-end", "value", processedData).domain;
|
|
const fixedXExtent = [Math.min(xExtent[0], 0), Math.max(xExtent[1], 0)];
|
|
return { domain: fixNumericExtent11(fixedXExtent) };
|
|
} else {
|
|
return dataModel.getDomain(this, "radiusValue", "key", processedData);
|
|
}
|
|
}
|
|
async processData(dataController) {
|
|
const { angleKey, radiusKey, normalizedTo } = this.properties;
|
|
const animationEnabled = !this.ctx.animationManager.isSkipped();
|
|
const stackGroupId = this.getStackId();
|
|
const stackGroupTrailingId = `${stackGroupId}-trailing`;
|
|
const extraProps = [];
|
|
if (isDefined(normalizedTo)) {
|
|
extraProps.push(normaliseGroupTo3([stackGroupId, stackGroupTrailingId], Math.abs(normalizedTo)));
|
|
}
|
|
if (this.needsDataModelDiff() && this.processedData) {
|
|
extraProps.push(diff8(this.id, this.processedData));
|
|
}
|
|
if (animationEnabled) {
|
|
extraProps.push(animationValidation10());
|
|
}
|
|
const visibleProps = this.visible ? {} : { forceValue: 0 };
|
|
const radiusScaleType = this.axes["radius" /* Radius */]?.scale.type;
|
|
const angleScaleType = this.axes["angle" /* Angle */]?.scale.type;
|
|
const allowNullKey = this.properties.allowNullKeys ?? false;
|
|
await this.requestDataModel(dataController, this.data, {
|
|
props: [
|
|
keyProperty12(radiusKey, radiusScaleType, { id: "radiusValue", allowNullKey }),
|
|
valueProperty15(angleKey, angleScaleType, {
|
|
id: "angleValue-raw",
|
|
invalidValue: null,
|
|
...visibleProps
|
|
}),
|
|
...groupAccumulativeValueProperty4(
|
|
angleKey,
|
|
"normal",
|
|
{
|
|
id: `angleValue-end`,
|
|
rangeId: `angleValue-range`,
|
|
invalidValue: null,
|
|
groupId: stackGroupId,
|
|
separateNegative: true,
|
|
...visibleProps
|
|
},
|
|
angleScaleType
|
|
),
|
|
...groupAccumulativeValueProperty4(
|
|
angleKey,
|
|
"trailing",
|
|
{
|
|
id: `angleValue-start`,
|
|
invalidValue: null,
|
|
groupId: stackGroupTrailingId,
|
|
separateNegative: true,
|
|
...visibleProps
|
|
},
|
|
angleScaleType
|
|
),
|
|
...extraProps
|
|
],
|
|
groupByKeys: true,
|
|
groupByData: false
|
|
});
|
|
this.animationState.transition("updateData");
|
|
}
|
|
didCircleChange() {
|
|
const r = this.radius;
|
|
const cx = this.centerX;
|
|
const cy = this.centerY;
|
|
const cache = this.circleCache;
|
|
if (!(r === cache.r && cx === cache.cx && cy === cache.cy)) {
|
|
this.circleCache = { r, cx, cy };
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
maybeRefreshNodeData() {
|
|
const circleChanged = this.didCircleChange();
|
|
if (!circleChanged && !this.nodeDataRefresh)
|
|
return;
|
|
this.contextNodeData = this.createNodeData();
|
|
this.nodeData = this.contextNodeData?.nodeData ?? [];
|
|
this.nodeDataRefresh = false;
|
|
}
|
|
getAxisInnerRadius() {
|
|
const radiusAxis = this.axes["radius" /* Radius */];
|
|
return radiusAxis instanceof PolarAxis4 ? this.radius * radiusAxis.innerRadiusRatio : 0;
|
|
}
|
|
createNodeData() {
|
|
const { processedData, dataModel } = this;
|
|
if (!dataModel || processedData?.type !== "grouped")
|
|
return;
|
|
const angleAxis = this.axes["angle" /* Angle */];
|
|
const radiusAxis = this.axes["radius" /* Radius */];
|
|
const angleScale = angleAxis?.scale;
|
|
const radiusScale = radiusAxis?.scale;
|
|
if (!angleScale || !radiusScale) {
|
|
return;
|
|
}
|
|
const radiusValues = dataModel.resolveKeysById(this, "radiusValue", processedData);
|
|
const angleStartValues = dataModel.resolveColumnById(this, `angleValue-start`, processedData);
|
|
const angleEndValues = dataModel.resolveColumnById(this, `angleValue-end`, processedData);
|
|
const angleRawValues = dataModel.resolveColumnById(this, `angleValue-raw`, processedData);
|
|
const angleRangeIndex = dataModel.resolveProcessedDataIndexById(this, `angleValue-range`);
|
|
let groupPaddingInner = 0;
|
|
if (radiusAxis instanceof RadiusCategoryAxis) {
|
|
groupPaddingInner = radiusAxis.groupPaddingInner;
|
|
}
|
|
const { groupScale } = this;
|
|
const { index: groupIndex, visibleGroupCount } = this.ctx.seriesStateManager.getVisiblePeerGroupIndex(this);
|
|
groupScale.domain = Array.from({ length: visibleGroupCount }).map((_, i) => String(i));
|
|
groupScale.range = [0, Math.abs(radiusScale.bandwidth ?? 0)];
|
|
groupScale.paddingInner = visibleGroupCount > 1 ? groupPaddingInner : 0;
|
|
const barWidth = groupScale.bandwidth >= 1 ? groupScale.bandwidth : groupScale.rawBandwidth;
|
|
const angleAxisReversed = angleAxis.isReversed();
|
|
const radiusAxisReversed = radiusAxis.isReversed();
|
|
const axisInnerRadius = radiusAxisReversed ? this.radius : this.getAxisInnerRadius();
|
|
const axisOuterRadius = radiusAxisReversed ? this.getAxisInnerRadius() : this.radius;
|
|
const axisTotalRadius = axisOuterRadius + axisInnerRadius;
|
|
const angleDomain = this.getSeriesDomain("angle" /* Angle */).domain;
|
|
const { angleKey, radiusKey, angleName, radiusName, legendItemName, label } = this.properties;
|
|
const getLabelNodeDatum = (datum, angleDatum, x, y) => {
|
|
const labelText = this.getLabelText(
|
|
angleDatum,
|
|
datum,
|
|
angleKey,
|
|
"angle",
|
|
angleDomain,
|
|
label,
|
|
{ value: angleDatum, datum, angleKey, radiusKey, angleName, radiusName, legendItemName }
|
|
);
|
|
if (labelText) {
|
|
return { x, y, text: labelText, textAlign: "center", textBaseline: "middle" };
|
|
}
|
|
};
|
|
const nodeData = [];
|
|
const styles = getItemStyles5(
|
|
(nodeDatum, isHighlight, highlightState) => getItemStyle(this, nodeDatum, isHighlight, highlightState)
|
|
);
|
|
const context = {
|
|
itemId: radiusKey,
|
|
nodeData,
|
|
labelData: nodeData,
|
|
styles
|
|
};
|
|
if (!this.visible)
|
|
return context;
|
|
const { dataSources } = processedData;
|
|
const rawData = dataSources.get(this.id)?.data ?? [];
|
|
for (const { datumIndex, group } of dataModel.forEachGroupDatum(this, processedData)) {
|
|
const datum = rawData[datumIndex];
|
|
const radiusDatum = radiusValues[datumIndex];
|
|
if (radiusDatum === void 0 && !this.properties.allowNullKeys)
|
|
return;
|
|
const angleDatum = angleRawValues[datumIndex];
|
|
const angleStartDatum = angleStartValues[datumIndex];
|
|
const angleEndDatum = angleEndValues[datumIndex];
|
|
const isPositive = angleDatum >= 0 && !Object.is(angleDatum, -0);
|
|
const angleRange = group.aggregation[angleRangeIndex][isPositive ? 1 : 0];
|
|
const reversed = isPositive === angleAxisReversed;
|
|
let startAngle = angleScale.convert(angleStartDatum, { clamp: true });
|
|
let endAngle = angleScale.convert(angleEndDatum, { clamp: true });
|
|
let rangeStartAngle = angleScale.convert(0, { clamp: true });
|
|
let rangeEndAngle = angleScale.convert(angleRange, { clamp: true });
|
|
if (reversed) {
|
|
[rangeStartAngle, rangeEndAngle] = [rangeEndAngle, rangeStartAngle];
|
|
[startAngle, endAngle] = [endAngle, startAngle];
|
|
}
|
|
const dataRadius = axisTotalRadius - radiusScale.convert(radiusDatum);
|
|
const innerRadius = dataRadius + groupScale.convert(String(groupIndex));
|
|
const outerRadius = innerRadius + barWidth;
|
|
const midRadius = (innerRadius + outerRadius) / 2;
|
|
const midAngle = startAngle + angleBetween(startAngle, endAngle) / 2;
|
|
const x = Math.cos(midAngle) * midRadius;
|
|
const y = Math.sin(midAngle) * midRadius;
|
|
const labelNodeDatum = this.properties.label.enabled ? getLabelNodeDatum(datum, angleDatum, x, y) : void 0;
|
|
const clipSector = new SectorBox7(startAngle, endAngle, innerRadius, outerRadius);
|
|
nodeData.push({
|
|
series: this,
|
|
datum,
|
|
datumIndex,
|
|
point: { x, y, size: 0 },
|
|
midPoint: { x, y },
|
|
label: labelNodeDatum,
|
|
angleValue: angleDatum,
|
|
radiusValue: radiusDatum,
|
|
innerRadius,
|
|
outerRadius,
|
|
startAngle: rangeStartAngle,
|
|
endAngle: rangeEndAngle,
|
|
clipSector,
|
|
reversed,
|
|
index: datumIndex
|
|
});
|
|
}
|
|
return context;
|
|
}
|
|
update({ seriesRect }) {
|
|
const resize = this.checkResize(seriesRect);
|
|
this.maybeRefreshNodeData();
|
|
this.contentGroup.translationX = this.centerX;
|
|
this.contentGroup.translationY = this.centerY;
|
|
this.highlightGroup.translationX = this.centerX;
|
|
this.highlightGroup.translationY = this.centerY;
|
|
if (this.labelGroup) {
|
|
this.labelGroup.translationX = this.centerX;
|
|
this.labelGroup.translationY = this.centerY;
|
|
}
|
|
this.updateSectorSelection(this.itemSelection, false);
|
|
this.updateSectorSelection(this.highlightSelection, true);
|
|
this.updateLabels();
|
|
if (resize) {
|
|
this.animationState.transition("resize");
|
|
}
|
|
this.animationState.transition("update");
|
|
}
|
|
updateSectorSelection(selection, isHighlight) {
|
|
let selectionData = [];
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
if (isHighlight) {
|
|
if (activeHighlight?.datum && activeHighlight.series === this) {
|
|
selectionData.push(activeHighlight);
|
|
}
|
|
} else {
|
|
selectionData = this.nodeData;
|
|
}
|
|
const { contextNodeData } = this;
|
|
if (!contextNodeData) {
|
|
return;
|
|
}
|
|
const highlightedDatum = this.ctx.highlightManager.getActiveHighlight();
|
|
const fillBBox = this.getShapeFillBBox();
|
|
const hasItemStylers = this.hasItemStylers();
|
|
selection.update(selectionData, void 0, (datum) => this.getDatumId(datum)).each((node, nodeDatum) => {
|
|
const datum = readDatum(nodeDatum);
|
|
if (datum == null)
|
|
return;
|
|
if (hasItemStylers) {
|
|
const highlightState = this.getHighlightState(activeHighlight, isHighlight, nodeDatum.datumIndex);
|
|
nodeDatum.style = getItemStyle(this, nodeDatum, isHighlight, highlightState);
|
|
}
|
|
const style2 = nodeDatum.style ?? contextNodeData.styles[this.getHighlightState(highlightedDatum, isHighlight, nodeDatum.datumIndex)];
|
|
const cornerRadius = style2.cornerRadius;
|
|
const fill = style2.fill;
|
|
const fillParams = isGradientFill(fill) && fill.bounds !== "item" ? { centerX: 0, centerY: 0 } : void 0;
|
|
node.setStyleProperties(style2, fillBBox, fillParams);
|
|
node.lineJoin = "round";
|
|
node.inset = node.stroke == null ? 0 : node.strokeWidth / 2;
|
|
node.startInnerCornerRadius = datum.reversed ? cornerRadius : 0;
|
|
node.startOuterCornerRadius = datum.reversed ? cornerRadius : 0;
|
|
node.endInnerCornerRadius = datum.reversed ? 0 : cornerRadius;
|
|
node.endOuterCornerRadius = datum.reversed ? 0 : cornerRadius;
|
|
if (isHighlight) {
|
|
node.startAngle = nodeDatum.startAngle;
|
|
node.endAngle = nodeDatum.endAngle;
|
|
node.clipSector = nodeDatum.clipSector;
|
|
node.innerRadius = nodeDatum.innerRadius;
|
|
node.outerRadius = nodeDatum.outerRadius;
|
|
}
|
|
});
|
|
}
|
|
updateLabels() {
|
|
const { properties } = this;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightDatum = activeHighlight?.series === this && activeHighlight?.datum ? activeHighlight : void 0;
|
|
const highlightData = highlightDatum ? [highlightDatum] : [];
|
|
this.labelSelection.update(this.nodeData).each((node, datum) => {
|
|
const isHighlight = false;
|
|
updateLabelNode9(this, node, properties, properties.label, datum.label, isHighlight, activeHighlight);
|
|
node.fillOpacity = this.getHighlightStyle(isHighlight, datum.datumIndex).opacity ?? 1;
|
|
});
|
|
this.highlightLabelSelection.update(highlightData, void 0, (datum) => this.getDatumId(datum)).each((node, datum) => {
|
|
const isHighlight = true;
|
|
updateLabelNode9(this, node, properties, properties.label, datum.label, isHighlight, activeHighlight);
|
|
node.fillOpacity = this.getHighlightStyle(isHighlight, datum.datumIndex).opacity ?? 1;
|
|
});
|
|
}
|
|
getBarTransitionFunctions() {
|
|
const angleScale = this.axes["angle" /* Angle */]?.scale;
|
|
let axisZeroAngle = 0;
|
|
if (!angleScale) {
|
|
return prepareRadialBarSeriesAnimationFunctions(axisZeroAngle);
|
|
}
|
|
const d0 = Math.min(angleScale.domain[0], angleScale.domain[1]);
|
|
const d1 = Math.max(angleScale.domain[0], angleScale.domain[1]);
|
|
if (d0 <= 0 && d1 >= 0) {
|
|
axisZeroAngle = angleScale.convert(0);
|
|
}
|
|
return prepareRadialBarSeriesAnimationFunctions(axisZeroAngle);
|
|
}
|
|
animateEmptyUpdateReady() {
|
|
const { labelSelection } = this;
|
|
const fns = this.getBarTransitionFunctions();
|
|
motion11.fromToMotion(this.id, "datums", this.ctx.animationManager, [this.itemSelection], fns);
|
|
seriesLabelFadeInAnimation9(
|
|
this,
|
|
"labels",
|
|
this.ctx.animationManager,
|
|
labelSelection,
|
|
this.highlightLabelSelection
|
|
);
|
|
}
|
|
animateClearingUpdateEmpty() {
|
|
const { itemSelection } = this;
|
|
const { animationManager } = this.ctx;
|
|
const fns = this.getBarTransitionFunctions();
|
|
motion11.fromToMotion(this.id, "datums", animationManager, [itemSelection], fns);
|
|
seriesLabelFadeOutAnimation3(
|
|
this,
|
|
"labels",
|
|
animationManager,
|
|
this.labelSelection,
|
|
this.highlightLabelSelection
|
|
);
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const { id: seriesId, dataModel, processedData, axes, properties } = this;
|
|
const { angleKey, angleName, radiusKey, radiusName, legendItemName, tooltip } = properties;
|
|
const angleAxis = axes["angle" /* Angle */];
|
|
const radiusAxis = axes["radius" /* Radius */];
|
|
const nodeDatum = this.nodeData?.[datumIndex];
|
|
if (!dataModel || !processedData || !angleAxis || !radiusAxis || !nodeDatum)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const radiusValue = dataModel.resolveKeysById(this, `radiusValue`, processedData)[datumIndex];
|
|
const angleValue = dataModel.resolveColumnById(this, `angleValue-raw`, processedData)[datumIndex];
|
|
if (radiusValue === void 0 && !this.properties.allowNullKeys)
|
|
return;
|
|
const format = getItemStyle(this, nodeDatum, false);
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading: this.getAxisValueText(radiusAxis, "tooltip", radiusValue, datum, radiusKey, void 0),
|
|
symbol: this.legendItemSymbol(),
|
|
data: [
|
|
{
|
|
label: angleName,
|
|
fallbackLabel: angleKey,
|
|
value: this.getAxisValueText(angleAxis, "tooltip", angleValue, datum, angleKey, void 0),
|
|
missing: module_support_exports.isTooltipValueMissing(angleValue)
|
|
}
|
|
]
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title: angleName,
|
|
angleKey,
|
|
angleName,
|
|
radiusKey,
|
|
radiusName,
|
|
legendItemName,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
pickNodeClosestDatum(point) {
|
|
return this.pickNodeNearestDistantObject(point, this.itemSelection.nodes());
|
|
}
|
|
legendItemSymbol() {
|
|
const { fill, stroke: stroke3, fillOpacity, strokeOpacity, strokeWidth, lineDash, lineDashOffset } = getStyle(
|
|
this,
|
|
false,
|
|
module_support_exports.HighlightState.None
|
|
);
|
|
const markerStyle = {
|
|
fill: fill ?? "rgba(0, 0, 0, 0)",
|
|
stroke: stroke3 ?? "rgba(0, 0, 0, 0)",
|
|
fillOpacity,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash,
|
|
lineDashOffset
|
|
};
|
|
if (isGradientFill(markerStyle.fill)) {
|
|
markerStyle.fill = { ...markerStyle.fill, gradient: "linear", rotation: 0, reverse: false };
|
|
}
|
|
return {
|
|
marker: markerStyle
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
if (legendType !== "category") {
|
|
return [];
|
|
}
|
|
const { id: seriesId, visible } = this;
|
|
const { angleKey, angleName, legendItemName, showInLegend } = this.properties;
|
|
return [
|
|
{
|
|
legendType: "category",
|
|
id: seriesId,
|
|
itemId: angleKey,
|
|
seriesId,
|
|
enabled: visible,
|
|
label: {
|
|
text: legendItemName ?? angleName ?? angleKey
|
|
},
|
|
symbol: this.legendItemSymbol(),
|
|
legendItemName,
|
|
hideInLegend: !showInLegend
|
|
}
|
|
];
|
|
}
|
|
getDatumId(datum) {
|
|
return createDatumId19(datum.radiusValue);
|
|
}
|
|
computeLabelsBBox() {
|
|
return null;
|
|
}
|
|
getStackId() {
|
|
const groupIndex = this.seriesGrouping?.groupIndex ?? this.id;
|
|
return `radialBar-stack-${groupIndex}-xValues`;
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null || this.properties.styler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
RadialBarSeries.className = "RadialBarSeries";
|
|
RadialBarSeries.type = "radial-bar";
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-bar/radialBarSeriesOptionsDef.ts
|
|
var { radialBarSeriesThemeableOptionsDef: radialBarSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var radialBarSeriesOptionsDef = {
|
|
...commonSeriesOptionsDefs,
|
|
...radialBarSeriesThemeableOptionsDef2,
|
|
type: required(constant("radial-bar")),
|
|
angleKey: required(string),
|
|
radiusKey: required(string),
|
|
angleName: string,
|
|
radiusName: string,
|
|
legendItemName: string,
|
|
grouped: boolean,
|
|
stacked: boolean,
|
|
stackGroup: string,
|
|
normalizedTo: number
|
|
};
|
|
radialBarSeriesOptionsDef.angleKeyAxis = undocumented(string);
|
|
radialBarSeriesOptionsDef.radiusKeyAxis = undocumented(string);
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-bar/radialBarThemes.ts
|
|
var RADIAL_BAR_SERIES_THEME = {
|
|
series: {
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_CONIC_SERIES_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: { $palette: "stroke" },
|
|
strokeWidth: { $isUserOption: ["./stroke", 1, 0] },
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "chartBackgroundColor" }
|
|
},
|
|
highlight: MULTI_SERIES_HIGHLIGHT_STYLE
|
|
},
|
|
axes: {
|
|
["radius-category" /* RADIUS_CATEGORY */]: {
|
|
innerRadiusRatio: 0.2,
|
|
groupPaddingInner: 0.2,
|
|
paddingInner: 0.2,
|
|
paddingOuter: 0.1
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-bar/radialBarModule.ts
|
|
var RadialBarSeriesModule = {
|
|
type: "series",
|
|
name: "radial-bar",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
stackable: true,
|
|
groupable: true,
|
|
version: VERSION,
|
|
dependencies: [PolarChartModule],
|
|
options: radialBarSeriesOptionsDef,
|
|
defaultAxes: { angle: { type: "angle-number" /* ANGLE_NUMBER */ }, radius: { type: "radius-category" /* RADIUS_CATEGORY */ } },
|
|
axisKeys: { ["angle" /* Angle */]: "angleKeyAxis", ["radius" /* Radius */]: "radiusKeyAxis" },
|
|
themeTemplate: RADIAL_BAR_SERIES_THEME,
|
|
create: (ctx) => new RadialBarSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-column/radialColumnSeriesProperties.ts
|
|
var RadialColumnSeriesProperties = class extends RadialColumnSeriesBaseProperties {
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesProperties.prototype, "columnWidthRatio", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], RadialColumnSeriesProperties.prototype, "maxColumnWidthRatio", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-column/radialColumnSeries.ts
|
|
var { PolarAxis: PolarAxis5, RadialColumnShape: RadialColumnShape2, getRadialColumnWidth: getRadialColumnWidth2 } = module_support_exports;
|
|
var RadialColumnSeries = class extends RadialColumnSeriesBase {
|
|
constructor(moduleCtx) {
|
|
super(moduleCtx, {
|
|
animationResetFns: {
|
|
item: resetRadialColumnSelectionFn
|
|
}
|
|
});
|
|
this.properties = new RadialColumnSeriesProperties();
|
|
}
|
|
getStackId() {
|
|
const groupIndex = this.seriesGrouping?.groupIndex ?? this.id;
|
|
return `radarColumn-stack-${groupIndex}-yValues`;
|
|
}
|
|
nodeFactory() {
|
|
return new RadialColumnShape2();
|
|
}
|
|
getColumnTransitionFunctions() {
|
|
const axisZeroRadius = this.isRadiusAxisReversed() ? this.radius : this.getAxisInnerRadius();
|
|
return prepareRadialColumnAnimationFunctions(axisZeroRadius);
|
|
}
|
|
isRadiusAxisCircle() {
|
|
const radiusAxis = this.axes["radius" /* Radius */];
|
|
return radiusAxis instanceof PolarAxis5 ? radiusAxis.shape === "circle" : false;
|
|
}
|
|
updateItemPath(node, datum, highlight5) {
|
|
node.isBeveled = this.isRadiusAxisCircle();
|
|
if (highlight5) {
|
|
node.innerRadius = datum.innerRadius;
|
|
node.outerRadius = datum.outerRadius;
|
|
node.startAngle = datum.startAngle;
|
|
node.endAngle = datum.endAngle;
|
|
node.columnWidth = datum.columnWidth;
|
|
node.axisInnerRadius = datum.axisInnerRadius;
|
|
node.axisOuterRadius = datum.axisOuterRadius;
|
|
}
|
|
}
|
|
getColumnWidth(startAngle, endAngle) {
|
|
const { columnWidthRatio = 0.5, maxColumnWidthRatio = 0.5 } = this.properties;
|
|
return getRadialColumnWidth2(startAngle, endAngle, this.radius, columnWidthRatio, maxColumnWidthRatio);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null || this.properties.styler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
RadialColumnSeries.className = "RadialColumnSeries";
|
|
RadialColumnSeries.type = "radial-column";
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-column/radialColumnSeriesOptionsDef.ts
|
|
var { radialColumnSeriesThemeableOptionsDef: radialColumnSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var radialColumnSeriesOptionsDef = {
|
|
...commonSeriesOptionsDefs,
|
|
...radialColumnSeriesThemeableOptionsDef2,
|
|
type: required(constant("radial-column")),
|
|
angleKey: required(string),
|
|
radiusKey: required(string),
|
|
angleName: string,
|
|
radiusName: string,
|
|
legendItemName: string,
|
|
grouped: boolean,
|
|
stacked: boolean,
|
|
stackGroup: string,
|
|
normalizedTo: number
|
|
};
|
|
radialColumnSeriesOptionsDef.angleKeyAxis = undocumented(string);
|
|
radialColumnSeriesOptionsDef.radiusKeyAxis = undocumented(string);
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-column/radialColumnThemes.ts
|
|
var RADIAL_COLUMN_SERIES_THEME = {
|
|
series: {
|
|
fill: {
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $palette: "fill" },
|
|
["gradient", FILL_GRADIENT_RADIAL_SERIES_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
},
|
|
stroke: { $palette: "stroke" },
|
|
columnWidthRatio: 0.5,
|
|
maxColumnWidthRatio: 0.5,
|
|
strokeWidth: { $isUserOption: ["./stroke", 1, 0] },
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" }
|
|
},
|
|
highlight: MULTI_SERIES_HIGHLIGHT_STYLE
|
|
},
|
|
axes: {
|
|
["angle-category" /* ANGLE_CATEGORY */]: {
|
|
shape: { $findFirstSiblingNotOperation: "circle" /* CIRCLE */ },
|
|
groupPaddingInner: 0,
|
|
paddingInner: 0,
|
|
label: {
|
|
spacing: 10
|
|
}
|
|
},
|
|
["radius-number" /* RADIUS_NUMBER */]: {
|
|
shape: { $findFirstSiblingNotOperation: "circle" /* CIRCLE */ },
|
|
innerRadiusRatio: 0.5
|
|
}
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/radial-column/radialColumnModule.ts
|
|
var RadialColumnSeriesModule = {
|
|
type: "series",
|
|
name: "radial-column",
|
|
chartType: "polar",
|
|
enterprise: true,
|
|
stackable: true,
|
|
groupable: true,
|
|
version: VERSION,
|
|
dependencies: [PolarChartModule],
|
|
options: radialColumnSeriesOptionsDef,
|
|
defaultAxes: { angle: { type: "angle-category" /* ANGLE_CATEGORY */ }, radius: { type: "radius-number" /* RADIUS_NUMBER */ } },
|
|
axisKeys: { ["angle" /* Angle */]: "angleKeyAxis", ["radius" /* Radius */]: "radiusKeyAxis" },
|
|
themeTemplate: RADIAL_COLUMN_SERIES_THEME,
|
|
create: (ctx) => new RadialColumnSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/module-bundles/polar.ts
|
|
var AllPolarModule2 = [
|
|
AllPolarModule,
|
|
AngleNumberAxisModule,
|
|
AngleCategoryAxisModule,
|
|
RadiusNumberAxisModule,
|
|
RadiusCategoryAxisModule,
|
|
NightingaleSeriesModule,
|
|
RadarAreaSeriesModule,
|
|
RadarLineSeriesModule,
|
|
RadialBarSeriesModule,
|
|
RadialColumnSeriesModule,
|
|
AnimationModule,
|
|
ContextMenuModule,
|
|
DataSourceModule,
|
|
GradientLegendModule
|
|
].flat();
|
|
|
|
// packages/ag-charts-enterprise/src/charts/topologyChart.ts
|
|
var { Chart: Chart3, MercatorScale: MercatorScale2 } = module_support_exports;
|
|
function isTopologySeries(series) {
|
|
return series.type === "map-shape" || series.type === "map-line" || series.type === "map-marker" || series.type === "map-shape-background" || series.type === "map-line-background";
|
|
}
|
|
var TopologyChart = class extends Chart3 {
|
|
constructor(options, resources) {
|
|
super(options, resources);
|
|
this.xAxis = { id: createId(module_support_exports.Axis), direction: "x" /* X */ };
|
|
this.yAxis = { id: createId(module_support_exports.Axis), direction: "y" /* Y */ };
|
|
this.ctx.zoomManager.setAxes([this.xAxis, this.yAxis]);
|
|
}
|
|
getChartType() {
|
|
return "topology";
|
|
}
|
|
updateData() {
|
|
super.updateData();
|
|
const options = this.getOptions();
|
|
if (this.topology !== options.topology) {
|
|
this.topology = options.topology;
|
|
}
|
|
const { topology } = this;
|
|
for (const series of this.series) {
|
|
if (isTopologySeries(series)) {
|
|
series.setChartTopology(topology);
|
|
}
|
|
}
|
|
}
|
|
performLayout(ctx) {
|
|
const { seriesRoot, annotationRoot } = this;
|
|
const seriesRect = ctx.layoutBox.clone().shrink(this.seriesArea.getPadding());
|
|
this.seriesRect = seriesRect;
|
|
this.animationRect = seriesRect;
|
|
const mapSeries = this.series;
|
|
const combinedBbox = mapSeries.reduce((combined, series) => {
|
|
if (!series.visible)
|
|
return combined;
|
|
const bbox = series.topologyBounds;
|
|
if (bbox == null)
|
|
return combined;
|
|
if (combined == null)
|
|
return bbox;
|
|
return combined.merge(bbox);
|
|
}, void 0);
|
|
let scale2;
|
|
if (combinedBbox != null) {
|
|
const { lon0, lat0, lon1, lat1 } = combinedBbox;
|
|
const domain = [
|
|
[lon0, lat0],
|
|
[lon1, lat1]
|
|
];
|
|
const bounds = MercatorScale2.bounds(domain);
|
|
const { width: width2, height: height2 } = seriesRect;
|
|
const viewBoxScale = Math.min(width2 / bounds.width, height2 / bounds.height);
|
|
const viewBoxWidth = bounds.width * viewBoxScale;
|
|
const viewBoxHeight = bounds.height * viewBoxScale;
|
|
const viewBoxOriginX = (width2 - viewBoxWidth) / 2;
|
|
const viewBoxOriginY = (height2 - viewBoxHeight) / 2;
|
|
const x0 = viewBoxOriginX;
|
|
const y0 = viewBoxOriginY;
|
|
const x1 = viewBoxOriginX + viewBoxWidth;
|
|
const y1 = viewBoxOriginY + viewBoxHeight;
|
|
const xZoom = this.ctx.zoomManager.getAxisZoom(this.xAxis.id);
|
|
const yZoom = this.ctx.zoomManager.getAxisZoom(this.yAxis.id);
|
|
const xSpan = (x1 - x0) / (xZoom.max - xZoom.min);
|
|
const xStart = x0 - xSpan * xZoom.min;
|
|
const ySpan = (y1 - y0) / (1 - yZoom.min - (1 - yZoom.max));
|
|
const yStart = y0 - ySpan * (1 - yZoom.max);
|
|
scale2 = new MercatorScale2(domain, [
|
|
[xStart, yStart],
|
|
[xStart + xSpan, yStart + ySpan]
|
|
]);
|
|
}
|
|
for (const series of mapSeries) {
|
|
series.scale = scale2;
|
|
}
|
|
const seriesVisible = this.series.some((s) => s.visible);
|
|
seriesRoot.visible = seriesVisible;
|
|
for (const group of [seriesRoot, annotationRoot]) {
|
|
group.translationX = Math.floor(seriesRect.x);
|
|
group.translationY = Math.floor(seriesRect.y);
|
|
group.setClipRect(seriesRect.clone());
|
|
}
|
|
this.ctx.layoutManager.emitLayoutComplete(ctx, {
|
|
series: { visible: seriesVisible, rect: seriesRect, paddedRect: ctx.layoutBox }
|
|
});
|
|
}
|
|
};
|
|
TopologyChart.className = "TopologyChart";
|
|
TopologyChart.type = "topology";
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], TopologyChart.prototype, "topology", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/charts/topologyChartModule.ts
|
|
var { topologyChartOptionsDefs: topologyChartOptionsDefs2 } = module_support_exports;
|
|
var TopologyChartModule = {
|
|
type: "chart",
|
|
name: "topology",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
options: topologyChartOptionsDefs2,
|
|
create(options, resources) {
|
|
return new TopologyChart(options, resources);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-util/mapThemeDefaults.ts
|
|
var MAP_THEME_DEFAULTS = {
|
|
zoom: {
|
|
axes: "xy",
|
|
anchorPointX: "pointer",
|
|
anchorPointY: "pointer",
|
|
buttons: {
|
|
// @ts-expect-error undocumented options
|
|
anchorPointX: "middle",
|
|
anchorPointY: "middle"
|
|
}
|
|
},
|
|
legend: {
|
|
enabled: false
|
|
}
|
|
};
|
|
function applyMapPalette(object2) {
|
|
const clone2 = deepClone(object2);
|
|
jsonWalk(clone2, (value) => {
|
|
if (typeof value === "object" && "$palette" in value) {
|
|
value["$mapPalette"] = value["$palette"];
|
|
delete value["$palette"];
|
|
}
|
|
});
|
|
return clone2;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-util/lineStringUtil.ts
|
|
var delta4 = 1e-9;
|
|
function lineSegmentDistanceToPointSquared(a, b, x, y) {
|
|
const [ax, ay] = a;
|
|
const [bx, by] = b;
|
|
const abx = bx - ax;
|
|
const aby = by - ay;
|
|
const l = abx * abx + aby * aby;
|
|
let x0;
|
|
let y0;
|
|
if (Math.abs(l) < delta4) {
|
|
x0 = ax;
|
|
y0 = ay;
|
|
} else {
|
|
let t = ((x - ax) * abx + (y - ay) * aby) / l;
|
|
t = Math.max(0, Math.min(1, t));
|
|
x0 = ax + t * (bx - ax);
|
|
y0 = ay + t * (by - ay);
|
|
}
|
|
const dx2 = x - x0;
|
|
const dy2 = y - y0;
|
|
return dx2 * dx2 + dy2 * dy2;
|
|
}
|
|
function lineStringDistance(lineString, x, y) {
|
|
let minDistanceSquared = Infinity;
|
|
let p0 = lineString.at(-1);
|
|
for (const p1 of lineString) {
|
|
minDistanceSquared = Math.min(minDistanceSquared, lineSegmentDistanceToPointSquared(p0, p1, x, y));
|
|
p0 = p1;
|
|
}
|
|
return Math.sqrt(minDistanceSquared);
|
|
}
|
|
function lineStringLength(lineSegment) {
|
|
let [x0, y0] = lineSegment[0];
|
|
let totalDistance = 0;
|
|
for (let i = 1; i < lineSegment.length; i += 1) {
|
|
const [x1, y1] = lineSegment[i];
|
|
const distance2 = Math.hypot(x1 - x0, y1 - y0);
|
|
totalDistance += distance2;
|
|
x0 = x1;
|
|
y0 = y1;
|
|
}
|
|
return totalDistance;
|
|
}
|
|
function lineStringCenter(lineSegment) {
|
|
if (lineSegment.length === 0)
|
|
return;
|
|
const targetDistance = lineStringLength(lineSegment) / 2;
|
|
let [x0, y0] = lineSegment[0];
|
|
let totalDistance = 0;
|
|
for (let i = 1; i < lineSegment.length; i += 1) {
|
|
const [x1, y1] = lineSegment[i];
|
|
const segmentDistance = Math.hypot(x1 - x0, y1 - y0);
|
|
const nextDistance = totalDistance + segmentDistance;
|
|
if (nextDistance > targetDistance) {
|
|
const ratio2 = (targetDistance - totalDistance) / segmentDistance;
|
|
const point = [x0 + (x1 - x0) * ratio2, y0 + (y1 - y0) * ratio2];
|
|
const angle2 = Math.atan2(y1 - y0, x1 - x0);
|
|
return { point, angle: angle2 };
|
|
}
|
|
totalDistance = nextDistance;
|
|
x0 = x1;
|
|
y0 = y1;
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-util/lonLatBbox.ts
|
|
var LonLatBBox = class _LonLatBBox {
|
|
constructor(lon0, lat0, lon1, lat1) {
|
|
this.lon0 = lon0;
|
|
this.lat0 = lat0;
|
|
this.lon1 = lon1;
|
|
this.lat1 = lat1;
|
|
}
|
|
extend(lon0, lat0, lon1, lat1) {
|
|
this.lon0 = Math.min(this.lon0, lon0);
|
|
this.lat0 = Math.min(this.lat0, lat0);
|
|
this.lon1 = Math.max(this.lon1, lon1);
|
|
this.lat1 = Math.max(this.lat1, lat1);
|
|
return this;
|
|
}
|
|
merge(other) {
|
|
return this.extend(other.lon0, other.lat0, other.lon1, other.lat1);
|
|
}
|
|
static extend(into, lon0, lat0, lon1, lat1) {
|
|
return into ? into.extend(lon0, lat0, lon1, lat1) : new _LonLatBBox(lon0, lat0, lon1, lat1);
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-util/polygonUtil.ts
|
|
function polygonBbox(polygon, into) {
|
|
for (const coordinates of polygon) {
|
|
const [lon, lat] = coordinates;
|
|
into = LonLatBBox.extend(into, lon, lat, lon, lat);
|
|
}
|
|
return into;
|
|
}
|
|
function polygonCentroid(polygon) {
|
|
if (polygon.length === 0)
|
|
return;
|
|
let x = 0;
|
|
let y = 0;
|
|
let k = 0;
|
|
let [x0, y0] = polygon.at(-1);
|
|
for (const [x1, y1] of polygon) {
|
|
const c = x0 * y1 - x1 * y0;
|
|
k += c;
|
|
x += (x0 + x1) * c;
|
|
y += (y0 + y1) * c;
|
|
x0 = x1;
|
|
y0 = y1;
|
|
}
|
|
k *= 3;
|
|
return [x / k, y / k];
|
|
}
|
|
function polygonDistance(polygons, x, y) {
|
|
let inside = false;
|
|
let minDistanceSquared = Infinity;
|
|
for (const polygon of polygons) {
|
|
let p0 = polygon.at(-1);
|
|
let [x0, y0] = p0;
|
|
for (const p1 of polygon) {
|
|
const [x1, y1] = p1;
|
|
if (y1 > y !== y0 > y && x < (x0 - x1) * (y - y1) / (y0 - y1) + x1) {
|
|
inside = !inside;
|
|
}
|
|
minDistanceSquared = Math.min(minDistanceSquared, lineSegmentDistanceToPointSquared(p0, p1, x, y));
|
|
p0 = p1;
|
|
x0 = x1;
|
|
y0 = y1;
|
|
}
|
|
}
|
|
return (inside ? -1 : 1) * Math.sqrt(minDistanceSquared);
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-util/geoGeometry.ts
|
|
var { Path: Path15, ExtendedPath2D: ExtendedPath2D6, BBox: BBox29 } = module_support_exports;
|
|
var GeoGeometry = class extends Path15 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.projectedGeometry = void 0;
|
|
this.renderMode = 3 /* All */;
|
|
// Keep non-filled shapes separate so we don't fill them
|
|
this.strokePath = new ExtendedPath2D6();
|
|
}
|
|
computeBBox() {
|
|
if (this.dirtyPath || this.isDirtyPath()) {
|
|
this.updatePath();
|
|
this.dirtyPath = false;
|
|
}
|
|
return this.bbox?.clone();
|
|
}
|
|
updatePath() {
|
|
const { projectedGeometry } = this;
|
|
this.strokePath.clear();
|
|
this.path.clear();
|
|
this.bbox = projectedGeometry == null ? void 0 : this.drawGeometry(projectedGeometry, void 0);
|
|
}
|
|
drawPath(ctx) {
|
|
super.drawPath(ctx);
|
|
this.renderStroke(ctx, this.strokePath.getPath2D());
|
|
}
|
|
containsPoint(x, y) {
|
|
const { projectedGeometry } = this;
|
|
if (projectedGeometry == null)
|
|
return false;
|
|
if (!this.getBBox().containsPoint(x, y))
|
|
return false;
|
|
return this.geometryDistance(projectedGeometry, x, y) <= 0;
|
|
}
|
|
distanceSquared(x, y) {
|
|
const { projectedGeometry } = this;
|
|
if (projectedGeometry == null)
|
|
return Infinity;
|
|
const distance2 = this.geometryDistance(projectedGeometry, x, y);
|
|
return distance2 > 0 ? distance2 * distance2 : 0;
|
|
}
|
|
geometryDistance(geometry, x, y) {
|
|
const { renderMode, strokeWidth } = this;
|
|
const drawPolygons = (renderMode & 1 /* Polygons */) !== 0;
|
|
const drawLines = (renderMode & 2 /* Lines */) !== 0;
|
|
const minStrokeDistance = Math.max(strokeWidth / 2, 1) + 1;
|
|
switch (geometry.type) {
|
|
case "GeometryCollection":
|
|
return geometry.geometries.reduce(
|
|
(minDistance, g) => Math.min(minDistance, this.geometryDistance(g, x, y)),
|
|
Infinity
|
|
);
|
|
case "MultiPolygon":
|
|
return drawPolygons ? geometry.coordinates.reduce(
|
|
(minDistance, polygon) => Math.min(minDistance, Math.max(polygonDistance(polygon, x, y), 0)),
|
|
Infinity
|
|
) : Infinity;
|
|
case "Polygon":
|
|
return drawPolygons ? Math.max(polygonDistance(geometry.coordinates, x, y), 0) : Infinity;
|
|
case "MultiLineString":
|
|
return drawLines ? geometry.coordinates.reduce((minDistance, lineString) => {
|
|
return Math.min(
|
|
minDistance,
|
|
Math.max(lineStringDistance(lineString, x, y) - minStrokeDistance, 0)
|
|
);
|
|
}, Infinity) : Infinity;
|
|
case "LineString":
|
|
return drawLines ? Math.max(lineStringDistance(geometry.coordinates, x, y) - minStrokeDistance, 0) : Infinity;
|
|
case "MultiPoint":
|
|
case "Point":
|
|
default:
|
|
return Infinity;
|
|
}
|
|
}
|
|
shouldDrawPolygons() {
|
|
return (this.renderMode & 1 /* Polygons */) !== 0;
|
|
}
|
|
shouldDrawLines() {
|
|
return (this.renderMode & 2 /* Lines */) !== 0;
|
|
}
|
|
drawGeometryCollection(geometries, bbox) {
|
|
for (const g of geometries) {
|
|
bbox = this.drawGeometry(g, bbox);
|
|
}
|
|
return bbox;
|
|
}
|
|
drawMultiPolygon(coordinates, bbox) {
|
|
if (!this.shouldDrawPolygons())
|
|
return bbox;
|
|
for (const polygon of coordinates) {
|
|
bbox = this.drawPolygon(this.path, polygon, bbox);
|
|
}
|
|
return bbox;
|
|
}
|
|
drawSinglePolygon(coordinates, bbox) {
|
|
if (!this.shouldDrawPolygons())
|
|
return bbox;
|
|
return this.drawPolygon(this.path, coordinates, bbox);
|
|
}
|
|
drawMultiLineString(coordinates, bbox) {
|
|
if (!this.shouldDrawLines())
|
|
return bbox;
|
|
for (const lineString of coordinates) {
|
|
bbox = this.drawLineString(this.strokePath, lineString, bbox, false);
|
|
}
|
|
return bbox;
|
|
}
|
|
drawSingleLineString(coordinates, bbox) {
|
|
if (!this.shouldDrawLines())
|
|
return bbox;
|
|
return this.drawLineString(this.strokePath, coordinates, bbox, false);
|
|
}
|
|
drawGeometry(geometry, bbox) {
|
|
switch (geometry.type) {
|
|
case "GeometryCollection":
|
|
return this.drawGeometryCollection(geometry.geometries, bbox);
|
|
case "MultiPolygon":
|
|
return this.drawMultiPolygon(geometry.coordinates, bbox);
|
|
case "Polygon":
|
|
return this.drawSinglePolygon(geometry.coordinates, bbox);
|
|
case "MultiLineString":
|
|
return this.drawMultiLineString(geometry.coordinates, bbox);
|
|
case "LineString":
|
|
return this.drawSingleLineString(geometry.coordinates, bbox);
|
|
case "Point":
|
|
case "MultiPoint":
|
|
return bbox;
|
|
}
|
|
}
|
|
drawPolygon(path, polygons, bbox) {
|
|
if (polygons.length < 1)
|
|
return bbox;
|
|
bbox = this.drawLineString(path, polygons[0], bbox, true);
|
|
for (let i = 1; i < polygons.length; i += 1) {
|
|
const enclave = polygons[i];
|
|
this.drawLineString(path, enclave, void 0, true);
|
|
}
|
|
return bbox;
|
|
}
|
|
drawLineString(path, coordinates, bbox, isClosed) {
|
|
if (coordinates.length < 2)
|
|
return bbox;
|
|
const end3 = isClosed ? coordinates.length - 1 : coordinates.length;
|
|
for (let i = 0; i < end3; i += 1) {
|
|
const [x, y] = coordinates[i];
|
|
if (i === 0) {
|
|
path.moveTo(x, y);
|
|
} else {
|
|
path.lineTo(x, y);
|
|
}
|
|
if (bbox == null) {
|
|
bbox = new BBox29(x, y, 0, 0);
|
|
} else {
|
|
const { x: x0, y: y0 } = bbox;
|
|
const x1 = x0 + bbox.width;
|
|
const y1 = y0 + bbox.height;
|
|
bbox.x = Math.min(x0, x);
|
|
bbox.y = Math.min(y0, y);
|
|
bbox.width = Math.max(x1, x) - bbox.x;
|
|
bbox.height = Math.max(y1, y) - bbox.y;
|
|
}
|
|
}
|
|
if (isClosed) {
|
|
path.closePath();
|
|
}
|
|
return bbox;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
SceneObjectChangeDetection({ equals: objectsEqual })
|
|
], GeoGeometry.prototype, "projectedGeometry", 2);
|
|
__decorateClass([
|
|
SceneChangeDetection()
|
|
], GeoGeometry.prototype, "renderMode", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-util/geometryUtil.ts
|
|
function calculatePolygonArea(polygon) {
|
|
const bbox = polygonBbox(polygon[0], void 0);
|
|
if (bbox == null)
|
|
return 0;
|
|
return Math.abs(bbox.lat1 - bbox.lat0) * Math.abs(bbox.lon1 - bbox.lon0);
|
|
}
|
|
function findLargestByMetric(items, metric) {
|
|
let maxValue;
|
|
let maxItem;
|
|
for (const item of items) {
|
|
const value = metric(item);
|
|
if (value == null)
|
|
continue;
|
|
if (maxValue == null || value > maxValue) {
|
|
maxValue = value;
|
|
maxItem = item;
|
|
}
|
|
}
|
|
return maxItem;
|
|
}
|
|
function geometryBbox(geometry, into) {
|
|
if (geometry.bbox != null) {
|
|
const [lon0, lat0, lon1, lat1] = geometry.bbox;
|
|
into = LonLatBBox.extend(into, lon0, lat0, lon1, lat1);
|
|
return into;
|
|
}
|
|
switch (geometry.type) {
|
|
case "GeometryCollection":
|
|
for (const g of geometry.geometries) {
|
|
into = geometryBbox(g, into);
|
|
}
|
|
break;
|
|
case "MultiPolygon":
|
|
for (const c of geometry.coordinates) {
|
|
if (c.length > 0) {
|
|
into = polygonBbox(c[0], into);
|
|
}
|
|
}
|
|
break;
|
|
case "Polygon":
|
|
if (geometry.coordinates.length > 0) {
|
|
into = polygonBbox(geometry.coordinates[0], into);
|
|
}
|
|
break;
|
|
case "MultiLineString":
|
|
for (const c of geometry.coordinates) {
|
|
into = polygonBbox(c, into);
|
|
}
|
|
break;
|
|
case "LineString":
|
|
into = polygonBbox(geometry.coordinates, into);
|
|
break;
|
|
case "MultiPoint":
|
|
for (const p of geometry.coordinates) {
|
|
const [lon, lat] = p;
|
|
into = LonLatBBox.extend(into, lon, lat, lon, lat);
|
|
}
|
|
break;
|
|
case "Point": {
|
|
const [lon, lat] = geometry.coordinates;
|
|
into = LonLatBBox.extend(into, lon, lat, lon, lat);
|
|
break;
|
|
}
|
|
}
|
|
return into;
|
|
}
|
|
function largestPolygon(geometry) {
|
|
switch (geometry.type) {
|
|
case "Polygon":
|
|
return geometry.coordinates;
|
|
case "MultiPolygon":
|
|
return findLargestByMetric(geometry.coordinates, calculatePolygonArea);
|
|
case "GeometryCollection": {
|
|
const polygons = geometry.geometries.map(largestPolygon).filter((p) => p != null);
|
|
return findLargestByMetric(polygons, calculatePolygonArea);
|
|
}
|
|
case "MultiLineString":
|
|
case "LineString":
|
|
case "MultiPoint":
|
|
case "Point":
|
|
return;
|
|
}
|
|
}
|
|
function largestLineString(geometry) {
|
|
switch (geometry.type) {
|
|
case "LineString":
|
|
return geometry.coordinates;
|
|
case "MultiLineString":
|
|
return findLargestByMetric(geometry.coordinates, lineStringLength);
|
|
case "GeometryCollection": {
|
|
const lineStrings = geometry.geometries.map(largestLineString).filter((l) => l != null);
|
|
return findLargestByMetric(lineStrings, lineStringLength);
|
|
}
|
|
case "MultiPolygon":
|
|
case "Polygon":
|
|
case "MultiPoint":
|
|
case "Point":
|
|
return;
|
|
}
|
|
}
|
|
function containsType(geometry, type) {
|
|
if (geometry == null)
|
|
return false;
|
|
switch (geometry.type) {
|
|
case "GeometryCollection":
|
|
return geometry.geometries.some((g) => containsType(g, type));
|
|
case "MultiPolygon":
|
|
case "Polygon":
|
|
return (type & 1 /* Polygon */) !== 0;
|
|
case "MultiLineString":
|
|
case "LineString":
|
|
return (type & 2 /* LineString */) !== 0;
|
|
case "MultiPoint":
|
|
case "Point":
|
|
return (type & 4 /* Point */) !== 0;
|
|
}
|
|
}
|
|
function projectGeometry(geometry, scale2) {
|
|
switch (geometry.type) {
|
|
case "GeometryCollection":
|
|
return {
|
|
type: "GeometryCollection",
|
|
geometries: geometry.geometries.map((g) => projectGeometry(g, scale2))
|
|
};
|
|
case "Polygon":
|
|
return {
|
|
type: "Polygon",
|
|
coordinates: projectPolygon(geometry.coordinates, scale2)
|
|
};
|
|
case "MultiPolygon":
|
|
return {
|
|
type: "MultiPolygon",
|
|
coordinates: projectMultiPolygon(geometry.coordinates, scale2)
|
|
};
|
|
case "MultiLineString":
|
|
return {
|
|
type: "MultiLineString",
|
|
coordinates: projectPolygon(geometry.coordinates, scale2)
|
|
};
|
|
case "LineString":
|
|
return {
|
|
type: "LineString",
|
|
coordinates: projectLineString(geometry.coordinates, scale2)
|
|
};
|
|
case "MultiPoint":
|
|
return {
|
|
type: "MultiPoint",
|
|
coordinates: projectLineString(geometry.coordinates, scale2)
|
|
};
|
|
case "Point":
|
|
return {
|
|
type: "Point",
|
|
coordinates: scale2.convert(geometry.coordinates)
|
|
};
|
|
}
|
|
}
|
|
function projectMultiPolygon(multiPolygon, scale2) {
|
|
return multiPolygon.map((polygon) => projectPolygon(polygon, scale2));
|
|
}
|
|
function projectPolygon(polygon, scale2) {
|
|
return polygon.map((lineString) => projectLineString(lineString, scale2));
|
|
}
|
|
function projectLineString(lineString, scale2) {
|
|
return lineString.map((lonLat) => scale2.convert(lonLat));
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-util/topologySeries.ts
|
|
var TopologySeriesProperties = class extends module_support_exports.SeriesProperties {
|
|
};
|
|
var TopologySeries = class extends module_support_exports.DataModelSeries {
|
|
constructor(options) {
|
|
super(options);
|
|
this.cleanup.register(
|
|
this.ctx.eventsHub.on("data:update", () => {
|
|
}),
|
|
this.ctx.eventsHub.on("legend:item-click", (event) => {
|
|
this.onLegendItemClick(event);
|
|
}),
|
|
this.ctx.eventsHub.on("legend:item-double-click", (event) => {
|
|
this.onLegendItemDoubleClick(event);
|
|
})
|
|
);
|
|
}
|
|
getSeriesDomain() {
|
|
return { domain: [Number.NaN, Number.NaN] };
|
|
}
|
|
getSeriesRange() {
|
|
return [Number.NaN, Number.NaN];
|
|
}
|
|
getHighlightedDatum() {
|
|
let highlightedDatum = this.ctx.highlightManager?.getActiveHighlight();
|
|
const { legendItemName } = this.properties;
|
|
const matchingLegendItemName = legendItemName != null && highlightedDatum?.datum == null && legendItemName === highlightedDatum?.legendItemName;
|
|
if (highlightedDatum != null && (highlightedDatum.series !== this && !matchingLegendItemName || highlightedDatum.datum == null)) {
|
|
highlightedDatum = void 0;
|
|
}
|
|
return highlightedDatum;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-line-background/mapLineBackgroundSeriesProperties.ts
|
|
var { SeriesProperties: SeriesProperties10, makeSeriesTooltip: makeSeriesTooltip21 } = module_support_exports;
|
|
var MapLineBackgroundSeriesProperties = class extends SeriesProperties10 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.topology = void 0;
|
|
this.stroke = "black";
|
|
this.strokeOpacity = 1;
|
|
this.strokeWidth = 0;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.tooltip = makeSeriesTooltip21();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineBackgroundSeriesProperties.prototype, "topology", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineBackgroundSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineBackgroundSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineBackgroundSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineBackgroundSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineBackgroundSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineBackgroundSeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-line-background/mapLineBackgroundSeries.ts
|
|
var { createDatumId: createDatumId20, Group: Group22, Selection: Selection15, PointerEvents: PointerEvents12 } = module_support_exports;
|
|
var MapLineBackgroundSeries = class extends TopologySeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
categoryKey: void 0,
|
|
pickModes: []
|
|
});
|
|
this.properties = new MapLineBackgroundSeriesProperties();
|
|
this._chartTopology = void 0;
|
|
this.itemGroup = this.contentGroup.appendChild(new Group22({ name: "itemGroup" }));
|
|
this.datumSelection = Selection15.select(
|
|
this.itemGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.itemGroup.pointerEvents = PointerEvents12.None;
|
|
}
|
|
getNodeData() {
|
|
return this.contextNodeData?.nodeData;
|
|
}
|
|
get topology() {
|
|
return this.properties.topology ?? this._chartTopology;
|
|
}
|
|
get focusable() {
|
|
return false;
|
|
}
|
|
setOptionsData() {
|
|
}
|
|
setChartData() {
|
|
}
|
|
get hasData() {
|
|
return false;
|
|
}
|
|
renderToOffscreenCanvas() {
|
|
return true;
|
|
}
|
|
setChartTopology(topology) {
|
|
this._chartTopology = topology;
|
|
if (this.topology === topology) {
|
|
this.nodeDataRefresh = true;
|
|
}
|
|
}
|
|
setZIndex(zIndex) {
|
|
super.setZIndex(zIndex);
|
|
this.contentGroup.zIndex = [0 /* ShapeLineBackground */, zIndex, 0];
|
|
this.highlightGroup.zIndex = [0 /* ShapeLineBackground */, zIndex, 1];
|
|
return true;
|
|
}
|
|
nodeFactory() {
|
|
const geoGeometry = new GeoGeometry();
|
|
geoGeometry.renderMode = 2 /* Lines */;
|
|
geoGeometry.lineJoin = "round";
|
|
geoGeometry.lineCap = "round";
|
|
geoGeometry.pointerEvents = PointerEvents12.None;
|
|
return geoGeometry;
|
|
}
|
|
processData() {
|
|
const { topology } = this;
|
|
this.topologyBounds = topology?.features.reduce((current, feature) => {
|
|
const geometry = feature.geometry;
|
|
if (geometry == null)
|
|
return current;
|
|
return geometryBbox(geometry, current);
|
|
}, void 0);
|
|
if (topology == null) {
|
|
logger_exports.warnOnce(`no topology was provided for [MapShapeBackgroundSeries]; nothing will be rendered.`);
|
|
}
|
|
}
|
|
createNodeData() {
|
|
const { id: seriesId, topology, scale: scale2, properties } = this;
|
|
if (topology == null)
|
|
return;
|
|
const { stroke: stroke3, strokeOpacity, lineDash, lineDashOffset, strokeWidth } = properties;
|
|
const nodeData = [];
|
|
const labelData = [];
|
|
for (const [index, feature] of topology.features.entries()) {
|
|
const { geometry } = feature;
|
|
const projectedGeometry = geometry != null && scale2 != null ? projectGeometry(geometry, scale2) : void 0;
|
|
if (projectedGeometry == null)
|
|
continue;
|
|
nodeData.push({
|
|
series: this,
|
|
datum: feature,
|
|
datumIndex: 0,
|
|
index,
|
|
projectedGeometry,
|
|
style: { stroke: stroke3, strokeOpacity, lineDash, lineDashOffset, strokeWidth }
|
|
});
|
|
}
|
|
return {
|
|
itemId: seriesId,
|
|
nodeData,
|
|
labelData
|
|
};
|
|
}
|
|
updateSelections() {
|
|
if (this.nodeDataRefresh) {
|
|
this.contextNodeData = this.createNodeData();
|
|
this.nodeDataRefresh = false;
|
|
}
|
|
}
|
|
update() {
|
|
const { datumSelection } = this;
|
|
this.updateSelections();
|
|
this.contentGroup.visible = this.visible;
|
|
this.labelGroup.visible = this.visible;
|
|
const { nodeData = [] } = this.contextNodeData ?? {};
|
|
this.datumSelection = this.updateDatumSelection({ nodeData, datumSelection });
|
|
this.updateDatumNodes({ datumSelection });
|
|
}
|
|
updateDatumSelection(opts) {
|
|
return opts.datumSelection.update(opts.nodeData, void 0, (datum) => createDatumId20(datum.index));
|
|
}
|
|
updateDatumNodes(opts) {
|
|
const { datumSelection } = opts;
|
|
datumSelection.each((geoGeometry, datum) => {
|
|
const { projectedGeometry } = datum;
|
|
if (projectedGeometry == null) {
|
|
geoGeometry.visible = false;
|
|
geoGeometry.projectedGeometry = void 0;
|
|
return;
|
|
}
|
|
geoGeometry.visible = true;
|
|
geoGeometry.projectedGeometry = projectedGeometry;
|
|
geoGeometry.setProperties(datum.style);
|
|
});
|
|
}
|
|
resetAnimation() {
|
|
}
|
|
getLegendData() {
|
|
return [];
|
|
}
|
|
getTooltipContent(_seriesDatum) {
|
|
return;
|
|
}
|
|
computeFocusBounds(_opts) {
|
|
return void 0;
|
|
}
|
|
hasItemStylers() {
|
|
return false;
|
|
}
|
|
};
|
|
MapLineBackgroundSeries.className = "MapLineBackgroundSeries";
|
|
MapLineBackgroundSeries.type = "map-line-background";
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-line-background/mapLineBackgroundSeriesOptionsDef.ts
|
|
var { mapLineBackgroundSeriesThemeableOptionsDef: mapLineBackgroundSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var mapLineBackgroundSeriesOptionsDef = {
|
|
...mapLineBackgroundSeriesThemeableOptionsDef2,
|
|
type: required(constant("map-line-background")),
|
|
topology: geoJson
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-line-background/mapLineBackgroundModule.ts
|
|
var MapLineBackgroundSeriesModule = {
|
|
type: "series",
|
|
name: "map-line-background",
|
|
chartType: "topology",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
dependencies: [TopologyChartModule],
|
|
options: mapLineBackgroundSeriesOptionsDef,
|
|
themeTemplate: {
|
|
...MAP_THEME_DEFAULTS,
|
|
series: {
|
|
stroke: { $path: ["/1", { $mapPalette: "stroke" }, { $mapPalette: "secondHierarchyColors" }] },
|
|
strokeWidth: 1,
|
|
lineDash: [0],
|
|
lineDashOffset: 0
|
|
}
|
|
},
|
|
create: (ctx) => new MapLineBackgroundSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-util/mapUtil.ts
|
|
function prepareMapMarkerAnimationFunctions() {
|
|
const fromFn = (marker, _datum, status) => {
|
|
if (status === "removed") {
|
|
return { scalingX: 1, scalingY: 1 };
|
|
} else if (marker.previousDatum == null) {
|
|
return { scalingX: 0, scalingY: 0 };
|
|
}
|
|
return { scalingX: marker.scalingX, scalingY: marker.scalingY };
|
|
};
|
|
const toFn = (_marker, _datum, status) => {
|
|
if (status === "removed") {
|
|
return { scalingX: 0, scalingY: 0 };
|
|
}
|
|
return { scalingX: 1, scalingY: 1 };
|
|
};
|
|
return { fromFn, toFn };
|
|
}
|
|
function findFocusedGeoGeometry(series, opts) {
|
|
const datum = series.contextNodeData?.nodeData[opts.datumIndex];
|
|
if (datum === void 0)
|
|
return void 0;
|
|
for (const node of series.datumSelection.nodes()) {
|
|
if (node.datum === datum) {
|
|
return node;
|
|
}
|
|
}
|
|
return void 0;
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-line/mapLineSeriesProperties.ts
|
|
var { SeriesProperties: SeriesProperties11, makeSeriesTooltip: makeSeriesTooltip22, Label: Label19 } = module_support_exports;
|
|
var MapLineSeriesProperties = class extends SeriesProperties11 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.topology = void 0;
|
|
this.idKey = "";
|
|
this.topologyIdKey = "name";
|
|
this.idName = void 0;
|
|
this.labelKey = void 0;
|
|
this.labelName = void 0;
|
|
this.colorRange = void 0;
|
|
this.maxStrokeWidth = void 0;
|
|
this.stroke = "black";
|
|
this.strokeOpacity = 1;
|
|
this.strokeWidth = 0;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.label = new Label19();
|
|
this.tooltip = makeSeriesTooltip22();
|
|
}
|
|
getStyle() {
|
|
const { stroke: stroke3, strokeOpacity, strokeWidth, lineDash, lineDashOffset } = this;
|
|
return {
|
|
stroke: stroke3,
|
|
strokeOpacity,
|
|
strokeWidth,
|
|
lineDash,
|
|
lineDashOffset,
|
|
opacity: 1
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "topology", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "title", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "legendItemName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "idKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "topologyIdKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "idName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "labelKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "labelName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "sizeKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "sizeName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "colorKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "colorName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "sizeDomain", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "colorRange", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "maxStrokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapLineSeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-line/mapLineSeries.ts
|
|
var {
|
|
getMissCount: getMissCount3,
|
|
getLabelStyles: getLabelStyles7,
|
|
createDatumId: createDatumId21,
|
|
SeriesNodePickMode: SeriesNodePickMode15,
|
|
valueProperty: valueProperty16,
|
|
ColorScale: ColorScale3,
|
|
LinearScale: LinearScale6,
|
|
Selection: Selection16,
|
|
Text: Text9,
|
|
Transformable: Transformable7
|
|
} = module_support_exports;
|
|
var MapLineSeries = class extends TopologySeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
categoryKey: void 0,
|
|
propertyKeys: {
|
|
size: ["colorKey"],
|
|
color: ["colorKey"],
|
|
label: ["labelKey"]
|
|
},
|
|
propertyNames: {
|
|
size: ["sizeName"],
|
|
color: ["colorName"],
|
|
label: ["labelName"]
|
|
},
|
|
pickModes: [SeriesNodePickMode15.EXACT_SHAPE_MATCH, SeriesNodePickMode15.NEAREST_NODE],
|
|
usesPlacedLabels: true
|
|
});
|
|
this.properties = new MapLineSeriesProperties();
|
|
this._chartTopology = void 0;
|
|
this.colorScale = new ColorScale3();
|
|
this.sizeScale = new LinearScale6();
|
|
this.datumSelection = Selection16.select(
|
|
this.contentGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.labelSelection = Selection16.select(this.labelGroup, Text9);
|
|
this.highlightDatumSelection = Selection16.select(
|
|
this.highlightNodeGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.highlightLabelSelection = Selection16.select(this.highlightLabelGroup, Text9);
|
|
this.placedLabelData = [];
|
|
this._previousDatumMidPoint = void 0;
|
|
}
|
|
getNodeData() {
|
|
return this.contextNodeData?.nodeData;
|
|
}
|
|
get topology() {
|
|
return this.properties.topology ?? this._chartTopology;
|
|
}
|
|
get hasData() {
|
|
return super.hasData && this.topology != null;
|
|
}
|
|
renderToOffscreenCanvas() {
|
|
return true;
|
|
}
|
|
setZIndex(zIndex) {
|
|
super.setZIndex(zIndex);
|
|
this.contentGroup.zIndex = [1 /* ShapeLine */, zIndex];
|
|
this.highlightGroup.zIndex = [4 /* ShapeLineHighlight */, zIndex];
|
|
return true;
|
|
}
|
|
setChartTopology(topology) {
|
|
this._chartTopology = topology;
|
|
if (this.topology === topology) {
|
|
this.nodeDataRefresh = true;
|
|
}
|
|
}
|
|
isLabelEnabled() {
|
|
return this.properties.labelKey != null && this.properties.label.enabled;
|
|
}
|
|
nodeFactory() {
|
|
const geoGeometry = new GeoGeometry();
|
|
geoGeometry.renderMode = 2 /* Lines */;
|
|
geoGeometry.lineJoin = "round";
|
|
geoGeometry.lineCap = "round";
|
|
return geoGeometry;
|
|
}
|
|
async processData(dataController) {
|
|
if (this.data == null)
|
|
return;
|
|
const { data, topology, sizeScale, colorScale } = this;
|
|
const { topologyIdKey, idKey, sizeKey, colorKey, labelKey, sizeDomain, colorRange } = this.properties;
|
|
const featureById = /* @__PURE__ */ new Map();
|
|
for (const feature of topology?.features.values() ?? []) {
|
|
const property = feature.properties?.[topologyIdKey];
|
|
if (property == null || !containsType(feature.geometry, 2 /* LineString */))
|
|
continue;
|
|
featureById.set(property, feature);
|
|
}
|
|
const sizeScaleType = this.sizeScale.type;
|
|
const colorScaleType = this.colorScale.type;
|
|
const mercatorScaleType = this.scale?.type;
|
|
const { dataModel, processedData } = await this.requestDataModel(dataController, data, {
|
|
props: [
|
|
valueProperty16(idKey, mercatorScaleType, { id: "idValue", includeProperty: false }),
|
|
valueProperty16(idKey, mercatorScaleType, {
|
|
id: "featureValue",
|
|
includeProperty: false,
|
|
processor: () => (datum) => featureById.get(datum)
|
|
}),
|
|
...labelKey == null ? [] : [valueProperty16(labelKey, "category", { id: "labelValue" })],
|
|
...sizeKey == null ? [] : [valueProperty16(sizeKey, sizeScaleType, { id: "sizeValue" })],
|
|
...colorKey == null ? [] : [valueProperty16(colorKey, colorScaleType, { id: "colorValue" })]
|
|
]
|
|
});
|
|
const featureValues = dataModel.resolveColumnById(this, `featureValue`, processedData);
|
|
this.topologyBounds = featureValues.reduce((current, feature) => {
|
|
const geometry = feature?.geometry;
|
|
if (geometry == null)
|
|
return current;
|
|
return geometryBbox(geometry, current);
|
|
}, void 0);
|
|
if (sizeKey != null) {
|
|
const sizeIdx = dataModel.resolveProcessedDataIndexById(this, `sizeValue`);
|
|
const processedSize = processedData.domain.values[sizeIdx] ?? [];
|
|
sizeScale.domain = sizeDomain ?? processedSize;
|
|
}
|
|
if (colorRange != null && this.isColorScaleValid()) {
|
|
const colorKeyIdx = dataModel.resolveProcessedDataIndexById(this, "colorValue");
|
|
colorScale.domain = processedData.domain.values[colorKeyIdx];
|
|
colorScale.range = colorRange;
|
|
colorScale.update();
|
|
}
|
|
if (topology == null) {
|
|
logger_exports.warnOnce(`no topology was provided for [MapLineSeries]; nothing will be rendered.`);
|
|
}
|
|
}
|
|
isColorScaleValid() {
|
|
const { colorKey } = this.properties;
|
|
if (!colorKey) {
|
|
return false;
|
|
}
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData) {
|
|
return false;
|
|
}
|
|
const colorIdx = dataModel.resolveProcessedDataIndexById(this, "colorValue");
|
|
const dataCount = processedData.input.count;
|
|
const missCount = getMissCount3(this, processedData.defs.values[colorIdx].missing);
|
|
const colorDataMissing = dataCount === 0 || dataCount === missCount;
|
|
return !colorDataMissing;
|
|
}
|
|
getLabelDatum(datum, datumIndex, idValue, labelValue, projectedGeometry, measurer3) {
|
|
if (labelValue == null || projectedGeometry == null || idValue == null)
|
|
return;
|
|
const lineString = largestLineString(projectedGeometry);
|
|
if (lineString == null)
|
|
return;
|
|
const { idKey, idName, sizeKey, sizeName, colorKey, colorName, labelKey, labelName, label } = this.properties;
|
|
if (labelKey == null || !label.enabled)
|
|
return;
|
|
const labelText = this.getLabelText(
|
|
labelValue,
|
|
datum,
|
|
labelKey,
|
|
"label",
|
|
[],
|
|
label,
|
|
{
|
|
value: labelValue,
|
|
datum,
|
|
idKey,
|
|
idName,
|
|
sizeKey,
|
|
sizeName,
|
|
colorKey,
|
|
colorName,
|
|
labelKey,
|
|
labelName
|
|
}
|
|
);
|
|
if (labelText == null)
|
|
return;
|
|
const labelSize = measurer3.measureLines(String(labelText));
|
|
const labelCenter = lineStringCenter(lineString);
|
|
if (labelCenter == null)
|
|
return;
|
|
const [x, y] = labelCenter.point;
|
|
const { width: width2, height: height2 } = labelSize;
|
|
return {
|
|
point: { x, y, size: 0 },
|
|
label: { width: width2, height: height2, text: labelText },
|
|
anchor: void 0,
|
|
placement: void 0,
|
|
datumIndex,
|
|
idValue
|
|
};
|
|
}
|
|
resolveColumn(key, columnId, processedData) {
|
|
if (key == null || this.dataModel == null)
|
|
return void 0;
|
|
return this.dataModel.resolveColumnById(this, columnId, processedData);
|
|
}
|
|
resolveLineDataColumns(processedData) {
|
|
const { sizeKey, colorKey, labelKey } = this.properties;
|
|
return {
|
|
idValues: this.dataModel.resolveColumnById(this, "idValue", processedData),
|
|
featureValues: this.dataModel.resolveColumnById(this, "featureValue", processedData),
|
|
labelValues: this.resolveColumn(labelKey, "labelValue", processedData),
|
|
sizeValues: this.resolveColumn(sizeKey, "sizeValue", processedData),
|
|
colorValues: this.resolveColumn(colorKey, "colorValue", processedData)
|
|
};
|
|
}
|
|
prepareProjectedLineGeometries(idValues, featureValues, processedData) {
|
|
const projectedGeometries = /* @__PURE__ */ new Map();
|
|
for (const [datumIndex] of processedData.dataSources.get(this.id)?.data.entries() ?? []) {
|
|
const id = idValues[datumIndex];
|
|
const geometry = featureValues[datumIndex]?.geometry;
|
|
const projectedGeometry = geometry != null && this.scale != null ? projectGeometry(geometry, this.scale) : void 0;
|
|
if (id != null && projectedGeometry != null) {
|
|
projectedGeometries.set(id, projectedGeometry);
|
|
}
|
|
}
|
|
return projectedGeometries;
|
|
}
|
|
warnMissingGeometries(missingGeometries) {
|
|
if (missingGeometries.length === 0)
|
|
return;
|
|
const missingGeometriesCap = 10;
|
|
if (missingGeometries.length > missingGeometriesCap) {
|
|
const excessItems = missingGeometries.length - missingGeometriesCap;
|
|
missingGeometries.length = missingGeometriesCap;
|
|
missingGeometries.push(`(+${excessItems} more)`);
|
|
}
|
|
logger_exports.warnOnce(`some data items do not have matches in the provided topology`, missingGeometries);
|
|
}
|
|
createNodeData() {
|
|
const { id: seriesId, dataModel, processedData, sizeScale, properties } = this;
|
|
const { label, legendItemName, colorKey } = properties;
|
|
if (dataModel == null || processedData == null)
|
|
return;
|
|
if (!this.visible) {
|
|
return { itemId: seriesId, nodeData: [], labelData: [] };
|
|
}
|
|
const columns = this.resolveLineDataColumns(processedData);
|
|
const maxStrokeWidth = properties.maxStrokeWidth ?? properties.strokeWidth;
|
|
sizeScale.range = [Math.min(properties.strokeWidth, maxStrokeWidth), maxStrokeWidth];
|
|
const measurer3 = cachedTextMeasurer(label);
|
|
const projectedGeometries = this.prepareProjectedLineGeometries(
|
|
columns.idValues,
|
|
columns.featureValues,
|
|
processedData
|
|
);
|
|
const nodeData = [];
|
|
const labelData = [];
|
|
const missingGeometries = [];
|
|
const rawData = processedData.dataSources.get(this.id)?.data ?? [];
|
|
for (const [datumIndex, datum] of rawData.entries()) {
|
|
const dataValues = {
|
|
idValue: columns.idValues[datumIndex],
|
|
colorValue: columns.colorValues?.[datumIndex],
|
|
sizeValue: columns.sizeValues?.[datumIndex],
|
|
labelValue: columns.labelValues?.[datumIndex]
|
|
};
|
|
const projectedGeometry = projectedGeometries.get(dataValues.idValue);
|
|
if (projectedGeometry == null) {
|
|
missingGeometries.push(dataValues.idValue);
|
|
}
|
|
if (colorKey != null && dataValues.colorValue == null) {
|
|
continue;
|
|
}
|
|
const labelDatum = this.getLabelDatum(
|
|
datum,
|
|
datumIndex,
|
|
dataValues.idValue,
|
|
dataValues.labelValue,
|
|
projectedGeometry,
|
|
measurer3
|
|
);
|
|
if (labelDatum != null) {
|
|
labelData.push(labelDatum);
|
|
}
|
|
nodeData.push({
|
|
series: this,
|
|
datum,
|
|
datumIndex,
|
|
...dataValues,
|
|
projectedGeometry,
|
|
legendItemName,
|
|
style: this.getItemStyle(
|
|
{ datumIndex, datum, colorValue: dataValues.colorValue, sizeValue: dataValues.sizeValue },
|
|
false
|
|
)
|
|
});
|
|
}
|
|
this.warnMissingGeometries(missingGeometries);
|
|
return {
|
|
itemId: seriesId,
|
|
nodeData,
|
|
labelData
|
|
};
|
|
}
|
|
updateSelections() {
|
|
if (this.nodeDataRefresh) {
|
|
this.contextNodeData = this.createNodeData();
|
|
this.nodeDataRefresh = false;
|
|
}
|
|
}
|
|
update() {
|
|
const { datumSelection, highlightDatumSelection } = this;
|
|
this.updateSelections();
|
|
this.contentGroup.visible = this.visible;
|
|
this.labelGroup.visible = this.visible;
|
|
const drawingMode = this.ctx.chartService.highlight?.drawingMode ?? "overlay";
|
|
const highlightedDatum = this.getHighlightedDatum();
|
|
const nodeData = this.contextNodeData?.nodeData ?? [];
|
|
this.datumSelection = this.updateDatumSelection({ nodeData, datumSelection });
|
|
this.updateDatumStyles({ datumSelection, isHighlight: false });
|
|
this.updateDatumNodes({ datumSelection, isHighlight: false, drawingMode: "overlay" });
|
|
this.highlightDatumSelection = this.updateDatumSelection({
|
|
nodeData: highlightedDatum == null ? [] : [highlightedDatum],
|
|
datumSelection: highlightDatumSelection
|
|
});
|
|
this.updateDatumStyles({ datumSelection: highlightDatumSelection, isHighlight: true });
|
|
this.updateDatumNodes({ datumSelection: highlightDatumSelection, isHighlight: true, drawingMode });
|
|
this.updateLabelNodes({ labelSelection: this.labelSelection, isHighlight: false });
|
|
this.updateHighlightLabelSelection(highlightedDatum);
|
|
}
|
|
updateDatumSelection(opts) {
|
|
return opts.datumSelection.update(opts.nodeData, void 0, (datum) => createDatumId21(datum.idValue));
|
|
}
|
|
getItemStyle({ datumIndex = 0, datum, colorValue, sizeValue }, isHighlight) {
|
|
const { properties, colorScale, sizeScale } = this;
|
|
const { colorRange, itemStyler } = properties;
|
|
const baseStyle = properties.getStyle();
|
|
if (colorValue != null) {
|
|
baseStyle.stroke = this.isColorScaleValid() ? colorScale.convert(colorValue) : colorRange?.[0] ?? properties.stroke;
|
|
}
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex);
|
|
const style2 = mergeDefaults(highlightStyle, baseStyle);
|
|
if (sizeValue != null) {
|
|
style2.strokeWidth = sizeScale.convert(sizeValue, { clamp: true });
|
|
}
|
|
let overrides;
|
|
if (itemStyler != null) {
|
|
overrides = this.cachedDatumCallback(createDatumId21(datumIndex, isHighlight ? "highlight" : "node"), () => {
|
|
const params = this.makeItemStylerParams(datum, datumIndex, isHighlight, style2);
|
|
return this.callWithContext(itemStyler, params);
|
|
});
|
|
}
|
|
return overrides ? mergeDefaults(style2, overrides) : style2;
|
|
}
|
|
makeItemStylerParams(datum, datumIndex, isHighlight, style2) {
|
|
const { id: seriesId } = this;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightState = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
return {
|
|
seriesId,
|
|
datum,
|
|
highlightState,
|
|
...style2
|
|
};
|
|
}
|
|
updateDatumStyles({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
datumSelection.each((_, nodeDatum) => {
|
|
nodeDatum.style = this.getItemStyle(nodeDatum, isHighlight);
|
|
});
|
|
}
|
|
updateDatumNodes({
|
|
datumSelection,
|
|
drawingMode
|
|
}) {
|
|
datumSelection.each((geoGeometry, nodeDatum) => {
|
|
const { projectedGeometry, style: style2 } = nodeDatum;
|
|
if (projectedGeometry == null) {
|
|
geoGeometry.visible = false;
|
|
geoGeometry.projectedGeometry = void 0;
|
|
return;
|
|
}
|
|
geoGeometry.visible = true;
|
|
geoGeometry.projectedGeometry = projectedGeometry;
|
|
geoGeometry.setProperties(style2);
|
|
geoGeometry.drawingMode = drawingMode;
|
|
});
|
|
}
|
|
updatePlacedLabelData(labelData) {
|
|
this.placedLabelData = labelData;
|
|
this.labelSelection = this.labelSelection.update(labelData, (text2) => {
|
|
text2.pointerEvents = module_support_exports.PointerEvents.None;
|
|
});
|
|
this.updateLabelNodes({ labelSelection: this.labelSelection, isHighlight: false });
|
|
this.updateHighlightLabelSelection();
|
|
}
|
|
updateLabelNodes({
|
|
isHighlight,
|
|
labelSelection
|
|
}) {
|
|
const { properties } = this;
|
|
const activeHighlight = this.getHighlightedDatum();
|
|
labelSelection.each((label, placedLabel) => {
|
|
const { x, y, width: width2, height: height2, text: text2, datum: labelDatum } = placedLabel;
|
|
const style2 = getLabelStyles7(this, void 0, properties, properties.label, isHighlight, activeHighlight);
|
|
const { color: fill, fontStyle, fontWeight: fontWeight2, fontSize, fontFamily } = style2;
|
|
label.visible = true;
|
|
label.x = x + width2 / 2;
|
|
label.y = y + height2 / 2;
|
|
label.text = text2;
|
|
label.fill = fill;
|
|
label.fontStyle = fontStyle;
|
|
label.fontWeight = fontWeight2;
|
|
label.fontSize = fontSize;
|
|
label.fontFamily = fontFamily;
|
|
label.textAlign = "center";
|
|
label.textBaseline = "middle";
|
|
const datumIndex = labelDatum?.datumIndex;
|
|
label.fillOpacity = this.getHighlightStyle(isHighlight, datumIndex).opacity ?? 1;
|
|
label.setBoxing(style2);
|
|
});
|
|
}
|
|
updateHighlightLabelSelection(highlightedDatum = this.getHighlightedDatum()) {
|
|
const highlightId = highlightedDatum?.idValue;
|
|
const highlightLabels = highlightId == null || !this.isLabelEnabled() ? [] : this.placedLabelData.filter((label) => label.datum.idValue === highlightId);
|
|
this.highlightLabelSelection = this.highlightLabelSelection.update(highlightLabels);
|
|
if (highlightLabels.length === 0) {
|
|
this.highlightLabelSelection.cleanup();
|
|
}
|
|
this.updateLabelNodes({ labelSelection: this.highlightLabelSelection, isHighlight: true });
|
|
}
|
|
resetAnimation() {
|
|
}
|
|
getLabelData() {
|
|
if (!this.isLabelEnabled())
|
|
return [];
|
|
return this.contextNodeData?.labelData ?? [];
|
|
}
|
|
pickNodeClosestDatum({ x, y }) {
|
|
let minDistanceSquared = Infinity;
|
|
let minDatum;
|
|
this.datumSelection.each((node, datum) => {
|
|
const distanceSquared2 = node.distanceSquared(x, y);
|
|
if (distanceSquared2 < minDistanceSquared) {
|
|
minDistanceSquared = distanceSquared2;
|
|
minDatum = datum;
|
|
}
|
|
});
|
|
return minDatum == null ? void 0 : { datum: minDatum, distance: Math.sqrt(minDistanceSquared) };
|
|
}
|
|
datumMidPoint(datum) {
|
|
const { _previousDatumMidPoint } = this;
|
|
if (_previousDatumMidPoint?.datum === datum) {
|
|
return _previousDatumMidPoint.point;
|
|
}
|
|
const projectedGeometry = datum.projectedGeometry;
|
|
const lineString = projectedGeometry == null ? void 0 : largestLineString(projectedGeometry);
|
|
const center2 = lineString == null ? void 0 : lineStringCenter(lineString)?.point;
|
|
const point = center2 == null ? void 0 : { x: center2[0], y: center2[1] };
|
|
this._previousDatumMidPoint = { datum, point };
|
|
return point;
|
|
}
|
|
legendItemSymbol(datumIndex) {
|
|
const { dataModel, processedData, properties } = this;
|
|
const { strokeWidth, strokeOpacity, lineDash } = properties;
|
|
let { stroke: stroke3 } = properties;
|
|
if (datumIndex != null && this.isColorScaleValid()) {
|
|
const colorValues = dataModel.resolveColumnById(this, "colorValue", processedData);
|
|
const colorValue = colorValues[datumIndex];
|
|
if (colorValue != null) {
|
|
stroke3 = this.colorScale.convert(colorValue);
|
|
}
|
|
}
|
|
return {
|
|
marker: {
|
|
fill: void 0,
|
|
fillOpacity: 0,
|
|
stroke: void 0,
|
|
strokeWidth: 0,
|
|
strokeOpacity: 0,
|
|
lineDash: [0],
|
|
lineDashOffset: 0,
|
|
enabled: false
|
|
},
|
|
line: {
|
|
enabled: true,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
const { processedData, dataModel } = this;
|
|
if (processedData == null || dataModel == null)
|
|
return [];
|
|
const { id: seriesId, visible } = this;
|
|
const { title, legendItemName, idKey, idName, colorKey, colorRange, showInLegend } = this.properties;
|
|
if (legendType === "gradient" && colorKey != null && colorRange != null) {
|
|
const colorDomain = processedData.domain.values[dataModel.resolveProcessedDataIndexById(this, "colorValue")];
|
|
const legendDatum = {
|
|
legendType: "gradient",
|
|
enabled: visible,
|
|
seriesId,
|
|
series: this.getFormatterContext("color"),
|
|
colorRange,
|
|
colorDomain
|
|
};
|
|
return [legendDatum];
|
|
} else if (legendType === "category") {
|
|
const legendDatum = {
|
|
legendType: "category",
|
|
id: seriesId,
|
|
itemId: seriesId,
|
|
seriesId,
|
|
enabled: visible,
|
|
label: { text: legendItemName ?? title ?? idName ?? idKey },
|
|
symbol: this.legendItemSymbol(),
|
|
legendItemName,
|
|
hideInLegend: !showInLegend
|
|
};
|
|
return [legendDatum];
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const {
|
|
id: seriesId,
|
|
dataModel,
|
|
processedData,
|
|
properties,
|
|
ctx: { formatManager }
|
|
} = this;
|
|
const {
|
|
idKey,
|
|
idName,
|
|
colorKey,
|
|
colorName,
|
|
sizeKey,
|
|
sizeName,
|
|
labelKey,
|
|
labelName,
|
|
title,
|
|
legendItemName,
|
|
tooltip
|
|
} = properties;
|
|
if (!dataModel || !processedData)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const idValues = dataModel.resolveColumnById(this, `idValue`, processedData);
|
|
const sizeValue = sizeKey == null ? void 0 : dataModel.resolveColumnById(this, `sizeValue`, processedData)[datumIndex];
|
|
const colorValue = colorKey == null ? void 0 : dataModel.resolveColumnById(this, `colorValue`, processedData)[datumIndex];
|
|
if (colorKey != null && colorValue == null) {
|
|
return;
|
|
}
|
|
const data = [];
|
|
if (this.isLabelEnabled() && labelKey != null && labelKey !== idKey) {
|
|
const labelValue = dataModel.resolveColumnById(this, `labelValue`, processedData)[datumIndex];
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "category",
|
|
value: labelValue,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key: labelKey,
|
|
source: "tooltip",
|
|
property: "label",
|
|
domain: [],
|
|
boundSeries: this.getFormatterContext("label")
|
|
});
|
|
data.push({ label: labelName, fallbackLabel: labelKey, value: content ?? labelValue });
|
|
}
|
|
if (sizeValue != null && sizeKey != null) {
|
|
const domain = dataModel.getDomain(this, `sizeValue`, "value", processedData).domain;
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value: sizeValue,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key: sizeKey,
|
|
source: "tooltip",
|
|
property: "size",
|
|
domain,
|
|
boundSeries: this.getFormatterContext("size"),
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
});
|
|
data.push({ label: sizeName, fallbackLabel: sizeKey, value: content ?? String(sizeValue) });
|
|
}
|
|
if (colorValue != null && colorKey != null) {
|
|
const domain = dataModel.getDomain(this, `colorValue`, "value", processedData).domain;
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value: colorValue,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key: colorKey,
|
|
source: "tooltip",
|
|
property: "color",
|
|
domain,
|
|
boundSeries: this.getFormatterContext("color"),
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
});
|
|
data.push({ label: colorName, fallbackLabel: colorKey, value: content ?? String(colorValue) });
|
|
}
|
|
const format = this.getItemStyle({ datumIndex, datum, colorValue, sizeValue }, false);
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading: idValues[datumIndex],
|
|
title: title ?? legendItemName,
|
|
symbol: this.legendItemSymbol(datumIndex),
|
|
data
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title,
|
|
idKey,
|
|
idName,
|
|
colorKey,
|
|
colorName,
|
|
sizeKey,
|
|
sizeName,
|
|
labelKey,
|
|
labelName,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
computeFocusBounds(opts) {
|
|
const geometry = findFocusedGeoGeometry(this, opts);
|
|
return geometry ? Transformable7.toCanvas(this.contentGroup, geometry.getBBox()) : void 0;
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
MapLineSeries.className = "MapLineSeries";
|
|
MapLineSeries.type = "map-line";
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-line/mapLineSeriesOptionsDef.ts
|
|
var { mapLineSeriesThemeableOptionsDef: mapLineSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var mapLineSeriesOptionsDef = {
|
|
...without(commonSeriesOptionsDefs, ["highlightStyle", "highlight"]),
|
|
...mapLineSeriesThemeableOptionsDef2,
|
|
type: required(constant("map-line")),
|
|
idKey: required(string),
|
|
sizeKey: string,
|
|
colorKey: string,
|
|
labelKey: string,
|
|
idName: string,
|
|
sizeName: string,
|
|
colorName: string,
|
|
labelName: string,
|
|
topology: geoJson,
|
|
topologyIdKey: string,
|
|
legendItemName: string,
|
|
title: string
|
|
};
|
|
mapLineSeriesOptionsDef.colorRange = undocumented(and(arrayOf(color), arrayLength(1)));
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-line/mapLineModule.ts
|
|
var MapLineSeriesModule = {
|
|
type: "series",
|
|
name: "map-line",
|
|
chartType: "topology",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
dependencies: [TopologyChartModule],
|
|
options: mapLineSeriesOptionsDef,
|
|
themeTemplate: {
|
|
...MAP_THEME_DEFAULTS,
|
|
series: {
|
|
stroke: applyMapPalette(SAFE_STROKE_FILL_OPERATION),
|
|
colorRange: {
|
|
$if: [
|
|
{ $eq: [{ $mapPalette: "type" }, "inbuilt"] },
|
|
{ $mapPalette: "divergingColors" },
|
|
applyMapPalette(SAFE_RANGE2_OPERATION)
|
|
]
|
|
},
|
|
strokeWidth: 1,
|
|
maxStrokeWidth: 3,
|
|
lineDash: [0],
|
|
lineDashOffset: 0,
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" }
|
|
},
|
|
highlight: applyMapPalette(MULTI_SERIES_HIGHLIGHT_STYLE)
|
|
},
|
|
tooltip: {
|
|
range: "exact"
|
|
}
|
|
},
|
|
create: (ctx) => new MapLineSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-util/polygonPointSearch.ts
|
|
function polygonPointSearch(polygons, precision, valueFn) {
|
|
const bbox = polygonBbox(polygons[0], void 0);
|
|
if (bbox == null)
|
|
return;
|
|
const boundingXCenter = (bbox.lon0 + bbox.lon1) / 2;
|
|
const boundingYCenter = (bbox.lat0 + bbox.lat1) / 2;
|
|
const boundingWidth = Math.abs(bbox.lon1 - bbox.lon0);
|
|
const boundingHeight = Math.abs(bbox.lat1 - bbox.lat0);
|
|
const centroid = polygonCentroid(polygons[0]);
|
|
const [cx, cy] = centroid;
|
|
const centroidDistanceToPolygon = -polygonDistance(polygons, cx, cy);
|
|
let bestResult;
|
|
const cellValue = (distanceToPolygon, distanceToCentroid) => {
|
|
const centroidDriftFactor = 0.5;
|
|
const centroidDrift = Math.max(distanceToCentroid - centroidDistanceToPolygon, 0);
|
|
return distanceToPolygon - centroidDriftFactor * centroidDrift;
|
|
};
|
|
const createLabelPlacement = (x2, y2, stride) => {
|
|
const { distance: distance3, maxDistance } = valueFn(polygons, x2, y2, stride);
|
|
const distanceToCentroid = Math.hypot(cx - x2, cy - y2);
|
|
const maxXTowardsCentroid = Math.min(Math.max(cx, x2 - stride / 2), x2 + stride / 2);
|
|
const maxYTowardsCentroid = Math.min(Math.max(cy, y2 - stride / 2), y2 + stride / 2);
|
|
const minDistanceToCentroid = Math.hypot(cx - maxXTowardsCentroid, cy - maxYTowardsCentroid);
|
|
const value = cellValue(distance3, distanceToCentroid);
|
|
const maxValue = cellValue(maxDistance, minDistanceToCentroid);
|
|
return { distance: distance3, maxDistance, value, maxValue, x: x2, y: y2, stride };
|
|
};
|
|
const appendLabelPlacement = (into, x2, y2, stride) => {
|
|
const labelPlacement = createLabelPlacement(x2, y2, stride);
|
|
if (labelPlacement.maxDistance >= 0) {
|
|
into.push(labelPlacement);
|
|
}
|
|
};
|
|
const initialStride = Math.min(boundingWidth, boundingHeight) / 2;
|
|
let queue = {
|
|
value: createLabelPlacement(boundingXCenter, boundingYCenter, initialStride),
|
|
next: null
|
|
};
|
|
while (queue != null) {
|
|
const item = queue.value;
|
|
const { distance: distance3, value, maxValue, x: x2, y: y2, stride } = item;
|
|
queue = queue.next;
|
|
if (distance3 > 0 && (bestResult == null || value > bestResult.value)) {
|
|
bestResult = item;
|
|
}
|
|
if (bestResult != null && maxValue - bestResult.value <= precision) {
|
|
continue;
|
|
}
|
|
const nextStride = stride / 2;
|
|
const newLabelPlacements = [];
|
|
appendLabelPlacement(newLabelPlacements, x2 - nextStride, y2 - nextStride, nextStride);
|
|
appendLabelPlacement(newLabelPlacements, x2 + nextStride, y2 - nextStride, nextStride);
|
|
appendLabelPlacement(newLabelPlacements, x2 - nextStride, y2 + nextStride, nextStride);
|
|
appendLabelPlacement(newLabelPlacements, x2 + nextStride, y2 + nextStride, nextStride);
|
|
newLabelPlacements.sort(labelPlacementCmp);
|
|
queue = insertListItemsSorted(queue, newLabelPlacements, labelPlacementCmp);
|
|
}
|
|
if (bestResult == null)
|
|
return;
|
|
const { distance: distance2, x, y } = bestResult;
|
|
return { x, y, distance: distance2 };
|
|
}
|
|
var labelPlacementCmp = (a, b) => b.maxValue - a.maxValue;
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-util/markerUtil.ts
|
|
function polygonMarkerCenter(polygons, precision) {
|
|
const result = polygonPointSearch(polygons, precision, (p, x2, y2, stride) => {
|
|
const distance2 = -polygonDistance(p, x2, y2);
|
|
const maxDistance = distance2 + stride * Math.SQRT2;
|
|
return { distance: distance2, maxDistance };
|
|
});
|
|
if (result == null)
|
|
return;
|
|
const { x, y } = result;
|
|
return [x, y];
|
|
}
|
|
function markerPositions(geometry, precision) {
|
|
let center2;
|
|
switch (geometry.type) {
|
|
case "GeometryCollection":
|
|
return geometry.geometries.flatMap((g) => markerPositions(g, precision));
|
|
case "MultiPoint":
|
|
return geometry.coordinates;
|
|
case "Point":
|
|
return [geometry.coordinates];
|
|
case "MultiPolygon": {
|
|
const polygon = largestPolygon(geometry);
|
|
center2 = polygon == null ? void 0 : polygonMarkerCenter(polygon, precision);
|
|
break;
|
|
}
|
|
case "Polygon": {
|
|
const polygon = geometry.coordinates;
|
|
center2 = polygon == null ? void 0 : polygonMarkerCenter(polygon, precision);
|
|
break;
|
|
}
|
|
case "MultiLineString": {
|
|
const lineString = largestLineString(geometry);
|
|
center2 = lineString == null ? void 0 : lineStringCenter(lineString)?.point;
|
|
break;
|
|
}
|
|
case "LineString": {
|
|
const lineString = geometry.coordinates;
|
|
center2 = lineStringCenter(lineString)?.point;
|
|
break;
|
|
}
|
|
}
|
|
return center2 == null ? [] : [center2];
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-util/shapeFillBBox.ts
|
|
var { BBox: BBox30 } = module_support_exports;
|
|
function getTopologyShapeFillBBox(scale2) {
|
|
if (!scale2)
|
|
return;
|
|
const { range: range3 } = scale2;
|
|
const x = range3[0][0];
|
|
const y = range3[0][1];
|
|
const width2 = range3[1][0] - x;
|
|
const height2 = range3[1][1] - y;
|
|
const bbox = new BBox30(x, y, width2, height2);
|
|
return {
|
|
series: bbox,
|
|
axis: bbox
|
|
};
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-marker/mapMarkerSeriesProperties.ts
|
|
var { SeriesProperties: SeriesProperties12, makeSeriesTooltip: makeSeriesTooltip23, Label: Label20 } = module_support_exports;
|
|
var MapMarkerSeriesLabel = class extends Label20 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.placement = "bottom";
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesLabel.prototype, "placement", 2);
|
|
var MapMarkerSeriesProperties = class extends SeriesProperties12 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.topology = void 0;
|
|
this.idKey = void 0;
|
|
this.topologyIdKey = "name";
|
|
this.idName = void 0;
|
|
this.latitudeKey = void 0;
|
|
this.latitudeName = void 0;
|
|
this.longitudeKey = void 0;
|
|
this.longitudeName = void 0;
|
|
this.labelKey = void 0;
|
|
this.labelName = void 0;
|
|
this.colorRange = void 0;
|
|
this.shape = "circle";
|
|
this.size = 6;
|
|
this.fill = "black";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "black";
|
|
this.strokeWidth = 1;
|
|
this.strokeOpacity = 1;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.label = new MapMarkerSeriesLabel();
|
|
this.tooltip = makeSeriesTooltip23();
|
|
}
|
|
getStyle() {
|
|
const { size, shape, fill, fillOpacity, stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = this;
|
|
return {
|
|
size,
|
|
shape,
|
|
fill,
|
|
fillOpacity,
|
|
opacity: 1,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "topology", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "title", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "legendItemName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "idKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "topologyIdKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "idName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "latitudeKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "latitudeName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "longitudeKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "longitudeName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "labelKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "labelName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "sizeKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "sizeName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "colorKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "colorName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "colorRange", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "shape", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "size", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "maxSize", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "sizeDomain", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapMarkerSeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-marker/mapMarkerSeries.ts
|
|
var {
|
|
fromToMotion: fromToMotion7,
|
|
getMissCount: getMissCount4,
|
|
createDatumId: createDatumId22,
|
|
SeriesNodePickMode: SeriesNodePickMode16,
|
|
valueProperty: valueProperty17,
|
|
computeMarkerFocusBounds: computeMarkerFocusBounds4,
|
|
ColorScale: ColorScale4,
|
|
LinearScale: LinearScale7,
|
|
Group: Group23,
|
|
Selection: Selection17,
|
|
Text: Text10,
|
|
Marker: Marker7,
|
|
getLabelStyles: getLabelStyles8
|
|
} = module_support_exports;
|
|
var MapMarkerSeries = class extends TopologySeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
categoryKey: void 0,
|
|
propertyKeys: {
|
|
size: ["colorKey"],
|
|
color: ["colorKey"],
|
|
label: ["labelKey"]
|
|
},
|
|
propertyNames: {
|
|
size: ["sizeName"],
|
|
color: ["colorName"],
|
|
label: ["labelName"]
|
|
},
|
|
pickModes: [SeriesNodePickMode16.EXACT_SHAPE_MATCH, SeriesNodePickMode16.NEAREST_NODE],
|
|
usesPlacedLabels: true
|
|
});
|
|
this.properties = new MapMarkerSeriesProperties();
|
|
this._chartTopology = void 0;
|
|
this.colorScale = new ColorScale4();
|
|
this.sizeScale = new LinearScale7();
|
|
this.markerGroup = this.contentGroup.appendChild(new Group23({ name: "markerGroup" }));
|
|
this.labelSelection = Selection17.select(this.labelGroup, Text10, false);
|
|
this.highlightLabelSelection = Selection17.select(this.highlightLabelGroup, Text10, false);
|
|
this.markerSelection = Selection17.select(
|
|
this.markerGroup,
|
|
Marker7,
|
|
false
|
|
);
|
|
this.highlightMarkerSelection = Selection17.select(this.highlightNodeGroup, Marker7);
|
|
this.placedLabelData = [];
|
|
this.animationState = new StateMachine(
|
|
"empty",
|
|
{
|
|
empty: {
|
|
update: {
|
|
target: "ready",
|
|
action: () => this.animateMarkers()
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
ready: {
|
|
updateData: "waiting",
|
|
clear: "clearing",
|
|
resize: () => this.resetAllAnimation(),
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
waiting: {
|
|
update: {
|
|
target: "ready",
|
|
action: () => this.animateMarkers()
|
|
},
|
|
// chart.ts transitions to updateData on zoom change
|
|
resize: {
|
|
target: "ready",
|
|
action: () => this.resetAllAnimation()
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
},
|
|
clearing: {
|
|
update: {
|
|
target: "empty",
|
|
action: () => this.resetAllAnimation()
|
|
},
|
|
reset: "empty",
|
|
skip: "ready"
|
|
}
|
|
},
|
|
() => this.checkProcessedDataAnimatable()
|
|
);
|
|
}
|
|
getNodeData() {
|
|
return this.contextNodeData?.nodeData;
|
|
}
|
|
get topology() {
|
|
return this.properties.topology ?? this._chartTopology;
|
|
}
|
|
get hasData() {
|
|
const hasLatLon = this.properties.latitudeKey != null && this.properties.longitudeKey != null;
|
|
return super.hasData && (this.topology != null || hasLatLon);
|
|
}
|
|
renderToOffscreenCanvas() {
|
|
return true;
|
|
}
|
|
setChartTopology(topology) {
|
|
this._chartTopology = topology;
|
|
if (this.topology === topology) {
|
|
this.nodeDataRefresh = true;
|
|
}
|
|
}
|
|
setZIndex(zIndex) {
|
|
super.setZIndex(zIndex);
|
|
this.contentGroup.zIndex = [2 /* Marker */, zIndex];
|
|
this.highlightGroup.zIndex = [3 /* MarkerHighlight */, zIndex];
|
|
return true;
|
|
}
|
|
isLabelEnabled() {
|
|
return this.properties.labelKey != null && this.properties.label.enabled;
|
|
}
|
|
async processData(dataController) {
|
|
if (this.data == null)
|
|
return;
|
|
const { data, sizeScale, colorScale } = this;
|
|
const { topologyIdKey, idKey, latitudeKey, longitudeKey, sizeKey, colorKey, labelKey, sizeDomain, colorRange } = this.properties;
|
|
const featureById = this.buildFeatureMap(topologyIdKey);
|
|
const sizeScaleType = this.sizeScale.type;
|
|
const colorScaleType = this.colorScale.type;
|
|
const mercatorScaleType = this.scale?.type;
|
|
const hasLatLon = latitudeKey != null && longitudeKey != null;
|
|
const { dataModel, processedData } = await this.requestDataModel(dataController, data, {
|
|
props: [
|
|
...idKey == null ? [] : [
|
|
valueProperty17(idKey, mercatorScaleType, { id: "idValue", includeProperty: false }),
|
|
valueProperty17(idKey, mercatorScaleType, {
|
|
id: "featureValue",
|
|
includeProperty: false,
|
|
processor: () => (datum) => featureById.get(datum)
|
|
})
|
|
],
|
|
...hasLatLon ? [
|
|
valueProperty17(latitudeKey, mercatorScaleType, { id: "latValue" }),
|
|
valueProperty17(longitudeKey, mercatorScaleType, { id: "lonValue" })
|
|
] : [],
|
|
...labelKey ? [valueProperty17(labelKey, "category", { id: "labelValue" })] : [],
|
|
...sizeKey ? [valueProperty17(sizeKey, sizeScaleType, { id: "sizeValue" })] : [],
|
|
...colorKey ? [valueProperty17(colorKey, colorScaleType, { id: "colorValue" })] : []
|
|
]
|
|
});
|
|
const featureValues = idKey == null ? void 0 : dataModel.resolveColumnById(this, `featureValue`, processedData);
|
|
const latValues = hasLatLon ? dataModel.resolveColumnById(this, `latValue`, processedData) : void 0;
|
|
const lonValues = hasLatLon ? dataModel.resolveColumnById(this, `lonValue`, processedData) : void 0;
|
|
this.topologyBounds = processedData.dataSources.get(this.id)?.data.reduce((current, _datum, datumIndex) => {
|
|
const feature = featureValues?.[datumIndex];
|
|
const geometry = feature?.geometry;
|
|
if (geometry != null) {
|
|
current = geometryBbox(geometry, current);
|
|
}
|
|
if (latValues != null && lonValues != null) {
|
|
const lon = lonValues[datumIndex];
|
|
const lat = latValues[datumIndex];
|
|
current = LonLatBBox.extend(current, lon, lat, lon, lat);
|
|
}
|
|
return current;
|
|
}, void 0);
|
|
if (sizeKey != null) {
|
|
const sizeIdx = dataModel.resolveProcessedDataIndexById(this, `sizeValue`);
|
|
const processedSize = processedData.domain.values[sizeIdx] ?? [];
|
|
sizeScale.domain = sizeDomain ?? processedSize;
|
|
}
|
|
if (colorRange != null && this.isColorScaleValid()) {
|
|
const colorKeyIdx = dataModel.resolveProcessedDataIndexById(this, "colorValue");
|
|
colorScale.domain = processedData.domain.values[colorKeyIdx];
|
|
colorScale.range = colorRange;
|
|
colorScale.update();
|
|
}
|
|
this.animationState.transition("updateData");
|
|
}
|
|
isColorScaleValid() {
|
|
const { colorKey } = this.properties;
|
|
if (!colorKey) {
|
|
return false;
|
|
}
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData) {
|
|
return false;
|
|
}
|
|
const colorIdx = dataModel.resolveProcessedDataIndexById(this, "colorValue");
|
|
const dataCount = processedData.input.count;
|
|
const missCount = getMissCount4(this, processedData.defs.values[colorIdx].missing);
|
|
const colorDataMissing = dataCount === 0 || dataCount === missCount;
|
|
return !colorDataMissing;
|
|
}
|
|
getLabelDatum(node, labelValue, measurer3) {
|
|
if (labelValue == null)
|
|
return;
|
|
const {
|
|
idKey,
|
|
idName,
|
|
latitudeKey,
|
|
latitudeName,
|
|
longitudeKey,
|
|
longitudeName,
|
|
sizeKey,
|
|
sizeName,
|
|
colorKey,
|
|
colorName,
|
|
labelKey,
|
|
labelName,
|
|
label,
|
|
shape
|
|
} = this.properties;
|
|
if (labelKey == null || !label.enabled)
|
|
return;
|
|
const { datum, datumIndex, index, idValue, lonValue, latValue, point } = node;
|
|
const { placement } = label;
|
|
const labelText = this.getLabelText(
|
|
labelValue,
|
|
datum,
|
|
labelKey,
|
|
"label",
|
|
[],
|
|
label,
|
|
{
|
|
value: labelValue,
|
|
datum,
|
|
idKey,
|
|
idName,
|
|
latitudeKey,
|
|
latitudeName,
|
|
longitudeKey,
|
|
longitudeName,
|
|
sizeKey,
|
|
sizeName,
|
|
colorKey,
|
|
colorName,
|
|
labelKey,
|
|
labelName
|
|
}
|
|
);
|
|
if (labelText == null)
|
|
return;
|
|
const { width: width2, height: height2 } = measurer3.measureLines(String(labelText));
|
|
const anchor = Marker7.anchor(shape);
|
|
return {
|
|
point: { x: point.x, y: point.y, size: point.size },
|
|
label: { width: width2, height: height2, text: labelText },
|
|
anchor,
|
|
placement,
|
|
datumIndex,
|
|
datumId: createDatumId22(index, idValue, lonValue, latValue)
|
|
};
|
|
}
|
|
resolveColumn(key, columnId, processedData) {
|
|
if (key == null || this.dataModel == null)
|
|
return void 0;
|
|
return this.dataModel.resolveColumnById(this, columnId, processedData);
|
|
}
|
|
resolveDataColumns(processedData) {
|
|
const { idKey, latitudeKey, longitudeKey, sizeKey, colorKey, labelKey } = this.properties;
|
|
const hasLatLon = latitudeKey != null && longitudeKey != null;
|
|
return {
|
|
idValues: this.resolveColumn(idKey, "idValue", processedData),
|
|
featureValues: this.resolveColumn(idKey, "featureValue", processedData),
|
|
latValues: hasLatLon ? this.resolveColumn(latitudeKey, "latValue", processedData) : void 0,
|
|
lonValues: hasLatLon ? this.resolveColumn(longitudeKey, "lonValue", processedData) : void 0,
|
|
labelValues: this.resolveColumn(labelKey, "labelValue", processedData),
|
|
sizeValues: this.resolveColumn(sizeKey, "sizeValue", processedData),
|
|
colorValues: this.resolveColumn(colorKey, "colorValue", processedData)
|
|
};
|
|
}
|
|
prepareProjectedGeometries(idValues, featureValues, processedData) {
|
|
if (idValues == null || featureValues == null || this.scale == null)
|
|
return void 0;
|
|
const projectedGeometries = /* @__PURE__ */ new Map();
|
|
for (const [datumIndex] of processedData.dataSources.get(this.id)?.data.entries() ?? []) {
|
|
const id = idValues[datumIndex];
|
|
const geometry = featureValues[datumIndex]?.geometry;
|
|
const projectedGeometry = geometry == null ? void 0 : projectGeometry(geometry, this.scale);
|
|
if (id != null && projectedGeometry != null) {
|
|
projectedGeometries.set(id, projectedGeometry);
|
|
}
|
|
}
|
|
return projectedGeometries;
|
|
}
|
|
calculateMarkerSize(sizeValue) {
|
|
return sizeValue == null ? this.properties.size : this.sizeScale.convert(sizeValue, { clamp: true });
|
|
}
|
|
buildNodeDatum(datum, datumIndex, index, point, dataValues) {
|
|
return {
|
|
series: this,
|
|
datum,
|
|
datumIndex,
|
|
index,
|
|
...dataValues,
|
|
point,
|
|
midPoint: { x: point.x, y: point.y },
|
|
legendItemName: this.properties.legendItemName,
|
|
style: this.getMarkerItemStyle(
|
|
{ datumIndex, datum, colorValue: dataValues.colorValue, sizeValue: dataValues.sizeValue },
|
|
false
|
|
)
|
|
};
|
|
}
|
|
createNodeFromLatLon(datum, datumIndex, lonValue, latValue, dataValues, size, measurer3) {
|
|
if (this.scale == null) {
|
|
throw new Error("Scale is required for createNodeFromLatLon");
|
|
}
|
|
const [x, y] = this.scale.convert([lonValue, latValue]);
|
|
const point = { x, y, size };
|
|
const node = this.buildNodeDatum(datum, datumIndex, -1, point, dataValues);
|
|
const label = this.getLabelDatum(node, dataValues.labelValue, measurer3) ?? void 0;
|
|
return { node, label };
|
|
}
|
|
createNodesFromGeometry(datum, datumIndex, geometry, dataValues, size, measurer3) {
|
|
const nodes = [];
|
|
const labels = [];
|
|
for (const [index, [x, y]] of markerPositions(geometry, 1).entries()) {
|
|
const point = { x, y, size };
|
|
const node = this.buildNodeDatum(datum, datumIndex, index, point, dataValues);
|
|
nodes.push(node);
|
|
const label = this.getLabelDatum(node, dataValues.labelValue, measurer3);
|
|
if (label) {
|
|
labels.push(label);
|
|
}
|
|
}
|
|
return { nodes, labels };
|
|
}
|
|
warnMissingGeometries(missingGeometries) {
|
|
if (missingGeometries.length === 0)
|
|
return;
|
|
const missingGeometriesCap = 10;
|
|
if (missingGeometries.length > missingGeometriesCap) {
|
|
const excessItems = missingGeometries.length - missingGeometriesCap;
|
|
missingGeometries.length = missingGeometriesCap;
|
|
missingGeometries.push(`(+${excessItems} more)`);
|
|
}
|
|
logger_exports.warnOnce(`some data items do not have matches in the provided topology`, missingGeometries);
|
|
}
|
|
buildFeatureMap(topologyIdKey) {
|
|
const featureById = /* @__PURE__ */ new Map();
|
|
for (const feature of this.topology?.features.values() ?? []) {
|
|
const property = feature.properties?.[topologyIdKey];
|
|
if (property != null) {
|
|
featureById.set(property, feature);
|
|
}
|
|
}
|
|
return featureById;
|
|
}
|
|
createNodeData() {
|
|
const { id: seriesId, dataModel, processedData, sizeScale, properties, scale: scale2 } = this;
|
|
const { label } = properties;
|
|
if (dataModel == null || processedData == null || scale2 == null)
|
|
return;
|
|
if (!this.visible) {
|
|
return { itemId: seriesId, nodeData: [], labelData: [] };
|
|
}
|
|
const columns = this.resolveDataColumns(processedData);
|
|
const markerMaxSize = properties.maxSize ?? properties.size;
|
|
sizeScale.range = [Math.min(properties.size, markerMaxSize), markerMaxSize];
|
|
const measurer3 = cachedTextMeasurer(label);
|
|
const projectedGeometries = this.prepareProjectedGeometries(
|
|
columns.idValues,
|
|
columns.featureValues,
|
|
processedData
|
|
);
|
|
const nodeData = [];
|
|
const labelData = [];
|
|
const missingGeometries = [];
|
|
const rawData = processedData.dataSources.get(this.id)?.data ?? [];
|
|
for (const [datumIndex, datum] of rawData.entries()) {
|
|
const dataValues = {
|
|
idValue: columns.idValues?.[datumIndex],
|
|
lonValue: columns.lonValues?.[datumIndex],
|
|
latValue: columns.latValues?.[datumIndex],
|
|
colorValue: columns.colorValues?.[datumIndex],
|
|
sizeValue: columns.sizeValues?.[datumIndex],
|
|
labelValue: columns.labelValues?.[datumIndex]
|
|
};
|
|
const size = this.calculateMarkerSize(dataValues.sizeValue);
|
|
const projectedGeometry = dataValues.idValue == null ? void 0 : projectedGeometries?.get(dataValues.idValue);
|
|
if (dataValues.idValue != null && projectedGeometries != null && projectedGeometry == null) {
|
|
missingGeometries.push(dataValues.idValue);
|
|
}
|
|
if (dataValues.lonValue != null && dataValues.latValue != null) {
|
|
const result = this.createNodeFromLatLon(
|
|
datum,
|
|
datumIndex,
|
|
dataValues.lonValue,
|
|
dataValues.latValue,
|
|
dataValues,
|
|
size,
|
|
measurer3
|
|
);
|
|
nodeData.push(result.node);
|
|
if (result.label)
|
|
labelData.push(result.label);
|
|
} else if (projectedGeometry != null) {
|
|
const result = this.createNodesFromGeometry(
|
|
datum,
|
|
datumIndex,
|
|
projectedGeometry,
|
|
dataValues,
|
|
size,
|
|
measurer3
|
|
);
|
|
nodeData.push(...result.nodes);
|
|
labelData.push(...result.labels);
|
|
}
|
|
}
|
|
this.warnMissingGeometries(missingGeometries);
|
|
return {
|
|
itemId: seriesId,
|
|
nodeData,
|
|
labelData
|
|
};
|
|
}
|
|
updateSelections() {
|
|
if (this.nodeDataRefresh) {
|
|
this.contextNodeData = this.createNodeData();
|
|
this.nodeDataRefresh = false;
|
|
}
|
|
}
|
|
checkScaleChange() {
|
|
if (this.previousScale === this.scale)
|
|
return false;
|
|
this.previousScale = this.scale;
|
|
return true;
|
|
}
|
|
update({ seriesRect }) {
|
|
const resize = this.checkResize(seriesRect);
|
|
const scaleChange = this.checkScaleChange();
|
|
const { markerSelection, highlightMarkerSelection } = this;
|
|
const drawingMode = this.ctx.chartService.highlight?.drawingMode ?? "overlay";
|
|
this.updateSelections();
|
|
this.contentGroup.visible = this.visible;
|
|
this.labelGroup.visible = this.visible;
|
|
const highlightedDatum = this.getHighlightedDatum();
|
|
const nodeData = this.contextNodeData?.nodeData ?? [];
|
|
this.markerSelection = this.updateMarkerSelection({ markerData: nodeData, markerSelection });
|
|
this.updateMarkerNodes({ markerSelection, isHighlight: false, highlightedDatum, drawingMode: "overlay" });
|
|
this.highlightMarkerSelection = this.updateMarkerSelection({
|
|
markerData: highlightedDatum == null ? [] : [highlightedDatum],
|
|
markerSelection: highlightMarkerSelection
|
|
});
|
|
this.updateMarkerNodes({
|
|
markerSelection: highlightMarkerSelection,
|
|
isHighlight: true,
|
|
highlightedDatum,
|
|
drawingMode
|
|
});
|
|
this.updateLabelNodes({ labelSelection: this.labelSelection, isHighlight: false });
|
|
this.updateHighlightLabelSelection(highlightedDatum);
|
|
if (scaleChange || resize) {
|
|
this.animationState.transition("resize");
|
|
}
|
|
this.animationState.transition("update");
|
|
}
|
|
updatePlacedLabelData(labelData) {
|
|
this.placedLabelData = labelData;
|
|
this.labelSelection = this.labelSelection.update(labelData, (text2) => {
|
|
text2.pointerEvents = module_support_exports.PointerEvents.None;
|
|
});
|
|
this.updateLabelNodes({ labelSelection: this.labelSelection, isHighlight: false });
|
|
this.updateHighlightLabelSelection();
|
|
}
|
|
updateLabelNodes({
|
|
isHighlight,
|
|
labelSelection
|
|
}) {
|
|
const { properties } = this;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
labelSelection.each((label, placedLabel) => {
|
|
const { x, y, width: width2, height: height2, text: text2, datum: labelDatum } = placedLabel;
|
|
const style2 = getLabelStyles8(
|
|
this,
|
|
void 0,
|
|
properties,
|
|
properties.label,
|
|
isHighlight,
|
|
activeHighlight
|
|
);
|
|
const { color: fill, fontStyle, fontWeight: fontWeight2, fontSize, fontFamily } = style2;
|
|
label.visible = true;
|
|
label.x = x + width2 / 2;
|
|
label.y = y + height2 / 2;
|
|
label.text = text2;
|
|
label.fill = fill;
|
|
label.fontStyle = fontStyle;
|
|
label.fontWeight = fontWeight2;
|
|
label.fontSize = fontSize;
|
|
label.fontFamily = fontFamily;
|
|
label.textAlign = "center";
|
|
label.textBaseline = "middle";
|
|
const datumIndex = labelDatum?.datumIndex;
|
|
label.fillOpacity = this.getHighlightStyle(isHighlight, datumIndex).opacity ?? 1;
|
|
label.setBoxing(style2);
|
|
});
|
|
}
|
|
getHighlightedLabelId(highlightedDatum = this.getHighlightedDatum()) {
|
|
if (highlightedDatum == null)
|
|
return void 0;
|
|
return createDatumId22(
|
|
highlightedDatum.index,
|
|
highlightedDatum.idValue,
|
|
highlightedDatum.lonValue,
|
|
highlightedDatum.latValue
|
|
);
|
|
}
|
|
updateHighlightLabelSelection(highlightedDatum = this.getHighlightedDatum()) {
|
|
const highlightId = this.getHighlightedLabelId(highlightedDatum);
|
|
const highlightLabels = highlightId == null || !this.isLabelEnabled() ? [] : this.placedLabelData.filter((label) => label.datum.datumId === highlightId);
|
|
this.highlightLabelSelection = this.highlightLabelSelection.update(highlightLabels);
|
|
if (highlightLabels.length === 0) {
|
|
this.highlightLabelSelection.cleanup();
|
|
this.highlightLabelGroup.visible = false;
|
|
return;
|
|
}
|
|
this.highlightLabelGroup.visible = true;
|
|
this.updateLabelNodes({
|
|
labelSelection: this.highlightLabelSelection,
|
|
isHighlight: true
|
|
});
|
|
}
|
|
updateMarkerSelection(opts) {
|
|
const { markerData, markerSelection } = opts;
|
|
return markerSelection.update(
|
|
markerData,
|
|
void 0,
|
|
(datum) => createDatumId22(datum.index, datum.idValue, datum.lonValue, datum.latValue)
|
|
);
|
|
}
|
|
getMarkerItemStyle({ datumIndex, datum, colorValue, sizeValue }, isHighlight) {
|
|
const { properties, colorScale, sizeScale } = this;
|
|
const { colorRange, itemStyler } = properties;
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex);
|
|
const baseStyle = mergeDefaults(highlightStyle, properties.getStyle());
|
|
if (!isHighlight && colorValue != null) {
|
|
baseStyle.fill = this.isColorScaleValid() ? colorScale.convert(colorValue) : colorRange?.[0] ?? baseStyle.fill;
|
|
}
|
|
if (sizeValue != null) {
|
|
baseStyle.size = sizeScale.convert(sizeValue, { clamp: true });
|
|
}
|
|
let style2 = baseStyle;
|
|
if (itemStyler != null && datumIndex != null) {
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId22(datumIndex, isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const params = this.makeItemStylerParams(datum, datumIndex, isHighlight, style2);
|
|
return this.callWithContext(itemStyler, params);
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(overrides, baseStyle);
|
|
}
|
|
}
|
|
return style2;
|
|
}
|
|
makeItemStylerParams(datum, datumIndex, isHighlight, style2) {
|
|
const { id: seriesId } = this;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightState = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
seriesId,
|
|
datum,
|
|
highlightState,
|
|
...style2,
|
|
fill
|
|
};
|
|
}
|
|
updateMarkerNodes(opts) {
|
|
const { markerSelection, isHighlight, highlightedDatum, drawingMode } = opts;
|
|
const fillBBox = getTopologyShapeFillBBox(this.scale);
|
|
markerSelection.each((marker, markerDatum) => {
|
|
const { datum, point } = markerDatum;
|
|
const style2 = this.getMarkerItemStyle(markerDatum, isHighlight);
|
|
marker.shape = style2.shape;
|
|
marker.size = style2.size;
|
|
marker.setStyleProperties(style2, fillBBox);
|
|
marker.x = point.x;
|
|
marker.y = point.y;
|
|
marker.scalingCenterX = point.x;
|
|
marker.scalingCenterY = point.y;
|
|
marker.zIndex = !isHighlight && highlightedDatum != null && datum === highlightedDatum.datum ? 1 : 0;
|
|
marker.drawingMode = drawingMode;
|
|
});
|
|
}
|
|
isProcessedDataAnimatable() {
|
|
return true;
|
|
}
|
|
resetAnimation(phase) {
|
|
if (phase === "initial") {
|
|
this.animationState.transition("reset");
|
|
} else if (phase === "ready") {
|
|
this.animationState.transition("skip");
|
|
}
|
|
}
|
|
resetAllAnimation() {
|
|
this.ctx.animationManager.stopByAnimationGroupId(this.id);
|
|
this.ctx.animationManager.skipCurrentBatch();
|
|
this.labelSelection.cleanup();
|
|
this.markerSelection.cleanup();
|
|
this.highlightMarkerSelection.cleanup();
|
|
this.highlightLabelSelection.cleanup();
|
|
this.highlightLabelGroup.visible = false;
|
|
this.placedLabelData = [];
|
|
}
|
|
animateMarkers() {
|
|
const { animationManager } = this.ctx;
|
|
const fns = prepareMapMarkerAnimationFunctions();
|
|
fromToMotion7(this.id, "markers", animationManager, [this.markerSelection, this.highlightMarkerSelection], fns);
|
|
}
|
|
getLabelData() {
|
|
if (!this.isLabelEnabled())
|
|
return [];
|
|
return this.contextNodeData?.labelData ?? [];
|
|
}
|
|
pickNodeClosestDatum(p) {
|
|
const { x: x0, y: y0 } = p;
|
|
let minDistanceSquared = Infinity;
|
|
let minDatum;
|
|
for (const datum of this.contextNodeData?.nodeData ?? []) {
|
|
const { x, y, size } = datum.point;
|
|
const dx2 = Math.max(Math.abs(x - x0) - size, 0);
|
|
const dy2 = Math.max(Math.abs(y - y0) - size, 0);
|
|
const distanceSquared2 = dx2 * dx2 + dy2 * dy2;
|
|
if (distanceSquared2 < minDistanceSquared) {
|
|
minDistanceSquared = distanceSquared2;
|
|
minDatum = datum;
|
|
}
|
|
}
|
|
return minDatum == null ? void 0 : { datum: minDatum, distance: Math.sqrt(minDistanceSquared) };
|
|
}
|
|
legendItemSymbol(datumIndex) {
|
|
const { dataModel, processedData, properties } = this;
|
|
const { shape, fillOpacity, stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = properties;
|
|
let { fill } = properties;
|
|
if (datumIndex != null && this.isColorScaleValid()) {
|
|
const colorValues = dataModel.resolveColumnById(this, "colorValue", processedData);
|
|
const colorValue = colorValues[datumIndex];
|
|
fill = this.colorScale.convert(colorValue);
|
|
}
|
|
return {
|
|
marker: {
|
|
shape,
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
const { processedData, dataModel } = this;
|
|
if (processedData == null || dataModel == null)
|
|
return [];
|
|
const { id: seriesId, visible } = this;
|
|
const { title, legendItemName, idName, idKey, colorKey, colorRange, showInLegend } = this.properties;
|
|
if (legendType === "gradient" && colorKey != null && colorRange != null) {
|
|
const colorDomain = processedData.domain.values[dataModel.resolveProcessedDataIndexById(this, "colorValue")];
|
|
const legendDatum = {
|
|
legendType: "gradient",
|
|
enabled: visible,
|
|
seriesId,
|
|
series: this.getFormatterContext("color"),
|
|
colorRange,
|
|
colorDomain
|
|
};
|
|
return [legendDatum];
|
|
} else if (legendType === "category") {
|
|
const legendDatum = {
|
|
legendType: "category",
|
|
id: seriesId,
|
|
itemId: seriesId,
|
|
seriesId,
|
|
enabled: visible,
|
|
label: { text: legendItemName ?? title ?? idName ?? idKey ?? seriesId },
|
|
symbol: this.legendItemSymbol(),
|
|
legendItemName,
|
|
hideInLegend: !showInLegend
|
|
};
|
|
return [legendDatum];
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const {
|
|
id: seriesId,
|
|
dataModel,
|
|
processedData,
|
|
properties,
|
|
ctx: { formatManager }
|
|
} = this;
|
|
const {
|
|
idKey,
|
|
idName,
|
|
latitudeKey,
|
|
latitudeName,
|
|
longitudeKey,
|
|
longitudeName,
|
|
colorKey,
|
|
colorName,
|
|
sizeKey,
|
|
sizeName,
|
|
labelKey,
|
|
labelName,
|
|
title,
|
|
legendItemName,
|
|
tooltip
|
|
} = properties;
|
|
if (!dataModel || !processedData)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const sizeValue = sizeKey == null ? void 0 : dataModel.resolveColumnById(this, `sizeValue`, processedData)[datumIndex];
|
|
const colorValue = colorKey == null ? void 0 : dataModel.resolveColumnById(this, `colorValue`, processedData)[datumIndex];
|
|
const data = [];
|
|
if (this.isLabelEnabled() && labelKey != null && labelKey !== idKey) {
|
|
const labelValue = dataModel.resolveColumnById(this, `labelValue`, processedData)[datumIndex];
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "category",
|
|
value: labelValue,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key: labelKey,
|
|
source: "tooltip",
|
|
property: "label",
|
|
domain: [],
|
|
boundSeries: this.getFormatterContext("label")
|
|
});
|
|
data.push({ label: labelName, fallbackLabel: labelKey, value: content ?? labelValue });
|
|
}
|
|
if (sizeKey != null && sizeValue != null) {
|
|
const domain = dataModel.getDomain(this, `sizeValue`, "value", processedData).domain;
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value: sizeValue,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key: sizeKey,
|
|
source: "tooltip",
|
|
property: "size",
|
|
domain,
|
|
boundSeries: this.getFormatterContext("size"),
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
});
|
|
data.push({ label: sizeName, fallbackLabel: sizeKey, value: content ?? String(sizeValue) });
|
|
}
|
|
if (colorKey != null && colorValue != null) {
|
|
const domain = dataModel.getDomain(this, `colorValue`, "value", processedData).domain;
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value: colorValue,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key: colorKey,
|
|
source: "tooltip",
|
|
property: "color",
|
|
domain,
|
|
boundSeries: this.getFormatterContext("color"),
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
});
|
|
data.push({ label: colorName, fallbackLabel: colorKey, value: content ?? String(colorValue) });
|
|
}
|
|
let heading;
|
|
if (idKey != null) {
|
|
heading = dataModel.resolveColumnById(this, `idValue`, processedData)[datumIndex];
|
|
} else if (latitudeKey != null && longitudeKey != null) {
|
|
const latValue = dataModel.resolveColumnById(this, `latValue`, processedData)[datumIndex];
|
|
const lonValue = dataModel.resolveColumnById(this, `lonValue`, processedData)[datumIndex];
|
|
heading = `${Math.abs(latValue).toFixed(4)}\xB0 ${latValue >= 0 ? "N" : "S"}, ${Math.abs(lonValue).toFixed(4)}\xB0 ${lonValue >= 0 ? "W" : "E"}`;
|
|
}
|
|
const format = this.getMarkerItemStyle({ datumIndex, datum, colorValue, sizeValue }, false);
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading,
|
|
title: title ?? legendItemName,
|
|
symbol: this.legendItemSymbol(datumIndex),
|
|
data
|
|
},
|
|
{
|
|
seriesId,
|
|
datum,
|
|
title,
|
|
idKey,
|
|
idName,
|
|
latitudeKey,
|
|
latitudeName,
|
|
longitudeKey,
|
|
longitudeName,
|
|
colorKey,
|
|
colorName,
|
|
sizeKey,
|
|
sizeName,
|
|
labelKey,
|
|
labelName,
|
|
...format
|
|
}
|
|
);
|
|
}
|
|
getFormattedMarkerStyle(markerDatum) {
|
|
const format = this.getMarkerItemStyle(markerDatum, false);
|
|
return { size: format.size, shape: format.shape };
|
|
}
|
|
computeFocusBounds(opts) {
|
|
return computeMarkerFocusBounds4(this, opts);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
MapMarkerSeries.className = "MapMarkerSeries";
|
|
MapMarkerSeries.type = "map-marker";
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-marker/mapMarkerSeriesOptionsDef.ts
|
|
var { mapMarkerSeriesThemeableOptionsDef: mapMarkerSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var mapMarkerSeriesOptionsDef = {
|
|
...without(commonSeriesOptionsDefs, ["highlightStyle", "highlight"]),
|
|
...mapMarkerSeriesThemeableOptionsDef2,
|
|
type: required(constant("map-marker")),
|
|
idKey: string,
|
|
latitudeKey: string,
|
|
longitudeKey: string,
|
|
sizeKey: string,
|
|
colorKey: string,
|
|
labelKey: string,
|
|
idName: string,
|
|
latitudeName: string,
|
|
longitudeName: string,
|
|
sizeName: string,
|
|
colorName: string,
|
|
labelName: string,
|
|
topology: geoJson,
|
|
topologyIdKey: string,
|
|
legendItemName: string,
|
|
title: string
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-marker/mapMarkerModule.ts
|
|
var MapMarkerSeriesModule = {
|
|
type: "series",
|
|
name: "map-marker",
|
|
chartType: "topology",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
dependencies: [TopologyChartModule],
|
|
options: mapMarkerSeriesOptionsDef,
|
|
themeTemplate: {
|
|
...MAP_THEME_DEFAULTS,
|
|
series: {
|
|
shape: "circle",
|
|
maxSize: 30,
|
|
fill: applyMapPalette({
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $mapPalette: "fill" },
|
|
["gradient", FILL_GRADIENT_RADIAL_REVERSED_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
}),
|
|
stroke: { $mapPalette: "stroke" },
|
|
colorRange: {
|
|
$if: [
|
|
{ $eq: [{ $mapPalette: "type" }, "inbuilt"] },
|
|
{ $mapPalette: "divergingColors" },
|
|
applyMapPalette(SAFE_RANGE2_OPERATION)
|
|
]
|
|
},
|
|
fillOpacity: 0.5,
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: false,
|
|
fontSize: { $ref: "fontSize" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontWeight: { $ref: "fontWeight" },
|
|
color: { $ref: "textColor" }
|
|
},
|
|
highlight: applyMapPalette(MULTI_SERIES_HIGHLIGHT_STYLE)
|
|
},
|
|
tooltip: {
|
|
range: "exact"
|
|
}
|
|
},
|
|
create: (ctx) => new MapMarkerSeries(ctx),
|
|
validate(options, optionsDefs2, path) {
|
|
const result = validate(options, optionsDefs2, path);
|
|
const { cleared, invalid } = result;
|
|
if (cleared?.idKey == null && (cleared?.latitudeKey == null || cleared?.longitudeKey == null)) {
|
|
const extendPath2 = (key) => path ? `${path}.${key}` : key;
|
|
const message = `Either \`${extendPath2("idKey")}\` or both \`${extendPath2("latitudeKey")}\` and \`${extendPath2("longitudeKey")}\` are required.`;
|
|
invalid.push(new ValidationError("required", message, null, path));
|
|
}
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-shape-background/mapShapeBackgroundSeriesProperties.ts
|
|
var { SeriesProperties: SeriesProperties13, makeSeriesTooltip: makeSeriesTooltip24 } = module_support_exports;
|
|
var MapShapeBackgroundSeriesProperties = class extends SeriesProperties13 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.topology = void 0;
|
|
this.fill = "black";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "black";
|
|
this.strokeOpacity = 1;
|
|
this.strokeWidth = 0;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.tooltip = makeSeriesTooltip24();
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeBackgroundSeriesProperties.prototype, "topology", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeBackgroundSeriesProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeBackgroundSeriesProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeBackgroundSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeBackgroundSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeBackgroundSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeBackgroundSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeBackgroundSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeBackgroundSeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-shape-background/mapShapeBackgroundSeries.ts
|
|
var { createDatumId: createDatumId23, Selection: Selection18, Group: Group24, PointerEvents: PointerEvents13 } = module_support_exports;
|
|
var MapShapeBackgroundSeries = class extends TopologySeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
categoryKey: void 0,
|
|
pickModes: []
|
|
});
|
|
this.properties = new MapShapeBackgroundSeriesProperties();
|
|
this._chartTopology = void 0;
|
|
this.itemGroup = this.contentGroup.appendChild(new Group24({ name: "itemGroup" }));
|
|
this.datumSelection = Selection18.select(
|
|
this.itemGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.itemGroup.pointerEvents = PointerEvents13.None;
|
|
}
|
|
get topology() {
|
|
return this.properties.topology ?? this._chartTopology;
|
|
}
|
|
get focusable() {
|
|
return false;
|
|
}
|
|
setOptionsData() {
|
|
}
|
|
setChartData() {
|
|
}
|
|
getNodeData() {
|
|
return;
|
|
}
|
|
get hasData() {
|
|
return false;
|
|
}
|
|
renderToOffscreenCanvas() {
|
|
return true;
|
|
}
|
|
setChartTopology(topology) {
|
|
this._chartTopology = topology;
|
|
if (this.topology === topology) {
|
|
this.nodeDataRefresh = true;
|
|
}
|
|
}
|
|
setZIndex(zIndex) {
|
|
super.setZIndex(zIndex);
|
|
this.contentGroup.zIndex = [0 /* ShapeLineBackground */, zIndex, 0];
|
|
this.highlightGroup.zIndex = [0 /* ShapeLineBackground */, zIndex, 1];
|
|
return true;
|
|
}
|
|
nodeFactory() {
|
|
const geoGeometry = new GeoGeometry();
|
|
geoGeometry.renderMode = 1 /* Polygons */;
|
|
geoGeometry.lineJoin = "round";
|
|
geoGeometry.pointerEvents = PointerEvents13.None;
|
|
return geoGeometry;
|
|
}
|
|
processData() {
|
|
const { topology } = this;
|
|
this.topologyBounds = topology?.features.reduce((current, feature) => {
|
|
const geometry = feature.geometry;
|
|
if (geometry == null)
|
|
return current;
|
|
return geometryBbox(geometry, current);
|
|
}, void 0);
|
|
if (topology == null) {
|
|
logger_exports.warnOnce(`no topology was provided for [MapShapeBackgroundSeries]; nothing will be rendered.`);
|
|
}
|
|
}
|
|
createNodeData() {
|
|
const { id: seriesId, topology, scale: scale2, properties } = this;
|
|
if (topology == null)
|
|
return;
|
|
const { fill, fillOpacity, stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = properties;
|
|
const nodeData = [];
|
|
const labelData = [];
|
|
for (const [index, feature] of topology.features.entries()) {
|
|
const { geometry } = feature;
|
|
const projectedGeometry = geometry != null && scale2 != null ? projectGeometry(geometry, scale2) : void 0;
|
|
if (projectedGeometry == null)
|
|
continue;
|
|
nodeData.push({
|
|
series: this,
|
|
datum: feature,
|
|
datumIndex: 0,
|
|
index,
|
|
projectedGeometry,
|
|
style: { fill, fillOpacity, stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset }
|
|
});
|
|
}
|
|
return {
|
|
itemId: seriesId,
|
|
nodeData,
|
|
labelData
|
|
};
|
|
}
|
|
updateSelections() {
|
|
if (this.nodeDataRefresh) {
|
|
this.contextNodeData = this.createNodeData();
|
|
this.nodeDataRefresh = false;
|
|
}
|
|
}
|
|
update() {
|
|
const { datumSelection } = this;
|
|
this.updateSelections();
|
|
this.contentGroup.visible = this.visible;
|
|
this.labelGroup.visible = this.visible;
|
|
const { nodeData = [] } = this.contextNodeData ?? {};
|
|
this.datumSelection = this.updateDatumSelection({ nodeData, datumSelection });
|
|
this.updateDatumNodes({ datumSelection });
|
|
}
|
|
updateDatumSelection(opts) {
|
|
return opts.datumSelection.update(opts.nodeData, void 0, (datum) => createDatumId23(datum.index));
|
|
}
|
|
updateDatumNodes(opts) {
|
|
const { datumSelection } = opts;
|
|
datumSelection.each((geoGeometry, datum) => {
|
|
const { projectedGeometry } = datum;
|
|
if (projectedGeometry == null) {
|
|
geoGeometry.visible = false;
|
|
geoGeometry.projectedGeometry = void 0;
|
|
return;
|
|
}
|
|
geoGeometry.visible = true;
|
|
geoGeometry.projectedGeometry = projectedGeometry;
|
|
geoGeometry.setProperties(datum.style);
|
|
});
|
|
}
|
|
resetAnimation() {
|
|
}
|
|
getLegendData() {
|
|
return [];
|
|
}
|
|
getTooltipContent(_seriesDatum) {
|
|
return;
|
|
}
|
|
pickFocus() {
|
|
return void 0;
|
|
}
|
|
computeFocusBounds(_opts) {
|
|
return void 0;
|
|
}
|
|
hasItemStylers() {
|
|
return false;
|
|
}
|
|
};
|
|
MapShapeBackgroundSeries.className = "MapShapeBackgroundSeries";
|
|
MapShapeBackgroundSeries.type = "map-shape-background";
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-shape-background/mapShapeBackgroundSeriesOptionsDef.ts
|
|
var { mapShapeBackgroundSeriesThemeableOptionsDef: mapShapeBackgroundSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var mapShapeBackgroundSeriesOptionsDef = {
|
|
...mapShapeBackgroundSeriesThemeableOptionsDef2,
|
|
...commonSeriesOptionsDefs,
|
|
type: required(constant("map-shape-background")),
|
|
topology: geoJson
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-shape-background/mapShapeBackgroundModule.ts
|
|
var MapShapeBackgroundSeriesModule = {
|
|
type: "series",
|
|
name: "map-shape-background",
|
|
chartType: "topology",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
dependencies: [TopologyChartModule],
|
|
options: mapShapeBackgroundSeriesOptionsDef,
|
|
themeTemplate: {
|
|
...MAP_THEME_DEFAULTS,
|
|
series: {
|
|
fill: applyMapPalette({
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $path: ["/1", { $mapPalette: "fill" }, { $mapPalette: "hierarchyColors" }] },
|
|
["gradient", FILL_GRADIENT_LINEAR_HIERARCHY_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_HIERARCHY_DEFAULTS]
|
|
]
|
|
}),
|
|
stroke: { $ref: "chartBackgroundColor" },
|
|
strokeWidth: 1
|
|
}
|
|
},
|
|
create: (ctx) => new MapShapeBackgroundSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-util/polygonLabelUtil.ts
|
|
function preferredLabelCenter(polygons, { aspectRatio, precision }) {
|
|
const result = polygonPointSearch(polygons, precision, (p, cx, cy, stride) => {
|
|
const width2 = maxWidthOfRectConstrainedByCenterAndAspectRatioToPolygon(p, cx, cy, aspectRatio);
|
|
const maxWidth2 = width2 + 2 * stride * aspectRatio;
|
|
const distance3 = width2 * Math.SQRT2;
|
|
const maxDistance = maxWidth2 * Math.SQRT2;
|
|
return { distance: distance3, maxDistance };
|
|
});
|
|
if (result == null)
|
|
return;
|
|
const { x, y, distance: distance2 } = result;
|
|
const maxWidth = distance2 / Math.SQRT2;
|
|
return { x, y, maxWidth };
|
|
}
|
|
function maxWidthOfRectConstrainedByCenterAndAspectRatioToLineSegment(a, b, cx, cy, aspectRatio) {
|
|
const [ax, ay] = a;
|
|
const [bx, by] = b;
|
|
const positiveM = 1 / aspectRatio;
|
|
const abx = bx - ax;
|
|
const aby = by - ay;
|
|
const [topPointX, topPointY] = ay <= by ? a : b;
|
|
const [leftPointX, leftPointY] = ax <= bx ? a : b;
|
|
const [bottomPointX, bottomPointY] = ay <= by ? b : a;
|
|
const [rightPointX, rightPointY] = ax <= bx ? b : a;
|
|
let maxWidth = Infinity;
|
|
if (abx === 0) {
|
|
for (let i = 0; i <= 1; i += 1) {
|
|
const m = i === 0 ? positiveM : -positiveM;
|
|
const y = m * (ax - cx) + cy;
|
|
if (y >= topPointY && y <= bottomPointY) {
|
|
const height2 = Math.abs(cy - y) * 2;
|
|
const width2 = height2 * aspectRatio;
|
|
maxWidth = Math.min(maxWidth, width2);
|
|
}
|
|
}
|
|
} else {
|
|
const abm = aby / abx;
|
|
for (let i = 0; i <= 1; i += 1) {
|
|
const m = i === 0 ? positiveM : -positiveM;
|
|
const x = (abm * ax - ay - m * cx + cy) / (abm - m);
|
|
if (x >= leftPointX && x <= rightPointX) {
|
|
const width2 = Math.abs(cx - x) * 2;
|
|
maxWidth = Math.min(maxWidth, width2);
|
|
}
|
|
}
|
|
}
|
|
const positiveMRecip = aspectRatio;
|
|
const centerToTopMRecip = Math.abs((topPointX - cx) / (topPointY - cy));
|
|
const centerToBottomMRecip = Math.abs((bottomPointX - cx) / (bottomPointY - cy));
|
|
if (bottomPointY < cy && centerToBottomMRecip < positiveMRecip) {
|
|
const height2 = Math.abs(cy - bottomPointY) * 2;
|
|
const width2 = height2 * aspectRatio;
|
|
maxWidth = Math.min(maxWidth, width2);
|
|
} else if (topPointY > cy && centerToTopMRecip < positiveMRecip) {
|
|
const height2 = Math.abs(cy - topPointY) * 2;
|
|
const width2 = height2 * aspectRatio;
|
|
maxWidth = Math.min(maxWidth, width2);
|
|
}
|
|
const centerToLeftM = Math.abs((leftPointY - cy) / (leftPointX - cx));
|
|
const centerToRightM = Math.abs((rightPointY - cy) / (rightPointX - cx));
|
|
if (rightPointX < cx && centerToRightM < positiveM) {
|
|
const width2 = Math.abs(cx - rightPointX) * 2;
|
|
maxWidth = Math.min(maxWidth, width2);
|
|
} else if (leftPointX > cx && centerToLeftM < positiveM) {
|
|
const width2 = Math.abs(cx - leftPointX) * 2;
|
|
maxWidth = Math.min(maxWidth, width2);
|
|
}
|
|
return maxWidth;
|
|
}
|
|
function maxWidthOfRectConstrainedByCenterAndAspectRatioToPolygon(polygons, cx, cy, aspectRatio) {
|
|
let inside = false;
|
|
let minWidth = Infinity;
|
|
for (const polygon of polygons) {
|
|
let p0 = polygon.at(-1);
|
|
let [x0, y0] = p0;
|
|
for (const p1 of polygon) {
|
|
const [x1, y1] = p1;
|
|
if (y1 > cy !== y0 > cy && cx < (x0 - x1) * (cy - y1) / (y0 - y1) + x1) {
|
|
inside = !inside;
|
|
}
|
|
const width2 = maxWidthOfRectConstrainedByCenterAndAspectRatioToLineSegment(p0, p1, cx, cy, aspectRatio);
|
|
minWidth = Math.min(minWidth, width2);
|
|
p0 = p1;
|
|
x0 = x1;
|
|
y0 = y1;
|
|
}
|
|
}
|
|
return (inside ? 1 : -1) * minWidth;
|
|
}
|
|
function applyX(into, cx, x) {
|
|
if (x >= cx) {
|
|
into.maxX = Math.min(into.maxX, x - cx);
|
|
}
|
|
if (x <= cx) {
|
|
into.minX = Math.max(into.minX, x - cx);
|
|
}
|
|
}
|
|
function xExtentsOfRectConstrainedByCenterAndHeightToLineSegment(into, a, b, cx, cy, height2) {
|
|
const ry0 = cy - height2 / 2;
|
|
const ry1 = cy + height2 / 2;
|
|
const [ax, ay] = a;
|
|
const [bx, by] = b;
|
|
const abx = bx - ax;
|
|
const aby = by - ay;
|
|
const [leftPointX, leftPointY] = ax <= bx ? a : b;
|
|
const [rightPointX, rightPointY] = ax <= bx ? b : a;
|
|
if (abx !== 0) {
|
|
const abm = aby / abx;
|
|
for (let i = 0; i <= 1; i += 1) {
|
|
const y = i === 0 ? ry0 : ry1;
|
|
const x = (y - ay) / abm + ax;
|
|
if (x >= leftPointX && x <= rightPointX) {
|
|
applyX(into, cx, x);
|
|
}
|
|
}
|
|
} else if (Math.max(ry0, Math.min(ay, by)) <= Math.min(ry1, Math.max(ay, by))) {
|
|
applyX(into, cx, ax);
|
|
}
|
|
if (rightPointX < cx && rightPointY >= ry0 && rightPointY <= ry1) {
|
|
applyX(into, cx, rightPointX);
|
|
} else if (leftPointX > cx && leftPointY >= ry0 && leftPointY <= ry1) {
|
|
applyX(into, cx, leftPointX);
|
|
}
|
|
return into;
|
|
}
|
|
function maxWidthInPolygonForRectOfHeight(polygons, cx, cy, height2) {
|
|
const result = {
|
|
minX: -Infinity,
|
|
maxX: Infinity
|
|
};
|
|
for (const polygon of polygons) {
|
|
let p0 = polygon.at(-1);
|
|
for (const p1 of polygon) {
|
|
xExtentsOfRectConstrainedByCenterAndHeightToLineSegment(result, p0, p1, cx, cy, height2);
|
|
p0 = p1;
|
|
}
|
|
}
|
|
const { minX, maxX } = result;
|
|
if (Number.isFinite(minX) && Number.isFinite(maxX)) {
|
|
return { x: cx + (minX + maxX) / 2, width: maxX - minX };
|
|
} else {
|
|
return { x: cx, width: 0 };
|
|
}
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-shape/mapShapeSeriesProperties.ts
|
|
var { SeriesProperties: SeriesProperties14, makeSeriesTooltip: makeSeriesTooltip25 } = module_support_exports;
|
|
var MapShapeSeriesProperties = class extends SeriesProperties14 {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.topology = void 0;
|
|
this.idKey = "";
|
|
this.idName = void 0;
|
|
this.topologyIdKey = "name";
|
|
this.labelKey = void 0;
|
|
this.labelName = void 0;
|
|
this.colorRange = void 0;
|
|
this.fill = "black";
|
|
this.fillOpacity = 1;
|
|
this.stroke = "black";
|
|
this.strokeOpacity = 1;
|
|
this.strokeWidth = 0;
|
|
this.lineDash = [0];
|
|
this.lineDashOffset = 0;
|
|
this.padding = 0;
|
|
this.label = new AutoSizedSecondaryLabel();
|
|
this.tooltip = makeSeriesTooltip25();
|
|
}
|
|
getStyle() {
|
|
const { fill, fillOpacity, stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = this;
|
|
return {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset,
|
|
opacity: 1
|
|
};
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "topology", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "title", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "legendItemName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "idKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "idName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "topologyIdKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "labelKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "labelName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "colorKey", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "colorName", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "colorRange", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "fillOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "stroke", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "strokeOpacity", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "strokeWidth", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "lineDash", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "lineDashOffset", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "padding", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "itemStyler", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "label", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], MapShapeSeriesProperties.prototype, "tooltip", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-shape/mapShapeSeries.ts
|
|
var {
|
|
getMissCount: getMissCount5,
|
|
createDatumId: createDatumId24,
|
|
SeriesNodePickMode: SeriesNodePickMode17,
|
|
valueProperty: valueProperty18,
|
|
ColorScale: ColorScale5,
|
|
Group: Group25,
|
|
Selection: Selection19,
|
|
Text: Text11,
|
|
PointerEvents: PointerEvents14,
|
|
getLabelStyles: getLabelStyles9
|
|
} = module_support_exports;
|
|
var fixedScale = module_support_exports.MercatorScale.fixedScale();
|
|
var MapShapeSeries = class extends TopologySeries {
|
|
constructor(moduleCtx) {
|
|
super({
|
|
moduleCtx,
|
|
categoryKey: void 0,
|
|
propertyKeys: {
|
|
color: ["colorKey"],
|
|
label: ["labelKey"]
|
|
},
|
|
propertyNames: {
|
|
color: ["colorName"],
|
|
label: ["labelName"]
|
|
},
|
|
pickModes: [SeriesNodePickMode17.EXACT_SHAPE_MATCH, SeriesNodePickMode17.NEAREST_NODE]
|
|
});
|
|
this.properties = new MapShapeSeriesProperties();
|
|
this._chartTopology = void 0;
|
|
this.colorScale = new ColorScale5();
|
|
this.itemGroup = this.contentGroup.appendChild(new Group25({ name: "itemGroup" }));
|
|
this.itemLabelGroup = this.contentGroup.appendChild(new Group25({ name: "itemLabelGroup" }));
|
|
this.datumSelection = Selection19.select(
|
|
this.itemGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.labelSelection = Selection19.select(
|
|
this.itemLabelGroup,
|
|
Text11
|
|
);
|
|
this.highlightDatumSelection = Selection19.select(
|
|
this.highlightNodeGroup,
|
|
() => this.nodeFactory()
|
|
);
|
|
this.highlightLabelSelection = Selection19.select(this.highlightLabelGroup, Text11);
|
|
this.previousLabelLayouts = void 0;
|
|
this._previousDatumMidPoint = void 0;
|
|
this.itemLabelGroup.pointerEvents = PointerEvents14.None;
|
|
}
|
|
getNodeData() {
|
|
return this.contextNodeData?.nodeData;
|
|
}
|
|
get topology() {
|
|
return this.properties.topology ?? this._chartTopology;
|
|
}
|
|
get hasData() {
|
|
return super.hasData && this.topology != null;
|
|
}
|
|
renderToOffscreenCanvas() {
|
|
return true;
|
|
}
|
|
setChartTopology(topology) {
|
|
this._chartTopology = topology;
|
|
if (this.topology === topology) {
|
|
this.nodeDataRefresh = true;
|
|
}
|
|
}
|
|
setZIndex(zIndex) {
|
|
super.setZIndex(zIndex);
|
|
this.contentGroup.zIndex = [1 /* ShapeLine */, zIndex];
|
|
this.highlightGroup.zIndex = [4 /* ShapeLineHighlight */, zIndex];
|
|
return true;
|
|
}
|
|
isLabelEnabled() {
|
|
return this.properties.labelKey != null && this.properties.label.enabled;
|
|
}
|
|
nodeFactory() {
|
|
const geoGeometry = new GeoGeometry();
|
|
geoGeometry.renderMode = 1 /* Polygons */;
|
|
geoGeometry.lineJoin = "round";
|
|
return geoGeometry;
|
|
}
|
|
async processData(dataController) {
|
|
if (this.data == null)
|
|
return;
|
|
const { data, topology, colorScale } = this;
|
|
const { topologyIdKey, idKey, colorKey, labelKey, colorRange } = this.properties;
|
|
const featureById = /* @__PURE__ */ new Map();
|
|
for (const feature of topology?.features.values() ?? []) {
|
|
const property = feature.properties?.[topologyIdKey];
|
|
if (property == null || !containsType(feature.geometry, 1 /* Polygon */))
|
|
continue;
|
|
featureById.set(property, feature);
|
|
}
|
|
const colorScaleType = this.colorScale.type;
|
|
const mercatorScaleType = this.scale?.type;
|
|
const { dataModel, processedData } = await this.requestDataModel(dataController, data, {
|
|
props: [
|
|
valueProperty18(idKey, mercatorScaleType, { id: "idValue", includeProperty: false }),
|
|
valueProperty18(idKey, mercatorScaleType, {
|
|
id: "featureValue",
|
|
includeProperty: false,
|
|
processor: () => (datum) => featureById.get(datum)
|
|
}),
|
|
...labelKey ? [valueProperty18(labelKey, "category", { id: "labelValue" })] : [],
|
|
...colorKey ? [valueProperty18(colorKey, colorScaleType, { id: "colorValue" })] : []
|
|
]
|
|
});
|
|
const featureValues = dataModel.resolveColumnById(this, `featureValue`, processedData);
|
|
this.topologyBounds = featureValues.reduce((current, feature) => {
|
|
const geometry = feature?.geometry;
|
|
if (geometry == null)
|
|
return current;
|
|
return geometryBbox(geometry, current);
|
|
}, void 0);
|
|
if (colorRange != null && this.isColorScaleValid()) {
|
|
const colorKeyIdx = dataModel.resolveProcessedDataIndexById(this, "colorValue");
|
|
colorScale.domain = processedData.domain.values[colorKeyIdx];
|
|
colorScale.range = colorRange;
|
|
colorScale.update();
|
|
}
|
|
if (topology == null) {
|
|
logger_exports.warnOnce(`no topology was provided for [MapShapeSeries]; nothing will be rendered.`);
|
|
}
|
|
}
|
|
isColorScaleValid() {
|
|
const { colorKey } = this.properties;
|
|
if (!colorKey) {
|
|
return false;
|
|
}
|
|
const { dataModel, processedData } = this;
|
|
if (!dataModel || !processedData) {
|
|
return false;
|
|
}
|
|
const colorIdx = dataModel.resolveProcessedDataIndexById(this, "colorValue");
|
|
const dataCount = processedData.input.count;
|
|
const missCount = getMissCount5(this, processedData.defs.values[colorIdx].missing);
|
|
const colorDataMissing = dataCount === 0 || dataCount === missCount;
|
|
return !colorDataMissing;
|
|
}
|
|
getLabelLayout(datum, labelValue, measurer3, geometry, previousLabelLayout) {
|
|
if (labelValue == null || geometry == null)
|
|
return;
|
|
const { idKey, idName, colorKey, colorName, labelKey, labelName, padding: padding2, label } = this.properties;
|
|
if (labelKey == null || !label.enabled)
|
|
return;
|
|
const labelText = this.getLabelText(
|
|
labelValue,
|
|
datum,
|
|
labelKey,
|
|
"label",
|
|
[],
|
|
label,
|
|
{
|
|
value: labelValue,
|
|
datum,
|
|
idKey,
|
|
idName,
|
|
colorKey,
|
|
colorName,
|
|
labelKey,
|
|
labelName
|
|
}
|
|
);
|
|
if (labelText == null)
|
|
return;
|
|
const baseSize = isArray(labelText) ? measureTextSegments(labelText, label) : measurer3.measureLines(String(labelText));
|
|
const aspectRatio = (baseSize.width + 2 * padding2) / (baseSize.height + 2 * padding2);
|
|
if (previousLabelLayout?.geometry === geometry && previousLabelLayout?.labelText === labelText && previousLabelLayout?.aspectRatio === aspectRatio) {
|
|
return previousLabelLayout;
|
|
}
|
|
const fixedGeometry = projectGeometry(geometry, fixedScale);
|
|
const fixedPolygon = largestPolygon(fixedGeometry);
|
|
if (fixedPolygon == null)
|
|
return;
|
|
const labelPlacement = preferredLabelCenter(fixedPolygon, {
|
|
aspectRatio,
|
|
precision: 1e-3
|
|
});
|
|
if (labelPlacement == null)
|
|
return;
|
|
const { x, y, maxWidth } = labelPlacement;
|
|
return { geometry, labelText, aspectRatio, x, y, maxWidth, fixedPolygon };
|
|
}
|
|
getLabelDatum(labelLayout, scaling, datumIndex, idValue) {
|
|
const { scale: scale2 } = this;
|
|
if (scale2 == null)
|
|
return;
|
|
const { padding: padding2, label } = this.properties;
|
|
const { labelText, aspectRatio, x: untruncatedX, y, maxWidth, fixedPolygon } = labelLayout;
|
|
const maxSizeWithoutTruncation = {
|
|
width: Math.ceil(maxWidth * scaling),
|
|
height: Math.ceil(maxWidth * scaling / aspectRatio),
|
|
meta: untruncatedX
|
|
};
|
|
const labelFormatting = formatSingleLabel(
|
|
toPlainText(labelText),
|
|
label,
|
|
{ padding: padding2 },
|
|
(height2, allowTruncation) => {
|
|
if (!allowTruncation) {
|
|
return maxSizeWithoutTruncation;
|
|
}
|
|
const result = maxWidthInPolygonForRectOfHeight(fixedPolygon, untruncatedX, y, height2 / scaling);
|
|
return {
|
|
width: result.width * scaling,
|
|
height: height2,
|
|
meta: result.x
|
|
};
|
|
}
|
|
);
|
|
if (labelFormatting == null)
|
|
return;
|
|
const [{ text: text2, fontSize, lineHeight, width: width2 }, formattingX] = labelFormatting;
|
|
const x = width2 < maxSizeWithoutTruncation.width ? untruncatedX : formattingX;
|
|
const position = this.scale.convert(fixedScale.invert([x, y]));
|
|
return {
|
|
x: position[0],
|
|
y: position[1],
|
|
text: text2,
|
|
fontSize,
|
|
lineHeight,
|
|
datumIndex,
|
|
idValue,
|
|
datumId: createDatumId24(idValue)
|
|
};
|
|
}
|
|
resolveColumn(key, columnId, processedData) {
|
|
if (key == null || this.dataModel == null)
|
|
return void 0;
|
|
return this.dataModel.resolveColumnById(this, columnId, processedData);
|
|
}
|
|
resolveShapeDataColumns(processedData) {
|
|
const { colorKey, labelKey } = this.properties;
|
|
return {
|
|
idValues: this.dataModel.resolveColumnById(this, "idValue", processedData),
|
|
featureValues: this.dataModel.resolveColumnById(this, "featureValue", processedData),
|
|
labelValues: this.resolveColumn(labelKey, "labelValue", processedData),
|
|
colorValues: this.resolveColumn(colorKey, "colorValue", processedData)
|
|
};
|
|
}
|
|
warnMissingGeometries(missingGeometries) {
|
|
if (missingGeometries.length === 0)
|
|
return;
|
|
const missingGeometriesCap = 10;
|
|
if (missingGeometries.length > missingGeometriesCap) {
|
|
const excessItems = missingGeometries.length - missingGeometriesCap;
|
|
missingGeometries.length = missingGeometriesCap;
|
|
missingGeometries.push(`(+${excessItems} more)`);
|
|
}
|
|
logger_exports.warnOnce(`some data items do not have matches in the provided topology`, missingGeometries);
|
|
}
|
|
createNodeData() {
|
|
const { id: seriesId, dataModel, processedData, properties, scale: scale2, previousLabelLayouts } = this;
|
|
const { label, legendItemName, colorKey } = properties;
|
|
if (dataModel == null || processedData == null)
|
|
return;
|
|
if (!this.visible) {
|
|
return { itemId: seriesId, nodeData: [], labelData: [] };
|
|
}
|
|
const scaling = scale2 == null ? Number.NaN : (scale2.range[1][0] - scale2.range[0][0]) / scale2.bounds.width;
|
|
const columns = this.resolveShapeDataColumns(processedData);
|
|
const measurer3 = cachedTextMeasurer(label);
|
|
const labelLayouts = /* @__PURE__ */ new Map();
|
|
this.previousLabelLayouts = labelLayouts;
|
|
const nodeData = [];
|
|
const labelData = [];
|
|
const missingGeometries = [];
|
|
const rawData = processedData.dataSources.get(this.id)?.data ?? [];
|
|
for (const [datumIndex, datum] of rawData.entries()) {
|
|
const dataValues = {
|
|
idValue: columns.idValues[datumIndex],
|
|
colorValue: columns.colorValues?.[datumIndex],
|
|
labelValue: columns.labelValues?.[datumIndex]
|
|
};
|
|
const geometry = columns.featureValues[datumIndex]?.geometry ?? void 0;
|
|
if (geometry == null) {
|
|
missingGeometries.push(dataValues.idValue);
|
|
}
|
|
if (colorKey != null && dataValues.colorValue == null) {
|
|
continue;
|
|
}
|
|
const labelLayout = this.getLabelLayout(
|
|
datum,
|
|
dataValues.labelValue,
|
|
measurer3,
|
|
geometry,
|
|
previousLabelLayouts?.get(dataValues.idValue)
|
|
);
|
|
if (labelLayout != null) {
|
|
labelLayouts.set(dataValues.idValue, labelLayout);
|
|
}
|
|
const labelDatum = labelLayout != null && scale2 != null ? this.getLabelDatum(labelLayout, scaling, datumIndex, dataValues.idValue) : void 0;
|
|
if (labelDatum != null) {
|
|
labelData.push(labelDatum);
|
|
}
|
|
const projectedGeometry = geometry != null && scale2 != null ? projectGeometry(geometry, scale2) : void 0;
|
|
nodeData.push({
|
|
series: this,
|
|
datum,
|
|
datumIndex,
|
|
...dataValues,
|
|
projectedGeometry,
|
|
legendItemName,
|
|
style: this.getItemStyle({ datum, datumIndex, colorValue: dataValues.colorValue }, false)
|
|
});
|
|
}
|
|
this.warnMissingGeometries(missingGeometries);
|
|
return {
|
|
itemId: seriesId,
|
|
nodeData,
|
|
labelData
|
|
};
|
|
}
|
|
updateSelections() {
|
|
if (this.nodeDataRefresh) {
|
|
this.contextNodeData = this.createNodeData();
|
|
this.nodeDataRefresh = false;
|
|
}
|
|
}
|
|
update() {
|
|
const { datumSelection, labelSelection, highlightDatumSelection } = this;
|
|
this.updateSelections();
|
|
this.contentGroup.visible = this.visible;
|
|
this.labelGroup.visible = this.visible;
|
|
const drawingMode = this.ctx.chartService.highlight?.drawingMode ?? "overlay";
|
|
const highlightedDatum = this.getHighlightedDatum();
|
|
const nodeData = this.contextNodeData?.nodeData ?? [];
|
|
const labelData = this.contextNodeData?.labelData ?? [];
|
|
this.datumSelection = this.updateDatumSelection({ nodeData, datumSelection });
|
|
this.updateDatumStyles({ datumSelection, isHighlight: false });
|
|
this.updateDatumNodes({ datumSelection, drawingMode: "overlay" });
|
|
this.labelSelection = this.updateLabelSelection({ labelData, labelSelection });
|
|
const highlightLabelData = this.getHighlightLabelData(labelData, highlightedDatum);
|
|
this.highlightLabelSelection = this.updateLabelSelection({
|
|
labelData: highlightLabelData,
|
|
labelSelection: this.highlightLabelSelection
|
|
});
|
|
this.updateLabelNodes({ labelSelection: this.labelSelection, isHighlight: false });
|
|
this.updateLabelNodes({ labelSelection: this.highlightLabelSelection, isHighlight: true });
|
|
this.highlightDatumSelection = this.updateDatumSelection({
|
|
nodeData: highlightedDatum == null ? [] : [highlightedDatum],
|
|
datumSelection: highlightDatumSelection
|
|
});
|
|
this.updateDatumStyles({ datumSelection: highlightDatumSelection, isHighlight: true });
|
|
this.updateDatumNodes({ datumSelection: highlightDatumSelection, drawingMode });
|
|
}
|
|
getHighlightLabelData(labelData, highlightedDatum) {
|
|
if (labelData.length === 0)
|
|
return [];
|
|
const highlightId = createDatumId24(highlightedDatum?.idValue);
|
|
return labelData.filter(
|
|
(labelDatum) => labelDatum.datumId === highlightId && labelDatum.datumIndex === highlightedDatum?.datumIndex
|
|
);
|
|
}
|
|
updateDatumSelection(opts) {
|
|
return opts.datumSelection.update(opts.nodeData, void 0, (datum) => createDatumId24(datum.idValue));
|
|
}
|
|
getItemStyle({ datumIndex, datum, colorValue }, isHighlight) {
|
|
const { properties, colorScale } = this;
|
|
const { colorRange, itemStyler } = properties;
|
|
const baseStyle = properties.getStyle();
|
|
if (colorValue != null) {
|
|
const fillOverride = this.isColorScaleValid() ? colorScale.convert(colorValue) : colorRange?.[0];
|
|
if (fillOverride != null) {
|
|
baseStyle.fill = fillOverride;
|
|
}
|
|
}
|
|
const highlightStyle = this.getHighlightStyle(isHighlight, datumIndex);
|
|
let style2 = mergeDefaults(highlightStyle, baseStyle);
|
|
if (itemStyler != null && datumIndex != null) {
|
|
const overrides = this.cachedDatumCallback(
|
|
createDatumId24(datumIndex, isHighlight ? "highlight" : "node"),
|
|
() => {
|
|
const params = this.makeItemStylerParams(datum, datumIndex, isHighlight, style2);
|
|
return this.ctx.optionsGraphService.resolvePartial(
|
|
["series", `${this.declarationOrder}`],
|
|
this.callWithContext(itemStyler, params)
|
|
);
|
|
}
|
|
);
|
|
if (overrides) {
|
|
style2 = mergeDefaults(overrides, style2);
|
|
}
|
|
}
|
|
return style2;
|
|
}
|
|
makeItemStylerParams(datum, datumIndex, isHighlight, style2) {
|
|
const { id: seriesId } = this;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
const highlightState = this.getHighlightStateString(activeHighlight, isHighlight, datumIndex);
|
|
const fill = this.filterItemStylerFillParams(style2.fill) ?? style2.fill;
|
|
return {
|
|
seriesId,
|
|
datum,
|
|
highlightState,
|
|
...style2,
|
|
fill
|
|
};
|
|
}
|
|
updateDatumStyles({
|
|
datumSelection,
|
|
isHighlight
|
|
}) {
|
|
datumSelection.each((_, nodeDatum) => {
|
|
nodeDatum.style = this.getItemStyle(nodeDatum, isHighlight);
|
|
});
|
|
}
|
|
updateDatumNodes({
|
|
datumSelection,
|
|
drawingMode
|
|
}) {
|
|
const fillBBox = getTopologyShapeFillBBox(this.scale);
|
|
datumSelection.each((geoGeometry, nodeDatum) => {
|
|
const { projectedGeometry } = nodeDatum;
|
|
if (projectedGeometry == null) {
|
|
geoGeometry.visible = false;
|
|
geoGeometry.projectedGeometry = void 0;
|
|
return;
|
|
}
|
|
geoGeometry.visible = true;
|
|
geoGeometry.projectedGeometry = projectedGeometry;
|
|
geoGeometry.setStyleProperties(nodeDatum.style, fillBBox);
|
|
geoGeometry.drawingMode = drawingMode;
|
|
});
|
|
}
|
|
updateLabelSelection(opts) {
|
|
const labels = this.isLabelEnabled() ? opts.labelData : [];
|
|
return opts.labelSelection.update(labels);
|
|
}
|
|
updateLabelNodes({
|
|
isHighlight,
|
|
labelSelection
|
|
}) {
|
|
const { properties } = this;
|
|
const activeHighlight = this.ctx.highlightManager?.getActiveHighlight();
|
|
labelSelection.each((label, labelDatum) => {
|
|
const { x, y, text: text2, fontSize, lineHeight, datumIndex } = labelDatum;
|
|
const style2 = getLabelStyles9(
|
|
this,
|
|
void 0,
|
|
properties,
|
|
properties.label,
|
|
isHighlight,
|
|
activeHighlight
|
|
);
|
|
const { color: fill, fontStyle, fontWeight: fontWeight2, fontFamily } = style2;
|
|
label.visible = true;
|
|
label.x = x;
|
|
label.y = y;
|
|
label.text = text2;
|
|
label.fill = fill;
|
|
label.fontStyle = fontStyle;
|
|
label.fontWeight = fontWeight2;
|
|
label.fontSize = fontSize;
|
|
label.lineHeight = lineHeight;
|
|
label.fontFamily = fontFamily;
|
|
label.textAlign = "center";
|
|
label.textBaseline = "middle";
|
|
label.fillOpacity = this.getHighlightStyle(isHighlight, datumIndex).opacity ?? 1;
|
|
label.setBoxing(style2);
|
|
});
|
|
}
|
|
resetAnimation() {
|
|
}
|
|
pickNodeClosestDatum({ x, y }) {
|
|
let minDistanceSquared = Infinity;
|
|
let minDatum;
|
|
this.datumSelection.each((node, datum) => {
|
|
const distanceSquared2 = node.distanceSquared(x, y);
|
|
if (distanceSquared2 < minDistanceSquared) {
|
|
minDistanceSquared = distanceSquared2;
|
|
minDatum = datum;
|
|
}
|
|
});
|
|
return minDatum == null ? void 0 : { datum: minDatum, distance: Math.sqrt(minDistanceSquared) };
|
|
}
|
|
datumMidPoint(datum) {
|
|
const { _previousDatumMidPoint } = this;
|
|
if (_previousDatumMidPoint?.datum === datum) {
|
|
return _previousDatumMidPoint.point;
|
|
}
|
|
const projectedGeometry = datum.projectedGeometry;
|
|
const polygon = projectedGeometry == null ? void 0 : largestPolygon(projectedGeometry);
|
|
const center2 = polygon == null ? void 0 : polygonMarkerCenter(polygon, 2);
|
|
const point = center2 == null ? void 0 : { x: center2[0], y: center2[1] };
|
|
this._previousDatumMidPoint = { datum, point };
|
|
return point;
|
|
}
|
|
legendItemSymbol(datumIndex) {
|
|
const { dataModel, processedData, properties } = this;
|
|
const { fillOpacity, stroke: stroke3, strokeWidth, strokeOpacity, lineDash, lineDashOffset } = properties;
|
|
let { fill } = properties;
|
|
if (datumIndex != null && this.isColorScaleValid()) {
|
|
const colorValues = dataModel.resolveColumnById(this, "colorValue", processedData);
|
|
const colorValue = colorValues[datumIndex];
|
|
if (colorValue != null) {
|
|
fill = this.colorScale.convert(colorValue);
|
|
}
|
|
}
|
|
return {
|
|
marker: {
|
|
fill,
|
|
fillOpacity,
|
|
stroke: stroke3,
|
|
strokeWidth,
|
|
strokeOpacity,
|
|
lineDash,
|
|
lineDashOffset
|
|
}
|
|
};
|
|
}
|
|
getLegendData(legendType) {
|
|
const { processedData, dataModel } = this;
|
|
if (processedData == null || dataModel == null)
|
|
return [];
|
|
const { id: seriesId, visible } = this;
|
|
const { title, legendItemName, idKey, idName, colorKey, colorRange, showInLegend } = this.properties;
|
|
if (legendType === "gradient" && colorKey != null && colorRange != null) {
|
|
const colorDomain = processedData.domain.values[dataModel.resolveProcessedDataIndexById(this, "colorValue")];
|
|
const legendDatum = {
|
|
legendType: "gradient",
|
|
enabled: visible,
|
|
seriesId,
|
|
series: this.getFormatterContext("color"),
|
|
colorRange,
|
|
colorDomain
|
|
};
|
|
return [legendDatum];
|
|
} else if (legendType === "category") {
|
|
const legendDatum = {
|
|
legendType: "category",
|
|
id: seriesId,
|
|
itemId: seriesId,
|
|
seriesId,
|
|
enabled: visible,
|
|
label: { text: legendItemName ?? title ?? idName ?? idKey },
|
|
symbol: this.legendItemSymbol(),
|
|
legendItemName,
|
|
hideInLegend: !showInLegend
|
|
};
|
|
return [legendDatum];
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
getTooltipContent(datumIndex) {
|
|
const {
|
|
id: seriesId,
|
|
dataModel,
|
|
processedData,
|
|
properties,
|
|
ctx: { formatManager }
|
|
} = this;
|
|
const { idKey, idName, colorKey, colorName, labelKey, labelName, legendItemName, title, tooltip } = properties;
|
|
if (!dataModel || !processedData)
|
|
return;
|
|
const datum = processedData.dataSources.get(this.id)?.data[datumIndex];
|
|
const idValue = dataModel.resolveColumnById(this, `idValue`, processedData)[datumIndex];
|
|
const colorValue = colorKey == null ? void 0 : dataModel.resolveColumnById(this, `colorValue`, processedData)[datumIndex];
|
|
if (colorKey != null && colorValue == null) {
|
|
return;
|
|
}
|
|
const data = [];
|
|
if (this.isLabelEnabled() && labelKey != null && labelKey !== idKey) {
|
|
const labelValue = dataModel.resolveColumnById(this, `labelValue`, processedData)[datumIndex];
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "category",
|
|
value: labelValue,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key: labelKey,
|
|
source: "tooltip",
|
|
property: "label",
|
|
domain: [],
|
|
boundSeries: this.getFormatterContext("label")
|
|
});
|
|
data.push({ label: labelName, fallbackLabel: labelKey, value: content ?? labelValue });
|
|
}
|
|
if (colorValue != null) {
|
|
const domain = dataModel.getDomain(this, `colorValue`, "value", processedData).domain;
|
|
const content = formatManager.format(this.callWithContext.bind(this), {
|
|
type: "number",
|
|
value: colorValue,
|
|
datum,
|
|
seriesId,
|
|
legendItemName,
|
|
key: colorKey,
|
|
source: "tooltip",
|
|
property: "color",
|
|
domain,
|
|
boundSeries: this.getFormatterContext("color"),
|
|
fractionDigits: void 0,
|
|
visibleDomain: void 0
|
|
});
|
|
data.push({ label: colorName, fallbackLabel: colorKey, value: content ?? String(colorValue) });
|
|
}
|
|
const format = this.getItemStyle({ datum, datumIndex, colorValue }, false);
|
|
return this.formatTooltipWithContext(
|
|
tooltip,
|
|
{
|
|
heading: idValue,
|
|
title: title ?? legendItemName,
|
|
symbol: this.legendItemSymbol(datumIndex),
|
|
data
|
|
},
|
|
{ seriesId, datum, title, idKey, idName, colorKey, colorName, labelKey, labelName, ...format }
|
|
);
|
|
}
|
|
computeFocusBounds(opts) {
|
|
return findFocusedGeoGeometry(this, opts);
|
|
}
|
|
hasItemStylers() {
|
|
return this.properties.itemStyler != null || this.properties.label.itemStyler != null;
|
|
}
|
|
};
|
|
MapShapeSeries.className = "MapShapeSeries";
|
|
MapShapeSeries.type = "map-shape";
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-shape/mapShapeSeriesOptionsDef.ts
|
|
var { mapShapeSeriesThemeableOptionsDef: mapShapeSeriesThemeableOptionsDef2 } = module_support_exports;
|
|
var mapShapeSeriesOptionsDef = {
|
|
...without(commonSeriesOptionsDefs, ["highlightStyle", "highlight"]),
|
|
...mapShapeSeriesThemeableOptionsDef2,
|
|
type: required(constant("map-shape")),
|
|
idKey: required(string),
|
|
colorKey: string,
|
|
labelKey: string,
|
|
idName: string,
|
|
colorName: string,
|
|
labelName: string,
|
|
topology: geoJson,
|
|
topologyIdKey: string,
|
|
legendItemName: string,
|
|
title: string
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/series/map-shape/mapShapeModule.ts
|
|
var MapShapeSeriesModule = {
|
|
type: "series",
|
|
name: "map-shape",
|
|
chartType: "topology",
|
|
enterprise: true,
|
|
version: VERSION,
|
|
dependencies: [TopologyChartModule],
|
|
options: mapShapeSeriesOptionsDef,
|
|
themeTemplate: {
|
|
...MAP_THEME_DEFAULTS,
|
|
series: {
|
|
fill: applyMapPalette({
|
|
$applySwitch: [
|
|
{ $path: "type" },
|
|
{ $mapPalette: "fill" },
|
|
["gradient", FILL_GRADIENT_LINEAR_DEFAULTS],
|
|
["image", FILL_IMAGE_DEFAULTS],
|
|
["pattern", FILL_PATTERN_DEFAULTS]
|
|
]
|
|
}),
|
|
stroke: { $ref: "chartBackgroundColor" },
|
|
colorRange: {
|
|
$if: [
|
|
{ $eq: [{ $mapPalette: "type" }, "inbuilt"] },
|
|
{ $mapPalette: "divergingColors" },
|
|
applyMapPalette(SAFE_RANGE2_OPERATION)
|
|
]
|
|
},
|
|
fillOpacity: 1,
|
|
strokeWidth: 1,
|
|
lineDash: [0],
|
|
lineDashOffset: 0,
|
|
padding: 2,
|
|
label: {
|
|
...LABEL_BOXING_DEFAULTS,
|
|
enabled: true,
|
|
color: { $ref: "chartBackgroundColor" },
|
|
fontFamily: { $ref: "fontFamily" },
|
|
fontSize: { $ref: "fontSize" },
|
|
fontWeight: "bold",
|
|
overflowStrategy: "hide"
|
|
},
|
|
highlight: applyMapPalette(MULTI_SERIES_HIGHLIGHT_STYLE)
|
|
},
|
|
tooltip: {
|
|
range: "exact"
|
|
}
|
|
},
|
|
create: (ctx) => new MapShapeSeries(ctx)
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/module-bundles/topology.ts
|
|
var AllMapSeriesModule = [
|
|
MapLineSeriesModule,
|
|
MapLineBackgroundSeriesModule,
|
|
MapMarkerSeriesModule,
|
|
MapShapeSeriesModule,
|
|
MapShapeBackgroundSeriesModule
|
|
];
|
|
|
|
// packages/ag-charts-enterprise/src/module-bundles/all.ts
|
|
var AllEnterpriseModule = [
|
|
AllCommunityModule,
|
|
AllCartesianModule2,
|
|
AllPolarModule2,
|
|
AllMapSeriesModule,
|
|
AllGaugeModule,
|
|
FinancialChartModule,
|
|
ChordSeriesModule,
|
|
PyramidSeriesModule,
|
|
SankeySeriesModule,
|
|
SunburstSeriesModule,
|
|
TreemapSeriesModule
|
|
].flat();
|
|
|
|
// packages/ag-charts-enterprise/src/features/image/image.ts
|
|
var Image3 = class extends BaseProperties {
|
|
constructor() {
|
|
super();
|
|
this.opacity = 1;
|
|
this.loadedSynchronously = true;
|
|
this.containerWidth = 0;
|
|
this.containerHeight = 0;
|
|
this.onLoad = void 0;
|
|
this.onImageLoad = () => {
|
|
if (this.loadedSynchronously) {
|
|
return;
|
|
}
|
|
this.node.visible = false;
|
|
this.performLayout(this.containerWidth, this.containerHeight);
|
|
this.onLoad?.();
|
|
};
|
|
this.imageElement = createElement("img");
|
|
this.imageElement.onload = this.onImageLoad;
|
|
this.node = new module_support_exports.Image(this.imageElement);
|
|
}
|
|
get complete() {
|
|
return this.imageElement.width > 0 && this.imageElement.height > 0;
|
|
}
|
|
performLayout(containerWidth, containerHeight) {
|
|
this.containerWidth = containerWidth;
|
|
this.containerHeight = containerHeight;
|
|
const container = { width: containerWidth, height: containerHeight };
|
|
const placement = calculatePlacement(this.imageElement.width, this.imageElement.height, container, this);
|
|
this.node.setProperties(
|
|
this.complete ? {
|
|
visible: true,
|
|
opacity: this.opacity,
|
|
...placement
|
|
} : { visible: false }
|
|
);
|
|
return placement;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Image3.prototype, "top", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Image3.prototype, "right", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Image3.prototype, "bottom", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Image3.prototype, "left", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Image3.prototype, "width", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Image3.prototype, "height", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty
|
|
], Image3.prototype, "opacity", 2);
|
|
__decorateClass([
|
|
ProxyProperty("imageElement.src"),
|
|
ObserveChanges((target) => target.loadedSynchronously = target.complete)
|
|
], Image3.prototype, "url", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/background/background.ts
|
|
var Background3 = class extends module_support_exports.Background {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.image = new Image3();
|
|
}
|
|
onLayoutComplete(event) {
|
|
super.onLayoutComplete(event);
|
|
if (this.image) {
|
|
const { width: width2, height: height2 } = event.chart;
|
|
this.image.performLayout(width2, height2);
|
|
}
|
|
}
|
|
onImageLoad() {
|
|
this.ctx.updateService.update(9 /* SCENE_RENDER */);
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ActionOnSet({
|
|
newValue(image) {
|
|
this.node.appendChild(image.node);
|
|
image.onLoad = () => this.onImageLoad();
|
|
},
|
|
oldValue(image) {
|
|
image.node.remove();
|
|
image.onLoad = void 0;
|
|
}
|
|
})
|
|
], Background3.prototype, "image", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/features/foreground/foreground.ts
|
|
var Foreground = class extends module_support_exports.Background {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.image = new Image3();
|
|
this.fill = "transparent";
|
|
this.fillOpacity = void 0;
|
|
}
|
|
createNode() {
|
|
return new module_support_exports.Group({ name: "foreground", zIndex: 18 /* FOREGROUND */ });
|
|
}
|
|
onLayoutComplete(event) {
|
|
super.onLayoutComplete(event);
|
|
const { width: width2, height: height2 } = event.chart;
|
|
const placement = this.image.performLayout(width2, height2);
|
|
if (this.text) {
|
|
this.updateTextNode(placement);
|
|
}
|
|
}
|
|
onImageLoad() {
|
|
this.ctx.updateService.update(9 /* SCENE_RENDER */);
|
|
}
|
|
updateTextNode(placement) {
|
|
const { textNode } = this;
|
|
textNode.fontWeight = "bold";
|
|
textNode.fontFamily = "Impact, sans-serif";
|
|
textNode.fontSize = 19;
|
|
textNode.opacity = 0.7;
|
|
textNode.fill = "#9b9b9b";
|
|
textNode.textBaseline = "top";
|
|
const { width: width2 } = textNode.getBBox();
|
|
const textPadding = 10;
|
|
textNode.x = placement.x + placement.width / 2 - width2 / 2;
|
|
textNode.y = placement.y + placement.height + textPadding;
|
|
}
|
|
};
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ActionOnSet({
|
|
newValue(image) {
|
|
this.node.appendChild(image.node);
|
|
image.onLoad = () => this.onImageLoad();
|
|
},
|
|
oldValue(image) {
|
|
image.node.remove();
|
|
image.onLoad = void 0;
|
|
}
|
|
})
|
|
], Foreground.prototype, "image", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("rectNode", "fill")
|
|
], Foreground.prototype, "fill", 2);
|
|
__decorateClass([
|
|
addFakeTransformToInstanceProperty,
|
|
ProxyPropertyOnWrite("rectNode", "fillOpacity")
|
|
], Foreground.prototype, "fillOpacity", 2);
|
|
|
|
// packages/ag-charts-enterprise/src/license/md5.ts
|
|
var MD5 = class {
|
|
constructor() {
|
|
this.ieCompatibility = false;
|
|
}
|
|
init() {
|
|
this.ieCompatibility = this.md5("hello") != "5d41402abc4b2a76b9719d911017c592";
|
|
}
|
|
md5cycle(x, k) {
|
|
let a = x[0], b = x[1], c = x[2], d = x[3];
|
|
a = this.ff(a, b, c, d, k[0], 7, -680876936);
|
|
d = this.ff(d, a, b, c, k[1], 12, -389564586);
|
|
c = this.ff(c, d, a, b, k[2], 17, 606105819);
|
|
b = this.ff(b, c, d, a, k[3], 22, -1044525330);
|
|
a = this.ff(a, b, c, d, k[4], 7, -176418897);
|
|
d = this.ff(d, a, b, c, k[5], 12, 1200080426);
|
|
c = this.ff(c, d, a, b, k[6], 17, -1473231341);
|
|
b = this.ff(b, c, d, a, k[7], 22, -45705983);
|
|
a = this.ff(a, b, c, d, k[8], 7, 1770035416);
|
|
d = this.ff(d, a, b, c, k[9], 12, -1958414417);
|
|
c = this.ff(c, d, a, b, k[10], 17, -42063);
|
|
b = this.ff(b, c, d, a, k[11], 22, -1990404162);
|
|
a = this.ff(a, b, c, d, k[12], 7, 1804603682);
|
|
d = this.ff(d, a, b, c, k[13], 12, -40341101);
|
|
c = this.ff(c, d, a, b, k[14], 17, -1502002290);
|
|
b = this.ff(b, c, d, a, k[15], 22, 1236535329);
|
|
a = this.gg(a, b, c, d, k[1], 5, -165796510);
|
|
d = this.gg(d, a, b, c, k[6], 9, -1069501632);
|
|
c = this.gg(c, d, a, b, k[11], 14, 643717713);
|
|
b = this.gg(b, c, d, a, k[0], 20, -373897302);
|
|
a = this.gg(a, b, c, d, k[5], 5, -701558691);
|
|
d = this.gg(d, a, b, c, k[10], 9, 38016083);
|
|
c = this.gg(c, d, a, b, k[15], 14, -660478335);
|
|
b = this.gg(b, c, d, a, k[4], 20, -405537848);
|
|
a = this.gg(a, b, c, d, k[9], 5, 568446438);
|
|
d = this.gg(d, a, b, c, k[14], 9, -1019803690);
|
|
c = this.gg(c, d, a, b, k[3], 14, -187363961);
|
|
b = this.gg(b, c, d, a, k[8], 20, 1163531501);
|
|
a = this.gg(a, b, c, d, k[13], 5, -1444681467);
|
|
d = this.gg(d, a, b, c, k[2], 9, -51403784);
|
|
c = this.gg(c, d, a, b, k[7], 14, 1735328473);
|
|
b = this.gg(b, c, d, a, k[12], 20, -1926607734);
|
|
a = this.hh(a, b, c, d, k[5], 4, -378558);
|
|
d = this.hh(d, a, b, c, k[8], 11, -2022574463);
|
|
c = this.hh(c, d, a, b, k[11], 16, 1839030562);
|
|
b = this.hh(b, c, d, a, k[14], 23, -35309556);
|
|
a = this.hh(a, b, c, d, k[1], 4, -1530992060);
|
|
d = this.hh(d, a, b, c, k[4], 11, 1272893353);
|
|
c = this.hh(c, d, a, b, k[7], 16, -155497632);
|
|
b = this.hh(b, c, d, a, k[10], 23, -1094730640);
|
|
a = this.hh(a, b, c, d, k[13], 4, 681279174);
|
|
d = this.hh(d, a, b, c, k[0], 11, -358537222);
|
|
c = this.hh(c, d, a, b, k[3], 16, -722521979);
|
|
b = this.hh(b, c, d, a, k[6], 23, 76029189);
|
|
a = this.hh(a, b, c, d, k[9], 4, -640364487);
|
|
d = this.hh(d, a, b, c, k[12], 11, -421815835);
|
|
c = this.hh(c, d, a, b, k[15], 16, 530742520);
|
|
b = this.hh(b, c, d, a, k[2], 23, -995338651);
|
|
a = this.ii(a, b, c, d, k[0], 6, -198630844);
|
|
d = this.ii(d, a, b, c, k[7], 10, 1126891415);
|
|
c = this.ii(c, d, a, b, k[14], 15, -1416354905);
|
|
b = this.ii(b, c, d, a, k[5], 21, -57434055);
|
|
a = this.ii(a, b, c, d, k[12], 6, 1700485571);
|
|
d = this.ii(d, a, b, c, k[3], 10, -1894986606);
|
|
c = this.ii(c, d, a, b, k[10], 15, -1051523);
|
|
b = this.ii(b, c, d, a, k[1], 21, -2054922799);
|
|
a = this.ii(a, b, c, d, k[8], 6, 1873313359);
|
|
d = this.ii(d, a, b, c, k[15], 10, -30611744);
|
|
c = this.ii(c, d, a, b, k[6], 15, -1560198380);
|
|
b = this.ii(b, c, d, a, k[13], 21, 1309151649);
|
|
a = this.ii(a, b, c, d, k[4], 6, -145523070);
|
|
d = this.ii(d, a, b, c, k[11], 10, -1120210379);
|
|
c = this.ii(c, d, a, b, k[2], 15, 718787259);
|
|
b = this.ii(b, c, d, a, k[9], 21, -343485551);
|
|
x[0] = this.add32(a, x[0]);
|
|
x[1] = this.add32(b, x[1]);
|
|
x[2] = this.add32(c, x[2]);
|
|
x[3] = this.add32(d, x[3]);
|
|
}
|
|
cmn(q, a, b, x, s, t) {
|
|
a = this.add32(this.add32(a, q), this.add32(x, t));
|
|
return this.add32(a << s | a >>> 32 - s, b);
|
|
}
|
|
ff(a, b, c, d, x, s, t) {
|
|
return this.cmn(b & c | ~b & d, a, b, x, s, t);
|
|
}
|
|
gg(a, b, c, d, x, s, t) {
|
|
return this.cmn(b & d | c & ~d, a, b, x, s, t);
|
|
}
|
|
hh(a, b, c, d, x, s, t) {
|
|
return this.cmn(b ^ c ^ d, a, b, x, s, t);
|
|
}
|
|
ii(a, b, c, d, x, s, t) {
|
|
return this.cmn(c ^ (b | ~d), a, b, x, s, t);
|
|
}
|
|
md51(s) {
|
|
const n = s.length;
|
|
const state = [1732584193, -271733879, -1732584194, 271733878];
|
|
let i;
|
|
for (i = 64; i <= s.length; i += 64) {
|
|
this.md5cycle(state, this.md5blk(s.substring(i - 64, i)));
|
|
}
|
|
s = s.substring(i - 64);
|
|
const tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for (i = 0; i < s.length; i++) {
|
|
tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3);
|
|
}
|
|
tail[i >> 2] |= 128 << (i % 4 << 3);
|
|
if (i > 55) {
|
|
this.md5cycle(state, tail);
|
|
for (i = 0; i < 16; i++) {
|
|
tail[i] = 0;
|
|
}
|
|
}
|
|
tail[14] = n * 8;
|
|
this.md5cycle(state, tail);
|
|
return state;
|
|
}
|
|
/* there needs to be support for Unicode here, * unless we pretend that we can redefine the MD-5
|
|
* algorithm for multi-byte characters (perhaps by adding every four 16-bit characters and
|
|
* shortening the sum to 32 bits). Otherwise I suggest performing MD-5 as if every character
|
|
* was two bytes--e.g., 0040 0025 = @%--but then how will an ordinary MD-5 sum be matched?
|
|
* There is no way to standardize text to something like UTF-8 before transformation; speed cost is
|
|
* utterly prohibitive. The JavaScript standard itself needs to look at this: it should start
|
|
* providing access to strings as preformed UTF-8 8-bit unsigned value arrays.
|
|
*/
|
|
md5blk(s) {
|
|
const md5blks = [];
|
|
for (let i = 0; i < 64; i += 4) {
|
|
md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
|
|
}
|
|
return md5blks;
|
|
}
|
|
rhex(n) {
|
|
const hex_chr = "0123456789abcdef".split("");
|
|
let s = "", j = 0;
|
|
for (; j < 4; j++) {
|
|
s += hex_chr[n >> j * 8 + 4 & 15] + hex_chr[n >> j * 8 & 15];
|
|
}
|
|
return s;
|
|
}
|
|
hex(x) {
|
|
for (let i = 0; i < x.length; i++) {
|
|
x[i] = this.rhex(x[i]);
|
|
}
|
|
return x.join("");
|
|
}
|
|
md5(s) {
|
|
return this.hex(this.md51(s));
|
|
}
|
|
add32(a, b) {
|
|
return this.ieCompatibility ? this.add32Compat(a, b) : this.add32Std(a, b);
|
|
}
|
|
/* this function is much faster, so if possible we use it. Some IEs are the only ones I know of that
|
|
need the idiotic second function, generated by an if clause. */
|
|
add32Std(a, b) {
|
|
return a + b & 4294967295;
|
|
}
|
|
add32Compat(x, y) {
|
|
const lsw = (x & 65535) + (y & 65535), msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
|
return msw << 16 | lsw & 65535;
|
|
}
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/license/licenseManager.ts
|
|
function missingOrEmpty(value) {
|
|
return value == null || value.length === 0;
|
|
}
|
|
var WATERMARK_SVG_DATA_URL = `data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjU4IiBoZWlnaHQ9IjQwIiB2aWV3Qm94PSIwIDAgMjU4IDQwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjUuNzc5IDI4LjY1N0gxMy4zNTlMMTEuMTczIDM0LjAxMkg1LjY3Mjk3TDE3LjE4MiA3LjA1OTk5SDIxLjk1M0wzMy40NjIgMzQuMDEySDI3Ljk2MkwyNS43NzYgMjguNjU3SDI1Ljc3OVpNMjQuMDY4IDI0LjM5N0wxOS41ODggMTMuNDM0TDE1LjEwNyAyNC4zOTdIMjQuMDY4Wk02Mi4wOTIgMTguODIzSDQ5LjgxN1YyMy4wODZINTYuNzc1QzU2LjU1NSAyNS4yMjIgNTUuNzU1IDI2LjkyNyA1NC4zNzIgMjguMjAyQzUyLjk4OSAyOS40NzYgNTEuMTY2IDMwLjExNSA0OC45MDkgMzAuMTE1QzQ3LjYyMiAzMC4xMTUgNDYuNDUgMjkuODg1IDQ1LjM5MyAyOS40MjNDNDQuMzU4MyAyOC45NzgxIDQzLjQzMjYgMjguMzEzOCA0Mi42OCAyNy40NzZDNDEuOTI3IDI2LjYzOSA0MS4zNDQgMjUuNjMxIDQwLjkzMSAyNC40NTNDNDAuNTE5IDIzLjI3NSA0MC4zMTEgMjEuOTcgNDAuMzExIDIwLjUzN0M0MC4zMTEgMTkuMTA1IDQwLjUxNiAxNy44IDQwLjkzMSAxNi42MjFDNDEuMzQ0IDE1LjQ0MyA0MS45MjcgMTQuNDM2IDQyLjY4IDEzLjU5OEM0My40Mzc2IDEyLjc1NzcgNDQuMzY5NiAxMi4wOTMyIDQ1LjQxMSAxMS42NTFDNDYuNDc4IDExLjE4OSA0Ny42NTYgMTAuOTYgNDguOTQ2IDEwLjk2QzUxLjYxMiAxMC45NiA1My42MzcgMTEuNjAyIDU1LjAyIDEyLjg4NUw1OC4zIDkuNjA0OTlDNTUuODE3IDcuNjY5OTkgNTIuNjc2IDYuNjk5OTkgNDguODcyIDYuNjk5OTlDNDYuNzYgNi42OTk5OSA0NC44NTMgNy4wMzQ5OSA0My4xNTQgNy43MDA5OUM0MS40NTUgOC4zNjc5OSAzOS45OTggOS4zMDM5OSAzOC43ODMgMTAuNTA0QzM3LjU2NyAxMS43MDcgMzYuNjM0IDEzLjE1OCAzNS45NzcgMTQuODU3QzM1LjMxOSAxNi41NTYgMzQuOTk0IDE4LjQ1MSAzNC45OTQgMjAuNTRDMzQuOTk0IDIyLjYzIDM1LjMyOSAyNC40OTQgMzUuOTk1IDI2LjIwNUMzNi42NjIgMjcuOTE2IDM3LjYwNSAyOS4zNzQgMzguODE3IDMwLjU3N0M0MC4wMzIgMzEuNzggNDEuNDg2IDMyLjcxMyA0My4xODggMzMuMzgzQzQ0Ljg4OCAzNC4wNDkgNDYuNzgyIDM0LjM4NCA0OC44NzIgMzQuMzg0QzUwLjk2MSAzNC4zODQgNTIuNzUgMzQuMDQ5IDU0LjM5IDMzLjM4M0M1Ni4wMzEgMzIuNzE2IDU3LjQyNiAzMS43OCA1OC41NzkgMzAuNTc3QzU5LjczMyAyOS4zNzQgNjAuNjE5IDI3LjkxNiA2MS4yMzkgMjYuMjA1QzYxLjg2IDI0LjQ5NCA2Mi4xNyAyMi42MDUgNjIuMTcgMjAuNTRDNjIuMTY5NiAxOS45Njg4IDYyLjE0NDUgMTkuMzk4IDYyLjA5NSAxOC44MjlMNjIuMDkyIDE4LjgyM1pNMTUxLjgxIDE2Ljk4MUMxNTMuNDEgMTQuNjA5IDE1Ny40MTkgMTQuMzU4IDE1OS4wMjIgMTQuMzU4VjE4LjkxQzE1Ni45NTcgMTguOTEgMTU0Ljk4NSAxOC45OTYgMTUzLjc1NyAxOS44OTJDMTUyLjUyOSAyMC43OTIgMTUxLjkxOSAyMS45ODIgMTUxLjkxOSAyMy40NjRWMzMuOTlIMTQ2Ljk2NFYxNC4zNThIMTUxLjczNkwxNTEuODEgMTYuOTgxWk0xNDMuMDExIDE0LjM2MVYzNC4wMzFIMTM4LjI0TDEzOC4xMzEgMzEuMDQ1QzEzNy40NjYgMzIuMDc2IDEzNi41NTEgMzIuOTIxOSAxMzUuNDcxIDMzLjUwNEMxMzQuMzc2IDM0LjA5OSAxMzMuMDY4IDM0LjM5NiAxMzEuNTM2IDM0LjM5NkMxMzAuMiAzNC4zOTYgMTI4Ljk2MyAzNC4xNTIgMTI3LjgyMiAzMy42NjhDMTI2LjcgMzMuMTk2NCAxMjUuNjg5IDMyLjQ5NSAxMjQuODU1IDMxLjYwOUMxMjQuMDE4IDMwLjcyMiAxMjMuMzU0IDI5LjY2MiAxMjIuODcxIDI4LjQyMkMxMjIuMzg0IDI3LjE4NSAxMjIuMTQyIDI1LjgxMSAxMjIuMTQyIDI0LjMwNEMxMjIuMTQyIDIyLjc5OCAxMjIuMzg0IDIxLjM3OCAxMjIuODcxIDIwLjExNkMxMjMuMzU3IDE4Ljg1NCAxMjQuMDE4IDE3Ljc3MiAxMjQuODU1IDE2Ljg3M0MxMjUuNjg4IDE1Ljk3NjQgMTI2LjY5OCAxNS4yNjM2IDEyNy44MjIgMTQuNzhDMTI4Ljk2MyAxNC4yODEgMTMwLjIwMyAxNC4wMzMgMTMxLjUzNiAxNC4wMzNDMTMzLjA0MyAxNC4wMzMgMTM0LjMzIDE0LjMxOCAxMzUuMzk3IDE0Ljg4OEMxMzYuNDYyIDE1LjQ1ODkgMTM3LjM3NSAxNi4yNzggMTM4LjA1NyAxNy4yNzZWMTQuMzYxSDE0My4wMTFaTTEzMi42MzEgMzAuMTMzQzEzNC4yNTYgMzAuMTMzIDEzNS41NjcgMjkuNTk0IDEzNi41NjUgMjguNTEyQzEzNy41NjEgMjcuNDMgMTM4LjA2IDI1Ljk5MSAxMzguMDYgMjQuMTk2QzEzOC4wNiAyMi40MDEgMTM3LjU2MSAyMC45OSAxMzYuNTY1IDE5Ljg5OUMxMzUuNTcgMTguODA3IDEzNC4yNTkgMTguMjU4IDEzMi42MzEgMTguMjU4QzEzMS4wMDMgMTguMjU4IDEyOS43MjkgMTguODA0IDEyOC43MzQgMTkuODk5QzEyNy43MzggMjAuOTkzIDEyNy4yMzkgMjIuNDM4IDEyNy4yMzkgMjQuMjMzQzEyNy4yMzkgMjYuMDI4IDEyNy43MzUgMjcuNDMzIDEyOC43MzQgMjguNTE1QzEyOS43MjkgMjkuNTk0IDEzMS4wMjggMzAuMTM2IDEzMi42MzEgMzAuMTM2VjMwLjEzM1pNOTMuNjk4IDI3Ljg3NkM5My41Nzk1IDI4LjAwMjUgOTMuNDU2NCAyOC4xMjQ2IDkzLjMyOSAyOC4yNDJDOTEuOTQ3IDI5LjUxNiA5MC4xMjMgMzAuMTU1IDg3Ljg2NiAzMC4xNTVDODYuNTggMzAuMTU1IDg1LjQwOCAyOS45MjYgODQuMzUgMjkuNDY0QzgzLjMxNTUgMjkuMDE4OSA4Mi4zODk4IDI4LjM1NDYgODEuNjM3IDI3LjUxN0M4MC44ODQgMjYuNjc5IDgwLjMwMSAyNS42NzIgNzkuODg5IDI0LjQ5NEM3OS40NzYgMjMuMzE1IDc5LjI2OSAyMi4wMSA3OS4yNjkgMjAuNTc4Qzc5LjI2OSAxOS4xNDUgNzkuNDczIDE3Ljg0IDc5Ljg4OSAxNi42NjJDODAuMzAxIDE1LjQ4NCA4MC44ODQgMTQuNDc2IDgxLjYzNyAxMy42MzlDODIuMzk0OSAxMi43OTg3IDgzLjMyNzMgMTIuMTM0MiA4NC4zNjkgMTEuNjkyQzg1LjQzNiAxMS4yMyA4Ni42MTQgMTEgODcuOTAzIDExQzkwLjU3IDExIDkyLjU5NSAxMS42NDIgOTMuOTc3IDEyLjkyNkw5Ny4yNTggOS42NDQ5OUM5NC43NzQgNy43MTA5OSA5MS42MzMgNi43Mzk5OSA4Ny44MjkgNi43Mzk5OUM4NS43MTggNi43Mzk5OSA4My44MTEgNy4wNzQ5OSA4Mi4xMTIgNy43NDE5OUM4MC40MTMgOC40MDc5OSA3OC45NTYgOS4zNDQ5OSA3Ny43NCAxMC41NDVDNzYuNTI1IDExLjc0NyA3NS41OTIgMTMuMTk5IDc0LjkzNCAxNC44OThDNzQuMjc3IDE2LjU5NyA3My45NTEgMTguNDkxIDczLjk1MSAyMC41ODFDNzMuOTUxIDIyLjY3IDc0LjI4NiAyNC41MzQgNzQuOTUzIDI2LjI0NUM3NS42MTkgMjcuOTU3IDc2LjU2MiAyOS40MTQgNzcuNzc0IDMwLjYxN0M3OC45OSAzMS44MiA4MC40NDQgMzIuNzUzIDgyLjE0NiAzMy40MjNDODMuODQ1IDM0LjA5IDg1LjczOSAzNC40MjQgODcuODI5IDM0LjQyNEM4OS45MTkgMzQuNDI0IDkxLjcwOCAzNC4wOSA5My4zNDggMzMuNDIzQzk0LjcxOCAzMi44NjUgOTUuOTE4IDMyLjEyMSA5Ni45NDggMzEuMTkxQzk3LjE0OSAzMS4wMDggOTcuMzQ4IDMwLjgxNSA5Ny41MzcgMzAuNjJMOTMuNzAxIDI3Ljg4NUw5My42OTggMjcuODc2Wk0xMTAuODAyIDE0LjAxNUMxMDkuMTk5IDE0LjAxNSAxMDYuODM2IDE0LjQ3MSAxMDUuNjExIDE2LjE1OEwxMDUuNTM3IDYuMDE1OTlIMTAwLjc2NVYzMy45MzlIMTA1LjcyVjIyLjY0MUMxMDUuNzcxIDIxLjQ2MDcgMTA2LjI4OCAyMC4zNDg4IDEwNy4xNTcgMTkuNTQ4OUMxMDguMDI3IDE4Ljc0OTEgMTA5LjE3OCAxOC4zMjY2IDExMC4zNTggMTguMzc0QzExMy4zOTcgMTguMzc0IDExNC4yNjggMjEuMTU5IDExNC4yNjggMjIuNjQxVjMzLjkzOUgxMTkuMjIzVjIxLjA1OUMxMTkuMjIzIDIxLjA1OSAxMTkuMTQyIDE0LjAxNSAxMTAuODAyIDE0LjAxNVpNMTczLjc2MyAxNC4zNThIMTY5Ljk5OVY4LjcxNDk5SDE2NS4wNDhWMTQuMzU4SDE2MS4yODRWMTguOTE2SDE2NS4wNDhWMzQuMDAzSDE2OS45OTlWMTguOTE2SDE3My43NjNWMTQuMzU4Wk0xOTAuNzg3IDI1LjI2MkMxOTAuMTI5IDI0LjUwMTQgMTg5LjMwNyAyMy44OTk0IDE4OC4zODQgMjMuNTAxQzE4Ny40ODggMjMuMTE3IDE4Ni4zMzEgMjIuNzMyIDE4NC45NDggMjIuMzY0QzE4NC4xNjUgMjIuMTQzOSAxODMuMzkgMjEuODk3OCAxODIuNjIzIDIxLjYyNkMxODIuMTYzIDIxLjQ2MjEgMTgxLjc0MSAyMS4yMDY2IDE4MS4zODMgMjAuODc1QzE4MS4yMzUgMjAuNzQyMSAxODEuMTE4IDIwLjU3ODkgMTgxLjAzOSAyMC4zOTY0QzE4MC45NjEgMjAuMjE0IDE4MC45MjIgMjAuMDE2NiAxODAuOTI3IDE5LjgxOEMxODAuOTI3IDE5LjI3MiAxODEuMTU2IDE4Ljg0NCAxODEuNjI1IDE4LjUxQzE4Mi4xMjEgMTguMTU2IDE4Mi44NjIgMTcuOTc2IDE4My44MjYgMTcuOTc2QzE4NC43OSAxNy45NzYgMTg1LjU4NyAxOC4yMDkgMTg2LjE0OCAxOC42NjhDMTg2LjcwNiAxOS4xMjQgMTg3LjAwNyAxOS43MjUgMTg3LjA3MiAyMC41TDE4Ny4wOTQgMjAuNzgySDE5MS42MzNMMTkxLjYxNyAyMC40NkMxOTEuNTIxIDE4LjQ4NSAxOTAuNzcxIDE2LjkgMTg5LjM4NSAxNS43NUMxODguMDEyIDE0LjYxMiAxODYuMTg1IDE0LjAzMyAxODMuOTYyIDE0LjAzM0MxODIuNDc3IDE0LjAzMyAxODEuMTQxIDE0LjI4NyAxNzkuOTk0IDE0Ljc4NkMxNzguODMxIDE1LjI5MSAxNzcuOTI2IDE1Ljk5NSAxNzcuMjk2IDE2Ljg4MkMxNzYuNjczIDE3Ljc0NTUgMTc2LjMzOCAxOC43ODQgMTc2LjM0MSAxOS44NDlDMTc2LjM0MSAyMS4xNjcgMTc2LjY5OCAyMi4yNDkgMTc3LjM5OSAyMy4wNjRDMTc4LjA2IDIzLjg0MzIgMTc4Ljg5OCAyNC40NTM0IDE3OS44NDIgMjQuODQ0QzE4MC43NDQgMjUuMjE2IDE4MS45MjggMjUuNjA3IDE4My4zNjEgMjZDMTg0LjgwNiAyNi40MSAxODUuODcyIDI2Ljc4NSAxODYuNTMgMjcuMTIzQzE4Ny4xIDI3LjQxNCAxODcuMzc5IDI3Ljg0NSAxODcuMzc5IDI4LjQ0NEMxODcuMzc5IDI5LjA0MiAxODcuMTIyIDI5LjQ2NyAxODYuNTk1IDI5LjgzOUMxODYuMDQzIDMwLjIyNiAxODUuMjM3IDMwLjQyNSAxODQuMjAxIDMwLjQyNUMxODMuMTY2IDMwLjQyNSAxODIuMzk0IDMwLjE3NCAxODEuNzQ5IDI5LjY3NEMxODEuMTEzIDI5LjE4MSAxODAuNzcyIDI4LjU4OSAxODAuNzEgMjcuODY0TDE4MC42ODUgMjcuNTgySDE3Ni4wMTNMMTc2LjAyNSAyNy45MDFDMTc2LjA2NyAyOS4wOTU1IDE3Ni40NzIgMzAuMjQ4NyAxNzcuMTg4IDMxLjIwNkMxNzcuOTA3IDMyLjE4IDE3OC44OTMgMzIuOTU4IDE4MC4xMTggMzMuNTE5QzE4MS4zMzYgMzQuMDc3IDE4Mi43MzIgMzQuMzYyIDE4NC4yNjYgMzQuMzYyQzE4NS44MDEgMzQuMzYyIDE4Ny4xMDkgMzQuMTA4IDE4OC4yMzggMzMuNjA5QzE4OS4zNzYgMzMuMTA0IDE5MC4yNzIgMzIuMzk0IDE5MC45MDEgMzEuNDk0QzE5MS41MzQgMzAuNTkyIDE5MS44NTMgMjkuNTU0IDE5MS44NTMgMjguNDAzQzE5MS44MjggMjcuMTEgMTkxLjQ2NiAyNi4wNTMgMTkwLjc3NyAyNS4yNjJIMTkwLjc4N1oiIGZpbGw9IiM5QjlCOUIiLz4KPHBhdGggZD0iTTI0MS45ODIgMjUuNjU4MlYxNy43MTE3SDIyOC40NDFMMjIwLjQ5NCAyNS42NTgySDI0MS45ODJaIiBmaWxsPSIjOUI5QjlCIi8+CjxwYXRoIGQ9Ik0yNTcuMjM5IDUuOTUwODFIMjQwLjI2NUwyMzIuMjU1IDEzLjg5NzNIMjU3LjIzOVY1Ljk1MDgxWiIgZmlsbD0iIzlCOUI5QiIvPgo8cGF0aCBkPSJNMjEyLjYxMSAzMy42MDQ4TDIxNi42OCAyOS41MzYxSDIzMC40MTJWMzcuNDgyN0gyMTIuNjExVjMzLjYwNDhaIiBmaWxsPSIjOUI5QjlCIi8+CjxwYXRoIGQ9Ik0yMTUuNTk5IDIxLjc4MDNIMjI0LjM3MkwyMzIuMzgyIDEzLjgzMzdIMjE1LjU5OVYyMS43ODAzWiIgZmlsbD0iIzlCOUI5QiIvPgo8cGF0aCBkPSJNMjA2IDMzLjYwNDdIMjEyLjYxMUwyMjAuNDk0IDI1LjY1ODJIMjA2VjMzLjYwNDdaIiBmaWxsPSIjOUI5QjlCIi8+CjxwYXRoIGQ9Ik0yNDAuMjY1IDUuOTUwODFMMjM2LjE5NyAxMC4wMTk0SDIxMC4yNTlWMi4wNzI4OEgyNDAuMjY1VjUuOTUwODFaIiBmaWxsPSIjOUI5QjlCIi8+Cjwvc3ZnPgo=`;
|
|
var LICENSE_TYPES = {
|
|
"01": "GRID",
|
|
"02": "CHARTS",
|
|
"0102": "BOTH"
|
|
};
|
|
var LICENSING_HELP_URL = "https://www.ag-grid.com/charts/licensing/";
|
|
var _LicenseManager = class _LicenseManager {
|
|
constructor(document2) {
|
|
this.watermarkMessage = void 0;
|
|
this.totalMessageLength = 124;
|
|
this.document = document2;
|
|
this.md5 = new MD5();
|
|
this.md5.init();
|
|
}
|
|
validateLicense() {
|
|
const licenseDetails = this.getLicenseDetails(_LicenseManager.licenseKey, _LicenseManager.gridContext);
|
|
const currentLicenseName = `AG ${licenseDetails.currentLicenseType === "BOTH" ? "Grid and " : ""}Charts Enterprise`;
|
|
let suppliedLicenseName = "";
|
|
if (licenseDetails.suppliedLicenseType === "BOTH") {
|
|
suppliedLicenseName = "AG Grid and AG Charts Enterprise";
|
|
} else if (licenseDetails.suppliedLicenseType === "GRID") {
|
|
suppliedLicenseName = "AG Grid Enterprise";
|
|
} else if (licenseDetails.suppliedLicenseType !== void 0) {
|
|
suppliedLicenseName = "AG Charts Enterprise";
|
|
}
|
|
if (licenseDetails.missing) {
|
|
if (!this.isWebsiteUrl() || this.isForceWatermark()) {
|
|
this.outputMissingLicenseKey(currentLicenseName);
|
|
}
|
|
} else if (licenseDetails.expired) {
|
|
const gridReleaseDate = _LicenseManager.getChartsReleaseDate();
|
|
const formattedReleaseDate = _LicenseManager.formatDate(gridReleaseDate);
|
|
this.outputExpiredKey(licenseDetails.expiry, formattedReleaseDate, suppliedLicenseName);
|
|
} else if (!licenseDetails.valid) {
|
|
this.outputInvalidLicenseKey(
|
|
!!licenseDetails.incorrectLicenseType,
|
|
currentLicenseName,
|
|
suppliedLicenseName
|
|
);
|
|
} else if (licenseDetails.isTrial && licenseDetails.trialExpired) {
|
|
this.outputExpiredTrialKey(licenseDetails.expiry, currentLicenseName, suppliedLicenseName);
|
|
}
|
|
}
|
|
static extractExpiry(license) {
|
|
const restrictionHashed = license.substring(license.lastIndexOf("_") + 1, license.length);
|
|
return new Date(Number.parseInt(_LicenseManager.decode(restrictionHashed), 10));
|
|
}
|
|
static extractLicenseComponents(licenseKey) {
|
|
let cleanedLicenseKey = licenseKey.replaceAll(/[\u200B-\u200D\uFEFF]/g, "");
|
|
cleanedLicenseKey = cleanedLicenseKey.replaceAll(/\r?\n|\r/g, "");
|
|
if (licenseKey.length <= 32) {
|
|
return { md5: null, license: licenseKey, version: null, isTrial: null };
|
|
}
|
|
const hashStart = cleanedLicenseKey.length - 32;
|
|
const md5 = cleanedLicenseKey.substring(hashStart);
|
|
const license = cleanedLicenseKey.substring(0, hashStart);
|
|
const [version, isTrial, type] = _LicenseManager.extractBracketedInformation(cleanedLicenseKey);
|
|
return { md5, license, version, isTrial, type };
|
|
}
|
|
getLicenseDetails(licenseKey, gridContext = false) {
|
|
const currentLicenseType = "CHARTS";
|
|
if (missingOrEmpty(licenseKey)) {
|
|
return {
|
|
licenseKey,
|
|
valid: false,
|
|
missing: true,
|
|
currentLicenseType
|
|
};
|
|
}
|
|
const chartsReleaseDate = _LicenseManager.getChartsReleaseDate();
|
|
const { md5, license, version, isTrial, type } = _LicenseManager.extractLicenseComponents(licenseKey);
|
|
let valid = md5 === this.md5.md5(license) && !licenseKey.includes("For_Trialing_ag-Grid_Only");
|
|
let trialExpired = void 0;
|
|
let expired = void 0;
|
|
let expiry = null;
|
|
let incorrectLicenseType = false;
|
|
let suppliedLicenseType = void 0;
|
|
function handleTrial() {
|
|
const now = /* @__PURE__ */ new Date();
|
|
trialExpired = expiry < now;
|
|
expired = void 0;
|
|
}
|
|
if (valid) {
|
|
expiry = _LicenseManager.extractExpiry(license);
|
|
valid = !Number.isNaN(expiry.getTime());
|
|
if (valid) {
|
|
expired = chartsReleaseDate > expiry;
|
|
switch (version) {
|
|
case "legacy":
|
|
case "2": {
|
|
valid = false;
|
|
break;
|
|
}
|
|
case "3": {
|
|
if (missingOrEmpty(type)) {
|
|
valid = false;
|
|
} else {
|
|
suppliedLicenseType = type;
|
|
if (type !== LICENSE_TYPES["02"] && type !== LICENSE_TYPES["0102"]) {
|
|
valid = false;
|
|
incorrectLicenseType = true;
|
|
} else if (isTrial) {
|
|
handleTrial();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!valid) {
|
|
return {
|
|
licenseKey,
|
|
valid,
|
|
incorrectLicenseType,
|
|
currentLicenseType,
|
|
suppliedLicenseType
|
|
};
|
|
}
|
|
return {
|
|
licenseKey,
|
|
valid,
|
|
expiry: _LicenseManager.formatDate(expiry),
|
|
expired,
|
|
version,
|
|
isTrial,
|
|
trialExpired,
|
|
invalidLicenseTypeForCombo: gridContext ? suppliedLicenseType !== "BOTH" : void 0,
|
|
incorrectLicenseType,
|
|
currentLicenseType,
|
|
suppliedLicenseType
|
|
};
|
|
}
|
|
isDisplayWatermark() {
|
|
return this.isForceWatermark() || !this.isLocalhost() && !this.isE2ETest() && !this.isWebsiteUrl() && !missingOrEmpty(this.watermarkMessage);
|
|
}
|
|
getWatermarkMessage() {
|
|
return this.watermarkMessage ?? "";
|
|
}
|
|
getWatermarkForegroundConfig() {
|
|
const message = this.getWatermarkMessage();
|
|
if (!message) {
|
|
return void 0;
|
|
}
|
|
return this.buildWatermarkConfig(message);
|
|
}
|
|
getWatermarkForegroundConfigForBrowser() {
|
|
if (!this.isDisplayWatermark()) {
|
|
return void 0;
|
|
}
|
|
const message = this.getWatermarkMessage();
|
|
if (!message) {
|
|
return void 0;
|
|
}
|
|
return this.buildWatermarkConfig(message);
|
|
}
|
|
buildWatermarkConfig(text2) {
|
|
return {
|
|
text: text2,
|
|
image: {
|
|
url: WATERMARK_SVG_DATA_URL,
|
|
width: 170,
|
|
height: 25,
|
|
right: 25,
|
|
bottom: 50,
|
|
opacity: 0.7
|
|
}
|
|
};
|
|
}
|
|
getHostname() {
|
|
if (!this.document) {
|
|
return "localhost";
|
|
}
|
|
const win = this.document.defaultView ?? globalThis;
|
|
if (!win) {
|
|
return "localhost";
|
|
}
|
|
try {
|
|
const hostname = win.location?.hostname ?? "";
|
|
return hostname || "localhost";
|
|
} catch {
|
|
return "localhost";
|
|
}
|
|
}
|
|
isForceWatermark() {
|
|
if (!this.document) {
|
|
return false;
|
|
}
|
|
const win = this.document?.defaultView ?? globalThis.window != void 0 ? globalThis : void 0;
|
|
if (!win) {
|
|
return false;
|
|
}
|
|
const pathname = win.location?.pathname;
|
|
return pathname ? pathname.includes("forceWatermark") : false;
|
|
}
|
|
isWebsiteUrl() {
|
|
const hostname = this.getHostname();
|
|
return /^((?:[\w-]+\.)?ag-grid\.com)$/.exec(hostname) !== null;
|
|
}
|
|
isLocalhost() {
|
|
const hostname = this.getHostname();
|
|
return /^(?:127\.0\.0\.1|localhost)$/.exec(hostname) !== null;
|
|
}
|
|
isE2ETest() {
|
|
const hostname = this.getHostname();
|
|
return /^(?:172\.17\.0\.1|host\.docker\.internal)$/.exec(hostname) !== null;
|
|
}
|
|
static formatDate(date2) {
|
|
const monthNames = [
|
|
"January",
|
|
"February",
|
|
"March",
|
|
"April",
|
|
"May",
|
|
"June",
|
|
"July",
|
|
"August",
|
|
"September",
|
|
"October",
|
|
"November",
|
|
"December"
|
|
];
|
|
const day = date2.getDate();
|
|
const monthIndex = date2.getMonth();
|
|
const year = date2.getFullYear();
|
|
return day + " " + monthNames[monthIndex] + " " + year;
|
|
}
|
|
static getChartsReleaseDate() {
|
|
return new Date(Number.parseInt(_LicenseManager.decode(_LicenseManager.RELEASE_INFORMATION), 10));
|
|
}
|
|
static decode(input) {
|
|
const keystr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
let t = "";
|
|
let n, r, i;
|
|
let s, o, u, a;
|
|
let f = 0;
|
|
const e = input.replaceAll(/[^A-Za-z0-9+/=]/g, "");
|
|
while (f < e.length) {
|
|
s = keystr.indexOf(e.charAt(f++));
|
|
o = keystr.indexOf(e.charAt(f++));
|
|
u = keystr.indexOf(e.charAt(f++));
|
|
a = keystr.indexOf(e.charAt(f++));
|
|
n = s << 2 | o >> 4;
|
|
r = (o & 15) << 4 | u >> 2;
|
|
i = (u & 3) << 6 | a;
|
|
t = t + String.fromCodePoint(n);
|
|
if (u != 64) {
|
|
t = t + String.fromCodePoint(r);
|
|
}
|
|
if (a != 64) {
|
|
t = t + String.fromCodePoint(i);
|
|
}
|
|
}
|
|
t = _LicenseManager.utf8_decode(t);
|
|
return t;
|
|
}
|
|
static utf8_decode(input) {
|
|
input = input.replaceAll("rn", "n");
|
|
let t = "";
|
|
for (let n = 0; n < input.length; n++) {
|
|
const r = input.codePointAt(n);
|
|
if (r < 128) {
|
|
t += String.fromCodePoint(r);
|
|
} else if (r > 127 && r < 2048) {
|
|
t += String.fromCodePoint(r >> 6 | 192);
|
|
t += String.fromCodePoint(r & 63 | 128);
|
|
} else {
|
|
t += String.fromCodePoint(r >> 12 | 224);
|
|
t += String.fromCodePoint(r >> 6 & 63 | 128);
|
|
t += String.fromCodePoint(r & 63 | 128);
|
|
}
|
|
}
|
|
return t;
|
|
}
|
|
static setGridContext(gridContext = false) {
|
|
_LicenseManager.gridContext = gridContext;
|
|
}
|
|
static setLicenseKey(licenseKey) {
|
|
if (this.licenseKey && this.licenseKey !== licenseKey) {
|
|
console.warn(
|
|
`License Key being set multiple times with different values. This can result in an incorrect license key being used.`
|
|
);
|
|
}
|
|
_LicenseManager.licenseKey = licenseKey;
|
|
}
|
|
static extractBracketedInformation(licenseKey) {
|
|
if (!licenseKey.includes("[")) {
|
|
return ["legacy", false, void 0];
|
|
}
|
|
const matches = licenseKey.match(/\[(.*?)\]/g).map((match) => match.replace("[", "").replace("]", ""));
|
|
if (!matches || matches.length === 0) {
|
|
return ["legacy", false, void 0];
|
|
}
|
|
const isTrial = matches.filter((match) => match === "TRIAL").length === 1;
|
|
const rawVersion = matches.find((match) => match.startsWith("v"));
|
|
const version = rawVersion ? rawVersion.replace("v", "") : "legacy";
|
|
const type = LICENSE_TYPES[matches.find((match) => LICENSE_TYPES[match])];
|
|
return [version, isTrial, type];
|
|
}
|
|
centerPadAndOutput(input) {
|
|
const paddingRequired = this.totalMessageLength - input.length;
|
|
console.error(input.padStart(paddingRequired / 2 + input.length, "*").padEnd(this.totalMessageLength, "*"));
|
|
}
|
|
padAndOutput(input, padding2 = "*", terminateWithPadding = "") {
|
|
console.error(
|
|
input.padEnd(this.totalMessageLength - terminateWithPadding.length, padding2) + terminateWithPadding
|
|
);
|
|
}
|
|
outputInvalidLicenseKey(incorrectLicenseType, currentLicenseName, suppliedLicenseName) {
|
|
if (!_LicenseManager.gridContext) {
|
|
if (incorrectLicenseType) {
|
|
this.centerPadAndOutput("");
|
|
this.centerPadAndOutput(` ${currentLicenseName} License `);
|
|
this.centerPadAndOutput(" Incompatible License Key ");
|
|
this.padAndOutput(
|
|
`* Your license key is for ${suppliedLicenseName} only and does not cover you for ${currentLicenseName}.`,
|
|
" ",
|
|
"*"
|
|
);
|
|
this.padAndOutput(`* To troubleshoot your license key visit ${LICENSING_HELP_URL}.`, " ", "*");
|
|
this.centerPadAndOutput("");
|
|
this.centerPadAndOutput("");
|
|
} else {
|
|
this.centerPadAndOutput("");
|
|
this.centerPadAndOutput(` ${currentLicenseName} License `);
|
|
this.centerPadAndOutput(" Invalid License Key ");
|
|
this.padAndOutput(`* Your license key is not valid.`, " ", "*");
|
|
this.padAndOutput(`* To troubleshoot your license key visit ${LICENSING_HELP_URL}.`, " ", "*");
|
|
this.centerPadAndOutput("");
|
|
this.centerPadAndOutput("");
|
|
}
|
|
}
|
|
this.watermarkMessage = "Invalid License";
|
|
}
|
|
outputExpiredTrialKey(formattedExpiryDate, currentLicenseName, suppliedLicenseName) {
|
|
if (!_LicenseManager.gridContext) {
|
|
this.centerPadAndOutput("");
|
|
this.centerPadAndOutput(` ${currentLicenseName} License `);
|
|
this.centerPadAndOutput(" Trial Period Expired. ");
|
|
this.padAndOutput(
|
|
`* Your trial only license for ${suppliedLicenseName} expired on ${formattedExpiryDate}.`,
|
|
" ",
|
|
"*"
|
|
);
|
|
this.padAndOutput("* Please email info@ag-grid.com to purchase a license.", " ", "*");
|
|
this.centerPadAndOutput("");
|
|
this.centerPadAndOutput("");
|
|
}
|
|
this.watermarkMessage = "Trial Period Expired";
|
|
}
|
|
outputMissingLicenseKey(currentLicenseName) {
|
|
if (!_LicenseManager.gridContext) {
|
|
this.centerPadAndOutput("");
|
|
this.centerPadAndOutput(` ${currentLicenseName} License `);
|
|
this.centerPadAndOutput(" License Key Not Found ");
|
|
this.padAndOutput(`* All ${currentLicenseName} features are unlocked for trial.`, " ", "*");
|
|
this.padAndOutput(
|
|
"* If you want to hide the watermark please email info@ag-grid.com for a trial license key.",
|
|
" ",
|
|
"*"
|
|
);
|
|
this.centerPadAndOutput("");
|
|
this.centerPadAndOutput("");
|
|
}
|
|
this.watermarkMessage = "For Trial Use Only";
|
|
}
|
|
outputExpiredKey(formattedExpiryDate, formattedReleaseDate, currentLicenseName) {
|
|
if (!_LicenseManager.gridContext) {
|
|
this.centerPadAndOutput("");
|
|
this.centerPadAndOutput(` ${currentLicenseName} License `);
|
|
this.centerPadAndOutput(" Incompatible Software Version ");
|
|
this.padAndOutput(
|
|
`* Your license key works with versions of ${currentLicenseName} released before ${formattedExpiryDate}.`,
|
|
" ",
|
|
"*"
|
|
);
|
|
this.padAndOutput(`* The version you are trying to use was released on ${formattedReleaseDate}.`, " ", "*");
|
|
this.padAndOutput("* Please contact info@ag-grid.com to renew your license key.", " ", "*");
|
|
this.centerPadAndOutput("");
|
|
this.centerPadAndOutput("");
|
|
}
|
|
this.watermarkMessage = "License Expired";
|
|
}
|
|
};
|
|
_LicenseManager.RELEASE_INFORMATION = "MTc3MDgwNzY1NDM4MQ==";
|
|
_LicenseManager.gridContext = false;
|
|
var LicenseManager = _LicenseManager;
|
|
|
|
// packages/ag-charts-enterprise/src/license/watermark.ts
|
|
function injectWatermark(domManager, text2) {
|
|
const element2 = domManager.addChild("canvas-overlay", "watermark");
|
|
const textElement = createElement("span");
|
|
textElement.innerText = text2;
|
|
element2.addEventListener("animationend", () => {
|
|
domManager.removeChild("canvas-overlay", "watermark");
|
|
domManager.removeStyles("watermark");
|
|
});
|
|
element2.classList.add("ag-watermark");
|
|
element2.appendChild(textElement);
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/styles.css
|
|
var styles_default2 = `.ag-watermark{position:absolute;bottom:20px;right:25px;font-weight:700;font-family:Impact,sans-serif;font-size:19px;opacity:.7;animation:1s ease-out 3s ag-watermark-fadeout;color:#9b9b9b;pointer-events:none;&:before{content:"";display:block;height:40px;width:170px;background-image:url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjU4IiBoZWlnaHQ9IjQwIiB2aWV3Qm94PSIwIDAgMjU4IDQwIiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgo8cGF0aCBkPSJNMjUuNzc5IDI4LjY1N0gxMy4zNTlMMTEuMTczIDM0LjAxMkg1LjY3Mjk3TDE3LjE4MiA3LjA1OTk5SDIxLjk1M0wzMy40NjIgMzQuMDEySDI3Ljk2MkwyNS43NzYgMjguNjU3SDI1Ljc3OVpNMjQuMDY4IDI0LjM5N0wxOS41ODggMTMuNDM0TDE1LjEwNyAyNC4zOTdIMjQuMDY4Wk02Mi4wOTIgMTguODIzSDQ5LjgxN1YyMy4wODZINTYuNzc1QzU2LjU1NSAyNS4yMjIgNTUuNzU1IDI2LjkyNyA1NC4zNzIgMjguMjAyQzUyLjk4OSAyOS40NzYgNTEuMTY2IDMwLjExNSA0OC45MDkgMzAuMTE1QzQ3LjYyMiAzMC4xMTUgNDYuNDUgMjkuODg1IDQ1LjM5MyAyOS40MjNDNDQuMzU4MyAyOC45NzgxIDQzLjQzMjYgMjguMzEzOCA0Mi42OCAyNy40NzZDNDEuOTI3IDI2LjYzOSA0MS4zNDQgMjUuNjMxIDQwLjkzMSAyNC40NTNDNDAuNTE5IDIzLjI3NSA0MC4zMTEgMjEuOTcgNDAuMzExIDIwLjUzN0M0MC4zMTEgMTkuMTA1IDQwLjUxNiAxNy44IDQwLjkzMSAxNi42MjFDNDEuMzQ0IDE1LjQ0MyA0MS45MjcgMTQuNDM2IDQyLjY4IDEzLjU5OEM0My40Mzc2IDEyLjc1NzcgNDQuMzY5NiAxMi4wOTMyIDQ1LjQxMSAxMS42NTFDNDYuNDc4IDExLjE4OSA0Ny42NTYgMTAuOTYgNDguOTQ2IDEwLjk2QzUxLjYxMiAxMC45NiA1My42MzcgMTEuNjAyIDU1LjAyIDEyLjg4NUw1OC4zIDkuNjA0OTlDNTUuODE3IDcuNjY5OTkgNTIuNjc2IDYuNjk5OTkgNDguODcyIDYuNjk5OTlDNDYuNzYgNi42OTk5OSA0NC44NTMgNy4wMzQ5OSA0My4xNTQgNy43MDA5OUM0MS40NTUgOC4zNjc5OSAzOS45OTggOS4zMDM5OSAzOC43ODMgMTAuNTA0QzM3LjU2NyAxMS43MDcgMzYuNjM0IDEzLjE1OCAzNS45NzcgMTQuODU3QzM1LjMxOSAxNi41NTYgMzQuOTk0IDE4LjQ1MSAzNC45OTQgMjAuNTRDMzQuOTk0IDIyLjYzIDM1LjMyOSAyNC40OTQgMzUuOTk1IDI2LjIwNUMzNi42NjIgMjcuOTE2IDM3LjYwNSAyOS4zNzQgMzguODE3IDMwLjU3N0M0MC4wMzIgMzEuNzggNDEuNDg2IDMyLjcxMyA0My4xODggMzMuMzgzQzQ0Ljg4OCAzNC4wNDkgNDYuNzgyIDM0LjM4NCA0OC44NzIgMzQuMzg0QzUwLjk2MSAzNC4zODQgNTIuNzUgMzQuMDQ5IDU0LjM5IDMzLjM4M0M1Ni4wMzEgMzIuNzE2IDU3LjQyNiAzMS43OCA1OC41NzkgMzAuNTc3QzU5LjczMyAyOS4zNzQgNjAuNjE5IDI3LjkxNiA2MS4yMzkgMjYuMjA1QzYxLjg2IDI0LjQ5NCA2Mi4xNyAyMi42MDUgNjIuMTcgMjAuNTRDNjIuMTY5NiAxOS45Njg4IDYyLjE0NDUgMTkuMzk4IDYyLjA5NSAxOC44MjlMNjIuMDkyIDE4LjgyM1pNMTUxLjgxIDE2Ljk4MUMxNTMuNDEgMTQuNjA5IDE1Ny40MTkgMTQuMzU4IDE1OS4wMjIgMTQuMzU4VjE4LjkxQzE1Ni45NTcgMTguOTEgMTU0Ljk4NSAxOC45OTYgMTUzLjc1NyAxOS44OTJDMTUyLjUyOSAyMC43OTIgMTUxLjkxOSAyMS45ODIgMTUxLjkxOSAyMy40NjRWMzMuOTlIMTQ2Ljk2NFYxNC4zNThIMTUxLjczNkwxNTEuODEgMTYuOTgxWk0xNDMuMDExIDE0LjM2MVYzNC4wMzFIMTM4LjI0TDEzOC4xMzEgMzEuMDQ1QzEzNy40NjYgMzIuMDc2IDEzNi41NTEgMzIuOTIxOSAxMzUuNDcxIDMzLjUwNEMxMzQuMzc2IDM0LjA5OSAxMzMuMDY4IDM0LjM5NiAxMzEuNTM2IDM0LjM5NkMxMzAuMiAzNC4zOTYgMTI4Ljk2MyAzNC4xNTIgMTI3LjgyMiAzMy42NjhDMTI2LjcgMzMuMTk2NCAxMjUuNjg5IDMyLjQ5NSAxMjQuODU1IDMxLjYwOUMxMjQuMDE4IDMwLjcyMiAxMjMuMzU0IDI5LjY2MiAxMjIuODcxIDI4LjQyMkMxMjIuMzg0IDI3LjE4NSAxMjIuMTQyIDI1LjgxMSAxMjIuMTQyIDI0LjMwNEMxMjIuMTQyIDIyLjc5OCAxMjIuMzg0IDIxLjM3OCAxMjIuODcxIDIwLjExNkMxMjMuMzU3IDE4Ljg1NCAxMjQuMDE4IDE3Ljc3MiAxMjQuODU1IDE2Ljg3M0MxMjUuNjg4IDE1Ljk3NjQgMTI2LjY5OCAxNS4yNjM2IDEyNy44MjIgMTQuNzhDMTI4Ljk2MyAxNC4yODEgMTMwLjIwMyAxNC4wMzMgMTMxLjUzNiAxNC4wMzNDMTMzLjA0MyAxNC4wMzMgMTM0LjMzIDE0LjMxOCAxMzUuMzk3IDE0Ljg4OEMxMzYuNDYyIDE1LjQ1ODkgMTM3LjM3NSAxNi4yNzggMTM4LjA1NyAxNy4yNzZWMTQuMzYxSDE0My4wMTFaTTEzMi42MzEgMzAuMTMzQzEzNC4yNTYgMzAuMTMzIDEzNS41NjcgMjkuNTk0IDEzNi41NjUgMjguNTEyQzEzNy41NjEgMjcuNDMgMTM4LjA2IDI1Ljk5MSAxMzguMDYgMjQuMTk2QzEzOC4wNiAyMi40MDEgMTM3LjU2MSAyMC45OSAxMzYuNTY1IDE5Ljg5OUMxMzUuNTcgMTguODA3IDEzNC4yNTkgMTguMjU4IDEzMi42MzEgMTguMjU4QzEzMS4wMDMgMTguMjU4IDEyOS43MjkgMTguODA0IDEyOC43MzQgMTkuODk5QzEyNy43MzggMjAuOTkzIDEyNy4yMzkgMjIuNDM4IDEyNy4yMzkgMjQuMjMzQzEyNy4yMzkgMjYuMDI4IDEyNy43MzUgMjcuNDMzIDEyOC43MzQgMjguNTE1QzEyOS43MjkgMjkuNTk0IDEzMS4wMjggMzAuMTM2IDEzMi42MzEgMzAuMTM2VjMwLjEzM1pNOTMuNjk4IDI3Ljg3NkM5My41Nzk1IDI4LjAwMjUgOTMuNDU2NCAyOC4xMjQ2IDkzLjMyOSAyOC4yNDJDOTEuOTQ3IDI5LjUxNiA5MC4xMjMgMzAuMTU1IDg3Ljg2NiAzMC4xNTVDODYuNTggMzAuMTU1IDg1LjQwOCAyOS45MjYgODQuMzUgMjkuNDY0QzgzLjMxNTUgMjkuMDE4OSA4Mi4zODk4IDI4LjM1NDYgODEuNjM3IDI3LjUxN0M4MC44ODQgMjYuNjc5IDgwLjMwMSAyNS42NzIgNzkuODg5IDI0LjQ5NEM3OS40NzYgMjMuMzE1IDc5LjI2OSAyMi4wMSA3OS4yNjkgMjAuNTc4Qzc5LjI2OSAxOS4xNDUgNzkuNDczIDE3Ljg0IDc5Ljg4OSAxNi42NjJDODAuMzAxIDE1LjQ4NCA4MC44ODQgMTQuNDc2IDgxLjYzNyAxMy42MzlDODIuMzk0OSAxMi43OTg3IDgzLjMyNzMgMTIuMTM0MiA4NC4zNjkgMTEuNjkyQzg1LjQzNiAxMS4yMyA4Ni42MTQgMTEgODcuOTAzIDExQzkwLjU3IDExIDkyLjU5NSAxMS42NDIgOTMuOTc3IDEyLjkyNkw5Ny4yNTggOS42NDQ5OUM5NC43NzQgNy43MTA5OSA5MS42MzMgNi43Mzk5OSA4Ny44MjkgNi43Mzk5OUM4NS43MTggNi43Mzk5OSA4My44MTEgNy4wNzQ5OSA4Mi4xMTIgNy43NDE5OUM4MC40MTMgOC40MDc5OSA3OC45NTYgOS4zNDQ5OSA3Ny43NCAxMC41NDVDNzYuNTI1IDExLjc0NyA3NS41OTIgMTMuMTk5IDc0LjkzNCAxNC44OThDNzQuMjc3IDE2LjU5NyA3My45NTEgMTguNDkxIDczLjk1MSAyMC41ODFDNzMuOTUxIDIyLjY3IDc0LjI4NiAyNC41MzQgNzQuOTUzIDI2LjI0NUM3NS42MTkgMjcuOTU3IDc2LjU2MiAyOS40MTQgNzcuNzc0IDMwLjYxN0M3OC45OSAzMS44MiA4MC40NDQgMzIuNzUzIDgyLjE0NiAzMy40MjNDODMuODQ1IDM0LjA5IDg1LjczOSAzNC40MjQgODcuODI5IDM0LjQyNEM4OS45MTkgMzQuNDI0IDkxLjcwOCAzNC4wOSA5My4zNDggMzMuNDIzQzk0LjcxOCAzMi44NjUgOTUuOTE4IDMyLjEyMSA5Ni45NDggMzEuMTkxQzk3LjE0OSAzMS4wMDggOTcuMzQ4IDMwLjgxNSA5Ny41MzcgMzAuNjJMOTMuNzAxIDI3Ljg4NUw5My42OTggMjcuODc2Wk0xMTAuODAyIDE0LjAxNUMxMDkuMTk5IDE0LjAxNSAxMDYuODM2IDE0LjQ3MSAxMDUuNjExIDE2LjE1OEwxMDUuNTM3IDYuMDE1OTlIMTAwLjc2NVYzMy45MzlIMTA1LjcyVjIyLjY0MUMxMDUuNzcxIDIxLjQ2MDcgMTA2LjI4OCAyMC4zNDg4IDEwNy4xNTcgMTkuNTQ4OUMxMDguMDI3IDE4Ljc0OTEgMTA5LjE3OCAxOC4zMjY2IDExMC4zNTggMTguMzc0QzExMy4zOTcgMTguMzc0IDExNC4yNjggMjEuMTU5IDExNC4yNjggMjIuNjQxVjMzLjkzOUgxMTkuMjIzVjIxLjA1OUMxMTkuMjIzIDIxLjA1OSAxMTkuMTQyIDE0LjAxNSAxMTAuODAyIDE0LjAxNVpNMTczLjc2MyAxNC4zNThIMTY5Ljk5OVY4LjcxNDk5SDE2NS4wNDhWMTQuMzU4SDE2MS4yODRWMTguOTE2SDE2NS4wNDhWMzQuMDAzSDE2OS45OTlWMTguOTE2SDE3My43NjNWMTQuMzU4Wk0xOTAuNzg3IDI1LjI2MkMxOTAuMTI5IDI0LjUwMTQgMTg5LjMwNyAyMy44OTk0IDE4OC4zODQgMjMuNTAxQzE4Ny40ODggMjMuMTE3IDE4Ni4zMzEgMjIuNzMyIDE4NC45NDggMjIuMzY0QzE4NC4xNjUgMjIuMTQzOSAxODMuMzkgMjEuODk3OCAxODIuNjIzIDIxLjYyNkMxODIuMTYzIDIxLjQ2MjEgMTgxLjc0MSAyMS4yMDY2IDE4MS4zODMgMjAuODc1QzE4MS4yMzUgMjAuNzQyMSAxODEuMTE4IDIwLjU3ODkgMTgxLjAzOSAyMC4zOTY0QzE4MC45NjEgMjAuMjE0IDE4MC45MjIgMjAuMDE2NiAxODAuOTI3IDE5LjgxOEMxODAuOTI3IDE5LjI3MiAxODEuMTU2IDE4Ljg0NCAxODEuNjI1IDE4LjUxQzE4Mi4xMjEgMTguMTU2IDE4Mi44NjIgMTcuOTc2IDE4My44MjYgMTcuOTc2QzE4NC43OSAxNy45NzYgMTg1LjU4NyAxOC4yMDkgMTg2LjE0OCAxOC42NjhDMTg2LjcwNiAxOS4xMjQgMTg3LjAwNyAxOS43MjUgMTg3LjA3MiAyMC41TDE4Ny4wOTQgMjAuNzgySDE5MS42MzNMMTkxLjYxNyAyMC40NkMxOTEuNTIxIDE4LjQ4NSAxOTAuNzcxIDE2LjkgMTg5LjM4NSAxNS43NUMxODguMDEyIDE0LjYxMiAxODYuMTg1IDE0LjAzMyAxODMuOTYyIDE0LjAzM0MxODIuNDc3IDE0LjAzMyAxODEuMTQxIDE0LjI4NyAxNzkuOTk0IDE0Ljc4NkMxNzguODMxIDE1LjI5MSAxNzcuOTI2IDE1Ljk5NSAxNzcuMjk2IDE2Ljg4MkMxNzYuNjczIDE3Ljc0NTUgMTc2LjMzOCAxOC43ODQgMTc2LjM0MSAxOS44NDlDMTc2LjM0MSAyMS4xNjcgMTc2LjY5OCAyMi4yNDkgMTc3LjM5OSAyMy4wNjRDMTc4LjA2IDIzLjg0MzIgMTc4Ljg5OCAyNC40NTM0IDE3OS44NDIgMjQuODQ0QzE4MC43NDQgMjUuMjE2IDE4MS45MjggMjUuNjA3IDE4My4zNjEgMjZDMTg0LjgwNiAyNi40MSAxODUuODcyIDI2Ljc4NSAxODYuNTMgMjcuMTIzQzE4Ny4xIDI3LjQxNCAxODcuMzc5IDI3Ljg0NSAxODcuMzc5IDI4LjQ0NEMxODcuMzc5IDI5LjA0MiAxODcuMTIyIDI5LjQ2NyAxODYuNTk1IDI5LjgzOUMxODYuMDQzIDMwLjIyNiAxODUuMjM3IDMwLjQyNSAxODQuMjAxIDMwLjQyNUMxODMuMTY2IDMwLjQyNSAxODIuMzk0IDMwLjE3NCAxODEuNzQ5IDI5LjY3NEMxODEuMTEzIDI5LjE4MSAxODAuNzcyIDI4LjU4OSAxODAuNzEgMjcuODY0TDE4MC42ODUgMjcuNTgySDE3Ni4wMTNMMTc2LjAyNSAyNy45MDFDMTc2LjA2NyAyOS4wOTU1IDE3Ni40NzIgMzAuMjQ4NyAxNzcuMTg4IDMxLjIwNkMxNzcuOTA3IDMyLjE4IDE3OC44OTMgMzIuOTU4IDE4MC4xMTggMzMuNTE5QzE4MS4zMzYgMzQuMDc3IDE4Mi43MzIgMzQuMzYyIDE4NC4yNjYgMzQuMzYyQzE4NS44MDEgMzQuMzYyIDE4Ny4xMDkgMzQuMTA4IDE4OC4yMzggMzMuNjA5QzE4OS4zNzYgMzMuMTA0IDE5MC4yNzIgMzIuMzk0IDE5MC45MDEgMzEuNDk0QzE5MS41MzQgMzAuNTkyIDE5MS44NTMgMjkuNTU0IDE5MS44NTMgMjguNDAzQzE5MS44MjggMjcuMTEgMTkxLjQ2NiAyNi4wNTMgMTkwLjc3NyAyNS4yNjJIMTkwLjc4N1oiIGZpbGw9IiM5QjlCOUIiLz4KPHBhdGggZD0iTTI0MS45ODIgMjUuNjU4MlYxNy43MTE3SDIyOC40NDFMMjIwLjQ5NCAyNS42NTgySDI0MS45ODJaIiBmaWxsPSIjOUI5QjlCIi8+CjxwYXRoIGQ9Ik0yNTcuMjM5IDUuOTUwODFIMjQwLjI2NUwyMzIuMjU1IDEzLjg5NzNIMjU3LjIzOVY1Ljk1MDgxWiIgZmlsbD0iIzlCOUI5QiIvPgo8cGF0aCBkPSJNMjEyLjYxMSAzMy42MDQ4TDIxNi42OCAyOS41MzYxSDIzMC40MTJWMzcuNDgyN0gyMTIuNjExVjMzLjYwNDhaIiBmaWxsPSIjOUI5QjlCIi8+CjxwYXRoIGQ9Ik0yMTUuNTk5IDIxLjc4MDNIMjI0LjM3MkwyMzIuMzgyIDEzLjgzMzdIMjE1LjU5OVYyMS43ODAzWiIgZmlsbD0iIzlCOUI5QiIvPgo8cGF0aCBkPSJNMjA2IDMzLjYwNDdIMjEyLjYxMUwyMjAuNDk0IDI1LjY1ODJIMjA2VjMzLjYwNDdaIiBmaWxsPSIjOUI5QjlCIi8+CjxwYXRoIGQ9Ik0yNDAuMjY1IDUuOTUwODFMMjM2LjE5NyAxMC4wMTk0SDIxMC4yNTlWMi4wNzI4OEgyNDAuMjY1VjUuOTUwODFaIiBmaWxsPSIjOUI5QjlCIi8+Cjwvc3ZnPgo=);background-repeat:no-repeat;background-size:170px 40px}>span{padding-left:.7rem}}@keyframes ag-watermark-fadeout{0%{opacity:.5}to{opacity:0}}.ag-charts-dialog{display:flex;flex-direction:column;font-size:var(--ag-charts-chrome-font-size-large)}.ag-charts-dialog__tabs{display:flex;flex-direction:column}.ag-charts-dialog__header{border-bottom:1px solid var(--ag-charts-border-color);display:flex}.ag-charts-dialog__tab-list{display:flex;gap:calc(var(--ag-charts-spacing) * 2)}.ag-charts-dialog__drag-handle{align-items:center;color:inherit;cursor:grab;display:flex;padding:1px 6px;text-align:center}.ag-charts-dialog__drag-handle--dragging{cursor:grabbing}.ag-charts-dialog__tab-button{background:none;border:0;border-bottom:2px solid transparent;border-radius:0;color:var(--ag-charts-panel-subtle-text-color);margin-bottom:-1px;padding:var(--input-padding) calc(var(--input-padding) / 2)}.ag-charts-dialog__tab-button:hover{background:none}.ag-charts-dialog__tab-button--active{border-color:var(--ag-charts-accent-color);color:inherit}.ag-charts-dialog__drag-handle+.ag-charts-dialog__tab-button{margin-left:calc(var(--ag-charts-spacing) * -2)}.ag-charts-button.ag-charts-dialog__close-button{background:none;border:0;margin-left:auto;padding:1px 6px}.ag-charts-dialog__close-button:focus-visible{outline:var(--ag-charts-focus-border);box-shadow:var(--ag-charts-focus-border-shadow);z-index:calc(var(--ag-charts-layer-ui-overlay) + 1)}.ag-charts-dialog__tab-panel{display:none;flex-direction:column;gap:calc(var(--ag-charts-spacing) * 4);margin:0 calc(var(--ag-charts-spacing) * 4);padding:calc(var(--ag-charts-spacing) * 4) 0}.ag-charts-dialog__tab-panel--active{display:flex}.ag-charts-dialog__input-group-line{display:flex;gap:16px 18px;flex-wrap:wrap}.ag-charts-dialog__input-group{align-items:center;display:flex;font-size:var(--ag-charts-chrome-font-size)}.ag-charts-dialog__input-group-label{color:var(--ag-charts-panel-subtle-text-color);margin-right:5px}.ag-charts-dialog__input-group-label[for]{cursor:pointer}.ag-charts-dialog__button{border-radius:0;margin-right:-1px}.ag-charts-dialog__button.ag-charts-dialog__button--active{background:var(--ag-charts-button-focus-background-color);border-color:var(--ag-charts-input-focus-border-color);color:var(--ag-charts-input-focus-text-color);z-index:var(--input-layer-active)}.ag-charts-dialog__button:first-child,.ag-charts-dialog__input-group-label+.ag-charts-dialog__button{border-bottom-left-radius:var(--ag-charts-input-border-radius);border-top-left-radius:var(--ag-charts-input-border-radius)}.ag-charts-dialog__button:last-child{border-bottom-right-radius:var(--ag-charts-input-border-radius);border-top-right-radius:var(--ag-charts-input-border-radius)}.ag-charts-dialog__color-picker-button{--color: #000;background:var(--color);border:none;color:transparent;height:26px;width:26px}.ag-charts-dialog__color-picker-button:hover{background:var(--color)}.ag-charts-dialog__color-picker-button--multi-color,.ag-charts-dialog__color-picker-button--multi-color:hover{background:linear-gradient(135deg,red 0%,#ff0 calc(100% * 1 / 6),#0f0 calc(100% * 2 / 6),#0ff 50%,#00f calc(100% * 4 / 6),#f0f calc(100% * 5 / 6),red 100%)}.ag-charts-color-picker{width:190px;padding:8px;cursor:default;--h: 0;--s: 0;--v: 0;--a: 0;--color: #000;--color-a: #000;--thumb-size: 18px;--inner-width: 172px;--track-height: 12px;--palette-height: 136px;--checker: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="4" height="4"><rect x="0" y="0" width="4" height="4" fill="%23fff"/><path d="M0 0H2V4H4V2H0Z" fill="%23b2b2b2"/></svg>');--multi-color: linear-gradient( 135deg, #f00 0% , #ff0 calc(100% * 1 / 6), #0f0 calc(100% * 2 / 6), #0ff 50% , #00f calc(100% * 4 / 6), #f0f calc(100% * 5 / 6), #f00 100% )}.ag-charts-color-picker__content{display:flex;flex-direction:column}.ag-charts-color-picker__palette{position:relative;width:100%;height:var(--palette-height);margin-bottom:8px;background:linear-gradient(to bottom,#0000,#000),linear-gradient(to right,#fff,#fff0) hsl(var(--h),100%,50%);border-radius:calc(var(--ag-charts-border-radius) * 1.5);box-shadow:inset 0 0 0 1px #0003}.ag-charts-color-picker__palette:after{content:"";position:absolute;display:block;top:calc(var(--thumb-size) * -.5 + (1 - var(--v)) * 100%);left:calc(var(--thumb-size) * -.5 + var(--s) * 100%);background:var(--color);width:var(--thumb-size);height:var(--thumb-size);border-radius:calc(var(--ag-charts-border-radius) * 99);box-shadow:var(--box-shadow);--box-shadow: inset 0 0 0 3px white, inset 0 0 1px 3px #0006, 0 0 5px #00000038}.ag-charts-color-picker__palette:focus-visible:after{outline:var(--ag-charts-focus-border);box-shadow:var(--box-shadow),0 0 0 2px #fff8,var(--ag-charts-focus-border-shadow)}.ag-charts-color-picker__color-row{display:flex;gap:8px;align-items:center;margin-bottom:4px;--inset: calc((var(--thumb-size) - var(--track-height)) / 2)}.ag-charts-color-picker__hue-input,.ag-charts-color-picker__alpha-input{-webkit-appearance:none;display:block;position:relative;padding:0;margin:0 calc(var(--inset) * -1);border:0;height:var(--thumb-size);width:auto;background:transparent;--inset: calc((var(--thumb-size) - var(--track-height)) / 2)}.ag-charts-color-picker__hue-input::-moz-range-thumb,.ag-charts-color-picker__alpha-input::-moz-range-thumb{appearance:none;width:var(--thumb-size);height:var(--thumb-size);border-radius:calc(var(--ag-charts-border-radius) * 99);box-shadow:var(--box-shadow);--box-shadow: inset 0 0 0 3px white, inset 0 0 1px 3px #0006, 0 0 5px #00000038}.ag-charts-color-picker__hue-input::-webkit-slider-thumb,.ag-charts-color-picker__alpha-input::-webkit-slider-thumb{-webkit-appearance:none;width:var(--thumb-size);height:var(--thumb-size);border-radius:calc(var(--ag-charts-border-radius) * 99);box-shadow:var(--box-shadow);--box-shadow: inset 0 0 0 3px white, inset 0 0 1px 3px #0006, 0 0 5px #00000038;transform:translateZ(0)}.ag-charts-color-picker__hue-input::-moz-range-thumb{background:hsl(var(--h),100%,50%)}.ag-charts-color-picker__hue-input::-webkit-slider-thumb{background:hsl(var(--h),100%,50%)}.ag-charts-color-picker__alpha-input::-moz-range-thumb{background:transparent}.ag-charts-color-picker__alpha-input::-webkit-slider-thumb{background:transparent}.ag-charts-color-picker__alpha-input--opaque::-moz-range-thumb{background:var(--color)}.ag-charts-color-picker__alpha-input--opaque::-webkit-slider-thumb{background:var(--color)}.ag-charts-color-picker__hue-input:focus-visible::-moz-range-thumb,.ag-charts-color-picker__alpha-input:focus-visible::-moz-range-thumb{outline:var(--ag-charts-focus-border);box-shadow:var(--box-shadow),var(--ag-charts-focus-border-shadow)}.ag-charts-color-picker__hue-input:focus-visible::-webkit-slider-thumb,.ag-charts-color-picker__alpha-input:focus-visible::-webkit-slider-thumb{outline:var(--ag-charts-focus-border);box-shadow:var(--box-shadow),var(--ag-charts-focus-border-shadow)}.ag-charts-color-picker__hue-input::-moz-range-track,.ag-charts-color-picker__alpha-input::-moz-range-track{position:absolute;content:"";display:block;top:calc(50% - var(--track-height) / 2);left:var(--inset);right:var(--inset);height:var(--track-height);border-radius:calc(var(--ag-charts-border-radius) * 99);box-shadow:inset 0 0 0 1px #0003}.ag-charts-color-picker__hue-input:before,.ag-charts-color-picker__alpha-input:before{position:absolute;content:"";display:block;top:calc(50% - var(--track-height) / 2);left:var(--inset);right:var(--inset);height:var(--track-height);border-radius:calc(var(--ag-charts-border-radius) * 99);box-shadow:inset 0 0 0 1px #0003}.ag-charts-color-picker__multi-color-button{width:36px;margin-left:var(--inset);height:var(--track-height);border-radius:calc(var(--ag-charts-border-radius) * 99);border:0;background:var(--multi-color);box-shadow:inset 0 0 0 1px #0003}.ag-charts-color-picker__multi-color-button--hidden{display:none}.ag-charts-color-picker__multi-color-button--active{box-shadow:inset 0 0 0 1px #0003;outline-offset:1px;outline:2px solid #2196f3}.ag-charts-color-picker__hue-input{flex:1 0 0}.ag-charts-color-picker__hue-input::-moz-range-track{background:linear-gradient(to right,red,red calc((100% - var(--track-height)) * 0 / 6 + var(--track-height) / 2),#ff0 calc((100% - var(--track-height)) * 1 / 6 + var(--track-height) / 2),#0f0 calc((100% - var(--track-height)) * 2 / 6 + var(--track-height) / 2),#0ff calc((100% - var(--track-height)) * 3 / 6 + var(--track-height) / 2),#00f calc((100% - var(--track-height)) * 4 / 6 + var(--track-height) / 2),#f0f calc((100% - var(--track-height)) * 5 / 6 + var(--track-height) / 2),red calc((100% - var(--track-height)) * 6 / 6 + var(--track-height) / 2))}.ag-charts-color-picker__hue-input:before{background:linear-gradient(to right,red,red calc((100% - var(--track-height)) * 0 / 6 + var(--track-height) / 2),#ff0 calc((100% - var(--track-height)) * 1 / 6 + var(--track-height) / 2),#0f0 calc((100% - var(--track-height)) * 2 / 6 + var(--track-height) / 2),#0ff calc((100% - var(--track-height)) * 3 / 6 + var(--track-height) / 2),#00f calc((100% - var(--track-height)) * 4 / 6 + var(--track-height) / 2),#f0f calc((100% - var(--track-height)) * 5 / 6 + var(--track-height) / 2),red calc((100% - var(--track-height)) * 6 / 6 + var(--track-height) / 2))}.ag-charts-color-picker__alpha-input{margin-bottom:7px}.ag-charts-color-picker__alpha-input::-moz-range-track{background:linear-gradient(to right,transparent,var(--color)),var(--checker) top left / 4px 4px}.ag-charts-color-picker__alpha-input:before{background:linear-gradient(to right,transparent,var(--color)),var(--checker) top left / 4px 4px}.ag-charts-color-picker__color-field{display:flex;border:var(--ag-charts-border);background:var(--ag-charts-background-color);border-radius:var(--ag-charts-border-radius);overflow:hidden}.ag-charts-color-picker__color-field:has(:focus-visible){border-color:var(--ag-charts-accent-color);box-shadow:var(--ag-charts-focus-border-shadow)}.ag-charts-color-picker__color-label{width:16px;height:16px;margin:7px 0 7px 7px;color:transparent;background:linear-gradient(to right,var(--color-a),var(--color-a)),var(--checker) top left / 4px 4px;border-radius:calc(var(--ag-charts-border-radius) / 2);box-shadow:inset 0 0 0 1px #0003}.ag-charts-color-picker__color-label--multi-color{background:var(--multi-color)}.ag-charts-color-picker__color-input{flex:1;min-width:0;padding:7px 7px 7px 8px;border:0;margin:0;color:inherit;background:transparent;font-variant:tabular-nums}.ag-charts-color-picker__color-input:focus-visible{border:none;outline:none}.ag-charts-annotations__line-stroke-width-menu,.ag-charts-annotations__line-style-type-menu,.ag-charts-annotations__text-size-menu{border-top-left-radius:0;border-top-right-radius:0;.ag-charts-menu__row:first-child{border-radius:0}}.ag-charts-annotations__text-size-menu{--item-padding: 4px 8px;min-width:34px;text-align:center}.ag-charts-annotations__line-stroke-width-menu{--item-padding: 6px;column-gap:6px}.ag-charts-annotations__line-style-type-menu{--item-padding: 6px;column-gap:0}.ag-charts-annotations__stroke-width-button:before{background:var(--ag-charts-foreground-color);content:"";margin-right:var(--toolbar-button-padding);height:min(var(--stroke-width),20px);width:12px}.ag-charts-annotations__stroke-width-button[aria-disabled=true]:before{filter:grayscale(1);opacity:.5}.ag-charts-annotations__color-picker-button{--emblem: var(--color)}.ag-charts-annotations__color-picker-button--multi-color{--emblem: linear-gradient( to right, #f00 0% , #ff0 calc(100% * 1 / 6), #0f0 calc(100% * 2 / 6), #0ff 50% , #00f calc(100% * 4 / 6), #f0f calc(100% * 5 / 6), #f00 100% )}.ag-charts-annotations__color-picker-button:after{content:"";display:block;position:absolute;bottom:3px;left:5px;right:5px;height:4px;border-radius:99px;box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--ag-charts-foreground-color) 10%,transparent);background:var(--emblem)}.ag-charts-annotations__color-picker-button[aria-disabled=true]:after{filter:grayscale(1);opacity:.5}.ag-charts-annotations__toolbar-menu{min-width:200px}.ag-charts-annotations__axis-button--hidden{display:none}.ag-charts-annotations__axis-button{background-color:var(--ag-charts-crosshair-label-background-color);border-radius:calc(var(--ag-charts-border-radius) / 2);border:none;box-sizing:border-box;color:var(--ag-charts-crosshair-label-text-color);cursor:pointer;font-family:var(--ag-charts-button-font-family);font-size:var(--ag-charts-button-font-size);font-weight:var(--ag-charts-button-font-weight);left:0;line-height:16px;overflow:hidden;padding:0;position:absolute;top:0;user-select:none;white-space:nowrap;z-index:var(--ag-charts-layer-annotations)}.ag-charts-annotations__axis-button:hover{opacity:.8;color:var(--ag-charts-background-color)}.ag-charts-dialog--annotation-settings{min-height:233px;width:289px}.ag-charts-dialog--annotation-settings .ag-charts-textarea{height:calc(10px * 2 + var(--textarea-line-height) * 1em * 3 + 2px);overflow-y:auto;resize:vertical}.ag-charts-context-menu{font-family:var(--ag-charts-chrome-font-family);font-size:var(--ag-charts-chrome-font-size);font-weight:var(--ag-charts-chrome-font-weight);z-index:var(--ag-charts-layer-ui-overlay)}.ag-charts-context-menu__cover{position:fixed;left:0;top:0}.ag-charts-context-menu__menu{background:var(--ag-charts-menu-background-color);border-radius:var(--ag-charts-border-radius);border:var(--ag-charts-menu-border);box-shadow:var(--ag-charts-popup-shadow);color:var(--ag-charts-menu-text-color);display:flex;flex-direction:column;padding:.5em 0;transition:transform .1s ease;white-space:nowrap}.ag-charts-context-menu__menu:focus{outline:none}.ag-charts-context-menu__item{align-items:center;background:none;border:none;box-sizing:border-box;color:inherit;cursor:pointer;display:flex;font:inherit;justify-content:space-between;text-align:left;width:100%;-webkit-appearance:none;-moz-appearance:none}.ag-charts-context-menu__icon>img{width:var(--ag-charts-icon-size);height:var(--ag-charts-icon-size)}.ag-charts-context-menu__icon,.ag-charts-context-menu__cell{display:flex;align-items:center;flex-shrink:0}.ag-charts-context-menu__cell{flex-grow:1}.ag-charts-context-menu__cellpadding{padding:.5em 1em}.ag-charts-context-menu__icon{padding-right:0}.ag-charts-context-menu__item[data-focus-override=true],.ag-charts-context-menu__item:focus,.ag-charts-context-menu__item:active{background:var(--ag-charts-focus-color)}.ag-charts-context-menu__item[data-focus-override=false]{background:inherit}.ag-charts-context-menu__item[data-focus-visible-override=true]:focus,.ag-charts-context-menu__item:focus-visible{outline:var(--ag-charts-focus-border);box-shadow:var(--ag-charts-focus-border-shadow);z-index:calc(var(--ag-charts-layer-ui-overlay) + 1)}.ag-charts-context-menu__item[data-focus-visible-override=false]{outline:inherit;box-shadow:inherit;z-index:inherit}.ag-charts-context-menu__item[aria-disabled=true]{border:none;color:color-mix(in srgb,var(--ag-charts-input-text-color) 50%,transparent)}.ag-charts-context-menu__item[aria-disabled=true]:focus{background:inherit;cursor:inherit}.ag-charts-context-menu__divider{padding:5px 0}.ag-charts-context-menu__divider:after{content:"";display:block;border-top:1px solid var(--ag-charts-border-color)}.ag-charts-crosshair-label{position:absolute;left:0;top:0;user-select:none;pointer-events:none;font-family:var(--ag-charts-font-family);font-size:var(--ag-charts-font-size);font-weight:var(--ag-charts-font-weight);overflow:hidden;white-space:nowrap;z-index:var(--ag-charts-layer-crosshair);box-sizing:border-box}.ag-charts-crosshair-label-content{padding:0 8px;border-radius:calc(var(--ag-charts-border-radius) / 2);line-height:calc(var(--ag-charts-font-size) + 8px);background-color:var(--ag-charts-crosshair-label-background-color);color:var(--ag-charts-crosshair-label-text-color)}.ag-charts-crosshair-label--hidden{visibility:hidden!important}.ag-charts-text-input{position:absolute}.ag-charts-text-input__textarea{--placeholder-text-color: var(--ag-charts-input-placeholder-text-color);display:block;height:100%;width:100%;border:0;background:none;line-height:1.38;outline:none;transform:translateY(.09em)}.ag-charts-text-input__textarea[placeholder]:empty:before{content:attr(placeholder);color:var(--placeholder-text-color);font-weight:400}.ag-charts-text-input__textarea[placeholder]:not(:empty):before{content:""}.ag-charts-chart-toolbar__menu{min-width:200px}.ag-charts-range-buttons .ag-charts-toolbar__button{padding:var(--toolbar-button-padding) calc(var(--toolbar-button-padding) * 1.5)}.ag-charts-zoom-buttons{align-items:center;display:flex;height:44px;justify-content:center;overflow:hidden;padding-bottom:10px;pointer-events:none;width:100%;.ag-charts-toolbar{--toolbar-size: 24px;--toolbar-button-padding: 1px;display:flex;font-size:var(--ag-charts-chrome-font-size);height:var(--toolbar-size);justify-content:center;opacity:1;pointer-events:auto;transition:opacity .2s ease-in-out,transform .4s ease-in-out;.ag-charts-toolbar__button--first{border-bottom-left-radius:var(--ag-charts-border-radius);border-top-left-radius:var(--ag-charts-border-radius)}.ag-charts-toolbar__button--last{border-bottom-right-radius:var(--ag-charts-border-radius);border-top-right-radius:var(--ag-charts-border-radius)}.ag-charts-toolbar__label{padding-left:var(--ag-charts-spacing);padding-right:var(--ag-charts-spacing)}.ag-charts-toolbar__icon+.ag-charts-toolbar__label{padding-left:0}.ag-charts-toolbar__button--gap{margin-left:var(--toolbar-gap)}&.ag-charts-zoom-buttons__toolbar--hidden{opacity:0;transition:opacity .4s ease-in-out,transform .4s ease-in-out}}}.ag-charts-shared-toolbar{gap:var(--toolbar-gap);.ag-charts-toolbar__button{border-radius:var(--ag-charts-border-radius);margin:0}.ag-charts-toolbar__button--active+.ag-charts-toolbar__button{border-left-color:var(--ag-charts-border-color)}}
|
|
`;
|
|
|
|
// packages/ag-charts-enterprise/src/setup.ts
|
|
function setupEnterpriseModules() {
|
|
moduleRegistry_exports.registerModules(AllEnterpriseModule);
|
|
enterpriseRegistry.styles = styles_default2;
|
|
enterpriseRegistry.licenseManager = (options) => new LicenseManager(
|
|
options.container?.ownerDocument ?? (typeof document === "undefined" ? void 0 : document)
|
|
);
|
|
enterpriseRegistry.injectWatermark = injectWatermark;
|
|
enterpriseRegistry.createBackground = (ctx) => new Background3(ctx);
|
|
enterpriseRegistry.createForeground = (ctx) => new Foreground(ctx);
|
|
}
|
|
|
|
// packages/ag-charts-enterprise/src/module-bundles/integrated.ts
|
|
var LicenseManager2 = {
|
|
setLicenseKey(key) {
|
|
LicenseManager.setLicenseKey(key);
|
|
}
|
|
};
|
|
var AgChartsEnterpriseModule = {
|
|
VERSION,
|
|
_Scene: integrated_charts_scene_exports,
|
|
_Theme: integrated_charts_theme_exports,
|
|
_Util: integrated_charts_util_exports,
|
|
create: AgCharts.create.bind(AgCharts),
|
|
createSparkline: AgCharts.__createSparkline.bind(AgCharts),
|
|
setup: () => {
|
|
moduleRegistry_exports.setRegistryMode(moduleRegistry_exports.RegistryMode.Integrated);
|
|
setupEnterpriseModules();
|
|
},
|
|
setGridContext: LicenseManager.setGridContext.bind(LicenseManager),
|
|
setLicenseKey: LicenseManager.setLicenseKey.bind(LicenseManager),
|
|
isEnterprise: true
|
|
};
|
|
|
|
// packages/ag-charts-enterprise/src/main.ts
|
|
moduleRegistry_exports.setRegistryMode(moduleRegistry_exports.RegistryMode.Enterprise);
|
|
enterpriseRegistry.styles = styles_default2;
|
|
enterpriseRegistry.licenseManager = (options) => new LicenseManager(options.container?.ownerDocument ?? (typeof document === "undefined" ? void 0 : document));
|
|
enterpriseRegistry.injectWatermark = injectWatermark;
|
|
enterpriseRegistry.createBackground = (ctx) => new Background3(ctx);
|
|
enterpriseRegistry.createForeground = (ctx) => new Foreground(ctx);
|
|
|
|
// packages/ag-charts-enterprise/src/main-umd.ts
|
|
moduleRegistry_exports.setRegistryMode(moduleRegistry_exports.RegistryMode.UMD);
|
|
moduleRegistry_exports.registerModules(AllEnterpriseModule);
|
|
if (typeof module.exports == "object" && typeof exports == "object") {
|
|
var __cp = (to, from, except, desc) => {
|
|
if ((from && typeof from === "object") || typeof from === "function") {
|
|
for (let key of Object.getOwnPropertyNames(from)) {
|
|
if (!Object.prototype.hasOwnProperty.call(to, key) && key !== except)
|
|
Object.defineProperty(to, key, {
|
|
get: () => from[key],
|
|
enumerable: !(desc = Object.getOwnPropertyDescriptor(from, key)) || desc.enumerable,
|
|
});
|
|
}
|
|
}
|
|
return to;
|
|
};
|
|
module.exports = __cp(module.exports, exports);
|
|
}
|
|
return module.exports;
|
|
}))
|