123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- import React from 'react';
- import classNames from 'classnames';
- import { connect } from 'react-redux';
- import { fromJS, Map as makeMap, List as makeList } from 'immutable';
- import theme from 'weaveworks-ui-components/lib/theme';
- 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 { NODE_BASE_SIZE } from '../constants/styles';
- 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);
- this.state={
- nodeColor:'#5B8FF9'
- }
- }
- nodeDisplayLayer(node) {
- if (node.get('id') === this.props.mouseOverNodeId) {
- return HOVERED_NODES_LAYER;
- } if (node.get('blurred') && !node.get('focused')) {
- return BLURRED_NODES_LAYER;
- } 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;
- } if (edge.get('blurred') && !edge.get('focused')) {
- return BLURRED_EDGES_LAYER;
- } 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 } = this.props;
-
- // old versions of scope reports have a node shape of `storagesheet`
- // if so, normalise to `sheet`
- const shape = node.get('shape') === 'storagesheet' ? 'sheet' : node.get('shape');
- return (
- <NodeContainer
- matches={node.get('matches')}
- networks={node.get('networks')}
- metric={node.get('metric')}
- focused={node.get('focused')}
- highlighted={node.get('highlighted')}
- shape={shape}
- tag={node.get('tag')}
- stacked={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')}
- x={node.get('x')}
- y={node.get('y')}
- size={node.get('scale') * NODE_BASE_SIZE}
- isAnimated={isAnimated}
- color={node.get('color')}
- />
- );
- }
- 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')}
- scale={edge.get('scale')}
- isAnimated={isAnimated}
- />
- );
- }
- renderOverlay(element) {
- // NOTE: This piece of code is a bit hacky - as we can't set the absolute coords for the
- // SVG element, we set the zoom level high enough that we're sure it covers the screen.
- const className = classNames('nodes-chart-overlay', { active: element.get('isActive') });
- const scale = (this.props.selectedScale || 1) * 100000;
- return (
- <rect
- className={className}
- key="nodes-chart-overlay"
- transform={`scale(${scale})`}
- fill={theme.colors.purple25}
- x={-1}
- y={-1}
- width={2}
- height={2}
- />
- );
- }
- renderElement(element) {
- if (element.get('isOverlay')) {
- return this.renderOverlay(element);
- }
- // This heuristics is not ideal but it works.
- return element.get('points') ? this.renderEdge(element) : this.renderNode(element);
- }
- render() {
- 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()),
- fromJS([{ isActive: !!nodes.get(BLURRED_NODES_LAYER), isOverlay: true }]),
- 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="tour-step-anchor nodes-chart-elements">
- {orderedElements.map(this.renderElement)}
- </g>
- );
- }
- }
- function mapStateToProps(state) {
- return {
- contrastMode: state.get('contrastMode'),
- hasSelectedNode: hasSelectedNodeFn(state),
- highlightedEdgeIds: highlightedEdgeIdsSelector(state),
- highlightedNodeIds: highlightedNodeIdsSelector(state),
- isAnimated: !graphExceedsComplexityThreshSelector(state),
- layoutEdges: layoutEdgesSelector(state),
- layoutNodes: layoutNodesSelector(state),
- mouseOverEdgeId: state.get('mouseOverEdgeId'),
- mouseOverNodeId: state.get('mouseOverNodeId'),
- neighborsOfSelectedNode: getAdjacentNodes(state),
- nodeMetric: nodeMetricSelector(state),
- nodeNetworks: nodeNetworksSelector(state),
- searchNodeMatches: searchNodeMatchesSelector(state),
- searchQuery: state.get('searchQuery'),
- selectedNetwork: state.get('selectedNetwork'),
- selectedNetworkNodesIds: selectedNetworkNodesIdsSelector(state),
- selectedNodeId: state.get('selectedNodeId'),
- selectedScale: selectedScaleSelector(state),
- };
- }
- export default connect(mapStateToProps)(NodesChartElements);
|