18
18
from spyderlib.qt.QtGui import (QDialog, QTableView, QColor, QGridLayout,
19
19
QDialogButtonBox, QHBoxLayout, QPushButton,
20
20
QCheckBox, QMessageBox, QInputDialog,
21
QLineEdit, QApplication, QMenu)
21
QLineEdit, QApplication, QMenu, QKeySequence)
22
22
from spyderlib.qt.compat import to_qvariant, from_qvariant
23
23
from spyderlib.utils.qthelpers import (qapplication, get_icon, create_action,
24
24
add_actions, keybinding)
26
26
from spyderlib.baseconfig import _
27
from spyderlib.guiconfig import get_font
27
from spyderlib.guiconfig import get_font, new_shortcut
28
28
from spyderlib.py3compat import io, is_text_string, to_text_string
29
29
from spyderlib.utils import encoding
30
30
from spyderlib.widgets.arrayeditor import get_idx_rect
62
64
""" DataFrame Table Model"""
66
69
def __init__(self, dataFrame, format="%.3g", parent=None):
67
70
QAbstractTableModel.__init__(self)
68
71
self.dialog = parent
69
72
self.df = dataFrame
70
73
self.df_index = dataFrame.index.tolist()
74
self.df_header = dataFrame.columns.tolist()
71
75
self._format = format
72
self.bgcolor_enabled = True
73
76
self.complex_intran = None
78
self.total_rows = self.df.shape[0]
79
self.total_cols = self.df.shape[1]
80
size = self.total_rows * self.total_cols
75
82
huerange = [.66, .99] # Hue
76
83
self.sat = .7 # Saturation
79
86
self.hue0 = huerange[0]
80
87
self.dhue = huerange[1]-huerange[0]
81
88
self.max_min_col = None
82
self.max_min_col_update()
83
self.colum_avg_enabled = True
90
self.max_min_col_update()
91
self.colum_avg_enabled = True
92
self.bgcolor_enabled = True
95
self.colum_avg_enabled = False
96
self.bgcolor_enabled = False
86
# To define paging when the number of rows is large
87
self.total_rows = self.df.shape[0]
88
if self.total_rows > LARGE_NROWS:
99
# Use paging when the total size, number of rows or number of
100
# columns is too large
101
if size > LARGE_SIZE:
89
102
self.rows_loaded = self.ROWS_TO_LOAD
103
self.cols_loaded = self.COLS_TO_LOAD
91
self.rows_loaded = self.total_rows
105
if self.total_rows > LARGE_NROWS:
106
self.rows_loaded = self.ROWS_TO_LOAD
108
self.rows_loaded = self.total_rows
109
if self.total_cols > LARGE_COLS:
110
self.cols_loaded = self.COLS_TO_LOAD
112
self.cols_loaded = self.total_cols
93
114
def max_min_col_update(self):
94
115
"""Determines the maximum and minimum number in each column"""
101
122
self.complex_intran = self.df.applymap(lambda e:
102
123
isinstance(e, _sup_com))
103
124
mask = float_intran & (~ self.complex_intran)
104
df_abs = self.df[self.complex_intran].abs()
126
df_abs = self.df[self.complex_intran].abs()
128
df_abs = self.df[self.complex_intran]
105
129
max_c = df_abs.max(skipna=True)
106
130
min_c = df_abs.min(skipna=True)
107
131
df_real = self.df[mask]
293
316
return self.total_rows
295
318
return self.rows_loaded
297
def canFetchMore(self, index=QModelIndex()):
298
if self.total_rows > self.rows_loaded:
303
def fetchMore(self, index=QModelIndex()):
304
reminder = self.total_rows - self.rows_loaded
305
items_to_fetch = min(reminder, self.ROWS_TO_LOAD)
306
self.beginInsertRows(QModelIndex(), self.rows_loaded,
307
self.rows_loaded + items_to_fetch - 1)
308
self.rows_loaded += items_to_fetch
320
def can_fetch_more(self, rows=False, columns=False):
322
if self.total_rows > self.rows_loaded:
327
if self.total_cols > self.cols_loaded:
332
def fetch_more(self, rows=False, columns=False):
333
if self.can_fetch_more(rows=rows):
334
reminder = self.total_rows - self.rows_loaded
335
items_to_fetch = min(reminder, self.ROWS_TO_LOAD)
336
self.beginInsertRows(QModelIndex(), self.rows_loaded,
337
self.rows_loaded + items_to_fetch - 1)
338
self.rows_loaded += items_to_fetch
340
if self.can_fetch_more(columns=columns):
341
reminder = self.total_cols - self.cols_loaded
342
items_to_fetch = min(reminder, self.COLS_TO_LOAD)
343
self.beginInsertColumns(QModelIndex(), self.cols_loaded,
344
self.cols_loaded + items_to_fetch - 1)
345
self.cols_loaded += items_to_fetch
346
self.endInsertColumns()
311
348
def columnCount(self, index=QModelIndex()):
312
349
"""DataFrame column number"""
313
shape = self.df.shape
314
350
# This is done to implement timeseries
351
if len(self.df.shape) == 1:
353
elif self.total_cols <= self.cols_loaded:
354
return self.total_cols + 1
356
return self.cols_loaded + 1
321
359
class DataFrameView(QTableView):
329
367
self.connect(self.header_class,
330
368
SIGNAL("sectionClicked(int)"), self.sortByColumn)
331
369
self.menu = self.setup_menu()
370
new_shortcut(QKeySequence.Copy, self, self.copy)
371
self.connect(self.horizontalScrollBar(), SIGNAL("valueChanged(int)"),
372
lambda val: self.load_more_data(val, columns=True))
373
self.connect(self.verticalScrollBar(), SIGNAL("valueChanged(int)"),
374
lambda val: self.load_more_data(val, rows=True))
376
def load_more_data(self, value, rows=False, columns=False):
377
if rows and value == self.verticalScrollBar().maximum():
378
self.model().fetch_more(rows=rows)
379
if columns and value == self.horizontalScrollBar().maximum():
380
self.model().fetch_more(columns=columns)
333
382
def sortByColumn(self, index):
334
383
""" Implement a Column sort """
375
424
index_list = self.selectedIndexes()
376
425
[model.setData(i, '', change_type=func) for i in index_list]
378
def copy(self, index=False, header=False):
379
428
"""Copy text to clipboard"""
380
429
(row_min, row_max,
381
430
col_min, col_max) = get_idx_rect(self.selectedIndexes())
431
index = header = False
387
437
contents = '\n'.join(map(str, df.index.tolist()[slice(row_min,
389
439
else: # To copy DataFrame
390
if df.shape[0] == row_max+1 and row_min == 0:
440
if (col_min == 0 or col_min == 1) and (df.shape[1] == col_max):
392
442
obj = df.iloc[slice(row_min, row_max+1), slice(col_min-1, col_max)]
393
443
output = io.StringIO()