Mapbox in React make only one layer visible at a time - reactjs

Good day,
I have a map created with Mapbox in React. I have a list of layers that are currently being displayed on the map, together with a menu that allows the user to select the layers they want to view. At this point, the menu allows for multiple layers to be displayed at the same time. I want only one layer to be displayed at a time. In other words, when a certain layer is visible and the user selects a different layer - the current layer must be 'hidden' and the selected layer must be visible.
I hope this makes sense. I basically want the options to be exclusive so that only one layer displays at a time.
Here is the part in my code for the layer toggling:
map.current.on("idle", () => {
// If these layers were not added to the map, abort
if (
!map.current.getLayer("extrusion-num-hh") ||
!map.current.getLayer("extrusion-num-job") ||
!map.current.getLayer("extrusion-num-res") ||
!map.current.getLayer("extrusion-density-hh") ||
!map.current.getLayer("extrusion-density-job") ||
!map.current.getLayer("extrusion-density-res")
) {
return;
}
// Enumerate ids of the layers.
const toggleableLayerIds = [
"extrusion-num-hh",
"extrusion-num-job",
"extrusion-num-res",
"extrusion-density-hh",
"extrusion-density-job",
"extrusion-density-res",
];
// Set up the corresponding toggle button for each layer.
for (const id of toggleableLayerIds) {
// Skip layers that already have a button set up.
if (document.getElementById(id)) {
continue;
}
// Create a link.
const link = document.createElement("a");
link.id = id;
link.href = "#";
link.textContent = id;
link.className = "active";
// Show or hide layer when the toggle is clicked.
link.onclick = function (e) {
const clickedLayer = this.textContent;
e.preventDefault();
e.stopPropagation();
const visibility = map.current.getLayoutProperty(
clickedLayer,
"visibility"
);
// Toggle layer visibility by changing the layout object's visibility property.
if (visibility === "visible") {
map.current.setLayoutProperty(clickedLayer, "visibility", "none");
this.className = "";
} else {
this.className = "active";
map.current.setLayoutProperty(
clickedLayer,
"visibility",
"visible"
);
}
};
const layers = document.getElementById("menu");
layers.appendChild(link);
}
});
Any help is greatly appreciated, thanks!

Related

Canvas onclick event react

Hello and thank you for reading this question.
I'm struggling to deal with the onclick event on canvas with React.
I am currently building a component that has to draw bounding boxes on an image and make those boxes fire a onclick event with its coordinates and/or metadata.
I am using the following react-bounding-box component (https://www.npmjs.com/package/react-bounding-box) with a bit of customization.
The idea is that my component receive the following data :
an image
a JSON with a list of items that contains coordinates of bounding boxes and other data related to those boxes.
Once the JSON is loaded, my code iterates on the list of items and draws the bounding boxes on the image using canvas.
My component definition looks like that (I omitted useless lines of code) :
[...]
import BoundingBox from 'react-bounding-box'
[...]
export const ComicImageDrawer = (props) => {
const [boundingBoxesItems, setBoundingBoxesItems] = useState(Array<any>());
const [selectedBoxItem, setSelectedBoxItem] = useState({})
const [selectedBoxIndex, setSelectedBoxIndex] = useState<Number>(-1);
const [currentImageBoxes, setCurrentImageBoxes] = useState(Array<any>())
useEffect(() => {
[...] // loading data
}, [])
// That function is fired when a box is hovered
// param value is the index of the box
// I would like to do the same but with the `onclick` event
function onOver(param) {
[...] // don't care
}
const params = {
[...] // don't care
}
};
return (
<BoundingBox
image={currentImage}
boxes={currentImageBoxes}
options={params.options}
onSelected={onOver}
drawBox={drawBoxCustom}
drawLabel={() => {}}
/>
)
}
The redefined the component drawBox() function to add some customization. So that function definition looks like this :
function drawBoxCustom(canvas, box, color, lineWidth) {
if(!box || typeof box === 'undefined')
return null;
const ctx = canvas.getContext('2d');
const coord = box.coord ? box.coord : box;
let [x, y, width, height] = [0, 0, 0, 0]
[...] // removed useless lines of codes
ctx.lineWidth = lineWidth;
ctx.beginPath();
[...] // drawing element definition
ctx.stroke();
};
I haved tried the following stuff to make the canvas fire an onclick event but it never fires (i also tried other event like mouseover) :
// Listen for mouse moves
canvas.addEventListener('onmouseover', function (event) {
console.log('click event', event);
});
What I would like to obtain is to fire a function in my React component that looks like that. The idea is to determine which box has been clicked :
const handleCanvasClick = (event, box) => {
console.log('event', event);
console.log('box', box);
}
Any help or suggestion would be appreciated.
Thanks.

How to toggle off Openlayers custom control?

