编辑:和here is a link to a codepen of mine我可以在其中使用更简单的悬停功能。
我是 D3 新手,正在尝试在六边形图上创建相当棘手的悬停效果。我附上了下面的六 Angular 形图像来描述我的效果。
像这样的六边形图中的一个单独的六边形(除非它在边缘)与其他 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
坐标,我们必须找到所有 x
和 y
制作:
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
,我们可以修改我们的mouseover
和mouseout
玩这些相邻的六边形:
相邻六边形是我们需要对悬停六边形的 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/