我试图编写一个chart.js(v.2.7)插件来显示我的散点图/线图上的错误栏。 我设法从图表中获取像素位置并在画布上绘制错误栏,但我无法让时机看起来正确。我希望它们在线条动画后出现,然后在数据集被隐藏 - >显示时保持附着。
我试过了:
afterDraw
/ afterDataset(s)Draw
/ beforeDraw
挂钩:错误栏已经在线条动画之前的情节中显示(如示例所示)。当隐藏 - >显示时,错误栏就位。afterRender
/ afterEvent
hook:在动画完成后绘制它们,但每次隐藏数据集时重绘它们 - >显示(暂停后)beforeRender
/任何早期的挂钩:不提供错误栏setTimout()
,或者在其中的不同位置:什么都不做sleep()
:减慢整个动画的速度,但错误栏不受影响。options.animation.onComplete
有没有办法让错误栏像示例一样运行,但是初始外观发生在线动画之后(带插件?)
var errorbarPlugin = {
calcPoints: function(chartInstance, dataList){
var ds = chartInstance.data.datasets
var meta = chartInstance.getDatasetMeta(0)
var yScale = chartInstance.scales[meta.yAxisID];
var xScale = chartInstance.scales[meta.xAxisID];
var yList = []; var xList = [];
for(var i = 0; i < dataList.length; i++){
var yValue = dataList[i].y
var yPixel = yScale.getPixelForValue(yValue)
yList.push(yPixel)
var xValue = dataList[i].x
var xPixel = xScale.getPixelForValue(xValue)
xList.push(xPixel)
}
return {yList: yList, xList: xList}
},
calcErrorbars: function(chartInstance, ds_num){
var ds = chartInstance.data.datasets
var data_list = ds[ds_num].data
var isHidden = ds[ds_num]._meta[Object.keys(chartInstance.data.datasets[ds_num]._meta)[0]].hidden;
var yList = this.calcPoints(chartInstance, data_list).yList
var xList = this.calcPoints(chartInstance, data_list).xList
if(ds[ds_num].errors){
var errors = ds[ds_num].errors
} else {errors = 0}
return [xList, yList, errors, isHidden]
},
drawErrorbars: function(chartInstance){
var ctx = chartInstance.chart.ctx
var ds = chartInstance.data.datasets
for(var ds_num = 0; ds_num < ds.length; ds_num++){
var errCalc = this.calcErrorbars(chartInstance, ds_num)
var isHidden = errCalc[3]
var yList = errCalc[1]
var xList = errCalc[0]
var errors = errCalc[2]
var errWidth = 3
var capLen = 5
if(!isHidden){
for(var k = 0; k < xList.length; k++){
ctx.strokeStyle = "red"
ctx.beginPath();
ctx.moveTo(xList[k], yList[k]-errors[k]);
ctx.lineTo(xList[k], yList[k]+errors[k]);
ctx.moveTo(xList[k]-capLen, yList[k]+errors[k]);
ctx.lineTo(xList[k]+capLen, yList[k]+errors[k]);
ctx.moveTo(xList[k]-capLen, yList[k]-errors[k]);
ctx.lineTo(xList[k]+capLen, yList[k]-errors[k]);
ctx.stroke()
}
}
}
},
afterDatasetsDraw: function(chartInstance) {
this.drawErrorbars(chartInstance)
},
}
&#13;
<html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.js"></script>
<script type="text/javascript" src="{% static 'js/charts/errorbarPlugin3.js' %}"></script>
<div id="canvas-holder" class="col-sm-3">
<canvas id="chart-gamma" width="200" height="200"/></canvas>
</div>
<script defer>
var gammaChartData = {
datasets: [
{
label: 'Red',
data: [{x: 15, y: 30}, {x: 35, y: 17}, {x: 55, y: 37}, {x: 72, y: 45},],
borderColor: "red",
errors: [10, 28, 30, 34],
},
]
}
var ctx_gamma = document.getElementById("chart-gamma").getContext("2d");
window.onload = function() {
var gamma_chart = new Chart(ctx_gamma, {
type: 'scatter',
data: gammaChartData,
plugins: [errorbarPlugin],
options: {showLines: true},
});
};
</script>
</body>
</html>
&#13;
编辑:通过删除格式和默认选项缩短代码段
答案 0 :(得分:0)
这是我想出的(下面的代码段)
错误栏既可以固定到数据,也可以独立显示,并且/中的动画与数据一起隐藏/显示。
该插件可以添加x或y轴错误,表示为条形(带/不带帽)或椭圆形/圆形(实心或透明)。
"use strict";
var errorbarPlugin = {
afterDraw: function (chart) {
var type = chart.config.type;
var plugConfig = chart.config.options.errorbarPlugin;
if (plugConfig) {
if (plugConfig.showErrors) {
var showErrors = plugConfig.showErrors;
}
}
else
showErrors = true;
if (showErrors !== false) {
if (["line", "scatter"].includes(type)) {
errorbarPlugin.scatterErrorbars(chart);
}
else if (type == "bar") {
console.log("Bar charts not supported yet");
}
}
},
scatterErrorbars: function (chart) {
var ctx = chart.ctx;
var plugConfig = chart.config.options.errorbarPlugin;
chart.data.datasets.forEach(function (dataset, i) {
var ds = dataset;
var meta = chart.getDatasetMeta(i);
var showErrors;
(ds.showErrors === false) ? showErrors = false : showErrors = true;
var errWidth;
(ds.errWidth) ? errWidth = ds.errWidth : errWidth = 1;
var showCap;
(ds.showCap) ? showCap = ds.showCap : showCap = true;
var capLen;
(ds.capLen) ? capLen = ds.capLen : capLen = 3;
var errStyle;
(ds.errStyle) ? errStyle = ds.errStyle : errStyle = "T";
var errFillColor;
(ds.errFillColor) ? errFillColor = ds.errFillColor : errFillColor = "rgba(0,0,0,0)";
if (!meta.hidden && showErrors) {
meta.data.forEach(function (element, index) {
var x_point = element._model.x;
var y_point = element._model.y;
var errColor;
(ds.errColor) ? errColor = ds.errColor : errColor = element._view.borderColor;
var dataPoint = ds.data[index];
var yError;
var xError;
if (typeof (dataPoint) === "object" && 'r' in dataPoint) {
yError = dataPoint.r;
}
else if (ds.errors) {
yError = ds.errors[index];
}
else {
yError = null;
}
if (typeof (dataPoint) === "object" && dataPoint.e) {
xError = dataPoint.e;
}
else if (ds.xErrors) {
xError = ds.xErrors[index];
}
else {
xError = null;
}
var position = element.tooltipPosition();
if (errStyle == "circle") {
ctx.beginPath();
ctx.arc(position.x, position.y, yError, 0, 2 * Math.PI, false);
if (ds.hidden === true && meta.hidden === null) {
ctx.strokeStyle = "rgba(0,0,0,0)";
ctx.fillStyle = "rgba(0,0,0,0)";
}
else {
ctx.strokeStyle = errColor;
ctx.fillStyle = errFillColor;
}
console.log(meta.hidden);
ctx.fill();
ctx.stroke();
}
else if (errStyle == "oval" || errStyle == "ellipse") {
if (xError) {
var scaleFac = (xError) / yError;
}
else
scaleFac = 10 / yError;
ctx.beginPath();
ctx.save();
ctx.scale(scaleFac, 1);
ctx.arc(position.x / scaleFac, position.y, yError, 0, 2 * Math.PI, false);
ctx.restore();
if (ds.hidden === true && meta.hidden === null) {
ctx.strokeStyle = "rgba(0,0,0,0)";
}
else {
ctx.strokeStyle = errColor;
}
ctx.stroke();
}
else {
ctx.beginPath();
ctx.moveTo(position.x, position.y - yError);
ctx.lineTo(position.x, position.y + yError);
if (xError) {
ctx.moveTo(position.x - xError, position.y);
ctx.lineTo(position.x + xError, position.y);
}
if (ds.hidden === true && meta.hidden === null) {
ctx.strokeStyle = "rgba(0,0,0,0)";
}
else {
ctx.strokeStyle = errColor;
}
ctx.stroke();
if (showCap) {
ctx.beginPath();
ctx.moveTo(position.x - capLen, position.y - yError);
ctx.lineTo(position.x + capLen, position.y - yError);
ctx.moveTo(position.x - capLen, position.y + yError);
ctx.lineTo(position.x + capLen, position.y + yError);
if (xError) {
ctx.moveTo(position.x - xError, position.y - capLen);
ctx.lineTo(position.x - xError, position.y + capLen);
ctx.moveTo(position.x + xError, position.y - capLen);
ctx.lineTo(position.x + xError, position.y + capLen);
}
ctx.stroke();
}
}
});
}
});
}
};
<!DOCTYPE html>
<!--DOCTYPE html -->
<html>
<head>
<script src='https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.min.js'></script>
<body>
<div style = "position:relative;
width:60%;" >
<div id="canvas-holder" class="col-sm-6">
<canvas id="chart-gamma" width="500" height="500"/></canvas>
</div>
<div id="canvas-holderbf2" class="col-sm-6">
<canvas id="chart-humid" width="500" height="500"/></canvas>
</div>
<script defer>
Chart.defaults.global.legend.display = true
Chart.defaults.global.legend.position = 'right'
// Chart.defaults.global.legend.onHover = function(){}
// Chart.defaults.global.legend.onClick = function(){}
Chart.defaults.global.legend.labels.usePointStyle = true
Chart.defaults.global.legend.labels.fontsize = 12
Chart.defaults.global.legend.labels.padding = 10
var gammaChartData = {
datasets: [
{
label: 'Eu',
data: [{x: 5, y: 45}, {x: 10, y: 100}, {x: 25, y: 120}, {x: 50, y: 125}, {x: 100, y: 150}, {x: 120, y: 250},],
borderColor: "red",
//fillColor: "pink",
errors: [15, 20, 30, 12, 10, 10],
xErrors: [3, 7, 16, 12, 12, 30, 10],
//hidden: true,
errColor: "blue",
errStyle: "circle",
errFillColor: "pink",
hidden: true,
errWidth: 2,
showCap: true,
capLen: 3,
showErrors: true,
},
{
label: 'Am',
data: [{x: 15, y: 85, r: 14}, {x: 25, y: 37, r: 8}, {x: 62, y: 135, r: 44},],
borderColor: "blue",
errColor: "red",
errStyle: "circle",
showErrors: true,
},
]
}
var options_gamma = {
animation: {
duration: 1000,
},
errorbarPlugin: {
showErrors: true,
marginsOfError: [100, 50, 10],
},
elements: {
line: { fill: false,
borderWidth: 1,
},
point: { radius: 0,
pointStyle: 'circle',
borderWidth: 1,
hitRadius: 18, //size if should hover
// hoverBorderWidth: 13,
hoverRadius: 10, //size when hovered
},
},
annotation: {
annotations: [{
id: 'h-line-01', // optional
type: 'line',
mode: 'horizontal',
scaleID: 'y-axis-0',
value: '125',
borderColor: 'red',
borderDash: [2, 2],
borderWidth: 2,
label: {
enabled: true,
backgroundColor: 'rgba(255,255,255,1)', // Background color of label, default below
//fontFamily: "sans-serif", // Font family of text, inherits from global
fontStyle: "normal", // Font style of text, default "bold"
fontSize: 12, // Font size of text, inherits from global
fontColor: "red",// Font color of text, default below
xPadding: 5,// Padding of label to add top/bottom, default below
yPadding: 5,// Radius of label rectangle, default below
cornerRadius: 10, // Anchor position of label on line, can be one of: top, bottom, left, right, center. Default below.
position: "left", // Adjustment along x-axis (left-right) of label relative to above number (can be negative)
// For horizontal lines positioned left or right, negative values move the label toward the edge, and negative values toward the center.
xAdjust: 290, // Adjustment along y-axis (top-bottom) of label relative to above number (can be negative)
// For vertical lines positioned top or bottom, negative values move the label toward the edge, and negative values toward the center.
yAdjust: 0, // Whether the label is enabled and should be displayed
// Text to display in label - default is null
content: "Max"
},
onClick: function(e) { // Fires when the user clicks this annotation on the chart (be sure to enable the event in the events array below).
}
}],
},
responsive: true,
showLines: true,
hoverMode: 'single', // should always use single for a scatter chart
legend: {},
scales: {
yAxes: [{
display: true,
position: 'left',
id: 'y-axis-0',
ticks: {min: 0, //beginAtZero:true,
max: 200,
//display: true,
//fontColor: "black"
},
scaleLabel: {display: true, labelString: 'Number'},
gridLines: {color: "black",
//display: true,
drawOnChartArea: false,
zeroLineColor: "black",
//drawTicks: true,
},
}],
xAxes: [{
display: true,
type: 'linear',
id: 'x-axis-0',
position: 'bottom',
ticks: {min: 0,
max: 100,
//display: true,
//fontColor: "black",
},
scaleLabel: {display: true, labelString: 'Volume'},
gridLines: {color: "black",
zeroLineColor: "black",
drawOnChartArea: false,
},
}],
},
}
var ctx_gamma = document.getElementById("chart-gamma").getContext("2d");
var humidChartData = {
datasets: [
{
label: 'B errors',
data: [{x: 5, y: 45}, {x: 10, y: 100}, {x: 25, y: 120}, {x: 50, y: 125}, {x: 100, y: 150}, {x: 120, y: 250},],
borderColor: "green",
errors: [15, 20, 30, 12, 10, 10],
xErrors: [3, 7, 16, 12, 12, 30, 10],
errStyle: "oval",
showLine: false,
errColor: "border",
//pointBackgroundColor: "white",
//pointBordercolor: "white",
backgroundColor: "rgba(0,0,0,0)",
hidden: true,
errWidth: 2,
showCap: true,
capLen: 3,
radius: 0,
showErrors: true,
},
{
label: 'B trend',
data: [{x: 5, y: 45}, {x: 10, y: 100}, {x: 25, y: 120}, {x: 50, y: 125}, {x: 100, y: 150}, {x: 120, y: 250},],
borderColor: "green",
errors: [15, 20, 30, 12, 10, 10],
xErrors: [3, 7, 16, 12, 12, 30, 10],
pointStyle: "line",
showErrors: false,
radius: 0,
},
{
label: 'B data',
data: [{x: 5, y: 45}, {x: 10, y: 100}, {x: 25, y: 120}, {x: 50, y: 125}, {x: 100, y: 150}, {x: 120, y: 250},],
borderColor: "green",
backgroundColor: "green",
errors: [15, 20, 30, 12, 10, 10],
xErrors: [3, 7, 16, 12, 12, 30, 10],
showErrors: false,
showLine: false,
},
{
label: '',
data: [],
borderColor: "rgba(0,0,0,0)",
backgroundColor: "rgba(0,0,0,0)",
},
{
label: 'C data',
data: [{x: 15, y: 85, r: 14}, {x: 25, y: 37, r: 8}, {x: 62, y: 135, r: 44},],
borderColor: "blue",
backgroundColor: "rgba(0,0,0,0)",
xErrors: [3, 7, 16, 12, 12, 30, 10],
showLine: true,
showErrors: true,
},
]
}
var options_humid = {
hoverMode: 'single',
elements: {
line: { fill: false,
borderWidth: 2,
},
point: { radius: 3,
pointStyle: 'circle',
borderWidth: 1,
hitRadius: 0,
// hoverBorderWidth: 13,
hoverRadius: 9,
},
},
responsive: true,
showLines: true,
hoverMode: 'single', // should always use single for a scatter chart
legend: {
labels: {
usePointStyle: true,
// generateLabels: function() { }
}
},
scales: {
yAxes: [{
display: true,
position: 'left',
id: 'y-axis-0',
ticks: {min: 0, //beginAtZero:true,
max: 300 },
scaleLabel: {display: true, labelString: 'Number'},
gridLines: {zeroLineColor: "black", },
}],
xAxes: [{
display: true,
type: 'linear',
id: 'x-axis-0',
position: 'bottom',
ticks: {min: 0,
max: 200 },
scaleLabel: {display: true, labelString: 'Month'},
gridLines: {zeroLineColor: "black", },
}],
},
}
var ctx_humid = document.getElementById("chart-humid").getContext("2d");
window.onload = function() {
var humidChart = new Chart(ctx_humid, {
type: 'line',
data: humidChartData,
plugins: [errorbarPlugin],
options: options_humid,
});
var gamma_chart = new Chart(ctx_gamma, {
type: 'scatter',
data: gammaChartData,
plugins: [errorbarPlugin],
options: options_gamma,
});
};
</script>
</div>
</body>
</html>
答案 1 :(得分:0)
我在渲染文字时遇到类似的问题
pow()
要解决此问题,请在float sqr_sum = a.real * a.real + a.imaginary * a.imaginary;
循环内使用:
plugins:[{
afterDatasetsDraw: function(chart, options) {
var ctx = chart.ctx;
ctx.font = Chart.defaults.global.defaultFontStyle;
ctx.fillStyle = "#666666";
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
chart.data.datasets.forEach(function(dataset, i) {
var meta = chart.controller.getDatasetMeta(i);
meta.data.forEach(function (bar, index) {
ctx.fillText(Math.round(dataset.data[index]), bar._model.x, bar._model.y);
});
});
}
}]
(这是@Yobmod的文章,但似乎关键是要使用bar.tooltipPosition()位置作为所呈现内容的位置。)