Add circle chart for other stats

This commit is contained in:
Ruben 2017-03-08 11:09:39 +01:00
parent 528f4016b0
commit 5df9089b42
4 changed files with 200 additions and 20 deletions

73
charts.py Normal file
View file

@ -0,0 +1,73 @@
from svg.charts import pie
import math
from lxml import etree
def createPie(data, gaps = 0):
total = sum(data.values())
colours = ['#268ED7','#6A4F8D', '#A03D4F','#DC7432','#F6DC3B','#76B33B', '#267B60']
svg = '<svg viewBox="-115 -115 230 230" xmlns="http://www.w3.org/2000/svg">'
numbers = ""
position = 90
i = 0
for e in sorted(data.keys()):
data[e] = data[e] / total
arc = data[e] * 360.;
diff = polarToCartesian(0,0,5, position+0.5*arc) if gaps > 0 else {'x':0, 'y': 0}
d = describeArc(float(diff['x']), float(diff['y']), 60, position, position+arc)
# print(position, arc)
svg += '<path fill="none" stroke="%s" stroke-width="60" d="%s" />' % (colours[i%len(colours)], d)
t = polarToCartesian(0,0, 100, position+0.5*arc)
numbers += '<text x="%s" y="%s" font-family="sans-serif" font-size="14" style=" stroke:color: black;" text-anchor="middle" transform="rotate(%s %s,%s)">%s</text>' % (t['x'], t['y'], position+0.5*arc, t['x'],t['y'], e)
position += arc
i+=1
svg += numbers
svg += "</svg>"
return svg
# data is now in fractions
def polarToCartesian(centerX, centerY, radius, angleInDegrees):
"""
Adapted from http://stackoverflow.com/a/18473154
"""
angleInRadians = (angleInDegrees - 90) * math.pi / 180.0;
return {
"x": str(centerX + (radius * math.cos(angleInRadians))),
"y": str(centerY + (radius * math.sin(angleInRadians)))
}
def describeArc(x, y, radius, startAngle, endAngle):
"""
Adapted from http://stackoverflow.com/a/18473154
"""
start = polarToCartesian(x, y, radius, endAngle)
end = polarToCartesian(x, y, radius, startAngle)
largeArcFlag = "0" if endAngle - startAngle <= 180 else "1";
d = " ".join([
"M", start['x'], start['y'],
"A", str(radius), str(radius), "0", largeArcFlag, "0", end['x'], end['y']
]);
return d
# g = pie.Pie({})
# options = dict(
# width=640,
# height=480,
# fields=list(data.keys()),
# graph_title='Question 7',
# # expand_greatest = True,
# show_data_labels = True,
# compress=False,
# css_inline=True,
# )
# g.__dict__.update(options)
# g.add_data({'data': list(data.values()), 'title': 'Female'})
# # g.add_data({'data': [0, 2, 1, 5, 4], 'title': 'Male'})
# return str(g.burn())

View file

@ -47,3 +47,34 @@ class Artwork(BaseModel):
def getAges():
r = Artwork.select(fn.Distinct(Artwork.age)).dicts()
return [a['age'] for a in r]
def getEmotionCountsFromArtworks(artworks):
emotions = {}
for artwork in artworks:
e = artwork.emotion.name
if e in emotions:
emotions[e] += 1
else:
emotions[e] = 1
return emotions
def getEmotionFractionsFromArtworks(artworks):
emotions = getEmotionCountsFromArtworks(artworks)
total = sum(emotions.values())
for e in emotions:
emotions[e] = emotions[e] / total
return emotions
def getAgeFractionsFromArtworks(artworks):
ages = {}
for artwork in artworks:
age = artwork.age
if age in ages:
ages[age] += 1
else:
ages[age] = 1
total = sum(ages.values())
for age in ages:
ages[age] = ages[age] / total
return ages

View file

