react-simple-maps icon indicating copy to clipboard operation
react-simple-maps copied to clipboard

animating map markers with stagger/delay so they don't render at the same time?

Open Unionindesign opened this issue 6 years ago • 4 comments

Hello again community! I really love react-simple-maps, but I've been hitting a wall for a few days now! I have another issue (marked as solved), but have a work project to make an interactive map to show a marker everytime makes a booking with our software. To make it more interesting, we want to show the location of the customer who made the booking as a separate marker, with a line connecting to our client (you can see how I did this here: https://github.com/zcreativelabs/react-simple-maps/issues/115)

So the issue - not a bad problem to have - is that our booking software is very popular, and we have a booking almost every second! The plan is to run a report every 15 minutes, which will pass in data for several hundred bookings - all of which render at the same time! The map will be VERY cluttered with lines and markers if we don't stagger them somehow.

So, using the .map method to render your markers, per the docs:

{bookingData.map((data, iClient) => ( ))}

Has anyone found a way add a delay or timer here?

To get the best control, a javascript animation library is my top choice. GSAP in particular has excellent timeline features for animations, but does not want to be friends with the marker components!

  • Notice inside the Marker this line of code --> ref={this.clientRef} which you will define in your constructor:

constructor(props) { super(props); this.clientRef = React.createRef(); ...

Per the React documentation, createRef is the latest way to create a reference to a DOM node...however, it is not finding the true DOM node due to use of a 3rd party component library like react-simple-maps to generate the marker component? (https://reactjs.org/docs/refs-and-the-dom.html)

  • Meanwhile, other animation libraries like react-motion, or helper libraries like react-transition-group want you to put your mapped elements or components into some kind of wrapper such as:
....

and I've also tried <StaggeredMotion> from the react-motion library. Wrapping the marker component inside another component causes the map to break, giving a 'projection is not a function' error. From what I can tell, adding any sort of

or other wrapper around the Marker component to give React context to apply animations will mess up the parent-child relationship for this library, which (tell me if I'm wrong) wants to see the Marker component inside Geographies, inside the Composable map etc.

Any help is appreciated! My backup plan, which I'm hoping to avoid, is to rebuild the map in vanilla D3-geo so I can create and control the components and DOM nodes directly! Any ideas or other experimentation, let me know!

Unionindesign avatar Jan 16 '19 18:01 Unionindesign

Hi @Unionindesign,

There is a problem currently with the way markers are handled by react-simple-maps. I made an example codesandbox with staggered custom markers using react-pose for the animation. To get around the projection issue, you can pass a custom projection to ComposableMap and then use that projection on any circle or g inside of ZoomableGroup.

Hope this helps...

zimrick avatar Jan 17 '19 00:01 zimrick

A quick update - I do have the Greensock animation platform (GSAP) up and running! A few bugs to sort out since I did use the react-motion example for zoom and panning on the map, and the two are not working together at the moment...

Good news! While the createRefs API (see the link above) sounds like your only option (At the moment) for a 3rd party component library where the creator does not expose the DOM node, react-simple-maps does give you a html tag. My solution, while it may not be best practice, is to add a className to the circle, and grab this as the target for animations. Using GSAP, you can then use their robust timeline features to scroll through your data, mimicking real time, as well as use their play, pause, stop, reverse methods pre-built into the library! The effect is staggering ;-)

Unionindesign avatar Jan 24 '19 01:01 Unionindesign

To update this post, I've unfortunately hit another roadblock :-( While working with a set of test data, my approach listed above was doing just fine for several weeks! This worked - only because the data was static and hardcoded into the app. As we move toward a production build and we're querying our database(s), fetching the data and converting it to json, this is once again breaking the animations. I believe this is a 'best-practice' issue of fetching data in a container component using the componentDidMount method, and then passing it down as props to its children...meanwhile in the child component where the map and markers live, I am also calling the animations inside componentDidMount...no animations? Seems like the map and markers have already rendered, and I haven't found a way to trigger a re-render?

I've attempted to recreate the codesandbox above but find myself stumbling through D3 tutorials - the geoAlbers projection (especially the one with counties as well as states) does not plugin so easily! I've created a custom topojson file but still getting it in the map...but once that renders my data is ready to go, animations are there, and I should be able to connect GSAP again using react transition group? Fingers crossed! Any support or ideas are much appreciated!

Unionindesign avatar Feb 14 '19 01:02 Unionindesign

Without revealing too much company code :-) here,s a few snippets. In particular I was getting an error that the geoAlbers projection was not being exported from d3-geo-projection, so I imported from D3 geo...not sure if that's the issue? Here is that section of code:

`import { geoAlbers } from "d3-geo"; //import { geoAlbers } from "d3-geo-projection";

const geoPaths = "../../../data/statesAndCountiesUSA.json"; const width = 1280; const height = 700; const customProjection = () => geoAlbers() .translate([width / 2, height / 2]) .scale(1280); `

And then further down the page...

<ComposableMap //width={1440} //height={700} projection={customProjection} projectionConfig={{ scale: 1440 }} style={mapStyles}> <ZoomableGroup center={[x, y]} zoom={zoom}> <Graticule /> <Geographies geography={geoPaths}> {(geos, proj) => geos.map( (geo, i) => console.log(geo) || ( <Geography key={geo.id + i} cacheId={geo.properties.ID_1} geography={geo} projection={proj} style={{ default: { fill: "#E5F1FC", .....etc

Thank you! Love this library! Very disappointed to lose my animations at this late stage :-(

Unionindesign avatar Feb 14 '19 01:02 Unionindesign