- Look At Me: a simple way to plot the value of your variable, in real time, in your browser. Just add one line in your code and you're good to go. Very pretentious.
- Control Me: a simple way to modify some parameters in your code, in real time, from your browser, and you can see the effect in your look at me graph.
cd look_at_me
source .venv/bin/activate
python -m backend.maincd frontend
npm install # first time only
npm run devpython scripts/example_producer.pyNow go to http://localhost:3000/ and watch the magic happen.
One day, I had to change a piece code that produced some data and I wanted to see what it was doing. So I designed an ugly hack to plot the data. Redirect everything in a file, used Matplotlib / gnuplot to plot them and it was OK. Don't judge me, you've done that too -- or not, but in this case you still don't know what's happening and you're not better than me.
Then I had another project, other data, same problem. Data format was different, copy paste the hack, tweak it and Bob's your uncle, I have the trend.
Then, something awful happened. I had to look at the data while they were produced. No problem, Matplotlib has an animation library, you update the data, everything is good. Too bad it had to work on macOS where Matplotlib's animation framework is broken...
Now imagine I have to set a threshold on these data. What can I do? I can plot them, modify my program, kill it, compile / launch it and I can see my value is wrong. Great! Took me 20 seconds to change a number and now I'm checking my emails... All this to modify the threshold again.
So I decided to create look at me / control me, a simple logging / visualization tool for variables where you can also easily control / change them.
- XY Scatter: Plot 2D points with optional trails (great for joysticks, touchpads, robot positions)
- Line Chart: Rolling window of values over time
- Histogram: Distribution of values in a sliding window
- Text: Display the current value
- History Text: Scrolling log of values
Transform your raw data on-the-fly! Send "123 456" from your code, transform it to {x: 123, y: 456} in the browser. Available transformations:
- String ops:
split,strip,regex_extract,replace,substring - Numeric ops:
parse_int,parse_float,scale,offset,clamp,round - Vector ops:
add_vector,scale_vector,map_parse_int,map_parse_float - Structure ops:
to_dict,pick,get_field,get_index - JSON:
parse_json,to_json - Advanced:
expression(use formulas likex * 2 + 1)
Rate-limited at 20Hz to keep your browser happy. Because 1000 updates per second is great for your data, not so great for your CPU fan.
Your Code → ZMQ (5556) → Backend (FastAPI) → SSE → React Frontend
↓
Derived Sources
(server-side transforms)
- Print: great, but where is the plot?
- Print and gnuplot: what if I tell you that some people want to see things when they happen and not (a long time) after?
- Matplotlib (Python): same as gnuplot. Also, good luck with macOS.
- Awesome custom Qt5 application: you must be kidding.
More seriously, these are great tools for some applications. Nevertheless, you have to write custom ad hoc code each time and it's a pity.
python -m venv .venv
source .venv/bin/activate
pip install .Or with uv (faster):
uv venv
source .venv/bin/activate
uv pip install .For development (editable mode):
pip install -e .cd frontend
npm install# Pipe any command output to Look At Me
cat data.txt | lookatme-pipe myvar
tail -f /var/log/syslog | lookatme-pipe logs
sensor_reader | lookatme-pipe sensor -n # -n to parse as numbers
# Or wrap a command directly
lookatme myvar -- python my_script.py
lookatme metrics -n -- ./read_sensors # -n for numeric, -j for JSONfrom sdk import send, control
# Simple value
send("my_variable", 42)
# Multiple values (will be transformed via Derived Sources if needed)
send("touchpad", f"{x} {y}")
# JSON
send("robot_state", {"x": 1.5, "y": 2.3, "angle": 45})
# Controllable parameter (slider in the UI)
gain = control("gain", default=1.0, widget="slider", min=0.0, max=10.0)
print(gain.value) # Get current value from UI- Your code sends data via ZMQ PUB/SUB
- Backend receives, stores, and optionally transforms data
- Frontend subscribes via Server-Sent Events (SSE)
- Widgets render the data in real-time
- Frontend:
http://localhost:3000 - Backend API:
http://localhost:8080 - ZMQ SUB:
tcp://127.0.0.1:5556 - ZMQ PUB:
tcp://127.0.0.1:5557
- Currently Python-only client (but ZMQ is polyglot, PRs welcome!)
- No persistence of data (refresh = gone)
- Limited to ~20Hz display rate (by design)