How to create an active and dynamic bar graph from Mysql, php? - javascript

just to clarify the title, I want to make a bar graph which will read the data from the database table using MySql, and automatically changing the height to match the new data. The data is being fed to the database via an Arduino micro controller, so there is a continuous addition of data ever 0.5 seconds. I want the graph to detect the data and increase its height accordingly. Is there a way to do this without constantly refreshing the web page every 0.5 seconds?

Please don't refresh the whole page every .5 seconds. There is a better way!
If you're not opposed to Jquery, use
$("#someDiv").load("somePage.PHP");
If no jquery, use:
var xhttp;
if (str.length == 0) {
document.getElementById("someDiv").innerHTML = "";
return;
}
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4 && xhttp.status == 200) {
document.getElementById("someDiv").innerHTML = xhttp.responseText;
}
};
xhttp.open("GET", "somePage.PHP", true);
xhttp.send();
Now make a div to load the contents:
<div id="someDiv">
On the PHP page you load, you'll want to draw the chart. This method uses the bar chart from Chart.js: http://www.chartjs.org/docs/#bar-chart
See the link for more details. Your code will look something like this:
var data = {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [
{
label: "My First dataset",
backgroundColor: "rgba(255,99,132,0.2)",
borderColor: "rgba(255,99,132,1)",
borderWidth: 1,
hoverBackgroundColor: "rgba(255,99,132,0.4)",
hoverBorderColor: "rgba(255,99,132,1)",
data: [65, 59, 80, 81, 56, 55, 40],
}
]
};
var myBarChart = new Chart(ctx, {
type: 'bar',
data: data,
options: options
});

You could use AJAX to make a call to a script every 0.5 seconds which will refresh only a portion of your page(the graph). Through AJAX you would be able to change the graph according to the data it fetches from a PHP(or otherwise) script with javascript.

Related

How to avoid detached HTMLCanvasElements when updating canvas with JavaScript?

So my problem is that when I'm cyclically updating canvas element in my HTML with line chart created by Chart.js library I'm getting bunch of detached HTMLCanvasElements. I noticed this when I was fixing some memory leaks in my code which earlier lead my webpage to crash(chrome gave "aw snap" error page).
I was able to fix most of the memory leaks but this one is still bothering me and I am helpless because of not knowing why this is happening..
I have canvas elements in my HTML code like this:
<div class="kuvaaja"><canvas id="etaisyyskuvaaja"></canvas></div>
<div class="kuvaaja"><canvas id="etaisyyskuvaaja2"></canvas></div>
<div class="kuvaaja"><canvas id="etaisyyskuvaaja3"></canvas></div>
<div class="kuvaaja"><canvas id="etaisyyskuvaaja4"></canvas></div>
And this is how I fetch those elements in my JavaScript (in window.onload function):
pohjacanvas = document.getElementById("etaisyyskuvaaja");
pohjacanvas2 = document.getElementById("etaisyyskuvaaja2");
pohjacanvas3 = document.getElementById("etaisyyskuvaaja3");
pohjacanvas4 = document.getElementById("etaisyyskuvaaja4");
Then I start cyclic updating of those canvases with(also inside window.onload):
paivitysvali = setInterval(haeetaisyysmittaukset, 1000);
painepaivitys = setInterval(haepainemittaukset, 1000);
And in these functions I first fetch data from database like this (haepainemittaukset() is similar to this one just different data is fetched):
function haeetaisyysmittaukset() {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
etaisyys = [];
ajat = [];
var mittaukset = JSON.parse(xmlhttp.response);
for (var i = 0; i < mittaukset.length; i++) {
etaisyys.push(mittaukset[i]["etaisyys"]);
ajat.push(mittaukset[i]["timestamp"]);
}
if (paivitysbitti == 1) {
etaisyys.reverse();
ajat.reverse();
luokuvaaja2(pohjacanvas, etaisyys, ajat);
luokuvaaja2(pohjacanvas4, etaisyys, ajat);
}
}
}
xmlhttp.open("POST", "haeetaisyysmittaukset.php", true);
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlhttp.send("limit=" + mittausmaara);
}
After data is fetched in global arrays (etaisyys[] and ajat[]) I call luokuvaaja2() function with the canvas I want to update and these data arrays. And this is the phase where problem starts at least I think so.. Here is how I am trying to implement this update of canvas:
function luokuvaaja2(pohja, data, ajat) {
pohja.height = 400;
pohja.width = 700;
myChart = new Chart(pohja, {
type: 'line',
data: {
labels: ajat,
datasets: [{
data: data,
label: "Etaisyys",
borderColor: "blue",
fill: false
}
]
},
options: {
animation: {
duration: 0, // general animation time
},
hover: {
animationDuration: 0, // duration of animations when hovering an item
},
title: {
display: true,
text: 'Etaisyysmittaus'
},
scales: {
yAxes: [{
ticks: {
min: 0,
max: 6000
}
}]
}
}
});
}
So what I think that I'm doing here is setting canvas height and width, after that I create new chart in the same canvas where old one was(or am I?). However it looks like that there is something wrong and my current code is creating bunch of detached canvas elements but I can't understand why and where this is exactly happening? So now memory footprint of my page is slowly increasing after every cycle (canvas update).
Here is also snapshot of memory distribution:
memory distribution
In this memory distribution there are many detached canvas elements all pointing to those canvases(pohjacanvas, pohjacanvas2, pohjacanvas3, pohjacanvas4).
You should keep track (in an array, for example) of your charts previously created, then when the new data is fetched from the server, you need to retrieve the chart to update, update its data and/or options, and then call
chart.update();
to refresh the chart. In this case you won't create new charts at each update.
You may find more details in the Chart.js docs.

