Skip to content

Instantly share code, notes, and snippets.

@FedericoTartarini
Last active September 28, 2024 00:18
Show Gist options
  • Save FedericoTartarini/2339ba7b50253fd9fb0ebbf3c0d7d739 to your computer and use it in GitHub Desktop.
Save FedericoTartarini/2339ba7b50253fd9fb0ebbf3c0d7d739 to your computer and use it in GitHub Desktop.
gauge chart matplotlib
import math
from dataclasses import dataclass
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
from scipy.interpolate import interp1d
def gauge_chart(
risk_value: float,
colors: list,
thresholds: list,
show_image: list = None,
text: list = None,
show_value: bool = True,
text_rotated: bool = True,
bottom: int = 1,
height: int = 2,
icon_size: int = 20,
):
m = interp1d([min(thresholds), max(thresholds)], [0.0, np.pi])
x_axis_vals = [m(x) for x in thresholds]
width = [x - x_axis_vals[i - 1] for i, x in enumerate(x_axis_vals)][1:]
fig = plt.figure(figsize=(7, 4))
ax = fig.add_subplot(projection="polar")
ax.set_thetamin(0)
ax.set_thetamax(180)
ax.bar(
x=x_axis_vals[:-1],
width=width,
height=height,
bottom=bottom,
linewidth=2,
edgecolor="white",
color=colors,
align="edge",
)
ax.set_axis_off()
ax.set_theta_zero_location("W") # theta=0 at the top
ax.set_theta_direction(-1) # theta increasing clockwise
color_annotation = "black"
for col, tre in zip(colors, thresholds):
if risk_value > tre:
color_annotation = col
if show_value:
ann = plt.annotate(
str(risk_value),
xytext=(0, 0),
xy=(m(risk_value), 1.5),
arrowprops=dict(
arrowstyle="wedge, tail_width=0.25",
# arrowstyle="-|>",
# arrowstyle="fancy",
color=color_annotation,
# shrinkA=0,
# linewidth=0.8,
ec="black",
),
bbox=dict(
boxstyle="circle",
pad=0.25,
# facecolor=color_annotation,
facecolor="none",
linewidth=0.8,
# ec="black",
ec="none",
),
fontsize=20,
color=color_annotation,
# color="black",
ha="center",
va="center",
)
ann.set_zorder(11)
plt.tight_layout(h_pad=0)
plt.subplots_adjust(top=1.3, bottom=-0.3, right=1, left=0, hspace=0, wspace=0)
if text:
for i, label in enumerate(text):
text_angle = 0
if text_rotated:
text_angle = 180 - math.degrees(x_axis_vals[i] + width[i] / 2)
if text_angle > 90:
text_angle -= 180
if show_image:
text_height = height - 0.5
else:
text_height = height
txt = ax.text(
x_axis_vals[i] + width[i] / 2,
text_height,
label,
fontsize=12,
color="black",
ha="center",
va="center",
fontweight="bold",
rotation=text_angle,
)
if show_image:
txt.set_alpha(0)
else:
continue
# Get the bounding box of the text in display coordinates
bbox = txt.get_window_extent(renderer=fig.canvas.get_renderer())
# Convert display coordinates to figure coordinates
bbox_fig = bbox.transformed(fig.transFigure.inverted())
# Calculate the center point in figure coordinates
center_x = (bbox_fig.x0 + bbox_fig.x1) / 2
center_y = (bbox_fig.y0 + bbox_fig.y1) / 2
# Print the center point in figure coordinates
print(f"Center point in figure coordinates: ({center_x}, {center_y})")
# Convert figure coordinates to pixel coordinates
pixel_x = fig.bbox.x0 + center_x * fig.bbox.width
pixel_y = fig.bbox.y0 + center_y * fig.bbox.height
print(f"Figure coordinates: ({fig.bbox.x0}, {fig.bbox.y0})")
print(f"Figure width: {fig.bbox.width}")
print(f"Figure height: {fig.bbox.height}")
print(f"Center point in pixel coordinates: ({pixel_x}, {pixel_y})")
print("############################################")
image = Image.open(show_image[i])
# Place the image at the calculated coordinates
fig.figimage(
image.resize([icon_size, icon_size]),
pixel_x - icon_size / 2,
pixel_y - icon_size / 2,
zorder=1,
)
txt = ax.text(
x_axis_vals[i] + width[i] / 2,
text_height+0.75,
label,
fontsize=12,
color="black",
ha="center",
va="center",
fontweight="bold",
rotation=text_angle,
)
plt.show()
if __name__ == "__main__":
@dataclass
class RiskValues:
risk_name: str
color: str
threshold: [float, float]
icon: str
model = [
RiskValues("Risk 1", "tab:green", [0, 25], "icons/play-button.png"),
RiskValues("Risk 2", "tab:orange", [25, 100], "icons/water-bottle.png"),
RiskValues("Risk 3", "tab:red", [100, 150], "icons/pause.png"),
RiskValues("Risk 4", "tab:purple", [150, 200], "icons/no-stopping.png"),
]
colors = [risk.color for risk in model]
thresholds = []
for risk in model:
thresholds.append(risk.threshold[0])
thresholds.append(risk.threshold[1])
thresholds = sorted(list(set(thresholds)))
icons = [risk.icon for risk in model]
text = [risk.risk_name for risk in model]
risk_value = 130
gauge_chart(
risk_value=risk_value,
colors=colors,
thresholds=thresholds,
text=text,
# show_value=False,
text_rotated=True,
# show_image=icons,
icon_size=40,
# bottom=0
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment