There seems to be a few examples of how to do something similar, but all slightly different from my case. I am loading some stock data from an API (in a JS file) and then using it in my VUE. I would like to update my chart series with a new array compiles from the API data, but it's not working and I am not getting any errors.
My Vue looks like this:
<template>
<div>
<highcharts :options="chartOptions" :updateArgs="[true, false]" ref="highcharts"></highcharts>
</div>
</template>
<script>
import appService from '../stock_prices'
import {Chart} from 'highcharts-vue'
export default {
name: 'stocks',
props: {
msg: String
},
data () {
return {
chartOptions: {
mySeries: [],
info: {},
updateArgs: [true, true, true],
series: [{
data: [1,2,3,4,5,6,7]
}],
}
},
}
}, //data
components: {
highcharts: Chart
},
methods: {
updateSeries() {
for (var i = 0; i < this.info.stock_prices.length; i++) {
this.mySeries.push([this.info.stock_prices[i].volume]);
i++
}
data: this.mySeries
}
}, //methods
async created () {
this.info = await appService.getPosts();
this.updateSeries()
}, //async created
} //export default
I would like to obviously wait for all my data from my API (in the appService component) to load and then use it to create the updated series, but I am not sure that is actually what is happening.
Perhaps an important note: If I replace data: this.mySeries in my method with something like data: [10,10,10,10,10,10] it is still unsuccessful - no errors and the series is not being updated.
Thanks!
Notice, that your data doesn't contain chartOptions. Also, in updateSeries() you are updating data which refers to nothing. It should be something like the example below:
<template>
<div>
<highcharts :options="chartOptions" :updateArgs="[true, false]" ref="highcharts"></highcharts>
</div>
</template>
<script>
import appService from '../stock_prices'
import {Chart} from 'highcharts-vue'
export default {
name: 'stocks',
props: {
msg: String
},
data () {
return {
mySeries: [],
info: {},
updateArgs: [true, true, true],
chartOptions: {
series: [{
data: [1,2,3,4,5,6,7]
}]
}
}
}, //data
components: {
highcharts: Chart
},
methods: {
updateSeries() {
for (var i = 0; i < this.info.stock_prices.length; i++) {
this.mySeries.push([this.info.stock_prices[i].volume]);
}
this.chartOptions.series[0].data: this.mySeries;
}
}, //methods
async created () {
this.info = await appService.getPosts();
this.updateSeries()
}, //async created
} //export default
Check this example:
https://codesandbox.io/s/nw750l07nj
Related
<template>
<div class="container">
<div class="gameboard">
<div v-for="item in boardfields" :key="item.number">
{{ item.number }}
</div>
</div>
</div>
</template>
<script>
export default {
name: "App",
components: {},
data() {
return {
boardfields: [
{ number: 1, isclicked: false },
{ number: 2, isclicked: false },
{ number: 3, isclicked: false },
{ number: 4, isclicked: false },
{ number: 5, isclicked: false },
{ number: 6, isclicked: false },
],
};
},
As you can see I have a few similar objects in the 'boardfields' array. I have to make around 50 of those. Is there a way to create a loop that creates a certain amount of this object with a different number and pushing it to the array so I don't have to copy and paste it and manually change the numbers?
I think in JS it would be something like
var i;
for (var i = 0, i > 50, i++){
this.boardfields.push({number: i, isclicked: false});
}
I think #Boussadjra's answer is correct, but wanted to add some context.
The functional [...Array(50)].map()...etc is the popular way to go these days. You can populate the value on data definition or onmount or oncreate, there are some nuances that might be worthwhile considering.
Note that if you are using:
const initialBoard = []
for (var i = 1; i <= 50; i++) {
initialBoard.push({number: i, isclicked: false});
}
export default {
name: "App",
components: {},
data() {
return {
boardfields: initialBoard
};
},
}
The value of initialBoard is persistent. The objects are created on first run and are populating the array which is re-used. That means if you create two components, they may share the values of the objects inside the array. IMHO, this is a a side effect you want to avoid unless you explicitly looking for that functionality, even if you only use one instance of the component.
B's solution...
export default {
name: "App",
components: {},
data() {
return {
boardfields: [],
};
},
mounted() {
this.boardFields=[...Array(50)].map((_,i)=>({number: i+1, isclicked: false}))
}
}
Is safer in that regard, since it generates a new array with new objects every time it is mounted. My preference would be to use created, because it will make the data available on the first draw, but because the variable is preset to an empty array, it's not going to cause errors (like an error in your template if the variable had .length on undifined or null)
Here is an example that illustrates the difference. Not that when the component is remounted or recreated (doesn't make a difference which on here) the data is lost, but the (top) two components don't share the data., wheras the bottom two do.
const app = Vue.createApp({
data: function() {
return {
cKey: 1
}
}
})
const prepArr = [...Array(5)].map((_, i) => ({
name: 'item-' + i
}))
app.component("my-component", {
data: function() {
return {
myArr: []
}
},
created: function() {
this.myArr = [...Array(5)].map((_, i) => ({
name: 'item-' + i
}))
},
template: `<div class="h">Component Using created<ul>
<li v-for="item in myArr">{{ item.name }} <button #click="()=>{item.name = item.name +'!'}">+</button></li>
</ul></div>`
});
app.component("persistent-component", {
data: function() {
return {
myArr: prepArr
}
},
template: `<div class="h">Component using persistent<ul>
<li v-for="item in myArr">{{ item.name }} <button #click="()=>{item.name = item.name +'!'}">+</button></li>
</ul></div>`
});
app.mount('#app')
.h{display: inline-block; width: 49%;}
<script src="https://unpkg.com/vue#3.0.2/dist/vue.global.prod.js"></script>
<div id="app">
<div><button #click="()=>{cKey++}">regenerate</button></div>
<my-component :key="cKey"></my-component>
<my-component :key="cKey"></my-component>
<persistent-component :key="cKey"></persistent-component>
<persistent-component :key="cKey"></persistent-component>
</div>
You could achieve this by using [...Array(50)] which returns 50 items with undefined values then map this array to return your objects array, this is done in the mounted lifecycle hook :
export default {
name: "App",
components: {},
data() {
return {
boardfields: [],
};
},
mounted(){
this.boardFields=[...Array(50)].map((_,i)=>({number: i+1, isclicked: false}))
}
}
You can run any valid javascript code inside the <script> tag, so this will work
<script>
const initialBoard = []
for (var i = 1; i <= 50; i++) {
initialBoard.push({number: i, isclicked: false});
}
export default {
name: "App",
components: {},
data() {
return {
boardfields: initialBoard
};
},
#Daniel - thanks for the clarification. Upvote for Boussadjra Brahim's answer which is better.
Use a "factory" function to create the data if you wish to have independent boardfields per component.
<script>
const initializeBoard = () => {
const initialBoard = [];
for (var i = 1; i <= 50; i++) {
initialBoard.push({number: i, isclicked: false});
}
return initialBoard;
}
export default {
name: "App",
components: {},
data() {
return {
boardfields: initializeBoard()
};
},
I am displaying four charts, so i have created four different chart components, but all the components are same but they differ in API id.
In order to make code effective, i want to make a single Chart.vue component and then display the chart in any other component by their respective ID.
Here is one of my chart component:
<template>
<div class="chart-container" style="position: relative; height: 25vh; width:100%;">
<canvas id="DisplayChart" ></canvas>
</div>
</template>
<script>
import moment from 'moment'
export default {
name: 'Chart',
async mounted () {
await this.$http.get('/farfrom/chart/3') //Here 3 is the ID of the chart.
.then((response) => {
const result = response.data
const ctx = document.getElementById('DisplayChart').getContext('2d')
const Chart_data = []
for (let i = 0; i < result.date.length; i++) {
Chart_data.push({
x_axis: moment(result.date[i], 'X').toDate(),
y_axis: result.challenge[i]
})
}
let myChart
if (myChart !== undefined) {
myChart.destroy()
}
myChart = new Chart(ctx, {
type: 'line',
data: {
datasets: [
{
label: 'Chart_from_API',
data: Chart_data,
]
},
options: {
lineTension: 0,
maintainAspectRatio: false,
scales: {
yAxes: [
{
scaleLabel: {
display: false
},
ticks: {
beginAtZero: true,
// eslint-disable-next-line no-unused-vars
callback (value) {
return `${value }k` // y-axis value will append k to it
}
}
}
],
xAxes: [
{
type: 'time',
time: {
unit: 'month'
},
scaleLabel: {
display: true
}
}
]
}
}
})
})
.catch((error) => {
console.log(error)
})
}
}
</script>
i have imported this in another component like:
<template>
<div class="chart-container">
<Chart></Chart>
</div>
</template>
<script>
import Chart from 'Chart.vue'
....
</script>
Here i have shown the example of one of my chart among 4 charts, my other charts are also same only change is in the id of the API.
Now as i said i want to know how i can make one Single Chart.vue with some variable to API Id and use it in any component by their respective id. Please do help me in this.
Objective: I want to have a single Chart.vue component instead of 4 charts, and if i want to use any chart i should be able to use their respective id.
You could use props to pass data to a child component.
https://v2.vuejs.org/v2/guide/components-props.html
Example
App.vue
<template>
<div id="app">
<Chart :id="chartId" />
</div>
</template>
<script>
import Chart from "#/components/Chart.vue";
// #/ means src/
export default {
name: "App",
components: {
Chart,
},
data: () => ({
chartId: 2,
}),
};
</script>
Chart.vue
<template>
<div>
{{ id }}
</div>
</template>
<script>
export default {
name: "Chart",
props: {
id: {
type: Number,
required: true,
},
},
mounted() {
const apiURL = `/farfrom/chart/${this.id}`;
// API call
}
};
</script>
Is there anyone knowing how to use the Vue FusionCharts with the "template"?
I created a test VueJs Component, named FChart:
<template>
<fusioncharts
:type="type"
:width="width"
:height="height"
:dataFormat="dataFormat"
:dataSource="dataSource"
></fusioncharts>
</template>
<script>
import Vue from "vue";
import VueFusionCharts from "vue-fusioncharts";
import FusionCharts from "fusioncharts";
import TimeSeries from "fusioncharts/fusioncharts.timeseries";
// register VueFusionCharts component
Vue.use(VueFusionCharts, FusionCharts, TimeSeries);
var jsonify = res => res.json();
var dataFetch = fetch("https://s3.eu-central-1.amazonaws.com/fusion.store/ft/data/line-chart-with-time-axis-data.json").then(jsonify);
var schemaFetch = fetch("https://s3.eu-central-1.amazonaws.com/fusion.store/ft/schema/line-chart-with-time-axis-schema.json").then(jsonify);
export default {
name: "FChart",
data() {
return {
width: "100%",
height: "400",
type: "timeseries",
dataFormat: "json",
dataSource: {
chart: {},
caption: {
text: "Sales Analysis"
},
subcaption: {
text: "Grocery"
},
yaxis: [
{
plot: {
value: "Grocery Sales Value"
},
format: {
prefix: "$"
},
title: "Sale Value"
}
]
}
};
},
mounted: function() {
Promise.all([dataFetch, schemaFetch]).then(res => {
const data = res[0];
const schema = res[1];
const fusionTable = new FusionCharts.DataStore().createDataTable(
data,
schema
);
this.dataSource.data = fusionTable;
});
}
};
</script>
And my App.vue does the following:
<template>
<v-app>
<v-content>
<FChart />
</v-content>
</v-app>
</template>
<script>
import FChart from "./components/FChart";
export default {
name: "App",
components: { FChart }
};
</script>
but this does not work. I have in the console an error message saying:
Uncaught TypeError: Cannot read property 'getLogicalSpace' of undefined
at e.i.manageSpace (fusioncharts.timeseries.js?79dd:1)
at e.t.updateVisual (fusioncharts.js?8f68:13)
at Object.e.__drawJob [as job] (fusioncharts.js?8f68:13)
at b (fusioncharts.js?8f68:13)
I
After several hours of researches and tries, here is the version not really different from the initial one, however:
<template>
<fusioncharts
:type="type"
:width="width"
:height="height"
:dataFormat="dataFormat"
:dataSource="dataSource"
></fusioncharts>
</template>
<script>
import Vue from "vue";
import VueFusionCharts from "vue-fusioncharts";
import FusionCharts from "fusioncharts";
import TimeSeries from "fusioncharts/fusioncharts.timeseries";
Vue.use(VueFusionCharts, FusionCharts, TimeSeries);
const jsonify = res => res.json();
const dataFetch = fetch(
"https://s3.eu-central-1.amazonaws.com/fusion.store/ft/data/line-chart-with-time-axis-data.json"
).then(jsonify);
const schemaFetch = fetch(
"https://s3.eu-central-1.amazonaws.com/fusion.store/ft/schema/line-chart-with-time-axis-schema.json"
).then(jsonify);
export default {
name: "FChart",
data() {
return {
type: "timeseries",
width: "100%",
height: "500",
dataFormat: "json",
dataSource: {
data: null,
caption: {
text: "Sales Analysis"
},
subcaption: {
text: "Grocery"
},
yAxis: [
{
plot: {
value: "Grocery Sales Value",
type: "line"
},
format: {
prefix: "$"
},
title: "Sale Value"
}
]
}
};
},
mounted: function() {
// In this Promise we will create our DataStore and using that we will create a custom DataTable which takes two
// parameters, one is data another is schema.
Promise.all([dataFetch, schemaFetch]).then(res => {
const data = res[0];
const schema = res[1];
// First we are creating a DataStore
const fusionDataStore = new FusionCharts.DataStore();
// After that we are creating a DataTable by passing our data and schema as arguments
const fusionTable = fusionDataStore.createDataTable(data, schema);
// After that we simply mutated our timeseries datasource by attaching the above
// DataTable into its data property.
this.dataSource.data = fusionTable;
});
}
};
</script>
I'm trying to set up a Vue component that takes a flat list of items in an array, groups them by a property for use in a sub-component, and emits the updated flat array.
My section component uses these grouped items in their v-model and emits the updated list. The section component is a drag-and-drop with some input fields, so items are changed under the section component and the updated list is emitted.
Here's an example of the component that takes the flat list as a prop:
<template>
<div>
<div v-for="section in template.sections" :key="section.id">
<h2>{{ section.name }}</h2>
<item-section :section="section" v-model="sectionData[section.id]"></item-section>
</div>
</div>
</template>
<script type="text/javascript">
import { groupBy } from "lodash";
import ItemSection from "#/components/Section.vue";
export default {
name: "ItemAssignment",
props: {
// All items in flat array
value: {
type: Array,
required: true,
default: () => [
/**
* {
* id: null,
* section_id: null,
* name: null
* }
*/
]
},
// Template (containing available sections)
template: {
type: Object,
default: () => {
return {
sections: [
/**
* {
* id: null,
* name: null
* }
*/
]
};
}
}
},
components: {
ItemSection
},
data() {
return {
sectionData: []
};
},
mounted() {},
computed: {
flattenedData() {
return Object.values(this.sectionData).flat();
}
},
methods: {},
watch: {
// Flat list updated
value: {
immediate: true,
deep: true,
handler(val) {
this.sectionData = groupBy(val, "section_id");
}
},
// --- Causing infinite loop ---
// flattenedData(val) {
// this.$emit("input", val);
// },
}
};
</script>
The parent of this component is basically this:
<template>
<div>
<!-- List items should be updatable here or from within the assignment component -->
<item-assignment v-model="listItems"></item-assignment>
</div>
</template>
<script type="text/javascript">
import ItemAssignment from "#/components/ItemAssignment.vue";
export default {
name: "ItemExample",
props: {
},
components: {
ItemAssignment
},
data() {
return {
listItems: []
};
},
mounted() {},
computed: {
},
methods: {
// Coming from API...
importExisting(list) {
var newList = [];
list.forEach(item => {
const newItem = {
id: null, // New record, so don't inherit ID
section_id: item.section_id,
name: item.name
};
newList.push(newItem);
});
this.listItems = newList;
}
},
watch: {
}
};
</script>
When emitting the finalized flat array, Vue goes into an infinite loop trying to re-process the list and the browser tab freezes up.
I believe the groupBy and/or Object.values(array).flat() method are stripping the reactivity out so Vue constantly thinks it's different data, thus the infinite loop.
I've tried manually looping through the items and pushing them to a temporary array, but have had the same issue.
If anyone knows a way to group and flatten these items while maintaining reactivity, I'd greatly appreciate it. Thanks!
So it makes sense why this is happening...
The groupBy function creates a new array, and since you're watching the array, the input event is triggered which causes the parent to update and pass the same value which gets triggered again in a loop.
Since you're already using lodash, you may be able to include the isEqual function that can compare the arrays
import { groupBy, isEqual } from "lodash";
import ItemSection from "#/components/Section.vue";
export default {
// ...redacted code...
watch: {
// Flat list updated
value: {
immediate: true,
deep: true,
handler(val, oldVal) {
if (!isEqual(val, oldVal))
this.sectionData = groupBy(val, "section_id");
}
},
flattenedData(val) {
this.$emit("input", val);
},
}
};
this should prevent the this.sectionData from updating if the old and new values are the same.
this could also be done in flattenedData, but would require another value to store the previous state.
I have a vue chartjs component which imports the whole vue-chartjs library. My idea is, is it possible to somehow pass the type of the chart which I want and add it to the 'extends: VueCharts.charttype?.' In the example I provide it extends the VueCharts.Line, I need this property to be dynamically interpolated, passed from props. Is it possible this charttype to come from a parent props dynamically and how?
<script>
import { VueCharts } from "vue-chartjs";
export default {
extends: VueCharts.Line,
props: ["chartdata", "options"],
mounted() {
this.renderChart(this.chartdata, this.options);
}
}
</script>
<style scoped>
</style>
since extends the same as mixins, you need to pass a dynamic mixin, in order to do that you need two components, imagine we have component ChartWrapper :
<template>
<div>
<div>{{ chartType }}</div>
<chart :chart-data="datacollection"/>
</div>
</template>
<script>
import Chart from "./Chart";
import { VueCharts, mixins } from "vue-chartjs";
const { reactiveProp } = mixins;
export default {
name: "ChartWrapper",
components: {
Chart
},
props: {
chartType: {
type: String,
required: true
}
},
data() {
return {
datacollection: {
labels: [this.getRandomInt(), this.getRandomInt()],
datasets: [
{
label: "Data One",
backgroundColor: "#f87979",
data: [this.getRandomInt(), this.getRandomInt()]
},
{
label: "Data One",
backgroundColor: "#f87979",
data: [this.getRandomInt(), this.getRandomInt()]
}
]
}
};
},
methods: {
getRandomInt() {
return Math.floor(Math.random() * (50 - 5 + 1)) + 5;
}
},
created() {
if (this.chartType) {
Chart.mixins = [reactiveProp,VueCharts[this.chartType]];
}
}
};
</script>
this component takes chartType as a prop, and I import all charts as VueCharts in top of the script ==> 1
second component:
<script>
export default {
props: ["options"],
mounted() {
// this.chartData is created in the mixin.
// If you want to pass options please create a local options object
this.renderChart(this.chartData, this.options);
}
};
</script>
the second component just has options props, and renderChart function invoked.
==> 2
What is happening?
the ChartWrapper component receives the chart type by chartType prop, in the created hook, if chartType exist, assign the chart(resolved by VueCharts[this.chartType]) to Chart component as a mixin in addition to reactiveProp,
I also pass the chart data to Chart component.
in the end, call the ChartWrapper component:
<ChartWrapper chartType="Bar"/>
Live example on code sandbox: https://codesandbox.io/s/vue-template-w9r8k
You can also choose for the option to just extend the Line chart and update the config of the chart with the chart type you want and give it an update so it changes type.
<script>
import { Line, mixins } from 'vue-chartjs';
const { reactiveProp } = mixins;
export default {
extends: Line,
name: "LineChart",
mixins: [reactiveProp],
props: {
options: { type: Object },
chartType: { type: String }
},
mounted () {
this.renderChart(this.chartData, this.options);
},
watch: {
options: {
deep: true,
handler () {
this.$data._chart.options = this.options;
this.updateChart();
}
},
chartType (newVal) {
this.$data._chart.config.type = newVal;
this.updateChart()
}
},
methods: {
updateChart () {
this.$data._chart.update();
},
}
}
</script>