Etiquetas
Instituto Nacional de Estadística (Spain), javascript, R, SVG, visualization
In my previous posts (1 and 2) I wrote about maps with complex legends but without any kind of interactivity. In this post I show how to produce an SVG file with interactive functionalities with the gridSVG package.
As an example, I use a dataset about population from the Spanish Instituto Nacional de Estadística. This organism publishes information using the PC_Axis format which can be imported into R with the pxR package. This dataset is available at the INE webpage or directly here.
Let’s start loading packages.
library(gridSVG) library(pxR) library(sp) library(lattice) library(latticeExtra) library(maptools) library(classInt) library(colorspace)
Then I read the px
file and make some changes to get the datWide
data.frame
(which is available here if you are not interested in the PC-Axis details).
datPX <- read.px('pcaxis-676270323.px', encoding='latin1') datWide <- as.data.frame(datPX, direction = 'wide', use.codes=FALSE) provID <- strsplit(as.character(datWide$provincias), ' ') provID <- as.data.frame(do.call(rbind, provID)) names(provID) <- c('code', 'prov') datWide <- cbind(datWide, provID)
Now it’s time to read a suitable shapefile
(read the first post of this series for information about it).
mapSHP <- readShapePoly(fn = 'mapas_completo_municipal/spain_provinces_ind_2') Encoding(levels(mapSHP$NOMBRE99)) <- "latin1" ## The encoding must be UTF8 to be correctly displayed in the SVG file levels(mapSHP$NOMBRE99) <- enc2utf8(levels(mapSHP$NOMBRE99))
Both the shapefile
and the data.frame
have to be combined using the matches between the PROV
variable of the shapefile
and code
from the data.frame
. The numeric values of the row names (mapaIDs
) will be useful in the last step.
idx <- match(mapSHP$PROV, datWide$code) Total <- datWide[idx, "Total"] mapSHP@data <- cbind(mapSHP@data, Total) mapaDat <- as.data.frame(mapSHP) mapaIDs <- as.numeric(rownames(mapaDat))
A final step is needed before calling spplot
. I will use the functions of gridSVG
to include information in the SVG file according to the characteristics of each polygon. Since gridSVG
works after the plot has been created, I need a key to identify each polygon and match it with the correspondent element of the original dataset. Unfortunately, the panel.polygonsplot
(which is the function used by spplot
when drawing polygons) does not assign a name to each polygon. Let’s create a new function (panel.polygonNames
) adding a small change in panel.polygonsplot
(you should read the full code of panel.polygonsplot
to understand what is happening here).
panel.str <- deparse(panel.polygonsplot, width=500) panel.str <- sub("grid.polygon\\((.*)\\)", "grid.polygon(\\1, name=paste('ID', slot(pls\\[\\[i\\]\\], 'ID'\\), sep=':'))", panel.str) panel.polygonNames <- eval(parse(text=panel.str), envir=environment(panel.polygonsplot))
EDITED: Thanks to Andre’s comment (see below), I have found that panel.polygonsplot
has been changed to add hole-handling. This hack will only work if this new behaviour is disabled with set_Polypath(FALSE)
before the next code chunk.
Now everything is ready for drawing. I use the jenks style of the classIntervals
function from the classInt package to set the breaks of the color key.
n=7 int <- classIntervals(Total, n, style='jenks') pal <- brewer.pal(n, 'Blues') p <- spplot(mapSHP["Total"], panel=panel.polygonNames, col.regions=pal, at=signif(int$brks, digits=2)) p
EDITED: You can recover the default hole-handling setting with set_Polypath(TRUE)
after the p
object has been printed.
Once the plot has been created (do not close the graphic window!) the grid.garnish
attaches SVG attributes (in this example onmouseover
and onmouseout
) to each polygon, which is identified with its name thanks to the panel.polygonNames function
.
These attributes are related to javascript
functions (showTooltip
and hideTooltip
) included in this javascript file (this file is only a minor modification of the original file available at the webpage of the creator of grid
and gridSVG
). The grid.script
function attaches the javascript
file to the grob
and gridToSVG
produces the SVG file with a simple HTML page. EDITED: The javascript file (tooltip.js) must be saved in the same folder as the SVG and HTML files.
## grobs in the graphical output grobs <- grid.ls() ## only interested in those with "ID:" in the name nms <- grobs$name[grobs$type == "grobListing"] idxNames <- grep('ID:', nms) IDs <- nms[idxNames] for (id in unique(IDs)){ ## extract information from the data ## according to the ID value i <- strsplit(id, 'ID:') i <- sapply(i, function(x)as.numeric(x[2])) dat <- mapaDat[which(mapaIDs==i),] ## Information to be attached to each polygon info <- paste(dat$NOMBRE99, dat$Total, sep=':') g <- grid.get(id) ## attach SVG attributes grid.garnish(id, onmouseover=paste("showTooltip(evt, '", info, "')"), onmouseout="hideTooltip()") } grid.script(filename="tooltip.js") gridToSVG('map_with_annotations.svg')
(Click on the image to show the SVG graphic. Move the mouse over it to display the information)
Efstathios dijo:
Very useful and interesting, thank you.
Pingback: Mapping Flows in R … with data.table and lattice | Omnia sunt Communia!
Lucie dijo:
Dear Oscar,
I have one question about map background. Exist some better map of Spain? I need only Canary Islands and they are oversimplified. Thank you very much. Lucie
Oscar Perpiñán Lamigueiro dijo:
Yes. You have to choose a different shapefile from the set of files available at INE. It isn’t very well documented so you will have to try some of them.
Best,
Oscar.
Pingback: Client based web-mapping | Mortens meninger
Dieg dijo:
Hi again Oscar,
That was the problem. tooltip.js not in the wd.
Now it works just fine!
Thanks a lot for your time and for sharing knowledge.
Oscar Perpiñán Lamigueiro dijo:
Good news! I have edited the post to make it clear.
Andre dijo:
Thank you for this great application of gridSVG!
I get everything to work fine with my data until this chunk:
## grobs in the graphical output
grobs <- grid.ls()
## only interested in those with "ID:" in the name
nms <- grobs$name[grobs$type == "grobListing"]
idxNames <- grep('ID:', nms)
IDs <- nms[idxNames]
The output I get for IDs is character[0], which I suspect is not what I should have. I fail to see in general where the various id in this chunk and the following loop come from. Are we filtering the grid.ls() output? Any clarification would be much appreciated.
Thank you.
Oscar Perpiñán Lamigueiro dijo:
Hi Andre,
Thanks for your positive feedback.
You are right. With this code we are filtering the output of grid.ls(), which gives a listing of the names of the grobs and viewports of the graphical scene you are displaying. If you get character(0) for IDs, I suspect the content of the graphical scene is empty. Perhaps you closed the window before calling grid.ls()? What is the value of grobs?
Best,
Oscar.
Andre dijo:
With the original code, my grobs output is flatGridListing[6] (I did not close the graphical device). I changed the filter to ID instead of ID: and now have integer[50]., which seems right. However, when running
i <- sapply(i, function(x)as.numeric(x[2]))
in the loop, all I get is NAs and warnings, and my beautiful map disappears.
Thanks.
Andre
Oscar Perpiñán Lamigueiro dijo:
Hi Andre,
I have found the problem. The
panel.polygonsplot
has been changed to manage holes in polygons. Now this function usesgrid.path
instead ofgrid.polygon
by default. Thus, my hack is not working. But there is an easy solution:panel.polygonsplot
first checks if the optionPolypath
isTRUE
(default) withget_Polypath()
. You can change it withset_Polypath(FALSE)
and then the hole-handling is disabled, andgrid.polygon
is used instead ofgrid.path
.I will edit the post with this information. Thanks for your message!
Oscar.
afro dijo:
I appreciate the output. Your brains please. Am new 2 R. I am trying to map a shapefile in R2.15 after loading packages (Maptools, RColorBrwer and ClassInt). I try reading the .shp with readShapePoly or readShapeSpatial only getting this message,
Error: could not find function «readShapePoly»; Error: could not find function «readShapeSpatial».
Tnx in advance 4 ur advise.
Afro
Oscar Perpiñán Lamigueiro dijo:
Both functions are included in the package
maptools
, so perhaps you got an error when callinglibrary(maptools)
. Could you post the result ofsessionInfo()
?Oscar
Erin Hodgess dijo:
This is very beautiful.
I have a question please: I’m having problems with the last part:
> gridToSVG(‘map_with_annotations.svg’)
Warning message:
In grabDL(warn, wrap, …) :
grob(s) overwritten (grab WILL not be faithful; try ‘wrap=TRUE’)
and no plot appears.
Do you have any suggestions, please?
Thanks,
Erin
Oscar Perpiñán Lamigueiro dijo:
Hi Erin,
Thanks for the comment.
When you say «no plot appears», do you mean that you get nothing if you ask for «p»? Remember that you should not close the graphical window after drawing the result of spplot (p).
After the call to gridToSVG you should obtain two files in your working directory: SVG and HTML files. Is that true for you? What happens when you open the html file?
Oscar.
Dieg dijo:
I am having the same problem as «Erin».
It outputs the same warning when running «gridToSVG»:
> gridToSVG(‘map_with_annotations.svg’)
Mensajes de aviso perdidos
In grabDL(warn, wrap, …) :
one of more grobs overwritten (grab WILL not be faithful; try ‘wrap = TRUE’)
In the working directory I obtain 4 files:
map_with_annotations.svg
map_with_annotations.svg.convert.js
map_with_annotations.svg.coords.js
map_with_annotations.svg.html
When opening the html file it plots the map, but no labeling occurs when dragging the mouse over the polygons.
The file from dropbox works just fine!
Any ideas?
Oscar Perpiñán Lamigueiro dijo:
Hello Diego,
Please check that the file tooltip.js is included in the same folder as the HTML and SVG files.
Oscar
Andrés Bustamante dijo:
Estimado Oscar
Que fantástica habilidad en el manejo de éste excelente software libre! Yo lo he estado usando para investigación en agricultura y es una potente herramienta para mi toma de decisiones.
Quiero preguntarte si es posible compartir un script que tengo. Este script en base a coordenadas y valores de clorofila, me hace un mapa del cultivo haciendo una predicción en colores de los valores de clorofila, de modo que se pueden identificar aquellas zonas con menor nutrición.
Me puedes compartir tu email para enviarlo?, así lo puedes checar y mejorar para la comunidad R que lo use en investigación agrícola.
Mucho éxito y muchas gracias.