Mapbox - achieving three states of paint opacity - mapbox

I am trying to have 3 states of opacity for the following situation:
Clicked = Opacity: 0.8
Hover = Opacity: 0.6
Default = Opacity: 0.4
I have this code so far:
'paint': {
'fill-color': '#627BC1',
'fill-opacity': [
'case',
['boolean', ['feature-state', 'hover'], false],
0.6,
0.4
]
}
However given it's boolean, can someone help me understand how I can make this into an array with three states rather than 2?
Here is a useable example:
https://codepen.io/hiven/pen/NWwBXJj
James

You should be able to do something like the following
paint: {
"fill-color": "#627BC1",
"fill-opacity": [
"case",
["==", ["feature-state", "mystate"], 1],
0.6,
["==", ["feature-state", "mystate"], 2],
0.8,
0.4
]
}
Then you would just set "mystate" as required...
// Hover
map.setFeatureState(
{ source: "states", id: clickedStateId },
{ mystate: 1 }
);
// Clicked
map.setFeatureState(
{ source: "states", id: clickedStateId },
{ mystate: 2 }
);
With the default being 0.4
This way you can support an arbitrary number states, each with their own value - rather than a simple Boolean "true/false"

Related

Mapbox proportional symbol feature radius

I'm new to Mapbox GL JS and struggling with data-driven styling. I'd like to scale my circle features proportionally based on a value in my data source (a Google Sheet). Here's a code snippet:
map.addLayer({
id: 'csvData',
type: 'circle',
source: {
type: 'geojson',
data: data
},
paint: {
'circle-color': '#dd502c',
'circle-stroke-width': 1,
'circle-stroke-color': '#dd502c',
'circle-opacity': 0.5,
"circle-radius": {
property: 'value',
stops: [
[2, 5],
[1000, 10],
[8000, 20],
]
}
}
});
Many Thanks,
Looks like AverageNitrate (I assume this was the property you were trying to plot) was a string. Converting to int did the trick.
Also I referenced it in the styling expression instead of the generic 'value'
data.features.forEach((datum)=>{
datum.properties.AverageNitrate = parseInt(datum.properties.AverageNitrate)
})
//Add the the layer to the map
map.addLayer({
id: 'csvData',
type: 'circle',
source: {
type: 'geojson',
data: data
},
paint: {
'circle-color': '#dd502c',
'circle-stroke-width': 1,
'circle-stroke-color': '#dd502c',
'circle-opacity': 0.5,
"circle-radius": {
property: 'AverageNitrate',
stops: [
[2, 5],
[1000, 10],
[8000, 20],
]
}
}
});
Here is a link to the edited fiddle: https://jsfiddle.net/mojsc6pn/35/

Combine case and zoom expressions in Mapbox

