My STD deployment
This commit is contained in:
parent
9b5c924b15
commit
106c0f5b6f
163
.gitignore
vendored
Normal file
163
.gitignore
vendored
Normal file
@ -0,0 +1,163 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
#test_*
|
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: Current File",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
"args": ["--debug", "--data-path", "./data"]
|
||||
}
|
||||
]
|
||||
}
|
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"[python]": {"editor.rulers": [80, 100, 120]},
|
||||
"files.exclude": {
|
||||
".venv": true,
|
||||
"__pycache__": true
|
||||
}
|
||||
}
|
97
debug_lib.py
Normal file
97
debug_lib.py
Normal file
@ -0,0 +1,97 @@
|
||||
""" debug_lib
|
||||
A bunch of helpful debuging functions
|
||||
Cal.W 2019
|
||||
"""
|
||||
class auto_input:
|
||||
"""Given a list automagicly enter a value."""
|
||||
def __init__(self, input_list):
|
||||
#Just setup some internal stuff
|
||||
self.input_index = 0
|
||||
self.input_list = input_list
|
||||
|
||||
def __call__(self, input_text):
|
||||
#TLDR: Get the next value from the array and return that
|
||||
# If there was an error then ask the user for input
|
||||
try:
|
||||
out = self.input_list[self.input_index]
|
||||
except:
|
||||
out = None
|
||||
|
||||
self.input_index += 1
|
||||
|
||||
if out is None:
|
||||
out = __builtins__['input']('#Input#: '+input_text)
|
||||
else:
|
||||
print(str(input_text)+str(out))
|
||||
|
||||
return str(out)
|
||||
|
||||
def stringCompairTests(test_function, tests, doPrint=True, logFile=None):
|
||||
"""Run a bunch of tests on a function and check the outputs via comparing them to wanted outputs as strings.
|
||||
Prams:
|
||||
test_function: function - This needs to be the function to test
|
||||
tests: list - The list of tests to run. Each test is a list in the form: [(arg1, arg2, ...), expected_output]
|
||||
Note: The number of arguments in the tuple *must* equal the number required for the function
|
||||
doPrint: bool - Print the test results - Default: True
|
||||
logFile: str - The path to write the output as a log if it is NoneType(None) then it won't make a logfile - Default: None
|
||||
|
||||
Returns:
|
||||
list - A list containing each test result as a tuple in the form: (returned_result, Bool(test_succeeded), test)
|
||||
|
||||
|
||||
Example:
|
||||
def doThing(arg1, arg2):
|
||||
return arg1 + arg2
|
||||
|
||||
tests = [
|
||||
[(1, 2), 3],
|
||||
[("Hello", "World"), "HelloWorld"],
|
||||
[(1, 3), 2]
|
||||
]
|
||||
|
||||
test_results = stringCompairTests(doThing, tests)
|
||||
|
||||
>>>test_results => [
|
||||
(3, True, [(1, 2), 3]),
|
||||
("HelloWorld", True, [("Hello", "World"), "HelloWorld"]),
|
||||
(4, False, [(1, 3), 2])
|
||||
]
|
||||
"""
|
||||
test_results = []
|
||||
logString = ""
|
||||
|
||||
for i, test in enumerate(tests):
|
||||
result = test_function(**test[0]) if type(test[0]) is dict else test_function(*test[0])
|
||||
test_results.append((result, str(test[1]) == str(result), test))
|
||||
|
||||
logStringL = "-"*50
|
||||
logStringL += "\n"+ "Test: " + str(i) + (" - Success" if result else " - Failure")
|
||||
logStringL += "\n"+ "Given Arguments: \"" + '", "'.join(test[0]) + '"'
|
||||
logStringL += "\n"+ "Expected Output: " + str(test[1])
|
||||
logStringL += "\n"+ "Returned Output: " + str(result)
|
||||
logStringL += "\n"+ "Returned Values " + ("don't" if not result else "") + "match."
|
||||
|
||||
logString += '\n' + logStringL
|
||||
|
||||
if doPrint: print(logStringL)
|
||||
|
||||
|
||||
successfull_tests = sum([1 for x in test_results if x[1]])
|
||||
logStringL = "\n" + str(successfull_tests) + " test" + ("s" if len(test_results)-successfull_tests == 1 else '')
|
||||
logStringL += " succeeded with " + str(len(test_results)-successfull_tests) + " test"
|
||||
logStringL += ("s" if len(test_results)-successfull_tests == 1 else "") + " failing."
|
||||
logString += '\n' + logStringL
|
||||
|
||||
if doPrint: print(logStringL,"\n")
|
||||
|
||||
if logFile not in (None, ""):
|
||||
if type(logFile) is bool and logFile: logFile = __file__.rsplit('\\', 1)[1]
|
||||
if logString[0] == "\n": logString = logString[1:]
|
||||
|
||||
with open(str(logFile)+".log", 'w') as writeFile:
|
||||
writeFile.write(logString)
|
||||
writeFile.close()
|
||||
|
||||
if doPrint: print("The logfile:", '\''+str(logFile)+".log'", "has been written to.", "\n")
|
||||
|
||||
return test_results
|
590
makeGraph.py
Normal file
590
makeGraph.py
Normal file
@ -0,0 +1,590 @@
|
||||
### MatPlotLib Graph Wrapper
|
||||
#### Written by Cal.W 2020, originally for MECH2700 but continually
|
||||
#### expanded upon.
|
||||
#### 2023 - Added UQ Colors
|
||||
#### 2023 - Added pltKeyClose function
|
||||
#### 2023 - Added UQ Default Colours to MatPlotLib
|
||||
#### 2023 - Added auto-magic 3D plot generation, label addition & min/max line points
|
||||
|
||||
__author__ = "Cal Wing"
|
||||
__version__ = "0.1.12"
|
||||
|
||||
from collections.abc import Iterator
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.colors as colors
|
||||
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
|
||||
# Retrieved from https://joseph-long.com/writing/colorbars/ on 31/10/2021
|
||||
# Minor Modifications made by Cal.W 2021
|
||||
def colorbar(mappable, size="5%", pad=0.05, lsize=None, lpad=None, lax=True, **kwargs):
|
||||
last_axes = plt.gca()
|
||||
ax = mappable.axes
|
||||
fig = ax.figure
|
||||
divider = make_axes_locatable(ax)
|
||||
if lax:
|
||||
lsize = lsize if lsize is not None else size
|
||||
lpad = lpad if lpad is not None else pad
|
||||
dax = divider.append_axes("left", size=lsize, pad=lpad)
|
||||
dax.set_frame_on(False)
|
||||
dax.grid(False)
|
||||
dax.set_yticks([])
|
||||
dax.set_xticks([])
|
||||
cax = divider.append_axes("right", size=size, pad=pad)
|
||||
cbar = fig.colorbar(mappable, cax=cax, **kwargs)
|
||||
plt.sca(last_axes)
|
||||
return cbar
|
||||
|
||||
|
||||
## Make Graph Function
|
||||
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
|
||||
Input:
|
||||
dict(graphData): The dictionary containing all the graph data - see example for more info
|
||||
bool(showPlot[True]): Should the function display the plot
|
||||
bool(doProgramBlock[True]): Should the function block the main python thread
|
||||
str(figSavePath[None]): The path to save a copy of the figure, calls fig.savefig if not None
|
||||
|
||||
Returns: The the figure and axes from matplotlib.pyplot.subplots()
|
||||
From 'matplotlib.pyplot.subplots():
|
||||
fig : `matplotlib.figure.Figure`
|
||||
ax : `matplotlib.axes.Axes` or array of Axes
|
||||
*ax* can be either a single `~matplotlib.axes.Axes` object or an
|
||||
array of Axes objects if more than one subplot was created.
|
||||
Example:
|
||||
makeGraph({
|
||||
"title": "Simple Plot",
|
||||
"xLabel": "x label",
|
||||
"yLabel": "y label",
|
||||
"plots": [
|
||||
{"x":[0,1,2,3,4], "y":[0,1,2,3,4], "label":"Linear"},
|
||||
{"x":[0,1,2,3,4], "y":[5,5,5,5,5]},
|
||||
{"x":[4,3,2,1,0], "y":[4,3,2,1,0], "label":"Linear2"},
|
||||
{"x":0, "type":"axvLine", "label":"Red Vertical Line", "color":"red"},
|
||||
{"y":6, "type":"axhLine", "label":"Dashed Horizontal Line", "args":{"linestyle":"--"}},
|
||||
{"type":"scatter", "x":4, "y":4, "label":"A Random Point", "colour":"purple", "args":{"zorder":2}}
|
||||
]
|
||||
})
|
||||
"""
|
||||
|
||||
doKeyCopy = True
|
||||
plotDim = (1,)
|
||||
if "subPlots" in graphData:
|
||||
if "plotDim" in graphData: plotDim = graphData["plotDim"]
|
||||
else: plotDim = (1,len(graphData["subPlots"]))
|
||||
else:
|
||||
graphData["subPlots"] = [graphData]
|
||||
doKeyCopy = False
|
||||
|
||||
figSize = graphData["figSize"] if "figSize" in graphData else None
|
||||
tightLayout = graphData["tightLayout"] if "tightLayout" in graphData else True
|
||||
|
||||
fig, axes = plt.subplots(*plotDim, figsize=figSize) # Create a figure and an axes.
|
||||
|
||||
#if len(graphData["subPlots"]) <= 1:
|
||||
# axes = [axes]
|
||||
#Makes everything nice and linear
|
||||
# IE ((1,2), (3,4)) = (1,2,3,4)
|
||||
flatAxes = np.array(axes).flatten().tolist()
|
||||
|
||||
loopKeys = [
|
||||
"xLabel", "yLabel", "title", "axis", "grid", "xPos", "yPos",
|
||||
"xLabelPos", "yLabelPos", "xTickPos", "yTickPos", "xScale", "yScale",
|
||||
"xTickMap", "yTickMap", "plots", "xLim", "yLim", "ledgLoc", "y2Label",
|
||||
"ticklabel", "xInvert", "yInvert"
|
||||
]
|
||||
|
||||
#Feel like this could be optimized
|
||||
if doKeyCopy:
|
||||
for key in loopKeys:
|
||||
if key not in graphData: continue
|
||||
if key in graphData:
|
||||
for axGraphData in graphData["subPlots"]:
|
||||
if key not in axGraphData:
|
||||
axGraphData[key] = graphData[key]
|
||||
|
||||
|
||||
for i, axGraphData in enumerate(graphData["subPlots"]):
|
||||
ax1 = flatAxes[i]
|
||||
if "skip" in axGraphData and axGraphData["skip"]:
|
||||
ax1.set_axis_off()
|
||||
continue
|
||||
|
||||
if bool(sum([("y2" in pData) for pData in axGraphData["plots"]])):
|
||||
ax2 = ax1.twinx()
|
||||
else:
|
||||
ax2 = None
|
||||
|
||||
additional_legends = []
|
||||
|
||||
if bool(sum([("z" in pData) for pData in axGraphData["plots"]])):
|
||||
orig_ax = ax1
|
||||
ax1 = fig.add_subplot(orig_ax.get_position(), projection='3d')
|
||||
orig_ax.remove()
|
||||
if "tightLayout" not in graphData: tightLayout = False
|
||||
|
||||
# Duct Tape
|
||||
ax = ax1
|
||||
|
||||
#Draw many plots as needed
|
||||
# Also provide functions for drawing other types of lines
|
||||
if "plots" in axGraphData:
|
||||
for pData in axGraphData["plots"]:
|
||||
getSafeValue = lambda key, result=None: pData[key] if key in pData else result #Only return the key-value if present in pData
|
||||
getSafeValue2 = lambda key, key2, result=None: pData[key][key2] if key in pData and key2 in pData[key] else result
|
||||
getSafeColour = getSafeValue("colour") or getSafeValue("color") #Frigen American Spelling
|
||||
optArgs = getSafeValue("args", {}) #Allow for other args to be passed in
|
||||
|
||||
currentLine = None
|
||||
|
||||
if "skip" in pData and pData["skip"]: continue
|
||||
|
||||
if "x" in pData:
|
||||
xData = pData["x"]
|
||||
|
||||
if "y" in pData:
|
||||
yData = pData["y"]
|
||||
elif "y2" in pData:
|
||||
yData = pData["y2"]
|
||||
ax = ax2
|
||||
|
||||
zData = pData["z"] if "z" in pData else None
|
||||
|
||||
if "type" not in pData or pData["type"] == "plot":
|
||||
if zData is not None:
|
||||
currentLine = ax.plot(xData, yData, zData, label=getSafeValue("label"), color=getSafeColour, **optArgs)
|
||||
else:
|
||||
currentLine = ax.plot(xData, yData, label=getSafeValue("label"), color=getSafeColour, **optArgs)
|
||||
currentLine = currentLine[0]
|
||||
currentLineColour = currentLine.get_color()
|
||||
|
||||
if "maxPoint" in pData:
|
||||
labelText = pData["maxPoint"] if type(pData["maxPoint"]) == str else "Maximum Point ({y:.2f})"
|
||||
x, y = np.array(xData), np.array(yData)
|
||||
maxPoint = x[np.argmax(y)], np.max(y)
|
||||
ax.scatter(maxPoint[0], maxPoint[1],
|
||||
marker=getSafeValue("maxMarker", "o"), label=labelText.format(*maxPoint, x=maxPoint[0], y=maxPoint[1]),
|
||||
color=currentLineColour, zorder=getSafeValue("maxZorder", 2))
|
||||
|
||||
if "minPoint" in pData:
|
||||
labelText = pData["minPoint"] if type(pData["minPoint"]) == str else "Minimum Point ({1:.2f})"
|
||||
x, y = np.array(xData), np.array(yData)
|
||||
minPoint = x[np.argmin(y)], np.min(y)
|
||||
ax.scatter(minPoint[0], minPoint[1],
|
||||
marker=getSafeValue("minMarker", "*" if "maxPoint" in pData else "o"),
|
||||
label=labelText.format(*minPoint, x=minPoint[0], y=minPoint[1]), color=currentLineColour,
|
||||
zorder=getSafeValue("minxZorder", 2))
|
||||
|
||||
|
||||
|
||||
elif pData["type"] == "point":
|
||||
ax.scatter(xData, yData,
|
||||
marker=getSafeValue("marker"), label=getSafeValue("label"),
|
||||
color=getSafeColour, zorder=getSafeValue("zorder", 2),
|
||||
**optArgs)
|
||||
elif pData["type"] == "hLine":
|
||||
currentLine = ax.hlines(yData, *xData, label=getSafeValue("label"), color=getSafeColour, **optArgs)
|
||||
elif pData["type"] == "vLine":
|
||||
currentLine = ax.vlines(xData, *yData, label=getSafeValue("label"), color=getSafeColour, **optArgs)
|
||||
elif pData["type"] == "axvLine":
|
||||
if "y" not in pData: yData = (0, 1) #Span the whole graph
|
||||
currentLine = ax.axvline(xData, *yData, label=getSafeValue("label"), color=getSafeColour, **optArgs)
|
||||
elif pData["type"] == "axhLine":
|
||||
if "x" not in pData: xData = (0, 1) #Span the whole graph
|
||||
currentLine = ax.axhline(yData, *xData, label=getSafeValue("label"), color=getSafeColour, **optArgs)
|
||||
elif pData["type"] == "scatter":
|
||||
ax.scatter(xData, yData, marker=getSafeValue("marker"), label=getSafeValue("label"), color=getSafeColour, **optArgs)
|
||||
|
||||
elif pData["type"] == "contour":
|
||||
cs = ax.contour(getSafeValue("x"), getSafeValue("y"), pData["z"], levels=getSafeValue("levels"), colors=getSafeColour, **optArgs)
|
||||
if "label" in pData: cs.collections[0].set_label(getSafeValue("label"))
|
||||
elif pData["type"] == "matshow":
|
||||
ms = ax.matshow(pData["matrix"], origin=getSafeValue("origin"), label=getSafeValue("label"), **optArgs)
|
||||
if "colourBar" in pData:
|
||||
colorbar(ms, extend=getSafeValue2("colourBar", "extend"))
|
||||
elif pData["type"] == "pColourMesh":
|
||||
mesh = []
|
||||
if "X" in pData or "Y" in pData:
|
||||
mesh = [xData, yData, pData["Z"]]
|
||||
if "x" in pData or "y" in pData:
|
||||
x = xData; y = yData
|
||||
|
||||
if type(x) in [int, float]: x = (0, x, None)
|
||||
if type(y) in [int, float]: y = (0, x, None)
|
||||
x = tuple(x); y = tuple(y)
|
||||
if len(x) < 3: x = (x[0], x[1], None)
|
||||
if len(y) < 3: y = (y[0], y[1], None)
|
||||
|
||||
x = np.arange(x[0], x[1], x[2])
|
||||
y = np.arange(y[0], y[1], y[2])
|
||||
X, Y = np.meshgrid(x, y)
|
||||
|
||||
mesh = [X, Y, pData["Z"]]
|
||||
else:
|
||||
mesh = [pData["Z"]]
|
||||
|
||||
cNorm = None
|
||||
if "norm" in pData:
|
||||
cNorm = colors.LogNorm(vmin=pData["norm"][0], vmax=pData["norm"][1])
|
||||
|
||||
pcMesh = ax.pcolormesh(*mesh, norm=cNorm, shading=getSafeValue("shading"), label=getSafeValue("label"), **optArgs)
|
||||
#pcMesh = ax.imshow(pData["Z"], norm=cNorm, origin="lower")
|
||||
|
||||
if "colourBar" in pData:
|
||||
cBarOptArgs = pData["colourBar"]["optArgs"] if "optArgs" in pData["colourBar"] else {}
|
||||
fig.colorbar(pcMesh, ax=ax, extend=getSafeValue2("colourBar", "extend"), **cBarOptArgs)
|
||||
elif pData["type"] == "imshow":
|
||||
cNorm = None
|
||||
if "norm" in pData:
|
||||
cNorm = colors.LogNorm(vmin=pData["norm"][0], vmax=pData["norm"][1])
|
||||
|
||||
ims = ax.imshow(pData["data"], norm=cNorm, origin=getSafeValue("origin"), label=getSafeValue("label"), **optArgs)
|
||||
|
||||
if "colourBar" in pData:
|
||||
cBarOptArgs = pData["colourBar"]["optArgs"] if "optArgs" in pData["colourBar"] else {}
|
||||
colorbar(ims, extend=getSafeValue2("colourBar", "extend"), **cBarOptArgs)
|
||||
elif pData["type"] == "text":
|
||||
if not "props" in pData:
|
||||
props = {
|
||||
"boxstyle" : getSafeValue("boxstyle", "round"),
|
||||
"facecolor": getSafeValue("facecolor", getSafeValue("facecolour", "wheat")),
|
||||
"alpha" : getSafeValue("alpha", 0.5)
|
||||
}
|
||||
|
||||
align = (
|
||||
getSafeValue("valign", None),
|
||||
getSafeValue("halign", None),
|
||||
)
|
||||
align = getSafeValue("align", align)
|
||||
|
||||
ax.text(getSafeValue("x", 0.05), getSafeValue("y", 0.95), pData["text"], transform=ax.transAxes, fontsize=getSafeValue("fontsize", None), va=align[0], ha=align[1], bbox=props)
|
||||
elif pData["type"] == "addLabel":
|
||||
additional_legends.append(pData["line"])
|
||||
|
||||
if "fillAlpha" in pData or ("fill" in pData and ("type" not in pData or pData["type"] in ["plot"])):
|
||||
currentLine = ax.get_lines()[-1] if currentLine is None else currentLine
|
||||
|
||||
fillData = pData["fill"] if type(pData["fill"]) == dict and "fill" in pData else {}
|
||||
|
||||
if "color" not in fillData:
|
||||
line_colour = colors.to_rgba(currentLine.get_color())
|
||||
|
||||
if "fillAlpha" not in fillData: fillData["fillAlpha"] = pData["fillAlpha"] if "fillAlpha" in pData else 0.07
|
||||
line_colour = (line_colour[0], line_colour[1], line_colour[2], fillData["fillAlpha"])
|
||||
|
||||
fillData["color"] = line_colour
|
||||
|
||||
if "y2" not in fillData: fillData["y2"] = 0
|
||||
if "where" not in fillData: fillData["where"] = None
|
||||
if "interpolate" not in fillData: fillData["interpolate"] = False
|
||||
if "step" not in fillData: fillData["step"] = None
|
||||
if "data" not in fillData: fillData["data"] = None
|
||||
if "args" not in fillData: fillData["args"] = {}
|
||||
|
||||
ax.fill_between(xData, yData, fillData["y2"], color=fillData["color"],
|
||||
where=fillData["where"], interpolate=fillData["interpolate"],
|
||||
step=fillData["step"], data=fillData["data"], **fillData["args"]
|
||||
)
|
||||
|
||||
#Set extra options as needed
|
||||
ax = ax1
|
||||
if "xLabel" in axGraphData: ax.set_xlabel(axGraphData["xLabel"]) # Add an x-label to the axes.
|
||||
if "yLabel" in axGraphData: ax.set_ylabel(axGraphData["yLabel"]) # Add an y-label to the axes.
|
||||
if "zLabel" in axGraphData: ax.set_zlabel(axGraphData["zLabel"]) # Add a y2-label to the axes.
|
||||
if "y2Label" in axGraphData: ax2.set_ylabel(axGraphData["y2Label"]) # Add a y2-label to the axes.
|
||||
if "title" in axGraphData: ax.set_title(axGraphData["title"]) # Add an title to the axes.
|
||||
if "axis" in axGraphData: ax.axis(axGraphData["axis"]) # Set the axis type
|
||||
if "grid" in axGraphData: ax.grid(axGraphData["grid"]) # Add grids to the graph
|
||||
if "xPos" in axGraphData: # Add the abilty to move the x axis label and ticks
|
||||
ax.xaxis.set_label_position(axGraphData["xPos"])
|
||||
ax.xaxis.set_ticks_position(axGraphData["xPos"])
|
||||
if "yPos" in axGraphData: # Add the abilty to move the y axis label and ticks
|
||||
ax.yaxis.set_label_position(axGraphData["yPos"])
|
||||
ax.yaxis.set_ticks_position(axGraphData["yPos"])
|
||||
if "xLabelPos" in axGraphData: ax.xaxis.set_label_position(axGraphData["xLabelPos"]) # Add the ability to move the x axis label
|
||||
if "yLabelPos" in axGraphData: ax.yaxis.set_label_position(axGraphData["yLabelPos"]) # Add the ability to move the y axis label
|
||||
if "xTickPos" in axGraphData: ax.xaxis.set_ticks_position(axGraphData["xTickPos"]) # Add the ability to move the x axis ticks
|
||||
if "yTickPos" in axGraphData: ax.yaxis.set_ticks_position(axGraphData["yTickPos"]) # Add the ability to move the y axis ticks
|
||||
if "xScale" in axGraphData: ax.set_xscale(axGraphData["xScale"]) #Add x axis scaling if needed
|
||||
if "yScale" in axGraphData: ax.set_yscale(axGraphData["yScale"]) #Add y axis scaling if needed
|
||||
if "xInvert" in axGraphData and axGraphData["xInvert"]: ax.invert_xaxis()
|
||||
if "yInvert" in axGraphData and axGraphData["yInvert"]: ax.invert_yaxis()
|
||||
if "xLim" in axGraphData:
|
||||
xLimit = ()
|
||||
if type(axGraphData["xLim"]) in [int, float]:
|
||||
xLimit = (0, axGraphData["xLim"])
|
||||
else:
|
||||
xLimit = axGraphData["xLim"]
|
||||
ax.set_xlim(xLimit)
|
||||
if "yLim" in axGraphData:
|
||||
yLimit = ()
|
||||
if type(axGraphData["yLim"]) in [int, float]:
|
||||
yLimit = (0, axGraphData["yLim"])
|
||||
else:
|
||||
yLimit = axGraphData["yLim"]
|
||||
ax.set_ylim(yLimit)
|
||||
if "xTickMap" in axGraphData: #Allow for the mapping / transformation of the xAxis Ticks
|
||||
formatter = axGraphData["xTickMap"]
|
||||
xTicks = matplotlib.ticker.FuncFormatter(formatter)
|
||||
ax.xaxis.set_major_formatter(xTicks)
|
||||
if "yTickMap" in axGraphData: #Allow for the mapping / transformation of the yAxis Ticks
|
||||
formatter = axGraphData["yTickMap"]
|
||||
yTicks = matplotlib.ticker.FuncFormatter(formatter)
|
||||
ax.yaxis.set_major_formatter(yTicks)
|
||||
|
||||
if "plots" in axGraphData and bool(sum([("label" in pData) for pData in axGraphData["plots"]])) and not ("noLedg" in axGraphData and axGraphData["noLedg"]) :
|
||||
locPoint = axGraphData["ledgLoc"] if "ledgLoc" in axGraphData else None
|
||||
add_lines, add_labels = additional_legends, [line.get_label() for line in additional_legends]
|
||||
lines1, labels1 = ax1.get_legend_handles_labels()
|
||||
|
||||
if ax2:
|
||||
lines2, labels2 = ax2.get_legend_handles_labels()
|
||||
ax2.legend(lines1 + lines2 + add_lines, labels1 + labels2 + add_labels, loc=locPoint)
|
||||
else:
|
||||
ax1.legend(lines1 + add_lines, labels1 + add_labels, loc=locPoint)
|
||||
|
||||
if "ticklabel" in axGraphData:
|
||||
style = axGraphData["ticklabel"]["style"] if "style" in axGraphData["ticklabel"] else ""
|
||||
axis = axGraphData["ticklabel"]["axis"] if "axis" in axGraphData["ticklabel"] else "both"
|
||||
limits = axGraphData["ticklabel"]["limits"] if "limits" in axGraphData["ticklabel"] else None
|
||||
optArgs = axGraphData["ticklabel"]["optArgs"] if "optArgs" in axGraphData["ticklabel"] else {}
|
||||
ax.ticklabel_format(axis=axis, style=style, scilimits=limits, **optArgs)
|
||||
|
||||
#Should work?
|
||||
if hideEmptyAxis:
|
||||
if not ax.collections and not ax.lines:
|
||||
ax.set_axis_off()
|
||||
|
||||
|
||||
if "title" in graphData or "winTitle" in graphData:
|
||||
title = graphData["winTitle"] if "winTitle" in graphData else graphData["title"]
|
||||
fig.canvas.manager.set_window_title(title.replace("\n", " ")) #Set the figure title correctly
|
||||
if "figTitle" in graphData:
|
||||
getSafeValue = lambda key: graphData[key] if key in graphData else None #Only return the key-value if present in graphData
|
||||
|
||||
fig.suptitle(graphData["figTitle"], fontsize=getSafeValue("figTitleFontSize"))
|
||||
if "title" not in graphData: fig.canvas.manager.set_window_title(graphData["figTitle"].replace("\n", " "))
|
||||
|
||||
if tightLayout:
|
||||
fig.tight_layout() #Fix labels being cut off sometimes
|
||||
|
||||
#Very big hack
|
||||
if hideEmptyAxis:
|
||||
flatAxes[-1].set_axis_off()
|
||||
|
||||
if figSavePath:
|
||||
fig.savefig(figSavePath)
|
||||
|
||||
if showPlot:
|
||||
plt.show(block=doProgramBlock) #Show the plot and also block the program - doing things OO style allow for more flexible programs
|
||||
|
||||
|
||||
return fig, axes
|
||||
|
||||
# [TODO] Make this Async so the closure of all graphs exits
|
||||
def pltKeyClose():
|
||||
'''Show all plots and wait for user input to close them all.'''
|
||||
plt.show(block=False)
|
||||
input('Press any key to close all graphs...')
|
||||
plt.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
#This is an example of drawing 4 plots by generating them
|
||||
graphData = {
|
||||
"figTitle": "Simple Plot",
|
||||
"figTitleFontSize": 16,
|
||||
"figSize": (8,8), #Yay America, this is in inches :/ # Note: cm = 1/2.54
|
||||
"xLabel": "x label",
|
||||
"yLabel": "y label",
|
||||
"plotDim": (2,2),
|
||||
"subPlots":[]
|
||||
}
|
||||
|
||||
#Create 4 identical plots with different names
|
||||
for i in range(4):
|
||||
newPlot = {
|
||||
"title": f"Graph {i+1}",
|
||||
"noLedg": i % 2 == 0,
|
||||
"plots": [
|
||||
{"x":[0,1,2,3,4], "y":[0,1,2,3,4], "label":"Linear"},
|
||||
{"x":[0,1,2,3,4], "y":[5,5,5,5,5]},
|
||||
{"x":[4,3,2,1,0], "y":[4,3,2,1,0], "label":"Linear2"},
|
||||
{"x":0, "type":"axvLine", "label":"Red Vertical Line", "color":"uq:red"},
|
||||
{"y":6, "type":"axhLine", "label":"Dashed Horizontal Line", "args":{"linestyle":"--"}},
|
||||
{"type":"point", "x":4, "y":4, "label":"A Random Point", "colour":"uq:purple"},
|
||||
{
|
||||
"x":np.arange(0, 4, 0.1), "y": 3*np.sin(np.arange(0, 4, 0.1)),
|
||||
"colour":"uq:dark_grey", "maxPoint": True,
|
||||
"minPoint": "Custom Min {x:.1f}, {y:.1f}",
|
||||
"fill": {"color": UQ_COLOURS["green"].alpha_adj(0.07)}
|
||||
}
|
||||
]
|
||||
}
|
||||
graphData["subPlots"].append(newPlot)
|
||||
|
||||
makeGraph(graphData)
|
BIN
requirements.txt
Normal file
BIN
requirements.txt
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user