click function to only execute on certain data names - javascript

I have this click:function for an interactive jQuery US map.
The below defines the "active color states" -- the color states turn after being clicked. I would like to alter to only work / execute on a few states I choose. Every state has a data.name with it's abbreviation. Eg. 'OH' 'CA' 'TX" 'NY' etc. The active color removes upon click another states (as is below) I just need the ability to have this work with ONLY my select chosen states, not all 50.
click: function(c, l) {
$("#map > svg > path").each(function() {
$(this).css("fill", "")
}),
$("#" + l.name).css("fill", "#ffc600"),

Define an array with allowed states var clickable = ['OH','CA','TX','NY']; and then use indexOf to see if the clicked state is in it..
Try
click: function(c,l){
var node = $(l.shape.node),
siblings = node.siblings('path');
siblings.css('fill','');
if (clickable.indexOf(l.name) > -1){
// select it
node.css('fill','green');
}
}
Demo at http://codepen.io/gpetrioli/pen/dPLGVg
You could do the styling with CSS, though
(example requires browsers the support the classList property)
click: function(c,l){
var active = $(l.shape.node)
.siblings('.selected')
.get(0);
// if there is an selected path
if (active) {
// de-select it
active.classList.remove('selected');
}
// if clicked node is in list of clickables
if (clickable.indexOf(l.name) > -1){
// select it
l.shape.node.classList.add('selected')
}
}
Demo at http://codepen.io/gpetrioli/pen/dPLGRg

How about using the configuration options of the USMap and using its stateSpecificStyles
$('#map').usmap({
stateSpecificStyles: {
'MD': {fill: '#ffc600'},
'VA': {fill: '#ffc600'}
}
});
You could then collect all clicked states and update the stateSpecificStyles accordingly:
var clickedStates = []; // array with states that have been clicked
click: function(c, l) {
// if state was clicked before no need for update - so check
if (clickedStates.indexOf(l.name) < 0) {
// remember clicked state
clickedStats.push(l.name);
// build new state styles
var styles = {};
for (var i=clickedStats.length-1; i >= 0; i--) {
styles[clickedStats[i]] = {fill: '#ffc600'};
}
// pass new style object to map
$('#map').usmap({
stateSpecificStyles: styles
});
}
}
That should change the color than of each map clicked and if you want to implement a different logic (clicking CA colors CA, NY and TX) then you simply add to the clickedStates array.
Hope that helped

Related

How to return specific Geojson feature property using another property, e.g. return longitude property by name?

I'm trying to create an interface with Mapbox that zooms to the polygon the user clicks on using this function while also updating a dropdown menu value in an non-map div.
When the user clicks on a polygon/town in the map, it will zoom to that correct feature by using this map.on('click...) script.
map.on('click', 'Map Layer', function (e) {
townnames = e.features[0].properties.Object_Name;
townlat = e.features[0].properties.Object_latitude;
townlong = e.features[0].properties.Object_longitude;
map.setFilter('LayerFiltered', ['==', 'TOWN_NAME', Object_Name]);
map.setFilter('LayerClicked', ['!=', 'TOWN_NAME', Object_Name]);
map.setLayoutProperty('LayerFiltered', 'visibility', 'visible');
map.flyTo({
center: [
townlong, townlat],
zoom : 11.5
});
However I'm trying to trigger the same function using the dropdown menu.
I've figured out how to filter the layer using the inner HTML value from options in the dropdown menu, and setting them equal to the "Object_Name" in setFilter. But is there any way to return other properties from the geojson associated with a feature based on one of its properties like "TOWN_NAME"?
In other words can I use a variable from the dropdown menu so instead of returning e.features[0].properties.Object_longitude when a user clicks on a feature, when they click on its associated value in the dropdown menu it will return something to the effect of-"features.TOWN_NAME=DROPDOWN_VALUE_NAME".properties.Object_longitude"?
I apologize if this is unclear.
The dropdown does not fire the load event. You have to break the work above into general methods to be called by a handler for the map and for the dropdown.
Edit: The drop down is not being assigned the click event and would need to be given a listener to call a function that is also called by the map objects click listener.
demo
// Add an event listener for the links in the sidebar listing
listings.addEventListener('change', function () {
// Update the currentFeature to the store associated with the clicked link
var clickedListing = data.features[listings.selectedIndex];
// 1. Fly to the point associated with the clicked link
flyToStore(clickedListing);
// 2. Close all other popups and display popup for clicked store
createPopUp(clickedListing);
});
}
// Add an event listener for when a user clicks on the map
map.on('click', function (e) {
// Query all the rendered points in the view
var features = map.queryRenderedFeatures(e.point, { layers: ['locations'] });
if (features.length) {
var clickedPoint = features[0];
// 1. Fly to the point
flyToStore(clickedPoint);
// 2. Close all other popups and display popup for clicked store
createPopUp(clickedPoint);
// Find the index of the store.features that corresponds to the clickedPoint that fired the event listener
var selectedFeature = clickedPoint.properties.address;
for (var i = 0; i < stores.features.length; i++) {
if (stores.features[i].properties.address === selectedFeature) {
selectedFeatureIndex = i;
}
}
// Select the correct list item using the found index
var listings = document.getElementById('mySelect').selectedIndex = selectedFeatureIndex ;
}
});

Grab class from each element and move to next element in line - like a round robin

I have a circle with icons placed around it using CSS. On mouseenter or click, the html content is stored as a variable belonging to the icon and output inside the center of the circle. Each icon has a class that determines its position. (e.g. first, second, third, etc).
I would like to move whichever icon is active to the top (first) and position all the other in line respectively. So, if I click/mouseenter on the icon with a class of "third", I would like to:
a) remove the 'active' class from the active element (done)
b) add the 'active' class to this element (done)
c) change this element's class to 'first', which would make the fourth element's class be 'second', the fifth's 'third' and so on down the line.
I started by doing 'each' to store the current class in a variable, but I and then going to the parent() to change the class attr(), but couldn't figure out how to target this in a round robin style to move the class down the line. You can see below the code to move the 'active' class as well as the html content. I also have a codepen mocked up here: https://codepen.io/jphogan/pen/KqdNYZ
// on circle click add/remove class and replace html
$('.icon_circle_container .icon_circle').on('mouseenter click', function() {
// if it's already active, do nothing, but return false
if ( $(this).hasClass('active')) {
return false;
} else {
// otherwise...
$('.icon_circle_container .icon_circle.active').removeClass('active'); // remove active class from element that has it
$(this).addClass('active'); // add active class to this element
newData = $(this).find('.icon_circle_content').html(); //store html as attribute
$('.icon_circle_container .center .icon_circle_content').fadeOut(function() { // fade out old content
$(this).html(newData).fadeIn(); // fade in new html content
}); // replace with stored data
return false;
}
})
Codepen example
(function($) {
$(document).ready(function() {
// on circle click add/remove class and replace html
$(".icon_circle_container .icon_circle").on("click", function() {
// if it's already active, do nothing, but return false
if ($(this).hasClass("active")) {
return false;
} else {
// otherwise...
// What is the new active index
var new_item = $(this).attr('data-item-index');
// How many items total
var items_count = $('.icon_circle').length;
// Rotate
$('.icon_circle').each(function( index ) {
var adjusted_index = index + 1; // Since js indexes from 0
var difference = adjusted_index - new_item;
if(difference == 0){
console.log('activate '+ adjusted_index);
}else{
console.log('not active '+adjusted_index);
if(difference > items_count){
difference = difference - items_count;
}else if(difference < 1){
difference = difference + items_count;
}
}
var position = difference + 1;
// This is where you can active things and set the proper position class on the item
$(this).removeClass('position-1 position-2 position-3 position-4 position-5').addClass('position-' + position);
});
$(".icon_circle_container .icon_circle.active").removeClass("active"); // remove active class from element that has it
$(this).addClass("active"); // add active class to this element
newData = $(this).find(".icon_circle_content").html(); //store html as attribute
$(
".icon_circle_container .center .icon_circle_content"
).fadeOut(function() {
// fade out old content
$(this).html(newData).fadeIn(); // fade in new html content
}); // replace with stored data
return false;
}
});
// end document ready
});
// end jquery wrap
})(jQuery);
Key things:
I changed the classes for the positions to be numeric (position-1, position-2, etc).
I didn't look at your animation or fading stuff.
Essentially the stuff from line 38 down(your old stuff) may still need to apply differently around line 35 where I set the position class.
It loops through each item and figures out what position class to assign to that particular element based on where they clicked and the original data-item-index number.
We are going to assume that the order of the elements in the dom schematically is the same as the data-item-index.
In theory, we could probably drop the data-item-index and use some jquery to figure out where the clicked item fits into that order of the 5 icons in its parent but I didn't bother.

Searching markers with Leaflet.Control.Search from drop down list

I use Leaflet.Control.Search for searching markers by GeoJSON features and it works OK. Now I have to type first letters to find marker, but I want to choose them from drop down list with all markers in it. Is it way to do it?
If you want a dropdown list with all your markers, you are better off creating a custom control rather than trying to modify Leaflet.Control.Search. Creating a control with a select element that contains all your markers is a little complicated, but certainly far simpler than tweaking the code of someone else's finished project.
Start by creating an empty control:
var selector = L.control({
position: 'topright'
});
To put content within the control, you can use the control's .onAdd method. Create a container div for the control using L.DomUtil.create, which in this context will automatically assign the class leaflet-control, allowing any content within the div to display on the map and behave like a control should behave. Then create a select element within the div. Give it a default option if you want. Most importantly, give it an id, so that you can refer to it later:
selector.onAdd = function(map) {
var div = L.DomUtil.create('div', 'mySelector');
div.innerHTML = '<select id="marker_select"><option value="init">(select item)</option></select>';
return div;
};
Now that the control knows what to do when added to the map, go ahead and add it:
selector.addTo(map);
To add all your markers as options in the selector, you can use the .eachLayer method, which iterates through all the markers in the group and calls a function for each. For each layer, create an option element and append it to the select element, using the id assigned above. Assuming that you have created a GeoJSON layer called markerLayer, which has a property called STATION that you want to use as the option text, it would look like this:
markerLayer.eachLayer(function(layer) {
var optionElement = document.createElement("option");
optionElement.innerHTML = layer.feature.properties.STATION;
optionElement.value = layer._leaflet_id;
L.DomUtil.get("marker_select").appendChild(optionElement);
});
Here, we're relying on the fact that each layer gets assigned a unique internal id number, _leaflet_id, when created. We set each option's value attribute to the corresponding layer's _leaflet_id, so that when the option is selected, we have a way to access the marker.
Finally, to get the control to actually do something when you select one of the options, add some event listeners, using the id of the selector element:
var marker_select = L.DomUtil.get("marker_select");
L.DomEvent.addListener(marker_select, 'click', function(e) {
L.DomEvent.stopPropagation(e);
});
L.DomEvent.addListener(marker_select, 'change', changeHandler);
The click listener with the stopPropagation method is to prevent clicks on the selector from propagating through to the map if they overlap with the map pane, which could immediately unselect the layer you are trying to highlight. The change listener will run a handler function, which you can set to do anything you want. Here, I've set it to open the popup for a marker when its corresponding option is selected:
function changeHandler(e) {
if (e.target.value == "init") {
map.closePopup();
} else {
markerLayer.getLayer(e.target.value).openPopup();
}
}
And that's it! Here is an example fiddle with all this working together:
http://jsfiddle.net/nathansnider/ev3kojon/
EDIT:
If you are using the MarkerCluster plugin, you can modify the change handler function to work with clustered markers using the .zoomToShowLayer method:
function changeHandler(e) {
if (e.target.value == "init") {
map.closePopup();
} else {
var selected = markerLayer.getLayer(e.target.value);
markerClusterLayer.zoomToShowLayer(selected, function() {
selected.openPopup();
})
}
}
Example:
http://jsfiddle.net/nathansnider/oqk6u0sL/
(I also updated the original code and example to use the .getLayer method rather than ._layers[e.target.value], because this is a cleaner way to access the marker based on its id)
There would be a way to slightly modify Leaflet-search plugin so that it shows the full list of markers when user clicks on the magnifier button (i.e. when user expands the search control).
As if the search was triggered for 0 typed letter.
Using option minLength: 0 without modifying the plugin code does not trigger a search without typing unfortunately.
L.Control.Search.mergeOptions({
minLength: 0 // Show full list when no text is typed.
});
L.Control.Search.include({
_handleKeypress: function(e) {
switch (e.keyCode) {
case 27: //Esc
this.collapse();
break;
case 13: //Enter
if (this._countertips == 1)
this._handleArrowSelect(1);
this._handleSubmit(); //do search
break;
case 38: //Up
this._handleArrowSelect(-1);
break;
case 40: //Down
this._handleArrowSelect(1);
break;
case 37: //Left
case 39: //Right
case 16: //Shift
case 17: //Ctrl
//case 32://Space
break;
case 8: //backspace
case 46: //delete
this._autoTypeTmp = false;
if (this._collapsing) { // break only if collapsing.
break;
}
default: //All keys
this._doSearch(); // see below
}
},
// externalized actual search process so that we can trigger it after control expansion.
_doSearch: function() {
if (this._input.value.length)
this._cancel.style.display = 'block';
else
this._cancel.style.display = 'none';
if (this._input.value.length >= this.options.minLength) {
var that = this;
clearTimeout(this.timerKeypress);
this.timerKeypress = setTimeout(function() {
that._fillRecordsCache();
}, this.options.delayType);
} else
this._hideTooltip();
},
expand: function(toggle) {
toggle = typeof toggle === 'boolean' ? toggle : true;
this._input.style.display = 'block';
L.DomUtil.addClass(this._container, 'search-exp');
if (toggle !== false) {
this._input.focus();
this._map.on('dragstart click', this.collapse, this);
}
this.fire('search_expanded');
this._doSearch(); // Added to trigger a search when expanding the control.
return this;
},
collapse: function() {
this._hideTooltip();
this._collapsing = true; // added to prevent showing tooltip when collapsing
this.cancel();
this._collapsing = false; // added to prevent showing tooltip when collapsing
this._alert.style.display = 'none';
this._input.blur();
if (this.options.collapsed) {
this._input.style.display = 'none';
this._cancel.style.display = 'none';
L.DomUtil.removeClass(this._container, 'search-exp');
if (this.options.hideMarkerOnCollapse) {
this._markerLoc.hide();
}
this._map.off('dragstart click', this.collapse, this);
}
this.fire('search_collapsed');
return this;
}
});
Include this code in your JavaScript before instantiating L.Control.Search.
Demo: http://jsfiddle.net/ve2huzxw/190/
The big drawback of this solution is that the list of markers is built within the map container. As such, if it is too big, it will be cropped on the bottom, whereas a true select (drop-down) input would "overflow" beyond the container, as in nathansnider's solution.

