工具提示隐藏在D3树状图中

时间:2018-12-13 20:01:53

标签: javascript d3.js treemap

我正在构建需要工具提示的D3树图。工具提示是一个div元素,我通过在树形图的每个叶子上附加的“ mouseover”和“ mouseleave”事件来进行切换。开发者控制台确认元素已添加到DOM,它在div中具有正确的文本显示,悬停时将“ display”设置为“ block”,“ z-index”为10 ,并且“位置”是“绝对”,但是,我无法使工具提示真正按预期出现。

这里是Codepen的链接。

这是我的实际代码:

HTML:

<script src="https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js"></script>

<div>
  <h1 id='title'>JSON Data Treemaps</h1>
  <h2 id='description'>Top 100 Most Pledged Kickstarter Campaigns</h2>
  <svg id="canvas"></svg>
  <svg id="legend"></svg>
</div>

CSS:

@import url('https://fonts.googleapis.com/css?family=Noto+Sans');

body {
  background-color: ghostwhite;
  display: flex;
  flex-direction: column;
  font-size: 16px;
  font-family: 'Noto Sans', sans-serif;

  div#tooltip {
    position: absolute;
  }

  div {
    display: flex;
    flex-direction: column;

    h1 {
      margin: 4.6rem auto 0;
      font-size: 3.2rem;
    }

    h2 {
      margin: 1rem auto 2rem;
      font-size: 1.4rem;
    }

    svg {
      margin: auto;
    }

    svg#legend {
      margin: 2rem auto;
    }
  }
}

JS:

const margin = {
        top: 30,
        bottom: 100,
        left: 30,
        right: 30
      },
      width = 960 - margin.left - margin.right,
      height = 570 - margin.top - margin.bottom;

const KICK_STARTER_DATA_URL = 'https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/kickstarter-funding-data.json';

const MOVIE_DATA_URL = 'https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/movie-data.json';

const VIDEO_GAME_DATA_URL = 'https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/video-game-sales-data.json';

// Load all data and assign to variables.
d3.queue()
  .defer(d3.json, KICK_STARTER_DATA_URL)
  .defer(d3.json, MOVIE_DATA_URL)
  .defer(d3.json, VIDEO_GAME_DATA_URL)
  .await(storeDataCategories);

let KICK_STARTER_DATA;
let MOVIE_DATA;
let VIDEO_GAME_DATA;
let currentDataSet;

function storeDataCategories(error, kickStarter, movies, videoGames) {
  // console.log(...arguments);
  if(error) throw error;
  KICK_STARTER_DATA = kickStarter;
  MOVIE_DATA = movies;
  VIDEO_GAME_DATA = videoGames;
  currentDataSet = kickStarter;
  buildTreemap();
  buildLegend();
}

var svg = d3.select('svg#canvas')
            .attr('width', width)
            .attr('height', height)
            .style('background-color', 'ghostwhite')
            .style('box-shadow', '0 6px 26px darkgray');

var fader = (color) => {
  return d3.interpolateRgb(color, 'ghostwhite')(0.2);
},
    test = d3.schemeCategory20.map(fader),
    colorScale = d3.scaleOrdinal(d3.schemeCategory20.map(fader)),
    format = d3.format(',d');

var treemap = d3.treemap()
                .tile(d3.treemapResquarify)
                .size([width, height]);

// Adding tooltip for info on hover
const tooltip = d3.select('svg#canvas')
                  .append('div')
                  .attr('id', 'tooltip')
                  .attr('width', 60 + 'px')
                  .attr('height', 40 + 'px')
                  .style('z-index', 10)
                  .style('display', 'none')
                  .style('position', 'absolute');

function buildTreemap() {
  // console.log(KICK_STARTER_DATA);
  // console.log(MOVIE_DATA);
  // console.log(VIDEO_GAME_DATA);
  // console.log(currentDataSet);

  var root = d3.hierarchy(currentDataSet)
               .sum(sumValue)
               .sort((a, b) => b.height - a.height || b.value - a.value);

  treemap(root);

  var cell = svg.selectAll('g')
                .data(root.leaves())
                .enter().append('g')
                  .attr('transform', d => 'translate(' + [d.x0, d.y0] + ')')
                .on('mouseover', d => {
                  // console.log('mouseover - d:\n', d);
                  const tooltipText = formatTooltip(d);
                  tooltip.transition().duration(200)
                         .style('position', 'absolute')
                         .style('display', 'block');
                  tooltip.html(tooltipText)
                         .attr('data-value', d.value)
                         .style('top', (d3.event.pageY - 50) + 'px')
                         .style('left', (d3.event.pageX + 20) + 'px');
                })
                .on('mouseout', d => {
                  // console.log('mouseout!');
                  tooltip.transition().duration(500)
                         .style('display', 'none');
                });

  cell.append('rect')
      .attr('id', d => d.data.id)
      .attr('class', 'tile')
      .attr('width', d => d.x1 - d.x0)
      .attr('height', d => d.y1 - d.y0)
      .attr('fill', d => colorScale(d.parent.data.name))
      .attr('data-name', d => d.data.name)
      .attr('data-category', d => d.parent.data.name)
      .attr('data-value', d => d.data.value);

  cell.append('clipPath')
      .attr('id', d => `clip-${d.data.name}`)
      .append('use')
      .attr('xlink:href', d => `#${d.data.name}`);

  cell.append('text')
      .attr('clip-path', d => `url(#clip-${d.data.name})`)
      .selectAll('tspan')
      .data(d => d.data.name.split(/(?=[A-Z][^A-Z])/g))
      .enter().append('tspan')
      .attr('x', 4)
      .attr('y', (d, i) => 13 + i * 10)
      .text(d => d)
      .style('font-size', '10');

}

function buildLegend() {
  console.log(`inside 'buildLegend'`);
  // console.log(currentDataSet);

  const rectWidth = width / 8, 
        rectHeight = 40;

  const legend = d3.select('svg#legend')
                   .attr('width', width)
                   .attr('height', rectHeight);

  legend.append('g')
        .selectAll('g')
        .data(colorScale.domain())
        .enter()
        .append('g')
          .attr('class', 'legend')
        .append('rect')
          .attr('class', 'legend-item')
          .attr('width', '40px')
          .attr('height', '40px')
          .attr('transform', (d, i) => {
            return 'translate(' + i * 40 + ',' + 0 + ')';
          })
          .style('fill', d => colorScale(d))
          .style('stroke', d => colorScale(d));

}

function sumValue(d) {
  return d.value;
}

function formatTooltip(d) {
  const name = d.data.name,
        category = d.data.category,
        value = d.data.value,
        tooltipText = `
            <span>Name:</span> ${name} <br>
            <span>Category: </span> ${category} <br>
            <span>Value:</span> ${value} <br>`;;

  console.log('formatted tooltip:\n', tooltipText);
  return tooltipText;
}

1 个答案:

答案 0 :(得分:0)

轻微错误:您正在将<div>附加到SVG上,这是行不通的。更改代码并将其附加到主体,这是一个分支:

https://codepen.io/anon/pen/KbVvwR

代码更改:

const tooltip = d3.select('body') 以及一些次要的CSS附加功能:

div#tooltip {
   background: #FFF;
   pointer-events: none; // important
   padding: 4px;
   border: 1px solid #CCC;
   border-radius: 3px;
}

希望这会有所帮助。