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

event handler on center of doughnut chart using 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) && (pageX >= 440 && pageX <= 629)){   //coordinates are of rectangular area which has text inside the center of doughnut chart.
             //do something                                          
      }
});

但是如果屏幕的分辨率不同,这将不起作用,因为坐标会发生变化。

有什么想法吗?

我尝试使用 raphael.js 使中心可点击,但不太确定这种尝试。 我正在尝试使用 container 方法在甜甜圈的中心孔中创建一个圆圈,可以在其上附加点击处理程序。

代码信息使用 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 code 的原始问题的回答。自发布以来,问题已更改多次 - 添加了另存为 PNG 的要求,图表数量从原始代码中的 1 个更改为 4 个,使用的框架从 Chart.js 在 [ 上渲染更改=177=] Canvas 到 SVG 上的 Raphaël 渲染。我将保留我发布的解决方案,希望它对将来的人有用。

我这里没什么想法:

寻找像素

一种较慢但可靠的方法:知道您对黑色像素感兴趣,您可以遍历 canvas 的所有像素并记住 4 个数字:任何黑色的最小和最大 x 和 y 坐标你找到的像素。您可以为其添加一些边距,并且您将拥有一个始终正确的矩形,即使库在未来版本中开始将文本写入不同的位置也是如此。

在重绘 canvas 之后,每次调整 window 的大小时,您都必须重新计算它。

为此,您的文本必须使用 canvas 上其他任何地方都不存在的颜色(目前是这种情况)。

猜坐标

您可以猜测坐标 - 或者计算它们,更准确地说 - 知道它是如何绘制的。似乎大圆圈在较小的维度上占据了整个 space(在本例中为整个高度)并以另一个轴为中心(在本例中为水平居中)。

利用这些知识,您可以计算仅具有 canvas 维度的内部(白色)圆的大小和位置,方法类似于:

找出canvas的宽度或高度哪个较小,并以此作为基数。将它除以 2 将得到 R - 大圆的半径。除以 R/2 将大致得到 r - 内部白色小圆圈的半径。计算xy-canvas的中心坐标-x = width/2y = height /2.

现在您可以试验文本所在的矩形。它可能类似于:x - 0.7*rx + 0.7*r 用于左右边缘,y - 0.4*ry + 0.4*r 用于底部和顶部边缘。这些只是示例,您可以根据自己的喜好调整这些数字。

这些数字不一定是完美的,因为无论如何您都应该在文本周围留出几个像素的边距。

将来当库开始以完全不同的方式绘制它时,这里可能不起作用,但对于像这样的简单图表,它可能不会起作用。

好处是您不必寻找特定的颜色,而且计算速度比检查每个像素要快。

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

更改您的插件服务

另一个想法是更改 pluginServicebeforeDraw 函数,以便它保存已有的数字。

特别是,您已经拥有:

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;

(只是为了避免以后重新计算文本测量值)然后您可以在某处存储以下数字:

只是 textXtextY 以及 measured.widthmeasured.height 或者可能是具有以下属性的对象:

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

如果需要,请确保使用四舍五入。例如,您可以将该对象存储在某个全局对象中,或者作为某个 HTML 元素的 data-* 属性(例如 canvas 本身)。

最后一个解决方案很好,因为您不必担心颜色,不必猜测文本的放置位置,因为您确切地知道,而且您不必担心重新计算调整大小是因为每次绘制文本本身时都会运行该代码。

缺点是需要修改你的pluginService.

比 canvas

div

另一种方法是将 div 放在 canvas 上,然后将文本放在 div 而不是 canvas 中。这样你就可以方便地添加事件监听器等。

你可以这样做:

将你的 canvas 和空 div(或更多 div)放入更大的 div:

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

您可以像 text1 添加更多 div 以获得更多 circles/charts,像这样:

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

添加这个 CSS 让它们正确堆叠:

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

现在您将文本添加到内部 div 而不是在 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";
  }
});

参见 DEMO

它可能可以简化,但将文本放在 canvas 中可能更简单,并且您可以使用事件侦听器或简单的 CSS 样式。例如添加:

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

悬停时会变成红色。

清空 div 超过 canvas

在评论中发布了未包含在问题中的其他要求后,这里又进行了一次更新。

你可以像上面的版本那样HTML:

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

但是这次您可以在 canvas 上添加一个空的 div,这样文本就会包含在 canvas 中,并且保存时也会包含文本。

这是需要的 CSS:

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

这里是CSS,它会告诉你隐形的位置div:

#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(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();
  }
});

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

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

大空div过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,
        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();
  }
});

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

var a = d / 2.5;

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

在 canvas

上舍入空 div

这是一个变体,它使用 border-radius 制作圆形 div 而不是矩形,并且似乎完美地填满了内部白色圆圈。

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

参见 DEMO