Skip to content

Commit

Permalink
post(leaflet): add a new post about dataviz
Browse files Browse the repository at this point in the history
  • Loading branch information
jeanjerome committed Mar 2, 2024
1 parent 770c701 commit 179a2a3
Show file tree
Hide file tree
Showing 31 changed files with 172,521 additions and 33 deletions.
13 changes: 13 additions & 0 deletions _includes/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,17 @@
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
{% if page.leaflet %}
<link rel="stylesheet" href="https://unpkg.com/tachyons@4.12.0/css/tachyons.min.css">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin="">
<script src="https://unpkg.com/heatmap.js@2.0.5/build/heatmap.js"></script>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<script src="https://unpkg.com/leaflet-heatmap@1.0.0/leaflet-heatmap.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="/assets/js/dataviz-leaflet-engagement.min.js"></script>
{% endif %}
</head>
8 changes: 4 additions & 4 deletions _layouts/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@
</div>
<div class="language-switcher">
{% if site.active_lang == "fr" %}
<input type="radio" id="toggle-french" name="toggle" value="fr" checked/>
<input type="radio" id="toggle-french" name="toggle" value="fr" checked>
<label for="toggle-french" aria-label="{{ site.data.i18n.aria-francais-selected }}">Français</label>
<input type="radio" id="toggle-english" name="toggle" value="en" onclick="window.location.assign( '{{site.baseurl}}/en{{page.url}}' );" />
<input type="radio" id="toggle-english" name="toggle" value="en" onclick="window.location.assign( '{{site.baseurl}}/en{{page.url}}' );">
<label for="toggle-english" aria-label="{{ site.data.i18n.aria-english }}">English</label>
{% else %}
<input type="radio" id="toggle-french" name="toggle" value="fr" onclick="window.location.assign( '{{site.baseurl}}{{page.url}}' );"/>
<input type="radio" id="toggle-french" name="toggle" value="fr" onclick="window.location.assign( '{{site.baseurl}}{{page.url}}' );">
<label for="toggle-french" aria-label="{{ site.data.i18n.aria-francais }}">Français</label>
<input type="radio" id="toggle-english" name="toggle" value="en" checked />
<input type="radio" id="toggle-english" name="toggle" value="en" checked>
<label for="toggle-english" aria-label="{{ site.data.i18n.aria-english-selected }}">English</label>
{% endif %}
</div>
Expand Down
429 changes: 429 additions & 0 deletions _posts/2024-02-20-visualize-visitors-engagement-leaflet.markdown

Large diffs are not rendered by default.

410 changes: 410 additions & 0 deletions _posts/en/2024-02-20-visualize-visitors-engagement-leaflet.markdown

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions _sass/_media.scss
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,8 @@
}
}

.leaflet-control-zoom {
display: none;
}

}
12 changes: 12 additions & 0 deletions assets/css/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ img + em {
font-size: 14px;
}

#map img {
border-radius: 0px;
}

#map {
width: 100%;
height: 600px;
}




