Home > Enterprise >  Javascript to determine if svg text is partially or entirely visible
Javascript to determine if svg text is partially or entirely visible

Time:05-31

I am working with a svg element which has a line chart and the label at the end of the each line.

A minimum sample is below

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">

    <rect  width="1280" height="720" fill="black" stroke="bound" style="overflow: visible;"></rect>
    <rect  x="70" y="70" width="1120" height="600" fill="black"></rect>
    <g  style="transform: translate(70px, 70px);">

        <g >
            <path  fill="none" stroke="white" opacity="1" d="M0,385.8783581344254L46.666666666666664,340.04110795732606L93.33333333333333,340.04110795732606L140,340.04110795732606L186.66666666666666,340.04110795732606L233.33333333333334,340.04110795732606L280,261.5473775717186L326.6666666666667,261.5473775717186L373.3333333333333,261.5473775717186L420,261.5473775717186L466.6666666666667,261.5473775717186L513.3333333333333,193.79191415980043L560,193.79191415980043L606.6666666666666,193.79191415980043L653.3333333333334,174.38265408552468L700,174.38265408552468L746.6666666666666,140.17685505574866L793.3333333333334,140.17685505574866L840,140.17685505574866L886.6666666666666,140.17685505574866L933.3333333333334,140.17685505574866L980,99.07632474477359L1026.6666666666665,99.07632474477359L1073.3333333333335,99.07632474477359L1120,97.28970086328088"></path>
            <path  fill="none" stroke="white" opacity="1
            " d="M0,498.798228969007L46.666666666666664,498.798228969007L93.33333333333333,508.9184060721062L140,508.9184060721062M233.33333333333334,405.6265984654729L280,405.6265984654729L326.6666666666667,405.6265984654729L373.3333333333333,405.6265984654729L420,456.7774936061383L466.6666666666667,426.0869565217393L513.3333333333333,426.0869565217393L560,405.6265984654729L606.6666666666666,405.6265984654729L653.3333333333334,221.48337595907955L700,221.48337595907955L746.6666666666666,221.48337595907955L793.3333333333334,201.02301790281317L840,211.25319693094582L886.6666666666666,170.33248081841413L933.3333333333334,170.33248081841413L980,170.33248081841413L1026.6666666666665,180.56265984654772L1073.3333333333335,155.26826115061547L1120,124.24046541693643"></path>
            <path  fill="none" stroke="white" opacity="1" d="M0,577.4117647058824L46.666666666666664,577.4117647058824L93.33333333333333,577.4117647058824L140,577.4117647058824L186.66666666666666,577.4117647058824M280,545.3510436432637L326.6666666666667,539.2789373814043L373.3333333333333,539.2789373814043L420,539.2789373814043L466.6666666666667,551.4231499051233L513.3333333333333,551.4231499051233L560,551.4231499051233L606.6666666666666,551.4231499051233L653.3333333333334,479.8498122653327L700,459.8247809762202L746.6666666666666,459.8247809762202L793.3333333333334,459.8247809762202L840,459.8247809762202L886.6666666666666,459.8247809762202L933.3333333333334,479.8498122653327L980,456.43070787637083L1026.6666666666665,459.94397759103623L1073.3333333333335,454.75671750181556L1120,296.39468690702114"></path>
            <path  fill="none" stroke="white" opacity="1" d="M0,438.6554621848753L46.666666666666664,438.6554621848753L93.33333333333333,438.6554621848753L140,357.98319327731105L186.66666666666666,358.34658187599433L233.33333333333334,358.34658187599433L280,141.17647058823525L326.6666666666667,141.17647058823525L373.3333333333333,141.17647058823525L420,141.17647058823525L466.6666666666667,141.17647058823525L513.3333333333333,70.5882352941176L560,70.5882352941176L606.6666666666666,70.5882352941176L653.3333333333334,70.5882352941176L700,70.5882352941176L746.6666666666666,0L793.3333333333334,0L840,0L886.6666666666666,0L933.3333333333334,23.5294117647058L980,23.5294117647058L1026.6666666666665,23.5294117647058L1073.3333333333335,23.5294117647058L1120,23.5294117647058"></path>
        </g>

        <g  style="opacity: 1;">
            <text  text-anchor="left" fill="white" alignment-baseline="middle" x="1120" y="23.5294117647058" style="overflow: visible;">Rwanda:61.25%</text>
            <text  text-anchor="left" fill="white" alignment-baseline="middle" x="1120" y="97.28970086328088" style="overflow: visible;">Cuba:53.41%</text>
            <text  text-anchor="left" fill="white" alignment-baseline="middle" x="1120" y="124.24046541693643" style="overflow: visible;">Nicaragua:50.55%</text>
            <text  text-anchor="left" fill="white" alignment-baseline="middle" x="1120" y="296.39468690702114" style="overflow: visible;">Chad:32.26%</text>
        </g>

    </g>

