简体   繁体   English

使用chart.js在圆环图中心的事件处理程序

[英]event handler on center of doughnut chart using chart.js

I have created 4 doughnut chart on a page which has some text in the center,I know this can be done by placing a DIV over the center but I cant use that as the text doesn't get exported when the chart is downloaded as PNG : 我在一个页面上创建了4个圆环图,中间有一些文本,我知道这可以通过在中心放置DIV来完成,但我不能使用它,因为当图表下载为PNG时文本不会被导出:

Demo: https://jsfiddle.net/cmyker/ooxdL2vj/ 演示: https//jsfiddle.net/cmyker/ooxdL2vj/

I need to track the click of the center text for this I tried using the pageX,pageY to determine if the click is made on the center section. 我需要跟踪中心文本的点击,我尝试使用pageX,pageY来确定是否在中心部分进行了点击。

The coordinates are of the corners of the rectangular section which is inside the central hole of the doughnut chart & is likely to have the text within. 坐标是矩形部分的角,它位于圆环图的中心孔内,并且可能有文本。

jQuery('#canvas').on('click',function(e){
  var pageX = e.pageX;                                  
  var pageY = e.pageY;
      if((pageY >= 379 && pageY <= 571) && (pageX >= 440 && pageX <= 629)){   //coordinates are of rectangular area which has text inside the center of doughnut chart.
             //do something                                          
      }
});

but this wont work if the resolution of the screen is different as the coordinates will vary. 但是如果屏幕的分辨率不同,这将不起作用,因为坐标会有所不同。

Any Ideas please? 有什么想法吗?

I tried to use raphael.js to make the center clickable but not very sure of this attempt. 我试图使用raphael.js使中心可点击但不太确定这个尝试。 I am trying to use the container approach to create a circle in the center hole of donuts on which a click handler could be attached. 我正在尝试使用container方法在甜甜圈的中心孔中创建一个圆圈,可以在其上附加一个点击处理程序。

Code info using Raphael JS 使用Raphael JS的代码信息

Chart.pluginService.register({
                  beforeDraw: function(chart) {
                  if(chart['data']['midNum']){
                      var width = chart.chart.width,
                          height = chart.chart.height,
                          ctx = chart.chart.ctx;

                      ctx.restore();
                      var fontSize = (height / 114).toFixed(2);
                      ctx.font = fontSize + "em sans-serif";
                      ctx.textBaseline = "middle";

                      var text = chart['data']['midNum'],
                          textX = Math.round((width - ctx.measureText(text).width) / 2),
                          textY = height / 2.5;
                        var chartID = chart['chart']['canvas']['id']; //the ID of element on which this donut was created

                        var paper  = Raphael(chartID,textX,textY); //trying to use the container approach
                        var circle = paper.circle(textX, textY, 10);
                            circle.attr("fill", "#f00");
                      //ctx.clearRect(0,0,width,height);
                      //ctx.fillText(text, textX, textY);
                      //ctx.save();
                    }
                  }
                })

This is an answer to the original question about this code . 这是关于此代码的原始问题的答案。 Since it was posted the question has been changed several times - the requirement to save as PNG was added, the number of charts was changed from 1 in the original code to 4 and the framework used was changed from Chart.js rendering on HTML Canvas to Raphaël rendering on SVG. 自发布以来,问题已经多次更改 - 添加了保存为PNG的要求,图表的数量从原始代码中的1更改为4,并且使用的框架从HTML Canvas上的Chart.js呈现更改为Raphaël渲染SVG。 I am leaving the solutions that I posted in hope that it will be useful to someone in the future. 我将离开我发布的解决方案,希望将来对某人有用。

I have few ideas here: 我这里的想法很少:

Finding pixels 寻找像素

A slower but a sure way: knowing that you are interested in black pixels, you can iterate over all pixels of the canvas and remember 4 numbers: the smallest and biggest x and y coordinates of any black pixel that you find. 一种较慢但可靠的方法:知道您对黑色像素感兴趣,您可以迭代画布的所有像素并记住4个数字:您找到的任何黑色像素的最小和最大x和y坐标。 You can add some margin to that and you'll have a rectangle that is always spot on, even when the library starts to write the text in a different place in future versions. 您可以为此添加一些边距,并且您将拥有一个始终点亮的矩形,即使库在未来版本中开始在不同的位置写入文本也是如此。

