根据多个属性d3选择数据

时间:2016-01-05 10:06:16

标签: javascript d3.js svg

我有一个使用d3和svg表示的网格。我试图选择相邻(相邻)瓷砖到网格上的任何特定瓷砖。通过网格上的x和y坐标访问切片。 我的感觉相当混乱,并没有完全符合我的要求,我不希望选择点击的瓷砖,或者对角线的瓷砖。

solange

jsfiddle - https://jsfiddle.net/wkencq2w/15/

我想知道的是 - 有没有办法通过两个属性选择数据,如下所示:

var w = 960,
    h = 500,
    z = 20,
    x = w / z,
    y = h / z;

var svg = d3.select("body").append("svg")
    .attr("width", w)
    .attr("height", h);

svg.selectAll("rect")
    .data(d3.range(x * y))
  .enter().append("rect")
    .attr("width", z)
    .attr("height", z)
    .attr("clicked", false)
    .attr('x', horizontalpos)
    .attr('y', verticalpos)
    .on("click", test)
    .style("stroke", "rgb(6,120,155)")
    .style("stroke-width", 2)
    .style("fill", "rgb(255, 255, 255)")

function translate(d) {
  return "translate(" + (d % x) * z + "," + Math.floor(d / x) * z + ")";
}


function verticalpos(d) {
  return ((d % x) * z);
}

function horizontalpos(d) {
  return (Math.floor(d / x) * z );
 }

function test(){
  var d = d3.selectAll("[x='40']").filter("[y='40']");
  d3.selectAll("[x=" + "'"+ (parseInt(d.attr("x")) +20).toString() +"'" +"],[x=" + "'"+ (parseInt(d.attr("x")) -20).toString() +"'" +"],"+ "[x=" + "'"+ (parseInt(d.attr("x"))).toString() +"'" +"]")
    .filter("[y=" + "'"+ (parseInt(d.attr("y"))).toString() +"'" +"],[y=" + "'"+ (parseInt(d.attr("y")) +20).toString() +"'" +"]"+",[y=" + "'"+ (parseInt(d.attr("y")) -20).toString() +"'" +"]")
    .transition()
    .style("fill", "black"); 

}

这对我不起作用,但其背后的逻辑是我想如何选择数据。

3 个答案:

答案 0 :(得分:3)

因为它是D3,我不是基于位置计算而是基于数据绑定来做到这一点。这将大大简化问题并减少代码的数量和复杂性。一种可能的方法是定义具有x和y属性的二维对象数组,然后将其绑定到D3选择:

var grid = d3.range(y).map(function(dy) {
  return d3.range(x).map(function(dx) {
    return {x: dx, y: dy};
  });
});

var g = svg.selectAll("g")
    .data(grid)
  .enter().append("g")                 // Group each row's rects in a svg:g
    .selectAll("rect")                 // Do a nested selection
    .data(function(d) { return d; })   // Bind the sub-array for this row

从这种方法中受益最多的部分是你的test()函数,它现在可以对绑定到每个矩形的数据起作用,而不是必须获取属性值并使用它们进行计算。

function test(d) {
  var clicked = d3.select(this).datum();   // Object bound to the rect.
  d3.selectAll("rect").filter(function(d) {
    // Do the filtering based on data rather than on positions.
    return d.x === clicked.x && Math.abs(d.y - clicked.y) === 1 ||
           d.y === clicked.y && Math.abs(d.x - clicked.x) === 1;
  })
  .transition()
  .style("fill", "black"); 
}

请查看此JSFiddle以获取完整示例。

答案 1 :(得分:3)

您可以通过将两个属性放在一起来选择数据,例如: [X = '40 '] [Y = '40']。这与css运算符一起允许生成一个css选择字符串,为您提供所要求的内容。

var w = 960,
    h = 500,
    z = 20,
    x = w / z,
    y = h / z;

var svg = d3.select("body").append("svg")
    .attr("width", w)
    .attr("height", h);

