How to render figures in Pluto markdown?

Hi

How to render local figures in Pluto.jl markdown?
I’ve tried

md"""
![fig1](fig1.png)
"""

in pluto notebook cells where fig1.png is under the same directory as the notebook. However, the figure was note rendered at all.

Any help would be much appreciated!

2 Likes

One way is to use LocalResource from PlutoUI.jl.

Another, maybe more transferable, is to manually encode your image into base64 and embed it as

$(Resource(
    "data:image/png;base64,#=base64-encoded file here=#",
    MIME("image/png"),
    (:height => 240)
))
4 Likes

I have run into a tricky situation. I cannot get my image to display in Pluto using either of these two methods. :man_facepalming:

My image is a POMDP model that I have drawn using Inkscape and exported as a .png and then also as .jpg. Inkscape gives me a great deal of control in how I export the image, but I have always kept the default settings. The images do open in my web browser… and I have played with some settings, like turning off compression. Still no luck.

I have also tried PlutoUI.LocalResource examples as shown here:
Docstrings · PlutoUI.jl}

At best, if I use the 2nd method (recommended by @Vasily_Pisarev) except I only include the path, exclude MIME and other html, then I receive an icon of a “broken photo”.

I have copied the image from the original directory, to the directory where my Pluto environment is active, and tried using both links… but had no success.

At this point, I am totally baffled, and would appreciate any insight. :hot_face:

By the way… Google Bard could not provide me with a different recommendation than these two… so it does not seem like a code issue. Maybe there are some image requirements in Pluto that I am just unaware of. :person_shrugging:

I went ahead and put my image in a Google Drive, then generated the embed code. I have inserted the embed code using html in markdown. :white_check_mark:

html"""
<div><iframe src="https://drive.google.com/file/d/[*file id in drive*]/preview" width="640" height="480" allow="autoplay"></iframe></div>
"""

It works, but I do not intend to share the Pluto notebook, so it would really be convenient to facilitate local storage. Maybe even add a /public folder in the Pluto server that can be used to store “local resources” without the need for a path pointing to the file on the hard disk.

Unfortunately, even with the file stored in Google Drive, the other two methods discussed still did not work properly, probably due to security policies. :frowning_face:

  • $(Resource)
  • ![my text](url of image)

@GenerallyClueless,

Can you share the incriminated image file so that we can try something out or is it confidential?

If you original file is an svg (I assume it is since you mentioned creating it in InkScape) you can also try showing the svg directly rathern than going through conversion to png or jpg.

The browser has direct support for rendering svg files and you usually get better resolution and lower file-size with an svg.

The same issue :neutral_face:

And quite a beginner and I need help with this.

I just tried again with random images downloaded from the internet to check if something broke in PlutoUI but it does not seem the case.

If for example you take this very simple notebook (code below) where I download some images locally and try to show them with LocalResource I don’t have any problem:

Notebook Code
### A Pluto.jl notebook ###
# v0.19.26

using Markdown
using InteractiveUtils

# ╔═╡ 045e4cb0-3751-11ee-3712-51904ee2f4bd
using PlutoUI

# ╔═╡ 331661ec-eed1-4850-9bab-64f47b8b6032
md"""
## PNG
"""

# ╔═╡ b1d67c3c-9d3b-4965-84b3-b60540ac86f2
pngpath = let
	path = tempname() * ".png"
	Base.download("https://docs.axure.com/assets/screenshots/widgets/connector-icon.png", path)
end

# ╔═╡ b703d591-b8bb-48b7-b5c4-aa2dac422716
LocalResource(pngpath)

# ╔═╡ 7cd6cb7e-2fae-49b0-9314-16d2f1bd1448
md"""
## SVG
"""

# ╔═╡ dd7e3b14-ad1e-47ba-b3a9-30dc858a7a65
svgpath = let
	path = tempname() * ".svg"
	Base.download("https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/osa.svg", path)
end

# ╔═╡ 65001fd6-fef4-4337-bf98-b42d21665855
LocalResource(svgpath)

# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[deps]
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"

