Studio
GithubDribbbleTwitter
Richard Zimerman

August 14, 2017

Mapping Tutorial: Combining Victory Charts and React-Simple-Maps

Creating a beautiful map of Switzerland’s language distribution by canton using donut charts on a map.

Share
banner
Map of Switzerland's languages distribution

React-simple-maps is a react component library to help make SVG mapping with d3-geo, TopoJSON, and React easier. One of the strengths of using react-simple-maps is that it gives React full control over the DOM and does not treat the SVG map as a black box. This means that react-simple-maps can easily take advantage of the entire React ecosystem and all the good things that come with it.

React-simple-maps was partially inspired by the declarative api of Victory charts. If you haven’t heard about Victory, take a look on github, or see the examples and guides on their website.

The great thing about Victory charts is that it does not have to be used standalone, but that it can be embedded in already existing SVG containers. This makes it possible to combine Victory charts with any SVG visual, e.g. a map ;)

This tutorial will go over how to combine the two libraries and create the map shown on the cover of this article. If you are just interested in the final code, head on over to the github repo.

Here’s what this tutorial will cover:

  1. About the visualisation
  2. The repo and the app structure
  3. Setting up a basic map (TopoJSON, projection, style)
  4. The data
  5. Rendering <VictoryPie /> inside <Marker /> components

Let’s get started.

About the Visualisation

The visualisation for this tutorial shows the language distribution in Switzerland by canton. Even though Switzerland has a population of slightly more than 8 million, it has four official languages: German, French, Italian, and Romansh. This results in Switzerland being divided into three distinct linguistic regions, each with its own culture. Romansh constitutes a special case, as it is only spoken by 0.5% of the Swiss population, mostly in the canton of Graubünden.

While most cantons will identify predominantly with one language, there are some (Fribourg, Valais, Bern, and Graubünden) that have quite a diverse linguistic environment.

The Repo and App Structure

In order to simplify the setup for this tutorial, I will be using next.js to create an app scaffold. To get started, install these dependencies:

$ npm install react react-dom react-simple-maps victory next --save

In terms of scaffolding, here’s what the app structure will look like:

┬ app
├─┬ data
│ └── index.js
├─┬ pages
│ └── index.js
├─┬ static
│ └── cantons.json
├── node_modules
└── package.json

Don’t forget to add the next dev script in package.json.

// ./package.json
...
scripts: {
  "dev": "next",
  ...
}
...

Setting up a Basic Map

Every mapping visualisation starts with a good base map, and a solid TopoJSON file. If you are new to TopoJSON, check out the docs here. If you are new to creating your own TopoJSON files, you can check out my article on How to convert and prepare TopoJSON files for interactive mapping with d3.

The original shapefile used to create the TopoJSON file came from gadm.org.

The projection for the map of Switzerland was derived from this block, made by Mike Bostock. In order to use the Albers projection with react-simple-maps, we need to load in d3-geo. Since react-simple-maps uses d3-geo internally, it should already be installed. Using geoAlbers from d3-geo, you can easily create a custom projection, and pass it into the projection prop of <ComposableMap />.

// ./pages/index.js

import React, { Component } from "react"
import { geoAlbers } from "d3-geo"
import {
  ComposableMap,
  ZoomableGroup,
  Geographies,
  Geography,
} from "react-simple-maps"

const wrapperStyles = {
  width: "100%",
  maxWidth: 980,
  margin: "0 auto",
}

const geographyStyles = {
  fill: "#ECEFF1",
  stroke: "#607D8B",
  strokeWidth: 0.75,
  outline: "none",
}

const mapCenter = [8.3,46.8]

class MapWithVictory extends Component {
  projection(width, height) {
    return geoAlbers()
      .rotate([0,0])
      .center([mapCenter[0], mapCenter[1]])
      .scale(14000)
      .translate([width / 2, height / 2])
  }
  render() {
    return (
      <div>
        <ComposableMap
          projection={this.projection}
          width={980}
          height={551}
          style={{ width: "100%", height: "auto" }}
          >
          <ZoomableGroup
            center={[-mapCenter[0], -mapCenter[1]]}
            disablePanning
            >
            <Geographies geographyUrl="/static/cantons.json">
              {(geographies, projection) =>
                geographies.map((geography, i) =>
                  <Geography
                    key={i}
                    geography={geography}
                    projection={projection}
                    style={{
                      default: geographyStyles,
                      hover: geographyStyles,
                      pressed: geographyStyles,
                    }}
                  />
              )}
            </Geographies>
          </ZoomableGroup>
        </ComposableMap>
      </div>
    )
  }
}

