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],
]
Related
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"
Who know how to draw lines and points inside a bar something like this:
You can draw the lines as floating bars where individual bars are specified with the syntax [min, max], same as you already do for your your other bars. Given an array of number values, the "line" data can be produced with Array.map() as follows.
data: [110, 150, 140, 100, 120].map(v => [v - 1, v + 1])
Then you need to define individual stacked xAxes for the "bar" and the "line" datasets. In order to have the original "line" values displayed in the tooltips, a toolips.callback.label function is also needed.
The points can be defined in an additional dataset of type 'scatter'.
Please have a look at below runnable code snippet.
new Chart("chart", {
type: "bar",
data: {
labels: ["4", "3", "2", "1", "0"],
datasets: [{
label: "Bars",
backgroundColor: "rgba(0, 255, 0, 0.2)",
data: [[20, 100], [50, 180], [60, 120], [10, 130], [70, 140]],
xAxisID: "x-axis-actual",
order: 1
},
{
label: "Lines",
backgroundColor: "rgb(0, 0, 0)",
data: [90, 150, 110, 90, 120].map(v => [v - 1, v + 1]),
xAxisID: "x-axis-target",
order: 2
},
{
label: "Points",
backgroundColor: "rgb(255, 255, 255)",
borderColor: "rgb(0, 0, 0)",
data: [80, 110, 90, 100, 120],
type: "scatter"
}
]
},
options: {
legend: {
display: false
},
tooltips: {
callbacks: {
label: (tooltipItem, data) => {
const dataset = data.datasets[tooltipItem.datasetIndex];
const v = dataset.data[tooltipItem.index];
return dataset.label + ': ' + (tooltipItem.datasetIndex == 1 ? (v[1] + v[0]) / 2 : tooltipItem.value);
}
}
},
scales: {
xAxes: [{
id: "x-axis-target",
stacked: true
},
{
display: false,
offset: true,
stacked: true,
id: "x-axis-actual",
gridLines: {
offsetGridLines: true
}
}
],
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="chart" height="120"></canvas>
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
I want to achieve like this http://techblog.mappy.com/Leaflet-active-area/examples/index.html
I have added a floating content in left of the section. So the marker must remain in the right side.
Here is a demo.
JS:
// ***********************************************
mapboxgl.accessToken = 'pk.eyJ1IjoiZmFseiIsImEiOiJjaXdmczJha28wMG9sMnVsZnlxZWt1bmxhIn0.v8SlQ70Ay1MzNoPXhlXWVg';
// ***********************************************
// DID YOU FORK THIS EXAMPLE?
// Enter your access token below
// and uncomment the line to keep your
// project online!
// Need a token? Create free account
// mapbox.com/signup
// ***********************************************
// mapboxgl.accessToken = 'YOUR-ACCESS-TOKEN-HERE';
// ***********************************************
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v9',
center: [144.985258,-37.807426],
zoom: 14
});
var framesPerSecond = 15;
var initialOpacity = 1
var opacity = initialOpacity;
var initialRadius = 6;
var radius = initialRadius;
var maxRadius = 18;
map.on('load', function () {
// Add a source and layer displaying a point which will be animated in a circle.
map.addSource('point', {
"type": "geojson",
"data": {
"type": "Point",
"coordinates": [
144.985258, -37.807426
]
}
});
map.addLayer({
"id": "point",
"source": "point",
"type": "circle",
"paint": {
"circle-radius": initialRadius,
"circle-radius-transition": {duration: 0},
"circle-opacity-transition": {duration: 0},
"circle-color": "#007cbf"
}
});
map.addLayer({
"id": "point1",
"source": "point",
"type": "circle",
"paint": {
"circle-radius": initialRadius,
"circle-color": "#007cbf"
}
});
function animateMarker(timestamp) {
setTimeout(function(){
requestAnimationFrame(animateMarker);
radius += (maxRadius - radius) / framesPerSecond;
opacity -= ( .9 / framesPerSecond );
map.setPaintProperty('point', 'circle-radius', radius);
map.setPaintProperty('point', 'circle-opacity', opacity);
if (opacity <= 0) {
radius = initialRadius;
opacity = initialOpacity;
}
}, 1000 / framesPerSecond);
}
// Start the animation.
animateMarker(0);
});
Unfortunately there isn't support yet for something like fitBounds with padding. https://github.com/mapbox/mapbox-gl-js/issues/1740
A possible workaround could be flying to the marker with an offset.
map.flyTo({
center: [144.985258, -37.807426],
offset: [300, 0],
speed: 0.8,
curve: .6
});
I have a column chart with positive and negative values. Using the default dataLabels, keeps the label at the end of the column, which isn't ideal for negative values, as it goes below the x axis.
$(function () {
$('#container').highcharts({
chart: {
type: 'column',
height: 200,
borderColor: '#ddd'
},
title: {
text: ''
},
legend: {
padding: 0,
margin: 5
},
credits: {
enabled: true
},
tooltip: {
enabled: false
},
plotOptions: {
column: {
dataLabels: {
enabled: true,
crop: false,
overflow: 'none'
}
}
},
colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE', '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'],
loading: {
labelStyle: {
top: '35%',
fontSize: "2em"
}
},
xAxis: {
categories: ["7/12", "7/13", "7/14", "7/15", "7/16"]
},
series: [{
"name": "Odometer",
"data": [{
"y": 94.98
}, {
"y": 182.96
}, {
"y": -160.97
}, {
"y": -18.00
}, {
"y": 117.97
}]
}]
});});
Example: http://jsfiddle.net/absessive/NKXRk/54/
Add plotOptions.column.stacking as normal like this and it will work.
You can change a little bit Highcharts.seriesTypes.column.prototype.alignDataLabel function, so it will not have functionality of aligning the dataLabel below your column if its value is smaller than 0.
Here you can find code that may help you:
H.seriesTypes.column.prototype.alignDataLabel = function(point, dataLabel, options, alignTo, isNew) {
var inverted = this.chart.inverted,
pick = H.pick,
Series = H.Series,
series = point.series,
merge = H.merge,
dlBox = point.dlBox || point.shapeArgs, // data label box for alignment
below = false,
inside = pick(options.inside, !!this.options.stacking), // draw it inside the box?
overshoot;
// Align to the column itself, or the top of it
if (dlBox) { // Area range uses this method but not alignTo
alignTo = merge(dlBox);
if (alignTo.y < 0) {
alignTo.height += alignTo.y;
alignTo.y = 0;
}
overshoot = alignTo.y + alignTo.height - series.yAxis.len;
if (overshoot > 0) {
alignTo.height -= overshoot;
}
if (inverted) {
alignTo = {
x: series.yAxis.len - alignTo.y - alignTo.height,
y: series.xAxis.len - alignTo.x - alignTo.width,
width: alignTo.height,
height: alignTo.width
};
}
// Compute the alignment box
if (!inside) {
if (inverted) {
alignTo.x += below ? 0 : alignTo.width;
alignTo.width = 0;
} else {
alignTo.y += below ? alignTo.height : 0;
alignTo.height = 0;
}
}
}
// When alignment is undefined (typically columns and bars), display the individual
// point below or above the point depending on the threshold
options.align = pick(
options.align, !inverted || inside ? 'center' : below ? 'right' : 'left'
);
options.verticalAlign = pick(
options.verticalAlign,
inverted || inside ? 'middle' : below ? 'top' : 'bottom'
);
// Call the parent method
Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
};
The only change I have made is that I have changed below parameter to false.
Here you can find an example how it can work:
http://jsfiddle.net/NKXRk/59/
Best regards,