From 213cb2a1ccfb5a9d440630e5eb9f2e17f81a85e3 Mon Sep 17 00:00:00 2001 From: Alex Wolf Date: Sat, 11 Oct 2025 18:19:53 +0200 Subject: [PATCH 01/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Add=20a=20plotter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- githubcontribs/__init__.py | 1 + githubcontribs/_plotter.py | 87 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 githubcontribs/_plotter.py diff --git a/githubcontribs/__init__.py b/githubcontribs/__init__.py index 83cb245..7ab2419 100644 --- a/githubcontribs/__init__.py +++ b/githubcontribs/__init__.py @@ -3,3 +3,4 @@ __version__ = "0.1.0" # denote a pre-release for 0.1.0 with 0.1rc1 from ._fetcher import Fetcher +from ._plotter import Plotter diff --git a/githubcontribs/_plotter.py b/githubcontribs/_plotter.py new file mode 100644 index 0000000..b748fc3 --- /dev/null +++ b/githubcontribs/_plotter.py @@ -0,0 +1,87 @@ +import matplotlib.pyplot as plt +import pandas as pd +import seaborn as sns + + +def setup_svg_output(): + """Configure matplotlib to output SVG in Jupyter notebooks.""" + try: + from IPython import get_ipython + + ipython = get_ipython() + if ipython is not None: + ipython.run_line_magic("config", "InlineBackend.figure_formats = ['svg']") + except (ImportError, AttributeError): + # Not in IPython/Jupyter environment, or magic not available + pass + + +class Plotter: + def __init__(self, df: pd.DataFrame): + self.df = df + setup_svg_output() + sns.set_theme() + + def plot_contributor_activity(self, top_n: int = 10): + """A horizontal bar plot showing contribution types per author. + + Args: + top_n: Number of top contributors to show. Defaults to 10. + """ + df = self.df + + commits_df: pd.DataFrame = df[df.type == "commit"] + issues_df: pd.DataFrame = df[df.type == "issue"] + prs_df: pd.DataFrame = df[df.type == "pr"] + + # Prepare the data + contributors_data = pd.concat( + [ + commits_df.groupby("author").size().rename("Commits"), + issues_df.groupby("author").size().rename("Issues"), + prs_df.groupby("author").size().rename("Pull Requests"), + ], + axis=1, + ).fillna(0) + + # Sort by total contributions and get top N + contributors_data["Total"] = contributors_data.sum(axis=1) + contributors_data = contributors_data.sort_values("Total", ascending=True).tail( + top_n + ) + contributors_data = contributors_data.drop("Total", axis=1) + + # Reshape data for seaborn + plot_data = contributors_data.reset_index().melt( + id_vars="author", var_name="Activity Type", value_name="Count" + ) + + # Set up the plot style + plt.figure(figsize=(12, max(8, top_n * 0.5))) + + # Create the plot + sns.barplot( + data=plot_data, + y="author", + x="Count", + hue="Activity Type", + palette=["#2ecc71", "#3498db", "#e74c3c"], + orient="h", + ) + + # Add value labels + for c in plt.gca().containers: + plt.gca().bar_label(c, label_type="center", fmt="%d") + + # Customize the plot + plt.title("Contribution Types by Author") + plt.xlabel("Number of Contributions") + plt.ylabel("Author") + + # Adjust legend position + plt.legend(bbox_to_anchor=(1, 1.02), loc="upper left") + + # Ensure all labels are visible + plt.tight_layout() + + # No need to return anything - plot is displayed in notebook From 09dab12b8bdaa1f01082a60544c55c8974350778 Mon Sep 17 00:00:00 2001 From: Alex Wolf Date: Sat, 11 Oct 2025 18:46:28 +0200 Subject: [PATCH 02/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Add=20a=20plotter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- githubcontribs/_plotter.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/githubcontribs/_plotter.py b/githubcontribs/_plotter.py index b748fc3..0d91072 100644 --- a/githubcontribs/_plotter.py +++ b/githubcontribs/_plotter.py @@ -22,11 +22,14 @@ def __init__(self, df: pd.DataFrame): setup_svg_output() sns.set_theme() - def plot_contributor_activity(self, top_n: int = 10): + def plot_contributor_activity( + self, top_n: int = 10, exclude_author: str = "github-actions[bot]" + ): """A horizontal bar plot showing contribution types per author. Args: top_n: Number of top contributors to show. Defaults to 10. + exclude_author: Author to exclude from the plot. Defaults to "github-actions[bot]". """ df = self.df @@ -56,6 +59,16 @@ def plot_contributor_activity(self, top_n: int = 10): id_vars="author", var_name="Activity Type", value_name="Count" ) + # Calculate date range from the dataframe + min_date = pd.to_datetime(df["date"]).min() + max_date = pd.to_datetime(df["date"]).max() + date_range = ( + f"{min_date.strftime('%Y-%m-%d')} to {max_date.strftime('%Y-%m-%d')}" + ) + + # Get all unique repositories + repos = ", ".join(sorted(df["repo"].unique())) + # Set up the plot style plt.figure(figsize=(12, max(8, top_n * 0.5))) @@ -73,9 +86,9 @@ def plot_contributor_activity(self, top_n: int = 10): for c in plt.gca().containers: plt.gca().bar_label(c, label_type="center", fmt="%d") - # Customize the plot - plt.title("Contribution Types by Author") - plt.xlabel("Number of Contributions") + # Customize the plot with date range and repos in title + plt.title(f"Contribution to repositories by author: {repos}\n{date_range}") + plt.xlabel("Number of contributions") plt.ylabel("Author") # Adjust legend position @@ -83,5 +96,3 @@ def plot_contributor_activity(self, top_n: int = 10): # Ensure all labels are visible plt.tight_layout() - - # No need to return anything - plot is displayed in notebook From 2c42aea8ca8876fe57844a907e891ac5737f16fb Mon Sep 17 00:00:00 2001 From: Alex Wolf Date: Sat, 11 Oct 2025 18:53:27 +0200 Subject: [PATCH 03/12] =?UTF-8?q?=E2=9C=85=20Add=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/quickstart.ipynb | 11 +++++++++++ githubcontribs/_plotter.py | 12 ++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/quickstart.ipynb b/docs/quickstart.ipynb index 1634c3f..8e2e782 100644 --- a/docs/quickstart.ipynb +++ b/docs/quickstart.ipynb @@ -21,6 +21,17 @@ "df = fetcher.run(\"lamindb\")\n", "df.head()" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57b6dd2e", + "metadata": {}, + "outputs": [], + "source": [ + "plotter = githubcontribs.Plotter(df)\n", + "plotter.plot_total_number_by_author()" + ] } ], "metadata": { diff --git a/githubcontribs/_plotter.py b/githubcontribs/_plotter.py index 0d91072..781ec81 100644 --- a/githubcontribs/_plotter.py +++ b/githubcontribs/_plotter.py @@ -22,7 +22,7 @@ def __init__(self, df: pd.DataFrame): setup_svg_output() sns.set_theme() - def plot_contributor_activity( + def plot_total_number_by_author( self, top_n: int = 10, exclude_author: str = "github-actions[bot]" ): """A horizontal bar plot showing contribution types per author. @@ -31,7 +31,7 @@ def plot_contributor_activity( top_n: Number of top contributors to show. Defaults to 10. exclude_author: Author to exclude from the plot. Defaults to "github-actions[bot]". """ - df = self.df + df = self.df[self.df.author != exclude_author] commits_df: pd.DataFrame = df[df.type == "commit"] issues_df: pd.DataFrame = df[df.type == "issue"] @@ -40,18 +40,18 @@ def plot_contributor_activity( # Prepare the data contributors_data = pd.concat( [ + prs_df.groupby("author").size().rename("Pull requests"), commits_df.groupby("author").size().rename("Commits"), issues_df.groupby("author").size().rename("Issues"), - prs_df.groupby("author").size().rename("Pull Requests"), ], axis=1, ).fillna(0) # Sort by total contributions and get top N contributors_data["Total"] = contributors_data.sum(axis=1) - contributors_data = contributors_data.sort_values("Total", ascending=True).tail( - top_n - ) + contributors_data = contributors_data.sort_values( + "Total", ascending=False + ).head(top_n) contributors_data = contributors_data.drop("Total", axis=1) # Reshape data for seaborn From b2ead21948c7e43f33e65d6f3960cb6c1ccc3232 Mon Sep 17 00:00:00 2001 From: Alex Wolf Date: Sat, 11 Oct 2025 19:16:51 +0200 Subject: [PATCH 04/12] =?UTF-8?q?=F0=9F=92=9A=20Fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index d0148a5..fb0bc69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "pandas", "requests", "dotenv", + "seaborn", ] [project.urls] From 930e1fb75ef5400f864daf45c8888728be82bbc4 Mon Sep 17 00:00:00 2001 From: Alex Wolf Date: Sat, 11 Oct 2025 19:19:52 +0200 Subject: [PATCH 05/12] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- githubcontribs/_plotter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/githubcontribs/_plotter.py b/githubcontribs/_plotter.py index 781ec81..cba0875 100644 --- a/githubcontribs/_plotter.py +++ b/githubcontribs/_plotter.py @@ -87,7 +87,7 @@ def plot_total_number_by_author( plt.gca().bar_label(c, label_type="center", fmt="%d") # Customize the plot with date range and repos in title - plt.title(f"Contribution to repositories by author: {repos}\n{date_range}") + plt.title(f"Contributions to repositories by author: {repos}\n{date_range}") plt.xlabel("Number of contributions") plt.ylabel("Author") From bf3626ed4e36987eead190ba30e769ad71d6ec36 Mon Sep 17 00:00:00 2001 From: Alex Wolf Date: Sat, 11 Oct 2025 19:24:54 +0200 Subject: [PATCH 06/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Vertical=20bars?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- githubcontribs/_plotter.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/githubcontribs/_plotter.py b/githubcontribs/_plotter.py index cba0875..1161f92 100644 --- a/githubcontribs/_plotter.py +++ b/githubcontribs/_plotter.py @@ -25,7 +25,7 @@ def __init__(self, df: pd.DataFrame): def plot_total_number_by_author( self, top_n: int = 10, exclude_author: str = "github-actions[bot]" ): - """A horizontal bar plot showing contribution types per author. + """A vertical bar plot showing contribution types per author. Args: top_n: Number of top contributors to show. Defaults to 10. @@ -69,30 +69,33 @@ def plot_total_number_by_author( # Get all unique repositories repos = ", ".join(sorted(df["repo"].unique())) - # Set up the plot style - plt.figure(figsize=(12, max(8, top_n * 0.5))) + # Set up the plot style - adjusted figsize for vertical orientation + plt.figure(figsize=(max(10, top_n * 0.8), 8)) - # Create the plot + # Create the plot - changed to vertical orientation sns.barplot( data=plot_data, - y="author", - x="Count", + x="author", + y="Count", hue="Activity Type", palette=["#2ecc71", "#3498db", "#e74c3c"], - orient="h", + order=contributors_data.index, # Maintain the sorted order ) # Add value labels for c in plt.gca().containers: - plt.gca().bar_label(c, label_type="center", fmt="%d") + plt.gca().bar_label(c, label_type="edge", fmt="%d", padding=3) # Customize the plot with date range and repos in title plt.title(f"Contributions to repositories by author: {repos}\n{date_range}") - plt.xlabel("Number of contributions") - plt.ylabel("Author") + plt.ylabel("Number of contributions") + plt.xlabel("Author") - # Adjust legend position - plt.legend(bbox_to_anchor=(1, 1.02), loc="upper left") + # Rotate x-axis labels for better readability + plt.xticks(rotation=45, ha="right") + + # Position legend inside the canvas (upper right) + plt.legend(loc="upper right") # Ensure all labels are visible plt.tight_layout() From ee67dba79d0ce7a76de96e97cb70a9592b0e65c2 Mon Sep 17 00:00:00 2001 From: Alex Wolf Date: Sat, 11 Oct 2025 19:34:06 +0200 Subject: [PATCH 07/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- githubcontribs/_plotter.py | 207 +++++++++++++++++++++++++++---------- 1 file changed, 153 insertions(+), 54 deletions(-) diff --git a/githubcontribs/_plotter.py b/githubcontribs/_plotter.py index 1161f92..1aec830 100644 --- a/githubcontribs/_plotter.py +++ b/githubcontribs/_plotter.py @@ -22,80 +22,179 @@ def __init__(self, df: pd.DataFrame): setup_svg_output() sns.set_theme() - def plot_total_number_by_author( + def plot_total_number_by_author_by_type( self, top_n: int = 10, exclude_author: str = "github-actions[bot]" ): - """A vertical bar plot showing contribution types per author. + self._plot_contributions( + x="author", hue="type", top_n=top_n, exclude_author=exclude_author + ) + + def plot_number_by_month_by_author( + self, top_n: int = 10, exclude_author: str = "github-actions[bot]" + ): + self._plot_contributions( + x="time", hue="author", top_n=top_n, exclude_author=exclude_author + ) + + def _plot_contributions( + self, + x: str = "author", + hue: str = "type", + top_n: int = 10, + exclude_author: str = "github-actions[bot]", + time_aggregation: str = "month", + ): + """A configurable plot showing contributions. Args: - top_n: Number of top contributors to show. Defaults to 10. + x: Variable to plot on x-axis. Options: "author", "time". Defaults to "author". + hue: Variable to use for color grouping. Options: "type", "author". Defaults to "type". + top_n: Number of top items to show (authors or time periods). Defaults to 10. exclude_author: Author to exclude from the plot. Defaults to "github-actions[bot]". + time_aggregation: Time aggregation level when x="time". Options: "day", "week", "month", "year". Defaults to "month". """ - df = self.df[self.df.author != exclude_author] - - commits_df: pd.DataFrame = df[df.type == "commit"] - issues_df: pd.DataFrame = df[df.type == "issue"] - prs_df: pd.DataFrame = df[df.type == "pr"] - - # Prepare the data - contributors_data = pd.concat( - [ - prs_df.groupby("author").size().rename("Pull requests"), - commits_df.groupby("author").size().rename("Commits"), - issues_df.groupby("author").size().rename("Issues"), - ], - axis=1, - ).fillna(0) - - # Sort by total contributions and get top N - contributors_data["Total"] = contributors_data.sum(axis=1) - contributors_data = contributors_data.sort_values( - "Total", ascending=False - ).head(top_n) - contributors_data = contributors_data.drop("Total", axis=1) - - # Reshape data for seaborn - plot_data = contributors_data.reset_index().melt( - id_vars="author", var_name="Activity Type", value_name="Count" - ) - - # Calculate date range from the dataframe - min_date = pd.to_datetime(df["date"]).min() - max_date = pd.to_datetime(df["date"]).max() + df = self.df[self.df.author != exclude_author].copy() + + # Convert date column to datetime + df["date"] = pd.to_datetime(df["date"]) + + # Prepare data based on configuration + if x == "time": + # Aggregate by time period + if time_aggregation == "day": + df["time_period"] = df["date"].dt.to_period("D").astype(str) + elif time_aggregation == "week": + df["time_period"] = df["date"].dt.to_period("W").astype(str) + elif time_aggregation == "month": + df["time_period"] = df["date"].dt.to_period("M").astype(str) + elif time_aggregation == "year": + df["time_period"] = df["date"].dt.to_period("Y").astype(str) + else: + raise ValueError(f"Invalid time_aggregation: {time_aggregation}") + + if hue == "author": + # Group by time and author + plot_data = ( + df.groupby(["time_period", "author"]) + .size() + .reset_index(name="Count") + ) + + # Get top N authors by total contributions + top_authors = df.groupby("author").size().nlargest(top_n).index + plot_data = plot_data[plot_data["author"].isin(top_authors)] + + # Sort time periods + plot_data = plot_data.sort_values("time_period") + + x_var = "time_period" + hue_var = "author" + x_label = f"Time ({time_aggregation})" + hue_label = "Author" + palette = None # Use default palette for many authors + + elif hue == "type": + # Group by time and type + plot_data = ( + df.groupby(["time_period", "type"]).size().reset_index(name="Count") + ) + + # Map type names + type_map = {"commit": "Commits", "issue": "Issues", "pr": "PRs"} + plot_data["type"] = plot_data["type"].map(type_map) + + # Sort time periods and optionally limit to top_n periods + plot_data = plot_data.sort_values("time_period") + time_periods = ( + plot_data.groupby("time_period")["Count"] + .sum() + .nlargest(top_n) + .index + ) + plot_data = plot_data[plot_data["time_period"].isin(time_periods)] + plot_data = plot_data.sort_values("time_period") + + x_var = "time_period" + hue_var = "type" + x_label = f"Time ({time_aggregation})" + hue_label = "Activity Type" + palette = ["#2ecc71", "#3498db", "#e74c3c"] + else: + raise ValueError(f"Invalid hue for x='time': {hue}") + + elif x == "author": + if hue == "type": + # Original behavior: group by author and type + commits_df = df[df.type == "commit"] + issues_df = df[df.type == "issue"] + prs_df = df[df.type == "pr"] + + contributors_data = pd.concat( + [ + prs_df.groupby("author").size().rename("PRs"), + commits_df.groupby("author").size().rename("Commits"), + issues_df.groupby("author").size().rename("Issues"), + ], + axis=1, + ).fillna(0) + + contributors_data["Total"] = contributors_data.sum(axis=1) + contributors_data = contributors_data.sort_values( + "Total", ascending=False + ).head(top_n) + contributors_data = contributors_data.drop("Total", axis=1) + + plot_data = contributors_data.reset_index().melt( + id_vars="author", var_name="Activity Type", value_name="Count" + ) + + x_var = "author" + hue_var = "Activity Type" + x_label = "Author" + hue_label = "Activity Type" + palette = ["#2ecc71", "#3498db", "#e74c3c"] + + elif hue == "time": + raise ValueError("hue='time' is not supported when x='author'") + else: + raise ValueError(f"Invalid hue for x='author': {hue}") + else: + raise ValueError(f"Invalid x value: {x}") + + # Calculate date range + min_date = df["date"].min() + max_date = df["date"].max() date_range = ( f"{min_date.strftime('%Y-%m-%d')} to {max_date.strftime('%Y-%m-%d')}" ) - # Get all unique repositories + # Get repositories repos = ", ".join(sorted(df["repo"].unique())) - # Set up the plot style - adjusted figsize for vertical orientation - plt.figure(figsize=(max(10, top_n * 0.8), 8)) - - # Create the plot - changed to vertical orientation - sns.barplot( - data=plot_data, - x="author", - y="Count", - hue="Activity Type", - palette=["#2ecc71", "#3498db", "#e74c3c"], - order=contributors_data.index, # Maintain the sorted order + # Set up the plot + fig_width = max(12, len(plot_data[x_var].unique()) * 0.8) + plt.figure(figsize=(fig_width, 8)) + + # Create the plot + ax = sns.barplot( + data=plot_data, x=x_var, y="Count", hue=hue_var, palette=palette ) - # Add value labels - for c in plt.gca().containers: - plt.gca().bar_label(c, label_type="edge", fmt="%d", padding=3) + # Add value labels only if not too many bars + if len(plot_data[x_var].unique()) <= 20: + for c in ax.containers: + ax.bar_label(c, label_type="edge", fmt="%d", padding=3) - # Customize the plot with date range and repos in title - plt.title(f"Contributions to repositories by author: {repos}\n{date_range}") + # Customize the plot + plt.title(f"Contributions to repositories: {repos}\n{date_range}") plt.ylabel("Number of contributions") - plt.xlabel("Author") + plt.xlabel(x_label) # Rotate x-axis labels for better readability plt.xticks(rotation=45, ha="right") - # Position legend inside the canvas (upper right) - plt.legend(loc="upper right") + # Position legend + plt.legend(title=hue_label, loc="upper right") # Ensure all labels are visible plt.tight_layout() From 0cff89c7af4cba53020a7f0e05a6c7b4c4943a8f Mon Sep 17 00:00:00 2001 From: Alex Wolf Date: Sat, 11 Oct 2025 19:35:13 +0200 Subject: [PATCH 08/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Add=20type=20filter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- githubcontribs/_plotter.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/githubcontribs/_plotter.py b/githubcontribs/_plotter.py index 1aec830..d2b942c 100644 --- a/githubcontribs/_plotter.py +++ b/githubcontribs/_plotter.py @@ -30,10 +30,17 @@ def plot_total_number_by_author_by_type( ) def plot_number_by_month_by_author( - self, top_n: int = 10, exclude_author: str = "github-actions[bot]" + self, + top_n: int = 10, + exclude_author: str = "github-actions[bot]", + type_filter: str = None, ): self._plot_contributions( - x="time", hue="author", top_n=top_n, exclude_author=exclude_author + x="time", + hue="author", + top_n=top_n, + exclude_author=exclude_author, + type_filter=type_filter, ) def _plot_contributions( @@ -43,6 +50,7 @@ def _plot_contributions( top_n: int = 10, exclude_author: str = "github-actions[bot]", time_aggregation: str = "month", + type_filter: str = None, ): """A configurable plot showing contributions. @@ -52,9 +60,18 @@ def _plot_contributions( top_n: Number of top items to show (authors or time periods). Defaults to 10. exclude_author: Author to exclude from the plot. Defaults to "github-actions[bot]". time_aggregation: Time aggregation level when x="time". Options: "day", "week", "month", "year". Defaults to "month". + type_filter: Filter to specific contribution type. Options: "commit", "issue", "pr", or None for all types. """ df = self.df[self.df.author != exclude_author].copy() + # Filter by type if specified + if type_filter is not None: + if type_filter not in ["commit", "issue", "pr"]: + raise ValueError( + f"Invalid type_filter: {type_filter}. Must be 'commit', 'issue', 'pr', or None" + ) + df = df[df.type == type_filter] + # Convert date column to datetime df["date"] = pd.to_datetime(df["date"]) @@ -171,6 +188,14 @@ def _plot_contributions( # Get repositories repos = ", ".join(sorted(df["repo"].unique())) + # Build title with type filter info if applicable + title_parts = [f"Contributions to repositories: {repos}"] + if type_filter is not None: + type_name_map = {"commit": "Commits", "issue": "Issues", "pr": "PRs"} + title_parts[0] = f"{type_name_map[type_filter]} to repositories: {repos}" + title_parts.append(date_range) + title = "\n".join(title_parts) + # Set up the plot fig_width = max(12, len(plot_data[x_var].unique()) * 0.8) plt.figure(figsize=(fig_width, 8)) @@ -186,7 +211,7 @@ def _plot_contributions( ax.bar_label(c, label_type="edge", fmt="%d", padding=3) # Customize the plot - plt.title(f"Contributions to repositories: {repos}\n{date_range}") + plt.title(title) plt.ylabel("Number of contributions") plt.xlabel(x_label) From 6f4719bdb43716d96f1bc30e609fadec41142d36 Mon Sep 17 00:00:00 2001 From: Alex Wolf Date: Sat, 11 Oct 2025 19:42:23 +0200 Subject: [PATCH 09/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- githubcontribs/_plotter.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/githubcontribs/_plotter.py b/githubcontribs/_plotter.py index d2b942c..9db09a4 100644 --- a/githubcontribs/_plotter.py +++ b/githubcontribs/_plotter.py @@ -23,17 +23,25 @@ def __init__(self, df: pd.DataFrame): sns.set_theme() def plot_total_number_by_author_by_type( - self, top_n: int = 10, exclude_author: str = "github-actions[bot]" + self, + top_n: int = 10, + exclude_author: str = "github-actions[bot]", + start_date: str = None, ): self._plot_contributions( - x="author", hue="type", top_n=top_n, exclude_author=exclude_author + x="author", + hue="type", + top_n=top_n, + exclude_author=exclude_author, + start_date=start_date, ) def plot_number_by_month_by_author( self, top_n: int = 10, exclude_author: str = "github-actions[bot]", - type_filter: str = None, + type_filter: str = "pr", + start_date: str = None, ): self._plot_contributions( x="time", @@ -41,6 +49,7 @@ def plot_number_by_month_by_author( top_n=top_n, exclude_author=exclude_author, type_filter=type_filter, + start_date=start_date, ) def _plot_contributions( @@ -51,6 +60,7 @@ def _plot_contributions( exclude_author: str = "github-actions[bot]", time_aggregation: str = "month", type_filter: str = None, + start_date: str = None, ): """A configurable plot showing contributions. @@ -61,9 +71,18 @@ def _plot_contributions( exclude_author: Author to exclude from the plot. Defaults to "github-actions[bot]". time_aggregation: Time aggregation level when x="time". Options: "day", "week", "month", "year". Defaults to "month". type_filter: Filter to specific contribution type. Options: "commit", "issue", "pr", or None for all types. + start_date: Filter contributions to only include those on or after this date. Format: "YYYY-MM-DD". Defaults to None (no filter). """ df = self.df[self.df.author != exclude_author].copy() + # Convert date column to datetime + df["date"] = pd.to_datetime(df["date"]) + + # Filter by start_date if specified + if start_date is not None: + start_date_dt = pd.to_datetime(start_date) + df = df[df["date"] >= start_date_dt] + # Filter by type if specified if type_filter is not None: if type_filter not in ["commit", "issue", "pr"]: @@ -72,9 +91,6 @@ def _plot_contributions( ) df = df[df.type == type_filter] - # Convert date column to datetime - df["date"] = pd.to_datetime(df["date"]) - # Prepare data based on configuration if x == "time": # Aggregate by time period From 667d6b2f3f1c780fa8eb347a87c8c87d4471ce6e Mon Sep 17 00:00:00 2001 From: Alex Wolf Date: Sat, 11 Oct 2025 19:43:53 +0200 Subject: [PATCH 10/12] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- githubcontribs/_plotter.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/githubcontribs/_plotter.py b/githubcontribs/_plotter.py index 9db09a4..fc5ce5a 100644 --- a/githubcontribs/_plotter.py +++ b/githubcontribs/_plotter.py @@ -17,6 +17,12 @@ def setup_svg_output(): class Plotter: + """Initialize the Plotter with a DataFrame of contributions obtained by the `Fetcher`. + + Args: + df: DataFrame containing contribution data with columns: author, type, date, repo. + """ + def __init__(self, df: pd.DataFrame): self.df = df setup_svg_output() @@ -28,6 +34,17 @@ def plot_total_number_by_author_by_type( exclude_author: str = "github-actions[bot]", start_date: str = None, ): + """Plot total contributions by author, grouped by contribution type. + + Creates a bar chart showing the number of commits, pull requests, and issues + for the top N contributors. + + Args: + top_n: Number of top contributors to display. Defaults to 10. + exclude_author: Author to exclude from the plot. Defaults to "github-actions[bot]". + start_date: Only include contributions on or after this date (format: "YYYY-MM-DD"). + Defaults to None (no filter). + """ self._plot_contributions( x="author", hue="type", @@ -43,6 +60,19 @@ def plot_number_by_month_by_author( type_filter: str = "pr", start_date: str = None, ): + """Plot contributions over time by author. + + Creates a bar chart showing contributions aggregated by month, with different + colors for each author. Useful for tracking contributor activity over time. + + Args: + top_n: Number of top contributors to display. Defaults to 10. + exclude_author: Author to exclude from the plot. Defaults to "github-actions[bot]". + type_filter: Show only this type of contribution ("commit", "issue", or "pr"). + Defaults to "pr". + start_date: Only include contributions on or after this date (format: "YYYY-MM-DD"). + Defaults to None (no filter). + """ self._plot_contributions( x="time", hue="author", From bd1e27b1552f7dd5241b26303ba9dd7eabdaf469 Mon Sep 17 00:00:00 2001 From: Alex Wolf Date: Sat, 11 Oct 2025 19:45:55 +0200 Subject: [PATCH 11/12] =?UTF-8?q?=F0=9F=92=9A=20Fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++++ docs/quickstart.ipynb | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c298e61..c4f84e8 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,12 @@ df.head() #> Dataframe of contributions ``` +Plotting: + +```python +plotter = githubcontribs.Plotter(df) +plotter.plot_total_number_by_author_by_type() +plotter.plot_number_by_month_by_author() +``` + Contributing: Please run `pre-commit install` and `gitmoji -i` on the CLI before starting to work on this repository! diff --git a/docs/quickstart.ipynb b/docs/quickstart.ipynb index 8e2e782..91fc73e 100644 --- a/docs/quickstart.ipynb +++ b/docs/quickstart.ipynb @@ -30,7 +30,8 @@ "outputs": [], "source": [ "plotter = githubcontribs.Plotter(df)\n", - "plotter.plot_total_number_by_author()" + "plotter.plot_total_number_by_author_by_type()\n", + "plotter.plot_number_by_month_by_author()" ] } ], From f7b31737595b9e46d4e75e99384a864d4621374f Mon Sep 17 00:00:00 2001 From: Alex Wolf Date: Sat, 11 Oct 2025 19:47:08 +0200 Subject: [PATCH 12/12] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20Bump=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- githubcontribs/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/githubcontribs/__init__.py b/githubcontribs/__init__.py index 7ab2419..07bf510 100644 --- a/githubcontribs/__init__.py +++ b/githubcontribs/__init__.py @@ -1,6 +1,6 @@ """Simple analytics for GitHub contributions across an organization.""" -__version__ = "0.1.0" # denote a pre-release for 0.1.0 with 0.1rc1 +__version__ = "0.2a1" # denote a pre-release for 0.1.0 with 0.1rc1 from ._fetcher import Fetcher from ._plotter import Plotter