admin管理员组

文章数量:1318563

I have a grid represented using d3 and svg. I am trying to select the neighbouring (adjacent) tiles to any specific tile on the grid. the tiles are accessed via their x and y coordinates on the grid. What I have feels fairly messy, and doesn't do exactly what I want, I don't want the clicked tile to be selected, or the tiles diagonal to it.

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

}

jsfiddle - /

What I'm wondering is - Is there a way to select the data via two attributes, like this:

d3.select("[x='40'], [y='40']") 

This does not work for me, but the logic behind it is how I would like to select the data.

I have a grid represented using d3 and svg. I am trying to select the neighbouring (adjacent) tiles to any specific tile on the grid. the tiles are accessed via their x and y coordinates on the grid. What I have feels fairly messy, and doesn't do exactly what I want, I don't want the clicked tile to be selected, or the tiles diagonal to it.

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

}

jsfiddle - https://jsfiddle/wkencq2w/15/

What I'm wondering is - Is there a way to select the data via two attributes, like this:

d3.select("[x='40'], [y='40']") 

This does not work for me, but the logic behind it is how I would like to select the data.

Share Improve this question asked Jan 5, 2016 at 10:06 user3636636user3636636 2,4992 gold badges17 silver badges31 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 3

Because it's D3, I won't do this based on calculations of positions but rather on data binding. This will simplify matters a lot and reduce the amount and the plexity of your code. One possible way might be to define a two-dimensional array of objects having x and y properties which is then bound to a D3 selection:

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

The part which benefits most from this approach is your test() function which may now act on the data bound to each rect rather than having to get attribute values and doing calculation with them.

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

Have a look at this JSFiddle for a full example.

You can select data via two attributes just by putting them together e.g. [x='40'][y='40']. This together with the , css operator allows the generation of a css selection string that gives you what you asked for.

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 ma
  selector = selector.substring(0, selector.length - 1);
  d3.selectAll(selector)
    .transition()
    .style("fill", "black"); 
}
<script src="https://cdnjs.cloudflare./ajax/libs/d3/3.4.11/d3.min.js"></script>

Or if you just want a cross without the centre as you describe in the question you could do this...

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 ma
  selector = selector.substring(0, selector.length - 1);
  d3.selectAll(selector)
    .transition()
    .style("fill", "black"); 
}
<script src="https://cdnjs.cloudflare./ajax/libs/d3/3.4.11/d3.min.js"></script>

I tried to fix this problem using filter:

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

}

Working code here

Hope this helps!

本文标签: javascriptselect data based on multiple attributes d3Stack Overflow