Copy Multiple Charts from Sheets to Docs - google-apps-script

We have switched from Microsoft to Google Workspace.
We have a Sheet with multiple tabs with a table and graph in each tab. I would like to automate inserting these graphs (as images) at specific points into a preformated Google Doc. I am sure this is something very routinely done.
I am told this can be done with Google Apps Script, but I have never used it.
Could someone point me to some good examples to get me started?
Thanks

Description
Here is a sample example script to get a chart from Google Sheet and place in a Google Doc. I use as a place holder the paragraph "Chart1".
Spreadsheet
Doc (Before)
Doc (After)
Code.gs
function testChart() {
try {
let charts = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Chart").getCharts();
// In this case there is only 1
let chart = charts[0];
chart = chart.getAs("image/png");
let doc = DocumentApp.openById("1A........................ds");
let body = doc.getBody();
let paras = body.getParagraphs();
for( let i=0; i<paras.length; i++ ) {
let para = paras[i];
let text = para.getText();
if( text === "Chart1" ) {
para.addPositionedImage(chart);
}
}
}
catch(err) {
console.log(err);
}
}
Reference
Sheet.getCharts()
DocumentApp.openById()
Document.getBody()
PositionImage

Related

Google apps script - Bookmark a selected image

How can I addBookmark() a selected image from Goole docs.
In your situation, how about the following sample script?
Sample script:
function myFunction() {
const doc = DocumentApp.getActiveDocument();
const selection = doc.getSelection();
if (!selection) return;
selection.getRangeElements().forEach(e => {
const ele = e.getElement();
const type = ele.getType();
if (type == DocumentApp.ElementType.TEXT) {
doc.addBookmark(doc.newPosition(ele, e.getStartOffset()));
} else if (type == DocumentApp.ElementType.INLINE_IMAGE) {
doc.addBookmark(doc.newPosition(ele.asInlineImage().getParent(), 1));
}
});
}
When you use this script, please select an inline image on Google Drive and run the script. By this, the bookmark is added to the inline image. And, when you select a text, the bookmark is added to the text.
Reference:
asInlineImage()

Google Apps Script to Refresh Google Slide Presentation containing linked Google Sheet Charts: Fails after 10000 Calls?

Our non-profit information display is a simple looping Google Slides presentation, which works well for static content. Our dynamic content are composed of some current weather textual charts sourced from a Google Sheet (populated using API). These charts are 'refreshed' using a Google Apps Script every minute. Why every minute? Since one of the dynamic charts is the current time -- and this is the only way we have found to maintain a real-time clock in a Google Slide display.
In any case, this works fine -- but after running an entire week, every minute (10000 calls?) the linked charts fail with an 'exclamation mark in a triangle' symbol (and the message 'Image could not be loaded'). Re-embedding the charts does not help -- it is as if the entire Google Slide presentation is now corrupted and unusable for this purpose. We 'fix' this by merely creating a replacement copy of the presentation -- which (after re-activating our triggers to refresh) works just fine. It would be great if we did not need to maintain our presentation this way every week. Help? Suggestions? Thanks!
UPDATE: Here is our Google Slides 'refresh' code:
function refreshCharts(){
var gotSlides = SlidesApp.getActivePresentation().getSlides();
for (var i = 0; i < gotSlides.length; i++) {
var slide = gotSlides[i];
var sheetsCharts = slide.getSheetsCharts();
for (var k = 0; k < sheetsCharts.length; k++) {
var shChart = sheetsCharts[k];
shChart.refresh();
}
}
}
Description
If you are interested to try batch update I put together a simple example script.
Code.gs
function updateCharts() {
try {
let id = SlidesApp.getActivePresentation().getId();
let chartIds = [];
let slides = SlidesApp.getActivePresentation().getSlides();
slides.forEach( slide => { let charts = slide.getSheetsCharts();
charts.forEach( chart => chartIds.push(chart.getObjectId()) );
}
);
console.log(chartIds);
let charts = chartIds.map( id => { return { refreshSheetsChart: { objectId: id } } } );
console.log(charts);
let requests = { requests: charts };
Slides.Presentations.batchUpdate(requests,id);
}
catch(err) {
console.log(err);
}
}
Execution log
6:54:40 AM Notice Execution started
6:54:40 AM Info [ 'g139f22bb9da_0_0' ]
6:54:40 AM Info [ { refreshSheetsChart: { objectId: 'g139f22bb9da_0_0' } } ]
6:54:42 AM Notice Execution completed
Reference
Slides.batchUpdate()

