Chart.js - How to offset bars from ZeroLine - javascript

I've spent hours already trying to figure out how to offset the horizontal bars from the zero line on X-Axis so it doesn't overlap when the width of the line is bigger than 1.
Appreciate all the help.
Example is here on CodePen (hope it will show up): https://codepen.io/RomanKl/pen/mzmegG
var barOptions = {
tooltips: {
enabled: false
},
hover :{
animationDuration:0
},
scales: {
xAxes: [{
ticks: {
beginAtZero:true,
min: 0,
max: 10000,
fontFamily: "'Open Sans Bold', sans-serif",
fontSize:12,
callback: function(value, index, values) {
return Math.round(value/1000) + 'k';
}
},
scaleLabel:{
display:false
},
gridLines: {
color: ['#000', '#efefef', '#efefef', '#efefef', '#efefef', '#efefef', '#efefef', '#efefef', '#efefef', '#efefef', '#efefef'],
lineWidth: [4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
zeroLineWidth: 4,
zeroLineColor: '#000',
},
}],
yAxes: [{
gridLines: {
display: false,
},
ticks: {
fontFamily: "'Open Sans Bold', sans-serif",
fontSize:14,
},
}]
},
legend:{
display:false
},
animation: {
onComplete: function () {
var chartInstance = this.chart;
var ctx = chartInstance.ctx;
ctx.textAlign = "left";
ctx.font = "1.6rem Open Sans";
ctx.fillStyle = "#fff";
Chart.helpers.each(this.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
Chart.helpers.each(meta.data.forEach(function (bar, index) {
data = dataset.data[index];
data = data.toFixed(0).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
if(i==0){
ctx.fillText(data, 80, bar._model.y+4);
} else {
ctx.fillText(data, bar._model.x-25, bar._model.y+4);
}
}),this)
}),this);
}
},
};
var ctx = document.getElementById("Chart1");
var myChart = new Chart(ctx, {
type: 'horizontalBar',
borderSkipped: 'bottom',
data: {
labels: ["Aug.'17", "Aug.'18"],
datasets: [{
data: [6336, 6892],
backgroundColor: "rgba(63,103,126,1)",
hoverBackgroundColor: "rgba(50,90,100,1)"
}]
},
options: barOptions,
});

So I've brutally hacked it in the end. I've tried adding a border and then skip it everywhere except the start where I would set the colour to transparent but guess what? Border doesn't show up at the start. It's a requested feature not yet implemented in chart.js as of this posting.
In the end I've used a diabolical solution of using two stacked datasets while setting the first one via data to the desired offset and then setting backgroundColor to transparent and shifting data labels to the left via plugin.
you can see the result in the codepen link posted in the question.

Related

How to get radar chart coordinates using getValueForDistanceFromCenter with Chart.js?

