mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
highlight edges and connected nodes on edge hover
This commit is contained in:
@@ -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,
|
||||
|
||||
34
client/app/scripts/charts/edge.js
Normal file
34
client/app/scripts/charts/edge.js
Normal file
@@ -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;
|
||||
@@ -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 = node.id === this.props.mouseOverNodeId || _.includes(node.adjacency, this.props.mouseOverNodeId);
|
||||
const highlighted = _.includes(this.props.highlightedNodeIds, node.id);
|
||||
return (
|
||||
<Node
|
||||
highlighted={highlighted}
|
||||
@@ -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 + ')';
|
||||
|
||||
@@ -158,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]];
|
||||
|
||||
@@ -19,7 +19,8 @@ function getStateFromStores() {
|
||||
currentTopology: AppStore.getCurrentTopology(),
|
||||
connectionState: AppStore.getConnectionState(),
|
||||
currentGrouping: AppStore.getCurrentGrouping(),
|
||||
mouseOverNodeId: AppStore.getMouseOverNodeId(),
|
||||
highlightedEdgeIds: AppStore.getHighlightedEdgeIds(),
|
||||
highlightedNodeIds: AppStore.getHighlightedNodeIds(),
|
||||
selectedNodeId: AppStore.getSelectedNodeId(),
|
||||
nodeDetails: AppStore.getNodeDetails(),
|
||||
nodes: AppStore.getNodes(),
|
||||
@@ -68,7 +69,8 @@ const App = React.createClass({
|
||||
<Status connectionState={this.state.connectionState} />
|
||||
</div>
|
||||
|
||||
<Nodes nodes={this.state.nodes} mouseOverNodeId={this.state.mouseOverNodeId} />
|
||||
<Nodes nodes={this.state.nodes} highlightedNodeIds={this.state.highlightedNodeIds}
|
||||
highlightedEdgeIds={this.state.highlightedEdgeIds} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ const Nodes = React.createClass({
|
||||
return (
|
||||
<div id="nodes">
|
||||
<NodesChart
|
||||
mouseOverNodeId={this.props.mouseOverNodeId}
|
||||
highlightedEdgeIds={this.props.highlightedEdgeIds}
|
||||
highlightedNodeIds={this.props.highlightedNodeIds}
|
||||
nodes={this.props.nodes}
|
||||
onNodeClick={this.onNodeClick}
|
||||
width={this.state.width}
|
||||
|
||||
@@ -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,
|
||||
|
||||
4
client/app/scripts/constants/naming.js
Normal file
4
client/app/scripts/constants/naming.js
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
module.exports = {
|
||||
EDGE_ID_SEPARATOR: '-'
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -16,6 +17,7 @@ function isUrlForTopologyId(url, topologyId) {
|
||||
let connectionState = 'disconnected';
|
||||
let currentGrouping = 'none';
|
||||
let currentTopologyId = 'applications';
|
||||
let mouseOverEdgeId = null;
|
||||
let mouseOverNodeId = null;
|
||||
let nodes = {};
|
||||
let nodeDetails = null;
|
||||
@@ -58,8 +60,34 @@ const AppStore = assign({}, EventEmitter.prototype, {
|
||||
return currentGrouping;
|
||||
},
|
||||
|
||||
getMouseOverNodeId: function() {
|
||||
return mouseOverNodeId;
|
||||
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() {
|
||||
@@ -114,6 +142,11 @@ 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:
|
||||
mouseOverNodeId = payload.nodeId;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
@@ -125,6 +158,12 @@ AppStore.registeredCallback = function(payload) {
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.LEAVE_EDGE:
|
||||
mouseOverEdgeId = null;
|
||||
console.log('leave');
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
break;
|
||||
|
||||
case ActionTypes.LEAVE_NODE:
|
||||
mouseOverNodeId = null;
|
||||
AppStore.emit(AppStore.CHANGE_EVENT);
|
||||
@@ -149,6 +188,9 @@ AppStore.registeredCallback = function(payload) {
|
||||
if (mouseOverNodeId === nodeId) {
|
||||
mouseOverNodeId = null;
|
||||
}
|
||||
if (nodes[nodeId] && _.contains(mouseOverEdgeId, nodeId)) {
|
||||
mouseOverEdgeId = null;
|
||||
}
|
||||
delete nodes[nodeId];
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user