How to create a sliding window view over a dataframe in PyQt5 QTableView, QAbstractTableModel?
-
The following code will let me scroll down through a 200 by 4 table fetching 5 new rows each time. How do I modify this such that the table will open at a certain index (say 100) and I can scroll either up or down like a sliding window?
user @d_stranz mentioned this idea in this thread but didnt show how to implement - https://www.qtcentre.org/threads/62807-PyQt-QTableView-displying-100-000-000-rows
```
One possible solution is to implement a sliding window onto your underlying data that contains say, twice as many rows as can be viewed onscreen. When you scroll up or down, you slide the window along, moving the first and last index rows accordingly. This should also significantly improve your UI response time, since the view s only have to base their calculations on a few hundred rows instead of thousands or millions. This could probably be done with a proxy model. If having an accurate row number on the vertical header is important, you can re-implement your model's headerData() method to return a string containing the actual row number from the underlying data. "Here is the code I am working with
import sys from PyQt5 import QtWidgets, QtCore import pandas as pd import numpy as np class DelayedFetchingTableModel(QtCore.QAbstractTableModel): def __init__(self, batch_size=5, max_num_nodes=200): QtCore.QAbstractTableModel.__init__(self) self.batch_size = batch_size self.df = pd.DataFrame(np.random.randint(0,200,size=(200, 4))) self.nodes = self.df[:5] self.max_num_nodes = max(self.batch_size, max_num_nodes) def rowCount(self, index): return len(self.nodes) def columnCount(self, index): return 4 def data(self, index, role): if not index.isValid(): return None if role != QtCore.Qt.DisplayRole: return None if index.row() < 0 or index.row() >= len(self.nodes): return None else: return QtCore.QVariant(str(self.nodes.iloc[index.row(), index.column()])) def canFetchMore(self, index): if index.isValid(): return False return (len(self.nodes) < self.max_num_nodes) def fetchMore(self, index): print('fetch more') if index.isValid(): print('not valid') return QtCore.QVariant() current_len = len(self.nodes) target_len = min(current_len + self.batch_size, self.max_num_nodes) self.beginInsertRows(index, current_len, target_len - 1) for i in range(current_len, target_len): self.nodes = self.nodes.append(self.df.iloc[i]) self.endInsertRows() class MainForm(QtWidgets.QMainWindow): def __init__(self, parent=None): QtWidgets.QMainWindow.__init__(self, parent) self.model = DelayedFetchingTableModel(batch_size=self.batch_size, max_num_nodes=self.max_num_nodes) self.view = QtWidgets.QTableView() self.view.setModel(self.model) self.layout = QtWidgets.QVBoxLayout() self.layout.addWidget(self.view) self.window = QtWidgets.QWidget() self.window.setLayout(self.layout) self.setCentralWidget(self.window) def main(): app = QtWidgets.QApplication(sys.argv) form = MainForm() form.show() app.exec_() if __name__ == '__main__': main()
Cheers
-
I have tried this and can at least filter the table by the specific rows I want. How do I implement fetchMore function to insert rows when I scroll up and append rows when I scroll down. It is not obvious how fetchMore works and the conditions where the function is called. Who is calling this function?
import sys from PyQt5 import QtWidgets, QtCore import pandas as pd import numpy as np class TableModel(QtCore.QAbstractTableModel): def __init__(self, parent=None, visible_range = [2,5]): super().__init__(parent) self.df = pd.DataFrame(np.random.randint(0,1000000,size=(200, 4))) print(self.df) self.scope = self.df[visible_range[0]:visible_range[1] + 1] self.scope_range = visible_range #print(self.df) def rowCount(self, parent=QtCore.QModelIndex()): return len(self.scope) def columnCount(self, parent=QtCore.QModelIndex()): return 4 #return len(self.df.columns) def data(self, index, role=QtCore.Qt.DisplayRole): if not index.isValid(): return None if role != QtCore.Qt.DisplayRole: return None if index.row() < 0 or index.row() >= len(self.df): return None else: return QtCore.QVariant(str(self.scope.iloc[index.row(), index.column()])) def canFetchMore(self, index): if index.isValid(): return False return (len(self.scope) < len(self.df)) def fetchMore(self, index): print('fetchmore') pass """ if scroll up table: #prepend 20 rows self.scope_range = [max(self.scope_range[0] - 20, 0), self.scope_range[1]] self.scope = self.df[self.scope_range[0]:self.scope_range[1]+1] elif scroll down table: #append 20 rows self.scope_range = [self.scope_range[0], min(self.scope_range[1] + 20, len(self.df))] self.scope = self.df[self.scope_range[0]:self.scope_range[1]+1] """ class MainForm(QtWidgets.QMainWindow): def __init__(self, parent=None): QtWidgets.QMainWindow.__init__(self, parent) # Setup the model self.max_num_nodes = 200 self.batch_size = 5 self.model = TableModel() # Setup the view self.view = QtWidgets.QTableView() self.view.setModel(self.model) # Update the currently selected row in the spinbox # Collect all this stuff into a vertical layout self.layout = QtWidgets.QVBoxLayout() self.layout.addWidget(self.view) self.window = QtWidgets.QWidget() self.window.setLayout(self.layout) self.setCentralWidget(self.window) def main(): app = QtWidgets.QApplication(sys.argv) form = MainForm() form.show() app.exec_() if __name__ == '__main__': main()