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 { 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 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(); } }