Voir le flux RSS

phpiste

Réaliser un clustering de données avec la timeline VIS.js

Noter ce billet
par , 17/09/2016 à 15h02 (2418 Affichages)
Présentation

VIS.js propose une timeline très bien documentée, et à mon avis, l’avantage majeur de cette dernière et des autres widgets VIS (network, graph2d,..) et le découplage du modèle de données des widgets elles mêmes, le modèle de données est un composant VIS autonome dont vous pouvez l’utiliser indépendamment.

Voir en Action
http://codepen.io/ghaliano/pen/vXKVPr


C'est quoi un clustering

D'apres wikipedia
https://fr.wikipedia.org/wiki/Partit...e_donn%C3%A9es
Le partitionnement de données (ou data clustering en anglais) est une des méthodes d'analyse des données. Elle vise à diviser un ensemble de données en différents « paquets » homogènes, en ce sens que les données de chaque sous-ensemble partagent des caractéristiques communes, qui correspondent le plus souvent à des critères de proximité (similarité informatique) que l'on définit en introduisant des mesures et classes de distance entre objets.
Objectif de l’exemple

Réaliser une timeline qui gere les evenements sportifs tout en clusterisant les données pour alléger l’affichage et améliorer l’experience utilisateur.

Interaction
Des contrôles de type zoom-in zoom-out permettent d'éclater grouper les données selon une valeur de scale renvoyée par la timeline.

Problématique
Nom : Screen Shot 2016-09-17 at 1.33.52 PM.png
Affichages : 1265
Taille : 140,8 Ko
J’ai été récemment amené à intégrer une timeline pour gérer des événements, le challenge le plus important était de pouvoir regrouper des données pour alléger l’affichage et améliorer l’experience utilisateur.
La version précédente de VIS (nommé CHAP) intègre en natif le clustering
https://almende.github.io/chap-links...lustering.html
Malheureusement cette fonctionnalité a été retiré de la nouvelle version de VIS du coup il n’est plus possible de clusteriser les données avec une simple config.

Solution
Codant nous même le script de clustering!

Code source
Les données de test

Pour effectuer l’exemple on va générer des événements sportifs avec des dates aléatoires.
le nombre limite des événements est configurable via une variable get.

Code php : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php
 
date_default_timezone_set('America/New_York');
$limit = intval($_GET['limit']);
 
// Find a randomDate between $start_date and $end_date
//from http://stackoverflow.com/questions/1972712/generate-random-date-between-two-dates-using-php
function randomDate($start_date, $end_date)
{
    // Convert to timetamps
    $min = strtotime($start_date);
    $max = strtotime($end_date);
 
    // Generate random number using above bounds
    $val = rand($min, $max);
 
    // Convert back to desired date format
    return date('Y-m-d H:i', $val);
}
 
//Sports categories
$groupsData = array(
	array("id" => 1, "content" => 'Football'),
	array("id" => 2, "content" => 'Handball'),
	array("id" => 3, "content" => 'Criquet'),
	array("id" => 4, "content" => 'Natation'),
	array("id" => 5, "content" => 'basketball'),
	array("id" => 6, "content" => 'Tenis de table'),
	array("id" => 7, "content" => 'Cyclisme'),
	array("id" => 8, "content" => 'F1')
);
 
//The same service return both groups and item
$result = array(
	"groups" => $groupsData,
	"items" => array() 
);
 
for ($i=0; $i<$limit; $i++) {
	$result['items'][] = [
		"id" => $i+1, 
		"group" => $groupsData[array_rand($groupsData)]['id'], 
		"content" => "Event".$i, 
		"start" => randomDate('2016-09-01', '2016-10-20')
	];
}
 
 
header('Content-Type: application/json');
echo json_encode($result);

La classe de clusterisation

