Frage

I am using html5 canvas element to draw a graph with dots denoting various points in here.

I want to display different tool-tip on different points on mouse hover.the text to be displayed as tool-tip will be provided by the user.

I tried but couldn't figure out how to add tool-tip to various points in the graph.The code I'm using for displaying dots is..

// Draw the dots
c.fillStyle = '#333';

for (var i = 0; i < data.values.length; i++) {
  c.beginPath();
  c.arc(getXPixel(data.values[i].X), getYPixel(data.values[i].Y), 4, 0, Math.PI * 2, true);
  c.fill();
}

What addition should I make in this code so that i am able to display user input as tool-tip?

War es hilfreich?

Lösung

You can display tooltips when your user moves over your chart's data-dot

This tooltip is simply a second canvas which draws the text from the linked textbox and is positions itself above the data-dot.

First you create an array to hold the tooltip info for each of your data-dots.

    var dots = [];

For each tooltip, you will need:

  • The x/y coordinate of the data-dot,
  • The radius of the data-dot,
  • The id of the textbox you want to get the tip from.
  • You also need rXr which always == radius squared (needed during hit testing)

Here is the code for creating tooltip info to be stored in dots[]

    // define tooltips for each data point

    for(var i = 0; i < data.values.length; i ++) {
        dots.push({
            x: getXPixel(data.values[i].X),
            y: getYPixel(data.values[i].Y),
            r: 4,
            rXr: 16,
            tip: "#text"+(i+1)
        });
    }

Then you set up a mousemove handler that looks through the dots array. The tooltip is displayed if the user moves inside any data=dot:

    // request mousemove events

    $("#graph").mousemove(function(e){handleMouseMove(e);});

    // show tooltip when mouse hovers over dot
    function handleMouseMove(e){
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);

      // Put your mousemove stuff here
      var hit = false;
      for (var i = 0; i < dots.length; i++) {
          var dot = dots[i];
          var dx = mouseX - dot.x;
          var dy = mouseY - dot.y;
          if (dx * dx + dy * dy < dot.rXr) {
              tipCanvas.style.left = (dot.x) + "px";
              tipCanvas.style.top = (dot.y - 40) + "px";
              tipCtx.clearRect(0, 0, tipCanvas.width, tipCanvas.height);
              tipCtx.fillText($(dot.tip).val(), 5, 15);
              hit = true;
          }
      }
      if (!hit) { tipCanvas.style.left = "-200px"; }
    }

[ Edited to fit into your code ]

Here is code and a Fiddle: http://jsfiddle.net/m1erickson/yLBjM/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body{ background-color: ivory; margin-top:35px; }
    #wrapper{position:relative; width:300px; height:150px;}
    canvas{border:1px solid red;}
    #tip{background-color:white; border:1px solid blue; position:absolute; left:-200px; top:100px;}
</style>