I have a layer that renders Polygon features in a Geojson source as filled area.
Here's an example of one of the features:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"id": 12345,
"name": 'exampleName',
"hasCities": true | false,
},
"geometry": {
"type": "Polygon",
"coordinates": [[...], [...]]
}
}, {...}
]
"properties": {
"parent_name": "parent1",
"parent_id": 23,
}
}
I would like to reach this fill opacity logic:
const isRed = true | false
const redOpacity = 0.5
const notRedOpacity = 0.8
if (hasCities) {
opacity = 0
} else if (isRed) {
if(zoom <= 14) // use linear interpolation
opacity = redOpacity
else if(zoom >= 19)
opacity = 0.03
else
interpolate
} else {
if(zoom <= 14) // use linear interpolation
opacity = notRedOpacity
else if(zoom >= 19)
opacity = 0.03
else
interpolate
}
This is my starting point:
const opacity = [
'case',
['boolean', ['get', 'hasCities']],
0,
isRed ? redOpacity : notRedOpacity,
]
Then I don't know how to implement the zoom part.
I think I need something like this:
const zoomOpacity = ['interpolate', ['linear'], ['zoom'], 14, notRedOpacity | redOpacity, 19, 0.03]
but I'm not sure and how can I combine these two expressions?
I know that "zoom" expression may only be used as input to a top-level "step" or "interpolate" expression." so I think I need to use step operator.
Can someone help me please?
You need to start by flipping your logic so the zoom is the first decision:
if(zoom <= 14) // use linear interpolation
if (hasCities)
opacity = 0
else if (isRed) {
opacity = redOpacity
else
opacity = notRedOpacity
if (zoom >= 19)
if (hasCities)
opacity = 0
else
opacity = 0.03
Then express it as a function that computes opacity, from zoom and the other options:
opacity = interpolate (zoom)
14:
if (hasCities)
0
else if (isRed) {
redOpacity
else
notRedOpacity
19:
if (hasCities)
0
else
0.03
Finally, in actual Mapbox GL language:
"fill-opacity": ["interpolate", ["zoom"],
14,
["case", ["get", "hasCities"],
0
["case", ["get", "isRed"],
redOpacity,
notRedOpacity
]
]
19,
["case", ["get", "hasCities"],
0,
0.03
]
]
I solved in this way:
const baseValue = [
'case',
['boolean', ['get', 'hasDistricts']],
0,
isRed ? redOpacity : notRedOpacity,
]
const fillOpacity = [
'interpolate',
['linear'],
['zoom'],
14,
baseValue,
19,
['case', ['boolean', ['get', 'hasCities']], 0, 0.03],
]

echarts - visualMap according to y axis

I am trying to add a visual map according to the y axis in echarts.
Taking one of their example:
https://echarts.apache.org/examples/en/editor.html?c=area-pieces
The results looks as follow:
what I'm trying to achieve is:
Obviously here, I have just rotated the picture by 90 degree.
How can this be achieve in echarts directly (so not by saving the picture first)?
The simplest solution would be inverting the axis, data index, and visual map axis. See chart options below:
option = {
backgroundColor: '#fff',
xAxis: {
type: 'value',
boundaryGap: [0, '30%'],
position: 'top'
},
yAxis: {
type: 'category',
boundaryGap: false
},
visualMap: {
type: 'piecewise',
show: false,
dimension: 1,
seriesIndex: 0,
pieces: [{
gt: 1,
lt: 3,
color: 'rgba(0, 180, 0, 0.5)'
}, {
gt: 5,
lt: 7,
color: 'rgba(0, 180, 0, 0.5)'
}]
},
series: [
{
type: 'line',
smooth: 0.6,
symbol: 'none',
lineStyle: {
color: 'green',
width: 5
},
markLine: {
symbol: ['none', 'none'],
label: {show: false},
data: [
{yAxis: 1},
{yAxis: 3},
{yAxis: 5},
{yAxis: 7}
]
},
areaStyle: {},
data: [
[200, '2019-10-10'],
[400, '2019-10-11'],
[650, '2019-10-12'],
[500, '2019-10-13'],
[250, '2019-10-14'],
[300, '2019-10-15'],
[450, '2019-10-16'],
[300, '2019-10-17'],
[100, '2019-10-18']
]
}
]
};
Result:
See on Imgur

How to specify ticks locations in chart.js?

I am looking for a way to manually specify x/y ticks locations on a chart.js chart, an equivalent of matplotlib's matplotlib.pyplot.xticks. The documentation explains how to create custom tick formats, but this works on automatically calculated tick locations. How can I specify the ticks locations?
This is the config that I am using:
var config = {
type: "line",
data: {
labels: [],
datasets: [{
data: [{x: 1, y: 2}, {x: 5, y: 3}, {x: 6, y: 4}],
fill: false,
borderColor: "#1f77b4",
}],
},
options: {
scales: {
xAxes: [{
type: 'linear',
distribution: 'series',
ticks: {
min: 0.,
max: 7.,
},
}]
},
},
}
which produces this file:
I would like to specify xticks locations, e.g. [0., 3.5, 5.7, 6.1], so that the plot would look more like (not taking into account grid lines and smooth plot line):
Apparently with Chart.js v3, the accpted solution no longer works as expected. Trying afterBuildTicks as suggested by b1000 in his comment, it also didn't work.
To make it work with afterBuildTicks, you need to map the number values into objects that have the property value each ([{ value: 0 }, { value: 3.5 }, ... ]). This can be done as follows:
afterBuildTicks: axis => axis.ticks = [0, 3.5, 5.7, 6.1].map(v => ({ value: v }))
Please take a look at below runnable sample:
new Chart('line-chart', {
type: 'scatter',
data: {
datasets: [{
data: [{x: 1, y: 2}, {x: 5, y: 3}, {x: 6, y: 4}],
showLine : true,
borderColor: '#1f77b4',
}]
},
options: {
scales: {
x: {
min: 0,
afterBuildTicks: axis => axis.ticks = [0, 3.5, 5.7, 6.1].map(v => ({ value: v }))
}
}
}
});
canvas {
max-height: 180px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.min.js"></script>
<canvas id="line-chart"></canvas>
For not drawing grid lines on the chart, you need to add the following to your axes as described at Grid Line Configuration.
gridLines: {
drawOnChartArea: false
}
In order to obtain the ticks at the desired position only, you would define xAxis.ticks as shown below and documented at Tick Option and Creating Custom Tick Formats.
ticks: {
...
stepSize: 0.1,
autoSkip: false,
callback: value => [0, 3.5, 5.7, 6.1].includes(value) ? value : undefined,
maxRotation: 0
},
Finally, to obtain a straight line between the data points, define lineTension: 0 on your dataset as explained at Line Styling.
Please have a look at your amended code below.
var config = {
type: "line",
data: {
labels: [],
datasets: [{
data: [{x: 1, y: 2}, {x: 5, y: 3}, {x: 6, y: 4}],
fill: false,
lineTension: 0,
borderColor: "#1f77b4",
}],
},
options: {
scales: {
xAxes: [{
type: 'linear',
ticks: {
min: 0.,
max: 7.,
stepSize: 0.1,
autoSkip: false,
callback: value => [0, 3.5, 5.7, 6.1].includes(value) ? value : undefined,
maxRotation: 0
},
gridLines: {
drawOnChartArea: false
}
}],
yAxes: [{
ticks: {
showLabelBackdrop: true
},
gridLines: {
drawOnChartArea: false
}
}]
},
},
};
new Chart('line-chart', config);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="line-chart" height="80"></canvas>

How can I show the count of points in the particular latitude and longitude in Mapboxgl?

I want to show the marker as well as the number of points available in a particular latitude and longitude.
I expect something Similar as above.
But i have added another 2 layers to show different color and number on it. If I do this way, I am getting "undefined" error while showing popup. Since the data takes from the other layer. If it takes from layer "locations" it works as expected. But when we have multiple occurences popup content shows "undefined". Below is my implementation output and code
map.on('load', function () {
map.addSource("place", {
type: "geojson",
data: liveData,
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
map.addLayer({
"id": "locations",
"type": "circle",
"source": "location",
"paint": {
"circle-radius": 7,
"circle-color": "#FFF000",
"circle-stroke-width": 4,
"circle-stroke-color": "#FFFFFF"
}
});
map.addLayer({
id: "clusters",
type: "circle",
source: "location",
filter: ["has", "point_count"],
paint: {
"circle-color": {
property: "point_count",
type: "interval",
stops: [
[0, "#FFF000"],
[2, "#DC143C"],
]
},
"circle-radius": {
property: "point_count",
type: "interval",
stops: [
[0, 7],
[2, 7],
]
}
}
});
map.addLayer({
id: "cluster-count",
type: "symbol",
source: "location",
filter: ["has", "point_count"],
layout: {
"text-field": "{point_count_abbreviated}",
"text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"],
"text-size": 12,
"text-offset": [0, 5]
}
});
map.on('click', 'locations', function (e) {
let htmlString = "";
for (let i = 0; i < e.features.length; i++) {
htmlString = htmlString + e.features[i].properties.description;
if (i != e.features.length - 1) {
htmlString = htmlString + "</br>";
}
}
new mapboxgl.Popup()
.setLngLat(e.features[0].geometry.coordinates)
.setHTML(htmlString)
.addTo(map);
});
}
My working fiddle
I want to achieve this as first picture or popup should work in my approach?
As far as I know getting all features that get combined in the cluster is currently not possible via mapbox's cluster feature.
See here: https://github.com/mapbox/mapbox-gl-js/issues/3318