Skip to content

Commit 7718627

Browse files
committed
time formatting
1 parent aa16e16 commit 7718627

File tree

6 files changed

+79
-38
lines changed

6 files changed

+79
-38
lines changed

CHANGES

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
* progress bar for saving
77
* filtering tool
88
* snap works
9-
* fixes to import dialog
9+
* fixes to import dialog
10+
* can now set datetime display format
11+
* show memory usage in status bar
1012

1113
-----
1214
0.2.0

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
author_email = 'farrell.damien@gmail.com',
1717
packages = ['tablexplore'],
1818
package_data={'tablexplore': ['logo.png', '../description.txt',
19-
'styles/*.css','icons/*.png',
19+
'styles/*.qss','icons/*.png',
2020
'datasets/*.csv']},
2121
install_requires=['matplotlib>=3.0',
2222
'pandas>=1.1',

snapcraft.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
name: tablexplore
1+
name: Tablexplore
22
version: '0.3.0'
33
#version-script: git describe --abbrev=1 --tags
44
summary: data plotting and analysis package
55
description: |
6-
tablexplore is an open source desktop application for data analysis and plotting
6+
Tablexplore is an open source desktop application for data analysis and plotting
77
intended for use in both research and education. The program allows quick
88
visualization of data, table manipulation tools and supports large data tables.
99
1010
base: core18
1111
grade: stable
1212
confinement: strict
13-
icon: snap/gui/icon.png
13+
icon: img/logo.svg
1414

1515
apps:
1616
tablexplore:

tablexplore/app.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ def loadSettings(self):
117117
core.FONT = s.value("font")
118118
core.FONTSIZE = int(s.value("fontsize"))
119119
core.COLUMNWIDTH = int(s.value("columnwidth"))
120+
core.TIMEFORMAT = s.value("timeformat")
120121
r = s.value("recent_files")
121122
if r != '':
122123
self.recent_files = r.split(',')
@@ -125,7 +126,6 @@ def loadSettings(self):
125126
self.recent_urls = r.split('^^')
126127
except:
127128
pass
128-
129129
return
130130

131131
def saveSettings(self):
@@ -137,6 +137,7 @@ def saveSettings(self):
137137
self.settings.setValue('columnwidth', core.COLUMNWIDTH)
138138
self.settings.setValue('font', core.FONT)
139139
self.settings.setValue('fontsize', core.FONTSIZE)
140+
self.settings.setValue('timeformat', core.TIMEFORMAT)
140141
self.settings.setValue('recent_files',','.join(self.recent_files))
141142
self.settings.setValue('recent_urls','^^'.join(self.recent_urls))
142143
if hasattr(self, 'plotgallery'):
@@ -166,7 +167,7 @@ def createToolBar(self):
166167
'zoom in': {'action':self.zoomIn,'file':'zoom-in'},
167168
'decrease columns': {'action': lambda: self.changeColumnWidths(.9),'file':'decrease-width'},
168169
'increase columns': {'action': lambda: self.changeColumnWidths(1.1),'file':'increase-width'},
169-
'add sheet': {'action':self.addSheet,'file':'add'},
170+
'add sheet': {'action': lambda: self.addSheet(name=None),'file':'add'},
170171
#'lock': {'action':self.lockTable,'file':'lock'},
171172
'clean data': {'action':lambda: self._call('cleanData'),'file':'clean'},
172173
'table to text': {'action':lambda: self._call('showAsText'),'file':'tabletotext'},
@@ -372,7 +373,7 @@ def openProject(self, filename=None, asksave=False):
372373
if filename == None:
373374
options = QFileDialog.Options()
374375
filename, _ = QFileDialog.getOpenFileName(self,"Open Project",
375-
"","tablexplore Files (*.txpl);;All files (*.*)",
376+
homepath,"tablexplore Files (*.txpl);;All files (*.*)",
376377
options=options)
377378

378379
if not filename:
@@ -405,14 +406,15 @@ def saveAsProject(self):
405406

406407
options = QFileDialog.Options()
407408
filename, _ = QFileDialog.getSaveFileName(self,"Save Project",
408-
"","tablexplore Files (*.txpl);;All files (*.*)",
409+
homepath,"tablexplore Files (*.txpl);;All files (*.*)",
409410
options=options)
410411
if not filename:
411412
return
412413

413414
self.filename = filename
414415
self.do_saveProject(filename)
415416
self.addRecentFile(filename)
417+
self.proj_label.setText(self.filename)
416418
return
417419

418420
def saveProject(self, filename=None):
@@ -614,16 +616,20 @@ def exportAs(self):
614616
def addSheet(self, name=None, df=None, meta=None):
615617
"""Add a new sheet"""
616618

617-
names = self.sheets.keys()
618-
if name is None or name in self.sheets:
619-
name = 'dataset'+str(len(self.sheets)+1)
619+
names = list(self.sheets.keys())
620+
i=len(self.sheets)+1
621+
if name == None or name in names:
622+
name = 'dataset'+str(i)
623+
if name in names:
624+
import random
625+
name = 'dataset'+str(random.randint(i,100))
620626

621627
sheet = QSplitter(self.main)
622628
idx = self.main.addTab(sheet, name)
623629
#provide reference to self to dataframewidget
624630
dfw = DataFrameWidget(sheet, dataframe=df, app=self,
625631
font=core.FONT, fontsize=core.FONTSIZE,
626-
columnwidth=core.COLUMNWIDTH)
632+
columnwidth=core.COLUMNWIDTH, timeformat=core.TIMEFORMAT)
627633
sheet.addWidget(dfw)
628634

