Skip to content

Instantly share code, notes, and snippets.

@bpgould
Created April 24, 2025 21:46
Show Gist options
  • Save bpgould/63d8f849b5fa16df93ea4b9527a781d7 to your computer and use it in GitHub Desktop.
Save bpgould/63d8f849b5fa16df93ea4b9527a781d7 to your computer and use it in GitHub Desktop.
Generate Sankey Visualization in Google Sheets via AppScript
// In Google Sheets > Extensions > App Script
// Copy in code and agree to permissions
// Run script - it will run on current open worksheet
// Worksheet must have 3 columns: From, To, Weight
// Script will generate the diagram with Google Charts, copy to image, close modal, and upload image to sheet
// The Script also adds a shortcut that allows you to run it from the Sheets Toolbar via Chart Tools
// One limitation is no tooltips in image (vs modal) and no easy way to label nodes with the weight value
function drawSankey() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const data = sheet.getRange("A1:C" + sheet.getLastRow()).getValues();
const html = `
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script>
google.charts.load('current', { packages: ['sankey'] });
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
console.log("Drawing chart...");
const data = new google.visualization.DataTable();
data.addColumn('string', 'From');
data.addColumn('string', 'To');
data.addColumn('number', 'Weight');
data.addRows(${JSON.stringify(data.slice(1))});
const chartDiv = document.getElementById('chart');
const chart = new google.visualization.Sankey(chartDiv);
google.visualization.events.addListener(chart, 'ready', () => {
console.log("Chart ready. Capturing image...");
html2canvas(chartDiv).then(canvas => {
const scale = 0.5; // Adjust to stay under limits
const scaledCanvas = document.createElement("canvas");
scaledCanvas.width = canvas.width * scale;
scaledCanvas.height = canvas.height * scale;
const ctx = scaledCanvas.getContext("2d");
ctx.drawImage(canvas, 0, 0, scaledCanvas.width, scaledCanvas.height);
const imageData = scaledCanvas.toDataURL("image/png").replace(/^data:image\\/png;base64,/, "");
google.script.run
.withSuccessHandler(() => {
console.log("Image inserted into sheet.");
google.script.host.close();
})
.withFailureHandler(err => {
console.error("Image upload failed:", err);
alert("Image upload failed: " + err.message);
google.script.host.close();
})
.insertImageToSheet(imageData);
}).catch(err => {
console.error("html2canvas error:", err);
alert("Canvas render failed: " + err.message);
google.script.host.close();
});
});
chart.draw(data, {});
}
</script>
</head>
<body>
<div id="chart" style="width: 900px; height: 500px;"></div>
</body>
</html>
`;
const htmlOutput = HtmlService.createHtmlOutput(html)
.setWidth(960)
.setHeight(600);
SpreadsheetApp.getUi().showModalDialog(htmlOutput, 'Generating Sankey Diagram...');
}
function insertImageToSheet(base64) {
const blob = Utilities.newBlob(Utilities.base64Decode(base64), 'image/png', 'sankey.png');
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const lastRow = sheet.getLastRow();
sheet.insertImage(blob, 1, lastRow + 2); // Insert image 2 rows below the data
}
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('📊 Chart Tools') // You can name this anything
.addItem('Insert Sankey Diagram', 'drawSankey') // Menu text, function name
.addToUi();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment