Openlayers 4: Changing the draw order of selected features - javascript

I have a single vector layer in OpenLayers 4 (4.4.1). The layer has several features with LineString geometries. Some of the features overlap.
If I click at a point where features overlap, I want only one of the features to be drawn as selected. The others should still be available for selection later (by feature id in a separate UI selection list).
If I click on another feature id (in the separate UI selection List) that feature should be drawn as selected, and the previously selected should not be drawn as selected, but still available in the selection list.
This works, but it is only the first (default) selected feature that seems to be drawn at the top.
Image below shows when feature id 10049 is marked as selected.
Image below shows when feature id 10048 is marked as selected.
If I click somewhere on the southmost feature where they do not overlap, it is drawn correctly as selected on top.
To keep track of the feature that needs to be visually selected there is a variable:
var multiselectPrimaryId = 0;
I use the following selectInteraction code:
selectInteraction.on('select', function (e) {
e.deselected.forEach(function (feature) {
feature.setStyle(null);
});
if (e.selected.length <= 1) {
$("#multipleHitWindow").hide();
multiselectPrimaryId = 0;
if (e.selected.length == 0) {
return;
}
}
var features = e.selected;
if (multiselectPrimaryId == 0) {
multiselectPrimaryId = features[0].getId();
}
if (features.length > 1) {
var text = "Multiple hits: ";
features.forEach(function (elem, index, array) {
text += "<a href='javascript:changeSelectedFeature("
+ elem.getId() + ")'>" + elem.getId() + "</a> ";
if (elem.getId() == multiselectPrimaryId) {
elem.setStyle(selectedStyleFunction);
}
});
$('#multipleHit').html(text);
$("#multipleHitWindow").show();
}
else {
features[0].setStyle(selectedStyleFunction);
}
});
And I call this function from a dynamically created list of link:
function changeSelectedFeature(id) {
multiselectPrimaryId = id;
var featuresArray = selectInteraction.getFeatures().getArray().slice(0);
var event = new ol.interaction.Select.Event()
event.deselected = featuresArray;
event.selected = featuresArray;
event.type = 'select';
event.target = selectInteraction;
event.mapBrowserEvent = map.mapBrowserEventHandler_;
selectInteraction.dispatchEvent(event);
}
How can I get the one with selectedStyle set to be drawn at the top? I have tried to add a zIndex to the selectedStyle. But it does not seem to have any effect.
Here is a JsFiddle: https://jsfiddle.net/5j6c6mgo/7/ . There are some other minor issues with the selection, but hopefully you will able to see the behaviour that I described above.

I have a single vector layer ... The layer has several features with LineString geometries. Some of the
features overlap.
I think you would need the LineString geometries to be in separate Layers for you to be able to use 'zIndex' - you would do this by calling 'setZIndex' on the layer in question. This will easily allow you to set the draw order at runtime.
Other than that the vectors are going to be displayed in their initial draw order and short of redrawing changing their draw order isn't possible.

Related

How can we use legends as filters in AmCharts4?

Currently, in amchart4, the legends can be used to show / hide the target series on click. I would like the behaviour wherein on clicking on the legend:
Do not hide the clicked series.
Hide all other series except the one that was clicked so as to show only the seires whose legend that was clicked.
This question is on the back of an older question targetting amcharts3. However, since v4 is significantly different from v3, the answer does not work.
Based on the documentation here, it appears that the below should work:
series1.events.on("hidden", function() {
series2.hide();
series3.hide();
// but when I run series1.show() in order to mimic series1 to not hide, I get a max call size exceeded msg
});
Further to that, according to this, one can outright disable the toggle on the legends - but it works at the cart level and not at the series level.
Thanks.
Update: Follow up is available on GitHub. Posting the update here for the sake of completeness.
#zeroin 's answer works perfectly. I just needed it to be modified a bit more to have it working for the below scenario.
How do I re-enable all the series again? In the graph I'm building, I
have a series called 'allTraffic' and multiple other series.
AllTraffic should never get hidden.
When I click on any of the other series apart from AllTraffic, hide the other series apart from AllTraffic and the series whose legend has been clicked.
Reset everything (bring back all the series) when AllTraffic's legend is clicked.
chart.legend.itemContainers.template.togglable = false;
chart.legend.itemContainers.template.events.on("hit", function(event) {
var target = event.target;
chart.legend.itemContainers.each(function(item) {
if (target.dataItem.dataContext.name != 'All traffic' && item.dataItem.dataContext.name != 'All traffic') {
console.log("clicked other: ", target.dataItem.dataContext.name);
item.isActive = true;
item.dataItem.dataContext.hide();
}
if (target.dataItem.dataContext.name == 'All traffic') {
console.log("Clicked all traffic");
item.isActive = false;
item.dataItem.dataContext.show();
}
});
target.isActive = false;
target.dataItem.dataContext.show();
})
Here is how you do it:
https://codepen.io/team/amcharts/pen/285b315ff30a2740fdbf72f27230711c
To avoid SO you need to make itemContainers not togglable:
chart.legend.itemContainers.template.togglable = false;
chart.legend.itemContainers.template.events.on("hit", function(event){
var target = event.target;
chart.legend.itemContainers.each(function(item){
if(item != target){
item.isActive = true;
item.dataItem.dataContext.hide();
}
})
target.isActive = false;
target.dataItem.dataContext.show();
})

