icplot 0.0.7__tar.gz → 0.0.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {icplot-0.0.7/src/icplot.egg-info → icplot-0.0.9}/PKG-INFO +2 -2
- {icplot-0.0.7 → icplot-0.0.9}/pyproject.toml +2 -2
- icplot-0.0.9/src/icplot/color.py +54 -0
- {icplot-0.0.7 → icplot-0.0.9}/src/icplot/geometry.py +1 -7
- icplot-0.0.9/src/icplot/graph/__init__.py +2 -0
- icplot-0.0.9/src/icplot/graph/axis.py +44 -0
- icplot-0.0.9/src/icplot/graph/matplotlib.py +101 -0
- icplot-0.0.9/src/icplot/graph/plot.py +92 -0
- icplot-0.0.9/src/icplot/graph/plotter.py +22 -0
- icplot-0.0.9/src/icplot/graph/series.py +111 -0
- {icplot-0.0.7 → icplot-0.0.9/src/icplot.egg-info}/PKG-INFO +2 -2
- {icplot-0.0.7 → icplot-0.0.9}/src/icplot.egg-info/SOURCES.txt +5 -0
- {icplot-0.0.7 → icplot-0.0.9}/src/icplot.egg-info/requires.txt +1 -1
- {icplot-0.0.7 → icplot-0.0.9}/test/test_cairo_interface.py +1 -0
- {icplot-0.0.7 → icplot-0.0.9}/test/test_gantt_chart.py +2 -3
- {icplot-0.0.7 → icplot-0.0.9}/test/test_image_utils.py +3 -3
- icplot-0.0.9/test/test_line_plots.py +29 -0
- {icplot-0.0.7 → icplot-0.0.9}/test/test_tex_interface.py +1 -0
- icplot-0.0.7/src/icplot/graph/__init__.py +0 -1
- icplot-0.0.7/src/icplot/graph/plot.py +0 -200
- icplot-0.0.7/test/test_line_plots.py +0 -26
- {icplot-0.0.7 → icplot-0.0.9}/LICENSE +0 -0
- {icplot-0.0.7 → icplot-0.0.9}/README.md +0 -0
- {icplot-0.0.7 → icplot-0.0.9}/setup.cfg +0 -0
- {icplot-0.0.7 → icplot-0.0.9}/src/icplot/__init__.py +0 -0
- {icplot-0.0.7 → icplot-0.0.9}/src/icplot/cairo_interface.py +0 -0
- {icplot-0.0.7 → icplot-0.0.9}/src/icplot/gantt.py +0 -0
- {icplot-0.0.7 → icplot-0.0.9}/src/icplot/image_utils.py +0 -0
- {icplot-0.0.7 → icplot-0.0.9}/src/icplot/main_cli.py +0 -0
- {icplot-0.0.7 → icplot-0.0.9}/src/icplot/project_elements.py +0 -0
- {icplot-0.0.7 → icplot-0.0.9}/src/icplot/tex_interface.py +0 -0
- {icplot-0.0.7 → icplot-0.0.9}/src/icplot.egg-info/dependency_links.txt +0 -0
- {icplot-0.0.7 → icplot-0.0.9}/src/icplot.egg-info/entry_points.txt +0 -0
- {icplot-0.0.7 → icplot-0.0.9}/src/icplot.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: icplot
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.9
|
|
4
4
|
Summary: Utilities for generating plots and graphics for technical reports.
|
|
5
5
|
Author-email: "James Grogan, Irish Centre for High End Computing" <james.grogan@ichec.ie>
|
|
6
6
|
Project-URL: Repository, https://git.ichec.ie/performance/toolshed/icplot
|
|
@@ -16,7 +16,7 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: wand
|
|
18
18
|
Requires-Dist: matplotlib
|
|
19
|
-
Requires-Dist: iccore==0.0.
|
|
19
|
+
Requires-Dist: iccore==0.0.10
|
|
20
20
|
Provides-Extra: test
|
|
21
21
|
Requires-Dist: pytest; extra == "test"
|
|
22
22
|
Requires-Dist: pytest-cov; extra == "test"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "icplot"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.9"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="James Grogan, Irish Centre for High End Computing", email="james.grogan@ichec.ie" },
|
|
6
6
|
]
|
|
@@ -18,7 +18,7 @@ keywords = ["Publishing", "Technical Reports", "Graphics"]
|
|
|
18
18
|
|
|
19
19
|
dependencies = ["wand",
|
|
20
20
|
"matplotlib",
|
|
21
|
-
"iccore==0.0.
|
|
21
|
+
"iccore==0.0.10"]
|
|
22
22
|
|
|
23
23
|
[project.urls]
|
|
24
24
|
Repository = "https://git.ichec.ie/performance/toolshed/icplot"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module handles colors
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Callable
|
|
6
|
+
|
|
7
|
+
from iccore.serialization import Serializable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Color(Serializable):
|
|
11
|
+
"""
|
|
12
|
+
A color class, internal storage is rgba as float.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, r: float = 0.0, g: float = 0.0, b: float = 0.0, a: float = 1.0):
|
|
16
|
+
self.r = r
|
|
17
|
+
self.g = g
|
|
18
|
+
self.b = b
|
|
19
|
+
self.a = a
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def from_list(data: list):
|
|
23
|
+
return Color(data[0], data[1], data[2])
|
|
24
|
+
|
|
25
|
+
def serialize(self):
|
|
26
|
+
return {"r": self.r, "g": self.g, "b": self.b, "a": self.a}
|
|
27
|
+
|
|
28
|
+
def as_list(self) -> list:
|
|
29
|
+
return [self.r, self.g, self.b]
|
|
30
|
+
|
|
31
|
+
def is_black(self) -> bool:
|
|
32
|
+
return self.r == 0.0 and self.g == 0.0 and self.b == 0.0 and self.a == 1.0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ColorMap:
|
|
36
|
+
"""
|
|
37
|
+
A mapping from a flat index to a color
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, label: str, data_func: Callable):
|
|
41
|
+
self.label = label
|
|
42
|
+
self.data_func = data_func
|
|
43
|
+
self.start_offset = 0.25
|
|
44
|
+
self.scale = 2
|
|
45
|
+
|
|
46
|
+
def get_color(self, cursor: int, values: list) -> Color:
|
|
47
|
+
"""
|
|
48
|
+
Returns a colour based on a cmap and how far across
|
|
49
|
+
the datasets you are
|
|
50
|
+
"""
|
|
51
|
+
position = cursor / len(values)
|
|
52
|
+
return Color.from_list(
|
|
53
|
+
self.data_func(self.start_offset + (position / self.scale))
|
|
54
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module has a plot's axis
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from iccore.serialization import Serializable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Range(Serializable):
|
|
9
|
+
"""
|
|
10
|
+
This is a sequence of numbers specified by lower and upper
|
|
11
|
+
bounds and a step size. Eg (1, 5, 1) gives: [1, 2, 3, 4]
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, lower, upper, step):
|
|
15
|
+
self.lower = lower
|
|
16
|
+
self.upper = upper
|
|
17
|
+
self.step = step
|
|
18
|
+
|
|
19
|
+
def eval(self) -> list:
|
|
20
|
+
return list(range(self.lower, self.upper, self.step))
|
|
21
|
+
|
|
22
|
+
def serialize(self):
|
|
23
|
+
return {"lower": self.lower, "upper": self.upper, "step": self.step}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PlotAxis(Serializable):
|
|
27
|
+
"""
|
|
28
|
+
This is a plot's axis
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, label: str = ""):
|
|
32
|
+
self.label = label
|
|
33
|
+
self.ticks: Range | None = None
|
|
34
|
+
|
|
35
|
+
def set_ticks(self, lower: int, upper: int, step: int):
|
|
36
|
+
self.ticks = Range(lower, upper + 1, step)
|
|
37
|
+
|
|
38
|
+
def serialize(self):
|
|
39
|
+
return {"label": self.label, "ticks": self.ticks.serialize()}
|
|
40
|
+
|
|
41
|
+
def get_resolved_ticks(self) -> list:
|
|
42
|
+
if self.ticks:
|
|
43
|
+
return self.ticks.eval()
|
|
44
|
+
return []
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import logging
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
logging.getLogger("matplotlib").setLevel(logging.WARNING)
|
|
6
|
+
import matplotlib as mpl # NOQA
|
|
7
|
+
|
|
8
|
+
default_backend = mpl.get_backend()
|
|
9
|
+
mpl.use("Agg")
|
|
10
|
+
import matplotlib.pyplot as plt # NOQA
|
|
11
|
+
|
|
12
|
+
from icplot.color import ColorMap # NOQA
|
|
13
|
+
from .series import PlotSeries, LinePlotSeries, ScatterPlotSeries, ImageSeries # NOQA
|
|
14
|
+
from .plot import Plot, GridPlot # NOQA
|
|
15
|
+
from .plotter import Plotter # NOQA
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MatplotlibColorMap(ColorMap):
|
|
19
|
+
"""
|
|
20
|
+
A matplotlib based colormap
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, label: str):
|
|
24
|
+
super().__init__(label, mpl.colormaps[label])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MatplotlibPlotter(Plotter):
|
|
28
|
+
"""
|
|
29
|
+
A plot backend for matplotlib
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, cmap_label="viridis"):
|
|
33
|
+
super().__init__(MatplotlibColorMap(cmap_label))
|
|
34
|
+
|
|
35
|
+
def set_decorations(self, ax, plot: Plot):
|
|
36
|
+
ax.legend(loc="upper left")
|
|
37
|
+
if plot.x_axis.label:
|
|
38
|
+
ax.set_xlabel(plot.x_axis.label)
|
|
39
|
+
if plot.y_axis.label:
|
|
40
|
+
ax.set_ylabel(plot.y_axis.label)
|
|
41
|
+
if plot.x_axis.ticks:
|
|
42
|
+
ax.set_xticks(plot.x_axis.get_resolved_ticks())
|
|
43
|
+
if plot.y_axis.ticks:
|
|
44
|
+
ax.set_yticks(plot.y_axis.get_resolved_ticks())
|
|
45
|
+
if plot.title:
|
|
46
|
+
ax.set_title(plot.title)
|
|
47
|
+
|
|
48
|
+
def plot_line(self, ax, series: LinePlotSeries):
|
|
49
|
+
ax.plot(
|
|
50
|
+
series.x,
|
|
51
|
+
series.y,
|
|
52
|
+
label=series.label,
|
|
53
|
+
color=series.get_color().as_list(),
|
|
54
|
+
marker=series.marker,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def plot_scatter(self, ax, series: ScatterPlotSeries):
|
|
58
|
+
ax.scatter(series.data, label=series.label, color=series.get_color().as_list())
|
|
59
|
+
|
|
60
|
+
def plot_image(self, ax, series: ImageSeries):
|
|
61
|
+
ax.imshow(series.data)
|
|
62
|
+
ax.axis("off")
|
|
63
|
+
|
|
64
|
+
def plot_series(self, ax, series: PlotSeries):
|
|
65
|
+
if series.series_type == "line":
|
|
66
|
+
self.plot_line(ax, cast(LinePlotSeries, series))
|
|
67
|
+
elif series.series_type == "scatter":
|
|
68
|
+
self.plot_scatter(ax, cast(ScatterPlotSeries, series))
|
|
69
|
+
elif series.series_type == "image":
|
|
70
|
+
self.plot_image(ax, cast(ImageSeries, series))
|
|
71
|
+
|
|
72
|
+
def plot(self, plot: Plot, path: Path | None = None):
|
|
73
|
+
|
|
74
|
+
self.apply_cmap_colors(plot)
|
|
75
|
+
|
|
76
|
+
fig, ax = plt.subplots()
|
|
77
|
+
|
|
78
|
+
self.set_decorations(ax, plot)
|
|
79
|
+
|
|
80
|
+
for series in plot.series:
|
|
81
|
+
self.plot_series(ax, series)
|
|
82
|
+
|
|
83
|
+
self.render(fig, path)
|
|
84
|
+
|
|
85
|
+
def plot_grid(self, plot: GridPlot, path: Path | None = None, num_samples: int = 0):
|
|
86
|
+
|
|
87
|
+
rows, cols, series = plot.get_subplots(num_samples)
|
|
88
|
+
fig, axs = plt.subplots(rows, cols)
|
|
89
|
+
|
|
90
|
+
for ax, series_item in zip(axs, series):
|
|
91
|
+
self.plot_series(ax, series_item)
|
|
92
|
+
|
|
93
|
+
self.render(fig, path)
|
|
94
|
+
|
|
95
|
+
def render(self, fig, path: Path | None = None):
|
|
96
|
+
if path:
|
|
97
|
+
fig.savefig(path)
|
|
98
|
+
else:
|
|
99
|
+
plt.switch_backend(default_backend)
|
|
100
|
+
fig.show()
|
|
101
|
+
plt.switch_backend("Agg")
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module has content for generating plots
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
|
|
7
|
+
from iccore.serialization import Serializable
|
|
8
|
+
|
|
9
|
+
from .axis import PlotAxis
|
|
10
|
+
from .series import PlotSeries
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Plot(Serializable):
|
|
14
|
+
"""
|
|
15
|
+
A generic plot with optional axis ticks
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
title: str = "",
|
|
21
|
+
x_label: str = "",
|
|
22
|
+
y_label: str = "",
|
|
23
|
+
legend_label: str = "",
|
|
24
|
+
) -> None:
|
|
25
|
+
self.series: list[PlotSeries] = []
|
|
26
|
+
self.title = title
|
|
27
|
+
self.size: tuple | None = None
|
|
28
|
+
self.plot_type = ""
|
|
29
|
+
self.x_axis = PlotAxis(x_label)
|
|
30
|
+
self.y_axis = PlotAxis(y_label)
|
|
31
|
+
self.legend_label = legend_label
|
|
32
|
+
|
|
33
|
+
def set_x_ticks(self, lower, upper, step):
|
|
34
|
+
self.x_axis.set_ticks(lower, upper, step)
|
|
35
|
+
|
|
36
|
+
def set_y_ticks(self, lower, upper, step):
|
|
37
|
+
self.y_axis.set_ticks(lower, upper, step)
|
|
38
|
+
|
|
39
|
+
def add_series(self, series):
|
|
40
|
+
self.series.append(series)
|
|
41
|
+
|
|
42
|
+
def serialize(self):
|
|
43
|
+
return {
|
|
44
|
+
"series": [s.serialize() for s in self.series],
|
|
45
|
+
"title": self.title,
|
|
46
|
+
"type": self.plot_type,
|
|
47
|
+
"x_axis": self.x_axis.serialize(),
|
|
48
|
+
"y_axis": self.y_axis.serialize(),
|
|
49
|
+
"legend_label": self.legend_label,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class GridPlot(Plot):
|
|
54
|
+
"""
|
|
55
|
+
Make a grid of plots
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self, data, title: str = "", stride: int = 4, size: tuple = (25, 20)
|
|
60
|
+
) -> None:
|
|
61
|
+
super().__init__(title)
|
|
62
|
+
self.data = data
|
|
63
|
+
self.stride = stride
|
|
64
|
+
self.size = size
|
|
65
|
+
|
|
66
|
+
def get_series_indices(self, num_samples: int = 0):
|
|
67
|
+
rows = num_samples // self.stride
|
|
68
|
+
cols = num_samples // rows
|
|
69
|
+
len_data = len(self.data)
|
|
70
|
+
|
|
71
|
+
if num_samples == 0:
|
|
72
|
+
indices = list(range(0, len_data))
|
|
73
|
+
else:
|
|
74
|
+
indices = [random.randint(0, len_data - 1) for _ in range(num_samples)]
|
|
75
|
+
return rows, cols, indices
|
|
76
|
+
|
|
77
|
+
def get_subplots(self, num_samples: int = 0):
|
|
78
|
+
rows, cols, indices = self.get_series_indices(num_samples)
|
|
79
|
+
|
|
80
|
+
subplots = []
|
|
81
|
+
count = 1
|
|
82
|
+
for index in indices:
|
|
83
|
+
if num_samples > 0 and count == num_samples + 1:
|
|
84
|
+
break
|
|
85
|
+
if isinstance(self.data[index], list):
|
|
86
|
+
for series in self.data[index]:
|
|
87
|
+
subplots.append(series)
|
|
88
|
+
count += 1
|
|
89
|
+
else:
|
|
90
|
+
subplots.append(self.data[index])
|
|
91
|
+
count += 1
|
|
92
|
+
return rows, cols, subplots
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from icplot.color import ColorMap
|
|
4
|
+
|
|
5
|
+
from .plot import Plot
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Plotter:
|
|
9
|
+
|
|
10
|
+
def __init__(self, cmap: ColorMap):
|
|
11
|
+
self.cmap = cmap
|
|
12
|
+
|
|
13
|
+
def apply_cmap_colors(self, plot: Plot):
|
|
14
|
+
skipped_color = 0
|
|
15
|
+
for idx, series in enumerate(plot.series):
|
|
16
|
+
if not series.color:
|
|
17
|
+
series.color = self.cmap.get_color(idx - skipped_color, plot.series)
|
|
18
|
+
else:
|
|
19
|
+
skipped_color += 1
|
|
20
|
+
|
|
21
|
+
def plot(self, plot: Plot, path: Path | None = None):
|
|
22
|
+
pass
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from iccore.serialization import Serializable
|
|
2
|
+
|
|
3
|
+
from icplot.color import Color
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PlotSeries(Serializable):
|
|
7
|
+
"""
|
|
8
|
+
A data series in a plot, such as a single line in a line-plot
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, label: str = "", color: Color | None = None) -> None:
|
|
12
|
+
self.label = label
|
|
13
|
+
self.color = color
|
|
14
|
+
self.series_type = ""
|
|
15
|
+
|
|
16
|
+
def get_color(self) -> Color:
|
|
17
|
+
if self.color:
|
|
18
|
+
return self.color
|
|
19
|
+
return Color()
|
|
20
|
+
|
|
21
|
+
def serialize(self):
|
|
22
|
+
|
|
23
|
+
color_val = {}
|
|
24
|
+
if self.color:
|
|
25
|
+
color_val = self.color.serialize()
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
"label": self.label,
|
|
29
|
+
"color": color_val,
|
|
30
|
+
"type": self.series_type,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ImageSeries(PlotSeries):
|
|
35
|
+
"""
|
|
36
|
+
A plot data series where the elements are images
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
data,
|
|
42
|
+
label: str = "",
|
|
43
|
+
transform=None,
|
|
44
|
+
):
|
|
45
|
+
super().__init__(label)
|
|
46
|
+
self.data = data
|
|
47
|
+
self.series_type = "image"
|
|
48
|
+
self.transform = transform
|
|
49
|
+
|
|
50
|
+
def serialize(self):
|
|
51
|
+
ret = super().serialize()
|
|
52
|
+
ret["data"] = self.data
|
|
53
|
+
return ret
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class LinePlotSeries(PlotSeries):
|
|
57
|
+
"""
|
|
58
|
+
A plot series for line plots
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
x: list | None = None,
|
|
64
|
+
y: list | None = None,
|
|
65
|
+
label: str = "",
|
|
66
|
+
color: Color | None = None,
|
|
67
|
+
marker: str = "o",
|
|
68
|
+
) -> None:
|
|
69
|
+
super().__init__(label, color)
|
|
70
|
+
self.marker = marker
|
|
71
|
+
self.series_type = "line"
|
|
72
|
+
if x:
|
|
73
|
+
self.x = x
|
|
74
|
+
else:
|
|
75
|
+
self.x = []
|
|
76
|
+
if y:
|
|
77
|
+
self.y = y
|
|
78
|
+
else:
|
|
79
|
+
self.y = []
|
|
80
|
+
|
|
81
|
+
def add_entry(self, x_point: float, y_point: float):
|
|
82
|
+
self.x.append(x_point)
|
|
83
|
+
self.y.append(y_point)
|
|
84
|
+
|
|
85
|
+
def sort(self):
|
|
86
|
+
"""
|
|
87
|
+
Sorts by x values
|
|
88
|
+
"""
|
|
89
|
+
tuples = list(zip(self.x, self.y))
|
|
90
|
+
tuples.sort(key=lambda tup: tup[0])
|
|
91
|
+
for idx, _ in enumerate(tuples):
|
|
92
|
+
self.x[idx] = tuples[idx][0]
|
|
93
|
+
self.y[idx] = tuples[idx][1]
|
|
94
|
+
|
|
95
|
+
def serialize(self):
|
|
96
|
+
ret = super().serialize()
|
|
97
|
+
ret["x"] = self.x
|
|
98
|
+
ret["y"] = self.y
|
|
99
|
+
return ret
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ScatterPlotSeries(PlotSeries):
|
|
103
|
+
def __init__(self, data, label: str = "", color: Color | None = None):
|
|
104
|
+
super().__init__(label, color)
|
|
105
|
+
self.data = data
|
|
106
|
+
self.series_type = "scatter"
|
|
107
|
+
|
|
108
|
+
def serialize(self):
|
|
109
|
+
ret = super().serialize()
|
|
110
|
+
ret["data"] = self.data
|
|
111
|
+
return ret
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: icplot
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.9
|
|
4
4
|
Summary: Utilities for generating plots and graphics for technical reports.
|
|
5
5
|
Author-email: "James Grogan, Irish Centre for High End Computing" <james.grogan@ichec.ie>
|
|
6
6
|
Project-URL: Repository, https://git.ichec.ie/performance/toolshed/icplot
|
|
@@ -16,7 +16,7 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
License-File: LICENSE
|
|
17
17
|
Requires-Dist: wand
|
|
18
18
|
Requires-Dist: matplotlib
|
|
19
|
-
Requires-Dist: iccore==0.0.
|
|
19
|
+
Requires-Dist: iccore==0.0.10
|
|
20
20
|
Provides-Extra: test
|
|
21
21
|
Requires-Dist: pytest; extra == "test"
|
|
22
22
|
Requires-Dist: pytest-cov; extra == "test"
|
|
@@ -4,6 +4,7 @@ pyproject.toml
|
|
|
4
4
|
setup.cfg
|
|
5
5
|
src/icplot/__init__.py
|
|
6
6
|
src/icplot/cairo_interface.py
|
|
7
|
+
src/icplot/color.py
|
|
7
8
|
src/icplot/gantt.py
|
|
8
9
|
src/icplot/geometry.py
|
|
9
10
|
src/icplot/image_utils.py
|
|
@@ -17,7 +18,11 @@ src/icplot.egg-info/entry_points.txt
|
|
|
17
18
|
src/icplot.egg-info/requires.txt
|
|
18
19
|
src/icplot.egg-info/top_level.txt
|
|
19
20
|
src/icplot/graph/__init__.py
|
|
21
|
+
src/icplot/graph/axis.py
|
|
22
|
+
src/icplot/graph/matplotlib.py
|
|
20
23
|
src/icplot/graph/plot.py
|
|
24
|
+
src/icplot/graph/plotter.py
|
|
25
|
+
src/icplot/graph/series.py
|
|
21
26
|
test/test_cairo_interface.py
|
|
22
27
|
test/test_gantt_chart.py
|
|
23
28
|
test/test_image_utils.py
|
|
@@ -20,10 +20,9 @@ def test_gantt_chart():
|
|
|
20
20
|
milestone1.description = "Description of Milestone 1."
|
|
21
21
|
milestone1.start_date = datetime.date(2024, 7, 1)
|
|
22
22
|
milestone1.due_date = datetime.date(2024, 7, 15)
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
gantt = GanttChart()
|
|
25
|
-
gantt.milestones = [milestone0,
|
|
26
|
-
milestone1]
|
|
25
|
+
gantt.milestones = [milestone0, milestone1]
|
|
27
26
|
|
|
28
27
|
output_path = Path(os.getcwd()) / "gantt.svg"
|
|
29
28
|
|
|
@@ -3,9 +3,11 @@ from pathlib import Path
|
|
|
3
3
|
|
|
4
4
|
from icplot.image_utils import svg_to_png, svg_to_pdf
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
def get_test_data_dir():
|
|
7
8
|
return Path(__file__).parent / "data"
|
|
8
|
-
|
|
9
|
+
|
|
10
|
+
|
|
9
11
|
def test_convert_svg():
|
|
10
12
|
|
|
11
13
|
source = get_test_data_dir() / "test.svg"
|
|
@@ -17,5 +19,3 @@ def test_convert_svg():
|
|
|
17
19
|
pdf_out = Path(os.getcwd()) / "out.pdf"
|
|
18
20
|
svg_to_pdf(source, pdf_out)
|
|
19
21
|
pdf_out.unlink()
|
|
20
|
-
|
|
21
|
-
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from icplot.graph import Plot, LinePlotSeries
|
|
4
|
+
from icplot.graph.matplotlib import MatplotlibPlotter
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_line_plot():
|
|
8
|
+
|
|
9
|
+
plot = Plot(
|
|
10
|
+
title="test",
|
|
11
|
+
x_label="test_x",
|
|
12
|
+
y_label="test_y",
|
|
13
|
+
legend_label="test_legend"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
data = [([0, 5, 10], [1, 2, 3]), ([0, 5, 10], [3, 6, 9]), ([0, 5, 10], [4, 8, 12])]
|
|
17
|
+
|
|
18
|
+
for idx, [x, y] in enumerate(data):
|
|
19
|
+
plot.add_series(LinePlotSeries(x, y, label=f"Series {idx}"))
|
|
20
|
+
|
|
21
|
+
plot.set_x_ticks(0, 10, 5)
|
|
22
|
+
|
|
23
|
+
output_path = Path() / "output.svg"
|
|
24
|
+
|
|
25
|
+
plotter = MatplotlibPlotter()
|
|
26
|
+
plotter.plot(plot, output_path)
|
|
27
|
+
|
|
28
|
+
assert output_path.exists()
|
|
29
|
+
output_path.unlink()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .plot import * # NOQA
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This module has content for generating plots
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import random
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
from typing import cast
|
|
8
|
-
|
|
9
|
-
import logging
|
|
10
|
-
|
|
11
|
-
logging.getLogger("matplotlib").setLevel(logging.WARNING)
|
|
12
|
-
import matplotlib # NOQA
|
|
13
|
-
|
|
14
|
-
matplotlib.use("Agg")
|
|
15
|
-
import matplotlib.pyplot as plt # NOQA
|
|
16
|
-
import matplotlib as mpl # NOQA
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class PlotSeries:
|
|
20
|
-
|
|
21
|
-
def __init__(self, label: str = ""):
|
|
22
|
-
self.label = label
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class LinePlotSeries(PlotSeries):
|
|
26
|
-
def __init__(self, x: list[float], y: list[float], label: str = "") -> None:
|
|
27
|
-
super().__init__(label)
|
|
28
|
-
self.x = x
|
|
29
|
-
self.y = y
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class MatplotlibPlotter:
|
|
33
|
-
|
|
34
|
-
def __init__(self):
|
|
35
|
-
self.ax = plt.subplot(111)
|
|
36
|
-
|
|
37
|
-
def set_decorations(self, x_label, y_label, title, x_ticks, y_ticks):
|
|
38
|
-
self.ax.legend(loc="upper left")
|
|
39
|
-
if x_label:
|
|
40
|
-
self.ax.set_xlabel(x_label)
|
|
41
|
-
if y_label:
|
|
42
|
-
self.ax.set_ylabel(y_label)
|
|
43
|
-
if title:
|
|
44
|
-
self.ax.set_title(title)
|
|
45
|
-
if x_ticks:
|
|
46
|
-
self.ax.set_xticks(x_ticks)
|
|
47
|
-
if y_ticks:
|
|
48
|
-
self.ax.set_yticks(y_ticks)
|
|
49
|
-
|
|
50
|
-
def plot_line(self, x, y, label, color):
|
|
51
|
-
self.ax.plot(x, y, label=label, color=color)
|
|
52
|
-
|
|
53
|
-
def render(self, path: Path | None = None):
|
|
54
|
-
if path:
|
|
55
|
-
plt.savefig(path)
|
|
56
|
-
else:
|
|
57
|
-
plt.show()
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class Plot:
|
|
61
|
-
def __init__(
|
|
62
|
-
self,
|
|
63
|
-
title: str = "",
|
|
64
|
-
x_label: str = "",
|
|
65
|
-
y_label: str = "",
|
|
66
|
-
legend_label: str = "",
|
|
67
|
-
) -> None:
|
|
68
|
-
self.series: list[PlotSeries] = []
|
|
69
|
-
self.title = title
|
|
70
|
-
self.plot_type = ""
|
|
71
|
-
self.x_label = x_label
|
|
72
|
-
self.y_label = y_label
|
|
73
|
-
self.legend_label = legend_label
|
|
74
|
-
self.c_map = mpl.colormaps["viridis"]
|
|
75
|
-
self.x_ticks = None
|
|
76
|
-
self.y_ticks = None
|
|
77
|
-
self.impl = MatplotlibPlotter()
|
|
78
|
-
|
|
79
|
-
def get_colour(self, curr, values, c_map):
|
|
80
|
-
"""
|
|
81
|
-
Returns a colour based on a cmap and how far across
|
|
82
|
-
the datasets you are
|
|
83
|
-
"""
|
|
84
|
-
length = len(values)
|
|
85
|
-
position = curr / length
|
|
86
|
-
return c_map(0.25 + (position / 2))
|
|
87
|
-
|
|
88
|
-
def set_colour_map(self, cmap="viridis"):
|
|
89
|
-
self.c_map = mpl.colormaps[cmap]
|
|
90
|
-
|
|
91
|
-
def set_x_ticks(self, lower, upper, step):
|
|
92
|
-
self.x_ticks = range(lower, upper + 1, step)
|
|
93
|
-
|
|
94
|
-
def set_y_ticks(self, lower, upper, step):
|
|
95
|
-
self.y_ticks = range(lower, upper + 1, step)
|
|
96
|
-
|
|
97
|
-
def set_decorations(self):
|
|
98
|
-
self.impl.set_decorations(
|
|
99
|
-
self.x_label, self.y_label, self.title, self.x_ticks, self.y_ticks
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
def plot(self, path: Path | None = None):
|
|
103
|
-
raise NotImplementedError()
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
class LinePlot(Plot):
|
|
107
|
-
|
|
108
|
-
def plot(self, path: Path | None = None):
|
|
109
|
-
|
|
110
|
-
first = True
|
|
111
|
-
for i, series in enumerate(self.series):
|
|
112
|
-
colour = self.get_colour(i, self.series, self.c_map)
|
|
113
|
-
label = f"{series.label}"
|
|
114
|
-
if first and self.legend_label:
|
|
115
|
-
label = f"{label} {self.legend_label}"
|
|
116
|
-
first = False
|
|
117
|
-
line_series = cast(LinePlotSeries, series)
|
|
118
|
-
self.impl.plot_line(line_series.x, line_series.y, label=label, color=colour)
|
|
119
|
-
|
|
120
|
-
self.set_decorations()
|
|
121
|
-
self.impl.render(path)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
class MultiFigureLinePlotter:
|
|
125
|
-
"""
|
|
126
|
-
Plot with multiple figures. Each is a line plot
|
|
127
|
-
"""
|
|
128
|
-
|
|
129
|
-
def __init__(self, data: dict, figures: list, xlabel: str) -> None:
|
|
130
|
-
self.data = data
|
|
131
|
-
self.figures = figures
|
|
132
|
-
self.size: tuple = (10, 5)
|
|
133
|
-
self.xlabel = xlabel
|
|
134
|
-
self.output_fmt = "svg"
|
|
135
|
-
|
|
136
|
-
def _plot_line(self, key: str, label: str):
|
|
137
|
-
plt.plot(self.data[key], label=label)
|
|
138
|
-
|
|
139
|
-
def _decorate(self, ylabel: str, title: str):
|
|
140
|
-
plt.title(title)
|
|
141
|
-
plt.xlabel(self.xlabel)
|
|
142
|
-
plt.ylabel(ylabel)
|
|
143
|
-
plt.legend()
|
|
144
|
-
plt.show()
|
|
145
|
-
|
|
146
|
-
def plot(self, output_path: Path):
|
|
147
|
-
"""
|
|
148
|
-
Do the plot
|
|
149
|
-
"""
|
|
150
|
-
|
|
151
|
-
for idx, figure_info in enumerate(self.figures):
|
|
152
|
-
plt.figure(figsize=self.size)
|
|
153
|
-
|
|
154
|
-
for data_key, label in zip(figure_info["keys"], figure_info["labels"]):
|
|
155
|
-
self._plot_line(data_key, label)
|
|
156
|
-
|
|
157
|
-
title = figure_info["title"]
|
|
158
|
-
ylabel = figure_info["ylabel"]
|
|
159
|
-
|
|
160
|
-
self._decorate(ylabel, title)
|
|
161
|
-
plt.savefig(str(output_path) + f"_{idx}.{self.output_fmt}")
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
class DatasetGridPlotter:
|
|
165
|
-
"""
|
|
166
|
-
Plot a dataset on a grid. A dataset has pairs of images corresponding
|
|
167
|
-
to an image and an associated mask.
|
|
168
|
-
"""
|
|
169
|
-
|
|
170
|
-
def __init__(
|
|
171
|
-
self, title: str = "", stride: int = 4, size: tuple = (25, 20)
|
|
172
|
-
) -> None:
|
|
173
|
-
self.title = title
|
|
174
|
-
self.stride = stride
|
|
175
|
-
self.size = size
|
|
176
|
-
|
|
177
|
-
def _plot_grid_element(self, rows: int, cols: int, count: int, image, transform):
|
|
178
|
-
plt.subplot(rows, cols, count)
|
|
179
|
-
if transform:
|
|
180
|
-
plt.imshow(transform.reverse(image.squeeze(0).float()))
|
|
181
|
-
else:
|
|
182
|
-
plt.imshow(image.squeeze(0).float())
|
|
183
|
-
plt.axis("off")
|
|
184
|
-
plt.title(self.title)
|
|
185
|
-
return count + 1
|
|
186
|
-
|
|
187
|
-
def plot(self, data, num_samples: int, output_path: Path, transform=None):
|
|
188
|
-
plt.figure(figsize=self.size)
|
|
189
|
-
rows = num_samples // self.stride
|
|
190
|
-
cols = num_samples // rows
|
|
191
|
-
count = 1
|
|
192
|
-
indices = [random.randint(0, len(data) - 1) for _ in range(num_samples)]
|
|
193
|
-
|
|
194
|
-
for index in indices:
|
|
195
|
-
if count == num_samples + 1:
|
|
196
|
-
break
|
|
197
|
-
x, y = data[index]
|
|
198
|
-
count = self._plot_grid_element(rows, cols, count, x, transform)
|
|
199
|
-
count = self._plot_grid_element(rows, cols, count, y, transform)
|
|
200
|
-
plt.savefig(output_path)
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
from icplot.graph import LinePlot, LinePlotSeries
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_line_plot():
|
|
7
|
-
|
|
8
|
-
plot = LinePlot(title="test",
|
|
9
|
-
x_label="test_x",
|
|
10
|
-
y_label="test_y",
|
|
11
|
-
legend_label="test_legend")
|
|
12
|
-
|
|
13
|
-
data = [([0, 5, 10], [1, 2, 3]),
|
|
14
|
-
([0, 5, 10], [3, 6, 9]),
|
|
15
|
-
([0, 5, 10], [4, 8, 12])]
|
|
16
|
-
|
|
17
|
-
for idx, [x, y] in enumerate(data):
|
|
18
|
-
plot.series.append(LinePlotSeries(x, y, label=f"Series {idx}"))
|
|
19
|
-
|
|
20
|
-
plot.set_x_ticks(0, 10, 5)
|
|
21
|
-
|
|
22
|
-
output_path = Path() / "output.svg"
|
|
23
|
-
plot.plot(output_path)
|
|
24
|
-
|
|
25
|
-
assert output_path.exists()
|
|
26
|
-
output_path.unlink()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|