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:
(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.
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"
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>
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.
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