首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用于Vue.js组件的Chart.js - AJAX重新加载中的奇怪问题

用于Vue.js组件的Chart.js - AJAX重新加载中的奇怪问题
EN

Stack Overflow用户
提问于 2016-10-28 13:52:36
回答 2查看 1.4K关注 0票数 3

我有一个Vue.js 2.0组件,它提供了一个带有过滤器选择框的动态重新加载数据的Chart.js 2.0条形图。

在将单击事件添加到图表数据集之后,我在用过滤器重新加载数据时遇到了一个问题。当页面第一次加载时,我可以单击每个数据集并将信息记录到控制台。在从选择框中选择一个筛选器并通过AJAX重新加载数据集之后,当单击数据集时,我会得到一些零星的结果。大多数情况下,在重新加载之后,我会得到Uncaught TypeError: Cannot read property '_index' of undefined,但有时我会在2-4个数据集之间记录到控制台,除了一个之外,所有数据集都不再出现在当前视图中。我的组件代码在下面。如何在重新加载图表数据后修复单击事件?任何帮助都是非常感谢的。

代码语言:javascript
复制
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);
        });
    },
},

}

EN

回答 2

Stack Overflow用户

发布于 2016-10-28 15:37:28

我已经在这里发布了一个答案,其中指出了一些Vue.js使用问题。但我认为真正的问题是不同的,解释如下:

reload函数执行以下操作:

代码语言:javascript
复制
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也有问题。

您目前有以下内容:

代码语言:javascript
复制
this.$nextTick(function() {
    this.setChartClickHandler(this.ctx, this.chart);
});

相反,您需要将其更改为:

代码语言:javascript
复制
this.$nextTick(() => {
    this.setChartClickHandler(this.ctx, this.chart);
});

箭头函数确保您不会在内部创建新的作用域,并且函数中的this与Vue组件的this相同。

还有一个函数需要进行相同的更改:

代码语言:javascript
复制
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);
});

上述各项必须是:

代码语言:javascript
复制
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);
});
票数 3
EN

Stack Overflow用户

发布于 2016-10-28 15:03:46

您正在尝试使用{{...}} (胡子)绑定您的类和ID。VueJS不允许您这样做。相反,需要使用v-bind,如下所示:

模板中的第一行当前是:

代码语言:javascript
复制
<div class="{{ chartDivSize }}">

您需要将其更改为:

代码语言:javascript
复制
<div v-bind:class="[chartDivSize]">

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

最重要的是,您的画布ID可能没有在组件中正确绑定。目前,你有:

代码语言:javascript
复制
<canvas id="{{ chartId }}" style="height: 352px; width: 704px;" width="1408" height="704"></canvas>

它必须是:

代码语言:javascript
复制
<canvas v-bind:id="chartId" style="height: 352px; width: 704px;" width="1408" height="704"></canvas>

参考文献:https://vuejs.org/guide/syntax.html#Attributes

v-bind:classv-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/

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/40306573

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档