Improved rendering order of nodes/edges in Graph View (#2623)

* Rendering both nodes and edges from one component.

* Organized the render layers in a more readable manner.

* Rerendering optimization.

* Finer edge rendering layers.

* Moved the constants out of the component file.

* Typo fix.

* Include edge thickness in <Motion /> instead of fade-in effect.
This commit is contained in:
Filip Barl
2017-06-22 11:58:31 +02:00
committed by GitHub
parent 6697e64889
commit 36ee5db1da
8 changed files with 272 additions and 286 deletions
+23 -8
View File
@@ -5,7 +5,7 @@ import { line, curveBasis } from 'd3-shape';
import { each, omit, times, constant } from 'lodash';
import { NODES_SPRING_ANIMATION_CONFIG } from '../constants/animation';
import { EDGE_WAYPOINTS_CAP } from '../constants/styles';
import { NODE_BASE_SIZE, EDGE_WAYPOINTS_CAP } from '../constants/styles';
import Edge from './edge';
@@ -14,8 +14,8 @@ const spline = line()
.x(d => d.x)
.y(d => d.y);
const transformedEdge = (props, path) => (
<Edge {...props} path={spline(path)} />
const transformedEdge = (props, path, thickness) => (
<Edge {...props} path={spline(path)} thickness={thickness} />
);
// Converts a waypoints map of the format { x0: 11, y0: 22, x1: 33, y1: 44 }
@@ -45,7 +45,11 @@ const waypointsArrayToMap = (waypointsArray) => {
export default class EdgeContainer extends React.PureComponent {
constructor(props, context) {
super(props, context);
this.state = { waypointsMap: makeMap() };
this.state = {
waypointsMap: makeMap(),
thickness: 1,
};
}
componentWillMount() {
@@ -59,21 +63,32 @@ export default class EdgeContainer extends React.PureComponent {
if (this.props.isAnimated && nextProps.waypoints !== this.props.waypoints) {
this.prepareWaypointsForMotion(nextProps.waypoints);
}
// Edge thickness will reflect the zoom scale.
const baseScale = (nextProps.scale * 0.01) * NODE_BASE_SIZE;
const thickness = (nextProps.focused ? 3 : 1) * baseScale;
this.setState({ thickness });
}
render() {
const { isAnimated, waypoints } = this.props;
const forwardedProps = omit(this.props, 'isAnimated', 'waypoints');
const forwardedProps = omit(this.props, 'isAnimated', 'waypoints', 'scale');
if (!isAnimated) {
return transformedEdge(forwardedProps, waypoints.toJS());
return transformedEdge(forwardedProps, waypoints.toJS(), this.state.thickness);
}
return (
// For the Motion interpolation to work, the waypoints need to be in a map format like
// { x0: 11, y0: 22, x1: 33, y1: 44 } that we convert to the array format when rendering.
<Motion style={this.state.waypointsMap.toJS()}>
{interpolated => transformedEdge(forwardedProps, waypointsMapToArray(interpolated))}
<Motion
style={{
thickness: spring(this.state.thickness, NODES_SPRING_ANIMATION_CONFIG),
...this.state.waypointsMap.toJS(),
}}
>
{({ thickness, ...interpolatedWaypoints}) => transformedEdge(
forwardedProps, waypointsMapToArray(interpolatedWaypoints), thickness
)}
</Motion>
);
}
+5 -18
View File
@@ -3,7 +3,6 @@ import { connect } from 'react-redux';
import classNames from 'classnames';
import { enterEdge, leaveEdge } from '../actions/app-actions';
import { NODE_BASE_SIZE } from '../constants/styles';
class Edge extends React.Component {
@@ -14,34 +13,22 @@ class Edge extends React.Component {
}
render() {
const {
id,
path,
highlighted,
blurred,
focused,
scale,
source,
target
} = this.props;
const className = classNames('edge', { highlighted, blurred, focused });
const thickness = (scale * 0.01) * NODE_BASE_SIZE;
const strokeWidth = focused ? thickness * 3 : thickness;
const { id, path, highlighted, blurred, focused, thickness, source, target } = this.props;
const shouldRenderMarker = (focused || highlighted) && (source !== target);
// Draws the edge so that its thickness reflects the zoom scale.
// Edge shadow is always made 10x thicker than the edge itself.
const className = classNames('edge', { highlighted, blurred });
return (
<g
id={id} className={className}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
<path className="shadow" d={path} style={{ strokeWidth: 10 * strokeWidth }} />
<path className="shadow" d={path} style={{ strokeWidth: 10 * thickness }} />
<path
className="link"
d={path}
markerEnd={shouldRenderMarker ? 'url(#end-arrow)' : null}
style={{ strokeWidth }}
style={{ strokeWidth: thickness }}
/>
</g>
);
@@ -1,91 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import { Map as makeMap } from 'immutable';
import { searchNodeMatchesSelector } from '../selectors/search';
import { selectedNetworkNodesIdsSelector } from '../selectors/node-networks';
import { highlightedEdgeIdsSelector } from '../selectors/graph-view/decorators';
import { hasSelectedNode as hasSelectedNodeFn } from '../utils/topology-utils';
import EdgeContainer from './edge-container';
class NodesChartEdges extends React.Component {
constructor(props, context) {
super(props, context);
// Edge decorators
this.edgeFocusedDecorator = this.edgeFocusedDecorator.bind(this);
this.edgeBlurredDecorator = this.edgeBlurredDecorator.bind(this);
this.edgeHighlightedDecorator = this.edgeHighlightedDecorator.bind(this);
this.edgeScaleDecorator = this.edgeScaleDecorator.bind(this);
}
edgeHighlightedDecorator(edge) {
return edge.set('highlighted', this.props.highlightedEdgeIds.has(edge.get('id')));
}
edgeFocusedDecorator(edge) {
const sourceSelected = (this.props.selectedNodeId === edge.get('source'));
const targetSelected = (this.props.selectedNodeId === edge.get('target'));
return edge.set('focused', this.props.hasSelectedNode && (sourceSelected || targetSelected));
}
edgeBlurredDecorator(edge) {
const { selectedNodeId, searchNodeMatches, selectedNetworkNodesIds } = this.props;
const sourceSelected = (selectedNodeId === edge.get('source'));
const targetSelected = (selectedNodeId === edge.get('target'));
const otherNodesSelected = this.props.hasSelectedNode && !sourceSelected && !targetSelected;
const sourceNoMatches = searchNodeMatches.get(edge.get('source'), makeMap()).isEmpty();
const targetNoMatches = searchNodeMatches.get(edge.get('target'), makeMap()).isEmpty();
const notMatched = this.props.searchQuery && (sourceNoMatches || targetNoMatches);
const sourceInNetwork = selectedNetworkNodesIds.contains(edge.get('source'));
const targetInNetwork = selectedNetworkNodesIds.contains(edge.get('target'));
const notInNetwork = this.props.selectedNetwork && (!sourceInNetwork || !targetInNetwork);
return edge.set('blurred', !edge.get('highlighted') && !edge.get('focused') &&
(otherNodesSelected || notMatched || notInNetwork));
}
edgeScaleDecorator(edge) {
return edge.set('scale', edge.get('focused') ? this.props.selectedScale : 1);
}
render() {
const { layoutEdges, isAnimated } = this.props;
const edgesToRender = layoutEdges.toIndexedSeq()
.map(this.edgeHighlightedDecorator)
.map(this.edgeFocusedDecorator)
.map(this.edgeBlurredDecorator)
.map(this.edgeScaleDecorator);
return (
<g className="nodes-chart-edges">
{edgesToRender.map(edge => (
<EdgeContainer
key={edge.get('id')}
id={edge.get('id')}
source={edge.get('source')}
target={edge.get('target')}
waypoints={edge.get('points')}
highlighted={edge.get('highlighted')}
focused={edge.get('focused')}
blurred={edge.get('blurred')}
scale={edge.get('scale')}
isAnimated={isAnimated}
/>
))}
</g>
);
}
}
export default connect(
state => ({
hasSelectedNode: hasSelectedNodeFn(state),
searchNodeMatches: searchNodeMatchesSelector(state),
selectedNetworkNodesIds: selectedNetworkNodesIdsSelector(state),
highlightedEdgeIds: highlightedEdgeIdsSelector(state),
searchQuery: state.get('searchQuery'),
selectedNetwork: state.get('selectedNetwork'),
selectedNodeId: state.get('selectedNodeId'),
})
)(NodesChartEdges);
+232 -12
View File
@@ -1,30 +1,236 @@
import React from 'react';
import { connect } from 'react-redux';
import { Map as makeMap, List as makeList } from 'immutable';
import NodesChartEdges from './nodes-chart-edges';
import NodesChartNodes from './nodes-chart-nodes';
import NodeContainer from './node-container';
import EdgeContainer from './edge-container';
import { getAdjacentNodes, hasSelectedNode as hasSelectedNodeFn } from '../utils/topology-utils';
import { graphExceedsComplexityThreshSelector } from '../selectors/topology';
import { nodeNetworksSelector, selectedNetworkNodesIdsSelector } from '../selectors/node-networks';
import { searchNodeMatchesSelector } from '../selectors/search';
import { nodeMetricSelector } from '../selectors/node-metric';
import {
highlightedNodeIdsSelector,
highlightedEdgeIdsSelector
} from '../selectors/graph-view/decorators';
import {
selectedScaleSelector,
layoutNodesSelector,
layoutEdgesSelector
} from '../selectors/graph-view/layout';
import {
BLURRED_EDGES_LAYER,
BLURRED_NODES_LAYER,
NORMAL_EDGES_LAYER,
NORMAL_NODES_LAYER,
HIGHLIGHTED_EDGES_LAYER,
HIGHLIGHTED_NODES_LAYER,
HOVERED_EDGES_LAYER,
HOVERED_NODES_LAYER,
} from '../constants/naming';
class NodesChartElements extends React.Component {
constructor(props, context) {
super(props, context);
this.renderNode = this.renderNode.bind(this);
this.renderEdge = this.renderEdge.bind(this);
this.renderElement = this.renderElement.bind(this);
this.nodeDisplayLayer = this.nodeDisplayLayer.bind(this);
this.edgeDisplayLayer = this.edgeDisplayLayer.bind(this);
// Node decorators
this.nodeHighlightedDecorator = this.nodeHighlightedDecorator.bind(this);
this.nodeFocusedDecorator = this.nodeFocusedDecorator.bind(this);
this.nodeBlurredDecorator = this.nodeBlurredDecorator.bind(this);
this.nodeMatchesDecorator = this.nodeMatchesDecorator.bind(this);
this.nodeNetworksDecorator = this.nodeNetworksDecorator.bind(this);
this.nodeMetricDecorator = this.nodeMetricDecorator.bind(this);
this.nodeScaleDecorator = this.nodeScaleDecorator.bind(this);
// Edge decorators
this.edgeFocusedDecorator = this.edgeFocusedDecorator.bind(this);
this.edgeBlurredDecorator = this.edgeBlurredDecorator.bind(this);
this.edgeHighlightedDecorator = this.edgeHighlightedDecorator.bind(this);
this.edgeScaleDecorator = this.edgeScaleDecorator.bind(this);
}
nodeDisplayLayer(node) {
if (node.get('id') === this.props.mouseOverNodeId) {
return HOVERED_NODES_LAYER;
} else if (node.get('blurred') && !node.get('focused')) {
return BLURRED_NODES_LAYER;
} else if (node.get('highlighted')) {
return HIGHLIGHTED_NODES_LAYER;
}
return NORMAL_NODES_LAYER;
}
edgeDisplayLayer(edge) {
if (edge.get('id') === this.props.mouseOverEdgeId) {
return HOVERED_EDGES_LAYER;
} else if (edge.get('blurred') && !edge.get('focused')) {
return BLURRED_EDGES_LAYER;
} else if (edge.get('highlighted')) {
return HIGHLIGHTED_EDGES_LAYER;
}
return NORMAL_EDGES_LAYER;
}
nodeHighlightedDecorator(node) {
const nodeSelected = (this.props.selectedNodeId === node.get('id'));
const nodeHighlighted = this.props.highlightedNodeIds.has(node.get('id'));
return node.set('highlighted', nodeHighlighted || nodeSelected);
}
nodeFocusedDecorator(node) {
const nodeSelected = (this.props.selectedNodeId === node.get('id'));
const isNeighborOfSelected = this.props.neighborsOfSelectedNode.includes(node.get('id'));
return node.set('focused', nodeSelected || isNeighborOfSelected);
}
nodeBlurredDecorator(node) {
const belongsToNetwork = this.props.selectedNetworkNodesIds.contains(node.get('id'));
const noMatches = this.props.searchNodeMatches.get(node.get('id'), makeMap()).isEmpty();
const notMatched = (this.props.searchQuery && !node.get('highlighted') && noMatches);
const notFocused = (this.props.selectedNodeId && !node.get('focused'));
const notInNetwork = (this.props.selectedNetwork && !belongsToNetwork);
return node.set('blurred', notMatched || notFocused || notInNetwork);
}
nodeMatchesDecorator(node) {
return node.set('matches', this.props.searchNodeMatches.get(node.get('id')));
}
nodeNetworksDecorator(node) {
return node.set('networks', this.props.nodeNetworks.get(node.get('id')));
}
nodeMetricDecorator(node) {
return node.set('metric', this.props.nodeMetric.get(node.get('id')));
}
nodeScaleDecorator(node) {
return node.set('scale', node.get('focused') ? this.props.selectedScale : 1);
}
edgeHighlightedDecorator(edge) {
return edge.set('highlighted', this.props.highlightedEdgeIds.has(edge.get('id')));
}
edgeFocusedDecorator(edge) {
const sourceSelected = (this.props.selectedNodeId === edge.get('source'));
const targetSelected = (this.props.selectedNodeId === edge.get('target'));
return edge.set('focused', this.props.hasSelectedNode && (sourceSelected || targetSelected));
}
edgeBlurredDecorator(edge) {
const { selectedNodeId, searchNodeMatches, selectedNetworkNodesIds } = this.props;
const sourceSelected = (selectedNodeId === edge.get('source'));
const targetSelected = (selectedNodeId === edge.get('target'));
const otherNodesSelected = this.props.hasSelectedNode && !sourceSelected && !targetSelected;
const sourceNoMatches = searchNodeMatches.get(edge.get('source'), makeMap()).isEmpty();
const targetNoMatches = searchNodeMatches.get(edge.get('target'), makeMap()).isEmpty();
const notMatched = this.props.searchQuery && (sourceNoMatches || targetNoMatches);
const sourceInNetwork = selectedNetworkNodesIds.contains(edge.get('source'));
const targetInNetwork = selectedNetworkNodesIds.contains(edge.get('target'));
const notInNetwork = this.props.selectedNetwork && (!sourceInNetwork || !targetInNetwork);
return edge.set('blurred', !edge.get('highlighted') && !edge.get('focused') &&
(otherNodesSelected || notMatched || notInNetwork));
}
edgeScaleDecorator(edge) {
return edge.set('scale', edge.get('focused') ? this.props.selectedScale : 1);
}
renderNode(node) {
const { isAnimated, contrastMode } = this.props;
return (
<NodeContainer
matches={node.get('matches')}
networks={node.get('networks')}
metric={node.get('metric')}
blurred={node.get('blurred')}
focused={node.get('focused')}
highlighted={node.get('highlighted')}
shape={node.get('shape')}
stack={node.get('stack')}
key={node.get('id')}
id={node.get('id')}
label={node.get('label')}
labelMinor={node.get('labelMinor')}
pseudo={node.get('pseudo')}
rank={node.get('rank')}
dx={node.get('x')}
dy={node.get('y')}
scale={node.get('scale')}
isAnimated={isAnimated}
contrastMode={contrastMode}
/>
);
}
renderEdge(edge) {
const { isAnimated } = this.props;
return (
<EdgeContainer
key={edge.get('id')}
id={edge.get('id')}
source={edge.get('source')}
target={edge.get('target')}
waypoints={edge.get('points')}
highlighted={edge.get('highlighted')}
focused={edge.get('focused')}
blurred={edge.get('blurred')}
scale={edge.get('scale')}
isAnimated={isAnimated}
/>
);
}
renderElement(element) {
// This heuristics is not ideal but it works.
return element.get('points') ? this.renderEdge(element) : this.renderNode(element);
}
render() {
const { layoutNodes, layoutEdges, selectedScale, isAnimated } = this.props;
const nodes = this.props.layoutNodes.toIndexedSeq()
.map(this.nodeHighlightedDecorator)
.map(this.nodeFocusedDecorator)
.map(this.nodeBlurredDecorator)
.map(this.nodeMatchesDecorator)
.map(this.nodeNetworksDecorator)
.map(this.nodeMetricDecorator)
.map(this.nodeScaleDecorator)
.groupBy(this.nodeDisplayLayer);
const edges = this.props.layoutEdges.toIndexedSeq()
.map(this.edgeHighlightedDecorator)
.map(this.edgeFocusedDecorator)
.map(this.edgeBlurredDecorator)
.map(this.edgeScaleDecorator)
.groupBy(this.edgeDisplayLayer);
// NOTE: The elements need to be arranged into a single array outside
// of DOM structure for React rendering engine to do smart rearrangements
// without unnecessary re-rendering of the elements themselves. So e.g.
// rendering the element layers individually below would be significantly slower.
const orderedElements = makeList([
edges.get(BLURRED_EDGES_LAYER, makeList()),
nodes.get(BLURRED_NODES_LAYER, makeList()),
edges.get(NORMAL_EDGES_LAYER, makeList()),
nodes.get(NORMAL_NODES_LAYER, makeList()),
edges.get(HIGHLIGHTED_EDGES_LAYER, makeList()),
nodes.get(HIGHLIGHTED_NODES_LAYER, makeList()),
edges.get(HOVERED_EDGES_LAYER, makeList()),
nodes.get(HOVERED_NODES_LAYER, makeList()),
]).flatten(true);
return (
<g className="nodes-chart-elements">
<NodesChartEdges
layoutEdges={layoutEdges}
selectedScale={selectedScale}
isAnimated={isAnimated} />
<NodesChartNodes
layoutNodes={layoutNodes}
selectedScale={selectedScale}
isAnimated={isAnimated} />
{orderedElements.map(this.renderElement)}
</g>
);
}
@@ -33,10 +239,24 @@ class NodesChartElements extends React.Component {
function mapStateToProps(state) {
return {
hasSelectedNode: hasSelectedNodeFn(state),
layoutNodes: layoutNodesSelector(state),
layoutEdges: layoutEdgesSelector(state),
selectedScale: selectedScaleSelector(state),
isAnimated: !graphExceedsComplexityThreshSelector(state),
highlightedNodeIds: highlightedNodeIdsSelector(state),
highlightedEdgeIds: highlightedEdgeIdsSelector(state),
selectedNetworkNodesIds: selectedNetworkNodesIdsSelector(state),
searchNodeMatches: searchNodeMatchesSelector(state),
neighborsOfSelectedNode: getAdjacentNodes(state),
nodeNetworks: nodeNetworksSelector(state),
nodeMetric: nodeMetricSelector(state),
selectedScale: selectedScaleSelector(state),
searchQuery: state.get('searchQuery'),
selectedNetwork: state.get('selectedNetwork'),
selectedNodeId: state.get('selectedNodeId'),
mouseOverNodeId: state.get('mouseOverNodeId'),
mouseOverEdgeId: state.get('mouseOverEdgeId'),
contrastMode: state.get('contrastMode'),
};
}
@@ -1,141 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import { Map as makeMap } from 'immutable';
import { nodeMetricSelector } from '../selectors/node-metric';
import { searchNodeMatchesSelector } from '../selectors/search';
import { highlightedNodeIdsSelector } from '../selectors/graph-view/decorators';
import { nodeNetworksSelector, selectedNetworkNodesIdsSelector } from '../selectors/node-networks';
import { getAdjacentNodes } from '../utils/topology-utils';
import NodeContainer from './node-container';
class NodesChartNodes extends React.Component {
constructor(props, context) {
super(props, context);
this.nodeDisplayLayer = this.nodeDisplayLayer.bind(this);
// Node decorators
// TODO: Consider moving some of these one level up (or even to global selectors) so that
// other components, like NodesChartEdges, could read more info directly from the nodes.
this.nodeHighlightedDecorator = this.nodeHighlightedDecorator.bind(this);
this.nodeFocusedDecorator = this.nodeFocusedDecorator.bind(this);
this.nodeBlurredDecorator = this.nodeBlurredDecorator.bind(this);
this.nodeMatchesDecorator = this.nodeMatchesDecorator.bind(this);
this.nodeNetworksDecorator = this.nodeNetworksDecorator.bind(this);
this.nodeMetricDecorator = this.nodeMetricDecorator.bind(this);
this.nodeScaleDecorator = this.nodeScaleDecorator.bind(this);
}
nodeHighlightedDecorator(node) {
const nodeSelected = (this.props.selectedNodeId === node.get('id'));
const nodeHighlighted = this.props.highlightedNodeIds.has(node.get('id'));
return node.set('highlighted', nodeHighlighted || nodeSelected);
}
nodeFocusedDecorator(node) {
const nodeSelected = (this.props.selectedNodeId === node.get('id'));
const isNeighborOfSelected = this.props.neighborsOfSelectedNode.includes(node.get('id'));
return node.set('focused', nodeSelected || isNeighborOfSelected);
}
nodeBlurredDecorator(node) {
const belongsToNetwork = this.props.selectedNetworkNodesIds.contains(node.get('id'));
const noMatches = this.props.searchNodeMatches.get(node.get('id'), makeMap()).isEmpty();
const notMatched = (this.props.searchQuery && !node.get('highlighted') && noMatches);
const notFocused = (this.props.selectedNodeId && !node.get('focused'));
const notInNetwork = (this.props.selectedNetwork && !belongsToNetwork);
return node.set('blurred', notMatched || notFocused || notInNetwork);
}
nodeMatchesDecorator(node) {
return node.set('matches', this.props.searchNodeMatches.get(node.get('id')));
}
nodeNetworksDecorator(node) {
return node.set('networks', this.props.nodeNetworks.get(node.get('id')));
}
nodeMetricDecorator(node) {
return node.set('metric', this.props.nodeMetric.get(node.get('id')));
}
nodeScaleDecorator(node) {
return node.set('scale', node.get('focused') ? this.props.selectedScale : 1);
}
// make sure blurred nodes are in the background
nodeDisplayLayer(node) {
if (node.get('id') === this.props.mouseOverNodeId) {
return 3;
}
if (node.get('blurred') && !node.get('focused')) {
return 0;
}
if (node.get('highlighted')) {
return 2;
}
return 1;
}
render() {
const { layoutNodes, isAnimated, contrastMode } = this.props;
const nodesToRender = layoutNodes.toIndexedSeq()
.map(this.nodeHighlightedDecorator)
.map(this.nodeFocusedDecorator)
.map(this.nodeBlurredDecorator)
.map(this.nodeMatchesDecorator)
.map(this.nodeNetworksDecorator)
.map(this.nodeMetricDecorator)
.map(this.nodeScaleDecorator)
.sortBy(this.nodeDisplayLayer);
return (
<g className="nodes-chart-nodes">
{nodesToRender.map(node => (
<NodeContainer
matches={node.get('matches')}
networks={node.get('networks')}
metric={node.get('metric')}
blurred={node.get('blurred')}
focused={node.get('focused')}
highlighted={node.get('highlighted')}
shape={node.get('shape')}
stack={node.get('stack')}
key={node.get('id')}
id={node.get('id')}
label={node.get('label')}
labelMinor={node.get('labelMinor')}
pseudo={node.get('pseudo')}
rank={node.get('rank')}
dx={node.get('x')}
dy={node.get('y')}
scale={node.get('scale')}
isAnimated={isAnimated}
contrastMode={contrastMode}
/>
))}
</g>
);
}
}
function mapStateToProps(state) {
return {
nodeMetric: nodeMetricSelector(state),
nodeNetworks: nodeNetworksSelector(state),
searchNodeMatches: searchNodeMatchesSelector(state),
selectedNetworkNodesIds: selectedNetworkNodesIdsSelector(state),
neighborsOfSelectedNode: getAdjacentNodes(state),
highlightedNodeIds: highlightedNodeIdsSelector(state),
mouseOverNodeId: state.get('mouseOverNodeId'),
selectedNetwork: state.get('selectedNetwork'),
selectedNodeId: state.get('selectedNodeId'),
searchQuery: state.get('searchQuery'),
contrastMode: state.get('contrastMode')
};
}
export default connect(
mapStateToProps,
)(NodesChartNodes);
+10
View File
@@ -6,3 +6,13 @@ export const EDGE_ID_SEPARATOR = '---';
export const GRAPH_VIEW_MODE = 'topo';
export const TABLE_VIEW_MODE = 'grid';
export const RESOURCE_VIEW_MODE = 'resource';
// Named constants to avoid typos that would result in hard-to-detect bugs.
export const BLURRED_EDGES_LAYER = 'blurred-edges';
export const BLURRED_NODES_LAYER = 'blurred-nodes';
export const NORMAL_EDGES_LAYER = 'normal-edges';
export const NORMAL_NODES_LAYER = 'normal-nodes';
export const HIGHLIGHTED_EDGES_LAYER = 'highlighted-edges';
export const HIGHLIGHTED_NODES_LAYER = 'highlighted-nodes';
export const HOVERED_EDGES_LAYER = 'hovered-edges';
export const HOVERED_NODES_LAYER = 'hovered-nodes';
+1 -15
View File
@@ -399,7 +399,7 @@
fill: $text-secondary-color;
}
.nodes-chart-nodes .node {
.nodes-chart-elements .node {
transition: opacity .2s $base-ease;
text-align: center;
@@ -500,10 +500,6 @@
opacity: $edge-opacity-blurred;
}
&.focused {
animation: focusing 1.5s ease-in-out;
}
.link {
fill: none;
stroke: $edge-color;
@@ -1662,16 +1658,6 @@
}
}
@keyframes focusing {
0% {
opacity: 0;
} 33% {
opacity: 0.2;
} 100% {
opacity: 1;
}
}
@keyframes blinking {
0%, 50%, 100% {
opacity: 1.0;
+1 -1
View File
@@ -21,7 +21,7 @@ function clickIfVisible(list, index) {
function selectNode(browser) {
debug('select node');
return browser.elementByCssSelector('.nodes-chart-nodes .node:nth-child(1) > g', function(err, el) {
return browser.elementByCssSelector('.nodes-chart-elements .node:nth-child(1) > g', function(err, el) {
return el.click();
});
}