Simplifying a GeoJson FeatureCollection using mapshaper causes issues - reactjs

More specifically, it causes the same code to render an outline on the map, and only recognizes the last path element when using pointer events.
I simplified a continents.json file on mapshaper using their website and replaced the file in my public/data folder. Currently I'm fetching the data in one component and passing it down to another.
const WorldMapAtlas = () => {
const [continents, continents] = useState<null | FeatureCollection>(null)
useEffect(() => {
fetch('data/continents_simple.json')
.then(response => response.json())
.then((worldData) => {
let continents: FeatureCollection = (worldData as FeatureCollection)
setContinents(continents)
})
.catch(error => console.log(error))
}, [])
return (
<>
{continents &&
<div className="w-3/4" >
<TestMap projectionType={geoNaturalEarth1} GeoJson={continents} size={[928, 454]} translate={[464, 227]} />
</div>
}
</>
)
}
I then try to render it with the TestMap Component
interface props {
projectionType: () => GeoProjection;
GeoJson: FeatureCollection | null;
size: [number, number];
translate: [number, number];
}
const TestMap = (props: props) => {
const svgRef = useRef(null)
const [height, width] = props.size;
useEffect(() => {
//accesses the reference element
const svg = d3.select(svgRef.current)
//declares the geoJson, projection, and path(pathGenerator)
const geoJson = props.GeoJson
const projection = props.projectionType()
.fitSize(props.size, geoGraticule10())
.translate(props.translate)
const path = d3.geoPath().projection(projection)
//uses d3 to inject the data into the svg element
const features = svg.selectAll(".country")
.data(geoJson ? geoJson.features : [])
.enter().append("path")
//basic styling attributes
.attr("d", path)
.attr("fill", "none")
.attr("stroke", "aliceblue")
.attr("stroke-width", "0.5px")
//allows pointer events anywhere inside the path even when fill=none
.attr("pointer-events", "visibleFill")
.attr("visibility", "visible")
//sets the path element id to the continent name
.attr("id", (d) => {
return `${d.properties.continent}`
})
//selects the current continent and highlights it by increasing the stroke width
.on("mouseover", (d,i) => {
svg.select(`#${i.properties.continent}`).attr("stroke-width", "2px")
})
//deselects
.on("mouseleave", (d,i) => {
svg.select(`#${i.properties.continent}`).attr("stroke-width", "0.5px")
})
//adds the .country attribute to allow for later updates on the svg element
.attr("class", "country")
}, [geoJson]);
return (
<div className="bg-blue-400">
<svg ref={svgRef} viewBox={`0 0 ${props.size[0]} ${props.size[1]}`} />
</div>
)
}
export default TestMap
When I use the unsimplifed json file it works fine. Each country is highlighted, but the strokewidth redraw takes a while. When all I do is change the fetch in WorldMapAtlas to the simplified json, an outline appears (which does not seems to be specific to one path, only disappears when all paths elements are deleted (dev tools)), and only the last feature in the json gets highlighted no matter where the cursor is.
The red dot is where my cursor is.
I've spent a lot of time on this, and I appreciate any help I could get! Thank you

After trying out a lot of different simplify options I feel silly. All you have to do is add the gj2008 option to the output command.
gj2008 [GeoJSON] use original GeoJSON spec (not RFC 7946)
This fixed all the problems! Although I have no idea why, I'm not sure how to check the original GeoJSON spec, and I'm not sure what the differences are. Hope this helps anyone who ran into the same problem!

Related

Ant Design Upload component rescales images to 200x200 [React]

I met an issue at Ant Design <Upload /> component. When I upload an image it sends two Img typed requests at the Network console. First contains my orginal image (tested at 650x650 image size), second contains transformed image to 200x200. And here is my question: Why image is rescaled to lower resolution and how can I prevent that? I want to upload orignal images with transparency if that is required.
In my case Upload component is used to upload single image. Component is placed on modal component that is why I prepared dummyRequest.
const UploadImage = ({
defaultValue,
maxFilesAmount,
}: IUploadImageProps<any>): JSX.Element => {
const [fileList, setFileList] = useState<IUploadFile<any>[]>(
defaultValue ?? []
)
const onChange = useCallback(
(info: IUploadChangeParam<IUploadFile<unknown>>): void => {
setFileList(info.fileList)
},
[setFileList]
)
const onPreview = useCallback(async (file: IUploadFile): Promise<void> => {
let src: string | undefined = file?.url ?? file?.thumbUrl ?? ''
if (!_.isNull(src)) {
src = await convertImportImageToBase64(src)
}
const image: HTMLImageElement = new Image()
const imgWindow: Window | null = window.open(src)
image.src = src
imgWindow?.document.write(image.outerHTML)
}, [])
const dummyRequest = useCallback(({ onSuccess }: IUploadRequestOption) => {
setTimeout(() => {
//#ts-ignore
onSuccess('ok')
}, 0)
}, [])
const beforeUpload = useCallback((): false => {
//Required to ignore Ant Desing default converting uploaded image
//For example that removes transparency from png files
return false
}, [])
return (
<Upload
accept={'.jpg, .jpeg, .png'}
listType={'picture-card'}
beforeUpload={beforeUpload}
fileList={fileList}
onChange={onChange}
onPreview={onPreview}
customRequest={dummyRequest}
>
{_.size(fileList) < (maxFilesAmount ?? 1) && 'Upload'}
</Upload>
)
}
At the start I had also a problem with losing transparency at .png images. Image still was defined as png but transparent background got a white color. Used beforeUpload fix that, I not sure why but okej :).
What can I do more to fix this issue? Thanks in advance.

ReactJS-Leaflet: upload csv file in leaflet

here I have a question regarding references to creating a feature in the react leaflet.
So, the feature has the following functions =
The user page when they want to upload a location is in the form of a csv file containing latitude and longitude.
When the user clicks the red button above, a popup will appear to upload the csv file.
When finished uploading the csv file, it will go directly to the location based on latitude and longitude.
So my question is, does anyone have a tutorial on how to create a csv upload button that points directly to a map with reactjs and leaflets? Thank you very much
Although you have not asked to use react-leaflet I would advise you to do so because you will end up in a mess when you will have to export the map reference to reuse it across places.
First create a button that will handle the upload of a csv file. There is a really useful guide to do so without the use of libraries like paparse although it simplifies a lot this procedure. Next you need to transform the csv columns to some form of data to use. This is also included in the guide. You end up with an array of csv columns.
Then all you need to do is to create a custom react-leaflet component to render the markers and zoom to the markers viewport.
Also you can clean the csv file once you insert a new one.
function App() {
const [csvToArray, setCsvToArray] = useState([]);
const fileReader = new FileReader();
const csvFileToArray = (string) => {
const csvHeader = string.slice(0, string.indexOf("\n")).split(",");
const csvRows = string.slice(string.indexOf("\n") + 1).split("\n");
const array = csvRows.map((i) => {
const values = i.split(",");
const obj = csvHeader.reduce((object, header, index) => {
object[header] = values[index];
return object;
}, {});
return obj;
});
setCsvToArray(array);
};
const handleOnChange = (e) => {
if (csvFileToArray.length > 0) setCsvToArray([]);
const file = e.target.files[0];
if (file) {
fileReader.onload = function (event) {
const text = event.target.result;
csvFileToArray(text);
};
fileReader.readAsText(file);
}
};
console.log(csvToArray);
return (
<>
<input
type="file"
id="csvFileInput"
accept=".csv"
onChange={handleOnChange}
/>
<Map csvToArray={csvToArray} />
</>
);
}
function RenderCsvToArray({ csvToArray }) {
const map = useMap();
useEffect(() => {
if (csvToArray.length === 0) return;
const myPoints = csvToArray.map(({ Latitude, Longitude }) => [
Latitude,
Longitude
]);
const myBounds = new L.LatLngBounds(myPoints);
map.fitBounds(myBounds);
}, [csvToArray]);
return csvToArray?.map(({ Latitude, Longitude, Title }, index) => (
<Marker key={index} icon={icon} position={[Latitude, Longitude]}>
<Popup>{Title}</Popup>
</Marker>
));
}
You can see the full implementation on the demo
I have also inlcuded two csv files to play with in the form of
Title,Latitude,Longitude
Trinity College,41.745167,-72.69263
Wesleyan University,41.55709,-72.65691
and
Group,Title,Image,Description,Address,Latitude,Longitude
a,Trinity College,https://www.edx.org/sites/default/files/trinity1.jpg,"Not in the link view website more not in the link","300 Summit St - Hartford CT 06106,41.745167,-72.69263
a,Wesleyan University,https://upload.wikimedia.org/wikipedia/commons/4/41/You_are_here_-_T-shirt.jpg,"view website",45 Wyllys Ave Middletown CT 06459,41.55709,-72.65691

React Infinite Loading hook, previous trigger

Im trying to make a hook similar to Waypoint.
I simply want to load items and then when the waypoint is out of screen, allow it to load more items if the waypoint is reached.
I can't seem to figure out the logic to have this work properly.
Currently it see the observer state that its on the screen. then it fetches data rapidly.
I think this is because the hook starts at false everytime. Im not sure how to make it true so the data can load. Followed by the opposite when its reached again.
Any ideas.
Here's the hook:
import { useEffect, useState, useRef, RefObject } from 'react';
export default function useOnScreen(ref: RefObject<HTMLElement>) {
const observerRef = useRef<IntersectionObserver | null>(null);
const [isOnScreen, setIsOnScreen] = useState(false);
useEffect(() => {
observerRef.current = new IntersectionObserver(([entry]) => {
if (isOnScreen !== entry.isIntersecting) {
setIsOnScreen(entry.isIntersecting);
}
});
}, []);
useEffect(() => {
observerRef.current.observe(ref.current);
return () => {
observerRef.current.disconnect();
};
}, [ref]);
return isOnScreen;
}
Here's the use of it:
import React, { useRef } from 'react';
import { WithT } from 'i18next';
import useOnScreen from 'utils/useOnScreen';
interface IInboxListProps extends WithT {
messages: any;
fetchData: () => void;
searchTerm: string;
chatID: string | null;
}
const InboxList: React.FC<IInboxListProps> = ({ messages, fetchData, searchTerm, chatID}) => {
const elementRef = useRef(null);
const isOnScreen = useOnScreen(elementRef);
if (isOnScreen) {
fetchData();
}
const renderItem = () => {
return (
<div className='item unread' key={chatID}>
Item
</div>
);
};
const renderMsgList = ({ messages }) => {
return (
<>
{messages.map(() => {
return renderItem();
})}
</>
);
};
let messagesCopy = [...messages];
//filter results
if (searchTerm !== '') {
messagesCopy = messages.filter(msg => msg.user.toLocaleLowerCase().startsWith(searchTerm.toLocaleLowerCase()));
}
return (
<div className='conversations'>
{renderMsgList({ messages: messagesCopy })}
<div className='item' ref={elementRef} style={{ bottom: '10%', position: 'relative',backgroundColor:"blue",width:"5px",height:"5px" }} />
</div>
);
};
export default InboxList;
Let's inspect this piece of code
const [isOnScreen, setIsOnScreen] = useState(false);
useEffect(() => {
observerRef.current = new IntersectionObserver(([entry]) => {
if (isOnScreen !== entry.isIntersecting) {
setIsOnScreen(entry.isIntersecting);
}
});
}, []);
We have the following meanings:
.isIntersecting is TRUE --> The element became visible
.isIntersecting is FALSE --> The element disappeared
and
isOnScreen is TRUE --> The element was at least once visible
isOnScreen is FALSE--> The element was never visible
When using a xor (!==) you specify that it:
Was never visible and just became visible
this happens 1 time just after the first intersection
Was visible once and now disappeared
this happens n times each time the element is out of the screen
What you want to do is to get more items each time the element intersects
export default function useOnScreen(ref: RefObject<HTMLElement>, onIntersect: function) {
const observerRef = useRef<IntersectionObserver | null>(null);
const [isOnScreen, setIsOnScreen] = useState(false);
useEffect(() => {
observerRef.current = new IntersectionObserver(([entry]) => {
setIsOnScreen(entry.isIntersecting);
});
}, []);
useEffect(()=?{
if(isOnScreen){
onIntersect();
}
},[isOnScreen,onIntersect])
...
}
and then use it like:
const refetch= useCallback(()=>{
fetchData();
},[fetchData]);
const isOnScreen = useOnScreen(elementRef, refetch);
or simply:
const isOnScreen = useOnScreen(elementRef, fetchData);
If fetchData changes reference for some reason, you might want to use the following instead:
const refetch= useRef(fetchData);
const isOnScreen = useOnScreen(elementRef, refetch);
Remember that useOnScreen has to call it like onIntersect.current()
In InboxList component, what we are saying by this code
if (isOnScreen) {
fetchData();
}
is that, every time InboxList renders, if waypoint is on screen, then initiate the fetch, regardless of whether previous fetch is still in progress.
Note that InboxList could get re-rendered, possibly multiple times, while the fetch is going on, due to many reasons e.g. parent component re-rendering. Every re-rendering will initiate new fetch as long as waypoint is on screen.
To prevent this, we need to keep track of ongoing fetch, something like typical isLoading state variable. Then initiate new fetch only if isLoading === false && isOnScreen.
Alternatively, if it is guaranteed that every fetch will push the waypoint off screen, then we can initiate the fetch only when waypoint is coming on screen, i.e. isOnScreen is changing to true from false :
useEffect(() => {
if (isOnScreen) {
fetchData();
}
}, [isOnScreen]);
However, this will not function correctly if our assumption, that the waypoint goes out of screen on every fetch, does not hold good. This could happen because
pageSize of fetch small and display area can accommodate more
elements
data received from a fetch is getting filtered out due to
client side filtering e.g. searchTerm.
As my assumption. Also you can try this way.
const observeRef = useRef(null);
const [isOnScreen, setIsOnScreen] = useState(false);
const [prevY, setPrevY] = useState(0);
useEffect(()=>{
fetchData();
var option = {
root : null,
rootmargin : "0px",
threshold : 1.0 };
const observer = new IntersectionObserver(
handleObserver(),
option
);
const handleObserver = (entities, observer) => {
const y = observeRef.current.boundingClientRect.y;
if (prevY > y) {
fetchData();
}
setPrevY(y);
}
},[prevY]);
In this case we not focus chat message. we only focus below the chat<div className="item element. when div element trigger by scroll bar the fetchData() calling again and again..
Explain :
In this case we need to use IntersectionObserver for read the element position. we need to pass two parameter for IntersectionObserver.
-first off all in the hanlderObserver you can see boundingClientRect.y. the boundingClientRect method read the element postion. In this case we need only y axis because use y.
when the scrollbar reach div element, y value changed. and then fetchData() is trigger again.
root : This is the root to use for the intersection. rootMargin : Just like a margin property, which is used to provide the margin value to the root either in pixel or in percent (%) . threshold : The number which is used to trigger the callback once the intersection’s area changes to be greater than or equal to the value we have provided in this example .
finally you can add loading status for loading data.
return (
<div className='conversations'>
{renderMsgList({ messages: messagesCopy })}
<div className='item' ref={observeRef} style={{ bottom: '10%', position: 'relative',backgroundColor:"blue",width:"5px",height:"5px" }} />
</div>
);
};
I hope its correct, i'm not sure. may it's helpful someone. thank you..

