← All scenes

Live Chart

A line chart of a made-up sensor, updating as you watch. Move the mouse over it to read a value.

# A live chart: a line chart of a made-up sensor, updating as you watch.
# Shows: turning data into screen positions, gridlines, labels, a gradient fill,
# and a readout that follows the mouse.
#
# Run it:   pyctures run live_chart.py
# Share it: pyctures build live_chart.py -o site/

import math, random

readings = []          # the last 200 readings
t = 0.0
temperature = 20.0

def update(dt):
    global t, temperature
    t += dt
    # a made-up sensor: a slow daily swing plus some noise
    temperature += (20 + 5 * math.sin(t * 0.8) - temperature) * 0.05 + random.uniform(-0.3, 0.3)
    readings.append(temperature)
    if len(readings) > 200:
        readings.pop(0)

def draw():
    ctx.fill_style = "#0f172a"
    ctx.fill_rect(0, 0, width, height)
    if len(readings) < 2:
        return

    left, right, top, bottom = 50, width - 20, 50, height - 30
    plot_w, plot_h = right - left, bottom - top
    low, high = 10, 30                        # the range the chart shows

    def sx(i):                                 # reading number -> screen x
        return left + i / (len(readings) - 1) * plot_w

    def sy(v):                                 # temperature -> screen y
        return bottom - (v - low) / (high - low) * plot_h

    # gridlines every 5 degrees
    ctx.font = "12px sans-serif"
    ctx.text_align = "right"
    ctx.text_baseline = "middle"
    ctx.line_width = 1
    for v in range(low, high + 1, 5):
        ctx.stroke_style = "#1e293b"
        ctx.begin_path()
        ctx.move_to(left, sy(v))
        ctx.line_to(right, sy(v))
        ctx.stroke()
        ctx.fill_style = "#94a3b8"
        ctx.fill_text(f"{v}°", left - 8, sy(v))

    # the shaded area under the line
    ctx.begin_path()
    ctx.move_to(sx(0), bottom)
    for i, v in enumerate(readings):
        ctx.line_to(sx(i), sy(v))
    ctx.line_to(sx(len(readings) - 1), bottom)
    ctx.close_path()
    shade = ctx.create_linear_gradient(0, top, 0, bottom)
    shade.add_color_stop(0, "rgba(56, 189, 248, 0.35)")
    shade.add_color_stop(1, "rgba(56, 189, 248, 0)")
    ctx.fill_style = shade
    ctx.fill()

    # the line itself
    ctx.begin_path()
    for i, v in enumerate(readings):
        if i == 0:
            ctx.move_to(sx(i), sy(v))
        else:
            ctx.line_to(sx(i), sy(v))
    ctx.stroke_style = "#38bdf8"
    ctx.line_width = 2
    ctx.line_join = "round"
    ctx.stroke()

    # title and the latest value
    ctx.text_align = "left"
    ctx.text_baseline = "alphabetic"
    ctx.fill_style = "white"
    ctx.font = "bold 18px sans-serif"
    ctx.fill_text(f"Temperature now: {readings[-1]:.1f}°", left, 30)

    # a readout where the pointer is
    if left <= mouse.x <= right:
        i = round((mouse.x - left) / plot_w * (len(readings) - 1))
        i = min(max(i, 0), len(readings) - 1)
        x, y = sx(i), sy(readings[i])
        ctx.stroke_style = "#64748b"
        ctx.set_line_dash([4, 4])
        ctx.begin_path()
        ctx.move_to(x, top)
        ctx.line_to(x, bottom)
        ctx.stroke()
        ctx.set_line_dash([])
        ctx.fill_style = "white"
        ctx.begin_path()
        ctx.arc(x, y, 5, 0, math.tau)
        ctx.fill()
        ctx.font = "13px sans-serif"
        ctx.fill_text(f"{readings[i]:.1f}°", x + 8, y - 8)

Run it yourself

pip install pyctures
pyctures run live_chart.py

Put it on your website

pyctures build live_chart.py -o site/

Python runs in the visitor's browser, so any static host works. How · This file on GitHub