Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions client/app/scripts/actions/app-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ module.exports = {
WebapiUtils.getNodesDelta(AppStore.getCurrentTopologyUrl());
},

enterEdge: function(edgeId) {
AppDispatcher.dispatch({
type: ActionTypes.ENTER_EDGE,
edgeId: edgeId
});
},

enterNode: function(nodeId) {
AppDispatcher.dispatch({
type: ActionTypes.ENTER_NODE,
Expand All @@ -53,6 +60,13 @@ module.exports = {
RouterUtils.updateRoute();
},

leaveEdge: function(edgeId) {
AppDispatcher.dispatch({
type: ActionTypes.LEAVE_EDGE,
edgeId: edgeId
});
},

leaveNode: function(nodeId) {
AppDispatcher.dispatch({
type: ActionTypes.LEAVE_NODE,
Expand Down
34 changes: 34 additions & 0 deletions client/app/scripts/charts/edge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const d3 = require('d3');
const React = require('react');

const AppActions = require('../actions/app-actions');

const line = d3.svg.line()
.interpolate('basis')
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });

const Edge = React.createClass({

render: function() {
const className = this.props.highlighted ? 'edge highlighted' : 'edge';

return (
<g className={className} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} id={this.props.id}>
<path d={line(this.props.points)} className="shadow" />
<path d={line(this.props.points)} className="link" />
</g>
);
},

handleMouseEnter: function(ev) {
AppActions.enterEdge(ev.currentTarget.id);
},

handleMouseLeave: function(ev) {
AppActions.leaveEdge(ev.currentTarget.id);
}

});

module.exports = Edge;
14 changes: 13 additions & 1 deletion client/app/scripts/charts/node.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const React = require('react');
const tweenState = require('react-tween-state');

const AppActions = require('../actions/app-actions');
const NodeColorMixin = require('../mixins/node-color-mixin');

const Node = React.createClass({
Expand Down Expand Up @@ -47,15 +48,26 @@ const Node = React.createClass({
const className = this.props.highlighted ? 'node highlighted' : 'node';

return (
<g className={className} transform={transform} onClick={this.props.onClick} id={this.props.id}>
<g className={className} transform={transform} id={this.props.id}
onClick={this.props.onClick} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
{this.props.highlighted && <circle r={scale(0.7)} className="highlighted"></circle>}
<circle r={scale(0.5)} className="border" stroke={color}></circle>
<circle r={scale(0.45)} className="shadow"></circle>
<circle r={Math.max(2, scale(0.125))} className="node"></circle>
<text className="node-label" textAnchor="middle" x={textOffsetX} y={textOffsetY}>{this.props.label}</text>
<text className="node-sublabel" textAnchor="middle" x={textOffsetX} y={textOffsetY + 17}>{this.props.subLabel}</text>
</g>
);
},

handleMouseEnter: function(ev) {
AppActions.enterNode(ev.currentTarget.id);
},

handleMouseLeave: function(ev) {
AppActions.leaveNode(ev.currentTarget.id);
}

});

module.exports = Node;
29 changes: 16 additions & 13 deletions client/app/scripts/charts/nodes-chart.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const _ = require('lodash');
const d3 = require('d3');
const React = require('react');

const Edge = require('./edge');
const Naming = require('../constants/naming');
const NodesLayout = require('./nodes-layout');
const Node = require('./node');

Expand All @@ -12,11 +14,6 @@ const MARGINS = {
bottom: 0
};

const line = d3.svg.line()
.interpolate('basis')
.x(function(d) { return d.x; })
.y(function(d) { return d.y; });

const NodesChart = React.createClass({

getInitialState: function() {
Expand Down Expand Up @@ -77,9 +74,9 @@ const NodesChart = React.createClass({
return fingerprint.join(';');
},

getGraphNodes: function(nodes, scale) {
renderGraphNodes: function(nodes, scale) {
return _.map(nodes, function(node) {
const highlighted = _.includes(this.props.highlightedNodes, node.id);
const highlighted = _.includes(this.props.highlightedNodeIds, node.id);
return (
<Node
highlighted={highlighted}
Expand All @@ -96,17 +93,18 @@ const NodesChart = React.createClass({
}, this);
},

getGraphEdges: function(edges) {
renderGraphEdges: function(edges) {
return _.map(edges, function(edge) {
const highlighted = _.includes(this.props.highlightedEdgeIds, edge.id);
return (
<path className="link" d={line(edge.points)} key={edge.id} />
<Edge key={edge.id} id={edge.id} points={edge.points} highlighted={highlighted} />
);
});
}, this);
},

render: function() {
const nodeElements = this.getGraphNodes(this.state.nodes, this.state.nodeScale);
const edgeElements = this.getGraphEdges(this.state.edges, this.state.nodeScale);
const nodeElements = this.renderGraphNodes(this.state.nodes, this.state.nodeScale);
const edgeElements = this.renderGraphEdges(this.state.edges, this.state.nodeScale);
const transform = 'translate(' + this.state.translate + ')' +
' scale(' + this.state.scale + ')';

Expand All @@ -131,12 +129,17 @@ const NodesChart = React.createClass({

_.each(topology, function(node, id) {
nodes[id] = prevNodes[id] || {};

// initialize position for new nodes
_.defaults(nodes[id], {
x: centerX,
y: centerY,
textAnchor: 'start'
});

// copy relevant fields to state nodes
_.assign(nodes[id], {
adjacency: node.adjacency,
id: id,
label: node.label_major,
subLabel: node.label_minor,
Expand All @@ -153,7 +156,7 @@ const NodesChart = React.createClass({
_.each(topology, function(node) {
_.each(node.adjacency, function(adjacent) {
const edge = [node.id, adjacent];
const edgeId = edge.join('-');
const edgeId = edge.join(Naming.EDGE_ID_SEPARATOR);

if (!edges[edgeId]) {
const source = nodes[edge[0]];
Expand Down
5 changes: 4 additions & 1 deletion client/app/scripts/components/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ function getStateFromStores() {
currentTopology: AppStore.getCurrentTopology(),
connectionState: AppStore.getConnectionState(),
currentGrouping: AppStore.getCurrentGrouping(),
highlightedEdgeIds: AppStore.getHighlightedEdgeIds(),
highlightedNodeIds: AppStore.getHighlightedNodeIds(),
selectedNodeId: AppStore.getSelectedNodeId(),
nodeDetails: AppStore.getNodeDetails(),
nodes: AppStore.getNodes(),
Expand Down Expand Up @@ -67,7 +69,8 @@ const App = React.createClass({
<Status connectionState={this.state.connectionState} />
</div>

<Nodes nodes={this.state.nodes} />
<Nodes nodes={this.state.nodes} highlightedNodeIds={this.state.highlightedNodeIds}
highlightedEdgeIds={this.state.highlightedEdgeIds} />
</div>
);
}
Expand Down
4 changes: 3 additions & 1 deletion client/app/scripts/components/nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ const Nodes = React.createClass({
return (
<div id="nodes">
<NodesChart
onNodeClick={this.onNodeClick}
highlightedEdgeIds={this.props.highlightedEdgeIds}
highlightedNodeIds={this.props.highlightedNodeIds}
nodes={this.props.nodes}
onNodeClick={this.onNodeClick}
width={this.state.width}
height={this.state.height}
context="view"
Expand Down
2 changes: 2 additions & 0 deletions client/app/scripts/constants/action-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ module.exports = keymirror({
CLICK_GROUPING: null,
CLICK_NODE: null,
CLICK_TOPOLOGY: null,
ENTER_EDGE: null,
ENTER_NODE: null,
HIT_ESC_KEY: null,
LEAVE_EDGE: null,
LEAVE_NODE: null,
RECEIVE_NODE_DETAILS: null,
RECEIVE_NODES: null,
Expand Down
4 changes: 4 additions & 0 deletions client/app/scripts/constants/naming.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

module.exports = {
EDGE_ID_SEPARATOR: '-'
};
55 changes: 50 additions & 5 deletions client/app/scripts/stores/app-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const assign = require('object-assign');

const AppDispatcher = require('../dispatcher/app-dispatcher');
const ActionTypes = require('../constants/action-types');
const Naming = require('../constants/naming');

// Helpers

Expand All @@ -16,7 +17,8 @@ function isUrlForTopologyId(url, topologyId) {
let connectionState = 'disconnected';
let currentGrouping = 'none';
let currentTopologyId = 'applications';
let mouseOverNode = null;
let mouseOverEdgeId = null;
let mouseOverNodeId = null;
let nodes = {};
let nodeDetails = null;
let selectedNodeId = null;
Expand Down Expand Up @@ -58,6 +60,36 @@ const AppStore = assign({}, EventEmitter.prototype, {
return currentGrouping;
},

getHighlightedEdgeIds: function() {
if (mouseOverNodeId) {
// all neighbour combinations because we dont know which direction exists
const node = nodes[mouseOverNodeId];
return _.flatten(
_.map(node.adjacency, function(nodeId) {
return [
[nodeId, mouseOverNodeId].join(Naming.EDGE_ID_SEPARATOR),
[mouseOverNodeId, nodeId].join(Naming.EDGE_ID_SEPARATOR)
];
})
);
}
if (mouseOverEdgeId) {
return mouseOverEdgeId;
}
return null;
},

getHighlightedNodeIds: function() {
if (mouseOverNodeId) {
const node = nodes[mouseOverNodeId];
return _.union(node.adjacency, [mouseOverNodeId]);
}
if (mouseOverEdgeId) {
return mouseOverEdgeId.split(Naming.EDGE_ID_SEPARATOR);
}
return null;
},

getNodeDetails: function() {
return nodeDetails;
},
Expand Down Expand Up @@ -110,8 +142,13 @@ AppStore.registeredCallback = function(payload) {
AppStore.emit(AppStore.CHANGE_EVENT);
break;

case ActionTypes.ENTER_EDGE:
mouseOverEdgeId = payload.edgeId;
AppStore.emit(AppStore.CHANGE_EVENT);
break;

case ActionTypes.ENTER_NODE:
mouseOverNode = payload.nodeId;
mouseOverNodeId = payload.nodeId;
AppStore.emit(AppStore.CHANGE_EVENT);
break;

Expand All @@ -121,8 +158,13 @@ AppStore.registeredCallback = function(payload) {
AppStore.emit(AppStore.CHANGE_EVENT);
break;

case ActionTypes.LEAVE_EDGE:
mouseOverEdgeId = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;

case ActionTypes.LEAVE_NODE:
mouseOverNode = null;
mouseOverNodeId = null;
AppStore.emit(AppStore.CHANGE_EVENT);
break;

Expand All @@ -142,8 +184,11 @@ AppStore.registeredCallback = function(payload) {
// nodes that no longer exist
_.each(payload.delta.remove, function(nodeId) {
// in case node disappears before mouseleave event
if (mouseOverNode === nodeId) {
mouseOverNode = null;
if (mouseOverNodeId === nodeId) {
mouseOverNodeId = null;
}
if (nodes[nodeId] && _.contains(mouseOverEdgeId, nodeId)) {
mouseOverEdgeId = null;
}
delete nodes[nodeId];
});
Expand Down
33 changes: 28 additions & 5 deletions client/app/styles/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,27 @@ body {
cursor: pointer;
}

.link {
stroke: @text-secondary-color;
stroke-width: 1.5px;
fill: none;
opacity: 0.5;
.edge {
.link {
stroke: @text-secondary-color;
stroke-width: 1.5px;
fill: none;
stroke-opacity: 0.5;
}
.shadow {
stroke: @weave-blue;
stroke-width: 10px;
fill: none;
stroke-opacity: 0;
}
&.highlighted {
.shadow {
stroke-opacity: 0.1;
}
}
}


circle.border {
stroke-width: 3px;
fill: none;
Expand All @@ -182,6 +196,15 @@ body {
circle.node {
fill: @text-color;
}

circle.highlighted {
fill: @weave-blue;
fill-opacity: 0.1;
stroke: @weave-blue;
stroke-width: 1px;
stroke-opacity: 0.4;
}

}

#details {
Expand Down