如何获取d3树图单元格文本以换行而不溢出其他单元格

时间:2019-06-17 00:03:29

标签: javascript d3.js

我的d3树状图中的单元格文本没有换行并溢出其他单元格。这是我的project

我希望文本看起来像project。我已经看过他们的代码(以及许多其他代码),但是我无法在我的项目中使用它。

问题区域是:

svg.append('text')
.selectAll('tspan')
.data(root.leaves())
.enter()
.append('tspan')
.attr("x", (d) => d.x0 + 5)
.attr("y", (d) => d.y0 + 20)
.text( (d) => d.data.name)   //.html( (d) =>       d.data.name.replace(/\s/g, "<br>"))
.attr("font-size", "0.6em")
.attr("fill", "white");

我尝试使用.html而不是注释中的.text。在Safari和Chrome浏览器中,文本仍然溢出了单元格。在Firefox中,仅显示电影名称的第一个单词。

1 个答案:

答案 0 :(得分:1)

我们有两个选项以类似于您提供的示例的方式显示文本。

第一个也是最轻松的方法是保持代码结构,并按照提供的示例进行类似的过程来拆分文本:

d.data.name.split(/(?=[A-Z][^A-Z])/g)

因此,让我们稍微更改一下代码:

  svg.selectAll('text')
      .data(root.leaves())
      .enter()
      .append('text')
      .selectAll('tspan')
      .data(d => {
          return d.data.name.split(/(?=[A-Z][^A-Z])/g) // split the name of movie
              .map(v => {
                  return {
                      text: v,
                      x0: d.x0,                        // keep x0 reference
                      y0: d.y0                         // keep y0 reference
                  }
              });
      })
      .enter()
      .append('tspan')
      .attr("x", (d) => d.x0 + 5)
      .attr("y", (d, i) => d.y0 + 15 + (i * 10))       // offset by index 
      .text((d) => d.text)
      .attr("font-size", "0.6em")
      .attr("fill", "white");

这应该完成所需的显示。我们必须考虑到,标签的放置和显示非常困难,避免了重叠,因为在构建时需要更多的计算。

第二种方法是稍微更改代码结构并创建单元格,就像提供的示例一样:

const cell = svg.selectAll('g')
    .data(root.leaves())                                    
    .enter()
    .append('g')                                              // create a group for each cell / movie
    .attr('transform', d => `translate(${d.x0},${d.y0})`)     // let the group element handle the general positioning
    .on('mousemove', d => {
      //...
    })
    .on('mouseout', d => {
      //...
    });

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

cell.append('text')                                           // append text node for each cell / movie
    .selectAll('tspan')                                       
    .data(d => d.data.name.split(/(?=[A-Z][^A-Z])/g))         // split the name and use that as data to create indiviual tspan elements
    .enter()
    .append('tspan')                                          // append tspan node for each element of the string which got split
    .attr('font-size', '8px')
    .attr('x', 4)
    .attr('y', (d, i) => 13 + 10 * i)                         // offset the y positioning with the index of the data
    .text(d => d);

CodePen for approach 1

CodePen for approach 2

方法1的完整代码:

// !! IMPORTANT README:

// You may add additional external JS and CSS as needed to complete the project, however the current external resource MUST remain in place for the tests to work. BABEL must also be left in place. 

const w = 960;
const h = 600;
const padding = 60;
const svg = d3.select("#container").append("svg")
              .attr("width", w).attr("height", h);
const legendsvg = d3.select("#legend").append("svg")
              .attr("width", 960).attr("height", 50);
const legendPadding = 10;

