Mapbox GL JS: Clustering circle not rendered properly upon zooming out - javascript

I am building a website that renders circles on a map. If the ["feature-state", "value] property is >= 1, the circle is green and if the ["feature-state", "value"] property is 0 or null, the circle is red (the default value is 0). When my map is zoomed in enough, the circles are rendered properly (all circles with ["feature-state", "value] >= 1 are rendered green). However, when I zoom out, the cluster circle that contains children features/circles with a ["feature-state", "value], the circle is rendered as red instead of green. Is there any way to ensure that the clusters are rendered as green if the sum of their children's ["feature-state", "value] is >= 1?
Below is my rendering code:
map.addSource('cities', {
"type": "geojson",
"data": "cities.geojson",
"cluster": true,
"clusterMaxZoom": 14,
"clusterRadius": 80
});
map.addLayer({
"id": "cities",
"type": "circle",
"source": "cities",
"paint": {
"circle-color": [
"case",
["==", ["feature-state", "value"], null], "#ff4d4d",
[">=", ["feature-state", "value"], 1], "#33cc33",
"#ff4d4d"
],
"circle-radius": [
"case",
["==", ["feature-state", "value"], null], 9,
[">=", ["feature-state", "value"], 1], 12,
6
],
"circle-opacity" : [
"case",
["==", ["feature-state", "value"], null], 0.7,
[">=", ["feature-state", "value"], 1], 1,
0.7
]
},
});
Below is how I set the "feature-state":
map.setFeatureState({source: "cities", id : 124312}, {value: 1});
Here is a screenshot of the map when correctly zoomed in:
Here is a screenshot of the map when zoomed out (the red circle in the area marked with a white marker should be rendered green):

This can be done with clusterProperties option of the source object which takes custom expression to aggregate properties for a cluster point.
Note: clusterProperties option does not support feature-state aggregation and can only work with properties. I'll explain it in steps below and there's also a codepen example attached at bottom.
First, define an expression that aggregates feature's properties that form a cluster.
{
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 80,
clusterProperties: {
/*
get the property numUser, then cast it to number or 0,
then sum it, then store the sum as cluster's numUser property
*/
numUsers: ["+", ["number", ["get", "numUsers"], 0]]
},
type: "geojson",
data: geojson,
}
Update style expressions to use feature properties instead of feature-state:
paint: {
"circle-color": [
"case",
["==", ["get", "numUsers"], null], "#ff4d4d",
[">=", ["get", "numUsers"], 1], "#33cc33",
"#ff4d4d"
],
"circle-radius": [
"case",
["==", ["get", "numUsers"], null], 9,
[">=", ["get", "numUsers"], 1], 12,
6
],
"circle-opacity": [
"case",
["==", ["get", "numUsers"], null], 0.7,
[">=", ["get", "numUsers"], 1], 1,
0.7
]
}
Update GeoJSON properties:
// Update after 2 seconds
setTimeout(() => {
const newGeojson = {
...geojson,
features: geojson.features.map(feature => {
if ([0, 1].includes(feature.id)) { // update features selectively
feature.properties.numUsers = 1;
}
return feature;
})
};
map.getSource('geom').setData(newGeojson);
}, 2000);
Here's a working codepen: https://codepen.io/manishraj/full/wvwmNKR
There's a related example here, by mapbox: https://docs.mapbox.com/mapbox-gl-js/example/cluster-html/

Related

How to set circle-color using filters in mapbox data-expression?

Want to show different colored circles on the map using mapbox-gl-js.
Currently it is possible to show different colors using:
'circle-color': [
"case",
[">=", ["get", 'count'], 12],
"#000000",
"#ffffff"
]
, but need to perform multiple filter to be performed for single color.
need something like:
[">=", ["get", 'count'], 120], && ["<=", ["get", 'count'], 200],
and if above results to true show red color circle.
You want the all expression:
["all",
[">=", ["get", 'count'], 120],
["<=", ["get", 'count'], 200],
]

Make pins in symbol layer pixel perfect for clicking

I am using layer of symbols and want to make it possible as much as possible pixel perfect the icon be. How i can achieve this i am using custom maki icons for these symbols.
Is it somehow possible to increase the size of the icon but reduce the size of square for example?
Ive tried to play with size and paddings in the layer setup but this did not help.
My Layer looks like this:
"id": tableName + "-layer",
"type": "symbol",
"source": tableName,
"source-layer": tableName,
"layout": {
"icon-image": 'marker-15-svg',
"icon-size": ["interpolate",
["linear"], ["zoom"],
4, 1.3,
8, 1.5,
13, 2.5
],
"icon-allow-overlap": true,
"icon-padding": 0,
"icon-offset": [0, -3],
"icon-anchor": "center",
"symbol-z-order": "source"
},
"paint": {
"icon-color": materialSetColor,
"icon-opacity": {"stops": [[10, 0.7], [30, 1]]},
}

