How to dynamically create multiple horizontal bar charts in Google Chart? - javascript

I'm new in Google Chart API.
I want to use it to visualize my data about the review on Google Play Store which include multiple issues, sentiments and other conditions.
I want to build a horizontal bar chart which x axis containing different app , and each app's containing 7 issues, y axis is the sum of sentiment of each issue in different app.
I have already done the horizontal chart containing all data in a single div element. However, for the user's convenience, I want to show 5 data at most in a single div element, and dynamically create div element if there is more than 5 data in the current data set. At last, the div elements which paint the chart will horizontally append to the another div element called issueBar_div. The user can use the scrollbar to view different chart.
What I've done:
Partition data:
var title = ['APP'];
var issueRow = {{ projectissues|safe }}; // 7 different issues got from Django
var graph_data = [title.concat(issueRow)];
var cnt = 0;
var divide_row = [];
var tableRows = ... // tableRows is the app name mapping to sentiment sum which corresponding to different issue array dictionary.
// like the form APP -> [20, 12, -1, 3, 5, 21, 57]
for (var app in tableRows) {
cnt ++;
var row = [app];
for (var i = 0; i < tableRows[app].length; i++) {
row.push(tableRows[app][i]);
}
if(cnt < 6){
divide_row.push(row);
}
else{
graph_data.push(divide_row);
divide_row = [];
cnt = 0;
}
}
Create the div element and draw the chart
In order to build a use the scrollbar, I add some restriction to the issueBar_div.
<div id="issueBar_div" style="height:400px; overflow-x:scroll; overflow-y:hidden";></div>
Dynamically create the div element and draw.
function drawIssueBar() {
var issueBar = document.getElementById("issueBar_div");
// titleRow include the APP and other issue
var titleRow = graph_data[0];
delete_element();
for(var i = 1;i<graph_data.length;i++){
// create div element and set their attribute
var my_div = document.createElement("div");
my_div.id = "issuebar_" + i;
my_div.style.display = "table-cell";
// append this element to #issueBar_div
issueBar.appendChild(my_div);
// get the sliced data and push to total_data
var row_data = graph_data[i];
var total_data = [titleRow];
for(var k=0;k<row_data.length;k++){
total_data.push(row_data[k]);
}
// the new data container
var data = new google.visualization.arrayToDataTable(total_data);
var div_width = $("#home").width();
var materialOptions = {
height: 400,
width: div_width,
hAxis: {
title: 'Sum of sentiment'
},
vAxis: {
title: 'APP'
},
bars: 'vertical'
};
var materialChart = new google.charts.Bar(document.getElementById("issuebar_" + i));
materialChart.draw(data, materialOptions);
}
}
// delete the div element
function delete_element(){
i = 1;
while(true){
element = document.getElementById("issuebar_" + i);
if(element !== null){
element.outerHTML = "";
delete element;
i++;
}
else{
break;
}
}
}
Current Problem:
Basically, the created div elements will create as expect, but the chart will only show one chart as below.
The successful chart
The fail part when move the scrollbar to right
How can I solve this problem?
Any answer will be greatly appreciated. Thanks.

ALL. I have already solve my problem!!!
I notice that there is a very important sentence showing on Google Chart document:
For each chart on the page, add a call to google.charts.setOnLoadCallback() with the callback that draws the chart as an input
So I decide to callback drawing method when drawing each bar chart.
The specific solution
1.I use the new Function as a callback function. The row_data is the data needing to present, and the my_div is the div element where to store the chart.
var drawIssueBar = new Function("row_data", "my_div",
"var data = google.visualization.arrayToDataTable(row_data);" +
"var div_width = $('#home').width();" +
"var materialOptions = {" +
"height: 400," +
"width: div_width," +
"hAxis: {" +
"title: 'Sum of sentiment'" +
"}," +
"vAxis: {" +
"title: 'APP'" +
"}," +
"bars: 'vertical'" +
"};"+
"var materialChart = new google.charts.Bar(my_div);" +
"materialChart.draw(data, materialOptions);"
);
2.Rewrite the data processing.
var titleRow = ... // like the form of ["APP", "issue1", "issue2", ..."issue7"]
var cnt = 0;
var divide_row = [];
for (var app in tableRows) {
cnt ++;
var row = [app].concat(tableRows[app]);
if(cnt < 6){
divide_row.push(row);
}
else{
graph_data.push([titleRow].concat(divide_row));
// console.log(graph_data[graph_data.length - 1]);
divide_row = [];
cnt = 0;
}
}
3.Write a new function called drawIssueChart to draw all the bar chart.
function drawIssueChart(){
// create the div element and draw the chart
delete_element();
var issueBar = document.getElementById('issueBar_div');
for(var i = 0;i<graph_data.length;i++){
var my_div = document.createElement("div");
my_div.id = "issuebar_" + i;
my_div.style.display = "table-cell";
issueBar.appendChild(my_div);
var row_data = graph_data[i];
// use the drawIssueBar as a callback
google.charts.setOnLoadCallback(drawIssueBar(row_data, my_div));
}
}
Result
The successful chart
Another successful chart when moving the scrollbar to right
I learn a lot from this solution!
Hopefully it can help the people who encounter this problem.

Related

Google Slides API / Apps Script - find URLs and make them into hyperlinks

My script pulls rows from a form-populated spreadsheet and applies the values to a slide template.
They submit a Title, Description, and a Link to the resource they are submitting.
I need the "Link", which should be submitted as an URL, to automatically become a hyperlink when the slide is created. Everything I've tried has returned an error. I want to run .setLinkUrl(link) on the previously inserted link text. I just cant get it to stick. Any help would be appreciated.
--below is the function I'm trying to run--
function dataInjectionNN() {
var dataSpreadsheetUrl = "...spreadsheeturl.urlytypestuff..."
var ss = SpreadsheetApp.openByUrl(dataSpreadsheetUrl);
var deck = SlidesApp.getActivePresentation();
var mathCluster = deck.getName();
// Logger.log(mathCluster)
var sheet = ss.getSheetByName(mathCluster);
var lastR = sheet.getLastRow();
var range =sheet.getDataRange();
// Logger.log(range.getLastRow() + " Is the last Column.");
var values = range.getValues();
// Logger.log(values);
var slides = deck.getSlides();
var presLength = slides.length;
var templateSlide = slides[1];
values.forEach(function(page){
// run if the presentation has less slides that the number of form entries
if((presLength-2) < lastR){
if(page[3] = mathCluster){
var title = page[9];
var link = page[10];
var desc = page[11];
templateSlide.duplicate();//Duplicate the template page
slides = deck.getSlides();//update the slides array for indexes and length
newSlide = slides[2]; //declare the new page to update
var shapes = (newSlide.getShapes());
shapes.forEach(function(shape){
shape.getText().replaceAllText('<<Title>>',title);
shape.getText().replaceAllText('<<Link>>',link);
shape.getText().replaceAllText('<<Description>>',desc);
presLength = slides.length;
newSlide.move(presLength);
})
}
}
});
}

How to fix randomly populating HTML table?

I'm getting the lat/lng of a location based on excel sheet input & outputting onto an HTML table. I'm able to get the correct coordinates; however, they do not output to the right column (they randomly populate the table, I want them next to their corresponding address). I discovered that the coordinates will populate correctly when I have the alert(newArray[n]); before the geocode searches.
Is there anyway I could fix this? Below is a snippet of my code:
var tblBodyObj = document.getElementById(tableID).tBodies[0];
for (var i = 0; i < tblBodyObj.rows.length; i++) {
for (var k = 1; k < rowLength; k++) {
var oCells = oTable.rows.item(k).cells;
var cellLength = oCells.length;
newArray[n] = '';
for (var j = 2; j < cellLength; j++) {
var cellVal = oCells.item(j).innerHTML;
//tblBodyObj.rows[i].insertCell(-1);
tblBodyObj.rows[i].insertCell(-1);
var value = cellVal + ' ';
newArray[n] = newArray[n] + value;
}
//alert(newArray[n]);
MQ.geocode().search(newArray[n])
.on('success', function(e) {
var results = e.result,
result,
latlng,
best;
result = results.best;
latlng = result.latlng;
var x = document.getElementById("grid1").rows[o].cells;
x[cellLength].innerHTML = latlng.lat;
var y = document.getElementById("grid1").rows[o].cells;
y[cellLength + 1].innerHTML = latlng.lng;
//alert(latlng);
o++;
n++;
});
i++;
}
}
}
Here's a trick to populate tables asynchronously... but it requires a different approach to inserting cells like what you're doing.
Build your full table beforehand in a string with a dummy image in each cell like this...
<td> <img style="display:none;" src="X" onerror="this.outerHTML=FetchSomeValue(this.row,this.col);"> </td>
And display the string to screen. As each image load FAILS, it'll call the function FetchSomeValue(Row,Col) and populate correctly by replacing the dummy image.
NB: I use this technique with images or iframes (depending) all the time, flawlessly.
Images and Iframes have a ONLOAD event which we can take advantage of.

