#! /usr/bin/env python
# coding: latin-1

import sys
import re
import time
from PyQt4 import QtCore, QtGui
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
import numpy
from numpy import sin, cos, tan, arcsin, arccos, arctan, exp, log
from numpy import sinh, cosh, tanh, arcsinh, arccosh, arctanh, sqrt
import sympy

# Clase de dibujado
class MiCanvas(FigureCanvas):
  #
  def __init__(self, fig):
    super(MiCanvas, self).__init__(fig)
  #
  def mousePressEvent(self, ev):
    self.emit(QtCore.SIGNAL("pulsado(int,int)"), ev.x(), ev.y())
# Clase grafica
class VerFunciones(QtGui.QWidget):
  # Constructor
  def __init__(self):
    super(VerFunciones, self).__init__()
    #
    self.figure = plt.figure()
    self.ultT = time.time()
    self.canvas = MiCanvas(self.figure)
    self.connect(self.canvas, QtCore.SIGNAL("pulsado(int,int)"), self.pulsado)
    #
    self.z = sympy.Symbol("z")
    self.xp = None
    self.yp = None
    self.dx = None
    self.tempo = QtCore.QTimer(self)
    self.connect(self.tempo, QtCore.SIGNAL("timeout()"), self.cambioDx)
    self.tempo.start(1000)
    #
    gArriba = QtGui.QFormLayout()
    #
    lFun = QtGui.QLabel("f(x) = ")
    self.iFun = QtGui.QLineEdit()
    gArriba.addRow(lFun, self.iFun)
    #
    lxIni = QtGui.QLabel("x inicial = ")
    self.ixIni = QtGui.QLineEdit()
    gArriba.addRow(lxIni, self.ixIni)
    #
    lxFin = QtGui.QLabel("x final = ")
    self.ixFin = QtGui.QLineEdit()
    gArriba.addRow(lxFin, self.ixFin)
    #
    bDibujar = QtGui.QPushButton("Dibujar")
    bDibujar.clicked.connect( self.dibujar )
    #
    self.lInfo = QtGui.QLabel("Info: ")
    #
    grid = QtGui.QVBoxLayout()
    grid.setSpacing(10)
    grid.addItem(gArriba)
    grid.addWidget(bDibujar)
    grid.addWidget(self.canvas)
    grid.addWidget(self.lInfo)
    #
    self.setLayout(grid)
    #
    self.setWindowTitle("Pruebas con matplotlib")
    self.setFixedSize(900, 700)
    self.show()
  #
  def dibujar(self):
    try:
      xIni = float( self.ixIni.text() )
      xFin = float( self.ixFin.text() )
      x = numpy.linspace(xIni, xFin, 500)
      fx = str( self.iFun.text() )
      fx = re.sub("\^", "**", fx)
      y = eval(fx)
      #
      ax = self.figure.add_subplot(111)
      ax.hold(False)
      ax.plot(x, y, "b-")
      # Secante o tangente
      if self.dx:
        inv = ax.transData.inverted()
        x = inv.transform((self.xp,0))[0]
        x0 = x
        y0 = eval(fx)
        x = x0 + self.dx
        x1 = x
        y1 = eval(fx)
        m = (y1 - y0) / (x1 - x0)
        x = x0 + (xFin - xIni) / 10.0
        x2 = x
        y2 = y0 + m *(x2 - x0)
        #
        self.lInfo.setText("secante: dx=%g -> P=(%g, %g) -> m=%g" % (self.dx, x0, y0, m))
        #
        ax.hold(True)
        ax.plot([x0], [y0], 'ro')
        ax.plot([x0, x1], [y0, y1], 'r-')
        ax.plot([x1], [y1], 'ko')
        ax.plot([x1, x2], [y1, y2], 'k-')
      else:
        self.dx = (xFin - xIni) / 5.0
      # Calcula la derivada con sympy -> tangente
      fz = re.sub("x", "self.z", fx)
      for nomFun in [ "cos", "sin", "tan" ]:
        fz = re.sub(nomFun, "sympy.%s" % nomFun, fz)
        fz = re.sub("a%s" % nomFun, "sympy.arc%s" % nomFun, fz)
        fz = re.sub("%sh" % nomFun, "sympy.%sh" % nomFun, fz)
        fz = re.sub("a%sh" % nomFun, "sympy.arc%sh" % nomFun, fz)
      for nomFun in [ "exp", "log", "sqrt" ]:
        fz = re.sub(nomFun, "sympy.%s" % nomFun, fz)
      fz = eval(fz)
      dfz = fz.diff(self.z)
      strDfx = re.sub("z", "x", str(dfz))
      cadDfx = re.sub("[*][*]", "^", strDfx)
      ###print("df(x)/ dx = %s" % cadDfx)
      # Y la evalua si puede
      if self.xp:
        inv = ax.transData.inverted()
        x = inv.transform((self.xp,0))[0]
        x0 = x
        y0 = eval(fx)
        m = eval(strDfx)
        self.lInfo.setText(self.lInfo.text() + "\ntangente: P=(%g, %g) -> df(x)/ dx = %s -> m=%g" % (x0, y0, cadDfx, m))
      #
      self.canvas.draw()
    except:
      pass
  #
  def pulsado(self, xx, yy):
    self.xp = xx
  #
  def cambioDx(self):
    if self.dx:
      if self.dx > 1e-6:
        self.dx = self.dx / 2
      else:
        self.dx = None
    ###print self.dx
    self.dibujar()
#
# Programa principal
#
apli = QtGui.QApplication(sys.argv)
ventana = VerFunciones()
sys.exit( apli.exec_() )
