Google Charts: Dynamically Add Series to Chart - charts

There is a similar thread here but the answer isn't clear and there is no clear example to follow.
I need to dynamically add series to a Google Charts graph. Suppose each successive click of a button should add a new series from my array. Right now it just replaces it. What should I do?
// Init
google.charts.load('current', {'packages':['corechart']});
var currentclick = 0;
// My Data
var titles = ['Year1','Year2','Year3','Year4'];
var nums = [ [44,12,33,22], [33,11,7,8], [2,1,65,44] ];
$('#addSeries').click(function() {
if (currentclick < 3) {
draw(nums, currentclick);
}
currentclick++;
});
function draw(arr, seriesnum) {
var chartData = new google.visualization.DataTable();
chartData.addColumn('string', 'Year');
chartData.addColumn('number', 'Value');
var chartRowArray = $.makeArray();
for (var i = 0; i < 4; i++) {
chartRowArray.push( [ titles[i], arr[seriesnum][i] ] );
}
chartData.addRows(chartRowArray);
var chart = new google.visualization.LineChart(document.getElementById('chartarea'));
chart.draw(chartData, null);
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="addSeries">Add Next Series</button>
<div id="chartarea">
</div>

You want to add a line chart from the data of nums every time when you clicked "Add Next Series" button.
If my understanding is correct, how about this modification? I think that there are several answers for your situation. So please think of this as just one of several answers.
Modification point:
In this modification, chartRowArray is declared as a global variable. When the button is clicked, the data is added to chartRowArray. By this, the line chars are added to the existing chart.
Modified script:
// Init
google.charts.load('current', {'packages':['corechart']});
var currentclick = 0;
// My Data
var titles = ['Year1','Year2','Year3','Year4'];
var nums = [ [44,12,33,22], [33,11,7,8], [2,1,65,44] ];
$('#addSeries').click(function() {
if (currentclick < 3) {
draw(nums, currentclick);
}
currentclick++;
});
// Below script was modified.
var chartRowArray = $.makeArray(); // Added
function draw(arr, seriesnum) {
var chartData = new google.visualization.DataTable();
chartData.addColumn('string', 'Year');
for (var i = 0; i < seriesnum + 1; i++) { // Added
chartData.addColumn('number', 'Value');
}
if (seriesnum === 0) { // Added
for (var i = 0; i < 4; i++) {
chartRowArray.push( [ titles[i], arr[seriesnum][i] ] );
}
} else { // Added
for (var i = 0; i < 4; i++) {
chartRowArray[i].push(arr[seriesnum][i]);
}
}
chartData.addRows(chartRowArray);
var chart = new google.visualization.LineChart(document.getElementById('chartarea'));
chart.draw(chartData, null);
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="addSeries">Add Next Series</button>
<div id="chartarea"></div>
If I misunderstood your question and this was not the result you want, I apologize.

The key thing is the final structure of the data.
The Column Array should be
0: Year
1: Value
2: Value
etc.
The Column array is added 1-by-1 as chartData.addColumn(type, name).
The data's Row Array should be
0:
0: Year1
1: 44
2: 33
1:
0: Year2
1: 12
2: 11
etc.
The Row Array is added in one shot as chartData.addRows(rowArray).
Knowing this, I made both RowArray / ColArray global variables that get modified on the fly (thanks for the idea Tainake) and for the initial conditions when the arrays are empty, I construct or initialize them for the first time.
Working example below. Thanks again for the help!
// Init
google.charts.load('current', {'packages':['corechart']});
var currentclick = 0;
// My Data
var titles = ['Year1','Year2','Year3','Year4'];
var nums = [ [44,12,33,22], [33,11,7,8], [2,1,65,44] ];
var chartColArray = $.makeArray(); // Global var
var chartRowArray = $.makeArray(); // Global var
$('#addSeries').click(function() {
if (currentclick < 3) {
draw(nums, currentclick);
}
currentclick++;
});
function draw(arr, seriesnum) {
var chartData = new google.visualization.DataTable();
// For initial Column, push 'Year'; otherwise, push 'Value'
if (chartColArray.length == 0) {
chartColArray.push('Year');
}
chartColArray.push('Value');
// addColumn() has to be 1-by-1-by-1, there is no addColumns(colarray)
$.each(chartColArray, function(index, item) {
(index == 0 ? chartData.addColumn('string', item) : chartData.addColumn('number', item));
});
for (var i = 0; i < 4; i++) {
// For initial Row subarray, create subarray and push 'Year Series';
// otherwise, push actual 'Series' value
if (chartRowArray[i] == undefined) {
chartRowArray[i] = $.makeArray();
chartRowArray[i].push(titles[seriesnum]);
}
chartRowArray[i].push(nums[seriesnum][i]);
}
chartData.addRows(chartRowArray);
// Instantiate and draw the chart.
var chart = new google.visualization.LineChart(document.getElementById('chartarea'));
chart.draw(chartData, null);
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="addSeries">Add Next Series</button>
<div id="chartarea">
</div>

Related

My javascript wont work on my chrome app

I am developing a chrome app (on chromebook) and the javascript wont load. Is it the code, the fact that it is an app? Can someone help? Here is my code:
<!DOCTYPE html>
<html>
<body>
<h1>Cash Register</h1>
//inputs
<input type="text" id="myText1" value="Name">
<input type="text" id="myText2" value="Price">
//add button
<button onclick="add()">Add</button>
//The total goes here
<div id="div1"><h2 id="demo1"><h2 id="demo2"></h2></div>
<script>
//my function
function add() {
//variables
var x = 'Total:'
var y = document.getElementById("myText2").value;
var z = document.getElementById("myText1").value;
//writes the items you enter
//makes a separating line
var para = document.createElement("h4");
var node = document.createTextNode('_____');
para.appendChild(node);
var element = document.getElementById("div1");
element.appendChild(para);
//makes the item
var para = document.createElement("h4");
var node = document.createTextNode(z);
para.appendChild(node);
var element = document.getElementById("div1");
element.appendChild(para);
//makes the price
var para = document.createElement("p");
var node = document.createTextNode(y);
para.appendChild(node);
var element = document.getElementById("div1");
element.appendChild(para);
//writes "Total (total price)"
var w = document.getElementsByTagName("p"); // this gets all the P's as an object
// setup a total starting at 0
var total = 0;
for (var i = 0; i < w.length; i++) {
total += parseInt(w[i].innerText); // make the inner text an integer for addition.
}
document.getElementById("demo1").innerHTML = x;
document.getElementById("demo2").innerHTML = total; // replace w with total
}
</script>
</body>
</html>
Anything would help. If you have a solution, please shoe it to me! Thank you. By the way, I am new at this. When I put it through jshint, this is what I got:
(Image)
So, all I had to do was add
//my function
function add() {
//variables
var x = 'Total:';
var y = document.getElementById("myText2").value;
var z = document.getElementById("myText1").value;
//writes the items you enter
//makes a separating line
var para = document.createElement("h4");
var node = document.createTextNode('_____');
para.appendChild(node);
var element = document.getElementById("div1");
element.appendChild(para);
//makes the item
para = document.createElement("h4");
node = document.createTextNode(z);
para.appendChild(node);
element = document.getElementById("div1");
element.appendChild(para);
//makes the price
para = document.createElement("p");
node = document.createTextNode(y);
para.appendChild(node);
element = document.getElementById("div1");
element.appendChild(para);
//writes "Total (total price)"
var w = document.getElementsByTagName("p"); // this gets all the P's as an object
// setup a total starting at 0
var total = 0;
for (var i = 0; i < w.length; i++) {
total += parseInt(w[i].innerText); // make the inner text an integer for addition.
}
document.getElementById("demo1").innerHTML = x;
document.getElementById("demo2").innerHTML = total; // replace w with total
}
document.addEventListener('DOMContentLoaded', function() {
var link = document.getElementById('link');
// onClick's logic below:
link.addEventListener('click', function() {
add()
});
});
to a javascript file and change my script tag to
<script src="add.js"></script>
If anyone wants to use this, here is the code:
Manifest:
{
"name": "Cash register",
"description": "Use at garage sales",
"version": "0.1",
"manifest_version": 2,
"app": {
"background": {
"scripts": ["background.js"]
}
},
"icons": { "16": "calculator-16.png", "128": "calculator-128.png" }
}
background.js:
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('index.html', {
'outerBounds': {
'width': 400,
'height': 500
}
});
});
add.js:
//my function
function add() {
//variables
var x = 'Total:';
var y = document.getElementById("myText2").value;
var z = document.getElementById("myText1").value;
//writes the items you enter
//makes a separating line
var para = document.createElement("h4");
var node = document.createTextNode('_____');
para.appendChild(node);
var element = document.getElementById("div1");
element.appendChild(para);
//makes the item
para = document.createElement("h4");
node = document.createTextNode(z);
para.appendChild(node);
element = document.getElementById("div1");
element.appendChild(para);
//makes the price
para = document.createElement("p");
node = document.createTextNode(y);
para.appendChild(node);
element = document.getElementById("div1");
element.appendChild(para);
//writes "Total (total price)"
var w = document.getElementsByTagName("p"); // this gets all the P's as an object
// setup a total starting at 0
var total = 0;
for (var i = 0; i < w.length; i++) {
total += parseInt(w[i].innerText); // make the inner text an integer for addition.
}
document.getElementById("demo1").innerHTML = x;
document.getElementById("demo2").innerHTML = total; // replace w with total
}
document.addEventListener('DOMContentLoaded', function() {
var link = document.getElementById('link');
// onClick's logic below:
link.addEventListener('click', function() {
add()
index.html:
<html>
<head>
<script src="add.js"></script>
</head>
<body>
<h1>Cash Register</h1>
<input type="text" id="myText1" value="Name">
<input type="text" id="myText2" value="Price (numbers only)">
<a id="link">Add</a>
<div id="div1"><h2 id="demo1"></h2><h2 id="demo2"></h2></div>
</body></html>
Thanks Everyone!!!
Ok, you are creating the same elements with the same name three times and I don't see how you are using the additional tags and elements but that is most likely the reason an error is being thrown.
Try putting your code through http://jshint.com and you'll see your issues.

How to draw a custom polygon over a Scatter Series google chart?

I have a Scatter Series with a set of points, like the one shown here. https://developers.google.com/chart/interactive/docs/gallery/scatterchart
The points are grouped and each group is shown in different color. I would like to draw a polygon around each group (convex hull). Looks like there is not a straightforward way to add polygons each with n boundary-points to the chart.
if you have an algorithm to find the boundary points,
you can use a ComboChart to draw both the scatter and line series...
use option seriesType to set the default type
use option series to customize the type for a particular series
in the following working snippet,
the algorithm used was pulled from --> Convex Hull | Set 1 (Jarvis’s Algorithm or Wrapping)
(converted from the Java version)
google.charts.load('current', {
packages: ['corechart']
}).then(function () {
var groupA = [
[0,3],[2,3],[1,1],[2,1],[3,0],[0,0],[3,3],[2,2]
];
var groupB = [
[11,11],[12,12],[12,10],[12,14],[13,13],[14,12],[15,12],[16,12]
];
var data = new google.visualization.DataTable();
data.addColumn('number', 'x');
data.addColumn('number', 'y');
data.addRows(groupA);
data.addRows(groupB);
addGroup('A', data, groupA)
addGroup('B', data, groupB)
var options = {
chartArea: {
bottom: 48,
height: '100%',
left: 36,
right: 24,
top: 36,
width: '100%'
},
height: '100%',
seriesType: 'line',
series: {
0: {
type: 'scatter'
}
},
width: '100%'
};
var chart = new google.visualization.ComboChart(document.getElementById('chart_div'));
drawChart();
window.addEventListener('resize', drawChart, false);
function drawChart() {
chart.draw(data, options);
}
function addGroup(group, dataTable, points) {
var polygon = convexHull(points);
var colIndex = dataTable.addColumn('number', group);
for (var i = 0; i < polygon.length; i++) {
var rowIndex = dataTable.addRow();
dataTable.setValue(rowIndex, 0, polygon[i][0]);
dataTable.setValue(rowIndex, colIndex, polygon[i][1]);
}
}
function orientation(p, q, r) {
var val = (q[1] - p[1]) * (r[0] - q[0]) -
(q[0] - p[0]) * (r[1] - q[1]);
if (val == 0) {
return 0; // collinear
} else if (val > 0) {
return 1; // clock wise
} else {
return 2; // counterclock wise
}
}
function convexHull(points) {
// must be at least 3 rows
if (points.length < 3) {
return;
}
// init
var l = 0;
var p = l;
var q;
var hull = [];
// find leftmost point
for (var i = 1; i < points.length; i++) {
if (points[i][0] < points[l][0]) {
l = i;
}
}
// move counterclockwise until start is reached
do {
// add current point to result
hull.push(points[p]);
// check orientation (p, x, q) of each point
q = (p + 1) % points.length;
for (var i = 0; i < points.length; i++) {
if (orientation(points[p], points[i], points[q]) === 2) {
q = i;
}
}
// set p as q for next iteration
p = q;
} while (p !== l);
// add back first hull point to complete line
hull.push(hull[0]);
// set return value
return hull;
}
});
html, body, #chart_div {
height: 100%;
}
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="chart_div"></div>

css/javascript multiple card flip: reset other cards

So I'm currently using this one: http://jsfiddle.net/nawdpj5j/10/
Now what I need is that when I flip one card (doesn't matter which one) and then flip another one the first one resets/turnes back.
I think I need to add something in here:
var init = function() {
var flippers = document.getElementsByClassName("flip");
for(i = 0; i < flippers.length; i++){
flippers[i].addEventListener( 'click', function(){
var cardID = this.dataset.targetid;
var card = document.getElementById(cardID);
card.toggleClassName('flipped');
}, false);
}
};
Thank you in advance!
You can get an array of all flipped cards and flip them back whenever a card is flipped like so:
var init = function() {
var flippers = document.getElementsByClassName("flip");
for (i = 0; i < flippers.length; i++) {
flippers[i].addEventListener('click', function() {
var cardID = this.dataset.targetid;
var card = document.getElementById(cardID);
var flipped = document.getElementsByClassName('flipped');
for (i = 0; i < flipped.length; i++) {
if (card !== flipped[i]) {
flipped[i].toggleClassName('flipped');
}
}
card.toggleClassName('flipped');
}, false);
}
};
window.addEventListener('DOMContentLoaded', init, false);
Here is a link to a working demo JS FIDDLE

Inverting rows and columns on Google Area Chart

When I use an Area Chart on Google Drive, I can select an option to "Switch Rows / Columns".
Now that I am playing with the Javascript API, I'd like to do the same but couldn't find a way to do it in the documentation.
Here's the code I am using successfully. All I need is to switch row/column on the API.
<script type="text/javascript">
google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['data',0,1,2,3,4,5,6]
,['2013-04-14 (336)',064,04,03,02,06,02,02]
,['2013-04-21 (169)',0,028,03,02,04,02,02]
,['2013-04-28 (121)',0,0,027,02,01,02,02]
,['2013-05-05 (101)',0,0,0,020,0,01,0]
,['2013-05-12 (688)',0,0,0,0,0143,017,07]
,['2013-05-19 (3226)',0,0,0,0,0,0642,022]
,['2013-05-26 (321)',0,0,0,0,0,0,082]
]);
var options = {
title: 'Company Performance', isStacked:true,
hAxis: {title: 'Year', titleTextStyle: {color: 'red'}}
};
var chart = new google.visualization.AreaChart(document.getElementById('chart_div'));
chart.draw(data, options);
}
</script>
Can anyone help?
Unfortunately you have to transpose the DataTable. The following code will do the job. If anyone can improve it, please share improved version.
This function would work passing a DataView as well.
In your case: var transposedData = transposeDataTable(data);
function transposeDataTable(dataTable) {
//step 1: let us get what the columns would be
var rows = [];//the row tip becomes the column header and the rest become
for (var rowIdx=0; rowIdx < dataTable.getNumberOfRows(); rowIdx++) {
var rowData = [];
for( var colIdx = 0; colIdx < dataTable.getNumberOfColumns(); colIdx++) {
rowData.push(dataTable.getValue(rowIdx, colIdx));
}
rows.push( rowData);
}
var newTB = new google.visualization.DataTable();
newTB.addColumn('string', dataTable.getColumnLabel(0));
newTB.addRows(dataTable.getNumberOfColumns()-1);
var colIdx = 1;
for(var idx=0; idx < (dataTable.getNumberOfColumns() -1);idx++) {
var colLabel = dataTable.getColumnLabel(colIdx);
newTB.setValue(idx, 0, colLabel);
colIdx++;
}
for (var i=0; i< rows.length; i++) {
var rowData = rows[i];
console.log(rowData[0]);
newTB.addColumn('number',rowData[0]); //assuming the first one is always a header
var localRowIdx = 0;
for(var j=1; j< rowData.length; j++) {
newTB.setValue(localRowIdx, (i+1), rowData[j]);
localRowIdx++;
}
}
return newTB;
}
Source and credit:
http://captaindanko.blogspot.sg/2013/05/transpose-of-google-visualization-data.html
Example:
https://bitbucket.org/cptdanko/blog-code/src/0666cdce533db48cd89a4e2f02ef7e87a891c857/transpose.html?at=default
A neater and more efficient version with use of the getDate function on the first column label.
Here's a nice and verbose edition commented to explain what's going on -
function transposeDateDataTable (dataTable) {
// Create new datatable
var newDataTable = new google.visualization.DataTable ();
// Add first column from original datatable
newDataTable.addColumn ('string', dataTable.getColumnLabel (0));
// Convert column labels to row labels
for (var x=1; x < dataTable.getNumberOfColumns (); x++) {
var label = dataTable.getColumnLabel (x);
newDataTable.addRow ([label]);
}
// Convert row labels and data to columns
for (var x=0; x < dataTable.getNumberOfRows (); x++) {
newDataTable.addColumn ('number', dataTable.getValue (x, 0).getDate ()); // Use first column date as label
for (var y=1; y < dataTable.getNumberOfColumns (); y++) {
newDataTable.setValue (y-1, x+1, dataTable.getValue (x, y));
}
}
return newDataTable;
}
Or the nice and compact version...
function transposeDateDataTable (dt) {
var ndt = new google.visualization.DataTable;
ndt.addColumn ('string',dt.getColumnLabel(0));
for (var x=1; x<dt.getNumberOfColumns(); x++)
ndt.addRow ([dt.getColumnLabel(x)]);
for (var x=0; x<dt.getNumberOfRows(); x++) {
ndt.addColumn ('number', dt.getValue(x,0).getDate());
for (var y=1; y<dt.getNumberOfColumns(); y++)
ndt.setValue (y-1, x+1, dt.getValue (x,y));
}
return ndt;
}

Getting cursor position in a Textarea

I am trying to implement Autocomplete in a text area (similar to http://www.pengoworks.com/workshop/jquery/autocomplete.htm).
What I am trying to do is when a user enters a specific set of characters (say insert:) they will get an AJAX filled div with possible selectable matches.
In a regular text box, this is of course simple, but in a text area I need to be able to popup the div in the correct location on the screen based on the cursor.
Can anyone provide any direction?
Thanks,
-M
You can get the caret using document.selection.createRange(), and then examining it to reveal all the information you need (such as position). See those examples for more details.
Implementing an autocomplete in a text area is not that easy. I implemented a jquery plugin that does that, and i had to create a clone of the texarea to guess where the cursor is positioned inside the textarea.
Its working, but its not perfect.
You can check it out here: http://www.amirharel.com/2011/03/07/implementing-autocomplete-jquery-plugin-for-textarea/
I hope it helps.
function getCursor(nBox){
var cursorPos = 0;
if (document.selection){
nBox.focus();
var tmpRange = document.selection.createRange();
tmpRange.moveStart('character',-nBox.value.length);
cursorPos = tmpRange.text.length;
}
else{
if (nBox.selectionStart || nBox.selectionStart == '0'){
cursorPos = nBox.selectionStart;
}
}
return cursorPos;
}
function detectLine(nBox,lines){
var cursorPos = getCursor(nBox);
var z = 0; //Sum of characters in lines
var lineNumber = 1;
for (var i=1; i<=lines.length; i++){
z = sumLines(i)+i; // +i because cursorPos is taking in account endcharacters of each line.
if (z >= cursorPos){
lineNumber = i;
break;
}
}
return lineNumber;
function sumLines(arrayLevel){
sumLine = 0;
for (var k=0; k<arrayLevel; k++){
sumLine += lines[k].length;
}
return sumLine;
}
}
function detectWord(lineString, area, currentLine, linijeKoda){
function sumWords(arrayLevel){
var sumLine = 0;
for (var k=0; k<arrayLevel; k++){
sumLine += words[k].length;
}
return sumLine;
}
var cursorPos = getCursor(area);
var sumOfPrevChars =0;
for (var i=1; i<currentLine; i++){
sumOfPrevChars += linijeKoda[i].length;
}
var cursorLinePos = cursorPos - sumOfPrevChars;
var words = lineString.split(" ");
var word;
var y = 0;
for(var i=1; i<=words.length; i++){
y = sumWords(i) + i;
if(y >= cursorLinePos){
word = i;
break;
}
}
return word;
}
var area = document.getElementById("area");
var linijeKoda = area.value.split("\n");
var currentLine = detectLine(area,linijeKoda);
var lineString = linijeKoda[currentLine-1];
var activeWord = detectWord(lineString, area, currentLine, linijeKoda);
var words = lineString.split(" ");
if(words.length > 1){
var possibleString = words[activeWord-1];
}
else{
var possibleString = words[0];
}
That would do it ... :)
an ugly solution:
for ie: use document.selection...
for ff: use a pre behind textarea, paste text before cursor into it, put a marker html element after it (cursorPos), and get the cursor position via that marker element
Notes: | code is ugly, sorry for that | pre and textarea font must be the same | opacity is utilized for visualization | there is no autocomplete, just a cursor following div here (as you type inside textarea) (modify it based on your need)
<html>
<style>
pre.studentCodeColor{
position:absolute;
margin:0;
padding:0;
border:1px solid blue;
z-index:2;
}
textarea.studentCode{
position:relative;
margin:0;
padding:0;
border:1px solid silver;
z-index:3;
overflow:visible;
opacity:0.5;
filter:alpha(opacity=50);
}
</style>
hello world<br/>
how are you<br/>
<pre class="studentCodeColor" id="preBehindMyTextarea">
</pre>
<textarea id="myTextarea" class="studentCode" cols="100" rows="30" onkeyup="document.selection?ieTaKeyUp():taKeyUp();">
</textarea>
<div
style="width:100px;height:60px;position:absolute;border:1px solid red;background-color:yellow"
id="autoCompleteSelector">
autocomplete contents
</div>
<script>
var myTextarea = document.getElementById('myTextarea');
var preBehindMyTextarea = document.getElementById('preBehindMyTextarea');
var autoCompleteSelector = document.getElementById('autoCompleteSelector');
function ieTaKeyUp(){
var r = document.selection.createRange();
autoCompleteSelector.style.top = r.offsetTop;
autoCompleteSelector.style.left = r.offsetLeft;
}
function taKeyUp(){
taSelectionStart = myTextarea.selectionStart;
preBehindMyTextarea.innerHTML = myTextarea.value.substr(0,taSelectionStart)+'<span id="cursorPos">';
cp = document.getElementById('cursorPos');
leftTop = findPos(cp);
autoCompleteSelector.style.top = leftTop[1];
autoCompleteSelector.style.left = leftTop[0];
}
function findPos(obj) {
var curleft = curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
}
return [curleft,curtop];
}
//myTextarea.selectionStart
</script>
</html>