diff --git a/helpers/trendings_helper.py b/helpers/trendings_helper.py index b85caca..3e70ae3 100644 --- a/helpers/trendings_helper.py +++ b/helpers/trendings_helper.py @@ -1,6 +1,7 @@ import math, random import os import json +import copy import datetime, time import logging from collections import OrderedDict @@ -158,3 +159,48 @@ class Trendings_helper: tagSet.add(tag['name']) to_ret[self.keyTag] = list(tagSet) return to_ret + + # In contrary of getGenericTrending, it regroups items in the format: {item, start: timestamp1, end: timestamp2} + # so that it can be displayed easily on the timeline. + def getGenericTrendingOvertime(self, dateS, dateE, choice=None, topNum=0): + if choice == 'categs': + trendingType = self.keyCateg + elif choice == 'tags': + trendingType = self.keyTag + else: + trendingType = self.keyEvent + + dico_items = {} + to_format = [] + prev_days = (dateE - dateS).days + # get data + for curDate in util.getXPrevDaysSpan(dateE, prev_days): + keyname = "{}:{}".format(trendingType, util.getDateStrFormat(curDate)) + data = self.serv_redis_db.zrange(keyname, 0, topNum-1, desc=True, withscores=True) + data = [ [record[0].decode('utf8'), record[1]] for record in data ] + data = data if data is not None else [] + to_format.append([util.getTimestamp(curDate), data]) + + for timestamp, array in to_format: + for item, _ in array: + if item not in dico_items: + dico_items[item] = [] + dico_items[item].append(timestamp) + + # sort timestamps in correct order + for item in dico_items.keys(): + dico_items[item].sort() + # dico_items have the form: {item: [t1,t2,t4], ...} + to_ret = [] + ONEDAY = 60*60*24 + for item, timestamps in dico_items.items(): + obj = {'name': item, 'start': timestamps[0], 'end': timestamps[0]+ONEDAY} + for t in timestamps: + if t-obj['end'] > ONEDAY: #new entry + to_ret.append(copy.deepcopy(obj)) + obj['start'] = t + obj['end'] = t+ONEDAY + else: # contrinue entry + obj['end'] = t+ONEDAY + to_ret.append(obj) + return to_ret diff --git a/helpers/users_helper.py b/helpers/users_helper.py index 815a8e3..5dd9994 100644 --- a/helpers/users_helper.py +++ b/helpers/users_helper.py @@ -51,7 +51,7 @@ class Users_helper: keyname = "{}:{}".format(self.keyTimestamp, org) timestamps = self.serv_redis_db.zrange(keyname, 0, -1, desc=True, withscores=True) if date is None: - to_return = [ t[1] for t in timestamps ] + to_return = [ datetime.datetime.fromtimestamp(float(t[1])) for t in timestamps ] else: to_return = [] for t in timestamps: @@ -165,7 +165,7 @@ class Users_helper: data.append(to_append) except KeyError: # no data data.append([0 for x in range(24)]) - # swap: punchcard day starts on monday + # swap: punchcard day starts on sunday data = [data[6]]+data[:6] return data diff --git a/install_dependencies.sh b/install_dependencies.sh index 420fade..2059892 100755 --- a/install_dependencies.sh +++ b/install_dependencies.sh @@ -108,4 +108,9 @@ mv temp/jquery-punchcard/src/punchcard.js ./static/js mv temp/jquery-punchcard/src/punchcard.css ./static/css wget https://momentjs.com/downloads/moment.js -O ./static/js/moment.js +# timeline +VISJS_VERSION="4.21.0" +wget https://cdnjs.cloudflare.com/ajax/libs/vis/${VISJS_VERSION}/vis.min.js ./static/js/vis.min.js +wget https://cdnjs.cloudflare.com/ajax/libs/vis/${VISJS_VERSION}/vis.min.css ./static/css/vis.min.css + rm -rf ./temp diff --git a/server.py b/server.py index ed187d5..ffdd7e1 100755 --- a/server.py +++ b/server.py @@ -539,5 +539,18 @@ def getTypeaheadData(): data = trendings_helper.getTypeaheadData(dateS, dateE) return jsonify(data) +@app.route("/_getGenericTrendingOvertime") +def getGenericTrendingOvertime(): + try: + dateS = datetime.datetime.fromtimestamp(float(request.args.get('dateS'))) + dateE = datetime.datetime.fromtimestamp(float(request.args.get('dateE'))) + except: + dateS = datetime.datetime.now() - datetime.timedelta(days=7) + dateE = datetime.datetime.now() + choice = request.args.get('choice', 'events') + + data = trendings_helper.getGenericTrendingOvertime(dateS, dateE, choice) + return jsonify(data) + if __name__ == '__main__': app.run(host=server_host, port=server_port, threaded=True) diff --git a/static/js/trendings.js b/static/js/trendings.js index 79741e9..f1ce084 100644 --- a/static/js/trendings.js +++ b/static/js/trendings.js @@ -9,6 +9,7 @@ var tagPie = ["#tagPie"]; var tagLine = ["#tagLine"]; var sightingLineWidget; var discLine = ["#discussionLine"]; +var timeline; var allData; var globalColorMapping = {}; @@ -103,6 +104,14 @@ var typeaheadOption_tag = { updateLineForLabel(tagLine, tag, undefined, url_getTrendingTag); } } +var timeline_option = { + groupOrder: 'content', + maxHeight: '94vh', + verticalScroll: true, + horizontalScroll: true, + zoomKey: 'ctrlKey', +}; + /* FUNCTIONS */ function getColor(label) { @@ -134,7 +143,18 @@ function getTextColour(rgb) { return 'black'; } } -function legendFormatter(label, series) { + +// If json (from tag), only retreive the name> otherwise return the supplied arg. +function getOnlyName(potentialJson) { + try { + jsonLabel = JSON.parse(potentialJson); + return jsonLabel.name; + } catch(err) { + return potentialJson; + } +} + +function legendFormatter(label) { try { jsonLabel = JSON.parse(label); var backgroundColor = jsonLabel.colour; @@ -156,7 +176,7 @@ function legendFormatter(label, series) { } return '
' - + ' ' + labelLimited + + ' ' + labelLimited + ''; + '
'; } @@ -396,6 +416,43 @@ function updateDisc() { }); } +function updateTimeline() { + var selected = $( "#timeline_selector" ).val(); + $.getJSON( url_getGenericTrendingOvertime+"?dateS="+parseInt(dateStart.getTime()/1000)+"&dateE="+parseInt(dateEnd.getTime()/1000)+"&choice="+selected, function( data ) { + var items = []; + var groups = new vis.DataSet(); + var dico_groups = {}; + var i = 1; + var g = 1; + for (var obj of data) { + var index = dico_groups[obj.name]; + if (index == undefined) { // new group + index = groups.add({id: g, content: legendFormatter(obj.name)}); + dico_groups[obj.name] = g; + g++; + } + items.push({ + id: i, + content: getOnlyName(obj.name), + start: obj.start*1000, + end: obj.end*1000, + group: dico_groups[obj.name] + }); + i++; + } + items = new vis.DataSet(items); + if (timeline === undefined) { // create timeline + timeline = new vis.Timeline(document.getElementById('timeline')); + } + var dateEndExtended = new Date(dateEnd).setDate(dateEnd.getDate()+1); // dateEnd+1 + timeline_option.start = dateStart; + timeline_option.end = dateEndExtended; + timeline.setOptions(timeline_option); + timeline.setGroups(groups); + timeline.setItems(items); + }); +} + function dateChanged() { dateStart = datePickerWidgetStart.datepicker( "getDate" ); dateEnd = datePickerWidgetEnd.datepicker( "getDate" ); @@ -404,6 +461,7 @@ function dateChanged() { updatePieLine(tagPie, tagLine, url_getTrendingTag); updateSignthingsChart(); updateDisc(); + updateTimeline(); } $(document).ready(function () { @@ -426,6 +484,7 @@ $(document).ready(function () { updatePieLine(tagPie, tagLine, url_getTrendingTag) updateSignthingsChart(); updateDisc(); + updateTimeline(); $( "#num_selector" ).change(function() { var sel = parseInt($( this ).val()); @@ -433,9 +492,12 @@ $(document).ready(function () { window.location.href = url_currentPage+'?maxNum='+maxNum; }); + $( "#timeline_selector" ).change(function() { + updateTimeline(); + }); + $("
").css({ position: "absolute", display: "none", }).appendTo("body"); - }); diff --git a/templates/geo.html b/templates/geo.html index 1579801..8023a23 100644 --- a/templates/geo.html +++ b/templates/geo.html @@ -38,6 +38,12 @@