11 - Programy z GUI - PyQt5 cz. II
Teraz utworzymy aplikację, która będzie otwierała plik z danymi w formacie csv
a następnie umożliwi wygenerowanie prostego wykresu słupkowego, który będzie można zapisać w kilku formatach.
Działająca aplikacja będzie wyglądać mniej więcej tak:
Utworz nowy formularz, tym razem wybierając Main Window
:
Zmień rozmiar formularza na 700 x 420, możesz to ustawić w panelu właściwości po prawej (właściwość geometry
: Szerokość
i Wysokość
).
Zapisz w osobnym katalogu jako wizualizator.ui
.
Formularz powinien wyglądać mniej więcej tak:
Jak widać, na górze okna pojawił się pasek Menu na którym znajduje się napis Wpisz tutaj
.
Kliknij dwukrotnie w ten napis i zmień jego treść na Plik
.
Jeśli teraz znów klikniesz na napis, pojawi się rozwijane Menu a w nim kolejny napis Wpisz tutaj
.
Teraz możemy dodać kolejne pozycje Menu.
Dodaj kolejno pozycje i ustaw ich nazwy (Właściwość
-> QObject
-> objectName
):
Otwórz dane
- nazwa:actionOtworz
Zapisz wykres
- nazwa:actionZapisz
Zamknij aplikację
- nazwa:actionZamknij
Menu powinno wyglądać tak:
Teraz zmień dla pozycji Zapisz wykres
właściwość QAction
-> enabled
na Fałsz
, czyli trzeba odznaczyć ,,ptaszka’':
Zmieniając tę właściwość spowodowaliśmy, że ta pozycja w menu będzie domyślnie nieaktywna. Uaktywni się, dopiero wtedy, gdy wygenerujemy wykres, ale to zakodujemy później.
Na górze formularza umieść widżet Label
, a następnie:
- rozciągnij go na szerokość okna (zachowując pewien margines)
- zmień jego nazwę na
otwartyPlik
- ustaw jego wysokość na
16
(geometry
->Wysokość
) - ustaw właściwość
sizePolicy
->Strategia Pionowa
naFixed
Przy wciąż zaznaczonym widżecie otwartyPlik
znajdź właściwość styleSheet
, kliknij w prawej kolumnie i pojawi się okienko z przyciskiem z trzema kropkami:
Kliknij w ,,trzy kropki’’, pojawi się okienko:
Możemy w nim edytować wygląd widżetów, przy pomocy kaskadowych arkuszy stylów (CSS) o których nie będziemy szerzej tu mówić, ,,wyklikamy’’ odpowiednie opcje.
Kliknij na strzałkę w dół znajdującą się na prawo od Dodaj kolor
i wybierz background-color
:
Pojawi się okienko, w którym wybierz jakiś kolor:
Po zatwierdzeniu w poprzednim okienku pojawi się wybrana wartość:
Zatwierdź. Teraz zmienił się kolor widżetu:
Teraz z palety po lewej znajdź sekcję Item Views (Model-Based)
i tam wybierz widżet Table View
:
Przeciągnij na formularz i umieść po lewej stronie stronie, powiększ tak aby zajmował mniej więcej taki obszar:
Zmień nazwę widżetu na tabela
.
Po prawej stronie umieść kolejny widżet Label
a następnie:
- zmień jego tło na dowolny kolor,
- zmień jego wielkość tak aby zajmował podobny obszar jak
tabela
po jej prawej stronie - zmień nazwę na
wykres
- usuń domyślny tekst Teraz nasz formularz powinien wyglądać mniej więcej tak:
W lewym dolnym rogu dodaj Push Button
, zmień tekst na Generuj wykres
, nazwa widżetu powinna zmienić się na generujButton
, jeśli nie to zmień na taką:
Zaznacz teraz widżety tabela
i wykres
a następnie kliknij na ikonę Rozmieść poziomo w splitterze
.
Splitter organizuje układ obu widżetów w poziomie i pozwoli na regulację szerokości obszaru zajmowanego przez oba widżety.
Całość się zmniejszyła:
Przywróć widżetom poprzednią wielkość, przeciągając niebieskie kwadraty zaznaczenia.
Kliknij w miejsce formularza, gdzie nie znajduje się żaden z dodanych widżetów.
Następnie kliknij na ikonę Rozmieść w siatce
.
Teraz formularz będzie wyglądał mniej więcej tak:
Ustawiliśmy właśnie Top Level Layout
.
Dzięki tej operacji, widżety będą automatycznie zmieniały wielkość wraz ze zmianami wielkości okna.
Zmieniła się wielkość przycisku, ustawimy jego wielkość na stałą.
Znajdź właściwość sizePolicy
-> Strategia pozioma
na Fixed
.
Przy okazji odznacz właściwość QWidget
- enabled
.
Teraz przycisk będzie nieaktywny.
Nie ma sensu, żeby był aktywny, dopóki nie wczytamy danych.
W końcu projekt naszego interfejsu będzie wyglądał mniej więcej tak:
W panelu po prawej, w Hierarchii obiektów
wybierz centralwidget
i we Właściwościach
zmień wartości marginesów na 10
.
Możesz podejrzeć wygląd aplikacji wybierając w menu Formularz
-> Podgląd...
(klawisze: Ctrl+R
).
Oczywiście to tylko podgląd interfejsu, więc aplikacja jeszcze nie działa, ale można sprawdzić np. zachowanie widżetów w odpowiedzi na zmianę wielkości okna, czy przesuwanie ,,splittera’’ między widżetami tabela
i wykres
.
Zawartość pliku wizualizator.ui
powinna być mniej więcej (mogą być różnice np. w wybranych kolorach, czy rozmiarach widżetów) taka:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>422</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>10</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>10</number>
</property>
<property name="bottomMargin">
<number>10</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="otwartyPlik">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgb(226, 226, 226);</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTableView" name="tabela"/>
<widget class="QLabel" name="wykres">
<property name="styleSheet">
<string notr="true">background-color: rgb(230, 230, 230);</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="generujButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Generuj Wykres</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>20</height>
</rect>
</property>
<widget class="QMenu" name="menuPlik">
<property name="title">
<string>Plik</string>
</property>
<addaction name="actionOtworz"/>
<addaction name="actionZapisz"/>
<addaction name="actionZamknij"/>
</widget>
<addaction name="menuPlik"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionOtworz">
<property name="text">
<string>Otwórz dane</string>
</property>
</action>
<action name="actionZapisz">
<property name="text">
<string>Zapisz wykres</string>
</property>
</action>
<action name="actionZamknij">
<property name="text">
<string>Zamknij aplikację</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>
Teraz przygotujmy dwa pliki: tabela_model.py
oraz wizualizator.py
, które będą zawierały kod Pythona, który sprawi, ze nasza prosta aplikacja będzie nie tylko wyglądać ale także działać.
Pierwszy z nich zawiera klasę potomną abstrakcyjnej klasy QAbstractTableModel.
Nie omawialiśmy na kursie klas abstrakcyjnych, w skrócie: musimy napisać określone metody, aby klasa działała.
Drugi będzie głównym plikiem aplikacji, w którym znajdziemy główny kod aplikacji.
Zawartość pliku tabela_model.py
:
from PyQt5.QtCore import Qt, QAbstractTableModel
class TabelaModel(QAbstractTableModel):
"""Klasa potomna abstrakcyjnej klasy QAbstractTableModel
należy zaimplementować poniższe metody.
"""
def __init__(self, data):
super().__init__()
self._data = data
def rowCount(self, parent=None):
"""Zwraca liczbę rzędów"""
return self._data.shape[0]
def columnCount(self, parent=None):
"""Zwraca liczbę kolumn"""
return self._data.shape[1]
def data(self, index, role=Qt.DisplayRole):
"""Zwraca dane z tabeli określone przez index"""
if index.isValid():
if role == Qt.DisplayRole:
return str(self._data.iloc[index.row(), index.column()])
return None
def headerData(self, col, orientation, role):
"""Ustawia nazwy kolumn w tabeli"""
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._data.columns[col]
return None
Zawartość pliku wizualizator.py
:
import sys
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog
from PyQt5.uic import loadUi
from PyQt5.QtGui import QPixmap
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import tabela_model as tab
class Wizualizator(QMainWindow):
"""Główna klasa aplkacji"""
def __init__(self):
super().__init__()
# Import interfejsu użytkownika bezpośrednio
# z pliku wizualizator.ui
loadUi("wizualizator.ui", self)
self.show()
# Powiązanie sygnałów (akcji wywoływanych na widżetach)
# i slotów (funkcji)
self.actionOtworz.triggered.connect(self.otworz_plik)
self.actionZamknij.triggered.connect(self.zakoncz)
self.actionZapisz.triggered.connect(self.zapisz_wykres)
self.generujButton.clicked.connect(self.generuj_wykres)
self.tabela.clicked.connect(self.pobierz_wspolrzedne)
# Status załadowania danych do tabeli potrzebne
# do sprawdzenia czy można generować wykres
self.dane_zaladowane = False
def pobierz_wspolrzedne(self, item):
"""Pobiera ideks wybranej kolumny w tabeli.
Wykres będzie generowany dla danych w tej kolumnie
"""
if item.column() < 2:
self.wybrana_kolumna = 1
else:
self.wybrana_kolumna = item.column()
def otworz_plik(self):
"""Otwiera plik z użyciem okienka dialogowego"""
# Otwieranie okna dialogiwego
plik, _ = QFileDialog.getOpenFileName(self,
"Otwórz plik",
"",
"Wszystkie pliki (*);;CSV files (*.csv)")
# Jeżeli wybrano plik do otwarcia
if plik:
# Odczyt danych z pliku csv
self.dane = pd.read_csv(plik)
self.dane_zaladowane = True
# Tworzenie obiektu tabeli
dane_tab = tab.TabelaModel(self.dane)
# Załadowanie tabeli do widżetu
self.tabela.setModel(dane_tab)
# Uaktywnienie przycisku generowania wykresu
self.generujButton.setEnabled(True)
# W widżecie otwartyPlik zostanie wyświetlona
# ścieżka i nazwa otwartego poliku z danymi
self.otwartyPlik.setText(plik)
# Zmiana koloru tła widżetu
self.otwartyPlik.setStyleSheet("background-color: rgb(240, 240, 240)")
# Domyślnie do wykresu używane sa dane z 2 kolumny
# (index = 1)
self.wybrana_kolumna = 1
def zapisz_wykres(self):
"""Zapisuje wykres w wybranym formacie"""
# Okno dialogowe wyboru pliku do zapisania
plik, _ = QFileDialog.getSaveFileName(
self,
"Zapisz wykres jako",
".png",
"Wszystkie pliki (*);;"+
"Pliki bitmapowe (*.png *.jpg);;"+
"Pliki wektorowe (*.svg *.pdf)",
)
# Jeśli została wybrana nazwa pliku, to zapis wykresu
if plik:
self.wykres_sb.get_figure().savefig(plik)
def generuj_wykres(self):
"""Generuje wykres"""
if self.dane_zaladowane:
# Dane dla osi x - pierwsza kolumna danych
dane_x = self.dane.iloc[:,0]
# Dane dla osi y - wybrana kolumna (przez kliknięcie komórki)
# Domyślnie jest to 2 kolumna
dane_y = self.dane.iloc[:,self.wybrana_kolumna]
# Nazwa pliku używanego przez program do zapisu
# wykresu, który będzie wyświetlany
plik = 'wykres.png'
# Potrzebne aby wykresy się "resetowały" przy zmianie danych
plt.figure()
# Tworzenie wykresu z użyciem seaborn
self.wykres_sb = sns.barplot(x = dane_x,
y = dane_y,
palette = ['brown', 'red', 'darkorange'])
# Zapis wykresu do pliku, który będzie wyświetlany
self.wykres_sb.get_figure().savefig(plik)
# Tworzenie obiektu z obrazem wykresu
wykres_png = QPixmap(plik)
# Umieszczenie obrazu wykresu w widżecie wykres
self.wykres.setPixmap(wykres_png)
# Zmiana rozmiaru widżetu wykres do rozmiaru obrazka
self.wykres.resize(wykres_png.width(),wykres_png.height())
# Zmiana rozmiarów okna aplikacji
self.resize(self.tabela.width() +
wykres_png.width() +
25,wykres_png.height()+100)
# Uaktywnienie pozycji w menu umozliwiającej zapis wykresu
self.actionZapisz.setEnabled(True)
else:
print("Brak danych")
def zakoncz(self):
"""Wyłącza aplikację"""
self.close()
if __name__ == "__main__":
"""Główna funkcja aplikacji"""
# Uruchomienie obiektu aplikacji
aplikacja = QApplication(sys.argv)
# Tworzenie okna
okno = Wizualizator()
# Wyświetlenie okna
okno.show()
# Przy zamknięciu aplikacji, zwalnia się pamięć komputera
sys.exit(aplikacja.exec_())
Uruchom program:
python wizualizator.py
Wczytaj plik z danymi.
Powinien to być plik w formacie csv
(dane poprzedzielane przecinkami), w pierwszej kolumnie powinny się znajdować nazwy słupków wykresu, w innych dane liczbowe.
Możesz użyć np. pliku dane.csv
lub dane2.csv
.
Po wczytaniu danych, kliknij na przycisk Generuj wykres
, powinien się wygenerować wykres słupkowy dla pierwszej kolumny danych (czyli drugiej kolumny tabeli).
Kliknij w dowolną komórkę innej kolumny, ponownie wygeneruj wykres - teraz będzie to wykres dla danych we wskazanej kolumnie.
Zapisz wykres używając odpowiedniej opcji w menu.
Zadanie
Napisz program, który pokazuje wyniki symulacji wpływu mutacji na frekwencje alleli. Ogólne zasady symulacji opisałem w zadaniu do lekcji Biblioteki. Tym razem jednak, program powinien mieć GUI, w którym użytkownik wprowadza parametry, pojawiają się wyniki ,,znakowe’’ dla każdego pokolenia co sekundę i w którym wyświetla się końcowy wykres.