Skip to content
This repository has been archived by the owner on Nov 22, 2019. It is now read-only.

Commit

Permalink
TEIIDTOOLS-467: Adds diagramming to the canvas
Browse files Browse the repository at this point in the history
  • Loading branch information
phantomjinx committed Jul 3, 2018
1 parent 25c2c6c commit 0a123d9
Show file tree
Hide file tree
Showing 29 changed files with 1,165 additions and 7 deletions.
3 changes: 3 additions & 0 deletions ngapp/package.json
Expand Up @@ -26,6 +26,7 @@
"@angular/router": "^4.4.6",
"angular-tree-component": "^7.1.0",
"core-js": "^2.5.3",
"d3": "^5.5.0",
"express": "^4.16.2",
"file-saver": "1.3.3",
"lodash": "^4.17.10",
Expand All @@ -36,10 +37,12 @@
"patternfly-ng": "3.3.4",
"rxjs": "^5.5.6",
"vkbeautify": "^0.99.3",
"webcola": "^3.3.8",
"x2js": "^3.2.1",
"zone.js": "^0.8.20"
},
"devDependencies": {
"@types/d3": "^5.0.0",
"@types/jasmine": "~2.5.53",
"@types/jasminewd2": "~2.0.2",
"@types/node": "^6.0.101",
Expand Down
11 changes: 9 additions & 2 deletions ngapp/src/app/dataservices/dataservices.module.ts
Expand Up @@ -46,6 +46,9 @@ import { ViewEditorComponent } from "@dataservices/virtualization/view-editor/vi
import { ViewPreviewComponent } from "@dataservices/virtualization/view-editor/editor-views/view-preview/view-preview.component";
import { EditorViewsComponent } from '@dataservices/virtualization/view-editor/editor-views/editor-views.component';
import { MessageLogComponent } from '@dataservices/virtualization/view-editor/editor-views/message-log/message-log.component';
import { CanvasService } from '@dataservices/virtualization/view-editor/view-canvas/canvas.service';
import { GraphVisualComponent, NodeVisualComponent, LinkVisualComponent } from '@dataservices/virtualization/view-editor/view-canvas/visuals';

import { environment } from "@environments/environment";
import { ConfirmDialogComponent } from "@shared/confirm-dialog/confirm-dialog.component";
import { SharedModule } from "@shared/shared.module";
Expand Down Expand Up @@ -121,7 +124,10 @@ import { ViewPropertyEditorsComponent } from './virtualization/view-editor/view-
EditorViewsComponent,
ConnectionTreeSelectorComponent,
ConnectionTableDialogComponent,
ViewPropertyEditorsComponent
ViewPropertyEditorsComponent,
GraphVisualComponent,
NodeVisualComponent,
LinkVisualComponent
],
providers: [
{
Expand All @@ -138,7 +144,8 @@ import { ViewPropertyEditorsComponent } from './virtualization/view-editor/view-
},
LoggerService,
NotifierService,
SelectionService
SelectionService,
CanvasService
],
exports: [
],
Expand Down
@@ -0,0 +1,43 @@
/* tslint:disable:max-line-length */

/**
* @license
* Copyright 2017 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

export class CanvasConstants {

//
// CSS constants
//
public static readonly CSS_NODE_VISUAL_GROUP_CLASS = '.node-visual-group';
public static readonly CSS_NODE_VISUAL_TOOLS_CLASS = '.node-visual-tools';
public static readonly CSS_NODE_VISUAL_TOOLS_PLUS_CLASS = '.node-visual-tools-plus';
public static readonly CSS_NODE_VISUAL_TOOLS_MINUS_CLASS = '.node-visual-tools-minus';
public static readonly CSS_GRAPH_ID = '#canvasGraph';
public static readonly CSS_NV_TOOLS_PLUS_ID_SUFFIX = '-plus';
public static readonly CSS_NV_TOOLS_MINUS_ID_SUFFIX = '-minus';

//
// Node Types
//
public static readonly SOURCE_TYPE = 'SOURCE';
public static readonly COMPONENT_TYPE = 'COMPONENT';

//
// Model constants
//
public static readonly NODE_PREFIX = 'Node';
}
@@ -0,0 +1,246 @@
/**
* @license
* Copyright 2017 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable, EventEmitter, ChangeDetectorRef } from '@angular/core';
import { CanvasConstants } from '@dataservices/virtualization/view-editor/view-canvas/canvas-constants';
import { CanvasNode, CanvasLink, CanvasGraph } from '@dataservices/virtualization/view-editor/view-canvas/models';
import * as d3 from 'd3';
import * as _ from "lodash";

@Injectable()
export class CanvasService {

private canvasGraph: CanvasGraph;
private viewReference: ChangeDetectorRef;

public canvasNodesSelected: EventEmitter<CanvasNode[]> = new EventEmitter();

/**
* This service will provide methods to enable user interaction with elements
* while maintaining the d3 simulations physics
*/
constructor() {}

private stopPropagation(): void {
// Stop propagration of click event to parent svg
d3.event.stopPropagation();

// Stop shift-left-click shortcut being fired (firefox opens a new tab/window)
d3.event.preventDefault();
}

/**
* Callback for the command icon when the command has been depressed or not
*/
private commandIconChangeCallback(nodeId: string, commandType: string, depressed: boolean): void {
console.log(" commandIconChangeCallback: " + nodeId + " " + commandType + " " + depressed);
const selection = d3.select('#' + nodeId + '-' + commandType);
selection.attr('xlink:href', this.commandIcon(commandType, depressed));
this.stopPropagation();
}

