# -*- coding: iso-8859-1 -*-
from __future__ import division # 1/2 == .5 (par defaut, 1/2 == 0)
from __future__ import with_statement

##------------------------------------------#######
#                   Satistiques                   #
##------------------------------------------#######
#    WxGeometrie
#    Dynamic geometry, graph plotter, and more for french mathematic teachers.
#    Copyright (C) 2005-2007  Nicolas Pourcelot
#
#    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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

# version unicode

from API import *
from GUI import *
from experience import *


class Classe(tuple):
    lien = None

    def milieu(self):       return float(sum(self))/len(self)

    def lier(self, lien):
        self.lien = lien
        return self

    def __str__(self):      return "[%s,%s[" % (self[0], self[-1])
    def __repr__(self):     return self.__str__()
    def __unicode__(self):     return unicode2(self.__str__())
    def __int__(self):      return int(self.milieu())

    def __add__(self, y):   return self.milieu() + y
    def __mul__(self, y):   return self.milieu()*y
    def __div__(self, y):   return self.milieu()/y
    def __rdiv__(self, y):  return y/self.milieu()
    def __truediv__(self, y): return self.__div__(y)
    def __rtruediv__(self, y): return self.__rdiv__(y)
    def __neg__(self):   return -self.milieu()
    def __sub__(self, y):   return self.milieu() + (-y)
    def __rsub__(self, y):  return - self.milieu() + y
    def __pow__(self, y):   return self.milieu()**y
    def __rpow__(self):     return y**self.milieu()
    def __abs__(self):      return abs(self.milieu())
    def __eq__(self, y):    return self.milieu() == y
    def __ne__(self, y):   return not self.milieu() == y
    def __gt__(self, y):    return self.milieu() > y
    def __ge__(self, y):    return self.milieu() > y or self.milieu() == y
    def __lt__(self, y):    return self.milieu() < y
    def __le__(self, y):    return self.milieu() < y or self.milieu() == y
    def __nonzero__(self):  return self.milieu() <> 0

    def effectif(self):
        return float(sum([self.lien.valeurs[valeur] for valeur in self.lien.liste_valeurs() if self[0] <= valeur < self[1]]))

    def amplitude(self):
        return self[1] - self[0]

    def densite(self):
        return self.effectif()/self.amplitude()

    __radd__ = __add__; __rmul__ = __mul__
    __float__ = milieu



class StatMenuBar(MenuBar):
    def __init__(self, panel):
        MenuBar.__init__(self, panel)

        self.ajouter("Fichier", ["nouveau"], ["ouvrir"], ["ouvrir ici"], ["enregistrer"], ["enregistrer_sous"], ["exporter"], None, ["mise en page"], ["imprimer"], [u"presse-papier"], None, ["proprietes"], None, self.panel.doc_ouverts, None, ["fermer"], ["quitter"])
        self.ajouter("Editer", ["annuler"], ["refaire"], ["modifier"], ["supprimer"])
        self.ajouter("creer")
        self.ajouter("Affichage", ["onglet"], None, ["repere"], ["quadrillage"], ["orthonorme"], None, ["fenetre"], ["zoomer"], ["dezoomer"], ["orthonormaliser"])
        self.ajouter("Outils", [u"Expérience", u"Simuler une expérience.", "Alt+Ctrl+E", self.panel.creer_experience], [u"Lancers de dés", u"Simuler des lancers d'un ou de plusieurs dés.", "Ctrl+Shift+D", self.panel.creer_lancer_des], [u"Sondage", u"Simuler un sondage simple.", "Ctrl+Shift+S", self.panel.creer_sondage], None, ["options"])
##        self.ajouter(u"Avancé", [u"historique"], [u"securise"], [u"ligne_commande"], [u"debug"])
        self.ajouter(u"avance1")
        self.ajouter("?")




