from __future__ import annotations
from typing import Sequence, Any, List
from io import BytesIO
import base64
import matplotlib.pyplot as plt
from adjustText import adjust_text
[docs]
def create_scatter_plot_encoded_image(
x_values: Sequence[float],
y_values: Sequence[float],
labels: Sequence[Any] | None = None,
x_label: str = "X",
y_label: str = "Y",
title: str | None = None,
) -> str:
"""
Create a scatter plot and return it as a base64-encoded PNG data URI.
Labels are automatically adjusted to reduce overlaps using the adjustText library.
Args:
x_values: Sequence of x-axis coordinate values.
y_values: Sequence of y-axis coordinate values.
labels: Optional sequence of labels for each point. Defaults to None.
x_label: Label for the x-axis. Defaults to "X".
y_label: Label for the y-axis. Defaults to "Y".
title: Optional title for the plot. Defaults to None.
Returns:
str: Base64-encoded PNG image as a data URI (data:image/png;base64,...).
Raises:
ValueError: If x_values and y_values have different lengths.
ValueError: If labels are provided but have a different length than x_values.
"""
x_values = list(x_values)
y_values = list(y_values)
if len(x_values) != len(y_values):
raise ValueError("x_values and y_values must have the same length")
if labels is not None and len(labels) != len(x_values):
raise ValueError("labels must have the same length as x_values and y_values")
fig, ax = plt.subplots(figsize=(9, 6), dpi=160)
ax.scatter(
x_values,
y_values,
s=50,
alpha=0.85,
edgecolors="black",
linewidths=0.7,
zorder=3,
)
texts = []
if labels is not None:
for x, y, label in zip(x_values, y_values, labels):
text = ax.text(
x,
y,
str(label),
fontsize=9,
ha="center",
va="center",
zorder=4,
bbox={
"boxstyle": "round,pad=0.18",
"facecolor": "white",
"edgecolor": "none",
"alpha": 0.75,
},
)
texts.append(text)
adjust_text(
texts,
x=x_values,
y=y_values,
ax=ax,
expand=(1.2, 1.4),
force_text=(0.4, 0.6),
force_static=(0.2, 0.4),
arrowprops={
"arrowstyle": "-",
"lw": 0.5,
"alpha": 0.5,
},
)
ax.set_xlabel(x_label, fontsize=10)
ax.set_ylabel(y_label, fontsize=10)
if title is not None:
ax.set_title(title, fontsize=12, pad=12)
ax.grid(True, alpha=0.25, linewidth=0.8)
ax.set_axisbelow(True)
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.margins(x=0.08, y=0.12)
fig.tight_layout()
png_buffer = BytesIO()
fig.savefig(
png_buffer,
format="png",
dpi=300,
bbox_inches="tight",
pad_inches=0.2,
)
plt.close(fig)
png_buffer.seek(0)
encoded_png = base64.b64encode(png_buffer.getvalue()).decode("utf-8")
return f"data:image/png;base64,{encoded_png}"
[docs]
def create_graph_degree_distribution_encoded_image(
degrees: List[int],
bins: int = 20,
title: str | None = None,
log_scale: bool = False,
) -> str:
"""
Create a degree distribution histogram and return it as a base64-encoded PNG data URI.
Args:
degrees: List of degree values for all nodes in the network.
bins: Number of histogram bins. Defaults to 20.
title: Optional title for the plot. Defaults to None.
log_scale: If True, apply logarithmic scale to the y-axis. Defaults to False.
Returns:
str: Base64-encoded PNG image as a data URI (data:image/png;base64,...).
"""
fig, ax = plt.subplots(figsize=(8, 6))
ax.hist(
degrees,
bins=bins,
edgecolor="black",
alpha=0.75,
)
ax.set_xlabel(None)
ax.set_ylabel("Number of nodes")
ax.set_title(title)
ax.grid(True, axis="y", alpha=0.3)
if log_scale:
ax.set_yscale("log")
fig.tight_layout()
png_buffer = BytesIO()
fig.savefig(png_buffer, format="png", dpi=300, bbox_inches="tight")
plt.close(fig)
png_buffer.seek(0)
encoded_png = base64.b64encode(png_buffer.getvalue()).decode("utf-8")
return f"data:image/png;base64,{encoded_png}"
[docs]
def create_barplot_encoded_image(
edges: List[str],
values: List[float],
title: str | None = None,
y_label: str | None = None,
x_labebl: str | None = None,
) -> str:
"""
Create a horizontal bar plot and return it as a base64-encoded PNG data URI.
Args:
edges: List of labels for each bar (displayed on y-axis).
values: List of values corresponding to each bar.
title: Optional title for the plot. Defaults to None.
y_label: Optional label for the y-axis. Defaults to None.
x_labebl: Optional label for the x-axis (note: typo in parameter name). Defaults to None.
Returns:
str: Base64-encoded PNG image as a data URI (data:image/png;base64,...).
"""
fig, ax = plt.subplots(figsize=(10, 6))
ax.barh(edges, values)
ax.set_xlabel(x_labebl)
ax.set_ylabel(y_label)
ax.set_title(title)
ax.invert_yaxis()
ax.grid(True, axis="x", alpha=0.3)
fig.tight_layout()
png_buffer = BytesIO()
fig.savefig(png_buffer, format="png", dpi=300, bbox_inches="tight")
plt.close(fig)
png_buffer.seek(0)
encoded_png = base64.b64encode(png_buffer.getvalue()).decode("utf-8")
return f"data:image/png;base64,{encoded_png}"