ImportHTML - "Error Resource at url contents exceeded maximum size." [duplicate]

This question already has an answer here:
Replacing =ImportHTML with URLFetchApp [duplicate]
(1 answer)
Closed 1 year ago.
Hoping someone in here can help,
I have been pulling player prop data for the last couple years and it has worked fine, but this year they added more selections to the drop down and I get the error "Resource at url contents exceeded maximum size" anytime there are more than 5 games.
The function we use is =importhtml("https://www.scoresandodds.com/nba/props", "table",1)
Hoping somebody can point me in the right direction to get this working again, if possible at all. I am not very familiar with scripts and from what I've gathered that is the only way to work around this error.
Thank you for your time!
In your situation, how about using Sheets API with Google Apps Script? When the pasteData request of Sheets API is used, the HTML table can be parsed and put to the Spreadsheet. When this is reflected to the script, it becomes as follows.
Sample script:
Please copy and paste the following script to the script editor of Google Spreadsheet, and please enable Sheets API at Advanced Google services. And, please run myFunction at the script editor. By this, the retrieved table is put to the sheet.
function myFunction() {
const sheetName = "Sheet1"; // Please set the destination sheet name.
const url = "https://www.scoresandodds.com/nba/props";
const html = UrlFetchApp.fetch(url).getContentText();
const table = html.match(/<table[\s\S\w]+?<\/table>/);
if (table) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const requests = { requests: [{ pasteData: { html: true, data: table[0], coordinate: { sheetId: ss.getSheetByName(sheetName).getSheetId() } } }] };
Sheets.Spreadsheets.batchUpdate(requests, ss.getId());
return;
}
throw new Error("Table cannot be retrieved.");
}
References:
Method: spreadsheets.batchUpdate
PasteDataRequest
Try this script
function importTableHTML(url) {
var html = '<table' + UrlFetchApp.fetch(url).getContentText().match(/(?<=\<table).*(?=\<\/table)/g) + '</table>';
var trs = [...html.matchAll(/<tr[\s\S\w]+?<\/tr>/g)];
var data = [];
for (var i=0;i<trs.length;i++){
var tds = [...trs[i][0].matchAll(/<(td|th)[\s\S\w]+?<\/(td|th)>/g)];
var prov = [];
for (var j=0;j<tds.length;j++){
donnee=tds[j][0].match(/(?<=\>).*(?=\<\/)/g)[0].replace(/ /g,' ');
if(donnee.indexOf("</a>")>-1){
prov.push(donnee.match(/(?<=\>).*(?=\<\/)/g)[0]);
}else{
prov.push(donnee);
}
}
data.push(prov);
}
return(data);
}

Google Sheets Scraping Options Chain from Yahoo Finance, Incomplete Results [duplicate]