Doughnut chart - interaction with specific segment of a chart

I'm trying to create a simple doughnut chart with chartjs. I've been digging through the Documentation and Stack (of course), but I couldn't find it.
Here is my code: https://jsfiddle.net/zyqtyna7/1/
<div class="place-for-chart">
<canvas id="myChart"></canvas>
</div>
<div class="description">
<p class="first hide">I'm description to no. 1 and I was hide</p>
<p class="second hide">I'm description to no. 2 and I was hide</p>
<p class="third hide">I'm description to no. 3 and I was hide</p>
</div>
<script>
var data = {
datasets: [{
data: [20, 20, 20],
backgroundColor: ["#27ae60", "#95a5a6", "#488a99"]
}],
labels: ["first", "second", "third"],
};
$(document).ready(
function() {
var canvas = document.getElementById("myChart");
var ctx = canvas.getContext("2d");
var CompetenceChart = new Chart(ctx, {
type: 'doughnut',
data: data
});
})
I'm not sure, but I think that the biggest question is: how can I target a specific segment of a chart (and then do something with it)? I'm asking this, because my project requires that:
descriptions in paragraphs will be visible after user's click at related part of chart (how can I target this segment???);
tooltips will have only labels' name (no values) (I couldn't decipher the Documentation);
chart animation should be triggered with scroll (segments will appear in sequence after scrolling - is it even possible?)
I'll be very greatfull for any insight - I'm stuck!
I managed to do 2 of 3.. and I found documentation on how to do the third one.. But I was not able to make it work :/.... (i will try again when i have a bit more of time).
So here is the JSfiddle with this modifications:
1:Data is shown on mouse click
2:On top labels are shown, but when you click you wont see any labels but a black mark
JSFIDDLE
So what did I do?
options: {
// This chart will not respond to mousemove, etc
events: ['click'],
tooltips: {
callbacks: {
label: function(tooltipItem)
{
return tooltipItem.yLabel;
}
}
}
}
I added the options part with those codes.
the events:['click'] makes the labels show when you click a part of the chart instead of hovering it.
the tooltips with the callbacks "turn off" showing the labels on mouse click.
And for the other part that you ask, about showing the animation when you scroll to the part where the chart is I found this 2 links that tells you how to do so (I couldn't make it work, but I will try again when I have more time and update).
Link1
Link2
Please let me know if this is what you wanted to know! CHEERS!
OMG! Now I know. It took me so many hours, but was so obvious! Here's my new - working - code: https://jsfiddle.net/m954jto4/ Documentation of Chartjs and same basic js script - that's all, what I needed (shame on me!).
<div class="place-for-chart">
<canvas id="myChart"></canvas>
</div>
<div class="description">
<p class="first hidden" id="hide1">I'm description to no. 1 and I was hide</p>
<p class="first hidden" id="hide2">I'm description to no. 2 and I was hide</p>
<p class="first hidden" id="hide3">I'm description to no. 3 and I was hide</p>
</div>
var data = {
datasets: [{
data: [20, 20, 20],
backgroundColor: ["#27ae60", "#95a5a6", "#488a99"]
}],
labels: ["first", "second", "third"],
};
$(document).ready(
function() {
var canvas = document.getElementById("myChart");
var ctx = canvas.getContext("2d");
var myNewChart = new Chart(ctx, {
type: 'doughnut',
data: data,
options: {
legend: {
display: true,
onClick: (e) => e.stopPropagation(),
position: 'left',
labels: {
fontSize: 20
}
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var label = data.labels[tooltipItem.index];
return label;
}
}
},
cutoutPercentage: 65
}
}
);
canvas.onclick = function(event) {
var activePoints = myNewChart.getElementsAtEvent(event);
var hiddenparagraph1 = document.getElementById("hide1");
var hiddenparagraph2 = document.getElementById("hide2");
var hiddenparagraph3 = document.getElementById("hide3");
if (activePoints.length > 0) {
var clickedSegmentIndex = activePoints[0]._index;
if (clickedSegmentIndex==0) {
hiddenparagraph1.classList.remove("hidden");
}
else if(clickedSegmentIndex==1) {
hiddenparagraph2.classList.remove("hidden");
}
else {
hiddenparagraph3.classList.remove("hidden");
}
}
};
}
);

