Javascript, HTML, CSS e... !
0 commenti

Creazione programmatica di una presentazione in Google Slides

Creare un Report in Google Slides generato automaticamente con Google Apps Script

Recentemente mi sono cimentato in uno strumento della G Suite a mio avviso ancora poco sfruttato rispetto alle potenzialità che offre, ovvero, Google Slide, un tool per creare presentazioni che si avvicina molto al conosciutissimo Power Point.
Lo strumento si adatta bene per la realizzazione di report da condividere con i clienti tuttavia, nonostante la possibilità di prendere visione del report e collaborare online, un uso dello strumento alla pari di un Power Point non basta a giustificarne la sua sostituzione.

Per dare un valore aggiunto è necessario andare a colmare uno degli aspetti critici dei report, ovvero l'aggiornamento dei dati spesso effettuato manualmente.
Ecco quindi che ci viene incontro Google Apps Script che, tramite i Slides Service (insieme agli Extending Google Slides) e le relative Slides API, permette di realizzare e modificare in modo programmatico (da codice), una presentazione in Google Slides recuperando i dati dalle fonti opportune (ad esempio uno Spreadsheet così come da altri servizi quali Google Analytics o ancora da sorgenti terze come può essere un database).

Ho realizzato a tal scopo una presentazione esemplificativa, di poche slide, dove tramite Google Apps Script vengono utilizzati diversi metodi ed API.

Di seguito una lista non esaustiva di quello che viene effettuato dal codice di esempio che riporto poco sotto:

- Creazione di una presentazione, aggiunta di nuove slide e gestione di elementi (testo, colore di fondo, ...);
- Inserimento, ridimensionamento e posizionamento di elementi (testi, immagini, link, ...) all'interno delle slide;
- Elaborazione dei testi e relative modifiche agli stili (dimensione dei caratteri, grassetto, colore, ...);
- Importazione di grafici da Spreadsheet all'interno delle slide e relativa elaborazione.

Alcuni screenshot rappresentativi:

Slide contenente immagini recuperate tramite URL, con valori inseriti da array (allo stesso modo potrebbero essere recuperati da uno Spreadsheet o da altri servizi), Fig. 1:



Presentazione in Google Slide creata con Google Apps Script

Fi. 1 - Presentazione in Google Slide creata con Google Apps Script, slide di esempio


Grafico creato all'interno di uno Spreadsheet, Fig. 2:



Grafico creato in uno Spreadsheet

Fig. 2 - Grafico creato in uno Spreadsheet


Slide nella quale è stato importato il grafico creato nello Spreadsheet, Fig. 3:



Grafico importato da uno Spreadsheet in una slide di Google Slides con Apps Script

Grafico importato da uno Spreadsheet in una slide di Google Slides con Apps Script


Chi volesse prendere visione della presentazione in Google Slides realizzata con Google Apps Script descritta nel seguente tutorial può farlo tramite il seguente link: https://docs.google.com/presentation/d/1MyUOpS2ogYFN4pgKXnXuZIOmLYlmTCnrNPuJcE0MeFg/edit?usp=sharing

L'intero codice per l'ottenimento del risultato:

var scriptProperties = PropertiesService.getScriptProperties();

var ss_id = '1V8f9tFDPyGZ9v0EfI-F9ZVOovZzGrMvgOdoz9xbHEvI';

var images = [
  "http://media.bigbossweb.com/...path.../gas_slides_sessions1.png",
  "http://media.bigbossweb.com/..path.../gas_slides_timer1.png",
  "http://media.bigbossweb.com/..path.../gas_slides_bounce1.png",
  "http://media.bigbossweb.com/..path.../michele_pisani_apps_script_italia.jpg"
];

var data_overview = ['2.830', '00:02:20', '26,46%'];

function addImageSlide(slide_sel, image_url, page_width_pos, page_heigth_pos, scale_xy) {
  var image = slide_sel.insertImage(image_url);
  image.scaleWidth(scale_xy).scaleHeight(scale_xy);
  var imgWidth = image.getWidth();
  var imgHeight = image.getHeight();
  var newX = page_width_pos/2. - imgWidth/2.;
  var newY = page_heigth_pos/2. - imgHeight/2.;
  image.setLeft(newX).setTop(newY);
}