React TypeScript: How to set multiple files to state?

I'm facing a problem, probably a very simple one, but I'm stuck in since hours so I would like to ask for your help.
I have an simple File Input and I want to set this files to state to upload the files later when submitting the form.
const [inputTattoos, setInputTattoos] = useState<[{}]>();
const handleImageChange = async ({
currentTarget: input,
}: React.ChangeEvent<HTMLInputElement>) => {
if (input.files === null) return;
console.log(input.files);
setInputTattoos([{ ...input.files }]);
With this code I'm able to write the files into state, but this is not the way I want to store it on State because my State looks like this:
I have an Array and inside it an object with objects. What I'm actually get from input.files is just an array with objects but I'm not able to store this input.files like I get it on my console. I tried a lot of solutions but this is the only way I found which works. With other solutions I always get an empty object or a FileList(undefined) in State for example with this solution:
const [inputTattoos, setInputTattoos] = useState<FileList>()
const handleImageChange = async ({
currentTarget: input,
}: React.ChangeEvent<HTMLInputElement>) => {
if (input.files === null) return;
console.log(input.files);
setInputTattoos(input.files);
What's wrong here? Thank you!
I would just store the file objects, no object wrappers or anything:
const [inputTattoos, setInputTattoos] = useState<File[]>([]);
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^−−^^
const handleImageChange = ({
currentTarget: {files},
}: React.ChangeEvent<HTMLInputElement>) => {
if (files && files.length) {
setInputTattoos(existing => [...existing, ...files]);
// −−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}
// ...
}
A couple of notes on that:
I've removed async. Change handlers aren't async functions, and nothing is going to use the promise an async function would return.
I've destructured the files property into its own parameter, so that TypeScript knows it can't change between the guard and the state setter callback. (That's also important because you're not supposed to access properties from React's synthetic event objects asynchronously unless you call persist.)
I've used the callback version of the state setter. That's important when setting a state item based on its existing value (in this case, the previous contents).
The above relies on files (from the input) being iterable, which it is in modern browsers but not in some slightly-older browsers.
Re #4, if you need to work around that for slightly-older browsers:
const [inputTattoos, setInputTattoos] = useState<File[]>([]);
const handleImageChange = ({
currentTarget: {files},
}: React.ChangeEvent<HTMLInputElement>) => {
if (files && files.length) {
setInputTattoos(existing => existing.concat(Array.from(files))); // *** Only change is here
}
// ...
}
The change there is the callback:
existing => existing.concat(Array.from(files))
Note that since files is a FileList, not an array, we need to convert it to an array for concat to handle it properly.
Array.from is just a couple of years old but easily polyfilled; if you don't want to do that, here's an alternative using nothing modern (other than the arrow function):
existing => existing.concat(Array.prototype.slice.call(files))
Here's a complete example using Array.from for that part:
const { useState } = React;
function Example() {
const [inputTattoos, setInputTattoos] = useState/*<File[]>*/([]);
const [inputKey, setInputKey] = useState(0);
const handleImageChange = ({
currentTarget: {file},
}/*: React.ChangeEvent<HTMLInputElement>*/) => {
if (files && files.length) {
setInputTattoos(existing => existing.concat(Array.from(files)));
}
// Reset the input by forcing a new one
setInputKey(key => key + 1);
}
return (
<form>
Selected tattoos ({inputTattoos.length}):
<ul>
{inputTattoos.map((file, index) =>
<li key={index}>{file.name}</li>
)}
</ul>
Add: <input key={inputKey} type="file" onChange={handleImageChange} />
</form>
);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
Here's a running TypeScript version.

Recharts stacked area show wrong yAxis labels?

http://recharts.org/en-US/examples/StackedAreaChart
This example shows, and I also made pointers that the value of the values do not correspond to the label why is this happening?
Is there any explanation and how to fix it?
You can customize the tooltip using its content prop.
The component passed there receives the same props as the Rechart's Tooltip so you only need to override the needed and bypass the rest. If you don't want to change the visual part of tooltip you can return another Tooltip at the end.
// Do not pass the `content` since it causes a recursive rendering
const CumulativeTooltip = ({ content, payload, ...props }) => {
const values = payload.map(({ value }) => value);
const cumulativePayload = payload.reduce((result, item, index) => result.concat({
...item,
value: values.slice(0, index + 1).reduce((sum, v) => sum + v, 0)
}), []);
return (<Tooltip {...props} payload={cumulativePayload} />);
}
// ... later in chart
<Tooltip content={<CumulativeTooltip/>}/>
Playground

Resources