Source code for pltviz.utils

"""
Utilities
---------

Utility functions for general operations and coloration.

Note:
In order to standardize coloration and make plots more readable,
input colors are by default scaled to a lower saturation.

This follows seaborn's default darkening of colors,
but is not as dark to roughly maintain input colors.

Functions here use pandas, pyplot, and seaborn, hence the need for standardization.

Contents:
    round_if_int,
    gen_list_of_lists,
    add_num_commas,
    hex_to_rgb,
    rgb_to_hex,
    scale_saturation,
    create_color_palette,
    gen_random_colors
"""

import colorsys
from random import SystemRandom

import matplotlib as mpl
import numpy as np
import seaborn as sns
from colormath.color_conversions import convert_color
from colormath.color_objects import sRGBColor


[docs]def round_if_int(val): """ Rounds off the decimal of a value if it is an integer float. Parameters ---------- val : float or int A numeric value to be rounded. Retruns ------- val : int The original value rounded if applicable. """ if isinstance(val, float) and val.is_integer(): val = int(val) return val
[docs]def gen_list_of_lists(original_list, new_structure): """ Generates a list of lists with a given structure from a given list. Parameters ---------- original_list : list The list to make into a list of lists. new_structure : list of lists (contains ints). Returns ------- list_of_lists : list of lists The original list with elements organized with the given structure. """ assert len(original_list) == sum( new_structure ), "The number of elements in the original list and desired structure don't match." return [ [original_list[i + sum(new_structure[:j])] for i in range(new_structure[j])] for j in range(len(new_structure)) ]
[docs]def add_num_commas(num): """ Adds commas to a numeric string for readability. Parameters ---------- num : int or float A number to have commas added to. Retruns ------- str_with_commas : str The original number with commas to make it more readable. """ num_str = str(num) num_str_no_decimal = num_str.split(".")[0] decimal = num_str.split(".")[1] if "." in num_str else None str_list = [i for i in num_str_no_decimal] str_list = str_list[::-1] str_list_with_commas = [ s + "," if i % 3 == 0 and i != 0 else s for i, s in enumerate(str_list) ] str_list_with_commas = str_list_with_commas[::-1] str_with_commas = "".join(str_list_with_commas) if decimal != None: return str_with_commas + "." + decimal else: return str_with_commas
[docs]def hex_to_rgb(hex_rep): """ Converts a hexadecimal representation to its RGB ratios. Parameters ---------- hex_rep : str The hex representation of the color. Returns ------- rgb_trip : tuple An RGB tuple color representation. """ return sRGBColor( *[int(hex_rep[i + 1 : i + 3], 16) for i in (0, 2, 4)], is_upscaled=True )
[docs]def rgb_to_hex(rgb_trip): """ Converts rgb ratios to their hexadecimal representation. Parameters ---------- rgb_trip : tuple An RGB tuple color representation. Returns ------- hex_rep : str The hex representation of the color. """ trip_0, trip_1, trip_2 = rgb_trip[0], rgb_trip[1], rgb_trip[2] if isinstance(trip_0, (float, np.float64)): trip_0 *= 255 trip_1 *= 255 trip_2 *= 255 return "#%02x%02x%02x" % (int(trip_0), int(trip_1), int(trip_2))
[docs]def scale_saturation(rgb_trip, sat): """ Changes the saturation of an rgb color. Parameters ---------- rgb_trip : tuple An RGB tuple color representation. sat : float The saturation it rgb_trip should be modified by. Returns ------- saturated_rgb : tuple colorsys.hls_to_rgb saturation of the given color. """ if (isinstance(rgb_trip, str)) and (len(rgb_trip) == 9) and (rgb_trip[-2:] == "00"): # An RGBA has been provided and its alpha is 00, so return it for # a transparent marker. return rgb_trip if (isinstance(rgb_trip, str)) and (len(rgb_trip) == 7): rgb_trip = hex_to_rgb(rgb_trip) if isinstance(rgb_trip, sRGBColor): rgb_trip = rgb_trip.get_value_tuple() h, l, s = colorsys.rgb_to_hls(*rgb_trip) return colorsys.hls_to_rgb(h, min(1, l * sat), s=s)
[docs]def create_color_palette(start_rgb, end_rgb, num_colors, colorspace): """ Generates a color palette between two colors. Parameters ---------- start_rgb : colormath.color_objects.sRGBColor The first color in the palette. end_rgb : colormath.color_objects.sRGBColor The last color in the palette. num_colors : int The total colors of the palette including the start and end colors. colorspace : colormath.color_object The color scheme for the palette. Returns ------- palette : list (contains sts) A list of length num_colors with color hexes for the palette elements. """ # Define the start and end within a geometric space and find those points between. start_tuple = convert_color(start_rgb, colorspace).get_value_tuple() end_tuple = convert_color(end_rgb, colorspace).get_value_tuple() points_between = list( zip( *[ np.linspace(start=start_tuple[i], stop=end_tuple[i], num=num_colors) for i in range(3) ] ) ) # Convert points to RGB and then to hexes for the output. rgb_colors = [ convert_color(colorspace(*point), sRGBColor) for point in points_between ] return [color.get_rgb_hex() for color in rgb_colors]
[docs]def gen_random_colors(num_groups, colors=None): """ Generates random colors. Parameters ---------- num_groups : int The number of groups for which colors should be generated. colors : list : optional (contains strs) Hex based colors that should be appended if not enough have been provided. Returns ------- colors or colors + new_colors : list (contains strs) Randomly generated colors for figures and plotting. """ if colors is None: colors = [] if len(colors) < num_groups: while len(colors) < num_groups: cryptogen = SystemRandom() random_rgba = [cryptogen.random() for i in range(4)] colors.append(random_rgba) sns.set_palette(colors) if isinstance(colors[0][0], float): # Convert over for non-sns use. colors = [mpl.colors.to_hex([c[0], c[1], c[2]]).upper() for c in colors] return colors