2016-10-28 6 views
3

私は、フィルタ選択ボックスでデータを動的に再読み込みするChart.js 2.0棒グラフを提供するVue.js 2.0コンポーネントを持っています。Chart.jsのVue.jsコンポーネント - AJAXリロードの奇妙な問題

グラフのデータセットにクリックイベントを追加した後、フィルタでデータを再読み込みするときに問題が発生しました。ページが最初にロードされるとき、私は各データセットをクリックし、情報をコンソールに記録することができます。選択ボックスからフィルタを選択し、データセットをAJAX経由でリロードした後、データセットをクリックすると散発的な結果になります。再ロード後のほとんどの場合、私はUncaught TypeError: Cannot read property '_index' of undefinedを取得しますが、コンソールに記録された2〜4個のデータセットを取得することもありますが、現在のビューには存在しないものが1つあります。私のコンポーネントコードは以下の通りです。グラフデータを再読み込みした後にクリックイベントを修正するにはどうすればよいですか?どんな助けもありがとうございます。

import Chart from 'chart.js'; 

export default { 
template: ` 
    <div v-bind:class="[chartDivSize]"> 
     <div class="card"> 
      <div class="header"> 
       <div class="row"> 
        <div class="col-md-6"> 
         <h4 class="title"> 
          {{ title }} 
         </h4> 
         <p class="category">{{ category }}</p> 
        </div> 
        <div v-if="isManager" class="col-md-4 pull-right"> 
         <h4 class="title pull-right">Filter by Team</h4> 
         <select v-model="selectedFilter" @change="reload" class="form-control selectpicker"> 
          <option v-for="filter in filters">{{ filter }}</option> 
         </select> 
        </div> 
       </div> 
      </div> 
       <div class="content"> 
        <canvas v-bind:id="chartId" style="height: 352px; width: 704px;" width="1408" height="704"></canvas> 
       </div> 
       <div class="footer"> 
        <div class="legend"> 
        </div> 
        <hr> 
        <div class="stats"> 
         <i class="fa fa-clock-o"></i>{{ stats }} 
        </div> 
       </div> 
      </div> 
     </div> 
    </div> 
`, 

props: ['ctx', 'chart', 'url', 'chartId', 'chartDivSize', 'title', 'category', 'stats', 'filters', 'isManager', 'selectedFilter', 'activePoints'], 

ready() { 
    this.load(); 
}, 

methods: { 
    load() { 
     this.fetchData().then(
      response => this.render(response.data) 
     ); 
    }, 
    fetchData() { 
     if(this.selectedFilter) { 
      var resource = this.$resource(this.url); 
      return resource.get({ filter: this.selectedFilter }); 
     } else { 
      return this.$http.get(this.url); 
     } 
    }, 
    render(data) { 
     this.title = data.title; 
     this.category = data.category; 
     this.stats = data.stats; 
     this.filters = data.filters; 
     this.filters.unshift('All'); 
     if(data.selectedFilter) { 
      this.selectedFilter = data.selectedFilter; 
     } else { 
      this.selectedFilter = 'All'; 
     } 
     this.isManager = data.isManager; 

     this.ctx = $("#" + this.chartId); 

     var chartData = { 
      labels: data.labels, 
      datasets: [ 
       { 
        label: data.metric, 
        data: data.data, 
        backgroundColor: data.background_colors, 
        hoverBackgroundColor: data.hover_colors 
       }] 
     }; 

     this.chart = new Chart(this.ctx, { 
      type: "bar", 
      data: chartData, 
      options: { 
       scaleLabel: { 
        display: true 
       }, 
       scales: { 
        yAxes: [{ 
         ticks: { 
          beginAtZero: true, 
          fontStyle: "bold" 
         } 
        }], 
        xAxes: [{ 
         ticks: { 
          beginAtZero: true, 
          fontStyle: "bold" 
         } 
        }] 
       }, 
       legend: { 
        display: false 
       } 
      } 
     }); 

     this.$nextTick(() => { 
      this.setChartClickHandler(this.ctx, this.chart); 
     }); 

    }, 
    reload() { 
     this.chart.destroy(); 
     this.load(); 
    }, 

    setChartClickHandler(ctx, chart) { 
     ctx.on('click', evt => { 
      var activePoints = chart.getElementsAtEvent(evt); 
      var label = chart.data.labels[activePoints[0]._index]; 
      var value = chart.data.datasets[activePoints[0]._datasetIndex].data[activePoints[0]._index]; 
      console.log(label,value); 
     }); 
    }, 
}, 

}

答えて

0

あなたのクラスやIDを結合するための{{...}}(口ひげ)を使用しようとしています。 VueJSではこれを許可していません。代わりに、以下に説明するように、V-バインドを使用する必要があります。

<div v-bind:class="[chartDivSize]"> 

参考:https://vuejs.org/guide/class-and-style.html#Object-Syntax

あなたはそれを変更する必要が

<div class="{{ chartDivSize }}"> 

テンプレートの最初の行を現在

最も重要なことに、キャンバスIDがおそらくコンポーネントで正しくバインドされていない可能性があります。現在、あなたが持っている:

<canvas id="{{ chartId }}" style="height: 352px; width: 704px;" width="1408" height="704"></canvas> 

