Étape 17 : Design - graph
Données sont grandes, mais les visualisations sont mieux. Dans cette étape nous allons manipuler notre histoire stockée afin que nous puissions faire graphiques vraiment sympa !
Première nous allons commencer par faire nos capteurs nommés, alors que c’est plus facile pour nous de garder une trace de qui est ce que. Puis nous allons examiner nos options de graphique et les formats de données. Enfin nous allons reformater nos données pour la rendre prête à une représentation graphique
Configurer les noms de capteur
C’est pas amusant de disposer de données marquées comme « sensor #1 » alors j’ai ajouté une page « config » où l’app engine code examine quels numéros de capteur a transmis des données à la base de données et vous permet ensuite de les nommer. Bien sûr, vous devez avoir le capteur sur et envoi de données - la première - avant que cela fonctionne
L’écran de configuration ressemble à quelque chose comme l’image ci-dessous.
Ce code utilise GET quand elle doit vraiment utiliser POST. Je suis un peu vieux et n’aime pas le débogage avec POST alors... ouais.
classe Configure (webapp. RequestHandler) :
def get(self) :
# faire l’utilisateur ouvrir une session si aucun nom d’utilisateur n’est fourni
Si self.request.get('user') :
compte = users. User(self.Request.get('user'))
autre chose :
Si ce n’est pas users.get_current_user() :
Self.Redirect(Users.create_login_url(self.Request.Uri))
compte = users.get_current_user()
Self.Response.out.Write ("< html >< corps > ensemble vers le haut de votre sensornode de noms ici : < p >')
# trouver tous les capteurs à #10
sensorset =]
car moi dans range(10) :
c = db. GqlQuery ("SELECT * de Powerusage où l’auteur =: 1 et sensornum =: 2", users.get_current_user(), i)
Si c.get() :
sensorset.Append(i)
Self.Response.out.Write ("< forment action =" / config "méthode = « get » >")
pour capteur dans sensorset :
nom = ""
currnamequery = db. GqlQuery ("SELECT * de Sensorname où l’auteur =: 1 AND sensornum =: 2", users.get_current_user(), capteur)
currname = currnamequery.get()
# tout d’abord voir si nous allons mettre !
Si self.request.get('sensornum'+str(sensor)) :
nom = self.request.get('sensornum'+str(sensor))
Si ce n’est pas currname :
currname = Sensorname() # créer une nouvelle entrée
currname.sensornum = capteur
currname.Author = users.get_current_user()
currname.sensorname = nom
currname.put()
autre chose :
# Nous ne sommes pas mettre Oui entrée actuelle de l’extraction
Si currname :
nom = currname.sensorname
Self.Response.out.Write ("capteur #' + str(sensor) +: < input type = « text » name="sensornum'+str(sensor) + "" valeur = « '+ nom +' » >< / texte >< p >')
Self.Response.out.Write ("" "< div >< input type =" submit"value ="Modifier les noms de">< / div >
< / font >
< / body >
< / html > "" ")
Maintenant, nous pouvons avoir des données plus utiles dans la décharge de l’histoire
Maintenant nous pouvons voir que Phil est principalement responsable de notre facture d’électricité !
Visualiseur de Google
Nous avons donc des données et nous aimerions voir notre historique d’utilisation de puissance. Représentation graphique de données est beaucoup de travail, et je suis paresseux. Alors j’ai regarder en ligne et trouver que Google a - aussi - une visualisation API! Cela signifie que je ne dois pas écrire un tas de code graphique et peut juste brancher sur leur système. Sweet !
OK, vérifier la Galerie de visualisations disponibles, je suis passionné de celui-ci, la Chronologie annotée
Notez comment vous pouvez facilement voir les graphiques, défilement autour, zoomer et dézoomer, et chaque parcelle est étiqueté. Parfait pour le traçage des données de puissance !
Formatage des données
Theres quelques restrictions à comment obtenir les données pour la visualisation api et notre meilleure option est de données JSon. Autant que je sache, JSON est ce qui s’est passé quand tout le monde dit « wow, XML est vraiment encombrant et inutile ». En tout cas, theres comme 4 couches de cadre et d’interprétation de données exposition tructions et en fin de compte il y avait une bibliothèque assez facile à utiliser, rédigée par l’équipe Google visualisations qui me permettre de « faites-le » avec un seul appel en mettant les données en un python « dictionnaire » dans un certain format.
Permet de passer par le code dans les sections, étant donné que la fonction est assez longue
classe JSONout(webapp. RequestHandler) :
def get(self) :
# faire l’utilisateur ouvrir une session si aucun nom d’utilisateur n’est fourni
Si self.request.get('user') :
compte = users. User(self.Request.get('user'))
autre chose :
Si ce n’est pas users.get_current_user() :
Self.Redirect(Users.create_login_url(self.Request.Uri))
compte = users.get_current_user()
# suppose que nous voulons que 24 heures de données
historytimebegin = 24
Si self.request.get('bhours') :
historytimebegin = int(self.request.get('bhours'))
# suppose que nous voulons des données à partir de 0 heures
historytimeend = 0
Si self.request.get('ehours') :
historytimeend = int(self.request.get('ehours'))
format de données # pour le bonheur JSON
magasin de données =]
columnNames = ["date"]
jeu de colonnes = set(columnnames)
Description = {« date »: ("datetime", « Date »)}
# les noms de chaque capteur, si configuré
sensornames = [None] * 10
Tout d’abord vers le haut, nous obtenons l’utilisateur que nous allons regarder vers le haut les données. Ensuite, nous avons deux variables utilisées pour définir la quantité de données à saisir. On est "ehours" (heures de la fin) et l’autre est « heures ». Donc, si vous vouliez les 5 dernières heures, heures serait 5 et ehours serait 0. Si vous vouliez 5heures d’il y a un jour, heures serait 29 et ehours serait 24. magasin de données est où nous allons corall toutes les données. columnNames et description sont les « noms » de chaque colonne. Nous avons toujours une colonne de date, puis une autre colonne pour chaque flux de capteur. Nous avons également un tableau distinct pour mettre en cache les noms de capteur spécial.
dans la section suivante ! Voici où nous en fait saisir les données de la base de données. Maintenant moteur app a cela ennuyeux de restriction, vous ne pouvez obtenir 1000 points de données à la fois donc ce que je fais, c’est passer par là 12 heures d’affilée. Le magasin de données final a tous les points, mais c’est plus facile sur la base de données, je suppose. Une chose qui est source de confusion est peut-être chaque colonne a un nom et une description. Le nom est court, par exemple « watts3 » pour capteur #3, mais la description pourrait être « Établi de Limor ». Je ne sais même plus écrire ce code alors peut-être que vous pouvez figurer dehors sur votre propre ?
# Nous ne pouvons pas saisir plus de 1000 datapoints, grâce à la restriction de la libre-app-moteur
# qui vaut d’environ 3 capteurs en une seule journée
# et nous limiterons à saisir seulement 12 heures de données à la fois, environ 7 capteurs d’une valeur
tandis que (historytimebegin > historytimeend) :
Si (historytimebegin - historytimeend) > 12 :
timebegin = datetime.timedelta (heures = - historytimebegin)
timeend = datetime.timedelta (heures =-(historytimebegin-12))
historytimebegin-= 12
autre chose :
timebegin = datetime.timedelta (heures = - historytimebegin)
historytimebegin = 0
timeend = datetime.timedelta (heures = - historytimeend)
# récupérer toutes les données de capteur pour ce segment de temps
powerusages = db. GqlQuery ("sélectionnez * de Powerusage où date >: 1 AND date <: 2 et auteur =: 3 ORDER BY date », datetime.datetime.now (), datetime.datetime.now () + timebegin, timeend, compte)
# les trier dans le format approprié et ajouter des noms de capteur de que DB si ne pas encore fait
pour powerused dans powerusages :
Coln = « watts » + str(powerused.sensornum)
entrée = {« date »: powerused.date.replace(tzinfo=utc).astimezone(est), coln : powerused.watt}
Si ce n’est pas (coln dans le jeu de colonnes) :
columnNames.Append(Coln)
jeu de colonnes = set(columnnames)
# trouver le nom de capteur, si nous ne pouvons
Si (len(sensornames) < powerused.sensornum) ou (pas de sensornames[powerused.sensornum]) :
currnamequery = db. GqlQuery ("SELECT * de Sensorname où l’auteur =: 1 AND sensornum =: 2", compte, powerused.sensornum)
nom = currnamequery.get()
Si ce n’est pas le cas, le nom :
sensornames[powerused.sensornum] = "capteur #"+str(powerused.sensornum)
autre chose :
sensornames[powerused.sensornum] = name.sensorname
Description [coln] = (« nombre », sensornames[powerused.sensornum])
#self.response.out.write(sensornames)
# Ajoute une entrée à la fois
datastore.Append(Entry)
Enfin à la fin de la boucle, nous appeler la fonction magique qui transforme le dictionnaire en JSON, enveloppez-le dans le bon paquet visualisation Google, puis cracher !
# OK, toutes les données est prêt à aller, imprimez-le au format JSON !
data_table = gviz_api. DataTable(description)
data_table. LoadData(datastore)
Self.Response.headers ['Content-Type'] = « text/plain »
Self.Response.out.Write (data_table. ToJSonResponse(columns_order=(columnnames),
order_by = "date"))
Si vous deviez visiter http://wattcher.appspot.com/visquery.json?user=adawattz il serait sortie quelque chose comme ceci :
google.visualization.Query.setResponse({'version':'0.5', 'reqId':'0', 'status':'OK', 'table': {cols: [{id:'date',label:'Date',type:'datetime'},{id:'watts1',label:'Limor',type:'number'},{id:'watts5',label:'Workbench',type:'number'},{id:'watts2',label:'Adafruit',type:'number'},{id:'watts4',label:'Phil2',type:'number'}],rows: [{c:[{v:new Date(2009,1,25,21,20,2)},{v:64.8332291619},,,{v:null}]},{c:[{v:new Date(2009,1,25,21,20,3)},,{v:230.122099757},,{v:null}]},{c:[{v:new Date(2009,1,25,21,20,3)} ,,, {v: 65.4923925044}, {v: null}]}, {c: [{v: Date(2009,1,25,21,20,4) nouveau},,, {v: 48.6947643311}]}, {c: [{v: Date(2009,1,25,21,25,3) nouveau},, {v: 228.409810208},, {v: null}]}, {c: [{v: nouvelle Date(2009,1,25,21,25,3)}, {v: 67.3574917331},,, {v: null}]}, {c: [{v: Date(2009,1,25,21,25,3) nouvelle},,, {v: 66.0046383897}, {v: null}]}, {c: [{v: Date(2009,1,25,21,25,4) nouveau},,, {v: 47.3892235642}]}, {c: [{v: Date(2009,1,25,21,30,2) nouveau}, {v: 84.9379517795},,, {v: null}]}, {c: [{v: Date(2009,1,25,21,30,3) nouvelle},,, {v: 99.7553490071}]} , {c: [{v: Date(2009,1,25,21,30,5) nouveau},, {v: 229.73642288},, {v: null}]}, {c: [{v: Date(2009,1,25,21,30,6) nouvelle},,, {v: 66.6556291818}, {v: null}]}, {c: [{v: Date(2009,1,25,21,35,2) nouveau},,, {v: 67.3146052998}, {v: null}]}, {c: [{v: nouvelle Date(2009,1,25,21,35,3)}, {v: 96.2322216676},,, {v: null}]}, {c: [{v: Date(2009,1,25,21,35,3) nouveau},, {v: 226.678267688},, {v: null}]}, {c: [{v: Date(2009,1,25,21,35,4) nouveau},,, {v: 158.428422765}]}, {c: [{v: Date(2009,1,25,21,40,3) nouvelle},, {v: 232.644574879},, {v: null}]} , {c: [{v: Date(2009,1,25,21,40,4) nouveau},,, {v: 153.666193493}]}, {c: [{v: Date(2009,1,25,21,40,6) nouveau},,, {v: 66.7874343225}, {v: null}]}, {c: [{v: Date(2009,1,25,21,40,12) nouveau}, {v: 95.0019590395},,, {v: null}]}, {c: [{v: nouvelle Date(2009,1,25,21,40,21)}, {v: 95.0144043571},,, {v: null}]}, {c: [{v: Date(2009,1,25,21,40,23) nouvelle},,, {v: 66.8060307611}, {v: null}]}, {c: [{v: Date(2009,1,25,21,45,2) nouvelle},,, {v: 66.9814723201}, {v: null}]}, {c: [{v: Date(2009,1,25,21,45,3) nouveau},, {v: 226.036818816},, {v: null}]} , {c: [{v: nouvelle Date(2009,1,25,21,45,3)}, {v: 99.2775581827},,, {v: null}]}, {c: [{v: Date(2009,1,25,21,45,4) nouveau},,, {v: 154.261889366}]}, {c: [{v: Date(2009,1,25,21,50,4) nouvelle}, {v: 102.104642018},,, {v: null}]}, {c: [{v: Date(2009,1,25,21,50,4) nouveau},,, {v: 155.441084531}]}, {c: [{v: Date(2009,1,25,21,50,5) nouveau},,, {v: 67.0087146687}, {v: null}]}, {c: [{v: Date(2009,1,25,21,50,5) nouveau},, {v: 230.678636915},, {v: null}]}, {c: [{v: Date(2009,1,25,21,55,3) nouvelle}, {v: 103.493297176},,, {v: null}]}, {c: [{v : Date(2009,1,25,21,55,3) nouveau},,, {v: 151.309223916}]}, {c: [{v: Date(2009,1,25,21,55,4) nouveau},,, {v: 66.9174858741}, {v: null}]}, {c: [{v: Date(2009,1,25,21,55,4) nouveau},, {v: 227.765325835},, {v: null}]}, {c: [{v: Date(2009,1,25,22,0,3) nouveau},,, {v: 67.0004310254}, {v: null}]}, {c: [{v: Date(2009,1,25,22,0,3) nouveau},,, {v: 150.389989112}]}, {c: [{v: Date(2009,1,25,22,0,3) nouveau},, {v: 230.892049553},, {v: null}]}, {c: [{v: Date(2009,1,25,22,0,4) nouveau}, {v: 92.2432771363},,, {v: null}]}, {c: [{v: Date(2009,1,25,22,15,3) nouveau} {v: 97.5910440774},,, {v: null}]}, {c: [{v: nouvelle Date(2009,1,25,22,15,3)},,, {v: 143.722595861}]}, {c: [{v: nouvelle Date(2009,1,25,22,15,4)},,, {v: 64.4898008851}, {v: null}]}, {c: [{v: nouvelle Date(2009,1,25,22,15,4)},,{v:222.357617868},,{v:null}]}]}}) ;
de toute façon, vous pouvez voir un peu les données, Notez également sa en fait un appel de fonction, ce truc est vraiment crépu !
Maintenant, allez à l' Aire de jeux Google visualisations et entrer cette URL dans le bac à sable
Et vous pouvez voir la visualisation se sortir ! (c’est juste un écran coup alors allez faire yerself si vous voulez perdre son temps)
OK aller perdre son temps, ajoutant et en modifiant les heures et les ehours
Envelopper la visualisation
OK, nous avons presque terminé. Maintenant, nous avons juste besoin fondamentalement saisir le code à partir du bac à sable et faire une sous-page dans notre moteur de recherche app... comme suit :
classe Visualize (webapp. RequestHandler) :
def get(self) :
# faire l’utilisateur ouvrir une session si aucun nom d’utilisateur n’est fourni
Si self.request.get('user') :
compte = users. User(self.Request.get('user'))
autre chose :
Si ce n’est pas users.get_current_user() :
Self.Redirect(Users.create_login_url(self.Request.Uri))
compte = users.get_current_user()
historytimebegin = # 24 suppose que 24 heures
Si self.request.get('bhours') :
historytimebegin = int(self.request.get('bhours'))
historytimeend = 0 # Supposons 0 heures
Si self.request.get('ehours') :
historytimeend = int(self.request.get('ehours'))
# sortir la première partie, en-têtes,
() self.Response.out.Write
< ! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict / / EN" « http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd » >
< html xmlns = « http://www.w3.org/1999/xhtml » >
< tête >
< meta http-equiv = « content-type » content = "text/html ; charset = utf-8 "/ >
< titre > Google Visualization API exemple < /title >
< script type = « text/javascript » src = « http://www.google.com/jsapi » >< / script >
< script type = « text/javascript » >
Google.Load ("visualisation", "1", {paquets: ["annotatedtimeline"]}) ;
function drawVisualizations() {}
)
# créer notre visualisation
Self.Response.out.Write (new google.visualization.Query ("http://wattcher.appspot.com/visquery.json?user=+
Account.email() +& heures =+ str(historytimebegin) +») .send (
{function(Response)}
(google.visualization.AnnotatedTimeLine) nouveau
document.getElementById("visualization")).
Draw(Response.getDataTable(), {"displayAnnotations": true}) ;
});
)
Self.Response.out.Write (}
google.setOnLoadCallback(drawVisualizations) ;
< /script >
< / head >
< le style corps = "famille de police : Arial ; frontière: 0 none;" >
< div id = « visualisation » style = "width : 800px ; hauteur : 250px; » >< / div >
< / body >
< / html >)
La première partie est assez simple, obtenir le nom d’utilisateur ou de connexion. Ensuite, nous supposerons que l’utilisateur veut 1 dernier jour de données, donc régler les heures et les ehours. Puis nous avons littéralement juste imprimer le code que nous avons copiés à partir sandbox de visualisation de Google, fait !
Viz Viz Viz
La seule chose que je ne pouvais pas comprendre est comment obtenir 3 visualisations passait à la fois (dernière semaine, jour et heure) avec le code ci-dessus. Il s’est juste un peu cassé. Donc pour la triple vue j’ai dû utiliser les iframes:(
classe VisualizeAll(webapp. RequestHandler) :
def get(self) :
# faire l’utilisateur ouvrir une session si aucun nom d’utilisateur n’est fourni
Si self.request.get('user') :
compte = users. User(self.Request.get('user'))
autre chose :
Si ce n’est pas users.get_current_user() :
Self.Redirect(Users.create_login_url(self.Request.Uri))
compte = users.get_current_user()
() self.Response.out.Write
< h2 > consommation d’énergie au cours de la dernière heure : < / h2 >
< iframe src = "graphique? user = adawattz frameborder = « 0 » width = « 100 % » height = « 300px » >
< p > votre navigateur ne supporte pas les iframes. < /p >
< / iframe >
la consommation d’énergie au cours de la dernière journée < h2 > : < / h2 >
< iframe src = "graphique? user = adawattz frameborder = « 0 » width = « 100 % » height = « 300px » >
< p > votre navigateur ne supporte pas les iframes. < /p >
< / iframe >
< h2 > consommation d’énergie au cours de la dernière semaine : < / h2 >
< iframe src = "graphique? user = adawattz frameborder = « 0 » width = « 300 % » height = « 500px » >
< p > votre navigateur ne supporte pas les iframes. < /p >
< / iframe >
)
En tout cas, il fonctionne très bien.
Codes temporels !
La dernière chose qui ne sera pas examinée ici est comment je suis arrivé à la date et les heures pour être EST au lieu de l’UTC. Autant que je sache, ce genre de cassé et mystérieux. Vérifier le code si vous voulez le comprendre.