Getting gsheet Combo Charts to send them by email - google-apps-script

This is my first question here, though I've had some helpful guidance from lots of questions before. I hope I don't miss anything important in this question.
I am currently writing a script in Google Apps Script to send some charts from a Google Sheets document by email, as images. My issue is that with Combined Charts, when I try to read them and get them as images, I get an error.
I've reduced the code to the minimum that will still throw the error I'm talking about.
function test_chart_fails(){
let gdoc_id = "xxxxxxxxxxxx";
let sheet_name = "test2";
let document = SpreadsheetApp.openById(gdoc_id);
let sheet = document.getSheetByName(sheet_name);
let charts = sheet.getCharts();
let chart = charts[2];
let inLineImages = {};
inLineImages["chartC"] = chart.getAs('image/png');
}
I get the following error:
Exception: Service Spreadsheets failed while accessing document with id xxxxxxxxxxxxxxxxxxxxxxxxxx.
test_chart_fails # AppsScriptFile.gs:305
Where 305 is the last line of my sample code (the one with 'chart.getAs').
Now some additional info that may be relevant (or not):
The sheet in question has 9 charts in total. I get this error if I try to access 7 of the 9, which are only the combined charts. Other charts will work fine, and I can put them in an email sent through MailApp. All the charts were created manually in the Gsheets document.
Regarding the data in the sheet that builds the charts, the sheet has 103 rows and 44 columns. All data in the sheet is formulas, not values.
I've searched for existing questions in this and other sites where this error message appears, but I didn't find a question where the error was related to Chart retrieval - specifically no instance related to Combo Charts. One instance mentioned an error related to the sheet size, so I mentioned the data size (rows x columns), but I don't think that is the issue - because the script runs fine for non-Combo Charts based on the same dataset.
Is this expected for combined charts? Is there a way I can 'retrieve' them and send them in an email like I do the others?

This appears to be a bug
https://issuetracker.google.com/182701055
SpreadsheetApp.getActive().getSheetByName().getCharts().getAs() returns this error: "Service Spreadsheets failed while accessing document with id" ... when the line chart has a right vertical axis
I would encourage you to go and star this issue to let Google know that it affects you and to subscribe to updates.
You are also encouraged to post any further reproduction steps and workarounds there if they haven't been mentioned yet.
Workaround you mentioned
How to obtain a correct image of a ComboChart with Google App Script?

Related

Google Apps script: Spreadsheet to Chart to Image

I've created a function to take spreadsheet data, bring it to GAS, and then send it out to a webhook so I can use Zapier to build an image/pdf/etc. I used others samples and help on Google to figure out how to get the range, bring it over, etc and that's here:
const range = "Prior!H20:K23";
const [header, ...values] = SpreadsheetApp.getActiveSheet()
.getRange(range)
.getDisplayValues();
const table = Charts.newDataTable();
header.forEach((e) => table.addColumn(Charts.ColumnType.STRING, e));
values.forEach((e) => table.addRow(e));
const blob = Charts.newTableChart()
.setDataTable(table.build())
.setDimensions(500, 150)
.setOption("alternatingRowStyle", false)
.build()
.getBlob();
I then send the blob out, etc. The problem is that I'm losing all of the formatting from the spreadsheet, and since what I really want to do is almost just take a screenshot of the range in question, it's a problem. Specifically I'm losing text attributes, coloring, and the merging of cells.
Any ideas of how to do this, or, where in here I could modify/bring over the formatting?
There is no quick way to get a spreadsheet range with formatting through the SpreadsheetApp API.
See Tanaike's RichTextApp for one pretty simple method of getting formatted text content in a range of cells. The library that uses a temporary document to get the formatting.
To get a chart, complete with formatting, try .build().getAs('image/png') or 'image/svg' instead of .build().getBlob(). See Charts reference.
Alternatively, use Sheet.newChart() to get an EmbeddedTableChartBuilder instead of a TableChartBuilder.
You will need to extract formatting info from the sheet and feed it into the table builder
(Disclaimer, even by doing this you won't be able to do it with Apps Script, see below)
Especially if you use
.getDisplayValues();
Which will only get the values displayed from the sheet, no formatting information is taken from the sheet with this method.
You can use:
.getRichTextValues()
Though unfortunately a RichTextValue, is not recognized by the chart builder.
So the best thing is to break it up into a few steps, the first of which is using:
value.getTextStyle()
Which will then return a TextStyle object that has a few methods:
getFontFamily()
getFontSize()
getForegroundColor()
getForegroundColorObject()
isBold()
isItalic()
isStrikethrough()
isUnderline()
So now you can store the various formats you need to apply to your table.
However, the Apps Script Charts Service does not support this styling!
It seems that the Apps Script Chart Service does not support this type of styling. So maybe your only option is to open an HTML UI element via the UI service, or a Web app and then use the Google Charts service in the way demonstrated by the quickstart https://developers.google.com/chart/interactive/docs/quick_start.
You would need the setCell method.
This involves translating your styling to CSS, for example:
dataTable.setCell(22, 2, 15, 'Fifteen', {style: 'font-style:bold; font-size:22px;'});
// https://developers.google.com/chart/interactive/docs/reference#DataTable_setCell
File a feature request
You can always file a feature request for that by filling out this template:
https://issuetracker.google.com/issues/new?component=191640&template=823905
In the meantime though, you are probably best to get to grips with the JS library as it will always tend to be more complete than the Apps Script service that wraps it.
That, or build an HTML table without using Google Charts, then you could potentially use the HtmlService to pass it around in that format.
References
RichTextValue
DataTableBuilder
TableChartBuilder
UI service
Web apps
Charts
HtmlService

Error "Cannot find method insertChart(EmbeddedChartBuilder)" when using Apps Script to create Embedded Charts

I will like to take data from a spreadsheet "dataSheet" (shared with me as View only) to create a chart and output to my own spreadsheet "sheet".
I use Apps Script to create an embeddedChart and encountered this error: Cannot find method insertChart(EmbeddedChartBuilder).
I have followed Google's guide closely https://developers.google.com/apps-script/reference/spreadsheet/embedded-chart but can't resolve the error.
I have debugged step-by-step and realised the error happens at .insertChart (last line of code).
var chartBuilder = sheet.newChart();
chartBuilder.addRange(dataSheet.getRange('R'+earliestRowUsed + 'C1:R'+latestRowUsed + 'C1'))
.setChartType(Charts.ChartType.COLUMN)
.setPosition(5,5,0,0);
//collect data from dataSheet to add to Range.
for (indicator = 0; indicator < indicatorP.length; indicator++) {
indicatorIndex = indicatorList[0].indexOf(indicatorP[indicator]);
chartBuilder.addRange(dataSheet.getRange('R'+earliestRowUsed + 'C'+(dataCol + indicatorIndex) +':'+ 'R'+latestRowUsed + 'C'+(dataCol + indicatorIndex)));
}
chartBuilder.setMergeStrategy(Charts.ChartMergeStrategy.MERGE_COLUMNS); //join columns to create a table
chartBuilder.build();
sheet.insertChart(chartBuilder); //error takes place here.
I am open to using Charts or EmbeddedCharts to create my chart for my purpose (which is to take data from a shared view-only sheet to create a chart and embed in my sheet).
For working with Charts and Sheets, I found this Google guide https://developers.google.com/chart/interactive/docs/spreadsheets#embedding-achart-in-a-spreadsheet.
However the guide only shows a manual process to create Charts, and not with Apps Script. If anyone knows how, will appreciate your sharing as I haven't been able to find anything online.
Thank you.
I think your problem is the "View Only" permissions. Pretty sure that if you want to take data from a sheet programmatically and use it somewhere else you'll need to ask for Edit privileges.
After trialing a few scenarios, I found the problem to be due to EmbeddedCharts not being able to use data from another sheet file (view-only or owned). Using data from any sheets within the file EmbeddedCharts is to be created in works.

Error Connecting to Google Server using Script Editor

I am trying to use the Google Sheet's Script Editor to write a script for a Dependent List using the following script:
function setDataValid_(range, sourceRange) {
var rule =
SpreadsheetApp.newDataValidation().requireValueInRange(sourceRange,
true).build();
range.setDataValidation(rule);
}
function onEdit(){
var pageCell = SpreadsheetApp.getActiveSheet().getActiveCell();
var pageColumn = pageCell.getColumn();
if (pageColumn == 6 && SpreadsheetApp.getActiveSheet().getName() ==
'raw_data'){
var range = SpreadsheetApp.getActiveSheet().getRange(pageCell.getRow(),
pageColumn + 2);
var sourceRange =
SpreadsheetApp.getActiveSpreadsheet().getRangeByName(pageCell.getValue());
setDataValid_(range, sourceRange);
}
}
There are two different tabs I am using in this spreadsheet, "raw_data" and "Range". I had created a drop down list in column F of "raw_data" that lists values from a specified range in "range". Each of these values is also the title of a named range in that tab. The script above is suppose to read the value selected in column F and then two columns over, in column H, create another drop down list based on the values associated with that named range.
The problem I keep having is every time I click run I get the error message "Could not connect to server. Please save and try again" or if I try to set up a project trigger I get "We're sorry, a sever error occured. Please wait a bit and try again".
I have created new spread sheets, recreated the named ranges, created new script files and tried running the script from a separate computer but error message continues.
I haven't been able to find a solution to this and even when I try to use the dubug tool it gives me an error message. Does anyone know what the issue is or how I might resolve this?
I think this may be a legitimate server error at the moment because I'm getting the same error when I try to run any scripts. Same for a co-worker.
I have tried resaving several copies of my most recent script, all resulting in
Server error occurred. Please try saving the project again.
so I think that this is an issue on Google's side for some reason today.
My older scripts are running fine.
So I tried the script again this morning and it is working now. Hopefully it continues that way. Thanks for y'alls input!
Just checked the G Suite Status Dashboard and all products seems fine. This page offers performance information for G Suite services. Unless otherwise noted, this status information applies to consumer services as well as services for organizations using G Suite. No issues mentioned on Google's server as of this posting.
In script.google.com:
Resources -> Advance Google Services
Enable Drive API
You will be asked to enable it in the Google Dev console. Click the link and enable Drive API. Your script should run as intended now.

How do I speed up Sheet load times with a lot of importranges?

I've been promoted into developing & maintaining a Google Sheets database at work. I know very little about Google Sheets scripting & from asking around, and researching it's looking like GAS is probably the avenue that I need to start heading down.
So we have 3 Workbooks in Google Sheets; 2 contain large amounts of data, the other workbook provides a UI for our sales dpt. to access the data. I really wish I could share these with you, as describing them is difficult. In the UI workbook, separate pages are paired with sheets in the one database (lets call it database A).
A salesman will go to the UI sheet for the product he's selling a device for; the top section of the sheet allows him to select, essentially, a row from database A. After the selection is made, the rest of the sheet is populated with products we manufacture that work with the choice made in the top section; the products we make are stored in the other database ("B"). We have to have two databases, as we've earlier hit the cell-limit in sheets with the two databases combined.
On average each UI page has about 150 Importranges. Looking up done with Query.
Our problem is that this UI is getting pretty slow, initial load time makes it worthless for salesmen on the road, and annoying to the salesmen here in the office. The delay when making the initial selections (querying database A) is usable, but still much slower then we'd like. And we're not finished building UI pages.
I've seen online that most people recommend using Apps Script to replace importrange, but knowing nothing about Apps Script, I haven't been able to understand what is being done, or mainly how to take the apps script and actually put the data in the cells.
So I'd appreciate any help I could get in speeding this up.
First let me say that the Google Apps script documentation has improved greatly over the years and I find it pretty easy to use now. If you open up a code editor in Google Sheets and go to Help menu and select API reference then that links you up to just about everything you need to know. If you go to the Google Apps reference for spreadsheets and look at the SpreadsheetApp object you'll see that there's three commands to open up another Spreadsheet not a sheet but a Spreadsheet. You can do it by file, by id or by URL.
If you click on the Url command it will take you to an example like this:
// The code below opens a spreadsheet using its id and logs the name for it.
// Note that the spreadsheet is NOT physically opened on the client side.
// It is opened on the server only (for modification by the script).
var ss = SpreadsheetApp.openByUrl(
'https://docs.google.com/spreadsheets/d/abc1234567/edit');
Logger.log(ss.getName());
As it points out, it doesn't actually open the file on the client-side it just opens it up on the server. So it may be necessary for you to open them up manually at first just to get an idea of what they look like. Once you know how they are organized then you can use the open command to get a Spreadsheet Object and from it select a specific sheet and then a data range. Once you have a range then you can load an array like this.
var myArray = rng.getValues();
This will load the entire range in one fell swoop into a JavaScript array and of course it would be nice if you can filter out unwanted data from the array and then put it into your current sheet at a desired range. Note that the range sizes have to be exact matches and also please realize that ranges start from 1 and arrays start from 0 so that can cause you some grief. Also let me add a few caveats that I've run into.
If your dealing with a one row or one column range array then you have to get the array's in the correct form. I tried writing them here but the Stack Overflow text converter keeps messing them up so I'd recommend you go to my reference on that issue here.
If you've coded in JavaScript in the past I'm guessing that you'll have no problem coming up to speed with Google Apps Scripting with the new documentation and an occasional visit to Stack Overflow for a question or two. I've gotten some great answers here from other users. If you need a JavaScript reference here's one that I use.
You are probably best off using a WebApp served from Google Apps Script for the UI which I'd be happy to help with if you had some sample data. If you wanted to still use the sheets, then you could replace the importRanges with some Google Apps Script function that runs every 10 minutes or so to keep the UI sheet updated. It should speed up load times. Something like this would work for you:
function importSheetA() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var database = SpreadsheetApp.openByUrl("DATABASE_A_URL");
var dataToCopy = database.getSheetByName("DATABASE_A_SHEET_NAME").getDataRange().getValues();
var copyToSheet = ss.getSheetByName("UI_SHEET_NAME");
var copyData = copyToSheet.clearContents().getRange(1, 1, dataToCopy.length, dataToCopy[0].length).setValues(dataToCopy);
}

