Skip to content

Commit 19409f0

Browse files
committed
feat: add --dark/--light mode flag to all treemap commands
1 parent 08babe4 commit 19409f0

8 files changed

Lines changed: 96 additions & 34 deletions

File tree

src/dirplot/git_scanner.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def _render_frame_worker(args: tuple[Any, ...]) -> tuple[int, bytes, RectMap]:
251251
Args:
252252
args: ``(repo_str, files, current_highlights, sha, ts, orig_i,
253253
total_commits, depth, log_scale, width_px, height_px,
254-
font_size, colormap, cushion)``
254+
font_size, colormap, cushion, dark)``
255255
256256
Returns:
257257
``(orig_i, png_bytes, rect_map)``
@@ -271,6 +271,7 @@ def _render_frame_worker(args: tuple[Any, ...]) -> tuple[int, bytes, RectMap]:
271271
font_size,
272272
colormap,
273273
cushion,
274+
dark,
274275
) = args
275276

276277
from datetime import datetime
@@ -298,6 +299,7 @@ def _render_frame_worker(args: tuple[Any, ...]) -> tuple[int, bytes, RectMap]:
298299
rect_map_out=rect_map,
299300
title_suffix=f"sha:{sha[:8]} {dt_str}",
300301
progress=progress,
302+
dark=dark,
301303
)
302304
return (orig_i, buf.read(), rect_map)
303305

