@progress/kendo-angular-diagrams
Version:
Kendo UI Angular diagrams component
656 lines (655 loc) • 23.8 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 { Component, ElementRef, EventEmitter, HostBinding, Input, NgZone, Output, Renderer2, } from '@angular/core';
import { Diagram } from '@progress/kendo-diagram-common';
import { validatePackage } from '@progress/kendo-licensing';
import { packageMetadata } from './package-metadata';
import { hasObservers, shouldShowValidationUI, WatermarkOverlayComponent } from '@progress/kendo-angular-common';
import { NgIf } from '@angular/common';
import { events, loadTheme } from '@progress/kendo-diagram-common';
import { CONNECTION_DEFAULTS, SHAPE_DEFAULTS } from './utils';
import * as i0 from "@angular/core";
/**
* Represents the [Kendo UI Diagram component for Angular({% slug overview_diagrams %}).
*
* The Diagram component is used to create organizational charts, and other types of diagrams.
*
* @example
* ```html
* <kendo-diagram [shapes]="shapesData"></kendo-diagram>
* ```
*/
export class DiagramComponent {
wrapperElement;
renderer;
zone;
diagramClass = true;
/**
* Defines the default configuration options applicable to all connections.
* Changing the property value dynamically triggers a reinitialization of the Diagram.
*/
connectionDefaults = CONNECTION_DEFAULTS;
/**
* Defines the connections that render between the shapes in the Diagram.
* Changing this property dynamically reinitializes the Diagram.
*/
connections;
/**
* A set of settings to configure the Diagram behavior when the user attempts to drag, resize, or remove shapes.
* Changing the property value dynamically triggers a reinitialization of the Diagram.
* @default true
*/
editable = { drag: true, resize: true, remove: true };
/**
* The layout of a diagram consists of arranging the shapes (sometimes also the connections) in some fashion in order to achieve an aesthetically pleasing experience to the user.
* Changing the property value dynamically triggers a reinitialization of the Diagram.
*/
layout = { type: 'tree' };
/**
* Defines the pannable options. Use this setting to disable Diagram pan or change the key that activates the pan behavior.
* @default true
*/
pannable = true;
/**
* Defines the Diagram selection options.
*
* By default, you can select shapes in the Diagram in one of two ways:
* - Clicking a single shape to select it and deselect any previously selected shapes.
* - Holding the `Ctrl/Cmd on MacOS` key while clicking multiple shapes to select them and any other shapes between them.
*
* Use the `selectable` configuration to allow single selection only, enable selection by drawing a rectangular area with the mouse around shapes in the canvas, or disable selection altogether.
* @default true
*/
selectable = true;
/**
* Defines the default configuration options applicable to all shapes.
* Changing the property value dynamically triggers a reinitialization of the Diagram.
*/
shapeDefaults = SHAPE_DEFAULTS;
/**
* Defines the shapes that render in the Diagram.
* Changing the property value dynamically triggers a reinitialization of the Diagram.
*/
shapes;
/**
* Defines the zoom level of the Diagram.
*
* @default 1
*/
zoom = 1;
/**
* Defines the maximum zoom level of the Diagram.
*
* @default 2
*/
zoomMax = 2;
/**
* Defines the minimum zoom level of the Diagram.
*
* @default 0.1
*/
zoomMin = 0.1;
/**
* Defines the zoom rate of the Diagram.
*
* @default 0.1
*/
zoomRate = 0.1;
/**
* Fires when a shape or connection is created or removed.
*/
change = new EventEmitter();
/**
* Fires when the user clicks on a shape or a connection.
*/
diagramClick = new EventEmitter();
/**
* Fires when the user drags an item.
*/
drag = new EventEmitter();
/**
* Fires when the user stops dragging an item.
*/
dragEnd = new EventEmitter();
/**
* Fires when the user starts dragging an item.
*/
dragStart = new EventEmitter();
/**
* Fires when the location or size of an item are changed.
*/
shapeBoundsChange = new EventEmitter();
/**
* Fires when the mouse pointer enters a shape or connection.
*/
mouseEnter = new EventEmitter();
/**
* Fires when the mouse pointer leaves a shape or connection.
*/
mouseLeave = new EventEmitter();
/**
* Fires when the user pans the Diagram.
*/
onPan = new EventEmitter();
/**
* Fires when the user selects one or more items.
*/
onSelect = new EventEmitter();
/**
* Fires when the diagram has finished zooming out.
*/
zoomEnd = new EventEmitter();
/**
* Fires when the diagram starts zooming in or out.
*/
zoomStart = new EventEmitter();
/**
* @hidden
* Represents the Diagram instance, holding the core functionality of the Diagram.
*/
diagram;
/**
* The currently selected items in the Diagram.
*/
get selection() {
return this.diagram?.select();
}
/**
* The actual shapes created by the Diagram.
*/
get diagramShapes() {
return this.diagram?.shapes;
}
/**
* The actual connections created by the Diagram.
*/
get diagramConnections() {
return this.diagram?.connections;
}
/**
* @hidden
*/
showLicenseWatermark = false;
options = {
shapes: this.shapes,
connections: this.connections,
selectable: this.selectable,
editable: this.editable,
zoom: this.zoom,
zoomMax: this.zoomMax,
zoomMin: this.zoomMin,
zoomRate: this.zoomRate,
shapeDefaults: this.shapeDefaults,
connectionDefaults: this.connectionDefaults,
layout: this.layout,
};
constructor(wrapperElement, renderer, zone) {
this.wrapperElement = wrapperElement;
this.renderer = renderer;
this.zone = zone;
const isValid = validatePackage(packageMetadata);
this.showLicenseWatermark = shouldShowValidationUI(isValid);
}
ngOnChanges(changes) {
let recreate = false;
if (changes['shapes']) {
this.options.shapes = this.shapes;
recreate = true;
}
if (changes['connections']) {
this.options.connections = this.connections;
recreate = true;
}
if (changes['connectionDefaults']) {
this.options.connectionDefaults = { ...this.options.connectionDefaults, ...this.connectionDefaults };
recreate = true;
}
if (changes['shapeDefaults']) {
this.options.shapeDefaults = { ...this.options.shapeDefaults, ...this.shapeDefaults };
recreate = true;
}
if (changes['editable'] && this.editable || typeof this.editable === 'boolean') {
if (typeof this.editable === 'boolean') {
if (this.editable) {
this.options.editable = { drag: true, resize: true, remove: true };
this.options.shapeDefaults.editable = { drag: true, remove: true };
}
else {
this.options.editable = { drag: false, resize: false, remove: false };
this.options.shapeDefaults.editable = { drag: false, remove: false };
}
}
if (typeof this.editable === 'object') {
this.options.editable = {
drag: this.editable.drag,
resize: this.editable.resize,
remove: this.editable.remove
};
this.options.shapeDefaults.editable = { drag: this.editable.drag, remove: this.editable.remove };
}
recreate = true;
}
if (changes['zoomMax']) {
this.updateOptions('zoomMax');
}
if (changes['zoomMin']) {
this.updateOptions('zoomMin');
}
if (changes['zoomRate']) {
this.updateOptions('zoomRate');
}
if (changes['selectable']) {
this.updateOptions('selectable');
}
if (changes['layout']) {
this.updateOptions('layout');
this.diagram?.layout(this.options.layout);
}
if (changes['pannable']) {
this.updateOptions('pannable');
}
if (changes['zoom']) {
this.updateOptions('zoom');
this.diagram?.zoom(this.diagram.options.zoom);
}
if (recreate) {
this.init();
}
}
ngAfterViewInit() {
this.renderer.setStyle(this.wrapperElement.nativeElement, 'display', 'block');
this.init();
events.forEach((eventName) => {
this.diagram.bind(eventName, (e) => {
if (eventName === 'click') {
eventName = 'diagramClick';
}
if (eventName === 'select') {
eventName = 'onSelect';
}
if (eventName === 'pan') {
eventName = 'onPan';
}
if (eventName === 'itemBoundsChange') {
eventName = 'shapeBoundsChange';
}
const emitter = this.activeEmitter(eventName);
if (emitter) {
this.zone.run(() => {
emitter.emit(e);
});
}
});
});
}
ngOnDestroy() {
this.diagram?.destroy();
}
/**
* Provides the current Diagram's shapes and connections that can be used to create a new Diagram when needed.
* @returns {DiagramState} Object containing shapes and connections arrays.
*/
getState() {
return this.diagram?.save();
}
/**
* Focuses the Diagram.
* @returns {boolean} true if focus was set successfully.
*/
focus() {
return this.diagram?.focus();
}
/**
* Clears the Diagram.
*/
clear() {
this.diagram?.clear();
}
/**
* Determines whether two shapes are connected.
* @param {Shape} Shape.
* @param {Shape} Shape.
* @returns {boolean} true if the two shapes are connected.
*/
connected(source, target) {
return this.diagram?.connected(source, target);
}
/**
* Adds connection to the Diagram.
* @param {Connection} Connection.
* @param {boolean} Boolean.
* @returns {Connection} The newly created connection.
*/
addConnection(connection, undoable) {
const newConnection = this.diagram?.addConnection(connection, undoable);
return newConnection;
}
/**
* Adds shape to the Diagram.
* @param {ShapeOptions | Shape | Point} If you pass a `Point`, a new Shape with default options will be created and positioned at that point.
* @param {boolean} Boolean indicating if the action should be undoable.
* @returns {Shape} The newly created shape.
*/
addShape(item, undoable) {
const newShape = this.diagram?.addShape(item, undoable);
return newShape;
}
/**
* Removes shape(s) and/or connection(s) from the Diagram.
* @param {Shape | Connection | (Shape | Connection)[]} Shape, Connection or an Array of Shapes and/or Connections.
* @param {Boolean} Boolean indicating if the action should be undoable.
*/
remove(items, undoable) {
this.diagram?.remove(items, undoable);
}
/**
* Connects two items in the Diagram.
* @param {Shape | Connector | Point} Shape, Shape's Connector or Point.
* @param {Shape | Connector | Point} Shape, Shape's Connector or Point.
* @param {ConnectionOptions} Connection options.
* @returns {Connection} The created connection.
*/
connect(source, target, options) {
return this.diagram?.connect(source, target, options);
}
/**
* Executes the next undoable action on top of the undo stack if any.
*/
undo() {
this.diagram?.undo();
}
/**
* Executes the previous undoable action on top of the redo stack if any.
*/
redo() {
this.diagram?.redo();
}
/**
* Selects items on the basis of the given input.
* @param {Shape | Connection | (Shape | Connection)[]} Shape, Connection or an Array of Shapes and/or Connections.
* @param Selection options.
* @returns {(Shape | Connection)[]} Array of selected items.
*/
select(items, options) {
return this.diagram?.select(items, options);
}
/**
* Selects all items in the Diagram.
*/
selectAll() {
this.diagram?.selectAll();
}
/**
* Selects items in the specified area.
* @param {Rect} rect Rectangle area to select.
*/
selectArea(rect) {
this.diagram?.selectArea(rect);
}
/**
* Deselects the specified items or all items if no item is specified.
* @param {Shape | Connection | (Shape | Connection)[]} Shape, Connection or an Array of Shapes and/or Connections.
*/
deselect(items) {
this.diagram?.deselect(items);
}
/**
* Brings to front the passed items.
* @param {Shape | Connection | (Shape | Connection)[]} Shape, Connection or an Array of Shapes and/or Connections.
* @param {boolean} By default the action is undoable.
*/
bringToFront(items, undoable) {
this.diagram?.toFront(items, undoable);
}
/**
* Sends to back the passed items.
* @param {Shape | Connection | (Shape | Connection)[]} Shape, Connection or an Array of Shapes and/or Connections.
* @param {boolean} By default the action is undoable.
*/
bringToBack(items, undoable) {
this.diagram?.toBack(items, undoable);
}
/**
* Bring into view the passed item(s) or rectangle.
* @param {Shape | Connection | (Shape | Connection)[]} Shape, Connection or an Array of Shapes and/or Connections.
* @param {DiagramAlignOptions} controls the position of the calculated rectangle relative to the viewport.
* "Center middle" will position the items in the center. animate - controls if the pan should be animated.
*/
bringIntoView(item, options) {
this.diagram?.bringIntoView(item, options);
}
/**
* Aligns shapes in the specified direction.
* @param {Direction} Direction to align shapes.
*/
alignShapes(direction) {
this.diagram?.alignShapes(direction);
}
/**
* @hidden
* Pans the Diagram.
* @param {Point} Pan coordinates.
* @param {boolean} Whether to animate the pan.
* @returns {Point} Current pan position.
*/
pan(pan, animate) {
return this.diagram?.pan(pan, animate);
}
/**
* Gets the current `Diagram` viewport rectangle.
* @returns {Rect} Viewport rectangle.
*/
viewport() {
return this.diagram?.viewport();
}
/**
* Copies selected items to clipboard.
*/
copy() {
this.diagram?.copy();
}
/**
* @hidden
* Cuts selected items to clipboard.
*/
cut() {
this.diagram?.cut();
}
/**
* Pastes items from clipboard.
*/
paste() {
this.diagram?.paste();
}
/**
* Gets the bounding rectangle of the given items.
* @param {Shape | Connection | (Shape | Connection)[]} Shape, Connection or an Array of Shapes and/or Connections.
* @param {boolean} Pass 'true' if you need to get the bounding box of the shapes without their rotation offset.
* @returns {Rect} Bounding rectangle.
*/
boundingBox(items, origin) {
return this.diagram?.boundingBox(items, origin);
}
/**
* Converts document coordinates to view coordinates.
* @param {Point} Point in document coordinates.
* @returns {Point} Point in view coordinates.
*/
documentToView(point) {
return this.diagram?.documentToView(point);
}
/**
* Converts view coordinates to document coordinates.
* @param {Point} Point in view coordinates.
* @returns {Point} Point in document coordinates.
*/
viewToDocument(point) {
return this.diagram?.viewToDocument(point);
}
/**
* Converts view coordinates to model coordinates.
* @param {Point} Point in view coordinates.
* @returns {Point} Point in model coordinates.
*/
viewToModel(point) {
return this.diagram?.viewToModel(point);
}
/**
* Converts model coordinates to view coordinates.
* @param {Point} Point in model coordinates.
* @returns {Point} Point in view coordinates.
*/
modelToView(point) {
return this.diagram?.modelToView(point);
}
/**
* Converts model coordinates to layer coordinates.
* @param {Point} Point in model coordinates.
* @returns {Point} Point in layer coordinates.
*/
modelToLayer(point) {
return this.diagram?.modelToLayer(point);
}
/**
* Converts layer coordinates to model coordinates.
* @param {Point} Point in layer coordinates.
* @returns {Point} Point in model coordinates.
*/
layerToModel(point) {
return this.diagram?.layerToModel(point);
}
/**
* Converts document coordinates to model coordinates.
* @param {Point} Point in document coordinates.
* @returns {Point} Point in model coordinates.
*/
documentToModel(point) {
return this.diagram?.documentToModel(point);
}
/**
* Converts model coordinates to document coordinates.
* @param {Point} Point in model coordinates.
* @returns {Point} Point in document coordinates.
*/
modelToDocument(point) {
return this.diagram?.modelToDocument(point);
}
/**
* Gets a shape on the basis of its identifier.
* @param {string} The identifier of a shape.
* @returns {Shape} The shape with the specified ID.
*/
getShapeById(id) {
return this.diagram?.getShapeById(id);
}
/**
* Exports the diagram's DOM visual representation for rendering or export purposes.
* Creates a clipped group containing the canvas content with proper transformations.
* @returns {Group} A drawing Group element containing the exported DOM visual
*/
exportDOMVisual() {
return this.diagram?.exportDOMVisual();
}
/**
* Exports the diagram's visual representation with proper scaling based on zoom level.
* Creates a scaled group containing the main layer content.
* @returns {Group} A drawing Group element containing the exported visual with inverse zoom scaling
*/
exportVisual() {
return this.diagram?.exportVisual();
}
activeEmitter(name) {
const emitter = this[name];
if (emitter?.emit && hasObservers(emitter)) {
return emitter;
}
}
init() {
if (this.diagram) {
this.diagram.destroy();
}
this.zone.runOutsideAngular(() => {
const theme = loadTheme(this.wrapperElement.nativeElement);
this.diagram = new Diagram(this.wrapperElement.nativeElement, this.options, theme);
this.diagram._createOptionElements();
this.diagram.zoom(this.diagram.options.zoom);
this.diagram.canvas.draw();
this.diagram.bringIntoView(this.diagram.shapes);
});
}
updateOptions(prop) {
this.options[prop] = this[prop];
this.diagram?.setOptions(this.options);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DiagramComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: DiagramComponent, isStandalone: true, selector: "kendo-diagram", inputs: { connectionDefaults: "connectionDefaults", connections: "connections", editable: "editable", layout: "layout", pannable: "pannable", selectable: "selectable", shapeDefaults: "shapeDefaults", shapes: "shapes", zoom: "zoom", zoomMax: "zoomMax", zoomMin: "zoomMin", zoomRate: "zoomRate" }, outputs: { change: "change", diagramClick: "diagramClick", drag: "drag", dragEnd: "dragEnd", dragStart: "dragStart", shapeBoundsChange: "shapeBoundsChange", mouseEnter: "mouseEnter", mouseLeave: "mouseLeave", onPan: "pan", onSelect: "select", zoomEnd: "zoomEnd", zoomStart: "zoomStart" }, host: { properties: { "class.k-diagram": "this.diagramClass" } }, exportAs: ["kendoDiagram"], usesOnChanges: true, ngImport: i0, template: `
<div kendoWatermarkOverlay *ngIf="showLicenseWatermark"></div>
`, isInline: true, dependencies: [{ kind: "component", type: WatermarkOverlayComponent, selector: "div[kendoWatermarkOverlay]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DiagramComponent, decorators: [{
type: Component,
args: [{
standalone: true,
exportAs: 'kendoDiagram',
selector: 'kendo-diagram',
template: `
<div kendoWatermarkOverlay *ngIf="showLicenseWatermark"></div>
`,
imports: [WatermarkOverlayComponent, NgIf]
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.Renderer2 }, { type: i0.NgZone }]; }, propDecorators: { diagramClass: [{
type: HostBinding,
args: ['class.k-diagram']
}], connectionDefaults: [{
type: Input
}], connections: [{
type: Input
}], editable: [{
type: Input
}], layout: [{
type: Input
}], pannable: [{
type: Input
}], selectable: [{
type: Input
}], shapeDefaults: [{
type: Input
}], shapes: [{
type: Input
}], zoom: [{
type: Input
}], zoomMax: [{
type: Input
}], zoomMin: [{
type: Input
}], zoomRate: [{
type: Input
}], change: [{
type: Output
}], diagramClick: [{
type: Output
}], drag: [{
type: Output
}], dragEnd: [{
type: Output
}], dragStart: [{
type: Output
}], shapeBoundsChange: [{
type: Output
}], mouseEnter: [{
type: Output
}], mouseLeave: [{
type: Output
}], onPan: [{
type: Output,
args: ['pan']
}], onSelect: [{
type: Output,
args: ['select']
}], zoomEnd: [{
type: Output
}], zoomStart: [{
type: Output
}] } });