[compat]
PlutoUI = "~0.7.52"
"""

# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised

julia_version = "1.9.2"
manifest_format = "2.0"
project_hash = "f5c06f335ceddc089c816627725c7f55bb23b077"

[[deps.AbstractPlutoDingetjes]]
deps = ["Pkg"]
git-tree-sha1 = "91bd53c39b9cbfb5ef4b015e8b582d344532bd0a"
uuid = "6e696c72-6542-2067-7265-42206c756150"
version = "1.2.0"

[[deps.ArgTools]]
uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
version = "1.1.1"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

[[deps.ColorTypes]]
deps = ["FixedPointNumbers", "Random"]
git-tree-sha1 = "eb7f0f8307f71fac7c606984ea5fb2817275d6e4"
uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
version = "0.11.4"

[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
version = "1.0.5+0"

[[deps.Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"

[[deps.Downloads]]
deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"]
uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
version = "1.6.0"

[[deps.FileWatching]]
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"

[[deps.FixedPointNumbers]]
deps = ["Statistics"]
git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc"
uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
version = "0.8.4"

[[deps.Hyperscript]]
deps = ["Test"]
git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9"
uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91"
version = "0.0.4"

[[deps.HypertextLiteral]]
deps = ["Tricks"]
git-tree-sha1 = "c47c5fa4c5308f27ccaac35504858d8914e102f9"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.9.4"

[[deps.IOCapture]]
deps = ["Logging", "Random"]
git-tree-sha1 = "d75853a0bdbfb1ac815478bacd89cd27b550ace6"
uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89"
version = "0.2.3"

[[deps.InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"

[[deps.JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.4"

[[deps.LibCURL]]
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
version = "0.6.3"

[[deps.LibCURL_jll]]
deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"]
uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0"
version = "7.84.0+0"

[[deps.LibGit2]]
deps = ["Base64", "NetworkOptions", "Printf", "SHA"]
uuid = "76f85450-5226-5b5a-8eaa-529ad045b433"

[[deps.LibSSH2_jll]]
deps = ["Artifacts", "Libdl", "MbedTLS_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
version = "1.10.2+0"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.LinearAlgebra]]
deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"

[[deps.MIMEs]]
git-tree-sha1 = "65f28ad4b594aebe22157d6fac869786a255b7eb"
uuid = "6c6e2e6c-3030-632d-7369-2d6c69616d65"
version = "0.1.4"

[[deps.Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"

[[deps.MbedTLS_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
version = "2.28.2+0"

[[deps.Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"

[[deps.MozillaCACerts_jll]]
uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
version = "2022.10.11"

[[deps.NetworkOptions]]
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
version = "1.2.0"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
version = "0.3.21+4"

[[deps.Parsers]]
deps = ["Dates", "PrecompileTools", "UUIDs"]
git-tree-sha1 = "716e24b21538abc91f6205fd1d8363f39b442851"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "2.7.2"

[[deps.Pkg]]
deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
version = "1.9.2"

[[deps.PlutoUI]]
deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "FixedPointNumbers", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "MIMEs", "Markdown", "Random", "Reexport", "URIs", "UUIDs"]
git-tree-sha1 = "e47cd150dbe0443c3a3651bc5b9cbd5576ab75b7"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.52"

[[deps.PrecompileTools]]
deps = ["Preferences"]
git-tree-sha1 = "9673d39decc5feece56ef3940e5dafba15ba0f81"
uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
version = "1.1.2"

[[deps.Preferences]]
deps = ["TOML"]
git-tree-sha1 = "7eb1686b4f04b82f96ed7a4ea5890a4f0c7a09f1"
uuid = "21216c6a-2e73-6563-6e65-726566657250"
version = "1.4.0"

[[deps.Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"

[[deps.REPL]]
deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[deps.Random]]
deps = ["SHA", "Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"

[[deps.Reexport]]
git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.2.2"

[[deps.SHA]]
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
version = "0.7.0"

[[deps.Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"

[[deps.Sockets]]
uuid = "6462fe0b-24de-5631-8697-dd941f90decc"

[[deps.SparseArrays]]
deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"]
uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"

[[deps.Statistics]]
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
version = "1.9.0"

[[deps.SuiteSparse_jll]]
deps = ["Artifacts", "Libdl", "Pkg", "libblastrampoline_jll"]
uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c"
version = "5.10.1+6"

[[deps.TOML]]
deps = ["Dates"]
uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
version = "1.0.3"

[[deps.Tar]]
deps = ["ArgTools", "SHA"]
uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
version = "1.10.0"

[[deps.Test]]
deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[[deps.Tricks]]
git-tree-sha1 = "aadb748be58b492045b4f56166b5188aa63ce549"
uuid = "410a4b4d-49e4-4fbc-ab6d-cb71b17b3775"
version = "0.1.7"

[[deps.URIs]]
git-tree-sha1 = "b7a5e99f24892b6824a954199a45e9ffcc1c70f0"
uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
version = "1.5.0"

[[deps.UUIDs]]
deps = ["Random", "SHA"]
uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[[deps.Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
version = "1.2.13+0"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
version = "5.8.0+0"

[[deps.nghttp2_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
version = "1.48.0+0"

[[deps.p7zip_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
version = "17.4.0+0"
"""

# ╔═╡ Cell order:
# ╠═045e4cb0-3751-11ee-3712-51904ee2f4bd
# ╟─331661ec-eed1-4850-9bab-64f47b8b6032
# ╠═b1d67c3c-9d3b-4965-84b3-b60540ac86f2
# ╠═b703d591-b8bb-48b7-b5c4-aa2dac422716
# ╟─7cd6cb7e-2fae-49b0-9314-16d2f1bd1448
# ╠═dd7e3b14-ad1e-47ba-b3a9-30dc858a7a65
# ╠═65001fd6-fef4-4337-bf98-b42d21665855
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002

