Skip to content

Commit 0f647f1

Browse files
committed
refactor: split oversized Python module into focused components
- Split fortplot.py from 614 to 81 lines (86% reduction) - Create focused modules: core.py, axes.py, advanced.py, data.py - Maintain full backward compatibility through imports - Addresses file size compliance per architectural limits - Part of Sprint #5 architectural compliance resolution
1 parent 3b76fdf commit 0f647f1

File tree

5 files changed

+551
-551
lines changed

5 files changed

+551
-551
lines changed

python/fortplot/advanced.py

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
"""
2+
fortplot.advanced - Advanced plotting functions
3+
===============================================
4+
5+
Advanced plotting capabilities including contour plots, streamplots,
6+
and pcolormesh for the fortplot Python interface.
7+
"""
8+
9+
import numpy as np
10+
import fortplot.fortplot_wrapper as _fortplot
11+
12+
def _ensure_array(obj):
13+
"""Convert input to numpy array if not already an array (DRY helper)."""
14+
if not isinstance(obj, np.ndarray):
15+
return np.array(obj)
16+
return obj
17+
18+
def contour(X, Y, Z, levels=None):
19+
"""Draw contour lines.
20+
21+
Parameters
22+
----------
23+
X, Y : array-like
24+
The coordinates of the values in Z.
25+
Z : array-like
26+
The height values over which the contour is drawn.
27+
levels : array-like, optional
28+
Determines the number and positions of the contour lines.
29+
"""
30+
X = _ensure_array(X)
31+
Y = _ensure_array(Y)
32+
Z = _ensure_array(Z)
33+
34+
# Extract 1D coordinate arrays from 2D meshgrid (if needed)
35+
if X.ndim == 2:
36+
x = X[0, :] # First row
37+
else:
38+
x = X
39+
if Y.ndim == 2:
40+
y = Y[:, 0] # First column
41+
else:
42+
y = Y
43+
44+
# Transpose Z for Fortran column-major order
45+
z = Z.T.copy()
46+
47+
if levels is None:
48+
_fortplot.fortplot.contour(x, y, z)
49+
else:
50+
levels = _ensure_array(levels)
51+
_fortplot.fortplot.contour(x, y, z, levels)
52+
53+
def contourf(X, Y, Z, levels=None, **kwargs):
54+
"""Draw filled contours.
55+
56+
Parameters
57+
----------
58+
X, Y : array-like
59+
The coordinates of the values in Z.
60+
Z : array-like
61+
The height values over which the contour is drawn.
62+
levels : array-like, optional
63+
Determines the number and positions of the contour lines.
64+
"""
65+
X = _ensure_array(X)
66+
Y = _ensure_array(Y)
67+
Z = _ensure_array(Z)
68+
69+
# Extract 1D coordinate arrays from 2D meshgrid (if needed)
70+
if X.ndim == 2:
71+
x = X[0, :] # First row
72+
else:
73+
x = X
74+
if Y.ndim == 2:
75+
y = Y[:, 0] # First column
76+
else:
77+
y = Y
78+
79+
# Transpose Z for Fortran column-major order
80+
z = Z.T.copy()
81+
82+
if levels is None:
83+
_fortplot.fortplot.contour_filled(x, y, z)
84+
else:
85+
levels = _ensure_array(levels)
86+
_fortplot.fortplot.contour_filled(x, y, z, levels)
87+
88+
def streamplot(X, Y, U, V, density=1.0, **kwargs):
89+
"""Draw streamlines of a vector flow.
90+
91+
Parameters
92+
----------
93+
X, Y : array-like
94+
The coordinates of the values in U, V.
95+
U, V : array-like
96+
x and y-velocities. Number of rows should match length of Y, and
97+
the number of columns should match X.
98+
density : float, optional
99+
Controls the closeness of streamlines. Default is 1.
100+
"""
101+
X = _ensure_array(X)
102+
Y = _ensure_array(Y)
103+
U = _ensure_array(U)
104+
V = _ensure_array(V)
105+
106+
# Extract 1D coordinate arrays from 2D meshgrid (if needed)
107+
if X.ndim == 2:
108+
x = X[0, :] # First row
109+
else:
110+
x = X
111+
if Y.ndim == 2:
112+
y = Y[:, 0] # First column
113+
else:
114+
y = Y
115+
116+
# Transpose U, V for Fortran column-major order
117+
u = U.T.copy()
118+
v = V.T.copy()
119+
120+
_fortplot.fortplot.streamplot(x, y, u, v, density)
121+
122+
def pcolormesh(X, Y, C, cmap=None, vmin=None, vmax=None, edgecolors='none', linewidths=None, **kwargs):
123+
"""Create a pseudocolor plot with a non-regular rectangular grid.
124+
125+
Parameters
126+
----------
127+
X, Y : array-like
128+
The coordinates of the quadrilateral corners. Can be:
129+
- 1D arrays of length N+1 and M+1 for regular grid
130+
- 2D arrays of shape (M+1, N+1) for irregular grid
131+
C : array-like
132+
The color values. Shape (M, N).
133+
cmap : str or Colormap, optional
134+
The colormap to use. Supported: 'viridis', 'plasma', 'inferno',
135+
'coolwarm', 'jet', 'crest' (default).
136+
vmin, vmax : float, optional
137+
Data range for colormap normalization.
138+
edgecolors : color or 'none', optional
139+
Color of the edges. Default 'none'.
140+
linewidths : float, optional
141+
Width of the edges.
142+
**kwargs
143+
Additional keyword arguments (for matplotlib compatibility).
144+
145+
Returns
146+
-------
147+
QuadMesh
148+
The matplotlib QuadMesh collection (placeholder for compatibility).
149+
150+
Examples
151+
--------
152+
Basic usage with regular grid:
153+
154+
>>> x = np.linspace(0, 1, 11)
155+
>>> y = np.linspace(0, 1, 8)
156+
>>> C = np.random.random((7, 10))
157+
>>> pcolormesh(x, y, C, cmap='viridis')
158+
159+
With custom color limits:
160+
161+
>>> pcolormesh(x, y, C, cmap='plasma', vmin=0.2, vmax=0.8)
162+
"""
163+
X = _ensure_array(X)
164+
Y = _ensure_array(Y)
165+
C = _ensure_array(C)
166+
167+
# Handle 1D coordinate arrays (regular grid case)
168+
if X.ndim == 1 and Y.ndim == 1:
169+
x = X
170+
y = Y
171+
elif X.ndim == 2 and Y.ndim == 2:
172+
# Irregular grid support: validate grid structure and extract coordinates
173+
if X.shape != Y.shape:
174+
raise ValueError("For irregular grids, X and Y must have identical shapes")
175+
if X.shape[0] != C.shape[0] + 1 or X.shape[1] != C.shape[1] + 1:
176+
raise ValueError("For irregular grids, coordinate arrays must be (M+1, N+1) for data shape (M, N)")
177+
178+
# For irregular grids, we need to pass the full coordinate arrays
179+
# However, the current Fortran interface expects 1D arrays
180+
# Extract boundary coordinates as a reasonable approximation
181+
# This preserves the overall grid bounds while maintaining compatibility
182+
x = X[0, :] # First row (bottom edge)
183+
y = Y[:, 0] # First column (left edge)
184+
185+
# Note: Full irregular grid rendering would require modifying the Fortran interface
186+
# to accept 2D coordinate arrays and implement curvilinear grid interpolation
187+
else:
188+
raise ValueError("X and Y must have the same dimensionality (both 1D or both 2D)")
189+
190+
# Transpose C for Fortran column-major order
191+
c = C.T.copy()
192+
193+
# Set default colormap if not specified
194+
if cmap is None:
195+
cmap = 'viridis'
196+
197+
# Call Fortran function with optional arguments
198+
if vmin is not None and vmax is not None:
199+
if edgecolors != 'none' and linewidths is not None:
200+
_fortplot.fortplot.pcolormesh(x, y, c, cmap, vmin, vmax, edgecolors, linewidths)
201+
elif edgecolors != 'none':
202+
_fortplot.fortplot.pcolormesh(x, y, c, cmap, vmin, vmax, edgecolors)
203+
else:
204+
_fortplot.fortplot.pcolormesh(x, y, c, cmap, vmin, vmax)
205+
elif vmin is not None:
206+
_fortplot.fortplot.pcolormesh(x, y, c, cmap, vmin)
207+
elif cmap != 'viridis':
208+
_fortplot.fortplot.pcolormesh(x, y, c, cmap)
209+
else:
210+
_fortplot.fortplot.pcolormesh(x, y, c)
211+
212+
# Return placeholder object for matplotlib compatibility
213+
class QuadMeshPlaceholder:
214+
"""Minimal matplotlib QuadMesh compatibility placeholder."""
215+
def __init__(self):
216+
self.colorbar = None
217+
self.figure = None
218+
219+
def get_clim(self):
220+
"""Get the color limits (placeholder)."""
221+
return (vmin, vmax) if vmin is not None and vmax is not None else (0, 1)
222+
223+
def set_clim(self, vmin_new=None, vmax_new=None):
224+
"""Set the color limits (placeholder)."""
225+
pass
226+
227+
return QuadMeshPlaceholder()

