chartWrapper of dataView由data.join创建的dataTable不更新值更改

时间:2016-06-24 17:41:53

标签: google-visualization

首先,请原谅下面的小说,但这很难描述。

我有一个相当复杂的应用程序,我目前正在通过GAS应用程序的HtmlService使用gviz。

这更像是客户端gviz API问题。

我通过查询方法加载2个数据表,并通过data.join组合它们。生成的dataTable是数据视图的源。

然后,该视图用于生成包含多个类别过滤器和单个tableChart的仪表板。我有一个按钮,可以在显示的视图行和列的两个过滤器集之间切换。这个概念是加载所有记录并在挂起和完成之间切换,向用户显示相关列。

这部分实际上运作得很好。我遇到的问题是数据更新问题。每次可视化绘制时,我都会遍历生成的表并将操作附加到各个列。

一个这样的操作打开一个带有行值的jQueryUI对话框,允许用户进行修改并提交给服务器。当它提交,而不是重新查询我的数据时,我的方法是在AJAX成功时更新dataTable,这要快得多。

在实现连接之前,这是基于单个dataTable,当我更新dataTable时,基于它的dataView也会更新,并且在chartWrapper上调用draw()会像预期的那样更新UI。

麻烦的是,这并不适用于已加入的dataTable。我已经在客户端的开发人员工具中确认dataTable实际上是像dataView那样更新值,但是这从未传播到tableChart。

这是一个非常奇怪的部分。如果在开发工具中使用chartWrapper的.getDataTable方法,我可以确认有问题的记录已更新!虽然生成的可视化中的值不是

我尝试在我的AJAX成功中重新绘制chartWrapper无济于事。它会在滚动级别更改时重绘(chartWrapper),但数据不会更改。我尝试在dev工具中手动重绘chartWrapper和dashboard,甚至将视图作为dataTable传递。两者都实际上是重绘,但都没有更新数据。

我已尝试重新绘制chartWrapper上的getDataTable表,但这基本上与我的仪表板和样式无关,并绘制了不是我需要的整个数据集。

更改类别过滤器后,过滤回来后不会更新tableChart。即使将不同的过滤器应用于dataView然后返回也不会更新它。我发现工作的唯一方法是重新查询整个过程需要5-10秒。每一次改变都是不可接受的延迟。这在第一次加载时很好,但理想情况下,每个用户会话应该只发生一次。

我花了好几个小时调试和搜索(可怕的)文档,我所看到的一切似乎都表明它应该有效。

我开始怀疑这可能是某种缓存问题,或者我是否遗漏了某些未在文档中解释过的技巧。

此特定页面的客户端代码为1100行,因此很难完整发布。以下是我对所包含关键部件所做的极其简化的版本。非常感谢任何建议或意见。

注意,我省略了很多与生成操作对话框或按钮相关的部分,但它们大多不相关。

