diff --git a/NAMESPACE b/NAMESPACE index d0e1ee186..2ae7df011 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -156,7 +156,10 @@ export(removeTopoJSON) export(renderLeaflet) export(safeLabel) export(scaleBarOptions) +export(setCircleMarkerRadius) +export(setCircleMarkerStyle) export(setMaxBounds) +export(setShapeStyle) export(setView) export(showGroup) export(tileOptions) diff --git a/NEWS b/NEWS index f6f8f3260..0f4cc8a78 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,9 @@ + +leaflet 2.0.4 + +* Added new functions setShapeStyle, setCircleMarkerStyle and setCircleMarkerRadius to change +style and radius of already generated polygons, polylines and circle markers (leafletProxy) + leaflet 2.0.3 -------------------------------------------------------------------------------- diff --git a/R/layers.R b/R/layers.R index ca958da1f..ab6f925a6 100644 --- a/R/layers.R +++ b/R/layers.R @@ -989,6 +989,60 @@ addCircleMarkers <- function( expandLimits(pts$lat, pts$lng) } +#' @describeIn map-layers Change radius of existing circle markers. +#' @export +setCircleMarkerRadius <- function(map, layerId, radius, data=getMapData(map)){ + options <- list(layerId = layerId, radius = radius) + # evaluate all options + options <- evalFormula(options, data = data) + # make them the same length (by building a data.frame) + options <- do.call(data.frame, c(options, list(stringsAsFactors=FALSE))) + leaflet::invokeMethod(map, data, "setRadius", options$layerId, options$radius) +} + +#' @describeIn map-layers Change style of existing circle markers. +#' @export +setCircleMarkerStyle <- function(map, layerId + , radius = NULL + , stroke = NULL + , color = NULL + , weight = NULL + , opacity = NULL + , fill = NULL + , fillColor = NULL + , fillOpacity = NULL + , dashArray = NULL + , options = NULL + , data = getMapData(map) +){ + if (!is.null(radius)){ + setCircleMarkerRadius(map, layerId = layerId, radius = radius, data = data) + } + + options <- c(list(layerId = layerId), + options, + filterNULL(list(stroke = stroke, color = color, + weight = weight, opacity = opacity, + fill = fill, fillColor = fillColor, + fillOpacity = fillOpacity, dashArray = dashArray + ))) + + if (length(options) < 2) { # no style options set + return() + } + # evaluate all options + options <- evalFormula(options, data = data) + + # make them the same length (by building a data.frame) + options <- do.call(data.frame, c(options, list(stringsAsFactors=FALSE))) + layerId <- options[[1]] + style <- options[-1] # drop layer column + + #print(list(style=style)) + leaflet::invokeMethod(map, data, "setStyle", "marker", layerId, style); +} + + #' @rdname remove #' @export removeMarker <- function(map, layerId) { @@ -1235,6 +1289,37 @@ addPolygons <- function( expandLimitsBbox(pgons) } +#' @describeIn map-layers Change style of existing polygons or polylines +#' @export +setShapeStyle <- function( map, data = getMapData(map), layerId, + stroke = NULL, color = NULL, + weight = NULL, opacity = NULL, + fill = NULL, fillColor = NULL, + fillOpacity = NULL, dashArray = NULL, + smoothFactor = NULL, noClip = NULL, + options = NULL +){ + options <- c(list(layerId = layerId), + options, + filterNULL(list(stroke = stroke, color = color, + weight = weight, opacity = opacity, + fill = fill, fillColor = fillColor, + fillOpacity = fillOpacity, dashArray = dashArray, + smoothFactor = smoothFactor, noClip = noClip + ))) + # evaluate all options + options <- evalFormula(options, data = data) + # make them the same length (by building a data.frame) + options <- do.call(data.frame, c(options, list(stringsAsFactors=FALSE))) + + layerId <- options[[1]] + style <- options[-1] # drop layer column + + #print(list(style=style)) + leaflet::invokeMethod(map, data, "setStyle", "shape", layerId, style); +} + + #' @rdname remove #' @export removeShape <- function(map, layerId) { @@ -1247,6 +1332,8 @@ clearShapes <- function(map) { invokeMethod(map, NULL, "clearShapes") } + + #' @param geojson a GeoJSON list, or character vector of length 1 #' @describeIn map-layers Add GeoJSON layers to the map #' @export diff --git a/inst/examples/setStyle.R b/inst/examples/setStyle.R new file mode 100644 index 000000000..692ca2b5a --- /dev/null +++ b/inst/examples/setStyle.R @@ -0,0 +1,32 @@ +library(shiny) +library(leaflet) + +coor <- sp::coordinates(gadmCHE) + +ui <- fluidPage( + leafletOutput("map"), + radioButtons("color", "Color", choices = c("blue", "red", "green")), + sliderInput("radius", "Radius", min = 1, max = 30, value=5, animate = TRUE) +) + +server <- function(input, output, session){ + output$map <- renderLeaflet({ + leaflet(data=gadmCHE) %>% + addPolygons(layerId = ~NAME_1, weight = 1) %>% + addCircleMarkers(layerId = gadmCHE$NAME_1, data = coor, weight = 1) + }) + + observe({ + leafletProxy("map", data = gadmCHE) %>% + setCircleMarkerRadius(gadmCHE$NAME_1, input$radius) + }) + + observe({ + leafletProxy("map", data = gadmCHE) %>% + setShapeStyle(layerId = ~NAME_1, fillColor=input$color, color = input$color) %>% + setCircleMarkerStyle(layerId = ~NAME_1, fillColor = input$color, color = input$color) + }) + +} + +shinyApp(ui, server) diff --git a/inst/htmlwidgets/leaflet.js b/inst/htmlwidgets/leaflet.js index 2b09f0415..b80582e26 100644 --- a/inst/htmlwidgets/leaflet.js +++ b/inst/htmlwidgets/leaflet.js @@ -1,4 +1,4 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= tickWidth) - - // # Derived formatting parameters. - - // What's the height of a single bin, in percentage (of gradient height)? - // It might not just be 1/(n-1), if the gradient extends past the tick - // marks (which can be the case for pretty cut points). - var singleBinPct = (options.extra.p_n - options.extra.p_1) / (labels.length - 1); - // Each bin is `singleBinHeight` high. How tall is the gradient? - var totalHeight = 1 / singleBinPct * singleBinHeight + 1; - // How far should the first tick be shifted down, relative to the top - // of the gradient? - var tickOffset = singleBinHeight / singleBinPct * options.extra.p_1; - - gradSpan = (0, _jquery2.default)("").css({ - "background": "linear-gradient(" + colors + ")", - "opacity": options.opacity, - "height": totalHeight + "px", - "width": "18px", - "display": "block", - "margin-top": vMargin + "px" - }); - var leftDiv = (0, _jquery2.default)("
").css("float", "left"), - rightDiv = (0, _jquery2.default)("
").css("float", "left"); - leftDiv.append(gradSpan); - (0, _jquery2.default)(div).append(leftDiv).append(rightDiv).append((0, _jquery2.default)("
")); - - // Have to attach the div to the body at this early point, so that the - // svg text getComputedTextLength() actually works, below. - document.body.appendChild(div); - - var ns = "http://www.w3.org/2000/svg"; - var svg = document.createElementNS(ns, "svg"); - rightDiv.append(svg); - var g = document.createElementNS(ns, "g"); - (0, _jquery2.default)(g).attr("transform", "translate(0, " + vMargin + ")"); - svg.appendChild(g); - - // max label width needed to set width of svg, and right-justify text - var maxLblWidth = 0; - - // Create tick marks and labels - _jquery2.default.each(labels, function (i, label) { - var y = tickOffset + i * singleBinHeight + 0.5; - - var thisLabel = document.createElementNS(ns, "text"); - (0, _jquery2.default)(thisLabel).text(labels[i]).attr("y", y).attr("dx", labelPadding).attr("dy", "0.5ex"); - g.appendChild(thisLabel); - maxLblWidth = Math.max(maxLblWidth, thisLabel.getComputedTextLength()); - - var thisTick = document.createElementNS(ns, "line"); - (0, _jquery2.default)(thisTick).attr("x1", 0).attr("x2", tickWidth).attr("y1", y).attr("y2", y).attr("stroke-width", 1); - g.appendChild(thisTick); - }); + // # Formatting constants. + var singleBinHeight = 20; // The distance between tick marks, in px + var vMargin = 8; // If 1st tick mark starts at top of gradient, how + // many extra px are needed for the top half of the + // 1st label? (ditto for last tick mark/label) + var tickWidth = 4; // How wide should tick marks be, in px? + var labelPadding = 6; // How much distance to reserve for tick mark? + // (Must be >= tickWidth) + + // # Derived formatting parameters. + + // What's the height of a single bin, in percentage (of gradient height)? + // It might not just be 1/(n-1), if the gradient extends past the tick + // marks (which can be the case for pretty cut points). + var singleBinPct = (options.extra.p_n - options.extra.p_1) / (labels.length - 1); + // Each bin is `singleBinHeight` high. How tall is the gradient? + var totalHeight = 1 / singleBinPct * singleBinHeight + 1; + // How far should the first tick be shifted down, relative to the top + // of the gradient? + var tickOffset = singleBinHeight / singleBinPct * options.extra.p_1; + + gradSpan = (0, _jquery2.default)("").css({ + "background": "linear-gradient(" + colors + ")", + "opacity": options.opacity, + "height": totalHeight + "px", + "width": "18px", + "display": "block", + "margin-top": vMargin + "px" + }); + var leftDiv = (0, _jquery2.default)("
").css("float", "left"), + rightDiv = (0, _jquery2.default)("
").css("float", "left"); + leftDiv.append(gradSpan); + (0, _jquery2.default)(div).append(leftDiv).append(rightDiv).append((0, _jquery2.default)("
")); + + // Have to attach the div to the body at this early point, so that the + // svg text getComputedTextLength() actually works, below. + document.body.appendChild(div); + + var ns = "http://www.w3.org/2000/svg"; + var svg = document.createElementNS(ns, "svg"); + rightDiv.append(svg); + var g = document.createElementNS(ns, "g"); + (0, _jquery2.default)(g).attr("transform", "translate(0, " + vMargin + ")"); + svg.appendChild(g); + + // max label width needed to set width of svg, and right-justify text + var maxLblWidth = 0; + + // Create tick marks and labels + _jquery2.default.each(labels, function (i, label) { + var y = tickOffset + i * singleBinHeight + 0.5; + + var thisLabel = document.createElementNS(ns, "text"); + (0, _jquery2.default)(thisLabel).text(labels[i]).attr("y", y).attr("dx", labelPadding).attr("dy", "0.5ex"); + g.appendChild(thisLabel); + maxLblWidth = Math.max(maxLblWidth, thisLabel.getComputedTextLength()); + + var thisTick = document.createElementNS(ns, "line"); + (0, _jquery2.default)(thisTick).attr("x1", 0).attr("x2", tickWidth).attr("y1", y).attr("y2", y).attr("stroke-width", 1); + g.appendChild(thisTick); + }); - // Now that we know the max label width, we can right-justify - (0, _jquery2.default)(svg).find("text").attr("dx", labelPadding + maxLblWidth).attr("text-anchor", "end"); - // Final size for - (0, _jquery2.default)(svg).css({ - width: maxLblWidth + labelPadding + "px", - height: totalHeight + vMargin * 2 + "px" - }); + // Now that we know the max label width, we can right-justify + (0, _jquery2.default)(svg).find("text").attr("dx", labelPadding + maxLblWidth).attr("text-anchor", "end"); + // Final size for + (0, _jquery2.default)(svg).css({ + width: maxLblWidth + labelPadding + "px", + height: totalHeight + vMargin * 2 + "px" + }); - if (options.na_color && _jquery2.default.inArray(options.na_label, labels) < 0) { - (0, _jquery2.default)(div).append("
" + options.na_label + "
"); - } - })(); + if (options.na_color && _jquery2.default.inArray(options.na_label, labels) < 0) { + (0, _jquery2.default)(div).append("
" + options.na_label + "
"); + } } else { if (options.na_color && _jquery2.default.inArray(options.na_label, labels) < 0) { colors.push(options.na_color); @@ -1994,41 +2011,39 @@ methods.addLegend = function (options) { }; if (options.group) { - (function () { - // Auto generate a layerID if not provided - if (!options.layerId) { - options.layerId = _leaflet2.default.Util.stamp(legend); - } + // Auto generate a layerID if not provided + if (!options.layerId) { + options.layerId = _leaflet2.default.Util.stamp(legend); + } - var map = _this5; - map.on("overlayadd", function (e) { - if (e.name === options.group) { - map.controls.add(legend, options.layerId); - } - }); - map.on("overlayremove", function (e) { - if (e.name === options.group) { - map.controls.remove(options.layerId); - } - }); - map.on("groupadd", function (e) { - if (e.name === options.group) { - map.controls.add(legend, options.layerId); - } - }); - map.on("groupremove", function (e) { - if (e.name === options.group) { - map.controls.remove(options.layerId); - } - }); - })(); + var map = this; + map.on("overlayadd", function (e) { + if (e.name === options.group) { + map.controls.add(legend, options.layerId); + } + }); + map.on("overlayremove", function (e) { + if (e.name === options.group) { + map.controls.remove(options.layerId); + } + }); + map.on("groupadd", function (e) { + if (e.name === options.group) { + map.controls.add(legend, options.layerId); + } + }); + map.on("groupremove", function (e) { + if (e.name === options.group) { + map.controls.remove(options.layerId); + } + }); } this.controls.add(legend, options.layerId); }; methods.addLayersControl = function (baseGroups, overlayGroups, options) { - var _this6 = this; + var _this4 = this; // Only allow one layers control at a time methods.removeLayersControl.call(this); @@ -2036,23 +2051,23 @@ methods.addLayersControl = function (baseGroups, overlayGroups, options) { var firstLayer = true; var base = {}; _jquery2.default.each((0, _util.asArray)(baseGroups), function (i, g) { - var layer = _this6.layerManager.getLayerGroup(g, true); + var layer = _this4.layerManager.getLayerGroup(g, true); if (layer) { base[g] = layer; // Check if >1 base layers are visible; if so, hide all but the first one - if (_this6.hasLayer(layer)) { + if (_this4.hasLayer(layer)) { if (firstLayer) { firstLayer = false; } else { - _this6.removeLayer(layer); + _this4.removeLayer(layer); } } } }); var overlay = {}; _jquery2.default.each((0, _util.asArray)(overlayGroups), function (i, g) { - var layer = _this6.layerManager.getLayerGroup(g, true); + var layer = _this4.layerManager.getLayerGroup(g, true); if (layer) { overlay[g] = layer; } @@ -2086,23 +2101,23 @@ methods.removeScaleBar = function () { }; methods.hideGroup = function (group) { - var _this7 = this; + var _this5 = this; _jquery2.default.each((0, _util.asArray)(group), function (i, g) { - var layer = _this7.layerManager.getLayerGroup(g, true); + var layer = _this5.layerManager.getLayerGroup(g, true); if (layer) { - _this7.removeLayer(layer); + _this5.removeLayer(layer); } }); }; methods.showGroup = function (group) { - var _this8 = this; + var _this6 = this; _jquery2.default.each((0, _util.asArray)(group), function (i, g) { - var layer = _this8.layerManager.getLayerGroup(g, true); + var layer = _this6.layerManager.getLayerGroup(g, true); if (layer) { - _this8.addLayer(layer); + _this6.addLayer(layer); } }); }; @@ -2142,10 +2157,10 @@ function setupShowHideGroupsOnZoom(map) { } methods.setGroupOptions = function (group, options) { - var _this9 = this; + var _this7 = this; _jquery2.default.each((0, _util.asArray)(group), function (i, g) { - var layer = _this9.layerManager.getLayerGroup(g, true); + var layer = _this7.layerManager.getLayerGroup(g, true); // This slightly tortured check is because 0 is a valid value for zoomLevels if (typeof options.zoomLevels !== "undefined" && options.zoomLevels !== null) { layer.zoomLevels = (0, _util.asArray)(options.zoomLevels); @@ -2297,131 +2312,123 @@ methods.addRasterImage = function (uri, bounds, opacity, attribution, layerId, g getImageData(function (imgData, w, h, mipmapper) { try { - var _ret8 = function () { - // The Context2D we'll being drawing onto. It's always 256x256. - var ctx = canvas.getContext("2d"); - - // Convert our image data's top-left and bottom-right locations into - // x/y tile coordinates. This is essentially doing a spherical mercator - // projection, then multiplying by 2^zoom. - var topLeft = degree2tile(bounds[0][0], bounds[0][1], zoom); - var bottomRight = degree2tile(bounds[1][0], bounds[1][1], zoom); - // The size of the image in x/y tile coordinates. - var extent = { x: bottomRight.x - topLeft.x, y: bottomRight.y - topLeft.y }; - - // Short circuit if tile is totally disjoint from image. - if (!overlap(tilePoint.x, tilePoint.x + 1, topLeft.x, bottomRight.x)) return { - v: void 0 - }; - if (!overlap(tilePoint.y, tilePoint.y + 1, topLeft.y, bottomRight.y)) return { - v: void 0 - }; - - // The linear resolution of the tile we're drawing is always 256px per tile unit. - // If the linear resolution (in either direction) of the image is less than 256px - // per tile unit, then use nearest neighbor; otherwise, use the canvas's built-in - // scaling. - var imgRes = { - x: w / extent.x, - y: h / extent.y + // The Context2D we'll being drawing onto. It's always 256x256. + var ctx = canvas.getContext("2d"); + + // Convert our image data's top-left and bottom-right locations into + // x/y tile coordinates. This is essentially doing a spherical mercator + // projection, then multiplying by 2^zoom. + var topLeft = degree2tile(bounds[0][0], bounds[0][1], zoom); + var bottomRight = degree2tile(bounds[1][0], bounds[1][1], zoom); + // The size of the image in x/y tile coordinates. + var extent = { x: bottomRight.x - topLeft.x, y: bottomRight.y - topLeft.y }; + + // Short circuit if tile is totally disjoint from image. + if (!overlap(tilePoint.x, tilePoint.x + 1, topLeft.x, bottomRight.x)) return; + if (!overlap(tilePoint.y, tilePoint.y + 1, topLeft.y, bottomRight.y)) return; + + // The linear resolution of the tile we're drawing is always 256px per tile unit. + // If the linear resolution (in either direction) of the image is less than 256px + // per tile unit, then use nearest neighbor; otherwise, use the canvas's built-in + // scaling. + var imgRes = { + x: w / extent.x, + y: h / extent.y + }; + + // We can do the actual drawing in one of three ways: + // - Call drawImage(). This is easy and fast, and results in smooth + // interpolation (bilinear?). This is what we want when we are + // reducing the image from its native size. + // - Call drawImage() with imageSmoothingEnabled=false. This is easy + // and fast and gives us nearest-neighbor interpolation, which is what + // we want when enlarging the image. However, it's unsupported on many + // browsers (including QtWebkit). + // - Do a manual nearest-neighbor interpolation. This is what we'll fall + // back to when enlarging, and imageSmoothingEnabled isn't supported. + // In theory it's slower, but still pretty fast on my machine, and the + // results look the same AFAICT. + + // Is imageSmoothingEnabled supported? If so, we can let canvas do + // nearest-neighbor interpolation for us. + var smoothingProperty = getCanvasSmoothingProperty(ctx); + + if (smoothingProperty || imgRes.x >= 256 && imgRes.y >= 256) { + // Use built-in scaling + + // Turn off anti-aliasing if necessary + if (smoothingProperty) { + ctx[smoothingProperty] = imgRes.x >= 256 && imgRes.y >= 256; + } + + // Don't necessarily draw with the full-size image; if we're + // downscaling, use the mipmapper to get a pre-downscaled image + // (see comments on Mipmapper class for why this matters). + mipmapper.getBySize(extent.x * 256, extent.y * 256, function (mip) { + // It's possible that the image will go off the edge of the canvas-- + // that's OK, the canvas should clip appropriately. + ctx.drawImage(mip, + // Convert abs tile coords to rel tile coords, then *256 to convert + // to rel pixel coords + (topLeft.x - tilePoint.x) * 256, (topLeft.y - tilePoint.y) * 256, + // Always draw the whole thing and let canvas clip; so we can just + // convert from size in tile coords straight to pixels + extent.x * 256, extent.y * 256); + }); + } else { + // Use manual nearest-neighbor interpolation + + // Calculate the source image pixel coordinates that correspond with + // the top-left and bottom-right of this tile. (If the source image + // only partially overlaps the tile, we use max/min to limit the + // sourceStart/End to only reflect the overlapping portion.) + var sourceStart = { + x: Math.max(0, Math.floor((tilePoint.x - topLeft.x) * imgRes.x)), + y: Math.max(0, Math.floor((tilePoint.y - topLeft.y) * imgRes.y)) + }; + var sourceEnd = { + x: Math.min(w, Math.ceil((tilePoint.x + 1 - topLeft.x) * imgRes.x)), + y: Math.min(h, Math.ceil((tilePoint.y + 1 - topLeft.y) * imgRes.y)) }; - // We can do the actual drawing in one of three ways: - // - Call drawImage(). This is easy and fast, and results in smooth - // interpolation (bilinear?). This is what we want when we are - // reducing the image from its native size. - // - Call drawImage() with imageSmoothingEnabled=false. This is easy - // and fast and gives us nearest-neighbor interpolation, which is what - // we want when enlarging the image. However, it's unsupported on many - // browsers (including QtWebkit). - // - Do a manual nearest-neighbor interpolation. This is what we'll fall - // back to when enlarging, and imageSmoothingEnabled isn't supported. - // In theory it's slower, but still pretty fast on my machine, and the - // results look the same AFAICT. - - // Is imageSmoothingEnabled supported? If so, we can let canvas do - // nearest-neighbor interpolation for us. - var smoothingProperty = getCanvasSmoothingProperty(ctx); - - if (smoothingProperty || imgRes.x >= 256 && imgRes.y >= 256) { - // Use built-in scaling - - // Turn off anti-aliasing if necessary - if (smoothingProperty) { - ctx[smoothingProperty] = imgRes.x >= 256 && imgRes.y >= 256; - } + // The size, in dest pixels, that each source pixel should occupy. + // This might be greater or less than 1 (e.g. if x and y resolution + // are very different). + var pixelSize = { + x: 256 / imgRes.x, + y: 256 / imgRes.y + }; - // Don't necessarily draw with the full-size image; if we're - // downscaling, use the mipmapper to get a pre-downscaled image - // (see comments on Mipmapper class for why this matters). - mipmapper.getBySize(extent.x * 256, extent.y * 256, function (mip) { - // It's possible that the image will go off the edge of the canvas-- - // that's OK, the canvas should clip appropriately. - ctx.drawImage(mip, - // Convert abs tile coords to rel tile coords, then *256 to convert - // to rel pixel coords - (topLeft.x - tilePoint.x) * 256, (topLeft.y - tilePoint.y) * 256, - // Always draw the whole thing and let canvas clip; so we can just - // convert from size in tile coords straight to pixels - extent.x * 256, extent.y * 256); - }); - } else { - // Use manual nearest-neighbor interpolation - - // Calculate the source image pixel coordinates that correspond with - // the top-left and bottom-right of this tile. (If the source image - // only partially overlaps the tile, we use max/min to limit the - // sourceStart/End to only reflect the overlapping portion.) - var sourceStart = { - x: Math.max(0, Math.floor((tilePoint.x - topLeft.x) * imgRes.x)), - y: Math.max(0, Math.floor((tilePoint.y - topLeft.y) * imgRes.y)) - }; - var sourceEnd = { - x: Math.min(w, Math.ceil((tilePoint.x + 1 - topLeft.x) * imgRes.x)), - y: Math.min(h, Math.ceil((tilePoint.y + 1 - topLeft.y) * imgRes.y)) - }; - - // The size, in dest pixels, that each source pixel should occupy. - // This might be greater or less than 1 (e.g. if x and y resolution - // are very different). - var pixelSize = { - x: 256 / imgRes.x, - y: 256 / imgRes.y - }; - - // For each pixel in the source image that overlaps the tile... - for (var row = sourceStart.y; row < sourceEnd.y; row++) { - for (var col = sourceStart.x; col < sourceEnd.x; col++) { - // ...extract the pixel data... - var i = (row * w + col) * 4; - var r = imgData[i]; - var g = imgData[i + 1]; - var b = imgData[i + 2]; - var a = imgData[i + 3]; - ctx.fillStyle = "rgba(" + [r, g, b, a / 255].join(",") + ")"; - - // ...calculate the corresponding pixel coord in the dest image - // where it should be drawn... - var pixelPos = { - x: (col / imgRes.x + topLeft.x - tilePoint.x) * 256, - y: (row / imgRes.y + topLeft.y - tilePoint.y) * 256 - }; - - // ...and draw a rectangle there. - ctx.fillRect(Math.round(pixelPos.x), Math.round(pixelPos.y), - // Looks crazy, but this is necessary to prevent rounding from - // causing overlap between this rect and its neighbors. The - // minuend is the location of the next pixel, while the - // subtrahend is the position of the current pixel (to turn an - // absolute coordinate to a width/height). Yes, I had to look - // up minuend and subtrahend. - Math.round(pixelPos.x + pixelSize.x) - Math.round(pixelPos.x), Math.round(pixelPos.y + pixelSize.y) - Math.round(pixelPos.y)); - } + // For each pixel in the source image that overlaps the tile... + for (var row = sourceStart.y; row < sourceEnd.y; row++) { + for (var col = sourceStart.x; col < sourceEnd.x; col++) { + // ...extract the pixel data... + var i = (row * w + col) * 4; + var r = imgData[i]; + var g = imgData[i + 1]; + var b = imgData[i + 2]; + var a = imgData[i + 3]; + ctx.fillStyle = "rgba(" + [r, g, b, a / 255].join(",") + ")"; + + // ...calculate the corresponding pixel coord in the dest image + // where it should be drawn... + var pixelPos = { + x: (col / imgRes.x + topLeft.x - tilePoint.x) * 256, + y: (row / imgRes.y + topLeft.y - tilePoint.y) * 256 + }; + + // ...and draw a rectangle there. + ctx.fillRect(Math.round(pixelPos.x), Math.round(pixelPos.y), + // Looks crazy, but this is necessary to prevent rounding from + // causing overlap between this rect and its neighbors. The + // minuend is the location of the next pixel, while the + // subtrahend is the position of the current pixel (to turn an + // absolute coordinate to a width/height). Yes, I had to look + // up minuend and subtrahend. + Math.round(pixelPos.x + pixelSize.x) - Math.round(pixelPos.x), Math.round(pixelPos.y + pixelSize.y) - Math.round(pixelPos.y)); } } - }(); - - if ((typeof _ret8 === "undefined" ? "undefined" : _typeof(_ret8)) === "object") return _ret8.v; + } } catch (e) { error = e; } finally { @@ -2458,7 +2465,7 @@ methods.removeMeasure = function () { }; methods.addSelect = function (ctGroup) { - var _this10 = this; + var _this8 = this; methods.removeSelect.call(this); @@ -2469,32 +2476,30 @@ methods.addSelect = function (ctGroup) { title: "Make a selection", onClick: function onClick(btn, map) { btn.state("select-active"); - _this10._locationFilter = new _leaflet2.default.LocationFilter2(); + _this8._locationFilter = new _leaflet2.default.LocationFilter2(); if (ctGroup) { - (function () { - var selectionHandle = new global.crosstalk.SelectionHandle(ctGroup); - selectionHandle.on("change", function (e) { - if (e.sender !== selectionHandle) { - if (_this10._locationFilter) { - _this10._locationFilter.disable(); - btn.state("select-inactive"); - } + var selectionHandle = new global.crosstalk.SelectionHandle(ctGroup); + selectionHandle.on("change", function (e) { + if (e.sender !== selectionHandle) { + if (_this8._locationFilter) { + _this8._locationFilter.disable(); + btn.state("select-inactive"); } - }); - var handler = function handler(e) { - _this10.layerManager.brush(_this10._locationFilter.getBounds(), { sender: selectionHandle }); - }; - _this10._locationFilter.on("enabled", handler); - _this10._locationFilter.on("change", handler); - _this10._locationFilter.on("disabled", function () { - selectionHandle.close(); - _this10._locationFilter = null; - }); - })(); + } + }); + var handler = function handler(e) { + _this8.layerManager.brush(_this8._locationFilter.getBounds(), { sender: selectionHandle }); + }; + _this8._locationFilter.on("enabled", handler); + _this8._locationFilter.on("change", handler); + _this8._locationFilter.on("disabled", function () { + selectionHandle.close(); + _this8._locationFilter = null; + }); } - _this10._locationFilter.addTo(map); + _this8._locationFilter.addTo(map); } }, { stateName: "select-active", @@ -2502,9 +2507,9 @@ methods.addSelect = function (ctGroup) { title: "Dismiss selection", onClick: function onClick(btn, map) { btn.state("select-inactive"); - _this10._locationFilter.disable(); + _this8._locationFilter.disable(); // If explicitly dismissed, clear the crosstalk selections - _this10.layerManager.unbrush(); + _this8.layerManager.unbrush(); } }] }); @@ -2546,7 +2551,6 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons // pixel of the original image has some contribution to the downscaled image) // as opposed to a single-step downscaling which will discard a lot of data // (and with sparse images at small scales can give very surprising results). - var Mipmapper = function () { function Mipmapper(img) { _classCallCheck(this, Mipmapper); diff --git a/javascript/src/methods.js b/javascript/src/methods.js index 6e22ea0db..99a0550c2 100644 --- a/javascript/src/methods.js +++ b/javascript/src/methods.js @@ -477,6 +477,45 @@ methods.addPolylines = function(polygons, layerId, group, options, popup, popupO } }; + +methods.setStyle = function(category, layerId, style){ + var map = this; + if (!layerId){ + return; + } else if (!(typeof(layerId) === "object" && layerId.length)){ // in case a single layerid is given + layerId = [layerId]; + } + + //convert columnstore to row store + style = HTMLWidgets.dataframeToD3(style); + //console.log(style); + + layerId.forEach(function(d,i){ + var layer = map.layerManager.getLayer(category, d); + if (layer){ // or should this raise an error? + layer.setStyle(style[i]); + } + }); +}; + +methods.setRadius = function(layerId, radius){ + var map = this; + if (!layerId){ + return; + } else if (!(typeof(layerId) === "object" && layerId.length)){ // in case a single layerid is given + layerId = [layerId]; + radius = [radius]; + } + + layerId.forEach(function(d,i){ + var layer = map.layerManager.getLayer("marker", d); + if (layer){ // or should this raise an error? + layer.setRadius(radius[i]); + } + }); +}; + + methods.removeMarker = function(layerId) { this.layerManager.removeLayer("marker", layerId); }; diff --git a/man/map-layers.Rd b/man/map-layers.Rd index 01e1a94b2..eb0323033 100644 --- a/man/map-layers.Rd +++ b/man/map-layers.Rd @@ -8,11 +8,14 @@ \alias{addMarkers} \alias{addLabelOnlyMarkers} \alias{addCircleMarkers} +\alias{setCircleMarkerRadius} +\alias{setCircleMarkerStyle} \alias{highlightOptions} \alias{addCircles} \alias{addPolylines} \alias{addRectangles} \alias{addPolygons} +\alias{setShapeStyle} \alias{addGeoJSON} \alias{addTopoJSON} \title{Graphics elements and layers} @@ -51,6 +54,13 @@ addCircleMarkers(map, lng = NULL, lat = NULL, radius = 10, options = pathOptions(), clusterOptions = NULL, clusterId = NULL, data = getMapData(map)) +setCircleMarkerRadius(map, layerId, radius, data = getMapData(map)) + +setCircleMarkerStyle(map, layerId, radius = NULL, stroke = NULL, + color = NULL, weight = NULL, opacity = NULL, fill = NULL, + fillColor = NULL, fillOpacity = NULL, dashArray = NULL, + options = NULL, data = getMapData(map)) + highlightOptions(stroke = NULL, color = NULL, weight = NULL, opacity = NULL, fill = NULL, fillColor = NULL, fillOpacity = NULL, dashArray = NULL, bringToFront = NULL, @@ -88,6 +98,11 @@ addPolygons(map, lng = NULL, lat = NULL, layerId = NULL, options = pathOptions(), highlightOptions = NULL, data = getMapData(map)) +setShapeStyle(map, data = getMapData(map), layerId, stroke = NULL, + color = NULL, weight = NULL, opacity = NULL, fill = NULL, + fillColor = NULL, fillOpacity = NULL, dashArray = NULL, + smoothFactor = NULL, noClip = NULL, options = NULL) + addGeoJSON(map, geojson, layerId = NULL, group = NULL, stroke = TRUE, color = "#03F", weight = 5, opacity = 0.5, fill = TRUE, fillColor = color, fillOpacity = 0.2, dashArray = NULL, @@ -233,6 +248,10 @@ Add graphics elements and layers to the map widget. \item \code{addCircleMarkers}: Add circle markers to the map +\item \code{setCircleMarkerRadius}: Change radius of rendered circlemarkers. + +\item \code{setCircleMarkerStyle}: Change radius of rendered circlemarkers. + \item \code{highlightOptions}: Options to highlight a shape on hover \item \code{addCircles}: Add circles to the map @@ -243,6 +262,8 @@ Add graphics elements and layers to the map widget. \item \code{addPolygons}: Add polygons to the map +\item \code{setShapeStyle}: Change style of rendered layers (leafletproxy) + \item \code{addGeoJSON}: Add GeoJSON layers to the map \item \code{addTopoJSON}: Add TopoJSON layers to the map diff --git a/man/map-shiny.Rd b/man/map-shiny.Rd index 5ee740b98..8b104dea5 100644 --- a/man/map-shiny.Rd +++ b/man/map-shiny.Rd @@ -16,7 +16,7 @@ renderLeaflet(expr, env = parent.frame(), quoted = FALSE) \code{\link[htmlwidgets]{shinyWidgetOutput}})} \item{expr}{An expression that generates an HTML widget (or a -\href{https://rstudio.github.io/promises/}{promise} of an HTML widget).} +\href{{https://rstudio.github.io/promises/}{promise}} of an HTML widget).} \item{env}{The environment in which to evaluate \code{expr}.}