Skip to content

Commit

Permalink
Avoid updating unchange images by reporter + layout improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
theroggy committed Nov 24, 2023
1 parent 5b30309 commit 746e6f1
Showing 1 changed file with 57 additions and 61 deletions.
118 changes: 57 additions & 61 deletions reporter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
"""
Module to generate reports for benchmarks.
"""
Expand All @@ -7,12 +6,15 @@
import math
import os
from pathlib import Path
from typing import Optional, Tuple
import shutil
import tempfile
from typing import Literal, Optional, Tuple

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pandas.api
from PIL import Image

A4_LONG_SIDE = 11.69
A4_SHORT_SIDE = 8.27
Expand Down Expand Up @@ -59,7 +61,7 @@ def format_run_details(input: dict) -> str:
df=package_operation_df,
title=f"{package}-{operation}\n({operation_descr})",
size=(8, 6),
label_points="above",
print_labels_on_points=True,
y_value_formatter="{0:.2f}",
output_path=results_report_path,
)
Expand Down Expand Up @@ -92,7 +94,7 @@ def format_run_details(input: dict) -> str:
title="Comparison of libraries, time in sec",
output_path=results_report_path,
yscale="log",
label_points="right",
print_labels_on_points=True,
y_value_formatter="{0:.0f}",
size=(8, 6),
linestyle="None",
Expand All @@ -104,13 +106,25 @@ def save_chart(
df: pd.DataFrame,
title: str,
output_path: Path,
yscale: Optional[str] = None,
yscale: Optional[Literal["linear", "log", "symlog", "logit"]] = None,
y_value_formatter: Optional[str] = None,
label_points: Optional[str] = None,
print_labels_on_points: bool = False,
open_output_file: bool = False,
size: Tuple[float, float] = (8, 4),
plot_kind: str = "line",
gridlines: Optional[str] = None,
plot_kind: Literal[
"line",
"bar",
"barh",
"hist",
"box",
"kde",
"density",
"area",
"pie",
"scatter",
"hexbin",
] = "line",
gridlines: Optional[Literal["both", "x", "y"]] = None,
linestyle: Optional[str] = None,
):
"""
Expand All @@ -120,13 +134,13 @@ def save_chart(
df (pd.DataFrame): _description_
title (str): _description_
output_path (Path): _description_
yscale (str): y scale to use.
yscale (Literal["linear", "log", "symlog", "logit"], optional): y scale to use.
y_value_formatter (str, optional): a formatter for the y axes and
labels. Examples:
- {0:.2%} for a percentage.
- {0:.2f} for a float with two decimals.
Defaults to None.
label_points (str, optional): _description_. Defaults to None.
print_labels_on_points (bool, optional): _description_. Defaults to False.
open_output_file (bool, optional): _description_. Defaults to False.
size (Tuple[float, float], optional): _description_. Defaults to (8, 4).
plot_kind (str, optional): _description_. Defaults to "line".
Expand Down Expand Up @@ -154,97 +168,67 @@ def save_chart(
f"df has non-numeric columns, so cannot be plotted: {non_numeric_columns}"
)

# Init some things based on input
rot = 90

# Prepare plot figure and axes
fig, axs = plt.subplots(figsize=(size))
# Make sure all x axis values are shown
axs.set_xticks(range(len(df)))
if yscale is not None:
plt.yscale(yscale) # type: ignore
plt.yscale(yscale)

# Plot
df.plot(
ax=axs, kind=plot_kind, rot=90, title=title, linestyle=linestyle # type: ignore
)
df.plot(ax=axs, kind=plot_kind, rot=rot, title=title, linestyle=linestyle)

# Show y axes as percentages is asked
if y_value_formatter is not None:
axs.yaxis.set_major_formatter(
plt.FuncFormatter(y_value_formatter.format) # type: ignore
)
axs.yaxis.set_minor_formatter(
plt.FuncFormatter(y_value_formatter.format) # type: ignore
)
axs.yaxis.set_major_formatter(plt.FuncFormatter(y_value_formatter.format))
axs.yaxis.set_minor_formatter(plt.FuncFormatter(y_value_formatter.format))

# Show grid lines if specified
if gridlines is not None:
axs.grid(axis=gridlines, which="both") # type: ignore
axs.grid(axis=gridlines, which="both")

# Set different markers + print labels
# Set different markers for each line + get mn/max values + print labels
markers = ("+", ".", "o", "*", "v", "^", "<", ">", "1", "2", "3", "4")
markers = ("+", ".", "o", "*")
max_y_value = None
min_y_value = None

xytext = (0, 0)
horizontal_alignment = "center"
vertical_alignment = "center"
if label_points is not None:
if label_points in ["alternate", "below"]:
xytext = (0, -5)
vertical_alignment = "top"
elif label_points == "above":
xytext = (0, 5)
vertical_alignment = "bottom"
elif label_points == "left":
xytext = (-5, 0)
horizontal_alignment = "right"
elif label_points == "right":
xytext = (5, 0)
horizontal_alignment = "left"
else:
raise ValueError(
f"Invalid value for labelpoints: {label_points}, should be one of "
"alternate, below, above, left, right"
)

for i, line in enumerate(axs.get_lines()):
line.set_marker(markers[i % len(markers)])

label_above_line = True
for index, row in enumerate(df.itertuples()):
for row_fieldname, row_fieldvalue in row._asdict().items():
if row_fieldname != "Index":
if max_y_value is None or row_fieldvalue > max_y_value:
max_y_value = row_fieldvalue
if min_y_value is None or row_fieldvalue < min_y_value:
min_y_value = row_fieldvalue

if label_points is not None:
if print_labels_on_points is True:
# Format label
if y_value_formatter is not None:
text = y_value_formatter.format(row_fieldvalue)
else:
text = str(row_fieldvalue)

# Label below or above line? + switch
if label_points == "alternate":
if xytext[1] > 0:
xytext = (0, -5)
vertical_alignment = "top"
else:
xytext = (0, 5)
vertical_alignment = "bottom"
if label_above_line is True:
xytext = (0, 5)
label_above_line = False
else:
xytext = (0, -15)
label_above_line = True

axs.annotate(
text=text, # type: ignore
text=text,
# s=text,
# xy=(row.Index, row_fieldvalue),
xy=(index, row_fieldvalue),
xytext=xytext,
textcoords="offset points",
ha=horizontal_alignment,
va=vertical_alignment,
fontsize="small",
fontstyle="normal",
ha="center",
)

# Set bottom and top values for y axis
Expand All @@ -259,8 +243,20 @@ def save_chart(
plt.legend(loc="center left", bbox_to_anchor=(1, 0.5))
plt.tight_layout()

# Save and open if wanted
fig.savefig(str(output_path))
# Save the file if it doesn't exist yet
if not output_path.exists():
fig.savefig(str(output_path))
else:
# If it exists already, only save it if it has changed
with tempfile.TemporaryDirectory() as tmp_dir:
tmp_output_path = Path(tmp_dir) / output_path.name
fig.savefig(tmp_output_path)
img_new = np.asarray(Image.open(tmp_output_path))
img_old = np.asarray(Image.open(output_path))
if not np.array_equal(img_new, img_old):
shutil.move(tmp_output_path, output_path)

# Open if wanted
if open_output_file is True:
os.startfile(output_path)

Expand Down

0 comments on commit 746e6f1

Please sign in to comment.