eagleplot 3.0.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.
- eagleplot/Home.py +362 -0
- eagleplot/__init__.py +19 -0
- eagleplot/annotations.py +215 -0
- eagleplot/assets/logo.ico +0 -0
- eagleplot/assets/logo.png +0 -0
- eagleplot/assets/nccu-horiz-logo.png +0 -0
- eagleplot/assets/nccu-wings.png +0 -0
- eagleplot/batch.py +446 -0
- eagleplot/cli.py +719 -0
- eagleplot/data_tools.py +849 -0
- eagleplot/example-data/Artificial/bimodal_distribution.csv +501 -0
- eagleplot/example-data/Artificial/correlation_matrix.npy +0 -0
- eagleplot/example-data/Artificial/gaussian_surface.npy +0 -0
- eagleplot/example-data/Artificial/grouped_categorical.csv +241 -0
- eagleplot/example-data/Artificial/ir_spectrum.csv +1802 -0
- eagleplot/example-data/Artificial/molecular_properties.csv +121 -0
- eagleplot/example-data/Artificial/normal_distribution.csv +401 -0
- eagleplot/example-data/Artificial/reaction_kinetics.csv +121 -0
- eagleplot/example-data/Artificial/scatter_correlation.csv +201 -0
- eagleplot/example-data/Artificial/sine_cosine_waves.csv +301 -0
- eagleplot/gallery/box_plot.png +0 -0
- eagleplot/gallery/contour_plot.png +0 -0
- eagleplot/gallery/distribution_plot.png +0 -0
- eagleplot/gallery/heatmap.png +0 -0
- eagleplot/gallery/histogram.png +0 -0
- eagleplot/gallery/interactive_3d_surface.png +0 -0
- eagleplot/gallery/interactive_heatmap.png +0 -0
- eagleplot/gallery/interactive_histogram.png +0 -0
- eagleplot/gallery/interactive_line.png +0 -0
- eagleplot/gallery/interactive_scatter.png +0 -0
- eagleplot/gallery/line_plot.png +0 -0
- eagleplot/gallery/manifest.json +196 -0
- eagleplot/gallery/regression_plot.png +0 -0
- eagleplot/gallery/scatter_plot.png +0 -0
- eagleplot/io.py +1008 -0
- eagleplot/math.py +1210 -0
- eagleplot/molecular/__init__.py +21 -0
- eagleplot/molecular/atom_data.py +287 -0
- eagleplot/molecular/parsers.py +747 -0
- eagleplot/nist.py +241 -0
- eagleplot/pages/10_Molecular_Viz.py +225 -0
- eagleplot/pages/11_Batch_Analysis.py +325 -0
- eagleplot/pages/12_Help.py +199 -0
- eagleplot/pages/13_Settings.py +388 -0
- eagleplot/pages/1_Data_Upload.py +521 -0
- eagleplot/pages/2_Quick_Plot.py +1693 -0
- eagleplot/pages/3_Analysis_Tools.py +2510 -0
- eagleplot/pages/4_Multi_Plot_Dashboard.py +791 -0
- eagleplot/pages/5_Advanced_Plotting.py +691 -0
- eagleplot/pages/6_Export_Results.py +442 -0
- eagleplot/pages/7_Gallery.py +201 -0
- eagleplot/pages/8_Data_Tools.py +434 -0
- eagleplot/pages/9_Spectroscopy.py +821 -0
- eagleplot/pages/__init__.py +8 -0
- eagleplot/peaks.py +513 -0
- eagleplot/plotting.py +2630 -0
- eagleplot/plugin_loader.py +208 -0
- eagleplot/report.py +448 -0
- eagleplot/signal.py +494 -0
- eagleplot/spectroscopy.py +830 -0
- eagleplot/utils/__init__.py +76 -0
- eagleplot/utils/access.py +77 -0
- eagleplot/utils/data_preview.py +352 -0
- eagleplot/utils/plot_config.py +2721 -0
- eagleplot/utils/session_state.py +535 -0
- eagleplot/utils/theming.py +312 -0
- eagleplot/utils/user_settings.py +294 -0
- eagleplot-3.0.0.dist-info/METADATA +304 -0
- eagleplot-3.0.0.dist-info/RECORD +73 -0
- eagleplot-3.0.0.dist-info/WHEEL +5 -0
- eagleplot-3.0.0.dist-info/entry_points.txt +2 -0
- eagleplot-3.0.0.dist-info/licenses/LICENSE +21 -0
- eagleplot-3.0.0.dist-info/top_level.txt +1 -0
eagleplot/cli.py
ADDED
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
"""Command-Line Interface for EaglePlot.
|
|
2
|
+
|
|
3
|
+
This module provides a comprehensive command-line interface for the EaglePlot
|
|
4
|
+
package, allowing users to perform data visualization, analysis, and batch processing
|
|
5
|
+
from the terminal.
|
|
6
|
+
|
|
7
|
+
Usage
|
|
8
|
+
-----
|
|
9
|
+
python cli.py [command] [options]
|
|
10
|
+
|
|
11
|
+
Commands
|
|
12
|
+
--------
|
|
13
|
+
plot Create a plot from data file
|
|
14
|
+
stats Calculate and display statistics
|
|
15
|
+
batch Process multiple files using config
|
|
16
|
+
compare Compare multiple datasets
|
|
17
|
+
convert Convert data between formats
|
|
18
|
+
|
|
19
|
+
Examples
|
|
20
|
+
--------
|
|
21
|
+
# Create a histogram
|
|
22
|
+
$ python cli.py plot --input data.npy --type histogram --output plot.png
|
|
23
|
+
|
|
24
|
+
# Generate statistics
|
|
25
|
+
$ python cli.py stats --input data.csv --column Temperature
|
|
26
|
+
|
|
27
|
+
# Batch processing
|
|
28
|
+
$ python cli.py batch --config batch_config.json
|
|
29
|
+
|
|
30
|
+
# Compare datasets
|
|
31
|
+
$ python cli.py compare --inputs data1.csv data2.csv --type line
|
|
32
|
+
|
|
33
|
+
# Convert data format
|
|
34
|
+
$ python cli.py convert --input data.pkl --output data.csv
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
import argparse
|
|
38
|
+
import sys
|
|
39
|
+
import json
|
|
40
|
+
import yaml
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from typing import Any, Optional
|
|
43
|
+
import numpy as np
|
|
44
|
+
import pandas as pd
|
|
45
|
+
|
|
46
|
+
# Import module functions
|
|
47
|
+
from eagleplot import __version__
|
|
48
|
+
from eagleplot.io import load_data, save_data
|
|
49
|
+
from eagleplot.math import calculate_statistics, check_normality
|
|
50
|
+
from eagleplot.plotting import (
|
|
51
|
+
histogram,
|
|
52
|
+
line_plot,
|
|
53
|
+
scatter_plot,
|
|
54
|
+
heatmap,
|
|
55
|
+
contour_plot,
|
|
56
|
+
save_figure,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Optional imports for advanced plotting
|
|
60
|
+
try:
|
|
61
|
+
from eagleplot.plotting import (
|
|
62
|
+
distribution_plot,
|
|
63
|
+
box_plot,
|
|
64
|
+
regression_plot,
|
|
65
|
+
interactive_histogram,
|
|
66
|
+
interactive_scatter,
|
|
67
|
+
interactive_line,
|
|
68
|
+
export_interactive,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
HAS_ADVANCED = True
|
|
72
|
+
except ImportError:
|
|
73
|
+
HAS_ADVANCED = False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PlottingHelperCLI:
|
|
77
|
+
"""Main CLI application class."""
|
|
78
|
+
|
|
79
|
+
def __init__(self):
|
|
80
|
+
"""Initialize the CLI application."""
|
|
81
|
+
self.verbose = False
|
|
82
|
+
self.quiet = False
|
|
83
|
+
|
|
84
|
+
def log(self, message: str, level: str = "info"):
|
|
85
|
+
"""Print log messages based on verbosity settings.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
message : str
|
|
90
|
+
Message to print
|
|
91
|
+
level : str
|
|
92
|
+
Message level: 'info', 'warning', 'error'
|
|
93
|
+
"""
|
|
94
|
+
if self.quiet and level != "error":
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
prefix = {"info": "[INFO]", "warning": "[WARNING]", "error": "[ERROR]"}.get(level, "")
|
|
98
|
+
|
|
99
|
+
if self.verbose or level in ["warning", "error"]:
|
|
100
|
+
print(f"{prefix} {message}")
|
|
101
|
+
|
|
102
|
+
def load_file(self, filepath: str, column: Optional[str] = None) -> Any:
|
|
103
|
+
"""Load data from file.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
filepath : str
|
|
108
|
+
Path to data file
|
|
109
|
+
column : str, optional
|
|
110
|
+
Column name to extract from DataFrame
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
data : Any
|
|
115
|
+
Loaded data
|
|
116
|
+
"""
|
|
117
|
+
self.log(f"Loading data from {filepath}")
|
|
118
|
+
data = load_data(filepath)
|
|
119
|
+
|
|
120
|
+
# Extract column if specified
|
|
121
|
+
if column and isinstance(data, pd.DataFrame):
|
|
122
|
+
if column not in data.columns:
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"Column '{column}' not found in DataFrame. "
|
|
125
|
+
f"Available columns: {list(data.columns)}"
|
|
126
|
+
)
|
|
127
|
+
data = data[column].values
|
|
128
|
+
self.log(f"Extracted column: {column}")
|
|
129
|
+
|
|
130
|
+
return data
|
|
131
|
+
|
|
132
|
+
def create_plot(self, args):
|
|
133
|
+
"""Create a plot based on command-line arguments.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
args : Namespace
|
|
138
|
+
Parsed command-line arguments
|
|
139
|
+
"""
|
|
140
|
+
# Load data
|
|
141
|
+
column = getattr(args, "column", None)
|
|
142
|
+
data = self.load_file(args.input, column)
|
|
143
|
+
|
|
144
|
+
# Prepare plot parameters
|
|
145
|
+
plot_kwargs = {
|
|
146
|
+
"title": getattr(args, "title", None),
|
|
147
|
+
"xlabel": getattr(args, "xlabel", None),
|
|
148
|
+
"ylabel": getattr(args, "ylabel", None),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# Add optional parameters if provided
|
|
152
|
+
color = getattr(args, "color", None)
|
|
153
|
+
if color:
|
|
154
|
+
plot_kwargs["color"] = color
|
|
155
|
+
style = getattr(args, "style", None)
|
|
156
|
+
if style:
|
|
157
|
+
plot_kwargs["linestyle"] = style
|
|
158
|
+
marker = getattr(args, "marker", None)
|
|
159
|
+
if marker:
|
|
160
|
+
plot_kwargs["marker"] = marker
|
|
161
|
+
xlim = getattr(args, "xlim", None)
|
|
162
|
+
if xlim:
|
|
163
|
+
plot_kwargs["xlim"] = tuple(map(float, xlim.split(",")))
|
|
164
|
+
ylim = getattr(args, "ylim", None)
|
|
165
|
+
if ylim:
|
|
166
|
+
plot_kwargs["ylim"] = tuple(map(float, ylim.split(",")))
|
|
167
|
+
|
|
168
|
+
# Create plot based on type
|
|
169
|
+
self.log(f"Creating {args.type} plot")
|
|
170
|
+
|
|
171
|
+
if args.type == "histogram":
|
|
172
|
+
# Flatten multi-dimensional arrays for histogram
|
|
173
|
+
if isinstance(data, np.ndarray) and data.ndim > 1:
|
|
174
|
+
data = data.flatten()
|
|
175
|
+
fig, ax, hist_data = histogram(data, bins=args.bins, **plot_kwargs)
|
|
176
|
+
|
|
177
|
+
elif args.type == "line":
|
|
178
|
+
if isinstance(data, pd.DataFrame):
|
|
179
|
+
x = data.iloc[:, 0].values
|
|
180
|
+
y = data.iloc[:, 1].values
|
|
181
|
+
elif isinstance(data, np.ndarray) and data.ndim == 2:
|
|
182
|
+
x = data[:, 0]
|
|
183
|
+
y = data[:, 1]
|
|
184
|
+
else:
|
|
185
|
+
x = np.arange(len(data))
|
|
186
|
+
y = data
|
|
187
|
+
fig, ax = line_plot(x, y, **plot_kwargs)
|
|
188
|
+
|
|
189
|
+
elif args.type == "scatter":
|
|
190
|
+
if isinstance(data, pd.DataFrame):
|
|
191
|
+
x = data.iloc[:, 0].values
|
|
192
|
+
y = data.iloc[:, 1].values
|
|
193
|
+
elif isinstance(data, np.ndarray) and data.ndim == 2:
|
|
194
|
+
x = data[:, 0]
|
|
195
|
+
y = data[:, 1]
|
|
196
|
+
else:
|
|
197
|
+
raise ValueError("Scatter plot requires 2D data (x, y)")
|
|
198
|
+
fig, ax = scatter_plot(x, y, **plot_kwargs)
|
|
199
|
+
|
|
200
|
+
elif args.type == "heatmap":
|
|
201
|
+
if not isinstance(data, np.ndarray) or data.ndim != 2:
|
|
202
|
+
raise ValueError("Heatmap requires 2D array data")
|
|
203
|
+
fig, ax = heatmap(data, **plot_kwargs)
|
|
204
|
+
|
|
205
|
+
elif args.type == "contour":
|
|
206
|
+
if isinstance(data, dict) and "x" in data and "y" in data and "z" in data:
|
|
207
|
+
x, y, z = data["x"], data["y"], data["z"]
|
|
208
|
+
elif isinstance(data, np.ndarray) and data.ndim == 3 and data.shape[0] == 3:
|
|
209
|
+
x, y, z = data[0], data[1], data[2]
|
|
210
|
+
else:
|
|
211
|
+
raise ValueError("Contour plot requires x, y, z data")
|
|
212
|
+
fig, ax = contour_plot(x, y, z, **plot_kwargs)
|
|
213
|
+
|
|
214
|
+
# Advanced plot types (if available)
|
|
215
|
+
elif args.type == "distribution" and HAS_ADVANCED:
|
|
216
|
+
kind = args.dist_kind if hasattr(args, "dist_kind") else "hist"
|
|
217
|
+
fig, ax = distribution_plot(data, kind=kind, **plot_kwargs)
|
|
218
|
+
|
|
219
|
+
elif args.type == "box" and HAS_ADVANCED:
|
|
220
|
+
if not isinstance(data, pd.DataFrame):
|
|
221
|
+
raise ValueError("Box plot requires DataFrame input")
|
|
222
|
+
x_col = args.x_column if hasattr(args, "x_column") else None
|
|
223
|
+
y_col = args.y_column if hasattr(args, "y_column") else None
|
|
224
|
+
fig, ax = box_plot(data, x=x_col, y=y_col, **plot_kwargs)
|
|
225
|
+
|
|
226
|
+
elif args.type == "regression" and HAS_ADVANCED:
|
|
227
|
+
if isinstance(data, pd.DataFrame):
|
|
228
|
+
x = data.iloc[:, 0].values
|
|
229
|
+
y = data.iloc[:, 1].values
|
|
230
|
+
else:
|
|
231
|
+
x = data[:, 0]
|
|
232
|
+
y = data[:, 1]
|
|
233
|
+
order = args.order if hasattr(args, "order") else 1
|
|
234
|
+
fig, ax = regression_plot(x, y, order=order, **plot_kwargs)
|
|
235
|
+
|
|
236
|
+
# Interactive plots
|
|
237
|
+
elif (
|
|
238
|
+
args.type in ["interactive_histogram", "interactive_scatter", "interactive_line"]
|
|
239
|
+
and HAS_ADVANCED
|
|
240
|
+
):
|
|
241
|
+
if args.type == "interactive_histogram":
|
|
242
|
+
fig = interactive_histogram(data, bins=args.bins, title=args.title)
|
|
243
|
+
elif args.type == "interactive_scatter":
|
|
244
|
+
x = data[:, 0] if data.ndim == 2 else np.arange(len(data))
|
|
245
|
+
y = data[:, 1] if data.ndim == 2 else data
|
|
246
|
+
fig = interactive_scatter(x, y, title=args.title)
|
|
247
|
+
elif args.type == "interactive_line":
|
|
248
|
+
x = np.arange(len(data))
|
|
249
|
+
y = data
|
|
250
|
+
fig = interactive_line(x, y, title=args.title)
|
|
251
|
+
|
|
252
|
+
# Export interactive plot
|
|
253
|
+
output_path = args.output or "plot.html"
|
|
254
|
+
export_interactive(fig, output_path)
|
|
255
|
+
self.log(f"Interactive plot saved to {output_path}")
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
else:
|
|
259
|
+
raise ValueError(f"Unsupported plot type: {args.type}")
|
|
260
|
+
|
|
261
|
+
# Save figure
|
|
262
|
+
output_path = args.output or "plot.png"
|
|
263
|
+
dpi = args.dpi if hasattr(args, "dpi") else 300
|
|
264
|
+
save_figure(fig, output_path, dpi=dpi)
|
|
265
|
+
self.log(f"Plot saved to {output_path}")
|
|
266
|
+
|
|
267
|
+
def calculate_stats(self, args):
|
|
268
|
+
"""Calculate and display statistics.
|
|
269
|
+
|
|
270
|
+
Parameters
|
|
271
|
+
----------
|
|
272
|
+
args : Namespace
|
|
273
|
+
Parsed command-line arguments
|
|
274
|
+
"""
|
|
275
|
+
# Load data
|
|
276
|
+
column = getattr(args, "column", None)
|
|
277
|
+
data = self.load_file(args.input, column)
|
|
278
|
+
|
|
279
|
+
# Ensure data is numeric array
|
|
280
|
+
if isinstance(data, pd.DataFrame):
|
|
281
|
+
data = data.select_dtypes(include=[np.number]).values.flatten()
|
|
282
|
+
elif not isinstance(data, np.ndarray):
|
|
283
|
+
data = np.array(data)
|
|
284
|
+
|
|
285
|
+
# Calculate statistics
|
|
286
|
+
self.log("Calculating statistics...")
|
|
287
|
+
stats = calculate_statistics(data)
|
|
288
|
+
|
|
289
|
+
# Display statistics
|
|
290
|
+
print("\n" + "=" * 50)
|
|
291
|
+
print("Statistical Summary")
|
|
292
|
+
print("=" * 50)
|
|
293
|
+
for key, value in stats.items():
|
|
294
|
+
print(f"{key:15s}: {value:12.6f}")
|
|
295
|
+
print("=" * 50)
|
|
296
|
+
|
|
297
|
+
# Check normality if requested
|
|
298
|
+
if hasattr(args, "normality") and args.normality:
|
|
299
|
+
normality_result = check_normality(data)
|
|
300
|
+
print("\nNormality Test (Shapiro-Wilk):")
|
|
301
|
+
print(f" Statistic: {normality_result['statistic']:.6f}")
|
|
302
|
+
print(f" P-value: {normality_result['p_value']:.6f}")
|
|
303
|
+
print(
|
|
304
|
+
f" Result: {'Normal' if normality_result['is_normal'] else 'Not Normal'}"
|
|
305
|
+
" (alpha=0.05)"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# Save to file if requested
|
|
309
|
+
if args.output:
|
|
310
|
+
output_data = {**stats}
|
|
311
|
+
if hasattr(args, "normality") and args.normality:
|
|
312
|
+
output_data.update(normality_result)
|
|
313
|
+
|
|
314
|
+
with open(args.output, "w") as f:
|
|
315
|
+
json.dump(output_data, f, indent=2)
|
|
316
|
+
self.log(f"Statistics saved to {args.output}")
|
|
317
|
+
|
|
318
|
+
def batch_process(self, args):
|
|
319
|
+
"""Process multiple files using configuration.
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
args : Namespace
|
|
324
|
+
Parsed command-line arguments
|
|
325
|
+
"""
|
|
326
|
+
# Load configuration
|
|
327
|
+
config_path = Path(args.config)
|
|
328
|
+
if not config_path.exists():
|
|
329
|
+
raise FileNotFoundError(f"Config file not found: {config_path}")
|
|
330
|
+
|
|
331
|
+
self.log(f"Loading batch configuration from {config_path}")
|
|
332
|
+
|
|
333
|
+
# Parse config based on extension
|
|
334
|
+
if config_path.suffix in [".yaml", ".yml"]:
|
|
335
|
+
with open(config_path, "r") as f:
|
|
336
|
+
config = yaml.safe_load(f)
|
|
337
|
+
elif config_path.suffix == ".json":
|
|
338
|
+
with open(config_path, "r") as f:
|
|
339
|
+
config = json.load(f)
|
|
340
|
+
else:
|
|
341
|
+
raise ValueError("Config file must be JSON or YAML format")
|
|
342
|
+
|
|
343
|
+
# Process each task
|
|
344
|
+
tasks = config.get("tasks", [])
|
|
345
|
+
self.log(f"Processing {len(tasks)} task(s)")
|
|
346
|
+
|
|
347
|
+
for i, task in enumerate(tasks, 1):
|
|
348
|
+
print(f"\n[{i}/{len(tasks)}] Processing task: {task.get('name', f'Task {i}')}")
|
|
349
|
+
|
|
350
|
+
# Create args namespace from task
|
|
351
|
+
task_args = argparse.Namespace(**task)
|
|
352
|
+
|
|
353
|
+
# Execute appropriate command
|
|
354
|
+
command = task.get("command", "plot")
|
|
355
|
+
if command == "plot":
|
|
356
|
+
self.create_plot(task_args)
|
|
357
|
+
elif command == "stats":
|
|
358
|
+
self.calculate_stats(task_args)
|
|
359
|
+
else:
|
|
360
|
+
print(f" Warning: Unknown command '{command}', skipping")
|
|
361
|
+
|
|
362
|
+
self.log("Batch processing complete")
|
|
363
|
+
|
|
364
|
+
def compare_datasets(self, args):
|
|
365
|
+
"""Compare multiple datasets on same plot.
|
|
366
|
+
|
|
367
|
+
Parameters
|
|
368
|
+
----------
|
|
369
|
+
args : Namespace
|
|
370
|
+
Parsed command-line arguments
|
|
371
|
+
"""
|
|
372
|
+
# Load all input files
|
|
373
|
+
datasets = []
|
|
374
|
+
labels = []
|
|
375
|
+
|
|
376
|
+
for i, filepath in enumerate(args.inputs):
|
|
377
|
+
data = self.load_file(filepath, args.column)
|
|
378
|
+
datasets.append(data)
|
|
379
|
+
|
|
380
|
+
# Generate label
|
|
381
|
+
if args.labels and i < len(args.labels):
|
|
382
|
+
labels.append(args.labels[i])
|
|
383
|
+
else:
|
|
384
|
+
labels.append(Path(filepath).stem)
|
|
385
|
+
|
|
386
|
+
self.log(f"Comparing {len(datasets)} dataset(s)")
|
|
387
|
+
|
|
388
|
+
# Create comparison plot
|
|
389
|
+
if args.type == "histogram":
|
|
390
|
+
import matplotlib.pyplot as plt
|
|
391
|
+
|
|
392
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
393
|
+
for data, label in zip(datasets, labels):
|
|
394
|
+
ax.hist(data, bins=args.bins, alpha=0.6, label=label)
|
|
395
|
+
ax.set_xlabel(args.xlabel or "Value")
|
|
396
|
+
ax.set_ylabel(args.ylabel or "Frequency")
|
|
397
|
+
ax.set_title(args.title or "Dataset Comparison")
|
|
398
|
+
ax.legend()
|
|
399
|
+
ax.grid(True, alpha=0.3)
|
|
400
|
+
|
|
401
|
+
elif args.type == "line":
|
|
402
|
+
import matplotlib.pyplot as plt
|
|
403
|
+
|
|
404
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
405
|
+
for data, label in zip(datasets, labels):
|
|
406
|
+
x = np.arange(len(data))
|
|
407
|
+
ax.plot(x, data, label=label, marker="o", markersize=3)
|
|
408
|
+
ax.set_xlabel(args.xlabel or "Index")
|
|
409
|
+
ax.set_ylabel(args.ylabel or "Value")
|
|
410
|
+
ax.set_title(args.title or "Dataset Comparison")
|
|
411
|
+
ax.legend()
|
|
412
|
+
ax.grid(True, alpha=0.3)
|
|
413
|
+
|
|
414
|
+
elif args.type == "scatter":
|
|
415
|
+
import matplotlib.pyplot as plt
|
|
416
|
+
|
|
417
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
418
|
+
for data, label in zip(datasets, labels):
|
|
419
|
+
if data.ndim == 2:
|
|
420
|
+
ax.scatter(data[:, 0], data[:, 1], label=label, alpha=0.6)
|
|
421
|
+
ax.set_xlabel(args.xlabel or "X")
|
|
422
|
+
ax.set_ylabel(args.ylabel or "Y")
|
|
423
|
+
ax.set_title(args.title or "Dataset Comparison")
|
|
424
|
+
ax.legend()
|
|
425
|
+
ax.grid(True, alpha=0.3)
|
|
426
|
+
|
|
427
|
+
else:
|
|
428
|
+
raise ValueError(f"Comparison plot type '{args.type}' not supported")
|
|
429
|
+
|
|
430
|
+
# Save figure
|
|
431
|
+
output_path = args.output or "comparison.png"
|
|
432
|
+
dpi = args.dpi if hasattr(args, "dpi") else 300
|
|
433
|
+
save_figure(fig, output_path, dpi=dpi)
|
|
434
|
+
self.log(f"Comparison plot saved to {output_path}")
|
|
435
|
+
|
|
436
|
+
def convert_data(self, args):
|
|
437
|
+
"""Convert data between formats.
|
|
438
|
+
|
|
439
|
+
Parameters
|
|
440
|
+
----------
|
|
441
|
+
args : Namespace
|
|
442
|
+
Parsed command-line arguments
|
|
443
|
+
"""
|
|
444
|
+
# Load data
|
|
445
|
+
self.log(f"Loading data from {args.input}")
|
|
446
|
+
data = load_data(args.input)
|
|
447
|
+
|
|
448
|
+
# Save in new format
|
|
449
|
+
self.log(f"Converting to {Path(args.output).suffix} format")
|
|
450
|
+
save_data(data, args.output)
|
|
451
|
+
self.log(f"Data saved to {args.output}")
|
|
452
|
+
|
|
453
|
+
def show_examples(self):
|
|
454
|
+
"""Display usage examples."""
|
|
455
|
+
examples = """
|
|
456
|
+
PLOTTING HELPER - USAGE EXAMPLES
|
|
457
|
+
================================
|
|
458
|
+
|
|
459
|
+
1. Create a histogram from NumPy array:
|
|
460
|
+
$ python cli.py plot --input data.npy --type histogram --bins 50 \\
|
|
461
|
+
--output histogram.png --title "Data Distribution"
|
|
462
|
+
|
|
463
|
+
2. Generate line plot from CSV:
|
|
464
|
+
$ python cli.py plot --input timeseries.csv --type line \\
|
|
465
|
+
--xlabel "Time (s)" --ylabel "Signal" --output line.png
|
|
466
|
+
|
|
467
|
+
3. Create scatter plot with customization:
|
|
468
|
+
$ python cli.py plot --input points.csv --type scatter \\
|
|
469
|
+
--color blue --marker o --output scatter.png
|
|
470
|
+
|
|
471
|
+
4. Calculate statistics:
|
|
472
|
+
$ python cli.py stats --input data.csv --column Temperature \\
|
|
473
|
+
--output stats.json --normality
|
|
474
|
+
|
|
475
|
+
5. Batch processing with config file:
|
|
476
|
+
$ python cli.py batch --config batch_plots.json
|
|
477
|
+
|
|
478
|
+
6. Compare multiple datasets:
|
|
479
|
+
$ python cli.py compare --inputs data1.csv data2.csv data3.csv \\
|
|
480
|
+
--type line --labels "Trial 1" "Trial 2" "Trial 3" \\
|
|
481
|
+
--output comparison.png
|
|
482
|
+
|
|
483
|
+
7. Convert data format:
|
|
484
|
+
$ python cli.py convert --input data.pkl --output data.csv
|
|
485
|
+
|
|
486
|
+
8. Interactive plot (Plotly):
|
|
487
|
+
$ python cli.py plot --input data.npy --type interactive_histogram \\
|
|
488
|
+
--output interactive.html
|
|
489
|
+
|
|
490
|
+
9. Create heatmap:
|
|
491
|
+
$ python cli.py plot --input matrix.npy --type heatmap \\
|
|
492
|
+
--title "Correlation Matrix" --output heatmap.png
|
|
493
|
+
|
|
494
|
+
10. Advanced: Distribution plot with Seaborn:
|
|
495
|
+
$ python cli.py plot --input data.csv --column Values \\
|
|
496
|
+
--type distribution --output dist.png
|
|
497
|
+
|
|
498
|
+
For more help, use: python cli.py [command] --help
|
|
499
|
+
"""
|
|
500
|
+
print(examples)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def main():
|
|
504
|
+
"""Main entry point for the command-line interface.
|
|
505
|
+
|
|
506
|
+
Returns
|
|
507
|
+
-------
|
|
508
|
+
int
|
|
509
|
+
Exit code (0 for success, non-zero for errors).
|
|
510
|
+
"""
|
|
511
|
+
# Create main parser
|
|
512
|
+
parser = argparse.ArgumentParser(
|
|
513
|
+
description="EaglePlot - Scientific Data Visualization and Analysis Toolkit",
|
|
514
|
+
epilog="NCCU Department of Chemistry and Biochemistry — Schultz Lab",
|
|
515
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
parser.add_argument("--version", action="version", version=f"EaglePlot {__version__}")
|
|
519
|
+
|
|
520
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output")
|
|
521
|
+
|
|
522
|
+
parser.add_argument(
|
|
523
|
+
"--quiet", "-q", action="store_true", help="Suppress informational messages"
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
parser.add_argument("--examples", action="store_true", help="Show usage examples and exit")
|
|
527
|
+
|
|
528
|
+
# Create subparsers for commands
|
|
529
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
530
|
+
|
|
531
|
+
# ========================================================================
|
|
532
|
+
# PLOT command
|
|
533
|
+
# ========================================================================
|
|
534
|
+
plot_parser = subparsers.add_parser("plot", help="Create a plot from data")
|
|
535
|
+
|
|
536
|
+
plot_parser.add_argument("--input", "-i", required=True, help="Input data file path")
|
|
537
|
+
|
|
538
|
+
plot_parser.add_argument(
|
|
539
|
+
"--output", "-o", help="Output file path (default: plot.png or plot.html)"
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
plot_parser.add_argument(
|
|
543
|
+
"--type",
|
|
544
|
+
"-t",
|
|
545
|
+
required=True,
|
|
546
|
+
choices=[
|
|
547
|
+
"histogram",
|
|
548
|
+
"line",
|
|
549
|
+
"scatter",
|
|
550
|
+
"heatmap",
|
|
551
|
+
"contour",
|
|
552
|
+
"distribution",
|
|
553
|
+
"box",
|
|
554
|
+
"regression",
|
|
555
|
+
"interactive_histogram",
|
|
556
|
+
"interactive_scatter",
|
|
557
|
+
"interactive_line",
|
|
558
|
+
],
|
|
559
|
+
help="Type of plot to create",
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
plot_parser.add_argument("--column", help="Column name to extract from DataFrame")
|
|
563
|
+
|
|
564
|
+
# Plot customization
|
|
565
|
+
plot_parser.add_argument("--title", help="Plot title")
|
|
566
|
+
plot_parser.add_argument("--xlabel", help="X-axis label")
|
|
567
|
+
plot_parser.add_argument("--ylabel", help="Y-axis label")
|
|
568
|
+
plot_parser.add_argument("--color", help="Line/marker color")
|
|
569
|
+
plot_parser.add_argument("--style", help="Line style (-, --, -., :)")
|
|
570
|
+
plot_parser.add_argument("--marker", help="Marker style (o, s, ^, etc.)")
|
|
571
|
+
plot_parser.add_argument("--xlim", help='X-axis limits (e.g., "0,10")')
|
|
572
|
+
plot_parser.add_argument("--ylim", help='Y-axis limits (e.g., "0,100")')
|
|
573
|
+
|
|
574
|
+
# Plot-specific options
|
|
575
|
+
plot_parser.add_argument(
|
|
576
|
+
"--bins",
|
|
577
|
+
type=int,
|
|
578
|
+
default=30,
|
|
579
|
+
help="Number of bins for histogram (default: 30)",
|
|
580
|
+
)
|
|
581
|
+
plot_parser.add_argument(
|
|
582
|
+
"--dpi", type=int, default=300, help="Output resolution in DPI (default: 300)"
|
|
583
|
+
)
|
|
584
|
+
plot_parser.add_argument(
|
|
585
|
+
"--dist-kind",
|
|
586
|
+
choices=["hist", "kde", "ecdf"],
|
|
587
|
+
default="hist",
|
|
588
|
+
help="Distribution plot kind (for --type distribution)",
|
|
589
|
+
)
|
|
590
|
+
plot_parser.add_argument("--x-column", help="X column name for box/scatter plots")
|
|
591
|
+
plot_parser.add_argument("--y-column", help="Y column name for box/scatter plots")
|
|
592
|
+
plot_parser.add_argument(
|
|
593
|
+
"--order",
|
|
594
|
+
type=int,
|
|
595
|
+
default=1,
|
|
596
|
+
help="Polynomial order for regression (default: 1)",
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
# ========================================================================
|
|
600
|
+
# STATS command
|
|
601
|
+
# ========================================================================
|
|
602
|
+
stats_parser = subparsers.add_parser("stats", help="Calculate statistics")
|
|
603
|
+
|
|
604
|
+
stats_parser.add_argument("--input", "-i", required=True, help="Input data file path")
|
|
605
|
+
|
|
606
|
+
stats_parser.add_argument(
|
|
607
|
+
"--output", "-o", help="Output file path for statistics (JSON format)"
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
stats_parser.add_argument("--column", help="Column name to analyze from DataFrame")
|
|
611
|
+
|
|
612
|
+
stats_parser.add_argument("--normality", action="store_true", help="Perform normality test")
|
|
613
|
+
|
|
614
|
+
# ========================================================================
|
|
615
|
+
# BATCH command
|
|
616
|
+
# ========================================================================
|
|
617
|
+
batch_parser = subparsers.add_parser("batch", help="Process multiple files using config")
|
|
618
|
+
|
|
619
|
+
batch_parser.add_argument(
|
|
620
|
+
"--config", "-c", required=True, help="Configuration file (JSON or YAML)"
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
# ========================================================================
|
|
624
|
+
# COMPARE command
|
|
625
|
+
# ========================================================================
|
|
626
|
+
compare_parser = subparsers.add_parser("compare", help="Compare multiple datasets")
|
|
627
|
+
|
|
628
|
+
compare_parser.add_argument(
|
|
629
|
+
"--inputs", nargs="+", required=True, help="Input data files to compare"
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
compare_parser.add_argument("--output", "-o", help="Output plot file (default: comparison.png)")
|
|
633
|
+
|
|
634
|
+
compare_parser.add_argument(
|
|
635
|
+
"--type",
|
|
636
|
+
"-t",
|
|
637
|
+
choices=["histogram", "line", "scatter"],
|
|
638
|
+
default="line",
|
|
639
|
+
help="Type of comparison plot (default: line)",
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
compare_parser.add_argument("--labels", nargs="+", help="Labels for each dataset")
|
|
643
|
+
|
|
644
|
+
compare_parser.add_argument("--column", help="Column name to extract from DataFrames")
|
|
645
|
+
|
|
646
|
+
compare_parser.add_argument("--title", help="Plot title")
|
|
647
|
+
compare_parser.add_argument("--xlabel", help="X-axis label")
|
|
648
|
+
compare_parser.add_argument("--ylabel", help="Y-axis label")
|
|
649
|
+
compare_parser.add_argument("--bins", type=int, default=30, help="Number of bins for histogram")
|
|
650
|
+
compare_parser.add_argument("--dpi", type=int, default=300, help="Output resolution in DPI")
|
|
651
|
+
|
|
652
|
+
# ========================================================================
|
|
653
|
+
# CONVERT command
|
|
654
|
+
# ========================================================================
|
|
655
|
+
convert_parser = subparsers.add_parser("convert", help="Convert data between formats")
|
|
656
|
+
|
|
657
|
+
convert_parser.add_argument("--input", "-i", required=True, help="Input data file")
|
|
658
|
+
|
|
659
|
+
convert_parser.add_argument(
|
|
660
|
+
"--output",
|
|
661
|
+
"-o",
|
|
662
|
+
required=True,
|
|
663
|
+
help="Output data file (format detected from extension)",
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
# Parse arguments
|
|
667
|
+
args = parser.parse_args()
|
|
668
|
+
|
|
669
|
+
# Show examples if requested
|
|
670
|
+
if args.examples:
|
|
671
|
+
cli = PlottingHelperCLI()
|
|
672
|
+
cli.show_examples()
|
|
673
|
+
return 0
|
|
674
|
+
|
|
675
|
+
# Check if command was provided
|
|
676
|
+
if not args.command:
|
|
677
|
+
parser.print_help()
|
|
678
|
+
return 1
|
|
679
|
+
|
|
680
|
+
# Initialize CLI
|
|
681
|
+
cli = PlottingHelperCLI()
|
|
682
|
+
cli.verbose = args.verbose
|
|
683
|
+
cli.quiet = args.quiet
|
|
684
|
+
|
|
685
|
+
# Execute command
|
|
686
|
+
try:
|
|
687
|
+
if args.command == "plot":
|
|
688
|
+
cli.create_plot(args)
|
|
689
|
+
elif args.command == "stats":
|
|
690
|
+
cli.calculate_stats(args)
|
|
691
|
+
elif args.command == "batch":
|
|
692
|
+
cli.batch_process(args)
|
|
693
|
+
elif args.command == "compare":
|
|
694
|
+
cli.compare_datasets(args)
|
|
695
|
+
elif args.command == "convert":
|
|
696
|
+
cli.convert_data(args)
|
|
697
|
+
else:
|
|
698
|
+
print(f"Error: Unknown command '{args.command}'")
|
|
699
|
+
return 1
|
|
700
|
+
|
|
701
|
+
return 0
|
|
702
|
+
|
|
703
|
+
except FileNotFoundError as e:
|
|
704
|
+
print(f"[ERROR] File not found: {e}")
|
|
705
|
+
return 1
|
|
706
|
+
except ValueError as e:
|
|
707
|
+
print(f"[ERROR] Invalid value: {e}")
|
|
708
|
+
return 1
|
|
709
|
+
except Exception as e:
|
|
710
|
+
print(f"[ERROR] {type(e).__name__}: {e}")
|
|
711
|
+
if args.verbose:
|
|
712
|
+
import traceback
|
|
713
|
+
|
|
714
|
+
traceback.print_exc()
|
|
715
|
+
return 1
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
if __name__ == "__main__":
|
|
719
|
+
sys.exit(main())
|