Repository: streamlit/streamlit

Issue:
Clear matplotlib figure
When I do this code the second plot will show the labels of the first one:

```
    def pie_chart_users():
        a = plt.pie(
            # using data total)arrests
            app_data_graph['app_count']['count'],
            # with the labels being officer names
            labels=app_data_graph['user_type_apps'],
            # with the percent listed as a fraction
            autopct='%1.1f%%',
            )

        # View the plot drop above
        # b = plt.axis('equal')

        # View the plot
        c = plt.tight_layout()
        return a, c

    '### Breakdown of all app creators (total creators)'
    pie_1 = pie_chart_users()
    st.pyplot()

    # Graph breaking down number of apps created by each user group
    '### % of total apps created by creator group'
    def pie_chart_totals():
        a = plt.pie(
            # using data total)arrests
            app_data_graph['app_count']['sum'],
            # with the labels being officer names
            labels=app_data_graph['user_type_apps'],
            # with the percent listed as a fraction
            autopct='%1.1f%%',
            )

        # View the plot drop above
        # b = plt.axis('equal')

        # View the plot
        c = plt.tight_layout()
        return a, c

    pie_2 = pie_chart_totals()
    st.pyplot()
```

This is fixed by adding in  plt.figure() between the two

```
    def pie_chart_users():
        a = plt.pie(
            # using data total)arrests
            app_data_graph['app_count']['count'],
            # with the labels being officer names
            labels=app_data_graph['user_type_apps'],
            # with the percent listed as a fraction
            autopct='%1.1f%%',
            )

        # View the plot drop above
        # b = plt.axis('equal')

        # View the plot
        c = plt.tight_layout()
        return a, c

    '### Breakdown of all app creators (total creators)'
    pie_1 = pie_chart_users()
    st.pyplot()
    plt.figure()

    # Graph breaking down number of apps created by each user group
    '### % of total apps created by creator group'
    def pie_chart_totals():
        a = plt.pie(
            # using data total)arrests
            app_data_graph['app_count']['sum'],
            # with the labels being officer names
            labels=app_data_graph['user_type_apps'],
            # with the percent listed as a fraction
            autopct='%1.1f%%',
            )

        # View the plot drop above
        # b = plt.axis('equal')

        # View the plot
        c = plt.tight_layout()
        return a, c

    pie_2 = pie_chart_totals()
    st.pyplot()
```
Looks like st.pyplot needs to clear the Matplotlib figure after drawing it to the app


Discussion:
I'm not actually sure what the best course of action is here. We could call `plt.clf` from `st.pyplot` to clear the current figure - but is that users would expect?

(In other words, is this simply user error, and the user should be manually calling `plt.clf` when they want to clear the figure?)

Resolving patch:
diff --git a/lib/streamlit/DeltaGenerator.py b/lib/streamlit/DeltaGenerator.py
--- a/lib/streamlit/DeltaGenerator.py
+++ b/lib/streamlit/DeltaGenerator.py
@@ -1247,7 +1247,7 @@ def plotly_chart(
         )
 
     @_with_element
-    def pyplot(self, element, fig=None, **kwargs):
+    def pyplot(self, element, fig=None, clear_figure=True, **kwargs):
         """Display a matplotlib.pyplot figure.
 
         Parameters
@@ -1256,6 +1256,11 @@ def pyplot(self, element, fig=None, **kwargs):
             The figure to plot. When this argument isn't specified, which is
             the usual case, this function will render the global plot.
 
+        clear_figure : bool
+            If True or unspecified, the figure will be cleared after being
+            rendered. (This simulates Jupyter's approach to matplotlib
+            rendering.)
+
         **kwargs : any
             Arguments to pass to Matplotlib's savefig function.
 
@@ -1286,7 +1291,7 @@ def pyplot(self, element, fig=None, **kwargs):
         """
         import streamlit.elements.pyplot as pyplot
 
