Commit b1f409ac authored by Nicolas Lambert's avatar Nicolas Lambert
Browse files

initial commit

parent 71a94483
source("helpers.R")
ui <- fluidPage(
titlePanel("MigrexploreR 3 - International migrant stock in 2019 (Beta test)"),
useShinyjs(),
sidebarLayout(
sidebarPanel(width = 2,
selectInput("type",
label = "Origin or destination",
choices = c("from","to"),
selected = "from"),
selectInput("iso",
label = "Targeted Country",
choices = isolist,
selected = "FRA"),
sliderInput("threshold",
label = "Threshold",
min = 0, max = 100000, value = 10000),
sliderInput("k",
label = "Flow thickness",
min = 0, max = 1, value = 0.3),
checkboxInput("auto", "Auto Size", TRUE),
helpText('This application is part of a set of tools called "MigrExplorer" that allow to visualize international migrations in various aspects.'),
helpText("Data sources: United Nations, Department of Economic and Social Affairs, Population Division, 2019 - Natural Earth, 2020."),
helpText("By Françoise Bahoken & Nicolas Lambert, 2020."),
tags$a(img(src='gitlab.png', align = "center", height="30px"), href = "https://gitlab.huma-num.fr/nlambert/migrexplorer",target="_blank"),
tags$a(img(src='hypotheses.png', align = "center", height="30px"), href = "https://neocarto.hypotheses.org/9872",target="_blank"),
tags$a(img(src='ccbysanc.png', align = "center", height="30px"), href = "https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode",target="_blank")
),
mainPanel(width = 10,
d3Output("d3", width = 800, height = 850)
)
)
)
server <- function(input, output, session) {
observe({
updateCheckboxInput(session, "all")
if(input$auto == TRUE) {
disable("k")
output$d3 <- renderD3({
json <- getflows(input$iso, input$type, input$threshold, NULL)
r2d3(
data = json,
dependencies = c("js/topojson.min.js","js/layers.js"),
container = "svg",
d3_version = 5,
script = "js/map.js",
css = "css/style.css",
options(list(r2d3.shadow = FALSE, x=30, y=30))
)})
} else {
enable("k")
output$d3 <- renderD3({
json <- getflows(input$iso, input$type, input$threshold, input$k)
r2d3(
data = json,
dependencies = c("js/topojson.min.js","js/layers.js"),
container = "svg",
d3_version = 5,
script = "js/map.js",
css = "css/style.css",
options(list(r2d3.shadow = FALSE, x=30, y=30))
)})
}
})
}
shinyApp(ui = ui, server = server)
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,800,700,300);
@import url(https://fonts.googleapis.com/css?family=Squada+One);
.countries {
fill: #e8c25a ;
stroke-width: 0.4px;
stroke : white
}
.label {
fill: red ;
}
.land {
fill: #e3cc64 ;
stroke-width: 0.4px;
stroke : white
}
.flows {
fill: #ff6200;
fill-opacity: 0.9;
stroke: #8a4d74;
stroke-width:0.2;
}
.sphere {
fill:none ;
stroke: white;
stroke-width: .5px;
stroke-opacity: .5;
stroke-dasharray: 2
}
.label {
font-family: 'Open Sans', sans-serif;
fill: #333130;
stroke : red
text-decoration: none;
text-transform: uppercase;
font-size: 30px;
font-weight: 600;
letter-spacing: -3px;
line-height: 1;
text-anchor: middle;
alignment-baseline: middle;
}
.maptitle2 {
font-family: 'Open Sans', sans-serif;
fill: #90a0a3;
text-decoration: none;
text-transform: uppercase;
font-size: 20px;
font-weight: 400;
letter-spacing: -1px;
line-height: 1;
}
.note {
font-family: 'Open Sans', sans-serif;
fill: #90a0a3;
font-size: 10px;
}
.txthelp {
font-family: 'Open Sans', sans-serif;
fill: white;
font-size: 10x;
}
.stats {
font-family: 'Open Sans', sans-serif;
fill: white;
font-size: 40x;
}
div.tooltip {
width: 120px;
background-color: black;
color: #fff;
text-align: center;
padding: 5px 0;
border-radius: 6px;
}
This diff is collapsed.
library(sf)
library(geojsonsf)
library(flowmapper)
library(shiny)
library(shinyjs)
library(r2d3)
# IMPORT
migr <- read.csv("data/migr2019_T.csv")
ctr <- st_read("data/countries.gpkg")
ctr <- ctr[,c("adm0_a3_is" ,"label","geom")]
colnames(ctr) <- c("id","name","geom")
# List countries
isolist <- as.character(ctr$id)
names(isolist) <- ctr$name
dfctr <- data.frame(isolist,names(isolist))
colnames(dfctr) <- c("id","label")
# VARS
# iso = "FRA"
# type = "to" # or to
# threshold <- 10000
# k = 0.3
View(center)
# PROJ
# crs <- "+proj=aeqd +lat_0=90 +lon_0=0 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs"
# GET FLOWS
getflows <- function(iso, type, threshold, k) {
center <- st_centroid(ctr[ctr$id==iso,])
coords <- st_coordinates(center)
crs <- paste0("+proj=aeqd +lat_0=",coords[2]," +lon_0=",coords[1]," +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs")
countries <- st_transform(ctr,crs)
if (type == "from"){ m <- migr[migr$i == iso,] }
if (type == "to"){ m <- migr[migr$j == iso,]}
m <- m[m$fij >= threshold,]
m$i <- as.character(m$i)
m$j <- as.character(m$j)
flows <- plotflows(
x = countries,
xid = "id",
df = m,
dfid = c("i","j"),
dfvar = "fij",
k = k,
plot = FALSE
)[[3]]
ctr <- ctr[ctr$id == iso, ] %>% st_transform(4326)
ctr <- st_wrap_dateline(ctr, options = "WRAPDATELINE=YES", quiet = TRUE)
flows <- smoothr::densify(flows, n = 50) %>% st_transform(4326)
flows <- st_wrap_dateline(flows, options = "WRAPDATELINE=YES", quiet = TRUE)
flows <- st_buffer(flows,0.01)
if (type == "from"){ flows <- merge(flows, dfctr, by.x = "j", by.y = "id")}
if (type == "to"){ flows <- merge(flows, dfctr, by.x = "i", by.y = "id")}
flows <- flows[order(flows$height, decreasing = TRUE),]
flows.json <- sf_geojson(flows, simplify = TRUE)
countries.json <- sf_geojson(ctr, simplify = FALSE)
center.json <- sf_geojson(center, simplify = FALSE)
return(list(flows.json,countries.json, center.json))
}
This source diff could not be displayed because it is too large. You can view the blob instead.
var width = 800;
var height = 850;
d3.select("svg")
.attr("id","map")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("class","back")
.attr("viewBox", "0 0 " + width + " " + height)
.style("background","white");
var projection = d3.geoOrthographic()
.scale(380)
.translate([width / 2 , height / 2 + 20])
.clipAngle(90)
.precision(.1);
var path = d3.geoPath().projection(projection);
svg.selectAll(".graticule").datum(graticule).attr("d", path);
svg.selectAll(".land").attr("d", path);
svg.selectAll(".flows").attr("d", path);
svg.selectAll(".label").attr("d", path);
if(svg.selectAll(".sphere").empty()){
// Graticule
var radialGradient = svg.append("defs").append("radialGradient").attr("id", "radial-gradient");
radialGradient.append("stop").attr("offset", "50%").attr("stop-color", "#63b0af");
radialGradient.append("stop").attr("offset", "100%").attr("stop-color", "#428c8b");
var sphere = svg.append("g")
.attr("id","sphere")
.attr("class", "sphere");
var graticule = d3.geoGraticule().step([10, 10]);
var sphere1 = sphere.append("path")
.datum(graticule.outline)
.style("fill", "url(#radial-gradient)")
.attr("class", "sphere")
.attr("d", path);
sphere.append("path")
.datum(graticule)
.attr("d", path);
// Countries
var land = svg.append("g")
.attr("id","land")
.attr("class","land");
land.selectAll("path")
.data(countries.features)
.enter()
.append("path")
.attr("class","land")
.attr("d", path);
// countries
var countries = svg.append("g")
.attr("id","countries");
// flows
var flows = svg.append("g")
.attr("id","flows");
// labels
var label = svg.append("g")
.attr("id","label");
// Texts
var title = svg.append("g")
.attr("id","title");
var help1 = "Rotate";
var help2 = "the Globe";
var help3 = "to Change";
var help4 = "the View";
delta = 20;
x = 707;
y = 23;
title.append("polygon").attr("fill","#e3cc64").attr("points", "690,0, 800,0, 800,100 700,100 650,130 690,85");
title.append("text").text(help1).attr("text-anchor", "start").attr("x", x).attr("y", y).attr("class","txthelp");
title.append("text").text(help2).attr("text-anchor", "start").attr("x", x).attr("y", y+delta).attr("class","txthelp");
title.append("text").text(help3).attr("text-anchor", "start").attr("x", x).attr("y", y+delta*2).attr("class","txthelp");
title.append("text").text(help4).attr("text-anchor", "start").attr("x", x).attr("y", y+delta*3).attr("class","txthelp");
}
// ----------------------------------------------------------------
// Countries
svg.selectAll(".countries").remove();
svg.selectAll("#countries").selectAll('path')
.data(r2d3.data[1].features)
.enter()
.append("path")
.attr("d", path)
.attr("class","countries");
// Label
svg.selectAll(".label").remove();
svg.selectAll("#label").selectAll('path')
.data(r2d3.data[2].features)
.enter().append("text")
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
.text(function(d) { return d.properties.name.substring(0,3).toUpperCase(); })
.attr("class","label");
// Flows
d3.selectAll(".tooltip").remove();
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
svg.selectAll(".flows").remove();
svg.selectAll("#flows").selectAll('path')
.data(r2d3.data[0].features)
.enter()
.append("path")
.attr("d", path)
.attr("class","flows")
.on("mouseover", function(d) {
d3.select(this).style("fill", "#ffc400");
div.transition()
.duration(200)
.style("opacity", 0.9);
div.html(d.properties.label + "<br/>" + d.properties.fij / 1000 + " thousands people")
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 50) + "px");
})
.on("mouseout", function(d) {
d3.select(this).style("fill", "#ff6200");
div.transition()
.duration(500)
.style("opacity", 0);
});
// Interactivity and rotation
const λ = d3.scaleLinear()
.domain([0, width])
.range([-180, 180]);
const φ = d3.scaleLinear()
.domain([0, height])
.range([90, -90]);
var drag = d3.drag().subject(function() {
var r = projection.rotate();
return {
x: λ.invert(r[0]),
y: φ.invert(r[1])
};
}).on("drag", function() {
var x = λ(d3.event.x);
var y = φ(d3.event.y);
projection.rotate([λ(d3.event.x), φ(d3.event.y)]);
svg.selectAll(".graticule")
.datum(graticule)
.attr("d", path);
svg.selectAll(".label")
.attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; });
svg.selectAll(".land")
.attr("d", path);
svg.selectAll(".flows")
.attr("d", path);
svg.selectAll(".countries")
.attr("d", path);
});
svg.call(drag);
This diff is collapsed.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment