Skip to content
Snippets Groups Projects
mplot.py 7.72 KiB
Newer Older
# mplot.py ---
#
# Filename: mplot.py
# Description:
# Author:
# Maintainer:
# Created: Mon Mar 11 20:24:26 2013 (+0530)
# Version:
# Last-Updated: Wed Jul  3 10:32:35 2013 (+0530)
#           By: subha
#     Update #: 309
# URL:
# Keywords:
# Compatibility:
#
#

# Commentary:
#
# Moose plot widget default implementation. This should be rich enough
# to suffice for most purposes.
#
#

# Change log:
#
#
#
#
# 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 3, 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; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street, Fifth
# Floor, Boston, MA 02110-1301, USA.
#
#

# Code:
"""
    *TODO*

    1) Option for default colors, markers, etc.

    2) Option for configuring number of rows and columns of
    subplots. (I think matplotlib grids will be a bit too much to
    implement). Problem is this has to be done before actual axes are
    created (as far as I know). Idea: can we do something like movable
    widgets example in Qt?

    3) Option for selecting any line or set of lines and change its
    configuration (as in dataviz).

    4) Association between plots and the data source.

    5) Lots and lots of scipy/numpy/scikits/statsmodels utilities can be added. To
    start with, we should have
      a)digital filters
      b) fft
      c) curve fitting

    6) For (5), think of another layer of plugins. Think of this as a
    standalone program. All these facilities should again be
    pluggable. We do not want to overwhelm novice users with fancy
    machine-learning stuff. They should be made available only on
    request.
        - There is a proposal for data analysis library by Andrew Davison ...

"""


__author__ = "Subhasis Ray"
import sys
import numpy as np
from PyQt4 import QtGui, QtCore
from PyQt4.Qt import Qt
from matplotlib import mlab
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
#from moose import utils
import moose
from PyQt4.QtCore import *

class CanvasWidget(FigureCanvas):
    """Widget to draw plots on.

    This class keep track of all the axes in a dictionary. The key for
    an axis is its index number in sequence of creation.

    next_id: The key for the next axis.

    current_id: Key for current axis (anu plotting will happen on
    this).

    """
    updateSignal = pyqtSignal()

    def __init__(self, model, graph, index, *args, **kwargs):
        self.model = model
        self.graph = graph
        self.index = index
        # QColor(243, 239, 238, 255)
        self.figure = Figure(facecolor = '#F3EFEE')#figsize=(1,1))
        FigureCanvas.__init__(self, self.figure, *args, **kwargs)
        self.figure.set_canvas(self)
        # self.set_xlabel('Time (s)')
        # self.set_ylabel('Concentration (mM)')
        if len(args) > 0 and isinstance(args[0], QtGui.QWidget):
            self.reparent(args[0])
        elif (kwargs is not None) and ('parent' in kwargs):
            self.reparent(kwargs['parent'])
        #self.setAcceptDrops(True)
        # self.setMaximumSize(100, 100)
        FigureCanvas.updateGeometry(self)
        self.axes = {}
        self.next_id = 0
        self.current_id = -1
        tabList = []
        self.addTabletoPlot = ''
        self.setAcceptDrops(True)
        self.gridMode = False

    def dragEnterEvent(self, event):
        if event.mimeData().hasFormat('text/plain'):
            event.acceptProposedAction()

    def dragMoveEvent(self, event):
        if event.mimeData().hasFormat('text/plain'):
            event.acceptProposedAction()

    def eventFilter(self, source, event):
        if (event.type() == QtCore.QEvent.Drop):
            pass

    def dropEvent(self, event):
        """Insert an element of the specified class in drop location"""

        if not event.mimeData().hasFormat('text/plain'):
            return
        # print " active window ", self.isActiveWindow()
        # print "Mouse : ", self.mouse

        # pos = self.mapFromGlobal(QCursor.pos())
        # print "Mouse Position : ", pos
        modelRoot, element = event.mimeData().data
        if isinstance (element,moose.PoolBase):
            tablePath = moose.utils.create_table_path(self.model, self.graph, element, "Conc")
            table     = moose.utils.create_table(tablePath, element, "Conc","Table2")
            # moose.connect(table, 'requestOut', element, 'getConc')
            self.updateSignal.emit()
        elif isinstance(element, moose.CompartmentBase):
            tablePath = moose.utils.create_table_path(self.model, self.graph, element, "Vm")
            table     = moose.utils.create_table(tablePath, element, "Vm","Table")
            self.updateSignal.emit()
        else:
            QtGui.QMessageBox.question(self, 'Message',"This element's properties cannot be plotted.", QtGui.QMessageBox.Ok)

    def addSubplot(self, rows, cols):
        """Add a subplot to figure and set it as current axes."""
        assert(self.next_id <= rows * cols)
        axes = self.figure.add_subplot(rows, cols, self.next_id+1)
        axes.set_xlabel("Time (s)")
        axes.set_ylabel("Concentration (mM)")
        axes.set_xlim(left=0.0)
        axes.set_ylim(bottom=0.0)
        self.axes[self.next_id] = axes
        axes.set_title("Graph " + str(self.index + 1))
        self.current_id = self.next_id
        self.next_id += 1
        labelList = []
        axes.legend(loc='upper center')
        return axes

    def plot(self, *args, **kwargs):
        #self.callAxesFn('legend',loc='lower center',bbox_to_anchor=(0.5, -0.03),fancybox=True, shadow=True, ncol=3)
        return self.callAxesFn('plot', *args, **kwargs)

    def callAxesFn(self, fname, *args, **kwargs):
        """Call any arbitrary function of current axes object."""
        if self.current_id < 0:
            self.addSubplot(1,1)
        fn = eval('self.axes[self.current_id].%s' % (fname))

        return fn(*args, **kwargs)

    def resize_event(self, event):
        print("Resize event called ", event)

    def toggleGrid(self):
        self.gridMode = not self.gridMode
        for key in self.axes:
            self.axes[key].grid(self.gridMode)
        self.draw()

    def setXLimit(self, minX, maxX):
        for key in self.axes:
            self.axes[key].set_xlim([minX, maxX])
        self.draw()


import sys
import os
import config
import unittest

from PyQt4.QtTest import QTest

class CanvasWidgetTests(unittest.TestCase):
    def setUp(self):
        self.app = QtGui.QApplication([])
        QtGui.qApp = self.app
        icon = QtGui.QIcon(os.path.join(config.KEY_ICON_DIR,'moose_icon.png'))
        self.app.setWindowIcon(icon)
        self.window = QtGui.QMainWindow()
        self.cwidget = CanvasWidget()
        self.window.setCentralWidget(self.cwidget)
        self.window.show()

    def testPlot(self):
        """Test plot function"""
        self.cwidget.addSubplot(1,1)
        self.cwidget.plot(np.arange(1000), mlab.normpdf(np.arange(1000), 500, 150))

    def testCallAxesFn(self):
        self.cwidget.addSubplot(1,1)
        self.cwidget.callAxesFn('scatter', np.random.randint(0, 100, 100), np.random.randint(0, 100,100))

    def tearDown(self):
        self.app.exec_()

if __name__ == '__main__':
    unittest.main()

#
# mplot.py ends here