Search code examples
htmlpyscript

Displaying figures one after another using PyScript


I am a newbie with PyScript.

The following code correctly prints a single plot on the browser.

<head>

<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>

<pre>
<py-env>
    - numpy
    - matplotlib
</py-env>
        </pre>

</head>
<body>

<div id="matplotlib-lineplot">

    <pre>
<py-script output="matplotlib-lineplot" >
import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()
year_1 = [2016, 2017, 2018, 2019, 2020, 2021]
population_1 = np.random.randint(20,size=6)
plt.plot(year_1, population_1, marker='o', linestyle='--', color='g', label='Country 1')
plt.xlabel('Year')
plt.ylabel('Population (M)')
plt.title('Year vs Population')
plt.legend(loc='lower right')
fig

</py-script>
        </pre>
</div>
</body>
</html>

I would like to extend this to printing 20 plots one after another in a loop and then stop at the final plot. For this I am trying:

<html>
<head>

<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>

<pre>
<py-env>
    - numpy
    - matplotlib
</py-env>
        </pre>

</head>
<body>

<div id="matplotlib-lineplot">

    <pre>
<py-script output="matplotlib-lineplot" >
import matplotlib.pyplot as plt
import numpy as np

for step in range(20):

    fig, ax = plt.subplots()
    year_1 = [2016, 2017, 2018, 2019, 2020, 2021]
    population_1 = np.random.randint(20,size=6)
    plt.plot(year_1, population_1, marker='o', linestyle='--', color='g', label='Country 1')
    plt.xlabel('Year')
    plt.ylabel('Population (M)')
    plt.title('Year vs Population')
    plt.legend(loc='lower right')
    fig

</py-script>
        </pre>
</div>
</body>
</html>

Which prints nothing. How would go about this?

I have also tried replacing fig with plt.show(), which also shows nothing. I believe an alternative is to use IPython.display() but importing IPython throws an error ModuleNotFoundError: No module named 'termios' ) on my mac.

EDIT: After looking into the official docs I realised I was using an outdated way for printing. So I updated to:

<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
<script defer src="https://pyscript.net/latest/pyscript.js"></script>

<py-config>
  packages = ["matplotlib", "numpy"]

</py-config>
</head>
<body>


<py-script>
import matplotlib.pyplot as plt
import numpy as np
import time

for step in range(20):

    fig, ax = plt.subplots()
    year_1 = [2016, 2017, 2018, 2019, 2020, 2021]
    population_1 = np.random.randint(20,size=6)
    plt.plot(year_1, population_1, marker='o', linestyle='--', color='g', label='Country 1')
    plt.xlabel('Year')
    plt.ylabel('Population (M)')
    plt.title('Year vs Population')
    plt.legend(loc='lower right')
    display(fig, target="graph-area", append=False)
    time.sleep(1)
    plt.clf()

</py-script>

<div id="graph-area"></div>

</body>
</html>

But this prints a single figure


Solution

  • This issue in your final block of code (following EDIT) is the use of time.sleep(), which essentially instructs the operating system to leave the current thread alone for the given number of seconds, and resume execution of it at a later point. However, the browser is inherently a single-thread-per-tab environment* - there is no "other thread" to come back and "wake up" the current thread. So your execution is stuck at the first call of time.sleep.

    (*This is true in the current version of PyScript (2023.03.1) - in a future release, PyScript will have the option to run in Web Worker thread, in a way which allows for blocking operations like this.)

    To fix your issue, use a coroutine with asyncio.sleep like so:

    import matplotlib.pyplot as plt
    import numpy as np
    import asyncio
    
    async def showPlots():
        for step in range(20):
            fig, ax = plt.subplots()
            year_1 = [2016, 2017, 2018, 2019, 2020, 2021]
            population_1 = np.random.randint(20,size=6)
            plt.plot(year_1, population_1, marker='o', linestyle='--', color='g', label='Country 1')
            plt.xlabel('Year')
            plt.ylabel('Population (M)')
            plt.title('Year vs Population')
            plt.legend(loc='lower right')
            display(fig, target="graph-area", append=False)
            await asyncio.sleep(1)
            plt.clf()
        
    asyncio.ensure_future(showPlots())