Mapbox supercluster throws error when trying to zoom a cluster - mapbox

I use supercluster to display clusters on mabox with react-map-gl. If I click on a cluster, it doesn't zoom in as expected, but the above error is thrown. Does anyone know why or has the same problem solved?
Uncaught TypeError: Cannot read properties of undefined (reading 'length')
if (isCluster) {
return (
<Marker
key={`cluster-${cluster.id}`}
longitude={longitude}
latitude={latitude}
>
<div
className="cluster-marker"
style={{
width: `${10 + (point_count / articels.length) * 20}px`,
height: `${
10 + (point_count / articels.length) * 20
}px`,
}}
onClick={() => {
//cluster.id = 16
const zoom = Math.min(
supercluster.getClusterExpansionZoom(cluster.id),
20
);
mapRef.current.flyTo({
center: [longitude, latitude],
zoom,
speed: 5,
});
}}
>
{point_count}
</div>
</Marker>
);
}

Related

AgGrid Custom Cell Editor does not refresh when calling refreshCells()

TLDR; the cell editor does not rerender when we invoke the api.refreshCells method.
The following code renders a simple table with 2 rows. The value of the data is irrelevant since it uses both a custom cell renderer and a custom cell editor that reaches into context to pluck a value.
When context updates we need to call refreshCells, since no change is detected to the actual cell data value.
Note when clicking increment the value of the context is incremented and the value of the cells updates accordingly. Observe the console log messages for each view renderer.
Now double click cell to enter into edit mode and then click the increment button. Note that the cell editor is not re-rendered when the increment takes place.
I can get the editor to update using events but the props (and so the context) are stale.
Presume this is a design decision, but I need to rerender the cell editor component when my context data updates. Any ideas?
https://plnkr.co/edit/3cCRnAY14nOWt3tl?open=index.jsx
const EditRenderer = (props) => {
console.log('render edit cell')
return (
<input
value={props.context.appContext.cellValue}
/>
);
};
const ViewRenderer = (props) => {
console.log('render view cell')
return <React.Fragment>{props.context.appContext.cellValue}</React.Fragment>;
};
const Grid = () => {
const [rowData] = useState([{ number: 10 }, { number: 3 }]);
const columnDefs = useMemo(
() => [
{
field: 'number',
cellEditor: EditRenderer,
cellRenderer: ViewRenderer,
editable: true,
width: 200,
},
],
[]
);
const [context, setContext] = useState({ cellValue: 1 });
const gridApiRef = useRef();
return (
<React.Fragment>
<p>Context value: {JSON.stringify(context)}</p>
<button
onClick={() => {
setContext({ cellValue: context.cellValue += 1 });
gridApiRef.current.refreshCells({force: true});
}}
>
Increment
</button>
<div style={{ width: '100%', height: '100%' }}>
<div
style={{
height: '400px',
width: '200px',
}}
className="ag-theme-alpine test-grid"
>
<AgGridReact
onGridReady={(e) => {
gridApiRef.current = e.api;
}}
context={{ appContext: context }}
columnDefs={columnDefs}
rowData={rowData}
/>
</div>
</div>
</React.Fragment>
);
};
render(<Grid />, document.querySelector('#root'));

Combine Treeview Mui 5 and Datagrid to display but error as follows

I have 2 components:
1 is the treeview created as follows:
const renderTree = (nodes) => (
<TreeItem key={nodes.id} nodeId={nodes.id ? nodes.id : "defaultNodeId"} label={nodes.name ? nodes.name : "defaultNodeName"}
>
{Array.isArray(nodes.children)
? nodes.children.map((node) => renderTree(node))
: null}
</TreeItem>
);
<TreeView
aria-label="gmail"
defaultCollapseIcon={<ArrowDropDownIcon />}
defaultExpandIcon={<ArrowRightIcon />}
defaultEndIcon={<div style={{ width: 18 }} />}
defaultExpanded={['72']}
sx={{ height: 'auto', flexGrow: 1, maxWidth: 250, overflowY: 'auto' }}
onNodeSelect={handleSelectedItems}
>
{renderTree(data)}
</TreeView>
2 is the datagrid created as follows:
<DataGrid
rows={gridrows}
columns={columns}
pageSize={10}
rowsPerPageOptions={[10]}
{/*//checkboxSelection*/}
{/*//disableRowSelectionOnClick*/}
/>
When I click on the node on the TreeView, the below function will be executed:
const handleSelectedItems = (event, nodeId) => {
console.log('ON fetchGridData', nodeId);
event.preventDefault();
event.stopPropagation();
fetchGridData(nodeId);
event.preventDefault();
event.stopPropagation();
}
I have selected the data, but Treenode does not expand. Does anyone know what the error is? Please help me
I was able to select data and fill in datagrid, but every time I fill the treeview doesn't open node expand

Markers clustering in #react-native-mapbox-gl/maps

I am using #react-native-mapbox-gl/maps and I want to implement clustering for markers. I couldn't find any solution for my implementation. Attach image will show that two markers should be combined but they are not.
Below I am pasting my code:
<MapboxGL.MapView
showUserLocatin={true}
zoomLevel={10}
zoomEnabled={zoomEnabled}
pitchEnabled={true}
onPress={onMapPress}
onRegionIsChanging={onRegionIsChanging}
surfaceView={true}
rotateEnabled={rotateEnabled}
compassEnabled={false}
showUserLocation={false}
userTrackingMode={MapboxGL.UserTrackingModes.None}
scrollEnabled={scrollEnabled}
styleURL={styleURL}
centerCoordinate={getFocusPoint() || getStartingPoint()}
ref={(c) => (_map = c)}
onRegionDidChange={onRegionChange}
style={style}
cluster
>
{renderLines()}
<MapboxGL.SymbolLayer
id={'abc'}
sourceID={MapboxGL.StyleSource.DefaultSourceID}
/>
<MapboxGL.Camera
zoomLevel={zoom}
centerCoordinate={getFocusPoint() || getStartingPoint()}
/>
{(simplePlaceData?.length > 0 || places?.length > 0) && renderMarkers()}
</MapboxGL.MapView>
Below is our renderMarkers function( basically I am displaying any RN component like image/icon inside MapboxGL.PointAnnotation):
const renderMarkers = () => {
if (simplePlaceData)
return simplePlaceData?.map((_place) => {
const {lat, lng, id} = _place
const latVal = parseFloat(lat)
const lngVal = parseFloat(lng)
if (!lat || !lng || isNaN(latVal) || isNaN(lngVal)) return null
return (
<MapboxGL.PointAnnotation
key={`${id}`}
id={`${id}`}
title={`${lat}-${lng}`}
coordinate={[parseFloat(lng), parseFloat(lat)]}>
<Col style={styles.mapMarkers}>
<Icon
name={'map-marker'}
family={'materialCommunity'}
color={Colors.linkBlue}
size={31}
/>
</Col>
</MapboxGL.PointAnnotation>
)
})
else
return places?.length > 0 && places.map(_place => {
const {lat, lng, id, image, name} = _place.trip_place.place
const isSelected = (getFocusPoint() || getStartingPoint())?.first() == lng &&
(getFocusPoint() || getStartingPoint())?.last() == lat
if (Platform.OS === 'ios') {
return (
<MapboxGL.PointAnnotation
key={`${id}`}
id={`${id}`}
title={name}
coordinate={[parseFloat(lng), parseFloat(lat)]}
>
<MapMarker
image={{uri: image}}
imageSize={isSelected ? 41 : 31}
style={isSelected ? styles.mapMarkersSelected : styles.mapMarkers}
onPress={() => selectPlace(_place.trip_place.place, true)}
/>
</MapboxGL.PointAnnotation>
)
} else {
return (
<MapboxGL.MarkerView
id={`${id}`}
key={`${id}`}
coordinate={[parseFloat(lng), parseFloat(lat)]}
title={name}
>
<View style={isSelected ? styles.mapMarkerContainerSelected : styles.mapMarkerContainer}>
<MapMarker
image={{uri: image}}
imageSize={isSelected ? 41 : 31}
style={isSelected ? styles.mapMarkersSelected : styles.mapMarkers}
onPress={() => selectPlace(_place.trip_place.place, true)}
/>
</View>
</MapboxGL.MarkerView>
)
}
})
}
Is there any solution to to apply for MapboxGL.PointAnnotation to show markers as a combined cluster with number of items inside? Or there is anothe component of MapboxGL which I can use to achieve this functionality?
Thanks
So from my experience with React Native Mapbox GL, you can't use point annotations for clustering. You'll have to use icons. One rule you have to keep in mind for this to work is that your markers have to be GEO JSON features collection. Checkout the link below if you don't know what that is.
https://enterprise.arcgis.com/en/geoevent/latest/ingest/GUID-F489B3D2-74DB-4EA2-8A4E-330628193843-web.png
Once you have your feature collection, you feed it into the Shapsource and clusters should start showing up.
<MapboxGL.ShapeSource
ref={shapeSource}
shape={{ type: 'FeatureCollection', features: [...''] }}
id="symbolLocationSource"
hitbox={{ width: 18, height: 18 }}
onPress={async point => {
if (point.features[0].properties.cluster) {
const collection = await shapeSource.current.getClusterLeaves(
point.features[0],
point.features[0].properties.point_count,
0,
)
// Do what you want if the user clicks the cluster
console.log(collection)
} else {
// Do what you want if the user clicks individual marker
console.log(point.features[0])
}
}}
clusterRadius={50}
clusterMaxZoom={14}
cluster
>
In order to get individual pictures for markers to show up once you zoom in; You'll need to get the image from the individual marker. So if you have a feature collection, each feature should have an image, you could either use an image stored in your project folder and replace the iconImage property in the symbol. Or if your feature has a link to an image, you could use the property in the feature like so:
iconImage: ['get', '___ whatever name you gave the link___'],
<MapboxGL.SymbolLayer
id="singlePoint"
filter={['!', ['has', 'point_count']]}
style={{
iconImage: ['get', '___ whatever name you gave the link___'],
iconSize: 0.3,
iconHaloColor: 'black',
iconHaloWidth: 10,
iconColor: 'white',
iconHaloColor: 'black',
iconHaloWidth: 400,
iconAllowOverlap: true,
}}
/>
in order to get that to show up you need mapbox images
<MapboxGL.Images
images={images}
onImageMissing={async url => {
setImages({ ...images, [url]: { uri: await getImage(url) } })
}}
/>
So that get request we did with the link, will call the mapbox images. Just make sure you have an images, and setImages in your state. This will then allow you to show the current image of your point annotation. Only problem is that it's hard to edit, so you can't just make them appear as circles unless they're cropped that way.
<MapboxGL.MapView
style={styles.map}
ref={mapRef}
styleURL="___ url___"
zoomEnabled
>
<MapboxGL.Camera
animationDuration={250}
ref={ref => (this.camera = ref)}
minZoomLevel={5}
zoomLevel={6}
maxZoomLevel={20}
animationMode="flyTo"
centerCoordinate={currrentLocation}
Level={stateZoomLevel}
/>
<MapboxGL.Images
images={images}
onImageMissing={async url => {
setImages({ ...images, [url]: { uri: await getImage(url) } })
}}
/>
{/* Cluster Individual Drop View */}
<MapboxGL.ShapeSource
ref={shapeSource}
shape={{ type: 'FeatureCollection', features: [...''] }}
id="symbolLocationSource"
hitbox={{ width: 18, height: 18 }}
onPress={async point => {
if (point.features[0].properties.cluster) {
const collection = await shapeSource.current.getClusterLeaves(
point.features[0],
point.features[0].properties.point_count,
0,
)
// Do what you want if the user clicks the cluster
console.log(collection)
} else {
// Do what you want if the user clicks individual marker
console.log(point.features[0])
}
}}
clusterRadius={50}
clusterMaxZoom={14}
cluster
>
<MapboxGL.SymbolLayer
id="pointCount"
style={layerStyles.clusterCount}
/>
<MapboxGL.UserLocation
visible
onUpdate={location => {
setCurrentLocation({
latitude: location.coords.latitude,
longitude: location.coords.longitude,
})
}}
/>
<MapboxGL.CircleLayer
id="clusteredPoints"
belowLayerID="pointCount"
filter={['has', 'point_count']}
style={{
circlePitchAlignment: 'map',
circleColor: '#A59ADD',
circleRadius: [
'step',
['get', 'point_count'],
20,
100,
25,
250,
30,
750,
40,
],
circleOpacity: 0.84,
circleStrokeWidth: 0,
circleStrokeColor: 'blue',
}}
/>
<MapboxGL.SymbolLayer
id="singlePoint"
filter={['!', ['has', 'point_count']]}
style={{
iconImage: ['get', '__image name___'],
iconSize: 0.3,
iconHaloColor: 'black',
iconHaloWidth: 10,
iconColor: 'white',
iconHaloColor: 'black',
iconHaloWidth: 400,
iconAllowOverlap: true,
}}
/>
</MapboxGL.ShapeSource>
</MapboxGL.MapView>
const layerStyles = {
singlePoint: {
circleColor: 'green',
circleOpacity: 0.84,
circleStrokeWidth: 2,
circleStrokeColor: 'white',
circleRadius: 5,
circlePitchAlignment: 'map',
},
clusteredPoints: {},
clusterCount: {
textField: '{point_count}',
textSize: 12,
textPitchAlignment: 'map',
},
}
If this helped upvote!

