I am retrieving data from an api (working) and generating a line plot with vue-chartjs.
However only the first two points are plotted. I must have my data structured in the wrong way but I don't know what it is expecting.
Here is my component:
import { Line } from "vue-chartjs";
export default {
extends: Line,
props: {
chartdata: {
type: Object,
default: null
}
},
mounted() {
this.renderChart(this.chartdata);
}
};
And in my App.vue file, I have this simple template:
<template>
<div id="app">
<div class="container">
<div class="Chart">
<h2>LineChart</h2>
<line-chart v-if="loaded" :chartdata="chartdata"></line-chart>
</div>
</div>
</div>
</template>
and this script:
<script>
import LineChart from './components/LineChart.js'
export default {
name: 'app',
components: { LineChart },
data:() => ({
loaded: false,
chartdata: null
}),
async mounted () {
this.loaded = false;
try {
let mydata = await fetch("http://myserver00/api/load/myserver02")
.then(stream => stream.json())
.then(mydata => {
let usercpu = [];
mydata.forEach(record => {
usercpu.push( record.cpu.user );
});
this.usercpu = usercpu;
});
} catch (e) {
console.error(e)
}
this.loaded = true;
this.chartdata = {
datasets: [
{label: 'CPU', data: this.usercpu}
]
};
}
}
</script>
As you may notice, I'm trying to system data from psutils to monitor servers. The original record from the api has several fields. This example just shows my attempt at CPU usage.
The browser tools show the data I expect, but apparently not what the chart expects. Here's a view of the data from the chrome vue devtools extension
Edit: to make sure the data is actually loaded, I added {{chartdata}} into my template and I see it all. Here is how it starts, the array goes on with all the data array. And the actual plot again shows only the first two data points.
{ "datasets": [ { "label": "CPU", "data": [ 2.7, 2.9, 3
Finally got it; I needed to re-read the doc for Chartjs. So here's the mounted function now. See that I had to put in x and y values for each point.
async mounted () {
this.loaded = false;
try {
let usercpu = [];
await fetch("http://server/api/load/server02")
.then(stream => stream.json())
.then(mydata => { mydata.forEach(record => {
usercpu.push( { y: record.cpu.user, x: record.date });});});
this.loaded = true;
this.chartdata = {
datasets: [ {
fill: false,
pointRadius: 0,
borderWidth: 2,
label: 'CPU',
data: usercpu
} ] };
} catch (e) {
console.error(e)
}
}
Also, in the data portion of the default function I had to add axes. I'm not exactly sure why, but even when the data was okay (above) I still couldn't see the plot. So when I added the axes, there everything was:
options: {
scales: {
xAxes: [{
type: 'time',
time: {
unit: "hour",
displayFormats: {
hour: "M/DD # hA"
},
tooltipFormat: "MMM. DD # hA"
},
scaleLabel: {
display: true,
labelString: "Date/Time"
},
ticks: { autoSkip: true, maxTicksLimit: 5},
position: 'bottom'
}],
yAxes: [{
ticks: {
suggestedMin: 0,
suggestedMax: 10
}
}]
}
And of course I had to include the options in the directive:
<template>
<div id="app">
<div class="container">
<div id="cpu">
<h2>Server02</h2>
<line-chart v-if="loaded" :chartdata="chartdata" :options="options"></line-chart>
</div>
</div>
</div>
</template>
Related
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>
I'm using vue-awesome-swiper for a product gallery. The user can select a different product and I want the gallery to update accordingly.
If the product has more than 1 image (most cases), I want the swiper to use the loop feature. If the product only has 1 image, I want it to work without the loop feature.
Assuming I'm able to capture when it should be true or false at the right time, how can I get the swiper to update or re-initialise with a different setting for the loop feature?
I've tried every combination of
this.swiper.destroy()
this.swiper.init()
new Swiper()
this.swiper.passedParams.loop = true/false
Whatever I do, either the loop feature stays, or the swiper doesn't actually initialise.
Edit: This is a really simplified version of the code. You will probably ask "why is he doing that?" on a couple of bits. It would make sense if you saw the full code, but its a bit too large to post here. Either way, it should not affect in responding with a solution for the loop problem.
<template>
<div class="pdp-main__gallery">
<swiper ref="mainSwiper"
id="pdpMainSwiper"
:options="mainSwiperOptions"
:data-swiper-group-id="swiperGroup"
:auto-update="true"
:auto-destroy="true"
>
<swiper-slide v-if="galleryProduct.LargeImage[1]">
<img ref="image" :data-href="galleryProduct.ExtraLargeImage[1]" :src="galleryProduct.LargeImage[1]" />
</swiper-slide>
<swiper-slide v-if="galleryProduct.LargeImage[2]">
<img ref="image" :data-href="galleryProduct.ExtraLargeImage[2]" :src="galleryProduct.LargeImage[2]" />
</swiper-slide>
<swiper-slide v-if="galleryProduct.LargeImage[3]">
<img ref="image" :data-href="galleryProduct.ExtraLargeImage[3]" :src="galleryProduct.LargeImage[3]" />
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
</div>
</template>
<script>
import { Swiper, SwiperSlide, directive } from 'vue-awesome-swiper'
import 'swiper/css/swiper.css'
export default {
components: {
Swiper,
SwiperSlide,
},
props: {
swiperGroup: {
type: String,
required: true,
},
product: {
type: Object,
required: false,
default() {
return undefined;
},
},
},
data(){
return {
galleryProduct: {
LargeImage: {},
ExtraLargeImage: {},
},
mainSwiperOptions: {
loop: true,
watchOverflow: true,
setWrapperSize: false,
initialSlide: 0,
spaceBetween: 5,
centeredSlides: true,
slidesPerView: 'auto',
normalizeSlideIndex: false,
freeMode: false,
autoHeight: APP.Browser().data.isMobile,
followFinger: APP.Browser().data.isMobile,
pagination: {
el: '.swiper-pagination',
type: 'bullets',
clickable: true,
},
},
}
},
computed: {
mainSwiper() {
return this.$refs.mainSwiper.$swiper
},
shouldLoop() {
if(this.product.LargeImage1 && this.product.LargeImage2) return true; else return false;
},
},
created() {
this.setGalleryImages();
},
mounted() {
this.setGalleryImages();
},
watch: {
product(newVal, oldVal) {
if(newVal.LargeImage1 != oldVal.LargeImage1) {
this.setGalleryImages();
this.$nextTick(function() {
if(this.mainSwiperOptions.loop) this.mainSwiper.slideToLoop(0, 0);
this.thumbsSwiper.slideTo(0);
});
}
},
},
methods: {
setGalleryImages()
{
var needsReinit = false;
if(this.shouldLoop !== this.mainSwiperOptions.loop) {
this.mainSwiperOptions.loop = this.shouldLoop;
this.destroyMainSwiper();
needsReinit = true;
}
this.galleryProduct.LargeImage[1] = this.product.LargeImage1;
this.galleryProduct.ExtraLargeImage[1] = this.product.ExtraLargeImage1;
this.galleryProduct.LargeImage[2] = this.product.LargeImage2;
this.galleryProduct.ExtraLargeImage[2] = this.product.ExtraLargeImage2;
this.galleryProduct.LargeImage[3] = this.product.LargeImage3;
this.galleryProduct.ExtraLargeImage[3] = this.product.ExtraLargeImage3;
if(needsReinit) {
this.initMainSwiper();
}
return;
},
destroyMainSwiper() {
this.mainSwiper.destroy();
// this.mainSwiper.destroy(false, false);
},
initMainSwiper() {
//let swiper = this.mainSwiper.init();
var swiper = new Swiper('#pdpMainSwiper', this.mainSwiperOptions);
// this.$refs.mainSwiper.$swiper = swiper;
}
}
};
</script>
Help appreciated!
I am using Vue-Chartjs to create a simple Line chart, I'm filling the chart with data via a get request to an API
however I want to generate new values randomly when I click on a button, & pass the values as a prop to chart-line component.
I've tried using reactiveProp & I also tried using a watcher for chartData prop, but I'm always getting this error
client.js?06a0:83 TypeError: Cannot read property 'map' of undefined
Dashboard Component
<template>
<div class="container">
<h1>Dashboard Page</h1>
<v-alert v-if="errorDetected"
class="mt-4"
dense
outlined
type="error"
>
There was an error while getting the chart data
</v-alert>
<v-btn #click="generateNewData()">Generate new data</v-btn>
<div class="loader-container">
<img v-if="!loaded" class="chart-loader mt-3" src="../static/loader-dotted.gif" alt="">
</div>
<ChartLine v-if="loaded" :chartData="values" :bind="true" />
</div>
</template>
<script>
import ChartLine from '../components/chart-line'
export default {
middleware: 'session',
components: {
ChartLine
},
data() {
return {
values: [],
customValues: [],
loaded: false,
errorDetected: false
}
},
head() {
return {
title: 'Dashboard page',
meta: [
{
hid: 'description',
name: 'description',
content: 'simple dashboard SPA'
}
]
}
},
mounted() {
this.requestData()
},
methods: {
requestData() {
this.loaded = false
this.$axios.get('http://www.mocky.io/v2/5eda474f330000fefc79eab4?mocky-delay=2000ms').then(response => {
console.log("requestData -> response", response)
this.values = response.data.data.value
this.loaded = true
}).catch(error => {
this.loaded = true
this.errorDetected = true
})
},
generateNewData() {
this.values = [];
for(let i=0; i<7; i++)
this.values.push(Math.floor((Math.random() * 10) + 1))
}
}
}
</script>
<style>
.loader-container {
display: flex;
justify-content: center;
}
.chart-loader {
width: 150px;
}
</style>
ChartLine Component
<script>
//Importing Line class from the vue-chartjs wrapper
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
//Exporting this so it can be used in other components
export default {
extends: Line,
mixins: [reactiveProp],
props: ['chartData'],
data () {
return {
datacollection: {
//Data to be represented on x-axis
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [
{
label: "Data 1",
backgroundColor: "transparent",
borderColor: "rgba(1, 116, 188, 0.50)",
pointBackgroundColor: "rgba(171, 71, 188, 1)",
//Data to be represented on y-axis
data: this.chartData
}
],
},
options: {
responsive: true,
maintainAspectRatio: false
}
//Chart.js options that controls the appearance of the chart
}
},
watch: {
chartData() {
this.renderChart(this.datacollection, this.options)
}
},
mounted () {
//renderChart function renders the chart with the datacollection and options object.
this.renderChart(this.datacollection, this.options)
}
}
</script>
UPDATE
I've managed to solve the issue of updating the chart
I removed the dataCollection from the chart-line component & added it in the dashboard component, I've also used the requestData() method in the created() hook to make a get request to the API, then on a button click I generate a new values and pass it as a prop
Update Code
Dashboard Component
<template>
<div class="container">
<h1>Dashboard Page</h1>
<v-alert v-if="errorDetected"
class="mt-4"
dense
outlined
type="error"
>
There was an error while getting the chart data
</v-alert>
<v-btn class="primary" #click="generateNewData()">Generate New Data</v-btn>
<div class="loader-container">
<img v-if="!loaded" class="chart-loader mt-3" src="../static/loader-dotted.gif" alt="">
</div>
<ChartLine v-if="loaded" :chart-data="dataCollection" />
</div>
</template>
<script>
import ChartLine from '../components/chart-line'
export default {
middleware: 'session',
components: {
ChartLine
},
data() {
return {
dataCollection: null,
values: [],
customValues: [],
loaded: false,
errorDetected: false
}
},
head() {
return {
title: 'Dashboard page',
meta: [
{
hid: 'description',
name: 'description',
content: 'simple dashboard SPA'
}
]
}
},
created() {
// this.loaded = false
// this.fillData()
// this.loaded = true
this.requestData()
},
methods: {
requestData() {
this.loaded = false
this.$axios.get('http://www.mocky.io/v2/5eda474f330000fefc79eab4?mocky-delay=2000ms').then(response => {
this.values = response.data.data.value
this.loaded = true
this.fillData()
}).catch(error => {
this.loaded = true
this.errorDetected = true
})
},
fillData () {
this.dataCollection = {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [
{
label: "Data 1",
backgroundColor: "transparent",
borderColor: "rgba(1, 116, 188, 0.50)",
pointBackgroundColor: "rgba(171, 71, 188, 1)",
data: this.values
}
]
}
},
generateNewData() {
this.values = []
this.loaded = false
setTimeout(function(){},2000)
for(let i=0; i<7; i++) {
this.values.push(Math.floor(Math.random() * (50 - 5 + 1)) + 5)
}
this.fillData()
this.loaded = true
}
}
}
</script>
<style>
.loader-container {
display: flex;
justify-content: center;
}
.chart-loader {
width: 150px;
}
</style>
Chart-Line Component
<script>
//Importing Line class from the vue-chartjs wrapper
import { Line, mixins } from 'vue-chartjs'
//Exporting this so it can be used in other components
export default {
extends: Line,
mixins: [mixins.reactiveProp],
// props:['chartData'],
data () {
return {
options: {
responsive: true,
maintainAspectRatio: false
}
//Chart.js options that controls the appearance of the chart
}
},
mounted () {
//renderChart function renders the chart with the datacollection and options object.
this.renderChart(this.chartData, this.options)
}
}
</script>
however there's still one thing I can't figure out, which is the loading state, when clicking on the button to generate new data
when I first open the dashboard page, the loading state works, but when I click on the button, loading state doesn't work
any Idea why??????
I'm trying to make a stacked line chart using Vue-ChartJS but am having difficulties getting it to stack.
I tried adding the following into the fill data function but saw no change.
scales: {
yAxes: [{ stacked: true}]
}
I also tried creating a this.options entry but that didn't work either. The minimal reproducible code for the chart is as follows, any advice or help would be much appreciated!
## LineChart.js
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
export default {
extends: Line,
mixins: [reactiveProp],
props: ['options'],
mounted() {
this.renderChart(this.chartData, this.options)
}
}
## LineChart.vue
<template>
<div class="small">
<line-chart :chart-data="chartData"></line-chart>
<button #click="fillData()">Randomize</button>
</div>
</template>
<script>
import LineChart from '../store/LineChart.js'
export default {
components: {
LineChart
},
data() {
return {
chartData: null
}
},
mounted() {
this.fillData()
},
methods: {
fillData() {
this.chartData = {
labels: [this.getRandomInt(), this.getRandomInt()],
datasets: [
{
label: 'Data One',
backgroundColor: '#f87979',
data: [this.getRandomInt(), this.getRandomInt()]
},
{
label: 'Data Two',
backgroundColor: '#C23596',
data: [this.getRandomInt(), this.getRandomInt()]
}
]
}
},
getRandomInt() {
return Math.floor(Math.random() * (50 - 5 + 1)) + 5
}
}
}
</script>
<style>
.small {
max-width: 600px;
margin: 150px auto;
}
</style>
You need to pass scales in the options:
...
<div class="small">
<line-chart :chart-data="chartData" :options="options"></line-chart>
<button #click="fillData()">Randomize</button>
</div>
...
data() {
return {
chartData: null,
options: {
scales: {
yAxes: [
{
stacked: true
}
]
},
},
}
},
I use the highcharts-vue wrapper (https://github.com/highcharts/highcharts-vue) to display a chart in my project. Now I'm dynamically adding new data to the already existing data.
(Originally I believe the addPoint function is used, but I already switched to pushing it to the series array.)
Now when I add one more element to data the selected/displayed range stays the same, but I wish to set it anchored to the right, so the new pushed data point appears (without having to shift the selected range manually).
In this highcharts example it's very much what I need, except for it not updating automatically at an interval, but on clicking a button.
https://www.highcharts.com/stock/demo/dynamic-update
I thought, I could work around it by using setExtremes on xAxis, but I get an error, that xAxis doesn't exist. Something like in this example.
http://jsfiddle.net/wkBwW/16/
So I think it's because I have to change the values differently or call an update differently because of the wrapped version.
<template>
<div class="chartcard">
<highcharts :constructor-type="'stockChart'" :options="chartOptions" ></highcharts>
<button v-on:click="addPoint" key="nextbutton"> add data </button>
</div>
</template>
<script>
import chartdatajson from "../assets/chart_data.json"
import {Chart} from 'highcharts-vue'
import Highcharts from 'highcharts'
import stockInit from 'highcharts/modules/stock'
stockInit(Highcharts);
let groupingUnits = [['week', [1]], ['month', [1]]];
function getOhlcAndVolume(data) {
...
}
let dataOhlcVolume = getOhlcAndVolume(chartdatajson);
export default {
name: 'ChartCard',
props: {
},
components: {
highcharts: Chart
},
methods: {
rescale: function(){
this.chartOptions.rangeSelector.selected = 0;
},
addPoint: function() {
let somedata = this.test_add_data.shift();
let ohls = somedata.slice(0,5);
let volume = [somedata[0], somedata[5]];
this.chartOptions.series[0].data.push(ohls);
this.chartOptions.series[1].data.push(volume);
// this.chartOptions.xAxis.max = somedata[0]
this.chartOptions.xAxis[0].setExtremes(1554048000000 , somedata[0])
},
},
data () {
return {
test_add_data: [[1556812800000,262,265,260.5,265,30200],[1557072000000,260,260,258,259,33688],[1557158400000,259.5,263,259,262.5,25686]],
chartOptions: {
rangeSelector: {
selected: 0
},
title: { text: 'Some Title' },
yAxis: [{...
}],
xAxis: {
},
series:
[{
type: 'candlestick',
name: 'AAPL',
data: dataOhlcVolume.ohlc,
dataGrouping: {
units: groupingUnits
}
}, {
type: 'column',
name: 'Volume',
data: dataOhlcVolume.volume,
yAxis: 1,
dataGrouping: {
units: groupingUnits
}
}]
}
}
}
}
</script>
Check this example with setting extremes in vue app: https://codesandbox.io/s/vue-template-nutgx. As you can see setExtremes() method has to be invoked on chart.xAxis instance.
You can get chart reference following this approach:
data() {
return {
chartOptions: {
chart: {
events: {
load: (function(self) {
return function() {
self.chart = this; // saving chart reference in the component
};
})(this)
}
},
...
}
};
}
This way you can reference xAxis.setExtremes() like that:
methods: {
setNewExtremes: function() {
this.chart.xAxis[0].setExtremes(1525872600000, 1528291800000);
}
}