// Blockquote
blockquote {
border-left: 5px solid #000;
Expand Down
Binary file modified assets/img/crewai-mixtral-agile-team.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/dataviz-leaflet-engagement-africa.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/dataviz-leaflet-engagement-asia.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/dataviz-leaflet-engagement-europe.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/dataviz-leaflet-engagement-os-all.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/dataviz-leaflet-engagement.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
172 changes: 172 additions & 0 deletions assets/js/dataviz-leaflet-engagement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
document.addEventListener('DOMContentLoaded', function() {
var map = L.map('map', {
center: [30, -2],
zoom: 2,
minZoom:2,
maxZoom:10,
zoomDelta: 0.75,
zoomSnap: 1,
});

L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/attribution">CARTO</a>',
maxZoom: 20,
}).addTo(map);

var cfg = {
"radius": 1.5,
"maxOpacity": .8,
"scaleRadius": true,
"useLocalExtrema": false,
"gradient": {
0.125: "rgb(173, 216, 230)", // Bleu clair
0.25: "rgb(135, 206, 235)", // Bleu ciel
0.375: "rgb(0, 255, 255)", // Aqua
0.5: "rgb(144, 238, 144)", // Vert clair
0.625: "rgb(255, 255, 0)", // Jaune
0.75: "rgb(255, 165, 0)", // Orange
0.875: "rgb(255, 69, 0)", // Rouge orangé
1.0: "rgb(128, 0, 128)" // Violet
},
latField: 'l',
lngField: 'L',
valueField: 'c'
};

var heatmapLayer = new HeatmapOverlay(cfg).addTo(map);

fetch('/assets/js/dataviz-leaflet-engagement.min.json').then(response => response.json()).then(data => {
var customFilterControl = createControls(data);
customFilterControl.addTo(map);

data.forEach(point => { point.c = 1; });

heatmapLayer.setData({ data: data });

let isVisualizationRunning = false;
let displayInterval;

function createControls(data) {
var control = new L.Control({ position: 'topright' });

control.onAdd = function(map) {
var div = L.DomUtil.create('div', 'custom-filter-control');
L.DomEvent.disableClickPropagation(div);

createFilterSelector(div, data, 'r', 'Referer');
createFilterSelector(div, data, 'd', 'Device Type');
createFilterSelector(div, data, 'o', 'OS');

createChronosButton(div, data);
L.DomUtil.create('div', 'timeDisplay', div);

return div;
};

return control;
}

function createFilterSelector(parent, data, field, label) {
var allOption = document.createElement('option');
allOption.value = 'all';
allOption.innerText = 'All';
var uniqueValues = [...new Set(data.map(item => item[field]))].sort();
var select = L.DomUtil.create('select', 'select' + label.replace(/\s+/g, '_'), parent);
select.appendChild(allOption);
uniqueValues.forEach(value => {
var option = document.createElement('option');
option.value = value;
option.innerText = value;
select.appendChild(option);
});

L.DomEvent.on(select, 'change', () => filterData());
}

function createChronosButton(parent, data) {
var startChronoButton = L.DomUtil.create('button', 'start-chrono-btn', parent);
startChronoButton.innerText = 'Visitor Time';
L.DomEvent.on(startChronoButton, 'click', () => toggleChronologicalDisplay(data, 't', unixTimeToMinutes, startChronoButton));
}

function toggleChronologicalDisplay(data, timeKey, conversionFunction, chronoButton) {
if (isVisualizationRunning) {
clearInterval(displayInterval);
isVisualizationRunning = false;
chronoButton.innerText = 'Visitor Time';
} else {
startChronologicalDisplay(data, timeKey, conversionFunction, chronoButton);
isVisualizationRunning = true;
}
}

function filterData() {
var selectReferer = document.querySelector('.selectReferer');
var selectDeviceType = document.querySelector('.selectDevice_Type');
var selectOS = document.querySelector('.selectOS');

var selectedReferer = selectReferer ? selectReferer.value : 'all';
var selectedDeviceType = selectDeviceType ? selectDeviceType.value : 'all';
var selectedOS = selectOS ? selectOS.value : 'all';

var filteredData = data.filter(function(item) {
var matchesReferer = selectedReferer === 'all' || item.r === selectedReferer;
var matchesDeviceType = selectedDeviceType === 'all' || item.d === selectedDeviceType;
var matchesOS = selectedOS === 'all' || item.o === selectedOS;
return matchesReferer && matchesDeviceType && matchesOS;
});

heatmapLayer.setData({ data: filteredData });
}

function startChronologicalDisplay(data, timeKey, conversionFunction, chronoButton) {
const dataWithDuration = data.map(d => ({
...d,
displayUntil: conversionFunction(d[timeKey]) + 30 // TTL
}));

const startOfDay = 0; // 0h00
const endOfDay = 1439; // 23h59
let currentTime = startOfDay;

displayInterval = setInterval(() => {
const visibleData = dataWithDuration.filter(d => {
const dataTime = conversionFunction(d[timeKey]);
return dataTime <= currentTime && currentTime <= d.displayUntil;
});

heatmapLayer.setData({ data: visibleData });

updateTimeDisplay(currentTime, chronoButton);
currentTime += 10; // Avance de 15 minutes
if (currentTime > endOfDay) currentTime = startOfDay; // Loop

}, 100); // Speed
}

function unixTimeToMinutes(unixTimestamp) {
const date = new Date(unixTimestamp * 1000);
return date.getHours() * 60 + date.getMinutes();
}

function timeStringToMinutes(timeString) {
const [hours, minutes] = timeString.split(':').map(Number);
return hours * 60 + minutes;
}

function updateTimeDisplay(currentTime, chronoButton) {
var hours = Math.floor(currentTime / 60);
var minutes = currentTime % 60;
var timeString = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;

chronoButton.innerText = `${timeString}`;
}
});

map.on('zoomend', function() {
var zoomLevel = map.getZoom();
var newRadius = Math.max(0.1, 1.5 - zoomLevel / 5);
heatmapLayer.cfg.radius = newRadius;
heatmapLayer._update();
});
});
Loading

0 comments on commit 179a2a3

Please sign in to comment.