如何让 d3 树状图单元格文本换行而不溢出其他单元格

eoj*_*oja 0 javascript d3.js

我的 d3 树图中单元格的文本不会包装和溢出其他单元格。这是我的项目

我希望文本看起来像这个项目。我查看了他们的代码(以及许多其他代码),但无法在我的项目中使用它。

问题区域是:

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");
Run Code Online (Sandbox Code Playgroud)

我尝试在评论中使用 .html 而不是 .text 。在 Safari 和 Chrome 中,文本仍然会溢出单元格。在 Firefox 中,只显示电影名称的第一个单词。

tor*_*mar 5

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

第一种也是最简单的方法是保留您的代码结构并按照提供的示例进行类似的过程来拆分文本:

d.data.name.split(/(?=[A-Z][^A-Z])/g)
Run Code Online (Sandbox Code Playgroud)

所以让我们稍微改变一下你的代码:

  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");
Run Code Online (Sandbox Code Playgroud)

这应该完成所需的显示。我们必须考虑到标签很难以避免重叠的方式定位和显示,因为在构建时需要更多的计算。

第二种方法是稍微更改代码结构并创建单元格,与提供的示例非常相似:

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);
Run Code Online (Sandbox Code Playgroud)

方法 1 的 CodePen

方法 2 的 CodePen

方法 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);*/ 
});
Run Code Online (Sandbox Code Playgroud)

方法 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);*/ 
});
Run Code Online (Sandbox Code Playgroud)