##################################### # Packages & Dependencies ##################################### import param from panel.reactive import ReactiveHTML ##################################### # Canvas ##################################### class Canvas(ReactiveHTML): ''' The HTML canvas panel used for drawing digits (0-9) in the application. Reference: https://panel.holoviz.org/how_to/custom_components/examples/canvas_draw.html ''' uri = param.String() clear = param.Boolean(default = False) _template = ''' ''' _scripts = { 'render': ''' state.ctx = canvas.getContext('2d'); state.ctx.fillStyle = '#FFFFFF'; state.ctx.fillRect(0, 0, canvas.width, canvas.height); state.ctx.lineWidth = 30; state.ctx.strokeStyle = '#000000'; state.ctx.lineJoin = 'round'; state.ctx.lineCap = 'round'; // Helper to normalize mouse coordinates state.getCoords = function(e) { const rect = canvas.getBoundingClientRect(); return { x: (e.clientX - rect.left) * (canvas.width / rect.width), y: (e.clientY - rect.top) * (canvas.height / rect.height) }; }; ''', 'start': ''' if (state.isDrawing) return; state.isDrawing = true; const pos = state.getCoords(event); state.ctx.beginPath(); state.ctx.moveTo(pos.x, pos.y); ''', 'draw': ''' if (!state.isDrawing) return; const pos = state.getCoords(event); state.ctx.lineTo(pos.x, pos.y); state.ctx.stroke(); data.uri = canvas.toDataURL('image/png'); ''', 'end': ''' if (!state.isDrawing) return; // Early return if already not drawing state.isDrawing = false; ''', 'clear': ''' state.ctx.fillStyle = '#FFFFFF'; state.ctx.fillRect(0, 0, canvas.width, canvas.height); data.uri = ''; ''' } def toggle_clear(self, *event): ''' Toggles the value of self.clear to trigger the JS 'clear' function. ''' self.clear = not self.clear