mirror of https://github.com/CIRCL/AIL-framework
chg: [chats] factorise heatmap + chat icon
parent
9fbd3f4bb6
commit
2b8e9b43f3
|
@ -89,17 +89,26 @@ class FeederImporter(AbstractImporter):
|
||||||
feeder_name = feeder.get_name()
|
feeder_name = feeder.get_name()
|
||||||
print(f'importing: {feeder_name} feeder')
|
print(f'importing: {feeder_name} feeder')
|
||||||
|
|
||||||
obj = feeder.get_obj() # TODO replace by a list of objects to import ????
|
# Get Data object:
|
||||||
|
data_obj = feeder.get_obj()
|
||||||
|
|
||||||
# process meta
|
# process meta
|
||||||
if feeder.get_json_meta():
|
if feeder.get_json_meta():
|
||||||
feeder.process_meta()
|
objs = feeder.process_meta()
|
||||||
|
if objs is None:
|
||||||
|
objs = set()
|
||||||
|
else:
|
||||||
|
objs = set()
|
||||||
|
|
||||||
if obj.type == 'item': # object save on disk as file (Items)
|
objs.add(data_obj)
|
||||||
gzip64_content = feeder.get_gzip64_content()
|
|
||||||
return obj, f'{feeder_name} {gzip64_content}'
|
for obj in objs:
|
||||||
else: # Messages save on DB
|
if obj.type == 'item': # object save on disk as file (Items)
|
||||||
if obj.exists():
|
gzip64_content = feeder.get_gzip64_content()
|
||||||
return obj, f'{feeder_name}'
|
return obj, f'{feeder_name} {gzip64_content}'
|
||||||
|
else: # Messages save on DB
|
||||||
|
if obj.exists():
|
||||||
|
return obj, f'{feeder_name}'
|
||||||
|
|
||||||
|
|
||||||
class FeederModuleImporter(AbstractModule):
|
class FeederModuleImporter(AbstractModule):
|
||||||
|
|
|
@ -33,3 +33,4 @@ class BgpMonitorFeeder(DefaultFeeder):
|
||||||
tag = 'infoleak:automatic-detection=bgp_monitor'
|
tag = 'infoleak:automatic-detection=bgp_monitor'
|
||||||
item = Item(self.get_item_id())
|
item = Item(self.get_item_id())
|
||||||
item.add_tag(tag)
|
item.add_tag(tag)
|
||||||
|
return set()
|
||||||
|
|
|
@ -84,4 +84,4 @@ class DefaultFeeder:
|
||||||
Process JSON meta filed.
|
Process JSON meta filed.
|
||||||
"""
|
"""
|
||||||
# meta = self.get_json_meta()
|
# meta = self.get_json_meta()
|
||||||
pass
|
return set()
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*-coding:UTF-8 -*
|
||||||
|
"""
|
||||||
|
The Telegram Feeder Importer Module
|
||||||
|
================
|
||||||
|
|
||||||
|
Process Telegram JSON
|
||||||
|
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
sys.path.append(os.environ['AIL_BIN'])
|
||||||
|
##################################
|
||||||
|
# Import Project packages
|
||||||
|
##################################
|
||||||
|
from importer.feeders.abstract_chats_feeder import AbstractChatFeeder
|
||||||
|
from lib.ConfigLoader import ConfigLoader
|
||||||
|
from lib.objects import ail_objects
|
||||||
|
from lib.objects.Chats import Chat
|
||||||
|
from lib.objects import Messages
|
||||||
|
from lib.objects import UsersAccount
|
||||||
|
from lib.objects.Usernames import Username
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
|
class DiscordFeeder(AbstractChatFeeder):
|
||||||
|
|
||||||
|
def __init__(self, json_data):
|
||||||
|
super().__init__('discord', json_data)
|
||||||
|
|
||||||
|
# def get_obj(self):.
|
||||||
|
# obj_id = Messages.create_obj_id('telegram', chat_id, message_id, timestamp)
|
||||||
|
# obj_id = f'message:telegram:{obj_id}'
|
||||||
|
# self.obj = ail_objects.get_obj_from_global_id(obj_id)
|
||||||
|
# return self.obj
|
||||||
|
|
|
@ -52,4 +52,4 @@ class JabberFeeder(DefaultFeeder):
|
||||||
user_fr = Username(fr, 'jabber')
|
user_fr = Username(fr, 'jabber')
|
||||||
user_to.add(date, item)
|
user_to.add(date, item)
|
||||||
user_fr.add(date, item)
|
user_fr.add(date, item)
|
||||||
return None
|
return set()
|
||||||
|
|
|
@ -45,4 +45,4 @@ class TwitterFeeder(DefaultFeeder):
|
||||||
user = str(self.json_data['meta']['twitter:id'])
|
user = str(self.json_data['meta']['twitter:id'])
|
||||||
username = Username(user, 'twitter')
|
username = Username(user, 'twitter')
|
||||||
username.add(date, item)
|
username.add(date, item)
|
||||||
return None
|
return set()
|
||||||
|
|
|
@ -56,3 +56,5 @@ class UrlextractFeeder(DefaultFeeder):
|
||||||
item = Item(self.item_id)
|
item = Item(self.item_id)
|
||||||
item.set_parent(parent_id)
|
item.set_parent(parent_id)
|
||||||
|
|
||||||
|
return set()
|
||||||
|
|
||||||
|
|
|
@ -131,7 +131,7 @@ class AbstractChatFeeder(DefaultFeeder, ABC):
|
||||||
self.obj = Messages.Message(obj_id)
|
self.obj = Messages.Message(obj_id)
|
||||||
return self.obj
|
return self.obj
|
||||||
|
|
||||||
def process_chat(self, obj, date, timestamp, reply_id=None): # TODO threads
|
def process_chat(self, new_objs, obj, date, timestamp, reply_id=None): # TODO threads
|
||||||
meta = self.json_data['meta']['chat'] # todo replace me by function
|
meta = self.json_data['meta']['chat'] # todo replace me by function
|
||||||
chat = Chat(self.get_chat_id(), self.get_chat_instance_uuid())
|
chat = Chat(self.get_chat_id(), self.get_chat_instance_uuid())
|
||||||
|
|
||||||
|
@ -147,6 +147,12 @@ class AbstractChatFeeder(DefaultFeeder, ABC):
|
||||||
if meta.get('date'): # TODO check if already exists
|
if meta.get('date'): # TODO check if already exists
|
||||||
chat.set_created_at(int(meta['date']['timestamp']))
|
chat.set_created_at(int(meta['date']['timestamp']))
|
||||||
|
|
||||||
|
if meta.get('icon'):
|
||||||
|
img = Images.create(meta['icon'], b64=True)
|
||||||
|
img.add(date, chat)
|
||||||
|
chat.set_icon(img.get_global_id())
|
||||||
|
new_objs.add(img)
|
||||||
|
|
||||||
if meta.get('username'):
|
if meta.get('username'):
|
||||||
username = Username(meta['username'], self.get_chat_protocol())
|
username = Username(meta['username'], self.get_chat_protocol())
|
||||||
chat.update_username_timeline(username.get_global_id(), timestamp)
|
chat.update_username_timeline(username.get_global_id(), timestamp)
|
||||||
|
@ -228,6 +234,7 @@ class AbstractChatFeeder(DefaultFeeder, ABC):
|
||||||
objs = set()
|
objs = set()
|
||||||
if self.obj:
|
if self.obj:
|
||||||
objs.add(self.obj)
|
objs.add(self.obj)
|
||||||
|
new_objs = set()
|
||||||
|
|
||||||
date, timestamp = self.get_message_date_timestamp()
|
date, timestamp = self.get_message_date_timestamp()
|
||||||
|
|
||||||
|
@ -261,7 +268,7 @@ class AbstractChatFeeder(DefaultFeeder, ABC):
|
||||||
for obj in objs: # TODO PERF avoid parsing metas multiple times
|
for obj in objs: # TODO PERF avoid parsing metas multiple times
|
||||||
|
|
||||||
# CHAT
|
# CHAT
|
||||||
chat = self.process_chat(obj, date, timestamp, reply_id=reply_id)
|
chat = self.process_chat(new_objs, obj, date, timestamp, reply_id=reply_id)
|
||||||
|
|
||||||
# SENDER # TODO HANDLE NULL SENDER
|
# SENDER # TODO HANDLE NULL SENDER
|
||||||
user_account = self.process_sender(obj, date, timestamp)
|
user_account = self.process_sender(obj, date, timestamp)
|
||||||
|
@ -279,6 +286,8 @@ class AbstractChatFeeder(DefaultFeeder, ABC):
|
||||||
# -> subchannel ?
|
# -> subchannel ?
|
||||||
# -> thread id ?
|
# -> thread id ?
|
||||||
|
|
||||||
|
return new_objs | objs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -175,7 +175,7 @@ class ChatServiceInstance:
|
||||||
if 'chats' in options:
|
if 'chats' in options:
|
||||||
meta['chats'] = []
|
meta['chats'] = []
|
||||||
for chat_id in self.get_chats():
|
for chat_id in self.get_chats():
|
||||||
meta['chats'].append(Chats.Chat(chat_id, self.uuid).get_meta({'created_at', 'nb_subchannels'}))
|
meta['chats'].append(Chats.Chat(chat_id, self.uuid).get_meta({'created_at', 'icon', 'nb_subchannels'}))
|
||||||
return meta
|
return meta
|
||||||
|
|
||||||
def get_nb_chats(self):
|
def get_nb_chats(self):
|
||||||
|
@ -304,7 +304,7 @@ def api_get_chat(chat_id, chat_instance_uuid):
|
||||||
chat = Chats.Chat(chat_id, chat_instance_uuid)
|
chat = Chats.Chat(chat_id, chat_instance_uuid)
|
||||||
if not chat.exists():
|
if not chat.exists():
|
||||||
return {"status": "error", "reason": "Unknown chat"}, 404
|
return {"status": "error", "reason": "Unknown chat"}, 404
|
||||||
meta = chat.get_meta({'created_at', 'img', 'info', 'subchannels', 'username'})
|
meta = chat.get_meta({'created_at', 'icon', 'info', 'subchannels', 'username'})
|
||||||
if meta['username']:
|
if meta['username']:
|
||||||
meta['username'] = get_username_meta_from_global_id(meta['username'])
|
meta['username'] = get_username_meta_from_global_id(meta['username'])
|
||||||
if meta['subchannels']:
|
if meta['subchannels']:
|
||||||
|
@ -325,7 +325,7 @@ def api_get_subchannel(chat_id, chat_instance_uuid):
|
||||||
subchannel = ChatSubChannels.ChatSubChannel(chat_id, chat_instance_uuid)
|
subchannel = ChatSubChannels.ChatSubChannel(chat_id, chat_instance_uuid)
|
||||||
if not subchannel.exists():
|
if not subchannel.exists():
|
||||||
return {"status": "error", "reason": "Unknown chat"}, 404
|
return {"status": "error", "reason": "Unknown chat"}, 404
|
||||||
meta = subchannel.get_meta({'chat', 'created_at', 'img', 'nb_messages'})
|
meta = subchannel.get_meta({'chat', 'created_at', 'icon', 'nb_messages'})
|
||||||
if meta['chat']:
|
if meta['chat']:
|
||||||
meta['chat'] = get_chat_meta_from_global_id(meta['chat'])
|
meta['chat'] = get_chat_meta_from_global_id(meta['chat'])
|
||||||
if meta.get('username'):
|
if meta.get('username'):
|
||||||
|
|
|
@ -74,8 +74,8 @@ class Chat(AbstractChatObject):
|
||||||
meta = self._get_meta(options=options)
|
meta = self._get_meta(options=options)
|
||||||
meta['name'] = self.get_name()
|
meta['name'] = self.get_name()
|
||||||
meta['tags'] = self.get_tags(r_list=True)
|
meta['tags'] = self.get_tags(r_list=True)
|
||||||
if 'img':
|
if 'icon':
|
||||||
meta['icon'] = self.get_img()
|
meta['icon'] = self.get_icon()
|
||||||
if 'info':
|
if 'info':
|
||||||
meta['info'] = self.get_info()
|
meta['info'] = self.get_info()
|
||||||
if 'username' in options:
|
if 'username' in options:
|
||||||
|
|
|
@ -113,11 +113,13 @@ class AbstractChatObject(AbstractSubtypeObject, ABC):
|
||||||
def set_name(self, name):
|
def set_name(self, name):
|
||||||
self._set_field('name', name)
|
self._set_field('name', name)
|
||||||
|
|
||||||
def get_img(self):
|
def get_icon(self):
|
||||||
return self._get_field('img')
|
icon = self._get_field('icon')
|
||||||
|
if icon:
|
||||||
|
return icon.rsplit(':', 1)[1]
|
||||||
|
|
||||||
def set_img(self, icon):
|
def set_icon(self, icon):
|
||||||
self._set_field('img', icon)
|
self._set_field('icon', icon)
|
||||||
|
|
||||||
def get_info(self):
|
def get_info(self):
|
||||||
return self._get_field('info')
|
return self._get_field('info')
|
||||||
|
@ -187,7 +189,7 @@ class AbstractChatObject(AbstractSubtypeObject, ABC):
|
||||||
tags = {}
|
tags = {}
|
||||||
messages = {}
|
messages = {}
|
||||||
curr_date = None
|
curr_date = None
|
||||||
for message in self._get_messages(nb=10, page=3):
|
for message in self._get_messages(nb=30, page=1):
|
||||||
timestamp = message[1]
|
timestamp = message[1]
|
||||||
date_day = datetime.fromtimestamp(timestamp).strftime('%Y/%m/%d')
|
date_day = datetime.fromtimestamp(timestamp).strftime('%Y/%m/%d')
|
||||||
if date_day != curr_date:
|
if date_day != curr_date:
|
||||||
|
|
|
@ -42,6 +42,8 @@ class Exif(AbstractModule):
|
||||||
img_exif = img.getexif()
|
img_exif = img.getexif()
|
||||||
print(img_exif)
|
print(img_exif)
|
||||||
if img_exif:
|
if img_exif:
|
||||||
|
gps = img_exif.get(34853)
|
||||||
|
print(gps)
|
||||||
for key, val in img_exif.items():
|
for key, val in img_exif.items():
|
||||||
if key in ExifTags.TAGS:
|
if key in ExifTags.TAGS:
|
||||||
print(f'{ExifTags.TAGS[key]}:{val}')
|
print(f'{ExifTags.TAGS[key]}:{val}')
|
||||||
|
|
|
@ -40,7 +40,7 @@ def image(filename):
|
||||||
abort(404)
|
abort(404)
|
||||||
filename = filename.replace('/', '')
|
filename = filename.replace('/', '')
|
||||||
image = Images.Image(filename)
|
image = Images.Image(filename)
|
||||||
return send_from_directory(Images.IMAGE_FOLDER, image.get_rel_path(), as_attachment=True)
|
return send_from_directory(Images.IMAGE_FOLDER, image.get_rel_path(), as_attachment=False, mimetype='image')
|
||||||
|
|
||||||
|
|
||||||
@objects_image.route("/objects/images", methods=['GET'])
|
@objects_image.route("/objects/images", methods=['GET'])
|
||||||
|
|
|
@ -58,7 +58,9 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if message['images'] %}
|
{% if message['images'] %}
|
||||||
<img class="message_image mb-1" src="{{ url_for('objects_image.image', filename=message['images'][0])}}">
|
{% for message_image in message['images'] %}
|
||||||
|
<img class="message_image mb-1" src="{{ url_for('objects_image.image', filename=message_image)}}">
|
||||||
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<pre class="my-0">{{ message['content'] }}</pre>
|
<pre class="my-0">{{ message['content'] }}</pre>
|
||||||
{% for tag in message['tags'] %}
|
{% for tag in message['tags'] %}
|
||||||
|
|
|
@ -77,7 +77,8 @@
|
||||||
{% for chat in chat_instance["chats"] %}
|
{% for chat in chat_instance["chats"] %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<img src="{{ url_for('static', filename='image/ail-icon.png') }}" class="rounded-circle mr-1" alt="{{ chat['id'] }}" width="40" height="40">
|
<img src="{% if chat['icon'] %}{{ url_for('objects_image.image', filename=chat['icon'])}}{% else %}{{ url_for('static', filename='image/ail-icon.png') }}{% endif %}"
|
||||||
|
class="rounded-circle mr-1" alt="{{ chat['id'] }}" width="40" height="40">
|
||||||
</td>
|
</td>
|
||||||
<td><b>{{ chat['name'] }}</b></td>
|
<td><b>{{ chat['name'] }}</b></td>
|
||||||
<td><a href="{{ url_for('chats_explorer.chats_explorer_chat') }}?uuid={{ chat_instance['uuid'] }}&id={{ chat['id'] }}">{{ chat['id'] }}</a></td>
|
<td><a href="{{ url_for('chats_explorer.chats_explorer_chat') }}?uuid={{ chat_instance['uuid'] }}&id={{ chat['id'] }}">{{ chat['id'] }}</a></td>
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js')}}"></script>
|
<script src="{{ url_for('static', filename='js/jquery.dataTables.min.js')}}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/dataTables.bootstrap.min.js')}}"></script>
|
<script src="{{ url_for('static', filename='js/dataTables.bootstrap.min.js')}}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/d3.min.js')}}"></script>
|
<script src="{{ url_for('static', filename='js/d3.min.js')}}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/d3/heatmap_week_hour.js')}}"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.chat-message-left,
|
.chat-message-left,
|
||||||
|
@ -56,7 +57,10 @@
|
||||||
<div class="card my-3">
|
<div class="card my-3">
|
||||||
|
|
||||||
<div class="card-header" style="background-color:#d9edf7;font-size: 15px">
|
<div class="card-header" style="background-color:#d9edf7;font-size: 15px">
|
||||||
<h4 class="text-secondary">{% if chat['username'] %}{{ chat["username"]["id"] }} {% else %} {{ chat['name'] }}{% endif %} :</h4> {{ chat["id"] }}
|
<h4 class="text-secondary">{% if chat['username'] %}{{ chat["username"]["id"] }} {% else %} {{ chat['name'] }}{% endif %} :</h4>
|
||||||
|
{% if chat['icon'] %}
|
||||||
|
<div><img src="{{ url_for('objects_image.image', filename=chat['icon'])}}" class="mb-2" alt="{{ chat['id'] }}" width="200" height="200"></div>
|
||||||
|
{% endif %}
|
||||||
<ul class="list-group mb-2">
|
<ul class="list-group mb-2">
|
||||||
<li class="list-group-item py-0">
|
<li class="list-group-item py-0">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
|
@ -218,6 +222,12 @@ function toggle_sidebar(){
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
d3.json("{{ url_for('chats_explorer.chats_explorer_messages_stats_week') }}?uuid={{ chat['subtype'] }}&id={{ chat['id'] }}")
|
||||||
|
.then(function(data) {
|
||||||
|
create_heatmap_week_hour('#heatmapweekhour', data);
|
||||||
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
// set the dimensions and margins of the graph
|
// set the dimensions and margins of the graph
|
||||||
|
@ -323,225 +333,6 @@ d3.csv("").then( function(data) {
|
||||||
*/
|
*/
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
// based on gist nbremer/62cf60e116ae821c06602793d265eaf6
|
|
||||||
d3.json("{{ url_for('chats_explorer.chats_explorer_messages_stats_week') }}?uuid={{ chat['subtype'] }}&id={{ chat['id'] }}")
|
|
||||||
.then(function(data) {
|
|
||||||
|
|
||||||
var days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
|
|
||||||
times = d3.range(24);
|
|
||||||
|
|
||||||
var margin = {
|
|
||||||
top: 80,
|
|
||||||
right: 50,
|
|
||||||
bottom: 20,
|
|
||||||
left: 50
|
|
||||||
};
|
|
||||||
|
|
||||||
var width = Math.max(Math.min(window.innerWidth, 1000), 500) - margin.left - margin.right - 20,
|
|
||||||
gridSize = Math.floor(width / times.length),
|
|
||||||
height = gridSize * (days.length + 2);
|
|
||||||
|
|
||||||
var heatmap_font_size = width * 62.5 / 900;
|
|
||||||
|
|
||||||
//SVG container
|
|
||||||
var svg = d3.select('#heatmapweekhour')
|
|
||||||
.append("svg")
|
|
||||||
.attr("width", width + margin.left + margin.right)
|
|
||||||
.attr("height", height + margin.top + margin.bottom)
|
|
||||||
.append("g")
|
|
||||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
|
||||||
|
|
||||||
// create a tooltip
|
|
||||||
const tooltip = d3.select("#heatmapweekhour")
|
|
||||||
.append("div")
|
|
||||||
.style("opacity", 0)
|
|
||||||
.attr("class", "tooltip")
|
|
||||||
.style("background-color", "white")
|
|
||||||
.style("border", "solid")
|
|
||||||
.style("border-width", "2px")
|
|
||||||
.style("border-radius", "5px")
|
|
||||||
.style("padding", "5px")
|
|
||||||
|
|
||||||
// Three function that change the tooltip when user hover / move / leave a cell
|
|
||||||
const mouseover = function(d) {
|
|
||||||
tooltip.style("opacity", 1)
|
|
||||||
d3.select(this)
|
|
||||||
.style("stroke", "black")
|
|
||||||
//.style("stroke-opacity", 1)
|
|
||||||
|
|
||||||
tooltip.html(d.date + " " + d.hour + "-" + (d.hour + 1) + "h: <b>" + d.count + "</b> messages")
|
|
||||||
}
|
|
||||||
const mouseleave = function(d) {
|
|
||||||
tooltip.style("opacity", 0)
|
|
||||||
d3.select(this)
|
|
||||||
.style("stroke", "white")
|
|
||||||
//.style("stroke-opacity", 0.8)
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
//////////////////////////// Draw Heatmap /////////////////////////////////
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
var colorScale = d3.scaleLinear()
|
|
||||||
.domain([0, d3.max(data, function (d) {
|
|
||||||
return d.count;
|
|
||||||
}) / 2, d3.max(data, function (d) {
|
|
||||||
return d.count;
|
|
||||||
})])
|
|
||||||
.range(["#FFFFF6", "#3E9583", "#1F2D86"])
|
|
||||||
//.interpolate(d3.interpolateHcl);
|
|
||||||
|
|
||||||
var dayLabels = svg.selectAll(".dayLabel")
|
|
||||||
.data(days)
|
|
||||||
.enter().append("text")
|
|
||||||
.text(function (d) {
|
|
||||||
return d;
|
|
||||||
})
|
|
||||||
.attr("x", 0)
|
|
||||||
.attr("y", function (d, i) {
|
|
||||||
return i * gridSize;
|
|
||||||
})
|
|
||||||
.style("text-anchor", "end")
|
|
||||||
.attr("transform", "translate(-36, -11)")
|
|
||||||
.attr("class", function (d, i) {
|
|
||||||
return ((i >= 0 && i <= 4) ? "dayLabel mono axis axis-workweek" : "dayLabel mono axis");
|
|
||||||
})
|
|
||||||
.style("font-size", heatmap_font_size + "%");
|
|
||||||
|
|
||||||
var timeLabels = svg.selectAll(".timeLabel")
|
|
||||||
.data(times)
|
|
||||||
.enter().append("text")
|
|
||||||
.text(function (d) {
|
|
||||||
return d;
|
|
||||||
})
|
|
||||||
.attr("x", function (d, i) {
|
|
||||||
return i * gridSize;
|
|
||||||
})
|
|
||||||
.attr("y", 0)
|
|
||||||
.style("text-anchor", "middle")
|
|
||||||
.attr("transform", "translate(-" + gridSize / 2 + ", -36)")
|
|
||||||
.attr("class", function (d, i) {
|
|
||||||
return ((i >= 8 && i <= 17) ? "timeLabel mono axis axis-worktime" : "timeLabel mono axis");
|
|
||||||
})
|
|
||||||
.style("font-size", heatmap_font_size + "%");
|
|
||||||
|
|
||||||
var heatMap = svg.selectAll(".hour")
|
|
||||||
.data(data)
|
|
||||||
.enter().append("rect")
|
|
||||||
.attr("x", function (d) {
|
|
||||||
return (d.hour - 1) * gridSize;
|
|
||||||
})
|
|
||||||
.attr("y", function (d) {
|
|
||||||
return (d.day - 1) * gridSize;
|
|
||||||
})
|
|
||||||
.attr("class", "hour bordered")
|
|
||||||
.attr("width", gridSize)
|
|
||||||
.attr("height", gridSize)
|
|
||||||
.style("stroke", "white")
|
|
||||||
.style("stroke-opacity", 0.6)
|
|
||||||
.style("fill", function (d) {
|
|
||||||
return colorScale(d.count);
|
|
||||||
})
|
|
||||||
.on("mouseover", mouseover)
|
|
||||||
.on("mouseleave", mouseleave);
|
|
||||||
|
|
||||||
//Append title to the top
|
|
||||||
svg.append("text")
|
|
||||||
.attr("class", "title")
|
|
||||||
.attr("x", width / 2)
|
|
||||||
.attr("y", -60)
|
|
||||||
.style("text-anchor", "middle")
|
|
||||||
.text("Chat Messages");
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
//////////////// Create the gradient for the legend ///////////////////////
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
//Extra scale since the color scale is interpolated
|
|
||||||
var countScale = d3.scaleLinear()
|
|
||||||
.domain([0, d3.max(data, function (d) {
|
|
||||||
return d.count;
|
|
||||||
})])
|
|
||||||
.range([0, width])
|
|
||||||
|
|
||||||
//Calculate the variables for the temp gradient
|
|
||||||
var numStops = 10;
|
|
||||||
countRange = countScale.domain();
|
|
||||||
countRange[2] = countRange[1] - countRange[0];
|
|
||||||
countPoint = [];
|
|
||||||
for (var i = 0; i < numStops; i++) {
|
|
||||||
countPoint.push(i * countRange[2] / (numStops - 1) + countRange[0]);
|
|
||||||
}//for i
|
|
||||||
|
|
||||||
//Create the gradient
|
|
||||||
svg.append("defs")
|
|
||||||
.append("linearGradient")
|
|
||||||
.attr("id", "legend-heatmap")
|
|
||||||
.attr("x1", "0%").attr("y1", "0%")
|
|
||||||
.attr("x2", "100%").attr("y2", "0%")
|
|
||||||
.selectAll("stop")
|
|
||||||
.data(d3.range(numStops))
|
|
||||||
.enter().append("stop")
|
|
||||||
.attr("offset", function (d, i) {
|
|
||||||
return countScale(countPoint[i]) / width;
|
|
||||||
})
|
|
||||||
.attr("stop-color", function (d, i) {
|
|
||||||
return colorScale(countPoint[i]);
|
|
||||||
});
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
////////////////////////// Draw the legend ////////////////////////////////
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
var legendWidth = Math.min(width * 0.8, 400);
|
|
||||||
//Color Legend container
|
|
||||||
var legendsvg = svg.append("g")
|
|
||||||
.attr("class", "legendWrapper")
|
|
||||||
.attr("transform", "translate(" + (width / 2) + "," + (gridSize * days.length) + ")"); // 319
|
|
||||||
|
|
||||||
//Draw the Rectangle
|
|
||||||
legendsvg.append("rect")
|
|
||||||
.attr("class", "legendRect")
|
|
||||||
.attr("x", -legendWidth / 2)
|
|
||||||
.attr("y", 0)
|
|
||||||
//.attr("rx", hexRadius*1.25/2)
|
|
||||||
.attr("width", legendWidth)
|
|
||||||
.attr("height", 10)
|
|
||||||
.style("fill", "url(#legend-heatmap)");
|
|
||||||
|
|
||||||
//Append title
|
|
||||||
legendsvg.append("text")
|
|
||||||
.attr("class", "legendTitle")
|
|
||||||
.attr("x", 0)
|
|
||||||
.attr("y", -10)
|
|
||||||
.style("text-anchor", "middle")
|
|
||||||
.text("Number of Messages");
|
|
||||||
|
|
||||||
//Set scale for x-axis
|
|
||||||
var xScale = d3.scaleLinear()
|
|
||||||
.range([-legendWidth / 2, legendWidth / 2])
|
|
||||||
.domain([0, d3.max(data, function (d) {
|
|
||||||
return d.count;
|
|
||||||
})]);
|
|
||||||
|
|
||||||
//Define x-axis
|
|
||||||
var xAxis = d3.axisBottom(xScale)
|
|
||||||
//.orient("bottom")
|
|
||||||
.ticks(5);
|
|
||||||
//.tickFormat(formatPercent)
|
|
||||||
|
|
||||||
//Set up X axis
|
|
||||||
legendsvg.append("g")
|
|
||||||
.attr("class", "axis")
|
|
||||||
.attr("transform", "translate(0," + (10) + ")")
|
|
||||||
.call(xAxis);
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue