[Chartjs]-Chart.JS plugin, draw after animation

1👍

Here’s what I came up with (snippet below)

The errorbars can either be fixed to the data or shown independantly, and animate in / are hidden / revealed with the data.

The plugin can add x or y axis errors, repesented as bars (with/without caps) or ovals / circles (filled or transparent).

"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👍

I had a similar issue with rendered text in

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

To fix, inside the meta.data.forEach(function (bar, index) { ... } loop, simply use:

  var position = bar.tooltipPosition();
  ctx.fillText(Math.round(dataset.data[index]), position.x, position.y);

(This is in @Yobmod’s post, but it seems the key is to use the bar.tooltipPosition() location for the location of whatever is rendered.)

Leave a comment