svg.selectAll("rect")
    .data(d3.range(x * y))
  .enter().append("rect")
    .attr("width", z)
    .attr("height", z)
    .attr("clicked", false)
    .attr('x', horizontalpos)
    .attr('y', verticalpos)
    .on("click", test)
    .style("stroke", "rgb(6,120,155)")
    .style("stroke-width", 2)
    .style("fill", "rgb(255, 255, 255)")
    
function translate(d) {
  return "translate(" + (d % x) * z + "," + Math.floor(d / x) * z + ")";
}


function verticalpos(d) {
  return ((d % x) * z);
}

function horizontalpos(d) {
  return (Math.floor(d / x) * z );
 }
 
function test(d) {
  x = parseInt(d3.select(this).attr("x"));
  y = parseInt(d3.select(this).attr("y"));
  var selector = ""
  for (var dx=-20;dx<=20;dx+=20) {
  	for (var dy=-20;dy<=20;dy+=20) {
      selector += "[x='"+ (x + dx) +"'][y='"+ (y + dy) +"'],"
    }
  }
  // cut off the final extraneous comma
  selector = selector.substring(0, selector.length - 1);
  d3.selectAll(selector)
    .transition()
    .style("fill", "black"); 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

或者如果你只想要一个没有中心的十字架,你可以在问题中描述......

var w = 960,
    h = 500,
    z = 20,
    x = w / z,
    y = h / z;

var svg = d3.select("body").append("svg")
    .attr("width", w)
    .attr("height", h);

svg.selectAll("rect")
    .data(d3.range(x * y))
  .enter().append("rect")
    .attr("width", z)
    .attr("height", z)
    .attr("clicked", false)
    .attr('x', horizontalpos)
    .attr('y', verticalpos)
    .on("click", test)
    .style("stroke", "rgb(6,120,155)")
    .style("stroke-width", 2)
    .style("fill", "rgb(255, 255, 255)")
    
function translate(d) {
  return "translate(" + (d % x) * z + "," + Math.floor(d / x) * z + ")";
}


function verticalpos(d) {
  return ((d % x) * z);
}

function horizontalpos(d) {
  return (Math.floor(d / x) * z );
 }
 
function test(d) {
  x = parseInt(d3.select(this).attr("x"));
  y = parseInt(d3.select(this).attr("y"));
  var selector = ""
  var deltas = [[-20, 0], [20, 0], [0, 20], [0, -20]];
  for (var i=0;i < deltas.length;i++) {
    selector += "[x='"+ (x + deltas[i][0]) +"'][y='"+ (y + deltas[i][1]) +"'],"
  }
  
  // cut off the final extraneous comma
  selector = selector.substring(0, selector.length - 1);
  d3.selectAll(selector)
    .transition()
    .style("fill", "black"); 
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

答案 2 :(得分:2)

我尝试使用过滤器解决此问题:

function test(d){
  var x = d3.select(this);
  var x1 = (parseInt(x.attr("x")) +20);
  var x2 = (parseInt(x.attr("x")) -20);
  var y1 = (parseInt(x.attr("y")) +20);
  var y2 = (parseInt(x.attr("y")) -20);
var f = d3.selectAll("rect")[0].filter(function(d){
  //left rect
    if (d3.select(d).attr("x") == x1 && d3.select(d).attr("y") == parseInt(x.attr("y")))
    return true;
  //right rect  
    if (d3.select(d).attr("x") == x2 && d3.select(d).attr("y") == parseInt(x.attr("y")))
    return true;
  //bottom rect  
    if (d3.select(d).attr("y") == y1 && d3.select(d).attr("x") == parseInt(x.attr("x")))
    return true;
  //top rect  
    if (d3.select(d).attr("y") == y2 && d3.select(d).attr("x") == parseInt(x.attr("x")))
    return true;

  return false;
});
//select all filtered and make their fill black
d3.selectAll(f).transition().delay(100).style("fill", "black");

}

工作代码here

希望这有帮助!