I am experimenting with Chart.js to build radar charts. I mastered the basics (see basic chart below), but I would like to use the x y coordinates of the graph to place texts directly on the canvas.
After some digging, I found out that it is not possible to use getValueForPixel or getPixelForTick in a radar chart. See this github issue. In the connecting thread, a new method getValueForDistanceFromCenter is introduced.
As I understand it, it would be possible to calculate the distance from the center with this method, and use it to get coordinates. I searched the Chart.js documentation and other sites, but cannot find any code examples or information on how to implement this.
Can somebody point me in the right direction how to implement the method in the code?
var data = {
labels: ["Ball Skills", "Shooting", "Physical"],
datasets: [{
label: [`ikke`, `jij`],
backgroundColor: "rgba(38,120,255,0.2)",
borderColor: "rgba(38,120,255, 1)",
data: [90, 90, 90]
}]
};
var options = {
responsive: true,
tooltips: false,
title: {
text: 'Basic example',
display: true,
position: `bottom`,
},
scale: {
angleLines: {
display: true
},
ticks: {
suggestedMin: 0,
suggestedMax: 100,
stepSize: 25,
maxTicksLimit: 11,
display: false,
}
},
legend: {
labels: {
padding: 10,
fontSize: 14,
lineHeight: 30,
},
},
};
var myChart = new Chart(document.getElementById("chart"), {
type: 'radar',
data: data,
options: options
});
The radialLinear scale (in version 2.9.4 that I have seen your are using version 2) there is the method getValueForDistanceFromCenter(value) to get the distance from center but there is another method getPointPositionForValue(index, value) which can provide you the point at a specif index of your data.
To use them and to draw what you want on chart using those points, you need to implement a plugin.
In the below snippet, I'm drawing a rect between the points at a specific value.
const ctx = document.getElementById("myChart");
const data = {
labels: ["Ball Skills", "Shooting", "Physical"],
datasets: [{
label: [`ikke`, `jij`],
backgroundColor: "rgba(38,120,255,0.2)",
borderColor: "rgba(38,120,255, 1)",
data: [50, 50, 50]
}]
};
const options = {
responsive: true,
tooltips: false,
title: {
text: 'Basic example',
display: true,
position: `bottom`,
},
scale: {
angleLines: {
display: true
},
ticks: {
suggestedMin: 0,
suggestedMax: 100,
stepSize: 25,
maxTicksLimit: 11,
display: false,
}
},
legend: {
labels: {
padding: 10,
fontSize: 14,
lineHeight: 30,
},
},
};
const plugin = {
id: 'getDistance',
afterDraw(chart) {
const c = chart.ctx;
const rScale = chart.scale;
c.save();
chart.data.datasets[0].data.forEach(function(item, index) {
const point = rScale.getPointPositionForValue(0.5 + index, 50);
c.beginPath();
c.fillStyle = 'red';
c.fillRect(point.x - 5, point.y - 5, 10, 10);
c.fill();
});
c.restore();
}
};
const myChart = new Chart(ctx, {
type: 'radar',
plugins: [plugin],
data: data,
options: options
});
.myChartDiv {
max-width: 600px;
max-height: 400px;
}
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.9.4/dist/Chart.min.js"></script>
<html>
<body>
<div class="myChartDiv">
<canvas id="myChart" width="600" height="400"/>
</div>
</body>
</html>

How to plot a single value with line in line chart graph using charts.js?