export default MapWithVictory

You can now run the app. Just type $ npm run dev in the terminal, and go to http://localhost:3000.

The Data

The dataset for this tutorial comes from the Swiss Federal Statistics Office. In order to not mess with asynchronous data loading, this small dataset can be easily converted into a javascript object, and loaded in as a normal script. Here’s an excerpt:

// ./data/index.js
export const cantons = [
  {
    id: 25,
    canton: "Zurich",
    coordinates: [ 8.6356, 47.3595 ],
    languages: [
      { name: "German", value: 83.1 },
      { name: "French", value: 3 },
      { name: "Italian", value: 5.9 },
      { name: "Romansh", value: 0.4 },
    ]
  },
...

The data can now be easily imported into the app:

// ./pages/index.js
import { cantons } from "../data"
...

Rendering Victory Charts As Markers

The final step is now to use the data to render out react-simple-maps <Marker /> components and render <VictoryPie /> charts into those markers.


// ./pages/index.js

import React, { Component } from "react"
import { geoAlbers } from "d3-geo"
import {
  ComposableMap,
  ZoomableGroup,
  Geographies,
  Geography,
  Markers,
  Marker,
} from "react-simple-maps"
import { VictoryPie } from "victory"

import { cantons } from "../data"

const wrapperStyles = { /* ... */ }
const geographyStyles = { /* ... */ }
const mapCenter = [8.3,46.8]

class MapWithVictory extends Component {
  constructor() {
    super()
    this.state = {
      cantons: [],
    }
  }

  componentDidMount() {
    this.setState({
      cantons: cantons,
    })
  }

  projection(width, height) { /* ... */ }

  render() {
    return (
      <div>
        <ComposableMap
          projection={this.projection}
          width={980}
          height={551}
          style={{ width: "100%", height: "auto" }}
          >
          <ZoomableGroup
            center={[-mapCenter[0], -mapCenter[1]]}
            disablePanning
            >
            <Geographies geographyUrl="/static/cantons.json">
              { /* ... */ }
            </Geographies>
            <Markers>
              {
                this.state.cantons.map((canton, i) => 
                  <Marker
                    key={i}
                    marker={canton}
                    style={{
                      default: { outline: "none" },
                      hover: { outline: "none" },
                      pressed: { outline: "none" },
                    }}
                    >
                    <g transform="translate(-15,-15)">
                      <circle
                        cx={20}
                        cy={20}
                        r={21}
                        fill="transparent"
                        stroke="#607D8B"
                      />
                      <circle
                        cx={20}
                        cy={20}
                        r={9}
                        fill="transparent"
                        stroke="#607D8B"
                      />
                      <VictoryPie
                        standalone={false}
                        width={40}
                        height={40}
                        padding={0}
                        innerRadius={10}
                        style={{
                          labels: { fill: "transparent" },
                          data: { stroke: "#ECEFF1" },
                        }}
                        data={[
                          { x: null, y: canton.language[0].value, fill: "#FF5722" },
                          { x: null, y: canton.language[1].value, fill: "#00BCD4" },
                          { x: null, y: canton.language[2].value, fill: "#FFC107" },
                          { x: null, y: canton.language[3].value, fill: "#8BC34A" },
                        ]}
                      />
                    </g>
                  </Marker>                      
                )
              }
            </Markers>
          </ZoomableGroup>
        </ComposableMap>
      </div>
    )
  }
}

export default MapWithVictory

Note: The two <circle /> elements added on lines 67-68, are just for decorative purposes.

Conclusion

This tutorial covered how to use react-simple-maps together with Victory charts to create a nice little map of Switzerland’s language distribution by canton. The two libraries play well together, so give it a try. Thanks for reading, and happy mapping! ;)

Resources

For the complete code example, see this github repo. For more information on declarative mapping, check out How to create pure react SVG maps with topojson and d3-geo. For information on how to convert shapefiles into TopoJSON to use with react-simple-maps, or d3, check out How to convert and prepare TopoJSON files for interactive mapping with d3. More information about react-simple-maps More information about Victory.

Get our latest articles on design, UX, data visualisation, and code in your inbox.

Have a project in mind? Get in touch and let's make something cool. 

hello@zcreativelabs.comGet in touch

Newsletter

Sign up to get our latest articles on design, data visualization, and code directly in your inbox.

z creative labs GmbH
Sihlquai 131, 8005 Zurich
Switzerland

GithubDribbbleTwitterEmail

© 2020 z creative labs GmbH