#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Apr 10 22:50:51 2019

@author: juan

Este es un modulo para  alojar funciones

Para introducir una funcion nueva hay que:
    1 - añadir una línea en  VG_funciones:
        ejem:('log','Logaritmo: y = a0·log10(a1·x)',2, False),
        NEMO = sale en el combo, nombre: explicación debajo del combo,
        parametros: que necesita la función sin contar con la X y
        Si es circular, se repite cada 360 grados como el seno,coseno,.. no se usa de momento.

    2 - Defiir una función como las exitentes debajo de la última,
        ejem: def FIlog(self):  -- Hay que añadir FI an NEMO
        La función retornará:
            ejem: return (ayuda, salida, y, x_y_df, v_x, v_y)
        Ayuda, salida de la operación con los parámetros introducidos,
        y, el resultado para el x dado, x_y_df, tabla con todos los valores x,y,
        v_x y v_y los valores x e y para pintar la gráfica.


"""
import scipy as sp
import numpy as np
from numpy.lib.scimath import sqrt as csqrt
import pandas as pd
import tkinter as tk


# import io
# import random
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt

from tkinter import ttk
from matplotlib.backends.backend_tkagg import (
    FigureCanvasTkAgg, NavigationToolbar2Tk)



global VG_funciones
# Numero, Nemo, nombre, parametros,circular,
VG_funciones = [('fi', '_Número aúreo: y = fi·x', 0, False),  # base
                ('log', 'Logaritmo decimal: y = a0·log10(x·a1/a2)', 3, False),
                ('ln', 'Logaritmo neperiano: y = a0·ln(x·a1/a2)', 3, False),
                ('recta', 'p1_Recta: y = a0 + a1·x ', 2, False),
                ('sen', 'c_Seno: y = a0·sen(x·a1·a2/a3)', 4, True),  # pi y 180 Circular
                ('cos', 'c_Coseno: y = a0·cos(x·a1·a2/a3)', 4, True),  # pi y 180 Circular
                ('tan', 'c_Tangente: y = a0·tan(x·a1·a2/a3)', 4, True),  # pi y 180 Circular
                ('polinomio', 'p5_Polinomio: y = a0 + a1·x + ... + a1·x^5', 6, False),  # de 5º grado
                ('e2g', 'p2_Ecuación de 2º grado: y = a0 + a1·x + a2·x^2', 3, False)  # de 2º grado
                ]


DEBUGGING = 'ON'  # ON  para debug


def headline(text, sep):
    aa = ''.join(map(lambda x: x*7, sep))
    print('\n'+aa+text+aa)


def debugging(text):
    sep = '#'
    aa = ''.join(map(lambda x: x*3, sep))
    if DEBUGGING == 'ON':
        print('\n'+aa+text+aa)


def FIfun_listas():
    """ Rellena las listas con la list VG_funciones"""
    f_nemos, f_nombres, f_parametros, f_argumentos = [], [], [], []
    for nemo, nombre, parametros, argumentos in VG_funciones:
        f_nemos.append(nemo)
        f_nombres.append(nombre)
        f_parametros.append(parametros-1)  # numero parametros real
        f_argumentos.append(argumentos)
    df = pd.DataFrame(VG_funciones, columns=['nemo', 'name', 'parametros', 'argumentos'])
    #df = df.sort_values(by='name', key=lambda col: col.str.lower())
    return (f_nemos, f_nombres, f_parametros, f_argumentos, df)


""" Definicion de funciones """


class FIfun(object):
    """ FI Funciones """

    def __init__(self, x, funcion, a0, a1, a2, a3, a4, a5, circular):
        # Parámetros de la clase
        self.x = x
        self.funcion = funcion
        self.a0 = a0
        self.a1 = a1
        self.a2 = a2  # pi para senoidales
        self.a3 = a3  # 180 para sinoidades
        self.a4 = a4
        self.a5 = a5
        self.circular = circular

    def FIfuncion(self):
        """ Dispatch method """
        # Get the method from 'self'. Default to a lambda.
        method = getattr(self, self.funcion, lambda: "Invalid function")
        # Call the method as we return it
        return method()

# Divina proporción

    def FIfi(self):

        help = 'Recta pon pendiente φ (phi) que pasa por el origen de coordenadas'
        notes = """fi = (1 + √5)/2 = 1.6180339887498948...
        <------a------><----b----> fi = a/b  => (a+b)/a = a/b
        El número áureo (también llamado número de oro,
        razón extrema y media,​ razón áurea, razón dorada, media áurea,
        proporción áurea y divina proporción​) es un número irracional
        representado por la letra griega φ (phi) (en minúscula) o
        Φ (Phi) (en mayúscula) en honor al escultor griego Fidias.
                """
        x = self.x

        formula = 'f(x) = fi·x'

        y = self.x * 1.6180339887498948

        minimo = round(self.x) - 5
        maximo = round(self.x) + 5
        xy_df = pd.DataFrame([[l_x, np.round((l_x * 1.6180339887498948), 2)]
                              for l_x in np.arange(minimo, maximo)], columns=['x', 'y'])

        x1 = 0
        roots = [x1]
        xyroots_df = pd.DataFrame([[np.round(l_x, 2), np.round((l_x * 0), 2)]
                                   for l_x in np.array(roots)], columns=['x', 'y'])

        return(help, formula, notes, x, y, xy_df, xyroots_df)


# Logaritmo base 10


    def FIlog(self):
        """ Funcion logaritmo decimal base 10 """
        help = 'f(x) = a0·log10(x·a1/a2) Logaritmo base 10'
        notes = ''' y = log x => 10^y = x. Expresión del decibelio (dB) => a0 = 10.
                    Ganancia (db) = 10 log (Ps/Pe) = 20 log (Vs/Ve) = 20 log (Is/Ie).
                    Ps, Pe = potencias de salida y entrada, V = IR
                    Clasificación de sonidos según los decibelios:
                     Cercano al silencio total – 0 dB
                     Un suspiro – 15 dB
                     Una conversación normal – 60 dB
                     Una bocina de automovil – 110 dB
                     Una turbina de avión – 120 dB
                     Cualquier sonido por encima de los 85 dB, puede causar pérdida de audición. ||
                     log\u2090 (x) = log\u2091(x) / log\u2091(a)

                '''
        # Parameters
        x = self.x
        a0 = self.a0
        a1 = self.a1
        a2 = self.a2
        # Expresión de la formula numerica
        formula = 'f(x) = {:.2f}·log10(x·{:.2f}/{:.2f})'.format(a0, a1, a2)
        # Calculo de la funcion

        def f(x, a0, a1, a2):
            return a0 * sp.log10(x * a1 / a2)

        # Valor y para la x dada
        y = round(f(x, a0, a1, a2), 6)
        # Dataset (x, y)
        minimo = round(x) - 50
        maximo = round(x) + 50
        xy_df = pd.DataFrame([[np.round(l_x, 2), np.round(f(l_x, a0, a1, a2), 2)]
                              for l_x in np.arange(minimo, maximo, 3)], columns=['x', 'y'])
        xy_df = xy_df.replace([np.inf, -np.inf, np.nan], 0)
        # Dataset (raices)
        x1 = round(a2/a1, 6)
        roots = [x1]
        xyroots_df = pd.DataFrame([[np.round(l_x, 2), np.round((l_x * 0), 2)]
                                   for l_x in np.array(roots)], columns=['x', 'y'])

        return(help, formula, notes, x, y, xy_df, xyroots_df)


# Logaritmo neperiano


    def FIln(self):
        """ Funcion logaritmo neperiano"""
        help = 'f(x) = a0·ln(x·a1/a2) Logaritmo neperiano base e = 2.71828'
        notes = ''' y = ln x => e^y = x. ||  e^(jx) = cos(x) + j·sen(x) || e^(-jx) = cos(x) - j·sen(x) ||
                    jx = ln(cos(x) + j·sen(x)) || -jx = ln(cos(x) - j·sen(x)) || j = i = sqrt(-1) = \u221a-1  número imaginario, complejo ||
                    log\u2090 (x) = log\u2091(x) / log\u2091(a)
                '''
        # Parameters
        x = self.x
        a0 = self.a0
        a1 = self.a1
        a2 = self.a2
        # Expresión de la formula numerica
        formula = 'f(x) = {:.2f}·ln(x·{:.2f}/{:.2f})'.format(a0, a1, a2)
        # Calculo de la funcion

        def f(x, a0, a1, a2):
            return a0 * sp.log(x * a1 / a2)

        # Valor y para la x dada
        y = round(f(x, a0, a1, a2), 6)
        # Dataset (x, y)
        minimo = round(x) - 50
        maximo = round(x) + 50
        xy_df = pd.DataFrame([[np.round(l_x, 2), np.round(f(l_x, a0, a1, a2), 2)]
                              for l_x in np.arange(minimo, maximo, 3)], columns=['x', 'y'])
        xy_df = xy_df.replace([np.inf, -np.inf, np.nan], 0)
        # Dataset (raices)
        x1 = round(a2/a1, 6)
        roots = [x1]
        xyroots_df = pd.DataFrame([[np.round(l_x, 2), np.round((l_x * 0), 2)]
                                   for l_x in np.array(roots)], columns=['x', 'y'])

        return(help, formula, notes, x, y, xy_df, xyroots_df)


# Coseno


    def FIcos(self):
        headline(' Función coseno FIcos ', '+-+')
        help = "f(x) = a0 * cos(x * a1*a2/a3)  x vendrá expresado en radianes."
        notes = '''
                    (x) vendrá expresado en grados <=> a1 ó a2 = pi (3.1416) y a3 = 180 (grados). \n
                    Como tiene infinitas raices solo se calculan las cinco más cercanas al punto (x). \n
                    cos(x) = (e^jx + e^(-jx))/2 || j = i = sqrt(-1) = \u221a-1  número imaginario, complejo ||
                '''
        notes2 = '180 grados son pi radianes = {} \n'.format(np.pi)
        notes3 = 'y 90 grados son pi/2 radianes = {}. '.format(np.pi/2)
        notes = notes + notes2 + notes3
        # Parameters
        x = self.x
        a0 = self.a0
        a1 = self.a1
        a2 = self.a2
        a3 = self.a3

        # Expresión de la formula numerica
        formula = 'f(x) = {:.2f}·cos (x·{:.2f}·{:.2f}/{:.2f})'.format(a0, a1, a2, a3)

        # Calculo de la funcion

        def f(x, a0, a1, a2, a3):
            return a0*np.cos(x * a1 * a2 / a3)

        # Núcleo funcion en radianes

        def nucleo_rd(x, a0, a1, a2, a3):
            return x * a1 * a2 / a3

        # Núcleo origen de la función

        def nucleo_origen(xrd, a0, a1, a2, a3):
            return xrd * a3 / (a1 * a2)

        y = round(f(x, a0, a1, a2, a3), 6)  # Valor y para la x dada
        x_rd = nucleo_rd(x, a0, a1, a2, a3)  # valor del nucleo en radianes
        x_gr = x_rd*180/np.pi  # valor del nucleo en grados
        # rastreo print('Núcleo: radianes: {:.2f}, grados: {:.2f}'. format(x_rd, x_gr))

        # Dataset (x, y)

        minimo = nucleo_origen(x_rd - 2.5*np.pi, a0, a1, a2, a3)
        maximo = nucleo_origen(x_rd + 2.5*np.pi, a0, a1, a2, a3)
        step = nucleo_origen(np.pi/12, a0, a1, a2, a3)  # saltos de pi/xx grados
        puntos = round((maximo-minimo)/step)
        # rastreo print('minimo: ', minimo)
        # rastreo print('maximo: ', maximo)
        # rastreo print('step: ', step)
        # rastreo print('puntos: ', puntos)

        xy_df = pd.DataFrame([[np.round(l_x, 6), np.round(f(l_x, a0, a1, a2, a3), 6)]
                              for l_x in np.arange(minimo, maximo, step)], columns=['x', 'y'])
        # xy_df = xy_df.replace([np.inf, -np.inf, np.nan], 0)

        # Dataset (raices) ++++ Calculo de raices entorno al punto x +++
        # Periodo en el que está la x
        periodo = round(x_gr/90)
        # Multiplos de pi/2, 90 grados
        raicesprobables = list(
            map(lambda key: int(key*90), range(periodo-5, periodo+6, 1)))
        # rastreo print('posibles raices ', raicesprobables)
        # Para el coseno filtro por los multiplos de pi/2, 90 grados impares
        # raices en grados
        raices_gr = list(
            filter(lambda key: (key/90) % 2 != 0, raicesprobables))
        # rastreo print('raices_gr ', raices_gr)
        # raices en radianes
        raices_rd = list(
            map(lambda key: key * np.pi / 180, raices_gr))
        # rastreo print('raices_rd ', raices_rd)
        # raices en la unidad de la X de entrada
        raices_origen = list(
            map(lambda key: nucleo_origen(key, a0, a1, a2, a3), raices_rd))
        # rastreo print('raices_origen ', raices_origen)

        xyroots_df = pd.DataFrame([[np.round(l_x, 6), np.round((l_x * 0), 6)]
                                   for l_x in raices_origen], columns=['x', 'y'])
        # rastreo print('roots:', xyroots_df)
        headline(' FIN Función coseno FIcos ', '+-+')
        return(help, formula, notes, x, y, xy_df, xyroots_df)


# Seno


    def FIsen(self):
        debugging('  @FIsen ')
        help = "f(x) = a0 * sen(x * a1*a2/a3)  x vendrá expresado en radianes."
        notes = '''
                    (x) vendrá expresado en grados <=> a1 ó a2 = pi (3.1416) y a3 = 180 (grados). \n
                    Como tiene infinitas raices solo se calculan las cinco más cercanas al punto (x). \n
                    cos(x) = (e^jx + e^(-jx))/2 || j = i = sqrt(-1) = \u221a-1  número imaginario, complejo ||
                '''
        notes2 = '180 grados son pi radianes = {} \n'.format(np.pi)
        notes3 = 'y 90 grados son pi/2 radianes = {}. '.format(np.pi/2)
        notes = notes + notes2 + notes3
        # Parameters
        x = self.x
        a0 = self.a0
        a1 = self.a1
        a2 = self.a2
        a3 = self.a3

        # Expresión de la formula numerica
        formula = 'f(x) = {:.2f}·sen (x·{:.2f}·{:.2f}/{:.2f})'.format(a0, a1, a2, a3)

        # Calculo de la funcion

        def f(x, a0, a1, a2, a3):
            return a0*np.sin(x * a1 * a2 / a3)

        # Núcleo funcion en radianes

        def nucleo_rd(x, a0, a1, a2, a3):
            return x * a1 * a2 / a3

        # Núcleo origen de la función

        def nucleo_origen(xrd, a0, a1, a2, a3):
            return xrd * a3 / (a1 * a2)

        y = round(f(x, a0, a1, a2, a3), 6)  # Valor y para la x dada
        x_rd = nucleo_rd(x, a0, a1, a2, a3)  # valor del nucleo en radianes
        x_gr = x_rd*180/np.pi  # valor del nucleo en grados
        # rastreo print('Núcleo: radianes: {:.2f}, grados: {:.2f}'. format(x_rd, x_gr))

        # Dataset (x, y)

        minimo = nucleo_origen(x_rd - 2.5*np.pi, a0, a1, a2, a3)
        maximo = nucleo_origen(x_rd + 2.5*np.pi, a0, a1, a2, a3)
        step = nucleo_origen(np.pi/12, a0, a1, a2, a3)  # saltos de pi/xx grados
        puntos = round((maximo-minimo)/step)
        # rastreo print('minimo: ', minimo)
        # rastreo print('maximo: ', maximo)
        # rastreo print('step: ', step)
        # rastreo print('puntos: ', puntos)

        xy_df = pd.DataFrame([[np.round(l_x, 6), np.round(f(l_x, a0, a1, a2, a3), 6)]
                              for l_x in np.arange(minimo, maximo, step)], columns=['x', 'y'])
        # xy_df = xy_df.replace([np.inf, -np.inf, np.nan], 0)

        # Dataset (raices) ++++ Calculo de raices entorno al punto x +++
        # Periodo en el que está la x
        periodo = round(x_gr/90)
        # Multiplos de pi/2, 90 grados
        raicesprobables = list(
            map(lambda key: int(key*90), range(periodo-5, periodo+6, 1)))
        # rastreo print('posibles raices ', raicesprobables)
        # Para el seno filtro por los multiplos de pi, 180 grados impares
        # raices en grados
        raices_gr = list(
            filter(lambda key: (key/90) % 2 == 0, raicesprobables))
        # rastreo print('raices_gr ', raices_gr)
        # raices en radianes
        raices_rd = list(
            map(lambda key: key * np.pi / 180, raices_gr))
        # rastreo print('raices_rd ', raices_rd)
        # raices en la unidad de la X de entrada
        raices_origen = list(
            map(lambda key: nucleo_origen(key, a0, a1, a2, a3), raices_rd))
        # rastreo print('raices_origen ', raices_origen)

        xyroots_df = pd.DataFrame([[np.round(l_x, 6), np.round((l_x * 0), 6)]
                                   for l_x in raices_origen], columns=['x', 'y'])
        # rastreo print('roots:', xyroots_df)
        debugging('  @FIsen  - END ')
        return(help, formula, notes, x, y, xy_df, xyroots_df)


# Tangente


    def FItan(self):
        debugging('  @FItan ')
        help = "f(x) = a0 * tan(x * a1*a2/a3)  x vendrá expresado en radianes."
        notes = ''' tan(x) = sen(x)/cos(x) ||
                    (x) vendrá expresado en grados <=> a1 ó a2 = pi (3.1416) y a3 = 180 (grados). \n
                    Como tiene infinitas raices solo se calculan las cinco más cercanas al punto (x). \n ||
                '''
        notes2 = '180 grados son pi radianes = {} \n'.format(np.pi)
        notes3 = 'y 90 grados son pi/2 radianes = {}. '.format(np.pi/2)
        notes = notes + notes2 + notes3
        # Parameters
        x = self.x
        a0 = self.a0
        a1 = self.a1
        a2 = self.a2
        a3 = self.a3

        # Expresión de la formula numerica
        formula = 'f(x) = {:.2f}·tan (x·{:.2f}·{:.2f}/{:.2f})'.format(a0, a1, a2, a3)

        # Calculo de la funcion

        def f(x, a0, a1, a2, a3):
            return a0*np.tan(x * a1 * a2 / a3)

        # Núcleo funcion en radianes

        def nucleo_rd(x, a0, a1, a2, a3):
            return x * a1 * a2 / a3

        # Núcleo origen de la función

        def nucleo_origen(xrd, a0, a1, a2, a3):
            return xrd * a3 / (a1 * a2)

        y = round(f(x, a0, a1, a2, a3), 6)  # Valor y para la x dada
        x_rd = nucleo_rd(x, a0, a1, a2, a3)  # valor del nucleo en radianes
        x_gr = x_rd*90/np.pi  # valor del nucleo en grados
        # rastreo print('Núcleo: radianes: {:.2f}, grados: {:.2f}'. format(x_rd, x_gr))

        # Dataset (x, y)

        minimo = nucleo_origen(x_rd - 0.49*np.pi, a0, a1, a2, a3)
        maximo = nucleo_origen(x_rd + 0.49*np.pi, a0, a1, a2, a3)
        step = nucleo_origen(np.pi/120, a0, a1, a2, a3)  # saltos de pi/xx grados
        puntos = round((maximo-minimo)/step)
        # rastreo print('minimo: ', minimo)
        # rastreo print('maximo: ', maximo)
        # rastreo print('step: ', step)
        # rastreo print('puntos: ', puntos)

        xy_df = pd.DataFrame([[np.round(l_x, 6), np.round(f(l_x, a0, a1, a2, a3), 6)]
                              for l_x in np.arange(minimo, maximo, step)], columns=['x', 'y'])
        # xy_df = xy_df.replace([np.inf, -np.inf, np.nan], 0)

        # Dataset (raices) ++++ Calculo de raices entorno al punto x +++
        # Periodo en el que está la x
        periodo = round(x_gr/90)
        # Multiplos de pi/2, 90 grados
        raicesprobables = list(
            map(lambda key: int(key*90), range(periodo-5, periodo+6, 1)))
        # rastreo print('posibles raices ', raicesprobables)
        # Para el seno filtro por los multiplos de pi, 180 grados impares
        # raices en grados
        raices_gr = list(
            filter(lambda key: (key/90) % 2 == 0, raicesprobables))
        # rastreo print('raices_gr ', raices_gr)
        # raices en radianes
        raices_rd = list(
            map(lambda key: key * np.pi / 180, raices_gr))
        # rastreo print('raices_rd ', raices_rd)
        # raices en la unidad de la X de entrada
        raices_origen = list(
            map(lambda key: nucleo_origen(key, a0, a1, a2, a3), raices_rd))
        # rastreo print('raices_origen ', raices_origen)

        xyroots_df = pd.DataFrame([[np.round(l_x, 6), np.round((l_x * 0), 6)]
                                   for l_x in raices_origen], columns=['x', 'y'])
        # rastreo print('roots:', xyroots_df)
        debugging('  @FItan  - END ')
        return(help, formula, notes, x, y, xy_df, xyroots_df)


# Recta


    def FIrecta(self):

        help = 'f(x) = a0 + a1·x, recta de pendiente a1'
        notes = """Se define como ecuación afín.\n\
        Se define como ecuación lineal <=> (si y sólo si) a0 = 0\n\
        Todas las rectas  con a0 = 0 pasan por el origen de coordenadas (0,0)."""

        x = self.x
        a0 = self.a0
        a1 = self.a1

        # Expresión de la formula numerica
        formula = 'f(x) = {:.2f} + {:.2f}·x'.format(self.a0, self.a1)

        # Calculo de la funcion

        def f(x, a0, a1):
            return a0 + a1 * x

        # Valor y para la x dada
        y = round(f(x, a0, a1), 6)
        # Dataset (x, y)
        minimo = round(x) - 50
        maximo = round(x) + 50
        xy_df = pd.DataFrame([[np.round(l_x, 2), np.round(f(l_x, a0, a1), 2)]
                              for l_x in np.arange(minimo, maximo, 3)], columns=['x', 'y'])
        #xy_df = xy_df.replace([np.inf, -np.inf, np.nan], 0)
        # Dataset (raices)
        x1 = round(-a0/a1, 6)
        roots = [x1]
        xyroots_df = pd.DataFrame([[np.round(l_x, 2), np.round((l_x * 0), 2)]
                                   for l_x in np.array(roots)], columns=['x', 'y'])

        return(help, formula, notes, x, y, xy_df, xyroots_df)


# Polinomio

    def FIpolinomio(self):
        help = 'f(x) = a5 x^5 + a4 x^4 + a3 x^3 + a2 x^2 + a1 x^1 + a0 '

        # Parameters
        x = self.x
        a0 = self.a0
        a1 = self.a1
        a2 = self.a2
        a3 = self.a3
        a4 = self.a4
        a5 = self.a5

        # Calculo de la funcion
        po = [a5, a4, a3, a2, a1, a0]

        def f(x, po):
            return np.polyval(po, x)

        # Valor y para la x dada
        y = round(f(x, po), 6)
        # Dataset (x, y)
        minimo = round(x) - 50
        maximo = round(x) + 50
        xy_df = pd.DataFrame([[np.round(l_x, 2), np.round(f(l_x, po), 2)]
                              for l_x in np.arange(minimo, maximo, 3)], columns=['x', 'y'])
        #xy_df = xy_df.replace([np.inf, -np.inf, np.nan], 0)
        # Dataset (raices)
        x_raices = np.roots(po)
        roots = [x_raices]
        xyroots_df = pd.DataFrame([[np.round(l_x, 2), np.round((l_x * 0), 2)]
                                   for l_x in np.array(roots)], columns=['x', 'y'])
        # Expresión de la formula numerica
        txt_pol = 'y(x) = '
        grado = 0
        indice = 0
        for i in po:
            if i != 0:
                txt_pol += ' + ' + str(i) + 'x^' + str(len(po) - indice - 1)
                if (len(po) - indice - 1) > grado:
                    grado = len(po) - indice - 1
            indice += 1
        formula = txt_pol

        # Expresión para las notes
        if txt_pol == 'y(x) = ':
            txt_pol = ' y(x) = 0'
        salida = 'El polinomio tiene  ' + str(grado) + ' raices:\n'
        a = 0
        for i in x_raices:
            a += 1
            salida = salida + 'x'+str(a) + ' =' + str(round(i, 2)) + '  \\  '

        salida = 'Polinomio de grado ' + str(grado)+': \n' + txt_pol + '\n' + salida
        notes = salida

        return(help, formula, notes, x, y, xy_df, xyroots_df)

# Ecuación de segundo grado

    def FIe2g(self):
        help = 'f(x) = a2·x^2 + a1·x^1 + a0 , Grado 2, Ecuación de 2º grado '
        notes = ''
        formula = 'f(x) = {:.2f}·x^2 + {:.2f}·x + {:.2f} '.format(self.a2, self.a1, self.a0)
        x = self.x
        a = self.a2
        b = self.a1
        c = self.a0
        y = self.a0 + self.a1*self.x + self.a2 * self.x * self.x

        x1 = 0+0j
        x2 = 0+0j
        comentario = ''
        d = b*b-4*a*c
        if a != 0:
            x1 = (-b + csqrt(d)) / (2 * a)
            x2 = (-b - csqrt(d)) / (2 * a)
            if x1.imag != 0:
                comentario = ('COMPLEJAS')
            else:
                comentario = ('REALES')
            salida = ('   x1 = ' + str(np.round(x1, 2)) + '\n   x2 = ' + str(np.round(x2, 2)) +
                      ' \n   Ecuación cuadrática (y = '+str(np.round(c)) + ' + ' + str(np.round(b)) + ' x + ' + str(np.round(a)) + ' x^2) tiene dos soluciones ' + comentario)
        else:
            if b != 0:
                x1 = -c/b
                salida = ('   x1 = ' + str(round(x1, 2)) +
                          ' \n   Ecuación lineal (y = '+str(c) + ' + ' + str(b)+' x) tiene una solución REAL')
            else:
                salida = ('   Para todo valor de x :   y = {} \n   Recta paralela al eje y'.format(str(c)))
        notes = notes + salida

        minimo = round(self.x) - 50
        maximo = round(self.x) + 50
        xy_df = pd.DataFrame([[l_x, np.round((self.a0 + self.a1 * l_x + self.a2 * l_x * l_x), 2)]
                              for l_x in np.arange(minimo, maximo, 3)], columns=['x', 'y'])

        roots = [x1, x2]
        xyroots_df = pd.DataFrame([[np.round(l_x, 2), np.round((self.a0 + self.a1 * l_x + self.a2 * l_x * l_x), 2)]
                                   for l_x in np.array(roots)], columns=['x', 'y'])
        # v_x = np.array(x_y_df['x'].tolist())
        # v_y = np.array(x_y_df['y'].tolist())

        return(help, formula, notes, x, y, xy_df, xyroots_df)


def grafica(funcion):
    """ Grafica para ser representada """
    fig = Figure()
    ax = fig.add_subplot(111)

    # Valores iniciales de la gráfica
    titulo = funcion[1]  # formula
    x = funcion[3]
    y = funcion[4]
    xy_df = funcion[5]
    xx = np.array(xy_df['x'].tolist()).real
    yy = np.array(xy_df['y'].tolist()).real
    xyroots_df = funcion[6]
    xxr = np.array(xyroots_df['x'].tolist()).real
    yyr = np.array(xyroots_df['y'].tolist()).real

    # Dibujo de la función, sus raices y punto (x, y)
    ax.plot(xx.real, yy.real, '-', label='f(x)')
    ax.plot(xxr.real, yyr.real, 'ro', label='roots')
    ax.plot(x.real, y.real, 'bo', label='(x,y)')

    # Dibujamos las partes del grafico
    ax.grid(True)
    ax.grid(color='0.5', linestyle='--', linewidth=1)
    # Trazo una línea vertical en la coordenada x=0 e y= 0  color rojo (r)
    # y con trazo punteado
    ax.axvline(0, color='r', ls="dotted")
    ax.axhline(0, color='r', ls="dotted")
    ax.set_xlabel('eje X')
    ax.set_ylabel('eje Y')
    ax.set_title(titulo)
    # self.ax.text(2, 0.6, r'$\frac{\sin(x)}{x}$', fontsize=20)
    # self.ax.text(13, 0.2, r'$\sin(x) \cdot e^{-x}$', fontsize=16)
    # Representar las coordenadas en el plot
    xy_texto = "(x,y) = ({0:.2f}, {1:.2f})".format(x, y)
    ax.annotate(xy_texto, xy=(x, y), xytext=(x, y), ha='center', va='bottom', color='blue',
                bbox=dict(boxstyle='round,pad=0.2', fc='yellow', alpha=0.3),
                arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.95',
                                color='b'))
    """
    roots_texto = "roots = {0:.2f}, {1:.2f}".format(x, y)
    ax.annotate(roots_texto, xy=(x, y), xytext=(x*1.05, y*1.05),
                textcoords='offset points', ha='center', va='bottom', color='red',
                bbox=dict(boxstyle='round,pad=0.2', fc='gray', alpha=0.3))
    """
    # Texto en la gráfica en coordenadas (x,y)
    ax.legend()

    return fig


""" Módulo para probar las funciones """


def main():
    """ Prueba de la función FIfun """
    def Prueba_FIfun():
        # help (FIfun)
        """
        print(FIfun_listas()[0])
        print(FIfun_listas()[1])
        print(FIfun_listas()[2])
        print(FIfun_listas()[3])
        """

        f = FIfun(0, '', 0, 0, 0, 0, 0, 0, False)
        f.x = +0
        f.funcion = 'FIpolinomio'  # Este es el único valor necesario
        f.a0 = 1
        f.a1 = 1
        f.a2 = sp.pi  # valor de pi para x en grados
        f.a3 = 180   # 180 grados para x en grados
        f.a4 = 1
        f.a5 = 1

        print("\033[1;31m \nComienza la prueba de función.....: {} ".format(
            f.funcion))  # Funcion color rojo y negrita
        # print("\033[1;34m \nAyuda....: {}\n ".format(f.FIfuncion()[0]))  # Ayuda, color azul y negrita
        print("\033[m")  # reestablecer el color normal
        print('\nSalida, operacion....: {}'.format(f.FIfuncion()[1]))  # Salida, operacion
        print('\nPara x =', f.x, ' y = ', f.FIfuncion()[4])  # el y para el x dado
        # print(f.FIfuncion()[4]) # Imprime todos los valores x
        # print(f.FIfuncion()[5]) # Imprime todos los valores y
        xy_df = f.FIfuncion()[5]  # asigna la salida a un dataframe

        print('\nTipo datos: ', type(xy_df))
        print('\nshape del dataframe: ', xy_df.shape)
        print(xy_df.head())  # Imprime los primeros cinco valores del df
        print(xy_df.describe())  # Estadisticas de cada columna del df media, desviacion standard,..
        # print(df_xy) # Todos los valores del df

        """
        # Para resultados en ventana de caja de texto
        sb = tk.Scrollbar(root)
        Cajatexto = tk.Text(root, wrap='word',borderwidth=3,
                            relief="sunken", height=8) # width=80,height=8
        Cajatexto.pack(side='left', fill='both')
        sb.pack(side='right', fill='y')
        sb.config(command=Cajatexto.yview)
        Cajatexto.config(yscrollcommand=sb.set)
        Cajatexto.insert('end', xy_df.describe())
        Cajatexto.insert('end', '\n')
        Cajatexto.insert('end', xy_df.head())
        """
        return f

    def pruebaplot_FIfun(f):
        root = tk.Tk()
        root.title("My Grafic")
        """  ******  """
        # fig = create_figure()
        fig = grafica(f.FIfuncion())

        """  ******  """
        canvas = FigureCanvasTkAgg(fig, master=root)
        canvas.draw()
        canvas.get_tk_widget().pack(side="top", fill="both", expand=1)
        toolbar = NavigationToolbar2Tk(canvas, root)
        toolbar.update()
        canvas._tkcanvas.pack(side="top", fill="both", expand=1)
        root.mainloop()

    # root = tk.Tk()
    f = Prueba_FIfun()
    pruebaplot_FIfun(f)
    # root.mainloop()


if __name__ == '__main__':
    # main()
    headline(' Prueba de funcion ', '=.=')
    main()
