如何在d3.js中的多焦点强制布局中动态更新焦点 [英] how to update foci dynamically in multi-foci force-layout in d3.js

查看:232
本文介绍了如何在d3.js中的多焦点强制布局中动态更新焦点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个多焦点布局,找不到动态设置焦点的方法.

I have a multi-foci layout and couldn't find a way to dynamically set the foci.

在下面的代码中,使用数据的子集,我希望能够在id-group和熟悉程度之间切换,从而将图表从3个气泡簇更改为5个气泡簇.当前焦点是硬编码的,因此无法进行切换.

In the code below using a subset of data, I wish to be able to toggle between id-group and familiarity, which would change the chart from 3 clusters of bubbles to 5 clusters of bubbles. Current foci are hard-coded, which prevent toggling from working.

var data = [
  {"id": 0, "name": "AngularJS", "familiarity":0,"r": 50 },
  {"id": 0, "name": "HTML5", "familiarity":1,"r": 40 },
  {"id": 0, "name": "Javascript", "familiarity":2,"r": 30 },


  {"id": 1, "name": "Actionscript","familiarity":0, "r": 50 },
  {"id": 1, "name": "Flash", "familiarity":4, "r": 32 },


  {"id": 2, "name": "Node Webkit", "familiarity":3,"r": 40 },
  {"id": 2, "name": "Chrome App", "familiarity":3,"r": 30 },
  {"id": 2, "name": "Cordova", "familiarity":0,"r": 45 },
];

var width = window.innerWidth,
    height = 450;

var fill = d3.scale.category10();

var nodes = [], labels = [],
    foci = [{x: 0, y: 150}, {x: 400, y: 150}, {x: 200, y: 150}];

var svg = d3.select("body").append("svg")
    .attr("width", "100%")
    .attr("height", height)
    //.attr("domflag", '');

var force = d3.layout.force()
    .nodes(nodes)
    .links([])
    .charge(-200)
    .gravity(0.1)
    .friction(0.8)
    .size([width, height])
    .on("tick", tick);

var node = svg.selectAll("g");

var counter = 0;

function tick(e) {
  var k = .3 * e.alpha;

  // Push nodes toward their designated focus.
  nodes.forEach(function(o, i) {
    o.y += (foci[o.id].y - o.y) * k;
    o.x += (foci[o.id].x - o.x) * k;
  });

  node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

}


var timer = setInterval(function(){

  if (nodes.length > data.length-1) { clearInterval(timer); return;}

  var item = data[counter];
  nodes.push({id: item.id, r: item.r, name: item.name});
  force.start();

  node = node.data(nodes);

  var n = node.enter().append("g")
      .attr("class", "node")
      .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
      .style('cursor', 'pointer')
      .on('mousedown', function() {
         var sel = d3.select(this);
         sel.moveToFront();
      })
      .call(force.drag);

  n.append("circle")
      .attr("r",  function(d) { return d.r/2; })
      .style("fill", function(d) { return fill(d.id); })

  n.append("text")
      .text(function(d){
          return d.name;
      })
      .style("font-size", function(d) {
          return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 16) + "px"; 
       })
      .attr("dy", ".35em")

  counter++;
}, 100);


d3.selection.prototype.moveToFront = function() {
  return this.each(function(){
    this.parentNode.appendChild(this);
  });
};

function resize() {
  width = window.innerWidth;
  force.size([width, height]);
  force.start();
}

d3.select(window).on('resize', resize);

circle {
  stroke: #fff;
}

<script src="//d3js.org/d3.v3.min.js"></script>

如何动态设置焦点的坐标,以使其仅是3-4个簇,将其对齐一行,但是如果它是10个簇,则将其设置为3行小的倍数?

How can I dynamically set the coordinates of the foci such that if it's just 3-4 cluster, align it in one row, but if it's 10 cluster, make it a 3-row small multiples?

谢谢.

推荐答案

您最重要的更改是修改tick函数,以提供选择一组焦点或另一组焦点的选项.

You most important change here is modifying the tick function to give the option of selecting one set of foci or the other.

但是,首先,我们需要跟踪当前正在使用哪些焦点.所有这一切需要做的是在家庭"和熟悉"之间切换,或者如果您愿意,可以使用不太直观的内容,例如true或false.我在下面的代码中使用了变量current.

First, however, we need to keep track of which foci points are currently being used. All this needs to do is toggle between "family" and "familiarity" or something less intuitive such as true or false if you want. I've used the variable current in the code below.

现在,我们可以通过添加某种检查以查看应使用哪组焦点来添加到您现有的tick函数中:

Now we can add to your existing tick function by adding some sort of check to see what set of foci should be used:

function tick(e) {
  var k = .3 * e.alpha;

  // nudge nodes to proper foci:
  if(current == "family" ) {
    nodes.forEach(function(o, i) {
      o.y += (familyFoci[o.id].y - o.y) * k;
      o.x += (familyFoci[o.id].x - o.x) * k;
    });
  }
  else {
     nodes.forEach(function(o, i) {
      o.y += (familiarityFoci[o.familiarity].y - o.y) * k;
      o.x += (familiarityFoci[o.familiarity].x - o.x) * k;
    }); 

  }   
  node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
}

