如何确保树状图单元格中的文本不会溢出?

How to make sure text in treemap cell do not overflow?

http://bl.ocks.org/mundhradevang/1387786

类似这样的东西。但是我有很多数据,所以文本很多。那么确保每个单独的文本在树状图单元格内很好地环绕的最佳方法是什么?

我使用 https://github.com/iros/underscore.nest underscore.nest 来嵌套我的 JSON 数据,所以它看起来像这样

{"children":[{"name":"Afghanistan",
 "children":[{"name":"Qal eh-ye Now","value":2997},
 {"name":"Mahmud-E Eraqi","value":7407}

但是在绘制树状图时,文字到处都是,

这是我用于附加文本的 D3 代码:

 cells
.append("text")
.attr("x", function (d) {
  return d.x + d.dx / 2;
})
.attr("y", function (d) {
  return d.y + d.dy / 2;
})
.attr("text-anchor", "middle")
.text(function (d) { return d.children ? null : d.name })

完整数据集: https://api.myjson.com/bins/g9p4s

我的完整代码 :

 var coordinates = { x: 0, y: 0 };
 var margin = { top: 40, right: 10, bottom: 90, left: 20 }
  var cfg = {
    margin: { top: 40, right: 10, bottom: 90, left: 140 },
    width: 960 - margin.left - margin.right,
    height: 500 - margin.top - margin.bottom,
    color: d3.scale.category20()
  };

  //Put all of the options into a variable called cfg
  if ('undefined' !== typeof options) {
    for (var i in options) {
      if ('undefined' !== typeof options[i]) { cfg[i] = options[i]; }
    }//for i
  }

  var treemap,
    legendCategories,
    uniqueCategories;
  var half = cfg.height / 2;
  var tool = d3.select("body").append("div").attr("class", "toolTip");

  /////////////////////////////////////////////////////////
  //////////// Create the container SVG and g /////////////
  /////////////////////////////////////////////////////////


  //Remove whatever chart with the same id/class was present before
  d3.select(id).select("svg").remove();

  //Initiate the radar chart SVG
  var canvas = d3
    .select(id)
    .append("svg")
    .attr("class", "chart")
    .attr("width", cfg.width + cfg.margin.left + cfg.margin.right)
    .attr("height", cfg.height + cfg.margin.top + cfg.margin.bottom)
    .attr("id", "canvas");
  var innercanvas = canvas
    .append("g")
    .attr("class", "innercanvas")
    .attr("transform", "translate(" + cfg.margin.left + "," + cfg.margin.top + ")");

  legendCategories = data.children.map(a => a.name);
  uniqueCategories = legendCategories.filter(onlyUnique);

  var categoryTitle = String(categoryKey);
  categoryTitle = categoryTitle.substring(categoryTitle.indexOf("." + 1));
  categoryScale = cfg.color;
  categoryScale.domain(uniqueCategories);
  verticalLegend = d3.svg
    .legend()
    .labelFormat("none")
    .cellPadding(5)
    .orientation("vertical")
    .units(categoryTitle)
    .cellWidth(20)
    .cellHeight(10)
    .place(coordinates)
    .inputScale(categoryScale)
    .cellStepping(10);


  treemap = d3.layout
    .treemap()
    .round(false)
    .size([cfg.width, cfg.height])
    .padding(.25)
    .sticky(true)
    .nodes(data);

  var cells = innercanvas
    .selectAll(".newcell")
    .data(treemap)
    .enter()
    .append("g")
    .attr("class", "newcell");

  cells
    .append("rect")
    .attr("x", function (d) {
      return d.x;
    })
    .attr("y", function (d) {
      return d.y;
    })
    .attr("id", "rectangle")
    .attr("width", function (d) {
      return d.dx;
    })
    .attr("height", function (d) {
      return d.dy;
    })
    .style("fill", function (d) {
      return d.children ? cfg.color(d.name) : null;
    })
    .attr("stroke", "#000000")
    .attr('pointer-events', 'all')
    .on("mousemove", function (d) {
      tool.style("left", d3.event.pageX + 10 + "px")
      tool.style("top", d3.event.pageY - 20 + "px")
      tool.style("display", "inline-block");
      tool.html(d.children ? null : d.name + "<br>" + d.value);
    }).on("mouseout", function (d) {
      tool.style("display", "none");
    });

  cells
    .append("text")
    .attr("x", function (d) {
      return d.x + d.dx / 2;
    })
    .attr("y", function (d) {
      return d.y + d.dy / 2;
    })
    .attr("text-anchor", "middle")
    .text(function (d) { return d.children ? null : d.name })



  canvas
    .append("g")
    .attr("transform", "translate(40,50)")
    .attr("class", "legend")
    .attr("id", "legend")
    .call(verticalLegend);




  function onlyUnique(value, index, self) {
    return self.indexOf(value) === index;
  }

有几种方法可以解决我们需要将不同长度的字符串放入不同大小的盒子中的大型数据集的问题。

一种方法是为每个矩形元素添加一个剪切路径 (clip-path),但我认为这对于像这样的可视化来说有点过头了,所以我们将使用其他方法。

首先,您可以为每个 g 添加一个标题元素;大多数浏览器的默认操作是在将鼠标悬停在标题上时显示工具提示。所以:

cells
  .append('title')
  .text(function(d){ return d.name });

现在让我们看看文本元素。为样式表或文档头中的文本节点设置 font-familyfont-size,以便我们处理 predictable 文本大小。

.newcell text {
  font-family: Arial, sans-serif;
  font-size: 10px;
}

我建议稍微向下移动文本,因为当前代码将文本的基线设置在单元格的垂直中心,这太高了。在这里,我添加了 0.35em:

的偏移量
selection
  .append("text")
  .attr("x", function (d) {
    return d.x + d.dx / 2;
  })
  .attr("y", function (d) {
    return d.y + d.dy / 2;
  })
  .attr('dy', '.35em')
  .attr("text-anchor", "middle")
  .text(function (d) {
    return d.children ? '' : d.name;
  })

我们可以通过改变不透明度来过滤文本节点的可见性(这意味着我们可以轻松切换不透明度以显示/隐藏文本),使用

    cells
    .style('opacity', function(d){
      // some function returning 0 or 1
    });

我们可以使用多种不同的方法来决定是显示还是隐藏文本。一种是设置单元格必须具有的最小宽度和最小高度才能在其中显示文本,例如

  var minHeight = 12,
  minWidth = 20;
  cells
    .style('opacity', function(d){
      if ( d.dx <= minWidth || d.dy <= minHeight ) {
        return 0
      };
      return 1;
    });

另一种方法是计算单词的大致宽度并根据框宽度进行测试。我在https://www.math.utah.edu/~beebe/fonts/afm-widths.html找到了一个table的普通字体的平均字符宽度,所以我们可以通过将字长乘以平均字符宽度(以磅为单位)来猜测字的宽度,然后乘以字体大小(以点为单位)和将点转换为像素的转换因子

  var pt_px = 0.75, // convert font size in pt into pixels
  font_size = 10,   // or whatever it is set to in the stylesheet
  averageLetterWidth = 0.58344; // average character width for Arial

  cells
    .style('opacity', function(d){
      if ( d.name.length * averageLetterWidth * pt_px * font_size >= d.dx ) {
        return 0
      }
      return 1;
    });

另一种方法是比较文本元素边界框的大小与单元格的高度和宽度:

  cells
    .style('opacity', function(d){
      var bbox = this.getBBox();
      if ( d.dx <= bbox.width || d.dy <= bbox.height ) {
        return 0;
      }
      return 1;
    });

您可能还想确保文本边缘和框之间有一些填充:

  var h_pad = 2, // 2 pixels vertical padding
  v_pad = 4; // 4 pixels of horizontal padding (2 px at each side)
  cells
    .style('opacity', function(d){
      var bbox = this.getBBox();
      if ( d.dx <= bbox.width + h_pad || d.dy <= bbox.height + v_pad ) {
        return 0;
      }
      return 1;
    });

您可以选择最适合您的用例的方法(或将它们全部结合使用!)。我整理了 a block that demonstrates the different calculations and their effects.