"""Minimal plotting helpers used by api.py. Only `plot_label_distribution` and `plot_label_cooccurrence` are exercised in the Space; the full visualization module from the source repo pulls in many heavy deps (seaborn, networkx, umap, levenshtein, internal modules) we don't need here. """ import ast from collections import Counter import numpy as np import plotly.graph_objects as go def _parse_labels(labels_str): if labels_str is None or labels_str == "": return [] try: return ast.literal_eval(str(labels_str)) except (ValueError, SyntaxError): return [] def plot_label_distribution(df, taxonomy, output_path: str): """Stacked bar chart: per-label total count split into 'alone' vs 'with others'.""" df_parsed = df.copy() df_parsed["Labels"] = df_parsed["Labels"].apply(_parse_labels) label_counts = Counter([label for labels in df_parsed["Labels"] for label in labels]) alone_counts = {} for label in label_counts.keys(): alone_counts[label] = df_parsed["Labels"].apply( lambda lst: isinstance(lst, list) and len(lst) == 1 and lst[0] == label ).sum() sorted_labels = [label for label in sorted(label_counts) if label in taxonomy] alone = [alone_counts.get(label, 0) for label in sorted_labels] with_others = [label_counts[label] - alone_counts.get(label, 0) for label in sorted_labels] totals = [label_counts[label] for label in sorted_labels] x_labels = [f"{taxonomy[label]['category']} ({label})" for label in sorted_labels] fig = go.Figure(data=[ go.Bar(name="Alone", x=x_labels, y=alone, marker_color="navy"), go.Bar(name="With Other Labels", x=x_labels, y=with_others, marker_color="orange"), ]) for x, t, a in zip(x_labels, totals, alone): fig.add_annotation( x=x, y=t, text=f"Total: {t}
Alone: {a}", showarrow=False, yshift=15, font=dict(size=13, color="black"), align="center", ) y_max = max([a + w for a, w in zip(alone, with_others)]) * 1.2 if alone else 10 fig.update_layout( barmode="stack", title="Distribution of Edit Types Across Revisions", xaxis_title="Edit Type", yaxis_title="Number of Section Revisions", yaxis=dict(range=[0, y_max]), xaxis=dict(tickmode="linear"), font=dict(size=15), legend=dict(title="Occurrence Type"), ) fig.write_image(output_path, width=1920, height=1080, scale=2) return output_path def plot_label_cooccurrence(df, taxonomy, output_path: str): """Heatmap of label co-occurrence within the same revision.""" df_parsed = df.copy() df_parsed["Labels"] = df_parsed["Labels"].apply(_parse_labels) sorted_keys = sorted(taxonomy.keys()) label_titles = [taxonomy[key]["category"] for key in sorted_keys] num_labels = len(sorted_keys) co_occurrence_matrix = np.zeros((num_labels, num_labels), dtype=int) for labels in df_parsed["Labels"]: for i in labels: for j in labels: if i != j and i in sorted_keys and j in sorted_keys: co_occurrence_matrix[i - 1, j - 1] += 1 fig = go.Figure(data=go.Heatmap( z=co_occurrence_matrix, x=label_titles, y=label_titles, colorscale="Blues", text=co_occurrence_matrix, texttemplate="%{text}", )) fig.update_layout( title="Co-occurrence of Edit Types in Revisions", xaxis=dict(title="Edit Type", tickangle=45, tickfont=dict(size=12)), yaxis=dict(title="Co-occurring Edit Type", tickfont=dict(size=12)), ) fig.write_image(output_path, width=1920, height=1080, scale=2) return output_path