icplot 0.4.0__tar.gz → 0.5.0__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.
Files changed (87) hide show
  1. icplot-0.5.0/LICENSE +21 -0
  2. {icplot-0.4.0/src/icplot.egg-info → icplot-0.5.0}/PKG-INFO +16 -17
  3. {icplot-0.4.0 → icplot-0.5.0}/README.md +10 -2
  4. icplot-0.5.0/pyproject.toml +124 -0
  5. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/charts/tree.py +1 -1
  6. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/converter/image.py +14 -9
  7. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/mesh/mesh.py +0 -1
  8. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/mesh/openfoam/blockmesh.py +3 -4
  9. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/mesh/openfoam/foamfile.py +1 -1
  10. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/mesh/openfoam/polymesh.py +4 -1
  11. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/mesh/shapes.py +0 -2
  12. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/mesh/vertex.py +0 -2
  13. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/shapes.py +0 -2
  14. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/graph/plot_group.py +0 -1
  15. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/graph/series.py +0 -3
  16. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/charts/pgfgantt.py +1 -1
  17. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/formats/mermaid.py +5 -4
  18. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/formats/tex.py +7 -4
  19. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/graph/matplotlib.py +15 -6
  20. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/graph/mpl.py +3 -1
  21. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/graph/renderer.py +9 -5
  22. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/render.py +0 -1
  23. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/theme/color.py +1 -1
  24. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/theme/theme_generator.py +2 -0
  25. {icplot-0.4.0 → icplot-0.5.0/src/icplot.egg-info}/PKG-INFO +16 -17
  26. {icplot-0.4.0 → icplot-0.5.0}/src/icplot.egg-info/requires.txt +0 -11
  27. {icplot-0.4.0 → icplot-0.5.0}/test/test_image.py +0 -1
  28. icplot-0.4.0/LICENSE +0 -232
  29. icplot-0.4.0/pyproject.toml +0 -197
  30. {icplot-0.4.0 → icplot-0.5.0}/setup.cfg +0 -0
  31. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/__init__.py +0 -0
  32. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/charts/__init__.py +0 -0
  33. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/charts/gantt.py +0 -0
  34. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/converter/__init__.py +0 -0
  35. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/converter/cli.py +0 -0
  36. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/converter/convert.py +0 -0
  37. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/__init__.py +0 -0
  38. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/base.py +0 -0
  39. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/mesh/__init__.py +0 -0
  40. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/mesh/cell.py +0 -0
  41. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/mesh/edge.py +0 -0
  42. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/mesh/openfoam/__init__.py +0 -0
  43. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/mesh/operations.py +0 -0
  44. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/mesh/vtk.py +0 -0
  45. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/operations.py +0 -0
  46. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/geometry/transform.py +0 -0
  47. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/graph/__init__.py +0 -0
  48. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/graph/axis.py +0 -0
  49. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/graph/plot.py +0 -0
  50. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/main_cli.py +0 -0
  51. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/py.typed +0 -0
  52. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/__init__.py +0 -0
  53. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/charts/svggantt.py +0 -0
  54. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/cli.py +0 -0
  55. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/config.py +0 -0
  56. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/formats/__init__.py +0 -0
  57. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/formats/plantuml.py +0 -0
  58. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/frontmatter.py +0 -0
  59. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/graph/__init__.py +0 -0
  60. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/graph/color.py +0 -0
  61. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/graph/vtk.py +0 -0
  62. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/scene/cairo.py +0 -0
  63. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/renderer/video.py +0 -0
  64. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/scene.py +0 -0
  65. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/theme/__init__.py +0 -0
  66. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/theme/cli.py +0 -0
  67. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/theme/templates/puml.j2 +0 -0
  68. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/theme/templates/tikz.j2 +0 -0
  69. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/theme/theme.json +0 -0
  70. {icplot-0.4.0 → icplot-0.5.0}/src/icplot/theme/theme.py +0 -0
  71. {icplot-0.4.0 → icplot-0.5.0}/src/icplot.egg-info/SOURCES.txt +0 -0
  72. {icplot-0.4.0 → icplot-0.5.0}/src/icplot.egg-info/dependency_links.txt +0 -0
  73. {icplot-0.4.0 → icplot-0.5.0}/src/icplot.egg-info/entry_points.txt +0 -0
  74. {icplot-0.4.0 → icplot-0.5.0}/src/icplot.egg-info/top_level.txt +0 -0
  75. {icplot-0.4.0 → icplot-0.5.0}/test/test_cairo.py +0 -0
  76. {icplot-0.4.0 → icplot-0.5.0}/test/test_frontmatter.py +0 -0
  77. {icplot-0.4.0 → icplot-0.5.0}/test/test_gantt_chart.py +0 -0
  78. {icplot-0.4.0 → icplot-0.5.0}/test/test_geometry.py +0 -0
  79. {icplot-0.4.0 → icplot-0.5.0}/test/test_line_plots.py +0 -0
  80. {icplot-0.4.0 → icplot-0.5.0}/test/test_mermaid.py +0 -0
  81. {icplot-0.4.0 → icplot-0.5.0}/test/test_meshing.py +0 -0
  82. {icplot-0.4.0 → icplot-0.5.0}/test/test_openfoam.py +0 -0
  83. {icplot-0.4.0 → icplot-0.5.0}/test/test_plot_group.py +0 -0
  84. {icplot-0.4.0 → icplot-0.5.0}/test/test_polymesh.py +0 -0
  85. {icplot-0.4.0 → icplot-0.5.0}/test/test_puml.py +0 -0
  86. {icplot-0.4.0 → icplot-0.5.0}/test/test_tex.py +0 -0
  87. {icplot-0.4.0 → icplot-0.5.0}/test/test_tree.py +0 -0
