I'm new to Google Apps Script so am just exploring if what I want to achieve is possible.
From a Google form, I need to retrieve and display on a separate document the chart created from data on each individual form submission. I know this can be done.
The problem I have is that the chart type I want does not seem to be available here.
The chart needs to show a category and two values. This could be done with a bar chart, height is one value and colour the other value - this looks as though it might be possible but I am not sure if the colour of the whole bar can be changed.
An alternative is the bubble chart, X axis for category, Y axis for one value and size for the other value - but this type of chart does not seem to be supported.
You can display any of the 25+ chart types provided by the Google Visualization API within the Google Apps Script HTML Service.
Below is a modified version of the Bubble Chart example. Instead of fixed data, we'll pull data from a spreadsheet. The chart will be displayed in a modal dialog, as an add-on within that spreadsheet.
The source data:
A B C D E
ID Life Expectancy Fertility Rate Region Population
CAN 80.66 1.67 North America 33739900
DEU 79.84 1.36 Europe 81902307
DNK 78.6 1.84 Europe 5523095
EGY 72.73 2.78 Middle East 79716203
GBR 80.05 2 Europe 61801570
IRN 72.49 1.7 Middle East 73137148
IRQ 68.09 4.77 Middle East 31090763
ISR 81.55 2.96 Middle East 7485600
RUS 68.6 1.54 Europe 141850000
USA 78.09 2.05 North America 307007000
Client Side
The rest of the design is pretty straight-forward, but for Apps Script programmers who aren't used to javascript use in the HTML service, especially the behaviour of asynchronous function calls & call-backs, it's what's happening in the client side code that's most interesting. Here's the basic flow.
Present html page with a placeholder for the visualization.
Load external JavaScript libraries. We'll be using jQuery (for easy manipulation of DOM) and of course Google's JavaScript API, aka jsapi, for the visualization objects.
When the page loads, request a callback. We'll call that sendQuery(), as it will retrieve our spreadsheet data. This is a different approach than the original example that simply displayed a chart, because we're not just using hard-coded data.
When the jsapi finishes loading, sendQuery() is called. It requests our data, and passes the asynchronous response to another callback, drawSeriesChart().
Once data is received by drawSeriesChart(), draw the chart.
Options for retrieving data from spreadsheet
Since our visualization will be run in a browser (aka client-side), we need to get the info from the spreadsheet (aka server-side). Depending upon your specific needs, there are a few ways to retrieve that data.
Query via visualization API.
For a published spreadsheet, this is a very flexible way to retrieve data. Your client-side js can specify the range of data you're interested in, and you can utilize the Query Language to manipulate the data you'll display without modifying the source spreadsheet.
function sendQuery() {
var opts = {sendMethod: 'auto'};
var sheetId = "--- your sheet ID ---";
var dataSourceUrl = 'https://spreadsheets.google.com/tq?key=%KEY%&pub=1'
.replace("%KEY%",sheetId);
var query = new google.visualization.Query(dataSourceUrl, opts);
// Specify query string, if desired.
// Send the query with a callback function.
query.send(drawSeriesChart);
}
Handy for situations where you don't own the source data, for example
Create a web service that will feed the spreadsheet data. This approach keeps the spreadsheet itself private.
Use direct communication between the server & client side scripts, via google.script.run. This way, the spreadsheet remains private. This example is very simple, as it gleans the entire spreadsheet, but you could extend it to manipulate your data by filtering, sorting, or adding further metadata for formatting.
function sendQuery() {
// Send the query with a callback function.
google.script.run
.withSuccessHandler(drawSeriesChart)
.getSpreadsheetData();
}
This requires that function getSpreadsheetData() be implemented on the server side to return the desired data. That's shown in the actual code that follows.
Code.gs
Other than the usual yada-yada for menu creation, this file implements a getSpreadsheetData() function that we'll use to retrieve all the data from a sheet.
/**
* Adds a custom menu with items to show the sidebar and dialog.
*
* #param {Object} e The event parameter for a simple onOpen trigger.
*/
function onOpen(e) {
SpreadsheetApp.getUi()
.createAddonMenu()
.addItem('Bubble Chart Example', 'showBubbleEx')
.addToUi();
}
/**
* Runs when the add-on is installed; calls onOpen() to ensure menu creation and
* any other initializion work is done immediately.
*
* #param {Object} e The event parameter for a simple onInstall trigger.
*/
function onInstall(e) {
onOpen(e);
}
/**
* Opens a dialog for a visualization.
*/
function showBubbleEx() {
var ui = HtmlService.createTemplateFromFile('BubbleEx')
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.IFRAME)
.setWidth(450)
.setHeight(350);
SpreadsheetApp.getUi().showModalDialog(ui, "Bubble Chart Example");
}
/**
* Return all data from first spreadsheet as an array. Can be used
* via google.script.run to get data without requiring publication
* of spreadsheet.
*
* Returns null if spreadsheet does not contain more than one row.
*/
function getSpreadsheetData() {
var data = SpreadsheetApp.getActive().getSheets()[0].getDataRange().getValues();
return (data.length > 1) ? data : null;
}
BubbleEx.html
This was adapted from the "Sheets add-on" template, and refers to the Stylesheet.html file included there.
<!-- Use a templated HTML printing scriptlet to import common stylesheet. -->
<?!= HtmlService.createHtmlOutputFromFile('Stylesheet').getContent(); ?>
<!-- Below is the HTML code that defines the dialog element structure. -->
<div>
<div id="series_chart_div" style="width: 400px; height: 300px;"></div>
<div class="block" id="dialog-button-bar">
<button id="dialog-cancel-button" onclick="google.script.host.close()">Cancel</button>
</div>
</div>
<!-- Use a templated HTML printing scriptlet to import JavaScript. -->
<?!= HtmlService.createHtmlOutputFromFile('BubbleExJavaScript').getContent(); ?>
BubbleExJavaScript.html
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script>
// Load the Visualization API and desired package(s).
google.load('visualization', '1.0', {'packages':['corechart']});
/**
* Run initializations on dialog load.
*/
$(function() {
// Set a callback to run when the Google Visualization API is loaded.
google.setOnLoadCallback(sendQuery);
// Assign handler functions to dialog elements here, if needed.
// Call the server here to retrieve any information needed to build
// the dialog, if necessary.
});
/**
* Issue asynchronous request for spreadsheet data.
*/
function sendQuery() {
google.script.run
.withSuccessHandler(drawSeriesChart)
.getSpreadsheetData();
}
/**
* Callback function to generate visualization using data in response parameter.
*/
function drawSeriesChart(response) {
if (response == null) {
alert('Error: Invalid source data.')
return;
}
else {
var data = google.visualization.arrayToDataTable(response,false);
var options = {
title: 'Correlation between life expectancy, fertility rate and population of some world countries (2010)',
hAxis: {title: data.getColumnLabel(1)}, // 'Life Expectancy'
vAxis: {title: data.getColumnLabel(2)}, // 'Fertility Rate'
bubble: {textStyle: {fontSize: 11}}
};
var chart = new google.visualization.BubbleChart(document.getElementById('series_chart_div'));
chart.draw(data, options);
}
}
</script>
Related
I am new to Javascript, MVC, and making websites. I am making a company internal site that is used as a GUI for controlling hardware. The computer (Server) is connected to another machine taking measurements (data points). I have successfully created a page that plots this data using the plotly library, but instead of sending all of the data points from the server to the client after all the measurements have been taken, they want one point to be plotted at a time. This means making calls back to the server for each point. The class (object) that contains the functionality to do this is called the MeasurementManager. My first though was to pass this class into the view by ViewBag, then call the function that takes the measurement in a loop using razor syntax. For testing we can say we are taking 10 measurements. Then call the Javascript function that updates the plotly plot also inside of the loop and pass the data from the MeasurementManager to the plotly update function. I'm just not sure how to do this. For argument sake if it is possible to call the MeasurmentManager function inside of a javascript loop, that would be fine as well.
Controller: (Just created the MeasurementManager object and checks if it was created without error. I didn't want to do this error checking in the view with razor syntax. I tried passing the model strictly but I couldn't figure out how to get access to the same instance of the object, so ViewBag was the way to go. This function runs after clicking a button in another view)
[HttpPost]
public IActionResult Calibrate()
{
MeasurementManager measurementManager;
try
{
measurementManager = new MeasurementManager(ref ErrorMessage);
}
catch (Exception ex)
{
ErrorMessage = ex.Message;
return RedirectToAction("ErrorView");
}
ViewBag.MeasurementManager = measurementManager;
return View();
View: (This is the Calibrate view returned from the above function. It is enough code to get a plotly plot with no points plotted, and it contains an update plot function)
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div class="row">
<div class="col-12">
<div id="CalibrationPlot" style="width:100%; height:1000px" class="js-plotly-plot"></div>
<div class="plot-container plotly"></div>
</div>
</div>
<script>
// Define Layout
var layout = {
xaxis: {range: [0, 256], title: "Mux"},
yaxis: {range: [0, 5*Math.pow(10, -10)], title: "Capacitance"},
title: "Capacitance vs Mux"
};
// Display using Plotly
Plotly.newPlot("CalibrationPlot", data, layout);
</script>
<script>
function updatePlot(capacitancePoint, elementPoint)
{
var update = [{
x: elementPoint,
y: capacitancePoint
}];
Plotly.update("CalibrationPlot", update, 0)
}
</script>
What I would like to add to the view is something like this which will measure a point and plot that value on the plotly plot. Hopefully this would achieve making a live plot on the webpage.
#{
for(int i = 0; i < 10; i++)
{
double MeasuredValue = ViewBag.measurementManager.MeasureValue(); // Y Coordinate
int xValue = i; // X Coordinate
// Make a call to Plotly.update (updatePlot function) and pass in these two values here
}
}
Feel free to add any solution that you think would work. I'm new to all of this and this is my first stackoverflow question.
Please use SignalR to update the frontend graph in real-time. OR Try to Blazor maybe that helps also.
I'm having trouble displaying only a single data field via the Google Analytics API.
For example, how would I display the number of users yesterday? Or the number of users in the past week?
I'm assuming "gapi.analytics.report.Data" is the right constructor. I've tried following the "Data" code in Google's Built-in Components Reference but nothing displays on the front-end.
For the "DataChart" code, there's a "container" option that references the HTML element in the DOM that will correctly display the chart.
This "container" option is missing in "Data" - so how do I display a single data field?
EDIT: Here's the code I'm working with.
You can see a pastebin of my code here: https://pastebin.com/M6U0Bd8B
There's also a live staging version of the site here: http://madebychris.ca/dashboard2.html
The first four section ids are all displaying properly but nothing's showing up for the final <p class ="report">
If you are trying to access the raw data numbers, you can just access the response object:
// Set up the request
var report = new gapi.analytics.report.Data({
query: {
ids: 'ga:XXXX',
metrics: 'ga:sessions',
dimensions: 'ga:city'
}
});
// Define what to do with the response data.
report.on('success', function(response) {
console.log(response);
});
// Run the report
report.execute();
For example, the total's for the metrics is response.totalsForAllResults and the rows for each dimension are held in response.rows
You need to select what you want to do with the variables on the report.on("success", function(response){ function. For example:
report.on('success', function(response) {
$("body > main > div > section:nth-child(4) > h2")[0].innerText =
response.totalsForAllResults["ga:sessions"]
});
report.execute();
Note: You should be able to test on the embedded API demo. Copying and pasting the initial code with the console.log should help show what is going on.
I am trying to figure out a way to fetch only the filtered values from a table if a filter is active in Office-JS API.
Right now the only way I have figured to fetch all the table data is from the table range values property:
var table = tables.getItemAt(0);
var tableRange = table.getRange();
tableRange.load("values");
ctx.sync().then(function () {
// This returns all the values from the table, and not only the visible data
var values = tableRange.values;
});
Any ideas on how I can proceed to fetch only the visible values from the table if a filter is active?
From previous experience with Office Interop I have achieved the same by looping through the different Areas of the table range, but I am unable to find the equivalent to Areas in Office-JS.
The upcoming next wave of features as part of Excel JS APIs 1.3 will include a new object "RangeView" that allows you to read only the visible values off the Range object.
Here's a link to the open spec on GitHub - https://github.com/OfficeDev/office-js-docs/tree/ExcelJs_1.3_OpenSpec/excel.
Note that this isn't available just yet, but will be in the near future.
Usage for your case off a table would look like this:
var table = tables.getItemAt(0);
var visibleView = table.getRange().getVisibleView();
ctx.load(visibleView);
ctx.sync().then(function () {
var values = visibleView.values;
});
One way to get only filtered data is through the Binding.getDataAsync method, which takes a filterType parameter.
Office.select("bindings#myTableBinding1").getDataAsync({
coercionType: "table",
filterType: "onlyVisible"
},function(asyncResult){
var values = (asyncResult.value.rows);
});
This code assumes you have already created a binding to the table. If not, you can run the following code first, which uses the table name to call Bindings.addFromNamedItemAsync:
Office.context.document.bindings.addFromNamedItemAsync("Table1","table",{
id: "myTableBinding1"
},function(asyncResult){
// handle errors and call code sample #1
});
Note that the solution above is supported as far back as Excel 2013 because it uses the shared APIs. The Excel-specific API set doesn't yet have the capability to return only unfiltered data.
-Michael Saunders, PM for Office add-ins
I'm using the Google Visualization Javascript API to load a Chart from Google Sheets and display it in a div. My app is hosted on Google App Engine. I provide the URL to the sheet with the parameter gid=1 to specify the second sheet but the chart the gets displayed is the first sheet. Here's my simplified code (it's basically the example code provided in the documentation):
// sheetUrl is the URL of the Google sheet, e.g., http://https://docs.google.com/a/google.com/spreadsheet/ccc?key=0AobNU9T3MusKdGFqRHNJYkFnb3RuSkt4QlE#gid=1
// divId is the id of the <div> element I'm displaying in
google.load('visualization', '1.0', {packages: ['table']});
google.setOnLoadCallback(drawChart);
function drawChart() {
var query = new google.visualization.Query(sheetUrl);
query.send(handleQueryResponse);
}
function handleQueryResponse(response) {
var data = response.getDataTable();
var table = new google.visualization.Table(document.getElementById(divId));
table.draw(data);
}
You can see the #gid=1 in the URL. I've also tried &gid=1 and &sheet='Volume', which is the name of the tab but when the page loads, the data from the first tab gets rendered.
I have noticed Google sheet urls in the form I have above but also in this form:
https://docs.google.com/spreadsheet/tq?key=0AobNU9T3MusKdGFqRHNJYkFnb3RuSkt4QlE
I haven't been able to find any documentation explicitly explaining the tq endpoint. I tried using a URL in this form but I get timeout error when trying to load the chart. Any one run into this problem or have insight in the tq thing? Thanks!
Edit 2014-02-17:
I've changed my URL to use the tq endpoint and I've tried the following parameters:
#gid=1
&gid=1
#sheet=Volume
&sheet=Volume
When I query for the url in the browser:
https://docs.google.com/spreadsheet/tq?key=0AobNU9T3MusKdGFqRHNJYkFnb3RuSkt4QlE&sheet=Volume
I get the appropriate sheet back. But when I use the Visualization API to query, I get the first sheet.
The correct URL to pass to the Visualization API that will work with the new Google Sheets is this format:
https://docs.google.com/spreadsheets/d/{key}/gviz/tq
You can use the gid query parameter to pass the ID of the worksheet you want to retrieve. So if your gid is 0, the URL would be:
https://docs.google.com/spreadsheets/d/{key}/gviz/tq?gid=0
You can find additional information regarding URL formats for both the old and new Google Sheets in this bug report.
Hope that helps.
Referencing by sheet name also works as in
https://docs.google.com/spreadsheets/d/{key}/gviz/tq?sheet=MySheetName
Make sure spreadsheet is "new" vs "old" Google Sheets. I banged my head on that one as I thought my sheet was new. Once I created a new style sheet and used donnapep answer I was working again. I then confirmed using sheet=sheetname works as well which was what I was really after.
For a new project I'm working on, I am considering using Shield Chart for ASP.NET MVC to display some user stats. I have successfully rendered a shield javascript chart from my razor view using the Html.ShieldChart() wrapper methods. The actual data for the chart is coming from a local rest service in JSON format. The thing is, I can't quite manage to make the chart display the JSON data. The Shield UI demos show one approach for binding the MVC chart to remote web service, but my scenario is quite different.
In our web portal, we have implemented a REST service under url /api/users/12652/stats that will return some JSON stats for a user with the given ID. The responsone contains the sessions property that lists the user's sessions for the current month:
{
sessions: [{ start: 1379624400000, end: 1377023690271 }, { ... }]
}
The difference between start and end times gives the duration of the session.I need to display a bar chart that will show the duration of all sessions in a line on the Y axis, while the X axis will contain all session dates (taken from the start value).
Something like this should get you going:
<div id="chart"></div>
<script type="text/javascript">
$(function () {
$.get("/api/users/12652/stats", function (data) {
var sessions = sessions;
#(Html.ShieldChart()
.Name("chart")
.PrimaryHeader(header => header.Text("User session duration (in minutes)"))
.DataSeries(series => series.Bar()
.Data(#<text>
(function () {
return $.map(sessions, function (item) {
//return the difference between the end and start time in minutes
return (item.end - item.start) / 1000 / 60;
});
})()
</text>))
.GetScript())
});
});
</script>
Note how the Shield Chart is defined using a Razor server expression inside the javascript callback from the service request. We do this, because we need the session variable to contain the actual data returned from the service. Then we can use a self-calling function in a <text></text> template as a parameter to the series' .Data() method in MVC.This has the effect of rendering a self-calling function expression in the javascript initialization options that helps us map the service response to series data options.
Note the last .GetScript() method call. We use it to render only the initialization script of the chart widget and not the entire widget markup and scripts. This allows the widget to be initialized in javascript code in your HTML page.
For binding the X axis categorical values to the service response, however, you do not have a similar approach with the #<text></text> template available. For this case, it is best to use pure javascript initialization:
$(function() {
$("#chart").shieldChart({
primaryHeader: {
text: "User session duration (in minutes)"
},
dataSeries: [{
seriesType: 'bar',
data: $.map(sessions, function(item) {
return (item.end - item.start) / 1000 / 60;
})
}],
axisX: {
axisType: "datetime",
dataStart: sessions[0].start,
categoricalValues: $.map(sessions, function(item) {
return new Date(item.start);
})
}
});
});
Here is a JSBin of the above scenario with some hard-coded data just to concentrate on the chart initialization.