This question already has answers here:
Scraping data to Google Sheets from a website that uses JavaScript
(2 answers)
Closed last month.
I'm attempting to scrape options pricing data from Yahoo Finance in Google Sheets. Although I'm able to pull the options chain just fine, i.e.
=IMPORTHTML("https://finance.yahoo.com/quote/TCOM/options?date=1610668800","table",2)
I find that it's returning results that don't completely match what's actually shown on Yahoo Finance. Specifically, the scraped results are incomplete - they're missing some strikes. i.e. the first 5 rows of the chart may match, but then it will start returning only every other strike (aka skipping every other strike).
Why would IMPORTHTML be returning "abbreviated" results, which don't match what's actually shown on the page? And more importantly, is there some way to scrape complete data (i.e. that doesn't skip some portion of the available strikes)?
In Yahoo finance, all data are available in a big json called root.App.main. So to get the complete set of data, proceed as following
var source = UrlFetchApp.fetch(url).getContentText()
var jsonString = source.match(/(?<=root.App.main = ).*(?=}}}})/g) + '}}}}'
var data = JSON.parse(jsonString)
You can then choose to fetch the informations you need. Take a copy of this example https://docs.google.com/spreadsheets/d/1sTA71PhpxI_QdGKXVAtb0Rc3cmvPLgzvXKXXTmiec7k/copy
edit
if you want to get a full list of available data, you can retrieve it by this simple script
// mike.steelson
let result = [];
function getAllDataJSON(url = 'https://finance.yahoo.com/quote/TCOM/options?date=1610668800') {
var source = UrlFetchApp.fetch(url).getContentText()
var jsonString = source.match(/(?<=root.App.main = ).*(?=}}}})/g) + '}}}}'
var data = JSON.parse(jsonString)
getAllData(eval(data),'data')
var sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
sh.getRange(1, 1, result.length, result[0].length).setValues(result);
}
function getAllData(obj,id) {
const regex = new RegExp('[^0-9]+');
for (let p in obj) {
var newid = (regex.test(p)) ? id + '["' + p + '"]' : id + '[' + p + ']';
if (obj[p]!=null){
if (typeof obj[p] != 'object' && typeof obj[p] != 'function'){
result.push([newid, obj[p]]);
}
if (typeof obj[p] == 'object') {
getAllData(obj[p], newid );
}
}
}
}
Here's a simpler way to get the last market price of a given option. Add this function to you Google Sheets Script Editor.
function OPTION(ticker) {
var ticker = ticker+"";
var URL = "finance.yahoo.com/quote/"+ticker;
var html = UrlFetchApp.fetch(URL).getContentText();
var count = (html.match(/regularMarketPrice/g) || []).length;
var query = "regularMarketPrice";
var loc = 0;
var n = parseInt(count)-2;
for(i = 0; i<n; i++) {
loc = html.indexOf(query,loc+1);
}
var value = html.substring(loc+query.length+9, html.indexOf(",", loc+query.length+9));
return value*100;
}
In your google sheets input the Yahoo Finance option ticker like below
=OPTION("AAPL210430C00060000")
I believe your goal as follows.
You want to retrieve the complete table from the URL of https://finance.yahoo.com/quote/TCOM/options?date=1610668800, and want to put it to the Spreadsheet.
Issue and workaround:
I could replicate your issue. When I saw the HTML data, unfortunately, I couldn't find the difference of HTML between the showing rows and the not showing rows. And also, I could confirm that the complete table is included in the HTML data. By the way, when I tested it using =IMPORTXML(A1,"//section[2]//tr"), the same result of IMPORTHTML occurs. So I thought that in this case, IMPORTHTML and IMPORTXML might not be able to retrieve the complete table.
So, in this answer, as a workaround, I would like to propose to put the complete table parsed using Sheets API. In this case, Google Apps Script is used. By this, I could confirm that the complete table can be retrieved by parsing the HTML table with Sheet API.
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet, and please enable Sheets API at Advanced Google services. And, please run the function of myFunction at the script editor. By this, the retrieved table is put to the sheet of sheetName.
function myFunction() {
// Please set the following variables.
const url ="https://finance.yahoo.com/quote/TCOM/options?date=1610668800";
const sheetName = "Sheet1"; // Please set the destination sheet name.
const sessionNumber = 2; // Please set the number of session. In this case, the table of 2nd session is retrieved.
const html = UrlFetchApp.fetch(url).getContentText();
const section = [...html.matchAll(/<section[\s\S\w]+?<\/section>/g)];
if (section.length >= sessionNumber) {
if (section[sessionNumber].length == 1) {
const table = section[sessionNumber][0].match(/<table[\s\S\w]+?<\/table>/);
if (table) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const body = {requests: [{pasteData: {html: true, data: table[0], coordinate: {sheetId: ss.getSheetByName(sheetName).getSheetId()}}}]};
Sheets.Spreadsheets.batchUpdate(body, ss.getId());
}
} else {
throw new Error("No table.");
}
} else {
throw new Error("No table.");
}
}
const sessionNumber = 2; means that 2 of =IMPORTHTML("https://finance.yahoo.com/quote/TCOM/options?date=1610668800","table",2).
References:
Method: spreadsheets.batchUpdate
PasteDataRequest

Identify specific charts on google sheets using google script

I have a google Slides presentation containing a chart tied to a google Spreadsheets Sheet. I created a plug-in (google script), using which I am able to paste the sheets url and have all available charts by id.
I am unable to overcome the issue of identifying the chart I need solely using its id which is generated.
Right now I can do this:
var spreadSheetContainingChartAndData = Sheets.Spreadsheets.get(sheetsId)
var allSheets = spreadSheetContainingChartAndData.sheets
var allChartsOnTheOnlyExistingSheet = dataSheet.sheets[0].charts
var firstChart = allChartsOnTheOnlyExistingSheet[0]
var chartId = firstChart.chartId
Ideally I would want to find the chart by some form of user defined identifier like this:
var chartId = findByAnythingElse(allChartsOnTheOnlyExistingSheet, 'user-defined-tag')
The usecase is to have a spreadsheet template containing client data and a number of charts visualising said data.
Whenever someone wants to make a presentation, he can click on the plugin menu and have certain slides to be populated with specific charts which will be cropped and resized to a predefined format.
You want to retrieve the chartId from all charts in a Spreadsheet.
You want to search the chartId using the specific tag.
You want to achieve this using Google Apps Script.
If my understanding is correct, how about this sample script? Please think of this as just one of several answers.
In this answer, the chart's title, subtitle and alt title are used as the search tags.
Sample script:
Before you use this script, please enable Sheets API at Advanced Google Services. And when you test this script, please set the variables for searching the chart and run the function of run().
function findByAnythingElse(spreadsheetId, searchObj) {
var obj = Sheets.Spreadsheets.get(spreadsheetId, {fields: "sheets(charts(chartId,spec(altText,subtitle,title)))"});
var chartIds = [];
for (var i = 0; i < obj.sheets.length; i++) {
var charts = obj.sheets[i].charts;
if (charts) {
for (var j = 0; j < charts.length; j++) {
var title = charts[j].spec.title;
var subTitle = charts[j].spec.subtitle;
var altText = charts[j].spec.altText;
if (title == searchObj.searchTitle || subTitle == searchObj.searchSubTitle || altText == searchObj.searchAltText) {
chartIds.push(charts[j].chartId);
}
}
}
}
return chartIds;
}
// Please run this fnuction for testing.
function run() {
var searchObj = { // Please set search values.
searchTitle: "sample1",
searchSubTitle: "",
searchAltText: "",
}
var spreadsheetId = "###"; // Please set Spreadsheet ID here.
var res = findByAnythingElse(spreadsheetId, searchObj);
Logger.log(res)
}
In this sample script, when run() is run, one of searchTitle, searchSubTitle and searchAltText of searchObj is matched, the chart ID is retrieved.
For example, the chart ID can be also retrieved from only searchTitle.
When there are several charts with the same title, several chart IDs are returned.
Note:
This is a simple sample script. So please modify it for your situation.
References:
Advanced Google services
Charts
If I misunderstood your question and this was not the direction you want, I apologize.