icplot 0.0.10__tar.gz → 0.0.11__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 (40) hide show
  1. {icplot-0.0.10/src/icplot.egg-info → icplot-0.0.11}/PKG-INFO +8 -2
  2. {icplot-0.0.10 → icplot-0.0.11}/README.md +6 -0
  3. {icplot-0.0.10 → icplot-0.0.11}/pyproject.toml +2 -2
  4. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/color.py +3 -6
  5. icplot-0.0.11/src/icplot/graph/axis.py +33 -0
  6. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/graph/matplotlib.py +3 -3
  7. icplot-0.0.11/src/icplot/graph/plot.py +64 -0
  8. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/graph/plotter.py +6 -5
  9. icplot-0.0.11/src/icplot/graph/series.py +43 -0
  10. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/main_cli.py +48 -8
  11. icplot-0.0.11/src/icplot/tex_interface.py +83 -0
  12. {icplot-0.0.10 → icplot-0.0.11/src/icplot.egg-info}/PKG-INFO +8 -2
  13. {icplot-0.0.10 → icplot-0.0.11}/src/icplot.egg-info/requires.txt +1 -1
  14. icplot-0.0.11/test/test_line_plots.py +32 -0
  15. icplot-0.0.11/test/test_tex_interface.py +24 -0
  16. icplot-0.0.10/src/icplot/graph/axis.py +0 -44
  17. icplot-0.0.10/src/icplot/graph/plot.py +0 -92
  18. icplot-0.0.10/src/icplot/graph/series.py +0 -111
  19. icplot-0.0.10/src/icplot/tex_interface.py +0 -92
  20. icplot-0.0.10/test/test_line_plots.py +0 -29
  21. icplot-0.0.10/test/test_tex_interface.py +0 -18
  22. {icplot-0.0.10 → icplot-0.0.11}/LICENSE +0 -0
  23. {icplot-0.0.10 → icplot-0.0.11}/setup.cfg +0 -0
  24. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/__init__.py +0 -0
  25. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/cairo_interface.py +0 -0
  26. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/gantt/__init__.py +0 -0
  27. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/gantt/gantt.py +0 -0
  28. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/gantt/gantt_renderer.py +0 -0
  29. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/gantt/pgfgantt_interface.py +0 -0
  30. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/geometry.py +0 -0
  31. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/graph/__init__.py +0 -0
  32. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/image_utils.py +0 -0
  33. {icplot-0.0.10 → icplot-0.0.11}/src/icplot/py.typed +0 -0
  34. {icplot-0.0.10 → icplot-0.0.11}/src/icplot.egg-info/SOURCES.txt +0 -0
  35. {icplot-0.0.10 → icplot-0.0.11}/src/icplot.egg-info/dependency_links.txt +0 -0
  36. {icplot-0.0.10 → icplot-0.0.11}/src/icplot.egg-info/entry_points.txt +0 -0
  37. {icplot-0.0.10 → icplot-0.0.11}/src/icplot.egg-info/top_level.txt +0 -0
  38. {icplot-0.0.10 → icplot-0.0.11}/test/test_cairo_interface.py +0 -0
  39. {icplot-0.0.10 → icplot-0.0.11}/test/test_gantt_chart.py +0 -0
  40. {icplot-0.0.10 → icplot-0.0.11}/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.10
3
+ Version: 0.0.11
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.0
19
+ Requires-Dist: iccore==0.1.1
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.10"
3
+ version = "0.0.11"
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.0"]
21
+ "iccore==0.1.1"]
22
22
 
23
23
  [project.urls]
24
24
  Repository = "https://git.ichec.ie/performance/toolshed/icplot"
@@ -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,33 @@
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
+ def get_resolved_ticks(self) -> list:
31
+ if self.ticks:
32
+ return self.ticks.eval()
33
+ 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="viridis"):
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):
@@ -50,12 +50,12 @@ class MatplotlibPlotter(Plotter):
50
50
  series.x,
51
51
  series.y,
