Is it possible to use an interactive html5 canvas with dash? I would like to be able to draw a grid like Conway's game of life. And have it be interactive where I can click on a cell and activate / deactivate it. And be able to set canvas size. Thanks
Note: From the wording of the question the focus seems to be on having a canvas grid with clickable cells that can be toggled on and of in Dash, so I will address this in my answer. I won't go into Conway's Game of Life here, but this answer could serve as a starting point if you wanted to implement it or a variation of it.
The dash_html_components
module gives us access to a Canvas
component.
What we could do is create a clientside callback that runs once through which we have access to the DOM with Javascript so we can create the grid based on the canvas
element rendered by the Canvas
Dash component and attach click event listeners to it.
The Dash app could look something like this:
from dash import Dash
import dash_html_components as html
from dash.dependencies import ClientsideFunction, Output, Input
app = Dash(__name__)
app.layout = html.Canvas(id="canvas")
app.clientside_callback(
ClientsideFunction(namespace="clientside", function_name="init_grid"),
Output("canvas", "children"),
Input("canvas", "children"),
)
if __name__ == "__main__":
app.run_server()
Then you could add a .js
file in your assets
directory with this inside:
window.dash_clientside = Object.assign({}, window.dash_clientside, {
clientside: {
init_grid: function (_) {
const canvas = document.querySelector("canvas");
canvas.width = 400;
canvas.height = 400;
const ctx = canvas.getContext("2d");
const cellWidth = 50;
const cellHeight = 50;
const numRows = canvas.height / cellHeight;
const numCols = canvas.width / cellWidth;
const drawGrid = () => {
const coordinates = [];
for (let row = 0; row < numRows; row++) {
for (let col = 0; col < numCols; col++) {
ctx.strokeRect(
col * cellWidth,
row * cellHeight,
cellWidth,
cellHeight
);
coordinates.push({ row, col, filledIn: false });
}
}
return coordinates;
};
const grid = drawGrid();
canvas.addEventListener("click", (event) => {
const clickedRow = Math.floor(event.offsetY / cellHeight);
const clickedCol = Math.floor(event.offsetX / cellWidth);
const cell = grid.filter((cell) => {
return cell.row === clickedRow && cell.col === clickedCol;
})[0];
const rectDimensions = [
clickedCol * cellWidth, // x
clickedRow * cellHeight, // y
cellWidth,
cellHeight,
];
if (!cell.filledIn) {
ctx.fillStyle = "black";
} else {
ctx.fillStyle = "white";
}
ctx.fillRect(...rectDimensions);
ctx.fillStyle = "black";
ctx.strokeRect(...rectDimensions);
cell.filledIn = !cell.filledIn;
});
return [];
},
},
});
For more information on including Javascript in Dash apps see the documentation here.
So the idea of the code above is to use nested loops to create a grid. We draw rectangles based on the current row number, column number and the width and height we've set for all cells.
In addition to drawing the grid, all grid coordinates are saved so we can determine which cell a user clicks on if that cell should be filled in or cleared. For knowing whether a cell should be cleared or filled a filledIn
property is set for all xy coordinate pairs.
Result:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With