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

时间:2018-09-05 09:46:38

标签: javascript d3.js treemap

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}

但是在绘制我的树状图时,文字无处不在, enter image description here

这是我的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;
  }

1 个答案:

答案 0 :(得分:1)

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

一种方法是为每个矩形元素添加一个剪切路径(clip-path),但是我认为这样的可视化效果会超出顶部,因此我们将使用其他方式。

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

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

现在让我们看一下文本元素。为样式表或文档标题中的文本节点设置font-familyfont-size,以便我们处理可预测的文本大小。

.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处找到了常见字体的平均字符宽度表,因此我们可以通过将单词长度乘以平均字符宽度(以磅为单位),再乘以字体大小来猜测单词的宽度(以磅为单位)以及将点转换为像素的转换因子

  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