Search code examples
pythonbokeh

How to save multiple figures from bokeh html file as separate png figures but download just as one zip file?


This question is based on How to save the multiple figures in a bokeh gridplot into separate png files?, where the accepted answer already provides working code to save the multiple figures in a bokeh grid plot into separate PNG files.

However, with that answer, a number of png-figures are downloaded, and each png-figure is a separate file. It is more convenient for my users to download all the png-figures just as one zip file.
Could you help me to do it?


Solution

  • Using the accepted answer in the linked post as a starting point, you can modify the javascript to download a zip instead of individual pngs. Here's one way to do that:

    let script = document.createElement('script');
    script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js';
    script.onload = function () {
      let shadow_root1 = document.querySelector(".bk-GridPlot");
      let shadow_root2 = shadow_root1.shadowRoot.querySelector(".bk-GridBox");
      let shadow_root3 = shadow_root2.shadowRoot.querySelectorAll(".bk-Figure");
    
      let urls = [];
    
      shadow_root3.forEach(figure => {
        let shadow_root4 = figure.shadowRoot.querySelector(".bk-Canvas");
        let canvas = shadow_root4.shadowRoot.querySelector("canvas");
        let url = canvas.toDataURL("image/png");
        urls.push(url);
      });
    
      let zip = new JSZip();
    
      let count = 0;
      urls.forEach(url => {
        let xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.responseType = 'blob';
        xhr.onload = function() {
          if (xhr.status === 200) {
            let filename = `CanvasAsImage${++count}.png`;
            zip.file(filename, xhr.response, { binary: true });
            if (count === urls.length) {
              zip.generateAsync({ type: 'blob' }).then(function(content) {
                let downloadLink = document.createElement('a');
                downloadLink.download = 'CanvasAsImage.zip';
                downloadLink.href = URL.createObjectURL(content);
                downloadLink.click();
              });
            }
          }
        };
        xhr.send();
      });
    };
    document.head.appendChild(script);
    

    EDIT:

    To name the pngs according to their titles, a couple small modifications are necessary. First, pass in both p1 and p2 into the callback:

    button = Button(label="Save all figures")
    callback = CustomJS(
        args=dict(p1=p1, p2=p2),
        code="""
    

    Then, change the filename variable:

    let filename = [p1.title.text, p2.title.text][count++] + '.png';