Add circle chart for other stats
This commit is contained in:
parent
528f4016b0
commit
5df9089b42
4 changed files with 200 additions and 20 deletions
73
charts.py
Normal file
73
charts.py
Normal 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())
|
31
models.py
31
models.py
|
@ -47,3 +47,34 @@ class Artwork(BaseModel):
|
||||||
def getAges():
|
def getAges():
|
||||||
r = Artwork.select(fn.Distinct(Artwork.age)).dicts()
|
r = Artwork.select(fn.Distinct(Artwork.age)).dicts()
|
||||||
return [a['age'] for a in r]
|
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
|
50
server.py
50
server.py
|
@ -3,6 +3,9 @@ import os
|
||||||
import tornado.ioloop
|
import tornado.ioloop
|
||||||
import tornado.web
|
import tornado.web
|
||||||
import colour
|
import colour
|
||||||
|
import charts
|
||||||
|
from PIL import Image
|
||||||
|
import io
|
||||||
|
|
||||||
models.db.connect()
|
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())
|
html = loader.load('index.html').generate(host=self.request.host, emotions=emotions, ages=models.getAges(), groups=models.Group.select())
|
||||||
self.write(html)
|
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):
|
class ColourHandler(tornado.web.RequestHandler):
|
||||||
def get(self):
|
def get(self):
|
||||||
req_group = self.get_query_argument("group", default=None)
|
req_group = self.get_query_argument("group", default=None)
|
||||||
req_age = self.get_query_argument("age", default=None)
|
req_age = self.get_query_argument("age", default=None)
|
||||||
req_emotion = self.get_query_argument("emotion", default=None)
|
req_emotion = self.get_query_argument("emotion", default=None)
|
||||||
req_elId = self.get_query_argument("elId", default="")
|
|
||||||
|
|
||||||
q = models.Artwork.select()
|
q = models.Artwork.select()
|
||||||
if req_group is not None:
|
if req_group is not None:
|
||||||
|
@ -42,14 +59,39 @@ class ColourHandler(tornado.web.RequestHandler):
|
||||||
emotion = models.Emotion.get(models.Emotion.id == req_emotion)
|
emotion = models.Emotion.get(models.Emotion.id == req_emotion)
|
||||||
q = q.where(models.Artwork.emotion == emotion)
|
q = q.where(models.Artwork.emotion == emotion)
|
||||||
images = q
|
images = q
|
||||||
svg = colour.getSvgFromDbImages(images, elId = req_elId)
|
svg = colour.getSvgFromDbImages(images)
|
||||||
self.write(svg)
|
|
||||||
|
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__":
|
if __name__ == "__main__":
|
||||||
print("Start server", config)
|
print("Start server", config)
|
||||||
app = tornado.web.Application([
|
app = tornado.web.Application([
|
||||||
(r"/", MainHandler),
|
(r"/", MainHandler),
|
||||||
(r"/colours", ColourHandler),
|
(r"/colours", ColourHandler),
|
||||||
])
|
(r"/thumbs/(\d+)", ThumbHandler),
|
||||||
|
], debug=True)
|
||||||
app.listen(config['port'])
|
app.listen(config['port'])
|
||||||
tornado.ioloop.IOLoop.current().start()
|
tornado.ioloop.IOLoop.current().start()
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QAdwB3AHctbh3qAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QMFFQkN1CxOCgAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAXElEQVRo3u3YQRHAIAxFwVAT1L85gopgoNdc6D4DmZ1/y8hcFc3N+XafiCcuCQQEBAQEBAQEBAQEBAQEBATkV5BRVe0Pur3TIiAgICAgICAgICAgICAgICAgIB8dr84LFaZAYuYAAAAASUVORK5CYII=');
|
background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAABmJLR0QAdwB3AHctbh3qAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QMFFQkN1CxOCgAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAXElEQVRo3u3YQRHAIAxFwVAT1L85gopgoNdc6D4DmZ1/y8hcFc3N+XafiCcuCQQEBAQEBAQEBAQEBAQEBATkV5BRVe0Pur3TIiAgICAgICAgICAgICAgICAgIB8dr84LFaZAYuYAAAAASUVORK5CYII=');
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
|
height:100%;
|
||||||
|
margin:0;
|
||||||
}
|
}
|
||||||
body.loading #colourImage{
|
body.loading #colourImage{
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
@ -17,6 +19,9 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left:0;top:0;right:0;bottom: 0;
|
left:0;top:0;right:0;bottom: 0;
|
||||||
}
|
}
|
||||||
|
aside{
|
||||||
|
padding:0.3em;
|
||||||
|
}
|
||||||
aside ul{
|
aside ul{
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
@ -42,8 +47,26 @@
|
||||||
float:left;
|
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{
|
aside#stats{
|
||||||
float:right;
|
float:right;
|
||||||
|
text-align: center;
|
||||||
|
padding-right:1em;
|
||||||
|
}
|
||||||
|
aside#stats svg{
|
||||||
|
width:15em;
|
||||||
|
height:15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#spinner{
|
#spinner{
|
||||||
|
@ -123,10 +146,11 @@
|
||||||
}
|
}
|
||||||
function transferComplete(evt, r)
|
function transferComplete(evt, r)
|
||||||
{
|
{
|
||||||
// console.log(evt);
|
|
||||||
var div = document.createElement('div');
|
var div = document.createElement('div');
|
||||||
div.innerHTML = evt.target.response;
|
div.innerHTML = evt.target.response;
|
||||||
document.getElementById('colourImage').innerHTML = div.children[0].innerHTML;
|
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');
|
document.body.classList.remove('loading');
|
||||||
}
|
}
|
||||||
function transferFailed(evt)
|
function transferFailed(evt)
|
||||||
|
@ -139,26 +163,36 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
var links = document.getElementById('selects').getElementsByTagName("li");
|
var links = document.getElementById('selects').getElementsByTagName("li");
|
||||||
|
|
||||||
|
function loadResults(type, value)
|
||||||
|
{
|
||||||
|
document.body.classList.add('loading');
|
||||||
|
// document.getElementById('colourImage').innerHTML = "";
|
||||||
|
var oReq = new XMLHttpRequest();
|
||||||
|
oReq.addEventListener("progress", updateProgress);
|
||||||
|
oReq.addEventListener("load", transferComplete);
|
||||||
|
oReq.addEventListener("error", transferFailed);
|
||||||
|
oReq.addEventListener("abort", transferCanceled);
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(let link of links)
|
for(let link of links)
|
||||||
{
|
{
|
||||||
link.onclick = function() {
|
link.onclick = function() {
|
||||||
document.body.classList.add('loading');
|
loadResults(this.dataset.param, this.dataset.id);
|
||||||
// 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.send()
|
|
||||||
|
|
||||||
for(let unsetLink of links) {
|
|
||||||
unsetLink.classList.remove('selected');
|
|
||||||
}
|
|
||||||
this.classList.add('selected');
|
|
||||||
}.bind(link)
|
}.bind(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadResults("no", "thing");
|
||||||
// var colourImage = document.getElementById('colourImage');
|
// var colourImage = document.getElementById('colourImage');
|
||||||
// var i = 0;
|
// var i = 0;
|
||||||
// for(let circle of colourImage.children) {
|
// for(let circle of colourImage.children) {
|
||||||
|
|
Loading…
Reference in a new issue