diff --git a/package-lock.json b/package-lock.json index 07cfe6222..26d94e52b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "3dmol", - "version": "2.0.3", + "version": "2.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "3dmol", - "version": "2.0.3", + "version": "2.0.4", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { diff --git a/py3Dmol/py3Dmol/__init__.py b/py3Dmol/py3Dmol/__init__.py index 02260e935..2b8d23586 100644 --- a/py3Dmol/py3Dmol/__init__.py +++ b/py3Dmol/py3Dmol/__init__.py @@ -54,7 +54,7 @@ class view(object): the exception that the functions all return None. http://3dmol.org/doc/GLViewer.html ''' - def __init__(self,query='',width=640,height=480,viewergrid=None,data=None,style=None,linked=True,options=dict(),js='https://cdnjs.cloudflare.com/ajax/libs/3Dmol/2.0.3/3Dmol-min.js'): + def __init__(self,query='',width=640,height=480,viewergrid=None,data=None,style=None,linked=True,options=dict(),js='https://cdnjs.cloudflare.com/ajax/libs/3Dmol/2.0.4/3Dmol-min.js'): '''Create a 3Dmol.js view. width -- width in pixels of container height -- height in pixels of container diff --git a/py3Dmol/setup.py b/py3Dmol/setup.py index 223755180..252e0e51b 100644 --- a/py3Dmol/setup.py +++ b/py3Dmol/setup.py @@ -24,7 +24,7 @@ # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html # Keep version in synce with 3Dmol.js version. Use "postX" suffix if needed - version='2.0.3', + version='2.0.4', description='An IPython interface for embedding 3Dmol.js views in Jupyter notebooks', long_description=long_description, diff --git a/src/GLViewer.ts b/src/GLViewer.ts index a2356412e..809f7fc70 100644 --- a/src/GLViewer.ts +++ b/src/GLViewer.ts @@ -466,7 +466,7 @@ export class GLViewer { //for grid viewers, return true if point is in this viewer private isInViewer(x: number, y: number) { - if (this.viewers != undefined && !this.control_all) { + if (this.viewers != undefined) { var width = this.WIDTH / this.cols; var height = this.HEIGHT / this.rows; var offset = this.canvasOffset(); @@ -590,8 +590,8 @@ export class GLViewer { this.setupRenderer(); - this.row = this.config.row; - this.col = this.config.col; + this.row = this.config.row == undefined ? 0 : this.config.row; + this.col = this.config.col == undefined ? 0 : this.config.col; this.cols = this.config.cols; this.rows = this.config.rows; this.viewers = this.config.viewers; @@ -670,14 +670,23 @@ export class GLViewer { returnsingle = true; } + let ratioX = this.renderer.getXRatio(); + let ratioY = this.renderer.getYRatio(); + + let col = this.col; + let row = this.row; + let viewxoff = col*(this.WIDTH/ratioX); + //row is from bottom + let viewyoff = (ratioY-row-1)*(this.HEIGHT/ratioY); + let results = []; let offset = this.canvasOffset(); coords.forEach(coord => { let t = new Vector3(coord.x, coord.y, coord.z); t.applyMatrix4(this.modelGroup.matrixWorld); this.projector.projectVector(t, this.camera); - let screenX = this.WIDTH * (t.x + 1) / 2.0 + offset.left; - let screenY = -this.HEIGHT * (t.y - 1) / 2.0 + offset.top; + let screenX = (this.WIDTH/ratioX) * (t.x + 1) / 2.0 + offset.left + viewxoff; + let screenY = -(this.HEIGHT/ratioY) * (t.y - 1) / 2.0 + offset.top + viewyoff; results.push({ x: screenX, y: screenY }); }); if (returnsingle) results = results[0]; @@ -902,11 +911,9 @@ export class GLViewer { if (this.isDragging && this.scene) { //saw mousedown, haven't moved var x = this.getX(ev); var y = this.getY(ev); - if (this.closeEnoughForClick(ev)) { - var offset = this.canvasOffset(); - var mouseX = ((x - offset.left) / this.WIDTH) * 2 - 1; - var mouseY = -((y - offset.top) / this.HEIGHT) * 2 + 1; - this.handleClickSelection(mouseX, mouseY, ev); + if (this.closeEnoughForClick(ev) && this.isInViewer(x,y)) { + let mouse = this.mouseXY(x,y); + this.handleClickSelection(mouse.x, mouse.y, ev); } } @@ -922,7 +929,7 @@ export class GLViewer { var y = this.getY(ev); if (x === undefined) return; - if (!this.isInViewer(x, y)) { + if (!this.control_all && !this.isInViewer(x, y)) { return; } @@ -1026,43 +1033,64 @@ export class GLViewer { this.hoverDuration = duration; }; + private mouseXY(x,y) { + //convert to -1..1 coordinates + let offset = this.canvasOffset(); + let ratioX = this.renderer.getXRatio(); + let ratioY = this.renderer.getYRatio(); + + let col = this.col; + let row = this.row; + let viewxoff = col*(this.WIDTH/ratioX); + //row is from bottom + let viewyoff = (ratioY-row-1)*(this.HEIGHT/ratioY); + + let mouseX = ((x - offset.left-viewxoff) / (this.WIDTH/ratioX)) * 2 - 1; + let mouseY = -((y - offset.top - viewyoff) / (this.HEIGHT/ratioY)) * 2 + 1; + + return {x: mouseX, y: mouseY}; + } + public _handleMouseMove(ev) { // touchmove clearTimeout(this.hoverTimeout); - var offset = this.canvasOffset(); - var mouseX = ((this.getX(ev) - offset.left) / this.WIDTH) * 2 - 1; - var mouseY = -((this.getY(ev) - offset.top) / this.HEIGHT) * 2 + 1; + ev.preventDefault(); + + + let x = this.getX(ev); + let y = this.getY(ev); + if (x === undefined) + return; + + let ratioX = this.renderer.getXRatio(); + let ratioY = this.renderer.getYRatio(); + + let mouse = this.mouseXY(x,y); + let self = this; // hover timeout if (this.current_hover !== null) { - this.handleHoverContinue(mouseX, mouseY); + this.handleHoverContinue(mouse.x, mouse.y); + } + + var mode = 0; + if (!this.control_all && !this.isInViewer(x, y)) { + return; } + if (!this.scene) + return; + if (this.hoverables.length > 0) { this.hoverTimeout = setTimeout( function () { - self.handleHoverSelection(mouseX, mouseY, ev); + self.handleHoverSelection(mouse.x, mouse.y, ev); }, this.hoverDuration); } - ev.preventDefault(); - if (!this.scene) - return; if (!this.isDragging) return; - var mode = 0; - - var x = this.getX(ev); - var y = this.getY(ev); - if (x === undefined) - return; - - if (!this.isInViewer(x, y)) { - return; - } - - var dx = (x - this.mouseStartX) / this.WIDTH; var dy = (y - this.mouseStartY) / this.HEIGHT; // check for pinch @@ -1078,8 +1106,7 @@ export class GLViewer { // translate mode = 1; } - var ratioX = this.renderer.getXRatio(); - var ratioY = this.renderer.getYRatio(); + dx *= ratioX; dy *= ratioY; var r = Math.hypot(dx, dy); @@ -1127,8 +1154,10 @@ export class GLViewer { var x = this.mouseStartX; var y = this.mouseStartY; var offset = this.canvasOffset(); - var mouseX = ((x - offset.left) / this.WIDTH) * 2 - 1; - var mouseY = -((y - offset.top) / this.HEIGHT) * 2 + 1; + let mouse = this.mouseXY(x,y); + let mouseX = mouse.x; + let mouseY = mouse.y; + let intersects = this.targetedObjects(mouseX, mouseY, this.contextMenuEnabledAtoms); var selected = null; if (intersects.length) { @@ -4715,7 +4744,7 @@ export function createViewerGrid(element, config:ViewerGridSpec={}, viewer_confi viewer_config.canvas = canvas; viewer_config.viewers = viewers; viewer_config.control_all = config.control_all; - var viewer = createViewer(element, viewer_config); + var viewer = createViewer(element, extend({},viewer_config)); row.push(viewer); } viewers.unshift(row); //compensate for weird ordering in renderer diff --git a/tests/auto/tests/testgridlabel.js b/tests/auto/tests/testgridlabel.js new file mode 100644 index 000000000..e1b6f694b --- /dev/null +++ b/tests/auto/tests/testgridlabel.js @@ -0,0 +1,95 @@ +var benz=` + RDKit 3D + + 6 6 0 0 0 0 0 0 0 0999 V2000 + -0.9517 0.7811 -0.6622 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.2847 1.3329 -0.3121 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.2365 0.5518 0.3512 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.9517 -0.7811 0.6644 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.2847 -1.3329 0.3144 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.2365 -0.5518 -0.3489 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 2 0 + 2 3 1 0 + 3 4 2 0 + 4 5 1 0 + 5 6 2 0 + 6 1 1 0 +M END +$$$$ +` + +var viewers = $3Dmol.createViewerGrid( + 'gldiv', //id of div to create canvas in + { + rows: 2, + cols: 2, + linked: true, + control_all: true //mouse controls all viewers + } +); + +var view0 = viewers[0][0]; +var view1 = viewers[1][1]; + +view0.addModel(benz,'sdf') +view0.setStyle({'stick':{}}) +view1.addModel(benz,'sdf') +view1.setStyle({'stick':{"colorscheme": "cyanCarbon"}}) +view0.setHoverable( + {}, + true, + function(atom,viewer,event,container) { + console.log(); + if(!atom.label) { + atom.label = viewer.addLabel(atom.atom,{position: atom, backgroundColor: 'mintcream', fontColor:'black'}); + viewer.render(); + }}, + function(atom,viewer) { + if(atom.label) { + viewer.removeLabel(atom.label); + delete atom.label; + } + } +) + +view1.setHoverable( + {}, + true, + function(atom,viewer,event,container) { + console.log(); + if(!atom.label) { + atom.label = viewer.addLabel(atom.atom,{position: atom, backgroundColor: 'cyan', fontColor:'black'}); + viewer.render(); + }}, + function(atom,viewer) { + if(atom.label) { + viewer.removeLabel(atom.label); + delete atom.label; + } + } +) + +view0.setClickable({},true,function(atom,viewer) { + viewer.removeAllShapes(); + viewer.addSphere({center:atom,radius:1.0,color:'red',alpha:0.4}); + viewer.render(); +}); + +view1.setClickable({},true,function(atom,viewer) { + viewer.removeAllShapes(); + viewer.addSphere({center:atom,radius:1.0,color:'purple',alpha:0.4}); + viewer.render( ); +}); + +view0.zoomTo(); +view1.zoomTo(); +view0.render( ); +view1.render( ); + +let xy = view0.modelToScreen(view0.models[0].atoms[1]) +view0._handleMouseDown({pageX: xy.x, pageY: xy.y, preventDefault: function(){}}); +view0._handleMouseUp({pageX: xy.x, pageY: xy.y, preventDefault: function(){}}); + +xy = view1.modelToScreen(view1.models[0].atoms[0]) +view1._handleMouseDown({pageX: xy.x, pageY: xy.y, preventDefault: function(){}}); +view1._handleMouseUp({pageX: xy.x, pageY: xy.y, preventDefault: function(){}}); diff --git a/tests/glcheck/reference-images/tests-glcheck-render-tests-testgridlabel.html.png b/tests/glcheck/reference-images/tests-glcheck-render-tests-testgridlabel.html.png new file mode 100644 index 000000000..deee2c17a Binary files /dev/null and b/tests/glcheck/reference-images/tests-glcheck-render-tests-testgridlabel.html.png differ