X-Git-Url: https://gerrit.onap.org/r/gitweb?p=aai%2Fsparky-fe.git;a=blobdiff_plain;f=src%2Fgeneric-components%2FOutputVisualization.jsx;fp=src%2Fgeneric-components%2FOutputVisualization.jsx;h=734887d7627ab5f34f052fe30620ff0e41020e1d;hp=0000000000000000000000000000000000000000;hb=5ee7367a101143715c2869d72ea4a6fbf55f5af6;hpb=ddc05d4ea0254b427fea6ec80e2b03950eeca4ce diff --git a/src/generic-components/OutputVisualization.jsx b/src/generic-components/OutputVisualization.jsx new file mode 100644 index 0000000..734887d --- /dev/null +++ b/src/generic-components/OutputVisualization.jsx @@ -0,0 +1,331 @@ +/* + * ============LICENSE_START======================================================= + * org.onap.aai + * ================================================================================ + * Copyright © 2017-2021 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * 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. + * ============LICENSE_END========================================================= + */ + +import React from 'react'; +import * as d3 from "d3"; +import 'd3-selection-multi'; +import {GlobalExtConstants} from 'utils/GlobalExtConstants.js'; + +let INVLIST = GlobalExtConstants.INVLIST; +let PAGINATION_CONSTANT = GlobalExtConstants.PAGINATION_CONSTANT; + +/** + * This function will create a visualization from query outputs + * @param props + * @returns {*} + */ +var populateGraphObject = (nodes, links, data) => { + for(var i = 0; i < data.results.length; i++){ + nodes[data.results[i].url] = data.results[i]; + let nodeType = data.results[i]['node-type']; + nodes[data.results[i].url].weight = 1/((((data.results[i].url).split('\/')).length) - 3); + let splitUrl = (data.results[i].url).split(nodeType + '\/'); + nodes[data.results[i].url].nodeTypeLabel = nodeType; + nodes[data.results[i].url].nodeKeyLabel = splitUrl.pop(); + let tempIconString = ((splitUrl.pop()).split('\/')); + tempIconString.pop(); //pop last off, not needed + let iconString = tempIconString.pop(); + let iconKey = (iconString.replace(/-/g, '')).toUpperCase(); + if(INVLIST.INVENTORYLIST[iconKey] && INVLIST.INVENTORYLIST[iconKey].icon){ + nodes[data.results[i].url].icon = INVLIST.INVENTORYLIST[iconKey].icon; + }else{ + nodes[data.results[i].url].icon = 'icon-datanetwork-serverL'; + } + console.log("icon string: " + nodes[data.results[i].url].icon); + nodes[data.results[i].url].id = data.results[i].url; + for(var j = 0; j < data.results[i]['related-to'].length; j++){ + let linkKey = data.results[i].url + '|' + data.results[i]['related-to'][j].url; + let inverseLinkKey = data.results[i]['related-to'][j].url + '|' + data.results[i].url; + if(!links[linkKey] && !links[inverseLinkKey]){ + links[linkKey] = data.results[i]['related-to'][j]; + links[linkKey].source = data.results[i].url; + links[linkKey].target = data.results[i]['related-to'][j].url; + links[linkKey].weight = 1/((((data.results[i].url).split('\/')).length) - 3); + let subset = (data.results[i]['related-to'][j]['relationship-label']).split(/[\.]+/); + links[linkKey].type = subset[subset.length - 1]; + } + } + } + + for (var key in links) { + if (links.hasOwnProperty(key)) { + console.log(key + " -> " + links[key]); + if(!nodes[links[key].source] || !nodes[links[key].target]){ + delete links[key]; + } + } + } +} +var chart = (chartId, nodesObj, linksObj, rawData, classContext) => { + if(rawData.results.length <= PAGINATION_CONSTANT.RESULTS_PER_PAGE){ + populateGraphObject( nodesObj, linksObj, rawData); + let links = Object.values(linksObj).map(d => Object.create(d)); + let nodes = Object.values(nodesObj).map(d => Object.create(d)); + let colors = d3.scaleOrdinal(d3.schemeCategory10); + + let svg = d3.select('#'+chartId), + width = +svg.attr("width"), + height = +svg.attr("height"), + node, + link, + edgepaths, + edgelabels; + + svg.html(null); + var g = svg.append("g") + .attr("class", "everything"); + g.append("rect") + .attr("width", "100%") + .attr("height", "100%") + .attr("fill", "white"); + + var forceLink = d3 + .forceLink().id(function (d) { + return d.id; + }) + .distance(function (d) { + return 1000 * d.weight; + }) + .strength(1.5); + + var collisionForce = d3.forceCollide(100).strength(1.5).iterations(100); + + var simulation = d3.forceSimulation() + .force("link", forceLink) + .force("charge", d3.forceManyBody().strength(function (d, i) { + var a = i == 0 ? -2000 : -1000; + return a; + }).distanceMin(200).distanceMax(1000)) + .force("center", d3.forceCenter(width / 2, height / 2)) + .force("collisionForce",collisionForce); + + //Zoom functions + function zoom_actions(){ + g.attr("transform", d3.event.transform) + } + //add zoom capabilities + var zoom_handler = d3.zoom() + .on("zoom", zoom_actions); + + zoom_handler(svg); + + update(links, nodes); + + function zoomFit() { + var bounds = g.node().getBBox(); + var parent = g.node().parentElement; + var fullWidth = parent.clientWidth || parent.parentNode.clientWidth, + fullHeight = parent.clientHeight || parent.parentNode.clientHeight; + var width = bounds.width, + height = bounds.height; + var midX = bounds.x + width / 2, + midY = bounds.y + height / 2; + if (width == 0 || height == 0) return; // nothing to fit + var scale = 0.95 / Math.max(width / fullWidth, height / fullHeight); + var translate = [fullWidth / 2 - scale * midX, fullHeight / 2 - scale * midY]; + + console.trace("zoomFit", translate, scale); + + /*zoom_handler.translateTo(g, translate[0], translate[1]) + .scaleTo(g, scale);*/ + } + + function update(links, nodes) { + link = g.selectAll(".link") + .data(links) + .enter() + .append("line") + .attrs({ + 'stroke': '#999', + 'stroke-opacity': .6, + 'stroke-width': '1px', + 'id': function (d, i) {return 'line' + chartId + d.id} + }); + + link.append("title") + .text(function (d) {return d.type;}); + + edgepaths = g.selectAll(".edgepath") + .data(links) + .enter() + .append('path') + .attrs({ + 'class': 'edgepath', + 'fill-opacity': 0, + 'stroke-opacity': 0, + 'id': function (d, i) {return 'edgepath' + chartId + d.id} + }) + .style("pointer-events", "none"); + + /*edgelabels = g.selectAll(".edgelabel") + .data(links) + .enter() + .append('text') + .style("pointer-events", "none") + .attrs({ + 'class': 'edgelabel', + 'id': function (d, i) {return 'edgelabel' + chartId + d.id}, + 'font-size': 8, + 'fill': '#aaa' + }); + + edgelabels.append('textPath') + .attr('xlink:href', function (d, i) {return '#edgepath' + chartId + d.id}) + .style("text-anchor", "middle") + .style("pointer-events", "none") + .attr("startOffset", "50%") + .text(function (d) {return d.type});*/ + + node = g.selectAll(".node") + .data(nodes) + .enter() + .append("g") + .attr("class", "node") + .attr("id", function (d) {return "node" + chartId + (((decodeURIComponent(d.url)).replace(new RegExp('\/', 'g'),'-')).replace(new RegExp(':', 'g'),'-')).replace(new RegExp('\\.', 'g'),'-')}) + .call(d3.drag() + .on("start", dragstarted) + .on("drag", dragged) + .on("end", dragended) + ); + + node.append("svg:foreignObject") + .attr("width", 70) + .attr("height", 70) + .attr("x", -45) + .attr("dy", function (d) {return -1 * (Math.max((Math.round(d.weight * 250)/10), 2));}) + .append("xhtml:span") + .attr("class", function (d) {return d.icon;}) + .style("padding", "10px") + .style("font-size", function (d) {return Math.max(Math.round(d.weight * 250), 20) + "px";}) + .attr("id", function (d) {return "nodeIcon" + chartId + (((decodeURIComponent(d.url)).replace(new RegExp('\/', 'g'),'-')).replace(new RegExp(':', 'g'),'-')).replace(new RegExp('\\.', 'g'),'-')}) + .style("color", '#387dff') + .style("display", "block"); + + + node.append("title") + .text(function (d) {return decodeURIComponent(d.id);}); + + node.append("text") + .attr("dy", 0) + .attr("dx", -10) + .attr('font-size', 10) + .text(function (d) {return d.nodeTypeLabel;}) + .style("text-anchor", "middle"); + + node.append("text") + .attr("dy", function (d) {return (Math.max(Math.round(d.weight * 250) + 15, 55));}) + .attr("dx", -10) + .attr('font-size', 8) + .text(function (d) {return decodeURIComponent(d.nodeKeyLabel);}) + .style("text-anchor", "middle"); + + node.on("dblclick",function(d){ classContext.openNodeModal("test", d.url, d['node-type']) }); + + simulation + .nodes(nodes) + .on("tick", ticked) + .on("end", zoomFit); + + simulation.force("link") + .links(links); + + svg.on("dblclick.zoom", null); + } + + function ticked() { + link + .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;}); + + node + .attr("transform", function (d) {return "translate(" + d.x + ", " + d.y + ")";}); + + edgepaths.attr('d', function (d) { + return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y; + }); + + /*edgelabels.attr('transform', function (d) { + if (d.target.x < d.source.x) { + let bbox = this.getBBox(); + let rx = bbox.x + bbox.width / 2; + let ry = bbox.y + bbox.height / 2; + return 'rotate(180 ' + rx + ' ' + ry + ')'; + } + else { + return 'rotate(0)'; + } + });*/ + } + + function dragstarted(d) { + simulation.stop(); + } + + function dragged(d) { + d.fx = d3.event.x; + d.fy = d3.event.y; + d.x = d3.event.x; + d.y = d3.event.y; + ticked(); + } + + function dragended(d) { + d.fixed = true; + ticked(); + } + }else{ + let svg = d3.select('#'+chartId), + width = +svg.attr("width"), + height = +svg.attr("height"), + node, + link, + edgepaths, + edgelabels; + let svgHtml = "" + + "" + + "
" + + "

Graphical output is limited to " + PAGINATION_CONSTANT.RESULTS_PER_PAGE + + " nodes. Your query returned " + rawData.results.length + + " nodes, which the GUI does not support, please limit your query or if this data" + + " is needed access it through our externally supported APIs." + + "

" + + "
"+ + ""+ + "
"; + svg.html(svgHtml); + } +} +const OutputVisualization = (props) => { + if (props.identifier && props.width && props.height && props.overflow) { + return ( + + ); + + }else{ + return (

Graph Configuration Error

); + } +}; + +export default OutputVisualization; +export const Visualization = { + chart: chart +}