Refactor into a Library Format #1

Merged
cal merged 7 commits from refactor/library into main 2026-05-20 01:34:48 +00:00
6 changed files with 329 additions and 234 deletions
Showing only changes of commit 82e94f5fd4 - Show all commits
+11 -1
View File
@@ -1 +1,11 @@
from .makegraph import * from .colours import ColourValue, ColourContainer
from .uq_colours import UQ_COLOURS_DICT, UQ_COLOURS, add_colours_to_mpl
from .makegraph import makeGraph, pltKeyClose
__all__ = [
"makeGraph", "pltKeyClose",
"ColourValue", "ColourContainer",
"UQ_COLOURS_DICT", "UQ_COLOURS"
]
+2 -1
View File
@@ -4,7 +4,8 @@
import numpy as np import numpy as np
from . import makeGraph, UQ_COLOURS from . import makeGraph
from . import UQ_COLOURS
if __name__ == '__main__': if __name__ == '__main__':
#This is an example of drawing 4 plots by generating them #This is an example of drawing 4 plots by generating them
-66
View File
@@ -1,66 +0,0 @@
from functools import lru_cache
import matplotlib.colors as colors
import numpy as np
class ColourValue(str):
def __new__(cls, value, /, name=None) -> "ColourValue":
hex_val = colors.to_hex(value, True)
instance = super().__new__(cls, hex_val)
instance._name = name
return instance
def __repr__(self) -> str:
if self.name:
name = f"\"{self.name}\" "
else:
name = ""
return f"<{self.__class__.__name__}" \
f" {name}{self} " \
f"({self.rgba[0]:.2f}, {self.rgba[1]:.2f}, " \
f"{self.rgba[2]:.2f}, {self.rgba[3]:.2f})>"
def alpha_adj(self, alpha):
r, g, b, a = self.rgba
return (r, g, b, alpha)
@property
@lru_cache
def rgba(self) -> tuple[float, float, float, float]:
return colors.to_rgba(self)
@property
@lru_cache
def rgb(self) -> tuple[float, float, float]:
return colors.to_rgb(self)
@property
@lru_cache
def hsv(self) -> np.ndarray:
return colors.rgb_to_hsv(self.rgb)
@property
@lru_cache
def name(self) -> str:
if hasattr(self, "_name") and self._name is not None:
name = self._name
else:
name = ""
assert isinstance(name, str), \
TypeError(f"`name` should be an instance of `str` not `{type(name)}`")
return name
@property
def hex(self) -> str:
return self
if __name__ == "__main__":
foo = ColourValue((0,0.5,0.6), "Whoa a name!")
print(foo)
exit()
+177
View File
@@ -0,0 +1,177 @@
# Better Colour Class for matplotlib
__author__ = "Cal Wing"
__version__ = "0.0.1"
from cycler import cycler, Cycler
from typing import Sequence, ItemsView, KeysView
from functools import lru_cache
import matplotlib.colors as colors
import numpy as np
type ValidColour = ColourValue | \
str | \
tuple[float, float, float, float] | \
tuple[float, float, float]
class ColourValue(str):
def __new__(
cls,
value: ValidColour,
/,
name: str | None = None
) -> "ColourValue":
hex_val = colors.to_hex(value, True)
instance = super().__new__(cls, hex_val)
instance._name = name
return instance
def __repr__(self) -> str:
if self.name:
name = f"\"{self.name}\" "
else:
name = ""
return f"<{self.__class__.__name__}" \
f" {name}{self} " \
f"({self.rgba[0]:.2f}, {self.rgba[1]:.2f}, " \
f"{self.rgba[2]:.2f}, {self.rgba[3]:.2f})>"
def alpha_adj(self, alpha):
r, g, b, _ = self.rgba
return (r, g, b, alpha)
@property
@lru_cache
def rgba(self) -> tuple[float, float, float, float]:
return colors.to_rgba(self)
@property
@lru_cache
def rgb(self) -> tuple[float, float, float]:
return colors.to_rgb(self)
@property
@lru_cache
def hsv(self) -> np.ndarray:
return colors.rgb_to_hsv(self.rgb)
@property
@lru_cache
def name(self) -> str:
if hasattr(self, "_name") and self._name is not None:
name = self._name
else:
name = ""
assert isinstance(name, str), \
TypeError(f"`name` should be an instance of `str` not `{type(name)}`")
return name
@property
def hex(self) -> str:
return self
type ValidColourDicts = dict[str, str | ColourValue] | \
dict[str, tuple[float, float, float, float]] | \
dict[str, tuple[float, float, float]]
class ColourContainer:
def __init__(
self,
colours: ValidColourDicts | Sequence[ColourValue],
*,
lookup_replacement: dict[str, str] = {" ": "_"}
) -> None:
self._colours: dict[str, ColourValue] = {}
self._lookup_replacement = lookup_replacement
if isinstance(colours, dict):
for key, value in colours.items():
assert isinstance(key, str), \
TypeError(f"`key` must be an instance of `str` not type `{type(key)}`")
self._colours[key] = ColourValue(value, name=key) # ty:ignore[invalid-argument-type]
setattr(self, key, self._colours[key])
else:
for colour in colours:
self._colours[colour.name] = colour
setattr(self, colour.name, self._colours[colour.name])
def __repr__(self) -> str:
return f"Colour List of {len(self._colours)} " \
f"colour{'s' if len(self._colours) > 0 else ''}: " + \
str(list(self._colours.keys()))
def __getitem__(self, key: str) -> ColourValue:
for alt_char, original_char in self._lookup_replacement.items():
if key.replace(alt_char, original_char) in self.keys():
key = key.replace(alt_char, original_char)
break
return self._colours[key]
def __iter__(self):
return iter(self._colours)
def items(self) -> ItemsView[str, ColourValue]:
return self._colours.items()
def keys(self) -> KeysView[str]:
return self._colours.keys()
def make_cycler(
self,
order: Sequence[str] | None = None,
/,
hex: bool = True
) -> Cycler[str, str]:
cycle_order = []
if order is None:
for _, colour in self.items():
c = colour.hex if hex else colour
cycle_order.append(c)
else:
for key in order:
c = self[key].hex if hex else self[key]
cycle_order.append(c)
return cycler(color=cycle_order)
# Generate a matplotlib colour mapping dictionary
def mpl_colour_mapping(
self,
prefix: str,
/,
hex: bool = True,
*,
add_separator: bool = True
) -> dict[str, str]:
mapping: dict[str, str] = {}
if add_separator:
prefix += ":"
for name, colour in self.items():
c = colour.hex if hex else colour
mapping[prefix + name] = c
# Allow all alt names
for alt_char, original_char in self._lookup_replacement.items():
mapping[prefix + name.replace(alt_char, original_char)] = c
return mapping
+1 -166
View File
@@ -8,177 +8,13 @@
#### 2025 - Released under MIT #### 2025 - Released under MIT
__author__ = "Cal Wing" __author__ = "Cal Wing"
__version__ = "0.1.12" __version__ = "0.2.0"
from collections.abc import Iterator
import numpy as np import numpy as np
import matplotlib import matplotlib
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib.colors as colors import matplotlib.colors as colors
from mpl_toolkits.axes_grid1 import make_axes_locatable from mpl_toolkits.axes_grid1 import make_axes_locatable
from cycler import cycler
#from tqdm import tqdm
from collections import UserString
# Define the UQ Colours
UQ_COLOURS_DICT = {
# Primary
"purple": "#51247A",
"white" : "#FFFFFF",
"black" : "#000000",
# Secondary
"light_purple": "#962A8B",
"red" : "#E62645",
"green" : "#2EA836",
"gold" : "#BB9D65",
"neutral" : "#D7D1CC",
"orange" : "#EB602B",
"yellow" : "#FBB800",
"blue" : "#4085C6",
"aqua" : "#00A2C7",
"dark_grey" : "#999490"
}
# Define a colour object that can do neat conversions & things, by default stores as hex value
class ColourValue(UserString):
def __init__(self, name, value):
self.name = name
self.value = colors.to_hex(value, True)
super().__init__(self.value)
#def __new__(self, name, value):
# self.name = name
# self.value = colors.to_hex(value, True)
#
# return super().__new__(self, self.value)
def __str__(self) -> str:
return self.value
def __repr__(self) -> str:
return self.name + " " + self.value + " " + str(self.rgba())
def rgba(self) -> tuple[float, float, float, float]:
return colors.to_rgba(self.value)
def rgb(self) -> tuple[float, float, float]:
return colors.to_rgb()
def hex(self) -> str:
return self.value
def hsv(self) -> np.ndarray:
return colors.rgb_to_hsv(self.rgb())
def alpha_adj(self, alpha):
r, g, b, a = self.rgba()
return (r, g, b, alpha)
# Define the UQ Colours in a nicer object
class ColourList(object):
def __init__(self, colours: dict) -> None:
self.colours = {}
for key, value in colours.items():
self.colours[key] = ColourValue(key, value)
setattr(self, key, self.colours[key])
def __getitem__(self, key: str) -> str:
if key.replace(" ", "_") in self.colours.keys():
key = key.replace(" ", "_")
return self.colours[key]
def items(self):
return self.colours.items()
def __repr__(self) -> str:
return f"Colour List of {len(self.colours)} colour{'s' if len(self.colours) > 0 else ''}: " + str(list(self.colours.keys()))
UQ_COLOURS = ColourList(UQ_COLOURS_DICT)
# Load UQ Colours into MatPlotLib
# UQ colours are prefaced with 'uq:', so UQ red is 'uq:red'
# Note: Any names That have a _ also have a version with spaces so both "uq:light_purple" and "uq:light purple" work
uq_colour_mapping = {'uq:' + name: str(value) for name, value in list(UQ_COLOURS.items()) + [(x[0].replace("_", " "), x[1]) for x in UQ_COLOURS.items() if "_" in x[0]]}
colors.get_named_colors_mapping().update( uq_colour_mapping )
## UQ Colour Cycler
# +-----------------------------+-----------------------------+
# | Default (Tab) | UQ |
# +-----------------------------+-----------------------------+
# | C00 | #1f77b4 -> tab:blue | #51247A -> uq:purple |
# | C01 | #ff7f0e -> tab:orange | #4085C6 -> uq:blue |
# | C02 | #2ca02c -> tab:green | #2EA836 -> uq:green |
# | C03 | #d62728 -> tab:red | #E62645 -> uq:red |
# | C04 | #9467bd -> tab:purple | #962A8B -> uq:light_purple |
# | C05 | #8c564b -> tab:brown | #999490 -> uq:dark_grey |
# | C06 | #e377c2 -> tab:pink | #EB602B -> uq:orange |
# | C07 | #7f7f7f -> tab:grey | #FBB800 -> uq:yellow |
# | C08 | #bcbd22 -> tab:olive | #00A2C7 -> uq:aqua |
# | C09 | #17becf -> tab:cyan | #BB9D65 -> uq:gold |
# | C10 | | #D7D1CC -> uq:neutral |
# +-----------------------------+-----------------------------+
# Build a colour cycler
uq_colour_cycler = cycler(color=[
UQ_COLOURS["purple"].data, #51247A -> C00 -> uq:purple
UQ_COLOURS["blue"].data, #4085C6 -> C01 -> uq:blue
UQ_COLOURS["green"].data, #2EA836 -> C02 -> uq:green
UQ_COLOURS["red"].data, #E62645 -> C03 -> uq:red
UQ_COLOURS["light_purple"].data, #962A8B -> C04 -> uq:light_purple
UQ_COLOURS["dark_grey"].data, #999490 -> C05 -> uq:dark_grey
UQ_COLOURS["orange"].data, #EB602B -> C06 -> uq:orange
UQ_COLOURS["yellow"].data, #FBB800 -> C07 -> uq:yellow
UQ_COLOURS["aqua"].data, #00A2C7 -> C08 -> uq:aqua
UQ_COLOURS["gold"].data, #BB9D65 -> C09 -> uq:gold
UQ_COLOURS["neutral"].data #D7D1CC -> C10 -> uq:neutral
])
# Tell MatPlotLib to use said cycler
plt.rc('axes', prop_cycle=uq_colour_cycler)
## UQ Colour Gradient (Not very good :( )
uq_colour_map_grad = [
UQ_COLOURS["purple"],
UQ_COLOURS["light_purple"],
UQ_COLOURS["light_purple"],
UQ_COLOURS["blue"],
UQ_COLOURS["blue"],
UQ_COLOURS["aqua"],
UQ_COLOURS["green"],
UQ_COLOURS["green"],
UQ_COLOURS["green"],
UQ_COLOURS["yellow"],
UQ_COLOURS["yellow"]
]
#Convert to RGB values
uq_colour_map_grad = [colors.to_rgb(c) for c in uq_colour_map_grad]
#Populate the working dict
uq_colour_dict = {
"red": [],
"green": [],
"blue": [],
}
for i, c in enumerate(uq_colour_map_grad):
offset = i / (len(uq_colour_map_grad) - 1)
uq_colour_dict["red"].append( (offset, c[0], c[0]) )
uq_colour_dict["green"].append( (offset, c[1], c[1]) )
uq_colour_dict["blue"].append( (offset, c[2], c[2]) )
#Define & register the colour map itself
uq_cmap = colors.LinearSegmentedColormap('uq',segmentdata=uq_colour_dict)
matplotlib.colormaps.register(uq_cmap)
# Set the colour map - Not a very good default so not doing that
#plt.set_cmap("uq")
## Colorbar Function by Joseph Long & Mike Lampton ## Colorbar Function by Joseph Long & Mike Lampton
# Retrieved from https://joseph-long.com/writing/colorbars/ on 31/10/2021 # Retrieved from https://joseph-long.com/writing/colorbars/ on 31/10/2021
@@ -201,7 +37,6 @@ def colorbar(mappable, size="5%", pad=0.05, lsize=None, lpad=None, lax=True, **k
plt.sca(last_axes) plt.sca(last_axes)
return cbar return cbar
## Make Graph Function ## Make Graph Function
def makeGraph(graphData, showPlot=True, doProgramBlock=True, figSavePath=None, hideEmptyAxis=False) -> tuple[matplotlib.figure.Figure, tuple[matplotlib.axes.Axes, ...]]: def makeGraph(graphData, showPlot=True, doProgramBlock=True, figSavePath=None, hideEmptyAxis=False) -> tuple[matplotlib.figure.Figure, tuple[matplotlib.axes.Axes, ...]]:
""" Generate a matplotlib graph based on a simple dictionary object """ Generate a matplotlib graph based on a simple dictionary object
+138
View File
@@ -0,0 +1,138 @@
# UQ Colour Definitions
__author__ = "Cal Wing"
__version__ = "0.0.1"
import matplotlib.pyplot as plt
from .colours import ColourContainer
import matplotlib
import matplotlib.colors as mpl_colors
# Need to use env-vars to catch if we want to inject colours
import os
_MG_INJECT_MPL_COLOURS_ENVIRON_VAR = "MG_INJECT_MPL_UQ_COLOURS"
if _MG_INJECT_MPL_COLOURS_ENVIRON_VAR in os.environ:
try:
INJECT_COLOURS = bool(int(os.environ[_MG_INJECT_MPL_COLOURS_ENVIRON_VAR]))
except ValueError:
INJECT_COLOURS = False
else:
# Default to always injecting colours on load
INJECT_COLOURS = True
# Define the UQ Colours
UQ_COLOURS_DICT = {
# Primary
"purple": "#51247A",
"white" : "#FFFFFF",
"black" : "#000000",
# Secondary
"light_purple": "#962A8B",
"red" : "#E62645",
"green" : "#2EA836",
"gold" : "#BB9D65",
"neutral" : "#D7D1CC",
"orange" : "#EB602B",
"yellow" : "#FBB800",
"blue" : "#4085C6",
"aqua" : "#00A2C7",
"dark_grey" : "#999490"
}
UQ_COLOURS = ColourContainer(UQ_COLOURS_DICT)
# Make UQ Colours names for MatPlotLib
# UQ colours are prefaced with 'uq:', so UQ red is 'uq:red'
# Note: Any names That have a _ also have a version with spaces so both "uq:light_purple" and "uq:light purple" work
uq_colour_mapping = UQ_COLOURS.mpl_colour_mapping("uq")
## UQ Colour Cycler
# +-----------------------------+-----------------------------+
# | Default (Tab) | UQ |
# +-----------------------------+-----------------------------+
# | C00 | #1f77b4 -> tab:blue | #51247A -> uq:purple |
# | C01 | #ff7f0e -> tab:orange | #4085C6 -> uq:blue |
# | C02 | #2ca02c -> tab:green | #2EA836 -> uq:green |
# | C03 | #d62728 -> tab:red | #E62645 -> uq:red |
# | C04 | #9467bd -> tab:purple | #962A8B -> uq:light_purple |
# | C05 | #8c564b -> tab:brown | #999490 -> uq:dark_grey |
# | C06 | #e377c2 -> tab:pink | #EB602B -> uq:orange |
# | C07 | #7f7f7f -> tab:grey | #FBB800 -> uq:yellow |
# | C08 | #bcbd22 -> tab:olive | #00A2C7 -> uq:aqua |
# | C09 | #17becf -> tab:cyan | #BB9D65 -> uq:gold |
# | C10 | | #D7D1CC -> uq:neutral |
# +-----------------------------+-----------------------------+
# Build a colour cycler
uq_colour_cycler = UQ_COLOURS.make_cycler([
"purple", #51247A -> C00 -> uq:purple
"blue", #4085C6 -> C01 -> uq:blue
"green", #2EA836 -> C02 -> uq:green
"red", #E62645 -> C03 -> uq:red
"light_purple", #962A8B -> C04 -> uq:light_purple
"dark_grey", #999490 -> C05 -> uq:dark_grey
"orange", #EB602B -> C06 -> uq:orange
"yellow", #FBB800 -> C07 -> uq:yellow
"aqua", #00A2C7 -> C08 -> uq:aqua
"gold", #BB9D65 -> C09 -> uq:gold
"neutral" #D7D1CC -> C10 -> uq:neutral
])
## UQ Colour Gradient (Not very good :( )
uq_colour_map_grad = [
UQ_COLOURS["purple"],
UQ_COLOURS["light_purple"],
UQ_COLOURS["light_purple"],
UQ_COLOURS["blue"],
UQ_COLOURS["blue"],
UQ_COLOURS["aqua"],
UQ_COLOURS["green"],
UQ_COLOURS["green"],
UQ_COLOURS["green"],
UQ_COLOURS["yellow"],
UQ_COLOURS["yellow"]
]
#Convert to RGB values
uq_colour_map_grad = [mpl_colors.to_rgb(c) for c in uq_colour_map_grad]
#Populate the working dict
uq_colour_dict = {
"red": [],
"green": [],
"blue": [],
}
for i, c in enumerate(uq_colour_map_grad):
offset = i / (len(uq_colour_map_grad) - 1)
uq_colour_dict["red"].append( (offset, c[0], c[0]) )
uq_colour_dict["green"].append( (offset, c[1], c[1]) )
uq_colour_dict["blue"].append( (offset, c[2], c[2]) )
#Define & register the colour map itself
uq_cmap = mpl_colors.LinearSegmentedColormap('uq', segmentdata=uq_colour_dict) # ty:ignore[invalid-argument-type]
# Wrap all MPL calls in a function
def add_colours_to_mpl(update_cycler: bool = True, once_only: bool = False):
# Set this env_var so that any subsequent imports inject the correct colours
# oh the joys of multi-processing
if not once_only:
os.environ[_MG_INJECT_MPL_COLOURS_ENVIRON_VAR] = "1"
# Load "uq:" colours into mpl
mpl_colors.get_named_colors_mapping().update( uq_colour_mapping )
# Tell MatPlotLib to use said cycler
# TODO: Make an optional import that tracks
if update_cycler:
plt.rc('axes', prop_cycle=uq_colour_cycler)
# Add the colour map to MPL
matplotlib.colormaps.register(uq_cmap)
# Set the colour map - Not a very good default so not doing that
#plt.set_cmap("uq")
if INJECT_COLOURS:
add_colours_to_mpl()