heatmap_cli 0.22.33__tar.gz → 0.22.35__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {heatmap_cli-0.22.33/src/heatmap_cli.egg-info → heatmap_cli-0.22.35}/PKG-INFO +1 -1
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/pyproject.toml +1 -1
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/src/heatmap_cli/__init__.py +3 -1
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/src/heatmap_cli/__main__.py +2 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/src/heatmap_cli/cli.py +8 -7
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/src/heatmap_cli/heatmap.py +32 -33
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35/src/heatmap_cli.egg-info}/PKG-INFO +1 -1
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/LICENSE.md +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/README.md +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/setup.cfg +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/src/heatmap_cli.egg-info/SOURCES.txt +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/src/heatmap_cli.egg-info/dependency_links.txt +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/src/heatmap_cli.egg-info/entry_points.txt +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/src/heatmap_cli.egg-info/requires.txt +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/src/heatmap_cli.egg-info/top_level.txt +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_animate_by_week.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_annotate_flag.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_cbar_flag.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_cmap_option.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_cmax_option.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_cmin_option.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_debug_flag.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_demo_option.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_end_date_option.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_env_flag.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_heatmap_direct.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_heatmap_errors.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_heatmap_utils.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_help_flag.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_output_dir_option.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_purge_flag.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_quiet_flag.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_start_date_option.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_title_option.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_verbose_flag.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_version_flag.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_week_option.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_year_option.py +0 -0
- {heatmap_cli-0.22.33 → heatmap_cli-0.22.35}/tests/test_yes_flag.py +0 -0
|
@@ -25,7 +25,7 @@ classifiers = [
|
|
|
25
25
|
"Programming Language :: Python",
|
|
26
26
|
"Topic :: Scientific/Engineering :: Visualization",
|
|
27
27
|
]
|
|
28
|
-
version = "0.22.
|
|
28
|
+
version = "0.22.35"
|
|
29
29
|
keywords = ["heatmap", "cli", "visualization", "calendar", "data-viz"]
|
|
30
30
|
dependencies = [
|
|
31
31
|
"matplotlib",
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
"""A console program that generates a yearly calendar heatmap."""
|
|
17
17
|
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
18
20
|
import argparse
|
|
19
21
|
import datetime
|
|
20
22
|
import logging
|
|
@@ -56,7 +58,7 @@ def setup_logging(config: argparse.Namespace) -> None:
|
|
|
56
58
|
|
|
57
59
|
match config:
|
|
58
60
|
case argparse.Namespace(quiet=True):
|
|
59
|
-
logging.disable(
|
|
61
|
+
logging.disable()
|
|
60
62
|
return
|
|
61
63
|
case argparse.Namespace(debug=True):
|
|
62
64
|
level = logging.DEBUG
|
|
@@ -20,12 +20,17 @@ changelog: https://github.com/kianmeng/heatmap_cli/blob/master/CHANGELOG.md
|
|
|
20
20
|
issues: https://github.com/kianmeng/heatmap_cli/issues
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
23
25
|
import argparse
|
|
24
26
|
import datetime
|
|
25
27
|
import multiprocessing
|
|
26
28
|
import sys
|
|
27
|
-
from collections.abc import Sequence
|
|
28
29
|
from itertools import zip_longest
|
|
30
|
+
from typing import TYPE_CHECKING
|
|
31
|
+
|
|
32
|
+
if TYPE_CHECKING:
|
|
33
|
+
from collections.abc import Sequence
|
|
29
34
|
|
|
30
35
|
from heatmap_cli import (
|
|
31
36
|
CMAPS,
|
|
@@ -57,6 +62,7 @@ IMAGE_FORMATS = [
|
|
|
57
62
|
logger = multiprocessing.get_logger()
|
|
58
63
|
|
|
59
64
|
DEFAULT_CMAP = "RdYlGn_r"
|
|
65
|
+
CMAP_ITEMS_PER_ROW = 6
|
|
60
66
|
|
|
61
67
|
|
|
62
68
|
def _valid_date(s: str) -> str:
|
|
@@ -361,9 +367,8 @@ def _generate_cmap_help() -> str:
|
|
|
361
367
|
cmap_default = f" (default: '{DEFAULT_CMAP}')"
|
|
362
368
|
|
|
363
369
|
# Always show all colormaps in help text
|
|
364
|
-
items_per_row = 6
|
|
365
370
|
cmap_choices = "\nAvailable colormaps:\n"
|
|
366
|
-
cmap_bygroups = zip_longest(*(iter(CMAPS),) *
|
|
371
|
+
cmap_bygroups = zip_longest(*(iter(CMAPS),) * CMAP_ITEMS_PER_ROW)
|
|
367
372
|
for cmap_bygroup in cmap_bygroups:
|
|
368
373
|
cmap_choices += " " + ", ".join(filter(None, cmap_bygroup)) + "\n"
|
|
369
374
|
|
|
@@ -438,10 +443,6 @@ def main(args: Sequence[str] | None = None) -> None:
|
|
|
438
443
|
"""
|
|
439
444
|
argv = args if args is not None else sys.argv[1:]
|
|
440
445
|
parser = build_parser()
|
|
441
|
-
# Use parse_known_args to potentially capture debug flag even if other args
|
|
442
|
-
# fail. However, we'll rely on the full parse_args within the try block for
|
|
443
|
-
# simplicity here.
|
|
444
|
-
parsed_args = None
|
|
445
446
|
debug_active = False # Initialize to False, will be set after parsing
|
|
446
447
|
|
|
447
448
|
try:
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
"""Main logic to generate heatmap."""
|
|
17
17
|
|
|
18
|
-
import
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
19
20
|
import copy
|
|
20
21
|
import io
|
|
21
22
|
import multiprocessing
|
|
@@ -24,7 +25,10 @@ import shutil
|
|
|
24
25
|
import webbrowser
|
|
25
26
|
from datetime import datetime, timedelta, timezone
|
|
26
27
|
from pathlib import Path
|
|
27
|
-
from typing import Any
|
|
28
|
+
from typing import TYPE_CHECKING, Any
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
import argparse
|
|
28
32
|
|
|
29
33
|
import matplotlib as mpl
|
|
30
34
|
import matplotlib.pyplot as plt
|
|
@@ -73,7 +77,6 @@ COLORBAR_ANNOTATION_SUFFIX = ", count: nearest hundred"
|
|
|
73
77
|
|
|
74
78
|
# Define constants for count truncation
|
|
75
79
|
TRUNCATE_DIVISOR = 100
|
|
76
|
-
TRUNCATE_ROUND_DECIMALS = -2
|
|
77
80
|
|
|
78
81
|
# Define constants for week ranges
|
|
79
82
|
ALL_WEEKS_RANGE = range(1, 54)
|
|
@@ -375,7 +378,7 @@ def _filter_and_pivot_data(
|
|
|
375
378
|
def _truncate_rounded_count(count: float) -> int:
|
|
376
379
|
"""Truncate and round count values to fit them in heatmap box.
|
|
377
380
|
|
|
378
|
-
|
|
381
|
+
Divides the count by 100 and rounds to the nearest integer.
|
|
379
382
|
This transformation is applied when annotations are enabled to simplify
|
|
380
383
|
large numbers and make them fit visually within the heatmap cells.
|
|
381
384
|
For example, 12345 becomes 123, 5678 becomes 57.
|
|
@@ -386,7 +389,7 @@ def _truncate_rounded_count(count: float) -> int:
|
|
|
386
389
|
Returns:
|
|
387
390
|
int: Truncated count value (divided by 100).
|
|
388
391
|
"""
|
|
389
|
-
return
|
|
392
|
+
return round(count / TRUNCATE_DIVISOR)
|
|
390
393
|
|
|
391
394
|
|
|
392
395
|
def _setup_plot_artists(
|
|
@@ -401,7 +404,7 @@ def _setup_plot_artists(
|
|
|
401
404
|
Args:
|
|
402
405
|
cmap (str): Colormap name used for the heatmap.
|
|
403
406
|
config (argparse.Namespace): Config from command line arguments.
|
|
404
|
-
dataframe (pd.
|
|
407
|
+
dataframe (pd.DataFrame): DataFrame with data.
|
|
405
408
|
|
|
406
409
|
Returns:
|
|
407
410
|
plt.Axes: The Axes object with the heatmap plotted.
|
|
@@ -444,7 +447,7 @@ def _generate_heatmap(
|
|
|
444
447
|
seq (int): Sequence number for generated heatmap image file.
|
|
445
448
|
cmap (str): Colormap name used for the heatmap.
|
|
446
449
|
config (argparse.Namespace): Config from command line arguments.
|
|
447
|
-
dataframe (pd.
|
|
450
|
+
dataframe (pd.DataFrame): DataFrame with data loaded from
|
|
448
451
|
CSV file.
|
|
449
452
|
return_image (bool): If True, returns a PIL Image object instead of
|
|
450
453
|
saving to disk.
|
|
@@ -506,9 +509,9 @@ def _configure_heatmap_options(
|
|
|
506
509
|
"cbar_kws": cbar_options,
|
|
507
510
|
}
|
|
508
511
|
|
|
509
|
-
if config.cmap_min:
|
|
512
|
+
if config.cmap_min is not None:
|
|
510
513
|
options["vmin"] = config.cmap_min
|
|
511
|
-
if config.cmap_max:
|
|
514
|
+
if config.cmap_max is not None:
|
|
512
515
|
options["vmax"] = config.cmap_max
|
|
513
516
|
|
|
514
517
|
if config.annotate:
|
|
@@ -545,7 +548,11 @@ def _configure_cbar(res: plt.Axes) -> None:
|
|
|
545
548
|
Args:
|
|
546
549
|
res (plt.Axes): The matplotlib axes object returned by seaborn.
|
|
547
550
|
"""
|
|
551
|
+
if not res.collections:
|
|
552
|
+
return
|
|
548
553
|
cbar = res.collections[0].colorbar
|
|
554
|
+
if cbar is None:
|
|
555
|
+
return
|
|
549
556
|
cbar.set_label(cbar.ax.get_xlabel(), rotation=0, labelpad=8, loc="left")
|
|
550
557
|
|
|
551
558
|
|
|
@@ -626,7 +633,7 @@ def _open_heatmap(filename: Path) -> None:
|
|
|
626
633
|
Returns:
|
|
627
634
|
None
|
|
628
635
|
"""
|
|
629
|
-
file_uri =
|
|
636
|
+
file_uri = filename.resolve().as_uri()
|
|
630
637
|
webbrowser.open(file_uri)
|
|
631
638
|
logger.info("Open heatmap: %s using default program.", filename.resolve())
|
|
632
639
|
|
|
@@ -720,29 +727,21 @@ def _refresh_output_dir(config: argparse.Namespace) -> None:
|
|
|
720
727
|
"""
|
|
721
728
|
output_dir = _get_output_dir(config)
|
|
722
729
|
|
|
723
|
-
# Determine if purging is required based on --purge and --yes flags.
|
|
724
|
-
should_purge = False
|
|
725
730
|
if config.purge:
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
shutil.rmtree(output_dir)
|
|
741
|
-
except OSError as e:
|
|
742
|
-
logger.error("Error removing directory: %s - %s.", output_dir, e)
|
|
743
|
-
# Depending on desired error handling, you might want to raise an
|
|
744
|
-
# exception here.
|
|
745
|
-
return
|
|
731
|
+
should_purge = config.yes or input(
|
|
732
|
+
f"Are you sure to purge output folder: {output_dir}? [y/N] "
|
|
733
|
+
).lower() in {"y", "yes"}
|
|
734
|
+
if should_purge and output_dir.exists():
|
|
735
|
+
logger.info("Purging output folder: %s", output_dir.absolute())
|
|
736
|
+
try:
|
|
737
|
+
shutil.rmtree(output_dir)
|
|
738
|
+
except OSError as e:
|
|
739
|
+
logger.error(
|
|
740
|
+
"Error removing directory: %s - %s.",
|
|
741
|
+
output_dir,
|
|
742
|
+
e,
|
|
743
|
+
)
|
|
744
|
+
return
|
|
746
745
|
|
|
747
746
|
# Ensure the output directory exists for writing files.
|
|
748
747
|
# This will create it if it doesn't exist or was just purged.
|
|
@@ -751,7 +750,7 @@ def _refresh_output_dir(config: argparse.Namespace) -> None:
|
|
|
751
750
|
|
|
752
751
|
|
|
753
752
|
def _get_output_dir(config: argparse.Namespace) -> Path:
|
|
754
|
-
"""Get the
|
|
753
|
+
"""Get the output directory path, resolved relative to CWD.
|
|
755
754
|
|
|
756
755
|
Args:
|
|
757
756
|
config (argparse.Namespace): Config from command line arguments.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|