javascript - 在 D3 hexbin 中,通过鼠标悬停增加多个六边形的半径

标签 javascript d3.js

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

我是 D3 新手,正在尝试在六边形图上创建相当棘手的悬停效果。我附上了下面的六 Angular 形图像来描述我的效果。

enter image description here

像这样的六边形图中的一个单独的六边形(除非它在边缘)与其他 6 个六边形接壤。我的目标是,当用户将鼠标悬停在六 Angular 形上时,该六 Angular 形以及周围 6 个六 Angular 形的半径两者都会增加,以产生某种弹出效果。

使用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),我认为我可能也需要移动这些邻域六 Angular 形的 (x,y) 中心以考虑更大的半径。

预先感谢您对此提供的任何帮助。我知道这是一篇很长的文章,但我已经为此完成了很多工作,这将是我正在研究的一个非常酷的悬停效果,因此感谢任何帮助!

最佳答案

这是代码的稍微修改版本,它也可以处理悬停六边形的相邻六边形:

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


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

如果悬停的六边形是从左起第 6 个、从上起第 3 个,那么我们可以给它 id #6-3 .

这样,当这个六边形悬停时,我们可以通过 id 选择它们来玩它相邻的六边形。 ,例如其左侧的有 id #5-3 .


为了给每个六边形一个 id ,如 d3 的 hexbin(input)仅用六边形的 x 替换我们的输入和y坐标,我们必须找到所有 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属性以便轻松更改六边形的悬停尺寸。

关于javascript - 在 D3 hexbin 中,通过鼠标悬停增加多个六边形的半径,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50029490/

相关文章:

javascript 正则表达式无法匹配给定的字符串

javascript - D3 条形图镜像

javascript - 使用数组中的文本向 SVG 元素添加工具提示

svg - d3.js:在 SVG 线性渐变中转换颜色?

javascript - jquery 之外的 DOM 操作

javascript - botConnection.activity$ node.js BOT

javascript - 接线元件事件

javascript - 将循环中的值设置为在第一个循环执行中设置的值

javascript - 通过 Javascript 反向链接

javascript - 来自 JSON 的 D3 节点和链接以及嵌套子数组