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

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

Related

Vue : Echart js how to export chart as image (Vuexy admin.)

I'm attempting to export the chart as a picture. I just joined eCharts. Can someone explain how the export button operates? I need to download charts as image.
Here's what I try : Do I have something wrong?
<template>
<b-card title="Data Science">
<app-echart-bar:option-data="option"/>
</b-card>
</template>
<script>
import { BCard } from 'bootstrap-vue'
import AppEchartBar from '#core/components/charts/echart/AppEchartBar.vue'
export default {
components: {
BCard,
AppEchartBar,
},
data() {
return {
option: {
toolbox: {
show: true,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar', 'stack'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
xAxis: [
{
type: 'category',
data: ['FB', 'IN', 'TW', 'YT',],
},
],
yAxis: [
{
type: 'value',
splitLine: { show: false },
},
],
grid: {
left: '40px',
right: '30px',
bottom: '30px',
},
series: [
{
name: 'Available',
type: 'bar',
barGap: 0,
emphasis: {
focus: 'series'
},
data: [22, 24, 20, 18]
},
{
name: 'Not Available',
type: 'bar',
barGap: 0,
emphasis: {
focus: 'series'
},
data: [12, 10, 14, 16]
},
],
},
}
},
}
</script>
Comp:
<template>
<e-charts
ref="line"
autoresize
:options="option"
theme="theme-color"
auto-resize/>
<script>
import ECharts from 'vue-echarts'
import 'echarts/lib/component/tooltip'
import 'echarts/lib/component/legend'
import 'echarts/lib/chart/bar'
import theme from './theme.json'
ECharts.registerTheme('theme-color', theme)
export default { components: {
ECharts,
}, props: {
optionData: {
type: Object,
default: null,
}, }, data() {
return {
option: {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
toolbox: {
show: true,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
mark: { show: true },
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar', 'stack'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
legend: {
left: 0,
},
grid: this.optionData.grid,
xAxis: this.optionData.xAxis,
yAxis: this.optionData.yAxis,
series: this.optionData.series,
},
} }, }
</script>
The chart created by eCharts is shown below :
Can somebody assist me with adding a button or tell me whether I need to add a script to download an image.

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"
},
},
},
},
}
},

How to correctly pass array with data to a chart from the VueApexCharts library in vue3?

I am writing a small covid project and trying to plot confirmed infection data using ApexCharts, but the graph is not showing. I enter the data from vuex in two tables. The data is valid however it comes from api and sa in the proxy object. What am I doing wrong? (I am using ApexCharts because vue Chartjs is not compatible with vue 3).
<template>
<apexchart width="500" type="bar" :options="options" :series="series"></apexchart>
</template>
<script>
import VueApexCharts from "vue3-apexcharts";
export default {
components: {
apexchart: VueApexCharts,
},
data(){
return {
series: [],
options: {
chart: {
type: "bar",
height: 400,
stacked: true
},
plotOptions: {
bar: {
horizontal: false
}
},
dataLabels: {
enabled: false
},
tooltip: {
shared: true,
followCursor: true
},
stroke: {
width: 1,
colors: ["#fff"]
},
fill: {
opacity: 1
},
legend: {
position: "top",
horizontalAlign: "center",
offsetX: 40
},
colors: ["rgb(95, 193, 215)", "rgb(226, 37, 43)", "rgb(94, 181, 89)"]
}
};
},
computed: {
getDate(){
return this.$store.getters['chart/getDate'];
},
getConfirmed(){
return this.$store.getters['chart/getConfirmed'];
}
},
methods: {
fillDate(){
this.options = {
xaxis: {
categories: this.getDate
}
}
this.series = [
{
name: 'Confirmed',
data: this.getConfirmed
}
];
}
},
async mounted() {
await this.fillDate();
}
}
The data from the vuex store are two arrays.
Proxy
[[Handler]]: Object
[[Target]]: Array(45)
[[IsRevoked]]: false
Instead of using mounted hook and method try to watch the computed properties then update the data based on that ones:
computed: {
getDate(){
return this.$store.getters['chart/getDate'];
},
getConfirmed(){
return this.$store.getters['chart/getConfirmed'];
}
},
watch:{
getDate:{
handler(newVal){
this.options = {
xaxis: {
categories: this.getDate
}
},
deep:true
},
getConfirmed:{
handler(newVal){
this.series = [
{
name: 'Confirmed',
data: this.getConfirmed
}
];
},
deep:true
}
}

Error in mounted hook: "TypeError: Cannot read property 'type' of null"

I am using echarts to draw a Heatmap
But giving me error!!
Error in mounted hook: "TypeError: Cannot read property 'type' of null"
mounted() {
this.initChart()
},
I am using the json data from here:
https://www.echartsjs.com/data/asset/data/hangzhou-tracks.json
Just took 1 data from the above link.
<template>
<div
:id="id"
:class="className"
:style="{height:height,width:width}"
/>
</template>
<script>
import echarts from 'echarts'
import resize from '../mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'newCustomerForecastChart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
},
data() {
return {
chart: null,
dataArr: [
{
"coord":
[
120.14322240845,
30.236064370321
],
"elevation": 21
},
{
"coord":
[
120.14280555506,
30.23633761213
],
"elevation": 5
}
]
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
var points = [
{
"coord":
[
120.14322240845,
30.236064370321
],
"elevation": 21
},
{
"coord":
[
120.14280555506,
30.23633761213
],
"elevation": 5
}
]
this.chart = echarts.init(document.getElementById(this.id))
var colors = ['#5793f3', '#d14a61', '#675bba'];
this.chart.setOption({
animation: false,
bmap: {
center: [120.13066322374, 30.240018034923],
zoom: 14,
roam: true
},
visualMap: {
show: false,
top: 'top',
min: 0,
max: 5,
seriesIndex: 0,
calculable: true,
inRange: {
color: ['blue', 'blue', 'green', 'yellow', 'red']
}
},
series: [{
type: 'heatmap',
coordinateSystem: 'bmap',
data: points,
pointSize: 5,
blurSize: 6
}]
});
var bmap = myChart.getModel().getComponent('bmap').getBMap();
bmap.addControl(new BMap.MapTypeControl());
}
}
}
</script>
What is the problem actually?
should use
import * as echarts from 'echarts'
imstead of
import echarts from 'echarts'

How to combine Filtering, Grouping, and Sorting in Kendo UI Vue Grid (native)

I'm trying to enable some operations on my grid such as grouping, filtering and sorting, individually they works as shown in the docs but there is no an example of those functionality working together.
By myself I was able to combine sorting and filtering but grouping does not work when i'm adding it as it shown in the docs. look at at my code
<template>
<div>
<Grid :style="{height: '100%'}"
ref="grid"
:data-items="getData"
:resizable="true"
:reorderable="true"
#columnreorder="columnReorder"
:filterable="true"
:filter="filter"
#filterchange="filterChange"
:sortable="true"
:sort= "sort"
#sortchange="sortChangeHandler"
:groupable="true"
:group= "group"
#dataStateChange="dataStateChange"
:columns="columns">
</Grid>
</div>
</template>
<script>
export default {
data() {
return {
items: [],
editID: null,
columns: [
{ field: 'AbsenceEmployeID', filterable:false, editable: false, title: '#'},
{ field: 'Employe', title: 'Employer', cell: DropDownEmployes},
{ field: 'Remarque', title: 'Remarque'},
{ field: 'Type', title: 'Type', cell: DropDownTypes},
{ field: 'CreatedDate', filter:'date', editable: false, editor: 'date', title: 'créé le', format: '{0:d}'},
{ title: 'Actions', filterable:false, cell: CommandCell}
],
filter: {
logic: "and",
filters: []
},
sort: [
{ field: 'CreatedDate', dir: 'desc' }
],
group: [],
gridData: []
}
}
mounted() {
this.loadItems()
},
computed: {
absencesList() {
return this.items.map((item) => Object.assign({ inEdit: item.AbsenceEmployeID === this.editID}, item));
},
getData() {
return orderBy(filterBy(this.absencesList, this.filter), this.sort);
},
...mapState({
absences: state => state.absences.absences
})
}
methods: {
loadItems () {
this.$store.dispatch('absences/getAbsences')
.then(resp => {
this.items = this.absences.map(item => item)
})
},
filterChange: function(ev) {
this.filter = ev.filter;
},
columnReorder: function(options) {
this.columns = options.columns;
},
sortChangeHandler: function(e) {
this.sort = e.sort;
},
// the following is for grouping but not yet used, read more
groupedData: function () {
this.gridData = process(this.getData, {group: this.group});
},
createAppState: function(dataState) {
this.group = dataState.group;
this.groupedData();
},
dataStateChange: function (event) {
this.createAppState(event.data);
},
}
}
</script>
The last three methods are not used yet, so filtering and sorting is working perfectly as of now. then in other to enable grouping I want to replace :data-items="getData" by :data-items="gridData" and run this.groupedData() method after the items are loaded but grouping doesn't work.
I think everything should be handle by the dataStateChange event and process() function but I also tried but without success
If you define the filterchange and sortchange events they are being triggered for filter and sort and you will have to updated data in their handlers. If you rather want to use datastatechage event for all the changes you have to remove the filterchange and sortchange events and the datastatechage event will be triggered instead of them. In this case you will have to update the data in its handler.
You can use the process method of #progress/kendo-data-query by passing the respective parameter each data change that is needed as in the example below:
const result = process(data, {
skip: 10,
take: 20,
group: [{
field: 'category.categoryName',
aggregates: [
{ aggregate: "sum", field: "unitPrice" },
{ aggregate: "sum", field: "unitsInStock" }
]
}],
sort: [{ field: 'productName', dir: 'desc' }],
filter: {
logic: "or",
filters: [
{ field: "discontinued", operator: "eq", value: true },
{ field: "unitPrice", operator: "lt", value: 22 }
]
}
});
Hers is a sample stackblitz example where such example is working correctly - https://stackblitz.com/edit/3ssy1k?file=index.html
You need to implement the groupchange method to handle Grouping
I prefer to use process from #progress/kendo-data-query
The following is a complete example of this
<template>
<Grid :style="{height: height}"
:data-items="gridData"
:skip="skip"
:take="take"
:total="total"
:pageable="pageable"
:page-size="pageSize"
:filterable="true"
:filter="filter"
:groupable="true"
:group="group"
:sortable="true"
:sort="sort"
:columns="columns"
#sortchange="sortChangeHandler"
#pagechange="pageChangeHandler"
#filterchange="filterChangeHandler"
#groupchange="groupChangeHandler"
/>
</template>
<script>
import '#progress/kendo-theme-default/dist/all.css';
import { Grid } from '#progress/kendo-vue-grid';
import { process } from '#progress/kendo-data-query';
const sampleProducts = [
{
'ProductID': 1,
'ProductName': 'Chai',
'UnitPrice': 18,
'Discontinued': false,
},
{
'ProductID': 2,
'ProductName': 'Chang',
'UnitPrice': 19,
'Discontinued': false,
},
{
'ProductID': 3,
'ProductName': 'Aniseed Syrup',
'UnitPrice': 10,
'Discontinued': false,
},
{
'ProductID': 4,
'ProductName': "Chef Anton's Cajun Seasoning",
'UnitPrice': 22,
'Discontinued': false,
},
];
export default {
components: {
Grid,
},
data () {
return {
gridData: sampleProducts,
filter: {
logic: 'and',
filters: [],
},
skip: 0,
take: 10,
pageSize: 5,
pageable: {
buttonCount: 5,
info: true,
type: 'numeric',
pageSizes: true,
previousNext: true,
},
sort: [],
group: [],
columns: [
{ field: 'ProductID', filterable: false, title: 'Product ID', width: '130px' },
{ field: 'ProductName', title: 'Product Name' },
{ field: 'UnitPrice', filter: 'numeric', title: 'Unit Price' },
{ field: 'Discontinued', filter: 'boolean', title: 'Discontinued' },
],
};
},
computed: {
total () {
return this.gridData ? this.gridData.length : 0;
},
},
mounted () {
this.getData();
},
methods: {
getData: function () {
this.gridData = process(sampleProducts,
{
skip: this.skip,
take: this.take,
group: this.group,
sort: this.sort,
filter: this.filter,
});
},
// ------------------Sorting------------------
sortChangeHandler: function (event) {
this.sort = event.sort;
this.getData();
},
// ------------------Paging------------------
pageChangeHandler: function (event) {
this.skip = event.page.skip;
this.take = event.page.take;
this.getData();
},
// ------------------Filter------------------
filterChangeHandler: function (event) {
this.filter = event.filter;
this.getData();
},
// ------------------Grouping------------------
groupChangeHandler: function (event) {
this.group = event.group;
this.getData();
},
},
};
</script>