Chart.js 2.1.2 Bar Chart Animation Issue

I have a bar chart in chart.js 2.1.2 which I just upgraded from 1.something. In 1.something, you could specify when to animate the bar chart. Essentially, I load the chart on page load and then set a timer to update the data and redraw the chart every 5 seconds without requiring the user to reload the page. I would like for the bar chart to only animate on the initial load and not in subsequent refreshes. In version 1.something you just needed to change the animation property of the bar chart when refreshing it. In version 2.1.2, I don't see a way to do that. At this point, I would be happy if I could disable the animation of the bar chart entirely. However, I also have other pie charts on the page which the animation settings are working so I don't want to change the animation settings globally.
HTML (Excerpt)
<div class="text-center">
<div class="row">
<div id="regbyhour-container" class="col-sm-12">
<h4>Registrations by Hour</h4>
<canvas id="regbyhour"></canvas>
</div>
</div>
</div>
Javascript
<script type="text/javascript">
var barData = defineBarDataArray();
$(function () {
// Global Chart Options
Chart.defaults.global.legend.display = false;
Chart.defaults.global.maintainAspectRatio = true;
// Bar Chart Options
Chart.defaults.bar.scaleBeginAtZero = false;
updateBarChart(true)
setInterval(function () {
updateBarChart(false)
}, 5000);
});
function defineBarDataArray() {
return {
labels: [],
datasets: [{
label: "Registrations",
backgroundColor: "rgba(151,187,205,0.5)",
borderColor: "rgba(151,187,205,0.8)",
borderWidth: 3,
data: []
}]
};
};
function drawRegByHourChart(animate) {
$("#regbyhour").remove();
$("#regbyhour-container").append('<canvas id="regbyhour"></canvas>');
var context = $("#regbyhour");
var chart = new Chart(context, {
type: 'bar',
data: barData
// Need to enable or disable animation here based on animate parameter
});
};
function updateBarChart(animate) {
barData = defineBarDataArray();
barData.labels.push("12:00 PM");
barData.labels.push("1:00 PM");
barData.labels.push("2:00 PM");
barData.datasets[0].data.push(1 + Math.floor(Math.random() * 2000));
barData.datasets[0].data.push(1 + Math.floor(Math.random() * 2000));
barData.datasets[0].data.push(1 + Math.floor(Math.random() * 2000));
drawRegByHourChart(animate);
};
</script>
I don't see anything in the documentation here that says you can specify the animation options like you can with a pie chart. What am I missing?
I ended up posting this as a bug on the chart.js GitHub page since I didn't get any responses here. Following the suggestion from etimberg I changed the drawRegByHourChart function from this:
function drawRegByHourChart(animate) {
$("#regbyhour").remove();
$("#regbyhour-container").append('<canvas id="regbyhour"></canvas>');
var context = $("#regbyhour");
var chart = new Chart(context, {
type: 'bar',
data: barData
// Need to enable or disable animation here based on animate parameter
});
};
To this:
function drawRegByHourChart(animate) {
$("#regbyhour").remove();
$("#regbyhour-container").append('<canvas id="regbyhour"></canvas>');
var context = $("#regbyhour");
var chart = new Chart(context, {
type: 'bar',
data: barData
});
if (!animate) {
chart.update(0);
}
};

jqplot set data series from table

I am using jqplot to create a pie chart from an HTML table.
However, when attempting to set a var the chart doesn't plot.
If i simply set a number for the var, such as:
var DataTest = 22;
then the chart works fine.
However, when I get the value from the table it doesn't, as below:
var DataTest = $(dataID + ' td:nth-child(46)').text();
The pie chart wont display.
The pie chart series is being set through a var also:
var PieChartS1 = [ ['Label', DataTest] ];
Then included in the chart code as follows:
var PieChartPlot = $.jqplot('PieChart', [PieChartS1, PieChartS2 ], {
seriesDefaults: {
renderer:$.jqplot.DonutRenderer,
rendererOptions:{
sliceMargin: 1,
startAngle: -145,
showDataLabels: true,
dataLabels: 'label',
padding: '5',
ringMargin: '5',
dataLabelThreshold: '1',
highlightMouseOver: true,
}
},
grid: {background: 'transparent', borderWidth: '0', cursor: 'pointer',},
});
I'm wondering whether it is a format issue?
Thanks for any help
Richard
Update:
Am wondering whether the graph is being formed by the script before the data has been obtained, so it is just showing blank as it is finding no data. Perhaps I need to delay the graph formation until the 'get data' scripts are fully executed?
Had some help from elsewhere.
It needs to be specified as an integer, so the change needs to be:
var DataTest = parseInt($(dataID + ' td:nth-child(46)').text());
This works fine.

ChartJS charts not generating within tabs

