I have a JSON string something like:
[
{
"Year":2018,
"Ov":1000,
"PD":2000,
"OL":3000
},
{
"Year":2017,
"Ov":4000,
"PD":5000,
"OL":6000
},
{
"Year":2012,
"Ov":600,
"PD":400,
"OL":200
},
{
"Year":2013,
"Ov":700,
"PD":500,
"OL":200
},
{
"Year":2014,
"Ov":700,
"PD":400,
"OL":300
},
{
"Year":2015,
"Ov":700,
"PD":300,
"OL":400
},
{
"Year":2016,
"Ov":500,
"PD":300,
"OL":200
},
{
"Year":2017,
"Ov":4000,
"PD":5000,
"OL":6000
},
{
"Year":2018,
"Ov":1000,
"PD":2000,
"OL":3000
}
]
My Html Code:
google.charts.load('current', {'packages':['bar']});
google.charts.setOnLoadCallback(drawChart);
var array = #Html.Raw(Json.Encode(Proj.Dash.JsonString));
alert("JSON.stringify " + array);
var dataArr = array.data;
function drawChart() {
var dataArray = [['Year', 'Ov', 'PD', 'OL']];
for (var i in dataArr) {
dataArray.push([dataArr[i].Year,dataArr[i].Ov,dataArr[i].PD,dataArr[i].OL]);
}
alert(dataArray.length);
var data = google.visualization.arrayToDataTable([dataArray
]);
var options = {
chart: {
title: '',
subtitle: '',
},
bars: 'vertical',
vAxis: { format: 'decimal' },
height: 500,
colors: ['#333333', '#3490e9', '#ff5a00'],
legend: {
position: 'none'
}
};
var chart = new google.charts.Bar(document.getElementById('columnchart_material'));
chart.draw(data, google.charts.Bar.convertOptions(options));
}
When I run the code it returns a null with this error:
Invalid data table format: must have at least 2 columns.
What do I missed up?
Any suggestion is much appreciated.
The problem seems coming from this line:
var dataArr = array.data;
You're trying to access data property which is not found inside the JSON string, hence dataArr returns undefined and causing dataArray.length returns 1, which throwing exception as mentioned in question because only column headers exist inside the array passed into arrayToDataTable() function.
To pass the data properly, you need to use JSON.parse() so that the JSON string returned as array:
var dataArr = JSON.parse(array);
This will produce array with which contains column headers and values like this example:
[["Year", "Ov", "PD", "OL"],
[2018, 1000, 2000, 3000],
[2017, 4000, 5000, 6000],
[2012, 600, 400, 200],
[2013, 700, 500, 200],
[2014, 700, 400, 300],
[2015, 700, 300, 400],
[2016, 500, 300, 200],
[2017, 4000, 5000, 6000],
[2018, 1000, 2000, 3000]]
Update:
The second problem is you're defining [dataArray] which actually creates nested array, this causing wg property in data has zero length array and the chart not properly displayed, hence you should remove outer array square brackets:
var data = google.visualization.arrayToDataTable(dataArray);
And the third problem is that you're duplicating data groups (i.e. [2017, 4000, 5000, 6000] in 2 different series). You should remove duplicated part using custom function.
Therefore, your code should be look like example below:
var array = #Html.Raw(Json.Encode(Proj.Dash.JsonString));
alert("JSON.stringify " + array);
var dataArr = JSON.parse(array);
function drawChart() {
var dataArray = [['Year', 'Ov', 'PD', 'OL']];
for (var i in dataArr) {
dataArray.push([dataArr[i].Year, dataArr[i].Ov, dataArr[i].PD, dataArr[i].OL]);
}
alert(dataArray.length);
var data = google.visualization.arrayToDataTable(dataArray);
var options = {
chart: {
title: '',
subtitle: '',
},
bars: 'vertical',
hAxis: { format: '#' }, // optional
vAxis: { format: 'decimal' },
height: 500,
colors: ['#333333', '#3490e9', '#ff5a00'],
legend: {
position: 'none'
}
};
var chart = new google.charts.Bar(document.getElementById('columnchart_material'));
chart.draw(data, google.charts.Bar.convertOptions(options));
}
Additional reference: arrayToDataTable() function
Working example: .NET Fiddle
Are you using your script in same html file or there is a separate script file. Please clarify. If its a separate file try loading your script in same html.
Related
I'm working on a small HTML application for my website that does some simulations and plots it to a graph (using Google Charts). All of the data will originate in the JavaScript code on the page (i.e. I'm not trying to pull in data from a database or anything like that). For this reason, I would like to have access to the data table from other functions so the data can be updated when a new simulation is run.
What I'm running into is that if I build a data table (and data view) inside of the drawChart() function, everything works fine. See this jsfiddle or the following code:
//Google charts stuff
google.charts.load('current', { 'packages': ['line', 'corechart'] });
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var forceChartDiv = document.getElementById('force_chart_div');
var sim_data = new google.visualization.DataTable();
sim_data.addColumn('number', 'Elapsed Time (sec)');
sim_data.addColumn('number', "Total Force");
sim_data.addColumn('number', "M1 Force(Each)");
sim_data.addRows([
[0.0, -.5, 5.7],
[0.1, .4, 8.7],
[0.2, .5, 12]
]);
var forceDataView = new google.visualization.DataView(sim_data);
forceDataView.setColumns([0, 1, 2]);
var forceChartOptions = {
chart: {title: 'Simulation Results: Force'},
width: 900,
height: 500,
series: {
// Gives each series an axis name that matches the Y-axis below.
0: { axis: 'Total' },
1: { axis: 'Individual' }
},
axes: {
// Adds labels to each axis; they don't have to match the axis names.
y: {
Total: { label: 'Total Force (Newtons)'},
Individual: { label: 'Per-Motor Force (Newtons)'}
}
}
};
var forceChart = new google.charts.Line(forceChartDiv);
forceChart.draw(forceDataView, google.charts.Line.convertOptions(forceChartOptions));
}
But if I move the code for the creation of the data table and data view outside of the function scope, it doesn't work. See this jsfiddle or the following code:
var sim_data;
var forceDataView;
//Google charts stuff
google.charts.load('current', { 'packages': ['line', 'corechart'] });
sim_data = new google.visualization.DataTable();
sim_data.addColumn('number', 'Elapsed Time (sec)');
sim_data.addColumn('number', "Total Force");
sim_data.addColumn('number', "M1 Force(Each)");
sim_data.addRows([
[0.0, -0.5, 5.7],
[0.1, 0.4, 8.7],
[0.2, 0.5, 12]
]);
forceDataView = new google.visualization.DataView(sim_data);
forceDataView.setColumns([0, 1, 2]);
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var forceChartDiv = document.getElementById('force_chart_div');
var forceChartOptions = {
chart: {title: 'Simulation Results: Force'},
width: 900,
height: 500,
series: {
// Gives each series an axis name that matches the Y-axis below.
0: { axis: 'Total' },
1: { axis: 'Individual' }
},
axes: {
// Adds labels to each axis; they don't have to match the axis names.
y: {
Total: { label: 'Total Force (Newtons)'},
Individual: { label: 'Per-Motor Force (Newtons)'}
}
}
};
var forceChart = new google.charts.Line(forceChartDiv);
forceChart.draw(forceDataView, google.charts.Line.convertOptions(forceChartOptions));
}
Both of these examples use the following HTML:
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<div id="force_chart_div"></div>
I thought it might have something to do with the execution order of the callback function. But putting it in different spots in the code doesn't seem to change anything. In my full project, I went so far as to add a button that called the drawChart() function just to check, but that didn't help either.
Depending on where I put the callback function call, I'll get a red "Data Table is not Defined" alert showing up where the chart is supposed to be on the webpage. That pretty much tells me what I already suspected, but I don't know how to fix it. Any help would be appreciated. I'm a huge JS noob, by the way, so go easy on me.
your instinct was correct, you must wait on the callback to finish,
before using the google.visualization or google.charts namespaces.
it has to do more with timing, than placement of the code.
instead of using the callback statement, we can use the promise that the load statement returns.
as in the following snippet...
var sim_data;
var forceDataView;
//Google charts stuff
google.charts.load('current', {
packages: ['line', 'corechart']
}).then(function () {
sim_data = new google.visualization.DataTable();
sim_data.addColumn('number', 'Elapsed Time (sec)');
sim_data.addColumn('number', "Total Force");
sim_data.addColumn('number', "M1 Force(Each)");
sim_data.addRows([
[0.0, -0.5, 5.7],
[0.1, 0.4, 8.7],
[0.2, 0.5, 12]
]);
forceDataView = new google.visualization.DataView(sim_data);
forceDataView.setColumns([0, 1, 2]);
});
function drawChart() {
var forceChartDiv = document.getElementById('force_chart_div');
var forceChartOptions = {
chart: {title: 'Simulation Results: Force'},
width: 900,
height: 500,
series: {
// Gives each series an axis name that matches the Y-axis below.
0: { axis: 'Total' },
1: { axis: 'Individual' }
},
axes: {
// Adds labels to each axis; they don't have to match the axis names.
y: {
Total: { label: 'Total Force (Newtons)'},
Individual: { label: 'Per-Motor Force (Newtons)'}
}
}
};
var forceChart = new google.charts.Line(forceChartDiv);
forceChart.draw(forceDataView, google.charts.Line.convertOptions(forceChartOptions));
}
I am attempting to take the example produced by Highcharts here http://www.highcharts.com/maps/demo/color-axis and substitute the data loaded by the $.getJson with a local JSON file called 'testdata1.json'.
The code I've modified below produces no errors yet the map does not render. I think it's because the testdata1.json is loaded late, after the javascript is executed. If so, is there a better way I should be doing this -- perhaps waiting for the data to load before executing the JS file? I attempted to do this by placing a
$(document).ready(
in front of the function but it didn't work. Any thoughts are greatly appreciated, I think it's something relatively minor that is just escaping me.
Thank you.
$(function () {
// Map options
var options = {
chart : {
renderTo: '#map',
borderWidth : 1
},
title : {
text : 'US population density (/km²)'
},
legend: {
layout: 'horizontal',
borderWidth: 0,
backgroundColor: 'rgba(255,255,255,0.85)',
floating: true,
verticalAlign: 'top',
y: 25
},
mapNavigation: {
enabled: true
},
colorAxis: {
min: 1,
type: 'logarithmic',
minColor: '#EEEEFF',
maxColor: '#000022',
stops: [
[0, '#EFEFFF'],
[0.67, '#4444FF'],
[1, '#000022']
]
},
series : [{
animation: {
duration: 1000
},
mapData: Highcharts.maps['countries/us/us-all'],
joinBy: ['postal-code', 'code'],
dataLabels: {
enabled: true,
color: 'white',
format: '{point.code}'
},
name: 'Population density',
tooltip: {
pointFormat: '{point.code}: {point.value}/km²'
}
}]
};
$.getJSON('static/data/testdata1.json', function (data) {
// Make codes uppercase to match the map data
$.each(data, function () {
this.code = this.code.toUpperCase();
});
options.series.data= data;
var chart = new Highcharts.Chart(options)
});
});
You have three problems. Here's a fiddle based on their sample that uses your approach, but still uses their data, and works: http://jsfiddle.net/g29k24vw/1/
Here are the important parts:
chart : {
renderTo: 'container',
borderWidth : 1,
type: 'map'
},
And:
$.getJSON('http://www.highcharts.com/samples/data/jsonp.php?filename=us-population-density.json&callback=?', function (data) {
// Make codes uppercase to match the map data
$.each(data, function () {
this.code = this.code.toUpperCase();
});
options.series[0].data= data;
var chart = new Highcharts.Chart(options);
});
Note the differences here:
You need to specify the chart type in options if you're going to instantiate the chart object directly instead of using the jQuery helper.
renderTo doesn't want a hash in front of the element name.
options.series[0].data, not options.series.data...series is actually an array of objects.
I'm using Google Visulaization API to render a chart showing a single row with multiple values, like this:
with the following code:
var data = google.visualization.arrayToDataTable([
['', '0%', '25%', '50%', '75%', '100%', {role: 'annotation'}],
['Mood', 3, 7, 20, 25, 45, '']
]);
var options = {
isStacked: true,
hAxis: { minValue: 0 }
}
var chart = new google.visualization.BarChart(document.getElementById('mood_chart'));
chart.draw(data, options);
Now I would like to customize the colors and add a label to every piece of the row.
If I do this:
var data = google.visualization.arrayToDataTable([
['', '0%', '25%', '50%', '75%', '100%', {role: 'annotation'}, {role: 'style'}],
['Mood', 3, 7, 20, 25, 45, 'ABC', '#f50']
]);
Then this only applies to the last value: (note the legend has also the wrong color)
And if I put an array of strings instead of a single label an error is given.
Is it possible to do what I am trying to do? How?
I have an interactive demo for this answer here.
The simple answer is that the annotation and style columns of the data table apply to the data column before them. Add annotation columns after each data column to add annotations to each value.
This is also why your colors differ from your legend. To apply colors to a series you do that from your options object. The style role of the data table affects an individual value without changing the option for the whole series.
var data = google.visualization.arrayToDataTable([
['', '0%', {role: 'annotation'}, '25%', {role: 'annotation'},
'50%', {role: 'annotation'}, '75%', {role: 'annotation'},
'100%', {role: 'annotation'}],
['Mood', 3, 'ABC', 7, 'DEF', 20, 'GHI', 25, 'JKL', 25, 'MNO']
]);
For assigning colors manually your options will look something like this:
var options = {
isStacked: true,
hAxis: {
minValue: 0
},
series: {
0:{color:'#222'},
1:{color:'#555'},
2:{color:'#888'},
3:{color:'#AAA'},
4:{color:'#EEE'}
}
};
My colors were just random greys because I can calculate the colors in my head that way. The series index is independant of the data table column, so it just assigns indexes to columns with data values.
In case someone would like to add the color series from json, i found a way to do that. It took quite some time to figure it out for me so here goes if anyone can use it.
Problem is that arrays of colors cannot be of type Array but must be object with objects.
Fetching colors from Controller (ASP.Net MVC):
/*array for colors to be convertet later*/
var mycolors = new Array();
function getColors() {
var postData = {};
postData.Ids = {1,2,3};
$.ajax({
type: "POST",
url: "#Url.Action("GoogleStackedBarchartColors", "MyController")",
data: postData,
dataType: "text",
success: function (d, status, request) {
/*some logging*/
console.log(d);
console.log($.parseJSON(d));
var colors = $.parseJSON(d);
/*I change the Type to object*/
mycolors.__proto__ = Object; /*The magic part*/
var i = 0;
colors.forEach(function (element) {
mycolors[i] = element;
i = i + 1;
});
And then in properties of the chart
var options = {
width: '100%',
height: 500,
bar: { groupWidth: '75%' },
bars: 'vertical',
legend: { 'position': 'top', maxLines: 3 },
isStacked: 'true',
series: mycolors /*Here we have the object containing colors*/
};
I have an api giving me back multiple arrays I have to place on a c3 line chart. I seem to be able to plot on just fine, but if I start to pass in multiples it starts having a fit. I've done some searching, but from what I've seen mine needs to be more dynamic than calling out specific variable names in the data because I don't know how many I will have at any given time.
My code kind of looks like this
array1 = [];
array2 = [];
array 3 = [];
Data = ?;
c3.generate({
bindto: '.timeline',
data:{
columns: [
Data,
],
axes: {
data2: 'x' // ADD
}
},
axis: {
y: {
label: { // ADD
text: 'Waiting',
position: 'outer-middle'
}
},
x: {
type: 'category',
categories: hour,
show: true, // ADD
label: { // ADD
text: '',
position: 'outer-center'
}
}
}
});
I've seen around to do something like this
columns: [
Data1,
Data2,
Data3
],
Is there any way I can put those arrays into one variable to pass into the c3 graph? I've tried making an array of arrays and passing that in. I've also tried putting them into an object, but that just blew it up too. Any help would be appreciated!
Should be pretty straight-forward to get the three arrays into an array of arrays, which is the columns array.
var array1 = ['data1', 30, 200, 100, 400, 150, 250];
var array2 = ['data2', 50, 20, 10, 40, 15, 25];
var array3 = ['data3', 230, 190, 300, 500, 300, 400];
var Data = [array1, array2, array3];
c3.generate({
bindto: '.timeline',
data: {
columns: Data
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.10/c3.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div class='timeline' />
I've been trying to accomplish a chart on jqPlot using the stacked bars (imho, there would be a better way to graphically display the data above, but thats not up to me.)
so heres the dummy data:
var data = {
"Network 1": {
"Avg. Speed": 10000,
"D/S": 10000,
"U/S": 10000
},
"Network 2": {
"Avg. Speed": 15000,
"D/S": 15000,
"U/S": 15000
},
"Network 3": {
"Avg. Speed": 20000,
"D/S": 20000,
"U/S": 20000
}
};
Network's would be the X axis and the Avg. Speed/ DS and US would be the facts so each network would have, in this case 3 stacked bars with their data.
I've been passing jqplot data like this just to check the results but, the graph is blank:
plot2 = $.jqplot('divGraphNetwork', [], {
animate: !$.jqplot.use_excanvas,
stackSeries: true,
dataRenderer: data,
seriesDefaults: {
renderer: $.jqplot.BarRenderer,
rendererOptions: {
barPadding: 2,
barMargin: 0,
barDirection: 'vertical',
barWidth: 20
},
pointLabels: {
//location: 'se',
ypadding: 0
}
},
title: {
text: 'Network Information', // title for the plot,
show: true,
},
legend: {
show: false,
location: 'e',
placement: 'outside'
},
axes: {
xaxis: {
renderer: $.jqplot.CategoryAxisRenderer
},
yaxis: {
min:0,
autoscale: true
}
}
});
dataRenderer should be a function not a JSON object. This function should take the JSON object and convert it to a 2D array.
The 2D array should be in the following format:
[[Network1 AV/Speed, Network2 AV/Speed, Network3 AV/Speed],
[Network1 D/S, Network2 D/S, Network3 D/S],
[Network1 U/S, Network2 U/S, Network3 U/S]]
I've created a Fiddle here to demonstrate. I've added a dataRenderer() function as follows:
function dataRenderer() {
var result = [];
var avgSpeed = [];
var ds = [];
var us = [];
// Loop over each network
for (var network in data) {
// Add each of the stats for the network into the corresponding array
avgSpeed.push(data[network]["Avg. Speed"]);
ds.push(data[network]["D/S"]);
us.push(data[network]["U/S"]);
}
// Add the av speed, DS and US arrays to the result array creating a 2D array
result.push(avgSpeed);
result.push(ds);
result.push(us)
return result;
}
I've then modified the dataRenderer option to point to the function instead of the JSON object data:
var plot2 = $.jqplot('divGraphNetwork', [], {
...
dataRenderer: dataRenderer,
....
});