Needed circular progress gauge. Googled "Python circular progress bar." Every answer: pip install [library].
Drew mine with pure tkinter Canvas:
- Zero dependencies
- Two parameters:
style="arc",extent=-270 - Professional look
- 20 lines of code
secret: tkinter has THREE arc styles. 99% of devs only use one.
The Problem
I needed a circular progress gauge for PC Workman's "First Setup & Drivers" page.
Something clean. Professional. The kind you see in system health dashboards.
First instinct: Google.
Search: "Python circular progress bar"
Results:
- matplotlib circular progress tutorial
- PIL + pillow gauge example
- PyQt QProgressBar subclass
- Custom tkinter widget library
Every answer started the same: pip install [library].
PC Workman is already PyQt6 + tkinter + psutil + SQLite. Adding matplotlib for one gauge? Overkill.
There had to be a better way.
The Discovery
Opened the tkinter Canvas docs.
Not Stack Overflow. Not a tutorial. The actual documentation.
Found create_arc():
style: One of 'pieslice' (default), 'chord', or 'arc'.
style="arc" — draws just the arc itself. No fill. No center lines.
This was it.
The Solution
def _draw_arc(canvas, score):
# Background arc (full 270° track)
canvas.create_arc(10, 10, 110, 110,
start=225, extent=-270,
style="arc", outline="#1f2937", width=7)
# Progress arc (proportional to score)
if score is not None:
color = _score_color(score)
extent = -int(270 * score / 100)
canvas.create_arc(10, 10, 110, 110,
start=225, extent=extent,
style="arc", outline=color, width=7)
Result:
[10-second PC Health gauge animation]
Clean. Professional. Zero dependencies.
How It Works
The Geometry
create_arc() takes a bounding box, not center + radius:
canvas.create_arc(x1, y1, x2, y2, ...)
# └─────┬─────┘
# rectangle containing circle
For a perfect circle at (60, 60) with radius 50:
canvas.create_arc(10, 10, 110, 110, ...)
# 60-50, 60-50, 60+50, 60+50
The Angles
tkinter measures angles from East (3 o'clock) = 0°, counterclockwise:
| Direction | Degrees |
|---|---|
| East | 0° |
| North | 90° |
| West | 180° |
| South | 270° |
Trick: Negative extent goes clockwise.
For a speedometer (7:30 to 4:30 position):
start=225, # 225° = bottom-left (7:30)
extent=-270 # -270° clockwise = wraps to bottom-right (4:30)
Perfect 270° gauge.
The Secret Third Style
Most devs know two Canvas arc styles:
| Style | What It Draws |
|---|---|
"pieslice" |
Filled wedge from center (default) |
"chord" |
Arc + straight line connecting ends |
"arc" |
Just the arc curve |
Almost nobody uses style="arc".
That's your circular progress bar.
style="arc", outline="#10b981", width=7
7-pixel green arc. No fill. No custom drawing.
style="arc" has been in tkinter since Python 1.x. 99% of developers have never touched it.
The Production Implementation
The two-line example is simplified. Here's what ships in PC Workman:
def _draw_arc(canvas, score):
canvas.delete("all")
W, H = 108, 92
cx, cy, r = W // 2, H // 2 + 4, 35
# Layer 1: Outer glow (wider, darker)
canvas.create_arc(cx-r-4, cy-r-4, cx+r+4, cy+r+4,
start=225, extent=-270, style="arc",
outline="#1a2035", width=10)
# Layer 2: Track (gray path)
canvas.create_arc(cx-r, cy-r, cx+r, cy+r,
start=225, extent=-270,
style="arc", outline="#1f2937", width=7)
# Layer 3: Progress (colored, proportional)
if score is not None:
col = _score_color(score)
extent = -int(270 * score / 100)
canvas.create_arc(cx-r, cy-r, cx+r, cy+r,
start=225, extent=extent,
style="arc", outline=col, width=7)
# Score text
canvas.create_text(cx, cy - 3, text=str(score),
fill=col, font=("Consolas", 22, "bold"))
canvas.create_text(cx, cy + 17, text="/100",
fill="#6b7280", font=("Consolas", 9))
Three layers on same Canvas:
- Outer glow — 10px dark, 4px larger radius (depth)
- Track — 7px gray, full 270° (background)
- Progress — 7px colored, proportional (gauge)
No masking. No blending. Just stacking arcs.
The Math
extent = -int(270 * score / 100)
| Score | Calculation | Result |
|---|---|---|
| 100 | -int(270 * 1.0) | -270 (full) |
| 75 | -int(270 * 0.75) | -202 (¾) |
| 50 | -int(270 * 0.5) | -135 (½) |
| 0 | -int(270 * 0.0) | 0 (empty) |
The int() is critical. tkinter won't accept float extents — it'll silently fail and draw nothing.
Color Grading
def _score_color(score):
if score >= 80:
return "#10b981" # Green
if score >= 55:
return "#fbbf24" # Amber
return "#ef4444" # Red
def _score_grade(score):
if score >= 85: return "EXCELLENT"
if score >= 70: return "GOOD"
if score >= 50: return "FAIR"
return "POOR"
Three thresholds. Same color in arc, number, and label.
No color library. Just hex values.
The Scanning State
Gauge drawn twice:
-
On load — gray track only (
score=None) -
After scan — colored progress (
score=0-100)
# Initial
_draw_arc(health_canvas, None)
# After scan thread
_draw_arc(health_canvas, calculated_score)
if score is not None prevents empty arc or crash.
While scanning (registry check, driver status, updates), gauge waits as clean gray circle.
Copyable Minimal Example
Want this in your project? Here's the stripped version:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=120, height=120, bg="#0a0e14")
canvas.pack()
def draw_gauge(score):
canvas.delete("all")
# Gray track
canvas.create_arc(10, 10, 110, 110,
start=225, extent=-270,
style="arc", outline="#1f2937", width=8)
# Colored progress
if score:
extent = -int(270 * score / 100)
canvas.create_arc(10, 10, 110, 110,
start=225, extent=extent,
style="arc", outline="#10b981", width=8)
# Text
canvas.create_text(60, 60, text=str(score),
fill="white", font=("Consolas", 18, "bold"))
draw_gauge(73)
root.mainloop()
20 lines. Zero dependencies. Production-ready.
Why This Matters
This gauge powers health check in PC Workman (110+ downloads, 23 GitHub stars).
First thing users see in "First Setup & Drivers."
Makes the section feel premium — smooth, clean, not default tkinter.
All because I read Canvas docs instead of installing matplotlib.
The Bigger Lesson
Most developers solve problems by adding dependencies.
Need progress bar? Install library.
Need chart? Install library.
Need widget? Install library.
Nothing wrong with libraries. Good ones save time.
But sometimes the best solution is in stdlib docs.
In a parameter you've never used.
On a page you've never read.
style="arc" has existed since Python 1.x.
99% of developers never touched it.
I found it in 5 minutes of reading docs instead of Googling "how to."
What's Next
PC Workman v1.7.x builds toward TURBO Mode. Windows optimization tools:
- CPU unparking
- RAM flushing
- Service management
- Power plan switching
"First Setup & Drivers" scans system, gives health score before TURBO touches anything.
This gauge is part of that foundation.
v1.7.2 ships next week.
Your Turn
What's your "I can't believe this is possible with just X" moment?
Time you almost installed library, then realized solution was in docs?
Drop it below
Links
PC Workman:
Follow the build:
- X: @hck_lab
- LinkedIn: Marcin Firmuga
- All links: linktr.ee/hck_labs
Wednesday Code Autopsy continues next week.
Building PC Workman in public. One arc at a time.



Top comments (0)