如何在图形的悬停转折点上显示Y轴值

时间:2015-08-07 10:24:28

标签: javascript jquery html canvas graph

我正在使用java脚本和画布制作图表。下面是jsfiddle链接

http://jsfiddle.net/ghan_123/n0r796ae/

我想在悬停气泡(转折点)上显示值,如

1月8日的价格2100

java脚本

var CanvasChart = function () {
    var ctx;
    var margin = { top: 40, left: 75, right: 0, bottom: 75 };
    var chartHeight, chartWidth, yMax, xMax, data;
    var maxYValue = 0;
    var ratio = 0;
    var renderType = { lines: 'lines', points: 'points' };

    var render = function(canvasId, dataObj) {
        data = dataObj;
        getMaxDataYValue();
        var canvas = document.getElementById(canvasId);
        chartHeight = canvas.getAttribute('height');
        chartWidth = canvas.getAttribute('width');
        xMax = chartWidth - (margin.left + margin.right);
        yMax = chartHeight - (margin.top + margin.bottom);
        ratio = yMax / maxYValue;
        ctx = canvas.getContext("2d");
        renderChart();
    };

    var renderChart = function () {
        renderBackground();
        renderText();
        renderLinesAndLabels();

        //render data based upon type of renderType(s) that client supplies
        if (data.renderTypes == undefined || data.renderTypes == null) data.renderTypes = [renderType.lines];
        for (var i = 0; i < data.renderTypes.length; i++) {
            renderData(data.renderTypes[i]);
        }
    };

    var getMaxDataYValue = function () {
        for (var i = 0; i < data.dataPoints.length; i++) {
            if (data.dataPoints[i].y > maxYValue) maxYValue = data.dataPoints[i].y;
        }
    };

    var renderBackground = function() {
        var lingrad = ctx.createLinearGradient(margin.left, margin.top, xMax - margin.right, yMax);
        lingrad.addColorStop(0.0, '#ffffff');
        lingrad.addColorStop(0.2, '#fff');
        lingrad.addColorStop(0.8, '#fff');
        lingrad.addColorStop(1, '#ffffff');
        ctx.fillStyle = lingrad;
        ctx.fillRect(margin.left, margin.top, xMax - margin.left, yMax - margin.top);
        ctx.fillStyle = '#6A6A6A';
    };

    var renderText = function() {
        var labelFont = (data.labelFont != null) ? data.labelFont : '20pt Arial';
        ctx.font = labelFont;
        ctx.textAlign = "center";

        //Title
        var txtSize = ctx.measureText(data.title);
        ctx.fillText(data.title, (chartWidth / 2), (margin.top / 2));

        //X-axis text
        txtSize = ctx.measureText(data.xLabel);
        ctx.fillText(data.xLabel, margin.left + (xMax / 2) - (txtSize.width / 2), yMax + (margin.bottom / 1.2));

        //Y-axis text
        ctx.save();
        ctx.rotate(-Math.PI / 2);
        ctx.font = labelFont;
        ctx.fillText(data.yLabel, (yMax / 2) * -1, margin.left / 4);
        ctx.restore();
    };

    var renderLinesAndLabels = function () {
        //Vertical guide lines
        var yInc = yMax / data.dataPoints.length;
        var yPos = 0;
        var yLabelInc = (maxYValue * ratio) / data.dataPoints.length;
        var xInc = getXInc();
        var xPos = margin.left;
        for (var i = 0; i < data.dataPoints.length; i++) {
            yPos += (i == 0) ? margin.top : yInc;
            //Draw horizontal lines
            drawLine(margin.left, yPos, xMax, yPos, '#E8E8E8');

            //y axis labels
            ctx.font = (data.dataPointFont != null) ? data.dataPointFont : '10pt Calibri';
            var txt = Math.round(maxYValue - ((i == 0) ? 0 : yPos / ratio));
            var txtSize = ctx.measureText(txt);
            ctx.fillText(txt, margin.left - ((txtSize.width >= 14) ? txtSize.width : 10) - 7, yPos + 4);

            //x axis labels
            txt = data.dataPoints[i].x;
            txtSize = ctx.measureText(txt);
            ctx.fillText(txt, xPos, yMax + (margin.bottom / 3));
            xPos += xInc;
        }

        //Vertical line
        drawLine(margin.left, margin.top, margin.left, yMax, 'black');

        //Horizontal Line
        drawLine(margin.left, yMax, xMax, yMax, 'black');
    };

    var renderData = function(type) {
        var xInc = getXInc();
        var prevX = 0, 
            prevY = 0;

        for (var i = 0; i < data.dataPoints.length; i++) {
            var pt = data.dataPoints[i];
            var ptY = (maxYValue - pt.y) * ratio;
            if (ptY < margin.top) ptY = margin.top;
            var ptX = (i * xInc) + margin.left;

            if (i > 0 && type == renderType.lines) {
                //Draw connecting lines
                drawLine(ptX, ptY, prevX, prevY, '#309FB2', 2);
            }

            if (type == renderType.points) {
                var radgrad = ctx.createRadialGradient(ptX, ptY, 8, ptX - 5, ptY - 5, 0);
                radgrad.addColorStop(0, '#309FB2');
                radgrad.addColorStop(0.9, 'White');
                ctx.beginPath();
                ctx.fillStyle = radgrad;
                //Render circle
                ctx.arc(ptX, ptY, 8, 0, 2 * Math.PI, false)
                ctx.fill();
                ctx.lineWidth = 1;
                ctx.strokeStyle = '#000';
                ctx.stroke();
                ctx.closePath();
            }

            prevX = ptX;
            prevY = ptY;
        }
    };

    var getXInc = function() {
        return Math.round(xMax / data.dataPoints.length) - 1;
    };

    var drawLine = function(startX, startY, endX, endY, strokeStyle, lineWidth) {
        if (strokeStyle != null) ctx.strokeStyle = strokeStyle;
        if (lineWidth != null) ctx.lineWidth = lineWidth;
        ctx.beginPath();
        ctx.moveTo(startX, startY);
        ctx.lineTo(endX, endY);
        ctx.stroke();
        ctx.closePath();
    };

    return {
        renderType: renderType,
        render: render
    };
} ();


