Updating in chart.js - javascript

I'm trying to figure out how to update a chart.js chart. Google's returned with a lot of answers and I think some are outdated because I can't seem to get any of the solutions to work. The documentation page says just use chartname.update() but it doesn't seem to work for me. I already checked console to make sure the chart object was updating. For some reason the chart itself on the page just isn't changing.
let chartContainer = document.getElementById('charts');
let overview = {
create: function () {
let chartCanvas = document.createElement('canvas');
chartCanvas.id = 'overviewChart';
chartCanvas.appendChild(document.createTextNode('test'));
chartContainer.appendChild(chartCanvas);
let overviewChart = document.getElementById('overviewChart').getContext('2d');
renderChart = new Chart(overviewChart, {
type: 'bar',
data: {
labels:subjectList,
datasets: [{
barThickness: 'flex',
label: 'Completed Credits',
data: []
}]
},
options: {
}
})
},
reload: function() {
console.log('reloaded overview chart');
renderChart.data.datasets.data = [];
for (subject in classes) {
console.log('adding: ' + classes[subject].count)
renderChart.data.datasets.data.push(classes[subject].count);
}
renderChart.update();
}
}
function reloadCharts() {
overview.reload();
}
overview.create();

There are problems in your reload function where you access renderChart.data.datasets.
Please note that renderChart.data.datasets is an array. Therefore, you need to make the following changes:
reload: function() {
// renderChart.data.datasets.data = []; // old
renderChart.data.datasets[0].data = []; // new
for (subject in classes) {
console.log('adding: ' + classes[subject].count)
// renderChart.data.datasets.data.push(classes[subject].count); // old
renderChart.data.datasets[0].data.push(classes[subject].count); // new
}
renderChart.update();
}

Related

Netlify CMS custom widget not working with Map?

I've been trying to create a custom widget for Netlify CMS to allow for key-value pairs to be inserted. However there are a few things going wrong, I'm thinking they might be related so I'm making a single question about them.
This is my first custom widget, and I’m mostly basing this on the official tutorial: https://www.netlifycms.org/docs/custom-widgets/
I’m using a Map as the value, but when I add a new element to the map, and then call the onChange(value) callback, nothing seems to happen. However, if I change it to onChange(new Map(value)) it does update. It seems that the onChange callback requires a new object?
Secondly, the value doesn’t seem to be actually saved. When I fill in other widgets and refresh the page, it then asks to restore the previous values. However it doesn’t restore the map, while restores the other values just fine.
And lastly, I get uncaught exception: Object like a second after I change anything to the map. My guess is that Netlify CMS is trying to save the map (debouncing it for a second so it doesn’t save every letter I type), but fails and throws that exception. That would explain the previous problem (the non-saving one).
My complete code for the custom widget currently is:
var IngredientsControl = createClass({
getDefaultProps: function () {
return {
value: new Map()
};
},
addElement: function (e) {
var value = this.props.value;
value.set("id", "Description");
//is.props.onChange(value);
this.props.onChange(new Map(value));
},
handleIdChange: function (oldId, newId) {
console.log(oldId, newId);
var value = this.props.value;
var description = value.get(oldId);
value.delete(oldId);
value.set(newId, description);
//this.props.onChange(value);
this.props.onChange(new Map(value));
},
handleDescriptionChange: function (id, description) {
console.log(id, description);
var value = this.props.value;
value.set(id.toLowerCase(), description);
//this.props.onChange(value);
this.props.onChange(new Map(value));
},
render: function () {
var value = this.props.value;
var handleIdChange = this.handleIdChange;
var handleDescriptionChange = this.handleDescriptionChange;
var items = [];
for (var [id, description] of value) {
var li = h('li', {},
h('input', { type: 'text', value: id, onChange: function (e) { handleIdChange(id, e.target.value); } }),
h('input', { type: 'text', value: description, onChange: function (e) { handleDescriptionChange(id, e.target.value); } })
);
items.push(li);
}
return h('div', { className: this.props.classNameWrapper },
h('input', {
type: 'button',
value: "Add element",
onClick: this.addElement
}),
h('ul', {}, items)
)
}
});
var IngredientsPreview = createClass({
render: function () {
var value = this.props.value;
var items = [];
for (var [id, description] of value) {
var li = h('li', {},
h('span', {}, id),
h('span', {}, ": "),
h('span', {}, description)
);
items.push(li);
}
return h('ul', {}, items);
}
});
CMS.registerWidget('ingredients', IngredientsControl, IngredientsPreview);
What am I doing wrong?
Thanks!
I solved this by using immutable-js's map: https://github.com/immutable-js/immutable-js

Global loaded data in VueJs is occasionally null

