Merge pull request #501 from weaveworks/495-cover-nodes

Dont cover other nodes on radial layout
This commit is contained in:
David
2015-09-18 18:07:00 +02:00
2 changed files with 72 additions and 44 deletions

View File

@@ -12,16 +12,23 @@ const Node = React.createClass({
render: function() {
const props = this.props;
const scale = this.props.scale;
const textOffsetX = 0;
const textOffsetY = scale(0.5) + 18;
let scaleFactor = 1;
if (props.focused) {
scaleFactor = 1.25;
} else if (props.blurred) {
scaleFactor = 0.75;
}
const labelOffsetY = 18;
const subLabelOffsetY = labelOffsetY + 17;
const isPseudo = !!this.props.pseudo;
const color = isPseudo ? '' : this.getNodeColor(this.props.rank);
const onClick = this.props.onClick;
const onMouseEnter = this.handleMouseEnter;
const onMouseLeave = this.handleMouseLeave;
const classNames = ['node'];
const ellipsis = this.ellipsis;
const animConfig = [80, 20]; // stiffness, bounce
const label = this.ellipsis(props.label, 14, scale(4 * scaleFactor));
const subLabel = this.ellipsis(props.subLabel, 12, scale(4 * scaleFactor));
if (this.props.highlighted) {
classNames.push('highlighted');
@@ -35,21 +42,25 @@ const Node = React.createClass({
const classes = classNames.join(' ');
return (
<Spring endValue={{x: {val: this.props.dx, config: animConfig}, y: {val: this.props.dy, config: animConfig}}}>
<Spring endValue={{
x: {val: this.props.dx, config: animConfig},
y: {val: this.props.dy, config: animConfig},
f: {val: scaleFactor, config: animConfig}
}}>
{function(interpolated) {
const transform = 'translate(' + interpolated.x.val + ',' + interpolated.y.val + ')';
const transform = `translate(${interpolated.x.val},${interpolated.y.val})`;
return (
<g className={classes} transform={transform} id={props.id}
onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
{props.highlighted && <circle r={scale(0.7)} className="highlighted"></circle>}
<circle r={scale(0.5)} className="border" stroke={color}></circle>
<circle r={scale(0.45)} className="shadow"></circle>
<circle r={Math.max(2, scale(0.125))} className="node"></circle>
<text className="node-label" textAnchor="middle" x={textOffsetX} y={textOffsetY}>
{ellipsis(props.label, 14)}
{props.highlighted && <circle r={scale(0.7 * interpolated.f.val)} className="highlighted"></circle>}
<circle r={scale(0.5 * interpolated.f.val)} className="border" stroke={color}></circle>
<circle r={scale(0.45 * interpolated.f.val)} className="shadow"></circle>
<circle r={Math.max(2, scale(0.125 * interpolated.f.val))} className="node"></circle>
<text className="node-label" textAnchor="middle" x="0" y={labelOffsetY + scale(0.5 * interpolated.f.val)}>
{label}
</text>
<text className="node-sublabel" textAnchor="middle" x={textOffsetX} y={textOffsetY + 17}>
{ellipsis(props.subLabel, 12)}
<text className="node-sublabel" textAnchor="middle" x="0" y={subLabelOffsetY + scale(0.5 * interpolated.f.val)}>
{subLabel}
</text>
</g>
);
@@ -58,15 +69,12 @@ const Node = React.createClass({
);
},
ellipsis: function(text, fontSize) {
const maxWidth = this.props.scale(4);
ellipsis: function(text, fontSize, maxWidth) {
const averageCharLength = fontSize / 1.5;
const allowedChars = maxWidth / averageCharLength;
let truncatedText = text;
let trimmedText = text;
while (text && trimmedText.length > 1 && trimmedText.length > allowedChars) {
trimmedText = trimmedText.slice(0, -1);
truncatedText = trimmedText + '...';
if (text && text.length > allowedChars) {
truncatedText = text.slice(0, allowedChars) + '...';
}
return truncatedText;
},

View File

@@ -21,7 +21,7 @@ const MARGINS = {
// make sure circular layouts lots of nodes spread out
const radiusDensity = d3.scale.sqrt()
.domain([12, 2]).range([2.5, 5]).clamp(true);
.domain([12, 2]).range([3, 4]).clamp(true);
const NodesChart = React.createClass({
@@ -105,30 +105,46 @@ const NodesChart = React.createClass({
renderGraphNodes: function(nodes, scale) {
const hasSelectedNode = this.props.selectedNodeId && this.props.nodes.has(this.props.selectedNodeId);
const adjacency = hasSelectedNode ? AppStore.getAdjacentNodes(this.props.selectedNodeId) : null;
return _.map(nodes, function(node) {
const highlighted = _.includes(this.props.highlightedNodeIds, node.id)
|| this.props.selectedNodeId === node.id;
const blurred = hasSelectedNode
&& this.props.selectedNodeId !== node.id
&& !adjacency.includes(node.id);
const onNodeClick = this.props.onNodeClick;
return (
<Node
blurred={blurred}
highlighted={highlighted}
onClick={this.props.onNodeClick}
key={node.id}
id={node.id}
label={node.label}
pseudo={node.pseudo}
subLabel={node.subLabel}
rank={node.rank}
scale={scale}
dx={node.x}
dy={node.y}
/>
);
_.each(nodes, function(node) {
node.highlighted = _.includes(this.props.highlightedNodeIds, node.id)
|| this.props.selectedNodeId === node.id;
node.focused = hasSelectedNode
&& (this.props.selectedNodeId === node.id || adjacency.includes(node.id));
node.blurred = hasSelectedNode && !node.focused;
}, this);
return _.chain(nodes)
.sortBy(function(node) {
if (node.blurred) {
return 0;
}
if (node.highlighted) {
return 2;
}
return 1;
})
.map(function(node) {
return (
<Node
blurred={node.blurred}
focused={node.focused}
highlighted={node.highlighted}
onClick={onNodeClick}
key={node.id}
id={node.id}
label={node.label}
pseudo={node.pseudo}
subLabel={node.subLabel}
rank={node.rank}
scale={scale}
dx={node.x}
dy={node.y}
/>
);
})
.value();
},
renderGraphEdges: function(edges) {
@@ -150,7 +166,7 @@ const NodesChart = React.createClass({
render: function() {
const nodeElements = this.renderGraphNodes(this.state.nodes, this.state.nodeScale);
const edgeElements = this.renderGraphEdges(this.state.edges, this.state.nodeScale);
const scale = this.state.scale;
let scale = this.state.scale;
// only animate shift behavior, not panning
const panTranslate = this.state.panTranslate;
@@ -268,8 +284,12 @@ const NodesChart = React.createClass({
adjacentLayoutNodes.push(layoutNodes[adjacentId]);
});
// circle layout for adjacent nodes
// shift center node a bit
const nodeScale = state.nodeScale;
selectedLayoutNode.x = selectedLayoutNode.px + nodeScale(1);
selectedLayoutNode.y = selectedLayoutNode.py + nodeScale(1);
// circle layout for adjacent nodes
const centerX = selectedLayoutNode.x;
const centerY = selectedLayoutNode.y;
const adjacentCount = adjacentLayoutNodes.length;