2023-11-02 15:28:33 +00:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
|
|
|
|
<head>
|
|
|
|
<title>Chats Protocols - AIL</title>
|
|
|
|
<link rel="icon" href="{{ url_for('static', filename='image/ail-icon.png') }}">
|
|
|
|
|
|
|
|
<!-- Core CSS -->
|
|
|
|
<link href="{{ url_for('static', filename='css/bootstrap4.min.css') }}" rel="stylesheet">
|
|
|
|
<link href="{{ url_for('static', filename='css/font-awesome.min.css') }}" rel="stylesheet">
|
|
|
|
<link href="{{ url_for('static', filename='css/dataTables.bootstrap.min.css') }}" rel="stylesheet">
|
|
|
|
|
|
|
|
<!-- JS -->
|
|
|
|
<script src="{{ url_for('static', filename='js/jquery.js')}}"></script>
|
|
|
|
<script src="{{ url_for('static', filename='js/popper.min.js')}}"></script>
|
|
|
|
<script src="{{ url_for('static', filename='js/bootstrap4.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>
|
2023-11-13 13:10:24 +00:00
|
|
|
<script src="{{ url_for('static', filename='js/d3.min.js')}}"></script>
|
2023-11-02 15:28:33 +00:00
|
|
|
|
2023-11-06 13:08:23 +00:00
|
|
|
<style>
|
|
|
|
.chat-message-left,
|
|
|
|
.chat-message-right {
|
|
|
|
display: flex;
|
|
|
|
flex-shrink: 0;
|
|
|
|
}
|
|
|
|
.chat-message-right {
|
|
|
|
flex-direction: row-reverse;
|
|
|
|
margin-left: auto
|
|
|
|
}
|
|
|
|
.divider:after,
|
|
|
|
.divider:before {
|
|
|
|
content: "";
|
|
|
|
flex: 1;
|
|
|
|
height: 2px;
|
|
|
|
background: #eee;
|
|
|
|
}
|
|
|
|
</style>
|
|
|
|
|
2023-11-02 15:28:33 +00:00
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
|
|
|
|
{% include 'nav_bar.html' %}
|
|
|
|
|
|
|
|
<div class="container-fluid">
|
|
|
|
<div class="row">
|
|
|
|
|
|
|
|
{% include 'sidebars/sidebar_objects.html' %}
|
|
|
|
|
|
|
|
<div class="col-12 col-lg-10" id="core_content">
|
|
|
|
|
|
|
|
<div class="card my-3">
|
|
|
|
|
|
|
|
<div class="card-header" style="background-color:#d9edf7;font-size: 15px">
|
2023-11-08 12:07:00 +00:00
|
|
|
<h4 class="text-secondary">{% if chat['username'] %}{{ chat["username"]["id"] }} {% else %} {{ chat['name'] }}{% endif %} :</h4> {{ chat["id"] }}
|
2023-11-02 15:28:33 +00:00
|
|
|
<ul class="list-group mb-2">
|
|
|
|
<li class="list-group-item py-0">
|
|
|
|
<table class="table">
|
2023-11-06 13:08:23 +00:00
|
|
|
<thead class="">
|
2023-11-02 15:28:33 +00:00
|
|
|
<tr>
|
|
|
|
<th>Icon</th>
|
|
|
|
<th>Name</th>
|
|
|
|
<th>ID</th>
|
2023-11-07 14:24:40 +00:00
|
|
|
<th>Created at</th>
|
2023-11-02 15:28:33 +00:00
|
|
|
<th>First Seen</th>
|
|
|
|
<th>Last Seen</th>
|
|
|
|
<th>NB Sub-Channels</th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody style="font-size: 15px;">
|
|
|
|
<tr>
|
|
|
|
<td></td>
|
|
|
|
<td>{{ chat['name'] }}</td>
|
|
|
|
<td>{{ chat['id'] }}</td>
|
2023-11-07 14:24:40 +00:00
|
|
|
<td>{{ chat['created_at'] }}</td>
|
2023-11-06 13:08:23 +00:00
|
|
|
<td>
|
|
|
|
{% if chat['first_seen'] %}
|
|
|
|
{{ chat['first_seen'][0:4] }}-{{ chat['first_seen'][4:6] }}-{{ chat['first_seen'][6:8] }}
|
|
|
|
{% endif %}
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{% if chat['last_seen'] %}
|
|
|
|
{{ chat['last_seen'][0:4] }}-{{ chat['last_seen'][4:6] }}-{{ chat['last_seen'][6:8] }}
|
|
|
|
{% endif %}
|
|
|
|
</td>
|
2023-11-02 15:28:33 +00:00
|
|
|
<td>{{ chat['nb_subchannels'] }}</td>
|
|
|
|
</tr>
|
|
|
|
</tbody>
|
|
|
|
</table>
|
2023-11-06 15:38:31 +00:00
|
|
|
{% if chat['info'] %}
|
|
|
|
<li class="list-group-item py-0">
|
|
|
|
<pre class="my-0">{{ chat['info'] }}</pre>
|
|
|
|
</li>
|
|
|
|
{% endif %}
|
2023-11-02 15:28:33 +00:00
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2023-11-06 13:08:23 +00:00
|
|
|
{% for tag in chat['tags_messages'] %}
|
|
|
|
<span class="badge badge-{{ bootstrap_label[loop.index0 % 5] }}">{{ tag }} <span class="badge badge-light">{{ chat['tags_messages'][tag] }}</span></span>
|
|
|
|
{% endfor %}
|
|
|
|
|
2023-11-02 15:28:33 +00:00
|
|
|
{% if chat['subchannels'] %}
|
|
|
|
<h4>Sub-Channels:</h4>
|
|
|
|
<table id="tablesubchannels" class="table">
|
|
|
|
<thead class="bg-dark text-white">
|
|
|
|
<tr>
|
|
|
|
<th>Icon</th>
|
|
|
|
<th>Name</th>
|
|
|
|
<th>ID</th>
|
2023-11-07 14:24:40 +00:00
|
|
|
<th>Created at</th>
|
2023-11-02 15:28:33 +00:00
|
|
|
<th>First Seen</th>
|
|
|
|
<th>Last Seen</th>
|
|
|
|
<th>NB Messages</th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody style="font-size: 15px;">
|
|
|
|
{% for meta in chat["subchannels"] %}
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<img src="{{ url_for('static', filename='image/ail-icon.png') }}" class="rounded-circle mr-1" alt="{{ meta['id'] }}" width="40" height="40">
|
|
|
|
</td>
|
|
|
|
<td><b>{{ meta['name'] }}</b></td>
|
|
|
|
<td><a href="{{ url_for('chats_explorer.objects_subchannel_messages') }}?uuid={{ meta['subtype'] }}&id={{ meta['id'] }}">{{ meta['id'] }}</a></td>
|
2023-11-07 14:24:40 +00:00
|
|
|
<td>{{ meta['created_at'] }}</td>
|
2023-11-06 13:08:23 +00:00
|
|
|
<td>
|
|
|
|
{% if meta['first_seen'] %}
|
|
|
|
{{ meta['first_seen'][0:4] }}-{{ meta['first_seen'][4:6] }}-{{ meta['first_seen'][6:8] }}
|
|
|
|
{% endif %}
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
{% if meta['last_seen'] %}
|
|
|
|
{{ meta['last_seen'][0:4] }}-{{ meta['last_seen'][4:6] }}-{{ meta['last_seen'][6:8] }}
|
|
|
|
{% endif %}
|
|
|
|
</td>
|
2023-11-02 15:28:33 +00:00
|
|
|
<td>{{ meta['nb_messages'] }}</td>
|
|
|
|
</tr>
|
|
|
|
{% endfor %}
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
{% endif %}
|
|
|
|
|
2023-11-13 13:10:24 +00:00
|
|
|
<div id="heatmapweekhour"></div>
|
|
|
|
|
2023-11-06 13:08:23 +00:00
|
|
|
{% if chat['messages'] %}
|
|
|
|
|
|
|
|
<div class="position-relative">
|
|
|
|
<div class="chat-messages p-2">
|
|
|
|
|
|
|
|
{% for date in chat['messages'] %}
|
|
|
|
|
|
|
|
<div class="divider d-flex align-items-center mb-4">
|
|
|
|
<p class="text-center h2 mx-3 mb-0" style="color: #a2aab7;">
|
|
|
|
<span class="badge badge-secondary mb-2" id="date_section_{{ date }}">{{ date }}</span>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{% for mess in chat['messages'][date] %}
|
|
|
|
|
|
|
|
<div class="chat-message-left pb-1">
|
|
|
|
<div>
|
|
|
|
<img src="{{ url_for('static', filename='image/ail-icon.png') }}" class="rounded-circle mr-1" alt="{{ mess['user-account']['id'] }}" width="40" height="40">
|
|
|
|
<div class="text-muted small text-nowrap mt-2">{{ mess['hour'] }}</div>
|
|
|
|
</div>
|
|
|
|
<div class="flex-shrink-1 bg-light rounded py-2 px-3 ml-4 pb-4" style="overflow-x: auto">
|
|
|
|
<div class="font-weight-bold mb-1">
|
|
|
|
{% if mess['user-account']['username'] %}
|
|
|
|
{{ mess['user-account']['username']['id'] }}
|
|
|
|
{% else %}
|
|
|
|
{{ mess['user-account']['id'] }}
|
|
|
|
{% endif %}
|
|
|
|
</div>
|
|
|
|
{% if mess['reply_to'] %}
|
2023-11-07 10:24:24 +00:00
|
|
|
<div class="flex-shrink-1 bg-white border rounded py-2 px-3 ml-4 mb-3" style="overflow-x: auto">
|
2023-11-06 13:08:23 +00:00
|
|
|
<div class="font-weight-bold mb-1">
|
|
|
|
{% if mess['reply_to']['user-account']['username'] %}
|
|
|
|
{{ mess['reply_to']['user-account']['username']['id'] }}
|
|
|
|
{% else %}
|
|
|
|
{{ mess['reply_to']['user-account']['id'] }}
|
|
|
|
{% endif %}
|
|
|
|
</div>
|
|
|
|
<pre class="my-0">{{ mess['reply_to']['content'] }}</pre>
|
|
|
|
{% for tag in mess['reply_to']['tags'] %}
|
|
|
|
<span class="badge badge-{{ bootstrap_label[loop.index0 % 5] }}">{{ tag }}</span>
|
|
|
|
{% endfor %}
|
2023-11-08 09:31:51 +00:00
|
|
|
<div class="text-muted small text-nowrap">{{ mess['reply_to']['full_date'] }}</div>
|
2023-11-06 13:08:23 +00:00
|
|
|
{# <div class="">#}
|
|
|
|
{# <a class="btn btn-light btn-sm text-secondary py-0" href="{{ url_for('correlation.show_correlation')}}?type={{ mess['reply_to']['type'] }}&subtype={{ mess['reply_to']['subtype'] }}&id={{ mess['reply_to']['id'] }}"><i class="fas fa-project-diagram"></i></a>#}
|
|
|
|
{# <a class="btn btn-light btn-sm text-secondary py-0" href="{{ mess['reply_to']['link'] }}"><i class="fas fa-eye"></i></a>#}
|
|
|
|
{# </div>#}
|
|
|
|
</div>
|
|
|
|
{% endif %}
|
|
|
|
<pre class="my-0">{{ mess['content'] }}</pre>
|
|
|
|
{% for tag in mess['tags'] %}
|
|
|
|
<span class="badge badge-{{ bootstrap_label[loop.index0 % 5] }}">{{ tag }}</span>
|
|
|
|
{% endfor %}
|
|
|
|
<div class="">
|
|
|
|
<a class="btn btn-light btn-sm text-secondary px-1" href="{{ url_for('correlation.show_correlation')}}?type={{ mess['type'] }}&subtype={{ mess['subtype'] }}&id={{ mess['id'] }}"><i class="fas fa-project-diagram"></i></a>
|
|
|
|
<a class="btn btn-light btn-sm text-secondary px-1" href="{{ mess['link'] }}"><i class="fas fa-eye"></i></a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{% endfor %}
|
|
|
|
<br>
|
|
|
|
{% endfor %}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{% endif %}
|
|
|
|
|
2023-11-02 15:28:33 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
$(document).ready(function(){
|
|
|
|
$("#page-Decoded").addClass("active");
|
|
|
|
$("#nav_cve").addClass("active");
|
|
|
|
|
|
|
|
{% if chat['subchannels'] %}
|
|
|
|
$('#tablesubchannels').DataTable({
|
|
|
|
"aLengthMenu": [[5, 10, 15, -1], [5, 10, 15, "All"]],
|
|
|
|
"iDisplayLength": 10,
|
2023-11-07 14:24:40 +00:00
|
|
|
"order": [[ 5, "desc" ]]
|
2023-11-02 15:28:33 +00:00
|
|
|
});
|
|
|
|
{% endif %}
|
|
|
|
});
|
|
|
|
|
|
|
|
function toggle_sidebar(){
|
|
|
|
if($('#nav_menu').is(':visible')){
|
|
|
|
$('#nav_menu').hide();
|
|
|
|
$('#side_menu').removeClass('border-right')
|
|
|
|
$('#side_menu').removeClass('col-lg-2')
|
|
|
|
$('#core_content').removeClass('col-lg-10')
|
|
|
|
}else{
|
|
|
|
$('#nav_menu').show();
|
|
|
|
$('#side_menu').addClass('border-right')
|
|
|
|
$('#side_menu').addClass('col-lg-2')
|
|
|
|
$('#core_content').addClass('col-lg-10')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
2023-11-13 13:10:24 +00:00
|
|
|
|
|
|
|
<script>
|
|
|
|
/*
|
|
|
|
|
|
|
|
// set the dimensions and margins of the graph
|
|
|
|
const margin = {top: 30, right: 30, bottom: 30, left: 30},
|
|
|
|
width = 450 - margin.left - margin.right,
|
|
|
|
height = 450 - margin.top - margin.bottom;
|
|
|
|
|
|
|
|
// append the svg object to the body of the page
|
|
|
|
const svg = d3.select("#my_dataviz")
|
|
|
|
.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})`);
|
|
|
|
|
|
|
|
// Labels of row and columns
|
|
|
|
const myGroups = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
|
|
|
|
const myVars = ["v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10"]
|
|
|
|
|
|
|
|
//Read the data
|
|
|
|
d3.csv("").then( function(data) {
|
|
|
|
|
|
|
|
// Labels of row and columns -> unique identifier of the column called 'group' and 'variable'
|
|
|
|
const myGroups = Array.from(new Set(data.map(d => d.group)))
|
|
|
|
const myVars = Array.from(new Set(data.map(d => d.variable)))
|
|
|
|
|
|
|
|
// Build X scales and axis:
|
|
|
|
const x = d3.scaleBand()
|
|
|
|
.range([ 0, width ])
|
|
|
|
.domain(myGroups)
|
|
|
|
.padding(0.05);
|
|
|
|
svg.append("g")
|
|
|
|
.style("font-size", 15)
|
|
|
|
.attr("transform", `translate(0, ${height})`)
|
|
|
|
.call(d3.axisBottom(x).tickSize(0))
|
|
|
|
.select(".domain").remove()
|
|
|
|
|
|
|
|
// Build Y scales and axis:
|
|
|
|
const y = d3.scaleBand()
|
|
|
|
.range([ height, 0 ])
|
|
|
|
.domain(myVars)
|
|
|
|
.padding(0.01);
|
|
|
|
svg.append("g")
|
|
|
|
.style("font-size", 15)
|
|
|
|
.call(d3.axisLeft(y).tickSize(0))
|
|
|
|
.select(".domain").remove()
|
|
|
|
|
|
|
|
// Build color scale
|
|
|
|
const myColor = d3.scaleSequential()
|
|
|
|
.interpolator(d3.interpolateInferno)
|
|
|
|
.domain([1,100])
|
|
|
|
|
|
|
|
// create a tooltip
|
|
|
|
const tooltip = d3.select("#my_dataviz")
|
|
|
|
.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(event,d) {
|
|
|
|
tooltip.style("opacity", 1)
|
|
|
|
d3.select(this)
|
|
|
|
.style("stroke", "black")
|
|
|
|
.style("opacity", 1)
|
|
|
|
}
|
|
|
|
const mousemove = function(event,d) {
|
|
|
|
tooltip.html("The exact value of<br>this cell is: " + d)
|
|
|
|
.style("left", (event.x)/2 + "px")
|
|
|
|
.style("top", (event.y)/2 + "px")
|
|
|
|
}
|
|
|
|
const mouseleave = function(d) {
|
|
|
|
tooltip.style("opacity", 0)
|
|
|
|
d3.select(this)
|
|
|
|
.style("stroke", "none")
|
|
|
|
.style("opacity", 0.8)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
svg.selectAll()
|
|
|
|
.data(data, function(d) {return d.group+':'+d.variable;})
|
|
|
|
.join("rect")
|
|
|
|
.attr("x", function(d) { return x(d.group) })
|
|
|
|
.attr("y", function(d) { return y(d.variable) })
|
|
|
|
.attr("rx", 4)
|
|
|
|
.attr("ry", 4)
|
|
|
|
.attr("width", x.bandwidth() )
|
|
|
|
.attr("height", y.bandwidth() )
|
|
|
|
.style("fill", function(d) { return myColor(d.value)} )
|
|
|
|
.style("stroke-width", 4)
|
|
|
|
.style("stroke", "none")
|
|
|
|
.style("opacity", 0.8)
|
|
|
|
.on("mouseover", mouseover)
|
|
|
|
.on("mousemove", mousemove)
|
|
|
|
.on("mouseleave", mouseleave)
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
*/
|
|
|
|
</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("opacity", 1)
|
|
|
|
|
|
|
|
tooltip.html(d.date + " " + d.hour + "-" + (d.hour + 1) + "h: <b>" + d.count + "</b> messages")
|
|
|
|
}
|
|
|
|
const mouseleave = function(d) {
|
|
|
|
console.log(d)
|
|
|
|
console.log(d.hour)
|
|
|
|
console.log(d.day)
|
|
|
|
tooltip.style("opacity", 0)
|
|
|
|
d3.select(this)
|
|
|
|
.style("stroke", "none")
|
|
|
|
.style("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>
|
|
|
|
|
2023-11-02 15:28:33 +00:00
|
|
|
</body>
|
|
|
|
|
|
|
|
</html>
|