Google Visualization group() aggregation function returning nulls - javascript

I have a column called someNumber which contains nulls and zeros. When applying google.visualization.data.group to this column I cannot get the zero to come through to the table, only the null.
The documentation for both .min and .max indicates nulls are ignored but I can't seem to return anything but the null.
How can I get the zero to come through to my table?
Thank you experts!
Working Example:
// Load the Visualization API and the corechart package.
google.charts.load('current', {
'packages': ['corechart', 'table', 'gauge', 'controls']
});
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(gChart0);
function gChart0() {
drawChart();
};
function drawChart() {
var result = [{
"calendarWeek": "2017-W30",
"clnCount": 1,
"someNumber": null //NOTE THIS IS SET TO NULL
}, {
"calendarWeek": "2017-W30",
"clnCount": 3,
"someNumber": 0 //NOTE THIS IS SET TO ZERO
}];
//Create DataTable
var data = new google.visualization.DataTable();
data.addColumn('string', 'Calendar Week');
data.addColumn('number', 'Count');
data.addColumn('number', 'Some Number');
var dataArray = [];
$.each(result, function(i, obj) {
dataArray.push([
obj.calendarWeek,
obj.clnCount,
obj.someNumber
]);
});
data.addRows(dataArray);
//grouping
var groupView = google.visualization.data.group(data,
[{
column: 0,
type: 'string'
}],
[{
column: 1,
aggregation: google.visualization.data.sum,
type: 'number'
},
{
column: 2,
aggregation: google.visualization.data.min,
type: 'number'
}
]);
//Options
var options = {};
// Instantiate and draw chart, passing in options.
var chart = new google.visualization.Table(document.getElementById('chart_div'));
chart.draw(groupView, options);
} //END function drawChart()
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

