Pandas Dataframe bad formating - python

I am trying to display a pandas DataFrame in tkinter python and all of the columns are misaligned.
First this function takes these 4 columns from my log table and converts it into DataFrame
def show_log():
df = pandas.read_sql("SELECT id, user_id, book_id, action FROM library.log LIMIT 10", con=mydb)
blankIndex = [''] * len(df)
df.index = blankIndex
return df
Then this function creates a tkinter label with the dataframe
def show_log(self):
self.label_show_log = tk.Label(self.master, text=fu.show_log(), borderwidth=2, relief="groove")
self.label_show_log.pack()
This is the tkinter output I get - and it's ugly:
I want to make all of the columns and the data evenly aligned!
Thank you for any help! :)

You need to use a fixed width (or monospaced) font, for example "Courier New", for the label:
def show_log(self):
self.label_show_log = tk.Label(self.master, text=fu.show_log(),
borderwidth=2, relief="groove",
font=("Courier New", 10))
self.label_show_log.pack()
Result:

I use pandastable to display dataframes in tkinter
Install: pip install pandastable
Code:
import pandas as pd
from pandastable import Table
#Create DataFrame class
class DataFrameTable(Frame):
def __init__(self, parent=None, df=pd.DataFrame()):
super().__init__()
self.parent = parent
self.pack(fill=BOTH, expand=True)
self.table = Table(
self, dataframe=df,
showtoolbar=False,
showstatusbar=True,
editable=False)
self.table.show()
#Tkinter window
root = Tk()
#Show Table
DataFrameTable(root, your_dataframe)
root.mainloop()

Related

How to get the selected item in a Python TkInter multiple combobox in a loop?

I would like to create a table similar to Excel's style. It should be depending on the size of the array to show.
For this reason the table columns and rows are inside loop cycles.
In the first line I would have a combobox to select the value to filter.
Also the combobox is inside a loop cycle for the different columns number.
When the table is created I am not able to recognize which combobox is chosen.
How can I do?
Example:
import tkinter
from tkinter import ttk #per button, label etc
from tkinter.ttk import *
import numpy as np #for the matrix tools
def newselection(event, output):
print("Value selected:", event, output)
def ShowTableGui(MatrixToShowIn):
MatrixToShow=np.array(MatrixToShowIn)
RowNumber = MatrixToShow.shape[0]
ArrayCombo=[]
windowx=tkinter.Tk()
windowx.title("Table viewer")
buttonExit = ttk.Button(windowx, text="Close table", command=windowx.destroy)
# buttonExit = ttk.Button(windowx, text="Run Filter", command= lambda: Run_filter(MatrixToShow.shape[1]))
buttonExit.grid(column=0, row=0)
for Col in range (int(MatrixToShow.shape[1])):
ValuesInsert=MatrixToShow[:,Col] # values in column
ValuesInsert=[row[Col]for row in MatrixToShowIn]
ValuesInsert=list(set(ValuesInsert)) # values listed only once
ValuesInsert.sort()
ValuesInsert.insert(0,"*") # add * to filter all
comboExample0 = ttk.Combobox(windowx, state='readonly', values=ValuesInsert)
comboExample0.grid(column=Col+2, row=0)
comboExample0.bind("<<ComboboxSelected>>", lambda event:newselection(comboExample0.get(), "output"))
# comboExample0.bind("<<ComboboxSelected>>", lambda event:newselection(event, "output"))
ArrayCombo.append(comboExample0)
Select=comboExample0.get()
print(Select)
for Row in range (RowNumber):
b = Entry(windowx, text="")
b.grid(row=Row+1, column=Col+2)
b.insert(0,str(MatrixToShow[Row][Col]))
windowx.mainloop()
return()
MatrixToShowIn=[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]
ShowTableGui(MatrixToShowIn)
Finally I have the time to post the solution found thanks to your help:
from tkinter import *
from functools import partial
from tkinter.ttk import *
class ComboTest:
def __init__(self, MatrixToShow):
self.top = Tk()
self.top.title('Combos Test')
self.top_frame = Frame(self.top, width =400, height=400)
self.button_dic = {}
self.combos_dic = {}
self.var = StringVar()
self.top_frame.grid(row=0, column=1)
Button(self.top_frame, text='Exit',
command=self.top.destroy).grid(row=0,column=0, columnspan=5)
self.combos(MatrixToShow)
self.top.mainloop()
##-------------------------------------------------------------------
def combos(self,MatrixToShow):
b_row=1
Columns=[]
for com_num in range(len(MatrixToShow[0])):
Column=["*"]
for Row in range(len(MatrixToShow)):
Column.append(MatrixToShow[Row][com_num])
Columns.append(Column)
## note that the correct "com_num" is stored
# self.combos_dic[com_num] = "self.cb_combo_%d()" % (com_num)
e = Entry(self.top_frame)
e.insert(0, com_num)
e.insert(0, "Column")
e.grid(row=b_row, column=com_num)
b = Combobox(self.top_frame, state='readonly', values=Column)
b.bind("<<ComboboxSelected>>", partial(self.cb_handler, com_num))
b.current(0)
b.grid(row=b_row+1, column=com_num)
##----------------------------------------------------------------
def cb_handler( self, cb_number, event ):
print ("ComboNumber", cb_number, "SelectedValue", event.widget.get())
##=================================================================
MatrixToShowIn=[[1,2,3],[4,5,6],[7,8,9],[10,11,12]]
CT=ComboTest(MatrixToShowIn)
Enjoy Roberto