<script>
$(function(){

    var graph = document.getElementById("graph");
    var ctx = graph.getContext("2d");
    var tipCanvas = document.getElementById("tip");
    var tipCtx = tipCanvas.getContext("2d");

    var canvasOffset = $("#graph").offset();
    var offsetX = canvasOffset.left;
    var offsetY = canvasOffset.top;

    var graph;
    var xPadding = 30;
    var yPadding = 30;

    // Notice I changed The X values
    var data = { values:[
        { X: 0, Y: 12 },
        { X: 2, Y: 28 },
        { X: 3, Y: 18 },
        { X: 4, Y: 34 },
        { X: 5, Y: 40 },
        { X: 6, Y: 80 },
        { X: 7, Y: 80 }
    ]};

    // define tooltips for each data point
    var dots = [];
    for(var i = 0; i < data.values.length; i ++) {
        dots.push({
            x: getXPixel(data.values[i].X),
            y: getYPixel(data.values[i].Y),
            r: 4,
            rXr: 16,
            color: "red",
            tip: "#text"+(i+1)
        });
    }

    // request mousemove events
    $("#graph").mousemove(function(e){handleMouseMove(e);});

    // show tooltip when mouse hovers over dot
    function handleMouseMove(e){
      mouseX=parseInt(e.clientX-offsetX);
      mouseY=parseInt(e.clientY-offsetY);

      // Put your mousemove stuff here
      var hit = false;
      for (var i = 0; i < dots.length; i++) {
          var dot = dots[i];
          var dx = mouseX - dot.x;
          var dy = mouseY - dot.y;
          if (dx * dx + dy * dy < dot.rXr) {
              tipCanvas.style.left = (dot.x) + "px";
              tipCanvas.style.top = (dot.y - 40) + "px";
              tipCtx.clearRect(0, 0, tipCanvas.width, tipCanvas.height);
              tipCtx.fillText($(dot.tip).val(), 5, 15);
              hit = true;
          }
      }
      if (!hit) { tipCanvas.style.left = "-200px"; }
    }

// unchanged code follows

    // Returns the max Y value in our data list
    function getMaxY() {
        var max = 0;

        for(var i = 0; i < data.values.length; i ++) {
            if(data.values[i].Y > max) {
                max = data.values[i].Y;
            }
        }

        max += 10 - max % 10;
        return max;
    }

    // Returns the max X value in our data list
    function getMaxX() {
        var max = 0;

        for(var i = 0; i < data.values.length; i ++) {
            if(data.values[i].X > max) {
                max = data.values[i].X;
            }
        }

        // omited
      //max += 10 - max % 10;
        return max;
    }

    // Return the x pixel for a graph point
    function getXPixel(val) {
        // uses the getMaxX() function
        return ((graph.width - xPadding) / (getMaxX() + 1)) * val + (xPadding * 1.5);
        // was
      //return ((graph.width - xPadding) / getMaxX()) * val + (xPadding * 1.5);
    }

    // Return the y pixel for a graph point
    function getYPixel(val) {
        return graph.height - (((graph.height - yPadding) / getMaxY()) * val) - yPadding;
    }

        graph = document.getElementById("graph");
        var c = graph.getContext('2d');            

        c.lineWidth = 2;
        c.strokeStyle = '#333';
        c.font = 'italic 8pt sans-serif';
        c.textAlign = "center";

        // Draw the axises
        c.beginPath();
        c.moveTo(xPadding, 0);
        c.lineTo(xPadding, graph.height - yPadding);
        c.lineTo(graph.width, graph.height - yPadding);
        c.stroke();

        // Draw the X value texts
        var myMaxX = getMaxX();
        for(var i = 0; i <= myMaxX; i ++) {
            // uses data.values[i].X
            c.fillText(i, getXPixel(i), graph.height - yPadding + 20);
        }
        /* was
        for(var i = 0; i < data.values.length; i ++) {
            // uses data.values[i].X
            c.fillText(data.values[i].X, getXPixel(data.values[i].X), graph.height - yPadding + 20);
        }
        */

        // Draw the Y value texts
        c.textAlign = "right"
        c.textBaseline = "middle";

        for(var i = 0; i < getMaxY(); i += 10) {
            c.fillText(i, xPadding - 10, getYPixel(i));
        }

        c.strokeStyle = '#f00';

        // Draw the line graph
        c.beginPath();
        c.moveTo(getXPixel(data.values[0].X), getYPixel(data.values[0].Y));
        for(var i = 1; i < data.values.length; i ++) {
            c.lineTo(getXPixel(data.values[i].X), getYPixel(data.values[i].Y));
        }
        c.stroke();

        // Draw the dots
        c.fillStyle = '#333';

        for(var i = 0; i < data.values.length; i ++) {  
            c.beginPath();
            c.arc(getXPixel(data.values[i].X), getYPixel(data.values[i].Y), 4, 0, Math.PI * 2, true);
            c.fill();
        }


}); // end $(function(){});
</script>

</head>

<body>
    <div id="wrapper">
        <canvas id="graph" width=300 height=150></canvas>
        <canvas id="tip" width=100 height=25></canvas>
    </div>
    <br><br>
    <input type="text" id="text1" value="text 1"/><br><br>
    <input type="text" id="text2" value="text 2"/><br><br>
    <input type="text" id="text3" value="text 3"/><br><br>
    <input type="text" id="text4" value="text 4"/><br><br>
    <input type="text" id="text5" value="text 5"/><br><br>
    <input type="text" id="text6" value="text 6"/><br><br>
    <input type="text" id="text7" value="text 7"/><br><br>
</body>
</html>

Andere Tipps

I tried markE's solution and it worked flawlessly, EXCEPT that when you scroll down just a little bit (e.g. when you have your canvas a little down the site).

Then the positions where your mouseover is recognized will shift to the bottom the same length and it could happen that they end up outside of the canvas and will not be recognized at all...

