icplot 0.0.9__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 (43) hide show
  1. {icplot-0.0.9/src/icplot.egg-info → icplot-0.0.11}/PKG-INFO +8 -2
  2. {icplot-0.0.9 → icplot-0.0.11}/README.md +6 -0
  3. {icplot-0.0.9 → icplot-0.0.11}/pyproject.toml +2 -2
  4. {icplot-0.0.9 → icplot-0.0.11}/src/icplot/cairo_interface.py +4 -24
  5. {icplot-0.0.9 → icplot-0.0.11}/src/icplot/color.py +16 -17
  6. icplot-0.0.11/src/icplot/gantt/__init__.py +1 -0
  7. icplot-0.0.11/src/icplot/gantt/gantt.py +19 -0
  8. icplot-0.0.11/src/icplot/gantt/gantt_renderer.py +59 -0
  9. icplot-0.0.11/src/icplot/gantt/pgfgantt_interface.py +26 -0
  10. icplot-0.0.11/src/icplot/graph/axis.py +33 -0
  11. {icplot-0.0.9 → icplot-0.0.11}/src/icplot/graph/matplotlib.py +5 -5
  12. icplot-0.0.11/src/icplot/graph/plot.py +64 -0
  13. {icplot-0.0.9 → icplot-0.0.11}/src/icplot/graph/plotter.py +6 -5
  14. icplot-0.0.11/src/icplot/graph/series.py +43 -0
  15. {icplot-0.0.9 → icplot-0.0.11}/src/icplot/image_utils.py +19 -4
  16. {icplot-0.0.9 → icplot-0.0.11}/src/icplot/main_cli.py +48 -8
  17. icplot-0.0.11/src/icplot/py.typed +0 -0
  18. icplot-0.0.11/src/icplot/tex_interface.py +83 -0
  19. {icplot-0.0.9 → icplot-0.0.11/src/icplot.egg-info}/PKG-INFO +8 -2
  20. {icplot-0.0.9 → icplot-0.0.11}/src/icplot.egg-info/SOURCES.txt +5 -2
  21. {icplot-0.0.9 → icplot-0.0.11}/src/icplot.egg-info/requires.txt +1 -1
  22. icplot-0.0.11/test/test_cairo_interface.py +29 -0
  23. {icplot-0.0.9 → icplot-0.0.11}/test/test_gantt_chart.py +0 -5
  24. icplot-0.0.11/test/test_line_plots.py +32 -0
  25. icplot-0.0.11/test/test_tex_interface.py +24 -0
  26. icplot-0.0.9/src/icplot/gantt.py +0 -64
  27. icplot-0.0.9/src/icplot/graph/axis.py +0 -44
  28. icplot-0.0.9/src/icplot/graph/plot.py +0 -92
  29. icplot-0.0.9/src/icplot/graph/series.py +0 -111
  30. icplot-0.0.9/src/icplot/project_elements.py +0 -4
  31. icplot-0.0.9/src/icplot/tex_interface.py +0 -92
  32. icplot-0.0.9/test/test_cairo_interface.py +0 -6
  33. icplot-0.0.9/test/test_line_plots.py +0 -29
  34. icplot-0.0.9/test/test_tex_interface.py +0 -18
  35. {icplot-0.0.9 → icplot-0.0.11}/LICENSE +0 -0
  36. {icplot-0.0.9 → icplot-0.0.11}/setup.cfg +0 -0
  37. {icplot-0.0.9 → icplot-0.0.11}/src/icplot/__init__.py +0 -0
  38. {icplot-0.0.9 → icplot-0.0.11}/src/icplot/geometry.py +0 -0
  39. {icplot-0.0.9 → icplot-0.0.11}/src/icplot/graph/__init__.py +0 -0
  40. {icplot-0.0.9 → icplot-0.0.11}/src/icplot.egg-info/dependency_links.txt +0 -0
  41. {icplot-0.0.9 → icplot-0.0.11}/src/icplot.egg-info/entry_points.txt +0 -0
  42. {icplot-0.0.9 → icplot-0.0.11}/src/icplot.egg-info/top_level.txt +0 -0
  43. {icplot-0.0.9 → 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.9
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.0.10
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.9"
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.0.10"]
21
+ "iccore==0.1.1"]
22
22
 
