Vue chart js Dynamic Radar Chart - vue.js

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

Related

apexcharts - Area chart not filling area

I'm trying to make an area chart using apexcharts with nuxt (vue2), but the area is not getting filled, and the options i set in chartOptions for fill are getting used by the line itself instead of the area, as below:
this is my apexchart component:
<client-only>
<apexcharts
type="area"
width="70%"
ref="realtimeChart"
:options="chartOptions"
:series="series">
</apexcharts>
</client-only>
this is my chartOptions:
chartOptions: {
chart: {
toolbar: {
show: true,
tools: {
download: true,
selection: false,
zoom: true,
zoomin: false,
zoomout: false,
pan: false,
reset: false,
},
},
id: "basic-bar",
colors: ["#FFA07A"],
fill: {
type: "gradient",
gradient: {
shadeIntensity: 1,
opacityFrom: 0.7,
opacityTo: 0.9,
stops: [0, 100],
},
},
forecastDataPoints: {
count: 28,
strokeWidth: 4,
dashArray: 5,
},
title: {
text: "doesnt matter",
align: "center",
},
grid: {
borderColor: "#e7e7e7",
row: {
colors: ["#f3f3f3", "transparent"],
opacity: 0.5,
},
},
xaxis: {
type: "date",
tickAmount: 40,
},
yaxis: [
{
title: {
text: "Mt CO2eq/we",
},
forceNiceScale: true,
},
]
},
Can anyone help ?
I was declaring type: "line" in my series. thats all.

Group transition with dynamic list as a whole

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>

Cannot change anything in chart options

I have a bar chart and I would like to change the font color, border width and some other tings, but it doesn't work. It is in my computed property. In the chartOptions I want to change the y axis min and max value but I don't know if it is correct. Can anyone help me?
Also I want to make a horizontal line in this bar chart. It is the "Enemy's Avarage" and now it is a constant. I set the type to line and now I have a dot in my chart.
This is my chart component:
<script>
import { defineComponent } from 'vue'
import { Bar } from 'vue3-chart-v2'
export default defineComponent({
name: 'ChanceChart',
extends: Bar,
props: {
chartData: {
type: Object,
required: true
},
chartOptions: {
type: Object,
required: false,
},
},
mounted () {
this.renderChart(this.chartData, this.chartOptions)
}
})
</script>
And this is my app:
<template>
<div id="chart">
<ChanceChart :chartData="chartData" :chartOptions="chartOptions" />
</div>
</template>
<script>
import Navigation from "../components/Navigation.vue";
import ChanceChart from "../components/ChanceChart.vue";
import PageLoader from "../components/PageLoader.vue";
export default {
components: {
ChanceChart,
},
computed: {
chartOptions() {
return {
options: {
scales: {
y: {
title: {
display: true,
text: 'Value'
},
min: 0,
max: 100,
ticks: {
stepSize: 10,
}
},
},
elements: {
point: {
radius: 0
},
line: {
borderWidth: 2
}
},
plugins: {
legend: {
labels: {
boxWidth: 0,
font: {
fontColor: "#fff",
color: "#fff"
},
},
},
},
}
}
},
chartData() {
return {
datasets: [
{
type: 'bar',
label: "Enemy's Chance",
borderColor: "#1161ed",
borderWidth: 2,
data: this.emnemyCardsFilled,
},
{
type: 'bar',
label: "My Chance",
borderColor: "#f87979",
borderWidth: 2,
data: this.myCardsFilled,
},
{
type: 'line',
label: "Enemy's Avarage",
borderColor: "rgb(238, 255, 0)",
borderWidth: 2,
data: [50],
},
],
}
},
},
You need to remove the options part in the computed chartOptions prop:
chartOptions() {
return {
scales: {
y: {
title: {
display: true,
text: 'Value'
},
min: 0,
max: 100,
ticks: {
stepSize: 10,
}
},
},
elements: {
point: {
radius: 0
},
line: {
borderWidth: 2
}
},
plugins: {
legend: {
labels: {
boxWidth: 0,
font: {
fontColor: "#fff",
color: "#fff"
},
},
},
},
}
},

ECharts - how to inject value from props?

I have the following Chart Component:
<template>
<div class="gauge-chart">
<chart :options="options"></chart>
</div>
</template>
<script>
export default {
props: {
chartValue: { type: Number, required: true },
chartName: { type: String, required: true },
},
data: () => ({
options: {
series: [
{
type: "gauge",
startAngle: 180,
endAngle: 0,
min: 1,
max: 5,
splitNumber: 8,
axisLine: {
lineStyle: {
width: 6,
color: [
[0.25, "#7CFFB2"],
[0.5, "#58D9F9"],
[0.75, "#FDDD60"],
[1, "#FF6E76"],
],
},
},
pointer: {
icon: "arrow",
offsetCenter: [0, "-30%"],
itemStyle: {
color: "auto",
},
},
axisTick: {
length: 12,
lineStyle: {
color: "auto",
width: 1,
},
},
splitLine: {
length: 20,
lineStyle: {
color: "auto",
width: 5,
},
},
title: {
fontSize: 30,
},
data: [
{
value: this.chartValue,
name: this.chartName,
},
],
},
],
},
}),
};
</script>
As you can see I am tryng to inject chartValue and chartName props into options.series.data.value and options.series.data.name respectively.
The values for the properties are coming from
<GaugeChart chartName="Sleep" :chartValue="2" />
At the moment the values are hardcoded, but eventually they will be dynamic.
However it keep throwing the following error:
"TypeError: Cannot read property 'chartName' of undefined"
"TypeError: Cannot read property 'chartValue' of undefined"
I have done a colsole.log of both properties and they come up as "Sleep" and 2. I have also done a typeof on both property names and they both come up as String and Number, respectively.
Could somebody tell me where I am going wrong please?
Many thanks in advance.
You cannot use 'this' operator inside an arrow function so define your data section as a normal function
<script>
export default {
props: {
chartValue: { type: Number, required: true },
chartName: { type: String, required: true },
},
data() {
return {
options: {
series: [
{
type: "gauge",
startAngle: 180,
endAngle: 0,
min: 1,
max: 5,
splitNumber: 8,
axisLine: {
lineStyle: {
width: 6,
color: [
[0.25, "#7CFFB2"],
[0.5, "#58D9F9"],
[0.75, "#FDDD60"],
[1, "#FF6E76"],
],
},
},
pointer: {
icon: "arrow",
offsetCenter: [0, "-30%"],
itemStyle: {
color: "auto",
},
},
axisTick: {
length: 12,
lineStyle: {
color: "auto",
width: 1,
},
},
splitLine: {
length: 20,
lineStyle: {
color: "auto",
width: 5,
},
},
title: {
fontSize: 30,
},
data: [
{
value: this.chartValue,
name: this.chartName,
},
],
},
],
},
};
}
};
</script>
You should use the prop in following manner:
<GaugeChart chart-name="Sleep" chart-value="2" />
Documentation
HTML attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase.

Generate highcharts from v-for not populating all graphs with data

I'm working with highcharts to generate some polar graphs. https://codesandbox.io/s/vue-template-nibjp?file=/src/App.vue
The idea is that once the user presses update, a new array is pulled and it generates an n amount of charts based on that.
This is the code for the chart:
<template>
<div>
<highcharts :options="chartOptions" ref="lineCharts" :constructor-type="'chart'"></highcharts>
</div>
</template>
<script>
import {Chart} from 'highcharts-vue'
import Highcharts from "highcharts";
import HighchartsMore from "highcharts/highcharts-more";
HighchartsMore(Highcharts);
export default {
props:["options"],
components: {
highcharts: Chart
},
watch: {
options: {
handler(newOpt) {
this.chartOptions.series = newOpt.series
},
deep: true
}
},
data() {
return {
chartOptions: Highcharts.merge(this.options, {
chart: {
polar: true,
},
tooltip: {
},
title: {
text: null
},
subtitle: {
text:null
},
legend: {
enabled: false
},
credits: {
enabled: false
},
lang: {
noData: null
},
exporting: {
buttons: {
contextButton: {
theme: {
fill: "#F5F5F5"
}
}
}
},
pane: {
startAngle: -60,
size: '100%'
},
xAxis: {
tickPositions:[0,120,240],
min: 0,
max: 360,
labels: false
},
yAxis: {
max: 10,
tickInterval:2,
labels: false
},
plotOptions: {
series: {
grouping: true,
groupPadding:0,
pointPadding:0,
borderColor: '#000',
borderWidth: '0',
stacking: 'normal',
pointPlacement: 'between',
showInLegend: true
},
column: {
pointPadding: 0,
groupPadding: 0
}
},
series: [
{
data: [],
type: 'column',
name: 'On-time',
color: "#1DACE8",
pointStart: 0,
pointRange: 120
},
{
data: [],
type: 'column',
name: 'Fare',
color: "#EDCB64",
pointStart: 120,
pointRange: 120
},
{
data: [],
type: 'column',
name: 'Route Share',
color: "#F24D29",
pointStart: 240,
pointRange: 120
}
]
})
}}
}
</script>
And the code for the page:
<template>
<div id="app">
<button #click="updateChart">Update</button>
<v-item-group>
<v-container>
<v-row>
<v-col v-for="(chart, index) in chartSet" :key="index" cols="2">
<highcharts v-bind:options="chart"/>
</v-col>
</v-row>
</v-container>
</v-item-group>
</div>
</template>
<script>
import Chart from "./components/Chart";
var newSet = [
{ series: [{ data: [8] }, { data: [2] }, { data: [10] }] },
{ series: [{ data: [4] }, { data: [6] }, { data: [3] }] },
{ series: [{ data: [2] }, { data: [3] }, { data: [1] }] }
];
export default {
name: "App",
components: {
highcharts: Chart
},
data() {
return {
chartSet: [
{ series: [{ data: [] }, { data: [] }, { data: [] }] },
{ series: [{ data: [] }, { data: [] }, { data: [] }] },
{ series: [{ data: [] }, { data: [] }, { data: [] }] }
]
};
},
methods: {
updateChart() {
this.chartSet = newSet;
}
}
};
</script>
<style>
</style>
The problem I'm having is the following. If the chartSet is bigger than the newSet, and the newSet (has data for 3 charts), all works well and the charts are populated as they should.
chartSet: [
{ series: [{ data: [] }, { data: [] }, { data: [] }] },
{ series: [{ data: [] }, { data: [] }, { data: [] }] },
{ series: [{ data: [] }, { data: [] }, { data: [] }] },
{ series: [{ data: [] }, { data: [] }, { data: [] }] }
]
newSet = [
{ series: [{ data: [8] }, { data: [2] }, { data: [10] }] },
{ series: [{ data: [4] }, { data: [6] }, { data: [3] }] },
{ series: [{ data: [2] }, { data: [3] }, { data: [1] }] }
];
If the chartSet is smaller than the newSet, then only the first charts are populated.
chartSet: [
{ series: [{ data: [] }, { data: [] }, { data: [] }] }
]
newSet = [
{ series: [{ data: [8] }, { data: [2] }, { data: [10] }] },
{ series: [{ data: [4] }, { data: [6] }, { data: [3] }] },
{ series: [{ data: [2] }, { data: [3] }, { data: [1] }] }
];
The problem is that I don't know in advance how large the newSet will be as this depends on user input. So I could create a chartSet with 20 entries but then my page would be populated with empty graphs until the user presses update and that doesn't look ok. How can I start with only one graph and populate all the rest on demand?
To achieve it you have to update only series data and not the whole series object:
watch: {
options: {
handler(newOpt) {
const newChartSeries = [];
this.chartOptions.series.forEach(function(series, i) {
newChartSeries.push(Highcharts.merge(
series,
newOpt.series[i]
));
});
this.chartOptions.series = newChartSeries;
},
deep: true
}
},
created: function() {
const newChartSeries = [];
const options = this.options;
this.plotOptions.series.forEach(function(series, i) {
newChartSeries.push(Highcharts.merge(
series,
options.series[i]
));
});
this.chartOptions = Highcharts.merge(this.plotOptions, {
series: newChartSeries
});
},
data() {
return {
plotOptions: {
...
plotOptions: {
series: {
grouping: true,
groupPadding: 0,
pointPadding: 0,
borderColor: '#000',
borderWidth: '0',
stacking: 'normal',
pointPlacement: 'between',
showInLegend: true
},
column: {
pointPadding: 0,
groupPadding: 0
}
},
series: [{
data: [],
type: 'column',
name: 'On-time',
color: "#1DACE8",
pointStart: 0,
pointRange: 120
}, {
data: [],
type: 'column',
name: 'Fare',
color: "#EDCB64",
pointStart: 120,
pointRange: 120
}, {
data: [],
type: 'column',
name: 'Route Share',
color: "#F24D29",
pointStart: 240,
pointRange: 120
}]
},
chartOptions: null
}
}
Demo:
https://codesandbox.io/s/vue-template-1gyhw