Openlayers 3: how to select a feature programmatically using ol.interaction.Select?

I'm using OpenLayers v3.6 (this is important, because most of solutions that I found and would potentialy work are for OpenLayers 2).
I have a table and when I select a row in that table, I would like to highlight/select a corresponding feature on the OpenLayers map. All features are simple polygons (ol.geom.Polygon) in the same vector layer (ol.layer.Vector).
I set up select interaction like this:
// there is a lot of other code here
...
addSelectListener: function() {
this.SelectInteraction = new ol.interaction.Select({
condition: ol.events.condition.singleClick,
layers: function (layer) {
// defines layer from which features are selectable
return layer.get('id') == 'polygons_layer';
},
style: this.Style.Selected
});
// Map = ol.Map
Map.addInteraction(this.SelectInteraction);
this.SelectInteraction.on('select', this.selectPolygon, this);
}
...
selectPolygon: function(event) {
var selectSrc = this.getSelectInfo(event);
// some code that relies on selectSrc data
}
...
getSelectInfo: function (event) {
var selectSrc = {
deselected: null,
selected: null,
type: null
};
if (event.selected.length == 0 && event.deselected.length == 1) {
// click outside of polygon with previously selected
selectSrc.type = 'deselect';
selectSrc.deselected = {
feature: event.deselected[0],
id: event.deselected[0].getId()
};
} else if (event.deselected.length == 0 && event.selected.length == 1) {
// click on polygon without previously selected
selectSrc.type = 'select';
selectSrc.selected = {
feature: event.selected[0],
id: event.selected[0].getId()
}
} else if (event.deselected.length == 0 && event.selected.length == 1) {
// click on polygon with previously selected
selectSrc.type = 'switch';
selectSrc.deselected = {
feature: event.deselected[0],
id: event.deselected[0].getId()
};
selectSrc.selected = {
feature: event.selected[0],
id: event.selected[0].getId()
}
} else {
selectSrc.type = 'out';
}
return selectSrc;
}
This functions well when I want to select polygon by clicking on it on the map. But what I want is to achieve the same, not by clicking on map but rather click on some element outside the map (table row in my example, but it could be anything really).
I would like to use select interaction because of event that is emitted and because of the styling it applies to selected features. However, if by any chance I can just manipulate the selected features in select interaction without having the same event it would be ok.
I'm aware of this question & answer - Openlayers 3: Select a feature programmatically - but the problem is that I cannot ask in comments for clarification (for example, what exactly is mySelectControl), because I don't have any reputation :)
The way to do is in the linked question. So, push a ol.Feature into the selected collection:
var select = new ol.interaction.Select({
//some options
});
map.addInteraction(select);
var selected_collection = select.getFeatures();
selected_collection.push(featurePoint);
If you want to trigger the select event:
select.dispatchEvent('select');
// OR
select.dispatchEvent({
type: 'select',
selected: [featurePoly],
deselected: []
});
See demo!

