Anki/anki/graphs.py

246 lines
7.8 KiB
Python

# -*- coding: utf-8 -*-
# Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
import os, sys, time, datetime, simplejson
from anki.lang import _
import anki.js
#colours for graphs
dueYoungC = "#ffb380"
dueMatureC = "#ff5555"
dueCumulC = "#ff8080"
reviewNewC = "#80ccff"
reviewYoungC = "#3377ff"
reviewMatureC = "#0000ff"
lrnTimeC = "#0fcaff"
revTimeC = "#ffcaff"
easesNewC = "#80b3ff"
easesYoungC = "#5555ff"
easesMatureC = "#0f5aff"
addedC = "#b3ff80"
firstC = "#b380ff"
intervC = "#80e5ff"
class Graphs(object):
def __init__(self, deck, selective=True):
self.deck = deck
self._stats = None
self.selective = selective
def report(self):
txt = (self.dueGraph() +
self.repsGraph() +
self.timeGraph() +
self.cumDueGraph() +
self.ivlGraph() +
self.easeGraph())
return "<script>%s</script>%s" % (anki.js.all, txt)
# Due and cumulative due
######################################################################
def dueGraph(self):
self._calcStats()
d = self._stats['due']
yng = []
mtr = []
for day in d:
yng.append((day[0], day[1]))
mtr.append((day[0], day[2]))
txt = self._graph(id="due", data=[
dict(data=yng, color=dueYoungC, label=_("Young")),
dict(data=mtr, color=dueMatureC, label=_("Mature"))
])
return txt
def cumDueGraph(self):
self._calcStats()
d = self._stats['due']
tot = 0
days = []
for day in d:
tot += day[1]+day[2]
days.append((day[0], tot))
txt = self._graph(id="cum", data=[
dict(data=days, color=dueCumulC, label=_("Cards")),
], type="fill")
return txt
def _due(self, days=7):
return self.deck.db.all("""
select due-:today,
count(), -- all
sum(case when ivl >= 21 then 1 else 0 end) -- mature
from cards
where queue = 2 and due < (:today+:days) %s
group by due order by due""" % self._limit(),
today=self.deck.sched.today, days=days)
# Reps and time spent
######################################################################
def repsGraph(self):
self._calcStats()
lrn = []
yng = []
mtr = []
lapse = []
cram = []
for row in self._stats['done']:
lrn.append((row[0], row[1]))
yng.append((row[0], row[2]))
mtr.append((row[0], row[3]))
lapse.append((row[0], row[4]))
cram.append((row[0], row[5]))
txt = self._graph(id="reps", data=[
dict(data=lrn, color=reviewNewC, label=_("Learning")),
dict(data=lapse, color=reviewMatureC, label=_("Relearning")),
dict(data=yng, color=reviewYoungC, label=_("Young")),
dict(data=mtr, color=reviewMatureC, label=_("Mature")),
dict(data=cram, color=reviewMatureC, label=_("Cramming")),
])
return txt
def timeGraph(self):
self._calcStats()
lrn = []
yng = []
mtr = []
lapse = []
cram = []
for row in self._stats['done']:
lrn.append((row[0], row[6]))
yng.append((row[0], row[7]))
mtr.append((row[0], row[8]))
lapse.append((row[0], row[9]))
cram.append((row[0], row[10]))
txt = self._graph(id="time", data=[
dict(data=lrn, color=lrnTimeC, label=_("Learning")),
dict(data=lapse, color=revTimeC, label=_("Relearning")),
dict(data=yng, color=revTimeC, label=_("Young")),
dict(data=mtr, color=revTimeC, label=_("Mature")),
dict(data=cram, color=revTimeC, label=_("Cramming")),
])
return txt
def _done(self):
# without selective for now
return self.deck.db.all("""
select
cast((time/1000 - :cut) / 86400.0 as int)+1 as day,
sum(case when type = 0 then 1 else 0 end), -- lrn count
sum(case when type = 1 and lastIvl < 21 then 1 else 0 end), -- yng count
sum(case when type = 1 and lastIvl >= 21 then 1 else 0 end), -- mtr count
sum(case when type = 2 then 1 else 0 end), -- lapse count
sum(case when type = 3 then 1 else 0 end), -- cram count
sum(case when type = 0 then taken/1000 else 0 end)/3600.0, -- lrn time
-- yng + mtr time
sum(case when type = 1 and lastIvl < 21 then taken/1000 else 0 end)/3600.0,
sum(case when type = 1 and lastIvl >= 21 then taken/1000 else 0 end)/3600.0,
sum(case when type = 2 then taken/1000 else 0 end)/3600.0, -- lapse time
sum(case when type = 3 then taken/1000 else 0 end)/3600.0 -- cram time
from revlog group by day order by day""", cut=self.deck.sched.dayCutoff)
# Intervals
######################################################################
def _ivls(self):
return self.deck.db.all("""
select ivl / 7, count() from cards
where queue = 2 %s
group by ivl / 7
order by ivl / 7""" % self._limit())
def ivlGraph(self):
self._calcStats()
ivls = self._stats['ivls']
txt = self._graph(id="ivl", data=[
dict(data=ivls, color=intervC)
])
return txt
# Eases
######################################################################
def _eases(self):
# ignores selective, at least for now
return self.deck.db.all("""
select (case
when type in (0,2) then 0
when lastIvl < 21 then 1
else 2 end) as thetype,
ease, count() from revlog
group by thetype, ease
order by thetype, ease""")
def easeGraph(self):
self._calcStats()
# 3 + 4 + 4 + spaces on sides and middle = 15
# yng starts at 1+3+1 = 5
# mtr starts at 5+4+1 = 10
d = {'lrn':[], 'yng':[], 'mtr':[]}
types = ("lrn", "yng", "mtr")
for (type, ease, cnt) in self._stats['eases']:
if type == 1:
ease += 5
elif type == 2:
ease += 10
n = types[type]
d[n].append((ease, cnt))
ticks = [[1,1],[2,2],[3,3],
[6,1],[7,2],[8,3],[9,4],
[11, 1],[12,2],[13,3],[14,4]]
txt = self._graph(id="ease", data=[
dict(data=d['lrn'], color=easesNewC, label=_("Learning")),
dict(data=d['yng'], color=easesYoungC, label=_("Young")),
dict(data=d['mtr'], color=easesMatureC, label=_("Mature")),
], conf=dict(
xaxis=dict(ticks=ticks, min=0, max=15)))
return txt
# Tools
######################################################################
def _calcStats(self):
if self._stats:
return
self._stats = {}
self._stats['due'] = self._due()
self._stats['ivls'] = self._ivls()
self._stats['done'] = self._done()
self._stats['eases'] = self._eases()
def _graph(self, id, data, conf={}, width=600, height=200, type="bars"):
conf['legend']= {'container': "#%sLegend" % id}
if type == "bars":
conf['series'] = dict(
bars=dict(show=True, barWidth=0.8, align="center"))
elif type == "fill":
conf['series'] = dict(
lines=dict(show=True, fill=True))
return (
"""
<table>
<tr><td><div id="%(id)s" style="width:%(w)s; height:%(h)s;"></div></td>
<td valign=top><br><div id=%(id)sLegend></div></td></tr></table>
<script>
$(function () {
$.plot($("#%(id)s"), %(data)s, %(conf)s);
});
</script>""" % dict(
id=id, w=width, h=height,
data=simplejson.dumps(data),
conf=simplejson.dumps(conf)))
def _limit(self):
if self.selective:
return self.deck.sched._groupLimit("rev")
else:
return ""
def _addMissing(self, dic, min, max):
for i in range(min, max+1):
if not i in dic:
dic[i] = 0