emilyfy
emilyfy

Reputation: 313

Implementing panning in matplotlib 3D plot

I have a 3D plot that I embed in tkinter and I'm trying to make interactive by binding mouse events to rotation, zooming and panning. I implemented the rotation and zooming function by referring to their axes3d source code and modifying the elev and azim or x, y and z limits respectively myself.

However, there is the panning part that is not implemented in the source code. It simply said

#        elif self.button_pressed == 2:
            # pan view
            # project xv, yv, zv -> xw, yw, zw
            # pan
#            pass

So I tried to implement it myself but I can't get it right. Here's what I have, if put into their source code:

The problem I'm having is how to project the 2 dx and dy values of mouse movement into the 3 dxx, dyy and dzz values of the 3d axes. What I have right now is wrong, although I can't really figure out why. It just doesn't always pan the way I want to, except for some values of elev and azim, such as when elev=0 and azim=0 or when only either one is at multiples of 90. When both elev and azim are at non-zero multiples of 90, it doesn't pan or pans the opposite way for some axes. When it's at any other value, it doesn't project to all the axes correctly such that it appears that I'm panning the whole figure left/right/up/down.

        elif self.button_pressed == 2:
            # get the x and y pixel coords
            if dx == 0 and dy == 0:
                return
            # how to convert dx dy -> dxx dyy dzz? this is wrong
            minx, maxx, miny, maxy, minz, maxz = self.get_w_lims()
            elev, azim = np.deg2rad(self.elev), np.deg2rad(self.azim)
            dxx = (maxx-minx) * ( (dx/w) * np.sin(azim) + (dy/h) * np.sin(elev) )
            dyy = (maxy-miny) * ( - (dx/w) * np.cos(azim) )
            dzz = (maxz-minz) * ( - (dy/h) * np.cos(elev) )
            # pan
            self.set_xlim3d(minx + dxx, maxx + dxx)
            self.set_ylim3d(miny + dyy, maxy + dyy)
            self.set_zlim3d(minz + dzz, maxz + dzz)
            self.get_proj()
            self.figure.canvas.draw_idle()

edit: the latest matplotlib source code would already have the panning implemented.

Upvotes: 1

Views: 939

Answers (2)

Scott
Scott

Reputation: 785

The panning and zooming toolbar buttons are enabled now as of Matplotlib 3.7.0, see: https://matplotlib.org/stable/users/prev_whats_new/whats_new_3.7.0.html#d-plot-pan-and-zoom-buttons

Without using the toolbar buttons, then by default the left mouse button rotates the view, the middle mouse button pans, and the right mouse button zooms.

These should all work for whatever view angle you currently have.

Upvotes: 0

emilyfy
emilyfy

Reputation: 313

somehow this works

        elif self.button_pressed == 2:
            # get the x and y pixel coords
            if dx == 0 and dy == 0:
                return
            # convert dx dy -> dxx dyy dzz
            minx, maxx, miny, maxy, minz, maxz = self.get_w_lims()
            elev, azim = np.deg2rad(self.elev), np.deg2rad(self.azim)
            dxe = (dy/h) * np.sin(elev)
            dye = - (dx/w)
            dze = - (dy/h) * np.cos(elev)
            dxx = (maxx-minx) * ( dxe * np.cos(azim) - dye * np.sin(azim) )
            dyy = (maxy-miny) * ( dye * np.cos(azim) + dxe * np.sin(azim) )
            dzz = (maxz-minz) * ( dze )
            # pan
            self.set_xlim3d(minx + dxx, maxx + dxx)
            self.set_ylim3d(miny + dyy, maxy + dyy)
            self.set_zlim3d(minz + dzz, maxz + dzz)
            self.get_proj()
            self.figure.canvas.draw_idle()

I noticed that before it worked only when either one of elev or azim is 0, so I thought that maybe I need to project through elev first then azim.

Upvotes: 0

Related Questions