$(document).ready(function () {
            var dataDef = { title: "US Population Chart",
                            xLabel: 'Date', 
                            yLabel: 'Price',
                            labelFont: '19pt Arial', 
                            dataPointFont: '10pt Arial',
                            renderTypes: [CanvasChart.renderType.lines, CanvasChart.renderType.points],
                            dataPoints: [{ x: '1-aug', y: 2100 }, 
                                         { x: '2-aug', y: 2000 },
                                         { x: '3-aug', y: 2200 },
                                         { x: '4-aug', y: 1800 },
                                         { x: '5-aug', y: 1500 },
                                         { x: '6-aug', y: 3000 },

                                                                ]
                           };
            CanvasChart.render('canvas', dataDef);
        });

HTML部分

<body style="margin-left:50px;margin-top:50px;">
<canvas id="canvas" width="500" height="300"></canvas>

1 个答案:

答案 0 :(得分:2)

以下是使用普通旧DIV作为工具提示的简单方法。

enter image description here

  • 创建一个div作为工具提示(tooltip-div)。
  • 最初隐藏tooltip-div。
  • 收听mousemove事件。
  • 在mousemove处理程序中,如果鼠标位于任何数据点内,则进行命中测试。
  • 如果鼠标位于数据点内,

    1. 在tooltip-div中设置所需的文字,
    2. 使用CSS在数据点上移动tooltip-div,
    3. 显示tooltip-div。

这是带注释的代码和演示:

