Initial commit
This commit is contained in:
178
matrix.py
Normal file
178
matrix.py
Normal file
@@ -0,0 +1,178 @@
|
||||
import numpy as np
|
||||
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 LinearGradientColourMap:
|
||||
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:
|
||||
def __init__(self, random_state: int | list[int] | None = [2, 3, 4, 5, 6]):
|
||||
self.rgen = np.random.default_rng([2, 3, 4, 5, 6])
|
||||
|
||||
def __call__(self, v: float):
|
||||
return RGB_to_hex([x * 255 for x in self.rgen.random(3)])
|
||||
|
||||
|
||||
def colourbar(
|
||||
cmap,
|
||||
min_value: float | None = 0,
|
||||
max_value: float | None = 1,
|
||||
height=100,
|
||||
width=10,
|
||||
resolution=256,
|
||||
):
|
||||
items = [
|
||||
svg.Rect(fill=cmap(v), x=0, y=i, width=width, height=1, stroke="none")
|
||||
for i, v in enumerate(np.linspace(min_value, max_value, resolution))
|
||||
]
|
||||
return svg.G(elements=items, transform=[svg.Scale(1, height / resolution)])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
m, n = 20, 20
|
||||
width = 20
|
||||
height = 20
|
||||
gap = 1
|
||||
text = False
|
||||
total_width = (gap + width) * n + gap
|
||||
total_height = (gap + height) * m + gap
|
||||
|
||||
filename = "matrix.svg"
|
||||
matrix = rgen.random(size=(m, n))
|
||||
colours = ["#f5d72a", "#ffffff", "#2182af"]
|
||||
# colours = ["#ff0000", "#00ff00", "#0000ff"]
|
||||
cmap = LinearGradientColourMap(colours, matrix.min(), matrix.max())
|
||||
# cmap = RandomColourMap()
|
||||
|
||||
items = []
|
||||
items.append(svg.Style(text=".mono { font: monospace; text-align: center;}"))
|
||||
items.append(svg.Style(text=".small { font-size: 25%; }"))
|
||||
|
||||
for i, y in enumerate(range(gap, total_height, gap + height)):
|
||||
for j, x in enumerate(range(gap, total_width, gap + width)):
|
||||
items.append(
|
||||
svg.Rect(
|
||||
x=x,
|
||||
y=y,
|
||||
width=width,
|
||||
height=height,
|
||||
stroke="transparent",
|
||||
fill=cmap(matrix[i, j]),
|
||||
)
|
||||
)
|
||||
if text:
|
||||
items.append(
|
||||
svg.Text(
|
||||
x=x + width / 5,
|
||||
y=y + 3 * height / 4,
|
||||
textLength=width / 2,
|
||||
lengthAdjust="spacingAndGlyphs",
|
||||
class_=["mono"],
|
||||
text=f"{matrix[i, j]:.02f}",
|
||||
)
|
||||
)
|
||||
|
||||
items.append(colourbar(cmap, matrix.min(), matrix.max(), height=100))
|
||||
|
||||
content = svg.SVG(width=total_width, height=total_height, elements=items)
|
||||
with open(filename, "w") as f:
|
||||
f.write(str(content))
|
||||
Reference in New Issue
Block a user