Tkinter Treeview used to work when creating striped rows and now it won't work

I have a function that retrieves information from a database and then displays the info in a Tkinter Treeview table. When the program used sqlite3, it worked just fine. Now that I've switched to MySQL, it doesn't. I haven't changed the treeview formatting at all.
I've tried changing the placement of the .tag_configure() method, and changing the arguments (background color, tag name). Nothing will make it work again.
from tkinter import *
from tkinter import ttk
class TreeviewTable(ttk.Treeview):
# Creates a gui_table with given columns.
def __init__(self, master, columns):
self.columns = columns
self.width = int(500 / (len(self.columns)))
ttk.Treeview.__init__(self, master, columns=self.columns,
show='headings', height=100)
for i in range(len(self.columns)):
self.column(self.columns[i], anchor='center', width=self.width)
self.heading('#{}'.format(str(i+1)), text=self.columns[i],)
self.pack(side=RIGHT, fill=BOTH, expand=1)
data = [
['Bottles', '1L', 500, '$1.62', '$810.00'],
['Bottles', 'Death Wish 1L', 300, '$2.76', '$828.00'],
['Labels', 'ALB 1L', 10500, '$0.11', '$1,155.00'],
['Capsules', 'ALB 1L', 3400, '$0.08', '$272.00'],
]
window = Tk()
window.geometry("%dx%d" % (500,130))
gui_table = TreeviewTable(window, columns=("Type", "Product", "Amount", "Price", "Total"))
gui_table.pack(side=RIGHT, fill=BOTH, expand=1)
for (index, row) in enumerate(data, 1): # Created striped rows
if (index % 2 == 0):
tag = 'even'
else:
tag = 'odd'
gui_table.insert("", END, values=row, tags=(tag,))
gui_table.tag_configure('even', background='#E8E8E8')
window.mainloop()

Filter pandas dataframe, display in tkinter/pandastable

