事件处理程序在中心的圆环图使用chart.js [英] event handler on center of doughnut chart using chart.js

查看:533
本文介绍了事件处理程序在中心的圆环图使用chart.js的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经在中心有一些文本的页面上创建了4个圆环图,我知道这可以通过在中心放置一个DIV来完成,但我不能使用,因为文本没有得到导出,当图表下载为PNG



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



我需要跟踪中心文本的点击,我试过使用pageX ,pageY以确定是否在中心部分上进行点击。



坐标是矩形截面的角的坐标,其位于圆环图的中心孔;很可能有文本。

  jQuery('#canvas')。on('click',function(e){
var pageX = e.pageX;
var pageY = e.pageY;
if((pageY> = 379&& pageY&= 571)&& amp;&< pageon> = 440&& ; pageX <= 629)){//坐标是具有在圆环图中心内的文本的矩形区域。
//做某事
}
});

但是如果屏幕的分辨率不同, >

有任何想法吗?



我试图使用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']; //创建这个甜甜圈的元素的ID

var paper = Raphael(chartID,textX,textY); //试图使用容器方法
var circle = paper.circle(textX,textY,10);
circle.attr(fill,#f00);
//ctx.clearRect(0,0,width,height);
//ctx.fillText(text,textX,textY);
//ctx.save();
}
}
})


解决方案

这是关于此代码的原始问题的答案。因为它被发布的问题已经改变了几次 - 要求保存为PNG添加,图表的数量从原来的代码中的1更改为4和使用的框架从Chart.js在HTML画布上的渲染更改Raphaël在SVG上呈现。



我在这里有几个想法:



查找像素



速度较慢但是确定的方式:知道您对黑色像素感兴趣,可以遍历所有像素的画布,并记住4数字:你发现任何黑色像素的最小和最大的x和y坐标。你可以添加一些边距,你会有一个矩形,总是突出,即使库开始写在不同的地方在未来的版本中的文本。



为了使工作,你的文本将必须是一个颜色,你需要重新计算它每次窗口调整大小后,重新绘制画布。



猜测坐标



你在画布上的任何其他地方都不存在可以猜测坐标 - 或者计算它们,更精确 - 知道它是如何绘制的。看起来,大圆圈在较小的维度(在这种情况下是整个高度)上占据整个空间,并且在另一个轴上居中(在这种情况下,它是水平居中的)。



使用这些知识,您可以以类似于以下方式计算只有画布尺寸的内部(白色)圆的大小和位置:



查找画布的宽度或高度较小,并将其用作基数。将它除以2将给你 R - 大圆的半径。划分 R / 2 将粗略地给出 r - 小的内部白色圆圈的半径。计算 x y - 画布中心的坐标 - x = width / 2 y = height / 2



文本将是。它可以是如下:对于左边缘和右边缘, x - 0.7 * r x + 0.7 * r 底部和顶部边缘的code> y - 0.4 * r 和 y + 0.4 * r 。这些只是例子,你可以调整这些数字,你的满意。



这些数字不必是完美的,因为你应该有几个像素的边缘的文本



这里它可能不工作,当库开始绘制它完全不同的未来,但它可能不会像这样一个简单的图表。



好处是,你不必查找特定的颜色,计算将更快,检查每个像素。


$ b $



更改您的pluginService


$ 如果您的图表已重新绘制不同尺寸,您必须重新计算这些尺寸。 b $ b

另一种想法是改变 pluginService beforeDraw 函数,以便它保存数字



特别是,您已经拥有:

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

如果将其更改为:

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

(只是为了避免以后重新计算文本测量),那么您可以存储以下数字:



只需 textX textY measured.width measured.height 或者可能是具有以下属性的对象:

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

如果需要,请务必使用四舍五入。您可以将该对象存储在某些全局对象中,或作为某些HTML元素的 data - * 属性(如画布本身)。



这最后一个解决方案是很好的,因为你不必担心颜色,你不必猜测文本将放在哪里,因为你知道这一点,你没有



缺点是你需要修改你的 pluginService



画布上的div



另一种方式是将div您的画布,并将您的文本放在div,而不是在画布上。



您可以这样做:



将你的画布和空div(或更多的div)放在一个更大的div:

 < div id =chart ; 
< canvas id =myChart>< / canvas>
< div class =chart-textid =text1>< / div>
< / div>

您可以像text1一样添加更多div,以获取更多圈子/图表,例如:

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

添加此CSS以使其正确堆叠:

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

现在你将你的文本添加到内部div,而不是在画布上绘制: p>

  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;
}
}

请参阅 DEMO



它可以简化,但将文本放在画布,你可以有事件监听器或容易的CSS样式。例如添加:

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

会在悬停时显示红色。



< h2> Empty div over canvas

这是在未包含在问题中的评论中发布另一个要求之后的另一个更新。



您可以在上述版本中使用此HTML:

 < div id =chart> ; 
< canvas id =myChart>< / canvas>
< div class =chart-textid =text1>< / div>
< / div>

但这次你可以在画布上添加一个空的div,



这里是需要的CSS:

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

这里是将显示不可见div位置的CSS:

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

现在代码将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 ),
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 $ b}
});

确保 emInPx 单位 px (CSS像素)每个 em 您在 em 单位中定义 fontSize ,我们需要像素来计算正确的位置。



请参阅 DEMO
它有一个红色边框,使div可见 - 只需从CSS中删除 border:1px solid red; ,使其消失)



画布上的大空div



另一个例子 - 此时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 ),
textX = Math.round((width - m.width)/ 2),
textY = height / 2;

var d = Math.min 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();
}
});

请参阅 DEMO
它不依赖于 px 中的 em 大小和文本大小。
此行更改正方形的大小:

  var a = d / 2.5; 

您可以尝试将2.5改为2或3或其他。



画布上的圆形空白div



这是一个使用 border-radius



HTML:

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

CSS:

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

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 ),
textX = Math.round((width - m.width)/ 2),
textY = height / 2;

var d = Math.min 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();
}
} ;

请参阅 DEMO


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 :

Demo: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.

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. 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.

Code info using 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. 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. 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. Dividing R/2 will roughly give you r - the radius of the small, internal white circle. Calculate x and y - coordinates of the center of the canvas - x = width/2 and y = 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. 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

Another idea would be to change you pluginService's beforeDraw function so that it saves the numbers that it already has.

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:

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).

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.

A div over canvas

Another way is putting a div over your canvas and putting your text in that div instead of in the canvas. 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 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 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:

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

And now you add your text to that inner div instead of drawing it on the canvas:

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.

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. For example adding:

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

will make it red on hover.

Empty div over canvas

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:

<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.

Here is CSS that is needed:

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

Here is CSS that will show you the position of the invisible div:

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

And now the code to put the div where it should be:

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. You define the fontSize in em units and we need pixels to calculate the correct position.

See DEMO (it has a red border to make the div visible - just remove border: 1px solid red; from CSS to make it disappear)

Big empty div over canvas

Another example - this time the div is bigger than the text:

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. It doesn't depend on the em size in px and on the text size. 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.

Round empty div over canvas

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.

HTML:

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

CSS:

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

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.

这篇关于事件处理程序在中心的圆环图使用chart.js的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