function appendTextInTop(deck) {
  var slides = deck.getSlides();
  slides.forEach(function(slide) {
    
    var shape_top = slide.insertShape(SlidesApp.ShapeType.TEXT_BOX, deck.getPageWidth() - 450, 10, 450, 20); // left; top; width; height
    var textRange_top = shape_top.getText();
    textRange_top.setText('Disclaimer: I dati contenuti in questo report...')
    .getTextStyle()
    .setBold(true)
    .setItalic(true)
    .setFontSize(8)
    .setForegroundColor('#BBBBBB');
  });
}

function appendTextInBottomLeft(deck) {
  var slides = deck.getSlides();
  slides.forEach(function(slide) {

    var shape_bottom_left = slide.insertShape(SlidesApp.ShapeType.TEXT_BOX, 40, deck.getPageHeight() - 40, 280, 20);
    shape_bottom_left.getText().setText('Periodo di riferimento: ').getTextStyle().setFontSize(10);
    shape_bottom_left.getText().appendText('mese precedente').getTextStyle().setBold(true);
  });
}

function appendTextInBottomRight(deck) {
  var slides = deck.getSlides();
  slides.forEach(function(slide) {

    var shape_bottom_right = slide.insertShape(SlidesApp.ShapeType.TEXT_BOX, deck.getPageWidth() - 280, deck.getPageHeight() - 40, 280, 20);
    shape_bottom_right.getText().setText('Realizzato da ').getTextStyle().setFontSize(10);
    shape_bottom_right.getText().appendText('Michele Pisani, ').getTextStyle().setBold(true);
    shape_bottom_right.getText().appendText('www.appsscript.it')
    .getTextStyle()
    .setBold(false)
    .setLinkUrl('www.appsscript.it')
    .setForegroundColor('#4b95d4');
  });
}

function importChartFromSheet(slide_id, ss_id, ss_chart_id, deck_id) {
  var emu4M = {
    magnitude: 4000000,
    unit: 'EMU'
  };
  var presentationChartId = 'grafico_importato';
  var requests = [{
    createSheetsChart: {
      objectId: presentationChartId,
      spreadsheetId: ss_id,
      chartId: ss_chart_id,
      linkingMode: 'LINKED',
      elementProperties: {
        pageObjectId: slide_id.getObjectId(),
        size: {
          height: emu4M,
          width: emu4M
        },
        transform: {
          scaleX: 1,
          scaleY: 1,
          translateX: 100000,
          translateY: 100000,
          unit: 'EMU'
        }
      }
    }
  }];
  
  var batchUpdateResponse = Slides.Presentations.batchUpdate({
    requests: requests
  }, deck_id); 
}

