Skip to content

Instantly share code, notes, and snippets.

@jsomers
Created September 27, 2018 12:50
Show Gist options
  • Select an option

  • Save jsomers/d32dd3507e5406c56e47b4cd6f28c60e to your computer and use it in GitHub Desktop.

Select an option

Save jsomers/d32dd3507e5406c56e47b4cd6f28c60e to your computer and use it in GitHub Desktop.

Revisions

  1. jsomers revised this gist Sep 27, 2018. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions websockets.md
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    # Using websockets to easily build GUIs for Python programs

    I recently built a small [agent-based model](https://en.wikipedia.org/wiki/Agent-based_model) using Python and wanted to visualize the model in action. But as much as Python is an ideal tool for scientific computation (numpy, scipy, matplotlib), it's not as good for dynamic visualization (pygame?).

    You know what's a very mature and flexible tool for drawing graphics? The DOM! For simple graphics you can use HTML and CSS; for more complicated stuff you can use Canvas, SVG, or WebGL. There are countless frameworks, libraries, and tutorials to help you draw exactly what you need. In my case, this was the animation I wanted:
  2. jsomers created this gist Sep 27, 2018.
    106 changes: 106 additions & 0 deletions websockets.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,106 @@
    I recently built a small [agent-based model](https://en.wikipedia.org/wiki/Agent-based_model) using Python and wanted to visualize the model in action. But as much as Python is an ideal tool for scientific computation (numpy, scipy, matplotlib), it's not as good for dynamic visualization (pygame?).

    You know what's a very mature and flexible tool for drawing graphics? The DOM! For simple graphics you can use HTML and CSS; for more complicated stuff you can use Canvas, SVG, or WebGL. There are countless frameworks, libraries, and tutorials to help you draw exactly what you need. In my case, this was the animation I wanted:

    ![high-priority](https://user-images.githubusercontent.com/21294/45828766-5c6d9780-bcc7-11e8-9e05-8f3e17e5ec08.gif)

    (Each row represents a "worker" in my model, and each rectangle represents a "task.")

    I would have had no idea how to do this in Python. But I knew it would be almost trivial using HTML and CSS. So I turned my command-line program into a simple server, and sent data over a websocket to a web page, which did all the drawing.

    ### Server-side: do fancy scientific calculations and publish data to the websocket

    Using [a tiny websocket library](https://github.com/Pithikos/python-websocket-server) for Python, I set up my server as follows:

    ```python
    import json
    import numpy
    import time
    from websocket_server import WebsocketServer

    server = WebsocketServer(9001)

    def run():
    """The actual model"""
    ...

    def new_client(client, server):
    run()

    server.set_fn_new_client(new_client)
    server.run_forever()
    ```

    Then, in the body of the `run` function, which runs on a loop, I simply pushed the state of my model whenever I needed to:

    ```python
    def run():
    while True:
    ... # do stuff
    data = {"rows": [...]} # model data
    server.send_message_to_all(json.dumps(data))
    time.sleep(0.1) # so as not to overwhelm the web UI. this gives us our effective "frame rate"
    ```

    ### Client-side: draw the data using our old friends HTML, CSS, and Javascript

    The client is equally simple: we just listen for websocket push events and rewrite our DOM. For my model, I just needed to draw those rectangles you see above. I could color and position them using CSS, and size them dynamically based on the data.

    ```html
    <title>Client</title>

    <style>
    .row {
    height: 6px;
    font-size: 13px;
    margin-bottom: 2px;
    }
    .rectangle {
    background-color: lightgray;
    float: left;
    margin-right: 2px;
    height: 6px;
    }
    .rectangle.highlight.current {
    background-color: pink;
    }
    .rectangle.highlight {
    background-color: red;
    }
    </style>

    <div id="rows"></div>

    <script type="text/javascript">
    const WIDTH_SCALE_FACTOR = 1;
    const draw_row = ({tasks}) => {
    const html = tasks.map((task, i) => {
    return `
    <div
    class="rectangle ${task.status} ${task.is_current ? 'current' : ''}"
    style="width: ${Math.ceil(task.cost / WIDTH_SCALE_FACTOR)}px"
    ></div>
    `;
    }).join('');
    return `<div class="row">${html}</div>`;
    }
    const ws = new WebSocket('ws://localhost:9001/');
    ws.onmessage = ({data}) => {
    const rows = JSON.parse(data).rows;
    const html = rows.map(row => draw_row(row)).join('');
    document.getElementById('rows').innerHTML = html;
    };
    </script>
    ```

    ### Conclusion: use websockets to bridge your favorite computing environment with the web

    The same technique would of course work for any language besides Python. The broader point is that you probably have a language you prefer using for scientific / numerical computing, and it's probably not Javascript. But HTML, CSS, Javascript, and Canvas offer a flexible and easy-to-use toolkit for drawing graphics—and the browser is a natural and ubiquitous GUI. Websockets can be the bridge between these two worlds.