【问题标题】:how to update foci dynamically in multi-foci force-layout in d3.js如何在 d3.js 的多焦点强制布局中动态更新焦点
【发布时间】:2018-09-27 17:07:09
【问题描述】:

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

在下面使用数据子集的代码中,我希望能够在 id-group 和熟悉度之间切换,这会将图表从 3 个气泡簇更改为 5 个气泡簇。当前焦点是硬编码的,这会阻止切换工作。

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>

如何动态设置foci的坐标,如果是3-4簇,就对齐一行,如果是10簇,就做成3行小倍数?

谢谢。

【问题讨论】:

    标签: javascript d3.js force-layout


    【解决方案1】:

    此处最重要的更改是修改刻度功能以提供选择一组焦点或另一组焦点的选项。

    但是,首先,我们需要跟踪当前正在使用哪些焦点。所有这些需要做的就是在“家庭”和“熟悉”之间切换,或者如果你愿意,可以在一些不太直观的东西之间切换,比如真或假。我在下面的代码中使用了变量current

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

    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 + ")"; });
    }
    

    我将数组 foci 重命名为 familyFoci,因为两个 foci 都可以描述任一数组,我还确保您的节点在下面的 sn-ps 中具有熟悉属性

    这种修改让我们可以轻松地指定用于在一组焦点中设置特定焦点的属性,并指定我们想要哪一组焦点。

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

    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 功能来检查所需的焦点集是什么。

    所有这些都在一个快速的 sn-p 中:

    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>

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

    但是,这里有一个问题,当您移动焦点时,图表会继续冷却,直到它最终停止。当我们点击我们的一个按钮时,我们可以用多行代码给车轮涂上一点油并重置温度(alpha):

      .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>

    【讨论】:

      猜你喜欢
      • 2017-03-17
      • 2015-09-17
      • 1970-01-01
      • 2019-05-14
      • 2015-07-21
      • 2012-09-10
      • 2016-05-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多