23
23
  [project.urls]
24
24
  Repository = "https://git.ichec.ie/performance/toolshed/icplot"
@@ -1,8 +1,8 @@
1
- import os
2
1
  from pathlib import Path
2
+
3
3
  import cairo
4
4
 
5
- from .geometry import Scene, Rectangle, Color, TextPath
5
+ from .geometry import Scene, Rectangle
6
6
 
7
7
 
8
8
  class CairoInterface:
@@ -52,29 +52,9 @@ class CairoInterface:
52
52
  if item.item_type == "shape":
53
53
  self.draw_shape(cr, item)
54
54
  elif item.item_type == "text":
55
- self.draw_text(cr, text)
55
+ self.draw_text(cr, item)
56
56
 
57
- def draw_svg(self, scene, path):
57
+ def draw_svg(self, scene: Scene, path: Path):
58
58
  with cairo.SVGSurface(path, scene.size[0], scene.size[1]) as surface:
59
59
  cr = cairo.Context(surface)
60
60
  self.draw_scene(cr, scene)
61
-
62
-
63
- if __name__ == "__main__":
64
-
65
- cairo_interface = CairoInterface()
66
-
67
- scene = Scene()
68
-
69
- rect = Rectangle(20, 20)
70
- rect.location = (10, 10)
71
- rect.fill = Color(0.5, 0.5, 1, 0.5)
72
- rect.stroke = Color(0.5, 0.0, 0.0, 0.5)
73
- scene.items.append(rect)
74
-
75
- text = TextPath("Hello World")
76
- text.location = (5, 5)
77
- scene.items.append(text)
78
-
79
- output_path = Path(os.getcwd()) / "output.svg"
80
- cairo_interface.draw_svg(scene, output_path)
@@ -4,26 +4,26 @@ This module handles colors
4
4
 
5
5
  from typing import Callable
6
6
 
7
- from iccore.serialization import Serializable
7
+ from pydantic import BaseModel
8
8
 
9
9
 
10
- class Color(Serializable):
10
+ class Color(BaseModel):
11
11
  """
12
12
  A color class, internal storage is rgba as float.
13
13
  """
14
14
 
15
- def __init__(self, r: float = 0.0, g: float = 0.0, b: float = 0.0, a: float = 1.0):
16
- self.r = r
17
- self.g = g
18
- self.b = b
19
- self.a = a
15
+ r: float = 0.0
16
+ g: float = 0.0
17
+ b: float = 0.0
18
+ a: float = 1.0
20
19
 
21
20
  @staticmethod
22
- def from_list(data: list):
23
- return Color(data[0], data[1], data[2])
21
+ def from_rgba(r: float = 0.0, b: float = 0.0, g: float = 0.0, a: float = 1.0):
22
+ return Color(**{"r": r, "g": g, "b": b, "a": a})
24
23
 
25
- def serialize(self):
26
- return {"r": self.r, "g": self.g, "b": self.b, "a": self.a}
24
+ @staticmethod
25
+ def from_list(data: list):
26
+ return Color(**{"r": data[0], "g": data[1], "b": data[2]})
27
27
 
28
28
  def as_list(self) -> list:
29
29
  return [self.r, self.g, self.b]
@@ -31,6 +31,9 @@ class Color(Serializable):
31
31
  def is_black(self) -> bool:
32
32
  return self.r == 0.0 and self.g == 0.0 and self.b == 0.0 and self.a == 1.0
33
33
 
34
+ def serialize(self) -> str:
35
+ return f"r: {self.r}, g: {self.g}, b: {self.b}, a: {self.a}"
36
+
34
37
 