</svg>

The entire text element for label at the end of line chart is not visible for all of them. For example, for and the entire text elements respectively, Rwanda:61.25% and Nicaragua:50.55% are not visible. But for other two, and then entire text elements respectively Cuba:53.41% and Chad:32.26% are visible.

Is there any way for me to know beforehand, by using javascript to know which text elements will be entirely visible and which will be partially visible.

I tried with getBBox() of each of the text elements but they were not of help to me. I initially thought that if x width is greater than viewbox width than that would be the candidate for partially visible` elements. But it did not work.

document.querySelector('.labelRwanda').getBBox().x document.querySelector('.labelRwanda').getBBox().width;
document.querySelector('.labelCuba').getBBox().x document.querySelector('.labelCuba').getBBox().width;
document.querySelector('.labelNicaragua').getBBox().x document.querySelector('.labelNicaragua').getBBox().width;
document.querySelector('.labelChad').getBBox().x document.querySelector('.labelChad').getBBox().width;

Viz

Update

Based on @Michael Mullany's suggestion, I tried the following where I used getBoundingClientRect() instead of getBBox() so that I don't need to account for any transform coming from the parent element. But I still could not get it to do the job accurately

const parent = document.querySelector('svg').getBoundingClientRect().right;

const ele = document.querySelectorAll('.Label>text');



ele.forEach(
    (a,i)=>{
        const child = a.getBoundingClientRect().right;
        const diff = parent-child;        
        (diff<0)?a.setAttribute('x',`${parseFloat(a.getAttribute('x')) diff}`):null
        
    }
);
<!DOCTYPE html>
<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>

<body>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">

    <rect  width="1280" height="720" fill="black" stroke="bound" style="overflow: visible;"></rect>
    <rect  x="70" y="70" width="1120" height="600" fill="black"></rect>
    <g  style="transform: translate(70px, 70px);">

        <g >
            <path  fill="none" stroke="white" opacity="1" d="M0,385.8783581344254L46.666666666666664,340.04110795732606L93.33333333333333,340.04110795732606L140,340.04110795732606L186.66666666666666,340.04110795732606L233.33333333333334,340.04110795732606L280,261.5473775717186L326.6666666666667,261.5473775717186L373.3333333333333,261.5473775717186L420,261.5473775717186L466.6666666666667,261.5473775717186L513.3333333333333,193.79191415980043L560,193.79191415980043L606.6666666666666,193.79191415980043L653.3333333333334,174.38265408552468L700,174.38265408552468L746.6666666666666,140.17685505574866L793.3333333333334,140.17685505574866L840,140.17685505574866L886.6666666666666,140.17685505574866L933.3333333333334,140.17685505574866L980,99.07632474477359L1026.6666666666665,99.07632474477359L1073.3333333333335,99.07632474477359L1120,97.28970086328088"></path>
            <path  fill="none" stroke="white" opacity="1
            " d="M0,498.798228969007L46.666666666666664,498.798228969007L93.33333333333333,508.9184060721062L140,508.9184060721062M233.33333333333334,405.6265984654729L280,405.6265984654729L326.6666666666667,405.6265984654729L373.3333333333333,405.6265984654729L420,456.7774936061383L466.6666666666667,426.0869565217393L513.3333333333333,426.0869565217393L560,405.6265984654729L606.6666666666666,405.6265984654729L653.3333333333334,221.48337595907955L700,221.48337595907955L746.6666666666666,221.48337595907955L793.3333333333334,201.02301790281317L840,211.25319693094582L886.6666666666666,170.33248081841413L933.3333333333334,170.33248081841413L980,170.33248081841413L1026.6666666666665,180.56265984654772L1073.3333333333335,155.26826115061547L1120,124.24046541693643"></path>
            <path  fill="none" stroke="white" opacity="1" d="M0,577.4117647058824L46.666666666666664,577.4117647058824L93.33333333333333,577.4117647058824L140,577.4117647058824L186.66666666666666,577.4117647058824M280,545.3510436432637L326.6666666666667,539.2789373814043L373.3333333333333,539.2789373814043L420,539.2789373814043L466.6666666666667,551.4231499051233L513.3333333333333,551.4231499051233L560,551.4231499051233L606.6666666666666,551.4231499051233L653.3333333333334,479.8498122653327L700,459.8247809762202L746.6666666666666,459.8247809762202L793.3333333333334,459.8247809762202L840,459.8247809762202L886.6666666666666,459.8247809762202L933.3333333333334,479.8498122653327L980,456.43070787637083L1026.6666666666665,459.94397759103623L1073.3333333333335,454.75671750181556L1120,296.39468690702114"></path>
            <path  fill="none" stroke="white" opacity="1" d="M0,438.6554621848753L46.666666666666664,438.6554621848753L93.33333333333333,438.6554621848753L140,357.98319327731105L186.66666666666666,358.34658187599433L233.33333333333334,358.34658187599433L280,141.17647058823525L326.6666666666667,141.17647058823525L373.3333333333333,141.17647058823525L420,141.17647058823525L466.6666666666667,141.17647058823525L513.3333333333333,70.5882352941176L560,70.5882352941176L606.6666666666666,70.5882352941176L653.3333333333334,70.5882352941176L700,70.5882352941176L746.6666666666666,0L793.3333333333334,0L840,0L886.6666666666666,0L933.3333333333334,23.5294117647058L980,23.5294117647058L1026.6666666666665,23.5294117647058L1073.3333333333335,23.5294117647058L1120,23.5294117647058"></path>
        </g>

        <g  style="opacity: 1;">
            <text  id="labelRwanda" text-anchor="left" fill="white" alignment-baseline="middle" x="1120" y="23.5294117647058" style="overflow: visible;">Rwanda:61.25%</text>
            <text  id="labelCuba" text-anchor="left" fill="white" alignment-baseline="middle" x="1120" y="97.28970086328088" style="overflow: visible;">Cuba:53.41%</text>
            <text  id="labelNicaragua" text-anchor="left" fill="white" alignment-baseline="middle" x="1120" y="124.24046541693643" style="overflow: visible;">Nicaragua:50.55%</text>
            <text  id="labelChad" text-anchor="left" fill="white" alignment-baseline="middle" x="1120" y="296.39468690702114" style="overflow: visible;">Chad:32.26%</text>
        </g>

    </g>

</svg>
</body>

</html>

CodePudding user response:

If you set the text-anchor attribute to "end" all the texts will align to the right. Would that be OK?

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
  <rect  width="1280" height="720" fill="black" stroke="bound"/>
  <rect  x="70" y="70" width="1120" height="600" fill="black"/>
  <g  transform="translate(70 70)">
    <g >
      <path  fill="none" stroke="white" opacity="1" d="M0,385.8783581344254L46.666666666666664,340.04110795732606L93.33333333333333,340.04110795732606L140,340.04110795732606L186.66666666666666,340.04110795732606L233.33333333333334,340.04110795732606L280,261.5473775717186L326.6666666666667,261.5473775717186L373.3333333333333,261.5473775717186L420,261.5473775717186L466.6666666666667,261.5473775717186L513.3333333333333,193.79191415980043L560,193.79191415980043L606.6666666666666,193.79191415980043L653.3333333333334,174.38265408552468L700,174.38265408552468L746.6666666666666,140.17685505574866L793.3333333333334,140.17685505574866L840,140.17685505574866L886.6666666666666,140.17685505574866L933.3333333333334,140.17685505574866L980,99.07632474477359L1026.6666666666665,99.07632474477359L1073.3333333333335,99.07632474477359L1120,97.28970086328088"></path>
      <path  fill="none" stroke="white" opacity="1" d="M0,498.798228969007L46.666666666666664,498.798228969007L93.33333333333333,508.9184060721062L140,508.9184060721062M233.33333333333334,405.6265984654729L280,405.6265984654729L326.6666666666667,405.6265984654729L373.3333333333333,405.6265984654729L420,456.7774936061383L466.6666666666667,426.0869565217393L513.3333333333333,426.0869565217393L560,405.6265984654729L606.6666666666666,405.6265984654729L653.3333333333334,221.48337595907955L700,221.48337595907955L746.6666666666666,221.48337595907955L793.3333333333334,201.02301790281317L840,211.25319693094582L886.6666666666666,170.33248081841413L933.3333333333334,170.33248081841413L980,170.33248081841413L1026.6666666666665,180.56265984654772L1073.3333333333335,155.26826115061547L1120,124.24046541693643"></path>
            <path  fill="none" stroke="white" opacity="1" d="M0,577.4117647058824L46.666666666666664,577.4117647058824L93.33333333333333,577.4117647058824L140,577.4117647058824L186.66666666666666,577.4117647058824M280,545.3510436432637L326.6666666666667,539.2789373814043L373.3333333333333,539.2789373814043L420,539.2789373814043L466.6666666666667,551.4231499051233L513.3333333333333,551.4231499051233L560,551.4231499051233L606.6666666666666,551.4231499051233L653.3333333333334,479.8498122653327L700,459.8247809762202L746.6666666666666,459.8247809762202L793.3333333333334,459.8247809762202L840,459.8247809762202L886.6666666666666,459.8247809762202L933.3333333333334,479.8498122653327L980,456.43070787637083L1026.6666666666665,459.94397759103623L1073.3333333333335,454.75671750181556L1120,296.39468690702114"></path>
            <path  fill="none" stroke="white" opacity="1" d="M0,438.6554621848753L46.666666666666664,438.6554621848753L93.33333333333333,438.6554621848753L140,357.98319327731105L186.66666666666666,358.34658187599433L233.33333333333334,358.34658187599433L280,141.17647058823525L326.6666666666667,141.17647058823525L373.3333333333333,141.17647058823525L420,141.17647058823525L466.6666666666667,141.17647058823525L513.3333333333333,70.5882352941176L560,70.5882352941176L606.6666666666666,70.5882352941176L653.3333333333334,70.5882352941176L700,70.5882352941176L746.6666666666666,0L793.3333333333334,0L840,0L886.6666666666666,0L933.3333333333334,23.5294117647058L980,23.5294117647058L1026.6666666666665,23.5294117647058L1073.3333333333335,23.5294117647058L1120,23.5294117647058"></path>
    </g>
    <g  style="opacity: 1;">
      <text  text-anchor="end" fill="white" alignment-baseline="middle" x="1120" y="23.5294117647058" style="overflow: visible;">Rwanda:61.25%</text>
      <text  text-anchor="end" fill="white" alignment-baseline="middle" x="1120" y="97.28970086328088" style="overflow: visible;">Cuba:53.41%</text>
      <text  text-anchor="end" fill="white" alignment-baseline="middle" x="1120" y="124.24046541693643" style="overflow: visible;">Nicaragua:50.55%</text>
      <text  text-anchor="end" fill="white" alignment-baseline="middle" x="1120" y="296.39468690702114" style="overflow: visible;">Chad:32.26%</text>
    </g>
  </g>
</svg>

CodePudding user response:

A modern (standard) Web Component <svg-linegraph> can help.

To make data entry as easy as possible, we'll do them in HTML

<svg-linegraph>
  <path d="mx y....">Cuba</path>
  <path stroke="red" stroke-width="3" d="mx y....">Nicaragua</path>
</svg-linegraph>

But this means the <path> Elements are now technically an UnknownHTMLElement, and not an SVG path. This is easily fixed by reading all of these <path> and adding the outerHTML to an existing <svg> (that will add the paths in the correct SVG NameSpace)

While parsing all <path> we can set the user defined attributes or defaults.

We are still lazy... Those <path> can be in any sized viewPort. So in one more step we parse the, now valid SVG!!, paths again, get the boundaries of each path, and thus automagically calculate the correct viewPort

Positioning the country label is easy, we get the last point in each <path> and position a <text> on the correct X,Y location. (and gave the viewPort an extra 100 units to draw the labels right of the chart)

All code required to create a <svg-linegraph> Web Component is:

Note: I simplified your original d-path values with: https://yqnn.github.io/svg-path-editor
You can, ofcourse, use any valid d-path

<script>
  customElements.define("svg-linegraph", class extends HTMLElement {
    connectedCallback() {
      setTimeout(() => { // ensure <svg-linegraph> innerHTML is parsed
        this.innerHTML = `<svg style="background:grey">${
          [...this.querySelectorAll("path")].map(path => {
              const cloneAttribute= (x,v)=> path.setAttribute(x, path.getAttribute(x)||v);
              cloneAttribute("fill", "none"); // prefer user defined settings
              cloneAttribute("stroke", "black");
              cloneAttribute("stroke-width", "2");
              return path.outerHTML;
          }).join("")}<g>COUNTRYLABELS GO HERE</g></svg>`;
        let xaxis=[], yaxis=[]; // find viewPort boundaries
        this.querySelector("g").innerHTML = [...this.querySelectorAll("path")].map(path => {
          let {x:labelX,y:labelY} = path.getPointAtLength(path.getTotalLength()); // last path point
          let {x,y,width,height} = path.getBBox();
          xaxis.push(x,x width); yaxis.push(y,y height);
          return `<text fill="red" stroke="black" font-family="Arial" font-size="20px" alignment-baseline="middle"
                        x="${labelX 5}" y="${labelY}">${path.innerHTML}</text>`;
        }).join("");
        xaxis.sort((a,b)=>a-b); yaxis.sort((a,b)=>a-b); // sort by number!
        let viewbox = `${xaxis.at(0)} ${yaxis.at(0)-10} ${xaxis.at(-1) 100} ${yaxis.at(-1)}`;
        this.querySelector("svg").setAttribute("viewBox",viewbox);
      })
    }
  })
</script>
<svg-linegraph>
  <path d="m0 386 47-46 46 0 47 0 47 0 46 0 47-78 47 0 46 0 47 0 47 0 46-68 47 0 47 0 46-20 47 0 47-34 46 0 47 0 47 0 46 0 47-41 47 0 46 0 47-2">Cuba</path>
  <path stroke="red" stroke-width="3" d="m0 499 47 0 46 10 47 0m93-103 47 0 47 0 46 0 47 51 47-31 46 0 47-20 47 0 46-185 47 0 47 0 46-20 47 10 47-41 46 0 47 0 47 11 46-26 47-31">Nicaragua</path>
  <path stroke="red" stroke-width="4" d="m0 577 47 0 46 0 47 0 47 0m93-32 47-6 46 0 47 0 47 12 46 0 47 0 47 0 46-71 47-20 47 0 46 0 47 0 47 0 46 20 47-24 47 4 46-5 47-159">Chad</path>
  <path d="m0 439 47 0 46 0 47-81 47 0 46 0 47-217 47 0 46 0 47 0 47 0 46-70 47 0 47 0 46 0 47 0 47-71 46 0 47 0 47 0 46 24 47 0 47 0 46 0 47 0">Rwanda</path>
</svg-linegraph>

  • Related