629635
self.sheets[name] = dfw
@@ -658,6 +664,10 @@ def renameSheet(self):
658664
new, ok = QInputDialog.getText(self, 'New name', 'Name:',
659665
QLineEdit.Normal, name)
660666
if ok:
667+
if new in self.sheets:
668+
QMessageBox.information(self, "Cannot rename",
669+
"Sheet name already present")
670+
return
661671
self.sheets[new] = self.sheets[name]
662672
del self.sheets[name]
663673
self.main.setTabText(index, new)
@@ -843,7 +853,7 @@ def preferences(self):
843853

844854
from . import dialogs
845855
opts = {'font':core.FONT, 'fontsize':core.FONTSIZE,
846-
'columnwidth':core.COLUMNWIDTH}
856+
'columnwidth':core.COLUMNWIDTH, 'timeformat':core.TIMEFORMAT}
847857
dlg = dialogs.PreferencesDialog(self, opts)
848858
dlg.exec_()
849859
return

tablexplore/core.py

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import tempfile
2525
import numpy as np
2626
import pandas as pd
27+
from pandas.api.types import is_datetime64_any_dtype as is_datetime
2728
import string
2829
from PySide2 import QtCore, QtGui
2930
from PySide2.QtCore import QObject, Signal, Slot
@@ -38,6 +39,7 @@
3839
FONTSIZE = 12
3940
FONTSTYLE = ''
4041
COLUMNWIDTH = 80
42+
TIMEFORMAT = '%m/%d/%Y'
4143

4244
try:
4345
_fromUtf8 = QtCore.QString.fromUtf8
@@ -59,6 +61,13 @@ def _fromUtf8(s):
5961
'subtable':'subtable','clear':'clear'
6062
}
6163

64+
timeformats = ['infer','%d/%m/%Y','%d/%m/%y',
65+
'%Y/%m/%d','%y/%m/%d','%Y/%d/%m',
66+
'%d%m%Y','%Y%m%d','%Y%d%m',
67+
'%d-%b-%Y',
68+
'%Y-%m-%d %H:%M:%S','%Y-%m-%d %H:%M',
69+
'%d-%m-%Y %H:%M:%S','%d-%m-%Y %H:%M']
70+
6271
class ColumnHeader(QHeaderView):
6372
def __init__(self):
6473
super(QHeaderView, self).__init__()
@@ -323,21 +332,32 @@ def findDuplicates(self):
323332
'tooltip':'Remove duplicates'},
324333
'useselected': {'type':'checkbox','default':0,'label':'Use selected columns' },
325334
'keep':{'label':'Keep','type':'combobox','default':'first',
326-
'items':['first','last'], 'tooltip':'values to keep'}
335+
'items':['first','last'], 'tooltip':'values to keep'},
336+
'inplace':{'type':'checkbox','default':0,'label':'In place' },
327337
}
328338
dlg = dialogs.MultipleInputDialog(self, opts, title='Clean Data')
329339
dlg.exec_()
330340
if not dlg.accepted:
331341
return
332342
kwds = dlg.values
333-
cols = df.columns
334343
keep = kwds['keep']
335344
remove = kwds['remove']
345+
inplace = kwds['inplace']
346+
if kwds['useselected'] == 1:
347+
idx = self.table.getSelectedColumns()
348+
cols = df.columns[idx]
349+
else:
350+
cols = df.columns
351+
336352
new = df[df.duplicated(subset=cols,keep=keep)]
337353
if remove == True:
338-
self.table.model.df = df.drop_duplicates(subset=cols,keep=keep)
339-
self.refresh()
340-
if len(new)>0:
354+
new = df.drop_duplicates(subset=cols,keep=keep)
355+
if inplace == True:
356+
self.table.model.df = new
357+
self.refresh()
358+
elif len(new)>0:
359+
self.showSubTable(new)
360+
else:
341361
self.showSubTable(new)
342362
return
343363

