在D3 hexbin中,使用鼠标悬停

时间:2018-04-25 18:53:42

标签: javascript d3.js

编辑:here is a link to a codepen of mine我可以使用更简单的悬停功能。

我是D3新手并试图在hexbin图上创建一个相当棘手的悬停效果。我附上了下面的六角形图像来描述我的效果。

enter image description here

像这样的十六进制图中的单个六边形(除非它在边缘上)与其他6个六边形相邻。我的目标是当用户悬停在十六进制上时,的半径十六进制以及6个周围的六边形增加,以提供一种弹出效果。

使用Bostocks starter hexbin code here并稍微调整一下(添加radiusScale和悬停效果),我在下面制作了以下代码片段,它具有更简单的悬停效果:

var svg = d3.select("svg"),
    margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom,
    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

const randomX = d3.randomNormal(width / 2, 80),
    randomY = d3.randomNormal(height / 2, 80),
    points = d3.range(2000).map(function() { return [randomX(), randomY()]; });

const color = d3.scaleSequential(d3.interpolateLab("white", "steelblue"))
    .domain([0, 20]);

const hexbin = d3.hexbin()
    .radius(20)
    .extent([[0, 0], [width, height]]);

const x = d3.scaleLinear()
    .domain([0, width])
    .range([0, width]);

const y = d3.scaleLinear()
    .domain([0, height])
    .range([height, 0]);

// radiusScale
const radiusScale = d3.scaleSqrt()
    .domain([0, 10]) // domain is # elements in hexbin
    .range([0, 8]);  // range is mapping to pixels (or coords) for radius


g.append("clipPath")
    .attr("id", "clip")
  .append("rect")
    .attr("width", width)
    .attr("height", height);

g.append("g")
    .attr("class", "hexagon")
    .attr("clip-path", "url(#clip)")
  .selectAll("path")
  .data(hexbin(points))
  .enter().append("path")
    .attr("d", d => hexbin.hexagon(radiusScale(d.length))) 
    // .attr("d", hexbin.hexagon()) 
    .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
    .attr("fill", function(d) { return color(d.length); })
  .on('mouseover', function(d) { 
        d3.select(this)
          .attr("d", d => hexbin.hexagon(radiusScale((5+d.length)*2)))
  })
  .on('mouseout', function(d) { 
        d3.select(this)
          .attr("d", d => hexbin.hexagon(radiusScale(d.length)))
  })

  g.append("g")
      .attr("class", "axis axis--y")
      .call(d3.axisLeft(y).tickSizeOuter(-width));

  g.append("g")
      .attr("class", "axis axis--x")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x).tickSizeOuter(-height));
.hexagon {
  stroke: #000;
  stroke-width: 0.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<script src="https://d3js.org/d3-hexbin.v0.2.min.js"></script>
<svg width="500" height="400"></svg>

此效果仅会增加悬浮在其上的单个六边形的半径,而不会增加周围六边形的半径。

为了开始解决增加周围六边形半径的问题,我编写了这个函数,该函数采用分箱数据,(x,y)位置(六边形的中心),以及足够宽的半径来捕获(x,y)相邻六边形的中心:

// hexbinData, which was created using the hexbin() function, 
// has a .x and .y value for each element, and the .x and .y values 
// represent the center of that hexagon.

const findNeighborHexs = function(hexbinData, xHex, yHex, radius) {
  var neighborHexs = hexbinData
    .filter(row => row.x < (xHex+radius) & row.x > (xHex-radius))
    .filter(row => row.y < (yHex+radius) & row.y > (yHex-radius))

  return neighborHexs;
}

这就是我被卡住的地方......我不确定如何使用findNeighborHexs来(1)在悬停时选择这些元素,以及(2)更改这些元素大小。作为一个非常艰难的(3),我想我可能需要移动这些neighbox六角形的(x,y)中心,以考虑更大的半径。

提前感谢您对此的任何帮助。我知道这是一个很长的帖子,但我已经为此做了一堆事情,这将是一个非常酷的悬停效果我正在努力,所以任何帮助表示赞赏!

2 个答案:

答案 0 :(得分:2)

以下是您的代码的略微修改版本,该版本也使用悬停六边形的相邻六边形:

&#13;
&#13;
var svg = d3.select("svg"),
    margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom,
    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

const randomX = d3.randomNormal(width / 2, 80),
    randomY = d3.randomNormal(height / 2, 80),
    points = d3.range(2000).map(function() { return [randomX(), randomY()]; });

const color = d3.scaleSequential(d3.interpolateLab("white", "steelblue"))
    .domain([0, 20]);

const hexbin = d3.hexbin()
    .radius(20)
    .extent([[0, 0], [width, height]]);

const x = d3.scaleLinear()
    .domain([0, width])
    .range([0, width]);

const y = d3.scaleLinear()
    .domain([0, height])
    .range([height, 0]);

// radiusScale
const radiusScale = d3.scaleSqrt()
    .domain([0, 10]) // domain is # elements in hexbin
    .range([0, 8]);  // range is mapping to pixels (or coords) for radius

g.append("clipPath")
    .attr("id", "clip")
  .append("rect")
    .attr("width", width)
    .attr("height", height);

function unique(arr) {
    var u = {}, a = [];
    for(var i = 0, l = arr.length; i < l; ++i){
        if(!u.hasOwnProperty(arr[i])) {
            a.push(arr[i]);
            u[arr[i]] = 1;
        }
    }
    return a;
}

var xs = unique(hexbin(points).map(h => parseFloat(h.x))).sort(function(a,b) { return a - b;});
var ys = unique(hexbin(points).map(h => parseFloat(h.y))).sort(function(a,b) { return a - b;});

g.append("g")
    .attr("class", "hexagon")
    .attr("clip-path", "url(#clip)")
  .selectAll("path")
  .data(hexbin(points))
  .enter().append("path")
    .attr("id", d => xs.indexOf(d.x) + "-" + ys.indexOf(d.y))
    .attr("length", d => d.length)
    .attr("d", d => hexbin.hexagon(radiusScale(d.length)))
    .attr("transform", function(d) {
    	return "translate(" + d.x + "," + d.y + ")";
    })
    .attr("fill", function(d) { return color(d.length); })
  .on('mouseover', function(d) {

    d3.select(this).attr("d", d => hexbin.hexagon(radiusScale((5 + d.length) * 2)));

    var dx = xs.indexOf(d.x);
    var dy = ys.indexOf(d.y);

    [[-2, 0], [-1, -1], [1, -1], [2, 0], [1, 1], [-1, 1]].forEach( neighbour => {
      var elmt = document.getElementById((dx + neighbour[0]) + "-" + (dy + neighbour[1]))
      if (elmt) {
        var elmtLength = parseInt(elmt.getAttribute("length"));
        elmt.setAttribute("d", hexbin.hexagon(radiusScale(5 + elmtLength)));
      }
    });
  })
  .on('mouseout', function(d) {

    d3.select(this).attr("d", d => hexbin.hexagon(radiusScale(d.length)));

    var dx = xs.indexOf(d.x);
    var dy = ys.indexOf(d.y);

    [[-2, 0], [-1, -1], [1, -1], [2, 0], [1, 1], [-1, 1]].forEach( neighbour => {
      var elmt = document.getElementById((dx + neighbour[0]) + "-" + (dy + neighbour[1]))
      if (elmt) {
        var elmtLength = parseInt(elmt.getAttribute("length"));
        elmt.setAttribute("d", hexbin.hexagon(radiusScale(elmtLength)));
      }
    });
  })

  g.append("g")
      .attr("class", "axis axis--y")
      .call(d3.axisLeft(y).tickSizeOuter(-width));

  g.append("g")
      .attr("class", "axis axis--x")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x).tickSizeOuter(-height));
&#13;
.hexagon {
  stroke: #000;
  stroke-width: 0.5px;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
<script src="https://d3js.org/d3-hexbin.v0.2.min.js"></script>
<svg width="500" height="400"></svg>
&#13;
&#13;
&#13;

这个想法是给每个六边形id以便能够选择它。

如果悬停的六边形是左边第6个,顶部是第3个,那么我们可以给它id #6-3

这种方式,当这个六边形悬停时,我们可以通过id选择它们来使用相邻的六边形,例如左边的六边形有id #5-3。 / p>

为了给每个六边形id,d3&#39; s hexbin(input)仅用六边形替换我们的输入。 xy坐标,我们必须找到所有生成的xy

var xs = unique(hexbin(points).map(h => parseFloat(h.x))).sort(function(a,b) { return a - b;});
var ys = unique(hexbin(points).map(h => parseFloat(h.y))).sort(function(a,b) { return a - b;});

其中unique是任何只保留不同值的函数。

这样,我们的六边形可以这样给出id

...
.data(hexbin(points))
  .enter().append("path")
  .attr("id", d => xs.indexOf(d.x) + "-" + ys.indexOf(d.y))
  ...

现在我们的六边形有id,我们可以修改我们的mouseovermouseout以使用这些相邻的六边形:

相邻的六边形是我们需要通过以下方式对徘徊的六边形的x和y求和的方法:

[[-2, 0], [-1, -1], [1, -1], [2, 0], [1, 1], [-1, 1]]

给出了mouseover(除了修改悬停的六边形的大小):

.on('mouseover', function(d) {

  d3.select(this).attr("d", d => hexbin.hexagon(radiusScale((5 + d.length) * 2)));

  var dx = xs.indexOf(d.x);
  var dy = ys.indexOf(d.y);

  [[-2, 0], [-1, -1], [1, -1], [2, 0], [1, 1], [-1, 1]].forEach( neighbour => {
    var elmt = document.getElementById((dx + neighbour[0]) + "-" + (dy + neighbour[1]))
    if (elmt) {
      var elmtLength = parseInt(elmt.getAttribute("length"));
      elmt.setAttribute("d", hexbin.hexagon(radiusScale(5 + elmtLength)));
    }
  });
})

请注意,除了设置每个六边形的id之外,我们还包含length属性,以便轻松更改六边形的悬停大小。

答案 1 :(得分:2)

您可以将mouseover和mouseout函数修改为以下内容,选择所有六边形并根据它们是否在您定义的半径范围内设置大小:

.on('mouseover', function(d) {
        let dx = d.x
        let dy = d.y
        let r = 50 //set this to be an appropriate size radius

        d3.selectAll(".hexagon").selectAll("path")
          .attr("d", function(f) {
              if ((f.x < (dx + r) & f.x > (dx - r)) &  (f.y < (dy + r) & f.y > (dy - r)))  {
                return hexbin.hexagon(radiusScale((5+f.length)*2))
              }
              else {
                return hexbin.hexagon(radiusScale((f.length)))
              }
          })


  })
  .on('mouseout', function(d) { 
         d3.selectAll(".hexagon").selectAll("path")
          .attr("d", d => hexbin.hexagon(radiusScale(d.length)))
  })