forEachFeatureAtPixel behaves differently on different features

I am trying to add an overlay when clicking on a feature on a map. This is working perfectly fine. If there are several features I would like to have a table showing the dates of the pictures and make the different Dates clickable to show the images.
Here comes the problem: I have 2 Layers added with different images/icons but in general the same code is creating all the information for the layers. When I click on the Icon for the first layer, everything is working perfectly fine like expected. However, clicking on overlapping icons on the second layer shows only one date and thus one image.
To see my problem you can go to: https://forestwatch.lup-umwelt.de/app_development/ , open the Map-layer on the left and add "Fotos" and "Drohnenbilder" to the map. You will see a stack of Camera-Symbols on the right side, behaving like expected: without zooming a click on the stack will give you a bunch of dates.
Clicking on the Drone-image in the middle will open the overlay, show a single date that has to be clicked (which should not happen if there is only one image). So - no auto-opening and no table with images.
This is the code I use for detecting features:
var overlay = new ol.Overlay({
element: document.getElementById('overlay'),
positioning: 'bottom-left'
});
var displayImage = function(pixel) {
var features = [];
map.forEachFeatureAtPixel(pixel, function(feature){
features.push(feature);
},{hitTolerance: 0});
if (features.length === 1){
var imag = features[0].values_.pointAttributes.Image;
var date = features[0].values_.pointAttributes.Datum;
overlay.setPosition(features[0].values_.geometry.flatCoordinates);
var innerHTML = "<img ID=\"theImage\" src= " + imag + "><table id=\"fototable\"><tr><td><button class=\"btn\" disabled>" + date + "</button><td><tr></table>";
var element = overlay.getElement();
element.innerHTML = innerHTML;
map.addOverlay(overlay);
}else if (features.length > 1) {
var imag = [];
var date = [];
for (var i = 0, ii = features.length; i < ii; ++i) {
imag.push(features[i].values_.pointAttributes.Image);
date.push(features[i].values_.pointAttributes.Datum);
overlay.setPosition(features[i].values_.geometry.flatCoordinates);
let innerHTMLStart = "<img ID=\"theImage\" src= \"\"><table id=\"fototable\">";
let innerHTMLEnd = "</table>";
let innerHTML = innerHTMLStart;
let lastIndex = imag.length - 1;
//loop over our items
for(var i = 0; i < date.length; i++) {
//first iteration
if(i % 3 === 0 && i === 0) {
innerHTML = innerHTML + "<tr>";
//modulo 3 but not first iteration
} else if(i % 3 === 0 && i > 0) {
innerHTML = innerHTML + "</tr><tr>";
}
//add cells to the table
innerHTML = innerHTML + "<td><button class=\"btn\" data-img=" + imag[i] + " onclick=\"showImage(this);\">" + date[i] + "</button></td>";
if(i === lastIndex) {
//last iteration? how many <td>?
//always finish with 3 cells
if((i+1) % 3 === 0) {
innerHTML = innerHTML + "</tr>";
} else if((i+1) % 2 === 0) {
innerHTML = innerHTML + "<td></td></tr>";
} else {
innerHTML = innerHTML + "<td></td></tr>";
}
}
}
innerHTML = innerHTML + innerHTMLEnd;
var element = overlay.getElement();
element.innerHTML=innerHTML
;
map.addOverlay(overlay);
}
} else{
var element = overlay.getElement();
element.innerHTML = null;
overlay.setPosition(undefined);
}
};
map.on('click', function(event) {
var pixel = event.pixel;
displayImage(pixel);
});
And here is the code of creating the layers (same code for both just different variable-names):
var FotoSource = new ol.source.Vector();
$.getJSON('config/Fotos.json', function (data) {
$.each(data, function(key, val){
var newMarker = new ol.Feature({
geometry: new ol.geom.Point([this.Longitude,this.Latitude]),
pointAttributes: {'Datum': this.Datum, 'Image':this.Name}
});
FotoSource.addFeature(newMarker);
});
});
var FotoStyle = new ol.style.Style({
image: new ol.style.Icon({
src: 'images/camera2.png',
class: "Fotoicon"
})
});
I am adding them as new ol.layer.Vector with Fotosource as source.

