How do I dynamically toggle multiple layers in Deck.gl? - javascript

I am building a basic visualization system that layers can be toggled from the control box.
I have layers that merge individual layers into one.
const [layers, setLayers] = useState([densityLayer, pedestrianLayer]);
I have filterState that tracks the activity in the control box. It contains the layer object as a property of linkTo
const [filterState, setFilterState] = useState([
{
id: 'densityFilter',
checked: true,
description: 'Population density',
linkedTo: densityLayer
},
{
id: 'pedestrianFilter',
checked: true,
description: 'Pedestrian volume',
linkedTo: pedestrianLayer
}
]);
and everytime checked property in filterState gets updated, it launches renderLayers()
which will select corresponding layers whose checked property is true.
useEffect(()=>{
renderLayers();
},[filterState]);
const renderLayers = () => {
const newLayers = [];
filterState.map(filter => (filter.checked && newLayers.push(filter.linkedTo)));
setLayers(newLayers);
}
Then layers is passed to DeckGL component as a layer prop.
<DeckGL
initialViewState={viewState}
controller={true}
layers={layers}
>
In my program, turning off the layers works fine, but they do not turn back on. In the console, I noticed that the lifecycles between layers are different. Is there anything incorrect about my approach?

Have you try to use visible property of layers? If you are going to switch multiple time and often, deck.gl suggests to use visible instead of recreated a new layer.
Some useful resources about this thread:
Creating layer instances is cheap.
Layer visibility over addition and removal.
Visible property.
First, create a Control box, like you did.
Then pass as props to DeckGL component what you selected from control box.
{
layer1: true, layer2: false, layer3: false, layer4: false,
}
Create a state for layers.
const [activeLayers, setActiveLayers] = useState(layersProps);
Check with useEffect when something changes in layersProps.
useEffect(() => {
const layers = {
layer1: false,
layer2: false,
layer3: false,
layer4: false,
};
if (typeMap === 'layer1') {
layers.layer1 = true;
} else if (typeMap === 'layer2') {
layers.layer2 = true;
}
...
setActiveLayers(layers);
}, [layerProps]);
Or you can create a state
const [activeLayers, setActiveLayers] = useState({
layer1: true, layer2: false,
});
And pass as props only what you selected from control box and check for changes.
useEffect(() => {
const layers = {
layer1: false,
layer2: false,
};
if (typeMap === 'layer1') {
layers.layer1 = true;
} else if (typeMap === 'layer2') {
layers.layer2 = true;
}
...
setActiveLayers(layers);
}, [inputLayerSelected]);
If you prefer you can also split each layer with a single one state (so you have a primitive value).
Finaly, you can create your layer
const layer1 = new ScatterplotLayer({
id: 'scatter',
data: data,
....
visible: activeLayers.layer1, OR
visible: layer1
});
and render
<DeckGL
layers={[layer1, layer2, layer3, layer4]}
...
>

Related

How to change values in an array nested in a map

I have a Map _filterMapLocal: Map<string, Filter>;
And the Filter object has this attribute options: FilterOption[].
It is an array of FilterOption. A FilterOption also has this attribute selected: boolean.
My goal is now to select this Map and change all selected values to false.
Is this possible?
My solution was a nested for loop like that:
this._filterMapLocal.forEach(filter => filter.options.forEach(option => option.selected = false))
But it is not working well... and I think in respect to performance this is bad.
Is there another way?
Basically my goal is to reset the filter.
The initial state is false and when I choose some filter selected will be true.
I have a clear all button which should change the state to false again.
I try to make a minimal reproduciple example...
export interface FilterOption {
selected: boolean;
}
export class Filter {
options: FilterOption[];
constructor(options?: FilterOption[]) {
this.options = options || [];
}
}
const _filterMapLocal = new Map<string, Filter>();
const filter1Options: FilterOption[] = [
{ selected: true },
{ selected: false },
{ selected: false }
];
const filter1 = new Filter();
filter1.options = filter1Options;
_filterMapLocal.set("filter1", filter1);

React object initial state is overriden

