skip to Main Content

See attached image – How do I center align all the text in the blue rect? I’ve done it by eye so far, using x, but in real life I won’t be able to do this as the text lengths will vary. The original rectangle was drawn in Inkscape.

image showing svg  with rectangle and text

The svg:

<g
  id="g2450">
  <rect
    width="30"
    height="40"
    stroke="#00ffff"
    fill-opacity="0"
    id="sensor-info"
  >
  </rect>
  <g
    id="sensor-info-text"
 transform="matrix(0.51584178,0,0,0.51641502,11.648419,22.062229)" 
  />
</g>

I then append the following in javascript:

let s = document.getElementById ('sensor-info-text');
s.innerHTML = `
  <text
    x="-20" y="-20"
    font-family="Verdana"
    font-size="12px"
    fill="#0000ff">
    <tspan >${sensor.Name}</tspan>
    <tspan x="-5" dy="20px" >${sensor.SetPoint + String.fromCharCode(176) + 'C'}</tspan>
    <tspan x="-5" dy="20px">${sensor.Measurement + String.fromCharCode(176) + 'C'}</tspan>
  </text>
`

I’ve tried svg text {dominant-baseline:middle;text-anchor:middle;} in the css, as suggested here: https://stackoverflow.com/questions/5546346/how-to-place-and-center-text-in-an-svg-rectangle, but the text "flies off" to the right if I unset e.g. x.

How do I proceed?

2

Answers


  1. You need to calculate suitable x/y coordinates for your <text> elements according to your <rect> elements position.

    The problem: svg <text> elements don’t behave like HTML block elements.

    So dominant-baseline will only shift each text baselines.
    It won’t take the text elements total height as a reference.

    Here’s an example of a labeling helper function:

    let rect = document.querySelector("#sensor-info");
    let rect2 = document.querySelector("#sensor-info2");
    
    let text = `Sensor 1||25°||22°`;
    let text2 = `Sensor 2||25°||29°||28°||22°`;
    
    addTextToRect(rect, text, 5, 6);
    addTextToRect(rect2, text2, 5, 6);
    
    function addTextToRect(rect, text, fontSize = 5, lineHeight = 6, seperator="||") {
      let ns = "http://www.w3.org/2000/svg";
      let svg = rect.closest("svg");
      let { x, y, width, height } = getBBoxTransform(rect);
    
      // get rect centroid
      let cx = x + width / 2;
      let cy = y + height / 2;
    
      // split text in lines
      let lines = text.split(seperator);
    
      // calculate text y offset according to lines
      let offY = ((lines.length - 1) / 2) * lineHeight;
    
      let textEl = document.createElementNS(ns, "text");
      textEl.setAttribute("x", cx);
      textEl.setAttribute("y", cy - offY);
      textEl.setAttribute("text-anchor", "middle");
      textEl.setAttribute("dominant-baseline", "middle");
      textEl.setAttribute("font-size", fontSize);
      textEl.textContent = lines[0];
    
      //add lines
      for (let i = 1; i < lines.length; i++) {
        let tspan = document.createElementNS(ns, "tspan");
        tspan.textContent = lines[1];
        tspan.setAttribute("x", cx);
        tspan.setAttribute("dy", lineHeight);
        textEl.append(tspan);
      }
    
      svg.append(textEl);
    }
    
    function getBBoxTransform(el) {
      let parent = el.farthestViewportElement;
      let bb = el.getBBox();
      // check transformations
      let matrix = parent.getScreenCTM().inverse().multiply(el.getScreenCTM());
      let { a, b, c, d, e, f } = matrix;
      let matrixStr = [a, b, c, d, e, f]
        .map((val) => {
          return +val.toFixed(8);
        })
        .join("");
    
      // no transformations - return bbox
      if (matrixStr === "100100") {
        console.log("default bbox");
        return bb;
      } else {
        let ns = "http://www.w3.org/2000/svg";
        let svg = el.closest("svg");
        // transform bbox
        let p0 = {
          x: bb.x,
          y: bb.y
        };
        let p1 = {
          x: bb.x + bb.width,
          y: bb.y
        };
        let p2 = {
          x: bb.x,
          y: bb.y + bb.height
        };
    
        let pts = [p0, p1, p2];
        let ptsTrans = [];
        pts.forEach((p) => {
          let pt = svg.createSVGPoint();
          pt.x = p.x;
          pt.y = p.y;
          pt = pt.matrixTransform(matrix);
          ptsTrans.push(pt);
        });
        bb = {
          x: ptsTrans[0].x,
          y: ptsTrans[0].y,
          width: ptsTrans[1].x - ptsTrans[0].x,
          height: ptsTrans[2].y - ptsTrans[0].y
        };
      }
      return bb;
    }
    <svg viewBox="0 0 100 100">
      <g id="g2450" transform="matrix(0.51584178,0,0,0.51641502,11.648419,22.062229)">
        <rect width="30" height="40" stroke="#00ffff" fill-opacity="0" id="sensor-info" />
        <rect transform="translate(40 -40) scale(1.2)" x="50" y="50" width="50" height="100" stroke="#00ffff" fill-opacity="0" id="sensor-info2" />
      </g>
    </svg>

    How it works

    • get bounding box of a <rect> and calculate a centroid/center point via custom getBBoxTransform(el) helper. This function will calculate a bounding box also respecting transformations (e.g inherited from parent groups)

    • generate <tspan> elements for pseudo-lines (by splitting text content according to a defined seperator like "||"

    • count lines and calculate the <text> y attribute value

      let offY = ((lines.length - 1) / 2) * lineHeight;
      textEl.setAttribute("y", cy - offY);
      

    You’ll need to adjust font-sizes to get the desired result.

    Login or Signup to reply.
  2. This alternative works for the first <rect> from HerrStrietzel his answer because the <path> and <text> are injected in the <g> where the transform is applied.

    Does not work for the second <rect> where the transform is applied on the rect itself.

    document.querySelector("svg")
    .querySelectorAll("rect").forEach(rect=>{
      let {x,y,width,height} = rect.getBBox();
      let dx = width-x;
      let dy = height-y;
      let d = `M${x} ${y+dy/2}H${width}`;
      let text = "Sensor1";
      let id = new Date()/1;
      console.log(x,y,width,height,dx,dy,"n",d);
      rect.insertAdjacentHTML("afterend",`<path stroke="red" id="P${id}" d="${d}"/>
      <text>
        <textPath href="#P${id}"
                  font-size="9px"
                  startoffset="50%" 
                  dominant-baseline="middle"
                  text-anchor="middle">${text}</textPath>
      </text>`);
    })
    <svg viewBox="0 0 100 100">
      <g id="g2450" transform="matrix(0.51584178,0,0,0.51641502,11.648419,.062229)">
    
        <rect width="30" height="40" stroke="#00ffff" fill-opacity="0" id="sensor-info" />
    
        <rect transform="translate(40 -40) scale(1.2)" x="50" y="50" width="50" height="100" stroke="#00ffff" fill-opacity="0" id="sensor-info2" />
    
      </g>
    </svg>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search