From 46dadbdd7822ea821cd89d282cbf65ad2224863b Mon Sep 17 00:00:00 2001 From: dhmay Date: Fri, 2 Jan 2026 11:10:09 -0800 Subject: [PATCH 1/2] numeric stats on jammer plot, and fixes --- src/jamstats/plots/advanced_plots.py | 13 +++++---- src/jamstats/plots/basic_plots.py | 25 +++++++++++------ src/jamstats/plots/plot_util.py | 41 ++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/src/jamstats/plots/advanced_plots.py b/src/jamstats/plots/advanced_plots.py index 9c58c92..cb98f4a 100644 --- a/src/jamstats/plots/advanced_plots.py +++ b/src/jamstats/plots/advanced_plots.py @@ -9,7 +9,8 @@ import pandas as pd from jamstats.plots.plot_util import ( make_team_color_palette, - wordwrap_x_labels + wordwrap_x_labels, + remove_ax_legend ) from jamstats.plots.plot_util import DerbyPlot import matplotlib.patches as mpatches @@ -56,7 +57,7 @@ def plot(self, derby_game: DerbyGame) -> Figure: sum(pdf_jammer_jamcounts.team == derby_game.team_2_name)] }), hue="team", ax=ax, palette=team_color_palette) - ax.get_legend().remove() + remove_ax_legend(ax) # word-wrap too-long team names wordwrap_x_labels(ax) ax.set_title("Jammers per team") @@ -65,7 +66,7 @@ def plot(self, derby_game: DerbyGame) -> Figure: sns.violinplot(x="team", y="jam_count", data=pdf_jammer_jamcounts, cut=0, ax=ax, hue="team", palette=team_color_palette, inner="stick") - ax.get_legend().remove() + remove_ax_legend(ax) ax.set_title("Jams per jammer") ax.set_ylabel("Jams per jammer") # word-wrap too-long team names @@ -90,7 +91,7 @@ def plot(self, derby_game: DerbyGame) -> Figure: ax.set_xlabel("# jams") # word-wrap too-long team names wordwrap_x_labels(ax) - ax.legend().remove() + remove_ax_legend(ax) f.set_size_inches(14,6) f.tight_layout() @@ -181,7 +182,7 @@ def plot(self, derby_game: DerbyGame) -> Figure: for i in range(len(pdf_jambools.columns)): ax.text(i + 0.25, -.5, pdf_jambools.columns[i], rotation=90, size="large") - ax.get_legend().remove() + remove_ax_legend(ax) ax.set_xlabel("") ax.set_ylabel("") ax.set_xticks([x+.5 for x in range(len(pdf_jambool_heatmap.columns))]) @@ -295,7 +296,7 @@ def plot(self, derby_game: DerbyGame) -> Figure: inner="stick", palette=team_color_palette) ax.set_ylabel("Time to Initial (s)") ax.set_title("Time to Initial per jam") - ax.get_legend().remove() + remove_ax_legend(ax) # word-wrap too-long team names wordwrap_x_labels(ax) diff --git a/src/jamstats/plots/basic_plots.py b/src/jamstats/plots/basic_plots.py index 59de0bb..8900d7d 100644 --- a/src/jamstats/plots/basic_plots.py +++ b/src/jamstats/plots/basic_plots.py @@ -9,7 +9,9 @@ import logging from jamstats.plots.plot_util import ( make_team_color_palette, - wordwrap_x_labels + wordwrap_x_labels, + remove_ax_legend, + add_number_to_barplot ) import matplotlib.patches as mpatches from matplotlib.pyplot import Figure @@ -62,38 +64,43 @@ def plot(self, derby_game: DerbyGame) -> Figure: ax = ax0 sns.barplot(y="Jammer", x="Jams", hue="Jammer", data=pdf_jammer_data, ax=ax, palette=mypalette) + add_number_to_barplot(ax, pdf_jammer_data, "Jams") ax.set_ylabel("") - ax.get_legend().remove() + remove_ax_legend(ax) ax = ax1 sns.barplot(y="Jammer", x="Total Score", hue="Jammer", data=pdf_jammer_data, ax=ax, palette=mypalette) + add_number_to_barplot(ax, pdf_jammer_data, "Total Score") ax.set_yticks([]) ax.set_ylabel("") - ax.get_legend().remove() + remove_ax_legend(ax) ax = ax2 sns.barplot(y="Jammer", x="Mean Net Points", hue="Jammer", data=pdf_jammer_data, ax=ax, palette=mypalette) + add_number_to_barplot(ax, pdf_jammer_data, "Mean Net Points") ax.set_xlabel("Mean Net Points/Jam\n(own - opposing)") ax.set_yticks([]) ax.set_ylabel("") - ax.get_legend().remove() + remove_ax_legend(ax) ax = ax3 sns.barplot(y="Jammer", x="Proportion Lead", hue="Jammer", data=pdf_jammer_data, ax=ax, palette=mypalette) + add_number_to_barplot(ax, pdf_jammer_data, "Proportion Lead") ax.set_xlim(0,1) ax.set_yticks([]) ax.set_ylabel("") - ax.get_legend().remove() + remove_ax_legend(ax) ax = ax4 sns.barplot(y="Jammer", x="Mean Time to Initial", hue="Jammer", data=pdf_jammer_data, ax=ax, palette=mypalette) + add_number_to_barplot(ax, pdf_jammer_data, "Mean Time to Initial") ax.set_yticks([]) ax.set_ylabel("") - ax.get_legend().remove() + remove_ax_legend(ax) f.set_size_inches(16, min(2 + len(pdf_jammer_data), 11)) f.suptitle(f"Jammer Stats: {team_name}") @@ -216,7 +223,7 @@ def plot(self, derby_game: DerbyGame) -> Figure: sns.barplot(y="prd_jam", x="Team with Lead", hue="Team with Lead", data=pdf_for_plot_lost, ax=ax, palette='dark:black') if ax.get_legend() is not None: - ax.get_legend().remove() + remove_ax_legend(ax) ax.set_ylabel("Jams") ax.set_title("Jams with Lead\n(black=lost, gray=not called)") @@ -426,8 +433,8 @@ def plot(self, derby_game: DerbyGame) -> Figure: penalty_plot_is_go = True except Exception as e: - logger.warn(f"Failed to make skater penalty subplot:") - logger.warn(traceback.format_exc()) + logger.warning(f"Failed to make skater penalty subplot:") + logger.warning(traceback.format_exc()) f, dummy_axis = plt.subplots() dummy_axis.set_xticks([]) diff --git a/src/jamstats/plots/plot_util.py b/src/jamstats/plots/plot_util.py index d1812c7..daeebc9 100644 --- a/src/jamstats/plots/plot_util.py +++ b/src/jamstats/plots/plot_util.py @@ -11,6 +11,7 @@ from matplotlib.pyplot import Figure from pandas.api.types import CategoricalDtype from PIL import ImageColor +import pandas as pd from abc import ABC, abstractmethod @@ -209,6 +210,46 @@ def build_anonymizer_map(names: Iterable[str]) -> Dict[str, str]: for i in range(len(names_set_list)) } +def remove_ax_legend(ax): + """Utility method to remove a legend from an ax, and fail silently. + + Args: + ax: _description_ + """ + try: + ax.get_legend().remove() + except Exception: + pass + +def add_number_to_barplot(ax, pdf: pd.DataFrame, column: str): + """Utility method to add a number to a barplot, derived + from a column in a pdf + + Args: + ax: ax containing a barplot + pdf (pd.DataFrame): dataframe + column (str): column name + """ + numbers = list(pdf[column]) + max_val = max(numbers) + text_x_pos = max_val / 10 + + vals_are_ints = True + for number in numbers: + if int(number) != number: + vals_are_ints = False + break + if vals_are_ints: + number_strs = [str(x) for x in numbers] + else: + number_strs = [f"{x:.1f}" for x in numbers] + for i in range(len(numbers)): + if number_strs[i] == "nan": + number_strs[i] = "" + ax.text(text_x_pos, i, number_strs[i], size="small", + horizontalalignment="left", + verticalalignment="center") + ANONYMIZED_SKATER_NAMES = [ "Middle Skull Crush", From 3599071ca152acde0d2956aefa2378c0606b9d6b Mon Sep 17 00:00:00 2001 From: dhmay Date: Fri, 2 Jan 2026 11:49:35 -0800 Subject: [PATCH 2/2] 3.5.2 --- resources/jamstats_version.txt | 2 +- setup.py | 2 +- setup_nogui.py | 2 +- src/jamstats/resources/jamstats_version.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/jamstats_version.txt b/resources/jamstats_version.txt index d5c0c99..87ce492 100644 --- a/resources/jamstats_version.txt +++ b/resources/jamstats_version.txt @@ -1 +1 @@ -3.5.1 +3.5.2 diff --git a/setup.py b/setup.py index debc7c8..a621c37 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='jamstats', - version='3.5.1', + version='3.5.2', description='Data processing, stats and plots on roller derby scoreboard JSON files', author='Damon May', package_dir={"":"src"}, diff --git a/setup_nogui.py b/setup_nogui.py index fb0c6ce..3254837 100644 --- a/setup_nogui.py +++ b/setup_nogui.py @@ -2,7 +2,7 @@ setup( name='jamstats-nogui', - version='3.5.1', + version='3.5.2', description='Data processing, stats and plots on roller derby scoreboard JSON files. No-GUI version.', author='Damon May', package_dir={"":"src"}, diff --git a/src/jamstats/resources/jamstats_version.txt b/src/jamstats/resources/jamstats_version.txt index d5c0c99..87ce492 100644 --- a/src/jamstats/resources/jamstats_version.txt +++ b/src/jamstats/resources/jamstats_version.txt @@ -1 +1 @@ -3.5.1 +3.5.2