icplot 0.0.10__tar.gz → 0.0.12__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.10/src/icplot.egg-info → icplot-0.0.12}/PKG-INFO +8 -2
- {icplot-0.0.10 → icplot-0.0.12}/README.md +6 -0
- {icplot-0.0.10 → icplot-0.0.12}/pyproject.toml +2 -2
- icplot-0.0.12/src/icplot/cairo_interface.py +59 -0
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot/color.py +3 -6
- icplot-0.0.12/src/icplot/gantt/gantt_renderer.py +59 -0
- icplot-0.0.12/src/icplot/gantt/pgfgantt.py +15 -0
- icplot-0.0.12/src/icplot/geometry.py +45 -0
- icplot-0.0.12/src/icplot/graph/axis.py +34 -0
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot/graph/matplotlib.py +5 -5
- icplot-0.0.12/src/icplot/graph/plot.py +61 -0
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot/graph/plotter.py +6 -5
- icplot-0.0.12/src/icplot/graph/series.py +43 -0
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot/main_cli.py +47 -8
- icplot-0.0.12/src/icplot/tex.py +77 -0
- icplot-0.0.12/src/icplot/trace/__init__.py +1 -0
- icplot-0.0.12/src/icplot/trace/trace.py +0 -0
- {icplot-0.0.10 → icplot-0.0.12/src/icplot.egg-info}/PKG-INFO +8 -2
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot.egg-info/SOURCES.txt +4 -2
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot.egg-info/requires.txt +1 -1
- icplot-0.0.12/test/test_cairo_interface.py +31 -0
- icplot-0.0.12/test/test_line_plots.py +32 -0
- icplot-0.0.12/test/test_tex_interface.py +23 -0
- icplot-0.0.10/src/icplot/cairo_interface.py +0 -60
- icplot-0.0.10/src/icplot/gantt/gantt_renderer.py +0 -59
- icplot-0.0.10/src/icplot/gantt/pgfgantt_interface.py +0 -26
- icplot-0.0.10/src/icplot/geometry.py +0 -50
- icplot-0.0.10/src/icplot/graph/axis.py +0 -44
- icplot-0.0.10/src/icplot/graph/plot.py +0 -92
- icplot-0.0.10/src/icplot/graph/series.py +0 -111
- icplot-0.0.10/src/icplot/tex_interface.py +0 -92
- icplot-0.0.10/test/test_cairo_interface.py +0 -29
- icplot-0.0.10/test/test_line_plots.py +0 -29
- icplot-0.0.10/test/test_tex_interface.py +0 -18
- {icplot-0.0.10 → icplot-0.0.12}/LICENSE +0 -0
- {icplot-0.0.10 → icplot-0.0.12}/setup.cfg +0 -0
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot/__init__.py +0 -0
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot/gantt/__init__.py +0 -0
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot/gantt/gantt.py +0 -0
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot/graph/__init__.py +0 -0
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot/image_utils.py +0 -0
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot/py.typed +0 -0
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot.egg-info/dependency_links.txt +0 -0
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot.egg-info/entry_points.txt +0 -0
- {icplot-0.0.10 → icplot-0.0.12}/src/icplot.egg-info/top_level.txt +0 -0
- {icplot-0.0.10 → icplot-0.0.12}/test/test_gantt_chart.py +0 -0
- {icplot-0.0.10 → icplot-0.0.12}/test/test_image_utils.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.12
|
|
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.1.
|
|
19
|
+
Requires-Dist: iccore==0.1.5
|
|
20
20
|
Provides-Extra: test
|
|
21
21
|
Requires-Dist: pytest; extra == "test"
|
|
22
22
|
Requires-Dist: pytest-cov; extra == "test"
|
|
@@ -62,6 +62,12 @@ To covert between image formats you can do:
|
|
|
62
62
|
icplot convert --source my_image.svg --target my_image.png
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
To render a Tex tikz image as pdf and png you can do:
|
|
66
|
+
|
|
67
|
+
``` shell
|
|
68
|
+
icplot convert --source my_tikz.tex
|
|
69
|
+
```
|
|
70
|
+
|
|
65
71
|
# Copyright #
|
|
66
72
|
|
|
67
73
|
Copyright 2024 Irish Centre for High End Computing
|
|
@@ -31,6 +31,12 @@ To covert between image formats you can do:
|
|
|
31
31
|
icplot convert --source my_image.svg --target my_image.png
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
To render a Tex tikz image as pdf and png you can do:
|
|
35
|
+
|
|
36
|
+
``` shell
|
|
37
|
+
icplot convert --source my_tikz.tex
|
|
38
|
+
```
|
|
39
|
+
|
|
34
40
|
# Copyright #
|
|
35
41
|
|
|
36
42
|
Copyright 2024 Irish Centre for High End Computing
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "icplot"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.12"
|
|
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.1.
|
|
21
|
+
"iccore==0.1.5"]
|
|
22
22
|
|
|
23
23
|
[project.urls]
|
|
24
24
|
Repository = "https://git.ichec.ie/performance/toolshed/icplot"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import cairo
|
|
4
|
+
|
|
5
|
+
from .geometry import Scene, Rectangle
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def draw_rect(cr, rect: Rectangle):
|
|
9
|
+
x0, y0 = rect.location
|
|
10
|
+
x1 = rect.w
|
|
11
|
+
y1 = rect.h
|
|
12
|
+
cr.move_to(x0, y0)
|
|
13
|
+
cr.line_to(x1, y0)
|
|
14
|
+
cr.line_to(x1, y1)
|
|
15
|
+
cr.line_to(x0, y1)
|
|
16
|
+
cr.close_path()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def draw_shape(cr, shape):
|
|
20
|
+
cr.save()
|
|
21
|
+
if shape.shape_type == "rect":
|
|
22
|
+
draw_rect(cr, shape)
|
|
23
|
+
else:
|
|
24
|
+
return
|
|
25
|
+
cr.set_source_rgba(shape.fill.r, shape.fill.g, shape.fill.b, shape.fill.a)
|
|
26
|
+
cr.fill_preserve()
|
|
27
|
+
if shape.stroke is not None:
|
|
28
|
+
cr.set_source_rgba(
|
|
29
|
+
shape.stroke.r, shape.stroke.g, shape.stroke.b, shape.stroke.a
|
|
30
|
+
)
|
|
31
|
+
cr.set_line_width(shape.stroke_thickness)
|
|
32
|
+
cr.stroke()
|
|
33
|
+
|
|
34
|
+
cr.restore()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def draw_text(cr, text):
|
|
38
|
+
cr.save()
|
|
39
|
+
|
|
40
|
+
cr.select_font_face(text.font.family)
|
|
41
|
+
cr.set_font_size(text.font.size)
|
|
42
|
+
cr.move_to(text.location[0], text.location[1])
|
|
43
|
+
cr.show_text(text.content)
|
|
44
|
+
|
|
45
|
+
cr.restore()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def draw_scene(cr, scene: Scene):
|
|
49
|
+
for item in scene.items:
|
|
50
|
+
if item.item_type == "shape":
|
|
51
|
+
draw_shape(cr, item)
|
|
52
|
+
elif item.item_type == "text":
|
|
53
|
+
draw_text(cr, item)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def draw_svg(scene: Scene, path: Path):
|
|
57
|
+
with cairo.SVGSurface(path, scene.size[0], scene.size[1]) as surface:
|
|
58
|
+
cr = cairo.Context(surface)
|
|
59
|
+
draw_scene(cr, scene)
|
|
@@ -34,6 +34,7 @@ class Color(BaseModel):
|
|
|
34
34
|
def serialize(self) -> str:
|
|
35
35
|
return f"r: {self.r}, g: {self.g}, b: {self.b}, a: {self.a}"
|
|
36
36
|
|
|
37
|
+
|
|
37
38
|
class ColorMap:
|
|
38
39
|
"""
|
|
39
40
|
A mapping from a flat index to a color
|
|
@@ -42,15 +43,11 @@ class ColorMap:
|
|
|
42
43
|
def __init__(self, label: str, data_func: Callable):
|
|
43
44
|
self.label = label
|
|
44
45
|
self.data_func = data_func
|
|
45
|
-
self.start_offset = 0.25
|
|
46
|
-
self.scale = 2
|
|
47
46
|
|
|
48
47
|
def get_color(self, cursor: int, values: list) -> Color:
|
|
49
48
|
"""
|
|
50
49
|
Returns a colour based on a cmap and how far across
|
|
51
50
|
the datasets you are
|
|
52
51
|
"""
|
|
53
|
-
position = cursor / len(values)
|
|
54
|
-
return Color.from_list(
|
|
55
|
-
self.data_func(self.start_offset + (position / self.scale))
|
|
56
|
-
)
|
|
52
|
+
position = cursor / (len(values) - 1)
|
|
53
|
+
return Color.from_list(self.data_func(position))
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from iccore.project import Milestone
|
|
2
|
+
from icplot.geometry import Scene, SceneItem, Rectangle
|
|
3
|
+
|
|
4
|
+
from .gantt import GanttChart
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _render_milestone(
|
|
8
|
+
gantt: GanttChart, milestone: Milestone, chart_range, yloc
|
|
9
|
+
) -> SceneItem:
|
|
10
|
+
# chart_delta = chart_range[1] - chart_range[0]
|
|
11
|
+
chart_delta = 0
|
|
12
|
+
start_delta = milestone.start_date - chart_range[0]
|
|
13
|
+
# milestone_delta = milestone.due_date - milestone.start_date
|
|
14
|
+
milestone_delta = 0
|
|
15
|
+
|
|
16
|
+
start_frac = float(start_delta / chart_delta)
|
|
17
|
+
milestone_frac = float(milestone_delta / chart_delta)
|
|
18
|
+
|
|
19
|
+
w = milestone_frac * gantt.width
|
|
20
|
+
h = gantt.bar_max_height * gantt.height
|
|
21
|
+
x = start_frac * gantt.width
|
|
22
|
+
y = yloc
|
|
23
|
+
|
|
24
|
+
rect = Rectangle(w=w, h=h, location=(x, y), fill=gantt.bar_color)
|
|
25
|
+
return rect
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _render_milestones(gantt: GanttChart):
|
|
29
|
+
|
|
30
|
+
if not gantt.milestones:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
milestones = gantt.milestones
|
|
34
|
+
# milestones.sort(key=lambda x: x.start_date, reverse=True)
|
|
35
|
+
|
|
36
|
+
start_date = gantt.start_date
|
|
37
|
+
end_date = gantt.end_date
|
|
38
|
+
if not start_date:
|
|
39
|
+
start_date = milestones[0].start_date
|
|
40
|
+
|
|
41
|
+
# if not end_date:
|
|
42
|
+
# end_date = max(m.due_date for m in milestones)
|
|
43
|
+
|
|
44
|
+
chart_range = (start_date, end_date)
|
|
45
|
+
yloc = 0.0
|
|
46
|
+
bar_height = gantt.bar_max_height * gantt.height
|
|
47
|
+
scene_items = []
|
|
48
|
+
for milestone in milestones:
|
|
49
|
+
item = _render_milestone(gantt, milestone, chart_range, yloc)
|
|
50
|
+
scene_items.append(item)
|
|
51
|
+
yloc += bar_height
|
|
52
|
+
return scene_items
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def render(gantt: GanttChart) -> Scene:
|
|
56
|
+
scene = Scene()
|
|
57
|
+
items = _render_milestones(gantt)
|
|
58
|
+
scene.items.extend(items)
|
|
59
|
+
return scene
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from .gantt import GanttChart
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def render(gantt: GanttChart) -> str:
|
|
7
|
+
|
|
8
|
+
with open(Path(__file__).parent / "pgfgantt.tex", "r", encoding="utf-8") as f:
|
|
9
|
+
template = f.read()
|
|
10
|
+
|
|
11
|
+
if gantt.title:
|
|
12
|
+
title_str = f"\\title{{{gantt.title}}}"
|
|
13
|
+
template.replace("%%TITLE%%", title_str)
|
|
14
|
+
|
|
15
|
+
return template
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
from .color import Color
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Font(BaseModel):
|
|
7
|
+
|
|
8
|
+
family: str = "Sans"
|
|
9
|
+
weight: str = "normal"
|
|
10
|
+
slant: str = "normal"
|
|
11
|
+
size: float = 0.5
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SceneItem(BaseModel):
|
|
15
|
+
|
|
16
|
+
item_type: str
|
|
17
|
+
location: tuple[float, float] = (0.0, 0.0)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Shape(SceneItem):
|
|
21
|
+
|
|
22
|
+
item_type: str = "shape"
|
|
23
|
+
fill: Color = Color()
|
|
24
|
+
stroke: Color = Color()
|
|
25
|
+
stroke_thickness: float = 0.5
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TextPath(SceneItem):
|
|
29
|
+
|
|
30
|
+
item_type: str = "text"
|
|
31
|
+
content: str
|
|
32
|
+
font: Font = Font()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Rectangle(Shape):
|
|
36
|
+
|
|
37
|
+
item_type: str = "rect"
|
|
38
|
+
w: float
|
|
39
|
+
h: float
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Scene(BaseModel):
|
|
43
|
+
|
|
44
|
+
items: list[SceneItem] = []
|
|
45
|
+
size: tuple[int, int] = (100, 100)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module has a plot's axis
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Range(BaseModel):
|
|
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
|
+
lower: int
|
|
15
|
+
upper: int
|
|
16
|
+
step: int
|
|
17
|
+
|
|
18
|
+
def eval(self) -> list:
|
|
19
|
+
return list(range(self.lower, self.upper, self.step))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PlotAxis(BaseModel):
|
|
23
|
+
"""
|
|
24
|
+
This is a plot's axis
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
label: str = ""
|
|
28
|
+
ticks: Range | None = None
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def resolved_ticks(self) -> list:
|
|
32
|
+
if self.ticks:
|
|
33
|
+
return self.ticks.eval()
|
|
34
|
+
return []
|
|
@@ -29,7 +29,7 @@ class MatplotlibPlotter(Plotter):
|
|
|
29
29
|
A plot backend for matplotlib
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
|
-
def __init__(self, cmap_label="
|
|
32
|
+
def __init__(self, cmap_label="gist_rainbow"):
|
|
33
33
|
super().__init__(MatplotlibColorMap(cmap_label))
|
|
34
34
|
|
|
35
35
|
def set_decorations(self, ax, plot: Plot):
|
|
@@ -39,9 +39,9 @@ class MatplotlibPlotter(Plotter):
|
|
|
39
39
|
if plot.y_axis.label:
|
|
40
40
|
ax.set_ylabel(plot.y_axis.label)
|
|
41
41
|
if plot.x_axis.ticks:
|
|
42
|
-
ax.set_xticks(plot.x_axis.
|
|
42
|
+
ax.set_xticks(plot.x_axis.resolved_ticks)
|
|
43
43
|
if plot.y_axis.ticks:
|
|
44
|
-
ax.set_yticks(plot.y_axis.
|
|
44
|
+
ax.set_yticks(plot.y_axis.resolved_ticks)
|
|
45
45
|
if plot.title:
|
|
46
46
|
ax.set_title(plot.title)
|
|
47
47
|
|
|
@@ -50,12 +50,12 @@ class MatplotlibPlotter(Plotter):
|
|
|
50
50
|
series.x,
|
|
51
51
|
series.y,
|
|
52
52
|
label=series.label,
|
|
53
|
-
color=series.
|
|
53
|
+
color=series.color.as_list(),
|
|
54
54
|
marker=series.marker,
|
|
55
55
|
)
|
|
56
56
|
|
|
57
57
|
def plot_scatter(self, ax, series: ScatterPlotSeries):
|
|
58
|
-
ax.scatter(series.data, label=series.label, color=series.
|
|
58
|
+
ax.scatter(series.data, label=series.label, color=series.color.as_list())
|
|
59
59
|
|
|
60
60
|
def plot_image(self, ax, series: ImageSeries):
|
|
61
61
|
ax.imshow(series.data)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module has content for generating plots
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from .axis import PlotAxis
|
|
10
|
+
from .series import PlotSeries
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Plot(BaseModel):
|
|
14
|
+
"""
|
|
15
|
+
A generic plot with optional axis ticks
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
title: str = ""
|
|
19
|
+
legend_label: str = ""
|
|
20
|
+
x_axis: PlotAxis = PlotAxis()
|
|
21
|
+
y_axis: PlotAxis = PlotAxis()
|
|
22
|
+
plot_type: str = ""
|
|
23
|
+
series: list[PlotSeries] = []
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GridPlot(Plot):
|
|
27
|
+
"""
|
|
28
|
+
Make a grid of plots
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
stride: int = 4
|
|
32
|
+
size: tuple = (25, 20)
|
|
33
|
+
data: list = []
|
|
34
|
+
|
|
35
|
+
def get_series_indices(self, num_samples: int = 0):
|
|
36
|
+
rows = num_samples // self.stride
|
|
37
|
+
cols = num_samples // rows
|
|
38
|
+
len_data = len(self.data)
|
|
39
|
+
|
|
40
|
+
if num_samples == 0:
|
|
41
|
+
indices = list(range(0, len_data))
|
|
42
|
+
else:
|
|
43
|
+
indices = [random.randint(0, len_data - 1) for _ in range(num_samples)]
|
|
44
|
+
return rows, cols, indices
|
|
45
|
+
|
|
46
|
+
def get_subplots(self, num_samples: int = 0):
|
|
47
|
+
rows, cols, indices = self.get_series_indices(num_samples)
|
|
48
|
+
|
|
49
|
+
subplots = []
|
|
50
|
+
count = 1
|
|
51
|
+
for index in indices:
|
|
52
|
+
if num_samples > 0 and count == num_samples + 1:
|
|
53
|
+
break
|
|
54
|
+
if isinstance(self.data[index], list):
|
|
55
|
+
for series in self.data[index]:
|
|
56
|
+
subplots.append(series)
|
|
57
|
+
count += 1
|
|
58
|
+
else:
|
|
59
|
+
subplots.append(self.data[index])
|
|
60
|
+
count += 1
|
|
61
|
+
return rows, cols, subplots
|
|
@@ -11,12 +11,13 @@ class Plotter:
|
|
|
11
11
|
self.cmap = cmap
|
|
12
12
|
|
|
13
13
|
def apply_cmap_colors(self, plot: Plot):
|
|
14
|
-
|
|
14
|
+
non_highlight = []
|
|
15
15
|
for idx, series in enumerate(plot.series):
|
|
16
|
-
if not series.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
if not series.highlight:
|
|
17
|
+
non_highlight.append(series)
|
|
18
|
+
|
|
19
|
+
for idx, series in enumerate(non_highlight):
|
|
20
|
+
series.color = self.cmap.get_color(idx, non_highlight)
|
|
20
21
|
|
|
21
22
|
def plot(self, plot: Plot, path: Path | None = None):
|
|
22
23
|
pass
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from icplot.color import Color
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PlotSeries(BaseModel):
|
|
9
|
+
"""
|
|
10
|
+
A data series in a plot, such as a single line in a line-plot
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
label: str
|
|
14
|
+
color: Color = Color()
|
|
15
|
+
series_type: str = ""
|
|
16
|
+
highlight: bool = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ImageSeries(PlotSeries):
|
|
20
|
+
"""
|
|
21
|
+
A plot data series where the elements are images
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
data: typing.Any
|
|
25
|
+
transform: typing.Any
|
|
26
|
+
series_type: str = "image"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class LinePlotSeries(PlotSeries):
|
|
30
|
+
"""
|
|
31
|
+
A plot series for line plots
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
x: list
|
|
35
|
+
y: list
|
|
36
|
+
marker: str = "o"
|
|
37
|
+
series_type: str = "line"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ScatterPlotSeries(PlotSeries):
|
|
41
|
+
|
|
42
|
+
data: typing.Any
|
|
43
|
+
series_type: str = "scatter"
|
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
import argparse
|
|
3
3
|
import logging
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
import os
|
|
5
6
|
|
|
6
|
-
from iccore import
|
|
7
|
-
from iccore import
|
|
7
|
+
from iccore.cli_utils import launch_common
|
|
8
|
+
from iccore.serialization import read_yaml
|
|
8
9
|
|
|
10
|
+
from icplot.graph import Plot
|
|
11
|
+
from icplot.graph.matplotlib import MatplotlibPlotter
|
|
9
12
|
from icplot.image_utils import pdf_to_png, svg_to_png, svg_to_pdf
|
|
13
|
+
from icplot import tex
|
|
14
|
+
from icplot.tex import TexBuildSettings
|
|
10
15
|
|
|
11
16
|
logger = logging.getLogger(__name__)
|
|
12
17
|
|
|
13
18
|
|
|
14
|
-
def launch_common(args):
|
|
15
|
-
runtime.ctx.set_is_dry_run(args.dry_run)
|
|
16
|
-
logging_utils.setup_default_logger()
|
|
17
|
-
|
|
18
|
-
|
|
19
19
|
def convert(args):
|
|
20
20
|
launch_common(args)
|
|
21
21
|
|
|
22
|
-
logger.info("
|
|
22
|
+
logger.info("Startubg conversion between %s and %s", args.source, args.target)
|
|
23
23
|
|
|
24
24
|
if args.target:
|
|
25
25
|
target = Path(args.target).resolve()
|
|
@@ -36,9 +36,33 @@ def convert(args):
|
|
|
36
36
|
svg_to_pdf(args.source.resolve(), target)
|
|
37
37
|
else:
|
|
38
38
|
svg_to_png(args.source)
|
|
39
|
+
elif args.source.suffix == ".tex":
|
|
40
|
+
if args.target:
|
|
41
|
+
target = Path(args.target).resolve()
|
|
42
|
+
else:
|
|
43
|
+
target = Path(os.getcwd())
|
|
44
|
+
|
|
45
|
+
settings = TexBuildSettings(
|
|
46
|
+
args.source.resolve(), args.build_dir.resolve(), args.target
|
|
47
|
+
)
|
|
48
|
+
tex.build(settings)
|
|
49
|
+
|
|
39
50
|
logger.info("Finished conversion")
|
|
40
51
|
|
|
41
52
|
|
|
53
|
+
def plot(args):
|
|
54
|
+
|
|
55
|
+
launch_common(args)
|
|
56
|
+
|
|
57
|
+
config = read_yaml(args.config.resolve())
|
|
58
|
+
|
|
59
|
+
plotter = MatplotlibPlotter()
|
|
60
|
+
if ["plots"] in config:
|
|
61
|
+
for eachplot in config["plots"]:
|
|
62
|
+
plot = Plot(**eachplot)
|
|
63
|
+
plotter.plot(plot, args.output_dir.resolve())
|
|
64
|
+
|
|
65
|
+
|
|
42
66
|
def main_cli():
|
|
43
67
|
parser = argparse.ArgumentParser()
|
|
44
68
|
parser.add_argument(
|
|
@@ -61,8 +85,23 @@ def main_cli():
|
|
|
61
85
|
default="",
|
|
62
86
|
help="Path to file to be converted to",
|
|
63
87
|
)
|
|
88
|
+
convert_parser.add_argument(
|
|
89
|
+
"--build_dir",
|
|
90
|
+
default=Path(os.getcwd()) / "_build/tikz",
|
|
91
|
+
help="Path for build output",
|
|
92
|
+
)
|
|
64
93
|
convert_parser.set_defaults(func=convert)
|
|
65
94
|
|
|
95
|
+
plot_parser = subparsers.add_parser("plot")
|
|
96
|
+
plot_parser.add_argument("--config", type=Path, help="Path to the plot config")
|
|
97
|
+
plot_parser.add_argument(
|
|
98
|
+
"--output_dir",
|
|
99
|
+
type=Path,
|
|
100
|
+
default=Path(os.getcwd()),
|
|
101
|
+
help="Path to the plot output directory",
|
|
102
|
+
)
|
|
103
|
+
plot_parser.set_defaults(func=plot)
|
|
104
|
+
|
|
66
105
|
args = parser.parse_args()
|
|
67
106
|
args.func(args)
|
|
68
107
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module has functionality for publishing Tex documents
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import NamedTuple
|
|
7
|
+
import shutil
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
import logging
|
|
11
|
+
|
|
12
|
+
from .image_utils import pdf_to_png
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TexBuildSettings(NamedTuple):
|
|
18
|
+
"""
|
|
19
|
+
This holds settings for the Tex build
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
source: Path
|
|
23
|
+
build_dir: Path
|
|
24
|
+
output_dir: Path | None = None
|
|
25
|
+
build_engine: str = "pdflatex"
|
|
26
|
+
output_png: bool = True
|
|
27
|
+
clear_workdir: bool = True
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def build_pdf(settings: TexBuildSettings, work_dir: Path):
|
|
31
|
+
|
|
32
|
+
cmd = f"{settings.build_engine} {settings.source}"
|
|
33
|
+
with open(work_dir / "stdout.txt", "w", encoding="utf-8") as f:
|
|
34
|
+
subprocess.run(cmd, shell=True, check=True, cwd=work_dir, stdout=f, stderr=f)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def build_single(settings: TexBuildSettings):
|
|
38
|
+
logger.info("Building source: %s", settings.source)
|
|
39
|
+
|
|
40
|
+
# Make a working dir
|
|
41
|
+
work_dir = settings.build_dir / settings.source.stem
|
|
42
|
+
os.makedirs(work_dir, exist_ok=True)
|
|
43
|
+
|
|
44
|
+
build_pdf(settings, work_dir)
|
|
45
|
+
|
|
46
|
+
tex_path = work_dir / settings.source.name
|
|
47
|
+
pdf_path = tex_path.parent / f"{tex_path.stem}.pdf"
|
|
48
|
+
if settings.output_png:
|
|
49
|
+
pdf_to_png(pdf_path)
|
|
50
|
+
|
|
51
|
+
# If output dir is different to build dir copy final content there
|
|
52
|
+
if settings.output_dir and settings.output_dir != work_dir:
|
|
53
|
+
os.makedirs(settings.output_dir, exist_ok=True)
|
|
54
|
+
shutil.copy(pdf_path, settings.output_dir)
|
|
55
|
+
|
|
56
|
+
if settings.output_png:
|
|
57
|
+
png_path = pdf_path.parent / f"{tex_path.stem}.png"
|
|
58
|
+
shutil.copy(png_path, settings.output_dir)
|
|
59
|
+
|
|
60
|
+
if settings.clear_workdir:
|
|
61
|
+
shutil.rmtree(work_dir)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def build(settings: TexBuildSettings):
|
|
65
|
+
if settings.source.is_dir():
|
|
66
|
+
for tex_file in settings.source.glob("*.tex"):
|
|
67
|
+
single_build = TexBuildSettings(
|
|
68
|
+
tex_file,
|
|
69
|
+
settings.build_dir,
|
|
70
|
+
settings.output_dir,
|
|
71
|
+
settings.build_engine,
|
|
72
|
+
)
|
|
73
|
+
build_single(single_build)
|
|
74
|
+
else:
|
|
75
|
+
build_single(settings)
|
|
76
|
+
|
|
77
|
+
logger.info("Finished building sources")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .trace import * # NOQA
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: icplot
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.12
|
|
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.1.
|
|
19
|
+
Requires-Dist: iccore==0.1.5
|
|
20
20
|
Provides-Extra: test
|
|
21
21
|
Requires-Dist: pytest; extra == "test"
|
|
22
22
|
Requires-Dist: pytest-cov; extra == "test"
|
|
@@ -62,6 +62,12 @@ To covert between image formats you can do:
|
|
|
62
62
|
icplot convert --source my_image.svg --target my_image.png
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
To render a Tex tikz image as pdf and png you can do:
|
|
66
|
+
|
|
67
|
+
``` shell
|
|
68
|
+
icplot convert --source my_tikz.tex
|
|
69
|
+
```
|
|
70
|
+
|
|
65
71
|
# Copyright #
|
|
66
72
|
|
|
67
73
|
Copyright 2024 Irish Centre for High End Computing
|