@progress/kendo-angular-charts
Version:
Kendo UI Charts for Angular - A comprehensive package for creating beautiful and interactive data visualization. Every chart type, stock charts, and sparklines are included.
480 lines (479 loc) • 18.6 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, NgZone, Output, Renderer2, ViewChild, } from '@angular/core';
import { WatermarkOverlayComponent, isDocumentAvailable, shouldShowValidationUI } from '@progress/kendo-angular-common';
import { L10N_PREFIX, LocalizationService } from '@progress/kendo-angular-l10n';
import { exportImage, exportSVG } from '@progress/kendo-drawing';
import { validatePackage } from '@progress/kendo-licensing';
import { combineLatest } from 'rxjs';
import { auditTime, tap } from 'rxjs/operators';
import { ConfigurationService, THROTTLE_MS } from './common/configuration.service';
import { copyChanges } from './common/copy-changes';
import { SankeyThemeService } from './sankey/theme.service';
import { toSimpleChanges } from './common/to-simple-changes';
import { packageMetadata } from './package-metadata';
import { InstanceEventService } from './sankey/events';
import { Sankey, } from '@progress/kendo-charts';
import { hasObservers } from './common/has-observers';
import { SankeyTooltipPopupComponent } from './sankey/tooltip/tooltip-popup.component';
import { SankeyTooltipTemplateService } from './sankey/tooltip/tooltip-template.service';
import { NgIf } from '@angular/common';
import { LocalizedMessagesDirective } from './sankey/localization/localized-messages.directive';
import { IntlService } from '@progress/kendo-angular-intl';
import * as i0 from "@angular/core";
import * as i1 from "./common/configuration.service";
import * as i2 from "./sankey/theme.service";
import * as i3 from "@progress/kendo-angular-l10n";
import * as i4 from "./sankey/events";
import * as i5 from "@progress/kendo-angular-intl";
/**
* The Sankey diagram component.
*
* @example
* ```ts
* import { Component } from '@angular/core';
*
* _@Component({
* selector: 'my-app',
* template: `
* <kendo-sankey [data]="data">
* </kendo-sankey>
* `,
* })
* export class AppComponent {
* public data: SankeyData = {
* nodes: [
* { id: 1, label: { text: 'Linux' } },
* { id: 0, label: { text: 'iOS'} },
* { id: 2, label: { text: 'Mobile' } },
* { id: 3, label: { text: 'Desktop' } },
* ],
* links: [
* { sourceId: 0, targetId: 2, value: 1 },
* { sourceId: 1, targetId: 2, value: 2 },
* { sourceId: 1, targetId: 3, value: 3 },
* ],
* };
* }
* ```
*/
export class SankeyComponent {
element;
configurationService;
themeService;
localizationService;
instanceEventService;
ngZone;
changeDetector;
renderer;
intlService;
/**
* The data of the Sankey component containing the `links` and `nodes`.
*/
data;
/**
* The default configuration for links.
* These settings will be applied to all links unless overridden by individual data items.
*/
links;
/**
* The default configuration for nodes.
* These settings will be applied to all nodes unless overridden by individual data items.
*/
nodes;
/**
* The default configuration for labels.
* These settings will be applied to all labels unless overridden by individual data items.
*/
labels;
/**
* The title configuration of the Sankey component.
*/
title;
/**
* The legend configuration of the Sankey component.
*/
legend;
/**
* The configuration of the Sankey tooltip.
*/
tooltip;
/**
* If set to `true`, the Sankey component will not perform automatic layout.
*/
disableAutoLayout;
/**
* If set to `false`, the Sankey keyboard navigation will be disabled.
* By default, navigation is enabled.
*
* @default true
*/
navigable = true;
/**
* The settings for the tooltip popup.
*/
popupSettings;
/**
* Fires when the mouse pointer enters a node. Similar to the `mouseenter` event.
*/
nodeEnter = new EventEmitter();
/**
* Fires when the mouse pointer leaves a node. Similar to the `mouseleave` event.
*/
nodeLeave = new EventEmitter();
/**
* Fires when a node is clicked.
*/
nodeClick = new EventEmitter();
/**
* Fires when the mouse pointer enters a link. Similar to the `mouseenter` event,
*/
linkEnter = new EventEmitter();
/**
* Fires when the mouse pointer leaves a link. Similar to the `mouseleave` event.
*/
linkLeave = new EventEmitter();
/**
* Fires when a link is clicked.
*/
linkClick = new EventEmitter();
tooltipInstance;
instanceElement;
/**
* @hidden
*/
showLicenseWatermark = false;
instance;
options;
theme;
optionsChange;
redrawTimeout;
destroyed;
subscriptions;
rtl = false;
hostClasses = ['k-chart', 'k-sankey'];
constructor(element, configurationService, themeService, localizationService, instanceEventService, ngZone, changeDetector, renderer, intlService) {
this.element = element;
this.configurationService = configurationService;
this.themeService = themeService;
this.localizationService = localizationService;
this.instanceEventService = instanceEventService;
this.ngZone = ngZone;
this.changeDetector = changeDetector;
this.renderer = renderer;
this.intlService = intlService;
const isValid = validatePackage(packageMetadata);
this.showLicenseWatermark = shouldShowValidationUI(isValid);
this.themeService.loadTheme();
this.refreshWait();
}
ngOnInit() {
if (this.element) {
this.hostClasses.forEach(name => {
this.renderer.addClass(this.element.nativeElement, name);
});
}
}
ngAfterViewInit() {
this.setDirection();
this.subscriptions = this.intlService.changes.subscribe(this.intlChange.bind(this));
this.subscriptions.add(this.localizationService.changes.subscribe(this.rtlChange.bind(this)));
}
ngOnChanges(changes) {
const store = this.configurationService.store;
copyChanges(changes, store);
store.popupSettings = null;
this.configurationService.push(store);
}
/**
* Updates the component fields with the specified values and refreshes the widget.
*
* Use this method when the configuration values cannot be set through the template.
*
* @example
* ```ts-no-run
* sankey.notifyChanges({ title: { text: 'New Title' } });
* ```
*
* @param changes An object containing the updated input fields.
*/
notifyChanges(changes) {
this.ngOnChanges(toSimpleChanges(changes));
}
ngOnDestroy() {
this.destroyed = true;
if (this.optionsChange) {
this.optionsChange.unsubscribe();
}
if (this.instance) {
this.instance.destroy();
this.instance = null;
}
if (this.subscriptions) {
this.subscriptions.unsubscribe();
}
clearTimeout(this.redrawTimeout);
}
/**
* @hidden
*/
messageFor(key) {
return this.localizationService.get(key);
}
createInstance(element) {
this.instance = new Sankey(element, this.instanceOptions, this.theme);
['nodeEnter', 'nodeLeave', 'nodeClick', 'linkEnter', 'linkLeave', 'linkClick'].forEach((eventName) => this.instance.bind(eventName, (e) => this.trigger(eventName, e)));
this.instance.bind('tooltipShow', (e) => this.onShowTooltip(e));
this.instance.bind('tooltipHide', () => this.onHideTooltip());
this.hostClasses.forEach(name => {
this.renderer.removeClass(this.instanceElement.nativeElement, name);
});
}
/**
* Exports the Sankey diagram as an image. The export operation is asynchronous and returns a promise.
*
* @param {ImageExportOptions} options - The parameters for the exported image.
* @returns {Promise<string>} - A promise that will be resolved with a PNG image encoded as a Data URI.
*/
exportImage(options = {}) {
return exportImage(this.exportVisual(options), options);
}
/**
* Exports the Sankey diagram as an SVG document. The export operation is asynchronous and returns a promise.
*
* @param options - The parameters for the exported file.
* @returns - A promise that will be resolved with an SVG document that is encoded as a Data URI.
*/
exportSVG(options = {}) {
return exportSVG(this.exportVisual(options), options);
}
/**
* Exports the visual of the Sankey component to a drawing group.
*
* @param options - The parameters for the export operation.
* @returns - The root Group of the scene.
*/
exportVisual(options = {}) {
return this.instance.exportVisual(options);
}
init() {
if (!this.canRender) {
return;
}
const element = this.instanceElement.nativeElement;
this.createInstance(element);
}
/**
* Reloads the Sankey appearance settings from the current [Kendo UI Theme]({% slug themesandstyles %}).
*
* Call this method after loading a different theme stylesheet.
*/
reloadTheme() {
if (!this.instance) {
return;
}
this.themeService.reset();
this.instance.destroy();
this.instance = null;
}
onShowTooltip(e) {
this.run(() => {
this.tooltipInstance.show(e);
}, true, true);
}
onHideTooltip() {
if (this.tooltipInstance.active) {
this.tooltipInstance.hide();
this.detectChanges();
}
}
trigger(name, e) {
const emitter = this.activeEmitter(name);
if (emitter) {
const args = this.instanceEventService.create(name, e, this);
this.run(() => {
emitter.emit(args);
});
return args.isDefaultPrevented && args.isDefaultPrevented();
}
}
requiresHandlers(names) {
for (let idx = 0; idx < names.length; idx++) {
if (this.activeEmitter(names[idx])) {
return true;
}
}
return false;
}
refresh() {
clearTimeout(this.redrawTimeout);
if (!this.instance) {
this.init();
return;
}
this.updateOptions();
}
updateOptions() {
this.instance.setOptions(this.instanceOptions);
}
get canRender() {
return isDocumentAvailable() && Boolean(this.instanceElement);
}
get instanceOptions() {
return { ...this.options, rtl: this.rtl, disableKeyboardNavigation: !this.navigable };
}
activeEmitter(name) {
const emitter = this[name];
if (emitter && emitter.emit && hasObservers(emitter)) {
return emitter;
}
}
refreshWait() {
this.ngZone.runOutsideAngular(() => {
this.optionsChange = combineLatest([this.configurationService.onChange$, this.themeService.onChange$])
.pipe(tap((result) => {
this.options = result[0];
this.theme = result[1];
}), auditTime(THROTTLE_MS))
.subscribe(() => {
this.refresh();
});
});
}
run(callback, inZone = true, detectChanges) {
if (inZone) {
if (detectChanges) {
this.changeDetector.markForCheck();
}
this.ngZone.run(callback);
}
else {
callback();
if (detectChanges) {
this.detectChanges();
}
}
}
detectChanges() {
if (!this.destroyed) {
this.changeDetector.detectChanges();
}
}
intlChange() {
if (this.instance) {
this.refresh();
}
}
rtlChange() {
if (this.instance && this.rtl !== this.isRTL) {
this.refresh();
}
}
setDirection() {
this.rtl = this.isRTL;
if (this.element) {
this.renderer.setAttribute(this.element.nativeElement, 'dir', this.rtl ? 'rtl' : 'ltr');
}
}
get isRTL() {
return Boolean(this.localizationService.rtl);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SankeyComponent, deps: [{ token: i0.ElementRef }, { token: i1.ConfigurationService }, { token: i2.SankeyThemeService }, { token: i3.LocalizationService }, { token: i4.InstanceEventService }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }, { token: i0.Renderer2 }, { token: i5.IntlService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: SankeyComponent, isStandalone: true, selector: "kendo-sankey", inputs: { data: "data", links: "links", nodes: "nodes", labels: "labels", title: "title", legend: "legend", tooltip: "tooltip", disableAutoLayout: "disableAutoLayout", navigable: "navigable", popupSettings: "popupSettings" }, outputs: { nodeEnter: "nodeEnter", nodeLeave: "nodeLeave", nodeClick: "nodeClick", linkEnter: "linkEnter", linkLeave: "linkLeave", linkClick: "linkClick" }, providers: [
ConfigurationService,
LocalizationService,
InstanceEventService,
SankeyTooltipTemplateService,
{
provide: L10N_PREFIX,
useValue: 'kendo.sankey',
},
], viewQueries: [{ propertyName: "tooltipInstance", first: true, predicate: SankeyTooltipPopupComponent, descendants: true, static: true }, { propertyName: "instanceElement", first: true, predicate: ["instance"], descendants: true, static: true }], exportAs: ["kendoSankey"], usesOnChanges: true, ngImport: i0, template: `
<ng-container
kendoSankeyLocalizedMessages
i18n-tooltipUnitFormat="kendo.sankey.tooltipUnitFormat|The format string to use when displaying node and link values in the tooltip"
tooltipUnitFormat="({0} units)"
></ng-container>
<div #instance class="k-chart-surface"></div>
<kendo-sankey-tooltip-popup
[popupSettings]="popupSettings"
[tooltipUnitFormat]="messageFor('tooltipUnitFormat')"
>
</kendo-sankey-tooltip-popup>
<div kendoWatermarkOverlay *ngIf="showLicenseWatermark"></div>
`, isInline: true, dependencies: [{ kind: "directive", type: LocalizedMessagesDirective, selector: "[kendoSankeyLocalizedMessages]" }, { kind: "component", type: SankeyTooltipPopupComponent, selector: "kendo-sankey-tooltip-popup", inputs: ["animate", "wrapperClass", "tooltipUnitFormat", "offset"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: WatermarkOverlayComponent, selector: "div[kendoWatermarkOverlay]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SankeyComponent, decorators: [{
type: Component,
args: [{
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'kendoSankey',
providers: [
ConfigurationService,
LocalizationService,
InstanceEventService,
SankeyTooltipTemplateService,
{
provide: L10N_PREFIX,
useValue: 'kendo.sankey',
},
],
selector: 'kendo-sankey',
template: `
<ng-container
kendoSankeyLocalizedMessages
i18n-tooltipUnitFormat="kendo.sankey.tooltipUnitFormat|The format string to use when displaying node and link values in the tooltip"
tooltipUnitFormat="({0} units)"
></ng-container>
<div #instance class="k-chart-surface"></div>
<kendo-sankey-tooltip-popup
[popupSettings]="popupSettings"
[tooltipUnitFormat]="messageFor('tooltipUnitFormat')"
>
</kendo-sankey-tooltip-popup>
<div kendoWatermarkOverlay *ngIf="showLicenseWatermark"></div>
`,
standalone: true,
imports: [LocalizedMessagesDirective, SankeyTooltipPopupComponent, NgIf, WatermarkOverlayComponent]
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i1.ConfigurationService }, { type: i2.SankeyThemeService }, { type: i3.LocalizationService }, { type: i4.InstanceEventService }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }, { type: i0.Renderer2 }, { type: i5.IntlService }]; }, propDecorators: { data: [{
type: Input
}], links: [{
type: Input
}], nodes: [{
type: Input
}], labels: [{
type: Input
}], title: [{
type: Input
}], legend: [{
type: Input
}], tooltip: [{
type: Input
}], disableAutoLayout: [{
type: Input
}], navigable: [{
type: Input
}], popupSettings: [{
type: Input
}], nodeEnter: [{
type: Output
}], nodeLeave: [{
type: Output
}], nodeClick: [{
type: Output
}], linkEnter: [{
type: Output
}], linkLeave: [{
type: Output
}], linkClick: [{
type: Output
}], tooltipInstance: [{
type: ViewChild,
args: [SankeyTooltipPopupComponent, { static: true }]
}], instanceElement: [{
type: ViewChild,
args: ['instance', { static: true }]
}] } });