开发者问题收集

通过电子邮件以图片/博客/png 形式发送 Google 表格系列中的 Sparkline 图表

2022-06-05
588

我尝试将此解决方案应用于我的情况:
通过电子邮件发送 SPARKLINE 图表会发送空白单元格而不是数据

但是当我尝试将其应用于我的情况时,会弹出错误:

TypeError: Cannot read property '0' of null

在执行过程中,有更多关于此错误的信息:

在此处输入图像描述

我的电子邮件解决方案的 GAS 代码能够仅发送值,并且它是这里:

function alertDailyInfo() {

    let emailAddress = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SANDBOX").getRange("F1").getValue();
    
    let treeIconUrl = "https://d1nhio0ox7pgb.cloudfront.net/_img/g_collection_png/standard/256x256/tree.png";
    let treeIconBlob = UrlFetchApp
                       .fetch(treeIconUrl)
                       .getBlob()
                       .setName("treeIconBlob");
    
    let treeUpdate = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SANDBOX").getRange("F6").getValue();
    let waterUpdate = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SANDBOX").getRange("F11").getValue();

            if (treeUpdate > 0) {
            
                    MailApp.sendEmail({
                        to: emailAddress,
                        subject: "TREE WATER UPDATE",
                        htmlBody: "<img src='cid:treeIcon'><br>" + '<br>' + '<br>' +  
                        '<b><u>Tree average is:</u></b>'+ '<br>' + treeUpdate + '<br>' + '<br>' +
                        '<b><u>Water average is:</u></b>'+ '<br>' + waterUpdate + '<br>' + '<br>' 
                        ,
                        inlineImages:
                        {
                            treeIcon: treeIconBlob,
                        }
                    });                
                }
            }

上面链接中提供的解决方案的代码以及我尝试适应我的情况的代码(请查看下面的文件)在这里:

drawTable();

function drawTable() {

 let emailAddress1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SANDBOX").getRange("F1").getValue();

    var ss_data = getData();
    var data = ss_data[0];
    var background = ss_data[1];
    var fontColor = ss_data[2];
    var fontStyles = ss_data[3];
    var fontWeight = ss_data[4];
    var fontSize = ss_data[5];
    var html = "<table border='1'>";
    var images = {}; // Added
    for (var i = 0; i < data.length; i++) {
        html += "<tr>"
        for (var j = 0; j < data[i].length; j++) {
            if (typeof data[i][j] == "object") { // Added
                html += "<td style='height:20px;background:" + background[i][j] + ";color:" + fontColor[i][j] + ";font-style:" + fontStyles[i][j] + ";font-weight:" + fontWeight[i][j] + ";font-size:" + (fontSize[i][j] + 6) + "px;'><img src='cid:img" + i + "'></td>"; // Added
                images["img" + i] = data[i][j]; // Added
            } else {
                html += "<td style='height:20px;background:" + background[i][j] + ";color:" + fontColor[i][j] + ";font-style:" + fontStyles[i][j] + ";font-weight:" + fontWeight[i][j] + ";font-size:" + (fontSize[i][j] + 6) + "px;'>" + data[i][j] + "</td>";
            }
        }
        html += "</tr>";
    }
    html + "</table>"
    MailApp.sendEmail({
        to: emailAddress1,
        subject: "Spreadsheet Data",
        htmlBody: html,
        inlineImages: images // Added
    })
}

function getData(){
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("SANDBOX");
  var ss = sheet.getDataRange();
  var val = ss.getDisplayValues();
  var background = ss.getBackgrounds();
  var fontColor = ss.getFontColors();
  var fontStyles = ss.getFontStyles();
  var fontWeight = ss.getFontWeights();
  var fontSize = ss.getFontSizes();
  var formulas = ss.getFormulas(); // Added
  val = val.map(function(e, i){return e.map(function(f, j){return f ? f : getSPARKLINE(sheet, formulas[i][j])})}); // Added
  return [val,background,fontColor,fontStyles,fontWeight,fontSize]; 
}

// Added
function getSPARKLINE(sheet, formula) {
  formula = formula.toUpperCase();
  if (~formula.indexOf("SPARKLINE")) {
    var chart = sheet.newChart()
      .setChartType(Charts.ChartType.SPARKLINE)
      .addRange(sheet.getRange(formula.match(/\w+:\w+/)[0]))
      .setTransposeRowsAndColumns(true)
      .setOption("showAxisLines", false)
      .setOption("showValueLabels", false)
      .setOption("width", 200)
      .setOption("height", 100)
      .setPosition(1, 1, 0, 0)
      .build();
    sheet.insertChart(chart); 
    var createdChart = sheet.getCharts()[0];
    var blob = createdChart.getAs('image/png');
    sheet.removeChart(createdChart);
    return blob;
  }
}

仅适用于我上面粘贴的值的代码(第一个代码块)将向我发送如下电子邮件:

在此处输入图像描述

但我需要收到这样的电子邮件,值下方的 Sparklines 如下:

在此处输入图片描述

我粘贴在上面(第一个代码块)的电子邮件解决方案代码(仅用于值)可以正常工作。但是出于某种原因,当将上面链接的解决方案中的代码(第二个代码块)导入/保存到我的 Google Sheets 文件 GAS 脚本库中并根据我的情况进行调整时,一切都停止工作,显示上述错误。

所以基本上,正如您可能已经理解的那样,我需要发送包含 Tree AverageWater Average 中的值的电子邮件,我设法让它正常工作。但是我还需要下面您可以看到的 Sparkline 图表,也可以通过检查下面链接的文件,将其作为图像/blob 发送,就在信息下方,就像上面的屏幕截图一样。

在此处输入图像描述

有人可以提供任何关于在应用上述解决方案时可能缺少什么的指示吗?或者有没有更好的选择可以通过电子邮件发送 SPARKLINE 图表作为图像/blob?

这是我的文件:
https://docs.google.com/spreadsheets/d/1ExXtmQ8nyuV1o_UtabVJ-TifIbORItFMWjtN6ZlruWc/edit?usp=sharing

EDIT_1:
我做了一些编辑,使其更加清晰。

EDIT_2:
根据要求,这是应用于第一个 Sparkline 的公式,第二个几乎相同:

=ARRAYFORMULA( SPARKLINE(QUERY({IFERROR(DATEVALUE(SANDBOX!$A$2:$A)), SANDBOX!$B$2:$B}, 
 "select Col2 
  where Col2 is not null 
  and Col1 <= "&INT(MAX(SANDBOX!$A$2:$A))&"
  and Col1 >  "&INT(MAX(SANDBOX!$A$2:$A))-(
IFERROR(
          VLOOKUP(
           SUBSTITUTE($F$4," ",""),
            {"24HOURS",0;
            "2DAYS",1;
            "3DAYS",4; 
            "7DAYS",8; 
            "2WEEKS",16;  
            "1MONTH",30;
            "3MONTHS",90; 
            "6MONTHS",180; 
            "1YEAR",365;
            "2YEARS",730; 
            "3YEARS",1095},
           2,FALSE))
)-1, 0),
 {"charttype","column";"color","#00bb21";"empty","ignore";"nan","ignore"}))

EDIT_3
根据 Rubén 的建议,我删除了代码块开头的 drawTable();

我还将 Sparkline 的公式转移到了另一个辅助表,并将其链接到主表。 尝试后似乎错误不再出现。虽然收到的电子邮件有两个问题:

  • 我以表格形式收到了整张工作表,而我只想看到 Sparklines。
  • 另外,Sparklines 不是以图像形式出现的,它们根本不显示。而且,它们应该出现的地方显示为 undefined

我猜是因为获取范围的函数 getDataRange(); 正在获取整个工作表范围,所以设置了整张工作表。

以下是屏幕截图:

在此处输入图像描述

2个回答

正如您引用的 问题 所解释的那样:

the chart created by SPARKLINE cannot be directly imported to the email.

为什么脚本不起作用?由于您没有对其进行任何重大修改,并且您使用的公式比另一个问题中提出的公式更复杂,因此很难(如果不是不可能)在不进行任何修改的情况下使其工作。

有哪些选择?在我看来,您有 3 种不同的选择。

  1. 遵循 Tanaike 在另一个问题中提出的解决方案的逻辑,并使用 EmbeddedChartBuilder 尝试粉碎 FORMULA 的内容,以实现与 SPARKLINE 相同的效果。

  2. 使用 SpreadsheetApp 方法直接从工作表中获取值并从那里构建图表。这里有一个小例子,说明如何使用 Chart Service 来执行此操作(您可以使用 EmbeddedChartBuilder 实现完全相同的功能)。由于您已经有一个 Blob 对象,您可以像我在 Sheet 中一样将其插入电子邮件中。

function constCreateChart() {
  const sS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('HELPER')
  const chart = Charts.newDataTable()
    .addColumn(Charts.ColumnType.NUMBER, '')
    .addColumn(Charts.ColumnType.NUMBER, '')

  // Modfify with your data
  // getRange('A2:A15').getValues()...
  const builder = [...Array(100).keys()].forEach(n => {
    chart.addRow([n, n * n * Math.random()])
  })
  chart.build()

  const chartShap = Charts.newColumnChart()
    .setDataTable(chart)
    .setLegendPosition(Charts.Position.NONE)
    .setOption('hAxis.ticks', [])
    .setOption('vAxis.ticks', [])
    .build()

  sS.insertImage(chartShap.getAs('image/png'), 5, 5)

}
结果

在此处输入图片说明

  1. 使用此 表单 请求 Google 添加可以将使用 SPARKLINES 获得的图表转换为可在电子邮件中使用的 Blob 对象。
文档
Emel
2022-06-06
  1. 删除 drawTable(); ,因为这行使得在调用任何函数时都会执行 drawTable 函数。
  2. 显然错误发生在 .addRange(sheet.getRange(formula.match(/\w+:\w+/)[0])) 上,更具体地说是因为 formula.match(/\w+:\w+/) (此表达式旨在提取 A1:B10 形式的范围引用)返回 null 。不幸的是,问题不包含公式。一种可能的解决方案可能很简单,只需用另一种方式替换 sheet.getRange(formula.match(/\w+:\w+/)[0]) 即可设置临时图表的源范围,但也可能更复杂,即添加一个辅助表作为临时图表的数据源。

注意:在 Rev 11 中添加了一个单元格内迷你图公式。由于公式非常复杂,最简单的解决方案是添加一个辅助表来添加 QUERY 函数

QUERY({IFERROR(DATEVALUE(SANDBOX!$A$2:$A)), SANDBOX!$B$2:$B}, 
 "select Col2 
  where Col2 is not null 
  and Col1 <= "&INT(MAX(SANDBOX!$A$2:$A))&"
  and Col1 >  "&INT(MAX(SANDBOX!$A$2:$A))-(
IFERROR(
          VLOOKUP(
           SUBSTITUTE($F$4," ",""),
            {"24HOURS",0;
            "2DAYS",1;
            "3DAYS",4; 
            "7DAYS",8; 
            "2WEEKS",16;  
            "1MONTH",30;
            "3MONTHS",90; 
            "6MONTHS",180; 
            "1YEAR",365;
            "2YEARS",730; 
            "3YEARS",1095},
           2,FALSE))
)-1, 0)

然后使用 helperSheet.getDataRange() 代替 sheet.getRange(formula.match(/\w+:\w+/)[0]) 。您必须设置适当的方式来声明 helperSheet


与 Rev. 8 相关

Tanaike 的答案 中的代码从 Sheet1 读取数据,但您的工作表名为 SANDBOX。

Wicket
2022-06-05