Source code for snipgenie.widgets
# -*- coding: utf-8 -*-
"""
Qt widgets for snpgenie.
Created Jan 2020
Copyright (C) Damien Farrell
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
import sys, os, io
import numpy as np
import pandas as pd
import string
from .qt import *
from . import tables
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
from . import core, tools, plotting
module_path = os.path.dirname(os.path.abspath(__file__))
iconpath = os.path.join(module_path, 'icons')
[docs]def dialogFromOptions(parent, opts, sections=None,
wrap=2, section_wrap=4,
style=None):
"""
Get Qt widgets dialog from a dictionary of options.
Args:
opts: options dictionary
sections:
section_wrap: how many sections in one row
style: stylesheet css if required
"""
sizepolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
sizepolicy.setHorizontalStretch(0)
sizepolicy.setVerticalStretch(0)
if style == None:
style = '''
QLabel {
font-size: 14px;
}
QPlainTextEdit {
max-height: 80px;
}
QComboBox {
combobox-popup: 0;
max-height: 30px;
max-width: 150px;
}
'''
if sections == None:
sections = {'options': opts.keys()}
widgets = {}
dialog = QWidget(parent)
dialog.setSizePolicy(sizepolicy)
l = QGridLayout(dialog)
l.setSpacing(1)
l.setAlignment(QtCore.Qt.AlignTop)
scol=1
srow=1
for s in sections:
row=srow
col=1
f = QWidget()
f.resize(50,100)
f.sizeHint()
l.addWidget(f,row,scol)
gl = QGridLayout(f)
gl.setAlignment(QtCore.Qt.AlignTop)
gl.setSpacing(5)
for o in sections[s]:
label = o
val = None
opt = opts[o]
if 'label' in opt:
label = opt['label']
val = opt['default']
t = opt['type']
lbl = QLabel(label)
lbl.setMinimumWidth(150)
gl.addWidget(lbl,row,col)
lbl.setStyleSheet(style)
if t == 'combobox':
w = QComboBox()
w.addItems(opt['items'])
index = w.findText(val)
if index != -1:
w.setCurrentIndex(index)
if 'editable' in opt:
w.setEditable(True)
if 'width' in opt:
w.setMinimumWidth(opt['width'])
w.resize(opt['width'], 20)
w.view().setMinimumWidth(120)
w.setMaxVisibleItems(12)
elif t == 'list':
w = QListWidget()
w.setSelectionMode(QAbstractItemView.MultiSelection)
w.addItems(opt['items'])
elif t == 'entry':
w = QLineEdit()
w.setText(str(val))
if 'width' in opt:
w.setMaximumWidth(opt['width'])
w.resize(opt['width'], 20)
elif t == 'textarea':
w = QPlainTextEdit()
#w.setSizePolicy(sizepolicy)
w.insertPlainText(str(val))
w.setMaximumHeight(100)
elif t == 'slider':
w = QSlider(QtCore.Qt.Horizontal)
s,e = opt['range']
w.setTickInterval(opt['interval'])
w.setSingleStep(opt['interval'])
w.setMinimum(s)
w.setMaximum(e)
w.setTickPosition(QSlider.TicksBelow)
w.setValue(val)
elif t == 'spinbox':
w = QSpinBox()
w.setValue(val)
if 'range' in opt:
min, max = opt['range']
min = int(min)
max = int(max)
w.setRange(min,max)
w.setMaximum(max)
w.setMinimum(min)
if 'interval' in opt:
w.setSingleStep(opt['interval'])
elif t == 'doublespinbox':
w = QDoubleSpinBox()
w.setValue(val)
if 'range' in opt:
min, max = opt['range']
w.setRange(min,max)
w.setMinimum(min)
if 'interval' in opt:
w.setSingleStep(opt['interval'])
elif t == 'checkbox':
w = QCheckBox()
w.setChecked(val)
elif t == 'font':
w = QFontComboBox()
index = w.findText(val)
#w.resize(w.sizeHint())
w.setCurrentIndex(index)
elif t == 'dial':
w = QDial()
if 'range' in opt:
min, max = opt['range']
w.setMinimum(min)
w.setMaximum(max)
w.setValue(val)
elif t == 'colorbutton':
w = ColorButton()
w.setColor(val)
col+=1
gl.addWidget(w,row,col)
w.setStyleSheet(style)
widgets[o] = w
#print (o, row, col)
if col>=wrap:
col=1
row+=1
else:
col+=2
if scol >= section_wrap:
scol=1
srow+=2
else:
scol+=1
return dialog, widgets
[docs]def getWidgetValues(widgets):
"""Get values back from a set of widgets"""
kwds = {}
for i in widgets:
val = None
if i in widgets:
w = widgets[i]
if type(w) is QLineEdit:
try:
val = float(w.text())
except:
val = w.text()
elif type(w) is QPlainTextEdit:
val = w.toPlainText()
elif type(w) is QComboBox or type(w) is QFontComboBox:
val = w.currentText()
elif type(w) is QCheckBox:
val = w.isChecked()
elif type(w) is QSlider:
val = w.value()
elif type(w) in [QSpinBox,QDoubleSpinBox]:
val = w.value()
if val != None:
kwds[i] = val
kwds = kwds
return kwds
[docs]def setWidgetValues(widgets, values):
"""Set values for a set of widgets from a dict"""
kwds = {}
for i in values:
val = values[i]
if i in widgets:
#print (i, val, type(val))
w = widgets[i]
if type(w) is QLineEdit:
w.setText(str(val))
elif type(w) is QPlainTextEdit:
w.insertPlainText(str(val))
elif type(w) is QComboBox or type(w) is QFontComboBox:
index = w.findText(val)
w.setCurrentIndex(index)
elif type(w) is QCheckBox:
w.setChecked(val)
elif type(w) is QSlider:
w.setValue(val)
elif type(w) in [QSpinBox,QDoubleSpinBox]:
w.setValue(val)
return
[docs]def addToolBarItems(toolbar, parent, items):
"""Populate toolbar from dict of items"""
for i in items:
if 'file' in items[i]:
iconfile = os.path.join(iconpath,items[i]['file'])
icon = QIcon(iconfile)
else:
icon = QIcon.fromTheme(items[i]['icon'])
btn = QAction(icon, i, parent)
btn.triggered.connect(items[i]['action'])
if 'shortcut' in items[i]:
btn.setShortcut(QKeySequence(items[i]['shortcut']))
#btn.setCheckable(True)
toolbar.addAction(btn)
return toolbar
[docs]class MultipleInputDialog(QDialog):
"""Qdialog with multiple inputs"""
def __init__(self, parent, options=None, title='Input', width=400, height=200):
super(MultipleInputDialog, self).__init__(parent)
self.values = None
self.accepted = False
self.setMinimumSize(width, height)
self.setWindowTitle(title)
dialog, self.widgets = dialogFromOptions(self, options)
vbox = QVBoxLayout(self)
vbox.addWidget(dialog)
buttonbox = QDialogButtonBox(self)
buttonbox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
buttonbox.button(QDialogButtonBox.Ok).clicked.connect(self.accept)
buttonbox.button(QDialogButtonBox.Cancel).clicked.connect(self.close)
vbox.addWidget(buttonbox)
self.show()
return self.values
[docs] def accept(self):
self.values = getWidgetValues(self.widgets)
self.accepted = True
self.close()
return
[docs]class ColorButton(QPushButton):
'''
Custom Qt Widget to show a chosen color.
Left-clicking the button shows the color-chooser, while
right-clicking resets the color to None (no-color).
'''
colorChanged = Signal(object)
def __init__(self, *args, color=None, **kwargs):
super(ColorButton, self).__init__(*args, **kwargs)
self._color = None
self._default = color
self.pressed.connect(self.onColorPicker)
# Set the initial/default state.
self.setColor(self._default)
[docs] def setColor(self, color):
if color != self._color:
self._color = color
self.colorChanged.emit(color)
if self._color:
self.setStyleSheet("background-color: %s;" % self._color)
else:
self.setStyleSheet("")
return
[docs] def onColorPicker(self):
'''
Show color-picker dialog to select color.
Qt will use the native dialog by default.
'''
dlg = QColorDialog(self)
if self._color:
dlg.setCurrentColor(QColor(self._color))
if dlg.exec_():
self.setColor(dlg.currentColor().name())
[docs] def mousePressEvent(self, e):
if e.button() == QtCore.Qt.RightButton:
self.setColor(self._default)
return super(ColorButton, self).mousePressEvent(e)
[docs]class ToolBar(QWidget):
"""Toolbar class"""
def __init__(self, table, parent=None):
super(ToolBar, self).__init__(parent)
self.parent = parent
self.table = table
self.layout = QVBoxLayout()
self.layout.setAlignment(QtCore.Qt.AlignTop)
self.layout.setContentsMargins(2,2,2,2)
self.setLayout(self.layout)
self.createButtons()
self.setMaximumWidth(40)
return
[docs] def createButtons(self):
funcs = {'load':self.table.load, 'save':self.table.save,
'importexcel': self.table.load,
'copy':self.table.copy, 'paste':self.table.paste,
'plot':self.table.plot,
'transpose':self.table.pivot,
'pivot':self.table.pivot}
icons = {'load': 'document-new', 'save': 'document-save-as',
'importexcel': 'x-office-spreadsheet',
'copy': 'edit-copy', 'paste': 'edit-paste',
'plot':'insert-image',
'transpose':'object-rotate-right',
'pivot': 'edit-undo',
}
for name in funcs:
self.addButton(name, funcs[name], icons[name])
[docs] def addButton(self, name, function, icon):
layout=self.layout
button = QPushButton(name)
button.setGeometry(QtCore.QRect(30,40,30,40))
button.setText('')
iconw = QIcon.fromTheme(icon)
button.setIcon(QIcon(iconw))
button.setIconSize(QtCore.QSize(20,20))
button.clicked.connect(function)
button.setMinimumWidth(30)
layout.addWidget(button)
[docs]class BasicDialog(QDialog):
"""Qdialog for table operations interfaces"""
def __init__(self, parent, table, title=None):
super(BasicDialog, self).__init__(parent)
self.parent = parent
self.table = table
self.df = table.model.df
#self.app = self.parent.app
self.setWindowTitle(title)
self.createWidgets()
self.setGeometry(QtCore.QRect(400, 300, 1000, 600))
self.show()
return
[docs] def createButtons(self, parent):
bw = self.button_widget = QWidget(parent)
vbox = QVBoxLayout(bw)
vbox.setAlignment(QtCore.Qt.AlignTop)
button = QPushButton("Apply")
button.clicked.connect(self.apply)
vbox.addWidget(button)
button = QPushButton("Update")
button.clicked.connect(self.update)
vbox.addWidget(button)
button = QPushButton("Copy to clipboard")
button.clicked.connect(self.copy_to_clipboard)
vbox.addWidget(button)
button = QPushButton("Close")
button.clicked.connect(self.close)
vbox.addWidget(button)
return bw
[docs] def update(self):
"""Update the original table"""
self.table.model.df = self.result.model.df
self.table.refresh()
self.close()
return
[docs] def copy_to_clipboard(self):
"""Copy result to clipboard"""
df = self.result.model.df
df.to_clipboard()
return
[docs]class MergeDialog(BasicDialog):
"""Dialog to melt table"""
def __init__(self, parent, table, df2, title='Merge Tables'):
self.table = table
self.df = table.model.df
self.df2 = df2
BasicDialog.__init__(self, parent, table, title)
return
[docs] def createWidgets(self):
"""Create widgets"""
cols = self.df.columns
cols2 = self.df2.columns
ops = ['merge','concat']
how = ['inner','outer','left','right']
hbox = QHBoxLayout(self)
main = QWidget(self)
main.setMaximumWidth(300)
hbox.addWidget(main)
l = QVBoxLayout(main)
w = self.ops_w = QComboBox(main)
w.addItems(ops)
l.addWidget(QLabel('Operation'))
l.addWidget(w)
w = self.lefton_w = QListWidget(main)
w.setSelectionMode(QAbstractItemView.MultiSelection)
w.addItems(cols)
l.addWidget(QLabel('Left on'))
l.addWidget(w)
w = self.righton_w = QListWidget(main)
w.setSelectionMode(QAbstractItemView.MultiSelection)
w.addItems(cols2)
l.addWidget(QLabel('Right on'))
l.addWidget(w)
w = self.leftindex_w = QCheckBox(main)
w.setChecked(False)
l.addWidget(QLabel('Use left index'))
l.addWidget(w)
w = self.rightindex_w = QCheckBox(main)
w.setChecked(False)
l.addWidget(QLabel('Use right index'))
l.addWidget(w)
w = self.how_w = QComboBox(main)
w.addItems(how)
l.addWidget(QLabel('How'))
l.addWidget(w)
w = self.left_suffw = QLineEdit('_1')
l.addWidget(QLabel('Left suffix'))
l.addWidget(w)
w = self.right_suffw = QLineEdit('_2')
l.addWidget(QLabel('Right suffix'))
l.addWidget(w)
self.result = tables.DataFrameTable(self)
hbox.addWidget(self.result)
bf = self.createButtons(self)
hbox.addWidget(bf)
return
[docs] def apply(self):
"""Do the operation"""
left_index = self.leftindex_w.isChecked()
right_index = self.rightindex_w.isChecked()
if left_index == True:
lefton = None
else:
lefton = [i.text() for i in self.lefton_w.selectedItems()]
if right_index == True:
righton = None
else:
righton = [i.text() for i in self.righton_w.selectedItems()]
how = self.how_w.currentText()
op = self.ops_w.currentText()
if op == 'merge':
res = pd.merge(self.df, self.df2,
left_on=lefton,
right_on=righton,
left_index=left_index,
right_index=right_index,
how=how,
suffixes=(self.left_suffw .text(),self.right_suffw.text())
)
else:
res = pd.concat([self.df, self.df2])
self.result.model.df = res
self.result.refresh()
return
[docs]class PreferencesDialog(QDialog):
"""Preferences dialog from config parser options"""
def __init__(self, parent, options={}):
super(PreferencesDialog, self).__init__(parent)
self.parent = parent
self.setWindowTitle('Preferences')
self.resize(400, 200)
self.setGeometry(QtCore.QRect(300,300, 600, 200))
self.setMaximumWidth(600)
self.setMaximumHeight(300)
self.createWidgets(options)
self.show()
return
[docs] def createWidgets(self, options):
"""create widgets"""
import pylab as plt
colormaps = sorted(m for m in plt.cm.datad if not m.endswith("_r"))
timeformats = ['%m/%d/%Y','%d/%m/%Y','%d/%m/%y',
'%Y/%m/%d','%y/%m/%d','%Y/%d/%m',
'%d-%b-%Y','%b-%d-%Y',
'%Y-%m-%d %H:%M:%S','%Y-%m-%d %H:%M',
'%d-%m-%Y %H:%M:%S','%d-%m-%Y %H:%M',
'%Y','%m','%d','%b']
plotstyles = ['','default', 'classic', 'fivethirtyeight',
'seaborn-pastel','seaborn-whitegrid', 'ggplot','bmh',
'grayscale','dark_background']
themes = QStyleFactory.keys() + ['dark','light']
self.opts = {
'FONT':{'type':'font','default':options['FONT'],'label':'Font'},
'FONTSIZE':{'type':'spinbox','default':options['FONTSIZE'],'range':(5,40),
'interval':1,'label':'Font Size'},
'BGCOLOR':{'type':'colorbutton','default':options['BGCOLOR'],'label':'Background Color'},
'TIMEFORMAT':{'type':'combobox','default':options['TIMEFORMAT'],
'items':timeformats,'label':'Date/Time format'},
'PLOTSTYLE':{'type':'combobox','default':options['PLOTSTYLE'],
'items':plotstyles,'label':'Plot Style'},
'DPI':{'type':'entry','default':options['DPI'],#'range':(20,300),'interval':10,
'label':'Plot DPI'},
'ICONSIZE':{'type':'spinbox','default':options['ICONSIZE'],'range':(16,64), 'label':'Icon Size'},
'THEME':{'type':'combobox','default':options['THEME'],'items': themes,
'label': 'Default Theme'}
}
sections = {'table':['FONT','FONTSIZE','TIMEFORMAT','BGCOLOR'],
'view':['ICONSIZE','PLOTSTYLE','DPI','THEME']
}
dialog, self.widgets = dialogFromOptions(self, self.opts, sections)
self.layout = QVBoxLayout(self)
self.layout.addWidget(dialog)
dialog.setFocus()
bw = self.createButtons(self)
self.layout.addWidget(bw)
return
[docs] def createButtons(self, parent):
bw = self.button_widget = QWidget(parent)
vbox = QHBoxLayout(bw)
button = QPushButton("Apply")
button.clicked.connect(self.apply)
vbox.addWidget(button)
button = QPushButton("Reset")
button.clicked.connect(self.reset)
vbox.addWidget(button)
button = QPushButton("Close")
button.clicked.connect(self.close)
vbox.addWidget(button)
return bw
[docs] def apply(self):
"""Apply options to current table"""
kwds = getWidgetValues(self.widgets)
core.FONT = kwds['FONT']
core.FONTSIZE = kwds['FONTSIZE']
core.BGCOLOR = kwds['BGCOLOR']
core.TIMEFORMAT = kwds['TIMEFORMAT']
core.PRECISION = kwds['PRECISION']
core.SHOWPLOTTER = kwds['SHOWPLOTTER']
core.PLOTSTYLE = kwds['PLOTSTYLE']
core.DPI = kwds['DPI']
core.ICONSIZE = kwds['ICONSIZE']
self.parent.theme = kwds['THEME']
self.parent.refresh()
self.parent.applySettings()
return
[docs] def updateWidgets(self, kwds=None):
"""Update widgets from stored or supplied kwds"""
if kwds == None:
kwds = self.kwds
for k in kwds:
setWidgetValues(self.widgets, {k: kwds[k]})
return
[docs] def setDefaults(self):
"""Populate default kwds dict"""
self.kwds = {}
for o in self.opts:
self.kwds[o] = core.defaults[o]
return
[docs] def reset(self):
"""Reset to defaults"""
self.setDefaults()
self.updateWidgets()
self.apply()
return
[docs]class BaseOptions(object):
"""Class to generate widget dialog for dict of options"""
def __init__(self, parent=None, opts={}, groups={}):
"""Setup variables"""
self.parent = parent
self.groups = groups
self.opts = opts
return
[docs] def applyOptions(self):
"""Set the plot kwd arguments from the widgets"""
self.kwds = getWidgetValues(self.widgets)
return
[docs] def showDialog(self, parent, wrap=2, section_wrap=2, style=None):
"""Auto create tk vars, widgets for corresponding options and
and return the frame"""
dialog, self.widgets = dialogFromOptions(parent, self.opts, self.groups,
wrap=wrap, section_wrap=section_wrap, style=style)
return dialog
[docs] def setWidgetValue(self, key, value):
"Set a widget value"
setWidgetValues(self.widgets, {key: value})
self.applyOptions()
return
[docs] def updateWidgets(self, kwds):
for k in kwds:
setWidgetValues(self.widgets, {k: kwds[k]})
return
[docs] def increment(self, key, inc):
"""Increase the value of a widget"""
new = self.kwds[key]+inc
self.setWidgetValue(key, new)
return
[docs]class DynamicDialog(QDialog):
"""Dynamic form using baseoptions"""
def __init__(self, parent=None, options={}, groups=None, title='Dialog'):
super(DynamicDialog, self).__init__(parent)
self.setWindowTitle(title)
layout = QVBoxLayout()
self.setLayout(layout)
self.opts = BaseOptions(self, options, groups)
dialog = self.opts.showDialog(self, wrap=1, section_wrap=1)
layout.addWidget(dialog)
buttonbox = QDialogButtonBox(self)
buttonbox.addButton("Cancel", QDialogButtonBox.RejectRole)
buttonbox.addButton("Ok", QDialogButtonBox.AcceptRole)
self.connect(buttonbox, QtCore.SIGNAL("accepted()"), self, QtCore.SLOT("accept()"))
self.connect(buttonbox, QtCore.SIGNAL("rejected()"), self, QtCore.SLOT("reject()"))
layout.addWidget(buttonbox)
return
[docs]class Editor(QTextEdit):
def __init__(self, parent=None, fontsize=12, **kwargs):
super(Editor, self).__init__(parent, **kwargs)
font = QFont("Monospace")
font.setPointSize(fontsize)
font.setStyleHint(QFont.TypeWriter)
self.setFont(font)
return
[docs] def contextMenuEvent(self, event):
menu = QMenu(self)
copyAction = menu.addAction("Copy")
clearAction = menu.addAction("Clear")
zoominAction = menu.addAction("Zoom In")
zoomoutAction = menu.addAction("Zoom Out")
action = menu.exec_(self.mapToGlobal(event.pos()))
if action == copyAction:
self.copy()
elif action == clearAction:
self.clear()
elif action == zoominAction:
self.zoom(1)
elif action == zoomoutAction:
self.zoom(-1)
[docs] def insert(self, txt):
self.insertPlainText(txt)
self.verticalScrollBar().setValue(self.verticalScrollBar().maximum())
return
[docs]class PlainTextEditor(QPlainTextEdit):
def __init__(self, parent=None, **kwargs):
super(PlainTextEditor, self).__init__(parent, **kwargs)
font = QFont("Monospace")
font.setPointSize(10)
font.setStyleHint(QFont.TypeWriter)
self.setFont(font)
return
[docs] def contextMenuEvent(self, event):
menu = QMenu(self)
copyAction = menu.addAction("Copy")
clearAction = menu.addAction("Clear")
zoominAction = menu.addAction("Zoom In")
zoomoutAction = menu.addAction("Zoom Out")
action = menu.exec_(self.mapToGlobal(event.pos()))
if action == copyAction:
self.copy()
elif action == clearAction:
self.clear()
elif action == zoominAction:
self.zoom(1)
elif action == zoomoutAction:
self.zoom(-1)
[docs]class TextViewer(QDialog):
"""Plain text viewer"""
def __init__(self, parent=None, text='', width=200, height=400, title='Text'):
super(TextViewer, self).__init__(parent)
self.setWindowTitle(title)
self.setGeometry(QtCore.QRect(200, 200, width, height))
self.setMinimumHeight(150)
self.add_widgets()
self.ed.appendPlainText(text)
return
[docs] def add_widgets(self):
"""Add widgets"""
l = QVBoxLayout(self)
self.setLayout(l)
font = QFont("Monospace")
font.setPointSize(10)
font.setStyleHint(QFont.TypeWriter)
self.ed = ed = PlainTextEditor(self, readOnly=True)
self.ed.setFont(font)
l.addWidget(self.ed)
self.show()
[docs]class FileViewer(QDialog):
"""Sequence records features viewer"""
def __init__(self, parent=None, filename=None):
#QDialog.__init__(self)
super(FileViewer, self).__init__(parent)
self.ed = ed = QPlainTextEdit(self, readOnly=True)
#ed.setStyleSheet("font-family: monospace; font-size: 14px;")
font = QFont("Monospace")
font.setPointSize(10)
font.setStyleHint(QFont.TypeWriter)
self.ed.setFont(font)
self.setWindowTitle('sequence features')
self.setGeometry(QtCore.QRect(200, 200, 800, 800))
#self.setCentralWidget(ed)
l = QVBoxLayout(self)
self.setLayout(l)
l.addWidget(ed)
self.show()
[docs] def show_records(self, recs, format='genbank'):
from Bio import SeqIO
recs = SeqIO.to_dict(recs)
if format == 'genbank':
for r in recs:
self.ed.appendPlainText(recs[r].format('genbank'))
elif format == 'gff':
tools.save_gff(recs,'temp.gff')
f = open('temp.gff','r')
for l in f.readlines():
self.ed.appendPlainText(l)
recnames = list(recs.keys())
return
[docs]class TableViewer(QDialog):
"""View row of data in table"""
def __init__(self, parent=None, dataframe=None, **kwargs):
super(TableViewer, self).__init__(parent)
self.setGeometry(QtCore.QRect(200, 200, 600, 600))
self.grid = QGridLayout()
self.setLayout(self.grid)
self.table = tables.DataFrameTable(self, dataframe, **kwargs)
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.grid.addWidget(self.table)
return
[docs]class PlotViewer(QWidget):
"""matplotlib plots widget"""
def __init__(self, parent=None):
super(PlotViewer, self).__init__(parent)
self.setGeometry(QtCore.QRect(200, 200, 600, 600))
self.grid = QGridLayout()
self.setLayout(self.grid)
self.create_figure()
self.setWindowTitle('plots')
return
[docs] def create_figure(self, fig=None):
"""Create canvas and figure"""
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
#ax.plot(range(10))
if fig == None:
fig, ax = plt.subplots(1,1, figsize=(7,5), dpi=120)
self.ax = ax
if hasattr(self, 'canvas'):
self.layout().removeWidget(self.canvas)
canvas = FigureCanvas(fig)
self.grid.addWidget(canvas)
self.toolbar = NavigationToolbar(canvas, self)
self.grid.addWidget(self.toolbar)
self.fig = fig
self.canvas = canvas
iconfile = os.path.join(iconpath,'reduce.png')
a = QAction(QIcon(iconfile), "Reduce elements", self)
a.triggered.connect(lambda: self.zoom(zoomin=False))
self.toolbar.addAction(a)
iconfile = os.path.join(iconpath,'enlarge.png')
a = QAction(QIcon(iconfile), "Enlarge elements", self)
a.triggered.connect(lambda: self.zoom(zoomin=True))
self.toolbar.addAction(a)
return
[docs] def set_figure(self, fig):
"""Set the figure"""
self.clear()
self.create_figure(fig)
#self.ax = fig.ax
self.canvas.draw()
return
[docs] def clear(self):
"""Clear plot"""
self.fig.clear()
self.ax = self.fig.add_subplot(111)
self.canvas.draw()
return
[docs] def zoom(self, zoomin=True):
"""Zoom in/out to plot by changing size of elements"""
if zoomin == False:
val=-1.0
else:
val=1.0
if len(self.opts['general'].kwds) == 0:
return
self.opts['format'].increment('linewidth',val/5)
self.opts['format'].increment('ms',val)
self.opts['labels'].increment('fontsize',val)
self.refraw()
return
[docs]class BrowserViewer(QDialog):
"""Browser widget"""
def __init__(self, parent=None):
super(BrowserViewer, self).__init__(parent)
self.add_widgets()
return
[docs] def add_widgets(self):
"""Add widgets"""
layout = self.layout = QVBoxLayout()
self.main = QWidget()
vbox = QVBoxLayout(self.main)
layout.addWidget(self.main)
self.browser = QWebEngineView()
self.browser.urlChanged.connect(self.update_urlbar)
vbox = QVBoxLayout()
self.setLayout(vbox)
# create QToolBar for navigation
navtb = QToolBar("Navigation")
vbox.addWidget(navtb)
# add actions to the tool bar
iconfile = os.path.join(iconpath,'arrow-left.png')
icon = QIcon(iconfile)
back_btn = QAction(icon, "Back", self)
back_btn.setStatusTip("Back to previous page")
back_btn.triggered.connect(self.browser.back)
navtb.addAction(back_btn)
iconfile = os.path.join(iconpath,'arrow-right.png')
icon = QIcon(iconfile)
next_btn = QAction(icon, "Forward", self)
next_btn.setStatusTip("Forward to next page")
navtb.addAction(next_btn)
next_btn.triggered.connect(self.browser.forward)
iconfile = os.path.join(iconpath,'reload.png')
icon = QIcon(iconfile)
reload_btn = QAction(icon, "Reload", self)
reload_btn.setStatusTip("Reload page")
reload_btn.triggered.connect(self.browser.reload)
navtb.addAction(reload_btn)
navtb.addSeparator()
# creating a line edit for the url
self.urlbar = QLineEdit()
# adding action when return key is pressed
self.urlbar.returnPressed.connect(self.navigate_to_url)
navtb.addWidget(self.urlbar)
vbox.addWidget(self.browser)
self.browser.setMinimumHeight(500)
toolswidget = QWidget()
toolswidget.setMaximumHeight(40)
vbox.addWidget(toolswidget)
l = QVBoxLayout(toolswidget)
self.zoomslider = w = QSlider(QtCore.Qt.Horizontal)
w.setSingleStep(3)
w.setMinimum(2)
w.setMaximum(20)
w.setValue(10)
l.addWidget(w)
w.valueChanged.connect(self.zoom)
return
[docs] def update_urlbar(self, q):
"""method for updating url
this method is called by the QWebEngineView object
"""
# setting text to the url bar
self.urlbar.setText(q.toString())
# setting cursor position of the url bar
self.urlbar.setCursorPosition(0)
[docs]class SimpleBamViewer(QDialog):
"""Sequence records features viewer using dna_features_viewer"""
def __init__(self, parent=None, filename=None):
super(SimpleBamViewer, self).__init__(parent)
#self.setWindowTitle('Bam File View')
self.setGeometry(QtCore.QRect(200, 200, 1000, 300))
self.setMinimumHeight(150)
self.fontsize = 8
self.add_widgets()
return
[docs] def add_widgets(self):
"""Add widgets"""
l = QVBoxLayout(self)
self.setLayout(l)
val=0
navpanel = QWidget()
navpanel.setMaximumHeight(60)
l.addWidget(navpanel)
bl = QHBoxLayout(navpanel)
slider = QSlider(QtCore.Qt.Horizontal)
slider.setTickPosition(slider.TicksBothSides)
slider.setTickInterval(1000)
slider.setPageStep(200)
slider.setValue(1)
#slider.sliderReleased.connect(self.value_changed)
slider.valueChanged.connect(self.value_changed)
self.slider = slider
bl.addWidget(slider)
backbtn = QPushButton('<')
backbtn.setMaximumWidth(50)
bl.addWidget(backbtn)
backbtn.clicked.connect(self.prev_page)
nextbtn = QPushButton('>')
nextbtn.setMaximumWidth(50)
bl.addWidget(nextbtn)
nextbtn.clicked.connect(self.next_page)
zoomoutbtn = QPushButton('-')
zoomoutbtn.setMaximumWidth(50)
bl.addWidget(zoomoutbtn)
zoomoutbtn.clicked.connect(self.zoom_out)
zoominbtn = QPushButton('+')
zoominbtn.setMaximumWidth(50)
bl.addWidget(zoominbtn)
zoominbtn.clicked.connect(self.zoom_in)
self.startw = QLineEdit()
bl.addWidget(self.startw)
self.startw.setMaximumWidth(100)
self.startw.returnPressed.connect(self.goto)
self.geneselect = QComboBox()
self.geneselect.currentIndexChanged.connect(self.find_gene)
bl.addWidget(self.geneselect)
self.chromselect = QComboBox()
self.chromselect.currentIndexChanged.connect(self.update_chrom)
bl.addWidget(self.chromselect)
self.textview = QTextEdit(readOnly=True)
self.font = QFont("Monospace")
self.font.setPointSize(self.fontsize)
self.font.setStyleHint(QFont.TypeWriter)
self.textview.setFont(self.font)
l.addWidget(self.textview)
bottom = QWidget()
bottom.setMaximumHeight(50)
l.addWidget(bottom)
bl2 = QHBoxLayout(bottom)
self.loclbl = QLabel('')
bl2.addWidget(self.loclbl)
return
[docs] def load_data(self, bam_file, ref_file, gb_file=None, vcf_file=None):
"""Load reference seq and get contig/chrom names"""
self.ref_file = ref_file
self.bam_file = bam_file
self.gb_file = gb_file
chromnames = plotting.get_fasta_names(ref_file)
length = plotting.get_fasta_length(ref_file)
if self.gb_file != None:
df = tools.genbank_to_dataframe(gb_file)
if 'locus_tag' in df.columns:
df.loc[df["gene"].isnull(),'gene'] = df.locus_tag
if 'gene' in df.columns:
genes = df.gene.unique()
self.annot = df
self.geneselect.addItems(genes)
self.geneselect.setStyleSheet("QComboBox { combobox-popup: 0; }");
self.geneselect.setMaxVisibleItems(10)
self.chrom = chromnames[0]
self.chromselect.addItems(chromnames)
self.chromselect.setStyleSheet("QComboBox { combobox-popup: 0; }");
self.chromselect.setMaxVisibleItems(10)
sl = self.slider
sl.setMinimum(1)
sl.setMaximum(length)
sl.setTickInterval(length/20)
return
[docs] def set_chrom(self, chrom):
"""Set the selected record which also updates the plot"""
index = self.chromselect.findText(chrom)
self.chromselect.setCurrentIndex(index)
return
[docs] def update_chrom(self, chrom=None):
"""Update after chromosome selection changed"""
recname = self.chromselect.currentText()
length = self.length = plotting.get_fasta_length(self.ref_file, key=chrom)
sl = self.slider
sl.setMinimum(1)
sl.setMaximum(length)
sl.setTickInterval(length/20)
self.redraw()
return
[docs] def find_gene(self):
"""Go to selected gene if annotation present"""
gene = self.geneselect.currentText()
df = self.annot
df = df[df.gene==gene]
if len(df) == 0:
return
f = df.iloc[0]
self.redraw(f.start)
return
[docs] def value_changed(self):
"""Callback for widgets"""
length = self.length
#r = self.view_range
start = int(self.slider.value())
self.redraw(start)
return
[docs] def redraw(self, xstart=1):
"""Plot the features"""
h = 5
length = self.length
if xstart<0:
xstart=1
ref = self.ref_file
txt = tools.samtools_tview(self.bam_file, self.chrom, xstart, 500, ref, 'H')
self.textview.setText(txt)
self.loclbl.setText(str(xstart))
return
[docs] def prev_page(self):
""" """
start = int(self.slider.value())
start -= 500
if start<0:
return
self.redraw(start)
self.slider.setValue(start)
return
[docs] def next_page(self):
""" """
start = int(self.slider.value())
start += 500
self.redraw(start)
self.slider.setValue(start)
return
[docs] def zoom_in(self):
"""Zoom in"""
self.fontsize = self.fontsize+1
self.font.setPointSize(self.fontsize)
self.textview.setFont(self.font)
self.redraw()
return
[docs] def zoom_out(self):
"""Zoom out"""
if self.fontsize <= 2:
return
self.fontsize = self.fontsize-1
self.font.setPointSize(self.fontsize)
self.textview.setFont(self.font)
self.redraw()
return
[docs]class GraphicalBamViewer(QDialog):
"""Alignment viewer with pylab"""
def __init__(self, parent=None, filename=None):
super(BamViewer, self).__init__(parent)
#self.setWindowTitle('Bam File View')
self.setGeometry(QtCore.QRect(200, 200, 1000, 300))
self.setMinimumHeight(150)
self.add_widgets()
return
[docs] def add_widgets(self):
"""Add widgets"""
l = QVBoxLayout(self)
self.setLayout(l)
val=0
navpanel = QWidget()
navpanel.setMaximumHeight(60)
l.addWidget(navpanel)
bl = QHBoxLayout(navpanel)
slider = QSlider(QtCore.Qt.Horizontal)
slider.setTickPosition(slider.TicksBothSides)
slider.setTickInterval(1000)
slider.setPageStep(200)
slider.setValue(1)
#slider.sliderReleased.connect(self.value_changed)
slider.valueChanged.connect(self.value_changed)
self.slider = slider
bl.addWidget(slider)
zoomoutbtn = QPushButton('-')
zoomoutbtn.setMaximumWidth(50)
bl.addWidget(zoomoutbtn)
zoomoutbtn.clicked.connect(self.zoom_out)
zoominbtn = QPushButton('+')
zoominbtn.setMaximumWidth(50)
bl.addWidget(zoominbtn)
zoominbtn.clicked.connect(self.zoom_in)
self.chromselect = QComboBox()
self.chromselect.currentIndexChanged.connect(self.update_chrom)
bl.addWidget(self.chromselect)
#add plot axes
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(15,2))
spec = fig.add_gridspec(ncols=1, nrows=4)
self.ax1 = fig.add_subplot(spec[0, 0])
self.ax2 = fig.add_subplot(spec[1:3, 0])
self.ax3 = fig.add_subplot(spec[3, 0])
self.canvas = FigureCanvas(fig)
self.fig = fig
l.addWidget(self.canvas)
bottom = QWidget()
bottom.setMaximumHeight(50)
l.addWidget(bottom)
bl2 = QHBoxLayout(bottom)
self.loclbl = QLabel('')
bl2.addWidget(self.loclbl)
return
[docs] def load_data(self, bam_file, ref_file, gb_file=None, vcf_file=None):
"""Load reference seq and get contig/chrom names"""
self.ref_file = ref_file
self.bam_file = bam_file
self.gb_file = gb_file
chromnames = plotting.get_fasta_names(ref_file)
length = plotting.get_fasta_length(ref_file)
self.chrom = chromnames[0]
self.chromselect.addItems(chromnames)
self.chromselect.setStyleSheet("QComboBox { combobox-popup: 0; }");
self.chromselect.setMaxVisibleItems(10)
sl = self.slider
sl.setMinimum(1)
sl.setMaximum(length)
sl.setTickInterval(length/20)
return
[docs] def set_chrom(self, chrom):
"""Set the selected record which also updates the plot"""
index = self.chromselect.findText(chrom)
self.chromselect.setCurrentIndex(index)
return
[docs] def update_chrom(self, chrom=None):
"""Update after chromosome selection changed"""
recname = self.chromselect.currentText()
length = self.length = plotting.get_fasta_length(self.ref_file, key=chrom)
sl = self.slider
sl.setMinimum(1)
sl.setMaximum(length)
sl.setTickInterval(length/20)
self.redraw()
return
[docs] def value_changed(self):
"""Callback for widgets"""
length = self.length
r = self.view_range
start = int(self.slider.value())
end = int(start+r)
if end > length:
return
self.redraw(start, end)
return
[docs] def zoom_in(self):
"""Zoom in"""
#length = len(self.rec.seq)
fac = 1.2
r = int(self.view_range/fac)
start = int(self.slider.value())
end = start + r
#if end > length:
# end=length
self.redraw(start, end)
return
[docs] def zoom_out(self):
"""Zoom out"""
#length = len(self.rec.seq)
fac = 1.2
r = int(self.view_range*fac)
start = int(self.slider.value())
end = start + r
#if end > length:
# end=length
# start = start-r
self.redraw(start, end)
return
[docs] def redraw(self, xstart=1, xend=2000):
"""Plot the features"""
h = 5
self.ax1.clear()
self.ax2.clear()
length = self.length
if xstart<0:
xstart=1
if xend <= 0:
xend = xstart+2000
if xend-xstart > 10000:
xend = xstart+10000
if xend > length:
xend = length
#print (start, end)
cov = plotting.get_coverage(self.bam_file, self.chrom, xstart, xend)
plotting.plot_coverage(cov,ax=self.ax1,xaxis=False)
plotting.plot_bam_alignment(self.bam_file, self.chrom, xstart, xend, ax=self.ax2)
if self.gb_file != None:
recs = tools.gb_to_records(self.gb_file)
plotting.plot_features(recs[0], self.ax3, xstart=xstart, xend=xend)
self.canvas.draw()
self.view_range = xend-xstart
self.loclbl.setText(str(xstart)+'-'+str(xend))
return