Filtering Sigma.Js graph from external events

I have an instance of Sigma.Js 1.0.0 rendering a graph in my Canvas element. (The code is below, but you can simply scroll to Step 2 of Tutorial on the main sigmajs.org page.
As you can see from that code, when the node is clicked, clickNode event occurs, which then applies filtering to the graph, showing only the clicked node and its neighborhood and dimming the others. That's quite clear.
However, how would I make exactly the same thing happen from the outside? Suppose I have the graph rendered already and I have a Tag Cloud next to it. And I want that when I click on a #hashtag, only that node is shown in the graph and the rest are dimmed. How would I do that?
Thanks!
<div id="sigma-container"></div>
<script src="path/to/sigma.js"></script>
<script src="path/to/sigma.parsers.min.gexf.js"></script>
<script>
// Add a method to the graph model that returns an
// object with every neighbors of a node inside:
sigma.classes.graph.addMethod('neighbors', function(nodeId) {
var k,
neighbors = {},
index = this.allNeighborsIndex[nodeId] || {};
for (k in index)
neighbors[k] = this.nodesIndex[k];
return neighbors;
});
sigma.parsers.gexf(
'path/to/les-miserables.gexf',
{
container: 'sigma-container'
},
function(s) {
// We first need to save the original colors of our
// nodes and edges, like this:
s.graph.nodes().forEach(function(n) {
n.originalColor = n.color;
});
s.graph.edges().forEach(function(e) {
e.originalColor = e.color;
});
// When a node is clicked, we check for each node
// if it is a neighbor of the clicked one. If not,
// we set its color as grey, and else, it takes its
// original color.
// We do the same for the edges, and we only keep
// edges that have both extremities colored.
s.bind('clickNode', function(e) {
var nodeId = e.data.node.id,
toKeep = s.graph.neighbors(nodeId);
toKeep[nodeId] = e.data.node;
s.graph.nodes().forEach(function(n) {
if (toKeep[n.id])
n.color = n.originalColor;
else
n.color = '#eee';
});
s.graph.edges().forEach(function(e) {
if (toKeep[e.source] && toKeep[e.target])
e.color = e.originalColor;
else
e.color = '#eee';
});
// Since the data has been modified, we need to
// call the refresh method to make the colors
// update effective.
s.refresh();
});
// When the stage is clicked, we just color each
// node and edge with its original color.
s.bind('clickStage', function(e) {
s.graph.nodes().forEach(function(n) {
n.color = n.originalColor;
});
s.graph.edges().forEach(function(e) {
e.color = e.originalColor;
});
// Same as in the previous event:
s.refresh();
});
}
);
</script>
<!-- [...] -->
I hope this goes some way to answering your question.
You have a tagcloud full of words, and when a word is clicked, you want to trigger the neighbors method on your sigma instance, for which you need the node id.
Simply put, you need the function which is called when the #hashtag is clicked, to be in the same scope as the sigma instantiation.
s= new sigma({
settings: {...}
})
//more code instantiating methods etc
//let's assume your tags are in elements with class='tagword' and have the hashtag stored in a 'name' attribute
$('.tagword').on('click', function(){
var name = this.attr('name');
s.graph.nodes().forEach(function(n){
if (n.label == name){
//use the node to trigger an event in sigma
//i.e. s.graph.neighbors(n.id);
};
};
};

Categories

Resources