python/fortplot/axes.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""
2+
fortplot.axes - Axes and label management
3+
==========================================
4+
5+
Axes, labels, scales, and legend functionality for the fortplot
6+
Python interface.
7+
"""
8+
9+
import numpy as np
10+
import fortplot.fortplot_wrapper as _fortplot
11+
12+
def title(label):
13+
"""Set the title of the current axes.
14+
15+
Parameters
16+
----------
17+
label : str
18+
The title text. If None, title is set to empty string.
19+
20+
Examples
21+
--------
22+
Set a simple title:
23+
24+
>>> import fortplot
25+
>>> fortplot.title('My Plot Title')
26+
27+
Clear the title:
28+
29+
>>> fortplot.title('')
30+
"""
31+
if label is None:
32+
label = ""
33+
_fortplot.fortplot.title(label)
34+
35+
def xlabel(xlabel):
36+
"""Set the label for the x-axis.
37+
38+
Parameters
39+
----------
40+
xlabel : str
41+
The label text for the x-axis. If None, label is set to empty string.
42+
43+
Examples
44+
--------
45+
Set x-axis label:
46+
47+
>>> import fortplot
48+
>>> fortplot.xlabel('Time (seconds)')
49+
50+
Set x-axis label with units:
51+
52+
>>> fortplot.xlabel('Temperature (°C)')
53+
"""
54+
if xlabel is None:
55+
xlabel = ""
56+
_fortplot.fortplot.xlabel(xlabel)
57+
58+
def ylabel(ylabel):
59+
"""Set the label for the y-axis.
60+
61+
Parameters
62+
----------
63+
ylabel : str
64+
The label text for the y-axis. If None, label is set to empty string.
65+
66+
Examples
67+
--------
68+
Set y-axis label:
69+
70+
>>> import fortplot
71+
>>> fortplot.ylabel('Amplitude')
72+
73+
Set y-axis label with units:
74+
75+
>>> fortplot.ylabel('Velocity (m/s)')
76+
"""
77+
if ylabel is None:
78+
ylabel = ""
79+
_fortplot.fortplot.ylabel(ylabel)
80+
81+
def legend(**kwargs):
82+
"""Place a legend on the axes.
83+
84+
Parameters
85+
----------
86+
**kwargs : optional keyword arguments
87+
Additional legend formatting options (for matplotlib compatibility).
88+
Currently, all legend formatting is handled by fortplot defaults.
89+
90+
Examples
91+
--------
92+
Simple legend using plot labels:
93+
94+
>>> import fortplot
95+
>>> fortplot.plot([1, 2, 3], [1, 4, 9], label='quadratic')
96+
>>> fortplot.legend()
97+
"""
98+
# For now, use default legend formatting
99+
_fortplot.fortplot.legend()
100+
101+
# Note: xscale and yscale not implemented in wrapper yet
102+
103+
def xlim(xmin, xmax):
104+
"""Set the x-axis limits."""
105+
_fortplot.fortplot.xlim(xmin, xmax)
106+
107+
def ylim(ymin, ymax):
108+
"""Set the y-axis limits."""
109+
_fortplot.fortplot.ylim(ymin, ymax)

0 commit comments

Comments
 (0)