I am using google scatter chart in my web app and it loads on document ready. I recently did some restructuring and trying to divide the page content in different bootstrap tabs.
If I load the graph on the active tab pane it work fine and the width, height parameters of the graph is correct. However if I shift the graph to a hidden tab the graph doesn't load properly with the parameters passed on the function (specially width and height). At the moment I am calling the graph on document ready.
Here is my code
HTML
<div class="layout layout-stack-sm layout-main-right">
<div class="col-sm-3 layout-sidebar">
<div class="col-sm-12">
<ul id="myTab" class="nav nav-layout-sidebar nav-stacked">
<li class="active">
<a href="#quick-stats" data-toggle="tab">
<i class="fa fa-th"></i>
Quick Stats
</a>
</li>
<li>
<a href="#engagement-graph" data-toggle="tab">
<i class="fa fa-bar-chart-o"></i>
Engagement graph
</a>
</li>
</ul>
</div>
</div> <!-- layout sidebar -->
<div class="col-sm-9 layout-main">
<div class="tab-content stacked-content">
<div class="tab-pane fade in active" id="quick-stats">
Some content
</div>
<div class="tab-pane fade" id="engagement-graph">
<div id="myMoodPage">
<div class="graph" id="graph">
</div>
</div>
</div>
</div>
</div>
Loading graph on document ready
<script type="text/javascript" src="https://www.google.com/jsapi">
</script>
<script type="text/javascript">
google.load("visualization", "1", {packages: ["corechart"]});
$(document).ready(function () {
$('#myMoodPage').myMood({
graphDataUrl: '<?php echo $this->url('my_mood/mood_graph_data'); ?>',
graphData: <?php echo $graphData; ?>,
titles: ['<?php echo $this->translate('time'); ?>', '<?php echo $this->translate('mood'); ?>', '<?php echo $this->translate('avg'); ?>']
});
});
function updatePageCharts() {
$('#myMoodPage').myMood('loadGraph');
}
$("a[href='#engagement-graph']").on('shown.bs.tab', function (e) {
google.load('visualization', '1', {
packages: ['corechart'],
callback: drawChart
});
});
</script>
This is my main JS file where I am defining the graph parameters and other things (which are not actually relevant for this question, but just for the sake of completeness I am posting the whole function)
(function ($) {
$.widget("ui.myMood", {
options: {
graphDataUrl: "",
graphData: {},
titles: ["time", "mood", "avg"]
},
_create: function () {
var self = this;
this.periodSelect = this.element.find('select[name="period"]');
this.questionSelect = this.element.find('select[name="question"]');
this.graphContainer = this.element.find(".statistics");
this.atStart = this.element.find("#atStart .value");
this.highest = this.element.find("#highest .value");
this.lowest = this.element.find("#lowest .value");
this.current = this.element.find("#current .value");
this.last30 = this.element.find("#last30 .value");
this.last30Trend = this.element.find("#last30");
this.week = this.element.find("#week .value");
this.weekTrend = this.element.find("#week");
this.graphId = "graph";
this.selectedFilterName = this.options.titles[2];
this.periodSelect.change(function () {
self.loadGraph()
});
this.questionSelect.change(function () {
self.loadGraph();
self.selectedFilterName = self.questionSelect.find("option:selected").text()
});
this.setGraphData(this.options.graphData);
return this
},
loadGraph: function () {
var self = this;
var data = {
period: self.periodSelect.val(),
question: self.questionSelect.val()
};
var success = function (data) {
self.setGraphData(data)
};
$.ajax({
type: "POST",
url: self.options.graphDataUrl,
data: data,
success: success
})
},
setGraphData: function (data) {
var self = this;
this.atStart.text(data.atStart);
this.highest.text(data.highest);
this.lowest.text(data.lowest);
this.current.text(data.current);
this.last30.text(data.last30.val);
this.last30Trend.find(".up, .down").hide();
this.last30Trend.removeClass("incr");
this.last30Trend.removeClass("decr");
if (Number(data.last30.trend) > 0) {
this.last30Trend.find(".up").show();
this.last30Trend.addClass("incr")
}
if (Number(data.last30.trend) < 0) {
this.last30Trend.find(".down").show();
this.last30Trend.addClass("decr")
}
this.week.text(data.week.val);
this.weekTrend.find(".up, .down").hide();
this.weekTrend.removeClass("incr");
this.weekTrend.removeClass("decr");
if (Number(data.week.trend) > 0) {
this.weekTrend.find(".up").show();
this.weekTrend.addClass("incr")
}
if (Number(data.week.trend) < 0) {
this.weekTrend.find(".down").show();
this.weekTrend.addClass("decr")
}
var moodData = data.mood;
var dataArray = new Array();
for (i in moodData) {
if (moodData[i].avg == null) {
moodData[i].avg = undefined
}
if (moodData[i].mood == null) {
moodData[i].mood = undefined
}
if (moodData[i].team == null) {
moodData[i].team = undefined
}
var moodText = '<div style="border-radius: 2px; padding: 10px; color: #ffffff; font-weight: bold; background-color: #9c3b8a;">';
if (moodData[i].event != null) {
moodText += moodData[i].event + " "
}
moodText += moodData[i].mood;
if (moodData[i].comment) {
moodText += "<br>" + moodData[i].comment
}
moodText += "</div>";
var avgText = '<div style="border-radius: 2px; padding: 10px; color: #ffffff; font-weight: bold; background-color: #15426c;">';
if (moodData[i].event != null) {
avgText += moodData[i].event + " "
}
avgText += moodData[i].avg;
if (moodData[i].publicComment) {
avgText += "<br>" + moodData[i].publicComment
}
avgText += "</div>";
var row = [new Date(moodData[i].time * 1000), Number(moodData[i].mood), moodText, Number(moodData[i].avg), avgText, false];
dataArray.push(row)
}
if (google) {
drawChart()
} else {
google.setOnLoadCallback(drawChart)
}
function drawChart() {
var dataTable = new google.visualization.DataTable();
dataTable.addColumn("date", self.options.titles[0]);
dataTable.addColumn("number", self.options.titles[1]);
dataTable.addColumn({
type: "string",
role: "tooltip",
p: {
html: true
}
});
dataTable.addColumn("number", self.selectedFilterName);
dataTable.addColumn({
type: "string",
role: "tooltip",
p: {
html: true
}
});
dataTable.addColumn({
type: "boolean",
role: "certainty"
});
dataTable.addRows(dataArray);
var dataView = new google.visualization.DataView(dataTable);
var chart = new google.visualization.ScatterChart(document.getElementById(self.graphId));
var options = {
legend: {
position: "bottom"
},
interpolateNulls: true,
chartArea: {
left: 25,
top: 25,
width: '92%',
height: '80%'
},
backgroundColor: "#ffffff",
vAxis: {
minValue: 0,
maxValue: 6,
viewWindow: {
min: 0,
max: 6
}
},
tooltip: {
isHtml: true
},
series: {
0: {
color: "#9c3b8a",
lineWidth: 2,
pointSize: 6
},
1: {
color: "#15426c",
lineWidth: 1,
pointSize: 3
}
}
};
chart.draw(dataView, options)
}
}
})
})(jQuery);
I tried reloading the graph like this, but it didn't work
$("a[href='#engagement-graph']").on('shown.bs.tab', function (e) {
google.load('visualization', '1', {
packages: ['corechart'],
callback: drawChart
});
});
Screenshot in 2 different situation
Loading on active tab pane
Loading on hidden tab pane
Drawing charts inside hidden divs (which is usually what tab interfaces are implemented as) breaks the dimension detection algorithms in the Visualization API, which is why the chart draws oddly when drawn in any tab other than the tab open at the start.
You can load the API only once; subsequent calls to google.load
for the Visualization API will be ignored, which is why your "shown.bs.tab" event handler does not do what you want.
Since delaying the tab initialization until after the charts draw is not really a viable option with Bootstrap, I would suggest replacing this code:
google.load("visualization", "1", {packages: ["corechart"]});
$("a[href='#engagement-graph']").on('shown.bs.tab', function (e) {
google.load('visualization', '1', {
packages: ['corechart'],
callback: drawChart
});
});
with this:
function init () {
if (/* boolean expression teting if chart tab is open */) {
drawChart();
}
else {
$("a[href='#engagement-graph']").one('shown.bs.tab', drawChart);
}
}
google.load("visualization", "1", {packages: ["corechart"], callback: init});
This should draw the chart if its container tab is open, and create a one-time event handler that draws the chart the first time the tab is opened if the tab is not open.
You need to put this code inside the setGraphData
function to avoid a race condition with your data loading, and so the drawChart
function will be in scope. Also, remove these lines:
if (google) {
drawChart()
} else {
google.setOnLoadCallback(drawChart)
}
The google
object exists as soon as the script tag for the jsapi
loads, which will be before this code is executed, so you will always trigger the drawChart
function here. Having the google
object available, however, does not mean that the Visualization API is loaded.
Just incase other people are looking for a simpler solution.
An alternative is to implement the following:
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var ev = document.createEvent('Event');
ev.initEvent('resize', true, true);
window.dispatchEvent(ev);
});
This will cause the chart to re-draw as if the browser was resized.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With