Hightcharts: reset the graph

I am building Highcharts within R by using rCharts library. But I think anyone who is familiar with Javascript or Highcharts could answer my question as well. I incorporated a function to allow user to ctrl+click to select a specific series.
Please see this for your reference: https://jsfiddle.net/derekrezek/Nkeep/109/
a$plotOptions(
series = list(
events = list(
legendItemClick = "#! function(e) {
var hideAllOthers = e.browserEvent.metaKey|| e.browserEvent.ctrlKey;
if (hideAllOthers) {
var seriesIndex = this.index;
var series = this.chart.series;
for (var i = 0; i < series.length; i++) {
if (series[i].index === seriesIndex) {
if (!series[i].visible) series[i].setVisible(true, false);
} else {
if (series[i].visible) series[i].setVisible(false, false);
}
}
this.chart.redraw();
return false;
}} !#")
)
)
It doesn't impact original functionality of Highcharts and allow user to select a specific series by ctrl+click the legend item. However, what if I have 50+ legends. After I select one specific series and then I want the all other series back, I will have to click all of invisible series to show them again, which is not practical.
Anyone knows how to make a reset button? or allow user to ctrl+click again to restore the original graph?
Thank you in advance!
Refer to this example, I have added a Reset button to destroy and re-instantiate the Line chart with default value.

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);
};
};
};

More control when doing animations in Dashcode/Dashboard?

Recently I am giving another shot to Dashcode ;)
It's great. Is just I think is not well documented.
I have a stackLayout object with only two views in it, and a couple of buttons that interchange the views with a transition.(views show data of a large array, list)
Animations and transitions work perfect. The problem is, when I press a button while animating the animation starts again and it looks ugly (If I had n views for a datasource array of length n this should't be a problem, but this is not my case).
I want to disable buttons while animation is taking place.
Is there any callback, delegate, or any way I can get a notification when the animation is finished?
This is what I have done:
function _changeView(transitionDirection, newIndex){
//Create transition
var newTransition = new Transition(Transition.SWAP_TYPE, 0.9, Transition.EASE_TIMING);
newTransition.direction = transitionDirection;
//I only have two views. I use currentView's id to calculate not current view id and change text inside of it.
var stackLayout = document.getElementById('stackLayout').object;//stackLayout object
var nextViewId = (stackLayout.getCurrentView().id == 'view1')? '2':'1'; //
//change the text in the view that is going to appear
document.getElementById('text'+nextViewId).innerHTML = list[curIndex];
stackLayout.setCurrentViewWithTransition('view'+ nextViewId, newTransition, false);
}
function goPrevious(event)
{
curIndex--;
if(curIndex < 0){
curIndex = list.length-1;
}
_changeView(Transition.LEFT_TO_RIGHT_DIRECTION, curIndex);
}
function goNext(event)
{
curIndex++;
if(curIndex >list.length - 1){
curIndex = 0;
}
_changeView(Transition.RIGHT_TO_LEFT_DIRECTION, curIndex);
}
Eventually found the answer to this. Here's how I did it:
document.getElementById('stackLayout').object.endTransitionCallback=function(stackLayout, oldView, newView) {
//PUT CODE HERE USING stackLayout, oldView, newView to show params
}
In fact you can find all the stackLayout methods and properties in the StackLayout.js file in your project!!
Hope this helps

Categories

Resources