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' />
Related
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.
I am trying to set custom colors in a c3.js timeseries chart following this example. The first element of each array is used to identify the dataset, so if I have an array:
var datatest1 = ['data1', 30, 200, 100, 400, 150, 250];
the color property can be accessed like this:
colors: {data1:'#0000'}
or:
colors: {'data1':'#0000'}
However, if I use the first element of the array to access them:
var data1id = datatest1[0];
and then:
colors: {data1id:'#0000'}
It fails. Not sure what I may doing wrong as I get no feedback in the browser...
Here is a working example:
var axis = ['x', '2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04', '2013-01-05', '2013-01-06'];
var datatest2 = ['data2', 130, 340, 200, 500, 250, 350];
var datatest1 = ['data1', 30, 200, 100, 400, 150, 250];
var data1id = datatest1[0];
var data2id = datatest2[0];
var chart = c3.generate({
data: {
x: 'x',
columns: [
axis,
datatest1,
datatest2
],
colors: {
//data1: '#0000',
//data2: '#0000'
datatest1: '#0000',
datatest2: '#0000'
}
},
axis: {
x: {
type: 'timeseries',
tick: {
format: '%Y-%m-%d'
}
}
}
});
----- EDIT
I am doing this because the data (including the identifier) is generated dynamically. Thanks
You can dynamically create the colors object like this.
var colors = {};
colors[datatest1[0]] = '#0000';
colors[datatest2[0]] = '#0000';
then set it in the graph like this
var chart = c3.generate({
data: {
x: 'x',
columns: [
axis,
datatest1,
datatest2
],
colors: colors //set colors object created above
},
axis: {
x: {
type: 'timeseries',
tick: {
format: '%Y-%m-%d'
}
}
}
});
working code here
I'm using C3.js library to create a stacked bar chart (my current code is in jsfiddle on the bottom). The problem is that by default the columns are... stacked. Since I need to create columns with min, average and max values, I'd like the values to rather contain each other, not stack. E.g. if I have min = 10, average = 50 and max = 100, I'd like the bar to be of the height 100, not 160. Is there any built in way to support such behavior?
My current code:
<div id="chart"></div>
<script>
var chart = c3.generate({
bindTo: '#chart',
data: {
columns: [
['min', 10, 25, 15],
['avg', 50, 33, 51],
['max', 100, 75, 200]
],
type: 'bar',
groups: [
['min', 'avg', 'max']
]
}
});
</script>
Here is a jsfiddle with my code:
https://jsfiddle.net/gguej6n0/
Right, I'm adding this as another answer as it's completely different plus if I changed my original answer the comments would make no sense..
This time I'm taking advantage of err... feature (bug?) found in c3 that caused another user to unintentionally get the effect that you wanted.
c3.js generate a stacked bar from JSON payload
Basically, if you supply the data as json you can get the effect you want if you supply each datum as a separate point
e.g. doing this will overplot min and max on the same column even if they are meant to be stacked
[{ "x-axis": "0",
"min": 30
},
{ "x-axis": "0",
"max": 40
}],
Whereas, this way will stack them:
[{ "x-axis": "0",
"min": 30,
"max": 40
}],
So what we need is a routine to turn the original column-based data into a json object where every datum is parcelled up separately:
http://jsfiddle.net/gvn3y0q6/5/
var data = [
['min', 10, 25, 15, 12],
['avg', 50, 33, 51, 24],
['max', 100, 75, 200, 76]
];
var json = [];
data.forEach (function (datum) {
var series = datum[0];
for (var i = 1; i < datum.length; i++) {
var jdatum = {"x-axis": i-1};
jdatum[series] = datum[i];
json.push (jdatum);
}
});
var chart = c3.generate({
data: {
x: "x-axis",
json:json,
keys: {
x: "x-axis",
value: ["max", "avg", "min"]
},
groups: [
['max', 'avg', 'min']
],
type: 'bar',
},
bar: {
width: {
ratio: 0.95
}
},
axis: {
x: {
padding: {
left: 0.5,
right: 0.5,
}
}
},
tooltip: {
grouped: true,
contents: function (d, defaultTitleFormat, defaultValueFormat, color) {
var data = this.api.data.shown().map (function(series) {
var matchArr = series.values.filter (function (datum) {
return datum.value != undefined && datum.x === d[0].x;
});
matchArr[0].name = series.id;
return matchArr[0];
});
return this.getTooltipContent(data, defaultTitleFormat, defaultValueFormat, color);
}
}
});
This time, hiding a series doesn't affect the other series. There's still some tooltip jiggery-pokery needed though otherwise we only get one of the values reported in the tooltip when hovering over one of the 'stacks'. It looks like the data includes a lot of empty value points, which leads me to think this is a bug of some sort I'm taking advantage of here..
So bearing in mind this might get fixed at some point in the future (or maybe left in, if someone points out it's useful for doing this sort of plot) - then this seems to do what you want
I'm not aware of anything built-in that will make the bars merge into one another as you describe, but could a side-by-side view help? If so, remove the grouping.
See https://jsfiddle.net/gguej6n0/1/
var chart = c3.generate({
bindTo: '#chart',
data: {
columns: [
['min', 10, 25, 15],
['avg', 50, 33, 51],
['max', 100, 75, 200]
],
type: 'bar'
},
bar: {
width: {
ratio: 0.3
}
}
});
I don't think c3 has an 'overplot' bars option, but you could massage your data...
Basically process the data beforehand so max is actually max - avg, and avg is actually avg - min
Then the tooltip routine restores the correct totals for showing to a user
Just be careful if you pass the data onto anything else and remember the data has been changed and restore it (or keep a copy)
https://jsfiddle.net/gguej6n0/5/
var data = [
['min', 10, 25, 15],
['avg', 50, 33, 51],
['max', 100, 75, 200]
];
for (var n = data.length-1; n > 0; n--) {
for (var m = 1; m < data[n].length; m++) {
data[n][m] -= data[n-1][m];
}
}
var chart = c3.generate({
bindTo: '#chart',
data: {
columns: data,
type: 'bar',
groups: [
['min', 'avg', 'max']
]
},
tooltip: {
contents: function (d, defaultTitleFormat, defaultValueFormat, color) {
var dc = d.map (function (dd) {
return {value: dd.value, x: dd.x, id: dd.id, name: dd.name, index: dd.index};
})
for (var n= 1; n < dc.length; n++) {
dc[n].value += dc[n-1].value;
}
return this.getTooltipContent(dc, defaultTitleFormat, defaultValueFormat, color);
}
}
});
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*/
};
If I have a C3JS grouped bar chart defined like the following, how can I get the segments to stay in the order I've defined them instead of in ascending order by value? By default C3 will order them as 5, 10, 40, but I want it to remain as 10, 40, 5.
c3.generate({
bindto: '.active-loads',
data: {
columns: [
['Picking up future', 10],
['Enroute', 40],
['Delivered', 5]
],
type: 'bar',
groups: [
['Picking up future', 'Enroute', 'Delivered']
],
onclick: function(d) {
console.debug(d);
}
},
axis: {
rotated: true,
x: {
show: false
}
}
});
EDIT
Turns out it's as easy as specifying order: null in the data property.
C3js documentation has page for this : http://c3js.org/samples/data_order.html
You can order your data in following way :
var chart = c3.generate({
data: {
columns: [
['data1', 130, 200, 320, 400, 530, 750],
['data2', -130, 10, 130, 200, 150, 250],
['data3', -130, -50, -10, -200, -250, -150]
],
type: 'bar',
groups: [
['data1', 'data2', 'data3']
],
order: 'desc' // stack order by sum of values descendantly.
// order: 'asc' // stack order by sum of values ascendantly.
// order: null // stack order by data definition.
},
grid: {
y: {
lines: [{value:0}]
}
}
});
Also detailed explanation here too : http://c3js.org/reference.html#data-order
You can specify your function too :)