diff options
Diffstat (limited to 'sources/pyside6/doc/tutorials/datavisualize')
26 files changed, 1001 insertions, 0 deletions
diff --git a/sources/pyside6/doc/tutorials/datavisualize/add_chart.rst b/sources/pyside6/doc/tutorials/datavisualize/add_chart.rst new file mode 100644 index 000000000..59a9d9ee0 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/add_chart.rst @@ -0,0 +1,20 @@ +Chapter 5 - Add a chart view +============================= + +A table is nice to present data, but a chart is even better. For this, you +need the QtCharts module that provides many types of plots and options to +graphically represent data. + +The placeholder for a plot is a QChartView, and inside that Widget you can +place a QChart. As a first step, try including only this without any data to +plot. + +Make the following highlighted changes to :code:`main_widget.py` from the +previous chapter to add a QChartView: + +.. literalinclude:: datavisualize5/main_widget.py + :linenos: + :lines: 3- + :emphasize-lines: 2-3,6,22-36,47-49 + + diff --git a/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst b/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst new file mode 100644 index 000000000..623372a07 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/add_mainwindow.rst @@ -0,0 +1,32 @@ +Chapter 3 - Create an empty QMainWindow +========================================== + +You can now think of presenting your data in a UI. A QMainWindow provides a +convenient structure for GUI applications, such as a menu bar and status bar. +The following image shows the layout that QMainWindow offers out-of-the box: + +.. image:: images/QMainWindow-layout.png + :alt: QMainWindow layout + :align: right + +In this case, let your application inherit from QMainWindow, and add the +following UI elements: + +* A "File" menu to open a File dialog. +* An "Exit" menu close the window. +* A status message on the status bar when the application starts. + +In addition, you can define a fixed size for the window or adjust it based on +the resolution you currently have. In the following snippet, you will see how +window size is defined based on available screen width (80%) and height (70%). + +.. note:: You can achieve a similar structure using other Qt elements like + QMenuBar, QWidget, and QStatusBar. Refer the QMainWindow layout for + guidance. + +.. literalinclude:: datavisualize3/main_window.py + :language: python + :linenos: + :lines: 4- + +Try running the script to see what output you get with it. diff --git a/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst b/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst new file mode 100644 index 000000000..f658640bf --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/add_tableview.rst @@ -0,0 +1,70 @@ +Chapter 4 - Add a QTableView +============================= + +Now that you have a QMainWindow, you can include a centralWidget to your +interface. Usually, a QWidget is used to display data in most data-driven +applications. Use a table view to display your data. + +The first step is to add a horizontal layout with just a QTableView. You +can create a QTableView object and place it inside a QHBoxLayout. Once the +QWidget is properly built, pass the object to the QMainWindow as its central +widget. + +Remember that a QTableView needs a model to display information. In this case, +you can use a QAbstractTableModel instance. + +.. note:: You could also use the default item model that comes with a + QTableWidget instead. QTableWidget is a convenience class that reduces + your codebase considerably as you don't need to implement a data model. + However, it's less flexible than a QTableView, as QTableWidget cannot be + used with just any data. For more insight about Qt's model-view framework, + refer to the + `Model View Programming <https://doc.qt.io/qt-5/model-view-programming.html>` + documentation. + +Implementing the model for your QTableView, allows you to: +- set the headers, +- manipulate the formats of the cell values (remember we have UTC time and float +numbers), +- set style properties like text alignment, +- and even set color properties for the cell or its content. + +To subclass the QAbstractTable, you must reimplement its virtual methods, +rowCount(), columnCount(), and data(). This way, you can ensure that the data +is handled properly. In addition, reimplement the headerData() method to +provide the header information to the view. + +Here is a script that implements the CustomTableModel: + +.. literalinclude:: datavisualize4/table_model.py + :language: python + :linenos: + :lines: 3- + +Now, create a QWidget that has a QTableView, and connect it to your +CustomTableModel. + +.. literalinclude:: datavisualize4/main_widget.py + :language: python + :linenos: + :emphasize-lines: 12-17 + :lines: 3- + +You also need minor changes to the :code:`main_window.py` and +:code:`main.py` from chapter 3 to include the Widget inside the +MainWindow. + +In the following snippets you'll see those changes highlighted: + +.. literalinclude:: datavisualize4/main_window.py + :language: python + :linenos: + :lines: 3- + :emphasize-lines: 8,11 + +.. literalinclude:: datavisualize4/main.py + :language: python + :linenos: + :lines: 3- + :emphasize-lines: 46-47 + diff --git a/sources/pyside6/doc/tutorials/datavisualize/all_hour.csv b/sources/pyside6/doc/tutorials/datavisualize/all_hour.csv new file mode 100644 index 000000000..400947c3c --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/all_hour.csv @@ -0,0 +1,8 @@ +time,latitude,longitude,depth,mag,magType,nst,gap,dmin,rms,net,id,updated,place,type,horizontalError,depthError,magError,magNst,status,locationSource,magSource +2019-01-10T12:11:24.810Z,34.1281662,-117.7754974,4.46,1.18,ml,22,69,0.04475,0.13,ci,ci38421072,2019-01-10T12:13:30.138Z,"3km NNW of La Verne, CA",earthquake,0.3,0.55,0.246,6,automatic,ci,ci +2019-01-10T12:04:26.320Z,19.4433327,-155.6159973,0.72,1.79,md,22,99,0.04026,0.3,hv,hv70763571,2019-01-10T12:07:28.690Z,"26km E of Honaunau-Napoopoo, Hawaii",earthquake,0.6,1.79,0.28,6,automatic,hv,hv +2019-01-10T11:57:48.980Z,33.3225,-116.3931667,4.84,0.62,ml,15,211,0.05776,0.16,ci,ci38421064,2019-01-10T12:01:29.166Z,"8km NNW of Borrego Springs, CA",earthquake,0.71,0.68,0.111,11,automatic,ci,ci +2019-01-10T11:52:09.490Z,38.8356667,-122.8366699,1.28,2.74,md,25,77,0.003061,0.04,nc,nc73131566,2019-01-10T12:14:02.757Z,"10km NW of The Geysers, CA",earthquake,0.19,0.29,0.06,7,automatic,nc,nc +2019-01-10T11:25:44.854Z,65.1082,-149.3701,20.6,2.1,ml,,,,1.02,ak,ak019gq2oer,2019-01-10T11:37:07.060Z,"60km NNW of North Nenana, Alaska",earthquake,,0.3,,,automatic,ak,ak +2019-01-10T11:25:23.786Z,69.1518,-144.4977,10.4,3.7,ml,,,,0.74,ak,ak019gq2ndz,2019-01-10T11:47:11.284Z,"114km SSW of Kaktovik, Alaska",earthquake,,1.6,,,reviewed,ak,ak +2019-01-10T11:16:11.761Z,61.3318,-150.0708,20.1,2.7,ml,,,,0.83,ak,ak019gq0ozj,2019-01-10T11:29:24.610Z,"15km NW of Anchorage, Alaska",earthquake,,0.4,,,automatic,ak,ak diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize1/main.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize1/main.py new file mode 100644 index 000000000..995b9906a --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize1/main.py @@ -0,0 +1,18 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import argparse +import pandas as pd + + +def read_data(fname): + return pd.read_csv(fname) + + +if __name__ == "__main__": + options = argparse.ArgumentParser() + options.add_argument("-f", "--file", type=str, required=True) + args = options.parse_args() + data = read_data(args.file) + print(data) + diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize2/main.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize2/main.py new file mode 100644 index 000000000..9ea96097c --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize2/main.py @@ -0,0 +1,41 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import argparse +import pandas as pd + +from PySide6.QtCore import QDateTime, QTimeZone + + +def transform_date(utc, timezone=None): + utc_fmt = "yyyy-MM-ddTHH:mm:ss.zzzZ" + new_date = QDateTime().fromString(utc, utc_fmt) + if timezone: + new_date.setTimeZone(timezone) + return new_date + + +def read_data(fname): + # Read the CSV content + df = pd.read_csv(fname) + + # Remove wrong magnitudes + df = df.drop(df[df.mag < 0].index) + magnitudes = df["mag"] + + # My local timezone + timezone = QTimeZone(b"Europe/Berlin") + + # Get timestamp transformed to our timezone + times = df["time"].apply(lambda x: transform_date(x, timezone)) + + return times, magnitudes + + +if __name__ == "__main__": + options = argparse.ArgumentParser() + options.add_argument("-f", "--file", type=str, required=True) + args = options.parse_args() + data = read_data(args.file) + print(data) + diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main.py new file mode 100644 index 000000000..bbf85e17d --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main.py @@ -0,0 +1,51 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +import argparse +import pandas as pd + +from PySide6.QtCore import QDateTime, QTimeZone +from PySide6.QtWidgets import QApplication +from main_window import MainWindow + + +def transform_date(utc, timezone=None): + utc_fmt = "yyyy-MM-ddTHH:mm:ss.zzzZ" + new_date = QDateTime().fromString(utc, utc_fmt) + if timezone: + new_date.setTimeZone(timezone) + return new_date + + +def read_data(fname): + # Read the CSV content + df = pd.read_csv(fname) + + # Remove wrong magnitudes + df = df.drop(df[df.mag < 0].index) + magnitudes = df["mag"] + + # My local timezone + timezone = QTimeZone(b"Europe/Berlin") + + # Get timestamp transformed to our timezone + times = df["time"].apply(lambda x: transform_date(x, timezone)) + + return times, magnitudes + + +if __name__ == "__main__": + options = argparse.ArgumentParser() + options.add_argument("-f", "--file", type=str, required=True) + args = options.parse_args() + data = read_data(args.file) + + # Qt Application + app = QApplication(sys.argv) + + window = MainWindow() + window.show() + + sys.exit(app.exec()) + diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py new file mode 100644 index 000000000..745f2fefe --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize3/main_window.py @@ -0,0 +1,31 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Slot +from PySide6.QtGui import QAction, QKeySequence +from PySide6.QtWidgets import QMainWindow + + +class MainWindow(QMainWindow): + def __init__(self): + QMainWindow.__init__(self) + self.setWindowTitle("Eartquakes information") + + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + # Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut(QKeySequence.Quit) + exit_action.triggered.connect(self.close) + + self.file_menu.addAction(exit_action) + + # Status Bar + self.status = self.statusBar() + self.status.showMessage("Data loaded and plotted") + + # Window dimensions + geometry = self.screen().availableGeometry() + self.setFixedSize(geometry.width() * 0.8, geometry.height() * 0.7) diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main.py new file mode 100644 index 000000000..87b962bd3 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main.py @@ -0,0 +1,53 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +import argparse +import pandas as pd + +from PySide6.QtCore import QDateTime, QTimeZone +from PySide6.QtWidgets import QApplication +from main_window import MainWindow +from main_widget import Widget + + +def transform_date(utc, timezone=None): + utc_fmt = "yyyy-MM-ddTHH:mm:ss.zzzZ" + new_date = QDateTime().fromString(utc, utc_fmt) + if timezone: + new_date.setTimeZone(timezone) + return new_date + + +def read_data(fname): + # Read the CSV content + df = pd.read_csv(fname) + + # Remove wrong magnitudes + df = df.drop(df[df.mag < 0].index) + magnitudes = df["mag"] + + # My local timezone + timezone = QTimeZone(b"Europe/Berlin") + + # Get timestamp transformed to our timezone + times = df["time"].apply(lambda x: transform_date(x, timezone)) + + return times, magnitudes + + +if __name__ == "__main__": + options = argparse.ArgumentParser() + options.add_argument("-f", "--file", type=str, required=True) + args = options.parse_args() + data = read_data(args.file) + + # Qt Application + app = QApplication(sys.argv) + + widget = Widget(data) + window = MainWindow(widget) + window.show() + + sys.exit(app.exec()) + diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py new file mode 100644 index 000000000..2a2bfeb09 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_widget.py @@ -0,0 +1,43 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtWidgets import (QHBoxLayout, QHeaderView, QSizePolicy, + QTableView, QWidget) + +from table_model import CustomTableModel + + +class Widget(QWidget): + def __init__(self, data): + QWidget.__init__(self) + + # Getting the Model + self.model = CustomTableModel(data) + + # Creating a QTableView + self.table_view = QTableView() + self.table_view.setModel(self.model) + + # QTableView Headers + self.horizontal_header = self.table_view.horizontalHeader() + self.vertical_header = self.table_view.verticalHeader() + self.horizontal_header.setSectionResizeMode( + QHeaderView.ResizeToContents + ) + self.vertical_header.setSectionResizeMode( + QHeaderView.ResizeToContents + ) + self.horizontal_header.setStretchLastSection(True) + + # QWidget Layout + self.main_layout = QHBoxLayout() + size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + + ## Left layout + size.setHorizontalStretch(1) + self.table_view.setSizePolicy(size) + self.main_layout.addWidget(self.table_view) + + # Set the layout to the QWidget + self.setLayout(self.main_layout) + diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py new file mode 100644 index 000000000..ebe2997a1 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/main_window.py @@ -0,0 +1,32 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Slot +from PySide6.QtGui import QAction, QKeySequence +from PySide6.QtWidgets import QMainWindow + + +class MainWindow(QMainWindow): + def __init__(self, widget): + QMainWindow.__init__(self) + self.setWindowTitle("Eartquakes information") + self.setCentralWidget(widget) + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + ## Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut(QKeySequence.Quit) + exit_action.triggered.connect(self.close) + + self.file_menu.addAction(exit_action) + + # Status Bar + self.status = self.statusBar() + self.status.showMessage("Data loaded and plotted") + + # Window dimensions + geometry = self.screen().availableGeometry() + self.setFixedSize(geometry.width() * 0.8, geometry.height() * 0.7) + diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py new file mode 100644 index 000000000..08eeeeed6 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize4/table_model.py @@ -0,0 +1,51 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex +from PySide6.QtGui import QColor + + +class CustomTableModel(QAbstractTableModel): + def __init__(self, data=None): + QAbstractTableModel.__init__(self) + self.load_data(data) + + def load_data(self, data): + self.input_dates = data[0].values + self.input_magnitudes = data[1].values + + self.column_count = 2 + self.row_count = len(self.input_magnitudes) + + def rowCount(self, parent=QModelIndex()): + return self.row_count + + def columnCount(self, parent=QModelIndex()): + return self.column_count + + def headerData(self, section, orientation, role): + if role != Qt.DisplayRole: + return None + if orientation == Qt.Horizontal: + return ("Date", "Magnitude")[section] + else: + return f"{section}" + + def data(self, index, role=Qt.DisplayRole): + column = index.column() + row = index.row() + + if role == Qt.DisplayRole: + if column == 0: + date = self.input_dates[row].toPython() + return str(date)[:-3] + elif column == 1: + magnitude = self.input_magnitudes[row] + return f"{magnitude:.2f}" + elif role == Qt.BackgroundRole: + return QColor(Qt.white) + elif role == Qt.TextAlignmentRole: + return Qt.AlignRight + + return None + diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main.py new file mode 100644 index 000000000..87b962bd3 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main.py @@ -0,0 +1,53 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +import argparse +import pandas as pd + +from PySide6.QtCore import QDateTime, QTimeZone +from PySide6.QtWidgets import QApplication +from main_window import MainWindow +from main_widget import Widget + + +def transform_date(utc, timezone=None): + utc_fmt = "yyyy-MM-ddTHH:mm:ss.zzzZ" + new_date = QDateTime().fromString(utc, utc_fmt) + if timezone: + new_date.setTimeZone(timezone) + return new_date + + +def read_data(fname): + # Read the CSV content + df = pd.read_csv(fname) + + # Remove wrong magnitudes + df = df.drop(df[df.mag < 0].index) + magnitudes = df["mag"] + + # My local timezone + timezone = QTimeZone(b"Europe/Berlin") + + # Get timestamp transformed to our timezone + times = df["time"].apply(lambda x: transform_date(x, timezone)) + + return times, magnitudes + + +if __name__ == "__main__": + options = argparse.ArgumentParser() + options.add_argument("-f", "--file", type=str, required=True) + args = options.parse_args() + data = read_data(args.file) + + # Qt Application + app = QApplication(sys.argv) + + widget = Widget(data) + window = MainWindow(widget) + window.show() + + sys.exit(app.exec()) + diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py new file mode 100644 index 000000000..15b7e97e5 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_widget.py @@ -0,0 +1,54 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import QDateTime, Qt +from PySide6.QtGui import QPainter +from PySide6.QtWidgets import (QWidget, QHeaderView, QHBoxLayout, QTableView, + QSizePolicy) +from PySide6.QtCharts import QChart, QChartView, QLineSeries, QDateTimeAxis, QValueAxis + +from table_model import CustomTableModel + + +class Widget(QWidget): + def __init__(self, data): + QWidget.__init__(self) + + # Getting the Model + self.model = CustomTableModel(data) + + # Creating a QTableView + self.table_view = QTableView() + self.table_view.setModel(self.model) + + # QTableView Headers + self.horizontal_header = self.table_view.horizontalHeader() + self.vertical_header = self.table_view.verticalHeader() + self.horizontal_header.setSectionResizeMode(QHeaderView.ResizeToContents) + self.vertical_header.setSectionResizeMode(QHeaderView.ResizeToContents) + self.horizontal_header.setStretchLastSection(True) + + # Creating QChart + self.chart = QChart() + self.chart.setAnimationOptions(QChart.AllAnimations) + + # Creating QChartView + self.chart_view = QChartView(self.chart) + self.chart_view.setRenderHint(QPainter.Antialiasing) + + # QWidget Layout + self.main_layout = QHBoxLayout() + size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + + ## Left layout + size.setHorizontalStretch(1) + self.table_view.setSizePolicy(size) + self.main_layout.addWidget(self.table_view) + + ## Right Layout + size.setHorizontalStretch(4) + self.chart_view.setSizePolicy(size) + self.main_layout.addWidget(self.chart_view) + + # Set the layout to the QWidget + self.setLayout(self.main_layout) diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py new file mode 100644 index 000000000..ed12c1f8f --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/main_window.py @@ -0,0 +1,32 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Slot +from PySide6.QtGui import QAction, QKeySequence +from PySide6.QtWidgets import QMainWindow + + +class MainWindow(QMainWindow): + def __init__(self, widget): + QMainWindow.__init__(self) + self.setWindowTitle("Eartquakes information") + self.setCentralWidget(widget) + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + # Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut(QKeySequence.Quit) + exit_action.triggered.connect(self.close) + + self.file_menu.addAction(exit_action) + + # Status Bar + self.status = self.statusBar() + self.status.showMessage("Data loaded and plotted") + + # Window dimensions + geometry = self.screen().availableGeometry() + self.setFixedSize(geometry.width() * 0.8, geometry.height() * 0.7) + diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py new file mode 100644 index 000000000..08eeeeed6 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize5/table_model.py @@ -0,0 +1,51 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex +from PySide6.QtGui import QColor + + +class CustomTableModel(QAbstractTableModel): + def __init__(self, data=None): + QAbstractTableModel.__init__(self) + self.load_data(data) + + def load_data(self, data): + self.input_dates = data[0].values + self.input_magnitudes = data[1].values + + self.column_count = 2 + self.row_count = len(self.input_magnitudes) + + def rowCount(self, parent=QModelIndex()): + return self.row_count + + def columnCount(self, parent=QModelIndex()): + return self.column_count + + def headerData(self, section, orientation, role): + if role != Qt.DisplayRole: + return None + if orientation == Qt.Horizontal: + return ("Date", "Magnitude")[section] + else: + return f"{section}" + + def data(self, index, role=Qt.DisplayRole): + column = index.column() + row = index.row() + + if role == Qt.DisplayRole: + if column == 0: + date = self.input_dates[row].toPython() + return str(date)[:-3] + elif column == 1: + magnitude = self.input_magnitudes[row] + return f"{magnitude:.2f}" + elif role == Qt.BackgroundRole: + return QColor(Qt.white) + elif role == Qt.TextAlignmentRole: + return Qt.AlignRight + + return None + diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main.py new file mode 100644 index 000000000..2d851cc14 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main.py @@ -0,0 +1,55 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import sys +import argparse +import pandas as pd + +from PySide6.QtCore import QDateTime, QTimeZone +from PySide6.QtWidgets import QApplication + +from main_window import MainWindow +from main_widget import Widget + + +def transform_date(utc, timezone=None): + utc_fmt = "yyyy-MM-ddTHH:mm:ss.zzzZ" + new_date = QDateTime().fromString(utc, utc_fmt) + if timezone: + new_date.setTimeZone(timezone) + return new_date + + +def read_data(fname): + # Read the CSV content + df = pd.read_csv(fname) + + # Remove wrong magnitudes + df = df.drop(df[df.mag < 0].index) + magnitudes = df["mag"] + + # My local timezone + timezone = QTimeZone(b"Europe/Berlin") + + # Get timestamp transformed to our timezone + times = df["time"].apply(lambda x: transform_date(x, timezone)) + + return times, magnitudes + + +if __name__ == "__main__": + options = argparse.ArgumentParser() + options.add_argument("-f", "--file", type=str, required=True) + args = options.parse_args() + data = read_data(args.file) + + # Qt Application + app = QApplication(sys.argv) + + # QWidget + widget = Widget(data) + # QMainWindow using QWidget as central widget + window = MainWindow(widget) + + window.show() + sys.exit(app.exec()) diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py new file mode 100644 index 000000000..cbcc126a1 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_widget.py @@ -0,0 +1,95 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import QDateTime, Qt +from PySide6.QtGui import QPainter +from PySide6.QtWidgets import (QWidget, QHeaderView, QHBoxLayout, QTableView, + QSizePolicy) +from PySide6.QtCharts import QChart, QChartView, QLineSeries, QDateTimeAxis, QValueAxis + +from table_model import CustomTableModel + + +class Widget(QWidget): + def __init__(self, data): + QWidget.__init__(self) + + # Getting the Model + self.model = CustomTableModel(data) + + # Creating a QTableView + self.table_view = QTableView() + self.table_view.setModel(self.model) + + # QTableView Headers + resize = QHeaderView.ResizeToContents + self.horizontal_header = self.table_view.horizontalHeader() + self.vertical_header = self.table_view.verticalHeader() + self.horizontal_header.setSectionResizeMode(resize) + self.vertical_header.setSectionResizeMode(resize) + self.horizontal_header.setStretchLastSection(True) + + # Creating QChart + self.chart = QChart() + self.chart.setAnimationOptions(QChart.AllAnimations) + self.add_series("Magnitude (Column 1)", [0, 1]) + + # Creating QChartView + self.chart_view = QChartView(self.chart) + self.chart_view.setRenderHint(QPainter.Antialiasing) + + # QWidget Layout + self.main_layout = QHBoxLayout() + size = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + + # Left layout + size.setHorizontalStretch(1) + self.table_view.setSizePolicy(size) + self.main_layout.addWidget(self.table_view) + + # Right Layout + size.setHorizontalStretch(4) + self.chart_view.setSizePolicy(size) + self.main_layout.addWidget(self.chart_view) + + # Set the layout to the QWidget + self.setLayout(self.main_layout) + + def add_series(self, name, columns): + # Create QLineSeries + self.series = QLineSeries() + self.series.setName(name) + + # Filling QLineSeries + for i in range(self.model.rowCount()): + # Getting the data + t = self.model.index(i, 0).data() + date_fmt = "yyyy-MM-dd HH:mm:ss.zzz" + + x = QDateTime().fromString(t, date_fmt).toSecsSinceEpoch() + y = float(self.model.index(i, 1).data()) + + if x > 0 and y > 0: + self.series.append(x, y) + + self.chart.addSeries(self.series) + + # Setting X-axis + self.axis_x = QDateTimeAxis() + self.axis_x.setTickCount(10) + self.axis_x.setFormat("dd.MM (h:mm)") + self.axis_x.setTitleText("Date") + self.chart.addAxis(self.axis_x, Qt.AlignBottom) + self.series.attachAxis(self.axis_x) + # Setting Y-axis + self.axis_y = QValueAxis() + self.axis_y.setTickCount(10) + self.axis_y.setLabelFormat("%.2f") + self.axis_y.setTitleText("Magnitude") + self.chart.addAxis(self.axis_y, Qt.AlignLeft) + self.series.attachAxis(self.axis_y) + + # Getting the color from the QChart to use it on the QTableView + color_name = self.series.pen().color().name() + self.model.color = f"{color_name}" + diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py new file mode 100644 index 000000000..116e53dc3 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/main_window.py @@ -0,0 +1,32 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Slot +from PySide6.QtGui import QAction, QKeySequence, QScreen +from PySide6.QtWidgets import QMainWindow + + +class MainWindow(QMainWindow): + def __init__(self, widget): + QMainWindow.__init__(self) + self.setWindowTitle("Eartquakes information") + + # Menu + self.menu = self.menuBar() + self.file_menu = self.menu.addMenu("File") + + # Exit QAction + exit_action = QAction("Exit", self) + exit_action.setShortcut(QKeySequence.Quit) + exit_action.triggered.connect(self.close) + + self.file_menu.addAction(exit_action) + + # Status Bar + self.status = self.statusBar() + self.status.showMessage("Data loaded and plotted") + + # Window dimensions + geometry = self.screen().availableGeometry() + self.setFixedSize(geometry.width() * 0.8, geometry.height() * 0.7) + self.setCentralWidget(widget) diff --git a/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py new file mode 100644 index 000000000..4952c0c38 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/datavisualize6/table_model.py @@ -0,0 +1,51 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from PySide6.QtCore import Qt, QAbstractTableModel, QModelIndex +from PySide6.QtGui import QColor + + +class CustomTableModel(QAbstractTableModel): + def __init__(self, data=None): + QAbstractTableModel.__init__(self) + self.color = None + self.load_data(data) + + def load_data(self, data): + self.input_dates = data[0].values + self.input_magnitudes = data[1].values + + self.column_count = 2 + self.row_count = len(self.input_magnitudes) + + def rowCount(self, parent=QModelIndex()): + return self.row_count + + def columnCount(self, parent=QModelIndex()): + return self.column_count + + def headerData(self, section, orientation, role): + if role != Qt.DisplayRole: + return None + if orientation == Qt.Horizontal: + return ("Date", "Magnitude")[section] + else: + return f"{section}" + + def data(self, index, role=Qt.DisplayRole): + column = index.column() + row = index.row() + + if role == Qt.DisplayRole: + if column == 0: + date = self.input_dates[row].toPython() + return str(date)[:-3] + elif column == 1: + magnitude = self.input_magnitudes[row] + return f"{magnitude:.2f}" + elif role == Qt.BackgroundRole: + return (QColor(Qt.white), QColor(self.color))[column] + elif role == Qt.TextAlignmentRole: + return Qt.AlignRight + + return None diff --git a/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst b/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst new file mode 100644 index 000000000..edfac3862 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/filter_data.rst @@ -0,0 +1,29 @@ +Chapter 2 - Filtering data +=========================== + +In the previous chapter, you learned how to read and print data that is a +bit raw. Now, try to select a few columns and handle them properly. + +Start with these two columns: Time (time) and Magnitude (mag). After getting +the information from these columns, filter and adapt the data. Try formatting +the date to Qt types. + +There is not much to do for the Magnitude column, as it's just a floating point +number. You could take special care to check if the data is correct. This could +be done by filtering the data that follows the condition, "magnitude > 0", to +avoid faulty data or unexpected behavior. + +The Date column provides data in UTC format (for example, +2018-12-11T21:14:44.682Z), so you could easily map it to a QDateTime object +defining the structure of the string. Additionally, you can adapt the time +based on the timezone you are in, using QTimeZone. + +The following script filters and formats the CSV data as described earlier: + +.. literalinclude:: datavisualize2/main.py + :language: python + :linenos: + :lines: 3- + +Now that you have a tuple of QDateTime and float data, try improving the +output further. That's what you'll learn in the following chapters. diff --git a/sources/pyside6/doc/tutorials/datavisualize/images/QMainWindow-layout.png b/sources/pyside6/doc/tutorials/datavisualize/images/QMainWindow-layout.png Binary files differnew file mode 100644 index 000000000..075d796b8 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/images/QMainWindow-layout.png diff --git a/sources/pyside6/doc/tutorials/datavisualize/images/datavisualization_app.png b/sources/pyside6/doc/tutorials/datavisualize/images/datavisualization_app.png Binary files differnew file mode 100644 index 000000000..8d7e4ba13 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/images/datavisualization_app.png diff --git a/sources/pyside6/doc/tutorials/datavisualize/index.rst b/sources/pyside6/doc/tutorials/datavisualize/index.rst new file mode 100644 index 000000000..35b56706a --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/index.rst @@ -0,0 +1,31 @@ +Data Visualization Tool Tutorial +********************************* + +In this tutorial, you'll learn about the data visualization capabilities +of |project|. To start with, find some open data to visualize. For example, +data about the magnitude of earthquakes during the last hour published on the +US Geological Survey website. You could download the +`All earthquakes <https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.csv>`_ +open data in a CSV format for this tutorial. + +.. image:: images/datavisualization_app.png + :width: 800 + :alt: Data Visualization Screenshot + +In the following chapters of this tutorial you'll learn how to +visualize data from a CSV in a line chart. + +.. toctree:: + :glob: + :titlesonly: + + read* + filter* + add_main* + add_tab* + add_chart* + plot* + +You can download the sources from `here`_. + +.. _here: https://code.qt.io/cgit/pyside/pyside-setup.git/tree/sources/pyside6/doc/tutorials/datavisualize diff --git a/sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst b/sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst new file mode 100644 index 000000000..f71fca4c8 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/plot_datapoints.rst @@ -0,0 +1,27 @@ +Chapter 6 - Plot the data in the ChartView +=========================================== + +The last step of this tutorial is to plot the CSV data inside our QChart. For +this, you need to go over our data and include the data on a QLineSeries. + +After adding the data to the series, you can modify the axis to properly +display the QDateTime on the X-axis, and the magnitude values on the Y-axis. + +Here is the updated :code:`main_widget.py` that includes an additional +function to plot data using a QLineSeries: + +.. literalinclude:: datavisualize6/main_widget.py + :language: python + :linenos: + :lines: 3- + :emphasize-lines: 33,56-91 + +Now, run the application to visualize the earthquake magnitudes +data at different times. + +.. image:: images/datavisualization_app.png + :width: 600 + :alt: Data Visualization Screenshot + +Try modifying the sources to get different output. For example, you could try +to plot more data from the CSV. diff --git a/sources/pyside6/doc/tutorials/datavisualize/read_data.rst b/sources/pyside6/doc/tutorials/datavisualize/read_data.rst new file mode 100644 index 000000000..53b0d1cb0 --- /dev/null +++ b/sources/pyside6/doc/tutorials/datavisualize/read_data.rst @@ -0,0 +1,41 @@ +Chapter 1 - Reading data from a CSV +=================================== + +There are several ways to read data from a CSV file. The following are the most +common ways: + +- Native reading +- the `CSV module <https://docs.python.org/3/library/csv.html>`_ +- the `numpy module <https://www.numpy.org>`_ +- the `pandas module <https://pandas.pydata.org/>`_ + +In this chapter, you will learn to use pandas to read and filter CSV data. +In addition, you could pass the data file through a command-line option to your +script. + +The following python script, :code:`main.py`, demonstrates how to do it: + +.. literalinclude:: datavisualize1/main.py + :language: python + :linenos: + :lines: 3- + +The Python script uses the :code:`argparse` module to accept and parse input +from the command line. It then uses the input, which in this case is the filename, +to read and print data to the prompt. + +Try running the script in the following way to check if you get desired output: + +:: + + $python datavisualize1/main.py -f all_hour.csv + time latitude longitude depth ... magNst status locationSource magSource + 0 2019-01-10T12:11:24.810Z 34.128166 -117.775497 4.46 ... 6.0 automatic ci ci + 1 2019-01-10T12:04:26.320Z 19.443333 -155.615997 0.72 ... 6.0 automatic hv hv + 2 2019-01-10T11:57:48.980Z 33.322500 -116.393167 4.84 ... 11.0 automatic ci ci + 3 2019-01-10T11:52:09.490Z 38.835667 -122.836670 1.28 ... 7.0 automatic nc nc + 4 2019-01-10T11:25:44.854Z 65.108200 -149.370100 20.60 ... NaN automatic ak ak + 5 2019-01-10T11:25:23.786Z 69.151800 -144.497700 10.40 ... NaN reviewed ak ak + 6 2019-01-10T11:16:11.761Z 61.331800 -150.070800 20.10 ... NaN automatic ak ak + + [7 rows x 22 columns] |