class Statistiques(Panel_API_graphique):

    __titre__ = u"Statistiques" # Donner un titre a chaque module

    def __init__(self, parent):


        self.couleurs = "bgrmcy"

        self.graphs = [u"diagramme en barres", u"diagramme en batons", u"histogramme", u"effectifs cumulés croissants", u"effectifs cumulés décroissants", u"diagramme en bandes", u"diagramme circulaire", u"diagramme semi-circulaire", u"diagramme en boite"]

        self.valeurs = {}
        self.classes = []
        self.valeur_unite = None
        self.legende_x = "" # axe des abscisses
        self.legende_y = "" # axe des ordonnees
        self.legende_u = "" # unite d'aire (histogramme)
        self.mode_resultats = ["effectifs", "frequences"][0]
        self.graph = 0
        self.intervalle_confiance = None

        #################
        #  Debugage
        #################
        #self.ajouter_valeurs(55, 67, 68, 72, 72, 72.5, 74, 75.5, 78, 78.5, 79, 81.5, 86, 91, 94.5)
        #self.ajouter_classes([40,60],[60,70],[70,75],[75,90],[90, 120])
        #self.legende_x  = "poids des objets (g)"
        #self.legende_y = ""
        #self.legende_u = "objet"
        #################



        Panel_API_graphique.__init__(self, parent)



        self.entrees = wx.BoxSizer(wx.VERTICAL)

        self.entrees.Add(wx.StaticText(self, -1, u" Mode graphique :"), 0, wx.ALL,5)

        self.choix = wx.Choice(self, -1, (100, 50), choices = self.graphs)
        self.choix.SetSelection(self.graph)
        self.Bind(wx.EVT_CHOICE, self.EvtChoice, self.choix)
        self.entrees.Add(self.choix, 0, wx.ALL, 5)

        #self.entrees.Add(wx.StaticText(self, -1, ""))

        box = wx.StaticBox(self, -1, u"Mesures")
        bsizer = wx.StaticBoxSizer(box, wx.VERTICAL)

        #self.entrees.Add(bsizer, 1, wx.EXPAND|wx.ALL, 5)
        self.entrees.Add(bsizer, 0, wx.ALL, 5)

        self._effectif_total = wx.StaticText(self, -1, u" Effectif total:")
        self._moyenne = wx.StaticText(self, -1, u" Moyenne:")
        self._mediane = wx.StaticText(self, -1, u" Médiane:")
        self._mode = wx.StaticText(self, -1, u" Mode:")
        self._etendue = wx.StaticText(self, -1, u" Etendue:")
        self._variance = wx.StaticText(self, -1, u" Variance:")
        self._ecart_type = wx.StaticText(self, -1, u" Ecart-type:" + 30*" ")

        bsizer.Add(self._effectif_total, 0, wx.TOP|wx.LEFT, 9)
        bsizer.Add(self._moyenne, 0, wx.TOP|wx.LEFT, 9)
        bsizer.Add(self._mediane, 0, wx.TOP|wx.LEFT, 9)
        bsizer.Add(self._mode, 0, wx.TOP|wx.LEFT, 9)
        bsizer.Add(self._etendue, 0, wx.TOP|wx.LEFT, 9)
        bsizer.Add(self._variance, 0, wx.TOP|wx.LEFT, 9)
        bsizer.Add(self._ecart_type, 0, wx.ALL, 9)

        haut = wx.BoxSizer(wx.HORIZONTAL)
        haut.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
        haut.Add(self.entrees, 0, wx.ALL, 5)




        rect = wx.GridBagSizer(8, 5)

        rect.Add(wx.StaticText(self, -1, u"Effectifs et valeurs associées:"), (1, 0), flag=wx.ALIGN_CENTER)

        self.txt_valeurs = wx.TextCtrl(self, -1, "", size=(500, -1), style=wx.TE_PROCESS_ENTER)
        self.txt_valeurs.SetToolTipString(u"Valeurs simples:\n8 8 9 12 17 18\nEffectifs et valeurs:\n2*7 14*8 5*9 1*10\nClasses et effectifs:\n17*[0;10[ 24*[10;20[")
        self.txt_valeurs.Bind(wx.EVT_CHAR, self.EvtChar)
        rect.Add(self.txt_valeurs, (1, 1), flag=wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)


        rect.Add(wx.StaticText(self, -1, u"Regroupement par classes:"), (2, 0), flag=wx.ALIGN_CENTER_VERTICAL)
        self.txt_classes = wx.TextCtrl(self, -1, "", size=(500, -1), style=wx.TE_PROCESS_ENTER)
        self.txt_classes.SetToolTipString(u"Exemple:\n[0;10[ [10;20[ [20;30[")
        self.txt_classes.Bind(wx.EVT_CHAR, self.EvtChar)
        rect.Add(self.txt_classes, (2, 1), flag=wx.EXPAND|wx.ALIGN_CENTER_VERTICAL)

        bas = wx.BoxSizer(wx.VERTICAL)
        bas.Add(rect, 1, wx.GROW|wx.LEFT, 15)

        box = wx.StaticBox(self, -1, u"Légendes")
        last = wx.StaticBoxSizer(box, wx.HORIZONTAL)

        last.Add(wx.StaticText(self, -1, u"Abscisses:"), 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL, 5)
        self.txt_x = wx.TextCtrl(self, -1, self.legende_x, size=(100, -1), style=wx.TE_PROCESS_ENTER)
        self.txt_x.Bind(wx.EVT_CHAR, self.EvtChar)
        last.Add(self.txt_x, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 7)

        last.Add(wx.StaticText(self, -1, u"Ordonnées:"), 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL, 5)
        self.txt_y = wx.TextCtrl(self, -1, self.legende_y, size=(100, -1), style=wx.TE_PROCESS_ENTER)
        self.txt_y.Bind(wx.EVT_CHAR, self.EvtChar)
        last.Add(self.txt_y, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 7)

        last.Add(wx.StaticText(self, -1, u"Unité d'aire:"), 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL, 5)
        self.txt_u = wx.TextCtrl(self, -1, self.legende_u, size=(80, -1), style=wx.TE_PROCESS_ENTER)
        self.txt_u.SetToolTipString(u"Pour les histogrammes.\nCorrespond à l'unité de l'objet étudié.\nExemples:\npersonnes, ampoules, %, $, ...")
        self.txt_u.Bind(wx.EVT_CHAR, self.EvtChar)
        last.Add(self.txt_u, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 7)

        last.Add(wx.StaticText(self, -1, u"Valeur:"), 0, wx.LEFT|wx.ALIGN_CENTER_VERTICAL, 5)
        self.txt_v = wx.TextCtrl(self, -1, self.valeur_unite or "", size=(60, -1), style=wx.TE_PROCESS_ENTER)
        self.txt_v.SetToolTipString(u"Permet de choisir manuellement l'unité d'aire d'un histogramme.")
        self.txt_v.Bind(wx.EVT_CHAR, self.EvtChar)
        last.Add(self.txt_v, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 7)

        bas.Add(last, 0, wx.ALL, 10)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(haut, 1, wx.GROW)
        self.sizer.Add(bas, 0)
        self.finaliser(contenu = self.sizer)



    def EvtChoice(self, event):
        self.graph = event.GetSelection()
        self.actualiser()


    def EvtChar(self, event):
        code = event.GetKeyCode()

        if code in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
            self.actualiser()
        else:
            event.Skip()



    def actualiser(self, afficher = True):
        try:
            self.legende_x = self.txt_x.GetValue()
            self.legende_y = self.txt_y.GetValue()
            self.legende_u = self.txt_u.GetValue()
            valeur_unite = self.txt_v.GetValue().strip()
            if valeur_unite:
                self.valeur_unite = securite.eval_safe(valeur_unite)
            else:
                self.valeur_unite = None

            self.classes = []
            self.valeurs = {}

            #valeurs = regexp("[ ]*[*][ ]*", self.txt_valeurs.GetValue(), "'*'") # on supprime les espaces autour des '*'
            #valeurs = regexp("[[][^]]*for[^]]*in[^]]*[]]", self.txt_valeurs.GetValue(), "x.replace(' ','|')") # une expression du style "[i for i in range(7)]" ne doit pas être découpée au niveau des espaces.

            # La chaine va être découpée au niveau des espaces ; on supprime donc les espaces inutiles
            valeurs = regsub("[ ]*[*][ ]*", self.txt_valeurs.GetValue(), "*") # on supprime les espaces autour des '*'
            valeurs = regsub("[[][^]]*for[^]]*in[^]]*[]]", self.txt_valeurs.GetValue(), lambda s:s.replace(' ','|')) # une expression du style "[i for i in range(7)]" ne doit pas être découpée au niveau des espaces.
            classes = self.txt_classes.GetValue()


            for classe in advanced_split(classes.replace(";", ","), " ", symbols = "({})"):
                if classe.endswith("["):
                    classe = classe[:-1] + "]"
                self.ajouter_classes(Classe(securite.eval_restricted(classe)).lier(self))

            for val in advanced_split(valeurs.replace(";", ","), " ", symbols = "({})"):
                if val.endswith("["):
                    val = val[:-1] + "]"

                if re.match("[[][^]]*for[^]]*in[^]]*[]]", val):
                    val = securite.eval_restricted(val.replace('|',' '))
                    for v in val:
                        if type(v) in (list, tuple): # syntaxe style "[(3,i) for i in range(7)]" où 3 est l'effectif
                            self.ajouter_valeur(v[1], v[0])
                        else:
                            self.ajouter_valeur(v) # syntaxe style "[i for i in range(7)]"
                else:
                    val = [securite.eval_restricted(x) for x in advanced_split(val, "*")]
                    val.reverse()
                    self.ajouter_valeur(*val)

            # par defaut, si toutes les valeurs entrees sont des classes, le decoupage en classes suit les classes entrees.
            if not self.classes and not [x for x in self.valeurs.keys() if not isinstance(x, Classe)]:
                self.classes = self.valeurs.keys()

            self.calculer()
            if afficher:
                self.affiche()
            self.canvas.message(u"Graphique fini.")

        except:
            self.canvas.message(u"Impossible de construire le graphique.")
            print_error()


    def calculer(self):
        e = self.effectif_total()
        if e == int(e):  e = int(e)
        self._effectif_total.SetLabel(u" Effectif total: %s" %e)
        self._moyenne.SetLabel(u" Moyenne: %s" %self.moyenne())
        self._mediane.SetLabel(u" Médiane: " + str(self.mediane()))
        self._mode.SetLabel(u" Mode: %s" %self.mode())
        self._etendue.SetLabel(u" Etendue: %s" %self.etendue())
        self._variance.SetLabel(u" Variance: %s" %self.variance())
        self._ecart_type.SetLabel(u" Ecart-type: %s" %self.ecart_type())

    # Bizarrement, %s ne marche pas ici, alors que ça marche la ligne de dessous... (?!?)
    def ajouter_valeurs(self, *valeurs):
        for val in valeurs:
            self.ajouter_valeur(val)


    def ajouter_valeur(self, valeur, effectif = 1):
        if type(valeur) in (list, tuple):
            valeur = Classe(valeur).lier(self)
        if self.valeurs.has_key(valeur):
            self.valeurs[valeur] += effectif
        else:
            self.valeurs[valeur] = float(effectif)

        #if self.valeurs[valeur] == 0:
        #    self.valeurs.pop(valeur)




    def ajouter_classes(self, *classes):
        self.classes += classes
        self.classes.sort()


    def liste_valeurs(self):
        valeurs = self.valeurs.keys()
        valeurs.sort()
        return valeurs

    def liste_valeurs_effectifs(self):
        valeurs = self.valeurs.items()
        valeurs.sort()
        return valeurs



    def intervalle_classes(self):
        return min([classe[0] for classe in self.classes]), max([classe[1] for classe in self.classes])


    def experience(self, formule, n, val_possibles = ()):
        u"""Réalise 'n' fois l'expérience décrite par 'formule'.
        Exemple: self.experience('int(6*rand())+1', 100) simule 100 lancers de dés."""

        self.actualiser(False)
        self.ajouter_valeurs(*[eval(formule) for i in xrange(n)])
        for val in val_possibles:
            self.ajouter_valeur(val, 0)
        self.calculer()
        self.affiche()


    def _affiche(self):
        if self.effectif_total() == 0 or (not self.classes and self.graph in (2,3,4)):
            self.canvas.afficher_quadrillage = False
            self.canvas.afficher_axes = False
            return

        self.canvas.afficher_axes = True
        if self.graph == 0:
            self.diagramme_barre()
        if self.graph == 5:
            self.canvas.afficher_quadrillage = False
            self.diagramme_bande()
        if self.graph == 1:
            self.canvas.afficher_quadrillage = False
            self.diagramme_baton(2)
        if self.graph == 6:
            self.canvas.afficher_quadrillage = False
            self.canvas.afficher_axes = False
            self.diagramme_circulaire()
        if self.graph == 7:
            self.canvas.afficher_quadrillage = False
            self.canvas.afficher_axes = False
            self.diagramme_circulaire(180)
        if self.graph == 8:
            self.canvas.afficher_quadrillage = False
            self.diagramme_boite(True)

        # Graphiques utilisant les classes :
        if self.graph == 2:
            self.canvas.afficher_quadrillage = False
            self.histogramme()
        if self.graph == 3:
            self.courbe_effectifs()
        if self.graph == 4:
            self.courbe_effectifs(-1)


    def creer_experience(self, event = None):
        win = ExperienceFrame(self)
        win.Show(True)

    def creer_lancer_des(self, event = None):
        win = LancerDes(self)
        win.Show(True)


    def creer_sondage(self, event = None):
        win = Sondage(self)
        win.Show(True)