I'm new to VueJs and currently trying to load some data only once and make it globally available to all vue components. What would be the best way to achieve this?
I'm a little bit stuck because the global variables occasionally seem to become null and I can't figure out why.
In my main.js I make three global Vue instance variables:
let globalData = new Vue({
data: {
$serviceDiscoveryUrl: 'http://localhost:40000/api/v1',
$serviceCollection: null,
$clientConfiguration: null
}
});
Vue.mixin({
computed: {
$serviceDiscoveryUrl: {
get: function () { return globalData.$data.$serviceDiscoveryUrl },
set: function (newUrl) { globalData.$data.$serviceDiscoveryUrl = newUrl; }
},
$serviceCollection: {
get: function () { return globalData.$data.$serviceCollection },
set: function (newCollection) { globalData.$data.$serviceCollection = newCollection; }
},
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) { globalData.$data.$clientConfiguration = newConfiguration; }
}
}
})
and in my App.vue component I load all the data:
<script>
export default {
name: 'app',
data: function () {
return {
isLoading: true,
isError: false
};
},
methods: {
loadAllData: function () {
this.$axios.get(this.$serviceDiscoveryUrl)
.then(
response => {
this.$serviceCollection = response.data;
let configurationService = this.$serviceCollection.services.find(obj => obj.key == "ProcessConfigurationService");
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
})
}
},
created: function m() {
this.loadAllData();
}
}
</script>
But when I try to access the $clientConfiguration it seems to be null from time to time and I can't figure out why. For example when I try to build the navigation sidebar:
beforeMount: function () {
let $ = JQuery;
let clients = [];
if (this.$clientConfiguration === null)
console.error("client config is <null>");
$.each(this.$clientConfiguration, function (key, clientValue) {
let processes = [];
$.each(clientValue.processConfigurations, function (k, processValue) {
processes.push(
{
name: processValue.name,
url: '/process/' + processValue.id,
icon: 'fal fa-project-diagram'
});
});
clients.push(
{
name: clientValue.name,
url: '/client/' + clientValue.id,
icon: 'fal fa-building',
children: processes
});
});
this.nav.find(obj => obj.name == 'Processes').children = clients;
The most likely cause is that the null is just the initial value. Loading the data is asynchronous so you'll need to wait for loading to finish before trying to create any components that rely on that data.
You have an isLoading flag, which I would guess is your attempt to wait for loading to complete before showing any components (maybe via a suitable v-if). However, it currently only waits for the first request and not the second. So this:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
would need to be:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
this.isLoading = false;
}
);
If it isn't that initial value that's the problem then you need to figure out what is setting it to null. That should be prety easy, just put a debugger statement in your setter:
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) {
if (!newConfiguration) {
debugger;
}
globalData.$data.$clientConfiguration = newConfiguration;
}
}
Beyond the problem with the null, if you're using Vue 2.6+ I would suggest taking a look at Vue.observable, which is a simpler way of creating a reactive object than creating a new Vue instance.
Personally I would probably implement all of this by putting a reactive object on Vue.prototype rather than using a global mixin. That assumes that you even need the object to be reactive, if you don't then this is all somewhat more complicated than it needs to be.

Vuejs chart not displaying after page reload

I created a chart using vue-chartjs which renders some data from a JSON endpoint.
The problem is that the chart is not rendered after the page reloads.
This is the HTML that calls the chart component.
<absentee-chart v-if="isLoaded==true" :chart-data="this.chartData"></absentee-chart>
This is the absenteeChart.js file that renders the chart data.
import { Bar, mixins } from 'vue-chartjs'
export default {
extends: Bar,
mixins: [mixins.reactiveProp],
data: () => ({
options: {
responsive: true,
maintainAspectRatio: false
}
}),
mounted () {
this.renderChart(this.chartData, this.options)
}
}
And finally my .vue file.
created () {
axios
.get(constants.baseURL + "absentee-reports/graph", auth.getAuthHeader())
.then(response => {
this.graph = response.data;
var males = {
data: []
};
var females = {
data: []
};
var len = this.graph.length;
for (var i = 0; i < len; i++) {
if (this.graph[i].year == "2009") {
this.labels.push(this.graph[i].month);
//push to males
this.datasets[0].data.push(this.graph[i].males);
//push to females
this.datasets[1].data.push(this.graph[i].females);
}
}
this.isLoaded = true;
this.chartData.labels = this.labels;
this.chartData.datasets = this.datasets;
});
}
UPDATE: The chart appears after I resize my browser page.
The solution to this was separating all my chart logic from my .vue file and omitting the use of mixins and reactiveProp. From what I could tell, this issue lies deep within the core of chart.js
Hope this helps somebody.
Cheers :)

Set object in data from a method in VUE.js

