Skip to content

Commit

Permalink
zooming
Browse files Browse the repository at this point in the history
  • Loading branch information
pvictor committed May 19, 2018
1 parent 299ffb2 commit 0568a50
Show file tree
Hide file tree
Showing 4 changed files with 363 additions and 2 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export(add_discrete_scale2)
export(add_labs)
export(add_legend)
export(add_tooltip)
export(add_zoom)
export(r2d3map)
importFrom(geojsonio,geo2topo)
importFrom(geojsonio,geojson_json)
Expand Down
42 changes: 40 additions & 2 deletions R/r2d3maps.R
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@ r2d3map <- function(shape, projection = "Mercator", width = NULL, height = NULL)
# script = "d3-legend.min.js"
# )
# ),
script = system.file("js/r2d3maps.js", package = "r2d3maps"),
script = system.file("js/r2d3maps2.js", package = "r2d3maps"),
options = list(
data = data, projection = projection,
tooltip = FALSE, legend = FALSE
tooltip = FALSE, legend = FALSE, zoom = FALSE
)
)
map$dependencies <- rev(map$dependencies)
Expand Down Expand Up @@ -369,6 +369,44 @@ add_tooltip <- function(map, value = "<b>{name}</b><<scale_var>>", as_glue = TRU



#' Add zoom capacity
#'
#' @param map A \code{r2d3map} \code{htmlwidget} object.
#' @param enabled Logical, enable zooming.
#' @param type Type of zoom: \code{"click"} for zooming to clicked polygon,
#' or \code{"wheel"} for zooming with mouse wheel. Both can be supplied.
#'
#' @note Zoom with mousewheel doesn't work in RStudio viewer.
#' Zooming can be slow for a map with lot of polygons.
#'
#' @export
#'
#' @examples
#' library( r2d3maps )
#' library( rnaturalearth )
#'
#'
#' turkey <- ne_states(country = "turkey", returnclass = "sf")
#' r2d3map(shape = turkey)
#'
#' # zoom with click
#' r2d3map(shape = turkey) %>% add_zoom()
#'
#' # zoom with mousewheel (open in browser)
#' r2d3map(shape = turkey) %>% add_zoom(type = "wheel")
#'
add_zoom <- function(map, enabled = TRUE, type = "click") {
type <- match.arg(type, c("click", "wheel"), TRUE)
map$x$options$zoom <- enabled
map$x$options$zoom_opts <- list(
click = "click" %in% type,
wheel = "wheel" %in% type
)
return(map)
}



#' Add a legend to a map
#'
#' @param map A \code{r2d3map} \code{htmlwidget} object.
Expand Down
284 changes: 284 additions & 0 deletions inst/js/r2d3maps2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
// D3 maps


r2d3.onRender(function(json, svg, width, height, options) {

// utils
// https://gist.github.com/mbostock/4699541
function clicked(d) {
if (active.node() === this) return reset();
active.classed("active", false);
active = d3.select(this).classed("active", true);
var bounds = path.bounds(d),
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = 0.8 / Math.max(dx / width, dy / height),
translate = [width / 2 - scale * x, height / 2 - scale * y];
g.transition()
.duration(750)
.style("stroke-width", 1.5 / scale + "px")
.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
}
function reset() {
active.classed("active", false);
active = d3.select(null);
g.transition()
.duration(750)
.style("stroke-width", "1.5px")
.attr("transform", "");
}
// https://bl.ocks.org/iamkevinv/0a24e9126cd2fa6b283c6f2d774b69a2
function zoomed() {
g.style("stroke-width", 1.5 / d3.event.transform.k + "px");
g.attr("transform", d3.event.transform); // updated for d3 v4
}

// global variables
var map, projection, active = d3.select(null);

// Projection
if (options.projection == "Mercator") {
projection = d3.geoMercator();
} else if (options.projection == "ConicEqualArea") {
projection = d3.geoConicEqualArea();
} else if (options.projection == "NaturalEarth") {
projection = d3.geoNaturalEarth1();
} else {
projection = d3.geoAlbers();
}

var path = d3.geoPath()
.projection(projection);

svg.attr("width", width)
.attr("height", height);

var states = topojson.feature(json, json.objects.states);

// set projection
projection
.scale(1)
.translate([0, 0]);

// define bounding box & associated scale & transform
var b = path.bounds(states),
s = 0.9 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];

// re-project
projection
.scale(s)
.translate(t);

// Tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);

// main g
var g = svg.append("g");

// colors
if (options !== null) {
if (typeof options.colors != 'undefined') {

// discrete colors
if (options.colors.color_type == 'discrete') {

var ordinal = d3.scaleOrdinal()
.domain(options.colors.values)
.range(options.colors.colors);
map = g.append("g")
.attr("class", "feature")
.selectAll("path")
.data(topojson.feature(json, json.objects.states).features)
.enter().append("path")
.attr("fill", function(d) { return ordinal(d.properties[options.colors.color_var]); })
.attr("d", path);


if (options.legend) {
// Legend
var gd = g.append("g")
.attr("class", "legendThreshold")
.attr("transform", "translate(10," + (height/2) + ")");
gd.append("text")
.attr("class", "caption")
.attr("x", 0)
.attr("y", -6)
.text(options.legend_opts.title);
var legend = d3.legendColor()
.orient("vertical")
.labels(function (d) { return options.colors.values[d.i]; })
.cellFilter(function (d) { if (typeof d.label === 'undefined') { return false; } else { return true; } })
.shapePadding(4)
.scale(ordinal);
g.select(".legendThreshold")
.call(legend);
}

}

// continuous colors
if (options.colors.color_type == 'continuous') {

var x = d3.scaleLinear()
.range([1, 200])
.domain(options.colors.range_var);
var color = d3.scaleThreshold()
.domain(options.colors.range_col)
.range(options.colors.colors);

if (options.legend) {
var gc = svg.append("g")
.attr("class", "key")
.attr("transform", "translate(10," + (height - 30) + ")");

gc.selectAll("rect")
.data(color.range().map(function(d) {
d = color.invertExtent(d);
if (d[0] === null) d[0] = x.domain()[0];
if (d[1] === null) d[1] = x.domain()[1];
return d;
}))
.enter().append("rect")
.attr("height", 8)
.attr("x", function(d) { return x(d[0]); })
.attr("width", function(d) { return x(d[1]) - x(d[0]); })
.attr("fill", function(d) { return color(d[0]); });

gc.append("text")
.attr("class", "caption")
.attr("x", 0)
.attr("y", -6)
.attr("fill", "#000")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(options.legend_opts.title);

gc.call(d3.axisBottom(x)
.tickSize(13)
.tickFormat(function(x, i) {
if (options.legend_opts.d3_format) {
return d3.format(options.legend_opts.d3_format)(x);
} else {
return options.legend_opts.prefix + x + options.legend_opts.suffix;
}
})
.tickValues(color.domain()))
.select(".domain")
.remove();
}

map = g.append("g")
.attr("class", "feature")
.selectAll("path")
.data(topojson.feature(json, json.objects.states).features)
.enter().append("path")
.attr("fill", function(d) { return color(d.properties[options.colors.color_var]); })
.attr("d", path);



}

} else {

map = g.append("g")
.attr("class", "feature")
.selectAll("path")
.data(topojson.feature(json, json.objects.states).features)
.enter().append("path")
.attr("fill", "#5f799c")
.attr("d", path);

}

} else {

g.append("path")
.datum(states)
.attr("class", "feature")
.attr("d", path);

}

if (options !== null) {

// Zooming
if (options.zoom) {
if (options.zoom_opts.click) {
map.on("click", clicked);
}
if (options.zoom_opts.wheel) {
var zoom = d3.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed);
svg.call(zoom);
}
}

if (options.tooltip) {

// Tooltip
g.selectAll("path")
.on("mouseover", function(d, i) {
d3.select(this).attr("opacity", 0.5);
// console.log(options.tooltip_value[i]);
if (options.tooltip_value[i] !== null) {
div.transition()
.duration(200)
.style("opacity", 0.9);
div.html(options.tooltip_value[i])
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
}
})
.on("mouseout", function(d) {
d3.select(this).attr("opacity", 1);
div.transition()
.duration(500)
.style("opacity", 0);
});

}
}

if (options !== null) {
if (typeof options.labs != 'undefined') {
if (typeof options.labs.title != 'undefined') {
var title = g.append("g")
.attr("class", "title")
.attr("transform", "translate(10,20)");
title.append("text")
.attr("class", "title")
.attr("x", 0)
.attr("y", -2)
.attr("fill", "#000")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(options.labs.title);
}
if (typeof options.labs.caption != 'undefined') {
g.append("text")
.attr("class", "caption")
.attr("x", width)
.attr("y", height-5)
//.attr("startOffset", "100%")
.attr("text-anchor", "end")
.attr("font-size", "90%")
.text(options.labs.caption);
}
} else {
//console.log("nope");
}
}

g.append("path")
.datum(topojson.mesh(json, json.objects.states, function(a, b) { return a !== b; }))
.attr("class", "mesh")
.attr("d", path);

});
38 changes: 38 additions & 0 deletions man/add_zoom.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0568a50

Please sign in to comment.