##    def __old__legende_x(self):  # A supprimer sans doute
##        legende = ""
##        if self.critere:
##            legende += self.critere
##        if self.population:
##            if legende:
##                legende += " des "
##            legende += self.population + "s"
##        if self.unite:
##            legende += " (%s)" % self.unite
##        return legende


##    def intervalle_confiance(self, n = 1000): # A REVOIR (TRES APPROXIMATIF !)
##        if self.graph == 1:            # EN PARTICULIER, IL FAUDRAIT UNE ECHELLE SUR L'AXE DES ORDONNEES.
##            m = self.moyenne(); f = m/100.
##            e = 200*math.sqrt(f*(1-f)/n)
##            x0 = m - e; x1 = m + e
##            y0 = self.canvas.fenetre[2] + 4*self.canvas.coeff(1)
##            y1 = self.canvas.fenetre[3] - 6*self.canvas.coeff(1)
##            #print x0,x1,y0,y1
##            self.canvas.dessiner_polygone(pylab.array([x0,x0,x1,x1]), pylab.array([y0,y1,y1,y0]), facecolor='y', edgecolor='y',alpha = .3)
##            self.canvas.dessiner_ligne([x0,x0,x1,x1,x0], [y0,y1,y1,y0,y0], 'y', alpha = 1)
##            #self.canvas.axes.set_xlim(self.canvas.fen()[:2])
##            #self.canvas.axes.set_ylim(self.canvas.fen()[2:])
####            self.canvas.affiche()

    def dessiner_intervalle_confiance(self):
        n = self.intervalle_confiance
        if n is None:
            return
        m = self.moyenne(); f = m/100.
        e = 200*math.sqrt(f*(1-f)/n)
        x0 = m - e; x1 = m + e
        y0 = self.canvas.fenetre[2] + 4*self.canvas.coeff(1)
        y1 = self.canvas.fenetre[3] - 6*self.canvas.coeff(1)
        self.canvas.dessiner_polygone(pylab.array([x0,x0,x1,x1]), pylab.array([y0,y1,y1,y0]), facecolor='y', edgecolor='y',alpha = .3)
        self.canvas.dessiner_ligne([x0,x0,x1,x1,x0], [y0,y1,y1,y0,y0], 'y', alpha = 1)

