如何在D3.js中为多线图添加Tooltip并修改线条颜色?
解决D3.js多线图的线条颜色和Tooltip问题
我来帮你搞定这两个需求——给每条线条设置不同颜色,以及添加鼠标悬停显示的Tooltip。
一、修改每条线条的颜色
你现在的代码里所有线条都用了同一个.line类,所以颜色都是默认的steelblue。这里有两种简单的区分方式:
方式1:通过CSS类定义不同颜色
先修改你的CSS样式,为每个线条系列单独定义类:
/* 基础线条样式 */ .line { fill: none; stroke-width: 2px; } /* Imports线条颜色:橙色 */ .line-imports { stroke: #ff7f0e; } /* Exports线条颜色:蓝色 */ .line-exports { stroke: #1f77b4; } /* Added线条颜色:绿色 */ .line-added { stroke: #2ca02c; }
然后在添加线条路径时,给每条线指定对应的类:
// 添加Imports线条 svg.append("path") .data([data]) .attr("class", "line line-imports") .attr("d", valueline); // 添加Exports线条 svg.append("path") .data([data]) .attr("class", "line line-exports") .attr("d", valueline2); // 添加Added线条 svg.append("path") .data([data]) .attr("class", "line line-added") .attr("d", valueline3);
方式2:直接在代码中设置stroke属性
如果不想修改CSS,也可以在创建路径时直接指定颜色:
// 添加Imports线条 svg.append("path") .data([data]) .attr("class", "line") .attr("stroke", "#ff7f0e") .attr("d", valueline); // 添加Exports线条 svg.append("path") .data([data]) .attr("class", "line") .attr("stroke", "#1f77b4") .attr("d", valueline2); // 添加Added线条 svg.append("path") .data([data]) .attr("class", "line") .attr("stroke", "#2ca02c") .attr("d", valueline3);
二、添加鼠标悬停的Tooltip
要实现Tooltip,我们需要先创建一个隐藏的容器,然后为每个数据点(圆形)绑定鼠标事件来控制Tooltip的显示和内容。
步骤1:创建Tooltip容器
在初始化SVG之后,添加这段代码创建Tooltip:
// 创建Tooltip容器 var tooltip = d3.select("body").append("div") .attr("class", "tooltip") .style("opacity", 0) // 初始隐藏 .style("position", "absolute") .style("background-color", "#fff") .style("border", "1px solid #ddd") .style("padding", "8px 12px") .style("border-radius", "4px") .style("pointer-events", "none") // 避免干扰鼠标交互 .style("font-size", "14px");
步骤2:添加数据点并绑定Tooltip事件
在添加完线条之后,为每个数据系列添加圆形数据点,并绑定鼠标事件:
// 添加Imports的数据点 svg.selectAll(".point-imports") .data(data) .enter() .append("circle") .attr("class", "point-imports") .attr("cx", d => x(d.Date)) .attr("cy", d => y(d.Imports)) .attr("r", 4) .style("fill", "#ff7f0e") // 和线条颜色一致 .style("cursor", "pointer") .on("mouseover", function(d) { // 显示Tooltip并填充内容 tooltip.transition().duration(200).style("opacity", 0.9); tooltip.html(` <strong>年份:</strong> ${d.Date.getFullYear()}<br> <strong>Imports:</strong> ${d.Imports} `) .style("left", (d3.event.pageX + 10) + "px") .style("top", (d3.event.pageY - 30) + "px"); }) .on("mouseout", function() { // 隐藏Tooltip tooltip.transition().duration(300).style("opacity", 0); }); // 添加Exports的数据点 svg.selectAll(".point-exports") .data(data) .enter() .append("circle") .attr("class", "point-exports") .attr("cx", d => x(d.Date)) .attr("cy", d => y(d.Exports)) .attr("r", 4) .style("fill", "#1f77b4") .style("cursor", "pointer") .on("mouseover", function(d) { tooltip.transition().duration(200).style("opacity", 0.9); tooltip.html(` <strong>年份:</strong> ${d.Date.getFullYear()}<br> <strong>Exports:</strong> ${d.Exports} `) .style("left", (d3.event.pageX + 10) + "px") .style("top", (d3.event.pageY - 30) + "px"); }) .on("mouseout", function() { tooltip.transition().duration(300).style("opacity", 0); }); // 添加Added的数据点 svg.selectAll(".point-added") .data(data) .enter() .append("circle") .attr("class", "point-added") .attr("cx", d => x(d.Date)) .attr("cy", d => y(d.added)) .attr("r", 4) .style("fill", "#2ca02c") .style("cursor", "pointer") .on("mouseover", function(d) { tooltip.transition().duration(200).style("opacity", 0.9); tooltip.html(` <strong>年份:</strong> ${d.Date.getFullYear()}<br> <strong>Added:</strong> ${d.added} `) .style("left", (d3.event.pageX + 10) + "px") .style("top", (d3.event.pageY - 30) + "px"); }) .on("mouseout", function() { tooltip.transition().duration(300).style("opacity", 0); });
完整修改后的代码
把以上修改整合到你的代码中,最终代码如下:
<!DOCTYPE html> <meta charset="utf-8"> <style> /* set the CSS */ .line { fill: none; stroke-width: 2px; } .line-imports { stroke: #ff7f0e; } .line-exports { stroke: #1f77b4; } .line-added { stroke: #2ca02c; } </style> <body> <!-- load the d3.js library --> <script src="https://d3js.org/d3.v4.min.js"></script> <script> // set the dimensions and margins of the graph var margin = {top: 20, right: 20, bottom: 30, left: 50}, width = 960 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; // parse the date / time var parseTime = d3.timeParse("%Y"); // set the ranges var x = d3.scaleTime().range([0, width]); var y = d3.scaleLinear().range([height, 0]); // define the line var valueline = d3.line() .x(function(d) { return x(d.Date); }) .y(function(d) { return y(d.Imports); }); // define the line var valueline2 = d3.line() .x(function(d) { return x(d.Date); }) .y(function(d) { return y(d.Exports); }); var valueline3 = d3.line() .x(function(d) { return x(d.Date); }) .y(function(d) { return y(d.added); }); var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // 创建Tooltip容器 var tooltip = d3.select("body").append("div") .attr("class", "tooltip") .style("opacity", 0) .style("position", "absolute") .style("background-color", "#fff") .style("border", "1px solid #ddd") .style("padding", "8px 12px") .style("border-radius", "4px") .style("pointer-events", "none") .style("font-size", "14px"); function draw(data, country) { var data = data[country]; // format the data data.forEach(function(d) { d.Date = parseTime(d.Date); d.Imports = +d.Imports; d.Exports = +d.Exports; d.added = +d.added; }); // sort years ascending data.sort(function(a, b){ return a["Date"]-b["Date"]; }) // Scale the range of the data x.domain(d3.extent(data, function(d) { return d.Date; })); y.domain([0, d3.max(data, function(d) { return Math.max(d.Imports, d.Exports,d.added); })]); // Add the valueline path. svg.append("path") .data([data]) .attr("class", "line line-imports") .attr("d", valueline); // Add the valueline path. svg.append("path") .data([data]) .attr("class", "line line-exports") .attr("d", valueline2); svg.append("path") .data([data]) .attr("class", "line line-added") .attr("d", valueline3); // 添加Imports的数据点 svg.selectAll(".point-imports") .data(data) .enter() .append("circle") .attr("class", "point-imports") .attr("cx", d => x(d.Date)) .attr("cy", d => y(d.Imports)) .attr("r", 4) .style("fill", "#ff7f0e") .style("cursor", "pointer") .on("mouseover", function(d) { tooltip.transition().duration(200).style("opacity", 0.9); tooltip.html(` <strong>年份:</strong> ${d.Date.getFullYear()}<br> <strong>Imports:</strong> ${d.Imports} `) .style("left", (d3.event.pageX + 10) + "px") .style("top", (d3.event.pageY - 30) + "px"); }) .on("mouseout", function() { tooltip.transition().duration(300).style("opacity", 0); }); // 添加Exports的数据点 svg.selectAll(".point-exports") .data(data) .enter() .append("circle") .attr("class", "point-exports") .attr("cx", d => x(d.Date)) .attr("cy", d => y(d.Exports)) .attr("r", 4) .style("fill", "#1f77b4") .style("cursor", "pointer") .on("mouseover", function(d) { tooltip.transition().duration(200).style("opacity", 0.9); tooltip.html(` <strong>年份:</strong> ${d.Date.getFullYear()}<br> <strong>Exports:</strong> ${d.Exports} `) .style("left", (d3.event.pageX + 10) + "px") .style("top", (d3.event.pageY - 30) + "px"); }) .on("mouseout", function() { tooltip.transition().duration(300).style("opacity", 0); }); // 添加Added的数据点 svg.selectAll(".point-added") .data(data) .enter() .append("circle") .attr("class", "point-added") .attr("cx", d => x(d.Date)) .attr("cy", d => y(d.added)) .attr("r", 4) .style("fill", "#2ca02c") .style("cursor", "pointer") .on("mouseover", function(d) { tooltip.transition().duration(200).style("opacity", 0.9); tooltip.html(` <strong>年份:</strong> ${d.Date.getFullYear()}<br> <strong>Added:</strong> ${d.added} `) .style("left", (d3.event.pageX + 10) + "px") .style("top", (d3.event.pageY - 30) + "px"); }) .on("mouseout", function() { tooltip.transition().duration(300).style("opacity", 0); }); // Add the X Axis svg.append("g") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(x)); // Add the Y Axis svg.append("g") .call(d3.axisLeft(y)); } // Get the data d3.json("data1.json", function(error, data) { if (error) throw error; // trigger render draw(data, "purchase"); }); </script> </body>
内容的提问来源于stack exchange,提问作者White Rose
相关产品推荐
相关产品推荐

