1

I am writing some JavaScript which is embedded in an SVG file. This code causes some multiline tooltips to appear when they mouse over a circle. The code is working fairly well at this point except for one thing. The text appears correctly when they first mouse over the circle, but the next time they mouseover, the same text gets appended to the first, so that the text appears twice. I have been trying to figure out how to clear this in my mouseoff function but I am stumped.

Update: I've added the SVG as well, so this is the full file.

<?xml version="1.0" encoding="utf-8"?>
<svg id="oidc" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 422 339.2" overflow="visible">
    <style type="text/css">
    .st13 {
        fill:#d9489b
    }
    .st13:hover {
        cursor:help;
        fill:#E66E31;
    }
    #tooltip {
          dominant-baseline: hanging;
          font-size: 8px;
      }
  </style>
    <g>
        <g>
            <circle class="st13" cx="47.8" cy="69.2" r="6" data-tooltip-text="I am some fairly long text." data-width="150" />
            <circle class="st13" cx="321.2" cy="65.7" r="6" data-tooltip-text="I am some much much much much much much longer text, so long that I cannot discuss or itemize my exact length. It's looooong, very long. I can't say more." data-width="100" />
        </g>
        <g id="tooltip" visibility="hidden" transform="translate(87.9511512134412 127.90914747977598)">
            <rect x="2" y="2" width="52.90066909790039" height="24" fill="black" opacity="0.4" rx="2" ry="2"></rect>
            <rect width="52.90066909790039" height="24" fill="lightblue" rx="2" ry="2"></rect>
            <text x="4" y="6">A box</text>
        </g>
    </g>
  <script type="text/javascript"><![CDATA[

  (function () {
  var svg = document.getElementById("oidc");
  var tooltip = svg.getElementById("tooltip");
  var tooltipRects = tooltip.getElementsByTagName("rect");
  var triggers = svg.getElementsByClassName("st13");
  var tooltipText = tooltip.getElementsByTagName("text")[0];

  // Add listeners
  for (var i = 0; i < triggers.length; i++) {
    triggers[i].addEventListener("mouseover", showTooltip);
    triggers[i].addEventListener("mouseout", hideTooltip);
  }

  function showTooltip(evt) {

    var CTM = svg.getScreenCTM();
    var x = (evt.clientX - CTM.e + 6) / CTM.a;
    var tspanX = tooltipText.getAttributeNS(null, 'x');
    var y = (evt.clientY - CTM.f + 20) / CTM.d;
    tooltip.setAttributeNS(null, "transform", "translate(" + x + " " + y + ")");
    tooltip.setAttributeNS(null, "visibility", "visible");

    // Sets variable containing data-width as float
    var width = parseFloat(evt.target.getAttributeNS(null, "data-width"));

    // Replaces text Element string with string from st13
    tooltipText.firstChild.data = evt.target.getAttributeNS(null, "data-tooltip-text");

    // Convert string to array of words
    var words = tooltipText.firstChild.data.split(' ');

    // Clear original text
    tooltipText.firstChild.data = "";

    // Create empty tspan element
    var tspanElement = document.createElementNS("http://www.w3.org/2000/svg", "tspan");

    // Create text node containing a word
    var textNode = document.createTextNode(words[0]);

    // Add tspan element to DOM
    tspanElement.appendChild(textNode);

    // Add text to tspan element
    tooltipText.appendChild(tspanElement);

    for (var i = 1; i < words.length; i++) {
      var len = textNode.data.length;

      // Add next word
      tspanElement.firstChild.data += " " + words[i];

      if (tspanElement.getComputedTextLength() > width) {
        // Remove added word
        tspanElement.firstChild.data = tspanElement.firstChild.data.slice(0, len);

        // Create new tspan element
        tspanElement = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
        tspanElement.setAttributeNS(null, "x", tspanX);
        tspanElement.setAttributeNS(null, "dy", 10);
        textNode = document.createTextNode(words[i]);
        tspanElement.appendChild(textNode);
        tooltipText.appendChild(tspanElement);
      }
    }

    var bbox = tooltipText.getBBox();
    var textWidth = bbox.width;
    for (var i = 0; i < tooltipRects.length; i++) {
      tooltipRects[i].setAttributeNS(null, "width", textWidth + 8);
    }

    var textHeight = bbox.height;
    for (var i = 0; i < tooltipRects.length; i++) {
      tooltipRects[i].setAttributeNS(null, "height", textHeight + 8);
    }

  }

  function hideTooltip(evt) {
    tooltip.setAttributeNS(null, "visibility", "hidden");
  }
  })()


  ]]>
  </script>
</svg>
ehmon
  • 151
  • 1
  • 10
  • Can you add the svg please? – enxaneta Apr 15 '19 at 08:03
  • OK yes, no problem. I've updated the code block above. It now contains the full SVG file – ehmon Apr 15 '19 at 16:31
  • 1
    If the text of the tooltip doesn't change, you don't need to clear the text. The problem is that you are creating new `tspan` elements on mouseover. I would move this part outside the function `showTooltip`. However if you really want to empty the text this answer may be helpful (option 2) https://stackoverflow.com/questions/3955229/remove-all-child-elements-of-a-dom-node-in-javascript – enxaneta Apr 15 '19 at 16:54
  • Thank you, I will look into these ideas. – ehmon Apr 15 '19 at 17:41

2 Answers2

1

Thanks for the tips. I finally managed to clear the text as follows

  function hideTooltip(evt) {
    tooltip.setAttributeNS(null, "visibility", "hidden");
    var text = svg.getElementById('text');
    while(text.firstChild) {
      text.removeChild(text.firstChild);
    }
    var str = "A box";
    text.innerHTML = str;
  }
ehmon
  • 151
  • 1
  • 10
0

Remove all the tspan elements your showTooltip function is adding, and don't clear the firstChild.data. You will need it when the next event takes place.

function hideTooltip(evt) {
  console.clear();
  console.log('Mouse out');
  tooltip.setAttributeNS(null, "visibility", "hidden");
  // tooltipText.firstChild.data = "";
  tooltip.querySelector("text").innerHTML = "<tspan>";
  console.log(tooltip);
}

In any case, this seems like an overkill to show a tooltip. You may want to write down on a piece of paper, with bullet-points, exactly what you want to happen in your document and when, regardless of the way you may implement it.

This will keep your goals simple and concise, so your code will be much shorter and clearer.

  • Thank you! This works to clear the text for the first mouseover, but when I go to the second circle, I see the appended text again. :( It also gives me this error: `Uncaught DOMException: Failed to set the 'innerHTML' property on 'Element': The provided markup is invalid XML, and therefore cannot be inserted into an XML document. at SVGCircleElement.hideTooltip` I have just put a small fraction of my SVG here on this board, but believe me, I do need the tooltips. :D – ehmon Apr 15 '19 at 17:40