[Mise à jour: le serveur du Monde n'autorise plus l'accès de sites tiers au fichier de données https://www.lemonde.fr/webservice/decodex/updates, ce qui casse cette visualisation. Ne sachant s'il est autorisé de recopier le fichier ici, je m'abstiens.]
Le Monde vient de publier son “Décodex: nos conseils pour vérifier les informations qui circulent en ligne”.
Il s'agit d'une liste de sites, commentés et notés selon un schéma de couleurs.
Nous les regroupons ici en fonction de certains mots-clés contenus dans les descriptifs, avec l'algorithme t-SNE.
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<style>
body {
margin: 0;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
font-family: monospace;
}
#tip {
position: absolute;
top: -100;
left: 0;
z-index: 2;
background: white;
padding: 2px;
pointer-events: none;
max-width: 27em;
}
#legende {
position: absolute;
top: 0;
left: 0;
z-index: 1;
background: rgba(255,255,255,0.2);
padding: 2px;
pointer-events: none;
max-width: 27em;
}
</style>
</head>
<body>
<script>
const width = 820,
height = 820,
margin = { top: 60, bottom: 60, left: 60, right: 60 },
scalecountry = d3.scaleLinear().range(['#777', '#60c7ff', '#fe4823', 'orange','#00e00b',]).domain([0,1,2,3,4]),
centerx = d3.scaleLinear()
.range([0, width]),
centery = d3.scaleLinear()
.range([0, height]);
const svg = d3.select("body").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})`);
const tip = d3.select('body').append('div').attr('id', 'tip')
d3.json('https://www.lemonde.fr/webservice/decodex/updates', function (json) {
tags = [/\bblog/, /bpas une source/, /\b(complot|conspi)/, /\bdroite/, /\b(gauche)/, /\b(extrême[- ]droite|national[ei]|patri|antisé|migran|raciste|homophob|islam|ivg)/, /\bfran[çc][ea]/, /\baméri/, /\b(faux|fausse|erroné|fictif|mauvais|trompeu|condamn)/, /\b(satirique|parodi|blague|caricatur)/, /b(opinion|militant)/, /\brégional/, /\b(journal\b|presse|quotidien|magazine|hebdo)/, /\bspécialis/, /\b(peu fiable|sensation|racole|vérifi)/ ];
const data = d3.values(json.sites)
.map((d, i) => {
return {
lon: Math.random(),
lat: Math.random(),
name: d[2],
link: 'https://www.lemonde.fr/verification/source/' + d[3] + '/',
r: 12.5,
score: +d[0],
desc: d[1],
tags: tags.map(tag => !!d[1].toLowerCase().match(tag)),
color: scalecountry(+d[0])
};
});
// pos is the array of positions that will be updated by the tsne worker
// start with the geographic coordinates as is (plate-carrée)
// random or [0,0] is fine too
let pos = data.map(d => [d.lon, -d.lat]);
const forcetsne = d3.forceSimulation(
data.map(d => (d.x = width / 2, d.y = height / 2, d))
)
.alphaDecay(0.005)
.alpha(0.2)
.force('tsne', function (alpha) {
centerx.domain(d3.extent(pos.map(d => d[0])));
centery.domain(d3.extent(pos.map(d => d[1])));
data.forEach((d, i) => {
d.x += alpha * (centerx(pos[i][0]) - d.x);
d.y += alpha * (centery(pos[i][1]) - d.y);
});
})
.force('collide', d3.forceCollide().radius(d => 1 + d.r))
.on('tick', function () {
// drawcanvas(canvas, data);
drawsvg(svg, data);
});
function drawsvg(svg, nodes) {
const g = svg.selectAll('g.city')
.data(nodes);
var enter = g.enter().append('g').classed('city', true);
enter.append('circle')
.attr('r', d => d.r)
.attr('fill', d => d.color)
.on('click', d => {
window.open(d.link,'targetWindow');
})
.on('mouseover', function(d) {
tip
.style('left', Math.min(width - 200, d.x - 10)+'px')
.style('top', Math.min(height-100, d.y + margin.top + 10)+'px')
.html(d.name + '<br><small><em>Commentaire des Décodeurs:</em> ' + d.desc + '</small>')
});
enter
.filter(d => d.r > 7)
.append('text')
.attr('fill', 'white')
.style('font-size', '10px')
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
.attr('pointer-events', 'none')
.text(d => d.name.replace(/^(Le |La |L')/, '').substring(0,4));
g.attr('transform', d => `translate(${d.x},${d.y})`);
}
d3.queue()
.defer(d3.text, 'tsne.js')
.defer(d3.text, 'https://unpkg.com/d3-geo')
.defer(d3.text, 'worker.js')
.awaitAll(function (err, scripts) {
const worker = new Worker(
window.URL.createObjectURL(
new Blob(scripts, {
type: "text/javascript"
})
)
);
worker.postMessage({
maxIter: 10,
dim: 2,
perplexity: 30.0,
data: data
});
worker.onmessage = function (e) {
if (e.data.log) console.log.apply(this, e.data.log);
if (e.data.pos) pos = e.data.pos;
if (e.data.done && e.data.done < 10000 && e.data.cost > 1e-2) {
worker.postMessage({
maxIter: e.data.done + 10,
});
}
};
});
});
</script>
</body>
https://d3js.org/d3.v4.min.js