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.
Files changed (73) hide show
  1. eagleplot/Home.py +362 -0
  2. eagleplot/__init__.py +19 -0
  3. eagleplot/annotations.py +215 -0
  4. eagleplot/assets/logo.ico +0 -0
  5. eagleplot/assets/logo.png +0 -0
  6. eagleplot/assets/nccu-horiz-logo.png +0 -0
  7. eagleplot/assets/nccu-wings.png +0 -0
  8. eagleplot/batch.py +446 -0
  9. eagleplot/cli.py +719 -0
  10. eagleplot/data_tools.py +849 -0
  11. eagleplot/example-data/Artificial/bimodal_distribution.csv +501 -0
  12. eagleplot/example-data/Artificial/correlation_matrix.npy +0 -0
  13. eagleplot/example-data/Artificial/gaussian_surface.npy +0 -0
  14. eagleplot/example-data/Artificial/grouped_categorical.csv +241 -0
  15. eagleplot/example-data/Artificial/ir_spectrum.csv +1802 -0
  16. eagleplot/example-data/Artificial/molecular_properties.csv +121 -0
  17. eagleplot/example-data/Artificial/normal_distribution.csv +401 -0
  18. eagleplot/example-data/Artificial/reaction_kinetics.csv +121 -0
  19. eagleplot/example-data/Artificial/scatter_correlation.csv +201 -0
  20. eagleplot/example-data/Artificial/sine_cosine_waves.csv +301 -0
  21. eagleplot/gallery/box_plot.png +0 -0
  22. eagleplot/gallery/contour_plot.png +0 -0
  23. eagleplot/gallery/distribution_plot.png +0 -0
  24. eagleplot/gallery/heatmap.png +0 -0
  25. eagleplot/gallery/histogram.png +0 -0
  26. eagleplot/gallery/interactive_3d_surface.png +0 -0
  27. eagleplot/gallery/interactive_heatmap.png +0 -0
  28. eagleplot/gallery/interactive_histogram.png +0 -0
  29. eagleplot/gallery/interactive_line.png +0 -0
  30. eagleplot/gallery/interactive_scatter.png +0 -0
  31. eagleplot/gallery/line_plot.png +0 -0
  32. eagleplot/gallery/manifest.json +196 -0
  33. eagleplot/gallery/regression_plot.png +0 -0
  34. eagleplot/gallery/scatter_plot.png +0 -0
  35. eagleplot/io.py +1008 -0
  36. eagleplot/math.py +1210 -0
  37. eagleplot/molecular/__init__.py +21 -0
  38. eagleplot/molecular/atom_data.py +287 -0
  39. eagleplot/molecular/parsers.py +747 -0
  40. eagleplot/nist.py +241 -0
  41. eagleplot/pages/10_Molecular_Viz.py +225 -0
  42. eagleplot/pages/11_Batch_Analysis.py +325 -0
  43. eagleplot/pages/12_Help.py +199 -0
  44. eagleplot/pages/13_Settings.py +388 -0
  45. eagleplot/pages/1_Data_Upload.py +521 -0
  46. eagleplot/pages/2_Quick_Plot.py +1693 -0
  47. eagleplot/pages/3_Analysis_Tools.py +2510 -0
  48. eagleplot/pages/4_Multi_Plot_Dashboard.py +791 -0
  49. eagleplot/pages/5_Advanced_Plotting.py +691 -0
  50. eagleplot/pages/6_Export_Results.py +442 -0
  51. eagleplot/pages/7_Gallery.py +201 -0
  52. eagleplot/pages/8_Data_Tools.py +434 -0
  53. eagleplot/pages/9_Spectroscopy.py +821 -0
  54. eagleplot/pages/__init__.py +8 -0
  55. eagleplot/peaks.py +513 -0
  56. eagleplot/plotting.py +2630 -0
  57. eagleplot/plugin_loader.py +208 -0
  58. eagleplot/report.py +448 -0
  59. eagleplot/signal.py +494 -0
  60. eagleplot/spectroscopy.py +830 -0
  61. eagleplot/utils/__init__.py +76 -0
  62. eagleplot/utils/access.py +77 -0
  63. eagleplot/utils/data_preview.py +352 -0
  64. eagleplot/utils/plot_config.py +2721 -0
  65. eagleplot/utils/session_state.py +535 -0
  66. eagleplot/utils/theming.py +312 -0
  67. eagleplot/utils/user_settings.py +294 -0
  68. eagleplot-3.0.0.dist-info/METADATA +304 -0
  69. eagleplot-3.0.0.dist-info/RECORD +73 -0
  70. eagleplot-3.0.0.dist-info/WHEEL +5 -0
  71. eagleplot-3.0.0.dist-info/entry_points.txt +2 -0
  72. eagleplot-3.0.0.dist-info/licenses/LICENSE +21 -0
  73. 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())