When you use mouseEvent.pageX and mouseEvent.pageY instead of .clientX and .clientY, you should be fine. For more context, here is my code:

// Filling the dots
var dots = [];
// [...]
dots.push({
    x: xCoord,
    y: yCoord,
    v: value,
    r: 5,
    tooltipRadius2: 7*7 // a little increased radius for making it easier to hit
});
// [...]

var tooltipCanvas = $('#tooltipCanvas')[0];
var tooltipCtx = tooltipCanvas.getContext('2d');
var canvasOffset = canvas.offset();
canvas.mousemove(function (e) {

    // getting the mouse position relative to the page - not the client
    var mouseX = parseInt(e.pageX - canvasOffset.left);
    var mouseY = parseInt(e.pageY - canvasOffset.top);

    var hit = false;
    for (var i = 0; i < dots.length; i++) {
        var dot = dots[i];
        var dx = mouseX - dot.x;
        var dy = mouseY - dot.y;
        if (dx * dx + dy * dy < dot.tooltipRadius2) {
            // show tooltip to the right and below the cursor
            // and moving with it
            tooltipCanvas.style.left = (e.pageX + 10) + "px";
            tooltipCanvas.style.top = (e.pageY + 10) + "px";
            tooltipCtx.clearRect(0, 0, tooltipCanvas.width, tooltipCanvas.height);
            tooltipCtx.textAlign = "center";
            tooltipCtx.fillText(dot.v, 20, 15);
            hit = true;
            // when a dot is found, don't keep on searching
            break;
        }
    }
    if (!hit) {
        tooltipCanvas.style.left = "-200px";
    }
});

Maybe you could play with the "title" attribute of graph, and adapt its contents depending on the mouse position. Try adding this handler to your fiddle code:

    graph.addEventListener("mousemove", (function(evt) {
            var rect = evt.target.getBoundingClientRect();
            var x = evt.clientX - rect.left;
            var y = evt.clientY - rect.top;
            var xd, yd;

            graph.title = "";
            for(var i = 0; i < data.values.length; i ++) {
                xd = getXPixel(data.values[i].X);
                yd = getYPixel(data.values[i].Y);
                if ((x > xd-5) && (x < xd+5) &&(y > yd-5) && (y < yd+5) ) {
                    graph.title = document.getElementById("text"+(i+1)).value;
                    break;
                }
            }
    }), false);

See here: Updated fiddle

Edit: in the code above, I choose to display the tooltip if the mouse is in a square of 10x10 around the point. Of course, this can be adapted. Moreover, there is probably more tests to do, especially before calling the value on getElementById, which could potentially return null.

Thank you very much for the answers above as they helped me formulate my own solution, so I would like to contribute to those who are trying to do the same.

HTML

<!-- https://jsfiddle.net/7kj2g5ur/ -->
<!-- https://stackoverflow.com/questions/17064913/display-tooltip-in-canvas-graph -->
<!-- https://stackoverflow.com/questions/1038727/how-to-get-browser-width-using-javascript-code -->
<!-- http://phrogz.net/tmp/canvas_zoom_to_cursor.html -->
<!-- https://www.w3schools.com/jsref/jsref_regexp_newline.asp -->
<!-- https://stackoverflow.com/questions/17064913/display-tooltip-in-canvas-graph -->
<!-- https://stackoverflow.com/questions/1134586/how-can-you-find-the-height-of-text-on-an-html-canvas -->
<!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>V5</title>
    <link href="style.css" rel="stylesheet">
</head>
<body>
    <canvas id="canvas" width="1000" height="500"></canvas>
    <script src="functions.js"></script>
    <script src="script.js"></script>
</body>
</html>

CSS

body { 
    background:#eee;
    /* text-align:center; */
    margin: 0;
    padding: 0;
}

#canvas { 
    display:block; 
    background:#fff;
    border:1px solid #ccc;
}

JS

/**
 * Mensura a largura e altura da fonte
 * @param {*} text 
 * @param {*} font 
 * @returns 
 */
function measureText(text, font) {
    const span = document.createElement('span');
    span.appendChild(document.createTextNode(text));
    Object.assign(span.style, {
        font: font,
        margin: '0',
        padding: '0',
        border: '0',
        whiteSpace: 'nowrap'
    });
    document.body.appendChild(span);
    const {width, height} = span.getBoundingClientRect();
    span.remove();
    return {width, height};
}

