Home > Mobile >  Adding a label above a SVG element without making the SVG element move
Adding a label above a SVG element without making the SVG element move

Time:11-30

I have a project that I am visualizing some data as a SVG element that contains rect elements. So what I want to do is to add a label on a specific rect element of the SVG and in order to do this I have to get the rect element’s position in order to place the label at this exact position.

So in order to implement this first I get the SVG position by using the getBoundingClientRect() javascrip method and then by using the same method I get the rect element position.

After that I get the difference between the position of the svg and the rect elements and the value that I get is the position that the label is going to be placed.

This is the code in order get the position of each element and then calculate the new position of the label.

Tha parentId parameter is the id of the svg element and the childId parameter is the id of the rect element

function getElemementRelativePosition(parentId, childId) {
    var parentPos = document.getElementById(parentId).getBoundingClientRect();
    var childPos = document.getElementById(childId).getBoundingClientRect();
    var relativePos = {};

    relativePos.top = childPos.top - parentPos.top;
    relativePos.right = childPos.right - parentPos.right;
    relativePos.bottom = childPos.bottom - parentPos.bottom;
    relativePos.left = childPos.left - parentPos.left;

    return relativePos;
}

The getElemementRelativePosition function returns the correct values for the label but the problem is that the label’s placeholder stays sticky at its initial position so even thought I change its position the heigh of the label pushes the svg element down.

The div element that contains the label has these properties

<div style="position:relative;z-index:10;top:@ relativePos.top; left:@ relativePos.left;">
MyLabel
</div>

Any suggestions on how to implement this without the svg moving below?

Thanks in advance!

CodePudding user response:

As pointed out by @maciek: You could wrap both svg and label in a div with position:relative and apply position:absolute to your svg and label.

Alternative: svg <text> labels

If you need only simple single-line labels – <text> has some advantages: You just need to get the center x and y coordinates of the <rect> element.
By applying text-anchor:middle and dominant-baseline:middle the text will be vertically and horizontally centered relative to the rect's center.

You get the center coordinates using getBBox() method (similar to getBoundingClientRect() - but specifically for svg elements).
A simple label helper might look like this:

function setSvgLabelPos(rect, label){
  let bb = rect.getBBox();
  let [x, y, width, height] = [bb.x, bb.y, bb.width, bb.height];
  label.setAttribute('x', (x width/2) )
  label.setAttribute('y', (y height/2) )
}

Also neat: the font-size will scale with your parent svg element.

Drawbacks

<text> elements don't act like block elements:

  • no auto line-wrapping
  • no support for background color and box-like appearance respectively

Example

// 1. svg text approach
setSvgLabelPos(rect2, labelSvg);

function setSvgLabelPos(rect, label) {
  let bb = rect.getBBox();
  let [x, y, width, height] = [bb.x, bb.y, bb.width, bb.height];
  label.setAttribute('x', (x   width / 2))
  label.setAttribute('y', (y   height / 2))
}


// 2. HTML relative/absolute layout
let posLabel = getElemementRelativePosition('svg', 'rect', 'label')
label.setAttribute('style', `top: ${posLabel.top}px; left: ${posLabel.left}px`);


function getElemementRelativePosition(parentId, childId, labelId) {
  var parentPos = document.getElementById(parentId).getBoundingClientRect();
  var childPos = document.getElementById(childId).getBoundingClientRect();
  var labelPos = document.getElementById(labelId).getBoundingClientRect();
  var relativePos = {};

  relativePos.top = childPos.top - parentPos.top;
  relativePos.right = childPos.right - parentPos.right;
  relativePos.bottom = childPos.bottom - parentPos.bottom;
  relativePos.left = childPos.left - parentPos.left;
  return relativePos;
}
svg {
  width: 20em;
  border: 1px solid #ccc;
  background: #eee;
}

.svgWrap {
  position: relative;
}

.svgWrap>* {
  position: absolute;
}

.label {
  padding: 0.3em;
  display: inline-block;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
}
<div >
  <svg id="svg" viewBox="0 0 100 100">
    <rect id="rect" x="25%" y="50%" width="20" height="20" fill="red" />
    <rect id="rect2" x="50%" y="25%" width="20" height="20" fill="green" />
    <text id="labelSvg"  x="50%" y="50%" style="font-size:4.5px; font-family:sans-serif;fill:currentColor " text-anchor="middle" dominant-baseline="middle">Svg label</text>
  </svg>
  <div id="label"  style="">MyLabel</div>
</div>

  • Related