我有一个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);
});
},
},
}
答案 0 :(得分:3)
我已经在这里发布了一个答案,指出了一些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
函数内400毫秒后才会使用new Chart(...)
重新创建。
但是,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);
});
答案 1 :(得分:0)
您正在尝试使用{{...}}
(胡须)来绑定您的类和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
。
在上述情况之后,您可能仍会遇到问题。 VueJS呈现DOM,但您的画布可能无法立即可用。在具有该ID的画布在DOM中可用之前,您需要等待稍微延迟。为此,您可以在调用 chart.js API之前使用Vue.nextTick()
或简称this.$nextTick()
。
Vue.nextTick()
的参考:the API docs for v-bind
修改:其他参考
上面的代码在技术上应该可行 - 它应该为您提供一个指定了ID的canvas元素。在nextTick()
中,您应该能够获得<canvas>
并将内容绘制到其中。
如果由于任何原因不起作用,您可能需要使用指令捕获<canvas>
元素。这是Stackoverflow中最近的一个答案(我几天前发布的),它有一个jsFiddle示例:http://vuejs.org/api/#Vue-nextTick
在该答案的jsFiddle示例中,我在不使用<canvas>
参数的情况下捕获了id
元素。原因:组件应在多个位置重复使用,因此具有固定的id
可能会失败该目的。如该示例所示,您可以尝试使用指令直接捕获<canvas>
元素,这使您的组件可以完全重用。
直接引用该jsFiddle示例:https://stackoverflow.com/a/40178466/654825