I am created graphs with dynamic data from axios by chartjs library.
But when I refresh browser graphs at first nothing available, like this:
If I change dimensions for browser window - all graphs appears:
Error from concole:
Uncaught TypeError: Cannot read properties of null (reading 'transition')
at Chart.transition (Chart.js?473e:9865:1)
at Chart.draw (Chart.js?473e:9827:1)
at Chart.render (Chart.js?473e:9799:1)
at Object.callback (Chart.js?473e:2208:1)
at Object.advance (Chart.js?473e:3544:1)
at Object.startDigest (Chart.js?473e:3517:1)
at eval (Chart.js?473e:3506:1)
I don't have idea, why it can be so, and what to do.
It's look like firstly browser tried build graphs (but data didn't received yet).
During all calculation approximately 580ms.
This MainChart.vue for build charts:
<template>
<div>
<canvas id="main-chart" height="400"></canvas>
</div>
</template>
<script>
import Chart from 'chart.js'
export default {
name: 'MainChart',
props: ['chartData'],
mounted() {
const ctx = document.getElementById('main-chart');
new Chart(ctx, this.chartData);
}
}
</script>
This common page Analytics-test.vue.
I skipped all methods.
<template>
<div> Прибыль/посещаемость <div class="small">
<MainChart :chart-data="datacollection" />
</div>
</div>
</template>
<script>
import MainChart from '../MainChart.vue'
export default {
components: { MainChart },
data: () => ({
flagStartDate: false,
chartData: null,
labels: [],
datasets: {},
draftData: null,
data: [],
datacollection: { type: 'line', },
clubsId: ['5c3c5e12ba86198828baa4a7', '5c3c5e20ba86198828baa4c5', '60353d6edbb58a135bf41856', '61e9995d4ec0f29dc8447f81', '61e999fc4ec0f29dc844835e'],
}),
methods: {
...some a lot of code..},
async mounted() {
await this.loadIncomings(this.clubsId),
await this.loadAvgIncomings(this.clubsId),
await this.loadAvgPayments(this.clubsId),
await this.loadAvgSchedule(this.clubsId),
await this.loadTrainings(this.clubsId)
},
</script>
There are 19 functions in methods, and I decided don't input here.
This error seems similar to the one described in this GitHub issue: https://github.com/chartjs/Chart.js/issues/5149 It sounds like it could be caused due to the fact that the chart is immediately loaded with data, but there are a couple of solutions in the comments on that issue.
Related
I have this very simple page, that works properly:
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://unpkg.com/vue#next"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js#3.7.0/dist/chart.js"></script>
</head>
<body>
<main id="vue-app">
<p>{{ name }}</p>
<canvas id="chart-flow-rate"></canvas>
</main>
</body>
<script>
// Start VueJS
const Application = {
data() {
return {
name: "My Chart"
};
}
}
vm = Vue.createApp(Application).mount('#vue-app');
// Use ChartJS
const myChart = new Chart('chart-flow-rate', {
type: 'bar',
data: {
labels: ['4', '2'],
datasets: [{
data: [4, 2],
}]
}
});
</script>
However, if I invert the blocs of JS to use ChartJS first and then to create the VueJS application, the page no longer works: the chart is not shown.
Why?
I have noticed that I can change my HTML's body a little in order to be able to use ChartJS before VueJS:
<body>
<main>
<p id="vue-app">{{ name }}</p>
<canvas id="chart-flow-rate"></canvas>
</main>
</body>
Again: why?
Thanks a lot! :)
That is because VueJS uses virtual DOM and it updates the actual DOM in batches: what you're providing your app is an inline template, which VueJS will parse, interpolate, and rewrite it to the DOM. Therefore if you initialise ChartJS first, it will lose the reference to the DOM element (it is either gone because of a race condition with VueJS, or it is quickly overwritten by Vue once it computes virtual DOM and outputs it to the actual DOM).
The reason why changing your markup worked is because now the element that ChartJS uses to mount and render the chart is no longer erased or manipulated by VueJS, as it is no longer part of the Vue app but resides outside of it in regular DOM.
In fact, the most parsimonious solution is to simply instantiate ChartJS in the mounted hook of your VueJS app, and after waiting for the DOM to be ready, i.e.:
mounted: async function() {
// Wait for the DOM to be rendered after next tick
await this.$nextTick();
// Use ChartJS with the element that now exists in the DOM
const myChart = new Chart('chart-flow-rate', {
type: 'bar',
data: {
labels: ['4', '2'],
datasets: [{
data: [4, 2],
}]
}
});
}
In fact, I would go a step further and implore you to avoid using DOM query methods and take advantage of the ref attribute and the $refs instance property. In your template, update your markup to use refs:
<canvas ref="chart"></canvas>
Since ChartJS accepts an element as the first argument, you can simply access the element using this.$refs.canvas:
const myChart = new Chart(this.$refs.chart, { ... })
See proof-of-concept below:
// Start VueJS
const Application = {
data() {
return {
name: "My Chart"
};
},
mounted: async function() {
await this.$nextTick();
// Use ChartJS
const myChart = new Chart(this.$refs.chart, {
type: 'bar',
data: {
labels: ['4', '2'],
datasets: [{
data: [4, 2],
}]
}
});
}
}
vm = Vue.createApp(Application).mount('#vue-app');
<script src="https://unpkg.com/vue#next"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js#3.7.0/dist/chart.js"></script>
<main id="vue-app">
<p>{{ name }}</p>
<canvas ref="chart"></canvas>
</main>
[Vue warn]: Property or method "modelInfo" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
This is the error I receive when fetch data
<template>
<main>
{{ modelInfo }}
</main>
</template>
<script>
import searchByNameQ from '~/apollo/queries/models/searchByNameq'
export default {
async asyncData({ $strapi }) {
// Fetch model data
const modelInfo = await $strapi.graphql({
query: searchByNameQ.loc.source.body,
variables: { name: ['Transit Custom'] }
})
return {
modelInfo
}
}
}
</script>
On refresh and nuxt-link this works. But it errors with hot module update. The output of modelInfo is:
{
"models": [{
"id": "2",
"name": "Transit Custom",
"size": "mellem"
}]
}
EDIT: Found the error. This code is correct, but the error is caused by a by a config in nuxt.config
I have been having this setting, because it fixed a Google Analytics issue. When this config is removed, there is no error.
features: { transitions: false },
I have been having this setting in nuxt.config, because it fixed a Google Analytics issue. When this config is removed, there is no error.
features: { transitions: false }
I'm a bit of a newbie with Vue (v2) and am trying to integrate it into a .net core mvc application. Essentially, I am planning on only using it on one or two pages but will have a number of nested components.
My problem (I think) is that I'm not using webpack and am just using .js files for the components. To do this, I'm trying to import the components as required. When I got started, I was just using script tags to bring in components as none were nested. However, I'm now finding the need to get the imports working and am having a problem which I can't seem to resolve.
My page looks as such (Simplified):
<div id="app" style="margin-top: 20px;"></div>
<script type="module">
import * as ComponentContainer from '#Href("~/js/components/ComponentContainer.js")';
new Vue({
el: '#app',
data: function () {
return {
bComponents: [
{
type: 'Type A',
sortOrder: 1,
uniqueId: 'ABCDEF'
},
{
type: 'Type B',
sortOrder: 3,
uniqueId: '123456'
},
{
type: 'Type A',
sortOrder: 2,
uniqueId: 'QWERTY'
}
],
editMode: false
}
},
methods: {
componentMethod(selectorId) {
console.log(selectorId);
},
},
computed: {
sortedBComponents: function () {
return this.bComponents.sort((a, b) => a.sortOrder - b.sortOrder);
}
},
components: {
'component-container': ComponentContainer
},
template: `
<div>
<component-container v-for="comp in sortedBComponents"
:key="comp.uniqueId"
:componentData="comp"
:editMode="editMode"
##component-method="componentMethod">
</component-container>
</div>
`
});
</script>
The component (component-container) has the following code:
export { ComponentContainer }
var ComponentContainer = {
props: ['componentData', 'editMode'],
mounted: function() {
},
methods: {
componentSelected() {
this.$emit('component-method', this.componentData.uniqueId);
}
},
components: {
},
template: `
<div :id="componentData.uniqueId + '-contianer'">
{{componentData.type}}, {{componentData.sortOrder}} & {{componentData.uniqueId}}
<a type="button" #click="componentSelected">Test</a>
</div>
`
}
This worked when the component was referenced via the script tag, but since trying to get it to work with the 'import' method, it appears to fail in a way were I can't figure out what's happening.
I get the following error in the conosle:
[Vue warn]: Error in render: "TypeError: "_Ctor" is read-only"
(found in <Root>)
And:
TypeError: "_Ctor" is read-only
I can't seem to figure out what this refers to. I know that the ComponentContainer is being loaded as I can write it to the console but maybe it's not loading in correctly. It works if I bring in the component through a script tag, but not through the import method.
Many thanks in advance for looking at this.
Try this:
import { ComponentContainer } from '/js/components/ComponentContainer.js';
I have this error here
I could limit the error to this line here
mounted() {
this.$nextTick(() => {
let ctx = this.$refs.canvas.getContext('2d')
let { chartType, dataOptions } = this.module
this.chart = new Chart(ctx, {
type: chartType,
data: dataOptions,
options: minimizeOptions,
})
})
},
The error comes from dataOptions. If i set data to {} everything works ok, but obviously my chart has no data then.
this.module is an prop that is being passed to my component. The component itself gets rendered in an v-for loop
<module
v-for="mod in modules"
:module="mod"
:key="mod._id.toString()"
/>
I am using here Chart.js.
I cannot find the reason for this call stack exceed error.
Maybe somebody had similar problems?
I also need to mention that this error happens when i want to toggle an global component that is placed in an layout layout
dataOptions:
{
datasets: [
{
backgroundColor: "#34495e",
borderColor: "bdc3c7",
data: [0],
label: "My first dataset"
}
],
labels: ["Start"]
}
I actually does not need reactivity on this prop, so i decided to freeze it up.
let { chartType, dataOptions } = Object.freeze(this.module);
Reactivity is gone an the error aswell. This fixed my problem.
Maybe somebody has an solution for reactive data
This is kind of a long explanation of an issue that I'm having on a personal project. Basically, I want to set a data property before my page loads when I read in data from a CSV file using D3.JS. I almost have it done but running into a small issue. Please read on to get more detail.
Basically, when the user comes to a page in my application, I want to display weather graphs. Like I said, I'm using D3.js to read in the data and created an action to do that. It works perfectly fine-I can console.log the data and I know its been read. However, in my vue instance I have a data property, which would hold the data set like this:
data() {
return {
name: this.$store.state.name
weatherData: this.$store.state.yearData
}
}
I then want to ensure that the weatherData is filled, with data from the csv file so I display it on the page like this:
<p>{{ weatherData }}</p>
Nothing special here. When the page loads, weatherData is blank. But I have a beforeMount life cycle hook and if I comment out the only line in it then it will display the data. If I then refresh the page, fire the action to get the data and then uncomment out the line in the beforeMount hook then the data appears! So before I continue this is my full code for the store:
export const store = new Vuex.Store({
state: {
name: 'Weather Data'
yearData: []
},
getters: {
},
mutations: {
setYearData(state, data) {
state.yearData = data
}
},
actions: {
getYearData: ({commit}) => {
d3.csv("../src/components/data/alaska.csv")
.then(function(data){
let yearData = []
for (let i = 0; i < data.length; i++){
let day = data[i].AKST
yearData.push(day)
}
//console.log(yearData)
commit('setYearData', yearData)
})
}
})
Here are parts of the vue file: The template:
<p>{{ weatherData }}</p>
The Vue Intance:
export default {
name: 'Weather',
data() {
return {
name: this.$store.state.name,
weatherData: this.$store.state.yearData
}
},
methods: {
...mapActions([
'getYearData'
])
},
beforeMount(){
this.$store.dispatch('getYearData') //(un)Commenting out this line will make my data appear
}
}
Page when it loads: Notice empty array:
Then either comment out or comment the one line in the beforeMount hook and get this: THE DATA!!!
Again, my end goal is to have the action called and the data set before the page finishes loading. Finally, I know that I don't need VUEX but this project is further helping me understand it. Any guidance on why this is happening would be great.
use mapState instead of putting your data in the data object, which sometimes being late on updating the template.
just make your Vue instance to look like:
import {mapState} from 'vuex'
export default {
name: 'Weather',
data() {
return { }
},
computed:{
...mapState({
name: state=>state.name,
weatherData: state=>state.yearData
})
},
methods: {
...mapActions([
'getYearData'
])
},
beforeMount(){
this.$store.dispatch('getYearData') //(un)Commenting out this line will make my data appear
}
thats way, you work directly with one source of truth-the store, and your name and weatherData will be reactive as well.
more about mapState here: https://vuex.vuejs.org/guide/state.html#the-mapstate-helper