family-hierarchy
Version:
A small library for **Angular 9+** that implements [vis-network](https://github.com/visjs/vis-network) to generate a hierarchy chart of a family.
338 lines (330 loc) • 16.6 kB
JavaScript
import { __decorate } from 'tslib';
import { ɵɵdefineInjectable, Injectable, ViewChild, ContentChild, Input, Component, NgModule } from '@angular/core';
import { BehaviorSubject, Subject, Subscription } from 'rxjs';
import { DataSet, Network } from 'vis-network/standalone';
import { isObject } from 'util';
import { CommonModule } from '@angular/common';
const DEFAULT_NODE_IMAGE = 'data:image/png;base64,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';
const DEFAULT_UNION_IMAGE = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE4LjEuMSwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzEuODkxIDMxLjg5MSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzEuODkxIDMxLjg5MTsiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJPHBhdGggZD0iTTMwLjU0Myw1Ljc0bC00LjA3OC00LjAzNWMtMS44MDUtMS43NzctNC43MzYtMS43ODktNi41NDUtMC4wMmwtNC41MjUsNC40MTRjLTEuODEyLDEuNzY4LTEuODIsNC42NDgtMC4wMiw2LjQyNA0KCQlsMi41ODYtMi40ODRjLTAuMjYyLTAuNzkxLDAuMDYxLTEuNjk3LDAuNzAxLTIuMzI0bDIuODc5LTIuODA3YzAuOTEyLTAuODg1LDIuMzc1LTAuODgxLDMuMjc1LDAuMDFsMi40NDksMi40Mg0KCQljMC45LDAuODkxLDAuODk2LDIuMzI2LTAuMDEsMy4yMTNsLTIuODc5LDIuODA5Yy0wLjYwOSwwLjU5NC0xLjYwOSwwLjkyLTIuMzg1LDAuNzExbC0yLjUzMywyLjQ4Ng0KCQljMS44MDMsMS43ODEsNC43MzIsMS43ODksNi41NDUsMC4wMmw0LjUyLTQuNDFDMzIuMzQsMTAuMzk2LDMyLjM0Niw3LjUxOSwzMC41NDMsNS43NHoiLz4NCgk8cGF0aCBkPSJNMTMuOTc1LDIxLjg5NGMwLjIxNSwwLjc3My0wLjEyOSwxLjc3My0wLjc1MiwyLjM4MWwtMi42ODksMi42MjdjLTAuOTIyLDAuOS0yLjQxNCwwLjg5NS0zLjMzMi0wLjAxMmwtMi40OTgtMi40NjENCgkJYy0wLjkxNi0wLjkwNi0wLjkxLTIuMzc5LDAuMDEyLTMuMjc1bDIuNjkxLTIuNjI3YzAuNjU2LTAuNjM3LDEuNTk4LTAuOTYxLDIuNDItMC42ODlsMi41OTQtMi41Nw0KCQljLTEuODM2LTEuODExLTQuODI0LTEuODItNi42NjgtMC4wMmwtNC4zNjMsNC4yNmMtMS44NDYsMS44MDMtMS44NTUsNC43MzQtMC4wMiw2LjU0OWw0LjE1NCw0LjEwNw0KCQljMS44MzQsMS44MDksNC44MiwxLjgxOCw2LjY2OCwwLjAxOGw0LjM2My00LjI2YzEuODQ0LTEuODA1LDEuODUyLTQuNzM0LDAuMDItNi41NDdMMTMuOTc1LDIxLjg5NHoiLz4NCgk8cGF0aCBkPSJNMTEuMTM5LDIwLjcyMmMwLjYxMSwwLjYxNywxLjYxMSwwLjYyMywyLjIzNCwwLjAwOGw3LjQ1NS03LjQxNmMwLjYyMS0wLjYxNywwLjYyNS0xLjYxNSwwLjAwOC0yLjIzNA0KCQljLTAuNjEzLTAuNjE1LTEuNjExLTAuNjE5LTIuMjMtMC4wMDZsLTcuNDU3LDcuNDE0QzEwLjUyOSwxOS4xMDMsMTAuNTI1LDIwLjEwMSwxMS4xMzksMjAuNzIyeiIvPg0KPC9nPg0KPC9zdmc+DQo=';
const OPTION_DEFAULT = {
interaction: {
hover: true,
selectConnectedEdges: false
},
layout: {
hierarchical: {
enabled: true,
levelSeparation: 150,
nodeSpacing: 200,
treeSpacing: 200,
blockShifting: true,
edgeMinimization: true,
parentCentralization: true,
direction: 'UD',
sortMethod: 'directed',
shakeTowards: 'leaves' // roots, leaves
}
},
edges: {
shadow: true
},
nodes: {
shadow: true
},
physics: {
maxVelocity: 10,
minVelocity: 0.1,
}
};
const DEFAULT_CONFIG = {
links: {
childrens: {
styles: {
color: '#848484',
highlight: '#848484',
hover: '#848484'
}
},
parents: {
styles: {
color: '#99004d',
highlight: '#800040',
hover: '#800040',
}
}
},
nodes: {
images: DEFAULT_NODE_IMAGE,
size: 20
},
union: {
images: DEFAULT_UNION_IMAGE,
size: 20
}
};
/* tslint:disable */
let FamilyHierarchyService = class FamilyHierarchyService {
constructor() {
this.currentEdges = new BehaviorSubject(new DataSet());
this._Edges = this.currentEdges.asObservable();
this.currentNodes = new BehaviorSubject(new DataSet());
this._Nodes = this.currentNodes.asObservable();
this.currentFhNodes = new BehaviorSubject([]);
this._fhNodes = this.currentFhNodes.asObservable();
this.currentFhUnions = new BehaviorSubject([]);
this._fhUnions = this.currentFhUnions.asObservable();
this.currentFhLinks = new BehaviorSubject([]);
this._fhLinks = this.currentFhLinks.asObservable();
this.currentConfig = new BehaviorSubject(DEFAULT_CONFIG);
this._config = this.currentConfig.asObservable();
this.init = new Subject();
this._init = this.init.asObservable();
this._clickNode = new Subject();
this.clickNode = this._clickNode.asObservable();
this._clickUnion = new Subject();
this.clickUnion = this._clickUnion.asObservable();
this._clickLink = new Subject();
this.clickLink = this._clickLink.asObservable();
}
get Edges() {
return this._Edges;
}
get Nodes() {
return this._Nodes;
}
setFhNodes(nodes) {
this.currentNodes.next(this.generateNodes(nodes));
this.currentFhNodes.next(nodes);
}
get FhNodes() {
return this._fhNodes;
}
setFhUnions(unions) {
this.generateUnions(unions);
this.currentFhUnions.next(unions);
}
get FhUnions() {
return this._fhUnions;
}
setFhLinks(links) {
this.currentEdges.next(this.generateEdges(links));
this.currentFhLinks.next(links);
}
get FhLinks() {
return this._fhLinks;
}
setConfig(config) {
this.currentConfig.next(this.mergeDeep(this.currentConfig.getValue(), config));
}
get config() {
return this._config;
}
sendClickEvent(e, type) {
if (type === 'node') {
const node = this.currentFhNodes.getValue().find(el => el.id === e.nodes[0]);
if (node) {
this._clickNode.next(node);
}
else {
const id = e.nodes[0].replace('u', '');
const union = this.currentFhUnions.getValue().find(el => el.id == id);
this._clickUnion.next(union);
}
}
else {
const link = this.currentFhLinks.getValue().find(el => el.id == e.edges[0]);
if (link) {
this._clickLink.next(link);
}
else {
const regex = /(?<=l)(.*?)(?=_)/;
const id = e.edges[0].toString().match(regex)[0];
this._clickLink.next(this.currentFhUnions.getValue().find(o => o.id == id));
}
}
}
initialize(data, config) {
this.currentFhLinks.next(data.links);
this.currentFhNodes.next(data.nodes);
this.currentFhUnions.next(data.unions);
this.currentEdges.next(this.generateEdges(data.links)),
this.currentNodes.next(this.generateNodes(data.nodes));
this.generateUnions(data.unions);
if (config) {
this.setConfig(config);
}
this.init.next(true);
}
mergeDeep(target, ...sources) {
if (!sources.length) {
return target;
}
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) {
Object.assign(target, { [key]: {} });
}
else {
target[key] = Object.assign({}, target[key]);
}
this.mergeDeep(target[key], source[key]);
}
else {
Object.assign(target, { [key]: source[key] });
}
}
}
return this.mergeDeep(target, ...sources);
}
generateNodes(nodes) {
return new DataSet(nodes.map((o) => {
return this.createNode(o);
}));
}
createNode(o) {
return {
id: o.id,
image: o.image ? o.image : this.currentConfig.getValue().nodes.images,
shape: 'image',
size: this.currentConfig.getValue().nodes.size,
level: (o.level * 2) - 1,
label: o.label
};
}
createEdge(o) {
return {
id: o.id,
from: o.nodeId,
to: `u${o.unionId}`,
color: this.currentConfig.getValue().links.childrens.styles
};
}
generateEdges(links) {
return new DataSet(links.map((o) => {
return this.createEdge(o);
}));
}
generateUnions(unions) {
unions.forEach((u) => {
this.createUnion(u);
});
}
createUnion(u) {
let level;
const node = {
id: `u${u.id}`,
image: this.currentConfig.getValue().union.images,
shape: 'image',
size: this.currentConfig.getValue().union.size,
};
u.components.forEach((e) => {
const edge = {
id: `l${u.id}_${node.id}-${e}`,
from: node.id,
to: e,
color: this.currentConfig.getValue().links.parents.styles,
};
level = this.currentFhNodes.getValue().filter(n => n.id === e)[0].level;
this.currentEdges.getValue().add(edge);
});
node.level = (level * 2);
this.currentNodes.getValue().add(node);
}
addNode(node) {
this.currentFhNodes.getValue().push(node);
this.currentNodes.getValue().add(this.createNode(node));
}
addLink(link) {
this.currentFhLinks.getValue().push(link);
this.currentEdges.getValue().add(this.createEdge(link));
}
addUnion(union) {
this.currentFhUnions.getValue().push(union);
this.createUnion(union);
}
};
FamilyHierarchyService.ɵprov = ɵɵdefineInjectable({ factory: function FamilyHierarchyService_Factory() { return new FamilyHierarchyService(); }, token: FamilyHierarchyService, providedIn: "root" });
FamilyHierarchyService = __decorate([
Injectable({
providedIn: 'root'
})
], FamilyHierarchyService);
let FamilyHierarchyComponent = class FamilyHierarchyComponent {
constructor(familyService) {
this.familyService = familyService;
this.class = '';
this.editMode = false;
this.subs = new Subscription();
}
ngOnInit() {
}
ngAfterViewInit() {
const init = this.familyService._init
.subscribe((e) => {
this.createNetwork();
});
this.subs.add(init);
this.subs.add(this.familyService._config.subscribe((config) => this.config = config));
this.subs.add(this.familyService.Edges.subscribe((edges) => this.dsEdge = edges));
this.subs.add(this.familyService.Nodes.subscribe((nodes) => this.dsNode = nodes));
}
createNetwork() {
this.visNetworkData = { nodes: this.dsNode, edges: this.dsEdge };
this.visNetwork = new Network(this.el.nativeElement, this.visNetworkData, OPTION_DEFAULT);
this.visNetwork.on('selectNode', (e) => {
this.familyService.sendClickEvent(e, 'node');
});
this.visNetwork.on('selectEdge', (e) => {
this.familyService.sendClickEvent(e);
});
}
ngOnDestroy() {
this.subs.unsubscribe();
}
};
FamilyHierarchyComponent.ctorParameters = () => [
{ type: FamilyHierarchyService }
];
__decorate([
ViewChild('box')
], FamilyHierarchyComponent.prototype, "el", void 0);
__decorate([
ContentChild('nodeTemplate')
], FamilyHierarchyComponent.prototype, "nodeTemplate", void 0);
__decorate([
ViewChild('contentNode')
], FamilyHierarchyComponent.prototype, "contentNode", void 0);
__decorate([
Input()
], FamilyHierarchyComponent.prototype, "class", void 0);
__decorate([
Input()
], FamilyHierarchyComponent.prototype, "editMode", void 0);
FamilyHierarchyComponent = __decorate([
Component({
selector: 'FamilyHierarchy',
template: "<!-- <div class=\"vis-container\"\r\n [visNetwork]=\"visNetwork\"\r\n [visNetworkData]=\"visNetworkData\"\r\n [visNetworkOptions]=\"visNetworkOptions\"\r\n (initialized)=\"networkInitialized()\">\r\n</div>\r\n -->\r\n<div #box [class.edit-mode]=\"editMode\" [ngClass]=\"['vis-container', class]\"></div>",
styles: [".vis-container{width:100%;height:100%;outline:0}.edit-mode{background-size:40px 40px;background-image:linear-gradient(to right,grey 1px,transparent 1px),linear-gradient(to bottom,grey 1px,transparent 1px)}"]
})
], FamilyHierarchyComponent);
let FamilyHierarchyModule = class FamilyHierarchyModule {
};
FamilyHierarchyModule = __decorate([
NgModule({
declarations: [FamilyHierarchyComponent],
imports: [
CommonModule,
],
exports: [FamilyHierarchyComponent]
})
], FamilyHierarchyModule);
/*
* Public API Surface of family-hierarchy
*/
/**
* Generated bundle index. Do not edit.
*/
export { FamilyHierarchyComponent, FamilyHierarchyModule, FamilyHierarchyService };
//# sourceMappingURL=family-hierarchy.js.map