Can you give some more details of what is failing for you?

Welcome @V_Batala, I am also new! Try using the html"“” [code] “”" method that I had posted above using a link to a file somewhere online.

If you are using Google Drive, set the sharing to “anyone with link” unless you want to log in each time :hot_face: and generate the embed code… don’t just use the link to the file in drive. I tried that, but for some reason the image still would not load (I think it was error 404 and I assumed permission). The embed code in that snippet works well, and I can zoom in/out.

Sure, this is the image that I wanted to put in the notebook. It’s a simple model for a POMDP. I started the JuliaAcademy POMDPs course (GREAT COURSE, THANK YOU @RobertMoss!!) and I was inspired to try and write one for a real-world application. This is a call center.

@GenerallyClueless

Using Resource and the link to the image you just attached I can see it fine in a notebook:

And the same should also work with LocalResource if you give as input to that function the path of the image on your disk.

It is easier if you put the full path of the image instead of the relative path so it will still work if you move the notebook around in the PC

Thanks for your attention @disberd. The only thing that I see we were doing differently is your code uses download() to pull a file from an https linked source … and I tried to provide a local path to the file on my hard disk.

I used download just because I am running the Pluto notebook on a remote server where I didn’t have any image file at hand, but the download was just a way to get any image on the local machine where pluto is running (and make it reproducible just with the notebook code).

The LocalResource should work regardless of how you obtained the image file

1 Like

That’s very interesting and I will have to look at how download() stores the file. I stopped trying the hard drive link once I noticed this in the PlutoUI.jl Featured Notebook:

I did spend more time than I would have liked trying to provide the path to the file on the hard disk, and unfortunately it did not work for me. My only guess (only a guess) was the Pluto Server may not be able to link to various hard drive paths, but perhaps a file downloaded with download() would be stored in a folder recognized and accessible to Pluto Server? :person_shrugging:

I just kind of reached the point where I spent too much time trying the local file, and I needed to move on. Putting the file in Google Drive worked, so now I could work on solving the problem that I really wanted to solve.

… although, now I am even more curious as to why it works for you and not for me. :thinking:

Thank you again @disberd :tada:

If the file is not found due to problems with correct path or server having acces to that path you should see a file not found error like in the example notebook screenshot you just posted.

Going through an online link is good for reproducibility as mentioned in the example notebook, but you should be able (and prefer) to use Resource directly rather doing this:

html"""
<div><iframe src="https://drive.google.com/file/d/[*file id in drive*]/preview" width="640" height="480" allow="autoplay"></iframe></div>
"""

as you mentioned above.

It is strange that they do not work and if you can share a google link drive on one such image we can try to see if the problem comes from the way you call Resource

I have retyped the three methods that I had used:
markdown format

online resource

local resource

:flushed:

WOW… Pluto just made a liar out of me.

I must have corrected some error that I struggled with for hours, because at this very moment, the LocalResource is working. The only thing that I did differently was use my entire path instead of using the “shortened” path in Linux with ~/

I am embarrassed :flushed: because I know that I had tried this before. Well… I hope my embarrassment helps to save someone else a headache :face_with_head_bandage:

So for the first two methods not working, I think the link is wrong as I can’t seem to access it (I get page can’t be reached). Even in the case where the link is actually pointing to a valid accessible image file (like if I wrote the wrong link which is possible), it might be that this fails because you have to specify the MIME to interpret this file.

For what concerns the problem with ~ in the local path. That is unfortunately normal as most of the functions in julia that take paths do not automatically substitute ~ with the home directory.
You have the expanduser function in Base specifically for that, so you’d have to write:
LocalResource(expanduser("~/something/somethingelse")) for it to work correctly.

The above code only works on UNIX systems, if you want something consistent across systems you’d better use homedir and normpath as:
LocalResource(normpath(homedir(),"something/somethingelse"))

1 Like

Thank you @disberd for clarifying, and I will keep the path in mind for the future.

The links do work for me, whether I am signed in or signed out of Google. I had tried to add MIME(“image/png”) and (:height => 415) as additional arguments to Resource(), as was done in the original marked solution, but ended up with errors.

Something might have changed since.
The error indicates that Resource wants a tuple, so it needs a comma to make it a tuple:

parameters = (height => 100, ) 
Resource("https://julialang.org/assets/infra/logo.svg", MIME("image/svg"), parameters)

Without the MIME argument it is possible to use a single pair, as

Resource("https://julialang.org/assets/infra/logo.svg", height=>100)

which is perhaps why the (height=>100) worked sometimes (it’s just a regular pair in this case).