react-network-diagrams
Version:
598 lines (544 loc) • 21.1 kB
JavaScript
/**
* 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.
*/
import React from "react";
import _ from "underscore";
import PropTypes from "prop-types";
import { Connection } from "./Connection";
import { Endpoint } from "./Endpoint";
import { Label } from "./Label";
export class PatchPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
hover: false
};
this.handleSelectionChange = this.handleSelectionChange.bind(this);
}
handleSelectionChange(e, value) {
if (!this.props.noNavigate) {
this.props.onSelectionChange(e, value);
}
}
renderPanelLabel(yStart, label, key) {
const y = yStart - this.props.panelSpacing / 2;
const x = this.props.width / 2;
const labelStyle = {
fontSize: 14,
fontFamily: "verdana, sans-serif",
fill: "#737373",
textAnchor: "middle"
};
return (
<g key={`panel-name-${key}`}>
<Label
x={x}
y={y}
label={label}
labelPosition="center"
labelClassed="panel-name"
style={labelStyle}
/>
</g>
);
}
renderFrontBackLabel(yStart, key) {
const x = this.props.width / 2;
const xLeft = x - this.props.width / 9;
const xRight = x + this.props.width / 9;
const yDown = yStart;
const front = "FRONT";
const back = "BACK";
const labelStyle = {
fill: "#9D9D9D",
fontFamily: "verdana, sans-serif",
fontSize: 10,
textAnchor: "middle"
};
return (
<g key={`panel-frontback-${key}`}>
<text
className="frontback-label"
key={`panel-front-${key}`}
style={labelStyle}
x={xLeft}
y={yDown}
>
{front}
</text>
<text
className="frontback-label"
key={`panel-back-${key}`}
style={labelStyle}
x={xRight}
y={yDown}
>
{back}
</text>
</g>
);
}
renderPanels(panelMap) {
const elements = [];
const panelWidthOffset = this.props.panelWidth;
const panelStyle = this.props.panelStyle;
// determine the middle of the svg element
const midpt = this.props.width / 2;
// determing the x location and width for the outer panel shape from the panelWidthOffest
const panelX = midpt - this.props.couplerStyle.squareWidth / 2 - panelWidthOffset;
const width = this.props.couplerStyle.squareWidth + panelWidthOffset * 2;
// set the start of the panel at the yOffset from the top
let panelY = this.props.yOffset;
_.each(this.props.panels, (panel, panelIndex) => {
// draw a panel
elements.push(
<g key={`panel-${panelIndex}`}>
<rect
className="panel"
width={width}
height={panelMap[panel.panelName]}
style={panelStyle}
x={panelX}
y={panelY}
rx={this.props.panelRoundedX}
ry={this.props.panelRoundedY}
/>
</g>
);
// set the start of the module group at the spacing below the panel start +
// 1/2 the coupler height. This will place the y at the middle of the coupler group
let moduleY = panelY + this.props.moduleSpacing + this.props.couplerStyle.size / 2;
_.each(panel.modules, module => {
// draw all the circuit groups in a module
elements.push(this.renderModule(module, moduleY));
// after each module is finished, space the next module start at the middle
// of the first coupler group, offset by the module spacing
moduleY +=
this.props.moduleSpacing +
module.length * this.props.couplerStyle.size +
(module.length - 1) * this.props.couplerSpacing;
});
elements.push(this.renderFrontBackLabel(panelY, panelIndex));
elements.push(this.renderPanelLabel(panelY, panel.panelName, panelIndex));
// once all panel modules are done, start the next module at the next panel
// using the spacing derived from the svg box height
panelY += this.props.panelSpacing + panelMap[panel.panelName];
});
return elements;
}
renderModule(module, moduleY) {
// moduleY is y1, y2 of the first circuitGroup in the module
const elements = [];
let y = moduleY;
// draw each circuit group in the module
_.each(module, (circuitGroup, groupIndex) => {
// draw the endpoints
elements.push(this.renderEndpoints(circuitGroup, y, groupIndex));
// draw the lines
elements.push(this.renderConnections(circuitGroup, y, groupIndex));
y += this.props.couplerStyle.size + this.props.couplerSpacing;
});
return elements;
}
/**
* draws 0, 2, or 4 endpoints - determined by presence of Front, Back and Coupler
*/
renderEndpoints(circuitGroup, y, key) {
const elements = [];
const midpt = this.props.width / 2;
let circuit;
let x1;
let x2;
if (circuitGroup.frontCircuit) {
circuit = circuitGroup.frontCircuit;
x1 = this.props.margin;
x2 =
midpt -
circuitGroup.coupler.styleProperties.squareWidth / 2 -
this.props.couplerEndpointRadius;
elements.push(
<g key={`endpoint-${circuit.endpointLabelA}-${key}`}>
<Endpoint
x={x1}
y={y}
style={circuit.endpointStyle}
labelStyle={circuit.endpointStyle.label}
labelPosition="bottomleftangled"
label={circuitGroup.frontLabel}
/>
</g>
);
elements.push(
<g key={`endpoint-${circuit.endpointLabelZ}-${key}`}>
<Endpoint x={x2} y={y} style={circuit.endpointStyle} />
</g>
);
}
if (circuitGroup.backCircuit) {
circuit = circuitGroup.backCircuit;
x1 =
midpt +
circuitGroup.coupler.styleProperties.squareWidth / 2 +
this.props.couplerEndpointRadius;
x2 = this.props.width - this.props.margin;
elements.push(
<g key={`endpoint-${circuit.endpointLabelA}-${key}`}>
<Endpoint x={x1} y={y} style={circuit.endpointStyle} />
</g>
);
elements.push(
<g key={`endpoint-${circuit.endpointLabelZ}-${key}`}>
<Endpoint
x={x2}
y={y}
style={circuit.endpointStyle}
labelStyle={circuit.endpointStyle.label}
labelPosition="bottomrightangled"
label={circuitGroup.backLabel}
/>
</g>
);
}
return elements;
}
renderConnections(circuitGroup, y, key) {
// draws center coupler and either front, back or both circuits
const elements = [];
const midpt = this.props.width / 2;
let circuit;
let x1;
let x2;
if (circuitGroup.coupler) {
circuit = circuitGroup.coupler;
x1 = midpt - circuit.styleProperties.squareWidth / 2;
x2 = midpt + circuit.styleProperties.squareWidth / 2;
elements.push(
<g key={`coupler-${circuit.circuitLabel}-${key}`}>
<Connection
x1={x1}
x2={x2}
y1={y}
y2={y}
roundedX={this.props.roundedX}
roundedY={this.props.roundedY}
endPointRoundedX={this.props.endpointRoundedX}
endPointRoundedY={this.props.endPointRoundedY}
style={circuit.styleProperties.style}
lineShape={circuit.styleProperties.lineShape}
label={circuit.circuitLabel}
labelPosition={this.props.couplerLabelPosition}
labelOffsetX={this.props.labelOffsetX}
labelOffsetY={this.props.labelOffsetY}
radius={this.props.couplerEndpointRadius}
endpointShape="square"
size={circuit.styleProperties.size}
onSelectionChange={this.handleSelectionChange}
noNavigate={circuit.styleProperties.noNavigate}
navTo={circuit.navTo}
/>
</g>
);
}
if (circuitGroup.frontCircuit) {
circuit = circuitGroup.frontCircuit;
x1 = this.props.margin;
x2 =
midpt -
circuitGroup.coupler.styleProperties.squareWidth / 2 -
this.props.couplerEndpointRadius;
elements.push(
<g key={`frontCircuit-${circuit.circuitLabel}-${key}`}>
<Connection
x1={x1}
x2={x2}
y1={y}
y2={y}
style={circuit.styleProperties.style}
lineShape={circuit.styleProperties.lineShape}
label={circuit.circuitLabel}
labelPosition={this.props.labelPosition}
onSelectionChange={this.handleSelectionChange}
noNavigate={circuit.styleProperties.noNavigate}
navTo={circuit.navTo}
/>
</g>
);
}
if (circuitGroup.backCircuit) {
circuit = circuitGroup.backCircuit;
x1 =
midpt +
circuitGroup.coupler.styleProperties.squareWidth / 2 +
this.props.couplerEndpointRadius;
x2 = this.props.width - this.props.margin;
elements.push(
<g key={`backCircuit-${circuit.circuitLabel}-${key}`}>
<Connection
x1={x1}
x2={x2}
y1={y}
y2={y}
style={circuit.styleProperties.style}
lineShape={circuit.styleProperties.lineShape}
label={circuit.circuitLabel}
labelPosition={this.props.labelPosition}
onSelectionChange={this.handleSelectionChange}
noNavigate={circuit.styleProperties.noNavigate}
navTo={circuit.navTo}
/>
</g>
);
}
return elements;
}
render() {
// Styling
const classed = "panel-container";
const circuitContainer = {
borderTopStyle: "solid",
borderBottomStyle: "solid",
borderWidth: 1,
borderTopColor: "#FFFFFF",
borderBottomColor: "#EFEFEF"
};
let numPanels = 0;
let viewBoxHeight = 0;
const yOffset = this.props.yOffset;
const moduleSpacing = this.props.moduleSpacing;
const panelSpacing = this.props.panelSpacing;
const panelMap = {};
// Calculate the height for each panel and store this in a mapping by panel name
_.each(this.props.panels, panel => {
numPanels += 1; // 1
const moduleCount = panel.modules.length; // 2
let couplerCount = 0;
_.each(panel.modules, module => {
couplerCount += module.length; // 6
});
const panelHeight =
couplerCount * this.props.couplerStyle.size +
(couplerCount - moduleCount) * this.props.couplerSpacing +
(moduleCount + 1) * moduleSpacing;
viewBoxHeight += panelHeight;
panelMap[panel.panelName] = panelHeight;
});
// dynamic viewBoxHeight
viewBoxHeight += yOffset * 3 + (numPanels - 1) * panelSpacing;
// Draw in order - Panel Rectangles, Circuit Endpoints, Circuit Connections
return (
<svg
key="panel-container"
width={this.props.width}
height={viewBoxHeight}
className={classed}
style={circuitContainer}
onClick={this._deselect}
>
<svg key="panel-box" preserveAspectRatio="xMinYMin">
{this.renderPanels(panelMap)}
</svg>
</svg>
);
}
}
PatchPanel.propTypes = {
/** The blank margin around the diagram drawing */
margin: PropTypes.number,
labelPosition: PropTypes.oneOf([
"left",
"right",
"center",
"top",
"topright",
"topleft",
"bottom",
"bottomright",
"bottomleft",
"bottomleftangled",
"bottomrightangled",
"topleftangled",
"toprightangled"
]),
/** The width of the circuit diagram */
width: PropTypes.number,
/**
* To accurately display each panel, modules, and groups of circuits,
* the Patch Panel requires an array of panels, where each panel contains
* a panel object. The panel object has two keys, `panelName` to display
* the title of the panel, and `modules` which is a two dimensional array
* of coupler objects. The rendering is sequential, and will display each
* panel, with the panels modules and coupler groupings in the order they
* are presented in the list.
*
* Each module in the two-dimensional `modules` array is an array of
* coupler groupings objects. The coupler groupings objects allways have:
*
* * `frontCircuit` - The circuit and its properties to be displayed to
* the left of the coupler. May be left `null`
* * `backCircuit` - The circuit and its properties to be displayed to
* the right of the coupler. May be left `null`
* * `coupler` - The circuit and its properties to be displayed in the
* center
*
* Each of these objects have there own style, labels, and navigation
* controls. The below structure, will render one panel, with one module,
* with 2 coupler groups.
*
* ```js
* const panels = [
* {
* panelName: "Panel 1",
* modules: [
* [
* {
* frontCircuit: {
* styleProperties: circuitTypeProperties.crossConnect,
* endpointStyle: stylesMap.endpoint,
* endpointLabelA: "Endpoint 1",
* endpointLabelZ: "Endpoint 2",
* circuitLabel: "Member 1",
* navTo: "Member 1",
* },
* coupler: {
* styleProperties: circuitTypeProperties.panelCoupler,
* endpointStyle: circuitTypeProperties.panelCoupler,
* endpointLabelA: "Endpoint 2",
* endpointlabelZ: "Endpoint 3",
* circuitLabel: "1/2-SC",
* navTo: "Coupler 1/2",
* },
* backCircuit: {
* styleProperties: circuitTypeProperties.leased,
* endpointStyle: stylesMap.endpoint,
* endpointLabelA: "Endpoint 3",
* endpointLabelZ: "Endpoint 4",
* circuitLabel: "Member 3",
* navTo: "Member 3",
* },
* frontLabel: "Endpoint A",
* backLabel: "Endpoint Z",
* },
* {
* frontCircuit: null,
* coupler: {
* styleProperties: circuitTypeProperties.panelCoupler,
* endpointStyle: circuitTypeProperties.panelCoupler,
* endpointLabelA: "Endpoint 2",
* endpointlabelZ: "Endpoint 3",
* circuitLabel: "3/4-SC",
* navTo: "Coupler 3/4",
* },
* backCircuit: null,
* frontLabel: "Endpoint A",
* backLabel: "Endpoint Z",
* },
* ]
* ]
* }
* ];
* ```
*/
panels: PropTypes.array.isRequired,
/**
* The style of the panel - this is the "container" for the modules and couplers.
*/
panelStyle: PropTypes.object,
/**
* The style for the couplers, rendered in groups according to their modules.
*/
couplerStyle: PropTypes.object,
/**
* This is the vertical distance from the center line to offset the connection label
*/
yOffset: PropTypes.number,
/**
* This is the vertical spacing between each module group
*/
moduleSpacing: PropTypes.number,
/**
* This is the vertical spacing between each panel
*/
panelSpacing: PropTypes.number,
/**
* This is the vertical spacing between each coupler
*/
couplerSpacing: PropTypes.number,
/**
* This is the distance from the center of the \<svg\> grid that the panel
* is to be rendered
*/
panelWidth: PropTypes.number,
/**
* Callback evoked when the selection changes
*/
onSelectionChange: PropTypes.func,
/**
* This is the distance from the endpoint that the endpoint label will be rendered.
*/
endpointLabelOffset: PropTypes.number,
//
// The following props have default values and are optional for styling:
//
/**
* Controls the corner rounding of the center coupler on the x-axis
*/
roundedX: PropTypes.number,
/**
* Controls the corner rounding of the center coupler on the y-axis
*/
roundedY: PropTypes.number,
/**
* Controls the size of the couper line cap
*/
couplerEndpointRadius: PropTypes.number,
/**
* Controls the corner rounding of the square line-caps on the x-axis
*/
endpointRoundedX: PropTypes.number,
/**
* Controls the corner rounding of the square line-caps on the y-axis
*/
endpointRoundedY: PropTypes.number,
/**
* Controls where label is situated in the center coupler
*/
couplerLabelPosition: PropTypes.oneOf(["top", "bottom", "center"]),
/**
* Controls the corner rounding of the panel on the x-axis
*/
panelRoundedX: PropTypes.number,
/**
* Controls the corner rounding of the panel on the y-axis
*/
panelRoundedY: PropTypes.number,
/**
* Controls the +/- x offset from labelPosition
*/
labelOffsetX: PropTypes.number,
/**
* Controls the +/- y offset from labelPosition
*/
labelOffsetY: PropTypes.number
};
PatchPanel.defaultProps = {
width: 851,
yOffset: 30,
margin: 150,
roundedX: 5,
roundedY: 5,
couplerEndpointRadius: 10,
endpointRoundedX: 2,
endpointRoundedY: 2,
couplerLabelPosition: "center",
labelPosition: "top",
panelRoundedX: 3,
panelRoundedY: 3,
labelOffsetX: 0,
labelOffsetY: 0
};