This commit is contained in:
Michael Murtaugh
2019-05-27 16:05:50 +02:00
commit b027ca341a
29 changed files with 17864 additions and 0 deletions

210
src/wikimap.js Normal file
View File

@@ -0,0 +1,210 @@
import { event, select, selectAll} from 'd3-selection';
import { values, set, map } from 'd3-collection';
import { drag } from 'd3-drag';
import { forceSimulation, forceLink, forceManyBody, forceCenter, forceX, forceY, forceRadial } from 'd3-force';
import { Wiki, Page } from './wiki.js';
import EventEmitter from 'eventemitter3';
export class Map {
constructor (apiurl) {
var width = 600,
height = 600;
this.wiki = new Wiki(apiurl);
this.events = new EventEmitter();
this.active_page = null;
// this.nodes = {};
this.simulation = forceSimulation()
.velocityDecay(0.1)
.force("link", forceLink().id(d => d.title))
.force("charge", forceManyBody())
.force("radial", forceRadial(180, width/2, height/2));
// .force("center", forceCenter(width / 2, height / 2));
this.svg = null;
this.historylinks = {};
}
on (message, callback, context) {
this.events.on(message, callback, context);
}
init_svg (svg) {
this.svg = select(svg || "svg");
this.linksg = this.svg.append("g")
.attr("class", "links");
this.nodesg = this.svg.append("g")
.attr("class", "nodes");
}
dragstarted (d) {
if (!event.active) this.simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
dragged (d) {
d.fx = event.x;
d.fy = event.y;
}
dragended(d) {
if (!event.active) this.simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
update_graph (graph) {
// console.log("UPDATE GRAPH", graph.nodes.length, graph.links.length);
var that = this,
link = this.linksg.selectAll("line")
.data(graph.links, function (d) { return that.link_key(d.source.title, d.target.title) });
var link_enter = link.enter()
.append("line");
link.exit().each(d => {
d.source.linked = false;
d.target.linked = false;
}).remove();
link_enter.merge(link).each(d => {
d.source.linked = true;
d.target.linked = true;
});
var node = this.nodesg
.selectAll("g.page")
.data(graph.nodes, function (d) { return d.title });
var node_enter = node.enter().append("g")
.attr("class", d=>"page "+this.wiki.get_ns_classname(d.ns))
.on("click", d => {
this.set_active_node(d.title);
})
.on("mouseover", function (d) {
// console.log("mouseover", this);
select(this).classed("mouse", true);
})
.on("mouseout", function (d) {
// console.log("mouseout", this);
select(this).classed("mouse", false);
})
.call(drag()
.on("start", this.dragstarted.bind(this))
.on("drag", this.dragged.bind(this))
.on("end", this.dragended.bind(this)));
node_enter.append("circle")
.attr("r", 6);
node_enter.append("text")
.text(d => d.title)
.attr("x", 10);
//node_enter.append("title")
// .text(function(d) { return d.title; });
node = node_enter.merge(node);
link = link_enter.merge(link);
node.classed("active", d=>d.active);
this.simulation
.nodes(graph.nodes)
.on("tick", ticked);
this.simulation.force("link")
.links(graph.links);
this.simulation.force("radial").radius(d => d.linked ? null : 200);
function ticked() {
link
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
// node
// .attr("cx", function(d) { return d.x; })
// .attr("cy", function(d) { return d.y; });
node
.attr("transform", d => `translate(${d.x},${d.y})`);
}
this.simulation.alphaTarget(0.3).restart();
}
link_key (a, b) {
return (a < b) ? ("link_"+a+"_"+b) : ("link_"+b+"_"+a);
}
walk (node, links) {
var links_seen = {};
// var node = this.ensure_node(nodename);
node.all_links.forEach(x => {
var link_key = this.link_key(node.title, x.title);
if (!links_seen[link_key]) {
links.push({source: node, target: x});
links_seen[link_key] = true;
}
})
return;
}
set_active_title (title) {
this.set_active_node(title);
}
async set_active_node (page) {
if (typeof(page) === "string") {
page = this.wiki.get_page_by_title(page)
}
if (page === this.active_page) {
// console.log("page is already the active page", page, this.active_page);
return;
}
if (this.active_page) {
this.active_page.active = false;
var lkey = this.link_key(this.active_page.title, page.title),
source = (this.active_page.title < page.title) ? this.active_page : page,
target = (this.active_page.title < page.title) ? page : this.active_page;
this.historylinks[lkey] = {source: source, target: target};
}
// n = this.ensure_node(pagetitle);
this.active_page = page;
this.active_page.active = true;
this.load(this.active_page);
this.events.emit("page", this.active_page.title);
}
async load (page) {
console.log("loading", page.title);
var links = await page.get_links(),
backlinks = await page.get_linkshere(),
alllinks = this.wiki.union(links, backlinks);
page.all_links = alllinks;
// console.log("alllinks", alllinks);
var graph = {};
graph.nodes = values(this.wiki.pages_by_title);
graph.links = [];
this.walk(page, graph.links);
// activate historylinks
values(this.historylinks).forEach(x => {
graph.links.push(x);
})
// graph.links = titles.map(t => ({source: pagetitle, target: t}));
this.update_graph(graph);
// console.log("GOT DATA", titles);
// return titles;
}
}
// http://erg.activearchives.org/mw/api.php?action=query&prop=links&titles=Bienvenue_%C3%A0_l%E2%80%99erg
// http://erg.activearchives.org/w/api.php?action=query&prop=info&titles=Main%20Page
// Bienvenue_à_lerg
// http://erg.activearchives.org/mw/index.php/Bienvenue_%C3%A0_l%E2%80%99erg