如何使用d3.js对图表标签进行自动换行

时间:2013-04-16 14:25:09

标签: d3.js nvd3.js

我正在尝试使用d3.js实现水平条形图。某些图表标签太长。 如何对y aixs上的图表标签进行自动换行?

源代码:

var data = [{"Name": "Label 1", "Count": "428275" }, { "Name": "Label 2", "Count": "365005" }, { "Name": "Label 3", "Count": "327619" }];

var m = [30, 10, 10, 310],
w = 1000 - m[1] - m[3],
h = 550 - m[0] - m[2];

var format = d3.format(",.0f");

var x = d3.scale.linear().range([0, w + 10]),
    y = d3.scale.ordinal().rangeRoundBands([0, h], .4);

var xAxis = d3.svg.axis().scale(x).orient("bottom").tickSize(h),
    yAxis = d3.svg.axis().scale(y).orient("left").tickSize(0);

$("#chartrendering").empty();
var svg = d3.select("#chartrendering").append("svg")
    .attr("width", w + m[1] + m[3])
    .attr("height", h + m[0] + m[2])
  .append("g")
    .attr("transform", "translate(" + m[3] + "," + m[0] + ")");

// Set the scale domain.

x.domain([0, d3.max(data, function (d) { return d.Count; })]);
y.domain(data.map(function (d) { return d.Name; }));

var bar = svg.selectAll("g.bar")
    .data(data)
  .enter().append("g")
    .attr("class", "bar")
    .attr("transform", function (d) { return "translate(0," + y(d.Name) + ")"; });

bar.append("rect")
    .attr("width", function (d) { return x(d.Count); })
    .attr("height", y.rangeBand());

bar.append("text")
    .attr("class", "value")
    .attr("x", function (d) { return x(d.Count); })
    .attr("y", y.rangeBand() / 2)
    .attr("dx", +55)
    .attr("dy", ".35em")
    .attr("text-anchor", "end")
    .text(function (d) { return format(d.Count); });

svg.append("g")
    .attr("class", "x axis")
    .call(xAxis);

svg.append("g")
    .attr("class", "y axis")
    .call(yAxis);

5 个答案:

答案 0 :(得分:9)

这是我通过将各个位拉在一起而编写的工作实现。正如另一个答案所暗示的那样,foreignObject仍然是可行的方法。首先是功能:

var insertLinebreaks = function (t, d, width) {
    var el = d3.select(t);
    var p = d3.select(t.parentNode);
    p.append("foreignObject")
        .attr('x', -width/2)
        .attr("width", width)
        .attr("height", 200)
      .append("xhtml:p")
        .attr('style','word-wrap: break-word; text-align:center;')
        .html(d);    

    el.remove();

};

这将获取文本元素(t),文本内容(d)和要包装的宽度。然后它获取text对象的parentNode,并在其中附加foreignObject节点,其中添加了xhtml:pforeignObject设置为所需width,偏移-width/2为中心。最后,删除原始文本元素。

然后可以按如下方式将其应用于轴元素:

d3.select('#xaxis')
    .selectAll('text')
        .each(function(d,i){ insertLinebreaks(this, d, x1.rangeBand()*2 ); });

这里我使用了rangeBand来获取宽度(图中有2个条形的* 2)。

Resulting image with wrapped labels

答案 1 :(得分:5)

我一直在寻找这个问题的解决方案,并发现Mike Bostock使用D3发布了working example。示例显示适用于x轴,但可以很容易地适应y轴。

答案 2 :(得分:2)

这是我写的一个函数,不仅用于解决y轴自动换行问题,还包括长度超过1行的单词,并且还在标签的中心对齐“tick”:

结果是这样的: enter image description here

请参阅此代码段:

var tempArray2 = [{date: "2017/3/11", ratio: 1}, {date: "2017/3/12", ratio: 0.5}, {date: "2017/3/13", ratio: 0.3}, {date: "2017/3/14", ratio: 0}, {date: "2017/3/15", ratio: 0.8}];

var margin = {
    top: 20,
    right: 20,
    bottom: 40,
    left: 80
  },
  width = 500 - margin.left - margin.right,
  height = 300 - margin.top - margin.bottom;
barHeight = 40;
labelWidth = 0;

tempArray2.sort(function(a, b) {
  return new Date(a.date) - new Date(b.date);
})

dateRange = Math.round((new Date(tempArray2[tempArray2.length - 1].date) - new Date(tempArray2[0].date)) / 1000 / 3600 / 24);

svg = d3.select('body')
  .append("svg")
  .attr("style", "width: 500px\; height: 300px\;");

var x = d3.scaleUtc().range([0, width])
  .domain([toUTCDate(tempArray2[0].date), calculateDays(toUTCDate(tempArray2[tempArray2.length - 1].date), 1)]);

var y = d3.scaleBand()
  .range([height, 0])
  .padding(0.1)
  .domain(["Domain for testinginginging", "Another domain used for testing", "Horizontal bar"]);

passBar = svg.selectAll(".passBar")
  .data(tempArray2)
  .enter();

passBar.append("rect")
  .attr("class", "passBar")
  .attr("height", barHeight)
  .attr("width", function(d) {
    return x(calculateDays(toUTCDate(d.date), d.ratio)) - x(toUTCDate(d.date));
  })
  .attr("y", y("Horizontal bar") + (y.bandwidth() - barHeight) / 2)
  .attr("transform", function(d) {
    return "translate(" + (margin.left + x(toUTCDate(d.date))) + ", 0)";
  });

failBar = svg.selectAll(".failBar")
  .data(tempArray2)
  .enter();

