From e66c6f3c7b57ba029c8b2a41c1b091fb5dca4517 Mon Sep 17 00:00:00 2001 From: Caio Fonseca Date: Mon, 16 Mar 2026 13:50:47 +0000 Subject: [PATCH 1/3] refactor(heatmaps): use baby-blue sequential color scale - Add heatmap_color_scale in graph_utils (light-to-dark blue) - Replace px.colors.sequential.deep with heatmap_color_scale in all three heatmaps (contribution, contributor, reviewer file heatmaps) - Aligns heatmap colors with app theme and other visualizations Made-with: Cursor --- 8Knot/pages/codebase/visualizations/cntrb_file_heatmap.py | 4 ++-- .../codebase/visualizations/contribution_file_heatmap.py | 3 ++- 8Knot/pages/codebase/visualizations/reviewer_file_heatmap.py | 4 ++-- 8Knot/pages/utils/graph_utils.py | 3 +++ 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/8Knot/pages/codebase/visualizations/cntrb_file_heatmap.py b/8Knot/pages/codebase/visualizations/cntrb_file_heatmap.py index 081f335ea..73e4bf796 100644 --- a/8Knot/pages/codebase/visualizations/cntrb_file_heatmap.py +++ b/8Knot/pages/codebase/visualizations/cntrb_file_heatmap.py @@ -8,7 +8,7 @@ import logging from dateutil.relativedelta import * # type: ignore import plotly.express as px -from pages.utils.graph_utils import get_graph_time_values, color_seq +from pages.utils.graph_utils import get_graph_time_values, color_seq, heatmap_color_scale from queries.contributors_query import contributors_query as cnq from queries.cntrb_per_file_query import cntrb_per_file_query as cpfq from queries.repo_files_query import repo_files_query as rfq @@ -335,7 +335,7 @@ def create_figure(df: pd.DataFrame): fig = px.imshow( df, labels=dict(x="Time", y="Directory Entries", color="Contributors"), - color_continuous_scale=px.colors.sequential.deep, + color_continuous_scale=heatmap_color_scale, ) fig["layout"]["yaxis"]["tickmode"] = "linear" diff --git a/8Knot/pages/codebase/visualizations/contribution_file_heatmap.py b/8Knot/pages/codebase/visualizations/contribution_file_heatmap.py index 59a86caa2..cf038309b 100644 --- a/8Knot/pages/codebase/visualizations/contribution_file_heatmap.py +++ b/8Knot/pages/codebase/visualizations/contribution_file_heatmap.py @@ -8,6 +8,7 @@ import logging from dateutil.relativedelta import * # type: ignore import plotly.express as px +from pages.utils.graph_utils import heatmap_color_scale from queries.prs_query import prs_query as prq from queries.pr_files_query import pr_file_query as prfq from queries.repo_files_query import repo_files_query as rfq @@ -346,7 +347,7 @@ def create_figure(df: pd.DataFrame, graph_view): fig = px.imshow( df, labels=dict(x="Time", y="Directory Entries", color=legend_title), - color_continuous_scale=px.colors.sequential.deep, + color_continuous_scale=heatmap_color_scale, ) fig["layout"]["yaxis"]["tickmode"] = "linear" diff --git a/8Knot/pages/codebase/visualizations/reviewer_file_heatmap.py b/8Knot/pages/codebase/visualizations/reviewer_file_heatmap.py index 9020eba30..287c94911 100644 --- a/8Knot/pages/codebase/visualizations/reviewer_file_heatmap.py +++ b/8Knot/pages/codebase/visualizations/reviewer_file_heatmap.py @@ -8,7 +8,7 @@ import logging from dateutil.relativedelta import * # type: ignore import plotly.express as px -from pages.utils.graph_utils import get_graph_time_values, color_seq +from pages.utils.graph_utils import get_graph_time_values, color_seq, heatmap_color_scale from queries.contributors_query import contributors_query as cnq from queries.cntrb_per_file_query import cntrb_per_file_query as cpfq from queries.repo_files_query import repo_files_query as rfq @@ -335,7 +335,7 @@ def create_figure(df: pd.DataFrame): fig = px.imshow( df, labels=dict(x="Time", y="Directory Entries", color="Contributors"), - color_continuous_scale=px.colors.sequential.deep, + color_continuous_scale=heatmap_color_scale, ) fig["layout"]["yaxis"]["tickmode"] = "linear" diff --git a/8Knot/pages/utils/graph_utils.py b/8Knot/pages/utils/graph_utils.py index 6eb32b3c9..569536ef7 100644 --- a/8Knot/pages/utils/graph_utils.py +++ b/8Knot/pages/utils/graph_utils.py @@ -25,6 +25,9 @@ "#B54708", # Yellow 700 - dark yellow ] +# Sequential color scale for heatmaps (light -> dark blue, matches app theme) +heatmap_color_scale = [baby_blue[0], baby_blue[2], baby_blue[4], baby_blue[6], baby_blue[8]] + def get_graph_time_values(interval): """ From a4ae00e77d151c588349e308701ffda2452ec47c Mon Sep 17 00:00:00 2001 From: Caio Fonseca Date: Mon, 16 Mar 2026 13:55:13 +0000 Subject: [PATCH 2/3] refactor(heatmaps): use update_layout and style colorbar for dark theme - Replace raw layout dict updates with fig.update_layout() - Add font=dict(size=14), axis titles to match other visualizations - Style coloraxis colorbar (tick/title font white) for dark theme Made-with: Cursor --- .../visualizations/cntrb_file_heatmap.py | 18 ++++++++++++++---- .../contribution_file_heatmap.py | 18 ++++++++++++++---- .../visualizations/reviewer_file_heatmap.py | 18 ++++++++++++++---- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/8Knot/pages/codebase/visualizations/cntrb_file_heatmap.py b/8Knot/pages/codebase/visualizations/cntrb_file_heatmap.py index 73e4bf796..2f82ba9d7 100644 --- a/8Knot/pages/codebase/visualizations/cntrb_file_heatmap.py +++ b/8Knot/pages/codebase/visualizations/cntrb_file_heatmap.py @@ -338,10 +338,20 @@ def create_figure(df: pd.DataFrame): color_continuous_scale=heatmap_color_scale, ) - fig["layout"]["yaxis"]["tickmode"] = "linear" - fig["layout"]["height"] = 700 - fig["layout"]["coloraxis_colorbar_x"] = -0.15 - fig["layout"]["yaxis"]["side"] = "right" + fig.update_layout( + height=700, + font=dict(size=14), + xaxis_title="Time", + yaxis_title="Directory Entries", + yaxis=dict(tickmode="linear", side="right"), + coloraxis_colorbar_x=-0.15, + coloraxis=dict( + colorbar=dict( + tickfont=dict(color="white"), + title=dict(font=dict(color="white")), + ) + ), + ) return fig diff --git a/8Knot/pages/codebase/visualizations/contribution_file_heatmap.py b/8Knot/pages/codebase/visualizations/contribution_file_heatmap.py index cf038309b..8e6d397a4 100644 --- a/8Knot/pages/codebase/visualizations/contribution_file_heatmap.py +++ b/8Knot/pages/codebase/visualizations/contribution_file_heatmap.py @@ -350,10 +350,20 @@ def create_figure(df: pd.DataFrame, graph_view): color_continuous_scale=heatmap_color_scale, ) - fig["layout"]["yaxis"]["tickmode"] = "linear" - fig["layout"]["height"] = 700 - fig["layout"]["coloraxis_colorbar_x"] = -0.15 - fig["layout"]["yaxis"]["side"] = "right" + fig.update_layout( + height=700, + font=dict(size=14), + xaxis_title="Time", + yaxis_title="Directory Entries", + yaxis=dict(tickmode="linear", side="right"), + coloraxis_colorbar_x=-0.15, + coloraxis=dict( + colorbar=dict( + tickfont=dict(color="white"), + title=dict(font=dict(color="white")), + ) + ), + ) return fig diff --git a/8Knot/pages/codebase/visualizations/reviewer_file_heatmap.py b/8Knot/pages/codebase/visualizations/reviewer_file_heatmap.py index 287c94911..b2c2040cb 100644 --- a/8Knot/pages/codebase/visualizations/reviewer_file_heatmap.py +++ b/8Knot/pages/codebase/visualizations/reviewer_file_heatmap.py @@ -338,10 +338,20 @@ def create_figure(df: pd.DataFrame): color_continuous_scale=heatmap_color_scale, ) - fig["layout"]["yaxis"]["tickmode"] = "linear" - fig["layout"]["height"] = 700 - fig["layout"]["coloraxis_colorbar_x"] = -0.15 - fig["layout"]["yaxis"]["side"] = "right" + fig.update_layout( + height=700, + font=dict(size=14), + xaxis_title="Time", + yaxis_title="Directory Entries", + yaxis=dict(tickmode="linear", side="right"), + coloraxis_colorbar_x=-0.15, + coloraxis=dict( + colorbar=dict( + tickfont=dict(color="white"), + title=dict(font=dict(color="white")), + ) + ), + ) return fig From a016dcef5b6ea0a2949e84a9eac9efdfeb586073 Mon Sep 17 00:00:00 2001 From: Caio Fonseca Date: Mon, 16 Mar 2026 13:58:46 +0000 Subject: [PATCH 3/3] refactor(heatmaps): add shared create_heatmap_figure helper (DRY) - Add create_heatmap_figure() in graph_utils for common imshow + layout - Refactor all three heatmaps to use helper; remove duplicated layout code - Drop unused plotly.express import from heatmap modules Made-with: Cursor --- .../visualizations/cntrb_file_heatmap.py | 26 ++-------------- .../contribution_file_heatmap.py | 31 ++----------------- .../visualizations/reviewer_file_heatmap.py | 26 ++-------------- 8Knot/pages/utils/graph_utils.py | 31 +++++++++++++++++++ 4 files changed, 38 insertions(+), 76 deletions(-) diff --git a/8Knot/pages/codebase/visualizations/cntrb_file_heatmap.py b/8Knot/pages/codebase/visualizations/cntrb_file_heatmap.py index 2f82ba9d7..ab3e3829f 100644 --- a/8Knot/pages/codebase/visualizations/cntrb_file_heatmap.py +++ b/8Knot/pages/codebase/visualizations/cntrb_file_heatmap.py @@ -7,8 +7,7 @@ import pandas as pd import logging from dateutil.relativedelta import * # type: ignore -import plotly.express as px -from pages.utils.graph_utils import get_graph_time_values, color_seq, heatmap_color_scale +from pages.utils.graph_utils import create_heatmap_figure, get_graph_time_values, color_seq from queries.contributors_query import contributors_query as cnq from queries.cntrb_per_file_query import cntrb_per_file_query as cpfq from queries.repo_files_query import repo_files_query as rfq @@ -332,28 +331,7 @@ def process_data( def create_figure(df: pd.DataFrame): - fig = px.imshow( - df, - labels=dict(x="Time", y="Directory Entries", color="Contributors"), - color_continuous_scale=heatmap_color_scale, - ) - - fig.update_layout( - height=700, - font=dict(size=14), - xaxis_title="Time", - yaxis_title="Directory Entries", - yaxis=dict(tickmode="linear", side="right"), - coloraxis_colorbar_x=-0.15, - coloraxis=dict( - colorbar=dict( - tickfont=dict(color="white"), - title=dict(font=dict(color="white")), - ) - ), - ) - - return fig + return create_heatmap_figure(df, color_label="Contributors") def df_file_clean(df_file: pd.DataFrame, df_file_cntbs: pd.DataFrame, bot_switch): diff --git a/8Knot/pages/codebase/visualizations/contribution_file_heatmap.py b/8Knot/pages/codebase/visualizations/contribution_file_heatmap.py index 8e6d397a4..c6eb2caa1 100644 --- a/8Knot/pages/codebase/visualizations/contribution_file_heatmap.py +++ b/8Knot/pages/codebase/visualizations/contribution_file_heatmap.py @@ -7,8 +7,7 @@ import pandas as pd import logging from dateutil.relativedelta import * # type: ignore -import plotly.express as px -from pages.utils.graph_utils import heatmap_color_scale +from pages.utils.graph_utils import create_heatmap_figure from queries.prs_query import prs_query as prq from queries.pr_files_query import pr_file_query as prfq from queries.repo_files_query import repo_files_query as rfq @@ -340,32 +339,8 @@ def process_data( def create_figure(df: pd.DataFrame, graph_view): - legend_title = "PRs Opened" - if graph_view == "merged_at": - legend_title = "PRs Merged" - - fig = px.imshow( - df, - labels=dict(x="Time", y="Directory Entries", color=legend_title), - color_continuous_scale=heatmap_color_scale, - ) - - fig.update_layout( - height=700, - font=dict(size=14), - xaxis_title="Time", - yaxis_title="Directory Entries", - yaxis=dict(tickmode="linear", side="right"), - coloraxis_colorbar_x=-0.15, - coloraxis=dict( - colorbar=dict( - tickfont=dict(color="white"), - title=dict(font=dict(color="white")), - ) - ), - ) - - return fig + color_label = "PRs Merged" if graph_view == "merged_at" else "PRs Opened" + return create_heatmap_figure(df, color_label=color_label) def df_file_clean(df_file: pd.DataFrame, df_file_pr: pd.DataFrame): diff --git a/8Knot/pages/codebase/visualizations/reviewer_file_heatmap.py b/8Knot/pages/codebase/visualizations/reviewer_file_heatmap.py index b2c2040cb..dff17034a 100644 --- a/8Knot/pages/codebase/visualizations/reviewer_file_heatmap.py +++ b/8Knot/pages/codebase/visualizations/reviewer_file_heatmap.py @@ -7,8 +7,7 @@ import pandas as pd import logging from dateutil.relativedelta import * # type: ignore -import plotly.express as px -from pages.utils.graph_utils import get_graph_time_values, color_seq, heatmap_color_scale +from pages.utils.graph_utils import create_heatmap_figure, get_graph_time_values, color_seq from queries.contributors_query import contributors_query as cnq from queries.cntrb_per_file_query import cntrb_per_file_query as cpfq from queries.repo_files_query import repo_files_query as rfq @@ -332,28 +331,7 @@ def process_data( def create_figure(df: pd.DataFrame): - fig = px.imshow( - df, - labels=dict(x="Time", y="Directory Entries", color="Contributors"), - color_continuous_scale=heatmap_color_scale, - ) - - fig.update_layout( - height=700, - font=dict(size=14), - xaxis_title="Time", - yaxis_title="Directory Entries", - yaxis=dict(tickmode="linear", side="right"), - coloraxis_colorbar_x=-0.15, - coloraxis=dict( - colorbar=dict( - tickfont=dict(color="white"), - title=dict(font=dict(color="white")), - ) - ), - ) - - return fig + return create_heatmap_figure(df, color_label="Contributors") def df_file_clean(df_file: pd.DataFrame, df_file_cntbs: pd.DataFrame, bot_switch): diff --git a/8Knot/pages/utils/graph_utils.py b/8Knot/pages/utils/graph_utils.py index 569536ef7..4f7460069 100644 --- a/8Knot/pages/utils/graph_utils.py +++ b/8Knot/pages/utils/graph_utils.py @@ -1,4 +1,6 @@ import datetime as dt +import pandas as pd +import plotly.express as px # list of graph color hex color_seq = [ @@ -29,6 +31,35 @@ heatmap_color_scale = [baby_blue[0], baby_blue[2], baby_blue[4], baby_blue[6], baby_blue[8]] +def create_heatmap_figure( + df: pd.DataFrame, + color_label: str, + x_label: str = "Time", + y_label: str = "Directory Entries", +): + + fig = px.imshow( + df, + labels=dict(x=x_label, y=y_label, color=color_label), + color_continuous_scale=heatmap_color_scale, + ) + fig.update_layout( + height=700, + font=dict(size=14), + xaxis_title=x_label, + yaxis_title=y_label, + yaxis=dict(tickmode="linear", side="right"), + coloraxis_colorbar_x=-0.15, + coloraxis=dict( + colorbar=dict( + tickfont=dict(color="white"), + title=dict(font=dict(color="white")), + ) + ), + ) + return fig + + def get_graph_time_values(interval): """ Utility needed in page visualizations-