Skip to content

Commit

Permalink
Addition of directional zoom + pan via mouse drag (#421)
Browse files Browse the repository at this point in the history
Summary:
Added 2 features to the ImagePane Component.
The first one allows to zoom on a particular area of the image (while keeping this area in the center of the visible part)
The second allows to pan the image with the mouse drag action.
Most of the changes are in ImagePane.js. Only two lines of css needed to be changed to allow zooming to work properly.

Image interaction was quite poor so far : zoom and pan were not very user-friendly.
#308 introduced image pan, but it used onWheel event, which did not allow for horizontal pan on desktop computers.
In addition, it was not possible to zoom in a particular area of the  image.

I tested my changes by compiling the js code, running a visdom server, and displaying a saved visdom environment which included a visdom.image call.

I am using a line of javascript which might not be very clean :
`
this._paneRef._windowRef.children[1]
` at line 71 of file ImagePane.js, to get the top and left position of the Pane. There may be a cleaner way to get these but I couldn't find. As it uses some "hidden" attributes, it may not be very robust to future changes of React API.

My changes should not modify existing features, apart from the zooming and panning speed that I increased in the handleZoom function (feel free to reset these constants to their previous value).

I tried to run python example/demo.py but it failed with ```ConnectionRefusedError: [Errno 111] Connection refused```, both with and without my changes (after installing from sources).

Not appropriate

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [x] My code follows the code style of this project.
- [x] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [x] For JavaScript changes, I have re-generated the minified JavaScript code.
Pull Request resolved: #421

Differential Revision: D8896258

Pulled By: JackUrb

fbshipit-source-id: bcdcef3d621c99a3300ecac4246ba06cdd57a397
  • Loading branch information
clement-masson authored and facebook-github-bot committed Jul 18, 2018
1 parent b72b5c0 commit 52c453a
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 30 deletions.
51 changes: 42 additions & 9 deletions js/ImagePane.js
Expand Up @@ -19,15 +19,18 @@ class ImagePane extends React.Component {
ty: 0.,
}

onEvent = (e) => {
drag_start_x = null;
drag_start_y = null;

onEvent = (event) => {
if( !this.props.isFocused ) {
return;
}

switch(e.type) {
switch(event.type) {
case 'keydown':
case 'keypress':
e.preventDefault();
event.preventDefault();
break;
case 'keyup':
this.props.appApi.sendPaneMessage(
Expand Down Expand Up @@ -59,18 +62,47 @@ class ImagePane extends React.Component {
if(ev.altKey) {
//var direction = natural.checked ? -1 : 1;
var direction = -1;
this.setState({tx: this.state.tx + ev.deltaX * direction});
this.setState({ty: this.state.ty + ev.deltaY * direction});
this.setState({tx: this.state.tx + ev.deltaX * direction*50});
this.setState({ty: this.state.ty + ev.deltaY * direction*50});
ev.stopPropagation();
ev.preventDefault();
} else if (ev.ctrlKey) {
var s = Math.exp(-ev.deltaY/100);
this.setState({scale: this.state.scale * s});
// get the x and y offset of the pane
var rect = this._paneRef._windowRef.children[1].getBoundingClientRect();
// Compute the coords of the mouse relative to the top left of the pane
var xscreen = ev.clientX - rect.x;
var yscreen = ev.clientY - rect.y;
// Compute the coords of the pixel under the mouse wrt the image top left
var ximage = (xscreen - this.state.tx) / this.state.scale;
var yimage = (yscreen - this.state.ty) / this.state.scale;
var new_scale = this.state.scale * Math.exp(-ev.deltaY/25);
// Update the state.
// The offset is modifed such that the pixel under the mouse
// is the same after zooming
this.setState({
scale: new_scale,
tx: xscreen - new_scale*ximage,
ty: yscreen - new_scale*yimage
});
ev.stopPropagation();
ev.preventDefault();
}
}

handleDragStart = (ev) => {
this.drag_start_x = ev.screenX;
this.drag_start_y = ev.screenY;
}

handleDragOver = (ev) => {
this.setState({
tx: this.state.tx + ev.screenX - this.drag_start_x,
ty: this.state.ty + ev.screenY - this.drag_start_y,
});
this.drag_start_x = ev.screenX;
this.drag_start_y = ev.screenY;
}

handleReset = () => {
this.setState({
scale: 1.,
Expand All @@ -84,8 +116,7 @@ class ImagePane extends React.Component {
const divstyle = {
left: this.state.tx,
top: this.state.ty,
position: "relative",
display: "block",
position: "absolute",
};
return (
<Pane
Expand All @@ -101,6 +132,8 @@ class ImagePane extends React.Component {
width={Math.ceil(1 + this.props.width * this.state.scale) + "px"}
height={Math.ceil(1 + this.props.height * this.state.scale) + "px"}
onDoubleClick={this.handleReset.bind(this)}
onDragStart={this.handleDragStart.bind(this)}
onDragOver={this.handleDragOver.bind(this)}
/>
</div>
<p className="caption">{content.caption}</p>
Expand Down
2 changes: 2 additions & 0 deletions py/visdom/static/css/style.css
Expand Up @@ -125,6 +125,8 @@ button {
.content {
width: 100%;
height: calc( 100% - 14px );
position: absolute;
overflow: hidden;
}

.grip {
Expand Down
42 changes: 21 additions & 21 deletions py/visdom/static/js/main.js

Large diffs are not rendered by default.

0 comments on commit 52c453a

Please sign in to comment.