Group transition with dynamic list as a whole - vue.js

I have a data_sets. I need to take out a certain dataset (data_source in the snipped), filter it (itemList in the snippet) and display it, with any simple group transition, preferably list transition in Vue's official guide
This is a snippet that I created on https://sfc.vuejs.org/ to play around.
<template>
<div>
<ul>
<transition-group name="list" mode="out-in">
<li v-for="item in itemList" :key="item.id">
{{item.content}}
</li>
</transition-group>
</ul>
</div>
<button #click="changeDatasource(0)">
Dataset 1
</button>
<button #click="changeDatasource(1)">
Dataset 2
</button>
<button #click="changeDatasource(2)">
Dataset 3
</button>
</template>
<script>
export default {
data() {
return {
data_sets: [
[{
id: 1,
content: "Frog",
active: 1,
},
{
id: 2,
content: "Elephant",
active: 0,
},
{
id: 3,
content: "Racoon",
active: 1,
},
{
id: 8,
content: "Cheetah",
active: 1,
},
],
[{
id: 4,
content: "Rooster",
active: 1,
},
{
id: 5,
content: "Cow",
active: 1,
},
{
id: 6,
content: "Cat",
active: 1,
},
{
id: 7,
content: "Dog",
active: 0,
},
],
[{
id: 10,
content: "Lion",
active: 1,
},
{
id: 11,
content: "Shark",
active: 1,
},
{
id: 12,
content: "Bee",
active: 0,
},
{
id: 13,
content: "Cockroaches",
active: 0,
},
],
],
data_source: [],
}
},
computed: {
itemList: {
get() {
return this.data_source.filter((item) => item.active)
},
set(newValue) {
//this.itemList = newValue; <--
}
}
},
methods: {
changeDatasource: function(id) {
//this.itemList = [] <--
this.data_source = this.data_sets[id];
}
}
}
</script>
<style scoped>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>
When clicking on buttons to choose a data set, you can see the transition is not okay: old items still present when the new ones come in. The expected order should be: old items disappears, then new items come in.
It's very likely because itemList is replaced as a whole at a time. So I've tried to:
Empty itemList = [],
Continue to make the changes. (the commented part in the snippet)
RangeError: Maximum call stack size exceeded
Guess it bounds to some kind of infinite loop, at this point I'm completely pointless.
I've also messed around with transition mode, it isn't better.
My question is there any way to apply transition to list as a whole like this?

Solved it by working around with setTimeout and setInterval and changed from computed propery to watch.
Content shifting is there but a little CSS and JavaScript manipulation will fix it. Not the best solution but by now that's what I can think of.
Result snippet
<template>
<div>
<ul>
<transition-group name="list">
<li v-for="item in itemList" :key="item.id">
{{item.content}}
</li>
</transition-group>
</ul>
</div>
<button #click="changeDatasource(0)">
Dataset 1
</button>
<button #click="changeDatasource(1)">
Dataset 2
</button>
<button #click="changeDatasource(2)">
Dataset 3
</button>
</template>
<script>
export default {
data() {
return {
data_sets: [
[{
id: 1,
content: "Frog",
active: 1,
},
{
id: 2,
content: "Elephant",
active: 0,
},
{
id: 3,
content: "Racoon",
active: 1,
},
{
id: 8,
content: "Cheetah",
active: 1,
},
],
[{
id: 4,
content: "Rooster",
active: 1,
},
{
id: 5,
content: "Cow",
active: 1,
},
{
id: 6,
content: "Cat",
active: 1,
},
{
id: 7,
content: "Dog",
active: 0,
},
],
[{
id: 10,
content: "Lion",
active: 1,
},
{
id: 11,
content: "Shark",
active: 1,
},
{
id: 12,
content: "Bee",
active: 0,
},
{
id: 13,
content: "Cockroaches",
active: 0,
},
],
],
data_source: [],
itemList: [],
}
},
methods: {
changeDatasource: function(id) {
//this.itemList = [] <--
this.data_source = this.data_sets[id];
}
},
watch: {
data_source: function(newValue, oldValue) {
let initialLength = this.itemList.length;
if (initialLength > 0) {
this.itemList = [];
}
let dataLength = newValue.length;
let interval = 200;
let i = 0;
let timer = setInterval(() => {
setTimeout(() => {
if (typeof newValue[i - 1] != 'undefined') {
this.itemList.push(newValue[i - 1])
}
}, interval)
if (i === dataLength - 1) {
clearInterval(timer);
}
i++;
}, interval)
}
}
}
</script>
<style scoped>
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(30px);
}
</style>