I've gone ahead and built a custom control that I'm adding to my map like so:
const BoundingBox = (function (Control) {
function BoundingBox(optOptions) {
const options = optOptions || {};
const button = document.createElement('button');
button.innerHTML = '[]';
const element = document.createElement('div');
element.className = 'bounding-box ol-unselectable ol-control';
element.appendChild(button);
Control.call(this, {
element,
target: options.target,
});
button.addEventListener('click', this.handleBoundingBox.bind(this), false);
}
if (Control) BoundingBox.__proto__ = Control;
BoundingBox.prototype = Object.create(Control && Control.prototype);
BoundingBox.prototype.constructor = BoundingBox;
BoundingBox.prototype.handleBoundingBox = function handleBoundingBox() {
this.getMap().addInteraction(extent);
};
return BoundingBox;
}(Control));
Next, I added that control to my map when my map is initialized. This is working fine. Now, I'm trying to find a way to toggle off the BoundingBox control. I was thinking that I could use the .removeInteraction() method. However, I'm unsure if that's correct. Also, should that be applied in a separate function or in my BoundingBox control?
I was able to accomplish this by checking the properties of the ol/interaction/extent and setting the value of the active property to false.
extent.setProperties({ active: false });

Showing/Hiding groups of markers using layers or some other grouping method

I have a set of markers which I want to have visible or not on a React Google map.
In ESRI/ArcGIS maps you can create layers which can be turned on or off, but it does not seem any equivalent features exist in Google maps.
I suppose can give the markers a specific class and turn their visibility on or off, but I am concerned this may impact performance.
Any suggestions on a way forward?
Google Maps API does not support this kind of custom layers (refer official docs for a supported list of layers).
The following custom component demonstrates how to group markers and toggle its visibility
function MarkersGroup(props, context) {
const layersRef = useRef(null);
useEffect(() => {
const map = context[MAP];
let layers = null;
if (!layersRef.current) {
layers = new window.google.maps.MVCObject();
for (let name in props.groupData) {
for (let item of props.groupData[name].items) {
const markerProps = { position: { lat: item.lat, lng: item.lng } };
const marker = new window.google.maps.Marker(markerProps);
marker.bindTo("map", layers, name);
}
}
layersRef.current = layers;
} else layers = layersRef.current;
for (let name in props.groupData) {
if (props.groupData[name].visible) {
layers.set(name, map);
} else {
layers.set(name, null);
}
}
});
return null;
}
Notes:
google.maps.MVCObject class - is used to store layer(markers) group
layer visibility is toggled via MVCObject.set method
Here is a demo

How to add legends in Amserial charts