I need to plot a single value in line chart. Currently i am using charts.JS library for line graph purpose.
The data will be varied some times i'll get the single data inside the data set at that time i need to plot the single value with line in the line chart.
I tried with the charts.js annotation plugin but it wasn't met my requirements. which is like it wis overlapping the plotted point in the graph area.
CODE WHICH I HAD TRIED
createLineChart() {
this.lineChart = new Chart(this.lineCanvas.nativeElement, {
type: "line",
data: {
labels:[],
datasets: [
{
fill: false,
backgroundColor: "#0168FF",
borderColor: "#0168FF",
pointBackgroundColor: "white", // wite point fill
pointBorderWidth: 1, // point border width
lineTension: 0,
pointBorderColor: "blue",
pointRadius: 4,
},
],
},
options: {
scales: {
yAxes: [
{
ticks: {
padding: 20,
beginAtZero: true,
min: 0,
stepSize: 100,
},
gridLines: {
drawBorder: false,
},
},
],
xAxes: [
{
// offset: true,
ticks: {
display: false,
//beginAtZero: true,
min: 0,
},
gridLines: {
zeroLineColor: "transparent",
drawBorder: false,
display: false,
},
//offset:true,
},
],
legend: {
display: false,
},
tooltips: {
enabled: false,
},
},
drawTime: "afterDraw", // (default)
} as ChartOptions,
// plugins: [ChartAnnotation]
},
});
}
To generate dynamic data and plot in the graph area.
generateRandomDataSet(size) {
let yaxisArr = [];
let xaxisArr = [];
let random_data:any = this.getRandomData(size)
let maxYTickVal = Math.max.apply(Math, random_data.map((val) => {return val.yaxis}));
let maxVal = Math.ceil((maxYTickVal+1) / 10) * 10
for(let data of random_data) {
yaxisArr.push(data.yaxis)
xaxisArr.push(data.xaxis)
}
console.log("X-Axis array values : "+xaxisArr)
console.log("Y-Axis array values : "+yaxisArr)
this.lineChart.data.datasets[0].data = yaxisArr
this.lineChart.config.data.labels = []
this.lineChart.config.data.labels = xaxisArr
this.lineChart.config.options.scales.yAxes[0].ticks.max =maxVal
this.lineChart.config.options.scales.yAxes[0].ticks.stepSize = maxVal/2
this.lineChart.update()
}
getRandomData(arraySize) {
let data = []
for(var i=1; i<=arraySize; i++) {
let number = Math.floor(Math.random() * 200) + 1
data.push({'xaxis':i,'yaxis':number})
}
return data
}
with the above code i am getting like
what i need to have
You can define an animation.onComplete function as follows to draw the line in case a single data value is present.
animation: {
onComplete: e => {
var ctx = chart.chart.ctx;
var data = chart.config.data.datasets[0].data;
if (data[0] == null) {
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
var y = yAxis.getPixelForValue(data[1]);
ctx.save();
ctx.globalCompositeOperation='destination-over';
ctx.strokeStyle = 'blue'
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(xAxis.left, y);
ctx.lineTo(xAxis.right, y);
ctx.stroke();
ctx.restore();
}
}
},
This function expects the data array to be of format [null, <value>, null] in case a single value is present, otherwise it will be hard to horizontally center the data point (see this answer). It's up to you to change the generateRandomDataSet() function in a way that it provides such data.
Please have a look at your changed code below.
const chart = new Chart('line-chart', {
type: "line",
data: {
labels: ['', 'A', ''],
datasets: [{
data: [null, 120, null],
fill: false,
backgroundColor: "#0168FF",
borderColor: "#0168FF",
pointBackgroundColor: "white",
pointBorderWidth: 1,
lineTension: 0,
pointBorderColor: "blue",
pointRadius: 4,
}],
},
options: {
legend: {
display: false
},
tooltips: {
enabled: false
},
animation: {
onComplete: e => {
var ctx = chart.chart.ctx;
var data = chart.config.data.datasets[0].data;
if (data[0] == null) {
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
var y = yAxis.getPixelForValue(data[1]);
ctx.save();
ctx.globalCompositeOperation='destination-over';
ctx.strokeStyle = 'blue'
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(xAxis.left, y);
ctx.lineTo(xAxis.right, y);
ctx.stroke();
ctx.restore();
}
}
},
scales: {
yAxes: [{
ticks: {
padding: 20,
min: 0,
stepSize: 100
},
gridLines: {
drawBorder: false
}
}],
xAxes: [{
ticks: {
display: false
},
gridLines: {
zeroLineColor: "transparent",
drawBorder: false,
display: false
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="line-chart" height="80"></canvas>

Chart.js : How I change the x axes ticks labels alignment in any sizes?

How can I move my labels on my x axes in between another x axes label. Nothing seems to work and I was unable to find anything on the docs. Is there a workaround? I'm using line chart time series.
https://www.chartjs.org/samples/latest/scales/time/financial.html
Currently, with the code I have its generating the figure below:
var cfg = {
elements:{
point: {
radius: 4
}
},
data: {
datasets: [
{
label: 'vsy',
backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(),
borderColor: window.chartColors.red,
data: firstData,
type: 'line',
pointRadius: 2,
fill: false,
lineTension: 0,
borderWidth: 2
},
{
label: 'de vsy',
backgroundColor: color(window.chartColors.blue).alpha(0.5).rgbString(),
borderColor: window.chartColors.blue,
data: dataMaker(15),
type: 'line',
pointRadius: 2,
fill: false,
lineTension: 0,
borderWidth: 2
}
],
},
options: {
animation: {
duration: 0
},
scales: {
xAxes: [{
type: 'time',
distribution: 'series',
offset: true,
time: {
unit: 'month',
displayFormats: {
month: 'MMM'
}
},
ticks: {
autoSkip: true,
autoSkipPadding: 75,
sampleSize: 100
},
}],
yAxes: [{
gridLines: {
drawBorder: false
}
}]
},
tooltips: {
intersect: false,
mode: 'index',
}
}
};
This is what I have now:
I want the labels on the x-axis to be on center instead of below the y axis grid line.
Thanks to uminder, with his comment it solves the issue but now I have a conflicting tooltip which lie on a same grid. When I hover to april line first point it shows me mar 30 which lies just above it and vice versa.
I fixed it by changing the mode to nearest but why is it activating the another point?
The option you're looking for is offsetGridLines.
If true, grid lines will be shifted to be between labels.
xAxes: [{
...
gridLines: {
offsetGridLines: true
}
In most cases, this produces the expected result. Unfortunately it doesn't work for time axes as documented in Chart.js issue #403. Thanks to Antti Hukkanen, there exists a workaround.
Please have a look at below runnable code snippet to see how it works.
function generateData() {
var unit = 'day';
function randomNumber(min, max) {
return Math.random() * (max - min) + min;
}
function randomPoint(date, lastClose) {
var open = randomNumber(lastClose * 0.95, lastClose * 1.05).toFixed(2);
var close = randomNumber(open * 0.95, open * 1.05).toFixed(2);
return {
t: date.valueOf(),
y: close
};
}
var date = moment().subtract(1, 'years');
var now = moment();
var data = [];
for (; data.length < 600 && date.isBefore(now); date = date.clone().add(1, unit).startOf(unit)) {
data.push(randomPoint(date, data.length > 0 ? data[data.length - 1].y : 30));
}
return data;
}
var TimeCenterScale = Chart.scaleService.getScaleConstructor('time').extend({
getPixelForTick: function(index) {
var ticks = this.getTicks();
if (index < 0 || index >= ticks.length) {
return null;
}
// Get the pixel value for the current tick.
var px = this.getPixelForOffset(ticks[index].value);
// Get the next tick's pixel value.
var nextPx = this.right;
var nextTick = ticks[index + 1];
if (nextTick) {
nextPx = this.getPixelForOffset(nextTick.value);
}
// Align the labels in the middle of the current and next tick.
return px + (nextPx - px) / 2;
},
});
// Register the scale type
var defaults = Chart.scaleService.getScaleDefaults('time');
Chart.scaleService.registerScaleType('timecenter', TimeCenterScale, defaults);
var cfg = {
data: {
datasets: [{
label: 'CHRT - Chart.js Corporation',
backgroundColor: 'red',
borderColor: 'red',
data: generateData(),
type: 'line',
pointRadius: 0,
fill: false,
lineTension: 0,
borderWidth: 2
}]
},
options: {
animation: {
duration: 0
},
scales: {
xAxes: [{
type: 'timecenter',
time: {
unit: 'month',
stepSize: 1,
displayFormats: {
month: 'MMM'
}
},
gridLines: {
offsetGridLines: true
}
}],
yAxes: [{
gridLines: {
drawBorder: false
}
}]
},
tooltips: {
intersect: false,
mode: 'index'
}
}
};
var chart = new Chart('chart1', cfg);
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart1" height="90"></canvas>
For chartJs v3 you can use offset property:
scales: {
x: {
grid: {
offset: true
}
},
...
}

chart.js - horizontalBar stacked chart with xaxis as dates

I am looking to create a bar chart but have had a hell of a time trying to find what I need through chart.js 2.1
Goal is to have a chart that looks like this:
sample image
So far I have been unsuccessful getting the xAxis to reflect dates.
Here's the story:
yAxis are vendor groups that have stacking due dates (the dates may/may not match up with another vendor group). Each stack has a numeric value as well, but this does not affect the layout of the chart, just an overlay of data.
xAxis are all the due dates, starting with the current date and moving on.
Any assistance or jfiddle examples would be EXTREMELY helpful!
Here's what I have today: https://jsfiddle.net/npk3xdb0/
HTML:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/moment#latest/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.0/Chart.min.js"></script>
<canvas id="bar-chart-horizontal" width="200" height="50"></canvas>
JAVASCRIPT:
new Chart(document.getElementById("bar-chart-horizontal"), {
type: "horizontalBar",
data: {
labels: ['V1','V2','V3'],
datasets: [{ data: [50, 60, 75], backgroundColor: "#8C453E"},{ data: [50, 70, 85], backgroundColor: "#684796"},{ data: [50, 53, 111], backgroundColor: "#427780"},{ data: [19, , ], backgroundColor: "#599647"}]
},
options: {
tooltips: { enabled: false },
hover :{ animationDuration:0 },
scales: {
xAxes: [{
ticks: { beginAtZero:true, fontFamily: "'Open Sans Bold', sans-serif", fontSize:11 },
scaleLabel:{ display:false },
gridLines: { },
stacked: true
}],
yAxes: [{
gridLines: { display:false, color: "#fff", zeroLineColor: "#fff", zeroLineWidth: 0 },
ticks: { fontFamily: "'Open Sans Bold', sans-serif", fontSize:11 },
stacked: true
}]
},
legend:{ display:false },
animation: {
onComplete: function () {
var chartInstance = this.chart;
var ctx = chartInstance.ctx;
ctx.textAlign = "right";
ctx.font = "9px Open Sans";
ctx.fillStyle = "#fff";
Chart.helpers.each(this.data.datasets.forEach(function (dataset, i) {
var meta = chartInstance.controller.getDatasetMeta(i);
Chart.helpers.each(meta.data.forEach(function (bar, index) {
data = dataset.data[index];
//if(i==0){
// ctx.fillText(data, 20, bar._model.y+4);
//} else {
ctx.fillText(data, bar._model.x-3, bar._model.y+4);
//}
}),this)
}),this);
}
},
pointLabelFontFamily : "Quadon Extra Bold",
scaleFontFamily : "Quadon Extra Bold",
}
});

Update ChartJs chart using $scope in AngularJS

I'm having some issues when trying to update a chart's data using $scope.
I know there's a function to update charts myChart.update(); but I can't get to update the char when I put it in a $scope.
The following code gets the chart's data and then tries to update the chart. The problem comes at $scope.lineChart.update();. It looks like chartjs can't detect any changes.
The following code is executed after triggering a select, so the chart has an initial data and the following code just tries to update it.
This does not work: $scope.lineChart.update();
$scope.getLineChartMaxData().then(function () {
$scope.getLineChartMinData().then(function () {
$scope.lineChart.update();
});
});
The chart function:
$scope.fillLineChart = function () {
console.log("FILLING LINE CHART");
const brandProduct = 'rgba(0,181,233,0.5)'
const brandService = 'rgba(0,173,95,0.5)'
var data1 = $scope.lineChartMaxWeekData;
var data2 = $scope.lineChartMinWeekData;
var maxValue1 = Math.max.apply(null, data1)
var maxValue2 = Math.max.apply(null, data2)
var minValue1 = Math.min.apply(null, data1)
var minValue2 = Math.min.apply(null, data2)
var maxValue;
var minValue;
if (maxValue1 >= maxValue2) {
maxValue = maxValue1;
} else {
maxValue = maxValue2;
}
if (minValue1 >= minValue2) {
minValue = minValue2;
} else {
minValue = minValue1;
}
$scope.minValue = minValue;
$scope.maxValue = maxValue;
var ctx = document.getElementById("recent-rep-chart");
if (ctx) {
ctx.height = 250;
$scope.lineChart = new Chart(ctx, {
type: 'line',
data: {
labels: $scope.lineChartMaxWeekLabels,
datasets: [{
label: 'Valor',
backgroundColor: brandService,
borderColor: 'transparent',
pointHoverBackgroundColor: '#fff',
borderWidth: 0,
data: data1
},
{
label: 'My Second dataset',
backgroundColor: brandProduct,
borderColor: 'transparent',
pointHoverBackgroundColor: '#fff',
borderWidth: 0,
data: data2
}
]
},
options: {
maintainAspectRatio: true,
legend: {
display: false
},
responsive: true,
scales: {
xAxes: [{
gridLines: {
drawOnChartArea: true,
color: '#f2f2f2'
},
ticks: {
fontFamily: "Poppins",
fontSize: 12
}
}],
yAxes: [{
ticks: {
beginAtZero: true,
maxTicksLimit: 5,
stepSize: 50,
max: maxValue,
fontFamily: "Poppins",
fontSize: 12
},
gridLines: {
display: true,
color: '#f2f2f2'
}
}]
},
elements: {
point: {
radius: 0,
hitRadius: 10,
hoverRadius: 4,
hoverBorderWidth: 3
}
}
}
});
}
};
UPDATE: $scope.lineChart.destroy(); works well, but I don't want to destroy the chart and build it again because it is built with another sizes.

Categories