35
38
  class ColorMap:
36
39
  """
@@ -40,15 +43,11 @@ class ColorMap:
40
43
  def __init__(self, label: str, data_func: Callable):
41
44
  self.label = label
42
45
  self.data_func = data_func
43
- self.start_offset = 0.25
44
- self.scale = 2
45
46
 
46
47
  def get_color(self, cursor: int, values: list) -> Color:
47
48
  """
48
49
  Returns a colour based on a cmap and how far across
49
50
  the datasets you are
50
51
  """
51
- position = cursor / len(values)
52
- return Color.from_list(
53
- self.data_func(self.start_offset + (position / self.scale))
54
- )
52
+ position = cursor / (len(values) - 1)
53
+ return Color.from_list(self.data_func(position))
@@ -0,0 +1 @@
1
+ from .gantt import * # NOQA
@@ -0,0 +1,19 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from iccore.project import Milestone
6
+
7
+ from icplot.color import Color
8
+
9
+
10
+ class GanttChart(BaseModel):
11
+
12
+ milestones: list[Milestone] = []
13
+ title: str = ""
14
+ start_date: datetime | None = None
15
+ end_date: datetime | None = None
16
+ bar_max_height: float = 0.1
17
+ height: int = 100
18
+ width: int = 500
19
+ bar_color: Color = Color.from_rgba(0.7, 0, 0)
@@ -0,0 +1,59 @@
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
@@ -0,0 +1,26 @@
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
@@ -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)
@@ -75,11 +75,11 @@ class MatplotlibPlotter(Plotter):
75
75
 
76
76
  fig, ax = plt.subplots()
77
77
 
78
- self.set_decorations(ax, plot)
79
-
80
78
  for series in plot.series:
81
79
  self.plot_series(ax, series)
82
80
 
81
+ self.set_decorations(ax, plot)
82
+
83
83
  self.render(fig, path)
84
84
 
85
85
  def plot_grid(self, plot: GridPlot, path: Path | None = None, num_samples: int = 0):
@@ -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"
@@ -1,3 +1,8 @@
1
+ """
2
+ This module has functions for converting between image
3
+ formats
4
+ """
5
+
1
6
  import logging
2
7
  from pathlib import Path
3
8
 
@@ -12,14 +17,16 @@ logger = logging.getLogger(__name__)
12
17
  def _get_out_filename(source: Path, target: Path | None, extension: str) -> Path:
13
18
  if target:
14
19
  return target
15
- else:
16
- return source.parent / f"{source.stem}.{extension}"
20
+ return source.parent / f"{source.stem}.{extension}"
17
21
 
18
22
 
19
- def pdf_to_png(source: Path, target: Path | None = None):
23
+ def pdf_to_png(source: Path, target: Path | None = None, resolution: int = 300):
24
+ """
25
+ Convert a pdf to png with white background
26
+ """
20
27
 
21
28
  outfile = _get_out_filename(source, target, "png")
22
- with Image(filename=source, resolution=300) as img:
29
+ with Image(filename=source, resolution=resolution) as img:
23
30
  img.format = "png"
24
31
  img.background_color = Color("white")
25
32
  img.alpha_channel = "remove"
@@ -27,10 +34,18 @@ def pdf_to_png(source: Path, target: Path | None = None):
27
34
 
28
35
 
29
36
  def svg_to_png(source: Path, target: Path | None = None):
37
+ """
38
+ Convert an svg to png
39
+ """
40
+
30
41
  outfile = _get_out_filename(source, target, "png")
31
42
  cairosvg.svg2png(url=str(source), write_to=str(outfile))
32
43
 
33
44
 
34
45
  def svg_to_pdf(source: Path, target: Path | None = None):
46
+ """
47
+ Convert an svg to pdf
48
+ """
49
+
35
50
  outfile = _get_out_filename(source, target, "pdf")
36
51
  cairosvg.svg2pdf(url=str(source), write_to=str(outfile))
@@ -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
 
File without changes