在鼠标悬停时显示数据
Posted
技术标签:
【中文标题】在鼠标悬停时显示数据【英文标题】:Show data on mouseover of circle 【发布时间】:2012-06-04 00:47:26 【问题描述】:我有一组数据以散点图的形式绘制。当我将鼠标悬停在其中一个圆圈上时,我希望它弹出数据(如 x、y 值,也许更多)。这是我尝试使用的:
vis.selectAll("circle")
.data(datafiltered).enter().append("svg:circle")
.attr("cx", function(d) return x(d.x);)
.attr("cy", function(d) return y(d.y))
.attr("fill", "red").attr("r", 15)
.on("mouseover", function()
d3.select(this).enter().append("text")
.text(function(d) return d.x;)
.attr("x", function(d) return x(d.x);)
.attr("y", function (d) return y(d.y);); );
我怀疑我需要更多关于输入哪些数据的信息?
【问题讨论】:
我也试过:vis.selectAll("circle").each(function (d) vis.append("svg:text").attr("x", dx). attr("y", dy) .text(function (d) return dx; ); );唉,无济于事。 bl.ocks.org/Caged/6476579 【参考方案1】:我假设你想要的是一个工具提示。最简单的方法是将svg:title
元素附加到每个圆圈,因为浏览器将负责显示工具提示,而您不需要鼠标处理程序。代码类似于
vis.selectAll("circle")
.data(datafiltered).enter().append("svg:circle")
...
.append("svg:title")
.text(function(d) return d.x; );
如果您想要更高级的工具提示,例如可以使用 Tipsy。示例见here。
【讨论】:
我喜欢醉醺醺的。我现在唯一的问题是它指向圆的左上角,而不是那个演示中的边缘。我没有找到任何明显的原因。 jsfiddle.net/scottieb/JwaaV(在最底部喝醉了) 那个 jsfiddle 好像没有工具提示? 您可以尝试将工具提示添加到与实际圆圈重叠的svg:g
,但宽度和高度为零。目前它正在使用边界框并将工具提示放在边缘。玩转酒醉的选择可能也会有所帮助。
它似乎不再起作用了。我还发现了一个使用 svg:title 失败的示例:bl.ocks.org/ilyabo/1339996
@nos 为我工作。【参考方案2】:
这里描述了一种制作工具提示的好方法:Simple D3 tooltip example
你必须附加一个 div
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text("a simple tooltip");
然后你可以使用它来切换它
.on("mouseover", function()return tooltip.style("visibility", "visible");)
.on("mousemove", function()return tooltip.style("top",
(d3.event.pageY-10)+"px").style("left",(d3.event.pageX+10)+"px");)
.on("mouseout", function()return tooltip.style("visibility", "hidden"););
d3.event.pageX
/d3.event.pageY
是当前鼠标坐标。
如果你想改变文字可以使用tooltip.text("my tooltip text");
工作示例
<script src="https://d3js.org/d3.v7.min.js"></script>
<body>
<div class="example_div"></div>
</body>
<script type="text/javascript">
var tooltip = d3.select("body")
.append("div")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text("a simple tooltip");
var sampleSVG = d3.select(".example_div")
.append("svg:svg")
.attr("class", "sample")
.attr("width", 300)
.attr("height", 300);
d3.select(".example_div svg")
.append("svg:circle")
.attr("stroke", "black")
.attr("fill", "aliceblue")
.attr("r", 50)
.attr("cx", 52)
.attr("cy", 52)
.on("mouseover", function()return tooltip.style("visibility", "visible");)
.on("mousemove", function()return tooltip.style("top", (event.pageY-10)+"px").style("left",(event.pageX+10)+"px");)
.on("mouseout", function()return tooltip.style("visibility", "hidden"););
</script>
【讨论】:
您可以将数据绑定到此工具提示吗? Afaik 你可以将数据绑定到每个 DOM 元素。 要将数据绑定到此,只需在括号内添加 d ,如下所示:function(d) ... 并将文本更改为您想要的任何内容。例如,假设你有一个名字:tooltip.text(d.name):【参考方案3】:我最近发现了一个很棒的库。它使用简单,结果非常简洁:d3-tip。
你可以看一个例子here:
基本上,你所要做的就是下载(index.js),包含脚本:
<script src="index.js"></script>
然后按照here 的说明进行操作(与示例相同的链接)
但是对于您的代码,它会是这样的:
定义方法:
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d)
return "<strong>Frequency:</strong> <span style='color:red'>" + d.frequency + "</span>";
)
创建你的 svg(就像你已经做的那样)
var svg = ...
调用方法:
svg.call(tip);
为您的对象添加提示:
vis.selectAll("circle")
.data(datafiltered).enter().append("svg:circle")
...
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
别忘了添加 CSS:
<style>
.d3-tip
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
/* Creates a small triangle extender for the tooltip */
.d3-tip:after
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
/* Style northward tooltips differently */
.d3-tip.n:after
margin: -1px 0 0 0;
top: 100%;
left: 0;
</style>
【讨论】:
最新的d3-tip支持d3v4就好了。如果您四处搜索,这并不明显,但使用 d3v4 对我来说效果很好。 很遗憾,.attr() 函数在 D3 v5 中不再有效。【参考方案4】:这个简洁的示例演示了如何在 d3 中创建自定义工具提示的常用方法。
var w = 500;
var h = 150;
var dataset = [5, 10, 15, 20, 25];
// firstly we create div element that we can use as
// tooltip container, it have absolute position and
// visibility: hidden by default
var tooltip = d3.select("body")
.append("div")
.attr('class', 'tooltip');
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
// here we add some circles on the page
var circles = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle");
circles.attr("cx", function(d, i)
return (i * 50) + 25;
)
.attr("cy", h / 2)
.attr("r", function(d)
return d;
)
// we define "mouseover" handler, here we change tooltip
// visibility to "visible" and add appropriate test
.on("mouseover", function(d)
return tooltip.style("visibility", "visible").text('radius = ' + d);
)
// we move tooltip during of "mousemove"
.on("mousemove", function()
return tooltip.style("top", (event.pageY - 30) + "px")
.style("left", event.pageX + "px");
)
// we hide our tooltip on "mouseout"
.on("mouseout", function()
return tooltip.style("visibility", "hidden");
);
.tooltip
position: absolute;
z-index: 10;
visibility: hidden;
background-color: lightblue;
text-align: center;
padding: 4px;
border-radius: 4px;
font-weight: bold;
color: orange;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.min.js"></script>
【讨论】:
如果有人需要工具提示来相对于对象的位置移动。就像在树图的情况下一样。您可能希望在'mousemove'
属性中使用return tooltip.style("top", (d.x + 40) + "px") .style("left", (d.y + 80) + "px");
。 d.x
将帮助工具提示相对于对象移动,而不是整个页面【参考方案5】:
您可以像这样传入要在 mouseover 中使用的数据 - mouseover 事件使用一个函数,将您之前的 enter
ed 数据作为参数(并将索引作为第二个参数),因此您不需要再次使用enter()
。
vis.selectAll("circle")
.data(datafiltered).enter().append("svg:circle")
.attr("cx", function(d) return x(d.x);)
.attr("cy", function(d) return y(d.y))
.attr("fill", "red").attr("r", 15)
.on("mouseover", function(d,i)
d3.select(this).append("text")
.text( d.x)
.attr("x", x(d.x))
.attr("y", y(d.y));
);
【讨论】:
谢谢。我真的需要d3.select(this)
来修改形状并且不知道如何在输入/更新中获取实例。
您正在使用一些未在您的代码中定义的函数 x() 和 y()。我认为可以删除。
它们是在 OP 中给出的【参考方案6】:
你可以在自己动手之前想好自己想要什么,我在这里提供4个例子。
基本上演示 1、2、4 的精神大致相同,demo3 使用的是title 方法。
demo 1, 2, 4:为每个项目添加text(或foreignobject)标签
demo1:纯javascript编写。
demo2:同demo1,改用d3.js
demo4:应用于直方图的示例,并说明为什么我使用这么多文本而不是只使用一个。
注意:
请不要使用DocumentFragment,这不起作用,请改用innerHTML。 标签显示可以使用text或foreignobject(可以写成类似HTML的代码)
demo3:这个很方便,如果要求不高,这可能是最好的方法。 (和Lars Kotthoff answered一样)
示例
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
text.tooltip
display: none;
circle:hover + text.tooltip
display: initial;
circle:hover + foreignobject
display: initial;
color: #ffff00;
background-color: #015db7;
/* ↓ used for demo4Histogram only */
rect:hover + foreignobject
display: initial;
rect:hover
fill: red;
</style>
<body></body>
<script>
const w = 500
const h = 150
const dataset = [5, 10, 15, 20, 25]
function demo1PureJS()
const svgFrag = document.createRange().createContextualFragment(`
<header>PureJS</header>
<svg ><g></g></svg><br>
`)
const gElem = svgFrag.querySelector(`g`)
for (const idx in dataset)
const r = dataset[idx]
const [cx, cy] = [idx * 50 + 25, h / 2];
gElem.insertAdjacentHTML("beforeend", `
<circle cx="$cx" cy="$cy" r="$r" data-tooltip="($cx, $cy)"></circle>
<text class="tooltip" x="$cx" y="$cy" fill="red">$r</text>
`)
document.body.append(svgFrag)
function demo2D3js()
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
svg.node().insertAdjacentHTML("beforebegin", "<header>demo2D3js</header>")
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", (d, i) => i * 50 + 25)
.attr("cy", h / 2)
.attr("r", d => d)
.text((d, idx, arr) =>
const circle = arr[idx]
const x = circle.getAttribute("cx")
const y = circle.getAttribute("cy")
const testCase = "foreignobject"
if (testCase === "foreignobject") // ? focus here
circle.insertAdjacentHTML("afterend", `
<foreignobject x="$x" y="$y" display="none">
<div>$d</div>
</foreignobject>
`)
else
circle.insertAdjacentHTML("afterend", `<text class="tooltip" x="$x" y="$y" fill="yellow">$d</text>`)
return ""
)
function demo3SVGTitle()
/*
https://developer.mozilla.org/en-US/docs/Web/SVG/Element/title
<rect x="11" y="1" >
<title>I'm a square</title>
</rect>
*/
const svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
svg.node().insertAdjacentHTML("beforebegin", "<header>SVGTitle</header>")
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", (d, i) => i * 50 + 25)
.attr("cy", h / 2)
.attr("r", d => d)
.append("svg:title") // ? focus here
.text(d => d)
async function demo4Histogram()
const margin = top: 50, right: 50, bottom: 50, left: 50,
width = 900 - margin.left - margin.right,
height = 900 - margin.top - margin.bottom
const svg = d3.select("body")
.append("svg")
svg.node().insertAdjacentHTML("beforebegin", "<header>Histogram</header>")
const mainG = svg.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate($margin.left, $margin.top)`)
const dataSet = []
await d3.csv("https://raw.githubusercontent.com/holtzy/data_to_viz/master/Example_dataset/1_OneNum.csv", (row) =>
dataSet.push(row)
)
// X: price
const scaleX = d3.scaleLinear()
.domain([0, 2000])
.range([0, width])
mainG.append("g")
.attr("transform", `translate(0,$height)`)
.call(d3.axisBottom(scaleX)
)
const histogram = d3.histogram()
.value(d => d.price)
.domain(scaleX.domain())
.thresholds(scaleX.ticks(50))
const bins = histogram(dataSet)
// Y: Count
const scaleY = d3.scaleLinear()
.domain([0, d3.max(bins, d => d.length)])
.range([height, 0])
mainG.append("g")
.call(d3.axisLeft(scaleY))
mainG.selectAll("rect")
.data(bins)
.enter()
.append("rect")
.attr("transform", d => `translate($scaleX(d.x0),$scaleY(d.length))`)
.attr("x", 1)
.attr("width", d => d3.max([0, scaleX(d.x1) - scaleX(d.x0) - 1]))
.attr("height", d => height - scaleY(d.length))
.attr("fill", "#298e75")
.attr("fill-opacity", 0.4)
.text((d, idx, arr) => // ? focus here
const rect = arr[idx]
const [x, y, width] = [rect.getAttribute("x"), rect.getAttribute("y") ?? 0, rect.getAttribute("width")];
if (width > 0)
const msg = `$d.x0~$d.x1: $d.length`
rect.insertAdjacentHTML("afterend", `
<foreignobject x="$x" y="$y" height=26 display="none" class="tooltip"
transform="translate($scaleX(d.x0),$scaleY(d.length))">
<div>$msg</div>
</foreignobject>
`)
return ""
)
/**
You can certainly consider creating just one element and moving it around to achieve the display effect. [see https://***.com/a/47002479/9935654]
On my side, I made a corresponding element individually, which seems to generate a lot of duplicate items, but it can be done as follows:
If you are interested in a specific marker, you can click on it, and it will display the message forever(cancel again to hidden)
* */
document.querySelectorAll(`foreignObject.tooltip`).forEach(div => // ? focus here
div.addEventListener("click", () =>
div.setAttribute("display", div.getAttribute("display") === "none" ? "" : "none")
)
)
demo1PureJS()
demo2D3js()
demo3SVGTitle()
demo4Histogram()
</script>
demo4:因为每个元素都有一个标签,所以可以同时显示多个标签,这是一个元素无法做到的。
d3.js 版本:v7
【讨论】:
以上是关于在鼠标悬停时显示数据的主要内容,如果未能解决你的问题,请参考以下文章