react-network-diagrams
Version:
514 lines (460 loc) • 24.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TrafficMap = undefined;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require("react");
var _react2 = _interopRequireDefault(_react);
var _propTypes = require("prop-types");
var _propTypes2 = _interopRequireDefault(_propTypes);
var _underscore = require("underscore");
var _underscore2 = _interopRequireDefault(_underscore);
var _BaseMap = require("./BaseMap");
var _Resizable = require("./Resizable");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /**
* Copyright (c) 2018, The Regents of the University of California,
* through Lawrence Berkeley National Laboratory (subject to receipt
* of any required approvals from the U.S. Dept. of Energy).
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* A high level component for showing network topology, including visualizing
* network traffic as a heat map.
*/
var TrafficMap = exports.TrafficMap = function (_React$Component) {
_inherits(TrafficMap, _React$Component);
function TrafficMap() {
_classCallCheck(this, TrafficMap);
return _possibleConstructorReturn(this, (TrafficMap.__proto__ || Object.getPrototypeOf(TrafficMap)).apply(this, arguments));
}
_createClass(TrafficMap, [{
key: "bounds",
value: function bounds() {
if (this.props.bounds) {
return this.props.bounds;
}
var minX = _underscore2.default.min(this.props.topology.nodes, function (node) {
return node.x;
}).x;
var minY = _underscore2.default.min(this.props.topology.nodes, function (node) {
return node.y;
}).y;
var maxX = _underscore2.default.max(this.props.topology.nodes, function (node) {
return node.x;
}).x;
var maxY = _underscore2.default.max(this.props.topology.nodes, function (node) {
return node.y;
}).y;
return { x1: minX, x2: maxX, y1: minY, y2: maxY };
}
}, {
key: "nodeSize",
value: function nodeSize(name) {
return this.props.nodeSizeMap[name] || 7;
}
}, {
key: "nodeShape",
value: function nodeShape(name) {
return this.props.nodeShapeMap[name] || "circle";
}
}, {
key: "edgeThickness",
value: function edgeThickness(capacity) {
return this.props.edgeThicknessMap[capacity] || 5;
}
}, {
key: "edgeShape",
value: function edgeShape(name) {
if (_underscore2.default.has(this.props.edgeShapeMap, name)) {
return this.props.edgeShapeMap[name].shape;
} else {
return "linear";
}
}
}, {
key: "edgeCurveDirection",
value: function edgeCurveDirection(name) {
var direction = void 0;
if (_underscore2.default.has(this.props.edgeShapeMap, name)) {
if (this.props.edgeShapeMap[name].shape === "curved") {
return this.props.edgeShapeMap[name].direction;
}
}
return direction;
}
}, {
key: "edgeCurveOffset",
value: function edgeCurveOffset(name) {
var offset = void 0;
if (_underscore2.default.has(this.props.edgeShapeMap, name)) {
if (this.props.edgeShapeMap[name].shape === "curved") {
return this.props.edgeShapeMap[name].offset;
}
}
return offset;
}
}, {
key: "selectEdgeColor",
value: function selectEdgeColor(bps) {
var gbps = bps / 1.0e9;
for (var i = 0; i < this.props.edgeColorMap.length; i++) {
var row = this.props.edgeColorMap[i];
if (gbps >= row.range[0]) {
return row.color;
}
}
return "#C9CACC";
}
}, {
key: "filteredPaths",
value: function filteredPaths() {
var _this2 = this;
return _underscore2.default.filter(this.props.topology.paths, function (path) {
if (_underscore2.default.isArray(_this2.props.showPaths)) {
return _underscore2.default.contains(_this2.props.showPaths, path.name);
}
return true;
});
}
}, {
key: "buildTopology",
value: function buildTopology() {
var _this3 = this;
var topology = {};
if (_underscore2.default.isNull(this.props.topology)) {
return null;
}
var genericStyle = {
node: {
normal: { fill: "#B0B0B0", stroke: "#9E9E9E", cursor: "pointer" },
selected: {
fill: "#37B6D3",
stroke: "rgba(55, 182, 211, 0.22)",
strokeWidth: 10,
cursor: "pointer"
},
muted: {
fill: "#B0B0B0",
stroke: "#9E9E9E",
opacity: 0.6,
cursor: "pointer"
}
},
label: {
normal: { fill: "#696969", stroke: "none", fontSize: 9 },
selected: { fill: "#333", stroke: "none", fontSize: 11 },
muted: {
fill: "#696969",
stroke: "none",
fontSize: 8,
opacity: 0.6
}
}
};
// Create a node list
topology.nodes = _underscore2.default.map(this.props.topology.nodes, function (node) {
var n = _underscore2.default.clone(node);
// Radius is based on the type of node, given in the nodeSizeMap
n.radius = _this3.nodeSize(node.type);
n.labelPosition = node.label_position;
n.labelOffsetX = node.label_dx;
n.labelOffsetY = node.label_dy;
var styleMap = _underscore2.default.has(_this3.props.stylesMap, node.type) ? _this3.props.stylesMap[node.type] : genericStyle;
n.style = styleMap.node;
n.labelStyle = styleMap.label;
n.shape = _this3.nodeShape(node.name);
return n;
});
// Create the edge list
topology.edges = _underscore2.default.map(this.props.topology.edges, function (edge) {
var edgeName = edge.source + "--" + edge.target;
return {
width: _this3.edgeThickness(edge.capacity),
classed: edge.capacity,
source: edge.source,
target: edge.target,
totalCapacity: edge.total_capacity,
ifaces: edge.ifaces,
name: edgeName,
shape: _this3.edgeShape(edgeName),
curveDirection: _this3.edgeCurveDirection(edgeName),
offset: _this3.edgeCurveOffset(edgeName)
};
});
// Create the path list, filtering based on what is in showPaths
if (this.props.showPaths) {
topology.paths = _underscore2.default.map(this.filteredPaths(), function (path) {
var color = _underscore2.default.has(_this3.props.pathColorMap, path.name) ? _this3.props.pathColorMap[path.name] : "lightsteelblue";
var width = _underscore2.default.has(_this3.props.pathWidthMap, path.name) ? _this3.props.pathWidthMap[path.name] : 4;
return {
name: path.name,
steps: path.steps,
color: color,
width: width
};
});
}
// Colorize the topology
if (this.props.traffic) {
if (!this.props.showPaths && this.props.edgeDrawingMethod === "bidirectionalArrow") {
_underscore2.default.each(topology.edges, function (edge) {
var sourceTargetName = edge.source + "--" + edge.target;
var targetSourceName = edge.target + "--" + edge.source;
var sourceTargetTraffic = _this3.props.traffic.get(sourceTargetName);
var targetSourceTraffic = _this3.props.traffic.get(targetSourceName);
edge.sourceTargetColor = _this3.selectEdgeColor(sourceTargetTraffic);
edge.targetSourceColor = _this3.selectEdgeColor(targetSourceTraffic);
});
} else {
var edgeMap = {};
_underscore2.default.each(this.filteredPaths(), function (path) {
var pathAtoZTraffic = _this3.props.traffic.get(path.name + "--AtoZ");
var pathZtoATraffic = _this3.props.traffic.get(path.name + "--ZtoA");
var prev = null;
_underscore2.default.each(path.steps, function (step) {
if (prev) {
var sourceTargetName = prev + "--" + step;
if (!_underscore2.default.has(edgeMap, sourceTargetName)) {
edgeMap[sourceTargetName] = 0;
}
edgeMap[sourceTargetName] += pathAtoZTraffic;
var targetSourceName = step + "--" + prev;
if (!_underscore2.default.has(edgeMap, targetSourceName)) {
edgeMap[targetSourceName] = 0;
}
edgeMap[targetSourceName] += pathZtoATraffic;
}
prev = step;
});
});
_underscore2.default.each(topology.edges, function (edge) {
edge.stroke = _this3.props.edgeColor ? _this3.props.edgeColor : "#DDD";
var sourceTargetName = edge.source + "--" + edge.target;
var targetSourceName = edge.target + "--" + edge.source;
if (_underscore2.default.has(edgeMap, sourceTargetName)) {
var sourceTargetTraffic = edgeMap[sourceTargetName];
edge.sourceTargetColor = _this3.selectEdgeColor(sourceTargetTraffic);
}
if (_underscore2.default.has(edgeMap, targetSourceName)) {
var targetSourceTraffic = edgeMap[targetSourceName];
edge.targetSourceColor = _this3.selectEdgeColor(targetSourceTraffic);
}
});
}
}
topology.name = this.props.topology.name;
topology.description = this.props.topology.description;
return topology;
}
}, {
key: "handleSelectionChanged",
value: function handleSelectionChanged(selectionType, selection) {
if (this.props.onSelectionChange) {
this.props.onSelectionChange(selectionType, selection);
}
}
}, {
key: "render",
value: function render() {
var _this4 = this;
var topo = this.buildTopology();
var bounds = this.bounds();
var aspect = (bounds.x2 - bounds.x1) / (bounds.y2 - bounds.y1);
var autoSize = this.props.autoSize;
var defaultStyle = {
background: "#F6F6F6",
borderStyle: "solid",
borderWidth: "thin",
borderColor: "#E6E6E6"
};
var style = this.props.style ? this.props.style : defaultStyle;
if (autoSize) {
return _react2.default.createElement(
_Resizable.Resizable,
{ aspect: aspect, style: style },
_react2.default.createElement(_BaseMap.BaseMap, {
topology: topo,
paths: topo.paths,
bounds: bounds,
width: this.props.width,
height: this.props.height,
margin: this.props.margin,
selection: this.props.selection,
edgeDrawingMethod: this.props.edgeDrawingMethod,
onSelectionChange: function onSelectionChange(selectionType, selection) {
return _this4.handleSelectionChanged(selectionType, selection);
}
})
);
} else {
return _react2.default.createElement(
"div",
{ style: style },
_react2.default.createElement(_BaseMap.BaseMap, {
topology: topo,
paths: topo.paths,
bounds: bounds,
width: this.props.width,
height: this.props.height,
margin: this.props.margin,
selection: this.props.selection,
edgeDrawingMethod: this.props.edgeDrawingMethod,
onSelectionChange: function onSelectionChange(selectionType, selection) {
return _this4.handleSelectionChanged(selectionType, selection);
}
})
);
}
}
}]);
return TrafficMap;
}(_react2.default.Component);
TrafficMap.defaultProps = {
edgeThicknessMap: {
"100G": 5,
"10G": 3,
"1G": 1.5,
subG: 1
},
edgeColor: "#DDD",
edgeColorMap: [],
nodeSizeMap: {},
nodeShapeMap: {},
edgeShapeMap: {},
selected: false,
shape: "circle",
stylesMap: {},
showPaths: false,
autoSize: true
};
TrafficMap.propTypes = {
/** The width of the circuit diagram */
width: _propTypes2.default.number,
/**
* The topology structure, as detailed above. This contains the
* descriptions of nodes, edges and paths used to render the topology
*/
topology: _propTypes2.default.object,
/**
* Specified as an object containing x1, y1 and x2, y2. This is the region
* to display on the map. If this isn't specified the bounds will be
* calculated from the nodes in the Map.
*/
bounds: _propTypes2.default.shape({
x1: _propTypes2.default.number,
y1: _propTypes2.default.number,
x2: _propTypes2.default.number,
y2: _propTypes2.default.number
}),
/**
* The is the overall rendering style for the edge connections. Maybe
* one of the following strings:
*
* * "simple" - simple line connections between nodes
* * "bidirectionalArrow" - network traffic represented by bi-directional arrows
* * "pathBidirectionalArrow" - similar to "bidirectionalArrow", but only for
* edges that are used in the currently displayed path(s).
*/
edgeDrawingMethod: _propTypes2.default.oneOf(["simple", "bidirectionalArrow", "pathBidirectionalArrow"]),
/**
* Either a boolean or a list of path names. If a bool, and true, then all
* paths will be shown. If a list then only the paths in that list will be
* shown. The default is to show no paths.
*/
showPaths: _propTypes2.default.oneOfType([_propTypes2.default.bool, _propTypes2.default.arrayOf(_propTypes2.default.string)]),
/**
* A mapping of the capacity field within the tologogy edge object
* to a line thickness for rendering the edges.
*
* Example:
*
* ```
* const edgeThicknessMap = {
* "100G": 5,
* "10G": 3,
* "1G": 1.5,
* "subG": 1
* };
* ```
*/
edgeThicknessMap: _propTypes2.default.object,
/**
* The default color for an edge which isn't colored using the `edgeColorMap`.
*/
edgeColor: _propTypes2.default.string,
/**
* A mapping of traffic on the link, in Gbps, to a color and label. The label is because the same
* mapping can be used to create a legend for the map.
*
* Example:
*
* ```
* const edgeColorMap = [
* { color: "#990000", label: ">=50 Gbps", range: [50, 100] },
* { color: "#bd0026", label: "20 - 50", range: [20, 50] },
* { color: "#cc4c02", label: "10 - 20", range: [10, 20] },
* { color: "#016c59", label: "5 - 10", range: [5, 10] },
* { color: "#238b45", label: "2 - 5", range: [2, 5] },
* { color: "#3690c0", label: "1 - 2", range: [1, 2] },
* { color: "#74a9cf", label: "0 - 1", range: [0, 1] }
* ];
* ```
*/
edgeColorMap: _propTypes2.default.array,
/**
* A mapping from the type field in the node object to a size to draw the shape
*
* Example:
* ```
* const nodeSizeMap = {
* hub: 5.5,
* esnet_site: 7
* };
* ```
*/
nodeSizeMap: _propTypes2.default.object,
/**
* Mapping of node name to shape (default is "circle", other options are
* "cloud" or "square", currently).
*
* Example:
* ```
* const nodeShapeMap = {
* DENV: "square"
* };
* ```
*/
nodeShapeMap: _propTypes2.default.object,
/**
* A mapping of the edge name (which is source + "--" + target) to a
* dict of edge shape options:
* * `shape` (either "linear" or "curved")
* * `direction` (if shape is curved, either "left" or "right")
* * `offset` (if shape is curved, the amount of curve, which is
* pixel offset from a straight line between the source and target at the midpoint)
*
* Example:
* ```
* const edgeShapeMap = {
* "ALBQ--DENV": {
* "shape": "curved",
* "direction": "right",
* "offset": 15
* }
* ```
*/
edgeShapeMap: _propTypes2.default.object,
/** Display the endpoint selected */
selected: _propTypes2.default.bool,
/** The shape of the endpoint */
shape: _propTypes2.default.oneOf(["circle", "square", "cloud"]),
stylesMap: _propTypes2.default.object,
autoSize: _propTypes2.default.bool
};