I'm using c3 + d3 + javascript to create a line chart in a webpage. I managed to create a code that was working fine locally, but when I uploaded it to my server, the code stopped working. I explain the problem below:
Problem: c3.generate is not throwing errors when uploaded to server
JSFiddle: http://jsfiddle.net/xq6wmyvp/10/
var chart;
function initialize(path) {
try {
c3.generate({
bindto: '#chart',
data: {
x: 'label',
url: path,
type: 'line',
},
axis: {
x: {
type: 'categories',
label: {
text: 'days',
},
},
y: {
label: {
text: 'yield',
},
tick: {
format: d3.format(".2%")
}
}
},
});
} catch (err) {
return false;
}
return true;
}
var path1 = 'https://gist.githubusercontent.com/SamMorrowDrums/f571240047c0344a4af8/raw/433eae455570b64edcdc7ece39416b468b612f49/test.csv';
alert(initialize('blabla'));
Code explanation: the code (in the fiddle) has a function that initializes a chart with a line graph using some data. The path to the data is given as a variable to that function (which is called 'initialize(path)'). This function uses c3.generate to create the graph. If the data is not available or does not exist, c3.generate throws an error (this works locally, but not when uploaded to a server - this is the problem) and the function (initialize) returns false. If the data exists, the graph is loaded and 'initialize' returns true.
Problem Restated: after uploading the code to a server, the function 'initialize(path)' only returns 'true', even if the data is not available/non-existent.
I don't know how to solve this. Can you help me?
Thanks for reading!
(Github link to problem: https://github.com/masayuki0812/c3/issues/960)
As I stated in my comment, under the hood c3 is using a d3.xhr to retrieve the data. This is an async call meaning that it's outside of your try block.
Possible workarounds include:
1.) fix it in the c3 source code At line 1903, you see the error is being dropped.
2.) create a global error handler
3.) don't use c3's url option. Issue your own d3 xhr request (handle the error the proper way there) and if successful, then call c3.generate with the columns option. This is the way I'd do it.
d3.csv("path/to/file.csv", function(error, json) {
if (error){
// handle error properly
return;
}
c3.generate({
...
});
});
Related
I am using Chart.js 3.5 and Vue 3.
I was successfully able to draw a chart, and I am trying to trigger a data change, inside a Vue method. Unfortunately, I encounter the following issue: "Uncaught TypeError: Cannot set property 'fullSize' of undefined".
Edit2: Added a missed }. Code should now be runnable
MyChart.vue:
<template>
<canvas id="chartEl" width="400" height="400" ref="chartEl"></canvas>
<button #click="addData">Update Chart</button>
</template>
<script>
import Chart from 'chart.js/auto';
export default {
name: "Raw",
data() {
return {
chart: null
}
},
methods: {
createChart() {
this.chart= new Chart(this.$refs["chartEl"], {
type: 'doughnut',
data: {
labels: ['VueJs', 'EmberJs', 'ReactJs', 'AngularJs'],
datasets: [
{
backgroundColor: [
'#41B883',
'#E46651',
'#00D8FF',
'#DD1B16'
],
data: [100, 20, 80, 20]
}
]
},
options: {
plugins: {}
}
})
},
addData() {
const data = this.chart.data;
if (data.datasets.length > 0) {
data.labels.push('data #' + (data.labels.length + 1));
for (var index = 0; index < data.datasets.length; ++index) {
data.datasets[index].data.push(123);
}
// Edit2: added missed }
this.chart.update(); } // this line seems to cause the error}
}
},
mounted () {
this.createChart()
},
}
</script>
Edit1: Adding the following to the options makes the chart update successfully, but the error is still present and the animation does not work. The chart flickers and displays the final (updated) state. Other animations, such as hiding/showing arcs do not seem to be afected
options: {
responsive: true,
}
Edit3: Adding "maintainAspectRatio:false" option seems to again stop chart from updating (the above mentioned error is still present)
By walking through the debugger, the following function from 'chart.esm.js' seems to be called successfully a few times, and then error out on last call:
beforeUpdate(chart, _args, options) {
const title = map.get(chart); // this returns null, which will cause the next call to error with the above mentioned exception.
layouts.configure(chart, title, options);
title.options = options;
},
//////////////////////
configure(chart, item, options) {
item.fullSize = options.fullSize;
item.position = options.position;
item.weight = options.weight;
},
This may be a stale post but I just spent several hours wrestling with what seems like the same problem. Perhaps this will help you and/or future people with this issue:
Before assigning the Chart object as an attribute of your Vue component, call Object.seal(...) on it.
Eg:
const chartObj = new Chart(...);
Object.seal(chartObj);
this.chart = chartObj;
This is what worked for me. Vue aggressively mutates attributes of objects under its purview to add reactivity, and as near as I can tell, this prevents the internals of Chart from recognising those objects to retrieve their configurations from its internal mapping when needed. Object.seal prevents this by barring the object from having any new attributes added to it. I'm counting on Chart having added all the attributes it needs at init time - if I notice any weird behaviour from this I'll update this post.
1 year later, Alan's answer helps me too, but my code failed when calling chart.destroy().
So I searched and found what seems to be the "vue way" of handling it: markRaw, here is an example using options API:
import { markRaw } from 'vue'
// ...
export default {
// ...
beforeUnmount () {
if (this.chart) {
this.chart.destroy()
}
},
methods: {
createChart() {
const chart = new Chart(this.$refs["chartEl"], {
// ... your chart data and options
})
this.chart = markRaw(chart)
},
addData() {
// ... your update
this.chart.update()
},
},
}
I am calling some data in via an api for an ionic app I'm making. The data is being called asynchronously and I need to assign the data to different variables for use in a chart that gets presented to the user. I'm struggling to assign the data to a variable that I can then access from the function which creates the chart (I'm using chart.js). Initially I've been trying to grab a list of dates from the data for use as the X axis scale, just to get things working.
Been trying quite a few things and failing. I initially thought it was because my variable was block scoped, but now I think its an async issue. Been reading about promises for hours, but although I understand the concept I can't see away to apply it to my current code (presuming the issue is async! I'm a noob on a self teaching mission here).
So this the code which handles pulling in the data from the api
async getData() {
const loading = await this.loadingController.create({
message: 'Loading'
});
await loading.present();
this.api.getData()
.subscribe(res => {
console.log(res);
this.data1 = res[0];
loading.dismiss();
console.log(this.data1);
const datelabel = this.data1.result[1].date;
}, err => {
console.log(err);
loading.dismiss();
});
}
And this is the code which creates the chart
useAnotherOneWithWebpack() {
var ctx = (<any>document.getElementById('canvas-linechart')).getContext('2d');
console.log('GotData', this.datelabel); //just to see what data I've got here if any in the console
var chart = new Chart(ctx, {
// The type of chart we want to create
type: 'line',
// The data for our dataset
data: {
labels: this.datelabel,
datasets: [{
data: [86,114,106],
label: "Africa",
borderColor: "#3e95cd",
fill: false
}, {
data: [282,350,411],
label: "Asia",
borderColor: "#8e5ea2",
fill: false
}, {
data: [168,170,178],
label: "Europe",
borderColor: "#3cba9f",
fill: false
}
]
},
options: {
title: {
display: true,
text: 'World population per region (in millions)'
}
}
});
}
So I'm calling the datalabel variable against labels, but its displaying as undefined on the axis and in the console. I'm expecting to see three months (which are saved as strings in the variable). Tried all sorts now and its driving me a bit mad. I'm not even sure its an async issue, but from what I've done so far it feels like the issue.
Any help really appreciated!!
Not sure when/where you're calling the useAnotherOneWithWebpack() method but one issue from your code is that you're assigning some values to the local constant datelabel but not to the property from the component:
// The following line just creates a local const available only in that scope
const datelabel = this.data1.result[1].date;
Instead, you should be initializing the component's property:
this.datelabel = this.data1.result[1].date;
Keeping that in mind, please try the following:
async getData() {
const loading = await this.loadingController.create({
message: 'Loading'
});
await loading.present();
this.api.getData().subscribe(
res => {
// This line will be executed when getData finishes with a success response
console.log('Inside of the subscribe - success');
console.log(res);
this.data1 = res[0];
this.datelabel = this.data1.result[1].date;
// Now that the data is ready, you can build the chart
this.useAnotherOneWithWebpack();
loading.dismiss();
},
err => {
// This line will be executed when getData finishes with an error response
console.log('Inside of the subscribe - error');
console.log(err);
loading.dismiss();
});
// This line will be executed without waiting for the getData async method to be finished
console.log('Outside of the subscribe');
}
I'm following this guide to setup JsTree with lazy load using Ajax in my Laravel 5.5 app .
This is my controller: https://gist.github.com/aaronr0207/7fa0a38f40bfd2f728a15d655254f82d
My View:
https://gist.github.com/aaronr0207/f87720263e3d6026b04b00c08bae5cb2
My JsTree class is exactly the same I didn't make any change.
Actually I'm getting the following error on chrome's console:
d9973d3e-1476-4453-a013-9e9c8430bcba:1 Uncaught TypeError: Cannot read property 'children' of undefined
But when I dump the response to debug it (at the end of TreeViewController data method):
dd(response()->json($tree->build()));
It works...
My response looks like this (when I die-dump it):
Any idea? Thank you
EDIT1: If I return a simple json_encode($tree->build) there are no errors but it shows an empty tree... and the response looks like this:
EDIT2: got it! But now there are a new issue... All I did to solve it was change the url string with a callback:
$('#jstree').jstree({
'core': {
'data': {
'url': function (node) {
return '{!! route('tree.data') !!}' ;
},
'data': function (node) {
console.log(node);
return {'id': node.id};
}
}
}
});
But now when I fetch next level directories, if they have another directorie inside it fails without error:
Test1 content is the following:
If I delete test1/test2 folder, it works showing:
Same when I delete the txt file...What is happening now? Maybe this is a new question so I'll post my solution to the main problem and I'll accept it.
I suspect your named route is not working correctly. In your TreeController.php, change the route tree/route as follows:
Route::get('tree/route', 'TreeController#data')->name('tree.data');
got it! All I did to solve it was change the url string with a callback:
$('#jstree').jstree({
'core': {
'data': {
'url': function (node) {
return '{!! route('tree.data') !!}' ;
},
'data': function (node) {
console.log(node);
return {'id': node.id};
}
}
}
});
This could be caused by the escaping in the response. Can you dd($request->id) when the id is set?
I have a search bar docked onto my tree panel. When I write something and press enter an Ajax request fires off and returns the folder ids required to expand the tree up to the point of the folder requested. Inside the success config of the ajax.request I call the expand function of each node via getNodeById using a loop. However after the first expansion ExtJS fires itself an ajax request from the proxy to fetch the folder data (since it hasn't been loaded yet). Since AJAX is asynchronous the loop is faster than the server response and it tries to call the .expand() function of the node before the node itself has been loaded and gives an undefined error. How should I tackle this? I know that generally with AJAX you have to use callback functions for everything you want to run AFTER the request has been processed but I'm not really sure how to do this in this case...
Ext.define('treeStore',
{
extend : 'Ext.data.TreeStore',
alias: 'widget.treeStore',
autoLoad : false,
model : 'treeModel',
root : {
id: 0,
name : 'Root',
expanded : true,
loaded: true
},
proxy : {
type : 'ajax',
url : 'MyServlet',
reader : {
type : 'json',
root : 'children'
}
},
folderSort: true
});
Ext.define('Ext.tree.Panel',{
.
.
.
//Stuff about the tree panel etc.
dockedItems: {
xtype: 'textfield',
name: 'Search',
allowBlank: true,
enableKeys: true,
listeners: {
specialkey: function (txtField, e) {
if (e.getKey() == e.ENTER){
var searchValue = txtField.getValue();
Ext.Ajax.request({
url: 'MyServlet',
params: {
caseType: 'search',
value: searchValue
},
success: function(response) { //ATTENTION: When calling the .expand() the AJAX hasn't finished and cannot find the node.
response = Ext.decode(response.responseText);
var panel = txtField.up();
response.IDs.forEach(function(entry){
panel.getStore().getNodeById(entry.folderId).expand(); <-problem here
});
}
});
}
}
}
}
Often when writing my Ext apps, I find that even though things should fire in order, there are times where calling things immediately after an AJAX call is sometimes just too fast. Maybe the tree nodes have not rendered properly yet, or some other issue.
Try wrapping a delayed task around that code:
new Ext.util.DelayedTask(function()
{
response = Ext.decode(response.responseText);
var panel = txtField.up();
response.IDs.forEach(function(entry){
panel.getStore().getNodeById(entry.folderId).expand(); <-problem here
});
}, this).delay(100);
You'll have to work around those scope issues though. The "this" just before the delay function is your scope.
I'm trying to put highcharts into my angular app. I'm getting my data from Google Sheets and returning a promise object from the call to google. I then call the Highcharts.Chart() method with my options object.
I get the error below when making the call. I've tried to figure out what's going on but I am currently lost. I have a prototype that I do not use angular and the chart works great. When I go and add the line new Highcharts.Chart(options) I get the error below. I remove that line the error goes away.
Any thoughts/help would be great!
Error:
TypeError: undefined is not a function
at Object.Chart.init (/highcharts.src.js:11014:4)
at Object.Chart (/highcharts.src.js:10937:12)
at data.then.$scope.sheetdata (/js/controllers/controlChartCtrl.js:11:17)
at wrappedCallback (/angularjs/1.2.6/angular.js:10905:81)
at /angularjs/1.2.6/angular.js:10991:26
at Scope.$eval (/angularjs/1.2.6/angular.js:11906:28)
at Scope.$digest (/angularjs/1.2.6/angular.js:11734:31)
at Scope.$delegate.__proto__.$digest (<anonymous>:844:31)
at /angularjs/1.2.6/angular.js:11945:26
at completeOutstandingRequest (/angularjs/1.2.6/angular.js:4098:10)
Partial:
Features:
<div id="feature"></div>
Controller:
angular.module('controlChartCtrl', []).
controller('ControlChartCtrl', ['$scope', 'GoogleService', function($scope, GoogleService) {
var data = GoogleService.getData();
$scope.helloworld = "hello world!";
data.then(function (data) {
// create charts here
var options = getOptionsForChart('Feature', 'feature', data);
var chart = new Highcharts.Chart(options);
}, function (error) {
$scope.sheetdata = error;
});
var getOptionsForChart = function (title, div, data) {
return {
chart: {
renderTo: div,
type: 'line'
},
title: {
text: title + ' Control Chart'
},
xAxis: {
title: {
text: 'End Dates'
},
categories: data.endDates
},
yAxis: {
title: {
text: 'Lead Time'
}
},
series: [{
name: 'Lead Time',
data: data.leadTimes
}]
};
}
}]);
I resolved the issue. The solution is as follows.
Highcharts requires jQuery to function correctly. When I added the jquery.js file above the highcharts.js file the angular application started to work correctly.
Thanks for the feedback!
Highcharts does not require JQuery to work (the current answer is wrong), however you need to load the 'standalone framework' library first:
define([
'vnd/highcharts/standalone-framework',
'vnd/highcharts/highcharts',
], function(){
...
}
See #Sebastian's jsfiddle for a demo:
http://jsfiddle.net/highcharts/aEuGP/