I have been stuck with this issues for 2 hours now and I really can't seem to get it work.
const app = new Vue({
el: '#book-search',
data: {
searchInput: 'a',
books: {},
},
methods: {
foo: function () {
axios.get('https://www.googleapis.com/books/v1/volumes', {
params: {
q: this.searchInput
}
})
.then(function (response) {
var items = response.data.items
for (i = 0; i < items.length; i++) {
var item = items[i].volumeInfo;
Vue.set(this.books[i], 'title', item.title);
}
})
.catch(function (error) {
console.log(error);
});
}
}
});
When I initiate search and the API call I want the values to be passed to data so the final structure looks similar to the one below.
data: {
searchInput: '',
books: {
"0": {
title: "Book 1"
},
"1": {
title: "Book 2"
}
},
Currently I get Cannot read property '0' of undefined.
Problem lies here:
Vue.set(this.books[i], 'title', item.title);
You are inside the callback context and the value of this is not the Vue object as you might expect it to be. One way to solve this is to save the value of this beforehand and use it in the callback function.
Also instead of using Vue.set(), try updating the books object directly.
const app = new Vue({
el: '#book-search',
data: {
searchInput: 'a',
books: {},
},
methods: {
foo: function () {
var self = this;
//--^^^^^^^^^^^^ Save this
axios.get('https://www.googleapis.com/books/v1/volumes', {
params: {
q: self.searchInput
//-^^^^--- use self instead of this
}
})
.then(function (response) {
var items = response.data.items
var books = {};
for (i = 0; i < items.length; i++) {
var item = items[i].volumeInfo;
books[i] = { 'title' : item.title };
}
self.books = books;
})
.catch(function (error) {
console.log(error);
});
}
}
});
Or if you want to use Vue.set() then use this:
Vue.set(self.books, i, {
'title': item.title
});
Hope this helps.
yep, the problem is about context. "this" returns not what you expect it to return.
you can use
let self = this;
or you can use bind
function(){this.method}.bind(this);
the second method is better.
Also google something like "how to define context in js", "bind call apply js" - it will help you to understand what is going wrong.
// update component's data with some object's fields
// bad idea, use at your own risk
Object
.keys(patch)
.forEach(key => this.$data[key] = patch[key])

Highcharts data will not load using angular directive

I'm trying to use high charts via angular to take advantage of double binding. I'm having an issue rendering the data, the graph works but the data is not showing up in the chart. When I check the DOM console I can get the array but for some reason its not showing up in the graph.
cpvmPartners = [];
cpvmPlannedCpm = [];
actualCpm = [];
graphData = [];
cpvm = [];
plannedPrepared = [];
getData = function(){
$.getJSON('/cpvmdata', function(data) {
for(k in data){
if(data[k]['audience'] == 'GCM'){
graphData.push([data[k]['partner'],data[k]['plannedcpm']])
actualCpm.push(Math.round((data[k]['mediacost']/data[k]['impressions']*1000)))
cpvmPlannedCpm.push(data[k]['plannedcpm'])
cpvmPartners.push(data[k]['partner'])
}
}
});
}
prepareData = function(){
for(var i = 0; i < actualCpm.length; i++) {
actualPrepared.push({name: "CPM", data: actualCpm[i]})
plannedPrepared.push({name: "Planned CPM", data: cpvmPlannedCpm[i]})
}
}
myApp = angular.module('main', ['highcharts-ng']);
myApp.controller('graphController', function ($scope) {
getData();
prepareData();
$scope.highchartsNG = {
options: {
chart: {
type: 'bar'
}
},
series: [{
data: actualCpm
}],
title: {
text: 'Hello'
},
loading: false
}
});
So the getData() function you call in the angular controller is asynchronous:
By the time you have gotten the data, you have already made your chart in $scope.highChartNg
That is why you can see your data the console but you don't actually set it to the actualCpm by the time angular is done. To fix this you need to create the chart IN your $.getJSON function like so:
var options = {
chart: {
renderTo: 'container',
type: 'spline'
},
series: [{}]
};
$.getJSON('data.json', function(data) {
options.series[0].data = data;
var chart = new Highcharts.Chart(options);
});
You can see more here: http://www.highcharts.com/docs/working-with-data/custom-preprocessing
Easier just to use
$http.get
Angular service.
Blending jQuery and Angular is troublesome for scoping.
$http.get('cpvmdata')
.then(function(response){
$scope.output = response.data;
for(k in $scope.output){
if($scope.output[k]['audience'] == 'GCM'){
$scope.planned.push($scope.output[k]['plannedcpm'])
$scope.partners.push($scope.output[k]['partner'])
$scope.cpm.push(Math.round(($scope.output[k]['mediacost']/$scope.output[k]['impressions']*1000)))
}
}
});

Categories