How do I execute a google script as 'owner' of a spreadsheet the user cannot view

Hopefully this is quite a simple question!
I've made a Google Script that writes to cells in a separate sheet "MasterSheet" (helped by several useful Q&As from here). This will ultimately be deployed embedded to multiple different sheets that I'm giving to individual users.
It works perfectly when the user has edit permissions on "MasterSheet", but I need that to remain private - i.e. not even viewable to anyone but me.
As background: In each 'user sheet', IMPORTRANGE is used to import the columns from 'MasterSheet' that that user is allowed to view, and then the script allows the user to add a comment to the MasterSheet.
I can view MasterSheet to see all the columns with comments from various users on one unified sheet, but the individual users shouldn't be able to view this.
The specific script for writing to the sheet is fairly generic:
function saveCommentToMasterSheet(form){
var company = form.company,
contact = form.contactselect,
comment = form.message ,
ss = SpreadsheetApp.openById("MASTERSHEET_ID").getSheetByName('MasterSheet');
if(company=="all"){
var row = findCell(contact);
} else {
var row = findCell2(contact,company);
}
//^The above finds the specific row number relating to the entry the user wants to comment on.
var cell2 = ss.getRange(row,11);
// ^In this case '11' is the column related to this sheet's specific user, I've made separate sheets for each user that are identical except this column number
cell2.setValue(comment);
}
I believe that I could make MasterSheet editable to anyone with the link, but I'd rather avoid that, particularly as the script is embedded in each spreadsheet so if the users just looked at it they'd find the MasterSheet id.
I understand that it's possible to run a script as me using the execute API, but if I'm honest, I struggled a little to figure out make that work.
Sorry if I'm asking a simple question - I've given it a good search and can't figure it out.
Many thanks!
Alex
N.B. This Running a google script from within a spreadsheet, but as a different user? looks like a similar question, but I'd really like to keep the comment system within the user's spreadsheet.
You can make a POST request to the master spreadsheet from the spreadsheets distributed to the users:
Apps Script Documentation - UrlFetchApp.fetch()
function saveCommentToMasterSheet(form) {//Function in the
//spreadsheets distributed to the users
var options,responseCode,url;
url = "https://script.google.com/macros/s/File_ID/exec";//Get from publishing
options = {};
options.method = 'post';
options.payload = form;
responseCode = UrlFetchApp.fetch(url, options).getResponseCode();
Logger.log('responseCode: ' + responseCode);//View the Logs
};
The above code will trigger the doPost(e) function in the master spreadsheet, and put the data into the event object e.
Then you can get the data out of e and write the data directly to what is the active spreadsheet, which is the master spreadsheet. Publish the Web App to run as "Me".
There are two versions of the published Web App; the "dev" version and the "exec" version. The "dev" version is always live with the latest changes, but should never be used in production. The "exec" version has a new version every time that you publish a the script again. To use the latest "exec" version in production, you must keep publishing the latest code.