0

I have build simple tkinter GUI. Now, I am trying to visualise 3 different graphs (by calling the same function with different variables) and place them in 3 different rows of the GUI.

When I do that I get 2 problems:

  1. Every time I run the script (interface.py) I get 2 windows - both GUI and external graph's window. How to get rid of the second one?
  2. I am not able to visualize all the 3 graphs. The script stops after showing the first one. I believe this is because of that the first graph works in a loop (iterates through plenty of data points). Is there any work around it?

Interface:

# -*- coding: utf-8 -*-
"""
Created on Tue Oct  6 10:24:35 2020

@author: Dar0
"""

from tkinter import * #import tkinter module
from visualizer import main #import module 'visualizer' that shows the graph in real time

class Application(Frame):
    ''' Interface for visualizing graphs, indicators and text-box. '''
    def __init__(self, master):
        super(Application, self).__init__(master)
        self.grid()
        self.create_widgets()
    
    def create_widgets(self):
        # Label of the 1st graph
        Label(self,
              text='Hook Load / Elevator Height / Depth vs Time'
              ).grid(row = 0, column = 0, sticky = W)
        
        # Graph 1 - Hook Load / Elevator Height / Depth vs Time
        # button that displays the plot 
        #plot_button = Button(self,2
        #                     command = main,
        #                     height = 2, 
        #                     width = 10, 
        #                     text = "Plot"
        #                     ).grid(row = 1, column = 0, sticky = W)
        
        self.graph_1 = main(root, 1, 0)
        # place the button 
        # in main window 
        
        # Label of the 2nd graph
        Label(self,
              text = 'Hook Load / Elevator Height vs Time'
              ).grid(row = 3, column = 0, sticky = W)
        
        # Graph 2 - Hook Load / Elevator Height vs Time
        self.graph_2 = main(root, 4, 0)
        
        #Label of the 3rd graph
        Label(self,
              text = 'Hook Load vs Time'
              ).grid(row = 6, column = 0, sticky = W)
        
        #Graph 3 - Hook Load vs Time
        
        #Label of the 1st indicator
        Label(self,
              text = '1st performance indicator'
              ).grid(row = 0, column = 1, sticky = W)
        
        #1st performance indicator
        
        #Label of 2nd performance indicator
        Label(self,
              text = '2nd performance indicator'
              ).grid(row = 3, column = 1, sticky = W)
        
        #2nd performance indicator
        
        #Label of 3rd performance indicator
        Label(self,
              text = '3rd performance indicator'
              ).grid(row = 6, column = 1, sticky = W)
        
        #Text-box showing comments based on received data
        self.text_box = Text(self, width = 50, height = 10, wrap = WORD)
        self.text_box.grid(row = 9, column = 0, columnspan = 1)
        self.text_box.delete(0.0, END)
        self.text_box.insert(0.0, 'My message will be here.')
        
#Main part
root = Tk()
root.title('WiTSML Visualizer by Dar0')
app = Application(root)
root.mainloop()

Visualizer:

#WiTSML visualizer
#Created by Dariusz Krol
#import matplotlib
#matplotlib.use('TkAgg')
#from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
#from matplotlib.figure import Figure

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import random