var loaded = false;
var ready = {pro: false, sec: false};
var data = {};

    // Ajax load gviz api
    $.ajax({
      url: 'https://www.google.com/jsapi?callback',
      cache: true,
      dataType: 'script',
      success: function(){
            google.load('visualization', '1', {packages:['controls'], 'callback' : sendQuery
        });
        return true;
      }
    });
    function sendQuery() {
        console.log('query setting');
        var opts = {sendMethod: 'auto'};

        var urlPro = 'Google Spreadsheet source 1';
        queryPro = new google.visualization.Query(urlPro, opts);
        queryPro.setQuery('select A,B,C,D,E where(G = \'No\')');//...15k+ rows 30+ cols in reality

        var urlSec = 'Google Spreadsheet source 2';
        querySec = new google.visualization.Query(urlSec, opts);
        querySec.setQuery('select A,B,C,D');//...~200+ rows 30+ cols in reality, rows created with app

        queryPro.send(function(response){
            console.log('query Pro returned');
            if (response.isError()) {
                alert('Error in query Pro: ' + response.getMessage() + ' ' + response.getDetailedMessage());
                return;
            }
            ready.pro = true;
            data.pro = response.getDataTable();
            if (ready.pro && ready.sec) {
                drawDashboard(false);
            }
        });
        querySec.send(function(response){
            if (response.isError()) {
                alert('Error in query Sec: ' + response.getMessage() + ' ' + response.getDetailedMessage());
                return;
            }
            console.log('query Sec returned');
            ready.sec = true;
            data.sec = response.getDataTable();
            if (ready.pro && ready.sec) {
                drawDashboard(false);
            }
        });

    }

    function drawDashboard(complete) {
      var pendingCols = [0,2,3,4];
      var completeCols = [1,2,3,4,5,7,8];

      if (!loaded) {
          console.log('first load of data');

          var joinProCols=[1,2,3,4];
          var joinSecCols=[1,2,3];

          joined = new google.visualization.data.join(data.pro, data.sec, 'left', [[0, 0]], joinProCols, joinSecCols);

          viewActive = new google.visualization.DataView(joined);

          var numCols = joined.getNumberOfColumns();
          for (var i=0; i<numCols; i++){
            ogColName = joined.getColumnLabel(i).replace(/\W/g, '');
            if (empTable.hasOwnProperty(ogColName)){ //passed from server; ommitted def in this example
                joined.setColumnLabel(i, empTable[ogColName][lang]);
            }
            joined.setColumnProperty(i, 'ident', ogColName);
          }

      }

      var pendingRows = joined.getFilteredRows([{column: 4, value: null}]);
      var completeRows = joined.getFilteredRows([{column: 4, minValue: ''}]);

      if (complete) {
          viewActive.setColumns(completeCols);
          viewActive.setRows(completeRows);
      } else {
          viewActive.setColumns(pendingCols);
          viewActive.setRows(pendingRows);
      }

      dashboard = new google.visualization.Dashboard(document.getElementById('dashboard-div'));

      // options for displayed table
      var tableOpts = { 
        width: '1500px',
        height: '100%', 
        page: 'enable', 
        pageSize: 40,
        cssClassNames: {
          headerCell: 'gviz header',
          headerRow: 'gviz header',
          oddTableRow: 'gviz odd',
          tableRow: 'gviz even',
          selectedTableRow: 'gviz selected',
          hoverTableRow: 'gviz hover',
          rowNumberCell: 'gviz rowNum'
        }
      };

      tableChart = new google.visualization.ChartWrapper({
        'chartType': 'Table',
        'containerId': 'table-div',
        'options': tableOpts
      });

      var picker1 = new google.visualization.ControlWrapper({
          'controlType': 'CategoryFilter',
          'containerId': 'sel-pick1',
          'options': {
            'filterColumnLabel': 'Picker1',
            'ui': {
              'labelStacking': 'vertical',
              'selectedValuesLayout': 'belowWrapping',
              'caption': 'Picker1'
            }
          }
        });
      var picker2 = new google.visualization.ControlWrapper({
          'controlType': 'CategoryFilter',
          'containerId': 'sel-pick2',
          'options': {
            'filterColumnLabel': 'Picker2',
            'ui': {
              'labelStacking': 'vertical',
              'selectedValuesLayout': 'belowWrapping',
              'caption': 'Picker2'
            }
          }
        });
      //...quite a few more pickers in real code
      if (complete) {

          var picker3 = new google.visualization.ControlWrapper({
            'controlType': 'CategoryFilter',
            'containerId': 'sel-pick3',
            'options': {
              'filterColumnLabel': 'picker3',
              'ui': {
                'labelStacking': 'vertical',
                'selectedValuesLayout': 'belowWrapping',
                'caption': 'picker3'
              }
            }
          });
          //...A couple more pickers in real app here too
      }
      // Set up dependencies between controls and charts
      dashboard.bind(picker1, picker2);
      //...bindings for other pickers
      if (complete) {
          dashboard.bind(picker2, picker3);
          dashboard.bind([picker1, picker2, picker3], tableChart);
      } else {
          dashboard.bind([picker1, picker2], tableChart);
      }
      // Draw all visualization components of the dashboard and add listeners

      google.visualization.events.addListener(tableChart, 'ready', function(){
        google.visualization.events.addListener(tableChart.getChart(), 'page', addFields);
        google.visualization.events.addListener(tableChart.getChart(), 'sort', addFields);
        google.visualization.events.addListener(tableChart.getChart(), 'select', function(e){
            //cancel selection here as it won't be useful
            tableChart.getChart().setSelection('');
        });
        //grab the table and inject our custom actions into it
        if (complete) {
            addFields(true);
        } else {
            addFields();
        }
        // show containers
        $('.processing.page').hide();
        $('#dashboard-div').show();
        loaded = true;

      });


      dashboard.draw(viewActive);


  }

  //........................................

  $('#dataForm').submit(function( event ) {
            if ($('#dataForm').valid()){
                $('#dataForm').hide();
                $('.processing.data.form').show();
                google.script.run.withSuccessHandler(onSuccessUpdate).recordData(this);
            }
            event.preventDefault();
    });
    //...................................................
    function onSuccessUpdate(response){
      var numCols = joined.getNumberOfColumns();
      for (var i=0; i<numCols; i++){
          for (var key in response.form){
            if (!response.form.hasOwnProperty(key)) continue;
            if (joined.getColumnProperty(i, 'ident') == key){
                joined.setValue(response.row, i, response.form[key]);
            }
          }
      }
      tableChart.draw();//This should be updating the values!
      $('.message.success').html(response.message).show();
      $('#dataForm').show();
      $('.processing.data.form').hide();
      $('#userProfileDialog').dialog('close');
  }

提前感谢任何见解。这个让我发疯了。

1 个答案:

答案 0 :(得分:1)

事实证明,一切都正常运作,问题实际上是我错过了一个关于如何运作的关键方面。

在循环中更新连接的dataTable值时,我正在使用setValue()方法更新'value'。

但是,渲染图表实际读取的“值”是可通过setFormattedValue()方法访问的“格式化值”。以编程方式更新值时,似乎没有自动更新formattedValue。我可能错误地认为,或者更可能的是,由于我没有使用它,因此对它的那一部分完全无知。

最初,在使用联接之前,当我只使用单个数据源且一切正常时,我将'options no_format'参数传递给查询。在这种情况下,似乎API会使用值而不是formatValue作为实际的表格图。

一旦我意识到我的错误,我第一次尝试修复就是在我的两个数据查询中添加'options no_format'参数。这似乎不起作用。看起来join方法会自动生成格式化的值,即使它的源dataTables没有。

因此,对我原始代码的更正是除了setValues方法之外还使用setFormattedValue方法,如下所示:

function onSuccessUpdate(response){
      var numCols = joined.getNumberOfColumns();
      for (var i=0; i<numCols; i++){
          for (var key in response.form){
            if (!response.form.hasOwnProperty(key)) continue;
            if (joined.getColumnProperty(i, 'ident') == key){
                joined.setValue(response.row, i, response.form[key]);
                //Need to also set formatted value
                joined.setFormattedValue(response.row, i, response.form[key]);
            }
          }
      }
      tableChart.draw();//This should be updating the values!
      $('.message.success').html(response.message).show();
      $('#dataForm').show();
      $('.processing.data.form').hide();
      $('#userProfileDialog').dialog('close');

更新值和格式化值后,图表会在绘制时按预期更新。

所以最后,这只是我的一个错误,但我希望有这个示例代码的人可能有一些价值,以及join方法生成的dataTable的这种特殊行为。