I got a react component with a form. I keep the form settings in an object outside the component:
const initialForm = {
name: {
elementType: 'input',
elementAtts: {
label: 'Tenant Name',
readOnly: false
},
isRequired : true,
value: '',
},
description: {
elementType: 'input',
elementAtts: {
label: 'Description',
readOnly: false
},
isRequired : false,
value: '',
}
}
const AddAndDisplay = (props) =>
{
const [formSettings, setFormSettings] = useState(initialForm);
...
}
the elementAtts is the attributes I pass the input.
What I'm trying to do is open a modal which displays the form - one time for display only and one time with editing allowed - can be for editing an existing item or for adding a new item.
I'm doing it like this for editing an existing item and for displaying:
//a callback
const OpenModalForEditOrDisplay = (isEditable, cardObject) =>
{
setFormSettings(prevForm =>
{
let newForm = {...prevForm};
newForm.name.elementAtts.readOnly = !isEditable;
newForm.description.elementAtts.readOnly = !isEditable;
return {...newForm}
});
setIsFormOpen(true);
}
};
and for adding a new item:
setFormSettings(initialForm);
setIsEditing(true);
setIsFormOpen(true); //this is merely a state saying if to show the modal with the form
The user can then submit or cancel the form, and on either case I'm doing:
setFormSettings(initialForm);
The problem is that it seems like initialForm is overridden and if I open the form for display only, it stays on display when trying to open the form for addition because the code for the edit part changed what I thought would be a copy of the initialForm. If I remove these lines in the open for edit function the form stays with the initial form's settings:
newForm.name.elementAtts.readOnly = !isEditable;
newForm.description.elementAtts.readOnly = !isEditable;
Why is the initial form being overridden here?
You have used Spread syntax to clone the prevForm values within setFormSettings. However you must note that Spread syntax only shallow clones the object and does not perform a deep cloning which means that you nested values within the prevForm still hold the original reference and when you update the values like
newForm.name.elementAtts.readOnly = !isEditable;
newForm.description.elementAtts.readOnly = !isEditable;
You are mutating it at the original reference. The correct way to update state is to immutably update it by cloning each nested level like
setFormSettings(prevForm =>
{
let newForm = {
...prevForm,
name: {
...prevForm.name,
elementAttrs: {
...prevForm.name.elementAttrs,
readOnly: !isEditable,
}
}
description: {
...prevForm.description,
elementAttrs: {
...prevForm.description.elementAttrs,
readOnly: !isEditable,
}
}
};
return newForm;
});
This is a problem for Deep Copy and shallow copy. The 'formSettings' data source is 'initialForm'. Use 'setFormSettings' will to change 'initialForm' , this is right. Because you use a shallow copy when you initialize it. you can use a function Deep Copy to 'initialForm'.
const createInitialForm = () => ({
name: {
elementType: 'input',
elementAtts: {
label: 'Tenant Name',
readOnly: false
},
isRequired : true,
value: '',
},
description: {
elementType: 'input',
elementAtts: {
label: 'Description',
readOnly: false
},
isRequired : false,
value: '',
}
})
const AddAndDisplay = (props) =>
{
const [formSettings, setFormSettings] = useState(createInitialForm());
...
}

problem creating a reusable filtered table using React

