Home > Net >  Accurate placement of SVG foreignObject based on path
Accurate placement of SVG foreignObject based on path

Time:07-29

I am working with a svg element which has a path and foreignObject. I am trying to place the foreignObject at the end of the path. But I can't figure out how to do that.

For example, this is what I tried.

// global
const bound = document.querySelector('.bound');
const svgns = 'http://www.w3.org/2000/svg';
const line = document.querySelector('#valLine');
const length = line.getTotalLength();
const pct =1;
const point = line.getPointAtLength(length * pct);

// create  svg text element
const textGroup = document.createElementNS(svgns,'g');
textGroup.setAttribute('class','textElement');
bound.appendChild(textGroup);

const text = document.createElementNS(svgns,'text');
text.setAttribute('x',`${point.x.toString()}`);
text.setAttribute('y',`${point.y.toString()}`);
text.setAttribute('fill','white');
text.textContent='31.13';
textGroup.appendChild(text);

// create  foreign object => g > foreignObject
const foGroup = document.createElementNS(svgns,'g');
foGroup.setAttribute('class','foreignObjectElement');
bound.appendChild(foGroup);

const fo = document.createElementNS(svgns, 'foreignObject')
fo.setAttribute("xmlns", 'http://www.w3.org/1999/xhtml');
fo.setAttribute('class','fo');
fo.setAttribute('x',`${point.x.toString()}`);
fo.setAttribute('y',`${point.y.toString()}`);
fo.setAttribute("width", "40");
fo.setAttribute("height", "40");
foGroup.appendChild(fo);


//div inside foreign object ==> g > foreignObject > div
const div = document.createElement('div');
div.setAttribute("xmlns", 'http://www.w3.org/1999/xhtml');
fo.appendChild(div);

//span inside div ==> g > foreignObject > div > span
const span = document.createElement('span');
span.setAttribute('class','pop');
span.style.setProperty('height','auto');
span.style.setProperty('width','auto');
span.style.setProperty('background-color', '#555');
span.style.setProperty('color', '#fff');
span.style.setProperty('text-align', 'center');
span.style.setProperty('border-radius', '6px');
span.style.setProperty('padding', '8px 0');
span.style.setProperty('position', 'absolute');
span.textContent = '31.13';
div.appendChild(span);
div > span::after {
    content: "";    
    position: absolute;   
    bottom: 0;   
    top: 100%;
    left: 50%;
    margin-left: -5px;
    border-width: 5px;
    border-style: solid;
    border-color: #555 transparent transparent transparent;
}
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <!--<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>-->
  <body>
    <div id="container"></div>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
      <rect  width="1280" height="720" fill="green" stroke="red"></rect>
      <rect  x="70" y="70" width="1020" height="600" fill="none" stroke="green"></rect>
      <g  style="transform: translate(70px, 70px);">
        <g >
          <path id="valLine" d="M0,600L42.5,600L85,334.0399725781784L127.5,313.86735572781834L212.5,222.9985644559286L255,222.9985644559286L297.5,248.66379557852153L340,248.66379557852153L382.5,248.66379557852153L425,171.66810221073513L467.5,171.66810221073513L510,146.00287108814229L552.5,248.66379557852153L595,248.66379557852153L637.5,248.66379557852153L680,197.33333333333186L722.5,197.33333333333186L765,171.66810221073513L807.5,94.67240884294881L850,94.67240884294881L892.5,94.67240884294881L935,25.495262704563594L977.5,25.495262704563594L1020,0" stroke="black" fill="none"></path>
        </g>
      </g>
    </svg>
    </svg>
  </body>
  <script src="prod.js" type="text/javascript"></script>
</html>

My initial assumption was to create a SVG Point through path.getPointAtLength(path.getTotalLength() * 1) and assign the x and y to the foreignObject, which unfortunately did not work for foreignObject but perfectly works for a svg text.

By assigning the same coordinate to a svg text element I am able to place it at the desired location.

Said differently, how can I programmatically position the foreignObject at the exact same location as the svg text?

CodePudding user response:

I managed to do this by using getExtentOfChar() where I create the svg text element first and then copy the y value from those nodes to foreign object nodes.

The drawback is the text nodes are required to be created first in order for the foreign objects to retrieve the accurate y position.