d3.json("https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/movie-data.json") 
.then(function(data) {
    var root = d3.hierarchy(data).sum(function(d){ return d.value});

    var treeMap = d3.treemap()
        .size([w, h])
        .paddingInner(1);

    treeMap(root);

    const toolTip = d3
        .select("#container")
        .append("div")
        .attr("class", "tooltip")
        .style("opacity", 0);

    var color = d3.scaleOrdinal()
        .domain(["Action", "Drama", "Adventure", "Family", "Animation", "Comedy", "Biography"])
        .range(["#db8a00", "#75b0ff", "#13ad37", "#5d6d00", "#757582", "#d37cff", "#f96868"])

    svg.selectAll("rect")
        .data(root.leaves())
        .enter().append("rect")
        .attr("class", "tile")
        .attr("data-name", (d) => d.data.name)
        .attr("data-category", (d) => d.data.category)
        .attr("data-value", (d) => d.data.value)
        .attr('x', (d) => d.x0)
        .attr('y', (d) => d.y0)
        .attr('width', (d) => d.x1 - d.x0)
        .attr('height', (d) => d.y1 - d.y0)
        .style("stroke", "black")
        .style("fill", (d) => color(d.parent.data.name))
        .on("mouseover", (d, i) => {
            toolTip
              .transition()
              .duration(0)
              .style("opacity", 0.8);
            toolTip
              .attr("id", "tooltip")
              .html(function() {
              return "<span>" + "Name: " + d.data.name + "<br />" + "Category: " + d.data.category + "<br />" + "Value: " + d.data.value + "</span>";
            })
              .style("left", d3.event.pageX - 87.5 + "px") // -87.5 is half width of tooltip in css
              .style("top", d3.event.pageY - 75 + "px")
              .attr("data-value", d.data.value);
          })
           .on("mouseout", function(d) {
            toolTip
              .transition()
              .duration(0)
              .style("opacity", 0);
           });

 svg.selectAll('text')
      .data(root.leaves())
      .enter()
      .append('text')
      .selectAll('tspan')
      .data(d => {
          return d.data.name.split(/(?=[A-Z][^A-Z])/g) // split the name of movie
              .map(v => {
                  return {
                      text: v,
                      x0: d.x0,                        // keep x0 reference
                      y0: d.y0                         // keep y0 reference
                  }
              });
      })
      .enter()
      .append('tspan')
      .attr("x", (d) => d.x0 + 5)
      .attr("y", (d, i) => d.y0 + 15 + (i * 10))       // offset by index 
      .text((d) => d.text)
      .attr("font-size", "0.6em")
      .attr("fill", "white");

console.log(root.leaves());
       /*svg.selectAll("text")
        .data(root.leaves())
        .enter()
        .append("text")
          .attr("x", function(d){ return d.x0+5})    
          .attr("y", function(d){ return d.y0+20})   
          .text(function(d){ return d.data.name })
          .attr("font-size", "0.6em")
          .attr("fill", "white")*/

      legendsvg.selectAll('rect')
          .data(root.children)
          .enter()
          .append('rect')
          .attr('class', 'legend-item')
          .style('stroke', 'white')
          .attr('x', (d,i) => i*140 )
          .attr('width', 130)
          .attr('height', 20)
          .style('fill', d => color(d.data.name))

     legendsvg.selectAll('text')
            .data(root.children)
            .enter()
            .append('text')
            .attr('x', (d,i) => i*140)
            .attr('y', 40)
            .text(d => d.data.name);

      //had to change the legend below because it wouldn't pass fcc test
  /*legendsvg.append("g").classed("legend", true).classed("legend-item", true);
  const legend = d3.legendColor().shape("rect")
    .shapeWidth(90).cells(7).orient("horizontal").scale(color);
  legendsvg.select(".legend").call(legend);*/ 
});

方法2的完整代码:

// !! IMPORTANT README:

// You may add additional external JS and CSS as needed to complete the project, however the current external resource MUST remain in place for the tests to work. BABEL must also be left in place. 

const w = 960;
const h = 600;
const padding = 60;
const svg = d3.select("#container").append("svg")
              .attr("width", w).attr("height", h);
const legendsvg = d3.select("#legend").append("svg")
              .attr("width", 960).attr("height", 50);
const legendPadding = 10;

d3.json("https://cdn.rawgit.com/freeCodeCamp/testable-projects-fcc/a80ce8f9/src/data/tree_map/movie-data.json") 
.then(function(data) {
    var root = d3.hierarchy(data).sum(function(d){ return d.value});

    var treeMap = d3.treemap()
        .size([w, h])
        .paddingInner(1);

    treeMap(root);

    const toolTip = d3
        .select("#container")
        .append("div")
        .attr("class", "tooltip")
        .style("opacity", 0);

    var color = d3.scaleOrdinal()
        .domain(["Action", "Drama", "Adventure", "Family", "Animation", "Comedy", "Biography"])
        .range(["#db8a00", "#75b0ff", "#13ad37", "#5d6d00", "#757582", "#d37cff", "#f96868"])

    const cell = svg.selectAll('g')
            .data(root.leaves())
            .enter()
            .append('g')
            .attr('transform', d => `translate(${d.x0},${d.y0})`)
            .on('mousemove', d => {
              toolTip.transition()
                      .duration(200)
                      .style('opacity', 0.75);
              toolTip.attr('data-value', d.data.value);
              toolTip.html(
                'Name: ' + d.data.name + '<br>' +
                'Category: ' + d.data.category + '<br>' +
                'Value: ' + d.data.value
              )
                .style('top', `${d3.event.pageY + 10}px`)
                .style('left', `${d3.event.pageX + 8}px`);
            })
            .on('mouseout', d => {
              toolTip.transition()
                      .duration(200)
                      .style('opacity', 0);
            });


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

      cell.append('text')
          .selectAll('tspan')
          .data(d => d.data.name.split(/(?=[A-Z][^A-Z])/g))
          .enter()
          .append('tspan')
          .attr('font-size', '8px')
          .attr('x', 4)
          .attr('y', (d, i) => 13 + 10*i)
          .text(d => d);



      legendsvg.selectAll('rect')
          .data(root.children)
          .enter()
          .append('rect')
          .attr('class', 'legend-item')
          .style('stroke', 'white')
          .attr('x', (d,i) => i*140 )
          .attr('width', 130)
          .attr('height', 20)
          .style('fill', d => color(d.data.name))

     legendsvg.selectAll('text')
            .data(root.children)
            .enter()
            .append('text')
            .attr('x', (d,i) => i*140)
            .attr('y', 40)
            .text(d => d.data.name);

      //had to change the legend below because it wouldn't pass fcc test
  /*legendsvg.append("g").classed("legend", true).classed("legend-item", true);
  const legend = d3.legendColor().shape("rect")
    .shapeWidth(90).cells(7).orient("horizontal").scale(color);
  legendsvg.select(".legend").call(legend);*/ 
});