我有一个Vue.js 2.0组件,它提供了一个带有过滤器选择框的动态重新加载数据的Chart.js 2.0条形图。
在将单击事件添加到图表数据集之后,我在用过滤器重新加载数据时遇到了一个问题。当页面第一次加载时,我可以单击每个数据集并将信息记录到控制台。在从选择框中选择一个筛选器并通过AJAX重新加载数据集之后,当单击数据集时,我会得到一些零星的结果。大多数情况下,在重新加载之后,我会得到Uncaught TypeError: Cannot read property '_index' of undefined,但有时我会在2-4个数据集之间记录到控制台,除了一个之外,所有数据集都不再出现在当前视图中。我的组件代码在下面。如何在重新加载图表数据后修复单击事件?任何帮助都是非常感谢的。
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);
});
},
},}
发布于 2016-10-28 15:37:28
我已经在这里发布了一个答案,其中指出了一些Vue.js使用问题。但我认为真正的问题是不同的,解释如下:
reload函数执行以下操作:
this.chart.destroy();
this.load();
this.setChartClickHandler(this.ctx, this.chart);如果您注意到,第二行是this.load(),它异步调用fetchData。当response通过时,可以使用this.render(response.data)呈现图表。
假设您的AJAX数据需要400毫秒的时间。所以你的图表只会在400毫秒后呈现。但是你的this.chart已经被销毁了,只有在你的render函数中使用new Chart(...)的400毫秒后才会被重新创建。
但是您的reload函数立即开始调用this.setChartClickHandler,并引用已经销毁的this.chart。
要解决这个问题,只需要在在this.setChartClickHandler(..)函数中创建new Chart之后才调用render。
编辑:关于更新的问题的其他想法
您现在有了另一个问题:您的函数正在内部创建一个新的作用域,而且绑定this也有问题。
您目前有以下内容:
this.$nextTick(function() {
this.setChartClickHandler(this.ctx, this.chart);
});相反,您需要将其更改为:
this.$nextTick(() => {
this.setChartClickHandler(this.ctx, this.chart);
});箭头函数确保您不会在内部创建新的作用域,并且函数中的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);
});上述各项必须是:
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);
});发布于 2016-10-28 15:03:46
您正在尝试使用{{...}} (胡子)绑定您的类和ID。VueJS不允许您这样做。相反,需要使用v-bind,如下所示:
模板中的第一行当前是:
<div class="{{ chartDivSize }}">您需要将其更改为:
<div v-bind:class="[chartDivSize]">参考文献:https://vuejs.org/guide/class-and-style.html#Object-Syntax
最重要的是,您的画布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>参考文献:https://vuejs.org/guide/syntax.html#Attributes
与v-bind:class和v-bind:id不同,您还可以使用:class或:id之类的速记格式,如用于v-bind的API文档中指定的那样。
您可能在上述情况下仍有问题。VueJS呈现DOM,但是您的画布可能不是即时可用的。您需要等待一个轻微的延迟,然后才能在DOM中使用带有该ID的画布。为此,您可以在调用Vue.nextTick() chart.jsAPI之前使用chart.js API,或者简单地使用this.$nextTick()。
Vue.nextTick():http://vuejs.org/api/#Vue-nextTick参考
编辑:附加参考
从技术上讲,上面的代码应该可以工作--它应该为您提供一个带有指定ID的画布元素。在nextTick()中,您应该能够得到那个<canvas>并在其中画一些东西。
如果由于任何原因它无法工作,则可能需要使用指令捕获<canvas>元素。下面是Stackoverflow中最近的一个答案(我几天前发布的),它有一个jsFiddle示例:https://stackoverflow.com/a/40178466/654825
在这个答案的jsFiddle示例中,我没有使用id参数就捕获了<canvas>元素。原因:组件应该在多个地方重用,因此有一个固定的id可能会达到这个目的。如该示例所示,您可以尝试使用指令直接捕获<canvas>元素,这使您的组件完全可重用。
直接引用该jsFiddle示例:https://jsfiddle.net/mani04/r4mbh6nu/
https://stackoverflow.com/questions/40306573
复制相似问题