You'll have to recalculate it every time a window is resized, after the canvas is redrawn. 重绘画布后,每次重新调整窗口时都必须重新计算它。

For that to work your text will have to be in a color that is not present anywhere else on the canvas (which is currently the case). 要使其工作,您的文本必须采用画布上任何其他位置都不存在的颜色(目前情况就是如此)。

Guess coordinates 猜猜坐标

You can guess the coordinates - or calculate them, to be more precise - knowing how it is drawn. 您可以猜测坐标 - 或者更准确地计算它们 - 知道它是如何绘制的。 It seems that the big circle is taking the entire space on the smaller dimension (the entire height in this case) and is centered in the other axis (in this case it's centered horizontally). 似乎大圆圈占据了较小尺寸上的整个空间(在这种情况下为整个高度),并且在另一个轴上居中(在这种情况下,它是水平居中的)。

Using that knowledge you can calculate the size and position of the inner (white) circle having only the canvas dimension in a way similar to this: 使用这些知识,您可以计算内部(白色)圆的大小和位置,只有画布尺寸的方式与此类似:

Find which width or height of the canvas is smaller and use it as a base number. 找到画布的宽度或高度较小,并将其用作基数。 Dividing it by 2 will give you R - the radius of the big circle. 将它除以2会得到R - 大圆的半径。 Dividing R/2 will roughly give you r - the radius of the small, internal white circle. 除以R/2将粗略地给出r - 小的内部白色圆的半径。 Calculate x and y - coordinates of the center of the canvas - x = width/2 and y = height /2 . 计算xy - 画布中心的坐标 - x = width/2y = height /2

Now you can experiment with the rectangle where the text will be. 现在,您可以尝试文本所在的矩形。 It may be something like: x - 0.7*r and x + 0.7*r for left and right edges and y - 0.4*r and y + 0.4*r for the bottom and top edges. 它可能类似于:左边和右边的x - 0.7*rx + 0.7*r ,底边和顶边的y - 0.4*ry + 0.4*r Those are just examples, you can tweek those numbers to your satisfaction. 这些仅仅是示例,您可以将这些数字调整到令您满意的程度。

Those numbers don't have to be perfect because you should have a few pixels of margin around the text anyway. 这些数字不一定非常完美,因为无论如何你应该在文本周围留下几个像素的边距。

Here it may not work when the library starts to draw it completely differently in the future, but it probably won't for a simple chart like this. 这里它可能无法工作,当图书馆开始在未来完全不同地绘制它,但它可能不会像这样的简单图表。

The good thing is that you don't have to look for specific colors and that calculation will be faster that examining every pixel. 好处是你不必寻找特定的颜色,并且检查每个像素的计算速度会更快。

Again, you have to recalculate those dimensions if the chart ever gets redrawn with a different size. 同样,如果图表以不同的大小重新绘制,则必须重新计算这些维度。

Change your pluginService 更改您的pluginService

Another idea would be to change you pluginService 's beforeDraw function so that it saves the numbers that it already has. 另一个想法是改变你pluginServicebeforeDraw函数,以便它保存已有的数字。

In particular, you already have: 特别是,你已经拥有:

textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;

If you change it to: 如果您将其更改为:

var measured = ctx.measureText(text);
textX = Math.round((width - measured.width) / 2),
textY = height / 2;

(just to avoid recalculating the text measurement later) then you can store somewhere the following numbers: (只是为了避免以后重新计算文本测量)然后你可以存储以下数字:

Either just textX and textY together with measured.width and measured.height or maybe an object with following properties: 要么textXtextY以及measured.widthmeasured.height要么是具有以下属性的对象:

var textPos = {
    x1: textX,
    y1: textY,
    x2: textX + measured.width,
    y2: textY + measured.height
}

Make sure to use rounding if you need to. 如果需要,请务必使用四舍五入。 You can store that object for example in some global object, or as a data-* attribute of some HTML element (like on the canvas itself). 您可以将该对象存储在某个全局对象中,或者存储为某些HTML元素的data-*属性(如画布本身)。

This last solution is nice because you don't have to worry about color, you don't have to guess where the text will be put because you know that exactly, and you don't have to worry about recalculation of this on resize because that code runs every time the text itself is drawn. 最后一个解决方案很好,因为你不必担心颜色,你不必猜测文本的放置位置,因为你完全知道,并且你不必担心重新调整大小,因为每次绘制文本本身时代码都会运行。

The drawback is that you need to modify your pluginService . 缺点是您需要修改pluginService

A div over canvas 画布上的div

Another way is putting a div over your canvas and putting your text in that div instead of in the canvas. 另一种方法是在画布上放置div并将文本放在div而不是画布中。 That way you have all the convenience of adding event listeners etc. 这样你就可以方便地添加事件监听器等。

You can do something like this: 你可以这样做:

Put your canvas and and empty div (or more divs) inside a bigger div: 把你的画布和空div(或更多div)放在一个更大的div中:

<div id="chart">
  <canvas id="myChart"></canvas>
  <div class="chart-text" id="text1"></div>
</div>

You can add more divs like the text1 for more circles/charts, like this: 您可以为更多的圆/图表添加更多div,例如text1,如下所示:

<div id="chart">
  <canvas id="myChart"></canvas>
  <div class="chart-text" id="text1"></div>
  <div class="chart-text" id="text2"></div>
</div>

Add this CSS to have them stack properly: 添加此CSS以使它们正确堆叠:

#chart { position: relative; }
.chart-text { position: absolute; }

And now you add your text to that inner div instead of drawing it on the canvas: 现在,您将文本添加到内部div而不是在画布上绘制它:

var text1 = document.getElementById('text1');
text1.addEventListener("click", function (e) {
  alert("CLICKED!");
});

Chart.pluginService.register({
  beforeDraw: function(chart) {
    var width = chart.chart.width,
        height = chart.chart.height;

    var fontSize = (height / 114).toFixed(2);
    text1.style.font = fontSize + "em sans-serif";

    var text = "75%";
    text1.innerText = text;
    var r = text1.getBoundingClientRect();
    text1.style.left = ((width-r.width)/2)+"px";
    text1.style.top = ((height-r.height)/2)+"px";
  }
});

See DEMO . DEMO

It can probably be simplified but it is probably simpler that putting the text inside of the canvas, and you can have event listeners or easy CSS styling. 它可以简化,但将文本放在画布中可能更简单,并且您可以拥有事件侦听器或简单的CSS样式。 For example adding: 例如添加:

.chart-text:hover { color: red; }

will make it red on hover. 将在悬停时使其变红。

Empty div over canvas 画布上的空div

Here is yet another update after posting another requirements in the comments that were not included in the question. 在评论中未包含在问题中的其他要求之后,这是另一个更新。

You can have this HTML as in the version above: 您可以像上面的版本中一样使用此HTML:

<div id="chart">
<canvas id="myChart"></canvas>
<div class="chart-text" id="text1"></div>
</div>

But this time you can add an empty div over your canvas, so that way the text is included in the canvas and saving will it will include the text. 但是这次你可以在画布上添加一个空div,这样文本就会包含在画布中,保存它将包含文本。

Here is CSS that is needed: 这是需要的CSS:

#chart { position: relative; }
.chart-text { position: absolute; }

Here is CSS that will show you the position of the invisible div: 这是CSS将显示隐形div的位置:

#chart { position: relative; }
.chart-text { position: absolute; border: 1px solid red; }

And now the code to put the div where it should be: 现在代码将div放在应该的位置:

var text1 = document.getElementById('text1');
text1.addEventListener("click", function (e) {
  alert("CLICKED!");
});

Chart.pluginService.register({
  beforeDraw: function(chart) {
    var width = chart.chart.width,
        height = chart.chart.height,
        ctx = chart.chart.ctx;

    ctx.restore();
    var fontSize = (height / 114).toFixed(2);
    ctx.font = fontSize + "em sans-serif";
    ctx.textBaseline = "middle";

    var text = "75%",
        m = ctx.measureText(text),
        textX = Math.round((width - m.width) / 2),
        textY = height / 2;

    var emInPx = 16;
    text1.style.left = textX + "px";
    text1.style.top = (textY - fontSize*emInPx/2) + "px";
    text1.style.width = m.width + "px";
    text1.style.height = fontSize+"em";

    ctx.fillText(text, textX, textY);
    ctx.save();
  }
});

Make sure that the emInPx has the correct numper of px (CSS pixels) per one em unit. 确保emInPx每个em单元具有正确的px (CSS像素)块。 You define the fontSize in em units and we need pixels to calculate the correct position. 您以em单位定义fontSize ,我们需要像素来计算正确的位置。

See DEMO (it has a red border to make the div visible - just remove border: 1px solid red; from CSS to make it disappear) 看到DEMO (它有一个红色边框使div可见 - 只需删除border: 1px solid red;从CSS中删除它)

Big empty div over canvas 在画布上的大空div

Another example - this time the div is bigger than the text: 另一个例子 - 这次div大于文本:

var text1 = document.getElementById('text1');
text1.addEventListener("click", function (e) {
  alert("CLICKED!");
});

Chart.pluginService.register({
  beforeDraw: function(chart) {
    var width = chart.chart.width,
        height = chart.chart.height,
        ctx = chart.chart.ctx;

    ctx.restore();
    var fontSize = (height / 114).toFixed(2);
    ctx.font = fontSize + "em sans-serif";
    ctx.textBaseline = "middle";

    var text = "75%",
        m = ctx.measureText(text),
        textX = Math.round((width - m.width) / 2),
        textY = height / 2;

    var d = Math.min(width, height);
    var a = d/2.5;

    text1.style.left = ((width - a) / 2) + "px";
    text1.style.top = ((height - a) / 2) + "px";
    text1.style.width = a + "px";
    text1.style.height = a + "px";

    ctx.fillText(text, textX, textY);
    ctx.save();
  }
});

See DEMO . DEMO It doesn't depend on the em size in px and on the text size. 它不依赖于pxem大小和文本大小。 This line changes the size of the square: 这一行改变了正方形的大小:

var a = d / 2.5;

You can try changing the 2.5 to 2 or 3 or something else. 您可以尝试将2.5更改为2或3或其他内容。

Round empty div over canvas 在画布上的圆形空div

This is a variant that uses border-radius to make a round div instead of rectangular and seems to fill up the inner white circle perfectly. 这是一个变体,它使用border-radius来制作圆形div而不是矩形,并且似乎完美地填充内部白色圆圈。

HTML: HTML:

<div id="chart">
<canvas id="myChart"></canvas>
<div class="chart-text" id="text1"></div>
</div>

CSS: CSS:

#chart, #myChart, .chart-text {  padding: 0; margin: 0; }
#chart { position: relative; }
.chart-text { position: absolute; border-radius: 100%; }

JS: JS:

var text1 = document.getElementById('text1');
text1.addEventListener("click", function (e) {
  alert("CLICKED!");
});

Chart.pluginService.register({
  beforeDraw: function(chart) {
    var width = chart.chart.width,
        height = chart.chart.height,
        ctx = chart.chart.ctx;

    ctx.restore();
    var fontSize = (height / 114).toFixed(2);
    ctx.font = fontSize + "em sans-serif";
    ctx.textBaseline = "middle";

    var text = "75%",
        m = ctx.measureText(text),
        textX = Math.round((width - m.width) / 2),
        textY = height / 2;

    var d = Math.min(width, height);
    var a = d / 2;

    text1.style.left = (((width - a) / 2 - 1)|0) + "px";
    text1.style.top = (((height - a) / 2 - 1)|0) + "px";
    text1.style.width = a + "px";
    text1.style.height = a + "px";

    ctx.fillText(text, textX, textY);
    ctx.save();
  }
});

See DEMO . DEMO

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM