Skip to content

Commit e3a8774

Browse files
committed
Added canvas and subfigure. Added structure for testing and tutorials.
1 parent f4054c1 commit e3a8774

File tree

11 files changed

+436
-0
lines changed

11 files changed

+436
-0
lines changed

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,3 +160,17 @@ cython_debug/
160160
# and can be added to the global gitignore or merged into this file. For a more nuclear
161161
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
162162
#.idea/
163+
164+
165+
# Figures and videos
166+
*.png
167+
*.pdf
168+
*.jpg
169+
*.jpeg
170+
*.eps
171+
*.mp4
172+
*.pkl
173+
*.json
174+
175+
# Pytest
176+
*.pymon

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ classifiers = [
1616
]
1717
dependencies = [
1818
"matplotlib",
19+
"pytest"
1920
]
2021

2122
[project.optional-dependencies]

pytest.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# pytest.ini
2+
[pytest]
3+
addopts = -ra -q

src/maxplotlib/canvas/__init__.py

Whitespace-only changes.

src/maxplotlib/canvas/canvas.py

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
import matplotlib.pyplot as plt
2+
import maxplotlib.subfigure.line_plot as lp
3+
4+
class Canvas:
5+
def __init__(self, nrows=1, ncols=1, caption=None, description=None, label=None, figsize=(10, 6)):
6+
"""
7+
Initialize the Canvas class for multiple subplots.
8+
9+
Parameters:
10+
nrows (int): Number of subplot rows. Default is 1.
11+
ncols (int): Number of subplot columns. Default is 1.
12+
figsize (tuple): Figure size.
13+
"""
14+
self._nrows = nrows
15+
self._ncols = ncols
16+
self._figsize = figsize
17+
self._caption = caption
18+
self._description = description
19+
self._label = label
20+
21+
# Dictionary to store lines for each subplot
22+
# Key: (row, col), Value: list of lines with their data and kwargs
23+
self.subplots = {}
24+
self._num_subplots = 0
25+
26+
self._subplot_matrix = [[None] * ncols for _ in range(nrows)]
27+
28+
# Property getters
29+
@property
30+
def nrows(self):
31+
return self._nrows
32+
33+
@property
34+
def ncols(self):
35+
return self._ncols
36+
37+
@property
38+
def caption(self):
39+
return self._caption
40+
41+
@property
42+
def description(self):
43+
return self._description
44+
45+
@property
46+
def label(self):
47+
return self._label
48+
49+
@property
50+
def figsize(self):
51+
return self._figsize
52+
53+
@property
54+
def subplot_matrix(self):
55+
return self._subplot_matrix
56+
57+
# Property setters
58+
@nrows.setter
59+
def nrows(self, value):
60+
self._nrows = value
61+
62+
@ncols.setter
63+
def ncols(self, value):
64+
self._ncols = value
65+
66+
@caption.setter
67+
def caption(self, value):
68+
self._caption = value
69+
70+
@description.setter
71+
def description(self, value):
72+
self._description = value
73+
74+
@label.setter
75+
def label(self, value):
76+
self._label = value
77+
78+
@figsize.setter
79+
def figsize(self, value):
80+
self._figsize = value
81+
82+
# Magic methods
83+
def __str__(self):
84+
return f"Canvas(nrows={self.nrows}, ncols={self.ncols}, figsize={self.figsize})"
85+
86+
def __repr__(self):
87+
return f"Canvas(nrows={self.nrows}, ncols={self.ncols}, caption={self.caption}, label={self.label})"
88+
89+
def __getitem__(self, key):
90+
"""Allows accessing subplots by tuple index."""
91+
row, col = key
92+
if row >= self.nrows or col >= self.ncols:
93+
raise IndexError("Subplot index out of range")
94+
return self._subplot_matrix[row][col]
95+
96+
def __setitem__(self, key, value):
97+
"""Allows setting a subplot by tuple index."""
98+
row, col = key
99+
if row >= self.nrows or col >= self.ncols:
100+
raise IndexError("Subplot index out of range")
101+
self._subplot_matrix[row][col] = value
102+
103+
def add_subplot(self, col=None, row=None, label=None):
104+
if row is None:
105+
for irow in range(self.nrows):
106+
has_none = any(item is None for item in self._subplot_matrix[irow])
107+
if has_none:
108+
row = irow
109+
break
110+
assert row is not None, "Not enough rows!"
111+
112+
if col is None:
113+
for icol in range(self.ncols):
114+
if self._subplot_matrix[row][icol] is None:
115+
col = icol
116+
break
117+
assert col is not None, "Not enough columns!"
118+
119+
# Initialize the LinePlot for the given subplot position
120+
line_plot = lp.LinePlot()
121+
self._subplot_matrix[row][col] = line_plot
122+
123+
# Store the LinePlot instance by its position for easy access
124+
if label is None:
125+
self.subplots[(row, col)] = line_plot
126+
else:
127+
self.subplots[label] = line_plot
128+
return line_plot
129+
def savefig(self, filename, backend = 'matplotlib'):
130+
if backend == 'matplotlib':
131+
fig = self.plot(show=False, savefig=True)
132+
fig.savefig(filename)
133+
#plt.savefig(filename)
134+
# def add_line(self, label, x_data, y_data, **kwargs):
135+
136+
def plot(self, backend='matplotlib', show=True, savefig=False):
137+
if backend == 'matplotlib':
138+
return self.plot_matplotlib(show=show, savefig=savefig)
139+
def plot_matplotlib(self, show=True, savefig=False):
140+
"""
141+
Generate and optionally display the subplots.
142+
143+
Parameters:
144+
filename (str, optional): Filename to save the figure.
145+
show (bool): Whether to display the plot.
146+
"""
147+
fig, axes = plt.subplots(self.nrows, self.ncols, figsize=self.figsize, squeeze=False)
148+
149+
for (row, col), line_plot in self.subplots.items():
150+
ax = axes[row][col]
151+
line_plot.plot(ax) # Assuming LinePlot's `plot` method accepts an axis object
152+
ax.set_title(f"Subplot ({row}, {col})")
153+
154+
# Set caption, labels, etc., if needed
155+
plt.tight_layout()
156+
157+
if show:
158+
plt.show()
159+
else:
160+
plt.close()
161+
return fig
162+
163+
# def generate_matplotlib_code(self):
164+
# """Generate code for plotting the data using matplotlib."""
165+
# code = "import matplotlib.pyplot as plt\n\n"
166+
# code += f"fig, axes = plt.subplots({self.nrows}, {self.ncols}, figsize={self.figsize})\n\n"
167+
# if self.nrows == 1 and self.ncols == 1:
168+
# code += "axes = [axes] # Single subplot\n\n"
169+
# else:
170+
# code += "axes = axes.flatten()\n\n"
171+
# for idx, (subplot_idx, lines) in enumerate(self.subplots.items()):
172+
# code += f"# Subplot {subplot_idx}\n"
173+
# code += f"ax = axes[{idx}]\n"
174+
# for line in lines:
175+
# x_data = line['x']
176+
# y_data = line['y']
177+
# label = line['label']
178+
# kwargs = line.get('kwargs', {})
179+
# kwargs_str = ', '.join(f"{k}={repr(v)}" for k, v in kwargs.items())
180+
# code += f"ax.plot({x_data}, {y_data}, label={repr(label)}"
181+
# if kwargs_str:
182+
# code += f", {kwargs_str}"
183+
# code += ")\n"
184+
# code += "ax.set_xlabel('X-axis')\n"
185+
# code += "ax.set_ylabel('Y-axis')\n"
186+
# if self.nrows * self.ncols > 1:
187+
# code += f"ax.set_title('Subplot {subplot_idx}')\n"
188+
# code += "ax.legend()\n\n"
189+
# code += "plt.tight_layout()\nplt.show()\n"
190+
# return code
191+
192+
# def generate_latex_plot(self):
193+
# """Generate LaTeX code for plotting the data using pgfplots in subplots."""
194+
# latex_code = "\\begin{figure}[h!]\n\\centering\n"
195+
# total_subplots = self.nrows * self.ncols
196+
# for idx in range(total_subplots):
197+
# subplot_idx = divmod(idx, self.ncols)
198+
# lines = self.subplots.get(subplot_idx, [])
199+
# if not lines:
200+
# continue # Skip empty subplots
201+
# latex_code += "\\begin{subfigure}[b]{0.45\\textwidth}\n"
202+
# latex_code += " \\begin{tikzpicture}\n"
203+
# latex_code += " \\begin{axis}[\n"
204+
# latex_code += " xlabel={X-axis},\n"
205+
# latex_code += " ylabel={Y-axis},\n"
206+
# if self.nrows * self.ncols > 1:
207+
# latex_code += f" title={{Subplot {subplot_idx}}},\n"
208+
# latex_code += " legend style={at={(1.05,1)}, anchor=north west},\n"
209+
# latex_code += " legend entries={" + ", ".join(f"{{{line['label']}}}" for line in lines) + "}\n"
210+
# latex_code += " ]\n"
211+
# for line in lines:
212+
# options = []
213+
# kwargs = line.get('kwargs', {})
214+
# if 'color' in kwargs:
215+
# options.append(f"color={kwargs['color']}")
216+
# if 'linestyle' in kwargs:
217+
# linestyle_map = {'-': 'solid', '--': 'dashed', '-.': 'dash dot', ':': 'dotted'}
218+
# linestyle = linestyle_map.get(kwargs['linestyle'], kwargs['linestyle'])
219+
# options.append(f"style={linestyle}")
220+
# options_str = f"[{', '.join(options)}]" if options else ""
221+
# latex_code += f" \\addplot {options_str} coordinates {{\n"
222+
# for x, y in zip(line['x'], line['y']):
223+
# latex_code += f" ({x}, {y})\n"
224+
# latex_code += " };\n"
225+
# latex_code += " \\end{axis}\n"
226+
# latex_code += " \\end{tikzpicture}\n"
227+
# latex_code += "\\end{subfigure}\n"
228+
# latex_code += "\\hfill\n" if (idx + 1) % self.ncols != 0 else "\n"
229+
# latex_code += "\\caption{Multiple Subplots}\n"
230+
# latex_code += "\\end{figure}\n"
231+
# return latex_code
232+
233+
234+
if __name__ == '__main__':
235+
c = Canvas(ncols=2,nrows=2)
236+
sp = c.add_subplot()
237+
sp.add_line("Line 1", [0, 1, 2, 3], [0, 1, 4, 9])
238+
c.plot()
239+
print('done')