I am trying to create an application which involves displaying a medium-sized pandas dataframe. I'm using pandastable and tkinter for my user interface. Part of what I want the user to be able to do is select a filter from a combobox and then have only matching rows display.
Below is a simplified but complete version of the code I'm trying to employ. I create a class which has the core UI code in it. Initially everything looks great. I created a test button function inside my class (change_df) that creates a dummy column, that works fine. I have another function inside the class (change_df_combo) that does successfully filter the dataframe, but it doesn't display onscreen. I.e., I can print the df or export it and it has filtered. However that doesn't show up on screen.
I assume somehow my class or tkinter itself isn't looping right, but for the life of me I can't figure it out! Any thoughts?
from win32com.shell import shell, shellcon
import pandas as pd
import openpyxl
from openpyxl import load_workbook
import numpy as np
import time
from tkinter import *
from tkinter import ttk
from pandastable import Table, TableModel
class UserInterface(Frame):
# Launch the df in a pandastable frame
def __init__(self, parent=None):
global ui_df
global pt
ui_df = pos_df
self.parent = parent
Frame.__init__(self)
self.main = self.master
#self.main.geometry('800x600+0+0')
f = Frame(self.main)
f.grid(column=0, row=1, sticky=(E, W))
screen_width = f.winfo_screenwidth() * 0.8
screen_height = f.winfo_screenheight() * 0.7
self.table = pt = Table(f, dataframe=ui_df, height = screen_height, width = screen_width)
pt.show()
return
def change_df(self, col_val_input):
#Responds to button
ui_df['Test col'] = col_val_input
pt.show()
def change_df_combo(self, event):
#Responds to combobox, supposed to filter by 'Sec_type'
combo_selection = str(combo_box.get())
ui_df = pos_df[pos_df['Sec_type'] == combo_selection]
pt.show()
#Create dataframe
pos_data = {'Location' : ['Denver', 'Boulder', 'Phoenix', 'Reno', 'Portland',
'Eugene', 'San Francisco'], 'Sec_type' : ['mbus', 'mbus', 'vmus', 'caus',
'vmus', 'mbus', 'mbus']}
pos_df = pd.DataFrame(data = pos_data)
#Launch Tkinter basics
root = Tk()
root.title("S test...")
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
ui_display = UserInterface(mainframe)
#'Test' button, this works fine
col_val_input = 'It worked!'
test_button = ttk.Button(mainframe, text = 'Test', command= lambda: ui_display.change_df(col_val_input))
test_button.grid(column=0, row=0, sticky=(W))
#Combobox, works to just show choice, but not to filter
combo_choices = ['mbus', 'vmus', 'caus']
choice = StringVar()
combo_box = ttk.Combobox(mainframe, textvariable=choice)
combo_box['values'] = combo_choices
combo_box.grid(column=1, row=0, sticky=(W))
combo_box.bind('<<ComboboxSelected>>', ui_display.change_df_combo)
root.mainloop()
Edit to add:
Changed imported df to a toy df for testing purposes
Alright I came up with something that works. Not sure it is the most pythonic code ever written but here it goes: (posting just the class segment)
class UserInterface(Frame):
# Launch the df in a pandastable frame
def __init__(self, parent=None):
global ui_df
ui_df = pos_df
self.parent = parent
self.refresh_df(df = ui_df)
def refresh_df(self, df):
Frame.__init__(self)
self.main = self.master
f = Frame(self.main)
f.grid(column=0, row=1, sticky=(E, W))
screen_width = f.winfo_screenwidth() * 0.8
screen_height = f.winfo_screenheight() * 0.7
self.table = pt = Table(f, dataframe=df, height = screen_height, width = screen_width)
pt.show()
return
def change_df(self, col_val_input):
#Responds to button
ui_df['Test col'] = col_val_input
self.refresh_df(df=ui_df)
def change_df_combo(self, event):
#Responds to combobox, supposed to filter by 'Sec_type'
combo_selection = str(combo_box.get())
ui_df = pos_df[pos_df['Sec_type'] == combo_selection]
ui_df['Test col combo'] = combo_selection
self.refresh_df(df=ui_df)

Format individual cell/item rather than entire row in tkinter ttk treeview

