Repository: CORE-GATECH-GROUP/serpent-tools (language: python)

Issue:
ENH Allow HomogUniv plot to be configured with keyword arguments
**Is your feature request related to a problem? Please describe.**
The HomogUniv has a very nice plot method, but I've found it to be frustrating to configure and tweak at times. Currently, there is no way to manually add markers, colors, tweak the draw style (line, steps, dashed, etc)

**Describe the solution you'd like**
By allowing the user to pass arguments as keyword arguments through to `ax.errorbar`, we can easily allow this configuration

https://github.com/CORE-GATECH-GROUP/serpent-tools/blob/ee88bf4c2e17901aba2101daba53d384313e48d2/serpentTools/objects/containers.py#L454-L455

The patch should be merged into the main branch rather than develop since there's a lot going on currently with respect to 0.10.0 and #335, #400 

**Describe alternatives you've considered**
The workaround is to extract and plot the data on your own, but we can do a little better

This is related to #417, but a smaller, more focused issue

DEP Deprecate steps plot argument in favor of matplotlib drawstyle
Initially brought up in #432 for the `HomogUniv` class. As we start forwarding more plot options to the underlying matplotlib plot functions (#417, #423, #422 and friends) there may be a point where we want to rely on users passing `drawstyle=<option>` rather than our own logic for the `step` argument.

Some examples

 https://github.com/CORE-GATECH-GROUP/serpent-tools/blob/67dc469e2697dfffbd6c06fa1bc20eea306383c9/serpentTools/objects/containers.py#L411 https://github.com/CORE-GATECH-GROUP/serpent-tools/blob/67dc469e2697dfffbd6c06fa1bc20eea306383c9/serpentTools/detectors.py#L561-L566 https://github.com/CORE-GATECH-GROUP/serpent-tools/blob/67dc469e2697dfffbd6c06fa1bc20eea306383c9/serpentTools/objects/xsdata.py#L363 https://github.com/CORE-GATECH-GROUP/serpent-tools/blob/67dc469e2697dfffbd6c06fa1bc20eea306383c9/serpentTools/parsers/sensitivity.py#L408-L409

## Roadmap

I propose that by 0.10.0 we support passing `steps` and `drawstyle` for the methods that currently support `steps`. Methods like the sensitivity plotter can have a default `drawstyle` using `kwargs.setdefault("drawstyle", ...)`. 

Instances where both are passed should raise a DeprecationWarning by 0.10.0. Following 0.11.0 we should no longer support the `steps` argument, instead falling back to the matplotlib `drawstyle` argument.

Instances where we currently allow `steps` and additional `kwargs` should raise a pending deprecation warning by 0.9.4

## Extras

