Javascript D3.js中的鼠标悬停问题,包含路径元素,并在焦点刷过后更改工具提示数据?
我在一个多系列、焦点+背景D3图表中遇到了以下问题,我想我会尝试一次性得到我主要问题的答案 我的问题如下:Javascript D3.js中的鼠标悬停问题,包含路径元素,并在焦点刷过后更改工具提示数据?,javascript,jquery,css,svg,d3.js,Javascript,Jquery,Css,Svg,D3.js,我在一个多系列、焦点+背景D3图表中遇到了以下问题,我想我会尝试一次性得到我主要问题的答案 我的问题如下: 如何确保我创建的工具提示不受图表的发际线(位于鼠标之后)的影响,即不消失,而是一直持续到移动鼠标为止?这就是如果发际线不可见或已创建,它们的行为方式 出于某种原因(可能很明显,但对我来说并不明显),数据的路径并不局限于焦点区域,而是溢出到图表的左侧。如何解决这个问题?我意识到这可能与左边距有关 最后,当数据被刷新时,焦点图表会通过上下文进行更新,但我很难更新工具提示数据。有人能看到现有代码
body {
font: 10px sans-serif;
}
.axis path,
.axis path_steelblue {
fill: none;
stroke: #000;
shape-rendering: geometricPrecision;
}
.path_steelblue {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}
.path_blue {
fill: none;
stroke: #3333FF;
stroke-width: 1.5px;
}
.path_red {
fill: none;
stroke:red;
stroke-width: 1.5px;
}
.path_yellow {
fill: none;
stroke:yellow;
stroke-width: 1.5px;
}
.brush .extent {
stroke: #FFF;
fill-opacity: .125;
shape-rendering: crispEdges;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
shape-rendering: crispEdges;
}
.grid path {
stroke-width: 0;
shape-rendering: crispEdges;
}
.dot {
fill: white;
stroke: white;
opacity:0.01;
stroke-width: 0px;
cursor:crosshair;
}
div.tooltip {
position: absolute;
text-align: left;
width: 120px;
height: 48px;
padding: 10px;
font: 12px sans-serif;
background: steelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
div.tooltipSolvency {
background: #3333FF;
}
div.tooltipTechnical {
background: red;
}
div.tooltipAccounting {
background: yellow;
}
.hover-line {
stroke: #000;
fill: none;
stroke-width: 1px;
left: 10px;
shape-rendering: crispEdges;
}
.hover-text {
stroke: none;
font-size: 12px;
font-weight: bold;
fill: #000000;
}
D3代码:
var margin = { top: 10, right: 10, bottom: 100, left: 40 },
margin2 = { top: 430, right: 10, bottom: 20, left: 40 },
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
height2 = 500 - margin2.top - margin2.bottom;
var parseDate = d3.time.format("%Y-%m-%dT%H:%M:%S").parse;
var formatTime = d3.time.format("%d/%m/%Y");
var x = d3.time.scale().range([0, width]),
x2 = d3.time.scale().range([0, width]),
y = d3.scale.linear().range([height, 0]),
y2 = d3.scale.linear().range([height2, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom"),
xAxis2 = d3.svg.axis().scale(x2).orient("bottom"),
yAxis = d3.svg.axis().scale(y).orient("left");
var brush = d3.svg.brush()
.x(x2)
.on("brush", brushed);
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.chartValue); });
var line2 = d3.svg.line()
.x(function(d) { return x2(d.date); })
.y(function(d) { return y2(d.chartValue); });
var svg = d3.select(".tracker-chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
//.style("margin-left", "40px")
//.style("margin-top", "40px");
//var container = svg.append("g")
// .attr("width", width + margin.left + margin.right)
// .attr("height", height + margin.top + margin.bottom);
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Hover line.
var hoverLineGroup = svg.append("g") //svg.append("g") -- Ensures hairline follows mouse pointer put interferes with the tooltip not allowing it to persist until the mouse moves again
.attr("class", "hover-line");
var hoverLine = hoverLineGroup
.append("line")
.attr("x1", 10).attr("x2", 10)
.attr("y1", 0).attr("y2", height + 10);
var hoverDate = hoverLineGroup.append('text')
.attr("class", "hover-text")
.attr('y', height - (height-10));
// Hide hover line by default.
hoverLine.style("opacity", 1e-6);
var div = d3.select(".tracker-chart").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var divSolvency = d3.select(".tracker-chart").append("div")
.attr("class", "tooltip tooltipSolvency")
.style("opacity", 0);
var divTechnical = d3.select(".tracker-chart").append("div")
.attr("class", "tooltip tooltipTechnical")
.style("opacity", 0);
var divAccounting = d3.select(".tracker-chart").append("div")
.attr("class", "tooltip tooltipAccounting")
.style("opacity", 0);
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
d3.xml("data.xml", "application/xml", function(error, data) {
var assetResultsData = d3.select(data).select("AssetResults").selectAll("Result");
assetResultsData[0].forEach(crunch);
var liabilityResultsDataSol = d3.select(data).selectAll("LiabilityResults Liability[name='Solvency'] Results Item");
liabilityResultsDataSol[0].forEach(crunch);
var liabilityResultsDataTP = d3.select(data).selectAll("LiabilityResults Liability[name='Technical provisions'] Results Item");
liabilityResultsDataTP[0].forEach(crunch);
var liabilityResultsDataTV = d3.select(data).selectAll("LiabilityResults Liability[name='Accounting'] Results Item");
liabilityResultsDataTV[0].forEach(crunch);
x.domain(d3.extent(assetResultsData[0], function (d) { return d.date; }));
y.domain([450, 600]); //Hard coded for demo purposes
//y.domain(d3.extent(assetResultsData[0], function (d) { return d.chartValue; }));
x2.domain(x.domain());
y2.domain(y.domain());
focus.append("path")
.datum(assetResultsData[0])
.attr("class", "path_steelblue")
.attr("d", line);
focus.append("path")
.datum(liabilityResultsDataSol[0])
.attr("class", "path_blue")
.attr("d", line);
focus.append("path")
.datum(liabilityResultsDataTV[0])
.attr("class", "path_red")
.attr("d", line);
focus.append("path")
.datum(liabilityResultsDataTP[0])
.attr("class", "path_yellow")
.attr("d", line);
focus.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
focus.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Millions ($)");
focus.append("g")
.attr("class", "grid")
.call(make_y_axis()
.tickSize(-width, 0, 0)
.tickFormat("")
);
context.append("path")
.datum(assetResultsData[0])
.attr("class", "path_steelblue")
.attr("d", line2);
context.append("path")
.datum(liabilityResultsDataSol[0])
.attr("class", "path_blue")
.attr("d", line2);
context.append("path")
.datum(liabilityResultsDataTV[0])
.attr("class", "path_yellow")
.attr("d", line2);
context.append("path")
.datum(liabilityResultsDataTP[0])
.attr("class", "path_red")
.attr("d", line2);
context.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", -6)
.attr("height", height2 + 7);
addTooltip(div, assetResultsData[0], "Assets");
addTooltip(divSolvency, liabilityResultsDataSol[0], "Solvency");
addTooltip(divTechnical, liabilityResultsDataTV[0], "Technical Provisions");
addTooltip(divAccounting, liabilityResultsDataTP[0], "Accounting");
});
// Add mouseover events for hover line.
d3.select(".tracker-chart").on("mouseover", function() {
}).on("mousemove", function() {
//console.log('mousemove', d3.mouse(this));
var mouse_x = d3.mouse(this)[0];
var mouse_y = d3.mouse(this)[1];
var graph_y = y.invert(mouse_y);
var graph_x = x.invert(mouse_x);
//console.log(graph_x);
var format = d3.time.format('%a %b %d %Y');
hoverDate.text(format(graph_x));
hoverDate.attr('x', mouse_x);
//console.log(x.invert(mouse_x));
hoverLine.attr("x1", mouse_x).attr("x2", mouse_x)
hoverLine.style("opacity", 1);
}).on("mouseout", function() {
//hoverLine.style("opacity", 1e-6);
});
function addTooltip(div, data, label) {
focus.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 5)
.attr("cx", function (d) { return x(d.date); })
.attr("cy", function (d) { return y(d.chartValue); })
.on("mouseover", function(d) {
div.transition()
.duration(50)
.style("opacity", .9);
div.html(label + "<br />" + formatTime(d.date) + "<br />" + "$" + (d.chartValue).toFixed(3) + " Million")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}).on("mouseout", function(d) {
div.transition()
.duration(200)
.style("opacity", 0);
});
/*
focus.on('mouseover', function(){
brush_elm = focus.select("circle").node();
console.log(brush_elm);
console.log(this);
new_click_event = new Event('mouseover');
new_click_event.pageX = d3.event.pageX;
new_click_event.clientX = d3.event.clientX;
new_click_event.pageY = d3.event.pageY;
new_click_event.clientY = d3.event.clientY;
brush_elm.dispatchEvent(new_click_event);
});*/
}
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.select(".path_steelblue").attr("d", line);
focus.select(".path_blue").attr("d", line);
focus.select(".path_yellow").attr("d", line);
focus.select(".path_red").attr("d", line);
focus.select(".x.axis").call(xAxis);
}
function make_y_axis() {
return d3.svg.axis()
.scale(y)
.orient("left")
.ticks(5)
}
function crunch(d) {
d.date = parseDate(d.getAttribute("date"));
d.chartValue = +d.getAttribute("value")/ 1000000;
d.hoverText = +d.getAttribute("value");
}
clipPath
元素,但从未将其附加到组brushed
函数中没有任何与工具提示相关的内容。至少,您应该选择任何现有工具提示,并根据更新的x比例函数重新定位它们 function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.selectAll("circle.dot")
//or you could just use ".dot"
//but NOT "dot", which you have in your initialization code!
.attr("cx", function (d) { return x(d.date); } );
focus.select(".path_steelblue").attr("d", line);
focus.select(".path_blue").attr("d", line);
focus.select(".path_yellow").attr("d", line);
focus.select(".path_red").attr("d", line);
focus.select(".x.axis").call(xAxis);
}
顺便说一句,如果您为行提供两个类,一个是所有行的公共类,另一个是每个数据类型的唯一类,那么效率会更高。只需用空格分隔类:
context.append("path")
.datum(assetResultsData[0])
.attr("class", "datapath steelblue")
.attr("d", line);
然后在CSS中,应用于所有路径的任何属性的选择器将是
path.datapath {
}
或
如果只希望样式应用于主图表中的线条(例如,如果希望主图表中的笔划宽度比小上下文图表中的笔划宽度更宽)
当然,您仍然可以使用这两个类选择单独的行:
path.datapath.steelblue {
stroke: steelblue;
}
由于d3选择功能使用相同的选择器,在拉丝功能中,您可以使用公共选择器一次性选择所有数据行并更新它们
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.selectAll("circle.dot")
.attr("cx", function(d) { return x(d.date); } );
focus.selectAll("path.datapath").attr("d", line);
focus.select("g.x.axis").call(xAxis);
}
行
是一个函数,它根据每个元素的数据以及创建行函数时指定的x和y比例创建路径信息。感谢您的回复。你对问题(1)的回答正是我想要的。我想我知道如何处理问题(2),所以我现在正在研究这个问题。关于问题(3),我使用的是XML数据,其中有很多数据(虽然我可以减少),但我在发布时遇到了困难。因此,很难给出一个有效的例子。我添加了一张图表的图像。重新定位现有的工具提示(参见代码)是我正在尝试做的,只是不确定如何做。谢谢@AmeliaBR工具提示问题现在已经解决,我已经使用了您关于CSS的建议。在“刷牙”时仍然难以包含路径。这些路径似乎包含在焦点小组中,这就是我认为剪辑发生的地方(显然不是)。这也导致了悬停线日期的另一个问题,当添加到svg
元素时,“刷”之后的悬停线日期稍微不同步。(请参见新编辑)如果将日期附加到focus
元素,则日期是同步的,但发际线未与鼠标指针对齐。对于剪切路径,您需要将属性添加到focus
元素:focus.attr(“剪切路径”,“url(#剪辑)”)
要定位鼠标跟踪线,需要相对于SVG的鼠标坐标,SVG是所定位元素的父元素:var mouse_x=d3.mouse(SVG.node())[0]代码>。然后,要从反转的比例中获得正确的值,需要鼠标坐标相对于包含图形的焦点
元素:var graph\u x=x.invert(d3.mouse(focus.node())代码>。再次感谢@AmeliaBR。我需要使用focus.node()来跟踪发际线鼠标。对于剪辑,我使用了剪辑路径
,而不是剪辑路径
+回答的3个问题中有3个。谢谢
path.datapath.steelblue {
stroke: steelblue;
}
function brushed() {
x.domain(brush.empty() ? x2.domain() : brush.extent());
focus.selectAll("circle.dot")
.attr("cx", function(d) { return x(d.date); } );
focus.selectAll("path.datapath").attr("d", line);
focus.select("g.x.axis").call(xAxis);
}