52
52
  label=series.label,
53
- color=series.get_color().as_list(),
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.get_color().as_list())
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,64 @@
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
+ def add_series(self, series: PlotSeries):
26
+ self.series.append(series)
27
+
28
+
29
+ class GridPlot(Plot):
30
+ """
31
+ Make a grid of plots
32
+ """
33
+
34
+ stride: int = 4
35
+ size: tuple = (25, 20)
36
+ data: list = []
37
+
38
+ def get_series_indices(self, num_samples: int = 0):
39
+ rows = num_samples // self.stride
40
+ cols = num_samples // rows
41
+ len_data = len(self.data)
42
+
43
+ if num_samples == 0:
44
+ indices = list(range(0, len_data))
45
+ else:
46
+ indices = [random.randint(0, len_data - 1) for _ in range(num_samples)]
47
+ return rows, cols, indices
48
+
49
+ def get_subplots(self, num_samples: int = 0):
50
+ rows, cols, indices = self.get_series_indices(num_samples)
51
+
52
+ subplots = []
53
+ count = 1
54
+ for index in indices:
55
+ if num_samples > 0 and count == num_samples + 1:
56
+ break
57
+ if isinstance(self.data[index], list):
58
+ for series in self.data[index]:
59
+ subplots.append(series)
60
+ count += 1
61
+ else:
62
+ subplots.append(self.data[index])
63
+ count += 1
64
+ 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
- skipped_color = 0
14
+ non_highlight = []
15
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
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,23 @@
2
2
  import argparse
3
3
  import logging
4
4
  from pathlib import Path
5
+ import os
5
6
 
6
- from iccore import logging_utils
7
- from iccore import runtime
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.tex_interface import TexBuildSettings, TexInterface
10
14
 
11
15
  logger = logging.getLogger(__name__)
12
16
 
13
17
 
14
- def launch_common(args):
15
- runtime.ctx.set_is_dry_run(args.dry_run)
16
- logging_utils.setup_default_logger()
17
-
18
-
19
18
  def convert(args):
20
19
  launch_common(args)
21
20
 
22
- logger.info("Attempting coversion between %s %s", args.source, args.target)
21
+ logger.info("Startubg conversion between %s and %s", args.source, args.target)
23
22
 
24
23
  if args.target:
25
24
  target = Path(args.target).resolve()
@@ -36,9 +35,35 @@ def convert(args):
36
35
  svg_to_pdf(args.source.resolve(), target)
37
36
  else:
38
37
  svg_to_png(args.source)
38
+ elif args.source.suffix == ".tex":
39
+ if args.target:
40
+ target = Path(args.target).resolve()
41
+ else:
42
+ target = Path(os.getcwd())
43
+
44
+ settings = TexBuildSettings(
45
+ args.source.resolve(), args.build_dir.resolve(), args.target
46
+ )
47
+
48
+ tex = TexInterface()
49
+ tex.build(settings)
50
+
39
51
  logger.info("Finished conversion")
40
52
 
41
53
 
54
+ def plot(args):
55
+
56
+ launch_common(args)
57
+
58
+ config = read_yaml(args.config.resolve())
59
+
60
+ plotter = MatplotlibPlotter()
61
+ if ["plots"] in config:
62
+ for eachplot in config["plots"]:
63
+ plot = Plot(**eachplot)
64
+ plotter.plot(plot, args.output_dir.resolve())
65
+
66
+
42
67
  def main_cli():
43
68
  parser = argparse.ArgumentParser()
44
69
  parser.add_argument(
@@ -61,8 +86,23 @@ def main_cli():
61
86
  default="",
62
87
  help="Path to file to be converted to",
63
88
  )
89
+ convert_parser.add_argument(
90
+ "--build_dir",
91
+ default=Path(os.getcwd()) / "_build/tikz",
92
+ help="Path for build output",
93
+ )
64
94
  convert_parser.set_defaults(func=convert)
65
95
 