#------------------------------------
#   Differents types de diagrammes.
#------------------------------------




    def histogramme(self):
        u"Construit un histogramme (à ne pas confondre avec le diagramme en barres !)"


        m, M = self.intervalle_classes()
        l = min([classe[1] - classe[0] for classe in self.classes])
        hmax = max([classe.densite() for classe in self.classes])
        #effectif = nchiffres(15.*l*hmax/15) ;


        # premiere estimation (pour prevoir la place a laisser):
        #self.canvas.fen((m - 0.1*(M-m), M + 0.3*(M-m), -0.1*hmax, 1.1*hmax))
        #aire_en_pixels = 1.*l/self.canvas.coeff(0)*h/self.canvas.coeff(1)
        #l_ = sqrt(aire_en_pixels)*self.canvas.coeff(0)

        # reglage definitif de la fenetre d'affichage
        self.canvas.fenetre = m - 0.1*(M-m), M + 0.4*(M-m), -0.1*hmax, 1.1*hmax
        self.canvas.utiliser_repere = False
        self.canvas.liste_axes = (0,)
        self.canvas.origine_axes = (m, 0)
        self.canvas.gradu = (l, 0)
##        self.canvas.clear()

        i = 0
        for classe in self.classes:
            h = classe.densite()
            self.canvas.dessiner_polygone([classe[0], classe[0], classe[1], classe[1]], [0, h, h, 0], self.couleurs[i%len(self.couleurs)])
            i += 1

        self.canvas.dessiner_texte(M + 0.3*(M-m)-5*self.canvas.coeff(0), -18*self.canvas.coeff(1), self.legende_x, ha = "right")

        # Calcul des dimensions du carre donnant l'unite:
        effectif = self.valeur_unite or arrondir(sum([classe.effectif() for classe in self.classes])/20) # l'effectif que represente le carre
        cote = math.sqrt(effectif/(self.canvas.coeff(0)*self.canvas.coeff(1))) # cote du carre en pixels
        l = cote*self.canvas.coeff(0); h = cote*self.canvas.coeff(1)
        x = M + 0.1*(M-m)
        self.canvas.dessiner_polygone([x, x + l, x + l, x], [.5*hmax, .5*hmax, .5*hmax + h, .5*hmax + h], "b")

        if effectif >= 1:   effectif = int(effectif) # pour l'affichage
        legende = str(effectif) + " " + (self.legende_u or "unite")

        if effectif > 1 and not self.legende_u:
            legende += "s"

        self.canvas.dessiner_texte(x, .5*hmax - 15*self.canvas.coeff(1), legende, va = "top")




    def courbe_effectifs(self, mode = 1):
        u"courbe des effectifs cumulés croissants si mode = 1, décroissants si mode = -1."

        valeurs = self.liste_valeurs()


        l = min([classe[1] - classe[0] for classe in self.classes])
        m, M = self.intervalle_classes()
        hmax = self.effectif_total()
        self.canvas.fenetre = m - 0.1*(M-m), M + 0.2*(M-m), -0.1*hmax, 1.1*hmax
        self.canvas.gradu = (l, arrondir(hmax/10))
        self.canvas.utiliser_repere = False
        self.canvas.liste_axes = (0, 1)
        self.canvas.origine_axes = (m, 0)