Make react-leaflet map component resize itself to fit available space

Setting the width of the Map component works, but height seems to only respond to an absolute size in pixels.
Is it possible to make the map control automatically consume available space inside the parent element?
I was able to create a component that passed in a component state value for the height of the map. I created a helper function that would resize the height to make it all responsive.
...
export default class MapContainer extends React.Component {
updateDimensions() {
const height = window.innerWidth >= 992 ? window.innerHeight : 400
this.setState({ height: height })
}
componentWillMount() {
this.updateDimensions()
}
componentDidMount() {
window.addEventListener("resize", this.updateDimensions.bind(this))
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateDimensions.bind(this))
}
...
render() {
...
return (
<div class="map-container" style={{ height: this.state.height }}>
<Map>
...
</Map>
</div>
)
}
}
I solved this by using the react-container-dimensions component, which passes width/height as props to its child:
<div className="div that can resize""
ContainerDimensions>
<MyMapContainer/>
</ContainerDimensions>
</div>
...
style.width = Math.floor(this.props.width);
return (
<Map style={style}
Setting min-height & height to 100% on MapContainer worked form me:
<div style={{ height: "175px" }}><MapContainer style={{ height: "100%", minHeight: "100%" }} ...</div>
I solved this by setting the height of leaflet-container to 100% in my .css file.
.leaflet-container {
height: 100%;
}
With my Map component looking like this:
const Map = () => {
return (
<MapContainer center={[51.4381, 5.4752]} zoom={10} >
<TileLayer
maxZoom='20'
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
</MapContainer>
)
}
export default Map;

Drag and drop with touch support for react.js

How to implement drag and drop for Facebooks' react.js with support for touch events?
There's a couple of questions and articles and libraries about drag and drop for react.js, but none of them seems to mention touch events, and none of the demo's work on my phone.
In general I wonder what would be the easiest: Try to implement this using existing d&d libraries which already support touch, but may need some work to properly work together with react. Or try to use any of the react d&d examples and make them work with touch (which, seeing this issue, may not be trivial?)
react-motion (with touch events)
We have tried "react-motion" for dragging items in a list. With more than 15-20 items it becomes really laggy. (But with small list it works good, like in this demo). Be aware that mobile devices are much slower than desktops.
Important note about react-motion: Don't forget to use production mode when testing your animation's performance!
react-dnd (with touch events)
The second option was "react-dnd". It is a great library. It is low level, however, it is rather easy to understand how to work with it. But at first, "react-dnd" was not an option for us because of no touch events support.
Later, when Yahoo had released react-dnd-touch-backend we decided to switch our app from "react-motion" to "react-dnd". This solved all our performance issues. We have list 50-70 items and it just works as expected.
Yahoo has done really good work and the solution works in our production apps.
You already mentioned react-dnd and I make PR that enable dnd for touch devices so you can try it
I haven't found any answer to this yet. The accepted answer is not really an answer but it points to a github library. I am going to try to include here a complete answer using only react.
Here it goes, the code should be self explanatory, but a couple of words ahead of time. We need to use a lot of state variables to keep state between renders, otherwise any variables get reset out. To make the transitions smooth, I update the position once a render was completed using useEffect hook. I tested this in codesandbox, I'm including the link here for anybody to edit the code and play with it, just fork it. It workd with the MS Surface Book2 Pro
and Android. It has a formatting problem with the iPhone IOS. Both for Safari and Chrome. If somebody fixes it that'd be great. For now I have what I need and claim success.
Here are the files under src in codesandbox.io:
App.js
import "./styles/index.pcss";
import "./styles/tailwind-pre-build.css";
import Photos from "./Photos.js";
export default function App() {
return (
<>
<div className="flow-root bg-green-200">
<div className="my-4 bg-blue-100 mb-20">
Drag and Drop with touch screens
</div>
</div>
<div className="flow-root bg-red-200">
<div className="bg-blue-100">
<Photos />
</div>
</div>
</>
);
}
Photos.js:
import React, { useState } from "react";
import "./styles/index.pcss";
import Image from "./image";
export default function Photos() {
const [styleForNumber, setStyleForNumber] = useState({
position: "relative",
width: "58px",
height: "58px"
});
const photosArray = [
"https://spinelli.io/noderestshop/uploads/G.1natalie.1642116451444",
"https://spinelli.io/noderestshop/uploads/G.2natalie.1642116452437",
"https://spinelli.io/noderestshop/uploads/G.3natalie.1642116453418",
"https://spinelli.io/noderestshop/uploads/G.4natalie.1642116454396",
"https://spinelli.io/noderestshop/uploads/G.5natalie.1642116455384",
"https://spinelli.io/noderestshop/uploads/G.6natalie.1642116456410",
"https://spinelli.io/noderestshop/uploads/G.7natalie.1642116457466",
"https://spinelli.io/noderestshop/uploads/G.8natalie.1642116458535",
"https://spinelli.io/noderestshop/uploads/G.0natalie.1642116228246"
];
return (
<>
<div
className="w-1/2 bg-green-200"
style={{
display: "grid",
gridTemplateColumns: "[first] 60px [second] 60px [third] 60px",
gridTemplateRows: "60px 60px 60px",
rowGap: "10px",
columnGap: "20px",
position: "relative",
justifyContent: "center",
placeItems: "center"
}}
>
{photosArray.map((photo, i) => (
<div
className="relative z-1 h-full w-full flex flex-wrap content-center touch-none"
key={i}
>
<div className="contents">
<Image photo={photo} i={i} />
</div>
</div>
))}
</div>
</>
);
}
Image.js:
import React, { useRef, useState, useEffect } from "react";
import "./styles/index.pcss";
export default function Image({ photo, i }) {
const imgRef = useRef();
const [top, setTop] = useState(0);
const [left, setLeft] = useState(0);
const [drag, setDrag] = useState(false);
const [styleForImg, setStyleForImg] = useState({
position: "absolute",
width: "58px",
height: "58px"
});
const [offsetTop, setOffsetTop] = useState(-40);
const [offsetLeft, setOffsetLeft] = useState(0);
const [xAtTouchPointStart, setXAtTouchPointStart] = useState(0);
const [yAtTouchPointStart, setYAtTouchPointStart] = useState(0);
useEffect(() => {
if (drag) {
setStyleForImg({
position: "relative",
width: "58px",
height: "58px",
top: top,
left: left
});
} else {
setStyleForImg({
position: "relative",
width: "58px",
height: "58px"
});
}
console.log("style: ", styleForImg);
}, [drag, top, left]);
const handleTouchStart = (e, i) => {
e.preventDefault();
let evt = typeof e.originalEvent === "undefined" ? e : e.originalEvent;
let touch = evt.touches[0] || evt.changedTouches[0];
const x = +touch.pageX;
const y = +touch.pageY;
console.log(
"onTouchStart coordinates of icon # start: X: " + x + " | Y: " + y
);
console.log("dragged from position n = ", i + 1);
// get the mouse cursor position at startup:
setXAtTouchPointStart(x);
setYAtTouchPointStart(y);
setDrag(true);
};
const handleTouchEnd = (e) => {
// if (process.env.NODE_ENV === 'debug5' || process.env.NODE_ENV === 'development') {
e.preventDefault();
setDrag(false);
console.log(
new Date(),
"onTouchEnd event, coordinates of icon # end: X: " +
e.changedTouches[0]?.clientX +
" | Y: " +
e.changedTouches[0]?.clientY +
" | top: " +
top +
" | left: " +
left
);
};
const handleElementDrag = (e) => {
e = e || window.event;
e.preventDefault();
let x = 0;
let y = 0;
//Get touch or click position
//https://stackoverflow.com/a/41993300/5078983
if (
e.type === "touchstart" ||
e.type === "touchmove" ||
e.type === "touchend" ||
e.type === "touchcancel"
) {
let evt = typeof e.originalEvent === "undefined" ? e : e.originalEvent;
let touch = evt.touches[0] || evt.changedTouches[0];
x = +touch.pageX; // X Coordinate relative to the viewport of the touch point
y = +touch.pageY; // same for Y
} else if (
e.type === "mousedown" ||
e.type === "mouseup" ||
e.type === "mousemove" ||
e.type === "mouseover" ||
e.type === "mouseout" ||
e.type === "mouseenter" ||
e.type === "mouseleave"
) {
x = +e.clientX;
y = +e.clientY;
}
console.log("x: ", x, "y: ", y);
// calculate the new cursor position:
const xRelativeToStart = x - xAtTouchPointStart;
console.log(
"xRel = ",
x,
" - ",
xAtTouchPointStart,
" = ",
xRelativeToStart
);
const yRelativeToStart = y - yAtTouchPointStart;
console.log(
"yRel = ",
y,
" - ",
yAtTouchPointStart,
" = ",
yRelativeToStart
);
// setXAtTouchPointStart(x); // Reseting relative point to current touch point
// setYAtTouchPointStart(y);
// set the element's new position:
setTop(yRelativeToStart + "px");
setLeft(xRelativeToStart + "px");
console.log("top: ", yRelativeToStart + "px");
console.log("Left: ", xRelativeToStart + "px");
};
const handleDragEnd = (e) => {
// if (process.env.NODE_ENV === 'debug5' || process.env.NODE_ENV === 'development') {
console.log(
new Date(),
"Coordinates of icon # end X: " + e.clientX + " | Y: " + e.clientY
);
};
const handleDragStart = (e, i) => {
// From https://stackoverflow.com/a/69109382/15355839
e.stopPropagation(); // let child take the drag
e.dataTransfer.dropEffect = "move";
e.dataTransfer.effectAllowed = "move";
console.log(
"Coordinates of icon # start: X: " + e.clientX + " | Y: " + e.clientY
);
// console.log ('event: ', e)
console.log("dragged from position n = ", i + 1);
};
return (
<img
ref={imgRef}
className="hover:border-none border-4 border-solid border-green-600 mb-4"
src={photo}
alt="placeholder"
style={styleForImg}
onDragStart={(e) => handleDragStart(e, i)}
onDragEnd={handleDragEnd}
onTouchStart={(e) => handleTouchStart(e, i)}
onTouchEnd={handleTouchEnd}
onTouchMove={handleElementDrag}
></img>
);
}
index.js:
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import "./styles/index.pcss";
import App from "./App";
const root = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<App />
</StrictMode>,
root
);
styles.css:
.Main {
font-family: sans-serif;
text-align: center;
}
/styles/index.pcss:
#tailwind base;
#tailwind components;
#tailwind utilities;
I couldn't make tailwinds grid work, so I used the actual css inline styles. No idea why they didn't in codesandbox.