diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index 2aa9916284..3a662bbb83 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -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, @@ -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, diff --git a/client/app/scripts/charts/edge.js b/client/app/scripts/charts/edge.js new file mode 100644 index 0000000000..c7bb9f4ccb --- /dev/null +++ b/client/app/scripts/charts/edge.js @@ -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 ( + + + + + ); + }, + + handleMouseEnter: function(ev) { + AppActions.enterEdge(ev.currentTarget.id); + }, + + handleMouseLeave: function(ev) { + AppActions.leaveEdge(ev.currentTarget.id); + } + +}); + +module.exports = Edge; diff --git a/client/app/scripts/charts/node.js b/client/app/scripts/charts/node.js index fa8e46efa3..d09089a10a 100644 --- a/client/app/scripts/charts/node.js +++ b/client/app/scripts/charts/node.js @@ -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({ @@ -47,7 +48,9 @@ const Node = React.createClass({ const className = this.props.highlighted ? 'node highlighted' : 'node'; return ( - + + {this.props.highlighted && } @@ -55,7 +58,16 @@ const Node = React.createClass({ {this.props.subLabel} ); + }, + + handleMouseEnter: function(ev) { + AppActions.enterNode(ev.currentTarget.id); + }, + + handleMouseLeave: function(ev) { + AppActions.leaveNode(ev.currentTarget.id); } + }); module.exports = Node; diff --git a/client/app/scripts/charts/nodes-chart.js b/client/app/scripts/charts/nodes-chart.js index 91b1034dd0..5a5d8648fa 100644 --- a/client/app/scripts/charts/nodes-chart.js +++ b/client/app/scripts/charts/nodes-chart.js @@ -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'); @@ -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() { @@ -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 ( + ); - }); + }, 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 + ')'; @@ -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, @@ -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]]; diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js index 97cdc112e3..309b56a289 100644 --- a/client/app/scripts/components/app.js +++ b/client/app/scripts/components/app.js @@ -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(), @@ -67,7 +69,8 @@ const App = React.createClass({ - + ); } diff --git a/client/app/scripts/components/nodes.js b/client/app/scripts/components/nodes.js index 945428b6ae..4d1896d867 100644 --- a/client/app/scripts/components/nodes.js +++ b/client/app/scripts/components/nodes.js @@ -31,8 +31,10 @@ const Nodes = React.createClass({ return (