Skip to content

Commit

Permalink
feat(core/graph): add new graph widget
Browse files Browse the repository at this point in the history
  • Loading branch information
sara-gnucoop authored and trik committed Apr 11, 2022
1 parent c8148d7 commit fb5817e
Show file tree
Hide file tree
Showing 33 changed files with 680 additions and 25 deletions.
4 changes: 4 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,8 @@
"scripts": [],
"allowedCommonJsDependencies": [
"chart.js",
"dagre",
"svg-pan-zoom",
"pdfmake/build/pdfmake"
]
},
Expand Down Expand Up @@ -459,6 +461,8 @@
"scripts": [],
"allowedCommonJsDependencies": [
"chart.js",
"dagre",
"svg-pan-zoom",
"pdfmake/build/pdfmake"
]
},
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"@zxing/browser": "^0.1.1",
"@zxing/library": "^0.19.1",
"chart.js": "^2.9.4",
"dagre": "^0.8.5",
"date-fns": "^2.28.0",
"flag-icon-css": "^4.1.7",
"ionicons": "5.5.4",
Expand All @@ -66,6 +67,7 @@
"numbro": "^2.3.6",
"pdfmake": "^0.2.4",
"rxjs": "^7.5.0",
"svg-pan-zoom": "^3.6.1",
"tslib": "^2.3.0",
"xlsx": "^0.18.3",
"zone.js": "^0.11.4"
Expand All @@ -81,6 +83,7 @@
"@angular/compiler-cli": "^13.2.5",
"@cypress/schematic": "1.6.0",
"@types/chart.js": "^2.9.35",
"@types/dagre": "^0.7.46",
"@types/jasmine": "^3.10.0",
"@types/leaflet": "^1.7.9",
"@types/marked": "^2.0.0",
Expand Down Expand Up @@ -115,4 +118,4 @@
"typescript": "~4.5.2",
"unique-names-generator": "^4.7.1"
}
}
}
Empty file added projects/core/graph/graph.md
Empty file.
23 changes: 23 additions & 0 deletions projects/core/graph/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* @license
* Copyright (C) Gnucoop soc. coop.
*
* This file is part of the Advanced JSON forms (ajf).
*
* Advanced JSON forms (ajf) is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* Advanced JSON forms (ajf) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
* General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Advanced JSON forms (ajf).
* If not, see http://www.gnu.org/licenses/.
*
*/

export * from './src/public_api';
1 change: 1 addition & 0 deletions projects/core/graph/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Empty file.
32 changes: 32 additions & 0 deletions projects/core/graph/src/graph-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @license
* Copyright (C) Gnucoop soc. coop.
*
* This file is part of the Advanced JSON forms (ajf).
*
* Advanced JSON forms (ajf) is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* Advanced JSON forms (ajf) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
* General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Advanced JSON forms (ajf).
* If not, see http://www.gnu.org/licenses/.
*
*/

import {NgModule} from '@angular/core';
import {AjfGraphComponent} from './graph';
import {CommonModule} from '@angular/common';

@NgModule({
declarations: [AjfGraphComponent],
exports: [AjfGraphComponent],
imports: [CommonModule],
})
export class AjfGraphModule {}
31 changes: 31 additions & 0 deletions projects/core/graph/src/graph-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @license
* Copyright (C) Gnucoop soc. coop.
*
* This file is part of the Advanced JSON forms (ajf).
*
* Advanced JSON forms (ajf) is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* Advanced JSON forms (ajf) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
* General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Advanced JSON forms (ajf).
* If not, see http://www.gnu.org/licenses/.
*
*/

export interface AjfGraphNode {
id: string;
label: string;
parentId: string | string[] | null;
green: boolean;
red: boolean;
yellow: boolean;
color?: string;
}
34 changes: 34 additions & 0 deletions projects/core/graph/src/graph.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div class="wrapper">
<svg #graph viewBox="0 0 1000 2000" width="100%" height="100%">
<g *ngFor="let edge of edges$|async">
<path class="edge" [attr.d]="edge.path" fill="none" stroke="gray" stroke-wodth="2px"></path>
</g>
<g
*ngFor="let box of boxes$|async"
[attr.fill]="box.color? box.color:'#d95989'"
[attr.stroke]="box.color !== 'white' ? 'white' : 'black'"
stroke-width="1px"
transition="fill 0.2s"
>
<rect
[attr.x]="box.x -(box.width/2)"
[attr.y]="box.y - (box.height/2)"
[attr.width]="box.width"
[attr.height]="box.height"
[attr.stroke]="box.red === true ? 'red' : box.yellow === true ? 'yellow' : box.green === true ? 'green' : 'black'"
style="stroke-width:6"
/>
<ng-container *ngFor="let line of lines(box.label);let idx= index">
<text
class="text"
[attr.x]="box.x"
[attr.y]="box.y - (box.height/2) + 30 +(30*idx)"
dominant-baseline="middle"
text-anchor="middle"
>
{{ line }}
</text>
</ng-container>
</g>
</svg>
</div>
27 changes: 27 additions & 0 deletions projects/core/graph/src/graph.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
ajf-graph {
width: 100%;
height: 600px;

.buttons {
position: absolute;
z-index: 100;
}
.wrapper {
position: absolute;
left: 0px;
top: 0px;
width: 100%;
height: 600px;
overflow: scroll;
}
svg {
display: block;
cursor: move;
height: 600px;
.edge {
fill: none;
stroke: gray;
stroke-width: 2px;
}
}
}
7 changes: 7 additions & 0 deletions projects/core/graph/src/graph.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {AjfGraphComponent} from './graph';