-        pyplot.marshall(element, fig, **kwargs)
+        pyplot.marshall(element, fig, clear_figure, **kwargs)
 
     @_with_element
     def bokeh_chart(self, element, figure):
diff --git a/lib/streamlit/cli.py b/lib/streamlit/cli.py
--- a/lib/streamlit/cli.py
+++ b/lib/streamlit/cli.py
@@ -211,7 +211,10 @@ def main_run(target, args=None, **kwargs):
 
     _, extension = os.path.splitext(target)
     if extension[1:] not in ACCEPTED_FILE_EXTENSIONS:
-        raise click.BadArgumentUsage("Streamlit requires raw Python (.py) files, not %s.\nFor more information, please see https://streamlit.io/docs" % extension)
+        raise click.BadArgumentUsage(
+            "Streamlit requires raw Python (.py) files, not %s.\nFor more information, please see https://streamlit.io/docs"
+            % extension
+        )
 
     if url(target):
         from streamlit.temporary_directory import TemporaryDirectory
diff --git a/lib/streamlit/elements/pyplot.py b/lib/streamlit/elements/pyplot.py
--- a/lib/streamlit/elements/pyplot.py
+++ b/lib/streamlit/elements/pyplot.py
@@ -32,7 +32,7 @@
 LOGGER = get_logger(__name__)
 
 