##        self.canvas.clear()

        for classe in self.classes:
                self.canvas.dessiner_ligne(classe, [sum([self.valeurs[valeur] for valeur in valeurs if mode*valeur <= mode*classe[i]]) for i in (0, 1)])

        dx, dy = self.canvas.dpix2coo(-5, -18)
        self.canvas.dessiner_texte(M + 0.2*(M-m) + dx, dy, self.legende_x, ha = "right")
        dx, dy = self.canvas.dpix2coo(15, -5)
        self.canvas.dessiner_texte(m + dx, 1.1*hmax + dy, self.legende_y or u"effectifs cumulés", va = "top")



    def diagramme_barre(self, ratio = .7):
        u"""Diagramme en barres ; ratio mesure le quotient largeur d'une barre sur largeur maximale possible.
         (nombre décimal entre 0 et 1).
         Essentiellement pertinent pour des séries qualitatives."""

        valeurs = self.liste_valeurs()

        lmax = 100./len(valeurs)
        l = ratio*lmax
        e = .5*(lmax - l)
        hmax = max(self.valeurs.values())
        self.canvas.fenetre = (-10, 110, -.2*hmax, 1.15*hmax)

        self.canvas.gradu = (0, arrondir(hmax/10))
        self.canvas.liste_axes = (0, 1)
        self.canvas.utiliser_repere = False
        self.canvas.origine_axes = (0, 0)