icplot-0.5.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Irish Centre for High End Computing
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,14 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: icplot
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Utilities for generating plots and graphics for technical reports.
5
- Author-email: "James Grogan, Irish Centre for High End Computing" <james.grogan@ichec.ie>
6
- Project-URL: Repository, https://git.ichec.ie/performance/toolshed/icplot
7
- Project-URL: Homepage, https://git.ichec.ie/performance/toolshed/icplot
5
+ Author: Irish Centre for High End Computing
6
+ License: MIT
7
+ Project-URL: Repository, https://git.ichec.ie/platform-engineering/modules/documentation/ichec-plot
8
+ Project-URL: Homepage, https://git.ichec.ie/platform-engineering/modules/documentation/ichec-plot
8
9
  Keywords: Publishing,Technical Reports,Graphics
9
10
  Classifier: Development Status :: 3 - Alpha
10
11
  Classifier: Programming Language :: Python :: 3
11
- Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
12
+ Classifier: License :: OSI Approved :: MIT License
12
13
  Classifier: Operating System :: OS Independent
13
14
  Classifier: Topic :: System :: Distributed Computing
14
15
  Requires-Python: >=3.10
@@ -17,16 +18,6 @@ License-File: LICENSE
17
18
  Requires-Dist: iccore==0.2.3
18
19
  Requires-Dist: graphviz
19
20
  Requires-Dist: Jinja2
20
- Provides-Extra: test
21
- Requires-Dist: pytest; extra == "test"
22
- Requires-Dist: pytest-cov; extra == "test"
23
- Requires-Dist: pytest-sugar; extra == "test"
24
- Requires-Dist: black; extra == "test"
25
- Requires-Dist: isort; extra == "test"
26
- Requires-Dist: mypy; extra == "test"
27
- Requires-Dist: flake8; extra == "test"
28
- Requires-Dist: pylint; extra == "test"
29
- Requires-Dist: types-PyYAML; extra == "test"
30
21
  Provides-Extra: cairo
31
22
  Requires-Dist: CairoSVG; extra == "cairo"
32
23
  Provides-Extra: scene
@@ -55,6 +46,14 @@ Dynamic: license-file
55
46
 
