@savantly/ngx-graphexp
Version:
Gremlin client [Tinkerpop] for an Angular app
2,480 lines • 79.6 kB
JavaScript
import { Injectable, Component, Inject, Input, ViewEncapsulation, NgModule } from '@angular/core';
import { GremlinService } from '@savantly/gremlin-js';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { scaleOrdinal, schemeCategory20, zoom, event, forceSimulation, forceManyBody, forceLink, forceCenter, forceY, forceX, select, selectAll, selection, mouse, drag, range } from 'd3';
import { Observable } from 'rxjs/Observable';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialog, MatSidenavModule, MatButtonModule, MatIconModule, MatFormFieldModule, MatInputModule, MatListModule, MatSelectModule, MatCheckboxModule, MatToolbarModule, MatSliderModule, MatDialogModule } from '@angular/material';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/** @enum {number} */
const GraphsonFormat = {
GraphSON1: 1,
GraphSON2: 2,
GraphSON3: 3,
};
GraphsonFormat[GraphsonFormat.GraphSON1] = "GraphSON1";
GraphsonFormat[GraphsonFormat.GraphSON2] = "GraphSON2";
GraphsonFormat[GraphsonFormat.GraphSON3] = "GraphSON3";
class KV {
}
class GraphexpService {
/**
* @param {?} options
*/
constructor(options) {
this.COMMUNICATION_METHOD = GraphsonFormat.GraphSON3;
this.graphInfoData = new BehaviorSubject({});
this.nodeNames = new BehaviorSubject([]);
this.nodeProperties = new BehaviorSubject([]);
this.edgeProperties = new BehaviorSubject([]);
this.node_limit_per_request = 50;
this.gremlinService = new GremlinService(options);
}
/**
* @return {?}
*/
queryGraphInfo() {
const /** @type {?} */ gremlin_query_nodes = 'nodes = g.V().groupCount().by(label);';
const /** @type {?} */ gremlin_query_edges = 'edges = g.E().groupCount().by(label);';
const /** @type {?} */ gremlin_query_nodes_prop = 'nodesprop = g.V().valueMap().select(keys).groupCount();';
const /** @type {?} */ gremlin_query_edges_prop = 'edgesprop = g.E().valueMap().select(keys).groupCount();';
const /** @type {?} */ gremlinQuery = gremlin_query_nodes + gremlin_query_nodes_prop
+ gremlin_query_edges + gremlin_query_edges_prop
+ '[nodes.toList(),nodesprop.toList(),edges.toList(),edgesprop.toList()]';
this.executeQuery(gremlinQuery).then((response) => {
this.handleGraphInfo(response.data);
});
}
/**
* @param {?} field
* @param {?} value
* @return {?}
*/
queryNodes(field, value) {
const /** @type {?} */ input_string = value;
const /** @type {?} */ input_field = field;
let /** @type {?} */ filtered_string = input_string; // You may add .replace(/\W+/g, ''); to refuse any character not in the alphabet
if (filtered_string.length > 50) {
filtered_string = filtered_string.substring(0, 50); // limit string length
}
// Translate to Gremlin query
let /** @type {?} */ gremlin_query_nodes = null;
let /** @type {?} */ gremlin_query_edges = null;
let /** @type {?} */ gremlin_query = null;
if (input_string === '') {
gremlin_query_nodes = `nodes = g.V().limit(${this.node_limit_per_request})`;
gremlin_query_edges =
`edges = g.V().limit(${this.node_limit_per_request}).aggregate('node').outE().as('edge').inV().where(within('node')).select('edge')`;
gremlin_query = gremlin_query_nodes + '\n' + gremlin_query_edges + '\n' + '[nodes.toList(),edges.toList()]';
}
else {
let /** @type {?} */ has_str = `has('${input_field}', '${filtered_string}')`;
if (this.isInt(input_string)) {
has_str = `has('${input_field}', ${filtered_string})`;
}
gremlin_query = 'g.V().' + has_str;
gremlin_query_nodes = 'nodes = g.V().' + has_str;
gremlin_query_edges = 'edges = g.V().' + has_str
+ `.aggregate('node').outE().as('edge').inV().where(within('node')).select('edge')`;
gremlin_query = gremlin_query_nodes + '\n' + gremlin_query_edges + '\n' + '[nodes.toList(),edges.toList()]';
}
console.log(gremlin_query);
return new Promise((resolve, reject) => {
this.executeQuery(gremlin_query).then(response => {
resolve(this.arrangeData(response.data));
}, error => { reject(error); });
});
}
/**
* @param {?} d
* @return {?}
*/
getRelatedNodes(d) {
let /** @type {?} */ id = d.id;
if (isNaN(id)) {
id = `'${id}'`;
}
const /** @type {?} */ gremlin_query_nodes = `nodes = g.V(${id}).as('node').both().as('node').select(all,'node').inject(g.V(${id})).unfold()`;
const /** @type {?} */ gremlin_query_edges = `edges = g.V(${id}).bothE()`;
const /** @type {?} */ gremlin_query = `${gremlin_query_nodes}\n ${gremlin_query_edges}\n[nodes.toList(),edges.toList()]`;
return new Promise((resolve, reject) => {
this.executeQuery(gremlin_query).then(response => {
resolve(this.arrangeData(response.data));
}, error => { reject(error); });
});
}
/**
* @param {?} label
* @param {?} properties
* @return {?}
*/
createNode(label, properties) {
const /** @type {?} */ promise = new Promise((resolve, reject) => {
let /** @type {?} */ propString = '';
properties.forEach((kv) => {
propString += `, '${kv.key}', '${kv.value}'`;
});
const /** @type {?} */ gremlin = `vertex = graph.addVertex(label, '${label}'${propString})`;
console.log(`executing query: ${gremlin}`);
this.executeQuery(gremlin).then(response => {
resolve(response.data);
}, error => {
console.error(error);
reject(error);
});
});
return promise;
}
/**
* @param {?} item
* @return {?}
*/
createLink(item) {
const /** @type {?} */ properties = item.properties;
const /** @type {?} */ promise = new Promise((resolve, reject) => {
const /** @type {?} */ gremlin = `edge = g.V(${item.source}).next().addEdge('${item.label}',g.V(${item.target}).next());`;
console.log(`executing query: ${gremlin}`);
this.executeQuery(gremlin).then(response => {
resolve(response.data);
}, error => {
console.error(error);
reject(error);
});
});
return promise;
}
/**
* @param {?} gremlin
* @param {?=} bindings
* @return {?}
*/
executeQuery(gremlin, bindings) {
const /** @type {?} */ promise = new Promise((resolve, reject) => {
const /** @type {?} */ query = this.gremlinService.createQuery(gremlin, bindings);
query.onComplete = (response) => {
resolve(response);
};
this.gremlinService.sendMessage(query);
});
return promise;
}
/**
* @param {?} data
* @return {?}
*/
handleGraphInfo(data) {
if (this.COMMUNICATION_METHOD === GraphsonFormat.GraphSON3) {
data = this.graphson3to1(data);
}
const /** @type {?} */ nodeNames = [];
data[0].map((nameGroup) => {
for (const /** @type {?} */ nameItem of Object.keys(nameGroup)) {
nodeNames.push({ key: nameItem, value: nameGroup[nameItem] });
}
});
this.nodeNames.next(nodeNames);
this.graphInfoData.next(data);
this.nodeProperties.next(this.make_properties_list(data[1][0]));
this.edgeProperties.next(this.make_properties_list(data[3][0]));
}
/**
* @param {?} data
* @return {?}
*/
graphson3to1(data) {
// Convert data from graphSON v2 format to graphSON v1
if (!(Array.isArray(data) || ((typeof data === 'object') && (data !== null)))) {
return data;
}
if ('@type' in data) {
if (data['@type'] === 'g:List') {
data = data['@value'];
return this.graphson3to1(data);
}
else if (data['@type'] === 'g:Set') {
data = data['@value'];
return data;
}
else if (data['@type'] === 'g:Map') {
const /** @type {?} */ data_tmp = {};
for (let /** @type {?} */ i = 0; i < data['@value'].length; i += 2) {
let /** @type {?} */ data_key = data['@value'][i];
if ((typeof data_key === 'object') && (data_key !== null)) {
data_key = this.graphson3to1(data_key);
}
if (Array.isArray(data_key)) {
data_key = JSON.stringify(data_key).replace(/\'/g, ' ');
}
data_tmp[data_key] = this.graphson3to1(data['@value'][i + 1]);
}
data = data_tmp;
return data;
}
else {
data = data['@value'];
if ((typeof data === 'object') && (data !== null)) {
data = this.graphson3to1(data);
}
return data;
}
}
else if (Array.isArray(data) || ((typeof data === 'object') && (data !== null))) {
for (const /** @type {?} */ key of Object.keys(data)) {
data[key] = this.graphson3to1(data[key]);
}
return data;
}
return data;
}
/**
* @param {?} data
* @return {?}
*/
arrangeData(data) {
if (this.COMMUNICATION_METHOD === GraphsonFormat.GraphSON3) {
data = this.graphson3to1(data);
return this.arrange_datav3(data);
}
else {
return this.arrange_datav2(data);
}
}
/**
* @param {?} data
* @return {?}
*/
arrange_datav3(data) {
// Extract node and edges from the data returned for 'search' and 'click' request
// Create the graph object
const /** @type {?} */ nodes = [], /** @type {?} */ links = [];
for (const /** @type {?} */ key of Object.keys(data)) {
data[key].forEach((item) => {
if (!('inV' in item) && this.idIndex(nodes, item.id) == null) {
// if vertex and not already in the list
item.type = 'vertex';
nodes.push(this.extract_infov3(item));
}
if (('inV' in item) && this.idIndex(links, item.id) == null) {
item.type = 'edge';
links.push(this.extract_infov3(item));
}
});
}
return { nodes: nodes, links: links };
}
/**
* @param {?} data
* @return {?}
*/
arrange_datav2(data) {
// Extract node and edges from the data returned for 'search' and 'click' request
// Create the graph object
const /** @type {?} */ nodes = [], /** @type {?} */ links = [];
for (const /** @type {?} */ key of Object.keys(data)) {
data[key].forEach(function (item) {
if (item.type === 'vertex' && this.idIndex(nodes, item.id) === null) {
// if vertex and not already in the list
nodes.push(this.extract_infov2(item));
}
if (item.type === 'edge' && this.idIndex(links, item.id) == null) {
links.push(this.extract_infov2(item));
}
});
}
return { nodes: nodes, links: links };
}
/**
* @param {?} data
* @return {?}
*/
extract_infov2(data) {
const /** @type {?} */ data_dic = { id: data.id, label: data.label, type: data.type, properties: {}, source: null, target: null };
const /** @type {?} */ prop_dic = data.properties;
for (const /** @type {?} */ key in prop_dic) {
if (prop_dic.hasOwnProperty(key)) {
data_dic.properties[key] = prop_dic[key];
}
}
if (data.type === 'edge') {
data_dic.source = data.outV;
data_dic.target = data.inV;
}
return data_dic;
}
/**
* @param {?} data
* @return {?}
*/
extract_infov3(data) {
const /** @type {?} */ data_dic = { id: data.id, label: data.label, type: data.type, properties: {}, source: null, target: null };
const /** @type {?} */ prop_dic = data.properties;
for (const /** @type {?} */ key in prop_dic) {
if (prop_dic.hasOwnProperty(key)) {
let /** @type {?} */ property = null;
if (data.type === 'vertex') {
// Extracting the Vertexproperties (properties of properties for vertices)
property = prop_dic[key];
property['summary'] = this.get_vertex_prop_in_list(prop_dic[key]).toString();
}
else {
property = prop_dic[key]['value'];
}
data_dic.properties[key] = property;
}
}
if (data.type === 'edge') {
data_dic.source = data.outV;
data_dic.target = data.inV;
}
return data_dic;
}
/**
* @param {?} vertexProperty
* @return {?}
*/
get_vertex_prop_in_list(vertexProperty) {
const /** @type {?} */ prop_value_list = [];
for (const /** @type {?} */ key of Object.keys(vertexProperty)) {
prop_value_list.push(vertexProperty[key]['value']);
}
return prop_value_list;
}
/**
* @param {?} list
* @param {?} elem
* @return {?}
*/
idIndex(list, elem) {
// find the element in list with id equal to elem
// return its index or null if there is no
for (let /** @type {?} */ i = 0; i < list.length; i++) {
if (list[i].id === elem) {
return i;
}
}
return null;
}
/**
* @param {?} data
* @return {?}
*/
make_properties_list(data) {
const /** @type {?} */ prop_dic = {};
for (let /** @type {?} */ prop_str of Object.keys(data)) {
prop_str = prop_str.replace(/[\[\ \"\'\]]/g, ''); // get rid of symbols [,",',] and spaces
const /** @type {?} */ prop_list = prop_str.split(',');
for (let /** @type {?} */ prop_idx = 0; prop_idx < prop_list.length; prop_idx++) {
prop_dic[prop_list[prop_idx]] = 0;
}
}
const /** @type {?} */ properties_list = [];
for (const /** @type {?} */ key of Object.getOwnPropertyNames(prop_dic)) {
properties_list.push(key);
}
return properties_list;
}
/**
* @param {?} value
* @return {?}
*/
isInt(value) {
return !isNaN(value) &&
!isNaN(parseInt(value, 10));
}
/**
* @param {?} edge
* @return {?}
*/
updateSelection(edge) {
console.log('graphexpService#updateSelection: edge selected: ' + edge.id);
}
}
GraphexpService.decorators = [
{ type: Injectable },
];
/** @nocollapse */
GraphexpService.ctorParameters = () => [
null,
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class GraphConfig {
constructor() {
this.enableEdit = true;
this.nodeLabels = [];
this.linkLabels = [];
this.numberOfLayers = 3;
this.format = GraphsonFormat.GraphSON3;
// Physics
this.force_strength = -600;
this.link_strength = 0.2;
this.force_x_strength = 0.1;
this.force_y_strength = 0.1;
// Nodes
this.default_node_size = 15;
this.default_stroke_width = 2;
this.default_node_color = '#80E810';
this.active_node_margin = 6;
this.active_node_margin_opacity = 0.3;
// Edges
this.default_edge_stroke_width = 3;
this.default_edge_color = '#CCC';
this.edge_label_color = '#111';
this.colorPalette = scaleOrdinal(schemeCategory20);
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class D3Node {
/**
* @param {?=} options
*/
constructor(options) {
this.properties = {};
Object.assign(this, options);
}
/**
* @param {?} key
* @param {?} value
* @return {?}
*/
addProperty(key, value) {
this.properties[key] = value;
}
/**
* @param {?} key
* @return {?}
*/
removeProperty(key) {
delete this.properties[key];
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class GraphLayers {
/**
* @param {?} graphViz
*/
constructor(graphViz) {
this.graphViz = graphViz;
// Submodule that handles layers of visualization
this.old_Nodes = [];
this.old_Links = [];
this._Nodes = [];
this._Links = [];
}
/**
* @return {?}
*/
depth() {
return this.config.numberOfLayers;
}
/**
* @return {?}
*/
get config() {
return this.graphViz.config;
}
/**
* @return {?}
*/
get nodes() {
return this._Nodes;
}
/**
* @return {?}
*/
get links() {
return this._Links;
}
/**
* @return {?}
*/
get _svg() {
return this.graphViz.graphRoot;
}
/**
* @return {?}
*/
push_layers() {
// old links and nodes become older
// and are moved to the next deeper layer
for (let /** @type {?} */ k = this.config.numberOfLayers; k > 0; k--) {
const /** @type {?} */ kp = k - 1;
this._svg.selectAll('.old_edge' + kp).classed('old_edge' + k, true);
this._svg.selectAll('.old_node' + kp).classed('old_node' + k, true);
this._svg.selectAll('.old_edgepath' + kp).classed('old_edgepath' + k, true);
this._svg.selectAll('.old_edgelabel' + kp).classed('old_edgelabel' + k, true);
}
}
/**
* @return {?}
*/
clear_old() {
this.old_Nodes = [];
this.old_Links = [];
}
/**
* @param {?} d
* @return {?}
*/
update_data(d) {
// Save the data
const /** @type {?} */ previous_nodes = this._svg.selectAll('g').filter('.active_node');
const /** @type {?} */ previous_nodes_data = previous_nodes.data();
this.old_Nodes = this.updateAdd(this.old_Nodes, previous_nodes_data);
const /** @type {?} */ previous_links = this._svg.selectAll('.active_edge');
const /** @type {?} */ previous_links_data = previous_links.data();
this.old_Links = this.updateAdd(this.old_Links, previous_links_data);
// handle the pinned nodes
const /** @type {?} */ pinned_Nodes = this._svg.selectAll('g').filter('.pinned');
const /** @type {?} */ pinned_nodes_data = pinned_Nodes.data();
// get the node data and merge it with the pinned nodes
this._Nodes = d.nodes;
this._Nodes = this.updateAdd(this._Nodes, pinned_nodes_data);
// add coordinates to the new active nodes that already existed in the previous step
this._Nodes = this.transfer_coordinates(this._Nodes, this.old_Nodes);
// retrieve the links between nodes and pinned nodes
this._Links = d.links.concat(previous_links_data); // first gather the links
this._Links = this.find_active_links(this._Links, this._Nodes); // then find the ones that are between active nodes
}
/**
* @param {?} array1
* @param {?} array2
* @return {?}
*/
updateAdd(array1, array2) {
// Update lines of array1 with the ones of array2 when the elements' id match
// and add elements of array2 to array1 when they do not exist in array1
const /** @type {?} */ arraytmp = array2.slice(0);
const /** @type {?} */ removeValFromIndex = [];
array1.forEach((d, index, thearray) => {
for (let /** @type {?} */ i = 0; i < arraytmp.length; i++) {
if (d.id === arraytmp[i].id) {
thearray[index] = arraytmp[i];
removeValFromIndex.push(i);
}
}
});
// remove the already updated values (in reverse order, not to mess up the indices)
removeValFromIndex.sort();
for (let /** @type {?} */ i = removeValFromIndex.length - 1; i >= 0; i--) {
arraytmp.splice(removeValFromIndex[i], 1);
}
return array1.concat(arraytmp);
}
/**
* @param {?} list_of_links
* @param {?} active_nodes
* @return {?}
*/
find_active_links(list_of_links, active_nodes) {
// find the links in the list_of_links that are between the active nodes and discard the others
let /** @type {?} */ active_links = [];
list_of_links.forEach((row) => {
for (let /** @type {?} */ i = 0; i < active_nodes.length; i++) {
for (let /** @type {?} */ j = 0; j < active_nodes.length; j++) {
if (active_nodes[i].id === row.source.id && active_nodes[j].id === row.target.id) {
const /** @type {?} */ L_data = new D3Node(row);
L_data.source = row.source.id;
L_data.target = row.target.id;
active_links = active_links.concat(L_data);
}
else if (active_nodes[i].id === row.source && active_nodes[j].id === row.target) {
const /** @type {?} */ L_data = row;
active_links = active_links.concat(L_data);
}
}
}
});
// the active links are in active_links but there can be some duplicates
// remove duplicates links
const /** @type {?} */ dic = {};
for (let /** @type {?} */ i = 0; i < active_links.length; i++) {
dic[active_links[i].id] = active_links[i]; // this will remove the duplicate links (with same id)
}
const /** @type {?} */ list_of_active_links = [];
for (const /** @type {?} */ key of Object.keys(dic)) {
list_of_active_links.push(dic[key]);
}
return list_of_active_links;
}
/**
* @param {?} Nodes
* @param {?} old_Nodes
* @return {?}
*/
transfer_coordinates(Nodes, old_Nodes) {
// Transfer coordinates from old_nodes to the new nodes with the same id
for (let /** @type {?} */ i = 0; i < old_Nodes.length; i++) {
for (let /** @type {?} */ j = 0; j < Nodes.length; j++) {
if (Nodes[j].id === old_Nodes[i].id) {
Nodes[j].x = old_Nodes[i].x;
Nodes[j].y = old_Nodes[i].y;
Nodes[j].fx = old_Nodes[i].x;
Nodes[j].fy = old_Nodes[i].y;
Nodes[j].vx = old_Nodes[i].vx;
Nodes[j].vy = old_Nodes[i].vy;
}
}
}
return Nodes;
}
/**
* @param {?} elem_class
* @param {?} elem_class_old
* @return {?}
*/
remove_duplicates(elem_class, elem_class_old) {
// Remove all the duplicate nodes and edges among the old_nodes and old_edges.
// A node or an edge can not be on several layers at the same time.
selectAll(elem_class).each((d) => {
const /** @type {?} */ ID = d.id;
for (let /** @type {?} */ n = 0; n < this.config.numberOfLayers; n++) {
const /** @type {?} */ list_old_elements = selectAll(elem_class_old + n);
// list_old_nodes_data = list_old_nodes.data();
list_old_elements.each((od) => {
if (od.id === ID) {
select(this).remove();
// console.log('Removed!!')
}
});
}
});
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class GraphShapes {
/**
* @param {?} graphSONFormat
* @param {?} graph_viz
* @param {?} graphexpService
*/
constructor(graphSONFormat, graph_viz, graphexpService) {
this.graphSONFormat = graphSONFormat;
this.graph_viz = graph_viz;
this.graphexpService = graphexpService;
// https://github.com/wbkd/d3-extended
selection.prototype.moveToFront = function () {
// move the selection to the front
return this.each(function () {
this.parentNode.appendChild(this);
});
};
selection.prototype.moveToBack = function () {
// move the selection to the back
return this.each(function () {
const /** @type {?} */ firstChild = this.parentNode.firstChild;
if (firstChild) {
this.parentNode.insertBefore(this, firstChild);
}
});
};
}
/**
* @param {?} nb_layers
* @return {?}
*/
decorate_old_elements(nb_layers) {
// Decrease the opacity of nodes and edges when they get old
for (let /** @type {?} */ k = 0; k < nb_layers; k++) {
selectAll('.old_edge' + k)
.style('opacity', function () {
return 0.8 * (1 - k / nb_layers);
});
selectAll('.old_node' + k)
.style('opacity', function () {
return 0.8 * (1 - k / nb_layers);
});
selectAll('.old_edgelabel' + k)
.style('opacity', function () {
return 0.8 * (1 - k / nb_layers);
});
}
}
/**
* @param {?} value
* @return {?}
*/
show_names(value) {
const /** @type {?} */ text_to_show = selectAll('.text_details');
if (value) {
text_to_show.style('visibility', 'visible');
}
else {
text_to_show.style('visibility', 'hidden');
}
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class GraphLinks {
/**
* @param {?} graphViz
*/
constructor(graphViz) {
this.graphViz = graphViz;
}
/**
* @return {?}
*/
get config() {
return this.graphViz.config;
}
/**
* @return {?}
*/
get graphRoot() {
return this.graphViz.graphRoot;
}
/**
* @return {?}
*/
get linkModels() {
return this.graphViz.linkModels;
}
/**
* @return {?}
*/
get nodeModels() {
return this.graphViz.nodeModels;
}
/**
* @return {?}
*/
get selectLinks() {
return this.graphViz.selectLinks;
}
/**
* @return {?}
*/
get selectEdgePaths() {
return this.graphViz.selectEdgePaths;
}
/**
* @return {?}
*/
get selectEdgeLabels() {
return this.graphViz.selectEdgeLabels;
}
/**
* @return {?}
*/
get selectGraphNodes() {
return this.graphViz.selectGraphNodes;
}
/**
* @param {?} arrangedData
* @return {?}
*/
update(arrangedData) {
// links not active anymore are classified old_links
this.selectLinks.exit().classed('old_edge0', true).classed('active_edge', false);
this.selectEdgePaths.exit().classed('old_edgepath0', true).classed('active_edgepath', false);
this.selectEdgeLabels.exit().classed('old_edgelabel0', true).classed('active_edgelabel', false);
// handling active links associated to the data
const /** @type {?} */ edgepaths_e = this.selectEdgePaths.enter(), /** @type {?} */
edgelabels_e = this.selectEdgeLabels.enter(), /** @type {?} */
link_e = this.selectLinks.enter();
const /** @type {?} */ decor_out = this.decorate(link_e, edgepaths_e, edgelabels_e);
const /** @type {?} */ links = decor_out[0], /** @type {?} */ edgepaths = decor_out[1], /** @type {?} */
edgelabels = decor_out[2];
// previous links plus new links are merged
links.merge(this.selectLinks);
edgepaths.merge(this.selectEdgePaths);
edgelabels.merge(this.selectEdgeLabels);
}
/**
* @return {?}
*/
tick() {
this.selectLinks
.attr('x1', function (d) {
return d.source.x;
})
.attr('y1', function (d) {
return d.source.y;
})
.attr('x2', function (d) {
return d.target.x;
})
.attr('y2', function (d) {
return d.target.y;
});
this.selectEdgePaths.attr('d', function (d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
});
this.selectEdgeLabels.attr('transform', function (d) {
if (d.target.x < d.source.x) {
const /** @type {?} */ bbox = this.getBBox();
const /** @type {?} */ rx = bbox.x + bbox.width / 2;
const /** @type {?} */ ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
}
else {
return 'rotate(0)';
}
});
}
/**
* @param {?} d
* @return {?}
*/
getStrokeWidth(d) {
if ('stroke_width' in d) {
return d.stroke_width;
}
else {
return this.config.default_edge_stroke_width;
}
}
/**
* @param {?} d
* @return {?}
*/
getEdgeText(d) {
if ('text' in d) {
return d.text;
}
else {
return d.properties.weight;
}
}
/**
* @param {?} d
* @return {?}
*/
getEdgeColor(d) {
if ('color' in d) {
return d.color;
}
else {
return this.config.default_edge_color;
}
}
/**
* @param {?} edges
* @param {?} edgepaths
* @param {?} edgelabels
* @return {?}
*/
decorate(edges, edgepaths, edgelabels) {
const /** @type {?} */ edges_deco = edges.append('line').attr('class', 'edge').classed('active_edge', true)
.attr('source_ID', function (d) {
return d.source;
})
.attr('target_ID', function (d) {
return d.target;
})
.attr('ID', function (d) {
return d.id;
});
this.createMarkers(edges_deco);
// Attach the arrows
edges_deco.attr('marker-end', function (d) {
return 'url(#marker_' + d.id + ')';
})
.attr('stroke-width', (d) => this.getStrokeWidth(d))
.append('title').text(function (d) {
return d.properties.weight;
});
// Attach the edge labels
const /** @type {?} */ e_label = this.createEdgeLabels(edgepaths, edgelabels);
const /** @type {?} */ edgepaths_deco = e_label[0];
const /** @type {?} */ edgelabels_deco = e_label[1];
edgelabels_deco.append('textPath')
.attr('class', 'edge_text')
.attr('href', function (d, i) {
return '#edgepath' + d.id;
})
.style('text-anchor', 'middle')
.style('pointer-events', 'none')
.attr('startOffset', '50%')
.text(function (d) {
return d.label;
});
// Attach the edge actions
this.attachEdgeEvents(edges_deco);
// Add property info if checkbox checked
this.addEnabledProperties('edges', edgelabels_deco);
return [edges_deco, edgepaths_deco, edgelabels_deco];
}
/**
* @param {?} id
* @return {?}
*/
nodeModelById(id) {
// return data associated to the node with id 'id'
for (const /** @type {?} */ node in this.nodeModels) {
// console.log(_Nodes[node])
if (this.nodeModels[node].id === id) {
return this.nodeModels[node];
}
}
}
/**
* @param {?} edge_in
* @return {?}
*/
createMarkers(edge_in) {
const /** @type {?} */ edge_data = edge_in.data();
const /** @type {?} */ arrow_data = this.graphRoot.selectAll('.arrow').data();
const /** @type {?} */ data = arrow_data.concat(edge_data);
this.graphRoot.selectAll('.arrow')
.data(data)
.enter()
.append('marker')
.attr('class', 'arrow')
.attr('id', (d) => { return 'marker_' + d.id; })
.attr('markerHeight', 5)
.attr('markerWidth', 5)
.attr('markerUnits', 'strokeWidth')
.attr('orient', 'auto')
.attr('refX', (d) => {
const /** @type {?} */ node = this.nodeModelById(d.target);
return this.graphViz.graphNodes.getNodeSize(node) + this.graphViz.graphNodes.getNodeStrokeWidth(node);
})
.attr('refY', 0)
.attr('viewBox', '0 -5 10 10')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5')
.style('fill', (d) => { return this.getEdgeColor(d); });
}
/**
* @param {?} item
* @param {?} selected_items
* @return {?}
*/
addEnabledProperties(item, selected_items) {
// Add text from a property if the checkbox is checked on the sidebar
const /** @type {?} */ item_properties = [];
for (let /** @type {?} */ prop_idx = 0; prop_idx < item_properties.length; prop_idx++) {
const /** @type {?} */ prop_name = item_properties[prop_idx];
const /** @type {?} */ prop_id_nb = prop_idx;
this.graphViz.attachEnabledProperties(selected_items, prop_name, prop_id_nb, item);
}
}
/**
* @param {?} edgepaths
* @param {?} edgelabels
* @return {?}
*/
createEdgeLabels(edgepaths, edgelabels) {
const /** @type {?} */ edgepaths_deco = edgepaths.append('path')
.attr('class', 'edgepath').classed('active_edgepath', true)
.attr('fill-opacity', 0)
.attr('stroke-opacity', 0)
.attr('id', function (d, i) {
return 'edgepath' + d.id;
})
.attr('ID', function (d) {
return d.id;
})
.style('pointer-events', 'none');
const /** @type {?} */ edgelabels_deco = edgelabels.append('text')
.attr('dy', -3)
.style('pointer-events', 'none')
.attr('class', 'edgelabel').classed('active_edgelabel', true)
.attr('id', function (d, i) {
return 'edgelabel' + d.id;
})
.attr('ID', function (d) {
return d.id;
})
.attr('font-size', 10)
.attr('fill', this.config.edge_label_color);
return [edgepaths_deco, edgelabels_deco];
}
/**
* @param {?} edge
* @return {?}
*/
attachEdgeEvents(edge) {
edge.on('mouseover', (theEdge, index, elements) => {
console.log('mouse over!!');
const /** @type {?} */ line = elements[index];
select(line).selectAll('.text_details').style('visibility', 'visible');
})
.on('mouseout', (theEdge, index, elements) => {
const /** @type {?} */ line = elements[index];
select(line).selectAll('.text_details').style('visibility', 'hidden');
})
.on('click', (theEdge, index, elements) => {
console.log('edge clicked!');
});
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class GraphNodes {
/**
* @param {?} graphViz
*/
constructor(graphViz) {
this.graphViz = graphViz;
this.connectionCreated = new BehaviorSubject(null);
}
/**
* @return {?}
*/
get config() {
return this.graphViz.config;
}
/**
* @return {?}
*/
get graphRoot() {
return this.graphViz.graphRoot;
}
/**
* @return {?}
*/
get nodeModels() {
return this.graphViz.nodeModels;
}
/**
* @return {?}
*/
get simulation() {
return this.graphViz.simulation;
}
/**
* @return {?}
*/
get isShifted() {
return window.event['shiftKey'] === true;
}
/**
* get all active nodes in the graph
* @return {?}
*/
get graphNodes() {
return this.graphViz.selectGraphNodes;
}
/**
* @param {?} relativeNode
* @return {?}
*/
mouseXY(relativeNode) {
const /** @type {?} */ xy = mouse(relativeNode);
return {
x: xy[0],
y: xy[1]
};
}
/**
* for each tick
* @return {?}
*/
tick() {
this.graphNodes
.attr('transform', function (d) {
return `translate(${d.x}, ${d.y})`;
});
}
/**
* update the node data in the graph
* @param {?} arrangedData
* @return {?}
*/
update(arrangedData) {
console.log('GraphNodes#update');
// old nodes not active any more are tagged
this.graphNodes.exit().classed('old_node0', true).classed('active_node', false);
// nodes associated to the data are constructed
let /** @type {?} */ nodes = this.graphNodes.enter();
// add node decoration
const /** @type {?} */ node_deco = this.decorateNodes(nodes);
nodes = node_deco.merge(nodes);
}
/**
* @param {?} node
* @return {?}
*/
decorateNodes(node) {
const /** @type {?} */ _self = this;
const /** @type {?} */ node_deco = node.append('g')
.attr('class', 'active_node').attr('ID', function (d) {
return d.id;
})
.classed('node', true);
// Attach the event listener
this.attachNodeEvents(node_deco);
node_deco.moveToFront();
// Create the circle shape
const /** @type {?} */ node_base_circle = node_deco.append('circle').classed('base_circle', true)
.attr('r', (d) => this.getNodeSize(d))
.style('stroke-width', (d) => this.getNodeStrokeWidth(d))
.style('stroke', 'black')
.attr('fill', (d) => this.getNodeColor(d));
node_base_circle.append('title').text(d => this.getNodeText(d));
// Add the text to the nodes
node_deco.append('text').classed('text_details', true)
.attr('x', (d) => {
return this.config.default_node_size + 2;
})
.text(d => this.getNodeText(d))
.style('visibility', 'hidden');
node_deco.append('text').classed('text_details', true)
.attr('x', (d) => {
return this.config.default_node_size + 4;
})
.attr('y', this.config.default_node_size)
.text(d => this.getNodeSubText(d))
.style('visibility', 'hidden');
// Add the node pin
const /** @type {?} */ node_pin = node_deco.append('circle').classed('Pin', true)
.attr('r', (d) => {
return this.config.default_node_size / 2;
})
.attr('transform', (d) => {
return 'translate(' + (this.config.default_node_size * 3 / 4) + ',' + (-this.config.default_node_size * 3 / 4) + ')';
})
.attr('fill', this.config.default_node_color)
.moveToBack()
.style('visibility', 'hidden');
node_pin.on('click', function (d) {
_self.pinIt(this, d);
});
// spot the active node and draw additional circle around it
/* TODO: make active node different
if (with_active_node) {
d3.selectAll('.active_node').each((d) => {
if (d.id === with_active_node) {
const n_radius = Number(d3.select(this).select('.base_circle').attr('r')) + this.active_node_margin;
d3.select(this)
.append('circle').classed('focus_node', true)
.attr('r', n_radius)
.attr('fill', this.node_color)
.attr('opacity', this.active_node_margin_opacity)
.moveToBack();
}
});
}
*/
return node_deco;
}
/**
* @param {?} node
* @return {?}
*/
attachNodeEvents(node) {
const /** @type {?} */ _self = this;
node.call(drag()
.on('start', function (d) {
if (_self.isShifted) {
_self.dragConnectionStarted(d);
}
else {
_self.dragNodeStarted(d);
}
})
.on('drag', function (d) {
if (_self.isShifted) {
_self.draggingConnection(d);
}
else {
_self.draggingNode(d);
}
})
.on('end', (ev) => {
if (this.isShifted) {
this.dragConnectionEnded(ev);
}
else {
this.dragNodeEnded(ev);
}
}));
node.on('click', (ev) => { this.clicked(ev); })
.on('mouseover', function () {
select(this).select('.Pin').style('visibility', 'visible');
select(this).selectAll('.text_details').style('visibility', 'visible');
})
.on('mouseout', function () {
const /** @type {?} */ chosen_node = select(this);
if (!chosen_node.classed('pinned')) {
select(this).select('.Pin').style('visibility', 'hidden');
}
if (!this.show_name) {
select(this).selectAll('.text_details').style('visibility', 'hidden');
}
});
}
/**
* @param {?} node_id
* @return {?}
*/
getConnectedEdgesByNodeId(node_id) {
// Return the in and out edges of node with id 'node_id'
const /** @type {?} */ connected_edges = selectAll('.edge').filter(function (item) {
if (item.source === node_id || item.source.id === node_id) {
return item;
}
else if (item.target === node_id || item.target.id === node_id) {
return item;
}
});
return connected_edges;
}
/**
* @param {?} d
* @return {?}
*/
dragConnectionStarted(d) {
this.mouseDownNode = d;
}
/**
* @param {?} d
* @return {?}
*/
dragNodeStarted(d) {
if (!event.active) {
this.simulation.alphaTarget(0.3).restart();
}
d.fx = d.x;
d.fy = d.y;
}
/**
* @param {?} d
* @return {?}
*/
draggingConnection(d) {
// reposition dragged directed edge
if (!this.mouseDownNode) {
return;
}
const /** @type {?} */ dragLine = this.graphViz.dragLine.classed('hidden', false);
const /** @type {?} */ transform = this.graphRoot.attr('transform');
this.graphViz.dragLine
.attr('d', `M ${d.x},${d.y} L ${event.x}, ${event.y}`)
.attr('transform', transform);
console.log('dragging new connection');
}
/**
* @param {?} d
* @return {?}
*/
draggingNode(d) {
const /** @type {?} */ connected_edges = this.getConnectedEdgesByNodeId(d.id);
const /** @type {?} */ f_connected_edges = connected_edges.filter('*:not(.active_edge)');
if (f_connected_edges._groups[0].length === 0) {
d.fx = event.x;
d.fy = event.y;
}
else {
f_connected_edges
.style('stroke-width', function () {
return parseInt(select(this).attr('stroke-width'), 10) + 2;
})
.style('stroke-opacity', 1)
.classed('blocking', true);
}
}
/**
* @param {?} d
* @return {?}
*/
dragConnectionEnded(d) {
const /** @type {?} */ target = event.sourceEvent.toElement;
this.graphViz.dragLine.classed('hidden', true);
console.log(`connecting to: ${target}`);
this.connectionCreated.next({
source: this.mouseDownNode,
target: select(target).data()[0]
});
}
/**
* @param {?} d
* @return {?}
*/
dragNodeEnded(d) {
if (!event.active) {
this.simulation.alphaTarget(0);
}
selectAll('.blocking')
.style('stroke-width', function () {
return select(this).attr('stroke-width');
})
.style('stroke-opacity', function () {
return select(this).attr('stroke-opacity');
})
.classed('blocking', false);
// d.fx = null;
// d.fy = null;
}
/**
* @param {?} d
* @return {?}
*/
clicked(d) {
select('.focus_node').remove();
// TODO: wire up 'freeze' button
// const input = document.getElementById('freeze-in');
// const isChecked = input.checked;
// if (isChecked) {
// infobox.display_info(d);
// } else {
this.simulation.stop();
// remove the oldest links and nodes
const /** @type {?} */ stop_layer = this.graphViz.graphLayers.depth() - 1;
this.graphRoot.selectAll('.old_node' + stop_layer).remove();
this.graphRoot.selectAll('.old_edge' + stop_layer).remove();
this.graphRoot.selectAll('.old_edgepath' + stop_layer).remove();
this.graphRoot.selectAll('.old_edgelabel' + stop_layer).remove();
this.graphViz.displayInfo(d);
this.graphViz.loadRelatedNodes(d);
console.log('node clicked');
}
/**
* @param {?} elem
* @param {?} data
* @return {?}
*/
pinIt(elem, data) {
event.stopPropagation();
const /** @type {?} */ node_pin = select(elem);
const /** @type {?} */ pinned_node = select(elem.parentNode);
if (!pinned_node.empty() && pinned_node.classed('active_node')) {
if (!pinned_node.classed('pinned')) {
pinned_node.classed('pinned', true);
console.log('Pinned!');
node_pin.attr('fill', '#000');
pinned_node.moveToFront();
}
else {
pinned_node.classed('pinned', false);
console.log('Unpinned!');
node_pin.attr('fill', () => this.getNodeColor(data));
}
}
}
/**
* @param {?} prop_name
* @return {?}
*/
colorize(prop_name) {
// Color the nodes according the value of the property 'prop_name'
let /** @type {?} */ node_code_color = null;
const /** @type {?} */ value_list = selectAll('.node').data();
if (prop_name === 'none') {
selectAll('.base_circle').style('fill', (d) => {
return this.getNodeColor(d);
});
selectAll('.Pin').style('fill', (d) => {
return this.getNodeColor(d);
});
}
else if (prop_name === 'label') {
const /** @type {?} */ value_set = new Set(value_list.map((d) => {
return d.label;
}));
node_code_color = scaleOrdinal().domain(value_set).range(range(0, value_set.size));
selectAll('.base_circle').style('fill', (d) => {
return this.config.colorPalette(node_code_color(d.label));
});
selectAll('.Pin').style('fill', (d) => {
return this.config.colorPalette(node_code_color(d.label));
});
}
else {
const /** @type {?} */ value_set = new Set(value_list.map((d) => {
if (typeof d.properties[prop_name] !== 'undefined') {
return d.properties[prop_name][0].value;
}
}));
node_code_color = scaleOrdinal().domain(value_set).range(range(0, value_set.size));
selectAll('.base_circle').style('fill', (d) => {
if (typeof d.properties[prop_name] !== 'undefined') {
return this.config.colorPalette(node_code_color(d.properties[prop_name][0].value));
}
return this.getNodeColor(d);
});
selectAll('.Pin').style('fill', (d) => {
if (typeof d.properties[prop_name] !== 'undefined') {
return this.config.colorPalette(node_code_color(d.properties[prop_name][0].value));
}
return this.getNodeColor(d);
});
}
}
/**
* @param {?} d
* @return {?}
*/
getNodeSize(d) {
if ('size' in d) {
return d.size;
}
else {
return this.config.default_node_size;
}
}
/**
* @param {?} d
* @return {?}
*/
getNodeStrokeWidth(d) {
if ('stroke_width' in d) {
return d.stroke_width;
}
else {
return this.config.default_stroke_width;
}
}
/**
* @param {?} d
* @return {?}
*/
getNodeColor(d) {
return this.config.default_node_color;
/*
if (colored_prop !== 'none') {
if (colored_prop === 'label') {
return this.color_palette(node_code_color(d.label));
} else if (typeof d.properties[colored_prop] !== 'undefined') {
if (this.graphSONFormat === GraphsonFormat.GraphSON3) {
return this.color_palette(node_code_color(d.properties[colored_prop]['summary']));
} else {
return this.color_palette(node_code_color(d.properties[colored_prop][0].value));
}
} else if ('color' in d.properties) {
return d.properties.color[0].value;
} else {
return this.default_node_color;
}
} else if ('color' in d.properties) {
return d.properties.color[0].value;
} else {
return this.default_node_color;
} */
}
/**
* @param {?} d
* @return {?}
*/
getNodeTitle(d) {
if ('node_title' in d) {
return d.node_title;
}
else {
return d.label;
}
}
/**
* @param {?} d
* @return {?}
*/
getNodeText(d) {
if ('node_text' in d) {
return d.node_text;
}
else {
return d.id;
}
}
/**
* @param {?} d
* @return {?}
*/
getNodeSubText(d) {
if ('node_subtext' in d) {
return d.node_subtext;
}
else {
return d.label;
}
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class GraphViz {
/**
* @param {?} graphexpService
* @param {?} _config
*/
constructor(graphexpService, _config) {
this.graphexpService = graphexpService;
this._config = _config;
this._graphWidth = 0;
this._graphHeight = 0;
this._simulation = {};
this.state = {
shiftNodeDrag: false
};
this.selectedNode = new BehaviorSubject(null);
this.createNodeEvent = new BehaviorSubject(null);
}
/**
* @return {?}
*/
get config() {
return this._config;
}
/**
* @return {?}
*/
get simulation() {
return this._simulation;
}
/**
* @return {?}
*/
get graphRoot() {
return this._graphRoot;
}
/**
* @return {?}
*/
get nodeModels() {
return this._graphLayers.nodes;
}
/**
* @return {?}
*/
get graphLayers() {
return this._graphLayers;
}
/**
* @return {?}
*/
get graphNodes() {
return this._graphNodes;
}
/**
* @return {?}
*/
get linkModels() {
return this._graphLayers._Links;
}
/**
* @return {?}
*/
get selectLinks() {
const /** @type {?} */ all_links = this.graphRoot.selectAll('.active_edge')
.data(this.linkModels, (n) => {
return n.id;
});
return all_links;
}
/**
* @return {?}
*/
get selectEdgePaths() {
const /** @type {?} */ all_edgepaths = this.graphRoot.selectAll('.active_edgepath')
.data(this.linkModels, (n) => {
return n.id;
});
return all_edgepaths;
}
/**
* @return {?}
*/
get selectEdgeLabels() {
const /** @type {?} */ all_edgelabels = this.graphRoot.selectAll('.active_edgelabel')
.data(this.linkModels, (n) => {
return n.id;
});
return all_edgelabels;
}
/**
* get all active nodes in the graph
* @return {?}
*/
get selectGraphNodes() {
// Existing active nodes
const /** @type {?} */ allNodes = this.graphRoot.selectAll('g').filter('.active_node')
.data(this.nodeModels, (n) => {
return n.id;
});
return allNodes;
}
/**
* @return {?}
*/
clear() {
console.log(this._simulation);
if (Object.keys(this._simulation).length !== 0) {
this._simulation.stop();
this._simulation.nodes([]);
this._simulation.force('link').links([]);
}
this._graphRoot.selectAll('*').remove();
this.nodeModels.length = 0, this.linkModels.length = 0;
this._graphLayers.clear_old();
this._simulation = {};
}
/**
* @param {?} svg
* @return {?}
*/
addzoom(svg) {
// Add zoom to the svg object
svg.append('rect')
.attr('width', this._graphWidth).attr('height', this._graphHeight)
.style('fill', 'none').style('pointer-events', 'all')
.call(zoom().scaleExtent([1 / 2, 4]).on('zoom', () => {
this._graphRoot.attr('transform', event.transform);
}));
}
/**
* @param {?} center_f
* @return {?}
*/
simulationStart(center_f) {
let /** @type {?} */ force_x, /** @type {?} */ force_y;
this.
_simulation = forceSimulation()
.force('charge', forceManyBody().strength(this._config.force_strength))
.force('link', forceLink().strength(this._config.link_strength).id((d) => {
return d.id;
}));
if (center_f === 1) {
force_y = this._config.force_x_strength;
force_x = this._config.force_y_strength;
this._simulation.force('center', forceCenter(this._graphWidth / 2, this._graphHeight / 2));
}
else {
force_y = 0;
force_x = 0;
}
this._simulation.force('y', forceY().strength((d) => {
return force_y;
}))
.force('x', forceX().strength((d) => {
return force_x;
}));
return this._simulation;
}
/**
* @param {?} arrangedData
* @param {?} center_f
* @param {?} with_active_node
* @return {?}
*/
refreshData(arrangedData, center_f, with_active_node) {
// Main visualization function
const /** @type {?} */ svg_graph = this.graphRoot;
this._graphLayers.push_layers();
this._graphLayers.update_data(arrangedData);
this._graphLinks.update(arrangedData);
this._graphNodes.update(arrangedData);
// Additional clean up
this._graphShapes.decorate_old_elements(this._graphLayers.depth());
svg_graph.selectAll('g').filter('.pinned').moveToFront();
this._graphLayers.remove_duplicates('.active_node', '.old_node');
this._graphLayers.remove_duplicates('.active_edge', '.old_edge');
this._graphLayers.remove_duplicates('.active_edgepath', '.old_edgepath');
this._graphLayers.remove_duplicates('.active_edgelabel', '.old_edgelabel');
// Force simulation simulation model and paramers Associate the simulation with the data
this._simulation = this.simulationStart(center_f);
this._simulation.nodes(this.nodeModels).on('tick', () => {
this._graphNodes.tick();
this._graphLinks.tick();
});
this._simulation.force('link').links(this.linkModels);
this._simulation.alphaTarget(0);
}
/**
* @param {?} prop
* @return {?}
*/
displayShapeProperty(prop) {
const /** @type {?} */ prop_id = prop.id;
const /** @type {?} */ prop_id_nb = prop.getAttribute('id_nb');
const /** @type {?} */ prop_name = prop_id.slice(prop_id.indexOf('_') + 1);
const /** @type {?} */ item = prop_id.slice(0, prop_id.indexOf('_'));
console.log(prop_id, item);
if (select('#' + prop_id).property('checked')) {
let /** @type {?} */ elements_text;
if (item === 'nodes') {
elements_text = selectAll('.node');
}
else if (item === 'edges') {
elements_text = selectAll('.edgelabel');
}
this.attachEnabledProperties(elements_text, prop_name, prop_id_nb, item);
}
else {
if (item === 'nodes') {
selectAll('.node').select('.' + prop_id).remove();
}
else if (item === 'edges') {
selectAll('.edgelabel').select('.' + prop_id).remove();
}
}
}
/**
* @param {?} graph_objects
* @param {?} prop_name
* @param {?} prop_id_nb
* @param {?} item
* @return {?}
*/
attachEnabledProperties(graph_objects, prop_name, prop_id_nb, item) {
let /** @type {?} */ elements_text;
const /** @type {?} */ text_base_offset = 10;
const /** @type {?} */ text_offset = 10;
const /** @type {?} */ prop_id = item + '_' + prop_name;
if (item === 'nodes') {
elements_text = graph_objects.append('text').style('pointer-events', 'none');
}
else if (item === 'edges') {
elements_text = graph_objects.append('textPath')
.attr('class', 'edge_text')
.attr('href', function (d, i) { return '#edgepath' + d.id; })
.style('text-anchor', 'middle')
.style('pointer-events', 'none')
.attr('startOffset', '70%');
prop_id_nb = prop_id_nb + 1;
}
else {
console.log('Bad item name.');
return 1;
}
elements_text.classed('prop_details', true).classed(prop_id, true)
.attr('dy', (d) => {
return this._graphNodes.getNodeSize(d) + text_base_offset + text_offset * parseInt(prop_id_nb, 10);
})
.text((d) => {
return this.getPropertyValue(d, prop_name, item);
});
}
/**
* @param {?} d
* @param {?} prop_name
* @param {?} item
* @return {?}
*/
getPropertyValue(d, prop_name, item) {
if (prop_name in d.properties) {
if (item === 'nodes') {
if (this._config.format === GraphsonFormat.GraphSON3) {
return d.properties[prop_name]['summary'];
}
else if (this._config.format === GraphsonFormat.GraphSON1) {
return d.properties[prop_name][0].value;
}
}
else if (item === 'edges') {
console.log(d.properties[prop_name]);
return d.properties[prop_name];
}
}
else {
return '';
}
}
/**
* @param {?} data
* @return {?}
*/
colorize(data) {
this._graphNodes.colorize(data);
}
/**
* @param {?} data
* @return {?}
*/
displayInfo(data) {
this.selectedNode.next(data);
}
/**
* @param {?} d
* @return {?}
*/
loadRelatedNodes(d) {
this.graphexpService.getRelatedNodes(d).then((arrangedData) => {
this.refreshData(arrangedData, 1, null);
this.displayInfo(d);
});
}
/**
* @param {?} svg
* @return {?}
*/
addSvgDefinitions(svg) {
// define arrow markers for graph links
const /** @type {?} */ defs = svg.append('svg:defs');
defs.append('svg:marker')
.attr('id', 'end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', '32')
.attr('markerWidth', 5)
.attr('markerHeight', 5)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5');
// define arrow markers for leading arrow
defs.append('svg:marker')
.attr('id', 'mark-end-arrow')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 7)
.attr('markerWidth', 5)
.attr('markerHeight', 5)
.attr('orient', 'auto')
.append('svg:path')
.attr('d', 'M0,-5L10,0L0,5');
}
/**
* @param {?} label
* @return {?}
*/
init(label) {
this._graphLayers = new GraphLayers(this);
this._graphShapes = new GraphShapes(this._config.format, this, this.graphexpService);
// GraphNodes class init
this._graphNodes = new GraphNodes(this);
this.connectionCreated = new Observable(observer => {
this._graphNodes.connectionCreated.subscribe(val => {
if (val != null) {
observer.next(val);
console.log(`connection created: ${val.source.id} -> ${val.target.id}`);
}
});
});
this._graphLinks = new GraphLinks(this);
const /** @type {?} */ svg = select(label).select('svg');
const /** @type {?} */ width = +select(label).node().getBoundingClientRect().width;
const /** @type {?} */ height = +select(label).node().getBoundingClientRect().height;
this._graphWidth = width;
this._graphHeight = height;
// displayed when dragging between nodes
this.dragLine = svg.append('svg:path')
.attr('class', 'link drag-line hidden')
.attr('d', 'M0,0L0,0')
.style('marker-end', 'url(#mark-end-arrow)');
svg.attr('width', this._graphWidth).attr('height', this._graphHeight);
this.addSvgDefinitions(svg);
this.addzoom(svg);
// Finally create a root g node for all the nodes/links
this._graphRoot = svg.append('g');
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class LinkEditComponent {
/**
* @param {?} dialogRef
* @param {?} data
*/
constructor(dialogRef, data) {
this.dialogRef = dialogRef;
this.data = data;
}
/**
* @return {?}
*/
createProperty() {
this.data.item.properties.push({ key: '', value: '' });
}
/**
* @return {?}
*/
ngOnInit() {
}
}
LinkEditComponent.decorators = [
{ type: Component, args: [{
selector: 'sv-link-edit',
template: `<h2 mat-dialog-title>Edit Link</h2>
<mat-dialog-content>
<div fxLayout fxLayoutAlign="start center" fxLayoutGap="1em">
<mat-input-container>
<mat-select [(ngModel)]="data.item.label" placeholder="Label">
<mat-option *ngFor="let item of data.labels" [value]="item">{{item}}</mat-option>
</mat-select>
</mat-input-container>
<div>
<button mat-raised-button (click)="createProperty()"><mat-icon>add</mat-icon>Add Property</button>
</div>
</div>
<div fxLayout fxLayoutGap="1em" *ngFor="let item of data.item.properties">
<mat-input-container>
<input matInput placeholder="property name" [(ngModel)]="item.key">
</mat-input-container>
<mat-input-container>
<input matInput placeholder="property value" [(ngModel)]="item.value">
</mat-input-container>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close>Cancel</button>
<!-- The mat-dialog-close directive optionally accepts a value as a result for the dialog. -->
<button mat-button [mat-dialog-close]="data.item">Confirm</button>
</mat-dialog-actions>`,
styles: [``]
},] },
];
/** @nocollapse */
LinkEditComponent.ctorParameters = () => [
{ type: MatDialogRef, },
{ type: undefined, decorators: [{ type: Inject, args: [MAT_DIALOG_DATA,] },] },
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class NodeEditComponent {
/**
* @param {?} dialogRef
* @param {?} data
*/
constructor(dialogRef, data) {
this.dialogRef = dialogRef;
this.data = data;
}
/**
* @return {?}
*/
createProperty() {
this.data.item.properties.push(new KV());
}
/**
* @return {?}
*/
ngOnInit() {
}
}
NodeEditComponent.decorators = [
{ type: Component, args: [{
selector: 'sv-node-edit',
template: `<h2 mat-dialog-title>Edit Node</h2>
<mat-dialog-content>
<div fxLayout fxLayoutAlign="start center" fxLayoutGap="1em">
<mat-input-container>
<mat-select [(ngModel)]="data.item.label" placeholder="Label">
<mat-option *ngFor="let item of data.labels" [value]="item">{{item}}</mat-option>
</mat-select>
</mat-input-container>
<div>
<button mat-raised-button (click)="createProperty()"><mat-icon>add</mat-icon>Add Property</button>
</div>
</div>
<div fxLayout fxLayoutGap="1em" *ngFor="let item of data.item.properties">
<mat-input-container>
<input #propertyName matInput placeholder="property name" [(ngModel)]="item.key" />
</mat-input-container>
<mat-input-container>
<input #propertyValue matInput placeholder="property value" [(ngModel)]="item.value" />
</mat-input-container>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close>Cancel</button>
<!-- The mat-dialog-close directive optionally accepts a value as a result for the dialog. -->
<button mat-button [mat-dialog-close]="data.item">Confirm</button>
</mat-dialog-actions>`,
styles: [``]
},] },
];
/** @nocollapse */
NodeEditComponent.ctorParameters = () => [
{ type: MatDialogRef, },
{ type: undefined, decorators: [{ type: Inject, args: [MAT_DIALOG_DATA,] },] },
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class GremlinNode {
constructor() {
this.label = '';
this.properties = [];
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class GremlinLink extends GremlinNode {
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class GraphexpComponent {
/**
* @param {?} dialog
*/
constructor(dialog) {
this.dialog = dialog;
this.searchValue = '';
this.searchField = 'id';
this.numberOfLayers = 3;
this.showGraphInfo = true;
this.newNode = {};
}
/**
* @return {?}
*/
get selectedNode() {
if (this.graphViz && this.graphViz.selectedNode && this.graphViz.selectedNode.value) {
return this.graphViz.selectedNode.value;
}
else {
return null;
}
}
/**
* @return {?}
*/
get nodeNames() {
return this.graphexpService.nodeNames;
}
;
/**
* @return {?}
*/
get nodeProperties() {
return this.graphexpService.nodeProperties;
}
;
/**
* @return {?}
*/
get edgeProperties() {
return this.graphexpService.edgeProperties;
}
;
/**
* @return {?}
*/
get enableEdit() {
return (this.graphConfig && this.graphConfig.enableEdit);
}
/**
* @return {?}
*/
get nodeLabels() {
return this.graphConfig.nodeLabels;
}
/**
* @return {?}
*/
get linkLabels() {
return this.graphConfig.linkLabels;
}
/**
* @return {?}
*/
ngOnInit() {
if (!this.graphConfig) {
this.graphConfig = new GraphConfig();
}
this.graphViz = new GraphViz(this.graphexpService, this.graphConfig);
setTimeout(() => {
this.graphViz.init('#sv_graphexp');
this.graphexpService.queryGraphInfo();
this.graphViz.connectionCreated.subscribe((val) => {
console.log(`GraphexpComponent#ngAfterViewInit: connection created ${val}`);
const /** @type {?} */ gremlinLink = new GremlinLink();
gremlinLink.source = val.source.id;
gremlinLink.target = val.target.id;
this.openLinkEditDialog(gremlinLink);
});
this.graphViz.createNodeEvent.subscribe((d3Node) => {
if (d3Node === null) {
return;
}
const /** @type {?} */ gremlinNode = new GremlinNode();
this.openNodeEditDialog(gremlinNode);
});
});
}
/**
* @param {?} item
* @return {?}
*/
openLinkEditDialog(item) {
const /** @type {?} */ dialogRef = this.dialog.open(LinkEditComponent, {
width: '30em',
data: { labels: this.linkLabels, item: item }
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
if (result) {
this.createLink(result);
}
});
}
/**
* @param {?=} item
* @return {?}
*/
openNodeEditDialog(item) {
item = item || new GremlinNode();
const /** @type {?} */ dialogRef = this.dialog.open(NodeEditComponent, {
width: '30em',
data: { labels: this.nodeLabels, item: item }
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
if (result) {
this.createNode(result);
}
});
}
/**
* @param {?} data
* @return {?}
*/
createLink(data) {
this.graphexpService.createLink(data).then(tinkerNode => {
console.log(data);
}, err => { console.error(err); });
}
/**
* @param {?} data
* @return {?}
*/
createNode(data) {
this.graphexpService.createNode(data.label, data.properties).then(tinkerNode => {
console.log(data);
}, err => { console.error(err); });
}
/**
* @return {?}
*/
search() {
console.log(`searching field: ${this.searchField}, value: ${this.searchValue}`);
this.graphexpService.queryNodes(this.searchField, this.searchValue).then(data => {
this.graphViz.refreshData(data, 1, null);
}).catch((err) => {
console.error(err);
});
}
/**
* @param {?} node
* @return {?}
*/
getFlattenedNodeProperties(node) {
const /** @type {?} */ props = [];
for (const /** @type {?} */ prop of Object.keys(node.properties)) {
const /** @type {?} */ valArray = node.properties[prop];
const /** @type {?} */ val = valArray[0]['value'];
props.push({
name: prop,
value: val
});
}
return props;
}
/**
* @return {?}
*/
showNames() {
}
/**
* @return {?}
*/
setNumberOfLayers() {
this.graphConfig.numberOfLayers = this.numberOfLayers;
}
/**
* @return {?}
*/
clearGraph() {
this.graphViz.clear();
}
/**
* @return {?}
*/
toggleGraphInfo() {
this.showGraphInfo = !this.showGraphInfo;
}
/**
* @return {?}
*/
getGraphInfo() {
this.graphexpService.queryGraphInfo();
}
}
GraphexpComponent.decorators = [
{ type: Component, args: [{
selector: 'sv-graphexp',
template: `<div fxLayout fxFlexFill class="content sv-graphexp">
<mat-sidenav-container fxFLex>
<mat-sidenav #graphexpSideMenu position="end" fxLayout="column" fxLayoutGap="1.5em">
<button mat-mini-fab color="accent" (click)="graphexpSideMenu.toggle()">
<mat-icon>arrow_right</mat-icon>
</button>
<div fxLayout fxLayoutGap="1em">
<button mat-raised-button (click)="getGraphInfo()">
Refresh<mat-icon>refresh</mat-icon>
</button>
<button mat-raised-button (click)="openNodeEditDialog()">
Create<mat-icon>add</mat-icon>
</button>
<span fxFlex></span>
<button mat-raised-button (click)="clearGraph()">
Clear<mat-icon>clear</mat-icon>
</button>
</div>
<mat-checkbox [(ngModel)]="showGraphInfo">Show Graph Info</mat-checkbox>
<mat-checkbox>Show Selection Properties</mat-checkbox>
<mat-checkbox (click)="showNames()">Show Labels</mat-checkbox>
<mat-checkbox>Freeze Graph</mat-checkbox>
<section>
<div fxLayout>
<label>Visible Layers</label>
<mat-slider min="1" max="5" [(ngModel)]="numberOfLayers" thumbLabel tickInterval="1"></mat-slider>
</div>
</section>
</mat-sidenav>
<div fxLayout="column" fxFlexFill>
<mat-toolbar fxLayoutGap="1em">
<label>Search</label>
<mat-form-field>
<mat-select [(ngModel)]="searchField">
<mat-optgroup label="Node">
<mat-option value="id">id</mat-option>
<mat-option *ngFor="let item of nodeProperties | async" [value]="item">{{item}}</mat-option>
</mat-optgroup>
<mat-optgroup label="Edge">
<mat-option value="id">id</mat-option>
<mat-option *ngFor="let item of edgeProperties | async" [value]="item">{{item}}</mat-option>
</mat-optgroup>
</mat-select>
</mat-form-field>
<mat-input-container>
<input matInput name="searchValue" [(ngModel)]="searchValue" placeholder="Id/Keyword">
</mat-input-container>
<button (click)="search()" mat-mini-fab>
<mat-icon>search</mat-icon>
</button>
<button mat-mini-fab (click)="openNodeEditDialog()">
<mat-icon>add</mat-icon>
</button>
<span fxFlex></span>
<button mat-mini-fab (click)="graphexpSideMenu.toggle()">
<mat-icon>menu</mat-icon>
</button>
</mat-toolbar>
<div class="sv-graphexp-content">
<div class="sv-graphexp-left-bar">
<div *ngIf="showGraphInfo"><br/>
<strong>Node Names</strong>
<div *ngFor="let item of nodeNames | async">
{{item.key}}: {{item.value}}
</div>
</div>
</div>
<div class="sv-graphexp-right-bar">
<strong>Selected Node</strong>
<div *ngIf="selectedNode">
<div>Label: {{selectedNode.label}}</div>
<div>Type: {{selectedNode.type}}</div>
<div *ngFor="let prop of getFlattenedNodeProperties(selectedNode)">
<div>{{prop.name}}: {{prop.value}}</div>
</div>
</div>
</div>
<div class="sv-graphexp" id="sv_graphexp">
<svg></svg>
</div>
</div>
</div>
</mat-sidenav-container>
</div>
`,
styles: [`.hidden{
display:none;
}
.sv-graphexp mat-sidenav-container{
width:100%;
}
.sv-graphexp mat-sidenav{
padding:1em;
width:25em;
}
.sv-graphexp mat-divider{
padding:5px 0;
}
div.sv-graphexp{
height:100%;
}
.sv-graphexp-content{
padding:1em;
position:relative;
top:0;
height:100%;
}
.sv-graphexp-left-bar{
position:absolute;
top:0;
width:10em;
}
.sv-graphexp-right-bar{
position:absolute;
top:0;
right:0;
width:10em;
}
path.drag-line{
fill:none;
stroke:#333;
stroke-width:4px;
stroke-dasharray:5 5;
cursor:default;
}
.edge{
stroke:#999;
stroke-opacity:0.8;
}
.old_edge0{
stroke:#999;
stroke-opacity:0.6;
}
.node circle{
stroke:#000;
stroke-width:1.5px;
}
.node text{
font:10px sans-serif;
}
.node:hover circle{
stroke-opacity:0.6;
}
.pinned circle{
stroke:#000;
stroke-width:1.5px;
}
.pinned text{
font:10px sans-serif;
}
.pinned:hover circle{
stroke-opacity:0.6;
}
.old_node0 circle{
stroke-opacity:0.9;
}
.old_node0 text{
font:10px sans-serif;
opacity:0.9;
color:#000;
color:rgba(0, 0, 0, 0.5);
}
.cell{
fill:none;
pointer-events:all;
}`],
encapsulation: ViewEncapsulation.None
},] },
];
/** @nocollapse */
GraphexpComponent.ctorParameters = () => [
{ type: MatDialog, },
];
GraphexpComponent.propDecorators = {
"graphexpService": [{ type: Input },],
"graphConfig": [{ type: Input },],
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class GraphexpModule {
}
GraphexpModule.decorators = [
{ type: NgModule, args: [{
imports: [
CommonModule,
FormsModule,
MatSidenavModule,
MatButtonModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatListModule,
MatSelectModule,
MatCheckboxModule,
MatToolbarModule,
MatSliderModule,
MatDialogModule,
FlexLayoutModule
],
declarations: [GraphexpComponent, NodeEditComponent, LinkEditComponent],
entryComponents: [NodeEditComponent, LinkEditComponent],
providers: [],
exports: [
GraphexpComponent,
CommonModule,
FormsModule,
MatSidenavModule,
MatButtonModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatListModule,
MatSelectModule,
MatCheckboxModule,
MatToolbarModule,
MatSliderModule,
MatDialogModule,
FlexLayoutModule
]
},] },
];
/** @nocollapse */
GraphexpModule.ctorParameters = () => [];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* Generated bundle index. Do not edit.
*/
export { GraphexpModule, GraphConfig, GraphexpComponent, GraphexpService, LinkEditComponent as ɵb, NodeEditComponent as ɵa };
//# sourceMappingURL=savantly-ngx-graphexp.js.map