Related

Hey! I want to implement some anchor links in vue with no library used

Here is my code below. I know there is some specific library like vue-scrollto but I want to resolve this task with no library.
I want to add isViewed true only in that certain link. But the problem is that when I clicked on links each object of that link has isViewed true instead of only one.I want it changes dynamically.
I would appreciate any help.
<template>
<div class="anchors">
<ul class="list">
<li class="item" v-for="(link, index) in anchorLinks" :key="link.id">
<a #click="goToSection(link, link.sectionId, link.id, index)" class="anchor__item-link"></a>
</li>
</ul>
</div>
</template>
export default {
name: "AnchorLinks",
data(){
return{
anchorLinks: [
{
id: 1,
sectionId: "work",
},
{
id: 2,
sectionId: "service",
},
{
id: 3,
sectionId: "partner",
},
{
id: 4,
sectionId: "partner",
},
{
id: 5,
sectionId: "partner",
},
{
id: 6,
sectionId: "partner",
},
{
id: 6,
sectionId: "partner",
}
]
}
},
methods: {
goToSection(link, sectionId, id, index){
const element = document.getElementById(sectionId);
if (element) {
link.isViewed = true;
window.scrollTo({
top: element.offsetTop,
behavior: 'smooth'
});
console.log(id)
console.log(index);
console.log(this.anchorLinks)
}
}
}
}
You should ensure that each anchorLink initially has an isViewed property,
The documentation states:
Vue cannot detect property addition or deletion
<template>
<div class="anchors">
<ul class="list">
<li class="item" v-for="(link, index) in anchorLinks" :key="link.id">
<a
#click="goToSection(link, link.sectionId, link.id, index)"
class="anchor__item-link"
></a>
</li>
</ul>
</div>
</template>
export default {
name: 'AnchorLinks',
data() {
return {
anchorLinks: [
{
id: 1,
sectionId: 'work',
isViewed: false,
},
{
id: 2,
sectionId: 'service',
isViewed: false,
},
{
id: 3,
sectionId: 'partner',
isViewed: false,
},
{
id: 4,
sectionId: 'partner',
isViewed: false,
},
{
id: 5,
sectionId: 'partner',
isViewed: false,
},
{
id: 6,
sectionId: 'partner',
isViewed: false,
},
{
id: 6,
sectionId: 'partner',
isViewed: false,
},
],
};
},
methods: {
goToSection(link, sectionId, id, index) {
const element = document.getElementById(sectionId);
if (element) {
link.isViewed = true;
window.scrollTo({
top: element.offsetTop,
behavior: 'smooth',
});
console.log(id);
console.log(index);
console.log(this.anchorLinks);
}
},
},
};
Without reinventing the wheel here, you basically need to remove the previous link.isViewed property each time you click a link.
One straightforward way to do this is to store another variable containing the active link, that you can update each time you click a new link. See simple example below (ignoring excess / cruft).
As an aside, I would consider not updating the isViewed variable at all and just storing the active link (similar to below) for reference wherever you need it.
<ul>
<li v-for="link in anchorLinks">
<a #click="goToSection(link)"></a>
</li>
</ul>
export default {
name: "AnchorLinks",
data() {
return {
activeLink: null,
// you're using an array here, lean on the index
anchorLinks: [
{ id: "work" },
{ id: "service" },
{ id: "partner" },
],
};
},
methods: {
goToSection(link) {
// remove the previous `isViewed` property
if (this.activeLink) {
delete this.activeLink.isViewed;
}
// update the active link
this.activeLink = link;
const el = document.getElementById(link.id);
if (el) {
// now this will be the only link with `isViewed = true`
link.isViewed = true;
window.scrollTo({
top: el.offsetTop,
behavior: 'smooth'
});
}
}
}
}
From what I can tell and from what you have given, I think you need to set the ids of each anchor link.
<a
:id="link.sectionId"
#click="goToSection(link, link.sectionId, link.id, index)"
class="anchor__item-link"
></a>