MapboxGL polygons are black colored if property missing

I am having issue with colors of the polygons. So i display polygones/areas in the map and i set for each color depending of their properties:
map.setPaintProperty("layerName", "fill-color", [
"interpolate",
["linear"],
["get", key],
range[0], colors[0],
range[1], colors[1],
range[2], colors[2],
]);
But some of these polygons just do not have the "key" property in them hence the map reads them like undefined/nullish and sets default color to them a pure black. I need to change this black color to something else but cant figure it out.
I tried to add default color after the ranges but it didnt work . Also tried if i could do it via styling/css but its a canvas so seems that this is not an option.
map.setPaintProperty("layerName", "fill-color", [
"interpolate",
["linear"],
["get", key],
range[0], colors[0],
range[1], colors[1],
range[2], colors[2],
defaultColor
]);
This solution worked for me :
//This will put color of white for the missing property
map.setPaintProperty(
'layerName',
'fill-color',
["case", ["==", ["get", key], null], "#ffffff", [
"interpolate",
["linear"],
["get", key],
range[0], colors[0],
range[1], colors[1],
range[2], colors[2],
]]
);
This works also for opacity:
map.setPaintProperty(
'layerName',
'fill-opacity',
["case", ["==", ["get", key], null], 0.1, 0.7]
);

Mapbox add background image on circle

I'm using mapbox and I've clustered my data. Now I want to show a
background image (instead of a black background!) when a user has zoomed to a point.
I add it like this:
this.map.addLayer({
id: "unclustered-point",
type: "circle",
source: "earthquakes",
filter: ["!has", "point_count"],
paint: {
"circle-color": "black",
"circle-radius": 8,
"circle-stroke-width": 4,
"circle-stroke-color": "#fff",
}
});
I'm loading my geojson like this:
this.map.addSource("earthquakes", {
type: "geojson",
data: this.locations,
cluster: true,
clusterMaxZoom: 14, // Max zoom to cluster points on
clusterRadius: 50 // Radius of each cluster when clustering points (defaults to 50)
});
this.map.addLayer({
id: "clusters",
type: "circle",
source: "earthquakes",
filter: ["has", "point_count"],
paint: {
"circle-color": [
"step",
["get", "point_count"],
"#002e5b",
100,
"#002e5b",
750,
"#002e5b"
],
"circle-radius": [
"step",
["get", "point_count"],
20,
100,
30,
750,
40
],
}
});
So how can I get a background image instead of a black background?
You can add another layer of points with your custom image that becomes visible at z14. This way the custom image will cover the black circles.
I recommend making the black circle extremely small or nonexistent so it doesn't conflict with the custom images.

Flot Charts: bar + line, different colours and custom data parameters

