222 lines
7.2 KiB
JavaScript
222 lines
7.2 KiB
JavaScript
import { event, select, selectAll} from 'd3-selection';
|
|
import { values, set, map } from 'd3-collection';
|
|
import { drag } from 'd3-drag';
|
|
import { zoom } from 'd3-zoom';
|
|
import { forceSimulation, forceLink, forceManyBody, forceCenter, forceX, forceY, forceRadial } from 'd3-force';
|
|
// import { Wiki, Page } from './wiki.js';
|
|
import EventEmitter from 'eventemitter3';
|
|
// import { json } from 'd3-fetch';
|
|
|
|
export class ForceNet {
|
|
|
|
constructor (symbols) {
|
|
var width = 600,
|
|
height = 600;
|
|
|
|
this.symbols = symbols || {};
|
|
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.zoom = zoom()
|
|
.scaleExtent([1 / 16, 4])
|
|
.on("zoom", () => {
|
|
this.content.attr("transform", event.transform);
|
|
// console.log("transform", event.transform, this.content.attr("transform"));
|
|
});
|
|
this.rect = this.svg.append("rect")
|
|
.attr("width", 1000)
|
|
.attr("height", 1000)
|
|
.style("fill", "none")
|
|
.style("pointer-events", "all")
|
|
.call(this.zoom);
|
|
this.content = this.svg.append("g")
|
|
.attr("id", "content"),
|
|
this.linksg = this.content.append("g")
|
|
.attr("class", "links");
|
|
this.nodesg = this.content.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;
|
|
}
|
|
|
|
link_key (a, b) {
|
|
return (a < b) ? ("link_"+a+"_"+b) : ("link_"+b+"_"+a);
|
|
}
|
|
|
|
get_symbol (d, def) {
|
|
for (var i=0, l=d.cats.length; i<l; i++) {
|
|
if (this.symbols[d.cats[i]]) {
|
|
return this.symbols[d.cats[i]];
|
|
}
|
|
}
|
|
return this.symbols.default || def;
|
|
}
|
|
|
|
update_graph (graph) {
|
|
console.log("UPDATE GRAPH", graph.nodes.length, graph.links.length);
|
|
var link = this.linksg.selectAll("line")
|
|
.data(graph.links, d => { return this.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 that = this;
|
|
var node_enter = node.enter().append("g")
|
|
.attr("class", "page")
|
|
// .attr("class", d=>"page "+this.wiki.get_ns_classname(d.ns))
|
|
.on("click", function(d) {
|
|
that.events.emit("nodeclick", d, this);
|
|
// 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("use")
|
|
.attr("xlink:href", d => this.get_symbol(d, "default"))
|
|
.attr("class", "testcolor");
|
|
// {
|
|
// for (var i=0, l=d.cats.length; i<l; i++) {
|
|
// if (this.symbols[d.cats[i]]) {
|
|
// return this.symbols[d.cats[i]];
|
|
// }
|
|
// }
|
|
// return this.symbols.default || "default";
|
|
// });
|
|
|
|
// 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.update_nodes();
|
|
this.update_forces();
|
|
// this.simulation.alphaTarget(0.3).restart();
|
|
}
|
|
|
|
update_nodes () {
|
|
var nodes = this.nodesg.selectAll("g.page");
|
|
console.log("update_nodes", nodes.size());
|
|
nodes.classed("active", d=>d.active);
|
|
nodes.classed("active2", d=>d.active2);
|
|
nodes.classed("highlight", d=>d.highlight);
|
|
nodes.sort((a, b) => {
|
|
// console.log("sort", a, b);
|
|
var x = a.active ? 10 : (a.active2 ? 5 : 0),
|
|
y = b.active ? 10 : (b.active2 ? 5 : 0);
|
|
return x - y;
|
|
});
|
|
var links = this.linksg.selectAll("line");
|
|
links.classed("active2", d=>d.active2);
|
|
links.classed("history", d=>d.type == "history");
|
|
links.sort((a, b) => {
|
|
// console.log("sort", a, b);
|
|
var x = a.active2 ? 10 : (a.history ? 5 : 0),
|
|
y = b.active2 ? 10 : (b.history ? 5 : 0);
|
|
return x - y;
|
|
});
|
|
}
|
|
|
|
update_forces () {
|
|
var force = this.simulation.force("link");
|
|
console.log("update_forces:force", force);
|
|
this.simulation.force("link").strength(d => {
|
|
if (d.source.active || d.target.active) {
|
|
return 1;
|
|
} else {
|
|
// same as d3.force's defaultStrength
|
|
return 0.5 * (1 / Math.min(d.source.count, d.target.count));
|
|
}
|
|
});
|
|
this.simulation.alphaTarget(0.3).restart();
|
|
}
|
|
|
|
|
|
}
|
|
|