Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 69aaece

Browse files
nalquasGui-FernandesBRLucas-Pratesphmbressan
authoredNov 10, 2024
ENH: Implement optional plot saving (#597)
* ENH: Add filename option to most plot functions * MNT: Use f-strings in plot_helpers.py Co-authored-by: Gui-FernandesBR <[email protected]> * MNT: Run isort * DOC: Make docstring fit into 80 columns and reference matplotlib formats * MNT: Run black * MNT: Move get_matplotlib_supported_file_endings into tools * Apply suggestions from code review Co-authored-by: Lucas Prates <[email protected]> * Fix function/__call__ check * Change plot_helpers warnings from UserWarning to ValueError * Update docstrings * Fix issues from rebase * MNT: post merge fixes regarding plots. * MNT: post merge fixes and linting. * TST: implement testing of show or save plots. * MNT: Correct CHANGELOG file. * MNT: Improve error handling of unsupported file endings. * DOC: add doc section for plot saving. --------- Co-authored-by: Gui-FernandesBR <[email protected]> Co-authored-by: Lucas Prates <[email protected]> Co-authored-by: Pedro Bressan <[email protected]>
1 parent d0c0f61 commit 69aaece

28 files changed

+953
-225
lines changed
 

‎CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Attention: The newest changes should be on top -->
3232

3333
### Added
3434

35-
-
35+
- ENH: Implement optional plot saving [#597](https://github.com/RocketPy-Team/RocketPy/pull/597)
3636

3737
### Changed
3838

‎docs/user/first_simulation.rst

+39
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,45 @@ Finally, the :meth:`rocketpy.Flight.export_data` method also provides a convenie
654654
os.remove("calisto_flight_data.csv")
655655

656656

657+
Saving and Storing Plots
658+
------------------------
659+
660+
Here we show how to save the plots and drawings generated by RocketPy.
661+
For instance, we can save our rocket drawing as a ``.png`` file:
662+
663+
.. jupyter-execute::
664+
665+
calisto.draw(filename="calisto_drawing.png")
666+
667+
Also, if you want to save a specific rocketpy plot, every RocketPy
668+
attribute of type :class:`rocketpy.Function` is capable of saving its plot
669+
as an image file. For example, we can save our rocket's speed plot and the
670+
trajectory plot as ``.jpg`` files:
671+
672+
.. jupyter-execute::
673+
674+
test_flight.speed.plot(filename="speed_plot.jpg")
675+
test_flight.plots.trajectory_3d(filename="trajectory_plot.jpg")
676+
677+
The supported file formats are ``.eps``, ``.jpg``, ``.jpeg``, ``.pdf``,
678+
``.pgf``, ``.png``, ``.ps``, ``.raw``, ``.rgba``, ``.svg``, ``.svgz``,
679+
``.tif``, ``.tiff`` and ``.webp``. More details on manipulating data and
680+
results can be found in the :ref:`Function Class Usage <functionusage>`.
681+
682+
.. note::
683+
684+
The ``filename`` argument is optional. If not provided, the plot will be
685+
shown instead. If the provided filename is in a directory that does not
686+
exist, the directory will be created.
687+
688+
.. jupyter-execute::
689+
:hide-code:
690+
:hide-output:
691+
692+
os.remove("calisto_drawing.png")
693+
os.remove("speed_plot.jpg")
694+
os.remove("trajectory_plot.jpg")
695+
657696
Further Analysis
658697
----------------
659698

‎rocketpy/mathutils/function.py

+34-6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
RBFInterpolator,
2323
)
2424

25+
from ..plots.plot_helpers import show_or_save_plot
26+
2527
# Numpy 1.x compatibility,
2628
# TODO: remove these lines when all dependencies support numpy>=2.0.0
2729
if np.lib.NumpyVersion(np.__version__) >= "2.0.0b1":
@@ -1378,7 +1380,7 @@ def remove_outliers_iqr(self, threshold=1.5):
13781380
)
13791381