failBar.append("rect")
  .attr("class", "failBar")
  .attr("height", barHeight)
  .attr("width", function(d) {
    return x(calculateDays(toUTCDate(d.date), 1 - d.ratio)) - x(toUTCDate(d.date));
  })
  .attr("y", y("Horizontal bar") + (y.bandwidth() - barHeight) / 2)
  .attr("transform", function(d) {
    return "translate(" + (margin.left + x(toUTCDate(d.date)) + x(calculateDays(toUTCDate(d.date), d.ratio)) - x(toUTCDate(d.date))) + ", 0)";
  });

//add grid lines
svg.append("g")
  .attr("class", "grid")
  .attr("transform", "translate(" + margin.left + "," + height + ")")
  .call(make_x_gridlines(dateRange)
    .tickSize(-height)
    .tickFormat("")
  )

// always draw axis at last
svg.append("g")
  .attr("transform", "translate(" + margin.left + "," + height + ")")
  .attr("class", "xAxis")
  .call(d3.axisBottom(x).ticks(dateRange).tickFormat(d3.utcFormat("%m-%d")))
  .selectAll("text")
  .style("text-anchor", "middle");
svg.append("g")
  .attr("transform", "translate(" + margin.left + ", 0)")
  .attr("class", "yAxis")
  .call(d3.axisLeft(y))
  .selectAll("text")
  .attr("class", "cateName")
  .style("text-anchor", "start")
  .call(wrapText, margin.left - 13);

function calculateDays(date, number) {
  date.setUTCDate(date.getUTCDate() + number);
  return date;
}

function make_x_gridlines(tickTime) {
  return d3.axisBottom(x).ticks(tickTime);
}

function toUTCDate(input) {
  var tempDate = new Date(input);
  return new Date(Date.UTC(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate()));
}



function wrapText(text, width) {
  text.each(function() {
    var text = d3.select(this),
      textContent = text.text(),
      tempWord = addBreakSpace(textContent).split(/\s+/),
      x = text.attr('x'),
      y = text.attr('y'),
      dy = parseFloat(text.attr('dy') || 0),
      tspan = text.text(null).append('tspan').attr('x', x).attr('y', y).attr('dy', dy + 'em');
    for (var i = 0; i < tempWord.length; i++) {
      tempWord[i] = calHyphen(tempWord[i]);
    }
    textContent = tempWord.join(" ");
    var words = textContent.split(/\s+/).reverse(),
      word,
      line = [],
      lineNumber = 0,
      lineHeight = 1.1, // ems
      spanContent,
      breakChars = ['/', '&', '-'];
    while (word = words.pop()) {
      line.push(word);
      tspan.text(line.join(' '));
      if (tspan.node().getComputedTextLength() > width) {
        line.pop();
        spanContent = line.join(' ');
        breakChars.forEach(char => {
          // Remove spaces trailing breakChars that were added above
          spanContent = spanContent.replace(char + ' ', char);
        });
        tspan.text(spanContent);
        line = [word];
        tspan = text.append('tspan').attr('x', x).attr('y', y).attr('dy', lineHeight+'em').text(word);
      }
    }
    var emToPxRatio = parseInt(window.getComputedStyle(text._groups[0][0]).fontSize.slice(0, -2));
    text.attr("transform", "translate(-" + (margin.left - 13) + ", -" + lineHeight + ")");

    function calHyphen(word) {
      tspan.text(word);
      if (tspan.node().getComputedTextLength() > width) {
        var chars = word.split('');
        var asword = "";
        for (var i = 0; i < chars.length; i++) {
          asword += chars[i];
          tspan.text(asword);
          if (tspan.node().getComputedTextLength() > width) {
            if (chars[i - 1] !== "-") {
              word = word.slice(0, i - 1) + "- " + calHyphen(word.slice(i - 1));
            }
            i = chars.length;
          }
        }
      }
      return word;
    }
  });

  function addBreakSpace(inputString) {
    var breakChars = ['/', '&', '-']
    breakChars.forEach(char => {
      // Add a space after each break char for the function to use to determine line breaks
      inputString = inputString.replace(char, char + ' ');
    });
    return inputString;
  }
}
svg {
    width: 100%;
    height: 100%;
    position: center;
}
.passBar {
    fill: #a6f3a6;
}
.failBar {
    fill: #f8cbcb;
}
.grid line {
    stroke: white;
    stroke-width: 2px;
}
.grid path {
    stroke-width: 0;
}
.xAxis {
    font-size: 15px;
    shape-rendering: crispEdges;
}
.yAxis {
    font-size: 15px;
    shape-rendering: crispEdges;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<body></body>

关于自动换行的主要功能是这一个:wrapText。 修改自 https://bl.ocks.org/ericsoco/647db6ebadd4f4756caehttps://bl.ocks.org/mbostock/7555321

希望它可以提供帮助。

答案 3 :(得分:1)

你不能在SVG中进行自动换行。您可以使用foreignObject和HTML div来实现此目的,但这需要修改创建轴标签的代码。或者,您可以旋转轴标签,以便它们有更多空间。有关如何执行此操作,请参阅here

答案 4 :(得分:0)

这里有一些代码可以将Mike Bostock的chart()函数转换为angular-nvd3。有关背景信息,请参阅https://github.com/krispo/angular-nvd3/issues/36

        discretebar: {
            dispatch: {
                renderEnd: function(e){
                    d3.selectAll(".tick text").call(wrap,_chart.xAxis.rangeBand());
                }
            }
        },
        callback: function(chart){
            _chart = chart; //global var
        }
    }