I have an experimental page where is a chart.js canvas.
Right now I add data points one by one from a webwocket data packet.
In that packet I got 200 data, which I want to add to my canvas, but not one by one, but all of it at once.
Here is my code:
<body>
<div id="lineGraph" class="chart-container">
<canvas id="line-chart"></canvas>
</div>
</body>
<script>
function myFunction1() {
maxDataPoints = document.getElementById("adatszelesseg").value;
if(maxDataPoints >= 200){
animateDur = 0;
animationisokay = false;
}else if (maxDataPoints < 200){
animateDur = 1000;
animationisokay = true;
}
init();
}
function removeData(){
dataPlot.data.labels.shift();
dataPlot.data.datasets[0].data.shift();
}
function addData(label, data) {
if(dataPlot.data.labels.length > maxDataPoints) removeData();
dataPlot.data.labels.push(label);
dataPlot.data.datasets[0].data.push(data);
dataPlot.update();
}
function init() {
dataPlot = new Chart(document.getElementById("line-chart"), {
type: 'line',
data: {
labels: [],
datasets: [{
backgroundColor: "rgba(159,170,174,0.2)",
borderWidth: 1.3,
hoverBackgroundColor: "rgba(232,105,90,0.8)",
hoverBorderColor: "orange",
data: [],
label: "Analog.Sign.(%)",
borderColor: "#f7f7f7",
fill: true
}]
},
options: {
legend: {
fontColor: "white",
labels: {
fontColor: "white",
fontSize: 18
}
},
animation: animationisokay,
scales: {
xAxes: [{
scaleLabel: {
fontColor: "white",
fontSize: 18,
display: true,
labelString: 'Sec / MicroSec'
}
}],
yAxes: [{
scaleLabel: {
fontColor: "white",
fontSize: 18,
display: true,
}
}]
},
responsiveAnimationDuration: animateDur,
maintainAspectRatio: true,
animation: {
animationEasing: 'linear',
duration: animateDur
}
}
})
};
webSocket1 = new WebSocket('ws://' + window.location.hostname + ':81/');
webSocket1.onmessage=function(a){
var t = a.data;
if(t.indexOf('}')>-1){
var j = t.substring(0, t.length - 1);
var today = new Date();
var m = today.getSeconds() + ":" + today.getMilliseconds();
console.log(j);
addData(m, j);
}};
</script>
So as you can see, I have a websocket event, where my data comes in as a.data.
I put it into a variable called t for further modification.
After that I have to look at it if it is have the string "}" than this is my data.
After that I add the j data to the canvas. And that is repeating while I got my 200 data to the canvas.
The data comes on the websocket almost each microsec.
The problem is I can see on the canvas that there is a delay in the display in each data, like if it is scrolling itself from right to the left.
I tried to collect my 200 data to an array, and pass that array to the canvas but it did not worked.
On the background I have a c++ server code.
I send the data like this:
for (int i = 0; i < datawidth; i++) {
String json = "";
json += String(vRealBuff[i] / (float)40.95);
json += "}";
webSocket.broadcastTXT(json.c_str(), json.length());
}
It is 200 data one by one, but it doesn't matter if I send all of it at once from the server, or collect it on the JavaScript code and add all of it at once.
The data does not display if I add an array of data to it.
Solved by collecting the data on an array and passing that array to the chart.
Related
I have been looking around for a way to add the value inside each bar in a horizontal bar chart, using chart.js but all posts seem to show how to add the label. Tooltip shows both label and value but I wanted to show the value so users can see the result without having to mouse-over the bar.
I put break points in the script to see if there is any property of "model" that contains the value but couldn't find one.
This is how I set up the chart including the animation section that shows the label instead of the value.
Dataset comes from an ajax call and chart is displayed in its onSuccess:
function OnSuccess_(response) {
var jResult = JSON.parse(response.d);
var chartLabels = [];
var chartData = []
$.each(jResult, function (index, val) {
chartLabels.push(val.TIMERANGE);
chartData.push(val.COUNT); // <--- This is what I want to display on each bar
});
var ctx = document.getElementById("rtChart").getContext("2d");
if (chartRespTime)
chartRespTime.destroy();
chartRespTime = new Chart(ctx, {
type: 'horizontalBar',
data: {
labels: chartLabels,
datasets: [{
label: "Response Time (ms)",
backgroundColor: ["#3e95cd", "#8e5ea2", "#3cba9f", "#e8c3b9", "#c45850"],
data: chartData
}]
},
options: {
title: {
display: true,
text: 'Database Response Time (milli-seconds)'
},
scales: {
xAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Count'
},
ticks: {
major: {
fontStyle: 'bold',
fontColor: '#FF0000'
}
}
}],
yAxes: [{
display: true,
scaleLabel: {
display: true,
labelString: 'Response Time (ms)'
}
}]
},
animation: {
onComplete: function () {
var ctx = this.chart.ctx;
ctx.font = Chart.helpers.fontString(Chart.defaults.global.defaultFontFamily, 'normal', Chart.defaults.global.defaultFontFamily);
ctx.textAlign = 'left';
ctx.textBaseline = 'bottom';
this.data.datasets.forEach(function (dataset) {
for (var i = 0; i < dataset.data.length; i++) {
var model = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._model,
left = dataset._meta[Object.keys(dataset._meta)[0]].data[i]._xScale.left;
ctx.fillStyle = '#444'; // label color
var label = model.label; // <--- need value not label
ctx.fillText(label, left + 15, model.y + 8);
}
});
}
},
maintainAspectRatio: false,
responsive: true,
showInlineValues: true,
centeredInllineValues: true,
tooltipCaretSize: 0
}
});
//console.log(ctx);
$('#rtChart').show();
}
There is no build in way to do this, but there is a verry good plugin that achieves what you want: the datalabels plugin https://chartjs-plugin-datalabels.netlify.app/samples/charts/bar.html
I created a dynamic line chart based on some input data. The intention is that the customer can indicate with a dropdown on which month the "Investment" should start.
So, for example, if the "Investment" does not start until month 6, then that line should only start at 6 on the x-axis. But the other lines "Case" and "ROI" should still just start at 1.
I've tried several things but to no avail.
I tried changing the x-axis "min ticks" based on the selection the user made, but that makes all lines start at another point instead of the "Investment" line only. Another problem is that every number before the selection then dissapears from the x-axis. But I really want to keep every number from 1-60, even if the user chooses to start the "Investment" on month 10, for example.
I would really appreciate some help! Thanks.
Here's my fiddle: https://jsfiddle.net/js5pha24/
var options = {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Case',
data: [],
backgroundColor: 'rgba(152,164,135, 0.5)',
borderColor: 'rgb(152,164,135)',
fill: false
}, {
label: 'Case',
data: [],
backgroundColor: 'rgba(145,139,167, 0.5)',
borderColor: 'rgb(145,139,167)',
fill: false
}, {
label: 'Case',
data: [],
backgroundColor: 'rgba(206,157,206, 0.5)',
borderColor: 'rgb(206,157,206)',
fill: false
}]
},
options: {
legend: {
display: true,
position: "top"
},
scales: {
xAxes: [{
ticks: {
beginAtZero: true,
autoSkip: true,
maxRotation: 0,
minRotation: 0
}
}],
yAxes: [{
ticks: {
callback: value => {
return "€ " + value;
}
}
}]
}
}
}
for (let i = 1; i <= 60; i++) {
options.data.labels.push(i);
const caseMonth = 118187 * i;
options.data.datasets.find(set => set.label === "Case").data.push(caseMonth);
const investMonth = 500000 + (20000 * i);
options.data.datasets.find(set => set.label === "Investment").data.push(investMonth);
const roiMonth = caseMonth - investMonth;
options.data.datasets.find(set => set.label === "ROI").data.push(roiMonth);
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
canvas { background-color : #eee;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.js"></script>
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
</body>
You can put null values on the chart data so one line can start after the others. For example if you want the investment line start at month 10, you can replace the the first ten investMonth values with null.
If understood correctly you still want to use the investMonth value in the roiMonth calculation so I created "investMonthValue" so only investment will get null if it is less than investmentStartMonth.
let investmentStartMonth = 10
for (let i = 1; i <= 60; i++) {
options.data.labels.push(i);
const caseMonth = 118187 * i;
options.data.datasets.find(set => set.label === "Case").data.push(caseMonth);
let investMonth = 500000 + (20000 * i);
let investMonthValue = i<investmentStartMonth?null:investMonth
options.data.datasets.find(set => set.label === "Investment").data.push(investMonthValue);
const roiMonth = caseMonth - investMonth;
options.data.datasets.find(set => set.label === "ROI").data.push(roiMonth);
}
My web page loads and automatically creates a chart with data it pulls from an API I wrote.
Ive also got a HTML input that allows me to select the month. I have added an event listener to that input that triggers a function to draw a new chart based on the month i have selected (it recalls the api too with these new parameters).
It looked like it worked, but on further inspection, I realised that the previous chart was behind the new chart.
Is there a way i can remove the old chart?
<div class="chart_div" style="max-height: 400px; max-width: 800px; margin: 5px">
<label for="monthSelector">Start month:</label>
<input
type="month"
id="monthSelector"
name="start"
min="{{min_date}}"
max="{{today_date}}"
value="{{today_date}}"
/>
<canvas id="myChart" width="400" height="400"> </canvas>
</div>
<script>
var canvas = document.getElementById("myChart");
const context = canvas.getContext("2d");
var monthSelector = document.getElementById("monthSelector");
// event listener for month slider
monthSelector.addEventListener("input", function () {
selected_date = monthSelector.value + "-01";
drawChart(selected_date);
});
var today = monthSelector.value + "-01";
// Draw chart upon loading page
drawChart(today);
function drawChart(date) {
x_labels = [];
data_set_scratches = [];
data_set_medical_scores = [];
context.clearRect(0, 0, canvas.width, canvas.height);
var url_scratches =
"http://127.0.0.1:8000/api/get-daily-scratch-count/" + date + "/";
var url_medical_scores =
"http://127.0.0.1:8000/api/get-daily-medical-score/" + date + "/";
// get x label based on dates of selected month
var date_vals = date.split("-");
var num_days = getDaysInMonth(date_vals[1], date_vals[0]);
console.log(num_days);
for (var i = 1; i <= num_days; i++) {
var num = minTwoDigits(i);
x_labels.push(num);
}
// call api to fetch the data
Promise.all([
fetch(url_scratches)
.then((res) => res.json())
.then(function (data) {
var scratches = data;
var dateIndex = 0;
var scratchesIndex = 0;
while (scratchesIndex < scratches.length) {
var scratchDates = scratches[scratchesIndex].date.split("-"); // Splits date into list ["YYYY", "MM", "DD"]
// if dates are equal, push total and increase both index
if (scratchDates[2] == x_labels[dateIndex]) {
data_set_scratches.push(scratches[scratchesIndex].total);
dateIndex += 1;
scratchesIndex += 1;
// if dates are not equal, push 0 and increase only date index
} else {
data_set_scratches.push(0);
dateIndex += 1;
}
}
console.log(data_set_scratches);
}),
fetch(url_medical_scores)
.then((res) => res.json())
.then(function (data) {
var medicalScores = data;
var dateIndex = 0;
var scoreIndex = 0;
while (scoreIndex < medicalScores.length) {
var scoreDates = medicalScores[scoreIndex].date.split("-"); // Splits date into list ["YYYY", "MM", "DD"]
// if dates are equal, push score then increase both index
if (scoreDates[2] == x_labels[dateIndex]) {
data_set_medical_scores.push(medicalScores[scoreIndex].score);
dateIndex += 1;
scoreIndex += 1;
// if dates are not equal, push 0 and increase only date index
} else {
data_set_medical_scores.push(0);
dateIndex += 1;
}
}
console.log(data_set_medical_scores);
}),
]).then(function () {
// Creat chart from api Data
let chartTest = new Chart(myChart, {
type: "line",
data: {
labels: x_labels,
datasets: [
{
label: "Scratch Total",
fill: false,
data: data_set_scratches,
borderColor: "green",
borderWidth: 1,
lineTension: 0,
backgroundColor: "red",
pointBackgroundColor: "red",
pointBorderColor: "red",
pointHoverBackgroundColor: "red",
pointHoverBorderColor: "red",
},
{
data: data_set_medical_scores,
label: "Medical Score",
fill: false,
borderColor: "orange",
borderWidth: 1,
lineTension: 0,
backgroundColor: "#e755ba",
pointBackgroundColor: "#55bae7",
pointBorderColor: "#55bae7",
pointHoverBackgroundColor: "#55bae7",
pointHoverBorderColor: "#55bae7",
},
],
},
options: {
title: {
display: true,
text: "Daily Scratches/Medical Scores",
},
scales: {
yAxes: [
{
ticks: {
beginAtZero: true,
},
},
],
xAxis: [
{
ticks: {
stepSize: 1,
autoSkip: false,
},
},
],
},
},
});
});
}
// function to get num of days in month
function getDaysInMonth(month, year) {
return new Date(year, month, 0).getDate();
}
function minTwoDigits(n) {
return (n < 10 ? "0" : "") + n;
}
</script>
What I would really like to do is delete the existing chart before the api is called again? Any help would be greatly appreciated.
call the destroy method of the chart object
.destroy()
Use this to destroy any chart instances that are created. This will clean up any references stored to the chart object within Chart.js, along with any associated event listeners attached by Chart.js. This must be called before the canvas is reused for a new chart.
// Destroys a specific chart instance
myLineChart.destroy();
https://www.chartjs.org/docs/latest/developers/api.html?h=destroy
I am creating multiple graphs on the same canvas but I am unable to successfully use the destroy() API to clean up the previous data.
HERE IS MY JS CODE FOR CREATING A CHART
const getCountryDataByMonth = async (country) => {
document.getElementById('casesGraphHeader').innerHTML = "Loading....";
const response = await fetch ('https://cors-anywhere.herokuapp.com/https://pomber.github.io/covid19/timeseries.json');
const data = await response.json();
const reports = await data[country];
var i;
var dateList = [];
var caseByDay = [];
var deathsByDay = [];
for(i = 0; i < reports.length; i++){
dateList.push(reports[i].date);
caseByDay.push(reports[i].confirmed);
deathsByDay.push(reports[i].deaths);
}
//GRAPH FOR TOTAL CASES
var casesOptions = {
type: 'bar',
data: {
labels: dateList,
datasets: [
{
label: 'Total Cases',
data: caseByDay,
backgroundColor: '#f49d12',
borderColor: '#f49d12',
fill: false,
borderWidth: 2
}
]
},
options: {
legend: {
labels: {
fontSize: 15
}
},
scales: {
yAxes: [{
ticks: {
reverse: false,
fontSize: 15
}
}],
xAxes: [{
ticks: {
fontSize: 15
}
}],
}
}
}
var totalCasesChart = document.getElementById('totalCasesContainer').getContext('2d');
new Chart(totalCasesChart, casesOptions);
document.getElementById('casesGraphHeader').innerHTML = "Total Cases for "+country;
//GRAPH FOR TOTAL Deaths
var deathOptions = {
type: 'bar',
data: {
labels: dateList,
datasets: [
{
label: 'Total Deaths',
data: deathsByDay,
backgroundColor: '#e84c3d',
borderColor: '#e84c3d',
fill: false,
borderWidth: 2
}
]
},
options: {
legend: {
labels: {
fontSize: 15
}
},
scales: {
yAxes: [{
ticks: {
reverse: false,
fontSize: 15
}
}],
xAxes: [{
ticks: {
fontSize: 15
}
}],
}
}
}
var totalCasesChart = document.getElementById('totalDeathsContainer').getContext('2d');
new Chart(totalDeathsContainer, deathOptions);
document.getElementById('deathsGraphHeader').innerHTML = "Total Deaths for "+country;
};
function renderChart(){
getCountryDataByMonth(document.getElementById('myInput').value);
}
function defaultChart() {
getCountryDataByMonth('US');
}
window.onload = defaultChart;
This is what I tried. I basically did
if(caseBar){
caseBar.destroy();
}
However, this does not work. In my FIDDLE you can try to type China first click to create the graph and then type Italy. Then HOVER over the Italy graph and you will see the stats from china appear on the graph.
Your code is riddle with issues, here is some of the stuff I see:
Look at what you are doing when you create the new charts:
var totalCasesChart = document.getElementById('totalCasesContainer').getContext('2d');
var caseBar = new Chart(totalCasesChart, casesOptions);
document.getElementById('casesGraphHeader').innerHTML = "Total Cases for " + country;
vs
var totalCasesChart = document.getElementById('totalDeathsContainer').getContext('2d');
new Chart(totalDeathsContainer, deathOptions);
document.getElementById('deathsGraphHeader').innerHTML = "Total Deaths for " + country;
You are calling the:
await fetch('https://cors-anywhere.herokuapp.com/https://pomber.github.io/...');
again and again when you should do it just once...
There are many variables that should be global to reduce what you do in getCountryDataByMonth, a perfect example are the totalCasesChart and caseBar
I made a few tweaks to your code here:
https://raw.githack.com/heldersepu/hs-scripts/master/HTML/chart_test.html
I have successfully displayed the value, but why is only one value displayed? I want the value in sequence
This my code
/*my datasets code*/
datasets: [{
label: 'Daily Data',
data: [730000, 1012000, 1220000, 1831000, 560000, 2012000, 890000],
borderColor: '#3f89fb',
borderWidth: 3,
fill: false
}]
/*my tooltips code*/
tooltips: {
callbacks: {
label: function(tooltipItem, chart) {
for (var i = 0; i < chart.datasets[0].data.length; i++) {
return chart.datasets[0].data[i] / 1e6 + 'M';
}
}
}
}
and this my result, all day value is 0.73M
Look in the Tooltip Item Documentation.
In your case tooltipItem.index contains the index of this data item in the dataset. So you can return the value doing so:
function(tooltipItem, chart) {
return chart.datasets[0].data[tooltipItem.index] / 1e6 + 'M';
}
And here is the demo:
var ctx = document.getElementById("myChart").getContext('2d');
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: 'Daily Data',
data: [730000, 1012000, 1220000, 1831000, 560000, 2012000, 890000],
borderColor: '#3f89fb',
borderWidth: 3,
fill: false
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
},
tooltips: {
callbacks: {
label: function(tooltipItem, chart) {
return chart.datasets[0].data[tooltipItem.index] / 1e6 + 'M';
}
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<canvas id="myChart" width="400" height="400"></canvas>
Instead of return inside for loop which will exit the loop, you can save your results somewhere like below
var result = []
for (var i = 0; i < chart.datasets[0].data.length; i++) {
result.push(chart.datasets[0].data[i] / 1e6 + 'M');
}
This is because return stops execution and exits the function. return always exits its function immediately, with no further execution if it's inside a loop.
See this answer:
Does return stop a loop?