//global
 const bound = document.querySelector('.bound');
 const svgns = 'http://www.w3.org/2000/svg';
 const line = document.querySelector('#valLine');
 const val = [63.75, 35.28, 48.86];

 // create  svg text element group => g for text
 const textGroup = document.createElementNS(svgns, 'g');
 textGroup.setAttribute('class', 'textElement');
 bound.appendChild(textGroup);

 // create  foreign object group=> g for foreignObject
 const foGroup = document.createElementNS(svgns, 'g');
 foGroup.setAttribute('class', 'foreignObjectElement');
 bound.appendChild(foGroup);

 document.querySelectorAll('#valLine>path').forEach(
    (a, i) => {
        const length = a.getTotalLength();
        const pct = 1;
        const point = a.getPointAtLength(length * pct);

        //create text => g > text
        const text = document.createElementNS(svgns, 'text');
        text.setAttribute('x', `${point.x.toString()}`);
        text.setAttribute('y', `${point.y.toString()}`);
        text.textContent = val[i].toString();
        textGroup.appendChild(text);

        // create  foreign object => g > foreignObject
        const fo = document.createElementNS(svgns, 'foreignObject')
        fo.setAttribute("xmlns", 'http://www.w3.org/1999/xhtml');
        fo.setAttribute('class', 'fo');
        fo.setAttribute('x', `${point.x.toString()}`);
        fo.setAttribute('y', `${(point.y).toString()}`);
        fo.setAttribute('alignment-baseline', `before-edge`);
        fo.setAttribute("width", "40");
        fo.setAttribute("height", "40");
        foGroup.appendChild(fo);

        //div inside foreign object ==> g > foreignObject > div
        const div = document.createElement('div');
        div.setAttribute("xmlns", 'http://www.w3.org/1999/xhtml');
        div.style.setProperty('position', 'fixed');
        fo.appendChild(div);

        //span inside div ==> g > foreignObject > div > span
        const span = document.createElement('span');
        span.setAttribute('class', 'pop');
        /*span.style.setProperty('height','80px');
        span.style.setProperty('width','auto');
        span.style.setProperty('background-color', '#555');
        span.style.setProperty('color', '#fff');
        span.style.setProperty('text-align', 'center');
        span.style.setProperty('border-radius', '6px');
        span.style.setProperty('padding', '8px 0');*/
        span.style.setProperty('position', 'fixed');
        span.textContent = val[i].toString();
        div.appendChild(span);

    }
 )

 const textElement = document.querySelectorAll('body > svg > g > g.textElement > text');
 const foElement = document.querySelectorAll('body > svg > g > g.foreignObjectElement > foreignObject');

 textElement.forEach((a, i) => {
    const y = a.getExtentOfChar(0).y;
    foElement[i].setAttribute('y', y);
 })


 const parent = textGroup.parentNode;
 //parent.removeChild(textGroup);
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <!--<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>-->
  <body>
    <div id="container"></div>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1536 720">
      <rect  width="1536" height="720" fill="green" stroke="red" opacity="0.1"></rect>
      <rect  x="70" y="70" width="1346" height="600" fill="none" stroke="none"></rect>
      <g  style="transform: translate(70px, 70px);">
        <g id="valLine">
          <path  id="Rwanda" fill="none" stroke="red" stroke-width="1" d="M0,438.6554621848753L56.08333333333333,438.6554621848753L112.16666666666666,438.6554621848753L168.25,357.98319327731105L224.33333333333331,358.34658187599433L280.4166666666667,358.34658187599433L336.5,141.17647058823525L392.58333333333337,141.17647058823525L448.66666666666663,141.17647058823525L504.75,141.17647058823525L560.8333333333334,141.17647058823525L616.9166666666666,70.5882352941176L673,70.5882352941176L729.0833333333333,70.5882352941176L785.1666666666667,70.5882352941176L841.25,70.5882352941176L897.3333333333333,0L953.4166666666667,0L1009.5,0L1065.5833333333333,0L1121.6666666666667,23.5294117647058L1177.75,23.5294117647058L1233.8333333333333,23.5294117647058L1289.9166666666667,23.5294117647058L1346,23.5294117647058"></path>
          <path  id="Andorra" fill="none" stroke="green" stroke-width="1" d="M0,532.7731092436975L56.08333333333333,532.7731092436975L112.16666666666666,532.7731092436975L168.25,532.7731092436975L224.33333333333331,465.5462184873948L280.4166666666667,465.5462184873948L336.5,465.5462184873948L392.58333333333337,465.5462184873948L448.66666666666663,331.0924369747897L504.75,331.0924369747897L560.8333333333334,331.0924369747897L616.9166666666666,364.70588235294116L673,263.8655462184875L729.0833333333333,263.8655462184875L785.1666666666667,129.4117647058823L841.25,129.4117647058823L897.3333333333333,129.4117647058823L953.4166666666667,129.4117647058823L1009.5,230.25210084033597L1065.5833333333333,297.478991596639L1121.6666666666667,297.478991596639L1177.75,297.478991596639L1233.8333333333333,163.02521008403383L1289.9166666666667,163.02521008403383L1346,163.02521008403383"></path>
          <path  id="Cuba" fill="none" stroke="blue" stroke-width="1" d="M0,385.8783581344254L56.08333333333333,340.04110795732606L112.16666666666666,340.04110795732606L168.25,340.04110795732606L224.33333333333331,340.04110795732606L280.4166666666667,340.04110795732606L336.5,261.5473775717186L392.58333333333337,261.5473775717186L448.66666666666663,261.5473775717186L504.75,261.5473775717186L560.8333333333334,261.5473775717186L616.9166666666666,193.79191415980043L673,193.79191415980043L729.0833333333333,193.79191415980043L785.1666666666667,174.38265408552468L841.25,174.38265408552468L897.3333333333333,140.17685505574866L953.4166666666667,140.17685505574866L1009.5,140.17685505574866L1065.5833333333333,140.17685505574866L1121.6666666666667,140.17685505574866L1177.75,99.07632474477359L1233.8333333333333,99.07632474477359L1289.9166666666667,99.07632474477359L1346,97.28970086328088"></path>
        </g>
      </g>
    </svg>
    </svg>
  </body>
  <script type="text/javascript"></script>
</html>

  • Related