##        self.canvas.clear()

        n = 0
        for valeur in valeurs:
            h = self.valeurs[valeur]
            x0, x1 = (2*n + 1)*e + n*l, (2*n + 1)*e + (n+1)*l
            self.canvas.dessiner_polygone([x0, x0, x1, x1], [0, h, h, 0], self.couleurs[(n - 1)%len(self.couleurs)])
            self.canvas.dessiner_texte((x0 + x1)/2., - 18*self.canvas.coeff(1), str(valeur), ha = "center")
            n += 1

        self.canvas.dessiner_texte(110 - 5*self.canvas.coeff(0), -35*self.canvas.coeff(1), self.legende_x, ha = "right")
        self.canvas.dessiner_texte(15*self.canvas.coeff(0), 1.15*hmax - 5*self.canvas.coeff(1), self.legende_y or "effectifs", va = "top")

        # les donnees sont affichees entre 0 et 100 en abscisse



    def diagramme_baton(self, largeur = 1):
        u"""Diagramme en batons (séries quantitatives discrètes).
        'largeur' est la demi-largeur en pixel."""

        valeurs = self.liste_valeurs()

        m, M = valeurs[0], valeurs[-1]
        if m == M:
            l = 1
            M += 1
            m -= 1
        else:
            l = arrondir((M-m)/20)   # on evite d'avoir des valeurs trop fantaisistes pour le pas !
            m = l*int(m/l) - l


        hmax = max(self.valeurs.values())

        # reglage de la fenetre d'affichage
        self.canvas.fenetre = (m - 0.1*(M-m), M + 0.1*(M-m), -0.1*hmax, 1.1*hmax)
        if int(m) == m: m = int(m) # pour des raisons esthetiques
        #if int(l) == l: l = int(l)
        self.canvas.utiliser_repere = False
        self.canvas.liste_axes = (0, 1)
        self.canvas.origine_axes = (m, 0)
        self.canvas.gradu = (l, arrondir(hmax/10))
##        self.canvas.clear()

        e = largeur*self.canvas.coeff(0)

        i = 0
        for val, eff in self.valeurs.items():
            self.canvas.dessiner_polygone([val - e, val - e, val + e, val + e], [0, eff, eff, 0], self.couleurs[(i - 1)%len(self.couleurs)])
            i+=1


        self.canvas.dessiner_texte(M + 0.1*(M-m) - 5*self.canvas.coeff(0), -18*self.canvas.coeff(1), self.legende_x, ha = "right")
        self.canvas.dessiner_texte(m + 15*self.canvas.coeff(0), 1.1*hmax - 5*self.canvas.coeff(1), self.legende_y or "effectifs", va = "top")

        self.dessiner_intervalle_confiance()

        #self.canvas.affiche()


    def diagramme_bande(self):
        u"""Diagramme en bande."""

        valeurs = self.liste_valeurs()

        l_unite = 100./self.effectif_total()
        n = 0
        x = 0

        self.canvas.fenetre = (-10, 110, -1, 2)
        self.canvas.liste_axes = ()
        self.canvas.gradu = (0, 0)
