I would like to know, how to color the clusters depending on icon in it.
My Data :
remorque time.stamp lat long geolocalisation maintenance temperature appairage
1 21/11/2017 10:36 48.86272 2.2875920 OnMouv noir
2 21/11/2017 10:36 43.60776 1.4421606 StartMouv rouge
3 21/11/2017 10:36 46.58619 0.3388710 OnMouv rouge
4 21/11/2017 10:36 45.76695 3.0556216 Life orange
5 21/11/2017 10:36 45.14555 1.4751652 EndMouv rouge
6 21/11/2017 10:36 46.81157 1.6936336 Life orange
7 21/11/2017 10:36 47.36223 0.6751146 alerte rouge
8 21/11/2017 10:36 47.36032 1.7441244 StartMouv
9 21/11/2017 10:36 48.85333 1.8215332 StartMouv
10 21/11/2017 10:36 48.84429 1.7913208 alerte
11 21/11/2017 10:36 48.81356 1.6759643 EndMouv
Example :
If there is an icon in my cluster, with appairage = rouge, the color of the cluster should be red.
If there is no red icon, If there is an icon in my cluster, with temperature = orange, the color of the cluster should be orange.
... for each variable (temperature, appairage, maintenance). And if all icons in the culster have their variables ok, the cluster should be green.
My map looks like :
I found a way to change the range for coloring the clusters on the Internet. But I don't want to color per number of markers in the cluster.
It is possible to color the clustered icons based on the properties of the icons clustered together. The easiest method might be to use htmlwidgets and call a javascript function on map rendering.
However, before getting to the htmlwidget, you need to set a clusterId for your clustered layer:
addAwesomeMarkers(clusterId = "cluster" ...
Now we can find this layer when in the htmlwidget:
function(el, x) {
map = this; // the map object
var cluster = map.layerManager.getLayer('cluster','cluster'); // the cluster layer
Within the cluster layer, we want to create a function for the icon property iconCreateFunction:
cluster.options.iconCreateFunction = function(d) {
// generate icon
}
This function should:
go through all the child markers represented by the clustered marker,
identify the highest rank of those child markers
return an appropriate icon
1. Iterating through child markers
For number one, and building on the above, we can iterate through each child marker with:
cluster.options.iconCreateFunction = function(c) {
var markers = c.getAllChildMarkers();
markers.forEach(function(m) {
// do something for each marker
})
}
I'm using c to represent a cluster, m to represent each individual child marker
2. Getting the Highest Ranked Marker
The primary challenge in the list is identifying the highest rank of the child icons - as the data is not bound to the icons we are limited in options. Assuming that the color of the icons corresponds to the color code of the item in the dataframe, I will use the color of the icon to determine its priority/rank. After determining the highest ranking child, I will color the cluster based on that child's rank.
I will color the cluster as follows (as I believe this is your intended result):
red if any child icons are red,
orange if none are red but there are some orange children, and
green if there is no orange or red children.
To get the color, I need to access the proper property. The color (fill) of an (awesome) marker resides at:
marker.options.icon.options.markerColor
To compare colors, I'll use an object to represent each color's rank, this will allow for a simple comparison of color:
var priority = {
'green':0,
'orange':1,
'red':2
}
This allows:
cluster.options.iconCreateFunction = function(c) {
var markers = c.getAllChildMarkers();
var priority = {
'green': 0,
'orange': 1,
'red': 2
};
var highestRank = 0; // defaults to the lowest level to start
markers.forEach(function(m) {
var color = m.options.icon.options.markerColor;
// check each marker to see if it is the highest value
if(priority[color] > highestRank) {
highestRank = priority[color];
}
})
}
3. Returning an Icon
Now that we have a value representing a color, we can return an icon. Leaflet clustered icons have limited styling options. They use L.divIcon(), which limits options somewhat. When combined with css styles for clustered labels, they create the familiar circle with green, yellow, and orange colors.
These default styles have the following css classes:
.marker-cluster-small // green
.marker-cluster-medium // yellow
.marker-cluster-large // orange
If we are happy with just using these classes, we can style the clustered polygons with minimal effort:
var styles = [
'marker-cluster-small', // green
'marker-cluster-medium', // yellow
'marker-cluster-large' // orange
]
var style = styles[highestRank];
var count = markers.length;
return L.divIcon({ html: '<div><span>'+count+'</span></div>', className: 'marker-cluster ' + style, iconSize: new L.Point(40, 40) });
The whole widget therefore looks like:
function(el,x) {
map = this;
var cluster = map.layerManager.getLayer('cluster','cluster');
cluster.options.iconCreateFunction = function(c) {
var markers = c.getAllChildMarkers();
var priority = {
'green': 0,
'orange': 1,
'red': 2
};
var highestRank = 0; // defaults to the lowest level to start
markers.forEach(function(m) {
var color = m.options.icon.options.markerColor;
// check each marker to see if it is the highest value
if(priority[color] > highestRank) {
highestRank = priority[color];
}
})
var styles = [
'marker-cluster-small', // green
'marker-cluster-medium', // yellow
'marker-cluster-large' // orange
]
var style = styles[highestRank];
var count = markers.length;
return L.divIcon({ html: '<div><span>'+count+'</span></div>', className: 'marker-cluster ' + style, iconSize: new L.Point(40, 40) });
}
}
Refining the Icons
Changing Colors
You probably want to have the high priority icons show up red. This can be done, but you need to add a css style to your map.
One way to do this at the same time as changing the icon function above is to append a style to the page with javascript in your widget. You need to make two styles, one for the div holding the icon, and one for the icon, you can do both at once:
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '.red, .red div { background-color: rgba(255,0,0,0.6); }'; // set both at the same time
document.getElementsByTagName('head')[0].appendChild(style);
(from https://stackoverflow.com/a/1720483/7106086)
Don't forget to update what classes you are using in the styles array:
var styles = [
'marker-cluster-small', // green
'marker-cluster-medium', // yellow
'red' // red
]
Showing More Information in the Icon
You aren't limited to a number in the icon, you could show 1-3-5 , representing one high priority, three medium etc. You just need to keep track of how many child icons of each priority are in each cluster:
var children = [0,0,0];
markers.forEach(function(m) {
var color = m.options.icon.options.markerColor;
children[priority[color]]++; // increment the appropriate value in the children array.
...
Then show it with:
return L.divIcon({ html: '<div><span>'+children.reverse()+'</span>...
Givings something like:
Test Example
This should be copy and pastable to show everything except the additional text in the icon (using the code in these documentation examples as a base):
library(leaflet)
# first 20 quakes
df.20 <- quakes[1:50,]
getColor <- function(quakes) {
sapply(quakes$mag, function(mag) {
if(mag <= 4) {
"green"
} else if(mag <= 5) {
"orange"
} else {
"red"
} })
}
icons <- awesomeIcons(
icon = 'ios-close',
iconColor = 'black',
library = 'ion',
markerColor = getColor(df.20)
)
leaflet(df.20) %>% addTiles() %>%
addAwesomeMarkers(~long, ~lat, icon=icons, label=~as.character(mag), clusterOptions = markerClusterOptions(), group = "clustered", clusterId = "cluster") %>%
htmlwidgets::onRender("function(el,x) {
map = this;
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '.red, .red div { background-color: rgba(255,0,0,0.6); }'; // set both at the same time
document.getElementsByTagName('head')[0].appendChild(style);
var cluster = map.layerManager.getLayer('cluster','cluster');
cluster.options.iconCreateFunction = function(c) {
var markers = c.getAllChildMarkers();
var priority = {
'green': 0,
'orange': 1,
'red': 2
};
var highestRank = 0; // defaults to the lowest level to start
markers.forEach(function(m) {
var color = m.options.icon.options.markerColor;
// check each marker to see if it is the highest value
if(priority[color] > highestRank) {
highestRank = priority[color];
}
})
var styles = [
'marker-cluster-small', // green
'marker-cluster-large', // orange
'red' // red
]
var style = styles[highestRank];
var count = markers.length;
return L.divIcon({ html: '<div><span>'+count+'</span></div>', className: 'marker-cluster ' + style, iconSize: new L.Point(40, 40) });
}
}")
You can now also use the iconCreateFunction in markerClusterOptions. Make sure you pass the variable that you want to use for your coloring in the markerOptions .
see also: leaflet R, how to make appearance of clustered icon related to statistics of the children?
An example is:
legend_pal <- hcl.colors(10, palette='Spectral', rev = T)
leaflet(quakes) %>% addTiles() %>% addMarkers(
options = markerOptions(mag = ~mag),
clusterOptions = markerClusterOptions(
iconCreateFunction=~JS(paste0("function (cluster) {
var markers = cluster.getAllChildMarkers();
var sum = 0;
for (i = 0; i < markers.length; i++) {
sum += Number(markers[i].options.mag);
}
var palette = ['", paste0(legend_pal, collapse="','"),"'];
var domain = [", paste0(sort(unique(na.omit(mag))), collapse=','),"];
var count = markers.length;
var avg = sum/count;
c = palette[Math.round(palette.length*(avg-Math.min(...domain))/(Math.max(...domain) - Math.min(...domain)))];
return L.divIcon({
html: '<div style=\"background-color:'+c+'\"><span>'+avg+'</span></div>',
className: 'marker-cluster',
iconSize: new L.Point(40, 40) });
}")))) %>%
addLegend(pal=colorNumeric(
palette = legend_pal,
domain = quakes$mag,
na.color = 'transparent'), values = ~mag)
Related
I am new to google earth engine and not so familiar with javascript. I want to display the cleared images (B4,B3,B2 bands) of Sentinel 2 by each dates in layers (each layer represent each date). The code is shown as below, but always get error 'no Band 4, constant band'. Can anyone help me to solve this problem? Thanks!
var lakes=table.geometry();
Map.centerObject(lakes, 15);
function maskS2clouds(image) {
var qa = image.select('QA60');
// Bits 10 and 11 are clouds and cirrus, respectively.
var cloudBitMask = 1 << 10;
var cirrusBitMask = 1 << 11;
// Both flags should be set to zero, indicating clear conditions.
var mask = qa.bitwiseAnd(cloudBitMask).eq(0)
.and(qa.bitwiseAnd(cirrusBitMask).eq(0));
return image.updateMask(mask).divide(10000);
}
var start = ee.Date('2015-06-20');
var finish = ee.Date('2018-06-01');
var collection = ee.ImageCollection('COPERNICUS/S2')
.filterDate(start, finish)
.filterBounds(lakes)
.filter(ee.Filter.lt('CLOUDY_PIXEL_PERCENTAGE', 10))
.map(maskS2clouds);
var rgbVis = {
min: 0.0,
max: 0.3,
bands: ['B4', 'B3', 'B2'],
};
function addImage(imageL) { // display each image in collection
var id = imageL.id;
var image = ee.Image(imageL.id);
Map.addLayer(image.select(['B4','B3','B2']).clip(lakes),rgbVis,id)
}
collection.evaluate(function(collection) { // use map on client-side
print(collection.features);
collection.features.map(addImage);
})
I need to add multiple arcs to one svg elements (every one got different animation). I need to fill them with radial gradients, but now, the center of radial gradient is not in the centre of whole svg element, but in the centre of specific arc. How it looks now. First i make all of the gradients i need in defs.
var tmpgrad=null;
for( var k = 0; k<data.length;k++){
tmpgrad = grads
.append("radialGradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("r", "50%")
.attr("id", function(d, i) { return "grad" + k; });
tmpgrad
.append("stop")
.attr("offset", "0%")
.style("stop-color", data[k].endColor)
.style("stop-opasity", 0);
tmpgrad
.append("stop")
.attr("offset", "100%")
.style("stop-color",data[k].startColor)
.style("stop-opasity", 1);
}
Then I make my arcs with deferent options:
var width= 1200;
var height=360;
var oArc = d3.svg.arc()
.innerRadius(iRadius)
.outerRadius(oRadius);
var oPie = d3.layout.pie()
.startAngle( sA )
.endAngle( eA )
.sort(null);
var group = svgDrawer.append("g");
var oPath = group.selectAll("g")
.data(oPie([0,200]))
.enter()
.append("path");
oPath
.attr("fill",function(d, i){ if(i==1){ i=0; } else { i = id; } return "url(#grad" + i +")"; })
.attr("d",oArc)
.each(function(d){
this._current = d;
});
But in the result as i said before i got the separated arcs with gradient starting in the middle of every arc element (path actually). How can i solve that.
I am the beginner in svg and javascript subject.
Needing some help... i was able to find an example of a rotating globe, that works great, i even found a way to put red circles at a point. Even better to setup a timer and everything rotates with the globe great. But if i put text on the map at the same point as the red circles it shows up at the starting point that i placed it, but as the world turns the red circle moves with the globe, but the text is frozen at the points that it was written. i am trying to get the text to rotate with the world and the red circles. think in the country of united states i want to put a number, brazil would have number when the globe rotates to china the values would still be on the countries i put it and when it rotates US and Brazil back to the front the numbers are there showing. This is what i have in code, bear with me I am still a noob when working with D3. thanks for any input...
// Initialize some variables:
var element = '#home1',
width = $("#home1").width(),
height = $("#home1").height();
var diameter = 460,
radius = diameter/2,
velocity = .001,
then = Date.now();
var features, circles;
var projection = d3.geo.orthographic()
.scale(radius - 2)
.translate([radius, radius])
.clipAngle(90);
// Save the path generator for the current projection:
var path = d3.geo.path()
.projection(projection)
.pointRadius( function(d,i) {
return radius;
});
// Define the longitude and latitude scales, which allow us to map lon/lat coordinates to pixel values:
var lambda = d3.scale.linear()
.domain([0, width])
.range([-180, 180]);
var phi = d3.scale.linear()
.domain([0, height])
.range([90, -90]);
// Create the drawing canvas:
var svg = d3.select("#home1").append("svg:svg")
.attr("width", diameter)
.attr("height", diameter);
//Create a base circle: (could use this to color oceans)
var backgroundCircle = svg.append("svg:circle")
.attr('cx', diameter / 2)
.attr('cy', diameter / 2)
.attr('r', 0)
.attr('class', 'geo-globe');
// Make a tag to group all our countries, which is useful for zoom purposes. (child elements belong to a 'group', which we can zoom all-at-once)
var world = svg.append('svg:g');
var zoomScale = 1; // default
// Create the element group to mark individual locations:
var locations = svg.append('svg:g').attr('id', 'locations');
// Having defined the projection, update the backgroundCircle radius:
backgroundCircle.attr('r', projection.scale() );
// Construct our world map based on the projection:
d3.json('world-countries.json', function(collection) {
features = world.selectAll('path')
.data(collection.features)
.enter()
.append('svg:path')
.attr('class', 'geo-path')
.attr('d', path);
// features.append('svg:title')
// .text( function(d) { return d.properties.name; });
}); // end FUNCTION d3.json()
d3.json("data.geojson", function(collection) {
console.log("2");
cs = locations.selectAll('path')
.data(collection.features)
.enter().append('svg:path')
.datum(function(d) {return {type: "Point", coordinates: [d.geometry.coordinates[0], d.geometry.coordinates[1]]}; })
.attr('class', 'geo-node')
.attr("d", path.pointRadius(5))
.attr('d', path);
cs1 = locations.selectAll('text')
.data(collection.features)
.enter().append('svg:text')
.attr("transform", function(d) {return "translate(" + projection(d.geometry.coordinates) + ")"; })
.attr("dy", ".35em")
.attr('d', path)
.text(function(d) { return d.properties.name; });
}); // end FUNCTION d3.json()
d3.timer(function() {
if(offpage === 0)
{
var angle = velocity * (Date.now() - then);
projection.rotate([angle,0,0])
svg.selectAll("path").attr("d", path.projection(projection));
}
});
d3.select(window)
.on("touchmove", mousemove)
.on("touchstart", mousedown);
function mousemove() {
offpage = 0;
}
function mousedown() {
offpage=1
}
In your code, features(the world map) is a path, and cs(the city points) is a path, but cs1(the city names) is a text. In your timer you rotate the paths, which doesn't rotate the text.
My solution uses rotation degrees, instead of angle, so you'll have to adapt the formula.
d3.timer(function() {
tcounter++
rotation++
if (rotation>=360) rotation = 0
projection.rotate([rotation,0,0])
www.attr("d", path.projection(projection));
citydot.attr("d", path.projection(projection));
ctext.attr("transform", function(d) {
return "translate(" + projection(d.geometry.coordinates) + ")"; })
.text(function(d) {
if (((rotation + d.geometry.coordinates[0] > -90) && (rotation + d.geometry.coordinates[0] <90)) ||
((rotation + d.geometry.coordinates[0] > 270) && (rotation + d.geometry.coordinates[0] <450)))
return d.properties.city;
else return "" });
if (tcounter > 360) return true
else return false
})
Im using Paper.js to create a Swarm of Circles.
Do they all have have the same fillColor?
var p = paper.Path.Circle({
'center': [0,0],
'radius': 20,
'fillColor': 'red',
});
var symbol = paper.Symbol(p);
for ( var i = 0; i < size; i ++ ) {
var center = [0,0];
var placed = symbol.place(center);
placed.fillColor('#7FCAFF');
}
The fillColor is red anyway. If I do placed.scale() it works, but not editing the fillColor.
In the current version, the fillColor of a placedSymbol is dependent on the fillColor of the Symbol itself. The only values that you can change independently of the symbol definition are compositing values, such as opacity, scale, blend-mode, etc.
With google maps api2 I was drawing a circle using this code:
var markerPoint = currentMarker.getPoint();
var polyPoints = Array();
var mapNormalProj = G_NORMAL_MAP.getProjection();
var mapZoom = map.getZoom();
var clickedPixel = mapNormalProj.fromLatLngToPixel(markerPoint, mapZoom);
var polySmallRadius = 20;
var polyNumSides = 20;
var polySideLength = 18;
for (var a = 0; a<(polyNumSides+1); a++) {
var aRad = polySideLength*a*(Math.PI/180);
var polyRadius = polySmallRadius;
var pixelX = clickedPixel.x + 5 + polyRadius * Math.cos(aRad);
var pixelY = clickedPixel.y - 10 + polyRadius * Math.sin(aRad);
var polyPixel = new GPoint(pixelX,pixelY);
var polyPoint = mapNormalProj.fromPixelToLatLng(polyPixel,mapZoom);
polyPoints.push(polyPoint);
}
// Using GPolygon(points, strokeColor?, strokeWeight?, strokeOpacity?, fillColor?, fillOpacity?)
highlightCircle = new GPolygon(polyPoints,"#000000",2,0.0,"#FF0000",.5);
map.addOverlay(highlightCircle);
I've managed to transform this code to api3:
var markerPoint = currentMarker.getPosition();
var polyPoints = Array();
var mapNormalProj = map.getProjection();
var mapZoom = map.getZoom();
var clickedPixel = mapNormalProj.fromLatLngToPoint(markerPoint);
var polyRadius = 20;
var polyNumSides = 20;
var polySideLength = 18;
for (var a = 0; a<(polyNumSides+1); a++) {
var aRad = polySideLength*a*(Math.PI/180);
var pixelX = clickedPixel.x + 5 + (polyRadius * Math.cos(aRad));
var pixelY = clickedPixel.y - 10 + (polyRadius * Math.sin(aRad));
var polyPixel = new google.maps.Point(pixelX,pixelY);
var polyPoint = mapNormalProj.fromPointToLatLng(polyPixel);
polyPoints.push(polyPoint);
}
highlightCircle = new google.maps.Polygon({
paths: polyPoints,
strokeColor: "#FF0000",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#FF0000",
fillOpacity: 0.35
});
highlightCircle.setMap(map);
If you look more closely at the api3 example, the mapZoom variable is not used anywhere.
In api2, the code generates a small circle around my marker - around 35px radius. When I zoom into the map, the radius stays at 35px (because the zoom is taken into account).
With api3 on the other hand, I have a huge circle - more than 200px wide and when I zoom in, the circle becomes bigger and bigger.
It behaves the same way as the circle object available in api3.
What I want is just a small circle around my marker, that is not 100km in diameter, but just a few pixels around my marker (this circle acts like a hover element in html).
Any ideas how to achieve that?
You might have better luck using custom marker, not a circle. See "Vector Icon" from the documentation here: https://developers.google.com/maps/documentation/javascript/overlays#Icons
var marker = new google.maps.Marker({
position: new google.maps.LatLng(-25.363882, 131.044922),
icon: {
path: google.maps.SymbolPath.CIRCLE,
scale: 10
},
draggable: true,
map: map
});
You're doing the calculation based on the Point plane which plane remains the same no matter what zoom level you are at. You probably mean to do the calculation using pixels.
The methods you are looking for are here. fromLatLngToContainerPixel and fromContainerPixelToLatLng or fromLatLngToDivPixel and fromDivPixelToLatLng.
This means you should probably wrap up that code into an OverlayView and call getProjection() on your map to get the projection and then use one set of those methods to do the calculation.