In [ ]:
import numpy as np
from ipycanvas import Canvas, hold_canvas
In [ ]:
from ipywidgets import FloatSlider

This module is local to the Notebook

In [ ]:
from py3d_engine import OrbitCamera, project_vector
In [ ]:
class Plot3d(Canvas):
    def __init__(self):
        super(Plot3d, self).__init__(width=500, height=500)

        self.dragging = False
        
        self.n = 200
        self.x = np.random.rand(self.n) - 0.5
        self.y = np.random.rand(self.n) - 0.5
        self.z = np.random.rand(self.n) - 0.5
        
        self.dx = 0
        self.dy = 0
        self.radius = 10

        self.camera = OrbitCamera(self.radius, [0, 0, 0], self.width/self.height)
        self.x2, self.y2, self.z2 = project_vector(self.x, self.y, self.z, self.camera.matrix)
        self.draw()

        self.on_mouse_down(self.mouse_down_handler)
        self.on_mouse_move(self.mouse_move_handler)
        self.on_mouse_up(self.mouse_up_handler)
        self.on_mouse_out(self.mouse_out_handler)

    def update_matrix(self, dx=None, dy=None, radius=None):
        dx = dx if dx is not None else self.dx
        dy = dy if dy is not None else self.dy
        self.radius = radius if radius is not None else self.radius
        
        self.camera.radius = self.radius
        self.camera.update_position(dy, dx)
        self.x2, self.y2, self.z2 = project_vector(self.x, self.y, self.z, self.camera.matrix)
        self.draw()

    def draw(self):
        x = self.x2 * self.width + self.width / 2
        y = self.y2 * self.height + self.height / 2
        with hold_canvas(self):
            self.clear()
            self.fill_circles(x, y, 2)

    def mouse_down_handler(self, pixel_x, pixel_y):
        self.dragging = True
        self.x_mouse = pixel_x
        self.y_mouse = pixel_y

    def mouse_move_handler(self, pixel_x, pixel_y):
        if self.dragging:
            self.dx_new = self.dx + pixel_x - self.x_mouse
            self.dy_new = self.dy + pixel_y - self.y_mouse
            
            self.update_matrix(self.dx_new, self.dy_new)
    
    def mouse_up_handler(self, pixel_x, pixel_y):
        if self.dragging:
            self.dragging = False
            self.dx = self.dx_new
            self.dy = self.dy_new
    
    def mouse_out_handler(self, pixel_x, pixel_y):
        if self.dragging:
            self.dragging = False
            self.dx = self.dx_new
            self.dy = self.dy_new
In [ ]:
p = Plot3d()
p
In [ ]:
# Link Camera position to a slider widget
slider = FloatSlider(description='Radius:', min=1., max=7., value=p.radius)

def on_slider_move(change):
    slider_value = change['new']

    p.update_matrix(radius=slider_value)

slider.observe(on_slider_move, 'value')

slider
In [ ]: