modified MultipleChartViews to instantiate only one chart.

added chart on index view
This commit is contained in:
artus40
2017-08-19 18:00:49 +02:00
parent 6d2ce7d7bc
commit 0bf2d9ff79
9 changed files with 319 additions and 301 deletions

View File

@@ -1,22 +1,48 @@
import datetime
from django.db.models.functions.datetime import ExtractMonth
from django.db.models import (Field, NullBooleanField,
Count,
)
from graphos.sources.simple import SimpleDataSource
from graphos.renderers import gchart
from notes.models import Sujet
NOM_MOIS = {
1: "Janvier",
2: "Février",
3: "Mars",
4: "Avril",
5: "Mai",
6: "Juin",
7: "Juillet",
8: "Août",
9: "Septembre",
10: "Octobre",
11: "Novembre",
12: "Décembre"
}
# Defines generic labels for common fields
LABELS = {
NullBooleanField: {True: "Oui", False: "Non", None:"Ne sait pas"},
NullBooleanField: {True: "Oui", False: "Non", None: "Ne sait pas"},
}
# TODO: Retrieve charts data from cache to avoid recalculating on each request
class CachedDataSource:
pass
# TODO: Clean up...
class FieldValuesCountDataSource(SimpleDataSource):
""" Generates data from a limited set of choices.
"""
def __init__(self, queryset, field, labels=None, excluded=[]):
def __init__(self, queryset, field, labels=None, excluded=None):
self.queryset = queryset
self.field_name = field.name
self.excluded = excluded
self.excluded = excluded or []
if not labels:
if field.__class__ in LABELS:
labels = LABELS[field.__class__]
@@ -52,38 +78,39 @@ class PieWrapper(gchart.PieChart):
- a data object and title
"""
height=400
width=800
options = {
'is3D': False,
'pieSliceText': 'value',
'legend': {'position': 'labeled', 'maxLines': 3, 'textStyle': {'fontSize': 16, }},
}
height = 400
width = 800
def __init__(self,
queryset=None, field=None,
data=None, title=None,
null_values=[],
null_values=None,
**kwargs):
if not data:
if data is None:
if not isinstance(field, Field):
raise TypeError(field, 'must be a child of django.models.db.fields.Field !')
if not queryset:
raise TypeError("You must give either a queryset and field or data")
data_source = FieldValuesCountDataSource(
queryset, field,
excluded=null_values,
labels=None #TODO: How to pass in labels ??
labels=None # TODO: How to pass in labels ??
)
else:
data_source = SimpleDataSource(data=data)
super().__init__(
data_source,
options={
'title': getattr(field, 'verbose_name', title),
'is3D': True,
'pieSliceText': 'value',
'legend': {'position': 'labeled', 'maxLines': 3, 'textStyle': {'fontSize': 16,}},
},
width=kwargs.get('width', self.width),
height=kwargs.get('height', self.height),
)
options = self.options.copy()
options.update(
title=getattr(field, 'verbose_name', title)
)
super().__init__(data_source,
options=options,
width=kwargs.get('width', self.width),
height=kwargs.get('height', self.height),
)
def get_js_template(self):
return "statistiques/gchart/pie_chart.html"
@@ -94,4 +121,176 @@ class PieWrapper(gchart.PieChart):
class ColumnWrapper(gchart.ColumnChart):
pass
options = {
'is3D': False,
'legend': {'position': 'labeled', 'maxLines': 3, 'textStyle': {'fontSize': 16, }},
'title': 'test',
}
def __init__(self, *args, **kwargs):
kwargs.update(options=self.options.copy())
super().__init__(*args, **kwargs)
class DonneeGeneraleChart(gchart.BarChart):
def __init__(self, maraudes=None, rencontres=None, sujets=None):
data = [("...", "Soirée", "Journée", { 'role': 'annotation'})]
data += [("Maraudes", maraudes[2].count(), maraudes[1].count(), maraudes[0].count())]
data += [("Rencontres", rencontres[2].count(), rencontres[1].count(), rencontres[0].count())]
if sujets:
data += [("Nouvelles rencontres", sujets[2].count(), sujets[1].count(), sujets[0].count())]
super().__init__(SimpleDataSource(data), options={'title': 'Données générales', 'isStacked': 'percent'})
def generate_piechart_for_field(field):
""" Returns a PieWrapper subclass working with a fixed field """
class FieldChart(PieWrapper):
def __init__(self, queryset):
super().__init__(queryset, field)
return FieldChart
class GenrePieChart(PieWrapper):
def __init__(self, queryset):
queryset = Sujet.objects.filter(pk__in=queryset.values_list('pk'))
super().__init__(queryset, Sujet._meta.get_field('genre'))
class AgePieChart(PieWrapper):
""" Chart for 'age' field of Sujet model """
labels = (('Mineurs', range(0, 18)),
('18-24', range(18, 25)),
('25-34', range(25, 35)),
('35-44', range(35, 45)),
('45-54', range(45, 55)),
('+ de 55', range(55, 110)),
)
def count_nbr_sujets(self, age_rng):
return self.queryset.filter(age__in=age_rng).count()
def __init__(self, queryset):
data = [("age", "count")]
if queryset:
self.queryset = Sujet.objects.filter(pk__in=queryset.values_list('pk'))
data += [(label, self.count_nbr_sujets(rg)) for label, rg in self.labels]
data += [("Ne sait pas", self.queryset.filter(age=None).count())]
super().__init__(data=data, title="Âge")
class RencontreParSujetChart(PieWrapper):
labels = (('Rencontre unique', (1,)),
('Entre 2 et 5 rencontres', range(2, 6)),
('Entre 6 et 20 rencontres', range(6, 20)),
('Plus de 20 rencontres', range(20, 999)),
)
def get_count_for_range(self, rng):
return self.queryset.filter(nbr__in=rng).count()
def __init__(self, queryset):
data = [('Type de rencontre', 'Nombre de sujets')]
if queryset:
self.queryset = queryset.values('sujet').annotate(nbr=Count('pk')).order_by()
data += [(label, self.get_count_for_range(rg)) for label, rg in self.labels]
super().__init__(data=data,
title='Fréquence de rencontres'
)
class RencontreParMoisChart(gchart.ColumnChart):
def __init__(self, queryset):
data = [("Mois", "Rencontres")]
if queryset:
par_mois = queryset.annotate(
mois=ExtractMonth('created_date')
).values(
'mois'
).annotate(
nbr=Count('pk')
).order_by()
data += [(NOM_MOIS[item['mois']], item['nbr']) for item in par_mois]
else:
data += [("Mois",0)]
super().__init__(SimpleDataSource(data),
options={
"title": "Nombre de rencontres par mois"
}
)
class RencontreParHeureChart(gchart.AreaChart):
def __init__(self, queryset):
data = [("Heure", "Rencontres démarrées", "Au total (démarré + en cours)")]
if queryset:
par_heure = self.calculer_frequentation_par_quart_heure(queryset, continu=False)
en_continu = self.calculer_frequentation_par_quart_heure(queryset, continu=True)
data += [(heure, par_heure[heure], en_continu[heure]) for heure in sorted(par_heure.keys())]
else:
data += [("Heure", 0, 0)]
super().__init__(SimpleDataSource(data),
options={
"title": "Fréquentation de la maraude en fonction de l'heure (par quart d'heure)"
}
)
@staticmethod
def calculer_frequentation_par_quart_heure(observations, continu=False):
""" Calcule le nombre d'observations, de 16h à 24h, par tranche de 15min.
L'algorithme est *très peu* efficace mais simple à comprendre : on calcule pour
chaque tranche les observations qui y sont contenues.
On peut calculer seulement les observations démarrées (continu = False) ou considérer
que l'observation est contenue dans un intervalle sur toute sa durée (continu = True).
"""
data = dict()
def genere_filtre_pour(heure, indice):
""" Renvoie une fonction qui renvoie True si l'intervalle donné contient l'observation, c'est-à-dire :
1. Elle démarre/finit dans l'intervalle.
2. Elle démarre avant et fini après l'intervalle.
"""
debut_intervalle = indice * 15
fin_intervalle = debut_intervalle + 15
rng = range(debut_intervalle, fin_intervalle)
def est_contenue(observation):
""" Vérifie l'observation est contenue dans l'intervalle """
debut = datetime.datetime.strptime(
"%s" % observation.rencontre.heure_debut,
"%H:%M:%S"
)
fin = debut + datetime.timedelta(0, observation.rencontre.duree * 60)
# L'observation démarre dans l'intervalle
if debut.hour == heure and debut.minute in rng:
return True
# L'observation finit dans l'intervalle, seulement si continu est True
elif continu and (fin.hour == heure and fin.minute in rng):
return True
# L'observation démarre avant ET finit après l'intervalle,
# seulement si continu est True
elif (continu
and (debut.hour <= heure and debut.minute <= debut_intervalle)
and (fin.hour >= heure and fin.minute >= fin_intervalle)):
return True
else:
return False
return est_contenue
for h in range(16, 24):
for i in range(4):
filtre = genere_filtre_pour(heure=h, indice=i)
contenus = list(filter(filtre, observations))
key = datetime.time(h, i * 15)
data[key] = len(contenus)
return data