Data is not display in correct order after update

I try to display an array of object sort by date.
When it's mount there is no problem but when i try to add new data, it is not shown in the correct order. I'm sorry but I cannot be more clear ...
Here is my code :
<template>
<div class="container">
<div class="row">
<div class="col" style="position: relative; height: 50vh">
<canvas id="graphique"></canvas>
</div>
<div>
<h1>Finds</h1>
<div v-for="(find, index) in finds" v-bind:key="index">
<input v-model="find.date" />
<input v-model="find.value" />
<input v-model="find.label" />
</div>
<input v-model="dateToAdd" />
<input v-model="valueToAdd" />
<input v-model="labelToAdd" />
<button #click="addFind">Add cost</button>
<p v-if="Chart != null">{{ Chart.data.datasets[0].data }}</p>
</div>
</div>
</div>
</template>
<script>
import Chart from "chart.js/auto";
export default {
name: "PlanetChart",
data() {
return {
Chart: null,
valueToAdd: 20,
labelToAdd: "A",
dateToAdd: "12/01/1900",
finds: [
{ date: "12/01/2020", label: "1", value: 12 },
{ date: "12/01/2021", label: "2", value: 2 },
{ date: "12/01/2000", label: "0", value: 1 },
{ date: "12/01/2023", label: "3", value: 12 },
],
};
},
mounted() {
const ctx = document.getElementById("graphique");
this.finds.sort(function (a, b) {
return new Date(a.date) - new Date(b.date);
});
this.Chart = new Chart(ctx, {
type: "line",
data: {
datasets: [
{
label: "My dataset",
fill: false,
lineTension: 0.1,
backgroundColor: "rgba(75,192,192,0.4)",
borderColor: "rgba(75,192,192,1)",
borderCapStyle: "butt",
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: "miter",
pointBorderColor: "rgba(75,192,192,1)",
pointBackgroundColor: "#fff",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(75,192,192,1)",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointHoverBorderWidth: 2,
pointRadius: 5,
pointHitRadius: 10,
data: this.finds,
},
],
},
options: {
parsing: {
xAxisKey: "date",
yAxisKey: "value",
},
responsive: true,
maintainAspectRatio: false,
},
});
},
methods: {
addFind: function () {
if (this.valueToAdd > 0 && this.labelToAdd != "") {
this.finds.push({
date: this.dateToAdd,
value: this.valueToAdd,
label: this.labelToAdd,
});
this.finds.sort(function (a, b) {
return new Date(a.date) - new Date(b.date);
});
this.Chart.data.datasets[0].data = this.finds;
console.log(this.Chart.data.datasets[0].data);
this.Chart.update();
this.valueToAdd = 0;
this.dateToAdd = "";
this.labelToAdd = "";
}
},
},
};
</script>
Here the result I have :
The point at the extrem right must be the first because his date is before the others points.
So it seems to be a rendering probleme and not a sort probleme.
If you need a js.fiddle let me know.
Thanks for your help !
EDIT : The only solution found is to create a function to create the graph and inside the addFind function do this :
this.Chart.destroy();
this.Chart = this.createChart(this.finds)
This is a working example with you data.
Here is the template code, I used a computed property to sort the array of data instead of sorting the actual data.
Please review the dependencies of the Chart.js package, as time series chart require external date adapters as mentioned here.
Also, there seems to be a Chart.js wrapper for Vue, perhaps you should try it.
<template>
<div class="container">
<div class="row">
<div class="col" style="position: relative; height: 50vh">
<canvas id="graphique"></canvas>
</div>
<div>
<h1>Finds</h1>
<div v-for="(find, index) in finds" v-bind:key="index">
<input v-model="find.date" />
<input v-model="find.value" />
<input v-model="find.label" />
</div>
<input v-model="dateToAdd" />
<input v-model="valueToAdd" />
<input v-model="labelToAdd" />
<button #click="addFind">Add cost</button>
<p v-if="Chart">{{ Chart.data }}</p>
</div>
</div>
</div>
</template>
<script>
import Chart from "chart.js/auto";
import "chartjs-adapter-moment";
export default {
name: "PlanetChart",
data() {
return {
Chart: null,
valueToAdd: 20,
labelToAdd: "A",
dateToAdd: "12/01/1900",
finds: [
{ date: "12/01/2020", label: "1", value: 12 },
{ date: "12/01/2021", label: "2", value: 2 },
{ date: "12/01/2000", label: "0", value: 1 },
{ date: "12/01/2023", label: "3", value: 12 },
],
};
},
mounted() {
const ctx = document.getElementById("graphique");
this.Chart = new Chart(ctx, {
type: "line",
data: {
datasets: [
{
label: "US Dates",
backgroundColor: "#fff",
borderWidth: 1,
pointBorderWidth: 1,
borderColor: "#f00",
data: this.transformedData,
fill: false,
borderColor: "red",
},
],
},
options: {
scales: {
x: {
type: "time",
time: {
unit: "month",
},
},
},
},
});
},
computed: {
transformedData() {
const d = this.finds.map((i) => {
return { x: new Date(i.date), y: i.value };
});
d.sort(function (a, b) {
return a.x - b.x;
});
return d;
},
},
methods: {
addFind: function () {
if (this.valueToAdd > 0 && this.labelToAdd !== "") {
this.finds.push({
date: this.dateToAdd,
value: this.valueToAdd,
label: this.labelToAdd,
});
this.finds.sort(function (a, b) {
return new Date(a.date) - new Date(b.date);
});
this.Chart.data.datasets[0].data = this.transformedData;
console.log(this.Chart.data.datasets[0].data);
this.Chart.update();
this.valueToAdd = 0;
this.dateToAdd = "";
this.labelToAdd = "";
}
},
},
};
</script>
So without using a computed method is it possible to do it simply by using the
import "chartjs-adapter-moment";
as Juand David mentioned. And don't forget to convert the string in Date.
Here is my solution :
<template>
<div class="container">
<div class="row">
<div class="col">
<canvas id="graphique"></canvas>
</div>
</div>
<div class="row">
<div class="col-xxs">
<h1>Mes catégories</h1>
<div v-for="(cat, index) in category" v-bind:key="index">
{{ cat.label }}
</div>
</div>
<div class="col">
<h1>Mes dépenses</h1>
<div v-for="(find, index) in finds" v-bind:key="index">
<input v-model="find.date" readonly />
<input v-model="find.value" readonly />
<input v-model="find.label" readonly />
</div>
<div>
<input v-model="dateToAdd" />
<input v-model="valueToAdd" />
<select v-model="labelToAdd">
<option
v-for="(cat, index) in category"
:value="cat.label"
v-bind:key="index"
>
{{ cat.label }}
</option>
</select>
<button #click="addFind">Add cost</button>
</div>
</div>
</div>
</div>
</template>
<script>
import Chart from "chart.js/auto";
import "chartjs-adapter-moment";
export default {
name: "PlanetChart",
data() {
return {
ctx: null,
Chart: null,
valueToAdd: null,
labelToAdd: null,
dateToAdd: null,
category: [
{ id: "salary", label: "Salaire" },
{ id: "fun", label: "Loisir" },
],
finds: [
{ date: new Date("12/01/2020"), label: "Dépense 1", value: 12 },
{ date: new Date("12/01/2021"), label: "Salariée", value: 2 },
{ date: new Date("12/01/2000"), label: "Ciné", value: 1 },
{ date: new Date("12/01/2023"), label: "Restaurant", value: 12 },
],
};
},
mounted() {
this.ctx = document.getElementById("graphique");
this.finds.sort(function (a, b) {
return a.date - b.date;
});
this.Chart = this.createChart(this.finds);
},
methods: {
createChart: function (dataToAdd) {
var toRet = new Chart(this.ctx, {
type: "line",
data: {
datasets: [
{
label: "My dataset",
fill: false,
lineTension: 0.1,
backgroundColor: "rgba(75,192,192,0.4)",
borderColor: "rgba(75,192,192,1)",
borderCapStyle: "butt",
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: "miter",
pointBorderColor: "rgba(75,192,192,1)",
pointBackgroundColor: "#fff",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(75,192,192,1)",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointHoverBorderWidth: 2,
pointRadius: 5,
pointHitRadius: 10,
data: dataToAdd,
},
],
},
options: {
parsing: {
xAxisKey: "date",
yAxisKey: "value",
},
scales: {
x: {
type: "time",
time: {
unit: "month",
},
},
},
responsive: true,
maintainAspectRatio: false,
},
});
return toRet;
},
addCategory: function () {},
addFind: function () {
if (this.valueToAdd !== null && this.labelToAdd !== null) {
this.finds.push( { date: new Date("12/01/1990"), label: "Dépense nouvelle", value: 5 });
this.finds.sort(function (a, b) {
return new Date(a.date) - new Date(b.date);
});
this.Chart.data.datasets[0].data = this.finds;
// console.log(this.Chart.data.datasets);
this.Chart.update();
// this.Chart = this.createChart(this.finds);
this.valueToAdd = 0;
this.dateToAdd = "";
this.labelToAdd = "";
}
},
},
};
</script>

Flickering of charts and getcontext error with chartjs in the context of Vuejs

Hello i am trying to display different charts using the chartjs by calling the API. Below code shows how i have formatted the chart.vue
Chart.vue:
<template>
<div class="chart-container" style="position: relative; height: 40vh; width:100%;">
<slot name="test1"></slot>
<slot name="test2"></slot>
</div>
</template>
<script>
export default {
name: 'charts',
data () {
return {
date: [],
challenge: [],
data: []
}
},
mounted () {
this.check(8, 'chart_8')
this.check(7, 'chart_7')
},
methods: {
check (id, name) {
this.$http.get(`/api_chart/${ id }/full`)
.then((response) => {
this.date = response.data.date
this.challenge = response.data.challenge
this.data = this.date.map((date, index) => ({
x: new Date(date * 1000),
y: this.challenge[index]
}))
const ctx = document.getElementById([name]).getContext('2d')
let myChart = new Chart(ctx, {
type: 'line',
data: {
datasets: [
{
label: 'Challenge',
data: this.data,
borderColor: ' #EA5455',
}
]
},
options: {
lineTension: 0,
maintainAspectRatio: false,
scales: {
yAxes: [
{
scaleLabel: {
display: false
},
ticks: {
beginAtZero: true,
callback (value) {
return `${value}%`
}
}
}
],
xAxes: [
{
type: 'time',
time: {
unit: 'month'
},
scaleLabel: {
display: true,
}
}
]
}
}
})
})
}
}
}
</script>
App.vue:
<template>
<div class="In order to display chart1">
<chart-display> <canvas slot="test1" id="chart_7" ></canvas> </chart-display>
</div>
<div class="In order to display chart1">
<chart-display> <canvas slot="test2" id="chart_8" ></canvas> </chart-display>
</div>
</template>
<script>
import chart-display from './Chart.vue'
export default {
component: {chart-display}
}
</script>
As you can see i have shared my Chart.vue and App.vue, i am able to see my chart in the browser, but whenever i run the code or refresh the page, the charts flickers and stops. And then in my console i get below error:
Please someone help me to get rid of this issue, and please tell me if any changes i should do in my code to solve it. Please send me the modification code.
As I wrote in my comment, the charts are rendered twice. This causes flickering.
// every time you use <chart-display>, 2 charts are rendered, this means chart 1 renders
// itself and chart 2, char 2 renders itself and chart 1, this is a bad pattern in Vue in general
mounted() {
this.check(8, "chart_8");
this.check(7, "chart_7");
}
Make the following changes:
ChartDisplay.vue
<template>
<div
class="chart-container"
style="position: relative; height: 40vh; width: 100%"
>
<canvas ref="chart_7"></canvas>
<canvas ref="chart_8"></canvas>
</div>
</template>
<script>
import Chart from "chart.js";
export default {
name: "ChartDisplay",
data() {
return {
date: [],
challenge: [],
data: [],
// save charts in an array
charts: [],
// charts options
options: {
lineTension: 0,
maintainAspectRatio: false,
scales: {
yAxes: [
{
scaleLabel: {
display: false,
},
ticks: {
beginAtZero: true,
callback(value) {
return `${value}%`;
},
},
},
],
xAxes: [
{
type: "time",
time: {
unit: "month",
},
scaleLabel: {
display: true,
},
},
],
},
},
};
},
mounted() {
this.render(7, this.$refs.chart_7);
this.render(8, this.$refs.chart_8);
},
methods: {
render(id, ctx) {
this.fetchData(id).then((response) => {
let data = response.date.map((date, index) => ({
x: new Date(date * 1000),
y: response.challenge[index],
}));
this.charts.push(
new Chart(ctx, {
type: "line",
data: {
datasets: [
{
label: "Challenge",
data: data,
borderColor: " #EA5455",
},
],
},
options: this.options,
})
);
});
},
fetchData(id) {
return this.$http.get(`/api_chart/${ id }/full`);
},
},
beforeDestroy() {
this.charts.forEach((chart) => chart.destroy());
},
};
</script>
<style >
[v-cloak] {
display: none;
}
</style>
App.vue
<template>
<div>
<div class="In order to display chart1">
<chart-display/>
</div>
</div>
</template>
<script>
import ChartDisplay from "./ChartDisplay.vue";
export default {
components: { ChartDisplay },
};
</script>
See it on sandbox
I found several errors on your code. I fix them in Sandbox
For Chat.vue :
I rename the file as ChartDisplay.vue as similar as the component name
import chart.js package for using Chart() function
I use a demo API
<template>
<div
class="chart-container"
style="position: relative; height: 40vh; width: 100%"
>
<slot name="test1"></slot>
<slot name="test2"></slot>
</div>
</template>
<script>
import Chart from "chart.js";
export default {
name: "ChartDisplay",
data() {
return {
date: [],
challenge: [],
data: [],
};
},
mounted() {
this.check(8, "chart_8");
this.check(7, "chart_7");
},
methods: {
check(id, name) {
fetch(
"https://api.wirespec.dev/wirespec/stackoverflow/fetchchartdataforvuejs"
)
.then((response) => response.json())
.then((response) => {
this.date = response.date;
this.challenge = response.challenge;
this.data = this.date.map((date, index) => ({
x: new Date(date * 1000),
y: this.challenge[index],
}));
const ctx = document.getElementById([name]).getContext("2d");
new Chart(ctx, {
type: "line",
data: {
datasets: [{
label: "Challenge",
data: this.data,
borderColor: " #EA5455",
}, ],
},
options: {
lineTension: 0,
maintainAspectRatio: false,
scales: {
yAxes: [{
scaleLabel: {
display: false,
},
ticks: {
beginAtZero: true,
callback(value) {
return `${value}%`;
},
},
}, ],
xAxes: [{
type: "time",
time: {
unit: "month",
},
scaleLabel: {
display: true,
},
}, ],
},
},
});
});
},
},
};
</script>
For App.vue
Your import should not carry any hyphen.
component should be components
render the component once to avoid flikering
<template>
<div>
<div class="In order to display chart1">
<chart-display>
<canvas slot="test1" id="chart_7"></canvas>
<canvas slot="test2" id="chart_8"></canvas>
</chart-display>
</div>
</div>
</template>
<script>
import ChartDisplay from "./ChartDisplay.vue";
export default {
components: {
ChartDisplay
},
};
</script>

Vue chart js Dynamic Radar Chart

Just wondering if someone can point me in the right direction here when it comes to dynamic data from vuex and applying it to radar charts in vue-chart.js.
Below is my vue file.
<template>
<div>
<h1>Chart Demo</h1>
<v-card class="question card__text pa-sm-5 py-5 px-2">
<div>
<radar-chart :data="radarChartData" :options="radarChartOptions" :height="400" />
</div>
</v-card>
<v-container fluid>
<v-row dense>
<v-col
v-for="card in getCards"
:key="card.id"
cols="12"
:sm="card.flex"
xs="1"
>
<v-card>
<v-card-title class="title" v-text="card.title">
</v-card-title>
<p class="pa-4">Importance: {{ card.importance }} <br />
Effectiveness: {{ card.effectiveness }} </p>
</v-card>
</v-col>
</v-row>
</v-container>
</div>
</template>
<script>
import { mapGetters } from "vuex"
import RadarChart from "~/components/RadarChart"
export default {
layout: "tabs",
components: {
RadarChart,
},
data() {
return {
radarChartOptions: {
responsive: true,
scales: {
yAxes: [
{
ticks: {
beginAtZero: true,
display: false,
min: 0,
max: 10,
},
gridLines: {
display: false,
},
scaleLabel: {
display: false,
}
}
],
xAxes: [
{
gridLines: {
display: false,
},
}
]
}
},
/* radarChartData: {
labels: [
"Personal Growth",
"Work",
"Leisure",
"Health",
"Community",
"Family",
"Parenting",
"Intimate",
"Social",
"Spirituality",
],
datasets: [
{
label: "Importance",
backgroundColor: 'rgb(54, 162, 235, 0.75)',
data: [9, 8, 8, 9, 4, 6, 2, 8, 9, 5],
},
{
label: "Effectiveness",
backgroundColor: 'rgb(75, 192, 192, 0.75)',
data: [7, 7, 7, 6, 3, 5, 10, 6, 7, 4],
},
]
} */
}
},
computed: {
...mapGetters("cards", ["getCards"]),
//...mapState(["getCards"]),
barChartData() {
return {
labels: ['Importance', 'Effectiveness'],
datasets: [
{
// backgroundColor: ["red", "orange", "yellow"],
backgroundColor: [chartColors.blue, chartColors.green],
data: [this.importance, this.effectiveness]
}
]
}
},
radarChartData() {
return {
labels: [
"Personal Growth",
"Work",
"Leisure",
"Health",
"Community",
"Family",
"Parenting",
"Intimate",
"Social",
"Spirituality",
],
datasets: [
{
label: "Importance",
backgroundColor: 'rgb(54, 162, 235, 0.75)',
data: [9, 8, 8, 9, 4, 6, 2, 8, 9, 5],
},
{
label: "Effectiveness",
backgroundColor: 'rgb(75, 192, 192, 0.75)',
data: [7, 7, 7, 6, 3, 5, 10, 6, 7, 4],
},
]
}
},
card() {
return this.getCards.find((el) => el.id === this.id)
},
effectiveness: {
get() {
return this.card.effectiveness
},
set(effectiveness) {
this.$store.commit("cards/updateEffectiveness", { card: this.card, effectiveness})
},
},
importance: {
get() {
return this.card.importance
},
set(importance) {
this.$store.commit("cards/updateImportance", { card: this.card, importance})
},
},
keywords: {
get() {
return this.card.keywords
},
set(keywords) {
this.$store.commit("cards/updateKeywords", { card: this.card, keywords})
}
}
},
/* computed: {
radarChartData() {
return {
labels: [
"Personal Growth",
"Work",
"Leisure",
"Health",
"Community",
"Family",
"Parenting",
"Intimate Relationships",
"Social",
"Spirituality",
],
datasets: [
{
// backgroundColor: ["red", "orange", "yellow"],
data: [9, 8, 8, 9, 4, 6, 2, 8, 9, 5],
}
]
}
},
}*/
}
</script>
below the radar chart is an array of cards that dynamically display the information that I also want to use for the radar chart. I can get the info to show for the cards, but not the radar chart.
I am guessing I am missing something really obvious, and I know I have to somehow reference the card.id, but I am not sure how to do that. I have tried a :key binding on the radar-chart tag but that throws an error of:
‘card’ is not defined
below is the array of store data in my store js file:
import createPersistedState from "vuex-persistedstate"
export const state = () => ({
cards: [
{
title: “Personal Growth”,
src: require("~/assets/images/values/growth.svg"),
flex: 6,
id: “1”,
keywords: [],
importance: “”,
effectiveness: “”,
},
{
title: “Leisure”,
src: require("~/assets/images/customize.svg"),
flex: 6,
id: “2”,
keywords: [],
importance: “”,
effectiveness: “”,
},
{
title: “Sprituality”,
src: require("~/assets/images/values/spirituality.svg"),
flex: 6,
id: “3”,
keywords: [],
importance: “”,
effectiveness: “”,
},
{
title: “Work”,
src: require("~/assets/images/values/work.svg"),
flex: 6,
id: “4”,
keywords: [],
importance: “”,
effectiveness: “”,
},
{
title: “Health”,
src: require("~/assets/images/values/health.svg"),
flex: 6,
id: “5”,
keywords: [],
importance: “”,
effectiveness: “”,
},
{
title: “Community \n& Environment”,
src: require("~/assets/images/values/community.svg"),
flex: 6,
id: “6”,
keywords: [],
importance: “”,
effectiveness: “”,
},
{
title: “Family Relationships”,
src: require("~/assets/images/values/family.svg"),
flex: 6,
id: “7”,
keywords: [],
importance: “”,
effectiveness: “”,
},
{
title: “Social Relationships”,
src: require("~/assets/images/values/social.svg"),
flex: 6,
id: “8”,
keywords: [],
importance: “”,
effectiveness: “”,
},
{
title: “Intimate Relationships”,
src: require("~/assets/images/values/intimate.svg"),
flex: 6,
id: “9”,
keywords: [],
importance: “”,
effectiveness: “”,
},
{
title: “Parenting”,
src: require("~/assets/images/values/parenting.svg"),
flex: 6,
id: “10”,
keywords: [],
importance: “”,
effectiveness: “”,
},
],
})
export const plugins = [createPersistedState()]
export const getters = {
getCards: (state) => {
return state.cards
},
}
export const mutations = {
updateEffectiveness(state, {card, effectiveness}) {
card.effectiveness = effectiveness
},
updateImportance(state, {card, importance}) {
card.importance = importance
},
updateKeywords(state, {card, keywords}) {
card.keywords = keywords
}
}
Any tips, advice are welcome and thanks for any help! :)
Screenshots of what the radar chart looks like with static data are attached.
You can use reactiveProp and reactiveData to make your chart reactive. See Updating Charts for more details.
For reactiveProp, it just creates a prop named chartData and adds a vue watch on this prop then call renderChart() when this prop changed.
Example for RadarChart.vue:
<script>
import { Radar, mixins } from "vue-chartjs";
export default {
extends: Radar,
mixins: [mixins.reactiveProp],
props: ["options"],
mounted() {
this.renderChart(this.chartData, this.options);
}
};
</script>
And then use the component:
<radar-chart :chart-data="radarChartData" :options="radarChartOptions"/>
CodeSandbox Example

Two-Way Binding in Component Scope

Suppose I'm trying to make a simple questionnaire, where the user answers a list of questions.
new Vue(
{
el: "#app",
data:
{
questions:
[
{
id: 1,
name: "What is your favorite color?",
selectedId: 2,
choices:
[
{ id: 1, name: "red" },
{ id: 2, name: "green" },
{ id: 3, name: "blue" },
]
},
...
]
}
});
How do I go about making a question component with two-way binding. That is, if the user swaps their favorite color from green to red, by clicking on the respective input, the selectedId will automatically update. I'm not very clear on how v-model works within a component. Does it only have access to the components data? Also, I don't understand the difference between props/data.
There are lots of ways you can approach this, here's my attempt:
let id = 0;
Vue.component('question', {
template: '#question',
props: ['question'],
data() {
return {
radioGroup: `question-${id++}`,
};
},
methods: {
onChange(choice) {
this.question.selectedId = choice.id;
},
isChoiceSelected(choice) {
return this.question.selectedId === choice.id;
},
},
});
new Vue({
el: '#app',
data: {
questions: [
{
title: 'What is your favorite color?',
selectedId: null,
choices: [
{ id: 1, text: 'Red' },
{ id: 2, text: 'Green' },
{ id: 3, text: 'Blue' },
],
},
{
title: 'What is your favorite food?',
selectedId: null,
choices: [
{ id: 1, text: 'Beans' },
{ id: 2, text: 'Pizza' },
{ id: 3, text: 'Something else' },
],
},
],
},
});
.question {
margin: 20px 0;
}
<script src="https://rawgit.com/yyx990803/vue/master/dist/vue.js"></script>
<div id="app">
<question v-for="question of questions" :question="question"></question>
</div>
<template id="question">
<div class="question">
<div>{{ question.title }}</div>
<div v-for="choice of question.choices">
<input type="radio" :name="radioGroup" :checked="isChoiceSelected(choice)" #change="onChange(choice)"> {{ choice.text }}
</div>
<div>selectedId: {{ question.selectedId }}</div>
</div>
</template>