1

I am struggling to understand why an image from google drive (private or shared to public) is displayed on google site without any issue, when I embed it like this:

<!DOCTYPE html>
<html>

<body>
    <img src="https://drive.google.com/uc?export=view&id=0B6wwyazyzml-OGQ3VUo0Z2thdmc" width="500">
</body>

</html>

Here, the image on google drive is stored at https://drive.google.com/uc?export=view&id=0B6wwyazyzml-OGQ3VUo0Z2thdmc and is publically shared. The file ID of the image is 0B6wwyazyzml-OGQ3VUo0Z2thdmc.

Since the image is displayed, it means that reading images from google drive on google site is possible.

But when I generate an html using Altair (Vega-lite), with the following minimal code:

import altair as alt
import pandas as pd
source = pd.DataFrame({"img": ["https://drive.google.com/uc?export=view&id=0B6wwyazyzml-OGQ3VUo0Z2thdmc",]})
chart=alt.Chart(source).mark_image(width=100,height=100,).encode(x=alt.value(0),y=alt.value(0),url='img')
chart.save('test/gdrive.html')

The content of the html:

<!DOCTYPE html>
<html>
<head>
  <style>
    .error {
        color: red;
    }
  </style>
  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm//vega@5"></script>
  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm//vega-lite@4.8.1"></script>
  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm//vega-embed@6"></script>
</head>
<body>
  <div id="vis"></div>
  <script>
    (function(vegaEmbed) {
      var spec = {"config": {"view": {"continuousWidth": 400, "continuousHeight": 300}}, "data": {"name": "data-f41fe47a6561a1d8f01ad1fd7b81dffd"}, "mark": {"type": "image", "height": 100, "width": 100}, "encoding": {"url": {"type": "nominal", "field": "img"}, "x": {"value": 0}, "y": {"value": 0}}, "$schema": "https://vega.github.io/schema/vega-lite/v4.8.1.json", "datasets": {"data-f41fe47a6561a1d8f01ad1fd7b81dffd": [{"img": "https://drive.google.com/uc?export=view&id=0B6wwyazyzml-OGQ3VUo0Z2thdmc"}]}};
      var embedOpt = {"mode": "vega-lite"};

      function showError(el, error){
          el.innerHTML = ('<div class="error" style="color:red;">'
                          + '<p>JavaScript Error: ' + error.message + '</p>'
                          + "<p>This usually means there's a typo in your chart specification. "
                          + "See the javascript console for the full traceback.</p>"
                          + '</div>');
          throw error;
      }
      const el = document.getElementById('vis');
      vegaEmbed("#vis", spec, embedOpt)
        .catch(error => showError(el, error));
    })(vegaEmbed);

  </script>
</body>
</html>

The image is not shown.

enter image description here

I wonder why that would happen.

For a quick diagnosis, I looked into the vega-editor's LOGS tab. There I see this error: [Error] Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The HTMLImageElement provided is in the 'broken' state.

  • Within the `LOGS` tab of the vega-editor, I see this error: `[Error] Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The HTMLImageElement provided is in the 'broken' state.` –  Dec 12 '21 at 22:23
  • You should add that information to your question (edit the question and put it in). – psmears Dec 12 '21 at 22:24
  • Could you include an example of a public drive image URL that doesn’t render? – jakevdp Dec 13 '21 at 02:12
  • Hi @jakevdp, I just added a drive image URL (that I saw in one of the questions on embedding google drive images in google site: https://stackoverflow.com/a/52067077/3521099) –  Dec 13 '21 at 03:02
  • Hi @psmears, I just added the error message in my question. –  Dec 13 '21 at 03:02

1 Answers1

0

Short answer: this is not working because of your browser's cross-origin security features, and the only fix is to host the image elsewhere.

Long version: if you open the Javascript console logs when viewing your chart, you'll see something like this:

Access to image at 'https://drive.google.com/uc?export=view&id=0B6wwyazyzml-OGQ3VUo0Z2thdmc' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

This indicates that the script running on your page is attempting to access a resource that deliberately disallows itself to be accessed from scripts. There is no way to get around this without changing the CORS headers on the Google Drive content, and Google does not make this possible for very good reasons: if the headers were set more permissively, then malicious javascript running on any webpage you visit could programmatically scrape content from your Google drive.

So you'll have to host your image elsewhere.

An easy alternative might be do download the image and host it on the same server where you are hosting the chart code; e.g.

from urllib.request import urlretrieve
urlretrieve("https://drive.google.com/uc?export=view&id=0B6wwyazyzml-OGQ3VUo0Z2thdmc", "test/img.jpg")

import altair as alt
import pandas as pd
source = pd.DataFrame({"img": ["img.jpg"]})
chart=alt.Chart(source).mark_image(width=100,height=100,).encode(x=alt.value(0),y=alt.value(0),url='img')
chart.save('test/chart.html')
jakevdp
  • 77,104
  • 11
  • 125
  • 160
  • Thank you very much for your answer. That explains a lot. I was actually using google-drive specifically because I did not want to not host the images elsewhere on a public domain. :) But that's unfortunately a no-go. –  Dec 13 '21 at 15:30
  • As a potential solution for this, I tried encoding the images in bytes/data. But then the size of the HTML becomes too large to embed it in google-site. Google-site does not allow such embedding of htmls with >1Mb size. –  Dec 19 '21 at 16:38
  • So, I am now in search of an alternative of google-site that allows embedding of large HTMLs (https://webapps.stackexchange.com/q/161819/163839). Encoding images as bytes/data would definitely make the html slow to render, but I guess this is the only solution I could think of for now. –  Dec 19 '21 at 16:39