LOADING...

Preview

Pen ID
Unlock Campus Themeforest adv

 

Code

Positioning a Tooltip on a SVG

SVG graphics are made up of DOM elements, and can respond to user events: for example, a mouseover event listener can be used to trigger a tooltip display. But getting that tooltip where you want it is complicated by the complex SVG coordinate system. Mousing over any of the coloured circles below will cause six tooltips to appear (seven including the browser's title text), each positioned with a different method.

SVG/CSS Tip SVG/CSS Tip SVG/CSS Tip SVG/CSS Tip SVG/CSS Tip SVG/CSS Tip SVG Tip Mouse-tracking SVG Tip
Absolute HTML Tip
Fixed HTML Tip
Mouse-tracking HTML Tip

A quick run-down of the pros and cons of each type of tooltip:

Title text tooltips
Pro: Quick and easy, just insert a <title> element within the SVG element that you want to trigger the tooltip. (Some browsers will display a tooltip if you use the title attribute, similar to how HTML title tooltips work, but Webkit browsers require you to use an SVG <title> element.) They are also semantic and screen-reader friendly and can be hard-coded in Javascript-free SVG.
Con: Ugly and out of your control.
SVG tooltips
Pro: Contained within your SVG code, scale with the SVG.
Con: An opaque background requires a separate rectangle element, which is difficult to size properly with the text while remaining responsive to user's font preferences (although you can query the text length with Javascript, and size the rectangle accordingly, I didn't do that here); line breaks have to be hard-coded as separately-positioned text spans. Webkit browsers don't respect "overflow:visible" on SVG, so the tip will be cropped if it overlaps the edge of the image.
HTML tooltips
Pro: The preferred choice; an absolutely positioned <div> that can contain formatted, wrapping text.
Con: You have to figure out how to convert between the SVG coordinate system and the page coordinates you use for absolutely positioning your tooltip. But that's why you're here. It's all commented up in the code!

And the pros and cons of each positioning method:

CSS hover-revealed tooltips
Pro: No Javascript required, position them where you want them when you create the SVG graphic.
Con: Requires a separate tooltip for every object, so your code gets repetitive. The tip has to be a sibling of the element that triggers it, so it has to be an SVG tip, with the corresponding formatting limitations. For pro or con, it will also be caught up in any transformations (e.g. rotations, scale) in that coordinate system.
Mouse-tracking tooltips
Pro: Easier to position, and the user can move the mouse if they block something underneath.
Con: Kind of annoying, floating around difficult to read. More importantly, they react to every mousemove event, slowing down the code.
Positioned tooltips
Pro: Neat and exact, and you can position them in an appropriate location relative to the underlying content.
Con: If the user doesn't agree with your determination of the appropriate position, they can't do anything about it.

If you want to confirm that the positions of the tooltips adjust to the current state of the SVG, press "Wiggle the SVG" to randomly shift and tilt the circles or resize the browser to change the SVG scale. Note, however, that if you have any tooltips visible during the shift, I don't currently have any code to update their positions as things move.

P.S. If you're in "Edit pen" view and are only getting the CSS tooltip, press the big blue "Run" button on the Javascript panel.

CSS
.tooltip {
    pointer-events:none; /*let mouse events pass through*/
    opacity:0;
    transition: opacity 0.3s;
    text-shadow:1px 1px 0px gray;
}

div.tooltip {
    background: lightblue;
    border:solid gray;
    position: absolute;
    max-width: 8em;
    text-align:center;
}
div.fixed {
    position:fixed;
}
g.tooltip:not(.css) {
  fill:currentColor;
}
g.tooltip rect {
    fill: lightblue;
    stroke: gray;
}
circle:hover + g.tooltip.css {
  opacity:1;
}
svg {
    margin:10px 20px;
    width:calc(100% - 40px);
    height:500px;
    max-height:100%;
    border: solid 1px black;
    background-color: #222;
    overflow:visible; 
    /*allow tooltips to spill into margins */
}
button{
  display:block;
  margin: 0 auto 20px;
  padding: 0.5em;
  box-shadow: 0 0 2px 2px navy;
}

h1{
  text-align:center;

}
body {
  max-width: 40em;
  margin-left:auto;
  margin-right:auto;
  background: lightyellow;
}
p, dl {
  margin: 0.5em 1.5em;
}
dt {
  font-weight:bold;
  display:run-in;
  padding-right:1em;
}
JS
var tooltip = d3.selectAll(".tooltip:not(.css)");
var HTMLabsoluteTip = d3.select("div.tooltip.absolute");
var HTMLfixedTip = d3.select("div.tooltip.fixed");
var HTMLmouseTip = d3.select("div.tooltip.mouse");
var SVGexactTip = d3.select("g.tooltip.exact");
var SVGmouseTip = d3.select("g.tooltip.mouse");
/* If this seems like a lot of different variables,
   remember that normally you'd only implement one 
   type of tooltip! */

/* I'm using d3 to add the event handlers to the circles
   and set positioning attributes on the tooltips, but
   you could use JQuery or plain Javascript. */
