Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I have an interactive html5 canvas with dash, plotly

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

like image 667
user2253054 Avatar asked Oct 15 '25 13:10

user2253054


1 Answers

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:

grid showcase

like image 57
Bas van der Linden Avatar answered Oct 18 '25 08:10

Bas van der Linden



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!