在隐藏的引导选项卡窗格上重绘google图表(redraw google charts on hidden bootstrap tab pane)

其他开发 IT屋
百度翻译此文   有道翻译此文
问 题

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>
                        &nbsp;&nbsp;Quick Stats
                    </a>
                </li>
                <li>
                    <a href="#engagement-graph" data-toggle="tab">
                        <i class="fa fa-bar-chart-o"></i>
                        &nbsp;&nbsp;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 enter image description here

Loading on hidden tab pane enter image description here

解决方案

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.

本文地址:IT屋 » redraw google charts on hidden bootstrap tab pane

问 题

我在我的网络应用程序中使用谷歌散点图,并在准备好的文档上加载。我最近做了一些重组,并尝试将页面内容分成不同的引导标签。



如果我在活动选项卡窗格中加载图形,它会正常工作,图形的宽度,高度参数是正确的。但是,如果我将图转移到隐藏的选项卡上,图形无法正确加载在函数上传递的参数(特别是宽度和高度)。目前我正在准备文件上的图表。



这是我的代码



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>
& nbsp;& nbsp;快速统计资料
< / a>
< / li>
< li>
< a href =“#engagement-graph”data-toggle =“tab”>
< i class =“fa fa-bar-chart-o”>< / i>
& nbsp;& nbsp;订婚图
< / a>
< / li>
< / ul>
< / div>
< / div> <! - 布局侧边栏 - >

< div class =“col-sm-9 layout-main”>
< div class =“tab-content stacked-content”>
< div class =“tab-pane fade in active”id =“quick-stats”>
一些内容
< / div>
< div class =“tab-pane fade”id =“engagement-graph”>
< div id =“myMoodPage”>
< div class =“graph”id =“graph”>
< / div>
< / div>
< / div>
< / div>
< / div>


载入文件准备就绪图

 < 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'); $'

$ b $('a [href ='#engagement-graph']“)。('shown.bs.tab',function(e){
google .load('visualization','1',{
packages:['corechart'],
callback:drawChart
});
});

< / script>


这是我的主要JS文件,我定义了图形参数和其他东西相关的这个问题,但只是为了完整起见,我发布了整个函数)

pre $ (function($){
$ .widget(“ui.myMood”,{
options:{
graphDataUrl:“”,
graphData:{},
titles:[“time”, “心情”,“平均”]
},
_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);
返回这个
},
loadGraph:function(){
var self = this;
var data = {
period:self.periodSelect.val(),
question:self.questionSelect.val()
};
var success = function(data){
self.setGraphData(data)
};
$ .ajax({
类型:“POST”,
url:self.options.graphDataUrl,
数据:数据,
成功:成功
})
},
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(“。 。下“)显示();
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(“。 。下“)显示();
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-颜色:#9c3b8a;“>';
if(moodData [i] .event!= null){
moodText + = moodData [i] .event +“”
}
moodText + = moodData [i]。心情;
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]。平均;
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({
类型:“布尔值”,
角色:“确定性”
});
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:{
colo r:“#9c3b8a”,
lineWidth:2,
pointSize:6
},
1:{
color:“#15426c”,
lineWidth:1,
pointSize:3
}
}
};
chart.draw(dataView,options)
}
}
})
})(jQuery);


我尝试像这样重新加载图表,但它无效

$ b $ ('shown.bs.tab',function(e){
$ b
  $(“a [href ='#engagement-graph']”)。 b $ b google.load('visualization','1',{
packages:['corechart'],
callback:drawChart
});
});


<2>截屏2种不同情况

在活动标签窗格



加载隐藏的标签页面


在隐藏div中绘制图表(这通常是tab接口的实现方式)打破了Visualization API中的维度检测算法,这就是为什么图表奇怪地绘制如果在开始时打开的选项卡以外的任何选项卡中绘制。



您只能加载API一次;后续对 google.load 对Visualization API的调用将被忽略,这就是为什么您的“shown.bs.tab”事件处理程序不能执行您想要的操作。



由于延迟了标签初始化,直到图表绘制对Bootstrap不是一个真正可行的选项,我会建议替换此代码:



  google.load(“visualization”,“1”,{packages:[“corechart”]}); $'
$ b $(“a [href ='#engagement-graph']”)。on('shown.bs.tab',function(e){
google.load('visualization ','1',{
packages:['corechart'],
callback:drawChart
});
});


用这个:

 函数init(){
if(/ *布尔表达式如果图表选项卡打开* /){
drawChart();

else {
$(“a [href ='#engagement-graph']”)。one('shown.bs.tab',drawChart);


$ b google.load(“visualization”,“1”,{packages:[“corechart”],callback:init});


如果它的容器选项卡打开,这应该绘制图表,并创建一次性事件处理程序



您需要将此代码放入 setGraphData 函数来避免数据加载时的竞争条件,所以 drawChart 函数将在范围内。另外,删除这些行:

  if(google){
drawChart()
} else {
google.setOnLoadCallback(drawChart)
}


只要 jsapi 的脚本标记加载时(这些代码在执行之前),就会存在google 对象,因此您将始终触发 drawChart 函数。然而,让 google 对象可用,并不意味着加载了Visualization API。


本文地址:IT屋 » 在隐藏的引导选项卡窗格上重绘google图表

官方微信
扫一扫关注IT屋
微信公众号搜索 “ IT屋 ” ,选择关注
与百万开发者在一起