I have created a Quarto blog post which contains many leaflet maps, generated in R. As the data for each map is embedded within the html file, the file itself is very large. This is causing problems on the server which hosts the file.
I want to make the html file smaller. The embed-resources: false YAML option in Quarto means that the libraries (e.g. leaflet.js) are stored in separate files. The helps but the data is still stored within the html (once per map). I am trying to load the data itself from a separate file. Here is a minimal example of a qmd file:
---
format:
html:
embed-resources: false
---
```{r}
leaflet::leaflet(elementId = "map1") |>
leaflet::addTiles() |>
leaflet::addMarkers(lng = 174.768, lat = -36.852, popup = "The birthplace of R")
```
When I quarto render this file it creates an html file. If opened in a browser, it shows a map. The file includes the data for the map in the following <div>:
<div class="leaflet html-widget html-fill-item-overflow-hidden html-fill-item" id="map1" style="width:100%;height:464px;"></div>
<script type="application/json" data-for="map1">{**json**}</script>
</div>
Where I have written {**json**} there is one long line of json with the map coordinates, CRS and various options.
It seemed to me that I might be able to copy the json content to a file and then change the <script> tag to load the data from that file:
<script src="page_data/map1.json" type="application/json" data-for="map1"></script>
However, I know now this is not possible. Instead, I have tried adding a script to inject the json into the innerHTML of the required element (using Live Server for testing):
<script>
fetch('./page_data/map1.json')
.then((response) => response.json())
.then((json) => (
document.querySelectorAll('[data-for="map1"]')[0].innerHTML =
JSON.stringify(json).replaceAll("\/", "/"))
);
</script>
This works in that it loads the exact json content into the tag as when it was hard-coded into the html file (the replaceAll() is required to make it identical as a couple of escape characters are added before backslashes).
However, this alone does not display the map and the console throws this error:
Uncaught SyntaxError: Unexpected end of JSON input
at JSON.parse (<anonymous>)
at htmlwidgets.js:646:27
The relevant line of htmlwidgets.js is:
var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']");
var data = JSON.parse(scriptData.textContent || scriptData.text);
i.e. at the point the loaded script is looking for the data, the fetch() request has not yet updated the innerHTML of the <script data-for="map1"></script> tag.
With this in mind, with the fetch() request method I moved the htmlwidgets.js and other <script> tags. Currently there are about 10 lines of tags in the <head> like:
<script src="page_files/libs/htmlwidgets-1.6.2/htmlwidgets.js"></script>
<script src="page_files/libs/jquery-1.12.4/jquery.min.js"></script>
If I move these from the <head> to between the </body> and </html> tags, the map renders around half the time. So it looks like there's some sort of race between them loading and the script in the injecting the json into the <script data-for="map1"></script> tag.
To ensure the loading happened in the right order, I removed them from the html and used this async loadScript() function to dynamically load the scripts to ensure that they are only loaded after the data loads:
fetch('./map1withhooks.json')
.then((response) => response.json())
.then((json) => (
document.querySelectorAll('[data-for="map1"]')[0].innerHTML = JSON.stringify(json).replaceAll("\/", "/")))
.then(() =>
loadScript("page_files/libs/htmlwidgets-1.6.2/htmlwidgets.js")
).then(() =>
loadScript("page_files/libs/jquery-1.12.4/jquery.min.js"));
/*etc - for all scripts on the page in the order they appear in the html*/
The scripts now only load after the json is injected into the <script data-for="map1"></script> tag. However, it does not render the map at all and the html widget is not registered (i.e. document.getElementById("map1").htmlwidget_data_init_result in the console returns null).
Am I missing something about the order that events are supposed to happen on a static Quarto-generated web page with htmlwidgets?
Is there a way for a Quarto html file to load the data for a leaflet map generated in R from a local json file?
from Load Quarto html map data from json for Leaflet map generated in R
No comments:
Post a Comment