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 color(self): return self._color
[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 createWidgets(self): """Create widgets - override this""" cols = list(self.df.columns)
[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 apply(self): """Override this""" return
[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] def close(self): self.destroy() 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 updateColumns(self): #self.df2 = cols2 = self.df2.columns 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 apply(self): self.applyOptions() if self.callback != None: self.callback() 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] def get_values(): """Get the widget values""" kwds = self.opts.kwds return kwds
[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 zoom(self, delta): if delta < 0: self.zoomOut(1) elif delta > 0: self.zoomIn(1)
[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 zoom(self, delta): if delta < 0: self.zoomOut(1) elif delta > 0: self.zoomIn(1)
[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] def setDataFrame(self, dataframe): self.table.model.df = dataframe 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 redraw(self): self.canvas.draw()
[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 load_page(self, url): self.browser.setUrl(url)
[docs] def navigate_to_url(self): """method called by the line edit when return key is pressed getting url and converting it to QUrl object""" q = QUrl(self.urlbar.text()) if q.scheme() == "": q.setScheme("http") # set the url to the browser self.browser.setUrl(q)
[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] def zoom(self): zoom = self.zoomslider.value()/10 self.browser.setZoomFactor(zoom)
[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 goto(self): loc = int(self.startw.text()) self.redraw(loc) 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