Some care should be taken for the spectrum plot and other cases where we perform a line plot with some uncertainties. Here we wish to draw the errorbars in the middle of the bin (#428)


Discussion / hints:

Fix patch:
diff --git a/serpentTools/objects/containers.py b/serpentTools/objects/containers.py
index 161b938e..8c00f3a7 100644
--- a/serpentTools/objects/containers.py
+++ b/serpentTools/objects/containers.py
@@ -9,6 +9,7 @@
 """
 from itertools import product
 from collections import namedtuple
+import warnings
 
 from matplotlib import pyplot
 from numpy import arange, hstack, ndarray, zeros_like
@@ -371,7 +372,7 @@ def _lookup(self, variableName, uncertainty):
     @magicPlotDocDecorator
     def plot(self, qtys, limitE=True, ax=None, logx=None, logy=None,
              loglog=None, sigma=3, xlabel=None, ylabel=None, legend=None,
-             ncol=1, steps=True, labelFmt=None, labels=None):
+             ncol=1, steps=True, labelFmt=None, labels=None, **kwargs):
         """
         Plot homogenized data as a function of energy.
 
@@ -394,10 +395,12 @@ def plot(self, qtys, limitE=True, ax=None, logx=None, logy=None,
         {ylabel}
         {legend}
         {ncol}
-        steps: bool
+        steps : bool
             If ``True``, plot values as constant within
             energy bins.
         {univLabelFmt}
+        {kwargs} :func:`matplotlib.pyplot.plot` or
+            :func:`matplotlib.pyplot.errorbar`
 
         Returns
         -------
@@ -408,7 +411,6 @@ def plot(self, qtys, limitE=True, ax=None, logx=None, logy=None,
         ax = ax or pyplot.gca()
         onlyXS = True
         sigma = max(0, int(sigma))
-        drawstyle = 'steps-post' if steps else None
         limitE = limitE and (self.groups is not None
                              and self.microGroups is not None)
         macroBins = self.numGroups + 1 if self.numGroups is not None else None
@@ -418,15 +420,36 @@ def plot(self, qtys, limitE=True, ax=None, logx=None, logy=None,
         if limitE:
             eneCap = min(self.microGroups.max(), self.groups.max())
 
+        # Check kwargs
+        if "drawstyle" in kwargs and steps:
+            # Conflicting arguments but defer to user value for now
+            warnings.warn(
+                "Passing steps and drawstyle will default to using the "
+                "drawstyle value and will cause an error after 0.11.0. See "
+                "https://github.com/CORE-GATECH-GROUP/serpent-tools/issues/433",
+                PendingDeprecationWarning
+            )
+        elif steps:
+            kwargs.setdefault("drawstyle", "steps-post")
+
+        if "label" in kwargs:
+            if len(qtys) > 1:
+                raise ValueError(
+                    "Passing label while plotting multiple entries {} is "
+                    "not allowed".format(qtys)
+                )
+            if labels is not None:
+                raise ValueError("Passing label and labels is not allowed")
+            labels = kwargs.pop("label")
+
         if isinstance(labels, str):
             labels = [labels, ]
-        if labels is None:
+        elif labels is None:
             labels = [labelFmt, ] * len(qtys)
-        else:
-            if len(labels) != len(qtys):
-                raise IndexError(
-                    "Need equal number of labels for plot quantities. "
-                    "Given {} expected: {}".format(len(labels), len(qtys)))
+        elif len(labels) != len(qtys):
+            raise IndexError(
+                "Need equal number of labels for plot quantities. "
+                "Given {} expected: {}".format(len(labels), len(qtys)))
 
         for key, label in zip(qtys, labels):
             yVals = self.__getitem__(key)
@@ -451,8 +474,7 @@ def plot(self, qtys, limitE=True, ax=None, logx=None, logy=None,
 
             label = self.__formatLabel(label, key)
 
-            ax.errorbar(xdata, yVals, yerr=yUncs, label=label,
-                        drawstyle=drawstyle)
+            ax.errorbar(xdata, yVals, yerr=yUncs, label=label, **kwargs)
 
         if ylabel is None:
             ylabel, yUnits = (("Cross Section", "[cm$^{-1}$]") if onlyXS
diff --git a/serpentTools/utils/plot.py b/serpentTools/utils/plot.py
index ffda54cc..2499ba74 100644
--- a/serpentTools/utils/plot.py
+++ b/serpentTools/utils/plot.py
@@ -248,8 +248,7 @@ def placeLegend(ax, legend, handlesAndLabels=None, **kwargs):
     {rax}
 
     """
-#    import pdb
-#    pdb.set_trace()
+
     if handlesAndLabels is None:
         handles, labels = ax.get_legend_handles_labels()
     else:


Test patch:
diff --git a/tests/plots/result_images/test_homoguniv_multi.png b/tests/plots/result_images/test_homoguniv_multi.png
new file mode 100644
index 00000000..3c92c619
Binary files /dev/null and b/tests/plots/result_images/test_homoguniv_multi.png differ
diff --git a/tests/plots/result_images/test_homoguniv_multi_named.png b/tests/plots/result_images/test_homoguniv_multi_named.png
new file mode 100644
index 00000000..bd9cb77c
Binary files /dev/null and b/tests/plots/result_images/test_homoguniv_multi_named.png differ
diff --git a/tests/plots/result_images/test_homoguniv_single.png b/tests/plots/result_images/test_homoguniv_single.png
new file mode 100644
index 00000000..b8fce6f1
Binary files /dev/null and b/tests/plots/result_images/test_homoguniv_single.png differ
diff --git a/tests/plots/test_homoguniv.py b/tests/plots/test_homoguniv.py
new file mode 100644
index 00000000..f805cdfb
--- /dev/null
+++ b/tests/plots/test_homoguniv.py
@@ -0,0 +1,40 @@
+import pytest
+from serpentTools.settings import rc
+from serpentTools.data import readDataFile
+
+from . import compare_or_update_plot
+
+
+@pytest.fixture(scope="module")
+def univ():
+    with rc:
+        rc["serpentVersion"] = "2.1.30"
+        reader = readDataFile("InnerAssembly_res.m")
+    yield reader.universes["0", 0, 0, 0]
+
+
+@compare_or_update_plot
+def test_homoguniv_single(univ):
+    univ.plot("infTot", label="Total", legend=True)
+
+
+@compare_or_update_plot
+def test_homoguniv_multi(univ):
+    univ.plot(
+        ["infAbs", "infTot"],
+        logx=False,
+        logy=False,
+        labelFmt="{u} @ {b} MWd/kgU // {d} days // step {i}: {k}",
+        xlabel="Incident energy (MeV)",
+        ylabel="Macroscopic cross section (cm$^{-1})$",
+        # Addtional arguments to pass along to the underlying plot
+        linestyle="--",
+    )
+
+
+@compare_or_update_plot
+def test_homoguniv_multi_named(univ):
+    univ.plot(
+        ["infAbs", "infFlx"],
+        labels=["Absorption", "Flux"],
+    )