使用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 = 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(); } } })
这是关于此代码的原始问题的答案。 自发布以来,问题已经多次更改 – 添加了保存为PNG的要求,图表的数量从原始代码中的1更改为4,并且使用的框架从HTML Canvas上的Chart.js渲染更改为Raphaël渲染SVG。 我将离开我发布的解决方案,希望将来对某人有用。
我这里的想法很少:
寻找像素
一种较慢但可靠的方法:知道您对黑色像素感兴趣,您可以遍历canvas的所有像素并记住4个数字:您找到的任何黑色像素的最小和最大x和y坐标。 你可以为它添加一些余量,你将拥有一个始终点亮的矩形,即使库在未来的版本中开始在不同的地方写入文本。
重绘canvas后,每次调整窗口大小时都必须重新计算它。
为了使其正常工作,您的文本必须采用canvas上其他任何位置都不存在的颜色(目前就是这种情况)。
猜猜坐标
您可以猜测坐标 – 或者更准确地计算它们 – 知道它是如何绘制的。 似乎大圆圈占据了较小尺寸上的整个空间(在这种情况下为整个高度),并且在另一个轴上居中(在这种情况下,它是水平居中的)。
使用该知识,您可以以类似于此的方式计算仅具有canvas尺寸的内(白色)圆的大小和位置:
查找canvas的宽度或高度较小,并将其用作基数。 将它除以2会得到R
– 大圆的半径。 除以R/2
将粗略地给出r
– 小的内部白色圆的半径。 计算x
和y
– canvas中心的坐标 – x = width/2
和y = height /2
。
现在,您可以尝试文本所在的矩形。 它可能类似于:左边和右边的x - 0.7*r
和x + 0.7*r
以及底边和顶边的y - 0.4*r
和y + 0.4*r
。 这些仅仅是示例,您可以将这些数字调整到令您满意的程度。
这些数字不一定非常完美,因为无论如何你应该在文本周围留下几个像素的边距。
这里它可能无法工作,当图书馆开始在未来完全不同地绘制它,但它可能不会像这样的简单图表。
好处是你不必寻找特定的颜色,并且检查每个像素的计算速度会更快。
同样,如果图表以不同的大小重新绘制,则必须重新计算这些维度。
更改您的pluginService
另一个想法是改变你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-*
属性(如canvas本身)。
最后一个解决方案很好,因为你不必担心颜色,你不必猜测文本的放置位置,因为你完全知道,而且你不必担心重新计算这个问题,因为每次绘制文本本身时代码都会运行。
缺点是您需要修改pluginService
。
canvas上的div
另一种方法是在canvas上放置div并将文本放在div而不是canvas中。 这样你就可以方便地添加事件监听器等。
你可以这样做:
把你的canvas和空div(或更多的div)放在一个更大的div中:
您可以添加更多div,例如text1以获取更多圆圈/图表,如下所示:
添加此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; }
将hover时变为红色。
canvas上的空div
在评论中未包含在问题中的其他要求之后,这是另一个更新。
您可以在上面的版本中使用此HTML:
但是这次你可以在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可见 – 只需删除border: 1px solid red;
从CSS中删除它)
在canvas上的大空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(); } });
见DEMO 。 它不依赖于px
的em
大小和文本大小。 这一行改变了正方形的大小:
var a = d / 2.5;
您可以尝试将2.5更改为2或3或其他内容。
在canvas上的圆形空div
这是一个变体,它使用border-radius
来制作圆形div而不是矩形,并且似乎完美地填充内部白色圆圈。
HTML:
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 。