import cairo
import numpy as np
[docs]def draw(
ctx: cairo.Context,
color_bar_pos: tuple[float, float],
color_bar_dims: tuple[float, float],
low: float,
high: float,
**kwargs,
):
"""Draws the color bar together with its labels.
Args:
ctx: Context.
color_bar_pos: Coordinates of the top left point of the color bar.
color_bar_dims: Width and height of the color bar.
low: Lower limit of the linear range.
high: Upper limit of the linear range.
**axis_label: Axis label of the color bar.
"""
middle = rectangle(ctx, color_bar_pos, color_bar_dims, low, high, **kwargs)
tick_labels(ctx, middle, low, high, color_bar_pos, color_bar_dims)
axis_label(ctx, color_bar_pos, color_bar_dims, label=kwargs.get("axis_label"))
[docs]def dimensions(
surface_dims: tuple[float, float],
color_bar_fraction: tuple[float, float],
border_fraction: float,
) -> tuple[tuple[float, float], tuple[float, float]]:
"""Extracts dimensions of the color bar.
Args:
surface_dims: Dimensions of the surface.
color_bar_fraction: The fraction of the surface that the color bar
region will take on the right (vertically and horizontally).
border_fraction: Fraction of the max_dim that will be blank on all
sides of the surface.
Returns:
color_bar_pos: Coordinates of the top left point of the color bar.
color_bar_dims: Width and height of the color bar.
"""
height = np.max(surface_dims) * color_bar_fraction[0]
width = np.max(surface_dims) * color_bar_fraction[1] / 4
x_start = surface_dims[0] * (1 - border_fraction) - 3 * width
y_start = 0.5 * surface_dims[1] - height / 2
color_bar_dims = (width, height)
color_bar_pos = (x_start, y_start)
return color_bar_pos, color_bar_dims
[docs]def rgb(
low: float,
high: float,
low_rgb: tuple[float, float, float],
zero_rgb: tuple[float, float, float],
high_rgb: tuple[float, float, float],
) -> tuple[tuple[float, float, float], tuple[float, float, float], tuple[float, float, float]]:
"""Extracts RGB values for the color bar gradient.
Args:
low: Lower limit of the linear range.
high: Upper limit of the linear range.
low_rgb: Normalized RGB value associated with the lower limit.
zero_rgb: Normalized RGB value associated with the value of zero.
high_rgb: Normalized RGB value associated with the upper limit.
Returns:
RGB values for the bottom, middle and top parts of the color map
gradient. If only two colors are used, `middle_rgb` is returned as None.
"""
if low < 0 < high:
top_rgb = high_rgb
middle_rgb = zero_rgb
bottom_rgb = low_rgb
else:
middle_rgb = None
if high > low >= 0:
top_rgb = high_rgb
bottom_rgb = zero_rgb
if low < high <= 0:
top_rgb = zero_rgb
bottom_rgb = low_rgb
if low == high > 0:
top_rgb = bottom_rgb = high_rgb
if low == high < 0:
top_rgb = bottom_rgb = low_rgb
if low == high == 0:
top_rgb = bottom_rgb = zero_rgb
return bottom_rgb, middle_rgb, top_rgb
[docs]def rectangle(
ctx: cairo.Context,
color_bar_pos: tuple[float, float],
color_bar_dims: tuple[float, float],
low: float,
high: float,
**kwargs,
) -> bool:
"""Draws rectangle with color gradient.
Args:
ctx: Context.
color_bar_pos: Coordinates of the top left point of the color bar.
color_bar_dims: Width and height of the color bar.
low: Lower limit of the linear range.
high: Upper limit of the linear range.
**low_rgb: Normalized RGB value associated with the lower limit.
**zero_rgb: Normalized RGB value associated with value of zero.
**high_rgb: Normalized RGB value associated with the upper limit.
Returns:
If False, only two colors were used for the gradient.
"""
ctx.rectangle(*color_bar_pos, *color_bar_dims)
x_start = color_bar_pos[0]
y_start = color_bar_pos[1] + color_bar_dims[1]
x_end = color_bar_pos[0]
y_end = color_bar_pos[1]
pattern = cairo.LinearGradient(x_start, y_start, x_end, y_end)
bottom_rgb, middle_rgb, top_rgb = rgb(
low,
high,
low_rgb=kwargs.get("low_rgb"),
zero_rgb=kwargs.get("zero_rgb"),
high_rgb=kwargs.get("high_rgb"),
)
pattern.add_color_stop_rgb(0, *bottom_rgb)
pattern.add_color_stop_rgb(1, *top_rgb)
if middle_rgb is not None:
pattern.add_color_stop_rgb(0.5, *middle_rgb)
middle = True
else:
middle = False
ctx.set_source(pattern)
ctx.fill()
return middle
[docs]def tick_labels(
ctx: cairo.Context,
middle: bool,
low: float,
high: float,
color_bar_pos: tuple[float, float],
color_bar_dims: tuple[float, float],
):
"""Draws tick labels of the color bar.
Args:
ctx: Context.
middle: If False, only two colors were used for the gradient.
low: Lower limit of the linear range.
high: Upper limit of the linear range.
color_bar_pos: Coordinates of the top left point of the color bar.
color_bar_dims: Width and height of the color bar.
"""
ctx.set_source_rgb(0, 0, 0)
font_size = color_bar_dims[0] / 2.5
ctx.set_font_size(font_size)
_, _, _, text_height, _, _ = ctx.text_extents(str(0))
x = color_bar_pos[0] + color_bar_dims[0] * 1.2
y = color_bar_pos[1] + 0.5 * text_height
ctx.move_to(x, y)
if high > 0 or middle:
ctx.show_text(str(high))
else:
if low == high:
ctx.show_text(str(low))
else:
ctx.show_text("0")
if middle:
x = color_bar_pos[0] + color_bar_dims[0] * 1.2
y = color_bar_pos[1] + 0.5 * color_bar_dims[1] + 0.5 * text_height
ctx.move_to(x, y)
ctx.show_text("0")
x = color_bar_pos[0] + color_bar_dims[0] * 1.2
y = color_bar_pos[1] + color_bar_dims[1] + 0.5 * text_height
ctx.move_to(x, y)
if low < 0 or middle:
ctx.show_text(f"−{abs(low)}")
else:
if low == high:
ctx.show_text(str(high))
else:
ctx.show_text("0")
[docs]def axis_label(
ctx: cairo.Context,
color_bar_pos: tuple[float, float],
color_bar_dims: tuple[float, float],
label: str = "Current (A)",
):
"""Draws axis label of a color bar.
Args:
ctx: Context.
color_bar_pos: Coordinates of the top left point of the color bar.
color_bar_dims: Width and height of the color bar.
label: Axis label of the color bar.
"""
ctx.set_source_rgb(0, 0, 0)
font_size = 0.6 * color_bar_dims[0]
ctx.set_font_size(font_size)
_, _, width, height, _, _ = ctx.text_extents(str(label))
x = color_bar_pos[0] + 2 * color_bar_dims[0] + height
y = color_bar_pos[1] + 0.5 * color_bar_dims[1] + 0.5 * width
ctx.move_to(x, y)
ctx.rotate(-np.pi / 2)
ctx.show_text(str(label))
ctx.rotate(np.pi / 2)