How to toggle off Openlayers custom control? - reactjs

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

Related

Mapbox in React make only one layer visible at a time

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!

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.

React Kendo Treeview scroll to item

I am using React Kendo Treeview UI. I want to try to scroll to the item that is selected in the tree. I found many examples for Javascript and JQuery but none for React version. I couldn't solve this problem by playing around with it.
Items in the tree are of type MyViewTreeModel. I have a selectOntree method that finds a node and set the selected to true. My problem is I want to scroll to that item.
export interface MyViewTreeModel {
text: string,
expanded: boolean,
employeeId : number,
treeId: number,
items?: MyViewTreeModel [],
selected: boolean
}
....
<TreeView
data={myData}
expandIcons={true}
onExpandChange={onExpandChange}
onItemClick={OnItemClick}
aria-multiselectable={false}
aria-label={'text'}
></TreeView>
....
const selectOnTree = (employeeId: number ) => {
let treeItem = recursivelyFindEmployeeInTree(myData[0], employeeId);
treeItem.selected = true;
forceUpdate();
}
}
myData is of type MyViewTreeModel .
One solution I tried : I added ref?: any to my model and tried treeItem.ref.current.focus(); in selectOnTree function, but ref was undefined.
Another solution I tried is adding this property to TreeView:
ref={component => treeViewRef.current = component}
Then tried this just to select the first 'li' tag in the TreeView:
if(!_.isNil(treeViewRef.current) ){
let domElement = ReactDOM.findDOMNode(treeViewRef.current);
let treeItemDom = domElement.firstChild.firstChild;
(treeItemDom as HTMLElement).focus();
}
This didn't work, it doesn't put the focus at that point.
I am thinking maybe I should define a custom itemRender that has a ref that I can find the offsetTop of it, but then there are more than one item, how can I create a different ref for each one? Or maybe a custom ItemRender that renders an input (with css I can make it look like a span) and then set autofocus to true if selected is true. Not sure if autofocus true make it scroll to that item.
This is the solution I could find to make it work:
Adding a reference to TreeView
let treeViewRef = useRef(null);
In return statement:
<TreeView
data={myData}
expandIcons={true}
onExpandChange={onExpandChange}
onItemClick={OnItemClick}
aria-multiselectable={false}
aria-label={'text'}
ref={component => treeViewRef.current = component}></TreeView>
2.Defined this function to scroll to a specific treeItem:
'k-in' is the className for each span that represent each item in the Kendo Treeview UI component.
const scrollToTreeViewItem = (treeItem: MyViewTreeModel ) => {
if(!_.isNil(treeViewRef.current)){
let domElement = ReactDOM.findDOMNode(treeViewRef.current);
let treeItemDoms = (domElement as Element).querySelectorAll('.k-in');
let domArray = [];
treeItemDoms.forEach((node) => {
domArray.push(node as HTMLElement);
});
let targetedDomElement = domArray.find((item) => {
return item.innerText === treeItem.text;
});
targetedDomElement.scrollIntoView();
}
}

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/

ExtJS 4 Temporarily disable loading mask appearance for a grid

I have a grid and I want to disable appearance of loading mask on it (to prevent double loading mask because I add loading mask to its parent component) at the the execution of certain scripts.
I've tried something like this
var myGridView = myGrid.getView();
myGridView.loadMask = false;
// I want so at this data loding did not appear loading mask
myGrid.getStore().load();
myGridView.loadMask = true;
but it doesnt work.
Any suggestions?
You can use setDisabled() method for LoadMask instance:
var myGridView = myGrid.getView();
myGridView.loadMask.setDisabled(true);
myGrid.getStore().load(function () {
myGridView.loadMask.setDisabled(false);
});
As well you can use enable(), disable() methods.
After carefully reading source code of grid, load mask and store I can suggest this small override
Ext.override(Ext.grid.Panel, {
setLoading: function (load) {
var me = this,
view = me.getView(),
store = me.getStore();
me.callParent(arguments);
if (Ext.isBoolean(load) && view && view.loadMask && view.loadMask.isLoadMask && store) {
if (load) {
view.loadMask.bindStore && view.loadMask.bindStore(store);
} else {
view.loadMask.unbindStoreListeners(store);
}
}
}
});
This sounds crazy, but spinner knows about grid's store. And even has (protected) methods to work with http://docs.sencha.com/extjs/5.1.1/Ext.LoadMask.html#method-unbindStoreListeners
http://docs.sencha.com/extjs/4.1.3/#!/api/Ext.LoadMask-method-unbindStoreListeners

Resources