describe('dummy', () => {
it('test', () => {
expect(AjfGraphComponent).toBeDefined();
});
});
166 changes: 166 additions & 0 deletions projects/core/graph/src/graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/**
* @license
* Copyright (C) Gnucoop soc. coop.
*
* This file is part of the Advanced JSON forms (ajf).
*
* Advanced JSON forms (ajf) is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* Advanced JSON forms (ajf) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
* General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Advanced JSON forms (ajf).
* If not, see http://www.gnu.org/licenses/.
*
*/

import {
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
OnInit,
Renderer2,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import {AjfGraphNode} from './graph-node';
import {BehaviorSubject} from 'rxjs';
import * as dagre from 'dagre';
import * as SvgPanZoom from 'svg-pan-zoom';

const TEXT_END = 20;
const LINE_HEIGHT = 40;
const BOX_WIDTH = 170;

interface Box {
green: boolean;
height: number;
label: string;
name: string;
red: boolean;
width: number;
x: number;
y: number;
yellow: boolean;
color?: string;
}

interface IPoint {
x: number;
y: number;
}

export class Edge {
constructor(private points: IPoint[]) {}

public get path(): string {
if (this.points.length < 2) {
return '';
}
let result = 'M ';
this.points.forEach(pt => {
result += `${pt.x} ${pt.y} L`;
});
return result.substr(0, result.length - 2);
}
}

@Component({
selector: 'ajf-graph',
templateUrl: 'graph.html',
styleUrls: ['graph.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class AjfGraphComponent implements OnInit {
@Input() nodes?: AjfGraphNode[];
@ViewChild('graph', {static: true}) graphElement!: ElementRef;
boxes$: BehaviorSubject<Box[]> = new BehaviorSubject<Box[]>([]);
edges$: BehaviorSubject<Edge[]> = new BehaviorSubject<Edge[]>([]);
graph: dagre.graphlib.Graph<{}> = new dagre.graphlib.Graph();

constructor(private _el: ElementRef, private _renderer: Renderer2) {
this.graph.setGraph({marginx: BOX_WIDTH / 2, marginy: LINE_HEIGHT});
}

/**
* data una stringa crea un array di stringhe in base ad un TEXT_END
* se la ripartizione ricade all'interno di una parola shifta il delimiter fino a trovare uno
* spazio bianco.
*/
lines(text: string): string[] {
const lines: string[] = [];
while (text != null && text.length > 0) {
let textEnd = TEXT_END;
while (text[textEnd - 1] !== ' ' && text.length > TEXT_END) {
textEnd--;
}
const line = text.slice(0, textEnd);
text = text.split(line)[1];
lines.push(line);
}
return lines;
}

ngOnInit(): void {
if (this.nodes != null) {
const widgetNodes: AjfGraphNode[] = this.nodes;

widgetNodes.forEach(node => {
this.graph.setNode(node.id, {
width: BOX_WIDTH,
height: this._calculateHeight(node.label),
label: node.label,
red: node.red,
yellow: node.yellow,
green: node.green,
color: node.color || undefined,
});
if (node.parentId != null) {
try {
node.parentId = JSON.parse(node.parentId as string);
} catch (e) {}
const parents: string[] = Array.isArray(node.parentId)
? node.parentId
: [node.parentId as string];
parents.forEach(parent => {
this.graph.setEdge(`${parent}`, node.id, {});
});
}
});
try {
SvgPanZoom(this.graphElement.nativeElement, {controlIconsEnabled: true});
} catch (e) {}
}

dagre.layout(this.graph);
const boxes: Box[] = [];
this.graph.nodes().forEach((nodeId: any) => {
const n: any = this.graph.node(nodeId);
if (n) {
boxes.push({...n});
}
});
this.boxes$.next(boxes);
const edges: Edge[] = [];
this.graph.edges().forEach((edge: any) => {
edges.push(new Edge(this.graph.edge(edge).points));
});
this.edges$.next(edges);
}

private _calculateHeight(text: string): number {
const linesLength = this.lines(text).length;
if (linesLength === 1) {
return LINE_HEIGHT * 1.5;
}
return linesLength * LINE_HEIGHT;
}
}

0 comments on commit fb5817e

Please sign in to comment.