diff --git a/charts.py b/charts.py new file mode 100644 index 0000000..0107a61 --- /dev/null +++ b/charts.py @@ -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 = '' + 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 += '' % (colours[i%len(colours)], d) + t = polarToCartesian(0,0, 100, position+0.5*arc) + numbers += '%s' % (t['x'], t['y'], position+0.5*arc, t['x'],t['y'], e) + position += arc + i+=1 + + svg += numbers + 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()) \ No newline at end of file diff --git a/models.py b/models.py index 52b6eff..c6f1ce7 100644 --- a/models.py +++ b/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 \ No newline at end of file diff --git a/server.py b/server.py index a351799..7164d30 100644 --- a/server.py +++ b/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 = ""; + + statHtml = ""; + + 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() diff --git a/templates/index.html b/templates/index.html index 687c60f..a163d08 100644 --- a/templates/index.html +++ b/templates/index.html @@ -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"); + + 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) { 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.send() - - for(let unsetLink of links) { - unsetLink.classList.remove('selected'); - } - this.classList.add('selected'); + 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) {