How to make dynamic chart using Vue component with chart-js - vuejs2

I am making Bubble sort visualization algorithm and wanted to show to the process of shorting using line graph.
I have tried to implement the computed property but the browser hangs.
<template>
<div class="hello">
<h1>Bubble Sort</h1>
<p>{{ bubbleSort()}}</p>
<line-chart :data="bubbleSort()"></line-chart>
</div>
</template>
<script>
export default {
name: "HelloWorld",
data() {
return {
a : [12, 0, 4, 546, 122, 84, 98, 64, 9, 1, 3223, 455, 23, 234, 213]
}
},
methods : {
bubbleSort: function () {
let swapped;
do {
swapped = false;
for(var i = 0; i < this.a.length; i++){
if(this.a[i] > this.a[i+1]){
let temp = this.a[i];
this.a[i] = this.a[i+1];
this.a[i+1] = temp;
swapped = true;
}
}
}while(swapped);
return Object.assign({},this.a);
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
</style>
I expect the chart to update while shorting is happening.

Using a computed property is not the best way to implement a bubble sort visualization, for two reasons:
The computed property is re-calculated every time a changes, and you are changing the value of a in the computed property itself; this is probably what causes the browser to hang.
There is no noticeable delay between executing the computed property function and updating the view, so the user won't see any animation.
Since you are not using a directly in your template, and the computed property has only one dependency, a, get rid of the computed property, it's not needed.
Instead, create a function that completes a single pass of the bubble sort algorithm, and call that function in a setInterval, cancelling the loop when a pass is made with 0 swaps:
export default {
name: 'HelloWorld',
data() {
return {
a : [12, 0, 4, 546, 122, 84, 98, 64, 9, 1, 3223, 455, 23, 234, 213],
intervalId: null,
}
},
methods: {
bubbleSort() {
let swapped = false;
for (let i = 0; i < this.a.length - 1; i++) {
if (this.a[i] > this.a[i+1]) {
const temp = this.a[i];
this.$set(this.a, i, this.a[i+1]);
this.$set(this.a, i+1, temp);
swapped = true;
}
}
if (!swapped) {
clearInterval(this.intervalId);
}
},
},
mounted() {
this.intervalId = setInterval(() => {
this.bubbleSort();
}, 2000);
}
};
Notes:
Vue's reactivity system won't notice changes to arrays when accessing them via index, so $set must be used instead. See https://v2.vuejs.org/v2/guide/list.html#Caveats.
A bar chart will look nicer in this case than a line chart.
a is a very nondescript variable name, try to give it a more meaningful and unique name.
Be more precise than "browser hangs" when describing a bug. Does the browser window just freeze (i.e. can't interact with the page)? How long does it take for that to happen? Are there errors in the console? Etc.
You have a typo in your question: swapp. Don't be so lazy. If you don't give a shit about your question, no one will give a shit about answering it.

Related

Changes made to a computed array won't show after a prop binding

I'm quite new to vue and right now I'm trying to figure out how to make changes to a computed array and make an element react to this change. When I click the div element (code section 4), I want the div's background color to change. Below is my failed code.
Code section 1: This is my computed array.
computed: {
arrayMake() {
let used = [];
for (let i = 0; i < 5; i++) {
used.push({index: i, check: true});
}
return used;
Code section 2: This is where I send it as a prop to another component.
<test-block v-for="(obj, index) in arrayMake" v-bind:obj="obj" v-on:act="act(obj)"></card-block>
Code section 3: This is a method in the same component as code section 1 and 2.
methods: {
act(obj){
obj.check = true;
}
Code section 4: Another component that uses the three sections above.
props: ["obj"],
template: /*html*/`
<div v-on:click="$emit('act')">
<div v-bind:style="{ backgroundColor: obj.check? 'red': 'blue' }">
</div>
</div>
Easiest way to achieve this, store the object into another data prop in the child component.
child component
data() => {
newObjectContainer: null
},
onMounted(){
this.newObjectContainer = this.obj
},
methods: {
act(){
// you don't need to take any param. because you are not using it.
newObjectContainer.check = !newObjectContainer.check
}
}
watch: {
obj(val){
// updated if there is any changes
newObjectContainer = val
}
}
And if you really want to update the parent component's computed data. then don't use the computed, use the reactive data prop.
child component:
this time you don't need watcher in the child. you directly emit the object from the method
methods: {
act(){
newObjectContainer.check = !newObjectContainer.check
this.emits("update:modelValue", nextObjectContainer)
}
}
parent component:
data() => {
yourDynamicData: [],
},
onMounted(){
this.yourDynamicData = setAnArray()
},
methods(){
setAnArray(){
let used = [];
for (let i = 0; i < 5; i++) {
used.push({index: i, check: true});
}
return used;
}
}
okay above you created a reactive data property. Now you need the update it if there is a change in the child component,
in the parent first you need object prop so, you can update that.
<test-block v-for="(obj, index) in arrayMake" v-model="updatedObject" :obj="obj"></card-block>
data() => {
yourDynamicData: [],
updatedObject: {}
},
watch:{
updatedObject(val){
const idx = val.index
yourDynamicData[idx] = val
}
}

Error in callback for watcher “get_settings”: “TypeError: Cannot read property ‘general’ of undefined”

Please help me out, how to handle this error i cant seem to handle this out as i am new to vue.
what im doing is getting data from server in store vuex with action. Now in component im accessing that data with getter in computed property and trying to watch that property but on component mount i get that error in console but functionality works fine.
data: function() {
return {
settings_flags :{
general: 0,
privacy: 0,
layouts: 0,
message: 0
}
}
}
1: mounting
mounted() {
let self = this;
self.userid = this.getUserId();
this.$store.dispatch('getGallerySettings',self.req);
self.initial_settings();
}
2: computed
computed: {
get_settings() {
return this.$store.getters.getGallerySettings;
}
}
3: watch
watch: {
'get_settings': {
deep: true,
handler() {
let self =this;
if (this.$_.isMatch(self.get_settings.gallery_settings.general,self.initialSettings.gallery_settings.general) == false) {
self.settings_flags.general = 1;
} else {
self.settings_flags.general = 0;
}
}
}
}
It seems to me that your watcher is looking for a property 'general' that is a child of gallery_settings.
get_settings.gallery_settings.general
In the meantime in data you have a property general that is a child of 'settings_flags'. Those two don't line up. So make sure that either your watcher is looking for something that exists when the component starts up, or tell your watcher to only start watching ' get_settings.gallery_settings.general' when 'get_settings.gallery_settings' actually exists.
if (get_settings.gallery_settings) { do something } #pseudocode
I'm not sure that's your problem, but it might be.

Accessing Vuex state object property from component

I'm trying to change value of a state object property from a component but I can't find the proper way to do it
Here is my state:
state: {
articles: [ {
title: "Lorem ipsum",
position: 7
}, {
title: "Lorem ipsum",
position: 8
}
]
}
In the computed property of the component :
return this.$store.state.articles.filter((article) => {
return (article.title).match(this.search); /// just a search field, don't mind it
});
I would like to change every article.position under 10 to article.position = +'0'article.position.
I tried :
let articlesList = this.$store.state.articles
if(articlesList.position < 10) {
articlesList.position = +'0'articlesList.position
}
I know this is not the good way to do it but this is all I have. Any tips? :)
You should put all Vuex state change code into the Vuex store itself, via a mutation or an action. Mutations are for synchronous data changes that will update all reactive components. Actions are for asynchronous events, such as persisting state changes to the server.
So, when the change occurs, it should call a method on your component, which should in turn call the appropriate mutation or action on the Vuex store.
You don't give your full code, but here is a hypothetical example:
(Edited to reflect question about accessing data from within the mutation method.)
Template:
<input type="text" name="position" :value="article.position" #input="updatePosition">
Method on Component:
methods: {
updatePosition(e) {
this.$store.commit('changeArticleOrder', article.position, e.target.value)
}
}
Mutation on Vuex Store:
mutations: {
changeArticleOrder(state, oldPosition, newPosition) {
for (var i = 0; i < 10; i++) {
state.articles[i].position = i + 1;
/* Or do whatever it is you want to do with your positioning. */
}
}

Vue.js 2: action upon state variable change

I am using a simple state manager (NOT vuex) as detailed in the official docs. Simplified, it looks like this:
export const stateholder = {
state: {
teams: [{id: 1, name:'Dallas Cowboys'}, {id: 2, name:'Chicago Bears'}, {id: 3, name:'Philadelphia Eagles'}, {id:4, name:'L.A. Rams'}],
selectedTeam: 2,
players: []
}
getPlayerList: async function() {
await axios.get(`http://www.someapi.com/api/teams/${selectedTeam}/players`)
.then((response) => {
this.state.players = response.data;
})
}
}
How can I (reactively, not via the onChange event of an HTML element) ensure players gets updated (via getPlayerList) every time the selectedTeam changes?
Any examples of simple state that goes a little further than the official docs? Thank you.
Internally, Vue uses Object.defineProperty to convert properties to getter/setter pairs to make them reactive. This is mentioned in the docs at https://v2.vuejs.org/v2/guide/reactivity.html#How-Changes-Are-Tracked:
When you pass a plain JavaScript object to a Vue instance as its data
option, Vue will walk through all of its properties and convert them
to getter/setters using Object.defineProperty.
You can see how this is set up in the Vue source code here: https://github.com/vuejs/vue/blob/79cabadeace0e01fb63aa9f220f41193c0ca93af/src/core/observer/index.js#L134.
You could do the same to trigger getPlayerList when selectedTeam changes:
function defineReactive(obj, key) {
let val = obj[key]
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
return val;
},
set: function reactiveSetter(newVal) {
val = newVal;
stateholder.getPlayerList();
}
})
}
defineReactive(stateholder.state, 'selectedTeam');
Or you could set it up implicitly using an internal property:
const stateholder = {
state: {
teams: [/* ... */],
_selectedTeam: 2,
get selectedTeam() {
return this._selectedTeam;
},
set selectedTeam(val) {
this._selectedTeam = val;
stateholder.getPlayerList();
},
players: []
},
getPlayerList: async function() {
/* ... */
},
};
Your question is also similar to Call a function when a property gets set on an object, and you may find some more information there.
You could use v-on:change or #change for short to trigger getPlayerList.
Here a fiddle, simulating the request with setTimeout.

Can't get data of computed state from store - Vue

I'm learning Vue and have been struggling to get the data from a computed property. I am retrieving comments from the store and them processing through a function called chunkify() however I'm getting the following error.
Despite the comments being computed correctly.
What am I doing wrong here? Any help would be greatly appreciated.
Home.vue
export default {
name: 'Home',
computed: {
comments() {
return this.$store.state.comments
},
},
methods: {
init() {
const comments = this.chunkify(this.comments, 3);
comments[0] = this.chunkify(comments[0], 3);
comments[1] = this.chunkify(comments[1], 3);
comments[2] = this.chunkify(comments[2], 3);
console.log(comments)
},
chunkify(a, n) {
if (n < 2)
return [a];
const len = a.length;
const out = [];
let i = 0;
let size;
if (len % n === 0) {
size = Math.floor(len / n);
while (i < len) {
out.push(a.slice(i, i += size));
}
} else {
while (i < len) {
size = Math.ceil((len - i) / n--);
out.push(a.slice(i, i += size));
}
}
return out;
},
},
mounted() {
this.init()
}
}
Like I wrote in the comments, the OPs problem is that he's accessing a store property that is not available (probably waiting on an AJAX request to come in) when the component is mounted.
Instead of eagerly assuming the data is present when the component is mounted, I suggested that the store property be watched and this.init() called when the propery is loaded.
However, I think this may not be the right approach, since the watch method will be called every time the property changes, which is not semantic for the case of doing prep work on data. I can suggest two solutions that I think are more elegant.
1. Trigger an event when the data is loaded
It's easy to set up a global messaging bus in Vue (see, for example, this post).
Assuming that the property is being loaded in a Vuex action,the flow would be similar to:
{
...
actions: {
async comments() {
try {
await loadComments()
EventBus.trigger("comments:load:success")
} catch (e) {
EventBus.trigger("comments:load:error", e)
}
}
}
...
}
You can gripe a bit about reactivity and events going agains the reactive philosophy. But this may be an example of a case where events are just more semantic.
2. The reactive approach
I try to keep computation outside of my views. Instead of defining chunkify inside your component, you can instead tie that in to your store.
So, say that I have a JavaScrip module called store that exports the Vuex store. I would define chunkify as a named function in that module
function chunkify (a, n) {
...
}
(This can be defined at the bottom of the JS module, for readability, thanks to function hoisting.)
Then, in your store definition,
const store = new Vuex.Store({
state: { ... },
...
getters: {
chunkedComments (state) {
return function (chunks) {
if (state.comments)
return chunkify(state.comments, chunks);
return state.comments
}
}
}
...
})
In your component, the computed prop would now be
computed: {
comments() {
return this.$store.getters.chunkedComments(3);
},
}
Then the update cascase will flow from the getter, which will update when comments are retrieved, which will update the component's computed prop, which will update the ui.
Use getters, merge chuckify and init function inside the getter.And for computed comment function will return this.$store.getters.YOURFUNC (merge of chuckify and init function). do not add anything inside mounted.