Showing
2 changed files
with
156 additions
and
40 deletions
... | @@ -87,7 +87,12 @@ | ... | @@ -87,7 +87,12 @@ |
87 | padLR: 8, | 87 | padLR: 8, |
88 | padTB: 6, | 88 | padTB: 6, |
89 | marginLR: 3, | 89 | marginLR: 3, |
90 | - marginTB: 2 | 90 | + marginTB: 2, |
91 | + port: { | ||
92 | + gap: 2, | ||
93 | + width: 12, | ||
94 | + height: 12 | ||
95 | + } | ||
91 | }, | 96 | }, |
92 | icons: { | 97 | icons: { |
93 | w: 32, | 98 | w: 32, |
... | @@ -113,7 +118,8 @@ | ... | @@ -113,7 +118,8 @@ |
113 | selected = {}, | 118 | selected = {}, |
114 | highlighted = null, | 119 | highlighted = null, |
115 | hovered = null, | 120 | hovered = null, |
116 | - viewMode = 'showAll'; | 121 | + viewMode = 'showAll', |
122 | + portLabelsOn = false; | ||
117 | 123 | ||
118 | 124 | ||
119 | function debug(what) { | 125 | function debug(what) { |
... | @@ -128,10 +134,13 @@ | ... | @@ -128,10 +134,13 @@ |
128 | return urlData().jsonUrl; | 134 | return urlData().jsonUrl; |
129 | } | 135 | } |
130 | 136 | ||
137 | + function safeId(id) { | ||
138 | + return id.replace(/[^a-z0-9]/gi, '_'); | ||
139 | + } | ||
140 | + | ||
131 | function detailJsonUrl(id) { | 141 | function detailJsonUrl(id) { |
132 | var u = urlData(), | 142 | var u = urlData(), |
133 | - encId = config.useLiveData ? encodeURIComponent(id) | 143 | + encId = config.useLiveData ? encodeURIComponent(id) : safeId(id); |
134 | - : id.replace(/[^a-z0-9]/gi, '_'); | ||
135 | return u.detailPrefix + encId + u.detailSuffix; | 144 | return u.detailPrefix + encId + u.detailSuffix; |
136 | } | 145 | } |
137 | 146 | ||
... | @@ -303,7 +312,9 @@ | ... | @@ -303,7 +312,9 @@ |
303 | } | 312 | } |
304 | 313 | ||
305 | function togglePorts() { | 314 | function togglePorts() { |
306 | - console.log('Toggle Ports - context = ' + contextLabel()); | 315 | + portLabelsOn = !portLabelsOn; |
316 | + var portVis = portLabelsOn ? 'visible' : 'hidden'; | ||
317 | + d3.selectAll('.port').style('visibility', portVis); | ||
307 | } | 318 | } |
308 | 319 | ||
309 | function unpin() { | 320 | function unpin() { |
... | @@ -347,7 +358,7 @@ | ... | @@ -347,7 +358,7 @@ |
347 | ix = Math.random() * 0.6 * nw + 0.2 * nw, | 358 | ix = Math.random() * 0.6 * nw + 0.2 * nw, |
348 | iy = ypc * nh, | 359 | iy = ypc * nh, |
349 | node = { | 360 | node = { |
350 | - id: n.id, | 361 | + id: safeId(n.id), |
351 | labels: n.labels, | 362 | labels: n.labels, |
352 | class: 'device', | 363 | class: 'device', |
353 | icon: 'device', | 364 | icon: 'device', |
... | @@ -369,7 +380,7 @@ | ... | @@ -369,7 +380,7 @@ |
369 | ix = Math.random() * 0.6 * nw + 0.2 * nw, | 380 | ix = Math.random() * 0.6 * nw + 0.2 * nw, |
370 | iy = ypc * nh, | 381 | iy = ypc * nh, |
371 | node = { | 382 | node = { |
372 | - id: n.id, | 383 | + id: safeId(n.id), |
373 | labels: n.labels, | 384 | labels: n.labels, |
374 | class: 'host', | 385 | class: 'host', |
375 | icon: 'host', | 386 | icon: 'host', |
... | @@ -390,7 +401,7 @@ | ... | @@ -390,7 +401,7 @@ |
390 | network.data.links.forEach(function(lnk) { | 401 | network.data.links.forEach(function(lnk) { |
391 | var src = network.lookup[lnk.src], | 402 | var src = network.lookup[lnk.src], |
392 | dst = network.lookup[lnk.dst], | 403 | dst = network.lookup[lnk.dst], |
393 | - id = src.id + "~" + dst.id; | 404 | + id = src.id + "-" + dst.id; |
394 | 405 | ||
395 | var link = { | 406 | var link = { |
396 | class: 'infra', | 407 | class: 'infra', |
... | @@ -398,7 +409,9 @@ | ... | @@ -398,7 +409,9 @@ |
398 | type: lnk.type, | 409 | type: lnk.type, |
399 | width: lnk.linkWidth, | 410 | width: lnk.linkWidth, |
400 | source: src, | 411 | source: src, |
412 | + srcPort: lnk.srcPort, | ||
401 | target: dst, | 413 | target: dst, |
414 | + tgtPort: lnk.dstPort, | ||
402 | strength: config.force.linkStrength.infra | 415 | strength: config.force.linkStrength.infra |
403 | }; | 416 | }; |
404 | network.links.push(link); | 417 | network.links.push(link); |
... | @@ -408,7 +421,7 @@ | ... | @@ -408,7 +421,7 @@ |
408 | network.data.hosts.forEach(function(n) { | 421 | network.data.hosts.forEach(function(n) { |
409 | var src = network.lookup[n.id], | 422 | var src = network.lookup[n.id], |
410 | dst = network.lookup[n.cp.device], | 423 | dst = network.lookup[n.cp.device], |
411 | - id = src.id + "~" + dst.id; | 424 | + id = src.id + "-" + dst.id; |
412 | 425 | ||
413 | var link = { | 426 | var link = { |
414 | class: 'host', | 427 | class: 'host', |
... | @@ -490,13 +503,6 @@ | ... | @@ -490,13 +503,6 @@ |
490 | // }); | 503 | // }); |
491 | 504 | ||
492 | 505 | ||
493 | - // add links to the display | ||
494 | - network.link = network.svg.append('g').selectAll('.link') | ||
495 | - .data(network.force.links(), function(d) {return d.id}) | ||
496 | - .enter().append('line') | ||
497 | - .attr('class', function(d) {return 'link ' + d.class}); | ||
498 | - | ||
499 | - | ||
500 | // TODO: move drag behavior into separate method. | 506 | // TODO: move drag behavior into separate method. |
501 | // == define node drag behavior... | 507 | // == define node drag behavior... |
502 | network.draggedThreshold = d3.scale.linear() | 508 | network.draggedThreshold = d3.scale.linear() |
... | @@ -551,6 +557,53 @@ | ... | @@ -551,6 +557,53 @@ |
551 | } | 557 | } |
552 | }); | 558 | }); |
553 | 559 | ||
560 | + // ............................................................... | ||
561 | + | ||
562 | + // add links to the display | ||
563 | + network.link = network.svg.append('g').attr('id', 'links') | ||
564 | + .selectAll('.link') | ||
565 | + .data(network.force.links(), function(d) {return d.id}) | ||
566 | + .enter().append('line') | ||
567 | + .attr('class', function(d) {return 'link ' + d.class}); | ||
568 | + | ||
569 | + network.linkSrcPort = network.svg.append('g') | ||
570 | + .attr({ | ||
571 | + id: 'srcPorts', | ||
572 | + class: 'portLayer' | ||
573 | + }); | ||
574 | + network.linkTgtPort = network.svg.append('g') | ||
575 | + .attr({ | ||
576 | + id: 'tgtPorts', | ||
577 | + class: 'portLayer' | ||
578 | + }); | ||
579 | + | ||
580 | + var portVis = portLabelsOn ? 'visible' : 'hidden'; | ||
581 | + | ||
582 | + network.link.filter('.infra').each(function(d, i) { | ||
583 | + network.linkSrcPort.append('rect').attr({ | ||
584 | + id: 'srcPort-' + d.id, | ||
585 | + class: 'port', | ||
586 | + width: 12, | ||
587 | + height: 12, | ||
588 | + x: i * 20, | ||
589 | + y: 0 | ||
590 | + }) | ||
591 | + .style('visibility', portVis) | ||
592 | + .append('text').text(d.srcPort); | ||
593 | + | ||
594 | + network.linkTgtPort.append('rect').attr({ | ||
595 | + id: 'tgtPort-' + d.id, | ||
596 | + class: 'port', | ||
597 | + width: 12, | ||
598 | + height: 12, | ||
599 | + x: i * 20, | ||
600 | + y: 20 | ||
601 | + }) | ||
602 | + .style('visibility', portVis); | ||
603 | + | ||
604 | + }); | ||
605 | + | ||
606 | + // ............................................................... | ||
554 | 607 | ||
555 | // add nodes to the display | 608 | // add nodes to the display |
556 | network.node = network.svg.selectAll('.node') | 609 | network.node = network.svg.selectAll('.node') |
... | @@ -659,14 +712,18 @@ | ... | @@ -659,14 +712,18 @@ |
659 | 712 | ||
660 | // this function is scheduled to happen soon after the given thread ends | 713 | // this function is scheduled to happen soon after the given thread ends |
661 | setTimeout(function() { | 714 | setTimeout(function() { |
715 | + var lab = config.labels, | ||
716 | + portGap = lab.port.gap, | ||
717 | + midW = portGap + lab.port.width/ 2, | ||
718 | + midH = portGap + lab.port.height / 2; | ||
719 | + | ||
662 | // post process the device nodes, to pad their size to fit the | 720 | // post process the device nodes, to pad their size to fit the |
663 | // label text and attach the icon to the right location. | 721 | // label text and attach the icon to the right location. |
664 | network.node.filter('.device').each(function(d) { | 722 | network.node.filter('.device').each(function(d) { |
665 | // for every node, recompute size, padding, etc. so text fits | 723 | // for every node, recompute size, padding, etc. so text fits |
666 | var node = d3.select(this), | 724 | var node = d3.select(this), |
667 | text = node.select('text'), | 725 | text = node.select('text'), |
668 | - box = adjustRectToFitText(node), | 726 | + box = adjustRectToFitText(node); |
669 | - lab = config.labels; | ||
670 | 727 | ||
671 | // now make the computed adjustment | 728 | // now make the computed adjustment |
672 | node.select('rect') | 729 | node.select('rect') |
... | @@ -676,7 +733,13 @@ | ... | @@ -676,7 +733,13 @@ |
676 | .attr('x', box.x + config.icons.xoff) | 733 | .attr('x', box.x + config.icons.xoff) |
677 | .attr('y', box.y + config.icons.yoff); | 734 | .attr('y', box.y + config.icons.yoff); |
678 | 735 | ||
679 | - var bounds = boundsFromBox(box); | 736 | + var bounds = boundsFromBox(box), |
737 | + portBounds = { | ||
738 | + x1: bounds.x1 - midW, | ||
739 | + x2: bounds.x2 + midW, | ||
740 | + y1: bounds.y1 - midH, | ||
741 | + y2: bounds.y2 + midH | ||
742 | + }; | ||
680 | 743 | ||
681 | // todo: clean up extent and edge work.. | 744 | // todo: clean up extent and edge work.. |
682 | d.extent = { | 745 | d.extent = { |
... | @@ -693,6 +756,21 @@ | ... | @@ -693,6 +756,21 @@ |
693 | bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2) | 756 | bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2) |
694 | }; | 757 | }; |
695 | 758 | ||
759 | + d.portEdge = { | ||
760 | + left : new geo.LineSegment( | ||
761 | + portBounds.x1, portBounds.y1, portBounds.x1, portBounds.y2 | ||
762 | + ), | ||
763 | + right : new geo.LineSegment( | ||
764 | + portBounds.x2, portBounds.y1, portBounds.x2, portBounds.y2 | ||
765 | + ), | ||
766 | + top : new geo.LineSegment( | ||
767 | + portBounds.x1, portBounds.y1, portBounds.x2, portBounds.y1 | ||
768 | + ), | ||
769 | + bottom : new geo.LineSegment( | ||
770 | + portBounds.x1, portBounds.y2, portBounds.x2, portBounds.y2 | ||
771 | + ) | ||
772 | + }; | ||
773 | + | ||
696 | }); | 774 | }); |
697 | 775 | ||
698 | network.numTicks = 0; | 776 | network.numTicks = 0; |
... | @@ -840,39 +918,71 @@ | ... | @@ -840,39 +918,71 @@ |
840 | preventCollisions(); | 918 | preventCollisions(); |
841 | } | 919 | } |
842 | 920 | ||
921 | + var portHalfW = config.labels.port.width / 2, | ||
922 | + portHalfH = config.labels.port.height / 2; | ||
923 | + | ||
843 | // clip visualization of links at bounds of nodes... | 924 | // clip visualization of links at bounds of nodes... |
844 | network.link.each(function(d) { | 925 | network.link.each(function(d) { |
845 | - var xs = d.source.x, | 926 | + var xs = d.source.x, |
846 | - ys = d.source.y, | 927 | + ys = d.source.y, |
847 | - xt = d.target.x, | 928 | + xt = d.target.x, |
848 | - yt = d.target.y, | 929 | + yt = d.target.y, |
849 | - line = new geo.LineSegment(xs, ys, xt, yt), | 930 | + line = new geo.LineSegment(xs, ys, xt, yt), |
850 | - e, ix; | 931 | + e, ix, |
932 | + exs, eys, ext, eyt, | ||
933 | + pxs, pys, pxt, pyt; | ||
934 | + | ||
935 | + if (d.class === 'host') { | ||
936 | + // no adjustment for source end of link, since hosts are dots | ||
937 | + exs = xs; | ||
938 | + eys = ys; | ||
851 | 939 | ||
940 | + } else { | ||
852 | for (e in d.source.edge) { | 941 | for (e in d.source.edge) { |
853 | ix = line.intersect(d.source.edge[e].offset(xs, ys)); | 942 | ix = line.intersect(d.source.edge[e].offset(xs, ys)); |
854 | if (ix.in1 && ix.in2) { | 943 | if (ix.in1 && ix.in2) { |
855 | - xs = ix.x; | 944 | + exs = ix.x; |
856 | - ys = ix.y; | 945 | + eys = ix.y; |
946 | + | ||
947 | + // also pick off the port label intersection | ||
948 | + ix = line.intersect(d.source.portEdge[e].offset(xs, ys)); | ||
949 | + pxs = ix.x; | ||
950 | + pys = ix.y; | ||
857 | break; | 951 | break; |
858 | } | 952 | } |
859 | } | 953 | } |
954 | + } | ||
860 | 955 | ||
861 | - for (e in d.target.edge) { | 956 | + for (e in d.target.edge) { |
862 | - ix = line.intersect(d.target.edge[e].offset(xt, yt)); | 957 | + ix = line.intersect(d.target.edge[e].offset(xt, yt)); |
863 | - if (ix.in1 && ix.in2) { | 958 | + if (ix.in1 && ix.in2) { |
864 | - xt = ix.x; | 959 | + ext = ix.x; |
865 | - yt = ix.y; | 960 | + eyt = ix.y; |
866 | - break; | 961 | + |
867 | - } | 962 | + // also pick off the port label intersection |
963 | + ix = line.intersect(d.target.portEdge[e].offset(xt, yt)); | ||
964 | + pxt = ix.x; | ||
965 | + pyt = ix.y; | ||
966 | + break; | ||
868 | } | 967 | } |
968 | + } | ||
869 | 969 | ||
870 | - d3.select(this) | 970 | + // adjust the endpoints of the link's line to match rectangles |
871 | - .attr('x1', xs) | 971 | + d3.select(this) |
872 | - .attr('y1', ys) | 972 | + .attr('x1', exs) |
873 | - .attr('x2', xt) | 973 | + .attr('y1', eys) |
874 | - .attr('y2', yt); | 974 | + .attr('x2', ext) |
875 | - }); | 975 | + .attr('y2', eyt); |
976 | + | ||
977 | + d3.select('#srcPort-' + d.id) | ||
978 | + .attr('x', pxs - portHalfW) | ||
979 | + .attr('y', pys - portHalfH); | ||
980 | + | ||
981 | + d3.select('#tgtPort-' + d.id) | ||
982 | + .attr('x', pxt - portHalfW) | ||
983 | + .attr('y', pyt - portHalfH); | ||
984 | + | ||
985 | + }); | ||
876 | 986 | ||
877 | // position each node by translating the node (group) by x,y | 987 | // position each node by translating the node (group) by x,y |
878 | network.node | 988 | network.node | ... | ... |
... | @@ -106,6 +106,12 @@ svg .link.host { | ... | @@ -106,6 +106,12 @@ svg .link.host { |
106 | Xstroke-dasharray: 3,3; | 106 | Xstroke-dasharray: 3,3; |
107 | } | 107 | } |
108 | 108 | ||
109 | +svg g.portLayer rect.port { | ||
110 | + stroke-width: 1; | ||
111 | + stroke: black; | ||
112 | + fill: white; | ||
113 | +} | ||
114 | + | ||
109 | svg .node.device rect { | 115 | svg .node.device rect { |
110 | stroke-width: 1.5px; | 116 | stroke-width: 1.5px; |
111 | 117 | ... | ... |
-
Please register or login to post a comment