##        self.canvas.clear()


        for valeur in valeurs:
            l = self.valeurs[valeur]*l_unite
            self.canvas.dessiner_polygone([x, x, x + l, x + l], [0, 1, 1, 0], self.couleurs[(n - 1)%len(self.couleurs)])
            self.canvas.dessiner_texte(x+l/2., - 18*self.canvas.coeff(1), str(valeur),  ha="center")
            n += 1
            x += l



    def diagramme_circulaire(self, angle = 360):

        valeurs = self.liste_valeurs()

        #self.canvas.axes.clear()
        valeurs, effectifs = zip(*self.valeurs.items())

        # petit raffinement pour eviter que 2 couleurs identiques se suivent
        n = len(effectifs)
        l = len(self.couleurs)
        if n%l == 1:
            couleurs =  tuple(((n-1)//l)*self.couleurs + self.couleurs[1])
        else:
            couleurs = tuple(self.couleurs)

        effectifs = angle/360.*pylab.array(effectifs)/sum(effectifs)
        labels = [str(valeur) for valeur in valeurs]
##        self.canvas.rafraichir_axes = True
##        self.canvas.clear()
        self.canvas.axes.pie(effectifs, labels = labels, labeldistance = 1.16, shadow = False, colors = couleurs)
        self.canvas.synchroniser_fenetre() # pour profiter du reglage effectue par matplotlib
        self.canvas.orthonormer() # pour avoir un vrai cercle
        # rafraichir produirait une recursion infinie !
        #self.canvas.draw()


    def diagramme_boite(self, afficher_extrema = True):
        u"Appelé aussi diagramme à moustache."

        med = self.mediane()
        q1 = self.quartile(1)
        q3 = self.quartile(3)
        d1 = self.decile(1)
        d9 = self.decile(9)
        vals = self.liste_valeurs()
        m = vals[0]
        M = vals[-1]
        if str in [type(i) for i in (med, q1, q3, d1, d9)]:
            # self.mediane() ou self.decile() ou... renvoit "calcul impossible."
##            self.canvas.clear()
            return

        self.canvas.utiliser_repere = False
        self.canvas.liste_axes = (0,)
        if int(m) == m: m = int(m) # pour des raisons esthetiques
        self.canvas.origine_axes = (m, 0)
        l = arrondir((M-m)/20)
        self.canvas.gradu = (l, 0)
        if m <> M:
            self.canvas.fenetre = (m - 0.1*(M-m), M + 0.1*(M-m), -0.1, 1.1)
        else:
            self.canvas.fenetre = (m - 0.1, M + 0.1, -0.1, 1.1)
##        self.canvas.clear()


        self.canvas.dessiner_ligne(pylab.array([d1, d1]), pylab.array([.4, .6]), linewidth = 1.5, color = "g")
        self.canvas.dessiner_ligne(pylab.array([d9, d9]), pylab.array([.4, .6]), linewidth = 1.5, color = "g")
        self.canvas.dessiner_ligne(pylab.array([q1, q1]), pylab.array([.2, .8]), linewidth = 1.5, color = "b")
        self.canvas.dessiner_ligne(pylab.array([q3, q3]), pylab.array([.2, .8]), linewidth = 1.5, color = "b")
        self.canvas.dessiner_ligne(pylab.array([med, med]), pylab.array([.2, .8]), linewidth = 1.5, color = "r")
        self.canvas.dessiner_ligne(pylab.array([d1, q1]), pylab.array([.5, .5]), color = "k")
        self.canvas.dessiner_ligne(pylab.array([q3, d9]), pylab.array([.5, .5]), color = "k")
        self.canvas.dessiner_ligne(pylab.array([q3, q1]), pylab.array([.2, .2]), color = "k")
        self.canvas.dessiner_ligne(pylab.array([q3, q1]), pylab.array([.8, .8]), color = "k")
        self.canvas.dessiner_ligne(pylab.array([m, M]), pylab.array([.5, .5]), linestyle = "None", marker = "o", color="k", markerfacecolor = "w")


#------------------------------------------------------------
#   Infos sur la serie.
#   (Mesures de tendance centrales, de dispersion, etc...)
#------------------------------------------------------------


    def effectif_total(self):
        return sum(self.valeurs.values())

    def mode(self):
        if not self.effectif_total():
            return u"Calcul impossible."
        m = max(self.valeurs.values())
        v = [str(key) for key in self.valeurs.keys() if self.valeurs[key] == m]
        if len(v) > 2:
            v = v[:2] + ["..."]
        return " ; ".join(v)

    def moyenne(self):
        try:
            return 1.*sum([eff*val for val, eff in self.valeurs.items()])/self.effectif_total()
        except (ZeroDivisionError, TypeError):
            return u"Calcul impossible."

    def etendue(self):
        try:
            valeurs = self.liste_valeurs()
            return valeurs[-1] - valeurs[0]
        except (TypeError, IndexError):
            return u"Calcul impossible."

    def variance(self):
        try:
            m = self.moyenne()
            if isinstance(m, basestring):
                raise TypeError
            return 1.*sum([eff*(val - m)**2 for val, eff in self.valeurs.items()])/self.effectif_total()
        except (ZeroDivisionError, TypeError):
            return u"Calcul impossible."

    def ecart_type(self):
        v = self.variance()
        if isinstance(v, basestring):
            return u"Calcul impossible."
        return math.sqrt(v)

    def mediane(self):
        u"""Correspond à la 'valeur du milieu' quand on ordonne les données.
        Dans certains cas, en divisant l'effectif par 2, on tombe entre 2 valeurs.
        Dans ce cas, on convient de prendre la demi-somme de ces valeurs.
        (ex: tous les effectifs valent un et l'effectif total impair)

        Une bonne manière de visualiser la médiane pour des effectifs non entiers
        est de tracer un diagramme en bande."""
        old_somme = somme = 0
        old_val = None
        objectif = self.effectif_total()/2
        for val, effectif in self.liste_valeurs_effectifs():
            somme += effectif
            if somme > objectif:
                if old_somme == objectif: # la mediane est a cheval sur 2 valeurs
                    try:
                        return (old_val + val)/2
                    except TypeError:
                        print_error()
                        return u"Calcul impossible."
                return val
            old_val = val
            old_somme = somme





    def tile(self, k = 4, i = 1):
        u"""
        Renvoie la valeur x de la série telle que au moins i/k des données de la série
        soient inférieures ou égales à x.

        Exemple :   tile(4,1) -> premier quartile.
                    tile(10,2) -> deuxième décile.
        """

        somme = 0
        objectif = i/k*self.effectif_total()
        for val, effectif in self.liste_valeurs_effectifs():
            somme += effectif
            if somme >= objectif:
                return val




    def quartile(self, i = 1):
        u"""
        Donne Qi, le ième quartile.
        Qi est le plus petit element de la serie statistique tel qu'au moins
        i*25% des données soient inférieures ou égales à Qi.

        Note: si l'effectif de la série est pair,
        le 2ème quartile ne correspond pas toujours à la médiane."""

        return self.tile(4, i)


    def decile(self, i = 1):
        u"""
        Donne Di, le ième décile.
        Di est le plus petit element de la serie statistique tel qu'au moins
        i*10% des données soient inférieures ou égales à Di.

        Note: si l'effectif de la série est pair,
        le 5ème décile ne correspond pas toujours à la médiane."""

        return self.tile(10, i)



    def centile(self, i = 1):
        u"""
        Donne Ci, le ième centile.
        Ci est le plus petit element de la serie statistique tel qu'au moins
        i% des données soient inférieures ou égales à Ci.

        Note: si l'effectif de la série est pair,
        le 50ème centile ne correspond pas toujours à la médiane."""

        return self.tile(100, i)






    #def effectifs_cumules_croissants(self):
    #    vals = {}
    #    effectif = 0
    #    for valeur in self.liste_valeurs():
    #        effectif += self.valeurs[valeur]
    #        vals[valeur] = effectif
    #    return vals


    def _sauvegarder(self, fgeo):
        Panel_API_graphique._sauvegarder(self, fgeo)
        fgeo.contenu["Diagramme"] = [{
            "serie" : [{"valeurs" : [self.txt_valeurs.GetValue()], "classes" : [self.txt_classes.GetValue()]}],
            "legende" : [{"x" : [self.legende_x], "y" : [self.legende_y], "unite_aire" : [self.legende_u], "valeur_unite" : [repr(self.valeur_unite)]}],
            "mode_graphique" : [repr(self.graph)]
            }]



    def _ouvrir(self, fgeo):
        Panel_API_graphique._ouvrir(self, fgeo)
        if fgeo.contenu.has_key("Diagramme"):
            diagramme = fgeo.contenu["Diagramme"][0]
            serie = diagramme["serie"][0]
            valeurs = serie["valeurs"][0]
            classes = serie["classes"][0]
            legende = diagramme["legende"][0]
            x = legende["x"][0]
            y = legende["y"][0]
            unite_aire = legende["unite_aire"][0]
            #~ valeur_unite = securite.eval_safe(legende["valeur_unite"][0])
            valeur_unite = legende["valeur_unite"][0]
            mode_graphique = securite.eval_safe(diagramme["mode_graphique"][0])

            self.txt_x.SetValue(x)
            self.txt_y.SetValue(y)
            self.txt_u.SetValue(unite_aire)
            self.txt_v.SetValue(valeur_unite or "")
            self.txt_valeurs.SetValue(valeurs)
            self.txt_classes.SetValue(classes)
            self.choix.SetSelection(mode_graphique)
            self.graph = mode_graphique

        self.actualiser()