class Visualizer(object):
    """ Includes all the methods needed to show streamed data. """
    def __init__(self):
        self.file_path = 'C:/Anaconda/_my_files/witsml_reader/modified_witsml.csv' #Defines which file is streamed

        self.datetime_mod = []
        self.bpos_mod = []
        self.woh_mod = []
        self.torq_mod = []
        self.spp_mod = []
        self.depth_mod = []
        self.flow_in_mod = []
        self.rpm_mod = []

    def open_file(self):
        self.df = pd.read_csv(self.file_path, low_memory = False, nrows = 300000) #Opens the STREAMED file (already modified so that data convert is not required)
        self.df = self.df.drop(0)
        self.df = pd.DataFrame(self.df)

        return self.df

    def convert_dataframe(self):
        self.df = self.df.values.T.tolist() #Do transposition of the dataframe and convert to list
        #Columns are as following:
        # - DATETIME
        # - BPOS
        # - WOH
        # - TORQ
        # - SPP
        # - DEPTH
        # - FLOW_IN
        # - RPM
        self.datetime_value = self.df[0]
        self.bpos_value = self.df[1]
        self.woh_value = self.df[2]
        self.torq_value = self.df[3]
        self.spp_value = self.df[4]
        self.depth_value = self.df[5]
        self.flow_in_value = self.df[5]
        self.rpm_value = self.df[7]

        return self.datetime_value, self.bpos_value, self.woh_value, self.torq_value, self.spp_value, self.depth_value, self.flow_in_value, self.rpm_value
        #print(self.bpos_value)

    def deliver_values(self, no_dp, columns):
        ''' Method gets no_dp amount of data points from the original file. '''
        self.no_dp = no_dp #defines how many data points will be presented in the graph

        val_dict = {
            'datetime': [self.datetime_value, self.datetime_mod],
            'bpos': [self.bpos_value, self.bpos_mod],
            'woh': [self.woh_value, self.woh_mod],
            'torq': [self.torq_value, self.torq_mod],
            'spp': [self.spp_value, self.spp_mod],
            'depth': [self.depth_value, self.depth_mod],
            'flow_in': [self.flow_in_value, self.flow_in_mod],
            'rpm': [self.rpm_value, self.rpm_mod]
            }
        
        for item in columns:
            if self.no_dp > len(val_dict[item][0]):
                dp_range = len(val_dict[item][0])
            else:
                dp_range = self.no_dp
                
            for i in range(dp_range):
                val_dict[item][1].append(val_dict[item][0][i])

        return self.datetime_mod, self.bpos_mod, self.woh_mod, self.torq_mod, self.spp_mod, self.depth_mod, self.flow_in_mod, self.rpm_mod    
        
    def show_graph2(self, tr_val, row, column):
        from pylive_mod import live_plotter, live_plotter2

        self.open_file()
        self.convert_dataframe()
        self.deliver_values(no_dp = 100000, columns = ['datetime', 'depth', 'bpos', 'woh'])

        fst_p = 0
        size = 300 # density of points in the graph (100 by default)
        
        x_vec = self.datetime_mod[fst_p:size]
        y_vec = self.depth_mod[fst_p:size]
        y2_vec = self.bpos_mod[fst_p:size]
        y3_vec = self.woh_mod[fst_p:size]
        line1 = []
        line2 = []
        line3 = []
        
        for i in range(self.no_dp):
            #print(self.datetime_mod[i:6+i])
            #print('Ostatni element y_vec: ', y_vec[-1])
            #print(x_vec)
            x_vec[-1] = self.datetime_mod[size+i]
            y_vec[-1] = self.depth_mod[size+i]
            y2_vec[-1] = self.bpos_mod[size+i]
            y3_vec[-1] = self.woh_mod[size+i]
            
            line1, line2, line3 = live_plotter2(tr_val, row, column, x_vec, y_vec, y2_vec, y3_vec, line1, line2, line3)

            x_vec = np.append(x_vec[1:], 0.0)
            y_vec = np.append(y_vec[1:], 0.0)
            y2_vec = np.append(y2_vec[1:], 0.0)
            y3_vec = np.append(y3_vec[1:], 0.0)

def main(tr_val, row, column):
    Graph = Visualizer()
    Graph.open_file() #Opens the streamed file
    Graph.convert_dataframe() #Converts dataframe to readable format
    Graph.show_graph2(tr_val, row, column)

#Show us the graph
#main()

Function that creates the graph:

def live_plotter2(tr_val, row, column, x_data, y1_data, y2_data, y3_data, line1, line2, line3, identifier='',pause_time=1):
    if line1 == [] and line2 == [] and line3 == []:
        # this is the call to matplotlib that allows dynamic plotting
        plt.ion()
        fig = plt.figure(figsize = (5, 4), dpi = 100)
        fig.subplots_adjust(0.15)
        
