X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=skyquake%2Fplugins%2Fcomposer%2Fsrc%2Fsrc%2Flibraries%2Fgraph%2Flayouts%2FRelationsAndNetworksLayout.js;fp=skyquake%2Fplugins%2Fcomposer%2Fsrc%2Fsrc%2Flibraries%2Fgraph%2Flayouts%2FRelationsAndNetworksLayout.js;h=f53b4dc7dfe10e08fb57a7ac2b0e1d5890efd78c;hb=e29efc315df33d546237e270470916e26df391d6;hp=0000000000000000000000000000000000000000;hpb=9c5e457509ba5a1822c316635c6308874e61b4b9;p=osm%2FUI.git diff --git a/skyquake/plugins/composer/src/src/libraries/graph/layouts/RelationsAndNetworksLayout.js b/skyquake/plugins/composer/src/src/libraries/graph/layouts/RelationsAndNetworksLayout.js new file mode 100644 index 000000000..f53b4dc7d --- /dev/null +++ b/skyquake/plugins/composer/src/src/libraries/graph/layouts/RelationsAndNetworksLayout.js @@ -0,0 +1,571 @@ +/** + * Created by onvelocity on 2/10/16. + */ +import alt from '../../../alt' +import _ from 'lodash' +import d3 from 'd3' +import math from '../math' +import ClassNames from 'classnames' +import ColorGroups from '../../ColorGroups' +import GraphVirtualLink from '../GraphVirtualLink' +import GraphNetworkService from '../GraphNetworkService' +import GraphForwardingGraph from '../GraphForwardingGraph' +import GraphConstituentVnfd from '../GraphConstituentVnfd' +import GraphVirtualNetworkFunction from '../GraphVirtualNetworkFunction' +import SelectionManager from '../../SelectionManager' +import GraphConnectionPointNumber from '../GraphConnectionPointNumber' +import CatalogItemsActions from '../../../actions/CatalogItemsActions' +import DescriptorModelFactory from '../../model/DescriptorModelFactory' +import DescriptorGraphSelection from '../DescriptorGraphSelection' +import GraphVirtualDeploymentUnit from '../GraphVirtualDeploymentUnit' +import GraphRecordServicePath from '../GraphRecordServicePath' +import GraphInternalVirtualLink from '../GraphInternalVirtualLink' +import TooltipManager from '../../TooltipManager' + +function onCutDelegateToRemove(container) { + function onCut(event) { + event.target.removeEventListener('cut', onCut); + if (container.remove()) { + CatalogItemsActions.catalogItemDescriptorChanged.defer(container.getRoot()); + } else { + event.preventDefault(); + } + } + this.addEventListener('cut', onCut); +} + +export default function RelationsAndNetworksLayout() { + + const graph = this; + const props = this.props; + const containerWidth = 250; + const containerHeight = 55; + const marginTop = 20; + const marginLeft = 10; + const containerList = []; + const leftOffset = containerWidth; + + const snapTo = (value) => { + return Math.max(props.snapTo * Math.round(value / props.snapTo), props.padding); + }; + + const line = d3.svg.line() + .x(d => { + return d.x; + }) + .y(d => { + return d.y; + }); + + function countAncestors(container = {}) { + let count = 0; + while (container.parent) { + count++; + container = container.parent; + } + return count; + } + + function renderRelationPath(src, dst) { + const path = line.interpolate('basis'); + const srcPoint = src.position.centerPoint(); + const dstPoint = dst.position.centerPoint(); + const angle = math.angleBetweenPositions(src.position, dst.position); + if (angle < 180) { + srcPoint.y = src.position.top; + dstPoint.y = dst.position.bottom; + } else { + srcPoint.y = src.position.bottom; + dstPoint.y = dst.position.top; + } + return path([srcPoint, dstPoint]); + } + + function renderConnectionPath(cpRef) { + const path = line.interpolate('basis'); + const srcPoint = cpRef.position.centerPoint(); + const dstPoint = cpRef.parent.position.centerPoint(); + const srcIsTopMounted = /top/.test(cpRef.location); + //const srcIsLeftMounted = /left/.test(cpRef.parent.location); + const offset = 15; + const srcSpline1 = { + x: srcPoint.x, + y: (srcIsTopMounted ? cpRef.position.top - offset : cpRef.position.bottom + offset) + }; + const srcSpline2 = { + x: srcPoint.x, + y: (srcIsTopMounted ? cpRef.position.top - offset : cpRef.position.bottom + offset) + }; + return path([srcPoint, srcSpline1, srcSpline2, dstPoint]); + } + + const containerLayoutInfo = { + nsd: { + list: [], + width: containerWidth, + height: containerHeight, + top() { + return 10; + }, + left(container, layouts) { + const positionIndex = layouts.vnffgd.list.length + this.list.length; + return (positionIndex * (this.width * 1.5)) + leftOffset / 2; + }, + renderRelationPath: renderRelationPath, + renderConnectionPath: renderConnectionPath + }, + vnffgd: { + list: [], + width: containerWidth, + height: containerHeight, + top(container, layouts) { + const positionIndex = layouts.nsd.list.length + this.list.length; + return (positionIndex * (this.height * 1.5)) + 120; + }, + left(container, layouts) { + return 10; + }, + renderRelationPath: renderRelationPath, + renderConnectionPath: renderConnectionPath + }, + vnfd: { + list: [], + width: containerWidth, + height: containerHeight, + top(container) { + return (countAncestors(container) * 100) + 10; + }, + left() { + const positionIndex = this.list.length; + return (positionIndex * (this.width * 1.5)) + leftOffset; + }, + renderRelationPath: renderRelationPath, + renderConnectionPath: renderConnectionPath + }, + 'constituent-vnfd': { + list: [], + width: containerWidth, + height: containerHeight, + top(container) { + return (countAncestors(container) * 100) + 10; + }, + left() { + const positionIndex = this.list.length; + return (positionIndex * (this.width * 1.5)) + leftOffset; + }, + renderRelationPath: renderRelationPath, + renderConnectionPath: renderConnectionPath + }, + pnfd: { + list: [], + width: containerWidth, + height: containerHeight, + top(container) { + return (countAncestors(container) * 100) + 10; + }, + left() { + const positionIndex = this.list.length; + return (positionIndex * (this.width * 1.5)) + leftOffset; + }, + renderRelationPath: renderRelationPath, + renderConnectionPath: renderConnectionPath + }, + vld: { + list: [], + width: containerWidth, + height: 38, + top(container) { + return (countAncestors(container) * 100) + 180; + }, + left() { + const positionIndex = this.list.length; + const marginOffsetFactor = 1.5; + const gutterOffsetFactor = 1.5; + return (positionIndex * (this.width * gutterOffsetFactor)) + ((this.width * marginOffsetFactor) / 2) + leftOffset; + }, + renderRelationPath: renderRelationPath, + renderConnectionPath: renderConnectionPath + }, + 'internal-vld': { + list: [], + width: containerWidth, + height: 38, + top(container, containerLayouts) { + return (countAncestors(container) * 100) + 100; + }, + left() { + const positionIndex = this.list.length; + const marginOffsetFactor = 1.5; + const gutterOffsetFactor = 1.5; + return (positionIndex * (this.width * gutterOffsetFactor)) + ((this.width * marginOffsetFactor) / 2) + leftOffset; + }, + renderRelationPath: renderRelationPath, + renderConnectionPath: renderConnectionPath + }, + vdu: { + list: [], + width: containerWidth, + height: containerHeight, + gutter: 30, + top(container) { + return (countAncestors(container) * 100) + 10; + }, + left(container) { + const positionIndex = this.list.length; + return (positionIndex * (this.width * 1.5)) + leftOffset; + }, + renderRelationPath: renderRelationPath, + renderConnectionPath: renderConnectionPath + } + }; + + function getConnectionPointEdges() { + + // 1. create a lookup map to find a connection-point by it's key + const connectionPointMap = {}; + containerList.filter(d => d.connectionPoint).reduce((result, container) => { + return container.connectionPoint.reduce((result, connectionPoint) => { + result[connectionPoint.key] = connectionPoint; + connectionPoint.uiState.hasConnection = false; + return result; + }, result); + }, connectionPointMap); + + // 2. determine position of the connection-point and connection-point-ref (they are the same) + const connectionPointRefList = []; + containerList.filter(container => container.connection).forEach(container => { + container.uiState.hasConnection = false; + container.connection.filter(d => d.key).forEach(cpRef => { + try { + const source = connectionPointMap[cpRef.key]; + const destination = container; + source.uiState.hasConnection = true; + destination.uiState.hasConnection = true; + const edgeStateMachine = math.upperLowerEdgeLocation; + // angle is used to determine location top, bottom, right, left + const angle = math.angleBetweenPositions(source.parent.position, destination.position); + // distance is used to determine order of the connection points + const distance = math.distanceBetweenPositions(source.parent.position, destination.position); + cpRef.location = source.location = edgeStateMachine(angle); + source.edgeAngle = angle; + if (destination.type === 'vdu') { + source.edgeLength = Math.max(source.edgeLength || 0, distance); + } + // warn assigning same instance (e.g. pass by reference) so that changes will reflect thru + cpRef.position = source.position; + connectionPointRefList.push(cpRef); + } catch(e) { + return; + } + }); + }); + + // 3. update the connection-point/-ref location based on the angle of the path + containerList.filter(d => d.connectionPoint).forEach(container => { + // group the connectors by their location and then update their position coordinates accordingly + const connectionPoints = container.connectionPoint.sort((a, b) => b.edgeLength - a.edgeLength); + const locationIndexCounters = {}; + connectionPoints.forEach(connectionPoint => { + // location index is used to calculate the actual position where the path will terminate on the container + const location = connectionPoint.location; + const locationIndex = locationIndexCounters[location] || (locationIndexCounters[location] = 0); + connectionPoint.positionIndex = locationIndex; + if (/left/.test(location)) { + connectionPoint.position.moveLeft(connectionPoint.parent.position.left + 5 + ((connectionPoint.width + 1) * locationIndex)); + } else { + connectionPoint.position.moveRight(connectionPoint.parent.position.right - 15 - ((connectionPoint.width + 1) * locationIndex)); + } + if (/top/.test(location)) { + connectionPoint.position.moveTop(connectionPoint.parent.position.top - connectionPoint.height); + } else { + connectionPoint.position.moveTop(connectionPoint.parent.position.bottom); + } + locationIndexCounters[location] = locationIndex + 1; + }); + }); + + return connectionPointRefList; + + } + + function drawConnectionPointsAndPaths(graph, connectionPointRefs) { + + const paths = graph.paths.selectAll('.connection').data(connectionPointRefs, DescriptorModelFactory.containerIdentity); + + paths.enter().append('path'); + + paths.attr({ + 'data-uid': d => { + return d.uid; + }, + 'class': d => { + return 'connection between-' + d.parent.type + '-and-' + d.type; + }, + 'stroke-width': 5, + stroke: ColorGroups.vld.primary, + fill: 'transparent', + d: edge => { + const layout = containerLayoutInfo[edge.parent.type]; + return layout.renderConnectionPath(edge, containerLayoutInfo); + } + }).on('cut', (container) => { + + let success = false; + + if (container && container.remove) { + success = container.remove(); + } + + if (success) { + CatalogItemsActions.catalogItemDescriptorChanged.defer(container.getRoot()); + } else { + d3.event.preventDefault(); + } + + d3.event.stopPropagation(); + + }); + + paths.exit().remove(); + + const symbolSize = props.connectionPointSize; + const connectionPointSymbol = d3.svg.symbol().type('square').size(symbolSize); + const internalConnectionPointSymbolBottom = d3.svg.symbol().type('triangle-down').size(symbolSize); + const internalConnectionPointSymbolTop = d3.svg.symbol().type('triangle-up').size(symbolSize); + + const connectors = containerList.filter(d => d.connectors).reduce((result, container) => { + return container.connectors.reduce((result, connector) => { + result.add(connector); + return result; + }, result); + }, new Set()); + + const points = graph.connectorsGroup.selectAll('.connector').data(Array.from(connectors), DescriptorModelFactory.containerIdentity); + + points.enter().append('path'); + + points.attr({ + 'data-uid': d => d.uid, + 'data-key': d => d.key, + 'data-cp-number': d => d.uiState.cpNumber, + 'class': d => { + return ClassNames('connector', d.type, d.parent.type, { + '-is-connected': d.uiState.hasConnection, + '-is-not-connected': !d.uiState.hasConnection + }); + }, + 'data-tip': d => { + const info = d.displayData; + return Object.keys(info).reduce((r, key) => { + if (info[key]) { + return r + `