MAP-Link is a React-based map-manipulation tool, made to be used by Geography teachers in Japan. It allows users to fill in sections of the map with different colors, completely reset or undo those fill operations, zoom the map and also pan it with their mouse.
Finally, when the user is done with their editing, they can directly download a PNG file of their map using the download button.
This project was for a friend of mine, who is creating a database website to provide Geography teachers in Japan with resources. I embedded the app directly into his Wordpress site, and also helped him integrate a subscription system into it too.
The whole project was a lot of fun, and doing anything with SVG manipulation is always an interesting challenge.
Most of the maps are only accessibly via a paid subscription, but you can try out one of the free here.
The controls are in Japanese, but if you are using Chrome you translate them using the inbuilt translator (right click -> 'Translate to English').
Under the hood
Overview
The app is split into the controller section (where all the buttons are located) and the map itself
Essentially, the user is directly manipulating the SVG element, the inner <path> elements (which form the states/countries of the map), and also some wrapper elements around the SVG
The maps are managed and stored using a Wordpress Custom Post type + a custom field attached to the post for storing the map's SVG data
Hence, to switch to a different the map, the user needs to change the page, and the app is re-initialised every time this happens (each time with different map data)
Converting and creating the map
I created a React hook (useMap) that provides the component tree with both the functions to manipulate the map, and the map element itself
The hook declares some refs (useRef), some state objects (useState), and also some functions that will use them
Then the hook takes the raw SVG data and React-ifies it. Turning normal HTML elements into React elements. It also attaches the refs mentioned above to the sub-elements
This is all done inside a useEffect, to ensure the DOM has been loaded and the map data can be retrieved safely
Painting & undoing
The current color selected by the user is managed with React's useState hook
During the map-conversion process, click handlers are added to the inner <path> elements, so that when clicked, they change color based on this state
Clicking on a <path> element just changes its fill color
The 'undo' feature is just a stack which holds the instructions to reverse the last paint operation
And the 'reset' feature just executes 'undo' on everything in the stack and clears it
Panning & zooming
For the panning feature, calculations are run based on where the user is dragging their mouse, the size of the map dimensions, and also the level of zoom the map is currently in
The result of these calculations is then used to change the CSS transform value of one of the map wrapper elements
Zooming is similar, using the zoom state to transform a different wrapper element's CSS transform value
Converting to a PNG and downloading
This was probably the most cumbersome thing to accomplish in this project
To download an image from the browser using Javascript, one must first have the contents be painted onto a HTML5 <canvas> element
Initially I tried to use a 3rd party library called HTML2Canvas to convert my map and its wrapper elements to a canvas, where it could then be downloaded
The library couldn't quite replicate the transformations I was performing on the map's wrapper elements
I ended up having to implement this conversion myself. And with some calculations based on the map SVG dimensions and the wrapper's transformations, I was able to copy the manipulated map onto the canvas element.
From there, I was easily able to complete the PNG download function.
Exporting the project for Wordpress
I used the 3rd-party CRACO (Create React App Configuration Override) library to wrap and export the entire React project as a single JS file. This allowed me to easily load the file at the bottom of each map page.
Shoutout to this blog post for the instructions on how to do this.
Lessons learnt
In hindsight, using React was probably not necessary for this project.
Some solutions had to be over-engineered just to work around some of React's quirks
refs were awkward to work with, and the order in which I had to initialize / assign them had to be fine tuned
There was a lot of awkward deep prop drilling, and passing of functions to other functions
State wasn't a huge part of the app
Vanilla JS with some globally scoped variables would have probably worked just as well
Anyway, I think the finished product turned out great!
Let me know if you have any thoughts or questions about this project.
Learn how I made this site with NextJS, the tools used and the pain-points encountered along the way. Hear about how I implemented i18n with NextJS routing, and how I used MDX to allow a dev-centric writing experience that allows JSX