myplot 0.1.0__py3-none-any.whl

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.
myplot/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # MyPlot - Uniform Plotting Module for Matplotlib
2
+
3
+ A Python module for creating publication-quality matplotlib plots with automatic font detection and consistent formatting.
4
+
5
+ ## Features
6
+
7
+ - **Automatic Font Detection**: Searches for Source Han Serif font across system paths and project directories
8
+ - **Cross-platform Support**: Works on Windows, macOS, and Linux
9
+ - **Consistent Formatting**: Pre-configured plot styling parameters
10
+ - **Flexible Configuration**: Platform-specific configuration directories
11
+ - **Utility Functions**: Formatters for radians, degrees, and dB values
12
+ - **Easy Integration**: Simple decorator for creating plots
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ # Install dependencies
18
+ pip install matplotlib numpy
19
+
20
+ # For development/testing (optional)
21
+ pip install hatch
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ### Basic Plotting
27
+
28
+ ```python
29
+ from myplot import myplot, plt, np
30
+
31
+ @myplot()
32
+ def my_function():
33
+ x = np.linspace(0, 10, 100)
34
+ y = np.sin(x)
35
+ plt.plot(x, y)
36
+ plt.xlabel('x')
37
+ plt.ylabel('sin(x)')
38
+ plt.title('Sine Wave')
39
+ ```
40
+
41
+ ### Custom Figure Size and Save Options
42
+
43
+ ```python
44
+ @myplot(figsize=(8, 6), save=True, log=True)
45
+ def custom_plot():
46
+ # Your plotting code here
47
+ pass
48
+ ```
49
+
50
+ ### Utilities
51
+
52
+ ```python
53
+ from myplot import myUtilities
54
+
55
+ # Use custom formatters
56
+ ax.xaxis.set_major_formatter(myUtilities.radFormatter())
57
+ ax.xaxis.set_major_formatter(myUtilities.degFormatter())
58
+ ax.xaxis.set_major_formatter(myUtilities.dBFormatter())
59
+
60
+ # Use custom locator
61
+ ax.xaxis.set_major_locator(myUtilities.radLocator())
62
+ ```
63
+
64
+ ### Configuration
65
+
66
+ The module uses platform-specific configuration directories:
67
+ - **Windows**: `%USERPROFILE%\AppData\Local\myplot\`
68
+ - **macOS**: `~/Library/Application Support/myplot/`
69
+ - **Linux**: `~/.config/myplot/`
70
+
71
+ ## Font Detection
72
+
73
+ The module automatically searches for Source Han Serif font in:
74
+ - System font directories
75
+ - User font directories
76
+ - Project local directories (fonts/, resources/fonts/, src/fonts/)
77
+ - Current directory
78
+
79
+ ## Testing
80
+
81
+ Run the test suite:
82
+
83
+ ```bash
84
+ python minimal_test.py
85
+ python test_myplot.py
86
+ ```
87
+
88
+ ## Development
89
+
90
+ To install in development mode:
91
+
92
+ ```bash
93
+ pip install -e .
94
+ ```
95
+
96
+ ## License
97
+
98
+ MIT License
myplot/__init__.py ADDED
@@ -0,0 +1,427 @@
1
+ """
2
+ MyPlot - A uniform plotting module for matplotlib with proper font handling.
3
+
4
+ This module provides decorators and utilities for creating publication-quality plots
5
+ with consistent formatting and automatic font detection.
6
+ """
7
+
8
+ import platform
9
+ import matplotlib.pyplot as plt
10
+ import matplotlib.font_manager as fm
11
+ import matplotlib.ticker as ticker
12
+ import numpy as np
13
+ import functools
14
+ import sys
15
+ import builtins
16
+ from pathlib import Path
17
+ import json
18
+ import os
19
+
20
+ __all__ = ['myplot', 'plt', 'np', 'myparams', 'myUtilities']
21
+
22
+
23
+ class myparams:
24
+ """Configuration parameter manager with JSON storage."""
25
+
26
+ def __init__(self, file="myplot_params.json", config_dir=None):
27
+ if config_dir:
28
+ config_path = Path(config_dir)
29
+ else:
30
+ # Use platform-specific configuration directories
31
+ if platform.system() == 'Windows':
32
+ config_path = Path.home() / 'AppData' / 'Local' / 'myplot'
33
+ elif platform.system() == 'Darwin': # macOS
34
+ config_path = Path.home() / 'Library' / 'Application Support' / 'myplot'
35
+ else: # Linux and other Unix-like systems
36
+ config_path = Path.home() / '.config' / 'myplot'
37
+
38
+ self.f = config_path / file
39
+ self.f.parent.mkdir(parents=True, exist_ok=True)
40
+
41
+ try:
42
+ with open(self.f, 'r') as ff:
43
+ self.p = json.load(ff)
44
+ except (FileNotFoundError, json.JSONDecodeError):
45
+ self.p = {}
46
+
47
+ def get(self, key):
48
+ return self.p.get(key)
49
+
50
+ def put(self, arg: dict = None, **kwargs):
51
+ d = (arg or {}) | (kwargs or {})
52
+ for k in d:
53
+ self.p[k] = d[k]
54
+ self.update()
55
+
56
+ def update(self):
57
+ with open(self.f, 'w') as ff:
58
+ json.dump(self.p, ff, indent=4)
59
+
60
+ def __getitem__(self, key):
61
+ return self.get(key)
62
+
63
+ def __setitem__(self, key, value):
64
+ self.put({key: value})
65
+
66
+ def __delitem__(self, key):
67
+ if key in self.p:
68
+ del self.p[key]
69
+ self.update()
70
+
71
+ def __contains__(self, key):
72
+ return key in self.p
73
+
74
+
75
+ def get_font_search_paths():
76
+ """Get platform-specific font search paths."""
77
+ system = platform.system()
78
+ paths = []
79
+
80
+ # Add system font paths
81
+ if system == 'Windows':
82
+ system_font_dirs = [
83
+ Path('C:/Windows/Fonts'),
84
+ Path('C:/Windows/Fonts/simhei'), # Chinese fonts
85
+ ]
86
+ if 'LOCALAPPDATA' in os.environ:
87
+ user_font_dir = Path(os.environ['LOCALAPPDATA']) / 'Microsoft' / 'Windows' / 'Fonts'
88
+ if user_font_dir.exists():
89
+ paths.append(user_font_dir)
90
+
91
+ elif system == 'Darwin': # macOS
92
+ system_font_dirs = [
93
+ Path('/System/Library/Fonts'),
94
+ Path('/System/Library/Fonts/ChineseSimplified'),
95
+ Path('/Library/Fonts'),
96
+ Path('/Library/Fonts/ChineseSimplified'),
97
+ ]
98
+ user_font_dir = Path.home() / 'Library' / 'Fonts'
99
+ if user_font_dir.exists():
100
+ paths.append(user_font_dir)
101
+ else: # Linux
102
+ system_font_dirs = [
103
+ Path('/usr/share/fonts'),
104
+ Path('/usr/share/fonts/truetype'),
105
+ Path('/usr/share/fonts/opentype'),
106
+ Path('/usr/share/fonts/truetype/freefont'),
107
+ Path('/usr/share/fonts/opentype/noto'),
108
+ ]
109
+ user_font_dir = Path.home() / '.local' / 'share' / 'fonts'
110
+ if user_font_dir.exists():
111
+ paths.append(user_font_dir)
112
+
113
+ # Add existing system font directories
114
+ for font_dir in system_font_dirs:
115
+ if font_dir.exists():
116
+ paths.append(font_dir)
117
+
118
+ # Add current directory and relative paths (for local font files)
119
+ current_dir = Path.cwd()
120
+ paths.extend([
121
+ current_dir,
122
+ current_dir / 'fonts',
123
+ current_dir / 'resources' / 'fonts',
124
+ current_dir / 'src' / 'fonts',
125
+ ])
126
+
127
+ return paths
128
+
129
+
130
+ def find_source_han_serif_font():
131
+ """Find Source Han Serif font across multiple search paths."""
132
+ font_names_variants = [
133
+ 'SourceHanSerifSC-Medium.otf',
134
+ 'SourceHanSerif-Medium.otf',
135
+ 'SourceHanSerifSC-Regular.otf',
136
+ 'SourceHanSerif-Regular.otf',
137
+ 'SourceHanSerif-Heavy.otf',
138
+ 'NotoSerifCJK-Regular.otf', # Alternative
139
+ 'NotoSerifCJK-Medium.otf', # Alternative
140
+ 'NotoSerifCJK-SC-Regular.otf',
141
+ 'NotoSerifCJK-TC-Regular.otf'
142
+ ]
143
+
144
+ # Search in all possible paths
145
+ search_paths = get_font_search_paths()
146
+
147
+ print(f"Searching in {len(search_paths)} paths:")
148
+ for i, path in enumerate(search_paths, 1):
149
+ print(f" {i}. {path}")
150
+
151
+ # First, try to find font files in the directory
152
+ for font_variant in font_names_variants:
153
+ for search_path in search_paths:
154
+ font_path = search_path / font_variant
155
+ if font_path.exists():
156
+ print(f"Found font file: {font_path}")
157
+ return font_path.resolve()
158
+
159
+ # Second, check matplotlib's known fonts
160
+ print("\nChecking matplotlib's known fonts...")
161
+ font_list = fm.findSystemFonts()
162
+ for font_file in font_list:
163
+ font_name = Path(font_file).name
164
+ for font_variant in font_names_variants:
165
+ if font_variant.lower() in font_name.lower():
166
+ font_path = Path(font_file).resolve()
167
+ print(f"Found font in system: {font_path}")
168
+ return font_path
169
+
170
+ # Check if any font variant is already registered
171
+ print("\nChecking registered fonts...")
172
+ try:
173
+ for font_variant in font_names_variants:
174
+ font_list = fm.findSystemFonts()
175
+ for font_file in font_list:
176
+ try:
177
+ font_obj = fm.FontProperties(fname=font_file)
178
+ font_name = font_obj.get_name()
179
+ if ('source' in font_name.lower() and 'serif' in font_name.lower()) or \
180
+ font_variant.replace('.otf', '') in font_name.replace(' ', ''):
181
+ font_path = Path(font_file).resolve()
182
+ print(f"Found matching font: {font_path}")
183
+ print(f" Font name: {font_name}")
184
+ return font_path
185
+ except Exception:
186
+ continue
187
+ except Exception as e:
188
+ print(f"Error checking fonts: {e}")
189
+
190
+ print('⚠ Cannot find Source Han Serif font, using default settings')
191
+ return None
192
+
193
+
194
+ def setup_font_properties():
195
+ """Setup matplotlib font properties if Source Han Serif is found."""
196
+ font_path = find_source_han_serif_font()
197
+
198
+ if font_path:
199
+ try:
200
+ # Force register the font
201
+ fontManager = fm.fontManager
202
+ fontManager.addfont(str(font_path))
203
+
204
+ # Create font properties
205
+ font_prop = fm.FontProperties(fname=str(font_path))
206
+ font_name = font_prop.get_name()
207
+ print(f"Font file found: {font_path}")
208
+ print(f"Font name detected: {font_name}")
209
+
210
+ # Try multiple possible family names
211
+ possible_families = [
212
+ 'Source Han Serif',
213
+ 'Source Han Serif SC',
214
+ 'Source Han Serif TC',
215
+ 'Noto Serif CJK SC',
216
+ 'Noto Serif CJK'
217
+ ]
218
+
219
+ # Set matplotlib font configuration
220
+ plt.rcParams['mathtext.fontset'] = 'stix'
221
+ plt.rcParams['axes.unicode_minus'] = False
222
+ plt.rcParams['font.family'] = 'sans-serif' # fallback
223
+ plt.rcParams['font.sans-serif'] = ['Source Han Serif'] + plt.rcParams['font.sans-serif']
224
+
225
+ # Manually set the font for the current session
226
+ try:
227
+ # Try to find the correct family name from the registered font
228
+ for family in possible_families:
229
+ if family in fontManager.get_font_names():
230
+ plt.rcParams['font.family'] = family
231
+ plt.rcParams['font.sans-serif'] = [family] + plt.rcParams['font.sans-serif']
232
+ print(f"Set font family to: {family}")
233
+ break
234
+ except Exception as e:
235
+ print(f"Note: Could not set font family explicitly: {e}")
236
+
237
+ return True
238
+ except Exception as e:
239
+ print(f"Error setting up font: {e}")
240
+ return False
241
+ return False
242
+
243
+
244
+ # Initialize font setup
245
+ setup_successful = setup_font_properties()
246
+ myparams.font = None
247
+
248
+ # Load the font into myparams if setup was successful
249
+ if setup_successful:
250
+ font_path = find_source_han_serif_font()
251
+ if font_path:
252
+ try:
253
+ myparams.font = fm.FontProperties(fname=str(font_path))
254
+ print("Font loaded into myparams")
255
+ except Exception as e:
256
+ print(f"Could not load font into myparams: {e}")
257
+
258
+ # Character size settings
259
+ plt.rcParams['font.size'] = 9
260
+ plt.rcParams['axes.titlesize'] = 11
261
+ plt.rcParams['axes.labelsize'] = 10
262
+ plt.rcParams['xtick.labelsize'] = 9
263
+ plt.rcParams['ytick.labelsize'] = 9
264
+ plt.rcParams['lines.linewidth'] = 1.2
265
+ plt.rcParams['lines.markersize'] = 4
266
+ plt.rcParams['errorbar.capsize'] = 3
267
+ elinewidth = 0.8
268
+
269
+
270
+ def myplot(figsize=(4.8, 3.6), save=True, log=True, savename='', config_dir=None):
271
+ """
272
+ Decorator for creating uniformly formatted matplotlib plots.
273
+
274
+ Args:
275
+ figsize: Figure size in inches (width, height)
276
+ save: Whether to save the figure
277
+ log: Whether to log plot creation
278
+ savename: Filename for saved figure (defaults to function name)
279
+ config_dir: Directory for configuration files (optional)
280
+
281
+ Returns:
282
+ Decorator function
283
+ """
284
+ def decorator(func):
285
+ @functools.wraps(func)
286
+ def wrap(*args, **kwargs):
287
+ # Process keyword arguments
288
+ recfg_list = ['figsize', 'save', 'log', 'savename', 'config_dir']
289
+ p = {}
290
+ for rec in recfg_list:
291
+ if rec in kwargs:
292
+ p[rec] = kwargs[rec]
293
+ del kwargs[rec]
294
+ else:
295
+ p[rec] = {
296
+ 'figsize': figsize,
297
+ 'save': save,
298
+ 'log': log,
299
+ 'savename': savename or func.__name__,
300
+ 'config_dir': config_dir
301
+ }.get(rec)
302
+
303
+ # Setup plot environment
304
+ plt.figure(figsize=p['figsize'])
305
+ biprint = builtins.print
306
+
307
+ # Setup logging and save paths
308
+ if p['log'] or p['save']:
309
+ # Determine save directories
310
+ if p['config_dir']:
311
+ base_path = Path(p['config_dir'])
312
+ else:
313
+ # Use platform-specific directories
314
+ if platform.system() == 'Windows':
315
+ base_path = Path.home() / 'Documents' / 'MyPlots'
316
+ elif platform.system() == 'Darwin': # macOS
317
+ base_path = Path.home() / 'Documents' / 'MyPlots'
318
+ else: # Linux
319
+ base_path = Path.home() / 'Documents' / 'MyPlots'
320
+
321
+ stats_dir = base_path / 'stats'
322
+ imags_dir = base_path / 'images'
323
+
324
+ stats_dir.mkdir(parents=True, exist_ok=True)
325
+ imags_dir.mkdir(parents=True, exist_ok=True)
326
+
327
+ if p['log'] and p['save']:
328
+ logpath = stats_dir / f"{p['savename']}.txt"
329
+ with open(logpath, 'w') as f:
330
+ pass
331
+ biprint(f"{p['savename']} created stats into path:\n{logpath.absolute()}")
332
+ builtins.print = myprint(logpath)(biprint)
333
+
334
+ # Call parent function
335
+ result = func(*args, **kwargs)
336
+
337
+ # Cleanup
338
+ plt.tight_layout()
339
+ builtins.print = biprint
340
+ if p['save']:
341
+ pltpath = imags_dir / f"{p['savename']}.png"
342
+ pltpath.parent.mkdir(parents=True, exist_ok=True)
343
+ plt.savefig(pltpath, dpi=400, bbox_inches='tight')
344
+ print(f"{p['savename']} saved figure into path:\n{pltpath.absolute()}")
345
+ plt.show()
346
+ return result
347
+
348
+ return wrap
349
+ return decorator
350
+
351
+
352
+ def myprint(target):
353
+ """Decorator for redirecting print output to a file."""
354
+ def decorator(func):
355
+ def wrap(*args, **kwargs):
356
+ temp = sys.stdout
357
+ try:
358
+ with open(target, 'a') as f:
359
+ sys.stdout = f
360
+ func(*args, **kwargs)
361
+ finally:
362
+ sys.stdout = temp
363
+ return wrap
364
+ return decorator
365
+
366
+
367
+ # Override errorbar with custom line width
368
+ def _myerrorbar(errorbar):
369
+ def wrap(*args, **kwargs):
370
+ if 'elinewidth' not in kwargs:
371
+ kwargs['elinewidth'] = elinewidth
372
+ return errorbar(*args, **kwargs)
373
+ return wrap
374
+
375
+
376
+ plt.errorbar = _myerrorbar(plt.errorbar)
377
+ plt.Axes.errorbar = _myerrorbar(plt.Axes.errorbar)
378
+
379
+
380
+ class myUtilities:
381
+ """Utility functions for plot formatting."""
382
+
383
+ @staticmethod
384
+ def radFormatter(dig='.2f'):
385
+ """Create radian tick formatter (e.g., 0.5π)."""
386
+ return ticker.FuncFormatter(lambda x, pos: f"{x / np.pi:{dig}}π")
387
+
388
+ @staticmethod
389
+ def degFormatter(dig='1.0f'):
390
+ """Create degree tick formatter (e.g., 90°)."""
391
+ return ticker.FuncFormatter(lambda x, pos: f"{x:{dig}}°")
392
+
393
+ @staticmethod
394
+ def dBFormatter(dig='1.2f'):
395
+ """Create dB tick formatter."""
396
+ return ticker.FuncFormatter(lambda x, pos: f"{x:{dig}} dB")
397
+
398
+ @staticmethod
399
+ def radLocator(base=np.pi / 4):
400
+ """Create radian tick locator."""
401
+ return ticker.MultipleLocator(base=base)
402
+
403
+ @staticmethod
404
+ def get_available_fonts():
405
+ """Get list of available fonts."""
406
+ return fm.findSystemFonts()
407
+
408
+ @staticmethod
409
+ def get_font_properties(font_path):
410
+ """Get font properties from font file."""
411
+ return fm.FontProperties(fname=str(font_path))
412
+
413
+
414
+ # Examples for documentation
415
+ if __name__ == "__main__":
416
+ # Test the module
417
+ print(f"Platform: {platform.system()}")
418
+ print(f"Font setup successful: {setup_successful}")
419
+
420
+ # List some available fonts
421
+ available_fonts = myUtilities.get_available_fonts()
422
+ print(f"Found {len(available_fonts)} fonts")
423
+
424
+ # Test font finding
425
+ source_han_font = find_source_han_serif_font()
426
+ if source_han_font:
427
+ print(f"Source Han Serif found at: {source_han_font}")
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: myplot
3
+ Version: 0.1.0
4
+ Summary: A uniform plotting module for matplotlib with automatic font detection
5
+ Requires-Python: >=3.8
6
+ Requires-Dist: matplotlib>=3.5.0
7
+ Requires-Dist: numpy>=1.20.0
8
+ Description-Content-Type: text/markdown
9
+
10
+ # MyPlot
11
+
12
+ A uniform plotting module for matplotlib with automatic font detection and cross-platform support.
13
+
14
+ ## Features
15
+
16
+ - **Uniform Formatting**: Consistent plot styling across all your projects
17
+ - **Automatic Font Detection**: Finds and configures Source Han Serif font for Chinese characters
18
+ - **Cross-Platform Support**: Works on Windows, macOS, and Linux
19
+ - **Easy to Use**: Simple decorator interface for creating publication-quality plots
20
+ - **Built-in Utilities**: Radian/degree formatters and other plotting utilities
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ pip install -e .
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```python
31
+ from myplot import myplot, plt, np
32
+
33
+ @myplot()
34
+ def simple_plot():
35
+ x = np.linspace(0, 10, 100)
36
+ plt.plot(x, np.sin(x))
37
+ plt.xlabel('x')
38
+ plt.ylabel('sin(x)')
39
+ plt.title('Simple Plot')
40
+
41
+ simple_plot()
42
+ ```
43
+
44
+ ## Advanced Usage
45
+
46
+ ### Custom Figure Size
47
+ ```python
48
+ @myplot(figsize=(8, 6))
49
+ def large_plot():
50
+ # Your plotting code here
51
+ pass
52
+ ```
53
+
54
+ ### Save Plots Automatically
55
+ ```python
56
+ @myplot(save=True, log=True)
57
+ def save_plot():
58
+ # Plot will be saved automatically
59
+ pass
60
+ ```
61
+
62
+ ### Using Utilities
63
+ ```python
64
+ from myplot import myUtilities
65
+
66
+ ax.xaxis.set_major_formatter(myUtilities.radFormatter())
67
+ ax.xaxis.set_major_locator(myUtilities.radLocator(np.pi/4))
68
+ ```
69
+
70
+ ## Requirements
71
+
72
+ - Python 3.8+
73
+ - matplotlib >= 3.5.0
74
+ - numpy >= 1.20.0
75
+
76
+ ## License
77
+
78
+ MIT License
@@ -0,0 +1,5 @@
1
+ myplot/README.md,sha256=_lfv96hxr9egsEzmma1SieNolWcDQCrjIm90l3C4PR4,2152
2
+ myplot/__init__.py,sha256=hEohqksp6EjfFkWBn64J4TIE3eTHQIVQoF_bhyrh60U,14532
3
+ myplot-0.1.0.dist-info/METADATA,sha256=OzI1Y8dw-eUlv-bVya6y5E4spUoOJE0tX8uPVz1gRFw,1629
4
+ myplot-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
5
+ myplot-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any