D3js como pintar un mapa

Ahora que tenemos los mapas en formato topojson, vamos a pintarlos en nuestro sitio web.

Lo que haremos:

  • Cargaremos el mapa en nuestro proyecto, en este caso en formato topojson para ahorrar ancho de banda.
  • Una vez que lo tengamos en cliente, lo convertiremos a formato geojson.
  • Elegiremos una proyección, es decir: si queremos pintar el mapa en un plano, o simulando un globo terráqueo, etc...
  • Lo pintamos.
  • Y lo centraremos en nuestra área de dibujo.

Manos a la obra

  • Como punto de partida tenemos un proyecto desarrollado con TypeScript y con las librerías de D3js ya instaladas (si buscas una introducción a esta configuración puedes ver nuestro vídeo instalando d3js).

  • Vamos a trabajar con este proyecto 00-boiler, nos copiamos el contenido de esa carpeta y ejecutamos npm install.

npm install
  • Cuando trabajamos con mapas y D3js, lo haremos con dos formatos:

    • Nos hace falta un formato optimizado que ocupe poco para hacer que nuestro sitio web no sea pesado, en este caso TopoJson está muy bien, porque entre otras cosas, nos ahorra almacenar dos veces los arcos que forman las fronteras entre, por ejemplo, paises.

    • Una vez que tenemos el fichero en cliente, tenemos que pasarlo a GeoJson para que d3 vaya pintando los paths que toquen.

Vamos a instalar el conversor de topojson a geojson y sus typings de typescript.

npm install topojson-client --save
npm install @types/topojson-client --save-dev
  • El siguiente paso es poner los mapas en nuestro proyecto web, vamos a optar por una opción fácil, copiamos lo ficheros json que hemos obtenido en el paso anterior del proyecto de Martín Gonzalez y los importamos directamente.

En concreto nos hemos descargado el mapa de España clasificado por regiones, que directamente nos lo da en formato TopoJson, y lo tenemos volcado a la ruta src/mapas

Dependiendo del proyecto, nos podríamos plantear colgar estos ficheros como recursos estáticos, o alojarlos en una CDN y descargarlos por una uri en vez de importarlos (la importación nos va a añdir peso a nuestro bundle).

  • Vamos a cargar estos mapas usando require, y para que no se nos queje TypeScript vamos a instalar los typings de node:
npm install @types/node --save-dev
  • Importamos el conversor de TopoJson a GeoJson y cargamos el mapa
 import * as d3 from "d3";
+ import * as topojson from "topojson-client";
+ const mapatopojson  = require('./mapas/comunidades_autonomas.json');
  • Ahora nos toca definir que proyección usar para pintar el mapa, elegiremos la geoMercator, las proyecciones nos permiten pintar el mapa en un plano, o como si estuviera en la bola del mundo, o.... spoiler alert, nos permite recolocar ciertas islas en un sitio más cercano a la península.
+ const miProyeccion = d3.geoMercator();

  • Vamos a crear un generador de Path para dibujar el mapa, le indicamos la proyección que vamos a usar.
const miProyeccion = d3.geoMercator();
+  const geoPath = d3.geoPath().projection(miProyeccion);
  • Convertimos el fichero TopoJson a GeoJson:
   const geoPath = d3.geoPath().projection(miProyeccion);
+  const mapageojson = topojson.feature(
+    mapatopojson,
+    mapatopojson.objects.autonomous_regions
+  );

Y seguro que te surge la siguiente duda, ¿de dónde hemos sacado mapatopojson.objects.autonomous_regions? si abrimos el fichero ./src/mapas/comunidades_autonomas.json y lo inspeccionamos veremos esta información.

Vamos a pintar el mapa, los pasos que vamos a seguir son los siguientes:

  • Creamos una agrupación lógica con los path que forman cada comunidad autónoma.
  • Le indicamos de donde sacar los datos de cada región.
  • Entramos en modo inserción.
  • Añadimos un path por cada región.
  • Le decimos que para los datos que le vengan, aplique la función geoPath que hemos creado anteriormente (esta función es la que tiene la proyección aplicada).
svg
  .selectAll("path")
  .data(mapageojson["features"])
  .enter()
  .append("path")
  .attr("d", geoPath as any);
  • Vale, se ve algo, pero no muy bien... tenemos que darle escala y centrar el mapa, para ello podemos usar la función fitSize que ofrecen la proyección de mapas.
const mapageojson = topojson.feature(
  mapatopojson,
  mapatopojson.objects.autonomous_regions
);

+ miProyeccion.fitSize(
+  [chartDimensions.width, chartDimensions.height],
+  mapageojson
+ );

También podrías centrarlo manualmente usando los atributos scale y translate pero ésto es un proceso un poco doloroso.

Hey ya se ve el mapa, ¿y si le damos un poco de color?

  • Vamos añadir una hoja de estilos.

./src/styles.css

.contorno {
  stroke-width: 1;
  stroke: #ffffff;
  fill: #6cadc6;
}
  • Vamos añadir los estilos a nuestra configuración de webpack (si estás usando en tu proyecto CSS modules o CSS in JS podrías importarlo directamente en el fichero js).

./wepback.config.js

entry: {
-  app: "./index.ts",
+  app: ["./styles.css", "./index.ts"],
},

Y vamos a darle a cada región ese estilo:

svg
  .selectAll("path")
  .data(mapageojson["features"])
  .enter()
  .append("path")
+  .attr("class", "contorno")
  .attr("d", geoPath as any);

También podemos, por ejemplo, reaccionar cuando pinchemos en una región:

svg
  .selectAll("path")
  .data(mapageojson["features"])
  .enter()
  .append("path")
  .attr("class", "contorno")
  .attr("d", geoPath as any);
+  .on("click", (m, d: any) => {
+    alert(d.properties.NAMEUNIT);
+  });

¿De donde hemos sacado este NAMEUNIT? del JSON del mapa, también podríamos haber puesto un breakpoint en el alert y haber inspeccionado el valor del parametro d.

Ahora cuando pinchemos en una región nos saltará un alert indicando el nombre de la región en la que hemos pinchado.

¿Te apuntas a nuestro máster?

Si te ha gustado este ejemplo y tienes ganas de aprender Front End guiado por un grupo de profesionales ¿Por qué no te apuntas a nuestro Máster Front End Online Lemoncode? Tenemos tanto edición de convocatoria con clases en vivo, como edición continua con mentorización, para que puedas ir a tu ritmo y aprender mucho.