2 * Created by onvelocity on 2/10/16.
4 import alt
from '../../../alt'
6 import math
from '../math'
7 import ClassNames
from 'classnames'
8 import ColorGroups
from '../../ColorGroups'
9 import GraphVirtualLink
from '../GraphVirtualLink'
10 import GraphNetworkService
from '../GraphNetworkService'
11 import GraphForwardingGraph
from '../GraphForwardingGraph'
12 import GraphConstituentVnfd
from '../GraphConstituentVnfd'
13 import GraphVirtualNetworkFunction
from '../GraphVirtualNetworkFunction'
14 import SelectionManager
from '../../SelectionManager'
15 import GraphConnectionPointNumber
from '../GraphConnectionPointNumber'
16 import CatalogItemsActions
from '../../../actions/CatalogItemsActions'
17 import DescriptorModelFactory
from '../../model/DescriptorModelFactory'
18 import DescriptorGraphSelection
from '../DescriptorGraphSelection'
19 import GraphVirtualDeploymentUnit
from '../GraphVirtualDeploymentUnit'
20 import GraphRecordServicePath
from '../GraphRecordServicePath'
21 import GraphInternalVirtualLink
from '../GraphInternalVirtualLink'
22 import TooltipManager
from '../../TooltipManager'
24 function onCutDelegateToRemove(container
) {
25 function onCut(event
) {
26 event
.target
.removeEventListener('cut', onCut
);
27 if (container
.remove()) {
28 CatalogItemsActions
.catalogItemDescriptorChanged
.defer(container
.getRoot());
30 event
.preventDefault();
33 this.addEventListener('cut', onCut
);
36 export default function RelationsAndNetworksLayout() {
39 const props
= this.props
;
40 const containerWidth
= 250;
41 const containerHeight
= 55;
43 const marginLeft
= 10;
44 const containerList
= [];
45 const leftOffset
= containerWidth
;
47 const snapTo
= (value
) => {
48 return Math
.max(props
.snapTo
* Math
.round(value
/ props
.snapTo
), props
.padding
);
51 const line
= d3
.svg
.line()
59 function countAncestors(container
= {}) {
61 while (container
.parent
) {
63 container
= container
.parent
;
68 function renderRelationPath(src
, dst
) {
69 const path
= line
.interpolate('basis');
70 const srcPoint
= src
.position
.centerPoint();
71 const dstPoint
= dst
.position
.centerPoint();
72 const angle
= math
.angleBetweenPositions(src
.position
, dst
.position
);
74 srcPoint
.y
= src
.position
.top
;
75 dstPoint
.y
= dst
.position
.bottom
;
77 srcPoint
.y
= src
.position
.bottom
;
78 dstPoint
.y
= dst
.position
.top
;
80 return path([srcPoint
, dstPoint
]);
83 function renderConnectionPath(cpRef
) {
84 const path
= line
.interpolate('basis');
85 const srcPoint
= cpRef
.position
.centerPoint();
86 const dstPoint
= cpRef
.parent
.position
.centerPoint();
87 const srcIsTopMounted
= /top/.test(cpRef
.location
);
88 //const srcIsLeftMounted = /left/.test(cpRef.parent.location);
92 y
: (srcIsTopMounted
? cpRef
.position
.top
- offset
: cpRef
.position
.bottom
+ offset
)
96 y
: (srcIsTopMounted
? cpRef
.position
.top
- offset
: cpRef
.position
.bottom
+ offset
)
98 return path([srcPoint
, srcSpline1
, srcSpline2
, dstPoint
]);
101 const containerLayoutInfo
= {
104 width
: containerWidth
,
105 height
: containerHeight
,
109 left(container
, layouts
) {
110 const positionIndex
= layouts
.vnffgd
.list
.length
+ this.list
.length
;
111 return (positionIndex
* (this.width
* 1.5)) + leftOffset
/ 2;
113 renderRelationPath
: renderRelationPath
,
114 renderConnectionPath
: renderConnectionPath
118 width
: containerWidth
,
119 height
: containerHeight
,
120 top(container
, layouts
) {
121 const positionIndex
= layouts
.nsd
.list
.length
+ this.list
.length
;
122 return (positionIndex
* (this.height
* 1.5)) + 120;
124 left(container
, layouts
) {
127 renderRelationPath
: renderRelationPath
,
128 renderConnectionPath
: renderConnectionPath
132 width
: containerWidth
,
133 height
: containerHeight
,
135 return (countAncestors(container
) * 100) + 10;
138 const positionIndex
= this.list
.length
;
139 return (positionIndex
* (this.width
* 1.5)) + leftOffset
;
141 renderRelationPath
: renderRelationPath
,
142 renderConnectionPath
: renderConnectionPath
144 'constituent-vnfd': {
146 width
: containerWidth
,
147 height
: containerHeight
,
149 return (countAncestors(container
) * 100) + 10;
152 const positionIndex
= this.list
.length
;
153 return (positionIndex
* (this.width
* 1.5)) + leftOffset
;
155 renderRelationPath
: renderRelationPath
,
156 renderConnectionPath
: renderConnectionPath
160 width
: containerWidth
,
161 height
: containerHeight
,
163 return (countAncestors(container
) * 100) + 10;
166 const positionIndex
= this.list
.length
;
167 return (positionIndex
* (this.width
* 1.5)) + leftOffset
;
169 renderRelationPath
: renderRelationPath
,
170 renderConnectionPath
: renderConnectionPath
174 width
: containerWidth
,
177 return (countAncestors(container
) * 100) + 180;
180 const positionIndex
= this.list
.length
;
181 const marginOffsetFactor
= 1.5;
182 const gutterOffsetFactor
= 1.5;
183 return (positionIndex
* (this.width
* gutterOffsetFactor
)) + ((this.width
* marginOffsetFactor
) / 2) + leftOffset
;
185 renderRelationPath
: renderRelationPath
,
186 renderConnectionPath
: renderConnectionPath
190 width
: containerWidth
,
192 top(container
, containerLayouts
) {
193 return (countAncestors(container
) * 100) + 100;
196 const positionIndex
= this.list
.length
;
197 const marginOffsetFactor
= 1.5;
198 const gutterOffsetFactor
= 1.5;
199 return (positionIndex
* (this.width
* gutterOffsetFactor
)) + ((this.width
* marginOffsetFactor
) / 2) + leftOffset
;
201 renderRelationPath
: renderRelationPath
,
202 renderConnectionPath
: renderConnectionPath
206 width
: containerWidth
,
207 height
: containerHeight
,
210 return (countAncestors(container
) * 100) + 10;
213 const positionIndex
= this.list
.length
;
214 return (positionIndex
* (this.width
* 1.5)) + leftOffset
;
216 renderRelationPath
: renderRelationPath
,
217 renderConnectionPath
: renderConnectionPath
221 function getConnectionPointEdges() {
223 // 1. create a lookup map to find a connection-point by it's key
224 const connectionPointMap
= {};
225 containerList
.filter(d
=> d
.connectionPoint
).reduce((result
, container
) => {
226 return container
.connectionPoint
.reduce((result
, connectionPoint
) => {
227 result
[connectionPoint
.key
] = connectionPoint
;
228 connectionPoint
.uiState
.hasConnection
= false;
231 }, connectionPointMap
);
233 // 2. determine position of the connection-point and connection-point-ref (they are the same)
234 const connectionPointRefList
= [];
235 containerList
.filter(container
=> container
.connection
).forEach(container
=> {
236 container
.uiState
.hasConnection
= false;
237 container
.connection
.filter(d
=> d
.key
).forEach(cpRef
=> {
239 const source
= connectionPointMap
[cpRef
.key
];
240 const destination
= container
;
241 source
.uiState
.hasConnection
= true;
242 destination
.uiState
.hasConnection
= true;
243 const edgeStateMachine
= math
.upperLowerEdgeLocation
;
244 // angle is used to determine location top, bottom, right, left
245 const angle
= math
.angleBetweenPositions(source
.parent
.position
, destination
.position
);
246 // distance is used to determine order of the connection points
247 const distance
= math
.distanceBetweenPositions(source
.parent
.position
, destination
.position
);
248 cpRef
.location
= source
.location
= edgeStateMachine(angle
);
249 source
.edgeAngle
= angle
;
250 if (destination
.type
=== 'vdu') {
251 source
.edgeLength
= Math
.max(source
.edgeLength
|| 0, distance
);
253 // warn assigning same instance (e.g. pass by reference) so that changes will reflect thru
254 cpRef
.position
= source
.position
;
255 connectionPointRefList
.push(cpRef
);
262 // 3. update the connection-point/-ref location based on the angle of the path
263 containerList
.filter(d
=> d
.connectionPoint
).forEach(container
=> {
264 // group the connectors by their location and then update their position coordinates accordingly
265 const connectionPoints
= container
.connectionPoint
.sort((a
, b
) => b
.edgeLength
- a
.edgeLength
);
266 const locationIndexCounters
= {};
267 connectionPoints
.forEach(connectionPoint
=> {
268 // location index is used to calculate the actual position where the path will terminate on the container
269 const location
= connectionPoint
.location
;
270 const locationIndex
= locationIndexCounters
[location
] || (locationIndexCounters
[location
] = 0);
271 connectionPoint
.positionIndex
= locationIndex
;
272 if (/left/.test(location
)) {
273 connectionPoint
.position
.moveLeft(connectionPoint
.parent
.position
.left
+ 5 + ((connectionPoint
.width
+ 1) * locationIndex
));
275 connectionPoint
.position
.moveRight(connectionPoint
.parent
.position
.right
- 15 - ((connectionPoint
.width
+ 1) * locationIndex
));
277 if (/top/.test(location
)) {
278 connectionPoint
.position
.moveTop(connectionPoint
.parent
.position
.top
- connectionPoint
.height
);
280 connectionPoint
.position
.moveTop(connectionPoint
.parent
.position
.bottom
);
282 locationIndexCounters
[location
] = locationIndex
+ 1;
286 return connectionPointRefList
;
290 function drawConnectionPointsAndPaths(graph
, connectionPointRefs
) {
292 const paths
= graph
.paths
.selectAll('.connection').data(connectionPointRefs
, DescriptorModelFactory
.containerIdentity
);
294 paths
.enter().append('path');
301 return 'connection between-' + d
.parent
.type
+ '-and-' + d
.type
;
304 stroke
: ColorGroups
.vld
.primary
,
307 const layout
= containerLayoutInfo
[edge
.parent
.type
];
308 return layout
.renderConnectionPath(edge
, containerLayoutInfo
);
310 }).on('cut', (container
) => {
314 if (container
&& container
.remove
) {
315 success
= container
.remove();
319 CatalogItemsActions
.catalogItemDescriptorChanged
.defer(container
.getRoot());
321 d3
.event
.preventDefault();
324 d3
.event
.stopPropagation();
328 paths
.exit().remove();
330 const symbolSize
= props
.connectionPointSize
;
331 const connectionPointSymbol
= d3
.svg
.symbol().type('square').size(symbolSize
);
332 const internalConnectionPointSymbolBottom
= d3
.svg
.symbol().type('triangle-down').size(symbolSize
);
333 const internalConnectionPointSymbolTop
= d3
.svg
.symbol().type('triangle-up').size(symbolSize
);
335 const connectors
= containerList
.filter(d
=> d
.connectors
).reduce((result
, container
) => {
336 return container
.connectors
.reduce((result
, connector
) => {
337 result
.add(connector
);
342 const points
= graph
.connectorsGroup
.selectAll('.connector').data(Array
.from(connectors
), DescriptorModelFactory
.containerIdentity
);
344 points
.enter().append('path');
347 'data-uid': d
=> d
.uid
,
348 'data-key': d
=> d
.key
,
349 'data-cp-number': d
=> d
.uiState
.cpNumber
,
351 return ClassNames('connector', d
.type
, d
.parent
.type
, {
352 '-is-connected': d
.uiState
.hasConnection
,
353 '-is-not-connected': !d
.uiState
.hasConnection
357 const info
= d
.displayData
;
358 return Object
.keys(info
).reduce((r
, key
) => {
360 return r
+ `<div class="${key}"><i>${key}</i><val>${info[key]}</val></div>`;
365 'data-tip-offset': d
=> {
366 if (d
.type
=== 'internal-connection-point') {
367 return '{"top": -7, "left": -9}';
369 return '{"top": -5, "left": -5}';
372 const point
= d
.position
.centerPoint();
373 return 'translate(' + (point
.x
) + ', ' + (point
.y
) + ')';
376 if (d
.type
=== 'connection-point') {
377 return connectionPointSymbol();
379 if (/top/.test(d
.location
)) {
380 return internalConnectionPointSymbolTop();
382 return internalConnectionPointSymbolBottom();
384 }).on('cut', (container
) => {
388 if (container
&& container
.remove
) {
389 success
= container
.remove();
393 CatalogItemsActions
.catalogItemDescriptorChanged
.defer(container
.getRoot());
395 d3
.event
.preventDefault();
398 d3
.event
.stopPropagation();
400 }).on('mouseenter', () => {
401 TooltipManager
.showTooltip(d3
.event
.target
);
404 points
.exit().remove();
406 const test
= new GraphConnectionPointNumber(graph
);
407 test
.addContainers(Array
.from(connectors
));
411 function drawRelationPointsAndPaths(graph
, relationEdges
) {
413 const paths
= graph
.paths
.selectAll('.relation').data(relationEdges
, DescriptorModelFactory
.containerIdentity
);
415 paths
.enter().append('path')
418 return ClassNames('relation', d
.type
, {
419 '-is-selected': d
.uiState
&& SelectionManager
.isSelected(d
) /*d.uiState && d.uiState.selected*/
424 'marker-start': 'url(#relation-marker-end)',
425 'marker-end': 'url(#relation-marker-end)'
431 const dst
= d
.parent
;
432 const layout
= containerLayoutInfo
[src
.type
];
433 return layout
.renderRelationPath(src
, dst
, containerLayoutInfo
);
437 paths
.exit().remove();
441 function updateContainerPosition(graph
, container
, layout
) {
442 // use the provided layout to generate the position coordinates
443 const position
= container
.position
;
444 position
.top
= layout
.top(container
, containerLayoutInfo
) + marginTop
;
445 position
.left
= layout
.left(container
, containerLayoutInfo
) + marginLeft
;
446 position
.width
= layout
.width
;
447 position
.height
= layout
.height
;
448 // cache the default layout position which may be needed by the layout
449 // of children elements that have not been positioned by the user
450 container
.uiState
.defaultLayoutPosition
= position
.value();
451 const savedContainerPosition
= graph
.lookupSavedContainerPosition(container
);
452 if (savedContainerPosition
) {
453 // set the container position with the saved position coordinates
454 container
.setPosition(savedContainerPosition
);
456 if (container
.uiState
.dropCoordinates
) {
457 const rect
= graph
.svg
.node().getBoundingClientRect();
458 const top
= container
.uiState
.dropCoordinates
.clientY
- (position
.height
/ 2) - rect
.top
;
459 const left
= container
.uiState
.dropCoordinates
.clientX
- (position
.width
/ 2) - rect
.left
;
460 container
.position
.move(Math
.max(props
.padding
, left
), Math
.max(props
.padding
, top
));
461 graph
.saveContainerPosition(container
);
462 delete container
.uiState
.dropCoordinates
;
464 graph
.saveContainerPosition(container
);
470 addContainers(containers
) {
474 //containers = containers.filter(d => containerLayouts[d.type]);
481 containers
.forEach(container
=> {
482 containerList
.push(container
);
485 containers
.forEach(container
=> {
486 const layout
= containerLayoutInfo
[container
.type
];
489 //throw new ReferenceError('unknown container type: ' + container.type);
491 updateContainerPosition(graph
, container
, layout
);
492 layout
.list
.push(container
);
493 graphSize
.width
= Math
.max(graphSize
.width
, container
.position
.right
, props
.width
);
494 graphSize
.height
= Math
.max(graphSize
.height
, container
.position
.bottom
, props
.height
);
498 width
: graphSize
.width
+ props
.width
,
499 height
: graphSize
.height
+ props
.height
502 const uiTransientState
= {
504 dragStartInfo
: [0, 0]
507 // todo extract drag behavior into class DescriptorGraphDrag
509 const drag
= this.drag
= d3
.behavior
.drag()
510 .origin(function (d
) {
513 .on('drag.graph', function (d
) {
514 uiTransientState
.isDragging
= true;
515 const mouse
= d3
.mouse(graph
.g
.node());
516 const offset
= uiTransientState
.dragStartInfo
;
517 const newTopEdge
= snapTo(mouse
[1] - offset
[1]);
518 const newLeftEdge
= snapTo(mouse
[0] - offset
[0]);
519 if (d
.position
.left
=== newLeftEdge
&& d
.position
.top
=== newTopEdge
) {
520 // do not redraw since we are not moving the container
523 d
.position
.move(newLeftEdge
, newTopEdge
);
524 graph
.saveContainerPosition(d
);
525 const connectionPointRefs
= getConnectionPointEdges();
526 d3
.select(this).attr({
528 const x
= d
.position
.left
;
529 const y
= d
.position
.top
;
530 return 'translate(' + x
+ ', ' + y
+ ')';
533 requestAnimationFrame(() => {
534 drawConnectionPointsAndPaths(graph
, connectionPointRefs
);
535 layout
.renderers
.forEach(d
=> d
.render());
537 }).on('dragend.graph', () => {
538 // d3 fires a drag-end event on mouse up, even if just clicking
539 if (uiTransientState
.isDragging
) {
540 uiTransientState
.isDragging
= false;
541 CatalogItemsActions
.catalogItemMetaDataChanged(graph
.containers
[0].model
);
542 d3
.select(this).on('.graph', null);
544 }).on('dragstart.graph', function (d
) {
545 // the x, y offset of the mouse from the container's left, top
546 uiTransientState
.dragStartInfo
= d3
.mouse(this);
549 this.renderers
= [GraphVirtualLink
, GraphNetworkService
, GraphForwardingGraph
, GraphVirtualNetworkFunction
, GraphConstituentVnfd
, GraphVirtualDeploymentUnit
, GraphRecordServicePath
, GraphInternalVirtualLink
].map(layout
=> {
550 const container
= new layout(graph
, props
);
551 const layoutInfo
= containerLayoutInfo
[container
.classType
&& container
.classType
.type
];
553 container
.props
.descriptorWidth
= layoutInfo
.width
;
554 container
.props
.descriptorHeight
= layoutInfo
.height
;
556 if (!props
.readOnly
) {
557 container
.dragHandler
= drag
;
559 container
.addContainers(containerList
);
565 render(graph
, updateCallback
= () => {}) {
566 const connectionPointRefs
= getConnectionPointEdges();
567 requestAnimationFrame(() => {
568 drawConnectionPointsAndPaths(graph
, connectionPointRefs
);
569 this.renderers
.forEach(d
=> d
.render());