Как сделать wordwrap для ярлыков диаграмм, используя d3.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);

Ответ 1

Вот рабочая реализация, которую я написал, объединяя различные биты. Как следует из другого ответа, 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) и ширину для переноса. Затем он получает parentNode объекта text и присоединяет к нему foreignObject node, в который добавляется xhtml:p. Для параметра foreignObject задано значение 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

Ответ 3

Вы не можете выполнять автоматическое перенос слов в SVG. Вы можете использовать foreignObject и HTML div для этой цели, но для этого потребуется изменить код, создающий метки оси. Кроме того, вы можете повернуть метки оси, чтобы они имели больше места. См., Например, здесь.

Ответ 4

Здесь функция, которую я написал не только для решения проблемы переноса слов по оси Y, но и для обертывания слова длиной более 1 строки, а также выровнять соответствующий "тик" в центре метки:

Результат выглядит так: введите описание изображения здесь

Смотрите этот фрагмент:

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>

Ответ 5

Здесь некоторый код для plumb 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
        }
    }