src/dirplot/main.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ def watch_cmd(
158158
cushion: bool = typer.Option(
159159
True, "--cushion/--no-cushion", help="Apply van Wijk cushion shading"
160160
),
161+
dark: bool = typer.Option(True, "--dark/--light", help="Dark background (default) or light"),
161162
animate: bool = typer.Option(
162163
False,
163164
"--animate/--no-animate",
@@ -253,6 +254,7 @@ def watch_cmd(
253254
depth=depth,
254255
crf=crf,
255256
codec=codec,
257+
dark=dark,
256258
)
257259

258260
observer = Observer()
@@ -363,6 +365,7 @@ def git_cmd(
363365
cushion: bool = typer.Option(
364366
True, "--cushion/--no-cushion", help="Apply van Wijk cushion shading"
365367
),
368+
dark: bool = typer.Option(True, "--dark/--light", help="Dark background (default) or light"),
366369
animate: bool = typer.Option(
367370
False,
368371
"--animate/--no-animate",
@@ -655,6 +658,7 @@ def git_cmd(
655658
font_size,
656659
colormap,
657660
cushion,
661+
dark,
658662
)
659663
for orig_i, sha, ts, files_copy, cur_hl, _del in snapshots
660664
]
@@ -752,6 +756,7 @@ def git_cmd(
752756
f"sha:{sha[:8]} {datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M')}"
753757
),
754758
progress=cumulative_ms / total_anim_ms,
759+
dark=dark,
755760
)
756761
output.write_bytes(png_buf.read())
757762
typer.echo(f" Updated {output}", err=True)
@@ -800,6 +805,7 @@ def replay_cmd(
800805
None, "--size", help="Output size as WIDTHxHEIGHT", metavar="WIDTHxHEIGHT"
801806
),
802807
cushion: bool = typer.Option(True, "--cushion/--no-cushion", help="Apply cushion shading"),
808+
dark: bool = typer.Option(True, "--dark/--light", help="Dark background (default) or light"),
803809
log: bool = typer.Option(False, "--log/--no-log", help="Use log of file sizes for layout"),
804810
depth: int | None = typer.Option(None, "--depth", help="Maximum directory depth"),
805811
workers: int | None = typer.Option(
@@ -949,6 +955,7 @@ def replay_cmd(
949955
font_size,
950956
colormap,
951957
cushion,
958+
dark,
952959
)
953960
for orig_i, ts, files_copy, cur_hl, _del in snapshots
954961
]
@@ -1205,6 +1212,7 @@ def main(
12051212
"--cushion/--no-cushion",
12061213
help="Apply van Wijk cushion shading: gives each tile a raised 3-D look.",
12071214
),
1215+
dark: bool = typer.Option(True, "--dark/--light", help="Dark background (default) or light"),
12081216
log: bool = typer.Option(
12091217
False,
12101218
"--log/--no-log",
@@ -1523,11 +1531,19 @@ def _info(msg: str) -> None:
15231531
t_render_start = time.monotonic()
15241532
if use_svg:
15251533
buf = create_treemap_svg(
1526-
root_node, width_px, height_px, font_size, colormap, legend, cushion, tree_depth
1534+
root_node, width_px, height_px, font_size, colormap, legend, cushion, tree_depth, dark
15271535
)
15281536
else:
15291537
buf = create_treemap(
1530-
root_node, width_px, height_px, font_size, colormap, legend, cushion, tree_depth
1538+
root_node,
1539+
width_px,
1540+
height_px,
1541+
font_size,
1542+
colormap,
1543+
legend,
1544+
cushion,
1545+
tree_depth,
1546+
dark=dark,
15311547
)
15321548
t_render = time.monotonic() - t_render_start
15331549

src/dirplot/render_png.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ def draw_node(
242242
img: Image.Image | None = None,
243243
root_label: str | None = None,
244244
rect_map: dict[str, tuple[int, int, int, int]] | None = None,
245+
dark: bool = True,
245246
) -> None:
246247
"""Recursively draw *node* and its children into *draw*.
247248
@@ -316,21 +317,24 @@ def draw_node(
316317
if rect_map is not None:
317318
rect_map[str(node.path)] = (x, y, w, h)
318319

319-
# Directory: 1-px white outer border + 1-px black inner border
320-
draw.rectangle([x, y, x + w - 1, y + h - 1], outline=(255, 255, 255), width=1)
320+
# Directory: 1-px outer border + 1-px inner border (colours swap in light mode)
321+
outer_col = (255, 255, 255) if dark else (0, 0, 0)
322+
inner_col = (0, 0, 0) if dark else (255, 255, 255)
323+
draw.rectangle([x, y, x + w - 1, y + h - 1], outline=outer_col, width=1)
321324
if w >= 4 and h >= 4:
322-
draw.rectangle([x + 1, y + 1, x + w - 2, y + h - 2], outline=(0, 0, 0), width=1)
325+
draw.rectangle([x + 1, y + 1, x + w - 2, y + h - 2], outline=inner_col, width=1)
323326

324327
# Header label — height driven by the font size
325328
header_h = font.size + 4
326329
if h > 2 + header_h:
327330
label = _truncate_breadcrumb(
328331
root_label if root_label is not None else node.name, draw, font, w - 8
329332
)
333+
header_text_col = (224, 224, 224) if dark else (32, 32, 32)
330334
draw.text(
331335
(x + w // 2, y + 2 + header_h // 2),
332336
label,
333-
fill=(224, 224, 224),
337+
fill=header_text_col,
334338
font=font,
335339
anchor="mm",
336340
align="center",
@@ -355,8 +359,9 @@ def draw_node(
355359
normed = squarify.normalize_sizes(sizes, iw, ih)
356360
rects = squarify.squarify(normed, ix, iy, iw, ih)
357361

358-
# Black background provides the 1-px separator between adjacent children
359-
draw.rectangle([ix, iy, ix + iw - 1, iy + ih - 1], fill=(0, 0, 0))
362+
# Background provides the 1-px separator between adjacent children
363+
sep_col = (0, 0, 0) if dark else (255, 255, 255)
364+
draw.rectangle([ix, iy, ix + iw - 1, iy + ih - 1], fill=sep_col)
360365

361366
for rect, child in zip(rects, positive_children, strict=False):
362367
rx = round(rect["x"])
@@ -376,6 +381,7 @@ def draw_node(
376381
cushion,
377382
img,
378383
rect_map=rect_map,
384+
dark=dark,
379385
)
380386

381387

@@ -428,6 +434,7 @@ def _draw_legend(
428434
corner: str,
429435
font: ImageFont.FreeTypeFont,
430436
max_rows: int = 20,
437+
dark: bool = True,
431438
) -> None:
432439
margin = 4
433440
bb = draw.textbbox((0, 0), "Ag", font=font)
@@ -455,8 +462,14 @@ def _draw_legend(
455462
bx = (width_px - box_w - margin) if "right" in corner else margin
456463
by = (height_px - box_h - margin) if "lower" in corner else margin
457464

458-
draw.rectangle([bx, by, bx + box_w - 1, by + box_h - 1], fill=(20, 20, 36))
459-
draw.rectangle([bx, by, bx + box_w - 1, by + box_h - 1], outline=(80, 80, 80), width=1)
465+
leg_bg = (20, 20, 36) if dark else (240, 240, 240)
466+
leg_border = (80, 80, 80) if dark else (160, 160, 160)
467+
leg_ext_text = (220, 220, 220) if dark else (40, 40, 40)
468+
leg_count_text = (160, 160, 160) if dark else (80, 80, 80)
469+
leg_more_text = (120, 120, 120) if dark else (100, 100, 100)
470+
leg_swatch_outline = (255, 255, 255) if dark else (0, 0, 0)
471+
draw.rectangle([bx, by, bx + box_w - 1, by + box_h - 1], fill=leg_bg)
472+
draw.rectangle([bx, by, bx + box_w - 1, by + box_h - 1], outline=leg_border, width=1)
460473

461474
for ri, ext in enumerate(top):
462475
rgba = color_map.get(ext, (0.5, 0.5, 0.5, 1.0))
@@ -467,20 +480,20 @@ def _draw_legend(
467480
draw.rectangle(
468481
[ex, sy, ex + SWATCH_PX - 1, sy + SWATCH_PX - 1],
469482
fill=rgb,
470-
outline=(255, 255, 255),
483+
outline=leg_swatch_outline,
471484
width=1,
472485
)
473486
draw.text(
474487
(ex + SWATCH_PX + LEG_PAD, row_mid),
475488
ext,
476-
fill=(220, 220, 220),
489+
fill=leg_ext_text,
477490
font=font,
478491
anchor="lm",
479492
)
480493
draw.text(
481494
(bx + box_w - LEG_PAD, row_mid),
482495
str(ext_counts[ext]),
483-
fill=(160, 160, 160),
496+
fill=leg_count_text,
484497
font=font,
485498
anchor="rm",
486499
)
@@ -490,7 +503,7 @@ def _draw_legend(
490503
draw.text(
491504
(bx + LEG_PAD + SWATCH_PX + LEG_PAD, row_mid),
492505
more_label,
493-
fill=(120, 120, 120),
506+
fill=leg_more_text,
494507
font=font,
495508
anchor="lm",
496509
)
@@ -547,6 +560,7 @@ def create_treemap(
547560
rect_map_out: dict[str, tuple[int, int, int, int]] | None = None,
548561
title_suffix: str | None = None,
549562
progress: float | None = None,
563+
dark: bool = True,
550564
) -> io.BytesIO:
551565
"""Render a nested squarified treemap and return it as a PNG in a BytesIO buffer.
552566
@@ -568,7 +582,8 @@ def create_treemap(
568582
exts = collect_extensions(root_node)
569583
color_map = assign_colors(exts, colormap)
570584

571-
img = Image.new("RGB", (width_px, height_px), color=(26, 26, 46))
585+
canvas_bg = (26, 26, 46) if dark else (255, 255, 255)
586+
img = Image.new("RGB", (width_px, height_px), color=canvas_bg)
572587
idraw = ImageDraw.Draw(img)
573588
font = _font(font_size, bold=True)
574589

@@ -596,6 +611,7 @@ def create_treemap(
596611
img,
597612
root_label=root_label,
598613
rect_map=_tile_rects,
614+
dark=dark,
599615
)
600616

601617
# Batch cushion: one PIL→numpy→PIL round-trip for all tiles instead of one per tile.
@@ -626,7 +642,7 @@ def create_treemap(
626642
corner = _best_corner(root_node, width_px, height_px)
627643
ext_counts = _collect_ext_counts(root_node)
628644
_draw_legend(
629-
idraw, ext_counts, color_map, width_px, height_px, corner, overlay_font, legend
645+
idraw, ext_counts, color_map, width_px, height_px, corner, overlay_font, legend, dark
630646
)
631647

632648
pnginfo = PngImagePlugin.PngInfo()

src/dirplot/replay_scanner.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ def _render_replay_frame_worker(args: tuple[Any, ...]) -> tuple[int, bytes, Rect
140140
font_size,
141141
colormap,
142142
cushion,
143+
dark,
143144
) = args
144145

145146
from datetime import datetime
@@ -168,5 +169,6 @@ def _render_replay_frame_worker(args: tuple[Any, ...]) -> tuple[int, bytes, Rect
168169
rect_map_out=rect_map,
169170
title_suffix=dt_str,
170171
progress=progress,
172+
dark=dark,
171173
)
172174
return (orig_i, buf.read(), rect_map)

0 commit comments

Comments
 (0)