The goal
What i need is one Flot chart that contains different coloured bars, with different coloured lines over the top.
Many questions answer how to achieve different coloured bars.
And many pages answer how to add bars AND lines.
But i want to combine these two solutions.
An added bonus would be to be able to add custom parameters to each bar / line (see the "testId" example below) - however this is not necessarily required, as a workaround I could create an object on page load that references each bar's position (e.g. the "ticks" array).
My current workaround
I can already achieve this using multiple data sets as seen in this jFiddle, which i have constructed based off this answer's jFiddle, however this requires multiple different data sets per colour variant of each bar / line like so:
var dataBarsRed = {
data: [
[0, 1],
[2, 1.9],
],
color: 'red'
};
var dataBarsOrange = {
data: [
[1, 2],
[3, 3.9],
],
color: 'orange'
};
var dataBarsGreen = {
data: [
[4, 4],
[5, 4.5]
],
color: 'green'
};
var dataLineRed = {
data: [
[0, 2, 2],
[1, 2, 2],
[2, 2, 2],
[3, 2, 2],
[4, 2, 2],
[5, 2, 2],
],
label: '60% efficiency',
color: 'red',
bars: {
barWidth: 1
}
};
var dataLineOrange = {
data: [
[0, 4, 4],
[1, 4, 4],
[2, 4, 4],
[3, 4, 4],
[4, 4, 4],
[5, 4, 4],
],
label: '85% efficency',
color: 'orange',
bars: {
barWidth: 1
}
};
And add them like so:
$.plot("#placeholder", [dataBarsRed, dataBarsOrange, dataBarsGreen, dataLineRed, dataLineOrange],...
From what i can tell, there is no way to add any custom data parameters to this data structure, in which each item has it's own set of custom variables (e.g. object id's to link to the back-end, like "testId" - see "The Rationale").
The rationale
Is there a way to achieve what i need using the same structure for the bar data as the first link:
var data = [
{ data: [[0,1]], color: "red", testId: 30 },
{ data: [[1,2]], color: "yellow", testId: 31 },
{ data: [[2,3]], color: "green", testId: 32 }
];
The reason i'd like to do it this way is that it would be much simpler to build the Flot Data Object on the Java side (return it as JSON from ajax) but also it has the added benefit of adding your own parameters to each bar / line (e.g. "testId" shown above. The id's themselves would be utilised to retrieve more data via ajax on click or hover of the bar.
For Example:
$(container).bind('plothover', function(event, position, item){
console.log(item.series.testId)
});
Final thoughts
I feel like i'm going round in circles with this. I love the Flot charts plugin and it's flexibility, but there are numerous ways to implement the same visual outcome.
Thanks.
- Steve.
The Solution
See this jFiddle for the complete code.
After considering what Raidri is saying regarding it's simplicity, I've taken another stab at it and I've gotten something working as i need it to, with a bit more consistency. I feel a bit silly for not seeing this before (perhaps i did and a syntax error caused me to move on). Anyway, if anyone is interested here's the code:
NOTE: This does feel like a bodge or a hack - i'm not sure if this kind of data structure was ever intended, but it gets the job done for my purposes.
var data = [
{ data: [[0,1]], color: "red", testId: 30, isBar: true },
{ data: [[1,2]], color: "orange", testId: 31, isBar: true },
{ data: [[2,1.9]], color: "red", testId: 32, isBar: true },
{ data: [[3,3.9]], color: "orange", testId: 33, isBar: true },
{ data: [[4,4]], color: "green", testId: 34, isBar: true },
{ data: [[5,4.5]], color: "green", testId: 35, isBar: true },
{ data: [[0, 4, 4],[0.5, 4, 4],[1, 4, 4],[1.5, 4, 4],[2, 4, 4],[2.5, 4, 4],[3, 4, 4],[3.5, 4, 4],[4, 4, 4],[4.5, 4, 4],[5, 4, 4]], shadowSize: 0, color: 'orange', isLine: true, label: '85% efficiency' },
{ data: [[0, 2, 2],[0.5, 2, 2],[1, 2, 2],[1.5, 2, 2],[2, 2, 2],[2.5, 2, 2],[3, 2, 2],[3.5, 2, 2],[4, 2, 2],[4.5, 2, 2],[5, 2, 2]], shadowSize: 0, color: 'red', isLine: true, label: '60% efficiency' },
{ data: [[0, -1, -1]], shadowSize: 0, color: 'green', label: '100% efficiency', isLine: true }
];
var plot = $.plot("#placeholder", data, {
bars: {
show: true,
align: 'center',
barWidth: 0.5
},
lines: {
show: true,
lineWidth: 0.1,
fill: false
},
grid: {
hoverable: true,
autoHighlight: true
},
xaxis: {
ticks: [[0,'Steve'],[1,'Bob'],[2,'Chris'],[3,'Joe'],[4,'Dave'], [5, 'Jon']]
},
yaxis: {
min: 0,
max: 5
},
legend:{
container: '#legend'
}
});
There are some slight nuances, such as the number of line points (data elements per line) need to take into account the bar width. E.g. if my bar width was 1 i'd only need 5 elements, but since i have a width of 0.5 i need 10 line elements per data array (to fill in the gaps). Also, "shadowSize: 0" is required or you'd get a grey shadow in the middle of the line.
I've also added "isBar" and "isLine" to each element so my hover event can distinguish between them so it does not update the info box when the line is hovered. This is because the way I've done this is a bit of a "hack" in the sense that the line is not one line, but a line per column. Without this further distinction of type, hovering over the line would display the bar's name depending on your mouse's x-position.
I've also had to add a "fake" green line for the legend - position 0, referencing start and end positions that are outside the axis range (-1, in this example, 6 would also work). I realise i can probably create a custom legend...but it was simpler to just let flot deal with it itself.
You can simply combine your five data objects into one array of objects und you can also add your own properties to these objects, like this:
var data = [ {
data: [
[0, 1],
[2, 1.9],
],
color: 'red',
testId: 31
}, {
data: [
[1, 2],
[3, 3.9],
],
color: 'orange',
testId: 32
}, {
//...
}];
And using your own properties for example in the hover-event is as simple as
$('#details').html("<span class='detail'>...(testId = " + item.series.testId + ")</span>");
See the updated fiddle for the full example.
PS: For the lines in your chart, maybe markings are a better fit (see the documentation and this fiddle, where one of the lines is replaced by a marking).

Categories