56
47
  This is a library used at [The Irish Centre for High End Computing (ICHEC)](https://www.ichec.ie) for generating plots, diagrams and other graphics for technical documents.
57
48
 
49
+ # Scope #
50
+
51
+ The library is **frozen to what its consumers use** — the `ichec-handbook`
52
+ build tool and `ichec-flow`. That means `converter`, `renderer`, `theme` and
53
+ `graph`: fixes and maintenance yes, new capability no (planning#136). The
54
+ `geometry`/`mesh` 3D and OpenFOAM code is still here but is scheduled to move
55
+ to a module of its own, and its optional-dependency weight goes with it.
56
+
58
57
  # Features #
59
58
 
60
59
  The project has three primary modules:
@@ -167,7 +166,7 @@ brew install imagemagick cairo
167
166
 
168
167
  # Copyright #
169
168
 
170
- Copyright 2024 Irish Centre for High End Computing
169
+ Copyright 2026 Irish Centre for High End Computing
171
170
 
172
- The software in this repository can be used under the conditions of the GPLv3+ license, which is available for reading in the accompanying LICENSE file.
171
+ The software in this repository can be used under the conditions of the MIT license, which is available for reading in the accompanying LICENSE file.
173
172
 
@@ -2,6 +2,14 @@
2
2
 
3
3
  This is a library used at [The Irish Centre for High End Computing (ICHEC)](https://www.ichec.ie) for generating plots, diagrams and other graphics for technical documents.
4
4
 
5
+ # Scope #
6
+
7
+ The library is **frozen to what its consumers use** — the `ichec-handbook`
8
+ build tool and `ichec-flow`. That means `converter`, `renderer`, `theme` and
9
+ `graph`: fixes and maintenance yes, new capability no (planning#136). The
10
+ `geometry`/`mesh` 3D and OpenFOAM code is still here but is scheduled to move
11
+ to a module of its own, and its optional-dependency weight goes with it.
12
+
5
13
  # Features #
6
14
 
7
15
  The project has three primary modules:
@@ -114,7 +122,7 @@ brew install imagemagick cairo
114
122
 
115
123
  # Copyright #
116
124
 
117
- Copyright 2024 Irish Centre for High End Computing
125
+ Copyright 2026 Irish Centre for High End Computing
118
126
 
119
- The software in this repository can be used under the conditions of the GPLv3+ license, which is available for reading in the accompanying LICENSE file.
127
+ The software in this repository can be used under the conditions of the MIT license, which is available for reading in the accompanying LICENSE file.
120
128
 
@@ -0,0 +1,124 @@
1
+ [project]
2
+ name = "icplot"
3
+ dynamic = ["version"]
4
+ license = { text = "MIT" }
5
+ authors = [
6
+ { name="Irish Centre for High End Computing"},
7
+ ]
8
+ description = "Utilities for generating plots and graphics for technical reports."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Programming Language :: Python :: 3",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Operating System :: OS Independent",
16
+ "Topic :: System :: Distributed Computing"
17
+ ]
18
+ keywords = ["Publishing", "Technical Reports", "Graphics"]
19
+
20
+ # Core is deliberately light: only what every import path needs. The heavy
21
+ # rendering backends (matplotlib/vtk/scipy/moviepy/wand) are optional extras
22
+ # so consumers that only use the converter or diagram renderer (e.g. the
23
+ # handbook build) can install a small footprint. Use icplot[all] for the
24
+ # previous behaviour of pulling every backend.
25
+ dependencies = ["iccore==0.2.3",
26
+ "graphviz",
27
+ "Jinja2"]
28
+
29
+ [project.urls]
30
+ Repository = "https://git.ichec.ie/platform-engineering/modules/documentation/ichec-plot"
31
+ Homepage = "https://git.ichec.ie/platform-engineering/modules/documentation/ichec-plot"
32
+
33
+ [project.optional-dependencies]
34
+ # svg/pdf conversion (converter.svg_to_png / svg_to_pdf). CairoSVG pulls
35
+ # cairocffi, which dlopens libcairo at runtime (no C build step needed).
36
+ cairo = [
37
+ "CairoSVG"
38
+ ]
39
+
40
+ # vector scene drawing (renderer.scene.cairo). pycairo (module `cairo`)
41
+ # builds against libcairo — needs the cairo headers + a C toolchain at install.
42
+ scene = ["pycairo"]
43
+
44
+ # raster conversion via ImageMagick bindings (converter.pdf_to_png);
45
+ # falls back to the `magick` CLI if wand is absent
46
+ imagemagick = ["wand"]
47
+
48
+ # 2D plotting backend (icplot.renderer.graph / icplot.charts)
49
+ plot = ["matplotlib"]
50
+
51
+ # 3D / mesh backend (icplot.geometry.mesh, vtk graph backend)
52
+ vtk = ["vtk"]
53
+
54
+ # geometry transforms (icplot.geometry.operations)
55
+ geometry = ["scipy"]
56
+
57
+ # video assembly (icplot.renderer.video)
58
+ video = ["moviepy"]
59
+
60
+ # convenience aggregate — every backend (pre-split behaviour)
61
+ all = [
62
+ "CairoSVG",
63
+ "pycairo",
64
+ "wand",
65
+ "matplotlib",
66
+ "vtk",
67
+ "scipy",
68
+ "moviepy"
69
+ ]
70
+
71
+ [build-system]
72
+ requires = ["setuptools>=64", "setuptools-scm>=8"]
73
+ build-backend = "setuptools.build_meta"
74
+
75
+ # Version derived from the git tag (setuptools-scm). fallback_version keeps
76
+ # non-release jobs building on the shallow CI checkout, which has no tags.
77
+ [tool.setuptools_scm]
78
+ fallback_version = "0.0.0"
79
+
80
+ # Development toolchain (uv-native, PEP 735). CI installs it with
81
+ # `uv sync --group dev`. Deliberately free of the rendering backends: the lint
82
+ # job runs in the standard slim image, and pycairo has no wheel there — it
83
+ # builds against libcairo headers. The test job adds the backends with
84
+ # `--extra all` in an image that has those headers.
85
+ [dependency-groups]
86
+ dev = [
87
+ "ruff",
88
+ "mypy",
89
+ "pytest",
90
+ "pytest-cov",
91
+ "pytest-sugar",
92
+ "types-PyYAML",
93
+ ]
94
+
95
+ [project.scripts]
96
+ icplot = "icplot.main_cli:main_cli"
97
+
98
+ [tool.setuptools.package-data]
99
+ "icplot" = ["py.typed", "controlDict", "theme/theme.json", "theme/templates/*.j2"]
100
+
101
+ [tool.setuptools.packages.find]
102
+ where = ["src"]
103
+
104
+ [tool.mypy]
105
+ ignore_missing_imports = true
106
+
107
+ [tool.ruff]
108
+ line-length = 88
109
+ target-version = "py310"
110
+ src = ["src", "test"]
111
+
112
+ [tool.ruff.lint]
113
+ # E/W pycodestyle, F pyflakes, I import-sort (was isort), N pep8-naming,
114
+ # UP pyupgrade, B bugbear. Extend deliberately, not by pulling in "ALL".
115
+ select = ["E", "W", "F", "I", "N", "UP", "B"]
116
+
117
+ [tool.ruff.lint.isort]
118
+ known-first-party = ["icplot"]
119
+
120
+ [tool.pytest.ini_options]
121
+ testpaths = ["test",]
122
+ log_cli = 1
123
+ log_cli_level = "debug"
124
+ addopts = "--cov=icplot --cov-report term --cov-fail-under=55 --cov-report xml:coverage.xml --cov-report html"
@@ -1,9 +1,9 @@
1
1
  import logging
2
2
  import os
3
3
  import shutil
4
+ from collections.abc import Callable
4
5
  from dataclasses import dataclass
5
6
  from pathlib import Path
6
- from typing import Callable
7
7
 
8
8
  import graphviz
9
9
 
@@ -45,19 +45,24 @@ def _get_out_filename(source: Path, target: Path | None, extension: str) -> Path
45
45
  return source.parent / f"{source.stem}.{extension}"
46
46
 
47
47
 
48
- # The order of ImageMagick arguments was changed so that `-alpha off`
49
- # is applied after loading the source file rather than before.
50
- # This ensures proper handling when converting PDFs/SVGs,
51
- # as some versions of ImageMagick ignore or misapply `-alpha off` if it
52
- # appears before the input file.
48
+ # `-alpha off` must come *after* the source file: some ImageMagick versions
49
+ # ignore or misapply it when it precedes the input.
53
50
  def _pdf_to_png(source: Path, target: Path | None = None, resolution: int = 300):
54
51
 
55
52
  outfile = _get_out_filename(source, target, "png")
56
53
  os.makedirs(outfile.parent, exist_ok=True)
57
- cmd = (
58
- f"magick -density {resolution} -background white {source} -alpha off {outfile}"
59
- )
60
- subprocess.run(cmd, shell=True, check=True)
54
+ cmd = [
55
+ "magick",
56
+ "-density",
57
+ str(resolution),
58
+ "-background",
59
+ "white",
60
+ str(source),
61
+ "-alpha",
62
+ "off",
63
+ str(outfile),
64
+ ]
65
+ subprocess.run(cmd, check=True)
61
66
 
62
67
 
63
68
  def pdf_to_png(source: Path, target: Path | None = None, resolution: int = 300):
@@ -27,7 +27,6 @@ class Patch:
27
27
 
28
28
  @dataclass(frozen=True)
29
29
  class Mesh:
30
-
31
30
  vertices: tuple[Vertex, ...]
32
31
  cells: tuple[Cell, ...]
33
32
  scale: float = 1.0
@@ -125,8 +125,7 @@ def generate_mesh(mesh: Mesh, case_dir: Path):
125
125
  if not control_dict.exists():
126
126
  shutil.copy(Path(__file__).parent / "controlDict", control_dict)
127
127
 
128
- cmd = f"blockMesh -case {case_dir}"
129
- subprocess.run(cmd, cwd=case_dir, shell=True, check=True)
128
+ cmd = ["blockMesh", "-case", str(case_dir)]
129
+ subprocess.run(cmd, cwd=case_dir, check=True)
130
130
 
131
- cmd = f"blockMesh -case {case_dir} -write-vtk"
132
- subprocess.run(cmd, cwd=case_dir, shell=True, check=True)
131
+ subprocess.run(cmd + ["-write-vtk"], cwd=case_dir, check=True)
@@ -114,5 +114,5 @@ def parse(content: str) -> FoamFile:
114
114
 
115
115
  def read_foamfile(path: Path) -> FoamFile:
116
116
 
117
- with open(path, "r", encoding="utf-8") as f:
117
+ with open(path, encoding="utf-8") as f:
118
118
  return parse(f.read())
@@ -53,7 +53,10 @@ class Polymesh:
53
53
  class PolymeshPatch:
54
54
  name: str
55
55
  faces: tuple[Face, ...]
56
- startFace: int
56
+ # Mirrors the field name in OpenFOAM's own polyMesh/boundary file, which
57
+ # this class both parses and writes back out — renaming it to snake_case
58
+ # would put the Python attribute out of step with the format on disk.
59
+ startFace: int # noqa: N815
57
60
  type: str = "patch"
58
61
 
59
62
  def has_face(self, face: Face) -> bool:
@@ -102,7 +102,6 @@ def _close_circle(inner_mesh: Mesh, outer_mesh: Mesh, num_circumferential: int)
102
102
  new_cells = []
103
103
  count = 0
104
104
  for idx in range(num_circumferential):
105
-
106
105
  next_id = idx + 1
107
106
  if next_id == num_circumferential:
108
107
  next_id = 0
@@ -317,7 +316,6 @@ def mesh_cuboids(
317
316
  for cuboid in cuboids:
318
317
  vert_ids = []
319
318
  for p in cuboid.points:
320
-
321
319
  if not verts:
322
320
  verts.append(Vertex.from_point(p, 0))
323
321
  vert_ids.append(0)
@@ -57,7 +57,6 @@ def find_closest(verts: list[Vertex], point: Point) -> int:
57
57
  min_id = -1
58
58
  min_distance = 0.0
59
59
  for idx, v in enumerate(verts):
60
-
61
60
  distance = v.get_distance(point)
62
61
 
63
62
  if idx == 0:
@@ -80,7 +79,6 @@ def find_closest_vertex(verts: tuple[Vertex, ...], v: Vertex) -> Vertex:
80
79
  min_id = -1
81
80
  min_distance = 0.0
82
81
  for idx, vert in enumerate(verts):
83
-
84
82
  distance = vert.get_vertex_distance(v)
85
83
 
86
84
  if idx == 0:
@@ -26,13 +26,11 @@ class Shape:
26
26
 
27
27
  @dataclass(frozen=True)
28
28
  class Circle(Shape):
29
-
30
29
  radius: float = 1.0
31
30
 
32
31
 
33
32
  @dataclass(frozen=True)
34
33
  class Annulus(Shape):
35
-
36
34
  outer_radius: float = 1.0
37
35
  inner_radius: float = 0.5
38
36
  angle: float = 360 # degrees
@@ -42,7 +42,6 @@ class LineConfig:
42
42
 
43
43
 
44
44
  class PlotGroupBase(BaseModel, frozen=True):
45
-
46
45
  start_datetime: datetime | None = None
47
46
  end_datetime: datetime | None = None
48
47
  video: VideoConfig | None = None
@@ -16,13 +16,11 @@ from icplot.theme.color import Color
16
16
 
17
17
 
18
18
  class PlotSeriesBase(BaseModel, frozen=True):
19
-
20
19
  min_value: float | None = None
21
20
  max_value: float | None = None
22
21
 
23
22
 
24
23
  class PlotSeriesCreate(PlotSeriesBase, frozen=True):
25
-
26
24
  measurement: str
27
25
  unit_name: str = ""
28
26
 
@@ -36,7 +34,6 @@ class PlotSeriesCreate(PlotSeriesBase, frozen=True):
36
34
 
37
35
 
38
36
  class PlotSeriesPublic(PlotSeriesBase, frozen=True):
39
-
40
37
  measurement: MeasurementPublicWithUnits
41
38
  unit: UnitPublic
42
39
  x: MeasurementPublicWithUnits | None
@@ -40,7 +40,7 @@ def _ganttbar(milestone) -> str:
40
40
 
41
41
  def render(gantt: GanttChart, theme: "Theme | None" = None) -> str:
42
42
 
43
- with open(Path(__file__).parent / "pgfgantt.tex", "r", encoding="utf-8") as f:
43
+ with open(Path(__file__).parent / "pgfgantt.tex", encoding="utf-8") as f:
44
44
  template = f.read()
45
45
 
46
46
  milestones = gantt.milestones
@@ -6,6 +6,7 @@ Mermaid must already be set up on the system, eg. via the npm ecosystem.
6
6
 
7
7
  import logging
8
8
  import os
9
+ import shlex
9
10
  import shutil
10
11
  import subprocess
11
12
  from pathlib import Path
@@ -39,9 +40,9 @@ def render(ctx: RenderContext):
39
40
  if not browser_config and (env_path := os.environ.get("MMDC_PUPPETEER_CONFIG")):
40
41
  browser_config = Path(env_path)
41
42
 
42
- cmd = f"mmdc -i '{ctx.source}' -o '{output_path}'"
43
+ cmd = ["mmdc", "-i", str(ctx.source), "-o", str(output_path)]
43
44
  if browser_config:
44
- cmd += f" -p '{browser_config}'"
45
- logger.debug("Command: %s", cmd)
46
- subprocess.run(cmd, shell=True, check=True)
45
+ cmd += ["-p", str(browser_config)]
46
+ logger.debug("Command: %s", shlex.join(cmd))
47
+ subprocess.run(cmd, check=True)
47
48
  logger.info("Done: %s", output_path.name)
@@ -5,6 +5,7 @@ This module has functionality for publishing Tex documents
5
5
  import hashlib
6
6
  import logging
7
7
  import os
8
+ import shlex
8
9
  import shutil
9
10
  import subprocess
10
11
  from pathlib import Path
@@ -38,12 +39,11 @@ def _run_commands(ctx: RenderContext, work_dir: Path):
38
39
  logger.debug("Engine: %s | work_dir: %s", ctx.config.tex.build_engine, work_dir)
39
40
 
40
41
  if ctx.config.tex.build_engine == "pdflatex":
41
- cmd = f"{ctx.config.tex.build_engine} {' '.join(extra_flags)} '{ctx.source}'"
42
- logger.info("Command: %s", cmd.strip())
42
+ cmd = [ctx.config.tex.build_engine, *extra_flags, str(ctx.source)]
43
+ logger.info("Command: %s", shlex.join(cmd))
43
44
  with open(work_dir / "stdout.txt", "w", encoding="utf-8") as f:
44
45
  subprocess.run(
45
46
  cmd,
46
- shell=True,
47
47
  check=True,
48
48
  cwd=work_dir,
49
49
  env=tex_env,
@@ -91,7 +91,10 @@ def render(ctx: RenderContext):
91
91
  ctx.config.tex.build_engine,
92
92
  )
93
93
 
94
- path_hash = hashlib.md5(str(ctx.source).encode()).hexdigest()[:8]
94
+ # Names a build directory, so collision resistance is all that is wanted.
95
+ path_hash = hashlib.md5(
96
+ str(ctx.source).encode(), usedforsecurity=False
97
+ ).hexdigest()[:8]
95
98
  work_dir = ctx.build_dir / f"{ctx.source.stem}_{path_hash}"
96
99
  work_dir.mkdir(exist_ok=True, parents=True)
97
100
 
@@ -2,9 +2,9 @@ import logging
2
2
  from pathlib import Path
3
3
  from typing import cast
4
4
 
5
- from icplot.graph.series import LinePlotSeries # NOQA
6
5
  from icplot.graph.series import (
7
6
  ImageSeries,
7
+ LinePlotSeries, # NOQA
8
8
  PlotSeries,
9
9
  ScatterPlotSeries,
10
10
  )
@@ -33,6 +33,11 @@ class MatplotlibColorMap(ColorMap):
33
33
  super().__init__(label, mpl.colormaps[label])
34
34
 
35
35
 
36
+ # Module-level singleton rather than a constructed default argument, which
37
+ # would be built once at import time and shared silently across every call.
38
+ _DEFAULT_COLORMAP = MatplotlibColorMap("gist_rainbow")
39
+
40
+
36
41
  def str_exact_values_formatter(x, pos):
37
42
  """
38
43
  When using log scale, axis ticks come out as scientific notation by default,
@@ -158,9 +163,10 @@ def _render(fig, path: Path | None = None):
158
163
  plt.switch_backend("Agg")
159
164
 
160
165
 
161
- def render(
162
- plot: Plot, path: Path | None = None, cmap=MatplotlibColorMap("gist_rainbow")
163
- ):
166
+ def render(plot: Plot, path: Path | None = None, cmap=None):
167
+
168
+ if cmap is None:
169
+ cmap = _DEFAULT_COLORMAP
164
170
 
165
171
  colors = get_series_colors(cmap, plot)
166
172
 
@@ -169,7 +175,8 @@ def render(
169
175
  if len(plot.y_axes) > 1:
170
176
  axs.append(ax.twinx())
171
177
 
172
- for series, color in zip(plot.series, colors):
178
+ # colors is derived from plot.series, so the two are the same length.
179
+ for series, color in zip(plot.series, colors, strict=True):
173
180
  _plot_series(axs, series, color)
174
181
 
175
182
  _set_decorations(axs, plot)
@@ -185,7 +192,9 @@ def render_grid(
185
192
  rows, cols, series = plot.get_subplots(num_samples)
186
193
  fig, axs = plt.subplots(rows, cols)
187
194
 
188
- for ax, series_item in zip(axs, series):
195
+ # strict=False keeps the existing behaviour: plt.subplots(rows, cols) can
196
+ # return more axes than there are series when the grid is not full.
197
+ for ax, series_item in zip(axs, series, strict=False):
189
198
  _plot_series(ax, series_item)
190
199
 
191
200
  _render(fig, path)
@@ -151,7 +151,9 @@ def plot_array(path: Path, series: PlotSeriesPublic, data_series: Series):
151
151
  raise RuntimeError("Attempted to plot series with missing data")
152
152
 
153
153
  for idx, (x, values) in enumerate(
154
- zip(data_series.get_array(data_series.x).data, array.data)
154
+ # strict=False preserves the existing zip-to-shortest behaviour; the
155
+ # x array and the measurement array are not guaranteed equal length.
156
+ zip(data_series.get_array(data_series.x).data, array.data, strict=False)
155
157
  ):
156
158
  fig, ax = plt.subplots()
157
159
 
@@ -1,6 +1,7 @@
1
1
  import os
2
+ from collections.abc import Callable
2
3
  from pathlib import Path
3
- from typing import Any, Callable
4
+ from typing import Any
4
5
 
5
6
  from iccore.data import Series
6
7
  from iccore.data.series import convert_units
@@ -16,7 +17,6 @@ from . import mpl, vtk
16
17
 
17
18
 
18
19
  class PlotConfig(BaseModel, frozen=True):
19
-
20
20
  series_attr: str
21
21
  x_attr: str
22
22
  plots: dict[str, Plot]
@@ -39,7 +39,6 @@ def make_plots(
39
39
  # Each 'y attribute' in the config corresponds to a 'plot' of this
40
40
  # attribute's values.
41
41
  for y_attr, plot_template in config.plots.items():
42
-
43
42
  series = []
44
43
  for label in series_labels:
45
44
  # Get results for this series
@@ -55,7 +54,7 @@ def make_plots(
55
54
  )
56
55
  if not points:
57
56
  continue
58
- x, y = [list(xy) for xy in zip(*points)]
57
+ x, y = [list(xy) for xy in zip(*points, strict=True)]
59
58
 
60
59
  if label in config.colormap:
61
60
  series.append(
@@ -79,13 +78,18 @@ def plot(
79
78
  results: list[Any],
80
79
  config: PlotConfig,
81
80
  point_func: Callable,
82
- output_dir: Path = Path(os.getcwd()),
81
+ output_dir: Path | None = None,
83
82
  ):
84
83
  """
85
84
  For a given set of results and plot config generate the
86
85
  corresponding plots and render them to files.
87
86
  """
88
87
 
88
+ # Resolved per call, not once at import: the previous default froze the
89
+ # working directory at the moment this module was first imported.
90
+ if output_dir is None:
91
+ output_dir = Path(os.getcwd())
92
+
89
93
  for p in make_plots(results, config, point_func):
90
94
  render(p, output_dir / f"{p.name}.svg")
91
95
 
@@ -114,7 +114,6 @@ def _setup_tasks(
114
114
 
115
115
  tasks = []
116
116
  for f in sources:
117
-
118
117
  ext = _get_extension(f)
119
118
 
120
119
  output_formats = (
@@ -3,7 +3,7 @@ This module handles colors
3
3
  """
4
4
 
5
5
  import re
6
- from typing import Callable
6
+ from collections.abc import Callable
7
7
 
8
8
  from pydantic import BaseModel, GetCoreSchemaHandler
9
9
  from pydantic_core import core_schema
@@ -100,6 +100,8 @@ def generate_theme(output_dir: Path, theme_path: Path, template_dir: Path) -> No
100
100
 
101
101
  env = Environment(
102
102
  loader=FileSystemLoader(str(template_dir)),
103
+ # Targets are PlantUML and TeX, not HTML — escaping would corrupt them.
104
+ autoescape=False,
103
105
  keep_trailing_newline=True,
104
106
  trim_blocks=True,
105
107
  lstrip_blocks=True,