在D3树形图Tooltip中添加含SVG折线图的HTML表格
问题:D3 Tooltip表格内嵌折线图无法显示
我正在开发一个基于D3 treemap的可视化Web应用,当用户悬停树形图节点时,Tooltip会以表格形式展示多行数据(比如5×4结构)。目前普通文本列能正常显示,但每行指定列内的折线图无法渲染出来,当前使用的代码如下:
// Tooltip leaf.on("mousemove", function (d) { if (d.depth < 3) return; var name1 = data.name; var name2 = d.parent.data.name; var name3 = d.data.name; var category = d.data.category; var change = d.data.change; var filtered = items.filter(function (item) { return item.level2 == d.parent.data.name }); var tooltip_template = '\ <h3 class="tooltip-header"> ' + name2 + ' / ' + name3 + '</h3> \ <div class="tooltip-body"> \ <table> \ '; filtered.forEach(function (item) { tooltip_template += item.name == d.data.name ? '<tr style="color: #fff; background: ' + getColor(d.data.change) + '"> \ <td> ' + item.name + '</td> \ <td> \ <svg></svg> \ </td> \ <td class="txtright"> ' + item.value + ' 건</td> \ <td class="txtright"> ' + (item.change > 0 ? '+' : '') + item.change + ' 건</td> \ </tr>' : '<tr> \ <td> ' + item.name + '</td> \ <td> \ <svg></svg> \ </td> \ <td class="txtright"> ' + item.value + ' 건</td> \ <td class="txtright"> ' + (item.change > 0 ? '+' : '') + item.change + ' 건</td> \ </tr>'; // set the dimensions and margins of the line graph var linemargin = { top: d3.event.pageY + 15, right: d3.event.pageX + 5, bottom: d3.event.pageY + 15, left: d3.event.pageX + 5 }, linewidth = 100 - linemargin.left - linemargin.right, lineheight = 20 - linemargin.top - linemargin.bottom; // Set the data for the chart const linedata = [ { x: 0, y: 5 }, { x: 1, y: 9 }, { x: 2, y: 7 }, { x: 3, y: 5 }, { x: 4, y: 3 }, { x: 5, y: 4 }, { x: 6, y: 8 }, { x: 7, y: 6 }, { x: 8, y: 3 }, { x: 9, y: 2 }, ]; const linechart = tooltip.selectAll("svg").append("g").enter().attr("class", "linechart").attr("transform", `translate(${linemargin.left}, ${linemargin.top})`); const x = d3.scaleLinear().domain([0, 9]).range([0, linewidth]); const y = d3.scaleLinear().domain([0, 10]).range([lineheight, 0]); // Create the line generator const line = d3 .line() .x((d) => x(d.x)) .y((d) => y(d.y)); // Add the line to the chart linechart.append("path").datum(linedata).attr("class", "line").attr("d", line); }) tooltip_template += ' \ </tbody> \ </table> \ </div> \ ' tooltip .attr('id', 'tooltip') .attr('data-change', change) .style("", "#") tooltip.transition() .style("opacity", 1); tooltip.html(tooltip_template) .style("left", (d3.event.pageX + 5) + "px") .style("top", (d3.event.pageY + 15) + "px") });
问题分析与修复方案
核心问题
- DOM操作顺序错误:先拼接HTML字符串再尝试绘制折线图,但此时SVG元素尚未插入页面,D3无法选中目标元素。
- 尺寸计算逻辑错误:将鼠标坐标作为SVG内部边距,导致计算出的宽高为负数,折线图无渲染空间。
- 选择器与数据绑定错误:全局选择所有SVG会导致折线图绘制混乱,且
append("g").enter()不符合D3数据绑定逻辑。
修复后的完整代码
// Tooltip leaf.on("mousemove", function (d) { if (d.depth < 3) return; const name2 = d.parent.data.name; const name3 = d.data.name; const change = d.data.change; const filtered = items.filter(item => item.level2 === d.parent.data.name); // 构建HTML模板框架(仅包含空SVG标签) let tooltip_template = ` <h3 class="tooltip-header">${name2} / ${name3}</h3> <div class="tooltip-body"> <table> <tbody> `; // 添加表格行 filtered.forEach(item => { const isActive = item.name === d.data.name; const rowStyle = isActive ? `style="color: #fff; background: ${getColor(d.data.change)}"` : ''; tooltip_template += ` <tr ${rowStyle}> <td>${item.name}</td> <td><svg class="line-svg"></svg></td> <td class="txtright">${item.value} 건</td> <td class="txtright">${item.change > 0 ? '+' : ''}${item.change} 건</td> </tr> `; }); tooltip_template += ` </tbody> </table> </div> `; // 先插入HTML到Tooltip,确保DOM元素存在后再绘制折线图 tooltip .attr('id', 'tooltip') .attr('data-change', change) .style("opacity", 0) .html(tooltip_template) .style("left", `${d3.event.pageX + 5}px`) .style("top", `${d3.event.pageY + 15}px`) .transition() .style("opacity", 1); // 遍历每个SVG元素,绘制对应折线图 filtered.forEach((item, index) => { // 设置SVG内部边距与尺寸 const linemargin = { top: 2, right: 2, bottom: 2, left: 2 }; const linewidth = 100 - linemargin.left - linemargin.right; const lineheight = 20 - linemargin.top - linemargin.bottom; // 获取当前行的SVG元素并设置基础尺寸 const svg = d3.select(tooltip.selectAll('.line-svg').nodes()[index]) .attr('width', 100) .attr('height', 20); // 创建绘图容器 const linechart = svg.append('g') .attr('transform', `translate(${linemargin.left}, ${linemargin.top})`); // 模拟折线图数据(可替换为item对应的真实数据) const linedata = [ { x: 0, y: 5 }, { x: 1, y: 9 }, { x: 2, y: 7 }, { x: 3, y: 5 }, { x: 4, y: 3 }, { x: 5, y: 4 }, { x: 6, y: 8 }, { x: 7, y: 6 }, { x: 8, y: 3 }, { x: 9, y: 2 } ]; // 定义比例尺 const x = d3.scaleLinear().domain([0, 9]).range([0, linewidth]); const y = d3.scaleLinear().domain([0, 10]).range([lineheight, 0]); // 创建折线生成器 const line = d3.line() .x(d => x(d.x)) .y(d => y(d.y)); // 添加折线并设置可见样式 linechart.append('path') .datum(linedata) .attr('class', 'line') .attr('fill', 'none') .attr('stroke', '#333') .attr('stroke-width', 1.5) .attr('d', line); }); });
关键修复点
- 调整DOM操作顺序:先插入完整HTML模板(含空SVG),确保元素存在后再进行折线图绘制。
- 修正尺寸计算:使用SVG内部小边距,保证宽高为正数,提供有效渲染空间。
- 精准匹配SVG元素:通过索引绑定每行对应的SVG,避免绘制混乱。
- 添加折线基础样式:设置
fill: none、stroke等属性,确保折线可见。
内容的提问来源于stack exchange,提问作者rensothearin
相关产品推荐
相关产品推荐

