【问题标题】:Embedding Altair htmls in google sites, how to `mark_image` using private google drive links?在谷歌网站中嵌入 Altair html,如何使用私有谷歌驱动器链接“mark_image”?
【发布时间】:2022-01-10 17:32:50
【问题描述】:

我正在尝试将使用 Altair 制作的交互式绘图嵌入到 google-site 中。在此图中,我想一次以交互方式显示存储在 google-drive 上的图像。当我尝试这样做时,mark_image 默默地失败了,大概是因为它没有读取图像。这并不奇怪,因为 google-drive 图像是私有的。使用公开共享的图像,我不会遇到这个问题。出于此情节的目的,我想将图像保密。另外,总共有很多图像(~1K),所以我可能不应该将它们编码为数据/字节。我怀疑这可能会使我的 HTML 文件变得又大又慢。如果我在这方面错了,请纠正我。

我想知道mark_image 是否可以从 google-drive 链接读取图像,可能使用某种“阅读器”(上游 JS 或 python 库),然后将读取的图像提供给mark_image。如果有人有这方面的经验,解决方案/建议/解决方法将非常有帮助。

这里有一个演示代码来测试这个:

案例 1:可公开访问的图片(没问题)。使用 mark_image 显示,以 HTML 格式保存。

import altair as alt
import pandas as pd
path="https://vega.github.io/vega-datasets/data/gimp.png"
source = pd.DataFrame([{"x": 0, "y": 0, "img": path},])

chart=alt.Chart(source).mark_image(width=100,height=100,).encode(x='x',y='y',url='img')
chart.save('test.html')

然后我使用这个选项将HTML嵌入到一个谷歌网站(私有,不向公众共享),然后将HTML文件的内容粘贴到Embed code标签中。

案例 2:Google-drive 上的图片(问题!)。图片存储在 google-drive 上的案例(私有,不向公众共享)。

# Please use the code above with `path` variable generated like this:
file_id='' # google drive file id
path=f"https://drive.google.com/uc?export=view&id={file_id}"

在这种情况下,mark_image 显然会静默失败,并且图像不会显示在绘图上。 ​

【问题讨论】:

    标签: google-drive-api altair vega-lite google-sites-2016


    【解决方案1】:

    在寻找最佳解决方案后,我决定依靠一种将图像编码为数据/字节的解决方法。这消除了从谷歌驱动器读取 URL 的问题,我找不到解决方案。 正如我所怀疑的那样,以数据/字节编码图像会使 HTML 变大,然而,令人惊讶的是(对我来说)加载速度一点也不慢。我想这是我想做的最好的事情。

    在下面的示例中,get_data 函数获取图像的数据/字节。我将它放入Altair 作为输入的数据框列中。

    def plot_(images_from):
        import altair as alt
        import pandas as pd
        import numpy as np
    
        np.random.seed(0)
        n_objects = 20
        n_times = 50
        # Create one (x, y) pair of metadata per object
        locations = pd.DataFrame({
            'id': range(n_objects),
            'x': np.random.randn(n_objects),
            'y': np.random.randn(n_objects)
        })
        def get_data(p):
            import base64
            with open(p, "rb") as f:
                return "data:image/jpeg;base64,"+base64.b64encode(f.read()).decode()
        import urllib.request
        if images_from=='url':
            l1=[f"https://vega.github.io/vega-datasets/data/{k}.png" for k in ['ffox','7zip','gimp']]
        elif images_from=='data':
            l1=[get_data(urllib.request.urlretrieve(f"https://vega.github.io/vega-datasets/data/{k}.png",f'/tmp/{k}.png')[0]) for k in ['ffox','7zip','gimp']]
    
        np.random.seed(0)
        locations['img']=np.random.choice(l1, size=len(locations))
    
        # Create a 50-element time-series for each object
        timeseries = pd.DataFrame(np.random.randn(n_times, n_objects).cumsum(0),
                                columns=locations['id'],
                                index=pd.RangeIndex(0, n_times, name='time'))
    
        # Melt the wide-form timeseries into a long-form view
        timeseries = timeseries.reset_index().melt('time')
    
        # Merge the (x, y) metadata into the long-form view
        timeseries['id'] = timeseries['id'].astype(int)  # make merge not complain
        data = pd.merge(timeseries, locations, on='id')
    
        # Data is prepared, now make a chart
    
        selector = alt.selection_single(empty='none', fields=['id'])
    
        base = alt.Chart(data).properties(
            width=250,
            height=250
        ).add_selection(selector)
    
        points = base.mark_point(filled=True, size=200).encode(
            x='mean(x)',
            y='mean(y)',
            color=alt.condition(selector, 'id:O', alt.value('lightgray'), legend=None),
        )
    
        timeseries = base.mark_line().encode(
            x='time',
            y=alt.Y('value', scale=alt.Scale(domain=(-15, 15))),
            color=alt.Color('id:O', legend=None)
        ).transform_filter(
            selector
        )
    
        images=base.mark_image(filled=True, size=200).encode(
            x='x',
            y='y',
            url='img',
        ).transform_filter(
            selector
        )
    
        chart=points | timeseries | images
        chart.save(f'test/chart_images_{images_from}.html')
    
    # generate htmls
    plot_(images_from='url') # generate the HTML using URLs
    plot_(images_from='data') # generate the HTML using data/bytes
    

    使用这些数据生成的 HTML 比使用 URL 生成的 HTML 大约 78 倍(~12Mb vs ~0.16Kb),但速度没有明显变慢。

    更新:我后来发现谷歌网站不允许嵌入超过 1Mb 大小的 HTML 文件。所以最后,对图像进行编码并没有真正的帮助。

    【讨论】:

    • 更新:当许多图像被编码存储在其中时,html 文件的大小呈指数增长。我的一个 html 文件变成了 ~140Mb(即使在使用低分辨率 JPEG 图像时)。当我试图将 html 的内容粘贴到谷歌网站的embed code 窗口时,谷歌网站变得无响应。因此,这种变通方法存在明显的局限性。
    • 更新:作为 google-sites 的替代方案,它允许嵌入高达 1Mb 的 HTML 代码,我尝试使用带有 Dropbox 的 Silex (silex.me) 来存储数据。在那里,嵌入式 HTML 的大小也是一个问题。 Silex 允许嵌入最大 5Mb 的 HTML。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-03
    • 1970-01-01
    • 2022-01-16
    • 1970-01-01
    • 2019-06-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多