I'm trying to use ChartsJS within a tabbing system. The first chart in the first tab is rendered, but subsequent charts are not.
I believe this is because the tabs have display:none, so when the charts are first generated they are created in a div with zero dimensions.
This fixes the issue, but breaks the tabs:
.vertical-nav>div.tab-content {
display: block !important;
}
With that in mind I've tried all kinds of ways to regenerate the charts after the tab is open, or force display:block just before generating the chart. Nothing works.
Here's my attempt to regenerate the charts:
jQuery(document).ready(function() {
jQuery('.vertical-nav ul li span').click(function(){
// Target the canvas within the correct tab
var tabIndex = jQuery(this).parent().index();
var canvasID = jQuery('.vertical-nav .tab-content:eq( ' + tabIndex + ' ) canvas').attr('id');
theChart = charts[canvasID].chart;
builtChart = charts[canvasID].builtChart;
// Destroy all of the charts so that we can rebuild them
for (var key in charts) {
if (charts.hasOwnProperty(key)) {
charts[key].builtChart.destroy();
}
}
// get the chart data and options from the charts object
newData = charts[canvasID].data;
newOptions = charts[canvasID].options;
// create the new chart
newChart = document.getElementById(canvasID).getContext("2d");
new Chart(newChart).Line(newData, newOptions);
});
})
The staging site is: http://telcomplusplc.uberleaf.com/company/?tab=our-growth
I've even tried setting a timeout to delay the chart generation until the tab is displayed, but no luck.
You can see in the URL above that the first chart generates and regenerates as it should. It's just the rest of them that don't (presumably because they are with a display:none div on first generation).
Any help is much appreciated. Happy to give more info if needed.
Update
When the window is resized, the chart is redrawn. Here's the code for that (in chart.js):
// Attach global event to resize each chart instance when the browser resizes
helpers.addEvent(window, "resize", (function(){
// Basic debounce of resize function so it doesn't hurt performance when resizing browser.
var timeout;
return function(){
clearTimeout(timeout);
timeout = setTimeout(function(){
each(Chart.instances,function(instance){
// If the responsive flag is set in the chart instance config
// Cascade the resize event down to the chart.
if (instance.options.responsive){
instance.resize(instance.render, true);
}
});
}, 50);
};
})());
I think what I need is to be able to fire that from a click function. I've tried an failed, my JS skills aren't that good.
If you use display:none then chartjs doesn't know the size to make the chart and then doesn't. If you switch to jquery.hide()/show() it will work. Here's a jsfiddle
*The fiddle works without it but if you have a lot of content then you may need to also hide the area temporarily.
Javascript
var data1 = {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [{ data: [65, 59, 80, 81, 56, 55, 40] }]
};
var data2 = {
labels: ["January", "February", "March", "April", "May", "June", "July"],
datasets: [{ data: [65, 59, 80, 81, 56, 55, 40] }]
};
// Load each chart and hide() the tab immediately.
var ctx = document.getElementById('chart1').getContext('2d');
var chart1 = new Chart(ctx).Bar(data1);
$('#tab1').hide();
var ctx = document.getElementById('chart2').getContext('2d');
var chart2 = new Chart(ctx).Line(data2);
$('#tab2').hide();
/*
// will not work because chart is not rendered
$('#tab1_btn').on('click',function(){
$('#tab1').css('display','block');
$('#tab2').css('display','none') })
$('#tab2_btn').on('click',function(){
$('#tab1').css('display','none');
$('#tab2').css('display','block') })
*/
$('#tab1_btn').on('click',function(){
$('#tab1').show();
$('#tab2').hide()
})
$('#tab2_btn').on('click',function(){
$('#tab1').hide();
$('#tab2').show()
})
// if you have a lot of content in multiple tabs
// it may be necessary to temporarily cover the content
$('#tab_cover').hide();
HTML
<button id="tab1_btn">tab1</button>
<button id="tab2_btn">tab2</button>
<div id="tab_cover"> </div>
<div id="tabs">
<div id="tab1" class="tab">
<canvas id="chart1" class="chart" width="400" height="300"></canvas>
</div>
<div id="tab2" class="tab">
<canvas id="chart2" class="chart" width="400" height="300"></canvas>
</div>
</div>
CSS
/* will not work because chart is not rendered
.tab { display:none } */
.chart { width:50% }
.float { float:right; width:50%; }
#tab_cover { background:#9ff; height: 100% !important; width:100%; position: absolute; z-index: 300; }
I've found a solution. I was close with the code I had in the question. It seems that I need to destroy and resize the charts.
I also had to change the display status of the tabs manually.
Here's what works for me:
function createNewChart(canvasID) {
// get the chart data and options from the charts object
newData = charts[canvasID].data;
newOptions = charts[canvasID].options;
// create the new chart
theChart = document.getElementById(canvasID).getContext("2d"); console.dir(theChart);
new Chart(theChart).Line(newData, newOptions);
}
jQuery('.vertical-nav ul li').click(function(){
// Target the canvas within the correct tab
var tabIndex = jQuery(this).index();
var canvasID = jQuery('.vertical-nav .tab-content:eq( ' + tabIndex + ' ) canvas').attr('id');
theChart = charts[canvasID].chart;
builtChart = charts[canvasID].builtChart;
// Destroy all of the charts so that we can rebuild them
for (var key in charts) {
if (charts.hasOwnProperty(key)) {
jQuery('.vertical-nav .tab-content:eq( ' + tabIndex + ' )').css('display', 'block');
jQuery('.vertical-nav .tab-content:eq( ' + tabIndex + ' ) .tab-content-chart').css('display', 'block');
charts[key].builtChart.destroy();
charts[key].builtChart.resize();
}
}
createNewChart(canvasID);
});
I was running into the same issues when using hidden divs that only display upon certain selections from a drop-down. What I found to work is similar to what ow3n did. I created the graphs and then had them only generate once the selection was made.
HTML:
<div>
<select>
<option value="option-1">Option 1</option>
<option value="option-2">Option 2</option>
<option value="option-3">Option 3</option>
</select>
</div>
<div class="chart1">
<canvas id="chart1"></canvas>
</div>
<div class="chart2">
<canvas id="chart2"></canvas>
</div>
<div class="chart3">
<canvas id="chart3"></canvas>
</div>
Javascript:
//example of chart variables (I only included 1 example)
var chart1 = {
labels : ["2009","2010","2011","2012","2013","2014"],
labelAlign: 'center',
datasets : [
{
label: "Visitor Average",
fillColor : "rgba(255,255,255,0)",
strokeColor : "rgba(0,34,221,1)",
pointColor : "rgba(0,34,221,1)",
pointStrokeColor : "#fff",
pointHighlightFill : "#fff",
pointHighlightStroke : "rgba(0,34,221,1)",
data: [28, 48, 40, 19, 86, 27]
},
{
label: "Baseline Average",
fillColor : "rgba(255,255,255,0)",
strokeColor : "rgba(255,0,0,1)",
pointColor : "rgba(255,0,0,1)",
pointStrokeColor : "#fff",
pointHighlightFill : "#fff",
pointHighlightStroke : "rgba(255,0,0,1)",
data: [65, 59, 80, 81, 56, 55]
}
]
}
window.onload = function(){
var ctx1 = document.getElementById("chart1").getContext("2d");
window.myLine1 = new Chart(ctx1).Line(chart1, {
responsive: true,
});
}
//change graph per drop-down selection
$(document).ready(function(){
$("select").change(function(){
$( "select option:selected").each(function(){
if($(this).attr("value")=="option-1"){
$(".chart2").hide();
$(".chart3").hide();
$(".chart1").show();
var ctx1 = document.getElementById("chart1").getContext("2d");
window.myLine1 = new Chart(ctx1).Line(chart1, {
responsive: true,
});
}
if($(this).attr("value")=="option-2"){
$(".chart2").show();
$(".chart1").hide();
$(".chart3").hide();
var ctx2 = document.getElementById("chart2").getContext("2d");
window.myLine2 = new Chart(ctx2).Line(chart2, {
responsive: true,
});
}
if($(this).attr("value")=="option-3"){
$(".chart3").show();
$(".chart1").hide();
$(".chart2").hide();
var ctx3 = document.getElementById("chart3").getContext("2d");
window.myLine3 = new Chart(ctx3).Line(chart3, {
responsive: true,
});
}
});
}).change();
});
Taking bootstrap's tab as an example, my solution is to add class .loading-data to .tab-content before rendering using chart.js and remove the class after the rendering is done.
.tab-content.loading-data .tab-pane {
display: block;
}
In this way, the .tab-panes will display in the instant of chartjs's rendering.
Try this one, I struggled with all other approaches but finally done with this code.
$("a").click(function(e){
let val = '#'+ e.target.href.split("#")[1]
localStorage.setItem('tab', val)
window.location.reload();
})
$(document).ready(function()
{
$('a[href="' + localStorage.getItem('tab') + '"]').parent().addClass('active')
$(document.getElementById(localStorage.getItem('tab').slice(1))).addClass('active')
});
Also, try with these approches
window.dispatchEvent(new Event('resize'))
Chartkick.charts["<id of chart element like chart-1>"].redraw()

Categories