you can provide your own custom aggregation functions.
the aggregation function should accept an array of values as the only argument.
in the custom sum function, we replace null with zero
function customSum(values) {
var sum = 0;
values.forEach(function (value) {
sum += value || 0;
});
return sum;
}
in the custom min function, Math.min will return zero when both values are null
function customMin(values) {
var min = null;
values.forEach(function (value) {
min = Math.min(value, min);
});
return min;
}
see following working snippet...
google.charts.load('current', {
'packages': ['corechart', 'table', 'gauge', 'controls']
});
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(gChart0);
function gChart0() {
drawChart();
};
function drawChart() {
var result = [{
"calendarWeek": "2017-W30",
"clnCount": 1,
"someNumber": null //NOTE THIS IS SET TO NULL
}, {
"calendarWeek": "2017-W30",
"clnCount": 3,
"someNumber": 0 //NOTE THIS IS SET TO ZERO
}];
//Create DataTable
var data = new google.visualization.DataTable();
data.addColumn('string', 'Calendar Week');
data.addColumn('number', 'Count');
data.addColumn('number', 'Some Number');
var dataArray = [];
$.each(result, function(i, obj) {
dataArray.push([
obj.calendarWeek,
obj.clnCount,
obj.someNumber
]);
});
data.addRows(dataArray);
//grouping
var groupView = google.visualization.data.group(data,
[{
column: 0,
type: 'string'
}],
[{
column: 1,
aggregation: customSum,
type: 'number'
},
{
column: 2,
aggregation: customMin,
type: 'number'
}
]);
function customSum(values) {
var sum = 0;
values.forEach(function (value) {
sum += value || 0;
});
return sum;
}
function customMin(values) {
var min = null;
values.forEach(function (value) {
min = Math.min(value, min);
});
return min;
}
//Options
var options = {};
// Instantiate and draw chart, passing in options.
var chart = new google.visualization.Table(document.getElementById('chart_div'));
chart.draw(groupView, options);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.1/jquery.min.js"></script>
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

Tested a max version and it also works. Here's the code to share:
function customMax(values) {
var max = null;
values.forEach(function (value) {
max = Math.max(value, max);
});
return max;
}

Related

google line chart date formatting

I'd like to draw google line chart with trending however type mismatch occurred because of date format.
The below is my code.gs which to return the date value to string from spreadsheet in the first column.
I'd tried to put new Date() in JS to draw the line chart with trending however i don't know how to put this date value in datatable.
In the JS, the columns can be dynamically added which it works. But i could not solve the date problem so please help.
Code.gs
function getSpreadsheetData() {
var ssID = "1994YM4uwB1mQORl-HLNk6o10-0ADLNQPgUxKGW6iW_8",
data = SpreadsheetApp.openById(ssID).getSheets()[0].getDataRange().getValues();
for(var i=1; i<data.length; i++){
var date = new Date(data[i][0]);
data[i][0] = Utilities.formatDate(date, "GMT+9", "M/dd");
}
return data;
}
JS
google.charts.load('current', {'packages':['corechart','controls']});
google.charts.setOnLoadCallback(getSpreadsheetData);
function getSpreadsheetData() {
google.script.run.withSuccessHandler(drawChart).getSpreadsheetData();
}
function drawChart(rows) {
var data = google.visualization.arrayToDataTable(rows, false);
var columnsTable = new google.visualization.DataTable();
columnsTable.addColumn('number', 'colIndex'); // numbrer to date then error
columnsTable.addColumn('string', 'colLabel');
var initState= {selectedValues: []}
for (var i = 1; i < data.getNumberOfColumns(); i++) {
columnsTable.addRow([i, data.getColumnLabel(i)]);
}
var chart = new google.visualization.ChartWrapper({
chartType: 'LineChart',
containerId: 'chart_div',
dataTable: data,
options: {
explorer: { axis: 'horizontal' },
pointSize: 3,
vAxis : { format: '#.#',
viewWindow:{
max:0.6,
min:0
},
},
hAxis: {format: 'M/dd'},
legend: 'none',
chartArea: {width: '90%'},
crosshair: { trigger: 'both', orientation: 'both' },
trendlines: {
0: {
type: 'polynomial',
lineWidth: 5,
color: 'orange',
labelInLegend: 'Trend',
degree: 5,
visibleInLegend: true,
},
}
}
});
var columnFilter = new google.visualization.ControlWrapper({
controlType: 'CategoryFilter',
containerId: 'colFilter_div',
dataTable: columnsTable,
options: {
filterColumnLabel: 'colLabel',
filterColumnIndex: 1,
useFormattedValue: true,
ui: {
allowTyping: false,
allowMultiple: false, // when true then filters stacked
caption : 'Choose your values',
allowNone: true,
selectedValuesLayout: 'belowStacked'
}
},
state: initState
});
function setChartView () {
var state = columnFilter.getState();
var row;
var view = {
columns: [0]
};
for (var i = 0; i < state.selectedValues.length; i++) {
row = columnsTable.getFilteredRows([{column: 1, value: state.selectedValues[i]}])[0];
view.columns.push(columnsTable.getValue(row, 0));
}
// sort the indices into their original order
view.columns.sort(function (a, b) {
return (a - b);
});
if (state.selectedValues.length > 0) {
chart.setView(view);
} else {
chart.setView(null);
}
chart.draw();
}
google.visualization.events.addListener(columnFilter, 'statechange', setChartView);
setChartView();
columnFilter.draw();
}
you could use a data view, to convert the date string to an actual date.
here, create the data table as normal.
then create the data view, using a calculated column for the date column.
assuming the first data table column is the date string
function drawChart(rows) {
// create data table
var data = google.visualization.arrayToDataTable(rows, false);
// create data view
data = new google.visualization.DataView(data);
// create view columns with calculated column
var viewColumns = [{
calc: function (dt, row) {
return new Date(dt.getValue(row, 0));
},
label: data.getColumnLabel(0),
type: 'date'
}];
// add remaining columns
for (var = 1; i < data.getNumberOfColumns(); i++) {
viewColumns.push(i);
}
// set view columns
data.setColumns(viewColumns);
...

Google category picker defined dynamically

I can define each category picker using a for loop. However, each picker variable ends up hard coded for the Google API to work:
var categoryPickerArray = [];
for (var i = 0; i < categoryPickers.length; i++) {
categoryPickerArray.push(
new google.visualization.ControlWrapper(categoryPicker_default(categoryPickers[i])),
);
//eval(`var categoryPicker${i} = categoryPickerArray[i];`);//works but uses eval
}
var categoryPicker0 = categoryPickerArray[0];
var categoryPicker1 = categoryPickerArray[1];
I can of use eval() but would prefer not to for all the security concerns.
My goal is to define the picker variables dynamically based on what I've pushed into categoryPickerArray. I'm trying to avoid the need for hard coding each var categoryPicker0 = categoryPickerArray[0] #1, #2, etc.
Hoping someone has an idea about how this can be accomplished. Thanks as always!
Here is my working code:
// Load the Visualization API and the corechart package.
google.charts.load('current', {
'packages': ['corechart', 'table', 'gauge', 'controls']
});
// Set a callback to run when the Google Visualization API is loaded.
google.charts.setOnLoadCallback(gChart0);
function gChart0() {
drawChart();
}
function drawChart() {
var result = [{
"calendarWeek": "2017-W30",
"partId": '1234567890xxx',
"someNumber": 0
}, {
"calendarWeek": "2017-W30",
"partId": '1234567890yyy',
"someNumber": 0
}, {
"calendarWeek": "2017-W30",
"partId": '1234567890111',
"someNumber": 0
}];
//Create DataTable
var data = new google.visualization.DataTable();
data.addColumn('string', 'Calendar Week');
data.addColumn('string', 'Part Id');
data.addColumn('number', 'Some Number');
var dataArray = [];
$.each(result, function(i, obj) {
dataArray.push([
obj.calendarWeek,
obj.partId,
obj.someNumber
]);
});
data.addRows(dataArray);
//Options
var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard'));
const categoryPicker_default = (categoryPicker) => {
var id = categoryPicker.id;
var controlType = categoryPicker.controlType;
var filterColumnIndex = categoryPicker.filterColumnIndex;
const picker_options = `
{
"controlType": "${controlType}",
"containerId": "categoryPicker${id}",
"options": {
"filterColumnIndex": ${filterColumnIndex},
"matchType": "any",
"ui":{
"labelStacking": "vertical",
"allowTyping": false,
"allowMultiple": false,
"allowNone": true
}
}
}
`;
return JSON.parse(picker_options);
};
const categoryPickers = [{
"id": 0,
"controlType": "StringFilter",
"filterColumnIndex": 0
},
{
"id": 1,
"controlType": "StringFilter",
"filterColumnIndex": 1
}
];
var categoryPickerArray = [];
for (var i = 0; i < categoryPickers.length; i++) {
categoryPickerArray.push(
new google.visualization.ControlWrapper(categoryPicker_default(categoryPickers[i])),
);
//eval(`var categoryPicker${i} = categoryPickerArray[i];`);//works but uses eval
}
// Commented out per suggestion from WhiteHat - See bind below
//var categoryPicker0 = categoryPickerArray[0];
//var categoryPicker1 = categoryPickerArray[1];
var table = new google.visualization.ChartWrapper({
chartType: 'Table',
containerId: 'table',
options: {
width: '100%',
height: 'auto',
page: 'enable',
pageSize: '15',
sort: 'enable',
allowHtml: true
}
});
// Picker reset
google.visualization.events.addOneTimeListener(dashboard, 'ready', function() {
var reset = document.getElementById('categoryPicker_resetBtn');
reset.addEventListener('click', function() {
for (var i = 0; i < categoryPickerArray.length; ++i) {
categoryPickerArray[i].setState({
selectedValues: []
});
categoryPickerArray[i].draw();
}
});
});
//dashboard.bind([categoryPicker0, categoryPicker1], [table]); //Old call using hard coded values
dashboard.bind(categoryPickerArray, [table]);//New call using array
dashboard.draw(data);
} //END function drawChart()
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="dashboard"></div>
<div id="categoryPicker0"></div>
<div id="categoryPicker1"></div><br>
<button id="categoryPicker_resetBtn">Reset</button>
<div id="table"></div>
UPDATE:
Made changes in snippet per suggestion from Mr. WhiteHat to use categoryPickerArray in place of hard coded values in the dashboard binding.
dashboard.bind(categoryPickerArray, [table]);
if you're hard coding for the bind method,
you can simply pass the array of pickers directly...
dashboard.bind(categoryPickerArray, [table]);

Google Annotationchart - Rebase index=100 upon Rangechange

I have an Google Annotation Chart to show relative performance of different investments. User should be able compare performance over a selected time frame, that is, the series values should be rebased / indexed to 100 at the startdate of the visible range of the chart once the timeframe is changed.
Other packages like Amcharts offer a "comparable" function, so have been looking for options like "scaleColumns" and "scaleType" in Google Docs and in other questions here, not finding any clue on how to do this.
Is there any feature I can use and might have missed, or what would be the best approach to recalculate the DataTable with index=100 upon "rangechange".
Code and screenshot is below:
google.charts.load('current', {
packages: ['corechart', 'line', 'table','annotationchart']
});
google.charts.setOnLoadCallback(drawChart);
// ---------- Chart ---------------------------- //
function drawChart() {
//data query
var query = new google.visualization.Query(
'https://docs.google.com/spreadsheets/d/1Gw67zHpKyEd1nu_V698yqYqNgE0x21_ZE_QDHJmsgtE/gviz/tq?gid=803335131&headers=1&range=A1:n451');
query.send(handleQueryResponse);
}
function handleQueryResponse(response) {
if (response.isError()) {
alert('Error in query: ' + response.getMessage() + ' ' + response.getDetailedMessage());
return;
}
var data = response.getDataTable();
var options = {
title: 'Development of the choosen Portfolio since 2008',
legend: {
textStyle : { fontSize: 8 },
maxLines : 2,
position: 'top'
},
width: '30%',
height: 700,
lineWidth: 1,
hAxis: {
format: 'M/d/yy',
title: 'Time'
},
vAxis: {
scaleType: 'log',
title: 'Return (log scale)'
},
//theme: 'maximized',
chartArea:{
left:0,
top:20,
width:'30%',
height:'85%'
},
series: {
22: {
lineWidth: 3,
color: 'red'
}
}
};
var chart = new google.visualization.AnnotationChart(document.getElementById('chart_div'))
chart.draw(data, options);
}
google.visualization.events.addListener(chart, 'rangechange', rangechange_handler);
function rangechange_handler(e) {
console.log('You changed the range to ', e['start'], ' and ', e['end']);
// How to recalculate datatable to keep index=100 for all series upon rangechange?
}
Update:
There is a way, working on it: https://groups.google.com/forum/#!searchin/google-visualization-api/compare$20zoom|sort:relevance/google-visualization-api/8HjybllsufY/z5uak6AymLcJ
After an extended period of pain got it working, hope it helps somebody: https://jsfiddle.net/AlexHorn/a9z15syr/
var viewColumns = [0];
var colors = [];
for (var i = 0; i < columnIndices.length; i++) {
viewColumns.push({
label: data.getColumnLabel(columnIndices[i]),
type: 'number',
calc: (function(x) {
// use a closure here to lock the value of i to each column
return function(dt, row) {
// return the value normalized to the first row in the view
return dt.getValue(row, columnIndices[x]) / dt.getValue(0, columnIndices[x]);
};
})(i)
});
}

Google column chart - set selected column color

I have simple column chart and I need to change color of selected (clicked) column. By default it adds white line around the column.
var options = {
title: 'Motivation and Energy Level Throughout the Day',
hAxis: {
title: 'Time of Day',
format: 'h:mm a',
viewWindow: {
min: [7, 30, 0],
max: [17, 30, 0]
}
},
vAxis: {
title: 'Rating (scale of 1-10)'
}
};
Here is simple example on jSFiddle
So if I click any column how to change its color to black?
I can't use Material Charts.
Thanks
Finally I found an answer here
Frankly I thought that there is simpler option, like setting configuration option e.g. hAxis.selected.color: '#000000'.
var chart = new google.visualization.ChartWrapper({
chartType: 'ColumnChart',
containerId: 'chart_div',
dataTable: data,
options: {
// setting the "isStacked" option to true fixes the spacing problem
isStacked: true,
height: 300,
width: 600,
series: {
1: {
// set the color to change to
color: '00A0D0',
// don't show this in the legend
visibleInLegend: false
}
}
}
});
google.visualization.events.addListener(chart, 'select', function () {
var selection = chart.getChart().getSelection();
if (selection.length > 0) {
var newSelection = [];
// if row is undefined, we selected the entire series
// otherwise, just a single element
if (typeof(selection[0].row) == 'undefined') {
newSelection.push({
column: 2
});
chart.setView({
columns: [0, {
type: 'number',
label: data.getColumnLabel(1),
calc: function () {
// this series is just a placeholder
return 0;
}
}, 1]
});
}
else {
var rows = [];
for (var i = 0; i < selection.length; i++) {
rows.push(selection[i].row);
// move the selected elements to column 2
newSelection.push({
row: selection[i].row,
column: 2
});
}
// set the view to remove the selected elements from the first series and add them to the second series
chart.setView({
columns: [0, {
type: 'number',
label: data.getColumnLabel(1),
calc: function (dt, row) {
return (rows.indexOf(row) >= 0) ? null : {v: dt.getValue(row, 1), f: dt.getFormattedValue(row, 1)};
}
}, {
type: 'number',
label: data.getColumnLabel(1),
calc: function (dt, row) {
return (rows.indexOf(row) >= 0) ? {v: dt.getValue(row, 1), f: dt.getFormattedValue(row, 1)} : null;
}
}]
});
}
// re-set the selection when the chart is done drawing
var runOnce = google.visualization.events.addListener(chart, 'ready', function () {
google.visualization.events.removeListener(runOnce);
chart.getChart().setSelection(newSelection);
});
}
else {
// if nothing is selected, clear the view to draw the base chart
chart.setView();
}
chart.draw();
});
chart.draw();
}
UPDATE:
Above solution works only if you are using a ChartWrapper.
I actually needed solution just for chart.
Finally I solved this by adding style.color to the data row. If my selected index = rowIndex then I change the color. Simple and works like a charm.
I hope it will help others.
var data = new google.visualization.DataTable();
data.addColumn({ id: 'name', type: 'string' });
data.addColumn({ id: 'm1', type: 'number' });
data.addColumn({type: 'string', role:'tooltip', 'p': {'html': true}});
data.addColumn({type: 'string', role:'style'});
$.each(scatterData.data, function (index, value) {
if (index == chartSelectedIndex) {
data.addRow([ {v:value.park}, {v:value.m1}, {v:getColumnChartHTMLTooltip(value.park,value.name,value.m1)}, 'color: #32CCFF' ]);
} else{
data.addRow([ {v:value.park}, {v:value.m1}, {v:getColumnChartHTMLTooltip(value.park,value.name,value.m1)}, null ]);
};
});
This worked for me
.my-div svg>g>g>g>g>rect {
fill: #79baeb;
}

Creating pivoted DataView from existing google charts DataTable object

I have a DataTable that contains:
id,day,proj,col1,col2,subtype,time
1,Nov 28,projectA,1050,880,foo,17481
2,Nov 28,projectA,1050,880,bar,16098
3,Nov 28,projectA,1080,40,foo,13509
4,Nov 28,projectA,1080,40,bar,9031
But would like to create a new pivoted DataView containing:
id,day,proj,col1,col2,foo,bar
1,Nov 28,projectA,1050,880,17481,16098
3,Nov 28,projectA,1080,40,13509,9031
Which I'd then like to create a stacked columnChart for.
There's a pivot clause in the query language, but how do I pivot data that's already in a DataTable?
Manually.
You can see this example on jsfiddle by asgallant. This uses a dataView to accomplish the task.
google.load('visualization', '1', {packages: ['table']});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('number', 'A');
data.addColumn('string', 'B');
data.addColumn('number', 'C');
data.addRows([
[1, 'foo', 6],
[2, 'foo', 2],
[3, 'foo', 1],
[4, 'foo', 3],
[1, 'bar', 7],
[2, 'bar', 3],
[1, 'baz', 8],
[2, 'baz', 4]
]);
var table1 = new google.visualization.Table(document.getElementById('table1'));
table1.draw(data, {});
/* manually pivot the data table
* set column A as the first column in the view,
* then we have to separate out the C values into their own columns
* according to the value of B, using a DataView with calculated columns
*/
var view = new google.visualization.DataView(data);
view.setColumns([0, {
type: 'number',
label: 'foo',
calc: function (dt, row) {
// return values of C only for the rows where B = "foo"
return (dt.getValue(row, 1) == 'foo') ? dt.getValue(row, 2) : null;
}
}, {
type: 'number',
label: 'bar',
calc: function (dt, row) {
// return values of C only for the rows where B = "bar"
return (dt.getValue(row, 1) == 'bar') ? dt.getValue(row, 2) : null;
}
}, {
type: 'number',
label: 'baz',
calc: function (dt, row) {
// return values of C only for the rows where B = "baz"
return (dt.getValue(row, 1) == 'baz') ? dt.getValue(row, 2) : null;
}
}]);
// next, we group the view on column A, which gets us the pivoted data
var pivotedData = google.visualization.data.group(view, [0], [{
column: 1,
type: 'number',
label: view.getColumnLabel(1),
aggregation: google.visualization.data.sum
}, {
column: 2,
type: 'number',
label: view.getColumnLabel(2),
aggregation: google.visualization.data.sum
}, {
column: 3,
type: 'number',
label: view.getColumnLabel(3),
aggregation: google.visualization.data.sum
}]);
var table2 = new google.visualization.Table(document.getElementById('table2'));
table2.draw(pivotedData, {});
}
Alternatively, you can do it the manual way.
var data = new google.visualization.DataTable();
data.addColumn('string', 'First Column Title');
var baseline = chartData.getValue(chartData.getNumberOfRows() - 1, 15);
for (var i = 0; i < chartData.getNumberOfRows(); i++) {
data.addColumn('number', chartData.getFormattedValue(i, 0));
};
for (var j = 0; j < chartData.getNumberOfColumns() - 2; j++) {
data.addRow();
data.setValue(j, 0, chartData.getColumnLabel(j + 1));
for (var i = 0; i < chartData.getNumberOfRows(); i++) {
data.setValue(j, i + 1, chartData.getValue(i, j+1));
};
};

Categories

Resources