var CanvasChart = function () {
  var ctx;
  var margin = { top: 40, left: 75, right: 0, bottom: 75 };
  var chartHeight, chartWidth, yMax, xMax, data;
  var maxYValue = 0;
  var ratio = 0;
  var renderType = { lines: 'lines', points: 'points' };

  // addition
  var dataXY=[];

  var render = function(canvasId, dataObj) {
    data = dataObj;
    getMaxDataYValue();
    var canvas = document.getElementById(canvasId);
    chartHeight = canvas.getAttribute('height');
    chartWidth = canvas.getAttribute('width');
    xMax = chartWidth - (margin.left + margin.right);
    yMax = chartHeight - (margin.top + margin.bottom);
    ratio = yMax / maxYValue;
    ctx = canvas.getContext("2d");
    renderChart();
  };

  var renderChart = function () {
    renderBackground();
    renderText();
    renderLinesAndLabels();

    //render data based upon type of renderType(s) that client supplies
    if (data.renderTypes == undefined || data.renderTypes == null) data.renderTypes = [renderType.lines];
    for (var i = 0; i < data.renderTypes.length; i++) {
      renderData(data.renderTypes[i]);
    }
  };

  var getMaxDataYValue = function () {
    for (var i = 0; i < data.dataPoints.length; i++) {
      if (data.dataPoints[i].y > maxYValue) maxYValue = data.dataPoints[i].y;
    }
  };

  var renderBackground = function() {
    var lingrad = ctx.createLinearGradient(margin.left, margin.top, xMax - margin.right, yMax);
    lingrad.addColorStop(0.0, '#ffffff');
    lingrad.addColorStop(0.2, '#fff');
    lingrad.addColorStop(0.8, '#fff');
    lingrad.addColorStop(1, '#ffffff');
    ctx.fillStyle = lingrad;
    ctx.fillRect(margin.left, margin.top, xMax - margin.left, yMax - margin.top);
    ctx.fillStyle = '#6A6A6A';
  };

  var renderText = function() {
    var labelFont = (data.labelFont != null) ? data.labelFont : '20pt Arial';
    ctx.font = labelFont;
    ctx.textAlign = "center";

    //Title
    var txtSize = ctx.measureText(data.title);
    ctx.fillText(data.title, (chartWidth / 2), (margin.top / 2));

    //X-axis text
    txtSize = ctx.measureText(data.xLabel);
    ctx.fillText(data.xLabel, margin.left + (xMax / 2) - (txtSize.width / 2), yMax + (margin.bottom / 1.2));

    //Y-axis text
    ctx.save();
    ctx.rotate(-Math.PI / 2);
    ctx.font = labelFont;
    ctx.fillText(data.yLabel, (yMax / 2) * -1, margin.left / 4);
    ctx.restore();
  };

  var renderLinesAndLabels = function () {
    //Vertical guide lines
    var yInc = yMax / data.dataPoints.length;
    var yPos = 0;
    var yLabelInc = (maxYValue * ratio) / data.dataPoints.length;
    var xInc = getXInc();
    var xPos = margin.left;
    for (var i = 0; i < data.dataPoints.length; i++) {
      yPos += (i == 0) ? margin.top : yInc;
      //Draw horizontal lines
      drawLine(margin.left, yPos, xMax, yPos, '#E8E8E8');

      //y axis labels
      ctx.font = (data.dataPointFont != null) ? data.dataPointFont : '10pt Calibri';
      var txt = Math.round(maxYValue - ((i == 0) ? 0 : yPos / ratio));
      var txtSize = ctx.measureText(txt);
      ctx.fillText(txt, margin.left - ((txtSize.width >= 14) ? txtSize.width : 10) - 7, yPos + 4);

      //x axis labels
      txt = data.dataPoints[i].x;
      txtSize = ctx.measureText(txt);
      ctx.fillText(txt, xPos, yMax + (margin.bottom / 3));
      xPos += xInc;
    }

    //Vertical line
    drawLine(margin.left, margin.top, margin.left, yMax, 'black');

    //Horizontal Line
    drawLine(margin.left, yMax, xMax, yMax, 'black');
  };

  var renderData = function(type) {
    var xInc = getXInc();
    var prevX = 0, 
        prevY = 0;

    // addition
    dataXY.length=0;

    for (var i = 0; i < data.dataPoints.length; i++) {
      var pt = data.dataPoints[i];
      var ptY = (maxYValue - pt.y) * ratio;
      if (ptY < margin.top) ptY = margin.top;
      var ptX = (i * xInc) + margin.left;

      if (i > 0 && type == renderType.lines) {
        //Draw connecting lines
        drawLine(ptX, ptY, prevX, prevY, '#309FB2', 2);
      }

      if (type == renderType.points) {
        var radgrad = ctx.createRadialGradient(ptX, ptY, 8, ptX - 5, ptY - 5, 0);
        radgrad.addColorStop(0, '#309FB2');
        radgrad.addColorStop(0.9, 'White');
        ctx.beginPath();
        ctx.fillStyle = radgrad;
        //Render circle
        ctx.arc(ptX, ptY, 8, 0, 2 * Math.PI, false)
        ctx.fill();
        ctx.lineWidth = 1;
        ctx.strokeStyle = '#000';
        ctx.stroke();
        ctx.closePath();
        // addition
        dataXY.push({cx:ptX,cy:ptY,r:8,tipText:'Price='+pt.y+' on '+pt.x});
      }

      prevX = ptX;
      prevY = ptY;
    }
  };

  var getXInc = function() {
    return Math.round(xMax / data.dataPoints.length) - 1;
  };

  var drawLine = function(startX, startY, endX, endY, strokeStyle, lineWidth) {
    if (strokeStyle != null) ctx.strokeStyle = strokeStyle;
    if (lineWidth != null) ctx.lineWidth = lineWidth;
    ctx.beginPath();
    ctx.moveTo(startX, startY);
    ctx.lineTo(endX, endY);
    ctx.stroke();
    ctx.closePath();
  };

  return {
    renderType: renderType,
    render: render,  // added comma

    // addition
    dataXY: dataXY
  };
} ();
//
// end CanvasChart
/////////////////////////////



