Google Geochart - same country multiple values from JSON data - javascript

Am trying to display all values for each country in a Google Geochart from data sent from my database (via JSON), but only the last row ever displays ('Supplier 2' in the case highlighted below). I have reviewed this answer and attempted to incorporate the suggested group() method; however this produces a console error which I am unable to successfully debug: 'unexpected token }', which refers to the closing curly bracket in the var groupData declaration.
This is what currently shows, without using any group() method. I need 'Supplier 1' to also display.
This is the code:
function incAvailableCountry() {
$.ajax({
url: "inc-analysis/country-available.php,
dataType: "json"
}).done(function(jsonData){
var data = new google.visualization.DataTable(jsonData);
var groupData = google.visualization.data.group(
data,
[0, 1],
[{
aggregation: google.visualization.data.count,
column, 0
}]);
var options = {
width: 'auto',
keepAspectRatio: true,
legend: 'none'
};
for (var i = 0; i < data.getNumberOfRows(); i++) {
var locationRows = groupData.getFilteredRows([{
column: 0,
value: data.getValue(i, 0)
}]);
var nameTooltip = '';
locationRows.forEach(function(index){
if (nameTooltip !== '') {
nameTooltip += ', ';
}
nameTooltip += groupData.getValue(index, 1);
});
data.setValue(i, 1, nameTooltip);
}
var chart = new google.visualization.GeoChart(document.getElementById('incAvailableCountry'));
chart.draw(data, options);
});}
PHP:
<?php include '../core/init.php';
$query = mysqli_query($conn, "(SELECT Country, Supplier
FROM table
GROUP BY Supplier)
ORDER BY Country");
$table = array();
$table['cols'] = array(
array('label' => 'Country', 'type' => 'string'),
array('label' => 'Supplier', 'type' => 'string')
);
$rows = array();
while ($r1 = mysqli_fetch_assoc($query)) {
$temp = array();
$temp[] = array('v' => (string)$r1['Country']);
$temp[] = array('v' => (string)$r1['Supplier']);
$rows[] = array('c' => $temp);
}
$table['rows'] = $rows;
$jsonTable = json_encode($table);
echo $jsonTable;
Resulting JSON:
{"cols":[
{"label":"Country","type":"string"},
{"label":"Supplier","type":"string"}],"rows":[
{"c":[{"v":"Spain"},
{"v":"Supplier 1"}]},
{"c":[{"v":"Spain"},
{"v":"Supplier 2"}]}]}
HTML:
<script src="https://www.google.com/jsapi"></script>
<script>google.charts.load('current', {'packages': ['geochart'], 'mapsApiKey': 'key...'});</script>
<div id="incAvailableCountry"></div>

in the group method, column should be followed by a colon :, instead of a comma ,
var groupData = google.visualization.data.group(
data,
[0, 1],
[{
aggregation: google.visualization.data.count,
column, 0
}]);
change to...
var groupData = google.visualization.data.group(
data,
[0, 1],
[{
aggregation: google.visualization.data.count,
column: 0,
type: 'number'
}]
);

Related

Array with subarray loop Javascript

Is it possible to create an array and loop through it in JavaScript like below?
<?php
$options = array(
'color' => array('blue', 'yellow', 'white'),
'size' => array('39', '40', '41'),
);
foreach($options as $option => $values){
echo $option.'<br>';
foreach($values as $value){
echo $value.' ';
}
echo '<br>';
}
?>
I checked the internet but I can not find a good example.
Thanks for helping!
Exactly
let options = {
color: ['blue', 'yellow', 'white'],
size: [39, 40, 41]
};
You have three ways to do this:
FOR
for (option in options) {
console.log(option);
var values = options[option];
for (let i = 0, len = values.length, value = values[i]; i < len; value = values[++i]) {
console.log(value);
}
}
ARRAY FOREACH
for (option in options) {
console.log(option);
options[option].forEach(value => {
console.log(value);
});
}
Easiest way: jQuery
$.each(options, (option, values) => {
console.log(option);
$.each(values, (key, value) => {
console.log(value);
});
});
If I understand you correctly you want a javascript equivalent to the php code you've given?
If so, it could be achieved the following way:
let options = {
color: ['blue', 'yellow', 'white'],
size: [39, 40, 41]
};
for(option of Object.keys(options)) {
console.log(option);
for(value of options[option]) {
console.log(value);
}
}
this is not exactly the same as your php code, but the loop works in a similar way.
Arrays in javascript have integer keys (indexes). You can use an object as you need to store string keys on the parent level and the values can be arrays as they don't have any string keys
const options = {
color: ['blue', 'yellow', 'white'],
size: ['39', '40', '41'],
}
Object.entries(options).forEach(([key, value]) => {
console.log(key)
value.forEach(el => console.log(el))
})

Google Visualization - Making a new DataTable from an existing DataTable?

I would like to know how to make a new DataTable from an existing DataTable?
Use case:
I have a proxyTable which is hidden and connected to a CategoryFilter. This is used used to construct other tables on the page which are all linked to one CategoryFilter.
Goal:
Include a Grand Total row in each new table which reflects summation of the filtered selection.
Initial solution:
I have tried extracting an array from sourceData, creating a new data table called dataResults, adding grand total row to dataResults, and drawing the final table. It works but seems like a lot of effort.
var sourceData = proxyTable.getDataTable();
var rowCount = sourceData.getNumberOfRows();
var colCount = sourceData.getNumberOfColumns();
var tempRow = [];
var tempArray = [];
var pushValue;
//push header row
for (var k = 0; k < colCount; k++) {
pushValue = sourceData.getColumnLabel(k);
tempRow.push(pushValue);
}
tempArray.push(tempRow);
tempRow = []; //reset
//push data rows
for (var i = 0; i < rowCount; i++) {
for (var j = 0; j < colCount; j++) {
pushValue = sourceData.getValue(i, j);
tempRow.push(pushValue);
}
tempArray.push(tempRow);
tempRow = []; //reset
}
//Create new Google DataTable from Array
var dataResults = new google.visualization.arrayToDataTable(tempArray);
My question:
How can I make a data table which contains all records from sourceData without going through the above steps I've tried?
You guidance is appreciated greatly!
Working Example:
UPDATE:
Added var dataResults = sourceData.clone(); per answer from #WhiteHat and I get an error sourceData.clone is not a function
Did I get the syntax wrong? Perhaps it's the ChartWrapper I'm using?
UPDATE 2:
Added var dataResults = sourceData.toDataTable().clone(); per answer #2 from #WhiteHat and it works.
google.charts.load('current', {
'packages': ['corechart', 'table', 'gauge', 'controls', 'charteditor']
});
$(document).ready(function() {
renderChart_onPageLoad();
});
function renderChart_onPageLoad() {
google.charts.setOnLoadCallback(function() {
drawDashboard();
});
}
function drawDashboard() {
var data = google.visualization.arrayToDataTable([
['Name', 'RoolNumber', 'Gender', 'Age', 'Donuts eaten'],
['Michael', 1, 'Male', 12, 5],
['Elisa', 2, 'Female', 20, 7],
['Robert', 3, 'Male', 7, 3],
['John', 4, 'Male', 54, 2],
['Jessica', 5, 'Female', 22, 6],
['Aaron', 6, 'Male', 3, 1],
['Margareth', 7, 'Female', 42, 8],
['Miranda', 8, 'Female', 33, 6]
]);
var dashboard = new google.visualization.Dashboard(document.getElementById('dashboard'));
var categoryPicker = new google.visualization.ControlWrapper({
controlType: 'CategoryFilter',
containerId: 'categoryPicker',
options: {
filterColumnLabel: 'Gender',
ui: {
labelStacking: 'vertical',
allowTyping: false,
allowMultiple: false
}
}
});
var proxyTable = new google.visualization.ChartWrapper({
chartType: 'Table',
containerId: 'proxyTable',
options: {
width: '500px'
}
});
var table = new google.visualization.ChartWrapper({
chartType: 'Table',
containerId: 'table',
options: {
width: '500px'
}
});
dashboard.bind([categoryPicker], [proxyTable]);
dashboard.draw(data);
google.visualization.events.addListener(dashboard, 'ready', function() {
redrawChart();
});
function redrawChart() {
var sourceData = proxyTable.getDataTable();
//WhiteHat suggestion2 - WORKS
var dataResults = sourceData.toDataTable().clone();
//WhiteHat suggestion1 - Didn't work
//var dataResults = sourceData.clone();
//INITIAL SOLUTION - works
//var rowCount = sourceData.getNumberOfRows();
//var colCount = sourceData.getNumberOfColumns();
//var tempRow = [];
//var tempArray = [];
//var pushValue;
//for (var k = 0; k < colCount; k++) {
//pushValue = sourceData.getColumnLabel(k);
//tempRow.push(pushValue);
//}
//tempArray.push(tempRow);
//tempRow = []; //reset
//push data rows
//for (var i = 0; i < rowCount; i++) {
//for (var j = 0; j < colCount; j++) {
//pushValue = sourceData.getValue(i, j);
//tempRow.push(pushValue);
//}
//tempArray.push(tempRow);
//tempRow = []; //reset
//}
//var dataResults = new google.visualization.arrayToDataTable(tempArray);
var group = google.visualization.data.group(sourceData, [{
// we need a key column to group on, but since we want all rows grouped into 1,
// then it needs a constant value
column: 0,
type: 'number',
modifier: function() {
return 1;
}
}], [{
column: 1,
id: 'SumRool',
label: 'SumRool',
type: 'number',
aggregation: google.visualization.data.sum
}, {
column: 3,
id: 'SumAge',
label: 'SumAge',
type: 'number',
aggregation: google.visualization.data.sum
}, {
// get the average age
column: 4,
id: 'SumEaten',
label: 'SumEaten',
type: 'number',
aggregation: google.visualization.data.sum
}]);
dataResults.insertRows(0, [
['Grand Total', group.getValue(0, 1), null, group.getValue(0, 2), group.getValue(0, 3)],
]);
//Set dataTable
table.setDataTable(dataResults);
table.draw();
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="dashboard">
<div id="categoryPicker"></div><br /> Proxy Table<br />
<div id="proxyTable"></div><br /> Table
<br />
<div id="table"></div><br /><br />
</div>
data table method --> clone()
var newDataTable = oldDataTable.clone();
from the docs...
clone() - Returns a clone of the data table. The result is a deep copy of the data table except for the cell properties, row properties, table properties and column properties, which are shallow copies; this means that non-primitive properties are copied by reference, but primitive properties are copied by value.

How to sum duplicates in an array

I am creating a javascriptchart, which I have done succesfully but I am lot having duplicates.
Below is a sample xmlwhere I am retrieving data from to display on my Bar chart.
How do I sum the duplicate values retrieved from the xml?
xml:
<counts>
<serial>3123111</serial>
<scans>3</scans>
<prints>1</prints>
<copies>0</copies>
</counts>
<counts>
<serial>3123111</serial>
<scans>0</scans>
<prints>2</prints>
<copies>0</copies>
</counts>
<counts>
<serial>AHTSD111</serial>
<scans>0</scans>
<prints>1</prints>
<copies>2</copies>
</counts>
<counts>
<serial>AHTSD111</serial>
<scans>0</scans>
<prints>1</prints>
<copies>2</copies>
</counts>
Expected result below which I use for the barchart:
<counts>
<serial>3123111</serial>
<scans>3</scans>
<prints>3</prints>
<copies>0</copies>
</counts>
<counts>
<serial>AHTSD111</serial>
<scans>0</scans>
<prints>2</prints>
<copies>4</copies>
</counts>
Data retrieved and pushed to the Chart data, I know i have to do a loop to sum the values of scan, prints, copies for each Serial Number but I still keep getting duplicate values, I just left the code out.
$.ajax({
url: "http://localhost5/api/",
dataType: 'xml',
method: "GET",
success: function (data) {
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(data, "text/html");
data = xmlDoc.getElementsByTagName("JobCounts");
var items = [];
var serial, prints, copies, scans;
function getAsText(parent, name) {
return parent.getElementsByTagName(name)[0].textContent
}
function getAsInt(parent, name) {
return parseInt(getAsText(parent, name));
}
for (i = 0; i < data.length; i++) {
items.push({
serial: getAsText(data[i], "serial"),
prints: getAsInt(data[i], "prints"),
copies: getAsInt(data[i], "copies"),
scans: getAsInt(data[i], "scans")
});
}
items = items.reduce((a, c) => {
var same = a.find(v => v.serial == c.serial);
console.log(same);
if (same) {
same.prints += c.prints;
same.copies += c.copies;
same.scans += c.scans;
} else {
a.push(c);
}
return a;
}, []);
// "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js"
// JS chart library I am using
var chartdata = {
labels: serial,
title: "Date",
datasets: [
{
label: 'Copies',
backgroundColor: 'rgba(0,255,0,0.6)',
data: copies
}
]
};
var ctx = $("#mycanvas");
var options = {
responsive: true,
title: {
display: true,
position: "top",
text: "DashBoard",
fontSize: 12,
fontColor: "#111"
},
legend: {
display: true,
position: "bottom",
labels: {
fontSize: 9
}
},
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
};
var barGraph = new Chart(ctx, {
'type': 'bar',
data: chartdata,
options : options,
"categoryField": "date",
"categoryAxis": {
"autoGridCount": false,
"gridCount": chartdata.length,
"gridPosition": "start",
"labelRotation": 90,
"startOnAxis": false,
"title":"Date"
}
});
},
error: function (data) {
console.log(data);
}
});
}
Instead of storing the info in 3 different arrays, you can stored it in a single array of objects which will make it easier to merge objects that have the same serial number.
Here is what I mean:
var items = [];
function getAsText(parent, name){
return parent.getElementsByTagName(name)[0].textContent
}
function getAsInt(parent, name){
return parseInt(getAsText(parent, name));
}
for (i = 0; i < data.length; i++) {
items.push({serial: getAsText(data[i], "serial"),
prints: getAsInt(data[i], "prints"),
copies: getAsInt(data[i], "copies"),
scans: getAsInt(data[i], "scans")});
}
// Merge items with the same `serial` value;
items = items.reduce((a, c) => {
var same = a.find(v => v.serial == c.serial);
if(same){
same.prints += c.prints;
same.copies += c.copies;
same.scans += c.scans;
}else{
a.push(c);
}
return a;
}, []);
You can iterate each counts element .children, .map() each .tagName and .textContent to an object, push the object to an array, iterate the array and increment each SCANS, PRINTS, and COPIES property if SERIAL matches.
Convert JSON to elements by iterating resulting array using .forEach() and Object.entries().
let data = `<counts>
<serial>3123111</serial>
<scans>3</scans>
<prints>1</prints>
<copies>0</copies>
</counts>
<counts>
<serial>3123111</serial>
<scans>0</scans>
<prints>2</prints>
<copies>0</copies>
</counts>
<counts>
<serial>AHTSD111</serial><scans>0</scans><prints>1</prints><copies>2</copies>
</counts>
<counts>
<serial>AHTSD111</serial><scans>0</scans><prints>1</prints><copies>2</copies>
</counts>`;
let parser = new DOMParser();
let xmlDoc = parser.parseFromString(data, "text/html");
let res = [];
for (let {counts:{SERIAL, SCANS, COPIES, PRINTS}} of
[...xmlDoc.querySelectorAll("counts")]
.map(el =>
({counts:[...el.children]
.reduce((o, {tagName, textContent}) =>
Object.assign(o, {[tagName]:textContent}), {})
}))) {
let curr = res.find(({counts:{SERIAL:s}}) => s === SERIAL);
if (!curr) {
res.push({counts:{SERIAL, SCANS, COPIES, PRINTS}})
} else {
curr = curr.counts;
curr.SCANS = +curr.SCANS + +SCANS;
curr.PRINTS = +curr.PRINTS + +PRINTS;
curr.COPIES = +curr.COPIES + +COPIES;
}
}
res.forEach(o => {
let key = Object.keys(o).pop();
let parentNode = document.createElement(key);
Object.entries(o[key])
.forEach(([tagName, textContent]) => {
let childNode = document.createElement(tagName)
childNode.textContent = textContent;
parentNode.appendChild(childNode);
parentNode.appendChild(document.createElement("br"));
document.body.appendChild(parentNode);
})
document.body.appendChild(document.createElement("br"));
})

Making 2 columns distinct

I want to increase the count of 4 different variables to fill in a pie chart. But i only want it to increment the variables per unique variable in the 2 columns. I want to try to make them distinct but i do not quite understand how to. In the if statement i try to compare the value with a string. This works. After the || i try to make it distinct but i have trouble with that.
Here is my code:
function createPiechartthree(){
var columns = {};
var xmlColumns = $j('head', xml);
xmlColumns.find('headColumn').each(function(){
var columnName = $j(this).find('columnValue').text();
var columnID = $j(this).attr('columnid');
columns[columnName] = (columnID);
});
var xmlData = $j('data', xml);
xmlData.find('item').each(function(){
$j(this).find('column').each(function(){
var colID = $j(this).attr("columnid");
console.log(colID);
var value = $j(this).find('displayData').text();
if(colID == columns["Risk level client"] || colID == columns["Counterparty name"] ){
if(value === "High" || value === value){
highRiskCategory++;
}
else if(value === "Medium" || value === value){
mediumRiskCategory++;
}
else if(value === "Low" || value === value){
lowRiskCategory++;
} else if(value === "" || value === value) {
unidentified++;
}
}
})
})
1) rather than incrementing the counts manually, build an array of the values found...
var riskData = [
['Starbucks', 'High'],
['Starbucks', 'High'],
['McDonalds', 'Low'],
['Dunkin', 'Medium'],
['Dunkin', 'Medium'],
['Dunkin', 'Medium'],
['Subway', 'Low'],
['Chick-fil-a', ''],
['Chick-fil-a', '']
];
2) then filter out the duplicates...
var pieData = new google.visualization.DataTable();
pieData.addColumn('string', 'Risk Level');
var counterParty = [];
riskData.forEach(function (riskLevel) {
if (counterParty.indexOf(riskLevel[0]) === -1) {
counterParty.push(riskLevel[0]);
var rowData = (riskLevel[1] === '') ? ['Unidentified'] : [riskLevel[1]];
pieData.addRow(rowData);
}
});
3) then use google's group method to aggregate the counts...
var groupPie = google.visualization.data.group(
pieData,
[0],
[{
aggregation: google.visualization.data.count,
column: 0,
label: 'Count',
type: 'number'
}]
);
see following working snippet...
google.charts.load('current', {
callback: drawChart,
packages: ['controls']
});
function drawChart() {
// create array like this
var riskData = [
['Starbucks', 'High'],
['Starbucks', 'High'],
['McDonalds', 'Low'],
['Dunkin', 'Medium'],
['Dunkin', 'Medium'],
['Dunkin', 'Medium'],
['Subway', 'Low'],
['Chick-fil-a', ''],
['Chick-fil-a', '']
];
// for example only <--
var tableRisk = new google.visualization.ChartWrapper({
chartType: 'Table',
containerId: 'table_risk',
dataTable: google.visualization.arrayToDataTable(riskData, true)
});
tableRisk.draw();
// -->
var pieData = new google.visualization.DataTable();
pieData.addColumn('string', 'Risk Level');
var counterParty = [];
riskData.forEach(function (riskLevel) {
if (counterParty.indexOf(riskLevel[0]) === -1) {
counterParty.push(riskLevel[0]);
var rowData = (riskLevel[1] === '') ? ['Unidentified'] : [riskLevel[1]];
pieData.addRow(rowData);
}
});
// for example only <--
var tablePie = new google.visualization.ChartWrapper({
chartType: 'Table',
containerId: 'table_pie',
dataTable: pieData
});
tablePie.draw();
// -->
var groupPie = google.visualization.data.group(
pieData,
[0],
[{
aggregation: google.visualization.data.count,
column: 0,
label: 'Count',
type: 'number'
}]
);
// for example only <--
var tablePie = new google.visualization.ChartWrapper({
chartType: 'Table',
containerId: 'table_group',
dataTable: groupPie
});
tablePie.draw();
// -->
var chartPie = new google.visualization.ChartWrapper({
chartType: 'PieChart',
containerId: 'chart_pie',
dataTable: groupPie,
options: {
colors: ['red', 'orange', 'green', 'blue']
}
});
chartPie.draw();
}
div {
display: inline-block;
margin-right: 8px;
vertical-align: top;
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="table_risk"></div>
<div id="table_pie"></div>
<div id="table_group"></div>
<div id="chart_pie"></div>

how to display label and values only on google pie chart legend with data from mysql db

I am using google charts API to display pie chart and I want the legend to display only the label and values and I'm stucked, most of the answers I searched includes percentage. How can I do this?
My code below:
PHP
$table = array();
$table['cols'] = array(
//Labels for the chart, these represent the column titles
array('id' => '', 'label' => 'Country', 'type' => 'string'),
array('id' => '', 'label' => 'Number of Devices', 'type' => 'number')
);
$rows = array();
foreach($exec as $row){
$temp = array();
//Values
$temp[] = array('v' => (string) $row['Country']);
$temp[] = array('v' => (int) $row['Number of Devices']);
$rows[] = array('c' => $temp);
}
$table['rows'] = $rows;
$jsonTable = json_encode($table);
echo $jsonTable
JavaScript
google.charts.load('current', {'packages':['corechart']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var PieChart_options = {
title: 'Number of Devices per country',
pieSliceText: 'value',
width: 900,
height: 500
};
var data = new google.visualization.DataTable(<?=$jsonTable?>);
var chart = new google.visualization.PieChart(document.getElementById('pie_div'));
chart.draw(data, PieChart_options);
}
I want to create like this one:
pie chart with label and value on legend
the chart can be modified, once the 'ready' event fires
be sure to set the event listener, before drawing the chart
the legend labels should appear in the same order as the data provided
the following snippet finds the legend labels by checking attribute values
then adds the value from the data...
google.charts.load('current', {
callback: function () {
var data = new google.visualization.DataTable({
"cols": [
{"label": "Country", "type": "string"},
{"label": "# of Devices", "type": "number"}
],
"rows": [
{"c": [{"v": "Canada"}, {"v": 33}]},
{"c": [{"v": "Mexico"}, {"v": 33}]},
{"c": [{"v": "USA"}, {"v": 34}]}
]
});
var container = document.getElementById('chart_div');
var chart = new google.visualization.PieChart(container);
google.visualization.events.addListener(chart, 'ready', function () {
var rowIndex = 0;
Array.prototype.forEach.call(container.getElementsByTagName('text'), function(label) {
// find legend labels
if ((label.getAttribute('text-anchor') === 'start') && (label.getAttribute('fill') !== '#ffffff')) {
label.innerHTML += ' (' + data.getValue(rowIndex++, 1) + ')';
}
});
});
chart.draw(data, {
height: 400,
width: 600
});
},
packages:['corechart']
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>
Simply, Just format the data from mysql like this
['Work (11)', 11]
It won't affect your chart though.

Categories