icplot 0.0.11__tar.gz → 0.0.13__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.11/src/icplot.egg-info → icplot-0.0.13}/PKG-INFO +2 -2
- {icplot-0.0.11 → icplot-0.0.13}/pyproject.toml +2 -2
- icplot-0.0.13/src/icplot/cairo_interface.py +59 -0
- icplot-0.0.13/src/icplot/gantt/gantt_renderer.py +59 -0
- icplot-0.0.13/src/icplot/gantt/pgfgantt.py +15 -0
- icplot-0.0.13/src/icplot/geometry.py +45 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot/graph/axis.py +2 -1
- icplot-0.0.13/src/icplot/graph/generator.py +84 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot/graph/matplotlib.py +2 -2
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot/graph/plot.py +1 -3
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot/main_cli.py +2 -3
- icplot-0.0.13/src/icplot/tex.py +77 -0
- icplot-0.0.13/src/icplot/trace/__init__.py +1 -0
- icplot-0.0.13/src/icplot/trace/trace.py +0 -0
- {icplot-0.0.11 → icplot-0.0.13/src/icplot.egg-info}/PKG-INFO +2 -2
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot.egg-info/SOURCES.txt +5 -2
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot.egg-info/requires.txt +1 -1
- icplot-0.0.13/test/test_cairo_interface.py +31 -0
- {icplot-0.0.11 → icplot-0.0.13}/test/test_tex_interface.py +2 -3
- icplot-0.0.11/src/icplot/cairo_interface.py +0 -60
- icplot-0.0.11/src/icplot/gantt/gantt_renderer.py +0 -59
- icplot-0.0.11/src/icplot/gantt/pgfgantt_interface.py +0 -26
- icplot-0.0.11/src/icplot/geometry.py +0 -50
- icplot-0.0.11/src/icplot/tex_interface.py +0 -83
- icplot-0.0.11/test/test_cairo_interface.py +0 -29
- {icplot-0.0.11 → icplot-0.0.13}/LICENSE +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/README.md +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/setup.cfg +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot/__init__.py +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot/color.py +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot/gantt/__init__.py +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot/gantt/gantt.py +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot/graph/__init__.py +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot/graph/plotter.py +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot/graph/series.py +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot/image_utils.py +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot/py.typed +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot.egg-info/dependency_links.txt +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot.egg-info/entry_points.txt +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/src/icplot.egg-info/top_level.txt +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/test/test_gantt_chart.py +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/test/test_image_utils.py +0 -0
- {icplot-0.0.11 → icplot-0.0.13}/test/test_line_plots.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.13
|
|
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"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "icplot"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.13"
|
|
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)
|
|
@@ -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,84 @@
|
|
|
1
|
+
from typing import Any, Callable
|
|
2
|
+
import copy
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from .plot import Plot
|
|
9
|
+
from .series import LinePlotSeries
|
|
10
|
+
from .matplotlib import MatplotlibPlotter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PlotConfig(BaseModel):
|
|
14
|
+
|
|
15
|
+
series_attr: str
|
|
16
|
+
x_attr: str
|
|
17
|
+
plots: dict[str, Plot]
|
|
18
|
+
colormap: Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def make_plots(
|
|
22
|
+
results: list[Any], config: PlotConfig, point_func: Callable
|
|
23
|
+
) -> list[Plot]:
|
|
24
|
+
"""
|
|
25
|
+
For a set of results and a given plot config generate a list of
|
|
26
|
+
plots.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# The instance attribute value for 'series_attr' is used to generate
|
|
30
|
+
# a set of labels, one per plot series
|
|
31
|
+
series_labels = set(getattr(r, config.series_attr) for r in results)
|
|
32
|
+
|
|
33
|
+
plots: list[Plot] = []
|
|
34
|
+
# Each 'y attribute' in the config corresponds to a 'plot' of this
|
|
35
|
+
# attribute's values.
|
|
36
|
+
for y_attr, plot_template in config.plots.items():
|
|
37
|
+
new_plot = copy.deepcopy(plot_template)
|
|
38
|
+
new_plot.name = y_attr
|
|
39
|
+
series = []
|
|
40
|
+
|
|
41
|
+
for label in series_labels:
|
|
42
|
+
# Get results for this series
|
|
43
|
+
label_results = [
|
|
44
|
+
r for r in results if getattr(r, config.series_attr) == label
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Use a user-provided 'point_func' to get the series 'x, y' points
|
|
48
|
+
# from the results, given a 'x_attr, y_attr' pair
|
|
49
|
+
points = [point_func(r, config.x_attr, y_attr) for r in label_results].sort(
|
|
50
|
+
key=lambda x: x[0]
|
|
51
|
+
)
|
|
52
|
+
x, y = zip(*points)
|
|
53
|
+
|
|
54
|
+
if label in config.colormap:
|
|
55
|
+
series.append(
|
|
56
|
+
LinePlotSeries(
|
|
57
|
+
x=x,
|
|
58
|
+
y=y,
|
|
59
|
+
label=label,
|
|
60
|
+
highlight=True,
|
|
61
|
+
color=config.colormap[label],
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
series.append(LinePlotSeries(x=x, y=y, label=label))
|
|
66
|
+
new_plot.series.extend(series)
|
|
67
|
+
plots.append(new_plot)
|
|
68
|
+
return plots
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def plot(
|
|
72
|
+
results: list[Any],
|
|
73
|
+
config: PlotConfig,
|
|
74
|
+
point_func: Callable,
|
|
75
|
+
output_dir: Path = Path(os.getcwd()),
|
|
76
|
+
plotter=MatplotlibPlotter(),
|
|
77
|
+
):
|
|
78
|
+
"""
|
|
79
|
+
For a given set of results and plot config generate the
|
|
80
|
+
corresponding plots and render them to files.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
for p in make_plots(results, config, point_func):
|
|
84
|
+
plotter.plot(p, output_dir / f"{p.name}.svg")
|
|
@@ -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
|
|
|
@@ -17,14 +17,12 @@ class Plot(BaseModel):
|
|
|
17
17
|
|
|
18
18
|
title: str = ""
|
|
19
19
|
legend_label: str = ""
|
|
20
|
+
name: str = ""
|
|
20
21
|
x_axis: PlotAxis = PlotAxis()
|
|
21
22
|
y_axis: PlotAxis = PlotAxis()
|
|
22
23
|
plot_type: str = ""
|
|
23
24
|
series: list[PlotSeries] = []
|
|
24
25
|
|
|
25
|
-
def add_series(self, series: PlotSeries):
|
|
26
|
-
self.series.append(series)
|
|
27
|
-
|
|
28
26
|
|
|
29
27
|
class GridPlot(Plot):
|
|
30
28
|
"""
|
|
@@ -10,7 +10,8 @@ from iccore.serialization import read_yaml
|
|
|
10
10
|
from icplot.graph import Plot
|
|
11
11
|
from icplot.graph.matplotlib import MatplotlibPlotter
|
|
12
12
|
from icplot.image_utils import pdf_to_png, svg_to_png, svg_to_pdf
|
|
13
|
-
from icplot
|
|
13
|
+
from icplot import tex
|
|
14
|
+
from icplot.tex import TexBuildSettings
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
16
17
|
|
|
@@ -44,8 +45,6 @@ def convert(args):
|
|
|
44
45
|
settings = TexBuildSettings(
|
|
45
46
|
args.source.resolve(), args.build_dir.resolve(), args.target
|
|
46
47
|
)
|
|
47
|
-
|
|
48
|
-
tex = TexInterface()
|
|
49
48
|
tex.build(settings)
|
|
50
49
|
|
|
51
50
|
logger.info("Finished conversion")
|
|
@@ -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.13
|
|
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"
|
|
@@ -9,7 +9,7 @@ src/icplot/geometry.py
|
|
|
9
9
|
src/icplot/image_utils.py
|
|
10
10
|
src/icplot/main_cli.py
|
|
11
11
|
src/icplot/py.typed
|
|
12
|
-
src/icplot/
|
|
12
|
+
src/icplot/tex.py
|
|
13
13
|
src/icplot.egg-info/PKG-INFO
|
|
14
14
|
src/icplot.egg-info/SOURCES.txt
|
|
15
15
|
src/icplot.egg-info/dependency_links.txt
|
|
@@ -19,13 +19,16 @@ src/icplot.egg-info/top_level.txt
|
|
|
19
19
|
src/icplot/gantt/__init__.py
|
|
20
20
|
src/icplot/gantt/gantt.py
|
|
21
21
|
src/icplot/gantt/gantt_renderer.py
|
|
22
|
-
src/icplot/gantt/
|
|
22
|
+
src/icplot/gantt/pgfgantt.py
|
|
23
23
|
src/icplot/graph/__init__.py
|
|
24
24
|
src/icplot/graph/axis.py
|
|
25
|
+
src/icplot/graph/generator.py
|
|
25
26
|
src/icplot/graph/matplotlib.py
|
|
26
27
|
src/icplot/graph/plot.py
|
|
27
28
|
src/icplot/graph/plotter.py
|
|
28
29
|
src/icplot/graph/series.py
|
|
30
|
+
src/icplot/trace/__init__.py
|
|
31
|
+
src/icplot/trace/trace.py
|
|
29
32
|
test/test_cairo_interface.py
|
|
30
33
|
test/test_gantt_chart.py
|
|
31
34
|
test/test_image_utils.py
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
4
|
+
from iccore.test_utils import get_test_output_dir
|
|
5
|
+
|
|
6
|
+
from icplot import cairo_interface
|
|
7
|
+
from icplot.geometry import Scene, Rectangle, TextPath
|
|
8
|
+
from icplot.color import Color
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_cairo_interface():
|
|
12
|
+
|
|
13
|
+
scene_items = []
|
|
14
|
+
rect = Rectangle(
|
|
15
|
+
w=20, h=20, location= (10, 10),
|
|
16
|
+
fill = Color.from_rgba(0.5, 0.5, 1, 0.5),
|
|
17
|
+
stroke = Color.from_rgba(0.5, 0.0, 0.0, 0.5)
|
|
18
|
+
)
|
|
19
|
+
scene_items.append(rect)
|
|
20
|
+
|
|
21
|
+
text = TextPath(content="Hello World",
|
|
22
|
+
location=(5,5))
|
|
23
|
+
scene_items.append(text)
|
|
24
|
+
|
|
25
|
+
output_dir = get_test_output_dir()
|
|
26
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
27
|
+
|
|
28
|
+
scene = Scene(items=scene_items)
|
|
29
|
+
cairo_interface.draw_svg(scene, output_dir / "output.svg")
|
|
30
|
+
shutil.rmtree(output_dir)
|
|
31
|
+
|
|
@@ -3,7 +3,8 @@ from pathlib import Path
|
|
|
3
3
|
|
|
4
4
|
from iccore.test_utils import get_test_data_dir, get_test_output_dir
|
|
5
5
|
|
|
6
|
-
from icplot
|
|
6
|
+
from icplot import tex
|
|
7
|
+
from icplot.tex import TexBuildSettings
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
def test_tex_interface():
|
|
@@ -14,8 +15,6 @@ def test_tex_interface():
|
|
|
14
15
|
|
|
15
16
|
settings = TexBuildSettings(source, build_dir, build_dir)
|
|
16
17
|
|
|
17
|
-
tex = TexInterface()
|
|
18
|
-
|
|
19
18
|
tex.build(settings)
|
|
20
19
|
|
|
21
20
|
assert Path(build_dir / "test.pdf").exists()
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
import cairo
|
|
4
|
-
|
|
5
|
-
from .geometry import Scene, Rectangle
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class CairoInterface:
|
|
9
|
-
|
|
10
|
-
def __init__(self):
|
|
11
|
-
pass
|
|
12
|
-
|
|
13
|
-
def draw_rect(self, cr, rect: Rectangle):
|
|
14
|
-
x0, y0 = rect.location
|
|
15
|
-
x1 = rect.w
|
|
16
|
-
y1 = rect.h
|
|
17
|
-
cr.move_to(x0, y0)
|
|
18
|
-
cr.line_to(x1, y0)
|
|
19
|
-
cr.line_to(x1, y1)
|
|
20
|
-
cr.line_to(x0, y1)
|
|
21
|
-
cr.close_path()
|
|
22
|
-
|
|
23
|
-
def draw_shape(self, cr, shape):
|
|
24
|
-
cr.save()
|
|
25
|
-
if shape.shape_type == "rect":
|
|
26
|
-
self.draw_rect(cr, shape)
|
|
27
|
-
else:
|
|
28
|
-
return
|
|
29
|
-
cr.set_source_rgba(shape.fill.r, shape.fill.g, shape.fill.b, shape.fill.a)
|
|
30
|
-
cr.fill_preserve()
|
|
31
|
-
if shape.stroke is not None:
|
|
32
|
-
cr.set_source_rgba(
|
|
33
|
-
shape.stroke.r, shape.stroke.g, shape.stroke.b, shape.stroke.a
|
|
34
|
-
)
|
|
35
|
-
cr.set_line_width(shape.stroke_thickness)
|
|
36
|
-
cr.stroke()
|
|
37
|
-
|
|
38
|
-
cr.restore()
|
|
39
|
-
|
|
40
|
-
def draw_text(self, cr, text):
|
|
41
|
-
cr.save()
|
|
42
|
-
|
|
43
|
-
cr.select_font_face(text.font.family)
|
|
44
|
-
cr.set_font_size(text.font.size)
|
|
45
|
-
cr.move_to(text.location[0], text.location[1])
|
|
46
|
-
cr.show_text(text.content)
|
|
47
|
-
|
|
48
|
-
cr.restore()
|
|
49
|
-
|
|
50
|
-
def draw_scene(self, cr, scene: Scene):
|
|
51
|
-
for item in scene.items:
|
|
52
|
-
if item.item_type == "shape":
|
|
53
|
-
self.draw_shape(cr, item)
|
|
54
|
-
elif item.item_type == "text":
|
|
55
|
-
self.draw_text(cr, item)
|
|
56
|
-
|
|
57
|
-
def draw_svg(self, scene: Scene, path: Path):
|
|
58
|
-
with cairo.SVGSurface(path, scene.size[0], scene.size[1]) as surface:
|
|
59
|
-
cr = cairo.Context(surface)
|
|
60
|
-
self.draw_scene(cr, scene)
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
from iccore.project import Milestone
|
|
2
|
-
from icplot.geometry import Scene, Rectangle
|
|
3
|
-
|
|
4
|
-
from .gantt import GanttChart
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class GanttRenderer:
|
|
8
|
-
|
|
9
|
-
def _add_milestone(
|
|
10
|
-
self, gantt: GanttChart, scene: Scene, milestone: Milestone, chart_range, yloc
|
|
11
|
-
):
|
|
12
|
-
# chart_delta = chart_range[1] - chart_range[0]
|
|
13
|
-
chart_delta = 0
|
|
14
|
-
start_delta = milestone.start_date - chart_range[0]
|
|
15
|
-
# milestone_delta = milestone.due_date - milestone.start_date
|
|
16
|
-
milestone_delta = 0
|
|
17
|
-
|
|
18
|
-
start_frac = float(start_delta / chart_delta)
|
|
19
|
-
milestone_frac = float(milestone_delta / chart_delta)
|
|
20
|
-
|
|
21
|
-
w = milestone_frac * gantt.width
|
|
22
|
-
h = gantt.bar_max_height * gantt.height
|
|
23
|
-
x = start_frac * gantt.width
|
|
24
|
-
y = yloc
|
|
25
|
-
|
|
26
|
-
rect = Rectangle(w, h)
|
|
27
|
-
rect.location = (x, y)
|
|
28
|
-
rect.fill = gantt.bar_color
|
|
29
|
-
scene.items.append(rect)
|
|
30
|
-
|
|
31
|
-
def _add_milestones(self, gantt: GanttChart, scene: Scene):
|
|
32
|
-
|
|
33
|
-
if not gantt.milestones:
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
milestones = gantt.milestones
|
|
37
|
-
# milestones.sort(key=lambda x: x.start_date, reverse=True)
|
|
38
|
-
|
|
39
|
-
start_date = gantt.start_date
|
|
40
|
-
end_date = gantt.end_date
|
|
41
|
-
if not start_date:
|
|
42
|
-
start_date = milestones[0].start_date
|
|
43
|
-
|
|
44
|
-
# if not end_date:
|
|
45
|
-
# end_date = max(m.due_date for m in milestones)
|
|
46
|
-
|
|
47
|
-
chart_range = (start_date, end_date)
|
|
48
|
-
yloc = 0.0
|
|
49
|
-
bar_height = gantt.bar_max_height * gantt.height
|
|
50
|
-
for milestone in milestones:
|
|
51
|
-
self._add_milestone(gantt, scene, milestone, chart_range, yloc)
|
|
52
|
-
yloc += bar_height
|
|
53
|
-
|
|
54
|
-
def render(self, gantt: GanttChart) -> Scene:
|
|
55
|
-
scene = Scene()
|
|
56
|
-
|
|
57
|
-
self._add_milestones(gantt, scene)
|
|
58
|
-
|
|
59
|
-
return scene
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
from .gantt import GanttChart
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class PgfGanttRenderer:
|
|
7
|
-
|
|
8
|
-
def __init__(self):
|
|
9
|
-
self.template: str = ""
|
|
10
|
-
|
|
11
|
-
def _load_template(self):
|
|
12
|
-
if self.template:
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
with open(Path(__file__).parent / "pgfgantt.tex", "r", encoding="utf-8") as f:
|
|
16
|
-
self.template = f.read()
|
|
17
|
-
|
|
18
|
-
def render(self, gantt: GanttChart) -> str:
|
|
19
|
-
|
|
20
|
-
output = self.template
|
|
21
|
-
|
|
22
|
-
if gantt.title:
|
|
23
|
-
title_str = f"\\title{{{gantt.title}}}"
|
|
24
|
-
output.replace("%%TITLE%%", title_str)
|
|
25
|
-
|
|
26
|
-
return output
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
from .color import Color
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class Font:
|
|
5
|
-
|
|
6
|
-
def __init__(self, family="Sans"):
|
|
7
|
-
self.family = family
|
|
8
|
-
self.weight = "normal"
|
|
9
|
-
self.slant = "normal"
|
|
10
|
-
self.size = 0.5
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class SceneItem:
|
|
14
|
-
|
|
15
|
-
def __init__(self, item_type):
|
|
16
|
-
self.location = (0, 0)
|
|
17
|
-
self.item_type = item_type
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class Shape(SceneItem):
|
|
21
|
-
|
|
22
|
-
def __init__(self, shape_type):
|
|
23
|
-
super().__init__("shape")
|
|
24
|
-
self.shape_type = shape_type
|
|
25
|
-
self.fill = Color()
|
|
26
|
-
self.stroke = None
|
|
27
|
-
self.stroke_thickness = 0.5
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class TextPath(SceneItem):
|
|
31
|
-
|
|
32
|
-
def __init__(self, content):
|
|
33
|
-
super().__init__("text")
|
|
34
|
-
self.content = content
|
|
35
|
-
self.font = Font()
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class Rectangle(Shape):
|
|
39
|
-
|
|
40
|
-
def __init__(self, w=1.0, h=1.0):
|
|
41
|
-
super().__init__("rect")
|
|
42
|
-
self.w = w
|
|
43
|
-
self.h = h
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class Scene:
|
|
47
|
-
|
|
48
|
-
def __init__(self):
|
|
49
|
-
self.items = []
|
|
50
|
-
self.size = (100, 100)
|
|
@@ -1,83 +0,0 @@
|
|
|
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
|
-
class TexInterface:
|
|
31
|
-
"""
|
|
32
|
-
This is an interface to Tex for building Tex doucments.
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
def build_pdf(self, settings: TexBuildSettings, work_dir: Path):
|
|
36
|
-
|
|
37
|
-
cmd = f"{settings.build_engine} {settings.source}"
|
|
38
|
-
|
|
39
|
-
with open(work_dir / "stdout.txt", "w", encoding="utf-8") as f:
|
|
40
|
-
subprocess.run(
|
|
41
|
-
cmd, shell=True, check=True, cwd=work_dir, stdout=f, stderr=f
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
def build_single(self, settings: TexBuildSettings):
|
|
45
|
-
logger.info("Building source: %s", settings.source)
|
|
46
|
-
|
|
47
|
-
# Make a working dir
|
|
48
|
-
work_dir = settings.build_dir / settings.source.stem
|
|
49
|
-
os.makedirs(work_dir, exist_ok=True)
|
|
50
|
-
|
|
51
|
-
self.build_pdf(settings, work_dir)
|
|
52
|
-
|
|
53
|
-
tex_path = work_dir / settings.source.name
|
|
54
|
-
pdf_path = tex_path.parent / f"{tex_path.stem}.pdf"
|
|
55
|
-
if settings.output_png:
|
|
56
|
-
pdf_to_png(pdf_path)
|
|
57
|
-
|
|
58
|
-
# If output dir is different to build dir copy final content there
|
|
59
|
-
if settings.output_dir and settings.output_dir != work_dir:
|
|
60
|
-
os.makedirs(settings.output_dir, exist_ok=True)
|
|
61
|
-
shutil.copy(pdf_path, settings.output_dir)
|
|
62
|
-
|
|
63
|
-
if settings.output_png:
|
|
64
|
-
png_path = pdf_path.parent / f"{tex_path.stem}.png"
|
|
65
|
-
shutil.copy(png_path, settings.output_dir)
|
|
66
|
-
|
|
67
|
-
if settings.clear_workdir:
|
|
68
|
-
shutil.rmtree(work_dir)
|
|
69
|
-
|
|
70
|
-
def build(self, settings: TexBuildSettings):
|
|
71
|
-
if settings.source.is_dir():
|
|
72
|
-
for tex_file in settings.source.glob("*.tex"):
|
|
73
|
-
single_build = TexBuildSettings(
|
|
74
|
-
tex_file,
|
|
75
|
-
settings.build_dir,
|
|
76
|
-
settings.output_dir,
|
|
77
|
-
settings.build_engine,
|
|
78
|
-
)
|
|
79
|
-
self.build_single(single_build)
|
|
80
|
-
else:
|
|
81
|
-
self.build_single(settings)
|
|
82
|
-
|
|
83
|
-
logger.info("Finished building sources")
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
import os
|
|
3
|
-
import shutil
|
|
4
|
-
|
|
5
|
-
from icplot.cairo_interface import CairoInterface
|
|
6
|
-
from icplot.geometry import Scene, Rectangle, TextPath
|
|
7
|
-
from icplot.color import Color
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def test_cairo_interface():
|
|
11
|
-
|
|
12
|
-
cairo_interface = CairoInterface()
|
|
13
|
-
scene = Scene()
|
|
14
|
-
|
|
15
|
-
rect = Rectangle(20, 20)
|
|
16
|
-
rect.location = (10, 10)
|
|
17
|
-
rect.fill = Color.from_rgba(0.5, 0.5, 1, 0.5)
|
|
18
|
-
rect.stroke = Color.from_rgba(0.5, 0.0, 0.0, 0.5)
|
|
19
|
-
scene.items.append(rect)
|
|
20
|
-
|
|
21
|
-
text = TextPath("Hello World")
|
|
22
|
-
text.location = (5, 5)
|
|
23
|
-
scene.items.append(text)
|
|
24
|
-
|
|
25
|
-
output_dir = Path(os.getcwd()) / "test_cairo_interface"
|
|
26
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
27
|
-
cairo_interface.draw_svg(scene, output_dir / "output.svg")
|
|
28
|
-
shutil.rmtree(output_dir)
|
|
29
|
-
|
|
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
|