それはする必要があります:

<canvas v-bind:id="chartId" style="height: 352px; width: 704px;" width="1408" height="704"></canvas> 

参考:代わりにv-bind:classv-bind:idhttps://vuejs.org/guide/syntax.html#Attributes

に指定されているように、あなたはまた、:classまたは:idのような速記形式を使用することができますthe API docs for v-bind

上記の後も問題が発生することがあります。 VueJSはDOMをレンダリングしますが、キャンバスは瞬時に利用できない場合があります。そのIDを持つキャンバスがDOMで使用できるようになるまで少し待つ必要があります。そのため、、またはchart.js APIを呼び出す前に、単にthis.$nextTick()を使用してください。 Vue.nextTick()ため

参考:http://vuejs.org/api/#Vue-nextTick

編集:追加の参考

上記のコードは、技術的に動作するはずです - それはあなたが指定したIDのcanvas要素を与える必要があります。そして、nextTick()では、あなたはそれを<canvas>にして、その中にものを描くことができるはずです。

何らかの理由で動作しない場合は、<canvas>要素をディレクティブを使用してキャプチャする必要があります。ここでjsFiddle例を持っているのStackOverflowの最近の答えは(私は数日前に掲示した)、次のとおりです。https://stackoverflow.com/a/40178466/654825

jsFiddleの例ではその答えのために、私はidパラメータを使用せずに<canvas>要素をキャプチャしています。理由:コンポーネントは複数の場所で再利用されることを意図しているため、固定されたidを使用するとその目的を無効にする可能性があります。この例のように、ディレクティブを使用して直接<canvas>要素をキャプチャしようとすると、コンポーネントを完全に再利用できるようになります。そのjsFiddle例に

直接参照:https://jsfiddle.net/mani04/r4mbh6nu/

+0

マイ上記の答えのポイントはしかし、実際の問題は何か他のもののようです。私はここに別の答えを掲示して、それをチェックしてください。 – Mani

3

私はすでにいくつかのVue.js使用上の問題を指しており、ここでの回答を掲載しています。しかし、私は、以下に説明するように本当の問題は、違うと思う:

あなたreload機能は、次の処理を実行します。

あなたが気付いた場合
this.chart.destroy(); 
this.load(); 
this.setChartClickHandler(this.ctx, this.chart); 

は、二行目は、非同期fetchDataを呼び出すthis.load()です。 responseが届くと、this.render(response.data)

としましょう。あなたのAJAXデータが400ミリ秒かかるとしましょう。したがって、あなたのグラフは400ミリ秒後にレンダリングされます。しかし、あなたのthis.chartは既に破棄されており、new Chart(...)を使用してrender関数内で400ミリ秒後に再作成されます。

しかし、reload関数は、すぐにthis.setChartClickHandlerを呼び出して、this.chartを参照して呼び出され、既に破棄されています。

この問題を解決するには、がrender関数内に作成された後にのみ、this.setChartClickHandler(..)に電話する必要があります。

EDIT:あなたの関数は内部で新しいスコープを作成している、とあなたはthis

の結合に問題を持っているあなたは現在、次のを持っている:更新された質問

上の追加の考えはあなたが今、別の問題を抱えています:

this.$nextTick(function() { 
    this.setChartClickHandler(this.ctx, this.chart); 
}); 

は代わりに、あなたはそれを変更する必要があります

this.$nextTick(() => { 
    this.setChartClickHandler(this.ctx, this.chart); 
}); 

arrow機能は、新しいスコープを内部で作成しないことを保証し、機能内のthisは、Vueコンポーネントのthisと同じです。

あなたが同じ変更を加える必要がある別の関数があります:

ctx.on('click', function(evt) { 
    var activePoints = chart.getElementsAtEvent(evt); 
    var label = chart.data.labels[activePoints[0]._index]; 
    var value = chart.data.datasets[activePoints[0]._datasetIndex].data[activePoints[0]._index]; 
    console.log(label,value); 
}); 

上記の行をする必要がありますいくつかの `Vue.js`の使用上の問題へ

ctx.on('click', evt => { 
    var activePoints = chart.getElementsAtEvent(evt); 
    var label = chart.data.labels[activePoints[0]._index]; 
    var value = chart.data.datasets[activePoints[0]._datasetIndex].data[activePoints[0]._index]; 
    console.log(label,value); 
}); 
+0

こんにちは@マニ、助けてくれてありがとう。私はあなたのフィードバックに基づいて私の質問のコンポーネントを更新し、私はおそらく$ nextTickを誤って使用していると思う同じ結果を得ています。私はバインディングに関するドキュメントも探検します。これがVueコンポーネントの最初の亀裂です。 – lefty29er

+2

あなたのエラーは「未定義の_indexを読むことができません」です。だから、 'setChartClickHandler'関数の中にある' activePoints [0] ._index'の周りにデバッグ作業を集中させる必要があります。 AJAXの実行後に 'load()'関数からコールバックを提供する必要があると思います。その後、 'this.chart'が定義され、' activePoints'には何かがあります。 – Mani

+0

あなたの編集したコードが気になりました。今、あなたは 'this'のバインドに問題があります。矢印関数をどこからでも使用する必要があります。上記の私の編集された答えをチェックしてください。 – Mani