import 'whatwg-fetch'; import { attr, call, select, selectAll } from 'd3-selection'; import { forceCenter, forceLink, forceManyBody, forceSimulation } from 'd3-force'; import PropTypes from 'prop-types'; import React from 'react'; import _get from 'lodash/get'; import _isEmpty from 'lodash/isEmpty'; import _map from 'lodash/map'; import _uniq from 'lodash/uniq'; import { drag } from 'd3-drag'; import { format } from 'd3-format'; import { metricsPropType } from './util/MetricUtils.jsx'; import { withContext } from './util/AppContext.jsx'; import withREST from './util/withREST.jsx'; // create a Object with only the subset of functions/submodules/plugins that we need const d3 = Object.assign( {}, { attr, call, drag, forceCenter, forceLink, forceManyBody, forceSimulation, select, selectAll, format, } ); const defaultSvgWidth = 524; const defaultSvgHeight = 325; const defaultNodeRadius = 15; const margin = { top: 0, right: 0, bottom: 10, left: 0 }; const simulation = d3.forceSimulation() .force("link", d3.forceLink() .id(d => d.id) .distance(140)) .force("charge", d3.forceManyBody().strength(-20)) .force("center", d3.forceCenter(defaultSvgWidth / 2, defaultSvgHeight / 2)); export class NetworkGraphBase extends React.Component { static defaultProps = { deployments: [] } static propTypes = { data: PropTypes.arrayOf(metricsPropType.isRequired).isRequired, deployments: PropTypes.arrayOf(PropTypes.object), } constructor(props) { super(props); // https://github.com/d3/d3-zoom/issues/32 d3.getEvent = (() => require("d3-selection").event).bind(this); } componentDidMount() { let container = document.getElementsByClassName("network-graph-container")[0]; let width = !container ? defaultSvgWidth : container.getBoundingClientRect().width; this.svg = d3.select(".network-graph-container") .append("svg") .attr("class", "network-graph") .attr("width", width) .attr("height", width) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); } componentDidUpdate() { simulation.alpha(1).restart(); this.drawGraph(); } getGraphData() { const { data } = this.props; let links = []; let nodeList = []; _map(data, (resp, i) => { let rows = _get(resp, ["ok", "statTables", 0, "podGroup", "rows"]); let dst = this.props.deployments[i].name; _map(rows, row => { links.push({ source: row.resource.name, target: dst, }); nodeList.push(row.resource.name); nodeList.push(dst); }); }); let nodes = _map(_uniq(nodeList), n => ({ id: n })); return { links, nodes }; } drawGraph() { let graphData = this.getGraphData(); // check if graph is present to prevent drawing of multiple graphs if (this.svg.select("circle")._groups[0][0]) { return; } this.drawGraphComponents(graphData.links, graphData.nodes); } drawGraphComponents(links, nodes) { if (_isEmpty(nodes)) { d3.select(".network-graph-container").select("svg").attr("height", 0); return; } else { d3.select(".network-graph-container").select("svg").attr("height", defaultSvgHeight); } this.svg.append("svg:defs").selectAll("marker") .data(links) // Different link/path types can be defined here .enter().append("svg:marker") // This section adds in the arrows .attr("id", node => node.source + "/" + node.target) .attr("viewBox", "0 -5 10 10") .attr("refX", 24) .attr("refY", -0.25) .attr("markerWidth", 3) .attr("markerHeight", 3) .attr("fill", "#454242") .attr("orient", "auto") .append("svg:path") .attr("d", "M0,-5L10,0L0,5"); // add the links and the arrows const path = this.svg.append("svg:g").selectAll("path") .data(links) .enter().append("svg:path") .attr("stroke-width", 3) .attr("stroke", "#454242") .attr("marker-end", node => "url(#"+node.source + "/" + node.target+")"); const nodeElements = this.svg.append('g') .selectAll('circle') .data(nodes) .enter().append('circle') .attr("r", defaultNodeRadius) .attr('fill', 'steelblue') .call(d3.drag() .on("start", this.dragstarted) .on("drag", this.dragged) .on("end", this.dragended)); const textElements = this.svg.append('g') .selectAll('text') .data(nodes) .enter().append('text') .text(node => node.id) .attr('font-size', 15) .attr('dx', 20) .attr('dy', 4); simulation.nodes(nodes).on("tick", () => { path .attr("d", node => "M" + node.source.x + " " + node.source.y + " L " + node.target.x + " " + node.target.y); nodeElements .attr("cx", node => node.x) .attr("cy", node => node.y); textElements .attr("x", node => node.x) .attr("y", node => node.y); }); simulation.force("link") .links(links); } dragstarted = d => { if (!d3.getEvent().active) { simulation.alphaTarget(0.3).restart(); } d.fx = d.x; d.fy = d.y; } dragged = d => { d.fx = d3.getEvent().x; d.fy = d3.getEvent().y; } dragended = d => { if (!d3.getEvent().active) { simulation.alphaTarget(0); } d.fx = null; d.fy = null; } render() { return (