At the same time, I found Konva, a promising library to draw Canvas elements and make them interactive in an easy way. It allows animations, Drag and Drop, actions, filters, etc. so it’s possible to do the same things as in SVG. I didn’t find any example of Konva for drawing maps, so here we’ll find a declarative way to do it, using Svelte.
Using Konva with Svelte
Let’s see first how to plot something using Konva and Svelte. You can check the source code at Svelte REPL. Be patient, since it has to load the npm modules!
The code here is short, and we have only one component:
- Note how the div container is binded to the variable
- Since the component has to be binded, we’ll have to wait until the component is mounted to create the Konva elements
- Konva needs a Stage (cimilar to the Canvas element), one or more Layer, where you can actually draw the objects, and any shape, text or whatever we want to draw.
Konva as Svelte components
The example is not bad, but it’s not using any Svelte style coding. It would be cool declaring things like:
Then, we could use a store to put as many elements as we wanted, for instance. Let’s see how to to that. Check the interactive example here
We’ll create a component for the Stage, another for the Layer and finally, a Circle component that acually draws a circle.
The Stage component is like this:
- As in the other case, you’ll see that we need to create the node where we’ll add all the stuff, plus the binding.
- The slot will allow us to add children components. Layers in this case.
- We create a context. This way, the Layer component will have access to the Konva stage object without passing it in the props. Since this component won’t be touched when creating new drawings, it’s much cleaner.
Then the Layer component:
- This one is much simpler, since we don’t have to create HTML elements.
- We create another context for the layer, so its children can have access to it.
Finally, the Circle component is:
- We export most of the parameters so they can be used as props, although they have a default value
- We have to call layer.draw() or the object won’t be drawn. That’s because we are creating it before the layer is added into the stage.
- We have to destroy the object on deleting the component, or we’ll have memory leaks. Think if we had hundreds of circles appearing and disappearing when a store value changes…
And now the cool part. To create the drawing, we only call:
Creating a map
So now, with the knowledge from the previous examples, we can create a map. I created an intermediate (simpler) version of the map, in case somebody wants to take a look. The final version is here.
Let’s start from the other side now! The App.svelte file is this one:
- We are using a BackgroundMap component and as many Marker components as we want.
- There’s a writable store where the children will put the name of the hovered feature. In the App component we’ll just render it.
- I use two layers because when using one, the markers can’t be detected if they are over a country. z-index is not well managed in konvas.
The projection and D3 GeoPath are defined in their own file so they can be accessed from all the components:
- The size and zooms are fixed. If we wanted a more complex map, we could make this a store and then chenge the values from a slider or a mouse event
The BackgroundMap component is:
- We can only draw when the file with the data is downloaded, so we’ll put this in an onMount element
- We iterate for each country and create a Konva element for it. So we’ll have hundreds of them here.
- Note how Konva handles the mouseover event.
Finally, the markers with animation:
- Very similar to the circle case, but with a Star element in this case.
- The animation is a rotation in this case. But the color can be changed too, the size, etc.
- I checked using a path instead of a star and a scale must be applied unless your icon has the proper size. Then, an animation that changes this size can’t be used easily. Also, the scale is difficult to find. There’s a width and height prop that isn’t used.
I’m very happy with the result. In not many hours, I could make a base for mapping in an easy way with all the Svelte capabilities. Svelte is awesome!