I'm working on a map using Google Charts.
When someone clicks on a region every region change opacity while the clicked one keeps the original color.
It's exactly like this but for regions:
https://developers.google.com/chart/interactive/docs/gallery/columnchart#creating-material-column-charts
Do you guys know where to begin ? I can retrieve the current item selected, it's easy... but now I have to retrieve every item but the selected one and change the color of them.
Thanks in advance.
using the colorAxis config option,
assign a higher number to the selected region
reset the remaining regions back to zero
see following working snippet...
google.charts.load('current', {
callback: function () {
var data = google.visualization.arrayToDataTable([
['Country', 'Popularity'],
['England', 0],
['Wales', 0],
['Scotland', 0],
['Ireland', 0],
]);
var options = {
colorAxis: {
minValue: 0,
colors: ['#FFEBEE', '#B71C1C']
},
region: 'GB',
resolution: 'provinces'
};
var chart = new google.visualization.GeoChart(document.getElementById('chart_div'));
google.visualization.events.addListener(chart, 'select', function () {
for (var i = 0; i < data.getNumberOfRows(); i++) {
if (i === chart.getSelection()[0].row) {
data.setValue(i, 1, 100);
} else {
data.setValue(i, 1, 0);
}
}
chart.draw(data, options);
});
chart.draw(data, options);
},
packages:['geochart']
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://www.google.com/jsapi"></script>
<div id="chart_div"></div>
Related
I'm using OpenLayers V4 and I'm trying to see if it's possible to allow a user to click on a feature's vector label and move/drag it to a location of their choice. My initial thought was to capture when a user clicked on the label, and then dynamically calculate and set the offsetX and offsetY properties of the label (ol.style.Text) as the user's mouse pointer moved around. To achieve this, I need to capture when the user clicks on the label and not the feature itself. The main problem is that I can't find a way to distinguish this. It appears as though the label is part of the vector feature because clicking on the feature highlights both the feature and the label and vice versa.
In summary, my question is two fold:
Does anyone have an idea how to create a user draggable vector label in OpenLayers 4?
Is there a way to detect/distinguish between a user clicking on a vector feature itself, or the vector label.
Note: I'm familiar with overlays and realize they might be easier to work with since they have a setPosition property, but the way my web map is constructed I need to display vector labels for each feature and not overlays
It is possible using vector labels in OpenLayers 6 where the modify interaction has access to the features being modified in its style function and can use hit detection of offset labels, but that is not available in version 4. In this example labels move with their features, but can also be moved independently of the features. Clones are needed to avoid changing feature geometries while moving the labels. The labels geometries are then restored and replaced with offsets for styling. When a feature is moved its label clone is kept in sync.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io#master/en/v6.5.0/css/ol.css" type="text/css">
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io#master/en/v6.5.0/build/ol.js"></script>
<style>
html, body, .map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map" class="map"></div>
<script>
var white = [255, 255, 255, 1];
var blue = [0, 153, 255, 1];
var width = 3;
var modifyStyle = new ol.style.Style({
image: new ol.style.Circle({
radius: width * 2,
fill: new ol.style.Fill({
color: blue
}),
stroke: new ol.style.Stroke({
color: white,
width: width / 2
})
}),
zIndex: Infinity
});
var labelStyle = new ol.style.Style({
text: new ol.style.Text({
offsetY: 10,
font: '12px Calibri,sans-serif',
fill: new ol.style.Fill({
color: '#000',
}),
stroke: new ol.style.Stroke({
color: '#fff',
width: 3,
}),
backgroundFill: new ol.style.Fill({
color: 'rgba(0 ,0, 0, 0)',
}),
}),
});
var featureLayer = new ol.layer.Vector({
source: new ol.source.Vector({
url: 'https://mikenunn.net/data/world_cities.geojson',
format: new ol.format.GeoJSON(),
}),
});
var labelLayer = new ol.layer.Vector({
source: new ol.source.Vector(),
renderBuffer: 1e3,
style: function (feature) {
labelStyle.getText().setOffsetX(feature.get('offsetX') || 0);
labelStyle.getText().setOffsetY((feature.get('offsetY') || 0) - 10);
labelStyle.getText().setText(feature.get('CITY_NAME'));
return labelStyle;
},
});
featureLayer.getSource().on('addfeature', function(event) {
var id = event.feature.getId();
var feature = event.feature.clone();
feature.setId(id);
labelLayer.getSource().addFeature(feature);
});
featureLayer.getSource().on('removefeature', function(event) {
var id = event.feature.getId();
var source = labelLayer.getSource();
source.removeFeature(source.getFeatureById(id));
});
var defaultStyle = new ol.interaction.Modify({
source: featureLayer.getSource()
}).getOverlay().getStyleFunction();
var featureModify = new ol.interaction.Modify({
source: featureLayer.getSource(),
style: function(feature) {
feature.get('features').forEach( function(modifyFeature) {
var id = modifyFeature.getId();
var geometry = feature.getGeometry().clone();
labelLayer.getSource().getFeatureById(id).setGeometry(geometry);
});
return defaultStyle(feature);
}
});
var labelModify = new ol.interaction.Modify({
source: labelLayer.getSource(),
hitDetection: labelLayer,
style: function(feature) {
var styleFeature;
feature.get('features').forEach( function(modifyFeature) {
var id = modifyFeature.getId();
styleGeometry = featureLayer.getSource().getFeatureById(id).getGeometry();
});
modifyStyle.setGeometry(styleGeometry);
return modifyStyle;
}
});
labelModify.on('modifyend', function(event) {
event.features.forEach( function(feature) {
var id = feature.getId();
var labelCoordinates = feature.getGeometry().getCoordinates();
var geometry = featureLayer.getSource().getFeatureById(id).getGeometry().clone();
var featureCoordinates = geometry.getCoordinates();
var resolution = map.getView().getResolution();
var offsetX = (labelCoordinates[0] - featureCoordinates[0]) / resolution + (feature.get('offsetX') || 0);
var offsetY = (featureCoordinates[1] - labelCoordinates[1]) / resolution + (feature.get('offsetY') || 0);
feature.set('offsetX', offsetX, true);
feature.set('offsetY', offsetY, true);
feature.setGeometry(geometry);
});
});
var map = new ol.Map({
layers: [featureLayer, labelLayer],
interactions: ol.interaction.defaults().extend([labelModify, featureModify]),
target: 'map',
view: new ol.View({
center: ol.proj.fromLonLat([5, 51]),
zoom: 8
})
});
</script>
</body>
</html>
I added a markers by adding layer in OpenLayers.
I use it in my asp project and I load a map and I can add multi icons.
Now I load my locations and show them by that icons.
My code is like this:
for (var i = 0; i < loc.length; i++) {
LocationArray.push(loc[i]);
iconFeature = new ol.Feature({
geometry: new ol.geom.Point(ol.proj.fromLonLat(loc[i])),
name: 'Null Island',
population: 4000,
rainfall: 500
});
iconFeatures.pop();
debugger
iconFeatures.push(iconFeature);
vectorSource = new ol.source.Vector({
features: iconFeatures //add an array of features
});
iconStyle = new ol.style.Style({
image: new ol.style.Icon(({
anchor: [0.5, 100],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
opacity: 0.8,
src: '/images/icon.png',
}))
});
vectorLayer = new ol.layer.Vector({
source: vectorSource,
style: iconStyle
});
map.addLayer(vectorLayer);
}
Now I want to Delete this layer when I click on it.
I can detect where is it clicked and I response it but I don't know how I can delete it.
My code is like this:
map.on('click', function (evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) {
return feature;
});
if (feature) {
var coordinates = feature.getGeometry().getCoordinates();
if (confirm("Do you want to delete?")) {
// What should I code here?
}
} else {
// .....
}
});
The forEachFeatureAtPixel callback can also receive the layer, if you save that it is easy to remove the layer:
map.on('click', function (evt) {
var vectorLayer;
var feature = map.forEachFeatureAtPixel(evt.pixel, function (feature, layer) {
vectorLayer = layer;
return feature;
});
if (feature) {
var coordinates = feature.getGeometry().getCoordinates();
if (confirm("Do you want to delete?")) {
map.removeLayer(vectorLayer);
}
} else {
// .....
}
});
If you want to keep the layer but remove the markers you can use the clear() method.
vectorLayer.getSource().clear();
Thanks for response
I've found it by map.removeLayer(layer); code
I have a Google chart. For example (from the Google Documentation):
<html>
<head>
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load('current', {'packages':['corechart']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Year', 'Sales', 'Expenses'],
['2004', 1000, 400],
['2005', 1170, 460],
['2006', 660, 1120],
['2007', 1030, 540]
]);
var options = {
title: 'Company Performance',
curveType: 'function',
legend: { position: 'bottom' }
};
var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));
chart.draw(data, options);
}
</script>
</head>
<body>
<div id="curve_chart" style="width: 900px; height: 500px"></div>
</body>
</html>
I want to change the font and weight of one of the legends, say Sales. I can't figure out how to do that. Is there an easy way?
there are no options out of the box, to change a specific label.
for each type of label, there is a textStyle option.
but again, this will change all the labels of that type.
for instance, to change all the legend labels, use --> legend.textStyle
legend: {
textStyle: {
bold: true,
color: 'cyan',
fontSize: 18
}
}
however, we can manually make changes to the chart, once it has finished drawing,
during the 'ready' event.
see following working snippet, here, we find the labels used in the chart,
compare those with the column headings in the data table,
if found, we change the style, depending on the column index...
google.charts.load('current', {
packages: ['corechart']
}).then(function () {
var data = google.visualization.arrayToDataTable([
['Year', 'Sales', 'Expenses'],
['2004', 1000, 400],
['2005', 1170, 460],
['2006', 660, 1120],
['2007', 1030, 540]
]);
var options = {
title: 'Company Performance',
curveType: 'function',
legend: {
position: 'bottom',
textStyle: {
bold: true,
color: 'cyan',
fontSize: 18
}
}
};
var chart = new google.visualization.LineChart(document.getElementById('curve_chart'));
// listen for ready event, must be assigned before calling draw
google.visualization.events.addListener(chart, 'ready', function () {
// get text elements from chart
var labels = chart.getContainer().getElementsByTagName('text');
// loop chart labels
Array.prototype.forEach.call(labels, function(label) {
// loop data table columns
for (var i = 1; i < data.getNumberOfColumns(); i++) {
// determine if label matches legend entry
if (label.textContent === data.getColumnLabel(i)) {
// determine column index
switch (i) {
// first series
case 1:
label.setAttribute('fill', 'magenta');
label.setAttribute('font-size', '24');
label.setAttribute('font-weight', 'normal');
break;
// second series
case 2:
label.setAttribute('fill', 'lime');
label.setAttribute('font-size', '12');
break;
}
}
}
});
});
chart.draw(data, options);
});
<script src="https://www.gstatic.com/charts/loader.js"></script>
<div id="curve_chart"></div>
I'm using this code to capture the co-ordinates when user clicks on the map by using below event listener:
google.maps.event.addListener(map, 'click', function(event) {
placeMarker(event.latLng);
});
However this function doesn't get called when user click on already marked location in Map.
Meaning this function is not called for points where mouse pointer changes to hand icon on Google Map.
Need help on capturing these kind of locations.
You should add the click listener on marker will give you the position of marker.
//Add listener
google.maps.event.addListener(marker, "click", function (event) {
var latitude = event.latLng.lat();
var longitude = event.latLng.lng();
console.log( latitude + ', ' + longitude );
}); //end addListener
Edit:
You need something like this
//Add listener
google.maps.event.addListener(marker, "click", function (event) {
var latitude = event.latLng.lat();
var longitude = event.latLng.lng();
console.log( latitude + ', ' + longitude );
radius = new google.maps.Circle({map: map,
radius: 100,
center: event.latLng,
fillColor: '#777',
fillOpacity: 0.1,
strokeColor: '#AA0000',
strokeOpacity: 0.8,
strokeWeight: 2,
draggable: true, // Dragable
editable: true // Resizable
});
// Center of map
map.panTo(new google.maps.LatLng(latitude,longitude));
}); //end addListener
Another solution is to place a polygon over the map, same size as the map rectangle, and collect this rectangles clicks.
function initialize() {
var mapDiv = document.getElementById('map-canvas');
var map = new google.maps.Map(mapDiv, {
center: new google.maps.LatLng(37.4419, -122.1419),
zoom: 13,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
google.maps.event.addListener(map, 'bounds_changed', function() {
var lat1 = 37.41463623043073;
var lat2 = 37.46915383933881;
var lng1 = -122.1848153442383;
var lng2 = -122.09898465576174;
var rectangle = new google.maps.Polygon({
paths : [
new google.maps.LatLng(lat1, lng1),
new google.maps.LatLng(lat2, lng1),
new google.maps.LatLng(lat2, lng2),
new google.maps.LatLng(lat1, lng2)
],
strokeOpacity: 0,
fillOpacity : 0,
map : map
});
google.maps.event.addListener(rectangle, 'click', function(args) {
console.log('latlng', args.latLng);
});
});
}
Now you get LatLng's for places of interest (and their likes) also.
demo -> http://jsfiddle.net/qmhku4dh/
You're talking about the Point of Interest icons that Google puts on the map.
Would it work for you to remove these icons entirely? You can do that with a Styled Map. To see what this would look like, open the Styled Map Wizard and navigate the map to the area you're interested in.
Click Point of interest under Feature type, and then click Labels under Element type. Finally, click Visibility under Stylers and click the Off radio button under that.
This should remove all of the point of interest icons without affecting the rest of the map styling. With those gone, clicks there will respond to your normal map click event listener.
The Map Style box on the right should show:
Feature type: poi
Element type: labels
Visibility: off
If the result looks like what you want, then click Show JSON at the bottom of the Map Style box. The resulting JSON should like this this:
[
{
"featureType": "poi",
"elementType": "labels",
"stylers": [
{ "visibility": "off" }
]
}
]
You can use that JSON (really a JavaScript object literal) using code similar to the examples in the Styled Maps developer's guide. Also see the MapTypeStyle reference for a complete list of map styles.
This example demonstrates the use of click event listeners on POIs (points of interest). It listens for the click event on a POI icon and then uses the placeId from the event data with a directionsService.route request to calculate and display a route to the clicked place. It also uses the placeId to get more details of the place.
Read the google documentation.
<!DOCTYPE html>
<html>
<head>
<title>POI Click Events</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<style>
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
.title {
font-weight: bold;
}
#infowindow-content {
display: none;
}
#map #infowindow-content {
display: inline;
}
</style>
</head>
<body>
<div id="map"></div>
<div id="infowindow-content">
<img id="place-icon" src="" height="16" width="16">
<span id="place-name" class="title"></span><br>
Place ID <span id="place-id"></span><br>
<span id="place-address"></span>
</div>
<script>
function initMap() {
var origin = {lat: -33.871, lng: 151.197};
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 18,
center: origin
});
var clickHandler = new ClickEventHandler(map, origin);
}
/**
* #constructor
*/
var ClickEventHandler = function(map, origin) {
this.origin = origin;
this.map = map;
this.directionsService = new google.maps.DirectionsService;
this.directionsDisplay = new google.maps.DirectionsRenderer;
this.directionsDisplay.setMap(map);
this.placesService = new google.maps.places.PlacesService(map);
this.infowindow = new google.maps.InfoWindow;
this.infowindowContent = document.getElementById('infowindow-content');
this.infowindow.setContent(this.infowindowContent);
// Listen for clicks on the map.
this.map.addListener('click', this.handleClick.bind(this));
};
ClickEventHandler.prototype.handleClick = function(event) {
console.log('You clicked on: ' + event.latLng);
// If the event has a placeId, use it.
if (event.placeId) {
console.log('You clicked on place:' + event.placeId);
// Calling e.stop() on the event prevents the default info window from
// showing.
// If you call stop here when there is no placeId you will prevent some
// other map click event handlers from receiving the event.
event.stop();
this.calculateAndDisplayRoute(event.placeId);
this.getPlaceInformation(event.placeId);
}
};
ClickEventHandler.prototype.calculateAndDisplayRoute = function(placeId) {
var me = this;
this.directionsService.route({
origin: this.origin,
destination: {placeId: placeId},
travelMode: 'WALKING'
}, function(response, status) {
if (status === 'OK') {
me.directionsDisplay.setDirections(response);
} else {
window.alert('Directions request failed due to ' + status);
}
});
};
ClickEventHandler.prototype.getPlaceInformation = function(placeId) {
var me = this;
this.placesService.getDetails({placeId: placeId}, function(place, status) {
if (status === 'OK') {
me.infowindow.close();
me.infowindow.setPosition(place.geometry.location);
me.infowindowContent.children['place-icon'].src = place.icon;
me.infowindowContent.children['place-name'].textContent = place.name;
me.infowindowContent.children['place-id'].textContent = place.place_id;
me.infowindowContent.children['place-address'].textContent =
place.formatted_address;
me.infowindow.open(me.map);
}
});
};
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap"
async defer></script>
</body>
</html>
If you are using npm load-google-maps-api with webpack this worked for me:
const loadGoogleMapApi = require("load-google-maps-api");
loadGoogleMapApi({ key: process.env.GOOGLE_MAP_API_KEY }).then(map => {
let mapCreated = new map.Map(mapElem, {
center: { lat: lat, lng: long },
zoom: 7
});
mapCreated.addListener('click', function(e) {
console.log(e.latLng.lat()); // this gives you access to the latitude value of the click
console.log(e.latLng.lng()); // gives you access to the latitude value of the click
var marker = new map.Marker({
position: e.latLng,
map: mapCreated
});
mapCreated.panTo(e.latLng); // finally this adds red marker to the map on click.
});
});
Next if you are integrating openweatherMap in your app you can use the value of e.latLng.lat() and e.latLng.lng() which I console logged above in your api request. This way:
http://api.openweathermap.org/data/2.5/weather?lat=${e.latLng.lat()}&lon=${e.latLng.lng()}&APPID=${YOUR_API_KEY}
I hope this helps someone as it helped me.
Cheers!
I want to enable drawing on Google Maps like (see this example)
When user finish with drawings he will click on save button to save his drawings in Database or KML file, anything :) .. I do not know how to the save part? Could anyone help me
Here, http://jsfiddle.net/X66L4/1/ try drawing some circles, click on SAVE, then edit the circles by switching to the hand cursor and SAVE again to see the changes.
I show an example to save circles' data, the main idea is to keep a global array for each drawing type (line, polygon, marker, circle), and use a listener on the drawing manager to detect each type being drawn (complete).
var circles = [];
google.maps.event.addDomListener(drawingManager, 'circlecomplete',
function(circle) {
circles.push(circle);
});
The reason to save the entire reference to the drawn object is to continue tracking changes. So you will need an array and listener for each type of drawing.
Then, when you want to save the data (you may wish to do so at every edit), iterate through the arrays and extract the minimum information to rebuild it (center, radius, path, latLng, and so on.)
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
html, body, #map_canvas { margin: 0; padding: 0; height: 100% }
</style>
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false&libraries=drawing"></script>
<script type="text/javascript">
var myOptions = {
center: new google.maps.LatLng(-25,177.5),
zoom: 3,
mapTypeId: google.maps.MapTypeId.SATELLITE
};
var map;
function initialize() {
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
var drawingManager = new google.maps.drawing.DrawingManager({
drawingMode: google.maps.drawing.OverlayType.CIRCLE,
drawingControl: true,
drawingControlOptions: {
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: [google.maps.drawing.OverlayType.CIRCLE]
},
circleOptions: {
editable: true
}
});
drawingManager.setMap(map);
var circles = [];
google.maps.event.addDomListener(drawingManager, 'circlecomplete', function(circle) {
circles.push(circle);
});
google.maps.event.addDomListener(savebutton, 'click', function() {
document.getElementById("savedata").value = "";
for (var i = 0; i < circles.length; i++) {
var circleCenter = circles[i].getCenter();
var circleRadius = circles[i].getRadius();
document.getElementById("savedata").value += "circle((";
document.getElementById("savedata").value +=
circleCenter.lat().toFixed(3) + "," + circleCenter.lng().toFixed(3);
document.getElementById("savedata").value += "), ";
document.getElementById("savedata").value += circleRadius.toFixed(3) + ")\n";
}
});
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<button id="savebutton">SAVE</button>
<textarea id="savedata" rows="8" cols="40"></textarea>
<div id="map_canvas"></div>
</body>
</html>
In my experience, it's easier to use map's dataLayer then the drawing manager.
Try out this fiddle.
FiddleLink
Showing the controls:
map.data.setControls(['Polygon']);
map.data.setStyle({
editable: true,
draggable: true
});
in this function you can see the Create, Read (localStorage) and Remove (not in that order):
function loadPolygons(map) {
var data = JSON.parse(localStorage.getItem('geoData'));
map.data.forEach(function (f) {
map.data.remove(f);
});
console.log(data);
map.data.addGeoJson(data)
}