Skip to content

Instantly share code, notes, and snippets.

@etpinard
Last active September 28, 2022 06:01
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save etpinard/58a9e054b9ca7c0ca4c39976fc8bbf8a to your computer and use it in GitHub Desktop.
Save etpinard/58a9e054b9ca7c0ca4c39976fc8bbf8a to your computer and use it in GitHub Desktop.
Generate plotly.js images (only svg for now) in Node.js using jsdom
"Generate plotly.js images (only svg for now) in Node.js using jsdom"
node_modules
package-lock.json

plotly.js-jsdom

Generate plotly.js images (only svg for now) in Node.js using jsdom.

How to run this thing

npm start

which outputs fig.svg

Future work

  • Try using the canvas npm package to support png and jpeg (and webp?) exports
  • Try to incorporate headless-gl into plotly.js to support exports for WebGL-based graphs
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
const fsPromises = require('fs').promises;
const jsdom = require('jsdom')
const pathToPlotly = require.resolve('plotly.js-dist')
const fig = { data: [{ y: [1, 2, 1] }] }
const opts = { format: 'svg', imageDataOnly: true }
const virtualConsole = new jsdom.VirtualConsole()
virtualConsole.sendTo(console)
const w = new jsdom.JSDOM('', { runScripts: 'dangerously', virtualConsole }).window
// mock a few things that JSDOM doesn't support out-of-the-box
w.HTMLCanvasElement.prototype.getContext = function() { return null; };
w.URL.createObjectURL = function() { return null; };
fsPromises.readFile(pathToPlotly, 'utf-8')
.then(w.eval)
.then(() => w.Plotly.toImage(fig, opts))
.then(img => fsPromises.writeFile('fig.svg', img))
.catch(console.warn)
{
"name": "plotly.js-jsdom",
"version": "1.0.0",
"description": "Generate plotly.js images (only svg for now) in Node.js using jsdom",
"main": "index.js",
"scripts": {
"start": "node main.ks"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"jsdom": "^13.1.0",
"plotly.js-dist": "^1.43.1"
},
"engines": {
"node": ">=10"
}
}
@spraot
Copy link

spraot commented Apr 3, 2019

The margins don't seem to get adjusted properly. The settings are ignored, and the legend text gets cut off. I guess this might be due to poor support in jsdom. Any ideas for a workaround?

@zbjornson
Copy link

Try using the canvas npm package to support png and jpeg (and webp?) exports

You can remove line main.js:15 and add "canvas": "2" as a dependency and this will "just work."

However, a lot of Plotly functionality still doesn't work with this (a lot of text is missing for some reason).

@HuddleHouse
Copy link

I'm going to leave this here in case anyone finds it useful. I was able to generate all of the Plotly charts with Puppeteer server side.

This returns a base64 encoded png.

const puppeteer = require('puppeteer')

var puppeteerPromise = function (plotlyData, layout) {
  return new Promise(async (resolve, reject) => {

    try {
      const browser = await puppeteer.launch()
      const page = await browser.newPage()
      await page.setContent(`<html>
      <body>
      <span id="chart">&nbsp;</span>
      </body>
      </html>`)
      await page.addScriptTag({ url: 'https://code.jquery.com/jquery-3.2.1.min.js' })
      await page.addScriptTag({ url: 'https://cdn.plot.ly/plotly-1.58.4.min.js' })

      await page.exposeFunction("plotlyData", plotlyData)
      await page.exposeFunction("layout", layout)

      let image = await page.evaluate(function (plotlyData, layout) {

        // @ts-ignore
        const Plotly = window.Plotly
        Plotly.newPlot('chart', plotlyData, layout)
        return Plotly.toImage('chart', { format: 'png', width: layout.width ? layout.width : 800, height: layout.height ? layout.height : 600 })
      }, plotlyData, layout)

      await browser.close()
      return resolve(image.replace(/^data:image\/(png|jpg);base64,/, ""))
    } catch (e) {
      console.log("🚀 ~ file: index.js ~ line 13 ~ returnnewPromise ~ e", e)
      return reject(e)
    }
  })
}

export const render = puppeteerPromise

Here is an example of how to use it.

import * as exp from "./plotly-png"
const fs = require("fs-extra")

function createHeatmap() {
  return new Promise(async (resolve, reject) => {

    var data = [
      {
        x: ['giraffes', 'orangutans', 'monkeys'],
        y: [20, 14, 23],
        type: 'bar'
      }
    ];
    var layout = {
        height: 600,
        width: 600,
        showlegend: true,
        margin: { t: 30, r: 30, l: 50, b: 30 }
    }
    
    let imageBuffer = await exp.render(data, layout)

   await fs.writeFile(`barchart.png`, imageBuffer, 'base64')

   // For my use case I upload it to s3
   let image = await uploadImageToS3(Buffer.from(imageBuffer, 'base64'))
   return resolve({ imageUrl: image['Location'] })

  })
}

@jplox
Copy link

jplox commented Sep 28, 2022

@HuddleHouse the above code doesn't generate any png and any alternative work around .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment