From 048d1c0d966dc18df51713f8e81aa3e0f70d18fa Mon Sep 17 00:00:00 2001 From: julius Date: Sun, 10 Nov 2024 18:54:23 +0100 Subject: [PATCH] refactoring --- colours.py | 103 ++++++++++++++++++++++++++++++++++++ matrix.py | 149 +++-------------------------------------------------- 2 files changed, 111 insertions(+), 141 deletions(-) create mode 100644 colours.py diff --git a/colours.py b/colours.py new file mode 100644 index 0000000..45cdd61 --- /dev/null +++ b/colours.py @@ -0,0 +1,103 @@ +from abc import ABC, abstractmethod +import numpy as np +# the following functions are taken from Ben Southgate: +# https://bsouthga.dev/posts/colour-gradients-with-python + + +def hex_to_RGB(hex): + """ "#FFFFFF" -> [255,255,255]""" + # Pass 16 to the integer function for change of base + return [int(hex[i : i + 2], 16) for i in range(1, 6, 2)] + + +def RGB_to_hex(RGB): + """[255,255,255] -> "#FFFFFF" """ + # Components need to be integers for hex to make sense + RGB = [int(x) for x in RGB] + return "#" + "".join( + ["0{0:x}".format(v) if v < 16 else "{0:x}".format(v) for v in RGB] + ) + + +def colour_dict(gradient): + """Takes in a list of RGB sub-lists and returns dictionary of + colours in RGB and hex form for use in a graphing function + defined later on.""" + return { + "hex": [RGB_to_hex(RGB) for RGB in gradient], + "r": [RGB[0] for RGB in gradient], + "g": [RGB[1] for RGB in gradient], + "b": [RGB[2] for RGB in gradient], + } + + +def linear_gradient(start_hex, finish_hex="#FFFFFF", n=10): + """returns a gradient list of (n) colours between + two hex colours. start_hex and finish_hex + should be the full six-digit colour string, + inlcuding the number sign ("#FFFFFF")""" + # Starting and ending colours in RGB form + s = hex_to_RGB(start_hex) + f = hex_to_RGB(finish_hex) + # Initilize a list of the output colours with the starting colour + RGB_list = [s] + # Calcuate a colour at each evenly spaced value of t from 1 to n + for t in range(0, n): + # Interpolate RGB vector for colour at the current value of t + curr_vector = [ + int(s[j] + (float(t) / (n - 1)) * (f[j] - s[j])) for j in range(3) + ] + # Add it to our list of output colours + RGB_list.append(curr_vector) + + return colour_dict(RGB_list) + + +def polylinear_gradient(colours, n): + """returns a list of colours forming linear gradients between + all sequential pairs of colours. "n" specifies the total + number of desired output colours""" + # The number of colours per individual linear gradient + n_out = int(float(n) / (len(colours) - 1)) + # returns dictionary defined by colour_dict() + gradient_dict = linear_gradient(colours[0], colours[1], n_out) + + if len(colours) > 1: + for col in range(1, len(colours) - 1): + next = linear_gradient(colours[col], colours[col + 1], n_out) + for k in ("hex", "r", "g", "b"): + # Exclude first point to avoid duplicates + gradient_dict[k] += next[k][1:] + + return gradient_dict + + +class ColourMap(ABC): + @abstractmethod + def __call__(self, v: float): ... + + +class LinearGradientColourMap(ColourMap): + def __init__( + self, + colours: list[str] | None = ["#ff0000", "#ffffff", "#0000ff"], + min_value: float | None = 0, + max_value: float | None = 1, + bins: int = 100, + ): + self.colours = polylinear_gradient(colours, bins) + self.min, self.max = min_value, max_value + + def __call__(self, v: float): + v = max(0, int((v - self.min) / (self.max - self.min) * 100) - 1) + if v >= len(self.colours["hex"]): + breakpoint() + return self.colours["hex"][v] + + +class RandomColourMap(ColourMap): + def __init__(self, random_state: int | list[int] | None = [2, 3, 4, 5, 6]): + self.rgen = np.random.default_rng(random_state) + + def __call__(self, v: float): + return RGB_to_hex([x * 255 for x in self.rgen.random(3)]) diff --git a/matrix.py b/matrix.py index 7d210d4..c75ef06 100644 --- a/matrix.py +++ b/matrix.py @@ -1,121 +1,6 @@ import numpy as np -from abc import ABC, abstractmethod import svg - -rgen = np.random.default_rng() - -# the following functions are taken from Ben Southgate: -# https://bsouthga.dev/posts/colour-gradients-with-python - - -def hex_to_RGB(hex): - """ "#FFFFFF" -> [255,255,255]""" - # Pass 16 to the integer function for change of base - return [int(hex[i : i + 2], 16) for i in range(1, 6, 2)] - - -def RGB_to_hex(RGB): - """[255,255,255] -> "#FFFFFF" """ - # Components need to be integers for hex to make sense - RGB = [int(x) for x in RGB] - return "#" + "".join( - ["0{0:x}".format(v) if v < 16 else "{0:x}".format(v) for v in RGB] - ) - - -def colour_dict(gradient): - """Takes in a list of RGB sub-lists and returns dictionary of - colours in RGB and hex form for use in a graphing function - defined later on.""" - return { - "hex": [RGB_to_hex(RGB) for RGB in gradient], - "r": [RGB[0] for RGB in gradient], - "g": [RGB[1] for RGB in gradient], - "b": [RGB[2] for RGB in gradient], - } - - -def linear_gradient(start_hex, finish_hex="#FFFFFF", n=10): - """returns a gradient list of (n) colours between - two hex colours. start_hex and finish_hex - should be the full six-digit colour string, - inlcuding the number sign ("#FFFFFF")""" - # Starting and ending colours in RGB form - s = hex_to_RGB(start_hex) - f = hex_to_RGB(finish_hex) - # Initilize a list of the output colours with the starting colour - RGB_list = [s] - # Calcuate a colour at each evenly spaced value of t from 1 to n - for t in range(0, n): - # Interpolate RGB vector for colour at the current value of t - curr_vector = [ - int(s[j] + (float(t) / (n - 1)) * (f[j] - s[j])) for j in range(3) - ] - # Add it to our list of output colours - RGB_list.append(curr_vector) - - return colour_dict(RGB_list) - - -def rand_hex_colour(num=1): - """Generate random hex colours, default is one, - returning a string. If num is greater than - 1, an array of strings is returned.""" - colours = [RGB_to_hex([x * 255 for x in rgen.rand(3)]) for i in range(num)] - if num == 1: - return colours[0] - else: - return colours - - -def polylinear_gradient(colours, n): - """returns a list of colours forming linear gradients between - all sequential pairs of colours. "n" specifies the total - number of desired output colours""" - # The number of colours per individual linear gradient - n_out = int(float(n) / (len(colours) - 1)) - # returns dictionary defined by colour_dict() - gradient_dict = linear_gradient(colours[0], colours[1], n_out) - - if len(colours) > 1: - for col in range(1, len(colours) - 1): - next = linear_gradient(colours[col], colours[col + 1], n_out) - for k in ("hex", "r", "g", "b"): - # Exclude first point to avoid duplicates - gradient_dict[k] += next[k][1:] - - return gradient_dict - - -class ColourMap(ABC): - @abstractmethod - def __call__(self, v: float): ... - - -class LinearGradientColourMap(ColourMap): - def __init__( - self, - colours: list[str] | None = ["#ff0000", "#ffffff", "#0000ff"], - min_value: float | None = 0, - max_value: float | None = 1, - bins: int = 100, - ): - self.colours = polylinear_gradient(colours, bins) - self.min, self.max = min_value, max_value - - def __call__(self, v: float): - v = max(0, int((v - self.min) / (self.max - self.min) * 100) - 1) - if v >= len(self.colours["hex"]): - breakpoint() - return self.colours["hex"][v] - - -class RandomColourMap(ColourMap): - def __init__(self, random_state: int | list[int] | None = [2, 3, 4, 5, 6]): - self.rgen = np.random.default_rng(random_state) - - def __call__(self, v: float): - return RGB_to_hex([x * 255 for x in self.rgen.random(3)]) +from colours import ColourMap class MatrixVisualisation: @@ -126,13 +11,14 @@ class MatrixVisualisation: text: bool = False, labels: int | list[str] | bool = False, ): - self.m, self.n = matrix.shape + self.matrix = matrix + self.m, self.n = self.matrix.shape width = 20 height = 20 gap = 1 self.text = text - self.total_width = (gap + width) * n + gap - self.total_height = (gap + height) * m + gap + self.total_width = (gap + width) * self.n + gap + self.total_height = (gap + height) * self.m + gap self.cmap = cmap self.elements = [] @@ -151,7 +37,7 @@ class MatrixVisualisation: width=width, height=height, stroke="transparent", - fill=cmap(matrix[i, j]), + fill=cmap(self.matrix[i, j]), ) ) if text: @@ -162,7 +48,7 @@ class MatrixVisualisation: textLength=width / 2, lengthAdjust="spacingAndGlyphs", class_=["mono"], - text=f"{matrix[i, j]:.02f}", + text=f"{self.matrix[i, j]:.02f}", ) ) @@ -278,25 +164,6 @@ class MatrixVisualisation: def __repr__(self): return f"""Matrix Visualisation: - shape: {matrix.shape} + shape: {self.matrix.shape} size: {self.total_width}x{self.total_height} """ - - -if __name__ == "__main__": - m, n = 30, 20 - matrix = rgen.random(size=(m, n)) - - colours = ["#f5d72a", "#ffffff", "#2182af"] - # colours = ["#ff0000", "#00ff00", "#0000ff"] - cmap = LinearGradientColourMap(colours, matrix.min(), matrix.max()) - # cmap = RandomColourMap() - - fig = MatrixVisualisation(matrix, cmap=cmap) - fig.colourbar(labels=["yellow", "white", "blue"]) - fig.colourbar(labels=5) - fig.colourbar(labels=[0.2, 0.5, 0.55, 0.66, 1]) - filename = "matrix.svg" - print(fig) - with open(filename, "w") as f: - f.write(fig.svg)