我将数组焦点重命名为familyFoci,因为这两个焦点都可以描述任一数组,我还确保您的节点在下面的代码片段中具有熟悉属性

此修改使我们可以轻松地指定用于在一组焦点中设置特定焦点的属性,并指定所需的焦点组.

This modification allows us to easily specify the property used to set a specific focal point in a set of focal points, and specify which set of focal points we want.

现在我们可以创建第二组焦点:

Now we can create a second set of foci:

var familyFoci = [{x: 0, y: 150}, {x: 400, y: 150}, {x: 200, y: 150}];
var familiarityFoci = [{x:0,y:200},{x:100,y:100},{x:200,y:200},{x:300,y:100},{x:400,y:200}];

为了完整起见,我添加了一组基本按钮,这些按钮使用onclick函数检查以查看所需的焦点集中是什么.

For the sake of completeness I've added a basic set of buttons that use an onclick function to check to see what the desired set of focal points is.

以下是所有内容:

var data = [
  {"id": 0, "name": "AngularJS", "familiarity":0,"r": 50 },
  {"id": 0, "name": "HTML5", "familiarity":1,"r": 40 },
  {"id": 0, "name": "Javascript", "familiarity":2,"r": 30 },


  {"id": 1, "name": "Actionscript","familiarity":0, "r": 50 },
  {"id": 1, "name": "Flash", "familiarity":4, "r": 32 },


  {"id": 2, "name": "Node Webkit", "familiarity":3,"r": 40 },
  {"id": 2, "name": "Chrome App", "familiarity":3,"r": 30 },
  {"id": 2, "name": "Cordova", "familiarity":0,"r": 45 },
];

var width = window.innerWidth,
    height = 450;

var fill = d3.scale.category10();

var nodes = [], labels = [];
    
// two sets of foci:
var familyFoci = [{x: 0, y: 150}, {x: 400, y: 150}, {x: 200, y: 150}];
var familiarityFoci = [{x:0,y:200},{x:100,y:100},{x:200,y:200},{x:300,y:100},{x:400,y:200}];
	
	
var svg = d3.select("body").append("svg")
    .attr("width", "100%")
    .attr("height", height)

var force = d3.layout.force()
    .nodes(nodes)
    .links([])
    .charge(-200)
    .gravity(0.1)
    .friction(0.8)
    .size([width, height])
    .on("tick", tick);
	
//var node = svg.selectAll("circle");
var node = svg.selectAll("g");

var counter = 0;

//
// Create a basic interface:
//
var current = "family";
var buttons = svg.selectAll(null)
  .data(["family","familiarity"])
  .enter()
  .append("g")
  .attr("transform",function(d,i)  { return "translate("+(i*120+50)+","+50+")"; })
  .on("click", function(d) {
    if(d != current) {
	  current = d;
	} 
  })
  .style("cursor","pointer")
  
buttons.append("rect")
  .attr("width",100)
  .attr("height",50)
  .attr("fill","lightgrey")
    
buttons.append("text")
  .text(function(d) { return d; })
  .attr("dy", 30)
  .attr("dx", 50)
  .style("text-anchor","middle");

  

function tick(e) {
  var k = .3 * e.alpha;

  //
  // Check to see what foci set we should gravitate to:
  //
  if(current == "family") {
    // Push nodes toward their designated focus.
    nodes.forEach(function(o, i) {
      o.y += (familyFoci[o.id].y - o.y) * k;
      o.x += (familyFoci[o.id].x - o.x) * k;
    });
  }
  else {
     nodes.forEach(function(o, i) {
      o.y += (familiarityFoci[o.familiarity].y - o.y) * k;
      o.x += (familiarityFoci[o.familiarity].x - o.x) * k;
    }); 
  
  }

  node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

}





var timer = setInterval(function(){

  if (nodes.length > data.length-1) { clearInterval(timer); return;}

  var item = data[counter];
  nodes.push({id: item.id, r: item.r, name: item.name, familiarity: item.familiarity});
  force.start();

  node = node.data(nodes);

  var n = node.enter().append("g")
      .attr("class", "node")
      .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
      .style('cursor', 'pointer')
      .on('mousedown', function() {
         var sel = d3.select(this);
         sel.moveToFront();
      })
      .call(force.drag);

  n.append("circle")
      .attr("r",  function(d) { return d.r/2; })
      .style("fill", function(d) { return fill(d.id); })

  n.append("text")
      .text(function(d){
          return d.name;
      })
      .style("font-size", function(d) {
          return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 16) + "px"; 
       })
      .attr("dy", ".35em")

  counter++;
}, 100);


d3.selection.prototype.moveToFront = function() {
  return this.each(function(){
    this.parentNode.appendChild(this);
  });
};

function resize() {
  width = window.innerWidth;
  force.size([width, height]);
  force.start();
}