-def marshall(new_element_proto, fig=None, **kwargs):
+def marshall(new_element_proto, fig=None, clear_figure=True, **kwargs):
     """Construct a matplotlib.pyplot figure.
 
     See DeltaGenerator.vega_lite_chart for docs.
@@ -58,3 +58,8 @@ def marshall(new_element_proto, fig=None, **kwargs):
     image_proto.marshall_images(
         image, None, -2, new_element_proto.imgs, False, channels="RGB", format="PNG"
     )
+
+    # Clear the figure after rendering it. This means that subsequent
+    # plt calls will be starting fresh.
+    if clear_figure:
+        fig.clf()


Test patch:
diff --git a/lib/tests/streamlit/cli_test.py b/lib/tests/streamlit/cli_test.py
--- a/lib/tests/streamlit/cli_test.py
+++ b/lib/tests/streamlit/cli_test.py
@@ -74,8 +74,9 @@ def test_run_not_allowed_file_extension(self):
         result = self.runner.invoke(cli, ["run", "file_name.doc"])
 
         self.assertNotEqual(0, result.exit_code)
-        self.assertTrue("Streamlit requires raw Python (.py) files, not .doc."
-                        in result.output)
+        self.assertTrue(
+            "Streamlit requires raw Python (.py) files, not .doc." in result.output
+        )
 
     @tempdir()
     def test_run_valid_url(self, temp_dir):
@@ -105,8 +106,7 @@ def test_run_non_existing_url(self, temp_dir):
             "streamlit.cli._main_run"
         ), requests_mock.mock() as m:
 
-            m.get("http://url/app.py",
-                  exc=requests.exceptions.RequestException)
+            m.get("http://url/app.py", exc=requests.exceptions.RequestException)
             with patch("streamlit.temporary_directory.TemporaryDirectory") as mock_tmp:
                 mock_tmp.return_value.__enter__.return_value = temp_dir.path
                 result = self.runner.invoke(cli, ["run", "http://url/app.py"])
@@ -130,8 +130,7 @@ def test_run_arguments(self):
                     ],
                 )
         mock_main_run.assert_called_with(
-            "some script.py",
-            ("argument with space", "argument with another space")
+            "some script.py", ("argument with space", "argument with another space")
         )
         self.assertEqual(0, result.exit_code)
 
@@ -142,8 +141,7 @@ def test_get_command_line(self):
         mock_context = MagicMock()
         mock_context.parent.command_path = "mock_command"
         with patch("click.get_current_context", return_value=mock_context):
-            with patch("click.get_os_args",
-                       return_value=["os_arg1", "os_arg2"]):
+            with patch("click.get_os_args", return_value=["os_arg1", "os_arg2"]):
                 result = cli._get_command_line_as_string()
                 self.assertEqual("mock_command os_arg1 os_arg2", result)
 
diff --git a/lib/tests/streamlit/delta_generator_test.py b/lib/tests/streamlit/delta_generator_test.py
--- a/lib/tests/streamlit/delta_generator_test.py
+++ b/lib/tests/streamlit/delta_generator_test.py
@@ -16,21 +16,20 @@
 """DeltaGenerator Unittest."""
 
 # Python 2/3 compatibility
-from __future__ import print_function, division, unicode_literals, absolute_import
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
 
-from streamlit.DeltaGenerator import _build_duplicate_widget_message
 from streamlit.compatibility import setup_2_3_shims
-from streamlit.errors import DuplicateWidgetID
-from streamlit.errors import StreamlitAPIException
 
 setup_2_3_shims(globals())
 
 import json
+import mock
 import sys
 import unittest
 
-import pandas as pd
-
 try:
     from inspect import signature
 except ImportError:
@@ -38,6 +37,11 @@
 
 from parameterized import parameterized
 
+import pandas as pd
+
+from streamlit.DeltaGenerator import _build_duplicate_widget_message
+from streamlit.errors import DuplicateWidgetID
+from streamlit.errors import StreamlitAPIException
 from streamlit.proto.Element_pb2 import Element
 from streamlit.proto.TextInput_pb2 import TextInput
 from streamlit.proto.TextArea_pb2 import TextArea
diff --git a/lib/tests/streamlit/streamlit_test.py b/lib/tests/streamlit/streamlit_test.py
--- a/lib/tests/streamlit/streamlit_test.py
+++ b/lib/tests/streamlit/streamlit_test.py
@@ -14,7 +14,6 @@
 # limitations under the License.
 
 """Streamlit Unit test."""
-
 from mock import patch
 import json
 import os
@@ -454,6 +453,48 @@ def test_st_pyplot(self):
         checksum = "iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAYAAACAvzb"
         self.assertTrue(el.imgs.imgs[0].data.base64.startswith(checksum))
 
+    def test_st_pyplot_clear_figure(self):
+        """st.pyplot should clear the passed-in figure."""
+        if sys.version_info < (3, 0):
+            # We don't test matplotlib under Python 2, because we're not
+            # able to reliably force the backend to "agg".
+            return
+
+        import matplotlib
+        import matplotlib.pyplot as plt
+
+        if matplotlib.get_backend().lower() != "agg":
+            plt.switch_backend("agg")
+
+        # Assert that plt.clf() is called by st.pyplot() only if
+        # clear_fig is True
+        for clear_figure in [True, False]:
+            plt.hist(np.random.normal(1, 1, size=100), bins=20)
+            with patch.object(plt, "clf", wraps=plt.clf, autospec=True) as plt_clf:
+                st.pyplot(clear_figure=clear_figure)
+
+                if clear_figure:
+                    plt_clf.assert_called_once()
+                else:
+                    plt_clf.assert_not_called()
+
+            # Manually clear for the next loop iteration
+            plt.clf()
+
+        # Assert that fig.clf() is called by st.pyplot(fig) only if
+        # clear_figure is True
+        for clear_figure in [True, False]:
+            fig = plt.figure()
+            ax1 = fig.add_subplot(111)
+            ax1.hist(np.random.normal(1, 1, size=100), bins=20)
+            with patch.object(fig, "clf", wraps=fig.clf, autospec=True) as fig_clf:
+                st.pyplot(fig, clear_figure=clear_figure)
+
+                if clear_figure:
+                    fig_clf.assert_called_once()
+                else:
+                    fig_clf.assert_not_called()
+
     def test_st_plotly_chart_simple(self):
         """Test st.plotly_chart."""
         import plotly.graph_objs as go