编辑:here is a link to a codepen of mine我可以使用更简单的悬停功能。
我是D3新手并试图在hexbin图上创建一个相当棘手的悬停效果。我附上了下面的六角形图像来描述我的效果。
像这样的十六进制图中的单个六边形(除非它在边缘上)与其他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)中心,以考虑更大的半径。
提前感谢您对此的任何帮助。我知道这是一个很长的帖子,但我已经为此做了一堆事情,这将是一个非常酷的悬停效果我正在努力,所以任何帮助表示赞赏!
答案 0 :(得分:2)
以下是您的代码的略微修改版本,该版本也使用悬停六边形的相邻六边形:
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;
这个想法是给每个六边形id
以便能够选择它。
如果悬停的六边形是左边第6个,顶部是第3个,那么我们可以给它id
#6-3
。
这种方式,当这个六边形悬停时,我们可以通过id
选择它们来使用相邻的六边形,例如左边的六边形有id
#5-3
。 / p>
为了给每个六边形id
,d3&#39; s 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
属性,以便轻松更改六边形的悬停大小。
答案 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)))
})