13801382
# Define all presentation methods
1381-
def __call__(self, *args):
1383+
def __call__(self, *args, filename=None):
13821384
"""Plot the Function if no argument is given. If an
13831385
argument is given, return the value of the function at the desired
13841386
point.
@@ -1392,13 +1394,18 @@ def __call__(self, *args):
13921394
evaluated at all points in the list and a list of floats will be
13931395
returned. If the function is N-D, N arguments must be given, each
13941396
one being an scalar or list.
1397+
filename : str | None, optional
1398+
The path the plot should be saved to. By default None, in which case
1399+
the plot will be shown instead of saved. Supported file endings are:
1400+
eps, jpg, jpeg, pdf, pgf, png, ps, raw, rgba, svg, svgz, tif, tiff
1401+
and webp (these are the formats supported by matplotlib).
13951402
13961403
Returns
13971404
-------
13981405
ans : None, scalar, list
13991406
"""
14001407
if len(args) == 0:
1401-
return self.plot()
1408+
return self.plot(filename=filename)
14021409
else:
14031410
return self.get_value(*args)
14041411

@@ -1459,8 +1466,11 @@ def plot(self, *args, **kwargs):
14591466
Function.plot_2d if Function is 2-Dimensional and forward arguments
14601467
and key-word arguments."""
14611468
if isinstance(self, list):
1469+
# Extract filename from kwargs
1470+
filename = kwargs.get("filename", None)
1471+
14621472
# Compare multiple plots
1463-
Function.compare_plots(self)
1473+
Function.compare_plots(self, filename)
14641474
else:
14651475
if self.__dom_dim__ == 1:
14661476
self.plot_1d(*args, **kwargs)
@@ -1488,6 +1498,7 @@ def plot_1d( # pylint: disable=too-many-statements
14881498
force_points=False,
14891499
return_object=False,
14901500
equal_axis=False,
1501+
filename=None,
14911502
):
14921503
"""Plot 1-Dimensional Function, from a lower limit to an upper limit,
14931504
by sampling the Function several times in the interval. The title of
@@ -1518,6 +1529,11 @@ def plot_1d( # pylint: disable=too-many-statements
15181529
Setting force_points to True will plot all points, as a scatter, in
15191530
which the Function was evaluated in the dataset. Default value is
15201531
False.
1532+
filename : str | None, optional
1533+
The path the plot should be saved to. By default None, in which case
1534+
the plot will be shown instead of saved. Supported file endings are:
1535+
eps, jpg, jpeg, pdf, pgf, png, ps, raw, rgba, svg, svgz, tif, tiff
1536+
and webp (these are the formats supported by matplotlib).
15211537
15221538
Returns
15231539
-------
@@ -1558,7 +1574,7 @@ def plot_1d( # pylint: disable=too-many-statements
15581574
plt.title(self.title)
15591575
plt.xlabel(self.__inputs__[0].title())
15601576
plt.ylabel(self.__outputs__[0].title())
1561-
plt.show()
1577+
show_or_save_plot(filename)
15621578
if return_object:
15631579
return fig, ax
15641580

@@ -1581,6 +1597,7 @@ def plot_2d( # pylint: disable=too-many-statements
15811597
disp_type="surface",
15821598
alpha=0.6,
15831599
cmap="viridis",
1600+
filename=None,
15841601
):
15851602
"""Plot 2-Dimensional Function, from a lower limit to an upper limit,
15861603
by sampling the Function several times in the interval. The title of
@@ -1620,6 +1637,11 @@ def plot_2d( # pylint: disable=too-many-statements
16201637
cmap : string, optional
16211638
Colormap of plotted graph, which can be any of the color maps
16221639
available in matplotlib. Default value is viridis.
1640+
filename : str | None, optional
1641+
The path the plot should be saved to. By default None, in which case
1642+
the plot will be shown instead of saved. Supported file endings are:
1643+
eps, jpg, jpeg, pdf, pgf, png, ps, raw, rgba, svg, svgz, tif, tiff
1644+
and webp (these are the formats supported by matplotlib).
16231645
16241646
Returns
16251647
-------
@@ -1692,7 +1714,7 @@ def plot_2d( # pylint: disable=too-many-statements
16921714
axes.set_xlabel(self.__inputs__[0].title())
16931715
axes.set_ylabel(self.__inputs__[1].title())
16941716
axes.set_zlabel(self.__outputs__[0].title())
1695-
plt.show()
1717+
show_or_save_plot(filename)
16961718

16971719
@staticmethod
16981720
def compare_plots( # pylint: disable=too-many-statements
@@ -1707,6 +1729,7 @@ def compare_plots( # pylint: disable=too-many-statements
17071729
force_points=False,
17081730
return_object=False,
17091731
show=True,
1732+
filename=None,
17101733
):
17111734
"""Plots N 1-Dimensional Functions in the same plot, from a lower
17121735
limit to an upper limit, by sampling the Functions several times in
@@ -1751,6 +1774,11 @@ def compare_plots( # pylint: disable=too-many-statements
17511774
False.
17521775
show : bool, optional
17531776
If True, shows the plot. Default value is True.
1777+
filename : str | None, optional
1778+
The path the plot should be saved to. By default None, in which case
1779+
the plot will be shown instead of saved. Supported file endings are:
1780+
eps, jpg, jpeg, pdf, pgf, png, ps, raw, rgba, svg, svgz, tif, tiff
1781+
and webp (these are the formats supported by matplotlib).
17541782
17551783
Returns
17561784
-------
@@ -1826,7 +1854,7 @@ def compare_plots( # pylint: disable=too-many-statements
18261854
plt.ylabel(ylabel)
18271855

18281856
if show:
1829-
plt.show()
1857+
show_or_save_plot(filename)
18301858

18311859
if return_object:
18321860
return fig, ax

‎rocketpy/motors/hybrid_motor.py

+16-13
Original file line numberDiff line numberDiff line change
@@ -601,16 +601,19 @@ def add_tank(self, tank, position):
601601
)
602602
reset_funcified_methods(self)
603603

604-
def draw(self):
605-
"""Draws a representation of the HybridMotor."""
606-
self.plots.draw()
607-
608-
def info(self):
609-
"""Prints out basic data about the Motor."""
610-
self.prints.all()
611-
self.plots.thrust()
612-
613-
def all_info(self):
614-
"""Prints out all data and graphs available about the Motor."""
615-
self.prints.all()
616-
self.plots.all()
604+
def draw(self, filename=None):
605+
"""Draws a representation of the HybridMotor.
606+
607+
Parameters
608+
----------
609+
filename : str | None, optional
610+
The path the plot should be saved to. By default None, in which case
611+
the plot will be shown instead of saved. Supported file endings are:
612+
eps, jpg, jpeg, pdf, pgf, png, ps, raw, rgba, svg, svgz, tif, tiff
613+
and webp (these are the formats supported by matplotlib).
614+
615+
Returns
616+
-------
617+
None
618+
"""
619+
self.plots.draw(filename)

‎rocketpy/motors/liquid_motor.py

+12-14
Original file line numberDiff line numberDiff line change
@@ -463,21 +463,19 @@ def add_tank(self, tank, position):
463463
self.positioned_tanks.append({"tank": tank, "position": position})
464464
reset_funcified_methods(self)
465465

466-
def draw(self):
467-
"""Draw a representation of the LiquidMotor."""
468-
self.plots.draw()
466+
def draw(self, filename=None):
467+
"""Draw a representation of the LiquidMotor.
469468
470-
def info(self):
471-
"""Prints out basic data about the Motor."""
472-
self.prints.all()
473-
self.plots.thrust()
474-
475-
def all_info(self):
476-
"""Prints out all data and graphs available about the Motor.
469+
Parameters
470+
----------
471+
filename : str | None, optional
472+
The path the plot should be saved to. By default None, in which case
473+
the plot will be shown instead of saved. Supported file endings are:
474+
eps, jpg, jpeg, pdf, pgf, png, ps, raw, rgba, svg, svgz, tif, tiff
475+
and webp (these are the formats supported by matplotlib).
477476
478-
Return
479-
------
477+
Returns
478+
-------
480479
None
481480
"""
482-
self.prints.all()
483-
self.plots.all()
481+
self.plots.draw(filename)

‎rocketpy/motors/motor.py

+14-3
Original file line numberDiff line numberDiff line change
@@ -1083,15 +1083,26 @@ def get_attr_value(obj, attr_name, multiplier=1):
10831083
# Write last line
10841084
file.write(f"{self.thrust.source[-1, 0]:.4f} {0:.3f}\n")
10851085

1086-
def info(self):
1086+
def info(self, filename=None):
10871087
"""Prints out a summary of the data and graphs available about the
10881088
Motor.
1089+
1090+
Parameters
1091+
----------
1092+
filename : str | None, optional
1093+
The path the plot should be saved to. By default None, in which case
1094+
the plot will be shown instead of saved. Supported file endings are:
1095+
eps, jpg, jpeg, pdf, pgf, png, ps, raw, rgba, svg, svgz, tif, tiff
1096+
and webp (these are the formats supported by matplotlib).
1097+
1098+
Returns
1099+
-------
1100+
None
10891101
"""
10901102
# Print motor details
10911103
self.prints.all()
1092-
self.plots.thrust()
1104+
self.plots.thrust(filename=filename)
10931105

1094-
@abstractmethod
10951106
def all_info(self):
10961107
"""Prints out all data and graphs available about the Motor."""
10971108
self.prints.all()

‎rocketpy/motors/solid_motor.py

+16-13
Original file line numberDiff line numberDiff line change
@@ -727,16 +727,19 @@ def propellant_I_13(self):
727727
def propellant_I_23(self):
728728
return 0
729729

730-
def draw(self):
731-
"""Draw a representation of the SolidMotor."""
732-
self.plots.draw()
733-
734-
def info(self):
735-
"""Prints out basic data about the SolidMotor."""
736-
self.prints.all()
737-
self.plots.thrust()
738-
739-
def all_info(self):
740-
"""Prints out all data and graphs available about the SolidMotor."""
741-
self.prints.all()
742-
self.plots.all()
730+
def draw(self, filename=None):
731+
"""Draw a representation of the SolidMotor.
732+
733+
Parameters
734+
----------
735+
filename : str | None, optional
736+
The path the plot should be saved to. By default None, in which case
737+
the plot will be shown instead of saved. Supported file endings are:
738+
eps, jpg, jpeg, pdf, pgf, png, ps, raw, rgba, svg, svgz, tif, tiff
739+
and webp (these are the formats supported by matplotlib).
740+
741+
Returns
742+
-------
743+
None
744+
"""
745+
self.plots.draw(filename)

‎rocketpy/motors/tank.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -476,9 +476,22 @@ def underfill_height_exception(param_name, param):
476476
elif (height < bottom_tolerance).any():
477477
underfill_height_exception(name, height)
478478

479-
def draw(self):
480-
"""Draws the tank geometry."""
481-
self.plots.draw()
479+
def draw(self, filename=None):
480+
"""Draws the tank geometry.
481+
482+
Parameters
483+
----------
484+
filename : str | None, optional
485+
The path the plot should be saved to. By default None, in which case
486+
the plot will be shown instead of saved. Supported file endings are:
487+
eps, jpg, jpeg, pdf, pgf, png, ps, raw, rgba, svg, svgz, tif, tiff
488+
and webp (these are the formats supported by matplotlib).
489+
490+
Returns
491+
-------
492+
None
493+
"""
494+
self.plots.draw(filename)
482495

483496

484497
class MassFlowRateBasedTank(Tank):

0 commit comments

Comments
 (0)
Please sign in to comment.