ElFishi
ElFishi

Reputation: 53

Python Matplotlib Buttons

I am a poor programmer so please excuse my simple question. I am trying to build a little program that reads data from the serial interface and displays it on the screen. I have been able to do this in iPython notebook and matplotlib and I have been able to add buttons to the screen that control data requests that go to the interface: Button click -> ser.write, ser.read, draw

I am now struggling to design the program such that pressing a button will start repeated data collection in fixed time steps until the button is toggled off again. Can someone please help me out with a sketch for such a program?

So far:

%pylab 

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Button

import serial
import binascii
import struct
from time import sleep

fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)
ax.axis([0, 10, 0, 255])

ser = serial.Serial('COM5', 1000000, timeout=0) 
ser.write("0".encode())
sleep(0.1)
if ser.read()== b'\xf1':
    print ("Interface is responding")
else:
    print ("Interface is NOT responding")
    ser.flush()
    ser.close()
    exit(1)

t = np.linspace(0, 10, 2048)
line1, line2 = plt.plot(t,0*t, t,0*t, lw=2)

def ShowChannel(Channel):
    if Channel==1:
        ser.write("11".encode())
    elif Channel==2:
        ser.write("12".encode())
    sleep(0.05)
    Header = ser.read().decode("utf-8")
    # print(Header)
    if Header == "1":
        data = ser.read(2048)
        y = struct.unpack('2048B', data)
        # print(y)
        if Channel==1:
            line1.set_ydata(y)
        elif Channel==2:
            line2.set_ydata(y)
        fig.canvas.draw()

def one(event):
    ShowChannel(1)

def two(event):
    ShowChannel(2)

axone = plt.axes([0.1, 0.05, 0.1, 0.075])
axtwo = plt.axes([0.21, 0.05, 0.1, 0.075])
axstart = plt.axes([0.40, 0.05, 0.1, 0.075])
axstop = plt.axes([0.51, 0.05, 0.1, 0.075])

bone = Button(axone, '1')
bone.on_clicked(one)
btwo = Button(axtwo, '2')
btwo.on_clicked(two)

Following the example cited in the comments, I added the following

# Build check button axes
rax = plt.axes([0.7, 0.05, 0.1, 0.1], aspect='equal')
labels = ('go!',)
check = CheckButtons(rax, labels, (False, ))

KeepShowing = False

def func(event):
    global KeepShowing
    KeepShowing = not KeepShowing
#    print(event, KeepShowing)

check.on_clicked(func)

while True:
    if KeepShowing:
        ShowChannel(1)
    sleep(1)

But the loop at the bottom is not how to do it. When I start the program with it, the graphics window opens, but doesn't show anything. Only if I interrupt the kernel in ipython the screen builds.

Upvotes: 4

Views: 2945

Answers (1)

Huan-Yu Tseng
Huan-Yu Tseng

Reputation: 566

If you want to call the function which reads the data routinely, you can use the timer in matplotlib module. The code is as following:

import matplotlib.pyplot as plt
fig, ax = plt.subplots()
timer = fig.canvas.new_timer(interval)
timer.add_callback(function, args)

The unit of the interval is ms, and you can use the timer.start() or timer.stop() methods to turn on or turn off the timer.


Based on your code, I add the timers for each button and add variables to check whether the timers are running:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.widgets import Button

import serial
import binascii
import struct
from time import sleep

fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)
ax.axis([0, 10, 0, 255])

ser = serial.Serial('COM5', 1000000, timeout=0) 
ser.write("0".encode())
sleep(0.1)
if ser.read()== b'\xf1':
    print ("Interface is responding")
else:
    print ("Interface is NOT responding")
    ser.flush()
    ser.close()
    exit(1)

t = np.linspace(0, 10, 2048)
line1, line2 = plt.plot(t,0*t, t,0*t, lw=2)

def ShowChannel(Channel):
    if Channel==1:
        ser.write("11".encode())
    elif Channel==2:
        ser.write("12".encode())
    sleep(0.05)
    Header = ser.read().decode("utf-8")
    # print(Header)
    if Header == "1":
        data = ser.read(2048)
        y = struct.unpack('2048B', data)
        # print(y)
        if Channel==1:
            line1.set_ydata(y)
        elif Channel==2:
            line2.set_ydata(y)
        fig.canvas.draw()

def one(event):
    global channel1_on

    if channel1_on == 0:
        channel1_on = 1
        timer1.start()
    else:
        channel1_on = 0
        timer1.stop()
        line1.set_ydata(None)

def two(event):
    global channel2_on

    if channel2_on == 0:
        channel2_on = 1
        timer2.start()
    else:
        channel2_on = 0
        timer2.stop()
        line2.set_ydata(None)

channel1_on = 0
channel2_on = 0
timer1 = fig.canvas.new_timer(interval = 50)
timer1.add_callback(ShowChannel, 1)
timer2 = fig.canvas.new_timer(interval = 50)
timer2.add_callback(ShowChannel, 2)

axone = plt.axes([0.1, 0.05, 0.1, 0.075])
axtwo = plt.axes([0.21, 0.05, 0.1, 0.075])
axstart = plt.axes([0.40, 0.05, 0.1, 0.075])
axstop = plt.axes([0.51, 0.05, 0.1, 0.075])

bone = Button(axone, '1')
bone.on_clicked(one)
btwo = Button(axtwo, '2')
btwo.on_clicked(two)

plt.show()

Another one thing is if I did not add the line plt.show() in your code, it did not show the figure when it ran in my computer. So I add it in the end of your code and it can show the figure now.

Hope it helps.

Upvotes: 2

Related Questions