/**
 * Desenha o ponto na tela
 * @param {*} context 
 * @param {*} x 
 * @param {*} y 
 * @param {*} circleRadius 
 * @param {*} fillStyle 
 * @param {*} labels 
 */
function drawPoint(context,x,y,circleRadius,fillStyle,labels=null) {

    context.beginPath();

    context.fillStyle=fillStyle;

    var point = new Path2D();

    point.arc(x,y, circleRadius, 0, 2 * Math.PI);

    context.fill(point);
    
    if(labels)
    {
        drawTooltips(context,x,y,labels);
    }
    context.closePath();
}

/**
 * Desenha o tooltip na tela
 * @param {*} context 
 * @param {*} x 
 * @param {*} y 
 * @param {*} label 
 * @param {*} alignY 
 */
function drawTooltip(context,x,y,label,alignY=10) {

    const { width, height } = measureText(label, '8px Arial, Helvetica, sans-serif');

    const reactWidth = width + 10;
    const reactHeight = height + 10;
    const reactX = x+12;
    const reactY = y-alignY;
    const labelX = reactX+((reactWidth-width)/2);
    const labelY = reactY+12;

    context.beginPath();

    context.fillStyle = "black";

    context.fillRect(reactX,reactY,reactWidth,reactHeight);
    
    context.font = '8px Arial, Helvetica, sans-serif';

    context.strokeStyle = 'white';

    context.strokeText(label,labelX,labelY);

    context.closePath();
}

/**
 * Desenha todos os tooltips na tela
 * @param {*} context 
 * @param {*} x 
 * @param {*} y 
 * @param {*} labels 
 */
function drawTooltips(context,x,y,labels) {
    for (const key in labels) {
        if (Object.hasOwnProperty.call(labels, key)) {
            const label = labels[key];
            drawTooltip(context,x,y+(key*20),label,labels.length*10);
        }
    }
}

const canvas = document.getElementById('canvas');

const context = canvas.getContext('2d');

drawPoint(context,50,50,5,'black',[
    'LOREM 1',
    'Lorem ipsum dolor sit amet',
    'View more'
]);

drawPoint(context,150,100,5,'black',[
    'LOREM 2',
    'Lorem ipsum dolor sit amet',
    'View more'
]);

Short answer: as you've done it now, you can't.

Long answer: you can, but you need to get the exact mouse position every 30milliseconds or so. For each millisecond, you must check if the mouse is hovering over the dot, re-draw the screen and show the tooltip if he's doing it. Doing so by yourself can be tedious, this is why I use gee.js.

Check out this example: http://jsfiddle.net/Saturnix/Aexw4/

This is the expression which controls the mouse hovering:

g.mouseX < x + r && g.mouseX > x -r && g.mouseY > y -r && g.mouseY < y+r

CSS ONLY method here. No javascript, JQUERY, or special library required. Lightweight, sexy.

HTML

<!DOCTYPE html>
<body>
    <br />
    <br />
    <br />
    <br />
<span class="popup" popupText="This is some popup text">Locate </span>
<img src="http://upload.wikimedia.org/wikipedia/en/thumb/f/f4/The_Scream.jpg/220px-The_Scream.jpg"
    />
<!--I used an image here but it could be anything, including a canvas-->
</body>
</html>

CSS

.popup{
    position:absolute; /*allows span to be on top of image*/
    border: solid; /*a border, just for demonstration purposes*/
    border-color: #333 ;
    border-width: 1px;
    width:220px; /*set the height, width equal to the size of your ing/canvas*/
    height:280px;
}
/*this bit draws the bottom arrow of the popup, it can excluded entire if you don't want it*/
.popup:hover:before{
    border: solid;
    border-color: #333 transparent;
    border-width: 6px 6px 0 6px;
    bottom: 300px;
    content: "";
    left: 40px;
    position: absolute;
    z-index: 99;
}
/*this bit draw the main portion of the popup, including the text*/
.popup:hover:after{
    background: #333;
    background: rgba(0,0,0,.8);
    border-radius: 5px;
    bottom: 306px;
    color: #fff;
    content: attr(popupText); /*this is where the text gets introduced.*/
    left: 20px;
    padding: 5px 15px;
    position: absolute;
    z-index: 98;
    width: 150px;
}

Here is the fiddle. http://jsfiddle.net/honkskillet/RKnEu/

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top