private addNodeCallback(source: CanvasNode): void {

//
// Ensure all nodes have been unfixed
// to allow the graph to properly relayout
//
this.canvasGraph.unfixNodes();

//
// Fix the source node
//
source.setFixed(true);

let type = null;
if (source.type === CanvasConstants.SOURCE_TYPE)
type = CanvasConstants.COMPONENT_TYPE;
else
type = CanvasConstants.SOURCE_TYPE;

const tgtId = this.createNode(type, '<<ToBeImplemented>>');
const srcId = source.id;

//
// Create the link and update the graph
//
this.createLink(srcId, tgtId, true);
this.stopPropagation();
}

private removeNodeCallback(node: CanvasNode) {
this.deleteNode(node.id, true);
this.stopPropagation();
}

/**
* The interactable graph.
* This method does not interact with the document, purely physical calculations with d3
*/
public newCanvasGraph(options: { width, height }, changeDetectorRef: ChangeDetectorRef): CanvasGraph {
this.viewReference = changeDetectorRef;

this.canvasGraph = new CanvasGraph(this, options);
this.canvasGraph.nodesSelected.subscribe((nodes) => {
this.canvasNodesSelected.emit(nodes);
});

/**
* Binding change detection check on each tick
* This along with an onPush change detection strategy should enforce checking only when relevant!
* This improves scripting computation duration, consistently.
* Also, it makes sense to avoid unnecessary checks when we are dealing only with simulations data binding.
*/
this.canvasGraph.ticker.subscribe((d) => {
this.viewReference.markForCheck();
});

return this.canvasGraph;
}

public nodes(): CanvasNode[] {
if (!this.canvasGraph)
return new Array<CanvasNode>();

return this.canvasGraph.nodes;
}

public links(): CanvasLink[] {
if (!this.canvasGraph)
return new Array<CanvasLink>();

return this.canvasGraph.links;
}

/**
* Makes sure both the canvas graph and the
* view are up to date and all events have
* been wired up.
*/
public update(refreshGraph: boolean, options?: any): void {
if (this.canvasGraph && refreshGraph) {
if (options !== undefined)
this.canvasGraph.setOptions(options);

this.canvasGraph.refresh();
}

if (this.viewReference)
this.viewReference.detectChanges();

const svg = d3.select(CanvasConstants.CSS_GRAPH_ID);
const svgGroup = svg.select('g');

//
// Create zoom / pan behaviour
//
const zoom = d3.zoom()
.scaleExtent([0.1, 3])
.on('zoom', () => {
svgGroup.attr("transform", d3.event.transform);
});

//
// Add mouse click listener and zoom listener to graph
//
svg
.on('click', () => this.canvasGraph.selectionCallback(null))
.call(zoom);

//
// Add mouse selection listener on each node
//
const nodeSelection = d3.selectAll(CanvasConstants.CSS_NODE_VISUAL_GROUP_CLASS);
nodeSelection
.data(this.canvasGraph.nodes)
.on('click', (cn) => this.canvasGraph.selectionCallback(cn));

//
// Add mouse listener on each plus button
//
const plusSelection = d3.selectAll(CanvasConstants.CSS_NODE_VISUAL_TOOLS_PLUS_CLASS);
plusSelection
.data(this.canvasGraph.nodes)
.on('mousedown', (cn) => this.commandIconChangeCallback(cn.id, 'plus', true))
.on('mouseup', (cn) => this.commandIconChangeCallback(cn.id, 'plus', false))
.on('click', (src) => this.addNodeCallback(src));

//
// Add mouse listener on each minus button
//
const minusSelection = d3.selectAll(CanvasConstants.CSS_NODE_VISUAL_TOOLS_MINUS_CLASS);
minusSelection
.data(this.canvasGraph.nodes)
.on('mousedown', (cn) => this.commandIconChangeCallback(cn.id, 'minus', true))
.on('mouseup', (cn) => this.commandIconChangeCallback(cn.id, 'minus', false))
.on('click', (cn) => this.removeNodeCallback(cn));
}

/**
* Find the bounding box of the element associated with
* the given canvas node. The box represents the outer
* perimeter of the drawn element, including x, y, width and height
*/
public boundingBox(elementId: string): any {
if (_.isEmpty(elementId))
return null;

const selection = d3.select('#' + elementId);
if (selection)
return (<SVGGraphicsElement>selection.node()).getBBox();

return null;
}

/**
* @returns the icon for the command type provided. If depressed then
* returns the depressed version of the icon
*/
public commandIcon(cmdType: string, depressed: boolean) {
if (depressed)
return "/assets/iconfinder/Aha-soft/" + cmdType + "-depressed.png";

return "/assets/iconfinder/Aha-soft/" + cmdType + ".png";
}

/**
* Create a new node and add it to the graph
*/
public createNode(type: string, label: string, refresh?: boolean): string {
if (! this.canvasGraph)
throw new Error("A canvas graph is required before creating a node");

const canvasNode = this.canvasGraph.addNode(type, label, refresh);
return canvasNode.id;
}

public deleteNode(nodeId: string, refresh?: boolean) {
if (! this.canvasGraph)
throw new Error("A canvas graph is required before removing a node");

this.canvasGraph.removeNode(nodeId, refresh);
}

/**
* Create a new link and add it to the graph
*/
public createLink(source: string, target: string, refresh?: boolean): void {
if (! this.canvasGraph)
throw new Error("A canvas graph is required before creating a node");

this.canvasGraph.addLink(source, target, refresh);
}
}

0 comments on commit 0a123d9

Please sign in to comment.