Skip to content

Instantly share code, notes, and snippets.

@MaximPiessen
Created May 13, 2019 10:11
Show Gist options
  • Save MaximPiessen/6c4637faeebbdc3fd12ecfd7b67cdd27 to your computer and use it in GitHub Desktop.
Save MaximPiessen/6c4637faeebbdc3fd12ecfd7b67cdd27 to your computer and use it in GitHub Desktop.

Revisions

  1. MaximPiessen created this gist May 13, 2019.
    551 changes: 551 additions & 0 deletions d3v4-brush-lite.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,551 @@
    (function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-dispatch'), require('d3-drag'), require('d3-interpolate'), require('d3-selection'), require('d3-transition')) :
    typeof define === 'function' && define.amd ? define(['exports', 'd3-dispatch', 'd3-drag', 'd3-interpolate', 'd3-selection', 'd3-transition'], factory) :
    (factory((global.d3 = global.d3 || {}),global.d3,global.d3,global.d3,global.d3,global.d3));
    }(this, (function (exports,d3Dispatch,d3Drag,d3Interpolate,d3Selection,d3Transition) { 'use strict';

    var constant = function(x) {
    return function() {
    return x;
    };
    };

    var BrushEvent = function(target, type, selection) {
    this.target = target;
    this.type = type;
    this.selection = selection;
    };

    function nopropagation() {
    d3Selection.event.stopImmediatePropagation();
    }

    var noevent = function() {
    d3Selection.event.preventDefault();
    d3Selection.event.stopImmediatePropagation();
    };

    var MODE_DRAG = {name: "drag"};
    var MODE_SPACE = {name: "space"};
    var MODE_HANDLE = {name: "handle"};
    var MODE_CENTER = {name: "center"};

    var X = {
    name: "x",
    handles: ["e", "w"].map(type),
    input: function(x, e) { return x && [[x[0], e[0][1]], [x[1], e[1][1]]]; },
    output: function(xy) { return xy && [xy[0][0], xy[1][0]]; }
    };

    var Y = {
    name: "y",
    handles: ["n", "s"].map(type),
    input: function(y, e) { return y && [[e[0][0], y[0]], [e[1][0], y[1]]]; },
    output: function(xy) { return xy && [xy[0][1], xy[1][1]]; }
    };

    var XY = {
    name: "xy",
    handles: ["n", "e", "s", "w", "nw", "ne", "se", "sw"].map(type),
    input: function(xy) { return xy; },
    output: function(xy) { return xy; }
    };

    var cursors = {
    overlay: "crosshair",
    selection: "move",
    n: "ns-resize",
    e: "ew-resize",
    s: "ns-resize",
    w: "ew-resize",
    nw: "nwse-resize",
    ne: "nesw-resize",
    se: "nwse-resize",
    sw: "nesw-resize"
    };

    var flipX = {
    e: "w",
    w: "e",
    nw: "ne",
    ne: "nw",
    se: "sw",
    sw: "se"
    };

    var flipY = {
    n: "s",
    s: "n",
    nw: "sw",
    ne: "se",
    se: "ne",
    sw: "nw"
    };

    var signsX = {
    overlay: +1,
    selection: +1,
    n: null,
    e: +1,
    s: null,
    w: -1,
    nw: -1,
    ne: +1,
    se: +1,
    sw: -1
    };

    var signsY = {
    overlay: +1,
    selection: +1,
    n: -1,
    e: null,
    s: +1,
    w: null,
    nw: -1,
    ne: -1,
    se: +1,
    sw: +1
    };

    function type(t) {
    return {type: t};
    }

    // Ignore right-click, since that should open the context menu.
    function defaultFilter() {
    return !d3Selection.event.button;
    }

    function defaultExtent() {
    var svg = this.ownerSVGElement || this;
    return [[0, 0], [svg.width.baseVal.value, svg.height.baseVal.value]];
    }

    // Like d3.local, but with the name “__brush” rather than auto-generated.
    function local(node) {
    while (!node.__brush) if (!(node = node.parentNode)) return;
    return node.__brush;
    }

    function empty(extent) {
    return extent[0][0] === extent[1][0]
    || extent[0][1] === extent[1][1];
    }

    function brushSelection(node) {
    var state = node.__brush;
    return state ? state.dim.output(state.selection) : null;
    }

    function brushX() {
    return brush$1(X);
    }

    function brushY() {
    return brush$1(Y);
    }

    var brush = function() {
    return brush$1(XY);
    };

    function brush$1(dim) {
    var extent = defaultExtent,
    filter = defaultFilter,
    listeners = d3Dispatch.dispatch(brush, "start", "brush", "end"),
    handleSize = 6,
    touchending;

    function brush(group) {

    var overlay = group
    .property("__brush", initialize)
    .selectAll(".overlay")
    .data([type("overlay")]);

    overlay.enter().append("rect")
    .attr("class", "overlay")
    .attr("pointer-events", "all")
    .attr("cursor", cursors.overlay)
    .merge(overlay)
    .each(function() {
    var extent = local(this).extent;
    d3Selection.select(this)
    .attr("x", extent[0][0])
    .attr("y", extent[0][1])
    .attr("width", extent[1][0] - extent[0][0])
    .attr("height", extent[1][1] - extent[0][1]);
    });

    group.selectAll(".selection")
    .data([type("selection")])
    .enter().append("rect")
    .attr("class", "selection")
    .attr("cursor", cursors.selection)
    .attr("fill", "#777")
    .attr("fill-opacity", 0.3)
    .attr("stroke", "#fff")
    .attr("shape-rendering", "crispEdges");

    var handle = group.selectAll(".handle")
    .data(dim.handles, function(d) { return d.type; });

    handle.exit().remove();

    handle.enter().append("rect")
    .attr("class", function(d) { return "handle handle--" + d.type; })
    .attr("cursor", function(d) { return cursors[d.type]; });

    group
    .each(redraw)
    .attr("fill", "none")
    .attr("pointer-events", "all")
    .style("-webkit-tap-highlight-color", "rgba(0,0,0,0)")
    .on("mousedown.brush touchstart.brush", started);
    }

    brush.move = function(group, selection) {
    if (group.selection) {
    group
    .on("start.brush", function() { emitter(this, arguments).beforestart().start(); })
    .on("interrupt.brush end.brush", function() { emitter(this, arguments).end(); })
    .tween("brush", function() {
    var that = this,
    state = that.__brush,
    emit = emitter(that, arguments),
    selection0 = state.selection,
    selection1 = dim.input(typeof selection === "function" ? selection.apply(this, arguments) : selection, state.extent),
    i = d3Interpolate.interpolate(selection0, selection1);

    function tween(t) {
    state.selection = t === 1 && empty(selection1) ? null : i(t);
    redraw.call(that);
    emit.brush();
    }

    return selection0 && selection1 ? tween : tween(1);
    });
    } else {
    group
    .each(function() {
    var that = this,
    args = arguments,
    state = that.__brush,
    selection1 = dim.input(typeof selection === "function" ? selection.apply(that, args) : selection, state.extent),
    emit = emitter(that, args).beforestart();

    d3Transition.interrupt(that);
    state.selection = selection1 == null || empty(selection1) ? null : selection1;
    redraw.call(that);
    emit.start().brush().end();
    });
    }
    };

    function redraw() {
    var group = d3Selection.select(this),
    selection = local(this).selection;

    if (selection) {
    group.selectAll(".selection")
    .style("display", null)
    .attr("x", selection[0][0])
    .attr("y", selection[0][1])
    .attr("width", selection[1][0] - selection[0][0])
    .attr("height", selection[1][1] - selection[0][1]);

    group.selectAll(".handle")
    .style("display", null)
    .attr("x", function(d) { return d.type[d.type.length - 1] === "e" ? selection[1][0] - handleSize / 2 : selection[0][0] - handleSize / 2; })
    .attr("y", function(d) { return d.type[0] === "s" ? selection[1][1] - handleSize / 2 : selection[0][1] - handleSize / 2; })
    .attr("width", function(d) { return d.type === "n" || d.type === "s" ? selection[1][0] - selection[0][0] + handleSize : handleSize; })
    .attr("height", function(d) { return d.type === "e" || d.type === "w" ? selection[1][1] - selection[0][1] + handleSize : handleSize; });
    }

    else {
    group.selectAll(".selection,.handle")
    .style("display", "none")
    .attr("x", null)
    .attr("y", null)
    .attr("width", null)
    .attr("height", null);
    }
    }

    function emitter(that, args) {
    return that.__brush.emitter || new Emitter(that, args);
    }

    function Emitter(that, args) {
    this.that = that;
    this.args = args;
    this.state = that.__brush;
    this.active = 0;
    }

    Emitter.prototype = {
    beforestart: function() {
    if (++this.active === 1) this.state.emitter = this, this.starting = true;
    return this;
    },
    start: function() {
    if (this.starting) this.starting = false, this.emit("start");
    return this;
    },
    brush: function() {
    this.emit("brush");
    return this;
    },
    end: function() {
    if (--this.active === 0) delete this.state.emitter, this.emit("end");
    return this;
    },
    emit: function(type) {
    d3Selection.customEvent(new BrushEvent(brush, type, dim.output(this.state.selection)), listeners.apply, listeners, [type, this.that, this.args]);
    }
    };

    function started() {
    if (d3Selection.event.touches) { if (d3Selection.event.changedTouches.length < d3Selection.event.touches.length) return noevent(); }
    else if (touchending) return;
    if (!filter.apply(this, arguments)) return;

    var that = this,
    type = d3Selection.event.target.__data__.type,
    mode = (d3Selection.event.metaKey ? type = "overlay" : type) === "selection" ? MODE_DRAG : (d3Selection.event.altKey ? MODE_CENTER : MODE_HANDLE),
    signX = dim === Y ? null : signsX[type],
    signY = dim === X ? null : signsY[type],
    state = local(that),
    extent = state.extent,
    selection = state.selection,
    W = extent[0][0], w0, w1,
    N = extent[0][1], n0, n1,
    E = extent[1][0], e0, e1,
    S = extent[1][1], s0, s1,
    dx,
    dy,
    moving,
    lockX,
    lockY,
    point0 = d3Selection.mouse(that),
    point = point0,
    emit = emitter(that, arguments).beforestart();

    if (type === "overlay") {
    state.selection = selection = [
    [w0 = dim === Y ? W : point0[0], n0 = dim === X ? N : point0[1]],
    [e0 = dim === Y ? E : w0, s0 = dim === X ? S : n0]
    ];
    } else {
    w0 = selection[0][0];
    n0 = selection[0][1];
    e0 = selection[1][0];
    s0 = selection[1][1];
    }

    w1 = w0;
    n1 = n0;
    e1 = e0;
    s1 = s0;

    var group = d3Selection.select(that)
    .attr("pointer-events", "none");

    var overlay = group.selectAll(".overlay")
    .attr("cursor", cursors[type]);

    if (d3Selection.event.touches) {
    group
    .on("touchmove.brush", moved, true)
    .on("touchend.brush touchcancel.brush", ended, true);
    } else {
    var view = d3Selection.select(d3Selection.event.view)
    .on("keydown.brush", keydowned, true)
    .on("keyup.brush", keyupped, true)
    .on("mousemove.brush", moved, true)
    .on("mouseup.brush", ended, true);

    d3Drag.dragDisable(d3Selection.event.view);
    }

    nopropagation();
    d3Transition.interrupt(that);
    redraw.call(that);
    emit.start();

    function moved() {
    var point1 = d3Selection.mouse(that);
    point = point1;
    moving = true;
    noevent();
    move();
    }

    function move() {
    var t;

    dx = point[0] - point0[0];
    dy = point[1] - point0[1];

    switch (mode) {
    case MODE_SPACE:
    case MODE_DRAG: {
    if (signX) dx = Math.max(W - w0, Math.min(E - e0, dx)), w1 = w0 + dx, e1 = e0 + dx;
    if (signY) dy = Math.max(N - n0, Math.min(S - s0, dy)), n1 = n0 + dy, s1 = s0 + dy;
    break;
    }
    case MODE_HANDLE: {
    if (signX < 0) dx = Math.max(W - w0, Math.min(E - w0, dx)), w1 = w0 + dx, e1 = e0;
    else if (signX > 0) dx = Math.max(W - e0, Math.min(E - e0, dx)), w1 = w0, e1 = e0 + dx;
    if (signY < 0) dy = Math.max(N - n0, Math.min(S - n0, dy)), n1 = n0 + dy, s1 = s0;
    else if (signY > 0) dy = Math.max(N - s0, Math.min(S - s0, dy)), n1 = n0, s1 = s0 + dy;
    break;
    }
    case MODE_CENTER: {
    if (signX) w1 = Math.max(W, Math.min(E, w0 - dx * signX)), e1 = Math.max(W, Math.min(E, e0 + dx * signX));
    if (signY) n1 = Math.max(N, Math.min(S, n0 - dy * signY)), s1 = Math.max(N, Math.min(S, s0 + dy * signY));
    break;
    }
    }

    if (e1 < w1) {
    signX *= -1;
    t = w0, w0 = e0, e0 = t;
    t = w1, w1 = e1, e1 = t;
    if (type in flipX) overlay.attr("cursor", cursors[type = flipX[type]]);
    }

    if (s1 < n1) {
    signY *= -1;
    t = n0, n0 = s0, s0 = t;
    t = n1, n1 = s1, s1 = t;
    if (type in flipY) overlay.attr("cursor", cursors[type = flipY[type]]);
    }

    if (state.selection) selection = state.selection; // May be set by brush.move!
    if (lockX) w1 = selection[0][0], e1 = selection[1][0];
    if (lockY) n1 = selection[0][1], s1 = selection[1][1];

    if (selection[0][0] !== w1
    || selection[0][1] !== n1
    || selection[1][0] !== e1
    || selection[1][1] !== s1) {
    state.selection = [[w1, n1], [e1, s1]];
    redraw.call(that);
    emit.brush();
    }
    }

    function ended() {
    nopropagation();
    if (d3Selection.event.touches) {
    if (d3Selection.event.touches.length) return;
    if (touchending) clearTimeout(touchending);
    touchending = setTimeout(function() { touchending = null; }, 500); // Ghost clicks are delayed!
    group.on("touchmove.brush touchend.brush touchcancel.brush", null);
    } else {
    d3Drag.dragEnable(d3Selection.event.view, moving);
    view.on("keydown.brush keyup.brush mousemove.brush mouseup.brush", null);
    }
    group.attr("pointer-events", "all");
    overlay.attr("cursor", cursors.overlay);
    if (state.selection) selection = state.selection; // May be set by brush.move (on start)!
    if (empty(selection)) state.selection = null, redraw.call(that);
    emit.end();
    }

    function keydowned() {
    switch (d3Selection.event.keyCode) {
    case 18: { // ALT
    if (mode === MODE_HANDLE) {
    if (signX) e0 = e1 - dx * signX, w0 = w1 + dx * signX;
    if (signY) s0 = s1 - dy * signY, n0 = n1 + dy * signY;
    mode = MODE_CENTER;
    move();
    }
    break;
    }
    case 32: { // SPACE; takes priority over ALT
    if (mode === MODE_HANDLE || mode === MODE_CENTER) {
    if (signX < 0) e0 = e1 - dx; else if (signX > 0) w0 = w1 - dx;
    if (signY < 0) s0 = s1 - dy; else if (signY > 0) n0 = n1 - dy;
    mode = MODE_SPACE;
    overlay.attr("cursor", cursors.selection);
    move();
    }
    break;
    }
    default: return;
    }
    noevent();
    }

    function keyupped() {
    switch (d3Selection.event.keyCode) {
    case 18: { // ALT
    if (mode === MODE_CENTER) {
    if (signX < 0) e0 = e1; else if (signX > 0) w0 = w1;
    if (signY < 0) s0 = s1; else if (signY > 0) n0 = n1;
    mode = MODE_HANDLE;
    move();
    }
    break;
    }
    case 32: { // SPACE
    if (mode === MODE_SPACE) {
    if (d3Selection.event.altKey) {
    if (signX) e0 = e1 - dx * signX, w0 = w1 + dx * signX;
    if (signY) s0 = s1 - dy * signY, n0 = n1 + dy * signY;
    mode = MODE_CENTER;
    } else {
    if (signX < 0) e0 = e1; else if (signX > 0) w0 = w1;
    if (signY < 0) s0 = s1; else if (signY > 0) n0 = n1;
    mode = MODE_HANDLE;
    }
    overlay.attr("cursor", cursors[type]);
    move();
    }
    break;
    }
    default: return;
    }
    noevent();
    }
    }

    function initialize() {
    var state = this.__brush || {selection: null};
    state.extent = extent.apply(this, arguments);
    state.dim = dim;
    return state;
    }

    brush.extent = function(_) {
    return arguments.length ? (extent = typeof _ === "function" ? _ : constant([[+_[0][0], +_[0][1]], [+_[1][0], +_[1][1]]]), brush) : extent;
    };

    brush.filter = function(_) {
    return arguments.length ? (filter = typeof _ === "function" ? _ : constant(!!_), brush) : filter;
    };

    brush.handleSize = function(_) {
    return arguments.length ? (handleSize = +_, brush) : handleSize;
    };

    brush.on = function() {
    var value = listeners.on.apply(listeners, arguments);
    return value === listeners ? brush : value;
    };

    return brush;
    }

    exports.brush = brush;
    exports.brushX = brushX;
    exports.brushY = brushY;
    exports.brushSelection = brushSelection;

    Object.defineProperty(exports, '__esModule', { value: true });

    })));
    356 changes: 356 additions & 0 deletions d3v4-selectable-force-directed-graph.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,356 @@
    function createV4SelectableForceDirectedGraph(svg, graph) {
    // if both d3v3 and d3v4 are loaded, we'll assume
    // that d3v4 is called d3v4, otherwise we'll assume
    // that d3v4 is the default (d3)
    if (typeof d3v4 == 'undefined')
    d3v4 = d3;

    var width = +svg.attr("width"),
    height = +svg.attr("height");

    let parentWidth = d3v4.select('svg').node().parentNode.clientWidth;
    let parentHeight = d3v4.select('svg').node().parentNode.clientHeight;

    var svg = d3v4.select('svg')
    .attr('width', parentWidth)
    .attr('height', parentHeight)

    // remove any previous graphs
    svg.selectAll('.g-main').remove();

    var gMain = svg.append('g')
    .classed('g-main', true);

    var rect = gMain.append('rect')
    .attr('width', parentWidth)
    .attr('height', parentHeight)
    .style('fill', 'white')

    var gDraw = gMain.append('g')


    var zoom = d3v4.zoom()
    .on('zoom', zoomed)

    gMain.call(zoom);


    function zoomed() {
    gDraw.attr('transform', d3v4.event.transform);
    }

    var color = d3v4.scaleOrdinal(d3v4.schemeCategory20);

    if (! ("links" in graph)) {
    console.log("Graph is missing links");
    return;
    }

    var nodes = {};
    var i;
    for (i = 0; i < graph.nodes.length; i++) {
    nodes[graph.nodes[i].id] = graph.nodes[i];
    graph.nodes[i].weight = 1.01;
    }

    // the brush needs to go before the nodes so that it doesn't
    // get called when the mouse is over a node
    var gBrushHolder = gDraw.append('g');
    var gBrush = null;

    // add color gradient to edges
    function getGradID(d){return "linkGrad-" + d.source + "-"+ d.target;}

    var defs = d3v4.select("svg").append("defs");

    var grads = defs.selectAll("linearGradient")
    .data(graph.links);

    var lingrads = grads.enter().append("linearGradient");

    lingrads.attr("id", getGradID)
    .attr("gradientUnits", "userSpaceOnUse")
    //source = red
    lingrads.append("stop")
    .attr("offset", "0%")
    .attr("stop-color", "#FF0000")
    //target = green
    lingrads.append("stop")
    .attr("offset", "100%")
    .attr("stop-color", "#39ff14")

    var link = gDraw.append("g")
    .attr("class", "links")
    .selectAll("line")
    .data(graph.links)
    .enter().append("line")
    .attr("stroke-width", function(d) { return Math.sqrt(d.value); });

    link.attr("source", function(d){return d.source})
    .attr("target", function(d){return d.target})
    .style("stroke", function(d){
    return "url(#" + getGradID(d) + ")";
    })
    .attr("class", function(d) {
    var id1 = d.source.toString();
    var id2 = d.target.toString();
    if(d.bi_directional){
    return "edge link-bi " + id1 + " " + id2;
    }else{
    return "edge link " + id1 + " " + id2;
    }
    })

    var node = gDraw.append("g")
    .attr("class", "nodes")
    .selectAll("circle")
    .data(graph.nodes)
    .enter().append("circle")
    .attr("r", 4)
    .attr("fill", function(d) {
    if ('color' in d)
    return d.color;
    else
    return "#626262";
    //return color(d.group);
    })
    .call(d3v4.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended));

    node.attr("id", function(d){return d.id})
    .attr("class", "node")

    // initialise force graph
    var simulation = d3v4.forceSimulation()
    .force("link", d3v4.forceLink()
    .id(function(d) { return d.id; })
    .distance(function(d) {
    return 30;
    //var dist = 20 / d.value;
    //console.log('dist:', dist);

    return dist;
    })
    )
    .force("charge", d3v4.forceManyBody())
    .force("center", d3v4.forceCenter(parentWidth / 2, parentHeight / 2))
    .force("x", d3v4.forceX(parentWidth/2))
    .force("y", d3v4.forceY(parentHeight/2));

    simulation
    .nodes(graph.nodes)
    .on("tick", ticked)

    simulation.force("link")
    .links(graph.links);

    function ticked() {
    // update node and line positions at every step of
    // the force simulation
    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; });

    lingrads.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; });
    }

    var brushMode = false;
    var brushing = false;

    var brush = d3v4.brush()
    .on("start", brushstarted)
    .on("brush", brushed)
    .on("end", brushended);

    function brushstarted() {
    // keep track of whether we're actively brushing so that we
    // don't remove the brush on keyup in the middle of a selection
    brushing = true;

    node.each(function(d) {
    d.previouslySelected = shiftKey && d.selected;
    });
    }

    rect.on('click', () => {
    node.each(function(d) {
    d.selected = false;
    d.previouslySelected = false;
    });
    node.classed("selected", false);
    });

    function brushed() {
    if (!d3v4.event.sourceEvent) return;
    if (!d3v4.event.selection) return;

    var extent = d3v4.event.selection;

    node.classed("selected", function(d) {
    return d.selected = d.previouslySelected ^
    (extent[0][0] <= d.x && d.x < extent[1][0]
    && extent[0][1] <= d.y && d.y < extent[1][1]);
    });
    }

    function brushended() {
    if (!d3v4.event.sourceEvent) return;
    if (!d3v4.event.selection) return;
    if (!gBrush) return;

    gBrush.call(brush.move, null);

    if (!brushMode) {
    // the shift key has been release before we ended our brushing
    gBrush.remove();
    gBrush = null;
    }

    brushing = false;
    }

    d3v4.select('body').on('keydown', keydown);
    d3v4.select('body').on('keyup', keyup);

    var shiftKey;

    function keydown() {
    shiftKey = d3v4.event.shiftKey;

    if (shiftKey) {
    // if we already have a brush, don't do anything
    if (gBrush)
    return;

    brushMode = true;

    if (!gBrush) {
    gBrush = gBrushHolder.append('g');
    gBrush.call(brush);
    }
    }
    }

    function keyup() {
    shiftKey = false;
    brushMode = false;

    if (!gBrush)
    return;

    if (!brushing) {
    // only remove the brush if we're not actively brushing
    // otherwise it'll be removed when the brushing ends
    gBrush.remove();
    gBrush = null;
    }
    }

    function dragstarted(d) {
    if (!d3v4.event.active) simulation.alphaTarget(0.9).restart();

    if (!d.selected && !shiftKey) {
    // if this node isn't selected, then we have to unselect every other node
    node.classed("selected", function(p) { return p.selected = p.previouslySelected = false; });
    }

    d3v4.select(this).classed("selected", function(p) { d.previouslySelected = d.selected; return d.selected = true; });

    node.filter(function(d) { return d.selected; })
    .each(function(d) { //d.fixed |= 2;
    d.fx = d.x;
    d.fy = d.y;
    })

    }

    function dragged(d) {
    //d.fx = d3v4.event.x;
    //d.fy = d3v4.event.y;
    node.filter(function(d) { return d.selected; })
    .each(function(d) {
    d.fx += d3v4.event.dx;
    d.fy += d3v4.event.dy;
    })
    }

    function dragended(d) {
    if (!d3v4.event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
    node.filter(function(d) { return d.selected; })
    .each(function(d) { //d.fixed &= ~6;
    d.fx = null;
    d.fy = null;
    })
    }

    /*var texts = ['Use the scroll wheel to zoom',
    'Hold the shift key to select nodes']
    svg.selectAll('text')
    .data(texts)
    .enter()
    .append('text')
    .attr('x', 900)
    .attr('y', function(d,i) { return 470 + i * 18; })
    .text(function(d) { return d; });
    */

    //change name on title to node clicked
    // Define the div for the tooltip
    var div = d3.select("body").append("div")
    .attr("class", "tooltip")
    .style("opacity", 0);

    node.on("mouseover", function(d) {
    div.transition()
    .duration(200)
    .style("opacity", .9);
    div .html(d.name)
    .style("left", (d3.event.pageX) + "px")
    .style("top", (d3.event.pageY - 28) + "px");
    })

    node.on("mouseout", function(d) {
    div.transition()
    .duration(500)
    .style("opacity", 0);
    })

    // only show edges related to node on click
    allEdges = $('.edge');
    allNodes = $('.node')
    node.on("click", function(d) {
    var id = d.id.toString();
    allEdges.removeClass('hidden');
    allNodes.removeClass('hidden');

    nonRelevantEdges = $('.edge:not(.' + id + ')');
    nonRelevantEdges.toggleClass('hidden');

    relevantNodes = ($('.'+id).map(function(){return $(this).attr("source");}).get()).concat($('.'+id).map(function(){return $(this).attr("target");}).get());
    relevantNodes[0] = '#' + relevantNodes[0];
    nodeIdList = relevantNodes.join(", #");

    allNodes.toggleClass('hidden')
    $(nodeIdList).removeClass('hidden');
    })

    // show all edges again when clicking on white space
    $('rect').on("click", function(d) {
    allEdges.removeClass('hidden');
    allNodes.removeClass('hidden');
    })

    return graph;
    };
    68 changes: 68 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,68 @@
    <!DOCTYPE html>
    <head>
    <meta charset="utf-8" />
    </head>
    <div align='center' id="d3_selectable_force_directed_graph">
    <svg />
    </div>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://d3js.org/d3.v4.js"></script>
    <script src="d3v4-brush-lite.js"></script>
    <script src="d3v4-selectable-force-directed-graph.js"></script>

    <script>
    $(document).ready(function(){
    var x=$(window).width();
    var y=$(window).height();

    $("#d3_selectable_force_directed_graph").css('width', x);
    $("#d3_selectable_force_directed_graph").css('height', y);

    var svg = d3.select('#d3_selectable_force_directed_graph');

    d3.json('relations.json', function(error, graph) {
    if (!error) {
    //console.log('graph', graph);
    createV4SelectableForceDirectedGraph(svg, graph);
    } else {
    console.error(error);
    }
    });
    });

    </script>
    <style type="text/css">
    div.tooltip {
    position: absolute;
    text-align: center;
    padding: 4px;
    font: 12px sans-serif;
    background: white;
    border: 0px;
    border-radius: 3px;
    pointer-events: none;
    }

    .hidden {
    opacity: 0.2;
    }

    #d3_selectable_force_directed_graph svg {
    font: 13px sans-serif;
    text-anchor: end;
    }

    #d3_selectable_force_directed_graph .node {
    stroke: #fff;
    stroke-width: 1px;
    }

    .node .selected {
    stroke: black;
    }

    .link-bi {
    stroke: #999 !important;
    }
    </style>
    1 change: 1 addition & 0 deletions relations.json
    1 addition, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.