使用chart.js的甜甜圈图中心的事件处理程序

tec*_*_28 5 javascript jquery charts canvas raphael

我在页面上创建了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                                          
      }
});
Run Code Online (Sandbox Code Playgroud)

但是,如果屏幕的分辨率不同(坐标会变化),则此方法将无效。

有任何想法吗?

我尝试使用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();
                    }
                  }
                })
Run Code Online (Sandbox Code Playgroud)

rsp*_*rsp 5

这是有关此代码的原始问题的答案。自发布以来,该问题已多次更改-添加了另存为PNG的要求,图表的数量从原始代码中的1更改为4,并且所使用的框架从HTML Canvas上的Chart.js呈现更改为SVG上的Raphaël渲染。我离开了我发布的解决方案,希望它对将来的某人有用。

我在这里没有什么主意:

寻找像素

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

重新绘制画布后,每次调整窗口大小时都必须重新计算它。

为此,您的文本必须使用画布上其他任何地方都没有的颜色(目前是这种情况)。

猜测坐标

您可以猜测坐标-或更精确地计算它们-了解如何绘制坐标。似乎大圆圈在较小的维度上占据了整个空间(在这种情况下为整个高度),并且在另一个轴上居中(在这种情况下,它在水平方向上居中)。

使用该知识,您可以通过类似于以下方式来计算仅具有画布尺寸的内(白色)圆的大小和位置:

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

现在,您可以尝试在其中放置文本的矩形。这可能是这样的:x - 0.7*rx + 0.7*r左,右边缘y - 0.4*r以及y + 0.4*r对底部和顶部边缘。这些仅是示例,您可以使这些数字满足您的要求。

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

在这种情况下,当库将来开始完全不同地绘制它时,它可能无法工作,但对于像这样的简单图表来说可能就不会。

好消息是,您不必寻找特定的颜色,并且计算将比检查每个像素更快。

同样,如果以其他大小重绘了图表,则必须重新计算这些尺寸。

更改您的pluginService

另一个想法是更改您pluginServicebeforeDraw功能,以便保存已经拥有的数字。

特别是,您已经具有:

textX = Math.round((width - ctx.measureText(text).width) / 2),
textY = height / 2;
Run Code Online (Sandbox Code Playgroud)

如果将其更改为:

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

(只是为了避免以后重新计算文本尺寸),您可以在以下位置存储以下数字:

无论是刚textXtextY一起measured.widthmeasured.height或可能与以下属性的对象:

var textPos = {
    x1: textX,
    y1: textY,
    x2: textX + measured.width,
    y2: textY + measured.height
}
Run Code Online (Sandbox Code Playgroud)

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

最后一个解决方案很好,因为您不必担心颜色,也不必猜测文本的放置位置,因为您知道的非常清楚,也不必担心调整大小时会重新计算该值,因为该代码在每次绘制文本本身时运行。

缺点是您需要修改pluginService

画布上的div

另一种方法是将div放在画布上,然后将文本放在该div中而不是在画布中。这样,您便可以轻松添加事件侦听器等。

您可以执行以下操作:

将画布和空div(或更多div)放入更大的div中:

<div id="chart">
  <canvas id="myChart"></canvas>
  <div class="chart-text" id="text1"></div>
</div>
Run Code Online (Sandbox Code Playgroud)

您可以添加更多的div(例如text1)来获得更多的圈子/图表,如下所示:

<div id="chart">
  <canvas id="myChart"></canvas>
  <div class="chart-text" id="text1"></div>
  <div class="chart-text" id="text2"></div>
</div>
Run Code Online (Sandbox Code Playgroud)

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

#chart { position: relative; }
.chart-text { position: absolute; }
Run Code Online (Sandbox Code Playgroud)

现在,您将文本添加到该内部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";
  }
});
Run Code Online (Sandbox Code Playgroud)

参见DEMO

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

.chart-text:hover { color: red; }
Run Code Online (Sandbox Code Playgroud)

悬停时会变红。

在画布上的空div

在问题中未包含的注释中发布了其他要求之后,这是又一个更新。

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

<div id="chart">
<canvas id="myChart"></canvas>
<div class="chart-text" id="text1"></div>
</div>
Run Code Online (Sandbox Code Playgroud)

但是这一次您可以在画布上添加一个空的div,这样,文本将包含在画布中,保存后将包含文本。

这是所需的CSS:

#chart { position: relative; }
.chart-text { position: absolute; }
Run Code Online (Sandbox Code Playgroud)

这是CSS,它将向您显示不可见div的位置:

#chart { position: relative; }
.chart-text { position: absolute; border: 1px solid red; }
Run Code Online (Sandbox Code Playgroud)

现在将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();
  }
});
Run Code Online (Sandbox Code Playgroud)

确保每1 单位emInPx具有正确的px(CSS像素)位数em。您以fontSizein为em单位定义,我们需要像素来计算正确的位置。

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

画布上的大空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(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();
  }
});
Run Code Online (Sandbox Code Playgroud)

参见DEMO。它不依赖于em大小px和文字大小。这条线改变了正方形的大小:

var a = d / 2.5;
Run Code Online (Sandbox Code Playgroud)

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

在画布上圆空div

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

HTML:

<div id="chart">
<canvas id="myChart"></canvas>
<div class="chart-text" id="text1"></div>
</div>
Run Code Online (Sandbox Code Playgroud)

CSS:

#chart, #myChart, .chart-text {  padding: 0; margin: 0; }
#chart { position: relative; }
.chart-text { position: absolute; border-radius: 100%; }
Run Code Online (Sandbox Code Playgroud)

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();
  }
});
Run Code Online (Sandbox Code Playgroud)

参见DEMO