zcreativelabs / react-simple-maps

Beautiful React SVG maps with d3-geo and topojson using a declarative api.
https://www.react-simple-maps.io/
MIT License
3.04k stars 424 forks source link

Incredibly slow performance due to heavy topography files #219

Closed saranshbansal closed 3 years ago

saranshbansal commented 3 years ago

Hi, i’m loading the full world map to show all the users of my application spread across the globe. I’m using one of the topography file which is ~4MB in size. This is my component:

import PropTypes from 'prop-types';
import React from 'react';
import { ComposableMap, Geographies, Geography, Marker } from 'react-simple-maps';
const worldMapJson = require('../../../images/world-10m.json');
const WorldMap = ({ markers }) => {
  return (
    <ComposableMap
      projection="geoMercator"
      projectionConfig={{
        scale: 100,
      }}
      height={400}
    >
      <Geographies geography={worldMapJson}>
        {({ geographies }) =>
          geographies.map(geo => <Geography key={geo.rsmKey} geography={geo} fill="#EAEAEC" stroke="#888" />)
        }
      </Geographies>
      {markers.map(({ key, coordinates }) => (
        <Marker key={key} coordinates={coordinates}>
          <circle r={3} fill="#3ca" stroke="#065" strokeWidth={1} />
        </Marker>
      ))}
    </ComposableMap>
  );
};
WorldMap.propTypes = {
  markers: PropTypes.array.isRequired,
};
export default WorldMap;

I’ve tried with 50 markers then 5000 markers, the performance is equally awful. The who page stutters when scrolling through this map. It is specially slow when my curser is anywhere on the map when i’m scrolling the page. I’ve downloaded the file locally and then using it but it made no difference. Can you help with tips to improving performance?

It looks gorgeous though. CE538FE4-804C-4E92-A8D7-9147620ECC52

zimrick commented 3 years ago

Hi @saranshbansal,

With around 5000 users the performance drop is quite normal since SVG has natural limits to how many items you can display in a way that will still be performant. Once you get into the 1000s it can get pretty bad regardless of what you do. With 50 there should definitely be no issues though.

However, there are a couple of things you could try:

  1. The world-10m file is extremely detailed. It's definitely too much detail for a map showing user dots on a world map like yours. I would try the world-110m instead. This will make the biggest difference.

  2. There is a huge performance drop with svg when using stroke on paths. If you don't necessarily need the country borders, you could try removing the stroke. If the country borders are important, then of course this is not an option.

  3. If hovering on the geographies is not important, you could also add a style={{ pointerEvents: "none" }} css rule on the Geographies component. That would bypass all events when hovering over the geographies. Markers would still react properly though.

  4. I don't know about the rest of the code on the page, but you could also check to make sure that there are no rerenders happening on the parent component (could be the case if you use parallax or some kind of other scrolling events). If the map is being rerendered on scroll that would definitely make the page stutter.

Small side note, I would recommend not using the Mercator projection. It's not the best projection to use for a world map for a number of reasons (e.g. weird size relationships between countries). The default geoEqualEarth projection is a better option.

https://www.vox.com/world/2016/12/2/13817712/map-projection-mercator-globe

https://www.axismaps.com/guide/general/map-projections/

https://www.esri.com/about/newsroom/arcuser/equal-earth/

Hope the above helps you improve the performance.

saranshbansal commented 3 years ago

Hi @zimrick thanks for these valuable tips. I’ll try them asap. Regarding the mercator projection, it’s the only one which produces a proper world map without any funny distortion. I’ve tried all of them. As you can see in the screenshot, it looks just a perfect flat projection of earth. I’ll see of there is any performance improvements when using any other projection. If there is, i’ll definitely switch. No rerenders are happening for sure. Neither on page loads, nor on scrolls. I’ll apply your tips and revert.

zimrick commented 3 years ago

Yup, you should see quite a significant improvement by switching out the topojson.

Regarding distortion etc. I get that you want a flat world map, but it's important to note that the Mercator projection is an incredibly distorted representation of the world. You can check this out:

https://thetruesize.com/

If you are set on a flat representation without the curvature, you could also try:

https://github.com/d3/d3-geo-projection#geoPatterson https://github.com/d3/d3-geo-projection#geoMiller

They are slightly better options, since they mitigate the monstrous polar size distortion of the Mercator projection a little bit. In the end this is up to you of course, but you can always pull in any projection from the d3-geo-projection extended pack and use it with react-simple-maps. :)

Here's an example of using a custom projection from d3-geo-projection with react-simple-maps: https://codesandbox.io/s/transverse-mercator-0b6v5?file=/src/MapChart.js

saranshbansal commented 3 years ago

Hey thanks for all the suggestions. Using a lighter version of the topography and removing strokes gave a huge boost in performance.

On top of that, I tweaked my response as well to load as less data as possible. Some of the coordinates were extremely close to each other so I identified and removed all those cases and reduced the number of markers from ~5000 to ~1100. It works really well now.

zimrick commented 3 years ago

Nice!!! That sounds great. Glad it worked out :)