freeze mouseCoordinates and enable copying
It would be great if we could
- define a keystroke that freezes the mouseCoordinates
- enable copying of the contents in the mouseCoordinates strip
I have something close now... Though currently only working in browser, not in RStudio viewer...
addMouseCoordinates2 <- function(map, style = c("detailed", "basic"),
epsg = NULL, proj4string = NULL,
native.crs = FALSE) {
style <- style[1]
if (inherits(map, "mapview")) map <- mapview2leaflet(map)
stopifnot(inherits(map, "leaflet"))
if (style == "detailed" && !native.crs) {
txt_detailed <- paste0("
' x: ' + L.CRS.EPSG3857.project(e.latlng).x.toFixed(0) +
' | y: ' + L.CRS.EPSG3857.project(e.latlng).y.toFixed(0) +
' | epsg: 3857 ' +
' | proj4: +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs ' +
' | lon: ' + (e.latlng.lng).toFixed(5) +
' | lat: ' + (e.latlng.lat).toFixed(5) +
' | zoom: ' + map.getZoom() + ' '")
} else {
txt_detailed <- paste0("
' x: ' + (e.latlng.lng).toFixed(5) +
' | y: ' + (e.latlng.lat).toFixed(5) +
' | epsg: ", epsg, " ' +
' | proj4: ", proj4string, " ' +
' | zoom: ' + map.getZoom() + ' '")
}
txt_basic <- paste0("
' lon: ' + (e.latlng.lng).toFixed(5) +
' | lat: ' + (e.latlng.lat).toFixed(5) +
' | zoom: ' + map.getZoom() + ' '")
txt <- switch(style,
detailed = txt_detailed,
basic = txt_basic)
map <- htmlwidgets::onRender(
map,
paste0(
"
function(el, x, data) {
// get the leaflet map
var map = this; //HTMLWidgets.find('#' + el.id);
// we need a new div element because we have to handle
// the mouseover output separately
// debugger;
function addElement () {
// generate new div Element
var newDiv = $(document.createElement('div'));
// append at end of leaflet htmlwidget container
$(el).append(newDiv);
//provide ID and style
newDiv.addClass('lnlt');
newDiv.css({
'position': 'relative',
'bottomleft': '0px',
'background-color': 'rgba(255, 255, 255, 0.7)',
'box-shadow': '0 0 2px #bbb',
'background-clip': 'padding-box',
'margin': '0',
'padding-left': '5px',
'color': '#333',
'font': '9px/1.5 \"Helvetica Neue\", Arial, Helvetica, sans-serif',
});
return newDiv;
}
// check for already existing lnlt class to not duplicate
var lnlt = $(el).find('.lnlt');
if(!lnlt.length) {
lnlt = addElement();
// grab the special div we generated in the beginning
// and put the mousmove output there
map.on('mousemove', function (e) {
if (document.querySelector('.lnlt') === null) lnlt = addElement();
lnlt.text(", txt, ");
});
// remove the lnlt div when mouse leaves map
map.on('mouseout', function (e) {
var strip = document.querySelector('.lnlt');
strip.remove();
});
};
$(el).keypress(67, function(e) {
if(e.ctrlKey) {
var txt = document.querySelector('.lnlt').textContent;
console.log(txt);
//txt.innerText.focus();
//txt.select();
setClipboardText(`'${txt}'`);
}
});
//map.on('click', function (e) {
// var txt = document.querySelector('.lnlt').textContent;
// console.log(txt);
// //txt.innerText.focus();
// //txt.select();
// setClipboardText(txt);
//});
function setClipboardText(text){
var id = 'mycustom-clipboard-textarea-hidden-id';
var existsTextarea = document.getElementById(id);
if(!existsTextarea){
console.log('Creating textarea');
var textarea = document.createElement('textarea');
textarea.id = id;
// Place in top-left corner of screen regardless of scroll position.
textarea.style.position = 'fixed';
textarea.style.top = 0;
textarea.style.left = 0;
// Ensure it has a small width and height. Setting to 1px / 1em
// doesn't work as this gives a negative w/h on some browsers.
textarea.style.width = '1px';
textarea.style.height = '1px';
// We don't need padding, reducing the size if it does flash render.
textarea.style.padding = 0;
// Clean up any borders.
textarea.style.border = 'none';
textarea.style.outline = 'none';
textarea.style.boxShadow = 'none';
// Avoid flash of white box if rendered for any reason.
textarea.style.background = 'transparent';
document.querySelector('body').appendChild(textarea);
console.log('The textarea now exists :)');
existsTextarea = document.getElementById(id);
}else{
console.log('The textarea already exists :3')
}
existsTextarea.value = text;
existsTextarea.select();
try {
var status = document.execCommand('copy');
if(!status){
console.error('Cannot copy text');
}else{
console.log('The text is now on the clipboard');
}
} catch (err) {
console.log('Unable to copy.');
}
}
}
"
)
)
map
}
library(leaflet)
leaflet() %>%
addTiles() %>%
addMouseCoordinates2()
Currently, lets you click on the map and copy the mouse coodinates content with Ctrl+c I'm thinking it would be better to define a Ctrl+click for this, but so far so good... :-)
@tim-salabim, sorry not on a computer with R at the moment. I don't remember any problems with querySelector in Rstudio Viewer, but perhaps try getElementsByClassName (MDN). Let me know if that does not work and I will try to work through on Sunday.
One other thought is that asynchrony can cause a similar issue when the htmlwidget has not been initialized yet, but since this is with onRender I don't think there should be a problem.
I got it working!! Turns out RStudio viewer didn't like the backticks in `'${txt}'`
The function below now works fine in both the browser and RStudio viewer. It has new features compared to the current version of addMouseCoordinates
- the info strip is only shown when the mouse is actually over the window, i.e. when it leaves the window it is removed
- by default, only the basic text (i.e. lon, lat and zoom) is shown, when pressing ctrl, the complete text will be shown (incl. web mercator x and y, epsg code and proj4string)
- the user can copy the contents of the strip to the clipboard with ctrl + click for the point of click (so that it can be pasted somewhere for further use)
addMouseCoordinates2 <- function(map, style = c("detailed", "basic"),
epsg = NULL, proj4string = NULL,
native.crs = FALSE) {
style <- style[1]
if (inherits(map, "mapview")) map <- mapview2leaflet(map)
stopifnot(inherits(map, "leaflet"))
if (style == "detailed" && !native.crs) {
txt_detailed <- paste0("
' lon: ' + (e.latlng.lng).toFixed(5) +
' | lat: ' + (e.latlng.lat).toFixed(5) +
' | zoom: ' + map.getZoom() +
' | x: ' + L.CRS.EPSG3857.project(e.latlng).x.toFixed(0) +
' | y: ' + L.CRS.EPSG3857.project(e.latlng).y.toFixed(0) +
' | epsg: 3857 ' +
' | proj4: +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs '")
} else {
txt_detailed <- paste0("
' x: ' + (e.latlng.lng).toFixed(5) +
' | y: ' + (e.latlng.lat).toFixed(5) +
' | epsg: ", epsg, " ' +
' | proj4: ", proj4string, " ' +
' | zoom: ' + map.getZoom() + ' '")
}
txt_basic <- paste0("
' lon: ' + (e.latlng.lng).toFixed(5) +
' | lat: ' + (e.latlng.lat).toFixed(5) +
' | zoom: ' + map.getZoom() + ' '")
txt <- switch(style,
detailed = txt_detailed,
basic = txt_basic)
map <- htmlwidgets::onRender(
map,
paste0(
"
function(el, x, data) {
// get the leaflet map
var map = this; //HTMLWidgets.find('#' + el.id);
// we need a new div element because we have to handle
// the mouseover output separately
// debugger;
function addElement () {
// generate new div Element
var newDiv = $(document.createElement('div'));
// append at end of leaflet htmlwidget container
$(el).append(newDiv);
//provide ID and style
newDiv.addClass('lnlt');
newDiv.css({
'position': 'relative',
'bottomleft': '0px',
'background-color': 'rgba(255, 255, 255, 0.7)',
'box-shadow': '0 0 2px #bbb',
'background-clip': 'padding-box',
'margin': '0',
'padding-left': '5px',
'color': '#333',
'font': '9px/1.5 \"Helvetica Neue\", Arial, Helvetica, sans-serif',
'z-index': '700',
});
return newDiv;
}
// check for already existing lnlt class to not duplicate
var lnlt = $(el).find('.lnlt');
if(!lnlt.length) {
lnlt = addElement();
// grab the special div we generated in the beginning
// and put the mousmove output there
map.on('mousemove', function (e) {
if (e.originalEvent.ctrlKey) {
if (document.querySelector('.lnlt') === null) lnlt = addElement();
lnlt.text(", txt_detailed, ");
} else {
if (document.querySelector('.lnlt') === null) lnlt = addElement();
lnlt.text(", txt_basic, ");
}
});
// remove the lnlt div when mouse leaves map
map.on('mouseout', function (e) {
var strip = document.querySelector('.lnlt');
strip.remove();
});
};
//$(el).keypress(67, function(e) {
map.on('preclick', function(e) {
if (e.originalEvent.ctrlKey) {
if (document.querySelector('.lnlt') === null) lnlt = addElement();
lnlt.text(", txt_basic, ");
var txt = document.querySelector('.lnlt').textContent;
console.log(txt);
//txt.innerText.focus();
//txt.select();
setClipboardText('\"' + txt + '\"');
}
});
//map.on('click', function (e) {
// var txt = document.querySelector('.lnlt').textContent;
// console.log(txt);
// //txt.innerText.focus();
// //txt.select();
// setClipboardText(txt);
//});
function setClipboardText(text){
var id = 'mycustom-clipboard-textarea-hidden-id';
var existsTextarea = document.getElementById(id);
if(!existsTextarea){
console.log('Creating textarea');
var textarea = document.createElement('textarea');
textarea.id = id;
// Place in top-left corner of screen regardless of scroll position.
textarea.style.position = 'fixed';
textarea.style.top = 0;
textarea.style.left = 0;
// Ensure it has a small width and height. Setting to 1px / 1em
// doesn't work as this gives a negative w/h on some browsers.
textarea.style.width = '1px';
textarea.style.height = '1px';
// We don't need padding, reducing the size if it does flash render.
textarea.style.padding = 0;
// Clean up any borders.
textarea.style.border = 'none';
textarea.style.outline = 'none';
textarea.style.boxShadow = 'none';
// Avoid flash of white box if rendered for any reason.
textarea.style.background = 'transparent';
document.querySelector('body').appendChild(textarea);
console.log('The textarea now exists :)');
existsTextarea = document.getElementById(id);
}else{
console.log('The textarea already exists :3')
}
existsTextarea.value = text;
existsTextarea.select();
try {
var status = document.execCommand('copy');
if(!status){
console.error('Cannot copy text');
}else{
console.log('The text is now on the clipboard');
}
} catch (err) {
console.log('Unable to copy.');
}
}
}
"
)
)
map
}
library(leaflet)
leaflet() %>%
addTiles() %>%
addPolygons(data = gadmCHE, popup = mapview::popupTable(gadmCHE)) %>%
addMouseCoordinates2()
# m = leaflet() %>%
# addTiles() %>%
# addMouseCoordinates2()
#
# tempDir <- tempfile()
# dir.create(tempDir)
# htmlFile <- file.path(tempDir, "index.html")
#
# mapview::mapshot(m, htmlFile)
# viewer <- getOption("viewer")
# viewer(htmlFile)
I think I should rewrite this to be a proper js lib binding rather than this hideous paste monster...
@timelyportfolio do you know why RStudio viewer doesn't like the backticks in '${txt}'?
@tim-salabim template literals were not introduced until ES2015 which Windows RStudio Viewer does not support. However, in the daily builds, they have a much more modern viewer. Knowing how long the corporate and government world takes to upgrade, I generally try to avoid adding modern JavaScript features in the production js. One option would be to use reactR::babel_transform to create a transpiled production js while you still work in new JavaScript. Or if you are comfortable with a JS build toolchain with node, you could transpile that way.
@timelyportfolio thanks for the detailed explanation. I am still only scratching the surface of js so it is hard for me to know which is new and which is traditional js. I already feel like a Jedi when I figure out to get stuff debugged properly in the browser. So rstudio viewer just adds another layer of confusion in many cases for me. Therefore, I am very greatful for all the time you take to explain things for me and guide me in the right direction, e.g. the tip about the rstudio dailies. The js learning curve is definitely steeper than the R learning curve...