我有一个基于此http://bl.ocks.org/d3noob/e99a762017060ce81c76的多线D3.js图表。
我的图表是每日累积量,但是x轴上的刻度是每季度一次。我想在此图表上的每一行以及在数据给出的每个日期(不是Q的日期)上添加工具提示。正如您在代码中看到的那样,我已经尝试过类似SO问题的一些建议,但是似乎没有任何效果。最好的一条是垂直线,显示每行上都有鼠标悬停值的圆圈。我的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Statistics</title>
<style>
.row {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
flex-wrap: wrap
}
.row>[class*=col-] {
display: flex;
flex-direction: column
}
.d3-tip:after,
.mapael .zoomButton {
position: absolute;
text-align: center
}
path {
stroke: #fff
}
path:hover {
opacity: .9
}
rect:hover {
fill: #00f
}
.axis {
font: 10px sans-serif
}
.legend tr {
border-bottom: 1px solid grey
}
.legend tr:first-child {
border-top: 1px solid grey
}
.axis line,
.axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges
}
.d3-tip {
line-height: 1;
font-weight: 700;
background: rgba(0, 0, 0, .8);
color: #fff;
border-radius: 2px
}
.d3-tip:after {
box-sizing: border-box;
display: inline;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, .8);
content: "\25BC"
}
.d3-tip.n:after {
margin: -1px 0 0;
top: 100%;
left: 0
}
.chart .legend {
fill: #000;
font: 90% sans-serif;
text-anchor: start;
font-size: 60%
}
.chart text {
fill: #fff;
font: 10px sans-serif;
text-anchor: end
}
.chart .label {
fill: #000;
font: 90% sans-serif;
text-anchor: end
}
rect.background {
fill: none;
pointer-events: all
}
.chart-wrapper {
max-width: 1100px;
min-width: 304px;
margin: 0 auto;
background-color: #FAF7F7
}
.chart-wrapper .inner-wrapper {
position: relative;
padding-bottom: 20%;
width: 100%;
height: 80%
}
#info,
.chart-wrapper .outer-box,
.ol-popup,
.ol-popup-closer,
.toolTip {
position: absolute
}
.chart-wrapper .outer-box {
top: 0;
bottom: 0;
left: 0;
right: 0;
height: 100%
}
.chart-wrapper .inner-box {
width: 100%;
height: 100%
}
.chart-wrapper text {
font-family: sans-serif;
/*font-size: 90%*/
}
.chart-wrapper p {
/*font-size: 100%;*/
margin-top: 5px;
margin-bottom: 40px
}
.chart-wrapper .axis line,
.chart-wrapper .axis path {
fill: none;
stroke: #1F1F2E;
stroke-opacity: .7;
shape-rendering: crispEdges
}
.chart-wrapper .axis path {
stroke-width: 2px
}
.chart-wrapper .line {
fill: none;
stroke: #4682b4;
stroke-width: 5px
}
.chart-wrapper .legend {
min-width: 100px;
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
/*font-size: 100%*/
}
.chart-wrapper .legend>div {
margin: 0 25px 10px 0;
flex-grow: 0
}
.chart-wrapper .legend p {
display: inline;
/*font-size: 80%;*/
font-family: sans-serif;
font-weight: 600
}
.chart-wrapper .legend .series-marker {
height: 1em;
width: 1em;
border-radius: 35%;
background-color: #dc143c;
display: inline-block;
margin-right: 4px;
margin-bottom: -.16rem
}
.toolTip,
.x.axis path {
display: none
}
.chart-wrapper .overlay {
fill: none;
pointer-events: all
}
.chart-wrapper .focus circle {
fill: #dc143c;
stroke: #dc143c;
stroke-width: 2px;
fill-opacity: 15%
}
.chart-wrapper .focus rect {
fill: #add8e6;
opacity: .4;
border-radius: 2px
}
.chart-wrapper .focus.line {
stroke: #4682b4;
stroke-dasharray: 2, 5;
stroke-width: 2;
opacity: .5
}
.toolTip {
width: auto;
height: auto;
background: #fff;
border: 0;
border-radius: 8px;
box-shadow: -3px 3px 15px #888;
color: #000;
font: 12px sans-serif;
text-align: center
}
.tooltip.in {
opacity: 1;
filter: alpha(opacity=100)
}
.tooltip.top .tooltip-arrow {
border-top-color: #fff
}
.tooltip-inner {
border: 2px solid #fff
}
.chart rect:first-of-type {
color: #fff;
stroke: #3994b6;
fill: #fff
}
.chart rect:nth-of-type(2) {
color: #fff;
fill: #3994b6
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.7/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.6.3/d3-tip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reqwest/2.0.5/reqwest.min.js"></script>
<script type="text/javascript">
Number.prototype.formatMoney = function(c, d, t){
var n = this,
c = isNaN(c = Math.abs(c)) ? 2 : c,
d = d == undefined ? "," : d,
t = t == undefined ? "." : t,
s = n < 0 ? "-" : "",
i = String(parseInt(n = Math.abs(Number(n) || 0).toFixed(c))),
j = (j = i.length) > 3 ? j % 3 : 0;
return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
};
function responsivefy(svg) {
// get container + svg aspect ratio
var container = d3.select(svg.node().parentNode),
width = parseInt(svg.style("width")),
height = parseInt(svg.style("height")),
aspect = width / height;
// add viewBox and preserveAspectRatio properties,
// and call resize so that svg resizes on inital page load
svg.attr("viewBox", "0 0 " + width + " " + height)
.attr("perserveAspectRatio", "xMinYMid")
.call(resize);
// to register multiple listeners for same event type,
// you need to add namespace, i.e., 'click.foo'
// necessary if you call invoke this function for multiple svgs
// api docs: https://github.com/mbostock/d3/wiki/Selections#on
d3.select(window).on("resize." + container.attr("id"), resize);
// get width of container and resize svg to fit it
function resize() {
var targetWidth = parseInt(container.style("width"));
svg.attr("width", targetWidth);
svg.attr("height", Math.round(targetWidth / aspect));
}
}
</script>
</head>
<body id="page-top">
<!-- Navigation-->
<div class="content-wrapper">
<div class="container-fluid">
<div class="card mb-3">
<div class="card-body">
<div class="box" style="border: 5px #cbcbcb double">
<div class="chart-wrapper col-12 d-block" id="chart-line1" style="height:50%;">
<script type="text/javascript">
// Set the dimensions of the canvas / graph
var marginlines = {top: 40, right: 60, bottom: 70, left: 80},
widthlines = 1100 - marginlines.left - marginlines.right,
heightlines = 400 - marginlines.top - marginlines.bottom;
// Parse the date / time
var parseDate = d3.time.format("%Y-%m-%d").parse;
// Set the ranges
var xlines = d3.time.scale().range([0, widthlines]);
var ylines = d3.scale.linear().range([heightlines, 0]);
// Define the axes
var xAxislines = d3.svg.axis().scale(xlines)
.ticks( d3.time.months, 3 )
.tickFormat( function ( x ) {
// get the milliseconds since Epoch for the date
var milli = (x.getTime() - 10000);
// calculate new date 10 seconds earlier. Could be one second,
// but I like a little buffer for my neuroses
var vanilli = new Date(milli);
// calculate the month (0-11) based on the new date
var mon = vanilli.getMonth();
var yr = vanilli.getFullYear();
// return appropriate quarter for that month
if ( mon <= 2 ) {return "Q1 " + yr;
} else if ( mon <= 5 ) {return "Q2 " + yr;
} else if ( mon <= 8 ) {return "Q3 " + yr;
} else {return "Q4 " + yr;
}
})
.orient( "bottom" );
var yAxislines = d3.svg.axis().scale(ylines)
.orient("left").ticks(5);
// Define the line
var priceline = d3.svg.line()
.x(function(d) { return xlines(d.date); })
.y(function(d) { return ylines(d.value); });
// Adds the svg canvas
var svglines = d3.select("#chart-line1")
.append("svg")
.attr("width", widthlines + marginlines.left + marginlines.right)
.attr("height", heightlines + marginlines.top + marginlines.bottom)
.call(responsivefy)
.append("g")
.attr("transform", "translate(" + marginlines.left + "," + marginlines.top + ")");
// Get the data
d3.json("http://88.99.13.199:3000/poreiapaa", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +Number(d.value);
});
// Scale the range of the data
xlines.domain(d3.extent(data, function(d) { return d.date; }));
ylines.domain([0, d3.max(data, function(d) {return d.value; })]);
// Nest the entries by symbol
var dataNest = d3.nest()
.key(function(d) {return d.variable;})
.entries(data);
var colorlines = d3.scale.category10(); // set the colour scale
legendSpace = widthlines/dataNest.length; // spacing for legend
// Loop through each symbol / key
let tipBox;
dataNest.forEach(function(d,i) {
svglines.append("path")
.attr("class", "line")
.style("stroke", function() { // Add the colours dynamically
return d.color = colorlines(d.key); })
.attr("d", priceline(d.values));
// Add the Legend
svglines.append("text")
.attr("x", (legendSpace/2)+i*legendSpace) // spacing
.attr("y", heightlines + (marginlines.bottom/2)+ 5)
.attr("class", "legend") // style the legend
.style("fill", function() { // dynamic colours
return d.color = colorlines(d.key); })
.text('Test');
});
// Add the X Axis
svglines.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + heightlines + ")")
.call(xAxislines);
// Add the Y Axis
svglines.append("g")
.attr("class", "y axis")
.call(yAxislines);
var mouseG = svg.append("g")
.attr("class", "mouse-over-effects");
mouseG.append("path") // this is the black vertical line to follow mouse
.attr("class", "mouse-line")
.style("stroke", "black")
.style("stroke-width", "1px")
.style("opacity", "0");
var lines = document.getElementsByClassName('line');
var mousePerLine = mouseG.selectAll('.mouse-per-line')
.data(dataNest)
.enter()
.append("g")
.attr("class", "mouse-per-line");
mousePerLine.append("circle")
.attr("r", 7)
.style("stroke", function(d) {
return color(d.name);
})
.style("fill", "none")
.style("stroke-width", "1px")
.style("opacity", "0");
mousePerLine.append("text")
.attr("transform", "translate(10,3)");
mouseG.append('svg:rect') // append a rect to catch mouse movements on canvas
.attr('width', widthlines) // can't catch mouse events on a g element
.attr('height', heightlines)
.attr('fill', 'none')
.attr('pointer-events', 'all')
.on('mouseout', function() { // on mouse out hide line, circles and text
d3.select(".mouse-line")
.style("opacity", "0");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "0");
d3.selectAll(".mouse-per-line text")
.style("opacity", "0");
})
.on('mouseover', function() { // on mouse in show line, circles and text
d3.select(".mouse-line")
.style("opacity", "1");
d3.selectAll(".mouse-per-line circle")
.style("opacity", "1");
d3.selectAll(".mouse-per-line text")
.style("opacity", "1");
})
.on('mousemove', function() { // mouse moving over canvas
var mouse = d3.mouse(this);
d3.select(".mouse-line")
.attr("d", function() {
var d = "M" + mouse[0] + "," + heightlines;
d += " " + mouse[0] + "," + 0;
return d;
});
d3.selectAll(".mouse-per-line")
.attr("transform", function(d, i) {
console.log(width/mouse[0])
var xDate = x.invert(mouse[0]),
bisect = d3.bisector(function(d) { return d.date; }).right;
idx = bisect(d.values, xDate);
var beginning = 0,
end = lines[i].getTotalLength(),
target = null;
while (true){
target = Math.floor((beginning + end) / 2);
pos = lines[i].getPointAtLength(target);
if ((target === end || target === beginning) && pos.x !== mouse[0]) {
break;
}
if (pos.x > mouse[0]) end = target;
else if (pos.x < mouse[0]) beginning = target;
else break; //position found
}
d3.select(this).select('text')
.text(y.invert(pos.y).toFixed(2));
return "translate(" + mouse[0] + "," + pos.y +")";
});
});
});
</script>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
我将非常感谢任何帮助。