icplot 0.0.8__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.8/src/icplot.egg-info → icplot-0.0.9}/PKG-INFO +2 -2
- {icplot-0.0.8 → icplot-0.0.9}/pyproject.toml +2 -2
- icplot-0.0.9/src/icplot/color.py +54 -0
- 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.8 → icplot-0.0.9/src/icplot.egg-info}/PKG-INFO +2 -2
- {icplot-0.0.8 → icplot-0.0.9}/src/icplot.egg-info/SOURCES.txt +3 -1
- {icplot-0.0.8 → icplot-0.0.9}/src/icplot.egg-info/requires.txt +1 -1
- {icplot-0.0.8 → icplot-0.0.9}/test/test_line_plots.py +8 -2
- icplot-0.0.8/src/icplot/color.py +0 -13
- icplot-0.0.8/src/icplot/graph/__init__.py +0 -1
- icplot-0.0.8/src/icplot/graph/matplotlib_plot_backend.py +0 -69
- icplot-0.0.8/src/icplot/graph/plot.py +0 -163
- icplot-0.0.8/src/icplot/graph/series.py +0 -54
- {icplot-0.0.8 → icplot-0.0.9}/LICENSE +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/README.md +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/setup.cfg +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/src/icplot/__init__.py +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/src/icplot/cairo_interface.py +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/src/icplot/gantt.py +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/src/icplot/geometry.py +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/src/icplot/image_utils.py +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/src/icplot/main_cli.py +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/src/icplot/project_elements.py +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/src/icplot/tex_interface.py +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/src/icplot.egg-info/dependency_links.txt +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/src/icplot.egg-info/entry_points.txt +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/src/icplot.egg-info/top_level.txt +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/test/test_cairo_interface.py +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/test/test_gantt_chart.py +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/test/test_image_utils.py +0 -0
- {icplot-0.0.8 → icplot-0.0.9}/test/test_tex_interface.py +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"
|
|
@@ -18,8 +18,10 @@ src/icplot.egg-info/entry_points.txt
|
|
|
18
18
|
src/icplot.egg-info/requires.txt
|
|
19
19
|
src/icplot.egg-info/top_level.txt
|
|
20
20
|
src/icplot/graph/__init__.py
|
|
21
|
-
src/icplot/graph/
|
|
21
|
+
src/icplot/graph/axis.py
|
|
22
|
+
src/icplot/graph/matplotlib.py
|
|
22
23
|
src/icplot/graph/plot.py
|
|
24
|
+
src/icplot/graph/plotter.py
|
|
23
25
|
src/icplot/graph/series.py
|
|
24
26
|
test/test_cairo_interface.py
|
|
25
27
|
test/test_gantt_chart.py
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
from icplot.graph import Plot, LinePlotSeries
|
|
4
|
+
from icplot.graph.matplotlib import MatplotlibPlotter
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def test_line_plot():
|
|
7
8
|
|
|
8
9
|
plot = Plot(
|
|
9
|
-
title="test",
|
|
10
|
+
title="test",
|
|
11
|
+
x_label="test_x",
|
|
12
|
+
y_label="test_y",
|
|
13
|
+
legend_label="test_legend"
|
|
10
14
|
)
|
|
11
15
|
|
|
12
16
|
data = [([0, 5, 10], [1, 2, 3]), ([0, 5, 10], [3, 6, 9]), ([0, 5, 10], [4, 8, 12])]
|
|
@@ -17,7 +21,9 @@ def test_line_plot():
|
|
|
17
21
|
plot.set_x_ticks(0, 10, 5)
|
|
18
22
|
|
|
19
23
|
output_path = Path() / "output.svg"
|
|
20
|
-
|
|
24
|
+
|
|
25
|
+
plotter = MatplotlibPlotter()
|
|
26
|
+
plotter.plot(plot, output_path)
|
|
21
27
|
|
|
22
28
|
assert output_path.exists()
|
|
23
29
|
output_path.unlink()
|
icplot-0.0.8/src/icplot/color.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .plot import * # NOQA
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
import logging
|
|
3
|
-
|
|
4
|
-
logging.getLogger("matplotlib").setLevel(logging.WARNING)
|
|
5
|
-
import matplotlib # NOQA
|
|
6
|
-
|
|
7
|
-
default_backend = matplotlib.get_backend()
|
|
8
|
-
matplotlib.use("Agg")
|
|
9
|
-
import matplotlib.pyplot as plt # NOQA
|
|
10
|
-
import matplotlib as mpl # NOQA
|
|
11
|
-
|
|
12
|
-
from icplot.color import ColorMap # NOQA
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class MatplotlibColorMap(ColorMap):
|
|
16
|
-
|
|
17
|
-
def __init__(self, label: str):
|
|
18
|
-
super().__init__(label)
|
|
19
|
-
self.c_map = mpl.colormaps[label]
|
|
20
|
-
self.cmap_start_offset = 0.25
|
|
21
|
-
self.cmap_scale = 2
|
|
22
|
-
|
|
23
|
-
def get_color(self, cursor: int, values: list):
|
|
24
|
-
"""
|
|
25
|
-
Returns a colour based on a cmap and how far across
|
|
26
|
-
the datasets you are
|
|
27
|
-
"""
|
|
28
|
-
position = cursor / len(values)
|
|
29
|
-
return self.c_map(self.cmap_start_offset + (position / self.cmap_scale))
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class MatplotlibPlotBackend:
|
|
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 plot_scatter(self, data, label, color):
|
|
54
|
-
self.ax.plot(data, label=label, color=color)
|
|
55
|
-
|
|
56
|
-
def make_subplot(self, rows, cols, count):
|
|
57
|
-
plt.subplot(rows, cols, count)
|
|
58
|
-
|
|
59
|
-
def plot_image(self, data):
|
|
60
|
-
plt.imshow(data)
|
|
61
|
-
plt.axis("off")
|
|
62
|
-
|
|
63
|
-
def render(self, path: Path | None = None):
|
|
64
|
-
if path:
|
|
65
|
-
plt.savefig(path)
|
|
66
|
-
else:
|
|
67
|
-
plt.switch_backend(default_backend)
|
|
68
|
-
plt.show()
|
|
69
|
-
plt.switch_backend("Agg")
|
|
@@ -1,163 +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
|
-
import json
|
|
9
|
-
|
|
10
|
-
from .series import PlotSeries, LinePlotSeries, ScatterPlotSeries, ImageSeries
|
|
11
|
-
from .matplotlib_plot_backend import MatplotlibPlotBackend, MatplotlibColorMap
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Range:
|
|
15
|
-
|
|
16
|
-
def __init__(self, lower, upper, step):
|
|
17
|
-
self.lower = lower
|
|
18
|
-
self.upper = upper
|
|
19
|
-
self.step = step
|
|
20
|
-
|
|
21
|
-
def eval(self):
|
|
22
|
-
return range(self.lower, self.upper, self.step)
|
|
23
|
-
|
|
24
|
-
def serialize(self):
|
|
25
|
-
return {"lower": self.lower, "upper": self.upper, "step": self.step}
|
|
26
|
-
|
|
27
|
-
def __str__(self):
|
|
28
|
-
return json.dumps(self.serialize(), ident=4)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class Plot:
|
|
32
|
-
"""
|
|
33
|
-
A generic plot with optional axis ticks
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
def __init__(
|
|
37
|
-
self,
|
|
38
|
-
title: str = "",
|
|
39
|
-
x_label: str = "",
|
|
40
|
-
y_label: str = "",
|
|
41
|
-
legend_label: str = "",
|
|
42
|
-
) -> None:
|
|
43
|
-
self.series: list[PlotSeries] = []
|
|
44
|
-
self.title = title
|
|
45
|
-
self.size: tuple | None = None
|
|
46
|
-
self.plot_type = ""
|
|
47
|
-
self.x_label = x_label
|
|
48
|
-
self.y_label = y_label
|
|
49
|
-
self.legend_label = legend_label
|
|
50
|
-
self.x_ticks: Range | None = None
|
|
51
|
-
self.y_ticks: Range | None = None
|
|
52
|
-
self.backend = MatplotlibPlotBackend()
|
|
53
|
-
self.cmap = MatplotlibColorMap("viridis")
|
|
54
|
-
|
|
55
|
-
def set_colour_map(self, cmap):
|
|
56
|
-
self.c_map = MatplotlibColorMap(cmap)
|
|
57
|
-
|
|
58
|
-
def set_x_ticks(self, lower, upper, step):
|
|
59
|
-
self.x_ticks = Range(lower, upper + 1, step)
|
|
60
|
-
|
|
61
|
-
def set_y_ticks(self, lower, upper, step):
|
|
62
|
-
self.y_ticks = Range(lower, upper + 1, step)
|
|
63
|
-
|
|
64
|
-
def set_decorations(self):
|
|
65
|
-
|
|
66
|
-
xtick_range = None
|
|
67
|
-
ytick_range = None
|
|
68
|
-
if self.x_ticks:
|
|
69
|
-
xtick_range = self.x_ticks.eval()
|
|
70
|
-
if self.y_ticks:
|
|
71
|
-
ytick_range = self.y_ticks.eval()
|
|
72
|
-
|
|
73
|
-
self.backend.set_decorations(
|
|
74
|
-
self.x_label,
|
|
75
|
-
self.y_label,
|
|
76
|
-
self.title,
|
|
77
|
-
xtick_range,
|
|
78
|
-
ytick_range,
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
def add_series(self, series):
|
|
82
|
-
self.series.append(series)
|
|
83
|
-
|
|
84
|
-
def plot_series(self, series, label):
|
|
85
|
-
if series.series_type == "line":
|
|
86
|
-
line_series = cast(LinePlotSeries, series)
|
|
87
|
-
self.backend.plot_line(
|
|
88
|
-
line_series.x, line_series.y, label=label, color=series.color
|
|
89
|
-
)
|
|
90
|
-
elif series.series_type == "scatter":
|
|
91
|
-
scatter_series = cast(ScatterPlotSeries, series)
|
|
92
|
-
self.backend.plot_scatter(
|
|
93
|
-
scatter_series.data, label=label, color=series.color
|
|
94
|
-
)
|
|
95
|
-
elif series.series_type == "image":
|
|
96
|
-
image_series = cast(ImageSeries, series)
|
|
97
|
-
self.backend.plot_image(image_series.data)
|
|
98
|
-
|
|
99
|
-
def plot(self, path: Path | None = None):
|
|
100
|
-
for idx, series in enumerate(self.series):
|
|
101
|
-
series.color = self.cmap.get_color(idx, self.series)
|
|
102
|
-
|
|
103
|
-
first = True
|
|
104
|
-
for series in self.series:
|
|
105
|
-
label = series.label
|
|
106
|
-
if first and self.legend_label:
|
|
107
|
-
label = f"{label} {self.legend_label}"
|
|
108
|
-
first = False
|
|
109
|
-
self.plot_series(series, label)
|
|
110
|
-
|
|
111
|
-
self.set_decorations()
|
|
112
|
-
self.backend.render(path)
|
|
113
|
-
|
|
114
|
-
def serialize(self):
|
|
115
|
-
return {
|
|
116
|
-
"series": [s.serialize() for s in self.series],
|
|
117
|
-
"title": self.title,
|
|
118
|
-
"type": self.plot_type,
|
|
119
|
-
"xlabel": self.x_label,
|
|
120
|
-
"ylabel": self.y_label,
|
|
121
|
-
"legend_label": self.legend_label,
|
|
122
|
-
"xticks": self.x_ticks.serialize(),
|
|
123
|
-
"yticks": self.y_ticks.serialize(),
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
class GridPlot(Plot):
|
|
128
|
-
"""
|
|
129
|
-
Make a grid of plots
|
|
130
|
-
"""
|
|
131
|
-
|
|
132
|
-
def __init__(
|
|
133
|
-
self, title: str = "", stride: int = 4, size: tuple = (25, 20)
|
|
134
|
-
) -> None:
|
|
135
|
-
super().__init__(title)
|
|
136
|
-
self.stride = stride
|
|
137
|
-
self.size = size
|
|
138
|
-
|
|
139
|
-
def _subplot(self, rows: int, cols: int, count: int, series):
|
|
140
|
-
|
|
141
|
-
self.backend.make_subplot(rows, cols, count)
|
|
142
|
-
self.plot_series(series, "")
|
|
143
|
-
return count + 1
|
|
144
|
-
|
|
145
|
-
def render(self, data, path: Path, num_samples: int = 0):
|
|
146
|
-
rows = num_samples // self.stride
|
|
147
|
-
cols = num_samples // rows
|
|
148
|
-
count = 1
|
|
149
|
-
if num_samples == 0:
|
|
150
|
-
indices = [random.randint(0, len(data) - 1) for _ in range(num_samples)]
|
|
151
|
-
else:
|
|
152
|
-
indices = list(range(0, len(data)))
|
|
153
|
-
|
|
154
|
-
for index in indices:
|
|
155
|
-
if num_samples > 0 and count == num_samples + 1:
|
|
156
|
-
break
|
|
157
|
-
if isinstance(data[index], list):
|
|
158
|
-
for series in data[index]:
|
|
159
|
-
count = self._subplot(rows, cols, count, series)
|
|
160
|
-
else:
|
|
161
|
-
count = self._subplot(rows, cols, count, data[index])
|
|
162
|
-
|
|
163
|
-
self.backend.render(path)
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class PlotSeries:
|
|
5
|
-
|
|
6
|
-
def __init__(self, label: str = ""):
|
|
7
|
-
self.label = label
|
|
8
|
-
self.color: list[float] = [0.0, 0.0, 0.0]
|
|
9
|
-
self.series_type = ""
|
|
10
|
-
|
|
11
|
-
def serialize(self):
|
|
12
|
-
return {"label": self.label, "color": self.color, "type": self.series_type}
|
|
13
|
-
|
|
14
|
-
def __str__(self):
|
|
15
|
-
return json.dumps(self.serialize(), indent=4)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class ImageSeries(PlotSeries):
|
|
19
|
-
def __init__(self, data, label: str = "", transform=None):
|
|
20
|
-
super().__init__(label)
|
|
21
|
-
self.data = data
|
|
22
|
-
self.series_type = "image"
|
|
23
|
-
self.transform = transform
|
|
24
|
-
|
|
25
|
-
def serialize(self):
|
|
26
|
-
ret = super().serialize()
|
|
27
|
-
ret["data"] = self.data
|
|
28
|
-
return ret
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class LinePlotSeries(PlotSeries):
|
|
32
|
-
def __init__(self, x: list[float], y: list[float], label: str = "") -> None:
|
|
33
|
-
super().__init__(label)
|
|
34
|
-
self.series_type = "line"
|
|
35
|
-
self.x = x
|
|
36
|
-
self.y = y
|
|
37
|
-
|
|
38
|
-
def serialize(self):
|
|
39
|
-
ret = super().serialize()
|
|
40
|
-
ret["x"] = self.x
|
|
41
|
-
ret["y"] = self.y
|
|
42
|
-
return ret
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class ScatterPlotSeries(PlotSeries):
|
|
46
|
-
def __init__(self, data, label: str = ""):
|
|
47
|
-
super().__init__(label)
|
|
48
|
-
self.data = data
|
|
49
|
-
self.series_type = "scatter"
|
|
50
|
-
|
|
51
|
-
def serialize(self):
|
|
52
|
-
ret = super().serialize()
|
|
53
|
-
ret["data"] = self.data
|
|
54
|
-
return ret
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|