diff options
Diffstat (limited to 'openecomp-ui/src/nfvo-components/tree/Tree.jsx')
-rw-r--r-- | openecomp-ui/src/nfvo-components/tree/Tree.jsx | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/openecomp-ui/src/nfvo-components/tree/Tree.jsx b/openecomp-ui/src/nfvo-components/tree/Tree.jsx new file mode 100644 index 0000000000..682f3b6d50 --- /dev/null +++ b/openecomp-ui/src/nfvo-components/tree/Tree.jsx @@ -0,0 +1,181 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import {select} from 'd3-selection'; +import {tree, stratify} from 'd3-hierarchy'; + + +function diagonal(d) { + + const offset = 50; + return 'M' + d.y + ',' + d.x + + 'C' + (d.parent.y + offset) + ',' + d.x + + ' ' + (d.parent.y + offset) + ',' + d.parent.x + + ' ' + d.parent.y + ',' + d.parent.x; +} + +const nodeRadius = 8; +const verticalSpaceBetweenNodes = 70; +const NARROW_HORIZONTAL_SPACES = 47; +const WIDE_HORIZONTAL_SPACES = 65; + +const stratifyFn = stratify().id(d => d.id).parentId(d => d.parent); + +class Tree extends Component { + + // state = { + // startingCoordinates: null, + // isDown: false + // } + + static propTypes = { + name: PropTypes.string, + width: PropTypes.number, + allowScaleWidth: PropTypes.bool, + nodes: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string, + name: PropTypes.string, + parent: PropTypes.string + })), + selectedNodeId: PropTypes.string, + onNodeClick: PropTypes.func, + onRenderedBeyondWidth: PropTypes.func + }; + + static defaultProps = { + width: 500, + allowScaleWidth : true, + name: 'default-name' + }; + + render() { + let {width, name, scrollable = false} = this.props; + return ( + <div + className={`tree-view ${name}-container ${scrollable ? 'scrollable' : ''}`}> + <svg width={width} className={name}></svg> + </div> + ); + } + + componentDidMount() { + this.renderTree(); + } + + // handleMouseMove(e) { + // if (!this.state.isDown) { + // return; + // } + // const container = select(`.tree-view.${this.props.name}-container`); + // let coordinates = this.getCoordinates(e); + // container.property('scrollLeft' , container.property('scrollLeft') + coordinates.x - this.state.startingCoordinates.x); + // container.property('scrollTop' , container.property('scrollTop') + coordinates.y - this.state.startingCoordinates.y); + // } + + // handleMouseDown(e) { + // let startingCoordinates = this.getCoordinates(e); + // this.setState({ + // startingCoordinates, + // isDown: true + // }); + // } + + // handleMouseUp() { + // this.setState({ + // startingCorrdinates: null, + // isDown: false + // }); + // } + + // getCoordinates(e) { + // var bounds = e.target.getBoundingClientRect(); + // var x = e.clientX - bounds.left; + // var y = e.clientY - bounds.top; + // return {x, y}; + // } + + componentDidUpdate(prevProps) { + if (this.props.nodes.length !== prevProps.nodes.length || + this.props.selectedNodeId !== prevProps.selectedNodeId) { + console.log('update'); + this.renderTree(); + } + } + + renderTree() { + let {width, nodes, name, allowScaleWidth, selectedNodeId, onRenderedBeyondWidth, toWiden} = this.props; + if (nodes.length > 0) { + + let horizontalSpaceBetweenLeaves = toWiden ? WIDE_HORIZONTAL_SPACES : NARROW_HORIZONTAL_SPACES; + const treeFn = tree().nodeSize([horizontalSpaceBetweenLeaves, verticalSpaceBetweenNodes]);//.size([width - 50, height - 50]) + let root = stratifyFn(nodes).sort((a, b) => a.data.name.localeCompare(b.data.name)); + let svgHeight = verticalSpaceBetweenNodes * root.height + nodeRadius * 6; + + treeFn(root); + + let nodesXValue = root.descendants().map(node => node.x); + let maxX = Math.max(...nodesXValue); + let minX = Math.min(...nodesXValue); + + let svgTempWidth = (maxX - minX) / 30 * (horizontalSpaceBetweenLeaves); + let svgWidth = svgTempWidth < width ? (width - 5) : svgTempWidth; + const svgEL = select(`svg.${name}`); + const container = select(`.tree-view.${name}-container`); + svgEL.html(''); + svgEL.attr('height', svgHeight); + let canvasWidth = width; + if (svgTempWidth > width) { + if (allowScaleWidth) { + canvasWidth = svgTempWidth; + } + // we seems to have a margin of 25px that we can still see with text + if (((svgTempWidth - 25) > width) && onRenderedBeyondWidth !== undefined) { + onRenderedBeyondWidth(); + } + }; + svgEL.attr('width', canvasWidth); + let rootGroup = svgEL.append('g').attr('transform', `translate(${svgWidth / 2 + nodeRadius},${nodeRadius * 4}) rotate(90)`); + + // handle link + rootGroup.selectAll('.link') + .data(root.descendants().slice(1)) + .enter().append('path') + .attr('class', 'link') + .attr('d', diagonal); + + let node = rootGroup.selectAll('.node') + .data(root.descendants()) + .enter().append('g') + .attr('class', node => `node ${node.children ? ' has-children' : ' leaf'} ${node.id === selectedNodeId ? 'selectedNode' : ''} ${this.props.onNodeClick ? 'clickable' : ''}`) + .attr('transform', node => 'translate(' + node.y + ',' + node.x + ')') + .on('click', node => this.onNodeClick(node)); + + node.append('circle').attr('r', nodeRadius).attr('class', 'outer-circle'); + node.append('circle').attr('r', nodeRadius - 3).attr('class', 'inner-circle'); + + node.append('text') + .attr('y', nodeRadius / 4 + 1) + .attr('x', - nodeRadius * 1.8) + .text(node => node.data.name) + .attr('transform', 'rotate(-90)'); + + let selectedNode = selectedNodeId ? root.descendants().find(node => node.id === selectedNodeId) : null; + if (selectedNode) { + + container.property('scrollLeft', (svgWidth / 4) + (svgWidth / 4 - 100) - (selectedNode.x / 30 * horizontalSpaceBetweenLeaves)); + container.property('scrollTop', (selectedNode.y / 100 * verticalSpaceBetweenNodes)); + + } else { + container.property('scrollLeft', (svgWidth / 4) + (svgWidth / 4 - 100)); + } + } + } + + onNodeClick(node) { + if (this.props.onNodeClick) { + this.props.onNodeClick(node.data); + } + } + +} + +export default Tree; |