6

I haven't quite found an answer for this using D3. I am making a rectangle tooltip from an svg element that shows up on a graph upon clicking on a node. The text associated with the tooltip, which is from the graph's json, currently only extends horizontally, even if the dimensions of the tooltip are extended vertically. I would like for the rectangle to wrap around the text vertically (like a carriage return), rather than horizontally, given a fixed width for the rectangle.

Below is where I create the tooltip upon clicking a node:

nodes.on("click", function (d){
    if (tip){ tip.remove()};

    tip = svg.append("g")
        .attr("id", "tip")
        .attr("transform", "translate(" + (d3.event.pageX - 10)  + "," + (d3.event.pageY - 35) + ")");

    let rect = tip.append("rect")
        .style("fill", "white")
        .style("stroke", "steelblue");

    tip.append("text")
        .text(d.description)
        .attr("dy", "1em")
        .attr("x", 5);

    let bbox = tip.node().getBBox();

    rect.attr("width", bbox.width + 5)
        .attr("height", bbox.height)
});

Here is a jsfiddle with the current functionality: https://jsfiddle.net/Thegreatmochi/epy6514t/14/

Brian Barry
  • 439
  • 2
  • 17
  • In order to be able to do this you'll need to ad ``s to your text like in this answer: https://stackoverflow.com/questions/16701522/how-to-linebreak-an-svg-text-within-javascript#16701952 – enxaneta May 13 '20 at 06:40

1 Answers1

3

From this bl.ocks.org you can use the wrap function with little modification:

function wrap(text, width) {
  text.each(function() {
    var text = d3.select(this),
        words = text.text().split(/\s+/).reverse(),
        word,
        line = [],
        lineNumber = 0,
        lineHeight = 1.1, // ems
        y = text.attr("y"),
        dy = parseFloat(text.attr("dy")),
        tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
    while (word = words.pop()) {
      line.push(word);
      tspan.text(line.join(" "));
      if (tspan.node().getComputedTextLength() > width) {
        line.pop();
        tspan.text(line.join(" "));
        line = [word];
        tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", lineHeight + "em").text(word);
      }
    }
  });
}

here is a working snippet:

let graph = {
  "nodes": [{
    "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed porta consequat felis, eget varius mi volutpat sit amet. Nullam lobortis vehicula felis, in tempor dui. Sed scelerisque purus ac nisl auctor finibus sit amet nec massa. Duis erat sem, suscipit non molestie vitae, accumsan nec tortor. Nulla sed facilisis ligula, nec interdum elit. Nam tortor lectus, sodales ut nulla ac, feugiat ultrices velit. Vestibulum nec ultricies nisi. Donec leo nibh, fringilla eget pretium nec, condimentum in nulla."
  }]
}

let svg = d3.select("body")
  .append("svg")
  .attr("width", 700)
  .attr("height", 800);

let nodes = svg.selectAll("circle")
  .data(graph.nodes)
  .enter()
  .append("circle")
  .attr("r", 20)
  .attr("cx", function(d) {
    return 20;
  })
  .attr("cy", function(d) {
    return 20;
  })
  .style("fill", "red");
let tip;
nodes.on("click", function(d) {
  if (tip) {
    tip.remove()
  };

  tip = svg.append("g")
    .attr("id", "tip")
    .attr("transform", "translate(" + (d3.event.pageX - 10) + "," + (d3.event.pageY - 35) + ")");

  let rect = tip.append("rect")
    .style("fill", "white")
    .style("stroke", "steelblue");

  tip.append("text")
    .text(d.description)
    .attr("dy", "1em")
    .attr("x", 5)
    .call(wrap, 300)

  let bbox = tip.node().getBBox();

  rect.attr("width", bbox.width + 5)
    .attr("height", bbox.height + 50)

});


function wrap(text, width) {
  text.each(function() {
    var text = d3.select(this),
      words = text.text().split(/\s+/).reverse(),
      word,
      line = [],
      lineNumber = 0,
      lineHeight = 1.1, // ems
      y = text.attr("y"),
      dy = parseFloat(text.attr("dy")),
      tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
    while (word = words.pop()) {
      line.push(word);
      tspan.text(line.join(" "));
      if (tspan.node().getComputedTextLength() > width) {
        line.pop();
        tspan.text(line.join(" "));
        line = [word];
        tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", lineHeight + "em").text(word);
      }
    }
  });
}
<p>
  click the node to show description
</p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.12.2/d3.js"></script>
ROOT
  • 11,363
  • 5
  • 30
  • 45