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():
|
||||
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
|
50
server.py
50
server.py
|
@ -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()
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue