Skip to content

Commit 5d42566

Browse files
committed
Started working on the tikz subplot.
1 parent 9db05f2 commit 5d42566

File tree

4 files changed

+304
-14
lines changed

4 files changed

+304
-14
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ cython_debug/
172172
*.pkl
173173
*.json
174174

175+
# tex
176+
*.aux
177+
*.tex
178+
175179
# Pytest
176180
*.pymon
177181

src/maxplotlib/canvas/canvas.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import matplotlib.pyplot as plt
22
import maxplotlib.subfigure.line_plot as lp
3+
import maxplotlib.subfigure.tikz_figure as tf
34
import maxplotlib.backends.matplotlib.utils as plt_utils
45
import plotly.graph_objects as go
56
from plotly.subplots import make_subplots
@@ -33,8 +34,25 @@ def __init__(self, **kwargs):
3334
self._num_subplots = 0
3435

3536
self._subplot_matrix = [[None] * self.ncols for _ in range(self.nrows)]
37+
38+
def generate_new_rowcol(self,row,col):
39+
if row is None:
40+
for irow in range(self.nrows):
41+
has_none = any(item is None for item in self._subplot_matrix[irow])
42+
if has_none:
43+
row = irow
44+
break
45+
assert row is not None, "Not enough rows!"
3646

