I'm trying to implement the plugin extension , groupedLayerControl, for leaflet maps. You can see below the code I'm working with (currently not working). I'm trying to put the overlayers in different group categories. I'm new to javascript so I really can't identify what I'm doing wrong. The code does not give me the groups' layers in the map. Can someone help me out?
registerPlugin <- function(map, plugin) {
map$dependencies <- c(map$dependencies, list(plugin))
map
}
Value1 = c(12,43,54,34,23,77,44,22)
Value2 = c(6,5,2,7,5,6,4,3)
Lat = c(51.1, 51.6, 57.3, 52.4, 56.3, 54.3, 60.4, 49.2)
Lon = c(5, -3, -2, -1, 4, 3, -5, 0)
leaflet() %>%
addProviderTiles(providers$CartoDB.Positron, group = "Alpha") %>%
registerPlugin(groupedLayerControlPlugin) %>%
onRender("function(el, x) {
var baseLayers = {
'Alpha': this.layerManager.getLayerGroup('Alpha'),
};
var groupedOverlays = {
'Group 1': {
'Value1': this.layerManager.getLayerGroup('Value1'),
},
'Group 2': {
'Value2': this.layerManager.getLayerGroup('Value2'),
}
};
var Options = {
groupCheckboxes: true
};
console.log(L.control.groupedLayers);
L.control.groupedLayers(baseLayers, groupedOverlays, Options).addTo(this);
}") %>%
addCircles(lat=Lat, lng=Lon,
radius = Value1*1000, group = "Value1") %>%
addCircles(lat=Lat, lng=Lon,
radius = Value2*10000, group = "Value1")
This is an old question , but thought I'd add a complete working example using your data. You only had one issue in the provided example, which was that you needed at least one group = "Value2" argument for one of the leaflet::addCircles(). Both of the addCircles() in your example show group = "Value1").
In the below code, I've also added an additional base layer to demonstrate base layer grouping in addition to the groupedlayercontrol of overlays.
library(dplyr)
library(leaflet)
library(htmlwidgets)
Value1 = c(12,43,54,34,23,77,44,22)
Value2 = c(6,5,2,7,5,6,4,3)
Lat = c(51.1, 51.6, 57.3, 52.4, 56.3, 54.3, 60.4, 49.2)
Lon = c(5, -3, -2, -1, 4, 3, -5, 0)
urlf <- 'https://raw.githubusercontent.com/ismyrnow/leaflet-groupedlayercontrol/gh-pages/dist/%s'
download.file(sprintf(urlf,'leaflet.groupedlayercontrol.min.js'), 'C:/Temp/L.Control.groupedlayer.js', mode="wb")
download.file(sprintf(urlf,'leaflet.groupedlayercontrol.min.css'), 'C:/Temp/L.Control.groupedlayer.css', mode="wb")
groupedLayerControlPlugin <- htmltools::htmlDependency(
name = 'ctrlGrouped',
version = "1.0.0",
src = c(file = normalizePath('C:/Temp')),
script = "L.Control.groupedlayer.js",
stylesheet = "L.Control.groupedlayer.css"
)
registerPlugin <- function(map, plugin) {
map$dependencies <- c(map$dependencies, list(plugin))
map
}
leaflet() %>%
addProviderTiles("Stamen.Watercolor", group = "Beta") %>%
addProviderTiles(providers$CartoDB.Positron, group = "Alpha") %>%
registerPlugin(groupedLayerControlPlugin) %>%
onRender("function(el, x) {
var groups = {
Value1: this.layerManager.getLayerGroup('Value1'),
Value2: this.layerManager.getLayerGroup('Value2')
};
var baseLayers = {
'Alpha': this.layerManager.getLayerGroup('Alpha'),
'Beta': this.layerManager.getLayerGroup('Beta')
};
var groupedOverlays = {
'all value groups': {
'Value 1' : groups.Value1,
'Value 2' : groups.Value2}
};
var Options = {
groupCheckboxes: true
};
console.log(L.control.groupedLayers);
L.control.groupedLayers(baseLayers, groupedOverlays, Options).addTo(this);
}") %>% addCircles(lat=Lat, lng=Lon,
radius = Value1*1000, group = "Value1") %>%
addCircles(lat=Lat, lng=Lon,
radius = Value2*10000, group = "Value2")
Related
intro
I am pretty agile in R, but my java skills are non-existent. Therefor I'm throwing myself at your mercy to answer this, hopefully not too complex, question (or I will have a hard time figuring out the answers ;-)).
Running the code below requires you to download three leaflet plugins from github (links in the comments inside the code). They should be placed in a folder ./script, relative to where you are running the code.
sample data
I have excel-sheets with multiple routes. For sake of simplicity, I already read in a file using the following code, so I do not have to share the excel-file online:
# read excel file
bestand <- "./data/CBM_Schuttorf_Buren.xlsx"
bladen <- readxl::excel_sheets(bestand)
xldata <- lapply(bladen, function(x) {
readxl::read_excel(path = bestand, sheet = x,
col_types = c(rep(c("numeric", "text"), 2), rep("numeric", 2)))
})
names(xldata) <- bladen
This results in the following object, which you will need to continue the code with
bladen <- c("A1L", "A1R")
xldata <- list(A1L = structure(list(route = c(1, 1, 2, 2, 2, 3, 3, 3),
routeType = c("stremming", "stremming", "omleiding", "omleiding",
"omleiding", "omleiding", "omleiding", "omleiding"), punt = c(1,
2, 1, 2, 3, 1, 2, 3), puntType = c("start", "eind", "start",
"via", "eind", "start", "via", "eind"), lat = c(52.341823,
52.284989, 52.340234, 52.193045, 52.302415, 52.349596, 52.193045,
52.302415), lon = c(7.254037, 6.74575, 7.271095, 7.102321,
6.715246, 7.258845, 7.102321, 6.715246)), class = c("tbl_df",
"tbl", "data.frame"), row.names = c(NA, -8L)), A1R = structure(list(
route = c(1, 1, 2, 2, 2, 3, 3, 3), routeType = c("stremming",
"stremming", "omleiding", "omleiding", "omleiding", "omleiding",
"omleiding", "omleiding"), punt = c(1, 2, 1, 2, 3, 1, 2,
3), puntType = c("start", "eind", "start", "via", "eind",
"start", "via", "eind"), lat = c(52.284267, 52.341886, 52.303024,
52.19279, 52.354846, 52.303024, 52.19279, 52.339145), lon = c(6.754951,
7.251379, 6.713831, 7.104181, 7.258402, 6.713831, 7.104181,
7.285606)), class = c("tbl_df", "tbl", "data.frame"), row.names = c(NA,
-8L)))
my problem
The sample data is a simplified issue. There are only two list entries, A1L and A1R. In my production data there will be many more entries.
What i want as a result, is a dynamic version of the code below. Here I hardcoded the Layergroups A1L and A1R everywhere. ButWhile this works as a proof-of-concept, this is not workable in production.
As mentioned before, I need the functionality of several leaflet pluging, so I relied heavily on the htmlwidgets::onRender()- funciton to get down what I need. This is also my Achilles-heel, since I am a complete n00b in javascript.
desired output
I am open for all suggestions that can replicate the results of the code below, without hardcoding the filtering/Layergroups..
note: the arrows on the end of the polyline only show when the leaflet is shown in the browser. They do not show inside the rstudio viewer (took me some frustration to find that one out ;-) )
click here for my output
my code
library(tidyverse)
library(readxl)
library(osrm)
library(leaflet)
library(geojsonsf)
# below commented out, xldata is already provided
# # read excel file
# bestand <- "./data/myfile.xlsx"
# bladen <- readxl::excel_sheets(bestand)
# xldata <- lapply(bladen, function(x) {
# readxl::read_excel(path = bestand, sheet = x,
# col_types = c(rep(c("numeric", "text"), 2), rep("numeric", 2)))
# })
# names(xldata) <- bladen
# split individual routes (will become polylines later on)
routes <- lapply(xldata, function(x) split(x, f = x$route))
# create real routes, using osm routing
df <-
dplyr::bind_rows(
lapply(seq.int(routes), function(i) {
dplyr::bind_rows(
lapply(seq.int(lengths(routes)[i]), function(j) {
temp <- osrmRoute(loc = as.data.frame(routes[[i]][[j]][, c("lon", "lat")]),
overview = "full", returnclass = "sf") %>%
mutate(naam = paste0(bladen[i], "_", routes[[i]][[j]][1,2], routes[[i]][[j]][1,1])) %>%
mutate(groep = bladen[i]) %>%
mutate(groepVol = paste0("groups.",bladen[i])) %>%
mutate(type = ifelse(grepl("stremming", naam), "stremming", "omleiding"))
}))
})
)
df
# get boundaries for map
grens <- sf::st_bbox(df) %>% as.vector()
# create named list of geojson routes
plotdata <- lapply(split(df, f = df$naam), sf_geojson)
# PLUGIN SECTION
# from: https://github.com/slutske22/leaflet-arrowheads
arrowHead <- htmlDependency(
"leaflet-arrowheads",
"0.1.2",
src = normalizePath(".\\script"),
#src = "./script",
script = "leaflet-arrowheads.js"
)
# from https://github.com/makinacorpus/Leaflet.GeometryUtil
geometryutil <- htmlDependency(
"leaflet.geometryutil",
"0.1.2",
src = normalizePath(".\\script"),
#src = "./script",
script = "leaflet.geometryutil.js"
)
registerPlugin <- function(map, plugin) {
map$dependencies <- c(map$dependencies, list(plugin))
map
}
# plot the map and layers
leaflet() %>%
#register plugins
registerPlugin(arrowHead) %>%
registerPlugin(geometryutil) %>%
# add basemap
addProviderTiles(providers$CartoDB.Positron) %>%
# set map boundaries
fitBounds( grens[1], grens[2], grens[3], grens[4]) %>%
onRender("function(el, x, data) {
// funciton to define line color based on
// feature.properties.type
function getColor(d) {
return d == 'stremming' ? 'red' :
d == 'omleiding' ? 'seagreen' :
'black';
}
// funciton to define line dash based on
// feature.properties.type
function getDash(d) {
return d == 'stremming' ? '20' :
d == 'omleiding' ? '' :
'';
}
// function to set style of polylines
function newstyle(feature) {
return {
color: getColor(feature.properties.type),
weight: 10,
opacity: 1,
dashArray: getDash(feature.properties.type),
fillOpacity: 0.7
};
}
///////////////////////////////////////
//would like to make the code below this dynamic
//based on the groep-property in the JSON object
//so A1L and A1R groups (and thereby the filtering)
//are read in directly from the data object df
///////////////////////////////////////
// filtering
function A1L(feature) {if (feature.properties.groep === 'A1L') return true}
function A1R(feature) {if (feature.properties.groep === 'A1R') return true}
// crteation of layergroups
var groups = {
A1L: new L.LayerGroup(),
A1R: new L.LayerGroup()
};
// create layers and add to groups
var A1L = L.geoJSON(data, {
filter: A1L,
style: newstyle,
arrowheads: {frequency: 'endonly', yawn: 45, size: '30px', fill: true}
})
.on('mouseover', function (e) {e.target.setStyle({weight: 15, opacity: 1 });})
.on('mouseout', function (e) {e.target.setStyle({weight: 10, opacity: 0.75});})
.addTo(groups.A1L);
var A1R = L.geoJSON(data, {
filter: A1R,
style: newstyle,
arrowheads: {frequency: 'endonly', yawn: 45, size: '30px', fill: true}
})
.on('mouseover', function (e) {e.target.setStyle({weight: 15, opacity: 1 });})
.on('mouseout', function (e) {e.target.setStyle({weight: 10, opacity: 0.75});})
.addTo(groups.A1R);
var baseLayers = {
'A1L': A1L,
'A1R': A1R
};
var layerControl = L.control.layers(baseLayers, null, {collapsed: false}).addTo(this);
baseLayers['A1L'].addTo(this);
}", data = sf_geojson(df))
what I have tried so far
I found something that might be the solution here, but I lack the java skills to:
see if this is indeed the way to go, and if so:
how can this be implementd inside my code.
After >1 day of fruitless effort, I finally found an answer I could work with.
It is now working as desired, here is my answer for anyone looking for this possibility (and I found some in the past 24 h ;-) )
# plot the map and layers
leaflet() %>%
#register plugins
registerPlugin(arrowHead) %>%
registerPlugin(geometryutil) %>%
registerPlugin(groupedlayercontrol) %>%
# add basemap
addProviderTiles(providers$CartoDB.Positron) %>%
# set map boundaries
fitBounds( grens[1], grens[2], grens[3], grens[4]) %>%
onRender("function(el, x, data) {
// read data from the named list passd to onRender
// data.name_from_list
var routes = data.routes;
var groups = data.groups;
var types = groups;
// function to define line color based on
// feature.properties.type
function getColor(d) {
return d == 'stremming' ? 'red' :
d == 'omleiding' ? 'seagreen' :
'black';
}
// funciton to define line dash based on
// feature.properties.type
function getDash(d) {
return d == 'stremming' ? '20' :
d == 'omleiding' ? '' :
'';
}
// function to set style of polylines
function newstyle(feature) {
return {
color: getColor(feature.properties.type),
weight: 10,
opacity: 1,
dashArray: getDash(feature.properties.type),
fillOpacity: 0.7
};
}
// layerControl optioesn for groupedOverlays
var options = {
exclusiveGroups: ['Stremming'],
groupCheckboxes: false,
collapsed: false
};
// add empty layercontrol
var layerControl = L.control.groupedLayers(null, null, options).addTo(this);
// iterate over types, filter by that type, and format the layer for that feature type
types.forEach(function(type) {
var layer = L.geoJson(routes, {
filter: function(feature, layer) {
return feature.properties.groep == type;
},
style: newstyle,
arrowheads: {frequency: 'endonly', yawn: 45, size: '30px', fill: true}
})
.on('mouseover', function (e) {e.target.setStyle({weight: 15, opacity: 1 });})
.on('mouseout', function (e) {e.target.setStyle({weight: 10, opacity: 0.75});})
// all done with the layer, add it to the control
layerControl.addOverlay(layer, type, 'Stremming');
});
}", data = list(routes = sf_geojson(df), groups = bladen))
I am trying to create a time line plot using leaflet and leaftime packages. I want to set custom color in addTimeline to specify each point to his groups, as follows:
library(leaflet)
library(leaftime)
library(geojsonio)
power_d <- data.frame(
"Latitude" = c(
33.515556, 38.060556, 47.903056, 49.71, 49.041667, 31.934167,
54.140586, 54.140586, 48.494444, 48.494444
),
"Longitude" = c(
129.837222, -77.789444, 7.563056, 8.415278, 9.175, -82.343889,
13.664422, 13.664422, 17.681944, 17.681944
),
"start" = seq.Date(as.Date("2015-01-01"), by = "day", length.out = 10),
"end" = seq.Date(as.Date("2015-01-01"), by = "day", length.out = 10) + 1,
color_temp=rep(c("red","blue","green"),len=10)
)
power_geo <- geojsonio::geojson_json(power_d ,lat="Latitude",lon="Longitude")
leaflet() %>%
addTiles() %>%
setView(44.0665,23.74667,2) %>%
addTimeline(data = power_geo,
timelineOpts = timelineOptions(
styleOptions = styleOptions(
radius = 5,
color=color_temp,
fillColor = color_temp,
fillOpacity = 1
)
)
)
Unfortunately I got following error:
Error in lapply(x, f) : object 'color_temp' not found
I also try replacing color_temp with power_d$color_temp, the code run without error, but the color of points do not change. The color arguments not work in above code, why? and how to fix it?
It doesn't seem as if you can pass a vector of colours with the standard styleOptions, however, an example from the help page for ?addTimeline show how you can add colours based on the data using a little JavaScript (which thankfully is provided in the example).
Using the example that starts "# to style each point differently based on the data" you need to change it slightly to point to your colour vector e.g. change data.properties.color to data.properties.color_temp. Running the code below leads to
# code
leaflet(power_geo) %>%
addTiles() %>%
setView(44.0665,23.74667,2) %>%
addTimeline(
timelineOpts = timelineOptions(
styleOptions = NULL,
pointToLayer = htmlwidgets::JS(
"
function(data, latlng) {
return L.circleMarker(
latlng,
{
radius: 25,
color: data.properties.color_temp,
fillColor: data.properties.color_temp,
fillOpacity: 1
}
);
}
"
)
)
)
I'm developing an R shiny app and ideally I would need to do precisely what is done here:
More specifically, I have dataframe with stocks open, close, high, low data and I would need to replicate what's displayed in the attached image in column "Range".
I understand I should attach some code, but the truth here, I can't find anything close to what I'm asking online.
A sample dataframe would be:
df = data.frame(STOCK=c("IBM","MSFT","FB"), OPEN=c(100,90, 80), CLOSE=c(102, 85, 82), LOW=c(99,81,78), HIGH=c(105, 91, 88))
Then, I have no idea of what to do from here. Any suggestions? Thanks
You can use custom-rendering follow this guide
https://glin.github.io/reactable/articles/examples.html#custom-rendering-1
library(dplyr)
library(sparkline)
data <- chickwts %>%
group_by(feed) %>%
summarise(weight = list(weight)) %>%
mutate(boxplot = NA, sparkline = NA)
reactable(data, columns = list(
weight = colDef(cell = function(values) {
sparkline(values, type = "bar", chartRangeMin = 0, chartRangeMax = max(chickwts$weight))
}),
boxplot = colDef(cell = function(value, index) {
sparkline(data$weight[[index]], type = "box")
}),
sparkline = colDef(cell = function(value, index) {
sparkline(data$weight[[index]])
})
))
I make a leaflet map with a responsive heatmap using addHeatmap. Unfortunately, this kind of tool it is not enough useful because two main problems: 1) The heatmap is redrawed with each new level of zoom and 2) you can not make the heatmap and the points in a separated group each one.
It is possible a similar solution with addWebGLHeatmap?
There is the code for the addHeatmap solution, following this question
library(crosstalk)
library(leaflet)
library(leaflet.extras)
library(dplyr)
# Wrap data frame in SharedData
sd <- SharedData$new(quakes[sample(nrow(quakes), 10),])
bscols(widths=c(3,9),
# Create a filter input
filter_slider("mag", "Magnitude", sd, column=~mag, step=0.1),
leaflet(sd) %>%
addTiles() %>%
addMarkers() %>%
addHeatmap(layerId="heatmap") %>%
removeHeatmap("heatmap") %>%
htmlwidgets::onRender("
function(el,x){
var myMap = this;
var coord_state;
var coords;
function get_markers(){
coord_state = [];
myMap.eachLayer(function(layer){
if (typeof layer.options.lat != 'undefined'){
coord_state.push([layer.options.lat, layer.options.lng, 0.5]);
}
})
return(coord_state)
}
function update_layer(){
coords = get_markers()
heat1.setLatLngs(coords);
heat1.redraw();
}
var heat1 = L.heatLayer(get_markers(), {radius: 25}).addTo(myMap);
myMap.on('layerremove', update_layer);
myMap.on('layeradd', update_layer);
}
"))
This method is kind of a hack, but still should be able to work with addWebGLHeatmap. It adds two sets of identical markers and hides one which controls the heatmap. This allows for the layer control. A working example can be found here:
https://rpubs.com/Jumble/leaflet_webgl_heatmap
Below is the code that produced this. This code solves the main two problems although it struggles if you wan't to plot over 1000 points.
Rather than using crosstalk it might be better to use a combination of something like leafgl, shiny and addWebGLHeatmap if you are wanting to plot thousands of points.
n <- 200
data <- data.frame(id = seq(1, n*2), lat = rnorm(n, 0, 15), long = rnorm(n, 0, 15), group=c(rep("Heatmap",n), rep("Markers", n)), mag=rep(as.integer(runif(n, 0, 20)),2))
sd <- SharedData$new(data)
bscols(widths=c(3,9),
filter_slider("mag", "Magnitude", sd, column=~mag, step=0.1),
leaflet(sd, options=leafletOptions(preferCanvas = TRUE)) %>%
addTiles() %>%
leaflet::setView(lat=0, lng=0, zoom=4) %>%
addMarkers(group=~group) %>%
leaflet.extras::addWebGLHeatmap(layerId = "heatmapwebgl") %>%
leaflet.extras::removeWebGLHeatmap("heatmapwebgl") %>%
addLayersControl(overlayGroups = c("Heatmap", "Markers"), options=layersControlOptions(collapsed=FALSE)) %>%
htmlwidgets::onRender("
function(el,x){
var myMap = this;
var coord_state;
// hide heatmap markers
setTimeout(function(){
myMap.eachLayer(function(layer){
if (layer.options.group=='Heatmap'){
layer.setOpacity(0);
layer.getElement().style.pointerEvents = 'none';
}
})
}, 100)
function get_markers(){
coord_state = [];
myMap.eachLayer(function(layer){
if (layer.options.group=='Heatmap'){
coord_state.push([layer.options.lat, layer.options.lng, 0.5]);
layer.getElement().style.pointerEvents = 'none';
}
})
return(coord_state)
}
function redraw_heatmap(){
heatmap.setData(get_markers());
}
var heatmap = L.webGLHeatmap({size: 1000000,units: 'm',alphaRange: 1});
heatmap.setData(get_markers());
myMap.addLayer(heatmap);
myMap.on('layerremove', redraw_heatmap);
myMap.on('layeradd', redraw_heatmap);
}
"))
Below for Circle Markers
n <- 200
data <- data.frame(id = seq(1, n*2), lat = rnorm(n, 0, 15), long = rnorm(n, 0, 15), group=c(rep("Heatmap",n), rep("Markers", n)), mag=rep(as.integer(runif(n, 0, 20)),2))
sd <- SharedData$new(data)
bscols(widths=c(3,9),
filter_slider("mag", "Magnitude", sd, column=~mag, step=0.1),
leaflet(sd) %>%
addTiles() %>%
leaflet::setView(lat=0, lng=0, zoom=4) %>%
addCircleMarkers(group=~group, opacity=~ifelse(group=="Heatmap", 0, 0.5), fillOpacity=~ifelse(group=="Heatmap", 0, 0.2)) %>%
leaflet.extras::addWebGLHeatmap(layerId = "heatmapwebgl") %>%
leaflet.extras::removeWebGLHeatmap("heatmapwebgl") %>%
addLayersControl(overlayGroups = c("Heatmap", "Markers"), options=layersControlOptions(collapsed=FALSE)) %>%
htmlwidgets::onRender("
function(el,x){
var myMap = this;
var coord_state;
function get_markers(){
coord_state = [];
myMap.eachLayer(function(layer){
if (layer.options.group=='Heatmap'){
coord_state.push([layer.options.lat, layer.options.lng, 0.5]);
layer.getElement().style.pointerEvents = 'none';
}
})
return(coord_state)
}
function redraw_heatmap(){
heatmap.setData(get_markers());
}
var heatmap = L.webGLHeatmap({size: 1000000,units: 'm',alphaRange: 1});
heatmap.setData(get_markers());
myMap.addLayer(heatmap);
myMap.on('layerremove', redraw_heatmap);
myMap.on('layeradd', redraw_heatmap);
}
"))
I found this image from the Internet (link) and I think it was draw in R. I tried to reproduce this Figure and make it more or less similar with one from the above link. The code I used is as following:
ID <- 0:24
NodeGroup <- c(rep(1,8),2,5,rep(4,7),3,5,6,6,7,7,8,9)
name <- c("29581k","5279k","4218k","1917k","1356k","Ventas diversas: 116","Venta diversa: 97","Venta de: 141","Venta totales: 42705","Contribucion marginal: 18183", "17531k","1744k","1326k","1208k","526k","459k","14k","IIBB: 1714","Costo: 22808","Gastos directos: 6734","Gastos distribudos: 2958","Resultado: 8851","Total Gastos: 9332","Imp. Gcias: 3098","Resultado Netto: 5.753")
nodes <- data.frame(ID, name, NodeGroup)
nodes$NodeGroup <- as.character(nodes$NodeGroup)
source <- c(0:7, rep(8,9), 10:16, rep(9,3), 19, 20, 21, 21)
target <- c(rep(8,8), 17, 10:16, 9, rep(18,7), 19:21, rep(22, 2), 23, 24)
value <- c(29581,5279,4218,1917,1356,116,97,141,1714,17531,1744,1326,1208,526,459,14,18138,17531,1744,1326,1208,526,459,14,6374,2958,8851,6374,2958,3098,5753)
group <- c(1:8, rep(9,8), 10, rep(19,7), rep(18,3), rep(23,2), rep(22,2))
links <- data.frame(source, target, value, group)
links$group <- as.character(links$group)
sn <- sankeyNetwork(Links=links, Nodes=nodes, Source='source', Target='target',
Value='value', NodeID='name', fontSize=18,
NodeGroup = "NodeGroup",
sinksRight = FALSE,
LinkGroup = "group",
#nodeWidth = 40,
#width=1500, height=500,
#margin = list("right"=250),
iterations = FALSE)
sn
From this links it is possible to change the position of a node not only vertically, but also horizontally. Can we implement it in R?
Update 1: I can solve issue in question 2 by changing the source code of sankeyNetwork.js by using the code provide at this links. I do not know how to implement it through htmlwidgets (I am not familiar with JS; hence, just do trial and error!). I just need to copy the following code to the end of sankeyNetwork.js.
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + (
d.x = Math.max(0, Math.min(width - d.dx, d3.event.x))
) + "," + (
d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
) + ")");
sankey.relayout();
link.attr("d", path);
}
To enable horizontal movement of the nodes, along with the vertical movement, you could adapt d3noob's code to work, but it's not as easy as dropping in just their dragmove function declaration.
It was written using D3v3, and networkD3 uses D3v4... and they're not entirely compatible.
That function refers to a bunch of objects that are defined elsewhere, so the function on its own cannot work without knowing what these are: width, height, sankey, link, and path.
Here is one way of adapting it to work...
library(networkD3)
library(htmlwidgets)
ID <- 0:24
NodeGroup <- c(rep(1,8),2,5,rep(4,7),3,5,6,6,7,7,8,9)
name <- c("29581k","5279k","4218k","1917k","1356k","Ventas diversas: 116",
"Venta diversa: 97","Venta de: 141","Venta totales: 42705",
"Contribucion marginal: 18183", "17531k","1744k","1326k","1208k",
"526k","459k","14k","IIBB: 1714","Costo: 22808",
"Gastos directos: 6734", "Gastos distribudos: 2958","Resultado: 8851",
"Total Gastos: 9332","Imp. Gcias: 3098","Resultado Netto: 5.753")
nodes <- data.frame(ID, name, NodeGroup)
nodes$NodeGroup <- as.character(nodes$NodeGroup)
source <- c(0:7, rep(8,9), 10:16, rep(9,3), 19, 20, 21, 21)
target <- c(rep(8,8), 17, 10:16, 9, rep(18,7), 19:21, rep(22, 2), 23, 24)
value <- c(29581,5279,4218,1917,1356,116,97,141,1714,17531,1744,1326,1208,526,
459,14,18138,17531,1744,1326,1208,526,459,14,6374,2958,8851,6374,
2958,3098,5753)
group <- c(1:8, rep(9,8), 10, rep(19,7), rep(18,3), rep(23,2), rep(22,2))
links <- data.frame(source, target, value, group)
links$group <- as.character(links$group)
sn <- sankeyNetwork(Links=links, Nodes=nodes, Source='source', Target='target',
Value='value', NodeID='name', fontSize=18,
NodeGroup = "NodeGroup",
sinksRight = FALSE,
LinkGroup = "group",
#nodeWidth = 40,
#width=1500, height=500,
#margin = list("right"=250),
iterations = FALSE)
onRender(sn,
'
function(el, x) {
var sankey = this.sankey;
var path = sankey.link();
var nodes = d3.selectAll(".node");
var link = d3.selectAll(".link")
var width = el.getBoundingClientRect().width - 40;
var height = el.getBoundingClientRect().height - 40;
window.dragmove = function(d) {
d3.select(this).attr("transform",
"translate(" + (
d.x = Math.max(0, Math.min(width - d.dx, d3.event.x))
) + "," + (
d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))
) + ")");
sankey.relayout();
link.attr("d", path);
};
nodes.call(d3.drag()
.subject(function(d) { return d; })
.on("start", function() { this.parentNode.appendChild(this); })
.on("drag", dragmove));
}
'
)