2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright © 2017-2021 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 import React from 'react';
22 import * as d3 from "d3";
23 import 'd3-selection-multi';
24 import {GlobalExtConstants} from 'utils/GlobalExtConstants.js';
26 let INVLIST = GlobalExtConstants.INVLIST;
27 let PAGINATION_CONSTANT = GlobalExtConstants.PAGINATION_CONSTANT;
30 * This function will create a visualization from query outputs
34 var populateGraphObject = (nodes, links, data) => {
35 for(var i = 0; i < data.results.length; i++){
36 nodes[data.results[i].url] = data.results[i];
37 let nodeType = data.results[i]['node-type'];
38 nodes[data.results[i].url].weight = 1/((((data.results[i].url).split('\/')).length) - 3);
39 let splitUrl = (data.results[i].url).split(nodeType + '\/');
40 nodes[data.results[i].url].nodeTypeLabel = nodeType;
41 nodes[data.results[i].url].nodeKeyLabel = splitUrl.pop();
42 let tempIconString = ((splitUrl.pop()).split('\/'));
43 tempIconString.pop(); //pop last off, not needed
44 let iconString = tempIconString.pop();
45 let iconKey = (iconString.replace(/-/g, '')).toUpperCase();
46 if(INVLIST.INVENTORYLIST[iconKey] && INVLIST.INVENTORYLIST[iconKey].icon){
47 nodes[data.results[i].url].icon = INVLIST.INVENTORYLIST[iconKey].icon;
49 nodes[data.results[i].url].icon = 'icon-datanetwork-serverL';
51 console.log("icon string: " + nodes[data.results[i].url].icon);
52 nodes[data.results[i].url].id = data.results[i].url;
53 for(var j = 0; j < data.results[i]['related-to'].length; j++){
54 let linkKey = data.results[i].url + '|' + data.results[i]['related-to'][j].url;
55 let inverseLinkKey = data.results[i]['related-to'][j].url + '|' + data.results[i].url;
56 if(!links[linkKey] && !links[inverseLinkKey]){
57 links[linkKey] = data.results[i]['related-to'][j];
58 links[linkKey].source = data.results[i].url;
59 links[linkKey].target = data.results[i]['related-to'][j].url;
60 links[linkKey].weight = 1/((((data.results[i].url).split('\/')).length) - 3);
61 let subset = (data.results[i]['related-to'][j]['relationship-label']).split(/[\.]+/);
62 links[linkKey].type = subset[subset.length - 1];
67 for (var key in links) {
68 if (links.hasOwnProperty(key)) {
69 console.log(key + " -> " + links[key]);
70 if(!nodes[links[key].source] || !nodes[links[key].target]){
76 var chart = (chartId, nodesObj, linksObj, rawData, classContext) => {
77 if(rawData.results.length <= PAGINATION_CONSTANT.RESULTS_PER_PAGE){
78 populateGraphObject( nodesObj, linksObj, rawData);
79 let links = Object.values(linksObj).map(d => Object.create(d));
80 let nodes = Object.values(nodesObj).map(d => Object.create(d));
81 let colors = d3.scaleOrdinal(d3.schemeCategory10);
83 let svg = d3.select('#'+chartId),
84 width = +svg.attr("width"),
85 height = +svg.attr("height"),
92 var g = svg.append("g")
93 .attr("class", "everything");
95 .attr("width", "100%")
96 .attr("height", "100%")
97 .attr("fill", "white");
100 .forceLink().id(function (d) {
103 .distance(function (d) {
104 return 1000 * d.weight;
108 var collisionForce = d3.forceCollide(100).strength(1.5).iterations(100);
110 var simulation = d3.forceSimulation()
111 .force("link", forceLink)
112 .force("charge", d3.forceManyBody().strength(function (d, i) {
113 var a = i == 0 ? -2000 : -1000;
115 }).distanceMin(200).distanceMax(1000))
116 .force("center", d3.forceCenter(width / 2, height / 2))
117 .force("collisionForce",collisionForce);
120 function zoom_actions(){
121 g.attr("transform", d3.event.transform)
123 //add zoom capabilities
124 var zoom_handler = d3.zoom()
125 .on("zoom", zoom_actions);
129 update(links, nodes);
132 var bounds = g.node().getBBox();
133 var parent = g.node().parentElement;
134 var fullWidth = parent.clientWidth || parent.parentNode.clientWidth,
135 fullHeight = parent.clientHeight || parent.parentNode.clientHeight;
136 var width = bounds.width,
137 height = bounds.height;
138 var midX = bounds.x + width / 2,
139 midY = bounds.y + height / 2;
140 if (width == 0 || height == 0) return; // nothing to fit
141 var scale = 0.95 / Math.max(width / fullWidth, height / fullHeight);
142 var translate = [fullWidth / 2 - scale * midX, fullHeight / 2 - scale * midY];
144 console.trace("zoomFit", translate, scale);
146 /*zoom_handler.translateTo(g, translate[0], translate[1])
147 .scaleTo(g, scale);*/
150 function update(links, nodes) {
151 link = g.selectAll(".link")
157 'stroke-opacity': .6,
158 'stroke-width': '1px',
159 'id': function (d, i) {return 'line' + chartId + d.id}
163 .text(function (d) {return d.type;});
165 edgepaths = g.selectAll(".edgepath")
173 'id': function (d, i) {return 'edgepath' + chartId + d.id}
175 .style("pointer-events", "none");
177 /*edgelabels = g.selectAll(".edgelabel")
181 .style("pointer-events", "none")
183 'class': 'edgelabel',
184 'id': function (d, i) {return 'edgelabel' + chartId + d.id},
189 edgelabels.append('textPath')
190 .attr('xlink:href', function (d, i) {return '#edgepath' + chartId + d.id})
191 .style("text-anchor", "middle")
192 .style("pointer-events", "none")
193 .attr("startOffset", "50%")
194 .text(function (d) {return d.type});*/
196 node = g.selectAll(".node")
200 .attr("class", "node")
201 .attr("id", function (d) {return "node" + chartId + (((decodeURIComponent(d.url)).replace(new RegExp('\/', 'g'),'-')).replace(new RegExp(':', 'g'),'-')).replace(new RegExp('\\.', 'g'),'-')})
203 .on("start", dragstarted)
205 .on("end", dragended)
208 node.append("svg:foreignObject")
212 .attr("dy", function (d) {return -1 * (Math.max((Math.round(d.weight * 250)/10), 2));})
213 .append("xhtml:span")
214 .attr("class", function (d) {return d.icon;})
215 .style("padding", "10px")
216 .style("font-size", function (d) {return Math.max(Math.round(d.weight * 250), 20) + "px";})
217 .attr("id", function (d) {return "nodeIcon" + chartId + (((decodeURIComponent(d.url)).replace(new RegExp('\/', 'g'),'-')).replace(new RegExp(':', 'g'),'-')).replace(new RegExp('\\.', 'g'),'-')})
218 .style("color", '#387dff')
219 .style("display", "block");
223 .text(function (d) {return decodeURIComponent(d.id);});
228 .attr('font-size', 10)
229 .text(function (d) {return d.nodeTypeLabel;})
230 .style("text-anchor", "middle");
233 .attr("dy", function (d) {return (Math.max(Math.round(d.weight * 250) + 15, 55));})
235 .attr('font-size', 8)
236 .text(function (d) {return decodeURIComponent(d.nodeKeyLabel);})
237 .style("text-anchor", "middle");
239 node.on("dblclick",function(d){ classContext.openNodeModal("test", d.url, d['node-type']) });
246 simulation.force("link")
249 svg.on("dblclick.zoom", null);
254 .attr("x1", function (d) {return d.source.x;})
255 .attr("y1", function (d) {return d.source.y;})
256 .attr("x2", function (d) {return d.target.x;})
257 .attr("y2", function (d) {return d.target.y;});
260 .attr("transform", function (d) {return "translate(" + d.x + ", " + d.y + ")";});
262 edgepaths.attr('d', function (d) {
263 return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
266 /*edgelabels.attr('transform', function (d) {
267 if (d.target.x < d.source.x) {
268 let bbox = this.getBBox();
269 let rx = bbox.x + bbox.width / 2;
270 let ry = bbox.y + bbox.height / 2;
271 return 'rotate(180 ' + rx + ' ' + ry + ')';
279 function dragstarted(d) {
283 function dragged(d) {
291 function dragended(d) {
296 let svg = d3.select('#'+chartId),
297 width = +svg.attr("width"),
298 height = +svg.attr("height"),
303 let svgHtml = "<foreignObject x=\"0\" y=\"0\" width=\"100%\" height=\"100%\">" +
304 "<body xmlns=\"http://www.w3.org/1999/xhtml\">" +
305 "<div class=\"svgbody\">" +
306 "<h1>Graphical output is limited to " + PAGINATION_CONSTANT.RESULTS_PER_PAGE +
307 " nodes. Your query returned " + rawData.results.length +
308 " nodes, which the GUI does not support, please limit your query or if this data" +
309 " is needed access it through our externally supported APIs." +
317 const OutputVisualization = (props) => {
318 if (props.identifier && props.width && props.height && props.overflow) {
320 <svg id={props.identifier} width={props.width} height={props.height} overflow={props.overflow}></svg>
324 return (<p>Graph Configuration Error</p>);
328 export default OutputVisualization;
329 export const Visualization = {