diff options
Diffstat (limited to 'openecomp-ui/src/nfvo-components/tree')
-rw-r--r-- | openecomp-ui/src/nfvo-components/tree/Tree.jsx | 406 | ||||
-rw-r--r-- | openecomp-ui/src/nfvo-components/tree/Tree.stories.js | 242 |
2 files changed, 372 insertions, 276 deletions
diff --git a/openecomp-ui/src/nfvo-components/tree/Tree.jsx b/openecomp-ui/src/nfvo-components/tree/Tree.jsx index 682f3b6d50..39434fcdf1 100644 --- a/openecomp-ui/src/nfvo-components/tree/Tree.jsx +++ b/openecomp-ui/src/nfvo-components/tree/Tree.jsx @@ -1,16 +1,28 @@ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import {select} from 'd3-selection'; -import {tree, stratify} from 'd3-hierarchy'; - +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 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; @@ -18,164 +30,226 @@ const verticalSpaceBetweenNodes = 70; const NARROW_HORIZONTAL_SPACES = 47; const WIDE_HORIZONTAL_SPACES = 65; -const stratifyFn = stratify().id(d => d.id).parentId(d => d.parent); +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); - } - } - + // 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} /> + </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; diff --git a/openecomp-ui/src/nfvo-components/tree/Tree.stories.js b/openecomp-ui/src/nfvo-components/tree/Tree.stories.js index b29920b3ec..aca1a13402 100644 --- a/openecomp-ui/src/nfvo-components/tree/Tree.stories.js +++ b/openecomp-ui/src/nfvo-components/tree/Tree.stories.js @@ -1,119 +1,141 @@ import React from 'react'; -import {storiesOf} from '@kadira/storybook'; -import {withKnobs} from '@kadira/storybook-addon-knobs'; +import { storiesOf } from '@kadira/storybook'; +import { withKnobs } from '@kadira/storybook-addon-knobs'; import Tree from './Tree.jsx'; -import SVGIcon from 'sdc-ui/lib/react/SVGIcon.js'; const stories = storiesOf('Version Tree', module); stories.addDecorator(withKnobs); const response = { - listCount: 6, - results: [ - { - 'id': '123', - 'name': '1.0', - 'description': 'string', - 'baseId': '', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '1234', - 'name': '1.1', - 'description': 'string', - 'baseId': '123', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '12345', - 'name': '2.0', - 'description': 'string', - 'baseId': '123', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '123456', - 'name': '3.0', - 'description': 'string', - 'baseId': '12345', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '1234567', - 'name': '1.2', - 'description': 'string', - 'baseId': '1234', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '12345678', - 'name': '2.1', - 'description': 'string', - 'baseId': '12345', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '123456789', - 'name': '4.0', - 'description': 'string', - 'baseId': '123456', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - }, - { - 'id': '12345678910', - 'name': '3.1', - 'description': 'string', - 'baseId': '123456', - 'status': 'Draft', - 'creationTime': '2017-06-08T08:55:37.831Z', - 'modificationTime': '2017-06-08T08:55:37.831Z' - } - ] + listCount: 6, + results: [ + { + id: '123', + name: '1.0', + description: 'string', + baseId: '', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '1234', + name: '1.1', + description: 'string', + baseId: '123', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '12345', + name: '2.0', + description: 'string', + baseId: '123', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '123456', + name: '3.0', + description: 'string', + baseId: '12345', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '1234567', + name: '1.2', + description: 'string', + baseId: '1234', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '12345678', + name: '2.1', + description: 'string', + baseId: '12345', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '123456789', + name: '4.0', + description: 'string', + baseId: '123456', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + }, + { + id: '12345678910', + name: '3.1', + description: 'string', + baseId: '123456', + status: 'Draft', + creationTime: '2017-06-08T08:55:37.831Z', + modificationTime: '2017-06-08T08:55:37.831Z' + } + ] }; -const divStyle = { width: '200px', borderStyle: 'solid', borderColor: 'black', border: '1px solid black'}; -const tree = response.results.map(item => ({id: item.id, name: item.name, parent: item.baseId})); -const nodeClickHandler = function (node) { - window.alert(node.name); +const divStyle = { + width: '200px', + borderStyle: 'solid', + borderColor: 'black', + border: '1px solid black' }; -stories.add('Classic Version Tree', () => ( - <div> - <Tree nodes={tree} onNodeClick={nodeClickHandler} selectedNodeId={'1234'}/> - </div> -)).add('Single Version Tree', () => ( - <div> - <Tree nodes={[tree[0]]} onNodeClick={nodeClickHandler}/> - </div> -)).add('Single Path Version Tree', () => ( - <div> - <Tree nodes={[tree[0], tree[1]]} onNodeClick={nodeClickHandler}/> - </div> -)).add('Empty Tree', () => ( - <div> - <Tree nodes={[]}/> - </div> -)).add('Add Tree in Version Page Frame', () => ( - <div style={divStyle}> - Tree wider than frame<br/><br/><br/> - <Tree - name={'versions-tree'} - width={200} - nodes={tree} - onRenderedBeyondWidth={() => {console.log('rendered beyond width')}} - allowScaleWidth={false} - onNodeClick={nodeClickHandler}/> - </div> -)); +const tree = response.results.map(item => ({ + id: item.id, + name: item.name, + parent: item.baseId +})); +const nodeClickHandler = function(node) { + window.alert(node.name); +}; +stories + .add('Classic Version Tree', () => ( + <div> + <Tree + nodes={tree} + onNodeClick={nodeClickHandler} + selectedNodeId={'1234'} + /> + </div> + )) + .add('Single Version Tree', () => ( + <div> + <Tree nodes={[tree[0]]} onNodeClick={nodeClickHandler} /> + </div> + )) + .add('Single Path Version Tree', () => ( + <div> + <Tree nodes={[tree[0], tree[1]]} onNodeClick={nodeClickHandler} /> + </div> + )) + .add('Empty Tree', () => ( + <div> + <Tree nodes={[]} /> + </div> + )) + .add('Add Tree in Version Page Frame', () => ( + <div style={divStyle}> + Tree wider than frame<br /> + <br /> + <br /> + <Tree + name={'versions-tree'} + width={200} + nodes={tree} + onRenderedBeyondWidth={() => { + console.log('rendered beyond width'); + }} + allowScaleWidth={false} + onNodeClick={nodeClickHandler} + /> + </div> + )); |