I am using Amcharts in my AngularJS Application to create a simple bar chart.The following is my code in the controller:
let empChart;
let empBarGraph;
let empLine;
const writeemp = data => {
const {
total,
employees,
} = data;
empChart.dataProvider = e;
empChart.write('emp');
empChart.validateData();
};
AmCharts.handleLoad();
var configChart = function () {
empChart = new AmCharts.AmSerialChart();
empChart.categoryField = "state";
empChart.labelRotation = 90;
var yAxis = new AmCharts.ValueAxis();
yAxis.position = "left";
empChart.addValueAxis(yAxis);
empBarGraph = new AmCharts.AmGraph();
empBarGraph.valueField = "count";
empBarGraph.type = "column";
empBarGraph.fillAlphas = 1;
empBarGraph.lineColor = "#f0ab00";
empBarGraph.valueAxis = yAxis;
empChart.addGraph(empBarGraph);
empChart.write('empChart');
$http.get(hostNameService.getHostName()+"/dashboard/employees/statecount")
.then(response => writeemp(response.data));
}
Code in html:
<div class='panel-body'>
<div id="empChart"></div>
</div>
This would return me the values of State on x-axis and count on y-axis. I wanted to filter the chart based on the value of state and was not sure how to create the legends for this chart. could anyone suggest me on how to use legends. I want to create legends for the state value that is being returned.
You can add a legend using the OO-based syntax by creating a legend object through new AmCharts.AmLegend() and adding it to the class by calling the chart's addLegend method:
var legend = new AmCharts.AmLegend();
empChart.addLegend(legend);
If you want the legend to show values upon hovering over a column, you need to add a ChartCursor to your chart:
var cursor = new AmCharts.ChartCursor();
empChart.addChartCursor(cursor);
You can change what the legend displays upon column rollover by setting the valueText property. It allows for the same [shortcodes] used in fields like balloonText and labelText, e.g. legend.valueText = "[[category]]: [[value]]". You can also use set its valueFunction if you need to customize the text it returns dynamically like in your previous questions. All of the properties available in the legend object can be found in the AmLegend API documentation.
Updated:
Legends work off of graph objects only, so there isn't an out of the box method that allows you to represent each column as a legend item that toggles the other columns' visibility unless you're willing to reorganize your dataset and use different graph objects for each state. A workaround for this is to use the the legend's custom data array and add some event handling so that clicking on the custom data items adds/removes a toggle by unsetting your count valueField in the dataProvider.
The following annotated code accomplishes what you're trying to do:
//create the legend but disable it until the dataProvider is populated,
//since you're retrieving your data using AJAX
var legend = new AmCharts.AmLegend();
legend.enabled = false;
chart.addLegend(legend);
chart.toggleLegend = false;
// Callback that handles clicks on the custom data entry markers and labels
var handleLegendClick = function(legendEvent) {
//Set a custom flag so that the dataUpdated event doesn't fire infinitely
legendEvent.chart.toggleLegend = true;
// The following toggles the markers on and off.
// The only way to "hide" a column is to unset the valueField at the data index,
// so a temporary "storedCount" property is added to the dataProvider that stores the
// original value so that the value can be restored when the legend marker is toggled
// back on
if (undefined !== legendEvent.dataItem.hidden && legendEvent.dataItem.hidden) {
legendEvent.dataItem.hidden = false;
legendEvent.chart.dataProvider[legendEvent.dataItem.stateIdx].count = legendEvent.chart.dataProvider[legendEvent.dataItem.stateIdx].storedCount; //restore the value
} else {
// toggle the marker off
legendEvent.dataItem.hidden = true;
legendEvent.chart.dataProvider[legendEvent.dataItem.stateIdx].storedCount = legendEvent.chart.dataProvider[legendEvent.dataItem.stateIdx].count; //store the value
legendEvent.chart.dataProvider[legendEvent.dataItem.stateIdx].count = undefined; //set to undefined to hide the column
}
legendEvent.chart.validateData(); //redraw the chart
}
chart.addListener('dataUpdated', function(e) {
var legendDataItems; //used to store the legend's custom data array.
if (e.chart.toggleLegend === true) {
//is the user toggling a legend marker? stop here as the dataProvider will get updated in handleLegendClick
e.chart.toggleLegend = false;
return;
}
// if we're at this point, the data provider was updated.
// reconstruct the data array.
// initialize by grabbing the state, setting a color and stoing the index
// for toggline the columns later
legendDataItems = e.chart.dataProvider.map(function(dataElement, idx) {
return {
'title': dataElement.state,
'color': graph.lineColor,
'stateIdx': idx //used in toggling
}
});
// if the legend is not enabled, then we're setting this up for the first time.
// turn it on and attach the event handlers
if (e.chart.legend.enabled === false) {
e.chart.legend.enabled = true;
e.chart.legend.switchable = true;
e.chart.legend.addListener('clickMarker', handleLegendClick);
e.chart.legend.addListener('clickLabel', handleLegendClick);
}
// update the legend custom data and redraw the chart
e.chart.legend.data = legendDataItems;
e.chart.validateNow();
});
Here's a fiddle that illustrates this: http://jsfiddle.net/g254sdq5/1/

Excel-like behaviour of Grids in Ext JS

I'm trying to figure out a way to have an Excel-like behavior with the Grids on Ext JS.
Here is the sample grid I am working with. So far we can already naviguate through the cells with the arrows but only in edit mode.
However what I am trying to reach is the naviguation with the arrows, TAB and Enter keys outside of the edit mode, just like excel.
I tried to integrate this piece of code which overrides the Editor class, hoping that it would change the behavior of the cells but it doesn't change a thing.
I believe this is the most important part that overrides the Editor class and tries to include the keys input :
Ext.override(Ext.Editor, {
startEdit: function (el, value) {
var me = this,
field = me.field;
me.completeEdit();
me.boundEl = Ext.get(el);
value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML;
if (!me.rendered) {
me.render(me.parentEl || document.body);
}
if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) {
me.startValue = value;
me.show();
field.reset();
if (deleteGridCellValue) {
field.setValue('');
me.editing = true;
me.completeEdit();
deleteGridCellValue = false; // reset global variable
}
else {
if (newGridCellValue == '') {
// default behaviour of Ext.Editor (see source if needed)
field.setValue(value);
}
else {
// custom behaviour to handle an alphanumeric key press from non-edit mode
field.setRawValue(newGridCellValue);
newGridCellValue = ''; // reset global variable
if (field instanceof Ext.form.field.ComboBox) {
// force the combo box's filtered dropdown list to be displayed (some browsers need this)
field.doQueryTask.delay(field.queryDelay);
}
}
me.realign(true);
field.focus(false, 10);
if (field.autoSize) {
field.autoSize();
}
me.editing = true;
}
}
}
});
This is the first time that I am working on a project that is outside of Comp-Sci classes so any help would be very much appreciated. Thanks !

Resources