Paso 17: Diseño - gráfico
Datos están grandes, pero son mejores visualizaciones. En este paso a manipular nuestra historia almacenado por lo que podemos hacer gráficos muy agradables!
Primero empezaremos haciendo nuestros llamados, los sensores para su más fácil para nosotros saber que es lo que. Luego veremos nuestro gráfico opciones y formatos de datos. Finalmente podrán formatear nuestros datos para que quede listo para graficar
Configuración de los nombres de sensor
Su no divertido tener datos marcados como "sensor #1" por lo que he añadido una página de 'configuración' donde el código del motor de la aplicación mira qué números sensor ha enviado datos a la base de datos y luego le permite nombrarlos. Por supuesto, usted necesita tener el sensor y el envío de datos - primero - antes de que esto funcionará
La pantalla configurar espera algo como la imagen de abajo.
Este código utiliza GET cuando realmente debería usar el POST. Soy un poco viejo y no como depuración con POST así... yeah.
clase configurar (webapp. RequestHandler):
DEF get(self):
# hacer que el usuario iniciar sesión si ningún nombre de usuario se suministra
Si self.request.get('user'):
cuenta = usuarios. User(self.request.get('user'))
otra cosa:
Si no users.get_current_user():
Self.Redirect(users.create_login_url(self.request.Uri))
cuenta = users.get_current_user()
Self.Response.out.Write ('< html >< cuerpo > sistema para arriba su sensornode nombres aquí: < p >')
# encontrar todos los sensores hasta #10
sensorset =]
para que en range(10):
c = db. GqlQuery ("SELECT * de Powerusage donde el autor =: 1 y sensornum =: 2", users.get_current_user(), i)
Si c.get():
sensorset.Append(i)
Self.Response.out.Write ('< forma acción = "/ config" método = "get" >')
sensor de sensorset:
nombre = ""
currnamequery = db. GqlQuery ("SELECT * de Sensorname donde el autor =: 1 y sensornum =: 2", users.get_current_user(), sensor)
currname = currnamequery.get()
# primero ver si la establecemos.
Si self.request.get('sensornum'+str(sensor)):
nombre = self.request.get('sensornum'+str(sensor))
Si no currname:
currname = Sensorname() # crear una nueva entrada
currname.sensornum = sensor de
currname.Author = users.get_current_user()
currname.sensorname = nombre
currname.put()
otra cosa:
# no establecemos lo que trae la entrada actual
Si currname:
nombre = currname.sensorname
Self.Response.out.Write ('Sensor #' + str(sensor) +': < entrada tipo = "texto" name="sensornum'+str(sensor) +" "valor ="' + nombre +' ">< / texto >< p >')
Self.Response.out.Write ("" "< div >< tipo de entrada =" enviar"valor ="Cambiar nombre">< / div >
< / form >
< /body >
< / html > "" ")
Ahora podemos tener datos más útiles en el basurero de la historia
Ahora podemos ver que Phil sobre todo tiene la culpa de nuestra factura de energía.
Visualizador de Google
Así tenemos datos y nos gustaría ver nuestra historia de uso de la energía. Graficación de datos es mucho trabajo, y soy perezoso. Así que mira en línea y encontrar que Google - también - tiene una visualización API! Esto significa que no hay que escribir un montón de código gráfico y sólo se puede conectar a su sistema. Dulce!
OK revisar la Galería de visualizaciones disponibles, soy amigo de éste, la Línea de tiempo anotado
Observe cómo usted puede ver fácilmente los gráficos, desplazamiento, zoom in y out y se etiqueta cada parcela. Ideal para trazar datos de energía!
Formato de datos
Hay algunas restricciones a cómo conseguir los datos para la visualización api y nuestra mejor opción es datos JSon. Lo puedo decir, JSON es lo que sucedió cuando todos decían "wow, XML es muy voluminosos y derrochadores". De todos modos, hay como 4 capas de marco y de instrucciones de interpretación de datos y al final había una biblioteca muy fácil de usar, escrita por el equipo de visualizaciones de Google que me deja 'just do it con una sola llamada al poner los datos en un pitón 'Diccionario' en un determinado formato.
Permite recorrer el código en secciones, ya que la función es bastante larga
clase JSONout(webapp. RequestHandler):
DEF get(self):
# hacer que el usuario iniciar sesión si ningún nombre de usuario se suministra
Si self.request.get('user'):
cuenta = usuarios. User(self.request.get('user'))
otra cosa:
Si no users.get_current_user():
Self.Redirect(users.create_login_url(self.request.Uri))
cuenta = users.get_current_user()
# asume que queremos 24 horas de datos
historytimebegin = 24
Si self.request.get('bhours'):
historytimebegin = int(self.request.get('bhours'))
# asumir queremos datos a partir de hace 0 horas
historytimeend = 0
Si self.request.get('ehours'):
historytimeend = int(self.request.get('ehours'))
formato de los datos # para felicidad JSON
almacén de datos =]
columnnames = ["fecha"]
columnset = set(columnnames)
Descripción = {"fecha": ("datetime", "Fecha")}
# los nombres de cada sensor, si ha configurado
sensornames = [ninguno] * 10
Primero para arriba obtenemos el usuario para que vamos a estar buscando los datos. Entonces tenemos dos variables para definir la cantidad de datos para agarrar. Uno es "ehours" (horas de la final) y el otro es "bhours". Así que si desea las últimas 5 horas, bhours 5 y ehours sería 0. Si desea 5 horas desde hace un dia, bhours sería 29 y ehours sería 24. almacén de datos es donde podremos corall todos los datos. columnnames y descripción son los nombres de cada columna. Siempre tenemos una columna de fecha, luego otra columna para cada corriente de sensor. También tenemos una matriz distinta para almacenar en caché los nombres del sensor especial.
en la siguiente sección. Aquí es donde nosotros realmente los datos de la base de datos. Ahora la aplicación tiene esta molesta restricción, sólo se pueden obtener 1000 puntos de datos a la vez así que lo que hago es pasar 12 horas en un momento. El almacén de datos final tiene todos los puntos pero fácil en la base de datos, supongo. Una cosa que es confusa es quizás cada columna tiene un nombre y una descripción. El nombre es corto, digamos "watts3" sensor #3, pero la descripción podría ser "Banco de trabajo de Limor". ¿Ni siquiera recuerdo escribiendo este código así que tal vez que puede figura hacia fuera por su cuenta?
# no podemos agarrar datapoints más de 1000, gracias al motor de aplicación libre de restricción
# que vale la pena de 3 sensores en un solo día
# por lo que restringirá a sólo tomar 12 horas de datos en un momento, alrededor de 7 sensores vale la pena
mientras que (historytimebegin > historytimeend):
Si (historytimebegin - historytimeend) > 12:
timebegin = datetime.timedelta (horas = - historytimebegin)
timeend = datetime.timedelta (= horas-(historytimebegin-12))
historytimebegin-= 12
otra cosa:
timebegin = datetime.timedelta (horas = - historytimebegin)
historytimebegin = 0
timeend = datetime.timedelta (horas = - historytimeend)
# tomar todos los datos del sensor para ese fragmento de tiempo
powerusages = db. GqlQuery ("seleccionar * de Powerusage donde Fecha >: 1 fecha y <: autor y 2 =: 3 ORDER BY fecha", (datetime.datetime.now), timebegin, datetime.datetime.now () + timeend, cuenta)
# ordenar en el formato adecuado y añadir nombres de sensor de DB si todavía no ha terminado
para salidaUtilizado en powerusages:
Coln "watts" = str(powerused.sensornum)
entrada = {"fecha": powerused.date.replace(tzinfo=utc).astimezone(est), coln: powerused.watt}
Si no (coln en columnset):
columnnames.Append(Coln)
columnset = set(columnnames)
# buscar el nombre del sensor, si podemos
Si (len(sensornames) < powerused.sensornum) o (no sensornames[powerused.sensornum]):
currnamequery = db. GqlQuery ("SELECT * de Sensorname donde el autor =: 1 y sensornum =: 2", cuenta, powerused.sensornum)
nombre = currnamequery.get()
Si no nombre:
sensornames[powerused.sensornum] = "sensor #"+str(powerused.sensornum)
otra cosa:
sensornames[powerused.sensornum] = name.sensorname
Descripción [coln] = ("número", sensornames[powerused.sensornum])
#self.response.out.write(sensornames)
# Añadir una entrada en un momento
DataStore.Append(Entry)
Finalmente al final de todo el bucle, llamar a la función mágica que convierte el Diccionario en JSON, envuélvalo en el paquete de Google visualización apropiado, después escupirla!
# OK todos los datos está listo para ir, imprime en formato 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 = "fecha"))
Si fueras a visitar http://wattcher.appspot.com/visquery.json?user=adawattz salida a algo como esto:
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) nuevo},,, {v: 48.6947643311}]}, {c: [{v: Date(2009,1,25,21,25,3) nuevo},, {v: 228.409810208}, {v: null}]}, {c: [{v: Date(2009,1,25,21,25,3) nuevo}, {v: 67.3574917331}, {v: null}]}, {c: [{v: Date(2009,1,25,21,25,3) nuevo},,, {v: 66.0046383897}, {v: null}]}, {c: [{v: Date(2009,1,25,21,25,4) nuevo},,, {v: 47.3892235642}]}, {c: [{v: Date(2009,1,25,21,30,2) nuevo}, {v: 84.9379517795},,, {v: null}]}, {c: [{v: Date(2009,1,25,21,30,3) nuevo},,, {v: 99.7553490071}]} , {c: [{v: Date(2009,1,25,21,30,5) nuevo},, {v: 229.73642288}, {v: null}]}, {c: [{v: Date(2009,1,25,21,30,6) nuevo},,, {v: 66.6556291818}, {v: null}]}, {c: [{v: Date(2009,1,25,21,35,2) nuevo},,, {v: 67.3146052998}, {v: null}]}, {c: [{v: Date(2009,1,25,21,35,3) nuevo}, {v: 96.2322216676}, {v: null}]}, {c: [{v: Date(2009,1,25,21,35,3) nuevo},, {v: 226.678267688}, {v: null}]}, {c: [{v: Date(2009,1,25,21,35,4) nuevo},,, {v: 158.428422765}]}, {c: [{v: Date(2009,1,25,21,40,3) nuevo},, {v: 232.644574879}, {v: null}]} , {c: [{v: Date(2009,1,25,21,40,4) nuevo},,, {v: 153.666193493}]}, {c: [{v: Date(2009,1,25,21,40,6) nuevo},,, {v: 66.7874343225}, {v: null}]}, {c: [{v: Date(2009,1,25,21,40,12) nuevo}, {v: 95.0019590395}, {v: null}]}, {c: [{v: Date(2009,1,25,21,40,21) nuevo}, {v: 95.0144043571},,, {v: null}]}, {c: [{v: Date(2009,1,25,21,40,23) nuevo},,, {v: 66.8060307611}, {v: null}]}, {c: [{v: Date(2009,1,25,21,45,2) nuevo},,, {v: 66.9814723201}, {v: null}]}, {c: [{v: Date(2009,1,25,21,45,3) nuevo},, {v: 226.036818816}, {v: null}]} , {c: [{v: Date(2009,1,25,21,45,3) nuevo}, {v: 99.2775581827},,, {v: null}]}, {c: [{v: Date(2009,1,25,21,45,4) nuevo},,, {v: 154.261889366}]}, {c: [{v: Date(2009,1,25,21,50,4) nuevo}, {v: 102.104642018}, {v: null}]}, {c: [{v: Date(2009,1,25,21,50,4) nuevo},,, {v: 155.441084531}]}, {c: [{v: Date(2009,1,25,21,50,5) nuevo},,, {v: 67.0087146687}, {v: null}]}, {c: [{v: Date(2009,1,25,21,50,5) nuevo},, {v: 230.678636915}, {v: null}]}, {c: [{v: Date(2009,1,25,21,55,3) nuevo}, {v: 103.493297176},,, {v: null}]}, {c: [{v : Date(2009,1,25,21,55,3) nuevo},,, {v: 151.309223916}]}, {c: [{v: Date(2009,1,25,21,55,4) nuevo},,, {v: 66.9174858741}, {v: null}]}, {c: [{v: Date(2009,1,25,21,55,4) nuevo},, {v: 227.765325835}, {v: null}]}, {c: [{v: Date(2009,1,25,22,0,3) nuevo},,, {v: 67.0004310254}, {v: null}]}, {c: [{v: Date(2009,1,25,22,0,3) nuevo},,, {v: 150.389989112}]}, {c: [{v: Date(2009,1,25,22,0,3) nuevo},, {v: 230.892049553}, {v: null}]}, {c: [{v: Date(2009,1,25,22,0,4) nuevo}, {v: 92.2432771363}, {v: null}]}, {c: [{v: Date(2009,1,25,22,15,3) nuevo} {v: 97.5910440774}, {v: null}]}, {c: [{v: Date(2009,1,25,22,15,3) nuevo},,, {v: 143.722595861}]}, {c: [{v: Date(2009,1,25,22,15,4) nuevo},,, {v: 64.4898008851}, {v: null}]}, {c: [{v: Date(2009,1,25,22,15,4)},,{v:222.357617868},,{v:null}]}]}}) nuevo;
de todos modos, puedes ver un poco los datos, tenga en cuenta también su llamada a una función, este material es realmente muy rizado!
Ahora ve al Patio de visualizaciones de Google y entrar en esa URL en la caja de arena
Y se puede ver la visualización sí mismo estallar hacia fuera! (esto es sólo una pantalla de tiro así que ir hacerlo yerself si quieres ensuciar)
OK, vaya lío alrededor, añadiendo y cambiando bhours y ehours
Terminando la visualización
OK hemos casi terminado. Ahora solo falta básicamente el código de la caja de arena y hacer una subpágina en nuestra app engine... así:
clase Visualize (webapp. RequestHandler):
DEF get(self):
# hacer que el usuario iniciar sesión si ningún nombre de usuario se suministra
Si self.request.get('user'):
cuenta = usuarios. User(self.request.get('user'))
otra cosa:
Si no users.get_current_user():
Self.Redirect(users.create_login_url(self.request.Uri))
cuenta = users.get_current_user()
historytimebegin = 24 # 24 horas de asumir
Si self.request.get('bhours'):
historytimebegin = int(self.request.get('bhours'))
historytimeend = 0 # asumir hace 0 horas
Si self.request.get('ehours'):
historytimeend = int(self.request.get('ehours'))
# salga a la primera parte, cabeceras,
() self.Response.out.Write
<! Html DOCTYPE público "-//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" >
< head >
< meta http-equiv = "content-type" content = "text/html; charset = utf-8 "/ >
< title > Google visualización API muestra < / title >
< script tipo = "texto/javascript" src = "http://www.google.com/jsapi" >< / script >
< script tipo = "texto/javascript" >
Google.Load ("visualización", "1", {paquetes: ["annotatedtimeline"]});
función drawVisualizations() {}
)
# crear la visualización
Self.Response.out.Write (nuevo google.visualization.Query ("http://wattcher.appspot.com/visquery.json?user=+
Account.email() +& bhours =+ str(historytimebegin) +") .enviar (
{function(Response)}
(nueva) google.visualization.AnnotatedTimeLine
document.getElementById("visualization")).
Draw(Response.getDataTable(), {"displayAnnotations": true});
});
)
Self.Response.out.Write (}
google.setOnLoadCallback(drawVisualizations);
< /script >
< /HEAD >
< cuerpo estilo = "font-family: Arial; frontera: 0 ninguno;" >
< div id = "visualización" style = "width: 800px; altura: 250px; " >< / div >
< /body >
< / html >)
La primera parte es bastante sencilla, obtener el nombre de usuario o login. Entonces se asume que el usuario quiere 1 último día de los datos, así que set bhours y ehours. Entonces literalmente a imprimir el código que copiamos de sandbox de visualización de Google, hecho!
Viz Viz Viz
Lo único que no pude averiguar es cómo hacer 3 visualizaciones sucediendo a la vez (última hora, día y semana) con el código anterior. Sólo un poco se rompió. Para el triple punto de vista tuve que utilizar iframes :(
clase VisualizeAll(webapp. RequestHandler):
DEF get(self):
# hacer que el usuario iniciar sesión si ningún nombre de usuario se suministra
Si self.request.get('user'):
cuenta = usuarios. User(self.request.get('user'))
otra cosa:
Si no users.get_current_user():
Self.Redirect(users.create_login_url(self.request.Uri))
cuenta = users.get_current_user()
() self.Response.out.Write
< h2 > uso de la energía sobre la última hora: < / h2 >
< iframe src = "gráfico? usuario = adawattz frameborder ="0"width ="100% "height ="300px">
< p > tu navegador no soporta iframes. < /p >
< / iframe >
< h2 > uso de la energía sobre el último día: < / h2 >
< iframe src = "gráfico? usuario = adawattz frameborder ="0"width ="100% "height ="300px">
< p > tu navegador no soporta iframes. < /p >
< / iframe >
< h2 > uso de la energía durante la última semana: < / h2 >
< iframe src = "gráfico? usuario = adawattz frameborder ="0"width ="300% "height ="500px">
< p > tu navegador no soporta iframes. < /p >
< / iframe >
)
De todos modos, funciona muy bien.
Códigos de tiempo!
La última cosa que no revisará aquí es cómo conseguí la fecha y tiempos que EST en lugar de UTC. Lo puedo decir, su tipo de roto y misterioso. Comprobar el código si desea descubrirlo.