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>