What: Dynamically set the contents of ggiraph tooltips in rmarkdown knitted to html on page load.
Why: Using embedded png's tooltips can be made to be graphics which is valuable for certain biological structures where text is insufficient. Here is a minimal example of what I'm currently doing:
encodedImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAUCAIAAAAcIrrpAAAACXBIWXMAAASdAAAEnQF8NGuhAAADGElEQVR4nI2Uy08TURTG+VuMJmCCoIKIxq2ujEQT9y40yKsiILrSGITIzoVxZUwDFQlKQiC0ttMpMwXsi9JSSkvftJ0WWkuhBVtoAb+ZKeNQy+PkZjJz5t7fPfec892yg1K2u7u7tbW1vr4ejUYDgYDL5fJ4PHhhGCYej+NXyVWwsqLvnZ0dULAsFAphvdfrBcvhcNjt9oWFhfn5eZPJpNfr8b62tra/v38sLpPJxGIxTML+q6urIAaDQZ/P53a7nU4ncFar1Ww2G41GnU43MzND0/T09DR2LYHb3t4OcxaJRAQiPq2u8KI36vDHXKGEJ5xkX/yMPxCYm5sjSVKlUikUCmx5BIdcrHCGcIBAXCBig3w+X95E1HVR4nG9i7rWSd3opls+mX+ZrJOTkxMTE8hsAYc1vkPz+/2AIsBcLsf/vtKuOfdEWXJcaFTdeTPLMJHx8fGxsTFknMVtbGwgOx7OkHhEhw2E4E/AYZQ3E8Na9iijo6M2m43FIZfLnKGC4KZSKXFqBVxFs/rRB/PDfuP9PgPOe75RxfsbevWYRlEUksjigHBwhvIBKg5NjKvvpgVnNJm9+nyK9996pYUHJxsZGWFxSPySyI7DIf3h35lg/A+en4mVKgnJ++/26DANJyMIgsVtbm4ucmbjLJFIlMRVtpIoa23HFJ6VbaRQjfc/3JimVCoRCovb29vDm5Uzi8WCU0MYZyxF/Qs6nclDPENDQ+l0utB30AOig4DQ9OhPcNF0J+NqO6l77/RMIgO1DA4OouePqCKbzUKGEJDBYIAkISOUBVUWcOXNaqSp5rAC1c80PTITOm5gYAASKhbZASd+hA0W9KjVajUaDTJyqZXg19d1aHC7qHVL1ZKC52KT/Mt3dTKZFOe6+EaBHiA1EKFHuVxe2fKTX3y5TS6TyaRSaXv/14qnCt55+/Vs0fJiHG+4eZA+XDDVEjW/sqZdjcZEzyOchl5dodxtJGGNn477v7LiNrYHU8gd77/5khZfeqfgqiQlcLAHfQZBtlIyeFbc448WSBWjZ3hZ7HeG07wf4+23f7/+An2Gx2N7QYsiAAAAAElFTkSuQmCC"
g = ggplot()+
geom_point_interactive(aes(x=1,y=1,tooltip=sprintf('<img src=\"%s\" />',encodedImage)))
girafe(code=print(g))
The downside to this is that for every plot using the tooltip graphic the encoded image is repeated, resulting in a file size that is too large to store many of.
How: To mitigate the increase in file size with the increase in use of tooltips I'm looking to assign the embedded image text into a json object, then dynamically update all tooltips to be the embedded image using javascript.
Things I've Done: I'm able to get the embedded image stored in a json by simply including script tags and outputing json text inside of those tags. For testing heres the hard-coded example with a simple replace text:
<script type="application/json" id="lookuptable">
{"ID1":"ReplaceText"}
</script>
What I can't do is replace the text of the tooltips. Essentially I was planning on setting the tooltip to and ID of some sort and using that to match the embedded image to the corresponding point. The tooltip text is stored in a json by ggiraph in something like:
<script type="application/json" data-for="htmlwidget-b8ceca7828d4dd46f692"> {x:{"html":.....}}</script>
In this json all html components (<,",>,etc) are "escaped," I believe this "htmlwidget" data is passed to what is essentially a mini-html page embedded in the html page, the "htmlwidget".
I've tried wrapping my temporary tooltips in <div> tags, but since they are tied up in a JSON they aren't seen in the DOM.
I've tried just naively replacing all instances if ID in any script tags:
<script type="application/javascript">
console.log(document.getElementById('lookuptable').innerHTML)
var lookuptable = JSON.parse(document.getElementById('lookuptable').innerHTML);
var scriptTags = document.getElementsByTagName("script");
for (s=0; s < scriptTags.length; s++){
var item = scriptTags[s];
for(var k in lookuptable){
console.log(item.innerHTML);
item.innerHTML.replace(k,lookuptable[k])
}
}
</script>
However it seems that by the time that this script runs the json no longer has the tooltip text in it (though it's in the html source).
This is where I currently am stuck. I'll update or answer this question if I make any more progress.
Also I'm well aware this would be trivial with Shiny, unfortunately that is not an option as the html page needs to be entirely standalone as the real rmarkdown I'm building is meant to be a standalone report.
Finally here is a complete, reproducible example. The final knitted project should result in the tooltip being "ReplaceText" (fix R code blocks by removing \):
---
title: "Demo"
author: "Zachary Klamer"
date: "12/9/2021"
output: html_document
---
\```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(ggplot2)
library(ggiraph)
library(rjson)
\```
\```{r,echo=FALSE}
lookup = list()
lookup$FullID1 = "ReplaceText"
jsonData = toJSON(lookup)
\```
<script type="application/json" id="lookuptable">
`r jsonData`
</script>
\```{r,echo=FALSE}
g = ggplot()+
geom_point_interactive(aes(x=1,y=1,tooltip='FullID1'))
girafe(code=print(g))
\```
<script type="application/javascript">
console.log(document.getElementById('lookuptable').innerHTML)
var lookuptable = JSON.parse(document.getElementById('lookuptable').innerHTML);
var scriptTags = document.getElementsByTagName("script");
for (s=0; s < scriptTags.length; s++){
var item = scriptTags[s];
for(var k in lookuptable){
console.log(item.innerHTML);
item.innerHTML.replace(k,lookuptable[k])
}
}
</script>
There may be several different ways to achieve this goal, in particular I suspect that using the htmlwidgets "onRender" function may be able to achieve this more cleanly but I never got that to work.
What I've found is that any editing of the innerHTML of the htmlwidget or htmlwidget data breaks the mouseover text completely, because it breaks the event listener which powers the mouseover text.
Instead I've found I can edit the resulting svg of the htmlwidget by wrapping my ending script in a $(window).load(function(){ ... }) call. If I find all svg elements (in this case circles!) and edit the title property of those svg objects I can preserve the event listeners and change the title contents (to be an image!).
See complete example below which has 1000 1kb images as tooltips but gives no increase in file size:
---
title: "Demo"
author: "Zachary Klamer"
date: "12/9/2021"
output: html_document
---
\```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
library(ggplot2)
library(ggiraph)
library(rjson)
library(htmlwidgets)
\```
\```{r,echo=FALSE}
lookup = list()
lookup$FullID1 = '<img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAUCAIAAAAcIrrpAAAACXBIWXMAAASdAAAEnQF8NGuhAAADGElEQVR4nI2Uy08TURTG+VuMJmCCoIKIxq2ujEQT9y40yKsiILrSGITIzoVxZUwDFQlKQiC0ttMpMwXsi9JSSkvftJ0WWkuhBVtoAb+ZKeNQy+PkZjJz5t7fPfec892yg1K2u7u7tbW1vr4ejUYDgYDL5fJ4PHhhGCYej+NXyVWwsqLvnZ0dULAsFAphvdfrBcvhcNjt9oWFhfn5eZPJpNfr8b62tra/v38sLpPJxGIxTML+q6urIAaDQZ/P53a7nU4ncFar1Ww2G41GnU43MzND0/T09DR2LYHb3t4OcxaJRAQiPq2u8KI36vDHXKGEJ5xkX/yMPxCYm5sjSVKlUikUCmx5BIdcrHCGcIBAXCBig3w+X95E1HVR4nG9i7rWSd3opls+mX+ZrJOTkxMTE8hsAYc1vkPz+/2AIsBcLsf/vtKuOfdEWXJcaFTdeTPLMJHx8fGxsTFknMVtbGwgOx7OkHhEhw2E4E/AYZQ3E8Na9iijo6M2m43FIZfLnKGC4KZSKXFqBVxFs/rRB/PDfuP9PgPOe75RxfsbevWYRlEUksjigHBwhvIBKg5NjKvvpgVnNJm9+nyK9996pYUHJxsZGWFxSPySyI7DIf3h35lg/A+en4mVKgnJ++/26DANJyMIgsVtbm4ucmbjLJFIlMRVtpIoa23HFJ6VbaRQjfc/3JimVCoRCovb29vDm5Uzi8WCU0MYZyxF/Qs6nclDPENDQ+l0utB30AOig4DQ9OhPcNF0J+NqO6l77/RMIgO1DA4OouePqCKbzUKGEJDBYIAkISOUBVUWcOXNaqSp5rAC1c80PTITOm5gYAASKhbZASd+hA0W9KjVajUaDTJyqZXg19d1aHC7qHVL1ZKC52KT/Mt3dTKZFOe6+EaBHiA1EKFHuVxe2fKTX3y5TS6TyaRSaXv/14qnCt55+/Vs0fJiHG+4eZA+XDDVEjW/sqZdjcZEzyOchl5dodxtJGGNn477v7LiNrYHU8gd77/5khZfeqfgqiQlcLAHfQZBtlIyeFbc448WSBWjZ3hZ7HeG07wf4+23f7/+An2Gx2N7QYsiAAAAAElFTkSuQmCC\" />'
jsonData = toJSON(lookup)
\```
<script type="application/json" id="lookuptable">
`r jsonData`
</script>
\```{r,echo=FALSE}
g = ggplot()+
geom_point_interactive(aes(x=1:1000,y=1:1000,tooltip='FullID1'))
girafe(code=print(g))
\```
<script type="application/javascript">
$(window).load(function(){
var lookuptable = JSON.parse(document.getElementById('lookuptable').innerHTML);
var keys = Object.keys(lookuptable);
var circleTags = document.getElementsByTagName("circle");
for (s=0; s < circleTags.length; s++){
var item = circleTags[s];
var itemTitle = item.attributes.title.nodeValue;
console.log(itemTitle);
if (keys.includes(itemTitle)){
item.attributes.title.nodeValue=lookuptable[itemTitle];
}
console.log(item.attributes.title.nodeValue);
}
});
</script>
Related
I need to update the contents of a dynamic div with information I a getting from a csv file using Python / Flask. I cannot attach my full code as it is quite long, but here is a summary of the problem.
I am using a normal csv read routine and then serving it with Flask:
#app.route('/myStuff', methods=['GET', 'POST'])
def myStuff():
with open('static/databases/myData.csv', 'rb') as n:
reader = csv.reader(n, delimiter=",")
counter = len(myDatabase)
myDatabase = list(reader)
myIds = [col[1] for col in myDatabase]
Then, in my myStuff.html I have a parent div to be fed:
<div id="myDiv"></div>
Later, I call the csv data like this:
var myDatabase = {{ myDatabase|safe }};
var counter = {{ counter|safe }};
Then, using javascript I generate a number of divs within myDiv according to the length of the csv data:
for(var i = 1; i < counter; i++) {
$('#myDiv').append('<div>' + myIds[i] + '</div>');
};
In parallel I have some routines to modify the csv file (specifically the number of items), and I want them to be updated wihout reloading the complete html. I have tried adding a button to my html:
<button type="button" id="myButton" >Reload</button>
And then using a simple function with jQuery to reload myDiv within the html:
$('#myButton').on('click', function() {
$('#myDiv').load('/myStuff.html');
});
But nothing seems to be reloading. I am quite new to this sort of routine, and I have not been able to find any tutorial or example dealing with the same kind of data (csv) using Python / Flask. Any help will be really appreciated.
Edit:
Thanks to #caisah I managed to reload the data using AJAX, but I am having a new problem. I only need the children of #myDiv to be reloaded and with the code I have for now the complete html is been reloaded within the #myDiv container:
$('#myButton').on('click', function() {
$('#myDiv').load('/myStuff');
});
I have tried pointing at the contents using:
$("#myDiv").load(location.href + " #myDiv");
After #Peca's answer to this question, but only the parent div (and not the children) gets reloaded. Any hints on how to achieve this?
I try to add a copy paste option in my program but links doesn't come with.
All my blocks are well copied but my links aren't.
var copied;
$("#copy").click(function(){
var papa = block_menu.model; //clicked element
var copied_cells=papa.clone({deep:true}); //take all embedded cells
copied=graph.getSubgraph(copied_cells, {deep:true}); //copy
});
$("#paste").click(function(){
graph.addCells(copied); //paste (add on graph)
});
I've tried to add this before "copied = ...." but that doesn't change anything :
var copied_cells = graph.getSubgraph(copied_cells)
`
Does someone nows how to copy my links with?
Thanks.
cells should be sorted before you're putting them back into graph. Elements first, then links. addCells has been adding cells as it is, so if there is link whose target/source is not in the graph yet, this link won't be added.
After much Googling, I resort to the chance at ridicule and moderation on Stack Exchange.
What I am trying to do sounds simple enough. I want to make a <div> id/class that will link automatically create a link to itself via some kind of scripting.
Let me put down some pseudocode, before I make it sound more complicated than it is:
#Let div.link = xxx.html
#Let div.pic = xxx.png/jpg
for div in HTMLdocument:
if div.class == "autolink":
div = "<img src=\"mysite/" + div.pic + ">"
Now, obviously that's Python pseudocode, but I am familiar(ish) with PHP and Javascript. Basically, I want to make the div generate an HTML link without having to actually type out the tags and links for every given div on a web page. I want to be able to type, in my index.html:
<html>
<head></head>
<body>
<div class = "1"></div>
<div class = "2"></div>
</body>
</html>
and then to be presented with a page that has the two divs linked, imaged, and, preferably, formatted.
Like I said, the problem seems simple, but I can't seem to get it to work right, in any language. This seems like a thing that would be very useful for begiiner web designers.
PERSONAL NOTE:
I would preferably like to see a solution in PHP or Javascript, but if you are good with Django and want to show me how to get it done in that, I would be just as grateful!
=========================================
EXAMPLE:
Let's say you have a browser based RPG, and you want to show your player's inventory. The Inventory page would display the items in a players inventory, complete with a link and image, based on whatever was in that user's inventory page. It would look like this (this is VERY rough output, Statements tagged in #these# are not code, and should be interpereted as what they describe):
<h1>User's Inventory:</h1>
<p><div class = "item_1">#Link to page of 'item_1', image of 'item_1'#</div></p>
<p><div class = "item_2">#Link to page of 'item_2', image of 'item_2'#</div></p>
The above would display, roughly, a header that said "User's Inventory", and then display a linked image of item_1, followed by a newline and then a linked image of item_2, where said items would be in a separate file OR a list that lists all the items and their respective links and images.
You can use jquery, and when page dom is loaded, you cycle through each div that has the class autolink and do your manipulations (add your desired html into each div). You can use the id of each div to place data inside. You can use a prefix to that id values for different types of data. For example, im using "inventory_" as a prefix.
<h1>User's Inventory:</h1>
<p><div class = "autolink" id="inventory_item_1">#Link to page of 'item_1', image of 'item_1'#</div></p>
<p><div class = "autolink" id="inventory_item_1">#Link to page of 'item_2', image of 'item_2'#</div></p>
then jquery on document ready:
<script type="text/javascript">
$(document).ready(function ()
{
// define your website here
var mysite = "http://www.example.com/";
// this will cycle through each div with class autolink. using `this` to reffer to each.
$(".autolink").each(function () {
// we get for div with id="inventory_item_1" ...
var mylink = $(this).attr('id').replace("inventory_",""); // ... a value item_1
var myimagesrc = $(this).attr('id').replace("inventory_","image_"); // ... image_item_1
$(this).html('<img src="'+mysite+'images/'+myimagesrc+'.jpg">');
// the above will add html code of this format:
// <img src="http://www.example.com/images/image_item_1.jpg">
});
});
</script>
try it here:
http://jsfiddle.net/5APhT/2/
I'll give a sample in php. Here is an example if you already have a set of links to use
<?php
//Create a multidimensional array to store all you need to create links
$links['1'][]="http://www.yahoo.com";
$links['1'][]="yahoo.com";
$links['2'][]="http://www.facebook.com";
$links['2'][]="yahoo.com";
$links['3'][]="http://www.google.com";
$links['3'][]="yahoo.com";
foreach($links as $class => $innerArray){
$link=innerArray[0];
$linktext=innerArray[1];
echo "<div class='$class'><a href='$link'>$linktext</a></div>";
}
?>
This creates the divs for you so you don't have to add them in advance.
You can add images in the same manner
I have emacs org-generated HTML that contains dvipng'd equations by default. That's ok, I want to provide a good fallback for those who disabled their JS. The generated pictures and resulting <img> tags have complete LaTeX source in their alt attribute. I have also tweaked the code to apply a specific class to all such images so it becomes a simpler matter in JS end, which isn't my forté.
The MathJax documentation lists a couple of different configurations and a way to customize a configuration by myself; I don't see a selector function or anything to apply Mathjax to suit my needs. I know I can write own DOM-munging JS and then call Mathjax afterwards but surely Mathjax bends to this kind of functionality by itself?
This is an example from the generated HTML.
<img class="dvipng" src="upload/blog_ff4e604.png"
alt="$\frac{1}{2\pi{}i}\int_C\frac{f'(z)}{f(z)}{\rm d}z = N_0(f)$">
MathJax doesn't include this directly, but you could do the following:
<script type="text/x-mathjax-config">
MathJax.Extension.myImg2jax = {
version: "1.0",
PreProcess: function (element) {
var images = element.getElementsByTagName("img");
for (var i = images.length - 1; i >= 0; i--) {
var img = images[i];
if (img.className === "dvipng") {
var script = document.createElement("script"); script.type = "math/tex";
var match = img.alt.match(/^(\$\$?)(.*)\1/);
if (match[1] === "$$") {script.type += ";mode=display"}
MathJax.HTML.setScript(script,match[2]);
img.parentNode.replaceChild(script,img);
}
}
}
};
MathJax.Hub.Register.PreProcessor(["PreProcess",MathJax.Extension.myImg2jax]);
</script>
<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML"></script>
which defines a new preprocessor that looks for your images and extracts the math from the ALT tags. This assumes you use $...$ for in-line math and $$...$$ for displayed math.
This code removed the image when it converts it to the MathJax <script> tag. It would be possible to move the image to a preview span where it will show up until MathJax processes the TeX code. That is a bit more sophisticated, but could be done with a little more work. I'll leave that to the interested reader. :-)
Basically, i'm building a basic jquery modal plugin as practice, and as much as the many varieties out in the wild have been helpful for reference, I wasn't sure about the best method for pushing html into the modal that is being built in my jQuery.
This code is building my popups:
var container = $('<div class="container"></div>');
var header = $('<div class="modal"><div class="modal-header"></div></div>');
var content = $('<div class="modal-body"></div>');
var footer = $('<div class="modal-footer"><a class="secondary" href="#">Cancel</a><span id = "button-secondary-label">or</span><span class="green_btn"></span></div>');
$(".popup").append(container);
$(".container").append(header);
$(".modal").append(content);
$(".modal").append(footer);
In an html file that is calling the javascript file with the above code, I would like to pass html to modal-body. I know I could just write the following:
$(".modal-body").html("html goes here")
but I would like to make the plugin handle as much as possible, so what would be the best way to do the following:
in example.html:
...
<script type="text/javascript">
$(.popup).modal();
</script>
</head>
<body>
<div class="popup"><div class="body-text">I want this text in the .modal-body</div></div>
in the js file:
I would like to take the html that is in .body-text and move it into the .modal-body class, or something that will get me similar results.
Again, trying to accomplish this in the external js file, and not in example.html
It's common to use IDs (as it can be set as the href and can be used to point to the given content of a link) instead when trying to target DOm element from modal boxes. So you could do
Some content to be in the modal
Then just grab it and append it to the cmodal
$('#boo').appendTo(content);
You can also have an option so that it can be clone and left the original copy behind
$('#boo').clone().appendTo(content);
You can technically handle classes as well but after depends how your plugin is built. As you can grab all and generate a single modal content or create the multiple content to browse between using them.
$('.popup').each(function() {
$(this).appendTo(content);
});
var items = $('.popup'),
len = items.length,
cur = 0;
$next.click(function() {
cur++;
if (cur === len) cur = 0;
items.eq(cur).show();
});