2 * Created by onvelocity on 2/10/16.
4 import alt
from '../../../alt'
7 import math
from '../math'
8 import ClassNames
from 'classnames'
9 import ColorGroups
from '../../ColorGroups'
10 import GraphVirtualLink
from '../GraphVirtualLink'
11 import GraphNetworkService
from '../GraphNetworkService'
12 import GraphForwardingGraph
from '../GraphForwardingGraph'
13 import GraphConstituentVnfd
from '../GraphConstituentVnfd'
14 import GraphVirtualNetworkFunction
from '../GraphVirtualNetworkFunction'
15 import SelectionManager
from '../../SelectionManager'
16 import GraphConnectionPointNumber
from '../GraphConnectionPointNumber'
17 import CatalogItemsActions
from '../../../actions/CatalogItemsActions'
18 import DescriptorModelFactory
from '../../model/DescriptorModelFactory'
19 import DescriptorGraphSelection
from '../DescriptorGraphSelection'
20 import GraphVirtualDeploymentUnit
from '../GraphVirtualDeploymentUnit'
21 import GraphRecordServicePath
from '../GraphRecordServicePath'
22 import GraphInternalVirtualLink
from '../GraphInternalVirtualLink'
23 import TooltipManager
from '../../TooltipManager'
25 function onCutDelegateToRemove(container
) {
26 function onCut(event
) {
27 event
.target
.removeEventListener('cut', onCut
);
28 if (container
.remove()) {
29 CatalogItemsActions
.catalogItemDescriptorChanged
.defer(container
.getRoot());
31 event
.preventDefault();
34 this.addEventListener('cut', onCut
);
37 export default function RelationsAndNetworksLayout() {
40 const props
= this.props
;
41 const containerWidth
= 250;
42 const containerHeight
= 55;
44 const marginLeft
= 10;
45 const containerList
= [];
46 const leftOffset
= containerWidth
;
48 const snapTo
= (value
) => {
49 return Math
.max(props
.snapTo
* Math
.round(value
/ props
.snapTo
), props
.padding
);
52 const line
= d3
.svg
.line()
60 function countAncestors(container
= {}) {
62 while (container
.parent
) {
64 container
= container
.parent
;
69 function renderRelationPath(src
, dst
) {
70 const path
= line
.interpolate('basis');
71 const srcPoint
= src
.position
.centerPoint();
72 const dstPoint
= dst
.position
.centerPoint();
73 const angle
= math
.angleBetweenPositions(src
.position
, dst
.position
);
75 srcPoint
.y
= src
.position
.top
;
76 dstPoint
.y
= dst
.position
.bottom
;
78 srcPoint
.y
= src
.position
.bottom
;
79 dstPoint
.y
= dst
.position
.top
;
81 return path([srcPoint
, dstPoint
]);
84 function renderConnectionPath(cpRef
) {
85 const path
= line
.interpolate('basis');
86 const srcPoint
= cpRef
.position
.centerPoint();
87 const dstPoint
= cpRef
.parent
.position
.centerPoint();
88 const srcIsTopMounted
= /top/.test(cpRef
.location
);
89 //const srcIsLeftMounted = /left/.test(cpRef.parent.location);
93 y
: (srcIsTopMounted
? cpRef
.position
.top
- offset
: cpRef
.position
.bottom
+ offset
)
97 y
: (srcIsTopMounted
? cpRef
.position
.top
- offset
: cpRef
.position
.bottom
+ offset
)
99 return path([srcPoint
, srcSpline1
, srcSpline2
, dstPoint
]);
102 const containerLayoutInfo
= {
105 width
: containerWidth
,
106 height
: containerHeight
,
110 left(container
, layouts
) {
111 const positionIndex
= layouts
.vnffgd
.list
.length
+ this.list
.length
;
112 return (positionIndex
* (this.width
* 1.5)) + leftOffset
/ 2;
114 renderRelationPath
: renderRelationPath
,
115 renderConnectionPath
: renderConnectionPath
119 width
: containerWidth
,
120 height
: containerHeight
,
121 top(container
, layouts
) {
122 const positionIndex
= layouts
.nsd
.list
.length
+ this.list
.length
;
123 return (positionIndex
* (this.height
* 1.5)) + 120;
125 left(container
, layouts
) {
128 renderRelationPath
: renderRelationPath
,
129 renderConnectionPath
: renderConnectionPath
133 width
: containerWidth
,
134 height
: containerHeight
,
136 return (countAncestors(container
) * 100) + 10;
139 const positionIndex
= this.list
.length
;
140 return (positionIndex
* (this.width
* 1.5)) + leftOffset
;
142 renderRelationPath
: renderRelationPath
,
143 renderConnectionPath
: renderConnectionPath
145 'constituent-vnfd': {
147 width
: containerWidth
,
148 height
: containerHeight
,
150 return (countAncestors(container
) * 100) + 10;
153 const positionIndex
= this.list
.length
;
154 return (positionIndex
* (this.width
* 1.5)) + leftOffset
;
156 renderRelationPath
: renderRelationPath
,
157 renderConnectionPath
: renderConnectionPath
161 width
: containerWidth
,
162 height
: containerHeight
,
164 return (countAncestors(container
) * 100) + 10;
167 const positionIndex
= this.list
.length
;
168 return (positionIndex
* (this.width
* 1.5)) + leftOffset
;
170 renderRelationPath
: renderRelationPath
,
171 renderConnectionPath
: renderConnectionPath
175 width
: containerWidth
,
178 return (countAncestors(container
) * 100) + 180;
181 const positionIndex
= this.list
.length
;
182 const marginOffsetFactor
= 1.5;
183 const gutterOffsetFactor
= 1.5;
184 return (positionIndex
* (this.width
* gutterOffsetFactor
)) + ((this.width
* marginOffsetFactor
) / 2) + leftOffset
;
186 renderRelationPath
: renderRelationPath
,
187 renderConnectionPath
: renderConnectionPath
191 width
: containerWidth
,
193 top(container
, containerLayouts
) {
194 return (countAncestors(container
) * 100) + 100;
197 const positionIndex
= this.list
.length
;
198 const marginOffsetFactor
= 1.5;
199 const gutterOffsetFactor
= 1.5;
200 return (positionIndex
* (this.width
* gutterOffsetFactor
)) + ((this.width
* marginOffsetFactor
) / 2) + leftOffset
;
202 renderRelationPath
: renderRelationPath
,
203 renderConnectionPath
: renderConnectionPath
207 width
: containerWidth
,
208 height
: containerHeight
,
211 return (countAncestors(container
) * 100) + 10;
214 const positionIndex
= this.list
.length
;
215 return (positionIndex
* (this.width
* 1.5)) + leftOffset
;
217 renderRelationPath
: renderRelationPath
,
218 renderConnectionPath
: renderConnectionPath
222 function getConnectionPointEdges() {
224 // 1. create a lookup map to find a connection-point by it's key
225 const connectionPointMap
= {};
226 containerList
.filter(d
=> d
.connectionPoint
).reduce((result
, container
) => {
227 return container
.connectionPoint
.reduce((result
, connectionPoint
) => {
228 result
[connectionPoint
.key
] = connectionPoint
;
229 connectionPoint
.uiState
.hasConnection
= false;
232 }, connectionPointMap
);
234 // 2. determine position of the connection-point and connection-point-ref (they are the same)
235 const connectionPointRefList
= [];
236 containerList
.filter(container
=> container
.connection
).forEach(container
=> {
237 container
.uiState
.hasConnection
= false;
238 container
.connection
.filter(d
=> d
.key
).forEach(cpRef
=> {
240 const source
= connectionPointMap
[cpRef
.key
];
241 const destination
= container
;
242 source
.uiState
.hasConnection
= true;
243 destination
.uiState
.hasConnection
= true;
244 const edgeStateMachine
= math
.upperLowerEdgeLocation
;
245 // angle is used to determine location top, bottom, right, left
246 const angle
= math
.angleBetweenPositions(source
.parent
.position
, destination
.position
);
247 // distance is used to determine order of the connection points
248 const distance
= math
.distanceBetweenPositions(source
.parent
.position
, destination
.position
);
249 cpRef
.location
= source
.location
= edgeStateMachine(angle
);
250 source
.edgeAngle
= angle
;
251 if (destination
.type
=== 'vdu') {
252 source
.edgeLength
= Math
.max(source
.edgeLength
|| 0, distance
);
254 // warn assigning same instance (e.g. pass by reference) so that changes will reflect thru
255 cpRef
.position
= source
.position
;
256 connectionPointRefList
.push(cpRef
);
263 // 3. update the connection-point/-ref location based on the angle of the path
264 containerList
.filter(d
=> d
.connectionPoint
).forEach(container
=> {
265 // group the connectors by their location and then update their position coordinates accordingly
266 const connectionPoints
= container
.connectionPoint
.sort((a
, b
) => b
.edgeLength
- a
.edgeLength
);
267 const locationIndexCounters
= {};
268 connectionPoints
.forEach(connectionPoint
=> {
269 // location index is used to calculate the actual position where the path will terminate on the container
270 const location
= connectionPoint
.location
;
271 const locationIndex
= locationIndexCounters
[location
] || (locationIndexCounters
[location
] = 0);
272 connectionPoint
.positionIndex
= locationIndex
;
273 if (/left/.test(location
)) {
274 connectionPoint
.position
.moveLeft(connectionPoint
.parent
.position
.left
+ 5 + ((connectionPoint
.width
+ 1) * locationIndex
));
276 connectionPoint
.position
.moveRight(connectionPoint
.parent
.position
.right
- 15 - ((connectionPoint
.width
+ 1) * locationIndex
));
278 if (/top/.test(location
)) {
279 connectionPoint
.position
.moveTop(connectionPoint
.parent
.position
.top
- connectionPoint
.height
);
281 connectionPoint
.position
.moveTop(connectionPoint
.parent
.position
.bottom
);
283 locationIndexCounters
[location
] = locationIndex
+ 1;
287 return connectionPointRefList
;
291 function drawConnectionPointsAndPaths(graph
, connectionPointRefs
) {
293 const paths
= graph
.paths
.selectAll('.connection').data(connectionPointRefs
, DescriptorModelFactory
.containerIdentity
);
295 paths
.enter().append('path');
302 return 'connection between-' + d
.parent
.type
+ '-and-' + d
.type
;
305 stroke
: ColorGroups
.vld
.primary
,
308 const layout
= containerLayoutInfo
[edge
.parent
.type
];
309 return layout
.renderConnectionPath(edge
, containerLayoutInfo
);
311 }).on('cut', (container
) => {
315 if (container
&& container
.remove
) {
316 success
= container
.remove();
320 CatalogItemsActions
.catalogItemDescriptorChanged
.defer(container
.getRoot());
322 d3
.event
.preventDefault();
325 d3
.event
.stopPropagation();
329 paths
.exit().remove();
331 const symbolSize
= props
.connectionPointSize
;
332 const connectionPointSymbol
= d3
.svg
.symbol().type('square').size(symbolSize
);
333 const internalConnectionPointSymbolBottom
= d3
.svg
.symbol().type('triangle-down').size(symbolSize
);
334 const internalConnectionPointSymbolTop
= d3
.svg
.symbol().type('triangle-up').size(symbolSize
);
336 const connectors
= containerList
.filter(d
=> d
.connectors
).reduce((result
, container
) => {
337 return container
.connectors
.reduce((result
, connector
) => {
338 result
.add(connector
);
343 const points
= graph
.connectorsGroup
.selectAll('.connector').data(Array
.from(connectors
), DescriptorModelFactory
.containerIdentity
);
345 points
.enter().append('path');
348 'data-uid': d
=> d
.uid
,
349 'data-key': d
=> d
.key
,
350 'data-cp-number': d
=> d
.uiState
.cpNumber
,
352 return ClassNames('connector', d
.type
, d
.parent
.type
, {
353 '-is-connected': d
.uiState
.hasConnection
,
354 '-is-not-connected': !d
.uiState
.hasConnection
358 const info
= d
.displayData
;
359 return Object
.keys(info
).reduce((r
, key
) => {
361 return r
+ `<div class="${key}"><i>${key}</i><val>${info[key]}</val></div>`;
366 'data-tip-offset': d
=> {
367 if (d
.type
=== 'internal-connection-point') {
368 return '{"top": -7, "left": -9}';
370 return '{"top": -5, "left": -5}';
373 const point
= d
.position
.centerPoint();
374 return 'translate(' + (point
.x
) + ', ' + (point
.y
) + ')';
377 if (d
.type
=== 'connection-point') {
378 return connectionPointSymbol();
380 if (/top/.test(d
.location
)) {
381 return internalConnectionPointSymbolTop();
383 return internalConnectionPointSymbolBottom();
385 }).on('cut', (container
) => {
389 if (container
&& container
.remove
) {
390 success
= container
.remove();
394 CatalogItemsActions
.catalogItemDescriptorChanged
.defer(container
.getRoot());
396 d3
.event
.preventDefault();
399 d3
.event
.stopPropagation();
401 }).on('mouseenter', () => {
402 TooltipManager
.showTooltip(d3
.event
.target
);
405 points
.exit().remove();
407 const test
= new GraphConnectionPointNumber(graph
);
408 test
.addContainers(Array
.from(connectors
));
412 function drawRelationPointsAndPaths (graph
, relationEdges
) {
414 const paths
= graph
.paths
.selectAll('.relation').data(relationEdges
, DescriptorModelFactory
.containerIdentity
);
416 paths
.enter().append('path')
419 return ClassNames('relation', d
.type
, {'-is-selected': d
.uiState
&& SelectionManager
.isSelected(d
) /*d.uiState && d.uiState.selected*/});
423 'marker-start': 'url(#relation-marker-end)',
424 'marker-end': 'url(#relation-marker-end)'
430 const dst
= d
.parent
;
431 const layout
= containerLayoutInfo
[src
.type
];
432 return layout
.renderRelationPath(src
, dst
, containerLayoutInfo
);
436 paths
.exit().remove();
440 function updateContainerPosition(graph
, container
, layout
) {
441 // use the provided layout to generate the position coordinates
442 const position
= container
.position
;
443 position
.top
= layout
.top(container
, containerLayoutInfo
) + marginTop
;
444 position
.left
= layout
.left(container
, containerLayoutInfo
) + marginLeft
;
445 position
.width
= layout
.width
;
446 position
.height
= layout
.height
;
447 // cache the default layout position which may be needed by the layout
448 // of children elements that have not been positioned by the user
449 container
.uiState
.defaultLayoutPosition
= position
.value();
450 const savedContainerPosition
= graph
.lookupSavedContainerPosition(container
);
451 if (savedContainerPosition
) {
452 // set the container position with the saved position coordinates
453 container
.setPosition(savedContainerPosition
);
455 if (container
.uiState
.dropCoordinates
) {
456 const rect
= graph
.svg
.node().getBoundingClientRect();
457 const top
= container
.uiState
.dropCoordinates
.clientY
- (position
.height
/ 2) - rect
.top
;
458 const left
= container
.uiState
.dropCoordinates
.clientX
- (position
.width
/ 2) - rect
.left
;
459 container
.position
.move(Math
.max(props
.padding
, left
), Math
.max(props
.padding
, top
));
460 graph
.saveContainerPosition(container
);
461 delete container
.uiState
.dropCoordinates
;
463 graph
.saveContainerPosition(container
);
469 addContainers(containers
) {
473 //containers = containers.filter(d => containerLayouts[d.type]);
480 containers
.forEach(container
=> {
481 containerList
.push(container
);
484 containers
.forEach(container
=> {
485 const layout
= containerLayoutInfo
[container
.type
];
488 //throw new ReferenceError('unknown container type: ' + container.type);
490 updateContainerPosition(graph
, container
, layout
);
491 layout
.list
.push(container
);
492 graphSize
.width
= Math
.max(graphSize
.width
, container
.position
.right
, props
.width
);
493 graphSize
.height
= Math
.max(graphSize
.height
, container
.position
.bottom
, props
.height
);
497 width
: graphSize
.width
+ props
.width
,
498 height
: graphSize
.height
+ props
.height
501 const uiTransientState
= {
503 dragStartInfo
: [0, 0]
506 // todo extract drag behavior into class DescriptorGraphDrag
508 const drag
= this.drag
= d3
.behavior
.drag()
509 .origin(function(d
) { return d
; })
510 .on('drag.graph', function (d
) {
511 uiTransientState
.isDragging
= true;
512 const mouse
= d3
.mouse(graph
.g
.node());
513 const offset
= uiTransientState
.dragStartInfo
;
514 const newTopEdge
= snapTo(mouse
[1] - offset
[1]);
515 const newLeftEdge
= snapTo(mouse
[0] - offset
[0]);
516 if (d
.position
.left
=== newLeftEdge
&& d
.position
.top
=== newTopEdge
) {
517 // do not redraw since we are not moving the container
520 d
.position
.move(newLeftEdge
, newTopEdge
);
521 graph
.saveContainerPosition(d
);
522 const connectionPointRefs
= getConnectionPointEdges();
523 d3
.select(this).attr({
525 const x
= d
.position
.left
;
526 const y
= d
.position
.top
;
527 return 'translate(' + x
+ ', ' + y
+ ')';
530 requestAnimationFrame(() => {
531 drawConnectionPointsAndPaths(graph
, connectionPointRefs
);
532 layout
.renderers
.forEach(d
=> d
.render());
534 }).on('dragend.graph', () => {
535 // d3 fires a drag-end event on mouse up, even if just clicking
536 if (uiTransientState
.isDragging
) {
537 uiTransientState
.isDragging
= false;
538 CatalogItemsActions
.catalogItemMetaDataChanged(graph
.containers
[0].model
);
539 d3
.select(this).on('.graph', null);
541 }).on('dragstart.graph', function (d
) {
542 // the x, y offset of the mouse from the container's left, top
543 uiTransientState
.dragStartInfo
= d3
.mouse(this);
546 this.renderers
= [GraphVirtualLink
, GraphNetworkService
, GraphForwardingGraph
, GraphVirtualNetworkFunction
, GraphConstituentVnfd
, GraphVirtualDeploymentUnit
, GraphRecordServicePath
, GraphInternalVirtualLink
].map(layout
=> {
547 const container
= new layout(graph
, props
);
548 const layoutInfo
= containerLayoutInfo
[container
.classType
&& container
.classType
.type
];
550 container
.props
.descriptorWidth
= layoutInfo
.width
;
551 container
.props
.descriptorHeight
= layoutInfo
.height
;
553 container
.dragHandler
= drag
;
554 container
.addContainers(containerList
);
560 render(graph
, updateCallback
= () => {}) {
561 const connectionPointRefs
= getConnectionPointEdges();
562 requestAnimationFrame(() => {
563 drawConnectionPointsAndPaths(graph
, connectionPointRefs
);
564 this.renderers
.forEach(d
=> d
.render());