GoJS是一个实现交互类图表(比如流程图,树图,关系图,力导图等等)的JS库
gojs提供了angular的基本例子,不过是离线版
https://github.com/NorthwoodsSoftware/GoJS/tree/master/projects/angular-basic
下图是运行结果。上面是可拖动的,下面显示当前图表的结构
一。首先完成上面可拖动的部分
diagram-editor
diagram-editor.component.ts
constructor中完成初始化图表的基本属性如颜色等
this.getModel();从服务器获得列表
this.networkService.getModelText().then(r => { console.log(r); this.createModel(JSON.stringify(r)); });
r => { } r是获得的数据,括号里面可以添加对数据进行的操作(可以加函数),我获取数据就是完整的json格式的gojs图表,直接string化传给model它就可以识别了,图表格式如下
{ "class": "go.GraphLinksModel",
"nodeDataArray": [
{"key":1, "text":"Alpha", "color":"lightblue", "loc":"0 0"},
{"key":2, "text":"Beta", "color":"orange", "loc":"72.09912109375 0"},
{"key":3, "text":"Gamma", "color":"lightgreen", "loc":"0 70"},
{"key":4, "text":"Delta", "color":"pink", "loc":"84.40087890625 70"},
{"text":"Gamma", "color":"lightgreen", "key":-3, "loc":"-138.71875 88.41666412353516"},
{"text":"Epsilon", "color":"yellow", "key":-5, "loc":"-316.71875 158.41666412353516"}
],
"linkDataArray": [
{"from":1, "to":2},
{"from":1, "to":3},
{"from":2, "to":2},
{"from":3, "to":4},
{"from":4, "to":1}
]}
然后调用函数createModel,用gojs自带函数go.Model.fromJson显示表格,这样可以实现异步加载图表。
onSave()保存图表到服务器
import { Component, OnInit, ViewChild, ElementRef, Input, Output, EventEmitter, AfterContentInit } from '@angular/core';
import * as go from 'gojs';
import { NetworkService } from '../network.service';
import { Observable } from 'rxjs/observable';
import { catchError, map, tap } from 'rxjs/operators';
import { interval } from 'rxjs/observable/interval';
import {passBoolean} from 'protractor/built/util';
@Component({
selector: 'app-diagram-editor',
templateUrl: './diagram-editor.component.html',
styleUrls: ['./diagram-editor.component.css']
})
export class DiagramEditorComponent implements OnInit {
private diagram: go.Diagram = new go.Diagram();
private palette: go.Palette = new go.Palette();
@ViewChild('diagramDiv')
private diagramRef: ElementRef;
@ViewChild('paletteDiv')
private paletteRef: ElementRef;
@Input()
get model(): go.Model { return this.diagram.model; }
set model(val: go.Model) { this.diagram.model = val; }
@Output()
nodeSelected = new EventEmitter<go.Node|null>();
@Output()
modelChanged = new EventEmitter<go.ChangedEvent>();
constructor(private networkService: NetworkService) {
this.getModel();
const $ = go.GraphObject.make;
this.diagram = new go.Diagram();
this.diagram.initialContentAlignment = go.Spot.Center;
this.diagram.allowDrop = true; // necessary for dragging from Palette
this.diagram.undoManager.isEnabled = true;
this.diagram.addDiagramListener("ChangedSelection",
e => {
const node = e.diagram.selection.first();
this.nodeSelected.emit(node instanceof go.Node ? node : null);
});
this.diagram.addModelChangedListener(e => e.isTransactionFinished && this.modelChanged.emit(e));
this.diagram.nodeTemplate =
$(go.Node, "Auto",
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
$(go.Shape,
{
fill: "white", strokeWidth: 0,
portId: "", cursor: "pointer",
// allow many kinds of links
fromLinkable: true, toLinkable: true,
fromLinkableSelfNode: true, toLinkableSelfNode: true,
fromLinkableDuplicates: true, toLinkableDuplicates: true
},
new go.Binding("fill", "color")),
$(go.TextBlock,
{ margin: 8, editable: true },
new go.Binding("text").makeTwoWay())
);
this.diagram.linkTemplate =
$(go.Link,
// allow relinking
{ relinkableFrom: true, relinkableTo: true },
$(go.Shape),
$(go.Shape, { toArrow: "OpenTriangle" })
);
this.palette = new go.Palette();
this.palette.nodeTemplateMap = this.diagram.nodeTemplateMap;
// initialize contents of Palette
this.palette.model.nodeDataArray =
[
{ text: "Alpha", color: "lightblue" },
{ text: "Beta", color: "orange" },
{ text: "Gamma", color: "lightgreen" },
{ text: "Delta", color: "pink" },
{ text: "Epsilon", color: "yellow" }
];
}
ngOnInit() {
this.diagram.div = this.diagramRef.nativeElement;
this.palette.div = this.paletteRef.nativeElement;
}
getModel(): void {
this.networkService.getModelText().then(r => { console.log(r); this.createModel(JSON.stringify(r)); });
}
createModel(a: string ): void {
this.model = go.Model.fromJson(a);
}
onSave(): void {
this.networkService.saveModel(this.diagram.model.toJson()).subscribe();
}
}
diagram-editor.component.html
<div class="diagramsPanel">
<div #paletteDiv class="paletteDiv"></div>
<div #diagramDiv class="diagramDiv"></div>
<div>
<button (click)="onSave()">Save Changes</button>
Diagram Model saved in JSON format:
</div>
<div>
<textarea *ngIf="model" style="width:100%;height:300px">
{{model.toJson()}}
</textarea>
</div>
</div>
二。下半部分显示json字符串:
import { Component, OnInit, ViewChild, ElementRef, Input, Output, EventEmitter } from '@angular/core';
import * as go from 'gojs';
@Component({
selector: 'app-diagram-detail',
templateUrl: './diagram-detail.component.html',
styleUrls: ['./diagram-detail.component.css']
})
export class DiagramDetailComponent implements OnInit {
@Input() node: go.Node;
@Input() data: any;
constructor() { }
ngOnInit() {
}
showDetails(node: go.Node | null) {
this.node = node;
if (node) {
// copy the editable properties into a separate Object
this.data = {
text: node.data.text,
color: node.data.color
};
} else {
this.data = null;
}
}
}
diagram-detail.component.html
<div *ngIf="node">
<form *ngIf="node" #form="ngForm" (ngSubmit)="onCommitDetails()">
Node Details:
<div><label>Key: </label>{{node.key}}</div>
<div><label>Text: </label><input [(ngModel)]="data.text" name="text"></div>
<div><label>Color: </label><input [(ngModel)]="data.color" name="color"></div>
<div><label>Location: </label>{{node.location.x.toFixed(2)}}, {{node.location.y.toFixed(2)}}</div>
<div><label># Links: </label>{{node.linksConnected.count}}</div>
</form>
</div>
三。与服务器通信,用了promise,可以实现异步传输,使用rxjs库需要具体说明路径,有部分冗余代码,不懂得可以看看angular官方文档http部分
network.service.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { HttpClient, HttpHeaders, HttpClientModule } from '@angular/common/http';
import { of } from 'rxjs/observable/of';
import { catchError, map, tap , retry } from 'rxjs/operators';
import 'rxjs/add/operator/toPromise';
import { MessageService } from './message.service';
import {promise} from 'selenium-webdriver';
const httpOptions = {
//headers: new HttpHeaders({ 'Content-Type': 'application/json' })
headers: new HttpHeaders({'Content-Type': 'application/x-www-form-urlencoded'})
};
@Injectable()
export class NetworkService {
public API = '//localhost:8888';
private getModelUrl = this.API + '/gojs/get'; // URL to web api
private saveModelUrl = this.API + '/gojs/save';
constructor(private http: HttpClient,
private messageService: MessageService) { }
// getModel(): Observable<string> {
// const url = `${this.getModelUrl}`;
// return this.http.get<string>(url).pipe(
// catchError(this.handleError<string>(`getModel`))
// );
// }
/** GET: get the model on the server */
getModelText(): Promise<any> {
// The Observable returned by get() is of type Observable<string>
// because a text response was specified.
// There's no need to pass a <string> type parameter to get().
return this.http.get(this.getModelUrl).toPromise().catch(this.handleError());
}
/** PUT: update the model on the server */
saveModel (data: string): Observable<any> {
// return this.http.post(this.saveModelUrl, data, httpOptions).pipe(
// catchError(this.handleError<any>('saveModel'))
// );
const body = {model: data};
this.http.post(this.saveModelUrl,
'model=' + data, httpOptions).subscribe(model => {
console.log(data);
});
return null;
}
/**
* Handle Http operation that failed.
* Let the app continue.
* @param operation - name of the operation that failed
* @param result - optional value to return as the observable result
*/
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
// TODO: better job of transforming error for user consumption
this.log(`${operation} failed: ${error.message}`);
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
/** Log a HeroService message with the MessageService */
private log(message: string) {
this.messageService.add('NetworkService: ' + message);
}
}
message.service.ts没什么大用
import { Injectable } from '@angular/core';
@Injectable()
export class MessageService {
messages: string[] = [];
add(message: string) {
this.messages.push(message);
}
clear() {
this.messages = [];
}
}
服务器和angular位于不同端口,添加以下代码,否则不允许访问,这里用的服务器是springboot,服务器就比较简单了,不再细说
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class CorsConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT")
.maxAge(3600);
}
}