Fix zooming and autoshift

* track panning to not trigger mouse up event
* offset angle for circle
* fixed panning/zooming
* center the center node if radius bigger than screen
This commit is contained in:
David Kaltschmidt
2015-09-21 11:44:26 +02:00
parent 808fc55832
commit a3597c9639

View File

@@ -21,7 +21,7 @@ const MARGINS = {
// make sure circular layouts a bit denser with 3-6 nodes
const radiusDensity = d3.scale.threshold()
.domain([3, 6]).range([3, 4, 3]);
.domain([3, 6]).range([2.5, 3.5, 3]);
const NodesChart = React.createClass({
@@ -30,7 +30,7 @@ const NodesChart = React.createClass({
nodes: {},
edges: {},
nodeScale: d3.scale.linear(),
translate: [0, 0],
shiftTranslate: [0, 0],
panTranslate: [0, 0],
scale: 1,
hasZoomed: false,
@@ -49,8 +49,7 @@ const NodesChart = React.createClass({
.scaleExtent([0.1, 2])
.on('zoom', this.zoomed);
d3.select('.canvas')
.on('click', this.handleBackgroundClick)
d3.select('.nodes-chart svg')
.call(this.zoom);
},
@@ -61,6 +60,7 @@ const NodesChart = React.createClass({
// wipe node states when showing different topology
if (nextProps.topologyId !== this.props.topologyId) {
_.assign(state, {
autoShifted: false,
nodes: {},
edges: {}
});
@@ -82,8 +82,7 @@ const NodesChart = React.createClass({
componentWillUnmount: function() {
// undoing .call(zoom)
d3.select('.canvas')
.on('click', null)
d3.select('.nodes-chart svg')
.on('mousedown.zoom', null)
.on('onwheel', null)
.on('onmousewheel', null)
@@ -159,7 +158,7 @@ const NodesChart = React.createClass({
// only animate shift behavior, not panning
const panTranslate = this.state.panTranslate;
const shiftTranslate = this.state.translate;
const shiftTranslate = this.state.shiftTranslate;
let translate = panTranslate;
let wasShifted = false;
if (shiftTranslate[0] !== panTranslate[0] || shiftTranslate[1] !== panTranslate[1]) {
@@ -175,7 +174,7 @@ const NodesChart = React.createClass({
<span className="nodes-chart-error-icon fa fa-ban" />
<div>Too many nodes to show in the browser.<br />We're working on it, but for now, try a different view?</div>
</div>
<svg width="100%" height="100%" className={svgClassNames}>
<svg width="100%" height="100%" className={svgClassNames} onMouseUp={this.handleMouseUp}>
<Spring endValue={{val: translate, config: [80, 20]}}>
{function(interpolated) {
let interpolatedTranslate = wasShifted ? interpolated.val : panTranslate;
@@ -287,9 +286,10 @@ const NodesChart = React.createClass({
const adjacentCount = adjacentLayoutNodes.length;
const density = radiusDensity(adjacentCount);
const radius = Math.min(props.width, props.height) / density;
const offsetAngle = Math.PI / 4;
_.each(adjacentLayoutNodes, function(node, i) {
const angle = Math.PI * 2 * i / adjacentCount;
const angle = offsetAngle + Math.PI * 2 * i / adjacentCount;
node.x = centerX + radius * Math.sin(angle);
node.y = centerY + radius * Math.cos(angle);
});
@@ -310,37 +310,43 @@ const NodesChart = React.createClass({
// shift canvas selected node out of view if it has not been shifted already
let autoShifted = this.state.autoShifted;
const translate = state.translate;
const shiftTranslate = state.shiftTranslate;
if (!autoShifted) {
const visibleWidth = Math.max(props.width - props.detailsWidth, 0);
const offsetX = translate[0];
const offsetX = shiftTranslate[0];
// normalize graph coordinates by zoomScale
const zoomScale = state.scale;
const outerRadius = radius + this.state.nodeScale(1.5);
if (offsetX + (centerX + outerRadius) * zoomScale > visibleWidth) {
if (2 * outerRadius * zoomScale > props.width) {
// radius too big, centering center node on canvas
shiftTranslate[0] = -(centerX * zoomScale - (props.width + MARGINS.left) / 2);
} else if (offsetX + (centerX + outerRadius) * zoomScale > visibleWidth) {
// shift left if blocked by details
const shift = (centerX + outerRadius) * zoomScale - visibleWidth;
translate[0] = -shift;
shiftTranslate[0] = -shift;
} else if (offsetX + (centerX - outerRadius) * zoomScale < 0) {
// shift right if off canvas
const shift = offsetX - offsetX + (centerX - outerRadius) * zoomScale;
translate[0] = -shift;
shiftTranslate[0] = -shift;
}
const offsetY = translate[1];
if (offsetY + (centerY + outerRadius) * zoomScale > props.height) {
const offsetY = shiftTranslate[1];
if (2 * outerRadius * zoomScale > props.height) {
// radius too big, centering center node on canvas
shiftTranslate[1] = -(centerY * zoomScale - (props.height + MARGINS.top) / 2);
} else if (offsetY + (centerY + outerRadius) * zoomScale > props.height) {
// shift up if past bottom
const shift = (centerY + outerRadius) * zoomScale - props.height;
translate[1] = -shift;
shiftTranslate[1] = -shift;
} else if (offsetY + (centerY - outerRadius) * zoomScale - props.topMargin < 0) {
// shift down if off canvas
const shift = offsetY - offsetY + (centerY - outerRadius) * zoomScale - props.topMargin;
translate[1] = -shift;
shiftTranslate[1] = -shift;
}
// debug('shift', centerX, centerY, outerRadius, translate);
// debug('shift', centerX, centerY, outerRadius, shiftTranslate);
// saving translate in d3's panning cache
this.zoom.translate(translate);
this.zoom.translate(shiftTranslate);
autoShifted = true;
}
@@ -348,16 +354,22 @@ const NodesChart = React.createClass({
autoShifted: autoShifted,
edges: layoutEdges,
nodes: layoutNodes,
translate: translate
shiftTranslate: shiftTranslate
};
},
handleBackgroundClick: function() {
AppActions.clickCloseDetails();
// allow shifts again
this.setState({
autoShifted: false
});
isZooming: false, // distinguish pan/zoom from click
handleMouseUp: function() {
if (!this.isZooming) {
AppActions.clickCloseDetails();
// allow shifts again
this.setState({
autoShifted: false
});
} else {
this.isZooming = false;
}
},
restoreLayout: function(state) {
@@ -443,11 +455,12 @@ const NodesChart = React.createClass({
zoomed: function() {
// debug('zoomed', d3.event.scale, d3.event.translate);
this.isZooming = true;
this.setState({
autoShifted: false,
hasZoomed: true,
panTranslate: d3.event.translate.slice(),
translate: d3.event.translate.slice(),
shiftTranslate: d3.event.translate.slice(),
scale: d3.event.scale
});
}