Displaying Kagi's stats page on a TRMNL

I ordered a TRMNL back in February, mere days after I was informed of their existence by the local tech discord (and, almost simultaneously, the #random channel at work). I had no idea what, specifically, I would do with it, but the company's unusual commitment to openness was enough to convince me to buy one anyway.

(You can build your own device and pay a one-time fee to use it with their server. You can buy a device from them and point it at your own server - the API that the server exposes is extremely simple to implement. You can build your own device and point it your own server, at which point you're just using their code. Have I convinced you to go buy one yet?)

The device arrived last Tuesday. For a couple of days it sat on top of my computer and displayed a slideshow of quotes, weather forecasts, and starship schematics. What else can I make it do?


Kagi has a public dashboard where we show our member count growing over time. I check it semi-regularly. It's exactly the kind of data that would look good on a physical dashboard gizmo.

There's a built-in plugin that displays a screenshot of a website, so we can just

... well, so much for that idea.

Take two:

TRMNL has built-in support for polling an endpoint, passing what it gets back to a Liquid template, and taking a screenshot of the resulting page. They don't support polling endpoints that return HTML, so I had to set up a short PHP script that runs on this server to fetch the stats page, extract the graph SVG, and stuff it into a JSON document. On TRMNL's side, we paste the SVG into an HTML page and then some JS adjusts the styles to look better on the 1-bit grayscale display.

Why were style adjustments necessary?

Here's a photo from before I implemented them.

JS included here for completeness
// Graph markup may change over time,
// therefore take care not to throw if an element goes missing

for (let c of document.querySelectorAll("circle")) {
	c.style.fill = "black";
	c.style.stroke = "black";
}

let line = document.querySelector("path[stroke=orange]");
if (line) {
	line.style.strokeWidth = 2;
	line.style.stroke = "black";
}

for (let l of document.querySelectorAll(".axis_line")) {
	l.style.opacity = "1";
}

let axes = document.querySelector("path[d='M500 0 L10 0 A10,-10 0 0 1 0,-10 L0 -300']");
if (axes) {
	axes.attributes.d.value = "M500 0 L0 0 L0 -300";
}

let fill = document.querySelector("[fill^='url(#grad-']");
if (fill) {
	fill.style.fill = "url(#dither)";
	fill.attributes.d.value = fill.attributes.d.value.replace("M", "M 0 0 L");
}

for (let t of document.querySelectorAll("text")) {
	t.classList.add("label");
}

If, for some reason, you also want a physical Kagi stats dashboard in your life, download the plugin. (I make no promises about how long this setup will continue to work.)


Published 2025-04-26

Next: Bit plane extractor

Previous: Bookmarklets for moving between environments