src/maxplotlib/subfigure/__init__.py

Whitespace-only changes.
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
class LinePlot:
2+
def __init__(self, figsize=(10, 6), caption=None, description=None, label=None):
3+
"""
4+
Initialize the LinePlot class for a subplot.
5+
6+
Parameters:
7+
figsize (tuple): Figure size.
8+
caption (str): Caption for the plot.
9+
description (str): Description of the plot.
10+
label (str): Label for the plot.
11+
"""
12+
self.figsize = figsize
13+
# List to store line data, with each entry containing x and y data, label, and plot kwargs
14+
self.line_data = []
15+
16+
self.caption = caption
17+
self.description = description
18+
self.label = label
19+
20+
def add_caption(self, caption):
21+
self.caption = caption
22+
23+
def add_line(self, x_data, y_data, **kwargs):
24+
"""
25+
Add a line to the plot.
26+
27+
Parameters:
28+
label (str): Label for the line.
29+
x_data (list): X-axis data.
30+
y_data (list): Y-axis data.
31+
**kwargs: Additional keyword arguments for the plot (e.g., color, linestyle).
32+
"""
33+
self.line_data.append({
34+
'x': x_data,
35+
'y': y_data,
36+
'kwargs': kwargs
37+
})
38+
39+
def plot(self, ax):
40+
"""
41+
Plot all lines on the provided axis.
42+
43+
Parameters:
44+
ax (matplotlib.axes.Axes): Axis on which to plot the lines.
45+
"""
46+
for line in self.line_data:
47+
ax.plot(
48+
line['x'], line['y'],
49+
# label=line['label'],
50+
**line['kwargs']
51+
)
52+
if self.caption:
53+
ax.set_title(self.caption)
54+
if self.label:
55+
ax.set_ylabel(self.label)
56+
ax.set_xlabel("X-axis")
57+
ax.legend()
58+
59+
60+
61+
if __name__ == "__main__":
62+
plotter = LinePlot()
63+
plotter.add_line("Line 1", [0, 1, 2, 3], [0, 1, 4, 9])
64+
plotter.add_line("Line 2", [0, 1, 2, 3], [0, 2, 3, 6])
65+
latex_code = plotter.generate_latex_plot()
66+
with open("figures/latex_code.tex", "w") as f:
67+
f.write(latex_code)
68+
print(latex_code)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
def test():
3+
import maxplotlib.canvas.
4+
5+
if __name__ == '__main__':
6+
test()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import pytest
2+
@pytest.mark.parametrize('x',[0])
3+
def import_modules(x):
4+
import maxplotlib
5+
import matplotlib
6+
7+
if __name__ == '__main__':
8+
import_modules(x=1)

src/maxplotlib/tests/test_plot.py

Whitespace-only changes.

0 commit comments

Comments
 (0)