How to hide a HTML class for a selected item in google org chart?

I have a google org chart and here is my source code in JSFIDDLE and I have this function
if(collapsed == 0)
{
$(function(){
$(".plus").hide();
});
}
I have a class named plus and it has a picture that displays for every node but I want if a node has no child item I want that class (plus image) to be hidden only for the item that has no child items. In my case it hides for all nodes and I don't need it.
In my case person Carol has no children and he does not need to has plus image same with Alice
Thank you
you are hiding element using a class selector, it will hide all the elements where this class is present, you should identify the node with some other selector
if you need to hide the node , then you will have to updated the data itself, here is how you can go for it.
updated fiddle : http://jsfiddle.net/w8Ytq/102/
var runOnce = google.visualization.events.addListener(chart, 'ready', function() {
// set up + sign event handlers
var previous;
$('#chart_div').on('click', 'div.plus', function () {
var selection = chart.getSelection();
var row;
if (selection.length == 0) {
row = previous;
}
else {
row = selection[0].row;
previous = row;
}
var collapsed = chart.getCollapsedNodes();
var collapse = (collapsed.indexOf(row) == -1);
chart.collapse(row, collapse);
chart.setSelection();
// get a new list of collapsed nodes
collapsed = chart.getCollapsedNodes();
// set up event listener to recollapse nodes after redraw
var runOnce2 = google.visualization.events.addListener(chart, 'ready', function() {
google.visualization.events.removeListener(runOnce2);
for (var i = 0; i < collapsed.length; i++) {
chart.collapse(collapsed[i], true);
}
});
var children =chart.getChildrenIndexes(row);
for(var i=0;i< children.length;i++)
{
var childrenOfChildren = chart.getChildrenIndexes(children[i]);
if(childrenOfChildren == "")
{
var col1 = data.getValue(children[i],0);
var col2 = data.getValue(children[i],1);
var col3 = data.getValue(children[i],2);
data.removeRow(children[i]);
data.insertRows(children[i], [[col1, col2, col3]]);
}
}
// redraw the chart to account for the change in the sign
chart.draw(data, options);
});

Return String variable delayed

I have a web page with a table identified by "table-rank" class.
This table has 3 pages : to go on next page, i use the .click function on the net button.
I tried to build a finalHtml string by capturing specific element in each lines of the table.
Problem : The finalHtml contains 3 times the datas of the first page of the table. No data of the second page and third page is captured..
It's because, when i click on the button, it's asynchronous, and the first loop continue to parse the table which have not been already refreshed. How can i do, to be sure that the data have been loaded when i click on the next button ?
function getClassement() {
var finalHtml = '' ;
for (var k=0; k<2; k++) {
for (var i=0; i<$(".table-rank tbody tr").length; i++) {
var currentLine = $(".table-rank tbody tr")[i];
// Get position
var positionEl = $(currentLine).find(".ng-binding")[0];
var position = $(positionEl).text();
// Get id
var idEl = $(currentLine).find(".ng-binding")[1];
var id = $(idEl).text();
// Get prenom/nom
var nomPrenom = "test";
// Nombre de paris
var nbrParisEl = $(currentLine).find(".ng-binding")[2];
var nbParis = $(nbrParisEl).text();
// Nombre de bons paris
var nbrBonsParisEl = $(currentLine).find(".ng-binding")[3];
var nbBonsParis = $(nbrBonsParisEl).text();
// Score exacts
var scoresExactsEl = $(currentLine).find(".ng-binding")[4];
var scoresExacts = $(scoresExactsEl).text();
// Points totals
var pointsTotalEl = $(currentLine).find(".ng-binding")[7];
var pointsTotals = $(pointsTotalEl).text();
finalHtml = finalHtml + "<tr><td><span class='position'>"+position+"</span></td><td>"+id+"</td><td>"+nomPrenom+"</td><td>"+nbParis+"</td><td>"+nbBonsParis+"</td><td>"+scoresExacts+"</td><td><span class='total'>"+pointsTotals+"</span></td></tr>";
}
// Go next page
$(".pager a")[1].click();
}
return finalHtml;
}

Categories

Resources