Source code for radioviz.services.color_service

#  Copyright 2025–2026 European Union
#  Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
#  SPDX-License-Identifier: EUPL-1.2
"""
Color service module for generating sequential colors.

This module provides functionality for generating a sequence of distinct
colors using the HSV color space. It includes utilities for converting
colors between different formats and a service class for generating
sequential colors based on the golden ratio.

The main components are:
- :class:`Color` data class for immutable color representation
- :func:`to_mpl` and :func:`to_qcolor` conversion functions
- :class:`ColorService` for generating sequential colors
"""

from __future__ import annotations

import colorsys
import uuid
from dataclasses import dataclass

import cmap
import matplotlib
from PySide6.QtGui import QColor

from radioviz.services.workspace_manager import ServiceWorkspace, WorkspaceReferenceManager


[docs] @dataclass(frozen=True) class Color: """ Immutable color representation in RGB space. This class represents a color using RGB values in the range [0, 1]. It is designed to be immutable and can be used as a key in dictionaries or stored in sets. :ivar r: Red component of the color (0.0 to 1.0) :vartype r: float :ivar g: Green component of the color (0.0 to 1.0) :vartype g: float :ivar b: Blue component of the color (0.0 to 1.0) :vartype b: float """ r: float """Red component of the color (0.0 to 1.0).""" g: float """Green component of the color (0.0 to 1.0).""" b: float """Blue component of the color (0.0 to 1.0)."""
[docs] def to_mpl(color: Color) -> tuple[float, float, float]: """ Convert a Color object to matplotlib RGB tuple format. Convert a Color instance to a tuple of RGB values suitable for matplotlib plotting functions. :param color: The color to convert :type color: Color :return: RGB tuple in the range [0, 1] :rtype: tuple[float, float, float] """ return color.r, color.g, color.b
[docs] def to_qcolor(color: Color) -> QColor: """ Convert a Color object to Qt QColor. Convert a Color instance to a QColor object for use in Qt applications. :param color: The color to convert :type color: Color :return: QColor object representing the same color :rtype: QColor """ return QColor.fromRgbF(color.r, color.g, color.b)
[docs] class ColorService: """ Service for generating sequential colors using HSV color space. This class generates a sequence of distinct colors using the golden ratio to distribute hues evenly across the color spectrum. Colors are generated in HSV color space and converted to RGB for output. :ivar _hue: Current hue value in the color space :vartype _hue: float :ivar _step: Step size for hue increment (golden ratio) :vartype _step: float :ivar _s: Saturation value for generated colors :vartype _s: float :ivar _v: Value (brightness) value for generated colors :vartype _v: float """ service_id = 'color_service' version = '1.0' id = uuid.uuid4() def __init__(self, saturation: float = 0.65, value: float = 0.9) -> None: """ Initialize the ColorService with given saturation and value. :param saturation: Saturation value for generated colors (default: 0.65) :type saturation: float :param value: Value (brightness) value for generated colors (default: 0.9) :type value: float """ self._hue = 0.0 self._step = 0.61803398875 # golden ratio self._s = saturation self._v = value
[docs] def next_color(self) -> Color: """ Generate the next color in the sequence. This method advances the internal hue value by the golden ratio step and returns the corresponding RGB color in the HSV color space. :return: The next color in the sequence :rtype: Color """ self._hue = (self._hue + self._step) % 1.0 return Color(*colorsys.hsv_to_rgb(self._hue, self._s, self._v))
[docs] def reset(self) -> None: """ Reset the color generation sequence to the beginning. This method resets the internal hue value to zero, allowing the color sequence to start from the beginning again. """ self._hue = 0.0
[docs] def to_workspace(self) -> ServiceWorkspace: """ Serialize the current state of the color service to a workspace. This method creates a ServiceWorkspace object containing the current hue, saturation, and value settings of the color service. This allows the color service state to be saved and restored later. :return: A ServiceWorkspace object containing the current state :rtype: ServiceWorkspace """ return ServiceWorkspace( service_id=self.service_id, version=self.version, state=dict(hue=self._hue, saturation=self._s, value=self._v), )
[docs] def from_workspace(self, workspace: ServiceWorkspace, ctx: WorkspaceReferenceManager) -> None: """ Restore the color service state from a workspace. This method updates the internal state of the color service using the data stored in the provided ServiceWorkspace object. It retrieves the hue, saturation, and value settings from the workspace state. :param workspace: The workspace containing the saved state :type workspace: ServiceWorkspace :param ctx: The workspace context (not used in this implementation) :type ctx: WorkspaceReferenceManager """ self._hue = workspace.state.get('hue', 0.0) self._s = workspace.state.get('saturation', 0.65) self._v = workspace.state.get('value', 0.9)
[docs] def cmap_to_mpl(cmap_name: str) -> matplotlib.colors.Colormap: """ Convert a colormap name to matplotlib format. This function attempts to convert a colormap name to a matplotlib compatible format. It first checks if the cmap library has a `to_mpl` method, and if not, it creates a Colormap instance and converts it manually. :param cmap_name: Name of the colormap to convert :type cmap_name: str :return: Matplotlib compatible colormap :rtype: matplotlib.colors.Colormap """ if hasattr(cmap, 'to_mpl'): return cmap.to_mpl(cmap_name) from cmap import Colormap cm = Colormap(cmap_name) return cm.to_matplotlib()