I am trying to create a reusable table component where I can only pass down as props the data and the filters and it will already handle most of the work for me. However, right now, I am hardcoding the select variables into the Table state and if I am to create another table with different filters I would have to create another component for that. And that's not viable in anyway.
right now I have a master component that hold the data and filter and passes down to Table component, like that:
class Master extends React.Component {
listHotels: [
createObject('BlueTree','05-02-2015','ativo', 'Vinhedo', 'São Paulo'),
createObject('Inner','07-08-2016','inativo', 'Belo Horizonte', 'Minas Gerais'),
createObject('Teste','05-02-2017','ativo', 'Teresina', 'Piauí'),
createObject('hello','05-02-2015','ativo', 'Osasco', 'São Paulo'),
createObject('Inner','07-08-2016','inativo', 'Lavras', 'Minas Gerais'),
createObject('Teste','05-02-2017','inativo', 'Barras', 'Piauí'),
createObject('xiaomi','05-02-2015','inativo', 'Indaiatuba', 'São Paulo'),
createObject('Inner','07-08-2016','ativo', 'Pedrinhas', 'Minas Gerais'),
createObject('Teste','05-02-2017','ativo', 'Esperantina', 'Piauí'),
].sort((a, b) => (a.id < b.id ? -1 : 1)),
selectFilter: [
{ id: 1, type: 'Name', options},
{ id: 2, type: 'Data', options},
{ id: 3, type: 'Cidade', options},
{ id: 4, type: 'Estado', options},
{ id: 5, type: 'PMS', options},
],
}
and then I call the table component on render function
<Table listHotels={listHotels} toggleList={this.toggleList} selectFilter={selectFilter}></Table>
On the Table component I have the state to keep track of the filter select options as you can see below(status, pms, state...):
state = {
page: 0,
rowsPerPage: 5,
query: '',
filter: [],
status: null,
pms: null,
state: null,
city: null,
name: null,
isLoading: false
};
And whenever the option is selected I create an API call to hit the back end and respond with some data to fill the state's filter property.
componentDidUpdate(prevProps, prevState){
const { status, pms, state, city, page, rowsPerPage } = this.state;
const filterOptions = [];
if (status !== null){
filterOptions.push({name: "status", value: status});
}
if (pms !== null){
filterOptions.push({name: "pms", value: pms});
}
if (state !== null){
filterOptions.push({name: "estado", value: state});
}
if (city !== null){
filterOptions.push({name: "cidade", value: city});
}
if(filterOptions.length !== 0){
const url = createAPICall(filterOptions, page, rowsPerPage);
//call api and set it to the filter property
}
However, the way the component is right now is not reusable. If I want to create another table with other filters, I would have to change the state to hold the filter variables and also to create the API url.
How can I make this more dynamic?
assuming you want to make the filter types more flexible. part of the issue is you manually list them out in state and table. start by bundling your filter types into an object to pass through from master (or extract them from selectFilter).
When you set state, set filters: {...parse filters here...}.
next in componentDidUpdate, you don't want to be manually listing each if statement, you want to iterate over your filters obj definition, adding an if per obj key in each loop. When values are updated in your form/table, push the key value in your state object, not the individual values like you have now.
i.e.
const { status, pms, state, city, page, rowsPerPage } = this.state;
would look more like
const { filters, page, rowsPerPage } = this.state;

React-native get all photos from Camera Roll and Camera Roll API

I am trying to get camera roll photos using react native CameraRoll.getPhotos API. The issue I found that the documentation is not great. In react-native official documentation there are two terms that were mentioned getPhotosReturnChecker and getPhotosParamChecker where I can get the detail about this parameters.
I found the following object that can be passed to CameraRoll.getPhotos from bhwgroup blog
{
first: ..., // (required) The number of photos wanted in reverse order of the photo application
after: ..., // A cursor returned from a previous call to 'getPhotos'
groupTypes: ..., // Specifies which group types to filter the results to
// One of ['Album', 'All', 'Event', 'Faces', 'Library', 'PhotoStream', 'SavedPhotos'(default)]
groupName: ..., // Specifies filter on group names, like 'Recent Photos' or custom album titles
assetType: ... // Specifies filter on assetType
// One of ['All', 'Videos', 'Photos'(default)]
}
According to these it always require a parameter first which dictates how many pictures we can get from CameraRoll. Instead if I want all the photos from camera roll how can I get it?
You'll want to do some paging to access all photos. Basically, you are loading them in chunks, and keeping track of the place where you left off after each fetch. You'll want a state similar to this:
this.state = {
dataSource: ds.cloneWithRows([]),
assets: [],
lastCursor: null,
noMorePhotos: false,
loadingMore: false,
};
Then fetching functions similar to these. This example assumes you are using a ListView to display your photos using a ListView.DataSource
tryPhotoLoad() {
if (!this.state.loadingMore) {
this.setState({ loadingMore: true }, () => { this.loadPhotos(); });
}
}
loadPhotos() {
const fetchParams = {
first: 35,
groupTypes: 'SavedPhotos',
assetType: 'Photos',
};
if (Platform.OS === 'android') {
// not supported in android
delete fetchParams.groupTypes;
}
if (this.state.lastCursor) {
fetchParams.after = this.state.lastCursor;
}
CameraRoll.getPhotos(fetchParams).then((data) => {
this.appendAssets(data);
}).catch((e) => {
console.log(e);
});
}
appendAssets(data) {
const assets = data.edges;
const nextState = {
loadingMore: false,
};
if (!data.page_info.has_next_page) {
nextState.noMorePhotos = true;
}
if (assets.length > 0) {
nextState.lastCursor = data.page_info.end_cursor;
nextState.assets = this.state.assets.concat(assets);
nextState.dataSource = this.state.dataSource.cloneWithRows(
_.chunk(nextState.assets, 3)
);
}
this.setState(nextState);
}
endReached() {
if (!this.state.noMorePhotos) {
this.tryPhotoLoad();
}
}

Update multiple entries in an ImmutableJS List

Specifically, I have an ImmutableJS List of Maps, and I want to conditionally update records to the same value (which may include just updating all of them). The use case is revealing mines in minesweeper, when someone loses the game. this is accomplished by setting the isVisible tile to true, if isMine is also true (or just make every tile visible, regardless of isMine)
so my JS schema is like this (where arrays are lists and objects are maps):
game = {
isGameOver: false,
tiles: [
{
isMine: boolean,
isRevealed: boolean
},
...
]
}
and so what I'm trying to do is, starting with game, set isRevealed to true for every tile where isMine is true.
this is what i came up with, but it looks so awful I have to hope there's another way
function revealAll(game){
let revealedTiles;
revealedTiles = game.get('tiles').map(tile => {
if (tile.get('isMine')) {
tile = tile.set('isRevealed', true);
}
return tile;
});
return game.set('isGameOver', true).set('tiles', revealedTiles);
}
this successfully ends the game (sets isGameOver to true) and reveals all tiles that are mines (sets isRevealed to true for every tile with isMine equal to true), but I can tell just looking at it that its both inefficient and messy. Is there a built in way to accomplish what i'm doing here?
From my point of view, the code is quite fine :) There are however few tricks that may make it nicer:
using multiple set on the same Map can be replaced by one merge:
return game.merge({
'isGameOver': true,
'tiles': revealedTiles
});
Also, updating an individual tile can be done nicer:
revealedTiles = game.get('tiles').map(
tile => tile.update('isRevealed', (v) => tile.get('isMine') || v))
so you end up with:
function revealAll(game){
let revealedTiles
revealedTiles = game.get('tiles').map(
tile => tile.update('isRevealed', (v) => tile.get('isMine') || v))
return game.merge({
'isGameOver': true,
'tiles': revealedTiles
});
}
OR, you can do it like this:
const revealAll = (game) =>
game
.set('isGameOver', true)
.update('tiles', (tiles) => tiles.map(
tile => tile.update('isRevealed', (v) => tile.get('isMine') || v)))
Here's a functional programming approach using mudash. The benefit of this approach is that mudash will handle both ImmutableJS data types as well as standard JS (or a mix of both). So your function can be used no matter the format.
import _ from 'mudash'
import fp from 'mudash/fp'
const revealAll = _.compose(
fp.set('isGameOver', true),
fp.update('tiles', fp.map((tile) =>
_.update(tile, 'isRevealed', (isRevealed) => _.get(tile, 'isMine') || isRevealed)))
)
const gameMutable = {
isGameOver: false,
tiles: [
{
isMine: false,
isRevealed: false
},
{
isMine: true,
isRevealed: false
},
{
isMine: false,
isRevealed: true
},
{
isMine: true,
isRevealed: true
}
]
}
const gameImmutable = _.immutable(gameMutable)
console.log(revealAll(gameMutable))
console.log(revealAll(gameImmutable))

Categories

Resources