refactoring
This commit is contained in:
parent
d390069609
commit
048d1c0d96
103
colours.py
Normal file
103
colours.py
Normal file
@ -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)])
|
149
matrix.py
149
matrix.py
@ -1,121 +1,6 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
import svg
|
import svg
|
||||||
|
from colours import ColourMap
|
||||||
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)])
|
|
||||||
|
|
||||||
|
|
||||||
class MatrixVisualisation:
|
class MatrixVisualisation:
|
||||||
@ -126,13 +11,14 @@ class MatrixVisualisation:
|
|||||||
text: bool = False,
|
text: bool = False,
|
||||||
labels: int | list[str] | 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
|
width = 20
|
||||||
height = 20
|
height = 20
|
||||||
gap = 1
|
gap = 1
|
||||||
self.text = text
|
self.text = text
|
||||||
self.total_width = (gap + width) * n + gap
|
self.total_width = (gap + width) * self.n + gap
|
||||||
self.total_height = (gap + height) * m + gap
|
self.total_height = (gap + height) * self.m + gap
|
||||||
self.cmap = cmap
|
self.cmap = cmap
|
||||||
|
|
||||||
self.elements = []
|
self.elements = []
|
||||||
@ -151,7 +37,7 @@ class MatrixVisualisation:
|
|||||||
width=width,
|
width=width,
|
||||||
height=height,
|
height=height,
|
||||||
stroke="transparent",
|
stroke="transparent",
|
||||||
fill=cmap(matrix[i, j]),
|
fill=cmap(self.matrix[i, j]),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if text:
|
if text:
|
||||||
@ -162,7 +48,7 @@ class MatrixVisualisation:
|
|||||||
textLength=width / 2,
|
textLength=width / 2,
|
||||||
lengthAdjust="spacingAndGlyphs",
|
lengthAdjust="spacingAndGlyphs",
|
||||||
class_=["mono"],
|
class_=["mono"],
|
||||||
text=f"{matrix[i, j]:.02f}",
|
text=f"{self.matrix[i, j]:.02f}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -278,25 +164,6 @@ class MatrixVisualisation:
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"""Matrix Visualisation:
|
return f"""Matrix Visualisation:
|
||||||
shape: {matrix.shape}
|
shape: {self.matrix.shape}
|
||||||
size: {self.total_width}x{self.total_height}
|
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)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user