37-
def add_subplot(self, **kwargs):
47+
if col is None:
48+
for icol in range(self.ncols):
49+
if self._subplot_matrix[row][icol] is None:
50+
col = icol
51+
break
52+
assert col is not None, "Not enough columns!"
53+
return row, col
54+
55+
def add_tikzfigure(self, **kwargs):
3856
"""
3957
Adds a subplot to the figure.
4058
@@ -48,20 +66,34 @@ def add_subplot(self, **kwargs):
4866
row = kwargs.get('row', None)
4967
label = kwargs.get('label', None)
5068

51-
if row is None:
52-
for irow in range(self.nrows):
53-
has_none = any(item is None for item in self._subplot_matrix[irow])
54-
if has_none:
55-
row = irow
56-
break
57-
assert row is not None, "Not enough rows!"
69+
row, col = self.generate_new_rowcol(row, col)
5870

59-
if col is None:
60-
for icol in range(self.ncols):
61-
if self._subplot_matrix[row][icol] is None:
62-
col = icol
63-
break
64-
assert col is not None, "Not enough columns!"
71+
# Initialize the LinePlot for the given subplot position
72+
tikz_figure = tf.TikzFigure(**kwargs)
73+
self._subplot_matrix[row][col] = tikz_figure
74+
75+
# Store the LinePlot instance by its position for easy access
76+
if label is None:
77+
self.subplots[(row, col)] = tikz_figure
78+
else:
79+
self.subplots[label] = tikz_figure
80+
return tikz_figure
81+
82+
def add_subplot(self, **kwargs):
83+
"""
84+
Adds a subplot to the figure.
85+
86+
Parameters:
87+
**kwargs: Arbitrary keyword arguments.
88+
- col (int): Column index for the subplot.
89+
- row (int): Row index for the subplot.
90+
- label (str): Label to identify the subplot.
91+
"""
92+
col = kwargs.get('col', None)
93+
row = kwargs.get('row', None)
94+
label = kwargs.get('label', None)
95+
96+
row, col = self.generate_new_rowcol(row, col)
6597

6698
# Initialize the LinePlot for the given subplot position
6799
line_plot = lp.LinePlot(**kwargs)
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import subprocess
2+
import os
3+
import tempfile
4+
5+
class TikzFigure:
6+
def __init__(self, **kwargs):
7+
"""
8+
Initialize the TikzFigure class for creating TikZ figures.
9+
10+
Parameters:
11+
**kwargs: Arbitrary keyword arguments.
12+
- figsize (tuple): Figure size (default is (10, 6)).
13+
- caption (str): Caption for the figure.
14+
- description (str): Description of the figure.
15+
- label (str): Label for the figure.
16+
- grid (bool): Whether to display grid lines (default is False).
17+
TODO: Add all options
18+
"""
19+
# Set default values
20+
self._figsize = kwargs.get('figsize', (10, 6))
21+
self._caption = kwargs.get('caption', None)
22+
self._description = kwargs.get('description', None)
23+
self._label = kwargs.get('label', None)
24+
self._grid = kwargs.get('grid', False)
25+
26+
# Initialize nodes and lines
27+
self.nodes = []
28+
self.lines = []
29+
30+
def add_node(self, name, x, y, **kwargs):
31+
"""
32+
Add a node to the TikZ figure.
33+
34+
Parameters:
35+
- name (str): Name of the node.
36+
- x (float): X-coordinate of the node.
37+
- y (float): Y-coordinate of the node.
38+
- **kwargs: Additional TikZ node options (e.g., shape, color).
39+
"""
40+
node = {
41+
'name': name,
42+
'x': x,
43+
'y': y,
44+
'options': kwargs
45+
}
46+
self.nodes.append(node)
47+
48+
def add_line(self, nodes, **kwargs):
49+
"""
50+
Add a line or path connecting multiple nodes.
51+
52+
Parameters:
53+
- nodes (list of str): List of node names to connect.
54+
- **kwargs: Additional TikZ line options (e.g., style, color).
55+
56+
Examples:
57+
- add_line(['A', 'B', 'C'], color='blue')
58+
Connects nodes A -> B -> C with a blue line.
59+
"""
60+
if not isinstance(nodes, list):
61+
raise ValueError("nodes parameter must be a list of node names.")
62+
63+
line = {
64+
'nodes': nodes,
65+
'options': kwargs
66+
}
67+
self.lines.append(line)
68+
69+
def generate_tikz(self):
70+
"""
71+
Generate the TikZ script for the figure.
72+
73+
Returns:
74+
- tikz_script (str): The TikZ script as a string.
75+
"""
76+
tikz_script = "\\begin{tikzpicture}\n"
77+
78+
# Add grid if enabled
79+
if self._grid:
80+
tikz_script += " \\draw[step=1cm, gray, very thin] (-10,-10) grid (10,10);\n"
81+
82+
# Add nodes
83+
for node in self.nodes:
84+
options = ', '.join(f"{k.replace('_',' ')}={{{v}}}" for k, v in node['options'].items())
85+
if options:
86+
options = f"[{options}]"
87+
tikz_script += f" \\node{options} ({node['name']}) at ({node['x']}, {node['y']}) {{{node['name']}}};\n"
88+
89+
# Add lines
90+
for line in self.lines:
91+
options = ', '.join(f"{k.replace('_',' ')}={{{v}}}" for k, v in line['options'].items())
92+
if options:
93+
options = f"[{options}]"
94+
# Create the path by connecting all nodes in the list
95+
path = ' -- '.join(f"({node_name})" for node_name in line['nodes'])
96+
tikz_script += f" \\draw{options} {path};\n"
97+
98+
tikz_script += "\\end{tikzpicture}"
99+
100+
# Wrap in figure environment if necessary
101+
if self._caption or self._description or self._label:
102+
figure_env = "\\begin{figure}\n" + tikz_script + "\n"
103+
if self._caption:
104+
figure_env += f" \\caption{{{self._caption}}}\n"
105+
if self._label:
106+
figure_env += f" \\label{{{self._label}}}\n"
107+
figure_env += "\\end{figure}"
108+
tikz_script = figure_env
109+
110+
return tikz_script
111+
112+
def compile_pdf(self, filename='output.pdf'):
113+
"""
114+
Compile the TikZ script into a PDF using pdflatex.
115+
116+
Parameters:
117+
- filename (str): The name of the output PDF file (default is 'output.pdf').
118+
119+
Notes:
120+
- Requires 'pdflatex' to be installed and accessible from the command line.
121+
"""
122+
tikz_code = self.generate_tikz()
123+
124+
# Create a minimal LaTeX document
125+
latex_document = (
126+
"\\documentclass[border=10pt]{standalone}\n"
127+
"\\usepackage{tikz}\n"
128+
"\\begin{document}\n"
129+
f"{tikz_code}\n"
130+
"\\end{document}"
131+
)
132+
133+
# Use a temporary directory to store the LaTeX files
134+
with tempfile.TemporaryDirectory() as tempdir:
135+
tex_file = os.path.join(tempdir, 'figure.tex')
136+
with open(tex_file, 'w') as f:
137+
f.write(latex_document)
138+
139+
# Run pdflatex
140+
try:
141+
subprocess.run(
142+
['pdflatex', '-interaction=nonstopmode', tex_file],
143+
cwd=tempdir,
144+
check=True,
145+
stdout=subprocess.PIPE,
146+
stderr=subprocess.PIPE
147+
)
148+
except subprocess.CalledProcessError as e:
149+
print("An error occurred while compiling the LaTeX document:")
150+
print(e.stderr.decode())
151+
return
152+
153+
# Move the output PDF to the desired location
154+
pdf_output = os.path.join(tempdir, 'figure.pdf')
155+
if os.path.exists(pdf_output):
156+
os.rename(pdf_output, filename)
157+
print(f"PDF successfully compiled and saved as '{filename}'.")
158+
else:
159+
print("PDF compilation failed. Please check the LaTeX log for details.")

tutorials/tutorial_02.ipynb

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"id": "3816e8ed-5107-4d8c-9e78-f801af118811",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"import maxplotlib.canvas.canvas as canvas\n",
11+
"\n",
12+
"%load_ext autoreload\n",
13+
"%autoreload 2"
14+
]
15+
},
16+
{
17+
"cell_type": "code",
18+
"execution_count": 34,
19+
"id": "650f0afa-f719-4495-b020-fc2b2df36bd7",
20+
"metadata": {},
21+
"outputs": [
22+
{
23+
"name": "stdout",
24+
"output_type": "stream",
25+
"text": [
26+
"\\begin{tikzpicture}\n",
27+
" \\node[shape={circle}, draw={black}, fill={blue!20}] (A) at (0, 0) {A};\n",
28+
" \\node[shape={rectangle}, draw={red}] (B) at (2, 2) {B};\n",
29+
" \\node[shape={rectangle}, draw={red}] (C) at (2, 5) {C};\n",
30+
" \\node[shape={rectangle}, draw={red}] (D) at (1, 5) {D};\n",
31+
" \\draw[color={darkgreen}, style={dashed}, line width={2pt}] (A) -- (B) -- (C) -- (A) -- (D);\n",
32+
"\\end{tikzpicture}\n",
33+
"An error occurred while compiling the LaTeX document:\n",
34+
"\n"
35+
]
36+
}
37+
],
38+
"source": [
39+
"c = canvas.Canvas(width=800, ratio=0.5)\n",
40+
"tikz = c.add_tikzfigure(grid=False)\n",
41+
"\n",
42+
"# Add nodes\n",
43+
"tikz.add_node('A', 0, 0, shape='circle', draw='black', fill='blue!20')\n",
44+
"tikz.add_node('B', 2, 2, shape='rectangle', draw='red')\n",
45+
"tikz.add_node('C', 2, 5, shape='rectangle', draw='red')\n",
46+
"tikz.add_node('D', 1, 5, shape='rectangle', draw='red')\n",
47+
"\n",
48+
"# Add a line between nodes \n",
49+
"tikz.add_line(['A', 'B', 'C', 'A', 'D'], color='darkgreen', style='dashed', line_width='2pt')\n",
50+
"\n",
51+
"# Generate the TikZ script\n",
52+
"script = tikz.generate_tikz()\n",
53+
"print(script)\n",
54+
"tikz.compile_pdf('my_figure.pdf')"
55+
]
56+
},
57+
{
58+
"cell_type": "code",
59+
"execution_count": null,
60+
"id": "344589d9-c60f-4d01-8171-e35db585f1ad",
61+
"metadata": {},
62+
"outputs": [],
63+
"source": []
64+
},
65+
{
66+
"cell_type": "code",
67+
"execution_count": null,
68+
"id": "7c37d9b8-256b-4907-9c95-288b18cb396d",
69+
"metadata": {},
70+
"outputs": [],
71+
"source": []
72+
}
73+
],
74+
"metadata": {
75+
"kernelspec": {
76+
"display_name": "Python 3 (ipykernel)",
77+
"language": "python",
78+
"name": "python3"
79+
},
80+
"language_info": {
81+
"codemirror_mode": {
82+
"name": "ipython",
83+
"version": 3
84+
},
85+
"file_extension": ".py",
86+
"mimetype": "text/x-python",
87+
"name": "python",
88+
"nbconvert_exporter": "python",
89+
"pygments_lexer": "ipython3",
90+
"version": "3.9.20"
91+
}
92+
},
93+
"nbformat": 4,
94+
"nbformat_minor": 5
95+
}

0 commit comments

Comments
 (0)