Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Smarter way of chaining SVG operations in this example?

In the following code, I create text svg elements, word-wrap it and then create bounding boxes based on the new (wrapped) text dimensions and recntagles. I was only able do it in the following way, and I would like to know if the code could not be simplified so that maybe the boxes are created in the same function as the text?

 var a=settings.g.selectAll(".axislabel")
                .data(featureData)
                .join(
                    enter => enter.append("text")
                        .attr("x", d => d.label_coord.x)
                        .attr("y", d => d.label_coord.y)
                        .text(d => d.name)
                        .attr("dy", "0.35em")
                        .call(wrap, 60)
                )
    
    var x=settings.g.selectAll("text")
/getting the elements I just created
    for(let a of x['_groups'][0])
    {
        //iterating through and create bounding boxes
        var v=a.getBBox();
       //createing rectangles based on the boundinx boxes
        settings.g.selectAll("labelBack")
        .data([a.textContent])
        .join(
            enter => enter.append("rect")
                .attr("x", v.x)
                .attr("y", v.y)
                .attr("width", v.width)
                .attr("height", v.height)
                .attr("fill", "white")
                        .attr("opacity", 1)
                        .attr("stroke-width", 1)
                        .attr("stroke", "black")
        );
like image 390
Ropes Avatar asked Jan 24 '26 13:01

Ropes


1 Answers

When you use selection.join with a functional argument, the function that you pass in constructs the dom element you want. Thus, you've got a lot of flexibility in what you build. In particular, you can do something like

enter => {
  let g = enter.append("g");
  let rect = g.append("rect");
  let text = g.append("text")
     ...Code to set the text
  rect
    .attr('height', ...Code to set the height)
    .attr('width', ...Code to set the width)

This can work because the text was set by the time the rect is sized. You can see this in action in the code below.

Credit: While this is a little bit different, I based the code partly on Gerardo's answer here.

  let pad = 2;
  let w = 200;
  let h = 100;
  let svg = d3
    .select('#viz')
    .append("svg")
    .attr("viewBox", [0, 0, w, h])
    .style("max-width", `${640}px`)
    .style('border', 'solid 1px black');
    
   let data = [
    { x: 10, y: 20, text: "This" },
    { x: 56, y: 50, text: "should" },
    { x: 120, y: 20, text: "work" }
  ];

  svg
    .append("g")
    .selectAll(null)
    .data(data)
    .join((enter) => {
      let g = enter.append("g");
      let rect = g.append("rect");
      let text = g
        .append("text")
        .attr("x", (d) => d.x)
        .attr("y", (d) => d.y)
        .text((d) => d.text)
        .call(wrap);
      rect
        .attr("x", (d) => d.x - pad)
        .attr("y", (d) => d.y - d.bbox.height + 2 * pad)
        .attr("width", (d) => d.bbox.width + 2 * pad)
        .attr("height", (d) => d.bbox.height + 2 * pad)
        .attr("fill", "#ddd")
        .attr("stroke", "black");
    });

  function wrap(selection) {
    selection.each(function (d) {
      d.bbox = this.getBBox();
    });
  }
<div id="viz"></div>
<script src="https://d3js.org/d3.v7.min.js"></script>
like image 126
Mark McClure Avatar answered Jan 26 '26 02:01

Mark McClure