# -------------------- FIRST GRAPH --------------------
        host = fig.add_subplot()

        ln1 = host
        ln2 = host.twinx()
        ln3 = host.twinx()

        ln2.spines['right'].set_position(('axes', 1.))
        ln3.spines['right'].set_position(('axes', 1.12))
        make_patch_spines_invisible(ln2)
        make_patch_spines_invisible(ln3)
        ln2.spines['right'].set_visible(True)
        ln3.spines['right'].set_visible(True)              
        
        ln1.set_xlabel('Date & Time') #main x axis
        ln1.set_ylabel('Depth') #left y axis
        ln2.set_ylabel('Elevator Height')
        ln3.set_ylabel('Weight on Hook')

        #
        x_formatter = FixedFormatter([x_data])
        x_locator = FixedLocator([x_data[5]])

        #ln1.xaxis.set_major_formatter(x_formatter)
        ln1.xaxis.set_major_locator(x_locator)
        #
        
        ln1.locator_params(nbins = 5, axis = 'y')
        ln1.tick_params(axis='x', rotation=90) #rotates x ticks 90 degrees down

        ln2.axes.set_ylim(0, 30)
        ln3.axes.set_ylim(200, 250)
        
        line1, = ln1.plot(x_data, y1_data, color = 'black', linestyle = 'solid', alpha=0.8, label = 'Depth')
        line2, = ln2.plot(x_data, y2_data, color = 'blue', linestyle = 'dashed', alpha=0.8, label = 'Elevator Height')
        line3, = ln3.plot(x_data, y3_data, color = 'red', linestyle = 'solid', alpha=0.8, label = 'Weight on Hook')
        
        fig.tight_layout() #the graphs is not clipped on sides
        plt.title('WiTSML Visualizer')
        plt.grid(True)
        
        #Shows legend
        lines = [line1, line2, line3]
        host.legend(lines, [l.get_label() for l in lines], loc = 'lower left')        

        #Shows the whole graph
        #plt.show()     
        
        #-------------------- Embedding --------------------
        canvas = FigureCanvasTkAgg(fig, master=tr_val)
        canvas.draw()
        canvas.get_tk_widget().grid(row=row, column=column, ipadx=40, ipady=20)

        # navigation toolbar
        toolbarFrame = tk.Frame(master=tr_val)
        toolbarFrame.grid(row=row,column=column)
        toolbar = NavigationToolbar2Tk(canvas, toolbarFrame)
        

    # after the figure, axis, and line are created, we only need to update the y-data
    mod_x_data = convert_x_data(x_data, 20)
    line1.axes.set_xticklabels(mod_x_data)
    line1.set_ydata(y1_data)
    line2.set_ydata(y2_data)
    line3.set_ydata(y3_data)

    
    #Debugging
    #rint('plt.lim: ', ln2.axes.get_ylim())
    
    # adjust limits if new data goes beyond bounds
    # limit for line 1
    if np.min(y1_data)<=line1.axes.get_ylim()[0] or np.max(y1_data)>=line1.axes.get_ylim()[1]:
        plt.ylim(0, 10)
        line1.axes.set_ylim([np.min(y1_data)-np.std(y1_data),np.max(y1_data)+np.std(y1_data)])

    # limit for line 2
    if np.min(y2_data)<=line2.axes.get_ylim()[0] or np.max(y2_data)>=line2.axes.get_ylim()[1]:
        plt.ylim([np.min(y2_data)-np.std(y2_data),np.max(y2_data)+np.std(y2_data)])
        #plt.ylim(0, 25)

    # limit for line 3
    if np.min(y3_data)<=line3.axes.get_ylim()[0] or np.max(y3_data)>=line3.axes.get_ylim()[1]:
        plt.ylim([np.min(y3_data)-np.std(y3_data),np.max(y3_data)+np.std(y3_data)])
        #plt.ylim(0, 25)

    # Adds lines to the legend
    #host.legend(lines, [l.get_label() for l in lines])
    # this pauses the data so the figure/axis can catch up - the amount of pause can be altered above
    plt.pause(pause_time)
    
    # return line so we can update it again in the next iteration
    return line1, line2, line3

1 Answer 1

4

The key is to not use pyplot when you want to plot within tkinter as shown in the official example. Use matplotlib.figure.Figure instead (see this for added info).

Below is a minimum sample that plots 3 independent graphs along a Text widget which I see in your code:

import pandas as pd
import numpy as np
import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
    
class Graph(tk.Frame):
    def __init__(self, master=None, title="", *args, **kwargs):
        super().__init__(master, *args, **kwargs)
        self.fig = Figure(figsize=(4, 3))
        ax = self.fig.add_subplot(111)
        df = pd.DataFrame({"values": np.random.randint(0, 50, 10)}) #dummy data
        df.plot(ax=ax)
        self.canvas = FigureCanvasTkAgg(self.fig, master=self)
        self.canvas.draw()
        tk.Label(self, text=f"Graph {title}").grid(row=0)
        self.canvas.get_tk_widget().grid(row=1, sticky="nesw")
        toolbar_frame = tk.Frame(self)
        toolbar_frame.grid(row=2, sticky="ew")
        NavigationToolbar2Tk(self.canvas, toolbar_frame)
    
root = tk.Tk()

for num, i in enumerate(list("ABC")):
    Graph(root, title=i, width=200).grid(row=num//2, column=num%2)

text_box = tk.Text(root, width=50, height=10, wrap=tk.WORD)
text_box.grid(row=1, column=1, sticky="nesw")
text_box.delete(0.0, "end")
text_box.insert(0.0, 'My message will be here.')

root.mainloop()

Result:

enter image description here

Sign up to request clarification or add additional context in comments.

3 Comments

Hi! Thanks a lot for your feedback. The only problem is that I need pyplot because those graphs are dynamic. They iterate over X amount of points and work like one on thwe website here
No you don't. You can do everything just fine with Figure.
Don't understand really why, but once I replace fig = plt.figure(figsize=(13, 6)) with fig = Figure(figsize=(13,6)) the console hangs and I am not able to open and update the graph.

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.