d3.select(window).on('resize', resize);

circle {
  stroke: #fff;
}

<script src="https://d3js.org/d3.v3.min.js"></script>

单击一个选项,如果不是当前选择的焦点,则力会更改它正在使用的焦点.

Click on one option, and if it isn't the currently selected foci, the force changes which foci it is using.

但是,这里有一个问题,随着焦点的移动,图形会继续冷却,直到最终停止.当我们单击其中一个按钮时,我们可以给车轮加一点润滑脂,并用另一行代码重置温度(alpha):

But, there is a problem here, the graph continues to cool down as you shift the foci until it ultimately stops. We can grease the wheels a bit and reset the temperature (alpha) with one more line of code when we click on one of our buttons:

  .on("click", function(d) {
    if(d != current) {
      current = d;
    force.alpha(0.228);  // reset the alpha
      } 
  })

这是一个演示:

var data = [
  {"id": 0, "name": "AngularJS", "familiarity":0,"r": 50 },
  {"id": 0, "name": "HTML5", "familiarity":1,"r": 40 },
  {"id": 0, "name": "Javascript", "familiarity":2,"r": 30 },


  {"id": 1, "name": "Actionscript","familiarity":0, "r": 50 },
  {"id": 1, "name": "Flash", "familiarity":4, "r": 32 },


  {"id": 2, "name": "Node Webkit", "familiarity":3,"r": 40 },
  {"id": 2, "name": "Chrome App", "familiarity":3,"r": 30 },
  {"id": 2, "name": "Cordova", "familiarity":0,"r": 45 },
];

var width = window.innerWidth,
    height = 450;

var fill = d3.scale.category10();

var nodes = [], labels = [];
    
// two sets of foci:
var familyFoci = [{x: 0, y: 150}, {x: 400, y: 150}, {x: 200, y: 150}];
var familiarityFoci = [{x:0,y:200},{x:100,y:100},{x:200,y:200},{x:300,y:100},{x:400,y:200}];
	
	
var svg = d3.select("body").append("svg")
    .attr("width", "100%")
    .attr("height", height)

var force = d3.layout.force()
    .nodes(nodes)
    .links([])
    .charge(-200)
    .gravity(0.1)
    .friction(0.8)
    .size([width, height])
    .on("tick", tick);
	
var node = svg.selectAll("g");

var counter = 0;

//
// Create a basic interface:
//
var current = "family";
var buttons = svg.selectAll(null)
  .data(["family","familiarity"])
  .enter()
  .append("g")
  .attr("transform",function(d,i)  { return "translate("+(i*120+50)+","+50+")"; })
  .on("click", function(d) {
    if(d != current) {
	  current = d;
    force.alpha(0.228);
	  } 
  })
  .style("cursor","pointer")
  
buttons.append("rect")
  .attr("width",100)
  .attr("height",50)
  .attr("fill","lightgrey")
    
buttons.append("text")
  .text(function(d) { return d; })
  .attr("dy", 30)
  .attr("dx", 50)
  .style("text-anchor","middle");


function tick(e) {
  var k = .3 * e.alpha;

  //
  // Check to see what foci set we should gravitate to:
  //
  if(current == "family") {
    // Push nodes toward their designated focus.
    nodes.forEach(function(o, i) {
      o.y += (familyFoci[o.id].y - o.y) * k;
      o.x += (familyFoci[o.id].x - o.x) * k;
    });
  }
  else {
     nodes.forEach(function(o, i) {
      o.y += (familiarityFoci[o.familiarity].y - o.y) * k;
      o.x += (familiarityFoci[o.familiarity].x - o.x) * k;
    }); 
  
  }

  node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

}





var timer = setInterval(function(){

  if (nodes.length > data.length-1) { clearInterval(timer); return;}

  var item = data[counter];
  nodes.push({id: item.id, r: item.r, name: item.name, familiarity: item.familiarity});
  force.start();

  node = node.data(nodes);

  var n = node.enter().append("g")
      .attr("class", "node")
      .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
      .style('cursor', 'pointer')
      .on('mousedown', function() {
         var sel = d3.select(this);
         sel.moveToFront();
      })
      .call(force.drag);

  n.append("circle")
      .attr("r",  function(d) { return d.r/2; })
      .style("fill", function(d) { return fill(d.id); })

  n.append("text")
      .text(function(d){
          return d.name;
      })
      .style("font-size", function(d) {
          return Math.min(2 * d.r, (2 * d.r - 8) / this.getComputedTextLength() * 16) + "px"; 
       })
      .attr("dy", ".35em")

  counter++;
}, 100);


d3.selection.prototype.moveToFront = function() {
  return this.each(function(){
    this.parentNode.appendChild(this);
  });
};

function resize() {
  width = window.innerWidth;
  force.size([width, height]);
  force.start();
}

d3.select(window).on('resize', resize);

circle {
  stroke: #fff;
}

<script src="https://d3js.org/d3.v3.min.js"></script>

这篇关于如何在d3.js中的多焦点强制布局中动态更新焦点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