@@ -723,13 +743,11 @@ def convertDates(self, column):
723743
colname = '-'.join(cols)
724744
temp = df[cols]'''
725745

726-
timeformats = ['infer','%d/%m/%Y','%Y/%m/%d','%Y/%d/%m',
727-
'%Y-%m-%d %H:%M:%S','%Y-%m-%d %H:%M',
728-
'%d-%m-%Y %H:%M:%S','%d-%m-%Y %H:%M']
729746
props = ['','day','dayofweek','month','hour','minute','second','microsecond','year',
730747
'dayofyear','weekofyear','quarter','days_in_month','is_leap_year']
731-
opts = {'format': {'type':'combobox','default':'int',
748+
opts = {'format': {'type':'combobox','default':'int','editable':True,
732749
'items':timeformats,'label':'Conversion format'},
750+
'errors':{'type':'combobox','items':['ignore','coerce'],'default':'ignore','label':'Errors'},
733751
'prop': {'type':'combobox','default':'int',
734752
'items':props,'label':'Extract from datetime'} }
735753

@@ -741,12 +759,16 @@ def convertDates(self, column):
741759

742760
format = kwds['format']
743761
prop = kwds['prop']
762+
errors = kwds['errors']
763+
infer=False
744764
if format == 'infer':
745765
format = None
746-
766+
infer = True
747767
temp = df[column]
768+
self.table.storeCurrent()
748769
if temp.dtype != 'datetime64[ns]':
749-
temp = pd.to_datetime(temp, format=format)
770+
temp = pd.to_datetime(temp, format=format, infer_datetime_format=infer,
771+
errors=errors)
750772

751773
if prop != '':
752774
new = getattr(temp.dt, prop)
@@ -1015,13 +1037,14 @@ class DataFrameTable(QTableView):
10151037
QTableView with pandas DataFrame as model.
10161038
"""
10171039
def __init__(self, parent=None, dataframe=None, font='Arial',
1018-
fontsize=12, columnwidth=80, align=None, **kwargs):
1040+
fontsize=12, columnwidth=80, timeformat='%m-%d-%Y', **kwargs):
10191041

10201042
QTableView.__init__(self)
10211043
self.parent = parent
10221044
self.font = font
10231045
self.fontsize = fontsize
1024-
self.columnwidth=columnwidth
1046+
self.columnwidth = columnwidth
1047+
self.timeformat = timeformat
10251048
self.clicked.connect(self.showSelection)
10261049
#self.doubleClicked.connect(self.handleDoubleClick)
10271050
#self.setSelectionBehavior(QTableView.SelectRows)
@@ -1126,7 +1149,7 @@ def getMemory(self):
11261149

11271150
m = self.model.df.memory_usage(deep=True).sum()
11281151
if m>1e5:
1129-
m = int(m/1048576)
1152+
m = round(m/1048576,2)
11301153
units='MB'
11311154
else:
11321155
units='Bytes'
@@ -1507,9 +1530,12 @@ def data(self, index, role=QtCore.Qt.DisplayRole):
15071530
i = index.row()
15081531
j = index.column()
15091532
coltype = self.df.dtypes[j]
1533+
isdate = is_datetime(coltype)
15101534
if role == QtCore.Qt.DisplayRole:
15111535
value = self.df.iloc[i, j]
1512-
if type(value) != str:
1536+
if isdate:
1537+
return value.strftime(TIMEFORMAT)
1538+
elif type(value) != str:
15131539
if type(value) in [float,np.float64] and np.isnan(value):
15141540
return ''
15151541
elif type(value) == np.float:

tablexplore/dialogs.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -914,16 +914,24 @@ def createWidgets(self, options):
914914

915915
import pylab as plt
916916
colormaps = sorted(m for m in plt.cm.datad if not m.endswith("_r"))
917+
timeformats = ['%m/%d/%Y','%d/%m/%Y','%d/%m/%y',
918+
'%Y/%m/%d','%y/%m/%d','%Y/%d/%m',
919+
'%d-%b-%Y','%b-%d-%Y',
920+
'%Y-%m-%d %H:%M:%S','%Y-%m-%d %H:%M',
921+
'%d-%m-%Y %H:%M:%S','%d-%m-%Y %H:%M']
917922
self.opts = {'rowheight':{'type':'spinbox','default':18,'range':(5,50),'label':'row height'},
918923
'columnwidth':{'type':'spinbox','range':(10,300),
919924
'default': options['columnwidth'], 'label':'column width'},
920925
'alignment':{'type':'combobox','default':'w','items':['left','right','center'],'label':'text align'},
921926
'font':{'type':'font','default':'Arial','default':options['font']},
922-
'fontsize':{'type':'slider','default':options['fontsize'],'range':(5,40),'interval':1,'label':'font size'},
923-
'floatprecision':{'type':'spinbox','default':2, 'label':'precision'},
927+
'fontsize':{'type':'slider','default':options['fontsize'],'range':(5,40),
928+
'interval':1,'label':'font size'},
929+
'timeformat':{'type':'combobox','default':options['timeformat'],
930+
'items':timeformats,'label':'Date/Time format'}
931+
#'floatprecision':{'type':'spinbox','default':2, 'label':'precision'},
924932
}
925933
sections = {'table':['alignment','rowheight','columnwidth'],
926-
'formats':['font','fontsize','floatprecision']}
934+
'formats':['font','fontsize','timeformat']}
927935

928936
dialog, self.widgets = dialogFromOptions(self, self.opts, sections)
929937

@@ -954,15 +962,10 @@ def apply(self):
954962
core.FONT = kwds['font']
955963
core.FONTSIZE = kwds['fontsize']
956964
core.COLUMNWIDTH = kwds['columnwidth']
965+
core.TIMEFORMAT = kwds['timeformat']
957966
self.parent.refresh()
958967
return
959968

960-
def save(self):
961-
"""Save from current dialog settings"""
962-
963-
cp.write(open(default_conf,'w'))
964-
return
965-
966969
class FilterDialog(QWidget):
967970
"""Qdialog for table query/filtering"""
968971
def __init__(self, parent, table, title=None):

0 commit comments

Comments
 (0)