Googleスプレッドシートで作成したグラフを自動で
- 画像として取得
- PDFに変換
これができるブラウザ完結型プログラムを作成してみました。
なぜ作ろうと思ったか
大学の論文用にグラフをスプレッドシートで作った。
しかし、以下の問題点があった。
- スプレッドシートには作ったグラフを直接画像に変換する機能はない
- 研究室から、Latex(論文書くやつ)にグラフ読み込む際はPDFにするよう指定された
スプレッドシートをhtmlとして保存すれば
そのhtmlを開いて、グラフを右クリックで画像として保存できるみたいだが、
そんなめんどくさいことを毎回するなんて絶対イヤだった。
そんなわけで、画像に変換し、さらにPDFに変換するとこまで全部自動化することにした。
完成品の動作デモ
完成品だけ見せてよって方がいると思うので先にお見せします
流れとしては
- スプレッドシートのページでコンソールにコマンド入力
- 画像に変換され、「画像をPDFに変換できるサイト」に飛ぶ
- 自動で画像ドラッグ&ドロップイベントを発生させ、PDFに変換
- PDFをダウンロードして完了
コマンド入力が面倒に感じる場合はボタンでも設置して押したら実行するようにすればいいかなと。
どうやって実現したか
とりあえず、全部ブラウザだけで完結させたいと思った。なので以下のものを用意
- Tampermonkey(サイト上で自動で任意のjsコードを実行できる拡張機能)
- png2pdf.com(ブラウザ完結させたいのでオンラインPDF変換サイトを利用)
あとはコードを書いていくだけ。
グラフを画像に変換する
スプレッドシート内のグラフはhtml5のCanvasAPIで描かれていることがわかった。
Canvasには元々描画されている内容を画像URLとして出力する機能がある。
toDataURL()
というメソッド?を使用する。
グラフのcanvasを指定して、toDataURL()
を実行すれば画像URLが得られる。
function getGraphImages() {
const canvasEls = document.getElementsByTagName("canvas");
let result = []
for (let i = 0; i < canvasEls.length; i++) {
const canvas = canvasEls[i];
if (canvas.parentElement.className.includes("chart")) {
const imageURL = canvas.toDataURL();
console.log(imageURL);
result.push(imageURL);
}
}
return result;
}
ちょっと余計なグラフまで取得するバグがあるけど、
とりあえずclassNameに"chart"
が含まれてるcanvasだけ取得して画像URLを得た。
PDF変換サイトで画像をPDFに変換
あとは取得した画像をPDFに変換するだけだが、ここで問題があった。
画像をPDFに変換するサイトには「画像URLをPDFに変換する機能」はない。
直接そのサイトで使われているAPIを叩くにしても、解析するのがめんどくさい。
ということで、もうこれは力技でいくしかないということで以下の手順ですることに。
- png2pdf.comに遷移するときに画像のDataURLをURLにハッシュとしてくっつける
- 画像URL(Data URL)を
File
オブジェクト?に変換 - 変換サイトで無理やりドラッグ&ドロップイベントを発火させ、ファイルをコンバートさせる
- PDFをダウンロード
画像DataURLを保持したままPDF変換サイトに遷移
スプレッドシートから得たグラフ画像のData URLを保持したまま、PDF変換サイト(png2pdf.com)に遷移したかったので
最適な方法かはわからないけど、とりあえずhashとしてくっつけた。
(そうすれば遷移した後にlocation.hash
すれば画像Data URLを取得できるので)
なので遷移後のURLはこんな感じになる
https://png2pdf.com/ja/#data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABL...
新しいタブで遷移は以下を参考にしました
https://qiita.com/TK-C/items/c64ca54b634b0cae0059
画像DataURLをFileオブジェクトに変換
コードはstackoverflowのアンサーから拝借しました。
https://stackoverflow.com/questions/16968945/convert-base64-png-data-to-javascript-file-objects/16972036
function dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {type:mime});
}
JavaScriptでドラッグ&ドロップイベントを発火
JavaScriptからファイルのドラッグ&ドロップイベントを発火させる方法は@anqooqieさんの記事を参考させていただいた。
[JavaScript] ファイルのドロップイベントをJavaScriptから発火させる
https://qiita.com/anqooqie/items/c72508810a5be022e03d
イベント発火系は普段扱わないので、非常に参考になりました。ありがとうございます。
完成したコード
あとはこれまでの流れを自動で実行できるようにTampermonkeyでスクリプトを読み込んで完了。
需要ないと思うけどコード置いておきますね
ちなみに 使い方 はスプレッドシートのページでwindow.convertToPDF()
コマンドをコンソール上で実行するだけです。
// ==UserScript==
// @name Spreadsheet Graph Downloader + PDF Converter
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author LostMyCode
// @match https://docs.google.com/spreadsheets/d/*
// @match https://png2pdf.com/ja/*
// @icon https://www.google.com/s2/favicons?domain=google.com
// @grant none
// ==/UserScript==
// Created by LostMyCode (https://github.com/LostMyCode)
(function () {
'use strict';
function getGraphImages() {
const canvasEls = document.getElementsByTagName("canvas");
let result = []
for (let i = 0; i < canvasEls.length; i++) {
const canvas = canvasEls[i];
if (canvas.parentElement.className.includes("chart")) {
const imageURL = canvas.toDataURL();
console.log(imageURL);
result.push(imageURL);
}
}
return result;
}
function convertToPDF() {
const imageDataURLs = getGraphImages();
const converter = "https://png2pdf.com/ja/";
let hash = "#";
for (let i = 0; i < imageDataURLs.length; i++) {
const dataURL = imageDataURLs[i];
hash += dataURL;
if (i != imageDataURLs.length - 1) {
hash += "**"
}
}
// location.href = converter + hash;
window.open(converter + hash, '_blank')
}
window.getGraph = getGraphImages;
window.convertToPDF = convertToPDF;
function fireDropEvent(domElement, files) {
// DataTransferの偽物を定義する
class FakeDataTransfer {
constructor(files) {
this.dropEffect = "none";
this.effectAllowed = "all";
this.files = files;
this.types = ["Files"];
}
addElement() {
// do nothing
}
clearData() {
// do nothing
}
getData() {
return "";
}
setData() {
// do nothing
}
setDragImage() {
// do nothing
}
}
const event = new DragEvent("drop");
Object.defineProperty(event, "dataTransfer", { value: new FakeDataTransfer(files) });
domElement.dispatchEvent(event);
}
function dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {type:mime});
}
console.log("Graph Downloader loaded.");
if (location.host == "png2pdf.com") {
const target = document.getElementById("filesScroll");
const dataURLs = location.hash.replace(/#/, "").split("**");
console.log(dataURLs)
let files = dataURLs.map((v, i) => dataURLtoFile(v, i + ".png"));
console.log(files)
fireDropEvent(target, files);
}
})();
おわりに
ぶっちゃけ大学の研究の論文書くのがいやで、現実逃避するためにこんなことやってます笑
Qiitaのこの記事も卒業論文書く片手間書いております
読んでいただきありがとうございました。