Plotting in a Non-Blocking Way with Matplotlib

Plotting in a non-blocking way with Matplotlib

I spent a long time looking for solutions, and found this answer.

It looks like, in order to get what you (and I) want, you need the combination of plt.ion(), plt.show() (not with block=False) and, most importantly, plt.pause(.001) (or whatever time you want). The pause is needed because the GUI events happen while the main code is sleeping, including drawing. It's possible that this is implemented by picking up time from a sleeping thread, so maybe IDEs mess with that—I don't know.

Here's an implementation that works for me on python 3.5:

import numpy as np
from matplotlib import pyplot as plt

def main():
plt.axis([-50,50,0,10000])
plt.ion()
plt.show()

x = np.arange(-50, 51)
for pow in range(1,5): # plot x^1, x^2, ..., x^4
y = [Xi**pow for Xi in x]
plt.plot(x, y)
plt.draw()
plt.pause(0.001)
input("Press [enter] to continue.")

if __name__ == '__main__':
main()

plotting lines without blocking execution

In IPython started with -pylab it should not block.

Otherwise:
With ion() you turn the interactive mode on. show() does not block your system
anymore. Every draw() or plot(x, y) updated your plot.

ioff() turns interactive mode off. Useful if you add lots of data and don't
want to update every little detail.

See also: http://www.scipy.org/Cookbook/Matplotlib/Animations

How to build a Python REPL with non-blocking matplotlib (like IPython's %matplotlib) on macOS

I discovered that IPython 4.2.1 actually does this wrong, which allowed me to bisect the IPython codebase and find the answer.

IPython 5, which uses prompt-toolkit, has a special inputhook that it passes to prompt-toolkit's eventloop argument of run_application. IPython's inputhook is defined in IPython.terminal.pt_inputhooks.osx. The code does a bunch of ctypes calls into the macOS APIs (basically, to get the GUI eventloop).

I don't know how to use this for the dummy REPL from my question, but I am actually using prompt-toolkit, so this is fine for me. To use it, use

from IPython.terminal.pt_inputhooks.osx import inputhook
from prompt_toolkit.shortcuts import create_eventloop

# <prompt-toolkit stuff>
...
run_application(eventloop=create_eventloop(inputhook)

You also still do need the matplotlib.interactive(True) call before importing matplotlib.pyplot to make the plots show automatically (otherwise you have to call plt.show() all the time, and, more importantly, the plots will block).

Is there a way to detach matplotlib plots so that the computation can continue?

Use matplotlib's calls that won't block:

Using draw():

from matplotlib.pyplot import plot, draw, show
plot([1,2,3])
draw()
print('continue computation')

# at the end call show to ensure window won't close.
show()

Using interactive mode:

from matplotlib.pyplot import plot, ion, show
ion() # enables interactive mode
plot([1,2,3]) # result shows immediatelly (implicit draw())

print('continue computation')

# at the end call show to ensure window won't close.
show()

Non-blocking Matplotlib Animation

The most generic way to solve this is by using the multiprocessing module.

import matplotlib.pyplot as plt
import matplotlib.animation as animation
from multiprocessing import Process
import numpy as np
import time

def runGraph():
# Parameters
print('show')
x_len = 200 # Number of points to display
y_range = [10, 40] # Range of possible Y values to display

# Create figure for plotting
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
xs = list(range(0, 200))
ys = [0] * x_len
ax.set_ylim(y_range)

# Create a blank line. We will update the line in animate
line, = ax.plot(xs, ys)

# Add labels
plt.title('TMP102 Temperature over Time')
plt.xlabel('Samples')
plt.ylabel('Temperature (deg C)')

# This function is called periodically from FuncAnimation
def animate(i, ys):

# Read temperature (Celsius) from TMP102
temp_c = np.random.random(1)*40

# Add y to list
ys.append(temp_c)

# Limit y list to set number of items
ys = ys[-x_len:]

# Update line with new Y values
line.set_ydata(ys)

return line,

# Set up plot to call animate() function periodically

ani = animation.FuncAnimation(fig,
animate,
fargs=(ys,),
interval=50,
blit=True)
plt.show()

def MainProgram():
while 1:
print('Main program')
time.sleep(0.5)

if __name__ == '__main__':
p = Process(target=runGraph)
p.start()
MainProgram()
p.join()

Plotting matplotlib plots in pyscript when a button is clicked

To make it work you should have a button listener defined to ensure the call back. To do that use create_proxy function and then process the button.
It looks like this:

1. index.html

I moved your python code to main.py file to have html more readable. Added - paths: in py-env node and src="./main.py" in py-script node. There are some stylings for the button and a hidden div that I use if there are more buttons to control all of them with the same button processing function.

<html>

<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>

<py-env>
- matplotlib
- paths:
./main.py
</py-env>

<style>
.button:hover {
background: #d9d9d9;
}
.button {
text-decoration: none;
display: inline;
padding: 5px;
width: 120px;
font-size: 1em;
background: #afc7b5;
color: darkgreen;
font-weight: bold;
}
</style>
</head>

<body>
<h1>Matplotlib</h1>
<div hidden id="evtMsg">0</div>

<py-script> print("My Lineplot")</py-script>
<div id="lineplot"></div>
<button id="plot-button" onClick="document.getElementById('evtMsg').innerHTML=100" class="button" style="">Plot</button>

<py-script src="./main.py"></py-script>
</body>

</html>

2. main.py

Your code is here along with imports and listener definition and button processing code. I changed the name of your function to plot_it and declared it asynchronous.

import matplotlib.pyplot as plt

import asyncio
from pyodide import create_proxy

def Setup_Button_Listeners():
btnList = document.querySelectorAll(".button")
for i in range(len(btnList)):
e = document.getElementById(btnList[i].id)
btn_event = create_proxy(Process_Button)
e.addEventListener("click", btn_event)
#
#
async def Process_Button(event):
if document.getElementById("evtMsg").innerHTML == '100': # button plot_it
fig = await plot_it()
pyscript.write('lineplot', fig)

async def plot_it(*args, **kwargs):
fig, ax = plt.subplots()
year_1 = [2016, 2017, 2018, 2019, 2020, 2021]
population_1 = [42, 43, 45, 47, 48, 50]
year_2 = [2016, 2017, 2018, 2019, 2020, 2021]
population_2 = [43, 43, 44, 44, 45, 45]
plt.plot(year_1, population_1, marker='o', linestyle='--', color='g', label='Country 1')
plt.plot(year_2, population_2, marker='d', linestyle='-', color='r', label='Country 2')
plt.xlabel('Year')
plt.ylabel('Population (M)')
plt.title('Year vs Population')
plt.legend(loc='lower right')
fig
return fig

Setup_Button_Listeners()

This way, when page loaded, the button listener will be established waiting for you to click it. When you do - the proxy will call Process_Button function where you can do whatever you want to do with that button clicked. Regards...



Related Topics



Leave a reply



Submit