@ -3,6 +3,9 @@ import os
import tornado.ioloop
import tornado.web
import colour
import charts
from PIL import Image
import io
models.db.connect()
@ -25,12 +28,26 @@ class MainHandler(tornado.web.RequestHandler):
html = loader.load('index.html').generate(host=self.request.host, emotions=emotions, ages=models.getAges(), groups=models.Group.select())
self.write(html)
class ThumbHandler(tornado.web.RequestHandler):
def get(self, imgId):
artwork = models.Artwork.select().where(models.Artwork.id == imgId).get()
if artwork is None:
self.send_error(404)
else:
img = Image.open(artwork.filename)
img.thumbnail((200,200))
fp = io.BytesIO()
img.save(fp, "jpeg")
fp.seek(0)
self.set_header("Content-Type","image/jpeg")
self.set_header("Cache-Control","max-age=2592000, public")
self.write(fp.read())
class ColourHandler(tornado.web.RequestHandler):
def get(self):
req_group = self.get_query_argument("group", default=None)
req_age = self.get_query_argument("age", default=None)
req_emotion = self.get_query_argument("emotion", default=None)
req_elId = self.get_query_argument("elId", default="")
q = models.Artwork.select()
if req_group is not None:
@ -42,14 +59,39 @@ class ColourHandler(tornado.web.RequestHandler):
emotion = models.Emotion.get(models.Emotion.id == req_emotion)
q = q.where(models.Artwork.emotion == emotion)
images = q
svg = colour.getSvgFromDbImages(images, elId = req_elId)
self.write(svg)
svg = colour.getSvgFromDbImages(images)
imgHtml = "<aside id='works'>";
# for artwork in images:
# imgHtml += "<img src='/thumbs/%s'>" % artwork.id
imgHtml += "</aside>";
statHtml = "<aside id='stats'>";
if not req_emotion:
statHtml += "<h4>Emoties</h4>"
statHtml += charts.createPie(models.getEmotionFractionsFromArtworks(images))
else:
statHtml += "<h4>Leeftijd</h4>"
statHtml += charts.createPie(models.getAgeFractionsFromArtworks(images))
statHtml += "<h4>Gezichten</h4>"
statHtml += "Geen gezichten gevonden"
# statHtml += "<h4>Meest voorkomende woorden</h4>"
# statHtml += "Geen woorden gevonden"
# TODO: licht/donker
# if not req_group:
statHtml += "</aside>";
self.write("\n".join([svg, imgHtml, statHtml]) )
if __name__ == "__main__":
print("Start server", config)
app = tornado.web.Application([
(r"/", MainHandler),
(r"/colours", ColourHandler),
])
(r"/thumbs/(\d+)", ThumbHandler),
], debug=True)
app.listen(config['port'])
tornado.ioloop.IOLoop.current().start()

View file

@ -7,6 +7,8 @@
background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QAdwB3AHctbh3qAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QMFFQkN1CxOCgAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAXElEQVRo3u3YQRHAIAxFwVAT1L85gopgoNdc6D4DmZ1/y8hcFc3N+XafiCcuCQQEBAQEBAQEBAQEBAQEBATkV5BRVe0Pur3TIiAgICAgICAgICAgICAgICAgIB8dr84LFaZAYuYAAAAASUVORK5CYII=');
font-family: monospace;
font-size: 12pt;
height:100%;
margin:0;
}
body.loading #colourImage{
opacity: 0;
@ -17,6 +19,9 @@
position: absolute;
left:0;top:0;right:0;bottom: 0;
}
aside{
padding:0.3em;
}
aside ul{
list-style: none;
}
@ -42,8 +47,26 @@
float:left;
}
aside#works{
display:none;
float:left;
width:5em;
overflow-y: auto;
height:100vh;
margin-left:1em;
}
aside#works img{
width:100%;
}
aside#stats{
float:right;
text-align: center;
padding-right:1em;
}
aside#stats svg{
width:15em;
height:15em;
}
#spinner{
@ -123,10 +146,11 @@
}
function transferComplete(evt, r)
{
// console.log(evt);
var div = document.createElement('div');
div.innerHTML = evt.target.response;
document.getElementById('colourImage').innerHTML = div.children[0].innerHTML;
document.getElementById('works').innerHTML = div.children[1].innerHTML;
document.getElementById('stats').innerHTML = div.children[2].innerHTML;
document.body.classList.remove('loading');
}
function transferFailed(evt)
@ -139,26 +163,36 @@
}
var links = document.getElementById('selects').getElementsByTagName("li");
for(let link of links)
function loadResults(type, value)
{
link.onclick = function() {
document.body.classList.add('loading');
// document.getElementById('colourImage').innerHTML = "";
console.log('click',this.dataset.param, this.dataset.id);
var oReq = new XMLHttpRequest();
oReq.addEventListener("progress", updateProgress);
oReq.addEventListener("load", transferComplete);
oReq.addEventListener("error", transferFailed);
oReq.addEventListener("abort", transferCanceled);
oReq.open("GET", "/colours?"+this.dataset.param+"="+this.dataset.id+"&elId=colourImage");
oReq.open("GET", "/colours?"+type+"="+value);
oReq.send()
for(let unsetLink of links) {
if(unsetLink.dataset.param == type && unsetLink.dataset.id == value) {
unsetLink.classList.add('selected');
} else {
unsetLink.classList.remove('selected');
}
this.classList.add('selected');
}
}
for(let link of links)
{
link.onclick = function() {
loadResults(this.dataset.param, this.dataset.id);
}.bind(link)
}
loadResults("no", "thing");
// var colourImage = document.getElementById('colourImage');
// var i = 0;
// for(let circle of colourImage.children) {