96
+ plot_parser = subparsers.add_parser("plot")
97
+ plot_parser.add_argument("--config", type=Path, help="Path to the plot config")
98
+ plot_parser.add_argument(
99
+ "--output_dir",
100
+ type=Path,
101
+ default=Path(os.getcwd()),
102
+ help="Path to the plot output directory",
103
+ )
104
+ plot_parser.set_defaults(func=plot)
105
+
66
106
  args = parser.parse_args()
67
107
  args.func(args)
68
108
 
@@ -0,0 +1,83 @@
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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: icplot
3
- Version: 0.0.10
3
+ Version: 0.0.11
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.0
19
+ Requires-Dist: iccore==0.1.1
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
@@ -1,6 +1,6 @@
1
1
  wand
2
2
  matplotlib
3
- iccore==0.1.0
3
+ iccore==0.1.1
4
4
 
5
5
  [cairo]
6
6
  pycairo
@@ -0,0 +1,32 @@
1
+ from pathlib import Path
2
+
3
+ from icplot.graph import Plot, LinePlotSeries
4
+ from icplot.graph.matplotlib import MatplotlibPlotter
5
+
6
+
7
+ def test_line_plot():
8
+
9
+ data = [([0, 5, 10], [1, 2, 3]), ([0, 5, 10], [3, 6, 9]), ([0, 5, 10], [4, 8, 12])]
10
+
11
+ series = []
12
+ for idx, [x, y] in enumerate(data):
13
+ series_config = {"label" : f"Series {idx}", "x" : x, "y" : y}
14
+ series.append(LinePlotSeries(**series_config))
15
+
16
+ plot_config = {"title": "test",
17
+ "legend_label" : "test_legend",
18
+ "series" : series,
19
+ "x_axis" : {"label" : "test_x",
20
+ "ticks" : {"lower" : 0,
21
+ "upper" : 10,
22
+ "step" : 5}}}
23
+
24
+ plot = Plot(**plot_config)
25
+
26
+ output_path = Path() / "output.svg"
27
+
28
+ plotter = MatplotlibPlotter()
29
+ plotter.plot(plot, output_path)
30
+
31
+ assert output_path.exists()
32
+ output_path.unlink()
@@ -0,0 +1,24 @@
1
+ import shutil
2
+ from pathlib import Path
3
+
4
+ from iccore.test_utils import get_test_data_dir, get_test_output_dir
5
+
6
+ from icplot.tex_interface import TexBuildSettings, TexInterface
7
+
8
+
9
+ def test_tex_interface():
10
+
11
+ data_dir = get_test_data_dir()
12
+ build_dir = get_test_output_dir()
13
+ source = data_dir / "test.tex"
14
+
15
+ settings = TexBuildSettings(source, build_dir, build_dir)
16
+
17
+ tex = TexInterface()
18
+
19
+ tex.build(settings)
20
+
21
+ assert Path(build_dir / "test.pdf").exists()
22
+ assert Path(build_dir / "test.png").exists()
23
+
24
+ shutil.rmtree(build_dir)
@@ -1,44 +0,0 @@
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 []
@@ -1,92 +0,0 @@
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
@@ -1,111 +0,0 @@
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,92 +0,0 @@
1
- import argparse
2
- import subprocess
3
- import shutil
4
- import os
5
- from pathlib import Path
6
- import logging
7
-
8
- from .image_utils import pdf_to_png
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
-
13
- class TexInterface:
14
- def __init__(self, build_dir: Path | None, output_dir: Path | None):
15
-
16
- if build_dir:
17
- self.build_dir: Path = build_dir.resolve()
18
- else:
19
- self.build_dir = Path()
20
- if not output_dir:
21
- self.output_dir: Path = self.build_dir
22
- else:
23
- self.output_dir = output_dir.resolve()
24
- self.build_engine = "pdflatex"
25
-
26
- def build_pdf(self, source_path: Path, build_dir: Path):
27
- cmd = f"{self.build_engine} {source_path}"
28
- subprocess.run(cmd, shell=True, check=True, cwd=build_dir)
29
-
30
- def build_single(self, source_path: Path):
31
- logging.info("Building source: %s", source_path)
32
-
33
- # Make a working dir
34
- work_dir = self.build_dir / source_path.stem
35
- os.makedirs(work_dir, exist_ok=True)
36
-
37
- tex_path = work_dir / source_path.name
38
- shutil.copy(source_path, tex_path)
39
-
40
- self.build_pdf(tex_path, work_dir)
41
-
42
- pdf_path = tex_path.parent / f"{tex_path.stem}.pdf"
43
- pdf_to_png(pdf_path)
44
-
45
- # If output dir is different to build dir copy final content there
46
- if self.output_dir != self.build_dir:
47
- png_path = pdf_path.parent / f"{tex_path.stem}.png"
48
- shutil.copy(pdf_path, self.output_dir)
49
- shutil.copy(png_path, self.output_dir)
50
-
51
- def build(self, search_path: Path):
52
-
53
- if not search_path.is_absolute():
54
- search_path = Path(os.getcwd()) / search_path
55
-
56
- os.makedirs(self.output_dir, exist_ok=True)
57
-
58
- if search_path.is_dir():
59
- for tex_file in search_path.glob("*.tex"):
60
- self.build_single(tex_file)
61
- else:
62
- self.build_single(search_path)
63
-
64
- logger.info("Finished building sources")
65
-
66
-
67
- if __name__ == "__main__":
68
-
69
- parser = argparse.ArgumentParser()
70
- parser.add_argument(
71
- "--source",
72
- type=Path,
73
- help="Path to the tikz source",
74
- )
75
- parser.add_argument(
76
- "--build_dir",
77
- type=Path,
78
- default=Path(os.getcwd()) / "_build/tikz",
79
- help="Path for build output",
80
- )
81
- parser.add_argument(
82
- "--output_dir",
83
- type=Path,
84
- default="",
85
- help="Optional dir to collect build output",
86
- )
87
-
88
- logging.basicConfig(encoding="utf-8", level=logging.INFO)
89
- args = parser.parse_args()
90
-
91
- tex = TexInterface(args.build_dir, args.output_dir)
92
- tex.build(args.source)
@@ -1,29 +0,0 @@
1
- from pathlib import Path
2
-
3
- from icplot.graph import Plot, LinePlotSeries
4
- from icplot.graph.matplotlib import MatplotlibPlotter
5
-
6
-
7
- def test_line_plot():
8
-
9
- plot = Plot(
10
- title="test",
11
- x_label="test_x",
12
- y_label="test_y",
13
- legend_label="test_legend"
14
- )
15
-
16
- data = [([0, 5, 10], [1, 2, 3]), ([0, 5, 10], [3, 6, 9]), ([0, 5, 10], [4, 8, 12])]
17
-
18
- for idx, [x, y] in enumerate(data):
19
- plot.add_series(LinePlotSeries(x, y, label=f"Series {idx}"))
20
-
21
- plot.set_x_ticks(0, 10, 5)
22
-
23
- output_path = Path() / "output.svg"
24
-
25
- plotter = MatplotlibPlotter()
26
- plotter.plot(plot, output_path)
27
-
28
- assert output_path.exists()
29
- output_path.unlink()
@@ -1,18 +0,0 @@
1
- import os
2
- import shutil
3
- from pathlib import Path
4
- from icplot.tex_interface import TexInterface
5
-
6
-
7
- def test_tex_interface():
8
-
9
- build_dir = Path(os.getcwd()) / "tmp_build"
10
- tex = TexInterface(build_dir, build_dir)
11
-
12
- data_dir = Path(__file__).parent / "data"
13
- tex_file = data_dir / "test.tex"
14
-
15
- tex = TexInterface(build_dir, build_dir)
16
- tex.build(tex_file)
17
-
18
- shutil.rmtree(build_dir)
File without changes
File without changes
File without changes
File without changes
File without changes