mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-28 01:31:17 +00:00
Fix node blurring for network hover
Fixes edge focus/blurring when selecting networks Configurable arc size net-view variant: Smaller arc (just the top) net-view revision: Add white container around arcs. To join them together in a sense. Trying to avoid the edge/arc position association. Not sure if this really helps net-view variations: shadows. bg fill, align network rotation w/ rank. Cute little dots Stacked lines Rounded rects Slightly thicker "pills" to repr networks Handle edge case by making line longer Fix network bar offset
This commit is contained in:
committed by
Simon Howe
parent
478a4a6d66
commit
b1cd16d92d
@@ -5,41 +5,46 @@ import { getNodeColor } from '../utils/color-utils';
|
||||
import { isContrastMode } from '../utils/contrast-utils';
|
||||
|
||||
|
||||
const h = 5;
|
||||
const padding = 0.05;
|
||||
const offset = Math.PI;
|
||||
const arc = d3.svg.arc()
|
||||
.startAngle(d => d.startAngle + offset)
|
||||
.endAngle(d => d.endAngle + offset);
|
||||
const arcScale = d3.scale.linear()
|
||||
.range([Math.PI * 0.25 + padding, Math.PI * 1.75 - padding]);
|
||||
|
||||
const rx = 1;
|
||||
const ry = rx;
|
||||
const labelOffset = 38;
|
||||
|
||||
function NodeNetworksOverlay({size, stack, networks = makeList()}) {
|
||||
arcScale.domain([0, networks.size]);
|
||||
const radius = size * 0.9;
|
||||
const r = size * 0.5;
|
||||
const offset = r + labelOffset;
|
||||
const w = Math.max(size, (size / 4) * networks.size);
|
||||
const x = d3.scale.ordinal()
|
||||
.domain(networks.map((n, i) => i).toJS())
|
||||
.rangeBands([w * -0.5, w * 0.5], padding, 0);
|
||||
|
||||
const paths = networks.map((n, i) => {
|
||||
const d = arc({
|
||||
padAngle: 0.05,
|
||||
innerRadius: radius,
|
||||
outerRadius: radius + 4,
|
||||
startAngle: arcScale(i),
|
||||
endAngle: arcScale(i + 1)
|
||||
});
|
||||
|
||||
return (<path d={d} style={{fill: getNodeColor(n)}} key={n} />);
|
||||
});
|
||||
const bars = networks.map((n, i) => (
|
||||
<rect
|
||||
x={x(i)}
|
||||
y={offset}
|
||||
width={x.rangeBand()}
|
||||
height={h}
|
||||
rx={rx}
|
||||
ry={ry}
|
||||
className="node-network"
|
||||
style={{
|
||||
fill: getNodeColor(n.get('colorKey'))
|
||||
}}
|
||||
key={n.get('id')}
|
||||
/>
|
||||
));
|
||||
|
||||
let transform = '';
|
||||
if (stack) {
|
||||
const contrastMode = isContrastMode();
|
||||
const [dx, dy] = contrastMode ? [0, 8] : [0, 5];
|
||||
const [dx, dy] = contrastMode ? [0, 8] : [0, 0];
|
||||
transform = `translate(${dx}, ${dy * -1.5})`;
|
||||
}
|
||||
|
||||
return (
|
||||
<g transform={transform}>
|
||||
{paths.toJS()}
|
||||
{bars.toJS()}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,9 +107,6 @@ class Node extends React.Component {
|
||||
<g className={nodeClassName} transform={transform}
|
||||
onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
|
||||
{showingNetworks && <NodeNetworksOverlay size={size} networks={networks}
|
||||
stack={stack} />}
|
||||
|
||||
{useSvgLabels ?
|
||||
|
||||
svgLabels(label, subLabel, labelClassName, subLabelClassName, labelOffsetY) :
|
||||
@@ -133,6 +130,9 @@ class Node extends React.Component {
|
||||
color={color}
|
||||
{...this.props} />
|
||||
</g>
|
||||
|
||||
{showingNetworks && <NodeNetworksOverlay id={this.props.id} size={size}
|
||||
networks={networks} stack={stack} />}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Map as makeMap } from 'immutable';
|
||||
import { Map as makeMap, List as makeList } from 'immutable';
|
||||
|
||||
import { hasSelectedNode as hasSelectedNodeFn } from '../utils/topology-utils';
|
||||
import EdgeContainer from './edge-container';
|
||||
@@ -9,7 +9,7 @@ class NodesChartEdges extends React.Component {
|
||||
render() {
|
||||
const { hasSelectedNode, highlightedEdgeIds, layoutEdges,
|
||||
layoutPrecision, searchNodeMatches = makeMap(), searchQuery,
|
||||
selectedNodeId } = this.props;
|
||||
selectedNodeId, selectedNetwork, selectedNetworkNodes } = this.props;
|
||||
|
||||
return (
|
||||
<g className="nodes-chart-edges">
|
||||
@@ -18,10 +18,16 @@ class NodesChartEdges extends React.Component {
|
||||
const targetSelected = selectedNodeId === edge.get('target');
|
||||
const highlighted = highlightedEdgeIds.has(edge.get('id'));
|
||||
const focused = hasSelectedNode && (sourceSelected || targetSelected);
|
||||
const blurred = !(highlightedEdgeIds.size > 0 && highlighted)
|
||||
&& ((hasSelectedNode && !sourceSelected && !targetSelected)
|
||||
|| !focused && searchQuery && !(searchNodeMatches.has(edge.get('source'))
|
||||
&& searchNodeMatches.has(edge.get('target'))));
|
||||
const otherNodesSelected = hasSelectedNode && !sourceSelected && !targetSelected;
|
||||
const noMatches = searchQuery &&
|
||||
!(searchNodeMatches.has(edge.get('source')) &&
|
||||
searchNodeMatches.has(edge.get('target')));
|
||||
const noSelectedNetworks = selectedNetwork &&
|
||||
!(selectedNetworkNodes.contains(edge.get('source')) &&
|
||||
selectedNetworkNodes.contains(edge.get('target')));
|
||||
const blurred = !highlighted && (otherNodesSelected ||
|
||||
!focused && noMatches ||
|
||||
!focused && noSelectedNetworks);
|
||||
|
||||
return (
|
||||
<EdgeContainer
|
||||
@@ -49,7 +55,9 @@ function mapStateToProps(state) {
|
||||
highlightedEdgeIds: state.get('highlightedEdgeIds'),
|
||||
searchNodeMatches: state.getIn(['searchNodeMatches', currentTopologyId]),
|
||||
searchQuery: state.get('searchQuery'),
|
||||
selectedNodeId: state.get('selectedNodeId')
|
||||
selectedNetwork: state.get('selectedNetwork'),
|
||||
selectedNetworkNodes: state.get('networkNodes').get(state.get('selectedNetwork'), makeList()),
|
||||
selectedNodeId: state.get('selectedNodeId'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ class NodesChartNodes extends React.Component {
|
||||
selectedNodeId && !node.get('focused')
|
||||
|| searchQuery && !searchNodeMatches.has(node.get('id'))
|
||||
&& !node.get('highlighted')
|
||||
|| selectedNetwork && !(node.get('networks') || makeList()).contains(selectedNetwork));
|
||||
|| selectedNetwork
|
||||
&& !(node.get('networks') || makeList()).find(n => n.get('id') === selectedNetwork));
|
||||
|
||||
// make sure blurred nodes are in the background
|
||||
const sortNodes = node => {
|
||||
|
||||
@@ -17,7 +17,9 @@ const SHAPES = ['square', 'hexagon', 'heptagon', 'circle'];
|
||||
const NODE_COUNTS = [1, 2, 3];
|
||||
const STACK_VARIANTS = [false, true];
|
||||
const METRIC_FILLS = [0, 0.1, 50, 99.9, 100];
|
||||
const NETWORKS = ['be', 'fe', 'lb', 'db'];
|
||||
const NETWORKS = [
|
||||
'be', 'fe', 'zb', 'db', 're', 'gh', 'jk', 'lol', 'nw'
|
||||
].map(n => ({id: n, label: n, colorKey: n}));
|
||||
|
||||
const LOREM = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
||||
@@ -45,7 +47,7 @@ const LABEL_PREFIXES = _.range('A'.charCodeAt(), 'Z'.charCodeAt() + 1)
|
||||
|
||||
const deltaAdd = (
|
||||
name, adjacency = [], shape = 'circle', stack = false, nodeCount = 1,
|
||||
networks = ['fe', 'be']
|
||||
networks = NETWORKS
|
||||
) => ({
|
||||
adjacency,
|
||||
controls: {},
|
||||
@@ -176,7 +178,7 @@ class DebugToolbar extends React.Component {
|
||||
_.sample(SHAPES),
|
||||
_.sample(STACK_VARIANTS),
|
||||
_.sample(NODE_COUNTS),
|
||||
sample(NETWORKS, 3)
|
||||
sample(NETWORKS, 10)
|
||||
))
|
||||
}));
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ export const initialState = makeMap({
|
||||
hostname: '...',
|
||||
mouseOverEdgeId: null,
|
||||
mouseOverNodeId: null,
|
||||
networkNodes: makeMap(),
|
||||
nodeDetails: makeOrderedMap(), // nodeId -> details
|
||||
nodes: makeOrderedMap(), // nodeId -> node
|
||||
// nodes cache, infrequently updated, used for search
|
||||
@@ -81,6 +82,24 @@ function processTopologies(state, nextTopologies) {
|
||||
return state.mergeDeepIn(['topologies'], immNextTopologies);
|
||||
}
|
||||
|
||||
function getNetworkNodes(nodes) {
|
||||
const networks = {};
|
||||
nodes.forEach(node => (node.get('networks') || makeList()).forEach(n => {
|
||||
const networkId = n.get('id');
|
||||
networks[networkId] = (networks[networkId] || []).concat([node.get('id')]);
|
||||
}));
|
||||
return fromJS(networks);
|
||||
}
|
||||
|
||||
function getAvailableNetworks(nodes) {
|
||||
return nodes
|
||||
.valueSeq()
|
||||
.flatMap(node => node.get('networks') || makeList())
|
||||
.toSet()
|
||||
.toList()
|
||||
.sortBy(m => m.get('label'));
|
||||
}
|
||||
|
||||
function setTopology(state, topologyId) {
|
||||
state = state.set('currentTopology', findTopologyById(
|
||||
state.get('topologies'), topologyId));
|
||||
@@ -526,19 +545,15 @@ export function rootReducer(state = initialState, action) {
|
||||
const networks = node.get('metadata')
|
||||
.find(field => field.get('id') === 'docker_container_networks');
|
||||
if (networks) {
|
||||
return node.set('networks', fromJS(networks.get('value').split(', ')));
|
||||
return node.set('networks', fromJS(
|
||||
networks.get('value').split(', ').map(n => ({id: n, label: n, colorKey: n}))));
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}));
|
||||
|
||||
state = state.set('availableNetworks', state.get('nodes')
|
||||
.valueSeq()
|
||||
.flatMap(node => node.get('networks') || makeList())
|
||||
.toSet()
|
||||
.toList()
|
||||
.sort()
|
||||
.map(n => makeMap({id: n, label: n})));
|
||||
state = state.set('networkNodes', getNetworkNodes(state.get('nodes')));
|
||||
state = state.set('availableNetworks', getAvailableNetworks(state.get('nodes')));
|
||||
|
||||
// optimize color coding for networks
|
||||
const networkPrefix = longestCommonPrefix(state.get('availableNetworks')
|
||||
@@ -547,7 +562,8 @@ export function rootReducer(state = initialState, action) {
|
||||
if (networkPrefix) {
|
||||
state = state.update('nodes',
|
||||
nodes => nodes.map(node => node.update('networks',
|
||||
networks => networks.map(n => n.substr(networkPrefix.length)))));
|
||||
networks => networks.map(n => n.set('colorKey',
|
||||
n.get('colorKey').substr(networkPrefix.length))))));
|
||||
|
||||
state = state.update('availableNetworks',
|
||||
networks => networks.map(network => network
|
||||
@@ -642,6 +658,7 @@ export function rootReducer(state = initialState, action) {
|
||||
}
|
||||
if (action.state.pinnedNetwork) {
|
||||
state = state.set('pinnedNetwork', action.state.pinnedNetwork);
|
||||
state = state.set('selectedNetwork', action.state.pinnedNetwork);
|
||||
}
|
||||
if (action.state.controlPipe) {
|
||||
state = state.set('controlPipes', makeOrderedMap({
|
||||
|
||||
@@ -341,9 +341,9 @@ h2 {
|
||||
transition: opacity .5s @base-ease;
|
||||
text-align: center;
|
||||
|
||||
.node-networks-overlay {
|
||||
fill: none;
|
||||
stroke: @background-darker-secondary-color;
|
||||
.node-network {
|
||||
// stroke: @background-lighter-color;
|
||||
// stroke-width: 4px;
|
||||
}
|
||||
|
||||
.node-label,
|
||||
|
||||
Reference in New Issue
Block a user