var circles = d3.select("svg").select("g")
    .selectAll("circle");

    /***** Easy but ugly tooltip *****/ 
circles.append("title")
      .text("Automatic Title Tooltip");

circles.on("mouseover", function () {
        tooltip.style("opacity", "1");
      
        /* You'd normally set the tooltip text
           here, based on data from the  element
           being moused-over; I'm just setting colour. */
        tooltip.style("color", this.getAttribute("fill") );
      /* Note: SVG text is set in CSS to link fill colour to 
         the "color" attribute. */
      
      
        /***** Positioning a tooltip precisely
               over an SVG element *****/ 
        
        /***** For an SVG tooltip *****/ 
        
        //"this" in the context of this function
        //is the element that triggered this event handler
        //which will be one of the circle elements.
        var tooltipParent = SVGexactTip.node().parentNode;
        var matrix = 
                this.getTransformToElement(tooltipParent)
                    .translate(+this.getAttribute("cx"),
                         +this.getAttribute("cy"));
        
        //getTransformToElement returns a matrix
        //representing all translations, rotations, etc.
        //to convert between two coordinate systems.
        //The .translate(x,y) function adds an additional 
        //translation to the centre of the circle.
        
        //the matrix has values a, b, c, d, e, and f
        //we're only interested in e and f
        //which represent the final horizontal and vertical
        //translation between the top left of the svg and 
        //the centre of the circle.
        
        //we get the position of the svg on the page
        //using this.viewportElement to get the SVG
        //and using offsetTop and offsetLeft to get the SVG's
        //position relative to the page.
        SVGexactTip
            .attr("transform", "translate(" + (matrix.e)
                      + "," + (matrix.f-20) + ")");
        
        /***** For an HTML tooltip *****/ 
        
        //for the HTML tooltip, we're not interested in a
        //transformation relative to an internal SVG coordinate
        //system, but relative to the page body
        
        //We can't get that matrix directly,
        //but we can get the conversion to the
        //screen coordinates.
        
        var matrix = this.getScreenCTM()
                .translate(+this.getAttribute("cx"),
                         +this.getAttribute("cy"));
        
        //You can use screen coordinates directly to position
        //a fixed-position tooltip        
        HTMLfixedTip 
            .style("left", 
                   (matrix.e) + "px")
            .style("top",
                   (matrix.f + 3) + "px");
        //The limitation of fixed position is that it won't
        //change when scrolled.
        
        //A better solution is to calculate the position 
        //of the page on the screen to position an 
        //absolute-positioned tooltip:
        HTMLabsoluteTip
            .style("left", 
                   (window.pageXOffset + matrix.e) + "px")
            .style("top",
                   (window.pageYOffset + matrix.f + 30) + "px");
        
    })
    .on("mousemove", function () {
        
        /***** Positioning a tooltip using mouse coordinates *****/ 
      
        /* The code is shorter, but it runs every time
           the mouse moves, so it could slow down other
           processes or animation. */
        
        /***** For an SVG tooltip *****/ 
       
        var mouseCoords = d3.mouse(
            SVGmouseTip.node().parentNode);
        //the d3.mouse() function calculates the mouse
        //position relative to an SVG Element, in that 
        //element's coordinate system 
        //(after transform or viewBox attributes).
        
        //Because we're using the coordinates to position
        //the SVG tooltip, we want the coordinates to be
        //with respect to that element's parent.
        //SVGmouseTip.node() accesses the (first and only)
        //selected element from the saved d3 selection object.
        
        SVGmouseTip
            .attr("transform", "translate("
                  + (mouseCoords[0]-10) + "," 
                  + (mouseCoords[1] - 10) + ")");
        
        /***** For an HTML tooltip *****/ 
      
        //mouse coordinates relative to the page as a whole
        //can be accessed directly from the click event object
        //(which d3 stores as d3.event)
        HTMLmouseTip
            .style("left", Math.max(0, d3.event.pageX - 150) + "px")
            .style("top", (d3.event.pageY + 20) + "px");
    })
    .on("mouseout", function () {
        return tooltip.style("opacity", "0");
    });

var circleGroup = d3.select("g#circle-group");
d3.select("button#wiggle").on("click", function() {
    circleGroup.transition().duration(1000)
        .attr("transform",
              "rotate("+ (20*(Math.random()-0.5)) + ")"
              +"translate(" + (20*(Math.random()-0.5)) +","
              + (20*(Math.random()-0.5)) + ")"
              );
});
Host Instantly Drag and Drop Website Builder

 

Description

All the ways to put some extra information right next to your fabulous graphics. Or your random coloured circles, as the case may be. Implemented with d3.js, but the key aspects use basic Javascript methods. A consolidation of a set of fiddles that I created in response to a Stack Overflow question. UPDATED to include a CSS-only tooltip, and to have the tips' colour match the object that triggered them.
Term
Mon, 11/27/2017 - 21:56

Related Codes

Pen ID
Pen ID
Pen ID