// addition
var CanvasTooltip=(function(){
  // ctor
  function CanvasTooltip(canvasId,targets){
    var that=this;
    this.$canvas=$("#"+canvasId);
    this.targets=targets;
    this.openTooltipIndex=-1;
    // add tip elements
    this.init();
    this.$tip=$('#canvasTip').hide();
    // eventing
    this.offsetX,this.offsetY;
    this.reOffset();
    this.$canvas.on('mousemove',function(e){ that.handleMouseMove(e); });
    $(window).on('scroll',function(e){ that.reOffset(); });
  }

  CanvasTooltip.prototype.reOffset=function(){
    var BB=this.$canvas[0].getBoundingClientRect();
    this.offsetX=BB.left;
    this.offsetY=BB.top;    
  };

  CanvasTooltip.prototype.init=function(){
    this.$canvas.wrap('<div id="canvasTipWrapper"></div>');
    $('#canvasTipWrapper').css({'position':'relative'});
    $('<div id="canvasTip"></div>')
    .css({
      'position':'absolute', 
      'border':'1px solid gray',
      'border-left-width':'7px',
      'border-left-color':'maroon',
      'margin-bottom':'20px',
      'background':'rgba(255,255,255,1.00)',
      'padding-left':'5px',
      'padding-right':'5px',
      'max-width':'150px'
    })
    .appendTo('#canvasTipWrapper');    
  };

  CanvasTooltip.prototype.handleMouseMove =function(e){
    e.preventDefault();
    e.stopPropagation();
    var mouseX=parseInt(e.clientX-this.offsetX);
    var mouseY=parseInt(e.clientY-this.offsetY);
    // hit-test each target
    var hit=-1;
    for(var i=0;i<this.targets.length;i++){
      var t=this.targets[i];
      if(t.cx){
        var dx=mouseX-t.cx;
        var dy=mouseY-t.cy;
        var r=t.r;
        if(dx*dx+dy*dy<r*r){
          hit=i;
          if(hit!==this.openTooltipIndex){
            // configure & open the tooltip
            this.openTooltipIndex=hit;
            this.$tip
            .text(t.tipText)
            .css({left:t.cx,top:t.cy-t.r-10-this.$tip.height()})
            .show();
          }
        }
      }
    }
    // hide the tip if no target was hit
    if(hit==-1 && this.openTooltipIndex!==-1){
      this.$tip.hide();
      this.openTooltipIndex=-1;
    }
  }

  return(CanvasTooltip);

})(); // end CanvasTooltip
//
// end CanvasTooltip
////////////////////////////


var dataDef = { title: "US Population Chart",
               xLabel: 'Date', 
               yLabel: 'Price',
               labelFont: '19pt Arial', 
               dataPointFont: '10pt Arial',
               renderTypes: [CanvasChart.renderType.lines, CanvasChart.renderType.points],
               dataPoints: [{ x: '1-aug', y: 2100 }, 
                            { x: '2-aug', y: 2000 },
                            { x: '3-aug', y: 2200 },
                            { x: '4-aug', y: 1800 },
                            { x: '5-aug', y: 1500 },
                            { x: '6-aug', y: 3000 },

                           ]
                            };
                            CanvasChart.render('canvas', dataDef);

                            // addition
                            var tooltip=new CanvasTooltip('canvas',CanvasChart.dataXY);
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<canvas id="canvas" width=300 height=300></canvas>