function main() {
  
  var deck_name = "AppsScript.it";
  var deck = SlidesApp.create(deck_name); // SlidesApp.getActivePresentation();
  var deck_id = deck.getId();
  scriptProperties.setProperty('deck_id', deck_id);
  
  var pageWidth = deck.getPageWidth();
  var pageHeight = deck.getPageHeight();   
  
  // SLIDE 1 (creata di default)
  deck.getSlides()[0].getBackground().setSolidFill('#4b95d4', 0.3);
  var [title, subtitle] = deck.getSlides()[0].getPageElements();
  title.asShape().getText().setText(deck_name);
  subtitle.asShape().getText().setText("Report in Google Slides generato automaticamente...");
  
  // SLIDE 2
  var slide_two = deck.appendSlide(SlidesApp.PredefinedLayout.TITLE_AND_TWO_COLUMNS);
  var [title, left, right] = deck.getSlides()[1].getPageElements();
  title.asShape().getText().setText("Google Analytics: panoramica metriche principali");
  left.asShape().getText().setText("Sessioni").getTextStyle().setForegroundColor('#000000');
  right.asShape().getText().setText("Frequenza di rimbalzo").getTextStyle().setForegroundColor('#000000');

  left.setTop(110);
  left.setLeft(left.asShape().getLeft() - 50);
  right.setTop(110);
  right.setLeft(right.asShape().getLeft() + 50);
  
  shape_left_width = left.asShape().getWidth();
  shape_left_height = left.asShape().getHeight();
  shape_left_top = left.asShape().getTop();  
  
  var shape_center = slide_two.insertShape(SlidesApp.ShapeType.TEXT_BOX, deck.getPageWidth()/2 - shape_left_width/2, shape_left_top, shape_left_width, shape_left_height);
  shape_center.getText().setText("Durata sessione media");    
  shape_center.setTop(110);  
  
  addImageSlide(slide_two, images[0], pageWidth/2 - 100, pageHeight, 0.4);
  addImageSlide(slide_two, images[2], (pageWidth/2)*3 + 100, pageHeight, 1);
  addImageSlide(slide_two, images[1], pageWidth, pageHeight, 0.6);
  
  var shape_data_left = slide_two.insertShape(SlidesApp.ShapeType.TEXT_BOX, left.asShape().getLeft(), left.asShape().getTop() + 160, left.asShape().getWidth(), 30);
  var shape_data_right = slide_two.insertShape(SlidesApp.ShapeType.TEXT_BOX, right.asShape().getLeft(), right.asShape().getTop() + 160, right.asShape().getWidth(), 30);
  var shape_data_center = slide_two.insertShape(SlidesApp.ShapeType.TEXT_BOX, shape_center.getLeft(), shape_center.getTop() + 160, shape_center.getWidth(), 30);
  shape_data_left.getText().setText(data_overview[0]).getTextStyle().setBold(true).setFontSize(26);
  shape_data_right.getText().setText(data_overview[2]).getTextStyle().setBold(true).setFontSize(26);
  shape_data_center.getText().setText(data_overview[1]).getTextStyle().setBold(true).setFontSize(26);
  
  var arr_shape = [];
  arr_shape.push(left.asShape());
  arr_shape.push(right.asShape());
  arr_shape.push(shape_center);
  arr_shape.push(shape_data_left);
  arr_shape.push(shape_data_right);
  arr_shape.push(shape_data_center);
  arr_shape_length = arr_shape.length;
  for (var i = 0; i < arr_shape_length; i++) {
    var textRange = arr_shape[i].getText();
    var paragraphs = textRange.getParagraphs();
    var paragraphs_length = paragraphs.length;
    for (var j = 0; j < paragraphs_length; j++) {
      var paragraphStyle = paragraphs[j].getRange().getParagraphStyle();
      paragraphStyle.setParagraphAlignment(SlidesApp.ParagraphAlignment.CENTER);
    }
  }
  
  // SLIDE 3
  var slide_three = deck.appendSlide(SlidesApp.PredefinedLayout.TITLE_AND_BODY);

  var [title,text] = deck.getSlides()[2].getPageElements();
  title.asShape().getText().setText("Google Analytics: andamento sessioni giornaliere");
  text.asShape().getText().setText(" ");
  
  // Recupero l'id del grafico dallo Spreadsheet indicato
  var ss = SpreadsheetApp.openById(ss_id);

  var ch = Sheets.Spreadsheets.get(ss.getId(), { "fields": "sheets(charts/chartId)" });
  var sheets = ch.sheets.map (function (d) {
    return { charts:d.charts }
  });
  chart_id = sheets[0].charts[0].chartId + '';
  
  scriptProperties.setProperty('ss_id', ss.getId());
  scriptProperties.setProperty('chart_id', chart_id);

  // Appende il discaimer in tutte le slide fino ad ora presenti
  appendTextInTop(deck);
  // Appende il periodo di riferimento in tutte le slide fino ad ora presenti
  appendTextInBottomLeft(deck);
  
  
  // SLIDE 4
  var slide_four = deck.appendSlide(SlidesApp.PredefinedLayout.TITLE_AND_BODY);
  slide_four.getBackground().setSolidFill('#4b95d4', 0.3);
  addImageSlide(slide_four, images[3], pageWidth, pageHeight - 50, 0.9);

  var [title,text] = deck.getSlides()[3].getPageElements();
  title.asShape().getText().setText(" ");
  text.setTop(320);
  text.asShape().getText().setText("Grazie!");
  text.asShape().getText().getParagraphs()[0].getRange().getParagraphStyle().setParagraphAlignment(SlidesApp.ParagraphAlignment.CENTER);
  
  
  // Appende il copyright in tutte le slide fino ad ora presenti
  appendTextInBottomRight(deck);

}

function importChart() {
  var deck_id = scriptProperties.getProperty('deck_id');
  var chart_id = scriptProperties.getProperty('chart_id');
  var deck = SlidesApp.openById(deck_id);

  // Importa il grafico dallo Sheet indicato all'interno Slide desiderata
  importChartFromSheet(deck.getSlides()[2], ss_id, chart_id, deck_id);  
}

function fixChart() {
  var deck_id = scriptProperties.getProperty('deck_id');
  var deck = SlidesApp.openById(deck_id);
  
  var pageWidth = deck.getPageWidth();
  var pageHeight = deck.getPageHeight();
  
  var chart_w = 0;
  var chart_h = 0;
  var pos_x = 0;
  var pos_y = 0;
  
  var arr_el = deck.getSlides()[2].getPageElements();
  
  arr_shape_length = arr_el.length;
  for (var i = 0; i < arr_shape_length; i++) {
    Logger.log(arr_el[i].getPageElementType());
    if (arr_el[i].getPageElementType() == 'SHEETS_CHART') {
      
      arr_el[i].scaleWidth(1.8).scaleHeight(1.8);

      chart_w = arr_el[i].asSheetsChart().getWidth();
      chart_h = arr_el[i].asSheetsChart().getHeight();
      
      pos_x = pageWidth/2. - chart_w/2.;
      pos_y = pageHeight/2. - chart_h/2.;   

      arr_el[i].asSheetsChart().setLeft(pos_x);
      arr_el[i].asSheetsChart().setTop(pos_y + 20);
    }
  }
}

Prima di eseguire il codice devono essere abilitate, all'interno dell'interfaccia dell'editor di script da Risorse -> Servizi avanzati di Google, le Google Sheets API e le Google Slides API. Di conseguenza i servizi appena nominati devono essere abilitati anche nella Console API Google.

Di seguito alcune linee guida da seguire per poter utilizzare al meglio il codice di cui sopra:

Una volta effettuata questa operazione basterà lanciare la funzione main() che si occuperà di generare la presentazione con tutte le relative slide riempite degli opportuni contenuti (ad eccezione del grafico da importare dallo Spreadsheet).

Per importare il grafico è necessario lanciare successivamente la funzione importChart().
L'inserimento del grafico avviene tramite API, considerando che tale chiamata esegue le opportune operazioni con un processo a se stante da quello di generazione della presentazione, è probabile che al momento in cui l'API viene lanciata non esista ancora fisicamente la slide nella quale si intende importare il grafico, con la conseguente restituzione di un errore simile al seguente 'The page (SLIDES_API842162797_13) could not be found' per questo motivo, per ovviare all'inconveniente, ho preferito lanciare una seconda funzione così che l'API venga chiamata quando la presentazione è già stata fisicamente creata su Drive.

L'importazione del grafico comporta dei tempi di caricamento dello stesso pertanto, per un motivo simile a quello appena descritto, ho riservato le operazioni di rifinitura (posizionamento del grafico e ridimensionamento) all'interno di una terza funzione da lanciare successivamente alla precedente, fixChart().

Nota:
Come è immaginabile quando si realizza da zero un report di questo tipo, per arrivare ad un risultato soddisfacente, sono necessari diversi tentativi iniziali per la sua messa a punto, al fine di definire correttamente la dimensione e la posizione che si intende dare agli elementi che andranno a comporre la presentazione così come la dimensione del font ed altri accorgimenti grafici, tuttavia il lavoro di impaginazione, una volta effettuato, fungerà da base per le successive generazioni di report (o aggiornamenti dei dati).

Tags

Michele Pisani

Michele Pisani

Sviluppatore Javascript ed esperto in Digital Analytics

L'esperienza nel settore Digital Analytics unita ad anni di sviluppo in Javascript ha trovato la massima espressione in Google Apps Script che mi ha permesso, con estrema facilità e poche righe di codice, di realizzare potenti applicazioni interattive e processi automatizzati integrati con i prodotti della G Suite.

Come contattarmi
scrivi un commento

0 Commenti

Non ci sono commenti

Nessuno ha ancora commentato questo articolo, fallo tu per primo!

scrivi un commento

Scrivi un commento

Il tuo indirizzo email non sarà pubblicato.I campi contrassegnati da un * sono obbligatori
Puoi utilizzare i seguenti tag nei commenti:
[bold]testo[/bold] se vuoi evidenziare un testo con il grassetto[code]function helloworld() { }[/code] se vuoi pubblicare una porzione di codice[url]http://www.appsscript.it[/url] se devi riferirti ad un indirizzo web