I am currently using a function to display a pandas dataframe in a spreadsheet style format. I would like to be able to add some functionality to format individual cells of the treeview based on their content e.g. if they contain substring 'X' or if their value is higher than Y.
The update function currently implemented is as follows:
def updateTree(self, dataframe):
'''
Updates the treeview with the data in the dataframe
parameter
'''
#Remove any nan values which may have appeared in the dataframe parameter
df = dataframe.replace(np.nan,'', regex=True)
#Currently displayed data
self.treesubsetdata = dataframe
#Remove existing items
for item in self.tree.get_children(): self.tree.delete(item)
#Recreate from scratch the columns based on the passed dataframe
self.tree.config(columns= [])
self.tree.config(columns= list(dataframe.columns))
#Ensure all columns are considered strings and write column headers
for col in dataframe.columns:
self.tree.heading(col,text=str(col))
#Get number of rows and columns in the imported script
self.rows,self.cols = dataframe.shape
#Populate data in the treeview
for row in dataframe.itertuples():
self.tree.insert('', 'end',values = tuple(row[1:]))
#Minimise first column
self.tree.column('#0',width=0)
self.tree.update()
Can anyone confirm that you can in fact edit an individual cell in a treview?
If yes are there any ideas as to how this could be implemented?
It's not possible to set the style of individual cells in Treeview; only entire rows can use the tag attribute.
If you just want a table of values then I'd recommend just using ttk.Label widgets, which you can format in a huge number of ways. For example:
import Tkinter as tk
import ttk
import pandas as pd
from random import randrange
PADDING = dict(padx=3, pady=3)
class GridView(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.labels = []
style = ttk.Style()
style.configure("red.TLabel", background='red')
style.configure("green.TLabel", background='green')
style.configure("header.TLabel", font = '-weight bold')
def set(self, df):
self.clear()
for col, name in enumerate(df.columns):
lbl = ttk.Label(self, text=name, style='header.TLabel')
lbl.grid(row=0, column=col, **PADDING)
self.labels.append(lbl)
for row, values in enumerate(df.itertuples(), 1):
for col, value in enumerate(values[1:]):
lbl = ttk.Label(self, text=value, style=self.get_style(value))
lbl.grid(row=row, column=col, **PADDING)
self.labels.append(lbl)
#staticmethod
def get_style(value):
if value > 70:
return "red.TLabel"
elif value < 30:
return "green.TLabel"
else:
return None
def clear(self):
for lbl in self.labels:
lbl.grid_forget()
self.labels = []
class GUI(tk.Frame):
def __init__(self, master=None, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
self.table = GridView(self)
self.table.pack()
btn = ttk.Button(self, text="populate", command=self.populate)
btn.pack()
btn = ttk.Button(self, text="clear", command=self.table.clear)
btn.pack()
def populate(self):
self.table.set(new_rand_df())
def main():
root = tk.Tk()
win = GUI(root)
win.pack()
root.mainloop()
def new_rand_df():
width = 5
height = 5
return pd.DataFrame([[randrange(100) for _ in range(width)] for _ in range(height)], columns = list('abcdefghijklmnopqrstuvwxyz'[:width]))
if __name__ == '__main__':
main()
this is possible using a custom cellrenderer in Gtk3, e.g.:
import gi
gi.require_version( 'Gtk', '3.0' )
from gi.repository import Gtk, GObject
class My_CellRendererText( Gtk.CellRendererText ):
def __init__( self ):
super().__init__()
def do_render( self, cr, widget, background_area, cell_area, flags ):
cell_text = self.props.text
self.props.underline = ( "X" in cell_text )
self.props.weight = 700 if ( cell_text.isdigit() and int( cell_text ) > 3 ) else 400
return Gtk.CellRendererText.do_render( self, cr, widget, background_area, cell_area, flags )
GObject.type_register( My_CellRendererText )

Deleting widgets (involving tkinter module)

new guy here and I'm slowly getting the hang of python, but I have a question.
I have two files here
one is named first_file.py
from other_file import GameFrame
from Tkinter import Tk
def main():
tk = Tk()
tk.title("Game of Life Simulator")
tk.geometry("380x580")
GameFrame(tk)
tk.mainloop()
main()
and the other is other_file.py
from Tkinter import *
from tkFileDialog import *
class GameFrame (Frame):
def __init__(self, root):
Frame.__init__(self,root)
self.grid()
self.mychosenattribute=8
self.create_widgets()
def create_widgets(self):
for rows in range(1,21):
for columns in range(1,21):
self.columns = columns
self.rows = rows
self.cell = Button(self, text='X')
self.cell.bind("<Button-1>", self.toggle)
self.cell.grid(row=self.rows, column=self.columns)
reset = Button(self, text="Reset")
reset.bind("<Button-1>", self.reset_button)
reset.grid(row=22, column = 3, columnspan=5)
def reset_button(self, event):
self.cell.destroy()
for rows in range(1,21):
for columns in range(1,21):
self.columns = columns
self.rows = rows
self.cell = Button(self, text='')
self.cell.bind("<Button-1>", self.toggle)
self.cell.grid(row=self.rows, column=self.columns)
After I push the reset button what happens right now is one button gets destroyed and another set of buttons are made on top of the already present buttons, but I need to be able to destroy or atleast configure all buttons to be blank. So how would I do that for all the buttons since I used a for loop to generate them? (Is there a better way to generate the buttons besides using a for loop?) Thanks.
A common method is to save your objects in a list (or dictionary) in order to access them when needed. A simple example:
self.mybuttons = defaultdict(list)
for rows in range(1,21):
for columns in range(1,21):
self.mybuttons[rows].append(Button(self, text=''))
Then you can get buttons, this way:
abutton = self.mybuttons[arow][acolumn]
There are some problems with your code that prevent running it (indentation of the reset lines and the use of the undefined self.toggle), so I could not fix it, but this example should be enough for you to do it.

Categories

Resources