Skip to content

Instantly share code, notes, and snippets.

@jsomers
Created September 27, 2018 12:50
Show Gist options
  • Star 90 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save jsomers/d32dd3507e5406c56e47b4cd6f28c60e to your computer and use it in GitHub Desktop.
Save jsomers/d32dd3507e5406c56e47b4cd6f28c60e to your computer and use it in GitHub Desktop.
Using websockets to easily build GUIs for Python programs

Using websockets to easily build GUIs for Python programs

I recently built a small 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

(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 for Python, I set up my server as follows:

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:

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.

<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.

@yashasweeyash
Copy link

I have done similar things in past. I see the point.

@aquilesC
Copy link

aquilesC commented Oct 5, 2018

Have you ever tried PyQt? It allows you to do the same and in a standalone application (no need of a browser).

I considered several times using a web browser as a GUI, but since I normally deliver code for people to keep building upon, I can't expect them to learn Javascript on top of all what they already have to do.

It's a great example! I'll play with the ideas a bit to see what can I dom

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment