# Tooltip Image

**URL:** <https://talk.observablehq.com/t/tooltip-image/7102>\
**Category:** Help\
**Created:** [October 4, 2022, 12:53am UTC](https://talk.observablehq.com/t/tooltip-image/7102 "2022-10-04T00:53:52Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![ramonweber](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/ramonweber/32/6991_2.png) [@ramonweber](https://talk.observablehq.com/u/ramonweber)\
**Post date:** [October 4, 2022, 12:53am UTC](https://talk.observablehq.com/t/tooltip-image/7102/1 "2022-10-04T00:53:52Z")

</div>

Hi all,

I’m trying to follow and combine the tutorials on image plotting and tooltips:  
Work in progress observable: [https://observablehq.com/d/cc4d71aba075ebfd](https://observablehq.com/d/cc4d71aba075ebfd)

Currenlty it seems to only handle text such as in the tutorial on [extracting image data](https://observablehq.com/@observablehq/extracting-image-data) there seems to be a workaround by adding a: const div = html`<div></div>` to the tooltip. But I have difficulties understanding the code block, and can’t seem to figure out where the image actually gets added in?

How would it be possible to add an image to a tooltip? To something like this:

 ![temp_of](https://canada1.discourse-cdn.com/flex030/uploads/observablehq/original/2X/b/bac2f1faa3f76d4acbb97365b26adc249903b4b9.jpeg)

Many thanks for your help in advance!  
Ramon

---

<div class="post-metadata">

**Author:** ![mcmcclur](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/mcmcclur/32/2364_2.png) [@mcmcclur](https://talk.observablehq.com/u/mcmcclur)\
**Post date:** [October 4, 2022, 1:38am UTC](https://talk.observablehq.com/t/tooltip-image/7102/2 "2022-10-04T01:38:37Z")

</div>

I feel like I answered almost [this exact some question the other day](https://talk.observablehq.com/t/plot-tooltip-with-clickable-url-and-or-images/7082), but here’s how to do it with [TippyJS](https://atomiks.github.io/tippyjs/):

https://observablehq.com/embed/2adf5306477302cd?cells=viewof+metric%2Cscatter_plot_with_image_tooltips%2Ctippy_style

---

<div class="post-metadata">

**Author:** ![ramonweber](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/ramonweber/32/6991_2.png) [@ramonweber](https://talk.observablehq.com/u/ramonweber)\
**Post date:** [October 4, 2022, 3:51am UTC](https://talk.observablehq.com/t/tooltip-image/7102/3 "2022-10-04T03:51:56Z")

</div>

Thanks so much for the super quick and clear explanation!!

---

<div class="post-metadata">

**Author:** ![ramonweber](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/ramonweber/32/6991_2.png) [@ramonweber](https://talk.observablehq.com/u/ramonweber)\
**Post date:** [October 4, 2022, 6:01am UTC](https://talk.observablehq.com/t/tooltip-image/7102/4 "2022-10-04T06:01:18Z")

</div>

A follow up. I am trying to add custom images via a .zip file.  
Loading via sampleHeads = (FileAttachment(“sample-Heads.zip”)).zip()

This gives this error and I am unsure how to then access the file via the

 ?

 ![image](https://canada1.discourse-cdn.com/flex030/uploads/observablehq/original/2X/3/372933369e9f3c72b4b7d39b2fa145737ded71e9.png)

---

<div class="post-metadata">

**Author:** ![mcmcclur](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/mcmcclur/32/2364_2.png) [@mcmcclur](https://talk.observablehq.com/u/mcmcclur)\
**Post date:** [October 4, 2022, 7:48am UTC](https://talk.observablehq.com/t/tooltip-image/7102/5 "2022-10-04T07:48:16Z")

</div>

> [@ramonweber](#):
>
> A follow up. I am trying to add custom images via a .zip file.

Here’s a notebook explaining how to use ZIP files in attachements:

> **[File attachments | Observable documentation](https://observablehq.com/documentation/data/files/file-attachments#zip-files)**
>
> All your Observable questions answered

* * *

In that notebook, you’ll see code like

```
dogZip = FileAttachment("Dog_Photos@3.zip").zip()

```

in one cell and then

```
dogZip.file('n02085936_2905.jpg').image()

```

in another cell.

Given the error you describe, I would guess that you’re combining these types of things into one cell, without `await`ing the result, like so:

```
img = {
  let dogZip = await FileAttachment("Dog_Photos@3.zip").zip();
  return dogZip.file('n02085936_2905.jpg').image()
}

```

The explicit `await` is not necessary in the example notebook since the runtime automatically forces cells to resolve before passing the result.

---

<div class="post-metadata">

**Author:** ![ramonweber](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/ramonweber/32/6991_2.png) [@ramonweber](https://talk.observablehq.com/u/ramonweber)\
**Post date:** [October 4, 2022, 9:57pm UTC](https://talk.observablehq.com/t/tooltip-image/7102/6 "2022-10-04T21:57:19Z")

</div>

Yeah I had looked at the file but don’t understand how to use it inside a loop.

I just have this code as one line, it will sucessfully show the correct image

```auto
dogZip.file(dogImageNames[2]).image()

```

If I iterate through all the image names with [idx] and the correct image “path” e.g. ‘n02085936\_2905.jpg’ is shown as a title, why wouldn’t the code below inside the scatter plot loop show an image?

```auto
let content = `<div><h2>${dogImageNames[idx]} </h2><img src="${dogZip.file(dogImageNames[idx]).image()}"></div>`;

```

---

<div class="post-metadata">

**Author:** ![mcmcclur](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/mcmcclur/32/2364_2.png) [@mcmcclur](https://talk.observablehq.com/u/mcmcclur)\
**Post date:** [October 4, 2022, 11:41pm UTC](https://talk.observablehq.com/t/tooltip-image/7102/7 "2022-10-04T23:41:58Z")

</div>

> [@ramonweber](#):
>
> If I iterate through all the image names with [idx] and the correct image “path” e.g. ‘n02085936\_2905.jpg’ is shown as a title, why wouldn’t the code below inside the scatter plot loop show an image?

Yeah, dealing with Promises can be tricky. I would say that [MDN’s official documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) is the best you’re going to find. When dealing with iterables, it’s often the case that [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all), which takes an iterable of promises and resolves to an array, is the way to go.

If your objective is to generate a grid of images, I’d consider doing something like so:

```
pic_grid = {
  let dogZip = await FileAttachment("Dog_Photos@3.zip").zip();
  let names = d3
    .sort(
      (await dogZip.file("Readme.txt").text())
        .split("\r")
        .slice(3, -1)
        .map((s) => s.split("\t")),
      (a) => a[0]
    )
    .map((a) => a[1]);
  let imgs = await Promise.all(
    dogZip.filenames
      .filter((f) => f.slice(-4) == ".jpg")
      .map(async function (name, i) {
        let url = await dogZip.file(name).url();
        return await `<div style="display:inline-block"><h2>${
          names[i]
        }</h2><img width=${width / 4} src=${url} /></div>`;
      })
  );
  return html`${imgs}`;
}

```

Have a look:

> **[Viewing a grid of images via FileAttachment.zip()](https://observablehq.com/@mcmcclur/viewing-a-grid-of-images-ia-fileattachment-zip)**
>
> A response to this forum question Scatter plot with tooltip follow up

---

<div class="post-metadata">

**Author:** ![ramonweber](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/ramonweber/32/6991_2.png) [@ramonweber](https://talk.observablehq.com/u/ramonweber)\
**Post date:** [October 5, 2022, 12:29am UTC](https://talk.observablehq.com/t/tooltip-image/7102/8 "2022-10-05T00:29:58Z")

</div>

I see, many thanks for the thorough example!! I had thought this would be super straight forward somehow… I’m trying to replace the images of the heads of the presidents with dogs from a zip.

So how do I wrap the existing d3.select(plot) into the Promise.all ? and why does it need the await then? Since displaying a single image with html works:

 ![image](https://canada1.discourse-cdn.com/flex030/uploads/observablehq/original/2X/3/3991c80949e5f70609c2d322e0a022227e648a87.jpeg)

but when I have it inside the loop the image doesn’t show.

```auto
scatter_plot_with_image_tooltips2 = {
  let dogZipInside = await FileAttachment("54_Dogs.zip").zip();
  let dogImageNamesInside = await dogZipInside.filenames.filter(d => d.match(/\.jpg$/))
  let plot = Plot.plot({
    marks: [
      Plot.dot(data2, {
        fill: "black",
        x: "First Inauguration Date",
        y: (d) => metric.value(d) / 100,
        title: (d, i) => i
      })
    ]
  });
  
  d3.select(plot)
    .selectAll("circle")
    .nodes()
    .forEach(function (c) {
      let idx = parseInt(d3.select(c).select("title").text());
      let p = data2[idx];
      let dogURL = dogZip.file(dogImageNamesInside[idx]).url()
      let content = `<div><h2>${dogImageNamesInside[idx]} </h2><img src="${dogURL}"></div>`;
      console.log(content);
      tippy(c, {
        content: content,
        theme: "light",
        allowHTML: true
      });
      d3.select(c).select("title").remove();
    });
  
  return plot;
}

```

also if I try and place the await (also if I try and place an await in the code block below (to let) it will throw an error ‘Unexpected token’)

```auto
let url5 = await dogZip.file(dogImageNames[4]).url()

```

---

<div class="post-metadata">

**Author:** ![mcmcclur](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/mcmcclur/32/2364_2.png) [@mcmcclur](https://talk.observablehq.com/u/mcmcclur)\
**Post date:** [October 5, 2022, 1:29am UTC](https://talk.observablehq.com/t/tooltip-image/7102/9 "2022-10-05T01:29:37Z")

</div>

> [@ramonweber](#):
>
> So how do I wrap the existing d3.select(plot) into the Promise.all ? and why does it need the await then? Since displaying a single image with html works:

Without looking at your notebook, it’s hard to say but, again, [Observable runs in a reactive manner](https://observablehq.com/@observablehq/how-observable-runs) which means, in part, that if Cell 2 depends on Cell 1, then Cell 2 will wait for Cell 1 to resolve before Cell 2 executes. Thus, the `await` is taken care of automatically by the runtime.

> [@ramonweber](#):
>
> I’m trying to replace the images of the heads of the presidents with dogs from a zip.

Well then, in your Scatter Plot notebook, your visualization is built on data stored in the `favorability` variable, which is a list of objects containing `Name` and `Portrait URL` keys. Let’s just format your dog data to match exactly that:

```
dog_data = {
  let dogZip = await FileAttachment("Dog_Photos@3.zip").zip();
  let breeds = d3
    .sort(
      (await dogZip.file("Readme.txt").text())
        .split("\r")
        .slice(3, -1)
        .map((s) => s.split("\t")),
      (a) => a[0]
    )
    .map((a) => a[1]);
  let urls = await Promise.all(
    dogZip.filenames
      .filter((f) => f.slice(-4) == ".jpg")
      .map(async function (name, i) {
        return dogZip.file(name).url();
      })
  );
  return d3.zip(breeds, urls).map((a) => ({ breed: a[0], url: a[1] }));
}

```

That’s not quite a drop in replacement for your `favorability` variable but it’s kinda close.

Here it is after a little fiddling with the `Plot` code. I assume you’d want some other data for the `x` or `y` channels.

https://observablehq.com/embed/@mcmcclur/viewing-a-grid-of-images-ia-fileattachment-zip?cells=scatter_plot_with_image_tooltips%2Ctippy_style

---

<div class="post-metadata">

**Author:** ![ramonweber](https://yyz2.discourse-cdn.com/flex030/user_avatar/talk.observablehq.com/ramonweber/32/6991_2.png) [@ramonweber](https://talk.observablehq.com/u/ramonweber)\
**Post date:** [October 5, 2022, 7:00pm UTC](https://talk.observablehq.com/t/tooltip-image/7102/10 "2022-10-05T19:00:58Z")

</div>

Thanks so much for all your help!! I got it to work now and created this small sample:  
[creating a plot that displays images associated with a csv dataset](https://observablehq.com/@ramonweber/shared-combining-a-csv-file-with-zipped-images-to-display-ea)
