3

Hi Stackoverflow Community!

So my problem is the following:

I have a d3 force-directed graph. The nodes of this graph are divs. Now i want to be able to resize this divs using the mouse. I use the jqueryui resizable() for this purpose.

Unfortunately it renders the ui changes (when i reach the edges of the nodea i get the mousesymbol for resize) but i cannot resize them. I believe it is because the d3-forcegraph overlays the jqueryui function. I tried to stop the graph so that i can access the resize functionality but this seems not to work.

Does anybody have an idea how i could make these divs resizable? I created a fiddle to show what i mean: https://jsfiddle.net/5jgrf5h8/

//constants for the network visualisation
var height = window.innerHeight-20; //fullsize svg
var width = window.innerWidth-20;


var nodes = [
    {"id": "RootNode", "group": 0},
    {"id": "Node1", 
      "ip_adresses": [
        {"ip_adress": "aa.bbb.114.80/28"}
      ],
      "group": 1},
    {"id": "Node2", 
      "ip_adresses": [
        {"ip_adress": "aa.bbb.117.96/28"}
      ],
      "group": 1},
    {"id": "Node3",
      "ip_adresses": [
        {"ip_adress": "eeee:ffff:400:3001::7"},
        {"ip_adress": "eeee:ffff:400:3001::8"},
        {"ip_adress": "eeee:ffff:400:3001::9"},
        {"ip_adress": "eeee:ffff:400:3001::10"},
        {"ip_adress": "eeee:ffff:400:3001::11"},
        {"ip_adress": "eeee:ffff:400:3001::12"},
        {"ip_adress": "eeee:ffff:400:3001::13"},
        {"ip_adress": "eeee:ffff:400:3001::14"},
        {"ip_adress": "eeee:ffff:400:3001::15"},
        {"ip_adress": "eeee:ffff:400:3001::16"},
        {"ip_adress": "eeee:ffff:400:3001::17"}
      ],
      "group": 1},
    {"id": "Node4", 
      "ip_adresses": [
        {"ip_adress": "cc.dd38.151"}, 
        {"ip_adress": "cc.dd38.152"}
      ],
      "group": 1},
    {"id": "Node5", 
      "ip_adresses": [
        {"ip_adress": "aa.bbb.114.36"}, 
        {"ip_adress": "aa.bbb.114.37"}
      ],
      "group": 1}
  ];

var links = [
    {"source": "RootNode", "target": "Node1", "value": 140},
    {"source": "RootNode", "target": "Node2", "value": 140},
    {"source": "RootNode", "target": "Node3", "value": 140},
    {"source": "RootNode", "target": "Node4", "value": 140},
    {"source": "RootNode", "target": "Node5", "value": 140}
  ];

var color = d3.scaleOrdinal(d3.schemeCategory10);

//creating and configuring the d3 force-directed graph simulation
var simulation = d3.forceSimulation()
      .force("charge", d3.forceManyBody().strength(-2000))
      .force("center", d3.forceCenter(width / 2, height / 2))
      .force("link", d3.forceLink()
        .distance(function(d){return d.value;})
        .strength(1.3).id(function(d){return d.id;}));

//create a svg in the body of index.html
var svg = d3.select("body").append("svg")
  .classed("simulation", 1)
  .attr("width", width)
  .attr("height", height);

//setting the links

var glink = svg.append("g")
.attr("class", "links")
.selectAll("links")
.data(links).enter();

var link = glink.append("polyline")
.attr("stroke-width", 2);



//setting the nodes. they will be div elements
var node = d3.select("body")
  .append("div")
  .attr("class", "nodes")
  .selectAll(".node")
  .data(nodes)
  .enter().append("div")
  .attr("class", "node")
  .attr("id", function(d){return d.id;})
  .style("background", function(d) { return color(d.group); });


node.each(function (d, i){
  if(d.group === 1 )
  {
    console.log(d.id, i, d3.select(this).attr("id"));

    d3.select(this).append("select")
    .attr("size", 2)
    .selectAll('option')
    .data(nodes[i].ip_adresses)
    .enter()
    .append("option")
    .text(function(d){return d.ip_adress;});
  }
  else
  {
    d3.select(this).text(function (d){return d.id;});
  }
});



//start the simulation
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));


//assigning data to the simulation and set tick
simulation
.nodes(nodes)
.on("tick", ticked);

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

function ticked() {

 link.attr("points", function(d) {

  var sourceX = d.source.x;
  var sourceY = d.source.y;
  var targetX = d.target.x;
  var targetY = d.target.y;

  return sourceX + "," + sourceY + " " + 
  (sourceX + targetX)/2 + "," + (sourceY + targetY)/2 + " " + 
  targetX + ", " + targetY; 
});

 node.style('left', function(d ,i) {
  return d.x-$("[id='"+d.id+"']").outerWidth()/2+"px";
})
 .style('top', function(d) {
  return d.y-$("[id='"+d.id+"']").outerHeight()/2+"px";
});

}

d3.select("body").selectAll("option").on("dblclick", function(){
  alert(this.text);
});

//this would be nice but it is not working in the d3 graph
$( ".node" ).resizable();


function dragstarted(d) {
  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
    d.fx = d.x;
    d.fy = d.y;
}

function dragged(d) {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
    d.fixed = true;
}

function dragended(d) {
  if (!d3.event.active) simulation.alphaTarget(0);
    d.fx = null;
    d.fy = null;
}
Bernd Da
  • 129
  • 9

1 Answers1

2

First change, you need is remove the drag start and end events:

node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));

Reason: they are eating up the event so JQUERY resizable will not work.

Second add class to your select so that they are unique (will be used for jquery selection):

 d3.select(this).append("select")
    .attr("size", 2)
    .attr("class", function(){ return "my-list" + i;})//giving unique class to all select.

Third add resize (alsoResize) to select unique class names made above, so that on resize the select also resizes.

$( ".node" ).each(function(i){
   $( this ).resizable({alsoResize: ".my-list"+i});
})

working code here

Cyril Cherian
  • 32,177
  • 7
  • 46
  • 55
  • Hello Cyril! The resizing now indeed works as it is intented thanks for your help here! However, due to the removal of the drag events it has the drawback that the nodes cannot be moved around anymore. Is there a way to still maintain the nodes to be draggable? – Bernd Da Feb 16 '17 at 06:14
  • well i am sorry I think you can't have best of both worlds :). May be a button or something to add drag listeners, and later revoke it when resize is happening. – Cyril Cherian Feb 16 '17 at 06:17
  • Okay :) So i found a way now to remove the drag-listener for a single node by using this line d3.select('#Node1').on('.drag', null); . This disables drag for Node1 and makes it resizable. Unfortunately I can't bring the drag-listener back on the element. I tried to use **var dragCallback = d3.select('#Node1').property('__onmousedown.drag')['_']; ** and then ´d3.select('#Node1').on('mousedown.drag', dragCallback);´ but i already receive the message: Uncaught TypeError: Cannot read property '_' of undefined. – Bernd Da Feb 16 '17 at 09:47
  • I saw this solution at: http://stackoverflow.com/questions/13136355/d3-js-remove-force-drag-from-a-selection. I don't know what i'm doing wrong at trying to reattach the drag-listener. Maybe the proposed approach doesn't work anymore because i use d3 v4? – Bernd Da Feb 16 '17 at 09:50
  • 1
    With var listener = selection.on("mousedown.drag"); selection.on("mousedown.drag", null); // removed selection.on("mousedown.drag", listener); // re-added It's possible now to remove the d3 listener then resize the div and after that re-add the listener. – Bernd Da Feb 17 '17 at 03:38