Updated Sparky to add ECOMP functionality Browse, Specialized Search, BYOQ, and the...
[aai/sparky-fe.git] / src / generic-components / OutputVisualization.jsx
1 /*
2  * ============LICENSE_START=======================================================
3  * org.onap.aai
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
10  *
11  *       http://www.apache.org/licenses/LICENSE-2.0
12  *
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=========================================================
19  */
20
21 import React from 'react';
22 import * as d3 from "d3";
23 import 'd3-selection-multi';
24 import {GlobalExtConstants} from 'utils/GlobalExtConstants.js';
25
26 let INVLIST = GlobalExtConstants.INVLIST;
27 let PAGINATION_CONSTANT = GlobalExtConstants.PAGINATION_CONSTANT;
28
29 /**
30  * This function will create a visualization from query outputs
31  * @param props
32  * @returns {*}
33  */
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;
48                         }else{
49                             nodes[data.results[i].url].icon = 'icon-datanetwork-serverL';
50                         }
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];
63                             }
64                         }
65                 }
66
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]){
71                               delete links[key];
72                         }
73                     }
74                 }
75 }
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);
82
83                 let svg = d3.select('#'+chartId),
84                     width = +svg.attr("width"),
85                     height = +svg.attr("height"),
86                     node,
87                     link,
88                     edgepaths,
89                     edgelabels;
90
91                 svg.html(null);
92                 var g = svg.append("g")
93                             .attr("class", "everything");
94                 g.append("rect")
95                     .attr("width", "100%")
96                     .attr("height", "100%")
97                     .attr("fill", "white");
98
99                 var forceLink = d3
100                     .forceLink().id(function (d) {
101                         return d.id;
102                     })
103                     .distance(function (d) {
104                         return 1000 * d.weight;
105                     })
106                     .strength(1.5);
107
108                 var collisionForce = d3.forceCollide(100).strength(1.5).iterations(100);
109
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;
114                             return a;
115                         }).distanceMin(200).distanceMax(1000))
116                     .force("center", d3.forceCenter(width / 2, height / 2))
117                     .force("collisionForce",collisionForce);
118
119                 //Zoom functions
120                 function zoom_actions(){
121                     g.attr("transform", d3.event.transform)
122                 }
123                 //add zoom capabilities
124                 var zoom_handler = d3.zoom()
125                     .on("zoom", zoom_actions);
126
127                 zoom_handler(svg);
128
129                 update(links, nodes);
130
131                 function zoomFit() {
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];
143
144                                  console.trace("zoomFit", translate, scale);
145
146                                  /*zoom_handler.translateTo(g, translate[0], translate[1])
147                                             .scaleTo(g, scale);*/
148                 }
149
150                 function update(links, nodes) {
151                     link = g.selectAll(".link")
152                         .data(links)
153                         .enter()
154                         .append("line")
155                         .attrs({
156                         'stroke': '#999',
157                         'stroke-opacity': .6,
158                         'stroke-width': '1px',
159                         'id': function (d, i) {return 'line' + chartId + d.id}
160                         });
161
162                     link.append("title")
163                         .text(function (d) {return d.type;});
164
165                     edgepaths = g.selectAll(".edgepath")
166                         .data(links)
167                         .enter()
168                         .append('path')
169                         .attrs({
170                             'class': 'edgepath',
171                             'fill-opacity': 0,
172                             'stroke-opacity': 0,
173                             'id': function (d, i) {return 'edgepath' + chartId + d.id}
174                         })
175                         .style("pointer-events", "none");
176
177                     /*edgelabels = g.selectAll(".edgelabel")
178                         .data(links)
179                         .enter()
180                         .append('text')
181                         .style("pointer-events", "none")
182                         .attrs({
183                             'class': 'edgelabel',
184                             'id': function (d, i) {return 'edgelabel'  + chartId + d.id},
185                             'font-size': 8,
186                             'fill': '#aaa'
187                         });
188
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});*/
195
196                     node = g.selectAll(".node")
197                         .data(nodes)
198                         .enter()
199                         .append("g")
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'),'-')})
202                         .call(d3.drag()
203                                  .on("start", dragstarted)
204                                  .on("drag", dragged)
205                                  .on("end", dragended)
206                         );
207
208                     node.append("svg:foreignObject")
209                             .attr("width", 70)
210                             .attr("height", 70)
211                             .attr("x", -45)
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");
220
221
222                     node.append("title")
223                         .text(function (d) {return decodeURIComponent(d.id);});
224
225                     node.append("text")
226                         .attr("dy", 0)
227                         .attr("dx", -10)
228                         .attr('font-size', 10)
229                         .text(function (d) {return d.nodeTypeLabel;})
230                         .style("text-anchor", "middle");
231
232                     node.append("text")
233                         .attr("dy", function (d) {return (Math.max(Math.round(d.weight * 250) + 15, 55));})
234                         .attr("dx", -10)
235                         .attr('font-size', 8)
236                         .text(function (d) {return decodeURIComponent(d.nodeKeyLabel);})
237                         .style("text-anchor", "middle");
238
239                     node.on("dblclick",function(d){ classContext.openNodeModal("test", d.url, d['node-type']) });
240
241                     simulation
242                         .nodes(nodes)
243                         .on("tick", ticked)
244                         .on("end", zoomFit);
245
246                     simulation.force("link")
247                         .links(links);
248
249                     svg.on("dblclick.zoom", null);
250                 }
251
252                 function ticked() {
253                     link
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;});
258
259                     node
260                         .attr("transform", function (d) {return "translate(" + d.x + ", " + d.y + ")";});
261
262                     edgepaths.attr('d', function (d) {
263                         return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
264                     });
265
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 + ')';
272                         }
273                         else {
274                             return 'rotate(0)';
275                         }
276                     });*/
277                 }
278
279                 function dragstarted(d) {
280                     simulation.stop();
281                 }
282
283                 function dragged(d) {
284                     d.fx = d3.event.x;
285                     d.fy = d3.event.y;
286                     d.x =  d3.event.x;
287                     d.y =  d3.event.y;
288                     ticked();
289                 }
290
291                 function dragended(d) {
292                   d.fixed = true;
293                   ticked();
294                 }
295         }else{
296              let svg = d3.select('#'+chartId),
297                                 width = +svg.attr("width"),
298                                 height = +svg.attr("height"),
299                                 node,
300                                 link,
301                                 edgepaths,
302                                 edgelabels;
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." +
310                                         "</h1>" +
311                                      "</div>"+
312                                 "</body>"+
313                             "</foreignObject>";
314              svg.html(svgHtml);
315         }
316 }
317 const OutputVisualization = (props) => {
318     if (props.identifier && props.width && props.height && props.overflow) {
319         return (
320           <svg id={props.identifier} width={props.width} height={props.height} overflow={props.overflow}></svg>
321         );
322
323     }else{
324         return (<p>Graph Configuration Error</p>);
325     }
326 };
327
328 export default OutputVisualization;
329 export const Visualization = {
330     chart: chart
331 }