0

I have a plot in which the x axis contains datetime.datetime objects. I am trying to change the range of the x axis with multiple matplotlib.widgets.Buttons. One button shows all datetimes, while another button shows only the last two seconds. When a button is clicked it should become green while the other button should become red.

Below is a minimal working example that shows my problem. There are two scenarios:

  1. Run script. Click on "Show only last two seconds". Works. Then click on "Show all times". Works. At this point, the "Show only last two seconds" stops working.
  2. Run script. Click on "Show all times". Works. Then click on "Show only last two seconds". This does nothing.

I added some print statements (commented out in the minimal working example) to try to find out the problem. It appears that when one button becomes activated, the other does not become immediately unactivated, which is what is intended. That is, setting the activated attribute to False in the while loop does not work as intended. There is also a problem with the button color.

How do I modify the minimal working example to produce the intended behavior? Feel free to suggest any other modifications.

#!/usr/bin/env python3

from datetime import datetime
from datetime import timedelta
from matplotlib.widgets import Button
import matplotlib.pyplot as plt
import random

class ButtonClickProcessor:
    def __init__(self, axes, label):
        self.button = Button(axes, label, color = 'red')
        self.button.on_clicked(self.process)
        self.activated = False
    def process(self, event):
        self.activated = True
        self.button.color = 'green'
        plt.gcf().canvas.draw()
        #print('ButtonClickProcessor.process', self.activated)

def main():

    start_time = datetime.now()

    fig, axes = plt.subplots()
    datetimes = []
    y_values = []

    button_show_all_times = ButtonClickProcessor(axes = plt.axes([0.0, 0.0, 0.5, 0.1]), label = 'Show all times')
    button_show_only_last_2_seconds = ButtonClickProcessor(axes = plt.axes([0.5, 0.0, 0.5, 0.1]), label = 'Show only last 2 seconds')

    while True:
        #print('Start of while True; button_show_all_times', button_show_all_times.activated)
        #print('Start of while True; button_show_only_last_2_seconds', button_show_only_last_2_seconds.activated)
        now = datetime.now()
        datetimes.append(now)
        y_values.append(random.randint(a = 0, b = 10))

        axes.plot(datetimes, y_values)

        time_difference = now - start_time
        if time_difference.total_seconds() > 2:
            delta = timedelta(seconds = 2)

            if button_show_all_times.activated is True:
                button_show_only_last_2_seconds.activated = False
                button_show_only_last_2_seconds.button.color = 'red'
                fig.canvas.draw()
                axes.set_xlim(left = datetimes[0])
                #print('button_show_all_times.activated is True:', button_show_all_times.activated, button_show_only_last_2_seconds.activated)
            if button_show_only_last_2_seconds.activated is True:
                button_show_all_times.activated = False
                button_show_all_times.button.color = 'red'
                fig.canvas.draw()
                axes.set_xlim(left = now - delta)
                #print('button_show_only_last_2_seconds.activated', button_show_all_times.activated, button_show_only_last_2_seconds.activated)

        fig.tight_layout(rect = [0, 0.1, 1, 1])
        plt.pause(0.0001)
        axes.clear()

    plt.show()

if __name__ == '__main__':
    main()

1 Answer 1

0

In case someone stumbles upon this in the future, below is my solution. I ended up having each button know about all the buttons via the all_buttons attribute. This solved the problem of activating the button I clicked on and simultaneously deactivating the other buttons. For changing the color, the key ingredient was the button.ax.set_facecolor method. Without this, the button color would not change until I moved the mouse. With this, the color changes as soon as I click on the button. I have no idea if this is the best or most efficient solution, but it works. I am still open to suggestions for improvement.

#!/usr/bin/env python3

from datetime import datetime
from datetime import timedelta
from matplotlib.widgets import Button
import matplotlib.pyplot as plt
import random

class ButtonClickProcessor:
    def __init__(self, axes, label):
        self.button = Button(ax = axes, label = label, color = 'red', hovercolor = 'red')
        self.button.on_clicked(self.process)
        self.activated = False
        self.all_buttons = None
    def process(self, event):
        for button in self.all_buttons:
            button.activated = False
            button.button.color = 'red'
            button.button.hovercolor = button.button.color
            button.button.ax.set_facecolor(button.button.color)
        self.activated = True
        self.button.color = 'green'
        self.button.hovercolor = self.button.color
        self.button.ax.set_facecolor(self.button.color)
        self.button.ax.figure.canvas.draw()

def main():

    start_time = datetime.now()

    fig, axes = plt.subplots()
    datetimes = []
    y_values = []

    button_show_all_times = ButtonClickProcessor(axes = plt.axes([0.01, 0.01, 0.5, 0.1]), label = 'Show all times')
    button_show_only_last_2_seconds = ButtonClickProcessor(axes = plt.axes([0.5, 0.01, 0.49, 0.1]), label = 'Show only last 2 seconds')
    all_buttons = (button_show_all_times, button_show_only_last_2_seconds)
    button_show_all_times.all_buttons = all_buttons
    button_show_only_last_2_seconds.all_buttons = all_buttons

    while True:
        now = datetime.now()
        datetimes.append(now)
        y_values.append(random.randint(a = 0, b = 10))

        axes.plot(datetimes, y_values)

        time_difference = now - start_time
        if time_difference.total_seconds() > 2:
            delta = timedelta(seconds = 2)

            if button_show_all_times.activated is True:
                axes.set_xlim(left = datetimes[0])
            if button_show_only_last_2_seconds.activated is True:
                axes.set_xlim(left = now - delta)

        fig.tight_layout(rect = [0, 0.1, 1, 1])
        plt.pause(0.0001)
        axes.clear()

    plt.show()

if __name__ == '__main__':
    main()
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.