Skip to content

Commit

Permalink
abstracting plotting (#278)
Browse files Browse the repository at this point in the history
* abstracting plotting

* moving plotting code to its own file, correcting data output, readme

* correcting ci install for gnuplot

* fixing gnuplot install for linux

* running tests after installing gnuplot

* Update pep8_autoformat.yml
  • Loading branch information
K20shores authored Dec 2, 2024
1 parent 11207c6 commit 0d566a2
Show file tree
Hide file tree
Showing 10 changed files with 271 additions and 104 deletions.
12 changes: 12 additions & 0 deletions .github/workflows/CI_Tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ jobs:
- name: Install this package
run: pip install -e '.[dev]'

- name: Install gnuplot
if: runner.os == 'Linux'
run: sudo apt-get install gnuplot

- name: Install gnuplot
if: runner.os == 'macOS'
run: brew install gnuplot

- name: Install gnuplot
if: runner.os == 'Windows'
run: choco install gnuplot

- name: Run tests and generate coverage reports
run: pytest --cov src/

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pep8_autoformat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: autopep8
uses: peter-evans/autopep8@v2
with:
args: --recursive --in-place --aggressive --aggressive --max-line-length 180 .
args: --recursive --in-place --aggressive --max-line-length 180 --indent-size=2 .

- name: Check for changes
id: check-changes
Expand All @@ -42,4 +42,4 @@ jobs:
commit-message: "Auto-format code using autopep8"
title: "Auto-format code by autopep8"
body: "This is an auto-generated PR with fixes by autopep8."
branch: main-autopep8
branch: main-autopep8
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ Run an example. Notice that the output, in csv format, is printed to the termina
music_box -e Chapman
```

Output can be saved to a file in csv file when no `--output-format` is passed
Output can be saved to a csv file and printed to the terminal.

```
music_box -e Chapman -o output.csv
```

Output can be saved to a file as csv file when `--output-format` csv is passed
Output can be saved to a csv file and the terminal output can be suppressed by specifying the `--output-format`

```
music_box --output-format csv -e Chapman -o output.csv
Expand All @@ -51,13 +51,13 @@ Output can be saved to a file as netcdf file when `--output-format` netcdf is pa
music_box --output-format netcdf -e Chapman -o output.nc
```

Output can be saved to a file in csv file to output.csv when no output path is given but `--output-format` is csv
Output can be saved to a file in csv format when a filename is not specified. In this case a timestamped csv file is made

```
music_box --output-format csv -e Chapman
```

Output can be saved to a file in netcdf file to output.nc when no output path is given but `--output-format` is netcdf
Output can be saved to a file in netcdf format when a filename is not specified. In this case a timestamped netcdf file is made

```
music_box --output-format netcdf -e Chapman
Expand All @@ -69,10 +69,21 @@ You can also run your own configuration
music_box -c my_config.json
```

And, if you have gnuplot installed, some basic plots can be made to show some resulting concentrations
## Plotting
Some basic plots can be made to show concentrations throughout the simulation

### matplotlib

```
music_box -e Chapman -o output.csv --plot O1D
```

### gnuplot
If you want ascii plots (maybe you're running over ssh and can't view a graphical window), you can set
the plot tool to gnuplo (`--plot-tool gnuplot`) to view some output

```
music_box -e Chapman -o output.csv --color-output --plot CONC.O1D
music_box -e Chapman -o output.csv --plot O1D --plot-tool gnuplot
```

# Development and Contributing
Expand Down
1 change: 1 addition & 0 deletions src/acom_music_box/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
from .music_box import MusicBox
from .examples import Examples
from .data_output import DataOutput
from .plot_output import PlotOutput
26 changes: 9 additions & 17 deletions src/acom_music_box/data_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,6 @@ class DataOutput:
This class manages file paths, unit mappings, and data output formats based on
the provided arguments, ensuring valid paths and creating necessary directories.
Parameters
----------
df : pandas.DataFrame
The DataFrame containing the data to output.
args : argparse.Namespace
Arguments specifying output path, format, and additional options.
Attributes
----------
df : pandas.DataFrame
Expand Down Expand Up @@ -64,12 +57,12 @@ def __init__(self, df, args):
- output_format : str, optional
Format of the output file, either 'csv' or 'netcdf'. Defaults to 'csv'.
"""
self.df = df
self.df = df.copy(deep=True)
self.args = args
self.unit_mapping = {
'ENV.temperature': 'K',
'ENV.pressure': 'Pa',
'ENV.number_density_air': 'kg -m3',
'ENV.number_density_air': 'kg m-3',
'time': 's'
}

Expand Down Expand Up @@ -110,7 +103,7 @@ def _convert_to_netcdf(self):

ds['ENV.temperature'].attrs = {'units': 'K'}
ds['ENV.pressure'].attrs = {'units': 'Pa'}
ds['ENV.number_density_air'].attrs = {'units': 'kg -m3'}
ds['ENV.number_density_air'].attrs = {'units': 'kg m-3'}
ds['time'].attrs = {'units': 's'}

ds.to_netcdf(self.args.output)
Expand Down Expand Up @@ -146,11 +139,10 @@ def output(self):
# Determine output type and call the respective method
if self.args.output_format is None or self.args.output_format == 'terminal':
self._output_terminal()
elif self.args.output_format is None or self.args.output_format == 'csv':

# Even if we are printing to the terminal, we still allow output to be written to csv if an output path is provided
if (self.args.output_format == 'csv') or (self.args.output is not None and self.args.output_format == 'terminal'):
self._output_csv()
elif self.args.output_format == 'netcdf':
self._output_netcdf()
else:
error = f"Unsupported output format: {self.args.output_format}"
logger.error(error)
raise ValueError(error)

if self.args.output_format == 'netcdf':
self._output_netcdf()
80 changes: 5 additions & 75 deletions src/acom_music_box/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
import datetime
import logging
import os
import subprocess
import tempfile
import matplotlib.pyplot as plt
import mplcursors
from acom_music_box import MusicBox, Examples, __version__, DataOutput
import sys
from acom_music_box import MusicBox, Examples, __version__, DataOutput, PlotOutput


def format_examples_help(examples):
Expand Down Expand Up @@ -97,69 +94,6 @@ def setup_logging(verbosity, color_output):
logging.basicConfig(level=log_level, handlers=[console_handler])


def plot_with_gnuplot(data, species_list):
# Prepare columns and data for plotting
columns = ['time'] + species_list
data_to_plot = data[columns]

data_csv = data_to_plot.to_csv(index=False)

try:
with tempfile.NamedTemporaryFile(delete=False, suffix='.csv') as data_file:
data_file.write(data_csv.encode())
data_file_path = data_file.name

plot_commands = ',\n\t'.join(
f"'{data_file_path}' using 1:{i+2} with lines title '{species}'" for i,
species in enumerate(species_list))

gnuplot_command = f"""
set datafile separator ",";
set terminal dumb size 120,25;
set xlabel 'Time';
set ylabel 'Value';
set title 'Time vs Species';
plot {plot_commands}
"""

subprocess.run(['gnuplot', '-e', gnuplot_command], check=True)
except FileNotFoundError:
logging.critical("gnuplot is not installed. Skipping plotting.")
except subprocess.CalledProcessError as e:
logging.error(f"Error occurred while plotting: {e}")
finally:
# Clean up the temporary file
if data_file_path:
os.remove(data_file_path)


def plot_with_matplotlib(data, species_list):
# Prepare columns and data for plotting
indexed = data.set_index('time')

fig, ax = plt.subplots()
indexed[species_list].plot(ax=ax)

ax.set(xlabel='Time [s]', ylabel='Concentration [mol m-3]', title='Time vs Species')

ax.spines[:].set_visible(False)
ax.spines['left'].set_visible(True)
ax.spines['bottom'].set_visible(True)

ax.grid(alpha=0.5)
ax.legend()

# Enable interactive data cursors with hover functionality
cursor = mplcursors.cursor(hover=True)

# Customize the annotation format
@cursor.connect("add")
def on_add(sel):
sel.annotation.set_text(f'Time: {sel.target[0]:.2f}\nConcentration: {sel.target[1]:1.2e}')

plt.show()


def main():
start = datetime.datetime.now()

Expand All @@ -180,8 +114,6 @@ def main():
else:
musicBoxConfigFile = args.config

musicBoxOutputPath = args.output

plot_species_list = args.plot.split(',') if args.plot else None

if not musicBoxConfigFile:
Expand All @@ -200,11 +132,9 @@ def main():
dataOutput = DataOutput(result, args)
dataOutput.output()

if plot_species_list:
if args.plot_tool == 'gnuplot':
plot_with_gnuplot(result, plot_species_list)
elif args.plot_tool == 'matplotlib':
plot_with_matplotlib(result, plot_species_list)
# Create an instance of PlotOutput
plotOutput = PlotOutput(result, args)
plotOutput.plot()

end = datetime.datetime.now()
logger.info(f"End time: {end}")
Expand Down
Loading

0 comments on commit 0d566a2

Please sign in to comment.