ng-zorro-antd
Version:
An enterprise-class UI components based on Ant Design and Angular
1,191 lines (1,176 loc) • 65 kB
JavaScript
import { SelectionModel } from '@angular/cdk/collections';
import { BehaviorSubject, merge, Subject, fromEvent, ReplaySubject, forkJoin } from 'rxjs';
import { map, take, filter, takeUntil, finalize } from 'rxjs/operators';
import * as i6 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { Component, ChangeDetectionStrategy, Input, Directive, EventEmitter, Output, TemplateRef, ElementRef, ViewEncapsulation, Host, Optional, ViewChildren, ContentChild, NgModule } from '@angular/core';
import * as i1$1 from 'ng-zorro-antd/core/no-animation';
import { NzNoAnimationModule } from 'ng-zorro-antd/core/no-animation';
import { NzIconModule } from 'ng-zorro-antd/icon';
import { NzSpinModule } from 'ng-zorro-antd/spin';
import { line, curveLinear, curveBasis } from 'd3-shape';
import { drag } from 'd3-drag';
import { select, pointer } from 'd3-selection';
import { zoomIdentity, zoom, zoomTransform } from 'd3-zoom';
import { reqAnimFrame, cancelRequestAnimationFrame } from 'ng-zorro-antd/core/polyfill';
import { __decorate } from 'tslib';
import * as i1 from '@angular/animations';
import { style, query, group, animate } from '@angular/animations';
import { InputBoolean } from 'ng-zorro-antd/core/util';
import { transition } from 'd3-transition';
import { buildGraph } from 'dagre-compound';
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
var NzGraphEdgeType;
(function (NzGraphEdgeType) {
NzGraphEdgeType["LINE"] = "line";
NzGraphEdgeType["CURVE"] = "curve";
})(NzGraphEdgeType || (NzGraphEdgeType = {}));
function nzTypeDefinition() {
return item => item;
}
const NZ_GRAPH_LAYOUT_SETTING = {
graph: {
meta: {
nodeSep: 50,
rankSep: 50,
edgeSep: 5
}
},
subScene: {
meta: {
paddingTop: 20,
paddingBottom: 20,
paddingLeft: 20,
paddingRight: 20,
labelHeight: 20
}
},
nodeSize: {
meta: {
width: 50,
maxLabelWidth: 0,
height: 50
},
node: {
width: 50,
height: 50,
labelOffset: 10,
maxLabelWidth: 40
},
bridge: {
width: 5,
height: 5,
radius: 2,
labelOffset: 0
}
}
};
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
class NzGraphData {
constructor(source) {
this._data = new BehaviorSubject({});
/** A selection model with multi-selection to track expansion status. */
this.expansionModel = new SelectionModel(true);
if (source) {
this.expansionModel?.clear();
this.dataSource = source;
this._data.next(source);
}
}
/** Toggles one single data node's expanded/collapsed state. */
toggle(nodeName) {
this.expansionModel.toggle(nodeName);
}
/** Expands one single data node. */
expand(nodeName) {
const compound = this.dataSource.compound || {};
const toBeSelected = this.findParents(compound, nodeName, [nodeName]);
this.expansionModel.select(...toBeSelected);
}
/** Collapses one single data node. */
collapse(nodeName) {
const compound = this.dataSource.compound || {};
const toBeDeselected = this.findChildren(compound, nodeName, [nodeName]);
this.expansionModel.deselect(...toBeDeselected);
}
/** Whether a given data node is expanded or not. Returns true if the data node is expanded. */
isExpanded(nodeName) {
return this.expansionModel.isSelected(nodeName);
}
/** Collapse all dataNodes in the tree. */
collapseAll() {
this.expansionModel.clear();
}
expandAll() {
this.expansionModel.select(...Object.keys(this._data.value.compound || {}));
}
setData(data) {
this.expansionModel?.clear();
this.dataSource = data;
this._data.next(data);
}
connect() {
const changes = [this._data, this.expansionModel.changed];
return merge(...changes).pipe(map(() => this._data.value));
}
disconnect() {
// do nothing for now
}
findParents(data, key, parents = []) {
const parent = Object.keys(data)
.filter(d => d !== key)
.find(d => data[d].includes(key));
if (!parent) {
return parents;
}
else {
return this.findParents(data, parent, [parent, ...parents]);
}
}
findChildren(data, key, children = []) {
const groupIds = Object.keys(data);
const child = (data[key] || []).filter((c) => groupIds.includes(c));
if (child && child.length > 0) {
return child.reduce((pre, cur) => Array.from(new Set([...pre, ...this.findChildren(data, cur, [...children, cur])])), children);
}
return children;
}
}
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
class NzGraphDefsComponent {
constructor() { }
}
NzGraphDefsComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphDefsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
NzGraphDefsComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.2.5", type: NzGraphDefsComponent, selector: "svg:defs[nz-graph-defs]", ngImport: i0, template: `
<svg:marker
class="nz-graph-edge-marker"
id="edge-end-arrow"
viewBox="1 0 20 20"
refX="8"
refY="3.5"
markerWidth="10"
markerHeight="10"
orient="auto"
>
<svg:polygon points="0 0, 10 3.5, 0 7"></svg:polygon>
</svg:marker>
`, isInline: true });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphDefsComponent, decorators: [{
type: Component,
args: [{
selector: 'svg:defs[nz-graph-defs]',
template: `
<svg:marker
class="nz-graph-edge-marker"
id="edge-end-arrow"
viewBox="1 0 20 20"
refX="8"
refY="3.5"
markerWidth="10"
markerHeight="10"
orient="auto"
>
<svg:polygon points="0 0, 10 3.5, 0 7"></svg:polygon>
</svg:marker>
`
}]
}], ctorParameters: function () { return []; } });
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
class NzGraphEdgeComponent {
constructor(elementRef, ngZone, cdr) {
this.elementRef = elementRef;
this.ngZone = ngZone;
this.cdr = cdr;
this.line = line()
.x(d => d.x)
.y(d => d.y)
.curve(curveLinear);
this.el = this.elementRef.nativeElement;
}
get id() {
return this.edge?.id || `${this.edge.v}--${this.edge.w}`;
}
ngOnInit() {
this.initElementStyle();
}
ngOnChanges(changes) {
const { edge, customTemplate, edgeType } = changes;
if (edge) {
this.ngZone.onStable.pipe(take(1)).subscribe(() => {
// Update path element if customTemplate set
if (customTemplate) {
this.initElementStyle();
}
this.setLine();
this.cdr.markForCheck();
});
}
if (edgeType) {
const type = this.edgeType === NzGraphEdgeType.LINE ? curveLinear : curveBasis;
this.line = line()
.x(d => d.x)
.y(d => d.y)
.curve(type);
}
}
initElementStyle() {
this.path = this.el.querySelector('path');
this.setElementData();
}
setLine() {
this.setPath(this.line(this.edge.points));
}
setPath(d) {
this.path.setAttribute('d', d);
}
setElementData() {
if (!this.path) {
return;
}
this.path.setAttribute('id', this.id);
this.path.setAttribute('data-edge', this.id);
this.path.setAttribute('data-v', `${this.edge.v}`);
this.path.setAttribute('data-w', `${this.edge.w}`);
}
}
NzGraphEdgeComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphEdgeComponent, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
NzGraphEdgeComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.2.5", type: NzGraphEdgeComponent, selector: "[nz-graph-edge]", inputs: { edge: "edge", edgeType: "edgeType", customTemplate: "customTemplate" }, usesOnChanges: true, ngImport: i0, template: `
<ng-container
*ngIf="customTemplate"
[ngTemplateOutlet]="customTemplate"
[ngTemplateOutletContext]="{ $implicit: edge }"
></ng-container>
<svg:g *ngIf="!customTemplate">
<path class="nz-graph-edge-line" [attr.marker-end]="'url(#edge-end-arrow)'"></path>
<svg:text class="nz-graph-edge-text" text-anchor="middle" dy="10" *ngIf="edge.label">
<textPath [attr.href]="'#' + id" startOffset="50%">{{ edge.label }}</textPath>
</svg:text>
</svg:g>
`, isInline: true, directives: [{ type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i6.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphEdgeComponent, decorators: [{
type: Component,
args: [{
selector: '[nz-graph-edge]',
template: `
<ng-container
*ngIf="customTemplate"
[ngTemplateOutlet]="customTemplate"
[ngTemplateOutletContext]="{ $implicit: edge }"
></ng-container>
<svg:g *ngIf="!customTemplate">
<path class="nz-graph-edge-line" [attr.marker-end]="'url(#edge-end-arrow)'"></path>
<svg:text class="nz-graph-edge-text" text-anchor="middle" dy="10" *ngIf="edge.label">
<textPath [attr.href]="'#' + id" startOffset="50%">{{ edge.label }}</textPath>
</svg:text>
</svg:g>
`,
changeDetection: ChangeDetectionStrategy.OnPush
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.NgZone }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { edge: [{
type: Input
}], edgeType: [{
type: Input
}], customTemplate: [{
type: Input
}] } });
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
class NzGraphEdgeDirective {
}
NzGraphEdgeDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphEdgeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
NzGraphEdgeDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.2.5", type: NzGraphEdgeDirective, selector: "[nzGraphEdge]", exportAs: ["nzGraphEdge"], ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphEdgeDirective, decorators: [{
type: Directive,
args: [{
selector: '[nzGraphEdge]',
exportAs: 'nzGraphEdge'
}]
}] });
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
class NzGraphGroupNodeDirective {
}
NzGraphGroupNodeDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphGroupNodeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
NzGraphGroupNodeDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.2.5", type: NzGraphGroupNodeDirective, selector: "[nzGraphGroupNode]", exportAs: ["nzGraphGroupNode"], ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphGroupNodeDirective, decorators: [{
type: Directive,
args: [{
selector: '[nzGraphGroupNode]',
exportAs: 'nzGraphGroupNode'
}]
}] });
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
const FRAC_VIEWPOINT_AREA = 0.8;
class Minimap {
constructor(ngZone, svg, zoomG, mainZoom, minimap, maxWidth, labelPadding) {
this.ngZone = ngZone;
this.svg = svg;
this.zoomG = zoomG;
this.mainZoom = mainZoom;
this.minimap = minimap;
this.maxWidth = maxWidth;
this.labelPadding = labelPadding;
this.unlisteners = [];
const minimapElement = select(minimap);
const minimapSvgElement = minimapElement.select('svg');
const viewpointElement = minimapSvgElement.select('rect');
this.canvas = minimapElement.select('canvas.viewport').node();
this.canvasRect = this.canvas.getBoundingClientRect();
const handleEvent = (event) => {
const minimapOffset = this.minimapOffset();
const width = Number(viewpointElement.attr('width'));
const height = Number(viewpointElement.attr('height'));
const clickCoords = pointer(event, minimapSvgElement.node());
this.viewpointCoord.x = clickCoords[0] - width / 2 - minimapOffset.x;
this.viewpointCoord.y = clickCoords[1] - height / 2 - minimapOffset.y;
this.updateViewpoint();
};
this.viewpointCoord = { x: 0, y: 0 };
const subject = drag().subject(Object);
const dragEvent = subject.on('drag', handleEvent);
viewpointElement.datum(this.viewpointCoord).call(dragEvent);
// Make the minimap clickable.
minimapSvgElement.on('click', event => {
if (event.defaultPrevented) {
// This click was part of a drag event, so suppress it.
return;
}
handleEvent(event);
});
this.unlisteners.push(() => {
subject.on('drag', null);
minimapSvgElement.on('click', null);
});
this.viewpoint = viewpointElement.node();
this.minimapSvg = minimapSvgElement.node();
this.canvasBuffer = minimapElement.select('canvas.buffer').node();
this.update();
}
destroy() {
while (this.unlisteners.length) {
this.unlisteners.pop()();
}
}
minimapOffset() {
return {
x: (this.canvasRect.width - this.minimapSize.width) / 2,
y: (this.canvasRect.height - this.minimapSize.height) / 2
};
}
updateViewpoint() {
// Update the coordinates of the viewpoint rectangle.
select(this.viewpoint).attr('x', this.viewpointCoord.x).attr('y', this.viewpointCoord.y);
// Update the translation vector of the main svg to reflect the
// new viewpoint.
const mainX = (-this.viewpointCoord.x * this.scaleMain) / this.scaleMinimap;
const mainY = (-this.viewpointCoord.y * this.scaleMain) / this.scaleMinimap;
select(this.svg).call(this.mainZoom.transform, zoomIdentity.translate(mainX, mainY).scale(this.scaleMain));
}
update() {
let sceneSize = null;
try {
// Get the size of the entire scene.
sceneSize = this.zoomG.getBBox();
if (sceneSize.width === 0) {
// There is no scene anymore. We have been detached from the dom.
return;
}
}
catch (e) {
// Firefox produced NS_ERROR_FAILURE if we have been
// detached from the dom.
return;
}
const svgSelection = select(this.svg);
// Read all the style rules in the document and embed them into the svg.
// The svg needs to be self contained, i.e. all the style rules need to be
// embedded so the canvas output matches the origin.
let stylesText = '';
for (const k of new Array(document.styleSheets.length).keys()) {
try {
const cssRules = document.styleSheets[k].cssRules || document.styleSheets[k].rules;
if (cssRules == null) {
continue;
}
for (const i of new Array(cssRules.length).keys()) {
// Remove tf-* selectors from the styles.
stylesText += `${cssRules[i].cssText.replace(/ ?tf-[\w-]+ ?/g, '')}\n`;
}
}
catch (e) {
if (e.name !== 'SecurityError') {
throw e;
}
}
}
// Temporarily add the css rules to the main svg.
const svgStyle = svgSelection.append('style');
svgStyle.text(stylesText);
// Temporarily remove the zoom/pan transform from the main svg since we
// want the minimap to show a zoomed-out and centered view.
const zoomGSelection = select(this.zoomG);
const zoomTransform = zoomGSelection.attr('transform');
zoomGSelection.attr('transform', null);
// Since we add padding, account for that here.
sceneSize.height += this.labelPadding * 2;
sceneSize.width += this.labelPadding * 2;
// Temporarily assign an explicit width/height to the main svg, since
// it doesn't have one (uses flex-box), but we need it for the canvas
// to work.
svgSelection.attr('width', sceneSize.width).attr('height', sceneSize.height);
// Since the content inside the svg changed (e.g. a node was expanded),
// the aspect ratio have also changed. Thus, we need to update the scale
// factor of the minimap. The scale factor is determined such that both
// the width and height of the minimap are <= maximum specified w/h.
this.scaleMinimap = this.maxWidth / Math.max(sceneSize.width, sceneSize.height);
this.minimapSize = {
width: sceneSize.width * this.scaleMinimap,
height: sceneSize.height * this.scaleMinimap
};
const minimapOffset = this.minimapOffset();
// Update the size of the minimap's svg, the buffer canvas and the
// viewpoint rect.
select(this.minimapSvg).attr(this.minimapSize);
select(this.canvasBuffer).attr(this.minimapSize);
if (this.translate != null && this.zoom != null) {
// Update the viewpoint rectangle shape since the aspect ratio of the
// map has changed.
this.ngZone.runOutsideAngular(() => reqAnimFrame(() => this.zoom()));
}
// Serialize the main svg to a string which will be used as the rendering
// content for the canvas.
const svgXml = new XMLSerializer().serializeToString(this.svg);
// Now that the svg is serialized for rendering, remove the temporarily
// assigned styles, explicit width and height and bring back the pan/zoom
// transform.
svgStyle.remove();
svgSelection.attr('width', '100%').attr('height', '100%');
zoomGSelection.attr('transform', zoomTransform);
const image = document.createElement('img');
const onLoad = () => {
// Draw the svg content onto the buffer canvas.
const context = this.canvasBuffer.getContext('2d');
context.clearRect(0, 0, this.canvasBuffer.width, this.canvasBuffer.height);
context.drawImage(image, minimapOffset.x, minimapOffset.y, this.minimapSize.width, this.minimapSize.height);
this.ngZone.runOutsideAngular(() => {
reqAnimFrame(() => {
// Hide the old canvas and show the new buffer canvas.
select(this.canvasBuffer).style('display', 'block');
select(this.canvas).style('display', 'none');
// Swap the two canvases.
[this.canvas, this.canvasBuffer] = [this.canvasBuffer, this.canvas];
});
});
};
image.addEventListener('load', onLoad);
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
this.unlisteners.push(() => {
image.removeEventListener('load', onLoad);
});
}
/**
* Handles changes in zooming/panning. Should be called from the main svg
* to notify that a zoom/pan was performed and this minimap will update it's
* viewpoint rectangle.
*
* @param transform
*/
zoom(transform) {
if (this.scaleMinimap == null) {
// Scene is not ready yet.
return;
}
// Update the new translate and scale params, only if specified.
if (transform) {
this.translate = [transform.x, transform.y];
this.scaleMain = transform.k;
}
// Update the location of the viewpoint rectangle.
const svgRect = this.svg.getBoundingClientRect();
const minimapOffset = this.minimapOffset();
const viewpointSelection = select(this.viewpoint);
this.viewpointCoord.x = (-this.translate[0] * this.scaleMinimap) / this.scaleMain;
this.viewpointCoord.y = (-this.translate[1] * this.scaleMinimap) / this.scaleMain;
const viewpointWidth = (svgRect.width * this.scaleMinimap) / this.scaleMain;
const viewpointHeight = (svgRect.height * this.scaleMinimap) / this.scaleMain;
viewpointSelection
.attr('x', this.viewpointCoord.x + minimapOffset.x)
.attr('y', this.viewpointCoord.y + minimapOffset.y)
.attr('width', viewpointWidth)
.attr('height', viewpointHeight);
// Show/hide the minimap depending on the viewpoint area as fraction of the
// whole minimap.
const mapWidth = this.minimapSize.width;
const mapHeight = this.minimapSize.height;
const x = this.viewpointCoord.x;
const y = this.viewpointCoord.y;
const w = Math.min(Math.max(0, x + viewpointWidth), mapWidth) - Math.min(Math.max(0, x), mapWidth);
const h = Math.min(Math.max(0, y + viewpointHeight), mapHeight) - Math.min(Math.max(0, y), mapHeight);
const fracIntersect = (w * h) / (mapWidth * mapHeight);
if (fracIntersect < FRAC_VIEWPOINT_AREA) {
this.minimap.classList.remove('hidden');
}
else {
this.minimap.classList.add('hidden');
}
}
}
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
class NzGraphMinimapComponent {
constructor(elementRef, ngZone) {
this.elementRef = elementRef;
this.ngZone = ngZone;
}
ngOnDestroy() {
this.minimap?.destroy();
}
init(containerEle, zoomBehavior) {
const svgEle = containerEle.nativeElement.querySelector('svg');
const zoomEle = containerEle.nativeElement.querySelector('svg > g');
this.minimap = new Minimap(this.ngZone, svgEle, zoomEle, zoomBehavior, this.elementRef.nativeElement, 150, 0);
}
zoom(transform) {
this.minimap?.zoom(transform);
}
update() {
this.minimap?.update();
}
}
NzGraphMinimapComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphMinimapComponent, deps: [{ token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
NzGraphMinimapComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.2.5", type: NzGraphMinimapComponent, selector: "nz-graph-minimap", host: { properties: { "class.nz-graph-minimap": "true" } }, ngImport: i0, template: `
<svg>
<defs>
<filter id="minimapDropShadow" x="-20%" y="-20%" width="150%" height="150%">
<feOffset result="offOut" in="SourceGraphic" dx="1" dy="1"></feOffset>
<feColorMatrix
result="matrixOut"
in="offOut"
type="matrix"
values="0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.5 0"
></feColorMatrix>
<feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="2"></feGaussianBlur>
<feBlend in="SourceGraphic" in2="blurOut" mode="normal"></feBlend>
</filter>
</defs>
<rect></rect>
</svg>
<canvas class="viewport"></canvas>
<!-- Additional canvas to use as buffer to avoid flickering between updates -->
<canvas class="buffer"></canvas>
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphMinimapComponent, decorators: [{
type: Component,
args: [{
selector: 'nz-graph-minimap',
template: `
<svg>
<defs>
<filter id="minimapDropShadow" x="-20%" y="-20%" width="150%" height="150%">
<feOffset result="offOut" in="SourceGraphic" dx="1" dy="1"></feOffset>
<feColorMatrix
result="matrixOut"
in="offOut"
type="matrix"
values="0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.5 0"
></feColorMatrix>
<feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="2"></feGaussianBlur>
<feBlend in="SourceGraphic" in2="blurOut" mode="normal"></feBlend>
</filter>
</defs>
<rect></rect>
</svg>
<canvas class="viewport"></canvas>
<!-- Additional canvas to use as buffer to avoid flickering between updates -->
<canvas class="buffer"></canvas>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'[class.nz-graph-minimap]': 'true'
}
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.NgZone }]; } });
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
/**
* https://angular.io/errors/NG3003
* An intermediate interface for {@link NzGraphComponent} & {@link NzGraphNodeComponent}
*/
class NzGraph {
}
class NzGraphNodeComponent {
constructor(ngZone, el, builder, renderer2, graphComponent) {
this.ngZone = ngZone;
this.el = el;
this.builder = builder;
this.renderer2 = renderer2;
this.graphComponent = graphComponent;
this.animationInfo = null;
this.initialState = true;
this.destroy$ = new Subject();
this.animationPlayer = null;
}
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
fromEvent(this.el.nativeElement, 'click')
.pipe(filter(event => {
event.preventDefault();
return this.graphComponent.nzNodeClick.observers.length > 0;
}), takeUntil(this.destroy$))
.subscribe(() => {
// Re-enter the Angular zone and run the change detection only if there're any `nzNodeClick` listeners,
// e.g.: `<nz-graph (nzNodeClick)="..."></nz-graph>`.
this.ngZone.run(() => this.graphComponent.nzNodeClick.emit(this.node));
});
});
}
ngOnDestroy() {
this.destroy$.next();
}
makeAnimation() {
const cur = this.getAnimationInfo();
if (this.animationPlayer) {
this.animationPlayer.destroy();
}
let animationFactory;
const pre = { ...this.animationInfo };
if (this.initialState) {
animationFactory = this.builder.build([
style({ transform: `translate(${cur.x}px, ${cur.y}px)` }),
query('g', [
style({
width: `${cur.width}px`,
height: `${cur.height}px`
})
])
]);
this.initialState = false;
}
else {
animationFactory = this.builder.build([
style({ transform: `translate(${pre.x}px, ${pre.y}px)` }),
query('g', [
style({
width: `${pre.width}px`,
height: `${pre.height}px`
})
]),
group([
query('g', [
animate('150ms ease-out', style({
width: `${cur.width}px`,
height: `${cur.height}px`
}))
]),
animate('150ms ease-out', style({ transform: `translate(${cur.x}px, ${cur.y}px)` }))
])
]);
}
this.animationInfo = cur;
this.animationPlayer = animationFactory.create(this.el.nativeElement);
this.animationPlayer.play();
const done$ = new Subject();
this.animationPlayer.onDone(() => {
// Need this for canvas for now.
this.renderer2.setAttribute(this.el.nativeElement, 'transform', `translate(${cur.x}, ${cur.y})`);
done$.next();
done$.complete();
});
return done$.asObservable();
}
makeNoAnimation() {
const cur = this.getAnimationInfo();
// Need this for canvas for now.
this.renderer2.setAttribute(this.el.nativeElement, 'transform', `translate(${cur.x}, ${cur.y})`);
}
getAnimationInfo() {
const { x, y } = this.nodeTransform();
return {
width: this.node.width,
height: this.node.height,
x,
y
};
}
nodeTransform() {
const x = this.computeCXPositionOfNodeShape() - this.node.width / 2;
const y = this.node.y - this.node.height / 2;
return { x, y };
}
computeCXPositionOfNodeShape() {
if (this.node.expanded) {
return this.node.x;
}
return this.node.x - this.node.width / 2 + this.node.coreBox.width / 2;
}
}
NzGraphNodeComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphNodeComponent, deps: [{ token: i0.NgZone }, { token: i0.ElementRef }, { token: i1.AnimationBuilder }, { token: i0.Renderer2 }, { token: NzGraph }], target: i0.ɵɵFactoryTarget.Component });
NzGraphNodeComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.2.5", type: NzGraphNodeComponent, selector: "[nz-graph-node]", inputs: { node: "node", noAnimation: "noAnimation", customTemplate: "customTemplate" }, host: { properties: { "id": "node.id || node.name", "class.nz-graph-node-expanded": "node.expanded", "class.nz-graph-group-node": "node.type===0", "class.nz-graph-base-node": "node.type===1" } }, ngImport: i0, template: `
<svg:g>
<ng-container
*ngIf="customTemplate"
[ngTemplateOutlet]="customTemplate"
[ngTemplateOutletContext]="{ $implicit: node }"
></ng-container>
<ng-container *ngIf="!customTemplate">
<svg:rect class="nz-graph-node-rect" [attr.width]="node.width" [attr.height]="node.height"></svg:rect>
<svg:text x="10" y="20">{{ node.id || node.name }}</svg:text>
</ng-container>
</svg:g>
`, isInline: true, directives: [{ type: i6.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i6.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
__decorate([
InputBoolean()
], NzGraphNodeComponent.prototype, "noAnimation", void 0);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphNodeComponent, decorators: [{
type: Component,
args: [{
selector: '[nz-graph-node]',
template: `
<svg:g>
<ng-container
*ngIf="customTemplate"
[ngTemplateOutlet]="customTemplate"
[ngTemplateOutletContext]="{ $implicit: node }"
></ng-container>
<ng-container *ngIf="!customTemplate">
<svg:rect class="nz-graph-node-rect" [attr.width]="node.width" [attr.height]="node.height"></svg:rect>
<svg:text x="10" y="20">{{ node.id || node.name }}</svg:text>
</ng-container>
</svg:g>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'[id]': 'node.id || node.name',
'[class.nz-graph-node-expanded]': 'node.expanded',
'[class.nz-graph-group-node]': 'node.type===0',
'[class.nz-graph-base-node]': 'node.type===1'
}
}]
}], ctorParameters: function () { return [{ type: i0.NgZone }, { type: i0.ElementRef }, { type: i1.AnimationBuilder }, { type: i0.Renderer2 }, { type: NzGraph }]; }, propDecorators: { node: [{
type: Input
}], noAnimation: [{
type: Input
}], customTemplate: [{
type: Input
}] } });
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
class NzGraphNodeDirective {
}
NzGraphNodeDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphNodeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
NzGraphNodeDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.2.5", type: NzGraphNodeDirective, selector: "[nzGraphNode]", exportAs: ["nzGraphNode"], ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphNodeDirective, decorators: [{
type: Directive,
args: [{
selector: '[nzGraphNode]',
exportAs: 'nzGraphNode'
}]
}] });
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
/**
* Calculate position and scale
*
* @param containerEle
* @param targetEle
* @param scale: if scale is set, skip calculate scale value
*/
const calculateTransform = (containerEle, targetEle, scale) => {
const containerEleSize = containerEle.getBoundingClientRect();
const targetEleSize = targetEle.getBBox();
if (!targetEleSize.width) {
// There is no g element anymore.
return null;
}
// TODO
// leave some place when re-scale
const scaleUnit = (containerEleSize.width - 48) / containerEleSize.width;
const k = scale ||
Math.min(containerEleSize.width / targetEleSize.width, containerEleSize.height / targetEleSize.height, 1) *
scaleUnit;
const x = (containerEleSize.width - targetEleSize.width * k) / 2;
const y = (containerEleSize.height - targetEleSize.height * k) / 2;
return {
x,
y,
k
};
};
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
Selection.bind('transition', transition);
class NzGraphZoomDirective {
constructor(element, cdr) {
this.element = element;
this.cdr = cdr;
this.nzMinZoom = 0.1;
this.nzMaxZoom = 10;
this.nzTransformEvent = new EventEmitter();
this.nzZoomChange = new EventEmitter();
this.destroy$ = new Subject();
}
ngAfterViewInit() {
this.bind();
}
ngOnDestroy() {
this.unbind();
this.destroy$.next();
this.destroy$.complete();
}
bind() {
this.svgElement = this.element.nativeElement.querySelector('svg');
this.gZoomElement = this.element.nativeElement.querySelector('svg > g');
const { width, height } = this.element.nativeElement.getBoundingClientRect();
this.svgSelection = select(this.svgElement);
this.zoomBehavior = zoom()
.extent([
[0, 0],
[width, height]
])
.scaleExtent([this.nzMinZoom, this.nzMaxZoom])
.on('zoom', e => {
this.zoomed(e);
});
this.svgSelection.call(this.zoomBehavior, zoomIdentity.translate(0, 0).scale(this.nzZoom || 1));
// Init with nzZoom
this.reScale(0, this.nzZoom);
}
unbind() {
// Destroy listener
this.svgSelection?.interrupt().selectAll('*').interrupt();
if (this.zoomBehavior) {
this.zoomBehavior.on('end', null).on('zoom', null);
}
}
// Methods
fitCenter(duration = 0) {
this.reScale(duration);
}
focus(id, duration = 0) {
// Make sure this node is under SVG container
if (!this.svgElement.getElementById(`${id}`)) {
return;
}
const node = this.svgElement.getElementById(`${id}`);
const svgRect = this.svgElement.getBoundingClientRect();
const position = this.getRelativePositionInfo(node);
const svgTransform = zoomTransform(this.svgElement);
const centerX = (position.topLeft.x + position.bottomRight.x) / 2;
const centerY = (position.topLeft.y + position.bottomRight.y) / 2;
const dx = svgRect.left + svgRect.width / 2 - centerX;
const dy = svgRect.top + svgRect.height / 2 - centerY;
this.svgSelection
.transition()
.duration(duration)
.call(this.zoomBehavior.translateBy, dx / svgTransform.k, dy / svgTransform.k);
}
/**
* Handle zoom event
*
* @param transform
*/
zoomed({ transform }) {
const { x, y, k } = transform;
// Update g element transform
this.gZoomElement.setAttribute('transform', `translate(${x}, ${y})scale(${k})`);
this.nzZoom = k;
this.nzZoomChange.emit(this.nzZoom);
this.nzTransformEvent.emit(transform);
this.cdr.markForCheck();
}
/**
* Scale with zoom and duration
*
* @param duration
* @param scale
* @private
*/
reScale(duration, scale) {
const transform = calculateTransform(this.svgElement, this.gZoomElement, scale);
if (!transform) {
return;
}
const { x, y, k } = transform;
const zTransform = zoomIdentity.translate(x, y).scale(Math.max(k, this.nzMinZoom));
this.svgSelection
.transition()
.duration(duration)
.call(this.zoomBehavior.transform, zTransform)
.on('end.fitted', () => {
this.zoomBehavior.on('end.fitted', null);
});
}
getRelativePositionInfo(node) {
const nodeBox = node.getBBox();
const nodeCtm = node.getScreenCTM();
let pointTL = this.svgElement.createSVGPoint();
let pointBR = this.svgElement.createSVGPoint();
pointTL.x = nodeBox.x;
pointTL.y = nodeBox.y;
pointBR.x = nodeBox.x + nodeBox.width;
pointBR.y = nodeBox.y + nodeBox.height;
pointTL = pointTL.matrixTransform(nodeCtm);
pointBR = pointBR.matrixTransform(nodeCtm);
return {
topLeft: pointTL,
bottomRight: pointBR
};
}
}
NzGraphZoomDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphZoomDirective, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
NzGraphZoomDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.2.5", type: NzGraphZoomDirective, selector: "[nz-graph-zoom]", inputs: { nzZoom: "nzZoom", nzMinZoom: "nzMinZoom", nzMaxZoom: "nzMaxZoom" }, outputs: { nzTransformEvent: "nzTransformEvent", nzZoomChange: "nzZoomChange" }, exportAs: ["nzGraphZoom"], ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.2.5", ngImport: i0, type: NzGraphZoomDirective, decorators: [{
type: Directive,
args: [{
selector: '[nz-graph-zoom]',
exportAs: 'nzGraphZoom'
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { nzZoom: [{
type: Input
}], nzMinZoom: [{
type: Input
}], nzMaxZoom: [{
type: Input
}], nzTransformEvent: [{
type: Output
}], nzZoomChange: [{
type: Output
}] } });
/** Checks whether an object is a data source. */
function isDataSource(value) {
// Check if the value is a DataSource by observing if it has a connect function. Cannot
// be checked as an `instanceof DataSource` since people could create their own sources
// that match the interface, but don't extend DataSource.
return value && typeof value.connect === 'function';
}
class NzGraphComponent {
constructor(cdr, elementRef, noAnimation, nzGraphZoom) {
this.cdr = cdr;
this.elementRef = elementRef;
this.noAnimation = noAnimation;
this.nzGraphZoom = nzGraphZoom;
this.nzRankDirection = 'LR';
this.nzAutoSize = false;
this.nzGraphInitialized = new EventEmitter();
this.nzGraphRendered = new EventEmitter();
this.nzNodeClick = new EventEmitter();
this.requestId = -1;
this.transformStyle = '';
this.graphRenderedSubject$ = new ReplaySubject(1);
this.renderInfo = { labelHeight: 0 };
this.mapOfNodeAttr = {};
this.mapOfEdgeAttr = {};
this.zoom = 1;
this.typedNodes = nzTypeDefinition();
this.layoutSetting = NZ_GRAPH_LAYOUT_SETTING;
this.destroy$ = new Subject();
this.nodeTrackByFun = (_, node) => node.name;
this.edgeTrackByFun = (_, edge) => `${edge.v}-${edge.w}`;
this.subGraphTransform = (node) => {
const x = node.x - node.coreBox.width / 2.0;
const y = node.y - node.height / 2.0 + node.paddingTop;
return `translate(${x}, ${y})`;
};
this.$asNzGraphEdges = (data) => data;
this.coreTransform = (node) => `translate(0, ${node.parentNodeName ? node.labelHeight : 0})`;
}
ngOnInit() {
this.graphRenderedSubject$.pipe(take(1), takeUntil(this.destroy$)).subscribe(() => {
// Only zooming is not set, move graph to center
if (!this.nzGraphZoom) {
this.fitCenter();
}
this.nzGraphInitialized.emit(this);
});
}
ngOnChanges(changes) {
const { nzAutoFit, nzRankDirection, nzGraphData, nzGraphLayoutConfig } = changes;
if (nzGraphLayoutConfig) {
this.layoutSetting = this.mergeConfig(nzGraphLayoutConfig.currentValue);
}
if (nzGraphData) {
if (this.dataSource !== this.nzGraphData) {
this._switchDataSource(this.nzGraphData);
}
}
if ((nzAutoFit && !nzAutoFit.firstChange) || (nzRankDirection && !nzRankDirection.firstChange)) {
// Render graph
if (this.dataSource.dataSource) {
this.drawGraph(this.dataSource.dataSource, {
rankDirection: this.nzRankDirection,
expanded: this.dataSource.expansionModel.selected || []
}).then(() => {
this.cdr.markForCheck();
});
}
}
this.cdr.markForCheck();
}
ngAfterContentChecked() {
if (this.dataSource && !this._dataSubscription) {
this.observeRenderChanges();
}
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
if (this.dataSource && typeof this.dataSource.disconnect === 'function') {
this.dataSource.disconnect();
}
if (this._dataSubscription) {
this._dataSubscription.unsubscribe();
this._dataSubscription = null;
}
cancelRequestAnimationFrame(this.requestId);
}
/**
* Move graph to center and scale automatically
*/
fitCenter() {
const { x, y, k } = calculateTransform(this.elementRef.nativeElement.querySelector('svg'), this.elementRef.nativeElement.querySelector('svg > g'));
if (k) {
this.zoom = k;
this.transformStyle = `translate(${x}, ${y})scale(${k})`;
}
this.cdr.markForCheck();
}
/**
* re-Draw graph
*
* @param data
* @param options
* @param needResize
*/
drawGraph(data, options, needResize = false) {
return new Promise(resolve => {
this.requestId = requestAnimationFrame(() => {
const renderInfo = this.buildGraphInfo(data, options);
// TODO
// Need better performance
this.renderInfo = renderInfo;
this.cdr.markForCheck();
this.requestId = requestAnimationFrame(() => {
this.drawNodes(!this.noAnimation?.nzNoAnimation).then(() => {
// Update element
this.cdr.markForCheck();
if (needResize) {
this.resizeNodeSize().then(() => {
const dataSource = this.dataSource.dataSource;
this.drawGraph(dataSource, options, false).then(() => resolve());
});
}
else {
this.graphRenderedSubject$.next();
this.nzGraphRendered.emit(this);
resolve();
}
});
});
});
this.cdr.markForCheck();
});
}
/**
* Redraw all nodes
*
* @param animate
*/
drawNodes(animate = true) {
return new Promise(resolve => {
if (animate) {
this.makeNodesAnimation().subscribe(() => {
resolve();
});
}
else {
this.listOfNodeComponent.map(node => {
node.makeNoAnimation();
});
resolve();
}
});
}
resizeNodeSize() {
return new Promise(resolve => {
const dataSource = this.dataSource.dataSource;
let scale = this.nzGraphZoom?.nzZoom || this.zoom || 1;
this.listOfNodeElement.forEach(nodeEle => {
const contentEle = nodeEle.nativeElement;
if (contentEle) {
let width;
let height;
// Check if foreignObject is set
const clientRect = contentEle.querySelector('foreignObject > :first-child')?.getBoundingClientRect();
if (clientRect) {
width = clientRect.width;
height = clientRect.height;
}
else {
const bBoxRect = contentEle.getBBox();
width = bBoxRect.width;
height = bBoxRect.height;
// getBBox will return actual value
scale = 1;
}
// Element id type is string
const node = dataSource.nodes.find(n => `${n.id}` === nodeEle.nativeElement.id);
if (node && width && height) {
node.height = height / scale;
node.width = width / scale;
}
}
});
resolve();
});
}
/**
* Switch to the provided data source by resetting the data and unsubscribing from the current
* render change subscription if one exists. If the data source is null, interpret this by
* clearing the node outlet. Otherwise start listening for new data.
*/
_switchDataSource(dataSource) {
if (this.dataSource && typeof this.dataSource.disconnect === 'function') {
this.nzGraphData.disconnect();
}
if (this._dataSubscription) {
this._dataSubscription.unsubscribe();
this._dataSubscription = null;
}
this.dataSource = dataSource;
this.observeRenderChanges();
}
/** Set up a subscriptio