summaryrefslogtreecommitdiffstats
path: root/src/generic-components/graph
diff options
context:
space:
mode:
Diffstat (limited to 'src/generic-components/graph')
-rw-r--r--src/generic-components/graph/ForceDefinitions.js37
-rw-r--r--src/generic-components/graph/ForceDirectedGraph.jsx498
-rw-r--r--src/generic-components/graph/IconFactory.js251
-rw-r--r--src/generic-components/graph/Link.jsx66
-rw-r--r--src/generic-components/graph/Node.jsx58
-rw-r--r--src/generic-components/graph/NodeFactory.js115
-rw-r--r--src/generic-components/graph/NodeVisualElementConstants.js49
-rw-r--r--src/generic-components/graph/NodeVisualElementFactory.js189
-rw-r--r--src/generic-components/graph/SVGShape.jsx65
9 files changed, 1328 insertions, 0 deletions
diff --git a/src/generic-components/graph/ForceDefinitions.js b/src/generic-components/graph/ForceDefinitions.js
new file mode 100644
index 0000000..92f939f
--- /dev/null
+++ b/src/generic-components/graph/ForceDefinitions.js
@@ -0,0 +1,37 @@
+/*
+ * ============LICENSE_START===================================================
+ * SPARKY (AAI UI service)
+ * ============================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * 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=====================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+
+const ONE_SECOND = 1000;
+const THIRTY_FRAMES_PER_SECOND = 30;
+
+export const simulationKeys = {
+ CENTERING_FORCE: {},
+ COLLISION_FORCE: {},
+ LINK_FORCE: {},
+ MANY_BODY_FORCE: {},
+ POSITIONING_FORCE: {},
+ DEFAULT_FORCE_NAME: 'defaultForce',
+ DATA_COPY_INTERVAL: ONE_SECOND / THIRTY_FRAMES_PER_SECOND
+};
diff --git a/src/generic-components/graph/ForceDirectedGraph.jsx b/src/generic-components/graph/ForceDirectedGraph.jsx
new file mode 100644
index 0000000..caad1c0
--- /dev/null
+++ b/src/generic-components/graph/ForceDirectedGraph.jsx
@@ -0,0 +1,498 @@
+/*
+ * ============LICENSE_START===================================================
+ * SPARKY (AAI UI service)
+ * ============================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * 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=====================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+
+import {drag} from 'd3-drag';
+import {forceSimulation, forceLink, forceManyBody, forceCenter} from 'd3-force';
+import {interpolateNumber} from 'd3-interpolate';
+import {select, event as currentEvent} from 'd3-selection';
+import React, {Component, PropTypes} from 'react';
+import {interval, now} from 'd3-timer';
+import {zoom, zoomIdentity} from 'd3-zoom';
+import NodeConstants from './NodeVisualElementConstants.js';
+
+import {simulationKeys} from './ForceDefinitions.js';
+import NodeFactory from './NodeFactory.js';
+import NodeVisualElementFactory from './NodeVisualElementFactory.js';
+
+class ForceDirectedGraph extends Component {
+ static propTypes = {
+ viewWidth: PropTypes.number,
+ viewHeight: PropTypes.number,
+ graphData: PropTypes.object,
+ nodeIdKey: PropTypes.string,
+ linkIdKey: PropTypes.string,
+ nodeSelectedCallback: PropTypes.func,
+ nodeButtonSelectedCallback: PropTypes.func,
+ currentlySelectedNodeView: PropTypes.string
+ };
+
+ static defaultProps = {
+ viewWidth: 0,
+ viewHeight: 0,
+ graphData: {
+ graphCounter: -1, nodeDataArray: [], linkDataArray: [], graphMeta: {}
+ },
+ nodeIdKey: '',
+ linkIdKey: '',
+ nodeSelectedCallback: undefined,
+ nodeButtonSelectedCallback: undefined,
+ currentlySelectedNodeView: ''
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ nodes: [], links: [], mainGroupTransform: zoomIdentity
+ };
+
+ this.updateSimulationForce = this.updateSimulationForce.bind(this);
+ this.resetTransform = this.resetTransform.bind(this);
+ this.applyBufferDataToState = this.applyBufferDataToState.bind(this);
+ this.createNodePropForState = this.createNodePropForState.bind(this);
+ this.createLinkPropForState = this.createLinkPropForState.bind(this);
+ this.startSimulation = this.startSimulation.bind(this);
+ this.simulationComplete = this.simulationComplete.bind(this);
+ this.simulationTick = this.simulationTick.bind(this);
+ this.nodeSelected = this.nodeSelected.bind(this);
+ this.onZoom = this.onZoom.bind(this);
+ this.onGraphDrag = this.onGraphDrag.bind(this);
+ this.onNodeDrag = this.onNodeDrag.bind(this);
+ this.addNodeInterpolator = this.addNodeInterpolator.bind(this);
+ this.runInterpolators = this.runInterpolators.bind(this);
+
+ this.nodeBuffer = [];
+ this.linkBuffer = [];
+ this.nodeDatum = [];
+ this.nodeButtonDatum = [];
+ this.nodeFactory = new NodeFactory();
+ this.visualElementFactory = new NodeVisualElementFactory();
+
+ this.isGraphMounted = false;
+
+ this.listenerGraphCounter = -1;
+ this.nodeIndexTracker = new Map();
+ this.interpolators = new Map();
+ this.areInterpolationsRunning = false;
+
+ this.newNodeSelected = true;
+ this.currentlySelectedNodeButton = undefined;
+
+ this.intervalTimer = interval(this.applyBufferDataToState, simulationKeys.DATA_COPY_INTERVAL);
+ this.intervalTimer.stop();
+
+ this.interpolationTimer = interval(this.runInterpolators, simulationKeys.DATA_COPY_INTERVAL);
+ this.interpolationTimer.stop();
+
+ this.simulation = forceSimulation();
+ this.simulation.on('end', this.simulationComplete);
+ this.simulation.stop();
+
+ this.svgZoom =
+ zoom().scaleExtent([NodeConstants.SCALE_EXTENT_MIN, NodeConstants.SACEL_EXTENT_MAX]);
+ this.svgZoom.clickDistance(2);
+ this.nodeDrag = drag().clickDistance(2);
+
+ this.updateSimulationForce();
+
+ if (props.graphData) {
+ if (props.graphData.graphCounter !== -1) {
+ this.startSimulation(props.graphData);
+ }
+ }
+ }
+
+ componentDidMount() {
+ this.isGraphMounted = true;
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.graphData.graphCounter !== this.props.graphData.graphCounter) {
+ this.listenerGraphCounter = this.props.graphData.graphCounter;
+ this.newNodeSelected = true;
+ this.resetTransform();
+ this.startSimulation(nextProps.graphData);
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ let hasNewGraphDataRendered = (prevProps.graphData.graphCounter ===
+ this.props.graphData.graphCounter);
+ let shouldAttachListeners = (this.listenerGraphCounter !== this.props.graphData.graphCounter);
+ let nodeCount = this.state.nodes.length;
+
+ if (nodeCount > 0) {
+ if (hasNewGraphDataRendered && shouldAttachListeners) {
+
+ let nodes = select('.fdgMainSvg').select('.fdgMainG')
+ .selectAll('.aai-entity-node')
+ .data(this.nodeDatum);
+
+ nodes.on('click', (d) => {
+ this.nodeSelected(d);
+ });
+
+ nodes.call(this.nodeDrag.on('drag', (d) => {
+ let xAndY = [currentEvent.x, currentEvent.y];
+ this.onNodeDrag(d, xAndY);
+ }));
+
+ let mainSVG = select('.fdgMainSvg');
+ let mainView = mainSVG.select('.fdgMainView');
+ this.svgZoom.transform(mainSVG, zoomIdentity);
+ this.svgZoom.transform(mainView, zoomIdentity);
+
+ mainSVG.call(this.svgZoom.on('zoom', () => { // D3 Zoom also handles panning
+ this.onZoom(currentEvent.transform);
+ })).on('dblclick.zoom', null); // Ignore the double-click zoom event
+
+ this.listenerGraphCounter = this.props.graphData.graphCounter;
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ this.isGraphMounted = false;
+
+ let nodes = select('.fdgMainSvg').select('.fdgMainG')
+ .selectAll('.aai-entity-node');
+ let nodeButtons = nodes.selectAll('.node-button');
+
+ nodes.on('click', null);
+ nodeButtons.on('click', null);
+
+ let mainSVG = select('.fdgMainSvg');
+
+ mainSVG.call(this.svgZoom.on('zoom', null)).on('dblclick.zoom', null);
+ mainSVG.call(drag().on('drag', null));
+ }
+
+ updateSimulationForce() {
+ this.simulation.force('link', forceLink());
+ this.simulation.force('link').id((d) => {
+ return d.id;
+ });
+ this.simulation.force('link').strength(0.3);
+ this.simulation.force('link').distance(100);
+
+ this.simulation.force('charge', forceManyBody());
+ this.simulation.force('charge').strength(-1250);
+ this.simulation.alpha(1);
+
+ this.simulation.force('center',
+ forceCenter(this.props.viewWidth / 2, this.props.viewHeight / 2));
+ }
+
+ resetTransform() {
+ if (this.isGraphMounted) {
+ this.setState(() => {
+ return {
+ mainGroupTransform: zoomIdentity
+ };
+ });
+ }
+ }
+
+ applyBufferDataToState() {
+ this.nodeIndexTracker.clear();
+
+ let newNodes = [];
+ this.nodeBuffer.map((node, i) => {
+ let nodeProps = this.createNodePropForState(node);
+
+ if (nodeProps.meta.nodeMeta.className === NodeConstants.SELECTED_NODE_CLASS_NAME ||
+ nodeProps.meta.nodeMeta.className === NodeConstants.SELECTED_SEARCHED_NODE_CLASS_NAME) {
+
+ this.nodeButtonDatum[0].data = nodeProps.meta;
+
+ nodeProps = {
+ ...nodeProps,
+ buttons: [this.nodeButtonDatum[0].isSelected]
+ };
+ }
+
+ newNodes.push(this.nodeFactory.buildNode(nodeProps.meta.nodeMeta.className, nodeProps));
+
+ this.nodeIndexTracker.set(node.id, i);
+ });
+
+ let newLinks = [];
+ this.linkBuffer.map((link) => {
+ let key = link.id;
+ let linkProps = this.createLinkPropForState(link);
+ newLinks.push(this.visualElementFactory.createSvgLine(linkProps, key));
+ });
+
+ if (this.isGraphMounted) {
+ this.setState(() => {
+ return {
+ nodes: newNodes, links: newLinks
+ };
+ });
+ }
+ }
+
+ createNodePropForState(nodeData) {
+ return {
+ renderProps: {
+ key: nodeData.id, x: nodeData.x, y: nodeData.y
+ }, meta: {
+ ...nodeData
+ }
+ };
+ }
+
+ createLinkPropForState(linkData) {
+ return {
+ className: 'aai-entity-link',
+ x1: linkData.source.x,
+ y1: linkData.source.y,
+ x2: linkData.target.x,
+ y2: linkData.target.y
+ };
+ }
+
+ startSimulation(graphData) {
+ this.nodeFactory.setNodeMeta(graphData.graphMeta);
+
+ // Experiment with removing length = 0... might not be needed as new array
+ // assignment will likely destroy old reference
+ this.nodeBuffer.length = 0;
+ this.nodeBuffer = Array.from(graphData.nodeDataArray);
+ this.linkBuffer.length = 0;
+ this.linkBuffer = Array.from(graphData.linkDataArray);
+ this.nodeDatum.length = 0;
+ this.nodeDatum = Array.from(graphData.nodeDataArray);
+
+ this.nodeButtonDatum.length = 0;
+
+ let isNodeDetailsSelected = true;
+ this.nodeButtonDatum.push({
+ name: NodeConstants.ICON_ELLIPSES, isSelected: isNodeDetailsSelected
+ });
+
+ if (isNodeDetailsSelected) {
+ this.currentlySelectedNodeButton = NodeConstants.ICON_ELLIPSES;
+ }
+
+ this.updateSimulationForce();
+
+ this.simulation.nodes(this.nodeBuffer);
+ this.simulation.force('link').links(this.linkBuffer);
+ this.simulation.on('tick', this.simulationTick);
+ this.simulation.restart();
+ }
+
+ simulationComplete() {
+ this.intervalTimer.stop();
+ this.applyBufferDataToState();
+ }
+
+ simulationTick() {
+ this.intervalTimer.restart(this.applyBufferDataToState, simulationKeys.DATA_COPY_INTERVAL);
+ this.simulation.on('tick', null);
+ }
+
+ nodeSelected(datum) {
+ if (this.props.nodeSelectedCallback) {
+ this.props.nodeSelectedCallback(datum);
+ }
+
+ let didUpdateNew = false;
+ let didUpdatePrevious = false;
+ let isSameNodeSelected = true;
+
+ // Check to see if a default node was previously selected
+ let selectedDefaultNode = select('.fdgMainSvg').select('.fdgMainG')
+ .selectAll('.aai-entity-node')
+ .filter('.selected-node');
+ if (!selectedDefaultNode.empty()) {
+ if (selectedDefaultNode.datum().id !== datum.id) {
+ this.nodeBuffer[selectedDefaultNode.datum().index].nodeMeta.className =
+ NodeConstants.GENERAL_NODE_CLASS_NAME;
+ didUpdatePrevious = true;
+ isSameNodeSelected = false;
+ }
+ }
+
+ // Check to see if a searched node was previously selected
+ let selectedSearchedNode = select('.fdgMainSvg').select('.fdgMainG')
+ .selectAll('.aai-entity-node')
+ .filter('.selected-search-node');
+ if (!selectedSearchedNode.empty()) {
+ if (selectedSearchedNode.datum().id !== datum.id) {
+ this.nodeBuffer[selectedSearchedNode.datum().index].nodeMeta.className =
+ NodeConstants.SEARCHED_NODE_CLASS_NAME;
+ didUpdatePrevious = true;
+ isSameNodeSelected = false;
+ }
+ }
+
+ if (!isSameNodeSelected) {
+ let newlySelectedNode = select('.fdgMainSvg').select('.fdgMainG')
+ .selectAll('.aai-entity-node')
+ .filter((d) => {
+ return (datum.id === d.id);
+ });
+ if (!newlySelectedNode.empty()) {
+
+ if (newlySelectedNode.datum().nodeMeta.searchTarget) {
+ this.nodeBuffer[newlySelectedNode.datum().index].nodeMeta.className =
+ NodeConstants.SELECTED_SEARCHED_NODE_CLASS_NAME;
+ } else {
+ this.nodeBuffer[newlySelectedNode.datum().index].nodeMeta.className =
+ NodeConstants.SELECTED_NODE_CLASS_NAME;
+ }
+ didUpdateNew = true;
+ }
+ }
+
+ if (didUpdatePrevious && didUpdateNew) {
+ this.newNodeSelected = true;
+ this.applyBufferDataToState();
+ }
+ }
+
+ onZoom(eventTransform) {
+ if (this.isGraphMounted) {
+ this.setState(() => {
+ return {
+ mainGroupTransform: eventTransform
+ };
+ });
+ }
+ }
+
+ onGraphDrag(xAndYCoords) {
+ let translate = `translate(${xAndYCoords.x}, ${xAndYCoords.y})`;
+ let oldTransform = this.state.mainGroupTransform;
+ if (this.isGraphMounted) {
+ this.setState(() => {
+ return {
+ ...oldTransform, translate
+ };
+ });
+ }
+ }
+
+ onNodeDrag(datum, xAndYCoords) {
+ let nodeIndex = this.nodeIndexTracker.get(datum.id);
+ if (this.nodeBuffer[nodeIndex]) {
+ this.nodeBuffer[nodeIndex].x = xAndYCoords[0];
+ this.nodeBuffer[nodeIndex].y = xAndYCoords[1];
+ this.applyBufferDataToState();
+ }
+ }
+
+ addNodeInterpolator(nodeId, key, startingValue, endingValue, duration) {
+ let numberInterpolator = interpolateNumber(startingValue, endingValue);
+ let timeNow = now();
+ let interpolationObject = {
+ nodeId: nodeId, key: key, duration: duration, timeCreated: timeNow, method: numberInterpolator
+ };
+ this.interpolators.set(nodeId, interpolationObject);
+
+ if (!this.areInterpolationsRunning) {
+ this.interpolationTimer.restart(this.runInterpolators, simulationKeys.DATA_COPY_INTERVAL);
+ this.areInterpolationsRunning = true;
+ }
+ }
+
+ runInterpolators() {
+ // If we have no more interpolators to run then shut'r down!
+ if (this.interpolators.size === 0) {
+ this.interpolationTimer.stop();
+ this.areInterpolationsRunning = false;
+ }
+
+ let iterpolatorsComplete = [];
+ // Apply interpolation values
+ this.interpolators.forEach((interpolator) => {
+ let nodeIndex = this.nodeIndexTracker.get(interpolator.nodeId);
+ if (nodeIndex) {
+ let elapsedTime = now() - interpolator.timeCreated;
+ // Normalize t as D3's interpolateNumber needs a value between 0 and 1
+ let t = elapsedTime / interpolator.duration;
+ if (t >= 1) {
+ t = 1;
+ iterpolatorsComplete.push(interpolator.nodeId);
+ }
+ this.nodeBuffer[nodeIndex][interpolator.key] = interpolator.method(t);
+ }
+ });
+
+ // Remove any interpolators that are complete
+ if (iterpolatorsComplete.length > 0) {
+ for (let i = 0; i < iterpolatorsComplete.length; i++) {
+ this.interpolators.delete(iterpolatorsComplete[i]);
+ }
+ }
+
+ this.applyBufferDataToState();
+ }
+
+ render() {
+ // We will be using these values veru shortly, commenting out for eslint
+ // reasons so we can build for PV let {viewWidth, viewHeight} = this.props;
+ let {nodes, links, mainGroupTransform} = this.state;
+
+ return (
+ <div className='ts-force-selected-graph'>
+ <svg className={'fdgMainSvg'} width='100%' height='100%'>
+ <rect className={'fdgMainView'} x='0.5' y='0.5' width='99%'
+ height='99%' fill='none'/>
+ <filter id='selected-node-drop-shadow'>
+ <feGaussianBlur in='SourceAlpha' stdDeviation='2.2'/>
+ <feOffset dx='-1' dy='1' result='offsetblur'/>
+ <feFlood floodColor='rgba(0,0,0,0.5)'/>
+ <feComposite in2='offsetblur' operator='in'/>
+ <feMerge>
+ <feMergeNode/>
+ <feMergeNode in='SourceGraphic'/>
+ </feMerge>
+ </filter>
+ <g className={'fdgMainG'} transform={mainGroupTransform}>
+ {links}
+ {nodes}
+ </g>
+ </svg>
+ </div>
+ );
+ }
+
+ static graphCounter = 0;
+
+ static generateNewProps(nodeArray, linkArray, metaData) {
+ ForceDirectedGraph.graphCounter += 1;
+ return {
+ graphCounter: ForceDirectedGraph.graphCounter,
+ nodeDataArray: nodeArray,
+ linkDataArray: linkArray,
+ graphMeta: metaData
+ };
+ }
+}
+
+export default ForceDirectedGraph;
diff --git a/src/generic-components/graph/IconFactory.js b/src/generic-components/graph/IconFactory.js
new file mode 100644
index 0000000..772cb1e
--- /dev/null
+++ b/src/generic-components/graph/IconFactory.js
@@ -0,0 +1,251 @@
+/*
+ * ============LICENSE_START===================================================
+ * SPARKY (AAI UI service)
+ * ============================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * 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=====================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+
+import React from 'react';
+
+import NodeVisualElementConstants from './NodeVisualElementConstants.js';
+
+class IconFactory {
+ static createIcon(iconName, iconProps, key, nodeProps) {
+ switch (iconName) {
+ case NodeVisualElementConstants.ICON_ELLIPSES:
+ let iconEllipsesChildren = [];
+
+ if (iconProps.svgAttributes) {
+ let circleProps = {
+ ...iconProps.svgAttributes,
+ key: key + '_ellipsesCircle'
+ };
+
+ let ellipsesBackgroundClassName = (iconProps.isSelected === true)
+ ? 'background-selected'
+ : 'background-unselected';
+
+ circleProps = {
+ ...circleProps,
+ className: ellipsesBackgroundClassName
+ };
+
+ iconEllipsesChildren.push(
+ React.createElement(NodeVisualElementConstants.SVG_CIRCLE,
+ circleProps));
+ }
+
+ let ellipseOneProps = {
+ className: 'ellipses-ellipse',
+ cx: '-4',
+ cy: '0',
+ rx: '1.5',
+ ry: '1.5',
+ key: key + '_ellipseOne'
+ };
+ iconEllipsesChildren.push(
+ React.createElement(NodeVisualElementConstants.ELLIPSE,
+ ellipseOneProps));
+
+ let ellipseTwoProps = {
+ className: 'ellipses-ellipse',
+ cx: '0',
+ cy: '0',
+ rx: '1.5',
+ ry: '1.5',
+ key: key + '_ellipseTwo'
+ };
+ iconEllipsesChildren.push(
+ React.createElement(NodeVisualElementConstants.ELLIPSE,
+ ellipseTwoProps));
+
+ let ellipseThreeProps = {
+ className: 'ellipses-ellipse',
+ cx: '4',
+ cy: '0',
+ rx: '1.5',
+ ry: '1.5',
+ key: key + '_ellipseThree'
+ };
+ iconEllipsesChildren.push(
+ React.createElement(NodeVisualElementConstants.ELLIPSE,
+ ellipseThreeProps));
+
+ let finalEllipsesProps = {
+ className: iconProps.class,
+ key: key
+ };
+
+ if (iconProps.shapeAttributes) {
+ if (iconProps.shapeAttributes.offset) {
+ finalEllipsesProps = {
+ ...finalEllipsesProps,
+ transform: `translate(
+ ${iconProps.shapeAttributes.offset.x},
+ ${iconProps.shapeAttributes.offset.y})`
+ };
+ }
+ }
+
+ return React.createElement(NodeVisualElementConstants.G,
+ finalEllipsesProps, iconEllipsesChildren);
+
+ case NodeVisualElementConstants.ICON_TRIANGLE_WARNING:
+ let iconTriangleWarningChildren = [];
+
+ if (iconProps.svgAttributes) {
+ let circleProps = {
+ ...iconProps.svgAttributes,
+ key: key + '_triangleWarningCircle'
+ };
+
+ let triangleWarningBackgrounClassName = (iconProps.isSelected ===
+ true) ? 'background-selected' : 'background-unselected';
+
+ circleProps = {
+ ...circleProps,
+ className: triangleWarningBackgrounClassName
+ };
+ iconTriangleWarningChildren.push(
+ React.createElement(NodeVisualElementConstants.SVG_CIRCLE,
+ circleProps));
+ }
+
+ let trianglePathProps = {
+ className: 'triangle-warning',
+ d: 'M-4.5 4 L 0 -6.5 L 4.5 4 Z M-0.5 3.75 L -0.5 3 L 0.5 3 L 0.5 3.75 Z M-0.35 2.75 L -0.75 -3.5 L 0.75 -3.5 L 0.35 2.75 Z',
+ key: key + '_triangleWarningPath'
+ };
+ iconTriangleWarningChildren.push(
+ React.createElement(NodeVisualElementConstants.PATH,
+ trianglePathProps));
+
+ let finalTriangleWarningProps = {
+ className: iconProps.class,
+ key: key
+ };
+
+ if (iconProps.shapeAttributes) {
+ if (iconProps.shapeAttributes.offset) {
+ finalTriangleWarningProps = {
+ ...finalTriangleWarningProps,
+ transform: `translate(
+ ${iconProps.shapeAttributes.offset.x},
+ ${iconProps.shapeAttributes.offset.y})`
+ };
+ }
+ }
+
+ return React.createElement(NodeVisualElementConstants.G,
+ finalTriangleWarningProps, iconTriangleWarningChildren);
+
+ case NodeVisualElementConstants.ICON_TICK:
+ let tickOverlayMainKey = nodeProps.meta.id + '_overlayTick';
+ let iconTickRadius = 5;
+ let tickNodeClassName = nodeProps.meta.nodeMeta.className;
+ if (tickNodeClassName ===
+ NodeVisualElementConstants.SELECTED_SEARCHED_NODE_CLASS_NAME ||
+ tickNodeClassName ===
+ NodeVisualElementConstants.SELECTED_NODE_CLASS_NAME) {
+ iconTickRadius = 8;
+ }
+ let tickIconcircleProps = {
+ className: 'icon_tick_circle',
+ r: iconTickRadius,
+ key: key + '_tickCircle'
+ };
+ let iconTickChildren = [];
+
+ iconTickChildren.push(
+ React.createElement(NodeVisualElementConstants.SVG_CIRCLE,
+ tickIconcircleProps));
+ let tickIconTransformProperty = 'translate(-15, -10)';
+ if (tickNodeClassName ===
+ NodeVisualElementConstants.SELECTED_SEARCHED_NODE_CLASS_NAME ||
+ tickNodeClassName ===
+ NodeVisualElementConstants.SELECTED_NODE_CLASS_NAME) {
+ tickIconTransformProperty = 'translate(-30, -18)';
+
+ }
+ let tickPathProps = {
+ className: 'icon_tick_path',
+ d: 'M-3 0 L -1.5 1.8 L3 -1.5 L -1.5 1.8',
+ key: key + '_tickPath'
+ };
+ iconTickChildren.push(
+ React.createElement(NodeVisualElementConstants.PATH, tickPathProps));
+
+ let finalTickIconProps = {
+ className: 'icon_tick',
+ key: tickOverlayMainKey + '_final',
+ transform: tickIconTransformProperty
+ };
+ return React.createElement(NodeVisualElementConstants.G,
+ finalTickIconProps, iconTickChildren);
+
+ case NodeVisualElementConstants.ICON_WARNING:
+ let warningOverlayMainKey = nodeProps.meta.id + '_overlayTick';
+ let iconWarningRadius = 5;
+ let warningNodeClassName = nodeProps.meta.nodeMeta.className;
+ if (warningNodeClassName ===
+ NodeVisualElementConstants.SELECTED_SEARCHED_NODE_CLASS_NAME ||
+ warningNodeClassName ===
+ NodeVisualElementConstants.SELECTED_NODE_CLASS_NAME) {
+ iconWarningRadius = 8;
+ }
+ let warningIconcircleProps = {
+ className: 'icon_warning_circle',
+ r: iconWarningRadius,
+ key: key + '_warningCircle'
+ };
+ let iconWarningChildren = [];
+
+ iconWarningChildren.push(
+ React.createElement(NodeVisualElementConstants.SVG_CIRCLE,
+ warningIconcircleProps));
+ let warningIconTransformProperty = 'translate(-15, -10)';
+ if (warningNodeClassName ===
+ NodeVisualElementConstants.SELECTED_SEARCHED_NODE_CLASS_NAME ||
+ warningNodeClassName ===
+ NodeVisualElementConstants.SELECTED_NODE_CLASS_NAME) {
+ warningIconTransformProperty = 'translate(-30, -18)';
+ }
+ let warningPathProps = {
+ className: 'icon_warning_path',
+ d: 'M-0.35 3.8 L -0.35 3.7 L 0.35 3.7 L 0.35 3.8 Z M-0.1 1.8 L -0.6 -3.5 L 0.6 -3.5 L 0.1 1.8 Z',
+ key: key + '_tickPath'
+ };
+ iconWarningChildren.push(
+ React.createElement(NodeVisualElementConstants.PATH,
+ warningPathProps));
+
+ let finalWarningIconProps = {
+ className: 'icon_warning',
+ key: warningOverlayMainKey + '_final',
+ transform: warningIconTransformProperty
+ };
+ return React.createElement(NodeVisualElementConstants.G,
+ finalWarningIconProps, iconWarningChildren);
+ }
+ }
+}
+
+export default IconFactory;
diff --git a/src/generic-components/graph/Link.jsx b/src/generic-components/graph/Link.jsx
new file mode 100644
index 0000000..c4ef235
--- /dev/null
+++ b/src/generic-components/graph/Link.jsx
@@ -0,0 +1,66 @@
+/*
+ * ============LICENSE_START===================================================
+ * SPARKY (AAI UI service)
+ * ============================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * 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=====================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+
+import React, {Component} from 'react';
+
+import TempCreateAttributes from './TempCreateAttributes.js';
+
+class Link extends Component {
+
+ static propTypes = {
+ x1: React.PropTypes.number,
+ y1: React.PropTypes.number,
+ x2: React.PropTypes.number,
+ y2: React.PropTypes.number,
+ linkAttributes: React.PropTypes.object
+ };
+
+ static defaultProps = {
+ x1: 0,
+ y1: 0,
+ x2: 0,
+ y2: 0,
+ linkAttributes: {}
+ };
+
+ render() {
+ let {x1, y1, x2, y2, linkAttributes} = this.props;
+
+ let combinedAttributes = {
+ ...linkAttributes,
+ x1: x1,
+ y1: y1,
+ x2: x2,
+ y2: y2
+ };
+
+ return (
+ <line {...combinedAttributes}
+ style={TempCreateAttributes.createLineStyle()}/>
+ );
+ }
+}
+
+export default Link;
diff --git a/src/generic-components/graph/Node.jsx b/src/generic-components/graph/Node.jsx
new file mode 100644
index 0000000..79f7161
--- /dev/null
+++ b/src/generic-components/graph/Node.jsx
@@ -0,0 +1,58 @@
+/*
+ * ============LICENSE_START===================================================
+ * SPARKY (AAI UI service)
+ * ============================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * 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=====================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+
+import React, {Component} from 'react';
+
+class Node extends Component {
+
+ static propTypes = {
+ x: React.PropTypes.number,
+ y: React.PropTypes.number,
+ nodeClass: React.PropTypes.string,
+ visualElements: React.PropTypes.array,
+ meta: React.PropTypes.object
+ };
+
+ static defaultProps = {
+ x: 0,
+ y: 0,
+ nodeClass: '',
+ visualElements: [],
+ meta: {}
+ };
+
+ render() {
+ let {x, y, nodeClass, visualElements} = this.props;
+ let translate = `translate(${x}, ${y})`;
+
+ return (
+ <g className={nodeClass} transform={translate}>
+ {visualElements}
+ </g>
+ );
+ }
+}
+
+export default Node;
diff --git a/src/generic-components/graph/NodeFactory.js b/src/generic-components/graph/NodeFactory.js
new file mode 100644
index 0000000..6dced1d
--- /dev/null
+++ b/src/generic-components/graph/NodeFactory.js
@@ -0,0 +1,115 @@
+/*
+ * ============LICENSE_START===================================================
+ * SPARKY (AAI UI service)
+ * ============================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * 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=====================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+
+import React from 'react';
+
+import NodeVisualElementConstants from './NodeVisualElementConstants.js';
+import NodeVisualElementFactory from './NodeVisualElementFactory.js';
+
+class NodeFactory {
+
+ constructor() {
+ this.graphMeta = {};
+ this.visualElementFactory = new NodeVisualElementFactory();
+
+ this.setNodeMeta = this.setNodeMeta.bind(this);
+ }
+
+ setNodeMeta(metaObject) {
+ this.graphMeta = metaObject;
+ this.visualElementFactory.setVisualElementMeta(metaObject);
+ }
+
+ buildNode(nodeType, nodeProps) {
+
+ let translate = `translate(
+ ${nodeProps.renderProps.x},
+ ${nodeProps.renderProps.y})`;
+ let finalProps = {
+ ...nodeProps.renderProps,
+ className: this.graphMeta.aaiEntityNodeDescriptors[nodeType].class,
+ transform: translate
+ };
+
+ let nodeVisualElementsData = this.extractVisualElementArrayFromMeta(
+ nodeType);
+ let nodeVisualElements = undefined;
+ if (nodeVisualElementsData) {
+ nodeVisualElements = [];
+ nodeVisualElementsData.map((elementData, index) => {
+ if (elementData.type === NodeVisualElementConstants.BUTTON) {
+ if (nodeProps.buttons) {
+ let isButtonSelected = true;
+ elementData = {
+ ...elementData,
+ isSelected: isButtonSelected
+ };
+ }
+ }
+ nodeVisualElements.push(
+ this.visualElementFactory.buildVisualElement(nodeProps.meta,
+ elementData.type, elementData, index));
+ });
+ //Draw overlay only if the node is validated
+ if (nodeProps.meta.nodeMeta.nodeValidated) {
+
+ if (nodeProps.meta.nodeMeta.nodeIssue) {
+ let warningOverlayProps = {
+ name: NodeVisualElementConstants.ICON_WARNING,
+ };
+ nodeVisualElements.push(
+ this.visualElementFactory.buildVisualElement(nodeProps,
+ NodeVisualElementConstants.ICON, warningOverlayProps,
+ nodeVisualElementsData.length + 1));
+ } else {
+ let tickOverlayProps = {
+ name: NodeVisualElementConstants.ICON_TICK,
+ };
+ nodeVisualElements.push(
+ this.visualElementFactory.buildVisualElement(nodeProps,
+ NodeVisualElementConstants.ICON, tickOverlayProps,
+ nodeVisualElementsData.length + 1));
+ }
+ }
+ }
+
+ if (nodeVisualElements) {
+ return React.createElement('g', finalProps, nodeVisualElements);
+ }
+
+ return React.createElement('g', finalProps);
+ }
+
+ extractVisualElementArrayFromMeta(nodeClassName) {
+ let nodeVisualElements = undefined;
+ if (this.graphMeta.aaiEntityNodeDescriptors) {
+ nodeVisualElements =
+ this.graphMeta.aaiEntityNodeDescriptors[nodeClassName].visualElements;
+ }
+ return nodeVisualElements;
+ }
+}
+
+export default NodeFactory;
diff --git a/src/generic-components/graph/NodeVisualElementConstants.js b/src/generic-components/graph/NodeVisualElementConstants.js
new file mode 100644
index 0000000..d89b6c9
--- /dev/null
+++ b/src/generic-components/graph/NodeVisualElementConstants.js
@@ -0,0 +1,49 @@
+/*
+ * ============LICENSE_START===================================================
+ * SPARKY (AAI UI service)
+ * ============================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * 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=====================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+
+export default {
+ SVG_CIRCLE: 'circle',
+ SVG_LINE: 'line',
+ TEXT: 'text',
+ IMAGE: 'image',
+ OBJECT: 'object',
+ ELLIPSE: 'ellipse',
+ G: 'g',
+ PATH: 'path',
+ BUTTON: 'button',
+ ICON: 'icon',
+ CSS_CLASS: 'className',
+ SELECTED_NODE_CLASS_NAME: 'selectedNodeClass',
+ GENERAL_NODE_CLASS_NAME: 'generalNodeClass',
+ SEARCHED_NODE_CLASS_NAME: 'searchedNodeClass',
+ SELECTED_SEARCHED_NODE_CLASS_NAME: 'selectedSearchedNodeClass',
+ SCALE_EXTENT_MIN: 0.125,
+ SACEL_EXTENT_MAX: 10,
+ ICON_ELLIPSES: 'icon_ellipses',
+ ICON_TRIANGLE_WARNING: 'icon_triangle_warning',
+ ICON_TICK: 'icon_tick',
+ ICON_WARNING: 'icon_warning',
+ BUTTON_CLICK_NODE_DETAILS: 'NODE_DETAILS'
+};
diff --git a/src/generic-components/graph/NodeVisualElementFactory.js b/src/generic-components/graph/NodeVisualElementFactory.js
new file mode 100644
index 0000000..13e9b7c
--- /dev/null
+++ b/src/generic-components/graph/NodeVisualElementFactory.js
@@ -0,0 +1,189 @@
+/*
+ * ============LICENSE_START===================================================
+ * SPARKY (AAI UI service)
+ * ============================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * 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=====================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+
+import React from 'react';
+
+import IconFactory from './IconFactory.js';
+import NodeVisualElementConstants from './NodeVisualElementConstants.js';
+
+class NodeVisualElementFactory {
+
+ constructor() {
+ this.visualElementMeta = {};
+
+ this.setVisualElementMeta = this.setVisualElementMeta.bind(this);
+ this.buildVisualElement = this.buildVisualElement.bind(this);
+ this.createSvgCircle = this.createSvgCircle.bind(this);
+ this.createSvgLine = this.createSvgLine.bind(this);
+ this.createTextElement = this.createTextElement.bind(this);
+ this.createImageElement = this.createImageElement.bind(this);
+ this.createObjectElement = this.createObjectElement.bind(this);
+ this.createButtonElement = this.createButtonElement.bind(this);
+ this.applySvgAttributes = this.applySvgAttributes.bind(this);
+ this.applyTransform = this.applyTransform.bind(this);
+ }
+
+ setVisualElementMeta(metaObject) {
+ this.visualElementMeta = metaObject;
+ }
+
+ buildVisualElement(nodeProps, elementType, elementProps, index) {
+ let elementKey = nodeProps.id + index.toString();
+ switch (elementType) {
+ case NodeVisualElementConstants.SVG_CIRCLE:
+ return this.createSvgCircle(elementProps, elementKey);
+
+ case NodeVisualElementConstants.SVG_LINE:
+ return this.createSvgLine(elementProps, elementKey);
+
+ case NodeVisualElementConstants.TEXT:
+ return this.createTextElement(nodeProps, elementProps, elementKey);
+
+ case NodeVisualElementConstants.IMAGE:
+ return this.createImageElement(elementProps, elementKey);
+
+ case NodeVisualElementConstants.OBJECT:
+ return this.createObjectElement(elementProps, elementKey);
+
+ case NodeVisualElementConstants.BUTTON:
+ return this.createButtonElement(elementProps, elementKey);
+
+ case NodeVisualElementConstants.ICON:
+ return this.createButtonElement(elementProps, elementKey, nodeProps);
+
+ }
+ }
+
+ createSvgCircle(circleProps, elementKey) {
+ let finalProps = {};
+ finalProps[NodeVisualElementConstants.CSS_CLASS] = circleProps.class;
+
+ finalProps = this.applyTransform(finalProps, circleProps.shapeAttributes);
+ finalProps = this.applySvgAttributes(finalProps, circleProps.svgAttributes);
+
+ finalProps = {
+ ...finalProps,
+ key: elementKey
+ };
+
+ return React.createElement(NodeVisualElementConstants.SVG_CIRCLE,
+ finalProps);
+ }
+
+ createSvgLine(lineProps, elementKey) {
+
+ /* Keep this commented code. Will be used again when
+ proper link construction is added
+ let finalProps = {};
+ finalProps[NodeVisualElementConstants.CSS_CLASS] = lineProps.class;
+ finalProps = this.applySvgAttributes(finalProps, lineProps.svgAttributes);
+ finalProps = this.applyTransform(finalProps, lineProps.shapeAttributes);
+ */
+
+ let finalProps = {
+ ...lineProps,
+ key: elementKey
+ };
+
+ return React.createElement(NodeVisualElementConstants.SVG_LINE, finalProps);
+ }
+
+ createTextElement(nodeProps, textProps, elementKey) {
+ let finalProps = {};
+ finalProps[NodeVisualElementConstants.CSS_CLASS] = textProps.class;
+
+ finalProps = this.applySvgAttributes(finalProps, textProps.svgAttributes);
+ finalProps = this.applyTransform(finalProps, textProps.shapeAttributes);
+
+ finalProps = {
+ ...finalProps,
+ key: elementKey
+ };
+
+ return React.createElement(NodeVisualElementConstants.TEXT, finalProps,
+ nodeProps[textProps.displayKey]);
+ }
+
+ createImageElement(imageProps, elementKey) {
+ let finalProps = {};
+ finalProps[NodeVisualElementConstants.CSS_CLASS] = imageProps.class;
+
+ finalProps = this.applyTransform(finalProps, imageProps.shapeAttributes);
+ finalProps = this.applySvgAttributes(finalProps, imageProps.svgAttributes);
+
+ finalProps = {
+ ...finalProps,
+ key: elementKey
+ };
+
+ return React.createElement(NodeVisualElementConstants.IMAGE, finalProps);
+ }
+
+ createObjectElement(objectProps, elementKey) {
+ let finalProps = {};
+ finalProps[NodeVisualElementConstants.CSS_CLASS] = objectProps.class;
+
+ finalProps = this.applyTransform(finalProps, objectProps.shapeAttributes);
+ finalProps = this.applySvgAttributes(finalProps, objectProps.svgAttributes);
+
+ finalProps = {
+ ...finalProps,
+ key: elementKey
+ };
+
+ return React.createElement(NodeVisualElementConstants.OBJECT, finalProps);
+ }
+
+ createButtonElement(buttonProps, elementKey, nodeMeta) {
+ return IconFactory.createIcon(buttonProps.name, buttonProps, elementKey,
+ nodeMeta);
+ }
+
+ applySvgAttributes(elementProps, svgAttributes) {
+ if (svgAttributes) {
+ return {
+ ...elementProps,
+ ...svgAttributes
+ };
+ }
+ return elementProps;
+ }
+
+ applyTransform(elementProps, shapeAttributes) {
+ if (shapeAttributes) {
+ if (shapeAttributes.offset) {
+ return {
+ ...elementProps,
+ transform: `translate(
+ ${shapeAttributes.offset.x},
+ ${shapeAttributes.offset.y})`
+ };
+ }
+ }
+ return elementProps;
+ }
+}
+
+export default NodeVisualElementFactory;
diff --git a/src/generic-components/graph/SVGShape.jsx b/src/generic-components/graph/SVGShape.jsx
new file mode 100644
index 0000000..7a3781c
--- /dev/null
+++ b/src/generic-components/graph/SVGShape.jsx
@@ -0,0 +1,65 @@
+/*
+ * ============LICENSE_START===================================================
+ * SPARKY (AAI UI service)
+ * ============================================================================
+ * Copyright © 2017 AT&T Intellectual Property.
+ * Copyright © 2017 Amdocs
+ * 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=====================================================
+ *
+ * ECOMP and OpenECOMP are trademarks
+ * and service marks of AT&T Intellectual Property.
+ */
+
+import React, {Component} from 'react';
+import NodeVisualElementConstants from './NodeVisualElementConstants';
+
+class SVGShape extends Component {
+
+ static propTypes = {
+ shapeType: React.PropTypes.string.isRequired,
+ shapeAttributes: React.PropTypes.object.isRequired,
+ shapeClass: React.PropTypes.object.isRequired,
+ textValue: React.PropTypes.string
+ };
+
+ static defaultProps = {
+ shapeType: '',
+ shapeAttributes: {},
+ shapeClass: {},
+ textValue: ''
+ };
+
+ render() {
+ let {shapeType, shapeAttributes, shapeClass, textValue} = this.props;
+
+ switch (shapeType) {
+ case NodeVisualElementConstants.SVG_CIRCLE:
+ return <circle {...shapeAttributes} className={shapeClass}/>;
+
+ case NodeVisualElementConstants.SVG_LINELINE:
+ return <line {...shapeAttributes} className={shapeClass}/>;
+
+ case NodeVisualElementConstants.TEXT:
+ return <text {...shapeAttributes}
+ className={shapeClass}>{textValue}</text>;
+
+ default:
+ return undefined;
+ }
+ }
+}
+
+export default SVGShape;