Code javascript : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
var clusteriser = function(timeline){
	this.timeline = timeline;
 
	this.getDiffDate = function(date1, date2){
		var second=1000;
		var minute = second*60;
		var hour = minute*60;
		var day = hour*24;
		var month = day*31;
		var year = month*12;
 
		// Convert both dates to milliseconds
		var diff = date2.getTime() - date1.getTime();
 
		// Convert back to components and return
		return {
			years: Math.round(diff/year),
			months: Math.round(diff/month),
			days: Math.round(diff/day),
			hours: Math.round(diff/hour),
			minutes: Math.round(diff/minute)
		};
	}
 
	//Get the scale between two date
	//We can use the scale information from timeline api in order to know how to cluster data
	this.getScale = function(start, end) {	
		var diff = this.getDiffDate(start, end);
 
		if (diff.months > 12*2) {
			return 'year';
		}
		else if (diff.days >31) {
			return 'month';
		}
		else if (diff.hours >24) {
			return 'day';
		}
		else if (diff.minutes >60) {
			return 'hour';
		}
		else {
			return 'second';
		}
	}
 
	this.getClusters = function(dataItems){
		var timelineWindow = this.timeline.getWindow();
 
		var scale = this.getScale(timelineWindow.start, timelineWindow.end);
 
	    var group = _.toArray(_.groupBy(dataItems, 'group'));;
		var result = {};
 
		var formatByScale = {
			year: 'Y',
			month: 'Y-M',
			weekday: 'Y-M-week-W',
			day: 'Y-M-day-D',
			hour: 'Y-M-D-H',
			second: 'Y-M-D-H-m'
		};
 
		group.forEach(function(items, i){
			items.forEach(function(item, j){
				var index = moment(item.start).format(formatByScale[scale]) + '-group' + item.group;
 
				if (!result[index]){
					result[index] = {
						count: 1,
						items: [],
						group: item.group,
						start: item.start
					};
				}
 
				result[index].items.push(item);
				result[index].count++;
			})
		});
 
		return _.toArray(result);
	}
}

La page d'affichage de la timeline
Code html : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<!DOCTYPE HTML>
<html>
  <head>
    <title>Timeline | Clustering demo</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
    <script src="http://underscorejs.org/underscore-min.js"></script>
    <script src="http://momentjs.com/downloads/moment.min.js"></script>
    <script src="http://visjs.org/dist/vis.js"></script>
    <script src="clusteriser.js"></script>
    <link href="http://visjs.org/dist/vis.css" rel="stylesheet" type="text/css" />
    <style type="text/css">
* {
  font-size: 12px;
}
    </style>
  </head>
  <body>
    <button class="zoom" data-dir="1">Zomm in</button>
    <button class="zoom" data-dir="-1">Zoom out</button>
 
    <div id="visualization"></div>
 
    <script type="text/javascript">
      var timeline = false;
 
      $.getJSON('data.php?limit=1000', function(jsonResponse){
          var groups = jsonResponse['groups'];
          var items = jsonResponse['items'];
 
          if (!timeline){
            timeline = new vis.Timeline(
              document.getElementById('visualization'), 
              [], 
              groups, 
              {
                template: function (item) {
                  return item.count?("<span>"+item.count+" évenement"+((item.count>1)?'s':'')+"</span>"):item.content; 
 
                }
              }
            );
 
           var clusters = new clusteriser(timeline);
            timeline.setItems(clusters.getClusters(items));
 
            timeline.on('rangechanged', function(){
              timeline.setItems(clusters.getClusters(items));
            });          }
        }
      );
 
      $(".zoom").on("click", function(){
          var dir = $(this).data('dir');
          var range = timeline.getWindow();
          var interval = range.end - range.start;
 
          timeline.setWindow({
              start: range.start.valueOf() - interval * dir * 0.2,
              end:   range.end.valueOf()   + interval * dir * 0.2
          });
      })
 
    </script>
  </body>
</html>
Résultat
Nom : Screen Shot 2016-09-17 at 2.10.13 PM.png
Affichages : 1080
Taille : 69,5 Ko

Mot finale
  • Le script n’est pas optimale mais un bon départ pour une solution plus pertinente.
  • On pourra facilement ajouter un system de cache avec un localStorage car a un certain moment on pourra sauvegarder les données d'un niveau de zoom pour éviter de calculer les clusters a chaque fois que ce niveau de zoom est atteint.
  • Il se peut que dans une version superieur de VIS cette fonctionnalité est bien integré en natif !

Envoyer le billet « Réaliser un clustering de données avec la timeline VIS.js » dans le blog Viadeo Envoyer le billet « Réaliser un clustering de données avec la timeline VIS.js » dans le blog Twitter Envoyer le billet « Réaliser un clustering de données avec la timeline VIS.js » dans le blog Google Envoyer le billet « Réaliser un clustering de données avec la timeline VIS.js » dans le blog Facebook Envoyer le billet « Réaliser un clustering de données avec la timeline VIS.js » dans le blog Digg Envoyer le billet « Réaliser un clustering de données avec la timeline VIS.js » dans le blog Delicious Envoyer le billet « Réaliser un clustering de données avec la timeline VIS.js » dans le blog MySpace Envoyer le billet « Réaliser un clustering de données avec la timeline VIS.js » dans le blog Yahoo

Mis à jour 19/08/2018 à 12h31 par LittleWhite (Coloration du code)

Catégories
Javascript , Développement Web

Commentaires